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

139 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第29天,点击查看活动详情

一、切片基本介绍

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

方式区别
方式一直接引用数组,数组事先存在,可见
方式二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 注意事项及细节

  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内置函数完成拷贝.详见案例三
    1. 进行拷贝时,要求copy()中的2个参数都必须是切片类型
    1. 数据空间是相互独立的,互不影响
  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