golang基础:一.切片(slice)

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

slice基础

切片的结构

slice是一个结构体

参照源码: src/runtime/slice.go

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

array:数组指针,len表示长度,cap表示容量

切片的声明

var a []int

采用数组创建切片

var a = [10]int{1,2,3,4,5,6,7,8,9}
sl := a[5:] //切片
fmt.Println(sl)

采用make创建切片

// make: 第一个参数:类型,第二个参数:初始长度,第三个参数:容量(如果容量不够的情况自动扩容)
var sl = make([]int, 3, 10)
fmt.Println(len(sl),cap(sl))

append函数

  1. 可以在Slice尾部追加元素
  2. 可以将一个Slice追加在另一个Slice尾部
a := []int{}
a = append(a,1)

b := []int{3,4,5}
a = append(a,b...)
// ... 是go的一种语法糖,用于打散切片,或者传不定参数。

copy函数

a := []int{1,2,3,4,5}
b := []int{7,8,9}
copy(a,b) // 将第二个切片拷贝到第一个切片上。且第一个切片的长度不会变
fmt.Println(a) //[7 8 9 4 5]

slice 扩容机制

  1. 长度小于容量时(不扩容)
a := make([]int,2,3)
a = append(a,2)
fmt.Println("长度:",len(a))
fmt.Println("容量:",cap(a))
// 结果:----------------------------
// 长度: 3
// 容量: 3
  1. 长度超过容量,且容量小于1024(容量翻倍)
a := make([]int,1023,1023)
a = append(a,2)
fmt.Println("长度:",len(a))
fmt.Println("容量:",cap(a))
// 结果:----------------------------
// 长度: 1024
// 容量: 2048
  1. 长度超过容量,且容量大于等于1024,容量增大1/4,且内存对齐

参照源码: src/runtime/slice.go

	newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		if old.len < 1024 {
			newcap = doublecap
		} else {
			// Check 0 < newcap to detect overflow
			// and prevent an infinite loop.
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			// Set newcap to the requested cap when
			// the newcap calculation overflowed.
			if newcap <= 0 {
				newcap = cap
			}
		}
	}

当切片的长度超过容量时,容量会进行扩容,如果容量(cap)小于1024,则容量翻倍,否则newcap += newcap / 4,然后再进行向上的内存对齐

slice的地址

  1. slice不扩容不改变地址
b := make([]int,0,10)
fmt.Printf("b的地址: %p\n",b)
b = append(b,1)
fmt.Printf("b的地址: %p\n",b)
// 结果:----------------------------
// b的地址: 0xc000016230
// b的地址: 0xc000016230
  1. slice扩容即改变地址
b := make([]int,1,1)
fmt.Printf("b的地址: %p\n",b)
b = append(b,1)
fmt.Printf("b的地址: %p\n",b)
// 结果:----------------------------
// b的地址: 0xc00000a128
// b的地址: 0xc00000a140

slice作为函数的参数

  1. 函数内改变slice的值,函数外的slice也会改变
func main()  {
	a := []int{2}
	updateSlice(a)
	fmt.Println(a)
}

func updateSlice (s []int) {
	if len(s) > 0 {
		s[0] = 10
	}
}
// 结果:----------------------------
// [10]
  1. 函数内append,函数外为函数append前的值
func main()  {
	a := []int{2}
	appendSlice(a,3)
	fmt.Println(a)

}

func appendSlice(s []int, v int) {
	if len(s) > 0 {
		s[0] = 5
	}
	s = append(s,v)
}
// 结果:----------------------------
// [5]

总结

  1. 切片是对底层数组的一个抽象,描述了它的一个片段。
  2. 切片实际上是一个结构体,它有三个字段:长度,容量,底层数据的地址。
  3. 多个切片可能共享同一个底层数组,这种情况下,对其中一个切片或者底层数组的更改,会影响到其他切片。
  4. append 函数会在切片容量不够的情况下扩容,扩容会改变元素原来的位置。
  5. 扩容策略并不是简单的扩为原切片容量的 2 倍或 1.25 倍,还有内存对齐的操作。扩容后的容量 >= 原容量的 2 倍或 1.25 倍。
  6. 当直接用切片作为函数参数时,可以改变切片的元素,不能改变切片本身;想要改变切片本身,可以将改变后的切片返回,函数调用者接收改变后的切片或者将切片指针作为函数参数。
posted @ 2022-02-10 22:18  EthanWell  阅读(355)  评论(0编辑  收藏  举报