目次
1. ベクターの概要
- ベクトルは、可変サイズの配列を表すシーケンス コンテナーです。
- 配列と同様に、ベクトルも要素を格納するために連続した記憶領域を使用します。つまり、ベクトルの要素には添字を使用してアクセスでき、配列と同じくらい効率的です。ただし、配列とは異なり、そのサイズは動的に変更でき、そのサイズはコンテナーによって自動的に処理されます。
- 基本的に、vector は動的に割り当てられた配列を使用して要素を格納します。新しい要素が挿入されると、記憶領域を増やすために配列のサイズを変更する必要があります。これは、新しい配列を割り当て、すべての要素をこの配列に移動することによって行われます。新しい要素がコンテナに追加されるたびにベクトルのサイズが変更されないため、これは時間の点で比較的高価なタスクです。
- ベクトル割り当てスペース戦略: ストレージ スペースが実際に必要なストレージ スペースよりも大きいため、ベクトルは、増加の可能性を考慮して追加のスペースを割り当てます。ライブラリーが異なれば、スペースの使用量と再割り当てを比較検討するために異なる戦略が採用されます。ただし、いずれの場合も、最後に要素を挿入する処理が一定の時間計算量で行われるように、再割り当ての間隔サイズは対数的に増加する必要があります。
- したがって、Vector は、ストレージ スペースを管理し、効率的な方法で動的に拡張する機能を得るために、より多くのストレージ スペースを占有します。
- 他の動的シーケンス コンテナー (deque、list、forward_list) と比較して、vector は要素にアクセスする際の効率が高く、最後の要素の追加と削除も比較的効率的です。最後ではない他の削除および挿入操作の場合、効率は低くなります。list および forward_list の統合イテレータと参照よりも優れています。
2. Vectorの共通利用
2.1 ベクトル コンストラクター
(コンストラクター) コンストラクター宣言 | インターフェースの説明 |
ベクター() | パラメータ構築なし |
ベクトル (size_type n,const value_type&val=value_type()) | n 個の値を構築して初期化する |
ベクトル (定数ベクトル& x)
|
コピー構築 |
ベクトル (InputIterator fifirst、InputIterator last)
|
イテレータが初期化および構築されます |
2.2 vector iterator使用
論理的な位置図:
イテレータの使用 | インターフェースの説明 |
開始+終了 |
最初のデータ位置の
iterator/const_iteratorを取得
、最後のデータの次の位置の iterator/const_iterator を取得
|
r開始+終了 |
最後のデータ位置の
reverse_iteratorを取得
、最初のデータの前の位置の reverse_iterator を取得
|
簡単な使用例:
void test1()
{
vector<int> V;
V.push_back(1);
V.push_back(2);
V.push_back(3);
V.push_back(4);
vector<int>::iterator it = V.begin();
while (it != V.end())
{
cout << *it << ' ';
++it;
}
cout << endl;
//反向迭代器
vector<int>::reverse_iterator rit = V.rbegin();
while (rit != V.rend())
{
cout << *rit << ' ';
++rit;
}
cout << endl;
}
2.3 ベクトル空間の成長問題
容量スペース | インターフェースの説明 |
サイズ | データ数を取得する |
容量 | 容量のサイズを取得する |
空 | 空かどうかを判断する |
サイズ変更 | ベクトルのサイズを変更する |
予約 | ベクターの容量を変更する |
ベクトルのデフォルトの展開メカニズムをテストします。
void TestVectorExpand()
{
size_t sz;
vector<int> v;
sz = v.capacity();
cout << "making v grow:\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
vs の下で実行します。
gcc で実行します。
- 容量コードはそれぞれ vs と g++ で実行され、 vs での容量は 1.5 倍、g++ では 2 倍増加することが わかります。ベクターの容量が 2 倍になるとは考えず、具体的な増加量は特定のニーズに応じて定義されます。vs は PJ バージョン STL、g++ は SGI バージョン STL です。
リザーブはスペースを空けることのみを担当します。必要なスペースがどのくらいかわかっている場合、リザーブはベクトル容量拡張のコスト上の欠点を軽減できます。 サイズ変更もスペースを開くときに初期化されるため、サイズに影響します。
void test2()
{
vector<int> v;
// set some initial content:
for (int i = 1; i < 10; i++)
v.push_back(i);
v.resize(5);
v.resize(8, 100);
v.resize(12);
cout << v.size() << endl;
cout << "v contains:";
for (size_t i = 0; i < v.size(); i++)
cout << ' ' << v[i];
cout << endl;
}
事前に容量を設定するには、reserve を使用します。
2.4 ベクターの追加、削除、変更、確認
ベクトルの追加、削除、変更、チェック | インターフェースの説明 |
プッシュバック | テールプラグ |
ポップバック | 末尾削除 |
探す | ルックアップ (ベクトル メンバー インターフェイスではなく、アルゴリズム モジュールの実装) |
入れる | pos の前に val を挿入 |
消す | 位置情報データの削除 |
スワップ | 2 つのベクトル データ空間を交換する |
演算子[ ] | 配列のような添字アクセス |
テストコード:
void test3()
{
vector<int> v{ 1,2,3,4 };//列表方式初始化,C++11新语法
v.push_back(5);
v.push_back(6);
v.pop_back();
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << ' ';
}
cout << endl;
vector<int>::iterator pos = find(v.begin(), v.end(), 3);
if (pos != v.end())
{
v.insert(pos, 0);
}
pos = find(v.begin(), v.end(), 3);
v.erase(pos);
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << ' ';
}
cout << endl;
}
2.5 ベクトル反復子の無効化問題
注意してください。上で丸で囲んだ find が一度検索されていることに気付いたかもしれません。これは、find を pos に割り当てなくなった場合、挿入後のイテレータの戻り値を受け入れず、消去を続行するためです。イテレータは失敗します。
イテレータの主な機能は、アルゴリズムが基礎となるデータ構造を考慮しないようにすることです。基礎となる層は実際にはポインタであるか、ポインタをカプセル化します。たとえば、vector のイテレータは元のポインタ T* です 。したがって、イテレータの無効化は実際には、イテレータの下部にある対応するポインタが指すスペースが破壊され、解放されたスペースの一部が使用されることを意味します。その結果、プログラムがクラッシュします (つまり、無効なイテレータを使用し続けると、プログラムがクラッシュする可能性があります。 )。
イテレータを無効にする可能性があるベクトルに対する有効な操作は次のとおりです。
- サイズ変更、予約、挿入、割り当て、プッシュバックなど、基になる領域に変更を引き起こす操作によりイテレータが失敗する可能性があります。
展開後、元の pos は無効になり、ワイルド ポインタになります。
挿入が展開されていない場合、pos は挿入された要素を指します。
- 指定した位置の要素を削除します - 消去
エクササイズ:
次のプログラムの正しい出力は ( ) です。
int main() { int ar[] ={1,2,3,4,0,5,6,7,8,9}; int n = sizeof(ar) / sizeof(int); vector<int> v(ar, ar+n); vector<int>::iterator it = v.begin(); while(it != v.end()) { if(*it != 0) cout<<*it; else v.erase(it); it++; } return 0; }
A. プログラムがクラッシュする
B.1 2 3 4 5 0 6 7 8 9
C.1 2 3 4 5 6 7 8 9
D.1 2 3 4 6 7 8 9
回答: イテレータの値が 0 の場合、この時点でイテレータは削除されます。削除後にイテレータが再割り当てされないと、元のイテレータは無効になります。このとき、無効なイテレータに対して ++ を実行すると、プログラムがクラッシュします。したがって、答えはAです
イテレータの無効化に関する次の説明のうち、間違っているものはどれですか ( )
A.vector の挿入操作は確実に反復子を失敗させます。
B.vector の挿入操作ではイテレータが無効にならない場合があります
C. ベクトルの削除操作は、削除された要素とそれに続く要素を指す反復子のみを無効にします。
D.vector の削除操作は、削除された要素を指す反復子のみを無効にします。
回答: ベクトルの挿入操作によって基礎となる空間が再度開かれる場合、反復子は無効になります。十分なスペースがある場合、データの相対位置が変更され、前の位置を指さなくなるため、反復は無効であると見なされます。
ベクトルが削除された場合、現在の要素は無効になる必要があり、後続の要素にはデータの移動が含まれるため、削除された要素の背後にある反復子も無効になります。
ということで答えはBDです
vector<int>::iterator pos = find(v.begin(), v.end(), 3);
if (pos != v.end())
{
pos=v.insert(pos, 0);
//pos当前指向0
}
//删除3
++pos;
v.erase(pos);
3. ベクトル深度解析とシミュレーションの実装
3.1 シミュレーションの実装 (スキップ可能)
プライベートメンバー変数: 上の図と一致
iterator _start;
iterator _finish;
iterator _end_of_storage;
コンストラクタ:
コピーコンストラクター:(現代文)
スワップ:
代入演算子のオーバーロード: (現代文)
デストラクター:
反復子:
サイズと容量:
予約: (memcpy を使用しない理由は後で解決されます)
サイズ変更:
演算子[ ]:
入れる:
消去:
Push_back、pop_back(): (挿入、消去を再利用)
3.2 memcpyを使用しない解析
memcpy は浅いコピーです
1. memcpy はメモリのバイナリ形式のコピーで、あるメモリ空間の内容を別のメモリ空間にそのままコピーします。2. カスタム タイプの要素をコピーする場合、memcpy は効率的でエラーはありませんが、カスタム タイプの要素をコピーし、そのカスタム タイプの要素にリソース管理が関与している場合は、エラーが発生します。 memcpy のコピー これは実際には浅いコピーです。
Vector<vector<int>> 型を想定すると、実際のメモリ割り当ては次のようになります。
memcpy 割り当て: (浅いコピー、スペースを 2 回解放します。2 回目ではワイルド ポインターのメモリ リークが発生します)
ディープコピーを作成します (自分で実装します)。