init函数
基本介绍
每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也就是说 init 会在 main 函数前被调用。
使用案例
package main
import "fmt"
func init() {
fmt.Println("init()执行")
}
func main() {
fmt.Println("main()执行")
}
执行结果
注意事项
如果一个文件同时包含全局变量定义,init 函数和 main 函数,则执行的流程全局变量定义->init 函数->main 函数
init 函数最主要的作用,就是完成一些初始化的工作
细节说明: 面试题:案例如果 main.go 和 utils.go 都含有 变量定义,init 函数时,执行的流程又是怎么样的呢?
package main
import "fmt"
var age int = test()
func test() int {
fmt.Println("test()执行")
return 40
}
func init() {
fmt.Println("init()执行")
}
func main() {
fmt.Println("main()执行")
}
关于注意点3的说明
匿名函数
基本介绍
Go 支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用。
使用案例1
在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。
package main
import "fmt"
func main() {
res := func(n1 int, n2 int) int {
return n1 + n2
}(10, 10)
fmt.Println("res =", res)
}
使用案例2
将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
package main
import "fmt"
func main() {
f := func(n1 int, n2 int) int {
return n1 + n2
}
res := f(20, 10)
fmt.Println("res =", res)
}
使用案例3
如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效。
package main
import "fmt"
var (
Fun1 = func(n1 int, n2 int) int {
return n1 * n2
}
)
func main() {
res := Fun1(3, 9)
fmt.Println("res =", res)
}
闭包
基本介绍
闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
基本语法
package main
import "fmt"
func AddUpper() func(int) int {
var n = 10
return func(i int) int {
n += i
return n
}
}
func main() {
f := AddUpper()
fmt.Println("n1 = ", f(1))
fmt.Println("n2 = ", f(2))
fmt.Println("n3 = ", f(3))
}
说明及注意事项
AddUpper 是一个函数,返回的数据类型是 fun (int) int
闭包的说明:返回的是一个匿名函数, 但是这个匿名函数引用到函数外的n ,因此这个匿名函数就和n 形成一个整体,构成闭包。
当我们反复的调用 f 函数时,因为 n 是初始化一次,因此每调用一次就进行累计。
闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包。
练习
编写一个函数 makeSuffix(suffix string) 可以接收一个文件后缀名(比如.jpg),并返回一个闭包,调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回 文件名.jpg , 如果已经有.jpg 后缀,则返回原文件名。
package main
import (
"fmt"
"strings"
)
func makeSuffix(suffix string) func(name string) string {
return func(name string) string {
if strings.HasSuffix(name, suffix) {
return name
} else {
return name + suffix
}
}
}
func main() {
f := makeSuffix(".jpg")
fmt.Println("文件名1 = ", f("file1"))
fmt.Println("文件名2 = ", f("file2.jpg"))
}
defer
基本介绍
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完毕后,及时的释放资源,Go 的设计者提供 defer (延时机制)。
使用案例
package main
import "fmt"
func getSum(n1 int, n2 int) int {
// 当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈中
// 当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制)
defer fmt.Println("n1 =", n1)
defer fmt.Println("n2 =", n2)
fmt.Println("getSum()执行到defer语句后面了")
return n1 + n2
}
func main() {
num := getSum(1, 2)
fmt.Println("num =", num)
}
注意事项
当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈中
当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制)
在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈。
package main
import "fmt"
func getSum(n1 int, n2 int) int {
defer fmt.Println("n1 =", n1) // 这个把值进行拷贝,不会进行+1的操作,是独立的一个栈空间
defer fmt.Println("n2 =", n2)
n1++
n2++
fmt.Println("getSum()执行到defer语句后面了")
return n1 + n2
}
func main() {
num := getSum(1, 2)
fmt.Println("num =", num)
}
函数参数的传递方式
传递方式
值传递
引用传递
其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。
值类型和引用类型
值类型:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体struct
引用类型:指针、slice 切片、map、管道chan、interface 等都是引用类型
值传递和引用传递使用特点
值传递和引用传递
如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。
变量作用域
函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
如果变量是在一个代码块,比如 for / if 中,那么这个变量的的作用域就在该代码块
package main
import "fmt"
var name = "tom"
func test1() {
fmt.Println("name =", name)
}
func test2() { // 编译器使用就近原则
name := "jack"
fmt.Println("name =", name)
}
func main() {
fmt.Println("name =", name)
test1()
test2()
test1()
}
评论区