引用也是c++的初學(xué)者比較容易迷惑的概念,它幾乎擁有指針?biāo)械墓δ?但是語法更加簡(jiǎn)單,本章我們就來學(xué)習(xí)引用,并且分清它與指針的區(qū)別.
那么什么是引用呢?
簡(jiǎn)單概述:引用就是別名
例:
int main(){
int num;
//注意:別名mum前面的符號(hào)&不是取址運(yùn)算符,而是引用運(yùn)算符,雖然它們符號(hào)相同,但是功能卻不一樣.
int &mum=num;
// mum是num的別名,這兩個(gè)變量是一個(gè)變量,只不過名字不同而已,這就好像李四有個(gè)外號(hào)叫李大嘴,大家稱呼李四指的是李四這個(gè)人,稱呼李大嘴也是指的李四
//這個(gè)人,李四和李大嘴都是一個(gè)人,只是名字出現(xiàn)了不同.
}
引用的地址
網(wǎng)站建設(shè)哪家好,找成都創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、小程序定制開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了高碑店免費(fèi)建站歡迎大家使用!int a;
int &ra=a;
按照棧堆內(nèi)存的方式就可以很容易理解 a ra在棧內(nèi)存 而他們的實(shí)例是在堆內(nèi)存,他們引用是相同的。
引用就是別名常量
當(dāng)給一個(gè)變量定義了一個(gè)別名,那么該別名就永遠(yuǎn)屬于這個(gè)變量,當(dāng)有別的變量對(duì)它進(jìn)行賦值時(shí),雖然它的內(nèi)容會(huì)改變,但是它的內(nèi)存地址值不會(huì)發(fā)生變化,同時(shí)這個(gè)別名所對(duì)應(yīng)的變量的
值也會(huì)發(fā)生改變.
由于這種特性,所以我們可以將引用看成是一個(gè)別名常量,它的內(nèi)存空間無法改變,能改變的只是它所引用的值。
引用對(duì)象
我們也可以定義一個(gè)對(duì)象的別名,例:
Human Mike;//定義一個(gè)Human對(duì)象Mike
Human &rMike=Mike;//定義一個(gè)Mike的別名 rMike;
注意:定義一個(gè)引用的時(shí)候,一定要對(duì)該引用進(jìn)行初始化.
以下例子是錯(cuò)誤的:
int a;
int &ra;
ra=a;
這樣是錯(cuò)誤的.引用就象常量,只能對(duì)其初始化,不能賦值。
空引用
我們知道指針進(jìn)行刪除操作后,需要將它們賦值設(shè)為空,引用卻不需要這么做,這是因?yàn)橐檬窃瓉韺?duì)象的別名,加入該對(duì)象存放在棧中,那么在對(duì)象超出作用域時(shí)別名會(huì)和對(duì)象一起消失.
加入該對(duì)象存放在堆中,由于堆中內(nèi)存空間必須使用指針來訪問,因此用不著別名,即使在定義一個(gè)該指針的別名,那么將指針刪除并賦空之后,該指針的別名中的地址也相應(yīng)賦空了。
按值傳遞
什么是按值傳遞呢?
例如:
void swap(int a,int b){
int c;
cout<<"swap函數(shù)中,交換前,a:"<<a<<"b:"<<b<<endl;
c=a;
a=b;
b=c;
cout<<"swap函數(shù)中,交換后,a:"<<a<<"b:"<<b<<endl;
}
int main(){
int a=3,b=4;
cout<<"在主程序中,交換前,a:"<<a<<"b:"<<b<<endl;
swap(a,b);
cout<<"在主程序中,交換后,a:"<<a<<"b:"<<b<<endl;
return 0;
}
通過上面事例可以發(fā)現(xiàn),主程序中(main函數(shù))的a b 兩個(gè)值在交換的前后并沒有發(fā)生改變,而swap函數(shù)中的ab在交換前后發(fā)生了改變
這到底是為什么呢?加入說swap函數(shù)沒有交換主程序中的a和b,那么它交換的到底是誰的值呢?
這個(gè)問題看起來復(fù)雜,其實(shí)很簡(jiǎn)單,swap函數(shù)交換的是main函數(shù)中a和b的“副本”的值,也就是說在main函數(shù)中定義的a和b的備份的值,swap函數(shù)交換的是main函數(shù)中的a和b的副本,
而不是a和b本身。
那么為什么swap函數(shù)不直接交換a和b本身,卻去交換它們的副本的值呢?
這是因?yàn)楫?dāng)我們直接將a和b傳遞給swap函數(shù)時(shí),這樣的傳遞方式是 --按值傳遞---.
假如將a和b按值傳遞給swap函數(shù),那么編譯器會(huì)自動(dòng)在棧中創(chuàng)建a和b的拷貝,然后將a和b的拷貝傳遞給swap函數(shù).在swap函數(shù)中對(duì)a和b的拷貝進(jìn)行交換.因此我們看到的輸出語句,a和
b確實(shí)進(jìn)行了交換,只不過交換的是a和b的副本。
由于交換的是a和b的副本,并不是a和b本身,所以在swap函數(shù)結(jié)束后,輸出的值顯示main函數(shù)中的a和b并沒有改變.
按址傳遞
什么是按址傳遞?
從字面上理解就是按地址的方式傳遞.
例:
void swap(int *a,int *b){//這里參數(shù)要改為指針
int c;
cout<<"swap函數(shù)中,交換前,a:"<<*a<<"b:"<<*b<<endl;
c=*a;
*a=*b;
*b=c;
cout<<"swap函數(shù)中,交換后,a:"<<*a<<"b:"<<*b<<endl;
}
int main(){
int a=3,b=4;
cout<<"在主程序中,交換前,a:"<<a<<"b:"<<b<<endl;
swap(&a,&b);//這里加上取地址符。
cout<<"在主程序中,交換后,a:"<<a<<"b:"<<b<<endl;
return 0;
}
根據(jù)輸出就可以看到,主函數(shù)中的a和b也改變了.
按別名傳遞
把指針作為函數(shù)的接收參數(shù)雖然能夠正常使用,但是它卻不易閱讀,而且很難使用.
所以我們可以把接收指針參數(shù)改為接收兩個(gè)別名,由于別名改變的是內(nèi)存地址中的值,所以也可以完成改變main函數(shù)中a b值的需求
void swap(int &a,int &b){//這里參數(shù)要改為指針
int c;
cout<<"swap函數(shù)中,交換前,a:"<<a<<"b:"<<b<<endl;
c=a;
a=b;
b=c;
cout<<"swap函數(shù)中,交換后,a:"<<a<<"b:"<<b<<endl;
}
int main(){
int a=3,b=4;
cout<<"在主程序中,交換前,a:"<<a<<"b:"<<b<<endl;
swap(a,b);//這里加上取地址符。
cout<<"在主程序中,交換后,a:"<<a<<"b:"<<b<<endl;
return 0;
}
利用指針返回多值
我們知道函數(shù)只能返回一個(gè)值,那么假如有的時(shí)候我們需要函數(shù)返回多個(gè)值時(shí)該怎么辦?
指針或者引用可以幫助我們解決這個(gè)問題,我們使用別名或者指針的方式傳遞給函數(shù)一個(gè)以上的變量,在函數(shù)體中將需要返回的值賦給這些變量,由于使用引用或者指針傳遞變量允許函數(shù)
改變?cè)瓉淼淖兞?因此這些在函數(shù)體中被修改的變量均可以看做是已經(jīng)被該函數(shù)返回的值。
下例:用指針來演示一下返回三個(gè)值的操作。
int func(int a,int *b,int *c);//聲明
int main(){
int a=1,b=2,c=3;
cout<<"主程序,調(diào)用func函數(shù)前..
";
cout<<"a:"<<a<<endl<<"b:"<<b<<endl<<"c:"<<c<<endl;
func(a,&b,&c);
cout<<"主程序,調(diào)用func函數(shù)前..
";
cout<<"a:"<<a<<endl<<"b:"<<b<<endl<<"c:"<<c<<endl;
return 0;
}
int func(int a,int *b,int *c){
cout<<"func函數(shù)中,計(jì)算前...
";
cout<<"a:"<<a<<endl<<"b:"<<*b<<endl<<"c:"<<*c<<endl;
a=a+1;
*b=(*b)*(*b);
*c=(*c)*(*c);
cout<<"func函數(shù)中,計(jì)算后...
";
cout<<"a:"<<a<<endl<<"b:"<<*b<<endl<<"c:"<<*c<<endl;
return a;
}
寫完以后我發(fā)現(xiàn)好坑爹啊,這里的返回3個(gè)值的概念,居然是改變3個(gè)值?好坑...
這樣的方法的好處是我們可以實(shí)現(xiàn)匯報(bào)執(zhí)行程序時(shí)的非法操作信息,我們也可以把a(bǔ)作為返回的判斷值,把*b和*c作為運(yùn)算的返回值。
用接下來的代碼進(jìn)行演示:
int func(int a,int *b,int *c);//聲明
int main(){
int a,b,c;
int check;
cout<<"請(qǐng)輸入要進(jìn)行運(yùn)算的數(shù)字,";
cout<<"您輸入的數(shù)字將作為圓的半徑和正方形的邊長(zhǎng),";
cin>>a;
check=func(a,&b,&c);
if(check){
cout<<"輸入的數(shù)字超過計(jì)算范圍!
";
}else{
cout<<"圓的面積為:"<<b<<endl;
cout<<"正方形的面積為:"<<c<<endl;
}
return 0;
}
int func(int a,int *b,int *c){
if(a>20000){
a=1;
}else{
*b=a*a*3.14;
*c=a*a;
}
}
用引用來返回多值
其實(shí)就是改變多值了,可看可不看,用引用重寫上面的代碼,
int func(int a,int &b,int &c);//聲明
int main(){
int a,b,c;
int check;
cout<<"請(qǐng)輸入要進(jìn)行運(yùn)算的數(shù)字,";
cout<<"您輸入的數(shù)字將作為圓的半徑和正方形的邊長(zhǎng),";
cin>>a;
check=func(a,b,c);
if(check){
cout<<"輸入的數(shù)字超過計(jì)算范圍!
";
}else{
cout<<"圓的面積為:"<<b<<endl;
cout<<"正方形的面積為:"<<c<<endl;
}
return 0;
}
int func(int a,int &b,int &c){
if(a>20000){
a=1;
}else{
b=a*a*3.14;
c=a*a;
}
}
按值傳遞對(duì)象
從前面幾節(jié)我們了解了按址傳遞和按值傳遞的區(qū)別,按址傳遞可以修改原始變量的值,按值傳遞由于是傳遞的原始變量的副本,因此它不會(huì)修改原始變量的值.
假如僅僅是傳遞變量的話,采用指針或者引用這種按址傳遞的優(yōu)勢(shì)不是很明顯,但是假如是傳遞較大的對(duì)象的話,這種優(yōu)勢(shì)是相當(dāng)?shù)拿黠@的.
這是因?yàn)?按值傳遞在向函數(shù)傳遞一個(gè)對(duì)象時(shí),會(huì)象傳遞變量那樣建立一個(gè)該對(duì)象的拷貝,而從函數(shù)返回一個(gè)對(duì)象時(shí),也要建立這個(gè)被返回的對(duì)象的一個(gè)拷貝.
這樣加入該對(duì)象的數(shù)據(jù)非常多時(shí),這種拷貝帶來的內(nèi)存開銷是相當(dāng)可觀的.比如說該對(duì)象擁有0.000多個(gè)double型成員變量,每個(gè)double型變量占據(jù)8個(gè)字節(jié),1000個(gè)就占據(jù)8000
字節(jié),每次通過值傳遞的方式給函數(shù)傳遞該對(duì)象,都要在棧中復(fù)制該對(duì)象,占用8000個(gè)字節(jié)的棧內(nèi)空間,而返回該對(duì)象,又要在棧中復(fù)制一次,這樣就又要占用8000個(gè)字節(jié)的內(nèi)存空間.我
們知道棧的內(nèi)存只有2M大小,8000個(gè)字節(jié)占用8K,那么僅僅傳遞該對(duì)象就占用了棧內(nèi)16k字節(jié)的空間。并且別的對(duì)象想要訪問該對(duì)象的8000個(gè)數(shù)據(jù)成員的時(shí)候,也要同樣采取復(fù)制的方式
,那么系統(tǒng)的開銷將無法估算了。
然后,按值傳遞所付出的開銷遠(yuǎn)不止如此,由于在傳遞過程中需要復(fù)制對(duì)象,因此會(huì)默認(rèn)調(diào)用復(fù)制構(gòu)造函數(shù),該函數(shù)的作用就是創(chuàng)建某個(gè)對(duì)象的臨時(shí)副本.關(guān)于復(fù)制構(gòu)造函數(shù),將會(huì)在深
入函數(shù)中做進(jìn)一步講解,這里你只需要知道,這要在棧中創(chuàng)建臨時(shí)拷貝都會(huì)自動(dòng)調(diào)用復(fù)制構(gòu)造函數(shù)即可。
而當(dāng)函數(shù)返回時(shí),傳遞該對(duì)象時(shí)創(chuàng)建的該對(duì)象的副本會(huì)被刪除,這時(shí)候又會(huì)自動(dòng)調(diào)用該對(duì)象的析構(gòu)函數(shù)來釋放內(nèi)存。假設(shè)返回的仍然是該對(duì)象,并且仍舊采用按值傳遞的方式,那么就
又會(huì)調(diào)用復(fù)制構(gòu)造函數(shù)建立一個(gè)該對(duì)象的臨時(shí)副本,當(dāng)改值被成功返回給調(diào)用程序后,然后再調(diào)用該對(duì)象的析構(gòu)函數(shù)刪除臨時(shí)拷貝并釋放內(nèi)存。
我們看到復(fù)制構(gòu)造函數(shù)和析構(gòu)函數(shù)一連被執(zhí)行了兩次,這無疑會(huì)增加系統(tǒng)的開銷,我們用一個(gè)實(shí)例來演示一下按值傳遞一個(gè)對(duì)象的復(fù)制與刪除過程。
class A{
public :
A(){cout<<"執(zhí)行構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象
";}
A(A&){cout<<"執(zhí)行復(fù)制構(gòu)造函數(shù)創(chuàng)建該對(duì)象的副本
";}
~A(){cout<<"執(zhí)行析構(gòu)函數(shù)刪除該對(duì)象
";}
};
A func(A one){
return one;//由于返回也是按值傳遞 所以又調(diào)用了類A的復(fù)制構(gòu)造函數(shù)
}
int main(){
A a;//創(chuàng)建一個(gè)對(duì)象會(huì)自動(dòng)調(diào)用構(gòu)造函數(shù)
func(a);//將對(duì)象a按值傳遞給func函數(shù)中,然后對(duì)調(diào)用類A的復(fù)制構(gòu)造函數(shù)
return 0;
}
通過輸出可以發(fā)現(xiàn),將一個(gè)對(duì)象按值傳遞給一個(gè)函數(shù),會(huì)調(diào)用兩次復(fù)制構(gòu)造函數(shù)和兩次析構(gòu)函數(shù),這樣系統(tǒng)的開銷很很大的
按址傳遞對(duì)象
為了解決上面開銷大的問題,我們可以如下寫法:
A func(A *one){
return *one;
}
int main(){
A a;
func(&a);
return 0;
}
這樣就可以減少一次復(fù)制構(gòu)造函數(shù)的執(zhí)行,也就對(duì)應(yīng)的減少一次調(diào)用析構(gòu)函數(shù)來刪除復(fù)制構(gòu)造函數(shù)創(chuàng)建對(duì)象.
但是復(fù)制構(gòu)造函數(shù)依然執(zhí)行了一次,那么我們?nèi)绾伪苊膺@次復(fù)制構(gòu)造函數(shù)的執(zhí)行呢?
上面函數(shù)func中 返回return *one 由于返回的是對(duì)象,而不是地址,所以這種返回方式是按值返回.就會(huì)造成調(diào)用復(fù)制構(gòu)造函數(shù)。
如果我們不需要調(diào)用復(fù)制構(gòu)造函數(shù)那么就可以如下寫
A* func(A *one){
return one;
}
這里需要注意的是返回值是類A的內(nèi)存地址引用 寫法為 A* 而不能是A 否則就會(huì)報(bào)類型不匹配異常。
使用const指針傳遞對(duì)象
按址傳遞對(duì)象雖然可以避免調(diào)用復(fù)制構(gòu)造函數(shù)和析構(gòu)函數(shù),但是由于它得到了該對(duì)象的內(nèi)存地址,可以隨時(shí)修改對(duì)象的數(shù)據(jù).所以它實(shí)際上是破壞了按值傳遞的保護(hù)機(jī)制。比如說按值傳遞
就象把盧浮宮的那副達(dá)芬奇的畫制作了一個(gè)副本,送交法國總理希拉克的官邸,這樣希拉克對(duì)該畫的任何操作也不會(huì)影響到原畫。不過假如希拉克親自跑到盧浮宮去觀賞原畫,那么他就完
全可以對(duì)原畫進(jìn)行修改或操作。不過我們?nèi)匀粚?duì)此有解決辦法,那就是用const指針來接受對(duì)象,這樣就可以防止任何試圖對(duì)該對(duì)象所進(jìn)行的操作行為,并且保證返回一個(gè)不被修改的對(duì)象
例:
const A* const func(const A *const one){}
//這樣參數(shù)one前加上const 那么參數(shù)one就是不可修改的
//在A前加上const這樣參數(shù)指針one 指向的對(duì)象也就是不可修改的
//在func前面加上const 代表返回的one指針不可修改
//在類名A前面加上const 代表返回的指針one 也是不可以被修改的
這樣就保證了傳遞進(jìn)來的數(shù)據(jù)不被修改,同時(shí)又保證了返回的數(shù)據(jù)也不會(huì)被修改。
我們將函數(shù)的返回值和接收參數(shù)都定義為const,就可以保證函數(shù)內(nèi)不可修改原始值,同時(shí)避免利用返回值對(duì)原始值進(jìn)行修改.所以加上這個(gè)const,實(shí)際上是為了實(shí)現(xiàn)按值傳遞的開銷,
因?yàn)椴挥迷僬{(diào)用復(fù)制構(gòu)造函數(shù)。但是加const很麻煩,下面有種簡(jiǎn)便的方法.按別名傳遞對(duì)象。
按別名傳遞對(duì)象
由于引用不能重新分配去引用另一個(gè)對(duì)象,它始終是常量,所以我們不用將它設(shè)置為常量.所以就不需要加const修飾符
A *cunc(A &one){
return one;
}
int main(){
A a;
a.set(11);
A &b=func(a);
count<<b.get()<<endl;
return 0;
}
那么到底是引用還是指針呢?
既然引用實(shí)現(xiàn)了指針的功能,而且使用起來更加方便,為什么還要指針呢?
這是因?yàn)橹羔樋梢詾榭?但是引用不能為空,指針可以被賦值,但是引用只可以被初始化,不可以被賦為另一個(gè)對(duì)象的別名.如果你想使一個(gè)變量記錄不同對(duì)象的地址,那么就必須使用指針
例:
int main(){
int a=6;
int *p=&a;
int b=9;
p=&b;
return 0;
}
指針p可以讓其指向a 也可以指向b 但是別名卻不可以,另外,在堆中創(chuàng)建一塊內(nèi)存區(qū)域,必須要用指針來指向它,否則該區(qū)域就會(huì)變成無法訪問的內(nèi)存空間。當(dāng)然我們也可以使用引用
來引用指向內(nèi)存空間的指針。
用例子來演示上面這句話的意思
例:
int main(){
int *p=new int;
int &r=*p; //這樣,這個(gè)r就變成了用指針p讀取到的值的別名。
r=4;
cout<<*p<<endl;
return 0;
}
但是我們要明白一點(diǎn),我們不可以直接用引用來指向堆中新建的空間,因?yàn)橐弥皇莻€(gè)別名,它不可以作為指針來使用。
因?yàn)樵跈C(jī)器運(yùn)行不正常的情況下,也就是機(jī)器虛擬內(nèi)存大小,無法創(chuàng)建新空間的情況下,那么new int 會(huì)自動(dòng)返回一個(gè)空指針。我們知道引用不能為空,因此這種情況下使用
int *&r=new int;就會(huì)導(dǎo)致一個(gè)無用的別名。而使用(*)讀取一個(gè)無用的別名則會(huì)引起系統(tǒng)崩潰。
解決的辦法是不要將引用初始化為新建內(nèi)存區(qū)域的別名,而要將r初始化為指向該區(qū)域的指針的別名。前提是首先要判斷該指針不為空。
例:
int *p=new int; //第一行定義了一個(gè)指向int的指針p,該指針指向新建的一塊內(nèi)存.
if(p!=null){ //第二行測(cè)試p,加入不為空,表示空間創(chuàng)建成功。
int &r=*p; //將r初始化為p指向的內(nèi)存空間中數(shù)據(jù)的別名
r=3; //設(shè)置空間值為3
cout<<r<endl; //輸出該空間的值
}
指針與引用的區(qū)別:
指針可以為空,引用不能為空.
指針可以被賦值,引用不能被賦值。
指針可以指向堆中空間,引用不可以指向堆中空間。
了解了 指針與引用的區(qū)別以后,我們就可以有選擇的來使用指針或者引用了。
引用和指針可以一起用
上一節(jié)我們已經(jīng)學(xué)習(xí)了一個(gè)指針和引用混合使用的例子,如:int *&r=new int;
int *func(int &one,int *two,int x);
上面這行語句聲明了一個(gè)func函數(shù),該函數(shù)有三個(gè)參數(shù),第一個(gè)是int型變量的別名one,第二個(gè)是指向int型變量的指針two,第三個(gè)是整型參數(shù)x。該函數(shù)返回一個(gè)指向int型變量
的指針。
另外我們也要注意指針的一些特殊寫法,如:
int *r,ra; 這里的r是指針 ra是變量。一定不要將兩者都看作是指針
引用容易犯的錯(cuò)誤
與指針一樣,引用使用不當(dāng)也會(huì)出現(xiàn)致命性的錯(cuò)誤。我們知道引用是對(duì)象的別名,那么假如這個(gè)對(duì)象不存在了,使用這個(gè)對(duì)象的別名會(huì)產(chǎn)生什么樣的后果呢?
class A{
public:
A(int i){x=i;}
int get(){return x;}
private:
int x;
};
A&func(){
A a(23);
return a;
}
int main(){
A &r=func();//用別名接收別名
cout<<r.get()<<endl;
return 0;
}
由于對(duì)象a是個(gè)局部對(duì)象,因此當(dāng)函數(shù)func結(jié)束后,局部對(duì)象a也就被刪除了。由于對(duì)象a消失了,所以func()函數(shù)返回的其實(shí)是一個(gè)并不存在的對(duì)象的別名。因此打印的結(jié)果并不是23
,返回一個(gè)隨機(jī)數(shù),如果去掉func方法上的& 那么就會(huì)返回a
引用按值返回的堆中對(duì)象
常見錯(cuò)誤2.
A func(){
cout<<"跳轉(zhuǎn)到func函數(shù)中!
";
A *p=new A(99);
cout<<"隊(duì)中對(duì)象的內(nèi)存地址"<<p<<endl;
return *p;
}
int main(){
A &r=func();
cout<<"隊(duì)中對(duì)象的副本的地址"<<&r<<endl;
cout<<r.get()<<endl;
return 0;
}
由于p所指向的堆中空間必須使用delete運(yùn)算符才能被刪除,因此該空間成了不可訪問的區(qū)域,結(jié)果導(dǎo)致了內(nèi)存泄露。
又或者說:p指針被刪除了,它所指向的空間還存在。該空間的地址只有p保存著,而p找不到了,所以我們無法找到該空間。由于無法找到該空間,所以無法對(duì)其進(jìn)行釋放,結(jié)果造成了
內(nèi)存泄露。
引用按別名返回的堆中對(duì)象
要避免上面例子出現(xiàn)的內(nèi)存泄露,我們就不能用按值的方式來返回一個(gè)堆中對(duì)象,而必須按地址或者別名的方式返回一個(gè)別名或者內(nèi)存地址,這樣就不會(huì)調(diào)用復(fù)制構(gòu)造函數(shù)創(chuàng)建一個(gè)該對(duì)象的
副本,而是直接將該對(duì)象的別名或者地址返回。由于返回的對(duì)象的別名或者地址初始化給了main函數(shù)中的一個(gè)引用或者指針,因此即使被調(diào)用函數(shù)中的局部指針超過作用域被系統(tǒng)釋放,也
可由main函數(shù)中的引用或者指針找到該堆中空間,不會(huì)令該空間成為不可訪問的區(qū)域,從而避免了內(nèi)存泄露。
所以將上例中的func()函數(shù)改為
A& func(){
cout<<"跳轉(zhuǎn)到func函數(shù)中!
";
A *p=new A(99);
cout<<"堆中對(duì)象的地址:"<<p<<endl;
return *p;
}
int main(){
A &r=func();
cout<<"堆中對(duì)象的副本的地址:"<<&r<<endl;
cout<<r.get()<<endl;
A *p=&r;
delete p;
return 0;
}
通過輸出語句可以看出兩次的內(nèi)存地址是相同的,由于它們的地址相同,所以即使func函數(shù)中的局部指針p被系統(tǒng)自動(dòng)銷毀了,我們也能通過main函數(shù)中的別名r來訪問到堆中對(duì)象。
因?yàn)閞與堆中對(duì)象的地址是相同,r是堆中對(duì)象的別名,所以對(duì)r的操作就是對(duì)堆中對(duì)象的操作。由于無法對(duì)引用使用delete運(yùn)算符,因此我們只能定義一個(gè)指針來存儲(chǔ)應(yīng)用的地址,然后
刪除該指針指向的內(nèi)存空間。
這一切操作都非常順利,程序也輸出了正確的結(jié)果。但是這個(gè)程序卻隱藏著一個(gè)非常嚴(yán)重的問題。
由于p指向的堆中對(duì)象被刪除了,因此堆中對(duì)象的別名r成了個(gè)空別名。它就成為了一個(gè)不存在的對(duì)象的別名,因此假如再使用這個(gè)空別名去訪問不存在的對(duì)象的成員函數(shù)get()時(shí) 就會(huì)輸出
一個(gè)隨機(jī)數(shù)。這樣都導(dǎo)致了一個(gè)不易察覺的錯(cuò)誤。
在哪里創(chuàng)建,就在哪里釋放
為了解決上例中的不易察覺的錯(cuò)誤,我們就需要做到在哪里創(chuàng)建,就在哪里釋放。
只要在堆中創(chuàng)建一塊內(nèi)存空間,就會(huì)返回一個(gè)指向該空間的指針,我們一定不要弄丟該指針,加入該指針丟失,那么該堆中空間就會(huì)成為一塊不可訪問的區(qū)域,也就是我們常說的內(nèi)存泄露
同時(shí)假如我們將存儲(chǔ)在堆中的對(duì)象初始化給一個(gè)引用,那么當(dāng)該對(duì)象被刪除時(shí),這個(gè)引用也就成了空引用,假如我們無意中使用了這個(gè)空引用的話,就會(huì)令程序出錯(cuò)。
上面的兩個(gè)例子中的程序所犯的錯(cuò)誤,在這里就來解決這些錯(cuò)誤,我們可以在func()函數(shù)中創(chuàng)建堆中對(duì)象,然后在main函數(shù)中釋放該對(duì)象,但是在上例的程序告訴我們,這樣也不安全,雖然
我們也可以用指針來代替引用,但是加入我們無法確定指向堆中對(duì)象的指針是哪一個(gè),是func()函數(shù)的p指針,還是main函數(shù)中接受堆中對(duì)象的r指針,那么我們就有可能將該指針刪除兩次,
或者忘記刪除指針。這樣為了避免指針混淆,我們就必須:在哪里創(chuàng)建,就在哪里釋放。
因此我們?cè)趍ain函數(shù)中創(chuàng)建一個(gè)堆中對(duì)象,然后按引用的方式傳遞到func()函數(shù)中,在func()函數(shù)中對(duì)該對(duì)象操作完畢后返回該對(duì)象,然后在main函數(shù)中釋放該對(duì)象.這樣就實(shí)現(xiàn)了在
main函數(shù)中創(chuàng)建,在main函數(shù)中釋放。
寫法如下:
#include<iostream>
using namespace std;
class A{
public:
A(int i){cout<<"執(zhí)行構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象
";x=1;}
A(A&A){x=a.x;cout<<"執(zhí)行復(fù)制構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象
";}
~A(){cout<<"執(zhí)行析構(gòu)函數(shù)!
";}
int get(){return x;}
void set(int i){x=1;}
private:
int x;
};
A& func(A&a){
cout<<"跳轉(zhuǎn)到func函數(shù)中!
";
a.set(66);
return a;
}
int main(){
A *p=new A(99);
func(*p);
cout<<p->get()<<endl;
delete p;
return 0;
}
新聞名稱:(五)引用-創(chuàng)新互聯(lián)
文章URL:http://jinyejixie.com/article44/dioghe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供做網(wǎng)站、外貿(mào)網(wǎng)站建設(shè)、ChatGPT、面包屑導(dǎo)航、小程序開發(fā)、標(biāo)簽優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容