在 C++ 中,有個特別好用的語法糖被稱作「參考(Reference)」,Reference 可以大大簡化不少複雜的實作問題,若熟悉使用的話,勢必能夠在競賽程式起到幫助。
一個最直接了當理解 Reference 的方式就是將其理解成一種「別名」,就好比下列這份程式碼:
for (int i = 0; i < n; ++i) {
cal(arr[pl[idx[i]]]);
if (check(arr[pl[idx[i]]]))
arr[pl[idx[i]]] = change(arr[pl[idx[i]]]);
} // 假設 cal, check, change 是三個已經寫好在別處的函式
天啊打那麼多中括號不會累嗎?不如幫變數取個綽號如何?若直接宣告一個變數 x
並賦值成 arr[pl[idx[i]]]
的話,在第四行的賦值操作又會免不了再打出整串東西來一次。
因此,C++ 的優質功能,Reference,就在這裡派上用場了,可以看看以下修改過的程式碼:
for (int i = 0; i < n; ++i) {
int &cur = arr[pl[idx[i]]];
cal(cur);
if (check(cur))
cur = change(cur);
} // 假設 cal, check, change 是三個已經寫好在別處的函式
先是宣告一次 int &cur = arr[pl[idx[i]]]
後,所有跟 cur
有關的語句將全部都會被 C++ 視為是在對 arr[pl[idx[i]]]
做操作!所以在同一個區域內,我們就不用再打這麼多中括號出來了。
沒錯,Reference 就是這麼單純,讀者可以完全將其當成一個別名系統在理解。而正因為他只能是別名,因此以下幾種寫法都會導致 CE:
int &x; // 不可以幫不存在的東西取別名
int &x = 100; // 不可以幫不是變數的東西取別名
int &x = a + b; // a + b 不是一個變數
還請讀者特別注意。
其實要講詳細一點的話,Reference 在做的事情就是讓使用者能夠用不同的方式呼叫變數「本身」。
什麼意思呢?這是因為 C++ 的每個變數被宣告出來就會有他所被儲存的「地址」,而有了這個地址,無論身在何處,都會有辦法直接讀取、甚至寫入這個變數。就好比我們想實作「交換兩個變數」的函式:
void swap(int a, int b) {
int t = a;
a = b;
b = t;
}
我們都知道上面這個函式什麼都沒做,因為數字傳進函式後,就變成獨立的值了,所以在函式裡面交換兩者的值的話,對呼叫方來說是沒有任何影響的。
但如果多加上 Reference:
void swap(int &a, int &b) {
int t = a;
a = b;
b = t;
}
這時候,變數 a
和變數 b
這兩個別名就能成功的被 C++ 定位到實際指到的變數,這透過的就是藏在語法背後、C++ 特別幫忙傳遞過來的「變數地址」。
因此,使用上述的這個寫法,被傳過來的 a
和 b
就真的能修改到呼叫方傳入的變數,達成交換的目的了。
同樣的,下列這行傳不是變數的東西進 swap
的寫法也是會 CE 的:
swap(a, 100);
Reference 一個衍伸出來的好功用,就是在傳遞變數給函式的時候,可以省去複製一遍的力氣。舉例來說:
void print_vector_position(vector<int> &v, int p) {
cout << v[p] << "\n";
}
假設 vector
裡裝著 $100$ 個元素,這段程式碼看似傳過來的時候就需要花費 $100$ 倍左右的力氣搬運這個 vector
變數……這當然是在沒有加 Reference 的情況下。
像上面這段程式加上了一個 &
在 v
前面後,傳過來的就是單純包含著地址的「別名」,而不是值本身,所以在程式實際運行的時候,C++ 所需要做的只有傳一個短短的「地址」過去而已,並不需要真的複製一遍 $100$ 個元素。
這樣的技巧可以讓程式寫起來更加的乾淨,才不會為了程式的效率,動不動就把變數宣告在全域讓函式使用,造成一堆不相干的變數混在一起,結果增加錯誤率。