你了解Go语言嘛?(十一) | 青训营笔记

91 阅读3分钟

大家好!这是我参与「第五届青训营 」笔记创作活动的第11天。

我是一名在校大二的学生,很荣幸的参加本次青训营,所以我想把我在这次青训营的学习经历通过这次活动记录下来,和本次参加项目的小伙伴一起学习探讨,今天讲解的是go语言基础的第十一节。加油!学习永无止境!💪💪💪

GO语言

8.11、append()方法为切片添加元素

        Go 语言的内建函数append()可以为切片动态添加元素,每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。

        “扩容”操作往往发生在append()函数调用时,所以我们通常都需要用原变量接收append 函数的返回值。给切片追加元素的错误写法:

s3 := []int{1, 2, 3, 5, 6, 7}
s3[6] = 8
fmt.Println(s3) //index out of range [6] with length 6

append()方法为切片追加元素:

func main() {
	//append()添加元素和切片扩容
	var numSlice []int
	for i := 0; i < 10; i++ {
		numSlice = append(numSlice, i)
		fmt.Printf("%v len:%d cap:%d ptr:%p\n", numSlice, len(numSlice), cap(numSlic
		e), numSlice)
	}
}
输出:
	[0] len:1 cap:1 ptr:0xc0000a8000
	[0 1] len:2 cap:2 ptr:0xc0000a8040
	[0 1 2] len:3 cap:4 ptr:0xc0000b2020
	[0 1 2 3] len:4 cap:4 ptr:0xc0000b2020
	[0 1 2 3 4] len:5 cap:8 ptr:0xc0000b6000
	[0 1 2 3 4 5] len:6 cap:8 ptr:0xc0000b6000
	[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc0000b6000
	[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc0000b6000
	[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc0000b8000
	[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc0000b8000

从上面的结果可以看出:

(1)、append()函数将元素追加到切片的最后并返回该切片。

(2)、切片numSlice 的容量按照1,2,4,8,16 这样的规则自动进行扩容,每次扩容后都是扩容前的2 倍。

append()函数还支持一次性追加多个元素。例如:
var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...)
fmt.Println(citySlice) //[北京 上海 广州 深圳 成都 重庆]
 
// 切片的追加切片
s1 := []int{100, 200, 300}
s2 := []int{400, 500, 600}
s3 := append(s1, s2...)
fmt.Println(s3)

8.12、切片的扩容策略

        可以通过查看$GOROOT/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
		}
	}
}

从上面的代码可以看出以下内容:

        1、首先判断,如果新申请容量(cap)大于2 倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。

        2、否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap)。

        3、否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)。

        4、如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int 和string类型的处理方式就不一样。

8.13、使用copy()函数复制切片

func main() {
	a := []int{1, 2, 3, 4, 5}
	b := a
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(b) //[1 2 3 4 5]
	b[0] = 1000
	fmt.Println(a) //[1000 2 3 4 5]
	fmt.Println(b) //[1000 2 3 4 5]
}

        由于切片是引用类型,所以a 和b 其实都指向了同一块内存地址。修改b 的同时a 的值也会发生变化。

        Go 语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:

copy(destSlice, srcSlice []T) 
其中: 
        . srcSlice: 数据来源切片
        . destSlice: 目标切片
func main() {
	// copy()复制切片
	a := []int{1, 2, 3, 4, 5}
	c := make([]int, 5, 5)
	copy(c, a) //使用copy()函数将切片a 中的元素复制到切片c
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1 2 3 4 5]
	c[0] = 1000
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1000 2 3 4 5]
}

8.14、从切片中删除元素

Go 语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:

func main() {
	// 从切片中删除元素
	a := []int{30, 31, 32, 33, 34, 35, 36, 37}
	// 要删除索引为2 的元素
	a = append(a[:2], a[3:]...)
	fmt.Println(a) //[30 31 33 34 35 36 37]
}

        要从切片a 中删除索引为index 的元素,操作方法是a = append(a[:index],a[index+1:]...)