Contents
2.2. 切片¶
Go语言切片的内部结构包含地址、大小和容量。切片一般用于快速地操作一块数据集合。 如果将数据集合比作切糕的话,切片就是你要的“那一块”。切的过程包含从哪里开始(这个就是切片的地址)及切多大(这个就是切片的大小)。 容量可以理解为装切片的口袋大小
2.2.1. 1.从数组切片生成新的切片¶
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。
slice [开始位置:结束位置]
slice表示目标切片对象 - 开始位置对应目标切片对象的索引 - 结束位置对于目标切片的结束索引
从数组生成切片,代码如下:
fmt.Println(array_list,array_list[1:2]) // [hujianli1 hujianli2 hujianli3] [hujianli2]
1.1 从指定范围中生成切片¶
package main
import "fmt"
func main() {
var number2 [30]int
// 循环出1~30
for i := 0; i < 30; i++ {
number2[i] = i + 1
}
// 区间
fmt.Println(number2[10:15]) //[11 12 13 14 15]
// 中间到尾部的所有元素
fmt.Println(number2[20:]) //[21 22 23 24 25 26 27 28 29 30]
}
1.2 表示原有切片¶
复制一份切片 生成切片的格式中,当开始和结束都范围都被忽略,则生成的切片将表示和原切片一致的切片,并且生成的切片与原切片在数据内容上是一致的,代码如下:
fmt.Println(number2[:]) //[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30]
1.3 重置切片,清空所有元素¶
把切片的开始和结束位置都设为0时,生成的切片将变空,代码如下:
fmt.Println(number2[0:0]) //[]
2.2.2. 2.声明切片¶
每一种类型都可以拥有其切片类型,表示多个类型元素的连续集合,因此切片类型也可以被声明。
切片类型声明格式如下:
var name []T
name表示切片类型的变量名。
T表示切片类型对应的元素类型。
package main
import "fmt"
func main() {
var strList []string // 声明字符串切片
var numList []int // 声明整型切片
var numListEmpty = []int{} // 声明一个空切片
fmt.Println(strList,numList,numListEmpty) // 输出3个切片 [] [] []
fmt.Println(len(strList),len(numList),len(numListEmpty)) //输出3个切片大小 0 0 0
// 切片判定空的结果
fmt.Println(strList == nil) // true
fmt.Println(numList == nil) // true
fmt.Println(numListEmpty == nil) // false
}
2.2.3. 3.使用make()函数构造切片¶
如果需要动态地创建一个切片,可以使用make()内建函数,格式如下:
make([]T, size, cap)
· T:切片的元素类型。
· size:就是为这个类型分配多少个元素
· cap: 预分配的元素数量,降低多次分配空间造成的性能问题
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a,b) //[0 0] [0 0]
fmt.Println(len(a),len(b)) //2 2
a和b均是预分配2个元素的切片,只是b的内部存储空间已经分配了10个,但实际使用了2个元素。
容量不会影响当前的元素个数,因此a和b取len都是2。
记住,如果在[]运算符里指定了一个值,那么创建的就是数组而不是切片。只有不指定值的时候,才会创建切片
package main
import (
"fmt"
"reflect"
)
func main() {
// // 创建有5个元素的字符串数组
array1 := [5]string{"hu1","hu2","hu3","hu4","hu5"}
fmt.Printf("array1的值是 %s,数据类型是%T\n",array1,array1)
fmt.Println(reflect.TypeOf(array1).String())
// 创建长度和容量都是3的字符串切片
string1 := []string{"s1","s2","s3"}
fmt.Printf("string1的值是 %s,数据类型是%T\n",string1,string1)
fmt.Println(reflect.TypeOf(string1).String())
string1 = append(string1, "s4","s5")
fmt.Printf("string1的值是 %s,数据类型是%T\n",string1,string1)
fmt.Println(string1[1:3])
}
2.2.4. 4.使用append()函数为切片添加元素¶
Go语言的内建函数append()可以为切片动态添加元素。 每个切片会指向一片内存空间,这片空间能容纳一定数量的元素。 当空间不能容纳足够多的元素时,切片就会进行“扩容”。
“扩容”操作往往发生在append()函数调用时。
package main
import "fmt"
func main() {
var numbers []int // 声明一个整型切片
for i := 0; i < 10; i++ {
numbers = append(numbers, i) // 循环向切片添加10个数
// 查看切片的长度、容量、指针变化
fmt.Printf("len: %d cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers)
}
}
append()一次添加一个元素和一次性添加很多元素
// 定义3个切片
var (
car []string
car2 []string
car3 []string
)
//添加1个元素
car = append(car, "hujianli1") //car2 []string
// 添加多个元素
car2 = append(car, "hujianli2", "hujianli3") //[hujianli1 hujianli2 hujianli3]
// 添加一个切片
team := []string{"var1", "var2", "var3"} //[hujianli1 var1 var2 var3]
car3 = append(car, team...)
fmt.Println(car)
fmt.Println(car2)
fmt.Println(car3)
2.2.5. 5.切片与数组的关系¶
5.1切片修改底层数组¶
package main
import "fmt"
func main() {
slace := []int{1, 2, 3, 4, 5}
fmt.Println(slace)
fmt.Println("===========================")
// 在数组上进行切片
new_slace := slace[1:3]
fmt.Println(new_slace)
// 在切片后新增数据
new_slace = append(new_slace, 60)
fmt.Println(new_slace)
fmt.Println(slace)
new_slace[2] = 50
fmt.Println(new_slace)
fmt.Println(slace)
}
5.2切片不修改底层数组¶
切片时强制生成底层新的数组,保证修改数据不影响原始数据
package main
import "fmt"
func main() {
// 创建字符串切片
// 其长度和容量都是5个元素
source := []string{"Apple", "Orange", "Plum", "Banana", "Grape"}
// 对第三个元素做切片,并限制容量
// 其长度和容量都是1个元素
slice := source[2:3:3]
fmt.Println(slice)
fmt.Println(source)
/**
如果在创建切片时设置切片的容量和长度一样,就可以强制让新切片的第一个append 操作创建新的底层数组,与原有的底层数组分离。
*/
// 向slice追加新字符串
// 注意此时新创建一个数组,此数组不会改变原始数组
slice = append(slice, "Kiwi")
fmt.Println(slice)
// 之前数组数据没有变化
fmt.Println(source)
}
5.3组合两个切片到一个切片中¶
package main
import "fmt"
func main() {
name1 := []int{1, 2, 3, 4} //切片1
name2 := []int{1, 2, 3, 4} //切片2
name3 := append(name1, name2...) // 组合两个切片
fmt.Println(name3)
}
5.4复制切片元素到另一个切片¶
使用Go语言内建的copy()函数,可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:
copy(destSlice, srcSlice []T) int
· srcSlice 数据来源切片
· destSlice 为复制的目标
package main
import "fmt"
func main() {
// 设置元素数量为1000
const elementCount = 1000
// 预分配足够多的元素切片
srcData := make([]int, elementCount)
// 切片赋值
for i := 0; i < elementCount; i++ {
srcData[i] = i
}
// 引用切片数据
refData := srcData
copyData := make([]int, elementCount)
// 将数据复制到新的切片空间中
copy(copyData, srcData)
srcData[0] = 999 // 修改原始数据的第一个元素
// 打印引用切片的第一个元素
fmt.Println(refData[0]) // 999
// 打印复制切片的第一个和最后一个元素
fmt.Println(copyData[0], copyData[elementCount-1]) // 0 999
// 复制原始数据4~6.不包括6
copy(copyData, srcData[4:6])
for i := 0; i < 5; i++ {
fmt.Printf("%d ", copyData[i]) //4 5 2 3 4
}
}
5.5迭代切片¶
package main
import "fmt"
func main() {
list := []int{1, 2, 3, 4, 5, 6}
for i, i2 := range list {
fmt.Printf("index %d is %d\n", i, i2)
}
fmt.Println("-----------------------------------------------")
for index := 0; index < len(list); index++ {
fmt.Printf("index %d is %d\n", index, list[index])
}
fmt.Println("-------------只获取值--------------------")
for _, value := range list {
fmt.Printf("value is %d\n",value)
}
}
5.6从切片中删除元素¶
Go语言中切片删除元素的本质是:
以被删除元素为分界点,将前后两个部分的内存重新连接起来。
package main
import "fmt"
func main() {
seq := []string{"a", "b", "c", "d", "e"}
// 指定删除位置
index := 2
// 查看删除位置之前和之后的元素
fmt.Println(seq[:index], seq[index+1:]) //[a b] [d e]
// 将删除之前和之后的元素连接起来
new_seq := append(seq[:index], seq[index+1:]...)
fmt.Println(new_seq) //[a b d e]
}
5.7在函数间传递切片¶
package main
import "fmt"
func main() {
// 分配包含100万个整型值的切片
slice := make([]int, 1e6)
/**
由于与切片关联的数据包含在底层数组里,不属于切片本身,
所以将切片复制到任意函数的时候,对底层数组大小都不会有影响。
复制时只会复制切片本身,不会涉及底层数组
*/
/**
在函数间传递24字节的数据会非常快速、简单。这也是切片效率高的地方。
不需要传递指针和处理复杂的语法,只需要复制切片,按想要的方式修改数据,然后传递回一份新的切片副本。
*/
// 将slice传递到函数foo
slice = foo(slice)
fmt.Println(slice)
}
// 定义一个接收切片的函数,传入值slice
func foo(slice []int) []int {
return slice
}
2.2.6. 6. slice就地修改¶
我们多看一些就地使用slice的例子,比如rotate和reveser这种可以就地修改slice的元素。
下面的例子,nonempty函数从字符串列表中去除空字符串并返回一个新的slice
package main
import "fmt"
func nonempty(strings []string) []string {
// 函数调用过程中底层数组发生了变化
i := 0
for _, s := range strings {
if s != "" {
strings[i] = s
i++
}
}
return strings[:i]
}
func nonempty2(strings []string) []string {
out := strings[:0]
for _, s := range strings {
if s != "" {
out = append(out, s)
}
}
return out
}
func main() {
data := []string{"one", "", "three"}
fmt.Printf("%q\n", nonempty(data)) //["one" "three"]
fmt.Printf("%q\n", data) // ["one" "three" "three"]
data2 := []string{"one", "", "three"}
data2_nonepty := nonempty2(data2)
fmt.Printf("%q\n", data2_nonepty) // ["one" "three"]
fmt.Printf("%q\n", data2) // ["one" "three" "three"]
}
一个slice可以用来模拟一个stack。最初给定的空slice对应一个空的stack,然后可以使用append函数将新的值压入stack:
stack = append(stack, v) // push v
stack的顶部位置对应slice的最后一个元素:
top := stack[len(stack)-1] // top of stack
通过收缩stack可以弹出栈顶的元素
stack = stack[:len(stack)-1] // pop
要删除slice中间的某个元素并保存原有的元素顺序,可以通过内置的copy函数将后面的子slice向前依次移动一位完成:
package main
import "fmt"
// 从slice中移除一个元素,并保留原slice的顺序
func remove(slice []int, i int) []int {
copy(slice[i:], slice[i+1:])
return slice[:len(slice)-1]
}
func main() {
var strList []string
// 压栈
strList = append(strList, "hu1")
strList = append(strList, "hu2")
strList = append(strList, "hu3")
fmt.Println(strList)
// 栈顶
fmt.Println(strList[len(strList)-1])
// 弹出最后一个元素
stack := strList[:len(strList)-1] // pop
fmt.Println(stack)
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(remove(s, 2))
}
如果删除元素后不用保持原来顺序的话,我们可以简单的用最后一个元素覆盖被删除的元素:
func remove(slice []int, i int) []int {
slice[i] = slice[len(slice)-1]
return slice[:len(slice)-1]
}
func main() {
s := []int{5, 6, 7, 8, 9}
fmt.Println(remove(s, 2)) // "[5 6 9 8]
}