C++多态知识点真理

目录

一、什么是多态:

二、多态的条件

三、理解虚函数的重写行为

四、虚函数重写的其他问题

 五、 纯虚函数和抽象类

六、多态的底层原理

七、动态绑定和静态绑定

八、虚函数表



一、什么是多态:

多态,通俗来说就是多种形态,分为编译时多态(静态多态)和运行时多态(动态多态)
        编译时多态主要就是前面讲的函数多态和函数模版,通过参数不同达到多种形态,
 将它叫做编译时多态,是因为他们的实参传给形参的过程中实现的
        一般将编译时成为静态,将运行时成为动态
        运行时多态,具体点就是去完成某个行为(函数),可以传不同的对象就会完成不同的行为,
 就达到多种形态


class A
{
public:
    virtual ~A()
    {
        cout << "~A" << endl;
    }

    virtual void print() final//final修饰的虚函数无法被重写
    {
        cout << ' ' << endl;
    }
};

class B : public A
{
public:
    构成虚函数重写
    ~B()//在编译器处理后都会被处理成的destructor
    {
        cout << "~B" << endl;
        delete _p;
        A::~A();
    }
protected:
    int* _p = new int[10];
};


二、多态的条件

class Person
{
    1、必须基类的指针或者引用调用虚函数;2、被调用的函数必须是虚函数,
     派生类必须对基类的虚函数重写或者覆盖
public:
    派生类的可以去掉,但是基类的不可以去掉
    virtual void BuyTicket()//用virtual修饰的成员函数叫做虚函数
    {
        cout << "买票-全价" << endl;
    }

    virtual A* BuyTicket()//协变
    {
        cout << " " << endl;
        return nullptr;
    }
};

class Student : public Person//多个子类之间也可以实现多态,一句代码实现不同的行为
{
public:
    派生、覆盖
    三个相同:返回值类型、参数列表、函数名(缺省值不一样也算重写)
    但是内部实现不同
    virtual void BuyTicket() override
    {
        cout << "买票-打折" << endl;//重写的是实现
    }
    派生类虚函数可以不加virtual,但是并不规范
};

void Func(Person* ptr)//基类的指针
{
    通过Person指针调用BuyTicket,满足多态,指向谁,调用谁
    不满足多态看传的指针的类型,是什么类型调用谁
    ptr->BuyTicket();//调用虚函数,并且完成了覆盖或者重写
}

void Func(Person& ptr)//基类的引用
{
    ptr.BuyTicket();
}

int main()
{

    实现了与类型无关,只与指向有关,如果基类的析构函数不设计为虚构函数
    这里就会因为不够成多态不看指向看类型导致析构B的时候调用的是A的析构函数
    A* p1 = new A;
    A* p2 = new B;
    先调用析构函数,再调用operator delete
    调用A的析构函数
    delete p1;
    调用B的析构函数,但他有两部分组成父类和子类自己的部分,派生类析构函数会在结束后自动调用
    父类的析构函数,以保证先子后父的析构顺序,所以会有~A ~B ~A
    delete p2;//构成多态,一个是父类指针——p1和p2的类型都是A,另一个是调用重写的虚函数
    return 0;
}


三、理解虚函数的重写行为

判断以下程序结果:
 输出结果是B->1
 在重写的情况下,重写的是虚函数实现,可以理解成是用派生类的函数体加上基类的函数声明实现
 绝不重新定义继承而来的缺省值原因如上
 
 因此B中的缺省值无效
 
 effective C++
 
class A
{
public:
    virtual void func(int val = 1)
    {
        std::cout << "A->" << val << std::endl;
    }
    
    virtual void test()//这里的this指针是A*
    {
        func();//这里的func满足多态,一个是有基类的指针(this),另一个是调用重写过的虚函数
    }
};

class B : public A
{
public:
    void func(int val = 0)
    {
        std::cout << "B->" << val << std::endl;
    }
};

int main()
{
    B* p = new B;
    p->test();//这里通过继承可以找到test函数
    return 0;
}
 
 int main()
 {
    B* p = new B;
  p->func();这里不构成多态,因为是派生类的指针,只是一个普通调用
    return 0;
 }



四、虚函数重写的其他问题


协变:虚函数返回值可以不同,要求必须返回基类或者派生类的指针或者引用
析构函数的重写:基类的析构函数为虚函数,此时派生类只要定义就行, 就可以构成重写override 和 final 关键字:override可以检查是否构成重写(编译的时候检查不出来),而如果不想让虚函数被重写,就加上final



 
五、 纯虚函数和抽象类


 在虚函数后面写上=0,即为纯虚函数,纯虚函数不需要定义实现(语法上也可以定义)
 包含纯虚函数的类是抽象类,抽象类不能实例化出对象
 如果派生类没有重写纯虚函数,那么派生类也是抽象类

class car//抽象类——不能实例化出对象
{
public:
    virtual void Drive() = 0;//纯虚函数
};

class Benz : public car//纯虚函数间接在语法上强制派生了重写纯虚函数
{
public:
    virtual void Drive()
    {
        cout << "Benz-舒适" << endl;
    }
};

class BMW : public car//纯虚函数间接在语法上强制派生了重写纯虚函数
{
public:
    virtual void Drive()
    {
        cout << "BMW-操控" << endl;
    }
};

int main()
{
    car car;
    Benz car_Benz;
    BMW car_BMW;

    car* pBenz = new Benz;
    pBenz->Drive();
    car* pBMW = new BMW;
    pBMW->Drive();
    return 0;
}



六、多态的底层原理
 

class base
{
public:
    virtual void Func()//这里有一个虚函数表指针_vfptr,大小4字节——virtual function table 
    {
        vfptr是一个函数指针数组,每个虚函数的地址都会被放进去
        cout << "Func()" << endl;
    }

    virtual void Func2()
    {
        cout << "Func2()" << endl;
    }
private:
    int _b = 1;
    char _ch = 'x';
};

class upper_base : public base
{
public:
    指向谁调用谁,指向那个对象,就到指向函数的虚函数表中找到对应的虚函数地址,进行调用,
     也就实现了运行时多态
    void Func1()//vfptr中的函数指针指向upper_base的func
    {
        cout << "upper_base" << endl;
    }
private:
    int _a = 1;
};

int main()
{
    base b;
    upper_base u;
    cout << sizeof(b) << endl;
    cout << sizeof(u) << endl;
    return 0;
}



七、动态绑定和静态绑定


 静态绑定:函数调用是在编译时确定调用函数的地址
 动态绑定:运行到指向对象的虚函数表中找到调用函数的地址
 从效率来说静态绑定效率更高,因为不需要查找


八、虚函数表


 ·基类虚函数表中存放基类所有虚函数地址,同一种类型的基类对象的虚函数表是一样的,共用,
 不同对象的虚表各自独立
 ·派生类分为基类和字节的成员,派生类的虚表是继承来的,但里面放的东西不一样,会将派生类中已经重写的虚函数
 在继承来的虚表里的原地址改为新的虚函数的地址
 ·基类的虚函数表总包含基类的虚函数地址、派生类重写的虚函数地址,派生类自己的虚函数地址
 (现将父类的虚函数表拷贝过来,再用重写的虚函数的地址去完成相应部分的覆盖,没有完成的部分不管
 还要放进去派生类自己的虚函数地址)
 ·虚函数表存在没有标准答案,vs编译器将这个函数指针数组存在常量区
 虚函数和普通函数也存在一起,只不过虚函数地址会被放在虚表
 
 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值