Go語言入門系列前面的文章:算法
若是你使用過C或C++,那你確定對指針這個概念不陌生。編程
咱們須要先介紹兩個概念:內存和地址。數組
咱們寫的代碼都存儲在外存(C盤、D盤)中,好比我存在了D:\Work\Program\go
目錄下。若是你想要運行你的代碼,必須先把你的代碼加載進內存中,而後交給CPU執行計算,而CPU計算的結果也會存到內存中。數據結構
內存的存取速度快,其中有許多存儲單元用來存儲數據,CPU能在內存中直接找到這些數據。這是由於內存中的每一個位置都有一個獨一無二的地址標識。能夠把內存當作一幢有許多房間的大樓,每一個存儲單元是一個房間,存儲的數據是房間中的物品,地址就是房間號。app
因此對CPU來講,若是想找到某個房間中的物品(從內存中取數據),或者向某個房間中放物品(向內存中存數據),咱們必須知道房間號(內存地址)。數據結構和算法
內存地址一般是一串16進制的數字,若是寫代碼時存個整數1或取個整數1都須要寫這麼一串數字,那太麻煩了。因此高級語言爲咱們提供了一個便利,用咱們人能記住的「名字」來代替這串數字。函數
這些「名字」就是變量名。.net
var a int = 1 var b int = 2 var c int = 333 var d int = 6666
變量名和地址的關聯由編譯器替咱們作,硬件訪問的仍然是內存地址。指針
簡單地來講,指針也是一個變量,只不過這個變量中存的不是咱們日常用到的一、二、三、"Hello"、true等值,而是其餘變量的地址。code
之因此取名指針,是由於指針變量b
中保存了變量a
的地址,咱們能夠經過該指針變量b
找到變量a
,若是畫圖看起來,看起來就像是指針b
指向了變量a
。
還能夠有指針的指針:
聲明一個指針:
var p *int
*int
表示p
是一個int
類型指針,p
指針中存的是一個int
類型變量的地址,這意味着p
中不能存其餘類型變量的地址。
如何獲取某個變量的地址呢?使用操做符&
:
var a int = 66 //a是值爲66的int變量 p = &a //將a的地址賦給指針p
那麼如何根據指針中的地址找到對應的變量呢?使用操做符*
:
var b = *p //根據p中的值找到a,將其值賦給b fmt.Println(b) //66 *p = 99 //根據p中的值找到a,改變a的值 fmt.Println(a) //99
必定要注意指針的初始化,若是不初始化,則指針的的值是其零值——nil
。對未初始化的指針賦值,則會出問題:
var p *int //只聲明,未初始化 *p = 12 //報錯:invalid memory address or nil pointer dereference
緣由是指針p
中沒值,是個nil
,天然就沒法根據地址找到變量。若是你想使用指針,必須先確保你的指針中有合法的內存地址才行。應當這樣寫:
var a int var p *int = &a //p被初始化爲a的地址 *p = 12 //根據p的值找到a,12賦值給a //或者 var a int var p *int p = &a //a的地址賦給p *p = 12 //根據p的值找到a,12賦值給a
下面是一個完整的例子:
package main import "fmt" func main() { var a int = 66 //變量a var p *int = &a //指針p指向a var b = *p //獲取p指向的變量a的值 fmt.Println("a =",a, ", b =", b, ", p =", p) fmt.Println("a的地址 =", &a, ", b的地址 =", &b, ", p的地址 =", &p) *p = 12 //改變p指向的變量a的值 fmt.Println("a =",a, ", b =", b, ", p =", p) fmt.Println("a的地址 =", &a, ", b的地址 =", &b, ", p的地址 =", &p) var pp **int = &p //指針pp指向指針p var c = *pp //獲取pp指向的p的值 var d = **pp //獲取pp指向的p指向的a的值 fmt.Println("pp =", pp, ", c =", c, ", d =", d) fmt.Println("pp的地址 =", &pp, ", c的地址 =", &c, ", d的地址 =", &d) }
和C語言同樣,Go語言中也有結構體。
結構體就是一組字段/屬性的集合。有告終構體,咱們能夠根據本身的需求定義本身的類型。好比狗,確定不能用基本數據類型來表示,由於狗身上有許多屬性:string
類型的姓名、int
類型的年齡等等,狗是一個擁有許多屬性的集合,換句話說,狗是一個結構體。咱們能夠定義一個dog
類型的結構體來表示狗。
結構體的聲明方式:
type 結構體名字 struct { 字段名1 類型1 字段名2 類型2 ... }
下面是結構體dog
的聲明:
type dog struct { name string age int }
聲明告終構體後,就可使用它。
首先,只要你正確聲明告終構體後,你就能像使用int
、string
等基本類型聲明變量同樣去聲明dog
類型的變量,而後,你就能給聲明的變量d
的字段賦值了,經過點號.
來訪問結構體的字段:
var d dog //聲明一個dog類型的變量d d.name = "哮天犬" d.age = 3
除此以外,還有幾種聲明方式。
你能夠按照字段順序直接賦值
d := dog{"哮天犬", 3}
或者指定字段賦值,這樣能夠忽略字段順序:
d := dog{age:3, name:"哮天犬"}
下面是一個完整的例子:
package main import "fmt" type dog struct { name string age int } func main() { var d dog //聲明一個dog類型的變量d d.name = "哮天犬" d.age = 3 d1 := dog{"哮地犬", 2} d2 := dog{age:4, name:"哮人犬"} fmt.Println(d, d1, d2) }
咱們能夠獲取結構體的指針:
d := dog{"哮地犬", 2} p := &d //獲取到d的地址
能夠根據結構體指針訪問其字段:
n := (*p).name fmt.Println(n) //哮天犬
這種方式比較麻煩,Go語言提供了隱式間接引用:
n := p.name //這樣也行 fmt.Println(n)
咱們能夠經過new
函數給結構體分配一個指針。
先介紹一下new
函數:new
函數用於給各類類型的內存分配。new(T)
會給T
類型分配對其合適的內存空間,用T
類型的零值填充,並返回其地址,是一個*T
類型的值。換句話說,該函數會返回一個指向T
類型零值的指針。
p := new(dog) fmt.Printf("%T\n", p) //*main.dog fmt.Println(p) //&{ 0} fmt.Println(*p) //{ 0}
從上面打印的三行語句中也能夠看出,new(dog)
返回的是一個指針。
一個結構體也能夠做爲另外一個結構體的字段,下面是一個例子:
package main import "fmt" type people struct { name string age int d dog } type dog struct { name string age int } func main() { a := people{"行小觀", 18, dog{"小狗", 2}} fmt.Println(a) //{行小觀 18 {小狗 2}} fmt.Println(a.d) //{小狗 2} fmt.Println(a.name) //行小觀 fmt.Println(a.d.name) //小狗 }
也可使用匿名字段,何爲匿名字段?顧名思義,只提供類型,不寫字段名:
package main import "fmt" type people struct { name string age int dog //匿名字段 } type dog struct { name string age int } func main() { a := people{"行小觀", 18, dog{"小狗", 2}} fmt.Println(a) //{行小觀 18 {小狗 2}} fmt.Println(a.dog) //{小狗 2} fmt.Println(a.name) //行小觀 fmt.Println(a.dog.name) //小狗 }
我是「行小觀」,於千萬人中的一個普通人。陰差陽錯地走上了編程這條路,既然走上了這條路,那麼我會盡量遠地走下去。
我會在公衆號『行人觀學』中持續更新「Java」、「Go」、「數據結構和算法」、「計算機基礎」等相關文章。
歡迎關注,咱們一塊兒踏上行程。
本文章屬於系列文章《Go語言入門系列》。
若有錯誤,還請指正。