什么是虚函数?为什么要提出虚函数?本文就来为大家解惑。
1 虚函数
虚函数是C++继承中最丰富的一段知识,它是C++用于实现多态的机制。有继承才会有虚函数。我们在需要实现多态的函数前面加上一个virtual,就可以通过父类指针随意地调用各个子类的同名函数,用一个指针实现了不同的运行效果,达到多态的目的。这就是常说的虚函数的主要用途。我们看个例子。
1: #include <iostream>2: using namespace std;3:4: class CParent
5: {6: public:
7: //注意jump和run的区别
8: void jump(){cout<<"CParent::jump is called."<<endl;}9: virtual void run(){cout<<"CParent::run is called."<<endl;}10: };11:12: class CChildA : public CParent13: {14: public:
15: void jump(){cout<<"CChildA::jump is called."<<endl;}16: virtual void run(){cout<<"CChildA::run is called."<<endl;}17: };18:19: class CChildB : public CParent20: {21: public:
22: void jump(){cout<<"CChildB::jump is called."<<endl;}23: virtual void run(){cout<<"CChildB::run is called."<<endl;}24: };25:26: int main()
27: {28: CParent *p;29: CChildA *a=new CChildA();
30: CChildB *b=new CChildB();
31:32: cout<<"用CParent指针调用普通函数jump"<<endl;
33: p=a;34: p->jump();35: p=b;36: p->jump();37: cout<<endl;38:39: cout<<"用CParent指针调用普通函数jump"<<endl;
40: p=a;41: p->run();42: p=b;43: p->run();44: return 0;
45: }46:
程序的运行结果如下:
虚函数是成员函数,而且是非static的成员函数,通常使用关键字virtual加以说明。C++中,虚函数是动态联编的基础,提到虚函数,就不得不说静态联编和动态联编。
2 静态联编和动态联编
联编是一个程序把自身各个部件(源代码、资源、相关的库和模块等)彼此关联的过程。通常说的联编是静态联编,可以分为编译和链接两个阶段。编译就是把程序的源代码通过C++编译器转换为指定的中间代码(如汇编代码);链接把中间代码和资源等转换为目标二进制可执行代码。
静态联编是早期联编,是在程序运行起来之前就已经完成的。而在程序运行时进行的联编工作被称为动态联编,或者称之为动态绑定,又叫做晚期联编。C++规定动态联编是在虚函数的支持下实现的。
3 虚的析构函数
我们先看个例子。
1: #include <iostream>2: using namespace std;3:4: class CParent
5: {6: public:
7: CParent(){cout<<"CParent::constructor is called."<<endl;}
8: ~CParent(){cout<<"CParent::destructor is called."<<endl;}
9: };10:11: class CChild : public CParent12: {13: public:
14: CChild()15: {16: p=new char[50];17: cout<<"CChild::constructor is called."<<endl;
18: }19: ~CChild()20: {21: delete p;
22: cout<<"CChild::destructor is called."<<endl;
23: }24: private:
25: //看到带指针数据成员的类,我们要当心了!
26: char *p;
27: };28:29:30: int main()
31: {32: CParent *pParent;33: CChild *pChild=new CChild();
34: pParent=pChild;35: delete pParent;
36: return 0;
37: }38:
大家注意了,子类CChild带有指针数据成员,如果你看过我以前的文章,会知道如果处理不好,会产生什么问题。具体参考文章《C++之类的构造与析构(二)》之构建自己的复制构造函数。我们先看一下运行结果:
从运行结果看,CChild的析构函数并没有执行,造成了内存泄漏问题。文章《C++之类的继承》中的示例告诉我们如果我们delete pChild,那么CChild的析构函数和CParent的析构函数都会正确地执行。但是我们在使用父类指针时,pParent有可能根据不同的情况指向不同的子类对象,在最后我们无法确认pParent到底指向谁。例如:
1: if (x>y)
2: {3: pParent=pChildA;4: }5: else (x==y)
6: {7: pParent=pChildB;8: }9: else pParent=pChildC;
10: ...11: delete pParent;
所以我们要将父类的析构函数定义为虚函数,这样在删除pParent的时候,会根据虚函数表选择正确的子类的析构函数去执行。修改方法是在CParent和CChild的析构函数前加上virtual关键字,修改后的运行结果如下:
4 多继承
文章《C++之类的继承》中我们提到了多继承,现在我们谈谈对于多继承,我们需要注意的问题:构造顺序和二义性。还记得在《C++之类的继承》中我给你们留的作业吗?现在公布答案:
#include <iostream>using namespace std;class A
{public:
A(){cout<<"This is A constructor."<<endl;}
~A(){cout<<"This is A destructor."<<endl;}
};class B
{public:
B(){cout<<"This is B constructor."<<endl;}
~B(){cout<<"This is B destructor."<<endl;}
};class C:public A,public B{public:
C(){cout<<"This is C constructor."<<endl;}
~C(){cout<<"This is C destructor."<<endl;}
};int main()
{C *c=new C();
delete c;
return 0;
}
程序的运行结果如下:
由此可以看出,多继承情况下,类的构造顺序是从左往右依次调用父类的构造函数,最后调用自己的构造函数。在类对象析构时,析构顺序正好相反。如上图所示。
至于多继承的二义性问题,主要是因为多个父类中可能存在同名成员函数,而在子类中调用了父类的这个函数,于是问题产生了,子类调用的是哪个父类的函数呢?编译器铁定不知道。我们得通过下面的方法告诉它。
#include <iostream>using namespace std;class A
{public:
A(){cout<<"This is A constructor."<<endl;}
~A(){cout<<"This is A destructor."<<endl;}
void speak(){cout<<"This is A speak!"<<endl;}};class B
{public:
B(){cout<<"This is B constructor."<<endl;}
~B(){cout<<"This is B destructor."<<endl;}
void speak(){cout<<"This is B speak!"<<endl;}};class C:public A,public B{public:
C(){cout<<"This is C constructor."<<endl;}
~C(){cout<<"This is C destructor."<<endl;}
};int main()
{C *c=new C();
delete c;
//就是这种方式通知编译器该调用谁的speak
c->A::speak();c->B::speak();return 0;
}
5 纯虚函数和抽象类
5.1 纯虚函数
纯虚函数是一种特殊的函数,它的一般格式:
class <类名>
{virtual <类型><函数名>(参数表)=0;
};
在多数情况下,在基类中不能对虚函数给出有意义的实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
5.2 抽象类
带有纯虚函数的类称为抽象类。抽象类是不能定义对象的。抽象类只描述类共同的操作接口,而完整的事想爱你留给子类。
- 抽象类只能作为其他类的基类,不能用抽象类来实例化一个对象。
- 抽象类不能作为函数参数、函数返回值或转换类型。
- 抽象类可以作为一个指针或引用指向它的子类,进而实现多态。
好了,现在挺晚了,就写到这了。如有问题,欢迎交流!
感谢:《C++编程关键路径——程序员求职指南》