最初はイメージしやすい「可変長配列」から説明しましょう。
「可変長配列」とは動的(プログラムの実行中)に大きさが変えられる配列を指します。
通常の配列は、
int data[10]; |
と宣言すると、要素数を変える事は出来ません。
しかし、malloc関数を使って、
int *p = (int*)malloc(sizeof(int) * 5); |
と書けば、最初から要素数を決めずに配列が使えました。
これで確保した領域を、プログラム中で増やしたり、減らしたりしながら使うような仕組みを「可変長配列」と言います。
C言語では、realloc関数などと組み合わせると作れそうです。
※realloc関数はC言語編で説明していません。
C++では、このような仕組みが最初から用意されています。
それが「vector:ベクタ」というコンテナ(データ構造)です。
ベクタの特徴を書きます。
・配列ベースのデータ構造 (添え字で各要素にアクセス可能) ・最後尾へデータを追加可能 ・最後尾のデータを削除可能 ・途中のデータに対する挿入や削除は苦手 |
データを配列にどんどん追加していくようなケースで使えそうなデータ構造です。
では実際に作ってみましょう。
ベクタの宣言方法から書いていきます。
<sample program cpp013-01>
#include <iostream> #include <vector> int main() { std::vector<int> vecData; return 0; } |
ヘッダファイルとして「vector」をインクルードします。
コンテナの「vector」も「名前空間std」に所属しています。
「vector」の後ろの < と > の間には「型」を書きます。
「型」は変数だけでなく、構造体でも大丈夫です。
これで宣言は出来ましたので、使い方について説明します。
上にも書きましたが、最後尾へのデータ追加を書き加えます。
<sample program cpp013-02>
#include <iostream> #include <vector> int main() { std::vector<int> vecData; vecData.push_back(5); return 0; } |
stringと同じく、vectorもメンバ関数を持っています。
push_back関数は、配列の最後尾にデータを追加する関数です。
細かく書くと、新しく配列の領域を確保し5をコピー(値渡し)しています。
本当に追加されているか、確認してみましょう。
<sample program cpp013-03>
#include <iostream> #include <vector> int main() { std::vector<int> vecData; vecData.push_back(5); std::cout << vecData[0] << std::endl; return 0; } |
<実行結果>
5 続行するには何かキーを押してください・・・
これも上で書きましたが、添え字で中身にアクセス出来ますから普通の配列と同じように扱えます。
試しに、配列の範囲を超えた添え字を指定した場合どうなるか実行してみましょう。
<sample program cpp013-04>
#include <iostream>
#include <vector>
int main()
{
std::vector<int> vecData;
vecData.push_back(5);
std::cout << vecData[1] << std::endl;
return 0;
}
|
<実行結果>
エラーで止まりました。
図の赤い枠で囲んだところに書いてあります。
vector subscript out of range (範囲外)
普通の配列と同じように、範囲を超えた添え字を指定することは危険です。
プログラムを変更して、もう少し詳しく説明します。
<sample program cpp013-05>
#include <iostream> #include <vector> int main() { const int DATA = 5; std::vector<int> vecData; for (int i = 0; i < DATA; i++) { vecData.push_back(i + 1); } for (int i = 0; i < DATA; i++) { std::cout << "vecData[" << i << "] = " << vecData[i] << std::endl; } return 0; } |
<実行結果>
vecData[0] = 1 vecData[1] = 2 vecData[2] = 3 vecData[3] = 4 vecData[4] = 5 続行するには何かキーを押してください・・・
for文で繰り返しながら5つのデータを追加しました。
その後、5つのデータを表示しています。
普通に配列と同じような使い方ですが、vectorには「現在の要素数を返すメンバ関数」が用意されています。
vecData.size() |
と書くと、現在の要素数が分かります。
そこで、次のように表示部分のプログラムを変更してみましょう。
<sample program cpp013-06>
#include <iostream>
#include <vector>
int main()
{
const int DATA = 5;
std::vector<int> vecData;
for (int i = 0; i < DATA; i++) {
vecData.push_back(i + 1);
}
for (int i = 0; i < vecData.size(); i++) {
std::cout << "vecData[" << i << "] = " << vecData[i] << std::endl;
}
return 0;
}
|
<実行結果>
vecData[0] = 1 vecData[1] = 2 vecData[2] = 3 vecData[3] = 4 vecData[4] = 5 続行するには何かキーを押してください・・・
ちゃんと表示されました。
これが何を示しているかと言うと、
ベクタの要素数を覚えておく必要が無い
と言う事です。
何個データを追加したか管理していなくても、全データの表示が出来ます。
プログラムを変えてみましょう。
<sample program cpp013-07>
#include <iostream> #include <vector> int main() { std::vector<int> vecData; int input; std::cin >> input; while (input > 0) { vecData.push_back(input); std::cin >> input; } for (int i = 0; i < vecData.size(); i++) { std::cout << "vecData[" << i << "] = " << vecData[i] << std::endl; } return 0; } |
<実行結果>
5 8 2 0 vecData[0] = 5 vecData[1] = 8 vecData[2] = 2 続行するには何かキーを押してください・・・
このプログラムではデータが何回入力されるかは不明です。
しかし、データの個数はベクタ自身が計算出来ますので、何個データを追加したか覚えておく必要はありません。
気づいた人もいるかもしれませんが、このプログラムは警告が1つ出ています。
warning C4018: '<': signed と unsigned の数値を比較しようとしました。
これは、
i < vecData.size() |
で発生しています。
変数iは「int型」、vecData.size()の戻り値は「size_t型」です。
「size_t型」は(私の環境では)「unsigned int型」をtypedefで再定義したものです。
「符号あり」と「符号無し」の数値を比較すると何がいけないのでしょうか?
コラム「signedとunsignedの比較」を読んでください。
この警告への対処法は、以下の通りです。
<sample program cpp013-08>
#include <iostream>
#include <vector>
int main()
{
std::vector<int> vecData;
int input;
std::cin >> input;
while (input > 0) {
vecData.push_back(input);
std::cin >> input;
}
for (unsigned int i = 0; i < vecData.size(); i++) {
std::cout << "vecData[" << i << "] = " << vecData[i] << std::endl;
}
return 0;
}
|
<実行結果>
5 8 2 0 vecData[0] = 5 vecData[1] = 8 vecData[2] = 2 続行するには何かキーを押してください・・・
変数iはマイナスになることは無いので、「unsigned int」で宣言し直しました。
次回もベクタについて書きます。