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