シーケンスコンテナ
STLコンテナ型その1
シーケンスコンテナはSTLコンテナの一種です。
以下のコンテナクラスがシーケンスコンテナに分類されます。
コンテナ名 | 説明 | ヘッダファイル |
---|---|---|
array | 固定長配列 要素数の増減不可 |
<array> |
vector | 動的配列 末尾への挿入/削除が高速 |
<vector> |
deque | 両端キュー 先頭または末尾への挿入/削除が高速 |
<deque> |
list | 双方向リスト 挿入/削除が高速 ランダムアクセス不可 |
<list> |
forward_list | 単方向リスト 挿入/削除が高速 ランダムアクセス不可 イテレータの後方移動不可 ( -- 演算ができない) |
<forward_list> |
シーケンスコンテナは要素の並び順をプログラマが任意に制御できます。
これらはさらに「配列タイプ」と「リストタイプ」に分類できます。
配列タイプ
arrayクラス、vectorクラス、dequeクラスは配列タイプです。
これらのクラスはメモリ上の要素の並びが配列のように連続しているのが特徴です。
arrayクラスのみ、要素の追加や削除はできず要素数は固定です。
以下の説明中での要素の追加や削除についてはarrayクラスは除きます。
ランダムアクセスは高速です。
要素の挿入/削除は低速です。
要素の検索は低速です。
ただし、vectorクラスは末尾への追加/削除は高速です。
dequeクラスは先頭と末尾への追加/削除は高速です。
vectorクラスに要素を挿入すると、挿入位置から後方にあるすべての要素が後ろにずらされます。
末尾に挿入(追加)した場合は後ろにずらす処理がないので高速ですが、先頭への挿入は全ての要素をずらすことになるため、先頭に近い位置への挿入ほど低速になります。
dequeクラスはvectorクラスとほぼ同じものとして使用できます。
先頭要素への要素追加/削除がvectorクラスよりも高速ですが、その代わりに内部構造が複雑化しており、その他の速度はvectorクラスにわずかに劣ります。
arrayクラスとvectorクラスは先頭要素から末尾要素までメモリ上に連続して配置されることが保証されており、先頭要素からのポインタ演算で各要素にアクセスできます。
dequeクラスはこのような並び方である保証はなく、要素アクセスにはイテレータを使用する必要があります。
リストタイプ
listクラス、forward_listクラスはリストタイプです。
ランダムアクセスはできません。
要素の挿入/削除は高速です。
要素の検索は低速です。
ランダムアクセスができないというのは、イテレータへのit[3]
や(it + 3)
などの演算で要素アクセスができないということです。
リストタイプは各要素のメモリ上の配置位置に決まりはなく、各要素が自分自身の前後の要素のアドレスを格納することでリスト全体を構成しています。
そのため各要素へのアクセスは先頭/末尾から順に要素をたどっていくしかありません。
その代わりに要素の挿入/削除はアドレス情報の書き換えだけで済むため高速に処理できます。
forward_listクラスはイテレータに--
演算が出来ません。
つまり前方向に進む事しかできず、後方向(並び順で言えば手前)に戻ることはできません。
後方要素の情報を持たないため、その分listクラスよりもメモリ効率が良いです。
速度比較
各シーケンスコンテナの速度と簡単な特徴のまとめです。
◎=高速、△=低速、-=使用不可
クラス | ランダム アクセス |
挿入/削除 | 検索 | 備考 |
---|---|---|---|---|
array | ◎ | - | △ | 要素の増減不可 |
vector | ◎ | △ | △ | 末尾要素の挿入/削除は高速 先頭付近ほど挿入/削除の速度低下 |
deque | ◎ | △ | △ | 先頭/末尾要素の挿入/削除は高速 その他の操作はvectorにやや劣る |
list | - | ◎ | △ | |
forward_list | - | ◎ | △ | 後方移動不可 その分メモリ効率に優れる |
共通のメンバ関数
シーケンスコンテナで共通するメンバ関数の対応一覧です。
一覧にない、コンテナクラス固有のメンバ関数は後述します。
array | vector | deque | list | forward_list | 説明 | |
---|---|---|---|---|---|---|
イテレータ | ||||||
begin | ○ | ○ | ○ | ○ | ○ | 先頭要素のイテレータ取得 |
cbegin | ○ | ○ | ○ | ○ | ○ | 先頭要素のconstイテレータ取得 (C++11) |
end | ○ | ○ | ○ | ○ | ○ | 末尾要素の次のイテレータ取得 |
cend | ○ | ○ | ○ | ○ | ○ | 末尾要素の次のconstイテレータ取得 (C++11) |
rbegin | ○ | ○ | ○ | ○ | × | 末尾要素の逆イテレータ取得 |
crbegin | ○ | ○ | ○ | ○ | × | 末尾要素のconst逆イテレータ取得 (C++11) |
rend | ○ | ○ | ○ | ○ | × | 先頭要素の手前の逆イテレータ取得 |
crend | ○ | ○ | ○ | ○ | × | 先頭要素の手前のconst逆イテレータ取得 (C++11) |
要素アクセス | ||||||
at operator[] |
○ | ○ | ○ | × | × | 任意の位置の要素へのアクセス |
data | ○ | ○ | × | × | × | 管理するメモリ領域へのポインタ取得 (C++11) |
front | ○ | ○ | ○ | ○ | ○ | 先頭要素への参照 |
back | ○ | ○ | ○ | ○ | × | 末尾要素への参照 |
容量 | ||||||
empty | ○ | ○ | ○ | ○ | ○ | コンテナが空か否かの判定 |
size | ○ | ○ | ○ | ○ | × | 要素数の取得 forward_listではstd::distance関数を使用する |
max_size | ○ | ○ | ○ | ○ | ○ | システムが格納可能な最大の要素数を取得 |
resize | × | ○ | ○ | ○ | ○ | 要素数の変更 |
shrink_to_fit | × | ○ | ○ | × | × | 余分なcapacityを縮小 (C++11) |
コンテナの変更 | ||||||
assign | × | ○ | ○ | ○ | ○ | コンテナへの代入 |
clear | × | ○ | ○ | ○ | ○ | 全ての要素を削除 |
insert | × | ○ | ○ | ○ | insert_after | 要素の挿入 |
emplace | × | ○ | ○ | ○ | emplace_after | 要素を構築して挿入 |
erase | × | ○ | ○ | ○ | erase_after | 要素を削除 |
push_front | × | × | ○ | ○ | ○ | 先頭に要素を追加 |
emplace_front | × | × | ○ | ○ | ○ | 先頭に要素を構築して追加 (C++11) |
push_back | × | ○ | ○ | ○ | × | 末尾に要素を追加 |
emplace_back | × | ○ | ○ | ○ | × | 末尾に要素を構築して追加 (C++11) |
pop_front | × | × | ○ | ○ | ○ | 先頭要素を削除 |
pop_back | × | ○ | ○ | ○ | × | 末尾要素を削除 |
swap | ○ | ○ | ○ | ○ | ○ | コンテナオブジェクトの入れ替え |
アロケーター | ||||||
get_allocator | × | ○ | ○ | ○ | ○ | アロケーターオブジェクトを取得 |
array | vector | deque | list | forward_list | 説明 |
arrayクラスやvectorクラスと共通の関数はそれぞれのページを参照してください。
イテレータについてはイテレータの項を参照してください。
以下は、上記のページでは説明していない関数について説明します。
以下のサンプルコードでは戻り値のデータ型を明示的に記述しているものもありますが、やたらと長いデータ型名が多いのでauto(型推論)を使用することをお勧めします。
decltypeはコンテナ型やデータ型に依存しない記述ができるので、auto
が使用できない場面ではこちらが便利です。
std::vector<int> vec;
//全て同じデータ型
std::vector<int>::iterator it1;
auto it2 = vec.begin();
decltype(vec)::iterator it3;
push_front、emplace_front関数
push_front
関数、emplace_front
関数は要素の先頭に新しい要素を追加します。
戻り値はありません。
#include <iostream>
#include <list>
int main()
{
std::list<int> lst{ 1, 2, 3 };
lst.push_front(0);
lst.emplace_front(-1);
for (auto e : lst) {
std::cout << e << " ";
}
//-1 0 1 2 3
}
push_front
関数とemplace_front
関数の違いは、引数から直接オブジェクトを生成できる点です。
#include <iostream>
#include <string>
#include <list>
class TestClass
{
int n;
std::string s;
public:
TestClass(int n, std::string s)
: n(n), s(s) {}
};
int main()
{
std::list<TestClass> lst;
//これでも可能だが
lst.emplace_front(TestClass(1, "abc"));
//こういう書き方もできる
lst.emplace_front(1, "abc");
//これはできない
//lst.push_front(1, "abc");
}
emplace系の関数は、実引数に目的のクラス(今回はTestClass
)のインスタンスを指定する以外にも、クラスのコンストラクタに渡す値を直接指定することでもインスタンスを生成でき、コンテナクラスに追加できます。
emplace_front
関数はpush_front
関数よりも高速に動作する可能性があります。
push_front
関数は「オブジェクトを生成して、それをコピーしたもの」が追加されますが、emplace_front
関数は「オブジェクトを生成したもの」が直接追加されます。
動作の違いについてはvectorクラスの項でも詳しく説明しているので参照してください。
→vectorクラス#push_back、emplace_back関数
push_back、emplace_back関数
push_back
関数、emplace_back
関数は要素の末尾に新しい要素を追加します。
追加位置が末尾である以外はpush_front関数、emplace_front関数と同じです。
pop_front、pop_back関数
pop_front
関数は先頭要素を削除します。
pop_back
関数は末尾要素を削除します。
引数や戻り値はありません。
listクラス、forward_listクラスのメンバ関数
listクラスとforward_listクラスには上記以外にもメンバ関数が存在します。
(vectorクラス固有のメンバ関数はvectorクラスの項で説明しています)
insert_after、emplace_after、erase_after関数(forward_list)
forward_listクラスでは、insert
関数はinsert_after
関数、emplace
関数はemplace_after
関数、erase
関数はerase_after
関数として提供されています。
vectorクラスなどのinsert
関数は指定の位置への要素の挿入を行いますが、forward_listクラスのinsert_after
関数は指定の位置の後ろに要素の挿入を行います。
emplace_after
関数、erase_after
関数も同様に指定の要素の後ろに対する操作となります。
forward_listは手前の要素に対する情報を持たないためこのような動作になります。
std::vector<int> vec{ 1, 2, 3, 4, 5 };
std::forward_list<int> flst{ 1, 2, 3, 4, 5 };
std::vector<int>::iterator itV = vec.begin();
std::forward_list<int>::iterator itF = flst.begin();
vec.insert(++itV, 9); //1番目に挿入
flst.insert_after(++itF, 9); //1番目の後ろに挿入
itV = vec.begin();
itF = flst.begin();
while (itV != vec.end())
{
std::cout << *itV++ << " ";
}
std::cout << std::endl;
while (itF != flst.end())
{
std::cout << *itF++ << " ";
}
1 9 2 3 4 5 1 2 9 3 4 5
merge関数
merge
関数は、ふたつのコンテナを結合します。
#include <iostream>
#include <list>
int main()
{
std::list<int> lst1 = { 1, 4, 7 };
std::list<int> lst2 = { 3, 6, 9 };
//マージ
//lst2は空になる
lst1.merge(lst2);
//C++11以降はstd::moveを使用したほうが高速
//lst1.merge(std::move(lst2));
for (int x : lst1) {
std::cout << x << " ";
}
//1 3 4 6 7 9
std::cin.get();
}
引数に指定されたコンテナの中身はすべて移動し、中身は空になります。
この関数の戻り値はありません。
merge
関数を実行するには、両方のコンテナがあらかじめ昇順で並べられている必要があります。
昇順に並んでいないコンテナに対してmerge
関数を実行した場合の並び順は保証されません。
要素の並べ替えを行わずに、単純に結合する場合はinsert
関数やsplice
関数を使用します。
std::list<int> lst1 = { 1, 4, 7 };
std::list<int> lst2 = { 3, 6, 9 };
//lst1の末尾にlst2の先頭から末尾までをコピーして追加
//lst2の中身は残る
lst1.insert(lst1.end(), lst2.begin(), lst2.end());
//1 4 7 3 6 9
//lst1の末尾にlst2を移動して追加
//lst2は空になる
lst1.splice(lst1.end(), lst2);
//1 4 7 3 6 9 3 6 9
splice関数、splice_after関数
splice
関数(listクラス)、splice_after
関数(forward_listクラス)は、任意の位置に他のコンテナの要素を移動します。
{
std::list<int> a = { 1, 2, 3 };
std::list<int> b = { 7, 8, 9 };
//aの0番目(先頭)にbを移動
//移動なのでbは空になる
a.splice(a.begin(), b);
//7 8 9 1 2 3
//C++11以降はstd::moveを使用したほうが効率が良い
//lst1.splice(lst1.begin(), std::move(lst2));
}
{
std::list<int> a = { 1, 2, 3 };
std::list<int> b = { 7, 8, 9 };
//aの2番目直前にbの1番目の要素を移動
a.splice(
std::next(a.begin(), 2),
std::move(b),
std::next(b.begin())
);
//1 2 8 3
b;
//7 9
}
{
std::list<int> a = { 1, 2, 3 };
std::list<int> b = { 7, 8, 9 };
//aの2番目直前にbの0番目から2番目の直前までの要素を移動
a.splice(
std::next(a.begin(), 2),
std::move(b),
b.begin(),
std::next(b.begin(), 2)
);
//1 2 7 8 3
b;
//9
}
この関数の戻り値はありません。
なお、splice_after
関数は指定の要素の後ろへの移動となります。
remove関数
remove
関数は、コンテナから引数に一致する要素を削除します。
std::list lst = { 1, 2, 3, 1, 4, 5, 1 };
//「1」を削除
lst.remove(1);
//2 3 4 5
C++17まではこの関数の戻り値はありません。
C++20からは削除された要素数を返します。
remove_if関数
remove_if
関数は、コンテナから引数に指定した条件に一致する要素を削除します。
「条件」には引数ひとつ、boo型を戻り値とする関数を指定します。
関数をわざわざ定義するのは面倒なので、C++11からはラムダ式を指定する方法がよく用いられます。
#include <list>
//引数が3未満なら真を返す関数
bool pred(int x)
{
return x < 3;
}
int main()
{
std::list<int> lst = { 1, 2, 3, 1, 4, 5, 1 };
//関数predの条件で要素を削除
lst.remove_if(pred);
//3 4 5
lst = { 1, 2, 3, 1, 4, 5, 1 };
//ラムダ式で同様の記述を行う場合
lst.remove_if([](int x) { return x < 3; });
//3 4 5
}
C++17まではこの関数の戻り値はありません。
C++20からは削除された要素数を返します。
sort関数
sort
関数は、コンテナの要素を昇順に並べ替えます。
std::list<int> lst = { 1, 4, 2, 9 };
//昇順に並べ替える
lst.sort();
//1 2 4 9
この関数の戻り値はありません。
降順に並べ替え
sort
関数はデフォルトでstd::less<データ型>()
という関数オブジェクトが並べ替えに使用されます。
これは昇順での並べ替えを行います。
降順での並べ替えはstd::greater<データ型>()
を引数に指定することで行えます。
std::list<int> lst = { 1, 4, 2, 9 };
lst.sort(std::greater<int>());
//9 4 2 1
std::greater
は<functional>
ヘッダファイルに定義があるので、使用できない場合はこれをインクルードしてください。
並べ替えの比較関数
list(forward_list)で自作のクラスや構造体を管理している場合、そのままでは並べ替えを行うことはできません。
これはオブジェクトの大小を比較する手段がないからです。
sort
関数の引数に比較のための関数(ラムダ式でも可)を指定することで、独自のルールで並べ替えを行うことができます。
#include <list>
class TestClass
{
int x;
int y;
public:
TestClass(int x = 0, int y = 0)
: x(x), y(y) {}
//インスタンス同士の順序の比較関数
//これは昇順で並べ替えている
static bool comp(const TestClass& a, const TestClass& b) {
if (a.x < b.x) {
return true;
}
else if (a.x > b.x) {
return false;
}
return (a.y < b.y);
}
};
int main()
{
std::list<TestClass> lst = {
TestClass(1, 2),
TestClass(3, 5),
TestClass(2, 2),
TestClass(3, 4),
};
//TestClass::comp関数のルールで並び替える
lst.sort(TestClass::comp);
//(x = 1, y = 2)
//(x = 2, y = 2)
//(x = 3, y = 4)
//(x = 3, y = 5)
}
比較関数は以下の定義で行います。
bool cmp(const T& a, const T& b);
戻り値はbool型で、関数名や引数名は自由です。
a
がb
よりも小さい場合はtrue
を返すようにすると昇順で並べ替えられます。
これを反対にすると降順となります。
上記コードは、メンバ変数x
の値で並べ替えを行い、x
の値が同じ場合はメンバ変数y
の値で並べ替えを行っています。
<、>演算子のオーバーロード
独自の並べ替えルールは<
または>
演算子のオーバーロードでも定義することができます。
#include <list>
class TestClass
{
int x;
int y;
public:
TestClass(int x = 0, int y = 0)
: x(x), y(y) {}
//「<」演算子のオーバーロード
bool operator<(const TestClass& r) const
{
if (this->x < r.x) {
return true;
}
else if (this->x > r.x) {
return false;
}
return (this->y < r.y);
}
//「>」演算子のオーバーロード
bool operator>(const TestClass& r) const
{
if (this->x > r.x) {
return true;
}
else if (this->x < r.x) {
return false;
}
return (this->y > r.y);
}
};
int main()
{
std::list<TestClass> lst = {
TestClass(1, 2),
TestClass(3, 5),
TestClass(2, 2),
TestClass(3, 4),
};
//昇順に並べ替える
//オーバーロードした<演算子が比較に使用される
lst.sort(); //std::less
//(x = 1, y = 2)
//(x = 2, y = 2)
//(x = 3, y = 4)
//(x = 3, y = 5)
//降順に並べ替える
//オーバーロードした>演算子が比較に使用される
lst.sort(std::greater<TestClass>());
//(x = 3, y = 5)
//(x = 3, y = 4)
//(x = 2, y = 2)
//(x = 1, y = 2)
}
std::less
は<
演算子、std::greater
は>
演算子を使用します。
演算子オーバーロードはconst関数にする必要があります。
reverse関数
reverse
関数は、コンテナの要素を前後逆に並べ替えます。
std::list<int> lst = { 1, 4, 2, 9 };
//前後を逆に並べ替える
lst.reverse();
//9 2 4 1
この関数の戻り値はありません。
unique関数
unique
関数は、コンテナの重複する要素を削除します。
std::list<int> lst = { 1, 4, 5, 2, 4, 0, 5 };
lst.sort();
lst.unique();
//0 1 2 4 5
unique
関数を実行するコンテナは、あらかじめ昇順または降順で並べられている必要があります。
そうでない場合、どのような結果になるかはコンパイラ依存です。
この関数の戻り値はありません。
C++20からは削除された要素数が返されます。
一致の比較関数
unique
関数は、1番目の要素と0番目の要素を比較、2番目の要素と1番目の要素を比較…という処理を終端まで行い、一致した要素を削除します。
あらかじめ並び替えられていないと正しい結果が得られないのはこのためです。
自作のクラスや構造体の場合は==
演算子をオーバーロードすることでunique
関数を使用することができます。
その他、比較関数を独自に定義することもできます。
(ラムダ式でも可)
#include <list>
class TestClass
{
int x;
int y;
public:
TestClass(int x = 0, int y = 0)
: x(x), y(y) {}
//sort関数用
bool operator<(const TestClass& r)
{
if (this->x < r.x) {
return true;
}
else if (this->x > r.x) {
return false;
}
return (this->y < r.y);
}
//==演算子のオーバーロード
bool operator==(const TestClass& r) {
return (this->x == r.x && this->y == r.y);
}
//インスタンス同士の一致の比較関数
static bool equals(const TestClass& a, const TestClass& b) {
return (a.x == b.x && a.y == b.y);
}
};
int main()
{
std::list<TestClass> lst = {
TestClass(1, 2),
TestClass(3, 5),
TestClass(1, 2),
TestClass(3, 4),
};
lst.sort();
//比較関数を使用する
lst.unique(TestClass::equals);
//(x = 1, y = 2)
//(x = 3, y = 4)
//(x = 3, y = 5)
//==演算子のオーバーロードを使用する
lst.unique();
}