[C++] std::listの使い方について詳しく解説

std::listは、C++標準ライブラリに含まれる双方向連結リストを実装するコンテナです。

このコンテナは、要素の挿入や削除が効率的で、特にリストの途中での操作が頻繁に行われる場合に適しています。

リストは、push_backpush_frontを使って要素を追加し、pop_backpop_frontで要素を削除できます。

また、inserteraseを用いて任意の位置に要素を挿入・削除することも可能です。

イテレータを使用してリストを巡回し、要素にアクセスすることができます。

この記事でわかること
  • std::listの基本概念と特性
  • 基本操作(宣言、初期化、要素の追加・削除・アクセス)
  • イテレータの使い方とその種類
  • std::listのメンバ関数(サイズの取得、要素の検索、ソート)
  • std::listを使った連結リスト、キュー、スタックの実装例

目次から探す

std::listとは

C++の標準ライブラリに含まれるstd::listは、双方向連結リストを実装したコンテナです。

要素の追加や削除が効率的に行えるため、特定の状況で非常に便利です。

std::listの基本概念

  • 双方向連結リスト: 各要素が前後の要素へのポインタを持つため、リストの任意の位置での挿入や削除が容易です。
  • 動的サイズ: 要素数に応じて自動的にサイズが変わるため、事前にサイズを指定する必要がありません。
  • メモリ管理: 要素が追加されるたびにメモリが動的に割り当てられ、削除されると解放されます。

std::vectorとの違い

スクロールできます
特徴std::liststd::vector
メモリ構造双方向連結リスト動的配列
要素の追加/削除O(1)(任意の位置で)O(n)(中間位置での操作)
ランダムアクセス不可可能(O(1))
メモリ使用効率効率が悪い(ポインタ分)効率が良い(連続メモリ)

使用する場面

  • 頻繁な挿入・削除: リストの中間での要素の追加や削除が多い場合に適しています。
  • サイズが不定: 要素数が事前にわからない場合や、動的に変化する場合に便利です。
  • メモリのフラグメンテーションを避けたい: 大量のデータを扱う際に、メモリの断片化を避けるために使用されることがあります。

std::listは、特定の要件に応じて非常に有用なコンテナですが、使用する際にはその特性を理解しておくことが重要です。

std::listの基本操作

std::listを使用する際の基本的な操作について解説します。

これには、宣言と初期化、要素の追加と削除、要素へのアクセスが含まれます。

std::listの宣言と初期化

std::listを使用するには、まずヘッダーファイルをインクルードし、リストを宣言します。

以下は、std::listの宣言と初期化の例です。

#include <iostream>
#include <list>
int main() {
    // 空のリストを宣言
    std::list<int> myList;
    // 初期値を持つリストを宣言
    std::list<int> initializedList = {1, 2, 3, 4, 5};
    return 0;
}
(出力はありませんが、リストが正常に初期化されます)

要素の追加と削除

std::listでは、要素の追加や削除が簡単に行えます。

以下のメソッドを使用します。

  • push_back(): リストの末尾に要素を追加
  • push_front(): リストの先頭に要素を追加
  • pop_back(): リストの末尾から要素を削除
  • pop_front(): リストの先頭から要素を削除
  • remove(): 指定した値を持つ要素を削除

以下は、要素の追加と削除の例です。

#include <iostream>
#include <list>
int main() {
    std::list<int> myList;
    // 要素の追加
    myList.push_back(10);
    myList.push_front(5);
    myList.push_back(15);
    // 要素の削除
    myList.pop_front(); // 5を削除
    myList.remove(15);  // 15を削除
    // リストの内容を表示
    for (int value : myList) {
        std::cout << value << " ";
    }
    return 0;
}
10

要素へのアクセス

std::listはランダムアクセスができないため、要素へのアクセスはイテレータを使用します。

イテレータを使ってリストの要素を参照する方法を以下に示します。

#include <iostream>
#include <list>
int main() {
    std::list<int> myList = {1, 2, 3, 4, 5};
    // イテレータを使って要素にアクセス
    for (std::list<int>::iterator it = myList.begin(); it != myList.end(); ++it) {
        std::cout << *it << " ";
    }
    return 0;
}
1 2 3 4 5

このように、std::listを使うことで、要素の追加、削除、アクセスが簡単に行えます。

特に、要素の追加や削除が頻繁に行われる場合に、その特性を活かすことができます。

イテレータの使い方

std::listを操作する際に非常に重要な役割を果たすのがイテレータです。

イテレータを使うことで、リストの要素にアクセスしたり、操作したりすることができます。

イテレータの基本

イテレータは、コンテナ内の要素を指し示すオブジェクトです。

std::listでは、イテレータを使って要素を順に処理することができます。

イテレータは、ポインタのように振る舞い、begin()メソッドでリストの最初の要素を指し、end()メソッドでリストの最後の要素の次を指します。

以下は、イテレータの基本的な使い方の例です。

#include <iostream>
#include <list>
int main() {
    std::list<int> myList = {1, 2, 3, 4, 5};
    // イテレータの宣言
    std::list<int>::iterator it = myList.begin();
    // 最初の要素を表示
    std::cout << *it << std::endl; // 1
    return 0;
}
1

イテレータを使った要素の操作

イテレータを使うことで、リストの要素を操作することができます。

以下の例では、イテレータを使ってリストの要素を変更する方法を示します。

#include <iostream>
#include <list>
int main() {
    std::list<int> myList = {1, 2, 3, 4, 5};
    // イテレータを使って要素を変更
    for (std::list<int>::iterator it = myList.begin(); it != myList.end(); ++it) {
        *it *= 2; // 各要素を2倍にする
    }
    // リストの内容を表示
    for (int value : myList) {
        std::cout << value << " ";
    }
    return 0;
}
2 4 6 8 10

イテレータの種類と使い分け

C++の標準ライブラリには、いくつかの種類のイテレータがあります。

std::listで使用される主なイテレータの種類は以下の通りです。

スクロールできます
イテレータの種類特徴
入力イテレータ読み取り専用、1回だけの読み取りが可能
出力イテレータ書き込み専用、1回だけの書き込みが可能
前方イテレータ読み取りと書き込みが可能、前方のみ移動
双方向イテレータ読み取りと書き込みが可能、前後に移動
ランダムアクセスイテレータ読み取りと書き込みが可能、任意の位置にアクセス可能

std::listは双方向イテレータを使用しているため、前後に移動しながら要素にアクセスできます。

これにより、リストの任意の位置での要素の操作が可能になります。

イテレータを適切に使い分けることで、プログラムの効率を向上させることができます。

特に、std::listのような双方向連結リストでは、双方向イテレータを活用することが重要です。

std::listのメンバ関数

std::listには、リストの操作を簡単に行うための多くのメンバ関数が用意されています。

ここでは、サイズの取得と操作、要素の検索、ソートと並び替えについて解説します。

サイズの取得と操作

std::listでは、リストのサイズを取得したり、リストを空にしたりするためのメンバ関数が用意されています。

主なメンバ関数は以下の通りです。

  • size(): リストの要素数を返します。
  • empty(): リストが空かどうかを判定します。
  • clear(): リストの全要素を削除します。

以下は、これらのメンバ関数を使用した例です。

#include <iostream>
#include <list>
int main() {
    std::list<int> myList = {1, 2, 3, 4, 5};
    // サイズの取得
    std::cout << "サイズ: " << myList.size() << std::endl; // 5
    // 空かどうかの判定
    std::cout << "リストは空ですか?: " << (myList.empty() ? "はい" : "いいえ") << std::endl; // いいえ
    // リストを空にする
    myList.clear();
    std::cout << "サイズ: " << myList.size() << std::endl; // 0
    return 0;
}
サイズ: 5
リストは空ですか?: いいえ
サイズ: 0

要素の検索

std::listでは、特定の要素を検索するためのメンバ関数が用意されています。

主に使用されるのは以下の関数です。

  • find(): 指定した値を持つ要素を検索します(<algorithm>ヘッダをインクルードする必要があります)。
  • count(): 指定した値を持つ要素の数を返します。

以下は、要素の検索の例です。

#include <iostream>
#include <list>
#include <algorithm>
int main() {
    std::list<int> myList = {1, 2, 3, 4, 5};
    // 要素の検索
    auto it = std::find(myList.begin(), myList.end(), 3);
    if (it != myList.end()) {
        std::cout << "要素 3 が見つかりました。" << std::endl;
    } else {
        std::cout << "要素 3 は見つかりませんでした。" << std::endl;
    }
    // 要素のカウント
    int count = std::count(myList.begin(), myList.end(), 2);
    std::cout << "要素 2 の数: " << count << std::endl; // 1
    return 0;
}
要素 3 が見つかりました。
要素 2 の数: 1

ソートと並び替え

std::listには、要素をソートするためのメンバ関数sort()が用意されています。

この関数を使うことで、リストの要素を簡単に並び替えることができます。

また、カスタムの比較関数を指定することも可能です。

以下は、ソートの例です。

#include <iostream>
#include <list>
int main() {
    std::list<int> myList = {5, 3, 1, 4, 2};
    // ソート前のリストを表示
    std::cout << "ソート前: ";
    for (int value : myList) {
        std::cout << value << " ";
    }
    std::cout << std::endl;
    // ソート
    myList.sort();
    // ソート後のリストを表示
    std::cout << "ソート後: ";
    for (int value : myList) {
        std::cout << value << " ";
    }
    std::cout << std::endl;
    return 0;
}
ソート前: 5 3 1 4 2 
ソート後: 1 2 3 4 5

このように、std::listのメンバ関数を活用することで、リストのサイズの取得、要素の検索、ソートと並び替えが簡単に行えます。

これにより、データの管理や操作が効率的に行えるようになります。

応用例

std::listは、その特性を活かしてさまざまなデータ構造を実装するのに適しています。

ここでは、std::listを使った連結リスト、キュー、スタックの実装例を紹介します。

std::listを使った連結リストの実装

std::list自体が連結リストの特性を持っているため、連結リストの基本的な操作を簡単に実装できます。

以下は、要素の追加と削除を行う連結リストの例です。

#include <iostream>
#include <list>
class LinkedList {
public:
    void add(int value) {
        myList.push_back(value);
    }
    void remove(int value) {
        myList.remove(value);
    }
    void display() {
        for (int value : myList) {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    }
private:
    std::list<int> myList;
};
int main() {
    LinkedList linkedList;
    linkedList.add(10);
    linkedList.add(20);
    linkedList.add(30);
    linkedList.display(); // 10 20 30
    linkedList.remove(20);
    linkedList.display(); // 10 30
    return 0;
}
10 20 30 
10 30

std::listを使ったキューの実装

キューは、先入れ先出し(FIFO)のデータ構造です。

std::listを使ってキューを実装することができます。

以下は、キューの基本的な操作を実装した例です。

#include <iostream>
#include <list>
class Queue {
public:
    void enqueue(int value) {
        myList.push_back(value);
    }
    void dequeue() {
        if (!myList.empty()) {
            myList.pop_front();
        }
    }
    int front() {
        if (!myList.empty()) {
            return myList.front();
        }
        return -1; // エラー値
    }
    bool isEmpty() {
        return myList.empty();
    }
private:
    std::list<int> myList;
};
int main() {
    Queue queue;
    queue.enqueue(1);
    queue.enqueue(2);
    queue.enqueue(3);
    std::cout << "先頭の要素: " << queue.front() << std::endl; // 1
    queue.dequeue();
    std::cout << "先頭の要素: " << queue.front() << std::endl; // 2
    return 0;
}
先頭の要素: 1
先頭の要素: 2

std::listを使ったスタックの実装

スタックは、後入れ先出し(LIFO)のデータ構造です。

std::listを使ってスタックを実装することも可能です。

以下は、スタックの基本的な操作を実装した例です。

#include <iostream>
#include <list>
class Stack {
public:
    void push(int value) {
        myList.push_back(value);
    }
    void pop() {
        if (!myList.empty()) {
            myList.pop_back();
        }
    }
    int top() {
        if (!myList.empty()) {
            return myList.back();
        }
        return -1; // エラー値
    }
    bool isEmpty() {
        return myList.empty();
    }
private:
    std::list<int> myList;
};
int main() {
    Stack stack;
    stack.push(1);
    stack.push(2);
    stack.push(3);
    std::cout << "トップの要素: " << stack.top() << std::endl; // 3
    stack.pop();
    std::cout << "トップの要素: " << stack.top() << std::endl; // 2
    return 0;
}
トップの要素: 3
トップの要素: 2

これらの例からもわかるように、std::listを使用することで、連結リスト、キュー、スタックといったデータ構造を簡単に実装することができます。

std::listの特性を活かして、効率的なデータ管理が可能になります。

よくある質問

std::listとstd::dequeの違いは?

std::liststd::dequeはどちらもC++の標準ライブラリに含まれるコンテナですが、いくつかの重要な違いがあります。

  • メモリ構造: std::listは双方向連結リストであり、各要素が前後の要素へのポインタを持っています。

一方、std::dequeは動的配列の集合であり、連続したメモリブロックを使用します。

  • アクセス速度: std::listはランダムアクセスができないため、特定の要素にアクセスするのにO(n)の時間がかかりますが、std::dequeはO(1)でランダムアクセスが可能です。
  • 要素の追加/削除: std::listは任意の位置での要素の追加や削除がO(1)で行えますが、std::dequeは末尾や先頭での操作はO(1)ですが、中間での操作はO(n)になります。

std::listのパフォーマンスを向上させる方法は?

std::listのパフォーマンスを向上させるための方法はいくつかあります。

  • 適切な使用: std::listは要素の追加や削除が頻繁に行われる場合に最適です。

逆に、ランダムアクセスが多い場合はstd::vectorstd::dequeを使用する方が良いでしょう。

  • イテレータの利用: イテレータを使って要素を操作することで、パフォーマンスを向上させることができます。

特に、ループ内での要素の操作はイテレータを使うと効率的です。

  • メモリの再利用: std::listの要素を削除した後、再利用することでメモリの断片化を防ぎ、パフォーマンスを向上させることができます。

std::listのメモリ管理について

std::listは動的にメモリを管理します。

各要素は独立したメモリブロックに格納され、要素が追加されるたびに新しいメモリが割り当てられ、削除されるとそのメモリが解放されます。

このため、std::listはメモリの断片化が発生しやすいですが、要素の追加や削除が頻繁に行われる場合には有利です。

まとめ

この記事では、C++のstd::listについて、その基本的な使い方や応用例、よくある質問に対する回答を紹介しました。

std::listは、特に要素の追加や削除が頻繁に行われる場合に非常に便利なコンテナであり、さまざまなデータ構造を実装するのに適しています。

ぜひ、実際のプログラムでstd::listを活用し、その特性を体験してみてください。

  • URLをコピーしました!
目次から探す