Chapter II. 新手上路
實作技巧
Reference
作者baluteshih

簡介


在 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++ 特別幫忙傳遞過來的「變數地址」。

因此,使用上述的這個寫法,被傳過來的 ab 就真的能修改到呼叫方傳入的變數,達成交換的目的了。

同樣的,下列這行傳不是變數的東西進 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$ 個元素。

這樣的技巧可以讓程式寫起來更加的乾淨,才不會為了程式的效率,動不動就把變數宣告在全域讓函式使用,造成一堆不相干的變數混在一起,結果增加錯誤率。