概述

   在很多情况下,尤其是读别人所写代码的时候,对 C语言声明的理解能力变得非常重要,而C语言本身的凝练简约也使得C语言的声明常常会令人感到非常困惑,因此,在这里我用一篇的内容来集中阐述一下这个问题。

  问题:声明与函数

  有一段程序存储在起始地址为 0的一段内存上,如果我们想要调用这段程序,请问该如何去做?

  答案

  答案是 (*(void (*)( ) )0)( )。看起来确实令人头大,那好,让我们知难而上,从两个不同的途径来详细分析这个问题。

  答案分析:从尾到头

  首先,最基本的函数声明: void function (paramList);

  最基本的函数调用: function(paramList);

  鉴于问题中的函数没有参数,函数调用可简化为 function();

   其次,根据问题描述,可以知道 0是这个函数的入口地址,也就是说,0是一个函数的指针。使用函数指针的函数声明形式是:void (*pFunction)(),相应的调用形式是: (*pFunction)(),则问题中的函数调用可以写作:(*0)( )。

  第三,大家知道,函数指针变量不能是一个常数,因此上式中的 0必须要被转化为函数指针。

  我们先来研究一下,对于使用函数指针的函数:比如 void (*pFunction)( ),函数指针变量的原型是什么?这个问题很简单,pFunction函数指针原型是( void (*)( ) ),即去掉变量名,清晰起见,整个加上()号。

  所以将 0强制转换为一个返回值为void,参数为空的函数指针如下:( void (*)( ) )。

   OK,结合2)和3)的分析,结果出来了,那就是:(*(void (*)( ) )0)( ) 。

  答案分析:从头到尾理解答案

   (void (*)( )) ,是一个返回值为void,参数为空的函数指针原型。
   (void (*)( ))0,把0转变成一个返回值为void,参数为空的函数指针,指针指向的地址为0.
   *(void (*)( ))0,前面加上*表示整个是一个返回值为void的函数的名字
   (*(void (*)( ))0)( ),这当然就是一个函数了。

  我们可以使用 typedef清晰声明如下:

   typedef void (*pFun)( );

  这样函数变为 (*(pFun)0 )( );

  问题:三个声明的分析

  对声明进行分析,最根本的方法还是类比替换法,从那些最基本的声明上进行类比,简化,从而进行理解,下面通过分析三个例子,来具体阐述如何使用这种方法。

# 1:int* (*a[5])(int, char*);

   首先看到标识符名 a,"[]"优先级大于"*",a与"[5]"先结合。所以a是一个数组,这个数组有5个元素,每一个元素都是一个指针,指针指向"(int, char*)",很明显,指向的是一个函数,这个函数参数是"int, char*",返回值是"int*"。OK,结束了一个。:)

# 2:void (*b[10]) (void (*)());

   b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是"void (*)()"【注10】,返回值是"void"。完毕!

  注意:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是 "void"。

# 3. doube(*)() (*pa)[9];

   pa是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是"doube(*)()"(也即一个函数指针,指向一个函数,这个函数的参数为空,返回值是"double")。

C语言中的函数指针

函数在内存中有一个物理位置,而这个位置是可以赋给一个指针的。一零点函数的地址就是该函数的入口点。因此,函数指针可被用来调用一个函数。函数的地址是用不带任何括号或参数的函数名来得到的。(这很类似于数组地址的得到方法,即,在只有数组名而无下标是就得到数组地址。)

怎样说明一个函数指针变量呢 ?
为了说明一个变量 fn_pointer 的类型是"返回值为 int 的函数指针", 你可以使用下面的说明语句:
int (*fn_pointer) ();
为了让编译器能正确地解释这句语句, *fn_pointer 必须用括号围起来。若漏了这对括号, 则:
int *fn_pointer ();
的意思完全不同了。fn_pointer 将是一个函数名, 其返回值为 int 类型的指针。

函数指针变量  

  在C语言中规定,一个函数总是占用一段连续的内存区,   而函数名就是该函数所占内存区的首地址。   我们可以把函数的这个首地址 ( 或称入口地址 ) 赋予一个指针变量,   使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。   我们把这种指向函数的指针变量称为 " 函数指针变量 " 。  
函数指针变量定义的一般形式为:  
类型说明符  (* 指针变量名 )();  
其中 " 类型说明符 " 表示被指函数的返回值的类型。 "(*  指针变量名 )" 表示 "*" 后面的变量是定义的指针变量。   最后的空括号表示指针变量所指的是一个函数。  
例如:  int (*pf)(); 
表示 pf 是一个指向函数入口的指针变量,该函数的返回值 ( 函数值 ) 是整型。  
下面通过例子来说明用指针形式实现对函数调用的方法。  
int max(int a,int b){ 
if(a>b)return a; 
else return b; 

main(){ 
int max(int a,int b); 
int(*pmax)(); 
int x,y,z; 
pmax=max; 
printf("input two numbers:/n"); 
scanf("%d%d",&x,&y); 
z=(*pmax)(x,y); 
printf("maxmum=%d",z); 

  从上述程序可以看出用,函数指针变量形式调用函数的步骤如下:

1.  先定义函数指针变量,如后一程序中第 9 行  int (*pmax)(); 定义 pmax 为函数指针变量。

2.  把被调函数的入口地址 ( 函数名 ) 赋予该函数指针变量,如程序中第 11 行  pmax=max;

3.  用函数指针变量形式调用函数,如程序第 14 行  z=(*pmax)(x,y);  调用函数的一般形式为:  (* 指针变量名 ) ( 实参表 ) 使用函数指针变量还应注意以下两点:

a.  函数指针变量不能进行算术运算,这是与数组指针变量不同的。数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的。

b.  函数调用中 "(* 指针变量名 )" 的两边的括号不可少,其中的 * 不应该理解为求值运算,在此处它只是一种表示符号。

指针型函数  

前面我们介绍过,所谓函数类型是指函数返回值的类型。   在C语言中允许一个函数的返回值是一个指针 ( 即地址 ) ,   这种返回指针值的函数称为指针型函数。  
定义指针型函数的一般形式为:   
类型说明符  * 函数名 ( 形参表 )  
{  
...... /* 函数体 */ 
}  
其中函数名之前加了 "*" 号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。  
如:  
int *ap(int x,int y) 

...... /* 函数体 */ 

  表示 ap 是一个返回指针值的指针型函数,   它返回的指针指向一个整型变量。下例中定义了一个指针型函数  day_name ,它的返回值指向一个字符串。该函数中定义了一个静态指针数组 name 。 name  数组初始化赋值为八个字符串,分别表示各个星期名及出错提示。形参 n 表示与星期名所对应的整数。在主函数中,   把输入的整数 i 作为实参,   在 printf 语句中调用 day_name 函数并把 i 值传送给形参  n 。 day_name 函数中的 return 语句包含一个条件表达式,  n  值若大于 7 或小于 1 则把 name[0]  指针返回主函数输出出错提示字符串 "Illegal day" 。否则返回主函数输出对应的星期名。主函数中的第 7 行是个条件语句,其语义是,如输入为负数 (i<0) 则中止程序运行退出程序。 exit 是一个库函数, exit(1) 表示发生错误后退出程序,  exit(0) 表示正常退出。

  应该特别注意的是函数指针变量和指针型函数这两者在写法和意义上的区别。如 int(*p)() 和 int *p() 是两个完全不同的量。 int(*p)() 是一个变量说明,说明 p  是一个指向函数入口的指针变量,该函数的返回值是整型量, (*p) 的两边的括号不能少。 int *p()  则不是变量说明而是函数说明,说明 p 是一个指针型函数,其返回值是一个指向整型量的指针, *p 两边没有括号。作为函数说明,   在括号内最好写入形式参数,这样便于与变量说明区别。   对于指针型函数定义, int *p() 只是函数头部分,一般还应该有函数体部分。  
main(){ 
int i; 
char *day_name(int n);  
printf("input Day No:/n"); 
scanf("%d",&i); 
if(i<0) exit(1); 
printf("Day No:%2d-->%s/n",i,day_name(i)); 

char *day_n

ame(int n){ 
static char *name[]={ "Illegal day", 
"Monday", 
"Tuesday", 
"Wednesday", 
"Thursday", 
"Friday", 
"Saturday", 
"Sunday"}; 
return((n<1||n>7) ? name[0] : name[n]); 

  本程序是通过指针函数,输入一个 1 ~ 7 之间的整数,   输出对应的星期名。指针数组的说明与使用一个数组的元素值为指针则是指针数组。   指针数组是一组有序的指针的集合。   指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。  
  指针数组说明的一般形式为:   类型说明符 * 数组名 [ 数组长度 ]  
  其中类型说明符为指针值所指向的变量的类型。例如:  int *pa[3]  表示 pa 是一个指针数组,它有三个数组元素,   每个元素值都是一个指针,指向整型变量。通常可用一个指针数组来指向一个二维数组。   指针数组中的每个元素被赋予二维数组每一行的首地址,   因此也可理解为指向一个一维数组。图 6—6 表示了这种关系。  
int a[3][3]={1,2,3,4,5,6,7,8,9}; 
int *pa[3]={a[0],a[1],a[2]}; 
int *p=a[0]; 
main(){ 
int i; 
for(i=0;i<3;i++) 
printf("%d,%d,%d/n",a[i][2-i],*a[i],*(*(a+i)+i)); 
for(i=0;i<3;i++) 
printf("%d,%d,%d/n",*pa[i],p[i],*(p+i)); 

  本例程序中, pa 是一个指针数组,三个元素分别指向二维数组 a 的各行。然后用循环语句输出指定的数组元素。其中 *a[i] 表示 i 行 0 列元素值; *(*(a+i)+i) 表示 i 行 i 列的元素值; *pa[i] 表示 i 行 0 列元素值;由于 p 与 a[0] 相同,故 p[i] 表示 0 行 i 列的值; *(p+i) 表示 0 行 i 列的值。读者可仔细领会元素值的各种不同的表示方法。   应该注意指针数组和二维数组指针变量的区别。   这两者虽然都可用来表示二维数组,但是其表示方法和意义是不同的

声明与函数、函数指针相关推荐

  1. C++ 笔记(14)— 指针(指针声明、取地址、取值、new/delete、NULL指针、指针运算、指针数组、数组指针、指针传递给函数、从函数返回指针)

    1. 声明指针 指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址.就像其他变量或常量一样,您必须在使用指 针存储其他变量地址之前,对其进行声明. 指针变量声明的一般形式为: type * ...

  2. 理解复杂的C/C++声明 const, typedef , 函数指针(转贴)

    让我们从一个非常简单的例子开始,如下: int n; 这个应该被理解为"declare n as an int"(n是一个int型的变量). 接下去来看一下指针变量,如下: int ...

  3. 怎么建立软连接和删除软连接、宏定义个声明一年有多少秒、关于自定义函数类型指针

    怎么建立软连接和删除软连接 参数-f指force强制的意思 创建个/data/test/etc_ln 软连接为/etc 目录: ln -sf /etc/ /data/test/etc_ln删除软连接: ...

  4. 为什么基类的析构函数要声明成虚函数

    记得以后基类(父类) 的析构函数最好是声明为 虚函数 即:virtual 开发中遇到了一个比较傻逼的bug,也证明了理论与实际之间的差距. 在基类中没有声明其析构函数为虚函数,导致delete 释放操 ...

  5. 初论函数指针、指针函数、指针的指针

    一.指针函数 1.定义 指针函数是指带指针的函数,即本质是一个函数.函数返回类型是某一类型的指针 函数返回值类型 函数名(参数表) int * f(int x,int y); //函数返回值类型是in ...

  6. 基类的析构函数不能被继承。_为什么要把C++类中的析构函数声明为虚函数?

    如题,当一个类为基类的时候,通常其析构函数被声明为虚函数,这是为啥? class BaseCls { public: BaseCls() { printf("BaseCls()n" ...

  7. c c++ 函数内数组初值_C编程基础-关键字-函数和指针

    C术语的基本定义,例如关键字,标识符,运算符,指针,数据类型,void main(),函数和递归. 在大多数情况下,我们突然无法回答一些简单的问题,例如C中的关键字是什么,C中的标识符是什么,C中的数 ...

  8. 指向函数的指针--转

    http://book.51cto.com/art/200908/146363.htm 5.1.2 指向函数的指针 C语言通过&和*操作符来操作数据的地址,但它并没有提供一个用一般的方式来操作 ...

  9. 构造函数不可以声明为虚函数,析构函数可以声明为虚函数

    构造函数不能声明为虚函数,而析构函数可以声明为虚函数,在有的情景下析构函数必须声明为虚函数.  不建议在构造函数和析构函数里调用虚函数. 构造函数不能声明为虚函数的原因? 构造一个对象时,必须知道对象 ...

  10. C++ Primer 5th笔记(6)chapter6 函数:函数指针

    1. 函数指针 bool lengthCompare(const string&, const string&); bool (*pf)(const string&, cons ...

最新文章

  1. php复合索引,关于复合索引和单独索引的一个问题
  2. 成功解决​​​​​​​安装pywin32时出现python version 3.6 required, which was not found in the registry
  3. android通用的UUID唯一标示符
  4. UVA 11038 How Many O's?
  5. 定义类的Python示例
  6. 《上市公司信息披露电子化规范》简介
  7. css3漂亮的渐变图案,CSS3 带渐变图案的圆球
  8. 基于html5的学生管理系统,基于HTML5的学生信息管理系统的设计与实现
  9. 调用新浪微博显示用户信息
  10. Pandas+Matplotlib,深入浅出Python数据分析
  11. alisql mysql_alisql|alisql数据库下载 v5.6 官方版_小皮网
  12. 【光剑藏书轩2021】《表象与本质:类比,思考之源和思维之火》
  13. 我的世界java材质包推荐下载_我的世界材质包排行-Minecraft材质包-我的世界高清材质包下载大全-Minecraft中文分享站...
  14. Idea新建项目和快捷键
  15. 主成分分析法步骤matlab,主成分分析法matlab实现程序
  16. 线程----code
  17. python selenium模拟浏览器操作实战(武汉大学原教务系统)
  18. 通过USB在传统电视上播放B站视频
  19. 窗口最小化后不出现在任务栏上
  20. 程序员新手上路第一步

热门文章

  1. altium designer学习记录
  2. ELK 环境搭建1-Elasticsearch
  3. JEESZ分布式框架之技术介绍文档
  4. [js对象]JS入门之Global对象
  5. 中国人工智能学会通讯——人工智能如何造福人类 1.1 人工智能是中性技术
  6. 15款帮助你实现响应式导航的 jQuery 插件
  7. java 大数据处理一
  8. 创建和触发Notification
  9. MS SQL入门基础:启动与关闭服务器
  10. dll文件32位64位检测工具以及Windows文件夹SysWow64的坑