std::vector
『C++』の標準テンプレートライブラリ(STL)が提供する std::vector は、動的にサイズが変わる配列コンテナです。要素の追加・削除・アクセスを安全かつ効率的に行えるため、C++ プログラムで最もよく使われるコンテナのひとつです。
構文
// ========================================
// std::vector の基本構文
// ========================================
#include <vector> // vector を使うには このヘッダが必要です
// 宣言と初期化のパターンです
std::vector<int> v1; // 空の vector を作ります
std::vector<int> v2(5); // 要素数5・値0で初期化します
std::vector<int> v3(5, 42); // 要素数5・値42で初期化します
std::vector<int> v4 = {1, 2, 3}; // 初期化子リストで初期化します(C++11)
// 要素の追加・削除です
v1.push_back(10); // 末尾に追加します
v1.pop_back(); // 末尾を削除します
// 要素へのアクセスです
int a = v2[0]; // インデックスアクセス(範囲チェックなし)
int b = v2.at(0); // 範囲チェックあり(範囲外は例外 std::out_of_range)
int f = v2.front(); // 先頭要素を取得します
int l = v2.back(); // 末尾要素を取得します
// サイズ・容量の確認です
v2.size(); // 現在の要素数を返します
v2.empty(); // 要素が0件のとき true を返します
v2.capacity(); // 確保済み容量を返します(再アロケーションのタイミングに影響します)
v2.reserve(100); // 容量を予約します(再アロケーションを抑えたいときに使います)
// イテレータでの走査です
for (std::vector<int>::iterator it = v4.begin(); it != v4.end(); ++it) {
std::cout << *it << std::endl;
}
// 範囲 for(C++11)での走査です
for (int x : v4) {
std::cout << x << std::endl;
}
// 全要素削除です
v1.clear();
主なメンバ関数一覧
| メンバ関数 | 概要 |
|---|---|
| push_back(val) | 末尾に要素を追加します。 |
| pop_back() | 末尾の要素を削除します。 |
| insert(it, val) | イテレータ it の位置に要素を挿入します。O(n) の操作です。 |
| erase(it) | イテレータ it の位置の要素を削除します。O(n) の操作です。 |
| at(i) | インデックス i の要素を返します。範囲外のとき std::out_of_range を投げます。 |
| operator[](i) | インデックス i の要素を返します。範囲チェックはありません。 |
| front() | 先頭要素への参照を返します。 |
| back() | 末尾要素への参照を返します。 |
| size() | 現在の要素数を返します。 |
| empty() | 要素が0件のとき true を返します。 |
| capacity() | 現在確保されている容量(再アロケーションが起きるまでの上限)を返します。 |
| reserve(n) | 容量を少なくとも n に予約します。要素の追加前に呼ぶと再アロケーションを防げます。 |
| resize(n) | 要素数を n に変更します。増加分はデフォルト値で初期化されます。 |
| clear() | 全要素を削除して要素数を0にします。容量は変化しません。 |
| begin() / end() | 先頭・末尾の次を指すイテレータを返します。範囲 for やアルゴリズム関数に渡して使います。 |
サンプルコード
steins_vector.cpp
// ========================================
// steins_vector.cpp
// Steins;Gate の登場人物を std::vector で管理する
// 追加・検索・削除・走査の基本操作を示すサンプルです
// ========================================
#include <iostream>
#include <vector>
#include <string>
#include <algorithm> // std::find を使うために必要です
// ========================================
// ラボメン情報を保持する構造体です
// ========================================
struct LabMember {
std::string name; // ラボメン名です
int memberNo; // ラボメン番号です
std::string role; // 役割です
};
// ========================================
// ラボメン情報を一行で表示するヘルパー関数です
// ========================================
void printMember(const LabMember& m) {
std::cout << "No." << m.memberNo
<< " " << m.name
<< " [" << m.role << "]"
<< std::endl;
}
int main() {
// ========================================
// 1. vector の宣言と初期要素の追加
// push_back() で末尾に追加するのが基本です
// ========================================
std::vector<LabMember> members;
// reserve() で容量を事前に確保します
// 追加のたびに再アロケーションが起きるのを防げます
members.reserve(5);
members.push_back({"岡部倫太郎", 1, "狂気のマッドサイエンティスト"});
members.push_back({"牧瀬紅莉栖", 2, "天才科学者"});
members.push_back({"椎名まゆり", 3, "コスプレイヤー"});
members.push_back({"阿万音鈴羽", 4, "未来からのタイムトラベラー"});
members.push_back({"橋田至", 5, "ハッカー(ダル)"});
std::cout << "=== フューチャーガジェット研究所 ラボメン一覧 ===" << std::endl;
std::cout << "要素数: " << members.size()
<< " 確保容量: " << members.capacity() << std::endl << std::endl;
// ========================================
// 2. インデックスアクセスと at() の違い
// ========================================
std::cout << "--- インデックスアクセス ---" << std::endl;
std::cout << "members[0].name = " << members[0].name << std::endl;
std::cout << "members.at(1).name = " << members.at(1).name << std::endl;
std::cout << "front: " << members.front().name << std::endl;
std::cout << "back: " << members.back().name << std::endl << std::endl;
// ========================================
// 3. 範囲 for ループで全員を表示します
// ========================================
std::cout << "--- 全ラボメン ---" << std::endl;
for (int i = 0; i < (int)members.size(); i++) {
printMember(members[i]);
}
std::cout << std::endl;
// ========================================
// 4. insert() で任意の位置に挿入します
// begin() + n でn番目の位置を指定します
// ========================================
LabMember faris = {"フェイリス・ニャンニャン", 6, "メイドの女王"};
// 先頭(No.1 岡部の前)に挿入します
members.insert(members.begin(), faris);
std::cout << "--- フェイリスを先頭に insert() した後 ---" << std::endl;
for (int i = 0; i < (int)members.size(); i++) {
printMember(members[i]);
}
std::cout << std::endl;
// ========================================
// 5. erase() で名前検索して削除します
// 名前が一致する最初の要素を削除しています
// ========================================
std::string targetName = "フェイリス・ニャンニャン";
for (std::vector<LabMember>::iterator it = members.begin();
it != members.end(); ++it) {
if (it->name == targetName) {
members.erase(it);
std::cout << targetName << " を erase() しました。" << std::endl;
break;
}
}
std::cout << std::endl;
// ========================================
// 6. pop_back() で末尾を削除します
// ========================================
std::cout << "pop_back() 前の末尾: " << members.back().name << std::endl;
members.pop_back();
std::cout << "pop_back() 後の末尾: " << members.back().name << std::endl;
std::cout << std::endl;
// ========================================
// 7. 最終状態を表示します
// ========================================
std::cout << "--- 最終ラボメン一覧(要素数: " << members.size() << ") ---" << std::endl;
for (int i = 0; i < (int)members.size(); i++) {
printMember(members[i]);
}
std::cout << std::endl;
// ========================================
// 8. clear() で全要素を削除します
// 容量(capacity)は変わらないことに注意してください
// ========================================
members.clear();
std::cout << "clear() 後 → size: " << members.size()
<< " capacity: " << members.capacity() << std::endl;
return 0;
}
# コンパイルします g++ -std=c++11 steins_vector.cpp -o steins_vector # 実行します ./steins_vector === フューチャーガジェット研究所 ラボメン一覧 === 要素数: 5 確保容量: 5 --- インデックスアクセス --- members[0].name = 岡部倫太郎 members.at(1).name = 牧瀬紅莉栖 front: 岡部倫太郎 back: 橋田至 --- 全ラボメン --- No.1 岡部倫太郎 [狂気のマッドサイエンティスト] No.2 牧瀬紅莉栖 [天才科学者] No.3 椎名まゆり [コスプレイヤー] No.4 阿万音鈴羽 [未来からのタイムトラベラー] No.5 橋田至 [ハッカー(ダル)] --- フェイリスを先頭に insert() した後 --- No.6 フェイリス・ニャンニャン [メイドの女王] No.1 岡部倫太郎 [狂気のマッドサイエンティスト] No.2 牧瀬紅莉栖 [天才科学者] No.3 椎名まゆり [コスプレイヤー] No.4 阿万音鈴羽 [未来からのタイムトラベラー] No.5 橋田至 [ハッカー(ダル)] フェイリス・ニャンニャン を erase() しました。 pop_back() 前の末尾: 橋田至 pop_back() 後の末尾: 阿万音鈴羽 --- 最終ラボメン一覧(要素数: 4) --- No.1 岡部倫太郎 [狂気のマッドサイエンティスト] No.2 牧瀬紅莉栖 [天才科学者] No.3 椎名まゆり [コスプレイヤー] No.4 阿万音鈴羽 [未来からのタイムトラベラー] clear() 後 → size: 0 capacity: 6
よくあるミス
push_back 中のイテレータ無効化
push_back() や insert() で要素が追加されると、内部で再アロケーションが発生することがあります。再アロケーションが起きると、それ以前に取得していたイテレータ・ポインタ・参照はすべて無効になります。無効なイテレータを使い続けると未定義動作です。
NG 例
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<std::string> members;
members.push_back("岡部倫太郎");
members.push_back("牧瀬紅莉栖");
// イテレータを取得します
std::vector<std::string>::iterator it = members.begin();
// push_back で再アロケーションが起きると it は無効になります
members.push_back("椎名まゆり");
members.push_back("阿万音鈴羽");
members.push_back("橋田至");
// NG: 無効化された it を使う(未定義動作)
std::cout << *it << std::endl;
return 0;
}
OK 例: reserve() で容量を確保しておくか、使う直前にイテレータを取得し直します。
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<std::string> members;
// 必要な容量を事前に確保しておくと再アロケーションが起きません
members.reserve(5);
members.push_back("岡部倫太郎");
members.push_back("牧瀬紅莉栖");
members.push_back("椎名まゆり");
members.push_back("阿万音鈴羽");
members.push_back("橋田至");
// reserve 後は capacity() の範囲内であれば再アロケーションは起きません
// イテレータを取得します(push_back の後なので安全です)
std::vector<std::string>::iterator it = members.begin();
std::cout << *it << std::endl;
return 0;
}
# コンパイルします g++ -std=c++11 ok_vector_reserve.cpp -o ok_vector_reserve # 実行します ./ok_vector_reserve 岡部倫太郎
reserve と resize の混同
reserve(n) は内部バッファの容量を確保するだけで、要素数(size())は変わりません。resize(n) は要素数を変更し、増加分はデフォルト値で初期化されます。reserve() の後にインデックスアクセス(v[i])をしても、要素はまだ存在しないため未定義動作になります。
NG 例(reserve 後の [] アクセス)
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<std::string> members;
members.reserve(5); // 容量を確保しただけで要素はありません
// NG: reserve 後に [] でアクセスしても要素は存在しません(未定義動作)
members[0] = "岡部倫太郎";
std::cout << members[0] << std::endl;
return 0;
}
OK 例: resize() で要素を確保してからアクセスするか、push_back() で追加します。
#include <iostream>
#include <vector>
#include <string>
int main() {
// resize() は要素を確保します(デフォルト値で初期化されます)
std::vector<std::string> members;
members.resize(5); // size() = 5 になります
members[0] = "岡部倫太郎";
members[1] = "牧瀬紅莉栖";
members[2] = "椎名まゆり";
members[3] = "阿万音鈴羽";
members[4] = "橋田至";
for (int i = 0; i < (int)members.size(); i++) {
std::cout << members[i] << std::endl;
}
// push_back で追加する場合は reserve() で容量だけ確保するのが効率的です
std::vector<std::string> members2;
members2.reserve(5); // 容量を予約します(size() は 0 のまま)
members2.push_back("岡部倫太郎");
members2.push_back("牧瀬紅莉栖");
std::cout << "size: " << members2.size() << " capacity: " << members2.capacity() << std::endl;
return 0;
}
# コンパイルします g++ -std=c++11 ok_vector_resize.cpp -o ok_vector_resize # 実行します ./ok_vector_resize 岡部倫太郎 牧瀬紅莉栖 椎名まゆり 阿万音鈴羽 橋田至 size: 2 capacity: 5
概要
std::vector は C++ STL の中で最も汎用的なシーケンスコンテナです。内部では連続したメモリ領域を使っているため、インデックスによるランダムアクセスが O(1) で行えます。末尾への push_back() は均せば O(1)(アモータイズド定数)ですが、容量が足りなくなると再アロケーションが発生します。要素数が事前にわかっている場合は reserve() で容量を予約しておくと再アロケーションを防いでパフォーマンスを向上させられます。一方、中間への insert() や erase() はその位置以降の要素をすべてずらす O(n) の操作になるため、頻繁に中間操作が必要なら std::list の利用も検討してください。要素へのアクセスには範囲チェックのある at() を使うと、インデックス誤りを std::out_of_range 例外として検出できます。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。