這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
前言
實值型別:所有像int、float、bool和string這些類型都屬於實值型別,使用這些類型的變數直接指向存在記憶體中的值,實值型別的變數的值儲存在棧中。當使用等號=將一個變數的值賦給另一個變數時,如 j = i ,實際上是在記憶體中將 i 的值進行了拷貝。可以通過 &i 擷取變數 i 的記憶體位址
指標類型:簡單地說go語言的指標類型和C/C++的指標類型用法是一樣的,除了出去安全性的考慮,go語言增加了一些限制,包括如下幾條:
- 不同類型的指標不能互相轉化,例如*int, int32, 以及int64
- 任何普通指標類型*T和uintptr之間不能互相轉化
- 指標變數不能進行運算, 比如C/C++裡面的++, --運算
下面將給大家詳細介紹golang中實值型別/指標類型的變數的一些區別,下面話不多說了,來一起看看詳細的介紹吧。
實值型別的變數和指標類型的變數
先聲明一個結構體:
type T struct { Name string}func (t T) M1() { t.Name = "name1"}func (t *T) M2() { t.Name = "name2"}
M1() 的接收者是實值型別 T, M2() 的接收者是實值型別 *T , 兩個方法內都是改變Name值。
下面聲明一個 T 類型的變數,並調用 M1() 和 M2() 。
t1 := T{"t1"} fmt.Println("M1調用前:", t1.Name) t1.M1() fmt.Println("M1調用後:", t1.Name) fmt.Println("M2調用前:", t1.Name) t1.M2() fmt.Println("M2調用後:", t1.Name)
輸出結果為:
M1調用前: t1
M1調用後: t1
M2調用前: t1
M2調用後: name2
下面猜測一下go會怎麼處理。
先來約定一下:接收者可以看作是函數的第一個參數,即這樣的: func M1(t T) , func M2(t *T) 。 go不是物件導向的語言,所以用那種看起來像物件導向的文法來理解可能有偏差。
當調用 t1.M1() 時相當於 M1(t1) ,實參和行參都是類型 T,可以接受。此時在M1()中的t只是t1的值拷貝,所以M1()的修改影響不到t1。
當調用 t1.M2() => M2(t1) ,這是將 T 類型傳給了 *T 類型,go可能會取 t1 的地址傳進去: M2(&t1) 。所以 M2() 的修改可以影響 t1 。
類型的變數這兩個方法都是擁有的。
下面聲明一個 *T 類型的變數,並調用 M1() 和 M2() 。
t2 := &T{"t2"} fmt.Println("M1調用前:", t2.Name) t2.M1() fmt.Println("M1調用後:", t2.Name) fmt.Println("M2調用前:", t2.Name) t2.M2() fmt.Println("M2調用後:", t2.Name)
輸出結果為:
M1調用前: t2
M1調用後: t2
M2調用前: t2
M2調用後: name2
t2.M1() => M1(t2) , t2 是指標類型, 取 t2 的值並拷貝一份傳給 M1。
t2.M2() => M2(t2) ,都是指標類型,不需要轉換。
*T 類型的變數也是擁有這兩個方法的。
傳給介面會怎樣?
先聲明一個介面
type Intf interface { M1() M2()}
使用:
var t1 T = T{"t1"} t1.M1() t1.M2() var t2 Intf = t1 t2.M1() t2.M2()
報錯:
./main.go:9: cannot use t1 (type T) as type Intf in assignment:
T does not implement Intf (M2 method has pointer receiver)
var t2 Intf = t1 這一行報錯。
t1 是有 M2() 方法的,但是為什麼傳給 t2 時傳不過去呢?
簡單來說,按照介面的理論:傳過去【賦值】的對象必須實現了介面要求的方法,而t1沒有實現M2() ,t1的指標實現了M2() 。另外和c語言一樣,函數名本身就是指標
當把 var t2 Intf = t1 修改為 var t2 Intf = &t1 時編譯通過,此時 t2 獲得的是 t1 的地址, t2.M2()的修改可以影響到 t1 了。
如果聲明一個方法 func f(t Intf) , 參數的傳遞和上面的直接賦值是一樣的情況。
巢狀型別
聲明一個類型 S,將 T 嵌入進去
type S struct { T }
使用下面的例子測試一下:
t1 := T{"t1"} s := S{t1} fmt.Println("M1調用前:", s.Name) s.M1() fmt.Println("M1調用後:", s.Name) fmt.Println("M2調用前:", s.Name) s.M2() fmt.Println("M2調用後:", s.Name) fmt.Println(t1.Name)
輸出:
M1調用前: t1
M1調用後: t1
M2調用前: t1
M2調用後: name2
t1
將 T 嵌入 S, 那麼 T 擁有的方法和屬性 S 也是擁有的,但是接收者卻不是 S 而是 T。
所以 s.M1() 相當於 M1(t1) 而不是 M1(s) 。
最後 t1 的值沒有改變,因為我們嵌入的是 T 類型,所以 S{t1} 的時候是將 t1 拷貝了一份。
假如我們將 s 賦值給 Intf 介面會怎麼樣呢?
var intf Intf = s intf.M1() intf.M2()
報錯:
cannot use s (type S) as type Intf in assignment: S does not implement Intf (M2 method has pointer receiver)
還是 M2() 的問題,因為 s 此時還是實值型別。
var intf Intf = &s 這樣的話編譯通過了,如果在 intf.M2() 中改變了 Name 的值, s.Name 被改變了,但是 t1.Name 依然沒變,因為現在 t1 和 s 已經沒有聯絡了。
下面嵌入 *T 試試:
type S struct { *T }
使用時這樣:
t1 := T{"t1"} s := S{&t1} fmt.Println("M1調用前:", s.Name) s.M1() fmt.Println("M1調用後:", s.Name) fmt.Println("M2調用前:", s.Name) s.M2() fmt.Println("M2調用後:", s.Name) fmt.Println(t1.Name)
M1調用前: t1
M1調用後: t1
M2調用前: t1
M2調用後: name2
name2
惟一的區別是最後 t1 的值變了,因為我們複製的是指標。
接著賦值給介面試試:
var intf Intf = s i ntf.M1() intf.M2() fmt.Println(s.Name)
編譯沒有報錯。這裡我們傳遞給 intf 的是實值型別而不是指標,為什麼可以通過呢?
拷貝 s 的時候裡面的 T 是指標類型,所以調用 M2() 的時候傳遞進去的是一個指標。
var intf Intf = &s 的效果和上面一樣。
http://www.pdfxs.com/search?q=APAK-113