안녕하세요, katte입니다.
이번 글에서는 함수 포인터와 함수 포인터 배열, 함수 포인터 배열의 포인터(...)에 대해 다뤄보겠습니다.
배열 포인터에 관해서는 아래의 글을 참고해주세요.
https://katteniiki.tistory.com/19
포인터는 C나 C++의 문법을 보기 껄끄럽게 만드는 요소중 하나입니다. 특히 함수 포인터와 배열 포인터로 여러 차례 꼬인 경우 한눈에 자료형을 파악하기 힘들죠. 물론 이런 경우를 위해 auto라는 키워드를 제공하고는 있습니다.
아무튼 그래서 이번 글에서 다룰 함수 포인터는 함수를 가리키는 포인터입니다.
함수를 가리킨다는 것이 잘 와닿지 않을 수 있지만, 함수 또한 엄연히 메모리 주소를 차지하고 있기 때문에 포인터를 통해 가리킬 수 있습니다.
함수의 메모리 주소라 하면, 그 함수의 기계어 코드가 저장되어 있는 메모리 블록의 시작 주소를 의미합니다.
일반적으로 생각했을 때, 함수의 주소를 사용할 일은 별로 없다고 느껴질 수 있습니다.
그러나 함수를 매개변수로 취하는 함수 등 특수한 경우엔 함수 포인터가 유용하게 사용될 수 있습니다.
함수 포인터를 선언하고 사용하기 위해서는 다음과 같은 절차가 필요할 것입니다.
1. 함수의 주소를 얻는다.
2. 함수를 지시하는 포인터를 선언한다.
3. 함수를 지시하는 포인터를 사용하여 그 함수를 호출한다.
1)
함수의 주소를 얻는 방법은 간단합니다.
함수 뒤의 괄호를 빼고 함수의 이름만을 사용하면 됩니다. 즉, ftn()이란 함수가 있을 때, ftn은 이 함수의 주소를 의미합니다.
2)
포인터를 선언할 때, 우리는 포인터가 가리킬 자료형에 대해 명시해야 합니다.
함수 포인터도 마찬가지로 선언 시에 함수에 대한 정보를 제공해주어야 합니다.
이 정보에는 함수의 리턴형과 매개변수 리스트가 포함됩니다.
따라서 다음과 같이 선언하고 할당합니다.
double ftn(int);
int main()
{
double (*pftn)(int) = ftn;
//선언과 초기화를 분리할 땐 pftn = ftn;
//double *pftn(int);(X) double*형을 리턴하는 함수의 선언
}
함수 포인터를 선언할 때 괄호를 빼면 안 됩니다.
괄호는 *연산자보다 우선순위가 높기 때문에, 괄호를 뺀다면 double* 형을 리턴값으로 갖는 pftn이라는 함수의 선언이 될 것입니다.
3)
pftn이 함수 포인터라면, (*pftn)은 함수입니다.
따라서 함수의 호출은 다음과 같은 방식으로 이루어 집니다.
double ftn(int);
double (*pftn)(int) = ftn; //함수 포인터의 선언과 초기화
std::cout << (*pftn)(5); //ftn(5)와 같음
함수의 이름 대신 (*pftn)을 사용할 수 있습니다.
그런데 C++의 경우 함수 포인터인 pftn도 함수의 이름으로 사용하는 것을 허용합니다.
즉 다음과 같은 코드도 동일한 결과를 출력합니다.
double ftn(int);
double (*pftn)(int) = ftn; //함수 포인터의 선언과 초기화
//std::cout << (*pftn)(5);
std::cout << pftn(5); //ftn(5)와 같음
함수 포인터를 선언할 때에는 다음과 같이 C++11 이상에서 지원하는 기능인 auto 키워드를 사용할 수도 있습니다.
double ftn(int);
auto pftn = ftn; //double (*pftn)(int) = ftn;와 동치
좀 더 복잡한 형태의 함수 포인터에 대해서 알아보겠습니다.
포인터 배열은 포인터를 원소로 갖는 배열입니다.
따라서 함수 포인터를 원소로 갖는 배열 역시 존재할 것입니다.
이러한 함수 포인터 배열은 다음과 같이 선언합니다.
const double* f1(const double*, int);
const double* f2(const double*, int);
const double* f3(const double*, int);
//f1, f2, f3를 원소로 갖는 함수 포인터 배열
const double* (*pa[3])(const double*, int) = {f1, f2, f3};
//pa[0](const double*, int)는 f1(const double*, int)와 같다
//혹은 (*pa[0])(const double*, int)
하나하나 뜯어보자면 다음과 같습니다.
먼저 f1, f2, f3는 const double* 형을 리턴값으로 갖고, const double*형과 int형을 매개변수로 갖는 함수입니다.
pa[3]은 이 배열이 세 개의 원소를 가진 배열임을 알려줍니다.
또한 이 원소의 자료형이 함수 포인터형임을 알리기 위해 배열의 이름 앞에 * 연산자를 붙이고,
가리키는 함수에 대한 정보를 제공하기 위해 리턴형과 매개변수의 자료형을 적어줍니다.
([]연산자가 *연산자보다 우선순위가 높음)
참고로 배열을 초기화할 때는 auto 키워드를 사용할 수 없습니다.
auto 키워드는 단일 값을 초기화할 때만 사용됩니다.
그러나 배열 단일 요소를 가리키기 위해서는 다음과 같이 코드를 작성할 수 있습니다.
const double* f1(const double*, int);
const double* f2(const double*, int);
const double* f3(const double*, int);
//f1, f2, f3를 원소로 갖는 함수 포인터 배열
const double* (*pa[3])(const double*, int) = {f1, f2, f3};
const double* (**pb)(const double*, int) = pa; //pa는 &pa[0]와 같음
//auto pb = pa; auto 키워드로 간략화 가능
위의 코드는 얼핏 보기에 복잡해 보일 수 있지만 사실 아래와 같은 작업을 함수 포인터에 대해서 수행한 것뿐입니다.
int arr[3] = { 1, 2, 3 };
int* parr = arr; //int* parr = &arr[0];
배열의 첫번째 원소의 주소를 저장할 수 있는 포인터를 만들어 저장한 것입니다.
물론 겉보기에 복잡한 건 사실이므로 auto 키워드를 사용하여 조금 더 편하게 선언할 수 있습니다.
함수 포인터 배열 역시 배열이므로 배열 포인터를 만들 수 있습니다.
배열 포인터의 경우 다음과 같이 선언하고 사용했습니다.
int arr[3] = { 1, 2, 3 };
int (*parr)[3] = &arr;
std::cout << (*parr)[0]; //1 출력
즉, 배열의 이름인 arr 대신 (*parr)를 넣어서 만듭니다.
함수 포인터 배열의 포인터도 마찬가지일 것입니다.
const double* f1(const double*, int);
const double* f2(const double*, int);
const double* f3(const double*, int);
//f1, f2, f3를 원소로 갖는 함수 포인터 배열
const double* (*pa[3])(const double*, int) = {f1, f2, f3};
//함수 포인터 배열을 가리키는 포인터 pd
const double* (*(*pd)[3])(const double*, int) = &pa; //auto pd = &pa;
//(*(*pd)[0])(const double*, int) f1 함수 호출 형식
//혹은 (*pd)[0](const double*, int) 형식 역시 가능
역시 auto 키워드를 통해 간략화할 수 있습니다.
* 해당 글은 'C++ 기초 플러스 6판'을 참고하여 작성되었습니다.
'Computer > C++' 카테고리의 다른 글
[C++] value categories (0) | 2022.11.17 |
---|---|
[C++] rvalue 참조와 move semantics (0) | 2022.11.16 |
[C++] 배열 포인터 정리 (0) | 2022.11.11 |
[C++] cout 관련(3) - 출력 형식 지정 (0) | 2022.11.06 |
[C++] cout 관련(2) - 출력 버퍼 비우기 (0) | 2022.11.06 |