本章内容:
- is-a关系的继承
- 保护以公有方式从一个类派生出另一个类
- 保护访问
- 构造函数初始化列表
- 向上和向下的强制转换
- 虚成员函数
- 早期联编与晚期联编
- 抽象基类
- 纯虚函数
- 何时及如何使用公有继承
13.1 一个简单基类
head
#include <string>
#ifndef GOLF_H_INCLUDED
#define GOLF_H_INCLUDED
using std::string;
class TableTennisPlayer
{
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 RatePlayer :public TableTennisPlayer
{
private:
unsigned int rating;
public:
RatePlayer(unsigned int r = 0, const string & fn = "none", const string & ln = "none", bool ht = false): TableTennisPlayer(fn, ln, ht), rating(r){}
RatePlayer(unsigned int r, const TableTennisPlayer & tp);
unsigned int Rating() const {return rating;}
void RessetRating (unsigned int r) {rating = r;}
};
#endif // GOLF_H_INCLUDED
definition
#include <iostream>
#include "golf.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 << std::endl;
}
RatePlayer::RatePlayer(unsigned int r, const TableTennisPlayer & tp):TableTennisPlayer(tp), rating(r){}
13.3 多态公有继承
两种方法:
- 派生类中重新定义基类方法
- 使用虚方法
definiton
#include <string>
#ifndef GOLF_H_INCLUDED
#define GOLF_H_INCLUDED
using std::string;
class Brass
{
string m_fullname;
long m_acctNum;
double m_balance;
public:
Brass(const string & fullname = "none", long acctNum = -1, double balance = 0.0);
void deposit(double amt);
virtual void Withdraw(double amt);
double Balance() const;
virtual void Vieacct() const;
virtual ~Brass(){};
};
class BrassPlus : public Brass
{
double m_maxloan;
double m_rate;
double m_owesBank;
public:
BrassPlus(const string& fullname = "none", long acctNum = -1, double balance = 0.0, double ml = 500, double r = 0.11125);
BrassPlus(const Brass & ba, double ml = 500, double r = 0.11125);
virtual void ViewAcct() const;
virtual void Withdraw(double amt);
void ResetMax(double m) {m_maxloan = m;}
void ResetRate(double r) {m_rate = r;}
void ResetOwes() {m_owesBank = 0;}
};
#endif //
上面的定义中使用了virtual关键字,该关键字决定该方法所使用的实现方法是哪一种,如果没有virtual,当使用引用或指针调用方法时,调用的是指针类型的类方法,有virtual时调用的是指针所指类型的类方法。
另外,基类中声明为虚方法的函数在派生类中会自动生成虚方法,但是可以自己打出来标识出虚方法,而且在定义中可以省略。
测试
// contain function called in file1
#include <iostream>
#include "golf.h"
using std::cout;
using std::endl;
using std::string;
/*typename std::ios_base::fmtflags format;
typename std::streamsize precis;
format setFormat();
void restore(format f, preces p);*/
Brass::Brass(const string & fn ,long an, double bal)
{
m_fullname = fn;
m_acctNum = an;
m_balance = bal;
}
void Brass::deposit(double amt)
{
if(amt > 0)
m_balance += amt;
else
cout << "Negative deposit not allowed\n";
}
void Brass::Withdraw(double amt)
{
//setup ###.##format
cout << std::fixed;
cout.precision(2);
cout.setf(std::ios_base::showpoint);
if(amt < 0)
{
cout << "Withdrawal amount must be positive;" << "withdrawal canceled.\n";
}
else if(m_balance >= amt)
m_balance -= amt;
else
cout << "You are poor ,how much money did you get you have no conscious.\n";
}
double Brass::Balance() const
{
return m_balance;
}
void Brass::ViewAcct() const
{
// setup ###.## format
cout << std::fixed;
cout.precision(2);
cout.setf(std::ios_base::showpoint);
cout << "Client: " << m_fullname << endl;
cout << "Account Number: " << m_acctNum << endl;
cout << "Balance: $" << m_balance << endl;
}
//BrassPlus methods
BrassPlus::BrassPlus(const string &fn, long an, double bal, double ml, double r) : Brass(fn, an, bal)
{
m_maxloan = ml;
m_owesBank = 0;
m_rate = r;
}
BrassPlus::BrassPlus(const Brass & b, double ml, double r) : Brass(b)
{
m_maxloan = ml;
m_owesBank = 0;
m_rate = r;
}
//redefine Viewacct()works
void BrassPlus::ViewAcct()const
{
//setup format
cout << std::fixed;
cout.precision(2);
cout.setf(std::ios_base::showpoint);
Brass::ViewAcct();
cout << "Maximum loan: $" << m_maxloan <<endl;
cout << "Owed to bank: $" << m_owesBank <<endl;
cout.precision(3);
cout << "Loan Rate: " << 100 * m_rate <<endl;
}
void BrassPlus::Withdraw(double amt)
{
cout << std::fixed;
cout.precision(2);
cout.setf(std::ios_base::showpoint);
double bal = Balance();
if(bal >= amt)
Brass::Withdraw(amt);
else if(amt <= bal + m_maxloan - m_owesBank)
{
m_owesBank += (amt - bal) * m_rate;
cout << "Bank advance: $" << amt - bal << endl;
cout << "Finance charge: $" << (amt - bal) * m_rate << endl;
deposit(amt - bal);
Brass::Withdraw(amt);
}
else
cout << "You shall not take my money away.\n";
}
但是上方代码没有使用虚方法的特性,因为使用对象来调用的方法,不是用指针
接下来用一个基类的指针数组来存储不同的类型,从而使用虚方法的特性
#include <iostream>
#include <string>
#include "golf.h"
int main(void)
{
using std::cin;
using std::cout;
using std::endl;
Brass* p_client[4];
std::string temp;
long tempnum;
double tempbal;
char kind;
for(int i = 0; i < 4; i++)
{
cout << "Enter client name: ";
getline(cin, temp);
cout << "Enter client's account number: ";
cin >> tempnum;
cout << "Enter client's balance: ";
cin >> tempbal;
cout << "Enter 1 for Brass Account or 2 for BrassPlus Account: ";
while(cin >> kind && (kind != '1'&& kind != '2'))
cout << "Enter either 1 or 2: ";
if (kind == '1')
p_client[i] = new Brass(temp, tempnum, tempbal);
else
{
double tmax, trate;
cout << "Enter the overdraft limit: $";
cin >> tmax;
cout << "Enter the interest rate as a decimal fraction: ";
cin >> trate;
p_client[i] = new BrassPlus(temp, tempnum, tempbal, tmax, trate);
}
while(cin.get() != '\n')
continue;
}
cout << endl;
for (int i = 0; i < 4; i++)
{
p_client[i]->ViewAcct();
cout << endl;
}
for(int i = 0; i < 4; i++)
{
delete p_client[i];
}
cout << "done";
return 0;
}
4.为何要使用虚析构函数
如果不用,那在上述的基类指针数组中,就只有基类对象会被释放,其他的不会调用析构函数
virtual类方法类似与一个家庭里的家族文化,传承下来的,大家都用,但如果不加,就是一直家中实体的东西,就只有一个,不能大家都用
13.4.2 虚成员函数与动态联编
动态联编:在程序运行的时候决定可执行代码块
静态联编:在编译的时候决定执行代码块
2.虚函数的工作原理
简单的说一下:对于提供虚函数声明的基类以及其派生类对象,都会生成一个虚函数表,虚函数表中存储的是各个虚函数的地址,而每个对象都会有个指针用于指向虚函数表的表头地址
此时如果派生类中的虚函数有提供新的定义,就在派生类对象的虚函数表中添加新定义的函数地址,如果没有就存储其基类的虚函数地址。
调用的时候就从这个虚函数表来查找调用什么位置的函数执行
13.4.3 有关虚函数注意事项
- 关键字:virtual
- 调用的是所指向的对象类方法而不是指针类型的方法
- 如果定义的类作为基类,则派生类中重新定义的函数都应用virtual来声明
1.构造函数
构造函数不能是虚函数,因为创建的时候调用的是派生类的构造函数,而在派生类的构造函数中使用基类的构造函数,所以虚的也没有意义
2.析构函数
基类需要一个虚析构函数
3.友元
不能是虚函数,因为只有成员函数才能是虚函数,友元函数不是成员函数
4.没有重新定义
使用最新的虚函数版本
5.重新定义将隐藏方法
在派生类中重新定义的成员函数,将隐藏其基类中的同名函数的方法,也就是说只有新的这个能用,这 和重载是不同的
13.5 访问控制protected
关键字:protected
特点:与private在基类中没有区别,而在派生类中有区别,就是派生类可以直接访问基类的protected成员,不能访问基类的private成员,就像子女可以打开父母的遗产的保险柜,但是不能知道他们自己私人的很多事情
13.6 抽象基类(abstract base class)
对于圆是一种特殊的椭圆这件事,如果将圆从椭圆类中派生出来则非常麻烦,不如重新定义,但重新定义又忽略了他们的共同点,这时候使用一个ABC将他们两者的共同点创建为一个类,再从这个类中派生出两者
中间不能实现的函数就作为纯虚函数,后面=0就行了
包含纯虚函数的类只能作为基类,不能被实例化
抽象基类的实现就是在之前的那边添加一个基础抽象类AcctABC,把两种账号都从此基类派生出来,就可以用一个AcctABC的指针数组管理两种派生类了。
13.7 继承和动态内存分配
13.7.1 派生类不用new
基类用new,派生类不用,则在派生类中不用显式析构函数等。
13.7.2 派生类使用new
必须为派生类定义显式的析构函数、复制构造函数和赋值运算符重载
13.7.3 动态分配继承实例
其中有使用friend函数来重载<<运算符。当派生类想要重载时,在输出中先强制转换为基类,用一次基类的<< 运算符,再输出派生类的新数据成员
动态分配的派生类要单独编写析构函数
head
#include <iostream>
#ifndef GOLF_H_INCLUDED
#define GOLF_H_INCLUDED
class baseDMA
{
char * label;
int rating;
public:
baseDMA(const char * l = "NULL", int r = 0);
baseDMA(const baseDMA & rs);
virtual ~baseDMA();
baseDMA & operator=(const baseDMA & rs);
friend std::ostream & operator<<(std::ostream & os, const baseDMA & rs);
};
class lacksDMA : public baseDMA
{
enum { COL_LEN = 40};
char color [COL_LEN];
public:
lacksDMA(const char * c = "blank", const char * l = "NULL", int r = 0);
lacksDMA(const char * c, const baseDMA & rs);
friend std::ostream & operator<<(std::ostream & os, const lacksDMA & rs);
};
class hasDMA : public baseDMA
{
char * style;
public:
hasDMA(const char * s = "none", const char * l = "NULL", int r = 0);
hasDMA(const char * s, const baseDMA & rs);
hasDMA(const hasDMA & rs);
~hasDMA();
hasDMA & operator=(const hasDMA & rs);
friend std::ostream & operator<<(std::ostream & os, const hasDMA & rs);
};
#endif // GOLF_H_INCLUDED
definition
// contain function called in file1
#include <iostream>
#include <cstring>
#include "golf.h"
using namespace std;
baseDMA::baseDMA(const char * l, int r)
{
label = new char[strlen(l) + 1];
strcpy(label, l);
rating = r;
}
baseDMA::baseDMA(const baseDMA & rs)
{
label = new char[strlen(rs.label) + 1];
strcpy(label, rs.label);
rating = rs.rating;
}
baseDMA::~baseDMA()
{
delete []label;
}
baseDMA & baseDMA::operator=(const baseDMA & rs)
{
if(this == &rs)
return *this;
delete [] label;
label = new char[strlen(rs.label) + 1];
strcpy(label, rs.label);
rating = rs.rating;
return *this;
}
ostream & operator<<(ostream & os, const baseDMA & rs)
{
os << "Label: " << rs.label << endl;
os << "rating: " << rs.rating << endl;
return os;
}
//lackDMA
lacksDMA::lacksDMA(const char * c, const char * l, int r): baseDMA(l, r)
{
strncpy(color, c, 39);
color[39] = '\0';
}
lacksDMA::lacksDMA(const char * c, const baseDMA & rs) : baseDMA(rs)
{
strncpy(color, c, 39);
color[39] = '\0';
}
ostream & operator<<(ostream & os, const lacksDMA & rs)
{
os << (const baseDMA &) rs;
os << "Color: " <<rs.color << endl;
return os;
}
hasDMA::hasDMA(const char * s, const char * l, int r):baseDMA(l, r)
{
style = new char[strlen(s) + 1];
strcpy(style, s);
}
hasDMA::hasDMA(const char * s, const baseDMA & rs):baseDMA(rs)
{
style = new char[strlen(s) + 1];
strcpy(style, s);
}
hasDMA::hasDMA(const hasDMA & hs):baseDMA(hs)
{
style = new char[strlen(hs.style) + 1];
strcpy(style, hs.style);
}
hasDMA::~hasDMA()
{
delete [] style;
}
hasDMA & hasDMA::operator=(const hasDMA & hs)
{
if(this == &hs)
return *this;
baseDMA::operator=(hs);
delete [] style;
style = new char[strlen(hs.style) + 1];
strcpy(style, hs.style);
return *this;
}
ostream & operator<<(ostream & os, const hasDMA & hs)
{
os << (const baseDMA &)hs;
os << "Style: " << hs.style << endl;
return os;
}
13.8 类设计回顾
13.8.1 编译器生成的成员函数
- 默认构造函数
- 复制构造函数
- 赋值运算符重载
13.8.2 其他类方法
- 构造函数
- 析构函数
- 转换
- 按值传递对象和传递引用
- 返回对象和返回引用
- 使用const
13.8.3 公有继承的考虑因素
- is-a关系
- 什么不能被继承
- 赋值运算符
- 私有成员和保护成员
- 虚方法
- 析构函数
- 友元函数