C++之成员函数声明为虚函数的规则

虚函数
定义:
虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public,是C++多态的一种表现。
作用:
实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型,以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。当程序发现虚函数名前的关键virtual后,会自动将其作为动态联编处理,即在程序运行时动态地选择合适的成员函数。
使用方法:
动态绑定指出,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式:
  指向基类的指针变量名->虚函数名(实参表)

基类对象的引用名. 虚函数名(实参表)

什么函数不能声明为虚函数:
1.构造函数不能声明为虚函数
因为在执行构造函数时类对象还未完成建立过程,更谈不上把函数与类对象的绑定(关于绑定可参考上一篇博文)因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。
2.静态成员函数不能声明为虚函数
(1)静态成员函数并不是“成员”,它没有this指针。
(2)静态成员函数对每个类只有一份代码而且是共享的,所以它不被继承,当然也不能被声明为虚函数。
(3)静态成员函数是静态的在编译时,而虚函数是动态的在运行时。两者创建时间不同。
(4)静态成员函数可以通过类调用,但不能通过对象调用,而虚函数只能通过对象调用才有意义。
3.友元函数不能声明为虚函数
和静态成员类似,它不是成员,不能被继承
4.赋值运算符重载函数最好不要声明为虚函数
5.析构函数可以声明为虚函数
因为无论指针值指的是同一个类中的哪个类对象,系统都会采用动态绑定,调用相应类的析构函数对该对象进行清理。而且继承时最好将析构函数声明为虚函数。
6.普通函数不声明为虚函数
普通函数(非成员函数)声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层函数不属于成员函数,是不能被继承的。
7.内联函数不能声明为虚函数
其实很简单,内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。再者内联函数和虚函数有着本质的区别,内联函数是在程序被编译时就展开,在函数调用处用整个函数体去替换,而虚函数是在运行期才能够确定如何去调用的,因而内联函数体现的是一种编译期机制,虚函数体现的是一种运行期机制。此外,一切虚函数都不可能是内联函数。
下面这个示例意在证明将析构函数声明为虚函数的好处:

第一段代码:
用派生类创建派生类指针

#include <iostream>
using namespace std;
    class Base
{
public:
    Base()
    {}
    ~Base()
    {
        cout<<"~Base()"<<endl;
    }
    void Fun1()
    {
        cout<<"Do something in Base"<<endl;
    }
};
class Derived:public Base
{
public:
    Derived()
    {}
    ~Derived()
    {
        cout<<"~Derived()"<<endl;
    }
    void Fun1()
    {
        cout<<"Do something in Derived"<<endl;
    }
};

int main()
{
    Derived *d = new Derived;
    d->Fun1 ();
    delete d;
    return 0;
}

 运行结果:

  Do something in Derived!

  ~Derived()

  ~Base()
这段代码中基类的析构函数不是虚函数,在main函数中用继承类的指针去操作继承类的成员,释放指针d的过程是:先释放继承类的资源,再释放基类资源。

若将以上代码的主函数改为用派生类创建基类指针

int main()
{
    Base *b = new Derived;
    b->Fun1 ();
    delete b;
    return 0;
}

 输出结果:

  Do something in Base
  ~Base()
这段代码中基类的析构函数同样不是虚函数,不同的是在main函数中用基类的指针去操作继承类的成员,释放指针b的过程是:只是释放了基类的资源,而没有调用继承类的析构函数.调用Fun1()函数执行的也是基类定义的函数。
一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,造成内存泄漏。
在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员.如果想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数。

析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该声明为虚的。

#include <iostream>
using namespace std;
class Base
{
public:
    Base()
    {}
    virtual ~Base()
    {
        cout<<"~Base()"<<endl;
    }
    void Fun1()
    {
        cout<<"Do something in Base"<<endl;
    }
};
class Derived:public Base
{
public:
    Derived()
    {}
    ~Derived()
    {
        cout<<"~Derived()"<<endl;
    }
    void Fun1()
    {
        cout<<"Do something in Derived"<<endl;
    }
};

int main()
{
    Base *b = new Derived;
    b->Fun1 ();
    delete b;
    return 0;
}

  运行结果:

  Do something in Derived
  ~Derived()
  ~Base()
这段代码中基类的析构函数被定义为虚函数,在main函数中用基类的指针去操作继承类的成员,释放指针b的过程是:只是释放了继承类的资源,再调用基类的析构函数.调用Fun1()函数执行的也是继承类定义的函数。
所以如果不需要基类对派生类及对象进行操作,则不能定义虚函数,因为这样会增加内存开销.当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间.所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值