2024. 3. 21. 17:49ㆍProgramming Language/C++
아래 링크 클릭 시 해당 본문으로 이동
- 예시 1 (myiter 삭제)
- 예시 2 (++myiter 삭제)
- 예시 3 (++myiter 출력)
- 예시 4 (myiter++ 출력)
- 예시 5 (역참조)
- 예시 6 (begin()으로 다른 vector 가리키기)
- 예시 7 (1~11 출력 및 짝수 제거)
- 예시 8 (1~11 숫자 중 1~5 제거) - 수정 전
- 예시 8 (1~11 숫자 중 1~5 제거) - 수정 후
erase()
• vector 멤버 함수로, 인자로 iterator를 받으며 iterator가 가리키는 부분을 제거하는 함수이다.
힙 메모리에서 인덱스가 지시하는 공간을 제거한다는 것의 의미
데이터들은 힙 메모리 쪽에 저장된다.
힙 메모리에서 인덱스가 지시하는 공간을 제거한다는 것은 뒤 쪽에 있는 데이터가 앞 쪽으로 당겨지는 것을 의미한다.
ex) 데이터 4개 중 인덱스 2의 데이터를 삭제하고 싶다.
인덱스 3의 데이터를 인덱스 2의 데이터에 덮어씌운다.
카운트가 3으로 유지되기 때문에 인덱스 3의 데이터는 그냥 놔둬도 된다.
다음 데이터는 어차피 인덱스 3의 자리에 들어온다.
따라서 데이터를 앞으로 옮겨오기만 하면 된다.
데이터 구조에서 삭제된 위치를 가리키는 iterator
iterator가 가리키고 있던 데이터가 삭제되면 iterator는 삭제된 위치를 가리키게 된다.
vector의 특성상 메모리가 연속적이기 때문에 삭제된 부분 이후의 데이터를 확인할 수 있는 경우도 있다.
하지만 삭제된 데이터를 가리키고 있는 iterator일 뿐이고, 해당 부분은 없어져서 쓸 수 없는, 즉 유효하지 않은 iterator라는 알림을 띄워야 한다.
삭제된 데이터를 그대로 참조할 수 있는 것은 배열의 특징일 뿐이며, 다른 데이터 구조에서는 다르게 동작하므로 동일한 인터페이스로 반복자를 설계할 때에는 이러한 점을 고려해야 한다.
iterator는 모든 자료구조에서 동일한 인터페이스 형태로 구성되어야 한다. (통일성)
삭제된 위치를 가리키는 반복자는 사용할 수 없다는 공통된 개념이 적용되어야 한다.
이는 리스트와 같은 다른 데이터 구조에서도 동일하게 적용된다.
▷ `erase()` 구현
#pragma once
#include <assert.h>
// 클래스 템플릿
template<typename T>
class CArr
{
private:
T* m_pData;
int m_iCount; // 현재 들어와있는 데이터 개수
int m_iMaxCount; // 최대 개수
class iterator
{
public:
// 코드 내용 생략
friend class CArr; // 배열 쪽에서 iterator 쪽의 private 멤버에 접근할 수 있다.
// 지금의 경우 가변 배열과 iterator는 긴밀한 관계이다. (데이터를 주고 받을 일이 많다.)
// 이너 클래스이기 때문에 가변 배열의 모든 것들은 iterator에 공개가 되어있다.
// 그래서 양방향으로 friend 선언을 해줬다.
};
};
template<typename T>
typename CArr<T>::iterator CArr<T>::erase(iterator& _iter) // iterator가 관리하고 있는 데이터들 중에 _iter가 지시하는 부분을 제거한다.
{
// iterator가 다른 Arr 쪽 요소를 가리키는 경우 / iterator가 end iterator인 경우 (end는 마지막의 다음이기 때문에 가리킬 수 없다.) / 인덱스 값이 내가 보유하고 있는 배열의 데이터 카운트 인덱스보다 크거나 같은 경우
// ex. 배열 안에 데이터가 10개 들어있다.
// iterator가 50 인덱스 즉, 51번째 데이터를 가르키고 있다.
// 같아도 안 되는 이유 : 데이터가 10개인데 인덱스가 10이면 11번째를 의미하기 때문이다.
if (this != _iter.m_pArr || end() == _iter || m_iCount <= _iter.m_iIdx)
{
assert(nullptr); // 경고 발생시키기
}
// 카운트 값에서 삭제하려는 인덱스에 +1을 한 것을 빼면 땡겨오는 횟수가 된다.
// ex1. 데이터가 4개일 때 인덱스 2의 데이터를 제거하면 땡겨오는 횟수는 1회이다.
// 4 - (2+1) = 1
// ex2. 데이터가 4개일 때 인덱스 1의 데이터를 제거하면 땡겨오는 횟수는 2회이다.
// 4 - (1+1) = 2
// ex3. 데이터가 10개일 때 인덱스 0의 데이터를 제거하면 땡겨오는 횟수는 9번이다.
// 10 - (0+1) = 9
// 반복문 반복 횟수 = 배열 안에 있는 데이터 개수 - (iterator가 제거하려는 인덱스 + 1)
int iLoopCount = m_iCount - (_iter.m_iIdx + 1);
// ex. 데이터 4개일 때 인덱스 1을 지우고 싶다.
// for문에서 i를 0부터 시작하게 했다.
// i값 입장에서 접근해야(제거해야) 될 인덱스는 1이다.
// 인덱스 1에서 하나 증가한 인덱스에서 인덱스 1로 옮겨가야 한다.
// 즉, 인덱스 3에서 인덱스 2로 먼저 덮어쓰는 것이 아니다.
for (int i = 0; i < iLoopCount; ++i)
{
// 데이터 시작으로부터 i값에 iterator가 제거하려는 인덱스를 더해준다.
// 매번 i값이 하나 증가하면서 채워질 공간이 한 칸씩 전진한다.
// 한 번 더 증가한 부분의 값이 옮겨와야 한다.
m_pData[i + _iter.m_iIdx] = m_pData[i + _iter.m_iIdx + 1]; // 왼쪽 : 채워야 할 곳 / 오른쪽 : 다음 칸
// +을 -로 잘못 적는 등 수식을 잘못 작성하면 힙 메모리에 잘못 접근할 가능성이 높아지기 때문에 주소 연산할 때 주의하기
// 테스트 및 디버깅 많이 해보기
}
// 받아온 iterator를 삭제했으니까 더이상 유효하지 않다.
_iter.m_bValid = false;
// 데이터가 줄어들었으므로 카운트 감소
--m_iCount;
// 가변 배열의 주소가 this와 같아야 한다. (_iter.m_pArr == this;)
return iterator(this, m_pData, _iter.m_iIdx); // 삭제하고 데이터들을 땡겨와도 원래 iterator는 가리키던 곳을 그대로 가리킨다. (복사 및 생성)
// 'this, 데이터 주소, 제거를 위해 iterator가 알고 있던 인덱스 값'을 그대로 넣어준다.
// 들어온 iterator와 리턴되는 iterator가 내부 멤버 값이 똑같은 상태가 된다.
//
// 반환으로 되돌려준 iterator는 유효하기 때문에 생성자 안에서 true 체크가 된다.
// 제거하려고 사용한 인자는 원본을 넘겼기 때문에 원본은 훼손돼서 사용할 수 없다. => 유효성은 false
}
`erase()`에서 반환하는 iterator의 의미
iterator가 가리키는 부분을 `erase()`로 제거하면 iterator의 기능을 상실하게 된다. (유효하지 않은 벡터로 본다.)
때문에 iterator가 정상적인 작동을 할 수 있도록 삭제된 부분의 다음 데이터를 가리키는 iterator를 반환한다.
`erase()`로 삭제한 후 iterator가 다음 요소를 가리켜야 하는 이유
만약 `erase()`로 iterator가 가리키는 부분을 지운다면 iterator(원본)는 사용할 수 없는 상태가 된다.
`erase()` 내부에서 참조할 수 없는 원본의 유효 상태를 false로 되돌려놓기 때문이다.
_iter.m_bValid = false;
따라서 제거하고 나면 반드시 삭제된 요소의 다음 요소를 가리키는 iterator를 리턴해야 한다.
`erase()` 예시
`더보기` 클릭하기
▷ CArr.h 전체 코드
#pragma once
#include <assert.h>
// 클래스 템플릿
template<typename T>
class CArr
{
private:
T* m_pData;
int m_iCount; // 현재 들어와있는 데이터 개수
int m_iMaxCount; // 최대 개수
public:
void push_back(const T& _Data); // 데이터 추가
void resize(int _iResizeCount); // 재할당 => 몇 칸으로 늘릴 것인지를 _iResizeCount로 받는다.
T* data() { return m_pData; } // 시작 주소 반환
int size() { return m_iCount; } // 데이터 개수
int capacity() { return m_iMaxCount; } // 현재 기준으로 허용 범위
T& operator[] (int idx);
class iterator;
iterator begin();
iterator end();
//iterator erase(const iterator& _iter); // (아래 쪽에 정의했다.)
// 수정할 여지가 생겼기 때문에 erase()에 const를 빼고 일반 래퍼런스로 변경했다.
iterator erase(iterator& _iter);
public:
CArr(); // 생성자
~CArr(); // 소멸자
// 이너 클래스를 포함하고 있는 클래스의 private 멤버에 접근 가능
class iterator
{
private:
CArr* m_pArr; // iterator가 가리킬 데이터를 관리하는 가변 배열 주소
T* m_pData; // 데이터들의 시작 주소 => 이 역시 벡터가 알고 있다.
int m_iIdx; // 가리키는 데이터의 인덱스 // 만약 iterator가 다음이나 이전을 가리키라고 하면 인덱스를 ++이나 --시키면 된다.
bool m_bValid; // iterator 유효성 체크
public:
// ----- 역참조 operator -----//
T& operator * ()
// 반환 타입이 그냥 T라면 수정할 수 없다.
// T&여야 수정이 가능하다.
{
// 데이터의 시작 주소와 배열 쪽의 시작 주소가 같은지
// 만약 같지 않다면 경고
// 1. iterator가 알고 있는 주소와 가변 배열이 알고 있는 주소가 달라진 경우
// 공간 확장으로 주소가 달라진 경우
// 또는
// 2. iterator가 end iterator인 경우
// 3. 유효성 체크가 false인 경우 => 유효하지 않기 때문에 역참조를 시도하면 문제로 삼아야 한다.
// false == m_bValid로 표현해도 되지만 !m_bValid로 축약하여 표현했다.
if (m_pArr->m_pData != m_pData || -1 == m_iIdx || !m_bValid )
{
assert(nullptr);
}
// 시작 주소(포인터)로부터 인덱스(위치)
return m_pData[m_iIdx]; // 여기서 []는 operator가 아니라 기본 문법이다. (포인터 연산)
}
// ----- 전위 연산자 -----//
iterator& operator ++() // 반환 타입이 iterator 본인이여야 증가시킨 iterator 본인을 다시 증가시키는 등의 작업을 할 수 있다.
{
// 예외 처리
// 2. end iterator인 경우
// 이 경우 더 이상 증가할 수 없다.
// 3. iterator가 알고 있는 주소와 가변 배열이 알고 있는 주소가 달라진 경우
// 공간 확장으로 주소가 달라진 경우
if (m_pArr->m_pData != m_pData || -1 == m_iIdx)
{
assert(nullptr);
}
// 1. iterator가 마지막 데이터를 가리키고 있는 경우
// --> end iterator가 된다.
// 예외 처리는 맞지만 오류가 발생하는 상황은 아니다.
// 어떻게 작성해야 될지 모를 때 iterator의 멤버 확인해보기
// iterator 클래스의 근본적인 역할 : 가변 배열(m_pArr)이 관리하고 있는 데이터들을 가리킨다.
if (m_pArr->size() - 1 == m_iIdx)
{
m_iIdx = -1;
}
else
{
++m_iIdx;
}
//++m_iIdx; // 주소로부터 떨어져있는 곳을 지칭하는 인덱스를 증가시킨다.
return *this;
}
iterator& operator --()
{
if (m_pArr->m_pData != m_pData || -1 == m_iIdx)
{
assert(nullptr);
}
if (m_pArr->size() - 1 == m_iIdx)
{
m_iIdx = -1;
}
else
{
--m_iIdx;
}
return *this;
}
// ----- 후위 연산자 -----//
iterator operator ++(int) // 인자로 적은 int는 아무 의미가 없고 받아올 변수명도 적을 필요 없다.
{
// 복사 생성자가 호출된다.
iterator copy_iter = *this; // 지역 변수로 iterator를 만든다.
++(*this); // 후위 연산자를 호출시킨 객체는 다음 요소를 가리킨다.
return copy_iter; // 다음 대상으로 증가하지 않은 객체를 반환한다.
// 증가하기 이전에 복사받은 것을 반환한다.
}
iterator operator --(int)
{
iterator copy_iter = *this;
--(*this);
return copy_iter;
}
// ----- 비교 연산자 ==, != -----//
// 반환 타입이 true 또는 false이기 때문에 bool 타입
bool operator ==(const iterator& _otheriter)
{
if (m_pData == _otheriter.m_pData && m_iIdx == _otheriter.m_iIdx)
{
return true;
}
return false;
}
bool operator !=(const iterator& _otheriter)
{
// 간단하게 표현
return !(*this == _otheriter);
}
public:
// 생성자
iterator()
: m_pArr(nullptr)
, m_pData(nullptr)
, m_iIdx(-1) // 아무것도 가르키지 않는다는 의미로 -1을 사용했다.
, m_bValid(false) // 기본 생성자에서는 false
{}
// 생성자 오버로딩
iterator(CArr* _pArr, T* _pData, int _iIdx)
: m_pArr(_pArr)
, m_pData(_pData)
, m_iIdx(_iIdx)
, m_bValid(false) // 누군가를 가리키게 됐으면 true인데 false로 하고 생성자 내부 함수에서 조건으로 다뤘다.
{
if (nullptr != _pArr && 0 <= _iIdx)
// 제대로 된 특정 가변 배열을 알게 됐고, 인덱스 값도 정상적이면
// 인덱스가 -1이면 end iterator로 봐야 되기 때문이다.
// end iterator도 유효한 iterator는 아니다.
{
m_bValid = true;
}
}
// 소멸자
~iterator()
{}
friend class CArr; // 양방향 friend 선언
};
};
// 생성자
template<typename T>
CArr<T>::CArr() // CArr 클래스의 T 버전 안에 구현되어있는 생성자를 지칭한다.
: m_pData(nullptr)
, m_iCount(0)
, m_iMaxCount(2)
{
m_pData = new T[2];
}
// 소멸자
template<typename T>
CArr<T>::~CArr()
{
delete[] m_pData;
}
template<typename T>
void CArr<T>::push_back(const T& _Data)
{
int i = 0;
if (m_iMaxCount <= m_iCount)
{
// 재할당
resize(m_iMaxCount * 2);
}
// 데이터 추가
m_pData[m_iCount++] = _Data;
}
template<typename T>
void CArr<T>::resize(int _iResizeCount)
{
if (m_iMaxCount >= _iResizeCount)
{
assert(nullptr);
}
// 1. resize시킬 개수만큼 동적 할당한다.
T* pNew = new T[_iResizeCount];
// 2. 기존 공간에 있던 데이터들을 새로 할당한 공간으로 복사시킨다.
for (int i = 0; i < m_iCount; ++i)
{
pNew[i] = m_pData[i];
}
// 3. 기존 공간은 메모리 해제
delete[] m_pData;
// 4. 배열이 새로 할당된 공간을 가리키게 한다.
m_pData = pNew;
// 5. iMaxCount 변경점 적용
m_iMaxCount = _iResizeCount;
}
template<typename T>
T& CArr<T>::operator[](int idx)
{
return m_pData[idx];
}
template<typename T>
typename CArr<T>::iterator CArr<T>::begin()
{
if (0 == m_iCount)
return iterator(this, m_pData, -1);
// 데이터가 없는 경우
// begin() == end()
else
return iterator(this, m_pData, 0);
}
template<typename T>
typename CArr<T>::iterator CArr<T>::end()
{
return iterator(this, m_pData, -1);
}
template<typename T>
typename CArr<T>::iterator CArr<T>::erase(iterator& _iter)
{
if (this != _iter.m_pArr || end() == _iter || m_iCount <= _iter.m_iIdx)
{
assert(nullptr); // 경고 발생시키기
}
int iLoopCount = m_iCount - (_iter.m_iIdx + 1);
for (int i = 0; i < iLoopCount; ++i)
{
m_pData[i + _iter.m_iIdx] = m_pData[i + _iter.m_iIdx + 1]; // 왼쪽 : 채워야 할 곳 / 오른쪽 : 다음 칸
}
// 받아온 iterator를 삭제했으니까 더이상 유효하지 않다.
_iter.m_bValid = false;
// 데이터가 줄어들었으므로 카운트 감소
--m_iCount;
return iterator(this, m_pData, _iter.m_iIdx); // 삭제하고 데이터들을 땡겨와도 원래 iterator는 가리키던 곳을 그대로 가리킨다. (복사 및 생성)
}
▷ main.cpp
▷ 예시 1 (myiter 삭제)
#include <iostream>
#include <vector>
#include "CArr.h"
using std::cout;
using std::endl;
using std::vector;
int main()
{
CArr<int> myvector;
myvector.push_back(10);
myvector.push_back(20);
myvector.push_back(30);
CArr<int>::iterator myiter = myvector.begin(); // begin부터 시작해서 쭉 순회를 한다.
myiter = myvector.erase(myiter); // 벡터로 iterator가 가리키는 부분을 제거하고 그것을 다시 받는다.
// 10 삭제 뒤 앞으로 하나씩 땡겨오게 되면 begin()은 20을 가리킨다.
for (myiter = myvector.begin(); myiter != myvector.end(); ++myiter)
{
cout << *myiter << endl;
}
return 0;
}
▽ 출력 결과
20
30
▷ 예시 2 (++myiter 삭제)
#include <iostream>
#include <vector>
#include "CArr.h"
using std::cout;
using std::endl;
using std::vector;
int main()
{
CArr<int> myvector;
myvector.push_back(10);
myvector.push_back(20);
myvector.push_back(30);
CArr<int>::iterator myiter = myvector.begin();
myiter = myvector.erase(++myiter);
// ++myiter로 20이 제거되었으므로 10, 30이 출력된다.
for (myiter = myvector.begin(); myiter != myvector.end(); ++myiter)
{
cout << *myiter << endl;
}
return 0;
}
▽ 출력 결과
10
30
▷ 예시 3 (++myiter 출력)
#include <iostream>
#include <vector>
#include "CArr.h"
using std::cout;
using std::endl;
using std::vector;
int main()
{
CArr<int> myvector;
myvector.push_back(10);
myvector.push_back(20);
myvector.push_back(30);
CArr<int>::iterator myiter = myvector.begin();
int iData = *(++myiter); // 20
cout << iData << endl;
return 0;
}
▽ 출력 결과
20
▷ 예시 4 (myiter++ 출력)
#include <iostream>
#include <vector>
#include "CArr.h"
using std::cout;
using std::endl;
using std::vector;
int main()
{
CArr<int> myvector;
myvector.push_back(10);
myvector.push_back(20);
myvector.push_back(30);
CArr<int>::iterator myiter = myvector.begin();
int iData = *(myiter++); // 10 // 10을 iData에 저장한 다음에 myiter가 20으로 증가한다.
cout << iData << endl;
return 0;
}
▽ 출력 결과
10
▷ 예시 5 (역참조)
#include <iostream>
#include <vector>
#include "CArr.h"
using std::cout;
using std::endl;
using std::vector;
int main()
{
vector<int> vecInt;
vecInt.push_back(100);
vecInt.push_back(200);
vecInt.push_back(300);
vecInt.push_back(400);
vector<int>::iterator veciter = vecInt.begin();
vecInt.erase(veciter); // 200
// 역참조
//int i = *veciter; // 오류
// iterator는 삭제된 곳을 가리키고 있다.
// 100을 삭제하고 앞으로 땡겨져서 200이 그 자리로 오게 된다.
// 이때 operator*를 사용하여 값을 꺼내오려고 하니까 iterator는 내부적으로 더 이상 사용할 수 없다고 본다.
// 다시 말해, 가리키고 있던 곳은 삭제됐고 있던 값들이 땡겨졌다.
// 없어진 곳을 가리키는 iterator는 쓸모가 없어져버렸다.
// 때문에 iterator를 그냥 재사용해서는 안 된다.
// ↓
// erase()의 반환 타입은 삭제된 요소의 다음을 가리키는 iterator이다.
// erase()하고 나서 iterator가 veciter를 다시 받아왔어야 한다.
// veciter를 삭제하고 되돌려주는 iterator를 받았어야 veciter가 다시 정상화된 iterator가 될 수 있다.
// 다시 begin을 받아서 역참조하면 정상적으로 200이 저장된다.
// 맨 앞에 있는 100을 지우고 나서의 begin은 200이다.
veciter = vecInt.begin();
int i = *veciter; // 200
return 0;
}
▷ 예시 6 (begin()으로 다른 vector 가리키기)
#include <iostream>
#include <vector>
#include "CArr.h"
using std::cout;
using std::endl;
using std::vector;
int main()
{
vector<int> vecInt;
vecInt.push_back(100);
vecInt.push_back(200);
vecInt.push_back(300);
vecInt.push_back(400);
vector<int> vecInt2;
vecInt2.push_back(1000);
// *저장하는 데이터 타입이 int로 같기 때문에* iterator는 양쪽 다 가리킬 수 있다.
vector<int>::iterator veciter = vecInt2.begin();
// iterator는 vecInt2의 1000을 가리키고 있다.
return 0;
}
▷ 예시 7 (1~11 출력 및 짝수 제거)
짝수 제거이지만 사실상 홀수를 검사하지 않고 1씩 더하면서 건너뛰며 출력하는 코드이다.
#include <iostream>
#include <vector>
#include "CArr.h"
using std::cout;
using std::endl;
using std::vector;
int main()
{
vector<int> vecInt;
vector<int>::iterator veciter = vecInt.begin();
// 1~10을 vector에 입력
//for (int i = 0; i < 10; ++i)
//{
// vecInt.push_back(i + 1);
//}
// ↓
// end에 ++까지 해버려서 예외가 발생하기 때문에 i < 10을 i < 11로 수정했다.
for (int i = 0; i < 11; ++i)
{
vecInt.push_back(i + 1);
}
cout << "1~11 출력" << endl;
for (int i = 0; i < vecInt.size(); ++i)
{
cout << vecInt[i] << endl;
}
cout << endl;
// 짝수만 제거하기
veciter = vecInt.begin();
for (; veciter != vecInt.end(); ++veciter)
{
if (0 == *veciter % 2)
{
// 제거
//vecInt.erase(veciter);
// 이때 발생하는 문제
// 가리키고 있던 iterator가 제거되었기 때문에 veciter는 이제 쓸 수 없다.
// 가리키고 있던 것이 날라가버려서 ++veciter도 할 수 없어서 다시 받아줘야 한다.
// 마지막에 있는 데이터가 10이었기 때문에 10이 제거됐고, 삭제된 다음인데 10이 마지막이다.
// 삭제된 다음을 받으면 veciter는 end iterator가 된다.
// end iterator인지 비교하기 전에 ++veciter를 만나서 end iterator에서 한 번 더 증가해버린다.
// 그래서 예외 처리가 발생한다.
// i < 11로 11까지 넣어본 이유도 삭제된 다음을 가리키면 11을 가리키게 되는데, 11에서 ++이 되면 end가 되면서 종료가 된다.
veciter = vecInt.erase(veciter);
}
}
cout << "짝수만 제거하기" << endl;
for (int i = 0; i < vecInt.size(); ++i)
{
cout << vecInt[i] << endl;
}
return 0;
}
▽ 출력 결과
1~11 출력
1
2
3
4
5
6
7
8
9
10
11
짝수만 제거하기
1
3
5
7
9
11
▷ 예시 8 (1~11 숫자 중 1~5 제거) - 수정 전
#include <iostream>
#include <vector>
#include "CArr.h"
using std::cout;
using std::endl;
using std::vector;
int main()
{
vector<int> vecInt;
vector<int>::iterator veciter = vecInt.begin();
for (int i = 0; i < 11; ++i)
{
vecInt.push_back(i + 1);
}
// 1~11 사이의 숫자 제거하기
veciter = vecInt.begin();
for (; veciter != vecInt.end(); ++veciter)
{
if (1 <= *veciter && *veciter <= 5)
{
// 제거
veciter = vecInt.erase(veciter);
}
}
// 6~11 외에 2, 4도 출력되는 문제가 발생한다.
// veciter가 가리키는 것을 벡터 내에서 제거시키고 그 다음을 가리키는 것을 veciter로 다시 되돌려줬다.
// 만약 1이 제거된다고 해보자.
// 1 제거하고 나서 iterator가 가리키는 것은 2이다.
// 반복문에서 ++veciter을 한다.
// 제거가 발생했는데 2번 증가된다.
// 때문에 2는 건너뛰고 3으로 간다.
// 3은 1~5 사이의 숫자이니까 제거되고 iterator는 4를 가리키게 되는데 ++veciter로 5를 가리키고, 5도 제거된다.
// 따라서 2와 4도 출력된다.
// 출력
cout << "1~11 숫자 중 1~5 제거" << endl;
for (int i = 0; i < vecInt.size(); ++i)
{
cout << vecInt[i] << endl;
}
return 0;
}
▽ 출력 결과
1~11 숫자 중 1~5 제거
2
4
6
7
8
9
10
11
▷ 예시 8 (1~11 숫자 중 1~5 제거) - 수정 후
#include <iostream>
#include <vector>
#include "CArr.h"
using std::cout;
using std::endl;
using std::vector;
int main()
{
vector<int> vecInt;
vector<int>::iterator veciter = vecInt.begin();
for (int i = 0; i < 11; ++i)
{
vecInt.push_back(i + 1);
}
cout << endl;
// 1~11 숫자 중 1~5 제거하기
// 무조건 ++을 하는 것이 아니라 if에 걸렸을 때만 ++하는 식으로 코드 수정
veciter = vecInt.begin();
for (; veciter != vecInt.end();)
{
if (1 <= *veciter && *veciter <= 5)
{
// 제거
veciter = vecInt.erase(veciter);
}
// 제거가 발생하지 않았을 때만 ++을 한다.
else
{
++veciter;
}
}
// 출력
cout << "1~11 숫자 중 1~5 제거" << endl;
for (int i = 0; i < vecInt.size(); ++i)
{
cout << vecInt[i] << endl;
}
return 0;
}
▽ 출력 결과
1~11 숫자 중 1~5 제거
6
7
8
9
10
11
`clear()` 문제점
`clear()`은 데이터를 0개로 만든다. (size를 0으로 만든다.)
void clear()
{
m_iCount = 0;
}
// 벡터의 clear() 함수
vecInt.clear();
만약 데이터를 1000개 만든 상태에서 `clear()`로 데이터를 다 없애도 배열이 관리하고 있던 공간 자체는 크게 늘어나있는 상태에서 줄어들지는 않는다.
다시 말해, size는 줄어들지만 capacity는 줄어들지 않는다.
'Programming Language > C++' 카테고리의 다른 글
[C++] 트리(Tree), set과 map (0) | 2024.03.22 |
---|---|
[C++] list iterator, inline (0) | 2024.03.22 |
[C++] iterator (0) | 2024.03.20 |
[C++ STL] 벡터(vector), 리스트(list) (0) | 2024.03.19 |
[C++] 범위 지정 연산자(::), using namespace std; (0) | 2024.03.19 |