一、基类和派生类

很多时候,一个类的对象也“是”另一个类的对象,如矩形是四边形,在C++中,矩形类Rectangle可以由四边形类Quad继承而来,于是,四边形类Quad是基类,矩形类Rectangle是派生类。但是如果说四边形一定是矩形显然不对。几个简单的基类和派生类的例子:

基类                                 派生类

       食物                 米饭、面条、水饺              

       交通工具               汽车、火车、飞机

国家                中国、美国、西班牙

可以看出,每个派生类的对象都是基类的一个对象,并且一个基类可以有很多派生类。继承关系构成一种树状层次结构。基类和派生类存在这种层次关系,如下图:

下面用程序实例来说明:

建立一个乒乓球会员的类TableTennisPlayer类:

  #ifndef TABTEN_H_
#define TABTEN_H_
#include <string>
using std::string;
//一个简单的基类
class TableTennisPlayer
{
private:
string firstname;
string lastname;
bool hasTable;
public:
TableTennisPlayer (const string & fn = "none",
const string & ln = "none", bool ht = false);
void Name() const;
bool HasTable() const {return hasTable;}
void ResetTable(bool v) {hasTable = v;}
}; #endif
  #include <iostream>
#include "tabten.h" TableTennisPlayer::TableTennisPlayer (const string & fn,
const string & ln, bool ht) : firstname(fn), lastname(ln),hasTable(ht) {} void TableTennisPlayer::Name() const
{
std::cout << lastname << ", " << firstname;
}

TableTennisPlayer类只记录会员的姓名以及是否有桌球。假设一些会员参加过锦标赛,则需要这样一个类,它能包括会员在比赛中的比分。我们要重新新建一个类吗?显然不用,这时候只需从TableTennisPlayer类派生出一个类,假设为RatedPlayer

class RatedPlayer : public TableTennisPlayer
{
...
};

冒号表示RatedPlayer类的基类是TableTennisPlayer类,public 表明TableTennisPlayer是一个公有基类,这被称为公有派生。

使用公有派生,基类的公有成员将成为派生类的公有成员;基类的私有部分也将成为派生类的一部分,但是只能通过基类的公有(public)和保护(protected)方法访问。

RatedPlayer对象将具有以下特征:

  • 派生类对象存储了基类的数据成员(派生类继承了基类的实现)
  • 派生类可以使用基类的方法(派生类继承了基类的接口)

派生类还需要做:

  • 添加自己的构造函数
  • 根据需要添加额外的数据成员和成员函数

添加派生类的头文件如下:

 #include <iostream>
#include "tabten.h" TableTennisPlayer::TableTennisPlayer (const string & fn,
const string & ln, bool ht) : firstname(fn), lastname(ln),hasTable(ht) {} void TableTennisPlayer::Name() const
{
std::cout << lastname << ", " << firstname;
} class RatedPlayer : public TableTennisPlayer
{
private:
unsigned int rating; //添加数据成员
public:
//派生类的构造函数必须给新成员(如果添加了的话)和基类的成员提供数据
RatedPlayer (unsigned int r = , const string & fn = "none",
const string & ln = "none", bool ht = false);
RatedPlayer (unsigned int r, const TableTennisPlayer & tp);
unsigned int Rating() const {return rating;}//添加方法
void ResetRating (unsigned int r) {rating = r;}//添加方法
};
  #include <iostream>
#include "tabten.h" TableTennisPlayer::TableTennisPlayer (const string & fn,
const string & ln, bool ht) : firstname(fn), lastname(ln),hasTable(ht) {} void TableTennisPlayer::Name() const
{
std::cout << lastname << ", " << firstname;
} RatedPlayer::RatedPlayer (unsigned int r, const string & fn,
const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht)
{
rating = r;
}
RatedPlayer::RatedPlayer (unsigned int r, const TableTennisPlayer & tp)
: TableTennisPlayer(tp), rating(r){}

派生类的构造函数必须给新成员(如果添加了的话)和基类的成员提供数据。派生类不能直接访问基类的私有成员,而必须通过基类方法访问,例如RatedPlayer构造函数不能直接设置继承的成员firstname, lastname和hasTable, 而必须使用基类的公有方法来访问私有的基类成员。

有关派生类构造函数的要点如下:

  • 首先创建基类对象
  • 派生类构造函数通过成员初始化列表将基类的信息传递给基类构造函数
  • 派生类构造函数应初始化派生类新增的数据成员。

二、使用派生类

  #include <iostream>
#include "tabten.h" int main ()
{
using std::cout;
using std::endl;
TableTennisPlayer player1("Tara", "Boomdea", false);
RatedPlayer rplayer1(, "Mallory", "Duck", true);
rplayer1.Name();//派生类调用基类的方法
cout << ( rplayer1.HasTable() ? (": has a table\n") : ("hasn't a table\n") );
player1.Name();
cout << ( rplayer1.HasTable() ? (": has a table\n") : ("hasn't a table\n") );
cout << "Name: ";
rplayer1.Name();
cout << "; Rating: " << rplayer1.Rating() << endl; //用基类对象初始化派生类
RatedPlayer rplayer2(, player1);
cout << "Name: ";
rplayer2.Name();
cout << "; Rating: " << rplayer2.Rating() << endl; return ;
}

运行结果:

三、protected数据的继承

当基类中的成员数据为protected时,派生类就可以直接访问,而不用通过基类的公共方法去访问这些protected数据,简单来说,派生类可以直接继承protected数据成员,可以免去调用成员函数的开销,

使程序的性能稍稍有所提高。

在一个类的声明中,一个良好的类声明顺序最好是先声明public,然后是protected成员,最后是private成员。

使用protected数据注意的事项

  • 派生类对象不必使用成员函数设置基类的protected数据成员值,派生类很容易将无效的值赋给基类的protected数据,导致对象处于不可靠的状态中
  • 使用protected数据成员,导致派生类成员函数实现可能太依赖基类的实现,实际上,派生类应该只依赖基类提供的服务(即非private成员函数),而不应该依赖基类的实现

多数情况下,使用private数据成员是更好的软件工程的方法,虽然protected数据的继承使程序的性能稍稍有所提高,但是代码优化就交给编译器去做好了,这样的话代码更易于维护、修改和调试,一句话,除非万不得已,尽量不使用protected数据的继承。

“程序员应该致力于编写符合软件工程原则的代码,而将优化的问题留给编译器去做”。一条好的准则是:“不要怀疑编译器”。

四、补充

派生类不会继承基类的构造函数、析构函数和重载的赋值运算符,但是派生类可以调用基类的构造函数、析构函数和重载的赋值运算符函数。

当由基类派生出一个类时,继承基类的方式三种,即public继承、protected继承和private继承。但实际情况是,一般很难采用private继承和protected继承,而且使用时需十分小心。

  • 当从public基类派生一个类时,基类的public成员成为派生类中的public成员,基类的protected成员成为派生类中的protected成员。派生类永远不能直接访问基类的private成员,但是可以通过调用基类的public和protected成员函数进行访问
  • 当从protected基类派生一个类时,基类的public和protected成员都变成派生类中的protected成员
  • 当从private基类派生一个类时,基类的public和protected成员都变成派生类中的private成员
  • private和protected继承不是“is-a”关系

下图是派生类对基类成员的访问权限的一个总结:

C++学习笔记之继承的更多相关文章

  1. c++学习笔记之继承篇

    title: c++学习笔记之继承篇 date: 2017-03-26 16:36:33 tags: [c++,继承,public,virtual,private,protected] categor ...

  2. 1.8(java学习笔记)继承与方法的重写

    继承 在java中可以通过继承提高代码的复用率. 例如A继承了B,就可以是 例如,首先有一个类似Person,这个类中有有一些属性和方法,我们再新建一个Student类,其中有一部分属性和方法与Per ...

  3. C++学习笔记 封装 继承 多态 重写 重载 重定义

    C++ 三大特性 封装,继承,多态 封装 定义:封装就是将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成类,其中数据和函数都是类的成员,目的在于将对 ...

  4. 0022 Java学习笔记-面向对象-继承、多态、组合

    继承的特点 单继承:每个子类最多只有一个直接父类,注意是直接父类,间接父类个数不限 注意父类的概念:A-->B-->C-->D,在这里,ABC都是D的父类,C是D的直接父类,AB是D ...

  5. swift学习笔记之-继承

    //继承 import UIKit /* 继承(Inheritance): 1.一个类可以继承(inherit)另一个类的方法(methods).属性(properties)和其它特性.当一个类继承其 ...

  6. Java编程思想学习笔记_3(继承,内部类)

    一.继承与清理 如果某个类需要去清理自身的资源,那么必须用心为其创建回收垃圾的方法,而如果此类有导出的子类,那么必须在导出类中覆盖回收的方法,当覆盖被继承类的回收垃圾的方法的时候,需要注意销毁的顺序应 ...

  7. Java编程思想学习笔记_2(继承和多态)

    静态初始化: 静态初始化只在必要的时刻进行.(即当程序需要加载类进入内存的时候,执行静态初始化.静态变量和静态代码块的初始化顺序,按照在代码中声明的顺序老执行.例如:如果要执行某个public类,那么 ...

  8. Java学习笔记之继承

    一.继承的基础 在Java术语中,被继承的类叫超类(superclass)或者父类,继承超类的类叫子类(subclass). 举例说明: class Box { public double width ...

  9. Entity Framework with MySQL 学习笔记一(继承)

    基本上sql中要表示继承关系有3中方式. 分别是,1表继承(TPH),2表继承(TPC),3表继承(TPT) 1表 : Person id type name classroom office 1 s ...

随机推荐

  1. Android ANR分析及解决方案

    一:什么是ANR ANR:Application Not Responding,即应用无响应. ANR定义:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个 ...

  2. VS如何设置OpenCV静态编译

      可以使用opencv提供的静态链接库也可以自己编译静态链接库. 1 使用opencv提供的静态链接库,位置如下图. 首先设置VS配置.有如下几个配置 1 工具->选项->项目和解决方案 ...

  3. [python]Python操作MySQL

    [安装] 安装MySQL 安装MySQL不用多说了,下载下来安装就是,没有特别需要注意的地方. 一个下载地址:点击打开链接 [样例] ? 1 2 3 4 5 6 7 8 9 10 11 12 13 1 ...

  4. Linux securecrt破解

    其实,以前接触过破解的东西,但是很多东西早就忘记了,何况是在Linux环境下. 结果我常识更改时间,哦,不是更改日期,往后推4天,结果显示了26 days remaining. 所以完全可以更改日期来 ...

  5. SQL和NOSQL有区别吗?

    在大数据高速发展的今天,数据量在不断的增加,传统的数据库可能不能满足人们的需求了,这个时候新霸哥注意到了NOSQL出现了可以解决这个问题.我们知道sql数据库可以存储数据和处理数据,但是NOSQL最大 ...

  6. (转)log4j:WARN No appenders could be found for logger 解决方案

    我们在使用Log4j的时候,总是出现: log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.Log ...

  7. 开源框架DNN简介以及安装

    donetnuke 是一款免费的开源cms框架,目前也有收费版,不过免费版也可以适应大家大部分的需求.我前些阵子是老板让我在20天内,做好一个官网并且发布,并且指定使用dnn这个框架,考虑到又可以学习 ...

  8. 《学习OpenCV》练习题第四章第七题abc

    题外话:一直是打算把这本书的全部课后编程题写完的,中间断了几个月,一直忙于其他事.现在开始补上. 这道题我不清楚我理解的题意是不是正确的,这道题可以练习用OpenCV实现透视变换(可以用于矫正在3维环 ...

  9. geeksforgeeks@ Minimum Points To Reach Destination (Dynamic Programming)

    http://www.practice.geeksforgeeks.org/problem-page.php?pid=91 Minimum Points To Reach Destination Gi ...

  10. MySQL/MariaDB/Percona数据库升级脚本

    MySQL/MariaDB/Percona数据库升级脚本截取<OneinStack>中upgrade_db.sh, 一般情况下不建议升级数据库版本,该脚本专提供给各位版本控们.为防止大版本 ...