C++參照型別詳解

2020-07-16 10:04:25
在 C++ 中可以定義“參照”。定義方式如下:

型別名 &參照名 = 同型別的某變數名;

此種寫法就定義了一個某種型別的參照,並將其初始化為參照某個同型別的變數。“參照名”的命名規則和普通變數相同。例如:
int n;
int & r = n;
r 就是一個參照,也可以說 r 的型別是 int &。第二條語句使得 r 參照了變數 n,也可以說 r 成為了 n 的參照。

某個變數的參照和這個變數是一回事,相當於該變數的一個別名。

注意:定義參照時一定要將其初始化,否則編譯無法通過。通常會用某個變數去初始化參照,初始化後,它就一直參照該變數,不會再參照別的變數。

也可以用一個參照去初始化另一個參照,這樣兩個參照就參照同一個變數。不能用常數初始化參照,也不能用表示式初始化參照(除非該表示式的返回值是某個變數的參照)。

總之,參照只能參照變數。

型別為 T& 的參照和型別為 T 的變數是完全相容的,可以互相賦值。

參照的範例程式如下:
#include <iostream>
using namespace std;
int main()
{
    int n = 4;
    int & r = n;        //r參照了n,從此r和n是一回事
    r = 4;              //修改r就是修改n
    cout << r << endl;  //輸出4
    cout << n << endl;  //輸出4
    n = 5;              //修改n就是修改r
    cout << r << endl;  //輸出 5
    int & r2 = r;         //r2和r參照同一個變數,就是n
    cout << r2 << endl; //輸出 5
    return 0;
}

參照作為函數的返回值

函數的返回值可以是參照。例如下面的程式:
#include <iostream>
using namespace std;
int n = 4;
int & SetValue()
{
    return n;  //返回對n的參照
}
int main()
{
    SetValue() = 40;  //返回值是參照的函數呼叫表示式,可以作為左值使用
    cout << n << endl;  //輸出40
    int & r = SetValue();
    cout << r << endl;  //輸出40
    return 0;
}
SetValue 函數的返回值是一個參照,是 int & 型別的。因此第 6 行使得其返回值成為變數 n 的參照。

第 10 行,SetValue 函數返回對 n 的參照,因此對 SetValue 函數的返回值進行賦值,就是對 n 進行賦值,結果就是使得 n 的值變為 40。

第 12 行,表示式 SetValue 函數的返回值是 n 的參照,因此可以用來初始化 r,其結果就 是 r 也成為 n 的參照。

參照作為函數的返回值,其用途會在後面的“運算子過載”和“標準模板庫”章節中介紹。

引數傳值

在 C++ 中,函數引數的傳遞有兩種方式:傳值和傳參照。在函數的形參不是參照的情況下,引數傳遞方式是傳值的。傳參照的方式要求函數的形參是參照。

“傳值”是指,函數的形參是實參的一個拷貝,在函數執行的過程中,形參的改變不會影響實參。例如下面的程式:
#include <iostream>
using namespace std;
void Swap(int a, int b)
{
    int tmp;
    //以下三行將a、b值互換
    tmp = a;
    a = b;
    b = tmp;
    cout << "In Swap: a = " << a << " b = " << b << endl;
}
int main()
{
    int a = 4, b = 5;
    Swap(a, b);
    cout << "After swaping: a = " << a << " b = " << b << endl;
    return 0;
}
在上面的程式中,Swap 函數的返回值型別是 void,因此函數體內可以不寫 return 語句。 在不寫 return 語句的情況下,函數執行到末尾的}才返回。

上面程式的輸出結果是:
In Swap: a = 5 b = 4
After swaping: a = 4 b = 5

輸出結果說明,在 Swap 函數內部,形參 a、b 的值確實發生了互換,但是在 main 函數中, a、b 還是維持原來的值。也就是說,形參的改變不會影響實參。這是因為,形參和實參存放在不同的記憶體空間中。

一個程式在執行時,其所佔用的記憶體空間有一部分被稱作“棧”,當一個函數被呼叫時,在“棧”中就會分配出一塊新的儲存空間,用來存放形參和函數中定義的變數(也稱為區域性變數,如上面程式中的 tmp)。實參的值會被複製到棧中存放對應形參的地方,因此形參的值才等於實參。函數執行過程中對形參的修改,相當於只是修改了實參的一個拷貝,因此不會影響實參。

引數傳參照

如果函數的形參是參照,那麼引數的傳遞方式就是傳參照的。在傳參照方式下,形參是對應的實參的參照。也就是說,形參和對應的實參是一回事,形參的改變會影響實參。

有了參照的概念,交換兩個變數的 Swap 函數可以如下編寫:
#include<iostream>
using namespace std;
void Swap(int & a, int & b)
{ //交換a、b的值
    int tmp;
    tmp = a; a = b; b = tmp;
}
int main()
{
    int n1 = 100, n2 = 50;
    Swap(n1, n2);  //n1、n2 的值被交換
    cout << n1 << " " << n2 << endl;  //輸出 50 100
}
第 11 行,進入 Swap 函數後,a 參照了 n1,b 參照了 n2,a、b 值的改變會導致 n1、n2 值的改變。因此本行會使 n1 和 n2 的值交換。

常參照

定義參照時,可以在前面加 const 關鍵字,則該參照就成為“常參照”。如:
int n;
const int & r = n;
上面的語句定義了常參照 r,其型別是 const int &。

常參照和普通參照的區別在於:不能通過常參照去修改其參照的內容。注意,不是常參照所參照的內容不能被修改,只是不能通過常參照去修改而已,但可以用別的辦法修改。例如下面的程式片段:
int n = 100;
const int & r = n;
r = 200;  //編譯出錯,不能通過常參照修改其參照的內容
n = 300;  //沒問題,n的值變為300
注意,const T& 和 T& 是不同的型別。T& 型別的參照或 T 型別的變數可以用來初始化 const T & 型別的參照,const T 型別的常變數和 const T & 型別的參照則不能用來初始化 T & 型別的參照,除非進行強制型別轉換。例如下面的程式:
void Func(char & r) { }
void Func2(const char & r) { }
int main()
{
    const char cc = 'a';
    char c;
    const char & rcl = cc;
    const char & rc2 = c;  //char變數可以用來初始化 const char & 的參照
    char & r = cc;  //編譯出錯,const char 型別的常變數不能用來初始化 char & 型別的參照
    char & r2 = (char &)cc;  //沒問題,強制型別轉換
    Func(rcl);  //編譯出錯,引數型別不匹配
    Func2(rcl);  //沒問題,引數型別匹配
    return 0;
}