Golang系列(五)之Golang指針


1. 指針的概念

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

2. 內存地址說明

2.1. 內存定義

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

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

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

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

2.2. 內存單位和編址

2.2.1. 內存單位

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM