一、Go语言数组
1.1数组的概念
-
语法说明
-
var 数组变量名 [元素数量]Type
-
元素数量:必须在编译器就能够确定
-
Type:可以是任意类型,类型为数组本身时,可以实现多维数组
func main() { var arr [3][3]int arr[0][0]=100 fmt.Println(len(arr)) //内置函数,返回数组中元素的个数 for _,v:=range arr{ fmt.Println(v) } } /* [100 0 0] [0 0 0] [0 0 0] */
-
-
-
默认情况下,数组的每个元素都会被初始化为元素类型对应的零值。同时也可以使用数组字面值语法,用一组值来初始化数组
var q [3]int = [3]int{1, 2, 3} var r [3]int = [3]int{1, 2} fmt.Println(r[2]) // "0",r数组0下标和1下标对应的元素已经被初始化,2下标对应的为默认的0
-
在数组的定义中,如果在数组长度的位置出现“…”省略号,则表示数组的长度是根据初始化值的个数来计算,因此,上面数组 q
q := [...]int{1, 2, 3}
-
数组的长度是数组类型的一个组成部分,数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定
q := [3]int{1, 2, 3} q = [4]int{1, 2, 3, 4} // 编译错误:无法将 [4]int 赋给 [3]int
-
如果两个数组类型相同(包括数组的长度,数组中元素的类型)的情况下,我们可以直接通过较运算符(
==
和!=
)来判断两个数组是否相等,只有当两个数组的所有元素都是相等的时候数组才是相等的,不能比较两个类型不同的数组,否则程序将无法完成编译
1.2多维数组
-
语法说明
-
声明:var array_name [size]…[sizen] array_type
-
使用数组字面量来声明并初始化一个二维整型数组
array = [4][2]int{1: {20, 21}, 3: {40, 41}}
-
-
要类型一致,就可以将多维数组互相赋值
func main() { var arr1 [2][2]int arr1[0][1] = 222 var arr2 [2][2]int arr2 = arr1 //将arr1赋值给arr2 for _, v := range arr2 { fmt.Println(v) } //通过索引赋值 arr2[1]=arr1[0] for _, v := range arr2 { fmt.Println(v) } } //类型相同直接赋值 [0 222] [0 0] //索引赋值结果 [0 222] [0 222]
二、Go语言切片
2.1切片的概念
- 切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内
- 切片的内部结构包含地址、大小、容量,切片一般用于快速的操作一块数据集合,如果将数据集合比作蛋糕的话,切片就是你要的那一块,切片的过程包含从哪里开始(切片的起始位置)及切多大(切片的大小),容量可以理解为装切片的口袋大小,如下图所示
2.2切片语法说明
- 切片默认指向一段连续内存区域,可以是数组,也可以是切片本身
- 从连续内存区域生成切片是常见的操作,格式如下
- slice [开始位置 : 结束位置]
- slice:表示目标切片对象
- 开始位置:对应目标切片对象的索引
- 结束位置:对应目标切片的结束索引
- slice [开始位置 : 结束位置]
- 切片有点像C语言里的指针,指针可以做运算,但代价是内存操作越界,切片在指针的基础上增加了大小,约束了切片对应的内存区域,切片使用中无法对切片内部的地址和大小进行手动调整,因此切片比指针更安全、强大
2.3使用演示
-
从数组生成切片
- 取出的元素数量为:结束位置 - 开始位置
- 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取
- 当缺省开始位置时,表示从连续区域开头到结束位置
- 当缺省结束位置时,表示从开始位置到整个连续区域末尾
- 两者同时缺省时,与切片本身等效
- 两者同时为 0 时,等效于空切片,一般用于切片复位(把切片的开始和结束位置都设为 0 时,生成的切片将变空)
func main() { arr := [...]int{1, 2, 3, 4} fmt.Println(arr, arr[1:3]) //[1 2 3 4] [2 3] fmt.Println(arr[:2]) //[1 2],缺省开始位置 fmt.Println(arr[2:]) //[3 4],缺省结束位置 fmt.Println(arr[:]) //1 2 3 4],两者同时缺省,与切片本身等效 fmt.Println(arr[0:0]) //[],等于空切片,一般用于切片复位 }
-
直接声明新的切片
-
除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此切片类型也可以被声明
-
var name []Type
- 声明一个xx类型的切片,切片中拥有多个xx
- 声明切片但是没有进行任何操作,切片不会指向任何数组或者其它切片
- 声明但未使用的切片的默认值是 nil
func main() { var strList []string //声明字符串切片 var numList []int //声明整形切片 var numListEmpty = []int{} //声明空切片 fmt.Println(strList, numList, numListEmpty) //输出三个切片 [] [] [] fmt.Println(len(strList), len(numList), len(numListEmpty)) //输出三个切片的大小 0 0 0 // 切片判定空的结果 fmt.Println(strList == nil) //true fmt.Println(numList == nil) //true fmt.Println(numListEmpty == nil) //false,已经分配了内存,但是没有元素(没有填充) }
-
-
使用make()函数构造切片
-
如果需要动态地创建一个切片,可以使用 make() 内建函数
- make( []Type, size, cap )
- 其中 Type 是指切片的元素类型,size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题
slice1 := make([]int,3) slice2:=make([]int,2,10) fmt.Println(slice1, slice2) //[0 0 0] [0 0] fmt.Println(len(slice1),len(slice2)) //3 2 //判空 fmt.Println(slice1==nil) //false -> 表示分配了内存 fmt.Println(slice2==nil) //false
- make( []Type, size, cap )
-
使用 make() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作
-
-
append()给切片添加元素
-
在使用 append() 函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变
var slice []int slice = append(slice, 1) //追加一个元素 slice = append(slice, 1, 2, 3) //追加多个元素 slice = append(slice, []int{4, 5, 6}...) //追夹一个切片 fmt.Println(slice) //[1 1 2 3 4 5 6]
-
切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充
//验证扩容 -> cap每次2倍增加,地址每次都会发生变化 var capSlice []int for i := 0; i < 10; i++ { capSlice = append(capSlice, i) fmt.Printf("len:%d, cap:%d, poniter:%p\n", len(capSlice), cap(capSlice), capSlice) } /* len:1, cap:1, poniter:0xc0000b2070 len:2, cap:2, poniter:0xc0000b2080 len:3, cap:4, poniter:0xc0000b4040 len:4, cap:4, poniter:0xc0000b4040 len:5, cap:8, poniter:0xc0000b6080 len:6, cap:8, poniter:0xc0000b6080 len:7, cap:8, poniter:0xc0000b6080 len:8, cap:8, poniter:0xc0000b6080 len:9, cap:16, poniter:0xc0000ba000 len:10, cap:16, poniter:0xc0000ba000 */
-
在切片头部添加元素
-
在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制 1 次,因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多
var slice = []int{1, 2} slice = append([]int{0}, slice...) //在切片头部添加1个元素(必须使用切片的模式) slice = append([]int{-3, -2}, slice...) //在切片头部添加一个切片 fmt.Println(slice) //[-3 -2 0 1 2]
-
append 函数返回新切片的特性,所以切片也支持链式操作,我们可以将多个 append 操作组合起来,实现在切片中间插入元素
var slice = []int{1, 2, 3, 7, 8, 9} //在3位置插入[]int{4,5,6} //首先是在slice[3:]前面插入{4,5,6}返回了一个新的切片 //然后在这个新的切片前面插入slice[:3] slice = append(slice[:3], append([]int{4, 5, 6}, slice[3:]...)...) fmt.Println(slice) //[1 2 3 4 5 6 7 8 9]
-
-
-
切片复制
-
Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制
-
copy( destSlice, srcSlice []T) int
-
虽然通过循环复制切片元素更直接,不过内置的 copy() 函数使用起来更加方便,copy() 函数的第一个参数是要复制的目标 slice,第二个参数是源 slice,两个 slice 可以共享同一个底层数组,甚至有重叠也没有问题
var slice = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} var slice2 []int copy(slice2, slice) fmt.Println(slice2) //[],slice2此时没有空间 slice2 = make([]int, 3, 6) //make 3个空间 copy(slice2, slice) fmt.Println(slice2) //[1 2 3]
-
切片不会因为等号操作进行元素的复制,而是进行引用
var data = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} quote := data //引用切片slice数据 copyData := make([]int, 9) //make 9个空间的新切片 copy(copyData, data) //进行拷贝 data[0] = 9999 //修改data的第一个元素 fmt.Println(quote) //打印引用的切片 -> [9999 2 3 4 5 6 7 8 9] fmt.Println(copyData) //打印拷贝的切片 -> 1 2 3 4 5 6 7 8 9]
-
-
切片数据的删除
-
Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快
-
连续容器的元素删除无论在任何语言中,都要将删除点前后的元素移动到新的位置,随着元素的增加,这个过程将会变得极为耗时,因此,当业务需要大量、频繁地从一个切片中删除元素时,如果对性能要求较高的话,就需要考虑更换其他的容器了(如双链表等能快速从删除点删除元素)
-
从开头位置进行删除
-
直接挪动数据指针进行删除
var data = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Printf("beforeAddress:%p\n", data) //beforeAddress:0xc0000be000 //直接移动数据指针 data=data[N:] 删除开头N个元素 data = data[1:] //删除开头第一个元素 fmt.Println(data) //[2 3 4 5 6 7 8 9] fmt.Printf("afterAddress:%p\n", data) //afterAddress:0xc0000be008
-
将后面的数据向开头进行移动,使用append原地完成(原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化)
var data = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Printf("beforeAddress:%p\n", data) //beforeAddress:0xc0000b6000 //使用append,不挪动指针 //data=append(data[:0],data[:N]...) 删除前面N个元素 data=append(data[:0],data[1:]...) fmt.Println(data) //[2 3 4 5 6 7 8 9] fmt.Printf("afterAddress:%p\n", data) //afterAddress:0xc0000b6000
-
使用copy来删除开头元素
var data = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Printf("beforeAddress:%p\n", data) //beforeAddress:0xc000136000 //使用copy进行删除 //data=data[:copy(data,data[N:])]删除前面N个元素 data=data[:copy(data,data[1:])] fmt.Println(data) //[2 3 4 5 6 7 8 9] fmt.Printf("afterAddress:%p\n", data) //afterAddress:0xc000136000
-
-
从中间进行删除
-
对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用 append 或 copy 原地完成
//--------使用append------- var data = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Printf("beforeAddress:%p\n", data) //beforeAddress:0xc00012c000 //append(data[:N],data[N+1:]...)删除中间的第N个元素 data = append(data[:2], data[3:]...) fmt.Println(data) //[1 2 4 5 6 7 8 9] fmt.Printf("afterAddress:%p\n", data) //afterAddress:0xc00012c000 //使用copy var data = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Printf("beforeAddress:%p\n", data) //beforeAddress:0xc000116000 //data[:n+copy(data[n:],data[n+1:])] 删除中间一个元素 data=data[:2+copy(data[2:],data[3:])] fmt.Println(data) //[1 2 4 5 6 7 8 9] fmt.Printf("afterAddress:%p\n", data) //afterAddress:0xc000116000
-
-
从尾部删除
var data = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Printf("beforeAddress:%p\n", data) //beforeAddress:0xc00012a000 data = data[:len(data)-1] fmt.Println(data) //[1 2 3 4 5 6 7 8] fmt.Printf("afterAddress:%p\n", data) //afterAddress:0xc00012a000
2.4多维切片
-
Go语言中同样允许使用多维切片,声明一个多维数组的语法格式如下
-
var sliceName [][]…[]sliceType
//初始化方法1 var slice [][]int slice = [][]int{{1}, {2, 3}} fmt.Println(slice) //[[1] [2 3]] //初始化方法2 slice2 := [][]int{{1}, {2, 3}} fmt.Println(slice2) //[[1] [2 3]]
-
-
append函数添加元素
-
Go语言里使用 append() 函数处理追加的方式很简明,先增长切片,再将新的整型切片赋值给外层切片的第一个元素,当下面代码中的操作完成后,再将切片复制到外层切片的索引为 0 的元素
slice := [][]int{{1}, {2, 3}} slice[0] = append(slice[0], 4, 5, 6) //给第一个切片后面添加元素 fmt.Println(slice) //[[1 4 5 6] [2 3]] slice[0] = append([]int{-1, -2, -3}, slice[0]...) //给第一个切片前面添加元素 fmt.Println(slice) //[-1 -2 -3 1 4 5 6] [2 3]]
-