init函数
-
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用。
-
一般用来做main函数运行前的初始化工作。
-
一个文件同时包含全局变量定义,init(),main(),那么执行顺序为: 全局变量定义->init()->main()。
-
第3条加强版,在src非main文件夹中,写一个函数,引入两个变量,并且写入init()函数,所以此时的顺序变成:
非main的import包中的init() -> main中的全局变量定义 -> init() -> main() 。 -
第4条加强版,如果src非main被调用函数中存在 全局变量定义和init(),src main文件中包含全局变量定义、init(),main(),这五项应该是什么执行顺序:
非main的import包中的全局变量定义 -> 非main的import包中的init() -> main中的全局变量定义 -> init() -> main() 。请参照下图示意
package main import "<被调用函数>" //①全局变量定义 ②init() ③ <全局变量定义> ④ func init { \<函数体> } ⑤func main { \<函数体> }
匿名函数:
- 使用场景:一般只使用一次就OK,调用一次就好了
- 定义匿名函数时就直接调用,这种方式只能调用一次,案例演示:
package main import "fmt" func main() { //直接在创建匿名函数时直接调用,注意下匿名函数格式,传参在函数定义后直接传 res := func (n1,n2 int) int { return n1+n2 } (10,20) fmt.Println(res) }
- 可以将匿名函数赋予一个变量,定义好形参之后由 <变量>(para1,para2) 方式使用。
- 在全款变量中定义一个变量,数据类型为匿名函数,同上可以,main()中任意一个地方都可以使用该匿名函数。
- 匿名函数的优点是在main()函数中如果定义其他有名称的函数是不允许的,所以匿名函数灵活性很高。
闭包
- 闭包就是一个函数与其相关的引用环境组成的一个整体。
- 闭包返回值是一个函数,这个函数会用到函数外的一些变量,它们共同组成一个整体,这个整体叫闭包。
- 可以这样理解,闭包是一个类,外部函数中的变量看做定义属性,返回函数值看成定义方法,属性和方法共同构成类,即闭包。
- 搞清楚闭包的关键,就是分析出返回函数和它引用到的变量,共同构成闭包,每次调用时函数外环境变量不是重新初始化,而是累加。
案例:package main import "fmt" func Addpp() func(int) int { //定义一个函数 传入的参数是一个func, 返回值为int var n int = 20 var str string = "shit!" return func (x int) int { //Addpp返回值是一个匿名函数,相当于一个累加器了 n = n + x str += " damn!" fmt.Println("str= ",str) return n } } func main() { /* 理解闭包的概念,闭包本身是一个函数,闭包的返回值也是一个函数,return func; return 的是一个匿名函数,匿名函数中会调用闭包函数中的变量,从而做一些事情,产生返回值; 被匿名函数返回的返回值是匿名函数外的变量,则变量会改变初始化的值 */ f := Addpp() fmt.Println(f(1)) fmt.Println(f(2)) fmt.Println(f(3)) }
- 案例:使用闭包完成传入一个文件名,如果文件名有后缀.jpg就不用修改,直接输出;如果没有后缀就将文件名加上.jpg输出:
判断文件名是否有后缀,使用strings.HasSuffix()
代码如下:package main import ( "fmt" "strings" //使用strings包的HasSuffix方法判断文件是否有后缀 ) func makeSuffix (suffix string) func (string) string { //makeSuffix()实现功能是添加后缀名,返回值为一个函数 return func (fileName string) string { // if !strings.HasSuffix(fileName,suffix) { fileName += suffix return fileName } return fileName } } func main() { /* 案例:使用闭包完成传入一个文件名,如果文件名有后缀.jpg就不用修改,直接输出;如果没有后缀就将文件名加上.jpg输出: 判断文件名是否有后缀,使用strings.HasSuffix() */ var fileName string fmt.Println("请输入传入值的文件名称: ") fmt.Scanf("%s",&fileName) f := makeSuffix(".avi") fmt.Println(f(fileName)) }
defer
在函数中,程序员经常需要创建资源(数据库连接,锁,句柄等),为了在函数执行完成后,及时释放资源,打开,建立连接后立刻defer xxclose
- 当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈)。
- 当函数执行完毕后,再从defer栈,按照先进后出的方式出栈,执行。
- 将语句压栈时,也会把相关的值拷贝到栈中,例如defer后的语句是n1++等改变数值的,根据压栈时间,n1后续不会++,与值传递函数一样。
示例:package main import "fmt" func sum (n1,n2 int) int { defer fmt.Println("n2 is OK",n2) //压栈,辅函数中输入defer,函数结束后本条第二个执行 defer fmt.Println("n1 is OK",n1) //压栈,根据压栈方式可以判定,函数结束后本条第一个执行 res := n1 + n2 fmt.Println("sum is finished") //终端最先输出的是这句话 return res } func main() { f := sum(10,20) fmt.Println("res的值为",f) //最后输出的是这句话 }
- 通过上面的示例可以看出,如果学过k8S就能理解pod在启动前有个pre-start,后续有个post-stop, 一个是pods启动前,需要配置的东西,另外一个是pods终止后,需要做什么。
- defer最主要的价值是函数执行完毕后,及时释放函数创建的资源,例如连接数据库,或者打开文件,立马defer输入资源关闭,这样安心处理事情,函数处理完了自然资源就关闭了,设计理念很好。
模拟:
func test {
file = openfile(<文件名>)
defer file.close() //立刻写,不会影响函数执行时或持久连接而close
}
Tips:
-
func sum (n1,n2 float32) {…} //注意这种方式使用没有问题,n1也是float32格式,不会出现编译错误。
-
被调用函数尽量根据需求是否改变形参为值传递还是引用传递,如果是值传递直接用基本变量类型,如果是引用传递则使用指针。
案例:写一个函数比较三个数大小,按照从小到大输出
被调用函数:
package compare func Compare(a *int,b *int,c *int) { var temp int if *a > *b { temp = *a *a = *b *b = temp } if *a > *c { temp = *a *a = *c *c = temp } if *b > *c { temp = *b *b = *c *c = temp } }
调用函数:
package main import ( "compare" "fmt" ) func main() { var ( a int b int c int ) fmt.Println("请输入三个数,用空格间隔开:\n") fmt.Scanf("%d %d %d",&a,&b,&c) compare.Compare(&a,&b,&c) fmt.Printf("三个数从小到大排列为:%v %v %v",a,b,c) }