继承(day09)
二十一 继承(Inheritance) ... 5 子类的构造函数和析构函数 5.1 子类的构造函数 1)如果子类构造函数没有显式指明基类子对象的初始化方式,那么该子对象将以无参方式被初始化。 2)如果希望基类子对象以有参的方式被初始化,必须在子类构造函数的初始化表中显式指明。 class 子类:public 基类{ 子类(...):基类(基类子对象构造实参表){} }; 3)子类对象的构造过程 --》分配内存 --》构造基类子对象(按继承表顺序) --》构造成员子对象(按声明顺序) --》执行子类的构造代码 5.2 子类的析构函数 1)子类的析构函数,会自动调用基类的析构函数,析构基类子对象。 2)子类对象的销毁过程 --》执行子类的析构代码 --》析构成员子对象(按声明逆序) --》析构基类子对象(按继承表逆序) --》释放内存 3)基类析构函数不能调用子类的析构函数,对一个指向子类对象的基类指针使用delete运算符,实际被执行的仅是基类的析构函数,所释放的仅是基类子对象构造时的分配的动态资源,而子类特有的动态资源将会形成内存泄露。 eg: class A{ A(void){动态资源分配} ~A(void){动态资源销毁} }; class B:class A{ B(void){动态资源分配} ~B(void){动态资源销毁} }; A* pa = new B;//pa:指向子类对象的基类指针 delete pa;//子类析构函数执行不到,内存泄露 ------------------------- 6 子类的拷贝构造和拷贝赋值 6.1 子类的拷贝构造 1)子类没有定义拷贝构造函数,编译器会为子类提供缺省拷贝构造函数,该函数会自动调用基类的拷贝构造函数,初始化基类子对象。 2)子类定义拷贝构造函数,需要使用初始化表,显式指明基类子对象也以拷贝方式进行初始化。 6.2 子类的拷贝赋值 1)子类没有定义拷贝赋值操作符函数,编译器会提供缺省拷贝赋值函数,该函数会自动调用基类的拷贝赋值函数,复制基类子对象。 2)子类定义拷贝赋值操作符函数,需要显式调用基类的拷贝赋值函数,完成对基类子对象的复制。 7 多重继承 1)一个子类继承多个基类,这样继承方式称为多重继承。 eg: 技术员 经理 \ / 技术主管 eg: 电话 播放器 计算机 \ | / 智能手机 2)向上造型时,编译器会根据各个基类子对象在子类对象中内存布局,进行适当的偏移计算,保证指针的类型和其所指向的目标对象类型一致。 3)名字冲突问题 一个子类的多个基类存在相同的名字,当通过子类访问这些名字时,编译器会报歧义错误--名字冲突。 解决名字冲突的通用做法就是显式地通过作用域限定,指明所访问的名字属于哪一个基类。 如果产生冲突的名字是成员函数,并且参数不同,也可以通过using声明,让其在子类中形成重载,通过函数重载匹配解决冲突问题。 8 钻石继承 1)一个子类的多个基类源自共同的祖先基类,这样继承结构称为钻石继承。 A(m_data) / \ B C \ / D 2)公共基类(A)子对象,在汇聚子类(D)对象中,存在多个实例。在汇聚子类中,或者通过汇聚子类对象,去访问公共基类的成员,会因为继承路径不同而导致结果不一致。 3)通过虚继承可以让公共基类(A)子对象,在汇聚子类(D)对象中的实例唯一,并且为所有子类共享,这样即使沿着不同的继承路径去访问公有基类的成员,结果也是一致的。 9 虚继承的语法 A(m_data) / \ B C(virtual) \ / D(负责构造虚基类子对象) 1)在继承表中使用virutal关键字 2)由汇聚子类的构造函数负责构造虚基类子对象 3)公共基类的所有子类,都必须在构造函数的初始化表中显式指明其初始化方式,否则编译器将会选择以无参方式初始化。 ---------------------------- 练习:薪资计算 员工 / | \ 技术员 经理 销售员 \ / \ / 技术主管 销售主管 所有员工:姓名、工号、职位等级、出勤率 经理:绩效奖金(元/月) 技术员:研发津贴(元/小时) 销售员:提成比率(x%) 薪资=基本工资+绩效工资 基本工资计算=职位等级的固定额度*出勤率(输入); 绩效工资根据具体的职位而定: 普通员工:基本工资一半 经理:绩效奖金*绩效因数(输入) 技术员:研发津贴*工作小时数*进度因数(输入) 销售员:提成比率*销售额度(输入) 技术主管:(技术员绩效工资+经理绩效工资)/2 销售主管:(销售员绩效工资+经理绩效工资)/2 结果:打印员工信息,输入必要数,计算和打印工资