package main
import "fmt"
// Go 语言中的切片
/* 学习目标
1.为什么要使用切片
2.切片的定义
3.关于nil的认识
4.切片的循环遍历
5.基于数组定义切片
6.切片再切片
7.关语切片的长度和容量
8.切片的本质
9.使用make()函数构造切片
10.切片不能直接比较
11.切片是引用数据类型--注意切片的赋值拷贝
12.append()方法为切片添加元素
13.切片的扩容策略
14.使用copy()函数复制切片
15.从切片中删除元素
16.练习题
*/
func arraySum(x [4]int) int {
sum := 0
for _, v := range x {
sum += v
}
return sum
}
func main() {
// 1.为什么要使用切片
// 因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。例如:
a_ := [4]int{1, 2, 3, 4}
println("求和结果是:", arraySum(a_)) // 这个参数符合要求
// b := [5]int{1,2,3,4,5}
// println(arraySum(b)) // 这个直接报错,类型虽然正确,但是长度不匹配。b的长度是5,arraySum()接收的参数长度是4。
// arraySum()这个函数只能接收[4]int类型,其他的都不支持。所以传入长度为5的数组的时候就会报错。
/* 2.切片的定义
切片是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。
切片是一个 引用类型, 它的内部结构包括地址、长度和容量。
声明切片类型的基本语法: var name []T 其中name是变量名称,T是切片的元素类型。
2.1 声明切片类型,切片示例如下:
*/
var a []string // 声明一个字符串类型的切片
var b = []int{} // 声明一个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
fmt.Println(c == nil) //false
//fmt.Println(c == d) // invalid operation: c == d (slice can only be compared to nil)
/* 2.2 关于nil的认识
切片记住一句话,切片只能和nil进行比较。切片未赋值时默认值是nil(例如上面a的默认值就是nil),
一旦初始化,就不再是 nil 了!【例如上面的b被初始化了,fmt.Println(b == nil)的结果就是false】
2.2.1 每种类型对应的零值如下:
bool -> false
numbers -> 0
string -> ""
pointers -> nil
slice -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil
*/
/* 3.切片的循环遍历
切片的循环遍历和数组的循环遍历是一样的。可以使用for循环和for-range循环。
*/
// 3.1 for循环方法遍历
var aa = []string{"北京", "上海", "深圳"}
for i := 0; i < len(aa); i++ {
fmt.Println("for循环打印切片aa的结果:", aa[i])
}
// 3.2 for-range方法遍历
for index, value := range aa {
fmt.Println("for-range循环打印切片aa的结果:", index, value)
}
// 4.基于数组定义切片
// 由于切片的底层就是一个数组,所以我们可以基于数组定义切片。
aaa := [5]int{55, 56, 57, 58, 59}
bbb := aaa[1:4]
fmt.Println(bbb) // [56 57 58]
fmt.Printf("type of bbb is:%T \n", bbb) // type of bbb is:[]int
// 还支持如下方式
ccc := aaa[1:] // 从索引为1的下标开始一直取到末尾,包含末尾的值。
ddd := aaa[:4] // 从索引为0的下标开始取到索引为4的位置,不包含索引为4的值
eee := aaa[:] // 从头取到尾,取全部的值。
fmt.Println(ccc) // [56 57 58 59]
fmt.Println(ddd) // [55 56 57 58]
fmt.Println(eee) // [55 56 57 58 59]
// 5.切片再切片
// 除了基于数组得到切片,我们还可以通过切片来得到切片。
as := [...]string{"北京", "上海", "广州", "深圳", "成都", "重庆"}
ab := as[1:3]
fmt.Printf("结果:ab:%v type:%T len:%d cap:%d \n", ab, ab, len(ab), cap(ab)) // 结果:ab:[上海 广州] type:[]string len:2 cap:5
ac := ab[1:5]
fmt.Printf("结果:ac:%v type:%T len:%d cap:%d \n", ac, ac, len(ac), cap(ac)) // 结果:ac:[广州 深圳 成都 重庆] type:[]string len:4 cap:4
// 注意:对切片进行再切片时,索引不能超过原数组的长度,否则会出现索引越界的错误。
/* 6.关语切片的长度和容量
6.1 切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。
6.2 切片的长度就是它所包含的元素个数。
6.3 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
示例如下:
*/
ss := []int{2, 3, 5, 7, 11, 13}
fmt.Println(ss) // [2 3 5 7 11 13]
fmt.Printf("长度:%v 容量:%v \n", len(ss), cap(ss)) // 长度:6 容量:6
cc := ss[:2]
fmt.Println(cc) // [2 3]
fmt.Printf("长度:%v 容量:%v \n", len(cc), cap(cc)) // 长度:2 容量:6
dd := ss[1:3]
fmt.Println(dd) // [3 5]
fmt.Printf("长度:%v 容量:%v \n", len(dd), cap(dd)) // 长度:2 容量:5
/* 7.切片的本质
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度len和切片的容量cap
这个具体的图片说明需要到自己的资料里面去看。图片在这里不好弄,故省略。
*/
/* 8.使用make()函数构造切片
我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的make()函数。
格式是:make([]T,size,cap),其中
T:切片的元素类型
size:切片中元素的数量
cap:切片的容量
8.1 举例说明如下:
a := make([]int,2,10)
fmt.Println(a) // [0,0]
fmt.Println(len(a)) // 2
fmt.Println(cap(a)) // 10
上面代码中a的内部存储空间已经分配了10个,但实际上只用了2个。容量并不会影响当前元素的个数,
所以len(a)返回2,cap(a)则返回该切片的容量。
*/
/* 9.切片不能直接比较
我们不能使用==操作符来判断两个切片是否含有全部相等元素。切片之间比较的话,会有这个报错,
invalid operation: c == d (slice can only be compared to 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的结果来判断。
*/
/* 10.切片是引用数据类型,注意切片的赋值拷贝
下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。
s1 := make([]int,3) // [0 0 0]
s2 := s1
s2[0] = 100
fmt.Println(s1) // [100 0 0]
fmt.Println(s2) // [100 0 0]
*/
/* 11.append()方法为切片添加元素
Go 语言的内建函数append()可以为切片动态添加元素,每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。
当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行扩容,每次扩容,新扩容的容量是上一次容量的2倍,
此时该切片指向的底层数组就会更换。扩容操作往往发生在append()函数调用时,所以我们通常都需要用原变量接收append函数的返回值。
11.1 给切片追加元素的错误写法:
s3 := []int{1,2,3,4,5,6,7}
s3[6] = 8
fmt.Println(s3) // index out of range [6] with length 6
11.2 append()方法为切片追加元素的正确方法;
append()添加元素和切片扩容,例子如下:
var numSlice []int
for i := 0; i < 10; i++ {
numSlice = append(numSlice, i)
fmt.Printf("%v len:%d cap:%d ptr:%p \n", numSlice, len(numSlice), cap(numSlice), numSlice)
}
输出结果是:
[0] len:1 cap:1 ptr:0xc0000a8000
[0 1] len:2 cap:2 ptr:0xc0000a8040
[0 1 2] len:3 cap:4 ptr:0xc0000b2020
[0 1 2 3] len:4 cap:4 ptr:0xc0000b2020
[0 1 2 3 4] len:5 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc0000b8000
从上面的结果可以看出:
1.append()函数将元素追加到切片的最后并返回该切片。
2.切片numSlice的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍。
11.3 append()函数还可以一次性的追加多个元素。例如:
var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...) // 注意:当append里面的两个参数都是切片的话,最后一个切片这个地方有个...
fmt.Println(citySlice) // [北京 上海 广州 深圳 成都 重庆]
11.4 切片的追加切片
s1 := []int{100,200,300}
s2 := []int{400,500,600}
s3 := append(s1, s2...)
fmt.Println(s3) // [100 200 300 400 500 600]
12. 切片的扩容策略,记住新申请的容量是上一次容量的2倍就行。其他的仅作了解,具体的见学习文档即可。
*/
/* 13.使用copy()函数复制切片
首先我们来看一个问题:
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其实都是指向了同一个内存地址。a和b共用一个内存地址。修改b的同时a的值也会发生变化。同理,修改a的同时b的值也会发生变化。
!!!注意!!!,Go语言中内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,他们在内存中分别有独立的内存地址,两者之间互不影响。
copy()函数的格式:copy(destSlice, srcSlice []T)
其中:
1.srcSlice:数据来源切片
2.destSlice:目标切片
举个例子:
// 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]
*/
/* 14.从切片中删除元素
Go 语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。代码如下:
// 从切片中删除元素
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:]...)
*/
s1 := []int{100, 200, 300}
s2 := []int{400, 500, 600}
s3 := append(s1, s2...)
fmt.Println("切片的追加切片:", s3) // 切片的追加切片: [100 200 300 400 500 600]
// 15.练习题,请写出下面代码的输出结果。
var a3 = make([]string, 5, 10)
fmt.Println("a3的初始化结果是:", a3) // a3的初始化结果是: [ ]
for i := 0; i < 12; i++ {
a3 = append(a3, fmt.Sprintf("%v", i))
}
fmt.Println("输出a3的最终结果:", a3, "a3的长度是:", len(a3), "a3的容量是:", cap(a3))
// 输出a3的最终结果: [ 0 1 2 3 4 5 6 7 8 9 10 11] a3的长度是: 17 a3的容量是: 20
// *****************end****************
/* 上面代码的全部执行结果如下:
PS C:\Users\Zhaoshan.lu\go\DaDi> go run .\10.go
求和结果是: 10
[]
[]
[false true]
true
false
false
for循环打印切片aa的结果: 北京
for循环打印切片aa的结果: 上海
for循环打印切片aa的结果: 深圳
for-range循环打印切片aa的结果: 0 北京
for-range循环打印切片aa的结果: 1 上海
长度:6 容量:6
[2 3]
长度:2 容量:6
[3 5]
长度:2 容量:5
切片的追加切片: [100 200 300 400 500 600]
a3的初始化结果是: [ ]
输出a3的最终结果: [ 0 1 2 3 4 5 6 7 8 9 10 11] a3的长度是: 17 a3的容量是: 20
PS C:\Users\Zhaoshan.lu\go\DaDi>
*/
}