C++ vector コンテナ
C++ において、vector最もよく使われる STL コンテナの一つです。
vector本質的には自動的に拡張できる動的配列です従来の配列と比較して、vector は手動でメモリを管理する必要がなく、要素数に応じて自動的に拡張できます。
vector には次のような特徴があるため:
- 連続メモリ保存
- ランダムアクセスが高速
- STL エコシステムが充実
- キャッシュフレンドリー
そのため、現代の C++ 開発では、ほとんどの順次データ保存シーンで vector が優先的に検討されます。
vector の核心的な特徴
- 動的なサイズ:vector は自動的に拡張・縮小できます。
- 連続メモリ:要素はメモリ上で連続して保存されるため、アクセス効率が非常に高いです。
- ランダムアクセス:添え字による高速な要素アクセスをサポートし、時間計算量は O(1) です。
- 自動メモリ管理:手動で malloc/free を行う必要はありません。
- イテレータをサポート:STL アルゴリズムと簡単に組み合わせて使用できます。
なぜ vector は速いのか?
vector の要素はメモリ上で連続して配置されています:
[1][2][3][4][5]
この配置は CPU キャッシュに対して非常にフレンドリーです。
CPU は最初の要素を読み取る際、通常後続のデータもまとめてキャッシュにロードするため、vector の走査速度は通常非常に速いです。
これが、多くの場合に
vector 比 list 更快
たとえ list の理論上の削除計算量がより低くても。
vector を使用する
vector を使用する前に、ヘッダーファイルをインクルードする必要があります:
#include <vector>
vector を作成する
空の vector を作成する:
std::vector<int>vec;
初期サイズを指定する:
std::vector<int>vec(5);
上記のコードは 5 つの要素を作成し、デフォルト値は 0 です。
初期値を指定する:
std::vector<int>vec(5, 10);
作成結果:
[10, 10, 10, 10, 10]
初期化リストを使用する:
std::vector<int>vec = {1, 2, 3, 4};
要素を追加する
使用するpush_back()末尾に要素を追加する:
vec.push_back(100);
vector の末尾に要素を追加する平均時間計算量は次の通りです:
O(1)
要素にアクセスする
添え字を使用してアクセスする:
int x = vec[0];
使用at():
int y = vec.at(1);
違い:
[]範囲外チェックを行わないため、より高速です。at()範囲外チェックを行うため、より安全です。
サイズを取得する
vec.size();
現在の要素数を返します。
size と capacity の違い
これは vector の最も重要な概念の一つです。
std::vector<int>vec; vec.push_back(1); vec.push_back(2); std::cout << vec.size() << std::endl; std::cout << vec.capacity() << std::endl;
出力は次のようになる場合があります:
2 4
- size:現在の要素数。
- capacity:現在割り当てられているメモリ容量。
capacity は通常 size 以上です。
これは vector が頻繁な拡張を減らすために、事前により多くのスペースを確保するためです。
vector の拡張メカニズム
vector のスペースが不足すると、拡張が発生します:
- より大きなメモリを確保する
- 古い要素をコピーする
- 古いメモリを解放する
このプロセスには高いコストがかかります。
例えば:
std::vector<int>vec;
for (int i = 0; i < 1000000; i++) {
vec.push_back(i);
}
複数回のメモリ再割り当てが発生する可能性があります。
reserve() によるスペースの事前割り当て
頻繁な拡張を避けるために、事前にメモリを割り当てることができます:
std::vector<int>vec; vec.reserve(1000000);
これにより、次のものを減らすことができます:
- メモリの再割り当て
- 要素のコピー
- パフォーマンスの損失
これはエンジニアリング開発において非常に重要な最適化テクニックです。
vector を走査する
添え字を使用
for (size_t i = 0; i < vec.size(); i++) {
std::cout << vec[i] << " ";
}
イテレータを使用
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
範囲 for ループ
for (int element : vec) {
std::cout << element << " ";
}
要素を削除する
3 番目の要素を削除する:
vec.erase(vec.begin() + 2);
注意:
vector で中央の要素を削除すると、後続の要素が全体として前に移動します。
例えば:
1 2 3 4 5 删除 2 后: 1 3 4 5
そのうち:
3 4 5
すべて位置を移動する必要があります。
したがって:
erase() 的时间复杂度为 O(n)
vector が適さないシーン
- 頻繁な先頭への挿入
- 頻繁な中央の削除
- 巨大オブジェクトの頻繁な拡張
例えば:
vec.insert(vec.begin(), 100);
これは比較的遅い操作です。
イテレータの無効化
vector が拡張されると、元のメモリアドレスが無効になる可能性があります。
例えば:
std::vector<int>vec = {1, 2, 3};
auto it = vec.begin();
vec.push_back(4);
std::cout << *it;
ここでのitは無効になっている可能性があります。
push_back() がメモリの再割り当てを引き起こす可能性があるためです。
これは vector で最もよくある落とし穴の一つです。
clear() の注意点
vec.clear();
clear() は要素をクリアするだけです:
- size は 0 になります
- capacity は保持される可能性があります
つまり:
clear() 不一定释放内存
メモリを解放したい場合:
std::vector<int>().swap(vec);
または:
vec.shrink_to_fit();
push_back と emplace_back
現代の C++ では、次を使用することがより推奨されます:
emplace_back()
例えば:
vec.push_back(Person("Tom", 20));
一時オブジェクトが生成される可能性があります。
一方:
vec.emplace_back("Tom", 20);
vector 内部で直接オブジェクトを構築します。
通常、より効率的です。
vector と配列の違い
| 特性 | 配列 | vector |
|---|---|---|
| サイズ固定 | である | 否 |
| 自動拡張 | 否 | である |
| 連続メモリ | である | である |
| ランダムアクセス | 快 | 快 |
| STL サポート | 少ない | 充実 |
| 安全なアクセス | 无 | at() |
vector の一般的な操作の計算量
| 操作 | 時間計算量 |
|---|---|
| ランダムアクセス | O(1) |
| push_back | 平均 O(1) |
| 末尾の削除 | O(1) |
| 先頭への挿入 | O(n) |
| 中央の削除 | O(n) |
| clear | O(n) |
例:学生の成績管理
例
#include <iostream>
#include <vector>
#include <numeric>
int main() {
std::vector<int>scores = {85, 90, 78, 92};
scores.push_back(88);
std::cout << "すべての成績: ";
for (int score : scores) {
std::cout << score << " ";
}
std::cout << std::endl;
int total = std::accumulate(scores.begin(), scores.end(), 0);
double average = total * 1.0 / scores.size();
std::cout << "平均成績: " << average << std::endl;
scores.erase(scores.begin() + 2);
std::cout << "削除後の成績: ";
for (int score : scores) {
std::cout << score << " ";
}
std::cout << std::endl;
return 0;
}
出力結果は次のようになります:
所有成绩: 85 90 78 92 88 平均成绩: 86.6 删除后成绩: 85 90 92 88
まとめ
vector は現代の C++ において最も重要で、最もよく使われるデータ構造の一つです。
その核心的な利点:
- 動的拡張
- 連続メモリ
- ランダムアクセスが高速
- キャッシュフレンドリー
- STL エコシステムが充実
しかし同時に注意すべき点もあります:
- 拡張のパフォーマンスコスト
- 中央の削除は効率が低い
- イテレータの無効化問題
ほとんどの順次保存のシーンでは:
vector 通常都是默认首选容器その他の拡張