C++ 虚函数

1.虚函数:
  • 用virtual定义的非static实例成员函数,虚函数可表现多态性,非虚函数的执行效率比较高,但是不能表现多态性;
  • .虚函数总是有this,故参数后面可出现const 和 volatile,而staic成员函数无 this,故参数表后不可出现const 和 volatile,所以virtual 和 static 不能同时出现,否则关于this是矛盾的;
  • 动态多态:重载函数表现的是静态(编译时)多态性,虚函数表现的是动态(运行时)的多态,多态一般指动态的编译;
  • 重载函数是静态多态函数,通过早期绑定调用重载函数,虚函数是动态多态函数,通过晚期绑定调用函数;晚期绑定是程序运行时由程序自己完成的,早期绑定是编译或者操作系统完成的;
  • 虚函数的晚期绑定通过存储在对象中的一个指向虚函数入口地址表VFT的指针完成;
  • 虚函数一般在基类的public或protected部分,在派生类中定义取代型函数时,函数原型必须和基类的虚函数必须完全相同;无论是否使用virtual保留字都将成为虚函数;
  • 虚函数只有在具有继承关系的类称重才需要表现多态,
    • union既不能作为基类,也不能作为派生类,故union不能定义为虚函数;
    • 构造对象时类型是确定的,不需要根据类型不同表现为不同多态行为,故构造函数不能定义为虚函数;
    • 析构函数可通过父类指针,引用或delete调用,父类指针可能指向父类或子类对象,因此析构函数需要多态性;
  • 虚函数可声明或者自动称为inline函数,也可重载,缺省和省略参数;
    (虚函数一定是内联失败的:根据地址表取地址晚期绑定,内联失败);
  • 虚函数不能使用constexper 定义,故构造函数 可用 constexper定义,但是有虚基类的派生类构造不能使用constexper定义;
示例代码
#include <iostream>

using namespace std;
class POINT
{
    int x, y;
public:
    int getx() { return x; }
    int gety() { return y; }
    virtual void show() { cout << "Show a point\n"; }
    POINT(int x, int y) : x(x), y(y) {}
};
class CIRCLE : public POINT
{ //公有继承,基类和派生类构成父子关系
    int r;
    void show() { cout << "Show a circle\n"; } //私有的自动内联并成为虚函数
public:
    int getr() { return r; }
    CIRCLE(int x, int y, int r) : POINT(x, y){ CIRCLE::r = r; }
} c(3, 7, 8);
int main()
{
    POINT *p = &c;//父类指针p指向子类对象,不用类型转换p=(POINT*)&c
    cout << "r: " << c.getr()<<" ,x: " << p->getx() << endl;//不能用p->getr( ),编译时无POINT::getr( )
    p->show();//编译按POINT::show检查,p指向子类对象,但调用CIRCLE::show( ) ,使用了关键字virtual,动态绑定
    system("pause");
    return 0;
}
输出结果
r: 8 ,x: 3
Show a circle
2. 虚析构函数
  • 如果基类的析构函数定义为虚函数,则派生类的析构函数就会自动称为析构函数;
  • 在使用delete运算符销毁一个对象时,多态特性保证执行的析构函数就是该对象的自己的析构函数,故最好将可能被继承的类的析构函数都定义为虚析构函数;
  • 如果为基类和派生类对象的成员分配了动态内存,则
    一定要将基类和派生类的析构函数定义为虚析构函数,否则,派生类对象可能错误调用基类析构函数,造成内存泄漏并导致出现内存(一般性)保护错误。
示例代码
#include <iostream>

using namespace std;
class STACK
{
    int *e, p, c;
    //c:最大栈的空间,p:当前栈的大小
public:
    virtual int getp() { return p; }
    virtual int push(int f) { return p < c ? (e[p++], 1) : 0; }
    virtual int pop(int &f) { return p > 0 ? (f = e[--p], 1) : 0; }
    STACK(int m) : e(new int[m]), c(e ? m : 0), p(0) {}
    virtual ~STACK()
    {
        if (e)
            delete e;
        e = 0;
        c = 0;
        p = 0;
    }
};
class QUEUE : public STACK
{
    STACK s;
public:
    virtual int enter(int f)
    {
        return s.getp ? push(f) : s.push(f);
    }
    virtual int leave(int &f)
    {//用栈模拟队列
        if (!s.getp())
            while (pop(f))
                s.push(f);
        return s.pop(f);
    }
    QUEUE(int m):STACK(m),s(m){ }
    ~QUEUE() { }
};
int main()
{
    STACK *p = new QUEUE(9);
    delete p;
    return 0;
}
说明
  • 如果~STACK没有定义为虚函数,则delete p调用析构函数~STACK,把QUEUE(9)当做基类的STACK对象析构,只释放基类对象成员e占用的内存;未析构对象成员s(不释放s.e)造成内存泄露;
  • 如果~STACK定义为虚函数,则delete p调用析构函数~QUEUE,把QUEUE(9)当做QUEUE对象析构;
  • 父类有址引用变量引用子类对象时不须析构。若引
    用new产生的子类对象,则必须用delete &析构:
STACK &z=*new QUEUE(20); //&定义有址引用变量
delete &z; //析构对象z并释放对象z占用的内存
  • 上述delete &z完成了两个任务:①调用析构函数
    ~QUEUE( ),释放其基类和对象成员各自为整型指
    针e分配的空间;②释放QUEUE对象自身占用的存
    储空间。
  • 如将delete &z改为z.~STACK( ),则只完成任务①而
    没完成②;如果改为free(&z),则只完成任务②而没
    完成①,都会造成内存泄露。为什么z.~STACK( )
    执行~QUEUE( )?(析构多态以及z实现为指针)。
class A
{
public:
    virtual void foo()
    {
        cout<<"A::foo() is called"<<endl;
    }
};
class B:public A
{
public:
    void foo()
    {
        cout<<"B::foo() is called"<<endl;
    }
};
int main(void)
{
    A *a = new B();
    a->foo();   // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!
    return 0;
}
虚就虚在所谓"推迟联编"或者"动态联编"上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为"虚"函数。虚函数只能借助于指针或者引用来达到多态的效果。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值