컨테이너
1. 같은 타입의 여러 객체를 저장할 수 있는 묶음 단위의 데이터 구조이며, 주 역할은 데이터를 저장하고 관리하며 저장된 원소에 접근할 수 있는 멤버 함수를 제공한다.
2. 표준 템플릿 라이브러리의 컨테이너는 템플릿 클래스 형태로 제공된다.
순차 컨테이너
1. 원소들이 선형으로 배열된 데이터 구조이며, 각 원소는 위치(인덱스)에 따라 접근할 수 있다.
순차 컨테이너의 종류
1. 배열(std::array)
1) C++11 에서 소개된 array는 고정된 크기의 배열을 담는 컨테이너이다.
2) 일반 배열 변수가 포인터로 변환되었을 때 배열의 길이 정보를 알 수 없다는 문제와 동적 배열의 메모리 할당, 해제 문제에서 벗어날 수 있는 장점이 있다.
3) 초기화할 때 작성한 원소의 갯수보다 많은 값을 넣으려고 시도하면 컴파일 단계에서 오류가 발생한다.
4) 일반 배열 처럼 [] 연산자로 원소에 접근할 때 배열의 유효 범위 밖의 인덱스로 접근을 시도하면 런타임 오류가 발생한다. (런타임 오류를 방지하고자 at 함수를 사용하고 배열의 유효 범위를 벗어나는 인덱스로 접근을 시도할 경우 std::out_of_range 예외를 던진다.)
#include <iostream>
#include <array>
using namespace std;
int main() {
// 크기가 5인 std::array 생성
array<int, 5> myArray;
// 배열 초기화
myArray = { 1, 2, 3, 4, 5 };
// 배열 출력
cout << "배열 출력: ";
for (int i = 0; i < 5; i++) {
cout << myArray[i] << " ";
}
cout << endl;
// 배열 크기 출력
cout << "배열 크기: " << myArray.size() << endl;
// 배열의 첫 번째 요소 출력
cout << "첫 번째 요소: " << myArray[0] << endl;
// 배열의 두 번째 요소 변경
myArray[1] = 10;
// 변경된 배열 출력
cout << "변경된 배열: ";
for (int i = 0; i < 5; i++) {
cout << myArray[i] << " ";
}
cout << endl;
return 0;
}
2. 벡터(std::vector)
1) 동적인 길이의 배열 컨테이너이며, 동적 배열 관리를 안전하게 수행해주기 때문에 표준 컨테이너 가운데 가장 많이 활용된다.
2) 벡터의 크기는 동적이므로 힙 메모리가 허용하는 한 계속 늘릴 수 있으며, 배열의 길이보다 항상 여유분을 포함한 메모리를 운영합니다.
3) 벡터는 원소의 삽입, 삭제시 밀리거나, 당겨져지며 복사 저장이 발생하기 때문에 삽입이나, 삭제가 빈번하게 발생하는 데이터일 경우 다른 컨테이너를 사용하는 것이 좋다.
4) 동적 배열로 구현되어 원소들은 연속된 메모리 공간에 저장된다. 따라서 임의 접근에 효율적이다.
5) 기존에 할당된 공간이 다차서 공간이 부족할 경우 더 큰 새로운 공간으로 이동하면서 원소의 갯수만큼 복사해야 한다. 따라서 원소의 갯수(n)만큼 시간 복잡도는 O(n)만큼 걸린다.
#include <iostream>
#include <vector>
using namespace std;
// 벡터의 모든 원소를 출력하는 함수
template <typename T>
void print_vector_all(vector<T>& vec) {
cout << "벡터 내 원소 개수 : " << vec.size() << endl;
// 벡터의 모든 원소 출력
for (typename vector<T>::iterator it = vec.begin(); it != vec.end(); it++) {
cout << *it << " ";
}
cout << endl << "---------------------" << endl;
}
int main() {
vector<int> vec;
vec.push_back(0);
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
vec.push_back(40);
cout << "원본" << endl;
print_vector_all(vec);
// 벡터에 원소 삽입
vec.insert(vec.begin() + 3, 25); // vec[3] 앞에 25 추가
cout << "insert 결과" << endl;
print_vector_all(vec);
// 벡터에서 원소 삭제
vec.erase(vec.begin() + 3); // vec[3] 제거
cout << "erase 결과" << endl;
print_vector_all(vec);
return 0;
}
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vec;
vec.push_back(0);
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
vec.push_back(40);
vector<int>::const_iterator const_it = vec.cbegin();
cout << *const_it << endl;
++const_it;
cout << *const_it << endl;
const_it = vec.cend();
--const_it;
cout << *const_it << endl;
//*const_it = 100; // 값을 변경할 경우 컴파일 오류 발생
return 0;
}
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vec;
for (int i = 0; i < 3; i++)
vec.push_back(i);
for (vector<int>::reverse_iterator it = vec.rbegin(); it != vec.rend(); it++)
cout << *it << endl;
return 0;
}
3. 리스트(std::list)
1) 연결 리스트(linked list)라는 데이터 구조를 구현한 컨테이너이며, 이중 연결 리스트로 구현되어 각 원소에는 다음 원소와 이전 원소를 가리키는 포인터가 있다. 따라서 데이터를 삽입하거나 삭제하는 데 효율적이며, 순서를 유지할 수 있다.
2) 임의 접근이 불가능하며 원소에 접근하려면 이전 원소들을 차례로 따라가야 한다.
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> myList;
// 리스트 뒤쪽에 값 추가
myList.push_back(2);
myList.push_back(3);
myList.push_back(4);
// 리스트 앞쪽에 값 추가
myList.push_front(1);
myList.push_front(0);
// 리스트 출력
cout << "리스트 출력: ";
for (const int& value : myList) {
cout << value << " ";
}
cout << endl;
myList.pop_front(); // 첫 번째 요소 제거
myList.pop_back(); // 마지막 요소 제거
// 삭제 후 리스트 출력
cout << "삭제 후 리스트 출력: ";
for (const int& value : myList) {
cout << value << " ";
}
cout << endl;
// 리스트 크기 확인
cout << "리스트 크기: " << myList.size() << endl;
// 리스트가 비었는지 확인
cout << "리스트가 비었는가? " << (myList.empty() ? "예" : "아니오") << endl;
return 0;
}
4. 덱(std::deque)
1) 벡터 컨테이너와 유사한 구조를 가지고 있으며, 멤버함수도 비슷하다.
2) 벡터와 달리 여러 개의 메모리 블록을 나눠서 저장하는 것이 특징이며, 저장할 공간이 부족하면 일정한 크기의 새로운 메모리 블록을 만들어서 연결한다. 따라서 벡터 컨테이너에서 발생하는 복사 저장이 발생하지 않는다. 즉 벡터의 단점을 보완하는 컨테이너이다.
3) 원소의 삽입과 삭제가 양쪽 끝에서 빈번하게 발생하는 경우 리스트(list)보다 효율적이다.
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> myDeque;
// 덱 뒤쪽에 값 추가
myDeque.push_back(2);
myDeque.push_back(3);
myDeque.push_back(4);
// 덱 앞쪽에 값 추가
myDeque.push_front(1);
myDeque.push_front(0);
// 덱 출력
cout << "deque 출력: ";
for (const int& value : myDeque) {
cout << value << " ";
}
cout << endl;
myDeque.pop_front(); // 첫 번째 요소 제거
myDeque.pop_back(); // 마지막 요소 제거
// 삭제 후 덱 출력
cout << "삭제 후 deque 출력: ";
for (const int& value : myDeque) {
cout << value << " ";
}
cout << endl;
// 덱 크기 확인
cout << "deque 크기: " << myDeque.size() << endl;
// 덱이 비었는지 확인
cout << "deque이 비었는가? " << (myDeque.empty() ? "예" : "아니오") << endl;
// 덱의 첫 번째와 마지막 원소 출력
cout << "deque 첫 번째 원소: " << myDeque.front() << endl;
cout << "deque 마지막 원소: " << myDeque.back() << endl;
return 0;
}
순차 컨테이너 비교
| 컨테이너 |
메모리 저장 위치 |
메모리 구조 / 관리 방식 |
임의 접근 (random access) |
앞 삽입 / 삭제 |
뒤 삽입 / 삭제 |
중간 삽입 / 삭제 |
비고 |
| 🟦 std::array |
스택(Stack) (기본적으로 자동 변수일 경우) ※ 동적할당 시 힙 |
고정 크기 배열 (연속된 메모리 블록) |
✅ O(1) |
❌ 불가능 (크기 고정) |
❌ 불가능 |
❌ 불가능 |
컴파일 타임에 크기 결정 (std::array<int, N>) |
| 🟩 std::vector |
힙(Heap) |
동적 배열 (연속된 메모리, 필요시 재할당) |
✅ O(1) |
❌ O(N) |
✅ amortized O(1) / 삭제 O(1) |
❌ O(N) |
재할당 시 포인터 무효화 |
| 🟨 std::deque |
힙(Heap) |
분할된 블록 배열 (chunk 구조) |
✅ O(1) (약간 느림) |
✅ O(1) |
✅ O(1) |
❌ O(N) |
중간 접근 느림, double-ended queue |
| 🟧 std::list |
힙(Heap) |
이중 연결 리스트 (노드 단위 분산) |
❌ O(N) |
✅ O(1) |
✅ O(1) |
✅ O(1) (iterator 위치에서) |
임의 접근 불가, 메모리 비연속 |
연관 컨테이너
1. 데이터를 저장하고 특정 키를 사용하여 데이터를 효율적으로 검색할 수 있다.
2. 내부 구조는 트리나 해시 테이블과 같은 최적화된 데이터 구조를 사용하므로 검색 작업의 시간 복잡도가 일반적으로 로그 시간[O(logN)] 또는 상수 시간[O(1)]에 가깝다.
3. 대량의 데이터에서도 빠르게 검색할 수 있으며, 실시간 또는 반응형 시스템에서 매우 유용한다.
연관 컨테이너의 종류
1. 세트(std::set)
1) 고유한 값을 정렬된 순서로 저장한다.
2) 데이터 삽입(insert 함수), 데이터 삭제(erase, clear 함수), 데이터 검색(find 함수), 특정 데이터의 갯수 검색(count 함수), 전체 데이터의 갯수 검색(size 함수)의 주요 함수를 제공한다.
3) 내부적으로 이진 탐색 트리를 사용하여 원소를 저장하고 정렬된 순서(기본 오름차순)를 유지하는 정렬 컨테이너이기도 하다.
4) 이진 탐색 트리의 특성 덕분에 삽입, 삭제, 검색 작업의 시간 복잡도가 로그 시간[O(log N)]에 가깝다
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> mySet;
// 값 삽입
mySet.insert(5);
mySet.insert(2);
mySet.insert(8);
// 값 5가 세트에 있는지 확인
if (mySet.find(5) != mySet.end()) {
cout << "5는 set에 저장되어 있음" << endl;
}
// 세트를 순회하며 값 출력
for (auto it = mySet.begin(); it != mySet.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 세트의 크기 출력
int size = mySet.size();
cout << "set 크기: " << size << endl;
return 0;
}
2. 멀티 세트(std::multiset)
1) 정렬된 세트 자료 구조를 구현하는 연관 컨테이너이지만, 중복된 값을 포함한다는 차이가 있다.
2) 데이터를 삭제(erase 함수)할 때 중복된 값이 있다면 모두 제거된다.
#include <iostream>
#include <set>
using namespace std;
int main() {
multiset<int> myMultiset;
// 값 삽입
myMultiset.insert(5);
myMultiset.insert(2);
myMultiset.insert(5); // 중복된 값도 저장됨
// 특정 값의 개수 출력
int count = myMultiset.count(5);
cout << "저장되어 있는 5의 갯수: " << count << endl;
// 멀티 세트를 순회하며 값 출력
for (auto it = myMultiset.begin(); it != myMultiset.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// 멀티 세트의 크기 출력
int size = myMultiset.size();
cout << "multiset 크기: " << size << endl;
return 0;
}
3. 맵(std::map)
1) 키 - 값 쌍을 저장하는 연관 컨테이너이다. 각 키는 고유해야하며 키를 기준으로 정렬된 순서로 저장된다.
2) 특정 키에 연관된 값을 검색하여, 값을 추가, 수정, 제거 등의 작업에 주로 사용된다.
3) 데이터 삽입(insert 함수)시 키 와 값을 묶어 std::pair 객체를 만들어주는 std::make_pair 함수를 사용한다.
4) 데이터 삭제(erase 함수)시 키 값만 넣어주면 삭제된다.
5) 특정 키로 검색(find 함수)하면 맵에서 특정 키에 해당하는 반복자를 반환하며, 이 반복자로 키에 해당하는 원소를 찾을 수 있다.
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main() {
map<string, int> scores;
// 키-값 쌍 삽입
scores.insert(make_pair("Bob", 85));
scores.insert(make_pair("Jane", 90));
scores.insert(make_pair("Tom", 70));
// 맵 크기 출력
cout << "map 크기: " << scores.size() << endl;
// 특정 키에 해당하는 값 검색
auto it = scores.find("Bob");
if (it != scores.end()) {
cout << "Bob의 점수 검색 결과: " << it->second << endl;
}
else {
cout << "Bob의 점수는 저장되어 있지 않음" << endl;
}
cout << endl;
// 특정 키에 해당하는 키-값 제거
scores.erase("Bob"); //Bob 정보 제거
// 맵 크기 출력
cout << "Bob 정보 제거 후, map 크기: " << scores.size() << endl << endl;
// 맵을 순회하며 모든 키와 값 출력
cout << "---map 모든 원소 출력---" << endl;
for (const auto& pair : scores) {
cout << pair.first << ": " << pair.second << endl;
}
return 0;
}
4. 멀티 맵(std::multimap)
1) 키 - 값 쌍으로 구성된 맵을 구현하는 연관 컨테이너이지만, 중복된 키을 허용한다.
2) 내부적으로 키를 기준으로 정렬(기본 오름차순)되고 정렬 기준을 정의할 수 있다.
3) 이진 탐색 트리 자료 구조를 기반으로 구현되어 검색, 삽입, 삭제 등의 작업을 효율적으로 수행한다.
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main() {
multimap<string, int> scores;
// 멀티 맵에 원소(키-값 쌍) 삽입
scores.insert(make_pair("Bob", 85));
scores.insert(make_pair("Jane", 90));
scores.insert(make_pair("Tom", 70));
scores.insert(make_pair("Bob", 100)); // 중복 키 허용
// 멀티 맵의 크기를 출력
cout << "multimap 크기: " << scores.size() << endl;
// 특정 키에 해당하는 원소의 개수 구하기
int count = scores.count("Bob");
cout << "저장되어 있는 Bob 점수의 갯수: " << count << endl;
// 특정 키를 가진 원소의 범위 구하기
auto range = scores.equal_range("Bob");
if (range.first != scores.end()) {
cout << "Bob의 모든 점수: ";
for (auto it = range.first; it != range.second; ++it) {
cout << it->second << " ";
}
cout << endl;
}
else {
cout << "Bob의 점수는 저장되어 있지 않음" << endl;
}
cout << endl;
// 특정 키에 해당하는 모든 원소 삭제
scores.erase("Bob"); //Bob 정보 제거
// 멀티 맵의 크기 출력
cout << "Bob 정보 제거 후, multimap 크기: " << scores.size() << endl << endl;
// 멀티 맵을 순회하며 모든 키와 값 출력
cout << "---multimap 모든 원소 출력---" << endl;
for (const auto& pair : scores) {
cout << pair.first << ": " << pair.second << endl;
}
return 0;
}
연관 컨테이너 비교
| 컨테이너 |
내부 자료구조 |
메모리 저장 위치 |
메모리 구조 방식 |
중복 허용 |
정렬 여부 |
검색 시간 복잡도 |
삽입 시간 복잡도 |
삭제 시간 복잡도 |
특징 요약 |
| 🟩 std::set |
Red-Black Tree (균형 이진 탐색 트리) |
힙(Heap) |
노드 기반 연결 구조(key + 좌/우/부모 포인터) |
❌ |
✅ 자동 정렬 |
O(log N) |
O(log N) |
O(log N) |
중복 없는 정렬된 key 집합 |
| 🟦 std::multiset |
Red-Black Tree |
힙(Heap) |
노드 기반 연결 구조 |
✅ |
✅ 자동 정렬 |
O(log N) |
O(log N) |
O(log N) |
동일 key 여러 개 가능 |
| 🟥 std::map |
Red-Black Tree |
힙(Heap) |
노드 기반 연결 구조(key + value 쌍을 노드로 저장) |
❌ |
✅ key 기준 자동 정렬 |
O(log N) |
O(log N) |
O(log N) |
(key, value) 쌍 저장, 키는 유일 |
| 🟨 std::multimap |
Red-Black Tree |
힙(Heap) |
노드 기반 연결 구조 |
✅ |
✅ key 기준 자동 정렬 |
O(log N) |
O(log N) |
O(log N) |
같은 키에 여러 value 가능 |
| 🟧 std::unordered_set |
Hash Table (버킷 + 체인 구조) |
힙(Heap) |
배열 + 연결 리스트(또는 오픈 어드레싱) |
❌ |
❌ 비정렬 |
평균 O(1)최악 O(N) |
평균 O(1)최악 O(N) |
평균 O(1)최악 O(N) |
중복 없는 비정렬 해시 기반 집합 |
| 🟪 std::unordered_multiset |
Hash Table |
힙(Heap) |
배열 + 체인 연결 구조 |
✅ |
❌ 비정렬 |
평균 O(1)최악 O(N) |
평균 O(1)최악 O(N) |
평균 O(1)최악 O(N) |
중복 허용 비정렬 해시 기반 집합 |
| 🟫 std::unordered_map |
Hash Table (key → value) |
힙(Heap) |
버킷 배열 + 체인 연결 구조 |
❌ |
❌ 비정렬 |
평균 O(1)최악 O(N) |
평균 O(1)최악 O(N) |
평균 O(1)최악 O(N) |
(key,value) 해시 매핑, 순서 없음 |
| ⬛ std::unordered_multimap |
Hash Table |
힙(Heap) |
버킷 배열 + 체인 연결 구조 |
✅ |
❌ 비정렬 |
평균 O(1)최악 O(N) |
평균 O(1)최악 O(N) |
평균 O(1)최악 O(N) |
동일 키 다수 value 가능 |
컨테이너 어댑터
1. 다른 컨테이너를 기반으로 새로운 기능을 제공하는 컨테이너이다. 즉, 기존의 컨테이너를 감싸서 새로운 인터페이스를 제공한다.
2. 반복자(iterator) 를 지원하지 않는다. 내부 구조 은닉. 반복자 없음
컨테이너 어댑터의 종류
1. 스택(std::stack)
1) C++ 표준 템플릿 라이브러리에서 제공하는 컨테이너 어댑터이다.
2) 스택 자료 구조를 구현하는데 사용된다.
3) std::stack 컨테이너 어댑터는 std::deque컨테이너를 기반으로 LIFO 자료 구조를 제공한다.
4) 스택은 후입선출(Last In First Out : LIFO)로 데이터를 꺼내며, 데이터를 임시로 저장하거나 역순으로 처리할 때 주로 사용한다.
5) 반복자(iterator) 를 지원하지 않는다.
6) 스택이 만들어진 주된 이유와 목적
- 함수 호출 관리 : 함수가 호출될 때 스택에 호출 정보(지역 변수, 반환 주소 등)를 저정하고, 함수가 반환될 때 스택에서 이 정보를 제거하여 이전 함수로 복귀합니다. 이로써 함수 호출과 반환을 효율적으로 추적하고 관리할 수 있다.
- 역순 처리 : 스택은 데이터나 작업을 역순으로 처리할 때 유용합니다. 데이터를 스택에 쌓고 나중에 역순으로 꺼내어 처리하면 역순 처리를 구현할 수 있다.
- 컴파일러와 언어 구현 : 컴파일러와 인터프리터는 함수 호출과 반환을 처리하기 위해 스택을 사용한다. 프로그래밍 언어의 호출 스택을 관리하여 함수 호출과 반환을 지원한다.
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> myStack;
// 스택에 데이터 추가
myStack.push(1);
myStack.push(2);
myStack.push(3);
// 스택의 맨 위쪽 값 확인
cout << "맨 위 요소: " << myStack.top() << endl;
// 스택에서 데이터 제거(맨 위쪽 데이터 꺼내기)
myStack.pop();
cout << "맨 위 요소 제거 후, 새로운 맨 위 요소: " << myStack.top()
<< endl;
// 스택의 크기(데이터 개수) 확인
cout << "스택 크기: " << myStack.size() << endl;
// 스택이 비었는지 확인
if (myStack.empty()) {
cout << "스택이 비었습니다." << endl;
}
else {
cout << "스택은 비어 있지 않습니다." << endl;
}
return 0;
}
2. 큐(std::queue)
1) C++ 표준 템플릿 라이브러리에서 제공하는 컨테이너 어댑터이다.
2) 큐 자료 구조를 구현하는 데 사용된다.
3) std::queue 컨테이너 어댑터는 std::deque 컨테이너를 기반으로 FIFO 자료 구조를 제공한다.
4) 큐는 선입선출(First In First Out : FIFO)로 데이터를 꺼내며, 데이터의 순서를 보장하고 먼저 들어온 데이터가 먼저 처리되어야 하는 상황에서 효과적으로 사용된다.
5) 반복자(iterator) 를 지원하지 않는다.
6) 큐이 만들어진 주된 이유와 목적
- 작업 대기열 : 프로세스 스케줄링에서 CPU가 실행할 프로세스를 대기열에 넣어두고 먼저 도착한 프로세스를 먼저 실행할 때에 사용한다.
- 이벤트 처리 : 사용자 입력, 네트워크 메시지, 다양한 이벤트를 큐에 저장하고 차례로 처리할 때도 유용하다.
- 멀티스레딩 환경에서 동기화 : 여러 스레드가 동시에 실행되는 상황에서 스레드 간에 작업이나 데이터 교환을 조절하기 위해 큐를 활용할 수 있다. 하나의 스레드에서 생성된 작업을 큐에 넣고 다른 스레드에서 꺼내어 처리하는 방식으로 스레드 간에 동기화를 간단하게 구현할 수 있다.
- 캐시 구현 : 캐시에서 데이터를 저장하고 검색할 때 큐로 구현할 수 있다. 가장 먼저 들어온 데이터가 먼저 나가는 큐 구조를 사용하여 데이터를 관리한다.
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<int> myQueue;
// 삽입하기
myQueue.push(1);
myQueue.push(2);
myQueue.push(3);
cout << "큐의 맨 앞: " << myQueue.front() << endl; // front
cout << "큐의 맨 뒤: " << myQueue.back() << endl; // back
// 꺼내기
myQueue.pop();
cout << "pop 후 맨 앞: " << myQueue.front() << endl; // pop 후 front
cout << "pop 후 맨 뒤: " << myQueue.back() << endl; // pop 후 back
// 비었는지 확인하기
cout << "큐가 비어 있나요? " << (myQueue.empty() ? "네" : "아니오") << endl;
// 크기 구하기
cout << "큐의 크기: " << myQueue.size() << endl;
return 0;
}
컨테이너 어댑터 비교
| 구분 |
내부 자료구조(기본 컨테이너) |
메모리 구조 |
주요 접근 방식 |
검색 (탐색) 시간복잡도 |
추가 (push) 시간복잡도 |
삭제 (pop) 시간복잡도 |
| stack |
기본적으로 std::deque 사용(옵션: std::vector, std::list 가능) |
동적 힙 메모리 (내부 컨테이너에 따라 다름) |
LIFO (Last-In-First-Out) |
❌ 직접 탐색 불가 (내부 반복자 미지원) |
O(1) (끝에 push) |
O(1) (끝에서 pop) |
| queue |
기본적으로 std::deque 사용(옵션: std::list 가능) |
동적 힙 메모리 |
FIFO (First-In-First-Out) |
❌ 직접 탐색 불가 |
O(1) (뒤에 push) |
O(1) (앞에서 pop) |
반복자
1. 객체지향 프로그래밍에서 배열 같은 컨테이너의 내부 원소들을 순회하는 객체이며, 포인터와 비슷한 동작을 한다고 볼 수 있다.
2. 표준 템플릿 라이브러리에서 모든 컨테이너는 각자의 반복자를 제공한다. 대표적으로 begin, end 함수가 있다.
3. 장점은 컨테이너의 내부 정보를 외부에 노출시키지 않고도 원소에 접근할 수 있다.
4. 반복자는 배열이나, 벡터, 리스트, 큐처럼 선형 컨테이너에 적합하지만, 뒤로 이동할 수 없는 컨테이너나 무한한 개수의 원소를 가진 컨테이너에는 반복자를 사용할 수 없다.
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vec;
for (int i = 0; i < 5; i++)
vec.push_back(i);
for (vector<int>::iterator it = vec.begin(); it != vec.end(); it++)
// 포인터처럼 역참조 연산자(*)를 이용하여 해당 원소에 접근할 수 있다.
cout << *it << endl;
return 0;
}