{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[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
```



댓글

이 블로그의 인기 게시물

[코딩의탑] 4층: 툰 쉐이딩

[코딩의탑] 3층: 바다 렌더링

[코딩의탑] 5층: 포탈(Portal), 더 나아가기