函数指针和指针函数
来源:互联网 发布:阿里云大厦地址 编辑:程序博客网 时间:2024/05/20 01:09
问题:
(1) float(**def)[10] def是什么?
(2) double*(*gh)[10] gh是什么?
(3) double(*f[10])() f是什么?
(4) int*((*b)[10]) b是什么?
回答:
(1) def是一个指针, 指向的对象也是一个指针, 指向的指针最终指向的是10个float构成的数组.
(2) gh是指针, 指向的是10个元素构成的数组, 数组的元素是double*类型的指针.
(3) f是10个元素构成的数组, 每个元素是指针, 指针指向的是函数, 函数类型为无参数且返回值为double. 下面要讲的窍门的例子跟这个很类似.
(4) b是指针,指向的是10个元素构成的数组, 数组元素为int*类型的指针.
识别的方法如下:
如果我们碰到复杂的类型声明,该如何解析它?例如:
char (*a[3])(int);
a到底被声明为什么东东?指针?数组?还是函数?
分析时,从a 最接近(按运算符优先级)处开始。我们看到a最接近符号是[ ]——注意:*比[ ]的优先级低。a后既然有[ ],那么a是数组,而且是包含3个元素的数组。
那这个数组的每个元素是什么类型呢?虽然数组a只含有a[0]、a[1]、a[2]三个元素,a[3]实际上已经越界,但在分析数组a的元素的类型时,我们正好需要形式上的元素a[3]。知道了a[3]的类型,就知道了a的元素的类型。 a[3]是什么类型?是指针,因为它的前面有*. 由此可知,数组a的元素是指针。
光说是指针还不够。对于指针,必须说出它指向的东东是什么类型。它指向的东东是什么,就看*a[3]是什么(a[3]是指针,它指向的东东当然是*a[3])了。继续按优先级观察,我们看到*a[3]后面有小括号,所以可以肯定*a[3]是函数。即数组a的元素是指向函数的指针。
指向的是什么类型的函数?这很明显,是入参为int、返回值为char的类型的函数。
至此解析完毕。
按上述方法,再复杂的也可以一步步解析出来。
就像习武不是为了打人而是为了防身一样,我们了解上述方法是为了看懂别人写的复杂声明,而不是为了在实践中自己去构造这种复杂的东东。实在需要复杂声明时,可以用typedef替代一部分。例如上面语句可改成两句:
typedef char (*FUN_PTR)(int);
FUN_PTR a[3];
这样就清晰多了。
此外,上面的分析方法还让我们对某些东西的本质更加清楚。比如,n维数组的本质都是一维数组。看个具体的例子:
int a[3][5];
这句声明的是一个包含3个元素的一维数组,其每个元素又是一个由5个int数构成的数组。我们不能理解为:a是一个包含5个元素的一维数组,其每个元素又是一个由3个int数构成的数组。为什么?还是按上面的方法分析,这里从略。
有的书上或网上提供"向右看,向左看"的方法, 其实缺乏通用性, 比如它不适用于对多维数组本质的分析. 而且这种方法掩盖了本质. 本质应该是按上面所讲的,根据运算符优先级逐层剥开.
关于函数指针的一些说明:
程序员常常需要实现回调。本文将讨论函数指针的基本原则并说明如何使用函数指针实现回调。注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数(类成员指针将在另文中讨论)。
声明函数指针
回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。请看下面的例子:
void f();// 函数原型
上面的语句声明了一个函数,没有输入参数并返回void。那么函数指针的声明方法如下:
void (*) ();
让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。注意本例中还没有创建指针变量-只是声明了变量类型。目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:
// 获得函数指针的大小
unsigned psize = sizeof (void (*) ());
// 为函数指针声明类型定义
typedef void (*pfv) ();
pfv是一个函数指针的自定义类型,它指向的函数没有输入参数,返回类行为void。使用这个类型定义名可以隐藏复杂的函数指针语法。
指针变量应该有一个变量名:
void (*p) (); //p是指向某函数的指针
p是指向某函数的指针,该函数无输入参数,返回值的类型为void。左边圆括弧里星号后的就是指针变量名。有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。例如:
void func()
{
/* do something */
}
p = func;
p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。
传递回调函数的地址给调用者
现在可以将p传递给另一个函数(调用者)- caller(),它将调用p指向的函数,而此函数名是未知的:
void caller(void(*ptr)())
{
ptr(); /* 调用ptr指向的函数 */
}
void func();
int main()
{
p = func;
caller(p); /* 传递函数地址到调用者 */
}
如果赋了不同的值给p(不同函数地址),那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。
调用规范
到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。
将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:
// 被调用函数是以int为参数,以int为返回值
__stdcall int callee(int);
// 调用函数以函数指针为参数
void caller( __cdecl int(*ptr)(int));
// 在p中企图存储被调用函数地址的非法操作
__cdecl int(*p)(int) = callee; // 出错
指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列。
- 指针函数和函数指针
- 函数指针和指针函数
- 指针函数和函数指针
- 指针函数和函数指针
- 函数指针和指针函数
- 函数指针和指针函数
- 函数指针和指针函数
- 指针函数和函数指针
- 函数指针和指针函数
- 指针函数和函数指针
- 函数指针和指针函数
- 指针函数和函数指针
- 指针函数和函数指针
- 指针函数和函数指针
- 函数指针和指针函数
- 函数指针和指针函数
- 函数指针和指针函数
- 函数指针和指针函数
- WINDOWS核心编程--读书笔记:第三章 内核对象
- TFCI码的编译码
- SmartFoxServer2X ,sfs2x --- 实战 一
- 软件开发中的葵花宝典(转)
- VC ADO Oracle 修改BLOB
- 函数指针和指针函数
- 网络编程中数据格式的转换
- SSH项目中异常处理与日志记录
- HGE系列番外篇
- linux的文件属性和权限学习——分析ls命令结果
- DSP学习
- 华丽丽的效果
- 1001-二分查找
- C与面向对象