文章目录
前言
本文简单介绍C++三大特性之一——继承,并通过简单代码进行说明。
一、类继承是什么 & 为什么要继承?
在大型项目中,使用已经经过测试的代码可以节省时间,同时有助于避免在程序中引入新的bug。尽管C++可以像C语言对函数重载来提供可重用的代码,但这需要重新编写代码。
所以C++的类提供了更高层次的代码重用性,叫做类继承,即:从已有的类派生出新的类,而派生类继承了原有类(基类)的特征,包括数据成员、成员函数等,同时派生类拥有不同于基类的特征。
继承有三种方式:公有继承(public)、保护继承(protected)和私有继承(private)。本文主要从公有继承的角度思考。
二、代码示例
0.派生类需要在继承特性中添加的东西
- 派生类需要自己的构造函数
- 派生类可以根据需要添加额外的数据成员和成员函数
1.基类和派生类的声明
代码如下(示例):
#ifndef TABTENN1_H_
#define TABTENN1_H_
#include <string>
//simple base class
class TableTennisPlayer{
private:
std::string m_FirstName;
std::string m_LastName;
bool m_HasTable;
public:
TableTennisPlayer(const std::string & fn = "none",
const std::string & ln = "none", bool ht = false);
void Name()const;
bool HasTable() const { return m_HasTable; };
void ResetTable(bool v) { m_HasTable = v; };
};
//simple derived class
class RatedPlayer : public TableTennisPlayer{
private:
unsigned int m_Rating;
public:
RatedPlayer(unsigned int r, const TableTennisPlayer & fn,
const std::string & ln, bool ht);
RatedPlayer(unsigned int r = 0, const std::string & fn = "none",
const std::string & ln = "none", bool ht = false);
RatedPlayer(unsigned int r, const TableTennisPlayer & tp);
unsigned int Rating() const { return m_Rating; }
void ResetRating(unsigned int r) { m_Rating = r; }
};
#endif
2.类方法的定义
代码如下(示例):
#include "tabtenn1.h"
#include <iostream>
TableTennisPlayer::TableTennisPlayer(const std::string & fn, const std::string & ln,
bool ht) : m_FirstName(fn), m_LastName(ln), m_HasTable(ht) {}
void TableTennisPlayer::Name()const{
std::cout << m_LastName << ", " << m_FirstName;
}
// RatedPlayer methods
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & fn,
const std::string & ln, bool ht){
m_Rating = r;
}
RatedPlayer::RatedPlayer(unsigned int r, const std::string & fn,
const std::string & ln, bool ht) : TableTennisPlayer(fn, ln, ht){
m_Rating = r;
}
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp)
: TableTennisPlayer(tp), m_Rating(r)//使用隐式拷贝构造函数
{
}
3. 构造函数
派生类不能直接访问基类的私有成员,必须通过基类的成员函数进行访问。
在创建派生类对象时,必须先创建基类对象。因为派生类对象的构造函数无法直接访问基类TableTennisPlayer
的私有成员变量,它必须使用基类构造函数,C++中使用成员初始化列表来完成这个工作,如下面代码所示:
RatedPlayer::RatedPlayer(unsigned int r, const std::string & fn,
const std::string & ln, bool ht) : TableTennisPlayer(fn, ln, ht){
m_Rating = r;
}
如果省略成员初始化列表,编译器降提供默认的基类构造函数,如下:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & fn,
const std::string & ln, bool ht) // : TableTennisPlayer()
{
m_Rating = r;
}
总结下派生类的构造函数的要点:
- 首先创建基类对象
- 派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数
- 派生类构造函数应初始化派生类新增的数据成员
释放对象的顺序与创建对象的顺序相反,即先执行派生类的析构函数,然后自动调用基类的析构函数。
三、派生类和基类之间的特殊关系
1. 派生类对象可以使用基类的公有或保护方法(派生类对象无法直接使用基类的私有方法)
RatedPlayer rp1(1140, "Mallory", "Duck", true);
rp1.Name(); //输出:Duck, Mallory
Name()
方法是基类TableTennisPlayer
的公有成员方法,基类TableTennisPlayer
被派生类RatedPlayer
继承,派生类对象rp1
可以调用基类TableTennisPlayer
的Name()
方法。
2. 基类指针可以在不进行显示类型转换的情况下指向派生类对象
RatedPlayer rp1(1140, "Mallory", "Duck", true);
TableTennisPlayer* p = &rp1;
p->Name(); //输出:Duck, Mallory
3. 基类引用可以在不进行显式类型转换的情况下应用派生类对象
RatedPlayer rp1(1140, "Mallory", "Duck", true);
TableTennisPlayer & rt = rp1;
rt.Name(); //输出:Duck, Mallory
4. 将基类对象赋给派生类引用或指针时,编译器会报错
TableTennisPlayer player("Betsy", "bloop", true);
RatedPlayer & rp1 = player;//error: non-const lvalue reference to type 'RatedPlayer' cannot bind to a value of unrelated type 'TableTennisPlayer
RatedPlayer* rp2 = player;//error: no viable conversion from 'TableTennisPlayer' to 'RatedPlayer *'
总结
本文主要介绍了C++的三大特性之二——继承,并通过简单的代码进行展示。