引子
因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。 例如:
func arraySum(x [3]int) int{
sum := 0
for _, v := range x{
sum = sum + v
}
return sum
}
这个求和函数只能接受[3]int
类型,其他的都不支持。 再比如,
a := [3]int{1, 2, 3}
数组a中已经有三个元素了,我们不能再继续往数组a中添加新元素了。
切片
切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。
切片是一个引用类型,它的内部结构包含地址
、长度
和容量
。切片一般用于快速地操作一块数据集合。
切片是引用类型的数据,而数组是值类型的数据
切片的定义
var name []T // 切片拥有数组的所有特性
- name:表示变量名
- T:表示切片中的元素类型
切片的声明
package main
import "fmt"
func main() {
// 切片声明方式一 直接声明
var s = []int{1,2,3}
fmt.Println(s) // [1 2 3]
fmt.Printf("%T \n", s) // []int
// 类型推断
m := []int{3,4,5}
// 只声明不初始化
var n []int // 不初始化的slice等同于nil
//注意: nil slice不能直接使用,不能通过索引向nil slice中添加元素
// 切片声明方式二 从数组中得到切片
var a = [3]int{1,2,3}
c := a[0:3]
fmt.Println(c) // [1 2 3]
fmt.Printf("%T \n", c) // []int
// 切片声明方式三:make
var n = make([]int,2,5) // 注意 make 只能用于 切片 map chenl
fmt.Println(n) // [0 0]
}
切片的长度和容量
切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。
package main
import "fmt"
func main() {
var a1 = [...]string{"北京", "上海", "深圳", "成都", "广州", "青岛"}
s1 := a1[1:4]
// 切片的大小 (切片内目前元素的数量)
fmt.Println(len(s1)) // 3
// 切片的容量 (底层数组最大能放多少元素)
fmt.Println(cap(s1)) // 5
}
切片的本质
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。
举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
,切片s1 := a[:5]
,相应示意图如下。切片s2 := a[3:6]
,相应示意图如下:
- 每一个切片都引用了一个底层数组
- 切片本身不存储数据,切片存储的是底层数组的地址,是底层数组在存储数据,所以修改数组中的元素就是在修改底层数组中的元素
- 当想切片中添加元素时,若没有超过底层数组的容量则直接添加,若超过则会扩容(倍数扩容),一旦扩容则重新指向一个底层数组(地址发生改变)
- 基于同一个底层数组创建的切片,在修改切片内的元素时,有可能会影响到其他的切片
切片的扩容策略
ewcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } }
- 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
- 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
- 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int
和string
类型的处理方式就不一样
修改切片中的元素
var n = make([]int,2,5)
n[0] = 1
n[1] = 2
fmt.Println(n) // [1 2]
append()方法为切片添加元素
Go语言的内建函数append()
可以为切片动态添加元素。 每个切片会指向一个底层数组,这个数组能容纳一定数量的元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()
函数调用时。 举个例子:
func main() {
var n = make([]int,2,5)
//添加元素
n = append(n, 7,8,9)
fmt.Println(n) // [0 0 7 8 9]
//将另一个切片中的元素添加到切片中
m := []int{3,4,5}
n = append(n, m...)
//append()添加元素和切片扩容
var numSlice []int
for i := 0; i < 10; i++ {
numSlice = append(numSlice, i)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
}
}
[0] len:1 cap:1 ptr:0xc0000a8000
[0 1] len:2 cap:2 ptr:0xc0000a8040
[0 1 2] len:3 cap:4 ptr:0xc0000b2020
[0 1 2 3] len:4 cap:4 ptr:0xc0000b2020
[0 1 2 3 4] len:5 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc0000b8000
append()
函数将元素追加到切片的最后并返回该切片。
切片numSlice的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍
copy()函数复制切片
Go语言内建的copy()
函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()
函数的使用格式如下:
copy(destSlice, srcSlice []T)
- srcSlice: 数据来源切片
- destSlice: 目标切片
使用
package main
import "fmt"
func main{
test()
}
func test() {
a := []int{1,2,3}
b := a // 直接赋值
var c []int // 定义一个切片,还没有申请内存
c = make([]int, 3, 3) // 为c切片申请内存 make([]T, len, cap)
copy(c, a) // 参数2是被copy对象
b[0] = 100
fmt.Println(a) // [100 2 3]
fmt.Println(b) // [100 2 3]
fmt.Println(c) // [1 2 3]
}
var c []int // 只定义,没初始化,没有内存
var c = []int{} // 定义,初始化,有内存
从切片中删除元素
Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:
package main
import "fmt"
func main{
test1()
}
func test1() {
a := []int{1,2,3,4,5,6,7}
a = append(a[:2], a[3:]...)
fmt.Println(a) // [1 2 4 5 6 7]
}
总结一下就是:要从切片a中删除索引为index
的元素,操作方法是a = append(a[:index], a[index+1:]...)
题
package main
import "fmt"
func main{
test2()
}
func test2() {
a := [...]int{1,2,3,4,5,6,7}
b := a[:]
b[0] = 100
fmt.Println(a[0]) // 100 因为切片是引用类型
c := a[2:5]
d := c[:5]
fmt.Println(c) // [3 4 5]
fmt.Println(len(c)) // 3
fmt.Println(cap(c)) // 5
fmt.Println(d) // [3 4 5 6 7]
fmt.Println(len(d)) // 5
fmt.Println(cap(d)) // 5
}
c与d内存地址一样,因为c与d的第一个元素是同一个