(根据《C++程序设计》(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明)

1    拓展部分

本节主要由两部分内容组成,分别是(1)基类与派生类的转换和(2)继承与组合

1.1    基类与派生类的转换

在前几篇博客中,可以看到在3种继承方式中,只有公有继承能较好的保留基类的特征,它保留了除构造函数和析构函数以外所有的基类成员,基类的公有或保护成员的访问权限在派生类中全部保留了下来,在派生类外可以调用基类的公有成员函数以访问基类的私有成员。而非公用派生类不能实现基类的全部功能(例如在派生类外不能调用基类的公有成员函数访问基类的私有成员)。因此,只有公有派生类才是基类真正的子类型,它完整的继承了基类的功能。

如果不同类型数据之间可以自动转换和赋值,成为赋值兼容。基类与公用派生类之间具有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子对象代替。基类与(公用)派生类之间的赋值兼容表现在以下4个方面:

(1)派生类对象可以向基类对象赋值。若类B是类A的公用派生类,则可进行以下操作:

   1: A a1 ;     //定义基类A对象a1

   2: B b1 ;     //定义公用派生类B的对象b1

   3: a1 = b1 ;   //用派生类B的对象b1对基类对象a1赋值

在进行赋值时舍弃派生类自己新增加的成员,所谓赋值只是对数据成员赋值,对成员函数不存在赋值问题。赋值后不能试图通过对象a1去访问派生类对象b1新增的成员,假设age是派生类B中新增加的成员,则:

   1: a1.age = 23 ;  //错误,a1中不包含派生类中增加的成员

   2: b1.age = 21 ;   //正确,b1中包含派生类中增加的成员

总结:只能用子类对象对基类对象赋值,而不能用基类对象对其子类对象赋值。同一基类的不同派生类对象之间也不能赋值。

(2)派生类对象可以代替基类对象向基类对象的引用进行赋值或初始化。

如果已经定义了基类A对象a1,可以定义a1的引用变量:

   1: A a1 ;           //定义基类A对象a1

   2: B b1 ;         //定义公用派生类B对象b1

   3: A& r = a1 ;    //定义基类A对象的引用r,并用a1对其初始化

   4: //也可以将上面最后一行改为

   5: A& r  = b1 ;    //定义基类A对象的引用r,并用派生类B对象b1对其初始化

此时r并不是b1的别名,也不是与b1共享同一段存储单元。它只是b1中基类部分的别名,r与b1中基类部分共享同一段存储单元,r与b1具有相同的起始地址。

(3)如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。例如:

   1: void fun ( A& r )                //形参是A类对象的引用

   2: {

   3:     cout << r.num << endl ;      //输出该引用中的数据成员num

   4: }

   5: B b1 ;                           //定义公用派生类的对象b1

   6: fun( b1 ) ;                      //输出B对象b1的基类的数据成员num的值

在fun函数中只能输出派生类中基类成员的值。

(4)派生类对象的地址可以赋给指向基类对象的指针变量,即指向基类对象的指针变量可以指向派生类对象。示例程序如下:

   1: #include <iostream>

   2: #include <string>

   3: using namespace std ;

   4:  

   5: class Student                                //声明Student类

   6: {

   7: public:

   8:     Student ( int , string , float ) ;       //声明构造函数

   9:     void display();                          //声明输出函数

  10: private:

  11:     int num ;

  12:     string name ;

  13:     float score ;

  14: };

  15: Student::Student( int n , string nam , float s )  //定义构造函数

  16: {

  17:     num = n ;

  18:     name = nam ;

  19:     score = s ;

  20: }

  21: void Student::display()                           //定义输出函数

  22: {

  23:     cout << endl << "num:" << num << endl ;

  24:     cout << "name:" << name << endl ;

  25:     cout << "score:" << score << endl ;

  26: }

  27:  

  28: class Graduate : public Student                //声明公用派生类Graduate

  29: {

  30: public:

  31:     Graduate( int , string , float , float ) ; //声明构造函数

  32:     void display() ;                           //声明输出函数

  33: private:

  34:     float wage ;

  35: };

  36: Graduate::Graduate( int n , string nam , float s , float w ) : Stident( n , nam , s )

  37:                     , wage( w ) {}            //定义构造函数

  38: void Graduate::display()                      //定义输出函数

  39: {

  40:     Student::display() ; 

  41:     cout << "wage:" << wage << endl ;

  42: }

  43:  

  44: int main()

  45: {

  46:     Student stud1( 1001 , "Li" , 87.5 ) ;     //定义Student类对象stud1

  47:     Graduate grad1( 2001 , "wang" , 98.5 , 1000 ) ;  //定义Graduate类对象grad1

  48:     Student *pt = &stud1 ;                    //定义指向基类的指针并指向stud1

  49:     pt->display() ;                           //调用stud1.display函数

  50:     pt = &grad1 ;                            //指针指向grad1

  51:     pt->display() ;                          //调用grad1.display函数

  52:  

  53:     return 0 ;

  54: }

先看一下程序运行结果,再进行具体的分析:

分析:有很多读者会认为,在派生类中有两个同名的display成员函数,根据同名覆盖的规则,第二次被调用的应当是派生类Graduate对象的display函数,在执行Graduate::display函数过程中调用Student::display函数,输出num,name,score,然后再输出wage的值。很明显与上述结果不符,why?问题在于pt是指向Student类对象的指针变量,即使让它指向了grad1,但实际上pt指向的是从grad1从基类继承的部分。通过指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生类增加的成员。

通过本例可以看到,用指向基类对象的指针变量指向子类对象是合法的、安全的,不会出现编译上的错误。但人们更希望通过使用基类指针能够调用基类和子类对象的成员,要解决这个问题,就要用到以后会讲到的多态性和虚函数。

1.2   继承与组合

在一个类中以另一个类的对象作为数据成员的,称为类的组合。例如,声明Professor类是Teacher类的派生类,另有一个类BirthDate,包含year,month,day等数据成员。我们可以将教授生日的信息加入到Professor类的声明中。如:

   1: class Teacher                    //声明教师类

   2: {

   3: public:

   4:     ...

   5: private: 

   6:     int num ;

   7:     string name ;

   8:     char sex ;

   9: };

  10:  

  11: class Birthdate                  //声明生日类

  12: {

  13: public:

  14:     ...

  15: private:

  16:     int year ;

  17:     int month ;

  18:     int day ;

  19: };

  20:  

  21: class Professor : public Teacher   //声明教授类

  22: {

  23: public:

  24:     ...

  25: private:

  26:     Birthdate birthday ;           //Birthdate类的对象作为数据成员

  27: };

类的组合和类的继承一样,都是有效地利用已有类的资源。但二者的概念和用法不同。通过继承建立了派生类与基类的关系,这是一种“is-a”的关系,如“白猫是猫”,派生类是基类的具体化的实现,是基类的一种。通过组合则建立了成员类和组合类的关系,它们之间是“has-a”的关系。不能说Professor是一个Birthdate,只能说教授有一个Birthdate的属性。

2   继承在软件开发中的重要意义

缩短软件开发过程的关键是鼓励软件的重用。继承机制在很大一部分上解决了这个问题。编写面向对象的程序时要把注意力放在实现对自己有用的类上面,对已有的类加以整理和分类,进行剪裁和修改,并在此基础上集中精力编写派生类新增加的部分。

人们为什么这么看重继承,要求在软件开发中使用继承机制,尽可能的通过继承建立一批新的类,有以下几个原因:

(1)有许多基类是被程序的其他部分或其他程序使用的,这些程序要求保持原有的基类不受破坏。

(2)用户往往得不到基类的源代码。如果使用的类库,用户无法知道成员函数的代码,因此也就无法对基类进行修改,保证了基类的安全。

(3)在类库中,一个基类可能已被指定与用户所需的多种组建有联系,因此类库不允许被修改。

(4)许多类是专门被设计为基类的,并没有什么独立的功能,只是一个框架,或者说是抽象类。设计这些通用的类目的是建立通用的数据结构,以便用户在此基础上添加各种功能建立派生类。

C++学习之路—继承与派生(四)拓展与总结的更多相关文章

  1. C++学习之路—继承与派生(一):基本概念与基类成员的访问属性

    (本文根据<c++程序设计>(谭浩强)总结而成,整理者:华科小涛@http://www.cnblogs.com/hust-ghtao,转载请注明) 1   基本思想与概念 在传统的程序设计 ...

  2. C++学习之路—继承与派生(三):多重继承与虚基类

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 多重继承是指一个派生类有两个或多个基类.例如,有 ...

  3. C++学习之路—继承与派生(二):派生类的构造函数与析构函数

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 由于基类的构造函数和析构函数是不能被继承的,所以 ...

  4. 【C++学习笔记】继承与派生基础概念

    面向对象的程序设计主要有四个特点:抽象.封装.继承和多态.其中继承是我认为最最重要的一个特性,可以说继承是面向对象的精华所在. 举一个继承的浅显易懂的例子:假如我们已经有了一个“马”的类,其中成员变量 ...

  5. python学习之路基础篇(第四篇)

    一.课程内容回顾 1.python基础 2.基本数据类型  (str|list|dict|tuple) 3.将字符串“老男人”转换成utf-8 s = "老男人" ret = by ...

  6. 前端学习之路之CSS (四)

    Infi-chu: http://www.cnblogs.com/Infi-chu/ CSS盒子模型    概念:CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际 ...

  7. JavaScript设计模式学习之路——继承

    早在学习java的时候,就已经接触了继承,在java中因为有extends关键字,因此继承就比较简单.但是在JavaScript中,只能通过灵活的办法实现类的继承. 下面是我昨天在学习过程中,了解到的 ...

  8. Python小白学习之路(二十四)—【装饰器】

    装饰器 一.装饰器的本质 装饰器的本质就是函数,功能就是为其他函数添加附加功能. 利用装饰器给其他函数添加附加功能时的原则: 1.不能修改被修饰函数的源代码        2.不能修改被修饰函数的调用 ...

  9. linux学习之路--(四)文件,目录管理

    1.mkdir:创建空目录 -p: -v:verbose mkdir -pv /mnt/test/x/m  /mnt/test/y mkdir -pv /mnt/test/{x/m,y} 命令行展开: ...

随机推荐

  1. Cocos2dx项目移植Android平台

    链接地址:http://blog.csdn.net/iuncle/article/details/24772183 版权声明:本文为博主原创文章,未经博主允许不得转载. 1.Classes目录下存放. ...

  2. BZOJ 1458: 士兵占领( 网络流 )

    先判无解 把整个棋盘都放上士兵, 只需求最多可以拿走多少个士兵即可.每一行看做一个点r(i), 每一列看做一个点c(i) S->r(i), c(i)->T 连边, 容量为可以拿走的最大士兵 ...

  3. java基础之junit测试框架

    1.导入junit包, 2.测试方法格式 public void test_*(){} 继承  TestCase  包(keep the bar green to keep the code clea ...

  4. Best Component for Bitmap Image

    The best is to purchase ImageEn and use the latest version. Coz nothing compares to ImageEn.... But ...

  5. .yml是什么文件

    YAML(IPA: /ˈjæməl/,尾音类似camel骆驼)是一个可读性高,用来表达资料序列的编程语言.YAML参考了其他多种语言,包括:XML.C语言.Python.Perl以及电子邮件格式RFC ...

  6. 利用MySQL 的GROUP_CONCAT函数实现聚合乘法

    MySQL 聚合函数里面提供了加,平均数.最小,最大等,可是没有提供乘法,我们这里来利用MYSQL现有的GROUP_CONCAT函数实现聚合乘法. 先创建一张演示样例表: CREATE TABLE ` ...

  7. 对base-adapter-helper的简单分析

    在微博上看到了这篇Android ListView适配器应该这样写,受益匪浅. 于是依据文章结尾的介绍来到了base-adapter-helper的github,地址:https://github.c ...

  8. JAVA ANDROID SOCKET通信检测(SERVER)连接是否断开

    Pre 解决思路 代码后记: 客户端app上的部分代码 调用: 服务器上: 客户端判断服务器是否还活着代码: PRE 在利用socket写通讯程序的时候,想检测服务器是否还活着. 从网上找了很多资料, ...

  9. Sql 参数的使用

    代码片段: a)声明实例 1.声明SQLCommand实例. SqlCommand cmd = new SqlCommand(); 2.声明SqlDataAdapter实例. SqlDataAdapt ...

  10. javascript每日一练(十二)——运动框架

    运动框架 可以实现多物体任意值运动 例子: <!doctype html> <html> <head> <meta charset="utf-8&q ...