没看懂的地方-golang-slice

这部分:

有时候可能需要使用一些比较tricky的技巧,比如利用make弄一块内存自己管理,或者用cgo之类的方式得到的内存,转换为Go类型使用。

从slice中得到一块内存地址是很容易的:

s := make([]byte, 200)

ptr := unsafe.Pointer(&s[0])

从一个内存指针构造出Go语言的slice结构相对麻烦一些,比如其中一种方式:

var ptr unsafe.Pointer

s := ((*[1<<10]byte)(ptr))[:200]

先将ptr强制类型转换为另一种指针,一个指向[1<<10]byte数组的指针,这里数组大小其实是假的。然后用slice操作取出这个数组的前200个,于是s就是一个200个元素的slice。

.....................(*[1<<10]byte)................妈蛋

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


2.2 slice

一个slice是一个数组某个部分的引用。在内存中,它是一个包含3个域的结构体:指向slice中第一个元素的指针,slice的长度,以及slice的容量。长度是下标操作的上界,如x[i]中i必须小于长度。容量是分割操作的上界,如x[i:j]中j不能大于容量。

数组的slice并不会实际复制一份数据,它只是创建一个新的数据结构,包含了另外的一个指针,一个长度和一个容量数据。 如同分割一个字符串,分割数组也不涉及复制操作:它只是新建了一个结构来放置一个不同的指针,长度和容量。在例子中,对[]int{2,3,5,7,11}求值操作会创建一个包含五个值的数组,并设置x的属性来描述这个数组。分割表达式x[1:3]并不分配更多的数据:它只是写了一个新的slice结构的属性来引用相同的存储数据。在例子中,长度为2--只有y[0]和y[1]是有效的索引,但是容量为4--y[0:4]是一个有效的分割表达式。

由于slice是不同于指针的多字长结构,分割操作并不需要分配内存,甚至没有通常被保存在堆中的slice头部。这种表示方法使slice操作和在C中传递指针、长度对一样廉价。Go语言最初使用一个指向以上结构的指针来表示slice,但是这样做意味着每个slice操作都会分配一块新的内存对象。即使使用了快速的分配器,还是给垃圾收集器制造了很多没有必要的工作。移除间接引用及分配操作可以让slice足够廉价,以避免传递显式索引。

slice的扩容

其实slice在Go的运行时库中就是一个C语言动态数组的实现,在$GOROOT/src/pkg/runtime/runtime.h中可以看到它的定义:

struct Slice

{ // must not move anything

byte* array; // actual data

uintgo len; // number of elements

uintgo cap; // allocated number of elements

};

在对slice进行append等操作时,可能会造成slice的自动扩容。其扩容时的大小增长规则是:

如果新的大小是当前大小2倍以上,则大小增长为新大小

否则循环以下操作:如果当前大小小于1024,按每次2倍增长,否则每次按当前大小1/4增长。直到增长的大小超过或等于新大小。

make和new

Go有两个数据结构创建函数:new和make。两者的区别在学习Go语言的初期是一个常见的混淆点。基本的区别是new(T)返回一个*T,返回的这个指针可以被隐式地消除引用(图中的黑色箭头)。而make(T, args)返回一个普通的T。通常情况下,T内部有一些隐式的指针(图中的灰色箭头)。一句话,new返回一个指向已清零内存的指针,而make返回一个复杂的结构。

有一种方法可以统一这两种创建方式,但是可能会与C/C++的传统有显著不同:定义make(*T)来返回一个指向新分配的T的指针,这样一来,new(Point)得写成make(*Point)。但这样做实在是和人们期望的分配函数太不一样了,所以Go没有采用这种设计。

slice与unsafe.Pointer相互转换

有时候可能需要使用一些比较tricky的技巧,比如利用make弄一块内存自己管理,或者用cgo之类的方式得到的内存,转换为Go类型使用。

从slice中得到一块内存地址是很容易的:

s := make([]byte, 200)

ptr := unsafe.Pointer(&s[0])

从一个内存指针构造出Go语言的slice结构相对麻烦一些,比如其中一种方式:

var ptr unsafe.Pointer

s := ((*[1<<10]byte)(ptr))[:200]

先将ptr强制类型转换为另一种指针,一个指向[1<<10]byte数组的指针,这里数组大小其实是假的。然后用slice操作取出这个数组的前200个,于是s就是一个200个元素的slice。

或者这种方式:

var ptr unsafe.Pointer

var s1 = struct {

addr uintptr

len int

cap int

}{ptr, length, length}

s := *(*[]byte)(unsafe.Pointer(&s1))

把slice的底层结构写出来,将addr,len,cap等字段写进去,将这个结构体赋给s。相比上一种写法,这种更好的地方在于cap更加自然,虽然上面写法中实际上1<<10就是cap。

或者使用reflect.SliceHeader的方式来构造slice,比较推荐这种做法:

var o []byte

sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&o)))

sliceHeader.Cap = length

sliceHeader.Len = length

sliceHeader.Data = uintptr(ptr)

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 1.安装 https://studygolang.com/dl 2.使用vscode编辑器安装go插件 3.go语...
    go含羞草阅读 5,529评论 0 6
  • Hello World这是开发的一个开发魔咒,几乎所有人都是从这开始的,今天我也从魔咒开始先打印一份Hello W...
    cocoaAhda阅读 5,331评论 0 3
  • 出处---Go编程语言 欢迎来到 Go 编程语言指南。本指南涵盖了该语言的大部分重要特性 Go 语言的交互式简介,...
    Tuberose阅读 18,643评论 1 46
  • 运营是对任务负责,解决问题,不是制造问题。 运营的步骤:策略,分解目标,执行,反馈 拉新推广的方式:渠道推广,病毒...
    蒋羽燃阅读 1,351评论 0 0
  • * 简介 - 运行时是一种面向对象的编程的运行环境,类似于JAVA的虚拟机 - OC 最主要的特征就是在程序运行时...
    KAKA_move阅读 1,234评论 0 0

友情链接更多精彩内容