函数指针

函数指针

函数指针就是函数的指针。它是一个指针,指向一个函数。
(即函数在内存中的起始位置地址)
实际上,所有的函数名在表达式和初始化中,总是隐式地退化为指针。
(函数名即函数的首地址,即函数的指针)

例:

int r , (*fp)( ) , func( ) ;
fp= func ;     //函数名即函数的首地址,即函数的指针
r= (*fp)( ) ;  //等价于 r=fp( ) ;

无论fp是函数名还是函数指针,都能正确工作。因为函数总是通过指针进行调用的!

例:

int f(int) ; //函数声明
int (*fp)(int) = &f ;//此取地址符是可选的。编译器就把函数名当做函数的入口地址。
int ans ;
//以下三种方式可调用函数
ans= f(25) ;
ans= (*fp)(25) ;
ans= fp(25) ;

函数名就是一个函数指针常量,函数调用操作符(即一对括号)相当于解引用。
函数的执行过程:

函数名首先被转换为一个函数指针常量,该指针指定函数在内存中的位置。然后函数调用操作符调用该函数,执行开始于这个地址的代码。

强制类型转换

void fun() { printf("Call fun "); }  
int main(void)  
{  
    void(*p)( ) ;  
    *(int*)&p = (int)fun ;  
    (*p)() ;  
    return 0 ;  
}

指针的强制类型转换只不过是改变了编译器对二进制位的解释方法罢了。

(int )&p = (int)fun ;中的fun是一个函数地址,被强制转换为int数字。
左边的(int *)&p是把函数指针p转换为int型指针。

(int )&p = (int)fun ;表示将函数的入口地址赋值给指针变量p。

(*p)( ) ;表示对函数的调用。

函数指针的用途

  1. 转移表(转移表就是一个函数指针数组)
    即可用来实现“菜单驱动系统”。系统提示用户从菜单中选择一个选项,每个选项由不同的函数提供服务。
    【若每个选项包含许多操作,用switch操作,会使程序变得很长,可读性差。这时可用转移表的方式】
例:
void(*f[3])(int) = {function1, function2, function3} ; //定义一个转移表
(*f[choice])( ) ; //根据用户的选择来调用相应的函数
  1. 回调函数
    (用函数指针做形参,用户根据自己的环境写个简单的函数模块,传给回调函数,这样回调函数就能在不同的环境下运行了,提高了模块的复用性)

【回调函数实现与环境无关的核心操作,而把与环境有关的简单操作留给用户完成,在实际运行时回调函数通过函数指针调用用户的函数,这样其就能适应多种用户需求】

例:C库函数中的快速排序函数
void qsort(void *base, int nelem, size_t width, int (*fcmp)(void*, void*) );//fcmp为函数指针。

这样,由用户实现fcmp的比较功能(用户可根据需要,写整型值的比较、浮点值的比较,字符串的比较等),这样qsort函数就能适应各种不同的类型值的排序。

使用函数指针的好处在于:

可以将实现同一功能的多个模块统一起来标识,这样一来更容易后期维护,系统结构更加清晰。或者归纳为:便于分层设计、利于系统抽象、降低耦合度以及使接口与实现分开。


指针的复杂声明

函数指针数组的指针
例:char *(*(pf)[3])(char )

这是一个数组指针,这个指针指向一个数组,这个数组里存储的都是指向函数的指针。这类函数是一种返回值为字符指针,参数为字符指针的函数。

关于变量的复杂声明:
  • 从外到内,层层剥开,先找核心,再向右看。
    找到核心变量后,从右向左读。
  • *读作”指向…的指针”
  • [] 读作”…的数组”
  • () 读作”返回…的函数”
简单的例子:
int *f() ;             // f: 返回指向int型的指针
步骤:
1)找标识符f:读作”f是…”
2)向右看,发现”()”读作”f是返回…的函数”
3)向右看没有什么,向左看,发现*,读作”f是返回指向…的指针的函数”
4)继续向左看,发现int,读作”f是返回指向int型的指针的函数”
int (*pf)() ;          // pf是一个指针——指向返回值为int型的函数
1)标识符pf,读作“pf是…”
2)向右看,发现),向左看,发现*,读作 “pf是指向…的指针”
3)向右看,发现”()”,读作“pf是指向返回…的函数的指针”
4)向右看,没有,向左看发现int,读作”pf是指向返回int型的函数的指针
指针数组与数组指针:
  • 指针数组:每个元素都是指针类型的数组 int *p[6] (先数组,再指针)
  • 数组指针:int (*p)[6] (先指针,再数组) pà指向数组的指针
    数组名为指针,即数组首元素地址为指针,即指向数组的指针
    int *[6] à数据类型 pà 数据变量
复杂指针的举例:
1. void (*b[10])(void (*)());

首先找到核心:b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是“void(*)()”( 这个参数又是一个指针,指向一个函数,函数参数为空,返回值是“void”) 返回值是“void”。完毕!

使用typedef简化声明:

建立一个类型别名的方法很简单,在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头

举例:
  1  void (*b[10]) (void (*)());
    typedef void (*pfv)(); //先把上式的后半部分用typedef换掉
    typedef void (*pf_taking_pfv)(pfv); //再把前半部分用typedef换掉
    pf_taking_pfv b[10]; //整个用typedef换掉
    跟void (*b[10]) (void (*)());的效果一样!
  2  doube(*)() (*pa)[9];
    定义同下:
    typedef double(*PF)(); //先替换前半部分
    typedef PF (*PA)[9];  //再替换后半部分
    PA pa;
    跟doube(*)() (*pa)[9];的效果一样!
2. int *(*(*(*abc) ( ) ) [6]) ( ) ;
    解析:
    abc是一个函数指针,这类函数接收0个参数,返回一个指针,这个指针指向一个具有6个元素的数组,数组里的每个元素是函数指针,这类函数接收0个参数,返回值为int* 类型。

1. (*abc) ( ) abc是一个函数指针,这类函数接收0个参数,返回一个指针
2. (*(*abc) ( ) ) [6] 这个返回的指针指向一个具有6个元素的数组
3. int * (((abc) ( ) ) [6]) ( ) ; 数组里的每个元素是函数指针,这类函数接收0个参数,返回值为int 类型。

    abc的定义同下: 
    typedef int* (*type1)();
    typedef type1 (*type2)[6];
    typedef type2 (*type3)(); 
    type3 abc;
    【从内到外解读声明,从外到内typedef】
3. (*(void(*)( ))0 )( )
    从内层到外层分析:
    1  void(*)()这是一个函数指针。 这个函数没有返回值也没有参数
    2  void(*)()0这是将整型0强制转换为函数指针类型。(即:0号地址处开始存储着一段函数)
    3  (*(void(*)( ))0 ) 取出0号地址处的函数
    4  (*(void(*)( ))0 )( ) 调用0号地址处的函数。
    定义同下:
    typedef  void(*fun)();
    (*(fun)0)();
4. void (*signal(int signo, void(*func)(int)))(int);
    定义同下:

typedef void(*type)(int);
type signal(int,type);


指针的反思

  1. 我们为什么需要指针?
      因为我们要访问一个对象,我们要改变一个对象。要访问一个对象,必须先知道它在哪,也就是它在内存中的地址。地址就是指针值。
    所以我们有函数指针:某块函数代码的起始位置(地址)
    指针的指针:因为我要访问(或改变)某个变量,只是这个变量是指针罢了
  2. 为什么要有指针类型?
      因为我们访问的对象一般占据多个字节,而代表它们的地址值只是其中最低字节的地址,我们要完整的访问对象,必须知道它们总共占据了多少字节。而指针类型即向我们提供这样的信息。
    注意:一个指针变量向我们提供了三种信息:
    ①一个首字节的地址值(起始位置)
    ②这个指针的作用范围(步长)
    ③对这个范围中的数位的解释规则(解码规则)
    【编译器就像一个以步数测量距离的盲人。故你要告诉它从哪开始走,走多少步,并且告诉他如何理解这里面的信息】
  3. 强制类型转换的真相?
      学过汇编的人都知道,什么指针,什么char,int,double,什么数组指针,函数指针,指针的指针,在内存中都是一串二进制数罢了。只是我们赋予了这些二进制数不同的含义,给它们设定一些不同的解释规则,让它们代表不同的事物。
      (比如1000 0000 0000 0001 是内存中某4个字节中的内容,如果我们认为它是int型,则按int型的规则解释它为-2^31+ 1;如果我们认为它是unsigned int ,则被解释为2^31+ 1;当然我们也可把它解释为一个地址值,数组的地址,函数的地址,指针的地址等)
      如果我们使用汇编编程,我们必须根据上下文需要,用大脑记住这个值当前的代表含义,当程序中有很多这样的值时,我们必须分别记清它们当前代表的含义。这样极易导致误用,所以编译器出现了,让它来帮我们记住这些值当前表示的含义。
       当我们想让某个值换一种解释的方案时,就用强制类型转换的方式来告诉编译器,编译器则修改解释它的规则,而内存中的二进制数位是不变的(涉及浮点型的强制转换除外,它们是舍掉一些位,保留一些位)。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值