読み込み中...

【C++】vectorクラス完全ガイド7選

C++のvectorクラスを徹底解説するイメージ C++
この記事は約17分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事を読むことで、C++のvectorクラスについての理解が深まります。

プログラミング初心者から上級者まで、vectorクラスの基本から応用までを網羅し、実際のコード例を交えながら丁寧に解説していきます。

C++のvectorクラスは、非常に強力なデータ構造の一つであり、多くのプログラムで利用されています。

本記事を通して、その機能と使い方を完全にマスターしましょう。

●vectorクラスとは

C++で提供される標準テンプレートライブラリの一部であるvectorクラスは、動的配列を提供します。

静的配列と異なり、vectorは実行時にそのサイズを変更できる柔軟性を持っています。

これにより、プログラマは配列の長さを事前に知る必要がなく、動的にデータを追加または削除することができます。

また、vectorは要素へのランダムアクセス、つまり任意の位置の要素に対する高速なアクセスをサポートしています。

○vectorクラスの基本概念

vectorクラスはテンプレートクラスであり、任意の型の要素を保持することができます。

例えば、int型、string型、またはユーザー定義型など、さまざまな型のオブジェクトをvectorで扱うことが可能です。

内部的には、vectorは動的に拡張可能な配列として実装されており、要素の追加や削除によって容量が自動的に調整されます。

この性質は、プログラムの柔軟性を高め、複雑なデータ構造の実装を容易にします。

○vectorクラスの特徴と利点

vectorクラスの最大の特徴はその動的なサイズ変更能力です。

これにより、プログラムの実行中に配列のサイズを増減させることができ、柔軟なプログラミングが可能になります。

また、vectorは標準テンプレートライブラリの一部であるため、ポータビリティが高く、様々なプラットフォームで利用することができます。

さらに、vectorはイテレータをサポートしているため、範囲ベースのループやアルゴリズムとの組み合わせが容易になります。

これらの特徴により、vectorはC++プログラミングにおいて非常に汎用的で強力なツールとなっています。

●vectorクラスの基本的な使い方

C++におけるvectorクラスの基本的な使い方を理解することは、効率的なプログラミングへの第一歩です。

vectorクラスは、その柔軟性とパワフルな機能により、多くのシナリオで利用されます。

ここでは、vectorの宣言、初期化、要素の追加とアクセス、イテレータの使用、サイズと容量の管理について、詳細に解説していきます。

○サンプルコード1:vectorの宣言と初期化

C++のvectorを使用するには、まずvectorクラスをインクルードする必要があります。

下記のサンプルコードでは、int型のvectorを宣言し、初期化しています。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    for (int num : numbers) {
        std::cout << num << " ";
    }
    return 0;
}

このコードは、整数型のvectorを作成し、初期値を与えています。

その後、範囲ベースのforループを使用して、vector内の各要素を出力しています。

この例では、vectorの基本的な宣言方法と初期化のプロセスを表しています。

○サンプルコード2:要素の追加とアクセス

vectorに要素を追加するには、push_backメソッドを使用します。

また、特定の要素にアクセスするには、添え字演算子やatメソッドを使います。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers;
    numbers.push_back(10);
    numbers.push_back(20);
    numbers.push_back(30);

    std::cout << "First element: " << numbers[0] << std::endl;
    std::cout << "Second element: " << numbers.at(1) << std::endl;
    return 0;
}

このコードでは、まず空のvectorを作成し、push_backを使って3つの整数を追加しています。

その後、最初と2番目の要素にアクセスし、それらを出力しています。

この方法で、vectorの動的な特性を活用し、実行時に要素を追加することができます。

○サンプルコード3:イテレータを使った操作

vectorのイテレータは、コンテナ内の要素を指し示すために使用されます。

イテレータを使用することで、vector内の要素をより柔軟に操作できます。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    for (auto it = numbers.begin(); it != numbers.end(); ++it) {
        std::cout << *it << " ";
    }
    return 0;
}

このコードでは、イテレータitを使ってvectorの各要素にアクセスしています。

イテレータはvectorのbeginメソッドで取得し、endメソッドで終端を判定しています。

*itにより、イテレータが指し示す現在の要素の値にアクセスしています。

○サンプルコード4:サイズと容量の管理

vectorのサイズと容量を理解し、管理することは重要です。

サイズはvector内の要素数を、容量はvectorが割り当てられたメモリの量を指します。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers;
    std::cout << "Initial size: " << numbers.size() << std::endl;
    std::cout << "Initial capacity: " << numbers.capacity() << std::endl;

    numbers.push_back(1);
    std::cout << "Size after adding an element: " << numbers.size() << std::endl;
    std::cout << "Capacity after adding an element: " << numbers.capacity() << std::endl;
    return 0;
}

このコードでは、最初にvectorのサイズと容量を表示し、要素を追加した後のサイズと容量を再度表示しています。

vectorは必要に応じて自動的にメモリを割り当てるため、容量はサイズを超えることがあります。

●vectorクラスの応用例

C++のvectorクラスは、その基本的な使い方を超えて、様々な応用が可能です。

ここでは、vectorを使ったソート、検索、カスタムオブジェクトの使用、多次元ベクターの操作、そしてアルゴリズムとの組み合わせについて、実際のサンプルコードを交えて詳しく解説します。

○サンプルコード5:ソートと検索

vectorをソートするためには、標準ライブラリのsort関数を使用します。

また、find関数を使って特定の要素を検索することもできます。

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> numbers = {4, 1, 3, 5, 2};
    std::sort(numbers.begin(), numbers.end());

    for (int num : numbers) {
        std::cout << num << " ";
    }

    auto result = std::find(numbers.begin(), numbers.end(), 3);
    if (result != numbers.end()) {
        std::cout << "\nNumber 3 found at position: " << std::distance(numbers.begin(), result);
    } else {
        std::cout << "\nNumber 3 not found";
    }
    return 0;
}

このコードでは、まずvectorを昇順にソートし、次に3という値を検索しています。

std::distance関数を使用して、見つかった要素の位置を表示しています。

○サンプルコード6:カスタムオブジェクトの使用

vectorはカスタムオブジェクトを格納することもできます。

ここでは、独自のクラスを作成し、それをvectorで管理します。

#include <vector>
#include <iostream>

class Item {
public:
    std::string name;
    int value;

    Item(std::string name, int value) : name(name), value(value) {}
};

int main() {
    std::vector<Item> items;
    items.push_back(Item("Apple", 5));
    items.push_back(Item("Banana", 2));

    for (const Item& item : items) {
        std::cout << item.name << " has value " << item.value << std::endl;
    }
    return 0;
}

このコードでは、Itemクラスのインスタンスをvectorに追加し、その内容を出力しています。

カスタムクラスを使うことで、複雑なデータ構造を効率的に管理できます。

○サンプルコード7:多次元ベクターの操作

vectorは多次元配列のようにも扱えます。

下記のサンプルでは、2次元のvectorを使用しています。

#include <vector>
#include <iostream>

int main() {
    std::vector<std::vector<int>> matrix = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    for (const auto& row : matrix) {
        for (int num : row) {
            std::cout << num << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}

このコードでは、3行3列の2次元vector(行列)を作成し、各要素を出力しています。

多次元vectorは、表やグリッドのようなデータ構造の表現に適しています。

○サンプルコード8:アルゴリズムとの組み合わせ

vectorは標準ライブラリのアルゴリズムと組み合わせて使用することができます。

ここでは、for_eachアルゴリズムを使用して、vectorの各要素に操作を適用しています。

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    std::for_each(numbers.begin(), numbers.end(), [](int& num) {
        num *= 2;
    });

    for (int num : numbers) {
        std::cout << num << " ";
    }
    return 0;
}

このコードでは、for_each関数とラムダ式を使用して、vector内の各要素を2倍にしています

標準ライブラリのアルゴリズムを利用することで、vectorの操作をより柔軟かつ強力に行うことができます。

●注意点と対処法

C++のvectorクラスを使用する際には、いくつかの重要な注意点があります。

これらを理解し、適切に対処することで、より安全で効率的なコードを書くことができます。

ここでは、メモリ管理、パフォーマンスの最適化、例外処理の重要性について説明します。

○メモリ管理の注意点

vectorは動的にメモリを割り当てますが、不適切な管理はメモリリークやパフォーマンスの問題を引き起こす可能性があります。

vectorのサイズを減らす際には、shrink_to_fit関数を使用して未使用のメモリを解放することが重要です。

また、vectorが不要になった場合には、明示的にclear関数を呼び出し、メモリを解放することが推奨されます。

○パフォーマンスに関するヒント

大量のデータをvectorに追加する場合、予めreserve関数を使用して必要なメモリを確保することで、パフォーマンスを向上させることができます。

これにより、vectorのサイズ変更時のメモリ再割り当てのオーバーヘッドを避けることが可能です。

また、不必要なコピーを避けるために、オブジェクトをvectorに移動する(ムーブセマンティクスを利用する)ことも効果的です。

○例外処理と安全なコーディング

vectorを使用する際には、例外処理を適切に行うことが重要です。

特に、範囲外の要素にアクセスしようとすると、std::out_of_range例外が発生する可能性があります。

このような状況を避けるためには、at関数を使用して要素にアクセスすることが推奨されます。

at関数は、指定されたインデックスが範囲外である場合に例外を投げるため、より安全なコーディングが可能になります。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    try {
        std::cout << numbers.at(10) << std::endl; // 範囲外アクセス
    } catch (const std::out_of_range& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
    return 0;
}

このサンプルコードでは、at関数を使用してvectorの範囲外の要素にアクセスしようとしています。

このアクセスは例外を引き起こし、キャッチブロックで適切に処理されます。

このような例外処理を行うことで、予期しないエラーからプログラムを守ることができます。

●vectorクラスのカスタマイズ

C++のvectorクラスは、基本的な機能に加えてカスタマイズを行うことができます。

特に、カスタムアロケータの使用やメンバ関数のオーバーライドにより、特定のニーズに合わせたより効率的な動作を実現することが可能です。

ここでは、これらのカスタマイズ方法について具体的なサンプルコードを交えて解説します。

○サンプルコード9:カスタムアロケータの使用

C++の標準ライブラリは、デフォルトのメモリアロケータを使用しますが、vectorクラスではカスタムアロケータを定義して使用することができます。

これにより、メモリ割り当ての挙動をコントロールし、特定の環境や要件に最適化されたパフォーマンスを実現できます。

#include <vector>
#include <iostream>

// カスタムアロケータの定義
template <typename T>
class CustomAllocator {
public:
    using value_type = T;

    T* allocate(std::size_t n) {
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }

    void deallocate(T* p, std::size_t n) {
        ::operator delete(p);
    }
};

int main() {
    // カスタムアロケータを使用するvectorの宣言
    std::vector<int, CustomAllocator<int>> customVector;
    customVector.push_back(10);
    customVector.push_back(20);

    for (int num : customVector) {
        std::cout << num << " ";
    }
    return 0;
}

このサンプルコードでは、独自のメモリアロケータCustomAllocatorを定義し、それを使用してvectorをインスタンス化しています。

このようなカスタマイズにより、アプリケーション特有のメモリ管理戦略を実装することが可能です。

○サンプルコード10:メンバ関数のオーバーライド

vectorクラスのメンバ関数をオーバーライドすることで、vectorの挙動をカスタマイズすることもできます。

これにより、特定の操作に対するカスタムロジックを実装することが可能です。

#include <vector>
#include <iostream>

template <typename T>
class CustomVector : public std::vector<T> {
public:
    void push_back(const T& value) {
        std::cout << "Adding value: " << value << std::endl;
        std::vector<T>::push_back(value);
    }
};

int main() {
    CustomVector<int> myVector;
    myVector.push_back(10);
    myVector.push_back(20);

    for (int num : myVector) {
        std::cout << num << " ";
    }
    return 0;
}

このコードでは、std::vectorを継承したCustomVectorクラスを定義し、push_backメソッドをオーバーライドしています。

このメソッドは、要素が追加されるたびにその値を出力し、その後標準のpush_backメソッドを呼び出します。

このようにメンバ関数をオーバーライドすることで、vectorの動作をカスタマイズし、追加の機能を実装することができます。

まとめ

この記事では、C++のvectorクラスについて基本から応用、注意点、カスタマイズ方法に至るまで詳細に解説しました。

vectorクラスはその柔軟性と機能の豊富さから、C++におけるプログラミングにおいて非常に重要な役割を果たします。

適切な使い方を理解し、効率的かつ安全にコードを記述することで、C++プログラミングの幅が大きく広がります。

本ガイドを通じて、C++のvectorクラスを深く理解し、あらゆるプログラミングシナリオで活用していただければ幸いです。