数组
数组的类型名是 [n]elementType
1 2 3 4 5 6 7 8 var arr0 []int var arr1 [5 ]int arr2 := [3 ]int {1 , 3 , 5 } arr3 := [10 ]int {0 :3 ,8 :3 } arr3 := [...]int {0 :3 ,8 :2 } arr4 := [...]int {2 , 4 , 6 , 8 , 10 } var grid [4 ][5 ]int
注意
变量名写在数组之前
不对数组长度规定的时候,一定要使用...
来代替长度。
Go数组的度的长度是固定的,一旦指定,不可以变化,但是数组的值是可以改变的。并且数组的长度是数组类型的组成部分,[10]int和[20]int表示的是不同的类型
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { var a = []int {1 ,2 ,43 } fmt.Printf("%T" , a) var b = [3 ]int {1 ,2 ,43 } fmt.Printf("%T" , b) }
注意:数组的声明中[]
中一定要有长度限制,或者使用...
代替,此时会自动设置为实际长度,如果什么都没有,则是切片的声明方式,内部的执行过程是先创建一个数组,然后再赋值给了一个切片。
数组的遍历
1 2 3 4 for i := 0 ; i < len (arr3); i++{ fmt.Println(arr3[i]) }
但是在遍历数组 的时候,有一个专门的关键字range
关键字
1 2 3 for i := range arr3 { fmt.Println(arr3[i]) }
rang关键字也可以用来获取值
1 2 3 4 5 6 7 8 for i,v := range arr3 { fmt.Println(i, v ) } for _, v := range arr3{ fmt.Println(v) }
数组是值类型 — 作为函数参数是值传递
调用func f(arr [10]int)
会拷贝数组
是类型意味着在作为函数参数进行传递时,传递的是拷贝值,在函数体内对传递的数组进行操作,不会修改原有的数组。
Go语言中都是值传递,意味着传递时会拷贝。
Go语言中,[5]int
和[3]int
是两中不同的类型,不能相互之间被使用赋值。
若想传递的时候可以对数组进行改变而不是值传递,可以使用指针的方式
1 2 3 4 5 6 7 8 9 func printArray (arr *[5]int ) { arr[0 ] = 100 for i, v := range arr { fmt.Println(i, v) } } printArray(&arr1)
上面的例子中arr[0] = 100
中,使用数组指针的话,可以不使用*
进行调用,而是直接在函数内使用数组名,这是十分灵活以及方便的,当然也可以使用*
,但是需要使用括号(*arr)[0] = 100
这样的赋值方式。rang后可以直接用for i:= range *arr
效果和for i:= range arr
一致。
在Go语言中一般不直接使用数组,而是使用的切片(切片名实际上是一个指针类型,拥有指针的属性)
切片(Slice) — 相当于可变数组
Go语言数组的定长性和值拷贝限制了其使用场景,Go提供了另一个数据类型slice, 是一种可变长数组。
1 2 3 4 arr := [...]int {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 } s := arr[2 :6 ]
Slice "不是"值类型(当然,Go语言所有的类型都是值类型),但是Slice内部有一个数据结构,是对array的一个view视图,所以对Slice的操作也会影响到原array的数据内容。
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 package mainimport "fmt" func main () { arr := [4 ]int {1 ,2 ,3 ,4 } s1 := arr[2 :3 ] fmt.Printf("%p\n" , s1) fmt.Printf("%p\n" , &s1[0 ]) fmt.Printf("%p\n" , &arr[2 ]) a := 1 b := &a fmt.Printf("%p\n" ,b) fmt.Printf("%p\n" ,&a) } 0xc000018210 0xc000018210 0xc000018210 0xc00001e0d0 0xc00001e0d0
结论:切片变量名可以作为指针使用,执行切片的第一个元素。不过这里要和切片类型的指针区分,后者指的是切片的位置指针(但是这个特性好像没有用,因为只是首位的元素地址不能代表切片,而且Golang中是不支持指针运算的,知晓了第一元素地址也不能以此类推到第二个元素等等)。
切片的内部构造:
1 2 3 4 5 6 7 type slice struct { array unsafe.Pointer len int cap int }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { arr := [...]int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 } s1 := arr[2 :] fmt.Println("s1 = " , s1) s1[0 ] = 100 fmt.Println("arr = " ,arr) } s1 = [2 3 4 5 6 7 ] arr = [0 1 100 3 4 5 6 7 ]
可见对切片的操作会反映到原数组上。( 和Python不一样,Python的slice是对数组的截断拷贝,对切片的操作不会影响到Python列表(数组))
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func updateSlice (s []int ) { s[0 ] = 100 } func main () { arr := [...]int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 } fmt.Println("arr[2:6] =" , arr[2 :6 ]) fmt.Println("arr[:6] =" , arr[:6 ]) s1 := arr[2 :] fmt.Println("s1 =" , s1) s2 := arr[:] fmt.Println("s2 =" , s2) fmt.Println("After updateSlice(s1)" ) updateSlice(s1) fmt.Println(s1) fmt.Println(arr) }
有了切片,之前的对数组使用指针的操作就不需要了(因为切片内部保存了数组的指针,实际操作都会注册在原先的数组上面),而是使用切片,就可以实现同样的效果。唯一需要注意的是,含有切片参数的函数接受的参数也需要为切片。
Reslice
1 2 3 4 5 6 7 8 9 10 11 12 13 arr := [...]int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 } s := arr[2 :6 ] fmt.Println("s" ,s) s = s[:3 ] fmt.Println("s" ,s) s = arr[:] fmt.Println("s" ,s) s [2 3 4 5 ] s [2 3 4 ] s [0 1 2 3 4 5 6 7 ]
时刻注意,切片是对原数组的视图,只要不对切片进行修改操作,就不会对数组进行修改。
Slice的扩展
切片含有一个cap(),capacity容量,会向后保存原始的数据。 使用cap()
可以查看切片的容量
1 2 3 4 arr := [...]int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 } s1 := arr[2 :6 ] s2 := s1[3 :5 ]
s1 的值为[2 3 4 5]
,s2的值为[5 6]
slice可以向后扩展,不可以向前扩展
s[i]
不可以超越len(s)
,向后扩展不可以超越底层数组cap(s)
。如上面若是s2 = s1[5]
是取不到值的。
向后拓展的值可以被新的切片取到。如上面的s2 := s1[3:5]
。
Slice 操作
向Slice添加元素
使用append(slice, val)
,注意的是,在添加的时候,若添加的内容的下标未超过原始数组的下标(但是在切片的容量之内),则添加的内容会覆盖掉原始数组相应位置的内容。
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 () { arr := [...]int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 } fmt.Println("arr =" , arr) s1 = arr[2 :6 ] s2 = s1[3 :5 ] fmt.Printf("s1=%v, len(s1)=%d, cap(s1)=%d\n" , s1, len (s1), cap (s1)) fmt.Printf("s2=%v, len(s2)=%d, cap(s2)=%d\n" , s2, len (s2), cap (s2)) s3 := append (s2, 10 ) s4 := append (s3, 11 ) s5 := append (s4, 12 ) fmt.Println("s3, s4, s5 =" , s3, s4, s5) fmt.Println("arr =" , arr) } s1=[2 3 4 5 ], len (s1)=4 , cap (s1)=6 s2=[5 6 ], len (s2)=2 , cap (s2)=3 s3, s4, s5 = [5 6 10 ] [5 6 10 11 ] [5 6 10 11 12 ] arr = [0 1 2 3 4 5 6 10 ]
超出的部分,这里的11 和 12 ,此时的s2 和 s3因为有超出的原先arry的内容,所以这里的s2和s3就不再是原先数组arry的视图view了,而是在内存中重新开辟了新的arry,将原先的arry拷贝过来,并将长度设置更长。
原先的数组若没有被使用,就会被垃圾回收掉,但是这里的不会,因为调用了。
切片和原先数组的关系
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package mainimport "fmt" func updateSlice (s []int ) { s[0 ] = 100 } func main () { arr := [8 ]int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 } fmt.Printf("arr=%v, len(arr)=%d, cap(arr)=%d\n" , arr, len (arr), cap (arr)) fmt.Printf("数组的地址: %p\n" , &arr) fmt.Printf("数组的首元素的地址: %p\n" , &arr[0 ]) s1 := arr[2 :6 ] fmt.Printf("切片的地址: %p\n" , &s1) fmt.Printf("切片的首元素的地址: %p\n" , &s1[0 ]) fmt.Printf("数组对应切片首元素的地址: %p\n" , &arr[2 ]) fmt.Println(s1) fmt.Printf("第一次切片: %p\n" , &s1) s2 := append (s1, 100 ) fmt.Printf("s2=%v, len(s2)=%d, cap(s2)=%d\n" , s2, len (s2), cap (s2)) fmt.Printf("第二次切片: %p\n" , &s2) fmt.Printf("第二次切片首元素的地址: %p\n" , &s2[0 ]) fmt.Printf("第二次切片切片变量的地址: %p\n" , s2) s3 := append (s2, 200 ) fmt.Printf("第三次切片首元素的地址: %p\n" , &s3[0 ]) fmt.Printf("s3=%v, len(s3)=%d, cap(s3)=%d\n" , s3, len (s3), cap (s3)) s4 := append (s3, 300 ) fmt.Printf("arr=%v, len(arr)=%d, cap(arr)=%d\n" , arr, len (arr), cap (arr)) fmt.Printf("s4=%v, len(s4)=%d, cap(s4)=%d\n" , s4, len (s4), cap (s4)) fmt.Printf("第四次切片的首元素的地址: %p\n" , &s4[0 ]) } arr=[0 1 2 3 4 5 6 7 ], len (arr)=8 , cap (arr)=8 数组的地址: 0xc00001a1c0 数组的首元素的地址: 0xc00001a1c0 切片的地址: 0xc00000c060 切片的首元素的地址: 0xc00001a1d0 数组对应切片首元素的地址: 0xc00001a1d0 [2 3 4 5 ] 第一次切片: 0xc00000c060 s2=[2 3 4 5 100 ], len (s2)=5 , cap (s2)=6 第二次切片: 0xc00000c0a0 第二次切片首元素的地址: 0xc00001a1d0 第二次切片切片变量的地址: 0xc00001a1d0 第三次切片首元素的地址: 0xc00001a1d0 s3=[2 3 4 5 100 200 ], len (s3)=6 , cap (s3)=6 arr=[0 1 2 3 4 5 100 200 ], len (arr)=8 , cap (arr)=8 s4=[2 3 4 5 100 200 300 ], len (s4)=7 , cap (s4)=12 第四次切片的首元素的地址: 0xc000074060
Slice创建
1 2 3 4 5 6 var s []int s1 := []int {2 ,4 ,6 ,8 } s2 := make (Type, size[,cap ])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func printSlice (s []int ) { fmt.Printf("%v len=%d cap=%d" , s, len (s), cap (s)) } func main () { s1 := []int {2 ,4 ,6 ,8 } printSlice(s1) s2 := make ([]int , 4 ) printSlice(s2) s3 := make ([]int , 6 ,19 ) printSlice(s3) } [2 4 6 8 ] len =4 cap =4 [0 0 0 0 ] len =4 cap =4 [0 0 0 0 0 0 ] len =6 cap =19
var s []int
,Go语言的特性,使用var创建变量之后没有付初值的情况下,有一个zero值,而切片的zero就是nil。直接使用var s []int
创建的切片没有值但是可以只用,cap在装不下的时候会进行扩充,每次在原先的基础上乘以2。
数组的len以及cap的值是一致的,切片:
在通过make(slice_name, len[, cap])
创建的时候,可以使用指定的len以及cap,但是len的长度一定要小于等于cap的值,当添加数据超出cap后,新切片的cap为未扩充前的2倍。
通过数组进行切片的,len为切片长度,切片的cap为数组的cap-切片的起始索引
无论通过什么方式创建的切片,在其cap长度内,都是在原先的数组基础上进行扩充的,一旦cap到达后就会进行双倍扩容,此时的就会拷贝原有数组的内容创建新的数组,新数组和之前的数组是没有关系的。
Slice 的复制
copy(dst, src []Type) int
,将src中的内容拷贝到dst中,覆盖原先中的内容,超出的部分抹去 。
为slice特有的方法,参数为切片类型 []Type
。
返回值类型为int,返回的是实际复制了的内容的长度。
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 29 30 31 32 33 34 35 36 37 38 func printSlice (s []int ) { fmt.Printf("%v, len=%d, cap=%d\n" , s, len (s), cap (s)) } func main () { fmt.Println("Deleting elements from slice" ) s1 := []int {1 ,2 ,3 ,0 ,0 ,0 ,0 ,0 ,0 ,4 ,5 ,6 } printSlice(s1) s1 = append (s1[:3 ], s1[9 :]...) printSlice(s1) fmt.Println("Popping from front" ) fornt := s1[0 ] s1 = s1[1 :] fmt.Printf("fornt=%d\n" ,fornt) printSlice(s1) fmt.Println("Popping form back" ) tail := s1[len (s1)-1 ] s1 = s1[:len (s1)-1 ] fmt.Printf("tail=%d\n" , tail) printSlice(s1) } Deleting elements from slice [1 2 3 0 0 0 0 0 0 4 5 6 ] len =12 cap =12 [1 2 3 4 5 6 ] len =6 cap =12 Popping from front fornt=1 [2 3 4 5 6 ] len =5 cap =11 Popping form back tail=6 [2 3 4 5 ] len =4 cap =11
解释:切片没有提供原生的删除操作,删除操作需要手动实现,如使用通过append
拼接、创建新的切片等方式实现,但是注意:这些操作都是产生一个新的切片,此时需要使用原先的切片变量来接受,此时从结果上看此时对原来的切片进行了修改(因为Golang和Python不一样,Golang变量就是内存地址,变量开辟内存存储匹配类型的数据,而Python的变量只是别名用来指向数据的内存地址)。
小结
Slice本身没有数据,是对底层array的一个view
添加元素时如果超越cap,系统会重新分配更大的底层数组,并将原来的arry拷贝过去。
由于值传递的关系,必须接受append的返回值 — 因为,slice 的三个指标 ptr指针,len长度, cap容量都可能发生变化,而一旦cap容量超过之后,系统就会重新分配新的更大的内存去存储该slice,所以必须要设置参数用来接受append的返回值。
S = append(s, val)
Slice底层是对array的一个view,所以对Slice的修改会影响到底层的array,以及有关联的Slice(如同一个array的切片)
1 2 3 4 5 6 7 8 slicea := arr[:2 ] sliceb := arr[:1 ] fmt.Print(slicea) slice[0 ] = 8 fmt.Print(arr) fmt.Print(sliceb) [0 1 ][8 1 2 ][8 ]
Map
1 2 3 4 5 6 7 8 9 10 11 m := map [string ]string { "name" : "ccmouse" , "course" : "golang" , "site" : "imooc" , "quality" : "notbad" , } m2 := make (map [string ]int ) var m3 map [string ]int
语法格式:map[key的类型]value的类型
,还支持复合结构map[K1]map[K2]V
注意区分方式二和方式三的区别,m2是一个为空的map,已经分配了内存,只是内容为空,而m3是的值为nil,是Go中的特殊类型0值,但是nil也可以参与运算。
Map的遍历
1 2 3 4 fmt.Println("Traversing map m" ) for k, v := range m { fmt.Println(k, v) }
需要注意的是,key 在Map中是无序的,是一个哈希Map
Map不保证遍历顺序,如需顺序,需手动对key排序 – 取出所有的key 放到slice中(slice可以排序)进行排序,然后在按照key取出。
使用len获得元素个数
Map的key:
map使用哈希表,必须可以比较相等
除了slice、map、function的其他内建类型都可以作为key
不包含slice、map、function的Struct类型都可以作为key
Map操作
读操作:courseName := m["course"]
,当key不存在时,会打印ZeroValue值,如这里会打印空字符串’ '。读操作有两个返回值,第一个返回值为key对应的value值,第二个返回值为反映该key对应的value值是否存在的标志,若key存在,则返回true, 反之返回false。
1 2 3 4 5 if causeName, ok := m["cause" ]; ok { fmt.Println(causeName) } else { fmt.Println("key 'cause' does not exist" ) }
删除操作:delete(map, key)
,该函数没有返回值。删除不存在的key也不会报错。
小结
创建: make(map[string]int)
获取元素: m[key]
key不存在时,获得Value类型的初始值
使用value, ok := m[key]
来判断是否存在key
用delete(map, key)
来删除一个key
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func lengthOfNonRepeatingSubStr (s string ) int { lastOccurred := make (map [byte ]int ) start := 0 maxLength := 0 for i, ch := range []byte (s) { if lastI, ok := lastOccurred[ch]; ok && lastI >= start { start = lastI + 1 } if i-start+1 > maxLength { maxLength = i - start +1 } lastOccurred[ch] = i } return maxLength }
1 2 3 4 5 6 package main func main () { cache := make (map [string ]string ) cache["name" ] = "ccmouse" }