1. 程式人生 > >C++:指標和引用

C++:指標和引用

引用的概念及用法 
所謂的引用並不是說重新定義的一個新的變數,而是給一個已經定義好了的變數起的一個別名。 
下面看看引用到底是如何使用的:

void test1()
{
    int a = 1;
    int& b = a; //引用變數b是a的別名

    std::cout<<"a:address->"<<&a<<std::endl;
    std::cout<<"A:address->"<<&b<<std::endl;  //注意這裡的&是取地址

    a = 10;
    b = 100;
    std::cout<<"a = "<<a<<std::endl;
    std::cout<<"b = "<<b<<std::endl;

    int& c = b; //應用變數c是引用變數b的別名,別名的別名
    c = 1000;
    std::cout<<"a = "<<a<<std::endl;
    std::cout<<"b = "<<b<<std::endl;
    std::cout<<"c = "<<c<<std::endl;
}

執行結果如下: 


 
由結果我們可以看出引用變m量b與變數a的地址是一樣的,該變b的值也會影響a。並且一個變數可以取多個別名,這裡b是a的別名,c是b的別名,也就是a的別名了。就像我們人一樣,你有一個大名(身份證上的名字),可能還會有一個小名,也或許還會給自己起一個洋氣的英文名,總之不管別人叫哪一個名字,叫的都是你本人就對了。

總結: 
1、一個變數可以有多個別名 
2、引用必須初始化b 
3、引用只能在初始化的時候引用一次,之後不能再引用其他的變數

引用做引數 
在之前的學習當中,我們知道呼叫函式的傳參有傳值呼叫和傳址呼叫。下面再來看一看這兩種方式:

1、傳值呼叫

void swap(int a,in b)
{
    int tmp = a;
    a = b;
    b = tmp;
}
//現在的我們都知道了這樣的函式是無法完成我們希望的交換功能的。
//究其原因,就是因為這裡使用的是傳值方式,那麼如果別人一旦想要
//呼叫我給我傳兩個引數,我就要生成兩個區域性的臨時變數用來接收
//別人給我傳的兩個引數。但是當呼叫結束,函式棧幀釋放,相應的
//用於接收引數的兩個區域性變數也會被一同釋放掉,但是交換是發生在
//這兩個區域性變數之間的,現在他們已經被釋放掉了,並且從頭到尾
//都沒有對呼叫方的兩個想要交換的變數產生任何影響。因此這裡的
//傳值呼叫並完成不了交換的功能。

2、傳址呼叫

void swap(int *a,int *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
//所謂的傳址呼叫就是傳指標。
//現在我們將兩個形參改為指標,也就是說別人想要呼叫我完成交換功能時
//就將想要交換的兩個變數的地址傳給我就好了。函式呼叫期間對地址裡面
//的內容進行交換,即便呼叫結束以後,函式棧幀被釋放,但是是對兩個地址
//的內容進行了交換,釋放棧幀以後這兩個地址並不是函式棧幀裡的,
//所以並不會一併被釋放,並且完成了交換的功能。

3、傳引用

void swap(int& a,int& b)
{
    int tmp = a;
    a = b;
    b = tmp;
}
//我們知道引用變數就是我們給一個已經定義好的變數起的一個別名,
//所以說,如果我們這裡採用傳引用的方式,那我們這裡的形參就是實參的別名
//在剛剛我們也看了,變數和變數的別名,他們兩個的地址是同一個,所以啊,
//這和傳址呼叫有著異曲同工之妙。

引用做返回值

有時候我們一個函式呼叫結束需要返回一些資訊供呼叫方使用。比如說一個加法函式。

//方法一
int ADD(int a,int b)
{
    int ret = a+b;
    return ret;
}
//方法二
int& ADD(int a,int b)
{
    int ret = a+b;
    return ret;
}
//這裡方法一是採用值的形式返回,而方法2是採用引用的形式返回。
//我們可以看看組合語言是如何這兩種不同返回方式的,如下圖:

 


 
那麼問題來了,我們有該如何選擇以那種方式返回呢???

1、如果返回的物件出了該函式作用域依舊存在,則使用引用返回,因為這樣會更加高效 
2、如果返回物件處了函式的作用域就不存在了,則使用值返回。 
注意:不要返回一個臨時變數的引用,因為臨時變數在函式呼叫結束以後會隨著棧幀的釋放而被釋放,而傳引用返回的方式返回的是變數的地址,而事實是該變數已經被釋放。

引用和指標的區別 
在這之前我們一直在說,引用是一個變數的別名,所以可能就會想到說這個引用變數時不會佔據任何的空間的。但是!請注意!這種想法是不對的。引用變數也是會佔據一定的記憶體空間的,也需要在棧上額外佔用儲存空間。 
因為引用的底層實現其實是指標。從語法上來看只是一個別名,但在底層上依舊是開闢了一塊空間。

int main()
{
    int a = 0;
    int& b = a;
    return 0;
}

看一下這段程式碼的彙編:是如何處理變數a,和引用變數b 


 
從彙編我們可以看出對引用變數初始化為a的別名,就是將a的地址給了引用變數b。想一想這種方式是不是很熟悉?對了,正如你所想到的我們經常寫的一個程式碼:

int a = 0;
int *p = &a;
//取a的地址賦給指標變數p

這樣看來其實引用的底層也就是一個指標,只不過明面上向我們所展示的是一個變數的別名,但我們應該注意引用變數是一個已經定義過的變數的別名,他是別名,他也佔空間,因為他的底層實現是指標。

下面就看一看引用和指標的區別:

1、引用只能在定義時初始化一次,之後不能改變指向其他的變數,但指標可以。 
2、引用必須指向有效的變數,但指標可以為空。 
3、sizeof引用得到的是所指向的變數的大小,但sizeof指標是物件的地址的大小。 
4、引用的自增(+ +)自減(- -)是對值的+1或-1,而指標++或–是+或-其所指向的型