HOWTO · C++

C++ で配列から要素を削除する

この記事では、C++ で配列から要素を削除する方法を示します

このページの内容

値の削除方法はコンテナーによって異なります。Cスタイル配列は宣言時の容量が変わらないため、値をずらして論理上の長さを短く管理するか、残す値を別のコンテナーにコピーします。std::vector はサイズを直接変更できます。以下の例では、説明に応じて最初に一致した値を1つ削除する方法と、一致する値をすべて削除する方法を示します。

要素を削除すると配列の論理サイズが変わる理由

int values[5] のような配列には、常に5個分の整数を格納する領域があります。要素を後続の値で上書きすれば有効な並びを短くできますが、配列の容量は変わりません。有効な長さは別の変数で管理し、並びを読み取ったり表示したりするときはその長さを使います。一方、std::vector<int> は、格納領域と現在の要素数の両方を管理します。

Cスタイル配列の要素をずらす

配列内の最初の一致要素をその場で削除するには、まずその要素を探し、後続の各値を1つ左へずらしてから、論理上の長さを1減らします。配列の容量は5のままで、短くなった並びに含まれるのは先頭からlength個の値だけです。次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <iostream>

void remove_first(int values[], int& length, int target) {
    int index = 0;
    while (index < length && values[index] != target) {
        ++index;
    }
    if (index == length) {
        return;
    }
    for (int i = index; i + 1 < length; ++i) {
        values[i] = values[i + 1];
    }
    --length;
}

int main() {
    int values[5] = {1, 2, 3, 4, 5};
    int length = 5;
    remove_first(values, length, 3);
    for (int i = 0; i < length; ++i) {
        if (i != 0) std::cout << ' ';
        std::cout << values[i];
    }
    std::cout << '\n';
}
1 2 4 5

この関数が削除するのは最初の一致要素だけです。一致する値をすべて削除するには、読み取り位置と書き込み位置を使って配列を詰めます。一致しない値を次の書き込み位置へコピーし、最後に書き込んだ要素数を論理上の長さに設定します。この方法では各要素を1回ずつ調べます。一致するたびに要素をずらす方法は単純ですが、同じ値を何度も移動することがあります。

残りの要素を新しい配列にコピーする

別の格納先を使うと、元の配列は変更されずに残ります。std::vector はサイズを動的に変更でき、格納領域の所有権も管理するため、new[] と delete[] を手動で扱わずに済む便利な格納先です。この例では3以外の値をすべてコピーします。一致する値がなければ、格納先には元の配列のコピーが入ります。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <cstddef>
#include <iostream>
#include <vector>

int main() {
    const int source[] = {1, 2, 3, 3, 4, 5};
    const int length = sizeof(source) / sizeof(source[0]);
    std::vector<int> remaining;
    for (int i = 0; i < length; ++i) {
        if (source[i] != 3) {
            remaining.push_back(source[i]);
        }
    }
    for (std::size_t i = 0; i < remaining.size(); ++i) {
        if (i != 0) std::cout << ' ';
        std::cout << remaining[i];
    }
    std::cout << '\n';
}
1 2 4 5

格納先のサイズは、残す要素の数とちょうど同じになります。インターフェースの都合で動的確保した生配列が必要な場合は、まず残す要素数を数え、その数(0の場合もあります)だけ要素を確保します。所有者が使い終えたらdelete[]で解放してください。生配列の所有権を求められない場合は、標準コンテナーを使うのが適切です。

std::vectorから一致する値をerase-removeイディオムで削除する

サイズを変更できるコンテナーでは、std::remove とvectorのメンバー関数eraseを組み合わせます。アルゴリズムには<algorithm>、コンテナーには<vector>をインクルードします。std::removeは、一致しない値を先頭側へ移動し、新しい論理上の末尾を指すイテレーターを返しますが、vectorのサイズは変更しません。その後、eraseが末尾側の範囲を取り除き、サイズを更新します。この方法では、対象と一致する値をすべて削除できます。

このセクションのコードと表示されるラベルや出力は英語のままです。

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

int main() {
    std::vector<int> values = {1, 2, 3, 3, 4, 5};
    values.erase(std::remove(values.begin(), values.end(), 3), values.end());
    for (std::size_t i = 0; i < values.size(); ++i) {
        if (i != 0) std::cout << ' ';
        std::cout << values[i];
    }
    std::cout << '\n';
}
1 2 4 5

固定長配列でstd::removeを使う

std::removeは、Cスタイル配列のイテレーター範囲にも使えます。値を並べ替えて新しい論理上の末尾を返しますが、配列の格納領域を短くすることはできません。結果を処理するときは、返されたイテレーターを残った範囲の末尾として使ってください。元の配列全体の範囲を、短くなった並びとして扱ってはいけません。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <algorithm>
#include <iostream>
#include <iterator>

int main() {
    int values[] = {1, 2, 2, 3, 4};
    int* logical_end = std::remove(std::begin(values), std::end(values), 2);
    for (int* current = std::begin(values); current != logical_end; ++current) {
        if (current != std::begin(values)) std::cout << ' ';
        std::cout << *current;
    }
    std::cout << '\n';
}
1 3 4

logical_endから配列の実際の末尾までの要素は、有効なオブジェクトのままですが、その値はアルゴリズムによって保証されません。この末尾部分を表示したり、値を前提にしたりしないでください。物理的に短い並びが必要なら、logical_endまでの先頭部分を適切なサイズの格納先にコピーします。

方法を選び、端のケースにも対応する

固定長配列をその場で更新し、論理上の長さを別に管理できるなら、要素をずらす方法を使います。元の配列を維持したい場合や、結果を独立した格納領域に置きたい場合は、新しい格納先を使います。コレクションのサイズを自然に増減させたい場合はstd::vectorを選びます。一致する値をすべて削除するには、通常、そのerase-removeイディオムが最も明快です。

最初の一致要素だけを削除するのか、一致する要素をすべて削除するのかを明確にしてください。一致する値が見つからなければ、並びは変わりません。要素が1つの配列では、削除後の論理上の長さを0にできますが、宣言時の容量は1のままです。空のvectorではstd::removeがbegin()を返し、その空範囲を消去しても空のままです。長さとイテレーター範囲を一貫して扱えば、こうしたケースに特別なメモリ確保の工夫は必要ありません。