Golang函数
函数
Golang函数特点
- 无需声明原型
- 支持多返回值
- 不定参数传参 也就是函数的参数个数不是固定的 但是后面的类型是固定的
- 支持命名返回参数
- 支持匿名函数和闭包
- 函数也是一种类型 可以赋值给一个变量
- 不支持 嵌套 一个包不能有2个名字一样的函数
- 不支持重载
- 不支持默认参数
函数声明
函数声明包含一个函数名,参数列表,返回值列表和函数体,如果函数没有返回值,则返回列表可以省略。函数从第一条语句开始执行,直到执行return语句或者执行函数的最后一条语句。
func test(str,x string,y int)(int,string){ //相同的2个类型 可以省略type合并称一个 //(int,string)返回参数 } //函数是第一类对象 可以作为参数传递
参数
函数定义时指出,函数定义时有参数,该变量可被称为函数的形参,形参就像定义在函数的局部变量。但是当调用函数的时候,传递过来的变量就是函数的实参,函数可以通过2种方式传递参数
- 值传递:在 在调用函数的时候将实际参数复制一份传递到函数中,这样在函数中如果对参数修改,不会影响到实际参数
func swap(x ,y int)int{ }
引用传递: 引用传递是指在调用函数时把实际参数的地址传递到函数中,那么函数中对参数进行修改就是对实际参数修改,会影响到实际参数。
func swap(x,y *int){//传入指针 var temp int //创建一个临时变量 temp =*x //x的地址的值赋值给temp临时变量 *x=*y //y的地址的值给x *y=temp //temp的地址给到y }
在默认情况下Go语言使用的是值传递,也就是在调用过程中不会影响到实际参数
无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值得拷贝。引用传递是地址得拷贝,一般来说,地址拷贝更加高效。而值拷贝取决于对象得大小,对象越大 性能越低
map\slice\array\chan\指针\interface 默认都是以引用的方式传递
Golang可变参数本质上就是slice。只能有一个,且必须是最后一个。在参数赋值的时候不用一个个的赋值。可以直接传递一个数组或者切片,注意最后一个参数要加上args
...
int返回值
"_"标识符,用来忽略函数的某个返回值。Go的返回值可以被命名,并且像在函数开头声明的变量那样使用。返回值的名称应当具有一定的意义,可以作为文档使用。没有参数的return语句返回各个变量的当前值。这种用法被称作"裸返回"。直接返回语句仅应用在像下面这样的短函数中,在长的函数中它们会影响代码的可读性。
//直接返回sum func add(x, y int) (sum int) { sum = x + y return } func main() { sum := add(10, 11) fmt.Println(sum)//21 } //命名返回参数允许defer延迟调用通过闭包读取和修改 //直接返回sum //直接返回sum func add(x, y int) (sum int) { defer func() { fmt.Println("加100") sum += 100 }() sum = x + y return } func main() { sum := add(10, 11) fmt.Println(sum) //加100 121 //显示return返回前,会先修改命名返回参数 }
匿名函数
匿名函数是指不需要定义函数名的一种函数实现方式。在GO里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。匿名函数由一个不带函数声明和函数体组成。匿名函数的优越性可以直接使用函数内的变量不必声明。
func main() { var sum = func() int { return 100 * 100 > } fmt.Println(sum())//10000 } //Golang匿名函数可赋值给变量,作为结构字段,或者在channel里传送 func main() { //创建一个函数变量 fun := func() { fmt.Println("111") } fun() //111 //fun 数组 创建多个函数 函数的格式为接受一个参数返回一个int类型 funs := [](func(x int) int){ func(x int) int { return x + 1 }, func(x int) int { return x + 2 }, } fmt.Println(funs[0](100)) //101 //作为结构体的一个field d := struct { fn func(x int) int }{ //创建一个函数变量 fn: func(x int) int { //函数变量赋值 return 1 }, } fmt.Println(d.fn(1)) //1 //channel c := make(chan func() string, 2) c <- func() string { return "hello world" } fmt.Println((<-c)())//hello world }
闭包
闭包可以理解为一种保存函数状态的方法,当我们调用一个函数,或者执行操作,或者返回结果,函数运行结束之后,随即消亡,因为函数一般都是在堆上,当系统检测当前内存空间没有被引用就会回收
闭包的作用就是保存函数的运行状态 避免函数被回收。
- 访问所在的作用域
- 函数嵌套
- 在所在作用域外被调用
func bb() func(i int) int { var s int = 0 fmt.Println("ccccc") fmt.Println("ccccc") b := func(i int) int { s = s + 1 fmt.Printf("地址%#v", &s) return s > } > return b > } > func main() { > a := bb() > b := bb() > fmt.Println(a(1)) > fmt.Println(a(1)) > fmt.Println(b(1)) > fmt.Println(b(1)) > } > //ccccc > //ccccc > //ccccc > //ccccc > //地址(*int)(0xc00000a0a8)1 > //地址(*int)(0xc00000a0a8)2 > //地址(*int)(0xc00000a0c0)1 > //地址(*int)(0xc00000a0c0)2 > //可以看到a和b变量的调用 s被存在了不同的内存当中 > //当采用a调用的时候 s的变量被保存到了0xc00000a0a8当中 > //当采用b调用的时候 s变量被保存到了0xc00000a0c0 中 因为这是在调用bb()方法的时候 都申请了一块内存 > > //把s定义成全局变量 > var s int = 0 > func bb() func(i int) int { > b := func(i int) int { > s = s + 1 > fmt.Printf("地址%#v", &s) > return s > } > return b > } > func main() { > a := bb() > b := bb() > fmt.Println(a(1)) > fmt.Println(a(1)) > fmt.Println(b(1)) > fmt.Println(b(1)) > } > //ccccc > //ccccc > //ccccc > //ccccc > //地址(*int)(0x85dc68)1 > //地址(*int)(0x85dc68)2 > //地址(*int)(0x85dc68)3 > //地址(*int)(0x85dc68)4 > //可以看到当s提升为全局变量 申请的一块地址 用于a和b调用的时候 是共享的内存变量 > ``` > > 通过以上案例我们可以得出在a:=bb()的时候执行了bb函数,实际上这里的指向是直接指向的bb()函数的内部子函数。 > > 当我们调用a()的时候 直接执行的也是内部的子函数。 > > 通过局部变量和全局变量可以发现,申请的内存方式是不同的
递归函数
递归就是在运行的过程中调用自己。一个函数调用自身叫做递归函数
构成递归函数得条件:
子问题必须与原始问题为同样的事,且更为简单
不能无限制调用必须有个出口,化简为分递归状态处理
func factorial(i int) int { if i <= 1 { return 1 } return i * factorial(i-1) } func main() { fmt.Println(factorial(7))//5040 }
延迟调用(defer)
defer特性: 1.关键字defer用于注册延迟调用 2. 这些调用直到return前才会被执行。因此可以用来做资源清理 3.多个defer,按照先进后出的方式执行 4.defer语句中的变量,在defer声明时就决定了 defer用途: 1.关闭文件句柄 2.锁资源释放 3.数据库连接释放 func main() { for i := 0; i < 10; i++ { //defer是先进后出的 defer 调用的函数参数的值 defer 被定义时就确定了. //defer fmt.Print(strconv.Itoa(i) + "\t") //9 8 7 6 5 4 3 2 1 0 //defer 碰上闭包 defer func() { fmt.Println(&i) //defer func内部所使用的变量的值需要在这个函数运行时才确定 //也就是讲这在闭包用到的时候这个变量已经变成了10,所以输出全都是10 }() } }
defer 与return
//defer 与return func foo() (i int) { i = 0 defer func() { fmt.Println(i) }() //在具名返回函数中,执行return 2的时候已经将i的值赋值为2了。所以输出的时候结果为2不是0 return 2 } func main() { foo() }
defer nil报错
//defer nil函数报错 func test() { //声明的时候未被调用 var run func() = nil //被调用 报错 defer run() fmt.Println("runs") } func main() { //捕捉异常 defer func() { if err := recover(); err != nil { fmt.Println("错误日志") fmt.Println(err) } }() //调用 runtime error: invalid memory address or nil pointer dereference //main从上到下开始执行。defer 延迟执行 //执行test方法。test方法调用的时候,出test的时候进行报错。 当test调用完成的时候 defer run才会被调用 test() }
在错误的地方使用defer
func do() error { res, err := http.Get("http://www.google.com") defer res.Body.Close() if err != nil { return err } return nil } func main() { do() //panic: runtime error: invalid memory address or nil pointer dereference //因为google无法访问因为网络原因 defer 直接使用了res变量 res为nill未判断 //可以在defer 的时候加上一层判断解决该错误 if res!=nil{ defer res.Body.Close() } }
不检查错误
//对于f.Close()可能会返回一个错误,但是这个错误会被我们忽略掉 func do() error { f, err := os.Open("book.txt") if err != nil { return err } if f != nil { defer f.Close() } return nil } func main() { do() } // 改进 if f!=nill{ defer func(){ if err:=f.Close();err!=nill{ //code } } }
释放相同的资源
func do() error { f, err := os.Open("book.txt") if err != nil { return err } if f != nil { defer func() { if err := f.Close(); err != nil { fmt.Printf("defer close book.txt err %v\n", err) } }() } f, err = os.Open("another-book.txt") if err != nil { return err } if f != nil { defer func() { if err := f.Close(); err != nil { fmt.Printf("defer close another-book.txt err %v\n", err) } }() } return nil } func main() { do()//输出结果: defer close book.txt err close ./another-book.txt: file already closed //当延迟函数执行时候,只有最后一个变量会被用到,因此f会成为最后那个资源。而且2个资源都将这一个资源作为关闭对象 } //解决方案 //可以把f当作参数传递进去 defer func(f io.Closer){ }(f)
异常处理
Golang没有结构化异常,使用panic抛出异常,recover捕获错误。
异常的使用场景很简单:Go可以抛出一个panic异常,然后在defer中通过recover捕获这个异常。然后正常处理
panic: 1.内置函数 2.加入函数F中书写了panic语句 ,会终止其后要执行得代码,在panic所在函数F内如果存在要执行得defer函数列表。按照defer先进后出执行 3.返回函数F得调用者G,在G中,调用函数F语句之后得代码不会执行,加入函数G在存在要执行得defer先进后厨执行 4.直到goroutine整个退出,并报告错误 recover: 1.内置函数 2.用来控制一个goroutine得panicking行为,捕获panic,从而影响应用得行为 3.一般的调用建议 1.在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行 2.可以获取通过panic传递的error 注: 1.利用recover处理panic指令,defer必须放在panic之前定义,另外recover只有在defer调用的函数中才有效,否则当panic,receover无法捕捉到panic,无法防止panic扩散 2.recover处理异常后,逻辑并不会恢复到panic那个点去,函数跑到defer之后的那个点 3.多个defer会形成defer栈,后定义的defer语句最先被调用。
延迟调用
//延迟调用中引发错误,可被后续异常调用捕获,但仅最后一个错误可被捕获 func test() { defer func() { fmt.Println("最后调用") fmt.Println(recover()) }() defer func() { fmt.Println("第一次调用") panic("defer panic") }() panic("test panic") } func main() { test() } //捕获函数recover 只有在延迟调用内直接调用才会终止错误,否则总是返回nill。任何未捕获的错误都会沿调用堆栈向外传递 package main import ( "fmt" ) func test() { defer func() { fmt.Println(recover()) //有效 }() defer recover() //无效! defer fmt.Println(recover()) //无效! defer func() { func() { println("defer inner") recover() //无效! }() }() panic("test panic") } func main() { test() }
Go实现类似try catch的异常处理
func Try(fn func(), handler func(interface{})) { defer func() { if err := recover(); err != nil { handler(err) } }() fn() } func main() { Try(func() { fmt.Println("测试") }, func(err interface{}) { }) }