深入学习C语言中的函数指针和左右法则

通常的函数调用
    一个通常的函数调用的例子:

//自行包含头文件
void MyFun(int x); //此处的申明也可写成:void MyFun( int );

int main(int argc, char* argv[])
{
  MyFun(10); //这里是调用MyFun(10);函数

   return 0;
}

void MyFun(int x) //这里定义一个MyFun函数
{
  printf(“%d\n”,x);
}

这个MyFun函数是一个无返回值的函数,它并不完成什么事情。这种调用函数的格式你应该是很熟悉的吧!看主函数中调用MyFun函数的书写格式:
MyFun(10);
    我们一开始只是从功能上或者说从数学意义上理解MyFun这个函数,知道MyFun函数名代表的是一个功能(或是说一段代码)。
    直到——
    学习到函数指针概念时。我才不得不在思考:函数名到底又是什么东西呢?
    (不要以为这是没有什么意义的事噢!呵呵,继续往下看你就知道了。)

函数指针变量的申明
    就象某一数据变量的内存地址可以存储在相应的指针变量中一样,函数的首地址也以存储在某个函数指针变量里的。这样,我就可以通过这个函数指针变量来调用所指向的函数了。
    在C系列语言中,任何一个变量,总是要先申明,之后才能使用的。那么,函数指针变量也应该要先申明吧?那又是如何来申明呢?以上面的例子为例,我来申明一个可以指向MyFun函数的函数指针变量FunP。下面就是申明FunP变量的方法:
void (*FunP)(int) ;   //也可写成void (*FunP)(int x);
    你看,整个函数指针变量的申明格式如同函数MyFun的申明处一样,只不过——我们把MyFun改成(*FunP)而已,这样就有了一个能指向MyFun函数的指针FunP了。(当然,这个FunP指针变量也可以指向所有其它具有相同参数及返回值的函数了。)

通过函数指针变量调用函数
    有了FunP指针变量后,我们就可以对它赋值指向MyFun,然后通过FunP来调用MyFun函数了。看我如何通过FunP指针变量来调用MyFun函数的:

//自行包含头文件
void MyFun(int x); //这个申明也可写成:void MyFun( int );
void (*FunP)(int ); //也可申明成void(*FunP)(int x),但习惯上一般不这样。

int main(int argc, char* argv[])
{
  MyFun(10); //这是直接调用MyFun函数
  FunP=&MyFun; //将MyFun函数的地址赋给FunP变量
  (*FunP)(20); //这是通过函数指针变量FunP来调用MyFun函数的。
}

void MyFun(int x) //这里定义一个MyFun函数
{
  printf(“%d\n”,x);
}

请看黑体字部分的代码及注释。
    运行看看。嗯,不错,程序运行得很好。
    哦,我的感觉是:MyFun与FunP的类型关系类似于int 与int *的关系。函数MyFun好像是一个如int的变量(或常量),而FunP则像一个如int *一样的指针变量。

int i,*pi;
pi=&i;  //与FunP=&MyFun比较。

(你的感觉呢?)
    呵呵,其实不然——

调用函数的其它书写格式
函数指针也可如下使用,来完成同样的事情:

//自行包含头文件
void MyFun(int x);
void (*FunP)(int ); //申明一个用以指向同样参数,返回值函数的指针变量。

int main(int argc, char* argv[])
{
  MyFun(10); //这里是调用MyFun(10);函数
  FunP=MyFun; //将MyFun函数的地址赋给FunP变量
  FunP(20); //这是通过函数指针变量来调用MyFun函数的。

   return 0;
}

void MyFun(int x) //这里定义一个MyFun函数
{
  printf(“%d\n”,x);
}

我改了黑体字部分(请自行与之前的代码比较一下)。
运行试试,啊!一样地成功。
咦?

FunP=MyFun;

可以这样将MyFun值同赋值给FunP,难道MyFun与FunP是同一数据类型(即如同的int 与int的关系),而不是如同int 与int*的关系了?(有没有一点点的糊涂了?)
看来与之前的代码有点矛盾了,是吧!所以我说嘛!
请容许我暂不给你解释,继续看以下几种情况(这些可都是可以正确运行的代码哟!):
代码之三:

int main(int argc, char* argv[])
{
  MyFun(10); //这里是调用MyFun(10);函数
  FunP=&MyFun; //将MyFun函数的地址赋给FunP变量
  FunP(20); //这是通过函数指针变量来调用MyFun函数的。

   return 0;
}

代码之四:

int main(int argc, char* argv[])
{
  MyFun(10); //这里是调用MyFun(10);函数
  FunP=MyFun; //将MyFun函数的地址赋给FunP变量
  (*FunP)(20); //这是通过函数指针变量来调用MyFun函数的。

   return 0;
}

真的是可以这样的噢!
(哇!真是要晕倒了!)
还有呐!看——

int main(int argc, char* argv[])
{
  (*MyFun)(10); //看,函数名MyFun也可以有这样的调用格式

   return 0;
}

你也许第一次见到吧:函数名调用也可以是这样写的啊!(只不过我们平常没有这样书写罢了。)
那么,这些又说明了什么呢?
呵呵!假使我是“福尔摩斯”,依据以往的知识和经验来推理本篇的“新发现”,必定会由此分析并推断出以下的结论:
1. 其实,MyFun的函数名与FunP函数指针都是一样的,即都是函数指针。MyFun函数名是一个函数指针常量,而FunP是一个函数数指针变量,这是它们的关系。
2. 但函数名调用如果都得如(*MyFun)(10);这样,那书写与读起来都是不方便和不习惯的。所以C语言的设计者们才会设计成又可允许MyFun(10);这种形式地调用(这样方便多了并与数学中的函数形式一样,不是吗?)。
3. 为统一起见,FunP函数指针变量也可以FunP(10)的形式来调用。
4. 赋值时,即可FunP=&MyFun形式,也可FunP=MyFun。
上述代码的写法,随便你爱怎么着!
请这样理解吧!这可是有助于你对函数指针的应用喽!
最后——
补充说明一点:在函数的申明处:

void MyFun(int );  //不能写成void (*MyFun)(int )。
void (*FunP)(int );  //不能写成void FunP(int )。

(请看注释)这一点是要注意的。

定义某一函数的指针类型:
就像自定义数据类型一样,我们也可以先定义一个函数指针类型,然后再用这个类型来申明函数指针变量。
我先给你一个自定义数据类型的例子。

typedef int* PINT; //为int* 类型定义了一个PINT的别名
int main()
{
 int x;
 PINT px=&x; //与int * px=&x;是等价的。PINT类型其实就是int * 类型
 *px=10; //px就是int*类型的变量
 return 0;
}

根据注释,应该不难看懂吧!(虽然你可能很少这样定义使用,但以后学习Win32编程时会经常见到的。)
下面我们来看一下函数指针类型的定义及使用:(请与上对照!)

//自行包含头文件
void MyFun(int x); //此处的申明也可写成:void MyFun( int );
typedef void (*FunType)(int ); //这样只是定义一个函数指针类型
FunType FunP; //然后用FunType类型来申明全局FunP变量

int main(int argc, char* argv[])
{
//FunType FunP; //函数指针变量当然也是可以是局部的 ,那就请在这里申明了。
  MyFun(10);
  FunP=&MyFun;
  (*FunP)(20); 

   return 0;
}

void MyFun(int x)
{
  printf(“%d\n”,x);
}

看黑体部分:
首先,在void (*FunType)(int ); 前加了一个typedef 。这样只是定义一个名为FunType函数指针类型,而不是一个FunType变量。
然后,FunType FunP;  这句就如PINT px;一样地申明一个FunP变量。
其它相同。整个程序完成了相同的事。
这样做法的好处是:
有了FunType类型后,我们就可以同样地、很方便地用FunType类型来申明多个同类型的函数指针变量了。如下:

FunType FunP2;
FunType FunP3;
//……

函数指针作为某个函数的参数
既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。所以,你还应知道函数指针是如何作为某个函数的参数来传递使用的。
给你一个实例:
要求:我要设计一个CallMyFun函数,这个函数可以通过参数中的函数指针值不同来分别调用MyFun1、MyFun2、MyFun3这三个函数(注:这三个函数的定义格式应相同)。
实现:代码如下:

//自行包含头文件
void MyFun1(int x);
void MyFun2(int x);
void MyFun3(int x);
typedef void (*FunType)(int ); //②. 定义一个函数指针类型FunType,与①函数类型一至
void CallMyFun(FunType fp,int x);

int main(int argc, char* argv[])
{
  CallMyFun(MyFun1,10); //⑤. 通过CallMyFun函数分别调用三个不同的函数
  CallMyFun(MyFun2,20);
  CallMyFun(MyFun3,30);
}
void CallMyFun(FunType fp,int x) //③. 参数fp的类型是FunType。
{
 fp(x);//④. 通过fp的指针执行传递进来的函数,注意fp所指的函数是有一个参数的
}
void MyFun1(int x) // ①. 这是个有一个参数的函数,以下两个函数也相同
{
  printf(“函数MyFun1中输出:%d\n”,x);
}
void MyFun2(int x)
{
  printf(“函数MyFun2中输出:%d\n”,x);
}
void MyFun3(int x)
{
  printf(“函数MyFun3中输出:%d\n”,x);
}

输出结果:略

分析:(看我写的注释。你可按我注释的①②③④⑤顺序自行分析。)
 
以上部分为转载网友所述。原文地址为:http://blog.pfan.cn/whyhappy/6030.html
 
 
地址跳转
void(*reset)(void)= (void(*)(void))0。
  void(*reset)(void)就是函数指针定义,(void(*)(void))0是强制类型转换操作,将数值“0”强制转换为函数指针地址“0”。
  通过调用reset()函数,程序就会跳转到程序执行的“0”地址处重新执行。在一些其他高级单片机Bootloader中,如NBoot、UBoot、EBoot,经常通过这些Bootloader进行下载程序,然后通过函数指针跳转到要执行程序的地址处。
1   void (*theUboot)(void);
    。。。。
    theUboot = (void (*)(void))(0x30700000);
    theUboot();
    。。。。。
2   (*(void (*)(void))(0x30700000))();
强制类型转换,将一个绝对地址转换为一个函数指针,并调用这个函数以跳转到前面提到的绝对地址.
翻译成汇编就是:
mov r0,0x30700000;
mov pc,r0
对于(*(void (*)(void))(0x30700000))();
可以这样理解
首先(void( * )(void) )是一个强制类型转换符,他将后面的0x30700000这个无符号整数强制转化为一个函数指针,该函数指针所指向的函数入口参数为 void,返回值也是void 。 如果到这步你看懂了,那么设(void (*)(void))(0x30700000)为 fp; 那么上面的表达式就可以简化为 (*fp)();   OK,这下就清楚了吧,我们将上面转化好的函数指针进行引用(也就是调用函数指针指向的函数)。

右左法则

c语言有复杂的指针声明,都是由各种声明嵌套构成的。各大公司的笔试题里经常会出现理解复杂指针声明,右左法则是一个著名又常用的方法。
The right-left rule : start reading the declaration from the innermost parentheses, go rigtht, and then go left. when you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.

右左规则:首先从最里面的括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。

原文作者对这里进行了修正:应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里可能有多个标识符,但未定义的标识符只会有一个

示例

  int (*func) (int *p); 

首先找到未定义标识符,就是func,它的外面有一对圆括号,而且左边是一个*,这说明func是一个指针。然后跳出括号,看右边,也是一个括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,也就是一个函数指针。这类函数具有int*类型的参数,返回值类型是int

  int (*func)(int *p, int (*f)(int *));

func被一对括号包含,且左边有一个*号,说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,说明func是一个函数指针。这类函数具有int *和 int (*)(int *)这样的形参,返回值是int。对于int (*f)(int *)的形参,分析方法跟func是一致的

  int (*func[5])(int *p);

func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个*,说明func的元素是指针,要注意这里的*不是修饰func的,而是修饰func[5]的,原因是[]运算符的优先级比*高,func先跟[]结合,因此*修饰的是func[5].跳出这个括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型是int

  int (*(*func)[5])(int *p);

func被一对圆括号包围,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,说明这个数组的元素是指针,再跳出括号,向右看,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下就是:func是一个指向数组的指针,这个数组的元素是函数指针,这些指针具有int *形参,返回值为int类型的函数

  int (*(*func)(int *p))[5];

func是一个函数指针,这类函数具有int *类型的形参,返回值是指向数组的指针,所指向的是具有5个int类型元素的数组。

(0)

相关推荐

  • 深入解析C语言中函数指针的定义与使用

    1.函数指针的定义     函数是由执行语句组成的指令序列或者代码,这些代码的有序集合根据其大小被分配到一定的内存空间中,这一片内存空间的起始地址就成为函数的地址,不同的函数有不同的函数地址,编译器通过函数名来索引函数的入口地址,为了方便操作类型属性相同的函数,c/c++引入了函数指针,函数指针就是指向代码入口地址的指针,是指向函数的指针变量. 因而"函数指针"本身首先应该是指针变量,只不过该指针变量指向函数.这正如用指针变量可指向整形变量.字符型.数组一样,这里是指向函数.C在编译时

  • 详解C语言编程中的函数指针以及函数回调

    函数指针: 就是存储函数地址的指针,就是指向函数的指针,就是指针存储的值是函数地址,我们可以通过指针可以调用函数. 我们先来定义一个简单的函数: //定义这样一个函数 void easyFunc() { printf("I'm a easy Function\n"); } //声明一个函数 void easyFunc(); //调用函数 easyFunc(); //定义这样一个函数 void easyFunc() { printf("I'm a easy Function\n

  • c语言:基于函数指针的两个示例分析

    第一个:------------------------------------------------------ 复制代码 代码如下: #include <stdio.h>#include <string.h>void tell_me(int f(const char *, const char *));int main(void){   tell_me(strcmp);   tell_me(main);   return 0;}void tell_me(int f(const

  • C语言中的函数指针基础学习教程

    顾名思义,函数指针就是函数的指针.它是一个指针,指向一个函数.看例子: A) char * (*fun1)(char * p1,char * p2); B) char * *fun2(char * p1,char * p2); C) char * fun3(char * p1,char * p2); 看看上面三个表达式分别是什么意思? C)这很容易,fun3是函数名,p1,p2是参数,其类型为char *型,函数的返回值为char *类型. B) 也很简单,与C)表达式相比,唯一不同的就是函数的

  • 详解C语言结构体中的函数指针

    结构体是由一系列具有相同类型或不同类型的数据构成的数据集合.所以,标准C中的结构体是不允许包含成员函数的,当然C++中的结构体对此进行了扩展.那么,我们在C语言的结构体中,只能通过定义函数指针的方式,用函数指针指向相应函数,以此达到调用函数的目的. 函数指针 函数类型 (*指针变量名)(形参列表):第一个括号一定不能少. "函数类型"说明函数的返回类型,由于"()"的优先级高于"*",所以指针变量名外的括号必不可少.  注意指针函数与函数指针表示

  • C++中函数指针详解及代码分享

    函数指针 函数存放在内存的代码区域内,它们同样有地址.如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,如同数组的名字就是数组的起始地址. 1.函数指针的定义方式:data_types (*func_pointer)( data_types arg1, data_types arg2, ...,data_types argn); c语言函数指针的定义形式:返回类型 (*函数指针名称)(参数类型,参数类型,参数类型,-); c++函数指针的定义形式:返回类型 (类名

  • 深入学习C语言中的函数指针和左右法则

    通常的函数调用     一个通常的函数调用的例子: //自行包含头文件 void MyFun(int x); //此处的申明也可写成:void MyFun( int ); int main(int argc, char* argv[]) { MyFun(10); //这里是调用MyFun(10);函数 return 0; } void MyFun(int x) //这里定义一个MyFun函数 { printf("%d\n",x); } 这个MyFun函数是一个无返回值的函数,它并不完成

  • C语言中的函数指针学习笔记

    一.定义函数指针 return_type (*func_pointer)(parameter_list) 普通指针变量的定义 int * p; char * pointer; 类型的限定都在变量前面: 函数指针类型的限定是前后都有,前面是返回类型,后面是输入参数. 利用typedef 可以简化上面的表达方式. typedef return_type (*FunctionPointer) (parameter_list); FunctionPointer func_pointer; 这样是不是容易

  • 详解C语言中的函数、数组与指针

    1.函数:当程序很小的时候,我们可以使用一个main函数就能搞定,但当程序变大的时候,就超出了人的大脑承受范围,逻辑不清了,这时候就需要把一个大程序分成许多小的模块来组织,于是就出现了函数概念:   函数是C语言代码的基本组成部分,它是一个小的模块,整个程序由很多个功能独立的模块(函数)组成.这就是程序设计的基本分化方法: (1) 写一个函数的关键: 函数定义:函数的定义是这个函数的实现,函数定义中包含了函数体,函数体中的代码段决定了这个函数的功能: 函数声明:函数声明也称函数原型声明,函数的原

  • c++语言中虚函数实现多态的原理详解

    前言 自上一个帖子之间跳过了一篇总结性的帖子,之后再发,今天主要研究了c++语言当中虚函数对多态的实现,感叹于c++设计者的精妙绝伦 c++中虚函数表的作用主要是实现了多态的机制.首先先解释一下多态的概念,多态是c++的特点之一,关于多态,简而言之就是 用父类的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,这种方法呢,可以让父类的指针具有多种形态,也就是说不需要改动很多的代码就可以让父类这一种指针,干一些很多子类指针的事情,这里是从虚函数的实现机制层面进行研究 在写这篇帖子之前

  • c语言中回调函数的使用以及实际作用详析

    目录 前言 一.通过这节课程你能掌握以下知识: 二.程序架构的核心理念和需求 三.回调函数的作用 1.输出型 2.输入型 四.掌握回调函数的程序编写 总结 前言 今天给大家讲一下芯片/模块厂家写SDK必须会使用的一种技术:回调函数. 回调函数这个知识点其实并不是很难,难是难在网上很多讲解回调函数的都说的太学术化了化了,一点也不亲民. 很多人即使知道怎么写回调函数也根本就搞不懂它们在实际产品中也有什么用,什么时候用. 所以这节课呢我们会以程序架构的需求为出发点,讲解回调函数是怎么满足它这个需求的.

  • 深入学习C语言中常见的八大排序

    目录 冒泡排序 1.算法描述 2.动图展示 3.图解展示 4.代码实现 5.冒泡排序的优化 6.复杂度分析 插入排序 1.算法描述 2.动图展示 3.图解展示 4.代码实现 5.复杂度分析 希尔排序 1.算法描述 2.动图展示 3.图解展示 4.代码实现 5.复杂度分析 堆排序 1.算法描述 2.动图展示 3.图解展示 4.堆的一些基本性质 5.堆的构造 6.代码实现 7.复杂度分析 选择排序 1.算法描述 2.动图展示 3.图解展示 4.代码实现 5.复杂度 快速排序 1.算法简介 2.动图展

  • C语言中回调函数和qsort函数的用法详解

    目录 回调函数 指向函数指针数组的指针 qsort(qulick sort)-库函数 回调函数 通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数. 回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应. 举例: #include<stdio.h> void menu() { printf("*************************

  • Go语言中普通函数与方法的区别分析

    本文实例分析了Go语言中普通函数与方法的区别.分享给大家供大家参考.具体分析如下: 1.对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然. 2.对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以. 以下为简单示例: 复制代码 代码如下: package structTest    //普通函数与方法的区别(在接收者分别为值类型和指针类型的时候)  //Date:2014-4-3 10:00:07    import ( 

随机推荐