【GO语言卵细胞级别教程】09.切片的超能力(含习题)
目录
🥰微信公众号:【给点知识】分享小知识,快速成长,欢迎关注呀!(底部点击二维码)
🥰本项目演示代码仓库:https://gitee.com/gdzsfun/csdn-study-go 演示项目仓库
🥰本项目创建方式:【GO语言卵细胞级别教程】05.项目创建和函数讲解
🥰学习宗旨:活到老,学到老。
😍写作宗旨:致力于改变现有文章难读难懂问题。
今日分享诗句
切片是Go语言中的一项强大功能,它不仅能够灵活地操作数据,还能让你的代码达到令人瞩目的性能。本文将揭示切片的神奇之处,以及如何利用它们在开发中获得无限可能。从切片的定义到高级应用,我们将深入探讨切片与数组的区别,并分享一些独家技巧和最佳实践。无论你是新手还是经验丰富的开发者,本文都将帮助你释放切片的威力,让你的代码瞬间提升到一个全新的水平!准备好迎接切片的变革了吗?
1.概述
1.1 简介
切片是go中的一种数据类型,类似python中的列表,也是Golang中的一种动态所长度的数组。切片是一种动态长度的数据结构,可以看作是对底层数组的引用,提供了灵活的操作方式。
切片的底层使用使用了数组,
切片上层使用一个结构体用来记录切片的长度、容量、用来指向底层数组首元素的指针
1.2 为什么需要切片
- 固定长度的数组限制:数组在创建时需要指定固定的长度,这限制了数组在处理动态数据集合时的灵活性。而切片允许动态增长和缩减长度,解决了数组固定长度的限制。
- 内存管理和传递效率:切片内部维护了一个指向底层数组的指针,多个切片可以共享同一个底层数组。这意味着切片的创建和操作不需要复制整个数据集合,减少了内存占用和时间开销。同时,切片作为引用类型,通过引用传递,避免了不必要的数据复制,提高了传递效率。
- 动态数据集合的处理:在许多应用场景中,数据集合的大小在运行时是不确定的,例如读取文件、处理网络请求等。切片的动态长度和灵活操作使其成为处理动态数据集合的理想选择,无需提前确定大小,可以随需增加或减少元素。
- 与标准库和第三方库的兼容性:切片是 Go 语言中广泛使用的数据结构,与标准库和许多第三方库具有良好的兼容性。标准库中的许多函数和方法接受和返回切片类型,使用切片可以更好地与标准库和其他库进行交互,提高代码的可重用性和互操作性。
2.语法介绍
在 Go 语言中,切片是一种动态数组类型,它提供了方便的操作和管理底层数组的能力。切片有两个相关的属性:长度(length)和容量(capacity)。
- 长度(Length):切片的长度表示当前切片中实际存储的元素个数。它可以通过内置函数
len()
来获取。例如,对于一个切片s
,可以通过len(s)
获取其长度。切片的长度可以随着元素的增加或删除而动态变化。 - 容量(Capacity):切片的容量表示底层数组从切片的起始位置到底层数组末尾的元素个数。它可以通过内置函数
cap()
来获取。例如,对于一个切片s
,可以通过cap(s)
获取其容量。切片的容量在创建时就确定,并且在不重新分配底层数组的情况下不会改变。
切片的长度和容量之间有以下关系:
- 切片的长度不会超过其容量。即
len(s) <= cap(s)
。 - 切片的长度表示当前实际存储的元素个数,容量表示切片能够容纳的最大元素个数。
- 切片可以通过切片操作
s[a:b]
来截取部分元素,截取后的切片长度为b - a
,容量为底层数组末尾的元素个数。
当使用 make
函数创建切片时,可以指定切片的长度和容量。如果只指定长度而不指定容量,则切片的容量与长度相等。例如,slice := make([]int, 3, 5)
创建一个长度为 3、容量为 5 的切片。
注意:切片的长度和容量是动态变化的,当使用 append
函数向切片追加元素时,切片的长度会增加,当超过容量时,底层数组会自动扩容。因此,在使用切片时,要根据需要合理管理长度和容量,以避免不必要的内存分配和复制。
2.1 切片的定义
切片与数组较为相似,所以这里使用与数组对比的方式来学习
- 定义一个空的切片,和数组定义差不多,只不过不带长度
var slice1 []int
或
slice1 := []int
package mystudy
// 切片知识总结
import "fmt"
func DemoSlice2(){
fmt.Println("------切片基本知识------")
// 1.切片的定义
//(1)空切片
fmt.Println("------1定义一个空切片------")
var slice1 []int
fmt.Println(slice1)
}
// 输出结果
------切片基本知识------
------1定义一个空切片------
[]
- 定义一个初始默认值的切片
slice2 := []int{1,2,33,44,55}
package mystudy
// 切片知识总结
import "fmt"
func DemoSlice2(){
fmt.Println("------切片基本知识------")
//(2)定义一个初始值的切片
fmt.Println("------2.定义一个初始值的切片------")
slice2 := []int{1,2,33,44,55}
fmt.Println(slice2)
}
- 由数组转为切片
package mystudy
// 切片知识总结
import "fmt"
func DemoSlice2(){
fmt.Println("------切片基本知识------")
//(3)由数组转为切片
var array1 [10]int = [10]int{1, 2, 3, 5}
slice3 := array1[:3]
fmt.Printf("%T, %v", slice3, slice3)
fmt.Println()
}
- 使用make关键字实现
(1)使用make申请空间,make有三个参数 类型、长度、容量
这个长度就是切片的元素个数,容量就是这个切片申请的空间
容量必须要大于等于长度
容量会动态更新大小
package mystudy
// 切片知识总结
import "fmt"
func DemoSlice2(){
fmt.Println("------切片基本知识------")
//(4)使用make申请空间 make(类型,长度,容量)
slice4 := make([]int, 5, 6)
fmt.Println("使用mak创建时的初始值slice4:",slice4)
fmt.Printf("长度%v, 容量%v\n",len(slice4), cap(slice4))
slice4 = []int{1,2,3}
fmt.Printf("type:%T, value:%v\n", slice4, slice4)
fmt.Printf("长度%v, 容量%v\n",len(slice4), cap(slice4))
}
2.2切片基本使用
2.2.1遍历
- 使用for
slice4 := make([]int, 5, 6)
slice4 = []int{1,2,3}
for i:=0;i<len(slice4);i++{
fmt.Println(slice4[i])
}
- 使用for-range
slice4 := make([]int, 5, 6)
slice4 = []int{1,2,3}
for i,v := range slice4{
fmt.Println(i,v)
}
2.2.2切片的骚操作
- 切片赋值:使用下标赋值,只能赋值长度范围内的,否侧报错panic: runtime error: index out of range [4] with length 3
// 使用下标赋值,只能赋值长度范围内的
slice4 := []int{1,2,3}
slice4[1]= 1
- 使用方法append()增加元素:
(1)创建一个新的数组
(2)把老数组赋值给新数组
(3)新数组,增加新的值,然后再把数组付给新的变量引用
slice4 := make([]int, 5, 6)
slice4 = []int{1,2,3}
slice4[2] = 12
slice4 = append(slice4, 88,99,100, 101)
fmt.Println(slice4)
fmt.Printf("长度%v, 容量%v\n",len(slice4), cap(slice4))
// 输出结果
[1 2 12 88 99 100 101]
长度7, 容量8
- 使用=号赋值给新的变量:其实是引用,而不是真正的赋值,
slice1 := []int{1,2}
slice2 = slice1
slice2[1]=99
fmt.Println(slice1, slice2)
// 结果
[1 99] [1 99]
- 如何解决=号赋值时引用的问题,使用copy
slice5 := []int{1,2,3}
var slice6 = make([]int, 2)
copy(slice6, slice5)
fmt.Println(slice6, slice5)
slice6[1]=99
fmt.Println(slice6, slice5)
// 输出结果
[1 2] [1 2 3]
[1 99] [1 2 3]
- 一个切片使用append扩展另一个切片
slice7 := []int{7, 77}
slice8 := []int{8, 88}
slice7 = append(slice7, slice8...)
fmt.Println(slice7)
// [7 77 8 88]
2.3切片与数组
- 定义方式
数组定义 var shuzu1 [10]int
切片定义 var qiepian []int
- 遍历方式: 相同都是通过for for-range
- 取值方式:相同都是通过下标取值
- 索引问题
(1)切片如果越界了,编译阶段不会报错,运行时报错
(2)数组越界编译阶段会直接报错 - 获取长度和容量
数组和切片都可以通过len获取长度 cap获取容量,只不过,数组的容量和长度相同
slice7 := []int{7, 77}
slice8 := []int{8, 88}
slice7 = append(slice7, slice8...)
fmt.Println(slice7)
var shuzu = [10]int{1,2,}
fmt.Printf("数组长度%v, 数组容量%v\n",len(shuzu), cap(shuzu))
fmt.Printf("切片长度%v, 切片容量%v\n",len(slice7), cap(slice7))
// 输出结果
[7 77 8 88]
数组长度10, 数组容量10
切片长度4, 切片容量4
- 赋值
都可以通过下标赋值
aaa[1]=1
- =号复制
比如有切片a=b 数组a=b
(1)数组复制是真的申请了新的空间复制 a与b是不同的,互不影响,切片是引用,a变b也变
数组复制,必须要求两个数组长度一样、类型一样
var shuzu = [10]int{1,2,}
var shuzu2 [10]int
shuzu2 = shuzu
fmt.Println(shuzu2)
切片复制 申请一个就可以复制
var slice9 = make([]int, 2)
slice9 = slice7
fmt.Println(slice9)
(2)数组可以赋值给切片,切片无法赋值给数组
数组赋值给切片
slice9 = shuzu2[:]
3.敢不敢练一练
选择题和算法题,并附有答案供参考:
选择题:
-
下列哪个选项描述了切片的特性?
a) 固定长度
b) 可变长度
c) 只能存储整数类型
d) 只能存储字符串类型答案:b) 可变长度
-
切片的长度和容量之间的关系是:
a) 长度永远小于容量
b) 长度永远大于容量
c) 长度可能小于、等于容量
d) 长度和容量没有直接关系答案:c) 长度可能小于、等于容量
算法题:
-
给定一个整数切片
nums
,编写一个函数removeDuplicates
,将切片中重复的元素去除,返回去重后的切片长度。要求在原地修改切片,不使用额外的空间。
示例输入:[1, 1, 2, 2, 3, 4, 4, 5]
示例输出:5
示例切片变为:[1, 2, 3, 4, 5]
答案:
func removeDuplicates(nums []int) int { if len(nums) == 0 { return 0 } index := 0 for i := 1; i < len(nums); i++ { if nums[i] != nums[index] { index++ nums[index] = nums[i] } } return index + 1 }
-
给定一个整数切片
nums
和一个目标值target
,编写一个函数twoSum
,找到切片中两个数的索引,使得它们的和等于目标值。假设切片中一定存在这样的两个数,并且每个数只能使用一次。
示例输入:[2, 7, 11, 15]
,目标值target = 9
示例输出:[0, 1]
答案:
func twoSum(nums []int, target int) []int { numMap := make(map[int]int) for i, num := range nums { complement := target - num if j, ok := numMap[complement]; ok { return []int{j, i} } numMap[num] = i } return nil }