Chapter II. 新手上路
實作知識
常用變數宣告方法
作者WiwiHo、baluteshih
先備知識全域、區域變數

本文將介紹一些在競賽程式中常使用的變數宣告方法。

auto


在 C++11 之後,C++ 提供了一個懶人的變數宣告方式,也就是 auto。簡單來說,就是可以在宣告時直接把變數型態寫成 auto,並交給編譯器自己決定該變數的型態為何。

舉例來說:

int x = 100;
auto y = x;

以上寫法會宣告一個變數型態為 int 的變數 y,其值和 x 一樣是 100。當然,其實我們可以直接把上面的程式碼片段寫成

auto y = 100;

這樣的話,宣告出來的一樣會是變數型態為 int 的變數 y,其值是 100。不過這裡就衍伸出了一個問題,既然我們說 auto 是交給 C++ 決定變數型態,第一個寫法確實很明顯就是 int 沒錯,但第二種寫法為什麼也是 int 呢?答案其實沒有什麼特殊原因,單純是因為整數在 C++ 的預設型態是 int 而已。關於預設變數這點,以下有更詳細的例子:

auto x = 100; // int
auto y = 1.0; // double
auto longx = 100ll; // long long int 

上面的程式碼片段中,後方的註解都註明了他們會獲得的變數型態。

不過讀者可能會想,這些變數型態也沒有特別難打,這樣會有什麼用?讀者在現階段可能還不會感受到他的便利性,但在未來其實會有許多很長的變數型態、甚至是無法正常宣告的變數型態出現!這讓 auto 變得非常方便,往往能省下不少實作時間,以下幾個例子讓讀者來感受一下他的便利性:

map<int, int> mp;
for (auto it = mp.begin(); it != mp.end(); ++it);

上面的 it 變數型態是 map<int, int>::iterator

auto num_list = {1, 2, 3, 4, 5};

上面的 num_list 變數型態是 initializer_list<int>

int f(int a, int b) {
    return a + b;
}
auto ff = f;

上面的 ff 變數型態是 int (*)(int, int),不使用 auto 的話得寫成 int (*ff)(int, int) = f

auto cmp = [](int a, int b) {
    return a < b;
};

上面的 cmp 變數型態不精確地說是 lambda(int, int),而且你沒辦法不使用 auto 宣告!

當然,讀者不知道上面的這些東西是什麼也沒關係,未來會慢慢學到的,現在只需要對 auto 關鍵字有一個印象在就行,未來介紹這些東西時都會再提到。

static


有時候我們會希望在一個函式之中,某個變數最後的狀態可以保留到下一次執行這個函式時使用,也就是下一次函式執行時變數不會被重置,這個時候一種容易想到的解決方法就是直接把這個變數開成全域變數。舉例來說,你想要寫一個函式,每次執行時都會輸出這個函式是第幾次被執行,可能就會這樣寫:

int cnt = 0;
void f() {
    cnt++;
    cout << "f: " << cnt << "\n";
}

這麼做的缺點是,cnt 只被 f() 使用到,但它仍然是一個全域變數,會影響到整份程式碼,例如其他函式可能會不小心改到它,或是其他全域變數不能也叫 cnt。這個時候,就是 static 這個關鍵字出場的時候了!我們把 cnt 的宣告移到 f() 裡面,並且改成 static int cnt = 0,這樣一來 cnt 的作用區域就只在 f() 裡面,而且它的值會一直保留。

void f() {
    static int cnt = 0;
    cnt++;
    cout << "f: " << cnt << "\n";
}

void g() {
    static int cnt = 0;
    cnt++;
    cout << "g: " << cnt << "\n";
}

int main() {
    f(); // f: 1
    f(); // f: 2
    g(); // g: 1
    f(); // f: 3
    g(); // g: 2
}

f()g() 裡面各有一個宣告成 staticcnt 變數,它們兩個之間互不干擾,修改過後的數值也可以一直保留到函式下一次執行,完美達到了我們的需求。

const


很多時候我們會用到一個固定的數字,例如有些題目會要求把答案 mod $10^9+7$ 或 $998244353$ 之類的數字後輸出,這個時候就很適合把這些固定的數字宣告成變數,免得每次用到都得完整重打數字,還有可能會打錯。除了宣告成變數之外,也可以在型態前面加上 const,例如 const int MOD = 1000000007,全域或區域變數都可以用。const 的意思是這個變數宣告之後就不能改變了,所以如果你宣告之後寫了 MOD = 123123 之類的,就會直接獲得一個 compile error。這麼做除了可以避免不小心修改到以外,還有一個好處是能告訴編譯器這個變數永遠不會改變,編譯器就可以根據這點偷偷進行一些優化,讓程式碼執行得更快。

舉例來說:

#include <bits/stdc++.h>

using namespace std;

const int MOD = 1000000007;
int main() {
    int n;
    cin >> n;
    int total = 0;
    for (int i = 0; i < n; i++) {
        total = (total + i) % MOD;
    }
    cout << total << "\n";
}

把第 5 行的 const 拔掉的話,就會慢一些些,讀者可以自己試試看。

const 還有另一個好用的用法,是加在函式的參數型態前面,例如:

#include <bits/stdc++.h>

using namespace std;

void f(const vector<int> &a) {
    a[0] = 2; // compile error!   
}
int main() {
    vector<int> a = {1, 2, 3, 4, 5};
    f(a);
}

不管是不是 reference 都可以加上 const,這個參數傳入函式後就不能被修改,可以用來防止不小心改到參數。就像這個例子,這通常會和 reference 一起使用,在希望用 reference 避免參數傳入函式時還要複製一份浪費時間,但又不希望改到它,就可以這麼做。這樣還有一個好處是,如果參數宣告成 vector<int> &a 而沒有 const,那傳入的參數 a 一定得是一個可以被 reference 的東西(正式術語是一個 lvalue),像是如果直接寫 f({1, 2, 3, 4, 5}) 的話是不行的,但有加 const 的話就可以直接這樣做。