【go】切片

1 为什么要引入切片?

go数组的缺陷:声明时确定容量,无法自动扩容;值传递,无法处理引用传递的数据;
我们的需求:

  • 不需要声明容量,并且自动扩容支持增删等操作的一个数据结构
  • 其他语言里就有这样的东西,比如Python里的List,C++里的Vector,JAVA里的ArrayList。

2 切片的基本介绍

切片需要包括三个变量:

  1. 开头指针,指向了切片开头的地址
  2. len,标明了使用到的空间长度
  3. cap,表示总空间长度

切片操作主要包括

  1. 增,见下文切片新增元素
  2. 删,go没有实现这个操作,需要通过重复切片来实现
  3. 查改,涉及切片的遍历,切片的遍历与数组的遍历一样,都可以使用for或for range遍历

3 切片的定义与初始化

3.1 直接声明和初始化

定义1——var

var identifier []type :T为变量类型,切片不需要说明长度,可以在声明时直接赋值,如果不赋值,则该切片len及capacity均为0,且只能通过append函数追加数据

var a []int     //只声明,未分配空间,不能通过下标方式使用,此时,len=cap=0

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

定义2—— := 定义与初始化
  1. 直接初始化切片 :s :=[] int {1,2,3}

[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3

定义3——切片截取,通过已有数组创建

可以通过设置下限(startIndex)及上限(endIndex)来设置截取切片 [lower-bound:upper-bound] ,
说明:从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。

  • 初始化切片 s,是数组 arr 的引用。s := arr[:] 等价于 var slice = arr[0:len(arr)

  • 将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。s := arr[startIndex:endIndex]

s := arr[:]s := arr[startIndex:endIndex] 两者等效

  • 默认 startIndex 时将表示从 arr 的第一个元素开始。s := arr[:endIndex] 等价于 var slice = arr[0:end]
  • 默认 endIndex 时将表示一直到arr的最后一个元素。s := arr[startIndex:] 等价于var slice = arr[start:len(arr)]

a := [6]int{12,43,23,56,75,66} //a是个长度为6的数组,a[0]=12
b := a[1:3:5]
从a的下标1切到3,且不包含3,最大容量到下标5,此时,b=[43 23],len=2,cap=4,此时,与凭空创建不同的是,此时b没有用到的两个位置(cap-len=2)是有元素的,他们是56和75
b[3] = 111 //将会报错,因为b[3]虽然有东西,但len=2,只能访问b[0]和b[1]
b = b[0:4:4] //在b上重复切片,屁股往后挪,b=[43 23 56 75],len=4,cap=4
b[3] = 111 //不会报错了

实例如下:

package main

import "fmt"

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

func main() {
   /* 创建切片 */
   numbers := []int{0,1,2,3,4,5,6,7,8}  
   printSlice(numbers) 
   //len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]

   /* 打印原始切片 */
   fmt.Println("numbers ==", numbers)
   //numbers == [0 1 2 3 4 5 6 7 8]

   /* 打印子切片从索引1(包含) 到索引4(不包含)*/
   fmt.Println("numbers[1:4] ==", numbers[1:4])
   //numbers[1:4] == [1 2 3]

   /* 默认startIndex为 0*/
   fmt.Println("numbers[:3] ==", numbers[:3])
   //numbers[:3] == [0 1 2]

   /* 默认endIndex为 len(s)-1*/
   fmt.Println("numbers[4:] ==", numbers[4:])
   //numbers[4:] == [4 5 6 7 8]

   numbers1 := make([]int,0,5)
   printSlice(numbers1)
   //len=0 cap=5 slice=[]

   /* 打印子切片从索引  0(包含) 到索引 2(不包含) */
   number2 := numbers[:2]
   printSlice(number2)
   //len=2 cap=9 slice=[0 1]

   /* 打印子切片从索引 2(包含) 到索引 5(不包含) */
   number3 := numbers[2:5]
   printSlice(number3)
  //len=3 cap=7 slice=[2 3 4]
}

3.2 通过 make() 函数来创建切片

slice1 := make([]T, length, capacity)

var slice1 []type = make([]type) //不指定容量和长度

var b = make([]int)  //此时,len=cap=0

slice1 := make([]type, lenth) //不指定容量

var b = make([]int, 3)  //此时,len=cap=3,全初始化为0

slice1 := make([]T, length, capacity) // 指定长度与容量

var a = make([]int,3,5) //此时,len=3,cap=5,且a的前三个元素自动初始化为0

通过内置函数 make() 初始化切片slice1,[]int 标识为其元素类型为 int 的切片。

slice2 := make([]int,len,cap)

通过切片 s 初始化切片 s1。s := make([]int,len,cap)
通过内置函数 make() 初始化切片s,[]int 标识为其元素类型为 int 的切片。s :=make([]int,len,cap)

3.3 初始化常见错误

初始化常见错误1
var a []int                     //只声明,未分配空间,不能通过下标方式使用
a[0] = 1                        //将报错
fmt.Println(nil == a)           //将输出true,说明只声明时,a相当于nil

在Go语言中,声明一个切片(slice)时,需要使用make()函数或者字面量语法进行初始化,否则切片的底层数组为nil,无法进行任何操作,因此会报错。
对于你以上的代码,a是一个切片,但是没有初始化。因此,当你尝试给a中的第一个元素a[0]赋值时,Go语言会返回一个“Index out of range”的错误,因为a的长度为0,没有任何元素。
可以a := make([]int, 1) a[0] = 1
或者使用字面量语法进行初始化:a := []int{1}
这样就可以给a中的第一个元素赋值了。

初始化常见错误2
var b = []int{}                  //声明并分配空间,len=cap=0
b[0] = 1                        //将报错

在Go语言中,使用字面量语法声明一个空切片时,切片的底层数组被分配了内存空间,但是长度为0。因此,当你尝试给空切片b中的第一个元素b[0]赋值时,Go语言会返回一个“Index out of range”的错误,因为b没有任何元素,长度为0,无法访问下标为0的元素。
如果你需要在空切片中添加元素,可以使用append()函数。例如:b := []int{} b = append(b, 1)

4 切片的赋值(增)–append()与copy()

4.1 len() 和 cap() 函数

切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

package main

import "fmt"

func main() {
   var numbers = make([]int,3,5)

   printSlice(numbers)
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
len=3 cap=5 slice=[0 0 0]

4.2 空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0,实例如下:

package main

import "fmt"

func main() {
   var numbers []int

   printSlice(numbers)

   if(numbers == nil){
      fmt.Printf("切片是空的")
   }
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
len=0 cap=0 slice=[]
切片是空的

4.3 append()

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。

package main

import "fmt"

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

func main() {
   var numbers []int
   printSlice(numbers)
   //len=0 cap=0 slice=[]

   /* 允许追加空切片 */
   numbers = append(numbers, 0)
   printSlice(numbers)
   //len=1 cap=1 slice=[0]

   /* 向切片添加一个元素 */
   numbers = append(numbers, 1)
   printSlice(numbers)
   //len=2 cap=2 slice=[0 1]

   /* 同时添加多个元素 */
   numbers = append(numbers, 2,3,4)
   printSlice(numbers)
   //len=5 cap=6 slice=[0 1 2 3 4]
   	var s []int{100}
	ss := append(s,numbers...)   //append的另一种用法,将C解开后,添加到a中

   /* 创建切片 numbers1 是之前切片的两倍容量*/
   numbers1 := make([]int, len(numbers), (cap(numbers))*2)
   /* 拷贝 numbers 的内容到 numbers1 */
   copy(numbers1,numbers)
   printSlice(numbers1)  
   //len=5 cap=12 slice=[0 1 2 3 4]
}

如果新增后cap装不下了,就必须自动扩容;

4.4 切片的自动扩容 & 切片在内存中的形式

切片 append 操作的底层原理分析:
切片 append 操作的本质就是对数组扩容
go 底层会创建一下新的数组 newArr(安装扩容后大小)
将 slice 原来包含的元素拷贝到新的数组 newArr
slice 重新引用到 newArr
注意 newArr 是在底层来维护的,程序员不可见.

  • 当容量到达原来的上限时,直接把原来的容量翻倍,重新申请一块内存,并把数据复制过去。(注意,此时内存位置发生了变化!!)
  • 如果一次添加的很多,翻倍也不够怎么办?先翻倍,然后一直+2,直到符合要求
  • cap已经很大了,比如1024,再翻倍要很久才能填满,浪费空间。那就不翻倍,一次添加原来的256(即1024的1/4),这种说法在一定范围内是正确的

https://studygolang.com/articles/34462

5 删

var a = []string{"A","B","C","D","E"}
a = append(a[:2],a[3:]...)
fmt.Print(a)            //输出[A B D E]

//要删除下标为x的元素,则
x := 2
a=append(a[:x],a[x+1:]...)

关于...

//三个点可以用在:
var a = […]int{1,2} //自动推导数组长度
append(a,b…) //解开切片
func append(slice []Type, elems …Type) []Type //接受不定长参数

6 复制

  /* 创建切片 numbers1 是之前切片的两倍容量*/
   numbers1 := make([]int, len(numbers), (cap(numbers))*2)
   /* 拷贝 numbers 的内容到 numbers1 */
   copy(numbers1,numbers)
   printSlice(numbers1)  
   //len=5 cap=12 slice=[0 1 2 3 4]

同样,简单赋值时,复制的也是指针:

var a = []int{9,99,999}
b := a
b[0] = 1
fmt.Print(a)    //a变成了[1 99 999]

但有时,我们不想让复制后的指针修改原来的值,此时我们需要使用到copy函数,将原切片底层数组的值复制到新的地方,之后的操作就在新的地方进行了:

var a = []int{9,99,999}
b := make([]int,3)
copy(b,a)
b[0] = 1
fmt.Print(a)    //a还是[9 99 999]

7 切片传参(改)

之前我们学到过,数组作为参数传递给函数,传递参数的形式是值传递,也就是说函数内部会自己开一块空间,复制这个传进来的数组,之后的操作都在自己的空间进行,不会修改原来的数组:

func change(a [3]int){
   a[0] = 1
}
func main() {
   var a = [...]int{9,99,999}
   change(a)
   fmt.Print(a)     //a还是[9 99 999]
}

但我们使用切片,就可以改变原始的内容,因为切片里存的是指针,传过去的时候,函数内部也会自己开一块空间,复制这个传进来的切片,但复制前后,切片内部存的指针指向的是同一块地址,因此函数内部使用自己复制出来的切片进行操作时,也会影响到原始的切片

func change(a []int){
   a[0] = 1
}
func main() {
   var a = []int{9,99,999}
   change(a)
   fmt.Print(a)     //a变成了[1 99 999]
}

8 切片的遍历

切片的遍历

切片的遍历和数组一样,也有两种方式

  1. for 循环常规方式遍历
    
  2. for-range 结构遍历切片
    

string与slice

  1. string 底层是一个 byte 数组,因此 string 也可以进行切片处理 案例演示
	str := "hello@atguigu"
	//使用切片获取到 atguigu
	slice := str[6:] 
	fmt.Println("slice=", slice)
  1. tring 和切片在内存的形式,以 “abcd” 画出内存示意图
    在这里插入图片描述

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

string是不可变的,也就说不能通过 str[0] = 'z' 方式来修改字符串
str[0] = 'z' [编译不会通过,报错,原因是string是不可变]
  1. 如果需要修改字符串,可以先将 string -> []byte / 或者 []rune -> 修改 -> 重写转成 string
//如果需要修改字符串,可以先将string -> []byte / 或者 []rune -> 修改 -> 重写转成string
// "hello@atguigu" =>改成 "zello@atguigu"
	 arr1 := []byte(str)
	 arr1[0] = 'z'
	 str = string(arr1)
	 fmt.Println("str=", str)
  1. 我们转成[]byte后,可以处理英文和数字,但是不能处理中文
// 原因是 []byte 字节来处理 ,而一个汉字,是3个字节,因此就会出现乱码
// 解决方法是 将  string 转成 []rune 即可, 因为 []rune是按字符处理,兼容汉字

arr1 := []rune(str)
arr1[0] = '北'
str = string(arr1)
fmt.Println("str=", str)

参考
【1】https://studygolang.com/articles/34462
【2】尚硅谷
【3】https://www.runoob.com/go/go-slice.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值