Go語言指針

目錄(?)[+]

1. 指針的概念

概念說明
變量是一種佔位符,用於引用計算機的內存地址。可理解爲內存地址的標籤
指針表示內存地址,表示地址的指向。指針是一個指向另一個變量內存地址的值
&取地址符,例如:{指針}:=&{變量}
*取值符,例如:{變量}:=*{指針}

2. 內存地址說明

2.1. 內存定義

計算機的內存 RAM 可以把它想象成一些有序的盒子,一個接一個的排成一排,每一個盒子或者單元格都被一個唯一的數字標記依次遞增,這個數字就是該單元格的地址,也就是內存的地址。 
這裏寫圖片描述

硬件角度:內存是CPU溝通的橋樑,程序運行在內存中。

邏輯角度:內存是一塊具備隨機訪問能力,支持讀寫操作,用來存放程序及程序運行中產生的數據的區域。

概念比喻
內存一層樓層
內存塊樓層中的一個房間
變量名房間的標籤,例如:總經理室
指針房間的具體地址(門牌號),例如:總經理室地址是2樓201室
變量值房間裏的具體存儲物
指針地址指針的地址:存儲指針內存塊的地址

2.2. 內存單位和編址

2.2.1. 內存單位

單位說明
位(bit)計算機中最小的數據單位,每一位的狀態只能是0或1
字節(Byte)1Byte=8bit,是內存基本的計量單位
“字”由若干個字節構成,字的位數叫字長,不同檔次的機器有不同的字長
KB1KB=1024Byte,即1024個字節
MB1MB=1024KB
GB1GB=1024MB

2.2.2. 內存編址

計算機中的內存按字節編址,每個地址的存儲單元可以存放一個字節的數據,CPU通過內存地址獲取指令和數據,並不關心這個地址所代表的空間在什麼位置,內存地址和地址指向的空間共同構成了一個內存單元。

2.2.3. 內存地址

內存地址通常用16進制的數據表示,例如0x0ffc1。

3.變量與指針運算理解

編寫一段程序,檢索出值並存儲在地址爲 200 的一個塊內存中,將其乘以 3,並將結果存儲在地址爲 201 的另一塊內存中

3.1.本質

  1. 檢索出內存地址爲 200 的值,並將其存儲在 CPU 中
  2. 將存儲在 CPU 中的值乘以 3
  3. 將 CPU 中存儲的結果,寫入地址爲 201 的內存塊中

這裏寫圖片描述

3.2.基於變量的理解

  1. 獲取變量 a 中存儲的值,並將其存儲在 CPU 中
  2. 將其乘以 3
  3. 將結果保存在變量 b 中

這裏寫圖片描述

var a = 6 
var b = a * 3

3.3.基於指針的理解

func main() {
    a := 200
    b := &a
    *b++
    fmt.Println(a)
}

以上函數對a進行+1操作,具體理解如下:

1.a:=200

這裏寫圖片描述

2. b := &a

這裏寫圖片描述

3. *b++

這裏寫圖片描述

這裏寫圖片描述

4. 指針的使用

4.1. 方法中的指針

方法即爲有接受者的函數,接受者可以是類型的實例變量或者是類型的實例指針變量。但兩種效果不同。

1、類型的實例變量

func main(){
    person := Person{"vanyar", 21}
    fmt.Printf("person<%s:%d>\n", person.name, person.age)
    person.sayHi()
    person.ModifyAge(210)
    person.sayHi()
}
type Person struct {
    name string
    age int
}
func (p Person) sayHi() {
    fmt.Printf("SayHi -- This is %s, my age is %d\n",p.name, p.age)
}
func (p Person) ModifyAge(age int) {
    fmt.Printf("ModifyAge")
    p.age = age
}


//輸出結果
person<vanyar:21>
SayHi -- This is vanyar, my age is 21
ModifyAgeSayHi -- This is vanyar, my age is 21

儘管 ModifyAge 方法修改了其age字段,可是方法裏的p是person變量的一個副本,修改的只是副本的值。下一次調用sayHi方法的時候,還是person的副本,因此修改方法並不會生效。

即實例變量的方式並不會改變接受者本身的值。

2、類型的實例指針變量

func (p *Person) ChangeAge(age int)  {
    fmt.Printf("ModifyAge")
    p.age = age
}

Go會根據Person的示例類型,轉換成指針類型再拷貝,即 person.ChangeAge會變成 (&person).ChangeAge。

指針類型的接受者,如果實例對象是值,那麼go會轉換成指針,然後再拷貝,如果本身就是指針對象,那麼就直接拷貝指針實例。因爲指針都指向一處值,就能修改對象了。

5. 零值與nil(空指針)

變量聲明而沒有賦值,默認爲零值,不同類型零值不同,例如字符串零值爲空字符串;

指針聲明而沒有賦值,默認爲nil,即該指針沒有任何指向。當指針沒有指向的時候,不能對(*point)進行操作包括讀取,否則會報空指針異常。

func main(){
    // 聲明一個指針變量 aPot 其類型也是 string
    var aPot *string
    fmt.Printf("aPot: %p %#v\n", &aPot, aPot) // 輸出 aPot: 0xc42000c030 (*string)(nil)
    *aPot = "This is a Pointer"  // 報錯: panic: runtime error: invalid memory address or nil pointer dereference
}

解決方法即給該指針分配一個指向,即初始化一個內存,並把該內存地址賦予指針變量,例如:

// 聲明一個指針變量 aPot 其類型也是 string
    var aPot *string
    fmt.Printf("aPot: %p %#v\n", &aPot, aPot) // 輸出 aPot: 0xc42000c030 (*string)(nil)

    aPot = &aVar
    *aPot = "This is a Pointer"
    fmt.Printf("aVar: %p %#v \n", &aVar, aVar) // 輸出 aVar: 0xc42000e240 "This is a Pointer"
    fmt.Printf("aPot: %p %#v %#v \n", &aPot, aPot, *aPot) // 輸出 aPot: 0xc42000c030 (*string)(0xc42000e240) "This is a Pointer"

或者通過new開闢一個內存,並返回這個內存的地址。

var aNewPot *int

aNewPot = new(int)
*aNewPot = 217
fmt.Printf("aNewPot: %p %#v %#v \n", &aNewPot, aNewPot, *aNewPot) // 輸出 aNewPot: 0xc42007a028 (*int)(0xc42006e1f0) 217

6. 總結

  • Golang提供了指針用於操作數據內存,並通過引用來修改變量。
  • 只聲明未賦值的變量,golang都會自動爲其初始化爲零值,基礎數據類型的零值比較簡單,引用類型和指針的零值都爲nil,nil類型不能直接賦值,因此需要通過new開闢一個內存,或者通過make初始化數據類型,或者兩者配合,然後才能賦值。
  • 指針也是一種類型,不同於一般類型,指針的值是地址,這個地址指向其他的內存,通過指針可以讀取其所指向的地址所存儲的值。
  • 函數方法的接受者,也可以是指針變量。無論普通接受者還是指針接受者都會被拷貝傳入方法中,不同在於拷貝的指針,其指向的地方都一樣,只是其自身的地址不一樣。

參考: 
http://www.jianshu.com/p/d23f78a3922b 
http://www.jianshu.com/p/44b9429d7bef

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章