【C++】競程筆記(實作技巧:Range-based for loop、Structured binding) === [TOC] 程式碼範例參考:NTUCPC Guide,此筆記僅為個人學習用途。 Range-based for loop --- 這個可以提供更簡潔的遍歷寫法,如下例子: ```cpp= vector <int> v = {1,2,3,4,5,6,7,8,9}; for (int i : v){ cout << i << endl; } ``` 若要對 i 進行修改,需要加上 &,如:`int& i`。 ### 字串陷阱 --- ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ for (char c : "abc"){ cout << c << "*\n"; } return 0; } ``` 輸出: ``` a* b* c* * ``` C / C++ 儲存字串時,是使用字元陣列的形式,他會在最後一個地方加上 `\0`,表示結束的意思。 所以可看到最後會有一個空格。 要解決此問題僅需要在 "abc" 後面加上一個 s 變成 `"abc"s`,使這串文字轉換成 C++ 字串型態。 ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ for (char c : "abc"s){ cout << c << "*\n"; } return 0; } ``` 輸出: ``` a* b* c* ``` Structured binding --- 以下是 pair 容器的範例: ```cpp= #include <iostream> #include <utility> #include <vector> #include <string> using namespace std; int main() { pair<string, int> student1("王奕翔", 0); pair<string, int> student2 = make_pair("LukeTseng", 100); vector<pair<string, int>> studentList; studentList.push_back(student1); studentList.push_back(student2); studentList.push_back(make_pair("Bob", 78)); for (const auto& student : studentList) { cout << "學生姓名:" << student.first << ",成績:" << student.second << endl; } return 0; } ``` 在撰寫 first 跟 second 的時候,會有點麻煩,所以我們可以透過 marco(巨集、宏) 這個小技巧去縮短它的長度。 ```cpp= #define f first #define s second ``` 完整範例如下: ```cpp= #include <iostream> #include <utility> #include <vector> #include <string> #define f first #define s second using namespace std; int main() { pair<string, int> student1("王奕翔", 0); pair<string, int> student2 = make_pair("LukeTseng", 100); vector<pair<string, int>> studentList; studentList.push_back(student1); studentList.push_back(student2); studentList.push_back(make_pair("Bob", 78)); for (const auto& student : studentList) { cout << "學生姓名:" << student.f << ",成績:" << student.s << endl; } return 0; } ``` 使用這樣的 marco 的缺點: * 如果我們同時宣告兩個變數 f 和 first,這樣會直接 CE。 * 當 pair 描述的物件是區間、平面上的二維點這種物件時,使用 f 和 s 其實相對不直覺,也許在這時候我們會比較喜歡使用 l, r 或 x, y。 from:NTUCPC Guide 將 f、s 的名稱自定義,會提升程式在閱讀上的可讀性,因此就需要 Structured binding。 以下就是 Structured binding 後的範例: ```cpp= #include <iostream> #include <utility> #include <vector> #include <string> using namespace std; int main() { vector<pair<string, int>> studentList; studentList.emplace_back("王奕翔", 0); studentList.emplace_back("LukeTseng", 100); studentList.emplace_back("Bob", 78); // 使用 structured binding 取出 pair 的元素 for (const auto& [name, score] : studentList) { cout << "學生姓名:" << name << ",成績:" << score << endl; } return 0; } ``` 註:emplace_back 是用於 vector 容器的成員函數,於 C++ 11 中被引入的特性,他比原先的 push_back() 更好、更有效率,使用上也與 push_back() 差不多。 兩者差異在於: * push_back(obj) 需要先建立 obj 物件,再將它複製和移動到 vector 中。 * emplace_back(args...) 不用建立 obj 物件,而是直接在 vector 中構建,少了複製和移動的動作,能提升效率。 在 `[name, score]` 的部分就是用到結構化綁定。 假設 `pair<int, int> a`,則結構化綁定 a 只要寫如下例子即可: `auto [name, score] = a` > Structured binding 在 C++17 後才正式成為 C++ 的標準,因此在更低的版本有可能會不適用。不過如果不幸遇到很舊的 C++ 版本,其實可以找時間測看看競賽使用的編譯器認不認得這個功能,有時候即使版本不對,編譯器看得懂的話只會跳警告但還是會幫你編譯完成。 > > 儘管實務上不太建議,但競程中如果因為舊版本綁手綁腳就有點太虧了,所以機器測試是很重要的! > from NTUCPC Guide ### 結構化綁定的實際適用型態 --- 除了 pair 也可用以下型態: 1. array 2. tuple-like 3. 以 struct 或 class 描述的 data members ### array ```cpp= int arr[5] = {1,2,3,4,5}; auto [a, b, c, d, e] = arr; ``` 其 `a, b, c, d, e` 分別對應 `arr[0], arr[1], arr[2], arr[3], arr[4]`。 ### tuple-like 如同 pair,將多種資料型態打包在一起的組合叫做 tuple-like。 > 不過其實只要大於兩種型別,在 C++ 中就會必須得使用 std::tuple。 如: ```cpp= tuple<int, double, bool> tup = {1, 0.5, true}; auto [a, b, c] = tup; ``` ### 以 struct 或 class 描述的 data members ```cpp= #include <bits/stdc++.h> using namespace std; struct my_struct { int x = 1, y = 2; vector<int> v = {3, 4, 5}; }; int main(){ my_struct S; auto [x, y, z] = S; cout << x << " " << y; return 0; } ``` 輸出: ``` 1 2 ``` :::danger vector 不能使用 Structured binding。 ::: 所以在讀取 struct 的時候,x y 只會代表 struct 裡面的變數 x y,並不是 vector。