08-Go语言之函数
内容目录
- 函数基础
- 定义
- 调用
- 参数
- 返回值
- 高阶函数
- 作用域
- 函数作为变量
- 函数作为参数
- 函数作为返回值
- 匿名函数
- 闭包
- defer语句
内容详细
函数基础:定义
-
Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属于“一等公民”。
-
Go语言中定义函数使用
func
关键字,具体格式如下:func 函数名(参数)(返回值){ 函数体 }
其中:
- 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名称不能重名(包的概念详见后文)。
- 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用
,
分隔。 - 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用
()
包裹,并用,
分隔。 - 函数体:实现指定功能的代码块。
函数基础:调用
-
定义了函数之后,我们可以通过
函数名()
的方式调用函数。 -
注意,调用有返回值的函数时,可以不接收其返回值。
func intSum(x int, y int) int { return x + y } func main() { ret := intSum(10, 20) fmt.Println(ret) }
函数基础:参数
- GO语言中没有默认参数!!
类型简写
-
如果定义函数时,把返回值也声明了,那么在函数体中无需再次声明
func intSum(x int, y int) (ret int) { // 也可以直接定义返回值名称和类型 ret = x + y // 此时则不需要声明,应为定义函数时已经声明好返回值了 return // 返回值也可以省略,函数会自动寻找定义好的返回值名称 } func main() { ret := intSum(10, 20) fmt.Println(ret) }
可变参数
-
可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加
...
来标识。 -
本质上,函数的可变参数是通过切片来实现的。
-
注意:可变参数通常作为函数的最后一个参数出现。
// 函数接收可变参数,在参数名后面加... 表示可变参数 // 可变参数在函数体中是切片类型 func intSum(a ...int) int { ret := 0 for _, arg := range a { ret += arg } return ret } func main() { ret1 := intSum() ret2 := intSum(10) ret3 := intSum(10,20) fmt.Println(ret1,ret2,ret3) } // 0 10 30
-
固定参数和可变参数搭配使用
-
多个参数如果类型一致,则只需要在参数末尾写类型即可,参数之间用逗号隔开
// 固定参数和可变参数同时出现时,可变参数要放在最后 func intSum(a, b ...int) int { ret := a for _, arg := range b { ret += arg } return ret } func main() { ret1 := intSum(100) ret2 := intSum(100,200,300) fmt.Println(ret1,ret2) } // 100 600
函数基础:返回值
多返回值
-
定义多个返回值,函数如果有多个返回值时必须用
()
将所有返回值包裹起来。// 定义具有多个返回值的函数 // 多返回值也支持类型简写 func calc(a, b int) (sum, sub int) { sum = a + b sub = a - b return } func main() { // 函数调用 x, y := calc(200, 100) fmt.Println(x,y) }
返回值补充
-
当我们的一个函数返回值类型为slice时,nil可以看做是一个有效的slice,没必要显示返回一个长度为0的切片。
func someFunc(x string) []int { if x == "" { return nil // 没必要返回[]int{} } ... }
函数高阶:作用域
全局变量
- 全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。 在函数中可以访问到全局变量。
局部变量
- 函数内定义的变量无法在函数外使用
- 如果局部变量和全局变量重名,优先访问局部变量。
- 语句块定义的变量:在if条件判断、for循环、switch语句上定义的变量,只能在其中使用
项目作用域
-
在一个项目中不同的包下,使用首字母大写的变量、函数、slice、map等都在该项目的其他文件中调用
// tt包中创建: package tt import "fmt" // 私有的全局变量 var a int = 100 // 项目中所有都可以调用的全局变量 var A int = 18 func f1(){ fmt.Println("f1") } func F1() { fmt.Println("F1") }
// main包中使用: package main import ( "fmt" "github.com/liyn/day02/tt" ) func main() { tt.A = 200 // 调用tt包中的A变量 tt.F1() // 调用tt包中的F1函数 fmt.Println(tt.A) } // F1 // 200
函数高阶:函数作为变量
-
函数可以作为变量的值
func add(x,y int)int{ return x+y } func main(){ ret := add fmt.Println(ret(5,6)) } // 11
函数高阶:函数作为参数
-
函数可以作为参数
func add(x,y int)int{ return x+y } func sub(x,y int)int{ return x - y } func calc(x,y int,op func(a,b int)int)int { return op(x,y) } func main() { r1 := calc(200,100,add) r2 := calc(200,100,sub) fmt.Println(r1,r2) }
函数高阶:函数作为返回值
-
函数也可以作为返回值
// 定义一个函数它的返回值是一个函数 func testDemo() func(){ return func() { fmt.Println("沙河小王子") } } func main(){ ret := testDemo() ret() // 相当于执行了testDemo函数内部的匿名函数 }
函数高阶:匿名函数
-
匿名函数因为没有函数名,所以没办法像普通函数那样调用,所以匿名函数需要保存到某个变量或者作为立即执行函数:
- 方式一:将匿名函数保存到变量中,执行变量()就可以执行匿名函数了
- 方式二:匿名函数末尾加参数,可直接执行匿名函数
// 格式: func(参数)(返回值){ 函数体 } // 示例: func main() { // 将匿名函数保存到变量 add := func(x, y int) { fmt.Println(x + y) } add(10, 20) // 通过变量调用匿名函数 //自执行函数:匿名函数定义完加()直接执行 func(x, y int) { fmt.Println(x + y) }(10, 20) }
函数高阶:闭包
-
闭包内部的变量不会回收
-
无参数的闭包
// 定义一个函数它的返回值是一个函数 func testDemo() func(){ name := "沙河娜扎" return func() { fmt.Println("hello", name) } } func main(){ // 闭包 = 函数 + 外层变量的引用 ret := testDemo() // ret此时就是一个闭包 ret() // 相当于执行了testDemo函数内部的匿名函数 }
-
闭包示例一:
// 定义一个函数它的返回值是一个函数 func testDemo(name string) func(){ return func() { fmt.Println("hello", name) } } func main(){ ret := testDemo("沙河娜扎") ret() }
-
闭包示例二:
// 判断一个字符串是否以指定字段结尾: // 定义一个函数它的返回值是一个函数 func makeSuffixFunc(suffix string) func(string)string{ return func(name string) string { if !strings.HasSuffix(name,suffix){ // 检测是否以第二个参数为末尾的值 return name + suffix }else { return name } } } func main() { // 闭包 = 函数 + 外层变量的引用 r := makeSuffixFunc(".txt") ret := r("沙河娜扎") fmt.Println(ret) } // 沙河娜扎.txt
-
闭包示例三:
// 定义一个函数它的返回值是一个函数 func calc(base int) (func(int) int, func(int) int) { add := func(i int) int { base += i return base } sub := func(i int) int { base -= i return base } return add, sub } func main() { // base一直没有消 x,y := calc(100) ret1 := x(200) fmt.Println(ret1) // 此时base=100,add函数参数i=200,结果为300 ret2 := y(300) fmt.Println(ret2) // 此时base=300,sub函数参数i=300,结果为0 }
defer语句
-
defer
语句会将其后面跟随的语句进行延迟处理。在defer
归属的函数即将返回时,将延迟处理的语句按defer
定义的逆序进行执行,也就是说,先被defer
的语句最后被执行,最后被defer
的语句,最先被执行。 -
由于
defer
语句延迟调用的特性,所以defer
语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、加锁解锁及记录时间等。func main() { fmt.Println("start") defer fmt.Println(1) defer fmt.Println(2) defer fmt.Println(3) fmt.Println("end") } // start // end // 3 // 2 // 1
-
defer不管出不出异常,肯定执行,为了关闭资源
func chuF(a,b int)int{ return a / b } func main() { defer fmt.Println("数据库关闭") defer chuF(3,0) defer fmt.Println("数据库开启") } // 数据库开启 // 数据库关闭 // panic: runtime error: integer divide by zero
-
如果defer中有函数,就先执行函数
func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } func main() { x := 1 y := 2 defer calc("AA", x, calc("A", x, y)) // 先执行calc中的函数 x = 10 defer calc("BB", x, calc("B", x, y)) y = 20 } /* A 1 2 3 B 10 2 12 BB 10 12 22 AA 1 3 4 */
defer执行时机
-
在Go语言的函数中
return
语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer
语句执行的时机就在返回值赋值操作后,RET指令执行前。
内置函数介绍
内置函数 | 介绍 |
---|---|
close | 主要用来关闭channel |
len | 用来求长度,比如string、array、slice、map、channel |
new | 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针 |
make | 用来分配内存,主要用来分配引用类型,比如chan、map、slice |
append | 用来追加元素到数组、slice中 |
panic和recover | 用来做错误处理 |