Golang通脈之指針

指針的概念

指針是存儲(chǔ)另一個(gè)變量的內(nèi)存地址的變量疟赊。

變量是一種使用方便的占位符,用于引用計(jì)算機(jī)內(nèi)存地址峡碉。

一個(gè)指針變量可以指向任何一個(gè)值的內(nèi)存地址近哟。

image

在上面的圖中,變量b的值為156鲫寄,存儲(chǔ)在內(nèi)存地址0x1040a124吉执。變量a持有b的地址,現(xiàn)在a被認(rèn)為指向b地来。

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

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

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

聲明指針

聲明指針,*T是指針變量的類型肌似,它指向T類型的值费就。

var var_name *var-type

var-type 為指針類型,var_name 為指針變量名川队,* 號(hào)用于指定變量是作為一個(gè)指針力细。

var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮點(diǎn)型 */

示例代碼:

func main() {
   var a int= 20   /* 聲明實(shí)際變量 */
   var ip *int        /* 聲明指針變量 */

   ip = &a  /* 指針變量的存儲(chǔ)地址 */
   fmt.Printf("a 變量的地址是: %x\n", &a  )
   /* 指針變量的存儲(chǔ)地址 */
   fmt.Printf("ip 變量的存儲(chǔ)地址: %x\n", ip )
   /* 使用指針訪問(wèn)值 */
   fmt.Printf("*ip 變量的值: %d\n", *ip )
}

運(yùn)行結(jié)果:

a 變量的地址是: 20818a220
ip 變量的存儲(chǔ)地址: 20818a220
*ip 變量的值: 20

示例代碼:

type name int8
type first struct {
    a int
    b bool
    name
}

func main() {
    a := new(first)
    a.a = 1
    a.name = 11
    fmt.Println(a.b, a.a, a.name)
}

運(yùn)行結(jié)果:

false 1 11

未初始化的變量自動(dòng)賦上初始值

type name int8
type first struct {
    a int
    b bool
    name
}

func main() {
    var a = first{1, false, 2}
    var b *first = &a
    fmt.Println(a.b, a.a, a.name, &a, b.a, &b, (*b).a)
}

運(yùn)行結(jié)果:

false 1 2 &{1 false 2} 1 0xc042068018 1

獲取指針地址在指針變量前加&的方式

指針地址和指針類型

每個(gè)變量在運(yùn)行時(shí)都擁有一個(gè)地址,這個(gè)地址代表變量在內(nèi)存中的位置固额。Go語(yǔ)言中使用&字符放在變量前面對(duì)變量進(jìn)行“取地址”操作眠蚂。 Go語(yǔ)言中的值類型(int、float斗躏、bool逝慧、string谍珊、array间校、struct)都有對(duì)應(yīng)的指針類型撒妈,如:*int伦乔、*int64华畏、*string等欠窒。

取變量指針的語(yǔ)法如下:

ptr := &v    // v的類型為T       取v的地址   其實(shí)就是把v的地址引用給了ptr,此時(shí)v和ptr指向了同一塊內(nèi)存地址,任一變量值的修改都會(huì)影響另一個(gè)變量的值

其中:

  • v:代表被取地址的變量坡锡,類型為T
  • ptr:用于接收地址的變量浪听,ptr的類型就為*T燕雁,稱做T的指針類型诞丽。*代表指針。
func main() {
   var a int = 10   
   fmt.Printf("變量a的地址: %x\n", &a  )
   b := &a
   fmt.Printf("變量b: %v\n", b  )
   fmt.Printf("變量b的地址: %v\n", &b  )
}

運(yùn)行結(jié)果:

變量a的地址: 0x20818a220
變量b: 0x20818a220
變量b的地址: 0x263e94530

b := &a的圖示:

image

指針取值

在對(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)行取值(*)操作,可以獲得指針變量指向的原變量的值著觉。

使用指針傳遞函數(shù)的參數(shù)

func change(val *int) {  
    *val = 55
}
func main() {  
    a := 58
    fmt.Println("value of a before function call is",a)
    b := &a
    change(b)
    fmt.Println("value of a after function call is", a)
}

運(yùn)行結(jié)果

value of a before function call is 58  
value of a after function call is 55  

不要將一個(gè)指向數(shù)組的指針傳遞給函數(shù)村生。使用切片。

假設(shè)想對(duì)函數(shù)內(nèi)的數(shù)組進(jìn)行一些修改饼丘,并且對(duì)調(diào)用者可以看到函數(shù)內(nèi)的數(shù)組所做的更改趁桃。一種方法是將一個(gè)指向數(shù)組的指針傳遞給函數(shù)。

func modify(arr *[3]int) {  
    (*arr)[0] = 90
}

func main() {  
    a := [3]int{89, 90, 91}
    modify(&a)
    fmt.Println(a)
}

運(yùn)行結(jié)果

[90 90 91]

示例代碼:

func modify(arr *[3]int) {  
    arr[0] = 90
}

func main() {  
    a := [3]int{89, 90, 91}
    modify(&a)
    fmt.Println(a)
}

運(yùn)行結(jié)果

[90 90 91]

雖然將指針傳遞給一個(gè)數(shù)組作為函數(shù)的參數(shù)并對(duì)其進(jìn)行修改肄鸽,但這并不是實(shí)現(xiàn)這一目標(biāo)的慣用方法卫病。切片是首選:

func modify(sls []int) {  
    sls[0] = 90
}

func main() {  
    a := [3]int{89, 90, 91}
    modify(a[:])
    fmt.Println(a)
}

運(yùn)行結(jié)果:

[90 90 91]

Go不支持指針?biāo)惴ā?/p>

func main() {
    b := [...]int{109, 110, 111} p := &b p++ 
}

nvalid operation: p++ (non-numeric type *[3]int)

指針數(shù)組

有一種情況,我們可能需要保存數(shù)組典徘,這樣就需要使用到指針蟀苛。

const MAX int = 3

func main() {
   a := []int{10,100,200}
   var i int
   var ptr [MAX]*int;

   for  i = 0; i < MAX; i++ {
      ptr[i] = &a[i] /* 整數(shù)地址賦值給指針數(shù)組 */
   }

   for  i = 0; i < MAX; i++ {
      fmt.Printf("a[%d] = %d\n", i,*ptr[i] )
   } 
}
結(jié)果
a[0] = 10
a[1] = 100
a[2] = 200

指針的指針

如果一個(gè)指針變量存放的又是另一個(gè)指針變量的地址,則稱這個(gè)指針變量為指向指針的指針變量烂斋。

func main() {

   var a int
   var ptr *int
   var pptr **int

   a = 3000

   /* 指針 ptr 地址 */
   ptr = &a

   /* 指向指針 ptr 地址 */
   pptr = &ptr

   /* 獲取 pptr 的值 */
   fmt.Printf("變量 a = %d\n", a )
   fmt.Printf("指針變量 *ptr = %d\n", *ptr )
   fmt.Printf("指向指針的指針變量 **pptr = %d\n", **pptr)
}
結(jié)果
變量 a = 3000
指針變量 *ptr = 3000
指向指針的指針變量 **pptr = 3000

指針作為函數(shù)參數(shù)

package main

import "fmt"

func main() {
   /* 定義局部變量 */
   var a int = 100
   var b int= 200

   fmt.Printf("交換前 a 的值 : %d\n", a )
   fmt.Printf("交換前 b 的值 : %d\n", b )

   /* 調(diào)用函數(shù)用于交換值
   * &a 指向 a 變量的地址
   * &b 指向 b 變量的地址
   */
   swap(&a, &b);

   fmt.Printf("交換后 a 的值 : %d\n", a )
   fmt.Printf("交換后 b 的值 : %d\n", b )
}

func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址的值 */
   *x = *y      /* 將 y 賦值給 x */
   *y = temp    /* 將 temp 賦值給 y */
}
結(jié)果
交換前 a 的值 : 100
交換前 b 的值 : 200
交換后 a 的值 : 200
交換后 b 的值 : 100

空指針

Go 空指針 當(dāng)一個(gè)指針被定義后沒(méi)有分配到任何變量時(shí)屹逛,它的值為 nil础废。 nil 指針也稱為空指針汛骂。 nil在概念上和其它語(yǔ)言的null、None评腺、nil帘瞭、NULL一樣,都指代零值或空值蒿讥。 一個(gè)指針變量通车睿縮寫為 ptr。

空指針判斷:

if(ptr != nil)     /* ptr 不是空指針 */
if(ptr == nil)    /* ptr 是空指針 */
func main() {
   var sp *string
   *sp = "張三"
   fmt.Println(*sp)
}

運(yùn)行這些代碼芋绸,會(huì)看到如下錯(cuò)誤信息:

panic: runtime error: invalid memory address or nil pointer dereference

這是因?yàn)橹羔橆愋偷淖兞咳绻麤](méi)有分配內(nèi)存媒殉,就默認(rèn)是零值 nil,它沒(méi)有指向的內(nèi)存摔敛,所以無(wú)法使用廷蓉,強(qiáng)行使用就會(huì)得到以上 nil 指針錯(cuò)誤。

指針使用

  1. 指針可以修改指向數(shù)據(jù)的值马昙;
  2. 在變量賦值桃犬,參數(shù)傳值的時(shí)候可以節(jié)省內(nèi)存。

注意事項(xiàng)

  1. 不要對(duì) map行楞、slice攒暇、channel 這類引用類型使用指針;
  2. 如果需要修改方法接收者內(nèi)部的數(shù)據(jù)或者狀態(tài)時(shí)子房,需要使用指針形用;
  3. 如果需要修改參數(shù)的值或者內(nèi)部數(shù)據(jù)時(shí)就轧,也需要使用指針類型的參數(shù);
  4. 如果是比較大的結(jié)構(gòu)體田度,每次參數(shù)傳遞或者調(diào)用方法都要內(nèi)存拷貝钓丰,內(nèi)存占用多,這時(shí)候可以考慮使用指針每币;
  5. 像 int携丁、bool 這樣的小數(shù)據(jù)類型沒(méi)必要使用指針;
  6. 如果需要并發(fā)安全兰怠,則盡可能地不要使用指針梦鉴,使用指針一定要保證并發(fā)安全;
  7. 指針最好不要嵌套揭保,也就是不要使用一個(gè)指向指針的指針肥橙,雖然 Go 語(yǔ)言允許這么做,但是這會(huì)使代碼變得異常復(fù)雜秸侣。

new 和 make

我們知道對(duì)于值類型來(lái)說(shuō)存筏,即使只聲明一個(gè)變量,沒(méi)有對(duì)其初始化味榛,該變量也會(huì)有分配好的內(nèi)存椭坚。

func main() {
   var s string
   fmt.Printf("%p\n",&s)
}

結(jié)構(gòu)體也是值類型,比如 var wg sync.WaitGroup 聲明的變量 wg 搏色,不進(jìn)行初始化也可以直接使用善茎,Go 語(yǔ)言自動(dòng)分配了內(nèi)存,所以可以直接使用频轿,不會(huì)報(bào) nil 異常垂涯。

于是可以得到結(jié)論:如果要對(duì)一個(gè)變量賦值,這個(gè)變量必須有對(duì)應(yīng)的分配好的內(nèi)存航邢,這樣才可以對(duì)這塊內(nèi)存操作耕赘,完成賦值的目的。

其實(shí)不止賦值操作膳殷,對(duì)于指針變量操骡,如果沒(méi)有分配內(nèi)存,取值操作一樣會(huì)報(bào) nil 異常秽之,因?yàn)闆](méi)有可以操作的內(nèi)存当娱。

所以一個(gè)變量必須要經(jīng)過(guò)聲明、內(nèi)存分配才能賦值考榨,才可以在聲明的時(shí)候進(jìn)行初始化跨细。指針類型在聲明的時(shí)候,Go 語(yǔ)言并沒(méi)有自動(dòng)分配內(nèi)存河质,所以不能對(duì)其進(jìn)行賦值操作冀惭,這和值類型不一樣震叙。map 和 chan 也一樣,因?yàn)樗鼈儽举|(zhì)上也是指針類型散休。

要分配內(nèi)存媒楼,就引出來(lái)了內(nèi)置函數(shù)new()和make()。 Go語(yǔ)言中new和make是內(nèi)建的兩個(gè)函數(shù)戚丸,主要用來(lái)分配內(nèi)存划址。

new

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

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

其中限府,

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

它的作用就是根據(jù)傳入的類型申請(qǐng)一塊內(nèi)存,然后返回指向這塊內(nèi)存的指針署穗,指針指向的數(shù)據(jù)就是該類型的零值:

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
}   

通過(guò) new 函數(shù)分配內(nèi)存并返回指向該內(nèi)存的指針后寥裂,就可以通過(guò)該指針對(duì)這塊內(nèi)存進(jìn)行賦值、取值等操作案疲。

make

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

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

在使用 make 函數(shù)創(chuàng)建 map 的時(shí)候萍恕,其實(shí)調(diào)用的是 makemap 函數(shù):

// makemap implements Go map creation for make(map[k]v, hint).
func makemap(t *maptype, hint int, h *hmap) *hmap{
  //省略無(wú)關(guān)代碼
}

makemap 函數(shù)返回的是 *hmap 類型,而 hmap 是一個(gè)結(jié)構(gòu)體车要,它的定義如下所示:

// A header for a Go map.
type hmap struct {
   // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
   // Make sure this stays in sync with the compiler's definition.
   count     int // # live cells == size of map.  Must be first (used by len() builtin)
   flags     uint8
   B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
   noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
   hash0     uint32 // hash seed
   buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
   oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
   nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
   extra *mapextra // optional fields
}

可以看到允粤, map 關(guān)鍵字其實(shí)非常復(fù)雜,它包含 map 的大小 count翼岁、存儲(chǔ)桶 buckets 等类垫。要想使用這樣的 hmap,不是簡(jiǎn)單地通過(guò) new 函數(shù)返回一個(gè) *hmap 就可以琅坡,還需要對(duì)其進(jìn)行初始化悉患,這就是 make 函數(shù)要做的事情:

m:=make(map[string]int,10)

其實(shí) make 函數(shù)就是 map 類型的工廠函數(shù),它可以根據(jù)傳遞它的 K-V 鍵值對(duì)類型榆俺,創(chuàng)建不同類型的 map售躁,同時(shí)可以初始化 map 的大小坞淮。

make 函數(shù)不只是 map 類型的工廠函數(shù),還是 chan陪捷、slice 的工廠函數(shù)回窘。它同時(shí)可以用于 slice、chan 和 map 這三種類型的初始化市袖。

make函數(shù)是無(wú)可替代的啡直,在使用slice、map以及channel的時(shí)候苍碟,都需要使用make進(jìn)行初始化付枫,然后才可以對(duì)它們進(jìn)行操作。

new與make的區(qū)別

  1. 二者都是用來(lái)做內(nèi)存分配的驰怎。
  2. make只用于slice阐滩、map以及channel的初始化,返回的還是這三個(gè)引用類型本身县忌;
  3. new用于類型的內(nèi)存分配掂榔,并且內(nèi)存對(duì)應(yīng)的值為類型零值,返回的是指向?qū)?yīng)類型零值的指針症杏。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末装获,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子厉颤,更是在濱河造成了極大的恐慌穴豫,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逼友,死亡現(xiàn)場(chǎng)離奇詭異精肃,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)帜乞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門司抱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人黎烈,你說(shuō)我怎么就攤上這事习柠。” “怎么了照棋?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵资溃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我烈炭,道長(zhǎng)溶锭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任梳庆,我火速辦了婚禮暖途,結(jié)果婚禮上卑惜,老公的妹妹穿的比我還像新娘。我一直安慰自己驻售,他們只是感情好露久,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著欺栗,像睡著了一般毫痕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上迟几,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天消请,我揣著相機(jī)與錄音,去河邊找鬼类腮。 笑死臊泰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蚜枢。 我是一名探鬼主播缸逃,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼厂抽!你這毒婦竟也來(lái)了需频?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤筷凤,失蹤者是張志新(化名)和其女友劉穎昭殉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體藐守,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挪丢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吗伤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吃靠。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖足淆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情礁阁,我是刑警寧澤巧号,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站姥闭,受9級(jí)特大地震影響丹鸿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜棚品,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一靠欢、第九天 我趴在偏房一處隱蔽的房頂上張望廊敌。 院中可真熱鬧,春花似錦门怪、人聲如沸骡澈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肋殴。三九已至,卻和暖如春坦弟,著一層夾襖步出監(jiān)牢的瞬間护锤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工酿傍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烙懦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓赤炒,卻偏偏與公主長(zhǎng)得像修陡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子可霎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容