目录
目录
一.公有继承
二.多态公有继承
三.访问控制 private
四.包含对象成员的类
五.私有继承
六.保护继承
七.多重继承
C++的继承方式有三种,分别是:公有继承、保护继承、私有继承;
特征 | 公有继承 | 保护继承 | 私有继承 |
公有成员变成 | 派生类公有成员 | 派生类保护成员 | 派生类私有成员 |
保护成员变成 | 派生类保护成员 | 派生类保护成员 | 派生类私有成员 |
私有成员变成 | 只能通过基类接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
能否隐式向上转换 | 是 | 是(但只能在派生类中) | 否 |
1.派生类
首先创建一个基类,然后派生一个类,如下:
// tabtenn1.h -- a table-tennis base class
#ifndef TABTENN1_H_
#define TABTENN1_H_
#include
using std::string;
// simple base class
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; };
};
// simple derived class
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
//tabtenn1.cpp -- simple base-class methods
#include "tabtenn1.h"
#include
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 methods
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)
{
}
程序说明:
(1)首先将 RatedPlayer 类声明为从 TableTennisPlayer 类派生而来:
class RatedPlayer : public TableTennisPlayer
{
...
}
冒号(:)指出 RatedPlayer 类的基是 TableTennisPlayer类;
public 表示公有派生;
使用公有,公有成员将成为派生成员的公有成员;
私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问;
(2)派生类可以根据自己需要添加数据成员和成员函数,且需要自己重新定义构造函数,在构造函数中需要给新成员和继承的成员提供数据,如下:
//声明
RatedPlayer (unsigned int r = 0, const string & fn = "none",
const string & ln = "none", bool ht = false);
//定义
RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht) //给基类成员提供数据
{
rating = r; //给新成员提供数据
}
因为派生类不能直接访问基类的私有成员,在创建派生对象调用构造函数时,会调用基类的构造函数给基类数据成员赋初值;
C++构造函数后面的冒号(:)后面的内容是初始化类的数据成员列表,所以 : TableTennisPlayer(fn, ln, ht) 就是初始化成员列表,这将会调用TableTennisPlayer的构造函数,上所以述构造函数也可以写成下面形式:
//定义
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 string & fn,
const string & ln, bool ht)
: TableTennisPlayer(tp), rating(r)
{
}
因此派生类构造函数要下如下:
首先创建基类对象;
派生类构造函数通过初始化成员列表,将基类数据传递给基类构造函数;
派生类构造函数应初始化新增的数据成员;
2.使用派生类
在派生类创建完成后,便可以使用派生类,示例程序如下:
// usett1.cpp -- using base class and derived class
#include
#include "tabtenn1.h"
int main ( void )
{
using std::cout;
using std::endl;
TableTennisPlayer player1("Tara", "Boomdea", false);
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
rplayer1.Name(); // derived object uses base method
if (rplayer1.HasTable())
cout << ": has a table.\n";
else
cout << ": hasn't a table.\n";
player1.Name(); // base object uses base method
if (player1.HasTable())
cout << ": has a table";
else
cout << ": hasn't a table.\n";
cout << "Name: ";
rplayer1.Name();
cout << "; Rating: " << rplayer1.Rating() << endl;
// initialize RatedPlayer using TableTennisPlayer object
RatedPlayer rplayer2(1212, player1);
cout << "Name: ";
rplayer2.Name();
cout << "; Rating: " << rplayer2.Rating() << endl;
// std::cin.get();
return 0;
}
可以看到,在这个示例中,派生类都是通过基类公有方法访问基类私有成员数据,并没有直接访问基类私有成员数据;
输出如下:
Duck, Mallory: has a table.
Boomdea, Tara: hasn't a table.
Name: Duck, Mallory; Rating: 1140
Name: Boomdea, Tara; Rating: 1212
多态是指同一方法在派生类和基类的行为是不同的,方法的行为取决于调用该方法的对象;
实现多态公有继承的机制:
在派生类中重新定义基类方法;
使用虚方法;
// brass.h -- bank account classes
#ifndef BRASS_H_
#define BRASS_H_
#include
// Brass Account Class
class Brass
{
private:
std::string fullName;
long acctNum;
double balance;
public:
Brass(const std::string & s = "Nullbody", long an = -1, double bal = 0.0);
void Deposit(double amt);
double Balance() const;
//增加关键字 virtual ,虚方法,用关于不同方法定义
virtual void Withdraw(double amt);
virtual void ViewAcct() const;
%123
//虚析构函数
virtual ~Brass() {}
};
//Brass Plus Account Class
class BrassPlus : public Brass
{
private:
//新增3个私有数据成员
double maxLoan;
double rate;
double owesBank;
public:
BrassPlus(const std::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 ,虚方法,用关于不同方法定义
virtual void ViewAcct()const;
virtual void Withdraw(double amt);
//新增3个公有成员函数
void ResetMax(double m) { maxLoan = m; }
void ResetRate(double r) { rate = r; };
void ResetOwes() { owesBank = 0; }
};
#endif
在基类和派生类的声明中,ViewAcct()、Withdraw()函数都加了 virtual 关键字,这样可以在不同类中实现同一个方法的不同定义,定义如下:
// brass.cpp -- bank account class methods
#include
#include "brass.h"
using std::cout;
using std::endl;
using std::string;
// formatting stuff
typedef std::ios_base::fmtflags format;
typedef std::streamsize precis;
format setFormat();
void restore(format f, precis p);
// Brass methods
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)
{
// set up ###.## format
format initialState = setFormat();
precis prec = cout.precision(2);
if (amt < 0)
cout << "Withdrawal amount must be positive; "
<< "withdrawal canceled.\n";
else if (amt <= balance)
balance -= amt;
else
cout << "Withdrawal amount of $" << amt
<< " exceeds your balance.\n"
<< "Withdrawal canceled.\n";
restore(initialState, prec);
}
double Brass::Balance() const
{
return balance;
}
void Brass::ViewAcct() const
{
// set up ###.## format
format initialState = setFormat();
precis prec = cout.precision(2);
cout << "Client: " << fullName << endl;
cout << "Account Number: " << acctNum << endl;
cout << "Balance: $" << balance << endl;
restore(initialState, prec); // Restore original format
}
// BrassPlus Methods
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) // uses implicit copy constructor
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
// redefine how ViewAcct() works
void BrassPlus::ViewAcct() const
{
// set up ###.## format
format initialState = setFormat();
precis prec = cout.precision(2);
Brass::ViewAcct(); // display base portion
cout << "Maximum loan: $" << maxLoan << endl;
cout << "Owed to bank: $" << owesBank << endl;
cout.precision(3); // ###.### format
cout << "Loan Rate: " << 100 * rate << "%\n";
restore(initialState, prec);
}
// redefine how Withdraw() works
void BrassPlus::Withdraw(double amt)
{
// set up ###.## format
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()
{
// set up ###.## format
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);
}
可以看到,ViewAcct()、Withdraw()函数在不同类中的定义是不同的;
使用这两个类示例:
// usebrass1.cpp -- testing bank account classes
// compile with brass.cpp
#include
#include "brass.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;
return 0;
}
输出:
#Piggy.ViewAcct();
Client: Porcelot Pigg
Account Number: 381299
Balance: $4000.00
#Hoggy.ViewAcct();
Client: Horatio Hogg
Account Number: 382288
Balance: $3000.00
Maximum loan: $500.00
Owed to bank: $0.00
Loan Rate: 11.125%
可以看到,不同类调用同一个成员函数,采用的方式不同;
然而,上面的方法是通过对象直接调用的,而不是使用指针或引用,没有使用虚方法特性;
因此,可以创建一个指向基类 Brass 的指针数组,实现同一个指针数组表示多种类型的对象,这就是多态,如下:
// usebrass2.cpp -- polymorphic example
// compile with brass.cpp
#include
#include
#include "brass.h"
const int CLIENTS = 4;
int main()
{
using std::cin;
using std::cout;
using std::endl;
Brass * p_clients[CLIENTS];
std::string temp;
long tempnum;
double tempbal;
char kind;
for (int i = 0; i < CLIENTS; i++)
{
cout << "Enter client's name: ";
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);
}
while (cin.get() != '\n')
continue;
}
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]; // free memory
}
cout << "Done.\n";
/* code to keep window open
if (!cin)
cin.clear();
while (cin.get() != '\n')
continue;
*/
return 0;
}
程序说明:
创建一个指向 Brass 的指针数组:
Brass * p_clients[CLIENTS];
在数组不同元素中,使用 new 创建并初始化不同类型的对象:
p_clients[i] = new Brass(temp, tempnum, tempbal);
p_clients[i] = new BrassPlus(temp, tempnum, tempbal, tmax, trate);
最后直接调用 ViewAcct() 函数,此时指针指向哪个对象,就用哪个对象调用 ViewAcct() 函数:
for (int i = 0; i < CLIENTS; i++)
{
p_clients[i]->ViewAcct();
cout << endl;
}
与 private 类似,在类外只能使用公有类成员函数来访问 protected 部分中的类成员;
区别在于派生类的成员可以直接访问基类的保护成员,但不能直接访问基类私有成员;
所以,对于外界来说,保护与私有成员函数相似;但对于派生类,保护和公有成员相似;
示例:
class Brass
{
protected:
double balance;
...
}
void BrassPlus::Reset(double amt)
{
balance = amt;
}
保护访问控制,优点在于,它让派生类能够访问公众不能使用的内部函数。
包含是指在一个类中包含其他类对象:
class Student
{
private:
std::string name; // use a string object for name
std::valarray scores; // use a valarray object for scores
,,,
};
上述数据成员被声明为私有的,这意味着Student类的成员函数可以使用 string 和 valarray
的公有接口访问 name 和 score,也就是Student获得了string 和 valarray 的实现,但不能获得继承接口(不像公有继承那样使得基类函数变成自己的成员函数)。
比如只能:
scores.sum();
name.size();
//而不是
student.sum();
student.size();
初始化:
Student(const char * str, const double * pd, int n)
: name(str), scores(pd, n) {}
因为初始化的是成员对象,而不是像公有继承那样的继承对象,所以初始化中使用的是成员名(: name(str), scores(pd, n)),而不是类名;
成员函数定义:
double Student::Average() const
{
if (scores.size() > 0)
return scores.sum()/scores.size();
else
return 0;
}
因为被包含的对象接口不是公有的,所以不能直接使用;
又因为是包含,所以与初始化类似,使用成员对象调用基类方法,而不是通过类调用,这点区别于下面的私有继承;
私有继承是使用关键字 private 来定义类,示例如下:
// studenti.h -- defining a Student class using private inheritance
#ifndef STUDENTC_H_
#define STUDENTC_H_
#include
#include
#include
class Student : private std::string, private std::valarray
{
private:
typedef std::valarray ArrayDb;
// private method for scores output
std::ostream & arr_out(std::ostream & os) const;
public:
Student() : std::string("Null Student"), ArrayDb() {}
explicit Student(const std::string & s)
: std::string(s), ArrayDb() {}
explicit Student(int n) : std::string("Nully"), ArrayDb(n) {}
Student(const std::string & s, int n)
: std::string(s), ArrayDb(n) {}
Student(const std::string & s, const ArrayDb & a)
: std::string(s), ArrayDb(a) {}
Student(const char * str, const double * pd, int n)
: std::string(str), ArrayDb(pd, n) {}
~Student() {}
double Average() const;
double & operator[](int i);
double operator[](int i) const;
const std::string & Name() const;
// friends
// input
friend std::istream & operator>>(std::istream & is,
Student & stu); // 1 word
friend std::istream & getline(std::istream & is,
Student & stu); // 1 line
// output
friend std::ostream & operator<<(std::ostream & os,
const Student & stu);
};
#endif
// studenti.cpp -- Student class using private inheritance
#include "studenti.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;
// public methods
double Student::Average() const
{
if (ArrayDb::size() > 0)
return ArrayDb::sum()/ArrayDb::size();
else
return 0;
}
const string & Student::Name() const
{
return (const string &) *this;
}
double & Student::operator[](int i)
{
return ArrayDb::operator[](i); // use ArrayDb::operator[]()
}
double Student::operator[](int i) const
{
return ArrayDb::operator[](i);
}
// private method
ostream & Student::arr_out(ostream & os) const
{
int i;
int lim = ArrayDb::size();
if (lim > 0)
{
for (i = 0; i < lim; i++)
{
os << ArrayDb::operator[](i) << " ";
if (i % 5 == 4)
os << endl;
}
if (i % 5 != 0)
os << endl;
}
else
os << " empty array ";
return os;
}
// friends
// use String version of operator>>()
istream & operator>>(istream & is, Student & stu)
{
is >> (string &)stu;
return is;
}
// use string friend getline(ostream &, const string &)
istream & getline(istream & is, Student & stu)
{
getline(is, (string &)stu);
return is;
}
// use string version of operator<<()
ostream & operator<<(ostream & os, const Student & stu)
{
os << "Scores for " << (const string &) stu << ":\n";
stu.arr_out(os); // use private method for scores
return os;
}
说明:
1.格式
class Student : private std::string, private std::valarray
{
...
}
这里使用了多个基类(string、valarray)的继承,称为多重继承(MI)
2.初始化基类组件(构造函数)
Student(const char * str, const double * pd, int n)
: std::string(str), ArrayDb(pd, n) {}
这里构造函数初始化的两个成员变量(name、scores)由基类提供,派生类没有新增新的成员变量,构造函数与公有继承类似;
注意这里与上面“包含对象成员的类”的区别,这里直接使用类来初始化,而包含是用对象初始化;
3.访问基类方法(成员函数定义)
double Student::Average() const
{
if (ArrayDb::size() > 0)
return ArrayDb::sum()/ArrayDb::size();
else
return 0;
}
可看到,同样要通过基类类名和作用域符访问基类的方法,这一点和公有派生不同,公有派生可以直接访问基类的公有成员函数;
注意这里与上面“包含对象成员的类”的区别,这里直接使用基类调用方法,而包含是用对象调用方法;
总结:大部分C++程序倾向于使用包含,因为更易理解!
保护继承是私有继承的变体,格式如下:
class Student : protected std::string, protected std::valarray
{
...
}
保护继承后,基类的公有、保护成员都将变成派生类的保护成员;
与私有继承不同的是:
在派生类再派生一个类后,也就是第三代类,对于私有继承,第三代类将不能访问基类接口,因为基类的公有方法再派生类中变成了私有方法;而保护继承,基类的公有方法再第二代中变成保护的,因此第三代派生类可以使用他们;
那么在保护、私有派生中,如何确保基类的方法能够在多代派生类中仍然可以使用呢?
方法一:
定义一个使用该基类方法的派生类方法,例如:
double Student::sum() const
{
return std::valarray::sum();
}
这样 Student 对象在调用 sum() 方法时,将会在函数中调用基类的 valarray
方法二:
在派生类声明,声明方法前加上 using 关键字,如下:
class Student : private std::string, private std::valarray
{
public:
using std::valarray::min;
using std::valarray::max;
...
}
上述声明使得
valarray
略