Go语言容器—数组切片

Posted ych9527

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言容器—数组切片相关的知识,希望对你有一定的参考价值。

文章目录

一、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]int1, 2, 3
    var r [3]int = [3]int1, 2
    fmt.Println(r[2]) // "0",r数组0下标和1下标对应的元素已经被初始化,2下标对应的为默认的0
    
  • 在数组的定义中,如果在数组长度的位置出现“…”省略号,则表示数组的长度是根据初始化值的个数来计算,因此,上面数组 q

    q := [...]int1, 2, 3
    
  • 数组的长度是数组类型的一个组成部分,数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定

    q := [3]int1, 2, 3
    q = [4]int1, 2, 3, 4 // 编译错误:无法将 [4]int 赋给 [3]int
    
  • 如果两个数组类型相同(包括数组的长度,数组中元素的类型)的情况下,我们可以直接通过较运算符(==!=)来判断两个数组是否相等,只有当两个数组的所有元素都是相等的时候数组才是相等的,不能比较两个类型不同的数组,否则程序将无法完成编译

1.2多维数组

  • 语法说明

    • 声明:var array_name [size]…[sizen] array_type

    • 使用数组字面量来声明并初始化一个二维整型数组

      array = [4][2]int1: 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:表示目标切片对象
      • 开始位置:对应目标切片对象的索引
      • 结束位置:对应目标切片的结束索引
  • 切片有点像C语言里的指针,指针可以做运算,但代价是内存操作越界,切片在指针的基础上增加了大小,约束了切片对应的内存区域,切片使用中无法对切片内部的地址和大小进行手动调整,因此切片比指针更安全、强大

2.3使用演示

  • 从数组生成切片

    • 取出的元素数量为:结束位置 - 开始位置
    • 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取
    • 当缺省开始位置时,表示从连续区域开头到结束位置
    • 当缺省结束位置时,表示从开始位置到整个连续区域末尾
    • 两者同时缺省时,与切片本身等效
    • 两者同时为 0 时,等效于空切片,一般用于切片复位(把切片的开始和结束位置都设为 0 时,生成的切片将变空)
    func main() 
    	arr := [...]int1, 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() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作

  • append()给切片添加元素

    • 在使用 append() 函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变

      var slice []int
      	slice = append(slice, 1)                 //追加一个元素
      	slice = append(slice, 1, 2, 3)           //追加多个元素
      	slice = append(slice, []int4, 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 = []int1, 2
        	slice = append([]int0, slice...)      //在切片头部添加1个元素(必须使用切片的模式)
        	slice = append([]int-3, -2, slice...) //在切片头部添加一个切片
        	fmt.Println(slice)      								//[-3 -2 0 1 2]
        
      • append 函数返回新切片的特性,所以切片也支持链式操作,我们可以将多个 append 操作组合起来,实现在切片中间插入元素

        var slice = []int1, 2, 3, 7, 8, 9
        
        	//在3位置插入[]int4,5,6
        	//首先是在slice[3:]前面插入4,5,6返回了一个新的切片
        	//然后在这个新的切片前面插入slice[:3]
        	slice = append(slice[:3], append([]int4, 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 = []int1, 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 = []int1, 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 = []int1, 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 = []int1, 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 = []int1, 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 = []int1, 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 = []int1, 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 = []int1, 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 = [][]int1, 2, 3
      	fmt.Println(slice) //[[1] [2 3]]
      
      	//初始化方法2
      	slice2 := [][]int1, 2, 3
      	fmt.Println(slice2) //[[1] [2 3]]
      
  • append函数添加元素

    • Go语言里使用 append() 函数处理追加的方式很简明,先增长切片,再将新的整型切片赋值给外层切片的第一个元素,当下面代码中的操作完成后,再将切片复制到外层切片的索引为 0 的元素

      	slice := [][]int1, 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]]
      

以上是关于Go语言容器—数组切片的主要内容,如果未能解决你的问题,请参考以下文章

Go语言容器—数组切片

Golang basic_leaming2 语言容器

Golang basic_leaming2 语言容器

go语言学习笔记 — 基础 — 高级数据类型 — 数据容器 — 切片:数组 vs. 切⽚

go语言之字符串指针数组切片

Go语言系列之数组和切片