컨테이너의 저장 타입
컨테이너는 아래 typedef를 갖는다.
tyepdef T value_type;
#include "show.h"
void print_first_element(vector<int>&v)
{
int n = v.front();
cout << n << endl;
}
template<typename T>
void print_first_element(vector<T>&v)
{
typename T n = v.front();
cout << n << endl;
}
template<typename T>
void print_first_element(T &v)
{
// T : list<double>
T::value_type n = v.front();
cout << n << endl;
}
int main()
{
vector<double> v = { 1, 2, 3 };
/* int형이 아니라 double로 바꾼다면 ?
vector가 아니라 list라면 ?
*/
print_first_element(v);
}
/*
template 기반의 container들은 자신이 [저장하는 타입]이 있음.
외부에서 [그 저장하는 타입]을 알고 싶을 때가 있음.
그래서 아래처럼 만들 수 있음.
template<typename T> class list
{
public:
tyepdef T value_type;
// ...
};
list<int> s = { 1, 2, 3 };
list<int>::value_type n = s.front(); // 이런 식으로는 쓰지 않겠지만,
template<typename T>
void printf_first_element(T&v)
{
? n = v.front();
//여기서 컨테이너 T가 저장하는 데이터의 타입을 알 수 있는 방법이 필요하다. 그래서 value_type을 사용한다.
cout << n << endl;
}
다만, T::value_type은 보통은 static 멤버로 생각함.
그래서 명확하게 typename으로 보게 하도록 하기 위해서
typename T::value_type n = v.front()처럼, typename이라는 키워드를 넣어 준다.
즉, 컨테이너 안에 있는 typedef된 타입을 사용할 때는 꼭 typename을 앞에 붙여 준다.
또한 컨테이너의 크기를 얻어 올때도
int sz = v.size(); 보다는 아래와 같이
list<double>::size_type sz = v.size();로 하는게 정확하다.
typename을 붙이지 않는다. 템플릿일 때만 typename을 붙여 준다.
즉,
typename list<double>::size_type sz = v.size(); // ERROR
typename T::size_type sz = v.size(); // OK
*/
반복자의 value_type
#include "show.h"
// 주어진 구간의 합을 구하는 함수
int sum(int* first, int *last)
{
int s = 0;
while (first != last) {
s = s + *first;
++first;
}
return s;
}
/*반복자도 자신이 가르키는 타입이 있습니다.*/
template<typename T> typename T::value_type
sum2(T first, T last)
{
typename T::value_type s = 0;
while (first != last) {
s = s + *first;
++first;
}
return s;
}
/*
반복자의 종류
1. 진짜 포인터
2. 객체형 포인터
위 2개의 차이점 때문에 일반화 함수를 만들기 어렵습니다.
해결책 : 포인터 반복자와 객체형 반복자를 구분 정의.
template<typename T> struct iterator_traits
{
typedef typename T::value_type value_type;
};
// 부분 정의 : 아래 <T*>는 T가 진짜 pointer일때는 컴파일 시 이 정의를 사용하라는 의미.
template<typename T> struct iterator_traits<T*>
{
typedef T value_type;
};
*/
template<typename T> typename iterator_traits<T>::value_type
sum3(T first, T last)
{
typename iterator_traits<T>::value_type s = 0;
while (first != last) {
s = s + *first;
++first;
}
return s;
}
int main()
{
int x[10] = { 1,2,3,4,5,6,7,8,9,10 };
cout << sum(x, x + 10) << endl;
// int는 어떻게 하나 ? int타입은 value_type을 지원하는가 ?
vector<int> v = { 1,2,3,4,5,6,7,8,9,10 };
cout << sum2(v.begin(), v.end()) << endl;
int x2[10] = { 1,2,3,4,5,6,7,8,9,10 };
// cout << sum2(x2, x2 + 10) << endl; // ERROR
// int는 어떻게 하나 ? int타입은 value_type을 지원하는가 ?
int x3[10] = { 1,2,3,4,5,6,7,8,9,10 };
cout << sum3(x3, x3 + 10) << endl; // OK
/* 반복자가 일반 포인터일 때도 처리할 수 있도록 iterator_trait을 2개 정의해 주는 걸 생각할 수있다.
(1) 객체형 (2) 포인터형
다만, iterator_traits 2개는 기 STL에서 제공하기 때문에, 그냥 사용하면 된다.
*/
}
auto의 사용 (C++11버전 부터 가능)
#include "show.h"
/*
int main()
{
int x[3] = { 1,2,3}; // int를 double로 바꾸면 아래 n의 타입도 바꿔줘야 하는 문제가 있다.
int n = x[0];
vector<int> v = { 1,2,3};
vector<int>::value_type n1 = v.front(); // 여기도
vector<int>::iterator p = v.begin(); // 여기도
}
*/
int main()
{
int x[3] = { 1,2,3 };
// C++ 11 auto : [컴파일] 시 우변의 타입을 조사해서 좌변에 결정해 달라.
auto n = x[0];
vector<int> v = { 1,2,3 };
auto n1 = v.front();
auto p = v.begin();
}
/*
template<typename T> typename iterator_traits<T>::value_type
sum3(T first, T last)
{
//typename iterator_traits<T>::value_type s = 0; // 이것도... 아래와 같이 바꿀 수 있음.
auto s = *first;
++first;
while (first != last) {
s = s + *first;
++first;
}
return s;
}
*/
단위 전략
#include "show.h"
#include <set> // STL의 set, RB tree입니다. 균형 잡힌 트리.
/*
아래와 같이 하면, 오른차순 밖에 안됨.
template <typename T> class set2
{
public:
void insert(const T&a)
{
if ( 기존 요소 > a)
왼쪽에 넣기
else
오른쪽에 넣기
}
};
그래서
less는 비교를 하기 위한 함수객체이다. 오른 차순.
greater는 내림 차순.
template <typename T, typename Pred = less<T>> class set3
{
Pred pred; // 비교 정책을 담은 함수 객체
public:
void insert(const T&a)
{
if (pred(기존요소, a))
왼쪽에 넣기
else
오른쪽에 넣기
}
};
*/
int main()
{
set<int> s;
set<int, greater<int>> s2; //greater : > 연산 함.
set<int, greater<int>> s3; //greater : > 연산 함.
s.insert(10);
s.insert(15);
s.insert(20);
s.insert(13);
// set반복자를 ++하면 inorder 방식(정렬 유지)로 순회
show(s);
return 0;
}
정책 클래스
#include "show.h"
/* STL string의 원리
template <typename T,
typename Traits = char_traits<T>,
typename Alloc = allocator<T>>
class basic_string
{
T*buff;
public:
bool operator == (const basic_string& s) {
if (Traits::compare(buff, s.buff, strlen(buff))
return true;
return false;
}
};
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
우리가 쓰고 있던 string은 typedef이다.
*/
/* [정책 클래스] : 특정 클래스가 사용하는 다양한 정책을 담은 클래스.
비교 정책, 메모리 할당 정책 등.
모든 정책 클래스는 지켜야할 규칙이 있다.
string의 비교 정책 클래스 : compare, qe, lt 등의 static 함수가 있어야 한다.
> C++ 표준 문서에 작성해야 할 함수 목록이 나와 있음. 다 만들기 귀찮으면 상속 받는다.
*/
class MyCharTraits : public char_traits<char>{
public:
static bool eq(char a, char b) { return toupper(a) == toupper(b); }
static bool lt(char a, char b) { return toupper(a) < toupper(b); }
static bool compare(const char* a, const char* b, int sz) {
return _memicmp(a, b, sz); // 대소문자 비교 않고 메모리 비교 할 때 씀.
}
};
typedef basic_string<char, MyCharTraits> cstring;
int main()
{
cstring s1 = "abcd";
cstring s2 = "ABCD";
if (s1 == s2)
cout << "같은 문자열" << endl;
else
cout << "다른 문자열" << endl;
}
메모리 정책 변경
#include "show.h"
/*
정책 클래스를 이용한
메모리 할당 정책 변경 방법 ?
allocator 는 STL 정의 된 녀석 new, delete
*/
template<typename T, typename Ax = allocator<T>> class vector2
{
Ax ax;
public:
void resize(int sz)
{
/* 메모리 재할당이 필요 합니다.
new / malloc / calloc / pool / system call ???
*/
T* p = ax.allocator(sz);
ax.deallocate(p);
}
};
int main()
{
vector<
}
MyAlloc
#include "show.h"
//cafe.naver.com/cppmaster --> C++ 관련 > 표준라이브러리게시판 >
// STL 표준 위원회 위원장 코드
template <class T> class MyAlloc {
public:
// type definitions
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// rebind allocator to type U
template <class U> struct rebind {
typedef MyAlloc<U> other;
};
// return address of values
pointer address(reference value) const {
return &value;
}
const_pointer address(const_reference value) const {
return &value;
}
/* constructors and destructor
* - nothing to do because the allocator has no state
*/
MyAlloc() throw() { }
MyAlloc(const MyAlloc&) throw() { }
~MyAlloc() throw() { }
template <class U> MyAlloc(const MyAlloc<U>&) throw() {}
// return maximum number of elements that can be allocated
size_type max_size() const throw() {
return std::numeric_limits<std::size_t>::max() / sizeof(T);
}
// allocate but don't initialize num elements of type T
pointer allocate(size_type num, const void* = 0) {
// print message and allocate memory with global new
std::cerr << "allocate " << num << " element(s)"
<< " of size " << sizeof(T) << std::endl;
pointer ret = (pointer)(::operator new(num*sizeof(T)));
std::cerr << " allocated at: " << (void*)ret << std::endl;
return ret;
}
// initialize elements of allocated storage p with value value
void construct(pointer p, const T& value) {
// initialize memory with placement new
new((void*)p)T(value);
}
// destroy elements of initialized storage p
void destroy(pointer p) {
// destroy objects by calling their destructor
p->~T();
}
// deallocate storage p of deleted elements
void deallocate(pointer p, size_type num) {
// print message and deallocate memory with global delete
std::cerr << "deallocate " << num << " element(s)"
<< " of size " << sizeof(T)
<< " at: " << (void*)p << std::endl;
::operator delete((void*)p);
}
};
int main()
{
vector<int, MyAlloc<int>> v;
v.resize(10); // int 크기 10개 메모리 할당 필요.
}
컨테이너 규칙
#include "show.h"
#include <stack>
/* 1. sequence container : vector 연속 메모리, list 비연속, deque 연속 + 비연속
3가지 컨테이너의 함수가 거의 동일 함.
push_back
insert
단 하나의 차이가 있음.
vector는 push_front가 없음 : 왜냐면, 벡터는 앞에 넣으면 성능 저하가 매우 큼.
때문에 아예 안만듬.
기능은 동일하나, 성능이 다름.
*/
/*
STL 컨테이너 함수 규칙
규칙 1. 함수 이름이 거의 동일하다.
규칙 2. 제거와 리턴을 동시에 하는 함수는 [거의] 없다. (예외 안정성 보장을 위해)
* 예외 안정성 보장
*/
int main() {
vector<int> v;
v.push_back(10);
int n1 = v.back(); // 리턴만. 제거 안됨
//int n2 = v.pop_back(); // 제거만, 리턴 안됨. // error void 리턴;
stack<int> s;
s.push(10);
s.push(20);
cout << s.top() << endl; // 20. 꺼내기만 함.
cout << s.top() << endl; // 20. 꺼내기만 함.
s.pop(); // 제거만 함.
// 멀티 쓰레드 할 때는 top과 pop을 하나로 동기 묶어줘야 해서 좀 귀찮음.
return 0;
}
#include "show.h"
/* vector, deque : 임의 접근 반복자
list : 양방향 반복자
3개 컨테이너의 사용법은 거의 유사 함.
*/
int main() {
#if 0
// 1. 생성. list, deque도 같음.
vector<int> v1;
vector<int> v2(10); // 디폴트 0으로 초기화 됨.
vector<int> v3(10, 3);
vector<int> v4(v3);
vector<int> v5 = { 1, 2, 3 }; //c++11부터 가능.
// 2. 요소 추가
v1.push_back(10);
//v1.push_front(10); // vector는 지원 안함. list, deque만 지원. 성능 문제로.
v1.insert(v1.begin() + 1, 20);
// 3. 제거
v1.pop_back();
v1.erase(v1.begin());
v1.erase(find(v1.begin(), v1.end(), 10), v1.end());
// 4. 요소 접근
v1.front() = 40;
v1[0] = 40; // []와 at은 vector와 deque만 가능. list는 안됨.
v1.at(0) = 40;
/*
*/
#endif
vector<int> v8(10);
try {
//v8.at(11) = 20;
v8[11] = 20;
}
catch (out_of_range &e) // out_of_range는 예외 클래스. at은 잘못된 인덱싱에 대해 예외 발생 시킴. []은 예외 발생 없음. 단 []가 빠름.
{
cout << "예외 발생" << endl;
}
for (int i = 0; i < v8.size(); i++) {
v8.at(i) = 0; // 1
v8[i] = 0; // 2가 더 낫다. 이경우는. for loop에서 v8.size()검사를 하고 있기 때문에 인덱스가 잘못될 가능성이 없다.
}
}
vector와 string만 적용되는 개념 : capacity
#include "show.h"
int main()
{
vector<int> v(10);
v.resize(5);
cout << v.size() << endl; // 5
cout << v.capacity() << endl; // 10
v.push_back(5);
cout << v.size() << endl; // 6
cout << v.capacity() << endl; // 10
v.shrink_to_fit();
cout << v.size() << endl; // 6
cout << v.capacity() << endl; // 6
v.push_back(1);
cout << v.size() << endl; // 7
cout << v.capacity() << endl; // ?? 보통은 기존 크기의 1.5배를 키움.(논문) 9가 됨. 다만, compiler에 따라 다름.
v.reserve(100); // capacity만 미리 확보.
cout << v.size() << endl;
cout << v.capacity() << endl;
}
사용자 정의 타입과 STL 컨테이너
#include "show.h"
/*
디폴트 생성자가 있는 것이 좋다.
*/
class People
{
string name;
int age;
public:
People(string n, int a) :name(n), age(a) { }
};
int main()
{
// 1. 생성
//vector<People> v(10); // Error. 디폴트 생성자 필요.
vector<People> v(10, People("Unknown", 0)); // OK
//v.resize(20); // Error
v.resize(20, People("u", 0)); // OK
// 2. 요소 넣기
People p1("park", 20); // 생성 후 유지 됨
v.push_back(p1);
v.push_back(People("park", 20)); // 임시객체로 처리하면 statement끝에서 메모리 해제됨.
// 위 2가지는 어쨌든 2개의 객체가 생긴다.
// 더 나은 방법. 생성자 인자만 전달해서 People 자체를 vector가 만들게 하자.
// 객체 생성이 한번만 일어난다 !
v.emplace_back("lee", 30); // C++11부터 가능. // perfect forwarding 기술 이용.
//v.emplace_front(), v.emplace() 등 insert_front()와 insert등을 대체 함.
}
사용자 정의 타입의 소팅을 위한 함수객체
#include "show.h"
/*
사용자 정의 타입과 STL 컨테이너
1. 디폴트 생성자가 있는 것이 좋다.
*/
class PeopleAgeCmp;
class People
{
string name;
int age;
public:
People(string n, int a) :name(n), age(a) { }
// vector에 넣기 위해서는 크기 비교 함수가 필요 없지만,
// sort사용시에는 구현 필요. 상수 함수를 만드는 것이 좋다.
bool operator<(const People&p) const { return age < p.age; }
bool operator==(const People&p) const { return age == p.age; }
friend class PeopleAgeCmp;
};
class PeopleAgeCmp{
public:
inline bool operator()(const People&a, const People &b) { return a.age == b.age; }
};
#if 0
class People
{
string name;
int age;
public:
People(string n, int a) :name(n), age(a) { }
// vector에 넣기 위해서는 크기 비교 함수가 필요 없지만,
// sort사용시에는 구현 필요. 상수 함수를 만드는 것이 좋다.
bool operator<(const People&p) const { return age < p.age; }
bool operator==(const People&p) const { return age == p.age; }
};
#endif
int main()
{
vector<People> v;
v.emplace_back("kim", 20);
v.emplace_back("aaa", 30);
v.emplace_back("bbb", 10);
//sort(v.begin(), v.end()); // ERROR 크기 비교 불가
//sort(v.begin(), v.end()); // 기본적으로 <와 ==로 비교하고 있다. --> operator<와 operator==정의 한다.
sort(v.begin(), v.end(), PeopleAgeCmp()); // 기본적으로 <와 ==로 비교하고 있다. --> operator<와 operator==정의 한다.
return 0;
}
less (<)와 eq(==)만 정의하면 되는 이유
#include "show.h"
/*
사용자 정의 타입과 STL 컨테이너
1. 디폴트 생성자가 있는 것이 좋다.
[상수 객체]는 [상수 함수]만 부를 수 있다.
C++에서는 상수 함수를 반드시 써야 한다.
*/
using namespace std::rel_ops; // relational operators의 약자.
class People
{
string name;
int age;
public:
People(string n, int a) :name(n), age(a) { }
// vector에 넣기 위해서는 크기 비교 함수가 필요 없지만,
// sort사용시에는 구현 필요. 상수 함수를 만드는 것이 좋다.
// 2개만 만들고 rel_ops 추가하면 된다.
bool operator<(const People&p) const { return age < p.age; }
bool operator==(const People&p) const { return age == p.age; }
};
/*
원리
namespace std
{
namespace rel_ops
{
template<typename T> inline bool operator> (const T &a, const T&b)
{
return !(a<b) && !(a==b);
}
}
}
*/
int main()
{
People p1("aaa", 10);
People p2("aaa", 15);
bool b1 = p1 < p2; // ?? > OK
bool b2 = p1 > p2; // ?? > NG > using namespace std::rel_ops;를 추가해 주면 OK
/* >를 만든 적은 없지만, C++STL에 전역으로 >가 정의되어 있음. 거기에서 우리가 정의한 <와 ==를 사용 함.*/
return 0;
}
컨테이너를 담는 컨테이너가 좋을 때가 많음.
#include "show.h"
#include <fstream>
/*
vector 안에 string 넣는 것.
둘다 container.
*/
bool foo(char a) { return a == 'a' || a == 'e' || a == 'i' || a == 'o' || a == 'u'; }
int main()
{
ifstream fin("3_컨테이너8.cpp");
string s;
vector<string> v;
while (getline(fin, s)) // fin에서 한줄 꺼내기
v.push_back(s);
// ------------
//reverse(v.begin(), v.end()); // 상하 뒤집기
for (int i = 0; i < v.size(); i++) {
//reverse(v[i].begin(), v[i].end()); // 좌우 뒤집기. 첫줄.
//replace(v[i].begin(), v[i].end(), 'i', ' '); // i를 공백으로 치환
// 모든 모음을 공백으로
replace_if(v[i].begin(), v[i].end(), foo, ' ');
}
// ------------
for (int i = 0; i < v.size(); i++)
cout << v[i] << endl;
}
C++11 컨테이너 forward_list (singly linked list)
#include "show.h"
#include <forward_list>
/*
선형 컨테이너 : vector, list, deque - 1998년에 만들어짐. C++98에 표준화 됨.
forward_list
단, singly linked list는 없었음. 표준이 아니라 컴파일러 제조사마다 각자 slist라는 이름으로 제작해 제공.
그래서 c++11에서는 forward_list라는 이름으로 지음.
//unordered_set (실제는 해시. 기 벤더들이 hash라는 이름을 사용함)
*/
int main() {
forward_list<int> s;
s.push_front(10);
show(s);
}
C++11 컨테이너 array
#include "show.h"
#include <array>
/*
배열과 벡터 : 성능 5% 정도 차이밖에 안남. heap(벡터)과 stack(배열)의 차이.
c++11에서 배열과 완전히 동일한 성능을 보장하는 컨테이너 만듬.
사이즈 완전 동일하며, size() 기능 제공 : array 컨테이너
template<typename T, int N> struct array
{
T buff[N];
inline int size() const { return N; }
typedef T value_type;
typedef T* iterator; // 연속된 메모리 이동.
inline iterator begin() { return buff; }
inline iterator end() { return buff+N; }
};
*/
int main() {
int x[10]; // 크기 고정.
vector<int> v(10); // 크기 변경 가능.
array<int, 10> ar = { 1, 2,3,4,5,6,7,8,9, 10 };
cout << ar.size() << endl;
show(ar);
}
C++11 컨테이너 tuple
#include "show.h"
#include <tuple>
/*
c++98 : vector, list, deque
c++11 : forward_list, array, tuple
tuple은 sorting 안됨.
용도는 여러개의 이질적인 데이터를 parameter 1개로 묶는다든가 할 때 사용.
typedef tuple<int, int> Point;
*/
int main()
{
// 10개가 모두 int. 모두 같은 타임. homogeneous.
vector<int> v(10);
// 서로 다른 타입 보관. heterogeneous
tuple<int, char, long, double, short> t(1, 'a', 3, 4.3, 7);
get<0>(t) = 10;
int n = get<0>(t);
cout << n << ' ' << get<1>(t) << endl;
}
tuple 접근
#include "show.h"
#include <tuple>
template<typename T> void foo(T&a)
{
//tuple에서 4번째(index 3) 것을 꺼내고 싶다.
typename tuple_element<3, T>::type n = get<3>(a);
cout << typeid(n).name() << endl; // double
}
int main()
{
// 10개가 모두 int. 모두 같은 타임. homogeneous.
vector<int> v(10);
// 서로 다른 타입 보관. heterogeneous
tuple<int, char, long, double, short> t(1, 'a', 3, 4.3, 7);
get<0>(t) = 10;
int n = get<0>(t);
cout << n << ' ' << get<1>(t) << endl;
}
컨테이너와객체 복사
#include "show.h"
/*
얕은 복사 문제
> 해결책 : 깊은 복사 또는 참조 개수 사용
또는 Move 생성자 사용
*/
class People
{
char* name;
public:
People(const char* n)
{
name = new char[strlen(n) + 1];
strcpy(name, n);
}
People(const People&p)
{
cout << "복사 생성자 호출" << endl;
name = new char[strlen(p.name) + 1];
strcpy(name, p.name);
}
~People() { delete[] name;}
};
int main()
{
People p1("AAA");
People p2 = p1;
}
C++11의 Move 생성자
#include "show.h"
/*
얕은 복사 문제
> 해결책 : 깊은 복사 또는 참조 개수 사용
*/
class People
{
char* name;
public:
People(const char* n = "AA")
{
name = new char[strlen(n) + 1];
strcpy(name, n);
}
People(const People&p)
{
cout << "복사 생성자 호출" << endl;
name = new char[strlen(p.name) + 1];
strcpy(name, p.name);
}
// C++11의 Move 생성자 - 메모리 소유권 이전을 사용한 기술. &&가 2개 임.
// move 생성자를 만들때는 예외가 없다고 표시 하는 것이 좋다.
// 깊은 복사 생성자로 옴기면 원본 유지 보장됨. move 는 원본에 변화 생김.
// 3개 이동은 OK하다가 4번째에서 예외 나면 ? 즉, exception safety 예외 안정성의 [강력 보장] 지원 못함.
// 때문에 gcc 컴파일러는 개발자가 move 중 예외가 발생하지 않음을 보장해줘야 move 생성자 사용 가능.
People(People&&p) noexcept // g++에서는 noexcept를 넣어 줘야 함.
{
cout << "move 생성자 호출" << endl;
name = p.name; // 얕은 복사 후
p.name = 0; // 원본을 0으로.
}
~People() { delete[] name; }
};
int main()
{
//vector<People> v(10);
//v.resize(20); // 원래 항목들은 어떻게 되나 ? > 복사 생성자 10번 불림. 그때마다 똑같은 메모리 생성 및 복사 발생. 부하 발생.
// 그래서 그냥 이전 항목을 재사용할 수 있는 Move 생성자 도입.
//People p1("AA");
//People p2 = p1; //복사
//People p3 = move(p1); // Move
// Move 생성자가 있으면 Move 생성자를 쓰고, 없으면 복사 생성자를 사용 함.
// v2.resize(20)할 때, 20개 영역 중 10개는 새로 생성되고, 10개는 move 생성자 사용.
vector<People> v2(10);
v2.resize(20);
}
연관 컨테이너 set
#include "show.h"
#include <set> // RedBlack Tree : Balanced Tree.
int main()
{
set<int> s;
s.insert(10);
s.insert(30);
s.insert(20);
s.insert(40);
set<int>::iterator p = s.begin(); // 가장 왼쪽 노드
while (p != s.end())
{
cout << *p << endl;
++p; // inorder 순회. 정렬은 오름차순.
}
set<int, greater<int>> s2;
s2.insert(10);
s2.insert(30);
s2.insert(20);
s2.insert(40);
set<int>::iterator p2 = s2.begin();
typedef set<int, greater<int>> SET; //으로 간략화 가능. 또는 auto 사용(c++11)
// set은 중복을 허용하지 않음.
pair<SET::iterator, bool> ret = s2.insert(20); // 기존 항목을 가리키는 반복자, 두번째는 성공 여부.
cout << ret.second << endl;
if (ret.second == false)
cout << "기존에 요소가 있습니다." << endl;
// 아래 코드를 평가해 봅시다.
SET::iterator p5 = find(s2.begin(), s2.end(), 20); // find는 선형 검색 ++. 결과는 나오지만 성능이 좋지 않음 !
// tree 선형 검색 대신 이진 검색이 좋음.
cout << *p5 << endl;
// 대신 아래를 쓴다.
SET::iterator p6 = s2.find(20); // 이진 검색.
}
연관 컨테이너 map
#include "show.h"
#include <map> // pair를 저장하는 set. key값으로 data 보관. RB Tree 임.
int main()
{
map<string, string> m;
//요소 넣기
pair<string, string> p1("mon", "월요일");
m.insert(p1);
m.insert(make_pair("tue", "화요일"));
m["wed"] = "수요일";
cout << m["wed"] << endl; // 수요일
cout << 'd' << m["fri"] << 'e' << endl; // "fri"가 없을 경우, 새롭게 만들게 된다. 즉, m.insert(make_pair("fri", ""));
// 존재를 검사 할 때는 []를 쓰면 안된다.
// 키값 조사 방법
map<string, string>::iterator p = m.find("wed");
if (p == m.end())
cout << "thr 가 없습니다." << endl;
cout << (*p).first << " " << (*p).second << endl; // 반복자 : 요소를 가르키는 포인터 역할.
}
컨테이너 아답터
#include "show.h"
#include <deque>
/*
list가 있는데, stack이 필요하다.(가정)
1. stack을 다시 만들자
2. list 한쪽으로 사용하면 stack이다. list를 재사용하자.
*/
/*
Adapter 패턴 : 기존 클래스의 인터페이스(함수이름)를 변경해서
다른 클래스처럼 보이게 하는 기술.
*/
template<typename T> class stack :public list<T>
{
public:
inline void push(const T&a) { list<T>::push_back(a); }
inline void pop() { list<T>::pop_back(); }
inline T top() { return list<T>::back(); }
};
template<typename T> class stack2
{
list<T> st;
public:
inline void push(const T&a) { st.push_back(a); }
inline void pop() { st.pop_back(); }
inline T top() { return st.back(); }
};
template<typename T, typename C = deque<T>> class stack3
{
C st;
public:
inline void push(const T&a) { st.push_back(a); }
inline void pop() { st.pop_back(); }
inline T top() { return st.back(); }
};
int main()
{
stack<int> s;
s.push(10);
s.push(20);
cout << s.top() << endl;
s.pop();
cout << s.top() << endl;
s.push_front(30); // 사용자 실수 가능.
// 못쓰게 하는 방법은 ?
// 상속을 포함으로 바꾼다.
// 상속은 구현 + 인터페이스도 상속하게 된다. stack
// 포함은 [구현]만 재사용 한다. stack2
// 상속보다는 포함이 더 좋은 디자인 가능하다.
// 저장소까지 generic하게 하자
stack3<int, vector<int>> s3;
stack3<int> s4;
// stack은 다른 자료 구조에 의존하는 존재.
// 때문에 STL에서는 stack을 "stack adapter"라고 부름.
// stack이 사용하는 메모리 할당자를 바꾸고 싶다면, 의존하는 container의 할당자를 바꾸면 된다.
// stack<int, list<int, MyAlloc<int>>> s5;
// Java는 stack을 vector의 자식으로 만듬?
}
#include "show.h"
// STL에는 3개의 컨테이너 어답터가 있음 : stack, queue, priority_queue
#include <stack>
#include <queue>
int main()
{
// 다음중 에러는 ?
stack<int, vector<int>> s1;
stack<int, list<int>> s2;
queue<int, vector<int>> s3; // vector는 앞쪽이 막힘. ERROR. Queue는 양쪽이 뚫려야 함.
s3.push(10); // 뒤로 넣는다.
s3.pop(); // 앞으로 제거.
queue<int, list<int>> s4;
priority_queue<int> pq;
pq.push(10);
pq.push(20);
pq.push(5);
priority_queue<int, vector<int>, greater<int>> pq2;
pq2.push(10);
pq2.push(30);
pq2.push(5);
//cout << pq.top() << endl; // 우선 순위가 제일 높은 요소가 나온다.
}
비트셋
#include "show.h"
#include <bitset> // 비트를 관리하기 위한 자료 구조
int main()
{
bitset<32> bs;
cout << sizeof(bs) << endl;
bitset<40> bs2;
cout << sizeof(bs2) << endl; // 4 바이트 단위로 커지는 것으로 보임.
bs.set(); // 모두 1로
bs.reset(); // 모두 0으로
bs.set(2);// 2번째 비트만 1
bs.flip(1); // 1번째 비트 뒤집기
string s = bs.to_string();
unsigned long n = bs.to_ulong();
cout << s << endl;
cout << n << endl;
}
난수 만들기
#include "show.h"
#include <bitset>
#include <ctime>
// unique한 난수 만들기
template <int N> class URandom {
bitset<N> bs;
bool recycle;
public:
URandom(bool b = false) : recycle(b)
{
bs.set(); // 모든 비트를 1로
srand(time(0));
}
int operator()() {
if (bs.none()) // 모두 0인 경우
{
if (recycle == true)
bs.set();
else
return -1;
}
int v = -1;
while (!bs.test(v = rand() % N));
bs.reset(v);
return v;
}
};
int main() {
// int n = rand() % 10;
// int n2 = rand() % 10;
URandom<10> r(true);
for (int i = 0; i < 15; i++)
cout << r() << endl;
}
pair, make_pair
#include "show.h"
/*
Point x, y
Complex a, b
template<typename T, typename U> struct pair
{
T first;
U second;
};
pair : c++ 98
tuple : c++ 11
*/
pair<int, double> foo(pair<int, double> p)
{
return p;
}
int main()
{
pair<int, double> p(1, 3.4);
foo(p);
foo(pair<int, double>(1, 3.4)); // 임시 객체 전달 시. 다만 복잡함.
foo(make_pair(1, 3.4)); // 함수 탬플릿으로 클래스 탬플릿 객체 생성 !!!
pair<int, pair<int, int>> p3;
pair<int, pair<int, pair<int, int>>> p4; // pair는 임의의 개수를 저장 가능... 다만 복잡함.
//make_tuple(); 튜플도 있다.
return 0;
}
3가지 스트림
#include <iostream> // 표준 입출력
#include <fstream> // 파일 입출력
#include <sstream> // 메모리 스트림
#include <string>
/*
c++에는 스트림이 3개
표준, io, sstream
*/
using namespace std;
/*
int main()
{
string s;
//cin >> s; // 표준 입력
ifstream fin("10_스트림.cpp");
//fin >> s; // 파일 입력
istringstream iss("i am a boy"); // 메모리 스트림
while(iss >> s)
cout << s << endl;
return 0;
}
*/
int main()
{
int n = 10;
//cout << "n = " << n;
ostringstream oss;
oss << "n = " << n; // C에서는 sprintf 사용. sprinf는 메모리 넘침 문제 있음.
string s = oss.str();
cout << s << endl;
}
[예외 안정성] 보장
// STL은 제거와 리턴을 동시에 하는 함수는 없다.
#include "show.h"
/*
exception safety
1. 완전 보장 : 예외가 없다. int a = 0; (절대 실패 할 수 없음. H/W 실패가 아니라면..)
2. 강력 보장 : 예외가 있어도 처리하면 객체는 이전 상태를 보장한다. 사용가능. (아래 stack은 강력 보장 안됨)
3. 기본 보장 : 예외가 있어도 자원 누수는 없다. 단, 객체의 상태는 보장 안됨. 사용 불가.
STL의 설계 철학은 [2. 강력 보장이다]. 아래 stack은 2를 지키지 못한다.
*/
template<typename T> class stack
{
T buff[10];
int index;
public:
stack() : index(0) {}
void push(const T&a) { buff[index++] = a; }
T pop() {
--index;
if (index < 0)
throw 1;
return buff[index];
}
};
int main() {
stack<int> s;
try {
s.pop();
} catch (int n) {
}
/* exception 발생해도 프로그램은 계속 실행 가능.
그런데 s객체를 사용할 수는 없음...
s의 index는 이미 --해서 잘못된 값임. s를 다시 쓸 수가 없음.
*/
}
// [예외 안정성] 보장
#include "show.h"
// 아래도 강력 보장이 안됨.
template<typename T> class stack
{
T buff[10];
int index;
public:
stack() : index(0) {}
void push(const T&a) { buff[index++] = a; }
T pop() {
if (index < 1)
throw 1;
--index;
return buff[index];
}
};
int main() {
stack<People> s;
People p;
s.push(p);
try {
// People p2 = s.pop(); // 이 코드는 실제 2가지가 실행됨.
// 1. pop() 실행 - 문제 없음.
// 2. People의 복사 생성자 실행. 복사 생성자 과정에서 메모리 문제로 실패 하면 ?
// 실제 쓰지 못한 상황이지만, stack에서 항목은 사라짐.
// 때문에 아래와 같이 바꿈.
People p3 = s.top(); // 복사
s.pop(); // 제거
}
catch (... n) {
}
}
예제
#include "show.h"
#include <map>
#include <sstream>
#include <fstream>
using namespace std;
// 인덱스 만드는 프로그램.
// readme.txt
int main()
{
ifstream fin("readme.txt");
string s;
int line = 0;
typedef map<string, list<int>> IndexMap;
IndexMap index;
while (getline(fin, s)) {
++line;
istringstream iss(s);
string word;
while (iss >> word) {
index[word].push_back(line);
}
}
// map 내용 출력
ostream_iterator<int> out(cout, ", ");
IndexMap::iterator p = index.begin();
while (p != index.end()) {
cout << p->first << " : ";
copy(p->second.begin(), p->second.end(), out);
cout << endl;
++p;
}
}
예제2
#include "show.h"
#include <map>
using namespace std::placeholders;
void foo(void* p) { cout << "foo" << (int)p << endl; }
void goo(void* p, int n) { cout << "goo" << (int)p << endl; }
class CallbackTable {
typedef function<void(void*)> HANDLER;
map<string, vector<HANDLER>> table;
public:
void Register(string ev, HANDLER h) {
table[ev].push_back(h);
}
void Fire(string ev, void *data) {
vector<HANDLER> &v = table[ev];
for (int i = 0; i < v.size(); i++)
v[i](data);
}
};
int main()
{
CallbackTable ct;
ct.Register("DISCONNECT_WIFI", &foo);
ct.Register("DISCONNECT_WIFI", bind(&goo, _1, 0));
ct.Register("LOW_POWER", &foo);
ct.Fire("DISCONNECT_WIFI", (void*)30); // 등록된 함수의 인자로 30 전달.
}
컨테이너는 아래 typedef를 갖는다.
tyepdef T value_type;
#include "show.h"
void print_first_element(vector<int>&v)
{
int n = v.front();
cout << n << endl;
}
template<typename T>
void print_first_element(vector<T>&v)
{
typename T n = v.front();
cout << n << endl;
}
template<typename T>
void print_first_element(T &v)
{
// T : list<double>
T::value_type n = v.front();
cout << n << endl;
}
int main()
{
vector<double> v = { 1, 2, 3 };
/* int형이 아니라 double로 바꾼다면 ?
vector가 아니라 list라면 ?
*/
print_first_element(v);
}
/*
template 기반의 container들은 자신이 [저장하는 타입]이 있음.
외부에서 [그 저장하는 타입]을 알고 싶을 때가 있음.
그래서 아래처럼 만들 수 있음.
template<typename T> class list
{
public:
tyepdef T value_type;
// ...
};
list<int> s = { 1, 2, 3 };
list<int>::value_type n = s.front(); // 이런 식으로는 쓰지 않겠지만,
template<typename T>
void printf_first_element(T&v)
{
? n = v.front();
//여기서 컨테이너 T가 저장하는 데이터의 타입을 알 수 있는 방법이 필요하다. 그래서 value_type을 사용한다.
cout << n << endl;
}
다만, T::value_type은 보통은 static 멤버로 생각함.
그래서 명확하게 typename으로 보게 하도록 하기 위해서
typename T::value_type n = v.front()처럼, typename이라는 키워드를 넣어 준다.
즉, 컨테이너 안에 있는 typedef된 타입을 사용할 때는 꼭 typename을 앞에 붙여 준다.
또한 컨테이너의 크기를 얻어 올때도
int sz = v.size(); 보다는 아래와 같이
list<double>::size_type sz = v.size();로 하는게 정확하다.
typename을 붙이지 않는다. 템플릿일 때만 typename을 붙여 준다.
즉,
typename list<double>::size_type sz = v.size(); // ERROR
typename T::size_type sz = v.size(); // OK
*/
반복자의 value_type
#include "show.h"
// 주어진 구간의 합을 구하는 함수
int sum(int* first, int *last)
{
int s = 0;
while (first != last) {
s = s + *first;
++first;
}
return s;
}
/*반복자도 자신이 가르키는 타입이 있습니다.*/
template<typename T> typename T::value_type
sum2(T first, T last)
{
typename T::value_type s = 0;
while (first != last) {
s = s + *first;
++first;
}
return s;
}
/*
반복자의 종류
1. 진짜 포인터
2. 객체형 포인터
위 2개의 차이점 때문에 일반화 함수를 만들기 어렵습니다.
해결책 : 포인터 반복자와 객체형 반복자를 구분 정의.
template<typename T> struct iterator_traits
{
typedef typename T::value_type value_type;
};
// 부분 정의 : 아래 <T*>는 T가 진짜 pointer일때는 컴파일 시 이 정의를 사용하라는 의미.
template<typename T> struct iterator_traits<T*>
{
typedef T value_type;
};
*/
template<typename T> typename iterator_traits<T>::value_type
sum3(T first, T last)
{
typename iterator_traits<T>::value_type s = 0;
while (first != last) {
s = s + *first;
++first;
}
return s;
}
int main()
{
int x[10] = { 1,2,3,4,5,6,7,8,9,10 };
cout << sum(x, x + 10) << endl;
// int는 어떻게 하나 ? int타입은 value_type을 지원하는가 ?
vector<int> v = { 1,2,3,4,5,6,7,8,9,10 };
cout << sum2(v.begin(), v.end()) << endl;
int x2[10] = { 1,2,3,4,5,6,7,8,9,10 };
// cout << sum2(x2, x2 + 10) << endl; // ERROR
// int는 어떻게 하나 ? int타입은 value_type을 지원하는가 ?
int x3[10] = { 1,2,3,4,5,6,7,8,9,10 };
cout << sum3(x3, x3 + 10) << endl; // OK
/* 반복자가 일반 포인터일 때도 처리할 수 있도록 iterator_trait을 2개 정의해 주는 걸 생각할 수있다.
(1) 객체형 (2) 포인터형
다만, iterator_traits 2개는 기 STL에서 제공하기 때문에, 그냥 사용하면 된다.
*/
}
auto의 사용 (C++11버전 부터 가능)
#include "show.h"
/*
int main()
{
int x[3] = { 1,2,3}; // int를 double로 바꾸면 아래 n의 타입도 바꿔줘야 하는 문제가 있다.
int n = x[0];
vector<int> v = { 1,2,3};
vector<int>::value_type n1 = v.front(); // 여기도
vector<int>::iterator p = v.begin(); // 여기도
}
*/
int main()
{
int x[3] = { 1,2,3 };
// C++ 11 auto : [컴파일] 시 우변의 타입을 조사해서 좌변에 결정해 달라.
auto n = x[0];
vector<int> v = { 1,2,3 };
auto n1 = v.front();
auto p = v.begin();
}
/*
template<typename T> typename iterator_traits<T>::value_type
sum3(T first, T last)
{
//typename iterator_traits<T>::value_type s = 0; // 이것도... 아래와 같이 바꿀 수 있음.
auto s = *first;
++first;
while (first != last) {
s = s + *first;
++first;
}
return s;
}
*/
단위 전략
#include "show.h"
#include <set> // STL의 set, RB tree입니다. 균형 잡힌 트리.
/*
아래와 같이 하면, 오른차순 밖에 안됨.
template <typename T> class set2
{
public:
void insert(const T&a)
{
if ( 기존 요소 > a)
왼쪽에 넣기
else
오른쪽에 넣기
}
};
그래서
less는 비교를 하기 위한 함수객체이다. 오른 차순.
greater는 내림 차순.
template <typename T, typename Pred = less<T>> class set3
{
Pred pred; // 비교 정책을 담은 함수 객체
public:
void insert(const T&a)
{
if (pred(기존요소, a))
왼쪽에 넣기
else
오른쪽에 넣기
}
};
*/
int main()
{
set<int> s;
set<int, greater<int>> s2; //greater : > 연산 함.
set<int, greater<int>> s3; //greater : > 연산 함.
s.insert(10);
s.insert(15);
s.insert(20);
s.insert(13);
// set반복자를 ++하면 inorder 방식(정렬 유지)로 순회
show(s);
return 0;
}
정책 클래스
#include "show.h"
/* STL string의 원리
template <typename T,
typename Traits = char_traits<T>,
typename Alloc = allocator<T>>
class basic_string
{
T*buff;
public:
bool operator == (const basic_string& s) {
if (Traits::compare(buff, s.buff, strlen(buff))
return true;
return false;
}
};
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
우리가 쓰고 있던 string은 typedef이다.
*/
/* [정책 클래스] : 특정 클래스가 사용하는 다양한 정책을 담은 클래스.
비교 정책, 메모리 할당 정책 등.
모든 정책 클래스는 지켜야할 규칙이 있다.
string의 비교 정책 클래스 : compare, qe, lt 등의 static 함수가 있어야 한다.
> C++ 표준 문서에 작성해야 할 함수 목록이 나와 있음. 다 만들기 귀찮으면 상속 받는다.
*/
class MyCharTraits : public char_traits<char>{
public:
static bool eq(char a, char b) { return toupper(a) == toupper(b); }
static bool lt(char a, char b) { return toupper(a) < toupper(b); }
static bool compare(const char* a, const char* b, int sz) {
return _memicmp(a, b, sz); // 대소문자 비교 않고 메모리 비교 할 때 씀.
}
};
typedef basic_string<char, MyCharTraits> cstring;
int main()
{
cstring s1 = "abcd";
cstring s2 = "ABCD";
if (s1 == s2)
cout << "같은 문자열" << endl;
else
cout << "다른 문자열" << endl;
}
메모리 정책 변경
#include "show.h"
/*
정책 클래스를 이용한
메모리 할당 정책 변경 방법 ?
allocator 는 STL 정의 된 녀석 new, delete
*/
template<typename T, typename Ax = allocator<T>> class vector2
{
Ax ax;
public:
void resize(int sz)
{
/* 메모리 재할당이 필요 합니다.
new / malloc / calloc / pool / system call ???
*/
T* p = ax.allocator(sz);
ax.deallocate(p);
}
};
int main()
{
vector<
}
MyAlloc
#include "show.h"
//cafe.naver.com/cppmaster --> C++ 관련 > 표준라이브러리게시판 >
// STL 표준 위원회 위원장 코드
template <class T> class MyAlloc {
public:
// type definitions
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// rebind allocator to type U
template <class U> struct rebind {
typedef MyAlloc<U> other;
};
// return address of values
pointer address(reference value) const {
return &value;
}
const_pointer address(const_reference value) const {
return &value;
}
/* constructors and destructor
* - nothing to do because the allocator has no state
*/
MyAlloc() throw() { }
MyAlloc(const MyAlloc&) throw() { }
~MyAlloc() throw() { }
template <class U> MyAlloc(const MyAlloc<U>&) throw() {}
// return maximum number of elements that can be allocated
size_type max_size() const throw() {
return std::numeric_limits<std::size_t>::max() / sizeof(T);
}
// allocate but don't initialize num elements of type T
pointer allocate(size_type num, const void* = 0) {
// print message and allocate memory with global new
std::cerr << "allocate " << num << " element(s)"
<< " of size " << sizeof(T) << std::endl;
pointer ret = (pointer)(::operator new(num*sizeof(T)));
std::cerr << " allocated at: " << (void*)ret << std::endl;
return ret;
}
// initialize elements of allocated storage p with value value
void construct(pointer p, const T& value) {
// initialize memory with placement new
new((void*)p)T(value);
}
// destroy elements of initialized storage p
void destroy(pointer p) {
// destroy objects by calling their destructor
p->~T();
}
// deallocate storage p of deleted elements
void deallocate(pointer p, size_type num) {
// print message and deallocate memory with global delete
std::cerr << "deallocate " << num << " element(s)"
<< " of size " << sizeof(T)
<< " at: " << (void*)p << std::endl;
::operator delete((void*)p);
}
};
int main()
{
vector<int, MyAlloc<int>> v;
v.resize(10); // int 크기 10개 메모리 할당 필요.
}
컨테이너 규칙
#include "show.h"
#include <stack>
/* 1. sequence container : vector 연속 메모리, list 비연속, deque 연속 + 비연속
3가지 컨테이너의 함수가 거의 동일 함.
push_back
insert
단 하나의 차이가 있음.
vector는 push_front가 없음 : 왜냐면, 벡터는 앞에 넣으면 성능 저하가 매우 큼.
때문에 아예 안만듬.
기능은 동일하나, 성능이 다름.
*/
/*
STL 컨테이너 함수 규칙
규칙 1. 함수 이름이 거의 동일하다.
규칙 2. 제거와 리턴을 동시에 하는 함수는 [거의] 없다. (예외 안정성 보장을 위해)
* 예외 안정성 보장
*/
int main() {
vector<int> v;
v.push_back(10);
int n1 = v.back(); // 리턴만. 제거 안됨
//int n2 = v.pop_back(); // 제거만, 리턴 안됨. // error void 리턴;
stack<int> s;
s.push(10);
s.push(20);
cout << s.top() << endl; // 20. 꺼내기만 함.
cout << s.top() << endl; // 20. 꺼내기만 함.
s.pop(); // 제거만 함.
// 멀티 쓰레드 할 때는 top과 pop을 하나로 동기 묶어줘야 해서 좀 귀찮음.
return 0;
}
#include "show.h"
/* vector, deque : 임의 접근 반복자
list : 양방향 반복자
3개 컨테이너의 사용법은 거의 유사 함.
*/
int main() {
#if 0
// 1. 생성. list, deque도 같음.
vector<int> v1;
vector<int> v2(10); // 디폴트 0으로 초기화 됨.
vector<int> v3(10, 3);
vector<int> v4(v3);
vector<int> v5 = { 1, 2, 3 }; //c++11부터 가능.
// 2. 요소 추가
v1.push_back(10);
//v1.push_front(10); // vector는 지원 안함. list, deque만 지원. 성능 문제로.
v1.insert(v1.begin() + 1, 20);
// 3. 제거
v1.pop_back();
v1.erase(v1.begin());
v1.erase(find(v1.begin(), v1.end(), 10), v1.end());
// 4. 요소 접근
v1.front() = 40;
v1[0] = 40; // []와 at은 vector와 deque만 가능. list는 안됨.
v1.at(0) = 40;
/*
*/
#endif
vector<int> v8(10);
try {
//v8.at(11) = 20;
v8[11] = 20;
}
catch (out_of_range &e) // out_of_range는 예외 클래스. at은 잘못된 인덱싱에 대해 예외 발생 시킴. []은 예외 발생 없음. 단 []가 빠름.
{
cout << "예외 발생" << endl;
}
for (int i = 0; i < v8.size(); i++) {
v8.at(i) = 0; // 1
v8[i] = 0; // 2가 더 낫다. 이경우는. for loop에서 v8.size()검사를 하고 있기 때문에 인덱스가 잘못될 가능성이 없다.
}
}
vector와 string만 적용되는 개념 : capacity
#include "show.h"
int main()
{
vector<int> v(10);
v.resize(5);
cout << v.size() << endl; // 5
cout << v.capacity() << endl; // 10
v.push_back(5);
cout << v.size() << endl; // 6
cout << v.capacity() << endl; // 10
v.shrink_to_fit();
cout << v.size() << endl; // 6
cout << v.capacity() << endl; // 6
v.push_back(1);
cout << v.size() << endl; // 7
cout << v.capacity() << endl; // ?? 보통은 기존 크기의 1.5배를 키움.(논문) 9가 됨. 다만, compiler에 따라 다름.
v.reserve(100); // capacity만 미리 확보.
cout << v.size() << endl;
cout << v.capacity() << endl;
}
사용자 정의 타입과 STL 컨테이너
#include "show.h"
/*
디폴트 생성자가 있는 것이 좋다.
*/
class People
{
string name;
int age;
public:
People(string n, int a) :name(n), age(a) { }
};
int main()
{
// 1. 생성
//vector<People> v(10); // Error. 디폴트 생성자 필요.
vector<People> v(10, People("Unknown", 0)); // OK
//v.resize(20); // Error
v.resize(20, People("u", 0)); // OK
// 2. 요소 넣기
People p1("park", 20); // 생성 후 유지 됨
v.push_back(p1);
v.push_back(People("park", 20)); // 임시객체로 처리하면 statement끝에서 메모리 해제됨.
// 위 2가지는 어쨌든 2개의 객체가 생긴다.
// 더 나은 방법. 생성자 인자만 전달해서 People 자체를 vector가 만들게 하자.
// 객체 생성이 한번만 일어난다 !
v.emplace_back("lee", 30); // C++11부터 가능. // perfect forwarding 기술 이용.
//v.emplace_front(), v.emplace() 등 insert_front()와 insert등을 대체 함.
}
사용자 정의 타입의 소팅을 위한 함수객체
#include "show.h"
/*
사용자 정의 타입과 STL 컨테이너
1. 디폴트 생성자가 있는 것이 좋다.
*/
class PeopleAgeCmp;
class People
{
string name;
int age;
public:
People(string n, int a) :name(n), age(a) { }
// vector에 넣기 위해서는 크기 비교 함수가 필요 없지만,
// sort사용시에는 구현 필요. 상수 함수를 만드는 것이 좋다.
bool operator<(const People&p) const { return age < p.age; }
bool operator==(const People&p) const { return age == p.age; }
friend class PeopleAgeCmp;
};
class PeopleAgeCmp{
public:
inline bool operator()(const People&a, const People &b) { return a.age == b.age; }
};
#if 0
class People
{
string name;
int age;
public:
People(string n, int a) :name(n), age(a) { }
// vector에 넣기 위해서는 크기 비교 함수가 필요 없지만,
// sort사용시에는 구현 필요. 상수 함수를 만드는 것이 좋다.
bool operator<(const People&p) const { return age < p.age; }
bool operator==(const People&p) const { return age == p.age; }
};
#endif
int main()
{
vector<People> v;
v.emplace_back("kim", 20);
v.emplace_back("aaa", 30);
v.emplace_back("bbb", 10);
//sort(v.begin(), v.end()); // ERROR 크기 비교 불가
//sort(v.begin(), v.end()); // 기본적으로 <와 ==로 비교하고 있다. --> operator<와 operator==정의 한다.
sort(v.begin(), v.end(), PeopleAgeCmp()); // 기본적으로 <와 ==로 비교하고 있다. --> operator<와 operator==정의 한다.
return 0;
}
less (<)와 eq(==)만 정의하면 되는 이유
#include "show.h"
/*
사용자 정의 타입과 STL 컨테이너
1. 디폴트 생성자가 있는 것이 좋다.
[상수 객체]는 [상수 함수]만 부를 수 있다.
C++에서는 상수 함수를 반드시 써야 한다.
*/
using namespace std::rel_ops; // relational operators의 약자.
class People
{
string name;
int age;
public:
People(string n, int a) :name(n), age(a) { }
// vector에 넣기 위해서는 크기 비교 함수가 필요 없지만,
// sort사용시에는 구현 필요. 상수 함수를 만드는 것이 좋다.
// 2개만 만들고 rel_ops 추가하면 된다.
bool operator<(const People&p) const { return age < p.age; }
bool operator==(const People&p) const { return age == p.age; }
};
/*
원리
namespace std
{
namespace rel_ops
{
template<typename T> inline bool operator> (const T &a, const T&b)
{
return !(a<b) && !(a==b);
}
}
}
*/
int main()
{
People p1("aaa", 10);
People p2("aaa", 15);
bool b1 = p1 < p2; // ?? > OK
bool b2 = p1 > p2; // ?? > NG > using namespace std::rel_ops;를 추가해 주면 OK
/* >를 만든 적은 없지만, C++STL에 전역으로 >가 정의되어 있음. 거기에서 우리가 정의한 <와 ==를 사용 함.*/
return 0;
}
컨테이너를 담는 컨테이너가 좋을 때가 많음.
#include "show.h"
#include <fstream>
/*
vector 안에 string 넣는 것.
둘다 container.
*/
bool foo(char a) { return a == 'a' || a == 'e' || a == 'i' || a == 'o' || a == 'u'; }
int main()
{
ifstream fin("3_컨테이너8.cpp");
string s;
vector<string> v;
while (getline(fin, s)) // fin에서 한줄 꺼내기
v.push_back(s);
// ------------
//reverse(v.begin(), v.end()); // 상하 뒤집기
for (int i = 0; i < v.size(); i++) {
//reverse(v[i].begin(), v[i].end()); // 좌우 뒤집기. 첫줄.
//replace(v[i].begin(), v[i].end(), 'i', ' '); // i를 공백으로 치환
// 모든 모음을 공백으로
replace_if(v[i].begin(), v[i].end(), foo, ' ');
}
// ------------
for (int i = 0; i < v.size(); i++)
cout << v[i] << endl;
}
C++11 컨테이너 forward_list (singly linked list)
#include "show.h"
#include <forward_list>
/*
선형 컨테이너 : vector, list, deque - 1998년에 만들어짐. C++98에 표준화 됨.
forward_list
단, singly linked list는 없었음. 표준이 아니라 컴파일러 제조사마다 각자 slist라는 이름으로 제작해 제공.
그래서 c++11에서는 forward_list라는 이름으로 지음.
//unordered_set (실제는 해시. 기 벤더들이 hash라는 이름을 사용함)
*/
int main() {
forward_list<int> s;
s.push_front(10);
show(s);
}
C++11 컨테이너 array
#include "show.h"
#include <array>
/*
배열과 벡터 : 성능 5% 정도 차이밖에 안남. heap(벡터)과 stack(배열)의 차이.
c++11에서 배열과 완전히 동일한 성능을 보장하는 컨테이너 만듬.
사이즈 완전 동일하며, size() 기능 제공 : array 컨테이너
template<typename T, int N> struct array
{
T buff[N];
inline int size() const { return N; }
typedef T value_type;
typedef T* iterator; // 연속된 메모리 이동.
inline iterator begin() { return buff; }
inline iterator end() { return buff+N; }
};
*/
int main() {
int x[10]; // 크기 고정.
vector<int> v(10); // 크기 변경 가능.
array<int, 10> ar = { 1, 2,3,4,5,6,7,8,9, 10 };
cout << ar.size() << endl;
show(ar);
}
C++11 컨테이너 tuple
#include "show.h"
#include <tuple>
/*
c++98 : vector, list, deque
c++11 : forward_list, array, tuple
tuple은 sorting 안됨.
용도는 여러개의 이질적인 데이터를 parameter 1개로 묶는다든가 할 때 사용.
typedef tuple<int, int> Point;
*/
int main()
{
// 10개가 모두 int. 모두 같은 타임. homogeneous.
vector<int> v(10);
// 서로 다른 타입 보관. heterogeneous
tuple<int, char, long, double, short> t(1, 'a', 3, 4.3, 7);
get<0>(t) = 10;
int n = get<0>(t);
cout << n << ' ' << get<1>(t) << endl;
}
tuple 접근
#include "show.h"
#include <tuple>
template<typename T> void foo(T&a)
{
//tuple에서 4번째(index 3) 것을 꺼내고 싶다.
typename tuple_element<3, T>::type n = get<3>(a);
cout << typeid(n).name() << endl; // double
}
int main()
{
// 10개가 모두 int. 모두 같은 타임. homogeneous.
vector<int> v(10);
// 서로 다른 타입 보관. heterogeneous
tuple<int, char, long, double, short> t(1, 'a', 3, 4.3, 7);
get<0>(t) = 10;
int n = get<0>(t);
cout << n << ' ' << get<1>(t) << endl;
}
컨테이너와객체 복사
#include "show.h"
/*
얕은 복사 문제
> 해결책 : 깊은 복사 또는 참조 개수 사용
또는 Move 생성자 사용
*/
class People
{
char* name;
public:
People(const char* n)
{
name = new char[strlen(n) + 1];
strcpy(name, n);
}
People(const People&p)
{
cout << "복사 생성자 호출" << endl;
name = new char[strlen(p.name) + 1];
strcpy(name, p.name);
}
~People() { delete[] name;}
};
int main()
{
People p1("AAA");
People p2 = p1;
}
C++11의 Move 생성자
#include "show.h"
/*
얕은 복사 문제
> 해결책 : 깊은 복사 또는 참조 개수 사용
*/
class People
{
char* name;
public:
People(const char* n = "AA")
{
name = new char[strlen(n) + 1];
strcpy(name, n);
}
People(const People&p)
{
cout << "복사 생성자 호출" << endl;
name = new char[strlen(p.name) + 1];
strcpy(name, p.name);
}
// C++11의 Move 생성자 - 메모리 소유권 이전을 사용한 기술. &&가 2개 임.
// move 생성자를 만들때는 예외가 없다고 표시 하는 것이 좋다.
// 깊은 복사 생성자로 옴기면 원본 유지 보장됨. move 는 원본에 변화 생김.
// 3개 이동은 OK하다가 4번째에서 예외 나면 ? 즉, exception safety 예외 안정성의 [강력 보장] 지원 못함.
// 때문에 gcc 컴파일러는 개발자가 move 중 예외가 발생하지 않음을 보장해줘야 move 생성자 사용 가능.
People(People&&p) noexcept // g++에서는 noexcept를 넣어 줘야 함.
{
cout << "move 생성자 호출" << endl;
name = p.name; // 얕은 복사 후
p.name = 0; // 원본을 0으로.
}
~People() { delete[] name; }
};
int main()
{
//vector<People> v(10);
//v.resize(20); // 원래 항목들은 어떻게 되나 ? > 복사 생성자 10번 불림. 그때마다 똑같은 메모리 생성 및 복사 발생. 부하 발생.
// 그래서 그냥 이전 항목을 재사용할 수 있는 Move 생성자 도입.
//People p1("AA");
//People p2 = p1; //복사
//People p3 = move(p1); // Move
// Move 생성자가 있으면 Move 생성자를 쓰고, 없으면 복사 생성자를 사용 함.
// v2.resize(20)할 때, 20개 영역 중 10개는 새로 생성되고, 10개는 move 생성자 사용.
vector<People> v2(10);
v2.resize(20);
}
연관 컨테이너 set
#include "show.h"
#include <set> // RedBlack Tree : Balanced Tree.
int main()
{
set<int> s;
s.insert(10);
s.insert(30);
s.insert(20);
s.insert(40);
set<int>::iterator p = s.begin(); // 가장 왼쪽 노드
while (p != s.end())
{
cout << *p << endl;
++p; // inorder 순회. 정렬은 오름차순.
}
set<int, greater<int>> s2;
s2.insert(10);
s2.insert(30);
s2.insert(20);
s2.insert(40);
set<int>::iterator p2 = s2.begin();
typedef set<int, greater<int>> SET; //으로 간략화 가능. 또는 auto 사용(c++11)
// set은 중복을 허용하지 않음.
pair<SET::iterator, bool> ret = s2.insert(20); // 기존 항목을 가리키는 반복자, 두번째는 성공 여부.
cout << ret.second << endl;
if (ret.second == false)
cout << "기존에 요소가 있습니다." << endl;
// 아래 코드를 평가해 봅시다.
SET::iterator p5 = find(s2.begin(), s2.end(), 20); // find는 선형 검색 ++. 결과는 나오지만 성능이 좋지 않음 !
// tree 선형 검색 대신 이진 검색이 좋음.
cout << *p5 << endl;
// 대신 아래를 쓴다.
SET::iterator p6 = s2.find(20); // 이진 검색.
}
연관 컨테이너 map
#include "show.h"
#include <map> // pair를 저장하는 set. key값으로 data 보관. RB Tree 임.
int main()
{
map<string, string> m;
//요소 넣기
pair<string, string> p1("mon", "월요일");
m.insert(p1);
m.insert(make_pair("tue", "화요일"));
m["wed"] = "수요일";
cout << m["wed"] << endl; // 수요일
cout << 'd' << m["fri"] << 'e' << endl; // "fri"가 없을 경우, 새롭게 만들게 된다. 즉, m.insert(make_pair("fri", ""));
// 존재를 검사 할 때는 []를 쓰면 안된다.
// 키값 조사 방법
map<string, string>::iterator p = m.find("wed");
if (p == m.end())
cout << "thr 가 없습니다." << endl;
cout << (*p).first << " " << (*p).second << endl; // 반복자 : 요소를 가르키는 포인터 역할.
}
컨테이너 아답터
#include "show.h"
#include <deque>
/*
list가 있는데, stack이 필요하다.(가정)
1. stack을 다시 만들자
2. list 한쪽으로 사용하면 stack이다. list를 재사용하자.
*/
/*
Adapter 패턴 : 기존 클래스의 인터페이스(함수이름)를 변경해서
다른 클래스처럼 보이게 하는 기술.
*/
template<typename T> class stack :public list<T>
{
public:
inline void push(const T&a) { list<T>::push_back(a); }
inline void pop() { list<T>::pop_back(); }
inline T top() { return list<T>::back(); }
};
template<typename T> class stack2
{
list<T> st;
public:
inline void push(const T&a) { st.push_back(a); }
inline void pop() { st.pop_back(); }
inline T top() { return st.back(); }
};
template<typename T, typename C = deque<T>> class stack3
{
C st;
public:
inline void push(const T&a) { st.push_back(a); }
inline void pop() { st.pop_back(); }
inline T top() { return st.back(); }
};
int main()
{
stack<int> s;
s.push(10);
s.push(20);
cout << s.top() << endl;
s.pop();
cout << s.top() << endl;
s.push_front(30); // 사용자 실수 가능.
// 못쓰게 하는 방법은 ?
// 상속을 포함으로 바꾼다.
// 상속은 구현 + 인터페이스도 상속하게 된다. stack
// 포함은 [구현]만 재사용 한다. stack2
// 상속보다는 포함이 더 좋은 디자인 가능하다.
// 저장소까지 generic하게 하자
stack3<int, vector<int>> s3;
stack3<int> s4;
// stack은 다른 자료 구조에 의존하는 존재.
// 때문에 STL에서는 stack을 "stack adapter"라고 부름.
// stack이 사용하는 메모리 할당자를 바꾸고 싶다면, 의존하는 container의 할당자를 바꾸면 된다.
// stack<int, list<int, MyAlloc<int>>> s5;
// Java는 stack을 vector의 자식으로 만듬?
}
#include "show.h"
// STL에는 3개의 컨테이너 어답터가 있음 : stack, queue, priority_queue
#include <stack>
#include <queue>
int main()
{
// 다음중 에러는 ?
stack<int, vector<int>> s1;
stack<int, list<int>> s2;
queue<int, vector<int>> s3; // vector는 앞쪽이 막힘. ERROR. Queue는 양쪽이 뚫려야 함.
s3.push(10); // 뒤로 넣는다.
s3.pop(); // 앞으로 제거.
queue<int, list<int>> s4;
priority_queue<int> pq;
pq.push(10);
pq.push(20);
pq.push(5);
priority_queue<int, vector<int>, greater<int>> pq2;
pq2.push(10);
pq2.push(30);
pq2.push(5);
//cout << pq.top() << endl; // 우선 순위가 제일 높은 요소가 나온다.
}
비트셋
#include "show.h"
#include <bitset> // 비트를 관리하기 위한 자료 구조
int main()
{
bitset<32> bs;
cout << sizeof(bs) << endl;
bitset<40> bs2;
cout << sizeof(bs2) << endl; // 4 바이트 단위로 커지는 것으로 보임.
bs.set(); // 모두 1로
bs.reset(); // 모두 0으로
bs.set(2);// 2번째 비트만 1
bs.flip(1); // 1번째 비트 뒤집기
string s = bs.to_string();
unsigned long n = bs.to_ulong();
cout << s << endl;
cout << n << endl;
}
난수 만들기
#include "show.h"
#include <bitset>
#include <ctime>
// unique한 난수 만들기
template <int N> class URandom {
bitset<N> bs;
bool recycle;
public:
URandom(bool b = false) : recycle(b)
{
bs.set(); // 모든 비트를 1로
srand(time(0));
}
int operator()() {
if (bs.none()) // 모두 0인 경우
{
if (recycle == true)
bs.set();
else
return -1;
}
int v = -1;
while (!bs.test(v = rand() % N));
bs.reset(v);
return v;
}
};
int main() {
// int n = rand() % 10;
// int n2 = rand() % 10;
URandom<10> r(true);
for (int i = 0; i < 15; i++)
cout << r() << endl;
}
pair, make_pair
#include "show.h"
/*
Point x, y
Complex a, b
template<typename T, typename U> struct pair
{
T first;
U second;
};
pair : c++ 98
tuple : c++ 11
*/
pair<int, double> foo(pair<int, double> p)
{
return p;
}
int main()
{
pair<int, double> p(1, 3.4);
foo(p);
foo(pair<int, double>(1, 3.4)); // 임시 객체 전달 시. 다만 복잡함.
foo(make_pair(1, 3.4)); // 함수 탬플릿으로 클래스 탬플릿 객체 생성 !!!
pair<int, pair<int, int>> p3;
pair<int, pair<int, pair<int, int>>> p4; // pair는 임의의 개수를 저장 가능... 다만 복잡함.
//make_tuple(); 튜플도 있다.
return 0;
}
3가지 스트림
#include <iostream> // 표준 입출력
#include <fstream> // 파일 입출력
#include <sstream> // 메모리 스트림
#include <string>
/*
c++에는 스트림이 3개
표준, io, sstream
*/
using namespace std;
/*
int main()
{
string s;
//cin >> s; // 표준 입력
ifstream fin("10_스트림.cpp");
//fin >> s; // 파일 입력
istringstream iss("i am a boy"); // 메모리 스트림
while(iss >> s)
cout << s << endl;
return 0;
}
*/
int main()
{
int n = 10;
//cout << "n = " << n;
ostringstream oss;
oss << "n = " << n; // C에서는 sprintf 사용. sprinf는 메모리 넘침 문제 있음.
string s = oss.str();
cout << s << endl;
}
[예외 안정성] 보장
// STL은 제거와 리턴을 동시에 하는 함수는 없다.
#include "show.h"
/*
exception safety
1. 완전 보장 : 예외가 없다. int a = 0; (절대 실패 할 수 없음. H/W 실패가 아니라면..)
2. 강력 보장 : 예외가 있어도 처리하면 객체는 이전 상태를 보장한다. 사용가능. (아래 stack은 강력 보장 안됨)
3. 기본 보장 : 예외가 있어도 자원 누수는 없다. 단, 객체의 상태는 보장 안됨. 사용 불가.
STL의 설계 철학은 [2. 강력 보장이다]. 아래 stack은 2를 지키지 못한다.
*/
template<typename T> class stack
{
T buff[10];
int index;
public:
stack() : index(0) {}
void push(const T&a) { buff[index++] = a; }
T pop() {
--index;
if (index < 0)
throw 1;
return buff[index];
}
};
int main() {
stack<int> s;
try {
s.pop();
} catch (int n) {
}
/* exception 발생해도 프로그램은 계속 실행 가능.
그런데 s객체를 사용할 수는 없음...
s의 index는 이미 --해서 잘못된 값임. s를 다시 쓸 수가 없음.
*/
}
// [예외 안정성] 보장
#include "show.h"
// 아래도 강력 보장이 안됨.
template<typename T> class stack
{
T buff[10];
int index;
public:
stack() : index(0) {}
void push(const T&a) { buff[index++] = a; }
T pop() {
if (index < 1)
throw 1;
--index;
return buff[index];
}
};
int main() {
stack<People> s;
People p;
s.push(p);
try {
// People p2 = s.pop(); // 이 코드는 실제 2가지가 실행됨.
// 1. pop() 실행 - 문제 없음.
// 2. People의 복사 생성자 실행. 복사 생성자 과정에서 메모리 문제로 실패 하면 ?
// 실제 쓰지 못한 상황이지만, stack에서 항목은 사라짐.
// 때문에 아래와 같이 바꿈.
People p3 = s.top(); // 복사
s.pop(); // 제거
}
catch (... n) {
}
}
예제
#include "show.h"
#include <map>
#include <sstream>
#include <fstream>
using namespace std;
// 인덱스 만드는 프로그램.
// readme.txt
int main()
{
ifstream fin("readme.txt");
string s;
int line = 0;
typedef map<string, list<int>> IndexMap;
IndexMap index;
while (getline(fin, s)) {
++line;
istringstream iss(s);
string word;
while (iss >> word) {
index[word].push_back(line);
}
}
// map 내용 출력
ostream_iterator<int> out(cout, ", ");
IndexMap::iterator p = index.begin();
while (p != index.end()) {
cout << p->first << " : ";
copy(p->second.begin(), p->second.end(), out);
cout << endl;
++p;
}
}
예제2
#include "show.h"
#include <map>
using namespace std::placeholders;
void foo(void* p) { cout << "foo" << (int)p << endl; }
void goo(void* p, int n) { cout << "goo" << (int)p << endl; }
class CallbackTable {
typedef function<void(void*)> HANDLER;
map<string, vector<HANDLER>> table;
public:
void Register(string ev, HANDLER h) {
table[ev].push_back(h);
}
void Fire(string ev, void *data) {
vector<HANDLER> &v = table[ev];
for (int i = 0; i < v.size(); i++)
v[i](data);
}
};
int main()
{
CallbackTable ct;
ct.Register("DISCONNECT_WIFI", &foo);
ct.Register("DISCONNECT_WIFI", bind(&goo, _1, 0));
ct.Register("LOW_POWER", &foo);
ct.Fire("DISCONNECT_WIFI", (void*)30); // 등록된 함수의 인자로 30 전달.
}
댓글 없음:
댓글 쓰기