面相对象的主要目的之一就是提供可重用的代码。
类继承就是从已有的类派生出新的类,而派生类继承了原有类,也就是基类的特征和成员函数。
继承一笔财富比自己白手起家要简单的多,写代码也是一样。
下面是可通过继承来外城的工作:
1、可以在已有类的基础上添加新功能,例如,对于数组类,可以添加数学运算;
2、可以给类添加数据,例如,对于数据串类,可以派生出一个类,并添加指定字符串显示颜色的数据成员;
3、可以修改类成员函数的行为,例如给飞机乘客提供服务的Passenger类,可以派生出提供头等舱服务FirstClassPassenger类。
从一个类派生出另一个类时,原始类称为基类,继承类称为派生类。
webtown俱乐部决定统计乒乓球会会员,俱乐部的程序员需要设计一个简单的TableTennisPlayer类
#ifndef TABLETENNISPLAYER_H
#define TABLETENNISPLAYER_H
#include
#include
using namespace std;
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 // TABLETENNISPLAYER_H
#include "tabletennisplayer.h"
TableTennisPlayer::TableTennisPlayer(const string& fn , const string& ln, bool ht ):firstname(fn),lastname(ln),hasTable(ht)
{
}
void TableTennisPlayer::Name() const
{
cout << lastname << ", " << firstname;
}
TableTennisPlayer类只是记录会员的姓名以及是否有球桌;
这个类是使用string类来存储姓名,比字符串数组更方便,更灵活,更安全;
构造函数使用了成员初始化列表语法,与下面的写法等效。
TableTennisPlayer::TableTennisPlayer(const string& fn , const string& ln, bool ht )
{
firstname = fn;
lastname = ln;
hasTable = ht;
}
使用前面的类
#include
#include "tabletennisplayer.h"
int main()
{
TableTennisPlayer player1("Chuck", "Blizzard", true);
TableTennisPlayer player2("Tara", "Boomdea", false);
player1.Name();
if(player1.HasTable())
cout << ": has a table.\n";
else
cout << ": hasn't a table.\n";
player2.Name();
if(player2.HasTable())
cout << ": has a table.\n";
else
cout << ": hasn't a table.\n";
return 0;
}
输出结果
Blizzard, Chuck: has a table.
Boomdea, Tara: hasn't a table.
webtown俱乐部的一些成员曾经参加过当地的乒乓球竞标赛,需要这样一个类能包含成员在比赛中的比分。与其从0开始,不如从TableTennisPlayer类派生出来一个类。首先将RatedPlayer类声明为从TableTennisPlayer类派生而来
//声明一个派生类
//冒号:前面的是派生类,冒号后面的是基类
//TableTennisPlayer前面的public说明它是一个公有基类
//基类的私有部分称为派生类的一部分,只能通过基类的公有和保护方法去访问
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;};
};
上面的代码完成了如下工作:
1、派生类对象存储了基类的数据成员,也就是说派生类继承了基类的实现;
2、派生类对象可以使用基类的方法,也就是成员函数,也叫派生类继承了基类的接口;
因此RatedPlayer对象可以存储运动员的姓名、以及是否有球桌,另外RatedPlayer对象还可以使用TableTennisPlayer类的name()、hasTable()、ResetTable()函数。
需要再继承特性中添加什么?
1、派生类需要自己的构造函数;
2、派生类可以根据需要添加额外的成员和成员函数;
构造函数必须给次年成员和继承的成员提供数据。
构造函数的访问权限:
派生类不能直接访问基类的私有成员,必须通过基类的成员函数进行访问;例如,RatedPlayer构造函数不能直接设置集成的成员,如firstname、lastname、hasTable,必须使用基类的公有方法来访问私有的基类成员,也就是说派生类构造函数必须使用基类的构造函数。
创建派生类对象时,程序首先创建基类独享,这意味着基类对象应在程序进入派生类构造函数之前被创建。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构造函数
派生类构造函数的注意事项:
1、首先创建基类对象;
2、派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数;
3、派生类构造函数应初始化派生类新增的数据成员
#ifndef TABLETENNISPLAYER_H
#define TABLETENNISPLAYER_H
#include
#include
using namespace std;
//声明一个基类
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;};
};
//声明一个派生类
//冒号:前面的是派生类,冒号后面的是基类
//TableTennisPlayer前面的public说明它是一个公有基类
//基类的私有部分称为派生类的一部分,只能通过基类的公有和保护方法去访问
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 // TABLETENNISPLAYER_H
#include "tabletennisplayer.h"
TableTennisPlayer::TableTennisPlayer(const string& fn , const string& ln, bool ht ):firstname(fn),lastname(ln),hasTable(ht)
{
}
void TableTennisPlayer::Name() const
{
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)
{
}
#include
#include "tabletennisplayer.h"
int main()
{
TableTennisPlayer player1("Chuck", "Blizzard", true);
RatedPlayer rplayer1(1140,"Tara", "Boomdea", false);
player1.Name();
if(player1.HasTable())
cout << ": has a table.\n";
else
cout << ": hasn't a table.\n";
rplayer1.Name();
if(rplayer1.HasTable())
cout << ": has a table.\n";
else
cout << ": hasn't a table.\n";
cout << "name: ";
rplayer1.Name();
cout << " ; Rating: " << rplayer1.Rating() << endl;
RatedPlayer rplayer2(1112, player1);
cout << "name: ";
rplayer2.Name();
cout << " ; Rating: " << rplayer2.Rating() << endl;
return 0;
}
输出结果
Blizzard, Chuck: has a table.
Boomdea, Tara: hasn't a table.
name: Boomdea, Tara ; Rating: 1140
name: Blizzard, Chuck ; Rating: 1112
1、派生类的对象可以使用基类的public成员函数,
如
RatedPlayer rplayer1(1140,"Tara", "Boomdea", false);
rplayer1.Name();
2、基类指针可以在不进行显式类型转换的情况下指向派生类对象;基类引用可以在不进行显式类型转换的情况下引用派生类对象;
TableTennisPlayer& rt = rplayer1;
TableTennisPlayer* pt = &rplayer2;
rt.Name();
pt->Name();
但是基类指针只能用于调用基类成员函数,因此不能使用rt,pt来调用派生类ResetRating函数。
3、不能将基类的对象和指针赋给派生类的引用和指针;
1、在派生类中重新定义基类的方法(成员函数);
2、使用虚函数
银行不同支票类
#ifndef BRASS_H
#define BRASS_H
#include
using namespace std;
//基类Brass
//virtual 表示虚函数
class Brass
{
private:
string fullName;
long acctNum;
double balance;
public:
Brass(const string& s = "Nullboby", long an = -1, double bal = 0.0);
void Deposit(double amt);
virtual void Withdrow(double amt);
double Balance() const;
virtual void ViewAcct() const;
virtual ~Brass() {};
};
//BrassPlus类在Brass类的基础上增加了三个私有数据成员和3个公有成员函数
//BrassPlus类与Brass类都声明了ViewAcct()和Withdrow()函数,但是它们的功能是不一样的
class BrassPlus : public Brass
{
private:
double maxLoan;
double rate;
double owesBank;
public:
BrassPlus(const string& s = "Nullbody", long an = -1, double ba = 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 Withdrow(double amt);
void ResetMax(double m){maxLoan = m;}
void ResetRate(double r){rate = r;}
void ResetOwes(){owesBank = 0;}
};
#endif // BRASS_H
#include
#include "brass.h"
typedef std::ios_base::fmtflags format;
typedef 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::Withdrow(double amt)
{
format initialState = setFormat();
precis prec = cout.precision(2);
if(amt < 0)
cout << "negative withdrow not allowed; " << "withdrow is cancelled.\n";
else if (amt <= balance)
balance -= amt;
else
cout << "withdrawal amount of $" << amt << " exceeds your balance.\n";
restore(initialState, prec);
}
//查询账户余额
double Brass::Balance() const
{
return balance;
}
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;
owesBank = 0.0;
rate = r;
}
BrassPlus::BrassPlus(const Brass& ba, double ml, double r):Brass(ba)
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
void BrassPlus::ViewAcct() const
{
format initialState = setFormat();
precis prec = cout.precision(2);
Brass::ViewAcct();
//cout << "maximumloan: " << maxLoan << endl;
cout << "maximumloan: " << maxLoan << endl;
cout << "owed to bank " << owesBank << endl;
cout.precision(3);
cout << "Loan Rate: " << 100 * rate << "%\n";
restore(initialState, prec);
}
void BrassPlus::Withdrow(double amt)
{
format initialState = setFormat();
precis prec = cout.precision(2);
double bal = Balance();
if(amt <= bal)
Brass::Withdrow(amt);
else if(amt <= bal + maxLoan - owesBank)
{
double advance = amt - bal;
owesBank += advance * (1.0 + rate);
cout << "Bank advance: $" << advance << endl;
cout << "Finance change: $" << advance * rate << endl;
Deposit(advance);
Brass::Withdrow(amt);
}
else
cout << "Credit limit exceeded. Transaction cancelled.\n";
restore(initialState, prec);
}
format setFormat()
{
return cout.setf(ios_base::fixed, ios_base::floatfield);
}
void restore(format f, precis p)
{
cout.setf(f, ios_base::floatfield);
cout.precision(p);
}
#include
#include "brass.h"
int main()
{
Brass Jiayin("Xu Jiayin", 381299, 4000.00);
BrassPlus Nianyu("beiji Nianyu", 382288, 3000.00);
Jiayin.ViewAcct();
cout << endl;
Nianyu.ViewAcct();
cout << endl;
cout << "Depositing $1000 into the Nianyu Account:\n";
Nianyu.Deposit(1000.00);
cout <<"new balance: $" << Nianyu.Balance() << endl;
cout << "withdrawing $4200 from the Jiayin Account:\n";
Jiayin.Withdrow(4200.00);
cout <<"Jiayin account balance: $" << Jiayin.Balance() << endl;
cout << "withdrawing $4200 from the Nianyu Account:\n";
Nianyu.Withdrow(4200.00);
Nianyu.ViewAcct();
return 0;
}
程序调用函数时,将使用哪个可执行块,是由编译器决定的。将源代码中的函数调用截石位执行特定的函数代码块的过程被称为函数名联编binding。在C语言中,这很简单,因为每个函数名都对应一个不同的函数。在C++中由于函数重载的缘故,这项任务更复杂。编译器必须查看函数参数和函数名才能确定使用哪个函数。但是编译器可以在编译过程中完成这种联编。在编译过程中进行联编就是静态联编。也叫早期联编。但是虚函数的存在,又加深了难度,在编译的时候是不知道该使用哪个函数的,所以编译器必须生成能够在程序运行时选择正确的虚方法的代码,这被称为动态联编,也叫晚期联编。
有关虚函数的注意事项
1、在基类的成员函数的声明中,使用关键字virtual,可使该函数在基类以及所有的派生类中都是虚的;
2、如果使用指向对象的引用或指针来调用虚函数,程序将使用为对象类型定义的函数,而不是使用引用或指针定义的函数。这也称为动态联编;
3、如果定义的类被作为基类,则应将那些要在派生类中重新定义的成员函数声明为虚函数。
4、构造函数不能是虚函数;
5、析构函数应当是虚函数;