c++primer第十三章 类继承

本章内容:
单个类就可以提供用于管理对话框的全部资源。通常,类库是以源代码的方式提供的,这意味着可以对其进行修改,以满足需求。但是,C+-+提供了比修改代码更好的方法来扩展和修改类。这种方法叫作类继承(class inheriance)。它能够从已有的类派生出新的类,而派生类继承了原有类(称为基类)的特征,包括方法。

继承能完成的工作:

一个简单的基类

头文件tabetenn0.h

#ifndef TABTENNO_H_
#define TABTENNO_H_ // simple base class
class TableTennisPlayer
{
private:enum{LIM = 20};char firstname[LIM];char lastname[LIM];bool hasTable;public:TableTennisPlayer(const char *fn = "none",const char *ln = "none", bool ht = false);void Name() const;bool HasTable() const { return hasTable; };void ResetTable(bool v) { hasTable = v; };
};#endif

方法文件tabtenn0.cpp

// tabtenn0.cppsimple base class methods
#include "tabtenn0.h"
#include <iostream>
#include <cstring>
TableTennisPlayer::TableTennisPlayer(const char *fn,const char *ln, bool ht)
{std::strncpy(firstname, fn, LIM - 1);firstname[LIM - 1] = '\0';std::strncpy(lastname, ln, LIM - 1);lastname[LIM - 1] = '\0';hasTable = ht;
}void TableTennisPlayer::Name() const
{std::cout << lastname << "," << firstname;
}

程序文件usett0.cpp

#include <iostream>
#include "tabtenn0.h"
int main(void)
{using std::cout;TableTennisPlayer playerl("Chuck", "Blizzard", true);TableTennisPlayer player2("Tara", "Boomdea", false);playerl.Name();if (playerl.HasTable())cout << ":has a table.\n";elsecout << ":hasn't a table.\n";player2.Name();if (player2.HasTable())cout << ":has a table";elsecout << ":hasn't a table.\n";return 0;
}

TableTennisPlayer类只是记录会员的名字以及是否有球桌。

派生一个类

--些成员曾经参加过当地的乒乓球锦标赛,需要这样一个类,它能包括成员在比赛中的比分。与其从零开始,不如从 TableTennisClass 类派生出一个类。

冒号指出 RatedPlayer 类的基类是 TableTennisplayer 类。

RatePlayer对象可以使用TableTennisPlayer类的 Name(),HasTable(),ResetTable()方法

继承需要添加

class RatePlayer: public TableTennisPlayer
{private:unsigned int rating;public:RatePlayer(unsigned int r = 0, const char * fn = "none",const char * ln = "none", bool ht = false);RaterPlayer(unsigned int r, const TableTennisPlayer & tp);unsigned int Rating(){return rating;}void ResetRating(unsigned int r){rating = r;}
}

 构造函数:访问权限的考虑

派生类不能直接访问基类的私有成员,而必须通过基类方法进行访问。例如,RatedPlayer 构造函数不能直接设置继承的成员(firsame、lastame和 hasTable),而必须使用基类的公有方法来访问私有的基类成员。具体地说,派生类构造函数必须使用基类构造函数。

创建派生类对象时,程序首先创建基类对象。

基类对象应当在程序进入派生类构造函数之前被创建。C++使用成员初始化列表句法来完成这种工作。

RatedPlayer::RatedPlayer(unsigned int r, const char * fn, const char * ln,
bool ht):TableTennisPlayer(fn, ln, ht)
{rating = r;
}

如果省略成员初始化列表:

基类对象必须首先被创建,如果不调用基类构造函数,程序将使用默认的基类构造函数,因此上述代码等效为

 第二个构造函数的代码

因为的类型为 const TableTennisPlayer&,因此将调用基类的复制构造函数。基类没有定义复制构造函数,但第 12章介绍过,如果需要使用复制构造函数,而又没有定义,编译器将自动生成一个。在这种情况下,执行成员复制的隐式复制构造承数是合适的,因为这个类没有使用动态内存分配

构造函数也可以写为

派生类构造函数的要点

 使用派生类

头文件

#ifndef TABTENNO_H_
#define TABTENNO_H_ // simple base class
class TableTennisPlayer
{
private:enum{LIM = 20};char firstname[LIM];char lastname[LIM];bool hasTable;public:TableTennisPlayer(const char *fn = "none",const char *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 char *fn = "none",const char *ln = "none", bool ht = false);RatedPlayer(unsigned int r, const TableTennisPlayer &tp);unsigned int Rating() { return rating; }void ResetRating(unsigned int r) { rating = r; }
};#endif

方法文件

// tabtenn0.cppsimple base class methods
#include "tabtenn0.h"
#include <iostream>
#include <cstring>
TableTennisPlayer::TableTennisPlayer(const char *fn,const char *ln, bool ht)
{std::strncpy(firstname, fn, LIM - 1);firstname[LIM - 1] = '\0';std::strncpy(lastname, ln, LIM - 1);lastname[LIM - 1] = '\0';hasTable = ht;
}void TableTennisPlayer::Name() const
{std::cout << lastname << "," << firstname;
}RatedPlayer::RatedPlayer(unsigned int r, const char *fn, const char *ln,bool ht) : TableTennisPlayer(fn, ln, ht)
{rating = r;
}RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer &tp) : TableTennisPlayer(tp), rating(r)
{
}

 程序文件

#include <iostream>
#include "tabtenn0.h"int main(void)
{using std::cout;using std::endl;TableTennisPlayer player1("Tara", "Boomdea", false);RatedPlayer rplayer1(1140, "Mallory", "Duck", true);rplayer1.Name(); // derived obiect uses base methodif (rplayer1.HasTable())cout << ":has a table.\n";elsecout << ":hasn't a table.\n";player1.Name(); // base object uses base methodif (player1.HasTable())cout << ":has a table";elsecout << ":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;
}

派生类和基类之间的关系

派生类与基类之间有一些特殊关系。其中之一是派生类对象可以使用基类的方法,条件是方法不是私有的:

RatedPlayer rplayer1(1140, "Mallory", "Duck", true);rplayer1.Name(); // derived obiect uses base method

另外两个重要的关系是:基类指针可以在不进行显式类型转换的情况下指向派生类对象;基类引用可以在不进行显式类型转换的情况下引用派生类对象:

基类指针只能调用基类方法,而不能调用派生类的方法。

但是不可以将基类对象和地址赋给派生类引用和指针:

在下列函数中

rt是基类引用,他可以指向基类对象或派生类对象,show()中使用TableTennis参数或Ratedplayer参数

形参指向基类的指针的函数有相似的关系

继承——is-a关系

c++三种继承关系:

  • 公有继承
  • 保护继承
  • 私有继承

公有继承是最常用的方式,它建立一种is-a关系,即派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类对象执行。

例如,假设有一个Fruit 类,可以保存水果的重量和热量。因为香蕉是一种特殊的水果,所以可以从Fruit类派生出Banana类。新类将继承原始类的所有数据成员,因此,Banana对象将包含表示香蕉重量和热量的成员。新的Banana 类还添加了专门用于香蕉的成员,这些成员通常不用于水果,例如 Banana InstitutePeelIndex(香蕉机构果皮索引)。因为派生类可以添加特性,所以,将这种关系称为is-a-kind-of/(是一种)关系可能更准确,但是通常使用术语 is-a。

多态公有继承

方法的行为应取决于调用该方法的对象。这种较复杂的行为称为多态--具有多种形态,就是指同一个方法的行为将随上下文而异。有两种重要的机制可用于实现多态公有继承:

例子:

一个类用于表示基本支票账户--Brass Account,另一个类用f表示代表 Brass Plus 支票账户,它添加了透支保护特性。也就是说,如果用户签出一张超出其存款余额的文票--但是超出的数额并不是很大,银行将支付这张支票,对超出的部分收取额外的费用,并追加罚款。可以根据要保存的数据以及允许执行的操作来确定这两种账户的特征。

下面是用于Brass Account支票账户的信息。

Brass Plus支票账户包含BrassAccount的所有信息以及一下信息项:

开发Brass类和BrassPlus类

有关BrassPlus类的信息

  • BrassPlus账户限制用户的透支款额,默认为500,有些客户的限额可能不同
  • 银行可以修改用户的透支限额
  • BrassPlus账户对贷款收取利息,默认为10%,有些用户的利率不同
  • 银行可以修改用户的利率
  • 账户记录用户所欠银行的金额,用户不能通过常规存款或从其他账户转账的方法偿付,必须一现金的方式交给特定的工作人员。

  • BrassPlus 类在 Brass类的基础上添加了3个私有数据成员和3个公有成员函数。
  • Brass 类和 BrassPlus 类都声明了 VewAcct()和 Withdraw()方法,但 BrassPlus 对象和 Brass 对象的这些方法的行为是不同的
  • Brass类在:声明 ViewAcct()和 Withdraw()时使用了新关键字virual。这些方法被称为虚方法( virtual method)
  • Brass类还声明了个虚拟析构函数,虽然该析构函数不执行任何操作。

第二点介绍了声明如何指出方法在派生类的行为的不同。两个VewAcct()原型表明将有2个独立的方法定义。基类版本的限定名为Brass:ViewAcct(),派生类版本的限定名为BrassPlus::ViewAcct()。程序将使用对象类型来确定使用哪个版本:

第三点(使用 virtual)比前两点要复杂。如果方法是通过引用或指针而不是对象调用的,它将确定使川哪一种方法。如果没有使用关键字vinual,程序将根据引用类型或指针类型选择方法:如果使用了virual程序将根术引川或指针指问的对象的类型来选择方法。如果 ViewAcct()不是虚拟的,则程序的行为如下:

使用指针代替,行为类似。

头文件

#ifndef BRASS_H_
#define BRASS_H_class Brass
{
private:enum{MAX = 35};char fullName[MAX];long acctNum;double balance;public:Brass(const char *s = "Nullbody", 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 char *s = "Nullbody", long an = -1, double bal = 0.0,double ml = 500, double r = 0.10);BrassPlus(const Brass &ba, double ml = 500, double r = 0.1);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

 类实现

方法文件

#include <iostream>
#include <cstring>
#include "brass.h"
using std::cout;
using std::endl;
using std::ios_base;Brass::Brass(const char *s, long an, double bal)
{std::strncpy(fullName, s, MAX - 1);fullName[MAX - 1] = '\0';acctNum = an;balance = bal;
}void Brass::Deposit(double amt)
{if (amt < 0)cout << "Negative deposit not allowed:" << "deposit is cancelled.\n";elsebalance += amt;
}void Brass::Withdraw(double amt)
{if (amt < 0)cout << "Withdrawal amount must be positive"<< "withdrawal canceled.\n";else if (amt <= balance)balance -= amt;elsecout << "Withdrawal amount of $" << amt << "exceeds your balance.\n"<< "withdrawal canceled.\n";
}double Brass::Balance() const
{return balance;
}void Brass::ViewAcct() const
{// set. up ###.## formatios_base::fmtflags initialState =cout.setf(ios_base::fixed, ios_base::floatfield);cout.setf(ios_base::showpoint);cout.precision(2);cout << "Client:" << fullName << endl;cout << "Account Number:" << acctNum << endl;cout << "Balance:$" << balance << endl;cout.setf(initialState); // restore original format
}// BrassPlus Methods
BrassPlus::BrassPlus(const char *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 ###.## formatios_base::fmtflags initialState =cout.setf(ios_base::fixed, ios_base::floatfield);cout.setf(ios_base::showpoint);cout.precision(2);Brass::ViewAcct(); // display base portioncout << "Maximum loan:$" << maxLoan << endl;cout << "Owed to bank:$ " << owesBank << endl;cout << "Loan Rate:" << 100 * rate << " %\n ";cout.setf(initialState);
}// redefine how Withdraw()works
void BrassPlus::Withdraw(double amt)
{// set up ###。##formatios_base::fmtflags initialState =cout.setf(ios_base::fixed, ios_base::floatfield);cout.setf(ios_base::showpoint);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);}elsecout << "Credit limit exceeded. Transaction cancelled.\n";cout.setf(initialState);
}

派生类并不能直接访问基类的私有数据,而必须使用基类的公有方法才能访问这些数据。访问的方式取决于方法。构造函数使用一种技术,而其他成员函数使用另一种技术。

派生类构造函数在初始化基类私有数据时,采用的是成员初始化列表句法。RatedPlayer 类构造函数和BrassPlus构造函数都使用这种技术。

// BrassPlus Methods
BrassPlus::BrassPlus(const char *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;
}

非构造函数不能使用成员初始化列表句法,但派生类方法可以调用公有的基类方法。例如,
BrassPlus版本的 ViewAcct()核心内容如下

void BrassPlus::ViewAcct() const
{// set up ###.## formatios_base::fmtflags initialState =cout.setf(ios_base::fixed, ios_base::floatfield);cout.setf(ios_base::showpoint);cout.precision(2);Brass::ViewAcct(); // display base portioncout << "Maximum loan:$" << maxLoan << endl;cout << "Owed to bank:$ " << owesBank << endl;cout << "Loan Rate:" << 100 * rate << " %\n ";cout.setf(initialState);
}

调用基类方法Brass::ViewAcct()来显示基类数据成员。

代码必须使用作用域解析操作符,不然

如果代码没有使用作用域解析操作符,编译器将认为VewAcct()是BrassPlus::ViewAcct(),
这将创建个不会终止的递归函数---这种情况可不好。

使用类

#include <iostream>
#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;
}

 虚方法的行为

在上述代码中,由于方法是通过对象(而不是指针或引用)调用的,所以没有使用虚方法特性。下面米看一个使用了虚方法的例子。假设要同时管理Brass和 BrassPlus账户,如果能使用同一个数组来保存 Brsss 和 BrassPlus 对象,将很有帮助,但这是不可能的。数组中所有元素的类型必须相同,而 Brass 和BrassPlus是不同的类型。不过,可以创建指向 Brass 的指针数组。这样,每个元素的类型都相同,但由于使用的是公有继承模型,因此 Brass 指针既可以指向 Brass对象,也可以指向 BrassPlus 对象。因此,可以使用…个数组来表示多种类型的对象。这就是多态性,程序单13.10是一个简单的例子。

#include <iostream>
#include "brass.h"
const int CLIENTS = 4;
const int LEN = 40;
int main()
{using std::cin;using std::cout;using std::endl;Brass *p_clients[CLIENTS];int i;for (i = 0; i < CLIENTS; i++){char temp[LEN];long tempnum;double tempbal;char kind;cout << "Enter client's name: ";cin.getline(temp, LEN);cout << "Enter client's account number: ";cin >> tempnum;cout << "Enter opening balance:$";cin >> tempbal;cout << "Enter lfor Brass Account or " << "2 for Brassplus Account:";while (cin >> kind && (kind != '1' && kind != '2'))cout << "Enter either 1or 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 decimai fraction:";cin >> trate;p_clients[i] = new BrassPlus(temp, tempnum, tempbal, tmax, trate);}while (cin.get() != '\n')continue;}cout << endl;for (i = 0; i < CLIENTS; i++){p_clients[i]->ViewAcct();cout << endl;}for (i = 0; i < CLIENTS; i++)delete p_clients[i]; // free memorycout << "Done.\n";return 0;
}

多态特性体现为

for (i = 0; i < CLIENTS; i++){p_clients[i]->ViewAcct();cout << endl;}

为何使用虚拟析构函数

 静态联编和动态联编

程序调用函数时,将使用哪个可执行代码块呢?编译器负责回答这个问题。将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编(binding)。

在C++中,由于函数重载的缘故,这项任务更复杂。编译器必须查看函数参数以及函数名才能确定使用哪个函数。然而,C/C++编译器可以在编译过程完成这种联编。在编译过程中进行联编被称为静态联编(static binding),又称为早期联编(eanly binding)。不过,虚函数使这项工作变得更困难。

将选择哪种类型的对象。所以,编译器必须生成能够在程序运行时选择正确的虚方法的代码,这被称为动态联编(dyamic binding),又称为晚期联编(late binding)

指针和引用类型的兼容性

将派生类引用或指针转换为基类引用或指针被称为向上强制转换(upcasting),这使公有继承不需要进行显式类型转换。该规则是is-a关系的一部分。

将指向对象的指针作为函数参数时,也是如此。向上强制转换是可传递的,也就是说,如果从 BrassPlus派生出BrassPlusPlus类,则Brass指针或引用可以引用Brass 对象、BrassPlus对象或 BrassPlusPlus 对象。

将基类指针或引用转换为派生类指针或引用--称为向下强制转换(downcasting)。相反的过程一如果不使用显式类型转换,则向下强制转换是不允许的。原因是is-a关系通常是不可逆的。派生类可以新增数据成员,因此使用这些数据成员的类成员函数不能应用于基类

隐式向上强制转换使基类指针或引用可以指向基类对象或派生类对象,因此需要动态联编。C++使用虚成员函数来满足这种需求。

虚成员函数和动态联编

 对于代码

如果没有定义虚函数,将 ViewAcct()关联到 Brass::ViewAcct()。简而言之,编泽器对非虚方法使用静态联编。 

但是,如果在基类中将 ViewAcct()声明为虚拟的,则bp->ViewAcct()根据对象类型(BrassPlus)调用BrassPlus:ViewAcct(),编译器对虚方法使用动态联编。

为什么有两种类型的联编以及为什么默认为静态联编

静态效率更高

虚函数的工作原理

、编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(virtual function table,vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址:如果派生类没有重新定义虚函数,该 vb将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数的资质也被添加到vtbl中。

有关虚函数的注意事项:

  • 在基类方法的声明中使用关键字 vintual可使该方法在基类以及所有的派生类(包括从派生类派生出来的类)中是虚拟的。
  • 如果使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不使用为引用或指针类型定义的方法。这称为动态联编或晚期联编。这种行为非常重要,因为这样基类指针或引用可以指向派生类对象。
  • 加果定义的类将被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚拟的。

对于一些特殊函数

构造函数

构造函数不能是虚函数。创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,然后,派生类的构造函数将使用基类的一个构造函数,这种顺序不同于继承机制。因此,派生类不继承基类的构造函数,所以将类构造函数声明为虚拟的没有什么意义。

析构函数

析构函数应当是虚函数,除非类不用做基类。例如,假设 Employee 是基类,Singer 是派生类,并添加一个 char *成员,该成员指向由 new 分配的内存。当 Singer 对象过期时,必须调用~Singer()析构函数来释放内存

 如果使用默认的静态联编,delete 语句将调用~Employee()析构函数。这将释放由 Singer 对象中的Employee 部分指向的内存,但不会释放新的类成员指向的内存。但如果析构函数是虚拟的,则上述代码将先调用~Singer 析构函数释放由 Singer 组件指向的内存,然后,调用~Employee()析构函数来释放由Employee 组件指问的内存,

友元函数 

没有重新定义的函数
重新定义隐藏方法

编译器警告:

代码的含义

新定义的函数覆盖了原来的的函数,是的showperks()不接受参数。简而言之,重新定义继承的方法并不是重载。如果在派生类中重新定义函数,将不是使用相同的函数特征标覆盖基类声明,而是隐藏同名的基类方法,不管参数特征标如何。

这引出了两条经验规则:第一,如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针(这种例外是新出现的)。这种特性被称为返问类型协变(covariance ofretumntype),因为允许返回类型随类类型的变化而变化:

第二,如果基类声明被重载了,则应在派生类总重新定义所有的基类版本

如果只定义一个版本,则另外两个版本会被隐藏。

访问控制:protected

关键字 protected与private 相似,在类外只能用公有类成员来访问 protected 部分中的类成员。private 和 protected之间的区别只有在基类派生的类中才会表现出来。派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。因此,对于外部世界来说保护成员的行为一私有成员相似:但对于派生类来说,保护成员的行为与公有成员相似。

如果定义balance为保护类型

则在派生类中的方法中可以直接访问balance

 抽象基类

抽象基类(abstract base class, ABC)

例如椭圆类

圆类

可以抽象为

C++通过使用纯虚函数(pure virtualfunction)提供未实现的函数。纯虚函数声明的结尾处为=0,参见Area()方法:

当类声明中包含纯虚函数时,则不能创建该类的对象。这里的理念是,包含纯虚函数的类只用作基类。要成为真正的 ABC,必须至少包含一个纯虚函数。原型中的=0 使虚函数成为纯虚函数。这里的方法 Area()没有定义,但 C++甚至允许纯虚函数有定义。

应用ABC概念

#ifndef ACCTABC_H_
#define ACCTABC_H_#include <iostream>
class AcctABC
{
private:enum{MAX = 35};char fullName[MAX];long acctNum;double balance;protected:const char *FulName() const { return fullName; }long AcctNum() const { return acctNum; }std::ios_base::fmtflags SetFormat() const;public:AcctABC(const char *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(){}
};class Brass : public AcctABC
{
public:Brass(const char *s = "Nullbody", long an = -1,double bal = 0.0) : AcctABC(s, an, bal) {}virtual void Withdraw(double amt);virtual void ViewAcct() const;virtual ~Brass() {}
};
class BrassPlus : public AcctABC
{
private:double maxLoan;double rate;double owesBank;public:BrassPlus(const char *s = "Nullbody", long an = -1, double bal = 0.0,double ml = 500, double r = 0.10);BrassPlus(const Brass &ba, double ml = 500, double r = 0.1);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

方法文件

#include <iostream>
#include <cstring>
using std::cout;
using std::endl;
using std::ios_base;
#include "acctabc.h"// Abstract Base ClassAcctABC::AcctABC(const char *s, long an, double bal)
{std::strncpy(fullName, s, MAX - 1);fullName[MAX - 1] = '\0';acctNum = an;balance = bal;
}void AcctABC::Deposit(double amt)
{if (amt < 0)cout << "Negative deposit not allowed:" << "deposit is cancelled.\n";elsebalance += amt;
}void AcctABC::Withdraw(double amt)
{balance -= amt;
}// protected method
std::ios_base::fmtflags AcctABC::SetFormat() const
{// set. up ###.## formatios_base::fmtflags initialState =cout.setf(ios_base::fixed, ios_base::floatfield);cout.setf(ios_base::showpoint);cout.precision(2);return initialState;
}
// Brass methodsvoid Brass::ViewAcct() const
{// set. up ###.## formatios_base::fmtflags initialState = SetFormat();cout << " Brass Client:" << FulName() << endl;cout << "Account Number:" << AcctNum() << endl;cout << "Balance:$" << Balance() << endl;cout.setf(initialState); // restore original format
}void AcctABC::Withdraw(double amt)
{if (amt < 0)cout << "Withdrawal amount must be positive"<< "withdrawal canceled.\n";else if (amt <= balance)balance -= amt;elsecout << "Withdrawal amount of $" << amt << "exceeds your balance.\n"<< "withdrawal canceled.\n";
}// BrassPlus Methods
BrassPlus::BrassPlus(const char *s, long an, double bal,double ml, double r) : AcctABC(s, an, bal)
{maxLoan = ml;owesBank = 0.0;rate = r;
}BrassPlus::BrassPlus(const Brass &ba, double ml, double r) : AcctABC(ba)
// uses implicit copy constructor
{maxLoan = ml;owesBank = 0.0;rate = r;
}// redefine how ViewAcct()works
void BrassPlus::ViewAcct() const
{// set up ###.## formatios_base::fmtflags initialState = SetFormat();cout << " Brass Client:" << FulName() << endl;cout << "Account Number:" << AcctNum() << endl;cout << "Balance:$" << Balance() << endl;cout << "Maximum loan:$" << maxLoan << endl;cout << "Owed to bank:$ " << owesBank << endl;cout << "Loan Rate:" << 100 * rate << " %\n ";cout.setf(initialState);
}// redefine how Withdraw()works
void BrassPlus::Withdraw(double amt)
{// set up ###。##formatios_base::fmtflags initialState = SetFormat();double bal = 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 << " Finance charge: $ " << advance * rate << endl;Deposit(advance);AcctABC::Withdraw(amt);}elsecout << "Credit limit exceeded. Transaction cancelled.\n";cout.setf(initialState);
}

继承和动态内存分配

派生类不使用new

该声明包含:析构函数,复制构造函数,重载赋值操作符;

从baseDMA 派生出lackDMA

不需要为派生类定义显式析构函数,复制构造函数,重载赋值操作符;

派生类使用new

这种情况需要定义定义显式析构函数,复制构造函数,重载赋值操作符。

使用动态内存分配和友元的继承范例

头文件

#ifndef DMA_H_
#define DMA_H_#include <iostream>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);
};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);
};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

方法文件

#include "dma.h"
#include <cstring>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 portionstyle = 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;
}

程序文件

#include <iostream>
#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 << shirt << endl;cout << balloon << endl;cout << map << endl;lacksDMA balloon2(balloon);hasDMA map2;map2 = map;cout << balloon2 << endl;cout << map2 << endl;return 0;
}

类设计回顾(待整理)

编译器生成的成员函数

默认构造函数

复制构造函数

赋值操作符

其他的类方法

构造函数

析构函数

转换

按值传递对象和传递引用

返回对象和返回引用

使用const

公有继承的考虑因素

is-a关系

什么不能被继承

赋值操作符

私有成员和保护成员

虚方法

析构函数

友元函数

有关基类方法的说明

类函数小结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/437242.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手机/平板端 Wallpaper 动态壁纸文件获取及白嫖使用指南

Wallpaper 动态壁纸文件获取及使用指南 目录 壁纸文件获取手机 / 平板使用手机 / 平板效果预览注意事项PC/Mac 使用 1. 壁纸文件获取链接 链接&#xff1a;夸克网盘分享 复制链接到浏览器打开并转存下载即可。 &#xff08;主页往期视频的 4K 原图和 mpkg 动态壁纸文件&#xf…

Redis接口访问优化

说明&#xff1a;之前写过一篇使用Redis接口访问的博客&#xff0c;如下。最近有相关需求&#xff0c;把代码拿出来后&#xff0c;做了一些优化&#xff0c;挺有意思的&#xff0c;本文介绍在原基础上 使用Redis实现接口防抖 优化 总的来说&#xff0c;这次使用Redis实现接口…

IDEA 系列产品 下载

准备工作 下载 下载链接&#xff1a;https://www.123865.com/ps/EF7OTd-yVHnH 仅供参考 环境 演示环境&#xff1a; 操作系统&#xff1a;windows10 产品&#xff1a;IntelliJ IDEA 版本&#xff1a;2024.1.2 注意&#xff1a;如果需要其他产品或者版本可以自行下载&#xff0…

ESP32微信小程序SmartConfig配网

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 ESP32&微信小程序SmartConfig配网 前言一、SmartConfig是什么&#xff1f;二、使用乐鑫官方的smart_config例子1.运行照片 三、微信小程序总结 前言 本人是酷爱ESP32S3这…

C++系列-多态

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 多态 多态就是不同类型的对象&#xff0c;去做同一个行为&#xff0c;但是产生的结果是不同的。 比如说&#xff1a; 都是动物叫声&#xff0c;猫是喵喵&#xff0c;狗是汪汪&am…

LC记录二:丑数专题,一文秒解丑数3题

文章目录 263.丑数1264.丑数21201.丑数3 263.丑数1 https://leetcode.cn/problems/ugly-number/description/ 简单题&#xff0c;丑数只包含质因子2、3、5。所以直接使用 n 循环 除 2 3 5最后判断结果是否等于1即可。 代码&#xff1a; class Solution {public boolean isUg…

Java中的顺序控制、分支控制、嵌套分支if-else

if-else 顺序控制分支控制if-else单分支1.基本语法2.说明&#xff1a;3.案例说明4.流程图 双分支1.基本语法2.说明&#xff1a;3.案例说明4.流程图5.练习 多分支1.基本语法2.说明&#xff1a;3.流程图4.练习 嵌套分支1.基本介绍2.基本语法3.练习 顺序控制 1.介绍&#xff1a;程…

AntFlow-Vue3 :一个仿钉钉流程审批,且满足99.8%以上审批流程需求的企业级工作流平台,开源且免费!

在现代企业管理中&#xff0c;流程审批的高效性直接影响到工作的流畅度与生产力。最近&#xff0c;我发现了一个非常有趣的项目—— AntFlow-Vue3 。这个项目不仅提供了一个灵活且可定制的工作流平台&#xff0c;还能让用户以可视化的方式创建和管理审批流程。 如果你是一名前…

【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-1

忽然有一天&#xff0c;我想要做一件事&#xff1a;去代码中去验证那些曾经被“灌输”的理论。                                                                                  – 服装…

【Java 集合】List接口 —— ArrayList 与 LinkedList 详解

List接口继承自Collection接口&#xff0c;是单列集合的一个重要分支。 在List集合中允许出现重复的元素&#xff0c;所有的元素是以一种线性方式进行存储的&#xff0c;在程序中可以通过索引&#xff08;类似于数组中的元素角标&#xff09;来访问集合中的指定元素。另外&…

ESP32 Bluedroid 篇(1)—— ibeacon 广播

前言 前面我们已经了解了 ESP32 的 BLE 整体架构&#xff0c;现在我们开始实际学习一下Bluedroid 从机篇的广播和扫描。本文将会以 ble_ibeacon demo 为例子进行讲解&#xff0c;需要注意的一点是。ibeacon 分为两个部分&#xff0c;一个是作为广播者&#xff0c;一个是作为观…

时序数据库 TDengine 的入门体验和操作记录

时序数据库 TDengine 的学习和使用经验 什么是 TDengine &#xff1f;什么是时序数据 &#xff1f;使用RPM安装包部署默认的网络端口 TDengine 使用TDengine 命令行&#xff08;CLI&#xff09;taosBenchmark服务器内存需求删库跑路测试 使用体验文档纠错 什么是 TDengine &…

【框架篇】过滤器和拦截器的区别以及使用场景

在项目开发中&#xff0c;常常会同时配置拦截器&#xff08;Interceptor&#xff09;和过滤器&#xff08;Filter&#xff09;&#xff0c;以下就是它们两个主要的区别&#xff1a; 过滤器&#xff08;Filter&#xff09; 配置和实现 Filter的实现还是很简单的&#xff0c;可…

【Python语言初识(六)】

一、网络编程入门 1.1、TCP/IP模型 实现网络通信的基础是网络通信协议&#xff0c;这些协议通常是由互联网工程任务组 &#xff08;IETF&#xff09;制定的。所谓“协议”就是通信计算机双方必须共同遵从的一组约定&#xff0c;例如怎样建立连接、怎样互相识别等&#xff0c;…

k8s搭建一主三从的mysql8集群---无坑

一&#xff0c;环境准备 1.1 k8s集群服务器 ip角色系统主机名cpumem192.168.40.129mastercentos7.9k8smaster48192.168.40.130node1centos7.9k8snode148192.168.40.131node2centos7.9k8snode248192.168.40.132node3centos7.9k8snode348 k8s集群操作请参考《K8s安装部署&…

力扣(leetcode)每日一题 1845 座位预约管理系统| treeSet和priority Queue的区别|线段树

之前发过一篇&#xff0c;感觉还有深挖的地方&#xff0c;于是又补充一些信息 这题目虽然是middle难度题目&#xff0c;要解答出来是只要easy的时间&#xff0c;但是深挖可以有hard的难度 题解1 可以帮助复习线段树的使用&#xff0c;题解2 可以复习一下java基础知识 题解1 线…

Springboot使用redis,以及解决redis缓存穿透,击穿,雪崩等问题

1.Redis面试题-缓存穿透,缓存击穿,缓存雪崩 1 穿透: 两边都不存在&#xff08;皇帝的新装&#xff09; &#xff08;返回空值&#xff09;&#xff08;互斥锁&#xff09;&#xff08;黑名单&#xff09; &#xff08;布隆过滤器&#xff09; 2 击穿&#xff1a;一个或多个热…

Kotlin:1.8.0 的新特性

一、概述 Kotlin 1.8.0版本英语官方文档 Kotlin 1.8.0 中文官方文档 The Kotlin 1.8.0 release is out and here are some of its biggest highlights: Kotlin 1.8.0发布了&#xff0c;下面是它的一些亮点: JVM 平台新增实验性函数&#xff1a;递归复制或删除目录内容改进了 …

9--苍穹外卖-SpringBoot项目中Redis的介绍及其使用实例 详解

目录 Redis入门 Redis简介 Redis服务启动与停止 服务启动命令 Redis数据类型 5种常用数据类型介绍 各种数据类型的特点 Redis常用命令 字符串操作命令 哈希操作命令 列表操作命令 集合操作命令 有序集合操作命令 通用命令 在java中操作Redis Redis的Java客户端 …