chapter 10 템플릿과 표준 템플릿 라이브러리(STL)

박준우·2025년 3월 16일

명품C++프로그래밍

목록 보기
10/10

1. 일반화와 탬플릿

탬플릿이란? type에 의한 함수중복 작성을 막고 함수타입을 통합해 기술할 수 있는 기능이다. 예를 들어 아래와 같은 함수가 있다.

void myswap(int &a, int &b){
	int tmp
    tmp = a;
    a = b;
    b = tmp;
}

void myswap(double &a, double &b){
	double tmp
    tmp = a;
    a = b;
    b = tmp;
}

위 처럼 myswap함수를 2개 만들면 정수, 실수 값이 와도 적절한 함수를 사용해 교환할 수 있지만, 코드를 2번 써야한다는 불편함이 존재한다.

이를 해결하기 위한 것이 탬플릿이다.

(1) 템플릿 사용 방법

탬플릿은 template <class T> 또는 template <typename T> 로 선언할 수 있다.

template <class T> // T라는 이름의 아무 type이나 입력가능한 탬플릿을 지정한다. 
void myswap (T &a, T &b){
	T tmp;
    tmp = a;
    a = b;
    b = tmp;
}

이렇게 만들어진 통합된 함수를 제네릭(일반화) 함수라고 한다. 제네릭 함수에 값을 입력할 때는 입력값의 type에 따라 매개변수 type을 변화(int입력시 int로처리, float입력시 float으로 처리)시켜 입력하는데 이를 구체화 라고 한다.

(2) 탬플릿 사용시 주의할 점

1. 구체화 오류

한번 구체화된 type은 함수가 끝날때 까지 그 type으로 지정되므로, 만약 2개이상의 다른 타입을 탬플릿으로 받고 싶다면, template <class T1, class T2> 처럼 탬플릿을 여러개 작성해야 한다.(같은 type이라면 상관없다,)

template <class T> 		  //<class T1, class T2> 이 처럼 바꿔주면 정상 작동한다. 
void myswap (T &a, T &b){ //<T1 &a, T2 &b>
	T tmp;
    tmp = a;
    a = b;
    b = tmp;
}
int main(){
	int s=4;
    double v=5;
    myswap(v, s); // 오류 발생, T가 int로 고정되어있어 매개변수를 받지 못한다.

2. 중복 함수 우선법칙

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

int maim() {
	int x[] = {1,2,3,4,5};
    double d[5] = {1.1, 1.2, 1.3, 1.4, 1.5};
    print(x, 5);
    print(d, 5);
    
    char c[5] = {1,2,3,4,5};
    print (C,5);
};    

위 코드에서는 탬플릿을 이용해 값을 출력하는 print함수를 만들었다.
위 함수의 문제점은 char를 입력시 T가 char이 되어, 숫자 1,2,3,4,5가 아닌 Ascci를 출력한다. 이때 중복함수를 사용하면, 중복함수가 탬플릿보다 우선되어 처리가 가능하다.

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

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

int maim() {
    char c[5] = {1,2,3,4,5};
    print (C,5);
};    

2. 제네릭 클래스 만들기

제네릭 클래스는 스택에 담을 데이터 타입을 통합하기 위해 만들어 졌다.

(1) 제레릭 클래스 사용법

제네릭 클래스 선언부

template <class T>
class myStack {
	int tos;
    T data[100];
public:
	MyStack();
    void push(T element); // T타입 원소 element를 data[]배열에 push
    T pop();// 스택의 최상위 위치 원소를 꺼낸다.
};    

제네릭 클래스 구현부

template <class T>
void myStack<T> :: push(T element){  //원소 1개 밀어넣기
	...
}

template <class T> 
T Mystack<T>::pop(){ // 원소 1개 꺼내기
	...
}

특징:
1. 탬플릿을 사용한 함수마다 탬플릿을 선언 해줘야 한다.
2. 클래스 명앞에 <T>를 사용해줘야 한다.
3.

(2) 제네릭 클래스의 구체화

제네릭 클래스를 이용하려면, 클래스의 이름과 제레릭 타입 T에 적용할 구체적 타입을 지정해야한다.

MyStack<int> iStack;
MyStack<double> dStack;

컴파일러는 이 코드를 보고, 클래스의 타입을 결정하고, 클래스의 소스를 생성한다.

(3) 제네릭 클래스 사용 예시

#include <iostream>
using namespace std;

template <class T>
class myStack{
	int tos; // 현재 위치 변수
    T data[100]; //T에 타입의 배열
public:
	myStack();  //생성자 선언
    void push(T element); // push선언
    T pop; // pop선언
 };
 
 template <class T>
 MyStack<T>::MyStack(){
 	tos = -1;
}    

template <class T>
void myStack<T>::push(T element){
	if(tos == 0){
    	cout << "stack full";
        reutrn;
    } 
    tos++;
    data[tos] = element; // tos 위치에 원소 삽입
}

template <class T>
void myStack<T>::pop(T element){    
    T retData;
    if(tos == -1){
    	cout << "stack empty";
        reutrn 0;
    } 
    
    retData = data[tos--];
    return retData;
}

int main(){
	myStack<int> iStack;	//int만 저장 가능한 iStack객체 생성
    iStack.push(3);			// 3을 집어넣는다.
    cout << iStack.pop() << endl; // 3을 출력한다.

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

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

3. 표준 탬플릿 라이브러리(STL) 활용

표준 템플릿 라이브러리 STL은 탬플릿으로 작성된 제네릭 클래스와 함수들의 라이브러리이다. STL에 포함된 제네릭 클래스와 함수들은 3종류로 분류된다.

(1) STL의 종류

1. 컨테이너 - 탬플릿 클래스

데이터를 저장하고 검색하기 위해 담아두는 자료구조를 구현한 클래스
종류: 리스트, 큐, 스택, 맵, 셋, 백터

컨테이너 클래스설명헤더파일
vector가변크기의 배열을 일반화한 클래스<vector>
deque앞 뒤 모두 입력가능한 큐 클래스<deque>
list빠른 상비과 삭제가 가능한 리스트 클래스<list>
set정렬된 순서대로 값을 저장하는 집합 클래스, 값이 unique하다.<set>
map(key, value)쌍을 저장하는 맵 클래스<map>
stack스택을 일반화한 클래스<stack>
queue큐를 일반화한 클래스<queue>

STL 컨테이너는 원소를 저장하는 방식에 따라 3가지로 분류된다.
1. 순차적 컨테이너 - vector, dequeue, list 등으로, 연속적인 메모리 공간에 순서대로 값을 저장하고 읽는 컨테이너이다.
2. 컨테이너 어뎁터 - stack, queue 등으로, 다른 컨테이너를 상속받아 기능중 일부만 사용하는 컨테이너이다.
3. 연관 컨테이너 - set, map과 같이, 키로 값을 저장하고 키로값을 알아내는 컨테이너이다.

2. iterator - 컨테이너 원소에 대한 포인터

iterator는 반복자라고도 불리며, 컨테이너의 원소들을 하나씩 순회 접근하기 위해 만들어진 컨테이너 원소에 대한 포인터이다.

iterator의 종류설명read/write
iterator다음 원소로 전진read/write
const_iterator다음 원소로 전진read
reverse_iterator지난 원소로 후진read/write
const_reverse_iterator지난 원소로 후진read

3. 알고리즘 - 템플릿 함수

컨테이너 원소에 대한 기능들을 함수로 만든 것으로 알고리즘이라고도 부른다.
종류: 복사, 검색, 삭제 정렬

(2) STL 기본 세팅

STL을 사용하기 위해서는 반드시 아래 코드가 필요하다.

using namespace std;

여기서 vector 클래스를 사용하고 싶다면 아래와 같이 include하면 된다.

#include<vector>

(3) vector 컨테이너(동적 크기 배열) 활용하기

vector는 가변길이의 배열을 구현한 제네릭 클래스로써, 내부에 배열을 가지고 원소를 저장, 삭제, 검색하는 멤버함수들을 제공한다.

1. vector 생성

vector<int> v;
vector<int> v = {1, 2, 3, 4, 5}; //백터 초기화

먼저 vector에 타입을 지정한다. 이때 한번 지정된 타입에는 그 타입만 값을 삽입할 수 있다.

2. vector에 원소 삽입

v.push_back(1)
v.push_back(2)
v.push_back(3)

백터에 원소를 삽입하려면, push_back을 사용하면 된다. 이때 push_back은 맨 뒤에 원소를 삽입하라는 명령어이다.

3. vector 원소 조회

v.at(2)=5; //v의 3번째 값을 5로 교환한다.
int n = v.at(1); // n에 v의 두번째 값을 집어넣는다.

위처럼 at을 사용하면 백터내의 원소를 조회할 수 있다.
하지만 이보다 쉽게 배열처럼 원소조회도 가능하다.

v[2]=5;
int n = v[1];

4. vector원소 개수 알아내기

for (int i; i<v.size(); i++){
	cout<< v[i] << endl;
}    

백터의 원소개수는 size 함수를 통해 확인가능하다.

5. vector 원소 삭제

백터의 원소를 삭제하기 위해서는 erase함수를 사용한다. erase함수를 사용하면, 그 위치의 원소를 삭제하고, 뒤쪽의 원소들을 앞으로 1칸씩 앞당긴다.

erase함수 사용시엔 반드시, iterator포인터와 같이 사용하여야 한다.

vector<int> :: iterator it; // it을 vector의 원소를 가리키는 포인터로 지정한다. 
it = v.begin(); 	// it은 v의 시작 위치를 가리키게한다.
it = v.erase(it);   // v의 시작위치에 담긴 값을 삭제한다. 그 후, 다음 원소를 가리키는 
					   포인터로 위치를 리턴한다. 

6. 그 외 vector 연산자

vector 연산자 함수설명
push_back(element)백터 마지막 위치에 원소 추가
at(int index)index 위치의 원소에 대한 참조 리턴
begin()백터의 첫번째 원소에 대한 참조 리턴
end()백터의 마지막 원소+1위치에 대한 참조 리턴
empty()백터가 비어있으면 true를 리턴
erase(iterator it)백터에서 it이 가리키는 원소 삭제후 자동 백터 조정
insert(iterator it, element)백터내 it위치에 원소 삽입
size()현재 백터의 원소 개수 리턴
capacity()백터의 총 용량 출력
operator[]()지정된 원소에 대한 참조 리턴
operator=()이 백터를 다른 백터에 복사

(4) iterator 사용법

vector<int>::iterator it; ///iterator it 선언 

vector<int> v; 		//v를 vector로 생성
it = v.begin(); 	//v의 시작위치를 it에저장 
it++;				//it이 가리키는 곳을 다음 원소위치로 이동

//포인터 처럼 사용도 가능하다.
int n = *it;
*it = 5; 		//it위치에 5삽입
it = v.end();   // 마지막 원소 +1위치 삽입

//모든 백터 원소 출력하기
for (it=v.begin(); it != v.end(); it++){
	int n = *it;
    cout << n << endl;
}

동적 길이의 배열 사용하기

동적 길이의 배열을 vector라고 한다면, 정적길이는 array라고 한다.

#include <array>
array<int, 5> arr = {1, 2, 3, 4, 5}; // 5개의 길이를 가진 배열 만들기.

cout << arr[0];

(5) map 컨테이너의 활용

map이란? key와 값으로 이루어진 원소를 저장하고, 키를 이용해 값을 검색하는 컨테이너이다.

1. map 컨테이너 생성

#include<map>
using namespace std;

map<string, string> dic; //키를 string, 값을  string으로 받는 map 컨테이너 생성

마찬가지로 map을 include하여 사용한다.

2. map 원소 저장 방법

dic.insert(make_pair("love", "사랑")); // key : love, value : 사랑
dic["love"] = "사랑";				  // 동일한 기능

3.map원소 키로 검색하기

string kor = dic["love"];

만약 일치하는 키가 존재하지 않는다면,

4. 그 외 map 연산자 함수

map 연산자 함수설명
insert(pair<> &element)키와 값으로 구성된 pair객체 element 삽입
at(key_type& key)키값에 해당하는 값을 리턴
begin()맵의 첫번째 원소에 대한 참조 리턴
end()맵의 마지막 원소+1위치에 대한 참조 리턴
empty()맵이 비어있으면 true를 리턴
erase(iterator it)맵에서 it이 가리키는 원소 삭제
size()현재 맵의 원소 개수 리턴
operator[key_type& key]()맵에서 키값에 해당하는 원소를 찾아 값 리턴
operator=()맵을 복사한다.

(6) algorithm 컨테이너 이용

STL 알고리즘은 전역함수이며, 템플릿으로 작성되어 있다.
아래는 알고리즘중 하나인 sort를 사용한 예이다.

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

int main(){
	vector<int> v; //정수 백터 생성
    
    cout << "정수입력 5번 << endl;
    for(int i=0; i<5; i++){
    	int n;
        cin >> n;
        v.push_back(n); //입력한 값을 전송
    }
    
    //v.begin~v.end의 원소를 오름차순 정렬
    sort(v.begin(), v.end());
    
    vector<int>::iterator it; // 포인터 생성
    
    for(it.begin(); it != v.end(); it++){
    	cout << *it << endl;
    }
    cout << endl;
}   

4. auto와 람다식

(1) auto

auto 키워드는 변수 선언문으로 부터 변수의 타입을 추론해 결정하도록 한다.

1.기본 사용법

auto pi = 3.14;

위와 같이 auto타입을 사용시, 컴파일러가 double타입으로 타입을 추정한다.

2. 함수의 리턴타입으로 부터 추정

int squar(int x) {return x*x}

auto ret = squar(3) // ret변수는 함수의 반환타입인 int로 추정해 결정한다.

3. STL탬플릿에 사용하기-iterator 생략하기.

vector<int> v = {1,2,3,4,5};

//보통의 원소 추출방법
vector<int>:: iterator it;
for(it = v.begin(); it != v.end; it++){
	cout << *it << endl;
}

// auto로 it을 대체하기
for(auto it= v.begin(); it != v.end; it++){
	cout << *it << endl;

컴파일러에서 변수 it을 vector<int>::iterator로 추정한다.

(2) 람다식

람다식(=람다함수)는 수학의 함수를 단순하게 표현하는 방식으로, 코드에서도 간편한 표현법이 존재한다.

람다식의 4가지 구성
1. [] : 캡쳐 리스트
2. () : 매개변수 리스트
3. -> : 리턴타입(생략가능)
4. {} : 함수 바디

ex) 
[](int x, int y) {cout << x+y;}			//x+y출력
[](int x, int y) -> int {return x+y;}	//int형으로 x+y출력
[](int x, int y) {cout << x+y;} (2, 3); // 2, 3을 x, y에 대입하여 결과 출력
profile
DB가 좋아요

0개의 댓글