[전공]

[명품 C++] 10 템플릿과 표준 템플릿 라이브러리 STL

danhan 2022. 6. 6. 17:49

출처 :

  • 명품 C++ Programming (저자 황기태)
  • 객체지향프로그래밍

일반화와 템플릿

  • 제네릭 또는 일반화
    • 함수나 클래스를 일반화시키고, 매개 변수 타입을 지정하여 틀에서 찍어내듯이 함수나 클래스 코드를 생산하는 기법
  • 템플릿
    • 변수나 매개 변수의 타입만 다르고, 코드 부분이 동일한 함수를 일반화 시킴
    • 함수나 클래스를 일반화하는 도구
    • template 키워드 사용
    • 제네릭 타입 - 일반화를 위한 데이터 타입
    template <class T>
    template <typename T>
    
    template <class T>
    void myswap (T & a, T & b) {
    	T tmp;
    	tmp = a;
    	a = b;
    	b = tmp;
    }
    

템플릿으로부터의 구체와

  • 구체화 - 템플릿의 제네릭 타입에 구체적인 타입 지정
  • 템플릿 함수로부터 구체화된 함수의 소스 코드 생성
  • 컴파일 될 때 T에 int를 대입하여 구체화된 소스 코드 생성(구체화) 후 실행

제네릭 myswap() 함수 만들기

#include <iostream>
using namespace std;

class Circle {
	int radius;
public:
	Circle(int radius=1) { this->radius = radius; }
	int getRadius() { return radius; }
};

template <class T>
// 자료형이 T로 같기 때문에
// myswap(int &, double &) 처럼 다르게 구체화할 수 없다.
void myswap(T & a, T & b) {
	T tmp;
	tmp = a;
	a = b;
	b = tmp;
}

int main() {
	int a=4, b=5;
	myswap(a, b);
	cout << "a=" << a << ", " << "b=" << b << endl;

	double c=0.3, d=12.5;
	myswap(c, d);
	cout << "c=" << c << ", " << "d=" << d << endl;

	Circle donut(5), pizza(20);
	myswap(donut, pizza);
	cout << "donut반지름=" << donut.getRadius() << ", ";
	cout << "pizza반지름=" << pizza.getRadius()<< endl;
}

구체화 오류

  • 매개 변수의 제네릭 타입이 동일해야 한다.

템플릿 장점과 제네릭 프로그래밍

  • 장점
    • 함수 코드의 재사용
  • 단점
    • 컴파일러에 따라 지원하지 않을 수 있음
    • 컴파일 오류 메시지가 빈약해 디버깅이 어려움
  • 제네릭 프로그래밍 - 다른 언어에서도 활용되고 있다.

제네릭 add() 함수 연습

#include <iostream>
using namespace std;

template <class T>
T add(T data [], int n) { // 배열 data에서 n개의 원소를 합한 결과를 리턴
	T sum = 0;
	for(int i=0; i<n; i++) { sum += data[i]; }
	return sum;             // sum와 타입과 리턴 타입이 모두 T로 선언되어 있음
}
int main() {
	int x[] = {1,2,3,4,5};
	double d[] = {1.2, 2.3, 3.4, 4.5, 5.6, 6.7};
	cout << "sum of x[] = " << add(x, 5) << endl; // x는 int로 구체화
	cout << "sum of d[] = " << add(d, 6) << endl; // d는 double로 구체화
}

제네릭 mcopy()

#include <iostream>
using namespace std;

// 두 개의 제네릭 타입 T1, T2를 가지는 copy()의 템플릿
// T1과 T2의 자료형은 다를 수 있다.
template <class T1, class T2>
// src[]의 n개 원소를 dest[]에 복사하는 함수
void mcopy(T1 src [], T2 dest [], int n) { 
	for(int i=0; i<n; i++)
		// T1 타입의 값을 T2 타입으로 변환한다.
		dest[i] = (T2)src[i]; 
}

int main() {
	int x[] = {1,2,3,4,5};
	double d[5];
	char c[5] = {'H', 'e', 'l', 'l', 'o'}, e[5];

	mcopy(x, d, 5); // int x[]의 원소 5개를 double d[]에 복사
	mcopy(c, e, 5); // char c[]의 원소 5개를 char e[]에 복사

	for(int i=0; i<5; i++) cout << d[i] << ' '; // d[] 출력
		cout << endl; // 1 2 3 4 5
	for(int i=0; i<5; i++) cout << e[i] << ' '; // e[] 출력
		cout << endl; // H e l l o
}

템플릿 함수보다 중복 함수가 우선

#include <iostream>
using namespace std;

template <class T>
void print(T array [], int n) {
	for(int i=0; i<n; i++)
		cout << array[i] << '\\t';
	cout << endl;
}

// char 배열을 출력하기 위한 함수 중복
// 중복된 print() 함수가 우선 바인딩
void print(char array [], int n) { 
	for(int i=0; i<n; i++)
		// array[i]를 int 타입으로 변환하여 정수 출력
		cout << (int)array[i] << '\\t'; 
	cout << endl;
}

int main() {
	int x[] = {1,2,3,4,5};
	double d[5] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
	print(x, 5);  // 템플릿으로 구체화한 함수 호출
	print(d, 5);  // 템플릿으로 구체화한 함수 호출

	char c[5] = {1,2,3,4,5};
	print(c, 5);  // char 배열을 숫자로 출력하는 중복 함수 호출
}

제네릭 클래스 만들기

  • 제네릭 클래스 선언부와 구현부를 모두 template으로 선언
#include <iostream>
using namespace std;
	
template <class T>
class MyStack {
	int tos;// top of stack
	T data [100]; // T 타입의 배열. 스택의 크기는 100
public:
	MyStack();
	void push(T element); // element를 data [] 배열에 삽입
	T pop(); // 스택의 탑에 있는 데이터를 data[] 배열에서 리턴
};

template <class T>
MyStack<T>::MyStack() { // 생성자
tos = -1; // 스택은 비어 있음
}

template <class T>
void MyStack<T>::push(T element) {
	if(tos == 99) {
		cout << "stack full";
		return;
	}
	tos++;
	data[tos] = element;
	}

template <class T>
T MyStack<T>::pop() {
	T retData;
	if(tos == -1) {
		cout << "stack empty";
		return 0; // 오류 표시
	}
	retData = data[tos--];
	return retData;
}

int main() {
	MyStack<int> iStack; 
	iStack.push(3);
	cout << iStack.pop() << endl;

	MyStack<double> dStack; 
	dStack.push(3.5);
	cout << dStack.pop() << endl;

	MyStack<char> *p = new MyStack<char>();
	p->push('a');
	cout << p->pop() << endl;
	delete p;
}

C++ 표준 템플릿 라이브러리 STL 활용

  • Standard Tempate Library
  • STL 구성
    • 컨테이너
      • 데이터를 담아두는 자료 구조를 표현한 클래스
      • 리스트, 큐, 스택, 맵, 셋 벡터
    • iterator (포인터)
      • 컨테이너 원소에 대한 포인터
      • 컨테이너의 원소들을 순회하면서 접근하기 위해 만들어짐
    • 알고리즘
      • 복사, 검색, 삭제, 정렬 등의 기능을 구현한 템플리 ㅅ함수
      • 컨테이너의 멤버 함수가 아님

vector 컨테이너

  • 가변 길이 배열을 구현한 제네릭 클래스
  • 원소의 저장, 삭제, 검색 등 지원
  • 벅터에 저장된 원소 인덱스로 접근 가능
#include <iostream>
#include <vector>
using namespace std;

int main() {
	vector<int> v; // 정수만 삽입 가능한 벡터 생성
	v.push_back(1); // 벡터에 정수 1 삽입
	v.push_back(2); // 벡터에 정수 2 삽입
	v.push_back(3); // 벡터에 정수 3 삽입
	
	for(int i=0; i<v.size(); i++)
		cout << v[i] << " "; // 1 2 3
	cout << endl;
	
	v[0] = 10; // 벡터의 첫 번째 원소를 10으로 변경
	int n = v[2]; // n에 3이 저장
	v.at(2) = 5; // 벡터의 3 번째 원소를 5로 변경
	
	for(int i=0; i<v.size(); i++)
		cout << v[i] << " "; // 10 2 5
	cout << endl;
}

iterator 사용

  • 컨테이너의 원소를 가리키는 포인터
  • 반복자
// vevtor<int>의 iterator다~ 라고 구체적으로 컨테이너 지정
vector<int>::iterator it;
it = v.begin();

iterator 예제

#include <iostream>
#include <vector>
using namespace std;

int main() {
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);

	vector<int>::iterator it; // 벡터 v의 원소에 대한 포인터 it 선언

	for(it=v.begin(); it != v.end(); it++) {
		int n = *it; // it가 가리키는 원소 값 리턴
		n = n*2;
		*it = n;     // it가 가리키는 원소에 값 쓰기
	}

	for(it=v.begin(); it != v.end(); it++)
		cout << *it << ' ';
	cout << endl;
}

map 컨테이너

  • #include <map>
  • (키, 값)의 쌍을 원소로 저장하는 제네릭 컨테이너
  • 키로 값 검색

주요 멤버

  • inser(pair<> &element
  • at(key_type& key)
  • begin()
  • end()
  • empty()
  • find(key_type& key)
  • erase(iterator it)
  • size()
  • operator[key_type & key] ()
  • operator=()

map으로 영한 사전 만들기

#include <iostream>
#include <string>
#include <map>
using namespace std;

int main() {
	map<string, string> dic;
	dic.insert(make_pair("love", "사랑"));
	dic.insert(make_pair("apple", "사과"));
	dic["cherry"] = "체리";

	cout << "저장된 단어 개수 " << dic.size() << endl;

	string eng;
	while (true) {
		cout << "찾고 싶은 단어>> ";
		getline(cin, eng); // 사용자로부터 키 입력

		if (eng == "exit")
			break;

		if(dic.find(eng) == dic.end()) // eng '키'를 끝까지 찾았는데 없음
			cout << "없음" << endl;
		else
			cout << dic[eng] << endl; // dic에서 eng의 값을 찾아 출력
	}
	cout << "종료합니다..." << endl;
}

STL 알고리즘 사용하기

  • 템플릿 함수
  • 전역함수 - STL 컨테이너 클래스의 멤버 함수가 아님
  • iterator와 함께 작동

sort()

  • sort(시작 원소의 주소, 마지막 원소의 “다음” 주소)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
	vector<int> v; // 정수 벡터 생성
	cout << "5개의 정수를 입력하세요>> ";
	for(int i=0; i<5; i++) {
		int n;
		cin >> n;
			v.push_back(n); // 키보드에서 읽은 정수를 벡터에 삽입
	}
	// v.begin()에서 v.end() 사이의 값을 오름차순으로 정렬
	// sort() 함수의 실행 결과 벡터 v의 원소 순서가 변경됨
	sort(v.begin(), v.end());

	vector<int>::iterator it; // 벡터 내의 원소를 탐색하는 iterator 변수 선언

	for(it=v.begin(); it != v.end(); it++) // 벡터 v의 모든 원소 출력
		cout << *it << ' ';
	cout << endl;
}

auto, 람다는 시험 안나옴