函数构成了代码执行的逻辑结构,在Go语言中,函数的基本组成为:关键字 func、函数名、参数列表、返回值、函数体、返回语句,每一个程序都包含很多的函数,函数是基本的代码块。
因为Go语言是编译型语言,所以函数编写的顺序是无关紧要的,鉴于可读性的需求,最好把main()
函数写在文件的前面,其他函数按照一定逻辑顺序进行编写(例如函数被调用的顺序)。
Go语言里面拥三种类型的函数:
函数声明包括函数名、参数列表、返回值列表(可省略)以及函数体
func 函数名 (参数列表) (返回值列表) {
return 函数体
}
如果一个函数在声明时,包含返回值列表,那么该函数必须以 return 语句结尾,除非函数明显无法运行到结尾处。
在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改。
多个同类型参数可以省略前面的参数的类型声明,此时的参数a类型为string,参数b、c类型为int
func funcname(a string, b, c int) {}
本质上是一个切片,也就是[]int
,可变参数必须位于参数列表的最后
func funcname(args ...int) {}
注意:在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须有命名。如果返回值有有多个返回值必须加上括号; 如果只有一个返回值并且有命名也需要加上括号。
package main
import "fmt"
func main() {
fmt.Println(add(1, 2)) // 3
fmt.Println(red(1, 2)) // -1
x, y := getPoint()
fmt.Println(x, y) //52 13
}
func add(a int, b int) int {
return a + b
}
// 函数声明时,可以给返回值声明一个变量,在函数体内进行操作
func red(a int, b int) (c int) {
c = a - b
return
}
// 函数支持返回多个返回值
func getPoint() (int, int) {
return 52, 13
}
在Go语言中,函数也是一种类型,可以和其他类型一样保存在变量中
package main
import "fmt"
func main() {
// 声明一个函数类型的变量
var f1 func(int, int) int
// 将函数add赋值给f2
f1 = add
// 自动类型推导
f2 := add
fmt.Println(f1(1, 2)) // 3
fmt.Println(f2(1, 2)) // 3
}
func add(a int, b int) int {
return a + b
}
匿名函数是指不需要定义函数名的一种函数实现方式,由一个不带函数名的函数声明和函数体组成,匿名函数可以声明在另一个函数的函数体中
func (参数列表) (返回参数列表) {
函数体
}
匿名函数的用途非常广泛,它本身就是一种值,可以方便地保存在各种容器中实现回调函数和操作封装。
// 定义的同时进行调用
func(a int, b int) {
fmt.Println(a + b)
}(1, 2)
// 匿名函数赋值给变量
f := func(a int, b int) int {
return a + b
}
fmt.Println(f(1, 2))
作为回调函数
package main
import "fmt"
func main() {
names := []string{"tom", "lucy", "lily"}
f := func(name string) {
fmt.Printf("%s,Hello\n", name)
}
// 调用函数,并传入回调函数
handlerNames(names, f)
}
// 遍历名单,对名字执行操作
func handlerNames(names []string, f func(string)) {
for _, k := range names {
f(k)
}
}
Go语言中闭包是引用了另一个函数局部变量的函数,被引用的局部变量和闭包函数一同存在,即使已经离开了局部变量的环境也不会被释放或者删除,在闭包中可以继续使用这个局部变量。
同一个函数与不同引用环境组合,可以形成不同的实例。
一个函数类型就像结构体一样,可以被实例化,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有记忆性。
函数是编译期静态的概念,而闭包是运行期动态的概念。
例如,实现一个累加器
package main
import "fmt"
func main() {
f := add()
fmt.Println(f()) //1
fmt.Println(f()) //2
fmt.Println(f()) //3
fmt.Println(f()) //4
}
func add() func() int {
i := 0
return func() int {
i++
return i
}
}
add()
中的匿名函数被函数add()
外的变量f
引用的时候,就创建了一个闭包。add()
中的变量i
始终存在,gc不会回收变量i
,因为函数add()
中的匿名函数执行需要依赖变量i
。Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer所属的函数即将返回时,将延迟处理的语句按 defer声明的逆序进行执行。也就是说,先被 defer 的语句最后被执行;最后被 defer 的语句,最先被执行。
在同一个函数中,defer可以看作为栈操作,也就是先注册的defer语句在栈底,所以后进先出
关键字 defer 的用法类似于面向对象编程语言Java的 finally 语句块,它一般用于释放某些已分配的资源,典型的例子就是对一个互斥解锁,或者关闭一个文件。
package main
import "fmt"
func main() {
fmt.Println("start")
defer fmt.Println("end")
defer fmt.Println(3)
defer fmt.Println(2)
defer fmt.Println(1)
/*
start
1
2
3
end
*/
}
package main
import "fmt"
func main() {
defer func() {
fmt.Println("hello")
fmt.Printf("world")
}()
}
程序执行到defer语句的时候(而不是defer语句执行的时候),如果defer后调用的函数需要传参,那么就会把变量值传递给函数。
package main
import "fmt"
func main() {
name := "go"
defer func(n string) {
fmt.Println(n) // go
}(name)
name = "python"
}
但是,如果defer后面是无参的匿名函数,那么程序无参可传,就会继续往下执行。直到defer语句执行的时候,才会去获取变量值。
package main
import "fmt"
func main() {
name := "go"
defer func() {
fmt.Println(name) // python
}()
name = "python"
}
在Go语言中,return
语句在defer
前执行
package main
import "fmt"
func main() {
compare()
}
func compare() int {
defer deferFunc()
return returnFunc()
}
func deferFunc() int {
fmt.Println("defer")
return 0
}
func returnFunc() int {
fmt.Println("return")
return 0
}
/*
return
defer
*/
虽然return
语句是在defer
语句前执行,但是对于底层,return语句并不是原子操作,它分为给返回值赋值和RET
指令两步。defer语句的执行时机就在返回值赋值操作后,RET
指令执行前。
package main
import "fmt"
func main() {
fmt.Println(ff()) // 0
fmt.Println(tt()) // 1
}
/*
ff()的return过程如下:
1、返回值赋值:returnVariable = (a = 0)
2、执行defer:a++
3、RET指令执行,函数结束
第二步a++实际是改变了局部变量a,而并没有改变返回值returnVariable,所以返回值还是0
*/
func ff() int {
a := 0
defer func() {
a++
}()
return a
}
/*
tt()的return过程如下:
1、返回值赋值:a = 0(根据Go语言规则,类型声明默认赋值为零值)
2、执行defer:a++
3、RET指令执行,函数结束
由于是命名返回值,所以返回值就是变量a,第二步a++改变了返回值a,所以返回值是1
*/
func tt() (a int) {
defer func() {
a++
}()
return
}
Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作,例如:len、cap 和 append,或必须用于系统级的操作,例如:panic。因此,它们需要直接获得编译器的支持。
append -- 用来追加元素到数组、slice中,返回修改后的数组、slice
close -- 主要用来关闭channel
delete -- 从map中删除key对应的value
panic -- 停止常规的goroutine (panic和recover:用来做错误处理)
recover -- 允许程序定义goroutine的panic动作
real -- 返回complex的实部 (complex、real imag:用于创建和操作复数)
imag -- 返回complex的虚部
make -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)
new -- 用来分配内存,主要用来分配值类型指针,比如*int、*string。
cap -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
copy -- 用于复制和连接slice,返回复制的数目
len -- 来求长度,比如string、array、slice、map、channel ,返回长度
print、println -- 底层打印函数,在开发中建议使用 fmt 包