当前位置:网站首页>Go (二) 函数部分1 -- 函数定义,传参,返回值,作用域,函数类型,defer语句,匿名函数和闭包,panic
Go (二) 函数部分1 -- 函数定义,传参,返回值,作用域,函数类型,defer语句,匿名函数和闭包,panic
2022-08-03 05:11:00 【天才小楼楼】
一、函数定义
1.1、函数的定义
Go语言中定义函数使用func关键字,具体格式如下:
func 函数名(参数 type)(返回值type){
函数体
}
- 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
- 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
- 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
- 函数体:实现指定功能的代码块。
1.2、函数的使用(汇总)
// 定义一个求和函数(有参数),有return返回值时(需要给返回值定义一个类型) func s1(x int, y int) int { return x + y } // 定义一个求和函数(有参数),没有return返回值时 func s2(x int, y int) { fmt.Println(x + y) } // 定义一个函数(无参数),没有return返回值时 func s3() { fmt.Println("szq") } // 定义一个函数(无参数),有return返回值时(需要给返回值定义一个类型) func s4() string { return "sudada" } // return返回值 可以命名(命名后可以直接使用,因为已经做了声明),也可以不命名 // return返回值"szq"在已经声明好的情况下,return后可以什么都不写,默认返回的就是"szq" func s5(x int, y int) (szq int) { szq = x + y return // 这里就可以不写szq,写上也没关系 } // 多个返回值 func s6() (int, string) { return 1, "2" } // 参数类型的简写(2个参数连续且类型一致时,可以这么写) func s7(x, y int) int { return x + y } // 可变长参数(...)必须放在函数参数的最后, y可以不传参,可以传一个参数,也可以传多个参数(拿到的就是一个切片类型的值) func s8(x string, y ...int) { fmt.Println(x) fmt.Println(y) // [1 2 3 4] 拿到的是一个int类型的切片 } // go语音中函数没有默认参数的概念 func main() { fmt.Println(s1(1, 2)) s2(2, 3) s3() fmt.Println(s4()) fmt.Println(s5(2, 2)) fmt.Println(s6()) s8("szq") }
1.3、函数传参
1.3.1、类型简写
函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:
// 参数类型的简写(2个参数连续且类型一致时,可以这么写) func s7(x, y int) int { return x + y }
上面的代码中,s7函数有两个参数,这两个参数的类型均为int,因此可以省略x的类型,因为y后面有类型说明,x参数也是该类型。
1.3.2、可变长参数
可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识。
注意:可变参数通常要作为函数的最后一个参数。// 可变长参数(...)必须放在函数参数的最后, y可以不传参,可以传一个参数,也可以传多个参数(拿到的就是一个切片类型的值) func s8(x string, y ...int) { fmt.Println(x) fmt.Println(y) // [1 2 3 4] 拿到的是一个int类型的切片 }
可变长参数使用:
func intSum2(x ...int) int { fmt.Println(x) // x是一个切片 sum := 0 for _, v := range x { sum = sum + v } return sum } func main() { ret1 := intSum2() ret2 := intSum2(10) ret3 := intSum2(10, 20) ret4 := intSum2(10, 20, 30) fmt.Println(ret1, ret2, ret3, ret4) //0 10 30 60 }
固定参数搭配可变参数使用时,可变参数要放在固定参数的后面,示例代码如下:
func intSum3(x int, y ...int) int { fmt.Println(x, y) sum := x for _, v := range y { sum = sum + v } return sum } func main() { ret5 := intSum3(100) ret6 := intSum3(100, 10) ret7 := intSum3(100, 10, 20) ret8 := intSum3(100, 10, 20, 30) fmt.Println(ret5, ret6, ret7, ret8) //100 110 130 160 }
本质上,函数的可变参数是通过切片来实现的。
1.4、函数 返回值
Go语言中通过return关键字向外输出返回值。
1.4.1、多返回值
Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。func calc(x, y int) (int, int) { sum := x + y sub := x - y return sum, sub } func main() { a, b := calc(1, 2) fmt.Println(a) fmt.Println(b) }
1.4.1、多返回值命名
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。// return返回值 可以命名(命名后可以直接使用,因为已经做了声明),也可以不命名 // return返回值"szq"在已经声明好的情况下,return后可以什么都不写,默认返回的就是"szq" func s5(x int, y int) (szq int) { szq = x + y return // 这里就可以不写szq,写上也没关系 } // 例子2 func calc(x, y int) (sum, sub int) { sum = x + y sub = x - y return } func main() { a, b := calc(1, 2) fmt.Println(a) fmt.Println(b) }
二、函数作用域
2.1、全局变量
全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。 在函数中可以访问到全局变量。
函数中查找变量的顺序:
1.先在函数的内部查找
2.找不到就往函数的外面查找,一直找到全局,如果找不到就报错。var szq = 123 func s0() { fmt.Println(szq) } func main() { s0() // 123 }
2.2、局部变量
1.局部变量又分为两种:函数内定义的变量无法在该函数外使用
func testLocalVar() { //定义一个函数局部变量x,仅在该函数内生效 var x int64 = 100 fmt.Printf("x=%d\n", x) } func main() { testLocalVar() fmt.Println(x) // 此时无法使用变量x }
2.如果局部变量和全局变量重名,优先访问局部变量。
//定义全局变量num var num int64 = 10 func testNum() { num := 100 fmt.Printf("num=%d\n", num) // 函数中优先使用局部变量 } func main() { testNum() // num=100 }
2.3、语句块定义的变量
1.通常我们会在if条件判断、for循环、switch语句上使用这种定义变量的方式。
func testLocalVar2(x, y int) { fmt.Println(x, y) //函数的参数也是只在本函数中生效 if x > 0 { z := 100 //变量z只在if语句块生效 fmt.Println(z) } fmt.Println(z) //此处无法使用变量z } func main() { testLocalVar2(1,2) }
2.还有for循环语句中定义的变量,也是只在for语句块中生效:
func testLocalVar3() { for i := 0; i < 10; i++ { fmt.Println(i) //变量i只在当前for语句块中生效 } fmt.Println(i) //此处无法使用变量i } func main() { testLocalVar3() }
三、函数类型与变量
3.1、定义函数类型
1.什么是函数类型,举个例子:
// 函数作为参数赋值给变量时,有哪些类型 func s1(){ fmt.Println("szq") } func s2() int{ return 18 } func s3(x int) int{ return x+1 } func main() { a := s1 // 打印变量a的类型 fmt.Printf("%T\n",a) //func():这是一个普通的函数类型 b := s2 // 打印变量b的类型 fmt.Printf("%T\n",b) //func() int :这是一个函数类型(包括函数的返回值类型) c := s3 // 打印变量c的类型 fmt.Printf("%T\n",c) //func(int) int :这是一个函数类型(包括函数的参数类型和函数的返回值类型) }
2.函数作为一个参数
// 定义函数s1 func s1() int { return 18 } // s1函数作为参数传递给s2函数 func s2(x func() int){ res := x() fmt.Println(res) } func main() { re1 := s1 // 将函数赋值给变量re1 fmt.Printf("%T\n",re1) // func() int s2(s1) // 函数s1作为参数传递给函数s2(s1函数的类型必须满足s2的接收参数类型,即:func() int) s2(re1) // 变量re1作为参数传递给函数s2(变量re1的类型必须满足s2的接收参数类型,即:func() int) }
3.函数作为一个返回值
// 定义函数s1 func s1() int { return 18 } func ff(a int,b int)int{ return a+b } // 函数作为一个返回值返回 // x func() int 表示x的类型为:func() int // y func(int,int)int 表示s3函数的返回值类型为:func(int,int)int func s3(x func() int)(y func(int,int)int){ fmt.Println(x()) res := func(a int,b int)int{ return a+b } //res := ff // 这里也可以换一种写法,ff函数的格式需要满足y即可 return res } func main() { szq:=s3(s1) // 把函数s1做为参数传入到函数s3中,执行函数s3的代码后拿到一个返回值res fmt.Println(szq(1,2)) // 拿到的返回值res类型为"函数类型:func(int,int)int",那么就可以正常的传值,查看执行结果 } //返回结果: //18 //3
3.2、函数类型变量 (变量的类型为函数类型,然后为该变量赋值)
了解以上函数类型之后,我们可以使用type关键字来定义一个函数类型,具体格式如下:
typecalculationfunc(int, int) int
上面语句定义了一个calculation类型,它是一种函数类型,这种函数接收两个int类型的参数并且返回一个int类型的返回值。// 定义一个函数类型 type calculation func(int, int) int // 定义一个全局变量c,c的类型为calculation var c calculation // 定义一个普通函数,类型为:add(x, y int) int func add(x, y int) int { return x + y } func main() { c = add // 给变量c做赋值操作(变量c = add函数) fmt.Println(add(1,2)) // 3 fmt.Printf("%T\n",c) // main.calculation fmt.Println(c(5,5)) // 像调用add一样调用c,返回值为:10 f := add // 将函数add赋值给变量f fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int fmt.Println(f(10, 20)) // 像调用add一样调用f,返回值为:30 }
四、defer语句
4.1、Go语言中的
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
语句延迟调用的特性,所以defer
语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。4.2、defer执行时机
在Go语言的函数中
return
语句在底层并不是原子操作,它分为给返回值赋值和RET(return)指令两步。而defer
语句执行的时机就在返回值赋值操作后,RET(return)指令执行前。具体如下图所示:4.3、defer经典案例
// 第一步:返回值赋值 //defer //第二步:真的的return,返回一个值 func f1() int { x := 5 defer func() { x++ // 修改的是x,不是返回值 }() return x } func f2() (x int) { defer func() { x++ }() return 5 // 返回值为x } func f3() (y int) { x := 5 defer func() { x++ // 修改的是x }() return x // 返回值为y,y的值=x=5 } func f4() (x int) { defer func(x int) { x++ // 改变的是函数中的副本 }(x) return 5 // 返回值=x=5 } func main() { fmt.Println(f1()) //5 fmt.Println(f2()) //6 fmt.Println(f3()) //5 fmt.Println(f4()) //5 }
4.4、defer面试题
func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } func main() { // var x int = 1 x := 1 // var y int = 2 y := 2 // 函数内套用函数调用时,代码走到这一层就会先执行函数内的函数,会把对应变量的值传过去。 即:defer calc("AA", 1, calc("A", 1, 2)) == defer calc("AA", 1, 3),详见下面的分析结果: defer calc("AA", x, calc("A", x, y)) // 这里的x因为在前面已经做了定义,所以可以直接用"="号赋值 x = 10 // 函数内套用函数调用时,代码走到这一层就会先执行函数内的函数,会把对应变量的值传过去即:defer calc("BB", 10, calc("B", 10, 2)) == defer calc("BB", 10, 12),详见下面的分析结果: defer calc("BB", x, calc("B", x, y)) // 这里的y因为在前面已经做了定义,所以可以直接用"="号赋值 y = 20 } // 分析以上代码的执行过程: // 1)x := 1 // 2)y := 2 // 3)defer calc("AA", x, calc("A", x, y)) == defer calc("AA", 1, calc("A", 1, 2)) == defer calc("AA", 1, 3) // 4)x = 10 // 5)defer calc("BB", x, calc("B", x, y)) == defer calc("BB", 10, calc("B", 10, 2)) == defer calc("BB", 10, 12) // 6)y = 20 // 7)defer calc("BB", 10, 12) == BB 10 12 22 // 8)defer calc("AA", 1, 3) == AA 1 3 4 //输出结果: //A 1 2 3 先执行函数内的函数 //B 10 2 12 先执行函数内的函数 //BB 10 12 22 在按照defer的逻辑,最后定义的最先执行 //AA 1 3 4 按照defer的逻辑,最先定义的最后执行
五、匿名函数和闭包
5.1、匿名函数 (匿名函数多用于实现回调函数和闭包,匿名函数一般都用在函数内部)
函数当然还可以作为返回值,但是在Go语言中函数内部不能再像之前那样定义函数了,只能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数的定义格式如下:
func(参数)(返回值){
函数体
}1.匿名函数作为变量执行,例子如下:
// 匿名函数使用1 var f1 = func(x,y int){ fmt.Println(x+y) } func main() { f1(1,2) // 调用匿名函数,返回值为:3 // 匿名函数使用2 f2 := func(x,y int){ fmt.Println(x+y) } f2(10,20) // 调用匿名函数,返回值为:3 // 如果只是调用一次的函数,可以简写成"立即执行函数" func(x,y int){ fmt.Println(x+y) }(2,3) // 调用"立即执行函数",返回值为:5 }
2.匿名函数作为立即执行函数(只是一次调用的函数),例子如下:
func main() { // 如果只是调用一次的函数,可以简写成"立即执行函数" func(x,y int){ fmt.Println(x+y) }(2,3) // 调用"立即执行函数",返回值为:5 }
5.2、闭包 (一个函数除了引用函数内定义的变量外,还引用了函数外部的变量)
闭包的底层原理:
1.函数可以作为返回值,
2.函数内部查找顺序,现在函数内部找,找不在再往外部找。闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,
闭包=函数+引用环境
。 首先我们来看一个例子:// 闭包的实际应用,要求:s1(s2) 把s2作为参数传入到s1 func s1(f func()) { fmt.Println("s1") f() } func s2(x, y int) { fmt.Println("s2") fmt.Println(x + y) } //s3函数要接收s2函数的传参,并且返回值的类型要符合s1函数参数类型 //f func(int, int), x, y int 这些都是给s2函数使用的 //func() 这个是给s1函数使用的 func s3(f func(int, int), x, y int) func() { res := func() { f(x, y) } return res // 这里的返回值res是一个func()类型,符合s1函数的接收类型 } func main() { ret := s3(s2, 100, 300) // 这里ret也就等于s3函数内的res s1(ret) // s1函数传入ret,本质上执行的是s2函数的代码。 } //输出结果 //s1 //s2 //400
例子2:在函数内修改函数的参数值
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() { f1, f2 := calc(10) fmt.Println(f1(1), f2(2)) //11 9,这里每次改的都是calc函数内的base,也就是说随着每次的改修,base的值会发生改变。 fmt.Println(f1(3), f2(4)) //12 8 fmt.Println(f1(5), f2(6)) //13 7 }
六、内置函数
6.1、内置函数介绍
内置函数 介绍 close 主要用来关闭channel len 用来求长度,比如string、array、slice、map、channel new 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针 make 用来分配内存,主要用来分配引用类型,比如chan、map、slice append 用来追加元素到数组、slice中 panic和recover 用来做错误处理 6.2、panic 错误处理
func funcA() { fmt.Println("func A") } func funcB() { panic("出现了严重的错误!") // 主动报错 } func funcC() { fmt.Println("func C") } func main() { funcA() funcB() funcC() } // 输出结果: // func A // 报错之前执行的代码 // panic: 出现了严重的错误! // 报错的输出 // goroutine 1 [running]: // main.funcB(...) // 报错的代码位置点 // /Users/suzhaoqiang/Desktop/Go/Goland_Document/学习过程/2021-8-16/05.go:10 // main.main() // 报错代码的执行点 // /Users/suzhaoqiang/Desktop/Go/Goland_Document/学习过程/2021-8-16/05.go:18 +0x96
6.3、panic + recover 接收错误信息,代码继续执行
注意:
recover()必须搭配defer使用。
defer一定要在可能引发panic的语句之前定义。func funcA() { fmt.Println("func A") } func funcB() { defer func() { err := recover() // recover()拿到的就是panic的输出。使用recover()后程序不会崩溃退出,继续往下执行。 fmt.Println(err) }() panic("出现了严重的错误!") // defer要在panic之前定义,否则panic程序崩溃,没法执行defer语句了。 } func funcC() { fmt.Println("func C") } func main() { funcA() funcB() funcC() } //输出结果: //func A //出现了严重的错误! //func C
边栏推荐
猜你喜欢
Common fluorescent dyes to modify a variety of groups and its excitation and emission wavelength data in the data
MySql数据库
web安全-SSTI模板注入漏洞
Redis常用命令
flask 面试题 问题
idea uses @Autowired annotation to explain the reasons and solutions
轨迹(形状)相似性判断与度量方法
安装IIS服务(Internet信息服务(Internet Information Services,简写IIS,互联网信息服务)
传说中可“免费白拿”的无线路由器 - 斐讯 K2 最简单刷 breed 与第三方固件教程
PotPlayer实现上班摸鱼电视自由
随机推荐
Pr第四次培训笔记
tag单调栈-单调栈预备知识-lt.739. 每日温度
力扣561. 数组拆分
D-PHY
-寻找鞍点-
MySql数据库
Odps temporary query can write SQL, turned out to a named?
详解Nurbs曲线
高可用 两地三中心
vim命令
-角谷猜想-
第四次培训
C# async and multithreading
Kaggle(四)Scikit-learn
lintcode2330 · 计算x秒后的时间
ss-2.子项目互相访问(order80 -> payment8001)
传说中可“免费白拿”的无线路由器 - 斐讯 K2 最简单刷 breed 与第三方固件教程
2.ROS通信机制
-完全数-
VSO Downloader Ultimate 5.0.1.45 中文多语免费版 在线视频下载工具