持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第29天,点击查看活动详情
一、切片基本介绍
1.1 切片的基本概念
- 切片是驻足的一个引用,因此切片是引用类型,在进行传递的时候,遵守引用传递机制
- 切片的使用和数组类似,
遍历切片、访问切片的元素以及求切片长度len(slice)都一样 - 切片长度可变,可以看作是一个动态数组
1.2 基本语法
var 变量名 []类型
// 例如
var arr[] int
1.3 快速上手案例
package main
import "fmt"
func main() {
// 切片基本使用
var intArr [5]int = [...]int{11, 22, 33, 44, 55}
// 声明/定义一个切片
// slice := intArr[1:3]
// 1.slice 是切片名称,
// 2. intArr[1:3] 表示 slice 引用到intArr这个数组的第二个元素到第三个元素
// 3. 引用intArr数组的起始下标为 1,最后的下标为3 (不包含3)
slice := intArr[1:3]
// intArr = [11 22 33 44 55]
fmt.Println("intArr = ", intArr)
// slice的元素 = [22 33]
fmt.Println("slice的元素 = ", slice)
// slice元素个数 = 2
fmt.Println("slice元素个数 = ", len(slice))
// slice容量 = 4
// 容量是动态变化的,一般为长度的两倍
fmt.Println("slice容量 = ", cap(slice))
// 查看地址
// intArr[1]的地址是 = 0xc00000a338
fmt.Printf("intArr[1]的地址是 = %p \n", &intArr[1])
// slice[0]]的地址是 = 0xc00000a338
fmt.Printf("slice[0]]的地址是 = %p \n", &slice[0])
}
1.4 切片在内存中的形式
从1.3例中可以看到
- slice是一个引用类型
- slice从底层来说,是一个数据结构(struct结构体)
type slice struct{
ptr *[2] int
len int
cap int
}
二、切片的使用
2.1 方式一
先定义切片,再引用数组(如1.3)
2.2 方式二
通过make创建切片
2.2.1 基本语法
var 切片名 []type = make([], len, [cap])
- type就是切片类型
- len是切片大小
- cap为切片容量(可选参数)
2.2.2 make 快速上手案例
package main
import "fmt"
func main() {
// make 创建切片
// 对于切片,必须 make 使用
// 如果不make就使用,那么数组长度为0,且为空
var slice []float64 = make([]float64, 5)
// slice 切片 = [0 0 0 0 0]
fmt.Println("slice 切片 = ", slice)
// 对切片中元素赋值
slice[1] = 22
slice[4] = 55
// slice 赋值后 = [0 22 0 0 55]
fmt.Println("slice 赋值后 = ", slice)
}
2.2.2 总结
- make方式创建切片可以指定切片大小和容量
- 若没有给切片元素赋值,那么会使用默认值
- make方式创建的切片对应的数组由make维护,对外不可见,只能通过slice去访问各个元素
2.3 方式三
定义切片,直接指定数组,类似make
2.3.1 语法
var 切片名 []类型 = []类型{"值1","值2"......}
// 例如
var slice []string = []string{"M1","M2","mmmm",.....}
2.3.2 快速上手案例
package main
import "fmt"
func main() {
// 方式三 直接指定具体值
var strSlice []string = []string{"m1", "M2", "Mkie", "Tim"}
// 查看切片内容 大小 容量
// strSlice = [m1 M2 Mkie Tim]
fmt.Println("strSlice = ", strSlice)
// strSlice 大小 = 4
fmt.Println("strSlice 大小 = ", len(strSlice))
// strSlice 容量 = 4
fmt.Println("strSlice 容量 = ", cap(strSlice))
}
3.不同方式的区别
| 方式 | 区别 |
|---|---|
| 方式一 | 直接引用数组,数组事先存在,可见 |
| 方式二 | make创建,make创建一个数组,由切片底层创建,不可见 |
三、切片遍历
for 和 for-range遍历
package main
import "fmt"
func main() {
// 定义数组
var arr01 [5]int = [...]int{11, 22, 33, 44, 55}
// 定义切片
intSlice := arr01[1:4]
for i := 0; i < len(intSlice); i++ {
// intSlice[0] = 22
// intSlice[1] = 33
// intSlice[2] = 44
fmt.Printf("intSlice[%v] = %v\n", i, intSlice[i])
}
//***********************************************************
for i, v := range intSlice {
//intSlice[0] = 22
// intSlice[1] = 33
// intSlice[2] = 44
fmt.Printf("intSlice[%v] = %v\n", i, v)
}
}
四、切片注意事项、细节
4.1 注意事项及细节
- 切片初始化时var slice = arr[startIndex:endIndex]
说明:从arr数组 下标为startIndex,取到下标为endIndex的元素(不含arr[endIndex])。-----前闭后开 - 切片初始化时,仍然不能越界。范围在[0-len(arr)] 之间,但是可以动态增长.
- var slice = arr[0:end]可以简写var slice = arr[:end]
- var slice = arr[start:len(arr)]可以简写: var slice = arr[start:]
- var slice = arr[0:len(arr)]可以简写: var slice = arr[:]
- cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
- 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者make-一个空间供切片来使用。 详见案例一
- 切片可以继续切片
- 用append内置函数,可以对切片进行动态追加。详见案例二 切片append操作的底层原理分析:
- 1)切片append操作的本质就是对数组扩容
- 2) go底层会创建一-下新的数组newArr(安装扩容后大小
- 3)将slice原来包含的元素拷贝到新的数组newArr
- 4) slice重新引用到newArr
- 5)注意newArr是在底层来维护的,程序员不可见.
- 6)案例演示说明
- 切片使用copy内置函数完成拷贝.详见案例三
-
- 进行拷贝时,要求copy()中的2个参数都必须是切片类型
-
- 数据空间是相互独立的,互不影响
- make创建的切片容量小于数组容量,可以进行copy。详见案例四
- 切片是引用类型,在传递值时,遵循引用传递机制。详见案例五
4.2 案例分析
4.2.1 案例一 make
package main
import "fmt"
func main() {
// 定义原始数组
var intArr [5]int = [...]int{11, 22, 33, 44, 55}
// 定义切片
intSlice01 := intArr[1:4]
fmt.Println("intSlice01 = ", intSlice01)
// 切片再切
intSlice02 := intSlice01[1:3]
fmt.Println("intSlice02 = ", intSlice02)
// 重新对intSlice02中元素赋值
intSlice02[1] = 111
// 因为intArr,intSlice01,intSlice02指向的数据空间都是同一个
// 所以 intSlice02[1]更改会导致其他的都更改
//intArr 更改后 = [11 22 33 111 55]
// intSlice01 更改后 = [22 33 111]
// intSlice02 更改后 = [33 111]
fmt.Println("intArr 更改后 = ", intArr)
fmt.Println("intSlice01 更改后 = ", intSlice01)
fmt.Println("intSlice02 更改后 = ", intSlice02)
}
4.2.2 案例二 append
package main
import "fmt"
func main() {
// 定义数组
arr := [5]int{11, 22, 33, 44, 55}
// 定义切片
slice := arr[:]
// 查看切片
// slice = [11 22 33 44 55]
fmt.Println("slice = ", slice)
// 追加具体元素
slice = append(slice, 100, 200, 300)
// appended slice = [11 22 33 44 55 100 200 300]
fmt.Println("appended slice = ", slice)
// 用append将 slice 追加给 slice
// 可以用别的切片,且只能是切片,不能用数组
// 这里的 slice... 三个点是固定语法
slice = append(slice, slice...)
// 查看
// double append slice = [11 22 33 44 55 100 200 300 11 22 33 44 55 100 200 300]
fmt.Println("double append slice = ", slice)
}
4.2.4 案例三 copy
package main
import "fmt"
func main() {
// 定义数组
arr := [5]int{11, 22, 33, 44, 55}
// arr = [11 22 33 44 55]
fmt.Println("arr = ", arr)
// 切片
slice01 := make([]int, 10)
// slice01 = [0 0 0 0 0 0 0 0 0 0]
fmt.Println("slice01 = ", slice01)
// 将arr的数据拷贝刀 slice01
// 只能是切片类型的操作 slice01 和 arr[:]
copy(slice01, arr[:])
// coppied slice01 = [11 22 33 44 55 0 0 0 0 0]
fmt.Println("coppied slice01 = ", slice01)
}
4.2.5 案例四 copy
package main
import "fmt"
func main() {
// 定义数组
intArr := []int{10, 20, 30, 40, 50}
// make创建切片
intSlice := make([]int, 1)
// 查看原始数组和切片
// intArr = [10 20 30 40 50]
// intSlice = [0]
fmt.Println("intArr = ", intArr)
fmt.Println("intSlice = ", intSlice)
// 拷贝
copy(intSlice, intArr)
// 查看
// copied intSlice = [10]
fmt.Println("copied intSlice = ", intSlice)
}
4.2.6 案例五
package main
import "fmt"
func sliceTest(slice []int) {
// 会直接改变实参
slice[0] = 1111
}
func main() {
slice01 := []int{}
arr := []int{11, 22, 33, 44, 55}
slice01 = arr[:]
slice02 := slice01
slice02[0] = 100
// arr = [100 22 33 44 55]
// slice01 = [100 22 33 44 55]
// slice02 = [100 22 33 44 55]
fmt.Println("arr = ", arr)
fmt.Println("slice01 = ", slice01)
fmt.Println("slice02 = ", slice02)
// 定义测试函数切片
intSlice := []int{1, 2, 3, 4, 5}
sliceTest(intSlice)
// after func intSlice = [1111 2 3 4 5]
fmt.Println("after func intSlice = ", intSlice)
}
五、string和slice关系
5.1 string简介
- string底层是一个byte数组,因此string也可以进行切片处理
- string是不可变的,也就说不能通过str[0]= 'z'方式来修改字符串
- 如果需要修改字符串,可以先将string -> []byte /或者[]rune-> 修改->重写转成string.
5.2 string 案例演示
5.2.1 案例一 string切片
package main
import "fmt"
func main() {
// string 底层是一个byte 数组,可以进行切片操作
// 声明字符串
str := "hello,golang"
// 声明slice,用切片获取 逗号后面的内容
slice := str[6:]
// 打印查看
// slice = golang
fmt.Println("slice = ", slice)
}
5.2.2 案例二 修改字符串
package main
import "fmt"
func main() {
// string 底层是一个byte 数组,可以进行切片操作
// 声明字符串
str := "hello,golang"
// 声明slice,用切片获取 逗号后面的内容
slice := str[6:]
// 打印查看
// slice = golang
fmt.Println("slice = ", slice)
// 修改字符串,使用[]byte
// 可以处理英文和数字,但是无法处理中文
// byte按字节计算,一个汉字占用三个字节
strSlice := []byte(str)
strSlice[0] = 'H'
str = string(strSlice)
// []byte 后的 str = Hello,golang
fmt.Println("[]byte 后的 str = ", str)
// 使用[]rune
// 将string转成 []rune , []rune按字符进行处理,兼容汉字
new_strSlice := []rune(str)
// []rune进行转换
new_strSlice[0] = '嗨'
str = string(new_strSlice)
// []rune 处理后的 str = 嗨ello,golang
fmt.Println("[]rune 处理后的 str = ", str)
}
六、切片练习
6.1 斐波那契数列
说明:编写一个函数fbn(n int) ,要求完成
- 1)可以接收一个n int
- 2)能够将斐波那契的数列放到切片中
- 3)提示, 斐波那契的数列形式:
arr[0]= 1; arr[1]= 1; arr[2]=2; arr[3]= 3; arr[4]=5; arr[5]=8
package main
import "fmt"
func fbn(n int) []uint64 {
// 声明切片
fcSlice := make([]uint64, n)
fcSlice[0] = 1
fcSlice[1] = 1
// for 循环实现
for i := 2; i < n; i++ {
fcSlice[i] = fcSlice[i-1] + fcSlice[i-2]
}
return fcSlice
}
func main() {
fmt.Println(fbn(5))
}