咱們如今有一段程序:算法
package main import "fmt" func main() { // a,b 是一個值 a := 5 b := 6 fmt.Println("a的值:", a) // 指針變量 c 存儲的是變量 a 的內存地址 c := &a fmt.Println("a的內存地址:", c) // 指針變量不容許直接賦值,須要使用 * 獲取引用 //c = 4 // 將指針變量 c 指向的內存裏面的值設置爲4 *c = 4 fmt.Println("a的值:", a) // 指針變量 c 如今存儲的是變量 b 的內存地址 c = &b fmt.Println("b的內存地址:", c) // 將指針變量 c 指向的內存裏面的值設置爲4 *c = 8 fmt.Println("a的值:", a) fmt.Println("b的值:", b) // 把指針變量 c 賦予 c1, c1 是一個引用變量,存的只是指針地址,他們兩個如今是獨立的了 c1 := c fmt.Println("c的內存地址:", c) fmt.Println("c1的內存地址:", c1) // 將指針變量 c 指向的內存裏面的值設置爲4 *c = 9 fmt.Println("c指向的內存地址的值", *c) fmt.Println("c1指向的內存地址的值", *c1) // 指針變量 c 如今存儲的是變量 a 的內存地址,但 c1 仍是不變 c = &a fmt.Println("c的內存地址:", c) fmt.Println("c1的內存地址:", c1) }
打印出:segmentfault
a的值: 5 a的內存地址: 0xc000016070 a的值: 4 b的內存地址: 0xc000016078 a的值: 4 b的值: 8 c的內存地址: 0xc000016078 c1的內存地址: 0xc000016078 c指向的內存地址的值 9 c1指向的內存地址的值 9 c的內存地址: 0xc000016070 c1的內存地址: 0xc000016078
那麼a,b
是一個值變量,而c
是指針變量,c1
是引用變量。數組
若是&
加在變量a
前:c := &a
,表示取變量a
的內存地址,c
指向了a
,它是一個指針變量。數據結構
當獲取或設置指針指向的內存的值時,在指針變量前面加*
,而後賦值,如:*c = 4
,指針指向的變量a
將會變化。併發
若是將指針變量賦予另一個變量:c1 := c
,那另一個變量c1
能夠叫作引用變量,它存的值也是內存地址,內存地址指向的也是變量a
,這時候,引用變量只是指針變量的拷貝,兩個變量是互相獨立的。app
值變量能夠稱爲值類型,引用變量和指針變量均可以叫作引用類型。數據結構和算法
如何聲明一個引用類型的變量(也就是指針變量)呢?函數
咱們能夠在數據類型前面加一個*
來表示:指針
var d *int
咱們之後只會以值類型,和引用類型來區分變量。code
有了基本的數據類型,還遠遠不夠,因此Golang
支持咱們定義本身的數據類型,結構體:
// 結構體 type Diy struct { A int64 // 大寫導出成員 b float64 // 小寫不能夠導出 }
結構體的名字爲Diy
,使用type 結構體名字 struct
來定義。
結構體裏面有一些成員A
和b
,和變量定義同樣,類型int64
和float64
放在後面,不須要任何符號分隔,只須要換行便可。結構體裏面小寫的成員,在包外沒法使用,也就是不可導出。
使用結構體時:
// 新建結構體,值 g := diy.Diy{ A: 2, //b: 4.0, // 小寫成員不能導出 } // 新建結構體,引用 k := &diy.Diy{ A: 2, } // 新建結構體,引用 m := new(diy.Diy) m.A = 2
能夠按照基本數據類型的樣子使用結構體,上述創立的:
g := diy.Diy{ A: 2, //b: 4.0, // 小寫成員不能導出 }
是一個值類型的結構體。
你也可使用結構體值前面加一個&
或者使用new
來建立一個引用類型的結構體,如:
// 新建結構體,引用 k := &diy.Diy{ A: 2, } // 新建結構體,引用 m := new(diy.Diy) m.A = 2
引用和值類型的結構體有何區別的?
咱們知道函數內和函數外的變量是獨立的,當傳參數進函數的時候,參數是值拷貝,函數裏的變量被約束在函數體內,就算修改了函數裏傳入的變量的值,函數外也發現不了。
但引用類型的變量,傳入函數時,雖然也是傳值,但拷貝的是引用類型的內存地址,能夠說拷貝了一個引用,這個引用指向了函數體外的某個結構體,使用這個引用在函數裏修改結構體的值,外面函數也會發現。
若是傳入的不是引用類型的結構體,而是值類型的結構體,那麼會完整拷貝一份結構體,該結構體和原來的結構體就沒有關係了。
內置的數據類型切片slice
和字典map
都是引用類型,不須要任何額外操做,因此傳遞這兩種類型做爲函數參數,是比較危險的,開發的時候須要謹慎操做。
結構體能夠和函數綁定,也就是說這個函數只能被該結構體使用,這種函數稱爲結構體方法:
// 引用結構體的方法,引用傳遞,會改變原有結構體的值 func (diy *Diy) Set(a int64, b float64) { diy.A = a diy.b = b return } // 值結構體的方法,值傳遞,不會改變原有結構體的值 func (diy Diy) Set2(a int64, b float64) { diy.A = a diy.b = b return }
只不過在之前函數的基礎上func Set(a int64, b float64)
,變成了func (diy *Diy) Set(a int64, b float64)
,只不過在函數裏面,可使用結構體變量diy
裏面的成員。
上面表示值類型的結構體diy Diy
可使用Set2
方法,引用類型的結構體diy *Diy
可使用Set
方法。
若是是這樣的話,咱們每次使用結構體方法時,都要注意結構體是值仍是引用類型,幸運的是Golang
操碎了心,每次使用一個結構體調用方法,都會自動將結構體進行類型轉換,以適配方法。好比下面:
// 新建結構體,值 g := diy.Diy{ A: 2, //b: 4.0, // 小寫成員不能導出 } g.Set(1, 1) fmt.Printf("type:%T:%v\n", g, g) // 結構體值變化 g.Set2(3, 3) fmt.Printf("type:%T:%v\n", g, g) // 結構體值未變化 // 新建結構體,引用 k := &diy.Diy{ A: 2, } k.Set(1, 1) fmt.Printf("type:%T:%v\n", k, k) // 結構體值變化 k.Set2(3, 3) fmt.Printf("type:%T:%v\n", k, k) // 結構體值未變化
結構體g
是值類型,原本不能調用Set
方法,可是Golang
幫忙轉換了,咱們毫無感知,而後值類型就變成了引用類型。同理,k
是引用類型,照樣可使用Set2
方法。
前面咱們也說過,函數傳入引用,函數裏修改該引用對應的值,函數外也會發現。
結構體的方法也是同樣,不過範圍擴散告終構體自己,方法裏能夠修改結構體自己,可是若是結構體是值,那麼修改後,外面的世界是發現不了的。
關鍵字new
主要用來建立一個引用類型的結構體,只有結構體能夠用。
關鍵字make
是用來建立和初始化一個切片或者字典。咱們能夠直接賦值來使用:
e := []int64{1, 2, 3} // slice f := map[string]int64{"a": 3, "b": 4} // map
可是這種直接賦值相對粗暴,由於咱們使用時可能不知道數據在哪裏,數據有多少。
因此,咱們在建立切片和字典時,能夠指定容量大小。看示例:
s := make([]int64, 5) s1 := make([]int64, 0, 5) m1 := make(map[string]int64, 5) m2 := make(map[string]int64) fmt.Printf("%#v,cap:%#v,len:%#v\n", s, cap(s), len(s)) fmt.Printf("%#v,cap:%#v,len:%#v\n", s1, cap(s1), len(s1)) fmt.Printf("%#v,len:%#v\n", m1, len(m1)) fmt.Printf("%#v,len:%#v\n", m2, len(m2))
運行後:
[]int64{0, 0, 0, 0, 0},cap:5,len:5 []int64{},cap:5,len:0 map[string]int64{},len:0 map[string]int64{},len:0
切片可使用make([],佔用容量大小,所有容量大小)
來定義,你能夠建立一個容量大小爲5
,可是實際佔用容量爲0
的切片,好比make([]int64, 0, 5)
,你預留了5
個空間,這樣當你切片append
時,不會由於容量不足而內部去分配空間,節省了時間。
若是你省略了後面的參數如make([]int64, 5)
,那麼其等於make([]int64, 5,5)
,由於這時所有容量大小就等於佔用容量大小。內置語言cap
和len
能夠查看所有容量大小,已經佔用的容量大小。
同理,字典也能夠指定容量,使用make([],容量大小)
,可是它沒有所謂的佔用容量,它去掉了這個特徵,由於咱們使用切片,可能須要五個空白的初始值,可是字典沒有鍵的狀況下,預留初始值也沒做用。省略容量大小,表示建立一個容量爲0
的鍵值結構,當賦值時會自動分配空間。
函數是代碼片斷的一個封裝,方法是將函數和結構體綁定。
可是Golang
裏面有一些內置語法,不是函數,也不是方法,好比append
,cap
,len
,make
,這是一種語法特徵。
語法特徵是高級語言提供的,內部幫你隱藏瞭如何分配內存等細節。
我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook。