HOWTO · C++
C++의 배열에서 요소 제거
이 문서에서는 C++의 배열에서 요소를 제거하는 방법을 보여줍니다.
이 페이지의 내용
값을 제거하는 방법은 컨테이너에 따라 다릅니다. C 스타일 배열은 선언된 용량을 유지하므로, 값을 옮기고 논리적 길이를 줄여 관리하거나 남길 값을 다른 컨테이너에 복사할 수 있습니다. std::vector는 크기를 직접 변경할 수 있습니다. 아래 예제는 설명에 명시된 대로 일치하는 값 하나 또는 모든 일치 항목을 제거합니다.
요소를 제거하면 배열의 논리적 크기가 달라지는 이유
int values[5]와 같은 배열에는 항상 정수 다섯 개를 저장할 공간이 있습니다. 어떤 요소를 뒤의 값으로 덮어쓰면 실제로 사용하는 시퀀스는 짧아질 수 있지만, 배열의 용량은 바뀌지 않습니다. 별도의 변수로 활성 길이를 관리하고, 시퀀스를 읽거나 출력할 때 그 길이를 사용하세요. 반면 std::vector<int>는 저장 공간과 현재 요소 수를 모두 관리합니다.
C 스타일 배열에서 요소 옮기기
배열에서 첫 번째로 일치하는 항목을 제자리에서 제거하려면 먼저 해당 항목을 찾고, 뒤에 있는 값을 한 칸씩 왼쪽으로 옮긴 다음 논리적 길이를 줄입니다. 배열 용량은 계속 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
이 함수는 첫 번째 일치 항목만 제거합니다. 모든 일치 항목을 제거하려면 읽기 인덱스와 쓰기 인덱스를 사용해 배열을 압축하세요. 일치하지 않는 값을 각각 다음 쓰기 위치에 복사한 뒤, 쓰인 값의 개수로 논리적 길이를 설정하면 됩니다. 이렇게 하면 각 요소를 한 번씩 확인합니다. 일치 항목을 하나씩 제거하며 뒤의 값을 옮기는 방법은 더 단순하지만 같은 값을 여러 번 옮길 수 있습니다.
남은 요소를 새 배열에 복사하기
별도의 대상 컨테이너를 사용하면 원본을 그대로 둘 수 있습니다. 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[]로 해제하세요. 원시 배열의 소유권이 인터페이스에 필요한 것이 아니라면 표준 컨테이너를 사용하는 편이 좋습니다.
erase-remove 관용구로 std::vector에서 일치하는 값 제거하기
크기를 조절할 수 있는 컨테이너에서는 std::remove와 벡터의 erase 멤버 함수를 함께 사용하세요. 알고리즘에는 <algorithm>, 컨테이너에는 <vector>를 포함해야 합니다. std::remove는 일치하지 않는 값을 앞쪽으로 모으고 새 논리적 끝을 가리키는 반복자를 반환하지만, 벡터의 크기를 바꾸지는 않습니다. 이어서 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 관용구가 대개 가장 명확합니다.
첫 번째 일치 항목만 제거할지, 모든 일치 항목을 제거할지 분명히 하세요. 일치하는 값을 찾지 못하면 시퀀스는 그대로 유지됩니다. 요소가 하나인 배열에서 그 값을 제거하면 논리적 길이는 0이 될 수 있지만 선언된 용량은 1로 남습니다. 빈 벡터에 std::remove를 적용하면 begin()을 반환하며, 그 결과로 지정한 빈 범위를 지워도 벡터는 빈 상태입니다. 길이와 반복자 범위를 일관되게 다루면 이런 경우에 특별한 메모리 할당 처리는 필요하지 않습니다.