目录
- 1. 数组
- 2. 切片
- 2.1. slice 声明、初始化
- 2.2. slice 操作
- 2.3. append() 追加切片、扩容
- 2.4. 字符串和切片
- 3. Copy
- 4. Array、Slice 内存布局
上一篇:基本类型、常量和变量
1. 数组
数组是同一种类型固定长度的序列(有长度、类型构成)。一但定义,长度不可改变。
- 数组为值类型,赋值和传参会复制整个数组,而不是指针。
- 数组声明时,必须固定长度;长度是数组类型的一部分,不同长度的数组类型表示不同的类型(
[2]int
与[3]int
为不同的数组类型); - 数组未赋值,则初始化为类型缺省值(零值)。
- 内置函数
len
、cap
都返回数组长度。 - 越界访问产生panic。
- 指针数组:
[n]*T
,数组指针:*[n]T
。 - 值拷贝行为会造成性能问题,通常会建议使用 slice,或数组指针。
package main
import (
"fmt"
"reflect"
)
func main() {
// 初始化
var arr1 [5]int
var arr2 = [5]int{0, 1, 2, 3, 4}
var arr3 = [3]int{0, 2: 4}
var arr4 = [...]int{1, 2, 3}
fmt.Println(arr1, arr2, arr3, arr4)
// 长度是数组类型的一部分,不同长度的数组类型为不同类型
if reflect.TypeOf(arr1) == reflect.TypeOf(arr2) {
fmt.Println("arr1 和 arr2 数据类型相同!")
}
if reflect.TypeOf(arr1) != reflect.TypeOf(arr3) {
fmt.Println("arr1 和 arr3 数据类型不相同!")
}
// 数组为值类型:赋值和传参复制整个数组
arr1copy := arr1
arr1copy[0] = 10
fmt.Println(arr1, arr1copy)
// 值拷贝行为会造成性能问题,通常会建议使用 slice,或数组指针。
fn := func(arr [5]int) {
arr[1] = 20
}
fn(arr1)
fmt.Println(arr1)
// 指针数组:[n]*T
var pa = [2]*int{new(int)}
*pa[0] = 10
pa[1] = new(int) // 引用类型必须先分配内存,须使用new分配内存
*pa[1] = 20 // 若不分配内存(注释上一行代码),则产生panic: runtime error: invalid memory address or nil pointer dereference
fmt.Printf("pa:%v \n", pa)
// 数组指针:*[n]T
var ap *[3]int // var ap = new([3]int)
ap = new([3]int)
*ap = [3]int{4, 5, 6}
fmt.Printf("ap:%v psr:%p type:%T \n", *ap, ap, ap)
// 多维数组
multArr := [...][2]int{{0, 0}, {1, 1}, {2, 2}}
fmt.Println(multArr)
}
2. 切片
切片并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。
- 切片是引用类型;底层数据结构是一个数组,可以看作是对数组某个连续片段的引用。
- 切片是可变长的(包含长度、容量)。
- 长度:用
len()
函数求长度,表示可用元素数量,读写操作不能超过该限制。 - 容量:用
cap()
函数求最大容量,为切片底层数组的长度。 - 扩容及规律: 扩容通常以 2 倍容量重新分配底层数组;当原有切片容量大于 1024 时,以 1.25 倍扩容。
- 若 slice == nil,则
len
、cap
结果都等于 0。
2.1. slice 声明、初始化
package main
import "fmt"
func main() {
// 声明
var s []int
if s == nil {
fmt.Println("s is nil")
}
s1 := []int{1, 2, 3, 4}
// 使用 make()
var s2 []int = make([]int, 0)
s3 := make([]int, 5)
s4 := make([]int, 5, 8)
fmt.Println(s, s1, s2, s3, s4)
fmt.Println("Cap:", cap(s), cap(s1), cap(s2), cap(s3), cap(s4))
// 从数组切片
arr := [5]int{1, 2, 3, 4, 5}
var s5 []int
s5 = arr[1:4] // 前包后不包
fmt.Println(s5, len(s5), cap(s5))
}
2.2. slice 操作
- slice 的索引起点为 0。
- 切片截取:s[i:j:max],满足
i <= j <= max
;其中,i
为起始索引(包含),省略值0
;j
结束索引(不包含),省略值len(s)
;max
为截取最大索引,省略值len(s)
。 - 截取新切片长度 = j - i
- 截取新切片容量 = max - i
- 截取新切片开始索引非 0,指针地址指向底层数组中新切片开始索引所在的地址;起始地址相同的
slice
指针地址相同,例:s[i], s[i:], s[i:j], s[i:j:max] 的指针引用地址都相同。
package main
import "fmt"
func main() {
s := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
v := s[0]
fmt.Println(v)
s1 := s[2:5]
s2 := s[2:8]
s3 := s[:10:12]
fmt.Println("val", v, s1, s2, s3)
fmt.Println("len", len(s1), len(s2), len(s3))
fmt.Println("cap", cap(s1), cap(s2), cap(s3))
fmt.Printf("prs %p %p %p \n", s1, s2, s3)
}
2.3. append() 追加切片、扩容
append() 向 slice 尾部添加数据,返回新的 slice 对象。
扩容通常以 2 倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的 len 属性,改用索引号进行操作。及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 无法回收。
package main
import "fmt"
func main() {
arr := []int{0, 1, 2, 3, 4}
fmt.Println("len =", len(arr), "cap =", cap(arr)) // len = 5 cap = 5
arr1 := append(arr, 5) // 追加数据,超出原切片容量,则进行扩容
fmt.Println("len =", len(arr1), "cap =", cap(arr1)) // len = 6 cap = 10
fmt.Printf("prs: %p %p \n", arr, arr1) // 对比底层数组起始指针,扩容后重新分配底层数组,与原数组无关。
arr1 = append(arr1, []int{6, 7, 8, 9, 10}...)
fmt.Println("len =", len(arr1), "cap =", cap(arr1)) // len = 11 cap = 20
}
2.4. 字符串和切片
string 底层就是一个 byte/rune 的数组,因此,也可以进行切片操作。
package main
import "fmt"
func main() {
str := "Hello, Chain"
s1 := str[:5]
println(s1) // Hello
//string 本身是不可变的,因此要改变string中字符;需将字符转为slice,进行更改,再转为string
//str[0] = 'X' // 报错:cannot assign to str[0] (value of type byte)
s := []byte(str) // 英文字符使用 byte,代表ASCII码的一个字符
// 赋值需要使用单引号(''),单引号定义一个字符,双引号定义个字符串
s = append(s, '!')
str = string(s)
fmt.Println(str)
str2 := "你好,中国"
s2 := []rune(str2) // 包含中文字符使用 rune,代表一个UTF-8字符
s2[3] = '伟'
s2[4] = '大'
s2 = append(s2, []rune{'的', '中', '国'}...)
fmt.Println(string(s2))
}
3. Copy
- 深拷贝:拷贝的是数据本身;值类型数据,默认是深拷贝,Array、Int、Sting、Struct、Bool、Float。
- 浅拷贝:拷贝的是数据地址;引用类型数据,默认浅拷贝,Map、Slice。
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
arr2 := arr
arr[1] = 10
fmt.Println(arr, arr2)
fmt.Printf("prs: %p %p \n", &arr, &arr2) // Array 属于深copy
fmt.Println()
s1 := arr[:6]
s2 := arr[4:]
s1[5] = 50 // Slice 属于浅copy
fmt.Println(s1, s2)
fmt.Printf("prs:%p %p \n", s1, s2)
// copy函数只能用于切片,属于浅拷贝;允许元素区间重叠
copy(s1, s2)
s1[1] = 100
fmt.Println(s1, s2, arr)
}