C++のSTLのイテレーターについてわかりやすく詳しく解説

C++のSTLには、様々な便利な機能が用意されています。

その中でもイテレーターは、コンテナ内の要素を順番にアクセスするための重要な概念です

本記事では、イテレーターの基礎から始めて、STLで提供される各種イテレーターの種類や使い方について詳しく解説していきます。

目次

イテレーターとは

イテレーターとは、コンテナ内の要素に順番にアクセスするためのオブジェクトです。

STL(Standard Template Library)では、様々な種類のコンテナが用意されており、それぞれに対応したイテレーターが提供されています。

イテレーターを使用することで、コンテナ内の要素を簡単かつ効率的に処理することができます。

また、イテレーターはポインターのようなものであり、ポインター演算子を使用して要素にアクセスすることも可能です。

次の章では、STLで提供される主なイテレーターについて説明します。

イテレーターの種類

C++のSTL(Standard Template Library)には、様々な種類のイテレーターがあります。

イテレーターとは、コンテナ内の要素にアクセスするためのポインターのようなもので、STLでは標準的な方法でコンテナを操作するために使用されます。

C++で扱うイテレーターは主に以下の5種類が存在します。

  • 入力イテレーター
  • 出力イテレーター
  • 前方イテレーター
  • 双方向イテレーター
  • ランダムアクセスイテレーター

入力イテレーター

入力イテレーターは、読み取り専用であることを示すものです。これらは、単方向リストや配列などのシーケンスコンテナで使用されます。入力イテレーターを使用して、コンテナ内の要素を1つずつ読み取ることができます。

出力イテレーター

出力イテレーターは、書き込み専用であることを示すものです。これらは、配列やリストなどのシーケンスコンテナで使用されます。出力イテレーターを使用して、新しい要素を追加したり既存の要素を変更したりすることができます。

前方イテレーター

前方イテレーターは、順方向に移動することができる単方向リストやフォワードリストなどのシーケンスコンテナで使用されます。前方イテレーターを使用して、先頭から末尾まで順番にアクセスすることができます。

双方向イテレーター

双方向イテレーターは、前後両方向に移動することが可能な双方向リストやデック(deque)などのシーケンスコンテナで使用されます。双方向性を持つために前後両方向へ移動可能です。

ランダムアクセスイテレーター

ランダムアクセスイテレーターは最も高度な種類です。これらはランダムアクセス可能な配列やベクタ(vector)等でも使われています。ランダムアクセス性能が高くO(1)時間計算量かかる演算子(+,-,+=,-=,<,>,<=,>=,[])が使えるため効率的です。

以上がC++ STL の主要な5種類のイテレーターです。

イテレーターの使い方

イテレーターは、コンテナ内の要素にアクセスするためのポインターのようなものです。イテレーターを使用することで、コンテナ内の要素を簡単に操作することができます。

イテレーターの宣言

イテレーターを宣言するには、まずコンテナを定義します。次に、そのコンテナからイテレーターを作成します。以下は、vectorコンテナからイテレーターを作成する例です。

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::vector<int>::iterator it = vec.begin();
    return 0;
}

上記の例では、std::vector<int>::iterator を使用して vec コンテナからイテレーター it を作成しています。また、begin() メソッドを使用して最初の要素に対応するイテレーターを取得しています。

イテレーターの移動

イテレーターは、前方向や後方向へ移動させることができます。以下は、前方向へ移動させる例です。

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::vector<int>::iterator it = vec.begin();

    // イテレーターを前方向へ移動
    ++it;

    return 0;
}

上記の例では、++it; を使用して it イテレーターを前方向へ移動させています。

イテレーターの比較

イテレーター同士を比較することができます。以下は、二つのイテレーターが等しいかどうか判断する例です。

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::vector<int>::iterator it1 = vec.begin();
    std::vector<int>::iterator it2 = vec.end();

    if (it1 == it2) {
        std::cout << "it1 and it2 are equal" << std::endl;
    } else {
        std::cout << "it1 and it2 are not equal" << std::endl;
    }

    return 0;
}

上記の例では、== 演算子を使用して it1 イテレーターと it2 イテレーターが等しいかどうか判断しています。

イテレーターの参照

イテレーターポインタ自体だけでなく、ポインタが指す値も取得できます。以下は、現在位置にある値を取得する例です。

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::vector<int>::iterator it = vec.begin();

    // 現在位置にある値を取得
    int value = *it;

    return 0;
}

上記の例では、*演算子を使用して現在位置(vecの先頭要素)にある値(つまり1)を取得しています。

取得できる値は、イテレーターを移動させることで変更させられます。

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::vector<int>::iterator it = vec.begin();

    // 現在位置にある値を取得
    int value1 = *it++; // 1
    int value2 = *it++; // 2
    int value3 = *it++; // 3
    int value4 = *it++; // 4
    int value5 = *it;   // 5

    return 0;
}

イテレーターの変更

イテレーターポインタ自体だけでなく、ポインタが指す値も変更できます。以下は、vecの先頭要素を1から10に変更する例です。

#include <iostream>
#include <vector>

int main() {
  	std::vector<int> vec = {1, 2, 3, 4 ,5};
  	std::vector<int>::iterator it = vec.begin();

  	// 値`1`から`10`に変更
  	*it = 10;

  	return 0;
}

上記の例では、*演算子と代入演算子(=) を使用して現在位置にある値(つまり3)から10に変更しています。

ループ処理

イテレーターはfor文やwhile文で現在位置から終端までループさせることができます。

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = { 1, 2, 3, 4 ,5 };
    std::vector<int>::iterator it = vec.begin();

    while (it != vec.end()) {
        std::cout << *it << std::endl;

        it++;
    }


    return 0;
}
1
2
3
4
5

イテレーターが指す値を順番に参照にしていきたい場合は、上記のようにループさせることで効率よく処理を行えます。

イテレーターの応用

イテレーターは、コンテナ内の要素にアクセスするための便利な手段であるだけでなく、様々な応用が可能です。

ここでは、イテレーターを使ったアルゴリズムやコンテナの操作、関数オブジェクトの実装について解説します。

イテレーターを使ったアルゴリズム

STLには、様々なアルゴリズムが用意されています。これらの多くは、イテレーターを引数として受け取ります。例えば、以下のようなアルゴリズムがあります。

  • std::sort:指定された範囲内の要素を昇順に並び替えます。
  • std::find:指定された値を持つ最初の要素を探し、そのイテレーターを返します。
  • std::accumulate:指定された範囲内の要素を合計します。

これらのアルゴリズムは非常に便利であり、自分で実装する必要がないため開発効率も向上します。

イテレーターを使ったコンテナの操作

イテレーターはコンテナ内部における位置情報を表すため、コンテナ自体を変更することができます。例えば以下のような操作が可能です。

  • 要素の挿入・削除
  • 要素の置換
  • コンテナ全体または一部分の反転

これらは全てイテレーターを使って行えます。例えば以下はvectorから4番目以降を削除する例です。

std::vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin() + 3; // 4番目から末尾まで
v.erase(it, v.end());

itは、v.begin()のイテレータを3つ前に移動したもので、4番目の要素を指しています。

この状態で、v.erase(it, v.end());を実行すると、4番目から最後の要素までを削除するという意味合いになり、効率よくコンテナのデータの変更を行えます。

イテレーターを使った関数オブジェクトの実装

関数オブジェクトとは、「関数」そのものではなく、「関数」に相当する動作を行うオブジェクトです。

STLでは様々な場面で使用されます。例えば以下が代表的な関数オブジェクトです。

  • std::less:比較演算子 < を提供する関数オブジェクト。
  • std::plus:加算演算子 + を提供する関数オブジェクト。
  • std::function:任意型・任意引数・任意戻り値型に対応した汎用的な関数オブジェクト。

これらは全て「呼び出し可能」である必要があります。

つまり、「()演算子」(カッコ)で呼び出せる必要があります。このような呼び出し可能性(callable)を持つオブジェクトは「ファンクタ」とも呼ばれます。

ファンクタは通常、operator()メソッド(カッコ演算子)という名前で定義されます。

そしてこのメソッド内部では何らかの処理が行われます。この時、引数として渡される値や戻り値型も自由に設定することが出来ます。

#include <iostream>
#include <vector>
#include <algorithm>

struct add_x {
    int x;
    add_x(int x) : x(x) {}
    int operator()(int y) const { return x + y; }
};

int main() {
    std::vector<int> v = { 1, 2, 3 };
    std::transform(v.begin(), v.end(), v.begin(), add_x(10));
    // 結果: [11, 12, 13]
    for (auto it = v.begin(); it != v.end(); it++) {
        std::cout << *it << std::endl;
    }
}

上記ではadd_xというファンクタ構造体を定義しました。

このファンクタ構造体ではxというメンバ変数(int型)とoperator()メソッドが定義されています。

そして[1,2,3]というvに対してtransformアルゴリズムでadd_x(10)ファンクタ構造体インスタンス化したもので各要素ごと加算しています。

実行すると以下のようになります。

11
12
13

終わりに

以上がC++のSTLのイテレーターについての解説でした。

イテレーターは、コンテナ内の要素を効率的に操作するために欠かせない機能です。

また、イテレーターを使ったアルゴリズムや関数オブジェクトの実装など、応用範囲も広く、プログラミングにおいて重要な役割を果たしています。

是非この記事を参考にして、C++プログラミングのスキルアップに役立ててください。

目次