先看一个C标准库<stdio.h>中的一个函数qsort(),它的功能是对任何类型的数组进行排序。
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
参数:
- base -- 指向要排序的数组的第一个元素的指针。
- nitems -- 由 base 指向的数组中元素的个数。
- size -- 数组中每个元素的大小,以字节为单位。
- compar -- 用来比较两个元素的函数。
其中形参compar就是一个函数指针。
我们先看一个使用qsort()函数的例子:
#include <stdio.h>
#include <stdlib.h>
int cmpfunc (const void * a, const void * b);
int values[] = { 88, 56, 100, 2, 25 };
int main() {
int n;
printf("排序之前的列表:\n");
for( n = 0 ; n < 5; n++ ) {
printf("%d ", values[n]);
}
qsort(values, sizeof(values) - 1, sizeof(int), cmpfunc);
printf("\n排序之后的列表:\n");
for( n = 0 ; n < 5; n++ ) {
printf("%d ", values[n]);
}
return(0);
}
int cmpfunc (const void * a, const void * b) {
return ( *(int*)a - *(int*)b ); ←这种用法划重点
}
排序之前的列表:
88 56 100 2 25
排序之后的列表:
2 25 56 88 100
我们可以看到函数名即是指向函数的指针。
我们分析一下comfunc()函数的算法:
由于qsort()函数是可以排序任意类型的数组的,所以形参是void类型的指针。
关于C之运算符优先级 查表可知:
优化级:* < (type)纯量表达式。所以*(int*)a:先void指针变量a强制转化为int类型的指针,然后通过*运算符取它的int型值,这样才可以进行比较。
考虑下面的函数原型:
void ToUpper(char *); // 把字符串中的字符转换成大写字符
ToUpper()函数的类型是“带char * 类型参数、 返回类型是void的函数”。下面声明了一个指针pf指向该函数类型:
void (*pf)(char *); // pf 是一个指向函数的指针
void ToUpper(char *);
void ToLower(char *);
int round(double);
void (*pf)(char *);
pf = ToUpper; // 有效, ToUpper是该类型函数的地址
pf = ToLower; //有效, ToUpper是该类型函数的地址
pf = round; // 无效, round与指针类型不匹配
pf = ToLower(); // 无效, ToLower()不是地址
作为函数的参数是数据指针最常见的用法之一, 函数指针亦如此。 例如, 考虑下面的函数原型:
void show(void (* fp)(char *), char * str);
show()函数有两个参数:1.fp(指向的函数授受char *类型的参数,返回类型为void),2.str(指向一个char类型的值)。
有以上声明,可以这样调用:
show(ToLower, mis); /* show()使用ToLower()函数: fp = ToLower */
show(pf, mis); /* show()使用pf指向的函数: fp = pf */
可以用函数指针直接调用函数:
int main() {
void (*fp)(char *); // 声明一个函数指针
fp = toUpper; // fp赋值为函数toUpper()的地址
char * uppercase = (*fp)(str); // 传char *类型参数,调用执行函数
}
char * toUpper(char *) {
...
}
下面给出一个使用函数指针的完整例子:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LEN 81
char* s_gets(char *st, int n);
char showmenu(void);
void eatline(void); // 读取至行末尾
void show(void (*fp)(char*), char *str);
void ToUpper(char*); // 把字符串转换为大写
void ToLower(char*); // 把字符串转换为小写
void Transpose(char*); // 大小写转置
void Dummy(char*); // 不更改字符串
int main(void) {
char line[LEN];
char copy[LEN];
char choice;
void (*pfun)(char*); // 声明一个函数指针, 被指向的函数接受char *类型的参数, 无返回值
puts("Enter a string (empty line to quit):");
while (s_gets(line, LEN) != NULL && line[0] != '\0') {
while ((choice = showmenu()) != 'n') {
switch (choice) { // switch语句设置指针
case 'u':
pfun = ToUpper;
break;
case 'l':
pfun = ToLower;
break;
case 't':
pfun = Transpose;
break;
case 'o':
pfun = Dummy;
break;
}
strcpy(copy, line); // 为show()函数拷贝一份
show(pfun, copy); // 根据用户的选择, 使用选定的函数
}
puts("Enter a string (empty line to quit):");
}
puts("Bye!");
return 0;
}
char showmenu(void) {
char ans;
puts("Enter menu choice:");
puts("u) uppercase l) lowercase");
puts("t) transposed case o) original case");
puts("n) next string");
ans = getchar(); // 获取用户的输入
ans = tolower(ans); // 转换为小写
eatline(); // 清理输入行
while (strchr("ulton", ans) == NULL) {
puts("Please enter a u, l, t, o, or n:");
ans = tolower(getchar());
eatline();
}
return ans;
}
void eatline(void) {
while (getchar() != '\n')
continue;
}
void ToUpper(char *str) {
while (*str) {
*str = toupper(*str);
str++;
}
}
void ToLower(char *str) {
while (*str) {
*str = tolower(*str);
str++;
}
}
void Transpose(char *str) {
while (*str) {
if (islower(*str))
*str = toupper(*str);
else if (isupper(*str))
*str = tolower(*str);
str++;
}
}
void Dummy(char *str) {} //不改变字符串
void show(void (*fp)(char*), char *str) {
(*fp)(str); // 把用户选定的函数作用于str
puts(str); // 显示结果
}
char* s_gets(char *st, int n) {
char *ret_val;
char *find;
ret_val = fgets(st, n, stdin);
if (ret_val) {
find = strchr(st, '\n'); // 查找换行符
if (find) // 如果地址不是NULL,
*find = '\0'; // 在此处放置一个空字符
else
while (getchar() != '\n')
continue; // 清理输入行中剩余的字符
}
return ret_val;
}
注意, ToUpper()、 ToLower()、 Transpose()和 Dummy()函数的类型都相同, 所以这 4 个函数都可以赋给pfun指针。
这种情况下, 可以使用typedef。 例如, 该程序中可以这样写:
typedef void (*V_FP_CHARP)(char *);
void show (V_FP_CHARP fp, char *);
V_FP_CHARP pfun;
如果还想更复杂一些, 可以声明并初始化一个函数指针的数组:
V_FP_CHARP arpf[4] = {ToUpper, ToLower, Transpose, Dummy};
然后把showmenu()函数的返回类型改为int, 如果用户输入u, 则返回0;如果用户输入l, 则返回2; 如果用户输入t, 则返回2, 以此类推。 可以把程序中的switch语句替换成下面的while循环:
index = showmenu();
while (index >= 0 && index <= 3) {
strcpy(copy, line); /* 为show()拷贝一份 */
show(arpf[index], copy); /* 使用选定的函数 */
index = showmenu();
}
上面提到typedef,下面简要介绍一下它的用法:
typedef工具是一个高级数据特性, 利用typedef可以为某一类型自定义名称。
1.定义类型别名(包括结构):
typedef unsigned char BYTE;
可以这样使用:
BYTE x, y[10], * z;
typedef struct {double x; double y;} rect;
可以这样使用:
rect r1 = {3.0, 6.0};
rect r2 = r1;
2.定义复杂类型(如数组、指针、函数):
typedef int arr5[5];
typedef arr5 * p_arr5;
typedef p_arr5 arrp10[10];
arr5 togs; // togs 是一个内含5个int类型值的数组==【int arr5[5]】
p_arr5 p2; // p2 是一个指向数组的指针, 该数组内含5个int类型的值==【int (*p2)[5]】
arrp10 ap; // ap 是一个内含10个指针的数组, 每个指针都指向一个内含5个int类型值的数组,
// == 【(*ap[10])[5]】
typedef char (* FRPTC ()) [5];
注解:把FRPTC声明为一个函数类型, 该函数返回一个指针, 该指针指向内含5个char类型元素的数组。