Go从入门到精通——切片(slice)(动态分配大小的连续空间)
切片(slice)(动态分配大小的连续空间)
Go 语言切片的内部结构包含地址、大小和容量。切片一般用于快速地操作一块数据集合。
1.1、从数组或切片生成新的切片
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。
从连续内存区域生成切片是常见的操作。格式如下:
slice [开始位置:结束位置] //slice 表示目标切片对象。 //开始位置对应目标切片对象的索引。 //结束位置对应目标切片的结束索引。
从数组生成切片例子:
package main import ( "fmt" ) func main() { var a = [3]int{1, 2, 3} //一个变量a的数组,数组大小是3,已初始化数值1,2,3. fmt.Println(a, a[1:2]) //使用 a[1:2]生成一个新切片 }
从数组或切片生成新的切片拥有如下特性:a :=[]int[1,2,3,4,5,6,7,8,9,10]
- 取出的元素数量为:结束位置 - 开始位置
- 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取。
- 当缺省开始位置时,表示从连续区域开头到结束位置,比如 a[:10]。
- 当缺省结束位置时,表示从开始位置到整个连续区域末尾,比如 a[1:]。
- 两者时同时缺省时,与切片本身等效,比如:a[:]。
- 两者同时为 0 时,等效于空切片,一般用于切片复位,比如 a[0:0]
- 根据索引位置取切片 slice 元素值时,取值范围是(0~len(slice)-1),超界会报运行时错误。生成切片时,结束位置可以填写 len(slice) 但不会报错。
1.2、声明切片
每一种类型都可以拥有其切片类型,表示多个类型元素的连续集合。因此切片类型也可以被声明。
切片类型声明格式如下:
var name []T //name 表示切片类型的变量名 //T 表示切片类型声明的使用过程
1.3、使用 make() 函数构造切片
如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下:
make( []T, size, cap) // T: 切片的元素类型。 // size: 就是为这个类型分配多少个月元素。 // cap: 预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。
注意:
- 使用 make() 函数生成的切片一定是发生了内存分配操作。
- 给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。
- 切片不一定就必须经过 make() 函数才能使用。
- 生成的切片,声明后使用 append() 函数均可以正常使用切片。
1.4、使用 append() 函数为切片添加元素
Go 语言的内建函数 append() 可以为切片动态添加元素。每个切片会指向一个内存空间,这片空间能容纳一定数量的元素。当空间不能再容纳足够多的元素时,切片就会进行 "扩容"。"扩容" 操作往往发生在 append() 函数调用时。
切片在扩容时,容量的扩展规律按容量的 2 倍数扩充,例如 1、2、4、8、16……,代码如下:
package main import "fmt" func main() { var numbers []int for i := 0; i < 10; i++ { numbers = append(numbers, i) fmt.Printf("len: %d\t cap:%d pointer: %p\n", len(numbers), cap(numbers), numbers) } }
append() 函数除了添加一个元素外,也可以一次性添加很多元素。
package main import "fmt" func main() { var car []string // 添加 1 个元素 car = append(car, "热烈欢迎世界各地") //使用 append() 函数向切片中添加 1 个元素 fmt.Println("添加 1 个元素:", car) // 添加 2 个元素 car = append(car, "参加", "2022北京冬奥会", "的代表团!") //使用 append() 函数向切片中添加 多 个元素 fmt.Println("添加 2 个元素:", car) // 添加切片 team := []string{"Warmly welcome the delegations from all over the world to the 2022 Beijing Winter Olympic Games!"} car = append(car, team...) //在 team 后面加上了 "...",表示将 team 整个添加到 car 的后面。 fmt.Println(car) }
1.5、复制切片元素到另一个切片
使用 Go 语言内建的 copy() 函数,可以迅速地将一个切片的数据复制到另外一个切片空间中,copy() 函数的使用格式如下:
copy(destSlice, srcSlice []T) int //srcSlice 为数据来源切片; //destSlice 为复制目标(来源的切片复制给谁)。目标切片必须分配过空间且足够承载复制的元素个数。来源和目标的类型一致,copy 的返回值表示实际发生复制的元素个数。
下面的代码将演示对切片的引用和复制操作后对切片元素的影响:
package main import "fmt" func main() { //设置元素数量为 1000 const elementCount = 1000 //预分配足够多的元素切片 srcData := make([]int, elementCount) //将切片赋值 for i := 0; i < elementCount; i++ { srcData[i] = i //srcData 切片中存储 1000 个元素,元素值分别是连续的 1-1000 数字 } fmt.Printf("srData 切片的值: %d\n", srcData) //引用切片数据 //定义一个变量,并将 srcData 值传递给它。切片赋值,实际上只是复制了地址,refData 引用了 srcData的地址,因此 srcData的任何修改,都会影响 refData refData := srcData fmt.Printf("refData 的内存地址: %p\nsrcData 的内存地址: %p\n", &refData, &srcData) //预分配足够多的元素切片 copyData := make([]int, elementCount) fmt.Printf("copyData 的内存地址: %p\n", ©Data) //将数据复制到新的切片空间中 copy(copyData, srcData) fmt.Printf("复制 srcData 后的 copyData 的内存地址: %p\n", ©Data) //修改原始数据的第一个元素 srcData[0] = 999 fmt.Printf("修改 srcData 第 1 个元素后的 copyData 的内存地址: %p\n", ©Data) //打印引用切片的一个元素 fmt.Printf("打印引用切片( refData )的一个元素: %d 内存地址: %p\n", refData[0], &refData) //打印复制切片的第一个和最后一个元素 fmt.Printf("打印复制切片( copyData )的一个元素: %d 内存地址: %p\n", copyData[0], ©Data) //复制原始数据从 4 到 6(不包含) for i := 0; i < 5; i++ { fmt.Printf("%d", copyData[i]) } fmt.Println(srcData[4:6]) fmt.Println(srcData) copy(copyData, srcData[4:6]) for i := 0; i < 5; i++ { fmt.Printf("%d", copyData[i]) } }
代码输出如下:
1.6、从切片中删除元素
Go 语言中并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素。
package main import "fmt" func main() { seq := []string{"a", "b", "c", "d", "e"} //指定删除位置 index := 2 //查看删除位置之间的元素和之后的元素 fmt.Println(seq[:index], seq[index+1:]) // 将删除点前后的元素连接起来 seq = append(seq[:index], seq[index+1:]...) //后面这3个点要加上,不然会报错,用于两个切片合并的。 fmt.Println(seq) }