7、golang-切片

切片

切片(Slice)是一个拥有相同类型元素可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

切片是一个引用类型,它的内部结构包含地址长度容量。切片一般用于快速地操作一块数据集合。

1. 切片的声明和初始化

var SliceName []type
var slice1 []type = make([]type, len)
slice1 := make([]type, len)
slice1 := make([]T, length, capacity)

 

func main() {
	// 声明切片类型 直接初始化
	var a []string              //声明一个字符串切片
	var b = []int{}             //声明一个整型切片并初始化
	var c = []bool{false, true} //声明一个布尔切片并初始化
	var d = []bool{false, true} //声明一个布尔切片并初始化
	fmt.Println(a)              //[]
	fmt.Println(b)              //[]
	fmt.Println(c)              //[false true]
	fmt.Println(a == nil)       //true
	fmt.Println(b == nil)       //false
    //  invalid operation: c == d (slice can only be compared to nil)
	fmt.Println(c == nil)       //false
	// fmt.Println(c == d)   //切片是引用类型,不支持直接比较,只能和nil比较
    
    // 方法2:用make()函数来创建切片:var 变量名 = make([]变量类型,长度,容量)
        var s = make([]int, 0, 5)
    // 数组已创建
    // 切分数组:var 变量名 []变量类型 = arr[low, high],low和high为数组的索引。
        var arr = [5]int{1,2,3,4,5}
        var slice []int = arr[1:4] // [2,3,4]
}

2. 判断切片是否为空

一个切片在未初始化之前默认为 nil,长度为 0

要检查切片是否为空,请始终使用len(s) == 0来判断,而不应该使用s == nil来判断。

3. 使用make()函数构造切片

我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的make()函数,格式如下:

make([]T, size, cap)

其中:

  • T:切片的元素类型
  • size:切片中元素的数量
  • cap:切片的容量

举个例子:

func main() {
	a := make([]int, 2, 10)
	fmt.Println(a)      //[0 0]
	fmt.Println(len(a)) //2
	fmt.Println(cap(a)) //10
}

4. 切片的长度和容量

  • 切片的长度是它所包含的元素个数
  • 切片的容量是从它的第一个元素到其底层数组元素末尾的个数。

从上面的源码,在对 slice 进行 append 等操作时,可能会造成 slice 的自动扩容。其扩容时的大小增长规则是:

  • 如果切片的容量小于 1024,则扩容时其容量大小乘以2;一旦容量大小超过 1024,则增长因子变成 1.25,即每次增加原来容量的四分之一。
  • 如果扩容之后,还没有触及原数组的容量,则切片中的指针指向的还是原数组,如果扩容后超过了原数组的容量,则开辟一块新的内存,把原来的值拷贝过来,这种情况丝毫不会影响到原数组。

小结:

  • 切片是一个结构体,保存着切片的容量,长度以及指向数组的指针(数组的地址)
  • 尽量对切片设置初始容量值,以避免 append 调用 growslice,因为新的切片容量比旧的大,会开辟新的地址,拷贝数据,降低性能。
func qiepian2() {
	// 创建一个长度和容量都为 5 的切片
	mySlice := []int{1, 2, 3, 4, 5}
	fmt.Println("cap:", cap(mySlice)) // cap: 5
	fmt.Println("len:", len(mySlice))  // len:5
	// 向切片追加一个新元素,将新元素赋值为 6
	mySlice = append(mySlice, 6)
	fmt.Println("cap", cap(mySlice)) // cap: 10
	fmt.Println("len", len(mySlice)) // len:6
}

5. append()方法为切片添加元素

Go语言的内建函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。

func main(){
	var s []int
	s = append(s, 1)        // [1]
	s = append(s, 2, 3, 4)  // [1 2 3 4]
	s2 := []int{5, 6, 7}  
	s = append(s, s2...)    // [1 2 3 4 5 6 7]
}

注意:通过var声明的零值切片可以在append()函数直接使用,无需初始化。

var s []int
s = append(s, 1, 2, 3)

没有必要像下面的代码一样初始化一个切片再传入append()函数使用,

s := []int{}  // 没有必要初始化
s = append(s, 1, 2, 3)

var s = make([]int)  // 没有必要初始化
s = append(s, 1, 2, 3)

每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()函数调用时,所以我们通常都需要用原变量接收append函数的返回值。

 

6. 切片表达式

表达式1:

a[low : high]

表达式2:

完整切片表达式需要满足的条件是0 <= low <= high <= max <= cap(a)

a[low : high : max]

结果切片的容量设置为max-low

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	s := a[1:3]  // s := a[low:high]
    // %v 按照默认格式输出
    // %+v 额外输出字段名
    // %#v 额外输出字段名+类型名
    fmt.Printf("s:%v len(s):%v cap(s):%v %#v\n", s, len(s), cap(s),cap(s))
    
    a[2:]  // 等同于 a[2:len(a)]
    a[:3]  // 等同于 a[0:3]
    a[:]   // 等同于 a[0:len(a)]
}
/*
s:[2 3] len(s):2 cap(s):4
s2:[5] len(s2):1 cap(s2):1
*/

 

切片的本质

切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。

举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。

切片s2 := a[3:6],相应示意图如下:

7. 切片的赋值拷贝

赋值拷贝前后两个变量共享底层数组对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

func main() {
	s1 := make([]int, 3) //[0 0 0]
	s2 := s1             //将s1直接赋值给s2,s1和s2共用一个底层数组
	s2[0] = 100
	fmt.Println(s1) //[100 0 0]
	fmt.Println(s2) //[100 0 0]
}

 

8. 使用copy()函数复制切片

首先我们来看一个问题:

func main() {
	a := []int{1, 2, 3, 4, 5}
	b := a
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(b) //[1 2 3 4 5]
	b[0] = 1000
	fmt.Println(a) //[1000 2 3 4 5]
	fmt.Println(b) //[1000 2 3 4 5]
}

由于切片是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。

Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:

copy(destSlice, srcSlice []T)

其中:

  • srcSlice: 数据来源切片
  • destSlice: 目标切片

举个例子:

func main() {
	// copy()复制切片
	a := []int{1, 2, 3, 4, 5}
	c := make([]int, 5, 5)
	copy(c, a)     //使用copy()函数将切片a中的元素复制到切片c
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1 2 3 4 5]
	c[0] = 1000
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1000 2 3 4 5]
}


package main

import "fmt"

func main() {
   var numbers []int
   printSlice(numbers)

   /* 允许追加空切片 */
   numbers = append(numbers, 0)
   printSlice(numbers)

   /* 向切片添加一个元素 */
   numbers = append(numbers, 1)
   printSlice(numbers)

   /* 同时添加多个元素 */
   numbers = append(numbers, 2,3,4)
   printSlice(numbers)

   /* 创建切片 numbers1 是之前切片的两倍容量*/
   numbers1 := make([]int, len(numbers), (cap(numbers))*2)

   /* 拷贝 numbers 的内容到 numbers1 */
   copy(numbers1,numbers)
   printSlice(numbers1)  
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}


// 以上代码执行输出结果为:len=0 cap=0 slice=[]
len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=6 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 1 2 3 4]

 

从切片中删除元素

Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:

func main() {
	// 从切片中删除元素
	a := []int{30, 31, 32, 33, 34, 35, 36, 37}
	// 要删除索引为2的元素
	a = append(a[:2], a[3:]...)
	fmt.Println(a) //[30 31 33 34 35 36 37]
}

总结一下就是:要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]...)

切片遍历

切片的遍历方式和数组是一致的,支持索引遍历和for range遍历。

练习:

func qiepian4() {
	var a = make([]string, 5, 10)
	for i := 0; i < 10; i++ {
		a = append(a, fmt.Sprintf("%v", i))
	}
	fmt.Println(a) // [     0 1 2 3 4 5 6 7 8 9]
	fmt.Println(cap(a)) //20
	fmt.Println(len(a))  //15

	fmt.Printf("%#v", a)
	// []string{"", "", "", "", "", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}%
}

// 排序
func mySort() {
	var a = [...]int{3, 7, 8, 9, 1}
	b := a[:]
	sort.Ints(b)
	fmt.Println(b) // [1 3 7 8 9]
	sort.Sort(sort.Reverse(sort.IntSlice(b)))
	fmt.Println(b) // [9 8 7 3 1]
}

 

 

参考链接:

1、切片的长度和容量:https://www.cnblogs.com/xiaoxi-jinchen/p/14751102.html?ivk_sa=1024320u

2、https://www.liwenzhou.com/posts/Go/06_slice/

3、sort包介绍:https://blog.csdn.net/weixin_52690231/article/details/124241094

4、https://blog.csdn.net/ffzhihua/article/details/83858464

posted on 2022-04-26 19:42  不出世不入世什么都不是  阅读(59)  评论(0编辑  收藏  举报