类继承(一)公有继承
OOP的主要目的之一是提供可重用的代码.而C++类提供了很好的重用性.目前很多厂商都提供了类库,类库由类声明和实现构成.但是,有时候我们需要对厂商提供的类进行修改,而C++提供了比直接修改代码更好的方式–类继承.它允许我们从已有的类派生出新的类,而派生类继承了原有类(或称基类)的特征.通过继承派生出的类通常比设计新类要容易得多.
通过继承可以完成的一些工作:
- 可以在基类的基础上添加功能.
- 可以给类添加数据.
- 可以修改类方法.
类继承还有一个好处是,可以在不公开实现的情况下将自己的类分发给其他人,同时允许他们在类中吧添加新特性.
1. 一个简单的例子
1.1 基类
#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;};
};
#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;
}
上面这段代码是一个简单的基类的实现,其中构造函数使用了成员初始化列表语法.
也可以这样写:
TableTennisPlayer::TableTennisPlayer(const string &fn,const string &ln,bool ht)
{
firstname=fn;
lastname=ln;
hasTable=ht;
}
而且我们发现,这个类的类成员使用了
string
类,例如在上面这个构造函数中,首先firstname
会调用string
类的默认构造函数,然后再调用赋值运算符进行深复制把firstname
设置成fn
;
但是使用初始化列表语法可以减少一个步骤,它直接使用string
的复制构造函数把firstname
初始化为fn
.
1.2 派生一个类
我们需要在原有
TableTennisPlayer
类基础上设计一个新的类,它能够包括选手的比赛得分.
class RatedPlayer:public TableTennisPlayer//一个简单的派生类
{
...
}
我们使用
:
指出,RatedPlayer
是TableTennisPlayer
的一个派生类.
而且TableTennisPlayer
是一个公有基类,这种称为公有派生.
派生类对象包含基类对象.
使用公有派生,基类的公有成员会成为派生类的公有成员.
基类的私有部分也会成为派生类的一部分,但是只能通过基类的公有和保护方法访问.上面那段代码表示
RatedPlayer
对象将具有这些特征:
- 派生类对象存储了基类的数据成员
- 派生类对象可以使用基类的方法
所有说,
RatedPlayer
对象存储着选手姓名,是否由球桌,还可以调用Name()
、hasTable()
、ResetTable()
方法
我们还需要在继承特性中添加什么?
- 派生类需要自己的构造函数
- 派生类可以根据需要添加额外的数据成员和成员函数
基类对象和派生类对象的关系图
- 关于构造函数的访问权限考虑
派生类不能直接访问基类的私有成员,必须通过基类方法进行访问,例如,
RatedPlayer
的构造函数不能直接设置继承的成员(firstname
和lastname
和hasTable
),而必须使用TableTennisPlayer
的公有方法类访问私有的基类成员.总之,派生类的构造函数必须使用基类的构造函数.
创建派生类对象时,程序首先创造基类对象.将参数传递给基类构造函数:
一个RatedPlayer
的构造函数的例子:
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
的构造函数,再把rating
的值设置一下.完成派生类对象的创建.总之,派生类对象会嵌套一个基类对象.
如果省略:TableTennisPlayer(fn,ln,ht)
这句话,那么编译器就会自动调用TableTennisPlayer
的默认构造函数.即相当于:
RatedPlayer::RatedPlayer(unsigned int r,const string &fn,const string &ln,bool ht)
:TableTennisPlayer()
{
rating=r;
}
创建派生类对象时,程序首先调用基类构造函数,然后调用派生类的构造函数.基类构造函数初始化继承的数据成员;派生类构造函数主要用于初始化新增的数据成员.派生类的构造函数总是调用一个基类的构造函数.可以用初始化列表语法指明要使用的基类构造函数,否则将使用默认的基类构造函数.
1.3 代码
//类继承1.h
#ifndef DERIVED1
#define DERIVED1
#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
//类继承1.cpp
#include"类继承1.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){}
//类继承1main.cpp
#include<iostream>
#include"类继承1.h"
int main()
{
using std::cin;
using std::cout;
using std::endl;
TableTennisPlayer player1("Tara","Boomdea",false);
RatedPlayer rplayer1(1140,"Mallory","Duck",true);
rplayer1.Name();//派生类对象使用基类方法
if(rplayer1.HasTable())
cout<<": has a table.\n";
else
cout<<": hasn't a table.\n";
player1.Name();//基类对象使用基类方法
if(player1.HasTable())
cout<<": has a table.\n";
else
cout<<": hasn't a table.\n";
cout<<"Name: ";
rplayer1.Name();
cout<<"; Rating: "<<rplayer1.Rating()<<endl;//派生类对象使用派生方法
RatedPlayer rplayer2 (1212,player1);//使用基类对象初始化派生类对象
cout<<"Name: ";
rplayer2.Name();
cout<<"; Rating: "<<rplayer2.Rating()<<endl;
return 0;
}
PS D:\study\c++\path_to_c++> g++ -I .\include\ -o 类继承1 .\类继承1.cpp .\类继承1main.cpp
PS D:\study\c++\path_to_c++> .\类继承1.exe
Duck, Mallory: has a table.
Boomdea, Tara: hasn't a table.
Name: Duck, Mallory; Rating: 1140
Name: Boomdea, Tara; Rating: 1212
1.4 派生类和基类的特殊关系
派生类对象可以使用基类的方法,当然了方法不是私有的
rplayer1.Name();//派生类对象使用基类方法
基类指针可以再不进行显式类型转换的情况下指向派生类对象;基类引用可以再不进行显式类型转换的情况下引用派生类对象.
RatedPlayer rplayer1(1140,"Mallory","Duck",true);
TableTennisPlayer &rt=rplayer1;//基类引用,引用派生类对象
TableTennisPlayer *pt=&rplater1;//基类指针,指向派生类对象
rt.Name();
pt->Name();
但是,基类引用或者指针只能用来调用基类的方法,所以
rt.Rating()
这样的调用派生类方法是不允许的.
C++要求引用和指针的类型与指向的类型匹配,但是这个规则对继承来说是个例外.
然而这种规则是单向的,不可以将基类对象和地址赋给派生类引用和指针.
TableTennisPlayer player1("Tara","Boomdea",false);
RatedPlayer & rr=player1;//不允许把基类对象赋给派生类引用
RatedPlayer * pr=&player1;//不允许把基类对象地址赋给派生类指针
实际上这种规则很有道理.因为派生类对象相当于是基类对象的扩充版,基类对象有的它都有,所以可以把派生类对象看成是一种基类对象来操作;但是基类对象相当于派生类对象的阉割版,它缺少了一些作为派生类对象的数据,所以我们不能把它看出派生类对象来操作.
例如,有一个函数:
void show(const TableTennisPlayer & rt)
{
rt.Name();
if(rt.HasTable())
cout<<": has a table.\n";
else
cout<<": hasn't a table.\n";
}
这个函数的形参
rt
是一个基类引用,那么它可以指向基类或派生类对象,所以如下代码是合理的:
TableTennisPlayer player1("Tara","Boomdea",false);
RatedPlayer rplayer1(1140,"Mallory","Duck",true);
show(player1);
show(rplayer1);
同样的,形参是指向基类的指针的函数,也存在相似的关系.它可以使用基类对象的地址或者派生类对象的地址作为实参.
这种性质体现了,派生类和基类的兼容性.
甚至,我可以用派生类对象来初始化基类对象.
RatedPlayer olaf1(1840,"Olaf","Loaf",true);
TableTennisPlayer olaf2(olaf1);
这是因为,
olaf2
会调用隐式复制构造函数TableTennisPlayer(const TableTennisPlayer &)
来创建olaf2
.
同样的,我可以把派生类对象赋值给基类对象,因为调用了隐式重载赋值运算符.
2. is-a 关系
C++有3种继承方式:公有继承,保护继承和私有继承.但是公有继承是最常用的方式.
一般来说,类和类的关系有5种:is-a 关系, has-a 关系, is-like 关系, is-implemented-as-a 关系, uses-a 关系.
但是对于公有继承来说,我们最好用它创建 is-a 关系;因为你如果使用公有继承来创建其他关系,通常会出现问题.
可以这样说,公有继承就是用来建立 is-a 关系的.
- is-a 关系
派生类对象也是一个基类对象,只要基类对象可以做到的,派生类对象也可以做到,而派生类对象能做到的,基类对象不一定能做到.这个很像是,哲学上讲的普遍性和特殊性的关系.
例如,我们有一个Fruit
类,我们可以以此派生一个Banana
类,我们知道香蕉是一种水果,水果有的共性香蕉都有,而香蕉有的特性,却不一定是是水果的共性.实际上,TableTennisPlayer
和RatedPlayer
就是 is-a 关系.
is-a 关系就是 is-a-kind-of(是一种) 关系.即派生类对象是一种基类对象.
- has-a 关系
公有继承不建立 has-a 关系.
例如,我们不能通过Fruit
类派生出Lunch
类.因为,午餐可以包含水果也可以不包含水果,午餐也不是 is-a-kind-of 水果.但是我们可以明确的一件事是:午餐可以有水果. 那么正确做法是,把Fruit
作为Lunch
的数据成员.
- is-like 关系
公有继承不能建立 is-like 关系,也就是说,它不采用明喻.
例如,我们经常说律师像鲨鱼一样敏锐.但是我们不能从Shark
类种派生出Lawyer
类,因为鲨鱼可以在水下呼吸,律师却不能.继承可以在Shark
类基础上添加属性,但是不能删除属性,就像你不能把水下呼吸属性删除一样.
我们可以这样实现 is-like 关系,先设计一个包含共有属性的类,以它为基础然后用 is-a 关系 或者 has-a 关系,建立一个新类.例如,设计出Sharp
类表示敏锐的事物,然后利用 is-a 关系,派生出一个Lawyer
类.
- is-implemented-as-a 关系
公有继承不能建立 is-implemented-as-a 关系.也就是说它不建立 作为…来实现关系.
例如,可以用数组来实现栈,但是Array
类无法派生出Stack
类.因为数组索引不是栈的属性,而且栈不一定要用数组来实现,它还可以用链表实现.
我们可以这样实现这种关系,让栈包含一个私有的Array
对象成员来隐藏数组的实现.
- uses-a 关系
公有继承不能建立 uses-a 关系.也就是说它不建立 可以使用 关系
例如,电脑可以使用打印机.但是Computer
类无法派生出Printer
类.因为打印机根本就不是电脑.
我们可以这样建立 uses-a 关系,用友元函数或新建一个包含两者的类,来实现打印机和电脑之间的通讯.
3.多态公有继承
我们提供的第一个例子是很简单的,派生类对象使用基类的方法,而未作任何修改。然而,我们有时候希望用一个方法在派生类和基类中的行为是不同的。即方法的行为取决于调用该类方法的对象。这种行为称为多态。
有两种方法实现多态公有继承:
- 在派生类中重新定义基类的方法
- 使用虚方法
3.1 一个银行账户的例子
假如你现在是一个银行的程序员。你收到一个项目要求你开发两个类。一个类表示基本支票账户
Brass
,另一个类表示BrassPlus
类,它增加了透支保护特性。也就是说,如果用户签出一张超出存款余额的支票,银行将支付这张支票,但是需要对超出余额的部分收取费用。关于
Brass
账户的信息:
- 客户姓名
- 账号
- 余额
可以执行的操作:
- 创建账户
- 存款
- 取款
- 显示账户信息
关于
BrassPlus
账户的信息包含一切Brass
账户的信息,并且新增如下信息:
- 透支上限
- 透支贷款利率
- 当前透支总额
不需要新增操作,但是有两种操作的实现不同:
- 对于取款操作,必须考虑透支保护
- 显示操作必须显示
BrassPlus
的其他信息关于透支系统的细节:
BrassPlus
账户限制了客户的透支上限,默认上限是500元- 银行可以修改客户透支上限
BrassPlus
账户对贷款收取利息.默认贷款利率是11.125%- 银行可以修改客户的贷款利率
- 客户欠银行的钱(透支金额加上利息),必须通过现金的方式交给特定的银行员工
一个关键的问题是:能使用公有继承完成类的设计吗?
BrassPlus
类和Brass
满足 is-a 关系。因为只要是Brass
有的,BrassPlus
都有。
3.2 代码实现
//类继承2.h
#ifndef BRASS_H
#define BRASS_H
#include<string>
using std::string;
class Brass
{
private:
string fullName;//客户姓名
long acctNum;//账号
double balance;//余额
public:
Brass(const string &s="Nullbody",long an=-1, double bal=0.0);//构造函数,创建账户
void Deposit(double amt);//存款
virtual void Withdraw(double amt);//取款
double Balance() const{return balance;};
virtual void ViewAcct() const;//显示账户信息
virtual ~Brass(){};
};
class BrassPlus : public Brass
{
private:
double maxLoan;//透支上限
double rate;//贷款利率
double owesBank;//透支总额
public:
BrassPlus(const string &s="Nullbody",long an=-1, double bal=0.0,
double ml=500,double r=0.11125);
BrassPlus(const Brass & ba,double ml=500,double r=0.11125);
virtual void Withdraw(double amt);//取款
virtual void ViewAcct() const;//显示账户信息
void ResetMax(double m){maxLoan=m;}//设置透支上限
void ResetRate(double r){rate=r;}//设置贷款利率
void ResetOwes(){owesBank=0;}//设置透支总额
};
#endif
对于上面这段声明,有几点需要说明一下
-
BrassPlus
类在Brass
类的基础上添加了3个数据成员和3个公有成员函数这一点没什么新鲜的
-
Brass
类和BrassPlus
类都声明了Withdraw()
和ViewAcct()
方法,但是不同类的方法行为不同我们声明了两个方法,基类的限定名是
Brass::ViewAcct()
,派生类版本的限定名是BrassPlus::ViewAcct()
。程序会根据调用对象的类型来确定使用哪个版本。
同样的Withdraw()
也有两个版本,而对于在两个类中行为相同的方法(如Deposit()
和Balance()
),则只需在基类中声明。 -
Brass
类使用了关键词virtual
.使用了virtual
的方法叫做虚方法.使用关键词
virtual
比较复杂。如果方法是通过引用或者指针而不是对象调用的,它将确定使用哪一种方法。如果没有virtual
,程序将根据引用类型或者指针类型选择方法;如果使用了virtual
,程序将根据引用或者指针指向的对象的类型来选择方法。
如果ViewAcct()
不是虚的,则Brass dom; BrassPlus dot; Brass & b1_ref=dom; Brass & b2_ref=dot; b1_ref.ViewAcct();//使用Brass::ViewAcct() b2_ref.ViewAcct();//使用Brass::ViewAcct()
由于引用变量的类型是
Brass
,所有选择了Brass::ViewAcct()
,如果使用指针代替引用,行为会与之类似。
如果ViewAcct()
是虚的,则Brass dom; BrassPlus dot; Brass & b1_ref=dom; Brass & b2_ref=dot; b1_ref.ViewAcct();//使用Brass::ViewAcct() b2_ref.ViewAcct();//使用BrassPlus::ViewAcct()
这两个引用的类型都是
Brass
,但是b2_ref
引用是一个BrassPlus
对象,所以使用的是BrassPlus::ViewAcct()
。如果使用指针代替引用,行为会与之类似。
虚函数这种行为非常方便。因此,经常在基类中将派生类会重新定义的方法声明为虚方法。方法在基类中被声明成虚的后,它在派生类中将自动成为虚方法。但是为了强调,我们在派生类的声明中也可使用关键词virtual
指出哪些函数是虚方法。 -
Brass
类声明了一个虚析构函数,虽然该析构函数不执行任何操作。基类声明了一个虚析构函数。这样做主要是为了确保释放对象时,按照正确的顺序调用析构函数。
如果要在派生类中重新定义基类的方法,通常应将基类方法声明为虚的。
为基类声明一个虚析构函数也是一个惯例。
下面是关于类的实现:
//类继承2.cpp
#include<iostream>
#include"类继承2.h"
using std::cout;
using std::endl;
typedef std::ios_base::fmtflags format;
typedef std::streamsize precis;
format setFormat();
void restore(format f,precis p);
Brass::Brass(const string &s,long an, double bal)//构造函数,创建账户
:fullName(s),acctNum(an),balance(bal){}
void Brass::Deposit(double amt)//存款
{
if(amt<0)
cout<<"negative deposit not allowed;"<<"deposit is cancelled.\n";
else
balance+=amt;
}
void Brass::Withdraw(double amt)//取款
{
format initialState=setFormat();
precis prec=cout.precision(2);
if(amt<0)
cout<<"withdrawal amount must be positive;"<<"withdrawal is cancelled.\n";
else if(amt<=balance)
balance-=amt;
else
cout<<"withdrawal amount of $"<<amt<<" exceeds your balance;"
<<"withdrawal is cancelled.\n";
restore(initialState,prec);
}
void Brass::ViewAcct() const//显示账户信息
{
format initialState=setFormat();
precis prec=cout.precision(2);
cout<<"Client: "<<fullName<<endl;
cout<<"Account Number: "<<acctNum<<endl;
cout<<"Balance: $"<<balance<<endl;
restore(initialState,prec);
}
BrassPlus::BrassPlus(const string &s,long an, double bal,
double ml,double r)
:Brass(s,an,bal),maxLoan(ml),rate(r),owesBank(0.0){}
BrassPlus::BrassPlus(const Brass & ba,double ml,double r)
:Brass(ba),maxLoan(ml),rate(r),owesBank(0.0){}
void BrassPlus::ViewAcct() const//显示账户信息
{
format initialState=setFormat();
precis prec=cout.precision(2);
Brass::ViewAcct();
cout<<"Maximum loan: $"<<maxLoan<<endl;
cout<<"Owed to bank: $"<<owesBank<<endl;
cout.precision(3);//###.###
cout<<"Loan Rate: "<<100*rate<<"%\n";
restore(initialState,prec);
}
void BrassPlus::Withdraw(double amt)//取款
{
format initialState=setFormat();
precis prec=cout.precision(2);
double bal=Balance();
if(amt<=bal)
Brass::Withdraw(amt);
else if(amt<=bal+maxLoan-owesBank)
{//处理透支
double advance=amt-bal;//银行预支金额
owesBank+=advance*(1.0+rate);//透支总额
cout<<"Bank advance: $"<<advance<<endl;
cout<<"Finance charge: $"<<advance*rate<<endl;//手续费(利息)
Deposit(advance);
Brass::Withdraw(amt);
}
else
cout<<"Credit limit exceeded.Transaction cancelled.\n";
restore(initialState,prec);
}
format setFormat()
{
//设置输出格式###.##
return cout.setf(std::ios_base::fixed,
std::ios_base::floatfield);
}
void restore(format f,precis p)
{
cout.setf(f,std::ios_base::floatfield);
cout.precision(p);
}
-
关于对浮点数的输出格式问题
一般我们不使用
cout
直接输出浮点数类型的数字,因为它格式不统一,包括是否显示小数点,保留几位小数等都不是固定的。typedef std::ios_base::fmtflags format; typedef std::streamsize precis; format setFormat() { //设置输出格式###.## return cout.setf(std::ios_base::fixed, std::ios_base::floatfield); } void restore(format f,precis p) { cout.setf(f,std::ios_base::floatfield); cout.precision(p); }
关于
setFormat()
做的事情是,他把浮点数输出格式变成定点,然后返回值是ios_base::fmtflags
类型的,其实就是这个函数返回一个状态即cout
的输出格式。
然后cout.precision(2);
就是来设置精度的,如果采用定点格式输出浮点数,那么该句话的意思就是保留2位小数。
restore()
做的事情就是设置格式和精度。format initialState=setFormat(); precis prec=cout.precision(2); ... restore(initialState,prec);
所以上面这段代码做的事情就是:把
cout
对于浮点数的输出格式改成定点,用initialState
存储这个格式,然后设置输出精度为保留两位小数,用prec
存储这个精度。最后,把cout
的格式和精度都设置成最初的格式。 -
构造函数
这里的构造函数都是使用成员初始化列表语法,将基类信息传递给基类构造函数,然后使用构造函数体初始化
BrassPlus
的新增的数据项。 -
BrassPlus::ViewAcct()
和BrassPlus::Withdraw()
的实现void BrassPlus::ViewAcct() const//显示账户信息 { format initialState=setFormat(); precis prec=cout.precision(2); Brass::ViewAcct(); cout<<"Maximum loan: $"<<maxLoan<<endl; cout<<"Owed to bank: $"<<owesBank<<endl; cout.precision(3);//###.### cout<<"Loan Rate: "<<100*rate<<"%\n"; restore(initialState,prec); }
上面这段代码是实现
BrassPlus::ViewAcct()
的。由于派生类无法访问基类的私有数据,那么只会通过基类的公有方法来访问了,这里通过限定名方法调用了基类的显示账户信息的方法Brass::ViewAcct();
,如果不使用作用域解析符,那么编译器会把它当成BrassPlus::ViewAcct()
。void BrassPlus::Withdraw(double amt)//取款 { format initialState=setFormat(); precis prec=cout.precision(2); double bal=Balance(); if(amt<=bal) Brass::Withdraw(amt); else if(amt<=bal+maxLoan-owesBank) {//处理透支 double advance=amt-bal;//银行预支金额 owesBank+=advance*(1.0+rate);//透支总额 cout<<"Bank advance: $"<<advance<<endl; cout<<"Finance charge: $"<<advance*rate<<endl;//手续费(利息) Deposit(advance); Brass::Withdraw(amt); } else cout<<"Credit limit exceeded.Transaction cancelled.\n"; restore(initialState,prec); }
上面这段代码是
BrassPlus::Withdraw()
的实现。由于派生类无法访问基类的私有数据,但是取款操作肯定需要知道余额,所以我们在基类中添加了一个Balance()
公有方法,这一接口的增加是非常重要的。而修改余额的方法也必须使用公有类方法Deposit()
和Brass::Withdraw()
。
总之,在设计派生类的时候如果发现缺少一些必要的数据项,可以在基类中增加接口。
3.3 使用Brass
类和BrassPlus
类
//类继承2main.cpp
#include<iostream>
#include"类继承2.h"
int main()
{
using std::cout;
using std::endl;
Brass Piggy("Porcelot Pigg",381299,4000.00);
BrassPlus Hoggy("Horatio Hogg",382288,3000.00);
Piggy.ViewAcct();
cout<<endl;
Hoggy.ViewAcct();
cout<<endl;
cout<<"Depositing $1000 into the Hogg Account:\n";
Hoggy.Deposit(1000.00);
cout<<"New balance: $"<<Hoggy.Balance()<<endl;
cout<<"Withdrawing $4200 from the Pigg Acount:\n";
Piggy.Withdraw(4200.00);
cout<<"Piggy Acount balance: $"<<Piggy.Balance()<<endl;
cout<<"Withdrawing $4200 from the Hogg Acount:\n";
Hoggy.Withdraw(4200.00);
Hoggy.ViewAcct();
}
PS D:\study\c++\path_to_c++> g++ -I .\include\ -o 类继承2 .\类继承2.cpp .\类继承2main.cpp
PS D:\study\c++\path_to_c++> .\类继承2.exe
Client: Porcelot Pigg
Account Number: 381299
Balance: $4000.00
Client: Horatio Hogg
Account Number: 382288
Balance: $3000.00
Maximum loan: $500.00
Owed to bank: $0.00
Loan Rate: 11.125%
Depositing $1000 into the Hogg Account:
New balance: $4000
Withdrawing $4200 from the Pigg Acount:
withdrawal amount of $4200.00 exceeds your balance;withdrawal is cancelled.
Piggy Acount balance: $4000
Withdrawing $4200 from the Hogg Acount:
Bank advance: $200.00
Finance charge: $22.25
Client: Horatio Hogg
Account Number: 382288
Balance: $0.00
Maximum loan: $500.00
Owed to bank: $222.25
Loan Rate: 11.125%
上面这段代码只是稍微演示一下,但是没用使用虚方法的特性。看看下面这段代码:
//类继承3.cpp
#include<iostream>
#include<string>
#include"类继承2.h"
const int CLIENTS=4;
int main()
{
using std::cin;
using std::cout;
using std::endl;
using std::string;
Brass *p_clients[CLIENTS];
string temp;
long tempnum;
double tempbal;
char kind;
for(int i=0;i<CLIENTS;i++)
{
cout<<"Enter client's name: ";
std::getline(cin,temp);
cout<<"Enter client's account number: ";
cin>>tempnum;
cout<<"Enter opening 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_clients[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_clients[i]=new BrassPlus(temp,tempnum,tempbal,tmax,trate);
}
cin.ignore();
}
cout<<endl;
for(int i=0;i<CLIENTS;i++)
{
p_clients[i]->ViewAcct();
cout<<endl;
}
for(int i=0;i<CLIENTS;i++)
{
delete p_clients[i];
}
cout<<"done!\n";
}
PS D:\study\c++\path_to_c++> g++ -I .\include\ -o 类继承3 .\类继承3.cpp .\类继承2.cpp
PS D:\study\c++\path_to_c++> .\类继承3.exe
Enter client's name: Harry Fishsong
Enter client's account number: 112233
Enter opening balance: $1500
Enter 1 for Brass Account or 2 for BrassPlus Account: 1
Enter client's name: Dinah Otternoe
Enter client's account number: 121213
Enter opening balance: $1800
Enter 1 for Brass Account or 2 for BrassPlus Account: 2
Enter the overdraft limit: $350
Enter the interest rate as a decimal fraction: 0.12
Enter client's name: Brenda Birdherd
Enter client's account number: 212118
Enter opening balance: $5200
Enter 1 for Brass Account or 2 for BrassPlus Account: 2
Enter the overdraft limit: $800
Enter the interest rate as a decimal fraction: 0.10
Enter client's name: Tim Turtletop
Enter client's account number: 233255
Enter opening balance: $688
Enter 1 for Brass Account or 2 for BrassPlus Account: 1
Client: Harry Fishsong
Account Number: 112233
Balance: $1500.00
Client: Dinah Otternoe
Account Number: 121213
Balance: $1800.00
Maximum loan: $350.00
Owed to bank: $0.00
Loan Rate: 12.000%
Client: Brenda Birdherd
Account Number: 212118
Balance: $5200.00
Maximum loan: $800.00
Owed to bank: $0.00
Loan Rate: 10.000%
Client: Tim Turtletop
Account Number: 233255
Balance: $688.00
done!
上面这段代码中
Brass *p_clients[CLIENTS];
指出数组的每个元素都是一个指向Brass
对象的指针。如果不使用虚方法,p_clients[i]->ViewAcct();
将都调用用Brass::ViewAcct()
。
- 关于为什么需要虚析构函数
如果不使用虚析构函数,那么将只会调用对应于指针类型的析构函数。对于上面这段代码,那么只有
Brass
的析构函数被调用,即使指针指向的是BrassPlus
对象。虽然对于上面代码代码说,由于析构函数什么都不做,所以说虚析构方法可以有可无。但是,我们下次再设计派生类的时候,如果BrassPlus
的析构函数需要做一下操作时,那么我们必须在基类中使用虚析构函数,来确保派生类对象正确被析构。