C++中的“引用”詳解

前言:在面向對象的程序設計語言中,我們經常聽見一些名詞,引用,地址,在函數傳遞參數的時候,我們又經常說值傳遞,引用傳遞,最容易讓人搞混淆的就是“引用”和“地址”這兩個概念了,對於C++和C#來說,引用一詞從他們所呈現的表象來看的確很類似,但是本質實際上是不一樣的,

C++:引用就是一個變量的別名;

C#:引用可以用指針去理解,雖然C#沒有指針,我們經常說某個變量所引用的數據,可以理解爲某個變量所指向的數據。

一、先從C語言的交換兩個變量說起

C語言中:函數傳參有傳值和傳址兩種方式
用swap函數舉例:


1.1 傳值方式(創建了臨時變量存放實參的值)
缺點:不能通過函數形參改變外部實參
優點:不能改變外部的實參

void swap(int left,int right)//此代碼不能完成兩數的交換
{
    int tmp = left;
    left = right;
    right = tmp;
} 

2.傳址方式(創建了臨時變量存放了實參的地址)


缺點:每次訪問實參都要解引用
優點:可以改變外部實參

void swap(int* left,int* right)
{
    int tmp = *left;
    *left = *right;
    *right = tmp;
} 

而C++中就引入了引用的概念,下面詳細介紹一下C++中的引用
上面的代碼就可以寫成這樣
此時代碼中的left和right就是實參的別名,通過交換left和right就能將傳過來的實參進行交換。

void swap(int& left,int& right)
{
    int tmp = left;
    left = right;
    right = tmp;
}

總結:C++通過引用就可以達到C語言的指針作爲參數的效果。

二、引用的簡單概念


引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會爲引用變量開闢內存空間,它和它引用的變量共用同一塊內存空間。

定義引用類型的格式:


類型 & 引用變量名(對象名) = 引用實體;

注意這裏的空格是可選的,即

  • &符號與前後均可以有一個空格;如下:int & ra=a;
  • &符號與類型挨着,如下:int& ra=a;
  • &符號與引用名稱挨着,如下:int &ra=a;

到底怎麼理解引用只是一個變量的別名呢?其實在生活中別名很好理解,比如 張三 有一個別名叫做  三兒 。那麼這兩個名稱指的實際上就是同一個人,張三幹嘛,三兒就在幹嘛,兩者是一起的,爲了說明,我們看一個例子:

int main()
{
	int  a = 100;
	int & ar = a;
	cout << a << endl;
	cout << ar << endl;
	cout << &a << endl;
	cout << &a << endl;

	ar = 200;  //改變引用變量
	cout << a << endl;
	cout << ar << endl;
	cout << &a << endl;
	cout << &a << endl;

	getchar();
	return 0;
}
/*
100
100
012FF77C
012FF77C
200
200
012FF77C
012FF77C
*/

2.1  總結引用的特點

  1. 引用變量的類型必須與它的實體類型一致(因爲取別名要符合引用實體的身份,如果類型不一致則會報錯)
  2. 引用變量使用必須要進行初始化(不然沒有實體都不知道給誰取別名)
  3. 一個變量可以有多個引用(就相當於一個變量有好幾個別名,這是可以的)
int a = 10;
int& ra = a;
int& rra = ra;  ra,raa都是a的別名
  1. 引用一旦引用一個實體,再不能引用其他實體(同一個別名不能引用不同的人,否則就分不清誰是誰了)
  2. 引用不是指針,他就是一個變量,僅僅是一個別名;

總而言之:

引用本身也是一個變量,但是這個變量又僅僅是另外一個變量一個別名,它不佔用內存空間,它不是指針哦!不要混淆了,僅僅是一個別名,別名,別名,重要的事情說三遍

 

三、引用的更多使用

3.1 常引用

變量可以使變量和常量,別名本質上也是變量,也可以是變量或者是常量,所以對應起來有四種情況,分別如下:

(1)變引用——變量

int a = 10;   //可讀可寫
int& ra = a;  //可讀可寫

(2)常引用——變量

int a = 10;         //可讀可寫
int const& ra = a;  //僅僅可讀,不可寫

ra=20;  //編譯不通過,ra是常量

(3)變引用——常量

int const  a = 100;  //常量
int  & ar = a;       //變量,編譯沒辦法通過,因爲本尊都是常量,別名自然不能是變量

(4)常引用——常量

int const  a = 100;  //常量
int const & ar = a;  //常量,自身和別名都是常量,沒有問題

3.2 指針引用

引用既然就是一個變量,那我同樣也可以給指針變量去一個別名啊,參見下面的

int main()
{
	int a = 100;
	int *p = &a;
	int * &rp = p;

	cout << a << endl;
	cout << *p << endl;
	cout << *rp << endl; //這裏爲什麼要將*放在前面,因爲p的類型是 int * 作爲一個整體哦!!

	cout << p << endl;
	cout << rp << endl;

	getchar();
	return 0;
}
/*
100
100
100
012FF84C
012FF84C
*/

我們發現這裏的指針變量p和它的引用(別名)rp是完全一樣的。但是由於引用的目的跟指針的目的是類似的,所以一般不需要對指針再起別名了。(參見兩數交換的函數)

總而言之一句話:

引用變量就是別名、別名、別名。

四、引用與函數的結合使用

4.1 引用變量作爲函數參數

把傳值和傳址的優點結合起來了,寫起來會比較方便,同時對形參修改了會體現到我們的外部實參上(因爲形參就是實參的別名),同時傳引用的效率比傳值的效率高,傳引用寫起來也比傳址方便。

void swap(int& left,int& right)
{
    int tmp = left;
    left = right;
    right = tmp;
}
//函數調用,由於引用僅僅是一個別名,對於形參的操作會影響到實參
a=10;,需要特別注意
b=20;
swap(a,b);

由於引用變量作爲函數參數,對形參修改了會體現到我們的外部實參上(因爲形參就是實參的別名),這需要特別注意,但是如果我不希望改變外面的實參呢?
傳了引用之後,在函數內部進行操作就會把實參修改怎麼辦?

此時就採用const引用

void testfun(const int& a)
{
    //a = 10;
    //此時a就不能改,因爲a是一個常量的引用,不允許修改
}

總結:所以我們在構建函數的時候,還是要根據實際的需求,來決定到底是傳值、傳指針、還是傳引用。不能一概而論。
 

4.2 引用變量作爲函數的返回值

(1)不要返回局部變量的引用——一個嚴重的問題

先看一個代碼:

using namespace std;

int& test1()
{
	int n = 5;
	return n;
}

int main()
{
	int i = test1();
	cout << i << endl;

	getchar();
	return 0;
}

注意:

不同的編譯器對於返回局部變量的引用有所區別對待:

  • 對於gcc和g++, 編譯報警告,運行的時候會出現錯誤
  • 對於msvc:編譯報警告,warning C4172: 返回局部變量或臨時變量的地址: n,但是運行的時候不會出現錯誤,而是像正常的運行一樣,比如上面的代碼結果爲5.

爲什麼不要返回局部變量的引用呢?

因爲當該函數調用結束之後,該函數內部創建的局部變量出了作用域會被銷燬,爲這個函數開闢的棧幀也會被系統回收,在調用下一個函數之前會對這一部分棧空間裏的垃圾數據進行清理,因此你也會失去對這個空間的管控能力。函數調用結束之後,所有的局部變量都銷燬了,哪裏來的別名這一說法。

(2)返回全局變量的引用

int c; //定義全局變量
int & add(int a, int b)
{
	c = a + b;
	return c;  //這裏的返回值就是一個int類型的變量,並不是一個引用類型啊,這是不是和int &不兼容?
}

不兼容問題並不會存在,由於引用變量並不會佔用內存,它實際上就是c,所以返回引用變量就是c。

當然我想下面這樣寫,更規範,也是沒問題的

int c; //定義全局變量
int &rc=c;  //先給c把別名起好
int & add(int a, int b)
{
	c = a + b;
	return rc;  //rc 就是c
}

怎麼調用呢?

如下就像普通函數調用即可:

int main()
{
	int a = 100;  
	int b = 200;
	int result = add(a, b);  //就像普通函數調用即可
	cout << result << endl;

	getchar();
	return 0;
}

當然也可以這樣做,這樣看起來更加規範一些:

int main()
{
    int a = 100;  
    int b = 200;
    int result;
    int &rresult=result  //先給返回值起一個別名
    rresultadd(a, b);  
    cout << rresult << endl;

    getchar();
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章