go切片基础及内存分配

目录

一、切片介绍

二、切片使用

切片创建

数组和切片的区别

切片的实现原理


一、切片介绍

切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数 append() 来实现的,这个函数可以快速且高效地增长切片,也可以通过对切片再次切割,缩小一个切片的大小。因为切片的底层也是在连续的内存块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。形式上类似于python中的list的,但是没有提供python中list那么多的可用方法,也情有可原。

二、切片使用

  • 切片创建

切片创建可以通过make函数,如创建一个整形切片

       //长度len=5,cap=5,使用make创建切片,不指定第3个参数时,默认切片容量等于长度

mSlic := make([]int,5)

此时,打印该切片,切片值为[0,0,0,0,0],即默认初始化为0.

除了用make外还能用指定切片元素创建,如下

//长度和容量依旧相等,为5

mSlic := []int{1,2,3,4,5}

  • 数组和切片的区别

数组和切片最本质的区别就是数组是值类型,切片是引用,且数组定长无法扩容,而切片可以。

//数组初始化,一个长度为5的定长数组

marray  := [5]{1,2,3,4,5}

//不定长数组

marray := [...]{1,2,3,4,5}

可以看到数组与切片的创建形式完全不同。

  • 切片的实现原理

 切片的实现其实是通过数组实现的,前面说过切片是引用,那么怎么引用?引用的又是谁?如下图

中间为数组,上面为切片mSlic的创建,mSlic的地址指向了数组,即切片是指向底层数组空间的,

当用切片mSlic去创建切片nSlic时,将地址mSlic地址向后移动,返回新的地址即为nSlic的空间,

可以看到,切片创建切片时两个切片mSlic和nSlic共享同一个数组空间,此时若是nSlic[1] = 70,那么读者觉得mSlic会发生什么呢?

没错,和你心里想 的一样,mSlic[2]会从30-->70,直接上代码展示,如下

func slic_dist() {
	mSlic := []int{10, 20, 30, 40, 50, 60}
	// 创建新的切片,其长度为 2 个元素,容量为 5
	nSlic := mSlic[1:3]

	nSlic[1] = 70
	fmt.Println("nSlic:", nSlic)
	fmt.Println("mSlic:", mSlic)

	//刚分配好切片的地址
	fmt.Printf("%v,%p\n", nSlic, nSlic)

	// 使用原有的容量来分配一个新元素
	// 将新元素赋值为 80
	nSlic = append(nSlic, 80)
	fmt.Printf("%v,%p\n", nSlic, nSlic)

	//继续增加元素90
	nSlic = append(nSlic, 90)
	fmt.Printf("%v,%p\n", nSlic, nSlic)

	//继续增加元素100
	nSlic = append(nSlic, 100)
	fmt.Printf("%v,%p\n", nSlic, nSlic)

	//继续增加元素110,地址发生变化
	nSlic = append(nSlic, 110)
	fmt.Printf("%v,%p\n", nSlic, nSlic)
}

结果:

nSlic: [20 70]
mSlic: [10 20 70 40 50 60]
[20 70],0xc00000a308
[20 70 80],0xc00000a308
[20 70 80 90],0xc00000a308
[20 70 80 90 100],0xc00000a308
[20 70 80 90 100 110],0xc0000140f0

从上述示例中可发现,确实和你们想的一样,原切片mSlic的第3个元素值随新切片nSlic的第2个值变化而变化,从30-->70,也充分说明了切片是依赖数组实现的,

且依赖切片创建的切片共享同一个底层数组空间,还有一点需要注意的是用切片创建切片,那么新切片不能超过原切片的容量,比如mSlic的cap=6,那么创建

新切片nSlic时,nSlic的cap<6,必须小于6,这是由于切片创建切片的方式是基于原来切片大小来的,而不是开辟新空间。

后面的地址打印是不断给新切片nSlic增加元素,nSlic地址的变化,你会发现当增加至第4个元素即nSlic元素总数为6时nSlic地址发生了变化,从0xc00000a308-->0xc0000140f0,

说明nSlic重新指向了一个新的数组空间,而将原nSlic指向数组中的值复制到了新数组空间,完成了nSlic的内存重新分配。

以上便是切片的一些基础及实现总结,没有好的画图软件,只能用画图工具来替代,读者见谅!

这里留个问题,大家猜猜最后mSlic的值是什么样的呢?可以自己尝试下。

### 回答1: Go 语言的 append 函数用于将一个或多个元素附加到切片的末尾。append 切片会分配新的内存空间,并复制旧切片的数据,将新元素添加到新的内存空间。而 append 元素则只是将元素添加到切片的原有内存空间。 ### 回答2: 在Go语言,使用`append`函数来添加元素到一个切片。无论是向一个切片`append`元素,还是向一个切片追加另一个切片,它们在内存分配上有些许区别。 当向切片`append`一个元素时,如果切片的容量足够容纳新的元素,那么会直接将元素添加在切片的末尾。这意味着元素是直接被追加到原切片的内存区域上的,不会重新分配内存。如果切片的容量不足以容纳新元素,那么Go语言会为切片分配一块新的内存区域,并将原切片的元素和新的元素都复制到这块新内存区域上,并返回一个指向新内存区域的切片。 当向切片`append`另一个切片时,Go语言首先会比较目标切片的容量和要追加切片的长度。如果目标切片的容量足够容纳要追加切片的元素,那么会直接将要追加切片的元素复制到目标切片的内存区域上,并返回一个指向目标切片切片。这种情况下,不会进行内存的重新分配和复制。如果目标切片的容量不足以容纳要追加切片的元素,那么Go语言会为目标切片和要追加切片分配一块新的内存区域,并将两个切片的元素都复制到这块新内存区域上,并返回一个指向新内存区域的切片。 总结起来,当向切片`append`元素或另一个切片时,如果切片的容量足够,不会重新分配内存;如果容量不足,将会重新分配内存并复制元素。 ### 回答3: 在Go语言,使用`append`函数可以向切片追加元素。在进行`append`操作时,会涉及到内存分配的问题。 首先,切片和数组的区别在于切片是一个引用类型,它的底层指向一个数组。切片本身并不存储元素,而是存储了一个指向底层数组的指针、切片的长度和容量。当切片的容量不足以容纳新的元素时,就需要进行内存扩容操作。 对于切片的`append`操作,当切片的容量不足时,会创建一个新的底层数组,并将原来的元素复制到新的底层数组。新的底层数组的容量通常会是原来的两倍,并且会根据实际情况进行调整。然后,将新的元素追加到新的底层数组,并更新切片的指针、长度和容量。 值得注意的是,由于切片本身是一个指向底层数组的指针,并不需要像数组那样重新赋值给一个新的变量。因此,对切片进行`append`操作并赋值给切片本身,会在原有的切片上进行操作,而不是创建一个新的切片。 一般情况下,`append`操作的时间复杂度为O(1),但是当底层数组发生扩容时,时间复杂度会升至O(n)。此外,由于底层数组的复制操作会涉及到内存分配和数据拷贝,`append`操作也会产生额外的内存开销。考虑到性能方面的原因,如果预先知道切片的容量大小,可以使用`make`函数初始化切片并指定容量,以减少切片的扩容次数,从而提高性能。 综上所述,golang的`append`切片和元素的内存分配区别在于:`append`切片会根据实际情况进行底层数组的内存扩容,并重新分配内存,同时对切片本身的指针、长度和容量进行更新;而`append`元素只需要将新的元素追加到底层数组的末尾,并不涉及内存扩容操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值