イテレータ¶
イテレータとはコンテナ内での要素の位置を指すもので、 ポインタのように扱うことができます。 イテレータを使用することで コンテナの種類に依存しないで処理を共通化できます。
std::vector<int> x = {0, 1, 2, 3, 4};
// begin() でコンテナ内の先頭要素を指すイテレータを取得
auto it = x.begin();
// イテレータを使用して要素を出力
std::cout << *it << std::endl; // 0
// イテレータを1つ進める
++it;
// イテレータを使用して要素を出力
std::cout << *it << std::endl; // 1
イテレータが指す要素を参照するには
ポインタのデリファレンス同様に *
をつけます。
イテレータはインクリメントで1つ進めることができます。
std::vector<int> x = {0, 1, 2, 3, 4};
// end() でコンテナ内の最終要素の1つ先を指すイテレータを取得
for (auto it = x.begin(); it != x.end(); ++it) {
std::cout << *it << std::endl;
}
end()
で取得するイテレータは最終要素ではなく、
最終要素の1つ先であるためループの終了条件として使用できます。
イテレータはコンテナの種類に依存しないで処理を行えるため、
次のように std::set
に変更してもそのまま動作します。
std::set<int> x = {0, 1, 2, 3, 4};
for (auto it = x.begin(); it != x.end(); ++it) {
std::cout << *it << std::endl;
}
この性質によってコンテナの種類に依存せず
<algorithm>
で提供される機能を使用できます。
#include <algorithm>
std::vector<int> x = {0, 1, 2, 3, 4};
// std::count_if は条件を満たすコンテナ要素の個数を数える処理
// - 第1引数と第2引数で範囲を指定
// - 第3引数で関数オブジェクトで条件を指定
auto n = std::count_if(x.begin(), x.end(), [](const int v) {
// 0 より大きい 2 の倍数
if (v <= 0) {
return false;
}
if (v % 2 != 0) {
return false;
}
return true;
});
std::cout << n << std::endl; // 2
イテレータを使用できるコンテナ¶
イテレータを使用できるコンテナとして、代表的なものはコンテナライブラリで提供されるコンテナです。 コンテナライブラリで提供されるコンテナは STL コンテナと呼ばれ、本書で紹介した以下ものが該当します。
std::array
std::vector
std::map
std::set
std::unordered_map
std::unordered_set
STL コンテナの一覧は コンテナライブラリ - cppreference.com を参照してください。
STL コンテナ以外にも std::string
などでイテレータは使用できます。
マップのイテレータ¶
std::map
や std::unordered_map
のイテレータが指す要素はペアとなっています。
このペアは first
がキーで second
が値です。
std::map<std::string, int> persons = {
{"Alice", 18},
{"Bob", 20}
};
for (auto it = persons.begin(); it != persons.end(); ++it) {
const auto& person = *it; // std::pair<std::string, int>
const std::string& name = person.first;
const int age = person.second;
std::cout << name << ": " << age << std::endl;
}
イテレータの種類¶
標準ライブラリのイテレータは5種類ありますが、 本書ではそのうち以下3種類だけを紹介します。
- 前方向イテレータ (Forward Iterator)
- 双方向イテレータ (Bidirectional Iterator)
- ランダムアクセスイテレータ (Random Access Iterator)
この3種類は次のような is-a 関係があります。
前方向イテレータ¶
イテレータを動かす場合に前に進めることだけができます。
std::unordered_map
や std::unordered_set
のイテレータが該当します。
std::unordered_set<int> x = {0, 1, 2, 3, 4};
auto it = x.begin();
std::cout << *it << std::endl;
++it; // 前に進める
std::cout << *it << std::endl;
双方向イテレータ¶
イテレータを動かす場合に前に進めるだけでなく、後ろへ戻すことができます。
std::map
や std::set
のイテレータが該当します。
std::set<int> x = {0, 1, 2, 3, 4};
auto it = x.begin();
std::cout << *it << std::endl; // 0
++it; // 前に進める
std::cout << *it << std::endl; // 1
--it; // 後ろへ戻す
std::cout << *it << std::endl; // 0
ランダムアクセスイテレータ¶
イテレータを動かす場合に任意の位置へ動かすことができます。
std::array
や std::vector
のイテレータが該当します。
std::vector<int> x = {0, 1, 2, 3, 4};
auto it = x.begin();
std::cout << *it << std::endl; // 0
++it; // 前に進める
std::cout << *it << std::endl; // 1
--it; // 後ろへ戻す
std::cout << *it << std::endl; // 0
it = it + 3; // 3つ前に進める
std::cout << *it << std::endl; // 3
// it は変更せずに it から2つ後ろへ戻した要素を参照
std::cout << it[-2] << std::endl; // 1 (it から2つ後ろへ戻した要素)
std::cout << *it << std::endl; // 3 (it は変更されていない)
イテレータと参照の無効化¶
要素の追加や削除を行うと 既存のイテレータや要素への参照が無効になることがあります。 無効化されたイテレータや参照を使用した場合の挙動は未定義動作 (保証されない) です。
たとえば std::vector
の要素を削除すると、
削除した要素の位置とそれ以降の位置のイテレータと参照が無効化されます。
std::vector<int> x = {0, 1, 2, 3, 4, 5, 6};
auto it1 = x.begin() + 2;
auto it2 = x.begin() + 3;
auto it3 = x.begin() + 4;
std::cout << *it1 << std::endl; // 2
std::cout << *it2 << std::endl; // 3
std::cout << *it3 << std::endl; // 4
x.erase(x.begin() + 3); // index=3 の要素を削除
std::cout << *it1 << std::endl; // 2 (有効なまま)
std::cout << *it2 << std::endl; // 未定義動作 (無効化される)
std::cout << *it3 << std::endl; // 未定義動作 (無効化される)