본문 바로가기

프로그래밍 공부/C++

vector, iterator 알아보기

728x90

vector

  • 크기가 자유롭게 늘어나는 배열
    일반적인 배열 int arr[]는 처음에 정한 크기를 바꿀 수 없다.
    하지만 vector는 데이터가 꽉 차면 스스로 덩치를 키운다.

iterator

한문장 정리: 컨테이너 안의 원소를 가리키는 '포인터 비슷한 것'

  • 인덱스가 아니고, 위치를 나타냄

이건 vector v의 첫 번째 원소 위치를 가리키는 변수이다.

포인터는 메모리 주소를 직접 접근하는 것에 목적이 있지만,
이터레이터는 컨테이너를 추상화해서 다루는 장비이다.

*it      // 값 접근
++it     // 다음 원소
it1 == it2  // 같은 위치인지
vector<int> v={1,2,3,4,5};
for (auto it = v.begin(); it != v.end(); ++it) {
    cout << *it << '\n';
}
//원소 모두 출력
//주의할 점: v.end()는 마지막 원소의 다음 위치! 그래서 *(v.end())는 error

v.begin(): 벡터의 첫 번째 데이터를 가리킵니다.

v.end(): 벡터의 마지막 데이터 바로 뒤를 가리킵니다. (데이터가 없는 끝 지점입니다.)

++it: 화살표를 다음 칸으로 한 칸 옮깁니다.

*it: 현재 화살표가 가리키고 있는 칸에 들어있는 실제 숫자를 꺼냅니다.

컨테이너에 따른 이터레이터 연산 가능/불가능


Q&A

1. iterator의 타입은 주소값이야?

it1 == it2는 같은 주소인지 비교하는 거야?

짧은 답

논리적으로는 ‘같은 위치’ 비교고, 구현상은 주소일 수도 있고 아닐 수도 있음.

조금 풀어서

  • iterator는 주소값 그 자체라고 약속된 적은 없음
  • 하지만 vector, 배열 같은 경우는 내부적으로 진짜 포인터처럼 구현되는 경우가 많음

그래서:

it1 == it2

의 의미는

“같은 컨테이너의 같은 원소를 가리키고 있나?”

이지

“메모리 주소 숫자가 같나?”

는 아님 (결과는 비슷해 보일 수 있지만 개념은 다름).

예를 들어:

  • vector<int>::iterator → 거의 포인터
  • map<int,int>::iterator → 트리 노드를 감싼 객체

둘 다 ==는 되지만
주소 비교라는 개념으로 생각하면 바로 오해 생김.

👉 안전한 사고 방식:
iterator는 ‘주소처럼 행동하는 추상 객체’라고 생각해라.


2. ++i는 전위 연산자잖아?

그럼 ++it는 왜 다음 원소고, it++는 왜 안 쓰는 거야?

핵심부터

  • ++itit++ 둘 다 다음 원소로 이동
  • 하지만 의미와 비용이 다름

전위 증가 (++it)

++it;
  • 먼저 증가
  • 증가된 iterator를 반환
  • 불필요한 복사 없음

후위 증가 (it++)

it++;
  • 증가 전 iterator를 복사
  • 그 복사본을 반환
  • 그 다음 실제 iterator 증가

즉 내부적으로:

auto temp = it;
++it;
return temp;

이런 느낌.


그래서 왜 ++it를 쓰냐

  • iterator는 객체라서 복사가 비용일 수 있음
  • 특히 map, set iterator는 가볍지 않음
  • STL 관례상 의미 없는 복사를 피하자는 문화

그래서:

“증가만 필요하면 무조건 ++it

이건 거의 C++ 계의 암묵적 헌법임.


3. 컨테이너마다 가능한 iterator 연산이 다른 거야?

정확히 그렇다.
이게 iterator 등급 개념의 핵심임.

예시로 보면 바로 감 옴

vector (random access iterator)

it + 5
it - 3
it2 - it1
it < it2

전부 가능.


list (bidirectional iterator)

++it
--it

이건 되는데

it + 3   // 컴파일 에러

안 됨.


forward_list (forward iterator)

++it

만 가능.

--it   // 불가

그래서 STL 알고리즘도 제약이 있음:

알고리즘 요구 iterator
sort random access
lower_bound 최소 forward (속도 차이 큼)
reverse bidirectional
find input iterator

👉 컴파일 에러가 나면 “내가 틀린 게 아니라 컨테이너가 못 하는 거일 수도 있음”


4. 컨테이너 종류를 바꿀 수 있는 거야?

정의할 때 이미 정해지는 거 아니야?

이 질문이 진짜 중요하다.

물리적으로는

vector<int> v;

하면
이 변수는 영원히 vector임.
list로 변신 안 함. 초능력 없음.


그런데 “바꿀 수 있다”는 말의 진짜 의미

코드를 거의 안 바꾸고, 컨테이너만 교체할 수 있게 설계 가능하다는 뜻.

예:

template <typename Container>
void print_all(Container& c) {
    for (auto it = c.begin(); it != c.end(); ++it)
        cout << *it << ' ';
}

이 함수는:

  • vector
  • list
  • deque
  • set

전부 받아먹음.

왜?

  • iterator 인터페이스가 같기 때문.

반대로 이런 코드는 바꾸기 힘듦

for (int i = 0; i < v.size(); i++) {
    cout << v[i];
}

이건:

  • vector 전용
  • list로 바꾸는 순간 전멸

그래서 STL 철학이 이거임

“컨테이너는 바뀔 수 있고,
알고리즘은 iterator 위에서 동작한다.”

이게 왜 좋냐면:

  • 처음엔 vector
  • 나중에 성능 문제 생기면 list / deque / set으로 교체
  • 로직은 거의 그대로