---17:54 2005-6-17------
五、继承的概念
5.1继承的基本知识
1.代码共享
在一个我们解决问题的实际系统中,如果需要有不同的类来分别代表一些具体的事物,但是这些事物之间又具有某些相同的属性(成员数据和成员函数)的时候,我们可以单独实现表示这些事物(数据和函数)的类,再让其他的实际应用类通过从这个类继承而达到共享代码的目的.
例如对于学生和老师这两个不同的事物类,可以先实现一个人所共有的属性(如姓名,性别等以及改变和获取这些属性的方法)的类,然后老师和学生类都从人这个类去继承,从而达到代码共享(不重复)以及良好的可维护性.
5.2"是一个"关系的含义
在继承中,基类与派生类(子类)之间是"是一个"的关系.基类是一个一般类,他的行为在派生类中被特殊化了.但是在实现这个特殊化的过程中并没有丢弃基类的任何特性.比如老师"是一个"人,学生也"是一个"人.所以在任何时候,派生类都可以作为"是一个"基类而作为基类来使用.
不仅仅可以在派生类中添加基类所没有的特性,而且可以重新实现基类的一些或者全部方法,一满足派生类的改进要求.
5.3继承关系的效果
1.直接基类和间接基类
对于a<--b<--c(c从b直接继承,而b又从a直接继承)
那么 a是b的直接基类,b是c的直接基类,而且a是c的间接基类.
5.4多态置换原则
1.多态置换原则(DSP),如果对于基类d的每个对象objd,都有一个类b的对象objd,使得当对象onjd置换objb时,在类b中定义的任何函数F以及函数F 的行为都不会变化,则类d是类b的子类型.(怎么有点象数学书里面的定义一样啊!呵呵)
2.例如设计一个函数printinfo(const tperson& a ),可以打印每个人的名字和年龄,那么对于学生和老师类,因为他们也是一个人,所以当给函数printinfo(const tperson& a )传入学生和老师类的任何一个对象时,它也可以正常的工作.
3.同样的,基类指针(或引用)总是可以指向(或引用)任何派生对象.(这个也是多态需要的基本要求)
4.对于把基类在函数中当作按值传递也是合法的,但是这时传递进去的派生类会被当作基类,所以在复制参数的时候多余基类的部分会被切断.
对于传递指针或者引用的情况,虽然没有产生切断,但是在被调用的函数里面通过该指针或者引用只可以访问到属于基类的部分.
5.4.1初始化基类对象
当调用派生类的构造函数的时候,每个派生类都必须初始化它的基类.而且应该在构造函数的初始化阶段完成,语法如下:
tstudent::tstudent(char name[],int age,int num):tperson(name,age){....}
对于复制构造函数也一样:
tstudent::tstudent(const tstudent& copy):tperson(copy){...}
注意:
1.这种调用基类方法的语法只有在构造函数(包括复制构造函数)中才合法,普通成员函数是不允许的.
2.一定要用正确的参数调用<直接基类>的构造函数,以作为每个派生类构造函数的一部分.(?是不是如果再有类从该派生类继承的时候,它的构造函数就不用顾及其间接基类的构造函数了呢?如果是,那么这个规则是一个良好的习惯呢,还是被限制在语法的合法性上面呢???)
3.(看来这里已经有答案了,呵呵)类只能调用他的直接基类的构造函数,而不可以调用非直接基类的构造函数.这种情况在使用虚基类的时候可以放宽.
(*我怎么发现这哪里是在做笔记啊,这样子就简直是在抄书啊!幸亏我这样做不是用来出版,那样可就是剽窃了啊!嘿嘿!也许是这块砖头太经典的缘故,常常几句话就把一个概念解释清楚了,我要是再多写几句吧简直就是画蛇添足了,有时候我也有很多自己的想法或者说是见解,可是一来我不知道怎么样把他们表述出来或者反映到文字上;二来吧,在没有真正
理解之前,我怕会有错误以至于印象深刻而对以后的学习产生阻碍.不过现在看来,到时候我还得回过头来再看一遍书,因为上面已经被我画满了问号开头的疑问了,呵呵!惭愧啊,别说自己能够学的象人家这样好,单单别人写的书都看不明白,真是汗颜!!!!*)
4.基类的行为和结构会被其派生类所继承,但是基类的构造函数永远不会被继承,派生类必须提供自己的构造函数.(?是不是为了初始化不属于基类的那部分呢??同样的析构函数也不会被继承了,因为它要负责释放不属于基类的那部分啊,是不是呢???再看后面一页析构函数果然也不会被继承,耶!!!可是结论对了,理由也真的对吗?)
如果派生类没有提供自己的构造函数,编译器自动产生一个构造函数,该函数调用直接基类的构造函数.
(书上是这样写的,"...调用基类的默认构造函数(如果有一个的话)".什么意思呢?如果基类也没有人为自定义构造函数,可以理解.可是如果有人为定义的呢?调用哪个?如果调用人为的构造函数,可是如果碰到需要参数的构造函数这个参数从哪里给呢?看来还是只调用默认的没有参数的构造函数省事啊!看来书上说的是对的.如果是,这样说是不是好理解一点呢?例如"如果没有定义派生类的构造函数,编译器会自动产生一个,该函数仅仅调用其直接基类的也是由编译器自动产生的构造函数".可是这样一来疑问又来了啊:1.按我理解编译器自动为基类(假设该类不是从任何类继承而来)产生的构造函数是什么也不作为的啊,那么产生两次不作为的函数调用又有什么意义呢?2.看来还是对书本原话的理解问题,那么默认构造函数是什么意思呢?难道是说,编译器为类自动产生的构造函数叫默认构造函数?上面的意思是,如果没有为派生类定义构造函数,那么编译器自动产生一个,其作用就是在直接基类也有编译器自动产生的默认构造函数的时候负责调用它,否则就根本不去调用基类的其他构造函数吗????天啊,问题又回到第一个不作为的疑问上了,搞不明白,一会到论坛上去请教好了,晕了我.)
5.派生类的构造函数接受自己使用的参数外还要接受其基类需要使用的所有那些参数.(我的理解,应该是接受其直接基类的构造函数的所有参数,因为首先派生类只能调用其直接基类的构造函数.这样就算经过多重继承如:
a::a(int arg1){...}
b::b(int arg1,int arg2):a(int arg1){...}
c::c(int arg1,int arg2,int arg3):b(int arg1,int arg2){...}
也符合该条的要接受所有参数的原则.)
6.对于派生类的赋值运算符
赋值运算符的定义按照正常进行,但是在执行他自己的数据的赋值之前,要调用基类的赋值运算符.
注意:其语法是一个普通的成员函数调用,而不是初始化.
??可不可以给个例子先?反正都已经800多页了,呵呵找到了:)
tstudent& tstudent::operator=(const tstudent& other)
{
tperson::operator=(other);
//把基类的赋值运算符当作函数调用,因为它也是一个人啊,呵呵.原来
//赋值运算符还可以这样用啊!还以为只能a=b呢,嘿嘿!
.....
}
7.对于派生类的析构函数
派生类的析购函数不需要调用基类的析构函数,编译器会为我们做到的.
*下一节还是明天再看吧,我想抽烟了,霍霍!*
5.5用继承扩展类层次