首页 > 编程语言 >C++类和对象_继承

C++类和对象_继承

时间:2023-08-07 22:01:01浏览次数:45  
标签:继承 子类 成员 C++ 对象 int 父类 public

继承概述

作为面向对象的三大特性之一,继承(inheritance)是面向对象编程中代码复用的一种重要手段。继承是类设计层面的一种复用,它允许在保证原有类性质不变的基础上对其进行扩展新的属性和功能,产生新的类。例如,在类 person 中定义关于 ‘人’ 的基本属性和行为,以 person 为基础扩展出特定人群的特殊属性和行为,即产生了一个新的类,这即是继承的基本思想。被继承的类称为父类(基类),继承产生的新的类称为子类(派生类)。

继承与组合

继承常常被与组合的概念进行对比。继承是一种白盒复用(white-box reuse),父类的内部细节对子类可见,耦合度高,是一种 is-a 的关系;组合(composition)是一种黑箱复用(black-box reuse),只能使用被组合对象的公开接口,没有强依赖关系,是一种 has-a 的关系。在保证逻辑合理的情况下,应优先考虑使用组合。

//人和工程师是一种is-a关系
class Person
{
  /*…………*/
}

class Engineer : public Person
{
	/*…………*/
}

//汽车和轮胎是一种has-a关系
class Tyre
{
	/*…………*/
};

class Car
{
protected:
	Tyre _tyre;
	
  /*…………*/
};

继承方式

在C++中有三种继承方式,分别是public继承、protected继承和private继承。继承方式和父类的访问限定符共同决定了子类对父类成员的访问权限,其中继承方式的影响力大于父类成员的访问限定符。无论以何种方式继承,父类的private成员对于子类都是不可见的,“不可见”指的是父类的private成员被子类继承,但是语法上限制了子类不能在类内外访问父类的private成员。

C++类和对象_继承_菱形虚拟继承

在实际使用中,一般进行的都是public继承,后两种继承方式很少见。

public继承中的对象赋值问题

C++语法规定,不允许父类对象赋值给子类对象,即向下转换,而允许子类对象赋值给父类对象,即向上转换。当子类对象赋值给父类对象时,不发生隐式类型转换和产生临时变量,而是进行赋值兼容,将共有部分进行切片,赋值给父类对象。

当用父类指针接收子类对象的地址时,父类指针维护的是父子类的共有部分;同样的,当用父类类型引用子类对象时,引用是共有部分的别名。这种切片行为也是多态实现的一种支持因素。

C++类和对象_继承_菱形虚拟继承_02

继承中的类作用域

在继承体系中,父类和子类处于不同的作用域,当访问一个成员时,优先在子类中寻找。当子类与父类中的成员同名时,这两个成员即构成“隐藏”关系,子类隐藏了父类的同名成员,访问被隐藏的父类成员需要用父类的域作用限定符进行指定,否则默认是子类的成员。对于成员函数,只要未被修饰的函数名相同即构成隐藏。

派生类的默认成员函数

派生类的默认成员函数遵循同一个设计理念:父类成员部分由父类的默认成员函数完成工作,子类独有的部分由子类的默认成员函数完成工作。

C++类和对象_继承_菱形虚拟继承_03

构造函数

子类的构造函数必须调用父类的构造函数对父类的那部分成员进行初始化。如果父类没有默认构造函数,则必须在子类构造函数的初始化列表进行显式调用

析构函数

在子类析构时,编译器会先析构子类的独有部分,再自动调用父类的析构函数对父类部分进行析构。编译器保证“先子后父”的析构顺序,一方面符合栈的使用习惯,另一方便考虑到子类中的某些行为可能依赖父类中的某些成员。

拷贝构造函数

与构造函数类似,需要在子类的拷贝构造函数初始化列表中显式调用父类的拷贝构造函数对父类部分进行拷贝构造,直接将子类对象作为参数传递给父类的拷贝构造函数,此时会发生赋值兼容。最后对子类的独有部分进行拷贝构造。

赋值运算符重载

子类的赋值重载需要首先调用父类的赋值重载对父类部分进行赋值,由于子类和父类的赋值重载会发生同名隐藏,所以调用父类的赋值重载时需要用类访问限定符进行指定。最后对子类的独有部分进行赋值。

继承与友元、继承与静态成员

在C++中,友元关系不能继承。子类可以使用父类的静态成员,静态成员同时属于父类和子类。静态成员不会额外拷贝,子类继承的是静态成员的使用权

C++多继承

一个子类只有一个直接父类,这种继承称为单继承,除单继承外,还存在一种事物同时具有多种事物的属性的情况,此时一个类继承自多个父类,这种继承称为多继承。

C++类和对象_继承_菱形继承_04

有多继承便有菱形继承。

菱形继承

菱形继承是多继承的一种特殊情况,其中一个子类的两个父类同时继承自同一个类。

C++类和对象_继承_菱形继承_05

从下面的代码和对象模型中可以看到,菱形继承会造成两个问题:

  • 数据冗余。类A中的成员在D中出现了两份,会造成空间浪费。
  • 二义性。由于D中有两份A的成员,所以调用的是B中的A成员或是C中的A成员便会有歧义,这便是二义性问题。实际访问时,需要用类访问限定符进行指定。
class A
{
public:
	int _a = 0;
	int _aa = 0;
};

class B : public A
{
public:
	int _b = 0;
	int _bb = 0;
};

class C : public A
{
public:
	int _c = 0;
	int _cc = 0;
};

class D : public B, public C
{
public:
	int _d = 0;
	int _dd = 0;
};

C++类和对象_继承_多继承_06

为了有效解决上述菱形继承造成的问题,虚继承应运而生。

菱形虚拟继承

在继承关系的腰部(例如上文的B类继承和C类继承)用virtual修饰,即进行虚拟继承。

class A
{
public:
	int _a = 0;
	int _aa = 0;
};

class B : virtual public A
{
public:
	int _b = 0;
	int _bb = 0;
};

class C : virtual public A
{
public:
	int _c = 0;
	int _cc = 0;
};

class D : public B, public C
{
public:
	int _d = 0;
	int _dd = 0;
};

进行虚拟继承后,通过观察对象的内存分布和对象模型,可以发现对象中只有一份A类的成员,解决了数据冗余和二义性问题。

与普通菱形继承不同的是,菱形虚拟继承中的B对象和C对象中的第一个成员变成了一个地址,通过寻址,可以发现这个地址指向了一个表,其中存储了一些整型,这个表即是虚基表(virtual base table)

C++类和对象_继承_多继承_07

虚基表存储了当前对象相对于共同父类对象(上文中的A)成员的偏移量,当用户通过B类或C类访问或修改A类成员时,只需要通过B对象或C对象的虚基表指针找到虚基表,并进一步拿到对象相对于A成员存储位置的偏移量即可得到A成员的实际存储位置,并对其进行操作,以达到同步的目的。

C++类和对象_继承_菱形继承_08

虚拟继承固然解决了菱形继承中的问题,但是避免造成这些问题最有效的方式便是不使用菱形继承。相比于菱形继承在实际中的使用频率,其造成的麻烦驱使我们尽量避免使用这种技术。

标签:继承,子类,成员,C++,对象,int,父类,public
From: https://blog.51cto.com/158SHI/6998733

相关文章

  • ASP.NET Core对象池
    【C#/.NET】使用ASP.NETCore对象池 NugetMicrosoft.Extensions.ObjectPool 使用对象池的好处减少初始化/资源分配,提高性能。这一条与线程池同理,有些对象的初始化或资源分配耗时长,复用这些对象减少初始化和资源分配。比如:我有一个执行耗时约500毫秒,内存空间2KB的任务......
  • 类与对象
    一、面向过程和面向对象初步认识C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。C++是基于面向对象的,关注的是对象,将一件事拆分成不同的对象,靠对象之间的交互完成。二、类的引入C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义......
  • C++入门到放弃(10)——操作符重载:operator
    ​1.重载重载允许创建多个名称相同,但输入不同的函数,这些函数的参数列表不同,可以通过给予不同输入变量调用对应的函数。函数重载的关键是函数的参数列表。如果两个函数的参数数量和类型相同,同时参数的排列顺序也相同,那么就是同一个函数,不构成重载,它与f返回值和变量名都无关。v......
  • Windows c++检测笔记本是否处于睡眠状态
    最近遇到一个问题,程序需要检测电脑是否处于睡眠状态。一开始使用的方式是在WindowProc里监听WM_POWERBROADCAST消息,对PBT_APMSUSPEND``PBT_APMRESUMEAUTOMATIC消息做处理。但是实际测试中发现,这种方法在台式机中运行良好,但是放到笔记本电脑里就不行,系统休眠时监听不到WM_POWERBRO......
  • Java学习面向对象Day01
    面向对象Day01一、名词解释OO:面向对象(OrientedObject)定义:是一种以对象为导向,围绕对象来开发应用程序的软件开发方法。OOA:面向对象分析OOD:面向对象设计OOAD:面向对象的分析与设计OOP:面向对象编程----------你们所参与的二、现实世界中的类和对象先要......
  • Java学习面向对象Day02
    面向对象Day02-继承一、继承1.1生活中的继承皇位继承------------------------儿子不需要打江山,可以直接坐江山努尔哈赤---------------------东三省做主的范围东三省皇太极-----------------------继承的努尔哈赤可以直接对东三省做主自己打下了内外蒙古做主......
  • c#学习笔记-------------继承和抽象类
    类继承通过继承我们可以定义一个新类,新类纳入一个已经声明的类并进行扩展继承是面向对象的编程的一种基本特性。借助继承,能够定义可重用(继承)、扩展或修改父类行为的子类。成员被继承的类称为基类。继承基类成员的类称为派生类。C#和.NET只支持单一继承。也就是说,类只能......
  • vc++2008通过paho c语言客户端接入MQTT
    因项目需要,IoT平台需要支持vc++2008接入。因为Paho的c++客户端不支持低版本vc++,所以不得不尝试通过c语言的库实现。类库下载从github下载c语言包。例如:eclipse-paho-mqtt-c-win32-1.3.12.ziphttps://github.com/eclipse/paho.mqtt.c/releases类库整合和配置解压出来的c语言......
  • 质因子分解C++
    1、题目2、AC代码#include<iostream>#include<cmath>usingnamespacestd;constintmaxn=100010;//10的5次方即可boolisPrime(intn){if(n<=1)returnfalse;if(n==2||n==3)returntrue;//特判if(n%6!=1&&n%6!=5)returnfalse;//不在6的倍......
  • 递推算法例题C++
    1、移动路线【题目描述】X桌子上有一个m行n列的方格矩阵,将每个方格用坐标表示,行坐标从下到上依次递增,列坐标从左至右依次递增,左下角方格的坐标为(1,1),则右上角方格的坐标为(m,n)。小明是个调皮的孩子,一天他捉来一只蚂蚁,不小心把蚂蚁的右脚弄伤了,于是蚂蚁只能向上或向右移动。小明......