b.f();//error
b.g();//error
b.k();//ok
}//------------------------------------
一方面,基类提供公有操作界面,public成员对一切使用者公开;另一方面,基类还有自己的隐私,private成员只对自己的成员开放,但即使是自己的后裔也保密;(父母亲过去的恋爱史对自己的子女或许还羞于启口,成为隐私吧)还有一方面,基类也提供只向自己的后裔开放的成员,便于继承者基于此而改进。
因此保护(protected)成员通常是基类为后裔所做的专业或精巧的公有界面,后裔可以访问保护成员函数和保护数据,最终向使用者提供公有界面。
保护成员函数一般不是原始的基本的操作,否则作为私有成员通过公有成员去访问就得了。
或许这些操作很高效很优秀,其后裔可以考虑将其公有化,但目前该界面还有可能会改变。
种种原因,使得这些成员既不能私有,也不能公有。
基类为了长远考虑,可以留下保护成员,但派生类简单而清晰的设计应该是不使用任何保护成员,即只使用公有成员。
10.3派生类的构造(ConstructingDerivedClasses)
1默认构造(DefaultConstruction)
派生类也是类,如果没有定义构造函数,则根据类机制,将会执行默认构造函数。
派生类的默认构造函数会首先调用父类的无参构造函数,如果父类定义了构造函数(因此没有默认构造函数),又没有定义无参构造函数,则会导致编译发怒。
如果父类还有父类,则父类会先调用父类的父类的无参构造函数,依此类推。
在程序f1001.cpp中,研究生类只有默认构造函数,默认构造函数首先要调用Student无参构造函数来创建Student对象。
因此调用了Student默认参数的构造函数(无参形式)。
所以输出的结果中名字为“noName”。
在构造一个子类时,完成其基类部分的构造由基类的构造函数去做,将基类对象看作是完全独立于派生类的对象,这样做的好处是,一旦基类的实现有错误,只要不涉及界面,那么,基类实现中的修改不会影响派生类的操作。
类与类之间,你做你的,我做我的,职责分明,即使父子继承关系的类之间也不例外。
2自定义构造(SelfDefinedConstruction)
可以在派生类的构造函数中规定调用基类构造函数的形式,并不是非得要调用无参构造函数的。
在下列程序中,GraduateStudent类定义了一个构造函数,它调用了基类的构造函数,因而研究生就变得有名有姓了:
1.//=====================================
2.//f1002.cpp
3.//constructingderivedclass
4.//=====================================
5.#include
6.usingnamespacestd;
7.//-------------------------------------
8.classAdvisor{
9.intnoOfMeeting;
10.public:
11.Advisor(){cout<<”Adviosr\n”;}
12.Advisor(){cout<<”copyAdvisor\n”;}
13.~Advisor(){cout<<”~Advisor\n”;}
14.};//-----------------------------------
15.classStudent{
16.stringname;
17.intsemesterHours;
18.doubleaverage;
19.public:
20.Student(stringpName="noName"):
name(pName),average(0),semesterHours(0){}
21.voidaddCourse(inthours,doublegrade){
22.doubletotalGrade=(semesterHours*average+grade);//总分
23.semesterHours+=hours;//总修学时
24.average=semesterHour?
totalGrade/semesterHours:
0;//平均分
25.}
26.voiddisplay(){
27.cout<<"name=\""<28.<<",average="<29.}
30.intgetHours(){returnsemesterHours;}
31.doublegetAverage(){returnaverage;}
32.~Student(){cout<<”~Student\n”;}
33.};//-----------------------------------
34.classGraduateStudent:
publicStudent{
35.Advisoradvisor;
36.intqualifierGrade;
37.public:
38.GraduateStudent(conststring&pN,Advisor&adv)
39.:
Student(pN),advisor(adv),qualifierGrade(0){}
40.voiddisplay(){
41.Student:
:
display();
42.cout<<"GraduateStudent\n";
43.}
44.getQualifier(){returnqualifierGrade;}
45.};//-----------------------------------
46.voidfn(Advisor&advisor){
47.GraduateStudentgs("YenKayDoodle",advisor);
48.gs.display();
49.}//------------------------------------
50.intmain(){
51.Advisorda;
52.fn(da);
53.}//====================================
调用基类的构造函数的形式与对象成员初始化形式相似,都是放在构造函数的初始化列表中。
在第43行有一条调用派生类成员函数display的语句,该语句先调用基类的display()语句,然后再执行自己特有的操作,输出研究生字样。
调用基类display的第36行语句中为了要区分基类与派生类的display的不同,在display前要加类名Student。
如果不定义派生类的display,gs对象也可以通过点操作符调用基类的display操作,这时候,编译就认定是基类的display操作了,因为派生类没有display成员。
在派生类中定义一个相同名字的操作,目的是表明与基类操作的相似性,并在此基础上,加上自己特有的操作。
另一个原因是便于进一步派生,类层次分明。
3拷贝构造与复制(CopyConstruction&Assignment)
拷贝构造的方式与构造函数的方式相似,也就是说,基类若没有自定义拷贝构造函数,则派生类的拷贝构造函数将调用基类的默认拷贝构造函数,否则,调用基类的自定义拷贝构造函数。
派生类若没有自定义拷贝构造函数,则拷贝构造时调用默认拷贝构造函数。
这对于对象本体与对象实体不一致的情况来说,需要在派生类中自定义拷贝构造函数。
拷贝构造时面临的问题是派生类对象拷贝给基类对象的转换问题。
这时,将按照参数传递规则办事。
例如:
classStudent{
Student&operator=(constStudent&);
Student(constStudent&s);
};//-------------------------------------------
voidfn(constGraduateStudent&gs){
Studentss=gs;
ss=gs;
}//--------------------------------------------
根据参数传递规则,gs将匹配Student(constStudent&s)的拷贝构造函数中的形参s,即:
constStudent&s=gs;
因此s将指向gs对象中的基类部分。
完成拷贝构造时,即将gs中的基类对象拷贝复制给ss。
同理,在赋值时,有相似过程。
4构造顺序(ConstructionOrder)
派生类构造函数被调用时,在还没有执行构造函数体之前,立刻调用基类的构造函数。
如果基类构造调用在初始化列表中有的,就按初始化列表的调用形式做,否则,就调用相应的基类无参构造函数。
同理,如果基类上面还有基类,则也会优先调用上面的基类构造函数的。
做完了基类的构造函数,接下来就要给自身的对象本体分配空间了,然后,就调用对象中的各个对象成员的构造函数。
一边调用一边分配空间。
如果有多个对象,其调用的顺序按类中对象定义的顺序排定。
任何构造函数的调用,总是先分配好对象本体的空间,给出该空间的this指针,然后,笃笃定定地执行构造函数的体。
因此,一个含有基类对象的对象,含有对