C++继承与派生总结

面向对象程序设计基于数据抽象、继承、动态绑定这三个基本概念。

  • OOP的核心思想:多态性(polymorphism)
  • 继承(inheritance)
  • 基类(base class)
  • 直接基类(direct base)
  • 间接基类(indirect base)
  • 派生类(derived class)
  • 虚函数(virtual function) 其解析过程发生在运行时,而不是编译时
  • 类派生列表(class derivation list)
  • 动态绑定(dynamic binding) 使用基类的引用或指针调用一个虚成员函数时会执行动态绑定,使用指针调运虚成员函数时,根据指针所指向的实际对象来 。 在运行时选择函数的版本
  • 覆盖(override)
  • 访问说明符(public, protected, private):控制派生类从基类继承而来的成员是否对派生类的用户可见
  • 共有(public)继承时,才能把派生类型的对象绑定到基类的引用或指针上
  • 在形参列表、const修饰符、引用限定符后面添加override关键字,来显示地注明它是覆盖了它继承的虚函数
  • static 成员在继承体系中唯一存在
  • 派生类的作用域嵌套在基类的作用域中
  • final类名后添加final关键字,禁止进程该类
  • 先构造基类再构造派生类,先析构派生类再析构基类
  • 构造函数不能继承,C++11中可用using Base::Base;声明继承构造函数(生成相同的构造函数)
    • 该using声明不会改变构造函数原有的访问基本
    • 默认实参不会被继承,而是获得多个继承的构造函数,每个构造函数省略掉几个有默认实参的形参

类型转换

  • 静态类型(static type)
    • 编译时已知的,指针/引用对应的类型
    • 决定那些成员是可见的
  • 动态类型(dynamic type)
    • 内存中对象的实际类型,运行时可知
    • 决定调用的版本
  • 如果表达式不是引用或指针,则它的动态类型和静态类型一致
  • 不存在从基类到派生类的隐式类型转换
    • 当基类含有一个或多个虚函数时,可使用dynamic_type<T&>( )/dynamic_type<T*>( )对尝试将 基类指针/引用 转换 派生类指针/引用
    • dynamic_type<T&>(t) 转换引用失败时抛出bad_cast
    • dynamic_type<T*>(t) 转换指针失败时返回空指针
  • 自动类型转换只对指针和引用有效,在派生类类型和基类类型之间不存在
  • 切掉(sliced down): 用派生类对象为一个基类对象初始化或赋值时,只有该派生类的对象中的基类部分会被拷贝、移动、赋值。

虚函数

  • 基类中虚函数的返回值类型为类本身的引用或指针时,(派生类到基类的类型转换时可访问的时)派生类中的该虚函数可以与基类中的返回值不同,可返回自己的指针/引用。
  • final修饰符(位于const 、引用修饰符后):禁止该虚函数被覆盖。
  • 使用作用域运算符指定版本
  • 纯虚函数 =0
  • 含有纯虚函数的类是抽象基类(abstract base class)不能创建其对象

访问控制

  • 派生类的成员或友元只能通过派生类对象来访问基类的protected成员
  • 派生说明符只影响派生类的用户,对派生类无影响。
  • 只有在可以访问基类的共有成员时,派生类向基类的类型的转型才是可访问的。
  • 派生类可以使用using声明改变其可访问的基类成员在该基类中的访问权。
  • !!名字查找优先于类型检查,即使派生类成员和基类成员的形参列表不一致,同名基类成员也会被隐藏(要么不隐藏,要么全部隐藏)
    • 可以使用using声明,将基类中的名字添加到该作用域,避免全部覆盖掉
class B{
    public:
        void func(int a){ }

        void func(){ }
};

class D : public B{
    public:
        //加上:using B::func;  才能d.func();
        void func(int a){ }
};

int main(){
    D d;
    d.func();   //错误无法访问
}

基类通常应该定义一个虚析构造函数

  • 这样才能在“delete基类指针”时执行正确的析构函数
  • 一个基类总是需要构造函数,并将其设为虚函数
  • 虚析构函数将阻止合成移动操作(一个类定义了析构函数,即使它通过=default,也不会自动合成移动操作)
  • 基类没有移动操作意味着派生类也没有

拷贝控制

派生类的拷贝和移动构造函数/赋值运算符在拷贝和移动自己的成员时,也要同时拷贝和移动基类部分的成员

合成的拷贝控制

  • 派生类的合成的拷贝赋值/构造函数、移动复制运算符/构造函数,会先调用其父类的对应成员复制其父类部分
  • 基类中
    • 默认构造函数、拷贝复制/构造函数、析构函数 被删除/不可访问,则派生类中的对应成员也被删除
    • 有不可访问/删除的析构函数, 则派生类中默认/拷贝构造函数将被删除
    • 用=default请求合成移动操作时,如果基类对应操作是删除/不可访问的,则请求不能完成,该操作还是删除的

多重继承

  • 基类的构造顺序与派生列表中基类的出现顺序保持一致
  • 析构顺序正好与构造顺序相反
  • 如果从多个基类继承了相同的构造函数(形参相同),将产生错误。这个类必须为该构造函数定义它自己的版本。
  • 派生类如果定义自己的拷贝/赋值构造和赋值运算符,必须在完整的对象上执行拷贝、赋值或移动,不要忘记处理其基类的赋值(直接基类和间接基类)。派生类的合成版本会自动完成对其基类部分的操作。
  • 任意的public继承的基类的指针可以指向一个派生类对象(都一样好)。
  • 指针和引用的静态类型决定了通过它能够访问那些成员。
  • 派生类的作用域嵌套在直接基类和间接基类的作用域中,名字的查找将由内向外。

虚继承

  • 派生类可以通过它的两个间接基类分别继承同一个间接基类;或直接继承+间接继承一个基类
  • 如果一个类在继承过程中出现多次,则派生类中将包含该类的多个子对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fw51XO3m-1624371512608)(…/virual_drive.png)]

#include<iostream>
using namespace std;


class Base0{
    public:
        int a;
};

class Base1 : public Base0{
    public:
        int b;
};

class Base2 : public Base0{
    public:
        int b;
};

class Foo : public Base1, public Base2{
    public:
       int c;
};

int main() {
    Foo f;
    f.a;            //错误
    f.b;            //错误
    Base0& b = a;   //错误
}
  • 虚继承解决上述问题,令某个类共享它的基类。共享的基类子对象称为虚基类(virtual base class),不论虚基类在继承体系中出现了多少次,在派生类中都只包含唯一一个共享的虚基类子对象。
#include<iostream>
using namespace std;

class Base0{
    public:
        Base0(int a):a(a){ }
        int a;
};

class Base1 : virtual public Base0{
    public:
        Base1(int a, int b):Base0(a), b(b){ }
        int b;
};

class Base2 : virtual public Base0{
    public:
        Base2(int a, int b):Base0(a), b(b){ }
        int b;
};

class Foo : public Base1, public Base2{
    public:
        //Base1,Base2构造时传入的0,将被忽略,基类Base0需在构造函数初始化列表中初始化(否则默认初始化)
        Foo(int a, int b1, int b2, int c):Base0(a), Base1(0, b1), Base2(0, b2), c(c){ }
        int c;
};

int main() {
    Foo f;
    f.a;            //正确
    f.b;            //错误
    Base0& b = f;   //正确
}
  • Foo对象只包含一个Base0的子对象
  • 虚基类之影响从指定了虚基类的派生类中进一步派生出的类,不影响派生类本身
  • 虚派生中,基类由最底层的派生类初始化。需在最底层的派生类的构造函数初始化列表中初始化(或者默认初始化),其他的基类的初始化列表中对其的初始化将被忽略。
  • 含有虚基类的对象的构造顺序:
    • 先构造虚基类,最底层派生类构造函数构造提供初始值构造虚基类(或默认初始化)
    • 有多个虚基类,按派生列表中出现的顺序构造
    • 再按照基类在派生列表中出现的次序初构造
  • 合成的拷贝和移动函数、赋值运算符也按该顺序
  • 销毁顺序与构造顺序相反
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

绫零依

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

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

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

打赏作者

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

抵扣说明:

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

余额充值