理解虚基类、虚函数与纯虚函数的概念

虚基类
       在说明其做用前先看一段代码

class A
{
public:
    int iValue;
};

class B:public A
{
public:
    void bPrintf(){cout<<"This is class B"<<endl;};
};

class C:public A
{
public:
    void cPrintf(){cout<<"This is class C"<<endl;};
};

class D:public B,public C
{
public:
    void dPrintf(){cout<<"This is class D"<<endl;};
};

void main()
{
    D d;
    cout<<d.iValue<<endl; //错误,不明确的访问
    cout<<d.A::iValue<<endl; //正确
    cout<<d.B::iValue<<endl; //正确
    cout<<d.C::iValue<<endl; //正确
}

从代码中能够看出类B C都继承了类A的iValue成员,所以类B C都有一个成员变量iValue ,而类D又继承了B C,这样类D就有一个重名的成员 iValue(一个是从类B中继承过来的,一个是从类C中继承过来的).在主函数中调用d.iValue 由于类D有一个重名的成员iValue编译器不知道调用 从谁继承过来的iValue因此就产生的二义性的问题.正确的作法应该是加上做用域限定符 d.B::iValue 表示调用从B类继承过来的iValue。不过 类D的实例中就有多个iValue的实例,就会占用内存空间。因此C++中就引用了虚基类的概念,来解决这个问题。

class A
{
public:
    int iValue;
};

class B:virtual public A
{
public:
    void bPrintf(){cout<<"This is class B"<<endl;};
};

class C:virtual public A
{
public:
    void cPrintf(){cout<<"This is class C"<<endl;};
};

class D:public B,public C
{
public:
    void dPrintf(){cout<<"This is class D"<<endl;};
};

void main()
{
    D d;
    cout<<d.iValue<<endl; //正确
}

在继承的类的前面加上virtual关键字表示被继承的类是一个虚基类,它的被继承成员在派生类中只保留一个实例。例如iValue这个成员,从类 D这个角度上来看,它是从类B与类C继承过来的,而类B C又是从类A继承过来的,但它们只保留一个副本。所以在主函数中调用d.iValue时就不 会产生错误。

       虚函数
       仍是先看代码

class A
{
public:
    void funPrint(){cout<<"funPrint of class A"<<endl;};
};

class B:public A
{
public:
    void funPrint(){cout<<"funPrint of class B"<<endl;};
};

void main()
{
    A *p; //定义基类的指针
    A a;
    B b;
    p=&a;
    p->funPrint();
    p=&b;
    p->funPrint();
}

你们觉得这段代码的输出结果是什么?有的人可能会立刻回答funPrint of class A 与 funPrint of class B 由于第一次输出是引用类A的实 例啊,第二次输出是引用类B的实例啊。那么我告诉你这样想就错啦,答案是funPrint of class A 与 funPrint of class A 至于为何输出 这样的结果不在本文讨论的范围以内;你就记住,无论引用的实例是哪一个类的当你调用的时候系统会调用左值那个对象所属类的方法。好比说 上面的代码类A B都有一个funPrint 函数,由于p是一个A类的指针,因此无论你将p指针指向类A或是类B,最终调用的函数都是类A的funPrint 函数。这就是静态联篇,编译器在编译的时候就已经肯定好了。但是若是我想实现跟据实例的不一样来动态决定调用哪一个函数呢?这就需要用到 虚函数(也就是动态联篇)

class A
{
public:
    virtual void funPrint(){cout<<"funPrint of class A"<<endl;};
};

class B:public A
{
public:
    virtual void funPrint(){cout<<"funPrint of class B"<<endl;};
};

void main()
{
    A *p; //定义基类的指针
    A a;
    B b;
    p=&a;
    p->funPrint();
    p=&b;
    p->funPrint();
}

在基类的成员函数前加virtual关键字表示这个函数是一个虚函数,所谓虚函数就是在编译的时候不肯定要调用哪一个函数,而是动态决定将要调 用哪一个函数,要实现虚函数必须派生类的函数名与基类相同,参数名参数类型等也要与基类相同。但派生类中的virtual关键字能够省略,也表 示这是一个虚函数。下面来解决一下代码,声明一个基类的指针(必须是基类,反之则不行)p,把p指向类A的实例a,调用funPrint函数,这 时系统会判断p所指向的实例的类型,若是是A类的实例就调用A类的funPrint函数,若是是B类的实例就调用B类的funPrint函数。

    纯虚函数
    与其叫纯虚函数还不如叫抽象类,它只是声明一个函数但不实现它,让派生类去实现它,其实这也很好理解。

class Vehicle
{
public:
    virtual void PrintTyre()=0; //纯虚函数是这样定义的
};

class Camion:public Vehicle
{
public:
    virtual void PrintTyre(){cout<<"Camion tyre four"<<endl;};
};

class Bike:public Vehicle
{
public:
    virtual void PrintTyre(){cout<<"Bike tyre two"<<endl;};
};

void main()
{
    Camion c;
    Bike b;
    b.PrintTyre();
    c.PrintTyre();
}

如上代码,定义了一个交通工具类(Vehicle),类中有一函数可打印出交通工具的轮胎个数,但交通工具不少轮胎个数天然也就不肯定,因此 就把它定义为纯虚函数,也就是光定义函数名不去实现它,类Camion继承了Vehicle并实现了里面的代码,打印出有4个轮胎。Bike类也是同样。 有一点需要注意一下,纯虚函数不能实化化,但能够声明指针。


总结

    虚基类
    1, 一个类能够在一个类族中既被用做虚基类,也被用做非虚基类。
    2, 在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的子对象。
    3, 虚基类子对象是由最派生类的构造函数经过调用虚基类的构造函数进行初始化的。
    4, 最派生类是指在继承结构中创建对象时所指定的类。
    5, 派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;若是未列出,则表示使用该虚基类的缺省构造函数。
    6, 从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但只有用于创建对象的最派生 类的构造函数调用虚基类的构造函数,而该派生类的全部基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象 只初始化一次。
    7, 在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。

    虚函数
    1, 虚函数是非静态的、非内联的成员函数,而不能是友元函数,但虚函数能够在另外一个类中被声明为友元函数。
    2, 虚函数声明只能出如今类定义的函数原型声明中,而不能在成员函数的函数体实现的时候声明。
    3, 一个虚函数不管被公有继承多少次,它仍然保持其虚函数的特性。
    4, 若类中一个成员函数被说明为虚函数,则该成员函数在派生类中可能有不一样的实现。当使用该成员函数操做指针或引用所标识的对象时 ,对该成员函数调用可采用动态联编。
    5, 定义了虚函数后,程序中声明的指向基类的指针就能够指向其派生类。在执行过程当中,该函数能够不断改变它所指向的对象,调用不一样 版本的成员函数,并且这些动做都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。 纯虚函数 版本的成员函数,并且这些动做都是在运行时动态实现的。虚函数充分体现了面向对象程序设计的动态多态性。

    纯虚函数
    1, 当在基类中不能为虚函数给出一个有意义的实现时,能够将其声明为纯虚函数,其实现留待派生类完成。
    2, 纯虚函数的做用是为派生类提供一个一致的接口。
    3, 纯虚函数不能实化化,但能够声明指针。函数

相关文章
相关标签/搜索