函数指针和指针函数 指针的指针

本文深入探讨了函数指针与指针函数的区别及其应用场景,包括初始化、调用方式、混合使用以及复杂声明等内容,并通过具体示例加深理解。

上学的时候一直对函数指针和指针函数的概念很模糊,在看了下面三篇文章后,终于有了进一步的了解。

原址:http://blog.youkuaiyun.com/porscheyin/article/details/3461632

函数指针和指针函数

上面的文章说得好,函数指针=指向函数的指针,指针函数=返回指针的函数,这样就很容易方便记忆了。还有一种方法是看函数前面*号有没有括号,如果有的话就是函数指针,否则为指针函数。

函数指针的初始化

对函数指针初始化时可以采用相同类型函数的函数名或函数指针(当然还有零指针常量)。假如有函数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就不会再发生转换了。因此直接引用函数名等效于在函数名上应用 & 运算符,两种方法都会得到指向该函数的指针。

通过函数指针调用函数

通过函数指针调用函数可以有两种方法,直接使用函数指针或在函数指针前使用解引用运算符,如下所示:

f = test;

ptf = test;

f ( );

(*f) ( );  //指针两侧的括号非常重要,表示先对f解引用,然后再调用相应的函数

ptf ( );

(*ptf) ( ); //括号同样不能少

以上语句都能达到调用test函数的作用。ANSI C标准将f ( )认为是(*f)( )的简写形式,并且推荐使用f ( )形式,因为它更符合函数调用的逻辑。要注意的是:如果指向函数的指针没有初始化,或者具有0(零指针常量),那么该指针不能在函数调用中使用。只有当指针已经初始化,或被赋值后指向某个函数才能安全地用来调用函数。

探究函数名

现在有如下程序:

#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;
}


返回指针的函数(指针函数)

类比指针数组(还记得吗),理解指针函数将会更加轻松。所谓指针函数,就是返回指针的函数,函数可以不返回任何值,也可以返回整型值,实型值,字符型值,当然也可以返回指针值。一个指针函数的声明: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现在也是一种类型。

函数指针和指针函数的混合使用

函数指针不仅可以作为返回值类型,还可以作为函数的形式参数,如果一个函数的形参和返回值都是函数指针,这个声明看起来会更加复杂,例如:

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编程中经常用到的一个函数,所以在此单独拿出来讲解一下。

函数指针数组

还有一种较为常用的关于函数指针的用法——函数指针数组。假设现在有一个文件处理程序,通过一个菜单按钮来选择相应的操作(打开文件,读文件,写文件,关闭文件)。这些操作都实现为函数且类型相同,分别为:

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对象相关联,以达到相应的目的。

关于指针的复杂声明

第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*型的元素。


原址: http://www.cnblogs.com/dzry/archive/2011/05/12/2044835.html

1. 指向整型指针

先看如下示例

 #include <iostream>
using namespace std;

int main()
{
    int a = 5;
    int * p = &a;
    cout << "a  = " <<  a << endl
         << "&a = " << &a << endl 
         << "*p = " << *p << endl 
         << "p  = " << p << endl 
         << "&p = " << &p << endl;

    return 0;
}

我们先看下内存分配图:

由上图可以清楚的知道,输出整形变量a的值是5,指针变量p的值是001BFD18,而*号的作用是取值,*p即取地址001BFD18中存放的值,即5。

2. 指向字符型指针

先看如下示例:

复制代码
 1  #include  < iostream >
 2  using   namespace  std;
 3 
 4  int  main()
 5  {
 6       char  a[]  =   " hello " ;
 7       char   * =  a;
 8 
 9      cout  <<   " p =  "   <<  p  <<  endl
10            << " p =  "   <<  ( void   * ) p  <<  endl
11            <<   " *p =  "   <<   * <<  endl;
12          
13          
14       for ( int  i  =   0  ; i  <   5 ; i ++ )
15      {
16          cout  <<   " &a[ "   <<  i  <<   " ] =  " <<  ( void   * ) & a[i]  <<  endl;
17      }
18       return   0 ;
19  }
复制代码

运行结果图如下:

为什么整型指针p输出的是地址,而字符型指针输出的是字符串呢,字符型指针里存放的不是地址吗?

我们先看下内存分配图:

由上图可以看出,其实p中存放的是地址,只是当cout时,如果指针是字符型指针,那么会输出p中地址指向的内存中的内容(这里是h)直到遇到'\0'才结束。所以直接输出p时会输出hello,而将p强制转换为void *时输出的是地址。

3. 指向整型指针的指针

先看如下示例:

复制代码
 1  #include  < iostream >
 2  using   namespace  std;
 3 
 4  int  main()
 5  {
 6       int  a[ 5 =  { 1 2 3 4 5 };
 7       int   * =  a;
 8       int   ** point  =   & p;
 9 
10      cout  <<   " a =  "   <<  a  <<  endl
11           <<   " p =  "   <<  p  <<  endl
12           <<   " &p =  "   <<   & <<  endl
13           <<   " point =  "   <<  point  <<  endl
14           <<   " &point =  "   <<   & point  <<  endl;
15 
16       for  ( int  i  =   0 ; i  <   5 ; i ++ )
17      {
18          cout  <<   " &a[ "   <<  i  <<   " ] =  "   <<   & a[i]  <<  endl;
19      }
20       return   0 ;
21  }
复制代码

运行结果图如下:

我们先看下内存分配图:

从上图可以看出point指针中存放的是p指针的地址,而p指针中存放的是a[0]的地址。所以*point和p是一样的,前者是取point指针中存放的地址(0025F754)中的值,即取地址0025F754中存放的值(0025F760),而后者就是0025F760,所以两者是等价的。**point和a[0]是等价的,前者可以写成*p,*p是取p中存放的地址(0025F760)中的值,即地址0025F760中存放的值1。由上可以得出*point等于p, **point 等于 a[0]。通过上图可以清晰的对付诸如*point++等问题。

4. 指向整型指针的指针

先看如下示例:

 

1 #include <iostream>
using namespace std;

int main()
{
    char *a[] = {"Wel", "To", "China"};
    char **p = a;
    for(int i = 0; i < 3; i++)
    {
        for (int j = 0; j < strlen(a[i]) + 1; j++)
        {
            cout << a[i][j] << "\t" << (void *)&a[i][j] << endl;
        }
        cout << endl;
    }
    
    for (int i = 0; i < 3; i++)
    {
        cout << "a[" << i << "] = " << (void *) a[i] << endl
             << "&a[" << i << "] = " << &a[i] << endl;
    } 


    cout << "p  = " << p << endl
         << "&p = " << &p << endl;
    return 0;
}

运行结果图如下:

我们先看下内存分配图:

由上图可以看出a[0]中存放着'W'的地址,a[1]中存放着'T'的地址,a[2]中存放着'C'的地址,只是这些地址都是指向字符型的,所以直接cout的会输出字符串,而指针p中存放着a[0]的地址,所以*p等于a[0],都是获得'W'的地址,即00A778CCC,而**p和a[0][0]等价都获得了地址00A778CCC中存放的值W。由上图我们可以看到字符地址相隔1个字节,而指针地址相隔4个字节,这样就便于++运算,获得下一个地址了,列如++p后,p就指向a[1],p中存放的是a[1]的地址。

 最后一个原址为:http://blog.pfan.cn/whyhappy/6030.html

其中摘取了部分代码:

函数指针作为某个函数的参数
既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。所以,你还应知道函数指针是如何作为某个函数的参数来传递使用的。
给你一个实例:
要求:我要设计一个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);
}
好了,最后感谢上面的那些前辈们,使得对指针得到了更深一步的认识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值