Go 语言切片(Slice)初始化_删除元素_遍历

更新时间 2022-03-31 14:56:10

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 54w+ 字,讲解图 2476+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 1900+ 小伙伴加入学习 ,欢迎点击围观

上面小节,我们学习的 Go 语言数组 ,本小节我们讲讲切片。

一、什么是切片

切片和数组类似,都是数据集合。和数组不同的是,切片是一块动态分配大小的连续空间。它和 Java 语言中的 AarryList 集合类似。

二、声明切片

切片的声明格式如下:

var name []T
  • name 表示切片变量 名;
  • T 表示切片类型。

下面是示例代码:

package main

import "fmt"

func main()  {
	// 声明整型切片
	var numList []int

	// 声明字符串切片
	var strList []string

	// 声明一个空切片, {} 表示已经分配内存,但是切片里面的元素是空的
	var numListEmpty = []int{}

	// 输出3个切片
	fmt.Println(numList, strList, numListEmpty)

	// 输出3个切片大小
	fmt.Println(len(numList), len(strList), len(numListEmpty))

	// 切片判定是否为空结果
	fmt.Println(numList == nil)
	fmt.Println(strList == nil)
	fmt.Println(numListEmpty == nil)
}

代码输出:

[] [] []
0 0 0
true
true
false

图示:

go语言切片代码示例go语言切片代码示例

三、使用 make() 函数构造切片

可以通过 make() 函数动态的创建一个切片,格式如下:

make( []T, size, cap )
  • T : 切片中元素的类型;
  • size : 表示为这个类型分配多少个元素;
  • cap : 预分配的元素数量,该值设定后不影响 size, 表示提前分配的空间,设置它主要用于降低动态扩容时,造成的性能问题。

示例代码如下:

package main

import "fmt"

func main()  {
	a := make([]int, 4)
	b := make([]int, 4, 10)

	fmt.Println(a, b)
	fmt.Println(len(a), len(b))
}

代码输出如下:

[0 0 0 0] [0 0 0 0]
4 4

a 和 b 切片均为大小为 2, 不同的是 b 内存空间预分配了 10 个,但是实际只使用了 2 个元素。

len() 函数计算的是元素的个数,与切片容量无关。

四、使用 append() 函数为切片添加元素

Go 语言 中的内置函数 append() 可以为切片动态添加元素, 示例代码如下:

package main

import "fmt"

func main()  {
	// 声明一个字符串类型的切片
	var strList []string

	// 循环动态向 strList 切片中添加 20 个元素,并打印相关参数
	for i := 0; i < 10; i++ {
		line := fmt.Sprintf("quanxiaoha %d", i)
		strList = append(strList, line)

		fmt.Printf("len: %d, cap: %d, pointer: %p, content: %s\n", len(strList), cap(strList), strList, strList[i])
	}
}

代码输出如下:

len: 1, cap: 1, pointer: 0xc00008e1e0, content: quanxiaoha 0
len: 2, cap: 2, pointer: 0xc0000a6040, content: quanxiaoha 1
len: 3, cap: 4, pointer: 0xc0000b2040, content: quanxiaoha 2
len: 4, cap: 4, pointer: 0xc0000b2040, content: quanxiaoha 3
len: 5, cap: 8, pointer: 0xc0000bc000, content: quanxiaoha 4
len: 6, cap: 8, pointer: 0xc0000bc000, content: quanxiaoha 5
len: 7, cap: 8, pointer: 0xc0000bc000, content: quanxiaoha 6
len: 8, cap: 8, pointer: 0xc0000bc000, content: quanxiaoha 7
len: 9, cap: 16, pointer: 0xc0000be000, content: quanxiaoha 8
len: 10, cap: 16, pointer: 0xc0000be000, content: quanxiaoha 9

通过上面的代码输出,会发现 len() 并不等于 cap。这是因为当切片空间不足以容纳足够多的元素时,切片会自动进行扩容操作, 扩容规律按切片容量的 2 倍进行扩容,如 1、2、4、8、16 ....

PS: 扩容一般发生在 append() 函数调用时。

另外,append() 函数除了添加一个元素外,还能一次性添加多个元素:

package main

import "fmt"

func main()  {
	var strList []string

	// 添加一个元素
	strList = append(strList, "quanxiaoha")

	// 添加多个元素
	strList = append(strList, "www", "quanxiaoha", "com")

	// 添加切片
	list := []string{"犬小哈", "教程"}
  // list 后面的 ... 表示将 list 整个添加到 strList 切片中
	strList = append(strList, list...)

	fmt.Println(strList)
}

代码输出如下:

[quanxiaoha www quanxiaoha com 犬小哈 教程]

五、从数组或切片生成新的切片

从数组或切片生成新的切片是很常见的操作,格式如下:

slice [开始位置:结束位置]
  • slice 表示切片目标;
  • 开始位置和结束位置对应目标切片的下标。

从数组中生成切片:

package main

import "fmt"

func main()  {
	var arr = [3]int{1, 2, 3}
	
	fmt.Println(arr, arr[1:2])
}

代码输出如下:

[1 2 3] [2]

[2] 是 arr[1:2] 切片操作的结果。注意取出的元素不包括结束位置的元素。

5.1 从指定范围中生成切片

package main

import "fmt"

func main()  {
	var arr = [20]int{}

	// 向数组中添加元素
	for i := 0; i < 20; i++ {
		arr[i] = i + 1
	}

	// 指定区间
	fmt.Println(arr[8:15])

	// 中间到尾部所有元素
	fmt.Println(arr[10:])

	// 开头到中间所有元素
	fmt.Println(arr[:10])

	// 切片本身
	fmt.Println(arr[:])
}

代码输出:

[9 10 11 12 13 14 15]
[11 12 13 14 15 16 17 18 19 20]
[1 2 3 4 5 6 7 8 9 10]
[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20]
  • 若不填写结束位置,如 arr[10:], 则表示从下标 10 置到数组的结束位置。
  • 若不填写开始位置,如 arr[:10],则表示从 0 到下标 10 的位置。
  • 若开始位置和结束位置都不填写,如 arr[:], 则会生成一个和原有切片一样的切片。

5.2 重置切片

若把切片的开始位置和结束位置都设置为 0, 则会生成一个空的切片:

package main

import "fmt"

func main()  {
	var arr = [20]int{}

	// 向数组中添加元素
	for i := 0; i < 20; i++ {
		arr[i] = i + 1
	}

	fmt.Println(arr[0:0])
}

代码输出:

[]

六、复制切片元素到另一个切片

Go 语言内置函数 copy() 可以将一个切片中的数据复制到另一个切片中,使用格式如下:

copy( destSlice, srcSlice []T) int
  • srcSlice 代表源切片;
  • destSlice 代表目标切片。注意,目标切片必须有足够的空间来装载源切片的元素个数。返回值为整型,表示实际发生复制的元素个数。

演示代码如下:

package main

import "fmt"

func main()  {
	// 设置元素数量为 10
	const count = 10

	// 源分片
	srcSlice := make([]int, count)

	// 给源分片赋值
	for i := 0; i < count; i++ {
		srcSlice[i] = i
	}

	// 目标分片
	destSlice := make([]int, count)

	// 将 srcSlice 分片的数据复制到 destSlice 中
	copy(destSlice, srcSlice)

	fmt.Println(srcSlice)
	fmt.Println(destSlice)
}

代码输出:

[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]

另外,我们还可以复制指定范围的数据:

// 将 srcSlice 分片中指定范围的数据复制到 destSlice 中
copy(destSlice, srcSlice[4:8])

fmt.Println(srcSlice)
fmt.Println(destSlice)

代码输出:

[0 1 2 3 4 5 6 7 8 9]
[4 5 6 7 0 0 0 0 0 0]

七、从切片中删除元素

Go 语言中并没有提供特定的函数来删除切片中元素,但是可以利用切片的特性来达到目的:

package main

import "fmt"

func main()  {
	// 声明一个字符串类型的切片
	arr := []string{"q", "u", "a", "n", "x", "i", "a", "o", "h", "a"}

	// 指定删除位置,也就是 u 元素
	index := 1

	// 打印删除位置之前和之后的元素, arr[:index] 表示的是被删除元素的前面部分数据,arr[index+1:] 表示的是被删除元素后面的数据
	fmt.Println(arr[:index], arr[index+1:])

	// 将删除点前后的元素拼接起来
	arr = append(arr[:index], arr[index+1:]...)

	fmt.Println(arr)
}

代码输出如下:

[q] [a n x i a o h a]
[q a n x i a o h a]

总结:Go 语言中切片删除元素的本质即: 以被删除元素为分界点, 将前后两个部分的内存重新连接起来。