2016년 7월 7일 목요일

C++ STL 수업 - 2일차

컨테이너의 저장 타입

컨테이너는 아래 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 전달.
}

댓글 없음:

댓글 쓰기