转载地址:https://studygolang.com/articles/31219,https://www.cnblogs.com/sparkdev/p/10704614.html
1 切片的创建和初始化
在 Golang 中可以通过多种方式创建和初始化切片。是否提前知道切片所需的容量通常会决定如何创建切片。
1.1 通过 make() 函数创建切片
使用 Golang 内置的 make() 函数创建切片,此时需要传入一个参数来指定切片的长度:
// 创建一个整型切片 // 其长度和容量都是 5 个元素 slice := make([]int, 5)
分别指定长度和容量时,创建的切片,底层数组的长度是指定的容量,但是初始化后并不能
访问所有的数组元素。
注意,Golang 不允许创建容量小于长度的切片,当创建的切片容量小于长度时会在编译时刻报错:
// 创建一个整型切片 // 使其长度大于容量 myNum := make([]int, 5, 3)
1.2 通过字面量创建切片
另一种常用的创建切片的方法是使用切片字面量,这种方法和创建数组类似,只是不需要指定[ ]运算符里的值。初始的长度和容量会基于初始化时提供的元素的个数确定:
// 创建字符串切片 // 其长度和容量都是 3 个元素 myStr := []string{"Jack", "Mark", "Nick"} // 创建一个整型切片 // 其长度和容量都是 4 个元素 myNum := []int{10, 20, 30, 40}
当使用切片字面量创建切片时,还可以设置初始长度和容量。要做的就是在初始化时给出所需的长度和容量作为索引。下面的语法展示了如何使用索引方式创建长度和容量都是100个元素的切片:
// 创建字符串切片 // 使用空字符串初始化第 100 个元素 myStr := []string{99: ""}
1.3 区分数组的声明和切片的声明方式
当使用字面量来声明切片时,其语法与使用字面量声明数组非常相似。二者的区别是:如果在 [] 运算符里指定了一个值,那么创建的就是数组而不是切片。只有在 [] 中不指定值的时候,创建的才是切片。看下面的例子:
// 创建有 3 个元素的整型数组 myArray := [3]int{10, 20, 30} // 创建长度和容量都是 3 的整型切片 mySlice := []int{10, 20, 30}
2 nil 和空切片
有时,程序可能需要声明一个值为 nil 的切片(也称nil切片)。只要在声明时不做任何初始化,就会创建一个 nil 切片
// 创建 nil 整型切片 var myNum []int
在 Golang 中,nil 切片是很常见的创建切片的方法。nil 切片可以用于很多标准库和内置函数。在需要描述一个不存在的切片时,nil 切片会很好用。比如,函数要求返回一个切片但是发生异常的时候。下图描述了 nil 切片的状态:
空切片和 nil 切片稍有不同,下面的代码分别通过 make() 函数和字面量的方式创建空切片:
// 使用 make 创建空的整型切片 myNum := make([]int, 0) // 使用切片字面量创建空的整型切片 myNum := []int{}
3 为切片中的元素赋值
对切片里某个索引指向的元素赋值和对数组里某个索引指向的元素赋值的方法完全一样。使
用 [] 操作符就可以改变某个元素的值,下面是使用切片字面量来声明切片:
// 创建一个整型切片 // 其容量和长度都是 5 个元素 myNum := []int{10, 20, 30, 40, 50} // 改变索引为 1 的元素的值 myNum [1] = 25
4 通过切片创建新的切片
切片之所以被称为切片,是因为创建一个新的切片,也就是把底层数组切出一部分。通过切片创建新切片的语法如下:
slice[i:j] slice[i:j:k]
其中 i 表示从 slice 的第几个元素开始切,j 控制切片的长度(j-i),k 控制切片的容量(k-i),如果没有给定 k,则表示切到底层数组的最尾部。下面是几种常见的简写形式:
slice[i:] // 从 i 切到最尾部 slice[:j] // 从最开头切到 j(不包含 j) slice[:] // 从头切到尾,等价于复制整个 slice
让我们通过下面的例子来理解通过切片创建新的切片的本质:
// 创建一个整型切片 // 其长度和容量都是 5 个元素 myNum := []int{10, 20, 30, 40, 50} // 创建一个新切片 // 其长度为 2 个元素,容量为 4 个元素 newNum := slice[1:3]
执行上面的代码后,我们有了两个切片,它们共享同一段底层数组,但通过不同的切片会看到底层数组的不同部分:
注意,截取新切片时的原则是 "左含右不含"。所以 newNum 是从 myNum 的 index=1 处开始截取,截取到 index=3 的前一个元素,也就是不包含 index=3 这个元素。所以,新的 newNum 是由 myNum 中的第2个元素、第3个元素组成的新的切片构,长度为 2,容量为 4。切片 myNum 能够看到底层数组全部 5 个元素的容量,而 newNum 能看到的底层数组的容量只有 4 个元素。newNum 无法访问到底层数组的第一个元素。所以,对 newNum 来说,那个元素就是不存在的。
共享底层数组的切片
需要注意的是:现在两个切片 myNum 和 newNum 共享同一个底层数组。如果一个切片修改了该底层数组的共享
部分,另一个切片也能感知到(请参考前图):
// 修改 newNum 索引为 1 的元素 // 同时也修改了原切片 myNum 的索引为 2 的元素 newNum[1] = 35
把 35 赋值给 newNum 索引为 1 的元素的同时也是在修改 myNum 索引为 2 的元素:
切片只能访问到其长度内的元素
切片只能访问到其长度内的元素,试图访问超出其长度的元素将会导致语言运行时异常。在使用这部分元素前,必须将其合并到切片的长度里。
5 切片扩容
相对于数组而言,使用切片的一个好处是:可以按需增加切片的容量。Golang 内置的 append() 函数会处理增加长度时的所有操作细节。要使用 append() 函数,需要一个被操作的切片和一个要追加的值,当 append() 函数返回时,会返回一个包含修改结果的新切片。函数 append() 总是会增加新切片的长度,而容量有可能会改变,也可能不会改变,这取决于被操作的切片的可用容量。
myNum := []int{10, 20, 30, 40, 50} // 创建新的切片,其长度为 2 个元素,容量为 4 个元素 newNum := myNum[1:3] // 使用原有的容量来分配一个新元素 // 将新元素赋值为 60 newNum = append(newNum, 60)
执行上面的代码后的底层数据结构如下图所示:
此时因为 newNum 在底层数组里还有额外的容量可用,append() 函数将可用的元素合并入切片的长度,并对其进行赋值。由于和原始的切片共享同一个底层数组,myNum 中索引为 3 的元素的值也被改动了。
如果切片的底层数组没有足够的可用容量,append() 函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值,此时 append 操作同时增加切片的长度和容量:
// 创建一个长度和容量都是 4 的整型切片 myNum := []int{10, 20, 30, 40} // 向切片追加一个新元素 // 将新元素赋值为 50 newNum := append(myNum, 50)
当这个 append 操作完成后,newSlice 拥有一个全新的底层数组,这个数组的容量是原来的两倍(图中容量有误):
函数 append() 会智能地处理底层数组的容量增长。在切片的容量小于 1000 个元素时,总是会成倍地增加容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是会每次增加 25%的容量(随着语言的演化,这种增长算法可能会有所改变)。
扩容代码示例:
myNum := []int{10, 20, 30, 40, 50}
slice :=myNum[1:3]
fmt.Println(slice)
fmt.Println(myNum)
slice =append(slice,777)
fmt.Println(slice)
fmt.Println(myNum)
slice =append(slice,888)
fmt.Println(slice)
fmt.Println(myNum)
slice =append(slice,999) //超出切片数组容量了,不在原数组上继续操作了,这步开始已经是两个数组了
fmt.Println(slice)
fmt.Println(myNum)
myNum[1]=50
fmt.Println("new",slice)
fmt.Println(myNum)
输出结果(手动加回车隔开了结果):
[20 30]
[10 20 30 40 50]
[20 30 777]
[10 20 30 777 50]
[20 30 777 888]
[10 20 30 777 888]
[20 30 777 888 999]
[10 20 30 777 888]
new [20 30 777 888 999]
[10 50 30 777 888]
如果切片的底层数组没有足够的可用容量,append() 函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值,此时 append 操作同时增加切片的长度和容量:
// 创建一个长度和容量都是 4 的整型切片 myNum := []int{10, 20, 30, 40} // 向切片追加一个新元素 // 将新元素赋值为 50 newNum := append(myNum, 50)
当这个 append 操作完成后,newSlice 拥有一个全新的底层数组,这个数组的容量是原来的两倍:
函数 append() 会智能地处理底层数组的容量增长。在切片的容量小于 1000 个元素时,总是会成倍地增加容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是会每次增加 25%的容量(随着语言的演化,这种增长算法可能会有所改变)。
6 切片间的拷贝操作
Golang 内置的 copy() 函数可以将一个切片中的元素拷贝到另一个切片中,其函数声明为:
func copy(dst, src []Type) int
它表示把切片 src 中的元素拷贝到切片 dst 中,返回值为拷贝成功的元素个数。如果 src 比 dst 长,就截断;如果 src 比 dst 短,则只拷贝 src 那部分:
num1 := []int{10, 20, 30} num2 := make([]int, 5) count := copy(num2, num1) fmt.Println(count) fmt.Println(num2)
运行这段单面,输出的结果为:
3 [10 20 30 0 0]
3 表示拷贝成功的元素个数。