10_1、C++继承与派生:声明与继承关系

2024-06-07 15:52
文章标签 c++ 关系 声明 继承 派生

本文主要是介绍10_1、C++继承与派生:声明与继承关系,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

声明与继承关系

  • 继承派生概念
  • 派生类声明
  • 派生类从基类继承的过程
    • 吸收基类成员
    • 修改基类成员
    • 添加新成员
  • 继承关系
    • 公有继承
    • 保护继承
    • 私有继承

继承派生概念

  • 类的继承就是新类由已经存在的类获得已有特性
  • 类的派生则是由已经存在的类产生新类的过程
    由已有类产生新类时,新类会拥有已有类的所有特性,然后又加入了自己独有的新特性。**已有类叫做基类或者父类,产生的新类叫做派生类或者子类。**派生类同样又可以作为基类派生新的子类,这样就形成了类的层次结构。

类是对现实中事物的抽象,类的继承和派生的层次结构则是对自然界中事物分类、分析的过程在程序设计中的体现。
在这里插入图片描述
某个公司雇员的派生关系。位于最高层的雇员其抽象程度最高,是最具一般性的概念。最下层抽象程度最低,最具体。从上层到下层是具体化的过程,从下层到上层是抽象话的过程。面向对象设计中上层与下层是基类与派生类的关系。
此公司的雇员有三类:兼职技术人员、管理人员和销售人员。每个雇员都有姓名、级别和薪水等信息。每种雇员都可以升级,但升级方式不同。他们的月薪计算方式也不同,兼职技术人员应按实际工作小时数领取月薪,管理人员领取固定月薪,而销售人员是根据当月销售额领取提成。
这三类雇员的升级方式和月薪的计算方法等不同,所以不能用同一个类来描述,需要有三个类来分别抽象三类雇员。但这三个类中又有很多数据成员是一样的,例如姓名、级别和薪水等,函数成员也有很多相同的,只是可能实现方法不同,例如升级函数和计算月薪函数等。
我们应该先描述所有雇员的共性,再分别描述每类雇员。分别描述时应先说明他是雇员,然后描述他特有的属性和处理方法。这种描述方法在面向对象设计中就是类的继承与派生。对雇员共性进行描述就形成了基类,而对每类雇员的特性的描述可以通过从基类派生出子类来实现。

派生类声明

派生类声明的语法形式为:

class 派生类名 : 继承方式1 基类名1, 继承方式2 基类名2, ... 继承方式n 基类名n
{派生类成员的声明;
}class Child : public Parent1, private Parent2
{
public:Child();~Child();
}
  • “基类名”(Parent1和Parent2)是已有类的名称,“派生类名”(Child)是从已有类产生的新类的名称。
  1. 多继承:一个派生类可以有多个基类。这种情况下派生类就同时具有多个基类的特性。同样,一个基类也可以产生多个派生类,这比多继承更常见。
  2. 单继承:一个派生类如果只有一个基类。
  3. 类族: 基类产生派生类,派生类又可以作为基类再派生它自己的派生类,任何基类又可以产生多个派生类,这样就形成了一个类族。
    • 直接基类:直接派生出某个类的基类叫做这个类的直接基类。
    • 间接基类:基类的基类或者更高层的基类叫做派生类的间接基类。
      类A派生出类B,类B派生出类C,则类A是类B的直接基类,类B是类C的直接基类,而类A是类C的间接基类。
  4. 继承方式:继承方式限定了派生类访问从基类继承来的成员的方式,指出了派生类成员或类外的对象对从基类继承来的成员的访问权限。每个“继承方式”只限定紧随其后的基类。如果没有显式指定继承方式,则默认为私有继承。
    • 公有继承public
    • 保护继承protect
    • 私有继承private
  5. 基类的扩展:派生类声明语法形式中的派生类成员指除了原封不动从基类中继承来的成员以外,修改的基类成员或者新增加的成员。派生类成员是对基类的扩展。

从基类继承产生派生类实现了对代码的复用,派生类中修改的基类成员或新增加的成员实现了对代码的扩展。这样继承与派生使得我们减少了重复性劳动,提高了软件开发效率,维护和扩展程序更容易。

派生类从基类继承的过程

派生类从基类继承的过程可以分为三个步骤:

  1. 吸收基类成员
  2. 修改基类成员
  3. 添加新成员。

吸收基类成员就是代码复用的过程,修改基类成员和添加新成员实现的是对原有代码的扩展,而代码的复用和扩展是继承与派生的主要目的。

 class employee                  // 雇员类
{
public:employee();              // 构造函数~employee();             // 析构函数void promote(int);       // 升级函数void getSalary();        // 计算工资
protected:char *m_szName;          // 雇员姓名int   m_nGrade;          // 级别float m_fSalary;         // 工资
};
class salesman : public employee
{
public:salesman();~salesman();void getSalary();        // 计算工资
private:float m_fProportion;     // 提成比例float m_fSalesSum;       // 当月总销售额
};

吸收基类成员

派生类从基类继承时首先就是吸收基类成员,将基类成员中除了构造函数和析构函数外的所有其他成员全部接收。这里要注意,基类的构造函数和析构函数都不能被派生类继承。

派生类不能从基类继承构造函数和析构函数。但是派生类同样需要有初始化和清理,所以我们需要为派生类添加新的构造函数和析构函数,就像上例中,派生类salesman中就添加了新的构造函数salesman()和新的析构函数~salesman()。

上例中,employee类除构造函数和析构函数外的所有成员:promote(int)函数、getSalary()函数,以及数据成员m_szName、m_nGrade和m_fSalary,都被派生类salesman继承过来。

修改基类成员

派生类修改基类成员的方式有两种:

  1. 通过设置派生类声明中的继承方式,来改变从基类继承的成员的访问属性。
  2. 通过在派生类中声明和基类中数据或函数同名的成员,覆盖基类的相应数据或函数。
    一旦我们在派生类中声明了一个和基类某个成员同名的成员,那么派生类这个成员就会覆盖外层的同名成员。这叫做同名覆盖。 需要注意的是,要实现函数覆盖不只要函数同名,函数形参表也要相同,如果函数形参表不同只有名字相同则属于前面所说的重载。

上例中,salesman类的getSalary函数覆盖了employee类的同名函数。比如,我们定义了一个salesman类的对象A,则A.getSalary()调用的是salesman类中的getSalary函数而不是基类employee中的。

添加新成员

代码的扩展是继承与派生的主要目的之一,而添加新成员是实现派生类在基类基础上扩展的关键。
上例中,派生类salesman就添加了两个新数据成员m_fProportion和m_fSalesSum。可见,能够添加新成员还是很方便的,我们在软件开发中可以根据实际需要为派生类添加新的数据成员或函数成员。

继承关系

  • 对象仅能访问类的公有成员,不能访问保护成员和私有成员。而类的内部成员使用类内部成员时没有公私之分。通过对象访问类的成员属于外部访问,只能访问类的公有成员。
  • 派生类的继承方式为public,即公有继承时,对基类中的公有成员和保护成员的访问属性都不变,而对基类的私有成员则不能访问。
  • 保护继承方式中,基类的公有成员和保护成员被派生类继承后变成派生类的保护成员,而基类的私有成员在派生类中不能访问。
  • 私有继承方式中,基类的公有成员和保护成员被派生类继承后变成派生类的私有成员,而基类的私有成员在派生类中不能访问。

派生类对基类成员的访问主要有两种:

  1. 派生类的新增成员对继承的基类成员的访问;
  2. 派生类的对象对继承的基类成员的访问。通过对象访问类的成员属于外部访问,只能访问类的公有成员。

公有继承

基类的公有成员和保护成员被继承到派生类中以后同样成为派生类的公有成员和保护成员,派生类中新增成员对他们可以直接访问,派生类的对象只能访问继承的基类公有成员。但是派生类的新增成员和派生类的对象都不能访问基类的私有成员。

#include <iostream>
using namespace std;
class Base            // 基类Base的声明
{
public:               // 公有成员函数void SetTwo(int a, int b)  { x=a; y=b; }int GetX()   { return x; }int GetY()   { return y; }
private:              // 私有数据成员int x;int y;
};
class Child : public Base    // 派生类的声明,继承方式为公有继承
{
public:                      // 新增公有成员函数void SetThree(int a, int b, int c)  { SetTwo(a, b); z=c; }int GetZ()   { return z; }
private:                     // 新增私有数据成员int z;
};
int main()
{Child child;           // 声明Child类的对象child.SetThree(1, 2, 3); // 设置派生类的数据cout << "The data of child:"<<endl;cout << child.GetX() << "," << child.GetY() << "," << child.GetZ() << endl;return 0;
}
  • 上面的程序声明了一个基类Base,又声明了Base类的派生类Child,最后是主函数部分。派生类Child从基类Base中继承了除构造函数和析构函数外的所有数据成员和函数成员,这些再加上派生类Child的新增成员就组成了Child类的全部。类Child的继承方式为公有继承,基类Base的所有公有成员在派生类Child中的访问属性不变,都可以直接访问,所以Child类的SetThree函数可以直接调用Base类的SetTwo函数。基类公有成员SetTwo、GetX和GetY都变成了Child类外部接口的一部分。但是上面说过,派生类不能访问基类的私有成员,所以Child类不能访问Base类的x和y。
  • 主函数中首先定义了派生类Child的对象child,然后通过对象child调用了派生类Child的新增公有函数SetThree和GetZ,还调用了从基类Base继承的公有成员函数GetX和GetY。

保护继承

基类的公有成员和保护成员在派生类中都成了保护成员,所以派生类的新增成员可以直接访问基类的公有成员和保护成员,而派生类的对象不能访问它们.
类的对象也是处于类外的,不能访问类的保护成员。对基类的私有成员,派生类的新增成员函数和派生类对象都不能访问。

假设A类是基类,B类是从A类继承的派生类,A类中有保护成员,则对派生类B来说,A类中的保护成员和公有成员的访问权限是一样的。而对A类的对象的使用者来说,A类中的保护成员和私有成员都一样不能访问。可见类中的保护成员可以被派生类访问,但是不能被类的外部对象(包括该类的对象、一般函数、其他类等)访问。我们可以利用保护成员的这个特性,在软件开发中充分考虑数据隐藏和共享的结合,很好的实现代码的复用性和扩展性。

class Base
{
protected:int x;           // 基类的保护成员
};
int main()
{Base base;base.x = 0;      // 编译报错return 0;
}
  • 这段代码在编译的时候会报错,错误就出在通过对象base访问保护成员x时,就像上面讲的,对Base类的对象base的使用者来说,Base类中的保护成员x和私有成员的访问特性是一样的,所以对象base不能访问x,这样跟使用私有成员一样通过保护成员实现了数据的隐藏。
class Base
{
protected:int x;           // 基类的保护成员
};
class Child : public Base
{
public:void InitX();
};
void Child::InitX()
{x = 0;
}
  • 对上面的派生类Child来说,基类Base中的保护成员x和公有成员的访问权限一样,所以Child类的成员函数InitX可以访问Base类的保护成员x。

私有继承

在私有继承方式中,基类的公有成员和保护成员被派生类继承后变成派生类的私有成员,而基类的私有成员在派生类中不能访问。派生类的新增成员可以直接访问基类的公有成员和保护成员,但是在类的外部通过派生类的对象不能访问它们。而派生类的成员和派生类的对象都不能访问基类的私有成员。

***不管是保护继承还是私有继承,在派生类中成员的访问特性都是一样的,都是基类的公有和保护成员可以访问,私有成员不能访问。***但是派生类作为基类继续派生新类时,两种继承方式就有差别了。例如,A类派生出B类,B类又派生出C类,如果B类是以保护继承方式从A类继承的,则A类的公有成员和保护成员都成为B类的保护成员,再由B类派生出C类时,原来A类的公有成员和保护成员间接继承到C类中,成为C类的保护成员或者私有成员(C类从B类公有继承或保护继承时为前者,私有继承时为后者),所以C类的成员可以间接访问A类的公有成员和保护成员。但是如果B类是以私有继承方式从A类继承的,则A类的公有成员和保护成员都成为B类的私有成员,A类的私有成员不能在B类中访问,B类再派生出C类时,原来A类的所有成员都不能在C类中访问。

由以上分析得出,私有继承使得基类的成员在其派生类后续的派生中不能再被访问,中止了基类成员继续向下派生,这对代码的复用性没有好处,所以一般很少使用私有继承方式。

#include <iostream>
using namespace std;
class Base            // 基类Base的声明
{
public:               // 公有成员函数void SetTwo(int a, int b)  { x=a; y=b; }int GetX()   { return x; }int GetY()   { return y; }
private:              // 私有数据成员int x;int y;
};
class Child : private Base    // 派生类的声明,继承方式为公有继承
{
public:                      // 新增公有成员函数void SetThree(int a, int b, int c)  { SetTwo(a, b); z=c; }int GetX()   { return Base::GetX(); }int GetY()   { return Base::Gety(); }int GetZ()   { return z; }
private:                     // 新增私有数据成员int z;
};
int main()
{Child child;           // 声明Child类的对象child.SetThree(1, 2, 3); // 设置派生类的数据cout << "The data of child:"<<endl;cout << child.GetX() << "," << child.GetY() << "," << child.GetZ() << endl;return 0;
}
  • hild类从Base类中私有继承,Base类中的公有成员SetTwo()、GetX()和GetY()成为Child类的私有成员,在Child类中可以直接访问它们,例如Child类的成员函数SetThree()中直接调用了Base类的公有成员函数SetTwo()。Base类的私有成员x和y在Child类中不能访问。在外部通过Child类的对象不能访问Base类的任何成员,因为Base类的公有成员成为Child类的私有成员,Base类的私有成员在Child类中不能访问。那么Base类的作为外部接口的公有成员SetTwo()、GetX()和GetY()都被派生类Child隐藏起来,外部不能通过Child类的对象直接调用。
  • 这里调用的函数GetX()和GetY()都是派生类Child的函数,由于是私有继承,基类Base中的同名函数都不能通过Child类的对象访问。

如果我们希望派生类也提供跟基类中一样的外部接口怎么办呢?
我们可以在派生类中重新定义重名的成员。上面的Child类就重新定义了公有成员函数GetX()和GetY(),函数体则只有一个调用基类函数的语句,照搬了基类函数的功能。因为派生类中重新定义的成员函数的作用域位于基类中同名函数的作用域范围的内部,根据前面可见性中讲的同名覆盖原则,调用时会调用派生类的函数。通过这种方式可以对继承的函数进行修改和扩展,在软件开发中经常会用到这种方法。

这篇关于10_1、C++继承与派生:声明与继承关系的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

C++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

java中新生代和老生代的关系说明

《java中新生代和老生代的关系说明》:本文主要介绍java中新生代和老生代的关系说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、内存区域划分新生代老年代二、对象生命周期与晋升流程三、新生代与老年代的协作机制1. 跨代引用处理2. 动态年龄判定3. 空间分

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat