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

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


目录

  • C++学习-入门到精通【9】面向对象编程:继承
    • 一、基类与派生类
      • CommunityMember类的继承层次结构
      • 如何定义一个派生类呢
    • 二、基类和派生类间的关系
      • 1.创建并使用类CommissionEmployee
      • 2.不使用继承创建类BasePlusCommissionEmployee
      • 3.创建CommissionEmployee-BasePlusCommissionEmployee继承层次结构
      • 4.使用protected数据的CommissionEmployee-BasePlusCommissionEmployee继承层次结构
      • 5.使用private数据的CommissionEmployee-BasePlusCommissionEmployee继承层次结构
    • 三、派生类中的构造函数和析构函数
      • C++11:继承基类的构造函数
    • 四、public、protected和private继承


一、基类与派生类

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

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

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

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

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定义的开始部分有:

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

#include <string>class CommissionEmployee
{
public:// 参数分别为firstName、lastName、socialSecurityNumber、grossSales和commnissionRateCommissionEmployee(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

#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

#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

#include <string>class BasePlusCommissionEmployee
{
public:// 参数分别为firstName、lastName、socialSecurityNumber、grossSales、commnissionRate和baseSalaryBasePlusCommissionEmployee(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

#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

#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

#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进行如下的修改

#include <string>class CommissionEmployee
{
public:// 参数分别为firstName、lastName、socialSecurityNumber、grossSales和commnissionRateCommissionEmployee(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

#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函数的实现

#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

#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,那么派生类无法替代基类,因为基类的接口在派生类中可能不可访问(工具函数等)。

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

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

相关文章

黑马k8s(十七)

一&#xff1a;高级存储 1.高级存储-pv和pvc介绍 2.高级存储-pv 3.高级存储-pvc 最后一个改成5gi pvc3是没有来绑定成功的 pv3没有绑定 删除pod、和pvc&#xff0c;观察状态&#xff1a; 4.高级存储-pc和pvc的生命周期 二&#xff1a;配置存储 1.配置存储-ConfigMap 2.配…

cf每日刷题c++

目录 Simple Repetition&#xff08;1000&#xff09; Fashionable Array&#xff08;800&#xff09; Kevin and Arithmetic(800) Permutation Warm-Up(800) Game of Mathletes(900) LRC and VIP(800) Simple Repetition&#xff08;1000&#xff09; https://codeforc…

历年中国科学技术大学计算机保研上机真题

2025中国科学技术大学计算机保研上机真题 2024中国科学技术大学计算机保研上机真题 2023中国科学技术大学计算机保研上机真题 在线测评链接&#xff1a;https://pgcode.cn/school?classification1 拆分数字 题目描述 给定一个数字&#xff0c;拆分成若干个数字之和&#xff…

PHP学习笔记(十一)

类常量 可以把在类中始终保持不变的值定义为常量&#xff0c;类常量的默认可见性是public。 接口中也可以定义常量。 可以用一个变量来动态调用类&#xff0c;但该变量的值不能为关键字 需要注意的是类常量只为每个类分配一次&#xff0c;而不是为每个类的实例分配。 特殊的…

Nginx 性能优化全解析:从进程到安全的深度实践

一、进程优化&#xff1a;释放硬件性能潜力 Nginx 通过多工作进程处理请求&#xff0c;合理配置进程参数能充分利用 CPU 资源&#xff0c;避免资源浪费。 1.1 worker_processes 参数详解 worker_processes用于设置 Nginx 工作进程的数量&#xff0c;它直接影响 Nginx 对 CP…

中国移动咪咕助力第五届全国人工智能大赛“AI+数智创新”专项赛道开展

第五届全国人工智能大赛由鹏城实验室主办&#xff0c;新一代人工智能产业技术创新战略联盟承办&#xff0c;华为、中国移动、鹏城实验室科教基金会等单位协办&#xff0c;广东省人工智能与机器人学会支持。 大赛发布“AI图像编码”、“AI增强视频质量评价”、“AI数智创新”三大…

《 PyTorch 2.3革新:torch.compile自动生成CUDA优化内核全解》

CUDA作为NVIDIA推出的并行计算平台和编程模型&#xff0c;为GPU计算提供了强大的支持&#xff0c;但手动优化CUDA代码不仅需要深厚的专业知识&#xff0c;而且过程繁琐、耗时费力&#xff0c;torch.compile的出现&#xff0c;犹如一道曙光&#xff0c;为解决这一困境带来了全新…

mysql-mysql源码本地调试

前言 先进行mysql源码本地编译&#xff1a;mysql源码本地编译 1.本地调试 这里以macbook为例 1.使用vscode打开mysql源码 2.创建basedir目录、数据目录、配置文件目录、配置文件 cd /Users/test/ mkdir mysqldir //创建数据目录和配置目录 cd mysqldir mkdir conf data …

带你手写React中的useReducer函数。(底层实现)

文章目录 前言一、为什么需要 Reducer&#xff1f;二、Reducer 的核心概念1. Reducer 函数2. useReducer 钩子 三&#xff0c;手写react中的useReducer 总结 前言 在 React 开发中&#xff0c;useReducer 是管理复杂状态逻辑的利器。它类似于 Redux 的简化版&#xff0c;允许我…

用wireshark抓了个TCP通讯的包

昨儿个整理了下怎么用wireshark抓包&#xff0c;链接在这里&#xff1a;捋捋wireshark 今天打算抓个TCP通讯的包试试&#xff0c;整体来说比较有收获&#xff0c;给大家汇报一下。 首先就是如何搞到可以用来演示TCP通讯的客户端、服务端&#xff0c;问了下deepseek&#xff0c;…

运维 pgsql 安装完后某次启动不了

pgsql 安装完后某次启动不了 错误 data directory "/usr/local/postgresql/data" has invalid permissions 安装成功后一直可以 后面同事敲了 chmod -R 777 /usr/local 导致不行 改到了 /usr/local/postgresql/data 权限 /usr/local/postgresql/data的权限有限…

查看·电脑安装·的 .NET 版本

方法 一&#xff1a;使用命令提示符或 PowerShell 打开命令提示符或 PowerShell。 输入以下命令&#xff1a;dotnet --version 按下回车键。 命令输出将显示已安装的 .NET 版本。 方法二&#xff1a;使用 .NET Framework 控制面板 打开控制面板。 点击“程序”。 点击“程序…

Linux WiFi 模组使用及故障排查整理文档

Linux WiFi 模组使用及故障排查整理文档 1. STA 模式下 WiFi 延时不稳定问题解决方法&#xff1a; 2. Power Saving 机制说明3. AP 模式下 WiFi 设置4. RTL8821CS AP 模式下 Windows 客户端异常断开问题问题描述问题原因解决方案步骤 1&#xff1a;修改 dnsmasq 配置步骤 2&…

mac mini m4命令行管理员密码设置

附上系统版本图 初次使用命令行管理员&#xff0c;让输入密码&#xff0c;无论是输入登录密码还是账号密码&#xff0c;都是错的&#xff0c;百思不得其解&#xff0c;去网上搜说就是登录密码啊 直到后来看到了苹果官方的文档 https://support.apple.com/zh-cn/102367 https…

栈内行为分析

栈内行为分析 一、源码分析 我们以以下简单的 C 程序为例&#xff0c;通过 GDB 动态调试分析函数调用过程中的栈内布局变化&#xff1a; #include <stdio.h> int add(){int a 10;int b 20;return (a b); }int main() {add();return 0; }编译为 32 位程序&#xff1a…

老旧设备数据采集破局 AI图像解析如何让质检LIMS系统焕发新生

在实验室数字化进程中&#xff0c;大量服役超过 10 年的老旧设备成为数据采集的 “拦路虎”&#xff1a;指针式仪表盘需人工读取、纸质原始记录靠手工录入、非标准接口设备数据无法自动获取…… 某化工实验室因 15 台老旧设备数据采集耗时占比达 40%&#xff0c;检测效率长期滞…

【征求意见】四川省大数据发展研究会关于对《数据资源建设费用测算标准》团体标准征求意见的通知

四川省大数据发展研究会 关于对《数据资源建设费用测算标准》团体标准征求意见的通知 各有关单位&#xff1a; 由四川省大数据发展研究会归口、成都东契奇科技有限公司牵头编制的《数据资源建设费用测算标准》团体标准已形成征求意见稿&#xff0c;现公开征求意见。请于2025年…

element上传文件多选 实现文件排序

上传文件多选排序 只上代码 不多逼逼 这是el-elment 的文件上传 <el-uploadaction"#"list-type"picture-card"ref"upload":accept"accept":on-change"onUploadChange":file-list"fileList":http-request&quo…

.NET 查找 DLL 的路径顺序

在 C# 中&#xff0c;[DllImport("SgCamWrapper.dll")] 这行代码表明它会在运行时从当前可执行文件的搜索路径中查找 SgCamWrapper.dll。具体搜索顺序如下&#xff08;按优先级&#xff09;&#xff1a; ✅ .NET 查找 DLL 的路径顺序&#xff1a; 应用程序启动目录&a…

低代码——表单生成器以form-generator为例

主要执行流程说明&#xff1a; 初始化阶段 &#xff1a; 接收表单配置对象formConf深拷贝配置&#xff0c;初始化表单数据和验证规则处理每个表单组件的默认值和特殊配置&#xff08;如文件上传&#xff09; 渲染阶段 &#xff1a; 通过render函数创建el-form根组件递归渲染表…