版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1第十章继承性与派生类
C
语言程序设计案例教程12/23/20242案例一雇员类设计1.问题描述设计一个雇员类,包括基础员工、销售员工、经理以及销售经理;基础员工每月有基本工资和等级,等级上升则基本工资提升;销售员工除了基本工资以及等级和基础员工一致外,还有销售业绩工资为销售额的10%;经理除了基本工资以及等级和基础员工一致外,还有每月固定的管理基本工资;销售经理除了基本工资以及等级和基础员工一致外,还有销售业绩工资为销售额的10%和每月固定的管理基本工资。2.问题分析注意掌握本例中虚基类的声明方法、虚基类的意义、派生类对虚基类中同名函数的覆盖;注意输出时显示的多重派生情况下的虚基类和派生类构造函数以及析构函数调用次序。12/23/202433. C++ 代码#include<iostream.h>#include<string.h>#include<iomanip.h>classemployee{//虚基类protected:charname[20];intidnum;intgrade;floatsalary;
staticinttotalnum;
public:employee(char*name="");~employee();char*getname(){returnname;}inlineintgetgrade(){returngrade;}floatinlinegetsalary(){returnsalary;}inlinevoidpromote(intg){grade=g;}voidgetpay(char*name="");voiddisplay();};12/23/20244inlineemployee::employee(char*name){totalnum++;strcpy(this->name,name);idnum=totalnum;grade=0;salary=3000;cout<<this->name<<"基础员工已经聘任"<<endl;}inlineemployee::~employee(){cout<<name<<"基础员工已经离职"<<endl;totalnum--;}voidemployee::getpay(char*name){strcpy(this->name,name);salary=salary+grade*1000;cout<<"员工信息已经重新设置"<<endl;}voidemployee::display(){cout.setf(ios::left);cout<<setw(10)<<"姓名"<<setw(10)<<"编号"<<setw(10)<<"级别"<<setw(10)<<"工资"<<endl;cout<<setw(10)<<name<<setw(10)<<idnum<<setw(10)<<grade<<setw(10)<<salary<<endl;}intemployee::totalnum=1000; //静态成员初始化classsalesperson:virtualpublicemployee{ //派生类salesperson,单一继承,虚基类protected:floatsales; //当月销售额public:salesperson();~salesperson();voidgetpay(char*name="",floatsales=0);};12/23/20245inlinesalesperson::salesperson(){this->sales=0;cout<<name<<"销售员工已经聘任"<<endl;}inlinesalesperson::~salesperson(){cout<<name<<"销售员工已经离职"<<endl;}voidsalesperson::getpay(char*name,floatsales) //同名函数getpay覆盖基类employee::getpay{strcpy(this->name,name);this->sales=sales;salary=salary+grade*1000+sales*0.1;cout<<"销售员工信息已经重新设置"<<endl;}classmanager:virtualpublicemployee{ //派生类manager,单一继承employee,虚基类protected:floatmonsalary;//固定月薪public:manager();~manager();voidgetpay(char*name="");};12/23/20246inlinemanager::manager(){monsalary=5000;cout<<name<<"经理已经聘任"<<endl;}inlinemanager::~manager(){cout<<name<<"经理已经离职"<<endl;}voidmanager::getpay(char*name) //manager::getpay覆盖基类employee中的同名函数getpay{strcpy(this->name,name);salary=salary+monsalary+grade*1000;cout<<"经理信息已经重新设置"<<endl;}classsalesmanager:publicsalesperson,publicmanager{ //多重继承,虚基类public:salesmanager();~salesmanager();voidgetpay(char*name="",floatsales=0);};inlinesalesmanager::salesmanager(){salary=8000;cout<<name<<"销售经理已经聘任"<<endl;}inlinesalesmanager::~salesmanager(){cout<<name<<"销售经理已经离职"<<endl;}voidsalesmanager::getpay(char*name,floatsales) //同名函数覆盖{strcpy(this->name,name);salary=salary+monsalary+grade*1000+sales*0.1;cout<<"销售经理信息已经重新设置"<<endl;}12/23/20247voidmain(){charname[20];intgrade;floatsales;employeeA;salespersonB;managerC;salesmanagerD;//注意输出时显示的多重派生情况下的虚基类和派生类构造函数和析构函数调用次序
cout<<"请输入员工的姓名:";cin>>name;cout<<"请输入"<<name<<"的提升级别:";cin>>grade;A.promote(grade);A.getpay(name);A.display();cout<<"请输入销售员工的姓名:";cin>>name;cout<<"请输入"<<name<<"的提升级别:";cin>>grade;cout<<"请输入"<<name<<"的销售业绩:";cin>>sales;B.promote(grade);B.getpay(name,sales);B.display();cout<<"请输入经理的姓名:";cin>>name;cout<<"请输入"<<name<<"的提升级别:";cin>>grade;C.promote(grade);C.getpay(name);C.display();cout<<"请输入销售经理的姓名:";cin>>name;cout<<"请输入"<<name<<"的提升级别:";cin>>grade;cout<<"请输入"<<name<<"的销售业绩:";cin>>sales;D.promote(grade);D.getpay(name,sales);D.display();}12/23/202484.运行结果基础员工已经聘任基础员工已经聘任销售员工已经聘任基础员工已经聘任经理已经聘任基础员工已经聘任销售员工已经聘任经理已经聘任销售经理已经聘任请输入员工的姓名:zhang请输入zhang的提升级别:2员工信息已经重新设置姓名编号级别工资zhang100125000请输入销售员工的姓名:wang请输入wang的提升级别:3请输入wang的销售业绩:80000销售员工信息已经重新设置姓名编号级别工资wang1002314000请输入经理的姓名:li请输入li的提升级别:4经理信息已经重新设置姓名编号级别工资li1003412000请输入销售经理的姓名:zhao请输入zhao的提升级别:4请输入zhao的销售业绩:96000销售经理信息已经重新设置姓名编号级别工资zhao1004426600zhao销售经理已经离职zhao经理已经离职zhao销售员工已经离职zhao基础员工已经离职li经理已经离职li基础员工已经离职wang销售员工已经离职wang基础员工已经离职zhang基础员工已经离职12/23/20249上一章我们学习了类,类是进行面向对象程序设计的基础。它能够定义数据和对数据的操作,并通过不同的访问权限,将类的接口和内部实现分开,支持信息的封装和隐藏。本章中,我们将介绍继承的用法。代码复用是C++ 最重要的性能之一,它是通过类继承机制来实现的。通过类继承,我们可以复用基类的代码,并可以在继承类中增加新代码或者覆盖基类的成员函数,为基类成员函数赋予新的意义,实现最大限度的代码复用。12/23/20241010.1类的继承与派生
10.1.1继承与派生的概念现实世界中的许多事物是具有继承性的。人们一般用层次分类的方法来描述它们的关系。如图10.1是一个简单的交通工具分类图。12/23/202411图10.1简单的交通工具分类图12/23/202412在这个图中建立了一个层次结构,最高层是最普遍的,具有所有层次最一般的特征;下面每一层都比它的前一层更具体,低层含有高层的所有特性,同时也增加了一些内容,与高层有细微的不同。它们之间是基类和派生类的关系,可以说交通工具类派生了汽车类,也可以说汽车类继承了交通工具类。继承与派生是一种现象的不同说法,例如,确定某一交通工具是客车后,没有必要指出它是汽车,因为客车本身就是从汽车派生出来的,它继承了这一特性。同样也不必指出它是交通工具,因为客车都是交通工具。继承是C++ 的一种重要机制,这一机制使得程序员可以在已有类的基础上建立新类。从而扩展程序功能、体现类的多态性特征。12/23/202413面向对象程序设计允许声明一个新类作为某一个类的派生。派生类(也称子类)可以声明新的属性(成员)和新的操作(成员函数)。继承可以重用父类代码而专注于为子类编写新代码。我们称最初的类为基类,根据基类生成的新类称为派生类(子类),这种派生可以是多层次的。下面我们通过例子进一步说明为什么要使用继承。现有一个person类,它包含name(姓名)、age(年龄)、sex(性别)等数据成员与成员函数display(),如下所示:classperson{private:charname[10];intage;charsex;public:voiddisplay();};12/23/202414假如现在要声明一个employee类,它包含name(姓名)、age(年龄)、sex(性别)、department(部门)、salary(工资)等数据成员与成员函数display(),如下所示:classemployee{private:charname[10];intage;charsex;chardepartment[20];floatsalary;public:voiddisplay();};12/23/202415从以上两个类的声明中可以看出,这两个类中的数据成员和成员函数有许多相同的地方。只要在person类的基础上再增加成员department和salary,再对display()成员函数稍加修改就可以定义出employee类。现在这样定义两个类,代码重复太严重。为了提高代码的可重用性,就必须引入继承机机制,将employee类说明成person类的派生类,那些相同的成员在employee类中就不需要再定义了,简化了程序设计。//下面定义一个派生类(employee类)classemployee:publicperson{private:chardepartment[20];floatsalary;public://需要增加的其他数据成员或函数成员};12/23/20241610.1.2派生类的声明
声明一个派生类的一般格式为class派生类名:派生方式基类名{
…//派生类新增的数据成员和成员函数}这里,“派生类名”就是要生成的新的类名,新类名可由用户任意给出,只要符合标识符的命名规则即可。“基类名”是一个已经定义过的类。“派生方式”可以是关键字public、private或protected。如果使用了private,则称派生类从基类私有派生;如果使用了public,则称派生类从基类公有派生;如果使用了protected,则称派生类从基类保护派生。公有派生、私有派生和保护派生方式有以下特点:(1)公有派生:基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的(这种私有性,不同于派生类自身的私有成员,派生类的新增成员函数不能访问它,派生类可以调用继承自基类的非私有成员函数,也就是可调用基类原来的公有或派生函数来访问它,见表10.1)。(2)私有派生:基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。基类的私有成员仍然是私有的。缺省继承方式为private。(3)保护派生:基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。12/23/202417表10.1给出了这几种派生方式的访问特性。派生方式基类中的访问属性派生类中的访问属性公有派生(public)publicprotectedprivatepublicprotected不可访问私有派生(private)publicprotectedprivateprivateprivate不可访问保护派生(protected)publicprotectedprivateprotectedprotected不可访问表10.1公有派生、私有派生和保护派生的访问属性12/23/202418下面,我们给出一个实例来说明派生类对基类的访问属性。例10.1派生类对基类的访问属性。例程代码如下:#include<iostream.h>classA{public:voidfa();protected:inta1;private:inta2;};classB:publicA{public:voidfb();protected:intb1;private:intb2;};classC:publicB{public:voidfc();};12/23/202419针对该程序,我们提出如下问题:(1) B中成员函数fb()能否访问基类A中的成员:fa()、a1、a2?(2) B的对象B1能否访问A的成员?(3) C的成员函数fc()能否访问直接基类B中的成员:fb()、b1、b2?(4)派生类C的对象C1是否可以访问直接基类B的成员?能否访问间接基类A的成员:fa()、a1、a2?根据表10.1,对以上问题的回答如下:(1) B中成员函数fb()可以访问fa()、a1,不可访问a2;(2) B的对象B1可以直接访问A的成员fa(),不可访问b1、b2;(3) C的成员函数fc()可以访问fb()、b1、fa()、a1,不可访问a2、a2;(4)派生类C的对象C1可以直接访问fb()、fa(),其他的都不可以直接访问。12/23/202420下面我们分别讨论私有派生、公有派生和保护派生的一些特性。1.私有派生1)私有派生类对基类成员的访问由私有派生得到的派生类,对它的基类的公有成员只能是私有继承。也就是说基类的所有公有成员和保护性成员都只能成为私有派生类的私有成员,这些私有成员能够被派生类的成员函数访问,但是基类私有成员不能被派生类成员函数访问。下面是一个私有派生类对基类成员的访问的例子。12/23/202421例10.2私有派生类对基类成员的访问。#include<iostream.h>classBase{ //声明一个基类
intx;public:voidsetx(intn){x=n;}voiddisplayx(){cout<<x<<endl;}};classDerived:privateBase{ //声明一个私有派生类
inty;public:voidsety(intn){y=n;}voiddisplayxy(){cout<<x<<y<<endl;} //非法,派生类不能访问基类的私有成员};12/23/202422例中首先定义了一个类Base,它有一个私有数据x和两个公有成员函数setx()和displayx()。将Base类作为基类,派生出一个类Derived。派生类Derived私有继承了基类的成员,Base类的私有成员x在Derived类中不可访问,Base类的公有成员函数在Derived类中是私有的属性,可访问。如果将例中函数displayxy()改成如下形式:voiddisplayxy(){displayx();cout<<y<<endl;}则正确。可见基类中的私有成员既不能被外部函数访问,也不能被派生类成员函数访问,只能被基类自己的成员函数访问。因此,我们在设计基类时,总要为它的私有数据成员提供公有成员函数作为接口,以使派生类和外部函数可以间接使用这些数据成员。12/23/2024232)外部函数对私有派生类继承来的成员的访问私有派生时,基类的所有成员在派生类中都成为私有成员,外部函数不能访问。通过下面的例子来说明外部函数对私有派生类继承来的成员的访问特性。12/23/202424例10.3外部函数对私有派生类继承来的成员的访问特性。#include<iostream.h>classBase{
intx;public:voidsetx(intn)
{x=n;}voiddisplayx()
{cout<<x<<endl;}};classDerived:privateBase{inty;public:voidsety(intn)
{y=n;}voiddisplayy()
{cout<<y<<endl;}};main(){Derivedobj;obj.setx(10);//非法
obj.sety(20);//合法
obj.displayx();//非法
obj.displayy();//合法}12/23/202425例中派生类Derived继承了基类Base的成员。但由于是私有派生,所以基类Base的公有成员setx()和displayx()被Derived私有继承后,成为Derived的私有成员,只能被Derived的成员函数访问,不能被外界函数访问。在main()函数中,定义了派生类Derived的对象obj,由于sety()和displayy()在类Derived中是公有函数,所以对obj.sety()和obj.displayy()的调用是没有问题的,但是对obj.setx()和obj.displayx()的调用是非法的,因为这两个函数在类Derived中已成为私有成员,不能通过Derived的对象来调用,可以通过Derived的对象中设计的公有成员函数来访问基类的保护性或公有性的成员。12/23/2024262.公有派生在公有派生时,基类成员的可访问性在派生类中维持不变,基类中的私有成员在派生类中仍是私有成员,不允许外部函数和派生类中的成员函数直接访问,派生类的公有成员函数内部可以通过调用基类的公有性或基类的保护性函数来访问基类的私有成员,外部函数可以调用基类对象的公有性函数来访问基类的私有成员。基类中的公有成员和保护成员在派生类中仍是公有成员和保护成员,派生类的成员函数可以直接访问,外部函数仅可访问基类中的公有成员和派生类的公有成员。下面我们看一个有关公有派生的例子。12/23/202427例10.4声明公有派生。#include<iostream.h>classBase{intx;public:voidsetx(intn){x=n;}voiddisplayx(){cout<<x<<endl;}};classDerived:publicBase{inty;public:voidsety(intn){y=n;}voiddisplayy(){cout<<y<<endl;}};main(){Derivedobj;obj.setx(10); //合法
obj.sety(20); //合法
obj.displayx(); //合法
obj.displayy(); //合法}12/23/202428在派生类中声明的名字可以屏蔽基类中声明的同名的名字,即如果在派生类的成员函数中直接使用该名字的话,则表示使用派生类中声明的名字,例如:classX{public:intf();};classY:publicX{public:intf();intg();};voidY::g(){f();//表示被调用的函数是Y::f(),而不是X::f()}对于派生类的对象的引用,也有相同的结论,例如:Yobj;obj.f();//被调用的函数是Y::f()如果要使用基类中声明的名字,则应使用作用域运算符限定,例如:Obj.X::f();//被调用的函数是X::f()12/23/2024293.保护派生前面讲过,无论私有派生还是公有派生,派生类无权访问它的基类的私有成员,派生类要想使用基类的私有成员,只能通过调用基类的成员函数的方式来实现,也就是使用基类所提供的接口来实现。这种方式对于需要频繁访问基类私有成员的派生类而言,使用起来非常不便,每次访问都需要进行函数调用。C++ 提供了具有另外一种访问属性的成员—protected成员,该成员可以让派生类访问基类的保护成员。保护成员可以被派生类的成员函数访问,但是对于外界是隐藏的,外部函数不能访问它。保护派生时基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。下面给出保护派生的例子。12/23/202430例10.5声明保护派生。#include<iostream.h>classBase{intx;public:voidsetx(intn){x=n;}voiddisplayx(){cout<<x<<endl;}};classDerived:protectedBase{inty;public:voidsety(intn){y=n;}voiddisplayy(){cout<<y<<endl;}};main(){Derivedobj;obj.setx(10); //非法,setx(intn)在派生类对象obj中是保护性函数,外界无法访问
obj.sety(20); //合法
obj.displayx(); //非法,原因同obj.setx(10);obj.displayy(); //合法}12/23/202431本例中派生类Derived继承了基类Base的成员。但由于是保护派生,所以基类Base的公有成员setx()和displayx()被Derived保护继承后,成为Derived的保护成员,只能被Derived的成员函数访问,不能被外界函数访问。在main()函数中,定义了派生类Derived的对象obj,由于sety()和displayy()在类Derived中是公有函数,所以对obj.sety()和obj.displayy()的调用是没有问题的,但是对obj.setx()和obj.displayx()的调用是非法的,因为这两个函数在类Derived中已成为保护成员。12/23/20243210.2派生类的构造函数和析构函数
构造函数不能被继承。若派生类无构造函数,则执行其基类的默认构造函数。否则,派生类的构造函数除了对自己新的成员初始化外,还必须调用基类的构造函数来对基类的数据成员初始化。由于构造函数可以带参数,所以派生类必须根据基类的情况来决定是否需要定义构造函数。12/23/20243310.2.1构造和析构的次序
通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数;当撤销派生类的对象时,则先执先派生类的析构函数,随后再执行基类的析构函数。下列程序的运行结果,反映了基类和派生类的构造函数与析构函数的执行顺序。12/23/202434例10.6基类和派生类的构造函数和析构函数的执行顺序。#include<iostream.h>classBase{public:Base(){cout<<"基类的构造函数"<<endl;}~Base(){cout<<"基类的析构函数"<<endl;}};classDerived:publicBase{public:Derived(){cout<<"派生类的构造函数"<<endl;}~Derived(){cout<<"派生类的析构函数"<<endl;}};main(){Derivedobj;}程序运行结果如下:基类的构造函数派生类的构造函数派生类的析构函数基类的析构函数12/23/20243510.2.2派生类构造函数和析构函数的构造规则
当基类的构造函数没有参数,或没有显示定义构造函数时,派生类可以不向基类传递参数,如果派生类自身不需要初始化自身成员,甚至可以不定义构造函数。但由于派生类不能继承基类中的构造函数和析构函数,当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径。在C++ 中,派生类构造函数的一般格式为派生类构造函数名(参数表):基类构造函数名(参数表){//…}其中基类构造函数的参数,通常来源于派生类构造函数的参数表,也可以用常数值。下面给出一个派生类构造函数给基类构造函数传递参数的例子。12/23/202436例10.7派生类构造函数给基类构造函数传递参数。#include<iostream.h>classBase{intx;public:Base(inta){cout<<"基类的构造函数"<<endl;x=a;}~Base(){cout<<"基类的析构函数"<<endl;}voiddisplayx(){cout<<x<<endl;}};classDerived:publicBase{inty;public:Derived(inta,intb):Base(a)//派生类的构造函数,要缀上基类的构造函数
{cout<<"派生类的构造函数"<<endl;y=b;}~Derived(){cout<<"派生类的析构函数"<<endl;}voiddisplayy(){cout<<y<<endl;}};main(){Derivedobj(10,20);obj.displayx();obj.displayy();}12/23/202437程序运行结果如下:基类的构造函数派生类的构造函数1020派生类的析构函数基类的析构函数12/23/202438当派生类中含有对象成员时,派生类必须负责该对象成员的构造,其构造函数的一般形式为派生类构造函数名(参数表):基类构造函数名(参数表),对象成员名1(参数表),…,对象成员名n(参数表){//…}其中基类构造函数,对象成员的参数,通常来源于派生类构造函数的参数表,也可以用常数值。在定义含有对象成员的派生类对象时,构造函数执行顺序如下:(1)基类的构造函数;(2)对象成员的构造函数;(3)派生类的构造函数。12/23/202439撤销这个对象时,析构函数的执行顺序与构造函数正好相反。下面给出一个例子进一步说明含有对象成员的派生类构造函数的执行情况。例10.8含有对象成员的派生类构造函数的执行情况。#include<iostream.h>classBase{intx;public:Base(inta){cout<<"基类的构造函数"<<endl;x=a;}~Base(){cout<<"基类的析构函数"<<endl;}voiddisplayx(){cout<<x<<endl;}};classDerived:publicBase{public:Based; //d为基类对象,作为派生类的对象成员
Derived(inta,intb):Base(a),d(b) //派生类的构造函数,缀上基类构造函数和对象成员的构造函数
{cout<<"派生类的构造函数"<<endl;}~Derived(){cout<<"派生类的析构函数"<<endl;}};main(){Derivedobj(10,20);obj.displayx();obj.d.displayx();}12/23/202440程序运行结果如下:基类的构造函数基类的构造函数派生类的构造函数1020派生类的析构函数基类的析构函数基类的析构函数说明:(1)当基类构造函数不带参数时,派生类不一定要定义构造函数,然而当基类的构造函数哪怕是只带有一个参数,它所有的派生类都必须定义构造函数,甚至构造函数的函数体可能为空,仅起参数传递的作用。(2)若基类使用缺省构造函数或不带参数的构造函数,则在派生类中定义构造函数时可略去“:基类构造函数名(参数表)”,若派生类也不需要构造函数,则可以不定义构造函数。(3)每个派生类只需负责其直接基类的构造。12/23/20244110.3多重继承前面介绍的派生类只有一个基类,这种派生称为单一继承;当一个派生类具有多个基类时,这种派生方式称为多重继承。如图10.2所示就是一个多重继承的例子。该例中玩具枪有玩具和枪两个基类,因此同时具备玩具和枪的特性。图10.2多重继承的例子12/23/20244210.3.1多重继承的声明
在C++ 中,声明具有两个以上基类的派生类与声明单一继承的形式相似,其声明的一般形式如下:class派生类名:派生方式1基类名1,…,派生方式n基类名n{//派生类新定义成员};冒号后面的部分称基类表,各基类之间用逗号分割,其中派生方式缺省为private。在多重继承中,各种派生方式对于基类成员在派生类中的可访问性与单一继承的规则相同。12/23/202443例10.9声明多重派生。#include<iostream.h>classBase1{inta;public:voidsetBase1(intx){a=x;}voiddisplayBase1(){cout<<"a="<<a<<endl;}};classBase2{
intb;public:voidsetBase2(inty){b=y;}voiddisplayBase2(){cout<<"b="<<b<<endl;}};12/23/202444classDerive:publicBase1,privateBase2{intc;public:voidsetDerive(intx,inty){c=x;setBase2(y);}voiddisplayDerive(){displayBase2();cout<<"c="<<c<<endl;}};voidmain(){Deriveobj;obj.setBase1(3);obj.displayBase1();obj.setBase2(4); //非法
obj.displayBase2(); //非法
obj.setDerive(6,8);obj.displayDerive();}12/23/202445上面的程序中,类Base1和类Base2是两个基类,Derive从Base1和Base2多重派生。从派生方式看,类Derive从Base1公有派生,而从Base2私有派生。根据派生的规则,类Base1的公有成员在类Derive中仍是公有成员,类Base2的公有成员在类Derive中是私有成员,所以在main()函数中不能访问。对基类的访问必须是无二义性的。多重继承时,若基类具有同名数据成员或函数,则要防止出现二义性。例如下例就具有二义性。12/23/202446例10.10多重继承时存在二义性的情况。#include<iostream.h>classBase1{public:voiddisplay(){}};classBase2{public:voiddisplay(){}};classDerive:publicBase1,privateBase2{public:voiddisplayDerive(){}};voidmain(){Deriveobj;obj.display();//二义性错误,不知调用的是Base1的display()还是Base2的display()}12/23/202447编译时就会提示下面的错误:'Derive::display'isambiguous。那么如何避免多重继承时存在的二义性呢?使用成员名限定可以消除二义性,例如:obj.Base1::display();obj.Base2::display();12/23/20244810.3.2多重继承的构造函数多重继承构造函数的定义形式与单继承构造函数的定义形式相似,只是n个基类的构造函数之间用逗号分隔,在多个基类之间,则严格按照派生类声明时从左到右的顺序来排列先后。多重继承构造函数定义的一般形式如下:派生类构造函数名(参数表):基类构造函数名1(参数表),基类构造函数名2(参数表),…,基类构造函数名n(参数表){//派生类中其他数据成员初始化}12/23/202449例10.11多重继承构造函数。#include<iostream.h>classBase1{inta;public:Base1(intx){a=x;}voiddisplayBase1(){cout<<"a="<<a<<endl;}};classBase2{
intb;public:Base2(inty){b=y;}voiddisplayBase2(){cout<<"b="<<b<<endl;}};classDerive:publicBase1,publicBase2{intc;public:Derive(intx,inty,intz):Base1(x),Base2(y) //派生类Derive的构造函数,缀上基类//Base1和Base2的构造函数
{c=z;}voiddisplayDerive(){cout<<"c="<<c<<endl;}};12/23/202450voidmain(){Deriveobj(1,2,3);obj.displayBase1();obj.displayBase2();obj.displayDerive();}12/23/20245110.4虚基类
10.4.1虚基类及其使用的原因当引用派生类的成员时,首先在派生类自身的作用域中寻找这个成员,若没有找到,则在它的基类中寻找。若一个派生类是从多个基类派生出来的,而这些基类又有一个共同的基类,则在这个派生类中访问这个共同的基类中的成员时,可能会产生二义性。12/23/202452例10.12多重派生产生二义性的情况。#include<iostream.h>classBase{protected:inta;public:Base(){a=5;}};classBase1:publicBase{public:Base1(){cout<<"Base1a="<<a<<endl;}};classBase2:publicBase{public:Base2(){cout<<"Base2a="<<a<<endl;}};classDerived:publicBase1,publicBase2{public:Derived(){cout<<"Deriveda="<<a<<endl;}图10.3例10.12中类的层次关系图};main(){Derivedobj;}12/23/202453上述程序中,类Base是一个基类,从Base派生出类Base1和类Base2,这是两个单一继承;从类Base1和类Base2共同派生出类Derived,这是一个多重继承。类的层次关系如图10.3所示。图10.3例10.12中类的层次关系图12/23/202454上述程序是有错误的,问题出在派生类Derived的构造函数定义中,它试图输出一个它有权访问的变量a,表面上看来这是合理的,但实际上它对a的访问存在二义性,即函数中的变量a的值可能是从类Base1的派生路径上来的,也有可能是从类Base2的派生路径上来的,这里没有明确的说明。二义性检查在访问控制权限或类型检查之前进行,访问控制权限不同或类型不同不能解决二义性问题。为了解决这种二义性问题,C++ 引入了虚基类的概念。12/23/20245510.4.2虚基类的定义
在例10.12中,如果类Base只存在一个拷贝,那么对a的引用就不会产生二义性。在C++ 中,如果想使这个公共的基类只产生一个拷贝,则可以将这个基类说明为虚基类。这就要求从类Base派生新类时,使用关键字virtual引出。我们在下面的例子中用虚基类重新定义例10.12中的类。12/23/202456例10.13定义虚基类。#include<iostream.h>classBase{protected:inta;public:Base(){a=5;}};classBase1:virtualpublicBase{//声明Base为虚基类public:Base1(){cout<<"Base1a="<<a<<endl;}};classBase2:virtualpublic
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024版店铺转租合同模板
- 二零二五年度公共设施工装采购合同规范3篇
- 大学会计数学试卷
- 2025年度智能化班组承包经营合作协议范本3篇
- 2025版智能制造产业链合作协议工厂协同发展
- 八年级下册开学数学试卷
- 2024年版新能源汽车充电设施建设合同
- 数学类专业就业能力展示
- 血卟啉病的健康宣教
- 2025年鲁教版九年级生物下册阶段测试试卷
- 精神科护理岗位竞聘
- 广西北海市2023-2024学年八年级(上)期末数学试卷
- 非急救转运合同范例
- 车辆使用安全培训
- 肺结核的护理个案
- 陕西省汉中市2024-2025学年高一上学期12月第二次月考地理试题(含答案)
- AutoCAD2024简明教程资料
- 《中国传统文化》课件模板(六套)
- 民航客舱服务管理Ⅱ学习通超星期末考试答案章节答案2024年
- 儿科主任年终总结
- 2023年上海市录用公务员考试真题
评论
0/150
提交评论