[C++] 8. 함수포인터, 멤버함수 포인터

2011. 6. 1. 23:00C++

퍼온 자료이나, 원본주소를 알지 못하겠군요.
---------------------------------------------------


함수포인터, 멤버함수 포인터

C++ 2011/03/09 14:46
함수 포인터

함수 포인터를 선언하는 형식: 리턴타입 (* 변수명)(인수목록)


int Function(int a) {
...
}

int main() {
  int (*pf)(int) = Function; // 이런 초기화및 대입이 가능한 이유는 함수명은 함수의 시작
                                    // 번지
를 나타내는 포인터 상수이기 때문이다. 배열명과 같음.
         // 함수 이름 자체가 포인터 타입이므로 &Function으로 쓸 필요 없다.
  pf();    // 함수 호출. 엄격하게 따지면 에러이지만, 컴파일러가 이미 함수 포인터라는 걸
           // 알고 있기 때문에 굳이 (*pf)라고 하지 않아도, pf가 가리키는 함수를
           // 호출하라는 것을 알수 있기 때문에 모호하지 않다.

  (*pf)();  // 동일한 함수 호출.
}


void Function() {
  std::cout << "abc" << std::endl;
}

int* Temp(int*, double) {
  std::cout << "Temp" << std::endl;
  return NULL;
}

int main() {
  typedef void (*pf)(void);
  typedef int* (*pf1)(int*, double);
   
  pf p = Function;
  pf1 p1 = Temp;
 
  int a= 100;
 
  p = (void (*)(void))p1;    // 함수 포인터 캐스트
}
여기서 p는 컴파일 타임에 void (*)(void); 타입이다.
따라서, p1을 (void (*)(void))로 캐스팅 하고 p에 대입하여도, p는 void(*)(void)타입으로 인식된다.
즉, 캐스팅 후에 p는 Temp 함수의 주소를 가지고 있지만, 함수 시그니쳐는 void에 파라미터가 없다.
그러므로, p(&a, 1.1); 형태로 호출하면 컴파일 에러가 난다.
호출은 원래 타입대로 p();로 호출해야 하며, 호출시 진입점은 Temp 함수이므로 "Temp"란 메시지가 출력되게 된다.

함수 포인터 배열
int (*arpf[5])(int);
함수 포인터의 포인터
int (**ppf)(int);

함수 포인터 타입은 복잡하기 때문에 간단히 typedef로 사용하도록 하자.
typedef int (*PFTYPE)(int);
PFTYPE arpf[5]; // 위와 동일한 함수포인터 배열
PFTYPE *ppf;     // 위와 동일한 함수포인터의 포인터

함수 포인터는 다른 포인터와는 달리 ++, --와 같은 연산자를 사용할 수 없으며 정수와 가감 연산도 할 수 없다. 함수는 코드 덩어리이며 이 덩어리의 크기는 가변적이고 실행중에 변경할 수없기 때문에 당연한 이야기이다.


함수 포인터 리턴

fp의리턴타입 (*함수명)(인수목록))(fp의인수목록)


int f1(int a, double b) {
  return 1;
}
int f2(int a, double b) {
  return 2;
}
int (*SelectFunc(char ch))(int,double) {   // 무진장 복잡하네...
  if (ch == 'a')
    return f1;
  else
    return f2;
}
void main() {
  int (*fp)(int,double);
  fp=SelectFunc('a');
  printf("리턴된 값 = %d\n",fp(1,2.3));
}

간단히 하기 위해서 typedef를 활용하자.
typedef int (*FP)(int, double);

FP SelectFunc(char ch) {
 ...
}

멤버 포인터
멤버 포인터 변수란 특정 클래스에 속한 멤버만을 가리키는 포인터이다. 일반 포인터가 메모리상의 임의 지점을 가리킬 수 있는데 비해 객체 내의 한 지점만을 가리킨다는 점에서 독특하다.

class Alber {
 public:
  int member_variable_;
};

int main() {
  int Alber::*mem_pf = &Alber::member_variable_;  // 멤버 변수를 가리킨다.
  /* 특정 변수의 번지를 가리키도록 하는 것이 아니라 클래스의 어떤 멤버를 가리킬 것인가
      만 초기화 하는 것이므로 이 상태에서 멤버 포인터 변수에 대입되는 번지가 결정되는 것
      은 아니다. 다만 가리키는 멤버가 클래스의 어디쯤에 있는지 위치에 대한 정보만을
      가질 뿐이다. 클래스 전체를 하나의 작은 주소 공간으로 보고 클래스내의 멤버 위치를
      기억하는 것이다. */

  Alber alber;
  alber.*mem_pf = 10;  // alber가 포인터라면 alber->*.mem_pf;  
 
  return 0;
}

class Test;
typedef void (Test::*fpop)(int,int);

class Test {
 public:
  void DoCalc(fpop fp,int a,int b) {
    puts("지금부터 연산 결과를 발표하겠습니다.");
    printf("%d와 %d의 연산 결과 : ",a,b);

    (this->*fp)(a,b);        // 멤버 함수는 반드시 호출하는 객체에 대한 정보를 가지
                             // 는
this라는 암시적인 인수를 전달방아야 한다.
                             // 멤버 함수 포인터는 함수 포인터와 달리 *를 명시해야
                             // 한다.


    puts("이상입니다.");
  }

  void Op1(int a,int b) { printf("%d\n",a+b); }
  void Op2(int a,int b) { printf("%d\n",a-b); }
  void Op3(int a,int b) { printf("%d\n",a*b); }
};

void main() {
  int ch;
  Test t;
  int a=3,b=4;

  static fpop arop[3]={&Test::Op1,&Test::Op2,&Test::Op3};
  printf("연산 방법을 선택하시오. 0=더하기, 1=빼기, 2=곱하기 : ");
  scanf("%d",&ch);

  t.DoCalc(arop[ch],3,4);
  t.DoCalc(&Test::Op1, 3, 4);  // 멤버 함수 포인터는 함수 포인터와 달리 &를
                                       // 생략할 수 없다.

}