C++_primer_plus学习笔记 第13章 类继承

本文详细探讨C++的类继承,涵盖is-a关系、公有派生、保护访问、构造函数成员初始化、向上和向下强制转换、虚成员函数、静态和动态联编、抽象基类以及继承与动态内存分配的关系。通过实例解析,揭示C++如何利用继承来扩展和修改类,并讨论了继承中涉及的关键概念和最佳实践。

本章内容包括:

  • is-a关系继承
  • 公有派生
  • 保护访问
  • 构造函数成员初始化列表
  • 向上和向下强制转换
  • 虚成员函数
  • 早起(静态)联编和晚期(动态)联编
  • 抽象基类
  • 纯虚函数
  • 何时及如何使用公有继承

C++提供了比修改代码更好的方法来扩展和修改类。

类继承能够从已有的类派生出新类,派生类继承了基类的特征。

通过继承可以完成:

  • 添加功能
  • 添加数据
  • 修改类方法

13.1 简单基类

//程序清单 13.1
#pragma once
#ifndef TABTENN0_H_
#define TABTENN0_H_

#include <iostream>
#include <string>

using namespace std;

//基类
class TableTennisPlayer
{
public:
	TableTennisPlayer(const string& fn = "none", const string& ln = "none", bool ht = false );
	~TableTennisPlayer();
	void Name() const;
	bool HasTable() const { return hasTable; }
	void ResetTable(bool v) { hasTable = v; }

private:
	string firstname;
	string lastname;
	bool hasTable;
};

#endif // !TABTENN0_H_
//程序清单 13.2
#include "tabtenn0.h"

TableTennisPlayer::TableTennisPlayer(const string& fn, const string& ln, bool ht)
	: firstname(fn), lastname(ln), hasTable(ht) {}	//成员初始化列表

/*
TableTennisPlayer::TableTennisPlayer(const string& fn, const string& ln, bool ht)
{
	firstname = fn;
	lastname = ln;
	hasTable = ht;
}
*/

TableTennisPlayer::~TableTennisPlayer()
{
}

void TableTennisPlayer::Name() const
{
	cout << lastname << ", " << firstname;
}
//程序清单 13.3
#include <iostream>
#include "tabtenn0.h"

int main(void)
{
	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;
}

13.1.1 派生一个类

class RatedPlayer : public TableTennisPlayer

          派生类            公有   基类

{

        ...

}

 使用公有派生,基类的公有成员将成为派生类的公有成员;基类的私有部分也成为派生的一部分,只能通过基类的公有和保护方法访问

  • 派生类对象存储了基类的数据成员(派生类继承了基类的实现)
  • 派生类对象可以使用基类的方法(派生类继承了基类的接口)

需要在继承特性中添加什么:

  • 派生类需要自己的构造函数
  • 派生类可以添加额外的数据成员和成员函数

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

  • 派生类不能直接访问基类的私有成员,而必须通过基类方法进行访问
  • 创建派生类对象时,程序首先创建基类对象。意味着基类对象应在程序进入派生类构造函数之前被创建。可以使用成员初始化列表实现。
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()
{
    rating = r;
}
  •  下述代码调用基类复制构造函数
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp)
     : TableTennisPlayer(tp)
{
    rating = r;
}
//或
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp)
     : TableTennisPlayer(tp), rating(r) {}

 派生类构造函数的要点:

  • 创建基类对象
  • 通过成员初始化列表将基类信息传递给基类构造函数
  • 派生类构造函数应初始化派生类新增的数据成员

释放对象的顺序与创建对象的顺序相反,先调用派生类析构,再调用基类析构

 成员初始化列表只能用于构造函数

13.1.3 使用派生类

//程序清单 13.4 tabtenn1.h
#pragma once
#ifndef TABTENN1_H_
#define TABTENN1_H_

#include <iostream>
#include <string>

using namespace std;

//基类
class TableTennisPlayer
{
public:
	TableTennisPlayer(const string& fn = "none", const string& ln = "none", bool ht = false);
	~TableTennisPlayer();
	void Name() const;
	bool HasTable() const { return hasTable; }
	void ResetTable(bool v) { hasTable = v; }

private:
	string firstname;
	string lastname;
	bool hasTable;
};

//派生类
class RatedPlayer : public TableTennisPlayer
{
public:
	RatedPlayer(unsigned int r = 0, const string& fn = "none", 
		const string& ln = "none", bool ht = false);
	RatedPlayer(unsigned int r, const TableTennisPlayer& tp);
	~RatedPlayer();
	unsigned int Rating() { return rating; }
	void resetRating(unsigned int r) { rating = r; }

private:
	unsigned int rating;
};

#endif // !TABTENN1_H_

//程序清单 13.5
#include "tabtenn1.h"

//基类
TableTennisPlayer::TableTennisPlayer(const string& fn, const string& ln, bool ht)
	: firstname(fn), lastname(ln), hasTable(ht) {}	//成员初始化列表

/*
TableTennisPlayer::TableTennisPlayer(const string& fn, const string& ln, bool ht)
{
	firstname = fn;
	lastname = ln;
	hasTable = ht;
}
*/

TableTennisPlayer::~TableTennisPlayer()
{
}

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 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){}

RatedPlayer::~RatedPlayer()
{
}
//程序清单 13.6
#include <iostream>
#include "tabtenn1.h"

int main(void)
{
	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";
	rplayer1.Name();
	cout << "; Rating = " << rplayer1.Rating() << endl;
	player1.Name();		//基类对象使用基类方法
	if (player1.HasTable())
		cout << ": has a table.\n";
	else
		cout << ": hasn't a table.\n";

	//使用基类对象初始化派生类对象
	RatedPlayer rplayer2(1212, player1);
	cout << "Name: ";
	rplayer2.Name();
	cout << "; Rating: " << rplayer2.Rating() << endl;

	return 0;
}

13.1.4 派生类和基类之间的特殊关系

  • 派生类对象可以使用基类的公有方法。
  • 基类指针可以指向派生类对象(单向)
  • 基类引用可以引用派生类对象(单向)
  • 基类指针和引用只能调用基类方法
  • 可以将派生类对象赋给基类对象(单向)

13.2 继承:is-a关系

  • 3种继承方式:公有、保护、私有
  • is-a关系:派生类对象也是一个基类对象,对基类对象执行的任何操作,都可以对派生类对象执行
  • 继承可以在基类基础上添加属性,但不能删除基类属性

13.3 多态公有继承

多态:方法的行为应取决于调用该方法的对象。

实现多态公有继承两种机制:

  • 在派生类中重新定义基类的方法
  • 使用虚方法

13.3.1 开发Brass类和BrassPlus类

//程序清单 13.7
#pragma once
#ifndef BRASS_H_
#define BRASS_H_

#include <iostream>
#include <string>

using namespace std;

//基类
class Brass
{
public:
	Brass(const string& s = "Nullbody", long an = -1, double bal = 0.0);
	virtual ~Brass();
	void Deposit(double amt);
	virtual void Withdraw(double amt);
	double Balance() const { return balance; }
	virtual void ViewAcct() const;

private:
	string fullname;
	long acctNum;
	double balance;
};

//派生类
class BrassPlus : public Brass
{
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 ~BrassPlus(){}
	virtual void ViewAcct() const;
	virtual void Withdraw(double amt);
	void ResetMax(double ml) { maxLoan = ml; }
	void ResatRate(double r) { rate = r; }
	void ResetOwes() { owesBank = 0.0; }

private:
	double maxLoan;
	double rate;
	double owesBank;
};

#endif // !BRASS_H_

 程序将使用对象类型来确定使用哪个版本:

Brass dom("AAA", 11, 111.1);            //基类对象

BrassPlus dot("BBB", 22, 22.2);        //派生类对象

dom.ViewAcct();                                //通过对象dom确定使用基类方法

dot.ViewAcct();                                  //通过对象dot确定使用派生类方法

如果方法是通过引用或指针而不是对象调用:

如果没有使用virtual:

Brass & b1_ref = dom;                       //基类引用指向基类对象

Brass & b2_ref = dot;                         //基类引用指向派生类对象

b1_ref.ViewAcct();                              //根据引用(指针)类型选择基类方法

b2_ref.ViewAcct();                              //根据引用(指针)类型选择基类方法

如果使用virtual:

Brass & b1_ref = dom;                       //基类引用指向基类对象

Brass & b2_ref = dot;                         //基类引用指向派生类对象

b1_ref.ViewAcct();                              //根据引用(指针)指向的类型dom选择基类方法

b2_ref.ViewAcct();                              //根据引用(指针)指向的类型dot选择派生类方法

如果要在派生类中重新定义基类的方法,通常将基类方法声明为虚的。

程序将根据对象类型而不是引用或指针类型选择方法版本。

//程序清单 13.8
#include "brass.h"

//基类
Brass::Brass(const string& s, long an, double bal)
{
	fullname = s;
	acctNum = an;
	balance = bal;
}

Brass::~Brass()
{
}

void Brass::Deposit(double amt)
{
	if (amt < 0)
		cout << "Negative deposit not allowed.\n";
	else
		balance += amt;
}

void Brass::Withdraw(double amt)
{
	if (amt < 0)
		cout << "Withdraw amount must be positive.\n";
	else if (amt > balance)
		cout << "Withdraw amount of $" << amt <<
		" exceeds your balance $" << balance << ".\n";
	else
		balance -= amt;
}

void Brass::ViewAcct() const
{
	cout << "Client:\t\t" << fullname << endl;
	cout << "Account Number:\t" << acctNum << endl;
	cout << "Balance:\t$" << balance << endl;
}

//派生类
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
{
	Brass::ViewAcct();        //调用基类方法
	cout << "Maxium loan:\t$" << maxLoan << endl;
	cout << "Loan Rate:\t$" << rate << endl;
	cout << "Owe to bank:\t$" << owesBank << endl;
}

void BrassPlus::Withdraw(double amt)
{
	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:\t$" << advance << endl;
		cout << "Finance charge:\t$" << advance * rate << endl;
		Deposit(advance);
		Brass::Withdraw(amt);
	}
	else
		cout << "Credit limit exceeded.\n";
}
//程序清单 13.9
//通过对象确定使用哪个类的方法
#include <iostream>
#include "brass.h"

int main(void)
{
	Brass Piggy("Porcelot Pigg", 381299, 4000.00);
	Piggy.ViewAcct();	//通过基类对象调用基类方法
	cout << endl;

	BrassPlus Hoggy("Horatio Hogg", 382288, 3000.0);
	Hoggy.ViewAcct();	//通过派生类对象调用派生类方法
	cout << endl;

	cout << "Depositing $1000 into the Hogg Account:\n";
	Hoggy.Deposit(1000.00);
	cout << "Hoggy new balance:\t$" << Hoggy.Balance() << endl;
	cout << "Withdrawing $4200 from the Pigg Account:\n";
	Piggy.Withdraw(4200.00);
	cout << "Piggy account balance:\t$" << Piggy.Balance() << endl;
	cout << "Withdrawing $4200 from the Hoggy Account:\n";
	Hoggy.Withdraw(4200.00);
	Hoggy.ViewAcct();

	return 0;
}
//程序清单 13.10
//通过引用或指针指向的类型确定使用方法版本
#include <iostream>
#include <string>
#include "brass.h"

const int CLIENTS = 2;

int main(void)
{
	Brass* p_clients[CLIENTS];		//基类指针(引用)可以指向基类对象也可以指向派生类对象
	string temp;
	long tempnum;
	double tempbal;
	int 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);
			//对类Brass使用new,将调用相应构造函数
		else
		{
			double tmax;
			double trate;
			cout << "Enter the overdraft limit: $";
			cin >> tmax;
			cout << "Enter the interest rate: ";
			cin >> trate;
			p_clients[i] = new BrassPlus(temp, tempnum, tempbal, tmax, trate);
		}
		while (cin.get() != '\n')	//消耗回车
			continue;
	}

	for (int i = 0; i < CLIENTS; i++)
	{
		p_clients[i]->ViewAcct();
		//ViewAcct()为虚时,看指针指向基类/派生类对象的类型,选择调用哪种ViewAcct()
		cout << endl;
	}

	for (int i = 0; i < CLIENTS; i++)
		delete p_clients[i];

	cout << "Done.\n";

	return 0;
}

 为何需要虚析构函数:

  • 如果析构不是虚的,将只调用对应于指针类型的析构函数
  • 如果析构是虚的,将调用相应对象类型的析构函数

13.4 静态联编和动态联编

  • 将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编。
  • 编译过程中进行联编称为静态联编(早期联编)
  • 程序运行时选择正确(虚)方法,称为动态联编(晚期联编)

13.4.2 虚成员函数和动态联编

  • 如果类不用作基类,或派生类不重新定义基类方法,则不需要动态联编(此处指虚方法)
  • 使用虚方法,内存增大,执行速度降低

13.4.3 有关虚函数注意事项

虚函数要点:

  • 在基类声明中使用关键字virtual即可,最好派生类也加上,好区分
  • 通过引用或指针调用虚方法,将根据引用或指针指向的对象类型确定使用方法版本
  • 将派生类中重新定义的基类方法声明为虚的
  1. 构造函数不能是虚的,名字不一样,也要避免基类和派生类构造函数重名
  2. 析构应当是虚的,即使类不做基类,虚析构可以确保正确的析构函数被调用
  3. 友元不能是虚函数,因为友元就不是类成员,只有成员才能是虚的。
  4. 重新定义虚函数,但是同名不同参,不会重载,派生类反而覆盖隐藏基类方法。
  • 如果重新定义继承的方法,要确保原型完全相同
  • 返回类型协变:特征标完全相同,返回类型可以不同。(返回类型是基类引用或指针,可以修改为指向派生类的引用或指针)
  • 如果基类声明被重载,则派生类中重新定义所有基类版本

13.5 访问控制:protected

protect和private相似,在类外只能用公有类成员来访问protect部分的类成员

派生类成员可以直接访问基类保护成员,但不能直接访问基类私有成员。

13.6 抽象基类

  • C++通过纯虚函数提供未实现的函数。
  • 纯虚函数声明的结尾为=0。
  • 有纯虚函数的类称为抽象类。
  • 类声明中包含纯虚函数时,不能创建该类的对象。(抽象类不能实例化)
  • 包含纯虚函数的类只用作基类。
  • 纯虚函数可以不在类方法中定义。

13.6.1 应用ABC概念

//程序清单 13.11
#pragma once
#ifndef ACCTABC_H_
#define ACCTABC_H_

#include <iostream>
#include <string>

using namespace std;

//抽象基类
class AcctABC
{
public:
	AcctABC(const string& s = "Nullbody", long an = -1,double bal = 0.0);
	virtual ~AcctABC() {}    //虚析构
	double Balance() const { return balance; }
	void Deposit(double amt);
	//纯虚函数
	virtual void Withdraw(double amt) = 0;
	virtual void ViewAcct() const = 0;

private:
	string fullName;
	long acctNum;
	double balance;

protected:
	const string& FullName() const { return fullName; }
	long AcctNum() const { return acctNum; }
	//间接访问抽象基类私有成员

};

//抽象基类派生
class Brass : public AcctABC
{
public:
	Brass(const string& s = "Nullbody", long an = -1, double bal = 0.0)
		: AcctABC(s, an, bal) {}
	virtual ~Brass(){}
	virtual void Withdraw(double amt);
	virtual void ViewAcct() const;

private:

};

class BrassPlus : public AcctABC
{
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 ~BrassPlus() {}
	virtual void ViewAcct() const;
	virtual void Withdraw(double amt);
	void ResetMax(double ml) { maxLoan = ml; }
	void ResatRate(double r) { rate = r; }
	void ResetOwes() { owesBank = 0.0; }

private:
	double maxLoan;
	double rate;
	double owesBank;
};

#endif // !ACCTABC_H_
//程序清单 13.12
#include "acctabc.h"

//基类
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;
}

//派生类Brass
void Brass::Withdraw(double amt)
{
	if (amt < 0)
		cout << "Withdraw amount must be positive.\n";
	else if (amt <= Balance())
		AcctABC::Withdraw(amt);
	else
		cout << "Withdraw amount of $" << amt <<
		" exceeds your balance $" << Balance() << ".\n";
	//派生类不能直接访问基类私有成员balance
	//只能通过基类成员函数Balance()
}

void Brass::ViewAcct() const
{
	cout << "Brass Client:\t" << FullName() << endl;
	cout << "Account Number:\t" << AcctNum() << endl;
	cout << "Balance:\t$" << Balance() << endl;
}

//派生类BrassPlus
BrassPlus::BrassPlus(const string& s, long an,
	double bal, double ml, double r) : AcctABC(s, an, bal)
{
	maxLoan = ml;
	rate = r;
	owesBank = 0.0;
}

BrassPlus::BrassPlus(const Brass& ba, double ml, double r) : AcctABC(ba)
{
	maxLoan = ml;
	rate = r;
	owesBank = 0.0;
}

void BrassPlus::ViewAcct() const
{
	cout << "BrassPlus Client:" << FullName() << endl;
	cout << "Account Number:\t" << AcctNum() << endl;
	cout << "Balance:\t$" << Balance() << endl;
	cout << "Maxium loan:\t$" << maxLoan << endl;
	cout << "Loan Rate:\t$" << rate << endl;
	cout << "Owe to bank:\t$" << owesBank << endl;
}

void BrassPlus::Withdraw(double amt)
{
	double bal = Balance();
	if (amt < 0)
		cout << "Withdraw amount must be positive.\n";
	else if (amt <= bal)
		AcctABC::Withdraw(amt);
	else if (amt <= bal + maxLoan - owesBank)
	{
		double advance = amt - bal;
		owesBank += advance * (1.0 + rate);
		cout << "Bank Advance:\t$" << advance << endl;
		cout << "Finance charge:\t$" << advance * rate << endl;
		Deposit(advance);
		AcctABC::Withdraw(amt);
	}
	else
		cout << "Credit limit exceeded.\n";
}
//程序清单13.13
#include <iostream>
#include <string>
#include "acctabc.h"

const int CLIENTS = 2;

int main(void)
{
	AcctABC* p_clients[CLIENTS];		//基类指针可以指向基类对象也可以指向派生类对象
	string temp;
	long tempnum;
	double tempbal;
	int 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);
			//对类Brass使用new,将调用相应构造函数
		else
		{
			double tmax;
			double trate;
			cout << "Enter the overdraft limit: $";
			cin >> tmax;
			cout << "Enter the interest rate: ";
			cin >> trate;
			p_clients[i] = new BrassPlus(temp, tempnum, tempbal, tmax, trate);
		}
		while (cin.get() != '\n')	//消耗回车
			continue;
	}

	for (int i = 0; i < CLIENTS; i++)
	{
		p_clients[i]->ViewAcct();
		//ViewAcct()为虚时,看指针指向基类/派生类对象的类型,选择调用哪种ViewAcct()
		cout << endl;
	}

	for (int i = 0; i < CLIENTS; i++)
		delete p_clients[i];

	cout << "Done.\n";

	return 0;
}

13.7 继承和动态内存分配

13.7.1 第一种情况:派生类不使用new

派生类中不用编写析构、复制构造和重载赋值

13.7.2 第二种情况:派生类使用new

必须为派生类定义析构、复制构造和重载赋值

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

//程序清单 13.14 dma.h
#pragma once
#ifndef DMA_H_
#define DMA_H_

#include <iostream>

using namespace std;

class baseDMA
{
public:
	baseDMA(const char* l = "null", int r = 0);
	baseDMA(const baseDMA& rs);               //复制构造
	virtual ~baseDMA();                       //虚析构
	baseDMA& operator=(const baseDMA& rs);    //重载赋值
	friend ostream& operator<<(ostream& os, const baseDMA& rs);

private:
	char* label;
	int rating;
};

class lacksDMA : public baseDMA
{
public:
	lacksDMA(const char* c = "blank", const char* l = "null", int r = 0);
	lacksDMA(const char* c, const baseDMA& rs);
	friend ostream& operator<<(ostream& os, const lacksDMA& ls);

private:
	enum {COL_LEN = 40};    //static const int COL_LEN = 40
	char color[COL_LEN];
};

class hasDMA : public baseDMA
{
public:
	hasDMA(const char* s = "blank", const char* l = "null", int r = 0);
	hasDMA(const char* s, const baseDMA& rs);
	hasDMA(const hasDMA& hs);
	virtual ~hasDMA();
	hasDMA& operator=(const hasDMA& hs);
	friend ostream& operator<<(ostream& os, const hasDMA& hs);

private:
	char* style;
};

#endif // !DMA_H_

//程序清单 13.15
#include "dma.h"
#include <cstring>
#pragma warning(disable:4996)

baseDMA::baseDMA(const char* l, int r)
{
	label = new char[strlen(l) + 1];
	strcpy(label, l);
	rating = r;
}
baseDMA::baseDMA(const baseDMA& rs)
{
	label = new char[strlen(rs.label) + 1];
	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[strlen(rs.label) + 1];
	strcpy(label, rs.label);
	rating = rs.rating;
	return *this;
}
ostream& operator<<(ostream& os, const baseDMA& ls)
{
	os << "Label: " << ls.label << endl;
	os << "Rating: " << ls.rating << endl;
	return os;
}


lacksDMA::lacksDMA(const char* c, const char* l, int r) : baseDMA(l, r)
{
	strncpy(color, c, COL_LEN - 1);
	color[COL_LEN - 1] = '\0';
}
lacksDMA::lacksDMA(const char* c, const baseDMA& rs) : baseDMA(rs)
{
	strncpy(color, c, COL_LEN - 1);
	color[COL_LEN - 1] = '\0';
}
ostream& operator<<(ostream& os, const lacksDMA& ls)
{
	os << (const baseDMA&)ls;        //强制类型转换,调用基类重载<<
	os << "Color: " << ls.color << endl;
	return os;
}


hasDMA::hasDMA(const char* s, const char* l, int r) : baseDMA(l, r)
{
	style = new char[strlen(s) + 1];
	strcpy(style, s);
}
hasDMA::hasDMA(const char* s, const baseDMA& rs) : baseDMA(rs)
{
	style = new char[strlen(s) + 1];
	strcpy(style, s);
}
hasDMA::hasDMA(const hasDMA& hs) : baseDMA(hs)
{
	cout << "初始化调用复制构造函数" << endl;
	style = new char[strlen(hs.style) + 1];
	strcpy(style, hs.style);
}
hasDMA::~hasDMA()
{
	delete[] style;
}
hasDMA& hasDMA::operator=(const hasDMA& hs)
{
	cout << "赋值调用重载=" << endl;
	if (this == &hs)
		return *this;
	baseDMA::operator=(hs);    //函数表示法,指定基类重载
	delete[] style;
	style = new char[strlen(hs.style) + 1];
	strcpy(style, hs.style);
	return *this;
}
ostream& operator<<(ostream& os, const hasDMA& hs)
{
	os << (const baseDMA&)hs;
	os << "Style: " << hs.style << endl;
	return os;
}
//程序清单 13.16
#include <iostream>
#include "dma.h"

int main(void)
{
	baseDMA shirt("AAA", 8);
	lacksDMA ballon("red", "BBB", 4);
	hasDMA map("ccc", "CCC", 5);

	cout << "Display baseDMA object:\n";
	cout << shirt << endl;
	cout << "Display lacksDMA object:\n";
	cout << ballon << endl;
	cout << "Display hasDMA object:\n";
	cout << map << endl;

	cout << "Result of lacksDMA copy:\n";
	lacksDMA ballon2(ballon);    //调用复制构造函数
	cout << ballon2 << endl;
	cout << "Result of hasDMA assignment:\n";
	hasDMA map2;
	map2 = map;    //调用重载赋值=
	cout << map2 << endl;
	hasDMA map3 = map;
	cout << map3 << endl;

	return 0;
}

13.8 类设计回顾

13.8.1 编译器生成的成员函数

  1. 默认构造函数要么没有参数,要么所以参数都有默认值
  2. 复制构造函数:将新对象初始化为同类对象;按值将对象传递给函数;函数按值返回对象;生成临时对象;深度复制
  3. 赋值运算符:修改已有对象的值时。显式定义复制构造函数时;一种类型赋给另一种类型时;使用转换函数

13.8.2 其他的类方法

  1. 构造函数不同于其他类方法,构造函数创建新对象,其他类方法只是被对象调用
  2. 一定要显式定义析构函数来释放new分配的内存,对于基类,析构应为虚析构
  3. 类类型转换为其他类型:转换函数;其他类型转换为类类型:构造函数;使用explicit禁止隐式转换
  4. 按值传递和按引用传递:按值传递,生成临时拷贝,调用复制构造函数;按引用传递,基类引用(指针)可以指向基类/派生类对象
  5. 返回对象和返回引用:返回临时对象时不要返回引用;返回通过引用或指针传递的对象时,按引用返回
  6. 尽量使用const

13.8.3 公有继承的考虑因素

  1. is-a关系:基类指针和引用可以指向派生类指针和引用,反之,在不进行显式类型转换时不可
  2. 什么不能继承:构造函数、析构函数、赋值运算符
  3. 私有成员和保护成员:对派生类,保护=公有,对外部,保护=私有
  4. 虚方法:派生类需要重新定义基类方法
  5. 析构函数:基类析构应该为虚
  6. 友元函数:友元不是类成员,不能继承

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值