6.go语言的 break continue 关键字
break的使用
break语句可以结束for,switch和select的代码块。
1.单独在select中使用break和不使用break没有啥区别。
2.单独在表达式switch语句,并且没有fallthrough,使用break和不使用break没有啥区别。
3.单独在表达式switch语句,并且有fallthrough,使用break能够终止fallthrough后面的case语句的执行。
4.带标签的break可以跳出多层select/switch作用域。让break更加灵活,写法更加简单灵活,不需要使用控制变量一层一层跳出循环,没有带break的只能跳出
//特例:跳转到标签处
func main(){
MY_LABEL:
for i := 0; i < 5; i++ {
if i == 3 {
break MY_LABEL
}
fmt.Printf("%v\n", i)
}
fmt.Println("end...")
}
continue的使用
continue只能用在循环中,在go中只能用在for循环中,它可以终止本次循环,进行下一次循环。
在continue语句后添加标签时,表示开始标签对应的循环
//特例跳转到标签
func main(){
MY_LABEL:
for i:=0;i<5;i++{
if i==3{
continue MY_LABEL
}
fmt.Printf("%v\n",i)
}
fmt.Println("end...")
}
7.数组
go语言中的数组是相同数据类型的一组数据的集合,数组一旦定义长度不能修改,数组可以通过下标(或者叫索引) 来访问元素
数组不能用make()函数来声明初始化
go语言数组的定义
var 数组名称 [数组长度,必须是常量] 数组保存元素的类型
//例子:
var arr [3] int//初始化
arr = [3]int{1, 2, 3}//初始化在赋值法
for k, v := range arr {
fmt.Println(k, v)
}
go语言数组的初始化
初始化,就是给数组的元素赋值,没有初始化的数组,默认元素值都是零值,布尔类型是false,字符串是空字符串
//1.使用初始化列表
var a = [3]int{1,2,3}
var s = [2]string{"小明","小刘"}
var b = [2]bool{true,false}
//2.使用初始化列表(类型推断法)
a:=[3]int{1,2}
s:=[2]string{"小明","小刘"}
b:=[2]bool{true,false}
//3.省略数组长度 数组长度可以省略,使用...代替,初始化值得数量自动判断
var a = [...]int{1,2,3}
var s = [...]string{"小明","小刘"}
var b = [...]bool{true,false}
a2: = [...]int{1,2,3}
//4.指定索引值的方式来初始化 可以通过指定所有的方式来初始化,未指定的int,float默认为零值,布尔为false string为空字符串
a:=[...]int{0:1,2:3}
s:=[...]string{0:"小明",2:"小刘"}
b:=[...]bool{0:true,2:false}
}
go语言访问数组元素
可以通过下标的方式,来访问数组元素。数组的最大下标为数组长度-1,大于这个下标会发生数组越界
func main{
var arr=[...]int{1,2,3}
fmt.Println(arr[0])//对
fmt.Println(arr[3])//越界
//修改arr[0] arr[1]
arr[0]=4
arr[1]=5
}
根据数组长度遍历数组
可以根据数组长度,通过for循环和for range循环的方式来遍历数组,数组的长度可以用lengo语言中内置函数来获得
//1.for循环
func main{
a := [...]string{"小明", "小刘", "小张"}
for i := 0; i < len(a); i++ {
fmt.Printf("下标:%v 值:%v", i, a[i])
}
}
//2.for range循环
func main(){
var arr = [...]bool{true, false, true, false}
for i, v := range arr {
fmt.Printf("下标为:%v,值为:%v", i, v)
}
}
多维(二维)数组
多维数组中的每一个元素又是一个数组
//二维数组的定义
var arrs[][]string
//1.初始化写法
var arrs=[][]string{
{"小明","小张"},
{"小刘","小兰"},
{"小红","小强"},
}
//2.初始化写法
arrs:=[...][]string{
{"小明","小张"},
{"小刘","小兰"},
{"小红","小强"},
}
//3.不支持写法
arrs:=[][...]string{
{"小明","小张"},
{"小刘","小兰"},
{"小红","小强"},
}
//1.二维数组的遍历方法1 for循环
func main(){
var arr = [...][]int{
{1, 2},
{3, 4},
{5, 6},
}
for _, v1 := range arr {
for _, v2 := range v1 {
fmt.Printf("值:%v", v2)
}
}
}
//2.二维数组的遍历方法2 for range循环
func main(){
var arr = [...][]int{
{1, 2},
{3, 4},
{5, 6},
}
for i := 0; i < len(arr); i++ {
for j := 0; j < len(arr[i]); j++ {
fmt.Print(arr[i][j])
}
fmt.Println()
}
}
8.切片
1.前面我们学习了数组,数组是固定长度,可以容纳相同数据类型的元素的集合。当长度固定时,使用还是带来一些
限制,比如:我们申请的长度太大浪费内存,大小又不够用
2.鉴于上述原因,我们有了go语言的切片,可以把切片理解为,可变长度的数组,其实它底层就是用数组实现的,增加
了自动扩容功能。切片(slice)是一个拥有相同类型元素的可变长度的序列
3.map引用类型
切片相关的语法
声明一个切片和声明一个数组类似,只要不添加长度就可以了
var sli [] int
切片的正常初始化
var sli = []int{1,2,3}
切片是引用类型,可以用make函数来创建切片:
var sli = make([]int,len)
还可以简写为
sli:=make([]int,len)
也可以指定容量,其中capacity为可选参数
make([]int,len,cap)
这里的len是数组的长度且也是切片的初始长度
切片的长度和容量
切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量
var names = []string{"小明", "小刘"}
var num = []int{1, 2, 3}
fmt.Printf("len:%d cap:%d\n", len(names), cap(names))
fmt.Printf("len:%d cap:%d\n", len(num), cap(num))
var s []int = make([]int, 2, 3)
var s1 = make([]string, 3, 4)
fmt.Printf("len:%d cap:%d\n", len(s), cap(s))
fmt.Printf("len:%d cap:%d\n", len(s1), cap(s1))
切片的初始化
切片的初始化方法很多,可以直接初始化,也可以使用数组初始化等
直接初始化
s := []int{1, 2, 3}
fmt.Println(s)
使用数组初始化
arr := [...]int{1, 2, 3}
s1 := arr[:]
fmt.Println(s1)
使用数组的部分元素初始化(切片表达式)
切片的底层就是一个数组, 所以我们可以基于数组通过切片表达式得到切片。切片表达式中的low和high表示一个
索引范围 (左包含,右不包含) ,得到的切片长度=high-low,容量等于得到的切片的起始位置开始到切片长度
注意:数组和切片都可以这样划分得到新数组或者新切片
arr := [...]int{1, 2, 3, 4, 5, 6}
s1 := arr[:] //[]
fmt.Println(s1, len(s1), cap(s1)) //1,2,3,4,5,6 len6 cap6
s2 := arr[2:5] //[)
fmt.Println(s2, len(s2), cap(s2)) //3,4,5 len3 cap4
s3 := arr[:3] //[)
fmt.Println(s3, len(s3), cap(s3)) //1,2,3 len3 cap6
s4 := arr[2:] //2]
fmt.Println(s4, len(s4), cap(s4)) //3,4,5,6 len4 cap4
空(nil)切片
一个切片在未初始化之前默认为nil,长度为0,容量为0
var s1 []int
fmt.Println(s1==nil)//true
fmt.Printf("len:%d,cap:%d\n",len(s1),cap(s1))//0,0
切片的遍历
两种方式:for 和for range
//1.for
s1 := []int{1, 2, 3, 4, 5, 6}
for i := 0; i < len(s1); i++ {
fmt.Println(s1[i])
}
//2.for range
s1 := []int{1, 2, 3, 4, 5, 6}
for i, v := range s1 {
fmt.Println(i, v)
}
切片的增删改查和copy
切片是一个动态数组,可以使用append()函数添加元素,go语言中没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除* 元素 。由于,切片是引用类型,通过赋值的方式,会修改原有的内容,go提供了copy()函数来拷贝切片( 但必须要用make()函数来创建切片)*
增
用append()函数扩容后再赋值的数组或者切片是新切片和新数组
//1.单个增加
var sliceA []int
sliceA = append(sliceA, 12)
fmt.Println(sliceA) //12
sliceA = append(sliceA, 24)
fmt.Println(sliceA) //12,24
//2.
var sliceA = []int{1, 2, 3}
sliceA = append(sliceA, 4, 5, 6, 7, 8, 9, 10)
fmt.Println(sliceA)//1,2,3,4,5,6,7,8,9,10
//3.
var sliceA = make([]int, 3, 4)
sliceA = append(sliceA, 1, 2, 3)
fmt.Println(sliceA)//0 0 0 1 2 3
//4.多个增加
var sliceB []int
sliceB= append(sliceB,12,24,36,48,60)
fmt.Println(sliceB)//12,24,36,48,60
//5.append()函数实现两个切片之间的合并 即 把一个切片合并到另一个切片里并形成一个新的切片
sliceA := []string{"小明", "爱学习"}
sliceB := []string{"小刘", "爱玩耍"}
var sliceC []string
sliceC = append(sliceA, sliceB...)//...固定语法
fmt.Println(sliceC)
删
go语言中并没有删除切片的元素的专用方法,我们可以使用切片本身的特性来删除元素
sliceA := []int{1, 2, 3, 4, 5, 6}
//要删除索引为2的元素
sliceA = append(sliceA[:2], sliceA[3:]...)
fmt.Println(sliceA)//1 2 4 5 6
改
//正确写法(make) 指定长度 没有越界
var s1 = make([]int, 4, 8)
s1[0] = 10
s1[1] = 11
s1[2] = 12
s1[3] = 13
fmt.Println(s1)
//正确写法(正常) 指定长度 没有越界
var s1 = []int{1,2}
s1[0]=2
s1[1]=3
fmt.Println(s1)
//错误写法 长度不够 越界了
var s1 = []int{1, 2}
s1[0] = 10
s1[1] = 11
s1[2] = 12
s1[3] = 13
fmt.Println(s1)
查
var s1 = []int{1, 2, 3, 4}
fmt.Println(s1[0])
fmt.Println(s1[1])
fmt.Println(s1[2])
fmt.Println(s1[3])
copy
copy()内置函数
sliceA := []int{1, 2, 3}
sliceB := make([]int, 3, 3)
copy(sliceB, sliceA) //右边复制给左边
sliceB[0] = 4
fmt.Println(sliceA)//1 2 3
fmt.Println(sliceB)//4 2 3
9.map
1.map是一种key:value键值对的数据结构容器。map内部实现是哈希表(hash)
2.map最重要的一点是通过key来快速检索数据,key类似与索引,指向数据的值
3.map是引用类型的 默认值是nil即map[]
map的语法格式
可以使用内置函数make()还可以使用map关键字来定义map
//声明变量,默认mapA是nil 即map[]
var mapA map [int] int
变量名 key的数据类型 值的数据类型
//使用make函数声明变量 默认mapB是nil 即map[] 可以指定长度
var mapB = make(map[int]int)
//初始化
var mapC = map[int]int{1,2,3}
var mapC = make(map[int]int){1,2,3}
mapD := map[int]int{1,2,3}
mapD := make(map[int]int){1,2,3}
访问map
可以通过下标key获得值
mapA := map[string]string{
"username": "小明",
"age": "18",
}
fmt.Printf("mapA["username"]: %v\n", mapA["username"])
fmt.Printf("mapA["age"]: %v\n", mapA["age"])
判断某个键是否存在
go语言中有个判断map中键是否存在的特殊写法,格式如下:
value,ok:=map[key]
如果ok为true,存在;否则不存在
mapA := make(map[string]string)
mapA["username"] = "小明"
value, ok := mapA["username"]
fmt.Printf("value:%v--ok:%v", value, ok)//小明 true
value, ok = mapA["age"]
fmt.Printf("value:%v--ok:%v", value, ok)//无
false
遍历map
可以用for range 循环进行map遍历,得到key和value值
//1.遍历key
mapA := make(map[string]string)
mapA["username"] = "小明"
mapA["age"] = "18"
for key := range mapA {
fmt.Printf("key:%v", key)
}
//2.遍历key 和 value
mapA := make(map[string]string)
mapA["username"] = "小明"
mapA["age"] = "18"
for key, value := range mapA {
fmt.Println(key + ":" + value)
}
map的增删改查
增改查很常规跟数组和切片一样操作这里就不展示了主要展示删(delete)
使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:
delete(map 对象,key)
其中:
- map对象:表示要删除键值对的map对象
- key:表示要删除的键值对的键
mapA := make(map[string]string)
mapA["username"] = "小明"
mapA["age"] = "18"
delete(mapA, "age")
for k, v := range mapA {
fmt.Println(k + ":" + v)
}
元素为map类型的切片
我们想在切片里面放一系列用户的信息,这时候我们就可以定义一个元素为map类型的切片
sliceA := make([]map[string]string, 2, 2) //map[string]string看作一个整体
if sliceA[0] == nil {
sliceA[0] = make(map[string]string)
sliceA[0]["username"] = "小明" //这里的sliceA[0]看作map
sliceA[0]["age"] = "18"
sliceA[0]["weight"] = "80kg"
}
if sliceA[1] == nil {
sliceA[1] = make(map[string]string)
sliceA[1]["username"] = "小刘" //这里的sliceA[0]看作map
sliceA[1]["age"] = "19"
sliceA[1]["weight"] = "75kg"
}
for _, v := range sliceA {
fmt.Println(v) //map[age:18 username:小明 weight:80kg]
// map[age:19 username:小刘 weight:75kg]
}
遍历元素为map类型的切片
sliceA := make([]map[string]string, 2, 2) //map[string]string看作一个整体
if sliceA[0] == nil {
sliceA[0] = make(map[string]string)
sliceA[0]["username"] = "小明" //这里的sliceA[0]看作map
sliceA[0]["age"] = "18"
sliceA[0]["weight"] = "80kg"
}
if sliceA[1] == nil {
sliceA[1] = make(map[string]string)
sliceA[1]["username"] = "小刘" //这里的sliceA[0]看作map
sliceA[1]["age"] = "19"
sliceA[1]["weight"] = "75kg"
}
for _, v1 := range sliceA {
for i, v2 := range v1 {
fmt.Printf("索引:%v--值:%v\n", i, v2)
/*
索引:username--值:小明
索引:age--值:18
索引:weight--值:80kg
索引:username--值:小刘
索引:age--值:19
索引:weight--值:75kg
*/
}
}
值为切片类型的map
map的值可以是一个还可以是一系列(即值为切片类型)
var mapA = make(map[string][]string)//值是切片类型
mapA["hobby"] = []string{
"吃饭",
"睡觉",
"打豆豆",
}
mapA["work"] = []string{
"java",
"js",
"go",
}
for _, v := range mapA {
for index, value := range v {
fmt.Printf("index:%v--value:%v\n", index, value)
}
}
map的排序
map的排序默认是随机的
//1.把map的key放在切片里面
var mapA = make(map[int]int)
mapA[1]=18
mapA[3]=20
mapA[2]=19
mapA[5]=22
mapA[4]=21
var keySlice [] int
for key,_:=range mapA{
keySlice = append(keySlice,key)
}
//2.让key进行升序排序
sort.Ints(keySlice)
//3.循环遍历key输出map的值
for _,v:= range keySlice{
fmt.Printf("key:%v--value:%v",v,map[v])
}
//写一个程序,统计一个字符串中每个单词出现的次数
var str = "how do you do"
var strSlice = strings.Split(str, " ")
fmt.Println(strSlice)
var strMap = make(map[string]int)
for _, v := range strSlice {
strMap[v]++
}
fmt.Printf("strMap: %v\n", strMap)
10.函数
函数在go语言中是一级公民所有的功能单元都定义在函数中,可以重复使用。函数包含函数的名称,参数列表
和返回值类型,这些构成了函数的签名(signature)
go语言中函数特性
- go语言中有3种函数:普通函数,匿名函数(没有名称的函数),方法(定义在struct上的函数)。
- go语言中不允许函数的重载(overload),也就是说不允许函数同名。
- go语言种的函数不能嵌套函数,但可以嵌套匿名函数。
- 函数是一个值,可以将函数复制给变量,使得这个变量也成为函数
- 函数可以作为参数传递给另一个函数
- 函数的返回值可以是一个函数
- 函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,再将副本传递给函数
- 函数参数可以没有名称
go语言函数定义语法
函数在使用之前必须先定义,可以调用函数来完成某个任务。函数可以重复调用,从而达到代码重用
func function_name([parameter list])[return_types]{
函数体
}
说明:
-
func:函数由func开始声明 -
function_name:函数名称,函数名和参数列表一起构成了函数签名 -
[parameter list]:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型,顺序,及参数个数。参数是可选的,也就是说函数也可以不包括参数。
-
return_types:返回类型,函数返回一列值。return_types是该列值的数据类型。有些功能不需要返回值,这种情况下return_types不是必须的 -
函数体:函数定义的代码集合
函数的返回值
函数可以有0或多个返回值,返回值需要指定数据类型,返回值通过return关键词来指定
return可以有参数,也可以没有参数,这些返回值可以有名称,也可以没有名称。go种的函数可以有多个返回值。
-
return关键词中指定了参数时,返回值可以不用名称。如果return省略参数,则返回值部分必须带名称 -
当返回值有名称时,必须使用括号包围,逗号分割,即使只有一个返回值
-
但即使返回值命名了,
return中也可以强制指定其他返回值名称,也就是说return的优先级更高 -
命名的返回值是预先声明好的,在函数内部可以直接使用,无需再次声明。命名返回值的名称不能和函数参数名称相同
否则报错提示变量重复定义
-
return中可以有表达式,但不能出现赋值表达式,这和其他语言可能有所不同。例如return a+b是正确的但
return c=a+b是错误的
go语言函数返回值实例
没有返回值
func f1(){
fmt.Printf("没有返回值,只是进行一些计算")
}
有一个返回值
func sum(a int,b int)(ret int){
ret = a + b
return ret//return
}
func sum(a int,b int)(int){
ret:=a+b
return ret
}
多个返回值,且在return中指定返回的内容
func f2()(name string,age int){
name = "小明"
age=30
return name,age
}
多个返回值,返回值名称没有被使用
func f3()(name string,age int){
name = "小明"
age=30
return //等价于 return name,age
}
总结:
-
返回值类型 声明了返回值变量如果和函数内用的一样 则函数体内可以直接使用该变量 且可以直接return结果
-
返回值类型那部分可以直接写数据类型不写变量名称 这样函数体内部需要重新声明 不能直接用return返回结果
-
go中经常会使用其中一个返回值作为函数是否执行成功,是否有错误信息的判断条件。例如
return value,existsreturn value,ok,return value,err -
当函数的返回值过多时,例如有4个以上的返回值,应该将这些返回值收集到容器中,然后以返回容器的方式去返回。例如
同类型的返回值可以放进slice中,不同类型的返回值可以放进map中
-
但函数有多个返回值时,如果其中某个或某几个返回值不想使用,可以通过下划线
_来丢弃这些返回值。例如下面的
f1函数两个返回值,调用该函数时,丢弃了第二个返回值b,只保留了第一个返回值a赋值给了变量a。func test()(a int,b int){ a=2 b=3 return } func main(){ a,_:=test() }
go函数的参数
go语言函数可以有0或多个参数,参数需要指定数据类型
声明函数的参数列表叫做形参,调用时传递的参数叫做实参。
go语言是通过传值的方式传参的,意味着传递给函数的是拷贝后的副本,所以函数内部访问,修改的也是这个副本
不修改原值
go语言可以使用边长参数,有时候不能确定参数的个数,可以使用变长参数,可以在函数定义语句的参数部分使用
ARGS...TYPE。这时会将...代表的参数全部保存到一个名为ARGS的slice中,注意这些参数的数据类型都是TYPE
演示参数传递,按值传递
func test2(a int, b int) {//形参列表
temp := a
a = b
b = temp
}
func main() {
a := 2
b := 3
fmt.Println(a, b)//实参列表
test2(a, b)
fmt.Println(a, b)
}
从运行结果可以看到,调用函数test后,a,b的值并没有被改变,说明参数传递是拷贝了一个副本,也就是拷贝了一份新的内容进行运算
map,slice,interface,channel这些数据类型本身就是指针类型的,所以就算是拷贝传值也是拷贝的指针,拷贝后的参数仍然指向
底层数据结构,所以修改它们可能会影响外部数据结构的值
演示参数传递,按址传递
func test3(a []int) {
a[0] = 100
}
func main() {
a := []int{1, 2, 3}
test3(a)
fmt.Println(a)
}
变长参数
func f1(args ...int){
for_,v:=range args{
fmt.Println(v)
}
}
func f2(name string,age int,args ...int){
fmt.Println(name)
fmt.Println(age)
for _,v:= range args{
fmt.Println(v)
}
}
func main(){
f1(1,2,3)
fmt.Println("-------")
f1(1,2,3,4,5,6)
fmt.Println("-------")
f2("小明",20,1,2,3)
}
go语言函数类型和函数变量
可以使用type关键字来定义一个函数类型,语法格式如下:
type fun func(int,int)int
上面语句定义了一个fun函数类型,它是一种函数类型,这种函数接收两个int类型的参数并返回一个int类型的返回值
下面我们定义两个这样结构的两个函数,一个求和,一个比较大小:
func sum (a int,b int)int{
return a+b
}
func max(a int, b int)int{
if a>b {
return a
} else {
return b
}
}
下面定义一个fun函数类型,把sum和max赋值给它
type fun func(int, int) int//fun = func(int,int) int
func sum(a int, b int) int {
return a + b
}
func max(a int, b int) int {
if a > b {
return a
} else {
return b
}
}
func main() {
var f fun//f = fun = func(int,int)int
f = sum
s := f(1, 2)
fmt.Println(s)
f = max
m := f(3, 4)
fmt.Println(m)
}
go语言的高阶函数
go语言的函数,可以作为函数的参数,传递给另外一个函数,可以作为另外一个函数的返回值返回
go语言函数作为参数
func sayHello(name string){
fmt.Printf("Hello,%s",name)
}
func f1(name string,f func(string)){
f(name)
}
func main(){
f1("tom",sayHello)//Hello,tom
}
go语言函数作为返回值
func add(a int, b int) int {
return a + b
}
func sub(a int, b int) int {
return a - b
}
func cal(operator string) func(int, int) int {
switch operator {
case "+":
return add
case "-":
return sub
default:
return nil
}
}
func main() {
f1 := cal("+")
add := f1(1, 2)
f2 := cal("-")
sub := f2(2, 1)
fmt.Println(add)
fmt.Println(sub)
}
go语言的匿名函数
go语言函数不能嵌套,但是在函数内部可以定义匿名函数,实现一下简单功能调用。
所谓匿名函数就是,没有名称的函数
语法格式如下:
func (参数列表)(返回值)
当然可以没有参数列表和返回值
匿名函数实例
func main(){
max:=func(a,int,b int)int{
if a>b{
return a
}else{
return b
}
}
i:=max(1,2)
fmt.Println(i)
}
自己执行
func main(){
func(a int,b int){
max:=0
if a>b{
max=a
}else{
max=b
}
fmt.Println(max)
}(1,2)//func(){}可以看作是一个函数名称如f1 f1里是函数的执行过程 即 func main{ f1(1,2)}自动执行
}
func test1(){
name:="tom"
age:="18"
f1:=func()string{
return name+age
}//f1是变量,变量内容是匿名函数,不是返回值。f1调用之后f1()才有返回值
msg:=f1()
fmt.Println(msg)
}
闭包
闭包可以理解成定义在一个函数内部的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。或者说
是函数和其引用环境的组合体。
闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境。
注意:
-
全局变量特点:常驻内存 污染全局
-
局部变量特点:不常驻内存 不污染全局
-
闭包:可以让一个变量常驻内存 可以让一个变量不污染全局
-
闭包是指有权访问另一个函数作用域中的变量的函数
创建闭包的常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量
由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存。过度使用闭包会导致
性能下降,建议在非常有必要的时候才使用闭包
func add() func(int) int{
var x int
return func(y int) int{
x+=y
return x
}
}
func main(){
var f = add()
fmt.Println(f(10))//10
fmt.Println(f(20))//30
fmt.Println(f(30))//60
fmt.Println("-------")
f1:=add()
fmt.Println(f1(50))//50
fmt.Println(f1(60))//110
}
递归
函数内部调用函数自身的函数称为递归函数
使用递归函数最重要的三点:
- 递归就是自己调用自己
- 必须先定义函数的退出条件,没有退出条件,递归将成为死循环
- go语言递归函数可能会产生一大堆的
goroutine,也很可能会出现栈空间内存溢出的问题
go语言递归实例
阶乘
func f1(n int)int{
//返回条件
if n==1{
return 1
}else{
//自己调用自己
return n*f1(n-1)
}
}
func main(){
n:=5
r:=f1(n)
fmt.Println(r)
}
斐波那契数列
它的计算公式为f(n)=f(n-1)+f(n-2)且f(2)=f(1)=1
func f2(n int)int{
//返回条件
if n==1||n==2{
return 1
}else{
return f(n-1)+f(n-2)
}
}
func main(){
r:=f2(5)
fmt.Println(r)
}
defer panic recover
go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句
按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。
defer特性
- 关键字
defer用于注册延迟调用 - 这些调用直到
return前才被执行。因此,可以用来做资源清理 - 多个
defer语句,按先进后出的方式执行 - defer语句中的变量,在defer声明就决定了
defer用途
- 关闭文件句柄
- 锁资源释放
- 数据库连接释放
go语言defer语句实例
//查看执行顺序
func main(){
fmt.Println("start")//1
defer fmt.Println("step1")//5
defer fmt.Println("step2")//4
defer fmt.Println("step3")//3
fmt.Println("end")//2
}
panic和recover
go语言目前是没有异常机制,但是使用panic/recover模式来处理错误。
panic可以在任何地方引发,但recover只有在defer调用的函数有效
func fn1(a int,b int){
defer func(){
err:=recover()
if err!=nil{
fmt.Println("error",err)
}
}()
fmt.Println(a/b)
panic("这里有异常")
}
func main(){
fn1(10,0)
fmt.Println("end...")
}
init函数
go语言有一个特殊的函数init函数,优先于main函数执行,实现包级别的一些初始化操作
init函数的主要特点
init函数陷于main函数自动执行,不能被其他函数调用init函数没有参数和返回值- 每个包包含有多个
init函数 - 包的每个源文件也可以有多个
init函数,这点比较特殊 - 同一个包的
init执行顺序,go没有明确定义,编程时要注意程序不要依赖这个执行顺序 - 不同包的
init函数按照包导入的依赖关系决定执行顺序
go初始化顺序
初始化顺序:变量初始化->init()->main()
var i int = initVar()
func init() {
fmt.Println("init...")
}
func initVar() int {
fmt.Println("初始化变量...")
return 200
}
func main() {
fmt.Println("main...")
}