go中的函数无须前置声明
go中的函数不支持默认参数
go中的函数不支持重载
函数定义
func name(parameter-list) (result-list) {
// body
}
func test(x, y int32) int32 {
// body
}
实参是按值传递的,函数接收到的是每一个实参的副本,修改函数的形参并不会影响到调用者的提供的实参,但是提供的实参包含了引用类型,比如:指针,slice,map,函数或者通道,那么就可以修改实参
go中的函数是第一类对象
go 中的函数对象属于第一类对象(first-class object 指可在运行期创建,可用作函数参数或返回值,可存入变量的实体),具备相同签名(参数及返回值)的视作同一类型
func hello() {
println("hello world")
}
func exec (f func) {
f()
}
func main() {
f := hello
exec(f)
}
-
定义函数类型
可以使用type
定义函数类型type FormatFunc(string, ...interface{}) (string, error) func format(f FormatFunc, s string, a ...interface{}) (string, error) { return f(s, a...) }
-
函数也只能判断其为
nil
,不支持其他比较操作func a() { } func b() { } func main() { fmt.Println(a == nil) // 正确 fmt.Println(a == b) // 编译错误 }
包级别函数
Go 语言没有用 public、private 这样的修饰符来修饰函数是公有还是私有,而是通过函数名称的大小写来代表,这样省略了烦琐的修饰符,更简洁:
- 函数名称首字母小写代表私有函数,只有在同一个包中才可以被调用
- 函数名称首字母大写代表公有函数,不同的包也可以调用
返回局部变量的指针是安全的
func test() *int {
a := 100
return &a
}
func main() {
var a *int = test()
}
从函数返回局部变量的指针是安全的,编译器会通过逃逸分析来决定该变量是否在堆上分配内存。
变长参数
在参数列表最后的类型名称之前使用省略号...
表示声明一个变长参数,调用整个函数的时候可以传递该类型任意数目的参数
func sum(vals ...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
sum()
sum(1, 2, 3)
但是如果想要传递一个切片,则必须显示写下省略号,将切片展开。
values := []int{1, 2, 3, 4}
sum(values...)
多返回值
go 函数还支持多返回值,多返回值需要用()
括起来
func foo() (bool, error) {
...
}
命名返回值
go 函数还支持对返回值进行命名:它可以让函数更加清晰,同时也会改善帮助文档和代码编辑器提示
func paging(sql string, index int) (count int, page int, err error) {
...
return
}
但是命名返回值可能会不被不同层级的同名变量遮蔽:
func add(x, y int) (z int) {
{
z := x + y // 这里的 z 遮蔽了命名返回值
return
}
return
}
什么时候使用命名返回值,什么时候不需要?
- 如果返回值类型能够明确表明其含义,就尽量不要对其命名
func newUser() (*User, error)
匿名函数
strings.Map(fybc(r rune) rune { return r + 1 }, "ABC") // 输出 BCD
函数字面量就像函数声明,但func
关键字后面没有函数的名称,它是一个表达式,它的值叫作匿名函数,类似于其他语言的 lambda 表达式
func squares() func() int {
var x int = 0
return func() int {
x++
return x*x
}
}
f := squares()
fmt.Println(f()) // 输出 1
fmt.Println(f()) // 输出 4
fmt.Println(f()) // 输出 9
上面的例子演示了函数变量不仅仅是一段代码还可以拥有状态,里层的匿名函数能够获取和更新外层函数的局部变量。
延迟调用 defer
一个defer
语句就是一个普通的函数或者方法调用,在调用之前加上,参数值会在注册时被复制并缓存起来,但实际调用会推迟到当前函数执行结束后才执行,且无论该函数是否正常执行完毕。defer
语句通常用于:
释放资源:
m.mutex.Lock()
defer m.mutex.Unlock()
流程控制:
var wg wait.Group
defer wg.Wait()
异常处理:
defer func() { recover() }()
使用defer
函数有以下注意:
-
defer
函数的参数在defer 语句出现时就已经确定func foo() { i := 0 defer fmt.Println(i) i++ return }
输出 i 的值为0
不过需要注意和匿名函数配合使用的区别:
func foo() { i := 0 defer func() { fmt.Println(i) }() i++ return }
输出 i 的值为1
-
defer
语句没有限制使用次数,执行的时候,以调用defer
语句顺序倒序进行。func main() { defer fmt.Println("a") defer fmt.Println("b") } // 输出: b a
-
defer
中可以更新命名返回值func test() (z int) { defer func() { fmt.Println("defer: ", z) z += 100 }() return 100 } func main() { fmt.Println("test: " test()) } // 输出: defer: 100 test: 200
说明,是先完成 z = 100,再执行defer中的
z+=100
,最后执行ret汇编指令。