지난 포스트에서 변수의 포인터에 대해서만 다루었는데, 함수도 포인터를 사용할 수 있다. 우리가 작성한 코드는 기계어로 바뀌어 모두 코드영역에 저장되는데, 함수 포인터는 함수의 내용이 저장된 주소를 가리키는 것이다. 사실 사용한 경험도 거의 없고, 선언 방식도 좀 생소해서 내용을 정리해두려고 글을 작성하게 되었다.
함수 포인터를 선언하는 방식 : 반환타입(*변수 이름)(인자 타입)
예를 들어 아래와 같이 반환 타입이 int고, 인자를 받지 않는 함수를 pFunc이라는 포인터 변수에 받고 싶다면, int(*pFunc)(void) = &func;이라고 선언할 수 있다. void 는 인자가 없음을 의미한다. 반환타입이 void인 경우는 void(*pFunc)(void)로 선언하면 된다. 그리고 이 포인터에 받는 값은 &func이라고 써야 하지만, '&'를 떼고 func라고만 써도 동일하게 작동한다. 함수의 이름 자체가 함수의 주소를 의미하도록 되어있기 때문이다.
이후 그 포인터변수에 ()를 붙여서 함수처럼 실행할 수 있게 된다.
#include <iostream>
int func ()
{
printf("func() called\n");
return 0;
}
int main()
{
// 선언
int(*pFunc)(void) = &func;
pFunc = func;
// 실행
pFunc();
return 0;
}
사실 이것만 봐선 어떻게 활용해야할 지 감이 잘 오지 않는다. 아래에서 iostream 라이브러리의 endl을 간단하게 구현해보며 이해해보자.
연산자 오버로딩을 활용해 std::cout과 std::endl과 같이 구현해 보았다. 아직 레퍼런스 변수나 클래스에서 static 키워드의 활용에 대해 정리하지 않아서 비교적 단순하게 구현했다.
우선 MyOStream 클래스의 << 연산자를 2가지 버전으로 오버로딩 했다.
첫 번째는 const char*로 인자를 받는데, 문자열을 수정하지 않고 받겠다는 의도로 const char*로 인자를 받고, 내부에서 printf()를 이용해서 출력하고 종료된다.. 리턴타입은 MyOStream인데, << 연산자를 연달아서 사용하기 위해 << 연산이 한 번 일어나고나서 MyOStream 객체를 반환한다. 예를 들어 main 함수에서 MyCout << "Hi!!" 를 실행하고 나서 MyOStream 객체가 반환되므로 바로 다시 << 연산자를 사용할 수 있게 되는 것이다.
두 번째 오버로딩은, 함수 포인터를 인자로 받아서 그 함수를 실행하고 종료한다. 그리고 클래스 밖에서 리턴 타입이 void이고 인자를 받지 않는 MyEndl()함수를 구현해두었고, main 함수에서 << 연산자의 피연산자로 MyEndl 함수의 주소를 받는 것이다. 그러면 두 번째 오버로딩 함수로 들어와서 MyEndl의 함수를 실행하고, 종료된다. 이 때 MyEndl이 개행 문자를 출력하므로 std::endl과 같이 동작하게 되는 것이다.
실제로 std::endl로 함수로 구현되어 있다.
#include <iostream>
void MyEndl ()
{
printf("\n");
}
class MyOStream
{
public:
MyOStream operator << (const char* _str)
{
printf(_str);
return *this;
}
void operator << (void(*_pFunc)(void))
{
_pFunc();
}
};
int main()
{
MyOStream MyCout;
MyCout << "Hi!!" << MyEndl;
return 0;
}
[C++] 함수 객체 (Functor) (0) | 2024.08.08 |
---|---|
[C++] 포인터 (0) | 2024.03.19 |
[C++] Struct (구조체) (0) | 2024.03.12 |
[C++] 변수의 종류와 메모리 영역 (0) | 2024.03.01 |
[C++] 자료형 (Data Type) (0) | 2024.02.29 |