函数
定义
关键字func
用于定义函数。
- 无须前置声明。
- 不支持命名嵌套类型(nested)。
- 不支持同名参数重载(overload)。
- 不支持默认参数。
- 支持不定长变参。
- 支持多返回值。
- 支持命名返回值。
- 支持命名函数和闭包。
函数属于第一类对象,具备相同签名的函数视作同一类型。
> 第一类对象指可在运行期间创建,可做函数参数或返回值,可存入变量的实体。
函数只能判断是否为nil,不支持其他比较。
从函数返回局部变量指针是安全的,编译器会通过逃逸分析来决定是否在堆上分配内存。
##参数
函数调用时,必须按照签名顺序传参,以_
命名的参数也不能省略。
在参数列表里,相邻的同类型参数可以合并。
func test(x,y int,s string,_ bool) *int{
return nil
}
func main(){
test(1,2,"abc")//error:not enough arguments in call to test
}
形参是指函数定义中的参数,实参则是函数调用时所传递的参数。形参类似函数局部变量,而实参则是函数外部对象。
在函数调用前,会为形参和返回值分配内存空间,并将实参拷贝给形参内存。
func test(x *int) {
fmt.Printf("pointer: %p,target: %v\n", &x, x) //输出形参x的地址
}
func main() {
a := 100
p := &a
fmt.Printf("pointer: %p,target: %v\n", &p, p) //输出实参p的地址
test(p)
}
output
pointer: 0xc04206a018,target: 0xc04204c080
pointer: 0xc04206a028,target: 0xc04204c080
从输出结果来看,尽管形参和实参都指向同一目标,但是指针传递依然被复制。所以可以认为值传递和指针传递都是一种值拷贝传递。 如果函数参数过多,可以将其重构为一个结构体,也算变相实现可选参数和命名的功能。
变参
变参本质上是一个切片,只能接收一到多个同类型参数,且必须放在列表尾部。
func test(s string, a ...int) {
fmt.Printf("%T, %v\n", a, a)
}
func main() {
test("abc", 1, 2, 3, 4)
}
output
[]int, [1 2 3 4]
切片作为变参时,必须进行展开操作。如果是数组,先将其转换为切片。
func test(a ...int) {
fmt.Println(a)
}
func main() {
a := [3]int{10, 20, 30}//a是数组
test(a[:]...)//将数组转换为slice后展开
}
func test(a ...int) {
fmt.Println(a)
}
func main() {
a := []int{10, 20, 30}//a是切片
test(a...)
}
因为变参是切片,所以参数复制的只是切片自身,并不包括底层数组。
返回值
有返回值的额函数,必须有明确的return
语句,除非有panic
或者是无break
的死循环。
错误写法:
func test(x int) int {
if x>0{
return 1
} else if x<0{
return -1
}
//error:missing return at the end of function
}
正确写法:
func test(x int) int {
var a int
if x > 0 {
a = 1
} else if x < 0 {
a = -1
}
return a
}
函数支持多返回值,例如常见的error
模式:
func div(x, y int) (int, error){
if y==0{
return 0,errors.New("division by zero")
} else {
return x/y,nil
}
}
命名返回值
命名返回值和命名参数一样,也可以作为局部变量来使用,最后由return
隐式返回。
func div(x, y int) (z int, err error) {
if y == 0 {
err = errors.New("division by zero")
} else {
z = x / y
err = nil
}
return
}
参数返回时,需要对所有命名值全部返回。 ##匿名函数 匿名函数是没有定义名字符号的函数。 匿名函数可以直接调用,保存到变量,作为参数或返回值。 直接使用:
func main(){
func (s string) {
println(s)
} ("hello world")
}
赋值给变量:
func main(){
add := func (x, y int) int {
return x+y
}
println(add(1,2))
}
作为参数:
func test(f func()){
f()
}
func main(){
test(func() {
println("hello world")
})
}
作为返回值:
func test() func(int,int) int {
return func (x,y int) int {
return x+y
}
}
func main() {
add := test()
println(add(1,2))
}
普通函数和匿名函数都可作为结构体字段,或经通道传递。
闭包
闭包是函数和引用环境的组合体。 闭包直接引用原环境变量。 闭包延迟求值的特性:
func test() []func() {
var s []func()
for i:=0;i<2;i++{
s = append(s, func() {
println(&i,i)
})
}
return s
}
func main(){
for _,f := range test() {
f()
}
}
output:
0xc042040000 2
0xc042040000 2
因为for
循环复用局部变量i
,每次添加匿名函数时引用的是同一变量。添加操作仅仅是将匿名函数放入列表,并未执行。所以执行的时候读取的是环境变量i
最后一次循环时的值。
解决方法是每次使用不同的环境变量或者传参复制,让各自闭包环境不同。
func test() []func() {
var s []func()
for i := 0; i < 2; i++ {
x := i
s = append(s, func() {
println(&x, x)
})
}
return s
}
func main() {
for _, f := range test() {
f()
}
}
output:
0xc042008038 0
0xc042008040 1
延迟调用
defer
向当前函数注册,等当前函数结束前才被执行。延迟调用常用于资源释放,解除锁定,以及错误处理等操作。
多个defer
按照FILO。
return
和panic
都会终止当前函数,引发defer
。return
执行前会先更新返回值。
func test() (z int) {
defer func() {
println("defer:",z)
z+=100//修改命名返回值
}()
return 100//实际执行次序 z=100,call defer return z
}
func main(){
println("test:",test())
}
性能
延迟调用花费代价很大,此过程包括注册、调用等操作,还有额外的缓存开销。所以性能要求高压力大的算法,应避免使用延迟调用。
错误处理
error
官方推荐做法是返回error
状态。
标准库将error
定义为接口类型,以便实现自定义错误类型。
type error interface {
Error() string
}
错误变量通常以err
作为前缀,且字符串内容全部小写,没有结束标点,以便嵌入到其他格式化字符串中输出。
var errDivByZero = errors.New("division by zero")
func div(x, y int) (int, error) {
if y == 0 {
return 0, errDivByZero
}
return x / y, nil
}
func main() {
z, err := div(5, 0)
if err == errDivByZero {
log.Fatalln(err)
}
println(z)
}
某些时候需要自定义错误类型。
type DivErr struct {
x, y int
}
func (DivErr) Error() string {
return "division by zero"
}
func div(x, y int) (int, error) {
if y == 0 {
return 0, DivErr{x, y}
}
return x / y, nil
}
func main() {
z, err := div(5, 0)
if err != nil {
switch e := err.(type) {
case DivErr:
fmt.Println(e, e.x, e.y)
default:
fmt.Println(e)
}
log.Fatalln(err)
}
println(z)
}
panic,recover
func panic(v interface{})
func recover() interface{}
panic/recover
在使用上更接近try/catch
结构化异常。他们是内置函数而非语句。panic
会立即中断当前 函数流程,执行延迟调用。而在延迟调用中,recover
可捕获并返回panic
提交的错误对象。
func main() {
defer func() {
if err := recover(); err != nil {//捕获错误
log.Fatalln(err)
}
}()
panic("i am dead")//引发错误
panic("exit")//不会执行
}
无论是否执行recover
,所有延迟调用都会被执行。但是中断性错误会沿调用堆栈向外传递,要么被外层捕获,要么导致进程崩溃。
func test() {
defer println("test.1")
defer println("test.2")
panic("i am dead")
}
func main() {
defer func() {
log.Println(recover())
}()
test()
}
output
test.2
test.1
i am dead
连续调用panic
,仅最后一个会被recover
捕获。
func main() {
defer func() {
for {
if err := recover(); err != nil {
log.Println(err)
} else {
log.Fatalln("fatal")
}
}
}()
defer func() {
panic("you are dead") // 类似重新抛出异常( rethrow)
}() // 可先 recover捕获,包装后重新抛出
panic("i am dead")
}
output
you are dead
fatal
recover
必须在延迟调用函数中才执行能正常工作。
func catch() {
log.Println("catch ",recover())
}
func main(){
defer log.Println(recover())
defer catch()
panic("i am ")
}
output
catch i am
nil
func catch() {
log.Println("catch ", recover())
}
func main() {
defer catch()
defer log.Println(recover())
panic("i am ")
}
output
nil
catch i am