golang学习笔记17-切片
注:本人已有C,C++,Python基础,只写本人认为的重点。
一、切片的定义,与数组的区别
切片(slice)是go中非常重要的数据结构,它与数组(array)有一些关键的区别:
1.数组的大小是固定的,在定义时就必须指定,不能动态改变。而切片是动态的,可以根据需要扩展。
2.数组是值类型,传递数组时会复制整个数组。切片是引用类型,传递的是指向底层数组的指针,因此切片的操作会影响原始数据。
3.数组在声明时分配内存,其大小固定。切片在运行时可以根据需要动态分配内存,并且可以通过append函数增加元素,go会自动处理内存分配。
切片的本质是对数组一个连续片段的引用,这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。切片的语法:var 切片名 []类型 = 数组的一个片段引用。示例如下:
package main
import "fmt"
func main() {
// 定义一个数组
arr := [5]int{10, 20, 30, 40, 50}
// 创建切片,引用数组的一个片段,注意区间是左闭右开
var slice []int = arr[1:4] // 包含索引 1 到 3 的元素,结果是 [20, 30, 40]
// 输出数组和切片
fmt.Println("数组:", arr) // 输出: 数组: [10 20 30 40 50]
fmt.Println("切片:", slice) // 输出: 切片: [20 30 40]
// 修改切片中的元素
slice[0] = 99
fmt.Println("修改后的数组:", arr) // 输出: 修改后的数组: [10 99 30 40 50]
fmt.Println("修改后的切片:", slice) // 输出: 修改后的切片: [99 30 40]
}
上述示例说明访问slice下标即访问数组元素,用指针的概念解释就是:slice是数组的头指针,用下标访问slice等效于取引用,即slice[i]=(*slice)[i]。所以slice可通过下标修改原数组,这就是为什么最后arr和slice都改变的原因。
二、容量与长度
切片有3个字段的数据结构:一个是指向底层数组的指针,一个是切片的长度,一个是切片的容量。长度是切片中实际存储的元素数,容量是切片能够存储的最大元素数,即从切片的起始位置到底层数组的末尾的元素数,它表示在不重新分配内存的情况下,切片可以扩展到的最大长度。切片slice的长度可通过len(slice) 获取,容量可通过cap(slice)获取。当用append函数向切片添加元素时,如果长度超过容量,go会自动分配新的底层数组,复制原有元素,容量也会相应增加。示例如下:
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
fmt.Println("长度:", len(slice)) // 输出: 长度: 3
fmt.Println("容量:", cap(slice)) // 输出: 容量: 3
slice = append(slice, 4, 5) // 添加元素
fmt.Println("新长度:", len(slice)) // 输出: 新长度: 5
fmt.Println("新容量:", cap(slice)) // 输出: 新容量: 6(可能会根据实现变化)
}
三、创建方式
package main
import "fmt"
func main() {
// 1. 定义一个切片,让它引用一个已创建的数组
arr := [5]int{10, 20, 30, 40, 50} // 创建一个数组
slice1 := arr[1:4] // 切片引用数组的一部分,包含索引 1 到 3 的元素
fmt.Println("切片1:", slice1) // 输出: 切片1: [20 30 40]
// 2. 使用 make 内置函数创建切片
slice2 := make([]int, 3) // 创建一个长度为 3 的切片,初始元素为 0
fmt.Println("切片2:", slice2) // 输出: 切片2: [0 0 0]
fmt.Println("切片2的容量:", cap(slice2)) // 输出: 切片2的容量: 3
// 可以指定容量
slice3 := make([]int, 2, 5) // 创建一个长度为 2,容量为 5 的切片
fmt.Println("切片3:", slice3) // 输出: 切片3: [0 0]
fmt.Println("切片3的容量:", cap(slice3)) // 输出: 切片3的容量: 5
// 3. 直接指定具体数组初始化切片
slice4 := []int{1, 2, 3, 4, 5} // 直接定义并初始化切片
fmt.Println("切片4:", slice4) // 输出: 切片4: [1 2 3 4 5]
}
四、遍历
package main
import "fmt"
func main() {
// 创建一个切片
slice := []int{10, 20, 30, 40, 50}
fmt.Println("常规 for 循环遍历:")
for i := 0; i < len(slice); i++ { // 使用索引遍历切片
fmt.Printf("索引 %d 的元素: %d\n", i, slice[i]) // 通过索引访问元素
}
fmt.Println("\nfor-range 遍历:")
for index, value := range slice { // range 返回索引和元素值
fmt.Printf("索引 %d 的元素: %d\n", index, value) // 直接使用元素值
}
}
五、一些细节
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
slice1 := arr[1:4]
// 【1】切片使用不能越界
fmt.Println("访问slice1的第一个元素:", slice1[0]) // 输出: 2
// fmt.Println(slice1[5])// 取消注释将导致运行时错误:索引越界(index out of range)
// 【2】切片引用数组的三种简写方式
// 1. 从头到某个索引
slice2 := arr[0:3] // 包含元素 1, 2, 3
fmt.Println("引用arr[0:3]:", slice2) // 输出: [1 2 3]
// 2. 从某个索引到末尾
slice3 := arr[1:] // 从索引 1 到末尾(包含元素 2, 3, 4, 5)
fmt.Println("引用arr[1:]:", slice3) // 输出: [2 3 4 5]
// 3. 从开始到末尾
slice4 := arr[:] // 包含所有元素
fmt.Println("引用arr[:]:", slice4) // 输出: [1 2 3 4 5]
// 【4】切片可以继续切片
slice5 := slice4[1:3] // 从 slice4 中切出元素 2 和 3
fmt.Println("从slice4切出的切片:", slice5) // 输出: [2 3]
// 【5】切片的拷贝
// 使用 copy 函数拷贝切片
slice6 := make([]int, len(slice4)) // 创建一个新的切片以存放拷贝
copy(slice6, slice4) // 拷贝 slice4 的内容到 slice6
fmt.Println("slice4的拷贝slice6:", slice6) // 输出: [1 2 3 4 5]
// 修改原切片,观察拷贝是否影响
slice4[0] = 100
fmt.Println("修改slice4后的slice6:", slice6) // 输出: [1 2 3 4 5],拷贝不受影响
}