函数
# 1.值传递和引用传递
- Go 语言中值类型有: int 系列、float 系列、bool、string、数组、结构体
- 值类型通常在栈中分配存储空间
- 值类型作为函数参数传递, 是拷贝传递
- 在函数体内修改值类型参数, 不会影响到函数外的值
- Go 语言中引用类型有: 指针、slice、map、channel
- 引用类型通常在堆中分配存储空间
- 引用类型作为函数参数传递,是引用传递
- 在函数体内修改引用类型参数,会影响到函数外的值
- slice 是一个 24Byte 的结构体。
- map 和 channel 都是 8Byte 的指针。
- slice、map、channel 使用的是浅拷贝,形参实参会通过指针共享数据,所以会相互影响。但是 golang 对这三个结构体做了封装,从广义上来定义引用的话(通过别名去修改原数据),那这三个数据类型也属于引用类型。
# 2.匿名函数
匿名函数也是函数的一种, 它的格式和普通函数一模一样,只不过没有名字而已。 匿名函数可以定义在函数外(全局匿名函数),也可以定义在函数内(局部匿名函数), Go 语言中的普通函数不能嵌套定义, 但是可以通过匿名函数来实现函数的嵌套定义。
- 全局匿名函数
一般情况下我们很少使用全局匿名函数, 大多数情况都是使用局部匿名函数, 匿名函数可以直接调用、保存到变量、作为参数或者返回值。
- 匿名函数应用场景
- 当某个函数只需要被调用一次时, 可以使用匿名函数
- 需要执行一些不确定的操作时,可以使用匿名函数
# 3.闭包(特殊的匿名函数)
- 只要闭包还在使用外界的变量, 那么外界的变量就会一直存在
- 闭包中使用的变量和外界的变量是同一个变量, 所以可以闭包中可以修改外界变量
- 也就是说只要匿名函数中用到了外界的变量, 那么这个匿名函数就是一个闭包
# 4.延迟调用
- Go 语言中没有提供其它面向对象语言的析构函数, 但是 Go 语言提供了defer 语句用于实现其它面向对象语言析构函数的功能。
- defer 语句常用于释放资源、解除锁定以及错误处理等。
- 无论你在什么地方注册 defer 语句,它都会在所属函数执行完毕之后才会执行, 并且如果注册了多个 defer 语句,那么它们会按照后进先出的原则执行。
使用建议: 一个函数中,有多个条件分支使用 return 结束函数运行的情况下,同时有资源打开需要关闭的情况下,建议使用 defer,在资源打开后直接使用 defer 注册关闭该资源的代码,这样可以避免忘记关闭资源操作。例如打开一个文件,然后读取内容,内容转换处理,然后关闭文件。这个流程就可以改成 打开文件、延迟关闭文件、读取内容、内容转换处理。 defer 不要在循环中使用,也不要对命名返回值变量操作,否则会出现很难理解的意外结果。defer 使用的位置如果不正确,会有可能导致宕机(panic),所以有错误检查的语句的,最好放在错误检查语句之后。
# 5.init 函数
- golang 里面有两个保留的函数:
- init 函数(能够应用于所有的 package)
注:init 函数用于处理当前文件的初始化操作, 在使用某个文件时的一些准备工作应该放到这里。
- main 函数(只能应用于 package main)
- 这两个函数在定义时不能有任何的参数和返回值。
- go 程序会自动调用 init()和 main(),所以你不能在任何地方调用这两个函数。
- package main 必须包含一个 main 函数, 但是每个 package 中的 init 函数都是可选的。
- 一个 package 里面可以写任意多个 init 函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个 package 中每个文件只写一个 init 函数。
执行顺序:
main 包 -> 常量 -> 全局变量 -> init 函数 -> main 函数 -> exit 函数
package main
import "fmt"
const constValue = 998 // 1
var gloalVarValue int = abc() // 2
func init() { // 3
fmt.Println("执行main包中main.go中init函数")
}
func main() { // 4
fmt.Println("执行main包中main.go中main函数")
}
func abc() int {
fmt.Println("执行main包中全局变量初始化")
return 998
}
执行main包中全局变量初始化
执行main包中main.go中init函数
执行main包中main.go中main函数