第04章 继承性与多态性Word文件下载.docx
《第04章 继承性与多态性Word文件下载.docx》由会员分享,可在线阅读,更多相关《第04章 继承性与多态性Word文件下载.docx(32页珍藏版)》请在冰点文库上搜索。
![第04章 继承性与多态性Word文件下载.docx](https://file1.bingdoc.com/fileroot1/2023-5/11/27cf5be5-ccf0-4f2a-91df-ec536e47c5da/27cf5be5-ccf0-4f2a-91df-ec536e47c5da1.gif)
从定义格式上来看,多重继承与单继承的区别主要是多重继承的基类多于一个。
3.访问方式
不管在单继承还是在多重继承的定义格式中,访问方式,即继承方式,可以为public、private或protected,如果省略,则默认为private方式。
访问方式为public方式时,这种继承称为公有继承;
访问方式为private方式时,这种继承称为私有继承;
访问方式为protected方式时,这种继承称为保护继承。
4.1.2派生类的生成过程
在给出了派生类的定义和相应成员函数的实现代码后,整个派生类的定义就算完成了,这是就可以利用该类定义相应的对象处理实际问题了。
由于派生类是在基类的基础上经过继承而产生的,所以搞清派生类中到底有哪些成员对于更好的使用派生类是很重要的。
事实上,派生新类经历了三个步骤:
1.吸收基类成员
派生类继承吸收了基类的全部数据成员以及除了构造函数、析构函数之外的全部函数成员。
也就是说,基类中的构造函数和析构函数不能继承到派生类中的。
2.改造基类成员
对继承到派生类中基类成员的改造包括两个方面:
一是基类成员的访问方式问题,这由派生类定义时的访问方式来控制;
二是对基类数据成员或成员函数的覆盖,也就是在派生类中定义了与基类中同名的数据成员或函数成员,由于作用域不同,于是发生同名覆盖,基类中的成员就被替换成派生类中的同名成员。
3.添加新成员
在派生类中,除了从基类中继承过来的成员外,还可以根据需要在派生类中添加新的数据成员和成员函数,以此实现必要的新功能。
可以看出,在派生类中可以添加新成员的机制是继承和派生机制的核心,保证了派生类在功能上比基类有所发展。
4.1.3继承方式对基类成员的访问控制
前面已经分析,派生类继承和吸收了基类的全部数据成员和除了构造函数、析构函数之外的全部函数成员,但这些成员在派生类中的访问属性是可以调整的,这是由派生类定义格式中的继承方式来决定的,也就是继承方式控制了基类中具有不同访问属性的成员在派生类中的访问属性。
由于继承方式可以有public、private和protected三种,不同的继承方式会导致原来具有不同访问属性的基类成员在派生类中的访问属性也有所不同。
这种访问包括两个方面:
一是派生类中新增成员对从基类继承来的成员的访问;
二是派生类的外部通过派生类的对象从基类继承来的成员的访问。
1.公有继承
当类的继承方式为公有继承时,基类中public和protected成员的访问属性在派生类中不变,而基类private成员不可访问。
也就是说,基类的public和protected成员在公有继承方式下分别继承为派生类的public和protected成员,派生类中的其他成员可以直接访问它们,在派生类的外部只能通过派生类的对象访问从基类继承来的public成员。
而无论是派生类的成员还是派生类的对象都无法访问从基类继承来的private成员。
2.私有继承
当类的继承方式为私有继承时,基类中的public和protected成员都以private成员出现在派生类中,而基类private成员不可访问。
也就是说,基类的public和protected成员在私有继承方式下被继承为派生类的private成员,派生类中的其他成员可以直接访问它们,但在派生类的外部无法通过派生类的对象访问它们。
可以看出,经过私有继承后,所有基类的成员都成为派生类的私有成员,如果进一步派生的话,基类的成员就无法在新的派生类中被访问。
因此,经过私有继承后,基类的成员再也无法在以后的派生类中发挥作用,实际是相当于中止了基类功能的继续派生。
3.保护方式
当类的继承方式为保护继承时,基类中的public和protected成员都以protected成员出现在派生类中,而基类private成员不可访问。
也就是说,基类的public和protected成员在保护继承方式下被继承为派生类的protected成员,派生类中的其他成员可以直接访问它们,但在派生类的外部无法通过派生类的对象访问它们。
继承访问方式
基类成员特性
派生类成员特性
公有
public
protected
private
不可访问
私有
保护
示例:
#include"
iostream.h"
public:
voidf1();
protected:
intj1;
private:
inti1;
voidf2();
intj2;
inti2;
}
publicB
voidf3();
voidf1();
voidf2();
intj1,j2;
回答如下问题:
⑴B的成员函数f2()能否访问A中的f1()、i1、j1等成员?
⑵B的对象b1能否访问A中的f1()、i1、j1等成员?
⑶C的成员函数f3()能否访问B中的f2()、j2等成员?
能否访问A中的f1()、i1、j1等成员?
⑷C的对象能否访问B中的f2()、i2、j2等成员?
⑸从上述过程可得出对公有访问继承什么样的结论?
解答:
⑴B的成员函数f2()能访问A中的f1()、j1,不能访问i1。
⑵B的对象b1能访问A中的f1(),不能访问i1、j1。
⑶C的成员函数f3()能访问B中的f2()、j2,不能访问i2。
能访问A中的f1()、j1,不能访问i1。
⑷C的对象能否访问B中的f2()和A中的f1(),其他均不可访问。
⑸在公有继承时,派生类的成员函数可以访问基类中的公有成员和保护成员;
派生类的对象仅可访问基类中的公有成员。
voidf(inti)
{
cout<
<
i<
endl;
voidg()
{
cout<
"
g"
A
voidh()
cout<
h\n"
;
A:
:
f;
voidmain()
Bd1;
d1.f(6);
//d1.g();
d1.h();
运行结果:
6
h
string.h"
A(constchar*nm){strcpy(name,nm);
//private:
charname[80];
B(constchar*nm):
A(nm){}
voidPrintName()const;
voidB:
PrintName()const
name:
name<
Bb1("
WangLi"
);
b1.PrintName();
}
4.1.4派生类的构造函数和析构函数
继承和派生的机制可以使派生类继承基类的成员,从而实现了原有代码的重用,但是,由于基类的构造函数和析构函数不能继承,那么在派生类中,如果对派生类新增的成员进行初始化,就必须在派生类中根据需要加入新的构造函数,如果对从基类继承下来的成员进行初始化,还必须由基类的构造函数来完成,所以需要在派生类中的构造函数,一方面负责调用基类的构造函数对基类成员进行初始化,另一方面还要负责对基类的构造函数所需要的参数进行必要的设置。
1.单继承方式下派生类构造函数的定义
在单继承方式下,派生类的构造函数的定义格式如下:
派生类名:
派生类构造函数名(形参表):
基类构造函数名(参数表),子对象名(参数表)...
//派生类构造函数的函数体
在此定义格式中,派生类构造函数名后面括号内的参数表中包括参数的类型和参数名,而基类构造函数名后面括号内的参数表中只有参数名而没有参数类型,并且这些参数必须是来源于派生类构造函数名后面括号内的参数。
inta;
A(){a=0;
cout<
A'
sdefaultconstructorcalled."
}
A(inti){a=i;
sconstructorcalled."
~A(){cout<
sdestructorcalled."
voidPrint()const{cout<
a<
"
intGeta(){returna;
intb;
Aaa;
B(){b=0;
B'
B(inti,intj,intk);
~B(){cout<
voidPrint();
B:
B(inti,intj,intk):
A(i),aa(j)
b=k;
Print()
A:
Print();
b<
aa.Geta()<
Bbb[2];
bb[0]=B(1,2,5);
bb[1]=B(3,4,7);
for(inti=0;
2;
i++)
bb[i].Print();
sdefaultconstructorcalled.
sconstructorcalled.
sdestructorcalled.
1,5,2
3,7,4
说明:
⑴该程序中先定义了类A,接着定义类B,它是类A的派生类。
继承方式为公有继承。
⑵派生类B的构造函数格式如下:
其中,B是派生类构造函数名,它的参数表中有3个参数:
参数i用来初始化基类的数据成员;
参数j用来初始化类B的子对象aa;
参数k用来初始化类B中数据成员b。
冒号后面的是成员初始化列表,如果该表中有多项,它们用逗号分隔。
该成员初始化列表的顺序如下:
先是基类构造函数;
再是派生类中子对象类的构造函数;
最后是派生类的构造函数。
故亦可写成如下形式:
A(i),aa(j),b(k)
//b=k;
⑶运行结果分析:
先创建两个对象元素的对象数组。
调用两次类B的缺省构造函数,每调用类B的缺省构造函数时,先调用两次类A的缺省构造函数和一次类B的构造函数。
于是出现了输出结果的前6行。
接着,程序中出现两个赋值语句,即给两个已定义的对象(对象数组元素)赋值。
系统要建立一个匿名对象,通过构造函数对它初始化,并将其值赋给已定义的左值对象,再调用析构函数将匿名对象删除。
因此输出结果中出现两个调用派生类B的构造函数和析构函数的12行信息。
输出两个类B对象的数据成员值,占有2行信息。
最后,程序结束前由系统自动调用派生类B的析构函数,显示输出了删除两个对象的6行信息。
2.多重继承方式下的派生类构造函数的定义
在多重继承方式下,派生类的构造函数必须同时负责所有基类构造函数的调用,对于派生类构造函数的参数个数必须同时满足多个基类初始化的需要。
所以,在多重继承方式下,派生类的构造函数的定义格式如下:
派生类构造函数名(参数表):
基类名1(参数表1)基类名2(参数表2)……子对象名(参数表)
其中,第1个参数表中的参数包含了其后的各个参数表中的参数。
3.派生类构造函数的执行次序
派生类构造函数执行的一般次序为:
⑴调用基类构造函数,调用顺序按照它们被继承时说明的顺序(从左到右),而不是按派生类构造函数在初始化表中的次序;
⑵调用子对象的构造函数(如果在派生类中存在子对象的话),调用顺序按照它们在类中说明的顺序;
⑶执行派生类构造函数的函数体。
当派生类的对象被删除时,派生类的析构函数被执行。
由于基类的析构函数不能被继承,因此在执行派生类的析构函数时,基类的析构函数也将被调用。
而执行顺序是先执行派生类的析构函数,再执行基类的析构函数,其顺序与执行构造函数是的顺序正好相反。
classB1
intb1;
B1(inti)
b1=i;
cout<
constructorB1."
voidprint(){cout<
b1<
classB2
intb2;
B2(inti)
b2=i;
constructorB2."
b2<
classB3
intb3;
B3(inti)
b3=i;
constructorB3."
intgetb3(){returnb3;
classA:
publicB2,publicB1
B3bb;
A(inti,intj,intk,intl):
B1(i),B2(j),bb(k),a(l)
//a=l;
constructorA."
l<
voidprint()
B1:
print();
B2:
bb.getb3()<
intmain(void)
Aaa(1,2,3,4);
aa.print();
return0;
constructorB2.2
constructorB1.1
constructorB3.3
constructorA.4
1
2
4,3
Pressanykeytocontinue
⑴派生类A的构造函数定义如下:
A(inti,intj,intk,intl):
B1(i),B2(j),bb(k)
a=l;
该函数的总参数表中参数有4个,分别是基类B1,基类B2,子对象bb以及派生类A构造函数的参数,亦可写成如下形式:
⑵派生类构造函数的执行顺序:
在构造函数的成员初始化表中,两个基类顺序是B1在前,B2在后。
而定义派生类A时的两个基类顺序是B2在前,B1在后。
输出结果中,可以看出,先执行B2的构造函数,后执行B1的构造函数。
因此,执行基类构造函数的顺序取决于定义派生类时基类的顺序。
可见,派生类构造函数的成员初始化列表中各项顺序可以任意排列。
⑶作用域运算符:
在该程序中用于解决作用域冲突问题很明显。
在派生类A中的print函数中,分别使用了B1:
和B2:
。
4.2派生中成员的标识与访问
在多重继承的情况下,派生类具有两个以上的直接基类,而这些直接基类的一部分或全部又是从另一个共同基类派生而来的,这些直接基类中从上一级基类继承来的成员拥有相同的名称,在派生类的对象中,这些同名成员在内存中同时拥有多个拷贝,如何进行分辨呢?
有两种方法,一是使用作用域运算符唯一标帜并分别访问它们;
二是将直接基类的共同基类设置为虚基类。
4.2.1使用作用域运算符
这种方法就是在需要访问的成员名前加上直接基类名和作用域运算符“:
”。
其格式是:
直接基类名:
数据成员名
成员函数名(参数表)
voidfa(){cout<
a="
intc;
classD:
publicB,publicC
intd;
voidfd(){cout<
d="
d<
Ddd;
dd.d=1;
dd.fd();
dd.B:
a=2;
fa();
dd.C:
a=3;
d=1
a=2
a=3
该示例中的继承关系和D对象结构分别如下:
4.2.2虚基类
该方法就是将直接基类的共同基类设置为虚基类,即在基类的访问方式前加上关键字“virtual“,声明虚基类的格式如下:
virtual访问方式基类名
{
//声明派生类成员
虚基类虽然被一个派生类间接地多次继承,但派生类却只继承一份该基类的成员,这样就避免了在派生类中访问这些成员时的二义性。
virtualpublicA