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
r 和 a 是同一個變數的兩個名字——不是複本、不是指向,就是同一格記憶體。參考有三條規則:
- 宣告時必須綁定對象:
int &r;是編譯錯誤('r' declared as reference but not initialized)——別名總得說是誰的別名。 - 綁定之後不能換對象:這輩子就跟定了。
- 之後對別名做的任何事,都是對本尊做。
第二條規則常被誤會,值得看個實驗:
#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 之後把三個名字都印出來驗證(提示:別名的別名,還是本尊的別名)。