Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

STL入門 第2回 ~vector編~

スポンサードリンク

f:id:fa11enprince:20150730081939j:plain
第1回目はこちら↓

ってかこっちのほうが第1回っぽいのは気にしないでください。

STLにはコンテナというテンプレートクラスでできているオブジェクトの入れ物があります。
大きく分けて、シーケンスコンテナ(Sequence Container)、連想コンテナ(Assosiative Container)、コンテナアダプター(Container Adapter)があります。

種類 特徴 代表的な例
シーケンスコンテナ 挿入した要素の順序を維持 vector, deque, list
連想コンテナ keyとvalueのペアで管理(ディクショナリとも) map, multimap, set, multiset
コンテナアダプター イテレータをサポートしない queue, stack

これらはスレッドセーフではありません。JavaのVectorはスレッドセーフですが(Listは違うよ)……

と眠たくなる説明を前半に書いてしまいましたが今日はvector編です。

vectorコンテナで最低限覚えておけばいいことはpush_back()とat()とsize()だけでしょう。
いや、本当はもっと覚えておかないといけないのですが、最初はそれだけで使えるよってことで。
push_back()って最初見たときは変な名前だなーと思いました。
ちなみにpush_front()はありますが、vectorコンテナさんはback専門です。
ちなみにpushしたらpullしたくなるじゃなくてpopなんですね。
スタックの用語的な感じです。なので、push_back()とpop_back()があります。
at()もしくは配列と同じアクセスの方法(operator[]())でアクセスしてあげます。
もちろんイテレータも使えますし、イテレータをつかったときと同等のfor_each()や
Javaの拡張for文と同じものが使えたりします。ちなみにvectorコンテナの中には基本的になんでも入ります。
JavaのようにintじゃなくてIntegerを入れなきゃなんてダサいことにはなってません。
ちなみにArrayListと同じようなC++でのコンテナはvectorです。
ちなみにSTLコンテナはすべてスタック変数として宣言します。
ポインタで宣言してる例は見たことないです。普通の使い方している限りはポインタで宣言するのはあり得ないですが。

まずは伝統的な使い方。

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<int> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);

    for (int i = 0; i < vec.size(); i++)
    {
        cout << "配列形式: " << vec[i] << ", at形式: " << vec.at(i) << endl;
    }
    return 0;
}

簡単ですね。
古いC++ではvectorの初期化方法がないです。アクセスに関しては配列形式のとat()は一緒ですが、
例外を投げるか投げないかの違いです。僕はC++なら別に配列形式でいいんじゃないかなぁと思います。
読みやすいし。JavaでArrayListつかっててget()使うと萎えますもん。

次にもうちょいましな初期化方法+イテレータアクセスです。

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    int arr[] = { 1, 2, 3 };
    // 配列の開始位置と終了位置を渡してあげる
    vector<int> vec(arr, arr + sizeof(arr) / sizeof(int));

    for (vector<int>::iterator it = vec.begin(); it != vec.end(); ++it)
    {
        cout << "中身: " << *it << endl;
    }
    return 0;
}

イテレータはポインタと同じアクセス方法です。vectorコンテナの中身を現在の順番で今回の場合は前から
順にたどっています。逆から見たいときはreverse_iteratorを使うのですが、
そのときはrbigin(), rend()というのをつかうことになります。
ちなみに、賛否両論あるかと思いますが、イテレータを使ったときだけ++i(前置インクリメント)にしているのは
このあたりの記事が参考になるかと思います。
C++で後置インクリメントよりも前置インクリメントが多用される理由 - 考える人、コードを書く人
Google信者ではありませんが、GoogleのC++コーディング規約でも++iにしろって書いてあった記憶があります。
ちなみにですが、イテレータで使ってるend()は実際の末尾より一つ後ろの要素を返します。
そこにアクセスしちゃだめですよ!
おそらくfor文で書くときに書きやすいようにという配慮でしょう。

C++11で楽に書く方法

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<int> vec = { 1, 2, 3 };

    // イテレータをこんな風にちょっとらくにできる
    for (auto it = vec.begin(); it != vec.end(); ++it)
    {
        cout << "中身: " << *it << endl;
    }
    // 配列形式でもOK
    for (int i = 0; i < vec.size(); i++)
    {
        cout << "中身: " << vec[i] << endl;
    }
    // こんな書き方もできる
    for (auto value : vec)
    {
        cout << "中身: " << value << endl;
    }
    return 0;
}

C++11便利すぎますね。
ちなみにvectorは自動で倍々ゲームの状態で自動で領域拡張してくれますが、resize()で自分で制御したり、
宣言時にサイズを指定することもできます。調べてみてください。

参考サイト

cpprefjp vector
https://sites.google.com/site/cpprefjp/reference/vector/vector

第3回はこちら↓