本文阐述了静态联编和动态联编的概念和区别,通过具体实例分析了实现动态联编的条件,指出了虚函数是实现动态联编的基础。
在C++中,联编是指一个计算机程序的不同部分彼此关联的过程。按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编。
1.静态联编
静态联编是指联编工作在编译阶段完成的,这种联编过程是在程序运行之前完成的,又称为早期联编。要实现静态联编,在编译阶段就必须确定程序中的操作调用(如函数调用)与执行该操作代码间的关系,确定这种关系称为束定,在编译时的束定称为静态束定。静态联编对函数的选择是基于指向对象的指针或者引用的类型。其优点是效率高,但灵活性差。
例1 :静态联编
#include"iostream"
using namespace std;
class A
{
public:
voidf(){cout<<"A"<<"";}
};
classB:publicA
{
public:
voidf(){cout<<"B"<<endl;}
};
Void main()
{
A *pa=NULL;
A a;
B b;
pa=&a;
pa->f();
pa=&b;
pa->f();
}
该程序的运行结果为:A A
从例1程序的运行结果可以看出,通过对象指针进行的普通成员函数的调用,仅仅与指针的类型有关,而与此刻指针正指向什么对象无关。要想实现当指针指向不同对象时执行不同的操作,就必须将基类中相应的成员函数定义为虚函数,进行动态联编。
2 动态联编
动态联编是指联编在程序运行时动态地进行,根据当时的情况来确定调用哪个同名函数,实际上是在运行时虚函数的实现。这种联编又称为晚期联编,或动态束定。动态联编对成员函数的选择是基于对象的类型,针对不同的对象类型将做出不同的编译结果。C++中一般情况下的联编是静态联编,但是当涉及到多态性和虚函数时应该使用动态联编。动态联编的优点是灵活性强,但效率低。
动态联编规定,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式为:指向基类的指针变量名->虚函数名(实参表)或基类对象的引用名.虚函数名(实参表)
实现动态联编需要同时满足以下三个条件:
① 必须把动态联编的行为定义为类的虚函数。
② 类之间应满足子类型关系,通常表现为一个类从另一个类公有派生而来。
③ 必须先使用基类指针指向子类型的对象,然后直接或者间接使用基类指针调用虚函数
例 2 动态联编
#include"iostream.h"
using namespace std;
classA
{
public:
Virtual voidf()//虚函数
{cout<<"A"<<"";}
};
classB:publicA
{
public:
Virtual voidf()//虚函数
{cout<<"B"<<endl;}
};
voidmain()
{
A*pa=NULL;
A a;
B b;
pa=&a;
pa->f();
pa=&b;
pa->f();
}
该程序的运行结果为:A B
从例2程序的运行结果可以看出,将基类A中的函数f定义为虚函数后,当指针指向不同对象时执行了不同的操作,实现了动态联编。
动态联编要求派生类中的虚函数与基类中对应的虚函数具有相同的名称、相同的参数个数和相同的对应参数类型、返回值或者相同,或者都返回指针或引用,并且派生类虚函数所返回的指针或引用的基类型是基类中虚函数所返回的指针或引用的基类型的子类型。如果不满足这些条件,派生类中的虚函数将丢失其虚特性,在调用时进行静态联编。例 3 通过指向基类的指针来调用虚函数
#include <iostream>
using namespace std;
class base
{
public:
virtual void fun1(){
cout<<"base fun1"<<endl;
}
virtual void fun2(){
cout<<"base fun2"<<endl;
}
void fun3(){
cout<<"base fun3"<<endl;
}
void fun4(){
cout<<"base fun4"<<endl;
}
};
class derived:public base
{
public:
virtual void fun1(){
cout<<"derived fun1"<<endl;
}
virtual void fun2(int x){
cout<<"derived fun2"<<endl;
}
virtual void fun3(){
cout<<"derived fun3"<<endl;
}
void fun4(){
cout<<"derived fun4"<<endl;
}
};
int main()
{
base *pb;
derived d;
pb=&d; //通过指向基类的指针来调用虚函数
pb->fun1();
pb->fun2();
pb->fun3();
pb->fun4();
return 0;
}
输出结果为:
Derived fun1
base fun2
base fun3
base fun4
分析:本例中函数fun1在基类base和派生类derived中均使用了关键字virtual定义为虚函数,并且这两个虚函数具有相同的参数个数、参数类型和返回值类型。因此,当指针pb访问fun1函数时,采用的是动态联编。函数fun2在基类base和派生类derived中定义为虚函数,但这两个虚函数具有不同的参数个数,函数fun2丢失了其虚特性,在调用时进行静态联编。函数fun3在基类base中说明为一般函数,在派生类derived中定义为虚函数。在这种情况下,应该以基类中说明的成员函数的特性为标准,即函数fun3是一般成员函数,在调用时采用静态联编。函数fun4在基类base和派生类derived中均说明为一般函数,因此基类指针pb只能访问base中的成员。
例 4:通过基类对象的引用来调用虚函数
#include <iostream>
using namespace std;
class CPoint
{
public:
CPoint(double i,double j){
x=i;
y=j;
}
virtual double Area(){
return 0;
}
private:
double x,y;
};
class CRectangle:public CPoint
{
public:
CRectangle(double i, double j, double k, double l);
double Area(){
return w*h;
}
private:
double w,h;
};
CRectangle::CRectangle(double i, double j, double k, double l):CPoint(i,j)
{
w=k;
h=l;
}
void fun(CPoint &s)
{
cout<<s.Area()<<endl;
}//通过基类对象的引用来调用虚函数
int main()
{
CRectangle rec(3, 5.2, 15, 25);
fun(rec);
return 0;
}
该程序的运行结果为:375
例4中的成员函数Area在基类CPoint中使用了关键字virtual定义为虚函数,在派生类CRectangle中定义为一般函数,但是进行了动态联编(以基类为准),结果为15*25即375。这是因为一个虚函数无论被公有继承多少次,它仍然保持其虚特性。在派生类中重新定义虚函数时,关键字virtual可以写也可不写,但为了保持良好的编程风格,避免引起混乱时,应写上该关键字。