C++继承

来源:互联网 发布:石油进出口数据 编辑:程序博客网 时间:2024/06/17 04:26

继承概念

继承是面向对象程序设计使代码可以复用的重要手段,在保持类原有特征的基础上,对类进行扩充,增加新的功能,以此产生一个新类,这就是继承。原有类叫做基类(父类),新产生的类叫做派生类(子类)。

继承定义格式

class 派生类名:继承方式 基类名
例如:

class Student:public Person

继承方式

类的成员访问限定符有三种:public(公有),protected(保护),private(私有),继承方式也是这三种。
这里写图片描述

  • 三种继承关系下基类成员在派生类的访问关系变化
  • 这里写图片描述

其实只要两句话就很容易记住这张表
1. 不论哪种继承方式,private成员都不可见(不能访问)
2. 列表内容成员权限不能超过继承方法权限(public>protect>private)
分析一下第二句话,因为private成员始终不可见,所以我们只用讨论public成员和protect成员在不同继承方式下的情况就好了。
如果是public继承方式,public成员和protect成员的权限都没有超过public,所以继承后类型不变,public成员仍为public,protect成员仍为protect
如果是protect继承,public成员权限超过了protect,就要变成protect类型,而protect成员没有超过protect,类型不变,仍为protect
如果是private继承,public成员和protect成员权限都超过了private,全部变为private

继承关系中构造和析构函数的调用顺序

  1. 构造函数:
    基类构造函数(按照继承列表中的顺序)——>派生类中对象构造函数(按照派生类中声明顺序)——>派生类构造函数
    假设A1,A2,B1,B2是几个不相关的类,将A1,A2作为父类继承产生一个新类C,并且将B1,B2整个作为C的成员变量,那么当我们C来实例化一个对象c时,首先调用A1和A2的构造函数(继承列表中A1在前就先调A1,A2在前就先调A2),然后是B1和B2的构造函数(先声明B1就先调B1,先声明B2就先调B2),最后才调用C自己的构造函数。
  2. 析构函数与构造函数相反:
    派生类析构函数——>派生类包含成员对象析构函数(调用顺序与成员对象在类中声明顺序相反)——>基类析构函数(调用顺序与基类在派生类列表中声明顺序相反)

继承体系的作用域

  1. 在继承体系中基类和派生类是两个不同的作用域
  2. 子类和父类中有同名成员,子类成员会屏蔽父类对成员的直接访问(在子类成员函数中,可以使用 基类::基类成员 访问)
  3. 在继承体系里最好不要定义同名的成员
class A{public:    void a()    {        cout<<'A'<<endl;    }};class B:public A{public:    void a()    {        cout<<'B'<<endl;    }};void test(){    B b;    b.a();    b.A::a();}

运行结果:

B
A
请按任意键继续…

赋值兼容规则

  1. 子类对象可以赋值给父类对象(切割/切片)
  2. 父类对象不能赋值给子类对象
  3. 父类的指针/引用可以指向子类对象
  4. 子类的指针/引用不能指向父类对象
    对象赋值

要使父类指针指向子类对象,只需让它指向子类继承父类部分的首地址,就可以用它来访问子类中从父类继承来的部分,因为父类指针只能访问父类大小的空间,所以用父类指针指向子类对象时无法利用此指针访问子类新增部分。
父类指针指向子类对象

如果用子类指针指向父类对象,子类指针会访问子类个大小的空间,就会访问到不属于这个类的空间,形成越界访问,所以不允许用子类指针指向父类对象。
子类指针指向父类空间

继承与静态成员

基类定义了static成员,则整个继承体系里都只有一个这样的成员,无论派生多少个子类,都只有一个这样的static成员实例。

单继承&多继承&菱形继承

  • 单继承
    一个子类只有一个直接父类
    单继承

  • 多继承
    一个子类有两个以上的直接父类
    这里写图片描述

  • 菱形继承
    菱形继承
    举个例子
class T{public:    int _a;};class L:public T{};class R:public T{};class D:public L,public R{};void Test(){    D d;    d._a=1;}

运行时编译器会报错:

1>—— 已启动生成: 项目: 继承, 配置: Debug Win32 ——
1>正在编译…
1>test.cpp
1>f:\vs项目\继承\继承\test.cpp(22) : error C2385: 对“_a”的访问不明确
1> 可能是“_a”(位于基“T”中)
1> 也可能是“_a”(位于基“T”中)
1>生成日志保存在“file://f:\vs项目\继承\继承\Debug\BuildLog.htm”
1>继承 - 1 个错误,0 个警告
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========

这个错误是因为d中有两份_a,编译器不知道该给哪个_a赋值,将d._a=1改为

    d.L::_a=1;    d.R::_a=0;

这样程序就可以正常运行
这里写图片描述
调试这段代码发现d中确实有两份_a,位于不同的作用域中,分别有着自己的内存地址和值。
因为菱形继承存在二义性和数据冗余的问题,所以尽量不要用菱形结构体系。

原创粉丝点击