分享
  1. 首页
  2. 文章

Golang-Slice

萌小A · · 3192 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1.相关概念

1.开篇

最近忙着看加密,以太坊的代码。今天打算换个口味想有必要在把Go的进阶一下。毕竟最近都在用Golang 但是 Golang的底层都没有接触过。突然想起来暑假在公司上班的时候有人问我slice的一些用法:为什么作为参数的时候,直接修改会对原有的值有改变,而用了append以后就还是保留原来的样子。我的回答就是 在golang里面对array,slice,map的操作全部都是指针操作,所以第一种是直接修改了指定地址里面的数据,第二种append的话会自己产生一个新的slice所以对本来的数据没有影响。基本的测试代码大概这样的:

func main() {
 a:=[]int{1,2,3,4,5,6}
 test(a)
 fmt.Println(a) //=>输出[10,2,3,4,5,6]
}
func test(input []int){
 input[0]=10
}
func main() {
 a:=[]int{1,2,3,4,5,6}
 test2(a)
 fmt.Println(a) => 输出[1,2,3,4,5,6]
}
func test2(input []int){
 input = append(input,10)
}

一直想深追一下底层代码看下是不是跟我理解的相同,由于当时上班太忙把这件事都给忘了,今天刚好周五 又想起来了这件事所以就想深究下golang的底层操作。

2.slice的构成

slice跟array的区别就是,slice是一个可以扩容的array,有点类似java中的arraylist。挺有必要去看下slice的结构和一些方法的,重点看append。首先先找到slice的源码(找了挺久: gopath/src/runtime/slice.go)

type slice struct {
 array unsafe.Pointer //=>指向一个array
 len int
 cap int
}

slice的结构体里面也就3个参数,array,len,cap。 array就是一个指针指向数组的一个指针。也就是说slice其实是对array指针操作的一个封装而已。
len当前slice的长度,cap当前slice的容量(cap>=len)

2.Slice

1.创建

func makeslice(et *_type, len, cap int) slice {
 // NOTE: The len > maxElements check here is not strictly necessary,
 // but it produces a 'len out of range' error instead of a 'cap out of range' error
 // when someone does make([]T, bignumber). 'cap out of range' is true too,
 // but since the cap is only being supplied implicitly, saying len is clearer.
 // See issue 4085.
 maxElements := maxSliceCap(et.size)
 if len < 0 || uintptr(len) > maxElements {
 panic(errorString("makeslice: len out of range"))
 }
 if cap < len || uintptr(cap) > maxElements {
 panic(errorString("makeslice: cap out of range"))
 }
 p := mallocgc(et.size*uintptr(cap), et, true)
 return slice{p, len, cap}
}

主要做一些长度和容量的判断这些到底无所谓,最后看
p := mallocgc(et.size*uintptr(cap), et, true)
return slice{p, len, cap}
也就是开辟了一块地址,p指向这个地址,最后生成了slice。 所以说slice就是对array指针操作的一层封装
这个方面的下面还有64位的操作,最后也是调用了这个方法

2.append

先解释下append的作用就是在slice的最后一个元素在添加内容。就是链表里面的加加加加加。追了好久 append最后的实现= =, 由于append 内建函数所以不提供源码,反正网上找来找去,找到了一些信息 最后追踪到:

//参数为 类型,以前的slice 和最小需要的容量 
func growslice(et *_type, old slice, cap int) slice {
 //raceenabled 和msanenabled 都为false 直接略过
 if raceenabled {
 callerpc := getcallerpc(unsafe.Pointer(&et))
 racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
 }
 if msanenabled {
 msanread(old.array, uintptr(old.len*int(et.size)))
 }
 if et.size == 0 {
 if cap < old.cap {
 panic(errorString("growslice: cap out of range"))
 }
 // append should not create a slice with nil pointer but non-zero len.
 // We assume that append doesn't need to preserve old.array in this case.
 return slice{unsafe.Pointer(&zerobase), old.len, cap}
 }
 
//新的容量的规则
 newcap := old.cap
 doublecap := newcap + newcap
 if cap > doublecap {
 newcap = cap
 } else {
//如果当前的长度小于1024 那么就直接翻倍,否则增加的量会比较小
 if old.len < 1024 {
 newcap = doublecap
 } else {
 for newcap < cap {
 newcap += newcap / 4
 }
 }
 }
 //所有元素的长度,新的所有元素的长度 和容量下元素的长度
 var lenmem, newlenmem, capmem uintptr
 const ptrSize = unsafe.Sizeof((*byte)(nil))
 switch et.size {
 case 1:
 lenmem = uintptr(old.len)
 newlenmem = uintptr(cap)
 capmem = roundupsize(uintptr(newcap))
 newcap = int(capmem)
 case ptrSize:
 lenmem = uintptr(old.len) * ptrSize
 newlenmem = uintptr(cap) * ptrSize
 capmem = roundupsize(uintptr(newcap) * ptrSize)
 newcap = int(capmem / ptrSize)
 default:
 lenmem = uintptr(old.len) * et.size. //原有的长度*元素的长度
 newlenmem = uintptr(cap) * et.size //最小容量的长度
 capmem = roundupsize(uintptr(newcap) * et.size)//容量下的长度
 newcap = int(capmem / et.size)//新容量
 }
 
 if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {
 panic(errorString("growslice: cap out of range"))
 }
 var p unsafe.Pointer
 if et.kind&kindNoPointers != 0 {
 p = mallocgc(capmem, nil, false) //申请新的长度地址
 
 memmove(p, old.array, lenmem)
 
 memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
 } else {
 p = mallocgc(capmem, nil, false) //申请新的长度地址
 
 p = mallocgc(capmem, et, true)
 if !writeBarrier.enabled {
 // 复制 n bytes from "from" to "to". 第一参数为to
 memmove(p, old.array, lenmem)
 } else {
 for i := uintptr(0); i < lenmem; i += et.size {
 //复制原来的值
 typedmemmove(et, add(p, i), add(old.array, i))
 }
 }
 }
 //新的切片产生
 return slice{p, old.len, newcap}
}

growslice 也就是给slice 扩容的,重要的代码都加上注释,也就是可以发现 是产生了一个新的slice 也就是新的array的指针跟新的长度和大小,那接下来就是往后面添加的东西了。讲真slice这方面有点像java的string,java的string的append 也是产生新的string之类的。
好的好的。就分析到这 biu biu biu 也好久没有写技术博客了,今天稍微写一下。有哪里说错的欢迎指出
之后还想写一些有关go高阶的东西,想想写什么比较好。。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:萌小A

查看原文:Golang-Slice

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
3192 次点击
1 回复 | 直到 2021年09月13日 16:28:45
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏