제네릭 함수 작성하기 WRITING GENERIC FUNCTIONS Chapter 8 제네릭 함수 작성하기 WRITING GENERIC FUNCTIONS
8장에서는… 지금까지는… 8장에서는 제네릭 함수 C++의 기본적인 기능과 표준 라이브러리 (standard library)가 제공하는 추상개체를 사용하여 현실적인 문제를 해결 8장에서는 제네릭 함수 호출하기 전까지는 매개변수 타입이 무엇인지 알 수 없는 함수 9~12장: 추상 데이터 타입 (abstract data type)을 정의하는 방법 13부터 객체지향 프로그래밍 (OOP)을 배웁니다.
8.1 제네릭 함수란? 예를 들어, find(b,e,t)는 제네릭 함수이다. 8장에서는 제네릭 함수가 어떻게 구현되는지 공부 매개변수 b와 e는 컨테이너의 반복자인데, 어떠한 컨테이너라도 가능하다. vector, list, string 모두 가능하다. 함수가 호출되기 전까지는 어떤 컨테이너인지 결정되어 있지 않다. 8장에서는 제네릭 함수가 어떻게 구현되는지 공부
알려지지 않은 타입의 중앙값 제네릭 함수를 구현하는 언어적 기능은 템플릿 함수 예) 4.1.1절의 중앙값 계산 템플릿 매개변수 (template parameter) 사용 8장은 템플릿 함수 (11장은 템플릿 클래스를 공부, 템플릿 클래스의 예는 vector, list, map 등) 다른 타입의 객체이더라도, 행동 양식은 공유하는 경우에 템플릿 사용 예) 4.1.1절의 중앙값 계산 그때는 double만 지원하였다. int도 지원하고 싶다면… 4
알려지지 않은 타입의 중앙값 template <class T> T median(vector<T> v) { typedef typename vector<T>::size_type vec_sz; vec_sz size = v.size(); if (size == 0) throw domain_error("median of an empty vector"); sort(v.begin(), v.end()); vec_sz mid = size/2; return size % 2 == 0 ? (v[mid] + v[mid-1]) / 2 : v[mid]; } 템플릿 헤더 타입 매개변수 (매개변수화 된 타입) 5
템플릿 인스턴스화 median() 을 호출하면, 그때 인자 타입에 따라 템플릿이 인스턴스화됨 예를 들어, 아래와 같은 코드가 있다면, 컴파일러는 모든 T를 int로 바꾸어서 vector<int>의 median() 함수 인스턴스를 생성하고, 그것을 컴파일 한다 인스턴스화를 언제 어떻게 할지는 구현 시스템 (즉 컴파일러) 마다 조금씩 다름 vector<int> vd; … // 읽어들이는 부분 int m = median(vd); … 6
제네릭 함수와 타입 제네릭 함수를 사용할 때는 인자의 타입에 더욱 신경을 써야 한다. 예를 들어, accumulate()는 세 번째 인자의 타입에 따라 다르게 값을 누적한다. accumulate(v.begin(),v.end(), 0.0) accumulate(v.begin(),v.end(), 0) 두 번째 경우, 만일 누적할 값들이 실수라면 정수값만 포함하도록 소수점 이하를 잘라낸 채 덧셈을 수행하고, 결과를 int를 돌려준다. 7
예. max() 함수 max(i1, d1)로 호출하면, 컴파일 실패. template<class T> T max(T a, T b) { return (a > b ? a : b); } int main() int i1 = 5, i2 = 3; int i3 = max(i1, i2); // i3 = 5 double d1 = 0.9, d2 = 1.0; double d3 = max(d1, d2); // d3 = 1.0 return 0; int max(int a, int b) { return (a > b ? a : b); } double max(double a, double b) { return (a > b ? a : b); } 인스턴스화 인스턴스화 max(i1, d1)로 호출하면, 컴파일 실패. 구현시스템이 어느 것을 결정할 방법이 없다
8.2 자료구조 독립성 템플릿은 타입을 일반화시킵니다. 게다가 자료구조도 일반화 시킬 수 있습니다. (자료구조 독립성) 어떤 종류의 자료구조 (list, vector, string, map 등)에서도 작동하고, 컨테이너의 전체가 아니라 일부에 대해서도 작동하는 함수를 만들 수 있다. 예를 들어, find()라는 알고리즘 함수. c는 컨테이너 1) find(c.begin(),c.end(), val); // 지원함 2) c.find(val); // 멤버 함수 형태 (지원 안함) 3) find(c,val); // 컨테이너를 통째로 받음(지원 안함) 왜 1)만 지원할까? 어떤 컨테이너의 어떤 연속 구간에 대해서라도 하나의 find함수로 값을 찾을 수 있다. 2)와 3)은 이것이 불가능하다. 9
알고리즘과 반복자 C++의 STL (standard template Library)은 크게 컨테이너, 반복자, 알고리즘으로 구성된다. 반복자는, 알고리즘이 컨테이너(자료구조)에 독립적으로 작동하도록 해 준다. 10
알고리즘과 반복자 라이브러리에서 제공하는 반복자 카테고리 (반복자 카테고리를 통해 어떤 컨테이너들이 어떤 알고리즘을 사용할지를 알 수 있다)`` 입력 반복자: 한 방향으로 순차적 접근, 입력만 가능 (istream) 출력 반복자: 한 방향으로 순차적 접근, 출력만 가능 (ostream, inserter) 순방향 반복자: 한 방향으로 순차적 접근, 입력과 출력 모두 가능 양방향 반복자: 양 방향으로 순차적 접근, 입력과 출력 모두 가능(list, map, set, multilist, multimap) 임의 접근 반복자: 어떤 요소에나 임의 접근, 입력과 출력 모두 가능(vector, string, array, deque) (입력이란 값을 읽어내는 것, 출력이란 값을 변경하는 것) 11
순차적 읽기전용 접근 입력 반복자(input iterator) 라이브러리 함수 예: find() 한 방향으로 순차적 접근, 읽기만 가능 라이브러리 함수 예: find() template <class In, class X> In find(In begin, In end, const X& x) { if (begin == end || *begin == x) return begin; begin++; return find(begin, end, x); } int num_to_find = 3; vector<int> v1; for( int i = 0; i < 10; i++ ) v1.push_back(i); vector<int>::iterator result; result = find( v1.begin(), v1.end(), num_to_find );
순차적 쓰기전용 접근 출력 반복자(output iterator) 라이브러리 함수 예: copy() 한 방향으로 순차적 접근, 쓰기 가능 라이브러리 함수 예: copy() template <class In, class Out> Out copy(In begin, In end, Out dest) { while (begin != end) *dest++ = *begin++; return dest; } vector<int> from_vector; for( int i = 0; i < 10; i++ ) from_vector.push_back( i ); vector<int> to_vector(10); copy( from_vector.begin(), from_vector.end(), to_vector.begin() );
순차적 읽기-쓰기 접근 순방향 반복자(forward iterator): 라이브러리 함수 예: replace() 한 방향으로 순차적 접근, 입력과 출력 모두 가능 라이브러리 함수 예: replace() template <class For, class X> void replace(For beg, For end, const X& x, const X& y) { while (beg != end) { if ( *beg == x) *beg = y; ++beg; } replace( forward_iterator start, forward_iterator end, const TYPE& old_value, const TYPE& new_value );
역방향으로 접근 양방향 반복자(bidirectional iterator) 라이브러리 함수 예: reverse() 양 방향으로 순차적 접근, 입력과 출력 모두 가능 표준 라이브러리 컨테이너 클래스들은 모두 양방향 반복자를 지원 라이브러리 함수 예: reverse() template <class Bi> void reverse(Bi begin, Bi end) { while (begin != end) { --end; if (begin != end) swap(*begin++, *end); } reverse( bidirectional_iterator start, bidirectional_iterator end );
임의 접근 임의 접근 반복자: 라이브러리 함수 예: binary_search() 어떤 요소에나 임의 접근, 입력과 출력 모두 가능 라이브러리 함수 예: binary_search() vector, string, array, deque 지원 template <class Ran, class X> bool binary_search(Ran begin, Ran end, const X& x) { while (begin < end) { // find the midpoint of the range Ran mid = begin + (end - begin) / 2; // see which part of the range contains `x'; // keep looking only in that part if (x < *mid) end = mid; else if (*mid < x) begin = mid + 1; // if we got here, then `*mid == x' so we're done else return true; } return false; bool binary_search( forward_iterator start, forward_iterator end, const TYPE& val );