c++多态

原文:【C++】多态(举例+详解,超级详细)_c++多态-CSDN博客

C++---静态多态与动态多态-CSDN博客

C++多态与虚函数表-CSDN博客

 

 c++的多态分为两种,一种是静态多态,一种是动态多态。我们一般说的虚函数的方法其实是属于动态多态。

 

继承中构成动态多态的两个条件:

1. 通过基类的指针或引用调用虚函数

2. 被调用的函数是虚函数

(要注意与隐藏的关系,隐藏不要求虚函数和参数,且基类的同名函数仍然存在,可以通过基类名字进行访问;而多态或说重写就只有一个函数了,即派生类重写的函数,虚表中也不会存基类的虚函数,只存派生类重写的函数)

 

虚函数:virtual修饰的函数

重写基类虚函数时,派生类的虚函数函数即使不加virtual也构成重写,仍旧保持虚函数属性。

虚函数重写的两个特例:

1. 派生类虚函数返回类型是基类返回类型的子类,称为协变

class A {};
class B : public A {};
class Person {
public:
    virtual A* f() { return new A; }
};
class Student : public Person {
public:
    virtual B* f() { return new B; }
};

2. 析构函数

基类的析构函数为虚函数,派生类的析构函数一定构成重写(即使名字不同)。编译器对对析构函数同一处理成destructor。

需要注意的是基类的析构函数需要声明为虚函数,否则释放基类指针(指向派生类)是只会调用基类的析构函数。

class A
{
public:
    virtual void func(int val = 1)
    { 
        std::cout << "A->" << val << std::endl; 
    }
    virtual void test()
    { 
        func(); 
    }
};
 
class B : public A
{
public:
    void func(int val = 0)
    { 
        cout << "B->" << val << std::endl; 
    }
};
 
int main(int argc, char* argv[])
{
    B*p = new B;
    p->test();
    return 0;
}

注意这里输出的是B->1,因为虚函数时接口继承,实现重写,func的参数还是val=1。

 

override 检查派生类虚函数是否重写了基类的某个函数,如果没有就编译报错;final 修饰虚函数,表示该虚函数不能被重写。

class Car
{
public:
    virtual void Drive() final {}
};
class Benz :public Car
{
public:
    virtual void Drive() { cout << "Benz-舒适" << endl; }
};
class Car {
public:
    virtual void Drive() {}
};
class Benz :public Car {
public:
    virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

 

纯虚函数:

基类中声明的虚函数,在后面加上“=0”,基类中没有实现,需要派生类去实现,这种函数叫纯虚函数。包含纯虚函数的类叫抽象类,抽象类不能实例化对象,需要通过派生类实现纯虚函数后在派生类中实例化。

 

这里还要讲一下底层实现:

class A {
public:
    virtual void vfunc1();
    virtual void vfunc2();
    void func1();
    void func2();
private:
    int m_data1, m_data2;
};
 
class B : public A {
public:
    virtual void vfunc1();
    void func1();
private:
    int m_data3;
};
 
class C: public B {
public:
    virtual void vfunc2();
    void func2();
private:
    int m_data1, m_data4;
};

由于这三个类都有虚函数,故编译器为每个类都创建了一个虚表,即类A的虚表(A vtbl),类B的虚表(B vtbl),类C的虚表(C vtbl)。类A,类B,类C的对象都拥有一个虚表指针,*__vptr,用来指向自己所属类的虚表。 
类A包括两个虚函数,故A vtbl包含两个指针,分别指向A::vfunc1()和A::vfunc2()。 
类B继承于类A,故类B可以调用类A的函数,但由于类B重写了B::vfunc1()函数,故B vtbl的两个指针分别指向B::vfunc1()和A::vfunc2()。 
类C继承于类B,故类C可以调用类B的函数,但由于类C重写了C::vfunc2()函数,故C vtbl的两个指针分别指向B::vfunc1()(指向继承的最近的一个类的函数)和C::vfunc2()。 
虽然图3看起来有点复杂,但是只要抓住“对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向其继承的最近的一个类的虚函数”这个特点,便可以快速将这几个类的对象模型在自己的脑海中描绘出来。

非虚函数的调用不用经过虚表,故不需要虚表中的指针指向这些函数。

 

此外还有静态多态:

1. 函数重载

2. 模板

特点是编译时绑定对应的方法,动态多态是运行时绑定方法。静态多态,编译时期的多态,编译器可以根据函数实参的类型确定要调用的函数,或确定模板类的具体类型。其中函数重载又包括普通函数的重载和成员函数的重载,与参数个数以及类型有关,与参数名和返回类型无关。

 

 

posted @ 2025-03-20 23:28  横渡大海的神仙鱼  阅读(39)  评论(0)    收藏  举报