本文最后更新于 2025年3月9日 晚上
继续 Go
条件语句 if,if…else,if 嵌套,switch,select,goto
select 类似于 switch,select 会随机执行可运行的 case,没有 case 可运行的话,会阻塞到有 case 可运行
if 1 2 3 4 5 6 7 8 func main () { a, b := 1 , 2 if a < b { fmt.Println("hhh" ) } else { fmt.Println("xxx" ) } }
switch 1 2 3 4 5 6 7 8 switch expr { case case1: statement1 case case2: statement2 default : default statement }
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { str := "a" switch str { case "a" : str += "a" str += "c" case "b" : str += "bb" str += "aa" default : str += "cc" } fmt.Println(str) } >>aac
还可以在表达式之前声明变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { num := 2 switch { case num >= 0 && num <= 1 : num++ case num > 1 : num-- fallthrough case num < 0 : num += num } fmt.Println(num) } >> 2
fallthrough
关键字会继续执行相邻的下一个分支,不然会自动跳出,在 go 中的 switch 相当于是 c 语言中每个 case
后面添加了 break
语句,所以会自动终止。``fallthroug它会无条件地执行下一个 case 语句,而不会进行条件检查,所以例子中的
num经过了
-,又经过了
+` , 并没有变化。
循环语句 for go 中只有一个 for
循环,一般格式
1 2 3 for init statement; expression; post statement { execute statement }
当成 while
来使用时
1 2 3 for expression { execute statement }
打印数组
1 2 3 4 5 6 7 8 9 func main () { var b int = 6 numbers := [6 ]int {1 , 2 , 3 , 5 , 7 , 9 } for a := 0 ; a < b; a++ { fmt.Print(numbers[a]) } } >> 123579
双循环
1 2 3 4 5 6 7 8 9 10 func main () { for i := 1 ; i <= 9 ; i++ { for j := 1 ; j <= 9 ; j++ { if i <= j { fmt.Printf("%d * %d = %2d " , i, j, i*j) } } fmt.Println() } }
for range for
循环的 range
格式可以对 slice、map、数组、字符串、切片、映射表,channel 等进行迭代循环,一般格式
1 2 3 for index, value := range iterable { }
数组 Array go 的数组和其他语言数组有一些区别
初始化 一维数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var arr0 [5 ]int = [5 ]int {1 , 2 , 3 }var arr1 = [5 ]int {1 , 2 , 3 , 4 , 5 }var arr2 = [...]int {1 , 2 , 3 , 4 , 5 , 6 }var str = [5 ]string {3 : "hhhhh" , 4 : "xxxxxx" }func main () { a := [3 ]int {1 , 2 } b := [...]int {1 , 2 , 3 , 4 } c := [5 ]int {2 : 100 , 4 : 200 } d := [...]struct { name string age uint8 }{ {"a" , 10 }, {"b" , 20 }, } fmt.Println(arr0, arr1, arr2, str) fmt.Println(a, b, c, d) } >> [1 2 3 0 0 ] [1 2 3 4 5 ] [1 2 3 4 5 6 ] [ hhhhh xxxxxx] [1 2 0 ] [1 2 3 4 ] [0 0 100 0 200 ] [{a 10 } {b 20 }]
多维数组 1 2 3 4 5 6 7 8 9 10 11 12 13 var arr0 [5 ][3 ]int var arr1 [2 ][3 ]int = [...][3 ]int {{1 , 2 , 3 }, {4 , 5 , 6 }}func main () { a := [2 ][3 ]int {{1 , 2 , 3 }, {7 , 8 , 9 }} b := [...][2 ]int {{1 , 1 }, {2 , 2 }, {3 , 3 }} fmt.Println(arr0, arr1) fmt.Println(a, b) } >> [[0 0 0 ] [0 0 0 ] [0 0 0 ] [0 0 0 ] [0 0 0 ]] [[1 2 3 ] [4 5 6 ]] [[1 2 3 ] [7 8 9 ]] [[1 1 ] [2 2 ] [3 3 ]]
第二维度不能用 ...
来表示,未赋值的数组初始化为 0
多维数组遍历 1 2 3 4 5 6 7 8 9 10 11 12 func main () { var a [2 ][3 ]int = [...][3 ]int {{1 , 2 , 3 }, {4 , 5 , 6 }} for k1, v1 := range a { for k2, v2 := range v1 { fmt.Printf("(%d,%d)=%d" , k1, k2, v2) } fmt.Println() } } >> (0 ,0 )=1 (0 ,1 )=2 (0 ,2 )=3 (1 ,0 )=4 (1 ,1 )=5 (1 ,2 )=6
数组拷贝和传参 值拷贝会造成性能问题,通常使用 slice 或数组指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func printArr (arr *[5]int ) { arr[0 ] = 10 for k, v := range arr { fmt.Println(k, v) } }func main () { var arr1 [5 ]int printArr(&arr1) fmt.Println(arr1) arr2 := [...]int {2 , 4 , 6 , 8 , 10 } printArr(&arr2) fmt.Println(arr2) } >>0 10 1 0 2 0 3 0 4 0 [10 0 0 0 0 ]0 10 1 4 2 6 3 8 4 10 [10 4 6 8 10 ]
为什么 arr2 数组输出的第一个值不是 2 呢,因为在进入 printArr
函数时,arr[0]=10
,所以会被赋值为 10,输出的就是 10
切片 Slice Slice 既不是数组也不是数组指针,是数组的一个引用,是引用类型。自身是结构体,值拷贝传递。长度可变,一般格式
1 2 var 变量名 []类型var str []string
创建切片 在 fun main 中
声明切片
1 2 3 4 5 6 7 8 var s1 []int if s1 == nil { fmt.Println("null" ) } else { fmt.Println("不是 null" ) }
:= 创建
1 2 s2 := []int {} fmt.Println(s2)
make() 创建
1 2 var s3 []int = make ([]int , 0 ) fmt.Println(s3)
初始化赋值
1 2 var s4 []int = make ([]int , 0 , 0 ) fmt.Println(s4)
从数组切片
1 2 3 4 5 6 7 8 9 s5 := []int {1 , 2 , 3 } fmt.Println(s5) arr := [5 ]int {1 , 2 , 3 , 4 , 5 }var s6 []int s6 = arr[1 :4 ] fmt.Println(s6) >> [1 2 3 ] [2 3 4 ]
切片初始化 len() 长度,cap() 容量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var arr = [...]int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }var slice0 []int = arr[2 :8 ]var slice1 []int = arr[0 :6 ]var slice2 []int = arr[5 :10 ]var slice3 []int = arr[0 :len (arr)]var slice4 = arr[:len (arr)-1 ]func main () { fmt.Println(arr, slice0, slice1, slice2, slice3, slice4) arr2 := [...]int {9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 } slice5 := arr[2 :8 ] slice6 := arr[0 :6 ] slice7 := arr[5 :10 ] slice8 := arr[0 :len (arr)] slice9 := arr[:len (arr)-1 ] fmt.Println(arr2, slice5, slice6, slice7, slice8, slice9) } >> [0 1 2 3 4 5 6 7 8 9 ] [2 3 4 5 6 7 ] [0 1 2 3 4 5 ] [5 6 7 8 9 ] [0 1 2 3 4 5 6 7 8 9 ] [0 1 2 3 4 5 6 7 8 ] [9 8 7 6 5 4 3 2 1 0 ] [2 3 4 5 6 7 ] [0 1 2 3 4 5 ] [5 6 7 8 9 ] [0 1 2 3 4 5 6 7 8 9 ] [0 1 2 3 4 5 6 7 8 ]
make() 创建切片 1 2 3 var slice []type = make ([]type , len ) slice := make ([]type , len ) slice := make ([]type , len , cap ) (类型,长度,容量)
1 2 3 4 5 6 7 var slice1 []int = make ([]int , 10 ) slice2 := make ([]int , 10 ) slice3 := make ([]int , 10 , 10 ) >> [0 0 0 0 0 0 0 0 0 0 ] [0 0 0 0 0 0 0 0 0 0 ] [0 0 0 0 0 0 0 0 0 0 ]
创建 slice 数组,自动分配底层数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { s1 := []int {0 , 1 , 2 , 3 , 8 : 100 } fmt.Println(s1, len (s1), cap (s1)) s2 := make ([]int , 6 , 8 ) fmt.Println(s2, len (s2), cap (s2)) s3 := make ([]int , 6 ) fmt.Println(s3, len (s3), cap (s3)) } >> [0 1 2 3 0 0 0 0 100 ] 9 9 [0 0 0 0 0 0 ] 6 8 [0 0 0 0 0 0 ] 6 6
用 make 动态创建 slice ,可以让数组不需要用常量做长度
1 2 3 4 5 6 7 8 func main () { s := []int {0 , 1 , 2 , 3 } p := &s[2 ] *p += 100 fmt.Println(s) } >> [0 1 102 3 ]
[][]T 表明元素类型为 []T,比如 [][]int 表明元素类型为 []int
1 2 3 4 5 6 7 8 9 10 func main () { data := [][]int { []int {1 , 2 , 3 }, []int {100 , 200 }, []int {11 , 22 , 33 , 44 }, } fmt.Println(data) } >> [[1 2 3 ] [100 200 ] [11 22 33 44 ]]
还可以直接修改 struct array/slice 成员
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { d := [5 ]struct { x int }{} s := d[:] d[1 ].x = 10 s[2 ].x = 20 fmt.Println(d) fmt.Printf("%p,%p\n" , &d, &d[0 ]) } >> [{0 } {10 } {20 } {0 } {0 }]0xc000010390 ,0xc000010390
数组 d
和切片 s
共享同一块底层数据,因此通过数组或切片修改数据会相互影响
append() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { var a = []int {1 , 2 , 3 } fmt.Println(a) var b = []int {4 , 5 , 6 } fmt.Println(b) c := append (a, b...) fmt.Println(c) d := append (c, 7 ) fmt.Println(d) } >> [1 2 3 ] [4 5 6 ] [1 2 3 4 5 6 ] [1 2 3 4 5 6 7 ]
append 向 slice 尾部添加数据,返回新的对象
超出原 slice.cap 的限制时,会重新分配底层数组,即使是原数组没有填满
1 2 3 4 5 6 7 8 9 10 11 12 func main () { data := [...]int {0 , 1 , 2 , 3 , 4 , 10 : 0 } s := data[:2 :3 ] s = append (s, 100 , 200 ) fmt.Println(s, data) fmt.Println(&s[0 ], &data[0 ]) } >> [0 1 100 200 ] [0 1 2 3 4 0 0 0 0 0 0 ]0xc000010390 0xc0000141e0
append 后的 s 重新分配了底层数组
slice 中 cap 重新分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func main () { s := make ([]int , 0 , 1 ) c := cap (s) for i := 0 ; i < 50 ; i++ { s = append (s, i) if n := cap (s); n > c { fmt.Printf("%d->%d\n" , c, n) c = n } } } >>1 ->2 2 ->4 4 ->8 8 ->16 16 ->32 32 ->64
通常以 2 倍容量来分配底层数组
切片拷贝 copy() 函数复制数据,以长度小的为准,长度小的复制到大的去,指向同一底层数组,允许元素区间重叠
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func main () { s1 := []int {1 , 2 , 3 , 4 , 5 } fmt.Printf("%v\n" , s1) s2 := make ([]int , 10 ) fmt.Printf("%v\n" , s2) copy (s2, s1) fmt.Printf("%v\n" , s1) fmt.Printf("%v\n" , s2) s3 := []int {1 , 2 , 3 } fmt.Printf("%v\n" , s3) s3 = append (s3, s2...) fmt.Printf("%v\n" , s3) s3 = append (s3, 4 , 5 , 6 ) fmt.Printf("%v" , s3) } >> [1 2 3 4 5 ] [0 0 0 0 0 0 0 0 0 0 ] [1 2 3 4 5 ] [1 2 3 4 5 0 0 0 0 0 ] [1 2 3 ] [1 2 3 1 2 3 4 5 0 0 0 0 0 ] [1 2 3 1 2 3 4 5 0 0 0 0 0 4 5 6 ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func main () { data := [...]int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } fmt.Println(data) s1 := data[8 :] s2 := data[:5 ] fmt.Printf("%v\n" , s1) fmt.Printf("%v\n" , s2) copy (s2, s1) fmt.Printf("%v\n" , s1) fmt.Printf("%v\n" , s2) fmt.Println(data) } >> [0 1 2 3 4 5 6 7 8 9 ] [8 9 ] [0 1 2 3 4 ] [8 9 ] [8 9 2 3 4 ] [8 9 2 3 4 5 6 7 8 9 ]
s2 是 data 的引用,所以后面 s2 变化导致了 data 的变化
slice 遍历 跟其他遍历差不多
1 2 3 4 5 6 7 8 9 10 func main () { data := [...]int {1 , 2 , 3 , 4 , 5 , 6 , 7 } slice := data[:] for value := range slice { fmt.Printf("%v," , value) } } >>0 ,1 ,2 ,3 ,4 ,5 ,6 ,
切片 resize 调整大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { var a = []int {1 , 2 , 3 , 4 , 5 } fmt.Printf("%v,%v\n" , a, len (a)) b := a[1 :2 ] fmt.Printf("%v,%v\n" , b, len (b)) c := b[0 :3 ] fmt.Printf("%v,%v" , c, len (c)) } >> [1 2 3 4 5 ],5 [2 ],1 [2 3 4 ],3
字符串和切片 string 底层是一个 byte 的数组,所以也能进行切片
1 2 3 4 5 6 7 8 9 10 11 func main () { str := "butt3rf1y" s1 := str[0 :5 ] fmt.Println(s1) s2 := str[6 :] fmt.Println(s2) } >> butt3 f1y
string 是不可变的,不能直接修改,可以将字符转化为字节切片,修改之后再转为字符
1 2 3 4 5 6 7 8 9 10 11 func main () { str := "butt3rf1y" s := []byte (str) s[7 ] = 'h' s = s[:8 ] s = append (s, '!' ) str = string (s) fmt.Println(str) } >> butt3rfh!
多维切片 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func main () { var nums [5 ][5 ]int for _, num := range nums { fmt.Println(num) } fmt.Println() slices := make ([][]int , 5 ) for _, slice := range slices { fmt.Println(slice) } } >> [0 0 0 0 0 ] [0 0 0 0 0 ] [0 0 0 0 0 ] [0 0 0 0 0 ] [0 0 0 0 0 ] [] [] [] [] []
Go 的数组和切片都是一维的,要创建二维数组或切片需要定义一个数组的数组或切片的切片。从上面例子能看出来,数组初始化时一维和二维长度是固定了,而切片是不固定的,所以切片需要单独初始化
1 2 3 4 5 6 7 8 9 func main () { slices := make ([][]int , 5 ) for i := 0 ; i < len (slices); i++ { slices[i] = make ([]int , 5 ) } fmt.Println(slices) } >> [[0 0 0 0 0 ] [0 0 0 0 0 ] [0 0 0 0 0 ] [0 0 0 0 0 ] [0 0 0 0 0 ]]
拓展表达 只有切片才能使用拓展表达 ,解决切片共享底层数组的读写问题
1 2 3 4 slice[low:high:max] low: 起始索引 high: 结束索引(不包含) max: 最大容量
像前面说的,s1 和 s2 共享的是一个底层数组,那么对 s2 进行改变时也会对 s1 有影响,比如说
1 2 3 4 5 6 7 8 9 10 func main () { s1 := []int {4 , 5 , 6 , 7 , 8 , 9 } s2 := s1[3 :4 ] s2 = append (s2, 1 ) fmt.Println(s2) fmt.Println(s1) } >> [7 1 ] [4 5 6 7 1 9 ]
这个就会影响 s1 的数据,向 s2 添加元素,把 s1 更改了,这个时候用拓展表达式就能解决了
1 2 3 4 s2 := s1[3 :4 :4 ] >> [7 1 ] [4 5 6 7 8 9 ]
clear() clear() 会将切片内所有的值置为零值
1 2 3 4 5 6 7 func main () { s := []int {1 , 2 , 3 , 4 } clear(s) fmt.Println(s) } >> [0 0 0 0 ]
清空切片
1 2 3 4 5 6 7 func main () { s := []int {1 , 2 , 3 , 4 } s = s[:0 :0 ] fmt.Println(s) } >> []
指针 Go 中是指针不能进行偏移和运算,有两个常用操作符:取地址符 &
,解引用符 *
指针地址 & 取地址,取变量指针
1 2 3 ptr := &v v: 被取地址的变量,类型为 Tptr: 接收地址的变量,ptr 的类型为 *T,为 T 的指针类型
比如说
1 2 3 4 5 6 7 8 9 10 11 func main () { a := 10 b := &a fmt.Printf("a:%d ptr:%p\n" , a, &a) fmt.Printf("b:%p type:%T\n" , b, b) fmt.Println(&b) } >> a:10 ptr:0xc00008c098 b:0xc00008c098 type :*int 0xc000090048
指针取值 对普通变量使用 &
取地址后会获得这个变量的指针,然后对指针使用 *
解引用,也就是指针取值
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { a := 10 b := &a fmt.Printf("%T\n" , b) c := *b fmt.Printf("%T\n" , c) fmt.Printf("%v\n" , c) } >> *int int 10
取地址操作符 &
和取值操作符 *
是一对互补操作符,&
取出地址,*
根据地址取出地址指向的值
而且 *
还能声明指针
1 2 3 4 func main () { var numPtr *int fmt.Println(numPtr) }
*int
代表变量是 int
型指针,指针必须要初始化,不然就是空指针,可以取地址符将其他变量的地址赋给该指针,还能使用内置函数 new
() 手动分配
new() 1 2 3 4 5 6 7 func main () { var numPtr *int numPtr = new (int ) fmt.Println(numPtr) } >>0xc00008c098
new()
函数只有一个参数,参数为类型,返回一个对应类型的指针,会对指针分配内存,并且指针指向对应类型的零值
1 2 3 4 5 6 7 8 9 10 11 func main () { fmt.Println(*new (string )) fmt.Println(*new (int )) fmt.Println(*new ([5 ]int )) fmt.Println(*new ([]float64 )) } >>0 [0 0 0 0 0 ] []
变量、指针地址、指针变量、取地址、取值
1 2 3 对变量进行取地址 ,可以得到变量的指针变量 指针变量值为指针地址 指针变量进行取值 ,可以得到指针变量指向的原变量的值
指针传值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func ptr1 (x int ) { x = 100 }func ptr2 (x *int ) { *x = 100 }func main () { a := 10 ptr1(a) fmt.Println(a) ptr2(&a) fmt.Println(a) } >>10 100
值传递不会影响 a 的值,所以第一次输出 a 的值不变
make 和 new() 一样,是用来分配内存的,但是与 new() 有区别的是,它只用于 slice、map 和 channel 的内存创建,返回的类型就是这三个类型本身,而不是指针类型
1 func make (t Type,size ...IntegerType) Type
比如说
1 2 3 4 5 6 7 8 func main () { var b map [string ]int b = make (map [string ]int , 10 ) b["aaa" ] = 100 fmt.Println(b) } >>map [aaa:100 ]
区别
make() 只用于 slice、map 以及 channel 的初始化,返回的还是这三个类型本身
new() 用于类型的内存分配,并对内存对应的值为类型零值,返回指向类型的指针
map 映射表的数据结构实现通常两种:哈希表 (hash table ) 和搜索树 (search tree),无序和有序。在 Go 中 map 的实现是基于哈希桶,所以也无序,必须要初始化才能使用
初始化
1 2 3 4 5 6 7 8 9 10 11 12 func main () { scoreMap := make (map [string ]int , 8 ) scoreMap["butt3rf1y" ] = 100 scoreMap["butt3rf2y" ] = 90 fmt.Println(scoreMap) fmt.Println(scoreMap["butt3rf2y" ]) fmt.Printf("%T\n" , scoreMap) } >>map [butt3rf1y:100 butt3rf2y:90 ]90 map [string ]int
map 类型的变量默认初始值为 nil,需要用 make()
函数来分配内存
1 make (map [KeyType]ValueType,[cap ])
接收两个参数,类型与初始容量
1 2 mp1 := make (map [string ]int , 8 ) mp2 := make (map [string ][]int , 10 )
map 数据都是成对出现的。支持在声明的时填充元素
1 2 3 4 5 6 7 8 9 10 11 func main () { mp := map [int ]string { 0 : "b" , 1 : "u" , 2 : "t" , 3 : "3" , } fmt.Println(mp) } >>map [0 :b 1 :u 2 :t 3 :3 ]
访问 map 的访问和数组通过索引访问差不多,通过 Key 访问 Value
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { mp := map [string ]int { "a" : 0 , "b" : 1 , "c" : 2 , "d" : 3 , } fmt.Println(mp["a" ]) fmt.Println(mp["b" ]) fmt.Println(mp["c" ]) fmt.Println(mp["d" ]) fmt.Println(mp["e" ]) }
上面的代码中并不存在 e 的键值对,但是依然有返回值 0,map 对于不存在的键返回值是对应类型的零值 ,并且在访问 map 的时候其实有两个返回值,第一个返回对应类型的值,第二个返回值为布尔值,代表键是否存在,比如说
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { mp := map [string ]int { "a" : 0 , "b" : 1 , "c" : 2 , "d" : 3 , } if val, exist := mp["e" ]; exist { fmt.Println(val) } else { fmt.Println("不存在" ) } } >> 不存在
求 map 长度用 len()
函数
存值 类似数组存值,存值时使用已存在的键会覆盖原有的值
1 2 3 4 5 6 7 8 9 10 11 func main () { mp := make (map [string ]int , 10 ) mp["a" ] = 1 mp["b" ] = 2 if _, exist := mp["b" ]; exist { mp["b" ] = 3 } fmt.Println(mp) } >>map [a:1 b:3 ]
但是当键为 math.NaN()
时
1 2 3 4 5 6 7 8 9 10 11 12 func main () { mp := make (map [float64 ]string , 10 ) mp[math.NaN()] = "a" mp[math.NaN()] = "b" mp[math.NaN()] = "c" _, exist := mp[math.NaN()] fmt.Println(exist) fmt.Println(mp) } >>false map [NaN:c NaN:a NaN:b]
相同的键值并没有被覆盖,而是并存。NaN 的实现是由底层的汇编指令 UCOMISD
完成的,这是一个无序比较双精度浮点数的指令。任何数字都不等于 NaN,NaN 也不等于自身,每次哈希值就不同,所以不要用 NaN 作为 map 的键
delete() 删除键值对,用 delete()
函数
1 2 3 delete (map ,key)map :要删除的 map key:要删除的键值对的 key
比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { mp := map [string ]int { "a" : 0 , "b" : 1 , "c" : 2 , "d" : 3 , } fmt.Println(mp) delete (mp, "a" ) fmt.Println(mp) } >>map [a:0 b:1 c:2 d:3 ]map [b:1 c:2 d:3 ]
如果值为 NaN,那么没办法删除键值对
遍历 用 for range
遍历 map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () { mp := map [string ]int { "a" : 0 , "b" : 1 , "c" : 2 , "d" : 3 , } for key, val := range mp { fmt.Println(key, val) } } >> c 2 d 3 a 0 b 1
因为 map 是无序的,遍历出来也是无序的
按照指定顺序遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func main () { var scoreMap = make (map [string ]int , 200 ) for i := 0 ; i < 100 ; i++ { key := fmt.Sprintf("%02d" , i) value := rand.Intn(100 ) scoreMap[key] = value } var keys = make ([]string , 0 , 200 ) for key := range scoreMap { keys = append (keys, key) } sort.Strings(keys) for _, key := range keys { fmt.Println(key, scoreMap[key]) } }
结构体 结构体声明,和一般结构体声明一样
1 2 3 4 5 6 7 8 9 10 11 12 type 类型名 struct { 字段名 字段类型 字段名 字段类型 … }type Programmer struct { Name string Age int Job string Language []string }
实例化 go 没有构造方法,初始化像 map 一样指定字段名称再初始化字段值
1 2 3 4 5 6 programmer:=Programmer{ Name:"butt3rf1y" , Age:18 , Job:"stu" , Language:[]string {"Go" ,"Java" } }
如果实例化过程比较复杂,可以使用函数来实例化
1 2 3 4 5 6 7 8 9 10 type Person struct { Name string Age int Address string Salary float64 }func NewPerson (name string , age int , address string , salary float64 ) *Person { return &Person{Name: name, Age: age, Address: address, Salary: salary} }
就像构造函数,但是 go 不支持函数与方法重载,如果需要用多种方式实例化结构体,要么创建多个构造函数,要么使用 options 模式
options 模式 假设有一个下面的结构体
1 2 3 4 5 6 7 type Person struct { Name string Age int Address string Salary float64 Birthday string }
声明一个 PersonOptions
类型,接收一个 *Person
类型的参数,必须是指针
1 type PersonOptions func (p *Person)
创建选项函数,一般是 With
开头,返回值为一个闭包函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func WithName (name string ) PersonOptions { return func (p *Person) { p.Name = name } }func WithAge (age int ) PersonOptions { return func (p *Person) { p.Age = age } }func WithAddress (address string ) PersonOptions { return func (p *Person) { p.Address = address } }func WithSalary (salary float64 ) PersonOptions { return func (p *Person) { p.Salary = salary } }
实际声明的构造函数签名,接受一个可变长的 PersonOptions
类型的参数
1 2 3 4 5 6 7 8 9 10 11 12 func NewPerson (options ...PersonOptions) *Person { p:=&Person{} for _,option:=range options{ option(p) } if p.Age<0 { p.Age=0 } ...... return p }
对于不同实例化的需求只需要一个构造函数就行,传入不同的 Options 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { p1:=NewPerson( WithName("butt3rf1y" ), WithAge(18 ), WithAddress("US" ), WithSalary(100.00 ), ) p2:=NewPerson( WithName("kikk" ), WithAge(19 ), ) }
组合 结构体之间的关系通过组合来表示,有显式组合和匿名组合
显式组合
1 2 3 4 5 6 7 8 9 10 11 12 type Person struct { name string age int }type Student struct { p Person school string }type Employee struct { p Person job string }
如果需要显式的指定字段 p
1 2 3 4 5 student:=Student{ p:Person{name:"jack" ,age:18 }, school:"lili school" , } fmt.Println(student.p.name)
匿名组合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 type Person struct { name string age int }type Student struct { Person school string }type Employee struct { Person job string }func main () { var user struct { Name string Age int } user.Name = "hhah" user.Age = 18 fmt.Printf("%#v\n" , user) } >>struct { Name string ; Age int }{Name:"hhah" , Age:18 }
匿名字段名称默认为类型名,调用者可以直接访问该类型的字段和方法
1 2 3 4 5 student:=Student{ Person:Person{name:"jack" ,age:18 }, school:"lili school" , } fmt.Println(student.name)
指针 结构体指针不需要解引用就能访问
1 2 3 4 5 6 7 8 9 10 11 func main () { type Person struct { name string age int } p := &Person{ name: "hhah" , age: 18 , } fmt.Println(p.age, p.name) }
p.age
其实在编译时会转换为 (*p).name
标签 标签是结构体的元信息,可以在运行时通过反射的机制读取
1 `key1:"value1" key2:"value2"`
假如 Student 结构体每个字段定义 json 序列化时用 Tag:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Student struct { ID int `json:"id"` Gender string name string }func main () { s1 := Student{ ID: 1 , Gender: "女" , name: "hhah" , } data, err := json.Marshal(s1) if err != nil { fmt.Println("json marshal failed" ) return } fmt.Printf("%s\n" , data) } >> {"id" :1 ,"Gender" :"女" }
方法和接收者 方法(Method)是作用于特定类型变量的函数,特定类型变量叫接收者 (Receiver)
1 2 3 func (Receiver变量 Receiver类型) 方法名(参数列表) (返回参数){ 函数体 }
比如说
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Person struct { name string age int8 }func NewPerson (name string , age int8 ) *Person { return &Person{ name: name, age: age, } }func (p Person) Dream() { fmt.Printf("%s" , p.name) }func main () { p1 := NewPerson("hhah" , 18 ) p1.Dream() } >> hhah
指针类型的接收者 由于指针的特性,调用方法时修改接收者指针的任意成员变量,方法结束后修改也有效。
1 2 3 4 5 6 7 8 9 10 11 12 func (p *Person) SetAge(newAge int8 ) { p.age = newAge }func main () { p1 := NewPerson("cc" , 18 ) fmt.Println(p1.age) p1.SetAge(20 ) fmt.Println(p1.age) } >>18 20
一般需要修改接收者中的值或者如果某个方法使用了指针接收者时用指针类型接收者
值类型的接收者 当方法用于值类型接收者时,会在代码运行时将值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但无法改变接收者本身
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func (p Person) SetAge(newAge int8 ) { p.age = newAge }func (p Person) Dream() { }func main () { p1 := NewPerson("hhah" , 18 ) p1.Dream() fmt.Println(p1.age) p1.SetAge(20 ) fmt.Println(p1.age) } >>18 18
任意类型添加方法 接收者可以是任意类型,不仅仅是结构体,任何类型都可以拥有方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type MyInt int func (m MyInt) SayHello() { fmt.Println("hello" ) }func main () { var m1 MyInt m1.SayHello() m1 = 100 fmt.Printf("%#v %T\n" , m1, m1) } >> hello100 main.MyInt
不能给别的包的类型定义方法
嵌套 一个结构体可以嵌套另一个结构体或结构体指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 type Address struct { Province string city string }type User struct { Name string Gender string Address Address }func main () { user1 := User{ Name: "hhah" , Gender: "w" , Address: Address{ Province: "四川" , city: "成都" , }, } fmt.Printf("user1=%#v\n" , user1) } >> user1=main.User{Name:"hhah" , Gender:"w" , Address:main.Address{Province:"四川" , city:"成都" }}
嵌套匿名结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Address struct { Province string city string }type User struct { Name string Gender string Address }func main () { var user2 User user2.Name = "cc" user2.Gender = "w" user2.Address.Province = "四川" user2.city = "成都" fmt.Printf("user2=%#v\n" , user2) } >> user2=main.User{Name:"cc" , Gender:"w" , Address:main.Address{Province:"四川" , city:"成都" }}
内存对齐 结构体字段的内存分布遵循内存对齐的规则