您好,登錄后才能下訂單哦!
我們上節(jié)講了 C++ 中的引用,那么我們就來看下引用的本質(zhì)。引用作為變量別名而存在,因此在一些場合可以代替指針。引用相對于指針來說具有更好的可讀性和實用性。注意:函數(shù)中的引用參數(shù)不需要進(jìn)行初始化!
下來我們來看看 swap 函數(shù)的實現(xiàn)對比,如下
void swap(int* a, int* b) // 指針形式的 { int t = *a; *a = *b; *b = t; } void swap(int& a, int& b) // 引用形式的 { int t = a; a = b; b = t; }
那么這塊就有個特殊的引用,便是 const 引用了。在 C++ 中可以聲明 const 引用,它的格式為 const Type& name = var;const 引用讓變量擁有只讀屬性。當(dāng)使用常量對 const 引用進(jìn)行初始化時,C++ 編譯器會為常量值分配空間并將引用作為這段空間的別名。使用常量對 const 引用初始化后將生成一個只讀變量!
下來我們以代碼為例進(jìn)行分析,看看引用的特殊意義,代碼如下
#include <stdio.h> void Example() { printf("Example:\n"); int a = 3; const int& b = a; int* p = (int*)&b; // b = 5; *p = 5; printf("a = %d\n", a); printf("b = %d\n", b); } void Demo() { printf("Demo:\n"); const int& c = 1; int* p = (int*)&c; // c = 5; *p = 5; printf("c = %d\n", c); } int main(int argc, char *argv[]) { Example(); printf("\n"); Demo(); return 0; }
我們在 Example 函數(shù)中定義了變量 a,用 b const 引用 a,然后用指針 p 指向 b。然后通過指針 p 改變 b 的值,但是這塊 b 是 const 引用,所以不能直接改變 b。我們看看 a 和 b 會是多少。在 Demo 函數(shù)中,我們通過 const 引用 c 為 1,并且定義指針 p 指向它。同樣不能直接改變 c,但是可以通過指針 p 來改變它的值。我們先來看看通過指針 p 改變后的值是否為 5 呢?看看編譯結(jié)果
我們看到值已經(jīng)都改變了,我們再來去掉第 11 和 26 行的注釋,看看直接改變 const 引用會怎樣?
我們看到報的都是它們是只讀變量。那么我們思考下:引用有自己的存儲空間嗎?我們通過程序來看看
#include <stdio.h> struct test { char& c; }; int main(int argc, char *argv[]) { char c = 'c'; char& rc = c; test r = { c }; printf("sizeof(char&) = %d\n", sizeof(char&)); printf("sizeof(rc) = %d\n", sizeof(rc)); printf("sizeof(test) = %d\n", sizeof(test)); printf("sizeof(r.c) = %d\n", sizeof(r.c)); return 0; }
我們在第 3 行定義了一個結(jié)構(gòu)體變量 test,但它里面只有一個 char 類型的引用 c。我們來看看這個結(jié)構(gòu)體占用內(nèi)存嗎?編譯如下
我們看到引用本身只占用了一個字節(jié),但是結(jié)構(gòu)體 test 占用了 4 個字節(jié)的內(nèi)存。我們猜想它是不是跟指針有某種聯(lián)系呢?其實引用在 C++ 中的內(nèi)部實現(xiàn)是一個指針常量。關(guān)系如下
注意:a> C++ 編譯器在編譯過程中用 指針常量 作為引用的內(nèi)部實現(xiàn),因此引用所占的空間大小與指針相同;b> 從使用的角度,引用只是一個別名,C++ 為了實用性而隱藏了引用的存儲空間這一細(xì)節(jié)。下來我們通過一個示例代碼進(jìn)行說明
#include <stdio.h> struct TRef { char* before; char& ref; char* after; }; int main(int argc, char* argv[]) { char a = 'a'; char& b = a; char c = 'c'; TRef r = {&a, b, &c}; printf("sizeof(r) = %d\n", sizeof(r)); printf("sizeof(r.before) = %d\n", sizeof(r.before)); printf("sizeof(r.after) = %d\n", sizeof(r.after)); printf("&r.before = %p\n", &r.before); printf("&r.after = %p\n", &r.after); return 0; }
我們看到在結(jié)構(gòu)體 TRef 內(nèi)部只有 3 個成員,兩個指針,一個引用。我們通過打印結(jié)構(gòu)體的大小和它的 before 指針和 after 指針的大小和地址來分別看看中間的引用究竟是什么
我們看到結(jié)構(gòu)體總共占 12 個字節(jié)的內(nèi)存,指針 before 和 after 各占 4 個字節(jié),并且他們的地址相差 8,從而雙重說明了中間的引用占 4 個字節(jié)的內(nèi)存空間,引用便是指向一個地址的。那么它的本質(zhì)便是指針了。
那么為什么還要弄個引用來代替指針呢?我們知道在 C 語言中,凡是涉及到指針的操作都是容易出 bug 的地方,因此 C++ 設(shè)計了引用來在大部分情況下代替指針。從功能性來說,可以滿足大多數(shù)的需要使用指針的場合;從安全性來說,可以避免由于操作指針不當(dāng)而帶來的內(nèi)存錯誤;從操作性來說,簡單易用,又不失功能強大。下面我們來看看函數(shù)返回引用的一個示例
#include <stdio.h> int& demo() { int d = 0; printf("demo: d = %d\n", d); return d; } int& func() { static int s = 0; printf("func: s = %d\n", s); return s; } int main(int argc, char* argv[]) { int& rd = demo(); int& rs = func(); printf("\n"); printf("main: rd = %d\n", rd); printf("main: rs = %d\n", rs); printf("\n"); rd = 10; rs = 11; demo(); func(); printf("\n"); printf("main: rd = %d\n", rd); printf("main: rs = %d\n", rs); printf("\n"); return 0; }
我們在 demo 函數(shù)里返回了局部變量 d,因此這個肯定會出問題。在 func 函數(shù)里返回的加 static 修飾的變量,因此它是會放在全局?jǐn)?shù)據(jù)區(qū),不會出錯。我們在第 23 和 24 行用 demo 和 func 函數(shù)進(jìn)行初始化,因此這會打印出 d = 0 和 s = 0;在第 27 和 28 行打印 rd 和 rs 的值,因為 demo 函數(shù)返回之后 d 會丟失,這時 rd 便是一個野指針了。所以 rd 指向的是一個隨機數(shù),但是 rs 還是為 0;第 31 和 32 行分別對 rd 和 rs 進(jìn)行重新賦值,再次調(diào)用 demo 和 func 函數(shù)時,d 還是為 0,s 就為 11 了;最后第 38 和 39 行會打印出 rd 為隨機數(shù),rs 為 11。我們來看看編譯結(jié)果和我們分析的是否一致
我們看到它在編譯的時候都已經(jīng)報警告了,打印的結(jié)果和我們所分析的是一致的。通過對引用本質(zhì)的學(xué)習(xí),總結(jié)如下:1、引用作為變量別名而存在旨在代替指針;2、const 引用可以使得變量具有只讀屬性;3、引用在編譯器內(nèi)部使用指針常量實現(xiàn),它的最終本質(zhì)為指針;4、引用可以盡可能的避開內(nèi)存錯誤。
歡迎大家一起來學(xué)習(xí) C++ 語言,可以加我QQ:243343083。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。