跳到內容

14|參考與函式

還沒完成只限 C++CPU 時間上限 1 秒

相關主題: C++ 基礎速查

只限 C++|CPU 時間上限 1 秒

有 n 對整數。對每一對,把比較小的放前面輸出。全部輸出完之後,再輸出三行統計:

  • small:每一對裡比較小的那個數(共 n 個)
  • large:每一對裡比較大的那個數(共 n 個)
  • all:全部 2n 個數

每一行統計都要有:個數、最小值、最大值、總和。

這一題的重點不是演算法,而是函式的參數要怎麼傳:什麼時候傳值(複製一份)、什麼時候傳參考(&)、什麼時候傳 const 參考(const &),以及它們和 C 的「傳指標」有什麼不同。

請一定要寫出下面的 struct 和 4 個函式(名稱、參數型別要一樣),並且在 main 裡用它們完成工作:

struct Stats {
long long sum; // 總和
int min; // 最小值
int max; // 最大值
int count; // 個數
};
void order(int& a, int& b); // 讓 a ≤ b:如果 a > b 就把兩個交換
void update(Stats& s, int x); // 把一個數字 x 加進統計 s
void add_all(Stats& s, const std::vector<int>& values); // 把 values 裡的每個數字都加進 s(呼叫 update)
void print_stats(const std::string& name, const Stats& s); // 輸出一行統計

Stats 的成員可以加上預設值(例如 long long sum = 0;),也可以在使用前自己設定初始值。

建議的 main 流程:讀一對 a b → 呼叫 order(a, b) → 輸出 → 把 a 存進 small、b 存進 large 這兩個 std::vector<int>;全部讀完之後,用 add_all 算出三組統計,再用 print_stats 輸出。

注意:評測只能比對輸出,沒辦法檢查你有沒有照規定寫這些函式,也分辨不出你用的是參考、指標還是全域變數。但這一題要練的就是這件事,請自己遵守規定;通過之後可以和參考解答比對看看。

為什麼 void order(int a, int b) 不行?

Section titled “為什麼 void order(int a, int b) 不行?”
void order(int a, int b) { // a、b 是「複製出來的副本」
if (a > b) std::swap(a, b); // 交換的是副本
} // 函式結束,副本消失;呼叫者的變數完全沒變
int x = 5, y = 2;
order(x, y); // x 還是 5,y 還是 2

傳值的時候,函式拿到的是一份副本,怎麼改都不會影響呼叫者。改成 void order(int& a, int& b) 之後,a、b 就是呼叫者那兩個變數的別名(參考),在函式裡交換 a、b,就是交換呼叫者的 x、y。

參數寫法函式拿到的是能改到呼叫者的變數嗎會複製整個資料嗎
int a(傳值)一份副本不能會(int 很小,沒關係)
int& a(參考)呼叫者變數的別名能不會
const std::vector<int>& v(const 參考)別名,但只能讀不能(寫了會編譯錯誤)不會
int* a(指標,C 的寫法)變數的位址能(要寫 *a)只複製位址
  • 第 1 行:一個整數 n(1 ≤ n ≤ 100000)。
  • 接下來 n 行:每行兩個整數 a、b,用一個空格分隔(-10^9 ≤ a, b ≤ 10^9)。a 可能大於、等於或小於 b。

一共 n + 3 行:

  1. 前 n 行:依照輸入的順序,每一對輸出一行 較小 較大(用一個空格分隔)。兩個數相等就照原樣輸出。
  2. 接著 3 行統計,順序是 small、large、all,格式是 名稱 個數 最小值 最大值 總和,用一個空格分隔。

總和可能超過 int 的範圍(最大可能到 2×10^14),要用 long long。

輸入:

4
5 2
-3 7
4 4
10 -10

輸出:

2 5
-3 7
4 4
-10 10
small 4 -10 4 -7
large 4 4 10 26
all 8 -10 10 19

整理後,比較小的數是 2、-3、4、-10(總和 -7),比較大的數是 5、7、4、10(總和 26),全部 8 個數的總和是 19。

C++:

  • 參考 int& r = x;:r 是 x 的別名,對 r 做的任何事都是對 x 做。參考建立時一定要綁定一個變數,之後不能改綁別的變數,也不會有「空的參考」
  • 函式參數寫成 int&:函式可以修改呼叫者的變數,用來「交換」或「一次傳回好幾個結果」(例如把結果寫進 Stats&)
  • 函式參數寫成 const T&:不複製、也不能修改。std::vector、std::string、struct 這類可能很大的資料,只需要讀的時候就這樣傳
  • int、double 這種小型別只需要讀的時候,直接傳值就好(例如 update 的 int x)
  • std::swap(a, b)(在 <utility>)交換兩個變數
  • struct 成員可以有預設值:long long sum = 0;,之後寫 Stats s; 就已經初始化好

對照 C:

  • C 沒有參考,要修改呼叫者的變數只能傳指標:void order(int *a, int *b),函式裡寫 *a、*b,呼叫時寫 order(&x, &y);struct 要寫 s->sum
  • C 傳 struct 會整個複製一份,所以大的 struct 通常傳 const struct Stats *
  • 參考的呼叫端看起來和傳值一樣(order(x, y)),光看呼叫看不出變數會不會被改。所以 C++ 的習慣是:只讀的參數一律加 const,這樣「沒有 const 的參考」就代表「這個函式會修改它」
提示 1:order 和 update 怎麼寫?

order:如果 a > b 就交換,可以用 std::swap(a, b),或用一個暫存變數自己交換。因為參數是參考,交換的就是呼叫者的變數。

update:用 s. 存取成員(參考不是指標,不用寫 ->)。總和加上 x、個數加 1,再看 x 有沒有比目前的最小值更小、比目前的最大值更大。

想一想:如果 update 寫成 void update(Stats s, int x)(傳值)會發生什麼事?

提示 2:Stats 一開始要設成什麼?

sum 和 count 從 0 開始。min、max 有兩種常見做法:

  • min 設成 int 能表示的最大值、max 設成最小值(std::numeric_limits<int>::max() 和 ::min(),在 <limits>),第一個數字進來時一定會更新
  • 或者在 update 裡判斷:count 是 0 的時候,直接把 min、max 都設成 x

sum 一定要用 long long:三個 10^9 加起來就超過 int 的上限 2147483647 了(Sanitizer 會抓到「有號整數溢位」)。

提示 3:add_all 和 print_stats 為什麼要用 const 參考?

small 這個 vector 最多有 100000 個數字。如果寫成 void add_all(Stats& s, std::vector<int> values)(傳值),每呼叫一次就要把 100000 個數字整個複製一份,用完再丟掉;寫成 const std::vector<int>& 就不會複製,而且 const 保證函式不會不小心改到它。

add_all 裡面用範圍 for 迴圈把每個數字交給 update(s, x)。all 那一組只要對同一個 Stats 變數呼叫兩次 add_all(一次給 small、一次給 large),就會把兩邊的數字累積在一起。

print_stats 只需要讀,所以 name 和 s 都用 const 參考。