函数的基本形式
1 2 3 4 5 6 func argf (a int , b int ) { a = a + b } var x, y int = 3 , 6 argf(x, y)
形参是函数内部的局部变量,实参的值会拷贝给形参。
函数定义时的第一个的大括号不能另起一行。
形参可以有0个或多个。
参数类型相同时可以只写一次,比如argf(a,b int)。
在函数内部修改形参的值,实参的值不受影响。
如果想通过函数修改实参,就需要指针类型。
1 2 3 4 5 6 func argf (a, b *int ) { *a = *a + *b *b = 888 } var x, y int = 3 , 6 argf(&x, &y)
slice、map、channel都是引用类型,它们作为函数参数时其实跟普通struct没什么区别,都是对struct内部的各个字段做一次拷贝传到函数内部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func slice_arg_1 (arr []int ) { arr[0 ] = 1 arr = append (arr, 1 ) } func main () { arr := []int {8 } slice_arg_1(arr) fmt.Println(arr[0 ]) fmt.Println(len (arr)) }
关于函数返回值
可以返回0个或多个参数。
可以在func行直接声明要返回的变量。
return后面的语句不会执行。
无返回参数时return可以不写。
1 2 3 4 5 func returnf (a, b int ) (c int ) { a = a + b c = a return }
不定长参数实际上是slice类型。
1 2 3 4 5 6 7 8 9 10 func variable_ength_arg (a int , other ...int ) int { sum := a for _, ele := range other { sum += ele } fmt.Printf("len %d cap %d\n" , len (other), cap (other)) return sum } variable_ength_arg(1 ) variable_ength_arg(1 ,2 ,3 ,4 )
append函数接收的就是不定长参数。
1 2 3 4 5 arr = append (arr, 1 , 2 , 3 ) arr = append (arr, 7 ) arr = append (arr) slice := append ([]byte ("hello " ), "world" ...) slice2 := append ([]rune ("hello " ), []rune ("world" )...)
在很多场景下string都隐式的转换成了byte切片,而非rune切片,比如"a中"[1]是228而非"中"。
递归函数
1 2 3 4 5 6 func Fibonacci (n int ) int { if n == 0 || n == 1 { return n } return Fibonacci(n-1 ) + Fibonacci(n-2 ) }
匿名函数
函数也是一种数据类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func function_arg1 (f func (a, b int ) int , b int ) int { a := 2 * b return f(a, b) } type foo func (a, b int ) int func function_arg2 (f foo, b int ) int { a := 2 * b return f(a, b) } type User struct { Name string bye foo hello func (name string ) string } ch := make (chan func (string ) string , 10 ) ch <- func (name string ) string { return "hello " + name }
闭包
闭包(Closure)是引用了自由变量的函数,自由变量将和函数一同存在,即使已经离开了创造它的环境。闭包复制的是原对象的指针。
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 26 27 28 29 30 31 32 33 34 35 36 37 package mainimport "fmt" func sub () func () { i := 10 fmt.Printf("%p\n" , &i) b := func () { fmt.Printf("i addr %p\n" , &i) i-- fmt.Println(i) } return b } func add (base int ) func (int ) int { return func (i int ) int { fmt.Printf("base addr %p\n" , &base) base += i return base } } func main () { b := sub() b() b() fmt.Println() tmp1 := add(10 ) fmt.Println(tmp1(1 ), tmp1(2 )) tmp2 := add(100 ) fmt.Println(tmp2(1 ), tmp2(2 )) }
递归函数
函数内部调用函数自身的函数称为递归函数
使用递归函数最重要的三点:
递归就是自己调用自己
必须先定义函数的退出条件,没有退出条件,递归将称为死循环
go语言递归函数很可能会产生一大堆的goroutine,也很可能会出现栈空间内存溢出问题
阶乘
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" func main () { n := 5 r := a(n) fmt.Printf("r: %v\n" , r) } func a (n int ) int { if n == 1 { return 1 } else { return n * a(n-1 ) } }
运行结果:
r: 120
斐波那契序列
计算公式 f(n)=f(n-1)+f(n-2)
且f(2)=f(1)=1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { i := f(6 ) fmt.Printf("i: %v\n" , i) } func f (n int ) int { if n == 1 || n == 2 { return 1 } return f(n-1 ) + f(n-2 ) }
运行结果:
i: 8
延迟调用defer
defer用于注册一个延迟调用(在函数返回之前调用)。
defer典型的应用场景是释放资源,比如关闭文件句柄,释放数据库连接等。
如果同一个函数里有多个defer,则后注册的先执行。
defer后可以跟一个func,func内部如果发生panic,会把panic暂时搁置,当把其他defer执行完之后再来执行这个。
defer后不是跟func,而直接跟一条执行语句,则相关变量在注册defer时被拷贝或计算。
1 2 3 4 5 6 7 func basic () { fmt.Println("A" ) defer fmt.Println(1 ) fmt.Println("B" ) defer fmt.Println(2 ) fmt.Println("C" ) }
1 2 3 4 5 6 7 8 9 10 11 func defer_exe_time () (i int ) { i = 9 defer func () { fmt.Printf("first i=%d\n" , i) }() defer func (i int ) { fmt.Printf("second i=%d\n" , i) }(i) defer fmt.Printf("third i=%d\n" , i) return 5 }
异常处理
go语言没有try catch,它提倡返回error。
1 2 3 4 5 6 7 8 9 func divide (a, b int ) (int , error ) { if b == 0 { return -1 , errors.New("divide by zero" ) } return a / b, nil } if res, err := divide(3 , 0 ); err != nil { fmt.Println(err.Error()) }
Go语言定义了error这个接口,自定义的error要实现Error()方法。
1 2 3 4 5 6 7 8 9 type PathError struct { path string op string createTime string message string } func (err PathError) Error() string { return err.createTime + ": " + err.op + " " + err.path + " " + err.message }
何时会发生panic:
运行时错误会导致panic,比如数组越界、除0。
程序主动调用panic(error)。
panic会执行什么:
逆序执行当前goroutine的defer链(recover从这里介入)。
打印错误信息和调用堆栈。
调用exit(2)结束整个进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func soo () { fmt.Println("enter soo" ) defer func () { if err := recover (); err != nil { fmt.Printf("soo函数中发生了panic:%s\n" , err) } }() fmt.Println("regist recover" ) defer fmt.Println("hello" ) defer func () { n := 0 _ = 3 / n defer fmt.Println("how are you" ) }() }