1、函数
1.1、函数声明
函数声明格式:
1 2 3 4
| func 函数名字(参数列表) (返回值列表){ // 函数体 return 返回值列表 }
|
注意:
- 函数名首字母小写为私有,大写为公有;
- 参数列表可以有0-多个,多参数使用逗号分隔,不支持默认参数;
- 返回值列表返回值类型可以不用写变量名
- 如果只有一个返回值且不声明类型,可以省略返回值列表与括号
- 如果有返回值,函数内必须有 return
Go中函数常见写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| func fn(){}
func fn1() (result int) { return 1 }
func fn2() (result int) { result = 1 return }
func fn3() (int, int, int) { return 1,2,3 }
func fn4() (a int, b int, c int) { 多个参数类型如果相同,可以简写为:a,b int a , b, c = 1, 2, 3 return }
|
1.2、值传递和引用传递
不管是值传递还是引用传递,传递给函数的都是 变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝。
一般来说,地址拷贝效率高,因为数据量小;而值拷贝 取决余拷贝的数据大小,数据越大,效率越低。
如果希望函数内的变量能修改函数外的变量,可以传入变量的地址 &
,函数内以指针的方式操作变量。
1.3、可变参数
可变参数变量是一个包含所有参数的切片。如果要在多个可变参数中传递参数,可以在传递时在可变参数变量中默认添加 ...
,将切片中的元素进行传递,而不是传递可变参数变量本身。
示例:对可变参数列表进行遍历
1 2 3 4 5 6 7 8 9 10 11
| func joinStrings(slist ...string) string { var buf bytes.Buffer for _, s := range slist { buf.WriteString(s) } return buf.String() }
func main() { fmt.Println(joinStrings("pig", " and", " bird")) }
|
示例:参数传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func rawPrint(rawList ...interface{}) { for _, a := range rawList { fmt.Println(a) } }
func print(slist ...interface{}) { rawPrint(slist...) }
func main() { print(1,2,3) }
|
1.4、匿名函数
匿名函数可以看做函数字面量,所有直接使用函数类型变量的地方都可以由匿名函数代替。匿名函数可以直接赋值给函数变量,可以当做实参,也可以作为返回值使用,还可以直接被调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| func main() {
a := 3 f1 := func(num int) { fmt.Println(num) } f1(a)
func() { fmt.Println(a) }() }
x, y := func(i,j int) (max,min int) { if i > j { max = i min = j } else { max = j min = i } return }(10,20) fmt.Println(x + ' ' + y)
|
1.5、函数类型
函数去掉函数名、参数名和{}后的结果即是函数类型,可以使用%T打印该结果。
两个函数类型相同的前提是:拥有相同的形参列表和返回值列表,且列表元素的次序、类型都相同,形参名可以不同。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func mathSum(a, b int) int { return a + b }
func mathSub(a, b int) int { return a - b }
type MyMath func(int, int) int
func Test(f MyMath, a , b int) int{ return f(a,b) }
|
通常可以把函数类型当做一种引用类型,实际函数类型变量和函数名都可以当做指针变量,只想函数代码开始的位置,没有初始化的函数默认值是nil。
2、Go函数特性总结
- 支持有名称的返回值;
- 不支持默认值参数;
- 不支持重载;
- 不支持命名函数嵌套,匿名函数可以嵌套;
- Go函数从实参到形参的传递永远是值拷贝,有时函数调用后实参指向的值发生了变化,是因为参数传递的是指针的拷贝,实参是一个指针变量,传递给形参的是这个指针变量的副本,实质上仍然是值拷贝;
- Go函数支持不定参数;
3、几个特殊函数
3.1、init 函数
Go 中,除了可以在全局声明中初始化实体,也可以在 init 函数中初始化。init 函数是一个特殊的函数,它会在包完成初始化后自动执行,执行优先级高于 main 函数,并且不能手动调用 init 函数,每一个文件可以有多个 init 函数,初始化过程会根据包的依赖关系顺序单线程执行。
1 2 3 4 5 6 7 8 9 10 11 12
| package main import ( "fmt" ) func init() { fmt.Println("init...") }
func main() { fmt.Println("main...") }
|
3.2、new 函数
new 函数可以用来创建变量。表达式 new(T)
将创建一个 T 类型的匿名变量,初始化为 T 类型的零值,然后返回变量地址,返回的指针类型为*T
:
1 2 3 4
| p := new(int) fmt.Println(*p) *p = 2 fmt.Println(*p)
|
new 函数还可以用来为结构体创建实例:
1 2 3
| type file struct { } f := new(file)
|
贴士:new 函数其实是语法糖,不是新概念,如下所示的两个函数其实拥有相同的行为。
1 2 3 4 5 6 7 8
| func newInt1() *int { return new(int) }
func newInt2() *int { var dummy int return &dummy }
|
注意:new
只是一个预定义函数,并不是一个关键字,所以new
也有可能会被项目定义为别的类型。
3.3、make 函数
make 函数经常用来创建切片、Map、管道:
1 2
| m1 := map[string]int{} m2 := make(map[string]int, 10)
|
上面展示了两种 map 的创建方式,其不同点是第一种创建方式无法预估长度,当长度超过了当前长度时,会引起内存的拷贝!!第二种创建方式直接限定了长度,这样能有效提升性能!