STL 컨테이너
2025. 3. 28. 08:54ㆍProgramming Languages/C++
8.2 STL 컨테이너
STL 컨테이너는 데이터를 저장하고 관리하기 위한 다양한 자료구조를 제공합니다.
8.2.1 시퀀스 컨테이너(Sequence Containers)
요소가 선형 순서로 저장되는 컨테이너입니다.
vector
가장 많이 사용되는 컨테이너로, 동적 배열을 구현합니다:
#include <iostream>
#include <vector>
int main() {
// 벡터 생성 및 초기화
std::vector<int> vec1; // 빈 벡터
std::vector<int> vec2(5, 10); // 5개의 10으로 초기화된 벡터
std::vector<int> vec3 = {1, 2, 3, 4, 5}; // 초기화 리스트 사용 (C++11)
// 요소 추가
vec1.push_back(10);
vec1.push_back(20);
vec1.push_back(30);
// 크기 및 용량 확인
std::cout << "vec1 크기: " << vec1.size() << std::endl;
std::cout << "vec1 용량: " << vec1.capacity() << std::endl;
// 요소 접근
std::cout << "vec1 첫 번째 요소: " << vec1[0] << std::endl;
std::cout << "vec1 마지막 요소: " << vec1.back() << std::endl;
// 범위 검사가 있는 요소 접근
try {
std::cout << "vec1 at(1): " << vec1.at(1) << std::endl;
} catch (const std::out_of_range& e) {
std::cerr << "예외 발생: " << e.what() << std::endl;
}
// 반복자 사용
std::cout << "vec3 모든 요소: ";
for (auto it = vec3.begin(); it != vec3.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 범위 기반 for 루프 (C++11)
std::cout << "vec3 모든 요소 (범위 기반 for): ";
for (const auto& elem : vec3) {
std::cout << elem << " ";
}
std::cout << std::endl;
// 요소 삽입
vec3.insert(vec3.begin() + 2, 100); // 세 번째 위치에 100 삽입
// 요소 삭제
vec3.erase(vec3.begin()); // 첫 번째 요소 삭제
// 벡터 비우기
vec2.clear();
std::cout << "vec2 비우기 후 크기: " << vec2.size() << std::endl;
// 벡터 크기 변경
vec1.resize(5, 0); // 크기를 5로 변경, 새 요소는 0으로 초기화
std::cout << "vec1 크기 변경 후: ";
for (const auto& elem : vec1) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
list
이중 연결 리스트를 구현한 컨테이너입니다:
#include <iostream>
#include <list>
int main() {
// 리스트 생성 및 초기화
std::list<int> list1; // 빈 리스트
std::list<int> list2(5, 10); // 5개의 10으로 초기화된 리스트
std::list<int> list3 = {1, 2, 3, 4, 5}; // 초기화 리스트 사용
// 요소 추가
list1.push_back(30); // 뒤에 추가
list1.push_front(10); // 앞에 추가
list1.push_back(40);
list1.push_front(5);
// 리스트 크기
std::cout << "list1 크기: " << list1.size() << std::endl;
// 요소 접근
std::cout << "list1 첫 번째 요소: " << list1.front() << std::endl;
std::cout << "list1 마지막 요소: " << list1.back() << std::endl;
// 반복자 사용
std::cout << "list1 모든 요소: ";
for (auto it = list1.begin(); it != list1.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 요소 삭제
list1.pop_front(); // 첫 번째 요소 삭제
list1.pop_back(); // 마지막 요소 삭제
// 특정 값 삭제
list1.remove(10); // 모든 10 삭제
// 조건에 따른 삭제
list2.remove_if([](int n) { return n % 2 == 0; }); // 짝수 삭제
// 리스트 정렬
list1.sort();
// 중복 요소 제거
list1.unique();
// 리스트 병합 (두 리스트 모두 정렬되어 있어야 함)
list1.merge(list3);
std::cout << "병합 후 list1: ";
for (const auto& elem : list1) {
std::cout << elem << " ";
}
std::cout << std::endl;
// 리스트 뒤집기
list1.reverse();
std::cout << "뒤집은 후 list1: ";
for (const auto& elem : list1) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
deque (Double-ended Queue)
양쪽 끝에서 빠른 삽입과 삭제가 가능한 컨테이너입니다:
#include <iostream>
#include <deque>
int main() {
// 덱 생성 및 초기화
std::deque<int> deq1; // 빈 덱
std::deque<int> deq2(5, 10); // 5개의 10으로 초기화된 덱
std::deque<int> deq3 = {1, 2, 3, 4, 5}; // 초기화 리스트 사용
// 요소 추가
deq1.push_back(30); // 뒤에 추가
deq1.push_front(10); // 앞에 추가
deq1.push_back(40);
deq1.push_front(5);
// 덱 크기
std::cout << "deq1 크기: " << deq1.size() << std::endl;
// 요소 접근
std::cout << "deq1 첫 번째 요소: " << deq1.front() << std::endl;
std::cout << "deq1 마지막 요소: " << deq1.back() << std::endl;
std::cout << "deq1[2]: " << deq1[2] << std::endl; // 배열처럼 접근 가능
// 반복자 사용
std::cout << "deq1 모든 요소: ";
for (auto it = deq1.begin(); it != deq1.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 요소 삭제
deq1.pop_front(); // 첫 번째 요소 삭제
deq1.pop_back(); // 마지막 요소 삭제
// 중간에 요소 삽입
deq1.insert(deq1.begin() + 1, 100);
// 중간 요소 삭제
deq1.erase(deq1.begin() + 1);
std::cout << "변경 후 deq1: ";
for (const auto& elem : deq1) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
array (C++11)
고정 크기 배열을 구현한 컨테이너입니다:
#include <iostream>
#include <array>
int main() {
// 배열 생성 및 초기화
std::array<int, 5> arr1; // 초기화되지 않은 배열
std::array<int, 5> arr2 = {1, 2, 3, 4, 5}; // 초기화 리스트 사용
std::array<int, 5> arr3{10, 20, 30, 40, 50}; // 균일 초기화 (C++11)
// 배열 크기
std::cout << "arr2 크기: " << arr2.size() << std::endl;
// 요소 접근
std::cout << "arr2 첫 번째 요소: " << arr2.front() << std::endl;
std::cout << "arr2 마지막 요소: " << arr2.back() << std::endl;
std::cout << "arr2[2]: " << arr2[2] << std::endl;
// at() 메서드로 범위 검사와 함께 접근
try {
std::cout << "arr2.at(10): " << arr2.at(10) << std::endl; // 범위를 벗어남
} catch (const std::out_of_range& e) {
std::cerr << "예외 발생: " << e.what() << std::endl;
}
// 배열 데이터에 대한 포인터 얻기
int* ptr = arr2.data();
std::cout << "ptr[4]: " << ptr[4] << std::endl;
// 반복자 사용
std::cout << "arr3 모든 요소: ";
for (auto it = arr3.begin(); it != arr3.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// fill 메서드로 모든 요소 설정
arr1.fill(7);
std::cout << "arr1 (fill 후): ";
for (const auto& elem : arr1) {
std::cout << elem << " ";
}
std::cout << std::endl;
// 두 배열 교환
arr1.swap(arr3);
std::cout << "swap 후 arr1: ";
for (const auto& elem : arr1) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
8.2.2 연관 컨테이너(Associative Containers)
키-값 쌍을 저장하거나 빠른 검색을 위해 사용되는 컨테이너입니다.
set과 multiset
정렬된 고유 키(set) 또는 중복 키를 허용하는(multiset) 이진 검색 트리 기반 컨테이너:
#include <iostream>
#include <set>
#include <string>
int main() {
// set 생성 및 초기화
std::set<int> set1; // 빈 집합
std::set<int> set2 = {5, 3, 1, 4, 2}; // 초기화 리스트 사용 (자동으로 정렬됨)
// 요소 삽입
set1.insert(30);
set1.insert(10);
set1.insert(20);
set1.insert(10); // 중복 요소는 무시됨
// 집합 크기
std::cout << "set1 크기: " << set1.size() << std::endl;
// 요소 검색
auto it = set1.find(20);
if (it != set1.end()) {
std::cout << "20을 찾았습니다." << std::endl;
}
// 요소 존재 여부 확인
if (set1.count(40) > 0) {
std::cout << "40이 존재합니다." << std::endl;
} else {
std::cout << "40이 존재하지 않습니다." << std::endl;
}
// 반복자 사용
std::cout << "set1 모든 요소 (오름차순): ";
for (auto it = set1.begin(); it != set1.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 역방향 반복자 사용
std::cout << "set1 모든 요소 (내림차순): ";
for (auto it = set1.rbegin(); it != set1.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 요소 삭제
set1.erase(10);
// 범위 기반 삭제
auto first = set2.find(2);
auto last = set2.find(5);
set2.erase(first, last); // [first, last) 범위 삭제
std::cout << "삭제 후 set2: ";
for (const auto& elem : set2) {
std::cout << elem << " ";
}
std::cout << std::endl;
// multiset 예제 (중복 키 허용)
std::multiset<int> mset = {5, 3, 5, 1, 3, 2};
std::cout << "multiset 모든 요소: ";
for (const auto& elem : mset) {
std::cout << elem << " ";
}
std::cout << std::endl;
// 특정 값의 발생 횟수
std::cout << "3의 발생 횟수: " << mset.count(3) << std::endl;
return 0;
}
map과 multimap
키-값 쌍을 저장하는 이진 검색 트리 기반 컨테이너:
#include <iostream>
#include <map>
#include <string>
int main() {
// map 생성 및 초기화
std::map<std::string, int> map1; // 빈 맵
std::map<std::string, int> map2 = {{"apple", 5}, {"banana", 3}, {"orange", 7}}; // 초기화 리스트
// 요소 삽입
map1["red"] = 10; // [] 연산자로 삽입 또는 수정
map1["green"] = 20;
map1["blue"] = 30;
// insert 메서드로 삽입
map1.insert(std::make_pair("yellow", 40));
map1.insert({"purple", 50}); // C++11
// 중복 키 삽입 시도
auto result = map1.insert({"red", 100}); // 이미 존재하므로 삽입되지 않음
if (!result.second) {
std::cout << "키 'red'는 이미 존재합니다. 값: " << map1["red"] << std::endl;
}
// 맵 크기
std::cout << "map1 크기: " << map1.size() << std::endl;
// 요소 접근
std::cout << "map1['green']: " << map1["green"] << std::endl;
// 안전한 요소 접근
if (map1.find("black") != map1.end()) {
std::cout << "map1['black']: " << map1["black"] << std::endl;
} else {
std::cout << "키 'black'은 존재하지 않습니다." << std::endl;
}
// at() 메서드로 범위 검사와 함께 접근
try {
std::cout << "map1.at('blue'): " << map1.at("blue") << std::endl;
std::cout << "map1.at('black'): " << map1.at("black") << std::endl; // 예외 발생
} catch (const std::out_of_range& e) {
std::cerr << "예외 발생: " << e.what() << std::endl;
}
// 모든 요소 반복
std::cout << "map1의 모든 요소:" << std::endl;
for (const auto& pair : map1) {
std::cout << " " << pair.first << ": " << pair.second << std::endl;
}
// 요소 삭제
map1.erase("red");
// 범위 기반 삭제
auto first = map2.find("apple");
auto last = map2.find("orange");
map2.erase(first, last); // [first, last) 범위 삭제
std::cout << "삭제 후 map2:" << std::endl;
for (const auto& pair : map2) {
std::cout << " " << pair.first << ": " << pair.second << std::endl;
}
// multimap 예제 (중복 키 허용)
std::multimap<std::string, int> mmap;
mmap.insert({"apple", 5});
mmap.insert({"orange", 3});
mmap.insert({"apple", 10}); // 중복 키 허용
mmap.insert({"apple", 7});
std::cout << "multimap의 모든 요소:" << std::endl;
for (const auto& pair : mmap) {
std::cout << " " << pair.first << ": " << pair.second << std::endl;
}
// 특정 키의 모든 값 찾기
std::cout << "키 'apple'의 모든 값:" << std::endl;
auto range = mmap.equal_range("apple");
for (auto it = range.first; it != range.second; ++it) {
std::cout << " " << it->second << std::endl;
}
return 0;
}
8.2.3 비정렬 연관 컨테이너(Unordered Associative Containers) (C++11)
해시 테이블 기반의 연관 컨테이너로, 평균적으로 더 빠른 검색이 가능합니다.
unordered_set과 unordered_multiset
#include <iostream>
#include <unordered_set>
#include <string>
int main() {
// unordered_set 생성 및 초기화
std::unordered_set<int> uset1; // 빈 집합
std::unordered_set<int> uset2 = {5, 3, 1, 4, 2}; // 초기화 리스트 사용
// 요소 삽입
uset1.insert(30);
uset1.insert(10);
uset1.insert(20);
uset1.insert(10); // 중복 요소는 무시됨
// 집합 크기
std::cout << "uset1 크기: " << uset1.size() << std::endl;
// 버킷 수와 로드 팩터
std::cout << "uset1 버킷 수: " << uset1.bucket_count() << std::endl;
std::cout << "uset1 로드 팩터: " << uset1.load_factor() << std::endl;
// 요소 검색
auto it = uset1.find(20);
if (it != uset1.end()) {
std::cout << "20을 찾았습니다." << std::endl;
}
// 요소 존재 여부 확인
if (uset1.count(40) > 0) {
std::cout << "40이 존재합니다." << std::endl;
} else {
std::cout << "40이 존재하지 않습니다." << std::endl;
}
// 모든 요소 반복 (set과 달리 순서가 보장되지 않음)
std::cout << "uset1의 모든 요소: ";
for (const auto& elem : uset1) {
std::cout << elem << " ";
}
std::cout << std::endl;
// 요소 삭제
uset1.erase(10);
// 커스텀 타입에 대한 해시 함수 및 비교 함수 정의
struct Person {
std::string name;
int age;
// 동등 비교 연산자
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};
// 커스텀 해시 함수
struct PersonHash {
std::size_t operator()(const Person& p) const {
return std::hash<std::string>()(p.name) ^ std::hash<int>()(p.age);
}
};
// 커스텀 타입의 unordered_set
std::unordered_set<Person, PersonHash> personSet;
personSet.insert({"Alice", 25});
personSet.insert({"Bob", 30});
// unordered_multiset 예제 (중복 키 허용)
std::unordered_multiset<int> umset = {5, 3, 5, 1, 3, 2};
std::cout << "unordered_multiset의 모든 요소: ";
for (const auto& elem : umset) {
std::cout << elem << " ";
}
std::cout << std::endl;
// 특정 값의 발생 횟수
std::cout << "3의 발생 횟수: " << umset.count(3) << std::endl;
return 0;
}
unordered_map과 unordered_multimap
#include <iostream>
#include <unordered_map>
#include <string>
int main() {
// unordered_map 생성 및 초기화
std::unordered_map<std::string, int> umap1; // 빈 맵
std::unordered_map<std::string, int> umap2 = {{"apple", 5}, {"banana", 3}, {"orange", 7}}; // 초기화 리스트
// 요소 삽입
umap1["red"] = 10; // [] 연산자로 삽입 또는 수정
umap1["green"] = 20;
umap1["blue"] = 30;
// insert 메서드로 삽입
umap1.insert(std::make_pair("yellow", 40));
umap1.insert({"purple", 50}); // C++11
// 맵 크기
std::cout << "umap1 크기: " << umap1.size() << std::endl;
// 해시 테이블 성능 지표
std::cout << "umap1 버킷 수: " << umap1.bucket_count() << std::endl;
std::cout << "umap1 로드 팩터: " << umap1.load_factor() << std::endl;
// 요소 접근
std::cout << "umap1['green']: " << umap1["green"] << std::endl;
// at() 메서드로 범위 검사와 함께 접근
try {
std::cout << "umap1.at('blue'): " << umap1.at("blue") << std::endl;
std::cout << "umap1.at('black'): " << umap1.at("black") << std::endl; // 예외 발생
} catch (const std::out_of_range& e) {
std::cerr << "예외 발생: " << e.what() << std::endl;
}
// 모든 요소 반복 (순서가 보장되지 않음)
std::cout << "umap1의 모든 요소:" << std::endl;
for (const auto& pair : umap1) {
std::cout << " " << pair.first << ": " << pair.second << std::endl;
}
// 요소 삭제
umap1.erase("red");
// 해시 테이블 리해싱
umap1.rehash(20); // 버킷 수를 20으로 설정
std::cout << "리해싱 후 umap1 버킷 수: " << umap1.bucket_count() << std::endl;
// 최대 로드 팩터 설정
umap1.max_load_factor(0.7f);
std::cout << "새 최대 로드 팩터: " << umap1.max_load_factor() << std::endl;
// unordered_multimap 예제 (중복 키 허용)
std::unordered_multimap<std::string, int> ummap;
ummap.insert({"apple", 5});
ummap.insert({"orange", 3});
ummap.insert({"apple", 10}); // 중복 키 허용
ummap.insert({"apple", 7});
std::cout << "unordered_multimap의 모든 요소:" << std::endl;
for (const auto& pair : ummap) {
std::cout << " " << pair.first << ": " << pair.second << std::endl;
}
// 특정 키의 모든 값 찾기
std::cout << "키 'apple'의 모든 값:" << std::endl;
auto range = ummap.equal_range("apple");
for (auto it = range.first; it != range.second; ++it) {
std::cout << " " << it->second << std::endl;
}
return 0;
}
8.2.4 컨테이너 어댑터(Container Adapters)
기존 컨테이너를 기반으로 특정 인터페이스를 제공하는 어댑터입니다.
stack
LIFO(Last In, First Out) 동작을 제공하는 컨테이너 어댑터:
#include <iostream>
#include <stack>
#include <vector>
#include <list>
int main() {
// 기본 스택 (deque 기반)
std::stack<int> stack1;
// 벡터 기반 스택
std::stack<int, std::vector<int>> stack2;
// 리스트 기반 스택
std::stack<int, std::list<int>> stack3;
// 요소 추가
stack1.push(10);
stack1.push(20);
stack1.push(30);
// 스택 크기
std::cout << "stack1 크기: " << stack1.size() << std::endl;
// 최상위 요소 접근
std::cout << "stack1 최상위 요소: " << stack1.top() << std::endl;
// 요소 삭제
stack1.pop();
std::cout << "pop 후 stack1 최상위 요소: " << stack1.top() << std::endl;
// 스택이 비어 있는지 확인
if (stack1.empty()) {
std::cout << "stack1이 비어 있습니다." << std::endl;
} else {
std::cout << "stack1이 비어 있지 않습니다." << std::endl;
}
// 스택 모든 요소 출력 (스택을 비우면서)
std::cout << "stack1의 모든 요소 (위에서 아래로): ";
while (!stack1.empty()) {
std::cout << stack1.top() << " ";
stack1.pop();
}
std::cout << std::endl;
return 0;
}
queue
FIFO(First In, First Out) 동작을 제공하는 컨테이너 어댑터:
#include <iostream>
#include <queue>
#include <deque>
#include <list>
int main() {
// 기본 큐 (deque 기반)
std::queue<int> queue1;
// list 기반 큐
std::queue<int, std::list<int>> queue2;
// 요소 추가
queue1.push(10);
queue1.push(20);
queue1.push(30);
// 큐 크기
std::cout << "queue1 크기: " << queue1.size() << std::endl;
// 첫 번째 요소 접근 (가장 먼저 들어온 요소)
std::cout << "queue1 첫 번째 요소: " << queue1.front() << std::endl;
// 마지막 요소 접근 (가장 나중에 들어온 요소)
std::cout << "queue1 마지막 요소: " << queue1.back() << std::endl;
// 요소 삭제
queue1.pop();
std::cout << "pop 후 queue1 첫 번째 요소: " << queue1.front() << std::endl;
// 큐가 비어 있는지 확인
if (queue1.empty()) {
std::cout << "queue1이 비어 있습니다." << std::endl;
} else {
std::cout << "queue1이 비어 있지 않습니다." << std::endl;
}
// 큐의 모든 요소 출력 (큐를 비우면서)
std::cout << "queue1의 모든 요소 (앞에서 뒤로): ";
while (!queue1.empty()) {
std::cout << queue1.front() << " ";
queue1.pop();
}
std::cout << std::endl;
return 0;
}
priority_queue
가장 큰(또는 사용자 정의 비교에 따른) 요소가 항상 맨 앞에 있는 컨테이너 어댑터:
#include <iostream>
#include <queue>
#include <vector>
#include <string>
#include <functional> // std::greater
int main() {
// 기본 우선순위 큐 (내림차순, 최대 힙)
std::priority_queue<int> pq1;
// 오름차순 우선순위 큐 (최소 힙)
std::priority_queue<int, std::vector<int>, std::greater<int>> pq2;
// 요소 추가
pq1.push(30);
pq1.push(10);
pq1.push(50);
pq1.push(20);
// 우선순위 큐 크기
std::cout << "pq1 크기: " << pq1.size() << std::endl;
// 최상위 요소 접근
std::cout << "pq1 최상위 요소: " << pq1.top() << std::endl; // 가장 큰 값 (50)
// 요소 삭제
pq1.pop();
std::cout << "pop 후 pq1 최상위 요소: " << pq1.top() << std::endl; // 다음으로 큰 값 (30)
// 최소 힙 사용
pq2.push(30);
pq2.push(10);
pq2.push(50);
pq2.push(20);
std::cout << "pq2 최상위 요소: " << pq2.top() << std::endl; // 가장 작은 값 (10)
// 우선순위 큐의 모든 요소 출력 (큐를 비우면서)
std::cout << "pq1의 모든 요소 (내림차순): ";
while (!pq1.empty()) {
std::cout << pq1.top() << " ";
pq1.pop();
}
std::cout << std::endl;
std::cout << "pq2의 모든 요소 (오름차순): ";
while (!pq2.empty()) {
std::cout << pq2.top() << " ";
pq2.pop();
}
std::cout << std::endl;
// 커스텀 타입과 비교자
struct Task {
int priority;
std::string name;
// 비교 연산자 정의
bool operator<(const Task& other) const {
// priority_queue는 기본적으로 less를 사용하므로,
// 높은 우선순위가 더 큰 것으로 처리하기 위해 부등호 방향 반대로 설정
return priority < other.priority;
}
};
std::priority_queue<Task> taskQueue;
taskQueue.push({3, "Cook dinner"});
taskQueue.push({1, "Watch TV"});
taskQueue.push({5, "Fix urgent bug"});
taskQueue.push({2, "Clean house"});
std::cout << "Tasks in priority order:" << std::endl;
while (!taskQueue.empty()) {
Task currentTask = taskQueue.top();
std::cout << " Priority " << currentTask.priority << ": " << currentTask.name << std::endl;
taskQueue.pop();
}
return 0;
}
'Programming Languages > C++' 카테고리의 다른 글
STL 알고리즘 (0) | 2025.03.28 |
---|---|
STL 반복자 (0) | 2025.03.28 |
STL (Standard Template Library) (0) | 2025.03.28 |
챕터7. 실습 문제 (0) | 2025.03.28 |
템플릿 메타프로그래밍 (0) | 2025.03.28 |