씹어먹는 C++
11장 c++ 표준 라이브러리(stl) 494p-555p
11장-3, 11장-4 556p - 612p
배열 처럼 객체들을 순차적으로 보관
vector, list, deque
원소들이 메모리 상에서 실제로 순차적으로 저장
임의의 위치의 원소를 접근 매우 빠르게 수행
복잡도(complexity)
O(1)
임의의 위치의 원소를 접근
맨 뒤에 새로운 원소를 추가하거나 제거
amortized O(1)
미리 더 많은 공간 할당 > 다쓰면 O(n)
접근
[ ] , at()
위치
begin() 첫번째 원소
end() 마지막 원소 +1칸
빈 벡터를 표현 가능
begin() == end()
추가 및 제거
맨 뒤에 원소를 추가하거나 제거
push_back()/ pop_back()
size()
벡터의 크기를 리턴하는 함수
값의 타입은 size_type 멤버 타입
example code
int main() {
std::vector<int> vec;
vec.push_back(10); // 맨 뒤에 10 추가
vec.push_back(20); // 맨 뒤에 20 추가
vec.push_back(30); // 맨 뒤에 30 추가
vec.push_back(40); // 맨 뒤에 40 추가
for (std::vector<int>::size_type i = 0; i < vec.size(); i++) {
std::cout << "vec 의 " << i + 1 << " 번째 원소 :: " << vec[i] << std::endl;
}
}
vec 의 1 번째 원소 :: 10
vec 의 2 번째 원소 :: 20
vec 의 3 번째 원소 :: 30
vec 의 4 번째 원소 :: 40
RandomAccessIterator
반복자
컨테이너에 iterator 멤버 타입
* 연산자 오버로딩
itr이 가르키는 원소 확인
+ 연산자 오버로딩
itr에서 해당 크기만큼 덜어진 원소 가르킴
insert erase 사용 가능
※ 오류 주의
erase로 itr를 지우면 해당 값은 유효한 값이 아님 반드시 새로운 값을 넣어줘야 itr을 다시 사용 가능
디버깅시 주의
const_iterator
const 포인터처럼 가리키고 있는 원소의 값을 바꿀 수 없음
역반복자 (reverse iterator)
벡터 뒤에서 부터 앞으로 거꾸로 간다는 특징
(const_reverse_iterator 타입도 있음)
rbegin() / rend()
std::vector<int>::reverse_iterator r_iter = vec.rbegin();
example code
#include <iostream>
#include <vector>
template <typename T>
void print_vector(std::vector<T>& vec) {
// 전체 벡터를 출력하기
// typename 필요 > iterator 가 std::vector<T>
의 의존 타입
for (typename std::vector<T>::iterator itr = vec.begin(); itr != vec.end();
++itr) {
std::cout << *itr << std::endl;
}
}
int main() {
std::vector<int> vec;
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
vec.push_back(40);
std::cout << "처음 벡터 상태" << std::endl;
print_vector(vec);
std::cout << "----------------------------" << std::endl;
// vec[2] 앞에 15 추가
vec.insert(vec.begin() + 2, 15);
print_vector(vec);
std::cout << "----------------------------" << std::endl;
// vec[3] 제거
vec.erase(vec.begin() + 3);
print_vector(vec);
}
처음 벡터 상태
10
20
30
40
----------------------------
10
20
15
30
40
----------------------------
10
20
15
40
for문 패턴을 매우 간단하게 나타낼 수 있는 방식을 제공
for (/* 원소를 받는 변수 정의 */ : /* 컨테이너 */) { }
example code
template <typename T>
void print_vector(std::vector<T>& vec) {
// 전체 벡터를 출력하기
for (typename std::vector<T>::iterator itr = vec.begin(); itr != vec.end();
++itr) {
std::cout << *itr << std::endl;
}
}
template <typename T>
void print_vector_range_based(std::vector<T>& vec) {
// 전체 벡터를 출력하기
// 범위 기반 for 문 레퍼런스 사용
for (const auto& elem : vec) {
std::cout << elem << std::endl;
}
}
int main() {
std::vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
std::cout << "print_vector" << std::endl;
print_vector(vec);
std::cout << "print_vector_range_based" << std::endl;
print_vector_range_based(vec);
return 0;
}
print_vector
1
2
3
4
print_vector_range_based
1
2
3
4
양방향 연결 구조를 가진 자료형
example code
#include <iostream>
#include <list>
int main() {
std::list<int> lst;
lst.push_back(10);
lst.push_back(20);
lst.push_back(30);
lst.push_back(40);
for (std::list<int>::iterator itr = lst.begin(); itr != lst.end(); ++itr) {
std::cout << *itr << std::endl;
}
}
10
20
30
40
접근
임의의 위치의 원소에 바로 접근 불가능
임의의 위치의 원소 접근을 위해 하나씩 링크를 따라가야함
반복자 (BidirectionalIterator)
++, -- 연산자는 가능 < 한칸씩 움직인 위치 접근 가능
+ 3, - 5 연산자 불가능 < 임의의 위치 원소 접근 못함
앞뒤의 insert erase 사용
원소를 추가하거나 제거하는 작업이 O(1) 으로 매우 빠르게 수행됨
원하는 위치 앞과 뒤에 있는 링크 값만 바꿈
원소를 지워도 반복자가 무효화 안음
itr를 계속 사용가능
덱
실행 속도를 위해 메모리를 (많이) 희생하는 컨테이너
#include <deque>
#include <iostream>
template <typename T>
void print_deque(std::deque<T>& dq) {
// 전체 덱을 출력하기
std::cout << "[ ";
for (const auto& elem : dq) {
std::cout << elem << " ";
}
std::cout << " ] " << std::endl;
}
int main() {
std::deque<int> dq;
dq.push_back(10);
dq.push_back(20);
dq.push_front(30);
dq.push_front(40);
std::cout << "초기 dq 상태" << std::endl;
print_deque(dq);
std::cout << "맨 앞의 원소 제거" << std::endl;
dq.pop_front();
print_deque(dq);
}
초기 dq 상태
[ 40 30 10 20 ]
맨 앞의 원소 제거
[ 30 10 20 ]
키(key) - 값(value) 구조
특정한 키를 넣으면 이에 대응되는 값 리턴
키와 값 모두 임의의 타입의 객체 가능
키의 존재 유무 / 대응 되는 값 질의 두가지 타입으로 나뉨
set, mulitset / map, multimap
set
데이터의 존재 유무 만 궁금할 경우
multiset
중복 데이터를 허락할 경우
insert, erase, find 모두 O(log N). 최악도 O(log N))
map
데이터에 대응되는 데이터를 저장하고 싶은 경우
multimap
중복 키를 허락할 경우
insert, erase, find 모두 O(log N). 최악도 O(log N))
unordered_set, unordered_map
속도가 매우매우 중요해서 최적화를 해야하는 경우
insert, erase, find 모두 O(1)
최악은 O(N)
그러므로 해시함수와 상자 개수 설정 유의
트리 구조로 되어 있음 find 최적화
추가
insert()
위치정보 없이 추가
추가 시 정렬된 상태를 유지 O(logN)
원소
위치 정보가 아닌 유무가 중요한 정보
반복자 BidirectionalIterator
순차적 접근만 가능
find
원소의 존재 유무 확인
정렬된 상태 유지하므로 O(logN)
존재 시 반복자 리턴
존재 하지 않을시 s.end() 리턴- 중복
중복된 원소 추가 불가능
있는데 추가시 무시함
-원하는 타입 셋 사용
크기 비교가 있어야 사용가능 < 정렬을 사용하기 때문
즉 operater <에 대한 정의로 오버로딩 필요
operator< 조건이 모두 만족해야 접근 가능
또는 함수 객체 사용
const 사용 하여 전달
: 셋 내부적으로 정렬 시에 상수 반복자를 사용
example code
#include <iostream>
#include <set>
template <typename T>
void print_set(std::set<T>& s) {
// 셋의 모든 원소들을 출력하기
std::cout << "[ ";
for (typename std::set<T>::iterator itr = s.begin(); itr != s.end(); ++itr) {
std::cout << *itr << " ";
}
std::cout << " ] " << std::endl;
}
int main() {
std::set<int> s;
s.insert(10);
s.insert(50);
s.insert(20);
s.insert(40);
s.insert(30);
std::cout << "순서대로 정렬되서 나온다" << std::endl;
print_set(s);
std::cout << "20 이 s 의 원소인가요? :: ";
auto itr = s.find(20);
if (itr != s.end()) {
std::cout << "Yes" << std::endl;
} else {
std::cout << "No" << std::endl;
}
std::cout << "25 가 s 의 원소인가요? :: ";
itr = s.find(25);
if (itr != s.end()) {
std::cout << "Yes" << std::endl;
} else {
std::cout << "No" << std::endl;
}
}
순서대로 정렬되서 나온다
[ 10 20 30 40 50 ]
20 이 s 의 원소인가요? :: Yes
25 가 s 의 원소인가요? :: No
키에 대응되는 값(value) 까지도 같이 보관
키와 값
템플릿 인자 2 개
첫번째는 키의 타입, 두 번째는 값의 타입
맵에 원소를 넣기 위해서는 반드시 std::pair 객체를 전달
make_pair() 사용 < pair 역시 stl (utility 헤더 사용)
operator[]도 사용가능
반복자
맵 반복자가 std::pair 객체
tr->first 키, itr->second 원소
탐색
키를 통해 원소 확인
find함수 사용
itr = m.find(key) itr->second
find 없는키 참조시 end()리턴
또는
[]로 맵에 없는 키를 참조
자동으로 값의 디폴트 생성자를 호출해서 원소를 추가
중복된 원소 추가 불가능
있는데 추가시 무시함
example code
#include <iostream>
#include <map>
#include <string>
template <typename K, typename V>
void print_map(std::map<K, V>& m) {
// 맵의 모든 원소들을 출력하기
for (auto itr = m.begin(); itr != m.end(); ++itr) {
std::cout << itr->first << " " << itr->second << std::endl;
}
}
int main() {
std::map<std::string, double> pitcher_list;
// 맵의 insert 함수는 pair 객체를 인자로 받습니다.
pitcher_list.insert(std::pair<std::string, double>("박세웅", 2.23));
pitcher_list.insert(std::pair<std::string, double>("해커 ", 2.93));
pitcher_list.insert(std::pair<std::string, double>("피어밴드 ", 2.95));
// 타입을 지정하지 않아도 간단히 std::make_pair 함수로
// std::pair 객체를 만들 수 도 있습니다.
pitcher_list.insert(std::make_pair("차우찬", 3.04));
pitcher_list.insert(std::make_pair("장원준 ", 3.05));
pitcher_list.insert(std::make_pair("헥터 ", 3.09));
// 혹은 insert 를 안쓰더라도 [] 로 바로
// 원소를 추가할 수 있습니다.
pitcher_list["니퍼트"] = 3.56;
pitcher_list["박종훈"] = 3.76;
pitcher_list["켈리"] = 3.90;
print_map(pitcher_list);
std::cout << "박세웅 방어율은? :: " << pitcher_list["박세웅"] << std::endl;
}
니퍼트 3.56
박세웅 2.23
박종훈 3.76
장원준 3.05
차우찬 3.04
켈리 3.9
피어밴드 2.95
해커 2.93
헥터 3.09
박세웅 방어율은? :: 2.23
셋과 맵과는 다르게 중복된 원소를 허락
#include <iostream>
#include <map>
#include <string>
template <typename K, typename V>
void print_map(const std::multimap<K, V>& m) {
// 맵의 모든 원소들을 출력하기
for (const auto& kv : m) {
std::cout << kv.first << " " << kv.second << std::endl;
}
}
int main() {
std::multimap<int, std::string> m;
m.insert(std::make_pair(1, "hello"));
m.insert(std::make_pair(1, "hi"));
m.insert(std::make_pair(1, "ahihi"));
m.insert(std::make_pair(2, "bye"));
m.insert(std::make_pair(2, "baba"));
print_map(m);
std::cout << "--------------------" << std::endl;
// 1 을 키로 가지는 반복자들의 시작과 끝을
// std::pair 로 만들어서 리턴한다.
auto range = m.equal_range(1);
for (auto itr = range.first; itr != range.second; ++itr) {
std::cout << itr->first << " : " << itr->second << " " << std::endl;
}
}
1 hello
1 hi
1 ahihi
2 bye
2 baba
--------------------
1 : hello
1 : hi
1 : ahihi
원소가 정렬되어 있지 않음
(셋이나 맵의 경우 원소들이 순서대로 정렬되어서 내부에 저장)
반복자로 원소 출력시 랜덤한 순서로 출력
임의의 크기의 데이터를 고정된 크기의 데이터로 대응시켜주는 함수
1 부터 D (= 상자의 수)까지의 값을반환하고 그 해시값 (해시 함수로 계산한 값)을 원소를 저장
같은 원소를 해시 함수에 전달한다면 같은 해시값을 리턴
example code
#include <iostream>
#include <string>
#include <unordered_set>
template <typename K>
void print_unordered_set(const std::unordered_set<K>& m) {
// 셋의 모든 원소들을 출력하기
for (const auto& elem : m) {
std::cout << elem << std::endl;
}
}
template <typename K>
void is_exist(std::unordered_set<K>& s, K key) {
auto itr = s.find(key);
if (itr != s.end()) {
std::cout << key << " 가 존재!" << std::endl;
} else {
std::cout << key << " 가 없다" << std::endl;
}
}
int main() {
std::unordered_set<std::string> s;
s.insert("hi");
s.insert("my");
s.insert("name");
s.insert("is");
s.insert("psi");
s.insert("welcome");
s.insert("to");
s.insert("c++");
print_unordered_set(s);
std::cout << "----------------" << std::endl;
is_exist(s, std::string("c++"));
is_exist(s, std::string("c"));
std::cout << "----------------" << std::endl;
std::cout << "'hi' 를 삭제" << std::endl;
s.erase(s.find("hi"));
is_exist(s, std::string("hi"));
}
c++
to
my
name
hi
is
psi
welcome
---------------
c++ 가 존재!
c 가 없다
---------------
'hi' 를 삭제
hi 가 없다
여러 함수로 작업
두종류
알고리즘을 수행할 반복자의 시작점과 끝점 바로 뒤를 인자로 받음
template <typename Iter> void do_something(Iter begin, Iter end);
반복자는 동일하나 ’특정한 조건’ 을 추가 인자를 받음
template <typename Iter, typename Pred> void do_something(Iter begin, Iter end, Pred pred);
’특정한 조건’ - 서술자(Predicate)
Pred 에는 보통 bool 을 리턴하는 함수객체(Functor)
오름차순으로 정렬
sort(vec.begin(), vec.end());
임의접근 반복자(RandomAccessIterator) 타입만 가능
즉 벡터와 데크만 가능
내림 차순으로 정렬을 원하면
compare함수 생성하여 사용
greater 사용도 가능
std::sort(vec.begin(), vec.end(), greater<int>());
example code
#include <algorithm>
#include <iostream>
#include <vector>
template <typename Iter>
void print(Iter begin, Iter end) {
while (begin != end) {
std::cout << *begin << " ";
begin++;
}
std::cout << std::endl;
}
struct int_compare {
bool operator()(const int& a, const int& b) const { return a > b; }
};
int main() {
std::vector<int> vec;
vec.push_back(5);
vec.push_back(3);
vec.push_back(1);
vec.push_back(6);
vec.push_back(4);
vec.push_back(7);
vec.push_back(2);
std::cout << "정렬 전 ----" << std::endl;
print(vec.begin(), vec.end());
std::sort(vec.begin(), vec.end(), int_compare());
std::cout << "정렬 후 ----" << std::endl;
print(vec.begin(), vec.end());
}
정렬 전 ----
5 3 1 6 4 7 2
정렬 후 ----
7 6 5 4 3 2 1
정렬을 [stard, end) 전체 원소들 중에서 [start, middle) 까지 원소들이 전체 원소들 중에서 제일 작은애들 순으로 정렬
std::partial_sort(vec.begin(), vec.begin() + 3, vec.end());
원소가 삽입되어 있는 순서를 보존하는 정렬
std::stable_sort(vec2.begin(), vec2.end());
컨테이너의 원소를 제거하는 함수
vector의 erase
pos 가 가리키는 원소를 벡터에서 지움
Iterator erase(Iterator pos);
first 부터 last 사이에 있는 모든 원소들을 지움
Iterator erase(Iterator first, Iterator last);
원하는 값을 가진 원소들을 컨테이너 뒤로 보내 지움
즉 지우지 말아야할 원소를 하나씩 앞으로 이동시키는 방식
크기가 지우는게 아니라 땡기는 거다. < 크기가 그대로임
대신 한번에 원하는 값을 갖는 원소 여러개 지우기 가능
보통 erase와 조합하여 사용
시작부터 끝전까지 값 찾아 제거
remove(vec.begin(), vec.end(), 3)
vec.erase(remove(vec.begin(), vec.end(), 3), vec.end());
시작부터 끝전까지 조건 충족하면 제거
vec.erase(remove_if(vec.begin(), vec.end(), is_odd()), vec.end());
조건을 위한 함수 객체 사용시 유의
※함수 객체 사용시 주의
객체 내부 변수를 통해절대로 특정 상태를 저장해서 이에 따라 결과가 달라지는 루틴을 짜면 안됨
-> 단순 전달이 아닌 복사나 소멸로 값이 다르게 파악됨
example code
template <typename Iter>
void print(Iter begin, Iter end) {
while (begin != end) {
std::cout << "[" << *begin << "] ";
begin++;
}
std::cout << std::endl;
}
struct is_odd {
bool operator()(const int& i) { return i % 2 == 1; }
};
int main() {
std::vector<int> vec;
vec.push_back(5);
vec.push_back(3);
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
std::cout << "처음 vec 상태 ------" << std::endl;
print(vec.begin(), vec.end());
std::cout << "벡터에서 홀수 인 원소 제거 ---" << std::endl;
vec.erase(std::remove_if(vec.begin(), vec.end(), is_odd()), vec.end());
print(vec.begin(), vec.end());
}
처음 vec 상태 ------
[5] [3] [1] [2] [3] [4]
벡터에서 홀수 인 원소 제거 ---
[2] [4]
임시적으로 함수 객체 생성
일반적인 꼴
[capture list] (받는 인자) -> 리턴 타입 { 함수 본체 }
리턴 타입 생략
[capture list] ( 받는 인자) {함수 본체}
example code
//함수 객체
struct is_odd {
bool operator()(const int& i) { return i % 2 == 1; }
};
//람다 함수
[](int i) -> bool { return i % 2 == 1; }
// 바로 호출
[](int i) { return i % 2 == 1; }(3);// true
//함수객체 성성 후 호출
auto func = [](int i) { return i % 2 == 1; };
func(4); // false;
//캡쳐 리스트 사용
[&num_erased](int i) {
if (num_erased >= 2)
return false;
else if (i % 2 == 1) {
num_erased++;
return true;
}
return false;
}
// 객체를 캡쳐 리스트로 사용
vec.erase(std::remove_if(vec.begin(), vec.end(),
[this](int i) {
if (this->num_erased >= 2)
return false;
else if (i % 2 == 1) {
this->num_erased++;
return true;
}
return false;
}),
vec.end());
transform
원소들을 수정하는 함수
컨테이너 전체 혹은 일부를 순회하면서 값들을 수정
transform (시작 반복자, 끝 반복자, 결과를 저장할 컨테이너의 시작 반복자, Pred)
example code
std::transform(vec.begin(), vec.end(), vec.begin(),[](int i) { return i + 1; });
원소들을 탐색하는 계열의 함수
first 부터 last 까지 순회하며 value와 같은 값 파악 있다면 이를 가리키는 반복자를 리턴
template <class InputIt, class T> InputIt find(InputIt first, InputIt last, const T& value)
current = find(current, vec.end(), 3);
함수 객체를 인자로 받아서 그 결과가 참인 원소들을 탐색
current = std::find_if(current, vec.end(), [](int i) { return i % 3 == 2; });
인자로 받은 범위안의 모든 원소들 중에서 조건을 하나라도 충족하는 것이 있다면 true 리턴
OR 연산과 비슷
std::any_of(users.begin(), users.end(), [](User& user) { return user.level >= 19; });
모든 원소들이 전부 조건을 충족해야 true 리턴
AND 연산과 비슷
std::all_of(users.begin(), users.end(), [](User& user) { return user.level >= 15; });
basic_string 은 CharT 타입의 객체들을 메모리에 연속적으로 저장하고, 여러가지 문자열 연산들을 지원해주는 클래스
여러 종류 타입으로 인스턴스화된 문자열
char, wchar_t, char8_t, char16_t, char32_t
basic_sting
문자열들을 어떻게 보관하는지에 대한 로직
Traits
문자열들을 어떻게 연산하는지 에 대한 로직
std::char_traits 사용 시
사용자가 원하는 기능을 가진 문자열을 생성 가능
짧은 길이 문자열의 경우 따로 문자 데이터를 위한 메모리를
할당하는 대신에 그냥 객체 자체에 저장
문자열 리터럴로 정의
std::string operator"" s(const char *str, std::size_t len);
std::string str = "hello"; // char[]
인코딩(Encoding) 방식 사용
인코딩 방식에 따라 컴퓨터에서 문자를 표현하기 위해
동일하게 4 바이트를 사용하는 대신에,
어떤 문자는 1 바이트, 2 바이트 등의 다른 길이로 저장
인코딩 방식
문자열을 읽기 만 하는 클래스
어딘가 존재하는 문자열을 참조해서 읽기만 함