1. 程式人生 > >C++中的新特性:引用

C++中的新特性:引用

先放一個例子

swap函式對比(作用,交換兩個數的數值)

void swap(int *a, int *b)//使用指標
{
    int temp = *a;
    *a = *b;
    *b = temp;
    return ;
}

void swap(int &a, int &b)//使用引用
{
    int temp = a;
    a = b;
    b = temp;
    return ;
}

什麼是引用

引用,就是一個變數的別名

再來一個例子

int a = 2;
int & ra = a;

這裡,我們定義ra,它是變數a的引用
下面會用上面定義的變數
放幾個引用的用法

引用玩法1

int a = 2;
int & ra = a;
ra = 3  //!!

如果使用了上面那一條語句會怎樣呢?
實際上
此時ra == 3 並且 a == 3
沒有動a啊,a為什麼會變啊

複習引用的定義
引用就是某一變數的別名

也就是說,變數ra 就是 變數a
我對ra變數執行的任何操作
等同於對a變數執行的任何操作
反之亦然

其實很好理解原理
變數a的地址和變數ra的地址是相同的
(可以自己在編譯器上試一試)

引用玩法2

還是一個例子

void function(int & r)
{
    r = 6
; } int main() { int a = 2; function (a); cout << a; }

將a作為形參傳入函式
最後會輸出多少呢?
????
實際上,會輸出6
也就是說,a的值被修改為6

不是隻有傳入指標才會修改它的值的嗎?

再來複習一下
引用也就是說是變數的別名
還有原理
引用變數和原變數地址是相同的

在函式宣告中

void function(int & r)

定義了一個引用r
也就是說,r變數將會是傳入實參的引用
因此
我們對r怎樣
也就是對實際傳入變數怎樣

再來看回開頭的例子

相信大家應該都懂了

關於概念

按值傳遞
(就是直接傳數)
按址傳遞
(就是傳入地址指標)
按引用傳遞
(就和前面講到的一樣)

為什麼用引用

1. 好打,好看

自行對比
什麼時候不小心打少了個*號的時候
什麼時候*號和++優先順序搞錯的時候
(*a++ 的理解!!)
……
就會知道引用的方便了。

void swap(int *a, int *b)//使用指標
{
    int temp = *a;
    *a = *b;
    *b = temp;
    return ;
}

void swap(int &a, int &b)//使用引用
{
    int temp = a;
    a = b;
    b = temp;
    return ;
}

2. 效率高

這裡會扯到一個是否生成臨時變數的問題

  1. 按值傳遞

按值傳遞的最大優點就是在函式中無論怎麼修改變數
都不會改變傳入變數的值

原因便在於按值傳遞中函式內的是臨時生成的變數
和傳入的變數完全沒有關係

  1. 生成臨時變數如何影響效率

也許生成一個int變數效率差別不大
但是如果是傳入一個有著好幾個陣列的結構體
或者是一個龐大的類物件
計算機宣告這些的臨時變數的時候
將會耗費不少計算資源與儲存空間
這個時候,把形參定義為引用便顯得尤為重要

引用可以同時具有引用優點和按值傳遞的優點

按值傳遞的最大優點就是不怕傳入的變數被修改
引用可以嗎??

我們可以這樣子定義來避免變數被改動
void function(const int & a);

通過把a定義為常值引用(加一個const)
我們可以在兼顧引用效率高優點的同時
避免不小心被修改
若是a的值被修改編譯器會馬上報錯方便修改

關於引用的幾點注意事項

1. 函式形參有引用

void function ( int & r)為例
我們可以這樣子呼叫嗎?

int a = 2;
function(a+2);
function(4);

前面提到
引用的原理便是記憶體地址相同
可是,這裡的(a+2)有地址嗎?
可是,這裡的4有地址嗎?

我們可以這樣用嗎?

事實上,這裡講的引用的全名是左值引用
左值:能夠放在賦值語句左邊的東東
(也就是可被賦值,有自己的記憶體地址)
能夠進行左值引用的一定前提是左值!!

a+2 ,4 明顯不能夠放在賦值語句左邊
(a+2 = 5? 4 = 2?)
因此此時編譯器會報錯

這裡有例外:const引用
如果是void function (const int & r)
那麼這個時候我
function(a+2)不會出錯
並且,會像按值傳遞那樣生成臨時變數
下面有一個總結

int x;
int f ( int & n);
f(x);
f(x+2);// error
f(10);//error , too.
int f2 (const int & n);
f2(x);
f2(x+2);// temporary variable
f2(10);// temporary variable

關於返回引用

// 讀取一個引用,返回一個引用
int & function (int & a)
{
    int b = 2;
    a = a + b;
    return a;
    //返回一個引用
}

這裡有幾個比較容易出錯的地方

1. 我可以返回b嗎?

再複習一遍原理
引用變數和原變數記憶體地址一樣!
如果這個程式改成

int & function (int & a)
{
    int b = 2;
    b = a + b;
    return b;
    //返回一個引用
}

將會報錯!!

b 是一個區域性變數
區域性變數在程式碼塊內可見
當退出程式碼塊時自動銷燬!
原記憶體地址的內容將會消失

假設我int一個變數c
int a = 2;
c = function(a)
變數c的地址將會與函式中臨時建立的區域性變數b相同
但是函式一旦結束
該地址所對應的記憶體塊將會自動釋放!!
此時變數c內的內容將不可訪問(或者是垃圾資料)

返回引用(是個左值)

// 讀取一個引用,返回一個引用
int & function (int & a)
{
    int b = 2;
    a = a + b;
    return a;
    //返回一個引用
}

對這個函式,我們有一個比較奇葩的用法
function (a) = 3;

原理其實很簡單
因為函式返回值是個左值
所以可以放在賦值語句的左邊

那這條語句到底做了什麼呢?
1. 函式function接受變數a,
2. 函式體內,返回變數a的引用
3. 對變數a的引用,賦值給3;

所以最終,就是a = 3;

如果怕自己搞錯
寫出這樣的程式碼
if ( (function(a) = 4 );
(是不會報錯的!)
可以把函式宣告改成這樣
const int & function(int & a);

還有一種奇葩用法

int a = 2;
const int & r = a;

這裡,a和r實際上是同一個變數
(引用的定義?變數別名)

可是,同一個變數,
我用a就可以賦值,就可以修改
我用r就不能夠賦值和修改
(因為用了const)

關於指標,引用,傳值的選擇

  1. 如果向函式中傳遞陣列,只能用指標
  2. 如果需要修改傳入的變數,用引用和指標(引用更好)
  3. 如果傳遞比較大塊的東東,推薦引用或指標(當然引用更好)
  4. 如果怕傳入的變數被修改,請用const限制
  5. 沒有如果了

總結

  1. 引用必須是左值
  2. 引用可以提高效率
  3. 不能反悔區域性變數的引用
  4. 避免引用被修改可以用const來限制
  5. 陣列不能引用!對於陣列只能使用指標
  6. debug的時候你會有更多的總結

一些比較高階的東西

  1. 對於類的引用,既可以引用該類
    也可以引用該基類的派生類
    (這是實現多型的基礎)
  2. c++11中還有右值引用