C++多态、虚函数和纯虚函数的关系

  • 多态

C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数;

形成多态必须具备三个条件:

1、必须存在继承关系;

2、继承关系必须有同名虚函数(其中虚函数是在基类中使用关键字Virtual声明的函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数);

3、存在基类类型的指针或者引用,通过该指针或引用调用虚函数;

 

  •     虚函数、纯虚函数

1、纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。

2、虚函数声明如下:virtual ReturnType FunctionName(Parameter) 虚函数必须实现,如果不实现,编译器将报错,错误提示为:

error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"

3、对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。

4、实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。

5、虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数

6、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。

7、友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。

8、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。

 

  • 虚函数表

动态联编的实现机制 VTABLE

编译器对每个包含虚函数的类创建一个虚函数表VTABLE,表中每一项指向一个虚函数的地址,即VTABLE表可以看成一个函数指针的数组,每个虚函数的入口地址就是这个数组的一个元素。

每个含有虚函数的类都有各自的一张虚函数表VTABLE。每个派生类的VTABLE继承了它各个基类的VTABLE,如果基类VTABLE中包含某一项(虚函数的入口地址),则其派生类的VTABLE中也将包含同样的一项,但是两项的值可能不同。如果派生类中重载了该项对应的虚函数,则派生类VTABLE的该项指向重载后的虚函数,如果派生类中没有对该项对应的虚函数进行重新定义,则使用基类的这个虚函数地址。

在创建含有虚函数的类的对象的时候,编译器会在每个对象的内存布局中增加一个vptr指针项,该指针指向本类的VTABLE。在通过指向基类对象的指针(设为bp)调用一个虚函数时,编译器生成的代码是先获取所指对象的vtb1指针,然后调用vtb1所指向类的VTABLE中的对应项(具体虚函数的入口地址)。

当基类中没有定义虚函数时,其长度=数据成员长度;派生类长度=自身数据成员长度+基类继承的数据成员长度;

当基类中定义虚函数后,其长度=数据成员长度+虚函数表的地址长度;派生类长度=自身数据成员长度+基类继承的数据成员长度+虚函数表的地址长度。

包含一个虚函数和几个虚函数的类的长度增量为0。含有虚函数的类只是增加了一个指针用于存储虚函数表的首地址。

派生类与基类同名的虚函数在VTABLE中有相同的索引号(或序号)。

 

  • 虚函数调用实例

虚函数可以不实现(定义)。不实现(定义)的虚函数是纯虚函数。

在一个类中如果存在未定义的虚函数,那么不能直接使用该类的实例,可以理解因为未定义 virtual 函数,其类是抽象的,无法实例化。将报错误:

undefined reference to `vtable for xxx'

这和其它语言的抽象类,抽象方法是类似的——我们必须实现抽象类,否则无法实例化。(virtual 和 abstract还是有些区别的)

也就是说,如果存在以下代码:

using namespace std;

class Base {
public:
    virtual void tall();
};

class People : Base {
public:
    void tall() {
        cout << "people" << endl;
    };
};

那么,在 main 方法中,我们不能使用 Base base; 这行代码,此时的 tall 没有实现,函数表(vtable)的引用是未定义的,故而无法执行。但我们可以使用 People people; 然后 people.tall();  (&people)->tall(); 因为People实现或者说重写、覆盖了 Base 的纯虚方法 tall(),使其在 People 类中有了定义,函数表挂上去了,于是可以诞生实例了。

i

int main() {
//    Base base;//不可用

    People people;//可用
    people.tall();
    (&people)->tall();

    return 0;
}

上述的是针对虚函数而言,普通的函数,即使我们只声明,不定义,也不会产生上述不可用的问题。

 

 

  • final——避免虚函数被重写

父类的虚函数或纯虚函数在子类中依然是虚函数。有时我们并不希望父类的某个函数在子类中被重写,在 C++11 及以后可以用关键字 final 来避免该函数再次被重写。

例:

#include<iostream>
using namespace std;
class Base
{
    public:
        virtual void func()
        {
            cout<<"This is Base"<<endl;
        }
};
class _Base:public Base
{
    public:
        void func() final//正确,func在Base中是虚函数
        {
            cout<<"This is _Base"<<endl;
        }
};
class __Base:public _Base
{
/*    public://不正确,func在_Base中已经不再是虚函数,不能再被重写
        void func()
        {
            cout<<"This is __Base"<<endl;
        }*/
};
int main()
{
    _Base a;
    __Base b;
    Base* ptr=&a;
    ptr->func();
    ptr=&b;
    _Base* ptr2=&b;    ptr->func();
    ptr2->func();
}

以上程序运行结果:

This is _Base
This is _Base
This is _Base

如果不希望一个类被继承,也可以使用 final 关键字。

格式如下:

class Class_name final{

    ...

};

则该类将不能被继承。

**源自菜鸟教程,可配合翁恺C++教学视频虚函数一讲观看,理解更透彻

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值