728x90
황기태 저자의 명품 C++ Programming 개정판을 읽고 학습한 내용을 정리한 포스트입니다!
https://search.shopping.naver.com/book/catalog/32436115747
제네릭 클래스 개요
- template를 이용하면 제네릭 클래스(generic class)도 만들 수 있다.
- int 값을 저장하는 스택, double 값을 저장하는 스택, char 값을 저장하는 스택 등 다양한 스택이 필요할 수 있다.
- 다만 이들 스택 클래스는 데이터의 타입만 다를 뿐 알고리즘은 동일하기에, 템플릿을 이용하여 스택에 저장되는 데이터 타입을 일반화시킨 제너릭 스택 클래스를 만들어보자.
선언
- 클래스 선언부와 구현부를 모두 template로 선언한다.
- 제네릭 클래스의 멤버 함수는 자동 제네릭 함수이다.
선언부
- 스택에서 다루는 데이터 타입은 하나이므로 제네릭 타입은 T 하나만 필요하기에 아래와 같이 선언한다.
- 같이 저장되는 스택 공간 data []를 T 타입으로 선언하고, push()의 매개 변수와 pop()의 리턴 타입도 T로 선언한다.
template <class T>
class MyStack {
int tos;// top of stack
T data [100]; // T 타입의 배열. 스택의 크기는 100
public:
MyStack();
void push(T element); // T 타입 원소 element를 data [] 배열에 삽입
T pop(); // 스택의 탑에 있는 데이터를 data[] 배열에서 리턴
};
구현부
- 멤버 함수를 작성하는 곳
- 클래스이름을 MyStack 대신 MyStack<T>로 사용하고, 각 멤버 함수 앞에 template <class T>를 붙여 제네릭 함수임을 선언한다.
template <class T1, class T2>
void mcopy(T1 src[], T2 dest[], int n = 5) {
for (int i = 0; i < n; i++)
dest[i] = (T2)src[i];
}
template <class T>
void MyStack<T>::push(T element) {
...
}
template <class T> T MyStack<T>::pop() { // 한 줄에 선언할 수 있음
...
}
구체화
- 제네릭 클래스를 이용할 때는 클래스의 이름과 함께 제네릭 타입 T에 적용할 구체적인 타입을 지정해야 하낟.
MyStack<int> iStack; // int 타입을 다루는 스택 객체 생성
MyStack<double> dStack; // double 타입을 다루는 스택 객체 생성
컴파일러는 아래와 같은 구체화 과정을 진행한다.
- MyStack 템플릿의 T에 int나 double을 적용하여 두 개의 구체화된 버전의 C++ 클래스 소스(specialized class)를 생성한다.
- 두 C++ 클래스를 컴파일하고 iStack 객체와 dStack 객체를 생성하도록 컴파일한다.
iStack.push(3);
int n = iSTack.pop();
dStack.push(3.5);
double d = dSTack.pop();
- 위와 같이 보통 객체처럼 사용할수도 있지만 아래와 같이 제네릭 클래스의 포인터를 선언하고 동적으로 객체를 생성할 수도 있다.
MyStack<char> *p = new MyStack<char>();
p->push('a');
char c = p->pop();
delete p;
함수의 매개 변수 타입이 제네릭 클래스일 때
- 함수의 매개 변수 타입이 제네릭 클래스 일 때, 함수의 원형을 선언하는 방법을 알아보자.
void popAll(MyStack<int> s) { ... } // 매개 변수 s: int 타입의 MyStack 객체
MyStack<int> iStack;
popAll(iStack);
- 앞의 popAll() 함수가 실행되면 iStack 객체를 복사하여 객체 s가 생긴다. 그러나 아래와 같이 참조 매개 변수를 사용하면 객체 복사의 오버헤드를 제거할 수 있다.
void popAll(MyStack<int> &r) { ... } // 매개 변수 r: int 타입의 MyStack 객체의 참조
popAll(iStack);
- 매개 변수로 제네릭 객체의 포인터도 선언 가능하다.
void popAll(MyStack<int> *p); // 매개 변수 p: int 타입의 MyStack 객체의 포인터
popAll(&iStack);
이제 스택 클래스를 일반화한 제네릭 스택 MyStack을 작성하고 실행해보자.
#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); // T 타입 원소 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; // int 만 저장할 수 있는 정수 스택
iStack.push(3);
cout << iStack.pop() << endl;
MyStack<double> dStack; // double 만 저장할 수 있는 정수 스택
dStack.push(3.5);
cout << dStack.pop() << endl;
MyStack<char> *p = new MyStack<char>(); // char 만 저장할 수 있는 정수 스택
p->push('a');
cout << p->pop() << endl;
delete p;
}
/*
3
3.5
a
*/
- 제네릭 스택의 제네릭 타입을 포인터나 클래스로 구체화해보자..
- MyStack 제네릭 클래스를 활용하여 int*. Point 객체, Point*. string 문자열 등을 저장하고 사용하는 예이다.
#include <iostream>
#include <string>
using namespace std;
// 위에서 작성한 MyStack 템플릿 클래스 코드가 생략되어있음
class Point {
int x, y;
public:
Point(int x=0, int y=0) { this->x = x; this->y = y; }
void show() { cout << '(' << x << ',' << y << ')' << endl; }
};
int main() {
MyStack<int *> ipStack; // int* 만을 저장하는 스택
int *p = new int [3];
for(int i=0; i<3; i++) p[i] = i*10; // 0, 10, 20으로 초기화
ipStack.push(p); // 포인터 푸시
int *q = ipStack.pop(); // 포인터 팝
for(int i=0; i<3; i++) cout << q[i] << ' '; // 화면 출력
cout << endl;
delete [] p;
MyStack<Point> pointStack; // Point 객체만 저장하는 스택
Point a(2,3), b;
pointStack.push(a); // Point 객체 a 푸시. 복사되어 저장
b = pointStack.pop(); // Point 객체 팝
b.show(); // Point 객체 출력
MyStack<Point*> pStack; // Point* 만 저장하는 스택
pStack.push(new Point(10,20)); // 동적 생성 Point 객체 푸시. 포인터만 저장
Point* pPoint = pStack.pop(); // Point 객체의 포인터 팝
pPoint->show(); // Point 객체 출력
MyStack<string> stringStack; // 문자열만 저장하는 스택
string s="c++";
stringStack.push(s);
stringStack.push("java");
cout << stringStack.pop() << ' ';
cout << stringStack.pop() << endl;
}
void popAll(MyStack<int> s) { ... } // 매개 변수 s: int 타입의 MyStack 객체
MyStack<int> iStack;
popAll(iStack);
두 개 이상의 제네릭 타입을 가진 제네릭 클래스
- 템플릿을 사용하여 2개 이상의 제네릭 타입을 가진 제네릭 클래스를 만들 수 있다.
template <class T1, class T2, class T3>
#include <iostream>
using namespace std;
template <class T1, class T2> // 두 개의 제네릭 타입 선언
class GClass {
T1 data1;
T2 data2;
public:
GClass();
void set(T1 a, T2 b);
void get(T1 &a, T2 &b);
};
template <class T1, class T2>
GClass<T1, T2>::GClass() {
data1 = 0; data2 = 0;
}
template <class T1, class T2>
void GClass<T1, T2>::set(T1 a, T2 b) {
data1 = a; data2 = b;
}
template <class T1, class T2>
void GClass<T1, T2>::get(T1 & a, T2 & b) {
a = data1; b = data2;
}
int main() {
int a;
double b;
GClass<int, double> x;
x.set(2, 0.5); // int, double의 매개 변수
x.get(a, b);
cout << "a=" << a << '\t' << "b=" << b << endl;
char c;
float d;
GClass<char, float> y;
y.set('m', 12.5); // char, double의 매개 변수
y.get(c, d);
cout << "c=" << c << '\t' << "d=" << d << endl;
}
728x90
'Programming Language > C++' 카테고리의 다른 글
[C++] auto와 람다식 (0) | 2023.12.20 |
---|---|
[C++] 표준 템플릿 라이브러리(STL) (0) | 2023.12.20 |
[C++] 다양한 제네릭 함수 만들기 (0) | 2023.12.19 |
[C++] 템플릿 (0) | 2023.12.19 |
[C++] 추상 클래스 (1) | 2023.12.19 |