C++には複数のデータをまとめて扱うための方法がいくつか存在します。
その中でも特に重要なのが、標準ライブラリに含まれる vector(ベクトル) というコンテナです。
vectorは、要素数を自由に増減できる 可変長配列(dynamic array) を実装したデータ構造です。
C++では固定サイズの配列も使用できますが、サイズを後から変更することができません。
一方、vectorは必要に応じて要素を追加したり削除したりできるため、柔軟にデータを管理できます。
このような理由から、現代のC++では可変長の配列が必要な場面では、従来の配列よりもvectorが使われることが多くなっています。
vectorの基本的な特徴
vectorの最大の特徴は、要素数を自由に変更できることです。
要素が追加されると内部的にサイズが増え、削除されるとサイズが減ります。
またvectorは、要素を 連続したメモリ領域 に格納するという性質を持っています。この特徴により、次のような利点があります。
- 任意の位置の要素へ高速にアクセスできる
- 先頭から順番にデータを処理する際の効率が良い
- キャッシュ効率が高い
このため、配列と同じような感覚で扱えるうえ、より柔軟に利用できるデータ構造として広く使われています。
vectorの初期状態
vectorを作成した直後は、通常は要素が存在しない空の状態になります。
必要に応じて要素を追加することで、vectorのサイズは自動的に増えていきます。
またvectorは、あらかじめ一定数の要素を持つ形で作成することもできます。
この場合、指定した数だけ要素が用意され、それぞれの要素は 値初期化 という方法で初期化されます。
例えば整数型の場合、値初期化された要素の値は0になります。
ただしこれは整数型の性質によるものであり、型によって初期値の扱いは異なります。
さらにvectorでは、初期値を指定して要素をまとめて作成することも可能です。
この方法を使うと、すべての要素を同じ値で初期化できます。
要素の追加
vectorには要素を追加するための仕組みが用意されています。
一般的には、vectorの末尾に新しい要素を追加する操作が最もよく使われます。
この操作によって、vectorのサイズは1つ増加します。
ただしvectorには内部的な容量の概念があり、その容量を超えて要素を追加すると 再配置(reallocation) が発生します。
再配置が起こると、次のような処理が行われます。
- より大きなメモリ領域を新たに確保する
- 既存の要素を新しい領域へコピーまたはムーブする
- 古いメモリ領域を解放する
この処理によってvectorはより多くの要素を格納できるようになります。
ただし再配置が発生すると、以前のメモリ領域に依存していた ポインタ、参照、イテレータ が無効化される可能性があります。
この点はvectorを使う際の重要な注意点です。
要素の削除
vectorでは要素を削除することも可能です。
もっとも簡単な削除方法は、末尾の要素を取り除く操作です。
この操作ではvectorのサイズが1つ減少します。
また、特定の位置にある要素を削除することもできます。
ただしvectorは連続したメモリを使って要素を管理しているため、途中の要素を削除すると、その後ろにある要素を前へ詰める必要があります。
このため、末尾以外の削除には 線形時間(O(n)) の処理コストがかかります。
さらに、削除された位置以降の要素に対するイテレータや参照は無効化されることがあります。
要素へのアクセス
vectorの要素には、配列と同じようにインデックスを使ってアクセスできます。
この方法は非常に高速ですが、範囲チェックは行われません。
もし存在しない位置にアクセスしてしまうと、プログラムの動作は未定義になります。
一方、vectorには範囲チェックを行う安全なアクセス方法も用意されています。
この方法では、範囲外アクセスが行われた場合に例外が発生します。
そのため、安全性を重視する場合にはこちらの方法を使うこともあります。
sizeとcapacityの違い
vectorには「サイズ」と「容量」という2つの概念があります。
サイズは、現在vectorに格納されている要素数を表します。
容量は、再配置を行わずに格納できる最大要素数を意味します。
例えば、現在3つの要素が格納されていて容量が4の場合、もう1つ要素を追加しても再配置は発生しません。
しかしそれ以上の要素を追加すると、vectorはより大きなメモリ領域を確保する必要があります。
容量の事前確保
vectorでは、あらかじめ一定量の容量を確保しておくことができます。
この操作は、大量の要素を追加することが分かっている場合に有効です。
事前に容量を確保しておくことで、再配置が発生する回数を減らし、処理性能を改善できる場合があります。
ただしこの操作は、必ずしもすべての再配置を完全に防ぐわけではありません。
ループ処理
vectorの要素は、さまざまな方法で順番に処理できます。
もっとも基本的な方法は、インデックスを使って先頭から順番に要素を取り出す方法です。
また、C++11以降では範囲ベースのループ構文を使って、より簡潔にすべての要素を処理することもできます。
さらに、イテレータという仕組みを使う方法もあります。
イテレータはコンテナ内の要素を順番に指し示すオブジェクトで、STLアルゴリズムと組み合わせて使われることが多いです。
2次元vector
vectorは入れ子構造にすることで、多次元のデータを扱うことができます。
例えば、vectorの中に別のvectorを格納することで、表形式のデータを表現することが可能です。
ただしこの構造は、あくまでvectorの入れ子であり、C言語の多次元配列とはメモリ配置が異なります。
各行のvectorは独立したメモリ領域を持つため、完全に連続したメモリ構造になるわけではありません。
vectorの性能特性
vectorは多くの場面で効率的に動作しますが、すべての操作が高速というわけではありません。
vectorが得意とする操作には次のようなものがあります。
- 任意位置へのランダムアクセス
- 末尾への要素追加
- 先頭から順番にデータを処理する操作
一方で、次のような操作はあまり得意ではありません。
- 先頭への挿入
- 途中への挿入
- 途中の要素削除
これらの操作では要素を移動させる必要があるため、処理コストが大きくなります。
vectorを使うメリット
vectorが広く使われている理由は次の通りです。
- メモリ管理を自動で行う
- 要素数を自由に変更できる
- ランダムアクセスが高速
- STLアルゴリズムと相性が良い
- 連続メモリによる高いキャッシュ効率
これらの特徴により、vectorはC++で最も基本的かつ重要なコンテナの一つとされています。
vectorを使う際の注意点
vectorを使用する際には、いくつか注意しておくべきポイントがあります。
まず、途中挿入や途中削除は処理コストが高くなります。
また、容量不足によって再配置が発生すると、イテレータや参照が無効化されることがあります。
さらに、インデックスによるアクセスは範囲チェックを行わないため、誤った位置にアクセスすると未定義動作になる可能性があります。
これらの特性を理解したうえで使用することが重要です。
まとめ
vectorは、C++標準ライブラリに含まれる可変長配列コンテナです。
主な特徴は次の通りです。
- 要素数を自由に増減できる
- 要素は連続したメモリに格納される
- ランダムアクセスが高速
- STLアルゴリズムと組み合わせて使いやすい
以上、C++のベクトルについてでした。
最後までお読みいただき、ありがとうございました。
