C++提供了比修改代码更好的方法来扩展和修改类。这种方法叫作类继承,它能够从已有的类派生出新的类,而派生类继承了原有类(称为基类)的特征,包括方法。
从一个类派生出另一个类时,原始类称为基类,继承类称为派生类。
tabtenn0.h
// tabtenn0.h -- a table-tennis base class
#ifndef PRIMERPLUS_TABTENN0_H
#define PRIMERPLUS_TABTENN0_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; };
};
#endif //PRIMERPLUS_TABTENN0_H
tabtenn0.cpp
//tabtenn0.cpp -- simple base-class methods
#include "tabtenn0.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;
}
usett0.cpp
// usett0.cpp -- using a base class
#include
#include "tabtenn0.h"
int main ( void )
{
using std::cout;
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";
else
cout << ": hasn't a table.\n";
// std::cin.get();
return 0;
}
out:
Blizzard, Chuck: has a table.
Boomdea, Tara: hasn't a table.
class RatedPlayer : public TableTennisPlayer
// simple derived class
class RatedPlayer : public TableTennisPlayer
{
private:
unsigned int rating; // add a data member
public:
RatedPlayer (unsigned int r = 0, const string & fn = "none",
const string & ln = "none", bool ht = false); // 构造函数1
RatedPlayer(unsigned int r, const TableTennisPlayer & tp); // 构造函数2
unsigned int Rating() const { return rating; } // add a method
void ResetRating (unsigned int r) {rating = r;} // add a method
};
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) // 调用基类复制构造函数
{
rating = r;
}
tabtenn1.h
// tabtenn1.h -- a table-tennis base class
#ifndef PRIMERPLUS_TABTENN1_H
#define PRIMERPLUS_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 //PRIMERPLUS_TABTENN1_H
tabtenn1.cpp
//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)
{
}
usett1.cpp
// 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;
}
out:
Duck, Mallory: has a table.
Boomdea, Tara: hasn't a table.
Name: Duck, Mallory; Rating: 1140
Name: Boomdea, Tara; Rating: 1212
RatedPlayer rplayer(1140, "Mallory", "Duck", true);
TableTennisPlayer & rt = rplayer;
TableTennisPlayer * pt = &rplayer;
rplayer.Name(); // derived object uses base method
rt.Name(); // invoke Name() with reference
pt->Name(); // invoke Name() with pointer
// 将基类对象初始化为派生类对象
RatedPlayer olaf1(1840, "Olaf", "Loaf", true);
TableTennisPlayer olaf2(olaf1);
RatedPlayer olaf1(1840, "Olaf", "Loaf", true);
TableTennisPlayer winner;
winner = olaf1; // 可以将派生对象赋给基类对象
同一个方法在派生类和基类中的行为是不同的。
// 如果ViewAcct( )是虚的
virtual void ViewAcct() const;
Brass dom("Dominic Banker", 11224, 4183.45);
BrassPlus dot("Dorothy Banker", 12118, 2592.00);
Brass & b1_ref = dom;
Brass & b2_ref = dot;
b1_ref.ViewAcct(); // use Brass::ViewAcct()
b2_ref.ViewAcct(); // use BrassPlus::ViewAcct()
// 如果ViewAcct( )不是虚的,都调用基类方法
brass.h
// virtual 如果方法是通过引用或指针而不是对象调用的,它将确定使用哪一种方法。
#ifndef PRIMERPLUS_BRASS_H
#define PRIMERPLUS_BRASS_H
#include
#include
using namespace std;
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 Withdraw(double amt);
double Balance() const;
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 ViewAcct() const;
virtual void Withdraw(double amt);
void ResetMax(double m) { maxLoan = m;}
void ResetRate(double r) { rate =r; }
void ResetOwes() { owesBank = 0; }
};
#endif //PRIMERPLUS_BRASS_H
brass.cpp
#include "brass.h"
typedef 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.\n";
else
balance += amt;
}
void Brass::Withdraw(double amt)
{
format initialState = setFormat();
precis prec = cout.precision(2);
if (amt < 0)
cout << "Negative withdraw not allowed.\n";
else if (amt <= balance)
balance -= amt;
else
cout << "money insufficient.\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 << "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(); // 调用基类的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 << "Fiance charge: $" << advance * rate << endl;
Deposit(advance);
Brass::Withdraw(amt);
}
else
cout << "Credit limit exceeded.\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);
}
usebrass.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;
cout << "Depositing $1000 into the Hogg Account:\n";
Hoggy.Deposit(1000.00);
cout << "New balance: $" << Hoggy.Balance() << endl;
cout << "Withdrawing $4200 from the Pigg Account:\n";
Piggy.Withdraw(4200.00);
cout << "Pigg account balance: $" << Piggy.Balance() << endl;
cout << "Withdrawing $4200 from the Hogg Account:\n";
Hoggy.Withdraw(4200.00);
Hoggy.ViewAcct();
return 0;
}
// 对于使用基类引用或指针作为参数的函数调用,将进行向上转换。
void fr(Brass & rb); // uses rb.ViewAcct()
void fp(Brass * pb); // uses pb->ViewAcct()
void fv(Brass b); // uses b.ViewAcct()
int main()
{
Brass b("Billy Bee", 123432, 10000.0);
BrassPlus bp("Betty Beep", 232313, 12345.0);
fr(b); // uses Brass::ViewAcct()
fr(bp); // uses BrassPlus::ViewAcct()
fp(b); // uses Brass::ViewAcct()
fp(bp); // uses BrassPlus::ViewAcct()
fv(b); // uses Brass::ViewAcct()
fv(bp); // uses Brass::ViewAcct()
}
1、为什么有两种类型的联编?为什么默认为静态联编?
2、虚函数的工作原理?
编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(virtual function table,vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址。
3、使用虚函数时,在内存和执行速度方面有一定的成本:
virtual ~Brass() {}
有些基类的方法和数据在派生类中用不到,浪费资源。
例如:从Ellipse和Circle类中抽象出它们的共性,将这些特性放到一个ABC中。然后从该ABC派生出Circle和Ellipse类(具体类)。这样,便可以使用基类指针数组同时管理Circle和Ellipse对象,即可以使用多态方法。
virtual double Area() = 0;
在ABC中可以不定义该函数。例子:首先定义一个名为AcctABC的ABC。这个类包含Brass和BrassPlus类共有的所有方法和数据成员,而那些在BrassPlus类和Brass类中的行为不同的方法应被声明为虚函数。
acctacb.h
// acctabc.h
#ifndef PRIMERPLUS_ACCTABC_H
#define PRIMERPLUS_ACCTABC_H
#include
#include
using namespace std;
// Abstract Base fullName
class AcctABC
{
private:
string fullName; // 姓名
long acctNum; // 账户
double balance; // 余额
protected: // 派生类的成员可以直接访问基类的保护成员
string hobby = "eat";
struct Formatting
{
std::ios_base::fmtflags flag;
std::streamsize pr;
};
const string & FullName() const {return fullName;}
long AcctNum() const {return acctNum;}
Formatting SetFormat() const;
void Restore(Formatting & f) const;
public:
AcctABC(const string & s = "Nullbody", long an = -1, double bal = 0.0);
void Deposit(double amt);
virtual void Withdraw(double amt) = 0; // 纯虚函数
double Balance() const {return balance;}// 内联函数
virtual void ViewAcct() const = 0; // 纯虚函数
virtual ~AcctABC() {} // 基类的析构函数必须是虚的
};
// Brass Account Class
class Brass : public AcctABC // Brass是AcctABC的派生类
{
public:
Brass(const string & s, long an = -1, double bal = 0.0) : AcctABC(s, an, bal) {}
virtual void Withdraw(double amt);
virtual void ViewAcct() const;
virtual ~Brass() {}
};
// Brass Plus Account Class
class BrassPlus : public AcctABC
{
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 AcctABC & ba, double ml = 500, double r = 0.11125);
virtual void ViewAcct() const;
virtual void Withdraw(double amt);
void ResetMax(double m) { maxLoan = m;}
void ResetRate(double r) { rate =r; }
void ResetOwes() { owesBank = 0; }
virtual ~BrassPlus() {}
};
#endif //PRIMERPLUS_ACCTABC_H
acctabc.cpp
#include "acctabc.h"
// ABC 基类方法定义
AcctABC::AcctABC(const string & s, long an, double bal)
{
fullName = s;
acctNum = an;
balance = bal;
}
void AcctABC::Deposit(double amt)
{
if (amt < 0)
cout << "Negative deposit not allowed.\n";
else
balance += amt;
}
void AcctABC::Withdraw(double amt) // 纯虚函数定义
{
balance -= amt;
}
// 基类的protected 方法定义
AcctABC::Formatting AcctABC::SetFormat() const
{
Formatting f;
f.flag = cout.setf(ios_base::fixed, ios_base::floatfield);
f.pr = cout.precision(2);
return f;
}
void AcctABC::Restore(Formatting & f) const
{
cout.setf(f.flag, ios_base::floatfield);
cout.precision(f.pr);
}
// 派生类Brass方法定义
void Brass::Withdraw(double amt)
{
if (amt < 0)
cout << "Negative withdraw not allowed.\n";
else if (amt <= Balance())
AcctABC::Withdraw(amt);
else
cout << "money insufficient.\n";
}
void Brass::ViewAcct() const
{
Formatting f = SetFormat();
cout << "Brass Client: " << FullName() << endl;
cout << "Account Number: " << AcctNum() << endl;
cout << "Balance: $" << Balance() << endl;
cout << "Hobby: " << hobby << endl;
Restore(f);
}
// 派生类BrassPlus 的方法定义
BrassPlus::BrassPlus(const string & s, long an, double bal,
double ml, double r) : AcctABC(s, an, bal)
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
BrassPlus::BrassPlus(const AcctABC & ba, double ml, double r) : AcctABC(ba)
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
void BrassPlus::ViewAcct() const
{
Formatting f = SetFormat();
cout << "BrassPlus Client: " << FullName() << endl;
cout << "Account Number: " << AcctNum() << endl;
cout << "Balance: $" << Balance() << endl;
cout << "Maximum loan: $" << maxLoan << endl;
cout << "Owed to bank: $" << owesBank << endl;
cout << "Hobby: " << hobby << endl;
cout.precision(3);
cout << "Loan Rate: " << 100*rate << "%\n";
Restore(f);
}
void BrassPlus::Withdraw(double amt)
{
Formatting f = SetFormat();
double bal = Balance(); // 调用基类的Balance()
if (amt <= bal)
AcctABC::Withdraw(amt);
else if (amt <= bal + maxLoan - owesBank)
{
double advance = amt - bal; // 透支余额
owesBank += advance * (1.0 + rate); // 欠银行的
cout << "Bank advance: $" << advance << endl;
cout << "Fiance charge: $" << advance * rate << endl;
Deposit(advance);
AcctABC::Withdraw(amt);
}
else
cout << "Credit limit exceeded.\n";
Restore(f);
}
useacctabc.cpp
可以创建指向AcctABC的指针数组。这样,每个元素的类型都相同,但由于使用的是公有继承模型,因此AcctABC指针既可以指向Brass对象,也可以指向BrassPlus对象。 因此,可以使用一个数组来表示多种类型的对象。这就是多态性。
// compile with acctacb.cpp
#include
#include
#include "acctabc.h"
const int CLIENTS = 4;
int main()
{
// 可以创建指向AcctABC的指针数组。
// 这样,每个元素的类型都相同,但由于使用的是公有继承模型,因此AcctABC指针既可以指向Brass对象,也可以指向BrassPlus对象。
// 因此,可以使用一个数组来表示多种类型的对象。这就是多态性。
AcctABC * 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 (q to quit):";
while (cin >> kind && (kind != '1' && kind != '2'))
cout <<"Enter either 1 or 2 (q to quit):";
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";
return 0;
}
第一种情况:假设基类使用了动态内存分配,派生类不需执行任何特殊操作,不需要为派生类定义显式析构函数、复制构造函数和赋值运算符。
第二种情况:假设基类和派生类都使用了new,在这种情况下,必须为派生类定义显式析构函数、复制构造函数和赋值运算符。
// 基类使用new
class baseDMA
{
private:
char * label;
int rating;
public:
baseDMA(const char * l = "null", int r = 0);
baseDMA(const baseDMA & rs);
virtual ~baseDMA(); // 基类必须有一个虚析构函数
baseDMA & operator=(const baseDMA & rs);
};
// 派生类使用new
class hasDMA :public baseDMA
{
private:
char * style; // use new in constructors
public:
~hasDMA();
};
baseDMA::~baseDMA() // 基类虚构函数
{
delete [] label;
}
// 派生类析构函数自动调用基类的析构函数,而自身析构函数是对派生类新增指针成员指向内存的释放。
hasDMA::~hasDMA() // 派生类虚构函数
{
delete [] style;
}
// BaseDMA的复制构造函数遵循用于char数组的常规模式,
// 即使用strlen( )来获悉存储C-风格字符串所需的空间、分配足够的内存(字符数加上存储空字符所需的1字节)
// 并使用函数strcpy( )将原始字符串复制到目的地
baseDMA::baseDMA(const baseDMA & rs) // 基类的复制构造函数,基类引用可以指向派生类型
{
label = new char[std::strlen(rs.label) + 1];
std::strcpy(label, rs.label);
rating = rs.rating;
}
// hasDMA复制构造函数只能访问hasDMA的数据,
// 因此它必须调用 baseDMA复制构造函数来处理共享的baseDMA数据
hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) // 派生类的复制构造函数
{
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
}
baseDMA & baseDMA::operator=(const baseDMA & rs) // 基类的赋值构造函数
{
if (this == &rs)
return *this;
delete [] label;
label = new char[std::strlen(rs.label) + 1];
std::strcpy(label, rs.label);
rating = rs.rating;
return *this;
}
// 派生类的显式赋值运算符可以通过显式调用基类赋值运算符,完成所有继承的基类对象的赋值。
hasDMA & hasDMA::operator=(const hasDMA & hs) // 派生类的赋值构造函数
{
if (this == &hs)
return *this;
baseDMA::operator=(hs); // copy base portion
delete [] style; // prepare for new style
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
return *this;
}
因为友元不是成员函数,所以不能使用作用域解析运算符来指出要使用哪个函数,可以使用强制类型转换(派生类转为基类),以便匹配原型时能够选择正确的函数。
dma.h
#ifndef PRIMERPLUS_DMA_H
#define PRIMERPLUS_DMA_H
#include
// Base Class Using DMA
class baseDMA
{
private:
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);
};
// derived class without DMA
// no destructor needed
// uses implicit copy constructor
// uses implicit assignment operator
class lacksDMA :public baseDMA
{
private:
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);
};
// derived class with DMA
class hasDMA :public baseDMA
{
private:
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 & hs);
~hasDMA();
hasDMA & operator=(const hasDMA & rs);
friend std::ostream & operator<<(std::ostream & os,
const hasDMA & rs);
};
#endif //PRIMERPLUS_DMA_H
dma.cpp
// dma.cpp --dma class methods
#include "dma.h"
#include
// baseDMA methods
baseDMA::baseDMA(const char * l, int r)
{
label = new char[std::strlen(l) + 1];
std::strcpy(label, l);
rating = r;
}
baseDMA::baseDMA(const baseDMA & rs)
{
label = new char[std::strlen(rs.label) + 1];
std::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[std::strlen(rs.label) + 1];
std::strcpy(label, rs.label);
rating = rs.rating;
return *this;
}
std::ostream & operator<<(std::ostream & os, const baseDMA & rs)
{
os << "Label: " << rs.label << std::endl;
os << "Rating: " << rs.rating << std::endl;
return os;
}
// lacksDMA methods
lacksDMA::lacksDMA(const char * c, const char * l, int r)
: baseDMA(l, r)
{
std::strncpy(color, c, 39);
color[39] = '\0';
}
lacksDMA::lacksDMA(const char * c, const baseDMA & rs)
: baseDMA(rs)
{
std::strncpy(color, c, COL_LEN - 1);
color[COL_LEN - 1] = '\0';
}
std::ostream & operator<<(std::ostream & os, const lacksDMA & ls)
{
os << (const baseDMA &) ls;
os << "Color: " << ls.color << std::endl;
return os;
}
// hasDMA methods
hasDMA::hasDMA(const char * s, const char * l, int r)
: baseDMA(l, r)
{
style = new char[std::strlen(s) + 1];
std::strcpy(style, s);
}
hasDMA::hasDMA(const char * s, const baseDMA & rs)
: baseDMA(rs)
{
style = new char[std::strlen(s) + 1];
std::strcpy(style, s);
}
hasDMA::hasDMA(const hasDMA & hs)
: baseDMA(hs) // invoke base class copy constructor
{
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
}
hasDMA::~hasDMA()
{
delete [] style;
}
hasDMA & hasDMA::operator=(const hasDMA & hs)
{
if (this == &hs)
return *this;
baseDMA::operator=(hs); // copy base portion
delete [] style; // prepare for new style
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
return *this;
}
std::ostream & operator<<(std::ostream & os, const hasDMA & hs)
{
os << (const baseDMA &) hs;
os << "Style: " << hs.style << std::endl;
return os;
}
usedma.cpp
// usedma.cpp -- inheritance, friends, and DMA
// compile with dma.cpp
#include
#include "dma.h"
int main()
{
using std::cout;
using std::endl;
baseDMA shirt("Portabelly", 8);
lacksDMA balloon("red", "Blimpo", 4);
hasDMA map("Mercator", "Buffalo Keys", 5);
cout << "Displaying baseDMA object:\n";
cout << shirt << endl;
cout << "Displaying lacksDMA object:\n";
cout << balloon << endl;
cout << "Displaying hasDMA object:\n";
cout << map << endl;
lacksDMA balloon2(balloon);
cout << "Result of lacksDMA copy:\n";
cout << balloon2 << endl;
hasDMA map2;
map2 = map;
cout << "Result of hasDMA assignment:\n";
cout << map2 << endl;
// std::cin.get();
return 0;
}
out:
Displaying baseDMA object:
Label: Portabelly
Rating: 8
Displaying lacksDMA object:
Label: Blimpo
Rating: 4
Color: red
Displaying hasDMA object:
Label: Buffalo Keys
Rating: 5
Style: Mercator
Result of lacksDMA copy:
Label: Blimpo
Rating: 4
Color: red
Result of hasDMA assignment:
Label: Buffalo Keys
Rating: 5
Style: Mercator
1、默认构造函数: 要么没有参数,要么所有的参数都有默认值。
2、复制构造函数: 接受其所属类的对象作为参数。
何时使用复制构造函数:
如果成员为类对象,则初始化该成员时,将使用相应类的复制构造函数。
在某些情况下,成员初始化是不合适的。例如,使用new初始化的成员指针通常要求执行深复制,或者类可能包含需要修改的静态变量。
3、默认的赋值运算符 :用于处理同类对象之间的赋值。
1、构造函数
2、析构函数
3、转换:只有一个参数
将其他类型转换为类对象:
将类对象转换为其他类型,应定义转换函数:
4、按值传递对象与传递引用
通常,编写使用对象作为参数的函数时,应按引用而不是按值来传递对象。这样做的原因之一是为了提高效率。按值传递对象涉及到生成临时拷贝,即调用复制构造函数,然后调用析构函数。调用这些函数需要时间,复制大型对象比传递引用花费的时间要多得多。如果函数不修改对象,应将参数声明为const引用。
按引用传递对象的另外一个原因是,在继承使用虚函数时,被定义为接受基类引用参数的函数可以接受派生类。
5、返回对象和返回引用
有些类方法返回对象。有时方法必须返回对象,但如果可以不返回对象,则应返回引用。
应返回引用而不是返回对象的的原因在于:返回对象涉及生成返回对象的临时副本,这是调用函数的程序可以使用的副本。因此,返回对象的时间成本包括调用复制构造函数来生成副本所需的时间和调用析构函数删除副本所需的时间。返回引用可节省时间和内存。直接返回对象与按值传递对象相似:它们都生成临时副本。同样,返回引用与按引用传递对象相似:调用和被调用的函数对同一个对象进行操作。
然而,并不总是可以返回引用的原因:函数不能返回在函数中创建的临时对象的引用,因为当函数结束时,临时对象将消失,因此这种引用将是非法的。在这种情况下,应返回对象,以生成一个调用程序可以使用的副本。
6、使用const
Star::Star(const char * s) {...}
void Star::show() const {...}
这里const表示const Star * this,而this指向调用的对象。const Stock & Stock::topval(const Stock & s) const
因为this和s都被声明为const,所以函数不能对它们进行修改,这意味着返回的引用也必须被声明为const。1、is-a关系
最好的方法可能是创建包含纯虚函数的抽象数据类,并从它派生出其他的类。
无需进行显式类型转换,基类指针就可以指向派生类对象,基类引用可以引用派生类对象。另外,反过来是行不通的,即不能在不进行显式类型转换的情况下,将派生类指针或引用指向基类对象。
2、什么不能被继承
构造函数是不能继承的:
析构函数也是不能继承的:
赋值运算符是不能继承的:
3、赋值运算符
基类引用能自动引用派生类对象。
派生类引用如何自动引用基类对象:
方法一:转换构造函数:可以接受一个类型为基类的参数和其他参数,条件是其他参数有默认值。即将基类对象转换为派生类对象进行了定义
BrassPlus(const Brass & ba, double ml = 500, double r = 0.1);
方法二:赋值运算符:定义一个用于将基类赋给派生类的赋值运算符。
BrassPlus & BrassPlus ::operator=(const Brass &) {...}
4、私有成员与保护成员
5、虚方法
如果希望派生类能够重新定义方法,则应在基类中将方法定义为虚的,这样可以启用晚期联编(动态联编)
6、析构函数
基类的析构函数应当是虚的。这样,当通过指向对象的基类指针或引用来删除派生对象时,程序将首先调用派生类的析构函数,然后调用基类的析构函数,而不仅仅是调用基类的析构函数。
7、友元函数
由于友元函数并非类成员,因此不能继承。
派生类的友元函数如何使用基类的友元函数:
ostream & operator<<(ostream & os, const hasDMA & hs) { os << (const baseDMA &) hs; return os;}
os << dynamic_cast (hs);
8、有关使用基类方法的说明
C++类函数有很多不同的变体,其中有些可以继承,有些不可以。 有些运算符函数既可以是成员函数,也可以是友元,而有些运算符函数只能是成员函数。
其中op=表示诸如+=、*=等格式的赋值运算符。
1.派生类从基类那里继承了什么?
派生类继承了基类大部分的成员函数。基类的保护成员成为派生类的保护成员,可以直接访问。基类的私有成员被继承,但不能直接访问。
2.派生类不能从基类那里继承什么?
不能继承构造函数、析构函数、赋值运算符和友元。
3.假设baseDMA ::operator=( )函数的返回类型为void,而不是baseDMA &,这将有什么后果?如果返回类型为baseDMA,而不是baseDMA &,又将有什么后果?
如果返回的类型为void,仍可以使用单个赋值,但不能使用连锁赋值;P394
如果方法返回一个对象,而不是引用,则该方法的执行速度将有所减慢,这是因为返回语句需要复制对象。
4.创建和删除派生类对象时,构造函数和析构函数调用的顺序是怎样的?
派生类对象创建时最先调用基类的构造函数,创建基类的对象,然后创建派生类的对象。调用析构函数的顺序正好相反。
5.如果派生类没有添加任何数据成员,它是否需要构造函数?
需要,每个类都必须有自己的构造函数。如果派生类没有添加新成员,则构造函数可以为空,但必须存在。
6.如果基类和派生类定义了同名的方法,当派生类对象调用该方法时,被调用的将是哪个方法?
只调用派生类方法。它取代基类定义。仅当派生类没有重新定义方法或使用作用域解析运算符时,才会调用基类方法。然而,应把将所有要重新定义的函数声明为虚函数。
7.在什么情况下,派生类应定义赋值运算符?
如果派生类构造函数使用new或new[ ]运算符来初始化类的指针成员(动态内存分配),则应定义一个赋值运算符。更普遍地说,如果对于派生类成员来说,默认赋值不正确,则应定义赋值运算符。
8.可以将派生类对象的地址赋给基类指针吗?可以将基类对象的地址赋给派生类指针吗?
可以将派生类对象的地址赋给基类指针;但只有通过显式类型转换,才可以将基类对象的地址赋给派生类指针(向下转换),而使用这样的指针不一定安全。
9.可以将派生类对象赋给基类对象吗?可以将基类对象赋给派生类对象吗?
可以将派生类对象赋给基类对象,对于派生类中新增的数据成员都不会传递给基类对象,然而,程序将使用基类的赋值运算符。
仅当派生类定义了转换运算符(即包含将基类引用作为唯一参数的构造函数)或使用基类为参数的赋值运算符时,相反方向的赋值才是可能的。
10.假设定义了一个函数,它将基类对象的引用作为参数。为什么该函数也可以将派生类对象作为参数?
它可以这样做,因为C++允许基类引用指向从该基类派生而来的任何类型。
11.假设定义了一个函数,它将基类对象作为参数(即函数按值传递基类对象)。为什么该函数也可以将派生类对象作为参数?
按值传递对象将调用复制构造函数。由于形参是基类对象,因此将调用基类的复制构造函数。复制构造函数以基类引用为参数,该引用可以指向作为参数传递的派生对象。最终结果是,将生成一个新的基类对象,其成员对应于派生对象的基类部分。
12.为什么通常按引用传递对象比按值传递对象的效率更高?
按引用(而不是按值)传递对象,这样可以确保函数从虚函数受益。在派生类中重新定义基类的方法,需要使用虚方法。然后,程序将根据对象类型而不是引用(或指针)的类型来选择方法版本。
另外,按引用(而不是按值)传递对象可以节省内存和时间,尤其对于大型对象。按值传递对象的主要优点在于可以保护原始数据,但可以通过将引用作为const类型传递,来达到同样的目的。
13.假设Corporation是基类,PublicCorporation是派生类。再假设这两个类都定义了head( )函数,ph是指向Corporation类型的指针,且被赋给了一个PublicCorporation对象的地址。如果基类将head( )定义为:a.常规非虚方法;b.虚方法; 则ph->head( )将被如何解释?
在派生类中重新定义基类的方法,需要使用虚方法。然后,程序将根据对象类型而不是引用(或指针)的类型来选择方法版本。
如果head( )是一个常规方法,则ph->head( )将调用Corporation::head( );
如果head( )是一个虚函数,则ph->head( )将调用PublicCorporation::head( )。
两种C-风格字符串的内存分配(基类使用静态内存分配,派生类使用动态内存分配)
13p1.h
#ifndef PRIMERPLUS_13P1_H
#define PRIMERPLUS_13P1_H
#include
using namespace std;
// base class
class Cd // represents a CD disk
{
private:
char performers[50];
char label[20];
int selections; // number of selections
double playtime; // playing time in minutes
public:
Cd(const char * s1, const char * s2, int n, double x);
Cd(const Cd & d);
Cd();
virtual ~Cd() {}
virtual void Report() const; // reports all CD data
Cd & operator=(const Cd & d);
};
// 派生类
class Classic : public Cd
{
private:
char *name;
public:
Classic(); // 默认构造函数
Classic(const Classic & c); // 复制构造函数
Classic(const char *s1, const char *s2, const char *s3, int n, double x);
~Classic(){ delete [] name; }
virtual void Report() const;
Classic & operator=(const Classic & c);
};
#endif //PRIMERPLUS_13P1_H
13p1.cpp
#include "13p1.h"
#include
Cd::Cd(const char * s1, const char * s2, int n, double x)
{
strncpy(performers, s1, 50);
if (strlen(s1) >= 50)
performers[49] = '\0';
else
performers[strlen(s1)] = '\0';
strncpy(label, s2, 20);
if (strlen(s2) >= 20)
label[19] = '\0';
else
label[strlen(s2)] = '\0';
selections = n;
playtime = x;
}
Cd::Cd(const Cd & d)
{
strcpy(performers, d.performers);
strcpy(label, d.label);
selections = d.selections;
playtime = d.playtime;
}
Cd::Cd()
{
performers[0] = '\0';
label[0] = '\0';
selections = 0;
playtime = 0.0;
}
void Cd::Report() const
{
cout << "performers:" << performers << endl;
cout << "label:" << label << endl;
cout << "selections:" << selections << endl;
cout << "playtime:" << playtime << endl;
}
Cd & Cd::operator=(const Cd & d)
{
if (this == &d)
return *this;
strcpy(performers, d.performers);
strcpy(label, d.label);
selections = d.selections;
playtime = d.playtime;
return *this;
}
// 派生类方法定义
Classic::Classic() : Cd()
{
name = nullptr; // 初始化为一个空指针
}
// 复制构造函数
Classic::Classic(const Classic & c) : Cd(c)
{
name = new char[strlen(c.name) + 1];
strcpy(name, c.name);
}
Classic::Classic(const char *s1, const char *s2, const char *s3,
int n, double x) : Cd(s2, s3, n, x)
{
name = new char[strlen(s1) + 1];
strcpy(name, s1);
}
void Classic::Report() const
{
Cd::Report();
cout << "name:" << name << endl; // 这里要注意
}
Classic & Classic::operator=(const Classic & c)
{
if (this == &c)
return *this;
Cd::operator=(c);
delete [] name; // 这里要注意
name = new char[strlen(c.name) + 1];
strcpy(name, c.name);
return *this;
}
use13p1.cpp
#include
#include"13p1.h"
using namespace std;
void Bravo(const Cd &disk);
int main(void)
{
Cd c1("Beatles", "Capitol", 14, 35.5);
Classic c2 = Classic("Piano Sonata in B flat,Fantasia in C", "Alfred Brendel", "Philips", 2, 57.17);
Cd *pcd = &c1; // 指针指向基类
cout << "Using object directly:\n";
c1.Report(); // 打印基类
c2.Report(); // 打印派生类
cout << "Using type cd * pointer to objects:\n";
pcd->Report(); // 打印基类
pcd = &c2; // 指针指向派生类
pcd->Report(); // 打印派生类
cout << "Calling a function with a Cd reference argument:\n";
Bravo(c1); // 引用和指针一样
Bravo(c2);
cout << "Testing assignment:";
Classic copy;
copy = c2; // 使用赋值运算符函数
copy.Report(); // 打印派生类
return 0;
}
void Bravo(const Cd & disk)
{
disk.Report();
}
13p3.h
#ifndef PRIMERPLUS_13P3_H
#define PRIMERPLUS_13P3_H
#include
using namespace std;
class DmaABC
{
private:
char * label;
int rating;
public:
DmaABC(const char * l = "NULL", const int r =0);
DmaABC(const DmaABC & rs);
DmaABC &operator=(const DmaABC &rs);
virtual ~DmaABC();
virtual void View() const;
friend ostream &operator<<(ostream &os, const DmaABC &rs);
};
//
class baseDMA : public DmaABC
{
public:
baseDMA(const char * l = "NULL", int r = 0) : DmaABC(l, r) {}
baseDMA(const baseDMA & rs) : DmaABC(rs) {}
virtual void View() const;
friend ostream &operator<<(ostream &os, const baseDMA &rs);
};
//
class lacksDMA :public DmaABC
{
private:
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 DmaABC & d);
friend ostream & operator<<(ostream & os, const lacksDMA & ls);
virtual void View() const;
};
//
class hasDMA :public DmaABC
{
private:
char * style;
public:
hasDMA(const char * s = "none", const char * l = "null", int r = 0);
hasDMA(const char * s, const DmaABC & d);
hasDMA(const hasDMA & hs);
~hasDMA();
hasDMA & operator=(const hasDMA & hs);
friend ostream & operator<<(ostream & os, const hasDMA & hs);
virtual void View() const;
};
#endif
13p3.cpp
#include "13p3.h"
#include
// 抽象基类定义
DmaABC::DmaABC(const char * l, const int r)
{
// label = nullptr; // 为什么不能定义为空指针,因为有参数了
label = new char[strlen(l) + 1];
std::strcpy(label, l);
rating = 0;
}
DmaABC::DmaABC(const DmaABC & rs)
{
label = new char[strlen(rs.label) + 1];
std::strcpy(label, rs.label);
rating = rs.rating;
}
DmaABC & DmaABC::operator=(const DmaABC &rs)
{
if (this == &rs)
return *this;
delete [] label;
label = new char[strlen(rs.label) + 1];
strcpy(label, rs.label);
rating = rs.rating;
return *this;
}
DmaABC::~DmaABC()
{
delete [] label;
}
void DmaABC::View() const
{
cout << "label :" << label << endl;
cout << "rating :" << rating << endl;
}
ostream &operator<<(ostream &os, const DmaABC &d)
{
os << "label :" << d.label << endl;
os << "rating :" << d.rating << endl;
// return os;
}
// baseDMA mothods
void baseDMA::View() const
{
DmaABC::View();
}
ostream & operator<<(ostream &os, const baseDMA &rs)
{
os << (const DmaABC &) rs;
// return os;
}
// lacksDMA methods
lacksDMA::lacksDMA(const char * c, const char * l, int r) : DmaABC(l, r)
{
strncpy(color, c, COL_LEN-1);
if (strlen(c) >= (COL_LEN-1))
color[COL_LEN-1] = '\0';
else
color[strlen(c)] = '\0';
}
lacksDMA::lacksDMA(const char * c, const DmaABC & d) : DmaABC(d)
{
strncpy(color, c, COL_LEN - 1);
if (strlen(c) >= (COL_LEN-1))
color[COL_LEN-1] = '\0';
else
color[strlen(c)] = '\0';
}
ostream & operator<<(ostream & os, const lacksDMA & ls)
{
os << (const DmaABC &) ls;
os << "color :" << ls.color << endl;
// return os;
}
void lacksDMA::View() const
{
DmaABC::View();
cout << "color :" << color << endl;
}
// hasDMA methods
hasDMA::hasDMA(const char * s, const char * l, int r) : DmaABC(l, r)
{
style = new char[strlen(s) + 1];
strcpy(style, s);
}
hasDMA::hasDMA(const char * s, const DmaABC & d) : DmaABC(d)
{
style = new char[strlen(s) + 1];
strcpy(style, s);
}
hasDMA::hasDMA(const hasDMA & hs) : DmaABC(hs)
{
style = new char[std::strlen(hs.style) + 1];
strcpy(style, hs.style);
}
hasDMA::~hasDMA()
{
delete [] style;
}
hasDMA & hasDMA::operator=(const hasDMA & hs)
{
if (this == &hs)
return *this;
DmaABC::operator=(hs); // copy base portion
delete [] style; // prepare for new style
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
return *this;
}
ostream & operator<<(ostream & os, const hasDMA & hs)
{
os << (const DmaABC &) hs;
os << "Style :" << hs.style << endl;
return os;
}
void hasDMA::View() const
{
DmaABC::View();
cout << "Style :" << style << endl;
}
use13p3.cpp
#include
#include "13p3.h"
int main()
{
baseDMA shirt("Portabelly", 8);
lacksDMA balloon("red", "Blimpo", 4);
hasDMA map("Mercator", "Buffalo Keys", 5);
cout << "Displaying baseDMA object:\n";
cout << shirt << endl;
cout << "Displaying lacksDMA object:\n";
cout << balloon << endl;
cout << "Displaying hasDMA object:\n";
cout << map << endl;
lacksDMA balloon2(balloon);
cout << "Result of lacksDMA copy:\n";
cout << balloon2 << endl;
hasDMA map2;
map2 = map;
cout << "Result of hasDMA assignment:\n";
cout << map2 << endl;
map2.View();
// std::cin.get();
return 0;
}
13p4.h
#ifndef PRIMERPLUS_13P4_H
#define PRIMERPLUS_13P4_H
#include
using namespace std;
class Port
{
private:
char * brand;
char style[20]; // i.e., tawny, ruby, vintage
int bottles;
public:
Port(const char * br = "none", const char * st = "none", int b = 0);
Port(const Port & p); // copy constructor
virtual ~Port() { delete [] brand; }
Port & operator=(const Port & p);
Port & operator+=(int b); // adds b to bottles
Port & operator-=(int b); // subtracts b from bottles, if available
int BottleCount() const { return bottles; }
virtual void Show() const;
friend ostream & operator<<(ostream & os, const Port & p);
};
class VintagePort : public Port // style necessarily = "vintage"
{
private:
char * nickname; // i.e., "The Noble" or "Old Velvet", etc.
int year; // vintage year
public:
VintagePort();
VintagePort(const char * br, const char * st, int b, const char * nn, int y);
VintagePort(const VintagePort & vp);
~VintagePort() { delete [] nickname; }
VintagePort & operator=(const VintagePort & vp);
void Show() const;
friend ostream & operator<<(ostream & os, const VintagePort & vp);
};
#endif //PRIMERPLUS_13P4_H
13p4.cpp
#include "13p4.h"
#include
Port::Port(const char * br, const char * st, int b)
{
brand = new char[strlen(br) + 1];
strcpy(brand, br);
strncpy(style, st, 20);
if (strlen(st) >= 20)
style[19] = '\0';
else
style[strlen(st)] = '\0';
bottles = b;
}
Port::Port(const Port & p)
{
brand = new char[strlen(p.brand) + 1]; // 注意复制构造函数中也需要为指针变量开辟内存
strcpy(brand, p.brand);
strcpy(style, p.style);
bottles = p.bottles;
}
Port & Port::operator=(const Port & p)
{
if (this == &p)
return *this;
delete [] brand;
brand = new char[strlen(p.brand) + 1];
strcpy(brand, p.brand);
strcpy(style, p.style);
bottles = p.bottles;
return *this;
}
Port & Port::operator+=(int b)
{
bottles += b;
return *this;
}
Port & Port::operator-=(int b) // 这里注意
{
bottles -= b;
return *this;
}
void Port::Show() const
{
cout << "brand :" << brand << endl;
cout << "style :" << style << endl;
cout << "bottles :" << bottles << endl;
}
ostream & operator<<(ostream & os, const Port & p)
{
os << p.brand << ", " << p.style << ", " << p.bottles; // 这里注意最后的分号
return os;
}
// 派生类
VintagePort::VintagePort() : Port()
{
nickname = nullptr;
year = 0;
}
VintagePort::VintagePort(const char * br, const char * st, int b, const char * nn, int y) : Port(br, st, b)
{
nickname = new char[strlen(nn) + 1];
strcpy(nickname, nn);
year = y;
}
VintagePort::VintagePort(const VintagePort & vp) : Port(vp)
{
nickname = new char[strlen(vp.nickname) + 1];
strcpy(nickname, vp.nickname);
year = vp.year;
}
VintagePort & VintagePort::operator=(const VintagePort & vp)
{
if (this == &vp)
return *this;
delete [] nickname;
Port::operator=(vp);
nickname = new char[strlen(vp.nickname) + 1];
strcpy(nickname, vp.nickname);
year = vp.year;
return *this;
}
void VintagePort::Show() const
{
Port::Show();
cout << "nickname :" << nickname << endl;
cout << "year :" << year << endl;
}
ostream & operator<<(ostream & os, const VintagePort & vp)
{
os << (const Port &)vp;
os << ", " << vp.nickname << ", " << vp.year;
return os; // 这里注意
}
use13p4.cpp
#include "13p4.h"
int main(void)
{
Port port1("port1", "port111", 100);
cout << port1 << endl;
VintagePort vp1("vp1", "vp111", 100, "VintagePort1", 10);
cout << vp1 << endl;
VintagePort vp2 = vp1;
cout << vp2 << endl << endl;
Port *p_port;
p_port = &port1;
p_port->Show();
cout << endl;
p_port = &vp1;
p_port->Show();
return 0;
}