第四章 派生类与继承

4.1 派生类的概念

4.1.1 为什么要使用派生

(1)许多事物都既有继承性,确定基类从而产生派生类
(2)从先辈那里得到属性和行为特征。
从已有类产生新类的过程就是类的派生,新类从已有类那里得到已有的特征就是类的继承;
如:人类、职员、教师工作者、教师管理人员;

4.1.2 派生类的声明

一般格式:
class 派生类名:[继承方式] 基类名{
派生类新增的数据成员和成员函数
};
分类:公有继承、私有继承、保护继承;

4.1.3 派生类的构成

(1)派生类从基类中接受成员(除构造函数和析构函数之外);
(2)调整从基类接受来的成员(访问属性,对基类的成员重新定义,就近访问原则);
(3)在派生类中增加新的成员;
一般需要在派生类中定义新的构造函数和析构函数;

4.1.4 基类成员在派生类中的访问属性

在这里插入图片描述

4.1.5 派生类对基类成员的访问规则

1.私有继承的访问规则
在这里插入图片描述
2.公有继承的访问规则
在这里插入图片描述
3.保护继承的访问规则
在这里插入图片描述

4.2 派生类的构造函数和析构函数

4.2.1 派生类构造函数和析构函数的执行顺序

基类的构造函数->派生类的构造函数-> 派生类的析构函数->基类的析构函数

4.2.2派生类构造函数和析构函数的构造规则

1.简单的派生类的构造函数
派生类不能继承基类中的构造函数和析构函数
当基类含有带有餐宿的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类的构造函数的途径;
在C++中,派生类的构造函数的一般格式为:

派生类名(参数总表):基类名(参数表){
    派生类新增数据成员初始化语句
}

注意派生类构造函数首行写法:

UStudent (int number1, string name1, float score1, string major1):Student(nunber1, name, score1)
{}
声明一个类时:
UStudent stu(22116, "张志", 95, "信息安全");

说明:
(1)派生类的构造函数定义在类的外部,而类体内只写该函数的声明。
如:
UStudent(int number1, string name1, float score1,string major1);
声明时不包括基类构造函数名及其参数表,只在类外定义构造函数时才将它列出;
(2)基类使用默认构造函数或不带参数的构造函数,可省略:后的部分;
(3)当基类构造函数不带参数时,派生类不一定需要定义构造函数;
当然,基类的构造函数只带一个参数,它所有的派生类都必须定义构造函数,甚至所定义的派生类构造函数体可能为空,仅仅起到参数传递的作用;

2.派生类的析构函数
由于析构函数是不带参数的,在派生类中是否需要自定义构造函数与它所属基类的析构函数无关。
在执行派生类的构造函数时,系统会自动调用基类的析构函数,对基类的对象进行清理;

含有对象成员(子对象)的派生类的构造函数
派生类中含有内嵌的对象成员,其构造函数形式

派生类名(参数总表):基类名(参数表0),对象成员名1(参数表1),……, 对象成员名n(参数表n)
{
   派生类新增成员的初始化语句
}

执行顺序如下:
①调用基类的构造函数,对基类数据成员初始化;
②调用内嵌对象的构造函数,对内嵌对象成员的数据初始化;
③执行派生类的构造函数体,对派生类数据成员初始化;

撤销对象时,析构函数的调用顺序与构造函数的调用方式正好相反
①执行派生类的析构函数
②执行内嵌对象成员的析构函数
③执行基类的析构函数

说明:
(1)在派生类中含有多个内嵌对象成员时,调用内嵌对象的构造函数顺序由它们在类中声明的顺序确定;
(2)如果派生类的基类也是一个派生类,每个派生类只需负责其直接激烈数据成员的初始化,依次上溯;

4.3 调整基类成员在派生类中的访问属性

4.3.1 同名成员

派生类可以重新说明与基类成员同名的成员, 其覆盖了基类的同名成员;
为了在派生类中使用基类的同名成员,必须在该成员名前加上基类名和作用域标识符“::”
基类名::成员名;

这种覆盖的方法,是对基类成员改造的关键手段,是程序设计中经常使用的方法。

4.3.2 访问声明

访问声明
例如:

class A{
private.....
public:
  int x1;
  print(){
  }
};
class B:private A{
private:
....
public:
  A::x1;
  A::printf;      // 把基类中的x1,print
  .....
};
 

(2)访问声明中只含不带类型和参数的函数名或变量名;
把上面的访问声明称
A::print();
void A::print;
void A::print();
都是错误的;

应声明为A::print;

(3)访问声明不能改变成员在基类中的访问属性,访问声明只能把原基类的保护成员调整为私有派生类的保护乘员,把原基类的公有成员调整为私有派生类的公有成员,但对基类的私有成员不能使用访问声明;

4.4 多重派生

4.4.1 多重继承派生类的声明

class 派生类名:继承方式1 基类名1,……,继承方式n 基类名n{
派生类新增数据成员和成员函数
};
如不写,默认private为继承方式;

说明:对积累成员的访问必须是无二义的,要必须想办法消除二义性;

如:
obj.X::f();
obj.Y::f();

4.4.2 多重继承派生类的构造函数与析构函数

派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),……,基类名n(参数表n)
{派生类新增成员初始化语句}

4.4.3 虚基类

1.为什么要引入虚基类

如果一个类由多个直接基类,而这些直接基类又有共同的基类,则在最底层的派生类中会保留这个间接的共同基类数据成员的多份同名成员。
在访问这些同名成员时,必须在派生类对象名后增加直接基类名,使其唯一标识一个成员,以免产生二义性;
为了解决二义性,引入虚基类的概念

2.虚基类的概念

class 派生类名:virtual 继承方式 基类名{
....
};

经过这样的声明,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类成员只保留一次。

3.虚基类的初始化
其初始化与一般的多继承的初始化在语法上是一样的,但构造函数的调用顺序不同;
(1)虚基类中定义有带形参的构造函数,没有定义默认形式的构造函数,则直接或间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用,以初始化在虚基类中定义的数据成员;
(2)一个对象含有从虚基类继承来的成员,则虚基类成员是由最远派生类的构造函数调用虚基类的构造函数进行初始化;其它的构造函数的调用都自动被忽略;
(3)若在同一层次上,先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类的构造函数;
(4)对于多个虚基类,构造函数从左后右,自上而下;
(5)对于非虚基类,构造函数从左后右,自上而下;
(6)若虚基类由非虚基类派生而来,仍先调用基类的构造函数,再调用派生类的构造函数;

说明:
(1)关键字virtual与派生方式关键字的先后顺序无关紧要;
(2)一个基类可以作为某些派生类的虚基类的同时,又可作为另一些派生类的非虚基类;

4.5 基类与派生类对象之间的赋值兼容关系

(1)派生类对象可以向基类对象赋值;

Base b;             //基类
Derived d;          //派生类
b = d;              //赋值

(2)派生类对象可以初始化基类对象的引用;

Base B;            //定义基类对象
Derived d;         //定义派生类对象
Base &br = d;      //定义基类对象的引用并用派生类对象
                   //对其初始化

(3)派生类对象的地址可以赋值给基类对象的指针

Derived d;           //定义派生类对象
Base *bp = &d;       //把派生类对象的地址赋值给基类指针
                     //也就是说把指向基类对象的指针指向派生类对象

(4)如果函数的形参是基类对象或者是基类对象的引用,在调用函数时,可以用派生类对象作为实参;

class Base{
 public:
    int i;
    ....
};
class Derived:public Base{
};
void fun (Base &bb){
  cout << bb.i << endl;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ChitGPT: 好的,您的问题是关于面向对象编程的实现。以下是建立基类building的代码,您可以在此基础上进行派生类的设计。 ```python class Building: def __init__(self, floors, rooms, square_feet): self.floors = floors self.rooms = rooms self.square_feet = square_feet ``` 现在,您可以建立第一个派生类house来继承building。 ```python class House(Building): def __init__(self, floors, rooms, square_feet, bedrooms, bathrooms): super().__init__(floors, rooms, square_feet) self.bedrooms = bedrooms self.bathrooms = bathrooms ``` 再建立一个派生类office来继承building,并存储灭火器与电话的数目。 ```python class Office(Building): def __init__(self, floors, rooms, square_feet, fire_extinguishers, telephones): super().__init__(floors, rooms, square_feet) self.fire_extinguishers = fire_extinguishers self.telephones = telephones ``` 最后,您可以创建一个主函数,来测试以上类的用法。示例代码如下: ```python def main(): house1 = House(2, 4, 2000, 3, 2) office1 = Office(3, 6, 3000, 10, 20) print("House: {}\nBedrooms: {}\nBathrooms: {}".format(house1.square_feet, house1.bedrooms, house1.bathrooms)) print("Office: {}\nFire Extinguishers: {}\nTelephones: {}".format(office1.square_feet, office1.fire_extinguishers, office1.telephones)) if __name__ == "__main__": main() ``` 这段代码将创建一个两层四室的面积为2000平方英尺的房子,以及一个三层六室的面积为3000平方英尺的办公室。然后,它会输出每个建筑物的详细信息。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值