C++引用及基本用法全解
一、什么是引用?
引用是C++中的一種語法特性,可以理解為變量的別名。引用本身不占用獨立的內(nèi)存空間,它和被引用的變量共享同一塊內(nèi)存。
引用的基本用法
int main() {
int a = 10;
int& ra = a; // ra是a的別名
a += 10;
cout << ra << endl; // 輸出20
ra += 10;
cout << a << endl; // 輸出30
return 0;
}關(guān)鍵點:對引用的操作就是對原變量的操作,兩者完全等價。
二、引用的三大特點
1. 定義引用時必須初始化
int& ra; // ? 錯誤:引用必須初始化 int& ra = a; // ? 正確
原因:引用是變量的別名,別名必須依附于某個已存在的變量,不能獨立存在。
2. 不存在空引用
int& ra = NULL; // ? 錯誤:不能給引用賦空值 int* p = NULL; // ? 指針可以為空
原因:引用必須綁定到一個有效的變量,不能指向空地址。指針是獨立的變量,可以存儲空地址,但引用不是變量,它只是別名。
3. 引用一旦綁定,不能改變指向
int a = 10, b = 20; int& ra = a; ra = b; // 這不是讓ra指向b,而是把b的值賦給a(即a = 20)
原因:引用在初始化時綁定了目標(biāo)變量,之后所有對引用的操作都直接作用于該變量,無法重新綁定到其他變量。
對比指針:
int a = 10, b = 20; int* p = &a; // 指針可以改變指向 p = &b; // 現(xiàn)在p指向b *p = 100; // 修改b的值為100
三、引用作為函數(shù)參數(shù)
引用的本質(zhì):引用是變量的別名,不是副本。對引用的操作就是對原變量的操作。
基于這個特性,引用最常用的場景是作為函數(shù)參數(shù),主要解決兩個問題:
1. 在函數(shù)內(nèi)部修改外部變量
C語言中,函數(shù)參數(shù)默認(rèn)是傳值:形參是實參的副本,函數(shù)內(nèi)部修改形參,外面的實參不受影響。
// C語言:傳值,交換失敗
void Swap(int a, int b) {
int tmp = a;
a = b;
b = tmp;
}
int main() {
int x = 10, y = 20;
Swap(x, y);
// x和y還是10和20,沒變
}想要修改實參,必須傳指針:
// C語言:傳指針,交換成功
void Swap(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}C++的引用可以做到同樣的事,語法更簡潔:
// C++:傳引用,交換成功
void Swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
int main() {
int x = 10, y = 20;
Swap(x, y); // 調(diào)用時和傳值一樣,但實參被改了
cout << x << " " << y << endl; // 輸出20 10
}優(yōu)點:調(diào)用時不用取地址,函數(shù)內(nèi)部不用解引用,代碼更清晰。
2. 避免拷貝,提高效率
當(dāng)參數(shù)是大型對象(如結(jié)構(gòu)體、類)時,傳值會拷貝整個對象,開銷很大。傳引用只傳遞地址(4/8字節(jié)),效率更高。
// 傳值:拷貝整個Student對象(幾十字節(jié))
void PrintStudent(Student s) { ... }
// 傳引用:只傳遞地址(4/8字節(jié)),不拷貝
void PrintStudent(const Student& s) { ... }注意:對于 int、char 這類小型數(shù)據(jù),傳值反而更快,因為直接復(fù)制到寄存器,不需要間接訪問。所以小型數(shù)據(jù)用傳值,大型對象用const引用。
3. 三種參數(shù)傳遞方式對比
| 方式 | 本質(zhì) | 能否修改實參 | 效率(小型數(shù)據(jù)) | 效率(大型對象) |
|---|---|---|---|---|
| 傳值 | 副本 | ? 不能 | 高 | 低 |
| 傳指針 | 地址 | ? 能 | 中 | 高 |
| 傳引用 | 別名 | ? 能 | 中 | 高 |
四、普通引用 vs const引用
1. 普通引用(可讀可寫)
int a = 10; int& ra = a; // 普通引用 ra = 100; // ? 可以修改 cout << ra; // ? 可以讀取
2. const引用(只讀)
int a = 10; const int& cra = a; // 常引用,只讀 cout << cra; // ? 可以讀取 cra = 100; // ? 錯誤:不能通過常引用修改
3. 權(quán)限規(guī)則:看原變量的權(quán)限
引用的權(quán)限不能超過原變量。
- 原變量是普通變量(可讀可寫)→ 引用可以是普通引用,也可以是常引用
- 原變量是常量(只讀)→ 引用只能是常引用
int a = 10; // 普通變量:可讀可寫 const int b = 20; // 常量:只讀 int& ra = a; // ? 普通引用引用普通變量 const int& cra = a; // ? 常引用引用普通變量 int& rb = b; // ? 錯誤:普通引用不能引用常量(權(quán)限超了) const int& crb = b; // ? 常引用引用常量
一句話:變量是什么權(quán)限,引用就不能超過這個權(quán)限。
五、const引用的萬能特性
const引用可以引用任何東西:普通變量、常變量、字面常量,甚至臨時對象。
1. 引用普通變量
int a = 10; const int& cra = a; // ?
2. 引用常變量
const int b = 20; const int& crb = b; // ?
3. 引用字面常量
const int& crx = 30; // ? 可以!
背后原理:
當(dāng) const 引用綁定到字面常量時,編譯器會生成一個臨時變量來保存這個常量,然后讓引用指向這個臨時變量。
// 編譯器實際做的事: int tmp = 30; const int& crx = tmp;
注意:引用本身不占內(nèi)存,但被引用的對象(臨時變量)需要占內(nèi)存。這個臨時變量由編譯器自動創(chuàng)建和管理,它的生命周期和引用一致。
4. 引用表達(dá)式結(jié)果
int a = 10, b = 20; const int& cr = a + b; // ? 表達(dá)式結(jié)果是臨時變量,const引用可以綁定
六、引用的常見誤區(qū)
誤區(qū)1:引用可以重新綁定
int a = 10, b = 20; int& ra = a; // ra是a的別名 ra = b; // ? 不是讓ra指向b,而是把b的值賦給a(a變成20)
誤區(qū)2:引用占用空間
引用本身不占用獨立內(nèi)存,它只是原變量的別名。sizeof 引用的結(jié)果是原變量的大小。
int a = 10; int& ra = a; cout << sizeof(ra); // 輸出4(int的大小),不是指針的大小
原因:sizeof 作用于引用時,返回的是被引用對象的大小。
誤區(qū)3:引用可以指向引用
int a = 10; int& ra = a; int&& rra = ra; // ? 錯誤:不存在"引用的引用"
原因:引用是別名,不是對象。不能給別名再起別名嗎?語法上不允許。
七、不能返回局部變量的引用或地址
先看一個錯誤示例
Student& func() {
Student sa{ "S001", "Alice", "F", 20 };
return sa; // 返回局部變量的引用
}
int main() {
Student& p = func();
printf("%s", p.s_name); // 可能崩潰,可能亂碼,可能碰巧正確
}為什么錯了?
困惑1:返回地址本身有什么錯?
返回地址本身沒錯。return &sa 返回的是一個數(shù)字(內(nèi)存地址),這個操作是合法的。錯的是函數(shù)結(jié)束后你還去用這個地址訪問內(nèi)存。
困惑2:為什么函數(shù)結(jié)束后就不能用了?
函數(shù)調(diào)用時,系統(tǒng)會在棧上給局部變量分配空間。函數(shù)結(jié)束后,這塊空間被系統(tǒng)回收,標(biāo)記為“可重用”。但回收不是清零,這塊內(nèi)存還在,只是不再屬于你了。
下次調(diào)用其他函數(shù)時,系統(tǒng)可能會把這塊空間分配給別的變量,里面的內(nèi)容隨時可能被覆蓋。所以你去訪問它,結(jié)果是不可預(yù)測的。
困惑3:返回值和返回引用有什么區(qū)別?
// 返回引用:返回地址,危險
Student& func1() {
Student sa{ ... };
return sa; // 返回地址,sa銷毀后地址失效 ?
}
// 返回值:返回副本,安全
Student func2() {
Student sa{ ... };
return sa; // 返回副本,sa銷毀不影響副本 ?
}區(qū)別:
- 返回引用:把原件的地址告訴別人,原件沒了,地址就成了空地址
- 返回值:復(fù)印一份交給別人,原件沒了,復(fù)印件還在
困惑4:返回局部變量的地址,和返回局部變量本身,有什么區(qū)別?
看返回類型:
- 返回類型是
Student&或Student*:返回的是地址(危險) - 返回類型是
Student:返回的是副本(安全)
Student& func1() { return sa; } // 返回類型是引用 → 地址
Student* func2() { return &sa; } // 返回類型是指針 → 地址
Student func3() { return sa; } // 返回類型是對象 → 副本類比理解
你在酒店開房:
sa就是你住的房間- 返回引用或地址:你把房間號告訴別人
- 退房(函數(shù)結(jié)束):房間被回收,不再屬于你
- 別人拿著房間號回去找:房間可能住了別人,可能在打掃,可能已經(jīng)被改成倉庫
- 去用這個房間號 → 結(jié)果不可預(yù)測
返回值:你退房時把房間里的東西復(fù)印一份帶走,原來的房間怎么變都跟你無關(guān)。
正確的做法
1. 返回值(拷貝)
Student func() {
Student sa{ ... };
return sa; // 返回副本
}2. 返回靜態(tài)局部變量
Student& func() {
static Student sa{ ... }; // 靜態(tài)變量,生命周期貫穿整個程序
return sa;
}Student& func() {
static Student sa{ ... }; // 靜態(tài)變量,生命周期貫穿整個程序
return sa;
}3. 在堆上分配
Student* func() {
Student* p = new Student{ ... }; // 堆上分配,手動管理
return p;
}一句話總結(jié)
返回局部變量的地址本身沒錯,錯的是函數(shù)結(jié)束后你還去用這個地址。因為這塊內(nèi)存已經(jīng)被系統(tǒng)回收,不再屬于你了。
八、能不能定義引用數(shù)組?
結(jié)論:不能定義“元素為引用的數(shù)組”,但可以定義“數(shù)組的引用”。
1. 不能定義元素為引用的數(shù)組
int& rbr[5]; // ? 錯誤:不能定義引用數(shù)組
原因:數(shù)組是一段連續(xù)的內(nèi)存空間,每個元素都需要占用內(nèi)存。引用本身不占內(nèi)存,它只是別名,無法作為數(shù)組元素存在。
2. 可以定義數(shù)組的引用
int ar[5] = {1,2,3,4,5};
int(&rar)[5] = ar; // ? 正確:rar是數(shù)組ar的引用含義:rar 是整個數(shù)組 ar 的別名,不是元素為引用的數(shù)組。
3. 對比
| 寫法 | 含義 | 是否正確 |
|---|---|---|
int& rbr[5] | 元素為引用的數(shù)組 | ? 錯誤 |
int(&rar)[5] | 數(shù)組的引用 | ? 正確 |
記憶技巧:括號位置決定一切。& 靠近數(shù)組名,表示引用整個數(shù)組;& 靠近類型,表示元素是引用。
九、數(shù)組名退化問題
1. 數(shù)組名退化的規(guī)則
數(shù)組名在三種情況下不會退化,保持為整個數(shù)組:
sizeof(ar)&ar(取數(shù)組地址)- 引用數(shù)組
int(&rar)[n] = ar
其他情況數(shù)組名退化為數(shù)組首元素的地址。
int ar[5] = {1,2,3,4,5};
cout << sizeof(ar); // 20(5*4,未退化)
int(*p)[5] = &ar; // 取數(shù)組地址,未退化
int(&rar)[5] = ar; // 引用數(shù)組,未退化
int* q = ar; // 退化:ar變成int*2. 數(shù)組名作為函數(shù)參數(shù):退化問題
C++中,數(shù)組名作為參數(shù)傳遞給函數(shù)時,會退化為指針,丟失數(shù)組大小信息。
void funa(int br[5]) {
cout << sizeof(br); // 輸出8(指針大?。?,不是20
// 函數(shù)內(nèi)部不知道數(shù)組有多大,只能通過額外參數(shù)傳入長度
}如果希望函數(shù)內(nèi)部能知道數(shù)組的大小,可以用數(shù)組的引用作為參數(shù):
void funb(int (&rar)[5]) {
cout << sizeof(rar); // 輸出20,保留了數(shù)組大小
// 函數(shù)內(nèi)部可以直接用sizeof獲取數(shù)組長度
}關(guān)鍵點:用數(shù)組的引用作為參數(shù),可以保留數(shù)組的大小信息,避免退化。
十、引用與指針的區(qū)別
1. 語法規(guī)則上的區(qū)別
| 對比項 | 引用 | 指針 |
|---|---|---|
| 本質(zhì) | 變量的別名 | 存儲變量的地址 |
| 內(nèi)存分配 | 不分配獨立內(nèi)存 | 分配4/8字節(jié)內(nèi)存 |
| 使用方式 | 直接使用 | 需要解引用(*) |
| 能否改變指向 | 不能 | 能 |
| 必須初始化 | 是 | 否 |
| 是否可為空 | 不能 | 可以(NULL/nullptr) |
| sizeof結(jié)果 | 原變量的大小 | 指針本身的大小(4/8) |
| 多級 | 無(無引用的引用) | 有(二級、n級指針) |
| ++操作 | 改變原變量的值 | 改變指向的地址 |
// 演示++操作的區(qū)別
int ar[5] = {10,20,30,40,50};
int& ra = ar[0];
int* p = &ar[0];
++ra; // ar[0]變成11
++p; // p指向ar[1]2. 匯編層面的本質(zhì)
從匯編角度看,指針和引用沒有區(qū)別,都是地址。
int a = 10; int& ra = a; int* p = &a; // 底層實現(xiàn):ra和p都是存儲a的地址
引用在底層也是用指針實現(xiàn)的,只是編譯器做了語法糖,讓使用更方便、更安全。
3. 一句話總結(jié)
| 維度 | 引用 | 指針 |
|---|---|---|
| 語法層面 | 別名,更安全 | 地址,更靈活 |
| 匯編層面 | 都是地址 | 都是地址 |
使用建議:能用引用就用引用,需要改變指向或可為空時再用指針。
總結(jié)
| 知識點 | 核心要點 |
|---|---|
| 引用本質(zhì) | 變量的別名,不占獨立內(nèi)存 |
| 三大特點 | 必須初始化、不能為空、不能改指向 |
| const引用 | 只讀,可引用普通變量/常變量/字面常量 |
| 權(quán)限規(guī)則 | 引用權(quán)限不能超過原變量 |
| const引用萬能 | 可綁定字面常量和臨時對象,編譯器自動生成臨時變量 |
| 引用作為參數(shù) | 可修改外部變量、避免大對象拷貝 |
| 返回局部變量 | 不能返回局部變量的引用或地址,函數(shù)結(jié)束后內(nèi)存被回收 |
| 數(shù)組的引用 | 可定義數(shù)組引用 int(&rar)[5],不能定義引用數(shù)組 int& rbr[5] |
| 數(shù)組名退化 | sizeof/&/數(shù)組引用 三種情況不退化,函數(shù)參數(shù)會退化 |
| 引用 vs 指針 | 引用更安全(不能為空、不能改指向),指針更靈活 |
到此這篇關(guān)于C++引用全解的文章就介紹到這了,更多相關(guān)C++引用全解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言 structural body結(jié)構(gòu)體詳解用法
C 數(shù)組允許定義可存儲相同類型數(shù)據(jù)項的變量,結(jié)構(gòu)是 C 編程中另一種用戶自定義的可用的數(shù)據(jù)類型,它允許您存儲不同類型的數(shù)據(jù)項,結(jié)構(gòu)用于表示一條記錄,假設(shè)您想要跟蹤圖書館中書本的動態(tài),您可能需要跟蹤每本書的下列屬性2021-10-10
C語言數(shù)據(jù)結(jié)構(gòu)之鏈隊列的基本操作
這篇文章主要為大家介紹了C語言之鏈隊列的基本操作,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2021-12-12
C語言創(chuàng)建線程thread_create()的方法
這篇文章主要介紹了C語言創(chuàng)建線程thread_create()的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
判斷指定的進(jìn)程或程序是否存在方法小結(jié)(vc等)
VC判斷進(jìn)程是否存在?比如我想知道記事本是否運行,要用到哪些函數(shù)等實例,需要的朋友可以參考下2013-01-01

