C++自制心得——開篇(引用)
前言:?
本心得只適合給那些擁有C語言基礎(chǔ) (系統(tǒng)的自主學(xué)習(xí)過C語言(以找工作為目標(biāo),學(xué)校老師教的一律視作沒學(xué)),了解一些底層機制,用C實現(xiàn)過一些基本數(shù)據(jù)結(jié)構(gòu) (順序表、鏈表、隊列與棧、初等二叉樹、十大經(jīng)典排序等) ) 的人觀看。如果你不滿足上述條件就不要在這里浪費時間了。
本人在寫專欄上的技術(shù)力并不好,如果你覺得這篇文章在排版上有需要改進的地方可以在評論區(qū)留言并附上具體操作流程。
本人目前是大二在讀生,目前剛開始學(xué)習(xí)C++相關(guān)知識,如果有大佬發(fā)現(xiàn)哪里介紹的不對或者有疏漏歡迎在評論區(qū)留言,我盡量在發(fā)現(xiàn)的第一時間糾正。
好,廢話不多講,我們進入正題。?
引用:
什么是引用

這就是一個簡單的引用代碼。在C++里,引用本質(zhì)上是指針的pro max plus版(據(jù)說在Java里引用是傳值調(diào)用),所以它有如下特點:
1. 在定義時必須指定是哪個變量的引用(Java可以不給)
2. 創(chuàng)建完成后不可更改指向(Java可以改)
3. 引用與其指向的變量共用一塊空間
4. 對引用的合法更改會同步影響它所指的對象
5. 一個變量可擁有無數(shù)個引用(可以套娃)
Java這作業(yè)抄的真爽
(tips: 關(guān)于第二點,如果能改,C++就和Java一樣再也不需要指針了,我懷疑C++這么設(shè)計就是為了保住指針僅剩的就業(yè)空間)


(tips:?在調(diào)試的時候打開反匯編窗口就能看到代碼的匯編層,算是一個很實用的小技巧)
我來簡單講一下這幾條匯編指令的意思:
mov? dword ptr [a], 0? ? ? 把值0放入內(nèi)存地址a維護的空間
lea? eax, [a]? ? ? ? ? ? ? ? ? ? 把內(nèi)存地址a放入寄存器eax
mov? dword ptr [b], eax? 把寄存器eax的值放入內(nèi)存地址b維護的空間
(我其實不用講匯編的,把這兩張圖貼出來就夠了)
引用的常見使用場景
顯然,如果不需要更改指定對象,引用在代碼可讀性與美觀度上比指針好得多,只有像類似鏈表,二叉樹等這一類需要頻繁更改指定對象的情況才需要用到指針。讓我們上點實例:
傳引用的Swap函數(shù)是不是比以前好用了不少?再上一個更復(fù)雜的例子。
test1.c
二叉樹遍歷(C).h
二叉樹遍歷(C).c
這段代碼用于輸出該二叉樹的中序遍歷,在_MidorderTraversal函數(shù)有一個參數(shù)int* curPos,表示此時返回用前序遍歷數(shù)組的待填空位。顯然,以這個函數(shù)的邏輯,我們希望這個參數(shù)獨立存在,不受遞歸調(diào)用的影響。因此這個參數(shù)應(yīng)為全局變量,或者是指針變量,也可以是靜態(tài)變量,上面的代碼用的是第二種?,F(xiàn)在有了引用就可以換一種寫法了。
test2.cpp
二叉樹遍歷(C++).h
二叉樹遍歷(C++).cpp
覺得代碼沒有發(fā)生翻天覆地的變化?那我們再來一個。
test3.c
s_clist.h
s_clist.cpp
這是一個經(jīng)典的單鏈表尾插,有沒有注意到尾插函數(shù)里的二級指針?超討厭(〝▼皿▼),但不得不用,誰叫在頭指針為空的情況下我們要改變頭指針本身,那只能傳頭指針的地址,即二級指針。不過現(xiàn)在,用引用就行了。
test3.c(修改后)
s_clist.h(修改后)
s_clist.cpp(修改后)
就問你,爽不爽?講到這里我突然想開個腔,請看下面的代碼:
這個代碼是不是有點眼熟?沒錯它就是在數(shù)據(jù)結(jié)構(gòu)課上很多老師教的單鏈表寫法(據(jù)說有些數(shù)據(jù)結(jié)構(gòu)的書上也是這么寫的),好多學(xué)生看不懂。當(dāng)然,現(xiàn)在的我們肯定是能看懂的,不就是把struct SClistNode* 重定義為PlistNode,然后函數(shù)傳PlistNode的引用。這和SClistNode*&沒有區(qū)別,干嘛寫的這么別扭?據(jù)某些小道消息稱,出書的人覺得指針已經(jīng)夠難了,再給你一個二級指針不就集體Orz了。所以他們整了一個爛活,把代碼寫成上面的樣子,覺得這樣同學(xué)們就能看懂了,把??看樂了。期待各路大仙在評論區(qū)的發(fā)揮。
引用的一些特性
1. 傳值調(diào)用與傳引用調(diào)用的效率問題

以這段代碼為例,函數(shù)a每次傳參都要拷貝四十萬字節(jié)的數(shù)據(jù),整個函數(shù)調(diào)用了十萬次,也就是說我們整整拷貝了37.25個G的數(shù)據(jù)。與此同時,函數(shù)b每次傳參不拷貝數(shù)據(jù)。顯然,在形參大小較大和函數(shù)頻繁調(diào)用的情況下傳引用調(diào)用比傳值調(diào)用擁有更好的性能優(yōu)勢。
2. 常引用問題

3. 引用做返回值(不知道函數(shù)棧幀創(chuàng)建和銷毀的同學(xué)建議去了解一下)
這塊的問題很有意思,我會重點講。
對于傳值返回,返回的是變量的值,而不是變量的地址,返回值會有一個臨時空間接收,一般情況下,這個臨時空間會是一個寄存器或者是一個在主調(diào)函數(shù)棧幀與被調(diào)函數(shù)棧幀之間的預(yù)先開好的空間(函數(shù)的形參也會被存在這個夾縫里,后面的題會用到這個小知識)
對于傳引用返回,返回的是變量的地址,而不是變量的值,如果返回的變量已經(jīng)隨著函數(shù)棧幀的銷毀被釋放,那這就成了一個經(jīng)典的野指針問題。
最有意思的部分來了,猜猜看這段代碼的運行結(jié)果。








下一個問題,為什么結(jié)果是這樣的?







對于第一第二行輸出,我們用的是int型變量接收的int&返回值,執(zhí)行賦值操作,盡管int&返回值所在的空間已被釋放,但釋放操作與賦值操作間隔時間過短,那個空間還沒有被其他線程占用,因此空間內(nèi)的值沒有被更改,返回值就被保存在一個穩(wěn)定空間內(nèi)。在這種情況下,除了會爆非法越界警告,沒有任何異常。
對于第三第四行輸出,我們們用的是int&型變量接收的int&返回值,此時我們保存了返回值的地址,接下來執(zhí)行printf操作,前文提到函數(shù)形參會被保存在主調(diào)函數(shù)棧幀與被調(diào)函數(shù)棧幀的夾縫里。在printf函數(shù)準(zhǔn)備形參時,因為其調(diào)用空間沒有觸及返回值所在空間,此時形參值正常,緊接著printf的函數(shù)棧幀開辟,返回值所在空間被占用,返回值被銷毀。從第二次調(diào)用printf函數(shù)開始,因返回值被銷毀,形參值異常,打印在屏幕上的結(jié)果就是隨機值。
這道題就留給各位了,答案扣在評論區(qū)就行。
這種代碼雖然沒什么實際意義,但是講解這種匪夷所思的錯誤示范能讓你對下面這個結(jié)論印象深刻。只有當(dāng)返回值所在的空間不會隨著函數(shù)棧幀的銷毀被釋放,才能使用引用返回。
那引用返回有什么讓人眼前一亮的用途嗎?讓我們來個大的。
test3.cpp
s_clist.h
s_clist.cpp
我剛剛又寫了一個查找和一個修改函數(shù),在C語言里我們需要給查找和修改分別設(shè)計一個接口,但是現(xiàn)在有了引用返回,我們就可以這么玩了。
test3.cpp(修改后)
s_clist.h(修改后)
s_clist.cpp(修改后)
諸位,引用返回爽不爽,還用不用C寫代碼了?
現(xiàn)在總結(jié)一下引用的好處:
傳引用傳參
1. 提高效率
2. 輸出型參數(shù)
傳引用返回
1. 提高效率
2. 修改返回對象
引用講完了,離正餐就不遠(yuǎn)了,目前就剩下一個運算符重載要花點時間,剩下的一筆帶過就行。基礎(chǔ)知識搞定了,就要開始玩類和對象了,難度會再次加碼,同志們還要繼續(xù)努力。