go学习笔记(数组&切片)
数组
数组是存储在一段连续内存中的固定长度的数据类型。数组中的数据类型是一致的可以是内置的类型,也可以是自定义的数据结构类型,由于在内存中是连续的所以很容易计算索引,快速迭代数组中的内容。
- 数组的声明和初始化以及使用
var arr1 [5]int //声明一个长度为5的数组,声明后数组的长度不能改变,数组中的值总是用零值存储。
arr2 := [5]int{1,2,3,4,5}// 声明并初始化长度为5的数组,并且数组中的值依次是{1,2,3,4,5}
arr3 := [...]int{1,2,3,4,5} //... 数组的长度由初始化的值决定。
arr4 := [5]int{1:5,2:4} //声明长度为5的数组,初始化数组中下标为1和2的值为5,打印数组{0,5,4,0,0}
arr4[1] = 2 //2赋给数组中下标为2的元素。打印数组{0,5,2,0,0}
arr5 := [5]*int{0:new(int),1:new(int)} //数组中的值是指针。new(int)创建一个空指针,类型是int
var arr6 [5]int
arr6 = arr2 //赋值给arr6后,会拷贝一份数组的副本,两个数组的长度,类型都是一样的。不同长度or类型不能赋值
arr7 := [2][string]{new(string),new(string)}
arr7[0] = "test"
arr7[1] = "test2"
var arr8 [2]*string
arr8 = arr7 //arr8 和arr7指向同样字符串数组。
- 数组的底层存储
举个栗子:arr2 := [5]int{1,2,3,4,5}
[0] | [1] | [2] | [3] | [4] | [5] |
---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 |
[0] | [1] | [2] | [3] | [4] | [5] |
---|---|---|---|---|---|
指向下面的test | 指针 | 指针 | 指针 | 指针 | 指针 |
Test1 | Test2 | Test3 | Test4 | Test5 | Test6 |
- 多维数组
var arr [2][4]int //声明一个长度为2的二维int数组,分别存储长度为4的一个数组。
arr1[2][4]int := { //声明初始化二维数组
{1,2,3,4},
{1,2,3,4}
}
arr1[0][0] = 2 //{{2,2,3,4},{1,2,3,4}}
数组在函数中传递默认是值传递,因此如果传递一个特别大的数组,比如100M的数组,那么在函数调用的时候会完整的拷贝数组,所以开销会很大,应该避免使用大数组进行函数之间的传递。
切片
切片是一种动态的数组,其底层和数组一样分配的是一段连续的内存空间,切片可以自动增长和缩小,切片有长度和容量,长度就是运算的个数,容量就是切片最大可一装多少个元素,在每次对切片增加元素的时候扩容,每次扩容都是一倍的增长,当容量增长大于1000的时候容量的增长因子就会设为1.25,每次增长25%。
- 切片的实现
切片是一个很小的对象,包含了3个字段的数据结构,分别是指向数组底层的指针,长度,容量。
指向数组底层的指针 | 长度3 | 容量4 | 整型的切片<br />长度:3,容量:4 |
---|---|---|---|
[0] | [1] | [2] | [3] |
10 | 15 | 20 | 0 |
- 切片的创建和初始化
var slice1 = make([]string, 3) //创建一个字符串类型的切片,长度为3,容量也为3
var slice1 = make([]string, 3, 5) //创建一个字符串类型的切片,长度为3,容量也为5
var slice1 = make([]string, 3, 2) //len larger than cap in make([]string) 不能创建长度小于容量的切片
var slice []int //声明一个切片,不初始化,那么slice的指向数组底层的指针就是一个nil。
var slice = make([]string, "")//声明一个空切片
slice := []int{} //声明并初始化一个空切片,此时数组底层并不分配空间,直到append值进去后才会分配。
slice2 := []string{"chujiu","xiaodong","wangsan"} //创建一个字符串类型切片,长度和容量分别是3
slice3 := []string{99:""} //创建一个长度和容量都是100的切片,并初始化弟100个元素为空字符
//数组的声明初实话方式,数组需要指定长度。
arr := [...]string{"chujiu","lalala"}
arr := [2]string{"chujiu","lalala"}
//切片的初始化,不需要指定长度
slice := []string{"chujiu","lalala"}
- 切片的使用
slice := []int{1,2,3,4,5}
slice[1] = 1 // 修改索引为1的值, result: [1,1,3,4,5]
/**
* 创建一个新的切片,长度为2,容量为4。
* 根据一个切片创建出来的切片第一个元素开始算到原始切片的容量就是新切片的容量。
* 切片的容量始终是从第一个元素开始算的
* 如果一个切片的容量=k,长度=l,那么slice[i:j]产生新的切片,切片容量=k-i,长度=j-i。
* 创建的切片和原切片共享同一个底层数组,两个切片的指针指向同一个底层数组。
* 如果其中一个切片的值发生了变化,那么另一个切片的值同样会发生变化。
*/
newSlice := slice[1:3]
fmt.Print(newSlice[4]) //error: index out of range [4] with length 2
newSlice := slice[1,6] //error: slice bounds out of range [:6] with capacity 5
var slice = make([]int, 2, 3) //创建一个长度为2,容量为3的切片
slice[1] = 2
newSlice := slice[0:3] // result: [0,2,0]
举个栗子:
slice := []int{1,2,3,4,5}
newSlice := slice[1:3] //newSlice = [2,3]
newSlice = append(newSlice, 6) //newSlice = [2,3,60]
底层存储:
image-20200102120819232.png但是当底层数组容量不足的时候,运行append追加元素,newSlice则会产生一个新的底层数组,数组的容量是原来的2倍,这个时候修改新切片的值不会影响原来的切片的值。append会优先使用已有的容量,如果不够了才会扩容,分配一个新的底层数组。
slice := []int{1, 2, 3, 4, 5}
newSlice := slice[1:4]
newSlice = append(newSlice, 6)
fmt.Print(newSlice, "\n", slice, "\n") // newSlice = [2,3,4,6] slice = [1,2,3,4,6]
newSlice = append(newSlice, 7)
newSlice[1] = 8 //修改newSlice索引为1的值,不会影响slice,因为此时底层已经是2个数组了。
/**
* slice = [1,2,3,4,6]
* newSlice = [2,8,4,6,7]
* len(newSlice) = 5
* cap(newSlice) = 8
*/
fmt.Print(slice , newSlice, "\n", len(newSlice), cap(newSlice), "\n")
<u>写代码的时候会很容易忘记2个切片共享一个底层数组的问题,所以我们可以利用3个索引创建新的切片,使用append函数,让切片进行一次扩容,这样新的切片和原切片就彻底分离了,再修改就不会污染原来的切片。</u>
s := []string{"chu","xiao","wang","li","zhao"}
/**
* 创建一个新切片,长度为2,容量也为2的新切片,
* [1:3:3]表示取切片中索引为1到3的元素,不算索引为3的元素到新的切片中。
* 最后一个3表示容量的限制,限制容量是2个。意思是限制新的切片到s的容量的第3个,当然不算第三个。
* [i:j:k] 表示长度为j-i,容量为k-i
*/
newS := s[1:3:3]
//append后再修改newS的值就不会影响原来的切片
newS = append(newS, "hahah") // [xiao wang hahah] 长度为3 容量为4
newS[1] = "" //[xiao hahah] 中间其实是有一个空值,只是打印出来看不见了。
迭代
/**
* k和v只在当前block中有效。
* for range迭代是一个值的拷贝,而不是引用数组元素的地址。
* 每迭代一次都是把当前数组的元素的值拷贝给value,所以value的地址始终是同一个,只是迭代的过程中值不一样。
* 如果要获取每个元素的地址就不能用&value,而是&s1[k]这样获取
*/
for k, v := range s1 {
fmt.Print(k, "--", v, "\n")
}
fmt.Print(k, v) //undefined: v
多维切片
slice := [][]int{{1,2},{1,2,3}} //创建一个多维的切片,和一纬的一样,切片的值指向另一个切片,另一个切片的指针指向底层真正的数组
/**
* 追加一个值到第一个切片中,result {{1,2,3},{1,2,3}}
* 对于多维切片的长度总是以外层的切片个数决定。slice的长度为2,容量也是2
* go中append总是先增长切片,再将新的值赋给第一个外层的切片。
*/
slice = append(slice[0], 3)
var slice1 = make([][]int, 2, 3) // [[],[]] 创建一个长度为2的多维切片,容量为3。
总结
数组在函数之间传递是拷贝传值,如果数组太大影响性能。
切片在函数之间传递只是传递的切片的3个字段结构,(指针,长度,容量),真正存储数据的是底层的数组,所以性能很高。
数组必须指定长度,而切片可以动态扩容。
切片的底层存储数据结构依赖于数组。