[C++] 範囲ベースforループ(foreach)の使い方

C++の範囲ベースforループは、コンテナや配列の要素を簡単に反復処理するための構文です。

従来のforループと異なり、インデックスを明示的に管理する必要がなく、コードが簡潔になります。

範囲ベースforループは、forキーワードの後にコロンを使って、コンテナや配列を指定します。

このループは、STLコンテナや配列、カスタムクラスのような範囲を持つオブジェクトに対して使用できます。

また、autoキーワードを使うことで、要素の型を自動的に推論することが可能です。

この記事でわかること
  • 範囲ベースforループの基本構文とその利点
  • 配列やSTLコンテナに対する具体的な使用例
  • 参照やconstを用いた応用的な使い方
  • 自作クラスやイテレータを用いた範囲ベースforループの実装方法
  • 範囲ベースforループを使用する際の注意点とパフォーマンスへの影響

目次から探す

範囲ベースforループの基本

範囲ベースforループとは?

範囲ベースforループは、C++11で導入された新しいループ構文です。

この構文を使用することで、配列やコンテナの要素を簡単に反復処理することができます。

従来のforループに比べて、コードが簡潔になり、可読性が向上します。

範囲ベースforループの基本構文

範囲ベースforループの基本構文は以下の通りです。

#include <iostream>
#include <vector>
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    // 範囲ベースforループの使用例
    for (int number : numbers) {
        std::cout << number << std::endl; // 各要素を出力
    }
    return 0;
}

この構文では、forキーワードの後に、要素を受け取る変数とコロン:、そして反復処理を行うコンテナを指定します。

上記の例では、numbersというベクターの各要素がnumberという変数に代入され、ループ内で使用されます。

1
2
3
4
5

この例では、numbersベクターの各要素が順番に出力されます。

従来のforループとの違い

範囲ベースforループと従来のforループの主な違いは以下の通りです。

スクロールできます
特徴範囲ベースforループ従来のforループ
コードの簡潔さ高い低い
可読性高い低い
インデックスの使用不要必要
イテレータの使用不要必要な場合あり

範囲ベースforループは、インデックスやイテレータを明示的に使用する必要がないため、コードが簡潔で可読性が高くなります。

一方、従来のforループは、インデックスを使用して要素にアクセスするため、より詳細な制御が可能です。

使用できるデータ型

範囲ベースforループは、以下のようなデータ型に対して使用できます。

コンテナ名説明
配列固定サイズの連続したメモリ領域にデータを格納する基本的なデータ構造。
std::vector動的配列で、サイズの変更が可能。要素へのランダムアクセスが高速。
std::list双方向連結リストで、要素の挿入・削除が高速。ランダムアクセスは遅い。
std::mapキーと値のペアを格納する連想配列。キーは自動的にソートされる。
std::set重複しない要素を格納する集合。要素は自動的にソートされる。
その他のSTLコンテナstd::deque, std::stack, std::queue, std::priority_queue など。

これらのデータ型は、範囲ベースforループを使用することで、簡単に要素を反復処理することができます。

特にSTLコンテナは、範囲ベースforループとの相性が良く、コードの可読性を大幅に向上させます。

範囲ベースforループの使い方

配列に対する範囲ベースforループ

配列に対して範囲ベースforループを使用することで、各要素を簡単に処理できます。

以下はその例です。

#include <iostream>
int main() {
    int numbers[] = {10, 20, 30, 40, 50};
    // 配列に対する範囲ベースforループ
    for (int number : numbers) {
        std::cout << number << std::endl; // 各要素を出力
    }
    return 0;
}
10
20
30
40
50

この例では、numbers配列の各要素が順番に出力されます。

ベクターに対する範囲ベースforループ

std::vectorに対しても範囲ベースforループを使用できます。

以下にその例を示します。

#include <iostream>
#include <vector>
int main() {
    std::vector<std::string> fruits = {"apple", "banana", "cherry"};
    // ベクターに対する範囲ベースforループ
    for (const std::string& fruit : fruits) {
        std::cout << fruit << std::endl; // 各要素を出力
    }
    return 0;
}
apple
banana
cherry

この例では、fruitsベクターの各要素が順番に出力されます。

マップに対する範囲ベースforループ

std::mapに対して範囲ベースforループを使用することで、キーと値のペアを処理できます。

#include <iostream>
#include <map>
int main() {
    std::map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};
    // マップに対する範囲ベースforループ
    for (const auto& pair : scores) {
        std::cout << pair.first << ": " << pair.second << std::endl; // キーと値を出力
    }
    return 0;
}
Alice: 90
Bob: 85
Charlie: 95

この例では、scoresマップの各キーと値のペアが順番に出力されます。

セットに対する範囲ベースforループ

std::setに対しても範囲ベースforループを使用できます。

#include <iostream>
#include <set>
int main() {
    std::set<int> uniqueNumbers = {1, 2, 3, 4, 5};
    // セットに対する範囲ベースforループ
    for (int number : uniqueNumbers) {
        std::cout << number << std::endl; // 各要素を出力
    }
    return 0;
}
1
2
3
4
5

この例では、uniqueNumbersセットの各要素が順番に出力されます。

ポインタを使った範囲ベースforループ

ポインタを使って範囲ベースforループを使用することも可能です。

以下にその例を示します。

#include <iostream>
int main() {
    int numbers[] = {100, 200, 300, 400, 500};
    int* begin = std::begin(numbers);
    int* end = std::end(numbers);
    // ポインタを使った範囲ベースforループ
    for (int* ptr = begin; ptr != end; ++ptr) {
        std::cout << *ptr << std::endl; // ポインタが指す要素を出力
    }
    return 0;
}
100
200
300
400
500

この例では、ポインタを使ってnumbers配列の各要素が順番に出力されます。

範囲ベースforループを使うことで、ポインタを明示的に操作することなく、簡潔に要素を処理できます。

範囲ベースforループの応用

参照を使った範囲ベースforループ

範囲ベースforループでは、要素を参照として受け取ることができます。

これにより、要素を直接変更することが可能です。

#include <iostream>
#include <vector>
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    // 参照を使った範囲ベースforループ
    for (int& number : numbers) {
        number *= 2; // 各要素を2倍にする
    }
    for (int number : numbers) {
        std::cout << number << std::endl; // 変更後の要素を出力
    }
    return 0;
}
2
4
6
8
10

この例では、numbersベクターの各要素が2倍に変更され、変更後の値が出力されます。

constを使った範囲ベースforループ

要素を変更しない場合は、constを使って要素を定数参照として受け取ることができます。

これにより、誤って要素を変更することを防ぎます。

#include <iostream>
#include <vector>
int main() {
    std::vector<std::string> fruits = {"apple", "banana", "cherry"};
    // constを使った範囲ベースforループ
    for (const std::string& fruit : fruits) {
        std::cout << fruit << std::endl; // 各要素を出力
    }
    return 0;
}
apple
banana
cherry

この例では、fruitsベクターの各要素が出力されますが、要素自体は変更されません。

自作クラスでの範囲ベースforループ

自作クラスでも範囲ベースforループを使用することができます。

クラスにbegin()とend()メソッドを実装する必要があります。

#include <iostream>
#include <vector>
class MyContainer {
public:
    std::vector<int> data = {10, 20, 30};
    auto begin() { return data.begin(); }
    auto end() { return data.end(); }
};
int main() {
    MyContainer container;
    // 自作クラスでの範囲ベースforループ
    for (int value : container) {
        std::cout << value << std::endl; // 各要素を出力
    }
    return 0;
}
10
20
30

この例では、MyContainerクラスdataメンバーの各要素が出力されます。

イテレータを使った範囲ベースforループ

範囲ベースforループは、イテレータを使ってコンテナを反復処理することもできます。

これにより、より柔軟な操作が可能です。

#include <iostream>
#include <list>
int main() {
    std::list<int> numbers = {100, 200, 300};
    // イテレータを使った範囲ベースforループ
    for (auto it = numbers.begin(); it != numbers.end(); ++it) {
        std::cout << *it << std::endl; // イテレータが指す要素を出力
    }
    return 0;
}
100
200
300

この例では、numbersリストの各要素がイテレータを使って出力されます。

範囲ベースforループとラムダ式の組み合わせ

範囲ベースforループとラムダ式を組み合わせることで、より強力な処理を行うことができます。

#include <iostream>
#include <vector>
#include <algorithm>
int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    // 範囲ベースforループとラムダ式の組み合わせ
    std::for_each(numbers.begin(), numbers.end(), [](int& number) {
        number += 10; // 各要素に10を加える
    });
    for (int number : numbers) {
        std::cout << number << std::endl; // 変更後の要素を出力
    }
    return 0;
}
11
12
13
14
15

この例では、numbersベクターの各要素に10が加えられ、変更後の値が出力されます。

ラムダ式を使うことで、範囲ベースforループ内での処理を簡潔に記述できます。

範囲ベースforループの注意点

コピーと参照の違い

範囲ベースforループでは、要素をコピーするか参照するかを選択できます。

コピーを使用すると、元のデータは変更されませんが、参照を使用すると元のデータを変更することができます。

#include <iostream>
#include <vector>
int main() {
    std::vector<int> numbers = {1, 2, 3};
    // コピーを使った範囲ベースforループ
    for (int number : numbers) {
        number += 10; // コピーされた値を変更
    }
    // 参照を使った範囲ベースforループ
    for (int& number : numbers) {
        number += 10; // 元の値を変更
    }
    for (int number : numbers) {
        std::cout << number << std::endl; // 変更後の要素を出力
    }
    return 0;
}
11
12
13

この例では、最初のループでコピーされた値は変更されませんが、2番目のループで参照を使って元の値が変更されます。

範囲ベースforループのパフォーマンス

範囲ベースforループは、従来のforループと比較して、コードの可読性を向上させる一方で、パフォーマンスに影響を与えることがあります。

特に、大きなデータセットを処理する場合、コピーを避けて参照を使用することで、パフォーマンスを向上させることができます。

  • コピー: 各要素がコピーされるため、メモリと時間のオーバーヘッドが発生する可能性があります。
  • 参照: 元のデータを直接操作するため、オーバーヘッドが少なくなります。

範囲ベースforループでの要素の変更

範囲ベースforループを使用して要素を変更する場合、参照を使用する必要があります。

コピーを使用すると、元のデータは変更されません。

#include <iostream>
#include <vector>
int main() {
    std::vector<int> numbers = {5, 10, 15};
    // 参照を使って要素を変更
    for (int& number : numbers) {
        number *= 2; // 各要素を2倍にする
    }
    for (int number : numbers) {
        std::cout << number << std::endl; // 変更後の要素を出力
    }
    return 0;
}
10
20
30

この例では、numbersベクターの各要素が2倍に変更されます。

範囲ベースforループのスコープ

範囲ベースforループ内で宣言された変数は、そのループのスコープ内でのみ有効です。

ループの外ではアクセスできません。

#include <iostream>
#include <vector>
int main() {
    std::vector<int> numbers = {1, 2, 3};
    for (int number : numbers) {
        std::cout << number << std::endl; // ループ内での使用
    }
    // std::cout << number << std::endl; // エラー: 'number'はスコープ外
    return 0;
}

この例では、number変数は範囲ベースforループ内でのみ有効であり、ループの外で使用しようとするとエラーが発生します。

範囲ベースforループを使用する際は、スコープに注意する必要があります。

よくある質問

範囲ベースforループはどのバージョンから使えますか?

範囲ベースforループは、C++11から使用可能です。

C++11は、2011年にリリースされたC++の標準規格で、多くの新機能が追加されました。

範囲ベースforループもその一つで、コードの簡潔さと可読性を向上させるために導入されました。

C++11以降のコンパイラを使用することで、範囲ベースforループを利用できます。

範囲ベースforループはどのような場合に使うべきですか?

範囲ベースforループは、以下のような場合に使用するのが適しています。

  • 配列やSTLコンテナの全要素を順番に処理したいとき。
  • インデックスやイテレータを明示的に使用する必要がない場合。
  • コードの可読性を向上させたい場合。
  • 要素を変更する必要がない、または参照を使って直接変更したい場合。

範囲ベースforループは、特に配列やコンテナの全要素を処理する際に便利で、コードを簡潔に保つことができます。

範囲ベースforループでエラーが発生するのはなぜですか?

範囲ベースforループでエラーが発生する主な原因は以下の通りです。

  • 非対応のコンパイラ: C++11以降に対応していないコンパイラを使用している場合、範囲ベースforループはサポートされません。
  • 不適切なデータ型: 範囲ベースforループは、begin()とend()メソッドを持つデータ型に対してのみ使用できます。

これらのメソッドが実装されていないクラスや型に対して使用するとエラーが発生します。

  • スコープの問題: ループ内で宣言された変数をループ外で使用しようとすると、スコープ外のエラーが発生します。

これらの点に注意することで、範囲ベースforループを正しく使用することができます。

まとめ

この記事では、C++の範囲ベースforループについて、その基本的な使い方から応用例までを詳しく解説しました。

範囲ベースforループは、配列やSTLコンテナの要素を簡潔に反復処理するための便利な構文であり、コードの可読性を向上させるとともに、パフォーマンスにも配慮した使い方が可能です。

この記事を参考に、実際のプログラムで範囲ベースforループを活用し、より効率的なコードを書いてみてください。

当サイトはリンクフリーです。出典元を明記していただければ、ご自由に引用していただいて構いません。

関連カテゴリーから探す

  • 条件分岐 (11)
  • 繰り返し処理 (11)
  • URLをコピーしました!
目次から探す