読者です 読者をやめる 読者になる 読者になる

kivantium活動日記

プログラムを使っていろいろやります

C++11のrange-based for文について

先月、アスキードワンゴから江添亮さんの「C++11/14コア言語」が発売されました。C++でコードを書いているくせにC++の言語仕様を真面目に勉強していなかったので、この本を読んでC++力の向上に努めています。今まで知らなかった細かいことがたくさん書いてありとても勉強になります。

C++11/14 コア言語

C++11/14 コア言語

ちなみにほとんど同内容の「C++11の文法と機能」はGNU Free Documentation Licenseで公開されています

6章にあったrange-based for文というC++11から導入された機能が便利そうだったので使い方をメモしておきます。

基本の使い方

本によれば「range-based for文はレンジをサポートしている配列、初期化リスト、クラスの各要素に対して、それぞれ文を実行するための文である。」とのことです。

書式は

for ( for-range-宣言: for-tange-初期化子 ) 文

という感じです。配列名を指定するだけでループのための変数を用意せずに配列全体に対して処理を行うことができます。他言語のforeach文やPythonのfor文に近いイメージで使えそうです。簡単な例を示します。

#include <iostream>

using namespace std;

int main() {
    int a[3] = {1, 2, 3};

    // よくある書き方
    for (int i=0; i<3; ++i) cout << a[i] << " ";
    cout << endl;
    
    // range-based for文を使った書き方
    
    // 配列の各要素をコピーで受ける
    for (int i : a) cout << i << " ";
    cout << endl;
    
    // 配列の各要素を参照で受ける
    for (int &ref : a) cout << ref << " ";
    cout << endl;
    
    // auto指定子を使う
    for (auto i : a) cout << i << " ";
    cout << endl;

    // auto指定子を使う
    for (auto &ref : a) cout << ref << " ";
    cout << endl;
}

出力

1 2 3 
1 2 3 
1 2 3 
1 2 3 
1 2 3 

なお、C++11の機能を使っているので通常はコンパイル時にオプションを指定する必要があります。

g++ main.cpp -std=c++11
clang++ main.cpp -std=c++11


参照を使った場合は配列の中身の書き換えができます。また、参照に対してconstを使うこともできます。

#include <iostream>

using namespace std;

int main() {
    int a[] = {1, 2, 3};

    // コピーに対する代入
    for (auto i : a) i *= 2;
    
    // 変更されない
    for (auto i : a) cout << i << " ";
    cout << endl;
    
    // 参照に対する代入
    for (auto &ref : a) ref *= 2;

    // 変更される
    for (auto &ref : a) cout << ref << " ";
    cout << endl;
    
    // const指定をすると代入に対してコンパイルエラーを吐く
    // for (const auto &ref : a) ref *= 2;
}

出力

1 2 3 
2 4 6

コピーを使うとコピーのための処理が必要ですが、参照にするとコピーの分のコストが抑えられるようです。

range-based for文は配列以外についても使うことが出来ます

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <iterator> // 本には必要とあったが、g++/clang++ではインクルードしなくてもコンパイルできた

using namespace std;

int main() {
    // 初期化リストに対する処理
    for (auto i : {1, 2, 3}) cout << i << " ";
    cout << endl;

    // vectorに対するループ
    vector<int> v = {1, 2, 3};
    for (auto i : v) cout << i << " ";
    cout << endl;

    // mapの作成
    map<string, int> m;
    m["shino"] = 155;
    m["alice"] = 139;
    m["ayaya"] = 159;
    m["youko"] = 163;
    m["karen"] = 150;
    
    // mapに対するループ
    for (const auto i : m) {
        cout <<  i.first << " " << i.second << endl;
    }

    // 2次元配列の作成
    int a[4][3] = { {1, 2, 3},
                    {4, 5, 6},
                    {7, 8, 9},
                    {10, 11, 12} };
    // 2次元配列に対するループ
    for (auto & row : a){
        for (auto & i : row){
            cout << i << " ";
        }
        cout << endl;
    }
    
    // 以下のコードはエラー
    /*for (auto row : a){     // rowには&a[0][0],...,&a[3][0]の値がコピーされる
        for (auto i : row){   // rowに対するbegin()が存在しないのでエラー
            cout << i << " ";
        }
        cout << endl;
    }*/
}

出力

1 2 3 
1 2 3 
alice 139
ayaya 159
karen 150
shino 155
youko 163
1 2 3 
4 5 6 
7 8 9 
10 11 12 

以上です。ループのための余計な変数を使わずに済み、コードが簡潔になりそうなので活用していきたいと思います。