공부 중 메모/C++ STL(Book : 뇌를 자극하는 STL)

3_STL basic

라이피 (Lypi) 2018. 7. 29. 02:06
반응형
#pragma region //include 구문

//include 구문
#include <iostream>
using std::cout;
using std::endl;
using std::cin;

using std::reverse_iterator;
using std::allocator;

#include <string>
using std::string;

#include <vector>
using std::vector;

#include <deque>
using std::deque;

#include <list>
using std::list;

#include <set>
using std::set;

#include <stack>
using std::stack;

#include <algorithm>
using std::find;
using std::sort;

#include <functional>
using std::less;
using std::greater;
using std::not2;


#pragma endregion

//container_ex, Iterator_ex, RAI_vector, RAI_deque, find_ex, solt_ex, sort_add_functor, container_adaptor, iterator_adaptor, function_adaptor, allocator_ex
#define iterator_adaptor

//1. STL은 프로그램에 필요한 자료구조와 알고리즘을 템플릿으로 제공하는 라이브러리이다.
//2. STL은 자료구조와 알고리즘을 반복자라는 구성요소를 통해 연결한다.
//
//3. STL의 구성요소들
// a. 컨테이너(Container)         : 객체를 저장하는 객체. 컬렉션 혹은 자료구조라고도 한다.
// b. 반복자(Iterator)            : 컨테이너의 원소를 가리키고, 가리키는 원소에 접근하여 다음 원소를 가리키게 하는 포인터와 비슷한 것.
// c. 알고리즘(Algorithm)         : 정렬, 삭제, 검색, 연산 등을 해결하는 일반화된 방법을 제공하는 함수 템플릿.
// d. 함수 객체(Function Object)  : 함수처럼 동작하는 객체. 컨테이너와 알고리즘 등에 클라이언트의 정책을 반영하게 한다.
// e. 어댑터(Adaptor)             : 구성 요소의 인터페이스를 변경해 새로운 인터페이스를 갖는 구성 요소로 변경한다. (새로운 구성 요소처럼 보임)
// f. 할당기(Allocator)           : 컨테이너의 메모리 할당 정책을 캡슐화한 클래스 객체. 모든 컨테이너는 자신만의 기본 할당기를 갖고 있다. 
//
//4. STL의 세 가지 특징 : 효율성, 일반화 프로그램(재사용성), 확장성


//A. 컨테이너 :
// 같은 타입을 저장, 관리할 목적으로 만들어진 클래스.
// 각각의 컨테이너들은 성능이나 메모리 사용, 지원 인터페이스 등에 큰 차이를 보이며 서로 다른 특징을 갖는다.
//
//A-1. 컨테이너의 종류
// a. 표준 시퀀스 컨테이너(standard sequence container)   : 삽입된 순서에 따라 원소의 위치가 정해지는 컨테이너                         (선형적   :: vector, deque, list)
// b. 표준 연관 컨테이너(standard associative container)  : 삽입된 순서와 별도로 특정 정렬 기준에 의해 원소가 자동 정렬되는 컨테이너.  (비선형적 :: set, multiset, map, multimap)
//
// c. 배열 기반 컨테이너(array-based container)  : 여러개의 데이터를 하나의 메모리 단위에 저장함. (vector, deque)
// d. 노드 기반 컨테이너(node-based container)   : 하나의 데이터를 하나의 메모리 단위에 저장함.   (list, set, multiset, map, multimap)

//A-a. 근사 컨테이너(almost container) : 컨테이너와 비슷하지만 표준 컨테이너의 요구사항을 지키지 못하는 것. (내장 배열, valarray, string 등)

#ifdef container_ex

int main()
{
	//정수를 저장하는 벡터 컨테이너 생성
	vector<int> iv;

	for (int i = 0; i < 5; i++) {
		iv.push_back(i+1);
	}

	for (int i = 0; i < iv.size(); i++) {
		cout << iv[i] << ' ';
	}
}

#endif


//B. 반복자 : 
// 컨테이너에 저장된 원소를 순회하고 접근화는 일반화된 방법을 제공하는 것. 
// 컨테이너와 알고리즘이 하나로 동작하게 묶어주는 인터페이스의 역할을 수행.           
// 알고리즘이 특정 컨테이너에 종속적이지 않고 독립적이면서도 언제든지 컨테이너와 결합하여 동작할 수 있게 해줌.
//
// B-1. 반복자의 특징
//  a. 반복자는 컨테이너 내부의 원소를 가리키고 접근할 수 있어야 한다. (* 연산자 오버로딩)
//  b. 반복자는 다음 원소로 이동하고 컨테이너의 모든 원소를 순회할 수 있어야 한다. (++, !=, == 연산자 오버로딩)
//
//   순차열(sequence) : 컨테이너 원소(객체)의 집합
//   반복자는 순차열의 한 원소를 가리키게 된다.
//   STL의 모든 컨테이너는 자신만의 반복자를 제공하며,
//   bigin() 메소드는 순차열의 시작을 가리키는 반복자를 반환하고,
//   end() 메소드는 순차열의 끝을 가리키는 반복자를 반환한다.
//
//   이때, 순차열의 시작 원소는 데이터가 저장된 실제 원소지만, 순차열의 끝 원소는 데이터가 저장되지 않은 가상의 원소이다.
//   begin에서 end까지를 구간이라 하며, 수학적으로 반개구간(harf-open range)이므로 [begin,end)와 같이 표시한다.
//   구간 [begin,end)뿐만 아니라, 구간 [begin,iter)와 [iter, end)도 모두 순차열이다.
//   순차열 [p,q)에서 p와 q가 같으면 (순차열의 시작과 끝이 같으면) 이 구간의 원소는 없다.

#ifdef Iterator_ex

int main()
{
	//정수를 저장하는 벡터 컨테이너 생성
	vector<int> iv;

	for (int i = 0; i < 5; i++) {
		iv.push_back(i+1);
	}

	vector<int>::iterator iter; // 반복자 생성
	//모든 컨테이너의 반복자 클래스는 내포 클래스나 typedef 타입.

	for (iter = iv.begin(); iter != iv.end(); iter++) {
		cout << *iter << ' ';  //반복자가 가리키는 원소를 역참조
	}

}

#endif

//  c. 반복자의 종류
//   1) 입력 반복자(input iterator) : 현 위치의 원소를 한 번만 읽을 수 있는 반복자                            (istream)
//   2) 출력 반복자(output iterator) : 현 위치의 원소를 한 번만 쓸 수 있는 반복자                             (ostream)
//   3) 순방향 반복자(forward iterator) : 입출력 반복자 기능에 순방향으로 이동(++)이 추가된 반복자
//   4) 양방향 반복자(bidirectional iterator) : 순방향 반복자 기능에 역방향으로 이동(--)이 추가된 반복자      (list, set, multiset, map, multimap)
//   5) 임의 접근 반복자(random access iterator) : 양방향 반복자 기능에 +,-,+=, -=, [] 연산이 추가된 반복자.  (vector, deque)


#ifdef RAI_vector

int main()
{
	vector<int> iv;

	for (int i = 0; i < 5; i++) {
		iv.push_back(i+1);
	}

	vector<int>::iterator iter = iv.begin(); 
	
	for (int i = 0; i < iv.size(); i++) {
		cout << iter[i] << ' ';
	}
	cout << endl;

	iter += 2; 
	cout << *iter << endl;

	vector<int>::iterator iter2 = iter + 2;
	cout << *iter2 << endl;
}

#endif

#ifdef RAI_deque

int main()
{
	deque<int> dq;

	for (int i = 0; i < 5; i++) {
		dq.push_back(i + 1);
	}

	deque<int>::iterator iter = dq.begin();

	for (int i = 0; i < dq.size(); i++) {
		cout << iter[i] << ' ';
	}
	cout << endl;

	iter += 2;
	cout << *iter << endl;

	deque<int>::iterator iter2 = iter + 2;
	cout << *iter2 << endl;
}

#endif

//C. 알고리즘 :
// 알고리즘은 순차열의 원소를 조사, 변경, 관리, 처리할 목적으로 제공됨.
// 알고리즘은 한 쌍의 반복자([begin, end))를 필요로 함.
// 대부분의 알고리즘은 순방향 반복자를 요구하지만, 몇몇 알고리즘은 임의 접근 반복자를 요구함.
//
//C-1. 알고리즘의 범주
// a. 원소를 수정하지 않는 알고리즘(nonmodifying algorithms)
// b. 원소를 수정하는 알고리즘(modifying algorithms)
// c. 제거 알고리즘(removing algorithms)
// d. 변경 알고리즘(mutating algorithms)
// e. 정렬된 범위 알고리즘(sorted range algorithms)
// f. 수치 알고리즘(numeric algorithms)

//대부분의 STL의 알고리즘은 특정 컨테이너나 원소 타입에 종속적이지 않다. (일반적이다.)
#ifdef find_ex

int main()
{
	vector<int> iv;

	for (int i = 0; i < 5; i++) {
		iv.push_back(i + 1);
	}

	vector<int>::iterator iter = iv.begin();

	iter = find(iv.begin(), iv.end(), 2); 
	if (iter == iv.end()) {
		cout << "찾는 수가 없음" << endl;
	}
	else {
		cout << *iter << endl;
	}
	

	iter = find(iv.begin(), iv.end(), 10);
	if (iter == iv.end()) {
		cout << "찾는 수가 없음" << endl;
	}
	else {
		cout << *iter << endl;
	}

	deque<char> dq;

	for (int i = 'a'; i < 'f'; i++) {
		dq.push_back(i);
	}

	deque<char>::iterator iter_2 = dq.begin();

	iter_2 = find(dq.begin(), dq.end(), 'a');
	if (iter_2 == dq.end()) {
		cout << "찾는 문자가 없음" << endl;
	}
	else {
		cout << *iter_2 << endl;
	}


	iter_2 = find(dq.begin(), dq.end(), 'g');
	if (iter_2 == dq.end()) {
		cout << "찾는 문자가 없음" << endl;
	}
	else {
		cout << *iter_2 << endl;
	}


	list<short> li;

	for (int i = 11; i < 15; i++) {
		li.push_back(i);
	}

	list<short>::iterator iter_3 = li.begin();

	iter_3 = find(li.begin(), li.end(), 12);
	if (iter_3 == li.end()) {
		cout << "찾는 수가 없음" << endl;
	}
	else {
		cout << *iter_3 << endl;
	}


	iter_3 = find(li.begin(), li.end(), 15);
	if (iter_3 == li.end()) {
		cout << "찾는 수가 없음" << endl;
	}
	else {
		cout << *iter_3 << endl;
	}
}

#endif

//sort 알고리즘은 임의 접근 반복자를 요구한다. 
#ifdef sort_ex

int main()
{
	//vector의 경우
	vector<int> iv;
	for (int i = 0; i < 5; i++) { iv.push_back(i + 1); }
	sort(iv.begin(), iv.end()); //정렬 가능   (임의 접근 반복자)


	//deque의 경우
	deque<char> dq;
	for (int i = 'a'; i < 'f'; i++) { dq.push_back(i); }
	sort(dq.begin(), dq.end()); //정렬 가능   (임의 접근 반복자)
	

	//list의 경우
	list<short> li;
	for (int i = 11; i < 15; i++) { li.push_back(i); }
	//sort(li.begin(), li.end()); //컴파일 에러 (양방향 반복자)
}

#endif

//STL에서 함수 객체는 클라이언트가 정의한 동작을 다른 구성 요소에 반영하려 할 때 사용된다.
//많은 STL 알고리즘이 함수 객체, 함수, 함수 포인터 등을 인자로 받을 수 있다.
#ifdef sort_add_functor

int main()
{
	//vector의 경우
	vector<int> iv;
	iv.push_back(4);
	iv.push_back(2);
	iv.push_back(1);
	iv.push_back(5);
	iv.push_back(3);

	vector<int>::iterator iter;

	//sort(iv.begin(), iv.end());와 sort(iv.begin(), iv.end(), less<int>());의 결과는 같다.
	sort(iv.begin(), iv.end(), less<int>()); //오름차순 정렬 	 
	for (iter = iv.begin(); iter != iv.end(); iter++) {
		cout << *iter << " ";
	}
	cout << endl;

	sort(iv.begin(), iv.end(), greater<int>()); //오름차순 정렬
	for (iter = iv.begin(); iter != iv.end(); iter++) {
		cout << *iter << " ";
	}
	cout << endl;

}

#endif

//D. 어댑터 :
// 구성 요소의 인터페이스를 변경.
//
//D-1. 어댑터의 종류
// a. 컨테이너 어댑터(container adaptor)  : stack, queue, priority_queue
// b. 반복자 어댑터(iterator adaptor)     : reverse_iterator, back_insert_iterator, front_insert_iterator, insert_iterator
// d. 함수 어댑터(function adaptor)       : binder(바인더), negator(부정자), adaptor for pointers to functions(함수 포인터 어댑터)
//
//stack 컨테이너 어댑터를 이용한 정수 입출력 예제
//모든 시퀀스 컨테이너는 empty, size, push_back, pop_back, back 인터페이스(메소드)를 가지므로 stack의 컨테이너로 사용될 수 있다.
#ifdef container_adaptor

int main()
{
	//stack<int> st  : 디폴트 컨테이너 deque를 사용해 stack 컨테이너 객체 st를 생성.
	stack<int> st;

	for (int i = 0; i < 3; i++) {
		st.push(i);
	}

	for (int i = 0; i < 5; i++) {
		if (st.empty()) {
			cout << "stack에 데이터 없음" << endl;
		}
		else {
			cout << st.top() << endl; st.pop();
		}
	}



	//stack<int, vector<int>> vst : vector 컨테이너를 사용해 stack 컨테이너 객체 st를 생성.
	stack<int, vector<int>> vst; 

	for (int i = 0; i < 3; i++) {
		vst.push(i);
	}

	for (int i = 0; i < 5; i++) {
		if (vst.empty()) {
			cout << "stack에 데이터 없음" << endl;
		}
		else {
			cout << vst.top() << endl; vst.pop();
		}
	}
}

//st.push(x)     : stack에 데이터 x를 입력
//st.top()       : stack의 top에 있는 데이터를 반환
//st.pop()       : stack의 top에 있는 데이터를 삭제
//st.empty()     : stack이 비어있는지를 판단

#endif

//역방향 반복자 예제. (반복자 어댑터 예제)
//컨테이너가 가리키는 범위는 반개구간이므로 순방향 반복자로 역방향 순회를 하는건 불편하다.
//그러므로 역방향 순회가 필요할 땐 역방향 반복자를 쓰는게 좋다.
//각각의 컨테이너는 순방향 반복자와 함께 역방향 반복자도 같이 갖고 있다.
//
//역방향 반복자가 있으므로 ++ 연산자로 정방향과 역방향 순회가 모두 가능하므로,
//그러므로 STL의 알고리즘이 ++ 연산자만으로 구현되어있어도 문제가 없다.
#ifdef iterator_adaptor

int main()
{
	vector<int> v;

	for (int i = 0; i < 5; i++) {
		v.push_back(i);
	}

	vector<int>::iterator iter = v.begin();
	for (; iter != v.end(); iter++) {
		cout << *iter << " ";
	}
	cout << endl;

	//iterator를 reverse_iterator로 변환 (순방향 반복자를 역방향 반복자로 변환)
	reverse_iterator<vector<int>::iterator> riter(v.end());
	reverse_iterator<vector<int>::iterator> end_riter(v.begin());

	for (; riter != end_riter; riter++) {
		cout << *riter << " ";
	}
	cout << endl;

	//reverse_iterator를 선언하는 다른 방법 (원래 컨테이너 안에 있는 역방향 반복자를 사용)
	vector<int>::reverse_iterator riter_b(v.rbegin()); // riter_b = v.rbegin() 이라 써도 같다.
	
	for (; riter_b != v.rend(); riter_b++) {
		cout << *riter_b << " ";
	}
	cout << endl;

	//순방향 반복자로 역방향 순회를 한 경우
	vector<int>::iterator reverse;

	for (reverse = v.end() - 1;; reverse--) {
		cout << *reverse << " ";
		if (reverse == v.begin()) { break; }
	}
	cout << endl;

	//정방향 반복자와 역방향 반복자는 서로 가리키는 값이 다르다.
	vector<int>::iterator forward = v.begin() + 3; //정방향 반복자
	vector<int>::reverse_iterator backward(forward); //를 역방향 반복자로 변환

	cout << "정방향 반복자의 값 : " << *forward << endl;
	cout << "역방향 반복자의 값 : " << *backward << endl;
	cout << endl;
}

//v.begin()  : 컨테이너의 첫 원소를 가리키는 반복자를 반환
//v.end()    : 컨테이너의 마지막 원소 다음 위치를 가리키는 반복자를 반환
//v.rbegin() : 컨테이너의 마지막 원소를 가리키는 역방향 반복자를 반환
//v.rend()   : 컨테이너의 첫 원소의 이전 위치를 가리키는 역방향 반복자를 반환

#endif

//함수 어댑터 예제
//not2() : 2개의 인자를 받아 bool값을 반환하는 함수 객체의 반환값을 반대로 바꿔주는 함수 어댑터
#ifdef function_adaptor

int main()
{
	cout << "less<int>()(...)" << endl;
	cout << less<int>() (10, 20) << endl;
	cout << less<int>() (20, 20) << endl;
	cout << less<int>() (20, 10) << endl;
	cout << endl;

	cout << "not2(less<int>())(...)" << endl;
	cout << not2(less<int>())(10, 20) << endl;
	cout << not2(less<int>())(20, 20) << endl;
	cout << not2(less<int>())(20, 10) << endl;

	cout << "less<int> L" << endl << endl;;
	less<int> L;
	

	cout << "L(...)" << endl;
	cout <<L(10, 20) << endl;
	cout <<L(20, 20) << endl;
	cout <<L(20, 10) << endl;
	cout << endl;

	cout << "not2(L)(...)" << endl;
	cout << not2(L)(10, 20) << endl;
	cout << not2(L)(20, 20) << endl;
	cout << not2(L)(20, 10) << endl;

}

#endif

//E. 할당기 :
// 컨테이너의 메모리 할당 정보와 정책(메모리 할당 모델)을 캡슐화한 STL 구성 요소.
// 할당기는 템플릿 클래스이며 모든 컨테이너는 기본 할당기를 사용한다.
//
// C++에서 동적 메모리 할당 연산자를 오버로딩해서 쓸 수 있듯이 STL의 할당기도 직접 정의해서 사용할 수 있다. 
// 사용자 정의 할당기는 사용자가 직접 메모리 할당 방식을 제어할 필요가 있을때 사용한다.
// 다중 스레드에 최적화되고 안전한 사용자 메모리 할당 모델이 필요하거나
// 사용자가 컨테이너에 맞는 메모리 할당 모델을 설계하거나 
// 특정 구현 환경(implementation)에서 최적화된 메모리 할당 모델을 구축할 때 사용한다.
// 대부분의 프로그램에서는 기본 할당기로 충분하므로 사용자 정의 할당기에 대한 내용은 다른 책을 참고할 것.
//
// 모든 컨테이너는 템플릿 메개변수에 할당기를 인자로 받는다. 
// 기본 할당기는 allocator<T>이며 컨테이너는 템플릿 매개변수에 디폴트 매개변수 값으로 기본 할당기를 지정한다.
// ex) vector라면 vector<typename T, typename Alloc = allocator<T>>, set이라면 set<typename T, typename Pred = less<T>, Typename Alloc = allocator<T>
//
// 할당기를 직접 지정한 예
#ifdef allocator_ex

int main()
{
	//vector<저장타입, 할당기>
	vector<int, allocator<int>> v; // = vector<int>와 같음
	v.push_back(10);
	cout << *v.begin() << endl;

	//set<저장타입, 정렬기준, 할당기>
	set<int, less<int>, allocator<int>> s; // = set<int>와 같음
	s.insert(10);
	cout << *s.begin() << endl;
}

#endif

//문제풀이_1
// 1) STL 구성 요소에서 객체들을 저장하는 객체를 컨테이너라 한다.
// 2) 컨테이너의 원소를 순회하고 참조하는 객체를 반복자라 한다.
// 3) 문제 해결을 위한 일반적인 방법을 반복자를 통해 제공하여 모든 컨테이너에 사용가능한 함수 템플릿을 알고리즘이라 한다.

//문제풀이_2
// 1) 삽입 순서에 따라 원소의 위치가 결정되는 컨테이너를 시퀀스 컨테이너라 한다.
// 2) 컨테이너에 따라서 원소가 자동 정렬되는 컨테이너를 연관 컨테이너라 한다.

//문제풀이_3
// 1) 배열 기반 컨테이너인 vector와 deque는 임의 접근 반복자를 제공하며, 
//    그외 모든 STL 컨테이너는 양방향 반복자를 제공한다.
// 2) 순차열(시퀀스)은 원소의 순서 있는 집합을 의미하며, 이 순차열은 반복자 쌍(구간)으로 표현한다.

//문제풀이_4
//  A(begin) -> B -> C(iter) -> D -> E(end) 일 때, 
//  순차열 [begin, end)의 원소 : A,B,C,D
//  순차열 [begin, iter)의 원소 : A,B
//  순차열 [iter, end)의 원소 : C,D

//문제풀이_5
//  양방향 반복자가 지원하는 연산자 : *(읽고 쓰기), ++(순방향이동), --(역방향 이동), = (대입 연산), ==, !=, >, < (비교 연산) 
//  임의 접근 반복자에서는 지원하는데 양방향 반복자에서는 지원하지 않는 연산자 : [](인덱스 연산), +, - (임의 위치 이동), +=, -= (임의 위치 이동 후 대입) 

//문제풀이_6
//  1) STL 컨테이너는 자신이 지원하는 반복자를 반환하기 위한 메소드로 begin()과 end()를 제공한다.
//     begin() : 시작 원소를 가리키는 반복자를 반환, end() : 끝 위치를 가리키는 반복자를 반환
//  2) 반복자가 가리키는 원소를 참조할 때는 * 연산자를 사용한다.

//문제풀이_7
//  1) 어댑터는 구성 요소의 인터페이스를 변경한다.
//  2) stack, queue, priority_queue는 컨테이너 어댑터라 하며, 
//     reverse_iterator, insert_iterator 등을 반복자 어댑터라 한다.
//  3) 함수 어댑터에는 바인더(binder), 부정자(negator)등이 있다.


반응형