3.4 函数地址和函数指针

函数、应用程序是编译器处理的对象。每一个函数的语句序列经过编译之后,生成的都是二进制代码。这些代码要调入内存才能够运行。每一条指令、每一个函数模块都有一个首地址。函数块的首地址称为函数的入口地址,或函数指针。

一个已经定义的函数,它的名字就是函数的入口地址。我们已经熟悉了用名字调用函数的方式,本节将更深入地讨论有关调用函数的问题。

3.4.1 函数的地址

为了弄清楚函数地址的表示,下面考察一个简单的程序。

【例3-23】用不同方式调用函数。

            #include<iostream>
            using namespace std;
            void simple()
            {  cout<<"It is a simple program.\n";  }
            int main()
            {  cout<<"Call function …\n";
              simple();                   //名方式调用
              (&simple)();                //地址方式调用
              (*&simple)();               //间址调用
              cout << "Address of function :\n";
              cout<<simple<<endl;         //函数名是地址
              cout<<&simple<<endl;        //取函数地址
              cout<<*&simple<<endl;       //函数地址所指对象
            }

程序运行结果:

            Call function …
            It is a simple program.
            It is a simple program.
            It is a simple program.
            Address of function :
            0043B248
            0043B248
            0043B248

下面分析上述例程。

① 函数名、函数地址都是函数的入口地址。对于

            simple
            &simple
            *&simple

它们在形式上看起来很不相同,但实际上,在C++中,它们都是函数simple的代码模块在内存的入口地址值,称为函数地址。

但是,对于一个数据对象:

            double x = 1.414;

如果有输出:cout << &x << x;

&x是地址表达式,将显示变量x在内存的地址;名访问x表示对象的值1.414。

程序设计语言对数据对象的地址和名加以区别。采用名访问数据对象时,编译器能够根据数据的类型解释存储单元的值。“函数”这种代码对象和普通数据对象性质不同。对函数的访问(调用)一旦找到入口地址,将按照指令的规则执行。编译器调用函数不需要区分地址和名。对于一个已经定义的函数,函数名、函数地址(指针)、函数指针所指对象都是同一样东西,表示函数的入口地址。

② 函数调用指令要做的事情是:找到函数入口地址,传递参数。因此,调用函数的语句(或表达式)由两部分组成:

            函数地址 (实际参数表)

其中,“函数地址”实质上是一个地址表达式,可以是函数名,或者能够表示函数入口地址的式子。以下调用都可以达到相同的效果:

            simple()
            (& simple)()
            (* & simple)()

注意,在后两种调用方式中,第一个括号不能省略,因为地址表达式计算要优先于参数结合。

3.4.2 函数指针

函数定义后,函数名表示函数代码在内存的直接地址。可以用一个指针变量获取函数的地址,通过指针变量的间址方式调用函数。指向函数的指针变量简称为函数指针。

1.函数的类型

要定义函数指针,首先要明确什么是函数的类型。

函数的类型(注意,不是函数的返回值类型)是指函数的接口,包括函数的参数定义和返回类型。例如,以下是类型相同的函数:

            double max(double, double);
            double min(double, double);
            double average(double, double);

这些函数都有两个double类型参数,返回值为double类型,它们的类型(接口)为:

            double (double, double)

一般地,表示函数类型的形式为:

            类型(形式参数表)

C++中,可以用关键字typedef定义函数类型名。函数类型名定义的一般形式为:

          typedef 类型  函数类型名(形式参数表);

其中,“函数类型名”是用户定义标识符。例如:

            typedef double functionType (double, double);

函数类型名 functionType 定义了一类接口相同的函数的抽象,即抽象了两个 double 参数,返回double类型的相同类型函数。此时:

            functionType max, min, average;

等价于前面的3个函数原型声明。

2.函数指针

要定义指向某一类函数的指针变量,可以用以下两种说明语句:

类型(* 指针变量名)(形式参数表);

或 函数类型 * 指针变量名;

在第一种说明语句中,因为“()”的优先级比“*”高,所以“(*指针变量名)”的圆括号不能省略;否则,就会变成一个返回指针值的函数原型。例如:

            double * f (double, double);

是函数原型声明,f是函数名,具有两个double参数,返回double类型指针。

对于上述已经定义的functionType,如果要定义指向这一类函数的指针变量,可以用说明语句:

            double (*fp)(double, double);

或 functionType * fp;

有了函数类型定义,便可以同时定义多个类型相同的函数指针:

            functionType  *fp1,*fp2;

还可以用关键字typedef定义指针类型。函数指针类型定义的一般形式为:

            typedef 类型(* 指针类型)(形式参数表);

或 typedef 函数类型 * 指针类型;

其中,“指针类型”是用户定义的类型名。例如:

typedef double (*pType)(double,double);

或 typedef functionType * pType;

定义了一个函数指针类型pType。

又如, pType pf1, pf2;

定义了两个pType类型的指针变量pf1和pf2,分别指向不同的函数:

            pf1=max;             //pf1指向函数max
            pf2=min;             //pf2指向函数min

3.用函数指针调用函数

一个已经定义的函数指针,赋给函数地址后就可以调用函数。使用函数指针调用函数的一般形式为:

(* 指针变量名)(实际参数表)

或 指针变量名 (实际参数表)

例如,向fp赋予不同函数的地址,通过fp调用不同函数:

            fp=max;                //获取max函数地址
            x=fp(0.5,3.92);        //相当于 x=max(0.5,3.92);
            fp=min;                //获取min函数地址
            x=fp(0.5,3.92);        //相当于 x=min(0.5,3.92);
            fp=average;            //获取average函数地址
            x=fp(0.5,3.92);        //相当于 x=average(0.5,3.92);

从函数调用性质可知:(*fp) (0.5, 3.92)与fp(0.5, 3.92)是等价的调用方式。

但是(&fp) (0.5, 3.92)是错误的。fp是指针变量,它的地址不是函数的地址,它的值才是函数的地址。

【例3-24】用函数指针调用不同函数。

程序定义了 4 个接口相同的函数,分别是:计算圆周长的 circlePerimeter、计算圆面积的circleArea、计算球面积的bollArea和计算球体积的bollVolume函数,它们都有一个double参数,返回类型为double。随着代码执行,函数指针pf指向不同函数,实现不同的计算。

            #include<iostream>
            #include<cmath>
            using namespace std;
            const double PI = 3.14159;
            double circlePerimeter(double radius)
            {  return 2*PI*radius;  }
            double circleArea(double radius)
            {  return PI*radius*radius;  }
            double bollArea(double radius)
            {  return 4*PI*radius*radius;  }
            double bollVolume(double radius)
            {  return 4.0/3*PI*pow(radius,3);  }
            int main()
            {  double(*pf)(double);      //定义函数指针
              double r, cP, cA, bA, bV;
              cout << "enter the radius of a circle : ";
              cin >> r;
              pf=circlePerimeter;        //获取函数地址
              cP=pf(r);                  //等价于circlePerimeter(r)
              pf=circleArea;             //获取函数地址
              cA=pf(r);                  //等价于circleArea(r)
              cout << "the perimeter of circle is : "<< cP << endl;
              cout << "the area of circle is : "<< cA << endl;
              cout << "enter the radius of a boll : ";
              cin >> r;
              pf=bollArea;               //获取函数地址
              bA=pf(r);                  //等价于bollArea(r)
              pf=bollVolume;             //获取函数地址
              bV=pf(r);                  //等价于bollVolume(r)
              cout << "the area of boll is : "<< bA << endl;
              cout << "the volume of boll is : "<< bV << endl;
            }

程序运行结果:

            enter the radius of a circle:3.5
            the perimeter of circle is:21.9911
            the area of circle is:38.4845
            enter the radius of a boll:4.2
            the area of boll is:221.671
            the volume of boll is:310.339

当函数指针作为函数参数时,可以传递函数的地址,通过参数调用不同函数。

【例3-25】使用函数指针参数调用函数。本程序与例3-24的功能相同。

            #include<iostream>
            #include<cmath>
            using namespace std;
            typedef double funType(double);    //定义函数类型
            funType circlePerimeter;           //用函数类型名定义函数原型
            funType circleArea;
            funType bollArea;
            funType bollVolume;
            double callFun(funType*,double);   //第一个参数是函数指针参数
            int main()
            {  double r;
              cout << "enter the radius : ";
              cin >> r;
              //用函数地址作为实际参数调用函数callFun
              cout << "the perimeter of circle is : " << callFun(circlePerimeter, r)<< endl;
              cout << "the area of circle is : "<< callFun(circleArea, r) << endl;
              cout << "enter the radius of a boll : ";
              cin >> r;
              cout << "the area of boll is : "<< callFun(bollArea, r) << endl;
              cout << "the volume of boll is : "<< callFun(bollVolume, r) << endl;
            }
            const double PI = 3.14159;
            double circlePerimeter(double radius)
            {  return PI*radius*radius;  }
            double circleArea(double radius)
            {  return 2*PI*radius;  }
            double bollArea(double radius)
            {  return 4*PI*radius*radius;  }
            double bollVolume(double radius)
            {  return 4.0/3*PI*pow(radius,3);  }
            double callFun(funType * qf, double r)
            {  return qf(r);  }

程序中定义了函数类型funType,抽象接口为:

            double (double)

的一类函数。类型名funType可以用于说明函数原型:

            funType circlePerimeter;

等价于 double circlePerimeter(double);

还可用于callFun函数的参数说明:

            double callFun(funType * qf, double r)
            {  return qf(r);  }

qf是funType类型的指针。main函数调用callFun时,用函数名(即函数地址)作为实际参数,传递给形参指针qf,调用所指函数:

            callFun(circlePerimeter, r)
            callFun(circleArea, r)
            callFun(bollArea, r)
            callFun(bollVolume, r)

上面4次调用,实际参数为不同函数的地址,使得执行callFun时调用不同的函数。

函数指针不仅可以调用自定义函数,还可以调用库函数。例如,正弦函数和余弦函数原型为:

            double sin(double);
            double cos(double);

它们是接口一样的同类型函数。以下例子用函数参数调用这两个库函数。

【例3-26】使用函数名作为函数参数,调用库函数sin(x)和cos(x),计算指定范围内间隔为0.1的函数值之和。

            #include<iostream>
            #include<cmath>
            using namespace std;
            typedef double fType(double);             //定义函数类型
            double func(fType*f,double l,double u)    //f是函数指针参数
            {  double d,s=0.0;
              for(d = l; d <= u; d += 0.1)
                  s += f(d);
              return s;
            }
            int main()
            {  double sum;
              sum=func(sin,0.0,1.0);                  //库函数sin作为实参
              cout << "the sum of sin from 0.0 to 1.0 is: " << sum << endl;
              sum=func(cos,0.0,1.0);                  //库函数cos作为实参
              cout << "the sum of cos from 0.0 to 1.0 is: " << sum << endl;
            }

运行程序,显示结果为:

            the sum of sin from 0.0 to 1.0 is: 5.01388
            the sum of cos from 0.0 to 1.0 is: 9.17785