2024. 3. 20. 14:20ㆍProgramming Language/C++
iterator (반복자)
• 저장된 데이터를 순회(iterate)할 수 있는 기능을 제공하는 객체
• iterator는 포인터처럼 동작하지만 포인터가 아니라 클래스 객체이다.
사용자가 데이터 구조의 내부 구현을 알 필요 없이 데이터를 순회할 수 있도록 돕는 공통 인터페이스를 제공한다.
• 각 iterator는 자기가 구현되어있는 해당 자료구조에 맞게 동작한다.
구현되어있는 컨테이너만 다를 뿐, 이름은 동일하고 사용법도 같다.
• list의 경우 iterator는 노드의 주소를 저장하며, 연산자 오버로딩(`operator*`, `operator++`, `operator!=` 등)을 통해 데이터에 접근할 수 있도록 한다.
iterator가 필요한 이유
`std::vector`는 연속된 메모리 구조이므로 인덱스를 통해 데이터에 직접 접근할 수 있지만, `std::list`처럼 불연속적인 메모리 구조라면 특정 인덱스에 접근하는 함수를 제공하지 않는다.
list는 노드 기반의 데이터 구조로, 특정 데이터를 접근하려면 맨 처음 노드부터 순회하면서 다음 노드를 찾아가야 한다.
이런 과정을 프로그래머가 직접 구현하면 복잡해질 수 있기 때문에 C++ 표준 라이브러리는 편리하게 데이터를 순회할 수 있게 iterator를 제공해 준다.
중첩 클래스(Nested class) iterator
중첩 클래스(nested class)
• 클래스 내부에 다른 클래스를 정의하는 것
• iterator는 일반적으로 데이터 구조 클래스 내부에 정의된 중첩 클래스로 구현된다.
ex) `std::list<int>::iterator`는 `std::list<int>` 클래스 내부에 정의된 iterator 클래스이다.
iterator 역할
• 데이터 구조의 첫 번째 요소를 가리킨다.
std::list<int> listInt = {10, 20, 30};
std::list<int>::iterator iter = listInt.begin(); // 첫 번째 노드를 가리킨다.
`listInt.begin()`은 리스트의 첫 번째 노드의 iterator를 반환한다.
리스트는 특정 인덱스 접근이 비효율적인 자료구조라서 벡터처럼 특정 인덱스에 접근하는 것과 관련된 함수를 제공하지 않는다.
따라서 iterator로 하나씩 증가하면서 순차적으로 순회해야 한다.
• 데이터에 접근한다.
// iter가 가리키는 데이터를 반환한다.
int iData = *iter; // 10
// *는 해당 자료구조 안에 있는 데이터를 가리키고 지칭하는 역할을 한다.
// 마치 포인터 역할을 하며, *를 operator로 역참조처럼 구현할 수 있다.
`*iter`는 `operator*()`가 iterator 클래스에 오버로딩되어 있기 때문에 iterator가 가리키는 데이터만 접근하여 반환한다.
`iter`가 마치 포인터처럼 동작한다.
`iter`가 리스트 안의 첫 번째 데이터를 받아오고 그것을 역참조하여 값을 확인한다.
※ 이때 `iter`는 포인터가 아니라 iterator 클래스의 객체이다.
• 다음 요소로 이동한다.
++iter; // 다음 노드로 이동한다.
int nextData = *iter; // 20
`++iter`는 iterator를 다음 노드로 이동시키는데, 이는 `operator++()`가 오버로딩되어 있기 때문이다.
• 끝까지 순회한다.
for (std::list<int>::iterator it = listInt.begin(); it != listInt.end(); ++it) {
std::cout << *it << " "; // 10 20 30
}
만약 end()가 마지막 요소의 다음이 아닌 마지막 요소라면?
• iterator가 마지막이 아니여야 반복되는데 마지막 데이터에서 같아지기 때문에 마지막 요소는 출력되지 않는 문제가 발생한다.
iterator 주요 특징
• 데이터 순회
list, vector, map 등 다양한 데이터 구조에서 사용할 수 있다.
• 포인터처럼 동작
`*`, `++` 등을 통해 데이터에 접근하거나 이동할 수 있다.
• 추상화 제공
프로그래머는 데이터 구조의 내부 구현을 몰라도 데이터를 다룰 수 있다.
▷ iterator 예시
#include <iostream>
#include <list>
int main() {
std::list<int> numbers = {10, 20, 30};
// iterator 선언
std::list<int>::iterator iter = numbers.begin();
// 데이터 순회
while (iter != numbers.end()) {
std::cout << *iter << " "; // 10 20 30
++iter; // 다음 노드로 이동
}
return 0;
}
▷ iterator 예시(vector, list)
#include <iostream>
#include <vector>
#include <list>
using std::cout;
using std::endl;
using std::vector;
using std::list;
int main()
{
// ----------- vector ----------- //
vector<int> vecInt;
vecInt.push_back(10);
vecInt.push_back(20);
// 배열 버전 (마치 배열처럼 연산자를 사용하여 접근)
vecInt[0] = 100;
vecInt.at(1); // vecInt[1];
vector<int>::iterator veciter = vecInt.begin();
*veciter = 100; // vecInt[0];과 같다. (벡터 안에 넣어놓은 첫 번째 데이터에 접근)
++veciter; // 다음 데이터를 가리킨다.
vecInt[0] = 100;
// 벡터는 특정 인덱스에 접근하기 최적화되어있는 자료구조라서 iterator를 굳이 사용할 필요는 없다. (비효율적이다.)
// 마냥 그렇지많은 않다.
// 벡터도 넣어놓은 데이터를 삭제하고 싶을 수도 있다.
// 특정 자리를 비웠다면 그 자리를 채우기 위해 뒤의 데이터들을 앞으로 땡겨와야 한다. (연속적인 구조)
// 원래 데이터가 6개였는데 1개를 삭제하여 5개가 된 경우
// 앞으로 땡기지 않고 데이터를 넣는다면(push_back()) 넣는 자리에 원래 있는 데이터를 덮어버린다.
// 특정 위치에 있는 데이터를 삭제하는 경우
// 몇 번째를 삭제하는지 알아야 한다.
// 찾아보니 제거하는 함수로 erase()가 있다.
// 그러나 해당 함수는 벡터에 구현되어있는 iterator를 요구한다.
//vecInt.erase();
// 삭제한 요소의 바로 다음 iterator를 반환한다.
// 삭제하고 싶은 부분을 iterator로 가리켜서 해당 데이터를 주면 가리키는 곳을 삭제한다.
// 이처럼 컨테이너마다 iterator를 이용한 설계가 공통적으로 적용되어있다.
// 같은 자료구조여도 각 iterator는 전혀 다른 클래스이다.
vector<float>::iterator;
vector<short>::iterator;
vector<int>::iterator;
// ----------- list ----------- //
list<int> listInt;
listInt.push_back(10);
listInt.push_back(100);
list<int>::iterator iter = listInt.begin(); // 해당 리스트의 시작 부분을 가리킨다.
int iData = *iter; // 10
++iter; // 다음 데이터를 가리킨다. (= 다음 노드로 접근하여 그곳의 데이터 파트에 접근한다.)
iData = *iter;
//listInt.erase();
// 리스트 안에 넣어놓은 데이터 중에 특정 위치를 가리키는 iterator를 받는다.
// 해당 iterator가 가리키고 있는 데이터를 지운다.
// 리스트 안에 넣어놓은 데이터 전체를 한 바퀴 순회하면서 출력
for (iter = listInt.begin(); iter != listInt.end(); ++iter)
{
cout << *iter << endl;
}
return 0;
}
'Programming Language > C++' 카테고리의 다른 글
[C++] list iterator, inline (0) | 2024.03.22 |
---|---|
[C++] erase() (+ clear() 문제점) (0) | 2024.03.21 |
[C++ STL] 벡터(vector), 리스트(list) (0) | 2024.03.19 |
[C++] 범위 지정 연산자(::), using namespace std; (0) | 2024.03.19 |
[C++] 구조체 · 클래스 템플릿으로 리스트 구현 (0) | 2024.03.18 |