go语言切片slice

一、基本介绍

  1. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用类型的机制
  2. 切片的使用和数组类似,如遍历切片,访问切片的元素和求切片长度len(slice)都一样。
  3. 切片的长度是可以变化的,因此切片是一个可以动态变化的数组

二、切片的定义

var 切片名 []类型
比如: var a []int

三、基本使用

  1. 通过引用一个已声明好的数组
package main

import (
	"fmt"
)

func main() {
	intArr := [...]int{0, 1, 2, 3, 4, 5, 6}
	slice := intArr[1:6]
	fmt.Printf("slice的类型为%T,slice=%v\n", slice, slice)
	fmt.Printf("slice的长度=%v\n", len(slice))
	fmt.Printf("slice的容量=%v\n", cap(slice)) // 切片的容量是可以动态改变的
}

输出结果:

slice的类型为[]int,slice=[1 2 3 4 5]
slice的长度=5
slice的容量=6

说明:

  • slice是一个切片
  • intArr[1:6]表示引用到intArr这个数组
  • 引用intArr数组的起始下标为1,最后的下标为3(但是不包含3)
  1. 通过make来创建切片
    基本语法:var 切片名 []type = make([]type,len,cap)
    参数说明:type就是数据类型,len为长度,cap为容量(可选),cap不小于len
package main

import (
	"fmt"
)

func main() {

	var slice []int = make([]int, 5, 10)
	// slice必须make后才能使用
	slice[0] = 1
	slice[4] = 9
	fmt.Printf("slice的类型为%T,slice=%v\n", slice, slice)
	fmt.Printf("slice的长度=%v\n", len(slice))
	fmt.Printf("slice的容量=%v\n", cap(slice))
}

输出结果:

slice的类型为[]int,slice=[1 0 0 0 9]
slice的长度=5
slice的容量=10

通过上面的总结:

  • 通过make方式创建切片可以指定切片的大小和容量
  • 如果没有给切片的各个元素赋值,那么就会使用默认值,
  • 通过make方式创建的切片对应的数组,是由make底层维护,对外不可见,即只能通过slice去访问各个元素。
  1. 直接就指定具体数组,使用原理类似make方式
package main

import (
	"fmt"
)

func main() {

	var slice []string = []string{"jake", "tom", "marry"}
	fmt.Printf("slice的类型为%T,slice=%v\n", slice, slice)
	fmt.Printf("slice的长度=%v\n", len(slice))
	fmt.Printf("slice的容量=%v\n", cap(slice))
}

输出结果:

slice的类型为[]string,slice=[jake tom marry]
slice的长度=3
slice的容量=3

第一种方式和第二种方式的区别:

  • 方式一是直接引用数组,这个数组是事先存在的,程序员是可见的
  • 方式二是通过make来创建切片,make也会创建一个数组,是由切片在底层进行维护的,程序员是看不见的,make创建切片的内存示意图如下:
    go语言切片slice

四、切片的内存示意图

package main

import (
	"fmt"
)

func main() {
	intArr := [...]int{0, 1, 2}
	slice := intArr[1:2]
	fmt.Printf("slice的类型为%T,slice=%v\n", slice, slice)
	fmt.Printf("slice的长度=%v\n", len(slice))
	fmt.Printf("slice的容量=%v\n", cap(slice))
}

输出结果:

slice的类型为[]int,slice=[1]
slice的长度=1
slice的容量=2
intArr[1]的地址=0xc0000124e8
slice[0]的地址=0xc0000124e8
slice的地址=0xc0000044a0

以上代码的内存分布图如下:
go语言切片slice

从上面的内存图可以总结出以下几点:

  1. slice的确是一个引用类型
  2. slice从底层来说,其实就是一个struct结构体
    type slice struct{
    ptr *[1]int
    len int
    cap int
    }

五、注意细节

  1. 切片初始化时 var slice = arr[startIndex,endIndex]
    说明:从数组arr下标为startIndex,取到下标为endIndex的元素(不含arr[endIndex])
    切片初始化化时,仍然不能越界,范围在0~len(arr)之间,但是可以动态增长
var slice = arr[0:endIndex] 可以简写为var slice = arr[:endIndex]
var slice = arr[startIndex:len(arr)] 可以简写为var slice = arr[startIndex:]
var slice = arr[0:len(arr)] 可以简写为var slice = arr[:]
  1. cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素
  2. 切片定义完之后,还不能使用,因为本身是一个空的,需要让其引用到一个数组或者make一个空间供其使用。
  3. 切片可以继续切片
package main

import (
	"fmt"
)

func main() {

	var slice []string = []string{"jake", "tom", "marry"}
	slice2 := slice[1:3]
	fmt.Printf("slice2的类型为%T,slice2=%v\n", slice2, slice2)
	fmt.Printf("slice2的长度=%v\n", len(slice2))
	fmt.Printf("slice2的容量=%v\n", cap(slice2))
}

输出结果:

slice2的类型为[]string,slice2=[tom marry]
slice2的长度=2
slice2的容量=2
  1. 用append内置函数,可以对切片进行动态追加
package main

import (
	"fmt"
)

func main() {

	var slice []string = []string{"jake", "tom", "marry"}
	slice = append(slice, "Lucy", "Lily")
	fmt.Printf("slice追加后=%v\n", slice)
	var slice2 []string = []string{"jake", "tom", "marry"}
	// 可以追加切片
	slice = append(slice, slice2...)
	fmt.Printf("slice追加切片slice2后=%v\n", slice)
}

输出结果:

slice追加后=[jake tom marry Lucy Lily]
slice追加切片slice2后=[jake tom marry Lucy Lily jake tom marry]

切片append操作的原理分析:

  • 切片append操作的本质就是对数组扩容
  • go底层会创建一个新的数组newArr(安装扩容后大小)
  • 将slice原来包含的元素拷贝到新的数组newArr
  • slice重新引用到newArr
  • 注意newArr是底层来维护的,程序员不可见。
  1. 使用copy内置函数来实现切片的拷贝操作
package main

import (
	"fmt"
)

func main() {

	var slice []int = []int{1, 2, 3, 4, 5}
	slice2 := make([]int, 10)
	copy(slice2, slice)
	fmt.Printf("slice2=%v\n", slice2)
}

输出结果:

slice2=[1 2 3 4 5 0 0 0 0 0]

说明:

  • copy(para1,para1)参数的数据类型是切片
  • 按照上面的代码来看,slice和slice2的数据空间是独立的,相互不影响。
  1. 切片是引用类型,所以在传递时,遵守引用传递机制
package main

import (
	"fmt"
)

func main() {

	var arr [5]int = [5]int{1, 2, 3, 4, 5}
	slice := arr[:]
	slice2 := slice
	slice2[0] = 100
	fmt.Printf("slice2=%v\n", slice2)
	fmt.Printf("slice=%v\n", slice)
	fmt.Printf("arr=%v\n", arr)
}

输出结果:

slice2=[100 2 3 4 5]
slice=[100 2 3 4 5]
arr=[100 2 3 4 5]

分析:
切片slice和切片slice2都是引用到数组arr

六、string和slice
  1. string底层是一个byte数组,本身是引用这个byte数组的切片 ,因此string也可以进行切片处理。
package main

import (
	"fmt"
)

func main() {

	str := "www.baidu.com"
	slice := str[10:]
	fmt.Printf("slice=%v\n", slice)
}

输出结果:

slice=com
  1. string是不可变的,也就是说不能通过str[0]='z'方式来修改字符串
  2. 如果需要修改字符串,可以先将string->[]byte或者[]rune->修改后重新转成string
上一篇:Golang 需要避免踩的 50 个坑1


下一篇:JS中的.map()方法-取数组,.sort()方法-数据排序,.slice()方法-数据切片的使用