Go数组、切片和映射


数组,切片和映射

数组

声明数组必须指定数据类型和数组的长度,而且其不可改变
数组的每个元素都初始化为对应变量的零值


//声明数组,必须指定数据类型和存储元素的数量
var varArray [5]int
// 声明并初始化
varArray2 := [5]int{1, 2, 3, 4}
// 初始化的长度由初始化的值来决定
varArray3 := [...]int{1, 2, 3, 4, 9, 10}
fmt.Println(len(varArray3)) // 6

// 通过索引使用数组
fmt.Println(varArray[2]) // 0

//复制数组,内存空间会复制。编译器会阻止不同类型复制
varArray4 := varArray2
varArray4[3] = 20

fmt.Println(varArray2[3]) //4
fmt.Println(varArray4[3]) //20

// 多维数组
var array = [4][2]int{{10, 11}, {12, 13}}
fmt.Println(array[1]) // [12 13]
// 可以指定下标
var array1 = [4][2]int{0: {10, 11}, 1: {12, 13}}
fmt.Println(array1[1]) // [12 13]

切片

切片是围绕动态数组构建的,可以按需增大或者缩小。切片的动态数组是通过内置的append来实现。append也可以用来合并两个切片。

如果在[] 运算符里指定了一个值,那么创建的就是数组而不是切片。反之,创建的则时切片。

切片之所以称为切片,是因为创建一个新的切片就是把底层数组切出一部分。同时注意切片只能访问其长度内的元素。

//切片操作,底层共享
newSlice := []int{1, 2, 3, 4, 5, 6}
newSlice1 := newSlice[1:3] // 对于容量为k的[i:j]的切片,长度为j-i,容量为k - i
newSlice1 = append(newSlice1, 60)
fmt.Println(newSlice)  // [1 2 3 60 5 6]
fmt.Println(newSlice1) //[2 3 60]

为了避免上述现象,可以使用第三个变量控制新切片的容量。其目的不是为了增加容量,是为了限制容量.

newSlice2 := newSlice[1:3:3]
newSlice2 = append(newSlice2, 70)
fmt.Println(newSlice) // [1 2 3 60 5 6]
fmt.Println(newSlice2) // [2 3 70]

切片的操作

可以使用append对两个切片进行合并。

可以使用for range对切片进行遍历。range创建了每个元素的副本,而不是直接返回该元素的引用。因此,一个迭代过程中切片返回变量的地址总是相同,即&value的地址是相同的。

   // 切片合并
newSlice3 := append(newSlice1, newSlice2...)
fmt.Println(newSlice3) // [2 3 60 2 3 70]

   //切片的遍历
for index, value := range newSlice3 {
	fmt.Printf("index is %d, value is %d, ElemAdrr is %X, ValueAdrr is %X \n", index, value, &newSlice3[index], &value)
}
/**
index is 0, value is 2, ElemAdrr is C000010230, ValueAdrr is C0000141A0
index is 1, value is 3, ElemAdrr is C000010238, ValueAdrr is C0000141A0
index is 2, value is 60, ElemAdrr is C000010240, ValueAdrr is C0000141A0
index is 3, value is 2, ElemAdrr is C000010248, ValueAdrr is C0000141A0
index is 4, value is 3, ElemAdrr is C000010250, ValueAdrr is C0000141A0
index is 5, value is 70, ElemAdrr is C000010258, ValueAdrr is C0000141A0
*/

切片的容量策略

  • 当元素个数小于1024,则增长因子为2,以2的倍数增长
  • 当元素个数多余1024,则增长因子为1.25.
var capSlice []int
for i := 0; i < 511; i++ {
	capSlice = append(capSlice, i)
}
fmt.Println(cap(capSlice)) // 512
for i := 0; i < 2; i++ {
	capSlice = append(capSlice, i)
}
fmt.Println(cap(capSlice)) // 1024
for i := 0; i < 525; i++ {
	capSlice = append(capSlice, i)
}
fmt.Println(cap(capSlice)) // 1280

映射

映射是一种数据结构,用户存储一些列的无序的键值对。底层使用的是散列表。

映射的散列表包含一组桶,在存储、删除或者查找键值对的时候,所有操作都要先选择一个桶,把操作映射时指定的键传到映射的散列函数,就能选中对应的桶。

映射使用两个数据结构来存储数据。一个数据结构时数组,内部存储的时用于选择桶的高八位,用于区分每个键值对要存在哪个桶里。另外一个数据结构时字节数组,用于存储键值对。该字节数组先依次存储了这个桶的所有键,之后依次存储这个桶里的所有值,这种实现可以减少每个桶所需的内存。

映射的键可以是内置的类型,也可以是结构类型。只要这个值可以使用==运算符做比较。但是对于切片、函数以及包含切片的结构类型这些类型具有引用语义,不能作为映射的键

// 创建映射,键的类型是string,值的类型是int
dict := make(map[string]int)
dict1 := map[string]int{"red": 1, "blue": 2}

value, exists := dict1["red"]   // 第一个返回值表示值,第二个返回值表示这个键是否存在
value1, exists1 := dict1["eee"] // 如果不存在键,value返回默认零值
value2 := dict1["red"]          // 只有一个参数的时候返回值
fmt.Println(exists, value)      //true 1
fmt.Println(exists1, value1)    //false 0
fmt.Println(value2)             // 1
fmt.Println(dict)

//使用内置delete()删除值
delete(dict1, "red")

//迭代映射
for key, value := range dict1 {
	fmt.Printf("key is %s, value is %d\n", key, value)
}
/**
key is blue, value is 2
*/

文章作者: 彭峰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 彭峰 !
  目录