12.3 參考(reference)

上冊 7.5 學過 pass by reference:參數寫成 int &x,函式改 x 就是改外面的原變數。當時 & 只在參數列表出現過——其實它是一個獨立的語法,叫參考(reference),一般的變數宣告也能用。

參考=幫變數取別名

在型態後面加 &,宣告的就不是新變數,而是現有變數的別名

#include <iostream>
using namespace std;

int main() {
    int a = 10;
    int &r = a;               // r 是 a 的別名

    cout << r << '\n';        // 讀 r 就是讀 a
    r = 25;                   // 改 r 就是改 a
    cout << a << '\n';
    a++;                      // 反過來也一樣
    cout << r << '\n';
    return 0;
}

執行結果:

10
25
26

ra同一個變數的兩個名字——不是複本、不是指向,就是同一格記憶體。參考有三條規則:

  1. 宣告時必須綁定對象int &r; 是編譯錯誤('r' declared as reference but not initialized)——別名總得說是誰的別名。
  2. 綁定之後不能換對象:這輩子就跟定了。
  3. 之後對別名做的任何事,都是對本尊做。

第二條規則常被誤會,值得看個實驗:

#include <iostream>
using namespace std;

int main() {
    int a = 10, b = 99;
    int &r = a;
    r = b;                    // 不是「改綁到 b」!是把 b 的值指派給 a
    cout << a << '\n';
    b = 123;
    cout << r << '\n';        // r 還是 a 的別名,跟 b 無關
    return 0;
}

執行結果:

99
99

r = b; 只是普通的指派——透過別名把 b塞給 a。想「換一個對象重新綁」?參考做不到,這是它跟指標最大的差別(見本節末的對照表)。

幫又臭又長的存取取個綽號

別名最有感的場合,是索引裡面還有索引的時候。看排序單元 TOI 入門考排名 的正解:成績排好之後,名次要填回這個人原本的編號那一格,於是每一行都得把 ans[student[i][2]] 整串打出來:

for (int i = 0; i < n; i++) {
    if (!i || student[i][0] != student[i - 1][0] || student[i][1] != student[i - 1][1]) {
        ans[student[i][2]] = i + 1;                    // 不平手:前面剛好 i 個人比他好
    } else {
        ans[student[i][2]] = ans[student[i - 1][2]];   // 平手:抄前一個人的名次
    }
}

先幫它取兩個別名,同一段就變成:

for (int i = 0; i < n; i++) {
    array<int, 3> &me = student[i];                    // 這次要排名的人
    int &my_rank = ans[me[2]];                         // 他的名次要填進哪一格
    if (!i || me[0] != student[i - 1][0] || me[1] != student[i - 1][1]) {
        my_rank = i + 1;
    } else {
        my_rank = ans[student[i - 1][2]];
    }
}

兩份程式碼做的事完全一樣(本站拿官方測資實測,15 筆的輸出逐位元組相同)。差別只在讀的人:my_rank = i + 1; 一眼就知道「把名次寫下去」,而 ans[student[i][2]] = i + 1; 得先在腦中把三層中括號拆開。

回頭看 7.5:參數的 & 就是它

現在可以把 7.5 的機制說穿了:void addByReference(int &x) 的意思是——呼叫 addByReference(a) 時,x 成為 a 的別名。函式裡的 x++ 當然改得到外面的 a,因為它們根本是同一個變數。pass by reference 沒有魔法,就是「參數是別名」而已。

跟 11.2 的指標比一比

參考和指標都能「間接操作一個變數」,但個性完全不同:

指標(11.2 參考(本節)
存的是什麼 記憶體位置(門牌號碼) 不另外存——就是本尊的別名
使用語法 要用 *p 取值 直接用,跟本尊一模一樣
能不能換對象 能(p = &b; 改指向 b) 不能,綁定終身
能不能「不指向任何東西」 不能,宣告就得綁定
競程用途 看懂 sort(a, a + n)、更懂陣列 函式參數、range-based for(下一節)

一句話總結:參考語法乾淨但死心塌地,指標靈活但麻煩。競程場合幾乎都是參考出場——指標維持 11.2 的定位:看得懂就好。想更深入,可讀 NTUCPC Guide 的 Reference 章節

動手試試看:宣告 int a = 5; 與別名 int &r = a;,再宣告 int &r2 = r;——猜猜 r2 是誰的別名?改 r2 之後把三個名字都印出來驗證(提示:別名的別名,還是本尊的別名)。