• C语言的本质(12)——指针与函数



     往往,我们一提到指针函数和函数指针的时候,就有很多人弄不懂。下面详细为大家介绍C语言中指针函数和函数指针。

    1、指针函数

     当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中。

     格式: 

    类型说明符 * 函数名(参数)

     

    由于返回的是一个地址,所以类型说明符一般都是int。

     在C语言中,函数也是一种类型,可以定义指向函数的指针。我们知道,指针变量的内存单元存放一个地址值,而函数指针存放的就是函数的入口地址(位于.text段)。下面看一个简单的例子:

    #include <stdio.h>
     
    void say_hello(const char *str)
    {
        printf("Hello %s
    ", str);
    }
     
    int main(void)
    {
        void (*f)(const char *) = say_hello;
        f("Guys");
        return 0;
    }

    分析一下变量f的类型声明void (*f)(const char *),f首先跟*号结合在一起,因此是一个指针。(*f)外面是一个函数原型的格式,参数是const char *,返回值是void,所以f是指向这种函数的指针。而say_hello的参数是const char *,返回值是void,正好是这种函数,因此f可以指向say_hello。注意,say_hello是一种函数类型,而函数类型和数组类型类似,做右值使用时自动转换成函数指针类型,所以可以直接赋给f,当然也可以写成void (*f)(const char *) = &say_hello;,把函数say_hello先取地址再赋给f,就不需要自动类型转换了。

    可以直接通过函数指针调用函数,如上面的f("Guys"),也可以先用*f取出它所指的函数类型,再调用函数,即(*f)("Guys")。可以这么理解:函数调用运算符()要求操作数是函数指针,所以f("Guys")是最直接的写法,而say_hello("Guys")或(*f)("Guys")则是把函数类型自动转换成函数指针然后做函数调用。

    2、函数指针

     指向函数的指针包含了函数的地址,可以通过它来调用函数。声明格式如下: 

    类型说明符 (*函数名)(参数)

    其实这里不能称为函数名,应该叫做指针的变量名。这个特殊的指针指向一个返回整型值的函数。指针的声明笔削和它指向函数的声明保持一致。

     指针名和指针运算符外面的括号改变了默认的运算符优先级。如果没有圆括号,就变成了一个返回整型指针的函数的原型声明。

     例如:

    void (*fptr)();


    把函数的地址赋值给函数指针,可以采用下面两种形式:

    fptr=&Function; 
    fptr=Function;


    取地址运算符&不是必需的,因为单单一个函数标识符就标号表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。

    可以采用如下两种方式来通过指针调用函数:

    x=(*fptr)(); 
    x=fptr();


    第二种格式看上去和函数调用无异。但是有些程序员倾向于使用第一种格式,因为它明确指出是通过指针而非函数名来调用函数的。下面举一个例子:

    void (*funcp)(); 
    void FileFunc(),EditFunc(); 
    int main(void) 
    { 
    	funcp=FileFunc; 
    	(*funcp)(); 
    	funcp=EditFunc; 
    	(*funcp)(); 
    } 
    void FileFunc() 
    {  
    	printf("FileFunc\n"); 
    } 
    void EditFunc() 
    { 
    	printf("EditFunc\n"); 
    }

    程序输出为:

     FileFunc 

    EditFunc

      

    下面再举几个例子区分函数类型和函数指针类型。首先定义函数类型F:

    typedef int F(void);


    这种类型的函数不带参数,返回值是int。那么可以这样声明f和g:

     F f, g;相当于声明:

    int f(void);
    int g(void);


    下面这个函数声明是错误的:

    F h(void);


    因为函数可以返回void类型、标量类型、结构体、联合体,但不能返回函数类型,也不能返回数组类型。而下面这个函数声明是正确的:

    F *e(void);


    函数e返回一个F *类型的函数指针。如果给e多套几层括号仍然表示同样的意思:

    F *((e))(void);


    但如果把*号也套在括号里就不一样了:

    int (*fp)(void);

    这样声明了一个函数指针,而不是声明一个函数。fp也可以这样声明:

    F *fp;



    3、指针类型的参数和返回值

     首先看下面的程序:

    #include <stdio.h>
     
    int *swap(int *px, int *py)
    {
             inttemp;
             temp= *px;
             *px= *py;
             *py= temp;
             returnpx;
    }
     
    int main(void)
    {
             inti = 10, j = 20;
             int*p = swap(&i, &j);
             printf("nowi=%d j=%d *p=%d
    ", i, j, *p);
             return0;
    }
     

    我们知道,调用函数的传参过程相当于用实参定义并初始化形参,swap(&i, &j)这个调用相当于:

    int *px = &i;
    int *py = &j;

    所以px和py分别指向main函数的局部变量i和j,在swap函数中读写*px和*py其实是读写main函数的i和j。尽管在swap函数的作用域中访问不到i和j这两个变量名,却可以通过地址访问它们,最终swap函数将i和j的值做了交换。

    上面的例子还演示了函数返回值是指针的情况,return px;语句相当于定义了一个临时变量并用px初始化: 

    int *tmp = px;

    然后临时变量tmp的值成为表达式swap(&i,&j)的值,然后在main函数中又把这个值赋给了p,相当于: 

    int *p = tmp;

    最后的结果是swap函数的px指向哪就让main函数的p指向哪。我们知道px指向i,所以p也指向i。

  • 相关阅读:
    Cannot find or open the PDB file from http://bbs.csdn.net/topics/350149584 呼吁大家忽视结贴率为0的求助。
    使用GDI+位图数据扫描线处理图像的小技巧 from http://blog.csdn.net/maozefa/article/details/4533770
    解决Visual C++ 编译器中混合 .c 文件时收到 C1853 预编译头错误的方法 from http://live.aulddays.com/tech/08/c1853precompile/
    使用GDI+保存用户的绘图数据.
    char、varchar、text和nchar、nvarchar、ntext的区别
    ASP.NET Cache
    ECMAScript 引用类型
    Meta标签——网站中必不可少的一部分
    ECMAScript 原始类型
    web性能测试分析工具篇
  • 原文地址:https://www.cnblogs.com/new0801/p/6177114.html
Copyright © 2020-2023  润新知