Go怎麼可能有引用?得了吧~ 有人要說了,那利用make()
函數執行後獲得的slice、map、channel等類型,不都是獲得的引用嗎?c++
我要說:那能叫引用嗎?你能肯定啥叫引用嗎? 若是你有點迷糊,那麼請聽我往下講:git
這一切要從變量提及。github
不管是引用變量仍是指針變量,都是變量;那麼,什麼叫變量? 其實變量本質就是一塊內存。一般,咱們對計算機內存進行操做,最直接的方式就是:「計算機,在0x0201地址內存一個整數100,在0x00202地址存一個浮點數10.6,讀取0x00203的數據...」 這種方式讓機器來操做還行,若是直接寫成代碼讓人看的話,這一堆「0x020一、0x0202...」難記的地址能把人給整崩潰了~ 因而,聰明的人們想出了一種方法:把一堆難記的地址用其餘人類能夠方便讀懂的方式來間接表示。例如:將「0x0201」的地址命名爲「id」,將「0x0202」命名爲「score」...而後,代碼編譯期間,再將"name"等人類能讀懂的文字轉化爲真實的內存地址;因而,變量誕生了~微信
因此,其實每一個變量都表明了一塊內存,變量名是咱們給那塊兒內存起的一個別名,內存中存的值就是咱們給變量賦的值。變量名在程序編譯期間會直接轉化爲內存地址。markdown
引用是指向另一個變量的變量,或者說,叫一個已知變量的別名。數據結構
注意,引用和引用自己指向的變量對應的是同一塊內存地址。引用自己也會在編譯期間轉化爲真正的內存地址。固然咯,引用和它指向的變量在編譯期間會轉化爲同一個內存地址。函數
指針自己也是一個變量,須要分配內存地址,可是內存地址中存的是另外一個變量的內存地址。有點繞口,請看圖:oop
咱們先看看「正統」的引用的例子,在C++中(C中是沒有引用的哈):ui
#include <stdio.h>
int main(void) {
int i = 3;
int *ptr = &i;
int &ref = i;
printf("%p %p %p\n", &i, ptr, &ref);
// 打印出:0x7ffeeac553a8 0x7ffeeac553a8 0x7ffeeac553a8
return 0;
}
複製代碼
變量地址、引用地址、指針的值 均相同;符合常理spa
那咱們再試試Go中相似代碼的例子:
package main
import "fmt"
func main() {
i := 3
ref := i
ptr := &i
fmt.Println(fmt.Sprintf("%p %p %p", &i, &ref, ptr))
// 打印出 0xc000118000 0xc000118008 0xc000118000
}
複製代碼
變量i地址和指針ptr的值同樣,這是符合預期的;可是:正如Go中沒有特別的「引用符號」(C++中是int &ref = i;
)同樣,上述go代碼中的ref
壓根就是個變量,根本不是引用。
但是,不少人不死心,是否是「實驗對象」不對啊?代碼中使用的是int整型,咱們換作slice
和map
試試?畢竟網上的"資料"都是這麼寫的: 例如如下截圖(只看標紅部分就好):
還有以下截圖(只看標紅部分就好):
ok,那咱們能夠試試以下map的代碼,看到底有沒有引用:
package main
import "fmt"
func main(){
i := make(map[string]string)
i["key"]="value"
ref := i
fmt.Println(fmt.Sprintf("%p %p", &i, &ref))
// 打印出:0xc00010e018 0xc00010e020
}
複製代碼
哈哈!不對呀,若是是引用的話,打印的地址應該相同纔對,可是如今不相同!因此不存在? 彆着急,緊接着看下面的例子:
package main
import "fmt"
func main(){
i := make(map[string]string)
i["key"]="value"
ref := i
ref["key"] = "value1"
fmt.Println(i["key"]) // 打印結果:value1
fmt.Println(ref["key"]) // 打印結果:value1
fmt.Println(fmt.Sprintf("%p %p", &i, &ref))
// 打印結果:0xc00000e028 0xc00000e030
}
複製代碼
能猜出來打印了什麼嗎?變量地址是不對,可是,可是值竟然變了!ref變量能夠「操控」i變量的內容!就和引用同樣!
這就很奇怪了~ 咋回事兒呢?
咱們細細研究一下map
、slice
、channel
等具體實現(詳情請看:個人其餘文章 圖解Go map底層實現、圖解Go slice底層實現、圖解Go channel底層實現)咱們發現,這些類型的底層實現都是會有一個指針指向另外的存儲地址,因此,在make
函數建立了具體的類型實例後,實際上在內存空間中會開闢多個地址空間,而隨着變量的賦值,指針引用的那個地址值也會跟着「複製」,於是其餘變量能夠改變原有變量的內容。
聽着是否是有點繞?咱們來看看圖:
首先實例化了map並賦值
而後又賦值給了另一個變量ref
因爲對於指針變量的值而言,就是一個地址(程序實現上就是一串數字),因此,在賦值的時候,就「複製」了一串數字,可是,這串數字背後的含義確是另一個地址,而地址的內容,偏偏就是map
slice
channel
等數據結構真正底層存儲的數據!
因此,兩變量由於同一個指針變量指向的內存,而產生了相似於「引用」的效果。假如實例化的類型數據中,沒有指針
屬性,則不會產生這種「類引用」的效果: 例如以下代碼:
package main
import "fmt"
func main(){
i := 3
ref := i
ref = 4
fmt.Println(i, ref) // 打印輸出:3 4
fmt.Println(fmt.Sprintf("%p %p", &i, &ref))
// 打印輸出:0xc000016070 0xc000016078
}
複製代碼
能夠將代碼上述仔細看看能輸出什麼,不出意外的話你會發現:「類引用」效果消失了~
要想再次展示「類引用」效果,只要建立一個帶有指針屬性的類型便可,咱們本身實現均可以,無需依賴Go基礎庫中的map
、slice
、channel
等
package main
import "fmt"
type Instance struct {
Name string
Data *int
}
func (i Instance) Store(num int) {
*(i.Data) = num
}
func (i Instance) Show() int{
return *(i.Data)
}
func main(){
data := 5
i := Instance{
Name:"hello",
Data:&data,
}
ref := i
ref.Store(7)
fmt.Println(i.Show(), ref.Show())
// 打印出:7 7
fmt.Println(fmt.Sprintf("%p %p", &i, &ref))
// 打印出:0xc0000a6018 0xc0000a6030
}
複製代碼
看看以上代碼,是否是實現了「類引用」? 有人要說了map
展現key值,slice
展現某個下標的值,沒有用方法呀? 這就不對了,其實map
的展現key的值mapData[key]
也好,更改值也好,slice
展現下標值sliceArray[0]
也好,更改值也好;背後底層實現也都是些「函數」和「方法」,只不過Go語言把這些函數和方法作成了語法糖,咱們無感知罷了~
好了,如今我再問你:還敢說Go語言有引用類型嗎?是否是感受:也有、也沒有了? 😝
互聯網技術窩