このエントリーをはてなブックマークに追加

手を動かしてさくさく理解する
C++ 両端キュー std::deque 入門
Copyright (C) 2015 by Nobuhide Tsuda

 

※ イラスト提供:かわいいフリー素材集「いらすとや」様

両端キュー std::deque とは

deque は double-ended-queue の略で「デック」と発音される。

deque は vector と同じ動的配列クラスで、末尾だけでなく先頭への要素追加・削除も O(1) と高速なクラスだぞ。
先頭への要素追加・削除もOKなので「両端キュー」とも呼ばれる。
用意されているメンバ関数も vector とほぼ同じで使い勝手もほぼ同じなので、先頭付近での挿入削除処理が多い場合は deque の使用を検討するといいぞ。

ただし、vector と違い、deque の要素は連続したアドレスに保存されているとは限らないので、要素のアドレスを取り出して、 それをポインタとして他の関数に渡すと問題が発生することがあるので、要注意だ。
また、先頭付近での挿入削除以外は、vector の方がパフォーマンスに優れるので、先頭付近での挿入削除を頻繁に行わないのであれば vector を使うのがよい。

準備:インクルード

まずは、deque を使うための準備だ。
deque は C++標準のライブラリであり、「#include <deque>」を記述することで利用可能になる。

名前空間は「std」なので、使用の度に「std::」を前置するか、または「using namespace std;」を記述しておく。

#include <deque>       // ヘッダファイルインクルード
int main()
{
    std::deque<int> dq;       // ローカル変数として、dq を生成
    .....
}
#include <deque>       // ヘッダファイルインクルード
using namespace std;         //  名前空間指定
int main()
{
    deque<int> dq;       // ローカル変数として、dq を生成
    .....
}

宣言・初期化

単純な宣言

std::deque オブジェクトを生成するには「std::deque<型> オブジェクト名;」 と記述する。
オブジェクト名とは、宣言する両端キューを識別するための名前のことだ。変数名と言ってもよい。 deque はクラスなので、それを実体化したものはオブジェクトまたはインスタンスと呼ばれる。
下記は、int 型の両端キュー data を宣言している例だ。

    std::deque<int> data;  //  int 型の両端キュー data の宣言

当然、型の部分はどんな型でも構わない。char や double や、自分で定義した構造体、クラスでもOKだ。
※ ただし、初期化時にデフォルトコンストラクタ、バッファ拡張時にコピーコンストラクタが呼ばれことがあるので、 それらがちゃんと定義されている必要がある。

通常の配列宣言の「型 配列名[要素数];」とはちょっと順序が違うが、似たような感じだ。
「std::deque<型> オブジェクト名;」には要素数の指定が無いじゃないかと気づいた人は観察力がある。要素数の指定方法は次節で説明する。

このようにして宣言したオブジェクトの要素数は0だ。データを追加するには次の章で説明する push_back() などを使う。

< > は何だ?と思う人もいるかもしれない。これはC++のテンプレートという機能を使うための記法だ。
テンプレートを完全に理解するのは難易度が高いので、深く理解しようとすると先に進めなくなる。 深入りはせず、現在のところはこういう書き方をすると覚えてほしい。

要素数を指定した宣言

両端キューは後でサイズを増減させることが出来るが、データ領域の確保・破棄とデータのコピー処理を伴う場合があり、若干の処理時間を要する。
なので、最初の要素数が分かっている場合は、要素数を指定してオブジェクトを作るようにした方がよい。
構文は「std::deque<型> オブジェクト名(要素数);」だ。

    std::deque<int> data(123);  //  int 型で、初期要素数 123 の両端キュー data の宣言

配列では要素数を「[要素数]」で指定するが、deque では「(要素数)」で指定する。実はこれ、コンストラクタの引数なのである。

要素の初期化

要素数を指定するだけでなく、全要素の値を指定して初期化することも可能だ。
「std::deque<型> オブジェクト名(要素数, 値);」と記述すると、指定された数の要素を確保し、全ての要素を指定された値で初期化する。

    std::deque<int> data(10, 5);      //  要素数10、全ての要素の値5 で初期化

上記の方法では、値が全て同じ場合でないと初期化できない。
「std::deque<型> オブジェクト名(型* first, 型* last);」と記述すると、first から last が指す先までのデータで両端キューを初期化する。 厳密に言うと、last は最後の元データの次を指す。[first, last) の範囲を元に、両端キューを初期化する。
通常配列でデータを指定し、それを元に両端キューを構築し初期化出来る。

    int org_data[] = {4, 6, 5};      // 元データ
    std::deque<int> data(org_data, org_data + 3);     // 元データから両端キューを生成

配列末尾へのポインタは org_data + 3 と記述しているが、データ配列サイズが変わった時に、修正しなくてはならないので、危険だ。
C++0x(VS2010以上)で std::end(配列名) という関数が追加され、配列末尾へのポインタを返してくれるようになった。
これを使えば、上記は下記のように記述することが出来る。

    int org_data[] = {4, 6, 5};      // 元データ
    std::deque<int> data(org_data, std::end(org_data));     // 元データから両端キューを生成

実は上記の記述方法は古い書き方で、C++11(VS2013以上)から deque の初期化で初期値を直接指定可能になった。
「std::deque<型> オブジェクト名{値0, 値1, 値2, ... 値N-1};」と記述できる。

    std::deque<int> data{4, 6, 5};     // データ列を指定して両端キューを生成

こちらの方法の方が直感的で分かりやすく、行数も短い。C++11 が利用可能であれば、この記法を使うことを強く薦める。

※ ちなみに、「std::deque<型> オブジェクト名 = {値0, 値1, 値2, ... 値N-1};」と書いてもよい。

コピーコンストラクタ

コピーコンストラクタとは、同じ型のオブジェクトを渡され、それと同じ内容のオブジェクトを生成するコンストラクタのことである。
「std::deque<型> オブジェクト名(コピー元オブジェクト名);」と記述する。
当然ながら、コピー元オブジェクトと生成するオブジェクトは通常同じ型である。
※ 暗黙の型変換が可能な型であれば、コピー元オブジェクトとして指定可能ではある。

    std::deque<int> org{1, 2, 3};
    std::deque<int> x(org);      // コピーコンストラクタ

上記のコードは org をコピーするので {1, 2, 3} という値をもつ両端キュー x を生成する。

演習問題

  1. int型、要素数5 の両端キュー dq を宣言しなさい。
  2.     std::deque<int> dq(5);
    
  3. string型、要素数10 の両端キュー dq2 を宣言しなさい。
  4.     std::deque<std::string> dq2(10);
    
  5. int型、要素数5 の両端キュー dq3 を {1, 2, 3, 4, 5} で初期化しなさい。
  6.     std::deque<int> dq3{1, 2, 3, 4, 5};
    

値の参照、代入

ランダム・アクセス

[] 演算子(operator[](int)) を使って、普通の配列や std::vector と同じように、インデックスを指定しての配列要素値の参照・代入が可能。

[] 演算子を使ったランダム・アクセスの所要時間は O(1) だ。これは通常配列同様に非常に高速ということだ。

下記は、配列要素値の参照と代入例。普通の配列要素の参照・代入とまったく同じでしょ。

    std::deque<int> data{3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
    for (int i = 0; i < 10; ++i)
        std::cout << data[i];      //  data の i 番目の要素を表示
    const int SZ = 10;          // 要素数
    std::deque<int>v(SZ);        // 指定要素数で、deque を生成
    for(int i = 0; i < SZ; ++i)
        v[i] = i;             // 要素を 0, 1, 2, 3, ... 9 に設定

最初と最後の要素

コンテナの最初と最後の要素は頻繁に参照されるので、front(), back() が用意されている。
front() はあまりありがたみが無いが、back() はタイプ数が大幅に減るし、分かりやすいので存在価値が高いぞ。

    std::deque<int> dq{3, 1, 4, 1, 5};
    std::cout << dq.front() << "\n";      //  最初の要素(3)を表示
    std::cout << dq.back() << "\n";      //  最後の要素(5)を表示

実は、front()・back() は値ではなく参照を返すので、値を代入することで、最初・最後の要素の値を変更することも出来るぞ。

    std::deque<int> dq{3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
    dq.front() = 123;
    dq.back() = 987;
    std::cout << dq.front() << "\n";      //  最初の要素を表示
    std::cout << dq.back() << "\n";      //  最後の要素を表示

イテレータ

deque, vector, list 等のコンテナクラスにはイテレータというものが用意されている。

イテレータとは抽象化されたポインタのことである。
イテレータはコンテナの要素を指し、*演算子でイテレータが指す要素を参照・変更することができ、 インクリメント・デクリメントで次・前の要素に移動することができる。

deque<T> のイテレータの型は deque<T>::iterator である。ちなみに vector であれば vector<T>::iterator だ。

    std::deque<int>::iterator itr;     // deque<int> の要素へのイテレータ

begin() は最初の要素への、end() は最後の要素の次へのイテレータを返す。 これらを使って、イテレータを初期化し、イテレータを介して要素を参照・変更する。

    std::deque<int> v{1, 2, 3, 4};
    std::deque<int>::iterator itr = v.begin();   // 最初の要素を指すイテレータ
    std::cout << *itr << "\n";      // 最初の要素の値を表示
    ++itr;   // 次の要素に移動
    *itr = 9;    // 二番めの要素の値を 9 に変更

std::deque<int>::iterator とタイプするのは面倒なので、通常は型推論の auto を使用する。

    std::deque<int> v{1, 2, 3, 4};
    v に要素を格納;
    auto itr = v.begin();   // 最初の要素を指すイテレータ

イテレータを使って、deque v の要素を全て走査するには以下のように記述する。

    for(auto itr = v.begin(); itr != v.end(); ++itr) {
        *itr でイテレータの指す要素を参照
    }

演習問題:

  1. int型で、100個の要素を持つ両端キューを作り、各要素を [0, 99] の範囲の乱数で満たすコードを書きなさい。
  2.     std::deque<int> v(100);
        for(int i = 0; i < 100; ++i)
            v[i] = rand() % 100;
    
  3. 上記で生成した配列の中身を全て表示するコードを書きなさい。
  4.     for(int i = 0; i < 100; ++i)
            std::cout << v[i] << "\n";
    

まとめ:

  • 通常の配列と同じように [] 演算子が使用可能
  • v[i] で i 番目(0 オリジン)の要素を参照可能

データの追加

  • push_front() : void
  • push_back() : void
  • emplace_front(コンストラクタ引数) : void
  • emplace_back(コンストラクタ引数) : void
  • insert(iterator, 値) : iterator

push_front() : void、push_back() : void

データを先頭に挿入するには push_front(値); を使う。

    std::deque<int> v;       // 空の両端キューを生成
    v.push_front(123);     // 先頭に 123 を追加

データを末尾に追加するには push_back(値); を使う。

    std::deque<int> v;       // 空の両端キューを生成
    v.push_back(789);     // 末尾に 789 を追加

どちらも処理時間は O(1) で高速。O(式) は処理時間を表す数学的な記法。「ビッグ・オー記法」と呼ばれる。 O(1) は配列要素数に依らず常に一定時間で処理出来るという意味で、高速なのだ。
通常、データの追加はこのメンバ関数のみを使用する。

emplace_front(コンストラクタ引数) : void、emplace_back(コンストラクタ引数) : void

コンテナが保持する型Tがクラスの場合、新規クラスオブジェクトをコンテナ末尾に追加するには、下記のように記述する。

    v.push_back(T(コンストラクタ引数));

こう書くと、push_back コール時にクラスTのコンストラクタが呼ばれ、オブジェクトが生成される。 そして、push_back() の中で、コピーコンストラクタに渡されて生成されたオブジェクトがコンテナに追加され、push_back() コール時に生成されたオブジェクトは破棄される。
コピーコンストラクタ、オブジェクト破棄の処理が重いクラスの場合、このような無駄な処理はパフォーマンス低下を招く場合がある。

そこで C++11 で導入されたのが emplace_front()・emplace_back() だ。
引数にコンテナが保持するクラスのコンストラクタ引数を指定すると、余分なテンポラリオブジェクトを生成することなく(生成しないので当然破棄することもなく) オブジェクトを生成し、コンテナの最後に追加してくれるぞ。

例えば、コンストラクタが文字列と整数を引数にとるクラス Hoge がある場合、末尾にデータを追加する処理は以下のように記述出来る。

class Hoge {
public:
    Hoge(std::string&, int);       //  コンストラクタ
    .....
};
    .....
    std::deque<Hoge> v;
    v.emplace_back("abc", 123);   // push_back(Hoge("abc", 123)) と同意

insert(iterator, 値) : iterator

なにがなんでも任意の場所にデータを挿入したい場合は insert(iterator, 値) を使う。
insert(iterator, 値) は、第1引数に挿入する場所へのイテレータを、第2引数に挿入するデータを指定する。
イテレータとは抽象化されたポインタのこと。ちゃんとした説明は長くなるし、初級者には理解が大変なのでここでは省略する。
i 番目に挿入したい場合は「オブジェクト名.begin() + i」と書くと覚えて欲しい。

    std::deque<int> v(10, 3); // 値が3,個数10個
    v.insert(v.begin() + 4, 7);       //  [4] の位置に 7 を挿入
    // 結果は {3, 3, 3, 3, 7, 3, 3, 3, 3, 3, 3} となる

※ 個人的には insert(size_t index, 値) というメンバ関数があってもよいと考えるが、何故無いのだろう?

insert() の処理時間は O(N) 。O(N) とは配列要素数に比例して処理時間を要するという意味。 データ数が100倍になると、処理時間も100倍になる。
データをずらす処理を行う必要があるので、その分処理時間を食うというわけだ。
それに対して push_back() は O(1) なので、データ数がいくら増えても常に一定時間で処理が終わる。

データ列の途中に頻繁に挿入・削除を行うのであれば std::list や、ギャップベクターなど他のコンテナクラスの使用を検討すべき。

演習問題:

  1. 空の int型 deque オブジェクトを生成し、末尾に 1 ~ 10 のデータを追加するコードを書きなさい。
  2.     std::deque<int> v;
        for(int i = 1; i <= 10; ++i)
            v.push_back(i);
    
  3. 空の int型 deque オブジェクトを生成し、先頭に 1 ~ 10 のデータを順に追加するコードを書きなさい。
  4.     std::deque<int> v;
        for(int i = 1; i <= 10; ++i)
            v.push_front(i);
    
  5. 初期値 0 で、10個の int型 deque オブジェクトを生成し、[5] の位置に 7 を挿入するコードを書きなさい。
  6.     std::deque<int> v(10, 0);
        v.insert(v.begin() + 5, 7);
    

まとめ:

  • オブジェクト.push_front(値) で先頭に値を挿入
  • オブジェクト.push_back(値) で末尾に値を追加
  • オブジェクト.emplace_front(コンストラクタ引数) で先頭に値を挿入
  • オブジェクト.emplace_back(コンストラクタ引数) で末尾に値を追加
  • オブジェクト.insert(イテレータ, 値) で任意の位置に値を挿入可能
    • ただし insert() の処理時間は O(N) なので、要素数が膨大な場合は注意

データの削除

  • pop_front() : void
  • pop_back() : void
  • erase(iterator) : iterator
  • erase(first, last) : iterator

pop_front() : void、pop_back() : void

最初の要素を削除したい場合は pop_front() を用いる。

    std::deque<int> v{3, 1, 4, 1, 5};
    v.pop_front();    //  先頭データ(この場合は 3)を削除

最後の要素を削除したい場合は pop_back() を用いる。

    std::deque<int> v{3, 1, 4, 1, 5};
    v.pop_back();    //  末尾データ(この場合は 5)を削除

pop_front(), pop_back() も push_front(), push_back() 同様、処理時間は O(1) と高速である。

空の deque に対して、pop_front(), pop_back() を実行するとデバッグモードでは例外が発生する。 リリースモードでは内容が不正になるようだ(VS2013)。
次の章で説明する empty() または size() で deque が空でないことを確認するようにした方がよい。

pop_font(), pop_back() は何故か void 型で、削除した値を返さない。 なので、最初・最後の要素の値を取り出して削除したい場合は、front()・back() を使用する。

    std::deque<int> v{3, 1, 4, 1, 5};
    int first = v.front();     // 先頭データを取り出しておく
    v.pop_front();            //  先頭データ(この場合は 3)を削除

erase(iterator) : iterator

任意の位置の要素を削除したい場合は erase(iterator) を使用する。

    std::deque<int> v{3, 1, 9, 4};
    v.erase(v.begin() + 2);       //  3番目の要素(9)を削除

erase() で途中の要素を削除すると、サイズが一つ減り、削除された要素以降のデータがひとつずつ前に移動される。
※ 要素数が膨大な場合、データをずらすにはそれなりの時間が必要なので、注意すること。

イテレータは前節で言及したように、削除する要素位置を示すもので、i 番目の要素は「オブジェクト名.begin() + i」で指定する。

erase(first, last) : iterator

deque の一部である [first, last) を削除する場合、その間の要素をひとつづつ erase(iterator) で消さなくても、erase(first, last) で一度に削除することができるぞ。
削除の処理時間は O(N) なので、M文字を削除するのにループを使うと O(N*M) になってしまうが、erase(first, last) を使えば O(N) で済むぞ。

下記は、v の2, 3番目の要素を一度に削除する例だ。

    std::deque<int> v{3, 1, 4, 1, 5};
    v.erase(v.begin() + 1, v.begin() + 3);       // 1, 4 を削除

演習問題:

  1. pop_front() で先頭を削除するコードを書き、デバッガで実行し、先頭データが削除されたことを確認しなさい。
  2.     std::deque<int> v{3, 1, 4, 1};
        v.pop_front();
    
  3. pop_back() で末尾を削除するコードを書き、デバッガで実行し、末尾データが削除されたことを確認しなさい。
  4.     std::deque<int> v{3, 1, 4, 1};
        v.pop_back();
    
  5. erase() で途中の要素を削除し、本当に削除されたことを確認するコードを書き、実行してみなさい。
  6.     std::deque<int> v{3, 1, 4, 1};
        v.erase(v.begin() + 2);   //  4 を削除
        for(int i = 0; i != v.size(); ++i) {
            std::cout << v[i] << "\n";
        }
    
  7. int 型の deque を {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} で初期化し、3 から 8 までを erase(first, last) を使って削除しなさい。
  8.     std::deque<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        v.erase(v.begin() + 2, v.begin() + 8);
    

まとめ:

  • オブジェクト.pop_front() で先頭要素を削除
  • オブジェクト.pop_back() で末尾要素を削除
  • オブジェクト.erase(イテレータ) で任意位置の要素を削除
    • ただし処理時間は O(N) なので、要素数が膨大な場合は注意

その他のメンバ関数

ここまで、両端キューの宣言、値の参照・代入・追加・削除方法について説明した。 それらを使えば通常配列を両端キューに置き換えることができるはずだ。
実は両端キューには上記以外にも便利な機能がメンバ関数としていくつも用意されている。 配列が空かどうかをチェックしたり、要素数を調べたり、サイズを変更することもできる。
これらは通常配列に対する明らかなアドバンテージである。

deque の状態を取得

  • empty() : bool
  • size() : size_t

empty() : bool

「bool empty()」は両端キューが空かどうかを判定する関数。空ならば true を、空でなければ false を返す。
次に出てくる size() を使って、size() == 0 と判定するのと同等だ。 が、コンテナクラスによっては size() 計算よりも empty() の方が高速な場合がある。 なので、deque などのコンテナクラスに対しては empty() を使うことが推奨されている。

    if( !v.empty() ) {
        // v が空でなければ、なんらかの処理を行う
    }

size() : size_t

「size_t size()」は、要素数を返す関数。
通常配列だと「sizeof(data)/sizeof(data[0])」のように記述しないと、要素数を取得できないが、 deque であれば「data.size()」と簡潔かつ分かりやすく書ける。

※ size_t はサイズを表す型で、符号なし整数の組み込み型である。ちなみに、sizeof() も size_t 型を返す。

    for(int i = 0; i != v.size(); ++i) {      // 全要素に対するループ
        .....
    }

deque の状態を変更

  • clear() : void
  • resize(サイズ) : void
  • reserve(サイズ) : void
  • swap(オブジェクト) : void
  • shrink_to_fit() : void

clear() : void

「clear()」は両端キューを空にする関数。
サイズを0にするだけ。メモリが解放されるわけではない。 メモリを解放したい場合は後で説明する shrink_to_fit() または swap() イディオムを使用する。

resize(サイズ) : void

「resize(size_t sz)」は要素数を指定サイズに設定する関数。
sz が現在のキャパシティを超えていれば、メモリがアロケートされ,データがコピーされる。
サイズが増えた部分の値を指定したい場合は、「resize(size_t sz, 値)」と、第2引数で値を指定する。
サイズが減っても、その分のメモリが解放されるわけではない。 不要になったメモリを解放したい場合は後で説明する shrink_to_fit() または swap() イディオムを使用する。

reserve(サイズ) : void

「reserve(size_t sz)」はキャパシティを指定する関数。
データを大量に追加する場合、メモリのアロケートとデータコピーが多く呼ばれる場合がある。 そうなるとパフォーマンスが低下する場合があるので、追加するデータの数が予めわかっていれば、reserve() でメモリを確保する方がパフォーマンス的に好ましい。

    std::deque<int> v;
    v.reserve(10000);      // 予め1万個分の領域を確保しておいた方が高速
    for(int i = 0; i < 10000; ++i)
        v.push_back(i);

swap(オブジェクト) : void

「swap(オブジェクト)」は引数で指定されたオブジェクトと内容を入れ替える関数。

    std::deque<int> v, z;
    v, z にデータを追加
    v.swap(z);     // v と z の内容を入れ替える

shrink_to_fit() : void

先に書いたように、clear() を行ってもメモリは解放されないので、C++11以前では以下のようなコードが使用されていた。

    std::deque<int> v;
    v にデータを追加;
    std::deque<int>().swap(v);     // 空のテンポラリオブジェクトを生成し、中身を入れ替える

「shrink_to_fit()」は、C++11 で追加された関数で、キャパシティを現在のサイズの値にし、余分なメモリを解放する関数。
C++11以前では、shrink_to_fit() がなかったので以下のようなイディオムが使用されていた。

    std::deque<int> v;
    v にデータを追加;
    std::deque<int>(v).swap(v);     // v をコピーしたテンポラリオブジェクトを生成し、中身を入れ替える

イテレータ

begin() : イテレータ

ベクターの最初の要素へのイテレータを返す。

end() : イテレータ

ベクターの最後の要素の次へのイテレータを返す。

全要素をイテレータで走査する場合、以下のように記述する。

    std::deque<int> v{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
    for(auto itr = v.begin(); itr != v.end(); ++itr) {
        *itr により要素を参照・変更
    }

cbegin() : const イテレータ

両端キューの最初の要素への const なイテレータを返す。

cend() : const イテレータ

両端キューの最後の要素の次への const なイテレータを返す。

全要素をイテレータで走査する場合、以下のように記述する。

    std::deque<int> v{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
    for(auto itr = v.cbegin(); itr != v.cend(); ++itr) {
        *itr により要素を参照;  // const なので要素を修正不可
    }

演習問題:

  1. std::deque<int> v; に対して、empty(), size() をコールし、それぞれの値を表示するコードを書きなさい。
  2.     std::deque<int> v;
        cout << v.empty() << "\n";
        cout << v.size() << "\n";
    
  3. std::deque<int> v{3, 1, 4}; に対して、empty(), size() をコールし、それぞれの値を表示するコードを書きなさい。
  4.     std::deque<int> v{3, 1, 4};
        cout << v.empty() << "\n";
        cout << v.size() << "\n";
    
  5. std::deque<int> v{3, 1, 4}; に対して、front(), back() をコールし、それぞれの値を表示するコードを書きなさい。
  6.     std::deque<int> v{3, 1, 4};
        cout << v.front() << "\n";
        cout << v.back() << "\n";
    
  7. std::deque<int> v{3, 1, 4}; に対して、clear() をコールし、empty(), size() の値を表示するコードを書きなさい。
  8.     std::deque<int> v{3, 1, 4};
        v.clear();
        cout << v.empty() << "\n";
        cout << v.size() << "\n";
    
  9. std::deque<int> v{3, 1, 4}; に対して、resize(8) をコールし、empty(), size(), 各要素の値を表示するコードを書きなさい。
  10.     std::deque<int> v{3, 1, 4};
        v.resize(8);
        cout << v.empty() << "\n";
        cout << v.size() << "\n";
    
  11. std::deque<int> v{3, 1, 4}; に対して、resize(1) をコールし、empty(), size(), 各要素の値を表示するコードを書きなさい。
  12.     std::deque<int> v{3, 1, 4};
        v.resize(1);
        cout << v.empty() << "\n";
        cout << v.size() << "\n";
    
  13. std::deque<int> v{3, 1, 4}; に対して、reserve(8) をコールし、empty(), size(), 各要素の値を表示するコードを書きなさい。
  14.     std::deque<int> v{3, 1, 4};
        v.reserve(8);
        cout << v.empty() << "\n";
        cout << v.size() << "\n";
    
  15. std::deque<int> v{3, 1, 4}; に対して、reserve(1) をコールし、empty(), size(), 各要素の値を表示するコードを書きなさい。
  16.     std::deque<int> v{3, 1, 4};
        v.reserve(1);
        cout << v.empty() << "\n";
        cout << v.size() << "\n";
    
  17. std::deque<int> v{3, 1, 4}; に対して、resize(1); shrink_to_fit() をコールし、empty(), size(), 各要素の値を表示するコードを書きなさい。
  18.     std::deque<int> v{3, 1, 4};
        v.resize(1);
        cout << v.empty() << "\n";
        cout << v.size() << "\n";
        v.shrink_to_fit();
        cout << v.empty() << "\n";
        cout << v.size() << "\n";
    
  19. std::deque<int> v{3, 1, 4}, z{8, 9}; に対して、v.swap(z); を実行し、それぞれの内容を表示するコードを書きなさい。
  20.     std::deque<int> v{3, 1, 4};
        std::deque<int> z{8, 9};
        v.swap(z);
        for(int i = 0; i < (int)v.size(); ++i)
            std::cout << v[i] << "\n";
        for(int i = 0; i < (int)z.size(); ++i)
            std::cout << z[i] << "\n";
    

まとめ:

  • オブジェクト.empty() で要素が空かどうかを判定
  • オブジェクト.size() で要素数を取得
  • オブジェクト.front() で最初の要素を取得
  • オブジェクト.back() で最後の要素を取得
  • オブジェクト.data() で要素格納領域アドレスを取得
  • オブジェクト.clear() で要素を空にする。メモリの解放は行われない。
  • clear() 後にメモリ解放を行い対場合は、オブジェクト.shrink_to_fit() をコール
  • オブジェクト.resize(SZ) で、要素数を指定
  • オブジェクト.reserve(SZ) で、キャパシティを指定
  • オブジェクト.swap(オブジェクト2) で、オブジェクト、オブジェクト2 の要素を全交換

参考