" /> C++ 13주차 템플릿과 제네릭 클래스 | BlackWerf's Blog
포스트

C++ 13주차 템플릿과 제네릭 클래스

템플릿 구체화의 오류

  • 제네릭 타입에 두 개 이상 구체적 타입 지정 시 주의

    1
    2
    3
    4
    5
    
    template <class T> void swap(T & a, T & b)
          
    int s = 4;
    double t = 5;
    swap(s,t)
    
    • swap 함수는 매개변수 a와 b 모두 제네릭 타입이 동일함
    • swap 함수를 호출할때 s와 t는 매개변수의 타입이 서로 다르므로, 컴파일 오류가 발생함
      • 템플릿으로부터 swap(int &, double &) 함수를 구체화가 불가능

    2개의 제너릭 타입을 갖는 템플릿

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    template <class T1, class T2>
    void myCopy(T1 src[], T2 dest[], int n) //src[]의 n개 원소를 dest[]에 복사
    {
        for (int i = 0; i < n; i++)
            dest[i] = (T2)src[i]; //T1타입의 값을 T2 타입으로 변경
    }
      
    int main()
    {
        int x[] = { 1,2,3,4,5 };
        double d[5];
        char c[5] = { 'H', 'e', 'l','l','o' }, e[5];
      
        myCopy(x, d, 5); //int x[]의 원소 5개를 double d[]에 복사
        myCopy(c, e, 5); //char c[]의 원소 5개를 char e[]에 복사
        for (int i = 0; i < 5; i++) 
            cout << d[i] << ' '; //d[] 출력
        cout << endl;
        for (int i = 0; i < 5; i++)
            cout << e[i] << ' '; //e[] 출력
        cout << endl;
    }
    

    Output

    1
    2
    
    1 2 3 4 5
    H e l l o
    


템플릿의 char형 구체화 문제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <class T>
void print(T array[], int n) {
    for (int i = 0; i < n; i++)
        cout << array[i] << '\t';
    cout << endl;
}

   int x[] = { 1,2,3,4,5 };
   double d[5] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
   print(x, 5);  // 템플릿 T가 int 타입으로 구체화
   print(d, 5);

   char c[5] = { 1, 2, 3, 4, 5 };
   print(c, 5); // 템플릿 T가 char 타입으로 구체화
1
2
3
1 2 3 4 5
1.1 2.2 3.3 4.4 5.5
┌ ┐ └ ┘ │
  • char형태로 구체화 될 경우 해당 숫자가 그 숫자의 위치를 가진 문자가 출력되는 문제가 발생함


중복 함수와 템플릿의 우선 순위

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <class T>
void print(T array[], int n) {
    for (int i = 0; i < n; i++)
        cout << (int)array[i] << '\t';
    cout << endl;
}

   int x[] = { 1,2,3,4,5 };
   double d[5] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
   print(x, 5);  // 템플릿 T가 int 타입으로 구체화
   print(d, 5);

   char c[5] = { 1, 2, 3, 4, 5 };
   print(c, 5); // 템플릿 T가 char 타입으로 구체화

Output

1
2
3
1       2       3       4       5
1       2       3       4       5
1       2       3       4       5

템플릿을 이용한 제네릭 클래스

제네릭 클래스 구현

1
2
3
4
5
6
7
8
9
10
template <class T>
class MyStack {
    int tos;
    T data[100]; //T타입의 배열

public:
    MyStack();
    void push(T element);
    T pop();
};

제네릭 클래스 구현

1
2
3
4
5
6
7
template <class T>
void MyStack<T>::push(T element) { //매개 변수에 템플릿 활용
	//구현 내용
}
template <class T> T MyStack<T>::pop() {
	//구현 내용
}

제네릭 클래스 활용

1
2
3
4
5
6
7
8
MyStack<int> iStack;
MyStack<double> dStack;

iStack.push(3);
int n = iStack.pop();

dStack.push(3.5);
double d = dStack.pop();


제네릭 클래스의 활용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
template <class T>
class MyStack2 {
    int tos; //스택의 Top
    T data[100]; //크기 100짜리 스택

public:
    MyStack2();
    void push(T element); //element를 data[] 배열에 삽입
    T pop(); //스택의 top 위치의 데이터를 data[] 배열에서 리턴
};
template <class T>
MyStack2<T>::MyStack2(){
    tos = -1;
}

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

    retData = data[tos--];
    return retData;
}


int main()
{
    MyStack2<int> iStack; //int만 저장하는 스택
    iStack.push(3);
    cout << iStack.pop() << endl;

    MyStack2<double> dStack; //double만 저장
    dStack.push(3.5);
    cout << dStack.pop() << endl;

    MyStack2<char>* p = new MyStack2<char>(); //char만 저장
    p->push('a');
    cout << p->pop() << endl;
    delete p;
}

Output

1
2
3
3
3.5
a

제네릭 클래스

cout의 실행 순서

  • Cout에서 « 연산자가 연속될 경우, 마지막 데이터부터 cout의 스택에 삽입 후, 스택에 삽입된 데이터를 팝하며 « 연산자를 순서대로 처리

포인터를 이용한 제네릭 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
template <class T>
class MyStack3 {
    int tos; //스택의 Top
    T data[100]; //크기 100짜리 스택

public:
    MyStack3();
    void push(T element);  //element를 data에 삽입
    T pop();
};
template <class T>
MyStack3<T>::MyStack3() {//생성자
    tos = -1; //스택은 비어있음
}
template <class T>
void MyStack3<T>::push(T element) {
    if (tos == 99) {
        cout << "stack full";
        return;
    }

    tos++;
    data[tos] = element;
}

template<class T>
T MyStack3<T>::pop() {
    T retData;
    if (tos == -1) {
        cout << "stack empty";
        return 0; //오류 표시
    }
    retData = data[tos--];
    return retData;
}
class Point3 {
    int x, y;
public:
    Point3(int x = 0, int y = 0) { this->x = x; this->y = y; }
    void show() { cout << '(' << x << ',' << y << ')' << endl; }
};


int main(){
	MyStack3<int*> ipStack;
    int* p = new int[3];
    for (int i = 0; i < 3; i++)
        p[i] = i * 10; //0,10,20으로 초기화
    ipStack.push(p); //포인터 push

    int* q = ipStack.pop(); //포인터 pop
    for (int i = 0; i < 3; i++)
        cout << q[i] << ' ';
    cout << endl;
    delete[] p;

    MyStack3<Point> pointStack; //Point 객체 저장 스택
    Point a(2, 3), b;
    pointStack.push(a); //Point 객체 a 푸시, 복사되어 저장
    b = pointStack.pop(); //point 객체 pop
    b.show(); 

    MyStack3<Point*> pStack;
    pStack.push(new Point(10, 20));
    Point* pPoint = pStack.pop(); //Point 객체의 포인터 Pop
    pPoint->show(); //Point 객체 출력

    MyStack3<string> stringStack; //문자열만 저장하는 스택
    string s = "C++";
    stringStack.push(s);
    stringStack.push("Java");
    cout << stringStack.pop() << ' ';
    cout << stringStack.pop() << endl;
}

Output

1
2
3
4
0 10 20
(2,3)
(10,20)
Java C++


2개의 제네릭 타입을 가진 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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()
{
    //2개의 제네릭 타입 가진 클래스
    int a;
    double b;
    GClass<int, double> x;
    x.set(2, 0.5);
    x.get(a, b);
    cout << "a=" << a << '\t' << "b=" << b << endl;

    char c;
    float d;
    GClass<char, float> y;
    y.set('m', 12.5);
    y.get(c, d);
    cout << "c=" << c << '\t' << "d=" << d << endl;
    }

Output

1
2
a=2     b=0.5
c=m     d=12.5

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

STL

  • 표준 템플릿 라이브러리

  • 많은 제네릭 클래스와 제네릭 함수를 포함함


구성

  • 컨테이너 - 템플릿 클래스

    1
    2
    3
    
    	- 데이터를 담은 자료 구조를 표현한 클래스
    	
    	- 리스트, 큐, 스택, 맵, 셋, 벡터
    
  • Iterator - 컨테이너 원소에 대한 포인터

    • 컨테이너의 원소들을 순회하면서 접근하기 위해 만들어진 컨테이너 원소에 대한 포인터
  • 알고리즘 - 템플릿 함수

    • 컨테이너 원소에 대한 기능(복사, 삭제, 정렬…)를 구현한 템플릿 함수
    • 컨테이너의 멤버함수가 X


포함되는 라이브러리

컨테이너

  • 임의 타입의 객체 보관
  • 시퀸스 컨테이너, 연관 컨테이너, 컨테이너 어댑터…

반복자

  • 컨테이너에 보관된 객체 접근

알고리즘

  • 반복자들을 가지고 일련의 작업 수행
컨테이너 클래스내용
array고정 길이 배열
vector가변배열
queueFIFO 자료구조, 우선순위 큐
deque앞 뒤로 삽입/삭제가 가능한 큐
list양방향 연결 리스트
forward_list단방향 연결 리스트
stackLIFO 자료구조
map이진탐색트리 기반, 자동정렬, key-value pair 구성
set이진탐색트리 기반, 자동정렬, key만 저장
unordered_map정렬되지않은 map
unoridered_set정렬되지 않은 set
  • 이외에도 다양한 클래스가 존재함


반복자의 종류

  • 입력 반복자

  • 출력 반복자

  • 순방향 반복자

  • 양방향 반복자

  • 임의 접근 반복자


STL 알고리즘의 종류

계수 알고리즘count, …
탐색 알고리즘search,, find, …
비교 알고리즘equal, mismatch, …
초기화 알고리즘fill, generate, …
변경 알고리즘transform, …
복사 알고리즘copy, …
삭제 알고리즘remove, unique, …
대치 알고리즘replace, …
정렬 알고리즘sort, …
분할 알고리즘partition, …

헤더파일

  • 컨테이너 클래스를 사용하기 위한 헤더 파일
    • 사용방법: 해당 클래스가 선언된 헤더파일을 include
    • Ex) #include
  • 알고리즘 함수를 사용하기 위한 헤더 파일
    • 알고리즘 함수에 상관없이 #include ****
  • 이름 공간
    • STL이 선언된 이름 공간은 std

Vector

특징

  • 가변 길이 배열을 구현한 제네릭 클래스
    • 벡터 길이에 대한 고민이 필요 X
  • 원소 저장, 삭제, 검색 등 다양한 멤버 함수 지원
  • 저장된 인덱스는 인덱스로 접근 가능
    • 인덱스는 0부터 시작

함수

Image Alt 텍스트

사용

예제

예제 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  int main()
  {
      //Vector 예제
      vector<int> v; //정수만 삽입 가능한 벡터 생성
      v.push_back(1); //벡터에 정수 1 삽입
      v.push_back(2);
      v.push_back(3);
  
      for (int i = 0; i < v.size(); i++)
          cout << v[i] << " ";
      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] << " ";
      cout << endl;
  }

Ouput

1
2
  1 2 3
  10 2 5


예제 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
   vector<string> sv;
   string name;
   cout << "이름 4개 입력" << endl;
   for (int i = 0; i < 4; i++) {
       cout << i + 1 << ">>";
       getline(cin, name);
       sv.push_back(name);
  }
   name = sv.at(0); //벡터의 첫 원소
   for (int i = 1; i < sv.size(); i++) {
       if (name < sv[i]) //sv[i]의 문자열이 name보다 사전에서 뒤에 나옴
           name = sv[i]; //name을 sv[i]의 문자열로 변경
   }
   cout << "사전에서 가장 뒤에 나오는 이름 : " << name << endl;

Output

1
2
3
4
5
6
  이름 4개 입력
  1>>한라산
  2>>백두산
  3>>후지산
  4>>설악산
  사전에서 가장 뒤에 나오는 이름 : 후지산

iterator

의미

  • 컨테이너의 원소를 가르키는 포인터

변수 선언

  • 구체적인 컨테이너를 지정해 반복자 변수 생성

  • Ex)

1
2
  vector<int>::iterator iter;
  iter = vec.begin();


사용

예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  int main(){
      //iterator 예제
  vector<int> vec;
  vec.push_back(1);
  vec.push_back(2);
  vec.push_back(3);
  vector<int>::iterator iter; //벡터 v의 원소에 대한 포인터 iter 선언
  for (iter = vec.begin(); iter != vec.end(); iter++) { //iterator를 이용해 모든 원소 탐색
      int n = *iter; //iter가 가르키는 원소값 리턴
      n = n * 2;
      *iter = n; //iter가 가르키는 원소에 값 쓰기
  }
  for (iter = vec.begin(); iter != vec.end(); iter++)
      cout << *iter << ' ';
  cout << endl;
  }

Output

1
  2 4 6
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.