# 함수 포인터의 정의
함수 포인터는 함수를 가리키는 포인터입니다.
포인터는 메모리상의 주소를 저장하는 변수인데, 함수도 메모리에 존재하며 그 시작 주소가 있으므로 포인터 변수로 가리킬 수 있습니다.
정수형 데이터 타입을 가리키는 정수형 포인터는 정수형 변수의 주소를 가집니다.
하지만 함수는 그 안에 여러 데이터 타입이 있을 수 있기 때문에 좀 더 복잡한 선언 형식이 필요합니다.
* 함수포인터의 선언
리턴 타입 (*변수명)(인수의 목록)
int func(int x); 라는 함수를 가리키는 함수 포인터를 선언하려면
int (*mypf)(int x); 로 선언해야합니다.
- 함수 포인터 선언 방법
함수 선언식에서
1. 함수명을 필요한 변수명으로 바꾸고
2. * 를 붙이고,
3. ( ) 감싸면 됩니다.
여기에서 인수목록의 변수는 생략이 가능합니다.
int (*mypf)(int); // x를 생략하였습니다.
그리고 이렇게 선언된 함수 포인터에 함수를 대입하면 됩니다.
mypf = func;
함수명에 괄호가 없으면 그 함수의 시작 주소를 가리키는 포인터 상수가 됩니다.
그래서 함수 포인터에 함수의 주소를 대입하는 것이 됩니다.
괄호없는 배열명이 그 배열의 시작번지를 가리키는 포인터 상수인 것과 같습니다.
그리고 포인터가 데이터타입이 같은 다른 변수를 가리킬 수 있듯이
함수 포인터도 데이터 타입(함수의 원형)이 같다면 다른 함수를 가리킬 수도 있습니다.
int func1(int a){
return 1;
}
int func2(int b){
return 2;
}
int func3(int value){
return 3;
}
void main(){
int (*mypf)(int);
// 함수 포인터의 타입이 같아 모두 대입 가능합니다.
mypf = func1;
mypf = func2;
mypf = func3;
}
- 함수 포인터로 함수 호출하기
mypf(2); 라고 호출하면
func(2); 라고 호출한 것과 같습니다.
원칙적으로 (*mypf)(2); 가 맞는 방법이지만
컴파일러에서 간편하게
mypf(2); 로 호출하여도 문제가 없도록 컴파일해주고 있습니다.
# 함수 포인터의 타입
기본적으로 타입이 다른 함수 포인터를 서로 대입할 수 없는데
강제적으로 가능한 방법이 있습니다.
왜냐하면 void 포인터에 저장된 함수의 주소를 대입받는다던가,
자료 구조 설계 시 미리 알 수 없는 함수에 대한 포인터를 다룬다던가 하는 이유에서 입니다.
이 때 캐스트연산자로 강제적 대입이 가능합니다.
int (*pf1)(char *);
void (*pf2)(double);
pf1 = pf2; // 에러.
pf1 = (int(*)(char*))pf2 // 강제 캐스트로 가능합니다.
- 캐스트 연산자 작성 법
1. int (*pf1)(char*) // 함수 포인터 선언식에서
2. int (*pf1)(char*) // 변수명을 지우고
3. (int (*)(char*)) // 전체를 괄호로 감싸면 캐스트연산자가 됩니다.
4. pf1 = (int(*)(char*))pf2; // 완성.
물론 무분별하게 캐스트를 하게되면 오작동을 유발하게됩니다.
- 함수 포인터의 배열 선언
int (*arrpf[5])(int); // 배열 첨자는 변수명뒤에 붙입니다.
int func(int);의 함수 데이터타입을 가진 함수 포인터 5개를 저장할 수 있는 배열입니다.
- 함수 포인터의 포인터 (이중 함수 포인터)
int (**ppf)(int); // * 하나 추가
(**ppf)(2); // 호출문
int func(int);를 가리키는 함수포인터를 가리키는 포인터입니다.
- 복잡한 함수 포인터 선언식을 typedef로 치환하기
#include <stdio.h>
typedef int(*myPfTypeName)(int); // (1) 복잡한 함수 포인터 선언식을 간단한 명칭 하나로 치환 후
int func(int x){
return x*2;
}
int main(){
myPfTypeName pf; // (2) 일반 데이터타입 변수 선언처럼 사용가능합니다.
pf=func;
int i = pf(2);
printf("%d", i);
return 0;
}
# 포인터로 함수 호출하기
함수 포인터를 사용하는 이유
#include <stdio.h>
#include <conio.h> //getch()의 헤더
int m2(int x){
return x*2;
}
int m3(int x){
return x*3;
}
int main(){
int (*pf)(int);
char ch = getch();
// 필요에 따라 다른 함수를 가리킨 후
if(ch=='2')
pf=m2;
else
pf=m3;
// 호출하여 사용할 수 있습니다.
printf("%d", pf(5));
return 0;
}
출력결과
10 // 실행 후 2를 입력했을 경우
15 // 실행 후 다른 것을 입력했을 경우
- 위 코드 예제로 보았을 때 너무나 간단해서 함수 포인터를 꼭 사용해야하나 싶겠지만
1. 함수가 수십개라면 함수 포인터 배열을 선언하고 첨자를 선택한다던가
2. 함수를 선택하는 시점과 함수를 호출하는 시점이 다르다던가
3. 호출할 함수가 외부에 있어 동적으로 연결할 계획이라서 컴파일할 때는 그 함수를 알 수 없을 때
등의 상황에서 함수 포인터를 꼭 필요한 존재입니다.
# 함수 포인터 인수
함수 포인터를 다른 함수에게 인수로 보내는 경우
대표적으로 <stdlib.h>의 qsort()함수가 있습니다.
qsort()함수의 원형은 다음과 같이 생겼습니다.
void qsort(void *base, size_t num, size_t width, int ( *compare )(const void *, const void *));
qsort()함수는 base번지에서부터, 하나의 사이즈가 width이고, num개가 있는 값을
일정한 기준에 따라 정렬하는데,
여기에서 일정한 기준을 정의하는 비교 함수인 compare 가 인수로 전달되어야 합니다.
- qsort()함수를 통해 오름차순으로 정렬하는 코드
#include <stdio.h>
#include <stdlib.h>
int compare(const void *a, const void *b)
{
if (*(int *)a == *(int *)b) return 0;
if (*(int *)a > *(int *)b) return 1;
return -1;
}
int main()
{
int i;
int ar[]={83, 4, 86, 8, 27, 12};
// compare 함수를 인수로 qsort()를 호출하고 있습니다.
qsort(ar,sizeof(ar)/sizeof(ar[0]),sizeof(int),compare);
for (i=0;i<sizeof(ar)/sizeof(ar[0]);i++) {
printf("ar[%d]= %d\n",i,ar[i]);
}
return 0;
}
출력결과
ar[0]= 4
ar[1]= 8
ar[2]= 12
ar[3]= 27
ar[4]= 83
ar[5]= 86
* 사용자가 비교 함수를 제공해야하는 이유
간단한 정수 비교만 한다면 명확하게 비교가 가능하겠지만 문자 비교의 경우 대소문자를 구분할 것인지 여부, 밑 줄이나 쉼 표 등까지 비교할 것인지 여부, 한글과 영문의 우선순위 등에 따라 복잡해지며, 이차 정렬까지 고려하면 일반화가 불가능하여 사용자가 비교 함수를 제공하도록 qsort()함수를 만들었습니다.
- 함수포인터를 다른 함수에게 인수로 보내는 경우 2
ftp서버에서 파일을 다운받아 로컬 HDD에 저장하는 함수가 있다고 가정합니다.
예를 들어 이렇게 생겼습니다.
// FtpDown("fpt서버의 주소", "다운받을 HDD의 주소");
void FtpDown(const char *src, const char *dest);
이 함수를 실행했는데 다운받는 파일의 용량이 매우 커서 시간이 오래 걸린다면 그 동안 프로그램은 멈춰있는 것처럼 보일 것입니다.
그래서 FtpDown함수 내에 진행사항을 문자열로 알려주는 코드를 추가 해 넣었습니다.
그 후 발생할 수 있는 문제점을 알아보면
1. 중간에 다운로드를 취소할 수 없습니다.
2. 진행사항을 문자열로만 알려줄 수 있습니다. 문자열이 아닌 그래프나, 애니메이션의 형태도 추가하고 싶을 수 있습니다.
그래서 FtpDown 함수 내에 안내 코드를 작성하지 않고,
사용자 정의 함수를 호출하여 진행사항을 알려주도록 하겠습니다.
// FtpDown("fpt서버의 주소", "다운받을 HDD의 주소", prog라는 함수 포인터를 추가);
void FtpDown(const char* src, const char* dest, BOOL (*prog)(int, int));
함수 포인터 prog는
(int (총 용량), int (현재까지 다운받은 용량))을 인수로 받고,
BOOL (다운로드 취소 여부)를 리턴받는 함수를 가리킬 수 있습니다.
#include <stdio.h>
#include <conio.h>
#include <windows.h>
void FtpDown(const char *src, const char *dest, bool (*prog)(int, int))
{
int total, now;
bool UserBreak;
// 실제로는 다운받을 파일의 용량을 대입해야 합니다.
total=100;
now=0;
for (now=0;now<total;now++) {
// 실제로 다운 받는 코드는 생략합니다.
// DownFile(src, dest);
// for문과 함께 1MB다운로드 받는데 1/10초 걸리는 것을 표현합니다.
Sleep(100);
// 인수로 받은 진행과정 및 취소 함수를 호출합니다.
UserBreak=(*prog)(total,now);
if (UserBreak==TRUE) {
puts("다운로드를 취소했습니다");
break;
}
}
}
bool Progress(int total, int now)
{
// 다운로드 과정을 보여 줍니다.
printf("총 %dMB중 %dMB만큼 받았습니다.\n",total,now);
// 만약 esc키를 누르면 true를 리턴하여 다운로드가 멈추게 됩니다.
if (kbhit() && getch()==27) {
return TRUE;
} else {
return FALSE;
}
}
int main()
{
FtpDown("원본의 주소","대상의 주소",Progress);
return 0;
}
# 함수 포인터 리턴
함수 포인터를 리턴하는 경우는 거의 없기 때문에 간단하게 알아보겠습니다.
- 함수 포인터를 리턴하는 함수의 원형
fp의 리턴타입 ((*함수명)(인수목록))(fp의 인수 목록)
#include <stdio.h>
int func1(int a, double b)
{
return 1;
}
int func2(int a, double b)
{
return 2;
}
// 인수목록이 (int, double)이고,리턴 타입이 int인 함수 포인터를 리턴하는 함수
// 즉 func1를 가리키는 함수포인터 혹은
// func2를 가리키는 함수포인터를 리턴하게 됩니다.
int (*SelectFunc(char ch))(int,double)
{
return (ch=='a'? func1 : func2);
}
int main()
{
int (*funcPointer)(int,double);
// b를 넣으면 func2 함수를 가리키는 함수 포인터가 리턴되고
funcPointer=SelectFunc('b');
// func2(1, 2.3); 가 호출되어 int형 2를 리턴합니다.
printf("%d",funcPointer(1,2.3));
return 0;
}
출력결과
2
'Lang > C' 카테고리의 다른 글
[C] 자료 구조 # 단일 연결 리스트 Single Linked List (0) | 2022.12.12 |
---|---|
[C] 문자열 서식 # 이스케이프 시퀀스 (0) | 2022.12.06 |
[C] C와 C++ Integer의 MIN, MAX (0) | 2022.11.30 |
[C] C 문법 공부 #3 문자열 함수 (0) | 2022.11.24 |
[C] C 문법 공부 #2 입력 scanf(), gets(), getch(), kbhit() (0) | 2022.11.22 |