一起剖析Go语言切片

Go切片是什么?

我们知道,Go语言数组的定长性和值拷贝限制了其使用场景,Go提供另一种数据类型slice,可以看着一种变长数组,其数据结构中有指向数组的指针,所以是一种引用类型。在开发中关于切片和数组怎么选择?

先看看数组有什么问题?

Go 数组是值类型,赋值和函数传参操作都会复制整个数组数据,下面代码是验证Go都是值复制:

func main() {
	aa := [3]int{9,9,6}
	var bb [3]int
	bb = aa
	fmt.Printf("aa=%p,%d\nbb=%p,%d",&aa,aa,&bb,bb)
}

输出结果:

aa=0xc0000b4000,[9 9 6]
bb=0xc0000b4018,[9 9 6]

两个地址都不一样,这样可以证明它们赋值和函数传参都是值复制。

那么问题就暴露出来,假想每次传参都用数组,每次数组都要被复制一遍。数组大小有 100万,在64位机器上就需要花费大约 800W 字节,即 8MB 内存。这样会消耗掉大量的内存。那么,就要解决这个问题?Go语言开发者,想到了使用函数传参用数组的指针

func main() {
	aa := []int{9,9,6}
	test(&aa) //传数组指针
	bb := aa[:] //切片
	test(&bb)
	fmt.Printf("aa=%p %v\n",&aa,&bb)
}
func test(num *[]int) {
	fmt.Printf("func Array : %p , %v\n", num, *num)
	(*num)[1] += 100
}

输出结果:

func Array : 0xc00011c018 , [9 9 6]
func Array : 0xc00011c048 , [9 109 6]
aa=0xc00011c018 &[9 209 6]

指针确实到达了我们想要的效果了,但是指针有个问题,打印结果可以看到,第一行和第三行指针地址都是同一个,万一原数组的指针指向更改了,那么函数里面的指针指向都会跟着更改。

切片的优势也就表现出来了。用切片传数组参数,既可以达到节约内存的目的,也可以达到合理处理好共享内存的问题。打印结果第二行就是切片,切片的指针和原来数组的指针是不同的。

由此我们可以得出结论:把第一个大数组传递给函数会消耗很多内存,采用切片的方式传参可以避免上述问题。切片是引用传递,所以它们不需要使用额外的内存并且比使用数组更有效率。

但是,并非所有时候都适合用切片代替数组,因为切片底层数组可能会在堆上分配内存,而且小数组在栈上拷贝的消耗也未必比make 消耗大。

如何创建切片?

(1)由数组创建

语法结构:arr[i:n],其中,arr是数组,i表示开始索引,默认为0,n表示结束索引,默认len(arr)。

Arr[i:n]表示创建一个包含i-n个元素的切片,第一个元素是arr[i],最后一个元素是arr[n-1],如

var arr = [...]int{1,2,3,4,5,6}
s := arr[0:4]

(2)通过内置函数 make 创建切片

注:由 make 创建的切片各元素被默认初始化为切片元素类型 的零值 。 例如:

a := make([]int,10,15)

创建一个长度为,len=10,cap=15

(3)切片基本操作

  • Len()
  • Cap()
  • Append()
  • Copy()
	a := make([]int,1,3) //创建一个切片为int类型,len=1,cap=3
	a = append(a,1)
	a = append(a,1) //已经满啦
	fmt.Printf("len = %d,cap = %d,slice = %v\n", len(a), cap(a),a)
	a = append(a,1) //cap会自动扩容,以原来的2倍扩容
	fmt.Printf("len = %d,cap = %d,slice = %v\n", len(a), cap(a),a)

输出:

len = 3,cap = 3,slice = [0 1 1]
len = 4,cap = 6,slice = [0 1 1 1]
Slice原理

切片的结构体由3部分构成,Pointer 是指向一个数组的指针,len 代表当前切片的长度,cap 是当前切片的容量。cap 总是大于等于 len 的。

1687DF74739E653D20C2FD73DF237375.jpg

  • ptr 指向Slice开头的元素
  • len 是指说明Slice长度多少,方括号取值只能去到len里面的值
  • cap 是代表整个arr从ptr开始到结束长度

注:Slice是可以向后扩展,但不能向前扩展。取值不可以超过底层数组cap长度

Slice如何扩容

首先,当添加元素超过capacity的长度的时,系统会重新分配更大的底层数组,旧的数组如有还有用就还在,不用Go语言垃圾回收机制自动清除,Slice触发扩容,是以原来数组*2(2倍)进行扩容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言底层原理剖析》这本电子书主要深入探讨了Go语言的底层实现原理和机制。在Go语言快速发展的背后,了解其底层原理对于深入理解和优化代码至关重要。 首先,书中介绍了Go语言的内存管理。Go语言通过垃圾回收的方式自动管理内存,通过分代垃圾回收和并发标记等技术来提高垃圾回收的效率,并保证程序的性能。 其次,书中对Go语言的并发模型进行了详细解析。Go语言以轻量级的协程(goroutine)为基础,通过使用通道(channel)进行通信和同步,实现高效的并发编程。这本书从底层原理的角度深入剖析了协程的调度、通道的实现以及锁和同步原语等内容。 此外,书中对Go语言的编译器和运行时进行了解析。Go语言的编译器采用前端和后端分离的设计,通过词法分析、语法分析、类型检查和优化等步骤将Go源代码翻译成机器码。同时,Go语言的运行时系统提供了垃圾回收、调度器、内存管理等一系列功能,保证了程序的正确性和性能。 最后,本书还深入研究了Go语言的网络编程、文件IO、系统调用以及反射等底层机制。这些底层原理的理解,能够帮助开发者更好地理解和利用Go语言的特性,写出高效可靠的代码。 总之,《Go语言底层原理剖析》这本电子书通过深入剖析Go语言的底层原理,为读者提供了深入理解Go语言的机会,有助于开发者更好地应用Go语言进行编程,写出高性能、可靠的代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值