slice

133 阅读3分钟

本节要掌握的问题

  • 遍历切片有哪几种方式?如何遍历一个切片?
  • 切片初始化的方式有哪几种?有什么区别?
  • 切片扩容的底层原理
  • copy切片的注意点
  • string和slice

一.切片初始化

切片是引用类型,在赋值、值传递等操作时,它不像数组一样需要拷贝数据,因此它的执行效率是高于数组的。

1)变量声明
 使用变量声明得到的变量都会初始化为对应类型的零值,因为切片是引用类型,所以初始化为nil。也就是说变量声明得到的切片并不指向任何空间。
2)字面量

s1 := []int{}//空切片
s2 := []int{1,2,3}

 通过字面量得到的空切片与变量声明得到的切片有什么区别呢? image.png
 如图可以看出使用字面量获得的切片经过了内存分配,其值不再是nil。
 s2的长度和容量都是3。字面量声明获得的切片其长度等于容量。
3)使用内置函数make()

  • 如果没有给元素赋初始值,则会自动初始化为对应类型的0值。
  • 通过make创建的切片对应的数组不可见,由make底层维护。

4)从切片和数组中切取
 定义一个数组,然后去引用

var array [5]int = [...]int{1, 2, 3, 4, 5}
var slice = array[1:2]

fmt.Printf("len(slice) = %d\n", len(slice))
fmt.Printf("cap(slice) = %d\n", cap(slice))

 此方法得到的切片其长度和容量分别是多少呢?

image.png

二、切片注意事项和细节

  • 切片初始化时,仍然不能越界。范围在[0-len(slice)]之间,但可以动态增长。例如:

image.png
这样会报访问越界的错误。

image.png

  • 切片如果不标明范围,如:[:],则使用默认值:[0:len(slice)]。
  • 切片定义完后还不能使用,因为本身是空的,需要让其引用到一个数组或者make一个空间供切片使用。

否则就会报访问越界的错误。如图:

image.png

image.png

三、append扩容

var slice []int = []int{1, 2, 3}
//1.直接追加具体的元素
slice = append(slice, 4, 5, 6)

//2.将切片追加给切片
slice = append(slice,slice...)

为什么要将append赋值给slice呢?我们先来看一下append()向slice添加一个元素的实现步骤:

  • 如果slice的容量够用,则将新元素追加进去,slice.len++,返回原slice;
  • 如果slice的容量不够,则触发扩容操作,扩容实际上是重新分配一块更大的空间,然后将原slice的数据拷贝进新slice。
  • 将新元素追加到新slice,slice.len++,返回新的slice。

 下列代码输出什么?

s := make([]int, 2, 3)
s[0] = 77
s[1] = 88
s2 := append(s, 99)
s3 := append(s2, 00)
fmt.Println(s)
fmt.Println(s2)
fmt.Println(s3)
fmt.Println(&s[0])
fmt.Println(&s2[0])
fmt.Println(&s3[0])

image.png

 由此可见,append会返回一个新的slice对象,如果原slice的底层数组容量不够用,就会换一个底层数组来使用。

copy

  • copy参数的数据类型都是切片。
  • 拷贝和被拷贝切片的数据空间相互独立,互不影响。不是说你拷贝后它俩就指向同一块内存空间了。

看一段代码,判断输出是什么:

var a[]int = []int{1,2,3,4,5}
var slice = make([]int,1)
fmt.Println(slice)//[0]
copy(slice,a)
fmt.Println(slice)//[1]

这是由于拷贝过程不会发生扩容,切片拷贝多少由最小的切片决定。

string和slice

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

image.png

  • string是不可变的。

image.png

  • 如果非要去修改,可以将string转换成[]byte或者[]rune修改完后转换回string。

image.png 第一张图会报错,为什么呢?因为中文字符占三个字节,也就是说arr1[0]能存放一个自己,三个字节的“北”是硬塞不进去的,因此就报错了。

image.png

遍历切片

//1.for循环遍历
var arr [5]int = [...]int{1, 2, 3, 4, 5}
slice := arr[1:4]
for i := 0; i < len(slice); i++ {
   fmt.Printf("slice[%v]=%v\t", i, slice[i])
}
fmt.Println()
//2.for-range遍历
for i, v := range slice {
   fmt.Printf("slice[%v]=%v\t", i, v)
}