函数指针

通常,函数指针常用作另一个函数的参数,告诉该函数要使用哪一个函数。例如,排序数组涉及比较两个元素,以确定先后。如果元素是数字,可以使用>运算符;如果元素是字符串或结构,就要调用函数
进行比较。C库中的 qsort()函数可以处理任意类型的数组,但是要告诉qsort()使用哪个函数来比较元素。为此, qsort()函数的参数列表中,有一个参数接受指向函数的指针。然后,qsort()函数使用该函数提供的方案进行排序,无论这个数组中的元素是整数、字符串还是结构。

我们来进一步研究函数指针。首先,什么是函数指针?假设有一个指向int类型变量的指针,该指针储存着这个int类型变量储存在内存位置的地址。同样,函数也有地址,因为函数的机器语言实现由载入内存的代码组成。指向函数的指针中储存着函数代码的起始处的地址。其次,声明一个数据指针时,必须声明指针所指向的数据类型。声明一个函数指针时,必须声明指针指向的函数类型。为了指明函数类型,要指明函数签名,即函数的返回类型和形参类型。

例如,考虑下面的函数原型:

void ToUpper(char *); // 把字符串中的字符转换成大写字符

ToUpper()函数的类型是“带char * 类型参数、返回类型是void的函数”。
下面声明了一个指针pf指向该函数类型:

void (*pf)(char *); // pf 是一个指向函数的指针

从该声明可以看出,第1对圆括号把*和pf括起来,表明pf是一个指向函数的指针。因此,(*pf)是一个参数列表为(char *)、返回类型为void的函数。

注意,把函数名ToUpper替换为表达式(*pf)是创建指向函数指针最简单的方式。所以,如果想声明一个指向某类型函数的指针,可以写出该函数的原型后把函数名替换成(*pf)形式的表达式,创建函数指针声明。 前面提到过,由于运算符优先级的规则(1.数组名后面的[]和函数名后面的()具有相同的优先级,2.[]和()的优先级相同,都是从左往右结合),在声明函数指针时必须把*和指针名括起来。如果省略第1个圆括号会导致完全不同的情况:

void *pf(char *); // pf 是一个无类型指针的函数

要声明一个指向特定类型函数的指针,可以先声明一个该类型的函数,然后把函数名替换成(*pf)形式的表达式。然后,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()不是地址

最后一条语句是无效的,不仅因为 ToLower()不是地址,而且ToLower()的返回类型是 void,它没有返回值,不能在赋值语句中进行赋值。注意,指针pf可以指向其他带char *类型参数、返回类型是void的函数,
不能指向其他类型的函数。
既然可以用数据指针访问数据,也可以用函数指针访问函数。奇怪的是,有两种逻辑上不一致的语法可以这样做,下面解释:

void ToUpper(char *);
void ToLower(char *);
void (*pf)(char *);
char mis[] = "Nina Metier";
pf = ToUpper;
(*pf)(mis); // 把ToUpper 作用于(语法1)
pf = ToLower;
pf(mis); // 把ToLower 作用于(语法2)

这两种方法看上去都合情合理。先分析第1种方法:由于pf指向ToUpper函数,那么*pf就相当于ToUpper函数,所以表达式(*pf)(mis)和ToUpper(mis)相同。从ToUpper函数和pf的声明就能看出,ToUpper和(*pf)是等价的。第2种方法:由于函数名是指针,那么指针和函数名可以互换使用,所以pf(mis)和ToUpper(mis)相同。从pf的赋值表达式语句就能看出ToUpper和pf是等价的。由于历史的原因,贝尔实验室的C和UNIX的开发者采用第1种形式,而伯克利的UNIX推广者却采用第2种形式。K&R C不允许第2种形式。但是,为了与现有代码兼容,ANSI C认为这两种形式(本例中是(*pf)(mis)和pf(mis))等价。后续的标准也延续了这种矛盾的和谐。
作为函数的参数是数据指针最常见的用法之一,函数指针亦如此。例
如,考虑下面的函数原型:

void show(void (* fp)(char *), char * str);

这看上去让人头晕。它声明了两个形参:fp和str。fp形参是一个函数指针,str是一个数据指针。更具体地说,fp指向的函数接受char * 类型的参数,其返回类型为void;str指向一个char类型的值。因此,假设有上面的声明,可以这样调用函数:

show(ToLower, mis); /* show()使用ToLower()函数:fp = ToLower */
show(pf, mis); /* show()使用pf指向的函数: fp = pf 
show()如何使用传入的函数指针?是用fp()语法还是(*fp)()语法调用函
数:*/
void show(void (* fp)(char *), char * str)
{
(*fp)(str); /* 把所选函数作用于str */
puts(str); /* 显示结果 */
}

例如,这里的show()首先用fp指向的函数转换str,然后显示转换后的字符串。顺带一提,把带返回值的函数作为参数传递给另一个函数有两种不同的方法。例如,考虑下面的语句:

function1(sqrt); /* 传递sqrt()函数的地址 */
function2(sqrt(4.0)); /* 传递sqrt()函数的返回值 */

第1条语句传递的是sqrt()函数的地址,假设function1()在其代码中会使用该函数。第2条语句先调用sqrt()函数,然后求值,并把返回值(该例中是2.0)传递给function2()。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值