C++继承与多态

本文大纲:

  • 继承的概念

  • 子类构造过程

  • 重载和隐藏

  • 静态绑定、动态绑定和覆盖

  • 虚析构函数

  • 多态的概念

  • 抽象类

继承

  • 概念:

    • 基类/父类 派生类/子类

    • 类和类之间的关系:组合(是一部分)和继承(是一种)

    • 代码复用

  • 继承访问限定

    • 外部只能访问对象public的成员,protected和privated的成员无法直接访问

    • 在继承结构中,子类可以从基类继承过来private的成员,但是子类无法直接访问

    • protected和private区别?在基类中定义的成员,如果要被子类访问,但是外部不能访问,在基类中把成员定义为protected。如果子类和外部都不访问,在基类中把成员定义为private

    • 默认的继承方式:class定义子类默认继承方式是private,struct定义子类默认继承方式是public

继承方式基类的访问限定子类的访问限定外部的访问限定
publicpublicpublic可以访问
publicprotectedprotected不可以访问
publicprivate不可以访问不可以访问
protectedpublicprotected不可以访问
protectedprotectedprotected不可以访问
protectedprivate不可以访问不可以访问
privatepublicprivate不可以访问
privateprotectedprivate不可以访问
privateprivate不可以访问不可以访问

子类构造过程

  • 子类初始化从基类继承过来的成员变量:调用基类相应的构造函数来初始化

    • 子类的构造和析构函数负责初始化和清理子类的成员

    • 子类从基类中继承来的成员初始化由基类的构造和析构负责

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

class Derive : public Base{
public:
    //调用基类相应的构造函数来初始化
    Derive(int data) : Base(data), m_b(data){
        cout << "Derive()" << endl;
    }
    ~Derive(){
        cout << "~Derive()" << endl
    }
private:
    int m_b;    
};

int main(){
    Derive d(20);
    return 0;
}
  • 上面代码运行结果如下。

    • Base()

    • Derive()

    • ~Derive()

    • ~Base()

  • 子类对象构造和析构过程

    • 子类调用基类的构造函数初始化从基类继承来的成员

    • 子类调用自己的构造函数初始化子类自己的成员

    • 当子类对象的作用域到期后,调用子类的析构函数,释放子类成员可能占用的外部资源(堆内存、文件等)

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

重载和隐藏

  • 重载关系:同一作用域、同样的函数名、参数列表不同

  • 隐藏(作用域隐藏)的关系:在继承结构中,子类的同名成员把基类的同名成员隐藏调用了

class Base{
public:
    Base(int data = 10) : m_a(data){}
    void display(){cout << "Base::display()" << endl;}
    void display(int){cout << "Base::display(int)" << endl;}
protected:
    int m_a;
};

class Derive : public Base{
public:
    //调用基类相应的构造函数来初始化
    Derive(int data = 15) : Base(data), m_b(data){}
    void display(){cout << "Derive::display()" << endl;}
private:
    int m_b;    
};

int main(){
    Derive d;
    d.display();//Derive::display()
    d.Base::display;//Base::display()
    d.display(10);//error: Derive::display函数不接受1个参数
    d.Base::display(10);//ok
    return 0;
}
  • 向上造型和向下造型:在继承结构中进行上下类型转换,默认只支持向上(基类)造型

    int main(){
        Base b(10);
        Derive d(20);
        //d = b;//类型从上到下的转换,向下造型是不可以的
        //Derive *pd = &b;向下造型是不可以的
        Derive *pd = (Derive *)&b;//不安全,涉及了内存的非法访问
        b = d;//类型从下到上的转换,向上造型是可以的
        Base *pb = &d;//类型从下到上的转换,向上造型是可以
    }
    

静态绑定、动态绑定、覆盖

  • 静态绑定:在编译时期绑定(函数调用)call确定的函数
class Base{
public:
    Base(int data = 10) : m_a(data){}
    void display(){cout << "Base::display()" << endl;}
    void display(int){cout << "Base::display(int)" << endl;}
protected:
    int m_a;
};

class Derive : public Base{
public:
    //调用基类相应的构造函数来初始化
    Derive(int data = 15) : Base(data), m_b(data){}
    void display(){cout << "Derive::display()" << endl;}
private:
    int m_b;    
};

int main(){
    Derive d(30);
    Base *pb = &d;
    pb->display();//静态绑定Base::display()
    pb->display(10);//静态绑定Base::display(int)
    cout << sizeof(Base) << typeid(pb).name() << endl; //4, class Base*
    cout << sizeof(Derive) << typeid(*pb).name() << endl;//8, class Base
    return 0;
}
  • 动态(运行时期)绑定(函数调用)

    • 如果类里面定义了虚函数,在编译阶段编译器给这个类类型产生一个唯一的vftable虚函数表,虚函数表中主要存储的内容是RTTI(Run-Time Type Information)指针和虚函数的地址

    • 当程序运行时,每一张虚函数表都会加载到内存的.rodata区

    • 一个类里面定义了虚函数,这个类定义的对象,其运行时内存中开始部分多存储一个vfptr虚函数指针,指向相应类型的虚函数表vftable。一个类型定义的多个对象指向同一个虚函数表

    • 一个类里面的虚函数个数,不影响对象内存大小,影响虚函数表大小

    • 如果子类中的函数和基类继承来的函数,返回值、函数名、参数列表都一样,而且基类函数的virtual虚函数,那么子类定义的这个函数自动处理为虚函数,覆盖从基类中继承过来的虚函数地址

class Base{
public:
    Base(int data = 10) : m_a(data){}
    virtual void display(){cout << "Base::display()" << endl;}
    virtual void display(int){cout << "Base::display(int)" << endl;}
protected:
    int m_a;
};

class Derive : public Base{
public:
    //调用基类相应的构造函数来初始化
    Derive(int data = 15) : Base(data), m_b(data){}
    void display(){cout << "Derive::display()" << endl;}
private:
    int m_b;    
};

int main(){
    Derive d(30);
    Base *pb = &d;
    //如果发现display()是虚函数,就进行动态绑定
    //mov eax, dword ptr[pb]//pb指向的子类对象前四个字节放到到eax寄存器
    //mov ecx, dword ptr[eax]
    //call ecx(虚函数地址)
    pb->display();//动态绑定,Derive::display()
    pb->display(10);//动态绑定,Base::display(int)
    cout << sizeof(Base) << typeid(pb).name() << endl; //8, class Base*
    //pb是Base类型,Base类中有没有虚函数
    //若Base没有虚函数,*pb识别的是编译时期的类型,*pb就是Base类型
    //若Base有虚函数,*pb识别的就是运行时期的RTTI类型
    cout << sizeof(Derive) << typeid(*pb).name() << endl;//12, class Derive
    return 0;
}
  • 覆盖的概念:基类和子类的函数,其返回值、函数名和参数列表都相同,而且基类的函数是虚函数,那么子类的函数就自动处理为虚函数,他们之间成为覆盖关系
  • 动态绑定:虚函数通过指针或引用变量调用,才发生动态绑定
    • 构造函数调用虚函数,静态绑定
    • 对象本身调用虚函数,静态绑定

虚析构函数

  • 虚函数依赖:
  1. 虚函数能产生函数地址,存储在vftable中
  2. 对象必须存在(对象中的vfptr -> vftable -> 虚函数地址)
  • 构造函数不能为虚函数,构造函数调用的任何函数,都是静态绑定
  • static静态成员函数不能为虚函数
  • 虚析构函数调用的时候对象是存在的
class Base{
public:
    Base(int data) : m_a(data){cout << "Base()" << endl;}
    virtual ~Base(){cout << "~Base()" << endl}
    virtual void display(){cout << "call Base::display()" << endl}
protected:
    int m_a;
};//&Base::~Base    &Base::display

class Derive : public Base{
public:
    //调用基类相应的构造函数来初始化
    Derive(int data) : Base(data), m_b(data), m_ptr(new int(data)){
        cout << "Derive()" << endl;
    }
    ~Derive(){
        delete m_ptr;
        cout << "~Derive()" << endl
    }
private:
    int m_b; 
    int *m_ptr;   
};//&Derive::~Derive    &Base::display

int main(){
    Base *pb = new Derive(20);
    pb->display();
    delete pb;//如果不定义虚析构函数,派生类的析构函数没有被调用到!!!
    //pb看到析构函数是普通函数,静态绑定,只调用Base::~Base()
    return 0;
}

多态的概念

  • 静态(编译期间)的多态:函数重载、模板(函数模板和类模板)

  • 动态(运行期间)的多态

    • 在继承结构中,基类指针(引用)指向子类对象,通过该指针(引用)调用同名覆盖函数(虚函数),基类指针指向哪个子类对象,就调用哪个子类对象的覆盖函数

    • 多态底层是通过动态绑定来实现的:基类指针指向哪个子类对象,就会访问该子类对象的vfptr指针,继续访问该子类对象的vftable,从虚函数表中调用的函数是子类对应的虚函数

class People{
public:
    People(string name) : _name(name){}
    virtual void who() = 0;//纯虚函数
protected:
    string _name;
};
class Student : public People{
public:
    Student(string name) : People(name){}
    void who(){cout << _name << "is a student" << endl;}
};
class Teacher : public People{
public:
    Teacher(string name) : People(name){}
    void who(){cout << _name << "is a teacher" <<endl;}
}
class Police : public People{
public:
    Police (string name) : People(name){}
    void who(){cout << _name << "is a police " <<endl;}
}
//全局的API,符合软件设计的“开-闭”原则:对修改关闭、对扩展开放
void who(People &people){
    people.who();
}
int main(){
    Student student("asc");
    Teacher teacher("qwe");
    Police police("asd");
    who(student);
    who(teacher);
    who(police);
    return 0;
}
  • 继承的优点:

    • 代码复用

    • 在基类中给所有的子类提供统一的虚函数接口,让子类进行重写,使用多态

抽象类

  • 问题抛出:把什么类设计成抽象类?

  • 定义基类的初衷并不是让基类抽象一个实体类型

  • 基类的好处:

    • 定义属性,子类都可以继承复用属性

    • 给所有的子类保留统一的覆盖/重写接口

  • 拥有纯虚函数的类是抽象类,抽象类不能再实例化对象,但是可以定义指针和引用变量

class Aircraft{
public:
    Aircraft(string name, double oil) : _name(name), _leftOil(oil){}
    double getLeftMiles(){
        return _leftOil*getMilesPerPounds() //动态绑定
    }
protected:
    string _name;
    double _leftOil;
    virtual double getMilesPerPounds() = 0;
};
class SmallAircraft{
public:
    SmallAircraft(string name, double oil) : Aircraft(name, oil){}
    double getMilesPerPounds(){return 20.0;}
};
class MiddleAircraft{
public:
    MiddleAircraft(string name, double oil) : Aircraft(name, oil){}
    double getMilesPerPounds(){return 15.0;}
};
class LargeAircraft{
public:
    LargeAircraft(string name, double oil) : Aircraft(name, oil){}
    double getMilesPerPounds(){return 10.0;}
};
//给外部提供一个统一的接口
void showLeftMiles(Aircraft &aircraft){
    cout << aircraft.getLeftMiles() << endl;//静态绑定
}
int main(){
    SmallAircraft sa("小飞机", 200);
    MiddleAircraft ma("中飞机", 300);
    LargeAircraft la("大飞机", 400);
    showLeftMiles(sa);
    showLeftMiles(ma);
    showLeftMiles(la);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值