【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。