函数的基本形式
//函数定义。a,b是形参
func argf(a int, b int) {
a = a + b
}
var x, y int = 3, 6
argf(x, y) //函数调用。x,y是实参
- 形参是函数内部的局部变量,实参的值会拷贝给形参。
- 函数定义时的第一个的大括号不能另起一行。
- 形参可以有0个或多个。
- 参数类型相同时可以只写一次,比如argf(a,b int)。
- 在函数内部修改形参的值,实参的值不受影响。
- 如果想通过函数修改实参,就需要指针类型。
func argf(a, b *int) {
*a = *a + *b
*b = 888
}
var x, y int = 3, 6
argf(&x, &y)
slice、map、channel都是引用类型,它们作为函数参数时其实跟普通struct没什么区别,都是对struct内部的各个字段做一次拷贝传到函数内部。
package main
import "fmt"
func slice_arg_1(arr []int) { //slice作为参数,实际上是把slice的arrayPointer、len、cap拷贝了一份传进来
arr[0] = 1 //修改底层数据里的首元素
arr = append(arr, 1) //arr的len和cap发生了变化,不会影响实参
}
func main() {
arr := []int{8}
slice_arg_1(arr)
fmt.Println(arr[0]) //1
fmt.Println(len(arr)) //1
}
关于函数返回值
- 可以返回0个或多个参数。
- 可以在func行直接声明要返回的变量。
- return后面的语句不会执行。
- 无返回参数时return可以不写。
func returnf(a, b int) (c int) { //返回变量c已经声明好了
a = a + b
c = a //直接使用c
return //由于函数要求有返回值,即使给c赋过值了,也需要显式写return
}
不定长参数
不定长参数实际上是slice类型。
不定长参数需要在类型前面加上三个点...
,以切片的形式存储不定长参数
取出参数需要使用循环遍历
func variable_ength_arg(a int, other ...int) int {
sum := a
for _, ele := range other {//不定长参数实际上是slice类型
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函数
append函数接收的就是不定长参数。
在切片的后面加上...
,就可以把切片转换为不定长参数
arr = append(arr, 1, 2, 3)
arr = append(arr, 7)
arr = append(arr)
slice := append([]byte("hello "), "world"...) //...自动把"world"转成byte切片,等价于[]byte("world")...
slice2 := append([]rune("hello "), []rune("world")...) //需要显式把"world"转成rune切片
在很多场景下string都隐式的转换成了byte
切片,而非rune切片,比如"a中"[1]是228而非"中"。
递归函数
func Fibonacci(n int) int {
if n == 0 || n == 1 {
return n //凡是递归,一定要有终止条件,否则会进入无限循环
}
return Fibonacci(n-1) + Fibonacci(n-2) //递归调用函数自身
}
匿名函数
函数也是一种数据类型。
func function_arg1(f func(a, b int) int, b int) int { //f参数是一种函数类型 f的函数类型就是一个匿名函数
a := 2 * b
return f(a, b)
}
type foo func(a, b int) int //foo是一种函数类型
func function_arg2(f foo, b int) int { //参数类型看上去简洁多了
a := 2 * b
return f(a, b)
}
type User struct {
Name string
bye foo //bye的类型是foo,而foo代表一种函数类型
hello func(name string) string //使用匿名函数来声明struct字段的类型
}
ch := make(chan func(string) string, 10)//声明一个管道,管道的值是就是匿名函数
ch <- func(name string) string { //使用匿名函数
return "hello " + name
}
闭包
闭包(Closure)是引用了自由变量的函数,自由变量将和函数一同存在,即使已经离开了创造它的环境。闭包复制的是原对象的指针。
package main
import "fmt"
//闭包(Closure)是引用了自由变量的函数。自由变量将和函数一同存在,即使已经离开了创造它的环境。
func sub() func() {
i := 10
fmt.Printf("%p\n", &i)
b := func() {
fmt.Printf("i addr %p\n", &i) //闭包复制的是原对象的指针
i-- //b函数内部引用了变量i
fmt.Println(i)
}
return b //返回了b函数,变量i和b函数将一起存在,即使已经离开函数sub()
}
// 外部引用函数参数局部变量
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)) //11,13
// 此时tmp1和tmp2不是一个实体了
tmp2 := add(100)
fmt.Println(tmp2(1), tmp2(2)) //101,103
}
延迟调用defer
- defer用于注册一个延迟调用(在函数返回之前调用)。
- defer典型的应用场景是释放资源,比如关闭文件句柄,释放数据库连接等。
- 如果同一个函数里有多个defer,则后注册的先执行。
- defer后可以跟一个func,func内部如果发生panic,会把panic暂时搁置,当把其他defer执行完之后再来执行这个。
- defer后不是跟func,而直接跟一条执行语句,则相关变量在注册defer时被拷贝或计算。
defer执行顺序
后注册先执行
func fn() {
fmt.Println("A")
//如果同一个函数里有多个defer,则后注册的先执行
defer fmt.Println("E")
fmt.Println("B")
defer fmt.Println("D")
fmt.Println("C")
}
func main() {
fn()
}
/*
A
B
C
D
E
*/
defer中的变量
func defer_exe_time() (i int) {
i = 9
defer func() { //defer后可以跟一个func
fmt.Printf("first i=%d\n", i) //打印5,而非9。充分理解“defer在函数返回前执行”的含义,不是在“return语句前执行defer”
}()
defer func(i int) {
fmt.Printf("second i=%d\n", i) //打印9,注册时就被赋了值
}(i)
defer fmt.Printf("third i=%d\n", i) //defer后不是跟func,而直接跟一条执行语句,则相关变量在注册defer时被拷贝或计算
return 5 //会将5赋值给i
}
/*
third i=9
second i=9
first i=5
*/
defer内部有异常
不会影响其他defer,会把异常放到最后处理
func fn() {
n := 0
defer fmt.Println("AAAAA")
defer func() {
fmt.Println(2 / n)
}()
defer fmt.Println("BBBBB")
}
func main() {
fn()
}
/*
BBBBB
AAAAA
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.fn.func1()
E:/Codes/GO/test.go:9 +0x86
main.fn()
E:/Codes/GO/test.go:12 +0xe9
main.main()
E:/Codes/GO/test.go:14 +0x17
exit status 2
*/
异常处理
go语言没有try catch,它提倡返回error。
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 {//函数调用方判断error是否为nil
fmt.Println(err.Error())
}
创建异常的方法:
errors.New(错误信息的字符串)
fmt.Errorf("hahhahh%d", 20)
可以是格式化字符串
自定义异常
Go语言定义了error这个接口,自定义的error一定要实现Error()方法。
type diyerror struct { //自定义error
name string
message string
}
func (err diyerror) Error() string { //error接口要求实现Error() string方法
return e.name + "=========" + e.msg + time.Now().Format("2006-01-02 15:04:05")
}
//调用
func newerror() diyerror {
return diyerror{"一个错误", "这是消息"}
}
//抛出异常
func main() {
panic(newerror())
}
何时会发生panic:
- 运行时错误会导致panic,比如数组越界、除0。
- 程序主动调用panic(error)。
panic会执行什么:
- 逆序执行当前goroutine的
defer
链(recover()
从这里介入)。 - 打印错误信息和调用堆栈。
- 调用exit(2)结束整个进程。
recover()
会使程序从panic中恢复(即不会执行panic的第2步和第3步),并返回panic value
。
recover()
所在的函数后续的代码不会执行,但函数可以正常返回。
在未发生panic时调用recover()
,会返回nil。
recover()必须在defer中才能生效。
处理异常
func soo() {
fmt.Println("enter soo")
defer func() { //去掉这个defer试试,看看panic的流程。把这个defer放到soo函数末尾试试。把这个defer移到main()里试试。
//recover必须在defer中才能生效
if err := recover(); err != nil {
fmt.Printf("soo函数中发生了panic:%v\n", err.Error())
}
}()
fmt.Println("regist recover")
defer fmt.Println("hello")
defer func() {
n := 0
_ = 3 / n //除0异常,发生panic,下一行的defer没有注册成功
defer fmt.Println("how are you")
}()
}
package main
import (
"fmt"
)
func B() {
// defer func() { //方式一,recover()在B()函数里,则在B()函数中panic后面的代码不会执行。不影响BBBBBBB的打印
// if panicValue := recover(); panicValue != nil {
// fmt.Printf("panic info %v\n", panicValue)
// }
// }()
panic(5)
}
func main() {
defer func() { //方式二,recover()在main()函数里,则在main()函数中panic后面的代码不会执行。BBBBBBB不会打印出来
if panicValue := recover(); panicValue != nil {
fmt.Printf("panic info %v\n", panicValue)
}
}()
B()
fmt.Println("BBBBBBB")
}
取出error结构体的内容
使用error.Error()