1. ホーム
  2. C++

c++ベクトル使用集

2022-02-20 22:08:19

I.

c++では、ベクターは非常に便利なコンテナです。

1. 基本操作

(1) ヘッダーファイル #include<vector>.

(2) ベクトルオブジェクト、vector<int> vec を作成します。

(3) 最後に数字を挿入する: vec.push_back(a);

(4) 添え字を使って要素にアクセスする cout<<vec[0]<<endl; 添え字は0から始まることを忘れないようにしましょう。

(5) イテレータを使った要素へのアクセス。

vector<int>::iterator it;
for(it=vec.begin();it!=vec.end();it++)
    cout<<*it<<endl;

(6) 要素の挿入: vec.insert(vec.begin()+i,a); i+1番目の要素の前にaを挿入する。

(7) 要素の削除: vec.erase(vec.begin()+2); 3番目の要素を削除する。

vec.erase(vec.begin()+i,vec.end()+j); 削除 区間 [i,j-1]; 区間は0から始まります。

(8) ベクトルサイズ:vec.size()。

(9) クリア: vec.clear()。

vectorの要素はint, double, stringだけでなく、構造体も可能ですが、構造体はグローバルに定義しないとエラーになるので注意が必要です。以下は、簡単なプログラムコードです。

#include<stdio.h>
#include<algorithm>
#include<vector>
#include<iostream>
using namespace std;

typedef struct rect
{
    int id;
    int length;
    int width;

  // For vector elements that are structures, you can define comparison functions inside the structure, and the following are sorted by id,length,width in ascending order.
  bool operator< (const rect &a) const
    {
        if(id!=a.id)
            return id<a.id;
        else
        {
            if(length!=a.length)
                return length<a.length;
            else
                return width<a.width;
        }
    }
}Rect;

int main()
{
    vector<Rect> vec;
    Rect rect;
    rect.id=1;
    rect.length=2;
    rect.width=3;
    vec.push_back(rect);
    vector<Rect>::iterator it=vec.begin();
    cout<<(*it).id<<' '<<(*it).length<<' '<<(*it).width<< endl;    

    return 0;
}

2. アルゴリズム

(1) 要素の反転にreverseを使用:ヘッダファイル#include<algorithm>が必要。

reverse(vec.begin(),vec.end()); 要素を反転させます (ベクトルで、関数内で 2 つのイテレータが必要な場合)。

通常、後者は含まれません ...)

(2) sortによるソート:ヘッダファイル#include<algorithm>が必要です。

sort(vec.begin(),vec.end()); (デフォルトは昇順、すなわち小さいものから大きいものへのソート).

以下のようにソート比較関数をオーバーライドすることで、降順で比較することができます。

ソート比較関数を定義する。

bool Comp(const int &a,const int &b)
{
    return a>b;
}

sort(vec.begin(),vec.end(),Comp) と共に呼び出され、降順にソートされます。

II.

はじめに

この記事の目的は、std::vectorの紹介とそのメンバ関数やその他の操作の適切な使用方法について説明することです。また、remove_if()やfor_each()のような反復アルゴリズムにおける条件関数や関数ポインタの使用についても説明します。この記事を読めば、読者はベクトルコンテナを効果的に使うことができ、C型の動的配列に戻る必要はないはずです。

ベクターの概要

vectorはC++標準テンプレートライブラリの一部で、様々なデータ構造とアルゴリズムを操作できるテンプレートクラスと関数の汎用ライブラリです。vectorはコンテナのように様々な型のオブジェクトを保持できるため、コンテナとみなされます。簡単に言えば、vectorは任意の型を保持でき、データの追加や圧縮ができる動的配列なのです。

ベクターを使用できるようにするには、ヘッダーファイルに以下のコードを記述する必要があります。

#include <vector>

vectorはstdの命名領域に属しているので、以下のように命名して修飾する必要があり、コードを完成させることができます。

using std::vector;

vector<int> vInts;

または、フルネームで連結してください。

std::vector<int> vInts;

グローバルな名前付きフィールドのアプローチを使用することが推奨されます。

using namespace std;

グローバル名前空間のアプローチは、後の操作で問題を起こす可能性があります。vectorコンテナは多くのインターフェースを提供し、以下の表はvectorのメンバー関数と操作の一覧です。

ベクターメンバー関数

<テーブル

機能一覧

表現方法

c.assign(beg,end)

c.assign(n,elem)

beg; end)区間のデータをcに代入する。

n elemのコピーをcに割り当てる。

c.at(idx)

インデックス idx で参照されるデータを返し、idx が範囲外の場合は out_of_range をスローします。

c.back()

このデータが存在するかどうかをチェックせずに、最後のデータを返します。

c.begin()

イテレータを多用するcan a dataを返します。

c.capacity()

コンテナ内のデータ数を返します。

c.clear()

コンテナからすべてのデータを削除します。

c.empty()

コンテナが空かどうかを判断する。

c.end()

イテレータの最後のデータ・アドレスを指す。

c.erase(pos)

c.erase(beg,end)です。

posの位置のデータを削除し、次のデータ位置に渡す。

beg,end)区間のデータを削除し、次のデータ位置へ戻す。

c.front()

最初のデータを引き渡します。

get_allocator

コピーを返すには、コンストラクタを使用します。

c.insert(pos,elem)

c.insert(pos,n,elem)

c.insert(pos,beg,end)

elem のコピーを pos の位置に挿入し、新しいデータ位置を返します。

n個の elem データを pos 位置に挿入する。戻り値なし。

beg,end)区間にあるデータをpos位置に挿入する。戻り値なし。

c.max_size()

コンテナ内の最大データ量を返します。

c.pop_back()

最後のデータを削除します。

c.push_back(elem)

最後にデータを追加します。

c.rbegin()

逆キューの最初のデータを渡す。

c.rend()

逆キューの最後のデータの次の位置を渡す。

c.resize(num)

キューの長さを再指定する。

c.reserve()

適切な量を保持する。

c.size()

コンテナ内の実際のデータ数を返します。

c1.swap(c2)

スワップ(c1,c2)

c1 と c2 の要素を入れ替える。

上記と同じ操作です。

ベクトル<エレム> c

ベクトル <Elem> c1(c2)

ベクトル <Elem> c(n)

ベクトル <Elem> c(n, elem)

ベクトル <Elem> c(beg, end)

c.~ ベクトル <Elem>()

空のベクトルを作成します。

ベクターをコピーします。

デフォルトの構築で生成されたn個のデータでベクトルを作成します。

elem を n 個コピーしたベクトルを作成します。

beg;end)の間隔を持つベクトルを作成します。

すべてのデータを破棄し、メモリを解放する。

ベクター操作

<テーブル

機能一覧

説明

演算子[]です。

コンテナ内の指定された位置への参照を返します。

 ベクターを作成する

ベクターコンテナには様々な作成方法がありますが、一般的なものを以下に説明します。

Widget 型の空のベクターオブジェクトを作成する。

<テーブル

vector<Widget> vWidgetsです。

// ------

// |

// |- ベクターはコンテナなので、そのメンバー関数は

// イテレータとコンテナそのものを操作するので

// 任意の型のオブジェクトを保持することができる。

500個のウィジェットタイプのデータでベクターを作成します。

vector<Widget> vWidgets(500);

500 個のウィジェットを持つベクトルを作成し、すべて 0 に初期化します。

vector<Widget> vWidgets(500, Widget(0));

Widgetのコピーを作成します。

vector<Widget> vWidgetsFromAnother(vWidgets);

ベクターにデータを追加する

ベクターにデータを追加するデフォルトの方法は、push_back()です。push_back() は、データがベクターの末尾に追加され、必要に応じてメモリが割り当てられることを示す関数です。例えば、vector<Widget>に10個のデータを追加する場合、以下のようなコードを書く必要があります。

for(int i= 0;i<10; i++)
    vWidgets.push_back(Widget(i));

ベクター内の指定した位置のデータを取得する

多くの場合、ベクターの中にどれだけのデータがあるのかを知る必要はありません。ベクター内のデータは動的に割り当てられ、push_back()を使った一連の割り当ては、ファイルや何らかのデータソースによって決定されることが多いのです。ベクターに格納されているデータの量を知りたい場合は、empty()を使用します。ベクターの大きさを知るには size() を使用します。たとえば、ベクトル v のサイズを取得したいが、それが空なのかすでにデータが格納されているのかがわからず、空なら -1 にしたい場合は、次のようなコードで実現できます。

int nSize = v.empty() ? -1 :static_cast<int>(v.size());

ベクター内のデータへのアクセス

2つのメソッドを使って、ベクターにアクセスします。

1. vector::at()

2.ベクトル::オペレータ[]

operator[]は主にCとの互換性のためで、Cの配列のように操作することができます。しかし、at()は境界チェックを行い、アクセスがベクターの範囲を超えている場合は例外を投げるので、at()を第一候補としています。operator[]はエラーが発生しやすいので、以下で検証するように、ほとんど使用しません。

次のコードを解析してください。

vector<int> v;
v.reserve(10);

for(int i=0; i<7; i++)
    v.push_back(i);

try
{
    int iVal1 = v[7]; // not bounds checked - will not throw
    int iVal2 = v.at(7); // bounds checked - will throw if out of range
}

catch(const exception& e)
{
    cout << e.what();
}

reserve()を使って、int型スペースを10個割り当てていますが、初期化なしには使えません。 

このコードでいろいろな条件を試して、その結果どうなるかを見ることができますが、at()を使うときはいつでも正しいのです。

ベクターからデータを削除する

ベクターは非常に簡単にデータを追加でき、また非常に簡単にデータを取り出すことができます。同様に、vector にはデータを削除するための erase(), pop_back(), clear() が用意されています。データを削除する場合、末尾のデータを削除するのか、すべてのデータを削除するのか、個々のデータを削除するのかを知っておく必要があります。deleteのような操作を考える前に、STLでの応用を少し考えてみましょう。

Remove_if() アルゴリズム

では、中のデータを操作することを考えよう。remove_if()を使うには、以下のコードをヘッダーファイルに記述する必要があります。

#include <algorithm>

Remove_if()には3つの引数があります。

1. イテレータ _First: 最初のデータへのイテレータポインタ。

2. iterator _Last: 最後のデータへのイテレータポインタ。

3. 述語 _Pred: イテレーションを操作することができる条件関数。

条件付き関数

条件付き関数は、ユーザーが定義した条件に従ってイエスまたはノーの結果を返す関数ポインタ、または関数オブジェクトです。この関数オブジェクトは、すべての関数呼び出し操作をサポートし、operator()()操作をオーバーライドする必要があります。remove_if()は unary_function から継承され、条件としてデータの受け渡しが可能です。

例えば、vector<CString>にある値が含まれている場合、その値から始まり、その値で終わるように、一致するデータを削除したいとしましょう。まず、このデータを格納するデータ構造を、次のようなコードで作成する必要があります。

#include <functional>

enum findmodes
{
    FM_INVALID = 0,
    FM_IS,
    FM_STARTSWITH,
    FM_ENDSWITH,
    FM_CONTAINS
};

typedefstruct tagFindStr
{
    UINT iMode;
    CString szMatchStr;
}FindStr;

typedef FindStr* LPFINDSTR;

そして、条件付き判定を処理する。

class FindMatchingString
    : public std::unary_function<CString,bool>
{
public:
    FindMatchingString(const LPFINDSTR lpFS) : m_lpFS(lpFS) {}
    bool operator()(CString& szStringToCompare)const
    {
        bool retVal =false;
        switch(m_lpFS->iMode)
        {
        case FM_IS:
            {
                retVal = (szStringToCompare == m_lpFDD->szMatchStr);
                break;
            }
        case FM_STARTSWITH:
        {
            retVal = (szStringToCompare.Left(m_lpFDD->szMatchStr.GetLength()) == m_lpFDD->szWindowTitle);
            break;
        }
        case FM_ENDSWITH:
        {
            retVal = (szStringToCompare.Right(m_lpFDD->szMatchStr.GetLength()) == m_lpFDD->szMatchStr);
            break;
        }
        case FM_CONTAINS:
        {
            retVal = (szStringToCompare.Find(m_lpFDD->szMatchStr) ! = -1);
            break;
        }
    }

    return retVal;
}

private:
    LPFINDSTR m_lpFS;
};


この操作で、ベクターからデータを効率的に削除することができます。

// remove all strings containing the value of
// szRemove from vector<CString> vs.

FindStr fs;
fs.iMode = FM_CONTAINS;
fs.szMatchStr = szRemove;
vs.erase(std::remove_if(vs.begin(), vs.end(), FindMatchingString(&fs)), vs.end());

Remove_if() は何をすることができますか?

上の例でremove_if()を呼び出すときにerase()を使うのはどうなんだろう、と思うかもしれません。これは、あなたがSTLのアルゴリズムに慣れていないからです。remove(), remove_if(), その他すべてのremove操作は、繰り返し範囲の上に構築されており、その後、コンテナ内のデータを操作することはできません。つまり、remove_if()を使うときは、実際にはコンテナ内のデータの先頭で操作していることになります。上の例を見てみましょう。

1. szRemove = "o"。

2. vs 下図の表示をご覧ください。

この結果を見ると、remove_if()は実際にデータの後ろに残っている、削除すべきデータがあるという条件に基づいて、繰り返しアドレスに変更を加えていることがわかります。残っているデータの位置は元のデータとは限らないが、それらは不明である。

erase()を呼んで、その残留データを削除します。なお、上の例では、remove_if()の結果と vs.enc() の範囲のデータが erase() で削除されています。

肥大化したベクターを圧縮する

多くの場合、多くのデータを削除したり、reserve()を使用したりすると、実際に必要な容量よりもはるかに多くの容量を持つベクターになります。Clear() はキャッシュのサイズを変更するだけで、 ベクターにとってはメモリの解放など非常に重要なことばかりです。これらの問題を解決する方法、やってみましょう。

ベクトルから別のベクトルを作ることができますので、ここでどうなるかを見てみましょう。すでにメモリサイズ1000のベクトルvがあり、size()を呼んだら7しかなかったとします。大量のメモリを浪費してしまいました。その上にベクトルを作ってみましょう。

std::vector<CString> vNew(v);
cout << vNew.capacity();

vNew.capacity()は7を返します。これは、新しく作成された領域は、実際のサイズに基づいてのみ割り当てられることを意味します。さて、vは他の場所で必要になるので解放したくないのですが、swap()を使ってvとvNewを互いに交換することはできるでしょうか?

vNew.swap(v);
cout << vNew.capacity();
cout << v.capacity();

面白いことに、vNew.capacity()は1000で、v.capacity()は7です。

これで望んだとおりになったが、あまりいい解決策とはいえない、次のように書くこともできる。

std::vector<CString>(v).swap(v);

何をやったかわかりますか?名前付きの変数の代わりに一時的な変数を作成し、swap()を使って不要なスペースを取り除き、vの実際のサイズを取得したのです。

まとめ

このドキュメントが、STLベクトルコンテナを使用する開発者にとって、貴重なリファレンスになることを願っています。また、この記事を読むことで、C言語でデータの代わりにベクトルを使うことに抵抗がなくなることを期待しています。