C++
std :: vector
サーチ…
前書き
ベクトルは、自動的に処理される記憶域を持つ動的配列です。ベクトル内の要素には、配列内の要素と同じように効率的にアクセスすることができ、ベクトルのサイズを動的に変更できる利点があります。
記憶の点では、ベクトルデータは(通常は)動的に割り当てられたメモリに配置されるため、わずかなオーバヘッドが必要です。逆にC-arrays
とstd::array
は、宣言された場所に相対的な自動ストレージを使用するためオーバーヘッドはありません。
備考
std::vector
使用するには、 #include <vector>
を使用して<vector>
ヘッダを含める必要があります。
std::vector
要素はフリーストアに連続して格納されます。 std::vector<std::vector<int> >
がstd::vector<std::vector<int> >
ようにネストされている場合、各ベクトルの要素は連続していますが、各ベクトルはフリーストアに独自の基本バッファを割り当てます。
std :: vectorの初期化
std::vector
は、宣言中にいくつかの方法で初期化することができます:
std::vector<int> v{ 1, 2, 3 }; // v becomes {1, 2, 3}
// Different from std::vector<int> v(3, 6)
std::vector<int> v{ 3, 6 }; // v becomes {3, 6}
// Different from std::vector<int> v{3, 6} in C++11
std::vector<int> v(3, 6); // v becomes {6, 6, 6}
std::vector<int> v(4); // v becomes {0, 0, 0, 0}
あるベクトルは、いくつかの方法で別のコンテナから初期化できます。
v2
データをコピーする構造体を別のベクターのみからコピーします。
std::vector<int> v(v2);
std::vector<int> v = v2;
v2
からデータを移動する構造体を別のベクトルのみから移動する:
std::vector<int> v(std::move(v2));
std::vector<int> v = std::move(v2);
要素をv
コピーするイテレーター(範囲)copy-construction:
// from another vector
std::vector<int> v(v2.begin(), v2.begin() + 3); // v becomes {v2[0], v2[1], v2[2]}
// from an array
int z[] = { 1, 2, 3, 4 };
std::vector<int> v(z, z + 3); // v becomes {1, 2, 3}
// from a list
std::list<int> list1{ 1, 2, 3 };
std::vector<int> v(list1.begin(), list1.end()); // v becomes {1, 2, 3}
要素をv
移動する、 std::make_move_iterator
を使用したイテレータの移動構築:
// from another vector
std::vector<int> v(std::make_move_iterator(v2.begin()),
std::make_move_iterator(v2.end());
// from a list
std::list<int> list1{ 1, 2, 3 };
std::vector<int> v(std::make_move_iterator(list1.begin()),
std::make_move_iterator(list1.end()));
assign()
メンバー関数の助けを借りて、 std::vector
は構築後に再初期化できます:
v.assign(4, 100); // v becomes {100, 100, 100, 100}
v.assign(v2.begin(), v2.begin() + 3); // v becomes {v2[0], v2[1], v2[2]}
int z[] = { 1, 2, 3, 4 };
v.assign(z + 1, z + 4); // v becomes {2, 3, 4}
要素を挿入する
ベクトルの最後に(コピー/移動によって)要素を追加する:
struct Point {
double x, y;
Point(double x, double y) : x(x), y(y) {}
};
std::vector<Point> v;
Point p(10.0, 2.0);
v.push_back(p); // p is copied into the vector.
要素をその場で構築することによって、ベクトルの最後に要素を追加する:
std::vector<Point> v;
v.emplace_back(10.0, 2.0); // The arguments are passed to the constructor of the
// given type (here Point). The object is constructed
// in the vector, avoiding a copy.
パフォーマンス上の理由から、 std::vector
はpush_front()
メンバ関数がないことに注意してください。最初に要素を追加すると、ベクトル内の既存の要素がすべて移動します。コンテナの先頭に頻繁に要素を挿入する場合は、代わりにstd::list
またはstd::deque
を使用することができます。
ベクトルの任意の位置に要素を挿入する:
std::vector<int> v{ 1, 2, 3 };
v.insert(v.begin(), 9); // v now contains {9, 1, 2, 3}
要素をその場で構築することによって、ベクトルの任意の位置に要素を挿入する:
std::vector<int> v{ 1, 2, 3 };
v.emplace(v.begin()+1, 9); // v now contains {1, 9, 2, 3}
ベクターの任意の位置に別のベクターを挿入する:
std::vector<int> v(4); // contains: 0, 0, 0, 0
std::vector<int> v2(2, 10); // contains: 10, 10
v.insert(v.begin()+2, v2.begin(), v2.end()); // contains: 0, 0, 10, 10, 0, 0
ベクトルの任意の位置に配列を挿入する:
std::vector<int> v(4); // contains: 0, 0, 0, 0
int a [] = {1, 2, 3}; // contains: 1, 2, 3
v.insert(v.begin()+1, a, a+sizeof(a)/sizeof(a[0])); // contains: 0, 1, 2, 3, 0, 0, 0
結果のベクトルサイズが事前にわかっている場合は、複数の要素を挿入する前にreserve()
使用して、複数の再割り当てを回避します( ベクトルサイズと容量を参照)。
std::vector<int> v;
v.reserve(100);
for(int i = 0; i < 100; ++i)
v.emplace_back(i);
この場合はresize()
を呼び出すことを間違えないでください。そうしないと、うっかり200個の要素を持つベクトルが作成され、後者の100個だけが意図した値になります。
繰り返し処理:: vector
いくつかの方法でstd::vector
反復処理できます。次の各セクションについて、 v
は以下のように定義されます。
std::vector<int> v;
前進方向の反復
// Range based for
for(const auto& value: v) {
std::cout << value << "\n";
}
// Using a for loop with iterator
for(auto it = std::begin(v); it != std::end(v); ++it) {
std::cout << *it << "\n";
}
// Using for_each algorithm, using a function or functor:
void fun(int const& value) {
std::cout << value << "\n";
}
std::for_each(std::begin(v), std::end(v), fun);
// Using for_each algorithm. Using a lambda:
std::for_each(std::begin(v), std::end(v), [](int const& value) {
std::cout << value << "\n";
});
// Using a for loop with iterator
for(std::vector<int>::iterator it = std::begin(v); it != std::end(v); ++it) {
std::cout << *it << "\n";
}
// Using a for loop with index
for(std::size_t i = 0; i < v.size(); ++i) {
std::cout << v[i] << "\n";
}
逆方向の反復
// There is no standard way to use range based for for this.
// See below for alternatives.
// Using for_each algorithm
// Note: Using a lambda for clarity. But a function or functor will work
std::for_each(std::rbegin(v), std::rend(v), [](auto const& value) {
std::cout << value << "\n";
});
// Using a for loop with iterator
for(auto rit = std::rbegin(v); rit != std::rend(v); ++rit) {
std::cout << *rit << "\n";
}
// Using a for loop with index
for(std::size_t i = 0; i < v.size(); ++i) {
std::cout << v[v.size() - 1 - i] << "\n";
}
反復を逆行させるための範囲を使用する組み込みの方法はありませんが、これを修正するのは比較的簡単です。 begin()
とend()
を使用してイテレータを取得し、ラッパーオブジェクトでこれをシミュレートすると、必要な結果が得られます。
template<class C>
struct ReverseRange {
C c; // could be a reference or a copy, if the original was a temporary
ReverseRange(C&& cin): c(std::forward<C>(cin)) {}
ReverseRange(ReverseRange&&)=default;
ReverseRange& operator=(ReverseRange&&)=delete;
auto begin() const {return std::rbegin(c);}
auto end() const {return std::rend(c);}
};
// C is meant to be deduced, and perfect forwarded into
template<class C>
ReverseRange<C> make_ReverseRange(C&& c) {return {std::forward<C>(c)};}
int main() {
std::vector<int> v { 1,2,3,4};
for(auto const& value: make_ReverseRange(v)) {
std::cout << value << "\n";
}
}
const要素の強制
C ++ 11以来、 cbegin()
とcend()
メソッドは、ベクトルが非constであっても、ベクトルの定数反復子を得ることを可能にします。定数イテレータを使用すると、ベクトルの内容を読み取ることはできますが、constの正確性を強制するのに便利です。
// forward iteration
for (auto pos = v.cbegin(); pos != v.cend(); ++pos) {
// type of pos is vector<T>::const_iterator
// *pos = 5; // Compile error - can't write via const iterator
}
// reverse iteration
for (auto pos = v.crbegin(); pos != v.crend(); ++pos) {
// type of pos is vector<T>::const_iterator
// *pos = 5; // Compile error - can't write via const iterator
}
// expects Functor::operand()(T&)
for_each(v.begin(), v.end(), Functor());
// expects Functor::operand()(const T&)
for_each(v.cbegin(), v.cend(), Functor())
as_constはこれを範囲の繰り返しに拡張します:
for (auto const& e : std::as_const(v)) {
std::cout << e << '\n';
}
これは、以前のバージョンのC ++で実装するのは簡単です:
template <class T>
constexpr std::add_const_t<T>& as_const(T& t) noexcept {
return t;
}
効率に関する注意
クラスstd::vector
は、基本的に動的に割り当てられた連続配列を管理するクラスであるため、 ここで説明したのと同じ原理がC ++ベクタに適用されます。行優先順位の原則に従うと、ベクトルの内容にインデックスでアクセスするほうがはるかに効率的です。もちろん、ベクトルへの各アクセスも同様にキャッシュに経営内容を置きますが、(特に何度も議論されてきたとして、こことここ )、を反復処理のためのパフォーマンスの違いstd::vector
生の配列に比べて無視できる。したがって、Cでの生の配列の効率の同じ原則は、C ++のstd::vector
も当てはまります。
要素へのアクセス
std::vector
要素にアクセスする主な方法は2つあります
- インデックスベースのアクセス
- イテレータ
インデックスベースのアクセス:
これは、添え字演算子[]
、またはメンバ関数at()
どちらかで行うことができます。
両方ともstd::vector
各位置の要素への参照を返します( vector<bool>
の場合を除きvector<bool>
)。これにより、読み込みと変更ができます(ベクトルがconst
でない場合)。
[]
とat()
という点で異なる[]
一方、任意の境界チェックを実行することが保証されていないat()
はありません。 index < 0
またはindex >= size
要素へのアクセスは[]
に対する未定義の動作で、 at()
はstd::out_of_range
例外をスローします。
注意:以下の例では、わかりやすくするためにC ++ 11スタイルの初期化を使用していますが、演算子はすべてのバージョンで使用できます(C ++ 11とマークされていない場合)。
std::vector<int> v{ 1, 2, 3 };
// using []
int a = v[1]; // a is 2
v[1] = 4; // v now contains { 1, 4, 3 }
// using at()
int b = v.at(2); // b is 3
v.at(2) = 5; // v now contains { 1, 4, 5 }
int c = v.at(3); // throws std::out_of_range exception
at()
メソッドは境界チェックを実行し、例外をスローすることができるため、 []
よりも遅くなります。これは、操作のセマンティクスがインデックスが境界内にあることを保証する[]
優先コードを作成します。いずれにしても、ベクトル要素へのアクセスは一定の時間内に行われます。つまり、ベクトルの最初の要素へのアクセスは、2番目の要素、3番目の要素などをアクセスするのと同じコスト(時間)を持ちます。
たとえば、このループを考えてみましょう
for (std::size_t i = 0; i < v.size(); ++i) {
v[i] = 1;
}
ここでは、インデックス変数ことを知っているi
境界内に常にあるので、ことを確認するためにCPUサイクルの無駄だろうi
すべての呼び出しのための境界内にあるoperator[]
。
front()
とback()
メンバー関数は、それぞれベクトルの最初の要素と最後の要素への参照を容易にします。これらの位置は頻繁に使用され、特殊アクセッサは[]
を使用した代替方法よりも読みやすくなり[]
。
std::vector<int> v{ 4, 5, 6 }; // In pre-C++11 this is more verbose
int a = v.front(); // a is 4, v.front() is equivalent to v[0]
v.front() = 3; // v now contains {3, 5, 6}
int b = v.back(); // b is 6, v.back() is equivalent to v[v.size() - 1]
v.back() = 7; // v now contains {3, 5, 7}
注 :空のベクトルに対してfront()
またはback()
を呼び出すことは未定義の動作です。 front()
またはback()
呼び出すfront()
、 empty()
メンバー関数(コンテナが空であるかどうかを調べるempty()
を使用してコンテナが空でないことを確認する必要があります。空のベクトルをテストするために 'empty()'を使用する簡単な例は次のとおりです:
int main ()
{
std::vector<int> v;
int sum (0);
for (int i=1;i<=10;i++) v.push_back(i);//create and initialize the vector
while (!v.empty())//loop through until the vector tests to be empty
{
sum += v.back();//keep a running total
v.pop_back();//pop out the element which removes it from the vector
}
std::cout << "total: " << sum << '\n';//output the total to the user
return 0;
}
上の例では、1から10までの数字のシーケンスを持つベクトルを作成します。次に、ベクトルが空になるまで( 'empty()'を使用して)未定義の動作を防ぐためにベクトルの要素をポップします。次に、ベクトル内の数字の合計が計算され、ユーザーに表示されます。
data()
メソッドはstd::vector
がその要素を内部的に格納するために使用する生のメモリへのポインタを返します。これは、Cスタイルの配列が必要なレガシーコードにベクターデータを渡すときに最もよく使用されます。
std::vector<int> v{ 1, 2, 3, 4 }; // v contains {1, 2, 3, 4}
int* p = v.data(); // p points to 1
*p = 4; // v now contains {4, 2, 3, 4}
++p; // p points to 2
*p = 3; // v now contains {4, 3, 3, 4}
p[1] = 2; // v now contains {4, 3, 2, 4}
*(p + 2) = 1; // v now contains {4, 3, 2, 1}
C ++ 11の前に、 data()
メソッドをシミュレートするには、 front()
を呼び出して戻り値のアドレスを取得します。
std::vector<int> v(4);
int* ptr = &(v.front()); // or &v[0]
これは、ベクタの内容が単項operator&
オーバーライドしないと仮定して、ベクタは常にその要素を連続したメモリ位置に格納することが保証されるためです。もしそうなら、pre-C ++ 11でstd::addressof
を再実装する必要があります。また、ベクトルが空ではないと仮定します。
イテレータ:
イテレーターについては、「 std::vector
繰り返し処理する」の例と記事のイテレーターで詳しく説明しています。つまり、ベクトルの要素へのポインタと同様に動作します。
std::vector<int> v{ 4, 5, 6 };
auto it = v.begin();
int i = *it; // i is 4
++it;
i = *it; // i is 5
*it = 6; // v contains { 4, 6, 6 }
auto e = v.end(); // e points to the element after the end of v. It can be
// used to check whether an iterator reached the end of the vector:
++it;
it == v.end(); // false, it points to the element at position 2 (with value 6)
++it;
it == v.end(); // true
std::vector<T>
のイテレータは実際に T*
sであることが標準と一致していますが、ほとんどの標準ライブラリはこれをしません。これを行わないと、エラーメッセージが改善され、移植性のないコードがキャッチされ、非リリースビルドのデバッグチェックでイテレータを計測するために使用できます。次に、リリースビルドでは、基になるポインタをラップするクラスが最適化されます。
間接アクセスのために、ベクトルの要素への参照またはポインタを永続化することができます。内の要素へのこれらの参照やポインタvector
安定を維持し、あなたが追加/時または内の要素の前の要素を削除しない限り、アクセスが定義されたままvector
、またはあなたが原因とvector
能力が変化します。イテレータを無効にするルールと同じです。
std::vector<int> v{ 1, 2, 3 };
int* p = v.data() + 1; // p points to 2
v.insert(v.begin(), 0); // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1; // p points to 1
v.reserve(10); // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1; // p points to 1
v.erase(v.begin()); // p is now invalid, accessing *p is a undefined behavior.
std :: vectorをC配列として使用する
std::vector
をC配列として使用するにはいくつかの方法があります(たとえば、Cライブラリとの互換性のため)。これは、ベクトルの要素が連続して格納されるために可能です。
std::vector<int> v{ 1, 2, 3 };
int* p = v.data();
以前のC ++標準(以下を参照)に基づくソリューションとは対照的に、メンバー関数.data()
は空のベクトルにも適用される可能性があります。
C ++ 11の前に、ベクトルの最初の要素のアドレスを取って同等のポインタを取得します。ベクトルが空でない場合、これらの両方のメソッドは互換性があります。
int* p = &v[0]; // combine subscript operator and 0 literal
int* p = &v.front(); // explicitly reference the first element
注:ベクトルが空の場合、 v[0]
およびv.front()
は定義されておらず、使用できません。
ベクタのデータのベースアドレスを格納するとき、 push_back
、 resize
などの多くの操作がベクタのデータメモリ位置を変更して以前のデータポインタを無効にすることができることに注意してください。例えば:
std::vector<int> v;
int* p = v.data();
v.resize(42); // internal memory location changed; value of p is now invalid
イテレータ/ポインタの無効化
std::vector
指すイテレータとポインタは無効になる可能性がありますが、特定の操作を実行する場合のみです。無効なイテレータ/ポインタを使用すると、未定義の動作になります。
イテレータ/ポインタを無効にする操作には、
vector
のcapacity
を変更する挿入操作は、 すべてのイテレータ/ポインタを無効にします。vector<int> v(5); // Vector has a size of 5; capacity is unknown. int *p1 = &v[0]; v.push_back(2); // p1 may have been invalidated, since the capacity was unknown. v.reserve(20); // Capacity is now at least 20. int *p2 = &v[0]; v.push_back(4); // p2 is *not* invalidated, since the size of `v` is now 7. v.insert(v.end(), 30, 9); // Inserts 30 elements at the end. The size exceeds the // requested capacity of 20, so `p2` is (probably) invalidated. int *p3 = &v[0]; v.reserve(v.capacity() + 20); // Capacity exceeded, thus `p3` is invalid.
auto old_cap = v.capacity();
v.shrink_to_fit();
if(old_cap != v.capacity())
// Iterators were invalidated.
容量を増やさない挿入操作は、挿入位置とそれを過ぎた要素を指すイテレータ/ポインタを無効にします。これには
end
イテレータが含まれます:vector<int> v(5); v.reserve(20); // Capacity is at least 20. int *p1 = &v[0]; int *p2 = &v[3]; v.insert(v.begin() + 2, 5, 0); // `p2` is invalidated, but since the capacity // did not change, `p1` remains valid. int *p3 = &v[v.size() - 1]; v.push_back(10); // The capacity did not change, so `p3` and `p1` remain valid.
削除操作は、削除された要素を指すイテレータ/ポインタと削除された要素を越えた要素を無効にします。これには
end
イテレータが含まれます:vector<int> v(10); int *p1 = &v[0]; int *p2 = &v[5]; v.erase(v.begin() + 3, v.end()); // `p2` is invalid, but `p1` remains valid.
operator=
(copy、move、またはotherwise)とclear()
は、ベクトルを指すすべてのイテレータ/ポインタを無効にします。
要素の削除
最後の要素を削除する:
std::vector<int> v{ 1, 2, 3 };
v.pop_back(); // v becomes {1, 2}
すべての要素を削除する:
std::vector<int> v{ 1, 2, 3 };
v.clear(); // v becomes an empty vector
インデックスによる要素の削除:
std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
v.erase(v.begin() + 3); // v becomes {1, 2, 3, 5, 6}
注:最後の要素ではない要素を削除するvector
場合、削除された要素を超えるすべての要素は、ギャップを埋めるためにコピーまたは移動する必要があります 。以下の注およびstd :: listを参照してください。
範囲内のすべての要素を削除する:
std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
v.erase(v.begin() + 1, v.begin() + 5); // v becomes {1, 6}
注:上記の方法ではベクトルの容量は変更されず、サイズのみが変更されます。 ベクターサイズと容量を参照してください。
ある範囲の要素を削除するerase
メソッドは、しばしばerase-removeイディオムの一部として使用されます。これは、最初のものであるstd::remove
ベクトルの最後に移動し、いくつかの要素を、次にerase
チョップにそれらをオフに。これは、消去されたセグメントの後のすべての要素が新しい位置に再配置されなければならないため、ベクトルの最後のインデックスよりも小さいインデックスの場合、比較的非効率的な操作です。コンテナ内の任意の要素を効率的に削除するスピードクリティカルなアプリケーションについては、 std :: listを参照してください。
値で要素を削除する:
std::vector<int> v{ 1, 1, 2, 2, 3, 3 };
int value_to_remove = 2;
v.erase(std::remove(v.begin(), v.end(), value_to_remove), v.end()); // v becomes {1, 1, 3, 3}
条件で要素を削除する:
// std::remove_if needs a function, that takes a vector element as argument and returns true,
// if the element shall be removed
bool _predicate(const int& element) {
return (element > 3); // This will cause all elements to be deleted that are larger than 3
}
...
std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
v.erase(std::remove_if(v.begin(), v.end(), _predicate), v.end()); // v becomes {1, 2, 3}
追加の述語関数を作成せずに、ラムダによる要素の削除
std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
v.erase(std::remove_if(v.begin(), v.end(),
[](auto& element){return element > 3;} ), v.end()
);
ループから条件による要素の削除:
std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
std::vector<int>::iterator it = v.begin();
while (it != v.end()) {
if (condition)
it = v.erase(it); // after erasing, 'it' will be set to the next element in v
else
++it; // manually set 'it' to the next element in v
}
削除の場合には増分it
ないことが重要ですが、ループで繰り返し消去する場合は、別の方法を使用することを検討する必要があります。より効率的な方法としてremove_if
を検討してください。
逆ループからの条件による要素の削除:
std::vector<int> v{ -1, 0, 1, 2, 3, 4, 5, 6 };
typedef std::vector<int>::reverse_iterator rev_itr;
rev_itr it = v.rbegin();
while (it != v.rend()) { // after the loop only '0' will be in v
int value = *it;
if (value) {
++it;
// See explanation below for the following line.
it = rev_itr(v.erase(it.base()));
} else
++it;
}
前のループのいくつかの点に注意してください。
いくつかの要素を指す逆イテレータを指定
it
と、メソッドのbase
は、同じ要素を指す規則的(非リバース)イテレータを与えます。vector::erase(iterator)
は、vector::erase(iterator)
指す要素を消去し、与えられた要素の後に続く要素にイテレータを返します。reverse_iterator::reverse_iterator(iterator)
、イテレータから逆イテレータを構築します。
、完全にライン入れてit = rev_itr(v.erase(it.base()))
言う:リバースイテレータを取るit
している、 v
定例反復子が指す要素を消去します。得られたイテレータを取り、そこから逆反復子を構築し、そして逆反復子に割り当てるit
。
v.clear()
を使用してすべての要素を削除しても、メモリは解放されませんcapacity()
ベクトルのcapacity()
は変更されません)。スペースを再利用するには、以下を使用します。
std::vector<int>().swap(v);
shrink_to_fit()
は未使用のベクトルの容量を解放します:
v.shrink_to_fit();
shrink_to_fit
は実際にスペースを再利用することを保証するものではありませんが、現在の実装ではほとんどありません。
std :: vectorの要素を見つける
<algorithm>
ヘッダで定義された関数std::find
は、 std::vector
内の要素を見つけるために使用できます。
std::find
は、 operator==
を使用して要素が等しいかoperator==
を比較します。この値と等しい値を比較する範囲の最初の要素にイテレータを返します。
問題の要素が見つからない場合、 std::find
はstd::vector::end
(またはvectorがconst
場合はstd::vector::cend
) std::find
返します。
static const int arr[] = {5, 4, 3, 2, 1};
std::vector<int> v (arr, arr + sizeof(arr) / sizeof(arr[0]) );
std::vector<int>::iterator it = std::find(v.begin(), v.end(), 4);
std::vector<int>::difference_type index = std::distance(v.begin(), it);
// `it` points to the second element of the vector, `index` is 1
std::vector<int>::iterator missing = std::find(v.begin(), v.end(), 10);
std::vector<int>::difference_type index_missing = std::distance(v.begin(), missing);
// `missing` is v.end(), `index_missing` is 5 (ie. size of the vector)
std::vector<int> v { 5, 4, 3, 2, 1 };
auto it = std::find(v.begin(), v.end(), 4);
auto index = std::distance(v.begin(), it);
// `it` points to the second element of the vector, `index` is 1
auto missing = std::find(v.begin(), v.end(), 10);
auto index_missing = std::distance(v.begin(), missing);
// `missing` is v.end(), `index_missing` is 5 (ie. size of the vector)
大きなベクトルで多くの検索を実行する必要がある場合は、 binary_search
アルゴリズムを使用する前に、まずベクトルをソートすることを検討してbinary_search
。
条件を満たすベクトルの最初の要素を見つけるには、 std::find_if
ことができます。 std::find
与えられた2つのパラメータに加えて、 std::find_if
は、述語関数への関数オブジェクトまたは関数ポインタである第3引数を受け入れます。述語はコンテナの要素を引数として受け取り、コンテナを変更せずにbool
に変換可能な値を返す必要があります。
bool isEven(int val) {
return (val % 2 == 0);
}
struct moreThan {
moreThan(int limit) : _limit(limit) {}
bool operator()(int val) {
return val > _limit;
}
int _limit;
};
static const int arr[] = {1, 3, 7, 8};
std::vector<int> v (arr, arr + sizeof(arr) / sizeof(arr[0]) );
std::vector<int>::iterator it = std::find_if(v.begin(), v.end(), isEven);
// `it` points to 8, the first even element
std::vector<int>::iterator missing = std::find_if(v.begin(), v.end(), moreThan(10));
// `missing` is v.end(), as no element is greater than 10
// find the first value that is even
std::vector<int> v = {1, 3, 7, 8};
auto it = std::find_if(v.begin(), v.end(), [](int val){return val % 2 == 0;});
// `it` points to 8, the first even element
auto missing = std::find_if(v.begin(), v.end(), [](int val){return val > 10;});
// `missing` is v.end(), as no element is greater than 10
配列をstd :: vectorに変換する
配列はstd::begin
とstd::end
を使って簡単にstd::vector
変換できます:
int values[5] = { 1, 2, 3, 4, 5 }; // source array
std::vector<int> v(std::begin(values), std::end(values)); // copy array to new vector
for(auto &x: v)
std::cout << x << " ";
std::cout << std::endl;
1 2 3 4 5
int main(int argc, char* argv[]) {
// convert main arguments into a vector of strings.
std::vector<std::string> args(argv, argv + argc);
}
C ++ 11のinitializer_list <>を使って、ベクトルを一度に初期化することもできます
initializer_list<int> arr = { 1,2,3,4,5 };
vector<int> vec1 {arr};
for (auto & i : vec1)
cout << i << endl;
ベクター :非常に多くの、非常に多くのルールへの例外
標準(セクション23.3.7)は、 bool
値をパックすることによってスペースを最適化するvector<bool>
特殊化が提供されるように指定します。ビットはC ++でアドレス可能ではないので、これはvector
いくつかの要件がvector<bool>
置かれていないことを意味します。
- 格納されたデータは連続的である必要はないので、
bool
配列を必要とするC APIにはvector<bool>
渡すことはできません。 -
at()
、operator []
、イテレータの逆参照はbool
への参照を返しません。むしろ、代入演算子をオーバーロードすることによってbool
への参照を(不完全に)シミュレートする代理オブジェクトを返します。例として、次のコードは、イテレータの逆参照が参照を返さないため、std::vector<bool>
に対して有効ではない可能性があります。
std::vector<bool> v = {true, false};
for (auto &b: v) { } // error
同様に、 bool&
引数を期待する関数は、 vector<bool>
適用さat()
operator []
またはat()
の結果、またはその反復子の参照を解除した結果とともに使用することはできません。
void f(bool& b);
f(v[0]); // error
f(*v.begin()); // error
std::vector<bool>
の実装は、コンパイラとアーキテクチャの両方に依存しています。特殊化は、 n
ブール値をメモリの最も低いアドレス指定可能なセクションにパックすることによって実装されます。ここで、 n
はアドレス指定可能な最下位メモリのビット単位のサイズです。現代のシステムでは、これは1バイトまたは8ビットです。つまり、1バイトに8つのブール値を格納できます。これは、1ブール値が1バイトのメモリに格納される従来の実装よりも改善されています。
注:以下の例は、従来の最適化されたvector<bool>
と個々のバイトの可能なビット単位の値を示しています。これはすべてのアーキテクチャで常に成立するとは限りません。しかし、これは最適化を視覚化する良い方法です。以下の例では、バイトは[x、x、x、x、x、x、x、x]として表されます。
伝統的な std::vector<char>
8つのブール値を格納する:
std::vector<char> trad_vect = {true, false, false, false, true, false, true, true};
ビット単位の表現:
[0,0,0,0,0,0,0,1], [0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1], [0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,1], [0,0,0,0,0,0,0,1]
Specialized std::vector<bool>
8つのブール値を格納する:
std::vector<bool> optimized_vect = {true, false, false, false, true, false, true, true};
ビット単位の表現:
[1,0,0,0,1,0,1,1]
上記の例では、 std::vector<bool>
の従来のバージョンでは、8つのブール値が8バイトのメモリを占めていましたが、 std::vector<bool>
の最適化バージョンでは、メモリ。これはメモリ使用量の大幅な改善です。 vector<bool>
をCスタイルのAPIに渡す必要がある場合は、メモリとパフォーマンスが危険にさらされている場合は、値を配列にコピーするか、APIを使用するより良い方法を見つける必要があります。
ベクトルサイズと容量
ベクトルサイズは単純にベクトルの要素数です。
現在のベクトルサイズは、
size()
メンバ関数によって照会されます。便利なempty()
関数は、sizeが0の場合にtrue
返しtrue
。vector<int> v = { 1, 2, 3 }; // size is 3 const vector<int>::size_type size = v.size(); cout << size << endl; // prints 3 cout << boolalpha << v.empty() << endl; // prints false
デフォルトの構築されたベクトルは、サイズ0で始まります:
vector<int> v; // size is 0 cout << v.size() << endl; // prints 0
ベクトルに
N
要素を追加すると、 サイズがN
増えます (たとえば、push_back()
、insert()
またはresize()
関数による)。ベクトルから
N
要素を削除すると、 サイズがN
減少します (pop_back()
、erase()
またはclear()
)。Vectorのサイズには実装固有の上限がありますが、到達する前にRAMが不足する可能性があります。
vector<int> v; const vector<int>::size_type max_size = v.max_size(); cout << max_size << endl; // prints some large number v.resize( max_size ); // probably won't work v.push_back( 1 ); // definitely won't work
よくある間違い: サイズは必ずしも(または通常は) int
はありません:
// !!!bad!!!evil!!!
vector<int> v_bad( N, 1 ); // constructs large N size vector
for( int i = 0; i < v_bad.size(); ++i ) { // size is not supposed to be int!
do_something( v_bad[i] );
}
ベクトルの容量はサイズとは異なります 。 sizeはベクトルの現在の要素数ですが、メモリを割り当て/確保する要素の数は容量になります。あまりにも大きいサイズの(再)割り当てが高すぎることがあるため、これは便利です。
現在のベクトルの容量は、
capacity()
メンバ関数によって問い合わせられます。 容量は常にサイズ以上です 。vector<int> v = { 1, 2, 3 }; // size is 3, capacity is >= 3 const vector<int>::size_type capacity = v.capacity(); cout << capacity << endl; // prints number >= 3
reserve( N )
機能によって手動で容量をreserve( N )
できます(ベクトル容量をN
変更します)。// !!!bad!!!evil!!! vector<int> v_bad; for( int i = 0; i < 10000; ++i ) { v_bad.push_back( i ); // possibly lot of reallocations } // good vector<int> v_good; v_good.reserve( 10000 ); // good! only one allocation for( int i = 0; i < 10000; ++i ) { v_good.push_back( i ); // no allocations needed anymore }
余分な容量が
shrink_to_fit()
によって解放されることを要求することができます(しかし、実装はあなたに従う必要はありません)。これは、使用済みメモリを節約するのに便利です。vector<int> v = { 1, 2, 3, 4, 5 }; // size is 5, assume capacity is 6 v.shrink_to_fit(); // capacity is 5 (or possibly still 6) cout << boolalpha << v.capacity() == v.size() << endl; // prints likely true (but possibly false)
Vectorは、部分的に容量を自動的に管理します。要素を追加すると、拡大する可能性があります。実装者は、成長係数に2または1.5を使用するのが好きです(黄金比は理想的な値になりますが、有理数のため実用的ではありません)。一方、ベクトルは通常自動的に縮小しません。例えば:
vector<int> v; // capacity is possibly (but not guaranteed) to be 0
v.push_back( 1 ); // capacity is some starter value, likely 1
v.clear(); // size is 0 but capacity is still same as before!
v = { 1, 2, 3, 4 }; // size is 4, and lets assume capacity is 4.
v.push_back( 5 ); // capacity grows - let's assume it grows to 6 (1.5 factor)
v.push_back( 6 ); // no change in capacity
v.push_back( 7 ); // capacity grows - let's assume it grows to 9 (1.5 factor)
// and so on
v.pop_back(); v.pop_back(); v.pop_back(); v.pop_back(); // capacity stays the same
ベクトルの連結
1つのstd::vector
は、メンバー関数insert()
を使用して別のstd::vector
に追加することができます。
std::vector<int> a = {0, 1, 2, 3, 4};
std::vector<int> b = {5, 6, 7, 8, 9};
a.insert(a.end(), b.begin(), b.end());
しかし、標準では、 insert()
与えられたイテレーターが受信側オブジェクトの要素と同じ範囲内にあってはならないということを指定しているため、このソリューションはベクトルを自身に追加しようとすると失敗します。
ベクトルのメンバ関数を使う代わりに、関数std::begin()
とstd::end()
を使うことができます:
a.insert(std::end(a), std::begin(b), std::end(b));
これはより一般的な解決策です。たとえば、 b
も配列である可能性があるからです。ただし、このソリューションでは、ベクターをそれ自体に追加することはできません。
受信ベクトルの要素の順序が重要でない場合は、各ベクトルの要素数を考慮すると、不要なコピー操作が回避されます。
if (b.size() < a.size())
a.insert(a.end(), b.begin(), b.end());
else
b.insert(b.end(), a.begin(), a.end());
ベクトルの容量を減らす
std::vector
は必要に応じて挿入時に自動的に容量を増やしますが、要素を削除した後は容量を減らすことはありません。
// Initialize a vector with 100 elements
std::vector<int> v(100);
// The vector's capacity is always at least as large as its size
auto const old_capacity = v.capacity();
// old_capacity >= 100
// Remove half of the elements
v.erase(v.begin() + 50, v.end()); // Reduces the size from 100 to 50 (v.size() == 50),
// but not the capacity (v.capacity() == old_capacity)
容量を減らすために、ベクトルの内容を新しい一時ベクトルにコピーすることができます。新しいベクトルは、元のベクトルのすべての要素を格納するのに必要な最小容量を持ちます。元のベクトルのサイズ縮小が重要であった場合、新しいベクトルの容量削減は重要である可能性が高い。次に、元のベクトルを一時的なベクトルと交換して、最小化された容量を保持することができます。
std::vector<int>(v).swap(v);
C ++ 11では、 shrink_to_fit()
メンバ関数を使用して同様の効果を得ることができます。
v.shrink_to_fit();
注意: shrink_to_fit()
メンバ関数はリクエストであり、容量を減らすことはできません。
高速要素検索のためのソート済みベクトルの使用
<algorithm>
ヘッダは、ソートされたベクトルを扱うための多くの便利な関数を提供します。
ソートされたベクトルを処理するための重要な前提条件は、格納された値が<
匹敵することです。
ソートされていないベクトルはstd::sort()
関数を使ってソートすることができます:
std::vector<int> v;
// add some code here to fill v with some elements
std::sort(v.begin(), v.end());
ソートされたベクトルは、関数std::lower_bound()
を使って効率的な要素検索を可能にします。 std::find()
とは異なり、これはベクトルに対して効率的なバイナリ検索を行います。欠点は、ソートされた入力範囲に対して有効な結果しか得られないことです。
// search the vector for the first element with value 42
std::vector<int>::iterator it = std::lower_bound(v.begin(), v.end(), 42);
if (it != v.end() && *it == 42) {
// we found the element!
}
注:要求された値がベクトルの一部でない場合、 std::lower_bound()
は、要求された値より大きい最初の要素にイテレータを返します。この振る舞いによって、既にソートされたベクトルの正しい場所に新しい要素を挿入することができます:
int const new_element = 33;
v.insert(std::lower_bound(v.begin(), v.end(), new_element), new_element);
一度にたくさんの要素を挿入する必要がある場合は、最初にすべての要素に対してpush_back()
を呼び出し、すべての要素が挿入されたらstd::sort()
を呼び出す方が効率的です。この場合、並べ替えのコスト増加は、ベクトルの最後に新しい要素を挿入するコストを削減し、途中ではなくすことができます。
ベクトルに同じ値の複数の要素が含まれている場合、 std::lower_bound()
は検索された値の最初の要素にイテレータを返そうとします。しかし、検索された値の最後の要素の後ろに新しい要素を挿入する必要がある場合は、 std::upper_bound()
関数を使用して、要素の移動を少なくする必要があります。
v.insert(std::upper_bound(v.begin(), v.end(), new_element), new_element);
上限と下限のイテレータが必要な場合は、関数std::equal_range()
を使用して、1回の呼び出しで両方を効率的に取得できます。
std::pair<std::vector<int>::iterator,
std::vector<int>::iterator> rg = std::equal_range(v.begin(), v.end(), 42);
std::vector<int>::iterator lower_bound = rg.first;
std::vector<int>::iterator upper_bound = rg.second;
ソートされたベクトルに要素が存在するかどうかをテストするには、関数std::binary_search()
使用します。
bool exists = std::binary_search(v.begin(), v.end(), value_to_find);
大きなベクトルを返す関数
C ++ 11では、コンパイラは返されるローカル変数から暗黙的に移動する必要があります。さらに、多くのコンパイラは多くの場合コピー・エリジョンを実行し、その動作を完全に排除することができます。その結果、安価に移動できる大きなオブジェクトを返す必要がなくなりました。
#include <vector>
#include <iostream>
// If the compiler is unable to perform named return value optimization (NRVO)
// and elide the move altogether, it is required to move from v into the return value.
std::vector<int> fillVector(int a, int b) {
std::vector<int> v;
v.reserve(b-a+1);
for (int i = a; i <= b; i++) {
v.push_back(i);
}
return v; // implicit move
}
int main() { // declare and fill vector
std::vector<int> vec = fillVector(1, 10);
// print vector
for (auto value : vec)
std::cout << value << " "; // this will print "1 2 3 4 5 6 7 8 9 10 "
std::cout << std::endl;
return 0;
}
C ++ 11より前のバージョンでは、ほとんどのコンパイラですでにcopy elisionが許可され実装されていました。しかし、この最適化を実装していない古いコンパイラバージョンでコンパイルする必要があるレガシーコードまたはコードでは、移動セマンティクスが存在しないため、不要なコピーを防ぐために出力引数として渡されるベクタを見つけることができます。
#include <vector>
#include <iostream>
// passing a std::vector by reference
void fillVectorFrom_By_Ref(int a, int b, std::vector<int> &v) {
assert(v.empty());
v.reserve(b-a+1);
for (int i = a; i <= b; i++) {
v.push_back(i);
}
}
int main() {// declare vector
std::vector<int> vec;
// fill vector
fillVectorFrom_By_Ref(1, 10, vec);
// print vector
for (std::vector<int>::const_iterator it = vec.begin(); it != vec.end(); ++it)
std::cout << *it << " "; // this will print "1 2 3 4 5 6 7 8 9 10 "
std::cout << std::endl;
return 0;
}
ベクトルの最大と最小の要素とそれぞれのインデックスを見つける
ベクトルに格納されている最大または最小の要素を見つけるには、それぞれstd::max_element
およびstd::min_element
メソッドを使用できます。これらのメソッドは<algorithm>
ヘッダで定義されています。いくつかの要素が最大(最小)の要素と等しい場合、メソッドはイテレータを最初のそのような要素に返します。空ベクトルの場合はv.end()
を返します。
std::vector<int> v = {5, 2, 8, 10, 9};
int maxElementIndex = std::max_element(v.begin(),v.end()) - v.begin();
int maxElement = *std::max_element(v.begin(), v.end());
int minElementIndex = std::min_element(v.begin(),v.end()) - v.begin();
int minElement = *std::min_element(v.begin(), v.end());
std::cout << "maxElementIndex:" << maxElementIndex << ", maxElement:" << maxElement << '\n';
std::cout << "minElementIndex:" << minElementIndex << ", minElement:" << minElement << '\n';
出力:
maxElementIndex:3、maxElement:10
minElementIndex:1、minElement:2
ベクトルの最小要素と最大要素は、 <algorithm>
ヘッダーで定義されているstd::minmax_element
メソッドを使用して同時に取得できます。
std::vector<int> v = {5, 2, 8, 10, 9};
auto minmax = std::minmax_element(v.begin(), v.end());
std::cout << "minimum element: " << *minmax.first << '\n';
std::cout << "maximum element: " << *minmax.second << '\n';
出力:
最小要素:2
最大要素数:10
行列を使った行列
ベクトルはベクトルのベクトルとして定義することによって2D行列として使用できます。
各セルが0として初期化された3行4列の行列は、次のように定義できます。
std::vector<std::vector<int> > matrix(3, std::vector<int>(4));
初期化子リストを使用して初期化する構文は、法線ベクトルの構文に似ています。
std::vector<std::vector<int>> matrix = { {0,1,2,3},
{4,5,6,7},
{8,9,10,11}
};
このようなベクトルの値は、2D配列と同様にアクセスできます
int var = matrix[0][2];
行列全体を反復することは、法線ベクトルに似ていますが、余分な次元があります。
for(int i = 0; i < 3; ++i)
{
for(int j = 0; j < 4; ++j)
{
std::cout << matrix[i][j] << std::endl;
}
}
for(auto& row: matrix)
{
for(auto& col : row)
{
std::cout << col << std::endl;
}
}
ベクトルのベクトルは行列を表現する便利な方法ですが、最も効率的ではありません。個々のベクトルはメモリの周りに散在し、データ構造はキャッシュに適していません。
また、適切な行列では、すべての行の長さは同じでなければなりません(これはベクトルのベクトルの場合ではありません)。追加の柔軟性は、エラーの原因となる可能性があります。