C++: STLイテレータ
レッスン34-35では、STLコンテナとアルゴリズムについて学びました。
ここでは、STLの「接着剤」——イテレータについて学びます。
イテレータを理解することは、STL全体を理解することにつながります。
1. イテレータの基本
(1) 1.1 イテレータとは?
イテレータはSTLの中核となる概念で、コンテナとアルゴリズムをつなぐ役割を果たします。
たとえ話:
- コンテナ = 本棚
- アルゴリズム = 仕事をする人
- イテレータ = 本棚の本を指差す指(ポインタのようなもの)
(2) 1.2 イテレータの基本操作
イテレータには以下の基本操作があります:
| 操作 | 説明 | 例 |
|---|---|---|
*it |
要素への参照 | int x = *it; |
it++ |
次の要素へ | ++it; |
it-- |
前の要素へ | --it; |
it1 == it2 |
等価比較 | if (it1 == it2) |
it1 != it2 |
不等価比較 | while (it != end()) |
2. イテレータのカテゴリ
(1) 2.1 イテレータの5つの型
STLには5種類のイテレータがあり、機能が異なります:
| イテレータ型 | 機能 | 対応コンテナ |
|---|---|---|
| 入力イテレータ | 読み取り専用 | istream_iterator |
| 出力イテレータ | 書き込み専用 | ostream_iterator |
| 前方イテレータ | 読み書き、前方移動のみ | forward_list |
| 双方向イテレータ | 読み書き、前後移動可能 | list、set、map |
| ランダムアクセスイテレータ | 読み書き、任意位置へジャンプ可能 | vector、deque、array |
(2) 2.2 イテレータの機能比較
機能が増える →
入力 → 前方 → 双方向 → ランダムアクセス
↓ ↓ ↓ ↓
読取 読書 読書 読書
のみ 可能 +後退 +ジャンプ
各イテレータがサポートする操作:
| 操作 | 入力 | 前方 | 双方向 | ランダムアクセス |
|---|---|---|---|---|
参照 * |
✅ | ✅ | ✅ | ✅ |
++ |
✅ | ✅ | ✅ | ✅ |
-- |
❌ | ❌ | ✅ | ✅ |
[] |
❌ | ❌ | ❌ | ✅ |
+ - |
❌ | ❌ | ❌ | ✅ |
(3) 2.3 例:コンテナ別のイテレータ機能
#include <iostream>
#include <vector>
#include <list>
#include <forward_list>
int main() {
std::vector<int> v = {1, 2, 3};
std::list<int> l = {1, 2, 3};
std::forward_list<int> fl = {1, 2, 3};
// vector:ランダムアクセスイテレータ
auto it_v = v.begin();
std::cout << it_v[2] << std::endl; // 3
// list:双方向イテレータ
auto it_l = l.begin();
++it_l; // 前へ進む
--it_l; // 後ろへ戻る
// it_l[2]; // ❌ エラー!listはジャンプ不可
return 0;
}
3. イテレータの無効化
(1) 3.1 イテレータの無効化とは?
イテレータの無効化:コンテナに対する操作によって、イテレータが指していた位置が変わったり、アクセスできなくなったりすることです。
主な原因:
- メモリ再割り当て(
vectorのpush_backなど) - 要素の削除(
erase)
(2) 3.2 コンテナ別のイテレータ無効化
| コンテナ | 操作 | 結果 |
|---|---|---|
vector |
push_back |
全イテレータが無効化(再割り当て時) |
vector |
erase |
削除位置以降のイテレータが無効化 |
list |
push_back |
影響なし |
list |
erase |
削除された要素のイテレータのみ無効化 |
map/set |
erase |
削除された要素のイテレータのみ無効化 |
(3) 3.3 例:vectorのイテレータ無効化(難易度 ⭐⭐)
▶ サンプル 1:STLコンテナの使用(難易度 ⭐)
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
std::cout << "*it = " << *it << std::endl; // 出力:1
// push_back後、イテレータが無効化される可能性
v.push_back(6);
// ❌ 危険!itは無効化されている可能性あり
// std::cout << "*it = " << *it << std::endl; // 未定義動作
// ✅ 正しい:イテレータを再取得
it = v.begin();
std::cout << "*it = " << *it << std::endl; // 出力:1
return 0;
}
(4) 3.4 反復中の削除
エラー例:
for (auto it = v.begin(); it != v.end(); ++it) {
### ▶ サンプル 2:C++での反復(難易度 ⭐)
if (*it % 2 == 0) {
v.erase(it); // ❌ itが無効化される!
}
}
正しい方法:
for (auto it = v.begin(); it != v.end(); ) {
if (*it % 2 == 0) {
it = v.erase(it); // ✅ eraseは次の有効なイテレータを返す
} else {
++it;
}
}
4. 逆イテレータ
(1) 4.1 逆イテレータとは?
逆イテレータはコンテナの要素を逆順に走査します。
▶ サンプル:逆順出力(難易度 ⭐)
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 逆順で走査
for (auto it = v.rbegin(); it != v.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用5 4 3 2 1
5. イテレータアダプタ
(1) 5.1 イテレータアダプタとは?
イテレータアダプタは出力イテレータを特殊な形に変換し、コンテナに自動的に要素を追加します。
主なイテレータアダプタ:
| アダプタ | 機能 | 例 |
|---|---|---|
back_inserter |
末尾に追加 | std::back_inserter(v) |
front_inserter |
先頭に追加 | std::front_inserter(l) |
inserter |
指定位置に追加 | std::inserter(v, v.begin()) |
(2) 5.2 例:back_inserterの使用(難易度 ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst; // 空のベクタ
// ❌ エラー!dstは空なのでbegin()が無効
// std::copy(src.begin(), src.end(), dst.begin());
// ✅ 正しい!back_inserterを使用
std::copy(src.begin(), src.end(), std::back_inserter(dst));
std::cout << "コピー後:";
for (int x : dst) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
6. ストリームイテレータ
(1) 6.1 入力ストリームイテレータ
機能: 入力ストリームからデータを読み取るイテレータ。
▶ サンプル:標準入力からの読み取り(難易度 ⭐⭐)
#include <iostream>
#include <vector>
#include <iterator>
int main() {
std::vector<int> v;
std::cout << "整数を入力(Ctrl+Zで終了):" << std::endl;
// 標準入力からvectorへコピー
std::copy(std::istream_iterator<int>(std::cin),
std::istream_iterator<int>(),
std::back_inserter(v));
std::cout << "入力された値:";
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
(2) 6.2 出力ストリームイテレータ
機能: 出力ストリームにデータを書き込むイテレータ。
▶ サンプル:ファイルへの出力(難易度 ⭐⭐)
#include <iostream>
#include <vector>
#include <iterator>
#include <fstream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 標準出力へ出力
std::copy(v.begin(), v.end(),
std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;
// ファイルへ出力
std::ofstream file("output.txt");
std::copy(v.begin(), v.end(),
std::ostream_iterator<int>(file, "\n"));
file.close();
return 0;
}
▶ サンプル 2:逆イテレータのデモ(難易度 ⭐⭐)
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {10, 20, 30, 40, 50};
std::cout << "Forward: ";
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
std::cout << "Reverse: ";
for (auto it = numbers.rbegin(); it != numbers.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用Forward: 10 20 30 40 50 Reverse: 50 40 30 20 10
▶ サンプル 3:アルゴリズムとイテレータ(難易度 ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> destination;
// back_inserterを使って空のvectorへ安全にコピー
std::copy(source.begin(), source.end(),
std::back_inserter(destination));
// 逆イテレータを使って逆順で表示
std::cout << "Reversed: ";
for (auto it = destination.rbegin(); it != destination.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用Reversed: 5 4 3 2 1
❓ よくある質問
Q:なぜlistにはランダムアクセスイテレータがないのですか? A:
listは連結リストで、メモリ上で連続していないため、ジャンプ操作のコストが高いからです。
Q:イテレータの無効化を防ぐには? A: 1. コンテナ操作後、イテレータを再取得する。2.
eraseの戻り値を使って次の有効なイテレータを取得する。3. アルゴリズム使用時は、イテレータを使い終わってからコンテナを操作する。
Q:const_iteratorとは? A:
const_iteratorは読み取り専用のイテレータで、要素の値を変更できません。
std::vector<int> v = {1, 2, 3};
std::vector<int>::const_iterator it = v.cbegin();
// *it = 10; // ❌ エラー!変更不可
📖 まとめ
| トピック | 要点 |
|---|---|
| イテレータのカテゴリ | 入力→出力→前方→双方向→ランダムアクセス |
| イテレータの無効化 | vector、listなどコンテナによって異なる |
| 逆イテレータ | rbegin()/rend()で逆順走査 |
| イテレータアダプタ | back_inserterなどで自動追加 |
| ストリームイテレータ | STLとIOの統合 |
📝 練習問題
-
初級(難易度 ⭐): イテレータを使って
vector<int>を作成し、すべての要素を出力してください。begin/end と range-based for の両方を使用してください。 -
中級(難易度 ⭐⭐): 逆イテレータ(rbegin/rend)を使って vector を逆順に出力してください。イテレータの動作を観察してください。
-
上級(難易度 ⭐⭐⭐): カスタムイテレータを実装してください。範囲(例:1〜10)を生成するイテレータを定義し、++ と * を実装してください。
- イテレータはコンテナとアルゴリズムの「接着剤」
- イテレータのカテゴリ:入力/出力/前方/双方向/ランダムアクセス
- range-based for loopも内部的にはイテレータを使用して実装されている
- イテレータの無効化:コンテナ操作でイテレータが無効になる可能性に注意
- const_iterator は読み取り専用
次のレッスン:STL関数オブジェクト(#38)