slice详解

一、基本原理

1、基本内容

slice详解_第1张图片

2、小总结

  • 切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。
  • 切片唯一合法的比较操作是和nil比较。 一个nil值的切片并没有底层数组。
    一个nil值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil
    要检查切片是否为空,请始终使用len(s) == 0来判断,而不应该使用s == nil来判断。
  • 对一个切片的修改会影响另一个切片的内容
  • 切片的遍历方式和数组是一致的,支持索引遍历和for range遍历。

3、操作切片的本质是操作底层数组

切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。

  • 举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。
    slice详解_第2张图片
    切片s2 := a[3:6],相应示意图如下:
    slice详解_第3张图片

上述两张图片引自李文周的博客

4、示例

slice详解_第4张图片

3、相关函数

append()

可以为切片动态添加元素。
可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)
通过var声明的零值切片可以在append()函数直接使用,无需初始化。

copy()

可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:
copy(destSlice, srcSlice []T)

二、关于append()函数【重点】

Go函数传递默认是值拷贝,将slice变量传入append函数相当于传了原slice变量的一个副本,注意不是拷贝底层数组。
因为slice变量并不是数组,它仅仅是存储了底层数组的一些信息。

1、原理

  • append函数是用来在slice末尾追加一个或者多个元素。
  • 当追加元素时,发现slice的len>cap时,会重新开辟一个2*cap新内存空间去存储追加过元素的slice。
  • 如果追加元素后slice的len<=cap,则append返回的新生成的slice的内存地址依旧是传入的slice参数的内存地址

2、示例1

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	testSlice := make([]int, 0)
	testSlice = append(testSlice, 0)
	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice), cap(testSlice), testSlice)
	//len: 1, cap: 1, data:[0]
	fmt.Printf("testSlice地址:%p \n", &testSlice[0])
	//testSlice地址:0xc000014070
	fmt.Println("---------------0---------------")

	testSlice = append(testSlice, 1)
	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice), cap(testSlice), testSlice)
	//len: 2, cap: 2, data:[0 1]
	//因为cap长度不够,所以重新申请一个2*cap长度的内存空间,将内容拷贝到2倍cap的内存中
	fmt.Printf("testSlice地址:%p \n", &testSlice[0])
	//testSlice地址:0xc0000140a0 —— testSlice的内存地址变化了
	fmt.Println("---------------1---------------")

	testSlice = append(testSlice, 2)
	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice), cap(testSlice), testSlice)
	//len: 3, cap: 4, data:[0 1 2]
	fmt.Printf("testSlice地址:%p \n", &testSlice[0])
	//testSlice地址:0xc000016140
	fmt.Println("----------------2--------------")

	testSlice = append(testSlice, 3)
	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice), cap(testSlice), testSlice)
	//len: 4, cap: 4, data:[0 1 2 3]
	fmt.Printf("testSlice地址:%p \n", &testSlice[0])
	//testSlice地址:0xc000016140
	//因为cap空间足够用,所以继续在原来的内存地址上追加数据
	fmt.Println("---------------3---------------")

	// ================ 这里开始用新变量接收 ====================
	testSlice1 := append(testSlice, 4)
	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice), cap(testSlice), testSlice)
	//len: 4, cap: 4, data:[0 1 2 3]
	fmt.Printf("testSlice地址:%p \n", &testSlice[0])
	//testSlice地址:0xc000016140
	//testSlice的数据不变
	fmt.Println("================ 从这里开始用新变量接收 ====================")

	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice1), cap(testSlice1), testSlice1)
	//len: 5, cap: 8, data:[0 1 2 3 4]
	fmt.Printf("testSlice1地址:%p \n", &testSlice1[0])
	//testSlice1地址:0xc00001a0c0
	//新生成的testSlice1内存地址变化了
	fmt.Println("---------------4---------------")

	testSlice2 := append(testSlice1, 5)
	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice1), cap(testSlice1), testSlice1)
	//len: 5, cap: 8, data:[0 1 2 3 4]
	fmt.Printf("testSlice1地址:%p \n", &testSlice1[0])
	//testSlice1地址:0xc00001a0c0
	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice2), cap(testSlice2), testSlice2)
	//len: 6, cap: 8, data:[0 1 2 3 4 5]
	fmt.Printf("testSlice2地址:%p \n", &testSlice2[0])
	//testSlice2地址:0xc00001a0c0
	//在这里可以看到testSlice1和testSlice2的内存地址是一样的
	fmt.Println("--------------5----------------")

	p := unsafe.Pointer(&testSlice1[4])
	q := uintptr(p) + 8
	t := (*int)(unsafe.Pointer(q))
	fmt.Println("testSlice1可以拿到testSlice2的第五个元素值:", *t)
	//testSlice1可以拿到testSlice2的第五个元素值: 5
	fmt.Println("================ 这里测试同地址变量 取值 ====================")

	testSlice2[0] = 99999
	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice1), cap(testSlice1), testSlice1)
	//len: 5, cap: 8, data:[99999 1 2 3 4]
	fmt.Printf("testSlice1地址:%p \n", &testSlice1[0])
	//testSlice1地址:0xc00001a0c0
	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice2), cap(testSlice2), testSlice2)
	//len: 6, cap: 8, data:[99999 1 2 3 4 5]
	fmt.Printf("testSlice2地址:%p \n", &testSlice2[0])
	//testSlice2地址:0xc00001a0c0

	//修改testSlice2的值同时会改变testSlice1中的值
	//当然testSlice1一般不能访问testSlice2中的第6个元素,因为testSlice1的len是5
	//但是可以通过上面的方法访问,通过操作内存地址来访问
}

3、示例2

package main

import (
	"fmt"
)
func main() {
	testMap := make(map[string][]string, 0)
	testSlice := make([]string, 0)
	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice), cap(testSlice), testSlice)

	fmt.Println("===============================")

	testSlice = append(testSlice, "hello","go")
	fmt.Printf("len: %d, cap: %d, data:%+v \n", len(testSlice), cap(testSlice), testSlice)
	fmt.Printf("testSlice地址:%p \n", &testSlice[0])

	fmt.Println("===============================")

	// 将testSlice的值赋值给 testMap["world"]
	testMap["world"] = testSlice
	// 这里要对map的value进行操作,即testSlice
	testSliceTmp := append(testSlice, "hello2","go2")
	// 发现地址变了,因为 append追加元素后,slice动态扩容,可能会生成新的slice(地址变了),但是原来的slice(上述map中slice)没变
	fmt.Printf("testSliceTmp地址:%p \n", &testSliceTmp[0])
	// 用append操作完以后,并不能改变 testMap["world"]的值, 因为append函数接收的是testSlice的副本
	fmt.Printf("false,len: %d, cap: %d, data:%+v \n", len(testMap["world"]), cap(testMap["world"]), testMap["world"])

	// append后的结果重新赋值回testSlice,发现testMap["world"]并没有改变
	// 因为上边map的赋值操作, =右边 代表 testSlice内存空间存储的数据值,而不是他指向的内存地址
	testSlice = append(testSlice, "hello2","go2")
	fmt.Printf("true,len: %d, cap: %d, data:%+v \n", len(testMap["world"]), cap(testMap["world"]), testMap["world"])

	// 解决办法是:将append()返回的slice重新赋值回 testMap["world"]
	// 这里恢复一下testSlice,上边append操作结果赋值testSlice已经修改了
	testMap["world"] = append(testSlice[0:2], "hello2","go2")
	fmt.Printf("true,len: %d, cap: %d, data:%+v \n", len(testMap["world"]), cap(testMap["world"]), testMap["world"])
}

你可能感兴趣的:(Go,golang)