C++学习之路—继承与派生(四)拓展与总结
(根据《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++学习之路—继承与派生(四)拓展与总结的更多相关文章
- C++学习之路—继承与派生(一):基本概念与基类成员的访问属性
(本文根据<c++程序设计>(谭浩强)总结而成,整理者:华科小涛@http://www.cnblogs.com/hust-ghtao,转载请注明) 1 基本思想与概念 在传统的程序设计 ...
- C++学习之路—继承与派生(三):多重继承与虚基类
(根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 多重继承是指一个派生类有两个或多个基类.例如,有 ...
- C++学习之路—继承与派生(二):派生类的构造函数与析构函数
(根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 由于基类的构造函数和析构函数是不能被继承的,所以 ...
- 【C++学习笔记】继承与派生基础概念
面向对象的程序设计主要有四个特点:抽象.封装.继承和多态.其中继承是我认为最最重要的一个特性,可以说继承是面向对象的精华所在. 举一个继承的浅显易懂的例子:假如我们已经有了一个“马”的类,其中成员变量 ...
- python学习之路基础篇(第四篇)
一.课程内容回顾 1.python基础 2.基本数据类型 (str|list|dict|tuple) 3.将字符串“老男人”转换成utf-8 s = "老男人" ret = by ...
- 前端学习之路之CSS (四)
Infi-chu: http://www.cnblogs.com/Infi-chu/ CSS盒子模型 概念:CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际 ...
- JavaScript设计模式学习之路——继承
早在学习java的时候,就已经接触了继承,在java中因为有extends关键字,因此继承就比较简单.但是在JavaScript中,只能通过灵活的办法实现类的继承. 下面是我昨天在学习过程中,了解到的 ...
- Python小白学习之路(二十四)—【装饰器】
装饰器 一.装饰器的本质 装饰器的本质就是函数,功能就是为其他函数添加附加功能. 利用装饰器给其他函数添加附加功能时的原则: 1.不能修改被修饰函数的源代码 2.不能修改被修饰函数的调用 ...
- linux学习之路--(四)文件,目录管理
1.mkdir:创建空目录 -p: -v:verbose mkdir -pv /mnt/test/x/m /mnt/test/y mkdir -pv /mnt/test/{x/m,y} 命令行展开: ...
随机推荐
- git阶段学习总结
学习git大约有两个星期了,脑子里总算有点干货了,可以拿出来总结一下: git,用于版本控制的,刚开始觉得它是linux下默认的命令,其实也是个工具需要apt-get install git 安装一下 ...
- Linux怎么设置PostgreSQL远程访问
原文链接: Linux怎么设置PostgreSQL远程访问 安装PostgreSQL数据库之后,默认是只接受本地访问连接.如果想在其他主机上访问PostgreSQL数据库服务器,就需要进行相应的配置. ...
- python——登陆接口设计(循环方法)
近日重新整理了登陆接口设计程序,感觉以前的代码没有注释,让园子的其他童鞋读起来比较费劲.也没有流程图和程序运行说明. 1.流程图 2.user_file.txt&lock_file.txt文件 ...
- ios 判断,qq,银行卡,手机号,邮编,生日,数字,字符串,护照, email
http://blog.csdn.net/dyllove98/article/details/8635079 IdentifierValidator.h // // IdentifierValida ...
- 纪念一下第一次写的django代码
@csrf_exemptdef new_project_detail(request): if 'project_name' not in request.POST or 'project_po ...
- Python函数式编程:内置函数reduce 使用说明
一.概述 reduce操作是函数式编程中的重要技术之一,其作用是通过对一个集合的操作,可以从中生成一个值.比如最常见的求和,求最大值.最小值等都是reduce操作的典型例子.python通过内置red ...
- 基于visual Studio2013解决算法导论之015第二小元素
题目 查找第二小元素 解决代码及点评 #include <stdio.h> #include <stdlib.h> #include <malloc.h> ...
- 基于SOAP的xml网络交互心得
感谢小二同学将遇到的问题分享给我们,再此给以掌声.如果看不懂下面文章的建议查找一下HTTP协议的文艺,对HTTP协议要有个概念. XML网络交互心得 目录 一. xml解析 1.根路径下 2. ...
- 再造 “手机QQ” 侧滑菜单(一)——实现侧滑效果
本系列文章中,我们将尝试再造手机QQ的侧滑菜单,力争最大限度接近手Q的实际效果,并使用 Auto Layout 仿造左侧菜单,实现和主视图的联动. 代码示例:https://github.com/jo ...
- linux定时调度器每秒运行一次
linux操作系统最小粒度的定时调度器仅仅能调到分钟的级别,工作中有时需在到秒的调度,所以须要自己编写脚本来实现 #!/bin/bash while [ true ]; do /bin/sleep 1 ...