Go語言基礎(chǔ)之指針

區(qū)別于C/C++中的指針,Go語言中的指針不能進(jìn)行偏移和運(yùn)算,是安全指針。

要搞明白Go語言中的指針需要先知道3個(gè)概念:指針地址、指針類型和指針取值。

Go語言中的指針

任何程序數(shù)據(jù)載入內(nèi)存后,在內(nèi)存都有他們的地址,這就是指針。而為了保存一個(gè)數(shù)據(jù)在內(nèi)存中的地址,我們就需要指針變量。

比如,“永遠(yuǎn)不要高估自己”這句話是我的座右銘,我想把它寫入程序中,程序一啟動(dòng)這句話是要加載到內(nèi)存(假設(shè)內(nèi)存地址0x123456),我在程序中把這段話賦值給變量A,把內(nèi)存地址賦值給變量B。這時(shí)候變量B就是一個(gè)指針變量。通過變量A和變量B都能找到我的座右銘。

Go語言中的指針不能進(jìn)行偏移和運(yùn)算,因此Go語言中的指針操作非常簡單,我們只需要記住兩個(gè)符號(hào):&(取地址)和*(根據(jù)地址取值)。

指針地址和指針類型

每個(gè)變量在運(yùn)行時(shí)都擁有一個(gè)地址,這個(gè)地址代表變量在內(nèi)存中的位置。Go語言中使用&字符放在變量前面對(duì)變量進(jìn)行“取地址”操作。 Go語言中的值類型(int、float、bool、string、array、struct)都有對(duì)應(yīng)的指針類型,如:*int、*int64*string等。

取變量指針的語法如下:

ptr := &v    // v的類型為T

其中:

  • v:代表被取地址的變量,類型為T
  • ptr:用于接收地址的變量,ptr的類型就為*T,稱做T的指針類型。*代表指針。

舉個(gè)例子:

func main() {
    a := 10
    b := &a
    fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
    fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
    fmt.Println(&b)                    // 0xc00000e018
}

我們來看一下b := &a的圖示:

取變量地址圖示

指針取值

在對(duì)普通變量使用&操作符取地址后會(huì)獲得這個(gè)變量的指針,然后可以對(duì)指針使用*操作,也就是指針取值,代碼如下。

func main() {
    //指針取值
    a := 10
    b := &a // 取變量a的地址,將指針保存到b中
    fmt.Printf("type of b:%T\n", b)
    c := *b // 指針取值(根據(jù)指針去內(nèi)存取值)
    fmt.Printf("type of c:%T\n", c)
    fmt.Printf("value of c:%v\n", c)
}

輸出如下:

type of b:*int
type of c:int
value of c:10

總結(jié): 取地址操作符&和取值操作符*是一對(duì)互補(bǔ)操作符,&取出地址,*根據(jù)地址取出地址指向的值。

變量、指針地址、指針變量、取地址、取值的相互關(guān)系和特性如下:

  • 對(duì)變量進(jìn)行取地址(&)操作,可以獲得這個(gè)變量的指針變量。
  • 指針變量的值是指針地址。
  • 對(duì)指針變量進(jìn)行取值(*)操作,可以獲得指針變量指向的原變量的值。

指針傳值示例:

func modify1(x int) {
    x = 100
}

func modify2(x *int) {
    *x = 100
}

func main() {
    a := 10
    modify1(a)
    fmt.Println(a) // 10
    modify2(&a)
    fmt.Println(a) // 100
}

new和make

我們先來看一個(gè)例子:

func main() {
    var a *int
    *a = 100
    fmt.Println(*a)

    var b map[string]int
    b["沙河娜扎"] = 100
    fmt.Println(b)
}

執(zhí)行上面的代碼會(huì)引發(fā)panic,為什么呢? 在Go語言中對(duì)于引用類型的變量,我們?cè)谑褂玫臅r(shí)候不僅要聲明它,還要為它分配內(nèi)存空間,否則我們的值就沒辦法存儲(chǔ)。而對(duì)于值類型的聲明不需要分配內(nèi)存空間,是因?yàn)樗鼈冊(cè)诼暶鞯臅r(shí)候已經(jīng)默認(rèn)分配好了內(nèi)存空間。要分配內(nèi)存,就引出來今天的new和make。 Go語言中new和make是內(nèi)建的兩個(gè)函數(shù),主要用來分配內(nèi)存。

new

new是一個(gè)內(nèi)置的函數(shù),它的函數(shù)簽名如下:

func new(Type) *Type

其中,

  • Type表示類型,new函數(shù)只接受一個(gè)參數(shù),這個(gè)參數(shù)是一個(gè)類型
  • *Type表示類型指針,new函數(shù)返回一個(gè)指向該類型內(nèi)存地址的指針。

new函數(shù)不太常用,使用new函數(shù)得到的是一個(gè)類型的指針,并且該指針對(duì)應(yīng)的值為該類型的零值。舉個(gè)例子:

func main() {
    a := new(int)
    b := new(bool)
    fmt.Printf("%T\n", a) // *int
    fmt.Printf("%T\n", b) // *bool
    fmt.Println(*a)       // 0
    fmt.Println(*b)       // false
}   

本節(jié)開始的示例代碼中var a *int只是聲明了一個(gè)指針變量a但是沒有初始化,指針作為引用類型需要初始化后才會(huì)擁有內(nèi)存空間,才可以給它賦值。應(yīng)該按照如下方式使用內(nèi)置的new函數(shù)對(duì)a進(jìn)行初始化之后就可以正常對(duì)其賦值了:

func main() {
    var a *int
    a = new(int)
    *a = 10
    fmt.Println(*a)
}

make

make也是用于內(nèi)存分配的,區(qū)別于new,它只用于slice、map以及chan的內(nèi)存創(chuàng)建,而且它返回的類型就是這三個(gè)類型本身,而不是他們的指針類型,因?yàn)檫@三種類型就是引用類型,所以就沒有必要返回他們的指針了。make函數(shù)的函數(shù)簽名如下:

func make(t Type, size ...IntegerType) Type

make函數(shù)是無可替代的,我們?cè)谑褂胹lice、map以及channel的時(shí)候,都需要使用make進(jìn)行初始化,然后才可以對(duì)它們進(jìn)行操作。這個(gè)我們?cè)谏弦徽轮卸加姓f明,關(guān)于channel我們會(huì)在后續(xù)的章節(jié)詳細(xì)說明。

本節(jié)開始的示例中var b map[string]int只是聲明變量b是一個(gè)map類型的變量,需要像下面的示例代碼一樣使用make函數(shù)進(jìn)行初始化操作之后,才能對(duì)其進(jìn)行鍵值對(duì)賦值:

func main() {
    var b map[string]int
    b = make(map[string]int, 10)
    b["沙河娜扎"] = 100
    fmt.Println(b)
}

new與make的區(qū)別

  1. 二者都是用來做內(nèi)存分配的。
  2. make只用于slice、map以及channel的初始化,返回的還是這三個(gè)引用類型本身;
  3. 而new用于類型的內(nèi)存分配,并且內(nèi)存對(duì)應(yīng)的值為類型零值,返回的是指向類型的指針。
?著作權(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)容