??引用
??引用概念
引用不是新定義一個(gè)變量,而是給已存在變量取了一個(gè)別名,編譯器不會(huì)為引用變量開辟內(nèi)存空間,它和它引用的變量共用同一塊內(nèi)存空間。
定義:類型&引用變量名(對(duì)象名) = 引用實(shí)體;
例如以下代碼,在變量名前加一個(gè)&
,意思是一個(gè)引用類型,b
是a
的別名,也就是a
有了一個(gè)外號(hào),但還是a
本身:
int a = 70;
int& b = a; //引用:b是a的別名
我們接下來(lái)看看引用后的地址是否會(huì)發(fā)生改變:
例如以下例子:
int main()
{
int a = 70;
int& b = a; //引用:b是a的別名
int& c = a; //引用:c是a的別名
c = 80;
cout << a << endl;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
代碼運(yùn)行圖:在這個(gè)代碼中,定義了一個(gè)變量a
為70
,int& b = a
; 這里b
是a
的引用,給a
取了一個(gè)外號(hào)b
,int& c = a
; 這里c
是a
的引用,又給a
取了一個(gè)外號(hào)是c
,因此我們對(duì)c
還是對(duì)b
進(jìn)行修改,a
都會(huì)發(fā)生改變,這是因?yàn)?strong>編譯器不會(huì)為引用變量開辟內(nèi)存空間,它和它引用的變量共用同一塊內(nèi)存空間。
注意:引用類型必須和引用實(shí)體是同種類型的
??引用特性
- 引用必須在定義時(shí)初始化:
引用必須在定義時(shí)初始化,不能在之后單獨(dú)為它賦值。
int a = 10;
int& ra = a; // 正確,ra初始化為a
int& ra; // 錯(cuò)誤,引用必須在定義時(shí)初始化
- 一個(gè)變量可以有多個(gè)引用
int a = 10;
int& ref1 = a;
int& ref2 = a;
ref1++; // a的值變?yōu)?1
cout << a << endl; // 輸出11
ref2--; // a的值變?yōu)?0
cout << a << endl; // 輸出10
- 引用一旦引用一個(gè)實(shí)體,再不能引用其他實(shí)體
引用本質(zhì)上就是給原變量添加一個(gè)別名,它的內(nèi)存地址就是原變量的地址。所以對(duì)引用賦值或修改,實(shí)際上就是修改原變量。而指針不同,指針可以改變指向的對(duì)象:一級(jí)指針可以改變指向,如p可以從指向a改為指向其他變量,二級(jí)指針可以改變一級(jí)指針指向的地址,如pp
可以改變p
指向的地址
而引用更像一個(gè)const
指針:定義后不能改變指向的對(duì)象,就像const
指針定義后不能改變指向
但可以通過(guò)這個(gè)“const
指針”來(lái)修改原對(duì)象,就像通過(guò)const
指針可以修改原對(duì)象
int a = 10;
int b = 20;
int& ref = a;
ref = b; // 錯(cuò)誤!引用ref已經(jīng)引用a,不能再引用b
cout << ref << endl; // 輸出10,ref依然引用a
如圖:ref引用了a,這里的值發(fā)生改變是因?yàn)閎賦值給了ref
??使用場(chǎng)景
??做參數(shù)(傳值與傳地址)
當(dāng)引用用來(lái)做參數(shù)時(shí)將會(huì)對(duì)代碼進(jìn)行大大的優(yōu)化,并且具有可讀性,如:當(dāng)我們看了很多遍的交換了兩個(gè)數(shù)的函數(shù):
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int ra = 88;
int rb = 99;
Swap(&ra, &rb);
return 0;
}
形參是實(shí)參的一份臨時(shí)拷貝,所以如果想交換需要,傳地址,不能傳值。
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int ra = 88;
int rb = 99;
Swap(ra, rb);
return 0;
}
a
和b
分別是ra
和rb
的別名,當(dāng)你調(diào)換a
和b
的紙時(shí),其實(shí)是修改了ra
和rb
的地址的值,這樣的好處就是,當(dāng)你看代碼時(shí),引用a
和b
給人一種感覺(jué),就是操作ra
和rb
本身。這隱藏了底層是通過(guò)地址操作原變量ra
和rb
的實(shí)現(xiàn)細(xì)節(jié)。從使用者的角度看,代碼讀起來(lái)就像直接交換ra
和rb
,而不是通過(guò)復(fù)雜的地址操作實(shí)現(xiàn)。
這里使用了引用挺好的,不用擔(dān)心指針的解引用,地址相關(guān)操作,但是,前面我們知道,引用一旦指向一個(gè)實(shí)體,就無(wú)法改變指向,例如,有關(guān)鏈表操作,當(dāng)我們要?jiǎng)h除一個(gè)節(jié)點(diǎn),是不是要改變前面節(jié)點(diǎn)的指針,讓他指向后面節(jié)點(diǎn),而引用恰恰不能改變,因此,引用也不是完全替代指針的
回歸正題,這里還有一個(gè)小注意點(diǎn):作用域的不同,因此,在Swap函數(shù)里,取別的名字都可以,任由發(fā)揮,結(jié)果都相同。
void Swap(int& x, int& x)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int ra = 88;
int rb = 99;
Swap(ra, rb);
return 0;
}
??傳值、傳引用效率比較
以值作為參數(shù)或者返回值類型,在傳參和返回期間,函數(shù)不會(huì)直接傳遞實(shí)參或者將變量本身直接返回,而是傳遞實(shí)參或者返回變量的一份臨時(shí)的拷貝,因此用值作為參數(shù)或者返回值類型,效率是非常低下的,尤其是當(dāng)參數(shù)或者返回值類型非常大時(shí),效率就更低。
#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
A a;
// 以值作為函數(shù)參數(shù)
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作為函數(shù)參數(shù)
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分別計(jì)算兩個(gè)函數(shù)運(yùn)行結(jié)束后的時(shí)間
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
TestRefAndValue();
return 0;
}
按值傳遞(TestFunc1):
調(diào)用
TestFunc1(a)時(shí),會(huì)將
a進(jìn)行拷貝,生成一個(gè)臨時(shí)對(duì)象**a_copy**。**a_copy**作為參數(shù)傳遞給TestFunc1
。TestFunc1
內(nèi)部操作的實(shí)際上是a_copy
,對(duì)a_copy
的修改不會(huì)影響實(shí)參a
。TestFunc1
返回時(shí),臨時(shí)對(duì)象a_copy
會(huì)被銷毀。TestFunc1
以值方式傳遞結(jié)構(gòu)體A
作為參數(shù)。這會(huì)導(dǎo)致每次調(diào)用都會(huì)對(duì)A進(jìn)行值拷貝,對(duì)于一個(gè)包含10000
個(gè)int
成員的大結(jié)構(gòu)體,拷貝開銷很大。
按引用傳遞(TestFunc2):
調(diào)用TestFunc2(a)
時(shí),不會(huì)進(jìn)行值拷貝,直接傳遞a
的引用。TestFunc2
內(nèi)部操作的仍然是實(shí)參a
本身。TestFunc2
返回時(shí),不需要銷毀任何對(duì)象。TestFunc2以引用方式傳遞A。這種方式下,函數(shù)內(nèi)直接操作的就是實(shí)參a本身,不會(huì)有任何拷貝開銷。
總結(jié):
TestFunc1
值傳遞,效率低是因?yàn)橹悼截愰_銷大TestFunc2
引用傳遞,效率高是因?yàn)楸苊饬酥悼截?,直接操作的就是?shí)參a
本身
通過(guò)上述代碼的比較,發(fā)現(xiàn)傳值和指針在作為傳參以及返回值類型上效率相差很大。
??引用做返回值
int& Count()
{
static int n = 0;
n++;
// ...
return n;
}
我們先看看下面代碼會(huì)輸出什么結(jié)果?
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :"<< ret <<endl;
return 0;
}
在Vs編譯運(yùn)行圖:結(jié)果是7,真的是正確嗎?
問(wèn)題分析:
如果函數(shù)返回時(shí),返回的對(duì)象已經(jīng)超出了函數(shù)作用域(即已經(jīng)被銷毀),那么不能返回該對(duì)象的引用,必須返回值。
在第一個(gè)示例中:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
這里函數(shù)返回了局部變量
c
的引用,但c
在函數(shù)返回后就已經(jīng)被銷毀了,所以這是一個(gè)未定義行為,輸出結(jié)果是不確定的。
而在第二個(gè)示例中:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :"<< ret <<endl;
return 0;
}
這里同樣是返回了局部變量c
的引用,但是在main
函數(shù)中又調(diào)用了一次Add
函數(shù),這時(shí)第一次調(diào)用返回的引用ret
已經(jīng)指向了一個(gè)不存在的對(duì)象,所以輸出結(jié)果也是未定義的。
函數(shù)返回引用時(shí)必須確保返回的對(duì)象在調(diào)用者作用域內(nèi)仍然存在,否則就會(huì)產(chǎn)生未定義行為。這是
C++
中函數(shù)返回引用需要特別注意的地方。
答案思考:
在Visual Studio
上運(yùn)行這段代碼,輸出結(jié)果是:
Add(1, 2) is :7
這個(gè)結(jié)果確實(shí)是未定義行為,但在某些情況下可能會(huì)輸出7
。之所以會(huì)出現(xiàn)這種情況,是因?yàn)?strong>Visual Studio的編譯器在處理這種未定義行為時(shí)可能會(huì)做一些特殊的優(yōu)化或處理,導(dǎo)致在某些環(huán)境下能夠得到一個(gè)看似合理的結(jié)果。但這種行為是不可靠的,因?yàn)樗蕾囉诰唧w的編譯器實(shí)現(xiàn)細(xì)節(jié)。在不同的編譯器或環(huán)境下,輸出可能會(huì)完全不同。
正確的做法:是要么返回值,要么返回一個(gè)在調(diào)用者作用域內(nèi)仍然存在的對(duì)象的引用。這樣可以確保代碼的行為是可預(yù)測(cè)和可移植的。
??引用和指針的區(qū)別
-
語(yǔ)法概念:
- 引用是變量的別名,沒(méi)有獨(dú)立的存儲(chǔ)空間,而是和其引用的實(shí)體共用同一塊內(nèi)存空間。
- 指針是一個(gè)獨(dú)立的變量,存儲(chǔ)了另一個(gè)變量的內(nèi)存地址。
-
聲明語(yǔ)法:
- 引用使用
&
符號(hào)聲明,如int& ref = x;
- 指針使用
*
符號(hào)聲明,如int* ptr = &x;
- 引用使用
-
操作方式:
- 引用直接訪問(wèn)和操作其引用的實(shí)體,如
ref = 10;
- 指針需要先解引用(
*
)才能訪問(wèn)其指向的實(shí)體,如*ptr = 10;
- 引用直接訪問(wèn)和操作其引用的實(shí)體,如
-
Null值:
- 引用不能為空(Null),必須在聲明時(shí)初始化為一個(gè)有效的實(shí)體。
- 指針可以為空(Null),指向空地址
0x0
。
讓我們看看例子來(lái)說(shuō)明引用和指針的區(qū)別:
假設(shè)我們有一個(gè)整型變量x
,值為10。
使用引用:
int x = 10;
int& ref = x; // 聲明引用ref,它是x的別名
ref = 20; // 通過(guò)ref修改x的值
cout << "x = " << x << endl; // 輸出 x = 20
ref
是x
的引用,它們共享同一塊內(nèi)存空間。通過(guò)ref
修改值,實(shí)際上是在修改x
的值。 輸出x
的值為20,因?yàn)?code>x的值已經(jīng)被修改了。
使用指針:
int x = 10;
int* ptr = &x; // 聲明指針ptr,存儲(chǔ)x的地址
*ptr = 20; // 通過(guò)ptr解引用并修改x的值
cout << "x = " << x << endl; // 輸出 x = 20
ptr
是一個(gè)指向x
的指針,存儲(chǔ)了x
的內(nèi)存地址。通過(guò)*ptr
解引用并修改值,實(shí)際上是在修改x
的值。輸出x
的值為20,因?yàn)?code>x的值已經(jīng)被修改了。
在底層實(shí)現(xiàn)上實(shí)際是有空間的,因?yàn)?strong>引用是按照指針?lè)绞絹?lái)實(shí)現(xiàn)的。
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
我們來(lái)看下引用和指針的匯編代碼對(duì)比得出:在匯編中引用的底層邏輯還是指針,經(jīng)過(guò)編譯轉(zhuǎn)換成匯編,還是進(jìn)行指針的操作
引用和指針的不同點(diǎn):
- 引用概念上定義一個(gè)變量的別名,指針存儲(chǔ)一個(gè)變量地址。
- 引用在定義時(shí)必須初始化,指針沒(méi)有要求
- 引用在初始化時(shí)引用一個(gè)實(shí)體后,就不能再引用其他實(shí)體,而指針可以在任何時(shí)候指向任何一個(gè)同類型實(shí)體
- 沒(méi)有NULL引用,但有NULL指針
- 在
sizeof
中含義不同:引用結(jié)果為引用類型的大小,但指針始終是地址空間所占字節(jié)個(gè)數(shù)(32
位平臺(tái)下占4
個(gè)字節(jié)) - 引用自加即引用的實(shí)體增加1,指針自加即指針向后偏移一個(gè)類型的大小
- 有多級(jí)指針,但是沒(méi)有多級(jí)引用
- 訪問(wèn)實(shí)體方式不同,指針需要顯式解引用,引用編譯器自己處理
- 引用比指針使用起來(lái)相對(duì)更安全
??常引用
從上述代碼中,我們可以得出以下關(guān)于常引用的結(jié)論:
- 常量引用:
const int a = 10;
//int& ra = a; // 該語(yǔ)句編譯時(shí)會(huì)出錯(cuò),a為常量
const int& ra = a;
對(duì)于常量對(duì)象a
,我們可以使用常引用const int& ra = a;
來(lái)引用它。這樣做可以避免對(duì)常量進(jìn)行修改,直接使用非常引用int& ra = a;
會(huì)在編譯時(shí)報(bào)錯(cuò),因?yàn)椴辉试S對(duì)常量進(jìn)行非常引用。
2. 字面量常引用:
// int& b = 10; // 該語(yǔ)句編譯時(shí)會(huì)出錯(cuò),b為常量
const int& b = 10;
我們可以使用常引用const int& b = 10;
來(lái)引用字面量常量。這樣做可以避免創(chuàng)建臨時(shí)變量, 直接使用非常引用int& b = 10;
會(huì)在編譯時(shí)報(bào)錯(cuò),因?yàn)樽置媪坎荒鼙环浅R谩?br> 3. 類型不匹配的常引用:
double d = 12.34;
//int& rd = d; // 該語(yǔ)句編譯時(shí)會(huì)出錯(cuò),類型不同
const int& rd = d;
根據(jù)類型不同的變量,如double d = 12.34;
,我們可以使用常引用const int& rd = d;
來(lái)引用它,直接使用非常引用int& rd = d;
會(huì)在編譯時(shí)報(bào)錯(cuò),因?yàn)轭愋筒黄ヅ洹?mark hidden color="red">文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-859338.html
??總結(jié)
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-859338.html
到了這里,關(guān)于我的C++奇跡之旅:值和引用的本質(zhì)效率與性能比較的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!