分清函数指针和指针函数
分清函数指针和指针函数
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<stdio.h>
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可以将这个声明简化:
typedef int (*ptf) (double*, char);
ptf function(int );
要说明一下,对于typedef int (*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. int *( *( *a[5]) ( ) ) ( );
Answer:a是一个数组,它的5个元素都是指向函数的指针,该函数仍旧返回一个指向函数的指针。
2. void * (*b) ( char, int (*) ( ) );
Answer:b是一个函数指针,该函数接受两个形参,分别是char型和一个函数指针,返回值类型为void *。
3. float ( *(*c[10]) (int *) ) [5];
Answer:c是一个数组,它的10个元素都是指向函数的指针,所指函数接受int *型形参并返回一个指向数组的指针,这个数组包含5个float元素。
4. int ( *(*d)[2][3] ) [4][5];
Answer:d是一个指向数组的指针,此数组的元素又是一个指向数组的指针。
5. int (*(*(*e) ( int* ))[15]) (int*);
Answer:e是一个函数指针,该函数的返回值是一个指向数组的指针,所指向数组的元素又是函数指针,指向的函数具有int *型形参,返回值类型为int。
6. int ( *(*f[4][5][6]) (int*) ) [10];
Answer:f是一个数组,这个数组的元素是函数指针,这类函数具有int *型形参并返回指向数组的指针,所指向的数组的元素是具有10个int型元素。
7. int *(*(*(*g)( ))[10]) ( );
Answer:g是个指向函数的指针,它所指向的函数返回一个指向包含10个元素的数组的指针,数组元素的类型是指向函数的指针,所指向的函数不接受参数且返回值类型为int *。
分清函数指针和指针函数相关推荐
- 函数返回数组指针的几种方法
最直接的方法是用类型名: typedef int arr[10]; // arr是一个类型名,表示含有10个int的数组 using arr = int[10]; // 与上等价声明arr* fun( ...
- c/c++中的函数指针和指针函数
定义 1.指针函数,本质是函数,返回值为指针,形如,int *pfun(int, int),由于"*"的优先级低于"()"的优先级,所以等同于int *(pfu ...
- C++ 笔记(14)— 指针(指针声明、取地址、取值、new/delete、NULL指针、指针运算、指针数组、数组指针、指针传递给函数、从函数返回指针)
1. 声明指针 指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址.就像其他变量或常量一样,您必须在使用指 针存储其他变量地址之前,对其进行声明. 指针变量声明的一般形式为: type * ...
- Go 学习笔记(13)— 指针定义、指针特点、空指针、指针数组、指向指针的指针、指针作为函数入参
1. 复合数据类型 Go 语言基本的复合数据类型有指针.数组.切片.字典.通道.结构和接口等.格式如下: * pointerType // 指针类型, [n]elementType // 数组类型, ...
- C指针6:指针变量作为函数参数
在C语言中,函数的参数不仅可以是整数.小数.字符等具体的数据,还可以是指向它们的指针.用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着 ...
- C++成员变量指针和成员函数指针【The semantics of funcitons】
原文:https://blog.csdn.net/laojiu_/article/details/68946915 (原文有笔误) 1. #include <cstdio> #includ ...
- 用指针、子函数的方法去一维数组中所有元素的平均值,并放在a[0]处
<程序设计基础实训指导教程-c语言> ISBN 978-7-03-032846-5 p142 7.1.2 上级实训内容 [实训内容7]用指针.子函数的方法去一维数组中所有元素的平均值,并放 ...
- 函数指针与指针函数的区别
原文:http://yliangliang.blog.sohu.com/86320000.html 一.函数指针 首先它是一个指针,只是这个指针指向的是一个函数.指针变量可以指向变量的地址.数组.字符 ...
- 多态指针访问虚函数不能被继承的类快速排序N皇后问题插入排序堆排序merge归并排序栈上生成对象两个栈实现一个队列...
多态 /*1. 要想实现覆盖(重写)父类必须声明为virtual,子类可以不声明为virtual.-->FunB()2. 派生类重写基类的虚函数实现多态,要求函数名.参数列表.返回值完全相同.( ...
最新文章
- CAS、原子操作类的应用与浅析及Java8对其的优化
- freeRtos学习笔记 (6)软件定时器
- rabbitmq-java api
- 微软超融合私有云测试08-SCVMM部署之SQL Server与前置条件安装
- 如何在PowerPoint中制作打字机或命令行动画
- PHP中怎样实现正负数的相加,PHP 求任意n个正负整数里面最大的连续和
- leetcode 19. 删除链表的倒数第N个节点(双指针)
- 格式化字符串漏洞利用 二、格式化函数
- 软件测试---弹出窗口
- [MATLAB]设置坐标轴标签
- mongodb 可视化_自动爬取疫情数据、交互式地图可视化
- sql server 2005 链接服务器:未将服务器 配置为用于 RPC
- vs2005中分割线怎么插入
- 英语句子划分表示符号使用规则
- python文件管理api_python调用有道智云API实现文件批量翻译
- 数组按照字母顺序排序
- 什么是接口测试?接口测试的流程步骤
- UVA 11021 繁衍麻球
- 学计算机买笔记本是i5 i7,对我们普通人买电脑来说,i7和i5、i3有多大区别,玩游戏选哪款好?!...
- 7-242 母牛问题
热门文章
- linux源码Makefile详解(完整)-转
- SQLite指南(0) 表和索引的文件存储结构
- 如何判断cin输入结束~
- 蓝桥杯 ALGO-63 算法训练 乘法表
- PAT 乙级 1046. 划拳(15) Java版
- bluecam连接步骤说明_迈拓维距Type-C扩展坞手机连接电视图文教程
- new blob文件设置编码_前端下载文件amp;下载进度
- python爬虫requests库_python爬虫使用Requests库 - pytorch中文网
- 实用Python之字符串长度计算
- CAT - 监控平台之装配篇