Coding Memo
STL 반복자와 연산 그리고 <algorithm> 헤더 본문
STL에서 반복자를 통해 각 원소(element)에 접근 할 수 있다. 이는 반복문에서 사용하기 편하다.
포인터 타입으로 생각하면 간단하다. STL의 모든 자료 구조는 내부 원소 순회를 위한 반복자를 제공한다.
각 템플릿마다 iterator 타입은 각각 따로 존재한다.
vector<int>::iterator vectorIntIt;
vector<float>::iterator vectorFloatIt;
list<string>::iterator listStringIt;
unordered_map<unsigned int, vector<string>>::iterator uMapIt;
set<long>::iterator longSetIt;
순회 방식에 따라 3가지로 나뉜다.
사용 가능 연산자 | ||
순방향 | ++로 다음 반복자 이동 | *, ++, ==, != |
양방향 | ++로 다음 반복자 이동 및 --로 이전 반복자 이동 | *, ++, --, ==, != |
임의 접근 | 순방향 + 양방향 + 임의 접근 (Random Access Iterator) | *, ++, --, +, -, +=, -=, ==, !=, <, >, <=, >= |
양방향 접근 반복자는 next()와 prev()로 이전/다음 반복자를 가져올 수 있다.
임의 접근 반복자는 next/prev도 사용할 수 있고 직접 연산 (+, -)를 통해 반복자를 증감 시킬 수 있다.
ex) vector<int>::iterator it; it += 3;
사용 시에는 다음을 주의하자.
1. end() 반복자는 마지막 요소를 가리키지 않는다.
2.반복자로 반복문을 돌 때 꼭 마지막 요소를 확인하고 시작 반복자를 너무 크게 증가해서 마지막 요소를 건너뛰는 일이 없도록 하자.
1. 반복문 사용
단순히 auto를 사용하면 값 수정이 가능한 iterator로 된다는 것에 주의하자.
값 변경을 막고 싶은 const로 선언하려면 아래 예시를 확인하자.
using namespace std;
int main()
{
vector<int> v{ 1, 2, 3, 4, 5 };
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it)
{
// *it 값 변경 가능
*it *= *it;
cout << *it << ' ';
}
cout << endl;
for (auto it = v.begin(); it != v.end(); ++it)
{
// *it 값 변경 가능
*it *= *it;
cout << *it << ' ';
}
cout << endl;
for (vector<int>::const_iterator it = v.begin(); it != v.end(); ++it)
{
// *it 값 변경 불가
cout << *it << ' ';
}
cout << endl;
for (auto it = begin(const_cast<const vector<int>&>(v)); it != v.end(); ++it)
{
// *it 값 변경 불가
cout << *it << ' ';
}
cout << endl;
// cbegin, cend C++14 이상
for (auto it = v.cbegin(); it != v.cend(); ++it)
{
// *it 값 변경 불가
cout << *it << ' ';
}
cout << endl;
// as_const C++17 이상
for (auto it = begin(as_const(v)); it != v.end(); ++it)
{
// *it 값 변경 불가
cout << *it << ' ';
}
cout << endl;
}
// output
1 4 9 16 25
1 16 81 256 625
1 16 81 256 625
1 16 81 256 625
1 16 81 256 625
1 16 81 256 625
2. 단순한 반복문
첫번째 루프에서 값이 복사되었기 때문에 내부에서 vv 값을 변경해도 v의 실제 원소에는 영향이 없다는 것을 확인하자.
using namespace std;
int main()
{
vector<int> v{ 1, 2, 3, 4, 5 };
for (auto vv : v)
{
// v의 값이 복사되어서 사용
vv *= vv;
cout << vv << ' ';
}
cout << endl;
for (auto& vv : v)
{
// v의 값이 참조로 사용
vv *= 2;
cout << vv << ' ';
}
cout << endl;
for (const auto vv : v)
{
// v의 값이 복사되고 값 변경 불가
cout << vv << ' ';
}
cout << endl;
for (const auto& vv : v)
{
// v의 값 상수 참조
cout << vv << ' ';
}
cout << endl;
}
// output
1 4 9 16 25
2 4 6 8 10
2 4 6 8 10
2 4 6 8 10
3. distance
두 반복자간의 거리를 계산
using namespace std;
int main()
{
vector<int> v{ 1, 3, 5, 7, 9 };
auto it = lower_bound(v.begin(), v.end(), 8);
auto dist = distance(v.begin(), it); // (index = 4)
cout << "v.begin(): " << *v.begin() << endl;
cout << "lower_bound(8): " << *it << endl;
cout << "dist: " << dist << endl; // 4 - 0
}
// output
v.begin(): 1
lower_bound(8): 9
dist: 4
4. advance
반복자를 n번 증가
(반복자 += n 과 비슷하며 반복자 값 자체를 바꿈)
using namespace std;
int main()
{
vector<int> v{ 1, 3, 5, 7, 9 };
auto it = v.begin();
cout << *it << endl;
advance(it, 2);
cout << *it << endl;
}
// output
1
5
5. 반복자를 사용하는 <algorithm> 헤더의 함수들
** 범위는 [first, last)로, first 요소는 포함하고 last 요소는 포함하지 않는다.
find (first, last, value) | 검색 구간에서 해당 값과 일치하는 첫번째 요소의 반복자를 반환; 찾지 못하였으면 last 반환 | 순차적으로 확인 (순방향) 컨테이너 변경 없음 |
find_if(first, last, predicate) | 검색 구간에서 술어(predicate)에 따라 검색을 수행; 조건에 맞는 첫번째 값을 찾지 못하였으면 last 반환 | 순차적으로 확인 (순방향) 컨테이너 변경 없음 람다식 사용 가능 |
count(first, last, value) | 검색 구간 내에서 해당 값과 일치하는 요소의 개수를 반환 | 순차적으로 확인 (순방향) 컨테이너 변경 없음 |
count_if(first, last, predicate) | 검색 구간에서 술어(predicate)에 따라 검색을 수행하고 조건에 맞는 요소의 개수 반환 | 순차적으로 확인 (순방향) 컨테이너 변경 없음 람다식 사용 가능 |
copy (first, last, begin) | 검색 구간(first ~ last)의 값을 begin에 복사 | 복사할 크기만 큼 미리 공간을 확보해야한다. (공간이 부족한 컨테이너에 값을 복사하려고 하면 에러가 나타날 것이다.) |
unique (first, last) | 구간(first ~ last)에서 중복된 요소들을 뒤로 보낸다. 중복되어서 뒤로 보내졌던 첫번째 중복된 요소를 가리키는 반복자를 반환 | 순차적으로 확인 (순방향) |
sort(first, last, comp) | 구간의 요소들을 오름차순 정렬 (정렬 방법은 comp에 따라 달라짐; default: 오름차순) |
컨테이너 수정 일어남 보통 quick_sort로 정렬 |
Sample Code
using namespace std;
void print(vector<int>::iterator first, vector<int>::iterator last)
{
for (auto it = first; it != last; ++it) cout << *it << '\t';
cout << endl;
}
int main()
{
vector<int> v{ -3, -1, 5, -9, 7, -1, 5, -3, -1 };
// 처음으로 양수가 나오는 반복자 반환
auto f = find_if(v.begin(), v.end(), [](int a) { return a > 0; });
cout << *f << endl; //
// (3, 10) 내의 요소 개수 반환
auto c = count_if(v.begin(), v.end(), [](int a) { return a > 3 && a < 10; });
cout << c << endl; //
vector<int> vv(v.size()); // 공간 확보
copy(v.begin(), v.end(), vv.begin());
print(vv.begin(), vv.end());
// 내림차순 정렬
sort(vv.begin(), vv.end(), greater<int>());
print(vv.begin(), vv.end());
// 중복된 요소 뒤로 보내기
auto unique_it = unique(vv.begin(), vv.end());
print(vv.begin(), vv.end());
// 중복된 요소를 뒤로 보낸 후에 resize를 통하여 중복 요소 제거
vv.resize(distance(vv.begin(), unique_it));
print(vv.begin(), vv.end());
// 절대값으로 오름차순 정렬
sort(vv.begin(), vv.end(), [](int a, int b) { return abs(a) < abs(b); });
print(vv.begin(), vv.end());
}
// output
처음으로 양수가 나오는 요소
5
(3, 10) 구간에 해당하는 요소의 개수
3
복사 후의 vv 벡터 요소
-3 -1 5 -9 7 -1 5 -3 -1
내림차순 정렬
7 5 5 -1 -1 -1 -3 -3 -9
unique 결과
7 5 -1 -3 -9 -1 -3 -3 -9
resize 결과 (중복 요소 제거)
7 5 -1 -3 -9
절댓값으로 오름차순 정렬
-1 -3 5 7 -9
알고리즘 문제를 풀 때, 반복자를 잘 활용하면 유용할 것이다.
'Language > C++' 카테고리의 다른 글
Visual Studio glog 사용 (2) | 2023.10.26 |
---|---|
메타 함수, 템플릿 1 (1) | 2023.10.26 |
Protobuf 프로젝트에 추가하기 (Windows, vcpkg) (0) | 2023.10.11 |
Protobuf 프로젝트에 추가하기 (Windows, CMake) (2) | 2023.10.10 |
비트 마스킹을 이용한 소수 찾기 (에라토스테네스의 체) (0) | 2023.09.27 |