继承与派生.docx
《继承与派生.docx》由会员分享,可在线阅读,更多相关《继承与派生.docx(16页珍藏版)》请在冰豆网上搜索。
继承与派生
第九章继承与派生类
9.1知识要点
1.1. 掌握继承和派生的定义,派生类的定义方法。
(1)
(1)掌握继承的两种类型:
单继承和多继承。
(2)
(2)掌握private,public,protected三种继承方式的特点。
继承方式决定了基类中的成员在派生类中的属性。
三种继承方式的共同点:
基类的private成员在派生类中不可见。
区别:
对于私有继承,基类的public、protected成员在派生类中作为private成员;对于公有继承,基类的public、protected成员在派生类中访问属性不变;对于保护继承,基类的public、protected成员在派生类中作为protected成员。
(3)(3)掌握派生类中的构造函数和析构函数的使用。
基类的构造函数和析构函数不能继承,所以必要时在派生类中定义自己的构造函数和析构函数。
派生列的构造函数完成基类中新增数据成员和基类数据成员的初始化,基类数据成员的初始化通过基类构造函数来实现。
(4)(4)掌握派生类的同名覆盖规则。
(5)(5)掌握赋值兼容规则。
基类对象可以使用公有派生类对象来代替,包括:
派生类对象可以赋值给基类对象;派生类对象可以初始化基类对象的引用;基类类型指针可以指向派生类对象。
2.2. 掌握多重继承的概念、定义方法、多重继承派生类构造函数的执行顺序。
派生类构造函数的执行顺序是先执行所有基类的构造函数(顺序按照定义派生类时指定的各基类顺序),在执行对象成员所在类的构造函数(顺序按照他们在类中的声明顺序),最后执行派生类构造函数体中的内容。
3.3. 掌握虚基类的概念和定义方法。
在多重继承中,如果多条继承路径上有一个公共的基类,则在这些路径的汇合点上的派生类会产生来自不同路径的公共基类的多个拷贝,如果用virtual把公共基类定义成虚基类,则只会保留公共基类的一个拷贝。
9.2典型例题分析与解答
例题1:
下列对派生类的描述中,()是错误的。
A.A. 一个派生类可以作为另一个派生类的基类
B.B. 派生类至少有一个基类
C.C. 派生类的成员除了它自己的成员外,还包含了它的基类成员
D.D.派生类中继承的基类成员的访问权限到派生类保持不变
答案:
D
分析:
一个派生类可以作为另一个派生类的基类。
无论是单继承还是多继承,派生类至少有一个基类。
派生类的成员除了它自己的成员外,还包含了它的基类成员。
派生类中继承的基类成员的访问权限到派生类受继承方式影响的,对于私有继承,基类的public,protected成员在派生类中作为private成员;对于公有继承,基类的public,protected成员在派生类中访问属性不变;对于保护继承,基类的public、protected成员在派生类中作为protected成员。
例题2:
派生类的对象对它的哪一类基类成员是可以访问的?
()
A.公有继承的基类的公有成员B.公有继承的基类的保护成员
C.公有继承的基类的私有成员D.保护继承的基类的公有成员
答案:
A
分析:
公有继承的基类的公有成员在派生类中保持公有访问权限,所以派生类对象可以访问它;公有继承的基类的保护成员在派生类中保持保护访问权限,所以派生类对象不可以访问它;基类的私有成员不能被派生到派生类中,所以派生类对象不可以访问它;保护继承的基类的公有成员在派生类中变成保护的访问权限,所以派生类对象不可以访问它。
例题3:
关于多继承二义性的描述,()是错误的。
A.A.派生类的多个基类中存在同名成员时,派生类对这个成员访问可能出现二义性
B.B. 一个派生类是从具有共同的间接基类的两个基类派生来的,派生类对该公共基类的访问可能出现二义性
C.C. 解决二义性最常用的方法是作用域运算符对成员进行限定
D.D. 派生类和它的基类中出现同名函数时,将可能出现二义性
答案:
D
分析:
出现二义性有两种情况:
调用不同基类的相同成员时可能出现二义性;访问共同基类的成员时可能出现二义性。
消除二义性的方法是采用作用域运算符。
派生类和它的基类中出现同名函数时,不可能出现二义性。
例题4:
多继承派生类构造函数构造对象时,()被最先调用。
A.派生类自己的构造函数B.虚基类的构造函数
C.非虚基类的构造函数 D.派生类中子对象类的构造函数
答案:
B
分析:
多继承派生类构造函数构造对象时,构造函数的调顺序是:
虚基类的构造函数,派生类中子对象类的构造函数,派生类自己的构造函数。
例题5:
C++类体系中,能被派生类继承的是()。
A.构造函数B.虚函数C.析构函数D.友元函数
答案:
B
分析:
C++类体系中,构造函数、析构函数和友元函数是不能被派生类继承的.
例题6:
设有基类定义:
classCbase
{private:
inta;
protected:
intb;
public:
intc;
};
派生类采用何种继承方式可以使成员变量b成为自己的私有成员()
A.私有继承B.保护继承
C.公有继承D.私有、保护、公有均可
答案:
A
分析:
私有继承时,基类的protected成员在派生类中作为private成员。
例题7:
C++将类继承分为(1)和(2)两种。
答案:
(1)单继承 (2)多继承
分析:
派生类可以只从一个基类中派生,也可以从多个基类中派生。
从一个基类中派生的继承方式称为单继承。
从多个基类中派生的继承方式称为多继承。
例题8:
派生类可以定义其_______________中不具备的数据和操作。
答案:
基类
分析:
派生类是从基类派生的,派生类包含了它所有基类的除构造函数、析构函数之外的所有成员,同时还拥有它自己的新成员。
例题9:
派生类构造函数的初始化列表中包含____________________________。
答案:
初始化基类数据成员、新增内嵌对象数据及新增一般成员数据所需要的全部参数。
分析:
在创建派生类对象时,不仅要给派生类中说明的数据成员初始化,而且还要给基类中说明的数据成员初始化。
由于构造函数不能被继承,因此,派生类的构造函数必须通过调用基类的构造函数来初始化基类中的数据成员。
所以,在定义派生类的构造函数中,不仅要包含对自己数据成员进行初始化,还要包含调用基类的构造函数对基类中数据成员进行初始化。
如果派生类中还有子对象时,还应包含调用对子对象初始化的构造函数。
例题10:
在继承机制下,当对象消亡时,编译系统先执行
(1)的析构函数,然后才执行
(2)的析构函数,最后执行(3)的析构函数。
答案:
(1)派生类
(2)派生类中子对象类 (3)基类
分析:
派生类的析构函数的执行次序与构造函数正好相反,先调用派生类的析构函数,再调用派生类中子对象类的析构函数,最后调用基类的析构函数。
例题11:
设有以下类的定义:
classAclassB:
protectedAclassC:
privateB
{intA1;{intb1;{intc1;
protected:
intA2;protected:
intb2;protected:
intc2;
public:
intA3;public:
intb3;public:
intc3;
};};};
请按访问权限写出派生类C中具有的成员。
私有成员:
(1)
保护成员:
(2)
公有成员:
(3)。
答案:
(1)c1、b2、b3、A2、A3
(2)c2(3)c3
分析:
B类有它自己的私有成员b1、保护成员b2和公有成员有b3,另外B类是以保护方式从A类中派生出来的,所以A类保护成员A2和公有成员A3在B类中都变成保护类型的;C类有自己的私有成员c1、保护成员c2和公有成员有c3,C类是以私有方式从B类中派生出来的,所以B类中的b2、b3、A2和A3在C类中都变成私有的访问方式。
例题12:
指出并改正下面程序中的错误。
#include
classPoint
{intx,y;
public:
Point(inta=0,intb=0){x=a;y=b;}
voidmove(intxoffset,intyoffset){x+=xoffset;y+=yoffset;}
intgetx(){returnx;}
intgety(){returny;}
};
classRectangle:
protectedPoint
{intlength,width;
public:
Rectangle(intx,inty,intl,intw):
Point(x,y)
{length=l;width=w;}
intgetlength(){returnlength;}
intgetwidth(){returnwidth;}
};
voidmain()
{Rectangler(0,0,8,4);
r.move(23,56);
cout<<}
分析:
保护继承方式使基类的public成员在派生类中的访问属性变为protected,所以派生类Rectangle的对象r不能直接访问基类的成员函数move()、getx()和gety()。
其改正方法有两种:
1)将Rectangle的继承方式改为公有继承public;2)在Rectangle类中重定义move(),getx()和gety()函数,覆盖基类的同名函数。
voidRectangle:
:
move(intxoffset,intyoffset){Point:
:
move(xoffset,yoffset);}
voidRectangle:
:
getx(){returnPoint:
:
getx();}
voidRectangle:
:
gety(){returnPoint:
:
gety();}
例题13:
指出并改正下面程序中的错误。
#include
classA
{public:
intx;
A(inta=0){x=a;}
voiddisplay(){cout<<"A.x="<};
classB
{public:
intx;
B(inta=0){x=a;}
voiddisplay(){cout<<"B.x="<};
classC:
publicA,publicB
{inty;
public:
C(inta,intb,intc):
A(a),B(b)
{y=c;}
intgety(){returny;}
};
voidmain()
{Cmyc(1,2,3);
myc.x=10;
myc.display();
}
分析:
类A、B中有同名公有数据成员x和同名成员函数display(),在主函数中访问对象myc的数据成员x是无法确定是访问从A中继承的还是从B中继承的x;调用成员函数也是如此,无法确认是调用类A中的还是类B中的,产生二义性。
改正方法,可以用作用域区分符加以限定,如改成myc.A:
:
x=10;myc.A:
:
display();或myc.B:
:
x=10;myc.B:
:
display();
例题14:
看程序写结果
#include
classBase
{inti;
public:
Base(intn){cout<<"Constuctingbaseclass"<~Base(){cout<<"Destructingbaseclass"<voidshowi(){cout<
intGeti(){returni;}
};
classDerived:
publicBase
{intj;
Baseaa;
public:
Derived(intn,intm,intp):
Base(m),aa(p){
cout<<"Constructingderivedclass"<j=n;
}
~Derived(){cout<<"Destructingderivedclass"<voidshow(){Base:
:
showi();
cout<};
voidmain()
{Derivedobj(8,13,24);
obj.show();
}
说明:
派生类的构造函数的执行次序,先调用基类的构造函数,再调用派生类中子对象类的构造函数,最后调用派生类的构造函数。
析构函数的执行次序与构造函数正好相反,先调用派生类的析构函数,再调用派生类中子对象类的析构函数,最后调用基类的析构函数。
运行结果:
Constuctingbaseclass
Constuctingbaseclass
Constructingderivedclass
13,8,24
Destructingderivedclass
Destructingbaseclass
Destructingbaseclass
例题15:
看程序写结果
#include
classA
{public:
A(char*s){cout<
~A(){}
};
classB:
virtualpublicA
{public:
B(char*s1,char*s2):
A(s1)
{cout<};
classC:
virtualpublicA
{
public:
C(char*s1,char*s2):
A(s1)
{
cout<}
};
classD:
publicB,publicC
{
public:
D(char*s1,char*s2,char*s3,char*s4):
B(s1,s2),C(s1,s3),A(s1)
{
cout<}
};
voidmain()
{
D*p=newD("classA","classB","classC","classD");
deletep;
}
说明:
创建D对象时,只有在D的构造函数的初始化列表中列出的虚基类构造函数被调用,D的两个基类B、C的构造函数中的虚基类构造函数被忽略,不执行,从而保证在D对象中只有一个虚基类子对象。
运行结果:
classA
classB
classC
classD
例题16:
建立一个基类Building,用来存储一座楼房的层数、房间数以及它的总平方英尺数。
建立派生类Housing,继承Building,并存储下面的内容:
卧室和浴室的数量,另外,建立派生类Office,继承Building,并存储灭火器和电话的数目。
然后,编制应用程序,建立住宅楼对象和办公楼对象,并输出它们的有关数据。
程序代码:
#include
classBuilding
{public:
Building(intf,intr,doubleft)
{floors=f;
rooms=r;
footage=ft;
}
voidshow()
{cout<<"floors:
"<cout<<"rooms:
"<cout<<"totalarea:
"<}
protected:
intfloors;
introoms;
doublefootage;
};
classHousing:
publicBuilding
{public:
Housing(intf,intr,doubleft,intbd,intbth):
Building(f,r,ft)
{bedrooms=bd;
bathrooms=bth;
}
voidshow()
{cout<<"\nHOUSING:
\n";
Building:
:
show();
cout<<"bedrooms:
"<cout<<"bathrooms:
"<}
private:
intbedrooms;
intbathrooms;
};
classOffice:
publicBuilding
{
public:
Office(intf,intr,doubleft,intph,intex):
Building(f,r,ft)
{phones=ph;
extinguishers=ex;
}
voidshow()
{cout<<"\nHOUSING:
\n";
Building:
:
show();
cout<<"phones:
"<cout<<"extinguishers:
"<}
private:
intphones;
intextinguishers;
};
voidmain()
{Housinghob(5,7,140,2,2);
Officeoob(8,12,500,12,2);
hob.show();
oob.show();
}
9.3教材习题分析与解答
1.选择题
(1)C++中的类有两种用法:
一种是类的实例化,即生成类对象,并参与系统的运行;另一种是通过()派生了新的类。
A.复用B.继承C.封装D.引用
答案:
B
(2)继承具有(),即当基类本身也是某一个类派生类时,底层的派生类也会自动继承间接基类的成员。
A)规律性B.传递性C.重复性D.多样性
答案:
B
(3)下列对基类和派生类关系的描述中,错误的是()。
A.派生类是基类的具体化B.派生类是基类的子集
C.派生类是基类定义的延续D.派生类是基类的组合
答案:
B
(4)下列对派生类的描述中,错误的是()。
A.A. 一个派生类可以作为另一个派生类的基类
B.B. 派生类至少有一个基类
C.C. 派生类的缺省继承方式是private
D.D. 派生类只含有基类的公有成员和保护成员
答案:
D
(5)下列对继承的描述中,错误的是()。
A.A. 析构函数不能被继承
B.B. 派生类也是基类的组合
C.C. 派生类的成员除了它自己的成员外,还包含了它的基类的成员
D.D. 派生类中继承的基类成员的访问权限到派生类保持不变
答案:
D
(6)派生类的对象对它的基类成员中()是可以访问的。
A.公有继承的公有成员B.公有继承的私有成员
C.公有继承的保护成员D.私有继承的公有成员
答案:
A
(7)下列说法错误的是()。
A.A. 公有继承的基类中的public成员在派生类中仍是public的
B.B. 公有继承的基类中的private成员在派生类中仍是private的
C.C. 私有继承的基类中的public成员在派生类中变成private的
D.D. 保护继承的基类中的public成员在派生类中变成protected的
答案:
B
(8)下面叙述错误的是(A)。
A.A. 基类的protected成员在派生类中仍然是protected
B.B. 基类的protected成员在public派生类中仍然是protected的
C.C. 基类的protected成员在private派生类中是private的
D.D. D. 基类的protected成员不能被派生类的对象访问
答案:
A
(9)C++类体系中,不能被派生类继承的有()。
A.构造函数B.虚函数C.静态成员函数D.赋值操作函数
答案:
A
(10)设置虚函数的声明中,正确的是()。
A.简化程序B.消除二义性C.提高运行效率D.减少目标代码
答案:
B
(11)下列虚基类的声明中,正确的是(D)。
A.classvirtualB:
publicAB.virtualclassB:
publicA
C.classB:
publicAvirtualD.classB:
virtualpublicA
答案:
D
(12)在下面的表达式中,不表示虚继承的是()。
A.virtualpublicB.publicvirtualC.publicD.virtual
答案:
C
2. 比较类的三种继承方式public(公有继承)、protected(保护继承)、private(私有继承)之间的差别。
答:
继承方式决定了基类中的成员在派生类中的属性。
三种继承方式的共同点:
基类的private成员在派生类中不可见。
区别:
对于私有继承,基类的public,protected成员在派生类中作为private成员;对于公有继承,基类的public,protected成员在派生类中访问属性不变;对于保护继承,基类的public,protected成员在派生类中作为protected成员。
3.派生类构造函数执行的次序是怎样的?
答:
派生类构造函数的执行顺序是先执行所有基类的构造函数(顺序按照定义派生类是指定的各基类顺序),再执行对象成员所在类的构造函数(顺序按照他们在类中的声明顺序),最后执行派生类构造函数体中的内容。
4.如果在派生类B已经重载了基类A的一个成员函数fn1(),没有重载成员函数fn2(),如何调用基类的成员函数fn1(),fn2()?
答:
因为在派生类B已经重载了基类A的一个成员函数fn1(),所以要用作用域运算符对fn1()函数加以限定,调用基类的成员函数fn1()是A:
:
fn1();因为在派生类B没有重载成员函数fn2(),所以直接可调用fn2()。
5.什么叫做虚基类?
它有何作用?
答:
在多重继承中,如果多条继承路径上有一个公共的基类,则在这些路径的汇合点上的派生类会产生来自不同路径的公共基类的多个拷贝,如果用virtual把公共基类定义成虚基类,则只会保留公共基类的一个拷贝。
引进虚基类的目的是为了解决二义性问题,使得公共基类在它的派生类对象中只产生一个基类子对象。
6.声明一个Shape基类,在此基础上派生出Rectangle和Circle类,二者都有GetArea()函数计算对象的面积。
使用Rectangle类创