四、函数
Go语言中的函数传参永远是拷贝
4.1、函数概念
● 函数就是将一堆代码进行重用的一种机制。函数就是段代码, 一个函数就像一个专门做这件事的人,调用它来做一些事情,它可能需要提供一些数据给它,它执行完成后可能会有一些执行结果。要求的数据就叫参数,返回的执行结果就是返回值。
● 注意:所有的函数都是全局函数可以被项目中所有文件使用在项目中,所以函数名是唯一的。
● Go语言拒绝使用默认参数,没有默认参数的概念
4.2、函数定义
基本格式:
func 函数名(形式参数名 数据类型,形式参数名 数据类型.....)(返回值名称 返回值类型){
//函数体
}
参数可以不命名,返回值也可以不命名。
命名返回值,相当于已经声明了一个变量。
当参数都是同一类型时,可省略前几个的类型声明
func f1(x, y, z int,m ,n string ,u ,v,w [5]int) {
}
4.3、函数调用
函数名(实际参数,实际参数…)
注意:形参和实参是不同的存储单元
调用过程图示:
4.4、不定参函数
4.4.1、定义格式:
func test(args ...int){
len(args)//切片的长度
}
以切片的形式接收
注意:可变长参数必须放在参数最后
4.4.2、嵌套使用
func test4(b ...int) { for i := 0; 1 < len(b); i++ { fmt.Println(i, b[i]) }}func test3(a ...int) { test4(a[0:]...)//从下标0开始到最后 test4(a[:2]...)//从开始到下表为2不包括2 test4(a[1:3]...)//从下标为1到下标为3不包括3}
4.5、带有返回值的函数
定义格式①(默认):
func sub(a int ,b int ) int {
sum:=a-b
//return表示函数的结束,匿名的返回值,必须指定返回的变量
return sum
}
return后面的代码不会执行
定义格式②(单个返回值):
func sub(a int, b int) (sum int) {
sum = a - b
//return表示函数的结束,声明了返回值,就可以不写返回值。
return
}
定义格式③(多个返回值):
func sub(a int, b int) (sum int, b int ,c int) {
sum ,b,c = 1,2,3
//return表示函数的结束
return
}
func main(){
a,b,c := sub(10,20)
e,_,f := sub(10,20)
}
4.6、函数类型
本质是一个指针
4.6.1、type
type 的作用:
① 定义函数类型。
② 为已存在的函数起别名。
func test1() {
fmt.Println("hello ")
}
func test2(a int, b int){
fmt.Println( a + b )
}
type FUNCTYPE func()//大小写均可以
type FUNCTEST func(int, int)
func main() {
//定义函数类型变量
var f FUNCTYPE
f = test1
//通过函数变量调用函数
f()
f1 := test2
f1(10,20)
}
4.7、函数的作用域
● 局部变量:在函数内部定义的变量,作用域限定于本函数内部从变量定义到本函数结束有效。在同一作用域范围内变量名是唯一的。
● 全局变量:既能在一个函数中使用,也能在其他的函数中使用。定义在函数外部的变量就是全局变量。全局变最在任何的地方(项目中所有文件)都可以使用,不存在先声明后定义或先定义后声明的区别。全局变量存储在数据区。
● const常量:常量存储位置是数据区。
● 函数中查找变量的顺序
1.先在函数内部查找。
2.找不到就往函数的外面查找。一直找到全局变量
4.8、匿名内部函数
定义函数的时候,是不能在一个函数中,再次定义一个函数。如果想在一个函数中再定义一个函数,可以使用匿名函数,所谓匿名函数就是没有名字的函数。
4.8.1、不带返回值的匿名函数
定义:
func (a int, b int) {
fmt.Println(a + b)
}(10, 20)
或者:
{
}
重复利用:
f:=func(a int, b int) {
fmt.Println(a + b)
}
f(10,20)
4.8.2、带返回值的匿名函数
v := func(a int, b int) int {
return a + b
}(10, 20)//已经调用了
fmt.Println(v) //30
fmt.Printf("%T", v) //int
重复利用:
m := func(a int, b int) int {
return a + b
}
fmt.Printf("%T",m)//func(int, int) int
a := m(5, 6)
fmt.Println(a)//11
4.9、闭包
● 闭包:是指 有权访问另一个函数作用域中的变量的函数。函数包含了他外部作用域的一个变量。
● 在Go语言里,所有的匿名函数(Go语言规范中称之为函数字面量)都是闭包。
● 根据以上定义,那么匿名函数其实就是闭包。
● 也可以这样理解闭包:虽然不能在一个函数里直接声明另一个函数,但是可以在一个函数中声明一个函数类型的变量,此时的函数称为闭包(closure)。
● 作用:可以通过匿名函数和闭包实现函数在栈区的本地化
举例:
①
func adder(x int) func(int) int {
return func(y int) int {
x += y
return x
}
}
func main() {
ret := adder(100)//包含函数返回值(一个函数)以及变量X
ret2 := ret(200)
fmt.Println(ret2)
}
②
③
func test222() func() int {
var a int
return func() int {
a++
return a
}
}
func main() {
//将test222函数类型赋值给m
//m:=test222
//函数调用将test222的返回值给f
f := test222()
for i := 0; i < 10; i++{
fmt.Println(f())
}
}
等价于:
func main() {
var a int
f := func() int {
a++
return a
}
for i := 0; i < 10; i++{
fmt.Println(f())
}
}
4.10、递归函数
● 如果一个函数在内部不调用其它的函数,而是自己本身的话,这个函数就是递归函数。
● 递归函数一定要有出口。
● 函数执行流程图:
4.11、defer语句
● Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。
● 多用于函数执行完之前释放资源
● defer 执行函数时,会先将函数参数值传进去,函数值是函数时,会优先执行此函数。(举例③)
4.11.1、举例
①
func demo() {
fmt.Println("1..........")
defer fmt.Println("2..........")
fmt.Println("3..........")
fmt.Println("4..........")
}
func main() {
demo()
}
②
func demo() {
fmt.Println("1..........")
defer fmt.Println("2..........")
defer fmt.Println("3..........")
fmt.Println("4..........")
fmt.Println("5..........")
}
func main() {
demo()
}
③
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
a := 1
b := 2
defer calc("1", a, calc("10", a, b))
a = 0
defer calc("2", a, calc("20", a, b))
b = 1
}
4.11.2、执行时机
在Go语言的函数中 return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:
func f1() int {
x := 5
defer func() {
x++
}()
return x
}
func f2() (x int) {
defer func() {
x++
}()
return 5
}
func f3() (y int) {
x := 5
defer func() {
x++
}()
return x
}
func f4() (x int) {
defer func(x int) {
x++
}(x)//传参
return 5
}
func main() {
fmt.Println(f1()) //5
fmt.Println(f2()) //6
fmt.Println(f3()) //5
fmt.Println(f4()) //5
}
4.12、内置函数
panic/recover
Go语言中目前( Go1.12 )是没有异常机制,但是使用panic/recover 模式来处理错误,panic 可以在任何地方引发,但 recover只有在defer 调用的函数中有效。
注意:recover是恢复错误前的状态,禁止滥用,最好少用。
func funcA() {
fmt.Println("a")
}
func funcB() {
panic("出现了严重的错误! ! ! ") //程序崩溃退出
fmt.Println("b")
}
func funcC() {
fmt.Println("c")
}
func main() {
funcA()
funcB()
funcC()
}
func funcA() {
fmt.Println("a")
}
func funcB() {
defer func() {
err := recover()
fmt.Println(err)
fmt . Println("释放数据库连接...")
}()
panic("出现了严重的错误! ! ! ") //程序崩溃退出
fmt.Println("b")
}
func funcC() {
fmt.Println("c")
}
func main() {
funcA()
funcB()
funcC()
}