[Go语言]函数介绍
Posted jiangwei0512
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Go语言]函数介绍相关的知识,希望对你有一定的参考价值。
函数
函数是Go语言中的“第一公民”,具体表现:
- 函数是一种类型,函数类型变量可以像其它类型变量一种使用,可以作为其它函数的参数或返回值,也可以直接调用运行;
- 函数支持多值返回;
- 支持闭包;
- 支持可变参数。
基本概念
函数格式:
func funcName(param-list) (return-list) {
func-body
}
参数列表中的()
是必须的,但是参数可以是空;返回值列表中的()
在没有返回值、只有一个非命名返回值的情况下可以不需要;{
必选在同行的行尾。
函数特性:
- 函数的首字母大小写决定该函数在其它包的可见性:大写时其它包可见(一个例子就是
fmt.Printf()
),小写时只有相同的包可以访问。 - 函数可以没有输入参数,也可以没有返回值:
func func1() {
// do nothing
}
- 多个相邻的相同类型的参数可以使用简写模式:
func func2(a, b int) {
// do nothing
}
- 支持有名的返回值:
func add(a, b int) (value int) {
value = a + b
return // return还是要的,但是不用写return X
}
- 不支持默认值参数。
- 不支持函数重载。
- 不支持函数嵌套,严格地说不支持命名函数的嵌套定义,但是支持嵌套的匿名函数。
- 支持多个值返回:
func func3(a, b int) (int, int) {
a++
b++
return a, b
}
- 函数实参到形参的传递永远是值拷贝。
- 支持不定参数:
func sum(arr ...int) (sum int) {
for _, v := range arr {
sum += v
}
return
}
arr ...int
就是不定参数,arr是参数名,int是参数类型,...
不能少。关于不定参数:
- 所有的不定参数类型必须是相同的;
- 不定参数必须是函数的最后一个参数;
- 不定参数名在函数体内相当于切片;
- 切片可以作为参数传递给不定参数,切片名后要加上
...
:
func main() {
s := []int{1, 2, 3}
fmt.Printf("%d\\n", sum(s...)) // s之后的...不能少
}
- 形参为不定参数的函数和形参为切片的函数类型不相同。
匿名函数
函数类型又叫函数签名
,%T
可以用来打印函数签名:
func main() {
fmt.Printf("%T\\n", sum) // 打印:func(...int) int
}
两个函数类型相同的条件是:拥有相同的形参列表和返回值列表。
可以使用type
定义函数类型:
type Op func(int, int) int // 定义了一个函数类型Op
匿名函数可以看作函数字面值。
func wrapper(op string) func(int, int) int {
switch op {
case "add":
return func(a, b int) int {
return a + b
}
case "sub":
return func(a, b int) int {
return a - b
}
default:
return nil
}
}
这里的返回值就是匿名函数。
defer
defer
是Go语言关键字,它用来注册多个延迟调用,这些调用按照FIFO的顺序在函数返回(return
,如果是os.Exit()
则不会调用)前调用。
defer
后接函数或者方法,不能是语句。示例:
func main() {
defer func() {
fmt.Printf("First defer\\n")
}()
defer func() {
fmt.Printf("Second defer\\n")
}()
fmt.Printf("Call first\\n")
return
}
注意函数最后的()
不能少。打印结果:
Call first
Second defer
First defer
defer
常用于保证一些资源最终一定会被回收或释放。
defer
的函数的实参在注册时通过值拷贝传递进去。
defer
语句的位置不当,有可能导致panic,一般defer
语句放在错误检查语句(if err != nil
)之后。
defer
的一些副作用:
defer
会导致资源释放延迟,尽量不要放在循环语句中;defer
相对于普通函数调用有一定的性能损耗。
defer
函数中最好不要对有名返回值参数进行操作,否则会引发匪夷所思的结果。
闭包
闭包 = 函数 + 引用的外部环境
。外部环境指的是包含闭包的函数的局部变量(入参和内部变量)和全局变量。
闭包对外部环境变量是直接引用,所以在闭包中的修改会同步到外部。
package main
func fa(a int) func(i int) int {
return func(i int) int {
println(&a, a) // a是外部环境变量,所以这里返回的是闭包
a = a + i
return a
}
}
func main() {
f := fa(1) // f是一个闭包,这里的1其实就是a
g := fa(1) // g也是一个闭包,也有一个a,但是跟f的a不是同一个
println(f(1)) // 这里的1是i,是闭包的参数,闭包里面打印0xc000067f60 1,这里打印2
println(f(1)) // 闭包里面打印0xc000067f60 2,这里打印3
println(g(1)) // 闭包里面打印0xc000067f58 1,这里打印2
println(g(1)) // 闭包里面打印0xc000067f58 2,这里打印3
}
以上是包含闭包的函数的局部变量被修改的例子,下面是对全局变量修改的例子:
var a = 1
func fb() func(i int) int {
return func(i int) int {
println(&a, a) // a也是外部环境变量,所以这里返回的是闭包
a = a + i
return a
}
}
func main() {
f := fb()
g := fb()
println(f(1)) // 闭包里面打印0x940180 1,这里打印2
println(f(1)) // 闭包里面打印0x940180 2,这里打印3
println(g(1)) // 闭包里面打印0x940180 3,这里打印4
println(g(1)) // 闭包里面打印0x940180 4,这里打印5
}
这里可以看到全局变量a
的值会一直增加。
使用闭包是为了减少全局变量,所以闭包引用全局变量不是好的编程方式。
同一个函数返回的多个闭包共享该函数的局部变量:
func fc(base int) (func(int) int, func(int) int) {
println(&base, base)
add := func(i int) int {
base += i
println(&base, base)
return base
}
sub := func(i int) int {
base -= i
println(&base, base)
return base
}
return add, sub
}
func main() {
f, g := fc(10) // 0xc000067f40 10
println(f(1)) // 闭包内打印0xc000067f40 11,这里打印11
println(g(1)) // 闭包内打印0xc000067f40 10,这里打印10
}
这里可以看到base
的值先增1后减1,且是是通过不同的闭包完成的。
panic和recover
panic()
和recover()
是Go语言的内置函数(builtin\\builtin.go):
// The panic built-in function stops normal execution of the current
// goroutine. When a function F calls panic, normal execution of F stops
// immediately. Any functions whose execution was deferred by F are run in
// the usual way, and then F returns to its caller. To the caller G, the
// invocation of F then behaves like a call to panic, terminating G's
// execution and running any deferred functions. This continues until all
// functions in the executing goroutine have stopped, in reverse order. At
// that point, the program is terminated with a non-zero exit code. This
// termination sequence is called panicking and can be controlled by the
// built-in function recover.
func panic(v interface{})
// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, or if the argument supplied to panic was nil, recover returns
// nil. Thus the return value from recover reports whether the goroutine is
// panicking.
func recover() interface{}
panic
的参数是一个空接口类型interface{}
,所以任意类型的变量都可以传递给panic()
函数。
引发panic
的情况:
- 程序主动调用
panic
函数:- 程序无法正常执行下去时调用;
- 调试程序时调用;
- 程序产生运行时错误时由运行时检测并抛出。
发生panic
后,会逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈,直到被recover
捕获或运行到最外层函数而退出。
recover()
和defer
一起使用,recover()
只有在defer
后面的函数体内被直接调用才能捕获panic
终止异常。
package main
import "fmt"
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
defer func() {
panic("first defer panic")
}()
defer func() {
panic("second defer panic")
}()
panic("main body panic")
}
可以有连续多个panic
被抛出,连续多个panic
的场景只能出现在延迟调用里面,否则不会出现多个panic
被抛出的场景,但是只有最后一个panic
能被捕获,所以上面程序的打印结果是:
first defer panic
函数不能捕获内部新启动的goroutine
所抛出的panic
。
错误处理
Go语言内置错误接口类型error
(builtin\\builtin.go):
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
错误处理的最佳实践:
- 在多个返回值的函数中,
error
通常作为函数最后的一个返回值; - 如果一个函数返回
error
类型变量,则先用if语句处理error != nil
,正常逻辑放到if
语句块的后面; defer
语句应该放到error
判断的后面,不然有可能产生panic
;- 在错误逐级向上传递的过程中,错误信息应该不断地丰富和完善,而不是简单地抛出下层调用的错误。
以上是关于[Go语言]函数介绍的主要内容,如果未能解决你的问题,请参考以下文章