瞭解指針以前,先講一下什麼是變量。html
每當咱們編寫任何程序時,咱們都須要在內存中存儲一些數據/信息。數據存儲在特定地址的存儲器中。內存地址看起來像0xAFFFF
(這是內存地址的十六進制表示)。git
如今,要訪問數據,咱們須要知道存儲它的地址。咱們能夠跟蹤存儲與程序相關的數據的全部內存地址。但想象一下,記住全部內存地址並使用它們訪問數據會有很是困難。這就是爲何引入變量。github
變量是一種佔位符,用於引用計算機的內存地址,可理解爲內存地址的標籤。golang
什麼是指針算法
指針是存儲另外一個變量的內存地址的變量。因此指針也是一種變量,只不過它是一種特殊變量,它的值存放的是另外一個變量的內存地址。緩存
在上面的例子中,指針p
包含值0x0001
,該值是變量的地址a
。數據結構
unsafe包能夠獲取變量的內存使用狀況函數
Go語言提供如下基本數字類型:性能
無符號整數
uint8,uint16,uint32,uint64ui
符號整數
int8,int16,int32,int64
實數
float32,float64 Predeclared
整數(依賴系統類型,跟系統有關)
uint,int,uintptr (指針)
32位系統
uint=uint32
int=int32
uintptr爲32位的指針
64位系統
uint=uint64
int=int64
uintptr爲64位的指針
示例:
package main import ( "fmt" "unsafe" ) func main() { var uint8Value uint8 var uint16Value uint16 var uint32Value uint32 var uint64Value uint64 var int8Value int8 var int16Value int16 var int32Value int32 var int64Value int64 var float32Value float32 var float64Value float64 fmt.Println("uint8Value = Size:", unsafe.Sizeof(uint8Value)) //uint8Value = Size: 1 fmt.Println("uint16Value = Size:", unsafe.Sizeof(uint16Value)) //uint16Value = Size: 2 fmt.Println("uint32Value = Size:", unsafe.Sizeof(uint32Value)) //uint32Value = Size: 4 fmt.Println("uint64Value = Size:", unsafe.Sizeof(uint64Value))// uint64Value = Size: 8 fmt.Println("int8Value = Size:", unsafe.Sizeof(int8Value)) //int8Value = Size: 1 fmt.Println("int16Value = Size:", unsafe.Sizeof(int16Value))//int16Value = Size: 2 fmt.Println("int32Value = Size:", unsafe.Sizeof(int32Value))//int32Value = Size: 4 fmt.Println("int64Value = Size:", unsafe.Sizeof(int64Value)) //int64Value = Size: 8 fmt.Println("float32Value = Size:", unsafe.Sizeof(float32Value)) //float32Value = Size: 4 fmt.Println("float64Value = Size:", unsafe.Sizeof(float64Value))//float64Value = Size: 8 }
上面的是基本類型,接下來了解下複雜類型,以結構體類型爲例
type Example struct { BoolValue bool IntValue int16 FloatValue float32 }
該結構表明複雜類型。它表明7個字節,帶有三個不一樣的數字表示。bool是一個字節,int16是2個字節,float32增長4個字節。可是,在此結構的內存中實際分配了8個字節。
全部內存都分配在對齊邊界上,以最大限度地減小內存碎片整理。要肯定對齊邊界Go用於您的體系結構,您能夠運行unsafe.Alignof函數。Go爲64bit Darwin平臺的對齊邊界是8個字節。所以,當Go肯定結構的內存分配時,它將填充字節以確保最終內存佔用量是8的倍數。編譯器將肯定添加填充的位置。
什麼是內存對齊呢?
內存對齊,也叫邊界對齊(boundary alignment),是處理器爲了提升處理性能而對存取數據的起始地址所提出的一種要求。編譯器爲了使咱們編寫的C程序更有效,就必須最大限度地知足處理器對邊界對齊的要求。
從處理器的角度來看,須要儘量減小對內存的訪問次數以實現對數據結構進行更加高效的操做。爲何呢?由於儘管處理器包含了緩存,但它在處理數據時還得讀取緩存中的數據,讀取緩存的次數固然是越少越好!如上圖所示,在採用邊界對齊的狀況下,當處理器須要訪問a_變量和b_變量時都只需進行一次存取(圖中花括號表示一次存取操做)。若不採用邊界對齊,a_變量只要一次處理器操做,而b_變量卻至少要進行兩次操做。對於b_,處理器還得調用更多指令將其合成一個完整的4字節,這樣無疑大大下降了程序效率。
如下程序顯示Go插入到Example類型struct的內存佔用中的填充:
package main import ( "fmt" "unsafe" ) type Example struct { BoolValue bool IntValue int16 FloatValue float32 } func main() { example := &Example{ BoolValue: true, IntValue: 10, FloatValue: 3.141592, } exampleNext := &Example{ BoolValue: true, IntValue: 10, FloatValue: 3.141592, } alignmentBoundary := unsafe.Alignof(example) sizeBool := unsafe.Sizeof(example.BoolValue) offsetBool := unsafe.Offsetof(example.BoolValue) sizeInt := unsafe.Sizeof(example.IntValue) offsetInt := unsafe.Offsetof(example.IntValue) sizeFloat := unsafe.Sizeof(example.FloatValue) offsetFloat := unsafe.Offsetof(example.FloatValue) sizeBoolNext := unsafe.Sizeof(exampleNext.BoolValue) offsetBoolNext := unsafe.Offsetof(exampleNext.BoolValue) fmt.Printf("example Size: %d\n", unsafe.Sizeof(example)) fmt.Printf("Alignment Boundary: %d\n", alignmentBoundary) fmt.Printf("BoolValue = Size: %d Offset: %d Addr: %v\n", sizeBool, offsetBool, &example.BoolValue) fmt.Printf("IntValue = Size: %d Offset: %d Addr: %v\n", sizeInt, offsetInt, &example.IntValue) fmt.Printf("FloatValue = Size: %d Offset: %d Addr: %v\n", sizeFloat, offsetFloat, &example.FloatValue) fmt.Printf("Next = Size: %d Offset: %d Addr: %v\n", sizeBoolNext, offsetBoolNext, &exampleNext.BoolValue) }
輸出:
example Size: 8
Alignment Boundary: 8
BoolValue = Size: 1 Offset: 0 Addr: 0xc00004c080
IntValue = Size: 2 Offset: 2 Addr: 0xc00004c082
FloatValue = Size: 4 Offset: 4 Addr: 0xc00004c084
Next = Size: 1 Offset: 0 Addr: 0xc00004c088
類型結構的對齊邊界是預期的8個字節。
大小值顯示將讀取和寫入該字段的內存量。正如所料,大小與類型信息一致。
偏移值顯示進入內存佔用的字節數,咱們將找到該字段的開頭。
地址是能夠找到內存佔用內每一個字段的開頭的地方。
咱們能夠看到Go在BoolValue和IntValue字段之間填充1個字節。偏移值和兩個地址之間的差別是2個字節。您還能夠看到下一個內存分配是從結構中的最後一個字段開始4個字節。
聲明一個指針
使用如下語法聲明類型爲T的指針
var p *int
指針的零值是nil
。這意味着任何未初始化的指針都將具備該值nil
。讓咱們看一個完整的例子
package main import "fmt" func main() { var p *int &p=1 }
注意:當指針沒有指向的時候,不能對(*point)進行操做包括讀取,不然會報空指針異常。
示例:
package main func main() { var p *int *p = 1 //panic: runtime error: invalid memory address or nil pointer dereference }
解決方法即給該指針分配一個指向,即初始化一個內存,並把該內存地址賦予指針變量
示例:
import "fmt" func main() { var p *int var m int p = &m *p = 1 fmt.Println("m=", m) fmt.Println("p=", p) }
或還可使用內置new()
函數建立指針。該new()
函數將類型做爲參數,分配足夠的內存以容納該類型的值,並返回指向它的指針。
import "fmt" func main() { var p *int p = new(int) *p = 1 fmt.Println("p=", *p) }
初始化指針
您可使用另外一個變量的內存地址初始化指針。可使用&
運算符檢索變量的地址
var x = 100 var p *int = &x
注意咱們如何使用&
帶變量的運算符x
來獲取其地址,而後將地址分配給指針p
。
就像Golang中的任何其餘變量同樣,指針變量的類型也由編譯器推斷。因此你能夠省略p
上面例子中指針的類型聲明,並像這樣寫
var p = &a
取消引用指針
您能夠*
在指針上使用運算符來訪問存儲在指針所指向的變量中的值。這被稱爲解除引用或間接
package main import "fmt" func main() { var a = 100 var p = &a fmt.Println("a = ", a) fmt.Println("p = ", p) fmt.Println("*p = ", *p) }
輸出:
a = 100 p = 0xc00004c080 *p = 100
您不只可使用*
運算符訪問指向變量的值,還能夠更改它。如下示例a
經過指針設置存儲在變量中的值p
package main import "fmt" func main() { var a = 1000 var p = &a fmt.Println("a (before) = ", a) // Changing the value stored in the pointed variable through the pointer *p = 2000 fmt.Println("a (after) = ", a) }
輸出:
a (before) = 1000 a (after) = 2000
指針指向指針
指針能夠指向任何類型的變量。它也能夠指向另外一個指針。如下示例顯示如何建立指向另外一個指針的指針
package main import "fmt" func main() { var a = 7.98 var p = &a var pp = &p fmt.Println("a = ", a) fmt.Println("address of a = ", &a) fmt.Println("p = ", p) fmt.Println("address of p = ", &p) fmt.Println("pp = ", pp) // Dereferencing a pointer to pointer fmt.Println("*pp = ", *pp) fmt.Println("**pp = ", **pp) }
Go中沒有指針算術
若是您使用過C / C ++,那麼您必須意識到這些語言支持指針算法。例如,您能夠遞增/遞減指針以移動到下一個/上一個內存地址。您能夠向/從指針添加或減去整數值。您也可使用關係運算符比較兩個三分球==
,<
,>
等。
但Go不支持對指針進行此類算術運算。任何此類操做都將致使編譯時錯誤
package main func main() { var x = 67 var p = &x var p1 = p + 1 // Compiler Error: invalid operation }
可是,您可使用==
運算符比較相同類型的兩個指針的相等性。
package main import "fmt" func main() { var a = 75 var p1 = &a var p2 = &a if p1 == p2 { fmt.Println("Both pointers p1 and p2 point to the same variable.") } }
Go中傳遞簡單類型
import "fmt" func main() { p := 5 change(&p) fmt.Println("p=", p)//p= 0 } func change(p *int) { *p = 0 }
Go中全部的都是按值傳遞,對於複雜類型,傳的是指針的拷貝
package main import "fmt" func main() { var m map[string]int m = map[string]int{"one": 1, "two": 2} n := m fmt.Printf("%p\n", &m) //0xc000074018 fmt.Printf("%p\n", &n) //0xc000074020 fmt.Println(m) // map[two:2 one:1] fmt.Println(n) //map[one:1 two:2] changeMap(m) fmt.Printf("%p\n", &m) //0xc000074018 fmt.Printf("%p\n", &n) //0xc000074020 fmt.Println(m) //map[one:1 two:2 three:3] fmt.Println(n) //map[one:1 two:2 three:3] } func changeMap(m map[string]int) { m["three"] = 3 fmt.Printf("changeMap func %p\n", m) //changeMap func 0xc000060240 }
直接傳指針 也是傳指針的拷貝
package main import "fmt" func main() { var m map[string]int m = map[string]int{"one": 1, "two": 2} n := m fmt.Printf("%p\n", &m) //0xc000074018 fmt.Printf("%p\n", &n) //0xc000074020 fmt.Println(m) // map[two:2 one:1] fmt.Println(n) //map[one:1 two:2] changeMap(&m) fmt.Printf("%p\n", &m) //0xc000074018 fmt.Printf("%p\n", &n) //0xc000074020 fmt.Println(m) //map[one:1 two:2 three:3] fmt.Println(n) //map[two:2 three:3 one:1] } func changeMap(m *map[string]int) { //m["three"] = 3 //這種方式會報錯 invalid operation: m["three"] (type *map[string]int does not support indexing) (*m)["three"] = 3 //正確 fmt.Printf("changeMap func %p\n", m) //changeMap func 0x0 }
總結:
參考資料
http://golang.org/doc/faq#Pointers
https://www.callicoder.com/go...
https://www.ardanlabs.com/blo...
https://www.ardanlabs.com/blo...