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

