[C++ STL] vector 완벽 정리: 기본부터 심화까지 (면접 대비)
카테고리: STL
태그: algorithm C++ coding test data structure STL vector implementation vector
1. 📚 vector의 개념
C++ STL(Standard Template Library)에서 가장 자주 사용되는 컨테이너 중 하나인
std::vector
는 동적 배열의 역할을 하며, 그 유연성과 효율성 덕분에 다양한 상황에서 활용됩니다.
📌 vector의 특징
- 연속적인 메모리 공간: 같은 타입의 요소를 연속적인 메모리 공간에 저장하는 동적 배열(dynamic array)
- 동적 크기 조절: C 스타일 배열과 달리, 크기가 프로그램 실행 중에 동적으로 조절될 수 있음
- 효율적인 접근: 인덱스를 사용한 요소 접근(O(1))과 순차적인 순회 성능이 뛰어남
- 자동 메모리 관리: 요소 삽입 또는 삭제 시 필요에 따라 자동으로 크기를 조절
2. 💻 vector의 기본 사용법
📋 vector 선언 및 초기화
-
기본 선언:
std::vector<T> vector_name; // T는 요소의 타입
-
초기화 리스트 사용:
std::vector<int> v = {1, 2, 3, 4, 5};
-
단일 값으로 초기화:
std::vector<int> v(5, 11); // 크기 5, 모든 요소를 11로 초기화
-
다른 컨테이너의 범위에서 초기화:
int arr[] = {1, 2, 3, 4, 5}; std::vector<int> v(arr, arr + 5); // 배열에서 초기화 std::vector<int> v1 = {1, 2, 3, 4, 5}; std::vector<int> v2(v1.begin(), v1.end()); // 다른 벡터에서 초기화
-
std::fill() 함수 사용:
std::vector<int> v(5); std::fill(v.begin(), v.end(), 11);
-
std::iota() 함수 사용:
std::vector<int> v(5); std::iota(v.begin(), v.end(), 11); // 11, 12, 13, 14, 15
🔧 요소 추가 및 삭제
-
끝에 요소 추가:
v.push_back(6); // 끝에 요소 추가 v.emplace_back(7); // 끝에 요소를 직접 생성하여 추가
-
중간에 요소 삽입:
v.insert(v.begin() + 1, 10); // 인덱스 1에 10 삽입 v.emplace(v.begin() + 1, 10); // 인덱스 1에 10을 직접 생성하여 삽입
-
끝에서 요소 삭제:
v.pop_back(); // 마지막 요소 제거
-
특정 위치 또는 범위 요소 삭제:
v.erase(v.begin() + 1); // 인덱스 1의 요소 제거 v.erase(v.begin() + 1, v.begin() + 3); // 인덱스 1부터 3 전까지의 요소 제거
🔍 요소 접근 및 수정
-
인덱스 접근:
int value = v[0]; // 인덱스 0의 요소 접근 (경계 검사 없음) int safe_value = v.at(0); // 인덱스 0의 요소 접근 (경계 검사 있음)
-
요소 값 변경:
v[0] = 100; // 인덱스 0의 요소 값을 100으로 변경 v.at(0) = 100; // 인덱스 0의 요소 값을 100으로 변경
-
첫 번째/마지막 요소 접근:
int first = v.front(); // 첫 번째 요소 int last = v.back(); // 마지막 요소
3. ⚡ vector의 내부 작동 및 성능
📊 크기(size)와 용량(capacity)
- size(): 벡터에 현재 존재하는 요소의 개수
- capacity(): 벡터에 현재 할당된 메모리 공간에 저장할 수 있는 최대 요소 개수
- 차이점:
size
는 실제 요소 수이고capacity
는 할당된 메모리 크기 (일반적으로capacity >= size
)
🔄 동적 재할당
- 요소를 추가하다가 현재 용량이 부족해지면 자동으로 더 큰 메모리 블록을 할당하고 기존 요소를 새 위치로 복사/이동
push_back
은 평균적으로 O(1)이지만, 재할당이 발생할 때는 O(n)- 잦은 재할당을 피하기 위해
reserve()
를 사용하여 미리 충분한 용량을 확보하는 것이 좋음
🚀 성능 최적화
-
reserve() 사용:
std::vector<int> v; v.reserve(1000); // 최소 1000개의 요소를 저장할 수 있도록 용량 예약
-
범위 기반 생성자/삽입 사용:
std::vector<int> v(first, last); // 범위 기반 생성자 v.insert(v.end(), first, last); // 범위 기반 삽입
4. 🛠️ vector의 주요 멤버 함수
함수 | 설명 | 시간 복잡도 |
---|---|---|
empty() |
벡터가 비어있는지 확인 | O(1) |
size() |
벡터의 현재 크기 반환 | O(1) |
capacity() |
벡터의 현재 용량 반환 | O(1) |
reserve(n) |
최소 n개의 요소를 저장할 수 있도록 용량 예약 | O(n) |
resize(n) |
벡터의 크기를 n으로 변경 | O(n) |
clear() |
모든 요소를 제거하고 벡터를 비움 | O(n) |
shrink_to_fit() |
벡터의 용량을 현재 크기에 맞게 줄임 | O(n) |
swap(other_vector) |
다른 벡터와 내용을 맞바꿈 | O(1) |
data() |
벡터 내부 배열에 대한 포인터 반환 | O(1) |
max_size() |
벡터가 보유할 수 있는 최대 요소 수 반환 | O(1) |
5. 🔄 이터레이터(iterator)
이터레이터는 벡터 요소의 메모리 주소를 가리키는 포인터와 유사한 객체로, 벡터의 요소를 순회하는 데 사용됩니다.
📝 이터레이터 사용법
-
이터레이터 선언:
std::vector<int>::iterator iter;
-
이터레이터 초기화:
iter = v.begin(); // 첫 번째 요소를 가리키는 이터레이터 iter = v.end() - 1; // 마지막 요소를 가리키는 이터레이터
-
이터레이터를 사용한 순회:
for (iter = v.begin(); iter != v.end(); ++iter) { std::cout << *iter << " "; }
-
범위 기반 for 루프:
for (const auto &element : v) { std::cout << element << " "; }
-
상수 이터레이터:
for (auto iter = v.cbegin(); iter != v.cend(); ++iter) { std::cout << *iter << " "; }
6. ⚠️ 주의사항 및 팁
🚨 접근 오류
[]
연산자는 경계 검사를 하지 않으므로, 존재하지 않는 요소에 접근 시 미정의 동작을 초래할 수 있음- 안전한 접근을 위해
at()
함수 사용 권장
🔄 크기 변경 중 순회
- 범위 기반 for 루프나 일반 for 루프에서
size()
를 사용하면서 벡터의 크기를 변경하면 문제가 발생할 수 있음 - 순회 중 요소 추가/삭제가 필요하다면 주의해야 함 (예: 루프 진입 전 size 저장 또는 이터레이터 사용 시 유효성 관리)
🧹 메모리 해제
clear()
는 size만 0으로 만들 뿐, capacity는 유지될 수 있음shrink_to_fit()
은 메모리 해제를 요청하지만 표준에 의해 보장되지 않음- 메모리를 확실히 해제하는 가장 확실한 방법은 빈 벡터와
swap
하는 것:std::vector<int> v; // ... 벡터 사용 ... std::vector<int>().swap(v); // 메모리 해제
⚠️ 이터레이터 무효화
- 벡터에 요소가 추가되거나 삭제될 때, 특히 재할당이 발생하면 기존 이터레이터, 포인터, 참조가 무효화될 수 있음
push_back
은 재할당이 발생하지 않는 한 기존 끝 이터레이터만 무효화하지만, 재할당 시 모든 이터레이터/포인터/참조가 무효화됨insert
나erase
는 해당 위치 이후의 모든 이터레이터/포인터/참조를 무효화할 수 있음
7. 💡 코드 구현 예시
📝 기본적인 vector 사용 예시
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
// 벡터 선언 및 초기화
std::vector<int> numbers = {5, 2, 8, 1, 9, 3};
// 요소 추가
numbers.push_back(7);
// 요소 접근 및 수정
std::cout << "첫 번째 요소: " << numbers.front() << std::endl;
std::cout << "마지막 요소: " << numbers.back() << std::endl;
// 정렬
std::sort(numbers.begin(), numbers.end());
// 범위 기반 for 루프로 순회
std::cout << "정렬된 벡터: ";
for (const auto &num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// 벡터 크기 및 용량 출력
std::cout << "벡터 크기: " << numbers.size() << std::endl;
std::cout << "벡터 용량: " << numbers.capacity() << std::endl;
return 0;
}
🔄 이터레이터를 사용한 벡터 조작 예시
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 이터레이터를 사용한 순회
std::cout << "이터레이터로 순회: ";
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 이터레이터를 사용한 요소 삽입
auto it = v.begin() + 2;
v.insert(it, 10);
// 이터레이터를 사용한 요소 삭제
it = v.begin() + 3;
v.erase(it);
// 결과 출력
std::cout << "수정된 벡터: ";
for (const auto &element : v) {
std::cout << element << " ";
}
std::cout << std::endl;
return 0;
}
8. 🎯 면접 대비 핵심 개념
📝 기본 개념 및 특징
Q1. std::vector
란 무엇인가요? std::array
나 일반 C++ 배열과의 차이점은 무엇인가요?
A. std::vector
는 C++ STL의 동적 배열 컨테이너입니다.
-
주요 특징:
- 동적 크기 조절 가능 (실행 중 크기 변경)
- 연속적인 메모리 공간에 요소 저장
- 자동 메모리 관리 (RAII)
- STL 알고리즘과의 완벽한 호환성
-
vs 일반 배열:
- 일반 배열: 고정 크기, 스택 할당, 수동 메모리 관리
std::vector
: 동적 크기, 힙 할당, 자동 메모리 관리
-
vs
std::array
:std::array
: 컴파일 타임에 크기 고정, 스택 할당std::vector
: 런타임에 크기 변경 가능, 힙 할당
💡 주요 멤버 함수 및 사용법
Q2. push_back()
과 emplace_back()
의 차이점은 무엇인가요?
A. 두 함수 모두 벡터의 끝에 요소를 추가하지만, 동작 방식이 다릅니다.
-
push_back()
:- 객체를 생성한 후 벡터에 복사/이동
- 임시 객체 생성 → 복사/이동 생성자 호출 → 임시 객체 파괴
vec.push_back(std::string("Hello")); // 임시 객체 생성 후 이동
-
emplace_back()
:- 벡터 내부에서 직접 객체 생성
- 생성자 인자만 전달하여 내부에서 직접 생성
- 불필요한 임시 객체 생성/파괴와 복사/이동 연산 제거
vec.emplace_back("Hello"); // 내부에서 직접 생성
Q3. vector::operator[]
와 vector::at()
의 차이점은 무엇인가요?
A. 두 함수는 요소 접근 방식이 다릅니다.
-
operator[]
:- 범위 검사 없음
- 더 빠른 접근 속도
- 잘못된 인덱스 접근 시 정의되지 않은 동작
- 성능이 중요한 내부 루프에서 사용 권장
-
at()
:- 범위 검사 수행
- 잘못된 인덱스 접근 시
std::out_of_range
예외 발생 - 안전성이 중요한 경우 사용 권장
🔧 메모리 관리 및 성능
Q4. size()
와 capacity()
의 차이점은 무엇인가요?
A. 두 값은 벡터의 메모리 관리와 관련이 있습니다.
-
size()
:- 현재 저장된 요소의 실제 개수
push_back()
,pop_back()
등으로 변경- 항상
capacity()
이하
-
capacity()
:- 재할당 없이 저장 가능한 최대 요소 개수
- 재할당 시 일반적으로 2배로 증가
reserve()
로 미리 설정 가능
Q5. 벡터의 메모리를 해제하는 방법은 무엇인가요?
A. 여러 방법이 있으며, 각각의 특징이 있습니다.
-
clear()
:- 요소만 제거하고
size
를 0으로 설정 capacity
는 유지
- 요소만 제거하고
-
shrink_to_fit()
:capacity
를size
에 맞게 줄이도록 요청- 표준상 보장되지 않음(구현에 따라 요청이 충족되지 않을 수 있음)
-
빈 벡터와 swap:
std::vector<T>().swap(vec); // 가장 확실한 메모리 해제 방법
⚡ 성능 및 최적화
Q6. reserve()
와 resize()
의 차이점은 무엇인가요?
A. 두 함수는 다른 목적으로 사용됩니다.
-
reserve(n)
:capacity
만 변경size
는 변경되지 않음- 재할당 비용 감소를 위한 최적화
-
resize(n)
:size
를 변경- 새로운 요소는 기본값으로 초기화
- 필요시
capacity
도 증가
Q7. 벡터를 순회하는 방법과 주의사항은 무엇인가요?
A. 여러 방법이 있으며, 각각의 장단점이 있습니다.
-
인덱스 기반 for 루프:
for (size_t i = 0; i < vec.size(); ++i) { // vec[i] 사용 }
-
범위 기반 for 루프:
for (const auto& element : vec) { // element 사용 }
-
이터레이터 사용:
for (auto it = vec.begin(); it != vec.end(); ++it) { // *it 사용 }
주의사항:
- 순회 중 벡터 크기 변경 시 이터레이터 무효화
- 범위 기반 for 루프에서 크기 변경 시 정의되지 않은 동작
- 삭제 시
erase()
가 반환하는 다음 이터레이터 사용
📊 컨테이너 선택 가이드
Q8. std::vector
와 std::list
중 어떤 것을 선택해야 하나요?
A. 사용 사례에 따라 선택이 달라집니다.
-
std::vector
선택 시:- 빈번한 랜덤 접근이 필요한 경우
- 순차적 순회가 주된 작업인 경우
- 끝에서 주로 추가/삭제하는 경우
- 메모리 효율성이 중요한 경우
-
std::list
선택 시:- 중간 삽입/삭제가 매우 빈번한 경우
- 랜덤 접근이 거의 없는 경우
- 요소 순서 유지가 덜 중요한 경우
🎯 추가 면접 팁
-
2차원 벡터:
std::vector<std::vector<int>> matrix;
-
함수 인자 전달:
- 값 전달:
void func(std::vector<int> vec)
- 참조 전달:
void func(const std::vector<int>& vec)
- 이동 전달:
void func(std::vector<int>&& vec)
- 값 전달:
-
자주 사용되는 멤버 함수:
empty()
,front()
,back()
swap()
,assign()
,data()
begin()
,end()
,rbegin()
,rend()