內容 | 關鍵字 |
---|---|
引用的概念及用法 | 別名,&符號 |
引用做參數 | 普通引用,const引用 |
引用做返回值 | 臨時變量,全局變量做返回值區別 |
彙編層看引用的特性 | 棧幀,eax寄存器 |
引用和指針的區別 | sizeof區別,定義區別,自增區別 |
(一 )引用的概念
什麼是“引用”?申明和使用“引用”要注意哪些問題?
- 引用就是某個目標變量的“別名”(alias),對應用的操作與對變量直接操作效果完全相同。
- 申明一個引用的時候,切記要對其進行初始化。
- 引用聲明完畢後,相當於目標變量名有兩個名稱,即該目標原名稱和引用名,不能-再把該引用名作爲其他變量名的別名。
下面這種引用就是不合法的
int a=9,c=10;
int &b=a;
int &b=c;
- 聲明一個引用,不是新定義了一個變量,它只表示該引用名是目標變量名的一個別名,它本身不是一種數據類型,因此引用本身不佔存儲單元,系統也不給引用分配存儲單元。不能建立數組的引用。
引用格式:
類型 & 變量名=已定義過的變量名
引用用法:
- 普通變量引用
- const引用
- 不同類型之間的引用
1)普通變量引用
int a=10;
int &b=a;
int &c=b;
int &d=c;
cout<<"&a="<<&a<<endl;
cout<<"&b="<<&b<<endl;
cout<<"&c="<<&c<<endl;
cout<<"&d="<<&d<<endl;
d=13;
c=2;
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
cout<<"c="<<c<<endl;
cout<<"d="<<d<<endl;
運行結果:
[zyc@localhost lesion1]$ ./a.out
&a=0x7fff1bc0b04c
&b=0x7fff1bc0b04c
&c=0x7fff1bc0b04c
&d=0x7fff1bc0b04c
a=2
b=2
c=2
d=2
結論:
- 一個 變量可以有多個引用,但是一個引用不對應多個變量;
- 儘管該變量被引用多次,但是,引用對象的地址都是一樣的,即不開闢空間,所以改變其中一個值,其他值跟着改變。
2)const引用
//類型1
int d1=4;
const int &d2=d1;
d1=5; //d1改變,d2也會改變
//d2=6; //d2是常量(const修飾),不能給其賦值
//類型2
const int d3=10;
//int &d4=d3;
const int &d4=d3; //常量具有常屬性,只用常引用才能引用常量
//類型3
const int &g=10;
cout<<"g="<<m<<endl;
cout<<"d1="<<d1<<endl;
cout<<"d2="<<d2<<endl;
cout<<"d3="<<d3<<endl;
cout<<"d4="<<d4<<endl;
cout<<"g="<<m<<endl;
運行結果:
[zyc@localhost lesion1]$ ./a.out
d1=5
d2=5
d3=10
d4=10
g=45
總結:
如果變量沒有用const修飾,而引用可以用coonst修飾,也可以不用const修飾。這裏可以理解爲具有可讀可寫的變量可以向僅可讀方向轉化,但反過來不行。
如果變量被const修飾,則引用也必須被const修飾,且變量值和引用值都不可以被修改。因爲常量具有隻讀屬性。常引用引用常變量。
3)不同類型變量之間的引用
cout<<"========不同類型之間的引用==========="<<endl;
double e=1.99998;
const int &f=e; //e是double類型,f是int類型,f引用e時,要創建一個臨時變量
//即f引用的是那個帶常量的臨時變量,所有不能賦值給引用。
e=2.333333;
cout<<"e="<<e<<endl;
cout<<"&e="<<&e<<endl;
cout<<"f="<<f<<endl;
cout<<"&f"<<&f<<endl;
運行結果:
[zyc@localhost lesion1]$ ./a.out
e=2.33333 //打印的是修改過的e值
&e=0x7fff1bc0b038 //e的地址
f=1 //打印的是e的初始值並且是截斷的。
&f0x7fff1bc0b050 //f的地址(臨時變量的地址),和e的地址不一樣,
結論:當引用類型和定義變量類型不一致時,會產生一個臨時變量。改變變量的值,引用值並不會改變。
(二)引用做參數
以交換兩數來舉例
值傳遞版本
#include<iostream>
using namespace std;
void Swap(int x,int y)
{
int tmp=x;
x=y;
y=tmp;
}
int main()
{
int a=1;
int b=3;
cout<<"交換前"<<a<<","<<b<<endl;
Swap(a,b);
cout<<"交換後"<<a<<","<<b<<endl;
return 0;
}
運行結果:
[zyc@localhost lession_quote]$ ./a.out
交換前1,3
交換後1,3
結論:並沒有實現交換的功能
引用版本
#include<iostream>
using namespace std;
void Swap(int &x,int& y)
{
int tmp=x;
x=y;
y=tmp;
}
int main()
{
int a=1;
int b=3;
cout<<"交換前"<<a<<","<<b<<endl;
Swap(a,b);
cout<<"交換後"<<a<<","<<b<<endl;
return 0;
}
運行結果
[zyc@localhost lession_quote]$ ./a.out
交換前1,3
交換後3,1
結論:可以實現交換功能
指針版本
#include<iostream>
using namespace std;
void Swap(int *x,int* y)
{
int tmp=*x;
*x=*y;
*y=tmp;
}
int main()
{
int a=1;
int b=3;
cout<<"交換前"<<a<<","<<b<<endl;
Swap(&a,&b);
cout<<"交換後"<<a<<","<<b<<endl;
return 0;
}
運行結果
[zyc@localhost lession_quote]$ ./a.out
交換前1,3
交換後3,1
結論:可以實現交換功能
綜合對比:
值傳遞:
失敗原因分析:使用一般變量傳遞函數的參數,當發生函數調用時,需要給形參分配存儲單元,形參變量是實參變量的副本,我們在Swap裏面的交換動作實際上是對副本的操作,所以不會對實參進行交換,就好像你建立了一個world文檔,然後又將其備份了,接下來的操作全是在備份裏更改,主文檔依然還是主文檔,並沒有改變。
指針傳遞
成功原因:將實參地址傳遞給形參,那麼在Swap函數中的操作機會按照地址尋址的方式找到實參,然後在進行交換,實質是對實參的更改。
引用傳遞
成功原因:傳遞引用給函數與傳遞指針的效果是一樣的。這時,被調函數的形參就成爲原來主調函數中的實參變量或對象的一個別名來使用,所以在被調函數中對形參變量的操作就是對其相應的目標對象(在主調函數中)的操作。
(三)引用作函數返回值
(1)以引用返回函數值,定義函數時需要在函數名前加&
(2)用引用返回一個函數值的最大好處是,在內存中不產生被返回值的副本。
以下面程序爲例來解析引用作函數返回值
#include <stdio.h>
int &Add(int x,int y) //引用返回
//int Add(int x,int y) //值返回
{
int ret=x+y;
return ret;
}
void Test()
{
int sum=Add(1,2);
}
int main()
{
Test();
return 0;
}
運行結果
[zyc@localhost lession_quote]$ ./a.out
4
分析過程:
查看棧幀
main函數棧幀(傳值返回和傳值返回相同)
Test函數棧幀(傳值返回和傳值返回相同)
Add函數棧幀(此時傳值返回和傳值返回不相同,注意區別)
- 傳值返回
- 傳引用返回
這裏注意,由於ret是在Add函數裏面定義的,當Add函數調用完畢,函數棧幀銷燬,ret也會被操作系統回收,放在eax裏面的地址就會變成野指針,容易造成程序bug。
比如上面程序就會爆出下面警告
add.cpp: In function ‘int& Add(int, int)’:
add.cpp:12: warning: reference to local variable ‘ret’ returned
(12:警告:引用本地變量' ret '返回)
程序更正:
將上面的局部變量ret變成全局變量,即可消除警告
總結:
1)不要反悔一個臨時變量的引用。就比如剛纔的局部變量ret。
因爲局部變量會隨着函數棧幀的銷燬而回收。存在eax裏面的地址就會變成野指針。
2)如果返回對象的生命週期不隨函數棧幀的銷燬而釋放,則建議使用傳引用返回。更高效。
(三)引用和指針的區別
1)參數使用情況
使用引用傳遞函數的參數,在內存中並沒有產生實參的副本,它是直接對實參操作;而使用一般變量傳遞函數的參數,當發生函數調用時,需要給形參分配存儲單元,形參變量是實參變量的副本;如果傳遞的是對象,還將調用拷貝構造函數。因此,當參數傳遞的數據較大時,用引用比用一般變量傳遞參數的效率和所佔空間都好。
使用指針作爲函數的參數雖然也能達到與使用引用的效果,但是,在被調函數中同樣要給形參分配存儲單元,且需要重複使用”*指針變量名”的形式進行運算,這很容易產生錯誤且程序的閱讀性較差;另一方面,在主調函數的調用點處,必須用變量的地址作爲實參。而引用更容易使用,更清晰.
2)其他方面
引用只能在定義時,初始化一次,之後不能使其指向其他變量,但是指針可以改變其指向,我們可以理解爲引用是從一而終的,而指針是 隨機應變的。
引用必須指向有效的 變量,而指針可以指向NULL
指針和引用自增和自減意義不一樣。
sizeof(指針對象)和sizeof(引用對象)的意義不同,sizeof(指針對象)求的是對象地址的大小,sizeof(引用對象)求的是對象佔的字節大小。
測試代碼
#include<iostream>
using namespace std;
int main()
{
int a=1;
int *p=&a;
int &b=a;
//測試自增區別
cout<<"++b="<<++b<<endl;
cout<<"p="<<p<<endl;
cout<<"++p="<<++p<<endl;
//測試sizeof區別
cout<<"sizeof(p)="<<sizeof(p)<<endl;
cout<<"sizeof(b)="<<sizeof(b)<<endl;
return 0;
}
運行結果
[zyc@localhost lession_quote]$ ./a.out
//測試自增
++b=2
p=0x7fffa36fa51c
++p=0x7fffd3fc9910
//測試sizeof
sizeof(p)=8
sizeof(b)=4
結論:
指針自增加1,定義對象地址一次加四個字節,引用自增加1,就是定義對象加一
sizeof(指針)如果是32位程序就是4字節,如果是64程序就是8字節。
32位程序是指程序的虛擬地址空間是4G(地址編號要到429467296,剛好四個字節可以裝下這些地址),64位程序同理。