沒看懂的地方-golang-slice

這部分:

有時(shí)候可能需要使用一些比較tricky的技巧,比如利用make弄一塊內(nèi)存自己管理,或者用cgo之類的方式得到的內(nèi)存,轉(zhuǎn)換為Go類型使用。

從slice中得到一塊內(nèi)存地址是很容易的:

s := make([]byte, 200)

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

從一個(gè)內(nèi)存指針構(gòu)造出Go語言的slice結(jié)構(gòu)相對(duì)麻煩一些,比如其中一種方式:

var ptr unsafe.Pointer

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

先將ptr強(qiáng)制類型轉(zhuǎn)換為另一種指針,一個(gè)指向[1<<10]byte數(shù)組的指針,這里數(shù)組大小其實(shí)是假的。然后用slice操作取出這個(gè)數(shù)組的前200個(gè),于是s就是一個(gè)200個(gè)元素的slice。

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

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


2.2 slice

一個(gè)slice是一個(gè)數(shù)組某個(gè)部分的引用。在內(nèi)存中,它是一個(gè)包含3個(gè)域的結(jié)構(gòu)體:指向slice中第一個(gè)元素的指針,slice的長度,以及slice的容量。長度是下標(biāo)操作的上界,如x[i]中i必須小于長度。容量是分割操作的上界,如x[i:j]中j不能大于容量。

數(shù)組的slice并不會(huì)實(shí)際復(fù)制一份數(shù)據(jù),它只是創(chuàng)建一個(gè)新的數(shù)據(jù)結(jié)構(gòu),包含了另外的一個(gè)指針,一個(gè)長度和一個(gè)容量數(shù)據(jù)。 如同分割一個(gè)字符串,分割數(shù)組也不涉及復(fù)制操作:它只是新建了一個(gè)結(jié)構(gòu)來放置一個(gè)不同的指針,長度和容量。在例子中,對(duì)[]int{2,3,5,7,11}求值操作會(huì)創(chuàng)建一個(gè)包含五個(gè)值的數(shù)組,并設(shè)置x的屬性來描述這個(gè)數(shù)組。分割表達(dá)式x[1:3]并不分配更多的數(shù)據(jù):它只是寫了一個(gè)新的slice結(jié)構(gòu)的屬性來引用相同的存儲(chǔ)數(shù)據(jù)。在例子中,長度為2--只有y[0]和y[1]是有效的索引,但是容量為4--y[0:4]是一個(gè)有效的分割表達(dá)式。

由于slice是不同于指針的多字長結(jié)構(gòu),分割操作并不需要分配內(nèi)存,甚至沒有通常被保存在堆中的slice頭部。這種表示方法使slice操作和在C中傳遞指針、長度對(duì)一樣廉價(jià)。Go語言最初使用一個(gè)指向以上結(jié)構(gòu)的指針來表示slice,但是這樣做意味著每個(gè)slice操作都會(huì)分配一塊新的內(nèi)存對(duì)象。即使使用了快速的分配器,還是給垃圾收集器制造了很多沒有必要的工作。移除間接引用及分配操作可以讓slice足夠廉價(jià),以避免傳遞顯式索引。

slice的擴(kuò)容

其實(shí)slice在Go的運(yùn)行時(shí)庫中就是一個(gè)C語言動(dòng)態(tài)數(shù)組的實(shí)現(xiàn),在$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

};

在對(duì)slice進(jìn)行append等操作時(shí),可能會(huì)造成slice的自動(dòng)擴(kuò)容。其擴(kuò)容時(shí)的大小增長規(guī)則是:

如果新的大小是當(dāng)前大小2倍以上,則大小增長為新大小

否則循環(huán)以下操作:如果當(dāng)前大小小于1024,按每次2倍增長,否則每次按當(dāng)前大小1/4增長。直到增長的大小超過或等于新大小。

make和new

Go有兩個(gè)數(shù)據(jù)結(jié)構(gòu)創(chuàng)建函數(shù):new和make。兩者的區(qū)別在學(xué)習(xí)Go語言的初期是一個(gè)常見的混淆點(diǎn)?;镜膮^(qū)別是new(T)返回一個(gè)*T,返回的這個(gè)指針可以被隱式地消除引用(圖中的黑色箭頭)。而make(T, args)返回一個(gè)普通的T。通常情況下,T內(nèi)部有一些隱式的指針(圖中的灰色箭頭)。一句話,new返回一個(gè)指向已清零內(nèi)存的指針,而make返回一個(gè)復(fù)雜的結(jié)構(gòu)。

有一種方法可以統(tǒng)一這兩種創(chuàng)建方式,但是可能會(huì)與C/C++的傳統(tǒng)有顯著不同:定義make(*T)來返回一個(gè)指向新分配的T的指針,這樣一來,new(Point)得寫成make(*Point)。但這樣做實(shí)在是和人們期望的分配函數(shù)太不一樣了,所以Go沒有采用這種設(shè)計(jì)。

slice與unsafe.Pointer相互轉(zhuǎn)換

有時(shí)候可能需要使用一些比較tricky的技巧,比如利用make弄一塊內(nèi)存自己管理,或者用cgo之類的方式得到的內(nèi)存,轉(zhuǎn)換為Go類型使用。

從slice中得到一塊內(nèi)存地址是很容易的:

s := make([]byte, 200)

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

從一個(gè)內(nèi)存指針構(gòu)造出Go語言的slice結(jié)構(gòu)相對(duì)麻煩一些,比如其中一種方式:

var ptr unsafe.Pointer

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

先將ptr強(qiáng)制類型轉(zhuǎn)換為另一種指針,一個(gè)指向[1<<10]byte數(shù)組的指針,這里數(shù)組大小其實(shí)是假的。然后用slice操作取出這個(gè)數(shù)組的前200個(gè),于是s就是一個(gè)200個(gè)元素的slice。

或者這種方式:

var ptr unsafe.Pointer

var s1 = struct {

addr uintptr

len int

cap int

}{ptr, length, length}

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

把slice的底層結(jié)構(gòu)寫出來,將addr,len,cap等字段寫進(jìn)去,將這個(gè)結(jié)構(gòu)體賦給s。相比上一種寫法,這種更好的地方在于cap更加自然,雖然上面寫法中實(shí)際上1<<10就是cap。

或者使用reflect.SliceHeader的方式來構(gòu)造slice,比較推薦這種做法:

var o []byte

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

sliceHeader.Cap = length

sliceHeader.Len = length

sliceHeader.Data = uintptr(ptr)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容