Go【No-6】切片

本文详细介绍了Go语言中的切片,包括切片的定义、基于数组创建、使用make()创建、切片的属性(长度、容量)、切片遍历、append()、copy()操作以及删除元素的方法。切片作为引用类型,其特点是可变长度和动态扩容,常用于高效地操作数据集合。需要注意的是,切片赋值会导致共享底层数组,可能导致意外的数据修改。
摘要由CSDN通过智能技术生成

6-切片

切片是一个拥有先用数据类型元素的可变长度的序列。

数组是固定长度,切片是可变长度
数组是值类型,切片是引用类型

数组有很多局限性,切片非常灵活,支持自动扩容

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

创建切片

注意:切片是引用类型!!!

// 切片
var identifier []type
var identifier = []type{initial value}
identifier := []type{initial value}

// 数组
var identifier [len]type
var identifier = [...]type{initial value}

identifier:切片名、type:切片数据类型

区别于数组,切片在定义时不用填写 len。
它和初始化数组时省略 len 不同,数组省略 len 时要写 ...,切片啥也不用写。

func main() {
	var a []string				 // 声明一个字符串切片
	var b = []int{1, 2, 3, 4, 5} // 声明一个整型切片,并初始化
	c := []bool{false, true}	 // 声明一个布尔型切片,并初始化
	fmt.Println(a)				 // []
	fmt.Println(b)				 // [1 2 3 4 5]
	fmt.Println(c)				 // [false, true]
}

基于数组创建

切片底层是数组,当底层数组不够的时候,切片就会扩容。

上面的定义是创建一个匿名数组,让切片指向这个匿名数组,下面是基于数组定义切片

identifier := array[start_with:end]
func main() {
	// 基于数组定义切片
	arr := [8]int{55, 56, 57, 58, 59, 60, 61, 62}
	b := arr[1:5]          // 其范围用数学表示为:[1,5) 从arr[1]取到arr[4]不包含arr[5]
	fmt.Println(b)         // [55 56 57 58 59]
	fmt.Printf("%T \n", b) // []int

	// 切片再次切片
	c := b[0:len(b)]       // len(b)为5,所以取了b[0]、b[1]、b[2]、b[3]、b[4],相当于复制一整个切片
	fmt.Println(c)         // [55 56 57 58 59]
	fmt.Printf("%T \n", c) // []int
}

直接定义切片指定数组定义切片的区别在于:

  1. 直接定义切片会引用一个匿名数组,指定数组定义切片会引用指定数组
  2. 直接定义切片长度=容量,指定数组定义切片长度和容量视具体情况

直接定义切片 ↓
直接定义切片

指定数组定义切片 ↓
指定数组定义切片
指定数组定义切片

容量指的是从接片第一个元素到底层数组的最后一个元素
例如上面第三张图中:

arr := [8]int{55, 56, 57, 58, 59, 60, 61, 62}
c := arr[3:6]

len(c)    // 3
cap(c)    // 5

使用make()创建

如果需要动态的创建一个切片,可以使用内置的make()函数

基本语法为:

make([]type, len, cap)

var identifier []type = make([]type, len, cap)
identifier := make([]type, len, cap)

type:切片数据类型、len:长度、cap:容量
cap 可以不填,默认和 len 相同

eg:

func main() {
	// make函数构造切片
	// make([]type, len, cap)

	d := make([]int, 4, 10) // 构造一个整型切片,填充5个元素,最大容量10
	fmt.Println(d)         // [0 0 0 0]
	fmt.Printf("%T \n", d) // []int
	fmt.Println(len(d))    // 4
	fmt.Println(cap(d))    // 10
}

动态就动态在于它能使用变量哈哈哈

func fn(a int, b int) []int {
    return make([]int, a, b)
}

切片是引用类型

判空

检查切片是否为空,不能用 s == nil,而是应该使用 len(s) == 0

切片是一种引用类型,当它被声明的时候,没有指向任何数组,包括匿名数组也没有,此时切片中指针为 nil

var s []int    // s == nil

当切片被初始化的时候,它就指向了一个数组,这时切片中的指针不为 nil

var s = []int{}    // s != nil

切片不能直接比较

切片是一种引用类型,我们不能用 == 操作符来判断两个切片是否含有全部相等元素。
切片唯一合法的比较操作是和 nil 比较。

一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。
但是我们不能说一个长度和容量都是0的切片一定是nil

var s1 []int         //len(s1)=0; cap(s1)=0; s1==nil
s2 := []int{}        //len(s2)=0; cap(s2)=0; s2!=nil
s3 := make([]int, 0) //len(s3)=0; cap(s3)=0; s3!=nil

所以要判断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == nil来判断。

切片的拷贝赋值

下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

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]
}

切片遍历

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

func main() {
	s := []int{1, 3, 5}

	for i := 0; i < len(s); i++ {
		fmt.Println(i, s[i])
	}

	for index, value := range s {
		fmt.Println(index, value)
	}
}

append()

Go 中的内建函数 append() 可以为切片动态添加元素,可以一次添加一个或多个元素。

append(slice, elem_arr_or_slice)

eg:

func main() {
	s := []int{1, 2, 3, 4, 5}   // [1 2 3 4 5]
    s = append(s, 11)			// [1 2 3 4 5 11]
    s = append(s, 12, 13, 14)   // [1 2 3 4 5 11 12 13 14]
}

append() 第二个参数也可以是另一个切片,不过记得加上 ...

func main() {
    s2 := []int{55, 56, 57}
    s = append(s, s2...)
}

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

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

// X 没必要
var s = []int{}
s = append(s, 1, 2, 3)
// X 没必要
var s = make([]int)
s = append(s, 1, 2, 3)

copy()

切片是引用类型,如果直接 s1 = s2 ,其实是将 s2 中的数组地址赋值给 s1 的指针,s1 修改的时候 s2 也会受影响。
要实现真正的复制,需要使用内建函数 copy() 进行复制。

copy(destSlice, srcSlice)

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

func main() {
    s1 := []int{1, 2, 3, 4}
    s2 := []int{12, 13, 14}
    copy(s1, s2)
	fmt.Println(s1)    // [12 13 14 4]
	fmt.Println(s2)    // [12 13 14]

	s3 := []int{1, 2, 3, 4}
	s4 := []int{12, 13, 14}
	copy(s4, s3)
	fmt.Println(s3)    // [1 2 3 4]
	fmt.Println(s4)    // [1 2 3]

    s5 := []int{1, 2, 3, 4}
	s6 := make([]int, 4)
	copy(s5, s6)
	fmt.Println(s5)    // [0 0 0 0]
	fmt.Println(s6)    // [0 0 0 0]

	s7 := []int{1, 2, 3, 4}
	s8 := make([]int, 4)
	copy(s8, s7)
	fmt.Println(s7)    // [1 2 3 4]
	fmt.Println(s8)    // [1 2 3 4]
	s8[0] = 100
	fmt.Println(s7)    // [1 2 3 4]
	fmt.Println(s8)    // [100 2 3 4]
}

删除

Go 并没有提供删除切片元素的方法,我们可以利用其本身的特性来删除元素
切片可以取自切片,那我们就将被删除元素之前的元素切下来,再把被删除元素之后的元素切下来,然后用append()拼接

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 = append(a[:index], a[index+1:]...)

总结

切片和数组之间有那么点像数据库中的 基本表和视图
基本表是存储数据定义和数据的,视图只存储数据定义。所以基本表改变时视图也改变。

数组是存储定义和数据的,切片只存储定义。所以数组元素改变时切片也改变。

这个例子可能不是很贴切,但联系一下这句话:数组是值类型,切片是引用类型

也就是说,数组是实实在在存数据的地方,切片只是对某个数组的引用,自己并没有数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TCP404

老板大方~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值