C++の「vector」は、標準ライブラリ(STL)に含まれる最も重要なコンテナの一つです。
配列と似た使い方ができますが、通常の配列とは異なり、要素数を動的に増減できるデータ構造になっています。
C++では多くの場面で配列の代わりにvectorが使われるため、STLを学ぶうえでも必ず理解しておく必要があります。
vectorとは何か
vectorは「動的配列」と呼ばれるデータ構造です。
通常の配列はサイズを固定して作成しますが、vectorは要素の追加や削除に応じて内部的にサイズを調整することができます。
主な特徴は次の通りです。
・要素はメモリ上に連続して格納される
・要素数を後から自由に増減できる
・インデックスを使って高速にアクセスできる
・末尾への追加が非常に高速
この性質により、vectorは次のような用途で広く利用されています。
・データを順番に保存する
・可変サイズの配列を扱う
・STLアルゴリズムと組み合わせる
・競技プログラミング
・一般的なアプリケーション開発
C++では「まずvectorを使う」というのが基本的な考え方です。
vectorの宣言
vectorは標準ライブラリのコンテナなので、使用するためにはヘッダを読み込む必要があります。
そのうえで、vectorには「どの型の要素を格納するか」を指定して宣言します。
例えば次のような型を扱えます。
・整数のvector
・浮動小数点数のvector
・文字列のvector
・クラスオブジェクトのvector
つまりvectorは「テンプレートコンテナ」であり、任意の型を格納できます。
要素の追加
vectorに要素を追加する場合、最も基本的に使われる操作は「末尾への追加」です。
vectorは内部的に末尾への追加が最も高速になるよう設計されています。
通常、この操作の計算量は平均的には定数時間で実行されます。
ただし、vectorの内部メモリが足りなくなった場合には、より大きなメモリを再確保してデータをコピーする処理が発生します。
このときは一時的に処理時間が増えます。
このような挙動のため、vectorの末尾追加は「償却計算量 O(1)」と呼ばれます。
要素へのアクセス
vectorの要素は、通常の配列と同じようにインデックスを使ってアクセスできます。
vectorの大きな特徴の一つは、ランダムアクセスが高速であることです。
特定の位置の要素を取得する操作は定数時間で実行できます。
ただし、vectorの範囲外にアクセスすると問題が発生します。
インデックス演算子によるアクセスは範囲チェックを行わないため、存在しない要素を参照すると未定義動作になります。
未定義動作とは、プログラムが正常に動く保証がなくなる状態を指します。
そのため、安全性を重視する場合には範囲チェックを行うアクセス方法を使うこともあります。
vectorのサイズ
vectorには現在の要素数を取得するための機能があります。
これは配列とは異なり、動的にサイズが変化するvectorにとって重要な情報です。
vectorには次の2種類のサイズ概念があります。
・size
現在格納されている要素数
・capacity
確保されているメモリの容量
capacityは内部メモリのサイズを示しており、実際の要素数とは異なる場合があります。
vectorの初期化
vectorは宣言と同時に初期化することもできます。
初期化の方法はいくつかあります。
例えば、最初から複数の要素を持ったvectorを作ることもできます。
また、指定した数だけ要素を持つvectorを作ることも可能です。
このとき、要素は型の初期化ルールに従って生成されます。
例えば整数型の場合は0で初期化されますが、クラス型の場合はそのクラスのコンストラクタが呼ばれます。
vectorのループ処理
vectorの要素を処理する場合、ループで順番にアクセスすることがよくあります。
C++では次のような方法が使われます。
・インデックスを使ったループ
・range-based for文
・イテレータ
現代のC++では、range-based for文を使う方法が読みやすく安全なためよく使われます。
要素の削除
vectorから要素を削除する方法はいくつかあります。
最も簡単な方法は、末尾の要素を削除する操作です。
これはvectorが最も得意とする操作の一つで、非常に高速に実行できます。
一方で、途中の要素を削除する場合は注意が必要です。
vectorでは削除された位置より後ろの要素が前に詰められるため、多くのデータ移動が発生します。
そのため、途中削除の計算量は要素数に比例します。
また、この操作を行うと削除位置以降の参照やイテレータが無効になることがあります。
要素の挿入
vectorには任意の位置に要素を挿入する機能もあります。
しかし、この操作も削除と同様に注意が必要です。
途中に要素を挿入すると、後ろの要素がすべて後方に移動します。
そのため、この操作も計算量は要素数に比例します。
vectorは途中挿入や途中削除には向いていないため、このような操作が多い場合には別のデータ構造を検討することもあります。
vectorを空にする
vectorの要素をすべて削除することもできます。
この操作を行うと、要素数は0になります。
ただし重要な点として、通常この操作では内部のメモリ容量は解放されません。
つまり、capacityはそのまま残る場合が多いです。
この設計は、同じvectorを再利用する場合のパフォーマンスを向上させるためです。
メモリ管理とreserve
vectorでは、要素追加のたびにメモリ確保が発生すると処理が遅くなります。
そのため、あらかじめ必要な容量がわかっている場合には、事前にメモリを確保しておくことができます。
この操作は、vectorのcapacityを増やすだけであり、要素数を増やすわけではありません。
つまり、容量だけ確保され、実際の要素はまだ存在しない状態になります。
この仕組みにより、要素追加時の再確保を減らしてパフォーマンスを改善できます。
vectorとアルゴリズム
vectorはSTLのアルゴリズムと組み合わせて使うことで強力になります。
代表的なアルゴリズムには次のようなものがあります。
・並び替え
・検索
・重複削除
・最大値や最小値の取得
vectorは要素が連続したメモリに配置されるため、多くのアルゴリズムと非常に相性が良いコンテナです。
2次元vector
vectorは他のvectorを要素として持つこともできます。
これにより、2次元配列のような構造を作ることが可能です。
例えば次のような用途があります。
・行列データ
・ゲームマップ
・表形式データ
・グリッド処理
この構造は競技プログラミングやアルゴリズム問題でよく使われます。
vectorと配列の違い
C++では従来の配列も使えますが、多くの場面でvectorのほうが安全で柔軟です。
主な違いは次の通りです。
配列
・サイズ固定
・機能が少ない
・メモリ管理を自分で考える必要がある
vector
・サイズ変更可能
・豊富な操作
・STLアルゴリズムと統合されている
そのため、現代のC++では配列よりvectorが推奨されることが多いです。
vectorを使うときの注意点
vectorを安全に使うためには、いくつかの注意点があります。
まず、存在しないインデックスにアクセスしてはいけません。
これは未定義動作になるため、プログラムの挙動が保証されません。
また、途中削除や途中挿入は処理コストが高くなるため、頻繁に行う設計は避けたほうがよい場合があります。
さらに、削除や挿入の操作を行うと、既存のイテレータや参照が無効になることがあります。
これらの性質を理解して使うことが重要です。
まとめ
vectorはC++で最も重要なコンテナの一つであり、多くのプログラムで中心的な役割を持っています。
主な特徴は次の通りです。
・動的にサイズが変化する配列
・ランダムアクセスが高速
・末尾追加が高速
・STLアルゴリズムと相性が良い
一方で、
・途中挿入や削除はコストが高い
・範囲外アクセスは未定義動作になる
といった特性も理解しておく必要があります。
C++を本格的に学ぶ場合、vectorの理解はSTL全体の理解につながる非常に重要な基礎になります。
以上、C++のvectorの使い方についてでした。
最後までお読みいただき、ありがとうございました。
