【C++】競程筆記(實作技巧:Range-based for loop、Structured binding)

程式碼範例參考:NTUCPC Guide,此筆記僅為個人學習用途。

Range-based for loop

這個可以提供更簡潔的遍歷寫法,如下例子:

1
2
3
4
vector <int> v = {1,2,3,4,5,6,7,8,9};
for (int i : v){
cout << i << endl;
}

若要對 i 進行修改,需要加上 &,如:int& i

字串陷阱


1
2
3
4
5
6
7
8
9
10
#include <bits/stdc++.h>

using namespace std;

int main(){
for (char c : "abc"){
cout << c << "*\n";
}
return 0;
}

輸出:

1
2
3
4
a*
b*
c*
*

C / C++ 儲存字串時,是使用字元陣列的形式,他會在最後一個地方加上 \0,表示結束的意思。

所以可看到最後會有一個空格。

要解決此問題僅需要在 “abc” 後面加上一個 s 變成 "abc"s,使這串文字轉換成 C++ 字串型態。

1
2
3
4
5
6
7
8
9
10
#include <bits/stdc++.h>

using namespace std;

int main(){
for (char c : "abc"s){
cout << c << "*\n";
}
return 0;
}

輸出:

1
2
3
a*
b*
c*

Structured binding

以下是 pair 容器的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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(巨集、宏) 這個小技巧去縮短它的長度。

1
2
#define f first
#define s second

完整範例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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 後的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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

1
2
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。

如:

1
2
tuple<int, double, bool> tup = {1, 0.5, true};
auto [a, b, c] = tup;

以 struct 或 class 描述的 data members

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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
1 2

:::danger
vector 不能使用 Structured binding。
:::

所以在讀取 struct 的時候,x y 只會代表 struct 裡面的變數 x y,並不是 vector。