无论是C语言中的数组还是Go语言中的数组,数组的长度一旦确定就不能改变,但在实际开发中我们可能事先不能确定数组的长度,为了解决这类问题 Go 语言中推出了一种新的数据类型切片
切片可以简单的理解为长度可以变化的数组,但是 Go 语言中的切片本质上是一个结构体
切片源码
- type slice struct{
- array unsafe.Pointer // 指向底层数组指针
- len int // 切片长度(保存了多少个元素)
- cap int // 切片容量(可以保存多少个元素)
- }
方式一:通过数组创建切片 array[startIndex:endIndex]
- package main
- import "fmt"
- func main() {
- var arr = [5]int{1, 3, 5, 7, 9}
- // 从数组0下标开始取,一直取到2下标前面一个索引
- var sce = arr[0:2]
- fmt.Println(sce) // [1 3]
- // 切片len = 结束位置 - 开始位置
- fmt.Println(len(sce)) // 2 - 0 = 2
- fmt.Println(cap(sce)) // 5 - 0 = 5
- // 数组地址就是数组首元素的地址
- fmt.Printf("%p\n", &arr) // 0xc04200a330
- fmt.Printf("%p\n", &arr[0]) // 0xc04200a330
- // 切片地址就是数组中指定的开始元素的地址
- // arr[0:2]开始地址为0, 所以就是arr[0]的地址
- fmt.Printf("%p\n", sce) // 0xc04200a330
- }
- package main
- import "fmt"
- func main() {
- var arr = [5]int{1, 3, 5, 7, 9}
- // 根据数组的索引片段创建切片
- var sce = arr[2:4]
- fmt.Println(sce) // [5 7]
- fmt.Println(len(sce)) // 4 - 2 = 2
- fmt.Println(cap(sce)) // 5 - 2 = 3
- fmt.Printf("%p\n", &arr[2]) // 0xc042076070
- fmt.Printf("%p\n", sce) // 0xc042076070
- }
指定起始位置时有三种方式可以指定
- package main
- import "fmt"
- func main() {
- var arr = [5]int{1, 3, 5, 7, 9}
- // 同时指定开始位置和结束位置
- var sce1 = arr[0:2]
- fmt.Println(sce1) // [1 3]
-
- // 只指定结束位置
- var sce3 = arr[:2]
- fmt.Println(sce3) // [1 3]
-
- // 只指定开始位置
- var sce2 = arr[0:]
- fmt.Println(sce2) // [1 3 5 7 9]
-
- // 都不指定
- var sce4 = arr[:]
- fmt.Println(sce4) // [1 3 5 7 9]
- }
方式二:通过 make 函数创建 make(类型, 长度, 容量)
- package main
- import "fmt"
- func main() {
- // 第一个参数: 指定切片数据类型
- // 第二个参数: 指定切片的长度
- // 第三个参数: 指定切片的容量
- var sce = make([]int, 3, 5)
- fmt.Println(sce) // [0 0 0]
- fmt.Println(len(sce)) // 3
- fmt.Println(cap(sce)) // 5
- /*
- 内部实现原理
- var arr = [5]int{0, 0, 0}
- var sce = arr[0:3]
- */
- }
方式三:通过 Go 提供的语法糖快速创建
- package main
- import "fmt"
- func main() {
- var sce = []int{1, 3, 5}
- fmt.Println(sce) // [1 3 5]
- fmt.Println(len(sce)) // 3
- fmt.Println(cap(sce)) // 3
- }
- package main
- import "fmt"
- func main() {
- var sce = []int{1, 3, 5}
- // 使用切片, 往切片中存放数据
- sce[1] = 666
- // 访问切片, 从切片中获取数据
- fmt.Println(sce) // [1 666 5]
- }
- package main
- import "fmt"
- func main() {
- var sce = []int{1, 3, 5}
- // 编译报错, 越界
- sce[3] = 666
- }
如果希望切片自动扩容,那么添加数据时必须使用 append 方法
- package main
- import "fmt"
- func main() {
- var sce = []int{1, 3, 5}
- fmt.Println("追加数据前:", sce) // [1 3 5]
- fmt.Println("追加数据前:", len(sce)) // 3
- fmt.Println("追加数据前:", cap(sce)) // 3
- fmt.Printf("追加数据前: %p\n", sce) // 0xc0420600a0
- // 第一个参数: 需要把数据追加到哪个切片中
- // 第二个参数: 需要追加的数据, 可以是一个或多个
- sce = append(sce, 666)
- fmt.Println("追加数据后:", sce) // [1 3 5 666]
- fmt.Println("追加数据后:", len(sce)) // 4
- fmt.Println("追加数据后:", cap(sce)) // 6
- fmt.Printf("追加数据前: %p\n", sce) // 0xc042076b60
- }
除了 append 函数外,Go 语言还提供了一个copy函数,用于两个切片之间数据的快速拷贝
- package main
- import "fmt"
- func main() {
- var sce1 = []int{1, 3, 5}
- var sce2 = make([]int, 5)
- fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0
- fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060
- // 将sce2的指向修改为sce1, 此时sce1和sce2底层指向同一个数组
- sce2 = sce1
- fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0
- fmt.Printf("赋值后:%p\n", sce2) // 0xc0420600a0
- //copy(sce2, sce1)
- fmt.Println(sce1) // [1 3 5]
- fmt.Println(sce2) // [1 3 5]
- sce2[1] = 666
- fmt.Println(sce1) // [1 666 5]
- fmt.Println(sce2) // [1 666 5]
- }
- package main
- import "fmt"
- func main() {
- var sce1 = []int{1, 3, 5}
- var sce2 = make([]int, 5)
- fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0
- fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060
- // 将sce1中的数据拷贝到sce2中,, 此时sce1和sce2底层指向不同数组
- copy(sce2, sce1)
- fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0
- fmt.Printf("赋值后:%p\n", sce2) // 0xc042076060
- //copy(sce2, sce1)
- fmt.Println(sce1) // [1 3 5]
- fmt.Println(sce2) // [1 3 5 0 0]
- sce2[1] = 666
- fmt.Println(sce1) // [1 3 5]
- fmt.Println(sce2) // [1 666 5 0 0]
- }
copy 函数在拷贝数据时永远以小容量为准
- package main
- import "fmt"
- func main() {
- // 容量为3
- var sce1 = []int{1, 3, 5}
- // 容量为5
- var sce2 = make([]int, 5)
- fmt.Println("拷贝前:", sce2) // [0 0 0 0 0]
- // sce2容量足够, 会将sce1所有内容拷贝到sce2
- copy(sce2, sce1)
- fmt.Println("拷贝后:", sce2) // [1 3 5 0 0]
- }
- package main
- import "fmt"
- func main() {
- // 容量为3
- var sce1 = []int{1, 3, 5}
- // 容量为2
- var sce2 = make([]int, 2)
- fmt.Println("拷贝前:", sce2) // [0 0]
- // sce2容量不够, 会将sce1前2个元素拷贝到sce2中
- copy(sce2, sce1)
- fmt.Println("拷贝后:", sce2) // [1 3]
- }
- package main
- import "fmt"
- func main() {
- arr := [5]int{1, 3, 5, 7, 9}
- sce1 := arr[0:4]
- sce2 := sce1[0:3]
- fmt.Println(sce1) // [1 3 5 7]
- fmt.Println(sce2) // [1 3 5]
- // 由于底层指向同一数组, 所以修改sce2会影响sce1
- sce2[1] = 666
- fmt.Println(sce1) // [1 666 5 7]
- fmt.Println(sce2) // [1 666 5]
- }
- package main
- import "fmt"
- func main() {
- var arr1 [3]int = [3]int{1, 3, 5}
- var arr2 [3]int = [3]int{1, 3, 5}
- var arr3 [3]int = [3]int{2, 4, 6}
- // 首先会判断`数据类型`是否相同,如果相同会依次取出数组中`对应索引的元素`进行比较,
- // 如果所有元素都相同返回true,否则返回false
- fmt.Println(arr1 == arr2) // true
- fmt.Println(arr1 == arr3) // false
-
- sce1 := []int{1, 3, 5}
- sce2 := []int{1, 3, 5}
- //fmt.Println(sce1 == sce2) // 编译报错
- fmt.Println(sce1 != nil) // true
- fmt.Println(sce2 == nil) // false
- }
- package main
- import "fmt"
- func main() {
- // 数组声明后就可以直接使用, 声明时就会开辟存储空间
- var arr [3]int
- arr[0] = 2
- arr[1] = 4
- arr[2] = 6
- fmt.Println(arr) // [2 4 6]
-
- // 切片声明后不能直接使用, 只有通过make或语法糖创建之后才会开辟空间,才能使用
- var sce []int
- sce[0] = 2 // 编译报错
- sce[1] = 4
- sce[2] = 6
- fmt.Println(sce)
- }
- package main
- import "fmt"
- func main() {
- str := "abcdefg"
- // 通过字符串生成切片
- sce1 := str[3:]
- fmt.Println(sce1) // defg
-
- sce2 := make([]byte, 10)
- // 第二个参数只能是slice或者是数组
- // 将字符串拷贝到切片中
- copy(sce2, str)
- fmt.Println(sce2) //[97 98 99 100 101 102 103 0 0 0]
- }