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.本質
- 檢索出內存地址為 200 的值,並將其存儲在 CPU 中
- 將存儲在 CPU 中的值乘以 3
- 將 CPU 中存儲的結果,寫入地址為 201 的內存塊中
3.2.基於變量的理解
- 獲取變量 a 中存儲的值,並將其存儲在 CPU 中
- 將其乘以 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