C++学习-入门到精通【9】面向对象编程:继承

C++学习-入门到精通【9】面向对象编程:继承


目录


一、基类与派生类

我们先从下表可以很清楚的看出基类和派生类之间的关系

基类 派生类
学生 本科生、研究生
形状 圆、三角形、矩形、球体、立方体
贷款 汽车贷款、住房贷款、抵押贷款
雇员 教职员工、后勤人员
账户 支票账户、储蓄账户

可以看到上图中的基类更加通用,派生类更加具体。

继承关系构成了类的层次结构。基类和它的派生类间存在这种层次关系。虽然类可以独立存在,但是一旦投入到继承关系中,它们和其他的类就有了关联。在继承关系中,类作为基类、或是派生类、或以二者兼有的身份出现。

CommunityMember类的继承层次结构

下面设计一个简单的继承层次结构,这个层次有5层。一个大学群体有数以千计的CommunityMember对象。

这些CommunityMember对象由Employee(雇员)、Student(学生)和Alumnus(校友)对象组成。Employee对象可能Faculty(教职员工)或者Staff(后勤人员),一个Faculty对象可能是Administrator(行政管理人员)或者Teacher(教师)对象。有些Administrator对象同时也可能是Teacher对象。所以这里我们使用多重继承来设计类AdministratorTeacher。多重继承会在后续章节详细介绍,但是并不建议使用多重继承。

下面是先前设计的CommunityMember类的继承层次结构的UML图。

上图中每个箭头表示一个is-a关系(继承)(同时还有has-a关系表示组成)。当我们跟踪这些类层次的箭头时,可以知道Employee对象是CommunityMember对象。

CommunityMemberEmployee、Student和Alumnus的直接基类。是图中其他类的间接基类(在类层次中被继承了两层或以上)。

如何定义一个派生类呢

那么我们应该如何将一个类定义为另一个类的派生(从另一个类继承)呢?以以下形式进行定义,我们可以指定类Employee是由类CommunityMember派生而来的,类Employee定义的开始部分有:

cpp 复制代码
class Employee : public CommunityMember

这是最常用的继承形式------public继承的一个例子。本文后续部分会继续讨论private继承和protected继承。

在各种形式的继承中,基类的private成员都不能被它的派生类直接访问,但是这些private基类成员仍被继承(派生类也包含它们)。在public继承中,基类的所有其他成员在成为派生类的成员时仍保持其原始的成员访问权限(比如,基类的public成员在派生类中仍是public成员,protected成员仍是protected成员)。那么派生类如何访问继承来的private成员呢------使用从基类继承来的成员函数来访问。注意:友元函数无法继承。

二、基类和派生类间的关系

这一节将通过一个雇员的继承层次结构来讨论基类和派生类之间的关系。这个层次结构包含了一个公司的工资发放系统所涉及的多种类型的雇员。佣金雇员(CommisisionEmployee)将被表示为基类的对象,他们的薪水完全由销售提成;带底薪佣金雇员(base-salaried commission employee)将被表示为派生类的对象,他们的薪水上底薪和销售提成组成。

下面我们将用5个例子来循序渐进地讨论佣金雇员和带底薪佣金雇员之间的关系。

1.创建并使用类CommissionEmployee

CommissionEmployee.h

cpp 复制代码
#include <string>

class CommissionEmployee
{
public:
	// 参数分别为firstName、lastName、socialSecurityNumber、grossSales和commnissionRate
	CommissionEmployee(const std::string&, const std::string&,
		const std::string&, double = 0.0, double = 0.0);

	void setFirstName(const std::string&);
	std::string getFirstName() const;

	void setLastName(const std::string&);
	std::string getLastName() const;

	void setSocialSecurityNumber(const std::string&);
	std::string getSocialSecurityNumber() const;

	void setGrossSales(double);
	double getGrossSales() const;

	void setCommissionRate(double);
	double getCommissionRate() const;

	double earnings() const; // 计算工资
	void print() const; // 输出CommissionEmployee的成员
private:
	std::string firstName;
	std::string lastName; 
	std::string socialSecurityNumber; // 社保号码
	double grossSales; // 销售总额
	double commissionRate; // 提成比例
};

CommissionEmployee.cpp

cpp 复制代码
#include <iostream>
#include <string>
#include <stdexcept>
#include "CommissionEmployee.h"

using namespace std;

CommissionEmployee::CommissionEmployee(const string& first, const string& last,
	const string& ssn, double sales, double rate)
	: firstName(first), lastName(last),
	socialSecurityNumber(ssn), grossSales(sales), commissionRate(rate)
{
	// 也可以不使用初始化列表进行初始化,在构造函数体中调用set成员函数来进行初始化
	// 这样可以对这些值的有效性进行检查
}

void CommissionEmployee::setFirstName(const string& first)
{
	// 还可以增加对名的有效性的限制
	firstName = first; 
}

string CommissionEmployee::getFirstName() const
{
	return firstName;
}

void CommissionEmployee::setLastName(const string& last)
{
	// 还可以增加对姓的有效性的限制
	lastName = last;
}

string CommissionEmployee::getLastName() const
{
	return lastName;
}

void CommissionEmployee::setSocialSecurityNumber(const string& ssn)
{
	// 还可以增加对姓的有效性的限制
	socialSecurityNumber = ssn;
}

string CommissionEmployee::getSocialSecurityNumber() const
{
	return socialSecurityNumber;
}

void CommissionEmployee::setGrossSales(double sales)
{
	if (sales >= 0.0)
	{
		grossSales = sales;
	}
	else
	{
		throw invalid_argument("Gross Sales must be greater than 0.0.");
	}
}

double CommissionEmployee::getGrossSales() const 
{
	return grossSales;
}

void CommissionEmployee::setCommissionRate(double rate)
{
	if (rate > 0.0 && rate < 1.0)
	{
		commissionRate = rate;
	}
	else
	{
		throw invalid_argument("Commission rate must be greater than 0.0 and smaller than 1.0.");
	}
}

double CommissionEmployee::getCommissionRate() const
{
	return commissionRate;
}

double CommissionEmployee::earnings() const
{
	return commissionRate * grossSales;
}

void CommissionEmployee::print() const
{
	cout << "commission employee: " << firstName << ' ' << lastName
		<< "\nsocial security number: " << socialSecurityNumber
		<< "\ngrossSales: " << grossSales
		<< "\ncommission rate: " << commissionRate;
}

test.cpp

cpp 复制代码
#include <iostream>
#include <iomanip>
#include "CommissionEmployee.h"
using namespace std;

int main()
{
	// 实例化一个CommissionEmployee对象
	CommissionEmployee employee(
		"Peter", "Griffin", "111-22-3333", 10000, .06
	);

	// 设置浮点数格式,固定保留小数点后两位
	cout << fixed << setprecision(2);

	// 调用get成员函数获取employee的信息
	cout << "Employee information obtained by get functions: \n"
		<< "\nFirst name is " << employee.getFirstName()
		<< "\nLast name is " << employee.getLastName()
		<< "\nSocial security number is " << employee.getSocialSecurityNumber()
		<< "\nGross sales is " << employee.getGrossSales()
		<< "\nCommission rate is " << employee.getCommissionRate() << endl;

	// 设置employee的总销售额和提成比例
	employee.setGrossSales(10000);
	employee.setCommissionRate(.1);

	cout << "\nUpdated employee information output by print function: \n\n";
	employee.print();

	// 打印employee的收入
	cout << "\n\nEmployee`s earings is $" << employee.earnings() << endl;
}

运行结果:

2.不使用继承创建类BasePlusCommissionEmployee

下面创建一个全新的类,比上面的类多了一个数据成员:baseSalary(底薪)。

BasePlusCommissionEmployee.h

cpp 复制代码
#include <string>

class BasePlusCommissionEmployee
{
public:
	// 参数分别为firstName、lastName、socialSecurityNumber、grossSales、commnissionRate和baseSalary
	BasePlusCommissionEmployee(const std::string&, const std::string&,
		const std::string&, double = 0.0, double = 0.0, double = 0.0);

	void setFirstName(const std::string&);
	std::string getFirstName() const;

	void setLastName(const std::string&);
	std::string getLastName() const;

	void setSocialSecurityNumber(const std::string&);
	std::string getSocialSecurityNumber() const;

	void setGrossSales(double);
	double getGrossSales() const;

	void setCommissionRate(double);
	double getCommissionRate() const;

	void setBaseSalary(double);
	double getBaseSalary() const;

	double earnings() const; // 计算工资
	void print() const; // 输出CommissionEmployee的成员
private:
	std::string firstName;
	std::string lastName;
	std::string socialSecurityNumber; // 社保号码
	double grossSales; // 销售总额
	double commissionRate; // 提成比例
	double baseSalary; // 底薪
};

BasePlusCommissionEmployee.cpp

cpp 复制代码
#include <iostream>
#include <string>
#include <stdexcept>
#include "BasePlusCommissionEmployee.h"

using namespace std;

BasePlusCommissionEmployee::BasePlusCommissionEmployee(const string& first, const string& last,
	const string& ssn, double sales, double rate, double base)
	: firstName(first), lastName(last),
	socialSecurityNumber(ssn), grossSales(sales), commissionRate(rate),
	baseSalary(base)
{
	// 也可以不使用初始化列表进行初始化,在构造函数体中调用set成员函数来进行初始化
	// 这样可以对这些值的有效性进行检查
}

void BasePlusCommissionEmployee::setFirstName(const string& first)
{
	// 还可以增加对名的有效性的限制
	firstName = first;
}

string BasePlusCommissionEmployee::getFirstName() const
{
	return firstName;
}

void BasePlusCommissionEmployee::setLastName(const string& last)
{
	// 还可以增加对姓的有效性的限制
	lastName = last;
}

string BasePlusCommissionEmployee::getLastName() const
{
	return lastName;
}

void BasePlusCommissionEmployee::setSocialSecurityNumber(const string& ssn)
{
	// 还可以增加对姓的有效性的限制
	socialSecurityNumber = ssn;
}

string BasePlusCommissionEmployee::getSocialSecurityNumber() const
{
	return socialSecurityNumber;
}

void BasePlusCommissionEmployee::setGrossSales(double sales)
{
	if (sales >= 0.0)
	{
		grossSales = sales;
	}
	else
	{
		throw invalid_argument("Gross Sales must be greater than 0.0.");
	}
}

double BasePlusCommissionEmployee::getGrossSales() const
{
	return grossSales;
}

void BasePlusCommissionEmployee::setCommissionRate(double rate)
{
	if (rate > 0.0 && rate < 1.0)
	{
		commissionRate = rate;
	}
	else
	{
		throw invalid_argument("Commission rate must be greater than 0.0 and smaller than 1.0.");
	}
}

double BasePlusCommissionEmployee::getCommissionRate() const
{
	return commissionRate;
}

void BasePlusCommissionEmployee::setBaseSalary(double base)
{
	if (base > 0.0)
	{
		baseSalary = base;
	}
	else
	{
		throw invalid_argument("base must be greater than 0.0.");
	}
}

double BasePlusCommissionEmployee::getBaseSalary() const
{
	return baseSalary;
}

double BasePlusCommissionEmployee::earnings() const
{
	return commissionRate * grossSales + baseSalary;
}

void BasePlusCommissionEmployee::print() const
{
	cout << "commission employee: " << firstName << ' ' << lastName
		<< "\nsocial security number: " << socialSecurityNumber
		<< "\ngrossSales: " << grossSales
		<< "\ncommission rate: " << commissionRate
		<< "\nbase salary: " << baseSalary;
}

对比类BasePlusCommissionEmployee和类CommissionEmployee,我们能够贬责这个新类的定义中只有大部分都是重复代码,只有涉及到新加入的数据成员的地方才有一些变化。

使用继承时,类层次结构中所有类共同的数据成员和成员函数应该在基类中声明。当需要对这些共同特征进行修改时,只需在基类中进行修改即可,派生类会继承这些修改。如果不使用继承机制,而是采用上面的方法,当这些共同部分出错时,我们就需要对所有包含有问题代码副本的源文件进行修改。

3.创建CommissionEmployee-BasePlusCommissionEmployee继承层次结构

现在我们采用继承的方式来创建BasePlusCommissionEmployee类。它由CommissionEmployee派生而来。一个BasePlusCommissionEmployee类的对象同时也是一个CommissionEmployee类的对象。但是BasePlusCommissionEmployee中除了具有所有CommissionEmployee类对象具有的成员之外,还有一个特有的数据成员baseSalary。

使用public继承,继承了基类中除构造函数、析构函数和重载的赋值运算符函数(如果有的话)之外的所有成员,每个类都会提供特定于自己的构造函数和析构函数。

BasePlusCommissionEmployee.h

cpp 复制代码
#include "CommissionEmployee.h"

class BasePlusCommissionEmployee : public CommissionEmployee
{
public:
	BasePlusCommissionEmployee(const std::string&, const std::string&,
			const std::string&, double = 0.0, double = 0.0, double = 0.0);

	void setBaseSalary(double);
	double getBaseSalary() const;
	
	// 收入发生变化,所以earnings成员函数需要重写
	double earnings() const;
	// 同样的打印函数一样需要重写
	void print() const;
private:
	double baseSalary;
};

BasePlusCommissionEmployee.cpp

cpp 复制代码
#include <iostream>
#include <stdexcept>
#include "BasePlusCommissionEmployee.h"
using namespace std;

BasePlusCommissionEmployee::BasePlusCommissionEmployee(
	const string & first, const string& last, const string& ssn,
	double sales, double rate, double salary)
	: CommissionEmployee(first, last, ssn, sales, rate)
{
	setBaseSalary(salary);
}

void BasePlusCommissionEmployee::setBaseSalary(double salary)
{
	if (salary >= 0.0)
	{
		baseSalary = salary;
	}
	else
	{
		throw invalid_argument("Base salary must be greater than 0.0.");
	}
}

double BasePlusCommissionEmployee::getBaseSalary() const
{
	return baseSalary;
}

double BasePlusCommissionEmployee::earnings() const
{
	return baseSalary + commissionRate * grossSales;
}

void BasePlusCommissionEmployee::print() const
{
	cout << "commission employee: " << firstName << ' ' << lastName
		<< "\nsocial security number: " << socialSecurityNumber
		<< "\ngrossSales: " << grossSales
		<< "\ncommission rate: " << commissionRate
		<< "\nbase salary: " << baseSalary;
}

编译结果:

上面的构造函数中引入了基类初始化器语法,使用成员初始化器将参数传递给基类的构造函数。C++要求派生类调用其基类的构造函数来初始化继承到派生类的基类的数据成员。为此在该类的构造函数函数中在它的成员初始化器中将显式的调用了基类的构造函数。如果类BasePlusCommissionEmployee的构造函数没有显式地调用基类的构造函数,C++将会尝试隐式调用基类的默认构造函数,但是该类并没有提供默认的构造函数,且显式的提供了构造函数,所以编译器也不会提供一个无参的默认构造函数,那么就会导致错误。

派生类的构造函数只能通过成员初始化器来初始化基类中的数据成员

常见错误:在派生类构造函数调用其基类的构造函数时,如果传递给基类构造函数的参数,它的个数和类型与基类构造函数不符,将导致编译错误。

注意:大家肯定发现了在派生类中我们又重新定义了earnings和print函数,因为基类中的这两个函数并没有声明为虚函数,所以这里是进行了函数隐藏,派生类中新定义的函数隐藏了基类中的相同函数原型的函数,派生类对象直接调用该函数会调用派生类中定义的函数,如果想要调用基类中的同名函数需要显式的指定作用域,例如:Commission::earnings()

除了上面的初始化问题外,可以看到上面的类在编译是出现了错误,无法访问基类的私有成员。那么我们在派生类中要如何访问基类的私有数据成员呢?公有的成员函数(基类中的私有成员函数同样的也不能调用)。还有没有其他方法呢?有的,兄弟有的。使用protected成员访问访问说明符。

4.使用protected数据的CommissionEmployee-BasePlusCommissionEmployee继承层次结构

之前我们已经介绍了访问说明符publicprivate。基类的public成员在该类的体内以及程序中任何有该类或其派生类对象的句柄(即名字、引用或指针)的地方,都是可以访问的。而基类的private成员只能在该类的体内被基类的成员和友元访问。

下面我们来介绍protected说明符,它在publicprivate之间之间提供了一级折中的保护。为了使类BasePlusCommissionEmployee能够直接访问类CommissionEmployee的数据成员,可以在基类中将它们声明为protected。基类中的protected成员即可以在基类的体内被基类的成员和友元访问,又可以被由基类派生的任何类的成员或友元访问。

使用protected数据定义基类CommissionEmployee

将CommissionEmployee.h进行如下的修改

cpp 复制代码
#include <string>

class CommissionEmployee
{
public:
	// 参数分别为firstName、lastName、socialSecurityNumber、grossSales和commnissionRate
	CommissionEmployee(const std::string&, const std::string&,
		const std::string&, double = 0.0, double = 0.0);

	void setFirstName(const std::string&);
	std::string getFirstName() const;

	void setLastName(const std::string&);
	std::string getLastName() const;

	void setSocialSecurityNumber(const std::string&);
	std::string getSocialSecurityNumber() const;

	void setGrossSales(double);
	double getGrossSales() const;

	void setCommissionRate(double);
	double getCommissionRate() const;

	double earnings() const; // 计算工资
	void print() const; // 输出CommissionEmployee的成员
protected:
	std::string firstName;
	std::string lastName; 
	std::string socialSecurityNumber; // 社保号码
	double grossSales; // 销售总额
	double commissionRate; // 提成比例
};

测试修改后的BasePlusCommissionEmployee类

test.cpp

cpp 复制代码
#include <iostream>
#include <iomanip>
#include "BasePlusCommissionEmployee.h"
using namespace std;

int main()
{
	// 实例化一个BasePlusCommissionEmployee对象
	BasePlusCommissionEmployee employee(
		"Chris", "Griffin", "333-22-1111", 8000, .06, 300
	);

	// 设置浮点数格式,固定保留小数点后两位
	cout << fixed << setprecision(2);

	// 调用get成员函数获取employee的信息
	cout << "Employee information obtained by get functions: \n"
		<< "\nFirst name is " << employee.getFirstName()
		<< "\nLast name is " << employee.getLastName()
		<< "\nSocial security number is " << employee.getSocialSecurityNumber()
		<< "\nGross sales is " << employee.getGrossSales()
		<< "\nCommission rate is " << employee.getCommissionRate() 
		<< "\nBase salary is " << employee.getBaseSalary() << endl;

	// 设置employee的总销售额和提成比例
	employee.setGrossSales(1000);
	employee.setCommissionRate(.08);

	cout << "\nUpdated employee information output by print function: \n\n";
	employee.print();

	// 打印employee的收入
	cout << "\n\nEmployee`s earings is $" << employee.earnings() << endl;
}

运行结果:

使用protected数据的一些注意事项

因为派生类可以直接访问基类中声明为protected的数据成员,所以派生类可以免去调用函数来设置或获取这些数据成员的额外开销。所以继承protected数据成员会使程序的性能稍稍提升。

但是,使用protected数据将会产生以下两个问题:

  • 派生类对象不必使用成员函数设置protected数据成员的值,因此,派生类很容易将无效的值赋给基类的protected数据,导致对象处于不一致的状态。例如,为基类的数据成员grossSales赋一个负值。
  • 派生类成员函数很可能太依赖基类的实现,派生类应该只依赖于基类提供的服务(非private的成员函数),而不是基类的实现。在派生类中使用基类的protected数据时,如果修改了基类的实现,那么很有可能还需要修改所有直接使用了protected数据成员的派生类,这极大的降低了代码的可维护性。

5.使用private数据的CommissionEmployee-BasePlusCommissionEmployee继承层次结构

下面对之前使用的代码进行优化:

将基类CommissionEmployee中的数据成员改回private的数据成员。

CommissionEmployee.cpp,修改了构造函数、earnings和print函数的实现

cpp 复制代码
#include <iostream>
#include <string>
#include <stdexcept>
#include "CommissionEmployee.h"

using namespace std;

CommissionEmployee::CommissionEmployee(const string& first, const string& last,
	const string& ssn, double sales, double rate)
	: firstName(first), lastName(last),
	socialSecurityNumber(ssn)
{
	// 调用成员函数来设置销售总额和提成比例,在设置之前进行有效性检查
	setGrossSales(sales);
	setCommissionRate(rate);
}

void CommissionEmployee::setFirstName(const string& first)
{
	// 还可以增加对名的有效性的限制
	firstName = first; 
}

string CommissionEmployee::getFirstName() const
{
	return firstName;
}

void CommissionEmployee::setLastName(const string& last)
{
	// 还可以增加对姓的有效性的限制
	lastName = last;
}

string CommissionEmployee::getLastName() const
{
	return lastName;
}

void CommissionEmployee::setSocialSecurityNumber(const string& ssn)
{
	// 还可以增加对姓的有效性的限制
	socialSecurityNumber = ssn;
}

string CommissionEmployee::getSocialSecurityNumber() const
{
	return socialSecurityNumber;
}

void CommissionEmployee::setGrossSales(double sales)
{
	if (sales >= 0.0)
	{
		grossSales = sales;
	}
	else
	{
		throw invalid_argument("Gross Sales must be greater than 0.0.");
	}
}

double CommissionEmployee::getGrossSales() const 
{
	return grossSales;
}

void CommissionEmployee::setCommissionRate(double rate)
{
	if (rate > 0.0 && rate < 1.0)
	{
		commissionRate = rate;
	}
	else
	{
		throw invalid_argument("Commission rate must be greater than 0.0 and smaller than 1.0.");
	}
}

double CommissionEmployee::getCommissionRate() const
{
	return commissionRate;
}

double CommissionEmployee::earnings() const
{
	// 调用get成员函数来获取数据成员
	// 当修改这些数据成员的名字时,不需要修改该函数的实现
	return getCommissionRate() * getGrossSales();
}

void CommissionEmployee::print() const
{
	// 同样的调用成员函数来访问数据成员
	cout << "commission employee: " << getFirstName() << ' ' << getLastName()
		<< "\nsocial security number: " << getSocialSecurityNumber()
		<< "\ngrossSales: " << getGrossSales()
		<< "\ncommission rate: " << getCommissionRate();
}

BasePlusCommissionEmployee.cpp

cpp 复制代码
#include <iostream>
#include <stdexcept>
#include "BasePlusCommissionEmployee.h"
using namespace std;

BasePlusCommissionEmployee::BasePlusCommissionEmployee(
	const string & first, const string& last, const string& ssn,
	double sales, double rate, double salary)
	: CommissionEmployee(first, last, ssn, sales, rate)
{
	setBaseSalary(salary);
}

void BasePlusCommissionEmployee::setBaseSalary(double salary)
{
	if (salary >= 0.0)
	{
		baseSalary = salary;
	}
	else
	{
		throw invalid_argument("Base salary must be greater than 0.0.");
	}
}

double BasePlusCommissionEmployee::getBaseSalary() const
{
	return baseSalary;
}

double BasePlusCommissionEmployee::earnings() const
{
	// 使用作用域分辨运算符指定基类的作用域来调用在派生类中被隐藏的基类的earnings函数
	return getBaseSalary() + CommissionEmployee::earnings();
}

void BasePlusCommissionEmployee::print() const
{
	cout << "base-salaried ";
	CommissionEmployee::print();
	cout << "\nbase salary: " << getBaseSalary();
}

提示

利用成员函数访问数据成员的值可能比直接访问这些数据稍慢些。但是,如今优化的编译器经过精心设置,可以隐式地执行许多优化工作(例如,把设置和获取成员函数的调用进行内联)。所以,程序员应该致力于编写出符合软件工程原则的代码,而将优化问题留给编译器去做。一条好的准则是:不要怀疑编译器

可以使用上面相同的测试代码来进行测试,看看是否输出相同的结果。

三、派生类中的构造函数和析构函数

从上面的例子中我们可以看出,当我们要实例化一个派生类的对象时,会调用一连串的构造函数。其中派生类的构造函数在执行它自己的任务之前,先显式地(通过成员初始化器)或隐式地(调用基类的默认构造函数)调用其直接基类的构造函数。同样如果该基类也是从其他类派生而来的,则该基类构造函数需要它的基类的构造函数。在这个构造函数调用链中,调用的最后一个构造函数是在继承层次结构中最顶层的构造函数。

例如,在我们前面研究的CommissionEmployee-BasePlusCommissionEmployee继承层次结构中,当程序要创建类BasePlusComissionEmployee的对象时,将调用类CommissionEmployee的构造函数。因为类CommissionEmployee是这个继承层次结构中的基类,所以它的构造函数先执行,所初始化的CommissionEmployee的private数据成员同时也是类BasePlusCommissionEmployee对象的一部分。当类CommissionEmployee的构造函数执行完毕时,它将程序控制权还给类BasePlusCommissionEmployee的构造函数,后者再初始化该类对象的数据成员baseSalary。
派生类的对象在创建时,最先执行的是继承层次结构顶层的类的构造函数。

当销毁派生类的对象时,程序将调用对象的析构函数。这又将展开一连串的析构函数的调用,其中派生类的析构函数,派生类的直接基类、派生类的间接基类及类的成员的析构函数,会按照它们的构造函数执行次序相反顺序依次执行。当调用派生类对象的析构函数时,该析构函数执行其任务,然后调用继承层次结构中上一层基类的析构函数。重复进行这一过程,直到继承结构顶层的最后一个基类的析构函数被调用。之后,该对象就从内存中删除了。

构造函数和析构函数的执行顺序

假设我们创建一个派生类对象,这个派生类及其基类中都包含(通过组成)其他类的对象。当这个派生类的对象被创建时,首先执行的是基类成员对象的构造函数,然后执行基类构造函数的函数体,接着执行派生类成员对象的构造函数,最后执行派生类构造函数的函数体。派生类对象析构函数函数的调用与相应的构造函数的调用顺序正好相反。

C++11:继承基类的构造函数

C++11中,派生类可以继承基类的构造函数。只要在派生类中定义中包含下面的声明:
using BaseClass::BaseClass;

上述声明中的BaseClass表示基类的名称。除了下列的少数例外情况外,对于基类的每个构造函数,编译器都生成一个派生类构造函数,它调用相应的基类构造函数。生成的构造函数对派生类新增的数据成员只执行默认的初始化。在继承构造函数时:

  • 默认情况下,每个继承而来的构造函数和它相应基类构造函数具有相同的访问级别(public、protected和private);
  • 缺省构造函数、拷贝构造函数和移动构造函数不被继承;
  • 如果在基类构造函数的原型中放置 = delete而在基类中删除这个构造函数,那么在派生类中相应的构造函数也被删除;
  • 如果派生类没有显式地定义构造函数,那么编译器在派生类生成一个默认构造函数,即使它从基类继承了其他构造函数;
  • 如果显式地定义在派生类的构造函数和基类的构造函数具有相同的形参列表,那么该基类的构造函数不被继承;
  • 基类构造函数的默认实参是不被继承的,编译器会在派生类中生成重载的构造函数。例如,如果基类声明了如下的构造函数:
    BaseClass(int = 0, double = 0.0);
    那么编译器生成如下的两个没有默认实参的派生类构造函数:
    DerivedClass(int);
    DerivedClass(int, double);
    这两个构造函数都调用这个指定默认实参的BaseClass构造函数。(不指定参数时,调用派生类DerivedClass的默认构造函数,如果没有显式定义,编译器自动生成,调用基类的默认构造函数进行初始化,所以执行起来效果相同。)

四、public、protected和private继承

从基类派生出一个类时,继承基类的方式有三种,即public继承、protected继承和private继承。下图总结了每种继承方式下,在派生类中对基类成员的可访问性。

当采用public继承派生一个类时,基类的public成员成为派生类的public成员,基类的protected成员成为派生类中的protected成员,基类的private成员对派生类隐藏;

当采用protected继承派生一个类时,基类的public和protected成员都变成派生类的protected成员,基类的private成员对派生类隐藏;

当采用private继承派生一个类时,基类的public和protected成员都变成派生类中的private成员,基类的private成员对派生类隐藏;

派生类永远不能直接访问基类的private成员,继承会将保护级别低于它的成员升级到保护级别相同(protected继承会将public的成员升级成protected,private继承是将成员升级成private,它仍在派生类中可以被访问,所以可以被友元函数访问。而private成员不论什么继承方式,都是被隐藏,无法通过友元函数访问。)

private和protected继承不满足 "is-a" 的关系
"is-a"关系的核心在派生类对象可以被当作基类对象使用,如果继承方式不是public,那么派生类无法替代基类,因为基类的接口在派生类中可能不可访问(工具函数等)。

相关推荐
老神在在0011 小时前
javaEE1
java·开发语言·学习·java-ee
魔道不误砍柴功1 小时前
《接口和抽象类到底怎么选?设计原则与经典误区解析》
java·开发语言
small_white_robot2 小时前
Tomcat- AJP协议文件读取/命令执行漏洞(幽灵猫复现)详细步骤
java·linux·网络·安全·web安全·网络安全·tomcat
我是李武涯2 小时前
C++ 条件变量虚假唤醒问题的解决
开发语言·c++·算法
Always_away2 小时前
26考研|高等代数:λ-矩阵
笔记·学习·线性代数·矩阵
DevangLic2 小时前
ffmpeg baidu
人工智能·pytorch·python·学习·ai·ffmpeg
图梓灵2 小时前
Maven与Spring核心技术解析:构建管理、依赖注入与应用实践
java·笔记·spring·maven
pop_xiaoli2 小时前
OC—UI学习-1
学习·ui·objective-c
职业考试资料墙3 小时前
登高架设作业考试中常见的安全规范考点是什么?
学习·考试·题库·考证