함수 중복의 약점과 제네릭
template <class T>
//template: 템플릿을 선언하는 키워드
//class: 제네릭 타입을 선언하는 키워드
//T: 제네릭 타입 T 선언
void swap(T& a, T& b) {
T tmp;
tmp a;
a = b;
b = tmp;
}
▶ 위의 소스 코드를 보자
template <class T>
//template: 템플릿을 선언하는 키워드
//class: 제네릭 타입을 선언하는 키워드
//T: 제네릭 타입 T 선언
void swap(T& a, T& b) {
T tmp;
tmp a;
a = b;
b = tmp;
}
▶ 둘다 동일한 코드에다가 동일한 이름이지만, 단순히 매개 변수만 달라(오버로딩) 중복하여 작성하였다. 이는 코드를 작성하는데 낭비가 될 수 있다.
▶ 이를 해결하기 위해 사용하는 것이 제네릭(generic, 일반화)이다. 제네릭이란 함수나 클래스를 일반화시키고, 매개 변수 타입을 직접 지정하여 틀에서 직어 내듯이 함수나 클래스 코드를 생산하는 기법이다.
▶템플릿은 함수나 클래스를 일반화하는데 사용되는 C++ 도구이다. template 키워드로 함수나 클래스를 선언할 수 있다. 다음은 템플릿을 이용한 제네릭 함수 swap이다.
template <class T>
//template: 템플릿을 선언하는 키워드
//class: 제네릭 타입을 선언하는 키워드
//T: 제네릭 타입 T 선언
void swap(T& a, T& b) {
T tmp;
tmp a;
a = b;
b = tmp;
}
제네릭 함수
#include<iostream>
using namespace std;
class Circle {
int radius;//private
public:
Circle(int radius = 1) {
this->radius = radius;
}
int getRadius() {
return radius;
}
};
template<class T>
void myswap(T& a, T& b) {
T tmp;
tmp = a;
a = b;
b = tmp;
}
void main() {
int a = 4, b = 5;
myswap(a, b);
cout << "a=" << a << "," << "b=" << b << endl;
double c = 4, d = 5;
myswap(c, d);
cout << "a=" << a << "," << "b=" << b << endl;
Circle donut(5), pizza(20);
myswap(donut, pizza);
cout << "donut 반지름="
<< donut.getRadius() << endl;
cout << "pizza반지름="
<< pizza.getRadius() << endl;
}
▶ int 형의 수, double 형의 수, Circle 클래스의 객체 , 이 세 가지의 다른 형식으로 myswap( )메소드에 대입했음에도 결과가 오류없이 나온다.
▶ 이처럼 템플릿을 사용하게 되면 함수의 재사용으로 인해 높은 sw의 생산성과 유용성에 기여한다. 하지만 포팅에 취약하고 컴파일 오류 메시지에 빈약하기에 디버깅에 많은 어려움을 겪는다고 한다.
▶ 다음은 제네릭 함수를 이용한 예제들이다.
1. 큰 값을 리턴하는 bigger()함수
#include<iostream>
using namespace std;
template <class T>
T bigger(T a, T b) {
if (a > b)
return a;
else return b;
}
void main() {
int a = 20, b = 50;
char c = 'a', d = 'z';
cout << "bigger(20,50)의 결과는"
<< bigger(a, b) << endl;
cout << "bigger(a,z)의 결과는"
<< bigger(c, d) << endl;
}
▶ class T로 지정을 하고, T는 int 형으로 쓰이기도 하고, char형으로도 쓰이기도 한다. 이를 통해 다양한 매개변수에서 쓰일 수 있음을 다시 한 번 강조한다.
2. 배열의 합을 구하여 return하는 제네릭 add( ) 메소드 예제 (feat. 배열과 변수가 매개변수인 template)
#include<iostream>
using namespace std;
template <class T>
T add(T data[], int n) {
T sum = 0;
for (int i = 0; i < n; i++) {
sum += data[i];
}
return sum;
}
void main() {
int x[] = { 1,2,3,4,5 };
double d[] = { 1.1,2.2,3.3,4.4,5.5,6.6 };
cout << "sum of x[] ="
<< add(x, 5) << endl;
cout << "sum of d[]="
<< add(d, 6) << endl;
}
▶ 이 예제는 위의 예제와는 달리 배열과 int 형의 숫자이다. 제네릭 함수에서 쓰이는 매개변수는 둘다 무조건 같은 자료형일 필요는 없다.
3. 배열을 복사하는 제네릭 함수 mcopy( ) 메소드 예제 (feat. class가 2개인 template)
#include<iostream>
using namespace std;
template<class T1, class T2>
void mcopy(T1 src[], T2 dest[], int n) {
for (int i = 0; i < n; i++) {
dest[i] = (T2)src[i];
//T1타입의 갑을 T2타입으로 변환한다.
}
}
void main() {
int x[] = { 1,2,3,4,5 };
double d[5];
char c[5]{ 'H','e','l','l','o' };
char e[5];
mcopy(x, d, 5);
mcopy(c, e, 5);
for (int i = 0; i < 5; i++) {
cout << d[i] << ' ';
}
cout << endl;
for (int i = 0; i < 5; i++) {
cout << e[i] << ' ';
}
cout << endl;
}
▶ 이번 예시 또한 뭔가 다르다. 이는 class가 두개이다. 서로 다른 자료형을 표시하기 위해 2개의 class를 별도로 선언을 해준 것이다.
▶ mcopy(x,d,5) 메소드는 int형과 double 형을 표시하기 위해 서로 다른 클래스인 class T1과 T2를 선언해주었다.
4. 배열을 출력하는 print() 템플릿 메소드의 문제점(feat. char형으로 구체화되는 경우 그래픽 문자 출력)
▶ 이 예제는 신기하다. 숫자를 char형의 배열에 입력하고 template함수로 표현하였을 때, 그래픽 문자가 출력이 된다고 한다.
#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;
}
void 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);
}
5. 중복함수가 템플릿 함수보다 우선임을 알려주는 예제
#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;
}
void print(char array[], int n) {
for (int i = 0; i < n; i++) {
cout << (int)array[i] << "\t";
}
cout << endl;
}
void main() {
int x[5] = { 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);
}
▶ 위의 예제는 print( )라는 이름의 템플릿 함수와 그냥 메소드를 선언하고, 둘 중의 어느 것을 사용하는지를 알려준다.
▶ 컴파일링 결과, 중복함수가 먼저 실행이 됨을 알 수 있다.
int x[5] = { 1,2,3,4,5 };
double d[5] = { 1.1,2.2,3.3,4.4,5.5 };
print(x, 5);
print(d, 5);
▶ int형의 배열과 double형의 배열이다. void 형의 print ( )메소드는 매개변수의 자료형이 char 형이기 때문에 위의 예시는 적용이 되지 않는다. 따라서 이 부분에서의 print 메소드는 템플릿 함수를 사용한다.
char c[5] = { 1,2,3,4,5 };
print(c, 5);
▶ 반면 c 배열은 자료형이 char이다. 템플릿 함수에서도 char형이 가능하고 void 형태의 메소드는 char형의 배열을 매개변수로 두고 있다. 그 결과, void형의 print 메서드가 실행됨을 알 수 있다.
제네릭 클래스(Generic Class)
▶ 템플릿 함수와 마찬가리조 클래스 멤버들을 템플릿으로 지정할 수 있다. 템플릿 클래스를 정의하는 형식은 다음과 같다.
template <템플릿 인수1, 템플릿 인수2...> 클래스의 정의
▶ 제네릭 클래스를 이용하여 스택을 만들어 보자. 만들기에 앞서 스택에 대해 간단하게 소개하도록 하겠다.
▶ 스택(stack)은 데이터를 일시적으로 저장하기 위해 사용하는 자료구조로, 데이터의 입력과 출력 순서를 후입 선출로 가지는 형태이다. (가장 나중에 넣은 데이터를 가장 먼저 꺼내는 형식)
#include<iostream>
using namespace std;
template<class T>
class MyStack {
int tos;//top of stack
T data[100];//T 타입의 data라는 이름을 가진 배열. 크기는 100
public:
MyStack();
void push(T element);//element를 data[]배열에 삽입
T pop();//스택의 탑에 있는 데이터를 data 배열에서 return
};
template<class T>
MyStack<T>::MyStack() {
tos = -1;//스택은 비어있음
}
template<class T>
void MyStack<T>::push(T element) {
if (tos == 99) {
cout << "Stack is full";
return;
}
else
tos++;
data[tos] = element;
}
template<class T>
T MyStack<T>::pop() {
T retData;
if (tos == -1) {
cout << "Stack is empty";
return 0;
}
else
retData = data[tos--];
return retData;
}
void 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;
}
▶
template<class T>
class MyStack {
int tos;//top of stack
T data[100];//T 타입의 data라는 이름을 가진 배열. 크기는 100
public:
MyStack();
void push(T element);//element를 data[]배열에 삽입
T pop();//스택의 탑에 있는 데이터를 data 배열에서 return
};
▶ MyStack 제네릭 클래스의 모습이다. 매개변수는 int형의 tos와 아직 확정되지 않은 T형의 data 배열이다.
메소드로 MyStack, void형의 push, T혀의 pop 이렇게 3개가 선언되었다.
template<class T>
MyStack<T>::MyStack() {
tos = -1;//스택은 비어있음
}
▶ MyStack( )메소드는 스택을 비어주게 만드는 메소드이다. tos에 -1을 대입함으로서 나중에 데이터를 입력하였을 때 0브터 시작할 수 있도록 만들어주는 것이 목적이다.
template<class T>
void MyStack<T>::push(T element) {
if (tos == 99) {
cout << "Stack is full";
return;
}
else
tos++;
data[tos] = element;
}
▶ 다음은 T형의 element라는 이름의 변수를 가진 push 메소드이다. data의 배열의 크기를 100이라고 지정을 하였고, 배열은 맨 처음이 1이 아니라 0이기 때문에 99개의 데이터가 쌓였으면 다 차였다고 볼 수 있으므로 데이터를 넣을 수 없다.
▶ 만약 99가 아니라면 현재 쌓여있는 tos 번째에서 하나를 추가하여 data배열에 넣을 T형의 element를 넣는다. T형은 직접 사용자가 선언하기 전에는 어떻게 쓰일지 모른다.
template<class T>
T MyStack<T>::pop() {
T retData;
if (tos == -1) {
cout << "Stack is empty";
return 0;
}
else
retData = data[tos--];
return retData;
}
▶ pop 메소드는 가장 나중에 넣은 데이터를 출력해준다. T 형의 retData라는 변수가 가장 나중에 넣은 데이터이다.
void 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;
}
▶ main 함수에서는 각각의 다른 자료형을 가지고 있고 그에 따른 객체를 가지고 있다.
▶ 다음은 두 개의 제네릭 타입을 가진 클래스 예제이다.
#include<iostream>
using namespace std;
template<class T1, class T2>
//서로 다른 클래스, 즉 서로 다른 2개의 자료형 선언가능
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;
}//data1과 data2를 0으로 초기화
template<class T1, class T2>
void GClass<T1, T2>::set(T1 a, T2 b) {
data1 = a; data2 = b;
}//data1과 data2에 사용자가 입력하는 값을 대입
template<class T1, class T2>
void GClass<T1, T2>::get(T1& a, T2& b) {
a = data1; b = data2;
}//set에서 data1과 data2에 넣은 값을 다시 a와b에 대입
void main() {
int a;
double b;
GClass<int, double> x;//GClass의 객체인 x
x.set(2, 0.5);
//data1=2, data2=0.5
x.get(a, b);
//a=2, b=0.5
cout << "a=" << a << "," << "b=" << b << endl;
char c;
float d;
GClass<char, float>y;//GClass의 객체인 y
y.set('m', 12.5);
//data1='m', data2=12.5
y.get(c, d);
//c='m', d=12.5
cout << "c=" << c << "," << "d=" << d << endl;
}
'💻 개인공부 💻 > C | C++' 카테고리의 다른 글
[C++] 예외 처리 (Exception) (0) | 2020.06.21 |
---|---|
[C++] 조작자를 이용한 구조체 객체들의 정보 출력 프로그램(feat. struct, setw(), switch) (0) | 2020.06.21 |
[C++] 상속(Inheritance) - 2 (feat. 가상 함수와 추상클래스) (0) | 2020.06.20 |
[C++] 상속(Inheritance) - 1 (feat. 개념, 상속 접근 지정자) (0) | 2020.06.19 |
[C++] 연산자 중복 (feat. 프랜드 함수) (0) | 2020.06.19 |