Go语言基础之函数下
defer 延迟函数
package main
import "fmt"
// defer
func main() {
f("1")
fmt.Println("2")
defer f("3")
fmt.Println("4")
}
func f(s string) {
fmt.Println(s)
}
defer函数或者方法:一个函数或方法的执行被延迟了
- 你可以在函数中添加多个defer语句,当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回,特别是当你在进行一些打开资源的操作时i/o 流,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题
- 如果有很多调用 defer,那么 defer 是采用 后进先出(栈) 模式。
package main
import "fmt"
// defer 作用:处理一些善后的问题,比如错误,文件、网络流关闭等等操作。
// 特点,多个defer的问题
// 你可以在函数中添加多个defer语句,当函数执行到最后时,这些defer语句会按照逆序执行
func main() {
f("1")
defer fmt.Println("2")
defer f("3")
fmt.Println("4")
defer f("5")
fmt.Println("6")
defer f("7")
fmt.Println("8")
}
func f(s string) {
fmt.Println(s)
}
defer存在传递参数:
package main
import "fmt"
// defer传参的调用时机
func main() {
n := 10
fmt.Println("main n=", n)
// 在defer的时候,函数其实已经被调用了,但是没有执行。参数是已经传递进去的了
defer ff(n) // 问题,defer延迟执行函数,参数时什么时候传递进去
n++
fmt.Println("main end n=", n)
}
func ff(n int) {
fmt.Println("ff函数中n=", n)
}
defer在文件流之后进行关闭操作
文件.open() 二进制流 建立了连接
defer 文件.close() // 关闭文件
//读写操作
//.......
defer:程序会报错: 异常(程序执行的时候突然报错了)、错误(我们开发的时候知道的预期错误)
善后工作:defer 处理异常。
函数的数据类型
- func (xxxx,xxx) xxx,xxxx
- 函数也是一种数据类型,可以定义函数类型的变量
package main
import "fmt"
// 函数是什么(数据类型)
func main() {
a := 10.01
fmt.Printf("%T\n", a) // 查看变量的类型
b := [4]int{1, 2, 3, 4}
fmt.Printf("%T\n", b) // 查看变量的类型
c := true
fmt.Printf("%T\n", c) // 查看变量的类型
// 函数的类型
func1() // 带了括号是函数的调用
fmt.Printf("%T\n", func1) // 查看变量的类型 func()
fmt.Printf("%T\n", func2) // 查看变量的类型 func(int) int
// func(int, int) (int, int)
// func(int, int, ...string) (int, int)
//var fun3 func(int, int, ...string) (int, int)
fun3 := func2
r1, r2 := fun3(1, 2, "111")
fmt.Println(r1, r2)
// 函数在Go语言中本身也是一个数据类型,加了() 是调用函数,不加(), 函数也是一个变量,可以赋值给别人。
// 函数的类型就等于该函数创建的类型,他也可以赋值给
}
// 无参无返回值的函数
func func1() {
}
// 有参有返回值的函数
func func2(a, b int, c ...string) (int, int) {
return 0, 0
}
函数的本质
函数在Go语言中不是一个简单的调用或者接收结果的。
函数在Go中是一个符合类型,可以看做是一个特殊的变量。var 定义吗,赋值。类型相同即可
函数类型的样子 :var f1 函数名(参数) 结果
变量名:指向一段内存 (num --> 0x11111aaaa)
函数名:指向一段函数体的内存地址,是一种特殊类型的变量。我们可以将一个函数赋值给另外一个类型相同的函数
匿名函数
package main
import "fmt"
// 匿名变量 (没有名字的变量)
// 匿名函数 (没有名字的函数)
func main() {
// 正常的调用
f12()
f2 := f12 // 函数赋值给另外一个函数
f2()
// f12 f2 本质指向了同一个内存空间,空间中的代码一致 {fmt.Println("我是f12函数")}
// 匿名函数,在函数体后增加(),调用了这个函数,匿名函数只能一次。
func() {
fmt.Println("我是一个匿名函数")
}()
// 将匿名函数进行赋值,就可以实现多次调用。
f3 := func() {
fmt.Println("我是一个匿名函数")
}
f3()
// 匿名函数是否可以添加参数和返回值
func(a, b int) {
fmt.Println("a,b")
}(1, 2)
// 将匿名函数的返回值定义给变量。
r1 := func(a, b int) int {
return a + b
}(1, 2)
fmt.Println(r1)
// 由于Go语言中的函数是一个特殊的变量,支持匿名操作
// Go语言支持函数式编程
// - 将匿名函数作为另外一个函数的参数,回调函数
// - 将匿名函数作为另外一个函数的返回值,可以形成闭包结构
}
func f12() {
fmt.Println("我是f12函数")
}
回调函数
高阶函数:可以将一个函数作为另外一个函数的参数。
fun1()
fun2(fun1)
fun1 函数作为 fun2 函数的参数
fun2函数,叫做高阶函数,接收了另外一个函数作为参数的函数
fun1函数,叫做回调函数,作为另外一个函数的参数
package main
import "fmt"
// 回调函数
func main() {
// 函数调用
r1 := add(1, 2)
fmt.Println(r1)
// 高阶函数调用
r2 := oper(1, 2, add)
fmt.Println(r2)
r3 := oper(1, 2, sub)
fmt.Println(r3)
// 匿名函数
fun1 := func(a, b int) int {
return a * b
}
r4 := oper(1, 2, fun1) // 调用匿名函数 *
fmt.Println(r4)
// 能够直接传递匿名函数
r5 := oper(1, 2, func(a int, b int) int {
if b == 0 {
fmt.Println("除数不能为0")
return 0
}
return a / b
})
fmt.Println(r5)
}
// 运算 (运算的数字,运算操作)
// 高阶函数,参数是接收另外一个函数
func oper(a, b int, fun func(int, int) int) int {
fmt.Println(a, b, fun)
r := fun(a, b)
return r
}
func add(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
小黄鸭调试法
此概念是参照于一个来自《程序员修炼之道》书中的一个故事。传说中程序大师随身携带一只小黄鸭,在调试代码的时候会在桌上放上这只小黄鸭,然后详细地向鸭子解释每行代码 [1] 。
许多程序员都有过向别人(甚至可能向完全不会编程的人)提问及解释编程问题,就在解释的过程中击中了问题的解决方案。一边阐述代码的意图一边观察它实际上的意图并做调试,这两者之间的任何不协调会变得很明显,并且更容易发现自己的错误。如果没有玩具小鸭子也可以考虑向其它东西倾诉,比如桌上的花花草草,键盘鼠标。
类似的,有一种现象叫做cone of answers,这是一个常见的现象。你的朋友跑来问你一个问题,但是当他自己把问题说完,或者说到一半的时候就想出了答案走了,留下一脸茫然的你。是的,这个时候你就起到了那只小黄鸭的作用。
闭包
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量
并且该外层函数的返回值就是这个内层函数。
这个内层函数和外层函数的局部变量,统称为闭包结构。
局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁
但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用
package main
import "fmt"
// 是一种特殊的结构:闭包结构,违反了程序正常的生命周期。合法的使用。程序允许的一种特殊结构,变量作用域升级了。
// 什么时候用闭包: js (xxxxxxx.html 引用大量的第三方库:10个js库,js库中很多变量名是冲突的)
// js 很多框架都是闭包结构的,防止变量冲突,全局变量污染
// 我的代码里面的变量就不会和你代码里面的变量冲突了。解决一些变量作用域冲突的问题。
/*
闭包结构:
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量并且该外层函数的返回值就是这个内层函数。
在闭包结构中:局部变量的生命周期就会发生改变,
正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁
但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用.
// 由于垃圾回收期不会将闭包中的变量销毁,可能会造成内存泄漏。
*/
// 你的代码变量和你同事的变量冲突了,解决。 i 新建一个变量。 第三方库中的代码都是闭包结构实现的导出。
var i int = 10
func main() {
r1 := increment()
fmt.Println(r1) // 返回的是一个 increment() 内存函数,还没有执行
// -- 执行这个内层函数
//
v1 := r1()
fmt.Println(v1)
v2 := r1()
fmt.Println(v2)
fmt.Println(r1())
fmt.Println(r1())
fmt.Println(r1())
// 你写的代码是对的,但是结果不对,你的变量被污染了
fmt.Println("--------------------------")
// r2和r1指向同一个地址
r2 := increment() // 再次调用的时候 ,i = 0
v3 := r2()
fmt.Println(v3) // 1
//因为我们内层还是用i,还存在引用,系统不会销货这个i,保护,单独作用r1
fmt.Println(r1()) // 6 // 这里的i 并没有随着 第二次创建就被销毁归0,而是在内层函数继续调用着。
fmt.Println(r2()) // 2
// r1 名字 ----> 内存地址 &r1
fmt.Printf("%p\n", &r1)
fmt.Printf("%p\n", &r2)
}
// 自增函数
// increment() 函数返回值为 func() int 类型
func increment() func() int { // 外层函数,项目(很多的全局变量)
// 定义一个局部变量
i := 0
// 在外层函数内部定义一个匿名函数,给变量自增并返回。
fun := func() int {
i++
return i
}
return fun
}
如果我们想使用闭包结构来解决全局变量污染的问题,那我们就可以写一个闭包结构来创建执行的函数。
通过这个闭包结构创建的函数内部的变量,都在这个函数中作用,不会和其他函数冲突。
**闭包结果的返回值是一个函数。**这个函数可以调用闭包结构中的变量。
拓展 函数中的参数传递
函数的参数传递中。存在的问题:值传递、引用传递。
按照数据类型存储特点
值类型:int、string、bool、float64、array… 拷贝,创建的时候,拷贝一份
引用类型:操作的是数据的地址,切片slice、map、chan…
package main
import "fmt"
// 参数传递中值类型、引用类型的问题
func main() {
// 定义一个数组
arr1 := [4]int{1, 2, 3, 4}
fmt.Println("arr1修改前的数据:", arr1) // 1 2 3 4
update(arr1)
fmt.Println("arr1修改前的数据:", arr1) // 1 2 3 4
// 数组当做参数来传递的时候,参数是对当前数组做了一个拷贝。值传递类型
}
// 更新数组
func update(arr2 [4]int) {
fmt.Println("arr2修改前的数据:", arr2) // 1 2 3 4
arr2[0] = 100
fmt.Println("arr2修改后的数据:", arr2) // 100 2 3 4
}
引用传递
package main
import "fmt"
// 参数传递中值类型、引用类型的问题
func main() {
// 定义一个切片
s1 := []int{1, 2, 3, 4}
fmt.Println("s1修改前的数据:", s1) // 1 2 3 4
updatee(s1)
fmt.Println("s1修改后的数据:", s1) // 100 2 3 4
// 如果参数是引用类型的,那么修改函数内的值,会影响函数外的值。因为两个变量指向同一个内存空间,修改任意一个都会导致另外一个发送变化
// 值传递 每个变量参数都有自己的内存空间,拷贝
// 引用传递 每个变量参数都指向同一个内存空间,指向,改变任意一个,其他的都会发生变化。
}
// 更新数组
func updatee(s2 []int) {
fmt.Println("s2修改前的数据:", s2) // 1 2 3 4
s2[0] = 100
fmt.Println("s2修改后的数据:", s2) // 100 2 3 4
}