golang中开发和面试中一些容易忽略知识点

252 阅读5分钟

文章转载自:golang中开发和面试中一些容易忽略知识点

slice

切片相关的考察点,主要设计切片扩容机制、切片拷贝、切片初始化、地址传递与值传递等。

slice扩容

示例程序

package main

import "fmt"
import "time"

func main() {
	// 不指定长度切片的扩容
	arr0 := make([]int, 0)
	fmt.Println("arr0:", arr0, "len:", len(arr0), "cap:", cap(arr0))	
	for i := 0; i < 10; i++ {
		arr0 = append(arr0, i)
		fmt.Println("arr0:", arr0, "len:", len(arr0), "cap:", cap(arr0))	
	}
	
	// 指定切片长度,不指定容量,默认当前数组容量等于指定的长度
	arr1 := make([]int, 10)
	fmt.Println("arr1:", arr1, "len:", len(arr1), "cap:", cap(arr1))	
	// 再往切片中添加一个元素,当切片长度未达1024时,按照原长度的2倍扩容
	arr1 = append(arr1, 1)
	fmt.Println(len(arr1), cap(arr1))
}

结果如下:

arr0: [] len: 0 cap: 0
arr0: [0] len: 1 cap: 1
arr0: [0 1] len: 2 cap: 2
arr0: [0 1 2] len: 3 cap: 4
arr0: [0 1 2 3] len: 4 cap: 4
arr0: [0 1 2 3 4] len: 5 cap: 8
arr0: [0 1 2 3 4 5] len: 6 cap: 8
arr0: [0 1 2 3 4 5 6] len: 7 cap: 8
arr0: [0 1 2 3 4 5 6 7] len: 8 cap: 8
arr0: [0 1 2 3 4 5 6 7 8] len: 9 cap: 16
arr0: [0 1 2 3 4 5 6 7 8 9] len: 10 cap: 16
arr1: [0 0 0 0 0 0 0 0 0 0] len: 10 cap: 10
11 20

slice扩容机制

使用append添加元素到切片时,先预估切片容量cap(预估容量=原切片容量 + append元素个数):
1. 如果原切片容量oldCap翻倍后,仍然小于预估容量(即oldCap * 2 < cap),则扩容后切片容量newCap为cap2. 如果原切片容量oldCap翻倍后,大于等于预估容量:
   a. 如果原切片长度oldLen < 1024,扩容后的切片容量直接翻倍(即newCap = oldCap * 2);
   b. 如果原切片长度oldLen >= 1024,扩容后的切片容量为原切片容量的1.25倍。

PS:当触发切片的扩容机制时,会重新分配内存,这就会造成原来指向同一个地址的切片,在扩容后指向不同的地址空间,使得在通过函数对切片传参时,函数内对切片修改后无法改变实参切片的值,得不到想要的结果,这时可以通过两种方式处理:

1. 对切片使用为全局变量,不用通过函数传参,即可在函数内对其进行操作。
2. 使用函数传参,传递切片的指针形式,这样即使触发切片的扩容,由于使用的是切片的指针形式,对切片元素进行操作时,还是指向同一个地址,因此在函数内对形参切片进行操作,仍可达到修改原切片的目的。

切片底层结构

切片底层结构包含3部分:指向的底层数组、长度、容量。 当切片指向的底层数组扩容时,原来的切片指向的底层数组与现在的切片指向的底层数组就不一样了。但是不扩容的时候,上面path切片里面的数据可能会改变,因此原来的二维切片已经添加的path内容也会改变,但是我们只是要添加时候的path,而不是改变之后的path。所以需要先拷贝一份。 比如组合和是3的时候[2,1,0]作为path加入到ret,后面path变成了[2,3,-2]加入到ret,那么path没有扩容,所以原来你加入的[2,1,0]就会变成[2,3,-2],但是我们只是要原来那个时候的[2,1,0]。

切片扩容会改变底层数组,要么传切片的指针,要么将切片定义为全局变量,不用传参即可在函数内对该切片进行操作。

slice拷贝

golang中的=等号拷贝、copy浅拷贝、深拷贝之间的区别及性能差异,详见golang浅拷贝与深拷贝

func main() {
	arr2 := []int{1, 2, 3, 5, 6, 8}
	// =类型copy,指向同一个地址,改变原切片或复制的切片,另一个都会相应改变
	arr22 := arr2[:]
	fmt.Printf("arr2:%v, arr2 address:%p, arr22:%v, arr22 address:%p\n", arr2, arr2, arr22, arr22)
	arr22[2]= 9
	fmt.Printf("arr2:%v, arr2 address:%p, arr22:%v, arr22 address:%p\n", arr2, arr2, arr22, arr22)
	// copy类型的浅拷贝,改变原值类型的切片或复制的切片,另一个不会改变
	arrCopy := make([]int, len(arr2))
	copy(arrCopy, arr2)
	fmt.Printf("arr2:%v, arr2 address:%p, arrCopy:%v, arrCopy address:%p\n", arr2, arr2, arrCopy, arrCopy)	
	arrCopy[1] = 6
	fmt.Printf("arr2:%v, arr2 address:%p, arrCopy:%v, arrCopy address:%p\n", arr2, arr2, arrCopy, arrCopy)	
}

结果如下:

arr2:[1 2 3 5 6 8], arr2 address:0xc420090090, arr22:[1 2 3 5 6 8], arr22 address:0xc420090090
arr2:[1 2 9 5 6 8], arr2 address:0xc420090090, arr22:[1 2 9 5 6 8], arr22 address:0xc420090090
arr2:[1 2 9 5 6 8], arr2 address:0xc420090090, arrCopy:[1 2 9 5 6 8], arrCopy address:0xc4200900c0
arr2:[1 2 9 5 6 8], arr2 address:0xc420090090, arrCopy:[1 6 9 5 6 8], arrCopy address:0xc4200900c0

go程带参与不带参

package main

import "fmt"
import "time"

func main() {
	arr2 := []int{1, 2, 3, 5, 6, 8}
	arr22 := arr2[:]
	arr222 := []int{1, 2, 3, 5, 6, 8}
	arr3 := [][]int{{1, 2, 3}, {1, 5, 8}, {3, 6, 9}}
	
	// 注意:这里没有传入实参,打印的相当于go程序外部的(值类型)变量的值:for循环结束时的索引和数值
	for i, v := range arr2 {
		go func(){
			fmt.Println("arr2", "index:", i, "value:", v, "value * value:", v * v)
		}()
	}
	
	// 注意:这里传入的实参是i,v,打印的是形参idx和val
	for i, v := range arr22 {
		go func(idx, val int){
			fmt.Println("arr22", "index:", idx, "value:", val, "value * value:", val * val)
		}(i, v)
	}
	
	// 注意:这里传入的实参是i,v,打印的是形参idx和实参v
	for i, v := range arr222 {
		go func(idx, val int){
			fmt.Println("arr222", "index:", idx, "value:", v, "value * value:", v * v)
		}(i, v)
	}
	
	// 注意:这里没有传入实参,打印的相当于go程序外部的(引用类型)变量的值:for循环结束时的索引和数值
	for i, v := range arr3 {
		go func(){
			fmt.Println("arr3", "index:", i, "value:", v)
		}()
	}
	
	// 不加休眠时间,上面两个for循环中的go协程勾出去后,没等打印结果,main程序就结束了
	time.Sleep(3 * time.Second)
}

结果如下:

arr22 index: 3 value: 5 value * value: 25
arr2 index: 5 value: 8 value * value: 64
arr2 index: 5 value: 8 value * value: 64
arr2 index: 5 value: 8 value * value: 64
arr2 index: 5 value: 8 value * value: 64
arr2 index: 5 value: 8 value * value: 64
arr2 index: 5 value: 8 value * value: 64
arr22 index: 0 value: 1 value * value: 1
arr22 index: 1 value: 2 value * value: 4
arr22 index: 2 value: 9 value * value: 81
arr222 index: 3 value: 8 value * value: 64
arr22 index: 4 value: 6 value * value: 36
arr22 index: 5 value: 8 value * value: 64
arr222 index: 0 value: 8 value * value: 64
arr222 index: 1 value: 8 value * value: 64
arr222 index: 2 value: 8 value * value: 64
arr3 index: 2 value: [3 6 9]
arr222 index: 4 value: 8 value * value: 64
arr222 index: 5 value: 8 value * value: 64
arr3 index: 2 value: [3 6 9]
arr3 index: 2 value: [3 6 9]