[C++] 스터디 CPPALOM 7주차: 뇌를 자극하는 C++ STL Chap 2-3
파워 포인트 파일(.pptx)을 Markdown으로 변환하여 업로드하였음)
# <br>CPPALOM # <br>7주차 – 뇌를 자극하는 C++ STL Chap 2-3 한수빈 # <br>함수 포인터 * 함수 포인터는 함수의 시작 주소를 저장하는 포인터이다. * 정수형 변수와 포인터 변수의 용례는 다음과 같다: * 함수 역시 포인터로 주소를 저장할 수 있다. * 반환형 (*이름)(인자) * void (*pf)(int) ```C++ void main() { int n = 10; int *pn = &n; } ``` ```C++ void Print(int n) { cout <<"정수: "<< n << endl; } void main() { // void Print(int n)의 함수 포인터 선언 void (*pf)(int ); // 함수의 이름은 함수의 시작 주소 pf=Print; Print( 10 ); //1. 함수 호출 pf( 10 ); //2. 포인터를 이용한 함수 호출, 첫 번째 방법 (*pf)( 10 ); //3. 포인터를 이용한 함수 호출, 두 번째 방법 cout << endl; cout << Print << endl; cout << pf << endl; cout << *pf << endl; } ``` ```C++ 정수: 10 정수: 10 정수: 10 00CD1154 00CD1154 00CD1154 ``` # <br>함수 포인터의 종류 * C++에서 함수는 정적 함수와 멤버 함수로 나눌 수 있다. * 정적 함수로는 전역 함수, namespace 내의 전역 함수, static 멤버 함수가 해당된다. * 함수 호출은 세 가지가 가능하다. * 정적 함수 호출 * 객체를 이용해 멤버 함수 호출 * 객체의 주소로 멤버 함수 호출 ```C++ void Print( ) { cout <<"정적 함수 Print()"<< endl; } class Point { public: void Print( ) { cout <<"멤버 함수 Print()"<< endl; } }; void main() { Point pt; Point * p = &pt; Print(); // 첫째, 정적 함수 호출 pt.Print(); // 둘째, 객체로 멤버함수 호출 p->Print(); // 셋째, 주소로 멤버함수 호출 } ``` ```C++ 정적 함수 Print() 멤버 함수 Print() 멤버 함수 Print() ``` # <br>정적 함수 호출 ```C++ void Print(int n) { cout <<"전역 함수: "<< n << endl; } namespace A { void Print(int n) { cout <<"namespace A 전역 함수: "<< n << endl; } } class Point { public: static void Print(int n) { cout <<"Point 클래스의 정적 멤버 함수: "<< n << endl; } }; void main() { void (*pf)(int ); Print( 10 ); // 1. namespace 없는 전역 함수 호출 A::Print( 10 ); // 2, namespace A의 전역 함수 호출 Point::Print( 10 ); // 3, Point 클래스의 정적 멤버 함수 호출 cout << endl; pf = Print; pf( 10 ); // 1. 함수 포인터로 namespace 없는 전역 함수 호출 pf = A::Print; pf( 10 ); // 2. 함수 포인터로 namespace A의 전역 함수 호출 pf = Point::Print; pf( 10 ); // 3. 함수 포인터로 Point 클래스의 정적 멤버 함수 호출 } ``` 정적 함수 포인터는 함수 시그너처만 알면 쉽게 선언할 수 있다. ```C++ 전역 함수: 10 namespace A 전역 함수: 10 Point 클래스의 정적 멤버 함수: 10 전역 함수: 10 namespace A 전역 함수: 10 Point 클래스의 정적 멤버 함수: 10 ``` # <br>멤버 함수 호출 ```C++ class Point { int x; int y; public: explicit Point(int _x =0, int _y =0 ):x(_x),y(_y) { } void Print( ) const { cout << x <<',' << y << endl; } void PrintInt(int n) { cout << "테스트 정수 : "<< n << endl; } }; int main() { Point pt(2,3); Point *p = &pt; void (Point::*pf1)() const; // 멤버 함수 포인터 선언 pf1 = &Point::Print; void (Point::*pf2)(int ) ; // 멤버 함수 포인터 선언 pf2 = &Point::PrintInt; pt.Print(); pt.PrintInt( 10 ); cout << endl; (pt.*pf1)(); // 객체로 멤버 함수 포인터를 이용한 호출 (pt.*pf2)(10);// 객체로 멤버 함수 포인터를 이용한 호출 cout << endl; (p->*pf1)(); // 주소로 멤버 함수 포인터를 이용한 호출 (p->*pf2)(10);// 주소로 멤버 함수 포인터를 이용한 호출 return 0; } ``` 멤버 함수도 동일하게 호출 가능하나, 객체를 이용해서 호출해야 한다. ```C++ 2,3 테스트 정수 : 10 2,3 테스트 정수 : 10 2,3 테스트 정수 : 10 ``` * 왜 다음과 같이 멤버 함수 호출을 할 수 없는가? * 함수 호출 규약이 다르기 때문이다. * 정적 함수는 __cdecl, 멤버 함수는 __thiscall 규약을 사용한다. * 어셈블리 수준에서 함수의 호출 방식이 다르며, 멤버 함수의 경우 반드시 객체의 주소를 참조할 수 있어야 하기 때문에 함수의 주소만을 갖고 있을 수 없다! * 그래서 반드시 객체를 통해서 호출하는 문법만이 가능하다. ```C++ class Point { int x; int y; public: explicit Point(int _x =0, int _y =0 ):x(_x),y(_y) { } void Print( ) const { cout << x <<',' << y << endl; } void PrintInt(int n) { cout << "테스트 정수 : "<< n << endl; } }; int main() { pf1 = pt.Print; (pf1)(); pf2 = p->PrintInt; (pf2)(10); return 0; } ``` # <br>클라이언트 코드와 서버 코드 클라이언트 코드: 사용자 서버 코드: 제공자 클라이언트 코드는 서버 코드를 사용한다. ```C++ #include <iostream> using namespace std; //////// 서버 ///////////// void PrintHello( ) { cout << "Hello!" << endl; } /////// 클라이언트 ///////// void main( ) { PrintHello( ); } ``` # <br>콜백 함수 콜백 메커니즘은 더 유용한 추상화를 실현할 수 있게 만든다. STL의 많은 알고리즘, GUI 프레임워크는 이러한 콜백을 사용한다. ```C++ void Client(); //////// 서버 ///////////// void PrintHello( ) { cout << "Hello!" << endl; Client(); //서버에서 클라이언트 코드 호출 } /////// 클라이언트 ///////// void Client( ) { cout << "난 client" << endl; } void main( ) { PrintHello( ); //서버 코드 호출 } ``` # <br>함수 포인터를 이용한 콜백 메커니즘 구현 ```C++ //////// 서버 ///////////// // 배열의 모든 원소에 반복적인 작업을 하도록 추상화되어 있음(구체적인 작업은 없음) void For_each(int *begin, int *end, void (*pf)(int ) ) { while( begin != end ) { pf( *begin++ ); // 클라이언트 함수 호출(콜백) } } /////// 클라이언트 ///////// void Print1(int n) // 공백을 이용하여 원소를 출력 { cout << n <<' '; } void Print2(int n) // 각 원소를 제곱하여 출력 { cout << n*n <<" "; } void Print3(int n) // 문자열과 endl을 이용하여 원소를 출력 { cout <<"정수 : "<< n <<endl; } void main( ) { int arr[5] = {10, 20, 30, 40, 50}; For_each(arr, arr+5, Print1); // Print1() 콜백 함수의 주소를 전달 cout << endl << endl; For_each(arr, arr+5, Print2); // Print2() 콜백 함수의 주소를 전달 cout << endl << endl; For_each(arr, arr+5, Print3); // Print3() 콜백 함수의 주소를 전달 } ``` * 콜백 메커니즘을 사용하기 위해서 함수 포인터를 활용할 수 있다. * 그 외에도 함수 객체, 위임(Delegate) 및 전략(Strategy) 패턴을 사용할 수 있다. ```C++ 10 20 30 40 50 100 400 900 1600 2500 정수 : 10 정수 : 20 정수 : 30 정수 : 40 정수 : 50 ``` # <br>STL의 for_each 사용 * 앞의 예제와 동일한 방식으로 활용되는 것이 있다: * for_each * 콜백 메커니즘을 이용해서 순회 방식만 추상화하고, 내부의 구현은 클라이언트가 수행한다. ```C++ #include <iostream> #include <algorithm> // for_each() 알고리즘(서버)을 사용하기 위한 헤더 using namespace std; /////// 클라이언트 ///////// void Print1(int n) // 공백을 이용하여 원소를 출력 { cout << n <<' '; } void Print2(int n) // 각 원소를 제곱하여 출력 { cout << n*n <<" "; } void Print3(int n) // 문자열과 endl을 이용하여 원소를 출력 { cout <<"정수 : "<< n <<endl; } void main( ) { int arr[5] = {10,20,30,40,50}; for_each(arr, arr+5, Print1); // Print1() 콜백 함수의 주소를 전달 cout << endl << endl; for_each(arr, arr+5, Print2); // Print2() 콜백 함수의 주소를 전달 cout << endl << endl; for_each(arr, arr+5, Print3); // Print3() 콜백 함수의 주소를 전달 } ``` ```C++ 10 20 30 40 50 100 400 900 1600 2500 정수 : 10 정수 : 20 정수 : 30 정수 : 40 정수 : 50 ``` # <br>함수 객체 * 함수 객체(Function Object)란 함수처럼 동작하는 객체이다. * 함수처럼 동작하기 위해서 () 연산자를 정의해야 한다. * 즉, () 연산자를 오버로딩한 객체이다. ```C++ #include <iostream> using namespace std; void Print( ) { cout << "전역 함수!" << endl; } struct Functor { void operator( )( ) { cout << "함수 객체!" << endl; } }; void main( ) { Functor functor; Print(); // 전역 함수 호출 functor(); // 멤버 함수 호출 functor.operator()( )와 같다; } ``` * 매개변수 리스트를 갖는 함수 객체 역시 만들 수 있다. * 함수 객체의 장점 * 함수처럼 동작하는 객체이므로 일반 함수에서 하지 못하는 지원을 받을 수 있다. * 다른 멤버 변수와 멤버 함수를 가질 수 있다. * 함수 포인터는 인라인 될 수 없지만, 함수 객체는 인라인 될 수 있다. ```C++ #include <iostream> using namespace std; void Print(int a, int b) { cout << "전역 함수: " << a << ',' << b << endl; } struct Functor { void operator( )(int a, int b) { cout << "함수 객체: " << a << ',' << b << endl; } }; void main( ) { Functor functor; Print(10, 20); // 전역 함수 호출 functor(10, 20); // 멤버 함수 호출 functor.operator()(10, 20)와 같다; } ``` ```C++ 전역 함수: 10, 20 함수 객체: 10, 20 ``` # <br>멤버 변수를 갖는 함수 객체 Adder 클래스는 멤버 변수를 이용해서 함수가 호출될 때마다 해당 값을 누적한다. operator()(int n) 함수는 클래스 내부에 정의되므로 암묵적으로 인라인 함수가 된다. ```C++ #include <iostream> using namespace std; class Adder { int total; public: explicit Adder(int n=0):total(n) { } int operator( )(int n) { return total+=n; } }; void main( ) { Adder add(0); //초기값 0 cout << add(10) << endl; //10을 누적=>10 cout << add(20) << endl; //20을 누적=>30 cout << add(30) << endl; //30을 누적=>60 } ``` # <br>함수 객체를 사용한 콜백 함수 구현 ```C++ /////// 클라이언트 ///////// struct Functor1 { void operator()(int n) // 공백을 이용하여 원소를 출력 { cout << n <<' '; } }; struct Functor2 { void operator()(int n) // 각 원소를 제곱하여 출력 { cout << n*n <<" "; } }; struct Functor3 { void operator()(int n) // 문자열과 endl을 이용하여 원소를 출력 { cout <<"정수 : "<< n <<endl; } }; void main( ) { int arr[5] = {10,20,30,40,50}; for_each(arr, arr+5, Functor1());//임시 함수자 객체(Functor1())를 만들어 함수로 전달 cout << endl << endl; for_each(arr, arr+5, Functor2());//임시 함수자 객체(Functor2())를 만들어 함수로 전달 cout << endl << endl; for_each(arr, arr+5, Functor3());//임시 함수자 객체(Functor3())를 만들어 함수로 전달 } ``` * 결과는 이전과 같으며, for_each() 알고리즘의 세 번째 인자에 함수가 아닌 함수 객체를 전달했다는 점을 주목하라. * 이는 템플릿을 응용한 것이며, ```C++ 10 20 30 40 50 100 400 900 1600 2500 정수 : 10 정수 : 20 정수 : 30 정수 : 40 정수 : 50 ``` # <br>함수 객체 구현 * STL에는 유용하게 사용할 수 있는 함수 객체가 내장되어 있다. * 가령, less와 greater. * 비교 연산자의 함수 객체이며, bool 형을 반환하는 조건자(predicate)이다. ```C++ #include <iostream> using namespace std; bool Pred_less(int a, int b) { return a < b; } struct Less { bool operator()(int a, int b) { return a < b; } }; void main( ) { Less l; cout << Pred_less(10, 20) << endl; cout << Pred_less(20, 10) << endl; cout << endl; cout << l(10, 20) << endl; // l 객체로 암묵적 함수 호출 cout << l(20, 10) << endl; // l 객체로 암묵적 함수 호출 cout << Less()(10, 20) << endl; // 임시객체로 암묵적 함수 호출 cout << Less()(20, 10) << endl; // 임시객체로 암묵적 함수 호출 cout << endl; cout << l.operator()(10, 20) << endl; // 명시적 호출 cout << Less().operator()(10, 20) << endl; // 명시적 호출 } ``` ```C++ 1 0 1 0 1 0 1 1 ``` * greater 역시 마찬가지로 활용할 수 있다. * 단, STL의 경우 템플릿 클래스이므로 템플릿 매개변수를 명시해주어야 한다. ```C++ #include <iostream> #include <functional> using namespace std; struct Less { bool operator()(int a, int b) { return a < b; } }; struct Greater { bool operator()(int a, int b) { return a > b; } }; void main( ) { cout << Less()(10, 20) << endl; //사용자 Less, Greater 사용 cout << Less()(20, 10) << endl; cout << Greater()(10, 20) << endl; cout << Greater()(20, 10) << endl; cout << endl; cout << less<int>()(10, 20) << endl; //STL의 less, greater 사용 cout << less<int>()(20, 10) << endl; cout << greater<int>()(10, 20) << endl; cout << greater<int>()(20, 10) << endl; } ``` ```C++ 1 0 1 0 1 0 1 1 ``` 마지막으로 유용하게 쓸 수 있는 plus와 minus 함수 객체 구현이다. ```C++ #include <iostream> #include <functional> //Less<> 헤더 using namespace std; struct Plus { int operator()(int a, int b) { return a + b; } }; struct Minus { int operator()(int a, int b) { return a - b; } }; void main( ) { cout << Plus()(10, 20) << endl; //사용자 Plus, Minus 사용 cout << Plus()(20, 10) << endl; cout << Minus()(10, 20) << endl; cout << Minus()(20, 10) << endl; cout << endl; cout << plus<int>()(10, 20) << endl; //STL의 plus, minus 사용 cout << plus<int>()(20, 10) << endl; cout << minus<int>()(10, 20) << endl; cout << minus<int>()(20, 10) << endl; } ``` ```C++ 30 30 -10 10 30 30 -10 10 ```
댓글
댓글 쓰기