一、类的继承
面向对象编程的重要目的就是代码的可复用性,提供可重用的代码。尤其是重用经过测试的代码比重新写代码要好得多。
C++提供了一个比较好的方法来扩展和修改类。即类的继承,它可以从已有的类派生出新的类,而派生类继承了原有类(基类)的数据和方法。
尤其是继承机制只需要提供新的特性,甚至不需要访问基类代码就可以派生出类,比如库提供了类方法的头文件和编译后的代码文件,仍可以使用库中的类派生出新的类。
1.1、基类示例
从一个类派生出另一个类时,原始类被称为基类,继承类称为派生类。举例说明一个基类:
// tabtenn0.h
#ifndef TABTENN0_H_
#define TABTENN0_H_
#include <string.h>
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};
};
//tabtenn0.cpp
#include "tabtenn0.h"
#include <iostream>
TableTennisPlayer::TableTennisPlayer(const string & fn, const string & ln, bool ht):firstName(fn),lastName(ln),hasTable(ht)
{}
void TableTennisPlayer::name()
{
std::cout<<lastName<<","<<firstName;
}
TableTennisPlayer player1("Chuck", "Bliz", true);
TableTennisPlayer player2("Tara", "Boom", false);
额外知识点:string类有一个将const char * 作为参数的构造函数,所以构造函数内形参声明为const string &,但是使用C风格的字符串初始化string对象时,将自动调用这个构造函数。
对于本例中的构造函数,可以将string对象或C风格字符串作为参数传入,前者将调用接受const string &作为参数的string构造函数,后者将调用接受const char * 作为参数的string构造函数。
1.2、派生类示例
需要增加一个具有更多特性的类,但是它还是一个乒乓球员类,由TableTennisPlayer类派生而来。
class RatesPlayer : public TableTennisPlayer
{
...
}
冒号指出RatedPlayer类的基类是TableTennisPlayer类,public关键字表明这是公有继承方式:派生类对象包含基类对象,此时基类的公有成员将成为派生类的公有成员,基类的私有成员将成为派生类的一部分,但只能通过基类的公有和保护方法访问。
RatedPlayer对象将通过继承自动具有以下特征:
1、派生类对象存储了基类的数据成员,但是不能直接访问它
2、派生类对象可以使用基类的方法
继承还需要添加哪些内容:
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 = 0, const TableTennisPlayer & ctp);
unsigned int rating () const { return rating;} //增加了两个方法
void resetRating(unsigned int r) {rating = r;}
}
需要注意派生类的构造函数必须要给新成员和继承的成员同时提供数据,其中基类的成员可以单独使用形参,也可以直接使用基类对象引用作为参数。
1.3、派生类构造函数的权限说明
派生类不能直接访问基类的私有成员,而必须通过基类的方法进行访问。比如基类的构造函数函数必须使用基类的构造函数完成基类私有数据成员的初始化。
创建一个派生类对象时,程序首先调用基类的构造函数创建基类对象,这意味着基类对象在程序进入派生类构造函数之前被创建完成,C++使用成员初始化列表来显示指定要使用的基类构造函数来完成基类继承的数据成员。再调用派生类的构造函数初始化新增的数据成员。
见派生类第一个构造函数的实现:
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::RatedPlayer(unsigned int r, const TableTennisPlayer & ctp):TableTennisPlayer(ctp)
{
rating = r;
}
这里也将TableTennisPlayer 的信息传输给了TableTennisPlayer 的构造函数。
这里也可以将派生类新增的数据成员使用成员初始化列表语法,所以上述第二个构造函数也可以写成:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & ctp):TableTennisPlayer(ctp), rating(r)
{
}
综上所述,派生类构造函数要点:
1、首先创建基类对象,
2、派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数,
3、派生类构造函数应初始化派生类新增的数据成员。
4、要注意派生类对象过期时,程序首先调用派生类的析构函数,在调用基类的析构函数,这与构造函数调用顺序相反。
1.4、基类与派生类的使用与联系
一般可以将基类与派生类的声明放在一个头文件内,便于使用派生类时可以直接访问基类的声明。
同样的,类的方法定义可以放在不同的文件中,但是将定义放在一个文件中更简单。
1、派生类对象可以使用基类的方法,但是不包括私有部分的方法。例如:
RatedPlayer player1(1140, “Mallory”,“Duck”, true);
player1.name();
2、基类指针可以在不进行显示类型转换的情况下指向派生类对象,基类引用可以在不进行显示类型转换的情况下引用派生类对象,且基类指针或基类引用只能用于调用基类方法,不能调用派生类中新增的方法。
一般情况下C++要求引用或指针与赋给的类型要匹配,但是这一规则不包括继承。这种指向是单向的,因为派生类引用能够为基类对象调用派生类的方法,这将会出错。
RatedPlayer player1(1140, “Mallory”,“Duck”, true);
TableTennisPlayer *player2 = player1;
TableTennisPlayer & player3 = player1;
player2.name();
player3->name();