数组和切片
数组
var a [3]int // 定义长度为3的int型数组, 元素全部为0
var b = [...]int{1, 2, 3} // 定义长度为3的int型数组, 元素为 1, 2, 3
var c = [...]int{2: 3, 1: 2} // 定义长度为3的int型数组, 元素为 0, 2, 3
var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1, 2, 0, 0, 5, 6
- 第一种方式是定义一个数组变量的最基本的方式,数组的长度明确指定,数组中的每个元素都以零值初始化。
- 第二种方式定义数组,可以在定义的时候顺序指定全部元素的初始化值,数组的长度根据初始化元素的数目自动计算。
- 第三种方式是以索引的方式来初始化数组的元素,因此元素的初始化值出现顺序比较随意。数组的长度以出现的最大的索引为准,没有明确初始化的元素依然用0值初始化。
- 第四种方式是混合了第二种和第三种的初始化方式,前面两个元素采用顺序初始化,第三第四个元素零值初始化,第五个元素通过索引初始化,最后一个元素跟在前面的第五个元素之后采用顺序初始化。
值传递
当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。
- copy 关键字可以完成值传递,另辟一片新的内存空间给新值
引用传递
- 为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组,而是指向同一片内存区间的指针
- 切片赋值,直接用切片赋值,传递的也是指针
- 引用传递实际指向的是同一片内存空间,因此相同引用的变量之间会受到互相更新的影响
func main() {
var a = [...]int{1, 2, 3} // a 是一个数组
var b = a // b 是值传递赋值数组
var c = a[:2] // c 是引用传递的切片数组
var d = &a // d 是指向源数组的指针
fmt.Println(a, b, c, d) // 打印数组元素
//[1 2 3] [1 2 3] [1 2] &[1 2 3]
a[0] = 6
fmt.Println(a, b, c, d)
//[6 2 3] [1 2 3] [6 2] &[6 2 3]
b[0] = 7
fmt.Println(a, b, c, d)
//[6 2 3] [7 2 3] [6 2] &[6 2 3]
c[1] = 8
fmt.Println(a, b, c, d)
//[6 8 3] [7 2 3] [6 8] &[6 8 3]
d[2] = 9
fmt.Println(a, b, c, d)
//[6 8 9] [7 2 3] [6 8] &[6 8 9]
}
String
值传递
直接赋值或通过切片赋值都是值传递,都会新开辟一段内存空间给新值
s := "hello, world"
h := s[:5]
fmt.Println(s, h)
//hello, world hello
s = "ertgg"
fmt.Println(s, h)
//ertgg hello
h = "asd"
fmt.Println(s, h)
//ertgg asd
通过指针引用内存
s := "hello, world"
h := &s
fmt.Println(s, *h)
//hello, world hello
s = "ertgg"
fmt.Println(s, *h)
//ertgg ertgg
[]Rune
fmt.Printf("%#v\n", []rune("世界")) // []int32{19990, 30028}
fmt.Printf("%#v\n", string([]rune{'世', '界'})) // 世界
- 这里的
rune只是int32类型的别名,并不是重新定义的类型。 string是只读的byte数据,string字符使用utf-8编码,每个字符占位1~3 bytes- 对于英文字符,
string和rune类型没有区别 - 对于中文字符,
rune类型占位4 bytes字符,用于操作中文字符不会出现乱码
Examples:
// string & rune compare,
package main
import "fmt"
// string & rune compare,
func stringAndRuneCompare() {
// string,
s := "hello你好"
fmt.Printf("%s, type: %T, len: %d\n", s, s, len(s))
fmt.Printf("s[%d]: %v, type: %T\n", 0, s[0], s[0])
li := len(s) - 1 // last index,
fmt.Printf("s[%d]: %v, type: %T\n\n", li, s[li], s[li])
// []rune
rs := []rune(s)
fmt.Printf("%v, type: %T, len: %d\n", rs, rs, len(rs))
}
func main() {
stringAndRuneCompare()
}
OutPut:
hello你好, type: string, len: 11
s[0]: 104, type: uint8
s[10]: 189, type: uint8
[104 101 108 108 111 20320 22909], type: []int32, len: 7
Analysis:
hello你好占位11 bytes(5 * 1 + 2 * 3 = 11)string转rune时,一共 7 个 utf-8 字符,因此转换成 size = 7 的 []rune
数组切片
切片可以和 nil 进行比较,只有当切片底层数据指针为空时切片本身为 nil
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
数组切片由三部分组成:
- 内存引用地址
- 长度,返回切片中有效元素的长度
- 容量,返回切片容量大小,容量必须大于或等于切片的长度,在容量使用完后会进行扩容

定义方式
var (
a []int // nil切片, 和 nil 相等, 一般用来表示一个不存在的切片
b = []int{} // 空切片, 和 nil 不相等, 一般用来表示一个空的集合
c = []int{1, 2, 3} // 有3个元素的切片, len和cap都为3
d = c[:2] // 有2个元素的切片, len为2, cap为3
e = c[0:2:cap(c)] // 有2个元素的切片, len为2, cap为3
f = c[:0] // 有0个元素的切片, len为0, cap为3
g = make([]int, 3) // 有3个元素的切片, len和cap都为3
h = make([]int, 2, 3) // 有2个元素的切片, len为2, cap为3
i = make([]int, 0, 3) // 有0个元素的切片, len为0, cap为3
)
- 切片定义– 数组不定长
- 通过
make分配内存,返回的是引用类型本身- make是生成一个可变大小的内存块,并返回一个它的引用
- 通过
new分配内存,返回的是指向类型的指针,并且内存置为0new可以申请任何类型变量内存块并返回一个指针指向它
- 通过
值传递
copy 复制切片数组
copy值传递,实现单独的内存分配,复制出的切片和原切片处于独立的内存区,两部分数据互不影响copy(dst, src)从源数据拷贝min(len(dst), len(src))个元素- 因此,拷贝的
dst数组需要初始化长度
a := make([]int, 0, 10)
a = append([]int{1, 2}, a...) //在开头添加1个切片
a = append(a, []int{3, 4, 5}...) // 在最后插入切片
var b = make([]int, len(a)-1) //
copy(b[:], a[:])
fmt.Println(a, b)
//[1 2 3 4 5] [1 2 3 4]
a[0] = 9
b[1] = 10
fmt.Println(a, b)
//[9 2 3 4 5] [1 10 3 4]
切片通过 append 在末尾增加数据
- 切片之间通过
append值传递,两部分数据互不影响 append时没必要初始化新切片的len,append时会自动将数据依次加到新切片末尾
a := make([]int, 0, 6) //默认0值为空 容量3
a = append(append(append(a, 1), 2), 3)
fmt.Printf("alen=%d acap=%d aslice=%v \n", len(a), cap(a), a)
// alen=3 acap=6 aslice=[1 2 3]
b := make([]int, 0) //声明一个长度为a 切片指针变量b
b = append(b, a...)
fmt.Printf("alen=%d acap=%d aslice=%v blen=%d bcap=%d bslice=%v \n", len(a), cap(a), a, len(b), cap(b), b)
//alen=3 acap=6 aslice=[1 2 3] blen=3 bcap=3 bslice=[1 2 3]
// append赋值值传递,两部分数据互不影响
a[0] = 99
fmt.Printf("alen=%d acap=%d aslice=%v blen=%d bcap=%d bslice=%v \n", len(a), cap(a), a, len(b), cap(b), b)
//alen=3 acap=6 aslice=[99 2 3] blen=3 bcap=3 bslice=[1 2 3]
b[1] = 100
fmt.Printf("alen=%d acap=%d aslice=%v blen=%d bcap=%d bslice=%v \n", len(a), cap(a), a, len(b), cap(b), b)
//alen=3 acap=6 aslice=[99 2 3] blen=3 bcap=3 bslice=[1 100 3]
赋值/切片 引用传递
容量
定义新切片时,默认采用旧切片的容量,因此已经提前分配好一片内存空间
在未超过原始内存的容量区间内,两个切片是引用传递
// -------------------- 切片 直接引用 复制 -----------------
a := make([]int, 0, 6) //默认0值为空 容量3
a = append(append(append(a, 1), 2), 3)
fmt.Printf("alen=%d acap=%d aslice=%v \n", len(a), cap(a), a)
// alen=3 acap=6 aslice=[1 2 3]
// 引用复制,在未超过旧切片的 cap 时,两部分指向相同的地址
b := a
fmt.Printf("alen=%d acap=%d aslice=%v blen=%d bcap=%d bslice=%v \n", len(a), cap(a), a, len(b), cap(b), b)
//alen=3 acap=6 aslice=[1 2 3] blen=3 bcap=3 bslice=[1 2 3]
a[0] = 99
fmt.Printf("alen=%d acap=%d aslice=%v blen=%d bcap=%d bslice=%v \n", len(a), cap(a), a, len(b), cap(b), b)
//alen=3 acap=6 aslice=[99 2 3] blen=3 bcap=6 bslice=[99 2 3]
b[1] = 100
fmt.Printf("alen=%d acap=%d aslice=%v blen=%d bcap=%d bslice=%v \n", len(a), cap(a), a, len(b), cap(b), b)
//alen=3 acap=6 aslice=[99 100 3] blen=3 bcap=6 bslice=[99 100 3]
// 再次新增数据,超出旧切片的容量
b = append(append(append(b, 4), 5), 6)
fmt.Printf("alen=%d acap=%d aslice=%v blen=%d bcap=%d bslice=%v \n", len(a), cap(a), a, len(b), cap(b), b)
//alen=3 acap=6 aslice=[99 100 3] blen=6 bcap=6 bslice=[99 100 3 4 5 6]
a[2] = 101
b[1] = 98
fmt.Printf("alen=%d acap=%d aslice=%v blen=%d bcap=%d bslice=%v \n", len(a), cap(a), a, len(b), cap(b), b)
//alen=3 acap=6 aslice=[99 98 101] blen=6 bcap=6 bslice=[99 98 101 4 5 6]
// a 也扩容到相同的内存空间
a = append(a, 7)
fmt.Printf("alen=%d acap=%d aslice=%v blen=%d bcap=%d bslice=%v \n", len(a), cap(a), a, len(b), cap(b), b)
//alen=4 acap=6 aslice=[99 98 101 7] blen=6 bcap=6 bslice=[99 98 101 7 5 6]
b = append(append(b, 7), 8)
a[0] = 555
b[1] = 666
fmt.Printf("alen=%d acap=%d aslice=%v blen=%d bcap=%d bslice=%v \n", len(a), cap(a), a, len(b), cap(b), b)
//alen=4 acap=6 aslice=[555 98 101 7] blen=7 bcap=12 bslice=[99 666 101 7 6 7 8]
切片扩容
当切片超出 cap 容量的时候,就会重新分配内存扩容,此时,两部分切片将不在引用同一个内存区间
array := [6]int{0, 1, 2, 3, 4, 5}
slice := array[1:3:4]
fmt.Println("slice:", slice)
//slice: [1 2]
slice = append(slice, 6)
fmt.Println("slice after appending 6:", slice, unsafe.SliceData(slice))
fmt.Println("array now:", array)
//slice after appending 6: [1 2 6] 0xc0003172c8
//array now: [0 1 2 6 4 5]
slice = append(slice, 7)
fmt.Println("slice after appending 7:", slice, unsafe.SliceData(slice))
fmt.Println("array now:", array)
//slice after appending 7: [1 2 6 7] 0xc000317320
//array now: [0 1 2 6 4 5]
扩容
容量增长表
Cap []int8 []int32 []int64
0 0 0 0
1 1
2 2 2
4 4 4
8 8 8 8
16 16 16 16
32 32 32 32
64 64 64 64
128 128 128 128
256 256 256 256
512 512 512 512
848 848
864 864
896 896
1280 1280
1344 1344
1408 1408
1792 1792
2048 2048 2048
2560 2560
3072 3072 3072 3072
3408 3408
4096 4096 4096 4096
5120 5120
5376 5376
5440 5440
6912 6912
7168 7168 7168
9216 9216
9472 9472
10240 10240 10240
12288 12288 12288
当切片容量达到 512 时,扩容速度变缓 oldCap + (oldCap + 3*256) / 4
Preference
https://victoriametrics.com/blog/go-slice/ https://go.dev/src/runtime/slice.go https://cloud.tencent.com/edu/learning/course-2412-38275