C++Primer Plus书之--C++类继承, 派生类和基类之间的关系(子类与父类之间的关系)

基类: 原始类, 也就是最顶层的类

派生类: 也叫做继承类, 是也就是基类的子类, 是继承自基类的类

先来看定义的一个基类, 及对应的实现:

// tabtennis0.h
#ifndef TABTENNIS_H_
#define TABTENNIS_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

第二个文件:

// tabtennis0.cpp
#include "tabtennis0.h"
#include <iostream>

// 构造函数, 使用了成员初始化列表的方法
TableTennisPlayer::TableTennisPlayer(const string & fn, const string & ln, bool ht): firstname(fn), lastname(ln), hasTable(ht)
{
	
}

void TableTennisPlayer::Name() const
{
	std::cout << lastname << ", " << firstname;
}

第三个文件: 

// usetabletennis.cpp
#include <iostream>
#include "tabtennis0.h"

int main(void)
{
	using std::cout;
	using std::endl;
	TableTennisPlayer player1("Tom", "Biz", true);
	TableTennisPlayer player2("Tara", "Boo", false);
	player1.Name();
	if(player1.HasTable())
		cout << ": has a table " << endl;
	else
		cout << ": hasn't a table" << endl;
	player2.Name();
	if(player2.HasTable())
		cout << ": has a table " << endl;
	else
		cout << ": hasn't a table" << endl;
	
	return 0;
}

程序运行结果为:

下面我们来定义一个派生类(java中的子类):

派生一个类:

例如:RatedPlayer类是继承自TableTennisPlayer类, 可以这么写:

class RatedPlayer : public TableTennisPlayer
{
	...
}

冒号指出RatedPlayer类的基类是TableTennisPlayer类. 上述特殊的声明头表明TableTennisPlayer是一个共有基类.

派生类对象包含基类对象. 使用共有派生类, 基类的共有成员将成为派生类的共有成员; 基类的私有部分也将成为派生类的一部分, 但只能通过基类的共有和保护方法访问.

Ratedplayer对象将具有以下特征:

1.派生类对象存储了基类的数据成员(派生类继承了基类的实现);

2.派生类对象可以使用基类的方法(派生类继承了基类的实现);

派生类需要有自己的构造函数, 派生类还可以根据需要添加额外的数据成员和成员函数.

例如:

class RatedPlayer: public TableTennisPlayer
{
private:
	unsigned int rating;
	
public:
	RatedPlayer(unsigned int r = 0, 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;}
};

派生类不能直接访问基类的私有成员, 必须通过基类的共有方法进行访问.

创建派生类对象时, 程序首先创建基类对象. 例如:

RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht): TableTennisPlayer(fn, ln, ht)
{
	rating = r;
}

其中:TableTennisPlayer(fn, ln, ht)是成员初始化列表, 它是可执行的代码, 调用TableTennisPlayer构造函数.

假如程序包含以下代码:

RatedPlayer rplayer1(1140, "Mallory", "Duck", true);

则RatedPlayer构造函数将把实参"Mallory", "Duck", true赋给形参fn, ln和ht, 然后将这些参数作为实参传递给TableTennisPlayer构造函数, 后者创建一个嵌套TableTennisPlayer对象, 并将"Mallory", "Duck", true存储在该对象中, 然后 程序进入RatedPlayer构造函数体, 完成RatedPlayer对象的创建, 并将参数r的值赋给rating成员.

调用派生类的构造函数的时候, 应该首先调用基类的构造函数, 假如我们再派生类的构造函数中没有通过成员初始化列表调用父类的构造函数:

RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht)
{
	rating = r;
}

如果像上面这样不调用基类的构造函数, 程序将使用默认的基类构造函数, 也就是上面的代码和下面这个等效:

RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht): TableTennisPlayer()
{
	rating = r;
}

除非要使用默认构造函数, 否则应显示调用正确的基类构造函数.

也可以用这种方式来定义派生类的构造函数:

RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp): TableTennisPlayer(tp)
{
	rating = r;
}
或:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp): TableTennisPlayer(tp), rating(r)
{
	
}

派生类构造函数注意点:

1.首先创建基类对象

2.派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数

3.派生类构造函数应初始化派生类新增数据成员

4.释放对象的时候顺序与创建对象的顺序相反, 先执行派生类的析构函数, 再执行基类的析构函数

看一个使用派生类的完整的例子:

第一个文件:

// tabtennis1.h
#ifndef TAGTENNIS1_H_
#define TAGTENNIS1_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;};
};

class RatedPlayer: public TableTennisPlayer
{
private:
	unsigned int rating;
	
public:
	RatedPlayer(unsigned int r = 0, 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;}
};

#endif

第二个文件:

// tabtennis1.cpp
#include "tabtennis1.h"
#include <iostream>

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)
{
}

第三个文件:

// usetabletennis1.cpp
#include <iostream>
#include "tabtennis1.h"

int main(void)
{
	using std::cout;
	using std::endl;
	TableTennisPlayer player1("Tom", "Jack", false);
	RatedPlayer rplayer1(1140, "Def", "Abc", true);
	// 使用的是基类的方法
	rplayer1.Name();
	if(rplayer1.HasTable())
		cout << ": has a table. " << endl;
	else
		cout << ": hasn't a table ." << endl;
	player1.Name();
	if(player1.HasTable())
		cout << ": has a table. " << endl;
	else
		cout << ": hasn't a table ." << endl;
	
	cout << "Name: ";
	rplayer1.Name();
	cout << ", Rating: " << rplayer1.Rating() << endl;
	// 使用父类对象初始化派生类
	RatedPlayer rplayer2(1212, player1);
	cout << "Name: ";
	rplayer2.Name();
	cout << ", Rating: " << rplayer2.Rating() << endl;
	
	return 0;
}

程序运行结果为:

 

派生类和基类之间的特殊关系:

1.派生类可以使用基类的非私有方法

2.基类指针可以在不进行显示类型转换的情况下指向派生类对象(这种特点是单向的, 不能反过来使用即不能将积累对象/地址赋给派生类的引用/指针), 基类引用可以在不进行显示类型转换的情况下引用派生类对象(这个也是实现多态的重要方式):

RatedPlayer rplayer1(1140, "Tom", "Jack", true);
TableTennisPlayer & rt = rplayer1;
TableTennisPlayer * pt = &rplayer1;
rt.Name();
pt->Name();

3.基类指针/引用指向派生类对象的时候, 指针/引用只能调用基类方法(这点和java类似), 不能使用派生类的方法

由于这种特殊的关系, 我们可以写出如下的代码:

RatedPlayer olaf1(163, "Olaf", "Loaf", true);
TableTennisPlayer olaf2(olaf1);

要初始化olaf2, 需要匹配的构造函数原型如下:

TableTennisPlayer(const RatedPlayer &);

类定义中没有这样的构造函数, 但存在隐式复制构造函数:

TableTennisPlayer(const TableTennisPlayer &);

形参是基类引用, 因此它可以引用派生类, 这样就可以用olaf1来初始化olaf2了, 同样也可以将派生对象赋给基类对象

RatedPlayer olaf1(163, "Olaf", "Loaf", true);
TableTennisPlayer winner;
winner = olaf1;

这种情况下将使用隐式重载赋值运算符:

TableTennisPlayer & operator=(const TableTennisPlayer &) const;

 

 

 

 

 

 

 


版权声明:本文为c1392851600原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。