数组和切片
两者区别、切片的介绍
参考文章
数组和切片申明
- 数组声明
数组中,没有初始化的数组,默认值为 0。
// 没有初始化的数组,默认值为 0
var arr1 [3]int
var arr2 [4]string
初始化数组的方式有四种:
// 初始化的数组
// 方式一
var arr1 [3]int
arr1[0] = 13
arr1[1] = 4
arr1[2] = 29
// 方式二
var arr2 = [4]int{23,12,42}
// 方式三
arr3 := [...]int{1,2}
// 方式四
arr4 := [...]int{1:1,3:5} // key : value
定义多维数组:
var arr = [3][2]string{
{"北京","上海"},
{"广州","深圳"},
{"成都","重庆"},
}
fmt.Println(arr[0])
fmt.Println(arr[0][1])
- 切片声明
切片中,没有初始化的切片, 默认数据是 nil
// 没有初始化的切片
var arr []int
// 没有数据, 切片中默认数据是 nil
fmt.Println(arr1 == nil) // true
//方式二 make() 声明一个切片 make([]T, size, cap)
var sliceA = make([]int,4,8)
初始化切片的方式有二种:
// 初始化的切片
// 方式一
var arr2 = []int{1,2,3,4,5}
// 方式二
var arr3 = []int{1:2, 3:5} // key : value
// 方式三 基于数组来定义切片
a := [5]int{1,2,3,4,5}
b := a[:] // 获取数组中的所有值
// b 就是切片
c := a[1:3] //左包右不包 [2,3]
- 总结:
带长度值便是 数组,不带便是 切片
数组和切片的区别
- 数组的长度不可变
切片是增长和收缩的动态序列
- 数组是 值类型
切片是 引用类型
切片的介绍
切片在 go 语言中并不是数组,而是以结构体的形式存在着,其定义如下:
type slice struct {
array unsafe.Pointer // 指向存放数据的数组指针
len int // 长度
cap int // 容量
}
熟悉 C/C++ 的同学一定会知道,在结构体里用数组指针的问题——数据会发生共享
接下来看一下slice的一些操作:
foo = make([]int, 5)
foo[3] = 2
foo[4] = 10
bar := foo[1:4]
bar[1] = 9
通过图示来进一步的理解以下上面的代码

通过上图就能很容易理解,为什么修改 bar 会改变原切片的值,为什么 bar 的 cap 是4,是因为它们数据共享。
插一嘴:
cap 介绍
cap 是切片的容量。
切片的容量:指的是从它的第一个元素开始数,到其底层数组元素末尾的个数。
所以,现在就能理解了 bar 的 cap 是 4 了。
cap 的作用
容量 ( cap ) 的作用:在 数据拷贝和内存申请的消耗 与 内存占用 之间提供一个权衡。
例如:在用 append 扩展长度时,若新的长度小于容量,不会更换底层数组,否则,会新申请一个底层数组,拷贝这边的值过去,把原来的数组丢掉。
接下来,使用一个 append 方法来进一步的理解 切片(slice)中 cap 。
a := make([]int, 32)
b := a[1:16]
a = append(a, 1)
a[2] = 42
上述代码的含义:把 a[1:16] 的切片赋给到了 b ,此时,a 和 b 的内存空间是共享的。然后,对 a 做了一个 append() 的操作,这个操作会让 a 重新分享内存,导致 a 和 b 不再共享,如下图所示:

从上图可以看以看到 append() 操作让 a 的容量变成了64,而长度是33。这里,需要重点注意一下——append()这个函数在cap****不够用的时候就会重新分配内存以扩大容量,而如果够用的时候不不会重新分享内存。
同时,最好在初始化时就将 容量设置为适当的值,不然,appned 扩容是以 2的n次方扩容的。
再看一个有意思的例子:
func main() {
path := []byte("AAAA/BBBBBBBBB")
sepIndex := bytes.IndexByte(path,'/')
dir1 := path[:sepIndex]
dir2 := path[sepIndex+1:]
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB
dir1 = append(dir1,"suffix"...)
fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => uffixBBBB
}
dir1 和 dir2 都分别共享 path 的部分内存数据,虽然dir1有一个append()操作,但是因为 cap 足够,于是数据扩展到了dir2的空间。

如果想要解决dir1 扩容,同时不影响 dir2 的数据:只需要修改一行代码:
dir1 := path[:sepIndex]
// 改为
dir1 := path[:sepIndex:sepIndex]
新的代码使用了 Full Slice Expression,其最后一个参数叫“Limited Capacity”,于是,后续的append()操作将会导致重新分配内存。
插一嘴
Full Slice Expression 介绍
slice 定义的全部表达式其实有三个变量:[low : high : max] 我们最常用的是前两个,第三个不写默认省略。
切片的长度 len 为:high - low
切片的容量 max 为:max - low
// input[low:high:max]
// Length of the the result is high — low
// Capacity of the result is max — low
// 例如:
numbers := [10]int{0,1,2,3,4,5,6,7,8,9}
s := numbers[2:4:6]
fmt.Println(s) // [2, 3]
fmt.Println(cap(s)) // 4
切片的方法
- 添加数据
使用 append 方法
var sliceA []int
sliceA = append(sliceA, 12)
sliceA = append(sliceA, 12, 23, 1)
// append 方法还可以合并切片
// 注意: append 合并切片时,最后一个元素要加 ...
slice1 := []string{"php","java"}
slice2 := []string{"nodejs","golang"}
slice1 = append(slice1, slice2...)
- 复制操作
使用 copy 方法
sliceA := []int{1,2,3,4}
sliceB := sliceB
sliceB[0] = 11 // sliceA = [11,2,3,4]
sliceC = make([]int, 4, 4)
copy(sliceC, sliceA)
sliceC[0] = 222 // sliceA = [11,2,3,4]
- 删除元素
Go语言中并没有除切片元素的专用方法,我们可以使用切片本身的特性来除元素。
a := []int{1, 2, 3, 4, 5, 6, 7}
// 删除索引为 2 的元素, 注意: append 合并切片时,最后一个元素要加 ...
a = append(a[:2], a[3:]...)
- 修改切片中结构体对象的值
type generalCates struct {
Name string
}
type category struct {
ID string
general generalCates
}
var cates []category
// 修改 cates 切片中 genreal 中的 Name 值
for index, cate := range cates {
cates[index].General.Name = "另一个值"
// 修改 cates 中的值时,必须用 cates + 索引 修改值。
// 不能使用 cate 修改值,因为循环结束时候临时变量的作用域消失,并没有真正的修改元素的属性;
}
- 排序
使用 sort 包中的方法对切片排序
- 升序排序
对于int、 float64 和 string 数组或是切片的排序,go 分别提供了sort.Ints()、sort.Float64s() 和 sort.Strings() 函数,默认都是从小到大排序。
intList := []int{1, 2, 34, 5, 6, 56, 7, 8}
float8List := []float64{4.2, 23.4, 13.2, 12.4}
stringList := []string{"a", "d", "f", "s", "x", "r", "g"}
sort.Ints(intList)
sort.Float64s(float8List)
sort.Strings(stringList)
fmt.Println(intList)
fmt.Println(float8List)
fmt.Println(stringList)
- 降序排序
Golang 的 sort 包可以使用 sort.Reverse(slice) 来调换 sice.Interface.Less ,也就是比较函数,所以,int、 float64 和 string 的逆序排序函数可以这么写。
intList := []int{1, 2, 34, 5, 6, 56, 7, 8}
float8List := []float64{4.2, 23.4, 13.2, 12.4}
stringList := []string{"a", "d", "f", "s", "x", "r", "g"}
sort.Sort(sort.Reverse(sort.IntSlice(intList)))
sort.Sort(sort.Reverse(sort.Float64Slice(float8List)))
sort.Sort(sort.Reverse(sort.StringSlice(stringList)))
fmt.Println(intList)
fmt.Println(float8List)
fmt.Println(stringList)
扩充知识
- 什么是值类型,什么是引用类型
值类型:改变变量副本值的时候,不会改变本身的值
引用类型:改变变量副本值的时候,会改变本身的值
切片是引用类型 , 赋值是赋的是地址,一个改变,另一个也改变
Go 中的 基本数据类型 和 数组 都是值类型,赋值是内容,相当于重新拷贝了一份
- unsafe.Pointer 是什么
它是 go 语言中的指针。还需要开一章讲一讲