參考


參考(Reference)是物件的別名(Alias),也就是替代名稱,對參考名稱存取時該有什麼行為,都參考了來源物件該有的行為,在 C++ 中,「物件」這個名詞,不單只是指類別的實例,而是指記憶體中的一塊資料。

要定義參考,是在型態關鍵字後加上 & 運算子,例如:

int n = 10;      // 定義變數
int *p = &n;     // 定義指標,儲存 n 的位址
int &r = n;      // 定義參考,是 n 的別名

上面的程式中,最後一行定義參考,參考一定要初始,例如以下定義無法通過編譯:

int &r; // error: 'r' declared as reference but not initialized

參考必須要有物件可以參考,因此一定要初始,初始後就是被參考物件的別名,對參考的任何存取,都是對物件的操作。例如:

#include <iostream>
using namespace std;

int main() {
    int n = 10;
    int &r = n;

    cout << "n:" << n << endl
         << "r:" << r << endl;

    r = 20;

    cout << "n:" << n << endl
         << "r:" << r << endl;

    return 0;
}

在上面的程式中,r 就是 nn 就是 r,它們是同一物件的別名,也就是同一塊記憶體且有兩個名稱,改變 nr,都是改變該記憶體空間的資訊,執行結果如下:

n:10
r:10
n:20
r:20

參考的物件也可以是個指標,只要指定對應的型態,例如參考至 int* 型態的變數:

int n = 10;
int *p = &n;
int *&r = p; // 也就是 int* &r = p;

陣列呢?必須指定長度,例如參考長度為 1 的一維陣列:

int arr[] = {1, 2};
int (&r)[2] = arr;

參考的應用之一,是作為函式呼叫時傳遞引數的一種方式,這之後文件再來討論;回頭來看看〈二維(多維)陣列〉中 for range 的範例:

#include <iostream> 
using namespace std; 

int main() { 
    int maze[2][3] = {
                         {1, 2, 3},
                         {4, 5, 6}
                     };

    for(auto row : maze) {
        for(int i = 0; i < 3; i++) {
            cout << row[i] << "\t"; 
        }
        cout << endl; 
    } 

    return 0; 
}

當時談到,row 的型態實際上是 int*row 儲存的只是位址,透過它只能依 int* 型態進行位址位移,然而它不帶有陣列的其他資訊,也就無法以 begin(row)end(row) 來操作,因此範例中內層 for 只能用索引來取得元素。

上面這個範例,展開來就像是以下:

#include <iostream> 
using namespace std; 

int main() { 
    int maze[2][3] = {
                         {1, 2, 3},
                         {4, 5, 6}
                     };

    for(int (*it)[3] = begin(maze); it < end(maze); it++) {
        int *row = *it;
        for(int i = 0; i < 3; i++) {
            cout << row[i] << "\t"; 
        }
        cout << endl; 
    } 

    return 0; 
}

也就是說,實際上 it 是陣列變數的位址,也就是 it 遞增過程會是 &maze[0]&maze[0]*it 的話,就會是 maze[0]maze[1],也就是一維陣列位址,若能直接參考至 *it,就可以在內層迴圈也使用 begin(row)end(row)

#include <iostream> 
using namespace std; 

int main() { 
    int maze[2][3] = {
                         {1, 2, 3},
                         {4, 5, 6}
                     };

    for(int (*it)[3] = begin(maze); it < end(maze); it++) {
        int (&row)[3] = *it;   // 使用參考
        for(auto offset = begin(row); offset < end(row); offset++) {
            int n = *offset;
            cout << n << "\t"; 
        }
        cout << endl; 
    } 

    return 0; 
}

不過這程式碼難以閱讀,改為 for range 並搭配 auto 就好多了:

#include <iostream> 
using namespace std; 

int main() { 
    int maze[2][3] = {
                         {1, 2, 3},
                         {4, 5, 6}
                     };

    for(auto &row : maze) {  // 使用參考
        for(auto n : row) {
            cout << n << "\t"; 
        }
        cout << endl; 
    } 

    return 0; 
}