C语言指针导学(4)——分清函数指针和指针函数(转载)

C语言指针导学(4)——分清函数指针和指针函数

http://blog.csdn.net/porscheyin/article/details/3461632/


四.分清函数指针和指针函数

关于指针和数组斩不断理还乱的恩怨还真是说了不少,不过现在应该已经理清了。有了上一讲的基础,本讲的内容相对来说就比较容易理解了。

1.指向函数的指针(函数指针)

来分析这样一个声明,void (*f) ( );虽然()的优先级高于*,但由于有括号存在,首先执行的是解引用,所以f是一个指针;接下来执行( ),表明f指向一个函数,这个函数不返回任何值。现在得出结论:f是一个指向不接受参数且不返回任何值的函数的指针,简称函数指针(pointer to function)。

对比一下int(*p) [100],p是一个指向含有100个整型元素的数组的指针,它们有一个共同的特点:指针声明符(*)和标识符(f或p)都被限制在一个括号中,由于括号的优先级是最高的,所以我们从标识符开始由内向外分析,即可得到以上结果。

<1>.初始化

注意指向函数的指针(函数指针)指向的是函数而非普通的变量,它所指向的函数也是有特定类型的,函数的类型由它的返回值类型以及形参列表确定,和函数名无关。对函数指针初始化时可以采用相同类型函数的函数名或函数指针(当然还有零指针常量)。假如有函数void test ( ),int wrong_match (int)和函数指针void (*ptf) ( )。

下面的初始化是错误的,因为函数指针的类型与函数的类型不匹配:

f = wrong_match;

f = & wrong_match;

ptf = wrong_match;

ptf = & wrong_match;

以下初始化及赋值是合法的:

f = test;

f = &test;

ptf = test;

ptf = &test;

f = pf;

要做出解释的是test和&test都可以用来初始化函数指针。C语言规定函数名会被转换为指向这个函数的指针,除非这个函数名作为&操作符或sizeof操作符的操作数(注意:函数名用于sizeof的操作数是非法的)。也就是说f = test;中test被自动转换为&test,而f= &test;中已经显示使用了&test,所以test就不会再发生转换了。因此直接引用函数名等效于在函数名上应用&运算符,两种方法都会得到指向该函数的指针。

<2>.通过函数指针调用函数

通过函数指针调用函数可以有两种方法,直接使用函数指针或在函数指针前使用解引用运算符,如下所示:

f = test;

ptf = test;

f ( );

(*f) ( );//指针两侧的括号非常重要,表示先对f解引用,然后再调用相应的函数

ptf ( );

(*ptf) ( );//括号同样不能少

以上语句都能达到调用test函数的作用。ANSI C标准将f ( )认为是(*f)( )的简写形式,并且推荐使用f ( )形式,因为它更符合函数调用的逻辑。要注意的是:如果指向函数的指针没有初始化,或者具有0值(零指针常量),那么该指针不能在函数调用中使用。只有当指针已经初始化,或被赋值后指向某个函数才能安全地用来调用函数。

<3>.探究函数名

现在有如下程序:

#include

void test( )

{

printf("test called!\n");

}

int main()

{

void (*f) ( );

f = test;

f ( );

(*f)( );

//test++;//error,标准禁止对指向函数的指针进行自增运算

//test = test + 2;//error,不能对函数名赋值,函数名也不能用于进行算术运算

printf("%p\n", test);

printf("%p\n", &test);

printf("%p\n", *test);

return 0;

}

在我机器上的运行结果为:

test called!

test called!

004013EE

004013EE

004013EE

这个程序中较难理解的是3个输出语句都可以得到函数的入口地址。首先来看函数名test,它与数组名类似(注意:只是类似),是一个符号用来标识一个函数的入口地址,在使用中函数名会被转换为指向这个函数的指针,指针的值就是函数的入口地址,&test在前面已经说了:显示获取函数的地址。对于*test,可以认为由于test已经被转换成了函数指针,指向这个函数,所以*test就是取这个指针所指向的函数名,而又根据函数名会被转换指向该函数的指针的规则,这个函数也转变成了一个指针,所以*test最终也是一个指向函数test的指针。对它们采用%p格式项输出,都会得到以16进制数表示的函数test的入口地址。注意函数的地址在编译期是未知的,而是在链接时确定的。

2.返回指针的函数(指针函数)

类比指针数组(还记得吗),理解指针函数将会更加轻松。所谓指针函数,就是返回指针的函数,函数可以不返回任何值,也可以返回整型值,实型值,字符型值,当然也可以返回指针值。一个指针函数的声明:int *f(int i, int j);回想一下指针数组的声明:char *cars[10];同样的把它写成好理解的形式(非业界惯例)int* f(int i, int j);这样一来已经十分明了了,由于( )的优先级高于*,因此f先与()结合,所以f是一个具有两个int型参数,返回一个指向int型指针的函数。

C语言的库函数中有很多都是指针函数,比如字符串处理函数,下面给出一些函数原型:

char *strcat( char *dest, const char *src );

char *strcpy( char *dest, const char *src );

char *strchr( const char *s, int c );

char *strstr( const char *src, const char*sub );

注意函数的返回值不仅仅局限于指向变量的指针,也可以是指向函数的指针。初遇这种函数的声明可能会痛苦一点儿,但练习两三次应该是可以理解并掌握的。首先来看这个声明:int (*function(int)) (double*,char);要了解此声明的含义,首先来看function(int),将function声明为一个函数,它带有一个int型的形式参数,这个函数的返回值为一个指针,正是我们本将开头讲过的函数指针int (*) (double*, char);这个指针指向一个函数,此函数返回int型并带有两个分别是double*型和char型的形参。如果使用typedef可以将这个声明简化:

typedefint (*ptf) (double*, char);

ptffunction(int );

要说明一下,对于typedefint (*ptf) (double*,char);注意不要用#define的思维来看待typedef,如果用#define的思维来看的话会以为(*ptf)(double*, char)是int的别名,但这样的别名看起来好像又不是合法的名字,于是会处于迷茫状态。实际上,上面的语句把ptf定义为一种函数指针类型的别名,它和函数指针类型int (*) (double*, char);等价,也就是说ptf现在也是一种类型。

3.函数指针和指针函数的混合使用

函数指针不仅可以作为返回值类型,还可以作为函数的形式参数,如果一个函数的形参和返回值都是函数指针,这个声明看起来会更加复杂,例如:

void (*signal (int sig, void (*func) (intsiga)) ) ( int siga );看上去确实有些恼人,我们来一步一步的分析。现在要分析的是signal,因为紧邻signal的是优先级最高的括号,首先与括号结合,所以signal为一个函数,括号内为signal的两个形参,一个为int型,一个为指向函数的指针。接下来从向左看,*表示指向某对象的指针,它所处的位置表明它是signal的返回值类型,现在可以把已经分析过的signal整体去掉,得到void (*) ( int siga ),很清晰了吧。又是一个函数指针,这个指针与signal形参表中的第二个参数类型一样,都是指向接受一个int型形参且不返回任何值的函数的指针。同样地,用typedef可以将这个声明简化:

typedef void (*p_sig) (int);

p_sig signal(int sig, p_sig func);

这个signal函数是C语言的库函数,在signal.h中定义,用来处理系统中产生的信号,是UNIX/Linux编程中经常用到的一个函数,所以在此单独拿出来讲解一下。

4.函数指针数组

还有一种较为常用的关于函数指针的用法——函数指针数组。假设现在有一个文件处理程序,通过一个菜单按钮来选择相应的操作(打开文件,读文件,写文件,关闭文件)。这些操作都实现为函数且类型相同,分别为:

void open( );

void read( );

void write( );

void close( );

现在定义一个函数指针类型的别名PF:typedefvoid (*PF) ( );把以上4种操作取地址放入一个数组中,得到:

PF file_options[ ] = {

&open,

&read,

&write,

&close

};

这个数组中的元素都是指向不接受参数且不返回任何值的函数的指针,因此这是一个函数指针数组。接下来,定义一个函数指针类型的指针action并初始化为函数指针数组的第一个元素:PF* action = file_options;,如果不好理解,可以类比一下int ia[4] = {0, 1, 2, 3}; int *ip = ia;,这里PF相当于int,这样应该比较好懂了。通过对指针action进行下标操作可以调用数组中的任一操作,如:action[2]( )会调用write操作,以此类推。在实际中,指针action可以和鼠标或者其他GUI对象相关联,以达到相应的目的。

5.关于指针的复杂声明

第4点中的函数指针数组采用了typedef来声明,这是应该提倡的方法,因为它可读性更高。如果不使用typedef,那么分析起来就会比较复杂,结果是void (*file_options[ ]) ( );对于C语言的复杂声明我不想讲太多,因为在实际中用到的机会并不多,并且推荐大家多用typedef来简化声明的复杂度。对于分析复杂声明有一个极为有效的方法——右左法则。右左法则的大致描述为:从未定义的变量名开始阅读声明,先向右看,然后向左看。当遇到括号时就调转阅读的方向。括号内的所有内容都分析完毕就跳出括号。这样一直继续下去,直到整个声明都被分析完毕。来分析一个的例子:int * (* (*fp) (int) ) [10];

阅读步骤:

1.从未定义的变量名开始阅读--------------------------------------------fp

2.往右看,什么也没有,遇到了),因此往左看,遇到一个* ------一个指向某对象的指针

3.跳出括号,遇到了(int) -----------------------------------一个带一个int参数的函数

4.向左看,发现一个* ---------------------------------------(函数)返回一个指向某对象的指针

5.跳出括号,向右看,遇到[10] ------------------------------一个10元素的数组

6.向左看,发现一个* ---------------------------------------一个指向某对象指针

7.向左看,发现int -----------------------------------------int类型

所以fp是指向函数的指针,该函数返回一个指向数组的指针,此数组有10个int*型的元素。

对此我不再多举例了,下面给出一些声明,有兴趣的朋友可以试着分析一下,答案我会在下一讲中给出:

1.int *( *( *a[5]) ( ) ) ( );

2.void * (*b) ( char, int (*) ( ) );

3.float ( *(*c[10]) (int*) ) [5];

4.int ( *(*d)[2][3] ) [4][5];

5.int (*(*(*e) ( int* ))[15]) (int*);

6.int ( *(*f[4][5][6]) (int*) ) [10];

7.int *(*(*(*g)( ))[10]) ( );

前言

1.指针到底是什么

2.指针的定义及运算

3.指针与数组的“爱恨情仇”

5.指针与结构

6.使用指针时的“陷阱”

后记

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容