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
双方向イテレータ 読み書き、前後移動可能 listsetmap
ランダムアクセスイテレータ 読み書き、任意位置へジャンプ可能 vectordequearray

(2) 2.2 イテレータの機能比較

TEXT 📖 参照専用
機能が増える →
入力 → 前方 → 双方向 → ランダムアクセス
  ↓        ↓         ↓            ↓
読取     読書      読書          読書
のみ     可能      +後退         +ジャンプ

各イテレータがサポートする操作:

操作 入力 前方 双方向 ランダムアクセス
参照 *
++
--
[]
+ -

(3) 2.3 例:コンテナ別のイテレータ機能

CPP
#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 イテレータの無効化とは?

イテレータの無効化:コンテナに対する操作によって、イテレータが指していた位置が変わったり、アクセスできなくなったりすることです。

主な原因:


(2) 3.2 コンテナ別のイテレータ無効化

コンテナ 操作 結果
vector push_back 全イテレータが無効化(再割り当て時)
vector erase 削除位置以降のイテレータが無効化
list push_back 影響なし
list erase 削除された要素のイテレータのみ無効化
map/set erase 削除された要素のイテレータのみ無効化

(3) 3.3 例:vectorのイテレータ無効化(難易度 ⭐⭐)

▶ サンプル 1:STLコンテナの使用(難易度 ⭐)

CPP
#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 反復中の削除

エラー例:

TEXT 📖 参照専用
for (auto it = v.begin(); it != v.end(); ++it) {
### ▶ サンプル 2:C++での反復(難易度 ⭐)

    if (*it % 2 == 0) {
        v.erase(it); // ❌ itが無効化される!
    }
}

正しい方法:

CPP
for (auto it = v.begin(); it != v.end(); ) {
    if (*it % 2 == 0) {
        it = v.erase(it); // ✅ eraseは次の有効なイテレータを返す
    } else {
        ++it;
    }
}



4. 逆イテレータ

(1) 4.1 逆イテレータとは?

逆イテレータはコンテナの要素を逆順に走査します。

▶ サンプル:逆順出力(難易度 ⭐)

CPP
#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の使用(難易度 ⭐⭐)

CPP
#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 入力ストリームイテレータ

機能: 入力ストリームからデータを読み取るイテレータ。

▶ サンプル:標準入力からの読み取り(難易度 ⭐⭐)

CPP
#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 出力ストリームイテレータ

機能: 出力ストリームにデータを書き込むイテレータ。

▶ サンプル:ファイルへの出力(難易度 ⭐⭐)

CPP
#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:逆イテレータのデモ(難易度 ⭐⭐)

CPP
#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:アルゴリズムとイテレータ(難易度 ⭐⭐)

CPP
#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は読み取り専用のイテレータで、要素の値を変更できません。

CPP
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の統合

📝 練習問題

  1. 初級(難易度 ⭐): イテレータを使って vector<int> を作成し、すべての要素を出力してください。begin/end と range-based for の両方を使用してください。

  2. 中級(難易度 ⭐⭐): 逆イテレータ(rbegin/rend)を使って vector を逆順に出力してください。イテレータの動作を観察してください。

  3. 上級(難易度 ⭐⭐⭐): カスタムイテレータを実装してください。範囲(例:1〜10)を生成するイテレータを定義し、++ と * を実装してください。



次のレッスン:STL関数オブジェクト(#38)

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%