运算符
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) 复制代码