함수 포인터(function pointer)
기본적으로 포인터는 어떠한 변수의 주소를 저장하고 가리키는 역할을 한다. 즉, 함수 포인터 또한 함수의 주소를 저장하여 가리키는 변수라는 뜻이다.
int foo() // foo 함수의 메모리 주소가 0x002717f0라고 가정
{
return 5;
}
int main()
{
foo(); // 주소 0x002717f0로 점프한다.
return 0;
}
위의 예시에서 foo는 함수의 이름이고, int(정수)를 반환하며 매개 변수가 없는 함수 타입이다. 여기서 변수와 마찬가지로
함수에도 메모리에 할당된 주소가 존재한다.
main에서 () 연산자를 통해 foo함수를 호출하면, 호출되는 함수의 주소로 점프하여 실행하는 방식이다.
따라서 cout으로 foo함수를 출력하면 주소가 나올 것이다.
함수 포인터에는 상수와 비 상수, 2가지 경우가 존재한다. 우선 비 상수 함수 포인터(non-const function pointer)의 문법이다.
//반환형 (*함수이름)(매개변수);
ex)int (*fcnPtr)(); -> fcnPtr은 매개변수가 없고, 정수(int)를 반환하는 함수에 대한 포인터
위의 예시처럼 fcnPtr은 매개변수가 없고, 정수를 반환하는 포인터이므로, 해당하는 타입과 같은 함수를 가리킬 수 있다.
여기서 (*fcnPtr)에 괄호가 필요한 이유는 연산 우선순위를 위해서 해주었다. 만약 괄호가 없었다면 int* fcnPtr();처럼
전혀 다른 함수로 변해버리기 때문이다.
다음으로 상수 함수 포인터(const function pointer)를 만들기 위해서는 * 뒤에 const 키워드를 붙이면 된다.
//반환형 (*const 함수이름)(매개변수);
ex) int (*const fcnPtr)();
이것도 const의 위치를 주의해서 사용해주도록 하자.
함수 포인터 할당
함수 포인터는 다음과 같이 함수로 초기화할 수 있다.
int foo()
{
return 5;
}
int goo()
{
return 6;
}
int main()
{
int (*fcnPtr)() = foo; //foo함수를 가리키는 fcnPtr
fcnPtr = goo; //지금은 goo함수를 가리키는 fcnPtr
return 0;
}
C++에서는 함수를 함수 포인터로 암묵적으로 변환해주어서 주소 연산자(&)를 사용해줄 필요는 없다.
함수 포인터를 사용해서 함수 호출하기
함수 포인터를 이용하여 가리키고 있는 함수를 호출하는 방법이 두 가지 있다.
첫 번째 방법인 명시적인 역참조 방법이다.
int foo(int x)
{
return x;
}
int main()
{
int (*fcnPtr)(int) = foo; //fcnPtr에 foo함수를 할당한다.
(*fcnPtr)(5); //foo(5)를 fcnPtr로 호출한다.
return 0;
}
그리고 두 번째 방법인 암시적 역참조 방법이다.
fcnPtr(5); //foo(5)를 fcnPtr로 호출한다.
일반적인 함수 이름은 함수의 포인터가 되기 때문에 암시적 역참조 방법은 일반 함수의 호출과 똑같이 생겼다.
다른 함수의 인자로 함수 전달하기
함수 포인터의 중요한 기능 중 하나로, 함수를 다른 함수에 전달하는 것이다. 다른 함수에 대한 인수로써 사용되는 함수를
콜백 함수라고 부른다.
밑의 예시를 먼저 보도록 하자.
void SelectionSort(int* array, int size)
{
for (int i = 0; i < size; ++i)
{
int smallestIndex = i;
for (int j = i + 1; j < size; ++j)
{
if (array[smallestIndex] > array[j]) //크기 비교 부분
smallestIndex = j;
}
swap(array[smallestIndex], array[j]);
}
}
if문의 비교 부분을 따로 함수를 만들어서 대체할 수 있다. 비교해줄 함수는 두 개의 정수를 매개변수로 받아와 비교한
다음, bool값으로 반환하여 교체 여부를 알아낼 수 있다.
bool ascending(int x, int y)
{
return x > y; //첫 번째 변수(x)가 두 번째 변수(y) 보다 크면 교환한다.
}
이제 위의 방법처럼 만든 asceding() 함수를 이용해서 비교하는 선택 정렬 함수로 바꾼다.
void SelectionSort(int* array, int size)
{
for (int i = 0; i < size; ++i)
{
int smallestIndex = i;
for (int j = i + 1; j < size; ++j)
{
if (ascending(array[sammlestIndex], array[j])) //선택 정렬 함수로 변경 부분
smallestIndex = j;
}
swap(array[smallestIndex], array[j]);
}
}
이렇게 만들어진 비교 함수(asceding())를 사용하는 대신 SelectionSort() 함수를 호출할 때 비교 작업을 결정할 수 있도록
함수 포인터를 사용해보도록 하자.
호출 시 비교 함수는 두 정수를 비교하고 bool값을 반환하므로 그에 맞는 함수 포인터는 이렇게 선언해주면 될 것이다.
bool (*comparisonFcn)(int, int); //함수 포인터
그리고 SelectionSort()함수를 호출 시에 정렬 함수에 원하는 비교 함수의 포인터를 세 번째 매개변수로 받도록 해서,
호출자의 함수를 사용하여 비교를 수행하도록 한다.
이와 같은 방법이 함수 포인터 매개변수를 사용하여 사용자 정의 비교를 수행할 수 있는 선택 정렬이다.
//세 번째 매개변수로 함수 포인터를 추가
void SelectionSort(int* array, int size, bool (*comparisonFcn)(int, int))
{
for (int i = 0; i < size; ++i)
{
int smallestIndex = i;
for (int j = i + 1; j < size; ++j)
{
if (comparisonFcn(array[sammlestIndex], array[j])) //비교 부분
smallestIndex = j;
}
swap(array[smallestIndex], array[j]);
}
}
//오름차순으로 해줄 함수
bool ascending(int x, int y)
{
return x > y;
}
//내림차순으로 해줄 함수
bool descending(int x, int y)
{
return x < y;
}
//정렬 후의 배열을 출력해줄 함수
void printArray(int *array, int size)
{
for (int index=0; index < size; ++index)
cout << array[index] << " ";
cout << '\n';
}
int main()
{
int array[9] = { 3, 7, 9, 5, 6, 1, 8, 2, 4 };
// 내림차순 선택정렬
selectionSort(array, 9, descending); //세 번째 매개변수로 내림차순 함수
printArray(array, 9);
// 오름차순 선택정렬
selectionSort(array, 9, ascending); //세 번째 매개변수로 오름차순 함수
printArray(array, 9);
return 0;
}
이러한 방식으로 호출자가 원하는 비교 방식으로 정렬할 수 있게 되었다.
지금처럼 함수 포인터의 문법(int (*fcnPtr)())을 사용하는 방법도 있지만, typedef를 사용하면 함수 포인터를 일반 변수처럼
깔끔하게 문법을 바꿀 수 있다.
typedef bool (*validateFcn)(int, int);
위 방법은 두 개의 매개변수 int를 가진 bool 반환형 validateFcn 함수 포인터를 나타내고 있다.
저런 방식 대신에 밑의 예시와 같은 방법도 있긴 하다.
bool validate(int x, int y, bool (*fcnPtr)(int, int));
하지만 좀 더 복잡해 보이기도 하는데, 그래서 좀 더 깔끔한 밑의 방법도 있다.
bool validate(int x, int y, validateFcn pfcn)
그리고 C++에서 using을 사용한 함수 포인터 타입에 대한 별칭을 만들 수도 있다고 한다.
이는 typedef보다 좀 더 보기 편하다.
using validateFcn = bool(*)(int, int);
functional 헤더 파일
C++의 표준 라이브러리인 <functional> 헤더 파일에 정의되어 있는 std::function을 이용하면 함수 포인터를
정의할 수 있다.
#include <functional>
bool validate(int x, int y, std::function<bool(int, int)> fcn);
//std::function<반환타입(매개변수)>
이처럼 반환형과 매개변수는 < > 안에 들어가 있어서 좀 더 가독성이 좋은 듯하다. 만약 매개변수가 없다면 괄호() 안을
비워두기만 하면 된다.
#include <functional>
#include <iostream>
using namespace std;
int foo()
{
return 5;
}
int goo()
{
return 6;
}
int main()
{
function<int()> fcnPtr; //int를 반환하고 매개 변수가 없는 함수 포인터 변수 fctPtr 선언
fcnPtr = goo; //fcnPtr은 함수 goo를 가리킨다.
cout << fcnPtr(); //일반적인 함수처럼 함수를 호출할 수 있다.
return 0;
}
'C++' 카테고리의 다른 글
C++ 배우기 29(시프트 연산자, 비트 단위 연산자) (0) | 2022.12.13 |
---|---|
C++ 배우기 28(WinAPI) (0) | 2022.12.05 |
C++ 배우기 26(LValue / RValue) (0) | 2022.11.30 |
C++ 배우기 25(복사생성자, 팩토리 패턴) (2) | 2022.11.29 |
C++ 배우기 24(가상함수, 재정의) (0) | 2022.11.25 |