<c++ primer plus>第六版
13 类继承
- 面向对象编程的主要目的之一是提供可重用的代码.
- 传统的C函数库通过预定义/预编译的函数(strlen(), rand()等)提供可重用性. 但函数库也有局限, 就是无法根据特定需求对函数进行扩展和修改.
- c++类提供了更高层次的重用性, 可以通过类继承对类进行扩展和修改.
13.1 一个简单的基类
TableTennisPlayer类
.h文件, 用于定义class, 函数原型等
//tabtenn0.h -- a table tennis base class
#ifndef TABTENN0_H_
#define TABTENN0_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
.cpp文件用于定义class中的函数
//tabtenn0.cpp -- simple base class methods
#include "tabtenn0.h"
#include <iostream>
//TableTennisPlayer::TableTennisPlayer(const string &fn, const string &ln, bool ht): firstname(fn), lastname(ln), hasTable(ht)
//{
//}
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这个类
// usett0.cpp -- using a base class
#include <iostream>
#include "tabtenn0.h" //注意这里只include .h文件, 不include .cpp文件, 只需要class定义及函数原型即可.
int main(void)
{
using std::cout;
TableTennisPlayer p1("Chuck", "Blizzard", true);
TableTennisPlayer p2("Tara", "Boomdea", false);
p1.Name();
if (p1.HasTable())
cout << ": has a table.\n";
else
cout << ": hasn't a table.\n";
p2.Name();
if (p2.HasTable())
cout << ": has a table.\n";
else
cout << ": hasn't a table.\n";
return 0;
}
编译+运行
g++ usett0.cpp tabtenn0.cpp -o usett0.exe #注意, 编译文件列表中没有.h文件
usett0.exe
13.1.1 派生一个类
//以TableTennisPlayer为基类, 派生一个类RatedPlayer
class RatedPlayer : public TableTennisPlayer
{
private:
unsigned int rating; //add a data member
public:
RatedPlayer(
unsigned int r=0,
const string &fn="none",
const string &ln="none",
bool ht=false
);
RatedPlayer(
unsigned int r=0,
const TableTennisPlayer &tp
);
unsigned int Rating() const{ //add a method
return rating;
}
void ResetRating(unsigned int r){ //add a method
rating=r;
}
}
注意: 构造函数必须给新成员(如果有的话)和继承的成员提供数据.
13.1.2 构造函数: 访问权限
派生类不能直接访问基类的私有成员, 需要通过基类方法来访问.
所以一般来说, 派生类的构造函数必须使用基类的构造函数.
创建派生类对象时, 程序产生创建基类对象. c++使用成员初始化列表语法来完成这种工作.
例: 一个派生类的构造函数
RatedPlayer::RatedPlayer
(
unsigned int r,
const string &fn,
const string &ln,
boot ht
): TableTennisPlayer(fn, ln, ht) //在成员初始化列表中调用基类的构造函数.
{
rating = r;
}
如果上述构造函数中没有显式地调用基类的构造函数, 则将使用基类的默认构造函数.
即:
RatedPlayer::RatedPlayer
(
unsigned int r,
const string &fn,
const string &ln,
boot ht
) //在成员初始化列表中没显式调用基类的构造函数.
{
rating = r;
}
等价于:
RatedPlayer::RatedPlayer
(
unsigned int r,
const string &fn,
const string &ln,
boot ht
): TableTennisPlayer() //等价于使用默认构造函数
{
rating = r;
}
对于如下代码
RatedPlayer::RatedPlayer
(
unsigned int r,
const TableTennisPlayer &tp
): TableTennisPlayer(tp) //传给基类的变量tp的类型是TableTennisPlayer&, 所以将调用基类的复制构造函数.
{
rating = r;
}
13.1.3 使用派生类
13.1.4 派生类和基类之间的特殊关系
- 派生类可以使用基类的方法(前提是该方法不能是私有方法).
- 基类指针可以在不进行显式类型转换的情况下指向派生类对象(但只能访问基类的方法).
- 基类引用可以在不进行显式类型转换的情况下引用派生类对象.
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
TableTennisPlayer & rt = = rplayer; //基类引用, 引用派生类对象
TableTennisPlayer * pt = = &rplayer; //基类指针, 指向派生类对象
- 如果一个函数的参数是基类引用(或基类指针), 则这个函数也可以接受派生类引用(或派生类指针)作为参数.
- 可以将基类对象初始化为派生类对象.
RatedPlayer olaf1(...); //派生类对象
TableTennisPlayer olaf2(olaf1); //用派生类对象 对 基类对象 进行初始化.
- 将派生对象赋值给基类对象, 这种情况下程序将使用隐式重载赋值运算符.
RatedPlayer olaf1(...); //派生类对象
TableTennisPlayer winner; //基类对象.
winner = olaf1; //将派生对象赋值给基类对象, 将使用隐式重载赋值运算符:
//TableTennisPlayer &operator=(const TableTennisPlayer &) const
13.2 继承: is-a关系
c++有3种继承方式: 公有继承, 保护继承, 私有继承.
其中, 公有继承是最常用的方式, 它建立一种is-a关系, 即派生类对象也是一个基类对象, 如果一个操作可以在基类对象上执行, 则这个操作也可以在派生对象上执行.