go语言的数组切片和映射

PHP的数组我感觉是最好用的数据结构了,开发完全不用担心它在内存上做了什么,相比下C语言中的数组就需要开发另外操心做一些内存上的处理。现在我们来了解下GO中的数组,切片和映射,你会发现Go语言的切片和映射都是基于的数组实现的。数组的容量即是声明时的容量,不可以再做更改,切片有容量的限制不过可以使用append函数扩容,映射没有容量或者任何限制。切片和映射传递的成本都很小,并且不会复制底层数据结构,通俗理解是Go底层帮我们维护了一个底层数组,切片和映射只是指向这个底层数组的内存地址。

数组

数组在内存中是连续分配的,因为连续分配CPU能把正在使用的数据缓存更久也更容易计算索引。

声明和初始化

声明一个数组,并设置为零值

1
2
//声明一个包含5个元素的整型数组
var array [5]int
  • 一旦声明,数组里储存的数据类型和数组长度就都不能改变
  • go语言数组初始化,每个元素对数对应类型的零值

使用数组字面声明数组

1
2
3
//声明包含5个元素的整型数组
//用具体值初始化每个元素
array := [5]int{1,2,3,4,5}

让Go自动计算声明的数据长度

1
2
3
4
//声明包含5个元素的整型数组
//用具体值初始化每个元素
//容量由初始化的数量决定
array := [...]int{1,2,3,4,5}

声明数组并指定特定元素的值

1
2
3
4
//声明一个5个元素的数组
//用具体值初始化元素
//其余元素为0
array := [5]int{3:5,4:2}

使用数组

访问数组元素

1
2
3
4
5
6
7
8
array := [...]int{1,2,3}
//修改索引为2的元素的值
array[2] = 1
//range迭代打印数组元素
for key,value := range array{
fmt.Printf("key:%d,value:%d\n",key,value)
}

访问指针数组元素

1
2
3
4
5
6
7
8
9

//声明一个包含5个元素的指向整数的数组
//用整数指针初始化索引为0的元素
array := [5]*int{0:new(int)}
//为索引0的元素赋值
*array[0] = 10
//解引索引0的值
fmt.Print(*array[0])

多维数组

1
2
3
4
5
6
7
8
9
10
11
//声明一个多维数组,两个维度分别储存4个元素和2个元素
var array [4][2]int

//使用数组字面量来声明并初始化一个二维数组
array := [4][2]int{{1,2},{3,4},{5,6},{7,8}}

//声明并初始化外层数组中索引为1和3的元素
array := [4][2]int{1:{1,2},3:{2,3}}

//声明并初始化外层数组和内层数组的单个元素
array := [4][2]int{1:{0:20}}

使用指针在函数内传值

指针的概念是指向一个值的内存地址,理解了这个概念我们就会知道,用指针来传递值可以减少内存的开销。

1
2
3
4
5
6
7
8

func main() {
//分配一个需要8MB的数组
var array [1e6]int
//讲数组的地址传递给foo函数
foo(&array)


切片

切片是一种数据结构,这种数据结构便于使用和管理数据集合。切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片有三个参数 分别是 指向底层数组的指针切片访问元素个数允许增长到的元素个数

创建和初始化

make声明切片

使用长度声明一个字符串切片

1
2
//创建一个字符串切片,其长度和容量都是5个元素
slice := make([]string,5)

使用长度和容量声明整型切片

1
2
//创建一个int切片,其长度3容量为5
slice := make([]int,3,5)

通过切片字面量来声明切片

1
2
//创建一个内容3个元素的字符串切片
slice := []string{"red","blue","green"}
1
2
//创建一个容量5个元素的整型切片
slice := []int{1,2,3,4,5}

使用索引声明切片

1
2
//创建字符串切片,使用空字符串初始化第100个元素
slice := []string{99:""}

记住[]中有了值就不是切片了是数组

使用切片

切片是在数组的基础上丰富的一个对象,切片操作和数组操也很像。[]写入了数量是数组,切片之所以是切片因为创建一个新的切片就是把底层数组切出一部分。

1
2
3
4
5
6
7
8
//创建一个长度容量为5的切片
slice := []int{1,2,3,4}
//创建一个长度为2容量为4的新切片
newSlice := slice[1:3]
//打印
for k,v := range(newSlice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

如何计算长度和容量

对底层数组容量是K的切片 slice[i:j]

长度:j-i
容量:k-i

切片创建的切片修改值

切片创建的切片其实用的是同一份内存地址,即新老切片同时指向一个内存地址。内存地址中的值更改了切片的内容也改了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建一个长度容量为5的切片
slice := []int{1,2,3,4}
//创建一个长度为2容量为4的新切片
newSlice := slice[1:3]

//修改新切片的第一个元素
newSlice[0] = 10

for k,v := range(slice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

fmt.Print("------- \n")

for k,v := range(newSlice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

输出

1
2
3
4
5
6
7
8
k=0,v=1
k=1,v=10
k=2,v=3
k=3,v=4
-------
k=0,v=10
k=1,v=3

切片增长(append)

相对数组而言,使用切片的一个好处是,可以按需增加切片的容量。Go内置的append函数会处理增加长度时所有操作细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建一个长度容量为5的切片
slice := []int{1,2,3,4}
//创建一个长度为2容量为4的新切片
newSlice := slice[1:3]

//newSlice切片新增一个元素
newSlice = append(newSlice,60)


for k,v := range(slice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

fmt.Print("------- \n")

for k,v := range(newSlice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

输出

1
2
3
4
5
6
7
8
9
k=0,v=1 
k=1,v=2
k=2,v=3
k=3,v=60
-------
k=0,v=2
k=1,v=3
k=2,v=60

可以看到append在底层数组还有额外容量可用的情况下会直接修改底层数组,在不可用的情况下会创建一个新的底层数组并把值复制过去再追加新的内容。追加规则为1000以下X2,1000以上X1.25即增长25%

设置长度和容量一样的切片

如上可见,append会修改底层数组造成数据上的污染。长度和容量一样的好处是可以避免这种污染,它会创建一个属于自己的底层数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建一个长度容量为5的切片
slice := []int{1,2,3,4}
//创建一个长度为2容量为4的新切片
newSlice := slice[1:3:3]//注意这里切片的第3个值

//newSlice切片新增一个元素
newSlice = append(newSlice,60)


for k,v := range(slice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

fmt.Print("------- \n")

for k,v := range(newSlice){
fmt.Printf("k=%d,v=%d \n",k,v)
}

输出结果

1
2
3
4
5
6
7
8
k=0,v=1 
k=1,v=2
k=2,v=3
k=3,v=4 //数据未被修改
-------
k=0,v=2
k=1,v=3
k=2,v=60 //追加上了新值

迭代切片 for range

前面有用到过不详细说明了

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
sclie := []string{"aaa","bbb","ccc"}

for k,v := range(sclie){
fmt.Printf("k=%d,v=%s\n",k,v)
}
}

原始实现

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
sclie := []string{"aaa","bbb","ccc"}

for index := 0;index <len(sclie) ; index++ {
fmt.Printf("k=%d,v=%s\n",index,sclie[index])
}
}

多维切片

和多维数组的玩法一样,差别就是别指定个数

1
2
3
4
//创建一个整型切片
slice := [][]int{{10},{100,200}}
//为第一个切片追加值为20的元素
slice[0] = append(slice[0],20)

在函数间传递切片

切片是底层数组的解引,所以不用像数组一样加上&地址符,切片本身的数据关联在底层数组里,所以讲切片复制到任意函数,对底层数据大小都不会有影响。

映射

映射是一种数据结构,用于存储一系列无序的键值对。映射会在散列表中进行一些操作高效的找到键值对

创建和初始化

1
2
3
4
5
6
//创建一个映射,键的类型是string,值的类型是int
dict :=make(map[string]int)
//创建一个映射,键和值类型都为string并初始化
dict := map[string]string{"red":"#da1337"}
//创建一个映射,使用字符串切片为值
dict := map[string][]string{}

使用映射

打印

创建一个映射,判断是否纯在并且打印出来

1
2
3
4
5
6
7
colore := map [string]string{}
colore["red"] = "#da1337"

value,exists :=colore["red"]
if exists{
fmt.Print(value)
}

删除

可以用delete删除一个键值对

1
delete(colore,"red")

在函数间传递映射

在函数间传递映射和切片类似,都是传递的内存地址。不会另外创建内存副本。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~