天天看點

C++初級主題--(4)引用

一.引用的概念

C++函數中參數的傳遞方式是傳值。在函數域中為參數重新配置設定記憶體,而把實參的數值傳遞到新配置設定的記憶體中。它的優點是有效避免函數的副作用(即改變實參的值)。

如果要求改變實參的值,怎麼辦呢?如果實參是一個複雜的對象,重新配置設定記憶體會引起程式執行效率大大下降,怎麼辦呢?在C++中有一種新的導出型資料類型—引用(reference)可以解決上面的難題。引用又稱别名(alias)。

二.傳值和傳引用的差別

1.用交換程式舉例說明

(1)直接通過傳值的方式無法更改實參的值

#include<iostream>
using namespace std;

void swap1(int x, int y)
{
    int tmp = y;
    y = x;
    x = tmp;
}
void main()
{
    int a = ;
    int b = ;
    swap1(a,b);
}
           

a,b的值仍未交換,隻交換了x,y的值。

C++初級主題--(4)引用

(2)C語言中通過指針的方式可以交換

#include<iostream>
using namespace std;

void swap1(int *x, int *y)
{
    int tmp = *y;
    *y = *x;
    *x = tmp;
}
void main()
{
    int a = ;
    int b = ;
    swap1(&a,&b);
}
           

可以看到x,y與a,b的位址不同,額外為形參開辟了空間

C++初級主題--(4)引用

(3)C++中提供引用的方式

#include<iostream>
using namespace std;

void swap1(int &x, int &y)
{
    int tmp = y;
    y = x;
    x = tmp;
}
void main()
{
    int a = ;
    int b = ;
    swap1(a,b);
}
           

可以看到x,y的位址與a,b相同,是a,b的别名,沒有重新配置設定空間。

C++初級主題--(4)引用

2.總結

(1)傳引用可以直接更改實參的值

(2)傳引用不用額外配置設定空間儲存實參的數值。

(3)不能定義空引用,即引用的對象必須存在。

3.引用的本質仍是指針

關于這一點,請參考博文 c++ 引用 底層實作機制

  1. 引用是在編譯的過程中被處理的,實際上就是在編譯層面對程式員進行的一個比較友好的文法,而在實作上是由編譯器完成了位址的傳遞,實質上還是指針。
  2. 不能簡單的了解為一個别名,我們可以這樣用,但是要知道底層就是一個指針變量,是要占用記憶體空間的,和define是不一樣的。

三.引用的分類

1.變量的引用

int a = ;
int b = &a;
           
b是a的引用

2.指針的引用

int a = ;
int *p = &a;
int *&q = p;
           
q是p的引用

3.數組的引用

int ar[] = {};
int (&br)[] = ar;
           
br是ar的引用

4.常量的引用

(1)常量必須用常量引用

const int x = ;
const int &y = x;
           

(2)變量可以用常量引用

int x = ;
const int &y = x;
           

(3)不同類型間進行常引用

double x = ;
const int &y = x;
           
C++初級主題--(4)引用

可以看到y與x的位址不同,此時,不同類型之間進行指派運算,一定會産生臨時對象(假設為tmp),最終y是tmp的引用。同時,應注意:對于所有的臨時對象,必須同樣假設它們是不可存取的,即具有常量的性質。當改變這種資料時,編譯器會指出錯誤,這是非常有用的提示,因為這個改變會導緻資訊丢失。

5.函數中的引用,即函數傳回引用或函數參數中包含引用

(1)最經常看見引用的地方是在函數參數和傳回值中。當引用被用作函數參數時,在函數内任何對引用的更改将對函數外的參數産生改變。

(2)當然可以傳遞一個指針來做相同的事情,但引用具有更清晰的文法。(可以把引用看作一個使文法更加便利的工具。)

(3)如果函數傳回一個引用,必須像從函數傳回一個指針一樣對待。當函數傳回時,無論引用關聯的是什麼都應該存在,否則,将不知道指向哪一個記憶體

int* fun1(int *x)    //(2)
{
    (*x)++;
    return x;    //正确,x指向确定的記憶體
} 
int& fun2(int &x)    //(1)
{
    x++;
    return x;    //正确,x指向确定的記憶體
}
int& fun3()
{
    int q;
    //return q;    //錯誤,局部變量,最終指向不明确的記憶體
    static int p;
    return p;    //正确,盡管fun3運作結束,但是p被static修飾,為全局變量,生存作用域仍存在,指向明确的記憶體
}
int main()
{
    int a = ;
    fun1(&a);   //ugly(but explicit)
    fun2(a);    //clean(but hidden)  
}
           

四.參數傳遞準則

當給函數傳遞參數時,人們習慣上通過常量引用來傳遞。雖然看起來似乎僅是出于效率考慮(通常在設計與裝配程式時并不考慮效率),但是這樣會帶來很多危險。拷貝構造函數需要通過傳值方式傳遞對象,但并不總是可行的

這種簡單習慣可以大大提高效率:傳值方式會調用構造函數和析構函數,但是如果不想改變參數,則可以通過常量引用傳遞,它僅需要将位址壓棧。

事實上,隻有一種情況不适合用傳位址方式。就是當傳值是唯一安全的途徑,否則将會破壞對象時。是以需要依據上下文

繼續閱讀