---
tags: Cpp
---
# C++ STL 筆記
此筆記最終目標是完整介紹 STL(Standard Template Library,標準模板庫)的相關項目和功能,若有任何建議、疑問(例如難以閱讀的地方)或錯誤還請希望告知。
基本上大多資料搬運自 [C++ reference](https://en.cppreference.com/w/cpp)(他們的資料太完整了,感恩讚歎)。
另外加上部分來自社群的討論,以及少部分自己的理解。
:::warning
:warning: 目前此項目僅完成 container 部分,並且有可能隨時會進行修改或重新編排;此外更有可能繁忙或懶惰棄坑導致未能修正錯誤,因此請將本文作爲引索或參考,再去詳閱相關的專文是比較建議的做法。
:::
## Container
Container 幾乎可以說是 STL 中最重要的功能。各種 library 以及官方的演算法都是圍繞 STL 容器展開的。C++ reference 將容器分爲底下幾種。
- Sequence containers
- Associative containers
- Unordered associative containers
- Container adaptors
### Sequence containers
基本的資料結構,sequential 雖然直翻是序列的,但大致上可以想像成資料是以線性(一個接一個有順序,而不會像樹一樣分叉)的排列方式儲存,因此可以「按照順序」去讀寫。
- `array` (C++11): static contiguous array
- `vector`: dynamic contiguous array
- `deque`: double-ended queue
- `forward_list` (C++11): singly-linked list
- `list`: doubly-linked list
既然都是線性的排列儲存,那麼這幾種的差別在哪?基本上是根據資料是否需要滿足:
- 記憶體配置:
- 只有 `array` 和 `vector` (since C\+\+03)保證連續排列。
- 只有 `array` 不能動態配置(但需要的額外記憶體最少)。
- 隨機存取效率 v.s. 隨機插入/刪除效率:
- `array`, `vector` 和 `deque` 皆可進行 `O(1)` 隨機存取。
- `list` 和 `forward_list` 有較好的 `O(1)` 隨機插入/刪除效率(連續插入/刪除優勢尤其明顯)。
- 迭代器 (iterator) 穩定性:
- 不同的容器可能會造成迭代器在不同的行爲(例如插入/刪除)後失效。
- 由於這個問題的細節相當複雜,建議需要時可以參考 [Stackoverflow - Iterator invalidation rules for C++ containers](https://stackoverflow.com/q/6438086)
- 反向迭代
- 很明顯的 `forward_list` 不能反向迭代,但比 `list` 省空間。
- 資料開頭插入/刪除
- `vector` 的開頭操作和隨機插入/刪除一樣爛。
:::info
:information_source: 有些人可能會發現,爲什麼在這裡有 `std::deque`,但 `std::queue` 這個容器反而被放到後面的 adaptor 分類去了。這不也是一個 sequential container 嗎?
::: spoiler
在 STL 裡,`stack`, `queue` 這類的抽象概念容器是以「高階容器」的概念被實作;相對的 `vector`, `deque`, `list` 之類的則是相對底層的容器——高階容器的底層是用他們來實作的。
對於 `stack`, `queue` 來說,只要有符合對應概念的操作手段的容器,就能被用來當作高階容器的底層實作。
Ref: [c++ deque vs queue vs stack](https://stackoverflow.com/a/2248063)
:::
### Associative containers
關聯容器,實作了一些可以以 `O(log n)` 複雜度進行搜尋的資料結構。這裏的「關聯」特別指的是能夠進行「比較」的排序關係 (ordering relation),而不是指像 `std::map` 中 key-value 之間的關聯。
由於這個容器是「有序」的,因此資料搜尋可以在 `O(log n)` 的複雜度下完成。
但另一方面就是在刪除和插入資料時會需要額外的 `O(log n)` 排序(或是以樹來說的話則是有平衡的行爲)——這點對於我們只是想單純的儲存(而不需要搜尋時)時通常反而是一個缺點。
- `set`: collection of unique keys, sorted by keys
- `map`: collection of key-value pairs, sorted by keys, keys are unique
- `multiset`: collection of keys, sorted by keys
- `multimap`: collection of key-value pairs, sorted by keys
實作上常用[紅黑樹 Red–black tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree)實作,但只要能符合條件的實作都可以,不見得要是紅黑樹。
### Unordered associative containers (since C++11)
無序關聯容器(這裏的關聯指的是 key-hash 的關聯),同樣可以快速搜尋在容器內的資料。
由於是透過 hash 函數實作,因此搜尋的複雜度最佳情況下爲 `O(1)`,最糟爲 `O(n)`。同時 key 和 key 之間缺少了「比較」關係(hash 不需要這個性質),資料在儲存時自然不會維持某種排列順序。反過來說資料會被「分散」到不同的「桶子」中儲存。決定分配到哪個桶子則是由 hash 計算後的結果決定。
計算 hash 是 `O(1)`,因此搜尋、插入、刪除「平均」來說都是 `O(1)`。
- `unordered_set`
- `unordered_map`
- `unordered_multiset`
- `unordered_multimap`
### Container adaptors
直到 C\+\+20 爲止,container adaptors 有 `stack`, `queue` 和 `priority_queue` 共 3 種。(C\+\+23 則增加了 `flat_set`, `flat_map`... 等。)
Container adaptors 顧名思義,是用其他容器(基本上就是前面提到的 sequential container)來實作,並沒有獨立的實作叫做 `queue` 或 `stack`,只是提供一個方便使用的「介面」讓大家方便使用而已。
宣告 `stack` 和 `queue` 時,也可以指定底層使用什麼容器,只要這個底層容器能符合要求:例如 `stack` 需要可以使用 push, pop, top 來操作的底層容器。
- `stack`: adapts a container to provide stack (LIFO data structure)
- `queue`: adapts a container to provide queue (FIFO data structure)
- `priority_queue`: adapts a container to provide priority queue
值得注意的是 `priority_queue` (預設)會將資料由大到小排成序列,因此需要是能夠定義大小的資料。插入和取出時也會是 `O(log n)` 的複雜度。