C++之虚函数

        什么是虚函数?为什么要提出虚函数?本文就来为大家解惑。

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 CParent 
 13: { 
 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 CParent 
 20: { 
 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: 

程序的运行结果如下:

2011-07-01_231329

        虚函数是成员函数,而且是非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 CParent 
 12: { 
 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++之类的构造与析构(二)》之构建自己的复制构造函数。我们先看一下运行结果:

2011-07-01_232727

从运行结果看,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关键字,修改后的运行结果如下:

2011-07-01_234829

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

程序的运行结果如下:

2011-07-01_235123

由此可以看出,多继承情况下,类的构造顺序是从左往右依次调用父类的构造函数,最后调用自己的构造函数。在类对象析构时,析构顺序正好相反。如上图所示。

        至于多继承的二义性问题,主要是因为多个父类中可能存在同名成员函数,而在子类中调用了父类的这个函数,于是问题产生了,子类调用的是哪个父类的函数呢?编译器铁定不知道。我们得通过下面的方法告诉它。

#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++编程关键路径——程序员求职指南》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值