728x90

이번 시간에는 지금까지 배운 내용을 종합하여 STL의 ptr_fun(), mem_fun(), mem_fun_ref()를 직접 구현해 보도록 하겠습니다.

이 세 함수는 함수자를 요구하는 알고리즘에 일반적인 함수들을 실행가능하도록 랩핑하는 함수들입니다.

이 페이지는 '함수 포인터와 함수자1,2,3'를 공부하고 보시기 바랍니다.

 

앞쪽에서 함수 포인터는 호출형태에 따라 세 가지로 나뉜다고 했습니다.(앞쪽 참고)

  • 첫째, 정적함수 호출 (예  (*pfunc)( 10 );  )
  • 둘째, 주소로 멤버 함수 호출 (예  (data.*pf)( 10 ); )
  • 셋째, 객체의 주소로 멤버 함수 호출 (예 (data->*pf)( 10 ); )

이 세가지 경우처럼 어댑터 역할을 하는 랩핑함수 ptr_fun(), mem_fun(), mem_fun_ref()도 그래서 세 가지입니다.

 

첫 번째 경우인 ptr_fun()함수를 사용한 예제입니다.

for_each()로 콜백하는 코드를 예제로 사용합니다.

bool Printint n )
{
    cout << "int : " << n << endl;
    return true;
}
void main()
{
    vector<int> v(10);
    int i;

    for(i = 0 ; i < (int)v.size() ; i++ )
        v[i] = i+1;

    for_each(v.begin(), v.end(), Print );
    cout << "=================" << endl;
    for_each(v.begin(), v.end(), ptr_fun(Print) );
}
  1. int : 1
    int : 2
    int : 3
    int : 4
    int : 5
    int : 6
    int : 7
    int : 8
    int : 9
    int : 10
    =================
    int : 1
    int : 2
    int : 3
    int : 4
    int : 5
    int : 6
    int : 7
    int : 8
    int : 9
    int : 10

내용은 어렵지 않지만, for_each()에 ptr_fun()을 사용한 이유가 궁금할 수 있습니다. 위 코드에는 굳이 ptr_fun()이 없어도 되지만 다른 어댑터 함수와 같이 사용하는 경우에(not1()등) 꼭 필요합니다. 간단한 설명은 ptr_fun()에서 사용하는 함수자 클래스의 typedef인 argument_type과 result_type을 사용하기 위해서 입니다. 자세한 내용은 STL에서 공부하고 지금은 ptr_fun()구현 사항에 집중하도록 하겠습니다.

 

이제 위와 같은 ptr_fun()과 비슷한 함수 Ptr_fun()을 만들어 보도록 하겠습니다.

//////////////////// server ///////////////////
// 클래스 : ptr_fun() 함수의 함수자 클래스
// 여러가지 기능을 추가로 제공하기 위한 클래스. typedef argument_type, result_type 등....
class Ptr_fun_class
{
    bool (*pf)(int );
public :
    Ptr_fun_class(bool (*_pf)(int ) ):pf(_pf) { }
    bool operator()(int n )
    {
        return pf(n);
    }
};
// 함수 : 함수자를 반환하는 ptr_fun() 함수
Ptr_fun_class Ptr_funbool (*pf)(int ) )
{
    return Ptr_fun_class(pf);  
}


/////////// client ///////////////////
 bool Printint n )
{
    cout << "int : " << n << endl;
    return true;
}
void main()
{
    vector<int> v(10);
    int i;

    for(i = 0 ; i < (int)v.size() ; i++ )
        v[i] = i+1;

    for_each(v.begin(), v.end(), Ptr_fun(Print) );
}

  1. int : 1
    int : 2
    int : 3
    int : 4
    int : 5
    int : 6
    int : 7
    int : 8
    int : 9
    int : 10

Ptr_fun_class 클래스는 정적함수의 주소를 저장하는 함수자 클래스입니다.

Print함수는 어댑터 기능을 하는 Ptr_fun()함수에 래핑되어 함수가 반환하는 Ptr_func_class의 함수자 객체를 for_each()에 전달합니다.

for_each()는 그 함수자를 호출합니다.(for_each()는 '함수 포인터와 함수자3'를 참고하세요)

 

다음은 모든 자료형이 가능하도록 템플릿 클래스로 수정한 예제입니다.

 //////////////////// server ///////////////////
// 클래스 : ptr_fun()의 함수자 클래스
// 여러가지 기능을 제공할 수 있습니다. typedef argument_type, result_type 등....
template<typename RType, typename AType>
class Ptr_fun_class
{
    RType (*pf)( AType );
public :
    Ptr_fun_class( RType (*_pf)( AType ) ):pf(_pf) { }
    RType operator()( AType n )
    {
        return pf(n);
    }
};
// 함수 : 함수자를 반환하는 ptr_fun() 함수
template <typename RType, typename AType>  
Ptr_fun_class<RType, AType> Ptr_fun( RType (*pf)(AType) )
{
    return Ptr_fun_class<RType, AType>(pf);
}

 

/////////// client ///////////////////
bool Printint n )
{
    cout << "int : " << n << endl;
    return true;
}
void main()
{
    vector<int> v(10);
    int i;

    for(i = 0 ; i < (int)v.size() ; i++ )
        v[i] = i+1;

    for_each(v.begin(), v.end(), Ptr_fun(Print) );
}

  1. int : 1
    int : 2
    int : 3
    int : 4
    int : 5
    int : 6
    int : 7
    int : 8
    int : 9
    int : 10

내용은 같습니다.

 

 

두 번째 경우인 mem_fun()를 사용한 예제입니다.

for_each()로 Point의 맴버함수 Print()를 콜백하는 예제입니다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
class Point
{
    int x, y;
public:
    Point(int _x = 0, int _y = 0) : x(_x), y(_y) { }
    void Print( )
    {
        cout << "(" << x << "," << y << ")" << endl;
    }
};

void main()
{
    vector<Point*> v(10);
    int i;

    for(i = 0 ; i < (int)v.size() ; i++ )
        v[i] = new Point(i+1, i+1);

    //for_each(v.begin(), v.end(), &Point::Print ); 에러~~~
    for_each(v.begin(), v.end(), mem_fun(&Point::Print) );
    // delete 생략
}
  1. (1,1)
    (2,2)
    (3,3)
    (4,4)
    (5,5)
    (6,6)
    (7,7)
    (8,8)
    (9,9)
    (10,10)

컨테이너 v는 Point의 주소들을 저장하고 있습니다.

for_each()는 Point의 멤버 함수 Print()를 호출할 수 없습니다. 이유는?? 우리가 앞쪽에서 공부했듯이 정적함수와 멤버 함수 호출방식(인터페이스)이 다르기 때문입니다. 그래서 호출방식을 통일 시키기위한 함수자(어댑터 객체)가 필요하고 mem_fun()이 멤버 함수를 호출가능하도록 함수자 객체를 생성하여 반환하는 것입니다.

 

이제 우리도 mem_fun()과 비슷한 함수 Mem_fun()을 만들어 보도록 하겠습니다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

//////////////////// server ///////////////////
// 클래스 : mem_fun()의 함수자 클래스
// 여러가지 기능을 제공할 수 있습니다. typedef argument_type, result_type 등....
class Point;
class Mem_fun_class
{
    bool (Point::*pf)();
public :
    Mem_fun_class( bool (Point::*_pf)() ):pf(_pf) {}
    bool operator()( Point* p )
    {      
        return (p->*pf)();
    }
};
// 함수 : 함수자를 반환하는 mem_fun() 함수
Mem_fun_class Mem_funbool (Point::*pf) () )
{
    return Mem_fun_class( pf );
}

/////////// client ///////////////////
class Point
{
    int x, y;
public:
    Point(int _x = 0, int _y = 0) : x(_x), y(_y) { }
    bool Print( )
    {
        cout << "(" << x << "," << y << ")" << endl;
        return true;
    }
};

void main()
{
    vector<Point*> v(10);
    int i;

    for(i = 0 ; i < (int)v.size() ; i++ )
        v[i] = new Point(i+1, i+1);

    for_each(v.begin(), v.end(), Mem_fun(&Point::Print) );
    cout << "=============" << endl;
    Mem_fun_class functor(&Point::Print);
    for_each(v.begin(), v.end(), functor );
    // delete 생략
}
  1. (1,1)
    (2,2)
    (3,3)
    (4,4)
    (5,5)
    (6,6)
    (7,7)
    (8,8)
    (9,9)
    (10,10)
    =============
    (1,1)
    (2,2)
    (3,3)
    (4,4)
    (5,5)
    (6,6)
    (7,7)
    (8,8)
    (9,9)

Mem_fun()함수는 Mem_fun_class의 객체(함수자)를 반환합니다. for_each()는 이 함수자를 이용하여 정적함수(일반함수)와 동일한 인터페이스로 Point::Print 멤버 함수를 호출합니다.

 

아래는 모든 자료형이 가능하도록 템플릿으로 변환한 예제입니다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

//////////////////// server ///////////////////
// 클래스 : mem_fun()의 함수자 클래스
// 여러가지 기능을 제공할 수 있습니다. typedef argument_type, result_type 등....
class Point;
template <typename RType, typename CType>
class Mem_fun_class
{
    RType (CType::*pf)();
public :
    Mem_fun_class( RType (CType::*_pf)() ):pf(_pf) {}
    RType operator()( CType* p )
    {      
        return (p->*pf)();
    }
};
// 함수 : 함수자를 반환하는 mem_fun() 함수
template <typename RType, typename CType>
Mem_fun_class<RType, CType> Mem_fun( RType (CType::*pf) () )
{
    return Mem_fun_class<RType,CType>( pf );
}

/////////// client ///////////////////
class Point
{
    int x, y;
public:
    Point(int _x = 0, int _y = 0) : x(_x), y(_y) { }
    bool Print( )
    {
        cout << "(" << x << "," << y << ")" << endl;
        return true;
    }
};

void main()
{
    vector<Point*> v(10);
    int i;

    for(i = 0 ; i < (int)v.size() ; i++ )
        v[i] = new Point(i+1, i+1);

    for_each(v.begin(), v.end(), Mem_fun(&Point::Print) );
    cout << "=============" << endl;
    Mem_fun_class<bool, Point> functor(&Point::Print);
    for_each(v.begin(), v.end(), functor );
    // delete 생략
}
  1. (1,1)
    (2,2)
    (3,3)
    (4,4)
    (5,5)
    (6,6)
    (7,7)
    (8,8)
    (9,9)
    (10,10)
    =============
    (1,1)
    (2,2)
    (3,3)
    (4,4)
    (5,5)
    (6,6)
    (7,7)
    (8,8)
    (9,9)
    (10,10)

 내용은 위 예제와 같습니다.

 

 

마지막으로 세 번째 경우인 mem_fun_ref()를 보도록 하겠습니다.

mem_fun()과 비슷합니다. 차이점은 컨테이너 v가 Point 주소가 아닌 Point 객체를 저장한다는 것입니다. for_each()로 Point의 맴버함수 Print()를 콜백하는 예제입니다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

class Point
{
    int x, y;
public:
    Point(int _x = 0, int _y = 0) : x(_x), y(_y) { }
    void Print( )
    {
        cout << "(" << x << "," << y << ")" << endl;
    }
};
void main()
{
    vector<Point> v(10);
    int i;

    for(i = 0 ; i < (int)v.size() ; i++ )
        v[i] = Point(i+1, i+1);

    //for_each(v.begin(), v.end(), &Point::Print ); 에러~~~~
    for_each(v.begin(), v.end(), mem_fun_ref(&Point::Print) );
    // delete 생략
}
  1. (1,1)
    (2,2)
    (3,3)
    (4,4)
    (5,5)
    (6,6)
    (7,7)
    (8,8)
    (9,9)
    (10,10)

 컨테이너 v가 Point객체를 저장합니다. 내용은 모두 동일합니다.

 

다음은 mem_fun_ref()과 비슷한 함수 Mem_fun_ref()을 만들어 본 예제입니다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
//////////////////// server ///////////////////
// 클래스 : mem_fun()의 함수자 클래스
// 여러가지 기능을 제공할 수 있습니다. typedef argument_type, result_type 등....
class Point;
template <typename RType, typename CType>
class Mem_fun_ref_class
{
    RType (CType::*pf)();
public :
    Mem_fun_ref_class( RType (CType::*_pf)() ):pf(_pf) {}
    RType operator()( CType& d )
    {
        return (d.*pf)();
    }
};
// 함수 : 함수자를 반환하는 mem_fun() 함수
template <typename RType, typename CType>
Mem_fun_ref_class<RType, CType> Mem_fun_ref( RType (CType::*pf) () )
{
    return Mem_fun_ref_class<RType, CType>( pf );
}

/////////// client ///////////////////
class Point
{
    int x, y;
public:
    Point(int _x = 0, int _y = 0) : x(_x), y(_y) { }
    bool Print( )
    {
        cout << "(" << x << "," << y << ")" << endl;
        return true;
    }
};

void main()
{
    vector<Point> v(10);
    int i;

    for(i = 0 ; i < (int)v.size() ; i++ )
        v[i] = Point(i+1, i+1);

    for_each(v.begin(), v.end(), Mem_fun_ref(&Point::Print) );
    cout << "=============" << endl;
    Mem_fun_ref_class<bool, Point> functor(&Point::Print);
    for_each(v.begin(), v.end(), functor );
    // delete 생략
}
  1. (1,1)
    (2,2)
    (3,3)
    (4,4)
    (5,5)
    (6,6)
    (7,7)
    (8,8)
    (9,9)
    (10,10)
    =============
    (1,1)
    (2,2)
    (3,3)
    (4,4)
    (5,5)
    (6,6)
    (7,7)
    (8,8)
    (9,9)
    (10,10)



728x90
728x90

'함수 포인터와 함수자' 세 번째 시간으로 이번에는 '콜백'에 대한 이야기를 해보도록 하겠습니다.

이 페이지는 함수 포인터, STL 기초를 알고 있어야 합니다.

 

콜백(callback)은 서버가 클라이언트를 호출하는 것을 말합니다.

보통 함수 호출은 클라이언트에서 서버를 호출하는 형태입니다. 하지만 때때로 서버가 클라이언트를 호출해야 하는 경우가 있습니다.

콜백메커니즘을 이용하면 알고리즘 정책을 클라이언트에서 유연하게 바꿀 수 있습니다. 또 GUI의 강력한 이벤트 기능도 콜백메커니즘으로 구현됩니다.

STL의 많은 알고리즘도 콜백을 이용하여 클라이언트 정책을 반영합니다.

윈도우즈(Windows)의 모든 프로시저(procedure)는 시스템이 호출하는 '콜백 함수'입니다. 중요하게 느껴지죠?

 

예를 들어 보겠습니다.

어떤 기능이나 서비스를 제공하는 코드 측을 서버라고 합니다.

그 기능을 제공 받는 코드 측을 클라이어트라 합니다.

void func( )
{
    printf("Hello!\n");
 }
void main( )
{
    func();
    func();
    func();
}
  1. Hello!
  2. Hello!
  3. Hello!

 main()함수 쪽이 클라이언트이고 func()쪽이 서버라면 main()쪽이 호출자(caller)가 되고 func()쪽이 호출받는자(callee)가 됩니다.

그림으로 보면 아래와 같습니다.

 

 

 

 

 

이때, 아래와 같이 클라이언트 코드와 서버 코드가 있을 때 서버 측에서 클라이언트 측 코드인 client()함수를 호출해야 한다면 서버 측에서 client()함수는 콜백되는 것입니다.

#include <iostream>
using namespace std;
void client( );
//////// server //////////////////////
void func( )
{
    printf("Hello!\n");
    client( ); //클라이언트 코드 호출
}

//////// client //////////////////////
void client( )
{
    printf("난 client!\n");
}
void main( )
{
    func(); //서버 코드 호출
}
  1. Hello!
    난 client!

 main()함수 쪽이 클라이언트이고 func()쪽이 서버라면 client함수 호출쪽이 caller가 되고 client()함수 쪽이 callee가 됩니다.

 아래 그림을 참고하세요.

 

 

 

콜백은 서버 측에서 클라이언트 측의 코드를 호출하므로 클라이언트 측 함수를 알아야 합니다.

위 예제는 예를 들기 위함으로 client()라는 함수를 server가 알고 있다는 가정하에 만든 것입니다.

하지만 실제 서버는 여러 클라이언트에 의해 호출되므로 미리 클라이언트 코드를 알지 못합니다.

그래서 콜백을 구현하기 위해서는 클라이언트에서 서버에게 콜백으로 사용될 함수의 정보를 알려주고 서버가 클라이언트를 호출하도록 합니다.

 

 이제 실예로 STL코드를 보도록 하겠습니다.

STL 알고리즘의 for_each()을 예로 들겠습니다. for_each() 많이 쓰시죠?

for_each()는 반복자를 이용하여 컨테이너에 들어 있는 모든 요소를 반복적으로 처리하는 함수입니다.

이때 Print1(),Print2()가 콜백 함수입니다.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void Print1int n )
{
    cout << "int : " << n << endl;
}

void Print2int n )
{
    cout << "int : " << n << endl;
}
void main()
{
    vector<int> v(10);
    int i;

    for(i = 0 ; i < (int)v.size() ; i++ )
        v[i] = i+1;

    for_each(v.begin(), v.end(), Print1);

       cout << "=================" << endl;

    for_each(v.begin(), v.end(), Print2);
}

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  2. ===========================
  3. int : 1
    int : 2
    int : 3
    int : 4
    int : 5
    int : 6
    int : 7
    int : 8
    int : 9
    int : 10

 for_each()는 클라이언트 Print1(),Print2()함수를 콜백하며 클라이언트 기능을 유연하게 바꿔가며 호출합니다.

아래 그림을 참고하세요.

 

 

 

함수뿐만 아니라 함수류는 모두 가능합니다.

Functor를 사용한 예제입니다.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class CallbackClass
{
public:
    void operator() (int n)
    {
        cout << "int : " << n << endl;
    }
};
void main()
{
    vector<int> v(10);
    int i;

    for(i = 0 ; i < (int)v.size() ; i++ )
        v[i] = i+1;

    for_each(v.begin(), v.end(), CallbackClass() );
    cout << "=================" << endl;
    CallbackClass callback;
    for_each(v.begin(), v.end(), callback);
}
  1. int : 1
    int : 2
    int : 3
    int : 4
    int : 5
    int : 6
    int : 7
    int : 8
    int : 9
    int : 10
    =================
    int : 1
    int : 2
    int : 3
    int : 4
    int : 5
    int : 6
    int : 7
    int : 8
    int : 9
    int : 10

 함수자 클래스(CallbackClass)를 만들어 사용한 예제일 뿐 나머지는 같습니다.

 아래는 그림입니다.

 

 

 

 

이제 for_each()과 비슷한 알고리즘을 만들어 보도록 하겠습니다.

for_each()는 반복자를 이용하여 모든 컨테이너 요소를 지정한 함수류로 반복(콜백)합니다.

 아래는 예제 코드입니다.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/////////////// server //////////////////////
template<typename IterT, typename FunT>
void For_each(IterT biter, IterT eiter, FunT fun)
{
    for( ; biter != eiter ; biter++ )
        fun( *biter );
}

/////////////// client /////////////////////
class CallbackClass
{
public:
    void operator() (int n)
    {
        cout << "클래스 : " << n << endl;
    }
};
void Printint n )
{
    cout << "함수 : " << n << endl;
}
void main()
{
    vector<int> v(10);
    int i;

    for(i = 0 ; i < v.size() ; i++ )
        v[i] = i+1;

    For_each(v.begin(), v.end(), Print);
    cout << "=================" << endl;
    For_each(v.begin(), v.end(), CallbackClass() );
}
  1. 함수 : 1
    함수 : 2
    함수 : 3
    함수 : 4
    함수 : 5
    함수 : 6
    함수 : 7
    함수 : 8
    함수 : 9
    함수 : 10
    =================
    클래스 : 1
    클래스 : 2
    클래스 : 3
    클래스 : 4
    클래스 : 5
    클래스 : 6
    클래스 : 7
    클래스 : 8
    클래스 : 9
    클래스 : 10

 For_each()는 모든 컨테이너와 다양한 함수에 적용해야 하므로 템플릿 함수로 만들어 집니다.

 아래는 그림입니다.

 

 

 

출처 : http://blog.daum.net/coolprogramming/76

728x90
728x90

이 페이지는 함수자를 공부해 보도록 하겠습니다.

이 페이지를 공부하기 위해서는 연산자 중복, 템플릿을 알고 있어야 합니다.

 

함수자(functor)는 함수처럼 동작하는 객체입니다.

함수자는 STL의 어뎁터나 술어(predicate), 바인더, 콜백 등에 사용됩니다.

 

1, 함수자 만들기

함수자를 사용하면 함수를 호출하는 것인지? 객체로 함수를 호출하는 것인지를 추상화할 수 있습니다.

함수자는 operator() 연산자 중복된 클래스의 객체입니다.

그럼 간단한 예제코드를 만들어 보도록 하겠습니다.

 

전역 함수 호출 예

#include <iostream>
using namespace std;
void Print( )
{
    cout << "전역 함수!" << endl;
}
void main( )
{
    Print( );
}
  1. 전역 함수!

 간단하죠? ㅡㅡ;;

 

함수자를 이용한 호출 예

#include <iostream>
using namespace std;
class Functor
{
public :
    void operator( ) ( )
    {
        cout << "함수자!" << endl;
    }
};
void main( )
{
    Functor functor;

    functor( ); //함수를 호출한 것인지, 객체의 멤버 함수 operator()를 호출한 것인지 알 수 없습니다.
    functor.operator( ) ( ); // 실제 이렇게 멤버 함수를 호출합니다..
}

  1. 함수자!
  2. 함수자!

 functor객체를 만들어 함수를 호출합니다.

 

인자를 갖는 함수를 보도록 하겠습니다.

전역 함수 호출 예

#include <iostream>
using namespace std;
void Print(int n)
{
    cout << "정수 : " << n << endl;
}
void main( )
{
    Print( 10 );
}
  1. 정수 : 10

간단!

 

함수자를 이용한 호출 예

#include <iostream>
using namespace std;
class Functor
{
public :
    void operator( ) (int n)
    {
        cout << "정수 : " << n << endl;
    }
};
void main( )
{
    Functor functor;

    functor( 10 ); // 암시적 호출
    functor.operator( ) ( 10 ); //명시적 호출
}
  1. 정수 : 10
    정수 : 10

간단하죠?!

 

2, 함수, 함수자, 함수 포인터

이 세 가지는 모두 동일한 인터페이스를 갖습니다.

 

함수와 함수자, 함수 포인터를 이용한 함수 호출 예제입니다.

#include <iostream>
using namespace std;
class Functor
{
public :
    void operator( ) (int n)
    {
        cout << "정수 : " << n << endl;
    }
};

void Print(int n)
{
    cout << "정수 : " << n << endl;
}

void main( )
{
    Functor functor;
    void (*pfunc)(int ) = &Print;

    Print( 10 );  //함수 호출 인터페이스가 모두 동일합니다.
    functor( 10 );//함수 호출 인터페이스가 모두 동일합니다.
    pfunc( 10 );  //함수 호출 인터페이스가 모두 동일합니다.
}
  1. 정수 : 10
    정수 : 10
    정수 : 10

이 예제처럼 호출 인터페이스만 보면 일반 함수, 함수자, 함수 포인터가 모두 동일한 방식으로 사용된다는 것을 알 수 있습니다.

 

이제 템플릿을 이용하면 함수의 리턴 자료형과 매개 변수 자료형에 상관없이 함수자를 이용할 수 있습니다.

#include <iostream>
using namespace std;
template <typename RType, typename AType>
class Functor
{
public :
    RType operator( ) (AType n)
    {
        cout << "인자 : " << n << endl;
    }
};

void main( )
{
    Functor< voidint > functor;

    functor( 10 );
}
  1. 인자 : 10

이처럼 좀 더 일반화할 수 있습니다.


출처 : http://blog.daum.net/coolprogramming/76

728x90
728x90

C++에서 함수 포인터는 함수자(functor)를 구현할 때 유용하게 사용되며 STL이나 콜백 메커니즘을 구현할 때도 유용하게 사용될 수 있습니다.

 

1, 함수 포인터의 종류

C++언어의 함수는 세 가지 방식으로 호출할 수 있습니다.

  • 첫째, 정적 함수 호출(전역함수, 클래스 내의 정적함수, 네임 스페이스 내의 전역함수등)
  • 둘째, 객체로 멤버 함수 호출(객체 자체로 호출)
  • 셋째, 객체의 주소로 멤버 함수 호출(객체의 주소로 호출)

위 세 가지에 대한 함수 포인터 선언및 호출 방식이 다릅니다.

 

우리는 이미

첫째, 정적 함수의 주소를 저장하는 함수 포인터는 알고 있습니다.

 

여기서 다시 복습해 볼까요?

#include <iostream>
using namespace std;
class Point
{
    int x, y;
public:
    // ... 여러 멤버들 ..
    static void Print(int n) // 2.Point 클래스 static 함수
    {
        cout << "n = " << n << endl;
    }
};

void Print(int n) // 1.전역 함수
{
    cout << "n = " << n << endl;
}

namespace NetGong
{
    void Print(int n) // 3.NetGong 네임스페이스  내의 전역함수
    {
        cout << "n = " << n << endl;
    }
}

void main( )
{
    void (*pfunc)(int ); // 함수 포인터 선언 - C언어 QuickStart1 참고
   
    pfunc = &Print; // 1.전역함수 주소 저장
    pfunc( 10 ); // 1.전역함수 호출

    pfunc = &Point::Print; // 2.Point 클래스 static 함수 저장
    pfunc( 10 ); // 2.Point 클래스 static 함수 호출

    pfunc = &NetGong::Print; // 3.NetGong 네임스페이스  내의 전역함수 저장
    pfunc( 10 ); // 3.NetGong 네임스페이스  내의 전역함수 호출
}
  1. n = 10
    n = 10
    n = 10

뭐.. 별로 설명할 것이 없습니다. 

알아 둘 것은 위 세 가지 경우 모두 같은 포인터 변수를 사용한다는 것입니다. 호출 방식이 같거든요.

함수의 주소를 저장할 때 Print가 함수 주소이므로 &Print 와 같이 &를 붙여도 그만 붙이지 않아도 그만이지만 명시적으로 &붙여 사용합니다. 연산자의 위치와 용도에 따라 의미가 달라지는 걸 아시죠? 이곳에서 &가 있든 없든 같은 코드입니다. 또, 아래쪽에서 배울 맴버함수는 꼭 & 연산자가 있어야 합니다. 하여튼 &를 붙이는 방식을 선호하므로 꼭 붙여서 사용하는 습관을 들이세요.

 

이제 둘째, 셋째 멤버 함수호출인 멤버 함수 포인터를 사용하는 예제를 공부해 보도록 하겠습니다.

  • 멤버 함수 포인터 선언

 class Point
{

//멤버 ...
    void Print( )
    {
        cout << "(" << x << "," << y << ")" << endl;
    }
};

 void (Point::*pfunc)( ); //멤버 함수 주소를 저장하기 위한 함수 포인터 선언

 일반 함수 포인터라면 void (*pfunc)( ); 라고 선언했겠지만 Point 클래스의 멤버 함수 포인터이므로 Point::* 와 같은 연산자를 사용합니다.

  • 멤버 함수 포인터를 이용한 호출

    • 객체로 호출 : (객체.*pfunc)( );
    • 객체의 주소로 호출 : (객체주소->*pfunc)( );

.* 연산자와 ->*연산자를 이용하여 각각 객체와 객체의 주소로 함수 포인터가 가리키는 멤버함수를 호출할 수 있습니다. 또 연산자 우선순위 때문에 ( )로 묶어 주어야합니다. 주의하세요.

 

2, 멤버 함수 포인터 실습

그럼 실제 함수 포인터 예제를 보도록 하겠습니다.

#include <iostream>
using namespace std;

class Point
{
    int x, y;
public:
    Point(int _x = 0, int _y = 0) : x(_x), y(_y) { }
    void Print( )
    {
        cout << "(" << x << "," << y << ")" << endl;
    }
};

void main( )
{
    Point d1(2,3);
    Point *p = &d1;

    void (Point::*pfunc)( ); //멤버 함수 주소를 저장하기 위한 함수 포인터 선언
    pfunc = &Point::Print; 

    d1.Print(); //둘째, 객체로 멤버 함수 호출
    p->Print();  //셋째, 객체의 주소로 멤버 함수 호출

    (d1.*pfunc)( ); //둘째, 객체로 함수 포인터 이용한 멤버 함수 호출
    (p->*pfunc)( ); //셋째, 객체의 주소로 함수 포인터 이용한 멤버 함수 호출

    Point d2, d3(5,5);
    (d2.*pfunc)();
    (d3.*pfunc)();
}
  1. (2,3)
    (2,3)
    (2,3)
    (2,3)
    (0,0)
    (5,5)

객체는 연산자 .*를 사용하고 객체의 주소는 ->* 연산자를 사용한다는 것만 주의하세요.

 

또 다른 예제를 보겠습니다.

#include <iostream>
using namespace std;
class Point
{
    int x, y;
public:
    Point(int _x = 0, int _y = 0) : x(_x), y(_y) { }
    bool Compare(const Point& arg)
    {
        return x==arg.x && y == arg.y ? true : false ;
    }
    void Print( )
    {
        cout << "(" << x << "," << y << ")" << endl;
    }
};

void main( )
{
    Point d1(2,3);
    Point d2(5,5);
    Point *p = &d1;

    bool (Point::*pfunc)(const Point& ); //멤버 함수 주소를 저장하기 위한 함수 포인터 선언
    pfunc = &Point::Compare;

    cout << d1.Compare(d2) << endl; //둘째, 객체로 멤버 함수 호출
    cout << p->Compare(d1) << endl; //셋째, 객체의 주소로 멤버 함수 호출

    //둘째, 객체로 함수 포인터 이용한 멤버 함수 호출
    cout << (d1.*pfunc)(d2) << endl;
    //셋째, 객체의 주소로 함수 포인터 이용한 멤버 함수 호출
    cout << (p->*pfunc)(d1) << endl;
}
  1. 0
    1
    0
    1

출처 : http://blog.daum.net/coolprogramming/76

728x90
728x90

이번 장은 앞쪽 포인터부분 공부가 필수적입니다. 앞쪽 포인터부분을 이해했다면 이 장은 그냥 덤입니다. ㅡㅡ;;

1, void 포인터

모든 변수는 변수의 크기와 메모리  저장형식인 자료형이라는 것이 존재합니다. C언어에서 void는 '자료형이 없다', '자료형이 정해지지 않았다'라는 의미로 사용됩니다. 그렇다면 int n; 처럼 void v; 이 가능할까요? 당근 불가합니다. 다시말하지만(변수장에서 했습니다.) 자료형이란 변수의 연산, 크기, 저장형태을 나타낸다고 했습니다. void는 자료형이 없다는 의미이므로 변수를 만들 수 없습니다.

 

여러분 포인터의 크기는 얼마일까요? 모든 분들이 아실거라 믿습니다. 4byte입니다. 포인터란 무엇일까요? 주소를 저장하는 변수입니다. 

 

void 포이터란? 주소를 저장하는 포인터입니다. 음? 이게 다야? 네~ void 포인터는 자료형이 결정되지 않은(어떤 자료형의 주소든) 주소를 저장하는 포인터입니다. (malloc() 함수의 리턴 자료형이 void* 입니다. 함 생각해 보세요.)

 

void 포인터는 어떤 자료형의 주소든 모두모두 담을 수 있습니다.

void main( )
{
    void *pv;
    char c = 'A';
    int n = 10;
    double d = 3.14;

    pv = &c;
    pv = &n;
    pv = &d;
}

 이처럼 어떤 자료형의 주소든 저장할 수 있습니다. 어떤 자료형의 주소든 4byte를 넘지 않을 것이고 void* 형 pv는 단지 주소를 저장하는 포인터입니다. 그렇다면, 주소 pv로 할 수 있는 일은? 자료형이 없어서 아직 할 수 있는 일은 없습니다. 주소는 단지 주소일 뿐 어떤 데이터가 메모리공간에 저장돼 있는지 알 수 없으므로 의미가 없습니다.

 

왜? void 포인터가 의미 없는 주소인지 포인터를 복습해 보도록 할까요?

그림을 보며 설명드리도록 하겠습니다. (이해가 쉽지 않다면 포인터 장을 참고하세요)

void main( )
{
    int n = 10;
    int * pn = &n;

}

 

 

pn이 int* 이므로 *pn은 int형이 됩니다.

 

또, .....

void main( )
{
    int n = 10;
    int * pn = &n;

    int ** ppn = &pn;

}

 

 

 ppn이 int ** 형이므로 *pn은 int* 형이며 **ppn은 int형 정수가 됩니다.

 

이제 다시 void*를 볼까요?

void main( )
{
    void *pv;
    int n = 10;
    pv = &n;

*pv // 에러~~

}

 

 

 

*pv는 연산 에러(컴파일 에러)입니다. void*형에 *연산자를 사용할 수 없습니다.

 

가지고 있는 주소 메모리에 접근하려면 아래와 같이 주소 형변환 후 접근할 수 있습니다.

void main( )
{
    void *vp;
    char c = 'A';
    int n = 10;
    double d = 3.14;

    vp = &c;
    printf("%c\n", *(char*)vp );
    vp = &n;
    printf("%d\n", *(int*)vp );
    vp = &d;
    printf("%lf\n", *(double*)vp );
}
  1. A
    10
    3.140000

 vp를 각 자료형으로 변환한 후 *연산자로 메모리 접근합니다.

아래 그림을  참고하세요

 

 

보통 아래 예제처럼 사용합니다.

void main( )
{
    void *vp;
    int *pn;
    int n = 10;
   
    vp = &n;
    pn = (int*)vp;
    printf("%d\n", *pn );
}
  1. 10

pn변수에 대입 후 *pn으로 메모리 접근합니다. 위 예제는 바보 같습니다. 바로 pn을 사용하면 되는 데 복잡하게 사용하고 있으니까요. 하지만, 이것은 예제일 뿐이고 실전에서는 정당한 이 예제와 같이 사용해야 하는 정당한 이유가 있습니다. 지금은 패~스!

 

2, 함수 포인터

포인터는 주소를 저장하는 변수이므로 함수 포인터는 함수의 주소를 저장하는 포인터입니다.

어?? 어어?? 함수의 주소를 저장해? 함수도 주소가 있다는 말인가?

그렇습니다. 실제 함수를 호출하면 함수의 주소를 이용하여 호출합니다.

 

함수의 주소를 출력해 보도록 하겠습니다.

void func( )
{
}
void main ( ) {
    printf("main : %x\n", main);
    printf("func : %x\n", func);
    printf("printf : %x\n", printf);
}
  1. main : 411140
    func : 4111d1
    printf : 1025abb0

함수의 이름은 그 함수의 시작주소입니다. 배열의 이름도 그 배열이 시작하는 시작주소죠? 같습니다.

함수 포인터의 크기는 4byte입니다. 모든 포인터는 4byte입니다. 함수 포인터도 단지 주소를 저장하는 4byte 메모리 공간일 뿐입니다.

아래 그림을 참고하세요.

 

 

 

어떤 함수의 주소가 K라면 우리는 함수의 주소를 이용하여

  •  K( );
  •  함수주소( );

이렇게 함수를 호출하는 것입니다.

 

함수 포인터란 위와 같은 함수의 주소를 저장하는 포인터입니다.

int형 주소는 int형 포인터에

double형 주소는 double형 포인터에

함수형 주소는 함수형 포인터에 저장합니다.

 

함수 포인터 선언

함수의 원형만 알면 쉽게 포인터를 선언할 수 있습니다.

예로

void func( )
{
}

원형은 ?

void func( ); 입니다.

위 함수의 주소를 저장하는 포인터 변수 선언은 void (*pf)( );

 

void  func(int n )
{
}

원형은 ?

void func(int  ); 입니다. 

위 함수의 주소를 저장하는 포인터 변수 선언은 void (*pf)(int );

원형과 같고 변수에 (* ) 연산자를 붙이면 됩니다. 연산자가 없으면 함수 원형 선어이 되겠죠?

 

간단한  함수의 주소를 저장하는 예제입니다.

void func( )
{
    printf("함수 포인터 예제!\n");
}
void main ( )
{
    void (*pf)( );
    pf = func ;

    func( );
    pf( );
}
  1. 함수 포인터 예제!
    함수 포인터 예제!

 함수포인터 pf를 선언하고 함수의 주소(func)를 저장한 후 함수를 호출하고(pf() ) 있습니다.

 

아래와 같이 호출할 수도 있습니다.

void func( )
{
    printf("함수 포인터 예제!\n");
}
void main ( )
{
    void (*pf)( );
    pf = func ;

    func( );
    (*pf)( );
}
  1. 함수 포인터 예제!
    함수 포인터 예제!

 함수 포인터는 pf()와 (*pf)() 로 포인터가 가리키는 함수를 호출할 수 있습니다. pf는 함수의 주소인데 어떻게 (*pf)와 같나? 이렇게 의문이 생길 수 있지만 함수 포인터는 완벽히 동일합니다. pf는 함수의 주소이며 *pf도 함수의 주소입니다. 연산자 공부할 때 연산자는 위치와 사용 용도에 따라 연산자 의미가 달라진다고 했습니다.

pf와 *pf는 함수의 주소로 같다. 아래 예제를 보시죠!

void func( )
{
    printf("함수 포인터 예제!\n");
}
void main ( )
{
    void (*pf)( );
    pf = func ;

    printf("%x  %x  %x\n", func, pf, *pf);
}
  1. 4111d1   4111d1   4111d1

모두 같은 함수의 주소입니다.

 

하나의 함수 포인터로 서로다른 함수를 호출하는 예제입니다.

void func1(int n1, int n2)
{
    printf("함수 func1(  ) 인자 : %d, %d\n", n1, n2);
}
void func2(int n1, int n2)
{
    printf("함수 func2(  ) 인자 : %d, %d\n", n1, n2);
}
void func3(int n1, int n2)
{
    printf("함수 func3(  ) 인자 : %d, %d\n", n1, n2);
}
void main ( )
{
    void (*pf)(intint );
    pf = func1 ;
    pf( 10, 20);

    pf = func2 ;
    pf( 10, 20);

    pf = func3 ;
    pf( 10, 20);
}
  1. 함수 func1(  ) 인자 : 10, 20
    함수 func2(  ) 인자 : 10, 20
    함수 func3(  ) 인자 : 10, 20

 간단하죠? ㅡㅡ; 패~스!

 

마지막으로 함수 포인터를 사용하는 이유는 무엇일까요?

첫째, 서로 다른 함수에 대해 동일한 인터페이스를 제공한다. 같은 이름으로 서로다른 함수를 호출할 수 있다는 것입니다.

둘째, 실행시간에 알고리즘이나 기능을 변경하는 유연성을 제공한다.

셋째, 콜백(callback) 메커니즘을 제공한다. 등..


출처 : http://blog.daum.net/coolprogramming/76

728x90
728x90

map과 multimap은 '연관 컨테이너'입니다. 모든 연관 컨테이너(set, multiset, map, multimap)는 '노드 기반 컨테이너'입니다. 또 모든 연관 컨테이너는 균형 2진 트리로 구현됩니다. 균형 2진 트리는 보통 레드-블랙 트리를 사용합니다. map은 key와 value의 쌍으로 구성된 데이터 셋의 집합입니다. 여기서 key는 유니크합니다. multiset도 map과 같이 key와 value의 쌍으로 구성된 데이터 셋의 집합입니다. 단, multiset은 map과 달리 중복된 key가 존재할 수 있습니다.

 

map의 주요 개념을 그림으로 표현하면

 

 

set과 map은 데이터의 key가 유니크합니다.

 

multiset의 주요 개념을 그림으로 표현하면

 

 

multiset과 multimap은 같은 key값의 데이터를 컨테이너에 저장할 수 있습니다.

 

map, multimap의 주요 특징과 함수

 map도 set처럼 데이터를 추가하는 push_? 류의 함수를 가지고 있지 않습니다. 삽입(insert)한다는 의미에서 insert() 함수를 가지고 있습니다.

map의 특징은 key와 value의 쌍으로 데이터를 저장합니다. 그리고 key값을 이용하여 빠르게 value값을 찾을 수 있습니다. 연관 컨테이너에서 유일하게 [] 연산자 중복을 제공합니다.

 

기본적인 map 예제


#include <iostream>
#include <map>
using namespace std;
void main( )
{
    map<int , int > m;

    m[5] = 100;
    m[3] = 50;
    m[7] = 80;
    m[2] = 100;
    m[4] = 100;

    cout << m[5] << endl;
    cout << m[3] << endl;
    cout << m[7] << endl;
    cout << m[2] << endl;
    cout << m[4] << endl;
}
  1. 100
    50
    80
    100
    100

map은 key와 value의 쌍으로 구성된 데이터들의 집합입니다. []연산자의 인덱스는 key이며 인덱스가 가리키는 메모리 값이 value입니다.

주의할 점은 m[5] = 100 연산에서 5라는 key가 없으면 노드 '추가'가되며 5라는 key가 있으면 '갱신'이 됩니다. 아래에서 공부합니다.

그림으로 설명하면..

 

 

map의 트리 그림입니다.

 

 

map의 key값 5로 value를 접근하는 그림입니다.

 

 

 

 map은 key와 value의 쌍을 pair 객체로 저장합니다. STL의 쌍을 이루는 모든 요소는 pair 객체를 사용합니다.

위 예제는 insert()함수를 사용하여 아래 예제로 바꿀 수 있습니다.


#include <iostream>
#include <map>
using namespace std;
void main( )
{
    map<int , int > m;

    m.insert( pair<int,int>( 5, 100) );
    m.insert( pair<int,int>( 3, 50) );
    m.insert( pair<int,int>( 7, 80) );
    m.insert( pair<int,int>( 2, 100) );
    pair<int,intpr( 4, 100);
    m.insert( pr );

    cout << m[5] << endl;
    cout << m[3] << endl;
    cout << m[7] << endl;
    cout << m[2] << endl;
    cout << m[4] << endl;
}

  1. 100
    50
    80
    100
    100

 map은 key, value를 pair 객체로 각각 first와 second에 저장합니다.

그림으로 표현하면

 

 

 

 

반복자를 사용한 모든 key, value 출력 예제


#include <iostream>
#include <map>
using namespace std;

void main( )
{
    map<int , int > m;

    m[5] = 100;
    m[3] = 50;
    m[7] = 80;
    m[2] = 100;
    m[4] = 100;

    map<intint>::iterator iter;
    for( iter = m.begin(); iter != m.end() ; iter++)
        cout << "m["<<(*iter).first <<"]: " << (*iter).second << endl;

    cout << "==============" << endl;
    for( iter = m.begin(); iter != m.end() ; iter++)
        cout << "m["<<iter->first <<"]: " << iter->second << endl;
}
  1. m[2]: 100
    m[3]: 50
    m[4]: 100
    m[5]: 100
    m[7]: 80
    ==============
    m[2]: 100
    m[3]: 50
    m[4]: 100
    m[5]: 100
    m[7]: 80

 map도 양방향 반복자를 제공합니다.

그림으로 간단하게..

 

 

 

 

연관 컨테이너는 모두 동일한 함수군을 가지며 동작 방식도 같습니다.

map의 검색 함수들입니다.


#include <iostream>
#include <map>
using namespace std;

void main( )
{
    map<int , int > m;

    m[5] = 100;
    m[3] = 50;
    m[7] = 80;
    m[2] = 100;
    m[4] = 100;

    map<intint >::iterator iter;

    iter = m.find( 5 );
    if( iter == m.end() )
        cout << "없음!" << endl;
    else
        cout << "m["<<iter->first <<"]: " << iter->second << endl;

    iter = m.lower_bound( 5 );
    if( iter == m.end() )
        cout << "없음!" << endl;
    else
        cout << "m["<<iter->first <<"]: " << iter->second << endl;

    iter = m.upper_bound( 5 );
    if( iter == m.end() )
        cout << "없음!" << endl;
    else
        cout << "m["<<iter->first <<"]: " << iter->second << endl;

    pair< map<intint>::iterator, map<intint>::iterator> iter_pair;
    iter_pair = m.equal_range(5);

    if( (iter_pair.first) == m.end() && (iter_pair.second == m.end()) )
        cout << "없음!" << endl;
    else
    {
        cout << "m["<< iter_pair.first->first <<"]: " << iter_pair.first->second <<" ~ ";
        cout << "m["<< iter_pair.second->first <<"]: " << iter_pair.second->second <<endl;
    }
}
  1. m[5]: 100
    m[5]: 100
    m[7]: 80
    m[5]: 100 ~ m[7]: 80

 동작은 set에서 설명했으므로 패스~!

그림으로 설명하면...

 

 

 

 

 

마지막으로 multimap 예제입니다.

map과 다른 점은 [] 연산자가 없으며 key 값이 중복될 수 있습니다.

#include <iostream>
#include <map>
using namespace std;

void main( )
{
    multimap<int , int > m;

    m.insert( pair<int,int>( 5, 100) );
    m.insert( pair<int,int>( 3, 50) );
    m.insert( pair<int,int>( 7, 80) );
    m.insert( pair<int,int>( 2, 100) );
    m.insert( pair<int,int>( 4, 100) );
    m.insert( pair<int,int>( 7, 200) );
    m.insert( pair<int,int>( 7, 300) );

    pair<multimap<int,int>::iterator, multimap<intint>::iterator > iter_pair;
    iter_pair = m.equal_range( 7 );
    multimap<int,int>::iterator iter;
    for( iter = iter_pair.first ; iter != iter_pair.second ; iter++)
        cout << "m["<< iter->first <<"]: " << iter->second <<' ';
    cout << endl;

}
  1. m[7]: 80 m[7]: 200 m[7]: 300

 설명은 multiset과 같습니다.

아래는 위 예제의 그림입니다.

 



출처 : http://blog.daum.net/coolprogramming/76

728x90

'Basic Programming > STL' 카테고리의 다른 글

STL - Set, MultiSet 컨테이너  (0) 2016.04.04
STL - Deque 컨테이너  (0) 2016.04.04
STL - String 컨테이너  (0) 2016.04.04
STL - List 컨테이너  (0) 2016.03.23
STL - Vector 컨테이너  (0) 2016.03.23
728x90

이번 장은 트리 자료구조를 이해하시면 조금 빠르게 공부할 수 있습니다.


set과 multiset은 '연관 컨테이너'입니다. 모든 연관 컨테이너는 '노드 기반 컨테이너'입니다. 또 모든 연관 컨테이너는 균형 2진 트리로 구현됩니다. 균형 2진 트리는 보통 레드-블랙 트리를 사용합니다. set의 특징은 유니크(unique)한 데이터(key) 셋의 균형 2진 트리로 저장합니다. multiset은 같은 데이터(key) 값을 가질 수 있는 set입니다. 그래서 set과 multiset은 데이터(key) 값을 빠르게 검색하고자 할 때 사용하면 좋은 컨테이너입니다.

 

set의 주요 개념을 그림으로 표현하면

 


set과 map은 컨테이너의 저장 데이터가 유니크합니다.

 

multiset의 주요 개념을 그림으로 표현하면

 

 

multiset과 multimap은 같은 값의 데이터를 컨테이너에 저장할 수 있습니다.

 

1, set과 multiset의 주요 특징과 함수

set은 데이터를 추가하는 push_? 류의 함수를 가지고 있지 않습니다. set뿐만 아니라 모든 연관 컨테이너는 데이터를 트리에 삽입(insert)한다는 의미에서 insert() 함수를 가지고 있습니다. 모든 연관 컨테이너에 데이터를 삽입하면 균형 2진 트리 형태로(비교 함수자를 사용하여) 크기 비교를 하여 2진 트리를 유지합니다.

또 모든 연관 컨테이너는 저장된 데이터를 빠르게 검색할 목적으로 사용되므로 검색류의 함수들을 가지고 있습니다.

 

데이터(key)의 저장과 출력( 랜덤으로 데이터를 저장합니다.)

#include <iostream>
#include <set>
using namespace std;
void main( )
{
    set<int> s;

    s.insert( 50 );
    s.insert( 10 );
    s.insert( 80 );
    s.insert( 30 );
    s.insert( 70 );
    s.insert( 60 );

    set<int>::iterator iter;
    for( iter = s.begin(); iter != s.end() ; iter++)
        cout << *iter << ' ';
    cout << endl;
}
  1. 10 30 50 60 70 80

 set은 같은 값의 데이터(key)를 저장할 수 없는 컨테이너입니다. 모든 연관 컨테이너는 '양방향 반복자'를 제공합니다. 출력 결과를 보면 inorder 방법으로 모든 컨테이너 요소를 탐색하는 것을 알 수 있습니다.

사실 연관 컨테이너에서 모든 요소를 출력하는 것은 별 의미가 없습니다. 연관 컨테이너라는 것이 데이터(key)를 저장하고 저장된 데이터(key)를 빠르게 검색할 목적으로 사용되기 때문입니다. 연관 컨테이너의 검색시간은 '로그 시간'입니다.

 

set은 기본 '비교 함수자'로 less<>를 사용합니다.

아래 예제는 위 예제와 같습니다.

 #include <iostream>
#include <set>
using namespace std;
void main( )
{
    set<int , less<int> > s;

    s.insert( 50 );
    s.insert( 10 );
    s.insert( 80 );
    s.insert( 30 );
    s.insert( 70 );
    s.insert( 60 );

    set<int , less<int> >::iterator iter;
    for( iter = s.begin(); iter != s.end() ; iter++)
        cout << *iter << ' ';
    cout << endl;
}
  1. 10 30 50 60 70 80

 set의 두번째 템플릿 인자가 '비교 함수자'입니다. less<>는 operator < 연산자를 중복한 멤버 함수를 갖는 클래스 템플릿입니다. 그래서 트리의 왼쪽 자식으로 작은 데이터(key)값이 오른쪽 자식으로 큰 데이터(key) 값이 위치합니다. 함수자 less<>은 '함수자와 함수 포인터5'에 자세히 설명되어 있습니다.

 

함수자를 greater<> 로 바꾸면 트리의 왼쪽 자식으로 큰 데이터(key)값이 오른쪽 자식으로 작은 데이터(key) 값이 위치합니다.

greater<> 사용 예제

#include <iostream>
#include <set>
using namespace std;

void main( )
{
    set<int , greater<int> > s;

    s.insert( 50 );
    s.insert( 10 );
    s.insert( 80 );
    s.insert( 30 );
    s.insert( 70 );
    s.insert( 60 );

    set<intgreater<int> >::iterator iter;
    for( iter = s.begin(); iter != s.end() ; iter++)
        cout << *iter << ' ';
    cout << endl;
}
  1. 80 70 60 50 30 10

 결과는 less<>와 반대로 출력됩니다.

 

이제 연관 컨테이너의 주요 함수인 검색함수를 공부합니다.

 

find() 함수 사용 예제

#include <iostream>
#include <set>
using namespace std;

void main( )
{
    set<int> s;

    s.insert( 50 );
    s.insert( 10 );
    s.insert( 80 );
    s.insert( 30 );
    s.insert( 70 );
    s.insert( 60 );

    set<int>::iterator iter;
    iter = s.find( 80 );
    if( iter != s.end() )
        cout << *iter << endl;
    else
        cout << "없음!" << endl;

    iter = s.find( 100 );
    if( iter != s.end() )
        cout << *iter << endl;
    else
        cout << "없음!" << endl;
}
  1. 80
    없음!

 검색한 데이터(key)의 반복자를 반환합니다. 검색한 데이터가 없다면 끝을 표시하는 반복자를 반환합니다.

 

검색하는 내용에는 이 외에도 equal_range()와 lower_bound(), upper_bound()가 있습니다.

이 내용을 공부하기 위해서는 템플릿 클래스 pair<>를 알아야 합니다.

잠시 pair<> 클래스를 공부하고 가겠습니다. 아주 간단합니다.

 

pair<>는 하나의 쌍을 표현하기 위한 클래스입니다. STL의 쌍을 표현하는 모든 곳에서 pair<> 클래스를 사용합니다.

 

사용 예제입니다.

#include <iostream>
#include <string>
using namespace std;

void main( )
{
    pair<int , int> p1(10,20);
    cout << p1.first << ',' << p1.second << endl;

    pair<int , string> p2(10, "Hello!" );
    cout << p2.first << ',' << p2.second << endl;

    pair<string, string> p3( "Hello!""Hi");
    cout << p3.first << ',' << p3.second << endl;
    cout << p3.first.c_str() << endl;
    cout << p3.first.size() << endl;
    cout << p3.second.c_str() << endl;

    cout << p3.second.size() << endl;
}

  1. 10,20
    10,Hello!
    Hello!,Hi
    Hello!
    6
    Hi
    2

 pair<>는 공용 멤버 first와 second를 가지고 있습니다. first는 첫 번째 값을 second는 두 번째 값을 저장하는 변수입니다.

그림으로 간단하게...

 

 

 

다시 set 컨테이너의 검색 함수를 돌아옵니다.

equal_range() 함수는 찾은 데이터(key)의 구간 반복자를 쌍으로 반환합니다.

사실 set은 저장한 데이터(key)가 유니크하므로(예외?를 제외하고) 반복자 쌍을 사용할 필요는 없지만 multiset이나 multimap에서는 유용하게 사용됩니다. 또 모든 연관 컨테이너는 같은 종류의 멤버 함수들을 제공합니다.

 

equal_range()의 예제입니다.

#include <iostream>
#include <set>
using namespace std;

void main( )
{
    set<int> s;

    s.insert( 50 );
    s.insert( 10 );
    s.insert( 80 );
    s.insert( 30 );
    s.insert( 70 );
    s.insert( 60 );

    pair< set<int>::iterator, set<int>::iterator> iter_pair;
    iter_pair = s.equal_range(50);

    if( (iter_pair.first) == s.end() && (iter_pair.second == s.end()) )
        cout << "없음!" << endl;
    else
        cout << *iter_pair.first << ',' << *iter_pair.second << endl; //주의

    iter_pair = s.equal_range(100);
    if( (iter_pair.first) == s.end() && (iter_pair.second == s.end()) )
        cout << "없음!" << endl;
    else
        cout << *iter_pair.first << ',' << *iter_pair.second << endl; //주의
}
  1. 50,60
    없음!

 equal_range()는 찾음 데이터의 시작을 가리키는 반복자와 끝은 가리키는 반복자를 반환합니다.

여기서 주의할 점이 있습니다. set의 경우 찾은 데이터는 first 멤버가 되고 찾은 데이터 다음이 second가 됩니다. 이것은 lower_bound와 upper_bound에 해당합니다.

 

 그림으로 보면...

 

 

 

그래서 만약 마지막 데이터인 80을 검색하면 그림은 아래와 같습니다.

 

 

 

lower_bound와 upper_bound 예제입니다.

#include <iostream>
#include <set>
using namespace std;

void main( )
{
    set<int> s;

    s.insert( 50 );
    s.insert( 10 );
    s.insert( 80 );
    s.insert( 30 );
    s.insert( 70 );
    s.insert( 60 );

    set<int>::iterator iter_lower;
    iter_lower = s.lower_bound(50);

    if( iter_lower == s.end() )
        cout << "없음!" << endl;
    else
        cout <<"lower: " << *iter_lower<< endl; 

    set<int>::iterator iter_upper;
    iter_upper = s.upper_bound(50);
    if( iter_upper == s.end() )
        cout << "없음!" << endl;
    else
        cout <<"upper: " << *iter_upper<< endl; //주의
}
  1. lower: 50
    upper: 60

 lower_bound()와 upper_bound() 함수 모두 찾은 데이터(key) 위치의 반복자를 반환합니다.

여기서 주의할 점은 lower_bound()은 찾은 데이터 위치의 반복자지만 upper_bound()은 찾은 데이터 위치의 다음 요소를 가리키는 반복자입니다.

 그림으로 보면...

 

 

 

 

지금까지 배운 내용은 모두 multiset에도 적용됩니다.

multiset이 set과 다른 점은 같은 값의 데이터(key)를 저장할 수 있다는 것입니다.

 

간단한 multiset의 출력입니다.

#include <iostream>
#include <set>
using namespace std;
void main( )
{
    multiset<int> s;

    s.insert( 50 );
    s.insert( 10 );
    s.insert( 80 );
    s.insert( 80 );
    s.insert( 80 );
    s.insert( 30 );
    s.insert( 50 );
    s.insert( 70 );
    s.insert( 60 );

    multiset<int>::iterator iter;
    for( iter = s.begin(); iter != s.end() ; iter++)
        cout << *iter << ' ';
    cout << endl;
}
  1. 10 30 50 50 60 70 80 80 80

 50과 80 데이터가 중복으로 저장됩니다.

 

여기서 주요 함수가 equal_range()입니다.

#include <iostream>
#include <set>
using namespace std;

void main( )
{
    multiset<int> s;

    s.insert( 50 );
    s.insert( 10 );
    s.insert( 80 );
    s.insert( 80 );
    s.insert( 80 );
    s.insert( 30 );
    s.insert( 50 );
    s.insert( 70 );
    s.insert( 60 );

    multiset<int>::iterator iter;
    for( iter = s.begin(); iter != s.end() ; iter++)
        cout << *iter << ' ';
    cout << endl;

    pair<multiset<int>::iterator, multiset<int>::iterator > iter_pair;
    iter_pair = s.equal_range( 80 );
    for( iter = iter_pair.first ; iter != iter_pair.second ; iter++)
        cout << *iter << ' ';
    cout << endl;
}
  1. 10 30 50 50 60 70 80 80 80
    80 80 80

 equal_range() 함수를 사용하면 찾은 데이터의 구간 반복자 쌍을 얻을 수 있습니다.

설명은 그림으로...

 

 

 

 

2, typedef 내장 자료형

모든 컨테이너 클래스에는 typedef되어 있는 자료형들을 제공합니다. 이 자료형중 연관 컨테이너처럼 '비교 함수자'를 가지는 컨테이너는 key_comp()라는 함수를 제공합니다.

key_comp()함수를 사용하면 우리가 2진 트리를 정렬하기 위해 지정했던 함수자(기본 함수자 less<>) 객체를 리턴합니다.

 

key_comp()함수 예제

#include <iostream>
#include <set>
using namespace std;

void main( )
{
    multiset<int> s;

    s.insert( 50 );
    s.insert( 10 );
    s.insert( 80 );
    s.insert( 30 );
    s.insert( 70 );
    s.insert( 60 );

    less<int> key_less = s.key_comp();
    if( key_less( 10, 20 ) )
        cout << "true" << endl;
    else
        cout << "false" << endl;
    if( s.key_comp()( 10, 20) )
        cout << "true" << endl;
    else
        cout << "false" << endl;
}
  1. true
    true

 key_comp()함수가 less<> 객체를 리턴하므로 함수를 사용하여 지정했던 함수자 객체를 반환받거나 임의의 데이터를 비교할 수 있습니다.

 

greater<>를지정한 key_comp()함수 예제

#include <iostream>
#include <set>
using namespace std;

void main( )
{
    multiset<intgreater<int> > s;

    s.insert( 50 );
    s.insert( 10 );
    s.insert( 80 );
    s.insert( 30 );
    s.insert( 70 );
    s.insert( 60 );

    greater<int> key_greater = s.key_comp();
    if( key_greater( 10, 20 ) )
        cout << "true" << endl;
    else
        cout << "false" << endl;
    if( s.key_comp()( 10, 20) )
        cout << "true" << endl;
    else
        cout << "false" << endl;
}
  1. false
    false

 greater<>를 사용하므로 결과는 false 입니다.


출처 : http://blog.daum.net/coolprogramming/76

728x90

'Basic Programming > STL' 카테고리의 다른 글

STL - Map, MultiMap 컨테이너  (0) 2016.04.04
STL - Deque 컨테이너  (0) 2016.04.04
STL - String 컨테이너  (0) 2016.04.04
STL - List 컨테이너  (0) 2016.03.23
STL - Vector 컨테이너  (0) 2016.03.23
728x90

deque 컨테이너는 '시퀀스 컨테이너'이며 '연속 메모리 기반 컨테이너'입니다.

연속 메모리 기반 컨테이너의 특징이 필요하고 요소의 추가, 삭제가 앞쪽과 뒤쪽에서 빈번하게 발생한다면 deque를 사용하는 것이 좋습니다.

 

deque의 주요 개념을 그림으로 표현하면

 

 

그림에서 보는 것처럼 deque는 앞과 뒤에 데이터 요소들이 추가될 수 있는 형태입니다. 또 데이터 요소를 저장하는 여러 개의 메모리 단위를 갖습니다.

그래서 요소의 추가, 삭제가 앞쪽과 뒤쪽에서 빈번하게 발생하더라도 vector나 string 처럼 메모리를 재할당하고 컨테이너의 모든 요소를 복사할 필요 없이 새로운 메모리 단위를 할당하여 새 요소만 추가하면 됩니다. 또 컨테이너의 중간 요소가 삽입(insert), 삭제(erase) 되더라도 요소들을 뒤쪽이나 앞쪽으로 모두 밀어낼 수 있으므로 vector나 string 보다 더 좋은 성능을 갖습니다. 위 그림을 이해한다면 그리 어렵지 않게 알 수 있을 것입니다.

 

1. deque의 특징과 주요 함수

 앞, 뒤로 추가, 삭제될 수 있으므로 push_front(), push_back(), pop_front(), pop_back()함수 등을 가지고 있으며 연속 메모리 기반 컨테이너이므로 []연산자 함수를 제공합니다.

 

데이터 추가와 출력

#include <iostream>
#include <deque>
using namespace std;
void main( )
{
    deque<int> dq;

    for(int i = 0 ; i < 10 ; i++)
        dq.push_back( (i+1)*10 );

    forint i = 0 ; i < dq.size() ; i++)
        cout << dq[i] << ' ';
    cout << endl;
}
  1. 10 20 30 40 50 60 70 80 90 100

 데이터를 추가하더라도 일정한 메모리 단위가 생성되며 요소들이 추가됩니다.

위 예제의 그림입니다. 새로운 메모리 단위만 생성할 뿐 vector처럼 메모리 재할당 및 모든 요소 복사가 발생하지 않습니다.

 

 

 

 

앞쪽에 데이터 요소를 추가하더라도 효율적으로 메모리를 생성합니다.

#include <iostream>
#include <deque>
using namespace std;

void main( )
{
    deque<int> dq;

    for(int i = 0 ; i < 10 ; i++)
        dq.push_back( (i+1)*10 );

    forint i = 0 ; i < dq.size() ; i++)
        cout << dq[i] << ' ';
    cout << endl;

    dq.push_front(100);
    dq.push_front(200);
    dq.push_front(300);
    forint i = 0 ; i < dq.size() ; i++)
        cout << dq[i] << ' ';
    cout << endl;
}
  1. 10 20 30 40 50 60 70 80 90 100
    300 200 100 10 20 30 40 50 60 70 80 90 100

 push_front()로 앞쪽에 데이터 요소를 추가하면 새로운 메모리 단위를 생성하고 데이터를 추가합니다.

위 예제의 그림입니다.

 

 

 

또 컨테이너 요소 중간에 삽입(insert), 삭제(erase) 하더라도 데이터 요소의 개수가 작은 쪽(앞쪽, 뒤쪽)을 밀어냅니다. vector 처럼 메모리가 부족하면 재할당하고 모든 요소를 복사할 필요없이 새로운 메모리 단위만 할당하면 됩니다.

간단한 insert() 예제입니다.

#include <iostream>
#include <deque>
using namespace std;

void main( )
{
    deque<int> dq;

    for(int i = 0 ; i < 10 ; i++)
        dq.push_back( (i+1)*10 );

    forint i = 0 ; i < dq.size() ; i++)
        cout << dq[i] << ' ';
    cout << endl;

    deque<int>::iterator iter = dq.begin()+1;

    iter = dq.insert( iter , 500);


    forint i = 0 ; i < dq.size() ; i++)
        cout << dq[i] << ' ';
    cout << endl;
    cout << *iter << endl;
}


  1. 10 20 30 40 50 60 70 80 90 100
    10 500 20 30 40 50 60 70 80 90 100
    500

 중간에 데이터 요소를 삽입하면 삽입할 위치를 기준으로 앞쪽 요소의 개수가 작으므로 앞쪽으로 밀어냅니다. 삽입은 항상 반복자가 가리키는 위치 앞에 삽입됩니다. erase도 비슷하게 동작합니다.

위 예제의 그림입니다.

 

 

 

 

 

2, deque의 반복자

deque는 '연속 메모리 기반 컨테이너'이므로 임의 접근 반복자를 제공합니다.

 

반복자 예제

#include <iostream>
#include <deque>
using namespace std;

void main( )
{
    deque<int> dq;

    for(int i = 0 ; i < 10 ; i++)
        dq.push_back( (i+1)*10 );

    deque<int>::iterator iter;
    for( iter = dq.begin(); iter != dq.end() ; iter++)
        cout << *iter << ' ';
    cout << endl;

    iter = dq.begin() + 3;
    cout << *iter << endl;
    iter -= 3;
    cout << *iter << endl;
}
  1. 10 20 30 40 50 60 70 80 90 100
    40
    10

deque도  vector, string처럼 '임의 접근 반복자'를 제공합니다.


출처 : http://blog.daum.net/coolprogramming/76

728x90

'Basic Programming > STL' 카테고리의 다른 글

STL - Map, MultiMap 컨테이너  (0) 2016.04.04
STL - Set, MultiSet 컨테이너  (0) 2016.04.04
STL - String 컨테이너  (0) 2016.04.04
STL - List 컨테이너  (0) 2016.03.23
STL - Vector 컨테이너  (0) 2016.03.23

+ Recent posts