深入解析C++中的引用類型
c++比起c來除了多了類類型外還多出一種類型:引用。這個(gè)東西變量不象變量,指針不象指針,我以前對(duì)它不太懂,看程序時(shí)碰到引用都稀里糊涂蒙過去。最近把引用好好地揣摩了一番,小有收獲,特公之于社區(qū),讓初學(xué)者們共享。
引用指的是對(duì)一個(gè)對(duì)象的引用。那么什么是對(duì)象?在c++中狹義的對(duì)象指的是用類,結(jié)構(gòu),聯(lián)合等復(fù)雜數(shù)據(jù)類型來聲明的變量,如 MyClass myclass,CDialog mydlg,等等。廣義的對(duì)象還包括用int,char,float等簡單類型聲明的變量,如int a,char b等等。我在下文提到“對(duì)象”一詞全指的是廣義的對(duì)象。c++
的初學(xué)者們把這個(gè)廣義對(duì)象的概念建立起來,對(duì)看參考書是很有幫助的,因?yàn)榇蠖鄶?shù)書上只顧用“對(duì)象”這個(gè)詞,對(duì)于這個(gè)詞還有廣義和狹義兩種概念卻只字不提。
一。引用的基本特性
首先讓我們聲明一個(gè)引用并使用它來初步認(rèn)識(shí)引用。
例一:
int v,k,h;
int &rv=v;
rv=3; //此時(shí)v的值也同時(shí)變成了3。
v=5;
k=rv+2; //此時(shí)k=5+2=7。
h=12;
rv=h;
rv=20;
第1句聲明了三個(gè)對(duì)象(簡單變量)。
第2句的意思是:聲明了一個(gè)引用,名字叫rv,它具有int類型,或者說它是對(duì)int類型的引用,而且它被初始化為與int類型的對(duì)象v“綁定”在一起。此時(shí)rv叫做對(duì)象v的引用。
第3句把rv的值賦為3。引用的神奇之處就在這里,改變引用的值的同時(shí)也改變了和引用所綁定在一起的對(duì)象的值。所以此時(shí)v的值也變成了3。
第4句把v的值改為5,此時(shí)指向v的引用的值也被改成了5。所以第5句的中k的值是5+2等于7。
上述5句說明了引用及其綁定的對(duì)象的關(guān)系:在數(shù)值上它們是聯(lián)動(dòng)的,改變你也就改變了我,改變我也就改變了你。事實(shí)上,訪問對(duì)象和訪問對(duì)象的引用,就是訪問同一塊內(nèi)存區(qū)域。
第6,7,8三句說明了引用的另一個(gè)特性:從一而終。什么意思?當(dāng)你在引用的聲明語句里把一個(gè)引用綁定到某個(gè)對(duì)象后,這個(gè)引用就永遠(yuǎn)只能和這個(gè)對(duì)象綁定在一起了,沒法改了。所以這也是我用了“綁定”一詞的原因。而指針不一樣。當(dāng)在指針的聲明語句里把指針初始化為指向某個(gè)對(duì)象后,這個(gè)指針在將來如有需要還可以改指別的對(duì)象。因此,在第7句里把rv賦值為h,并不意味著這個(gè)引用rv被重新綁定到了h。事實(shí)上,第7句只是一條簡單的賦值語句,執(zhí)行完后,rv和v的值都變成了12。第8句執(zhí)行完后,rv和v的值都是20,而h保持12不變。
引用還有一個(gè)特性:聲明時(shí)必須初始化,既必須指明把引用綁定到什么對(duì)象上。大家知道指針在聲明時(shí)可以先不初始化,引用不行。所以下列語句將無法通過編譯:
int v;
int &rv;
rv=v;
再舉一例:
例二:
class MyClass
{
public:
int a;
...
...
};
MyClass myclass;
Myclass& cc=myclass;
myclass.a=20; //等價(jià)于cc.a=20
cc.a=60; //等價(jià)于myclass.a=60
從以上例子可以看到,無論這個(gè)對(duì)象有多復(fù)雜,使用該對(duì)象的引用或是使用該對(duì)象本身,在語法格式上是一樣的,在本質(zhì)上我們都使用了內(nèi)存中的同一塊區(qū)域。
取一個(gè)引用的地址和取一個(gè)對(duì)象的地址的語法是一樣的,都是用取地址操作符"&"。例如:
int i;
int &ri;
int *pi=&ri;//這句的作用和int *pi=&i是一樣的。
當(dāng)然了,取一個(gè)對(duì)象的地址和取這個(gè)對(duì)象的引用的地址,所得結(jié)果是一樣的。
二。引用在函數(shù)參數(shù)傳遞中的作用
現(xiàn)在讓我們通過函數(shù)參數(shù)的傳遞機(jī)制來進(jìn)一步認(rèn)識(shí)引用。在c++中給一個(gè)函數(shù)傳遞參數(shù)有三種方法:1,傳遞對(duì)象本身。2,傳遞指向?qū)ο蟮闹羔槨?,傳遞對(duì)象的引用。
例三:
class MyClass
{
public:
int a;
void method();
};
MyClass myclass;
void fun1(MyClass);
void fun2(MyClass*);
void fun3(MyClass&);
fun1(myclass); //執(zhí)行完函數(shù)調(diào)用后,myclass.a=20不變。
fun2(&myclass); //執(zhí)行完函數(shù)調(diào)用后,myclass.a=60,改變了。
fun3(myclass); //執(zhí)行完函數(shù)調(diào)用后,myclass.a=80,改變了。
//注意fun1和fun3的實(shí)參,再次證明了:使用對(duì)象和使用對(duì)象的引用,在語法格式上是一樣的。
void fun1(MyClass mc)
{
mc.a=40;
mc.method();
}
void fun2(MyClass* mc)
{
mc->a=60;
mc->method();
}
void fun3(MyClass& mc)
{
mc.a=80;
mc.method();
}
我們有了一個(gè)MyClass類型的對(duì)象myclass和三個(gè)函數(shù)fun1,fun2,fun3,這三個(gè)函數(shù)分別要求以對(duì)象本身為參數(shù);以指向?qū)ο蟮闹羔槥閰?shù);以對(duì)象的引用為參數(shù)。
請(qǐng)看fun1函數(shù),它使用對(duì)象本身作為參數(shù),這種傳遞參數(shù)的方式叫傳值方式。c++將生成myclass對(duì)象的一個(gè)拷貝,把這個(gè)拷貝傳遞給fun1函數(shù)。在fun1函數(shù)內(nèi)部修改了mc的成員變量a,實(shí)際上是修改這個(gè)拷貝的成員變量a,絲毫影響不到作為實(shí)參的myclass的成員變量a。
fun2函數(shù)使用指向MyClass類型的指針作為參數(shù)。在這個(gè)函數(shù)內(nèi)部修改了mc所指向的對(duì)象的成員變量a,這實(shí)際上修改的是myclass對(duì)象的成員變量a。
fun3使用myclass對(duì)象的引用作為參數(shù),這叫傳引用方式。在函數(shù)內(nèi)部修改了mc的成員變量a,由于前面說過,訪問一個(gè)對(duì)象和訪問該對(duì)象的引用,實(shí)際上是訪問同一塊內(nèi)存區(qū)域,因此這將直接修改myclass的成員變量a。
從fun1和fun3的函數(shù)體也可看出,使用對(duì)象和使用對(duì)象的引用,在語法格式上是一樣的。
在fun1中c++將把實(shí)參的一個(gè)拷貝傳遞給形參。因此如果實(shí)參占內(nèi)存很大,那么在參數(shù)傳遞中的系統(tǒng)開銷將很大。而在fun2和fun3中,無論是傳遞實(shí)參的指針和實(shí)參的引用,都只傳遞實(shí)參的地址給形參,充其量也就是四個(gè)字節(jié),系統(tǒng)開銷很小。
三。返回引用的函數(shù)
引用還有一個(gè)很有用的特性:如果一個(gè)函數(shù)返回的是一個(gè)引用類型,那么該函數(shù)可以被當(dāng)作左值使用。什么是左值搞不懂先別管,只需了解:如果一個(gè)對(duì)象或表達(dá)式可以放在賦值號(hào)的左邊,那么這個(gè)對(duì)象和表達(dá)式就叫左值。
舉一個(gè)雖然無用但很說明問題的例子:
例四:
int i;
int& f1(int&);
int f2(int);
f1(i)=3;
f2(i)=4;
int& f1(int&i)
{
return i;
}
int f2(int i)
{
return i;
}
試試編譯一下,你會(huì)發(fā)現(xiàn)第4句是對(duì)的,第5句是錯(cuò)的。對(duì)這個(gè)例子而言,i的引用被傳遞給了f1,然后f1把這個(gè)引用原樣返回,第4句的意義和i=3是一樣的。
查了查書,引用的這個(gè)特性在重載操作符時(shí)用得比較多。但是我對(duì)重載操作符還是稀里糊涂,所以就舉不出例子了。
強(qiáng)調(diào)一個(gè)小問題,看看如下代碼有何錯(cuò)誤:
int &f1();
f1()=5;
...
...
int &f1()
{
int i;
int &ri=i;
return ri;
}
注意函數(shù)f1返回的引用ri是在函數(shù)體內(nèi)聲明的,一旦函數(shù)返回后,超出了函數(shù)作用域,ri所指向的內(nèi)存區(qū)域,即對(duì)象i所占據(jù)的內(nèi)存區(qū)域就被收回了,再對(duì)這片內(nèi)存區(qū)域賦值會(huì)出錯(cuò)的。
四。引用的轉(zhuǎn)換
前面所舉的例子,引用的類型都是int類型,并且這些引用都被初始化為綁定到int類型的對(duì)象。那么我們?cè)O(shè)想是否可以聲明一個(gè)引用,它具有int類型,卻被初始化綁定到一個(gè)float類型的對(duì)象?如下列代碼所示:
float f;
int &rv=f;
結(jié)果證明這樣的轉(zhuǎn)換不能通過msvc++6.0的編譯。但是引用的轉(zhuǎn)換并非完全不可能,事實(shí)上一個(gè)基類類型的引用可以被初始化綁定到派生類對(duì)象,只要滿足這兩個(gè)條件:
1,指定的基類是可訪問的。
2,轉(zhuǎn)換是無二義性的。
舉個(gè)例子: 例五:
class A
{
public:
int a;
};
class B:public A
{
public:
int b;
};
A Oa;
B Ob;
A& mm=Ob;
mm.a=3;
我們有一個(gè)基類A和派生類B,并且有一個(gè)基類對(duì)象Oa和派生類對(duì)象Ob,我們還聲明了一個(gè)引用mm,它具有基類類型但被綁定到派生類對(duì)象Ob上。由于我們的這兩個(gè)類都很簡單,滿足那兩個(gè)條件,因此這段代碼是合法的。在這個(gè)例子中,mm和派生類Ob中的基類子對(duì)象是共用一段內(nèi)存單元的。所以,語句mm.a=3相當(dāng)于Ob.a=3,但是表達(dá)式mm.b卻是不合法的,因?yàn)榛愖訉?duì)象并不包括派生類的成員。
五??偨Y(jié)
最后把引用給總結(jié)一下:
1。對(duì)象和對(duì)象的引用在某種意義上是一個(gè)東西,訪問對(duì)象和訪問對(duì)象的引用其實(shí)訪問的是同一塊內(nèi)存區(qū)。
2。使用對(duì)象和使用對(duì)象的引用在語法格式上是一樣的。
3。引用必須初始化。
4。引用在初始化中被綁定到某個(gè)對(duì)象上后,將只能永遠(yuǎn)綁定這個(gè)對(duì)象。
5。基類類型的引用可以被綁定到該基類的派生類對(duì)象,只要基類和派生類滿足上文提到的兩個(gè)條件 。這時(shí), 該引用其實(shí)是派生類對(duì)象中的基類子對(duì)象的引用。
6。用傳遞引用的方式給函數(shù)傳遞一個(gè)對(duì)象的引用時(shí),只傳遞了該對(duì)象的地址,系統(tǒng)消耗較小。在函數(shù)體內(nèi)訪問 形參,實(shí)際是訪問了這個(gè)作為實(shí)參的對(duì)象。
7。一個(gè)函數(shù)如果返回引用,那么函數(shù)調(diào)用表達(dá)式可以作為左值。
六。其他
1。本文中的代碼在msvc++6.0中調(diào)試驗(yàn)證過。
2。第四節(jié)“引用的轉(zhuǎn)換”中的例子:
float f;
int &rv=f;
查看bc++3.1的資料,據(jù)說是合法的。此時(shí)編譯器生成了一個(gè)float類型的臨時(shí)
對(duì)象,引用rv被綁定到了這個(gè)臨時(shí)對(duì)象上,就是說,此時(shí)rv并不是f的引用。不知
道bc++3.1里的這個(gè)特性有什么用。
3??梢栽趍svc++6.0里聲明這樣的引用:
const int &rv=3;
此時(shí)rv的值就是3,而且無法更改。這可能沒有有什么用。因?yàn)槿绻覀円?BR>用一個(gè)符號(hào)來代表常數(shù)的話,有的是更常見的方法:
#define rv 3
4。把第四節(jié)中的例子稍稍修改一下:
float f;
int &rv=(int&)f;
這時(shí)就可以通過msvc++6.0的編譯了。此時(shí)rv被綁定到了f上,rv和f共用一片存儲(chǔ)區(qū)。不過由于引用rv的類型是int,所以通過rv去訪問這片存儲(chǔ)區(qū)時(shí),存儲(chǔ)區(qū)的內(nèi)容被解釋為整數(shù);通過f去訪問這片存儲(chǔ)區(qū)時(shí),存儲(chǔ)區(qū)的內(nèi)容被解釋為實(shí)數(shù)。
相關(guān)文章
用C++實(shí)現(xiàn)strcpy(),返回一個(gè)char*類型的深入分析
本篇文章是對(duì)用C++實(shí)現(xiàn)strcpy(),返回一個(gè)char*類型進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C++/Php/Python/Shell 程序按行讀取文件或者控制臺(tái)的實(shí)現(xiàn)
下面小編就為大家?guī)硪黄狢++/Php/Python/Shell 程序按行讀取文件或者控制臺(tái)的實(shí)現(xiàn)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03使用VC6.0對(duì)C語言程序進(jìn)行調(diào)試的基本手段分享
這篇文章主要介紹了用VC6.0開發(fā)c語言程序的時(shí)候調(diào)試代碼的一些小技巧,需要的朋友可以參考下2013-07-07vscode和cmake編譯多個(gè)C++文件的實(shí)現(xiàn)方法
這篇文章主要介紹了vscode和cmake編譯多個(gè)C++文件的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03解析C/C++指針、函數(shù)、結(jié)構(gòu)體、共用體
這篇文章主要介紹了C/C++指針、函數(shù)、結(jié)構(gòu)體、共用體的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01