引用的本質分析
- 2019 年 10 月 3 日
- 筆記
1. 引用的定義
C++新增加了引用的概念:
- 引用可以看作一個已定義變數的別名
- 引用的語法
Type &name = var;
int a = 4; int &b = a; //b為a的別名 b = 5; //操作b就是操作a
2. 引用的本質
- 引用在C++中的內部實現是一個常量指針
Type &name <==> Type *const name
- C++編譯器在編譯過程中使用常量指針作為引用的內部實現,因此引用所佔用的記憶體大小和指針相同
- 從使用的角度,引用只是一個別名,C++為了實用性而隱藏了引用的存儲空間這一細節
#include <cstdio> struct TRef { char &r; }; int main(int argc, char *argv[]) { char c = 'c'; char &rc = c; TRef ref = { c }; printf("sizeof(rc) = %dn", sizeof(rc)); printf("sizeof(TRef) = %dn", sizeof(TRef)); printf("sizeof(ref) = %dn", sizeof(ref)); printf("sizeof(ref.r) = %dn", sizeof(ref.r)); /*sizeof(type &)的大小,就是type類型的大小*/ printf("sizeof(char &) = %dn", sizeof(char &)); printf("sizeof(int &) = %dn", sizeof(int &)); printf("sizeof(double &) = %dn", sizeof(double &)); return 0; }
#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) = %dn", sizeof(r)); printf("sizeof(r.before) = %dn", sizeof(r.before)); printf("sizeof(r.after) = %dn", sizeof(r.after)); printf("&r.before = %pn", &r.before); printf("&r.after = %pn", &r.after); return 0; }
3. 引用的意義
- C++中的引用作為變數別名而存在,旨在大多數的情況下代替指針
- 引用可以滿足絕大多數需要使用指針的場合
- 引用可以避開由於指針操作不當而帶來的記憶體錯誤
- 引用相對於指針來說具有更好的可讀性和實用性
注意:由於引用的內部實現為指針,因此函數不能返回非靜態局部變數的引用
#include <stdio.h> int &demo() { int d = 0; printf("demo: d = %dn", d); return d; } int &func() { static int s = 0; printf("func: s = %dn", s); return s; } int main(int argc, char *argv[]) { int &rd = demo(); int &rs = func(); printf("n"); printf("main: rd = %dn", rd); printf("main: rs = %dn", rs); printf("n"); rd = 10; rs = 11; demo(); func(); printf("n"); printf("main: rd = %dn", rd); printf("main: rs = %dn", rs); printf("n"); return 0; }
4. 特殊的引用—const引用
- 在C++中可以聲明const引用
const Type &name = var
- 可以使用const常量、變數、字面值常量對const引用初始化
- 不管使用何種方式初始化,const引用都將產生一個只讀變數
- 當使用字面值常量對const引用初始化時,C++編譯器會為常量值分配記憶體空間,並將引用作為這段記憶體空間的別名
const引用類型 VS 初始化變數類型
- 類型相同,const引用的就是初始化變數
- 類型不同,const引用的不是初始化變數,而是初始化變數的臨時對象
注意:const只是修飾符,不代表類型,也就是說,const int和int是相同類型。
#include <stdio.h> int main() { const int a = 3; int b = 4; char c = 'c'; const int &ra = a; const int &rb = b; const int &rc = c; const int &rd = 1; int *p1 = (int *)&ra; int *p2 = (int *)&rb; int *p3 = (int *)&rc; int *p4 = (int *)&rd; *p1 = 5; *p2 = 6; *p3 = 7; *p4 = 8; printf("ra = %dn", ra); printf("rb = %dn", rb); printf("rc = %dn", rc); printf("rd = %dn", rd); printf("n"); printf("b = %dn", b); //b的類型和rb相同,rb引用的就是b,所以改變rb的值,b也跟著一起改變 printf("c = %cn", c); //c的類型和rc不同,rb引用的是c的臨時對象,所以改變rc的值,c不受影響 return 0; }
5. 引用和指針的關係
指針 | 引用 |
---|---|
指針是一個變數,其值為一個記憶體地址 | 引用是一個變數的新名字 |
指針可以不初始化,而是在使用時賦值 | 引用必須在定義時初始化 |
通過指針可以訪問對應記憶體地址中的值 | 對引用的操作(賦值、取地址等)會傳遞到代表的變數上 |
指針可以保存不同的地址 | 引用在初始化之後無法代表其他變數 |
指針可以被const修飾,成為常量或只讀變數 | const引用使其代表的變數具有隻讀屬性 |
在工程項目開發中
- 當進行C++編程時,直接站在使用的角度,引用和指針沒有任何關係
- 當對C++程式碼進行調試分析時,一些特殊情況,可以考慮站在C++編譯器的角度,引用在內部實現為指針常量
我們給出一個站在C++編譯器的角度看待引用的示例,下面這段程式碼有問題嗎?
int a = 1; int b = 2; int *pc = new int(3); int &array[] = {a, b, *pc};
- 數組是一片連續的記憶體空間
- 引用數組會破壞該特性,各元素代表的變數可能存儲在不同的位置
- 因此,C++不支援引用數組!!!!!!
#include <stdio.h> int a = 1; struct SV { int &x; int &y; int &z; }; int main() { int b = 2; int *c = new int(3); SV sv = {a, b, *c}; int &array[] = {a, b, *c}; //&array[1] - &array[0] != 4,編譯報錯 printf("&sv.x = %pn", &sv.x); printf("&sv.y = %pn", &sv.y); printf("&sv.z = %pn", &sv.z); delete c; return 0; }
首先,注釋掉程式碼第17行,編譯運行結果如下,可以看出列印出的記憶體地址是各不相同的。
然後,去除程式碼第17行注釋,結果編譯報錯,原因就是數組的三個元素地址不連續,而是各不相同。