일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- vector capacity
- return by reference
- vector size
- std::vector
- delete function
- this call
- conversion constructor
- virtual destructor
- discord bot
- virtual function
- std::ostream
- c++ basic practice
- virtual function table
- pointer to member data
- diamond inheritance
- base from member
- member function pointer
- constructor
- placement new
- operator overloading
- suffix return type
- std::cout
- 더 지니어스 양면포커
- c++ multi chatting room
- std::endl
- increment operator
- dynamic_cast
- virtual inheritance
- new&delete
- C++
- Today
- Total
I'm FanJae.
[C++] Iterator 본문
1.반복자(iterator)
#include <iostream>
#include <list>
int main()
{
int x[5] = {1,2,3,4,5};
int* p1 = x;
++p1;
std::cout << *p1 << " ";
std::list s = {1,2,3,4,5};
auto p2 = s.begin();
++p2;
std::cout << *p2 << " "; // 2
}
- 배열은 연속된 메모리이다.
- 1번째 요소의 주소를 담은 포인터 변수만 있으면, ++, * 연산으로 모든 요소에 접근할 수 있다.
- 반면 일반적인 list는 연속된 메모리가 아니므로 ++연산등을 통한 요소 접근은 불가능하다.
- 그러나, std::list는 연속된 메모리가 아니지만, 반복자(iterator)만 있으면 ++, * 연산으로 모든 요소에 접근이 가능하다.
- p2 자체가 포인터는 아니다.
- 내부적으로 포인터 멤버 데이터가 있고, operator++, operator* 연산자를 재정의해 포인터처럼 만든 것이다.
std::vector v = {1,2,3,4,5};
auto p2 = v.begin();
++p2;
std::cout << *p2 << " "; // 2
- vector가 연속된 메모리임에도 불구하고 이와 같이 문제없이 출력하는것을 확인할 수 있다.
1-1. 반복자(iterator) 디자인 패턴
- 복합 객체의 내부 구조에 상관없이 동일한 방법으로 모든 요소에 접근하게 한다.
- 즉, 모든 컨테이너의 요소 접근을 동일한 방법으로 수행한다.
① STL 에서의 반복자 구현 방식
- 포인터와 동일한 방법으로 사용할 수 있도록 만들어져있다.
- 즉, 이는 진짜 포인터는 아니지만 포인터와 동일한 방식으로 ++, * 연산자를 사용한다.
1-2. 반복자(iterator)의 정확한 타입
#include <list>
#include <vector>
#include <ranges>
int main()
{
// 1. 반복자의 정확한 타입
std::list c = {1,2,3,4,5};
std::list::iterator p = c.begin(); // C++17
std::list<int>::iterator p = c.begin(); // C++17 이하
}
- 반복자의 정확한 타입은 컨테이너타입::iterator이다.
- std::list<int>::iteartor p1 = c.begin() // C++ 17 이전까지는 이와같다.
- std::list::iterator p1 = c.begin() // c++ 17 부터는 이 방버볻 지원한다.
① 문제점
- 코드가 복잡해보인다. 즉, 가독성이 상당히 나쁘다.
- 컨테이너 변경시 반복자 타입도 변경되어야 한다. (아래 예시 참고)
std::vector c = {1,2,3,4,5};
std::vector<int>::iterator p = c.begin();
- list를 사용하다가 vector로 바꾸면 반복자 타입도 수정해야한다.
- 따라서 되도록이면 auto를 사용한다.
② 반복자를 꺼내는 3가지 방법
c.begin() | C++98 시절 부터 사용되던 전통적인 방법 단점으로, ray array에는 사용을 할 수 없다. |
std::begin(c) | C++11 에서 추가된 방법이다. c가 raw array라도 사용이 가능하다. |
std::range::begin(c) | <ranges> 헤더 추가 필요 C++20 에서 추가된 방법으로 위 방식 보다 좀 더 안전한 방식이라고 한다. |
1-3. 반복자 관련 유의사항
#include <iostream>
#include <vector>
int main()
{
std::vector v1 = {1, 2, 3, 4, 5};
auto p1 = v1.begin();
auto p2 = v1.end();
*p1 = 10;
*p2 = 20; // runtime error
}
- begin()은 시작 주소를 가리키는데 반해, end()는 마지막 요소가 아닌 마지막 다음 요소를 가리키는 반복자이다.
- 따라서 * 연산을 하면 안된다. runtime error를 발생시킨다.
- c.begin() 등의 방식으로 꺼낸 반복자가 구간에 끝에 도달했는지 조사하는 용도로 사용된다.
- 대략 위와 같은 형태라고 볼 수 있다.
while( p1 != p2 )
{
std::cout << *p1 << " ";
++p1;
}
1-4. reverse iterator
#include <iostream>
#include <vector>
int main()
{
std::vector v1 = {1,2,3,4,5};
auto p1 = v1.begin();
auto p2 = v1.end();
while(p1 != p2)
{
std::cout << *p1 << " ";
++p1;
}
}
- 이는 일반적인 정방향 순회의 예제이다.
- iterator는 정방향 뿐만 아니라 역방향도 제공한다.
#include <iostream>
#include <vector>
int main()
{
std::vector v1 = {1,2,3,4,5};
auto p1 = v1.rbegin();
auto p2 = v1.rend();
while( p1 != p2 )
{
std::cout << *p1 << std::endl;
++p1;
}
}
- 역반복자(reverse iterator)란, 컨테이너의 요소를 거꾸로 순회할때 사용한다.
- 역반복자를 사용하면 기존에 작성된 정방향으로 동작하는 알고리즘을 코드 수정없이 역방향으로 동작하게 할 수 있다.
2. ranges
2-1. range - for
#include <iostream>
#include <vector>
int main()
{
std::vector v = {1,2,3,4,5};
for ( auto e : v )
std::cout << e << std::endl;
}
- 컨테이너의 모든 요소를 열거할 때 range - for를 사용하면 편리하게 열거 가능하다.
- 단, 유의할 점은 이와 같이 반복문을 이용해 모든 값을 수정한다면 참조를 사용해 수정해야한다.
#include <iostream>
#include <vector>
int main()
{
std::vector v = {1,2,3,4,5};
for ( auto& e : v )
std::cout << e << std::endl;
}
- 이와 같이 사용할 수 있다.
※ 일부 요소만 열거 하고 싶은 경우
① C++20 에서 추가된 view 개념 사용
② legacy for 문과 [] 연산자
③ legacy for 문과 반복자
2-1. std::views
2-1-1. view를 이용한 정방향 열거
#include <iostream>
#include <vector>
#include <ranges> // C++20
int main()
{
std::vector v = {1,2,3,4,5};
for ( auto& e : std::views::take(v, 3) )
{
std::cout << e << std::endl;
}
}
2-1-2. view를 이용한 역방향 열거
#include <iostream>
#include <vector>
#include <ranges> // C++20
int main()
{
std::vector v = {1,2,3,4,5};
for ( auto& e : std::views::take(v, 5) ) // 순차적으로 5개
{
std::cout << e << std::endl;
}
for ( auto& e : std::views::reverse(v, 5) ) // 역순으로 5개
{
std::cout << e << std::endl;
}
}
2-1-3. view를 이용한 중간 범위 삭제 후 열거
#include <iostream>
#include <vector>
#include <ranges> // C++20
int main()
{
std::vector v = {1,2,3,4,5};
for ( auto& e : std::views::drop(v, 5) ) // 5개를 삭제 후 열거 즉, 6,7,8,9,10
{
std::cout << e << std::endl;
}
}
- 이를 이용하면, 앞에 있는 데이터 5개를 제외시키고, 열거한다.
2-1-4. 특정 조건이 만족하는 것에 대한 열거
#include <iostream>
#include <vector>
#include <ranges> // C++20
bool foo(int n) { return n % 2 == 0; }
int main()
{
std::vector v = {1,2,3,4,5};
for ( auto& e : std::views::filter(v, foo) ) // 모든 요소 중 참을 리턴하는 것
{
std::cout << e << std::endl;
}
for ( auto& e : std::views::filter(v, [](int n) { return n % 2 == 0; } ) ) // 동일
{
std::cout << e << std::endl;
}
}
- 특정 조건이 만족하는 즉, 참을 리턴하는 것만 뽑아낼 수 있다.
- 위와 같이 활용할 수 있다. 위 아래는 모두 동일한 결과를 리턴한다.
2-2. legacy for 문과 [] 연산자
#include <iostream>
#include <vector>
#include <ranges> // C++20
int main()
{
std::vector v = {1,2,3,4,5};
for (int i = 0; i < 3; i++)
{
v[i] = 0; // list 지원 안됨.
}
}
- 이렇게 사용할 경우 list는 지원이 안된다는 문제가 존재한다.
2-3. legacy for 문과 반복자
#include <iostream>
#include <vector>
#include <ranges> // C++20
int main()
{
std::vector v = {1,2,3,4,5};
for (int i = 0; i < 3; i++)
{
// v[i] = 0; // list 지원 안됨.
*p = 0; // 모든 컨테이너 가능.
}
}
- 이와 같이 반복자를 사용하면 모든 컨테이너에 대해 지원이 된다.
'C++ > Basic' 카테고리의 다른 글
[C++] Exception (0) | 2024.08.29 |
---|---|
[C++] Algorithm (3) | 2024.08.28 |
[C++] STL Container II (0) | 2024.08.27 |
[C++] STL Container I (0) | 2024.08.27 |
[C++] Operator Overloading IV (2) | 2024.08.26 |