61. 함수 포인터
1. 키워드
- 함수 포인터
2. 함수 포인터 사용하기
- C에서 함수는 이름이 정해져 있기 때문에 함수를 호출하려면 함수 이름으로 직접 호출했다.
- C에는 함수를 배열 또는 구조체에 넣거나, 함수 자체를 함수의 매개변수로 넘겨주고, 반환값으로 가져올 수 있도록 함수 포인터라는 것을 만들어두었다.
- 함수 포인터는 함수를 저장하는 포인터를 뜻하며 함수 포인터를 주고 받거나 함수 포인터로 함수를 호출할 수 있다.
- 실제로 함수 이름도 포인터이므로
printf
에서 출력해 보면 메모리 주소가 나온다.
void hello()
{
printf("Hello, world!\n");
}
int main()
{
printf("%p\n", hello); // 함수 이름도 포인터이므로 메모리 주소가 출력됨
return 0;
}
// 0x00d1137f (메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐)
3. 함수 포인터 만들기
- 함수 포인터는 먼저 함수의 반환값 자료형을 지정해 주고, 함수 포인터 이름 앞에
*
(애스터리스크)를 붙인 뒤()
(괄호)로 묶어준다. - 그리고 다시
()
를 붙여 함수라는 것을 알려준다.
- 먼저 반환값과 매개변수가 없는
hello
함수가 있다.
- 이 함수를 담을 수 있는 함수 포인터
fp
(Function Pointer)는 다음과 같이 선언한다.
- 이제 함수 포인터를 사용해 보자.
#include <stdio.h>
void hello() // 반환값과 매개변수가 없음
{
printf("Hello, world!\n");
}
void bonjour() // 반환값과 매개변수가 없음
{
printf("bonjour le monde!\n");
}
int main()
{
void (*fp)(); // 반환값과 매개변수가 없는 함수 포인터 fp 선언
fp = hello; // hello 함수의 메모리 주소를 함수 포인터 fp에 저장
fp(); // 함수 포인터로 hello 함수 호출
fp = bonjour; // bonjour 함수의 메모리 주소를 함수 포인터 fp에 저장
fp(); // 함수 포인터로 bonjour 함수 호출
return 0;
}
// Hello, world!
// bonjour le monde!
- 함수 포인터를 선언할 때는 함수 포인터와 저장될 함수의 반환값 자료형, 매개변수 자료형과 개수가 일치해야 한다.
- 여기서는 반환값과 매개변수가 없는
hello
,bonjour
함수를 저장할 것이므로 반환값은void
타입으로 지정한다. - 그리고
(*fp)
와 같이 함수 포인터 이름fp
앞에*
를 붙이고()
로 묶어준다. - 또한, 매개변수가 없으므로
()
만 붙이면 된다.
- 함수의 메모리 주소를 함수 포인터에 저장할 때는
=
(할당 연산자)에 함수 이름만 지정해 주면 된다. - 함수 이름 뒤에
()
를 붙이면 함수 호출이 되므로 주의한다.
- 함수 포인터로 함수를 호출하는 방법은 간단하다.
- 함수 포인터에
()
를 붙여서 호출하면 함수 포인터에 들어있는 함수가 호출된다. - 여기서
fp
에 저장된hello
를 호출하므로"Hello, world!"
가 출력된다.
fp
에는hello
함수 대신bonjour
함수를 넣어서 호출해도 된다.- 즉, 함수 포인터를 사용하면 함수를 전구 소켓처럼 갈아 끼울 수 있다.
4. 반환값과 매개변수가 있는 함수 포인터 만들기
- 이번에는 반환값과 매개변수가 있는 함수 포인터를 만들어보자.
- 다음과 같이 반환값 자료형을 지정해 주고, 맨 뒤의
()
에 매개변수의 자료형을 지정한다. - 이때 매개변수 이름은 생략해도 된다.
- 예를 들어
int
타입 반환값,int
타입 매개변수가 두 개인 함수는 다음과 같다.
add
함수를 담을 수 있는 함수 포인터는 다음과 같이 만들 수 있다.
- 이제 함수 포인터를 사용해 보자.
#include <stdio.h>
// 덧셈 함수
int add(int a, int b) // int 타입 반환값, int 타입 매개변수 두 개
{
return a + b;
}
// 곱셈 함수
int mul(int a, int b) // int 타입 반환값, int 타입 매개변수 두 개
{
return a * b;
}
int main()
{
int (*fp)(int, int); // int 타입 반환값, int 타입 매개변수 두 개가 있는 함수 포인터 fp 선언
fp = add; // add 함수의 메모리 주소를 함수 포인터 fp에 저장
printf("%d\n", fp(10, 20)); // 함수 포인터로 add 함수를 호출하여 합을 구함
fp = mul; // mul 함수의 메모리 주소를 함수 포인터 fp에 저장
printf("%d\n", fp(10, 20)); // 함수 포인터로 mul 함수를 호출하여 곱을 구함
return 0;
}
// 30
// 200
int
타입 반환값과int
타입 매개변수 두 개를 가지고 있는add
,mul
함수를 저장할 것이므로 함수 포인터를 선언할 때 반환값은int
타입으로 지정하고, 맨 뒤의()
안에는int
타입을 두 개 넣어준다.- 이때 매개변수의 자료형만 알려주면 되므로 매개변수의 이름은 지정하지 않아도 된다.
//↓ 반환값 자료형
int (*fp)(int, int); // int 타입 반환값, int 타입 매개변수 두 개가 있는 함수 포인터 fp 선언
// ↑ ↖ int형 매개변수 두 개
// 함수 포인터 이름
- 이제
=
를 사용하여 함수 포인터에add
함수의 메모리 주소를 저장한다.
- 함수 포인터
fp
에()
를 붙여서 호출하면서 매개변수에 값을 넣는다. - 여기서는
fp
에 저장된add
함수를 호출하므로10
과20
을 더한 값인30
이 나온다.
fp
에mul
함수를 넣어서 곱을 구할 수도 있다.
fp = mul; // mul 함수의 메모리 주소를 함수 포인터 fp에 저장
printf("%d\n", fp(10, 20)); // 함수 포인터로 mul 함수를 호출하여 합을 구함
- 즉, 함수 포인터를 활용하면 상황에 따라서 함수를 바꿔가며 호출할 수 있다.
5. 함수 포인터 활용하기
- 함수 포인터를 만드는 방법을 알아봤으니 이번에는 함수 포인터를 다양하게 활용해 보자.
- 함수 포인터도 일반적인 포인터이므로 포인터로 할 수 있는 작업을 그대로 수행할 수 있다.
- 함수 포인터를 배열로 만들기, 구조체 멤버로 사용하기, 함수의 매개변수와 반환값으로 사용하기 등이 가능하다.
- 함수 포인터를 응용하면 함수를 고정된 형태가 아닌 데이터 형태로 취급할 수 있다.
- 즉, 함수를 보관하거나 주고받기가 쉬워진다.
6. 함수 포인터 배열 사용하기
- 먼저 사용자에게 함수 번호와 계산할 값을 입력받은 뒤 함수 포인터로 계산을 해보자.
- 계산 함수는
int
타입 반환값,int
타입 매개변수 두 개를 가지고 있는 함수이며0
부터3
까지 번호를 매겼다.
#define _CRT_SECURE_NO_WARNINGS // scanf 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int funcNumber; // 함수 번호
int num1, num2;
int (*fp)(int, int) = NULL; // int 타입 반환값, int 타입 매개변수 두 개가 있는 함수 포인터 선언
printf("함수 번호와 계산할 값을 입력하세요: ");
scanf("%d %d %d", &funcNumber, &num1, &num2); // 함수 번호와 계산할 값을 입력받음
switch (funcNumber) // 함수 번호에 따라 함수 포인터 설정
{
case 0: // 함수 번호에 따라 함수 포인터 설정
fp = add; // 덧셈 함수
break;
case 1: // 1이면
fp = sub; // 뺄셈 함수
break;
case 2: // 2이면
fp = mul; // 곱셈 함수
break;
case 3: // 3이면
fp = div; // 나눗셈 함수
break;
}
printf("%d\n", fp(num1, num2)); // 함수 포인터를 사용하여 계산 결과 출력
return 0;
}
// 함수 번호와 계산할 값을 입력하세요: 0 10 20 (입력)
// 30
- 함수 번호가
funcNumber
에 저장된다. switch
분기문에서funcNumber
에 따라 함수 포인터fp
에 함수의 주소를 넣어준다.- 여기서는
0
을 입력했으므로fp
에add
함수가 들어간다. - 그리고
fp
를 호출하여10
과20
을 더한 값인30
이 나왔다.
- 함수를 번호로 나타내려고 하니 코드가 너무 길어진다.
- 이때는 함수 포인터를 배열로 만들면 간단해진다.
- 함수 포인터 배열은 함수 포인터를 선언할 때 함수 포인터 이름 뒤에
[]
(대괄호) 안에 배열의 크기를 지정하면 된다.
- 요소의 개수가
4
개이며add
함수를 담을 수 있는 함수 포인터 배열은 다음과 같이 만든다.
- 이제 함수 포인터 배열을 사용해 보자.
#define _CRT_SECURE_NO_WARNINGS // scanf 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int funcNumber; // 함수 번호
int num1, num2;
int (*fp[4])(int, int); // int 타입 반환값, int 타입 매개변수 두 개가 있는 함수 포인터 배열 선언
fp[0] = add; // 첫 번째 요소에 덧셈 함수의 메모리 주소 저장
fp[1] = sub; // 두 번째 요소에 뺄셈 함수의 메모리 주소 저장
fp[2] = mul; // 세 번째 요소에 곱셈 함수의 메모리 주소 저장
fp[3] = div; // 네 번째 요소에 나눗셈 함수의 메모리 주소 저장
printf("함수 번호와 계산할 값을 입력하세요: ");
scanf("%d %d %d", &funcNumber, &num1, &num2); // 함수 번호와 계산할 값을 입력받음
printf("%d\n", fp[funcNumber](num1, num2)); // 함수 포인터 배열을 인덱스로 접근하여 함수 호출
return 0;
}
// 함수 번호와 계산할 값을 입력하세요: 0 10 20 (입력)
// 30
- 먼저 다음과 같이 함수 포인터 이름
fp
뒤에[]
를 붙이고 크기를4
로 지정하여 요소가4
개인 함수 포인터 배열을 선언했다.
// int 타입 반환값, int 타입 매개변수 두 개가 있는 함수 포인터 배열 선언
//↓ 반환값 자료형
int (*fp[4])(int, int); // ← 매개변수 자료형
// ↑ ↖ 크기가 4인 배열
// 함수 포인터 이름
- 이제
fp
는 배열이므로 인덱스로 접근할 수 있다. - 여기서는
add
,sub
,mul
,div
함수의 메모리 주소를 각 요소에 저장했다.
fp[0] = add; // 첫 번째 요소에 덧셈 함수의 메모리 주소 저장
fp[1] = sub; // 두 번째 요소에 뺄셈 함수의 메모리 주소 저장
fp[2] = mul; // 세 번째 요소에 곱셈 함수의 메모리 주소 저장
fp[3] = div; // 네 번째 요소에 나눗셈 함수의 메모리 주소 저장
- 함수 포인터 배열은 다음과 같이 인덱스를 사용하여 요소에 접근한 뒤 함수를 호출할 수 있다.
- 따라서
fp[funcNumber](10, 20)
과 같이fp
뒤에[]
를 붙이고 인덱스를 지정한 뒤 함수를 호출하면 된다. - 여기서는
0 10 20
을 입력하여funcNumber
에0
이 들어있으므로fp
의 첫 번째 요소에 들어있는add
함수가 호출된다.
- 만약 배열에 들어있는 모든 함수를 호출하고 싶다면 반복문을 사용하면 된다.
// 배열의 요소 개수만큼 반복(요소 개수: 배열의 전체 크기 / 요소 한 개의 크기)
for (int i = 0; i < sizeof(fp) / sizeof(fp[0]); i++)
{
printf("%d\n", fp[i](num1, num2)); // 함수 포인터 배열을 인덱스로 접근하여 함수 호출
}
- 이처럼 함수 포인터 배열을 활용하면 함수 자체도 데이터화하여 함수 호출을 자동화할 수 있다.
함수 포인터 배열을 선언하는 동시에 초기화하기
- 함수 포인터 배열도
{}
(중괄호)를 사용하여 초기화할 수 있다.
7. 함수 포인터를 구조체 멤버로 사용하기
- 지금까지 구조체에는 일반 자료형으로 된 멤버를 넣었다.
- 하지만 함수 포인터도 포인터이므로 구조체 멤버로 넣을 수 있다.
- 함수 포인터를 구조체 멤버로 사용하려면 구조체를 정의할 때 멤버로 지정해 주면 된다.
- 그럼
int
타입 반환값,int
타입 매개변수가 두 개인 함수를 구조체 멤버로 넣어보자.
add
함수를 담을 수 있는 구조체 멤버는 다음과 같이 만든다.
- 이제 구조체 멤버에 함수의 메모리 주소를 저장하고 사용해 보자.
#include <stdio.h>
struct Calc
{
int (*fp)(int, int); // 함수 포인터를 구조체 멤버로 지정
};
int add(int a, int b) // int 타입 반환값, int 타입 매개변수 두 개
{
return a + b;
}
int main()
{
struct Calc c;
c.fp = add; // add 함수의 메모리 주소를 구조체 c의 멤버에 저장
printf("%d\n", c.fp(10, 20)); // 구조체 멤버로 add 함수 호출
return 0;
}
// 30
- 구조체를 정의할 때 멤버 부분에 함수 포인터를 만들어준다.
- 여기서는
int
타입 반환값과int
타입 매개변수 두 개를 가지고 있는 함수 포인터fp
를 넣었다.
- 구조체 변수를 선언한 뒤
.
(점)으로 함수 포인터 멤버에 접근하여add
함수의 메모리 주소를 저장한다. - 만약 구조체 포인터에 메모리를 할당했다면
c->fp = add
가 된다.
- 이제 구조체 멤버
fp
에()
를 붙여서 함수를 호출할 수 있다.
C와 객체지향
- 보통 C는 객체지향 문법을 지원하지 않는다고 알려져 있다.
- 하지만 C에서도 구조체와 함수 포인터를 활용하면 충분히 객체지향으로 프로그래밍을 할 수 있다.
struct block_device_operations
{
int (*open)(struct block_device *, fmode_t);
void (*release)(struct gendisk *, fmode_t);
int (*rw_page)(struct block_device *, sector_t, struct page *, int rw);
int (*ioctl)(struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl)(struct block_device *, fmode_t, unsigned, unsigned long);
long (*direct_access)(struct block_device *, sector_t,
void **, unsigned long *pfn, long size);
unsigned int (*check_events)(struct gendisk *disk,
unsigned int clearing);
int (*media_changed)(struct gendisk *);
void (*unlock_native_capacity)(struct gendisk *);
int (*revalidate_disk)(struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
void (*swap_slot_free_notify)(struct block_device *, unsigned long);
struct module *owner;
};
8. 함수 포인터를 함수의 매개변수로 사용하기
- 이번에는 함수 포인터를 매개변수와 함수의 반환값으로 사용해 보자.
- 먼저 함수 포인터를 매개변수로 사용하려면 함수를 만들 때 매개변수 부분에 함수 포인터를 넣어주면 된다.
add
함수를 매개변수로 전달하려면 함수를 다음과 같이 만든다.
- 이제 함수를 완성하여 함수 포인터를 매개변수로 전달하고 사용해 보자.
#include <stdio.h>
int add(int a, int b) // int 타입 반환값, int 타입 매개변수 두 개
{
return a + b;
}
void executor(int (*fp)(int, int)) // 함수 포인터를 매개변수로 지정
{
printf("%d\n", fp(10, 20)); // 매개변수로 함수 호출
}
int main()
{
executor(add); // executor를 호출할 때 add 함수의 메모리 주소를 전달
return 0;
}
// 30
- 함수를 정의할 때 매개변수 부분에 함수 포인터를 그대로 만들어주면 된다.
- 따라서 함수 포인터 이름이 매개변수가 되므로
fp(10, 20)
과 같이 매개변수로 함수를 호출하면 된다.
void executor(int (*fp)(int, int)) // 함수 포인터를 매개변수로 지정
{
printf("%d\n", fp(10, 20)); // 매개변수로 함수 호출
}
- 함수의 메모리 주소를 전달할 때는 함수 이름만 적어주면 된다.
add()
처럼()
까지 붙이면add
함수가 호출되어 반환값이executor
에 전달되므로 주의한다.
- 다음과 같이
executor(add);
를 풀어서 쓸 수도 있다.
9. 함수 포인터를 함수의 반환값으로 사용하기
- 이번에는 함수 포인터를 반환값에 사용해 보자.
- 반환값으로 사용할 때는 지금까지 사용했던 문법과는 조금 다르다.
- 함수에서
add
함수를 반환하려면 다음과 같이 만든다.
- 이제 함수 안에서 함수 포인터를 반환하고, 바깥에서 사용해 보자.
#include <stdio.h>
int add(int a, int b) // int 타입 반환값, int 타입 매개변수 두 개
{
return a + b;
}
int (*getAdd())(int, int) // 함수 포인터를 반환값으로 지정
{
return add; // add 함수의 메모리 주소를 반환
}
int main()
{
printf("%d\n", getAdd()(10, 20)); // getAdd를 호출한 뒤 반환값으로 add 함수 호출
return 0;
}
// 30
- 함수 포인터를 반환값으로 사용할 때는 먼저
(*getAdd())
와 같이*
뒤에 함수 이름을 지정하고()
를 붙인다. - 다시 이 상태에서
()
로 묶어준 뒤 맨 앞에는 함수 포인터의 반환값 자료형, 맨 뒤()
안에는 함수 포인터의 매개변수 자료형을 지정한다. - 함수 안에서는
return
키워드로add
함수의 메모리 주소를 반환한다.
//↓ 함수 포인터의 반환값 자료형
int (*getAdd())(int, int) // 함수 포인터를 반환값으로 지정
{ // ↑ ↑ 함수 포인터의 매개변수 자료형
// 함수 이름
return add; // add 함수의 메모리 주소를 반환
}
getAdd
함수를 호출하면add
함수의 포인터가 반환된다.- 따라서 다음과 같이
getAdd
함수를 호출한 뒤 바로()
를 붙여서 반환값으로add
함수를 호출할 수 있다.
- 다음과 같이
getAdd()(10, 20)
을 풀어서 쓸 수도 있다.
int (*fp)(int, int);
fp = getAdd(); // getAdd의 반환값을 함수 포인터 fp에 저장
printf("%d\n", fp(10, 20)); // 함수 포인터 fp로 add 함수 호출
매개변수가 있는 함수에서 함수 포인터 반환하기
- 매개변수가 있는 함수에서 함수 포인터를 반환하려면 다음과 같이
getAdd
뒤의()
안에 매개변수의 자료형과 매개변수 이름을 지정해 주면 된다.
10. typedef
로 함수 포인터 별칭 정의하기
- 지금까지 함수 포인터를 만들 때
int (*fp)(int, int);
처럼 일일이 반환값 자료형과 매개변수 자료형을 명시해 주었다. - 이런 방식을 Full Pointer Type이라고 하는데 사용하기가 상당히 번거롭다.
- 이때는
typedef
로 별칭을 정의하면 함수 포인터를 매번 만들지 않아도 된다.
- 함수 포인터 별칭은
typedef
뒤에서 함수 포인터를 만들면 된다.
- 이제 함수 포인터 별칭을 사용해 보자.
#include <stdio.h>
int add(int a, int b) // int 타입 반환값, int 타입 매개변수 두 개
{
return a + b;
}
typedef int (*FP)(int, int); // FP를 함수 포인터 별칭으로 정의
FP getAdd() // 함수 포인터 별칭을 반환값으로 지정
{
return add; // add 함수의 메모리 주소를 반환
}
int main()
{
printf("%d\n", getAdd()(10, 20)); // getAdd을 호출한 뒤 반환값으로 add 함수 호출
return 0;
}
// 30
typedef
가 앞에 붙는다는 점 말고는 일반적인 함수 포인터를 만드는 방법과 같다.
- 다음과 같이 함수 포인터 별칭을 함수의 반환값 자료형으로 지정할 수 있다.
getAdd
의 모양이 훨씬 간단해졌다.
- 물론 함수 포인터 별칭으로 포인터 변수(배열)를 선언하거나, 구조체 멤버 자료형, 함수의 매개변수 자료형으로도 사용할 수 있다.
#include <stdio.h>
int add(int a, int b) // int 타입 반환값, int 타입 매개변수 두 개
{
return a + b;
}
typedef int (*FP)(int, int); // FP를 함수 포인터 별칭으로 정의
struct Calc
{
FP fp; // 함수 포인터 별칭을 구조체 멤버 자료형으로 사용
};
void executor(FP fp) // 함수 포인터 별칭을 매개변수 자료형으로 사용
{
printf("%d\n", fp(70, 80));
}
int main()
{
FP fp1; // 함수 포인터 별칭으로 변수 선언
fp1 = add;
printf("%d\n", fp1(10, 20));
FP fp[10]; // 함수 포인터 별칭으로 배열 선언
fp[0] = add;
printf("%d\n", fp[0](30, 40));
struct Calc c;
c.fp = add;
printf("%d\n", c.fp(50, 60));
executor(add); // executer를 호출할 때 add 함수의 메모리 주소를 전달
return 0;
}
// 30
// 70
// 110
// 150
References
- https://dojang.io/mod/page/view.php?id=591
- https://dojang.io/mod/page/view.php?id=592
- https://dojang.io/mod/page/view.php?id=593
- https://dojang.io/mod/page/view.php?id=597
- https://dojang.io/mod/page/view.php?id=598
- https://dojang.io/mod/page/view.php?id=599
- https://dojang.io/mod/page/view.php?id=600
- https://dojang.io/mod/page/view.php?id=601