Go語言學習10-指針類型

3.Go語言數據類型

書接上篇,咱們瞭解了Go語言的 結構體類型,本篇介紹Go語言的指針類型。主要以下:git

3.8 指針

指針是一個表明着某個內存地址的值。這個內存地址每每是在內存中存儲的另外一個變量的值的起始位置。Go語言既沒有像Java語言那樣取消了代碼對指針的直接操做的能力,也避免了C/C++語言中因爲對指針的濫用而形成的安全和可靠性問題。github

Go語言的指針類型指代了指向一個給定類型的變量的指針。它經常被稱爲指針的基本類型。指針類型是Go語言的複合類型之一。json

3.8.1 類型表示法

能夠經過在任何一個有效的數據類型的左邊加入 * 來獲得與之對應的指針類型。例如,一個元素類型爲 int 的切片類型所對應的指針類型是 *[]int ,前面的結構體類型 Sequence 所對應的指針類型是 *Sequence安全

注意:若是表明類型的是一個限定標識符(如 sort.StringSlice),那麼表示與其對應的指針類型的字面量應該是 *sort.StringSlice ,而不是 sort.*StringSlice框架

在Go語言中,還有一個專門用於存儲內存地址的類型 uintptr。而 uintptr 類型與 int 類型和 uint 類型同樣,也屬於整數類型。它的值是一個可以保存一個指針類型值(簡稱指針值)的位模式形式。分佈式

3.8.2 值表示法

若是一個變量 v 的值是可尋址的,表達式 &v 就表明了指向變量 v 的值的指針值。ide

知識點: 若是某個值確實被存儲在了計算機中,而且有一個內存地址能夠表明這個值在內存中存儲的起始位置,那麼就能夠說這個值以及表明它的變量是可尋址的函數

3.8.3 屬性和基本操做

指針類型屬於引用類型,它的零值是 nil佈局

對指針的操做,從標準代碼包 unsafe 講起,以下爲省略文檔的 unsafe 包下面的 unsafe.go 的源碼(可自行到Go安裝包 src 目錄查看詳細內容):ui

package unsafe

type ArbitraryType int
type Pointer *ArbitraryType

func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr

在代碼包 unsafe 中,有一個名爲 ArbitraryType 的類型。從類型聲明上看,它是 int 類型的一個別名類型。可是,它實際上能夠表明任意的Go語言表達式的結果類型。事實上,它也並不算是 unsafe 包的一部分,在這裏聲明它僅處於代碼文檔化的目的。另外 unsafe 還聲明瞭一個名爲 Pointer 的類型,它表明了ArbitraryType 類型的指針類型。

以下有4個與 unsafe.Pointer 類型相關的特殊轉換操做:

  1. 一個指向其餘類型的指針值均可以被轉換爲一個unsafe.Pointer類型值。例如,若是有一個float32類型的變量f32,那麼能夠將與它的對應的指針值轉換爲一個unsafe.Pointer類型的值:

    pointer := unsafe.Pointer(&f32)

    其中,在特殊標記 := 右邊就是用於進行轉換操做的調用表達式。取值表達式 &f32 的求值結果是一個 *float32 類型的值。

  2. 一個 unsafe.Pointer 類型值能夠被轉換爲一個與任何類型對應的指針類型的值。例如:

    vptr := (*int)(pointer)

    上面的代碼用於將 pointer 的值轉換爲與指向int類型值的指針值,並賦值給變量 vptr*int* 類型和 float32 類型在內存中的佈局是不一樣的,若是咱們在它們之上直接進行類型轉換(對應表達式 (int)(&f32)) 是不行,這會產生一個編譯錯誤。有了上面的 unsafe.Pointer 做爲中轉類型的時候,看起來操做沒有問題,但在使用取值表達式 vptr 的時候會出現問題,int 類型的值和 float32 類型的值解析獲得的結果是徹底不一樣的,這樣會產生一個不正確的結果。好比,若是這裏對變量 vptr** 的賦值語句改成:

    vptr := (*string)(pointer)

    取值表達式 *vptr的求值就會引起一個運行時恐慌。

  3. 一個 uintptr 類型的值也能夠被轉換爲一個 unsafe.Pointer 類型的值。例如:

    pointer2 := unsafe.Pointer(uptr)
  4. 一個 unsafe.Pointer 類型值能夠被轉換爲一個 uintptr 類型的值。例如:

    uptr := uintptr(pointer)

注意:正是由於這些特殊的轉換操做,unsafe.Pointer 類型可使程序繞過Go語言的類型系統並在任意的內存地址上進行讀寫操做成爲可能。但這些操做很是危險,當心使用

如今用以前的結構體類型 Person 舉例,以下:

type Person struct {
    Name    string `json:"name"`
    Age     uint8 `json:"age"`
    Address string `json:"addr"`
}

初始化 Person 的值,並把它的指針值賦給變量 p :

p := &Person(「Huazie」, 23, 「Nanjing」)

下面利用上述特殊轉換操做中的第一條和第三條獲取這個結構體值在內存中的存儲地址:

var puptr = uintptr(unsafe.Pointer(p))

變量 puptr 的值就是存儲上面那個 Person 類型值的內存地址。因爲類型 uintptr 的值其實是一個無符號整數,因此咱們能夠在該類型的值上進行任何算術運算。例如:

// 變量np表示結構體中的Name字段值的內存地址。
var np uintptr = puptr + unsafe.Offsetof(p.Name)

如上 unsafe.Offsetof 函數會返回做爲參數的某字段(由相應的選擇表達式表示)在其所屬的結構體類型之中的存儲偏移量。也就是,在內存中從存儲這個結構體值的起始位置到存儲其中某字段的值的起始位置之間的距離。這個存儲偏移量(或者說距離)的單位是字節,它的值的類型是 uintptr。對於同一個結構體類型和它的同一個字段來講,這個存儲偏移量老是相同的。

在得到存儲 Name 字段值的內存地址以後,將它還原成指向這個 Name 字段值的指針類型值,以下:

var name *string = (*string)(unsafe.Pointer(np))

獲取這個 Name 字段的值:

*name

只要得到了存儲某個值的內存地址,就能夠經過必定的算術運算獲得存儲在其餘內存地址上的值甚至程序。以下一個恆等式顯示上面的一些操做:

uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))

結語

Go數據類型的知識就記到這,下一篇介紹 Go語言數據的使用。其中 通道類型,比較特殊,將會在後續的博文仔細講解,敬請期待!!!

最後附上知名的Go語言開源框架:

Skynet: 一個分佈式服務框架。它能夠幫助咱們構建起大規模的分佈式應用系統。它的源碼放置在https://github.com/skynetservices/skynet上。

相關文章
相關標籤/搜索