运算符
Go语言内置的运算符:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
算数运算符
| 运算符 | 描述 |
|---|---|
| + | 相加 |
| - | 相减 |
| * | 相乘 |
| / | 相除 |
| % | 取余 |
++ 和 --在Go语言里是单独的语句,不是运算符,与JavaScript不同。
关系运算符
| 运算符 | 描述 |
|---|---|
| == | 两个值是否相等,如果相等返回 True 否则返回 False |
| != | 两个值是否不相等,如果不相等返回 True 否则返回 False |
| 左边是否大于右边 | |
| >= | 左边是否大于等于右边 |
| < | 左边是否小于右边 |
| <= | 左边是否小于等于右边 |
逻辑运算符
| 运算符 | 描述 |
|---|---|
| && | AND |
| || | OR |
| ! | NOT |
位运算符
| 运算符 | 描述 |
|---|---|
| & | 参与运算的两数各对应的二进位相与 |
| | | 参与运算的两数各对应的二进位相或 |
| 参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1 |
赋值运算符
| 运算符 | 描述 |
|---|---|
| = | 简单的赋值 |
| += | 相加赋值 |
| -= | 相减赋值 |
| *= | 相乘赋值 |
| /= | 相除赋值 |
练习题
有一堆数字,如果除了一个数字以外,其他数字都出现了两次,那么如何找到出现一次的数字?
// 我的解法
numarr := [...]int{1,1,2,2,3,3,4}
for i:=0;i<len(numarr);i++ {
flag := true
for j:=0;j<len(numarr);j++ {
if numarr[i]==numarr[j] && i!=j {
flag = false
break
}
}
if flag {
fmt.Println(numarr[i], ",只出现了一次")
break
}
}
// 比较好的解法
numarr := [...]int{1,1,2,2,3,3,4}
res := 0
for _,item := range numarr{
res ^= item
}
fmt.Println(res)
流程控制
if else
Go语言中if语句格式:
if 表达式1 {
分支1
} else if 表达式2 {
分支2
} else {
分支3
}
switch case
基本格式
switch 值 {
case 值1:
分支1
case 值2:
分支2
default:
默认分支
}
多个case值
一个分支可以有多个值,多个case值之间用,隔开
switch 值 {
case 值1,值2,值3:
分支1
case 值4,值5,值6:
分支2
default:
默认分支
}
### 分支表达式
switch {
case 表达式1:
分支1
case 表达式2:
分支2
default:
默认分支
}
fallthrough
fallthrough可以在满足一个case的条件下,继续向下执行case分支。
for循环
基本格式
for 初始语句;表达式;结束语句 {
循环体语句
}
无限循环
for {
循环体语句
}
for循环可以通过break、goto、return、panic语句强制退出循环。
for range键值循环
Go语言中可以使用for range遍历数组、切片、字符串、map、通道(channel),返回值有以下规律:
- 数组、切片、字符串返回索引和值(即index和item)
- map返回键和值
- channel只返回通道内的值 类似JavaScript中的map、forEach等循环。
goto(跳转到指定标签)
goto语句可以快速跳出循环,避免重复退出。
break(跳出循环)
break语句可以结束for、switch、select的代码块。
continue(继续下一次循环)
continue可以结束当前循环,立即开始执行下一次循环,只能在for循环内使用。
练习题
打印9*9乘法表
// 我的解法
for i:=1;i<=9;i++ {
for j:=1;j<=i;j++ {
fmt.Printf("%v*%v=%v ", j, i, j*i)
}
fmt.Println()
}
数组(array)
数组定义
var 变量名称 [元素数量]类型
举个栗子🌰
// 定义一个长度为3,元素类型为int的数组a
var a [3]int
与JavaScript不同的是,数组的长度是必须常量,一旦定义,不能改变。比如[5]int和[3]int就是不同的类型。
var a [5]int
var b [3]int
a = b // 不可以这样
数组可以通过下标进行访问,从0开始,到len-1结束。
数组初始化
方法一
var arr1 [3]int // [0 0 0]
var arr2 [4]int{1,2,3} // [1 2 3 0]
var arr3 [5]string{"北京","上海","广州","深圳","杭州"} // [北京 上海 广州 深圳 ]
方法二
可以让编译器根据初始值的个数自行推断数组的长度。
var arr1 = [...]int{1,2,3,4} //[1 2 3 4]
var arr2 = [...]string{"男","女"} //[男 女]
var arr3 = [...]bool{true, false} // [true false]
方法三
指定索引值初始化数组。
var arr1 = [...]int{2:22,5:55} // [0 0 22 0 0 55]
数组的遍历
遍历数组有两种方法:
// 方法一
var arr = [...]int{1,2,3,4,5}
for i:=0; i<len(arr); i++ {
fmt.Printf("当前索引:%v; 当前项:%v \n", i, arr[i])
}
// 方法二
for index,item := range arr {
fmt.Printf("当前索引:%v; 当前项:%v \n", index, item)
}
多维数组
二维数组的定义
var arr = [3][2]string{
{"北京","上海"},
{"广州","深圳"},
{"杭州","西安"},
}
fmt.Print(arr)//[[北京 上海] [广州 深圳] [杭州 西安]]
二维数组的遍历
var arr = [3][2]string{
{"北京","上海"},
{"广州","深圳"},
{"杭州","西安"},
}
// 方法一
for i:=0;i<len(arr);i++ {
for j:=0; j<len(arr[i]);j++ {
fmt.Println(arr[i][j])
}
}
// 方法二
for _,item1 := range arr {
for _,item2 := range item1 {
fmt.Println(item2)
}
}
多维数组只有第一层可以使用...来让编译器推导长度。
数组是值类型
数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。(即JavaScript中的Array为引用数据类型)
func main() {
var arr = [3]int{10,20,30}
modifyArr(arr)
var arr2 = [3][2]int{
{1,1},
{1,1},
{1,1},
}
modifyArr2(arr2)
fmt.Println("main函数里的arr",arr2)
}
func modifyArr(x [3]int) {
x[0] = 100
}
func modifyArr2(x [3][2]int) {
x[2][0] = 100
fmt.Println("modifyArr2中的arr",x)
}
练习题
- 求数组
[1, 3, 5, 7, 8]所有元素的和
// 我的解法
func reduceArr(arr [5]int) {
var count int = 0
for _, c := range arr{
count += c
}
fmt.Print(count)
}
- 找出数组中和为指定值的两个元素的下标,比如从数组
[1, 3, 5, 7, 8]中找出和为8的两个元素的下标分别为(0,3)和(1,2)。
// 我的解法
func findIndex(num int, arr [5]int) {
for i:=0;i<len(arr);i++ {
for j:=i+1; j<len(arr);j++ {
if arr[i]+arr[j] == num {
fmt.Printf("(%v,%v)", i, j)
}
}
}
}
切片(slice)
数组有很多局限性,长度固定,不能添加新的元素到数组里,所以有了切片(slice),一个切片,可以看作是由:一个左指针和一个右指针以及capacity组成。
基本概念
切片是一个拥有相同类型元素的可变长度的序列,是基于数组的一层封装,非常灵活。
切片是引用类型的,内容结构包含了地址、长度、容量。切片一般用于快速的操作一块数据集合。
基本语法
var 变量名 []元素类型
举个栗子🌰
var a []string
var b = []int{}
var c = []bool{false, true}
var d = []bool{false, true}
切片的长度和容量
len()求长度cap()求容量
切片表达式
切片表达式中low和high表示一个索引范围(包头不包尾),举个栗子🌰
var arr = [5]int{1,2,3,4,5}
var arr2 = arr[0,4]
fmt.Printf("arr2:%v;len:%v;cap:%v", arr2, len(arr2), cap(arr2))
//arr2:[1 2 3 4];len:4;cap:5
切片表达式中的索引可以省略,low默认为0,high默认为len(数组)
arr[2:0] //等于 arr[2:len(arr)]
arr[:3] // 等于 arr[0:3]
arr[:] // 等于 arr[0:len(arr)]
注意
对于数组和字符串的切片,0 <= low <= high <= len(arr),索引合法。
对于切片切片,high的上限边界是切片的容量,而不是长度。
var arr = [5]int{1,2,3,4,5}
var s1 = arr[1:3]
fmt.Printf("s1:%v;len:%v;cap:%v", s1, len(s1), cap(s1))
fmt.Println()
var s2 = s1[1:4] // 索引的上限是cap(s1)而不是len(s1)
fmt.Printf("s2:%v;len:%v;cap:%v", s2, len(s2), cap(s2))
cap函数
从数组中取
cap函数只计算从左指针到原数组最后的值,也就是low到len(arr)的个数,即len(arr) - low
从切片中取
举个栗子🌰
var arr = [9]int{1,2,3,4,5,6,7,8,9}
var s1 = arr[2:7] // 此时左指针偏移两位,cap(s1)是7,len(s1)是5, s1是[3 4 5 6 7]
var s2 = s1[3:4] // 此时左指针再次偏移三位,cap(s2)是4,len(s2)是1,s2是[6]
var s3 = s2[2:4] // 此时左指针再次偏移两位,cap(s3)是2,len(s3)是2,s3是[8 9]
使用make函数构造切片
不基于数组,动态创建一个切片,基本格式:
make([]元素类型,元素数量,切片容量)
举个栗子🌰
var s1 = make([]int, 2, 10)
//s1:[0 0] len(s1):2,cap(s1):10
判断切片是否为空
始终使用len(s1) == 0来判断
切片的赋值拷贝
s1 := make([]int,3)
s2 := s1 // s1与s2共用一个底层数组
s1[0] = 100
fmt.Println(s1) // [100 0 0]
fmt.Println(s2) // [100 0 0]
切片的遍历
var s1 = []int{1,2,3,4}
for index,item := range s1 {
fmt.Println(index, item)
}
append()添加元素到切片
var s1 = []int{1,2,3}
s1 = append(s1, 4) // [1 2 3 4]
s1 = append(s1, 5,6,7) // [1 2 3 4 5 6 7]
var s2 = []int{8,9}
s1 = append(s1, s2...) // [1 2 3 4 5 6 7 8 9]
每个切片都会指向一个底层数组,数组容量够用就添加新元素。底层数组不能容纳新增元素时,切片会自动按照一定的策略“扩容”,此时,该切片指向的底层数组就会更换。
举个栗子🌰
var s1 = []int
for i=0;i<10;i++ {
s1 = append(s1, i)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", s1, len(s1), cap(s1), s1)
}
输出:
[0] len:1 cap:1 ptr:0xc00010c008
[0 1] len:2 cap:2 ptr:0xc00010c030
[0 1 2] len:3 cap:4 ptr:0xc00011e020
[0 1 2 3] len:4 cap:4 ptr:0xc00011e020
[0 1 2 3 4] len:5 cap:8 ptr:0xc000120040
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc000120040
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc000120040
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc000120040
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc000122000
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc000122000
结论:
- append() 函数将元素追加到切片的最后,并返回切片
- s1的容量每次扩容后都是扩容前的2倍
copy()函数复制切片
切片直接赋值指向同一底层数组,利用copy可以将一个切片的数据复制到另外一个切片空间中。
copy(目标切片, 源切片 []元素类型)
举个栗子🌰
var s1 = []int{1,2,3,4,5}
var s2 = make([]int, 5)
copy(s2, s1)
fmt.Println(s1,s2) // [1 2 3 4 5] [1 2 3 4 5]
s1[0] = 100
s2[0] = 999
fmt.Println(s1,s2) // [100 2 3 4 5] [999 2 3 4 5]
注意:
- copy函数
目标切片需要先初始化长度 源切片中的元素类型为引用类型时,拷贝的是引用 举个栗子🌰
// 1.
var s1 = []int{1,2,3,4,5}
var s2 []int
copy(s2, s1)
fmt.Print(s1, s2) // [1 2 3 4 5] []
// 2.
var s1 = [][]int{
{1, 1},
{1, 1},
{1, 1}
}
var s2 = make([][]int, 5)
copy(s1, s2)
s1[0][0] = 100
fmt.Print(s1, s2) // [[100 1] [1 1] [1 1]] [[100 1] [1 1] [1 1] [] []]
正确的拷贝二维数组的方法为:
// 先初始化,再拷贝
var s1 = [][]int{
{1,1},
{1,1},
{1,1},
}
var s2 = make([]int, 3)
for index,item := range s1 {
s2[index] = make([]int, len(item))
copy(s2[index], item)
}
从切片中删除元素
Go语言中没有专门删除切片元素的方法......不像JavaScript,有splice、pop、shift。但是可以使用切片本身的特性来删除元素。
var s1 = []int{1,3,4,5,6,7}
// 要删除索引为2的元素
s1 = append(a[:2], a[3:]...)
fmt.Println(s1)
总结一下就是:要从切片s1中删除索引为index的元素,操作方法是s1 = append(s1[:index], s1[index+1:]...)
练习题
- 请写出下面代码的输出结果
var s1 = make([]string, 5, 10) // s1:["" "" "" "" ""] len(s1):5 cap(s1):10
for i:=0; i < 10; i++ {
s1 = append(s1, fmt.Sprintf("%v", i))
}
fmt.Println(s1) // ["" "" "" "" "" 0 1 2 3 4 5 6 7 8 9]
- 请使用内置的
sort包对数组var s1 = [...]int{3, 7, 8, 9, 1}进行排序。
package main
import (
"fmt"
"sort"
)
func main() {
var arr = [...]int{3,7,8,9,1}
var s1 = arr[:]
sort.Ints(s1)
fmt.Println(s1)
}
- 请写出下面代码的输出结果
s := []int{5}
s = append(s, 7)
s = append(s, 9)
x := append(s, 11)
y := append(s, 12)
fmt.Println(s, x, y)
这个题目非常的有意思,以下是我的个人理解:
- 首先定义切片
s:[5],len:1,cap: 1,sP: 0x001... append操作向切片s中添加一项7,此时len 2 > cap 1,触发扩容,产生新的数组,切片s:[5 7],len:2,cap: 2,sP: 0x002...append操作向切片s中添加一项9,此时len 3 > cap 2,触发扩容,产生新的数组,切片s:[5 7 9],len:3,cap: 4,sP: 0x003...- 定义切片
x,append操作向切片s中添加一项11,此时len 4 == cap 4,未触发扩容,不产生新的数组,切片s:[5 7 9],len:3,cap: 4,sP: 0x003...。 切片x:[5 7 9 11],len:4,cap: 4,sP: 0x003... - 定义切片
y,append操作向切片s中添加一项12,此时len 4 == cap 4,未触发扩容,不产生新的数组,切片s:[5 7 9],len:3,cap: 4,sP: 0x003...。 切片x:[5 7 9 12],len:4,cap: 4,sP: 0x003...重点:此时!len:4 cap4 sp:0x003变成了[5 7 9 12],所以切片x与切片y都是[5 7 9 12]。
演变一下:
s := []int{5} // s[5] slen:1 scap:1 sp:0x001
s = append(s, 7) // s[5 7] slen:2 scap:2 sp: 0x002
s = append(s, 9) // s[5 7 9] slen:3 scap:4 sp: 0x003
s = append(s, 11) // s[5 7 9 11] slen: 4 scap:4 sp: 0x003
x := append(s, 13) // s[5 7 9 11] slen:4 scap:4 scp: 0x003; x[5 7 9 11 13] xlen:5 xcap:8 xp: 0x101
y := append(s, 15) // s[5 7 9 11] slen:4 scap:4 scp: 0x003; y[5 7 8 11 15] ylen: 4 ycap:8 yp: 0x102
总结:
切片是切片,数组是数组,切片是数组一个片段的描述,决定性因素有三个:长度、容量、指针,其中:
长度指的是切片引用的数目容量指的是底层数组的元素数目指针指的是切片指向哪一个底层数组 对切片的操作还有一个点:- 某次操作未超过该切片的cap,此次会更新改变原数组
- 某次操作超过了该切片的cap,此次操作会新建数组,不会改变原数组
map
map是一种无序的基于key-value的数据结构,类似JavaScript中的object,是引用类型,必须初始化才能使用。
map的定义
语法如下:
map[键的类型]值的类型
map类型的变量初始值为nil,需要使用make()函数为其分配内存,语法如下:
make(map[键的类型]值的类型,[cap])
其中cap是map的容量,不必传。
map的基本使用
🌰
m1 := make(map[string]int, 8)
m1["李四"] = 12
m1["张三"] = 29
fmt.Println(m1, reflect.TypeOf(m1)) // [张三:12 李四:29] map[string]int
map也支持在声明时填充元素,举个🌰
var m1 = map[string]string{
"username": "HoMeTown",
"passowrd": "123456",
}
fmt.Println(m1)
判断某个键是否存在
Go语言中判断map中是否存在某个键的写法比较特殊,不像JavaScript或者python中可以使用in关键字来判断,写法如下:
value, ok = map[key]
举个🌰
var m1 = map[string]string{
"username": "admin",
"password": "123456",
}
var v,ok = m1["username"]
if ok {
fmt.Println(v)
} else {
fmt.Println("不存在username")
}
map的遍历
使用for range遍历map,写法如下:
var m1 = map[string]string{
"username": "admin",
"password": "123456",
"gender": "0",
"email": "admin123@gmail.com",
}
for key,val := range m1 {
fmt.Println(key,val)
}
只遍历key的话,可以这样:
for key := range m1 {
fmt.Println(key)
}
delete()函数删除键值对
格式如下:
delete(map, key)
举个🌰
var m1 = map[string]string{
"username": "admin",
"password": "123456",
"gender": "0",
"email": "admin123@gmail.com",
}
fmt.Println(m1)
delete(m1, "email")
fmt.Println(m1)
元素为map类型的切片
var mapS1 = make([]map[string]string,3)//make一个元素为map的切片
mapS1[0] = make(map[string]string,10)
mapS1[0]["name"] = "小王"
mapS1[0]["age"] = "12"
mapS1[0]["address"] = "北京市"
fmt.Println(mapS1)
值为切片的map
var sliceM1 = make(map[string][]string, 4)
sliceM1["names"] = make([]string, 4)
sliceM1["names"] = append(sliceM1["names"], "小王","小黄","小李")
fmt.Println(sliceM1)
练习题
- 写一个程序,统计一个字符串中每个单词出现的次数。比如:”how do you do”中how=1 do=2 you=1。
// 我的解法
var words string = "how do you do"
var wordsSlice []string = strings.Split(words, " ")
var wordCountMap map[string]int = make(map[string]int)
for _, word := range wordsSlice{
var _, isIn = wordCountMap[word]
if isIn {
wordCountMap[word]++
} else {
wordCountMap[word] = 1
}
}
var printMsg = "在"how do you do"中"
for key,val := range wordCountMap {
str:= key + "=" + strconv.Itoa(val) + ";"
printMsg += str
}
fmt.Print(printMsg)
- 观察下面代码,写出最终的打印结果。
type Map map[string][]int
m := make(Map) //[]
s := []int{1,2} // [1 2] len 2 cap 2 x001
s = append(s,3) [1 2 3] len 3 cap 4 x002
fmt.Printf("%+v \n", s) //[1 2 3] len3 cap4 x002
m["q1mi"] = s // [q1mi: [1 2 3] len3 cap4 x002]
s = append(s[:1], s[2:]...) //[1 3] len 2 cap4 x002
fmt.Printf("%+v\n",s) //len2 cap4 x002 [1 3]
fmt.Printf("%+v\n",m["q1mi"])// len3 cap4 //[1 3 3] x002
[1 2 3] [1 3] [1 3 3]
函数
Go语言中支持函数、匿名函数和闭包。
函数定义
格式如下:
func 函数名(参数)(返回值){
函数体
}
举个🌰
func main(){
fmt.Println(intSum(1,2)) //3
}
func intSum(x int, y int)(int) {
return x + y
}
参数
类型简写
函数参数中相邻变量的类型相同,则可以省略类型,举个🌰
func intSum(x,y int) int {
return x + y
}
可变参数
指函数的参数数量不固定,通过在参数名后面加...来表示。
注意:可变参数通常要作为函数的最后一个参数,举个🌰
func intSum(x ...int) int {
sum := 0
for _,v := range x {
x += sum
}
return sum
}
也可以与固定参数搭配使用
func intSum(x int, y ...int) int {
sum := x
for _,v := range x {
x += sum
}
return sum
}
本质上,函数的可变参数是通过切片来实现的。
返回值
Go语言通过return关键字向外输出返回值,这一点与JavaScript相同,但也有不同点。
多个返回值
func calc(x, y int) (int, int) {
sum := x + y
sub := x - y
return sub, sum
}
返回值命名
函数在定义时可以给返回值命名,并在函数体重使用这些变量,最后用return返回
func calc(x, y int) (sub, sum int) {
sum = x + y
sub = x - y
return
}
返回值补充
当函数返回值类型为slice是,nil可以看做是一个有效的slice,没必要返回一个长度为0的切片。
func someFunc(x string) []int {
if x == "" {
return nil
} else {
res := make([]int,4)
return res
}
}
变量作用域
全局变量
全局变量指的是定义在函数外部的变量,程序运行期间一直都可以有效访问,与JavaScript的全局变量类似。
var commonNum int = 100 // 全局变量 commonNum
func main() {
fmt.Println("num:", num) //100
}
func testCommonVar() {
fmt.Println("num": num) //100
}
局部变量
如果全局变量与局部变量重名,那么优先访问局部变量。
var num = 1
func main() {
num := 10
fmt.Println(num) // 10
}
语句块定义的变量(类似JavaScript中的块级作用域),比如for, if等,举个🌰
func testLocalVar(x, y int) {
fmt.Println(x,y)// 函数的参数也只在函数内生效
if x>0 {
z := 10
fmt.Println(z)// z只能在此处生效
}
fmt.Println(z) //不生效
}
函数类型与变量
定义函数类型
可以使用type关键字定义一个函数的类型,格式如下:
type calculation func(int, int) int
上面语句定义了一个calculation类型,是一种函数类型,这种函数接受两个int类型的参数并返回一个int类型的返回值。
也就是说,凡是满足这个条件的函数,都是calculation类型的函数,举个🌰
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
add和sub都能赋值给calculation类型的变量。
var c calculation
c = add
c(3,4) //7
函数类型变量
可以声明函数类型的变量并且为该变量赋值
var c calculation
c = add
fmt.Printf("type of c: %T\n", c) // main.calculation
fmt.Println(c(1,2))
f := add
fmt.Printf("type of f: %T\n", f) // func(int, int) int
fmt.Println(c(2,3))
高阶函数
高阶函数分为函数作为参数和函数作为返回值两部分
函数作为参数
func add(x, y int) int {
return x+y
}
func calc(x, y int, callback func(int, int) int) int {
return callback(10, 20)
}
func main() {
res := calc(10, 20, add)
fmt.Println(res) //30
}
函数作为返回值
func do(s string) (func(int, int) int, error) {
switch s {
case "+":
return add, nil
case "-":
return sub, nil
default:
err := errors.New("无法识别的操作符")
return nil, err
}
}
匿名函数和闭包
匿名函数
格式如下:
func(参数)(返回值){
函数体
}
匿名函数因为没有函数名,所以不能直接调用,需要保存到某个变量里,或者作为立即执行函数。
add := func(x, y) int {
return x + y
}
add(10, 20)
func(x, y) {
fmt.Println(x + y)
}(1, 2)
匿名函数多用于实现回调函数或者闭包
闭包
闭包值得是一个函数与其相关的引用环境组合而成的实体。简单来说就是:闭包=函数+引用环境,举个🌰
func add() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
f := add()
fmt.Println(f(10))//10
fmt.Println(f(20))//30
f1 := add()
fmt.Println(f1(20))//20
fmt.Println(f1(40))//60
与JavaScript相似,函数体内的变量如果被函数体以外的环境引用,就会形成闭包,该变量不会销毁。
闭包示例2:
func makeSuffixFunc(suffix string) func(string) string {
return func(fileName string) string {
if !strings.HasSuffix(fileName, suffix) {
return fileName + suffix
}
return fileName
}
}
闭包示例3:
func calc(base int) (func(int) int, func(int) int) {
add := func(num int) int {
base += num
return base
}
sub := func(num int) int {
base -= num
return base
}
return add, sub
}
add, sub := calc(10)
fmt.Println(add(1)) // 11
fmt.Println(add(2)) // 9
defer语句
defer语句会将其后面跟孙的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先defer的后执行,后defer的先执行,举个🌰
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println(4)
fmt.Println("end")
// start 4 end 3 2 1
多用于处理资源释放问题,例如:资源清理,文件关闭,解锁,记录时间等。
defer的执行时机
返回值=x --> 运行defer --> ret指令
牢记defer的三条规则
- 延迟函数的参数在defer语句出现的时候就已经确定下来了
- 后进先出的规则
- 延迟函数可能会影响主函数的具名返回值
defer案例
// 主函数拥有匿名返回值,返回变量
//一个主函数拥有一个匿名的返回值,返回使用本地或全局变量,这种情况下defer语句可以引用到返回值,但不会改变返回值。
// 返回5
func f1() int {
x := 5
defer func() {
x++
}()
return x
}
// 主函数拥有具名返回值
// 主函声明语句中带名字的返回值,会被初始化成一个局部变量,函数内部可以像使用局部变量一样使用该返回值。如果defer语句操作该返回值,可能会改变返回结果。
// x =5
// x++
// return
func f2() (x int) {
defer func() {
x++
}()
return 5
}
// 主函数拥有具名返回值
// x = 5
// y = x
// x++
// return
func f3() (y int) {
x := 5
defer func() {
x++
}()
return x
}
// 主函数拥有具名返回值,但是延迟函数的参数从defer开始就确定了,相当于拷贝一份,所以后面的x++是自执行函数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
}
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))
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
内置函数
| 函数名 | 介绍 |
|---|---|
| close | 用来关闭channel |
| len | 用来求长度,比如string array slice map channel |
| new | 用来分配内存,主要用来分配值类型,比如int, struct。返回的是指针 |
| make | 用来分配内存,主要用来分配引用类型,比如chan map slice |
| append | 用来追加元素到数组中、slice中 |
| panic和recover | 用来做错误处理 |
panic/recover
panic用来处理错误,可以在任何地方引发,但recover只有在defer调用的函数中有效,举个🌰
func funcA() {
fmt.Println("func A")
}
func funcB() {
panic("func B error")
}
func funcC() {
fmt.Println("func C")
}
func main() {
funcA()
funcB()
funC()
}
这时候程序就会崩溃,因为funcB中引发了panic,但是,我们可以通过recover让程序起死回生!只需要对funcB做一下修改举个🌰
func funcB() {
defer func(){
err := recover()
if err != nil {
fmt.Println("func B")
}
}()
panic("func B error")
}
注意
recover必须搭配defer使用defer一定要再可能发panic的语句之前定义
练习题
- 分金币
/*
你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
分配规则如下:
a. 名字中每包含1个'e'或'E'分1枚金币
b. 名字中每包含1个'i'或'I'分2枚金币
c. 名字中每包含1个'o'或'O'分3枚金币
d: 名字中每包含1个'u'或'U'分4枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
程序结构如下,请实现 ‘dispatchCoin’ 函数
*/
var (
coins = 50
users = []string{
"Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
}
distribution = make(map[string]int, len(users))
)
func main() {
left := dispatchCoin()
fmt.Println("剩下:", left)
}
// 我的解法
func dispatchCoin() int {
coinSum := coins
for _,person := range users{
distribution[person] = 0
nameLower := strings.ToLower(person)
nameSlice := strings.Split(nameLower, "")
for _,nameWord := range nameSlice {
switch nameWord {
case "e":
distribution[person] += 1
coinSum-=1
case "i":
distribution[person] += 2
coinSum-=2
case "o":
distribution[person] += 3
coinSum-=3
case "u":
distribution[person] += 4
coinSum-=4
default:
break
}
}
}
for key,value := range distribution{
fmt.Printf("%v分到了:%v个\n",key, value)
}
return coinSum
}