0
点赞
收藏
分享

微信扫一扫

第3关 内建容器



第3关 内建容器

  • ​​3-1 数组​​
  • ​​3-2 切片的概念​​
  • ​​3-3 切片的操作​​
  • ​​3-4 Map​​
  • ​​3-5 字符和字符串处理​​

3-1 数组

定义数组:

package main
import "fmt"

func main(){
var arr1 [6]int
arr2 := [3]int{1,2,3}
arr3 := [...]int{1,0,0,8,6}
fmt.Println(arr1,arr2,arr3)
// [0 0 0 0 0 0] [1,2,3] [1,0,0,8,6]
var grid [4][5]int
fmt.Println(grid)
// [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

}

我们来看数组,我们还是从代码来看,定义数组也有很多种定义方法,

比如说有6个int,定义数组的时候,跟我们常见的语言还是反的,中括号6 int‌‌代表了有6个int或者 arr2 我们可以冒号等于3个int,‌‌但是冒号等于的话,我们就要给它一个初值,我们要把这三个数字告诉他,比如说1 2 3,‌‌或者我们会觉得三个数字我们数起来太麻烦了,我们可不可以让编译器来数?‌

‌当然可以,‌‌但是用编译器数,我们不是说什么都不写的int‌‌,什么都不写,还有另外的含义是切片,我们要点三个点给它,int‌‌ 1 0 0 8 6。‌

‌好,我们来把它们打出来,结果见注释。

可以看到,我们用var定义出来的全是 ‌0,

‌第二个和第三个当然是1 2 3 和1 0 0 8 6,

或者我们还可以有一个二维数组,比如说我们有个grid,‌‌我们有4行5列,我们把这个grid定义成4行5列的int,运行结果见上方代码注释。‌

‌我们来回顾一下,这就是定义数字的几种方法,大家要注意这个数量是写在类型的前面,‌‌用点就是说让编译器来帮我们数有几个int。‌

‌当然多维数组就是这个方框后写在前面,‌‌为什么是4行5列?我们从前往后读,首先是双括号,4代表4个东西,4个什么东西呢?‌‌4个长度为5的int数组,所以4个长度为5的int数组,所以第一个4行,第二个5是列‌‌,

数量写在类型的前面。

接下去让我们来遍历一下这个数组。

比如说我们遍历 arr3这个数组,‌‌首先想到的是我们 for i 冒号等于0,对不对?i 小于 len(arr3),‌‌然后i加加,

// ...
for i :=0;i < len(arr3);i++{
fmt.Println(arr3[i])
/*
1
0
0
8
6
*/
}

然后 打印arr3中括号i。

这是我们首先想到的遍历方法,但是一般我们遍历数组不这样做,‌‌有一个range关键字,我们看一下,for i 冒号等于 range arr3,‌‌它出来的结果也是一样,

// ...
for i := range arr3{
fmt.Println(arr3[i])
/*
1
0
0
8
6
*/
}

我们用 range关键词能够获得这个数组的一个下标,‌‌我不仅想获得下标,我还想获得值可不可以?‌

‌当然可以,i逗号v等于range arr3,‌‌

// ...
for i,v:=range arr3{
fmt.Println(i,v)
/*
0 1
1 0
2 0
3 8
4 6
*/
}

我print i逗号v ,我们看看 i,v分别是多少,

我们arr3数组,

第0个是1,

第1个是0,

第2个是0,

第3个是8,

第4个是6,

我们采用 range 关键字我们可以同时获得第几个元素‌‌以及元素的值。

我们只要下标已经演示过,i冒号等于就可以了,‌‌我们只想要值怎么办?我们不想‌‌去知道第几个,我们只想print v,go语言是不可以说你定义了i但是不用的,我们用下划线来表示,‌‌就是说我们不要下标,只要数值,这样也是一样。‌

// ...
for _,v := range arr3{
fmt.Println(v)
/*
1
0
0
8
6
*/
}

这里就演示了range关键字的三种用法,下标和 v全要,‌‌或者只要一个ta都可以。‌

‌我们来回顾一下数组的遍历,‌‌我们可以用 len这种方法来遍历肯定是可以的。

然后go语言提供了 range 关键字,‌‌比如说for i 逗号 v 冒号等于range arr3。‌

可通过下划线 _ 来省略遍历。

不仅仅是range,任何地方都可以通过 _ 来省略变量。

如果我们只要一个下标‌‌,写成for i冒号等于range arre 就可以了。‌

‌说到了range,我们来看看为什么要用range,‌‌首先就是意义明确美观,

maxi := -1
maxValue := -1
for i,v := range numbers{
if v > maxValue{
maxi,maxValue = i,v
}
}

我比如说 for i逗号v‌‌,我们就很明显是对 numbers 里面所有的值进行操作,

同时能获得 i 和 v‌‌ 我们这个程序写上去 意图 就要明显的多。‌

接下来我们有一个非常重要的概念,就是数组是值类型,这句话意味着什么呢?‌‌

大家想想什么叫值类型?

值类型就是拷贝,我们通过几段程序来看一看,‌‌我们写一个函数叫做printArray,形参是 arr [6]int,‌‌但是因为数组是值类型,我们必须规定这个数组的长度。‌

func printArray(arr [6]int){
for i,v := range arr {
fmt.Println(i,v)
}
}
func main(){
var arr1 [6]int
arr2 := [3]int{1,2,3}
arr3 := [...]int{1,0,0,8,6}
fmt.Println(arr1,arr2,arr3)
// [0 0 0 0 0 0] [1,2,3] [1,0,0,8,6]
var grid [4][5]int
fmt.Println(grid)
// [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

printArray(arr1)
// printArray(arr2) // [3]int 和 [6]int是不同的类型
}

‌我们要的是type [6]int,那[3]int go语言中认为是不同的数据类型,类型不一样就不能传进去,‌‌

因此我们只能调arr1。

接下来讲讲老话题——参数传递。

‌因为之前说过go语言参数传递只有一种方法,就是值传递,‌‌值传递就意味着就会做拷贝,大家要特别注意这句话,‌‌把arr作为参数是会进行拷贝。

你可能会说这个数组很难用,我传一个参数还不能可变长,‌‌我必须要说传10个,而且你又会给我做一个拷贝,你用起来很不方便,‌‌大家可能会想到我不用拷贝,我用一个指向数组的指针可以吗?‌

‌当然是可以的,我们可以试一下,‌‌我用一个指针,指针只要前面加一个星就可以了,加了星以后我们这里就要取地址,‌‌这个不像C语言,什么数组名就是什么数组头指针,它没有这回说法的,数组就是一个东西,它是一个数字,‌‌数字的地址就是取一个&,那么这样的话我们去运行来看看,‌‌我们运行完之后100就被赋值上去了。

而且我们发现go语言的指针用起来很灵活,我们不需要什么‌‌(*arr)[0] = 100,

我们直接说arr中括号0,它也会‌‌到这个数组里面去把第零号元素改掉,最后改出来的结果是对的,‌‌但是这样也很麻烦,我们这个数组的元素,我们要知道个数,而且我们还要每次都要加一个指针,‌‌go语言一般我们不直接使用数组,前面我这种什么数组的指针的方法一般我们是不用的,‌‌那么go语言‌‌一般是用什么东西呢?‌

‌那不直接使用数字,‌‌也不像我们使用那个数组的指针,一看就很麻烦,非常不爽,肯定不是go语言设计的一个目标,‌‌go语言使用的是什么?是切片。

3-2 切片的概念

切片在英文里叫slice,‌‌ slice我们要仔细的来看,它里面变化是非常多的,‌‌而且我们一般之前说了,go语言一般我们不用数组,我们真正用的是 slice 切片。‌

案例:

arr := [...]int{0,1,2,3,4,5,6,7}
s := arr[2:6]

首先我们定义数组arr,‌‌还有01234567这样8个元素的一个数组,

然后我们说s 冒号等于 arr里面的2~6,‌‌这个s就是一个切片。‌

‌请问我们运行完 arr[2:6] s的值是多少呢?‌‌

大家想一下,s的值是[2 3 4 5],在我们计算机中一般我们这个区间都表示为‌‌半开半闭区间,也就是2是包括进去的,6是不包括进去的,一般我们都用半开半闭区间,‌‌基本上其他所有的语言都一样。‌

口诀记忆:取左不取右。

案例:

func main(){
arr := [...]int{0,1,2,3,4,5,6}
fmt.Println("arr[2:6]=",arr[2:6])
fmt.Println("arr[:6]=",arr[:6])
fmt.Println("arr[2:]=",arr[2:]) // 从左取到尾
fmt.Println("arr[:]=",arr[:]) // 获取所有的元素
}
/*
[2 3 4 5]
[0 1 2 3 4 5]
[2 3 4 5 6]
[0 1 2 3 4 5 6]
*/

案例:

// updateSlice 把传过来的切片首个元素改成100
func updateSlice(s []int){
s[0] = 100
}

func main(){
arr := [...]int{0,1,2,3,4,5,6}

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)
}
/*
s1=[2 3 4 5 6]
s2=[0 1 2 3 4 5 6]
After updateSlice(s1)
[100 3 4 5 6]
[0 1 100 3 4 5 6]
*/

‌因此我们再转到前面,

func printArray(arr []int){
arr[0] = 100
for i,v := range arr {
fmt.Println(i,v)
}
}
func main(){
var arr1 [6]int
arr2 := [3]int{1,2,3}
arr3 := [...]int{1,0,0,8,6}
fmt.Println(arr1,arr2,arr3)
var grid [4][5]int
fmt.Println(grid)

fmt.Println("printArray(arr1)")
printArray(arr1[:]) // 数组如何取得它的slice?中括号冒号[:]

fmt.Println("printArray(arr3)")
printArray(arr3[:])

fmt.Println("arr1 and arr3")
fmt.Println(arr1,arr3)
}
/*
[0 0 0 0 0 0] [1 2 3] [1 0 0 8 6]
[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
printArray(arr1)
0 100
1 0
2 0
3 0
4 0
5 0
printArray(arr3)
0 100
1 0
2 0
3 8
4 6
arr1 and arr3
[100 0 0 0 0 0] [100 0 0 8 6]

*/

数组怎么来取得它的slice?加个冒号。

这就是go语言中的使用的方法。‌‌

我们不要用指针,我们用 slice告诉它,我们看的是底层数组的全部的景象,‌‌它就能够把这个数值去改掉。‌

slice 本身是没有数据的,是对底层的array的一个view。

arr的值变为 [100 0 0 0 0 0] 。

记住:如果slice是在一个arr基础之上截取的片段生成的,那么对切片的值的修改会具体体现在arr上。

好,我们 slice它还有一个操作叫做reslice。‌‌

// updateSlice 把传过来的切片首个元素改成100
func updateSlice(s []int){
s[0] = 100
}

func main(){
arr := [...]int{0,1,2,3,4,5,6}

fmt.Println("arr[2:6]=",arr[2:6])
fmt.Println("arr[:6]=",arr[:6])

s1 := arr[2:]
fmt.Println("s1=",s1) // [2 3 4 5 6]

s2 := arr[:]
fmt.Println("s2=",s2) // [0 1 2 3 4 5 6]

fmt.Println("After updateSlice(s1)")
updateSlice(s1)

fmt.Println(s1) // [100 3 4 5 6]
fmt.Println(arr) // [0 1 100 3 4 5 6]

fmt.Println("After updateSlice(s2)")
updateSlice(s2)
fmt.Println(s2)// [100 1 100 3 4 5 6]
fmt.Println(arr) // [100 1 100 3 4 5 6]

fmt.Println("Reslice")
fmt.Println(s2) // [100 1 100 3 4 5 6]
s2 = s2[:5]
fmt.Println(s2) // [100 1 100 3 4]
s2 = s2[2:]
fmt.Println(s2) // [100 3 4]
}

s2是arr的一个slice, s2 := arr[:]它代表了全部arr的值,我们还可以进一步的对s2去slice,比如 s2 = s2[:5],又比如 s2 = s2[2:]。

所有结果输出:

第3关 内建容器_go语言

【一定要知道在第30行之前,跟s2有关的所有的变化的代码是什么?见注释】

slice上面还可以建立slice,那么这些不同的slice,ta们都分别都是view同一个arr,‌‌下面数据我们就开了7个整数,‌‌然后我上面建了非常多的各种不同的view,这是go的slice非常特别的地方。

接下来我们来看一些更加难的sclie的用法,

arr := [...]int{0,1,2,3,4,5,6,7}
s1 := arr[2:6]
s2 := s1[3:5]

我们来问一个问题,‌‌我们有 array还是0 1 2 3 4 5 6,

我们的问题来了,‌‌s1是多少?‌‌s2是多少?或者说s2到底取不取得出来,会不会报错?‌‌

‌那么arr和slice我们讲到之前也都很简单,slice就是一个view,‌‌它改变了,它就能够改变底层的arr,然后slice还可以reslice,非常简单。

s1我们都知道,就是2345,‌‌ s1里面2345,有几个元素,一共4个元素,

s2是从s1的下标开始取的,‌‌它取3是取得到的,但是5,s1我一共就4个元素,它取第5个它根本就取不到。‌

slice它是可以扩展的,

// ...
func main(){
fmt.Println("Extending slice")
arr[0],arr[2] = 0,2
s1 = arr[2:6]
s2 = s1[3:5] // [s1[3],s1[4]]
fmt.Println("s1=",s1) // s1=[2 3 4 5]
fmt.Println("s2=",s2) // s2=[5 6]
}

我们为了说清楚,我们把这arr的0‌‌,arr的2把它给设回去,等于0,2,对不对?设回去了以后,我们先来看结果,‌‌s1等于s1,s2=s2。

我们先来看这个结果,‌‌我们看到最后Extending slice,

s1等于[2 3 4 5],没问题,

s2等于[5 6],‌‌我们看到这个6都不是在s1里面,它也会出来。

s2 := s1[3:5],我们取的是谁?

[s1[3] s1[4]]这两个值组成的一个slice,结果它出来的是5和6,s1[4]根本就不在s1里面。‌【s1 = [2 3 4 5] s1[0] s1[1] s1[2] s1[3]】

‌我们想取 s1[4]它是取不出来的,下表会越界,s1[4]根本就没有的,

但是我们用一个slice它是可以取出来的,‌‌我们具体来看看 6 到底是怎么取出来的。

我们slice是对 arr 的一个view,它到底怎么view的?‌‌

我们来看一下,我们底层的数组是01234567,

那数组中的数值就是01234567,‌‌

然后我们做了s1 := arr[2:6],取到的是2345 这4个数字‌‌。

‌2345这4个数字映射到s1的下标就是0123,

然后s1里面‌‌因为是对底层的arr的一个view,它不是说到3就结束了,它下标到3以后还有一个打阴影的 4 5 ,4 5我直接用s1[4],s1[5]是看不见的,

但是ta还是知道‌‌s1下面还是有4和5两个元素的,因为ta知道底层的 arr,‌‌

然后 s2 := arr[3:5],是不是就取了3 4?‌

‌那3 4取出来呢?‌‌

s2的下标也是012,s2也是有阴影的,ta只取了3 4,那么3 4值到底是多少呢?‌‌

5和6。

第3关 内建容器_第3关 内建容器_02

我们看看slice的实现是怎么样的。‌

‌我们看看它的内部的机构,我们看到斯莱斯‌‌它底层有一个arr,

就是arr下面是涂成这样三种颜色的,其中它有一个叫做ptr,‌‌它指向了slice开头的元素,然后它有一个len,说明我 slice的长度是多少,‌‌我去用方括号取值的时候只能取到len里面的值,我超过len或者说我‌‌下标大于等于len,因为它从0开始我下标大于等于len它都会报错,说你下标越界了,‌‌但是它还有一个值叫做capacity,cap是代表了我下面整个array,‌‌从我ptr开始到结束整个的一个长度,slice的实现里面其实就装了这3个变量,‌‌

第3关 内建容器_数组_03

它在扩展的时候,它知道,‌‌我们只要不超过这个capacity,我们都能够扩展。

因此我们再回到这个例子,‌‌

s1的值是[2 3 4 5],s2的值是[5 6]。

slice是可以向后扩展的,不可以向前扩展。‌

‌比如说我s1看到的值有2345,我s2再怎么看,它也最多只能向前看到2,它前面的01就彻底看不到了,但是向后它还可以看下去,‌‌也就是说可以向后扩展,怎么扩展?

s[i]它不可以超越 len(s),‌‌ 长度是4,我就只能取 方框号0123,但向后扩展可以超越 len(s),‌‌但是当然不可以超越底层的数组,底层的数组叫做cap(s)。

lens和capacity‌‌值到底是多少?我们是可以取得的。‌

// ...
// updateSlice 把传过来的切片首个元素改成100
func updateSlice(s []int){
s[0] = 100
}

func main(){
arr := [...]int{0,1,2,3,4,5,6}

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)

fmt.Println("After updateSlice(s2)")
updateSlice(s2)
fmt.Println(s2)
fmt.Println(arr)

fmt.Println("Reslice")
fmt.Println(s2)
s2 = s2[:5]
fmt.Println(s2)
s2 = s2[2:]
fmt.Println(s2)

fmt.Println("Extending slice")
arr[0],arr[2] = 0,2
fmt.Println("arr=",arr)
s1 = arr[2:6]
s2 = s1[3:5] // [s1[3],s1[4]]
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))
}

‌‌‌在这之前先把arr打印出来,这样看得明显一点。‌

运行结果:

第3关 内建容器_go语言_04

我们这arr是0123456,那s1值的是2345,‌‌它的len是4,它的capacity是5,

也就是说2345,‌‌它的 capacity就是容量有5个这么大。‌

‌然后再看s2,s2的值是5 6,它的len是2,它的capacity是2,

s1 3 冒号 我们的capacity是达到了5,‌‌因此我们说5也是可以的,我们说6那就不可以了,

如果我再加一行代码:

fmt.Println(s1[3:6])
// panic: runtime error: slice bounds out of range [:6] with capacity 5

panic: runtime error: slice bounds out of range [:6] with capacity 5

slice bonds不是看 len的,是看 capacity,这个5的话‌‌它还是可以的,5的话就是5 6,

fmt.Println(s1[3:5]) // [5 6]

以上就是slice它可以向后扩展。‌

3-3 切片的操作

向slice添加元素。

arr := [...]int{0,1,2,3,4,5,6,7}
s1 := arr[2:6]
s2 := s1[3:5]
s3 := append(s2,10)
s4 := append(s3,11)
s5 := append(s4,12)

‌经过上小节的扩展之后,我们再来考虑一下,像slice添加元素又怎么样?‌‌

大家再想想上述代码运行后,s3、s4、s5分别是多少?arr的值是多少?

案例:

// updateSlice 把传过来的切片首个元素改成100
func updateSlice(s []int){
s[0] = 100
}

func main(){
arr := [...]int{0,1,2,3,4,5,6}

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)

fmt.Println("After updateSlice(s2)")
updateSlice(s2)
fmt.Println(s2)
fmt.Println(arr)

fmt.Println("Reslice")
fmt.Println(s2)
s2 = s2[:5]
fmt.Println(s2)
s2 = s2[2:]
fmt.Println(s2)

fmt.Println("Extending slice")
arr[0],arr[2] = 0,2
fmt.Println("arr=",arr)
s1 = arr[2:6]
s2 = s1[3:5] // [s1[3],s1[4]]
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)

}

运行结果:

第3关 内建容器_数组_05

我们先来看看结果,我们可以看到s2是5 6,

s3就是5 6 10,‌‌s4就是5 6 10 11,s5就是5 6 10 11 12,它都是append,

‌我们 append 不管 capacity是多少,我们看到s2的capacity后面只有一个6【底层数组 arr= [0 1 2 3 4 5 6]

】,‌‌

它后面6做完了以后就没数据了,怎么看到这arr它还是一个长度为7的一个数组,‌‌arr还是长度为7,

然后第一次s2它5 6,这arr后面一个值是6对吧?5 6,‌‌

对于 s3,s4,s5,然后append了一个10,我们再 append了一个11,再append了一个12去哪了?‌

‌大家想想这10 ,11和12它存在什么地方?‌‌

它肯定不是存在slice里的,slice始终对一个arr的view,这个时候我们s3和s4,‌‌它们view的就不是 arr,它们view的是一个新的arr,

go语言‌‌会在内部开一个新的arr,把arr拷过去,新的arr的长度它会设得更加长一些,‌‌

s4和s5不再是对arr的一个view,它view的是谁?‌

‌我也不知道是谁,那系统已经帮我们分配了一个更加长的新的arr,来填下我们这么多的数据。‌

‌因此我们发现‌‌添加元素时如果超越capacity,系统会重新分配更大的一个底层数组,‌‌并且原来的元素它就会拷贝过去。‌

‌那你可能会问,我们原来这个数组怎么办呢?‌‌

go语言是有垃圾回收机制的,原来的数组如果有人用,那就像这里,如果我们的arr如果有人用,‌‌它还在;

如果没人用,原来的数组就会被垃圾回收掉,系统会重新分配更大的一个底层数组。‌

‌还有一个因为由于值传递的关系,必须接受append的返回值,这个什么意思呢?‌‌

第3关 内建容器_第3关 内建容器_06

我们再看slice的一个结构,我们有一个ptr len 一个capacity,

如果往里面append它有可能改变什么?它的len会改变。它的capacity会改变吗?

也可能改变,如果说‌‌它这个数组装不下更多的元素,我要去分配一个新的底层数组, 那么ptr和capacity都会改变,‌‌

我们因此可以造成一旦append的一个元素,slice里面的几个值都会变掉,因此我们必须要‌‌采用一个新的slice来接收 append 的返回值。‌

‌一般我们写成这样 s = append(s,value),

s是一个slice,然后我们就不断的给它append value就可以了。‌

以上就是slice的append操作。

‌那接下来我们再来看看slice的其他操作,‌‌比如说copy delete这些动作,以及我们如何来创建。

首先我们来看看怎么来创建slice。

之前我们可以看到从一个array来创建slice,但是一般‌‌我们array都不用定义,我们就直接创建一个slice,var s []int 就可以了。‌

‌ var s []int ,我们定义了一个什么东西?

它定义了我们 s 它的类型是一个slice,‌‌但是slice值是多少?它下面的arr有没有?还没有。‌

‌那么var 只是声明变量的类型,‌‌但是go语言它有一个特点,‌‌每个变量我一旦定义,它就有一个叫做 zero value for slice‌‌ is nil。

这个时候我们 s 的 值实际上是 nil,go语言 没有 none 它是用nil来表示没有‌‌。

nil slice 它下面并没有分配数组,我们可不可以给它append值呢?

可以。‌

‌既然它有一个叫做zero value,就是说这个value我们是可以用的。

案例代码:

func main(){
var s []int // zero value for slice‌‌ is nil

for i := 0;i < 100; i++{
s = append(s,2 * i + 1)
}
fmt.Println(s) // [1 3 5 7 9 ... ]
}

‌我们来试一试,for i冒号‌‌等于0 , i小于100,我们去加100个元素,s等于append s‌‌ 我们给ta送一个2乘i+1,

然后我们打印s。1 3 5 7 9 等等等 ,前100个奇数就生出来了。‌

‌我们想看一下我们 s它在整个过程中,‌‌它的len是多少,capacity是多少?我们系统到底怎么分配的?我们来试一试。‌

定义一个函数叫做printSlice,形参s 我们说它是中括号int,‌‌

案例代码:

func printSlice(s []int){
fmt.Printf("len=%d,cap=%d\n",len(s),cap(s))
}

func main(){
var s []int // zero value for slice‌‌ is nil

for i := 0;i < 100; i++{
printSlice(s)
s = append(s,2 * i + 1)
}
fmt.Println(s) // [1 3 5 7 9 ... ]
}

运行结果:

第3关 内建容器_go语言_07

第3关 内建容器_第3关 内建容器_08

第3关 内建容器_go语言_09

第3关 内建容器_第3关 内建容器_10

第3关 内建容器_go语言_11

‌大家看看,一开始的时候我们这个s它下面是没有值的,是一个nil,但是nil我们系统不会崩溃,‌‌这是go语言的特点,它nil值也不会崩溃,就是len是0 cap是0,

然后我们不断的添加数字,‌‌我们看到len 是 1 2 3 4 5 6 7 8 依次递增,cap每次它装不下了,就去扩充乘以2,cap是0 1 2 4 8 16 32 64,‌‌当len是65的时候,cap 由64扩到128它装下了,

我们除了这种创建方法,我们还有一些其他的创建slice的方法,比如说我们s1 冒号等于‌‌中括号int,我们可以给它一些值,比如说2 4 6 8,

s1 := []int{2,4,6,8}

‌这行代码它做了什么事情?‌

‌我们建了一个array,‌‌它里面的值是2 4 6 8,然后又建了一个slice去view 这个arr 2 4 6 8,‌‌

然后我们去printSlice(s1),

或者我们还可以这样建,‌‌许多情况我们知道我们要建一个多大的slice但是里面值还不知道,

‌怎么建呢?

比如说我们想建一个长度为16的slice,这个时候我们要用到一个内建的函数叫做make,‌‌make什么? make一个int slice,然后告诉ta我的长度是16,这个就make好了,‌‌

s2 := make([]int,16)
s3 := make([]int,10,32)

或者我们make可以跟上它的capacity,中括号int,长度是10,‌‌但是它capacity比较大,32,

也就是说它虽然10个元素,但是下面的数组我要开32的长度,‌‌我们再来printSlice,

printSlice(s2)
printSlice(s3)

‌运行结果:

第3关 内建容器_go语言_12

我做了一个16‌‌大小的slice然后我做了个十10大小的slice,它虽然短一点,但是它的capacity比较大是32‌‌,我可能预估到我的slice长得会很快,我一上来就多开一些空间给它‌。

然后我们来做一个‌‌拷贝 slice,我们有一个系统内嵌的copy函数,第一个参数是副本,第二个参数是source,是原本,‌‌

// ...
func printSlice(s []int){
fmt.Printf("value=%d,len=%d,cap=%d\n",s,len(s),cap(s))
}

func main(){
var s []int // zero value for slice‌‌ is nil

for i := 0;i < 100; i++{
printSlice(s)
s = append(s,2 * i + 1)
}
fmt.Println(s) // [1 3 5 7 9 ... ]
s2 := make([]int,16)
s3 := make([]int,10,32)
printSlice(s2)
printSlice(s3)
fmt.Println("Copying slice")
s1 := []int{2, 4, 6, 8}
copy(s2,s1)
printSlice(s2)
}

运行结果:

第3关 内建容器_go语言_13

这时我们s2变成多少?

显然s2把2 4 6 8给拷进去了。‌

‌然后我们还要做一件事情 Deleting elements from slice。‌

‌怎么来删除一个element,系统没有内嵌的函数,但是我们有slice的reslice功能,‌‌它非常好做,比如说我想从[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0]里面把8这个元素删掉,‌‌删掉以后要把后面移上来,怎么删?‌

‌大家看好,首先我们知道8它的下标是3,‌‌也就是说要把下标为3的删掉,下标为3是什么? s2冒号3对不对?‌‌

s2冒号3【s2[:3]】就是0 1 2 就是2 4 6,

然后我们要去加上 s2 的4冒号【s2[4:]】对不对?‌‌

要把3跳掉,那这样就是2 4 6,然后3被跳掉了,然后从4开始那就是后面所有的,‌‌但是slice它没有加法,我们要用append函数来做, s2等于append,‌‌首先append谁?

我们往s2冒号3【s2[:3]】append,append的元素是谁?‌

‌是所有‌‌ s2里面的元素,

第3关 内建容器_数组_14

但是我们可以看到 append的后面是个可变的参数,那可变参数的话,我们通常‌‌我会说0,0,0这样一个个打进去,

但是我们现在想要append的s2 的4冒号【s2[4:]】所有的元素,4开头就是后面这一串0,‌‌想要一起送给ta怎么办? go语言有这样的语法,后面再加三个点就可以了,s2[4:]…

案例代码:

func printSlice(s []int){
fmt.Printf("value=%d,len=%d,cap=%d\n",s,len(s),cap(s))
}

func main(){
var s []int // zero value for slice‌‌ is nil

for i := 0;i < 100; i++{
printSlice(s)
s = append(s,2 * i + 1)
}
fmt.Println(s) // [1 3 5 7 9 ... ]
s2 := make([]int,16)
s3 := make([]int,10,32)
printSlice(s2)
printSlice(s3)
fmt.Println("Copying slice")
s1 := []int{2, 4, 6, 8}
copy(s2,s1)
printSlice(s2)

fmt.Println("Deleting elements from slice")
s2 = append(s2[:3],s2[4:]...)
printSlice(s2)
}

运行结果:

第3关 内建容器_golang_15

可以看到把8删掉了,len由16变成了15,capacity还是16,‌‌它认为上一个元素我们没有必要去缩减它的大小,这就是slice的一些操作。‌

‌那么删除中间的元素可能不是很多,我们常做的是删除头尾。

我们看poping from front。

func main(){
//...
fmt.Println("poping from front")
front := s2[0]
s2 = s2[1:]
}

然后我们再来‌‌ poping from back,从尾巴去pop。‌

func main(){
// ...
fmt.Println("poping from back")
tail := s2[len(s2) -1]
s2 = s2[:len(s2) -1]
}

这就是popping from back,把它最后一个尾巴拿下来。‌‌

总代码:

func printSlice(s []int){
fmt.Printf("value=%d,len=%d,cap=%d\n",s,len(s),cap(s))
}

func main(){
var s []int // zero value for slice‌‌ is nil

for i := 0;i < 100; i++{
printSlice(s)
s = append(s,2 * i + 1)
}
fmt.Println(s) // [1 3 5 7 9 ... ]
s2 := make([]int,16)
s3 := make([]int,10,32)
printSlice(s2)
printSlice(s3)
fmt.Println("Copying slice")
s1 := []int{2, 4, 6, 8}
copy(s2,s1)
printSlice(s2)

fmt.Println("Deleting elements from slice")
s2 = append(s2[:3],s2[4:]...)
printSlice(s2)

fmt.Println("poping from front")
front := s2[0]
s2 = s2[1:]

fmt.Println(front)
printSlice(s2)

fmt.Println("poping from back")
tail := s2[len(s2) -1]
s2 = s2[:len(s2) -1]

fmt.Println(tail)
printSlice(s2)
}

运行结果:

第3关 内建容器_go语言_16

我们来看一下,‌‌经过 poping from front,这2就弹出来了,然后剩下来的只能是4 6 以及一堆0,‌‌

它len是14,

经过 poping from back,这尾巴是一个0,然后它把尾巴的0拿掉,然后len变成了13,‌‌

这就是slice的一些操作。‌

3-4 Map

看看map的定义:

m := map[string]string{
"name":"keagen",
"course":"golang",
"site":"yuque",
"quality":"notbad",
}

定义方法:map[key的数据类型]value的数据类型。

map还可以复合map:

map[key]map[key2]value

案例代码:

func main(){
m := map[string]string{
"name":"keagen",
"course":"golang",
"site":"yuque",
"quality":"notbad",
}
m2 := make(map[string]int) // m2 == empty map
var m3 map[string]int // m3 == nil
fmt.Println("m, m2, m3:")
fmt.Println(m,m2,m3)
}

运行结果:

第3关 内建容器_go语言_17

go语言的nil不像其他语言的none,go语言的 nil 都是可以参与运算的, nil 和 empty map它们在运算起来都是可以混用的,我们即使是一个nil的值,我们也很安全的可以进行使用。‌

接下来我们来看看map的一个遍历,如:

func main(){
m := map[string]string{
"name":"keagen",
"course":"golang",
"site":"yuque",
"quality":"notbad",
}
m2 := make(map[string]int) // m2 == empty map
var m3 map[string]int // m3 == nil
fmt.Println("m, m2, m3:")
fmt.Println(m,m2,m3)

fmt.Println("Traversing map m")
for k,v := range m{
fmt.Println(k)
}
}

‌运行结果:

第3关 内建容器_go语言_18

‌当然我们 k v还是可以省略的,比如说我们省略了一个v‌‌这样就把它key打出来:

for k:= range m{…}

我们只要value‌‌,

for _,v:=range m{…}

只要value,一般我们这样用法不多,

我们还是恢复成一个k v。‌

不知道大家有没有注意过,如果没注意的话,大家可以把本小节运行结果的图片对比着看一下,

你会发现这些key它在map里是无序的,这个map是一个hash map。‌

‌好,接下来我们来看看 map的操作,

‌‌比如说我们想要获得 course的名字,courseName 冒号等于‌‌ m中括号字符串course,即

courseName := m[“course”]

func main(){
m := map[string]string{
"name":"keagen",
"course":"golang",
"site":"yuque",
"quality":"notbad",
}
m2 := make(map[string]int) // m2 == empty map
var m3 map[string]int // m3 == nil
fmt.Println("m, m2, m3:")
fmt.Println(m,m2,m3)

fmt.Println("Traversing map m")
for k,v := range m{
fmt.Println(k)
}

fmt.Println("Getting values")
courseName := m["course"]
fmt.Println(courseName) // golang
}

假如/如果 course 拼错了怎么办?‌‌然后拼成了cauau,这个时候我们会出现什么情况,大家可以猜一猜?

是啥都没有的,是运行会出错,还是会输出什么东西?

运行结果:

第3关 内建容器_第3关 内建容器_19

‌它其实是打了一个空串,所以当我们去取得map里面不存在的 key的时候,‌‌它还是能够拿到值的,

拿到的是 zero value,我之前也说到过几遍zero value,‌‌go语言它不怕 我们这个变量不初始化,不初始化变量我也能够用,我用的就是一个zero value,‌‌ course 当然是个string,

我们怎么判断 key到底在不在,我们用这样的方法,再收一个叫做ok:

代码案例:

func main(){
m := map[string]string{
"name":"keagen",
"course":"golang",
"site":"yuque",
"quality":"notbad",
}
m2 := make(map[string]int) // m2 == empty map
var m3 map[string]int // m3 == nil
fmt.Println("m, m2, m3:")
fmt.Println(m,m2,m3)

fmt.Println("Traversing map m")
for k,v := range m{
fmt.Println(k)
}

fmt.Println("Getting values")
courseName,ok:= m["course"]
fmt.Println(courseName,ok) // golang
cauauName,ok:= m["cauau"]
fmt.Println(cauauName,ok)
}

运行结果:

第3关 内建容器_数组_20

‌我们来看一下,‌‌我们看到golang它是ok的,它是存在于这个map里面,‌‌

但是 cauau拼错了以后,它是不存在在 map里面,它是false的,一般我们这样写‌‌:

if courseName,ok := m["course"];ok{
fmt.Println(courseName)
}else{
fmt.Println("key does not exist")
}

还有一个就是删除元素,比如说我们把‌‌name delete掉,我们有个delete函数,我们看到是map里面一个key叫name,

然后我们key叫name 给 delete掉,那么delete之前‌‌:

fmt.Println("Deleting values")
name,ok := m["name"]
fmt.Println(name,ok)
delete(m,"name")
name,ok = m["name"]
fmt.Println(name,ok)

‌‌第3关 内建容器_golang_21

本来 name keagen 是有的,我们用了delete后它就变false。‌

回顾一下,

首先我们创建用 make‌‌(map[string]int),

获取元素,m[key],

key不存在的时候,大家注意这个不会报错,‌‌它获得的是value 类型 的0值,或者说是一个zero value,是0值。‌

‌然后我既然获得了初始值或者0值,我怎样来判断是否存在,我用两个值去收它。‌‌

value,ok := m[key]来判断是否存才key。

删除就用delete函数,我们可以用它来删除一个key。‌

‌然后我们来看map的遍历,

使用range遍历key,或者遍历key,value键值对,还可以只遍历value。

不能保证遍历的顺序,如果需要排序,那么手动对key进行排序。

怎么手动排序?

我们把所有的key拿出来加到一个slice里面去,

然后slice是可以排序的,排完之后再去遍历 map,‌‌使用len来获得元素的个数。‌

‌我们再来看 map里面 key和value都能放一些什么样的类型,‌‌什么样类型能作为key,go语言它使用哈希表,我们的key必须比较相等。‌

几乎所有的内建类型都可以作为key,但除了slice map function 的内建类型都可以作为key,‌‌

slice map function 我们没法判断相等,‌‌其他类型都可以作为key,什么int,string这些当然都可以。‌

‌我们自定义的类型怎么办?‌‌

struct类型不包含上述字段,也可以作为key。

自定义的类型我们也不用写什么,我们的一个struct类型,不包含上述字段,也可以作为k‌‌ey。

struct也能作为key是怎么检查的呢?‌

‌编译的时候检查的,它会看你 struct里面到底含不含上面这些内建的类型,这就是map的key。‌

通常我们作为map的key,‌‌我们里面不会放这种slice,map、function这种类型的东西,‌‌通常来说我们也不用做过多的考虑,直接用struct类型的填上去就可以了。‌‌

3-5 字符和字符串处理

rune相当于go的char。int32的别名就是rune。

在go语言中,英文占用一个字节,中文占用三个字节。


举报

相关推荐

0 条评论