MSVCRT std::vector
概要
vectorは配列のようなものを扱うのに必要な機能を含んでいます。テンプレート名に指定した型を順序立てて保持し、先頭から後尾までを順番に取り出したり要素数を調べたり、途中の情報から一方向に抜きだしたりといった操作ができます。テンプレートに複数の要素を含む型を指定した場合は多次元配列のようにも振る舞い。PHPやPerlで言うハッシュ配列というモノに近い感じです。数値の型をテンプレート名に与えると、要素同士の比較もできます。vectorのようにあらゆる型のデータを格納できる型になるものをコンテナと読んでいますがここではあまりコンテナという呼び方はしないようにします。コンテナという単語がの意味が貨物船にのせるあの大きな箱をイメージしてしまう人が多いように思います。ここでのコンテナは内包するという意味でどちらかと言うとキッチン道具を入れたりする仕切り箱くらいのもので、その中にきちんといくつかの任意の型の変数を格納するようなイメージなのです。あのでっかい船のコンテナだとなんでも詰め込めそうで汎用性が恐ろしく高いように勘違いするのかな?
はなしはもどって、以下のように定義して使います。
std::vector<クラス・構造体・型テンプレート名> 変数名;
例
std::vector<QByteArray> objVectorQByteArray_UTF8String;
のようにするとVectorクラスのメンバ関数とメンバ変数を含んだ状態のQByteArrayクラスのオブジェクトが作られ。例ではオブジェクトobjVectorQByteArray_UTF8Stringが作られます。
このようにして作られたオブジェクトは以下のような階層構造で、メンバ関数が呼び出せて、テンプレート名とした型のobjectを操作できます。
objVectorQByteArray_UTF8String.(vectorクラスのメンバ関数or変数)
1番簡単な例はテンプレート名をintにすることです。最初に3つの要素があって、20,40,50となっている状態でいろいろ操作してみましょう。
#include<cstdio>
#include<vector>
int main(int iArgc, char** cArgv){
std::vector<int> objVectorInt_data = {20, 40 ,60};
objVectorInt_data[2] = 80; //要素番号 2 の 60 が 80 に書き換えられる。
objVectorInt_data.Insert(objVectorInt_data.begin(), 10);
//要素番号の一番最初objVectorInt_data.begin()に数値データ10を挿入する。
objVectorInt_data.push_back(100);
//要素番号の最後に数値データ100を挿入する。
}
これがVectorの簡単な使い方です。ここまでは配列を使うのと同じような働きですが、メンバ関数で最初に挿入したり、最後に挿入したりすることを実現できています。自分で配列を操作して制御するよりは楽です。要素番号は0から始まります。
結果としてはobjVectorInt_dataは{10, 20, 40, 80, 100}になっています。
公開 Public
メンバ関数 data
vector<TypeTmplate> obj;とした場合
- TypeTemplate* obj.data() noexcept;
- const TypeTemplate* obj.data() const noexcept;
TypeTemplateと記載された部分のvectorを生成するときに指定した型で、 vectorに取り込まれた先頭のデータを返します。データが無くても動作しますが、dataがあるかないか分からないパターンが考えられる場合、受け取った側でdataが有ったか無かったかを確認する必要があります。
返される値がポインタ型なので、取り込まれたデータが指定した型のポインタ型である場合は受け取る変数は、更にそのポインタである必要があるため、必ず、TypeTempateで指定した型より*が一つ多い型で受け取ることになります。
このメンバ関数は、noexceptだ!というキーワードの指定がありますが、これは明示的にこの関数は例外をスローしないことを示しています。これもC++11からの比較的新しい技術です。noexcept(関数名)でその関数がnoexceptかどうかを判定したbool値が取得できます。例外を受け取ることはないので、例外処理を構築する必要が無いことを示しています。このような関数は動作も軽くなることを例外のために準備する処理がなく、それでいて、その処理が安定して動作することを示しています。この関数のように、データを返すだけの単純な関数によく指定されるものです。普段から例外処理を記述しまくっている人でなければ、あんまり気にする必要はないですね。
メンバ関数 Insert
vector<TypeTmplate> obj;とした場合
- Iterator obj.Insert(Iterator position, const TypeTemplate&& data); //上記のサンプルプログラムで使った引数の多重定義パターン
※なんかややこしい型名になっていますが、vector型で扱う他の関数も含めて、配列の位置情報を返す型はIteratorという型名になっています。独自の型名定義にすることで、間違った引数を受け取らないようになっています。数値を受け取るはずなので、内部では単純にInt型のようなものと同じように扱われている筈です。TypeTemplateと書いた部分はよく見るリファレンスなんかでは単に T と表記されるものです。TypeTemplateは型名です。上記のサンプルプログラムではIntで定義されている部分です。const int なので、直接数字を入れれば、const intの扱いになる感じです。Tあらゆる型に対応するためのTemplateというモノです。char** でもいいし、float、longなんでも来いや!なのです。因みに&&はCpp 右辺値参照です。右辺値参照の説明はリンク先にあります。サンプルプログラムの10は右辺値なのでね。そして、この関数自体も戻り値は Iterator になっています。
全部分からなくても良いですが、他にも同じことをするために様々なパターンで引数を設定できて、多重定義になっています。
iterator obj.insert( iterator position, const TypeTemplate& data);
iterator obj.insert(const_iterator position, const TypeTemplate& data);
void obj.insert( iterator position, size_type size, const TypeTemplate& data);
iterator obj.insert(const_iterator position, size_type n, const TypeTemplate& data);
iterator obj.insert(const_iterator position, initializer_list<TypeTmplate> Initializer_listTypeTemlate_obj);
template <class InputIterator>//を使う場合
void obj.insert( iterator position, InputIterator first, InputIterator last);
iterator obj.insert(const_iterator position, InputIterator first, InputIterator last);
Iteretor を戻り値にもつ関数は挿入した後に、挿入したものが指し示された要素番号が返ってきます。難しい表記が多い世の中だけど、よくよく見るとあらゆる人間が理解できるほど単純なのだ。恐れるべからず。
メンバ関数 assign
- void obj.assign(size_type n, const TypeTemplate& u);
- void obj.assign(InputIterator first, InputIterator last);
- void obj.assign(Initializer_list<TypeTemplate>);
メンバ関数 emplace_back
- void obj.emplace_back(TypeTemplate(vector<TypeTemplate>&&...));
- TypeTemplate&(refference_InsertValue) obj.emplace_back(TypeTemplate(vector<TypeTemplate>&&...));
TypeTemplate(vector<TypeTemplate>)はvectorの型テンプレートで型を生成するコンストラクタによって初期化形式である必要があります。?何を言っているの?
例えばvectorはvector<std::string> obj;のようにstd::string型を定義した場合。objはsdt::string型が順序を持って連続するような値になります。そうすると以下の例のように定義します。
sample.cpp
#include<cstidio>
#include<vector>
int main(int iArgc char** cArgv){
std::vector<std::string> objVectorStdString_data;
obj.emplace_back(std::string("Hello! VectorDataString!"));
//std::stringのコンストラクタでstd::string型そのものが右辺値参照として引数になる。
}
※C++17以降は2つ目に書いたように TypeTemplate& のような挿入したデータへの参照を返却され、受け取ることができます。
※似た動作をする関数にpush_backという関数があります。emplace_backでは完全転送という処理が行われるためコンストラクタの呼び出しが少なく、処理速度が向上するという利点があります。push_backでは、値を渡したときにコンストラクタがそのあとvector型に格納するときもコンストラクタが呼ばれる仕組みになっています。そしてムーブ処理や値のコピーの処理が発生します。一方emplace_backは右辺値を渡した場合、コンストラクタが呼ばれてそのままvector型に取り込まれます。渡す値が右辺値でない場合は、計算量は同じになります。大量のTypeTemplate型のコンストラクタの引数に合致するデータでコンストラクタの処理が複雑の場合にvector型に格納するときには、良いと思います。大量ですよ。大量。最初の1回の処理には時間がかかるので、大量にvector型を使って、小さなオブジェクトを大量に作る場合もemplace_pushがいいみたい。じゃpush_backって利点がないんじゃ?ふむ、自分もそう思います。
メンバ関数 push_back
- void obj.push_back(const TypeTemplate& typeObjValue);
- void obj.push_back(TypeTemplate&& typeObjValue);
TypeTemplateはVector<>の<>の中に記述された型名です。
例えばvector<std::string> obj;のようにstd::string型を定義した場合。objはsdt::string型の値になっている必要があります。そうすると以下の例のように定義します。
sample.cpp
#include<cstidio>
#include<vector>
int main(int iArgc char** cArgv){
std::vector<std::string> objVectorStdString_data;
std::vector<std::string> obj;
objVectorStdString_data.emplase_back(std::string("Hello! VectorDataString!"));
//std::stringのコンストラクタでstd::string型そのものが右辺値参照として引数になる。
for(std::string objStdString_arg: objVectorStdString_data){
obj.push_back(objStdString_arg.data())
}
}
範囲に基づくfor文を利用して、Vector<std::string>型のstd::string型の値をひとつづつを取り出しています。分からない人はCpp 範囲に基づく for文の記事を参照して下さい。C++言語版のforeachと同様の機能がC++11から追加されたことについての記事になっています。
※似た動作をするものとしてemplace_backがありますが、emplace_backの方が動作的には速いです。速い理由はemplace_backの説明の方に記載しています。敷き詰めれば、使う利点は少ないです。