[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
```
댓글
댓글 쓰기