Go(二)函数
自定义类型
两种用户定义的类型
- 结构类型
- 基于一个已有的类型,将其作为新类型的类型说明
结构体变量定义和初始化
type struct_variable_type struct { member definition member definition ... member definition }
一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:
variable_name := structure_variable_type {value1, value2...valuen} // 或 variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
实例
package main import "fmt" type Person struct { name string sex byte age int } func main() { // 顺序初始化,必须全部初始化完整 var person Person = Person{"wang", 'f', 29} fmt.Println(person) // 部分初始化 person1 := Person{name: "zhao", sex: 'm'} fmt.Println(person1.name) }
赋值、比较和传参
使用.索引成员变量
// 部分初始化 person1 := Person{name: "zhao", sex: 'm'} fmt.Println(person1.name) person1.name = "li" fmt.Println(person1.name)
比较
只能使用 == 和 !=,不能使用 > < >= <=
// 结构体比较 person2 := Person{name: "zhao", sex: 'm', age: 18} person3 := Person{name: "zhao", sex: 'm', age: 18} person4 := Person{name: "zhao", sex: 'm', age: 181} fmt.Println(person2 == person3) // true fmt.Println(person3 == person4) // false
相同类型的结构体可以进行赋值
相同类型结构体:成员变量的类型、个数、顺序一致
var tmp Person
tmp = person1
fmt.Println(tmp.name)
因此函数体内部可以使用结构体传参,因为实参可以赋值给相同结构体类型的形参,值传递。几乎不用,内存消耗大,效率低。
func test(person Person) { println(person.name) } func main() { var temp Person test(temp) }
结构体指针
结构体指针变量定义和初始化
1.顺序初始化和部分初始化
var man *Person = &Person{"zhao", 'm', 18} fmt.Println(man.name) var man1 *Person = &Person{name: "zhao", sex: 'm'} fmt.Println(man1.name)
2.使用new
var man2 *Person = new(Person); man2.name = "zhao" man2.sex = 0 man2.age = 16
使用.指针索引成员变量
结构体变量的地址就是结构体首个元素的地址
fmt.Printf("&tmp = %p \n", &tmp)
fmt.Printf("&tmp.name = %p \n", &tmp.name)
结果
&tmp = 0xc000004500 &tmp.name = 0xc000004500
结构体指针的值就是结构体首个元素的地址
fmt.Printf("man2 = %p\n", man2) fmt.Printf("&man2.name = %p\n", &man2.name)
结果
man2 = 0xc00008e080 &man2.name = 0xc00008e080
结构体指针作为函数参数
import "fmt" type Person struct { name string sex byte age int } func test1(person * Person) { person.name = "wang" } func main() { fmt.Println("man2:", man2) test1(man2) fmt.Println("man2", man2) }
结果
man2: &{zhao 0 16} man2 &{wang 0 16}
发现通过指针变量可以修改结构体,传引用,使用频率高
也是实参拷贝值给形参,不过这次拷贝的是地址值
或
fmt.Println(person1.name) test1(&person1) fmt.Println(person1.name)
结果
li
wang
函数是一等公民
与其他主要编程语言的差异
1.可以有多个返回值
2.所有参数都是值传递
slice、map、channel会有传引用是错觉,如切片背后是数组,是一个数据结构,里面包含了指向对应数组的指针,数据结构被复制,指针操作的仍是同一块空间,感觉像是传引用
3.函数可以作为变量的值
4.函数可以作为参数和返回值
package fun_test import ( "fmt" "math/rand" "testing" "time" ) // 多个返回值 func returnMultiValues()(int, int) { return rand.Intn(10), rand.Intn(20) } // 函数可以作为参数和返回值 // 计算inner函数运行的时间 func timeSpent(inner func(op int)int) func(opt int) int { return func(n int) int { start := time.Now() ret := inner(n) fmt.Println("time spent:", time.Since(start).Seconds()) return ret } } func slowFun(op int)int{ time.Sleep(time.Second*1) return op } func TestFn(t *testing.T){ a, _ := returnMultiValues() t.Log(a) tsSF := timeSpent(slowFun) t.Log(tsSF(10)) }
结果:
=== RUN TestFn
time spent: 1.0000582
--- PASS: TestFn (1.00s)
func_test.go:32: 1
func_test.go:34: 10
PASS
可变参数及defer
可变参数会被转换为数组,通过数组变量来完成
func sum(ops ...int) int { s := 0 for _, op := range ops{ s += op } return s } func TestParam(t *testing.T) { s := sum(1, 2, 3, 4, 5) fmt.Println(s) }
=== RUN TestParam
15
--- PASS: TestParam (0.00s)
PASS
defer
func clear() { fmt.Println("clear resources") } func TestDefer(t *testing.T) { defer clear() fmt.Println("Start") panic("Fatal error") //defer仍会执行 println("Stop") }
=== RUN TestDefer
Start
clear resources
--- FAIL: TestDefer (0.00s)
panic: Fatal error [recovered]
panic: Fatal error
会发现出现了异常,clear()依然会执行,
异常之前的fmt.Println("Start")被执行
异常之后的fmt.Println("Stop")没有被执行