type Op func(int, int) int
func calculator(a int, b int, op Op) int {
return op(a, b)
}
func add(a int, b int) int {
return a + b
}
func sub(a int, b int) int {
return a - b
}
func main() {
result := calculator(1, 2, add)
result2 := calculator(1, 2, sub)
fmt.Println(result, result2) // 3 -1
}
result3 := calculator(1, 2, func(a int, b int) int {
return a * b
})
// 甚至直接调用匿名函数
result4 := func(a int, b int) int {
return a / b
}(1, 2)
fmt.Println(result3, result4) // 2 0
type Person struct {
name string
age int
}
func modify(i int, s string, p Person) {
i += 1
s += "!"
p.age += 1
}
func main() {
i, s, p := 1, "hello", Person{
name: "foo",
age: 1,
}
modify(i, s, p)
fmt.Println(i, s, p) // 1 hello {foo 1}
}
但在处理 slice 和 map 时稍有不同:
func modMap(m map[int]string) {
m[0] = "hello"
m[1] = "world"
delete(m, 2)
}
func modSlice(s []int) {
for i, v := range s {
s[i] = v * 2
}
s = append(s, 10)
}
func main() {
m := map[int]string{
1: "foo",
2: "bar",
3: "baz",
}
modMap(m)
s := []int{
1, 2, 3,
}
modSlice(s)
fmt.Println(m, s) // map[0:hello 1:world 3:baz] [2 4 6]
}
[golang] 函数
Go 程序是从
main
函数开始的,上述
main
函数没有入参,也没有返回。函数的一般形式会包含一个入参列表以及返回类型,e.g.:当入参类型相同时,可省略类型只保留参数列表中最后一个入参的类型:
命名参数与可选参数
Go 中函数是不支持 named and optional 参数的,但可通过结构体来变相实现。
变参函数
但 Go 支持 Variadic parameters。可变的入参部分位于参数列表最后,在类型前面加
...
表示。常见的就是用来打印输出的fmt.Println()
。变参部分在接收到之后,其实是个 slice,因此,可以像操作 slice 一样操作它。同时,在传递时,也可以传递展开后的 slice。
函数的返回值
Go 函数支持返回多个值,返回类型中用逗号将各个返回类型分隔。
约定俗成地,最后一个返回值为错误信息。
忽略返回值
如果不需要某个返回值,可使用
_
来忽略,但占位必需有。注意与
for-range
语句进行区分,如果我们不需要循环返回的值,除了使用_
来忽略,还可直接不书写这第二个变量的:命名返回
除了可返回多个值,还可为这些值指定名称。
声明返回值名称本质上是提前在函数作用域中声明了对应名称的变量,可在函数体中正常使用这些变量。
如果只想命名部分变量,其他返回可使用 _ 来代替。
需要注意,返回值的名称只约束函数,不约束使用的地方。即,调用后仍然可赋值给任意名称的变量:
另外,声明了返回变量,并不代表一定返回它,实际返回值仍以
return
语句为准,即,return 5
返回 5 而不是声明的返回变量。空返回
使用命名返回的情况下,
return
时可省略变量直接返回,因为return
后没跟任何东西,所以叫空返回。因为空返回不够直观,不建议使用。
作为值传递的函数
用过 JavaScript 肯定对于将函数作为普通值一样使用不陌生,可以用来赋值,可以用来传参。
Go 中的函数亦然。
匿名函数
接上面的例子,在传递函数时,可以不事先声明一个正式的函数,而是在用到的时候临时创建一个不带名字的匿名函数:
闭包
同 JavaScript,函数可以访问其定义处外层的变量,这些变量在外层作用域销毁时因为被当前函数保持,形成闭包,所以没销毁。典型的场景就是将函数作为回调进行传递时。
此时我们说
a
slice 被排序回调形成的闭包所持有。输出结果:
defer
defer
标识的语句在函数 return 作用域结束后执行,用于释放资源,做清理工作。一般,在创建了资源比如网络/文件 IO,应及时提供
defer
释放语句再开始其他业务。因为不管程序执行结果如何,defer
都会被执行,资源会得到正确的释放。多个
defer
的语句执行顺序是先后进先出,可以理解为,每次defer
将需要执行的语句压入任务栈,释放时从栈顶开始释放。Go 中函数是值传递
Go 中的函数都是值传递,意味着参数会被复制一份在函数体中被操作,原数据不受影响:
但在处理 slice 和 map 时稍有不同:
对于 map,函数体中所做的变更全部体现到了原数据上,而 slice,除了无法
append
,对其中元素的操作也是生效的。原因是 map 及 slice 本质上是指针,当通过这个指针操作其中元素时,操作的就是原数据。