继承与多态

继承的本质和原理

继承的本质: 代码复用

类和类之间的关系:

        组合:a part of .......  一部分的关系

        继承: 一种的关系

继承
继承方式基类的访问限定派生类的访问限定外部的访问限定
publicpublicpublicyes
protectedprotectedno
private不可见no
protectedpublicprotectedno
protectedprotectedno
private不可见no
privatepublicprivateno
protectedprivateno
private不可见no

总结:

        1.外部只能访问对象public的成员,protected 和 private 的成员无法直接访问

        2.在继承结构中,派生类从基类可以继承过来private的成员,但是派生类却无法直接访问

        3.protected 和 private的区别: 

                在基类中定义的成员,想被派生类访问,但是不想被外部访问,那么在基类中,把相关成员定义成protected 保护的。如果派生类和外部都不打算访问,那么基类中。就把相关成员定义成private私有的。

        class定义派生类。默认的继承方式是私有的

        struct定义派生类。默认的继承方式是公有的

 派生类构造过程

        1.派生类从继承可以继承来所有的成员(变量和方法),除过构造函数和析构函数

                1.派生类怎么初始化从基类继承来的成员变量呢?:通过调用基类相应的构造函数来初始化

                2.派生类的构造函数和析构函数,负责初始化和清理派生类部分

                3.派生类从基类继承类的成员,的初始化和清理谁来负责? 是由基类的构造和析构函数类负责

         2.派生类对象构造和析构过程是:

                1.派生类调用基类的构造函数,初始化从基类继承来的成员

                2.调用派生类自己的构造函数,初始化派生类自己的特有成员。

         3.派生类对象出了作用域之后:

                1.调用派生类的析构函数,释放派生类成员可能占用的外部资源(堆内存,文件)

                2.调用基类的析构函数,释放派生类内存中,从基类继承来的成员可能占用的外部资源(堆内存,文件)

#include <iostream>
#include <cstring>
using namespace std; 

class Base
{
    public:
        Base(int data): ma(data)
        {
            cout << "Base()" << endl;
        }
        ~Base()
        {
            cout << "~Base()" << endl;
        }
    protected:
        int ma;
};

class Derive : public Base
{
    public:
        Derive(int data):Base(data),mb(data)
        {
            cout << "Base()" << endl;
        }
        ~Derive()
        {
            cout << "~Derive()" << endl;
        }
    private:
        int mb;
};

int main()
{

    Derive d(20);
    return 0;
}

重载、隐藏、覆盖

        1.重载关系:一组函数要重载,必须要处在同一个作用当中;而且函数名相同,参数列表不同

        2.隐藏(作用域的隐藏)关系:在继承结构当中,派生类的同名成员,把基类的同名成员给隐藏掉了。优先找的是派生类自己作用域的show名字成员;没有的话,才去基类里面找

         

基类派生类转换
基类对象派生类对象转换赋值是否可以备注
Base base(10);Derive drive(20);base = drive;Y  派生类成员会被丢弃
Base base(10);Derive drive(20);drive = base;N不能转换。
Base *pb;Derive drive(20);Base *pb = &driveY只能访问从基类Base继承来的成员
Base base(10);Derive *pd;Derive *pd = &base;N会非法访问内存区域

        3.覆盖关系:虚函数表中虚函数地址的覆盖。

        基类和派生类的方法,返回值、函数名以及参数列表都相同,而且基类的方法是虚函数,那么派生类的方法就自动处理成虚函数,它们之间成为了覆盖

虚函数、静态绑定和动态绑定

   静态(编译时期)的绑定,编译的时候就绑定好了调用的函数。所以下边的Base *pb 调用的是Base的show

#include <iostream>
#include <cstring>
#include <typeinfo>
using namespace std; 

class Base
{
    public:
        Base(int data): ma(data)
        {
            
        }
        ~Base()
        {
            
        }
        void show() 
        {
            cout << "Base::show()" << endl;
        }
        void show(int)
        {
            cout << "Base::show(int)" << endl; 
        }

    protected:
        int ma;
};

class Derive : public Base
{
    public:
        Derive(int data = 20):Base(data),mb(data)
        {
            
        }
        void show() 
        {
            cout << "Derive::show()" <<endl;
        }
        ~Derive()
        {
            
        }
    private:
        int mb;
};

 那么如果我们想用Base *pd = &d;  调用到Derive中的show的话,应该怎么办呢?利用虚函数

虚函数

 1.一个类里边定义了虚函数,那么编译阶段,编译器就给这个类类型产生一个唯一的vftable虚函数表,虚函数表中主要存储的内容就是RTTI指针和虚函数的地址。当运行时,每一张虚函数表都会加载到内存.rodata区

2.一个类中定义了虚函数,那么这个定义的对象,其运行时,内存中开始部分,多存储一个vfptr虚函数指针,指向相应类型的虚函数表vftable。一个类型定义的n个对象,它们的vfptr指向的都是同一张虚函数表

3.一个类里面的虚函数的个数,不影响对象内存大小(vfptr),影响的是虚函数表的大小

4.如果派生类中的方法,和基类继承来的某个方法,返回值、函数名、参数列表都相同,而且基类的方法是virtual虚函数,那么派生类的这个方法,自动处理成虚函数 

class Base
{
    public:
        Base(int data): ma(data)
        {
            
        }
        ~Base()
        {
            
        }
        //虚函数
        virtual void show() 
        {
            cout << "Base::show()" << endl;
        }
        virtual void show(int)
        {
            cout << "Base::show(int)" << endl; 
        }

    protected:
        int ma;
};

class Derive : public Base
{
    public:
        Derive(int data = 20):Base(data),mb(data)
        {
            
        }
        void show() 
        {
            cout << "Derive::show()" <<endl;
        }
        ~Derive()
        {
            
        }
    private:
        int mb;
};

虚析构函数

虚函数的依赖:

        1.虚函数能产生地址,存储在vftable中

        2.对象必须要存在(vfptr ->vftable ->虚函数地址)

问题1:哪些函数不能实现成虚函数

        派生类对象构造过程 1.先调用的是基类的构造函数 2.才调用派生类的构造函数

        构造函数:

                1.不能实现成虚函数

                2.构造函数中调用虚函数,也不会发生动态绑定。调用普通函数都是静态绑定

        static 静态成员也不能实现成虚函数

问题2:虚析构函数

        1.析构函数调用的时候,对象是存在的

class Base
{
    public:
        Base(int data): ma(data)
        {
            cout << "Base()" << endl;
        }
        ~Base()
        {
            cout << "~Base()" << endl;
        }
        virtual void show()
        {
            cout << "Base::show()" <<endl;
        }
    protected:
        int ma;
};

class Derive : public Base
{
    public:
        Derive(int data = 20):Base(data),mb(data),ptr(&data)
        {
            cout << "Derive()" << endl;
        }
        ~Derive()
        {
            delete ptr;
            cout << "~Derive()" << endl;
        }
        void show()
        {
            cout<< "Derive::show()" <<endl;
        }
    private:
        int *ptr;
        int mb;
};

 什么时候把基类的析构函数必须要实现成虚函数?

        基类的指针(引用)指向堆上new出来的派生类对象的时候,delete pd(基类的指针),它调用析构函数的时候,必须要发生动态绑定,否则会导致派生类的析构函数无法调用。

虚函数和动态绑定

问题1:是不是虚函数的调用一定是动态绑定呢? 不是的

        在类的构造函数当中,调用虚函数也是静态绑定(构造函数中调用其他函数(虚),不会发生动态绑定)

class Base
{
    public:
        Base(int data = 0):ma(data) 
        {

        }
        virtual void show()
        {
            cout << "Base::show()" << endl;
        }
    protected:
        int ma;
};
class Derive:public Base
{
    public:
        Derive(int data= 0 ): Base(data), mb(data)
        {

        }
        void show()
        {
            cout << "Derive::show()" <<endl;
        }
    private:
        int mb;
};

理解多态是什么

如何解释多态?

1.静态(编译时期)的多态:函数重载、模板(函数模板、类模板)

        bool compare(int, int){}

        bool compare(double, double){}

        ....

2.动态(运行时期)的多态

        在继承结构中,基类指针(引用)指向派生类对象,通过该指针(引用)调用同名覆盖方法(虚函数)

        ,基类的指针指向哪个派生类,就会调用哪个派生类的虚函数表(覆盖方法),称为多态

        解释:多态的底层是通过动态绑定来实现的,pBase->访问谁的vfptr->就是访问谁的vftable

#include <iostream>
#include <cstring>
#include <string>
#include <typeinfo>
using namespace std; 

//动物的基类
class Animal
{
    public:
        Animal(string name):_name(name) {}
        virtual void bark(){}
    protected:
        string _name;
};
//动物实体类
class Cat:public Animal
{
    public:
        Cat(string name):Animal(name){}
        void bark()
        {
            cout << "name:"<< _name << ", bark:miao miao" << endl;
        }
};

class Dog:public Animal
{
    public:
        Dog(string name):Animal(name){}
        void bark()
        {
            cout << "name:" << _name << ", bark:wangwang" << endl;
        }
};

class Pig:public Animal
{
    public:
        Pig(string name):Animal(name){}
        void bark()
        {
            cout << "name:" << _name << ", bark:hengheng" << endl;
        }
};


void bark(Animal &animal)
{
    animal.bark();
}

int main()
{

    Cat cat("猫咪");
    Dog dog("二哈");
    Pig pig("佩奇");

    bark(cat);
    bark(dog);
    bark(pig);

    return 0;
}

继承的好处是什么?

        1.可以做代码复用

        2.在基类中提供统一的虚函数接口,让派生类重写,就可以使用多态了。

抽象类

1.抽象类和普通类有什么区别?

 定义Animal的初衷,并不是让Animal抽象某个实体的类型

        1.string _name; 让所有的动物实体类通过继承Animal直接复用该属性

        2.给所有的派生类保留统一的覆盖、重写接口

        拥有纯虚函数的类,叫做抽象类!(Animal)

注意:抽象类不能再实力化对象了(Animal a("猫");错误的),但是可以定义指针和引用变量

2.一般把什么类设计成抽象类? 是基类

#include <iostream>
#include <cstring>
#include <string>
#include <typeinfo>
using namespace std; 

//汽车的基类
class Car //抽象类
{
    public:
        Car(string name,double oil):_name(name),_oil(oil) {}
        //获取汽车剩余油量还能跑的公里数
        double getLeftMiles()
        {
            return _oil * getMinlesPerGallon();
        }
        string getName() const {return _name;}
    protected:
        string _name;
        double _oil;
        virtual double getMinlesPerGallon() = 0;//纯虚函数
};

class Bnze:public Car
{
    public:
        Bnze(string name,double oil):Car(name,oil) {}
        double getMinlesPerGallon()
        {
            return 20.0;
        }
};

class Audi:public Car
{
    public:
        Audi(string name,double oil):Car(name,oil){}
        double getMinlesPerGallon()
        {
            return 18.0;
        }
};

class BMW:public Car
{
    public:
        BMW(string name,double oil):Car(name,oil) {}
        double getMinlesPerGallon()
        {
            return 19.0;
        }
};

//给外部提供一个统一的获取汽车剩余路程的API
void showCarLeftMiles(Car &car)
{
    cout << car.getName() << " left miles:" <<  car.getLeftMiles() << "公里" << endl;
}

int main()
{
    Bnze b1("奔驰",3.0);
    Audi a1("奥迪",2.1);
    BMW  b2("宝马",3.9);
    showCarLeftMiles(b1);
    showCarLeftMiles(a1);
    showCarLeftMiles(b2);

    return 0;
}

注意事项:

        Base *pb = new Derive();

        1.派生类和基类的虚函数都有默认值的话:使用的是基类的默认值。编译时期是找到指针的Base,然后去找Base的默认值

        2.基类的虚函数是public权限,派生类的虚函数是private。最终调用的也是派生类的虚函数。权限的判断是在编译时期,调用发生在运行时期。派生类的私有方法也是虚函数,然后也会覆盖到虚函数表中。所以最后也能调用派生类的方法

 虚基类和虚继承

虚继承/virtual

        1.修饰成员方法是虚函数

        2.可以修饰继承方式,是虚继承。被虚继承的类,称作虚基类

class A
{
    public:
        void operator delete(void *ptr){
            cout << "delete:" << ptr << endl;
            free(ptr);
        }
    protected:
        int ma;
};
//虚继承
class B:virtual public A
{
    public:
        void* operator new(size_t size)
        {
            void *p = malloc(size);
            cout << "new:" << p << endl;
            return p;
        }
    private:
        int mb;
};


int main()
{
    cout << endl;
    A *p = new B(); //linux 上使用并没有问题,苹果和win有问题
    cout << "main:" << p <<endl;
    delete p;
    cout << endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CID( ͡ _ ͡°)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值