写点什么

【一 Go 到底】第二十九天 --- 切片入门

作者:指剑
  • 2022-10-29
    重庆
  • 本文字数:4574 字

    阅读完需:约 15 分钟

【一Go到底】第二十九天---切片入门

一、切片基本介绍

1.1 切片的基本概念

  1. 切片是驻足的一个引用,因此切片是引用类型,在进行传递的时候,遵守引用传递机制

  2. 切片的使用和数组类似,遍历切片、访问切片的元素以及求切片长度len(slice)都一样

  3. 切片长度可变,可以看作是一个动态数组

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 例中可以看到


  1. slice 是一个引用类型

  2. 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])
复制代码


  1. type 就是切片类型

  2. len 是切片大小

  3. 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 总结


  1. make 方式创建切片可以指定切片大小和容量

  2. 若没有给切片元素赋值,那么会使用默认值

  3. 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.不同方式的区别

三、切片遍历

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 注意事项及细节

  1. 切片初始化时 var slice = arr[startIndex:endIndex]说明:从 arr 数组 下标为 startIndex,取到下标为 endIndex 的元素(不含 arr[endIndex])。-----前闭后开

  2. 切片初始化时,仍然不能越界。范围在[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[:]


  1. cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。

  2. 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make-一个空间供切片来使用。 详见案例一

  3. 切片可以继续切片

  4. 用 append 内置函数,可以对切片进行动态追加。详见案例二切片 append 操作的底层原理分析:


  • 1)切片 append 操作的本质就是对数组扩容

  • 2) go 底层会创建一-下新的数组 newArr(安装扩容后大小

  • 3)将 slice 原来包含的元素拷贝到新的数组 newArr

  • 4) slice 重新引用到 newArr

  • 5)注意 newArr 是在底层来维护的,程序员不可见.

  • 6)案例演示说明


  1. 切片使用 copy 内置函数完成拷贝.详见案例三


  • 进行拷贝时,要求 copy()中的 2 个参数都必须是切片类型

  • 数据空间是相互独立的,互不影响


  1. make 创建的切片容量小于数组容量,可以进行 copy。详见案例四

  2. 切片是引用类型,在传递值时,遵循引用传递机制。详见案例五

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 简介

  1. string 底层是一个 byte 数组,因此 string 也可以进行切片处理

  2. string 是不可变的,也就说不能通过 str[0]= 'z'方式来修改字符串

  3. 如果需要修改字符串,可以先将 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))}
复制代码

6.2

发布于: 刚刚阅读数: 3
用户头像

指剑

关注

InfoQ签约作者 2022-07-13 加入

AWS社区建设者,AWS学生大使,微软学生大使,阿里云签约作者,Info Q签约作者,CSDN博客专家,华为云云享专家

评论

发布
暂无评论
【一Go到底】第二十九天---切片入门_Go_指剑_InfoQ写作社区