指針與引用

引用和指針非常相似部宿,它們都用來讓一個(gè)變量提供對(duì)另一個(gè)變量的訪問。

引用

需要從類型和傳遞兩個(gè)角度分別看待引用瓢湃。

  • 從類型角度理张,類型可分為值類型和引用類型,一般而言绵患,我們說到引用雾叭,強(qiáng)調(diào)的都是類型。
  • 從傳遞角度落蝙,有值傳遞织狐、址傳遞和引用傳遞,傳遞是在函數(shù)調(diào)用時(shí)才會(huì)提到的概念筏勒,用于表明實(shí)參與形參的關(guān)系移迫。

什么是引用

引用的實(shí)現(xiàn)主要有兩種。

  1. C++ 的實(shí)現(xiàn)管行,引用其實(shí)一種便于使用指針的語法糖起意,是某塊內(nèi)存的別名,對(duì)已存在的變量可以聲明別名病瞳,這種別名稱為引用變量。
  2. Python 中的實(shí)現(xiàn)悲酷,本質(zhì)是底層結(jié)構(gòu)中包含指向?qū)嶋H內(nèi)容的指針套菜。

參數(shù)傳遞

參數(shù)傳遞有值傳遞、址傳遞和引用傳遞设易。

值傳遞

函數(shù)調(diào)用時(shí)逗柴,實(shí)參通過拷貝將自身內(nèi)容傳遞給形參,形參實(shí)際上是實(shí)參值的一個(gè)拷貝顿肺,此時(shí)戏溺,針對(duì)函數(shù)中形參的任何操作,僅僅是針對(duì)實(shí)參的副本屠尊,不影響原始值的內(nèi)容旷祸。

址傳遞

值傳遞中有一個(gè)特殊形式,如果傳遞參數(shù)的類型是指針讼昆,我們就會(huì)稱之為址傳遞托享。

引用傳遞

實(shí)參地址在函數(shù)調(diào)用被傳遞給形參(即實(shí)參和形參擁有相同地址),則可以認(rèn)為是引用傳遞。此時(shí)闰围,針對(duì)函數(shù)中形參的操作會(huì)影響到實(shí)參赃绊。

C++ 支持引用傳遞。

Go 語言是值傳遞

func fn(m map[int]int) {
    fmt.Printf("fc: %p\n", &m)
    m = make(map[int]int)
    fmt.Printf("fn:%v\n", m == nil)
}

func main() {
    var m map[int]int
    fmt.Printf("main: %p\n", &m)
    fn(m)
    fmt.Printf("main:%v\n", m == nil)
}

輸出如下:

main: 0xc000006028
fc: 0xc000006038
fn:false
main:true

通過打印信息可以看到羡榴,實(shí)參和形參地址不同碧查,且對(duì)形參賦值不影響實(shí)參。因此校仑,Go 語言沒有引用傳遞忠售。

而址傳遞可以看做值傳遞中的一個(gè)特殊形式,因此可以說肤视,Go 語言是值傳遞档痪。

Go 引用類型

如果按照 C++ 中引用的實(shí)現(xiàn)機(jī)制,則 Go 語言沒有引用變量邢滑,Go 程序中定義的每個(gè)變量都占用一個(gè)唯一的內(nèi)存位置腐螟。創(chuàng)建兩個(gè)共享同一內(nèi)存位置的變量是不可能的±Ш螅可以創(chuàng)建兩個(gè)指向同一內(nèi)存位置的變量乐纸,不過這與兩個(gè)變量共享同一內(nèi)存位置是不同的。

如果按照 python 中引用的實(shí)現(xiàn)機(jī)制摇予,即結(jié)構(gòu)體中包含指針成員汽绢。對(duì)類型進(jìn)行分類:

  • 值類型:基本數(shù)據(jù)類型 int、float侧戴、bool宁昭、string 以及數(shù)組和 struct。值類型變量直接存儲(chǔ)值酗宋,內(nèi)存通常在棧中分配积仗。

  • 引用類型:指針、slice蜕猫、map寂曹、chan、interface回右、function隆圆。引用類型變量存儲(chǔ)的是一個(gè)地址,這個(gè)地址存儲(chǔ)最終的值翔烁,內(nèi)存通常在堆上分配渺氧,通過 GC 回收。引用類型都可以用 nil 進(jìn)行賦值租漂。

slice阶女、map 和 channel 的底層實(shí)現(xiàn)

slice颊糜、map 和 channel 的實(shí)現(xiàn)機(jī)制是結(jié)構(gòu)體中包含指針成員。它們都可以使用內(nèi)置函數(shù) make 進(jìn)行初始化秃踩。

map

map 實(shí)際是指向 runtime.hmap 結(jié)構(gòu)體的指針衬鱼。

當(dāng)我們寫如下代碼時(shí)

m := make(map[int]int)

編譯器會(huì)自動(dòng)去調(diào)用 runtime.makemap

func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap

從 runtime.makemap 返回的值的類型是指向 runtime.hmap 結(jié)構(gòu)體的指針。

那么如果 map 是指針憔杨,那是不是應(yīng)該這樣表示 *map[key]value 鸟赫?事實(shí)是編譯器將類型從 *map[int]int 重命名為 map[int]int 。

channel

也是 runtime 類型的指針消别。

slice

slice 的結(jié)構(gòu)包含三個(gè)成員抛蚤,分別是切片的底層數(shù)組地址、切片長度和容量大小寻狂。

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

指針

什么是指針

計(jì)算機(jī)內(nèi)存可以看做一串單元格岁经,每個(gè)單元格都有一個(gè)地址,是其所在的內(nèi)存位置蛇券,每個(gè)單元格存儲(chǔ)一個(gè)值缀壤。如果你知道某個(gè)單元格的內(nèi)存地址,就可以訪問該單元格并更新或讀取里面的內(nèi)容纠亚。而 CPU 所做的一切都是為獲取和存儲(chǔ)值到內(nèi)存單元中塘慕。

在代碼中,通過變量就可以操作存儲(chǔ)在內(nèi)存中的值蒂胞,變量只是一個(gè)由數(shù)字字母組成的图呢、標(biāo)識(shí)存儲(chǔ)位置的假名,由編譯器為變量分配唯一的內(nèi)存地址骗随。一個(gè)變量對(duì)應(yīng)了一段內(nèi)存空間蛤织,這段內(nèi)存空間存儲(chǔ)了該變量對(duì)應(yīng)類型的值。

指針變量的值是另一個(gè)變量的內(nèi)存地址鸿染。通過指針瞳筏,就可以更新或讀取另一個(gè)變量的值,而不需要用到變量名牡昆。

對(duì)于每一種類型,不管是自定義的還是 Go 語言內(nèi)置的摊欠,都有相應(yīng)的指針類型丢烘。例如內(nèi)置類型 int,對(duì)應(yīng)的指針類型是 *int些椒。如果你自己聲明了類型 User播瞳,對(duì)應(yīng)的指針類型就是 *User。

所有的指針類型有相同的特點(diǎn)免糕。首先赢乓,它們以 * 符號(hào)開頭忧侧;其次,占用相同的內(nèi)存空間并且都表示一個(gè)地址牌芋,使用 4 個(gè)(32 位機(jī)器)或 8 個(gè)字節(jié)(64 位機(jī)器)長度表示一個(gè)地址蚓炬。

設(shè)計(jì)指針的目的是實(shí)現(xiàn)函數(shù)間值共享,即使該值不在函數(shù)自己棧幀里躺屁,也能對(duì)其進(jìn)行讀寫操作肯夏。

與其他變量相比,指針變量并沒有特別之處犀暑,因?yàn)樗鼈円彩亲兞垦被鳎袃?nèi)存地址和值。

底層原理

《棧與指針》

指針的聲明和使用

指針由 * 操作符和存儲(chǔ)值的類型表示耐亏。

*也用于指針變量的解引用徊都,使得我們可以訪問指針指向的值。

var i int = 10          // 聲明int類型變量i广辰,初始值10
var ptr *int = &i       // 聲明指針變量ptr暇矫,初始值為i的地址。& 操作符用于獲取變量的地址轨域。
fmt.Println(ptr, *ptr)  // *ptr對(duì)應(yīng)指針指向的變量的值 0xc000018060 10 

*ptr = 12               // 更新指針指向的變量的值袱耽,實(shí)際是指針變量解引用,將結(jié)果存儲(chǔ)在 i 指向的內(nèi)存位置
fmt.Println(*ptr, i)    // 12 12

*int類型的指針干发,指向的必須是 int 類型變量的地址朱巨,若指向其他類型變量地址,編譯報(bào)錯(cuò)枉长。

str := "go"
var ip *int
ip = &str   // cannot use &str (type *string) as type *int in assignment

空指針

一個(gè)指針已聲明而沒有賦值時(shí)冀续,稱為空指針,值為 nil必峰。任何類型的指針的零值都是 nil洪唐。

var ip *int
fmt.Println(ip)                     // nil
fmt.Printf("ip 的值為:%x", ip)       // ip 的十六進(jìn)制的值為:0

指針相等判斷

指針之間也是可以進(jìn)行相等判斷的,只有當(dāng)它們指向同一個(gè)變量或全部是 nil 時(shí)才相等吼蚁。

指針作為函數(shù)參數(shù)使用

func a(p *int) {
    *p++
}

func main() {
    i := 10
    a(&i)
    fmt.Println(i)  // 打印11凭需,a函數(shù)中的指針p指向main函數(shù)中的i的內(nèi)存位置
}

new 函數(shù)創(chuàng)建指針

內(nèi)建函數(shù) new 也是一種創(chuàng)建指針的方法。new(type)表示創(chuàng)建一個(gè) type 類型的匿名變量肝匆,初始化為 type 類型的零值粒蜈,并返回變量的指針,指針類型為 *type旗国。new 適用于“值類型”枯怖,如 int、數(shù)組能曾、結(jié)構(gòu)體等度硝。

p := new(int)       // p, *int 類型, 指向匿名的 int 變量
fmt.Println(*p)     // 0
*p = 2              // 設(shè)置 int 匿名變量的值為 2
fmt.Println(*p)     // 2
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肿轨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蕊程,更是在濱河造成了極大的恐慌椒袍,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件存捺,死亡現(xiàn)場(chǎng)離奇詭異槐沼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)捌治,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門岗钩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肖油,你說我怎么就攤上這事兼吓。” “怎么了森枪?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵视搏,是天一觀的道長。 經(jīng)常有香客問我县袱,道長浑娜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任式散,我火速辦了婚禮筋遭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘暴拄。我一直安慰自己漓滔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布乖篷。 她就那樣靜靜地躺著响驴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撕蔼。 梳的紋絲不亂的頭發(fā)上豁鲤,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音鲸沮,去河邊找鬼畅形。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诉探,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播棍厌,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼肾胯,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼竖席!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起敬肚,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤毕荐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后艳馒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體憎亚,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年弄慰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了第美。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陆爽,死狀恐怖什往,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慌闭,我是刑警寧澤别威,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站驴剔,受9級(jí)特大地震影響省古,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丧失,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一豺妓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧利花,春花似錦科侈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挠乳,卻和暖如春权薯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睡扬。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國打工盟蚣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卖怜。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓屎开,卻偏偏與公主長得像,于是被迫代替她去往敵國和親马靠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奄抽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容