###### tags: `社團事務`
# 🗝 進階 C++ STL 迭代器
我們在之前的課程中介紹了基本功。
但是在實際上,若想要繼續走程式這條路,基本功實在遠遠不夠,接下來的學習大多是靠著自學方式進行,下的努力越多,收穫就會越多,加油!
**STL ( Standard Template Library ) 迭代器** 主要由 3 種東西構成:
1. **迭代器** ( Iterator )
使用者可以利用其逐一存取、宣告、使用 Container 中元素,常見的有 **begin()**、**end()** 等等
| index | 0 | 1 | 2 | 3 |
| -------- | -------- | -------- |-------- | -------- |
| interator | begin()+0 | begin()+1| begin()+2 | begin()+size()|
| interator | end()-size() | end()-3| end()-2 | end()-1|
2. **容器** ( Container )
這個講義裡面的超級重點,配合迭代器及演算法讓你搖身一變變電神。
:::info
**應該注意**:大部分的容器都具有**通性**( common methods ),需要特別注意喔
:::
1. **演算法** ( Algorithm )
在團戰裡面的超強輔助的概念,有很多小功能用得好的話可以省下一堆時間,這裡面的功能有很多都是以前需要思考很久才寫出來的。
學習起來困難重重ㄏ,但是學起來就可以用的得心應手。
覺得**英文很難不想看資料?** 覺得**學不會想放棄?** 你們已經撐到這裡了,**半途而廢絕對不是一個好選擇**,認真、努力、勤練才是學習的捷徑喔!
## 🧬 前置處理器
以下介紹的東西,都是在編譯的預處理階段完成的,這部分的東西不會被編譯成機械語言,通常只會涉及代換的內容。
+ **#define**
定義,它可以將之後程式出現之變數全部帶換成某值
格式為:
```cpp
#define 識別名稱 字串
```
例如:
```cpp
#define PI 3.14159
```
+ **include**
除了 include 內建的標頭檔外,也可以 include 自己撰寫的標頭檔,例如:
```cpp
#include "test.h"
#include <iostream>
```
則會在main.cpp下的目錄找到 test.h 並將它 include 進去。
## 🥏 algorithm 函式庫
**[參考網站](http://www.cplusplus.com/reference/algorithm/)**
**algorithm 函式庫** 是現代化 C++ 的代表之一,這裡扮演一個銜接初階與進階的一個踏板,裡面包含一些很厲害的功能,這裡來舉例一些很好用的函式ㄅ
+ **max** / **min** 最大最小值
在 **參數_1** 和 **參數_2** 選最大最小值
在 **參數_1** 和 **參數_2 前一個位址** 之範圍內選最大最小值
```cpp
max(1,2); // 回傳 2
min('a','z'); // 回傳 a
int num[] = {3,7,2,5,6,4,9};
*max_element(num,num + 7); // 回傳 9
*min_element(num,num + 7); // 回傳 2
// 這 2 個函式的參數和回傳值都是位址,所以需要配合指標使用
```
+ **swap** 置換 2 數
置換 **參數_1** 和 **參數_2**
```cpp
int arr[] = { 3,7,2,5,6,4,9 };
swap(arr[0],arr[1]); // 3,7 -> 7,3
float a = 0.314;
float b = 3.14;
swap(a,b); // 0.314,3.14 -> 3.14,0.314
```
+ **sort** 整理數列
從 **參數_1** 到 **參數_2前一個位址** 作排序
```cpp
int arr[] = { 3,7,2,5,6,4,9 };
sort( begin(arr), begin(arr)+3);
// (2 3 7) 5 6 4 9
sort( begin(arr)+4, end(arr));
// 2 3 7 5 (4 6 9)
sort( arr, arr+7);
// 2 3 4 5 6 7 9
```
+ **copy** 複製數列
從 **參數_1** 到 **參數_2前一個位址** 作複製
```cpp
int arr[] = { 3,7,2,5,6,4,9 };
vector<int> a(7) ;
copy(begin(arr)+4, end(arr),a.begin());
// n = {6 4 9 0 0 0 0}
copy(begin(arr), end(arr),a.begin());
// n = {3 7 2 5 6 4 9}
```
+ **reverse** 翻轉數列
從 **參數_1** 到 **參數_2前一個位址** 作翻轉
```cpp
int arr[] = { 3,7,2,5,6,4,9 };
reverse(begin(arr)+4, end(arr));
// arr[] = {3 7 2 5 9 4 6}
reverse(begin(arr), end(arr))
// arr[] = {6 4 9 5 2 7 3}
```
+ **find** 尋找某值
從 **參數_1** 到 **參數_2前一個位址** 作尋找
```cpp
int myints[] = { 10, 20, 30, 40 };
int * p;
p = find (myints, myints+4, 30);
if (p != myints+4) // 注意這裡的判斷句
cout << "Element found in myints: " << *p << '\n';
else
cout << "Element not found in myints\n";
vector<int> myvector (myints,myints+4);
vector<int>::iterator it;
it = find (myvector.begin(), myvector.end(), 30);
if (it != myvector.end())
cout << "Element found in myvector: " << *it << '\n';
else
cout << "Element not found in myvector\n";
```
+ **count** 計算次數
從 **參數_1** 到 **參數_2前一個位址** 作計算出現次數
```cpp
int myints[] = {10,20,30,30,20,10,10,20};
int mycount = count (myints, myints+8, 10); // 計算出現次數
cout << "10 appears " << mycount << " times.\n";
```
## 🥥 Container 容器
### 種類
STL容器的類型大致上可以分為這幾類,應用上可以簡略區分彼此
+ **序列式容器** ( Sequence containers )
- 陣列 **Array**
- 向量 **Vector**
- 串列 **List**
- 雙端佇列 **Deque**
+ **容器配接器** ( Sequence containers )
- 堆疊 **Stack**
- 佇列 **Queue**
+ **關聯性容器** ( Associative containers )
- 集合 **Set**
- 可重複集合 **Multiset**
- 映射 **Map**
- 可重複映射 **Multimap**
+ **無排序關聯性容器** (Unordered associative containers)
- 無排序集合 **Unordered_set**
- 無排序可重複集合 **Unordered_multiset**
- 無排序映射 **Unordered_map**
- 無排序可重複映射 **Unordered_multimap**
### 通性
在 STL 中容器大多具有下列函式之功能,在這邊先說,後面就不一一贅述ㄌ
+ **迭代器 Iterator**
1. **begin()**:指向首端之 iterator
2. **end()**:指向尾端之 iterator 的**下一個位置**
3. 其他
- 反向迭代器:**rbegin**、**rend**
- 常數迭代器:**cbegin**、**cend**
- 常數反向迭代器:**crbegin**、**crend**
*註: **rbegin** 是 指向尾端元素*,***rend** 是 指向首端元素的前一個位置*
+ **大小 Capacity**
1. **size()**:容器內有多少元素
2. **empty()**:是否已經清空所有元素
+ **接口 Access**
1. **front()**:回傳開口端元素值
2. **back()**:回傳最尾端元素值
+ **功能 Modifier**
1. **clear()**:清空容器
2. **insert()**:插入元素到指定位置,其餘位置順延
3. **erase()**:消除指定位置的元素
### 介面
## 🎠 stack 堆疊
**stack** 是一種先進後出 ( **FILO** ) 的容器。
就像將書疊起來,**先放下** 的書要將 **後放下** 的書 **移走後才能拿**。
+ 圖示
![](https://i.imgur.com/C9QmkkI.png)
### 函式
+ empty
+ size
+ top
+ push
+ pop
+ swap
## 🎭 queue 佇列
**queue** 是一種先進先出 ( **FIFO** ) 的容器。
就像排隊,**先排入** 的人可以比 **後排入** 的人 **先離開**。
+ 圖示
![](https://i.imgur.com/LxAFzob.jpg)
### 函式
+ empty
+ size
+ front
+ push
+ pop
+ swap
## 🎏 vector 向量
**vector** 是一種可以動態使用的陣列,相較於 C-style Array 更為常用。基本上寫 c++ 很建議使用 vector 取代低階的 array 和 pointer,比較易維護、容易除錯、活動度很高等等
**這些是使用 vector 的特點**:
:::info
1. 支援隨機存取
1. 尾端增刪元素很快 : O(1)
1. 中間增刪元素比較費時 : O(n)
:::
### 宣告
+ 一般宣告
```cpp
#include <vector> ;
// vector<資料型態> <陣列名稱>;
vector<int> a;
// vector<float> <陣列名稱> = {初始值_1, 初始值_2, ...};
vector<float> b = {1.14, 2.14, 3.14};
vector<int> c(5);
// 建立大小為 5 的 vector,且預設值為 0
vector<int> c(5,1);
// 建立大小為 5 的 vector,且預設值為 1
```
*註:``vector<int> a[10]`` 是指宣告出 10 個 int 型態的 vector*
+ 引數型宣告
```cpp
#include <vector> ;
vector<int> a = {1,2,3,4,5,6};
// 完全複製 a 之內容到 b
vector<int> b = a;
vector<int> b(a);
// 特定範圍
vector<int> vec( v1.begin(), v1.end() ) // vec = {1,2,3,4,5,6}
vector<int> vec( v1.begin()+1, v1.end()-1 ) // vec = {2,3,4,5}
```
*註:vec 中特定範圍宣告之第二參數為該參數**前一位置***
+ 二維宣告:
```cpp
vector<vector<int>> a;
// 建立空的二維 vector : a
vector<vector<int>> b(10,vector<int>(10));
// 建立 10 * 10 之二維 vector : b
a.resize(10, vector<int>(10));
// 將 a 變成 10*10 方陣,且原本的值不會被取代,但超過範圍的值會被抹去
```
+ 鏈結型二維宣告
```cpp
vector<int> ivec[10];
```
這行就會像這張圖顯示的一樣宣告,因為 vector 的 templete 特性,所以可以當作一個動態鏈結串列使用。
![](https://i.imgur.com/vD8fqOe.jpg)
若是在之後加上這幾行:
```cpp
vector<int> ivec[10];
ivec[0].push_back(10);
ivec[0].push_back(20);
.
.
.
```
就會像這張圖顯示的一樣,可以變成一組**長短不一**的二維鏈結串列
![](https://i.imgur.com/knHtoVT.jpg)
這裡附上[參考資料](https://ramihaha.tw/c-program-container-vector-array-linklist/),可以試著理解看看,這實在非常好用。
### 遍歷
**遍歷**( travel ),聽起來很難的名詞,其實就是全部順一次的意思,這邊介紹幾種方法以供選擇。
- **索引遍歷**
```cpp
vector<int> v = {0, 1, 2, 3, 4};
for (int i = 0; i < v.size(); i++)
cout << v[i] << '\n';
```
- **迭代器遍歷**
```cpp
vector<int> v = {0, 1, 2, 3, 4};
// auto == vector<int>::iterator;
for (auto itr = v.begin(); itr < v.end(); itr++)
cout << *itr << '\n';
```
- **auto + Range-based loop 遍歷**
詳情可以參閱[這裡](https://docs.microsoft.com/zh-tw/cpp/cpp/range-based-for-statement-cpp?view=vs-2019)
總之這裡的用法是真的很重要,大家要特別注意
```cpp
vector<int> v = {0, 1, 2, 3, 4};
// int 不推薦
for(int i : v)
cout << i << ' ';
// auto 不受歡迎,無法改變 i 值
for (auto i : v)
cout << i << ' ';
// auto& 較推薦
for (auto &i : v) // 非萬能像是遇到 bool 會掛掉
cout << i << ' ';
for (auto &&i : v) // 這樣就可以解決了
cout << i << ' ';
// const auto&,只能讀值,同樣不能改值
for (const auto &i : v)
cout << i << ' ';
```
### 函式
+ **增加元素**
我不知道怎麼形容這裡的重要,總之這邊絕對要會,這邊學會後學其他的就會很得心應手
- **push_back()**
```cpp
vector<int> a ;
a.push_back(1); // a = {1}
a.push_back(2); // a = {1,2}
a.push_back(3); // a = {1,2,3}
int i = 0;
a.push_back(i); // a = {1,2,3,0}
int b[]={1,2,3};
a.push_back(b[0]); // a= {1,2,3,0,1}
```
- **insert()**
```cpp
vector<int> a={0,2,4,6};
a.insert(a.begin()+1,1); // 0,1,2,4,6
a.insert(a.begin()+3,3); // 0,1,2,3,4,6
a.insert(a.begin()+5,5); // 0,1,2,3,4,5,6
// 特定範圍
// insert(目的地,複製地首位址,複製地尾位址後一格)
int num[]={7,8,9};
a.insert(a.end(),num.begin(),num.end()); // 0,1,2,3,4,5,6,7,8,9
```
:::warning
**特別注意**:另外有 ***emplace()*** 和 ***emplace_back()*** 的用法,有興趣可以了解一下。
:::
+ **刪除元素**
有增加就有刪除,兩個同等重要,加油 o(〃^▽^〃)o
- **pop_back()**
```cpp
vector<int> a = {1,2,3};
a.pop_back(); // a = {1,2}
a.pop_back(); // a = {1}
```
- **erase()**
```cpp
vector<int> a = {1,2,3,4,5,6,7,8,9};
a.erase(a.begin()+1); // a = {1,3,4,5,6,7,8,9}
a.erase(a.begin()+2); // a = {1,3,5,6,7,8,9}
a.erase(a.begin()+3); // a = {1,3,5,7,8,9}
// 特定範圍
a.erase( a.end()-3 , a.end() ); // a = {1,3,5}
```
+ **其他**
- **assign()**
```cpp
vector<int> a = { 0,2,4,6 };
int num[] = { 7,8,9 };
a.assign(begin(num), end(num)); // a = 7,8,9
```
*註:使用 **assign()** 將會取代原有元素,需特別注意*
- **swap()**
```cpp
vector<int> a(3,100);
vector<int> b(5,200);
swap(a,b);
// a = 200 200 200 200 200
// b = 100 100 100
```
### 函式間傳遞
其實很多人即使對 vector 有初步的認知,卻會不知道 vector 在自己寫的函式中傳遞應該怎麼寫,這邊來為各位解惑。
大致分成這幾種:
1. 傳值型
這麼做會讓整個 vec 的構造重新拷貝一次,不符合效益,**完全不建議使用**。
> 宣告:void 函式名( vector< int> a )
> 呼叫:deal( vec )
2. **傳遞引用型**
老實說,傳引用的函式不僅不會重新拷貝,函式寫的方式跟上面一模一樣。
因此,**絕對推薦大家使用引用傳遞**
> void 函式名( vector< int>& a )
> deal( vec )
> void 函式名( const vector< int>& a )
> deal( vec )
3. **傳遞指標型**
其實這個講起來挺複雜,應用起來不會那麼直觀,可以看完 map 之後再來了解
> void 函式名( vector< int>* a )
> void 函式名( const vector< int>* a )
> deal( &vec )
> deal( &vec )
範例:
```cpp=
#include <iostream>
#include <vector>
using namespace std;
void func(vector<int> &vect)
{
vect.push_back(30);
}
int main()
{
vector<int> vect;
vect.push_back(10);
vect.push_back(20);
func(vect);
for (int i=0; i<vect.size(); i++)
cout << vect[i] << " ";
return 0;
}
```
## 🛒 set 集合
**set** 是數學意義上的集合,你可以按照數學邏輯與程式相互搭配。
而 **set** 分成下列幾種,這些的應用各不相同,在上方 **容器** 那篇已經介紹過了,這邊就大概簡略使用與搭配:
1. **set**:有排序、不含重複元素之集合
3. **multiset** :有排序、含重複元素之集合
3. **unordered set**:無排序、不含重複元素之集合
5. **unordered multiset**:無排序、含重複元素之集合
可是 set 的使用上需注意效率問題,一般會搭配演算法使用。
### 宣告
+ 一般宣告
**set** 系列
```cpp
#include <set>
set<int> a;
multiset<int> b;
// multiset 包括在 set 函式庫中
```
**unordered_set** 系列
```cpp
#include <unordered_set>
unordered_set<int> c;
unordered_multiset<int> d;
// unordered_multiset 包括在 unordered_set 函式庫中
```
+ 引數宣告
```cpp
int myints[] = {75,23,65,42,13};
// 特定範圍
set<int> myset (myints,myints+5);
```
*註:set 中特定範圍為宣告之第二參數為該參數前一位置*
### 遍歷
遍歷在 set 之中非常重要,畢竟身為一個集合,應用上就會需要去遍歷過所有元素。
而且與 vector 相比,較為不直觀,總之大家加油!
+ **迭代器遍歷**
大眾上比較常使用的版本,因為判斷條件較明顯
```cpp
for (auto it=myset.begin(); it != myset.end(); ++it)
cout << ' ' << *it;
// std::set<int>::iterator it = auto it
```
+ **auto + Range-based loop 遍歷**
```cpp
for(auto i : a)
cout<< i << ' ';
for(auto& i : a)
cout<< i << ' ';
// set 裡都是不允許改值的,必竟性質不同
// 所以這 2 種基本上在 set 的遍歷上代表意義一樣,只要遍歷即可
```
### 函式
+ 增加、刪除元素
- **insert()** 插入元素
```cpp
set<int> myset;
// 定值插入
myset.insert(10); // 10
int num = 10;
myset.insert(num*10); // 10 100
// 範圍插入
int a[]={5269,64,1978};
myset.insert(myints, myints + 3); // 10 64 100 1978 5269
// 引數插入
pair< set<int>::iterator, bool> ret;
// pair(迭代器 ,是否存在 )
ret = myset.insert(20); // no new element inserted
if (ret.second == false)
it = ret.first; // "it" now points to element 20
myset.insert(it, 25); // max efficiency inserting
myset.insert(it, 24); // max efficiency inserting
myset.insert(it, 26); // no max efficiency inserting
```
*註:其中 **特定範圍** 之範圍為 第一參數 到 插入的第二個該參數**前一位置***
*註:**引數插入** 為最高效率之方法,會使用到 pair 的觀念,mpa 那邊會有介紹*
:::warning
**特別注意**:另外有 ***emplace()*** 和 ***emplace_hint()*** 的用法,有興趣可以了解一下。
:::
- **erase()** 刪除元素
```cpp
set<int> myset;
for (int i=1; i<10; i++)
myset.insert(i*10); // 10 20 30 40 50 60 70 80 90
// 定值刪除
myset.erase (40);
// 引數刪除
it = myset.begin();
++it; // "it" points now to 20
myset.erase (it);
// 範圍刪除
it = myset.find (60);
myset.erase (it, myset.end());
```
*註:其中 **特定範圍** 之範圍為 第一參數 到 插入的第二個該參數**前一位置***
+ 查找、計算元素
- **count()** 計算元素出現次數
```cpp
int a[]={10,73,12,22,73,73,12};
multiset<int> num (a, a+7);
cout << a.count(73); // 輸出:3,因為 73 出現 3 次
```
**值得注意**:在 **set** 中使用 **count()** 不是 1 就是 0,可以利用此性質檢查是否含有某元素
- **find()** 尋找某值
```cpp
int a[]={10,20,30,40,50,60,70,80,90};
set<int> num(begin(a), end(a));
auot it = find(20); // 回傳 20 之所在迭代器
// it 之後也可以應用
num.erase(it);
```
**值得注意**:假如是 multi系列的只會回傳第一個找到的位址喔
+ 其他
- **swap** 交換元素
寫法是 ``a.swap(b)`` 而不是 ``swap(a,b)`` 喔
```cpp
int num_1[] = { 1,2,3,4 };
int num_2[] = { 5,6,7,8 };
set<int> a(num_1, num_1+size(num_1));
set<int> b(begin(num_2),end(num_2));
a.swap(b);
// a = 5 6 7 8
// b = 1 2 3 4
```
- **upper_bound**、**lower_bound** 值的上下限
***lower_bound*** 會回傳第一個 **不小於** 某值之迭代器,
***upper_bound*** 則會回傳第一個 **大於** 某值之迭代器
```cpp
int num[]={1,2,5,8,11};
set<int> a(num, num + 5);
auto itlow = a.lower_bound(6); // itlow 指向 8
itlow = a.lower_bound(5) // itlow 指向 5
auto itup = a.upper_bound(9); // itup 指向 11
itup = a.upper_bound(8) // itup 指向 11
```
- **equal_range** 值的範圍
可以把他想成是 lower_bound 跟 upper_bound 的組合
而回傳值為 **pair** 型態,
**pair.first** = lower_bound 回傳值
**pair.second** = upper_bound 回傳值
```cpp
int num[] = { 10,20,30,30,30,40,50,60 };
multiset<int> a(num, end(num));
auto itrange = a.equal_range(30);
cout << *itrange.first << ' ' << *itrange.second << endl;
// 輸出 30 40
a.erase(itrange.first, itrange.second);
for (auto i : a)
cout << i<<' ';
// 輸出 10 20 40 50 60
```
+ 數學上集合應用( **algorithm** )
這邊是集合在數學上的主要功能,但是是寫在 **algotithm** 函式庫裡,參數也有點多,又會用到 **iterator** 函式庫的 **inserter** 函式。所以記得加上標頭檔喲。
總之也點麻煩 o(TヘTo)
``set_集合運算(a.a.begin(),a.end(),
b.begin(),b.end(),
inserter(c,c.begin()))``
也就相當於 c = a 集合運算 b
- **set_union** 聯集
```cpp
#include <algorithm>
#include <iterator>
set<int> a;
set<int> b;
.
// a = 5 10 15 20 25
// b = 10 20 30 40 50
set<int> result;
set_union(a.begin(),a.end(),
b.begin(),b.end(),
inserter(result,rusult.begin()))
// result = 5 10 15 20 25 30 40 50
```
- **set_intersection** 交集
```cpp
#include <algorithm>
#include <iterator>
set<int> a;
set<int> b;
.
// a = 5 10 15 20 25
// b = 10 20 30 40 50
set<int> result;
set_intersection(a.begin(),a.end(),
b.begin(),b.end(),
inserter(result,rusult.begin()))
// result = 10 20
```
- **set_difference()** 差集
```cpp
#include <algorithm>
#include <iterator>
set<int> a;
set<int> b;
.
// a = 5 10 15 20 25
// b = 10 20 30 40 50
set<int> result;
set_difference(a.begin(),a.end(),
b.begin(),b.end(),
inserter(result,rusult.begin()))
// result = 5 15 25
```
- **set_symmetric_difference** 聯集 - 差集
```cpp
#include <algorithm>
#include <iterator>
set<int> a;
set<int> b;
.
// a = 5 10 15 20 25
// b = 10 20 30 40 50
set<int> result;
set_symmetric_difference(a.begin(),a.end(),
b.begin(),b.end(),
inserter(result,rusult.begin()))
// result = 5 15 25 30 40 50
```
*註:其實以上介紹之功能也能運用在 **vector** 或 **tree** 上,但是要先 **sort** 喔*
:::warning
**iterator函式庫**
這裡面也是充滿了怪物函示呢 (笑
其中 **prev**、**next**、 **inserer** 都是較常使用的函式喔,詳請參考[這邊](http://www.cplusplus.com/reference/iterator/)
:::
### 函式間傳遞
## 💥 map 映射