联系我们
简单又实用的WordPress网站制作教学
当前位置:网站首页 > 程序开发学习 > 正文

C++继承

作者:访客发布时间:2024-01-06分类:程序开发学习浏览:184


导读:1.概述面向对象编程是一个巨大的编程范式,类之间的继承是它的一个基本方面,它是我们可以实际利用的最强大的特性之一。继承允许我们有一个相互关联的层次结构。换句话说,它允许我们有一...

1. 概述

面向对象编程是一个巨大的编程范式,类之间的继承是它的一个基本方面,它是我们可以实际利用的最强大的特性之一。继承允许我们有一个相互关联的层次结构。换句话说,它允许我们有一个包含公共功能的基类,然后允许我们从那个基类中分离出来,从最初的父类中创建子类。这些类、继承等如此有用的主要原因是它可以帮助我们避免代码重复。代码复制是指我们必须多次写完全相同的代码,或者只是可能略有不同,本质上是完全一样的东西。我们不需要一遍又一遍的重复自己,我们可以把类之间的所有公共功能放在一个父类中,然后从基类(父类)创建(派生)一些类 。稍微改变下功能,或者引入全新的功能,继承给我们提供了这样一种方式,将这些公共代码放到基类中,这样我们就不用像写模版那样不断重复了。

2. 案例

1. 项目准备

准备一个简单的项目,项目中有一个main.cpp文件,内容如下

image.png

2. 开始案例

假设我有一个Entity类,它将管理游戏中所有的实体对象,在我们的游戏中有很多非常具体的实体,然而在某方面,它们将共享功能。例如,每个实体在我们的游戏中都有自己的位置,这可以通过2个float数来表达,我们有float Xfloat Y

image.png

我们可能想赋予每个实体移动的能力,可通过一个Move方法,有一个xa和一个ya作为参数,作为我们想要移动的大小。

image.png

现在,我们有了一个基类,Entity类。

我们说过,在游戏中创建的每一个实体都将具有这些特征。

我们继续创建一个新类型,例如我们写一个Player类,目前还没有所谓的继承概念。所以基本上,如果我们从零开始,我们希望这个Player也有位置,因为这也是一个实体Entity。给player(角色)赋予位置是有意义的。其次我们还想让它能够移动,所以我们需要move函数。

image.png

所以,这个player最终写的东西和Entity这个很像。

也许这个player类有我们想要存储的额外数据,例如名字或类似的东西

image.png

所以可以看到,player和Entity实际上是不同的类了。然而,这有相当多的代码只是被复制粘贴。

image.png

所以,我们能做的就是利用继承的力量。

我们可以扩展这个Entity实体类,来创建一个名为Player的新类型,然后让Player存储新数据。比如,名字Name,或者可提供额外的功能,基类中不存在的函数。

image.png

下面,我们把Player变成Entity的子类,我们需要在类型声明后写一个冒号,然后我们写public Entity

image.png

在我们写的这行代码中,发生了一些事情,Player类现在不仅拥有Player类型,而且它也有Entity类型,意思就是现在是两种类型了。类型在C++中是相当复杂的主题,因为一方面它们实际上并不存在,然而另一方面,它们又搞事情,特别是如果你有特定的运行时标记激活的话,那个时候我们再去深入了解这个东西是如何工作的。另外,Player现在拥有Entity拥有的所有东西,所以我们拥有所有类成员,比如X和Y。我们Player类中这两个数只是通过复制粘贴Entity类来的,所以,我们可以将Player类中所有重复的代码都去掉,只有新的东西,如名字Name,和打印名字的函数PrintName,这些都是新加的。

image.png

好了,现在,这个Player类看起来更干净了。现在,这个Player实际上是一个Entity,这意味着,仅仅看这个Player类并不能告诉我们所有,我们需要去找Entity类,看看它有什么,因为就Player而言,任何在Entity类中不是私有的东西,实际上都可以被Player访问。

下面,我们来测试一下。

假设,我们有一个Player的实例player。我们不仅可以调用PrintName函数,这个PrintName函数就在Player类里面。而且我们也可以像调用PrintName函数那样调用Move函数,并访问X和Y,就好像这个player是一个Entity一样。

image.png

因为Player继承了Entity。其他我们可以应用的概念是叫做多态,这个将在后续的篇章讨论。但基本上,多态是一个单一类型,但有多个类型的意思。还记得我们提到Player不只是Player类型,它还是一个Entity类型。这意味着我们可以在任何我们想要使用Entity的地方使用Player。这是有道理的,因为Player拥有Entity所拥有的一切,再加上一点新东西,甚至也可能完全一样,什么新东西都不加。但Entity总是player的超集。这意味着Player总是拥有Entity所拥有的一切,意味着如果我想创建一个打印Entity对象的独立功能,例如,通过访问X和Y变量并将它们打印到控制台,我可以传入Player对象到相同的函数中,即使这个函数式接受Entity作为参数。可以这样搞的原因是,Player类或者Player类型保证会有这些X和Y变量。因为它作为Entity的子类,意味着它包含了所有Entity的东西。我们可以做很多事情。比如所有这些多态特征,我们也可以改变父类或基类的行为。例如,通过重写一个方法,并给它新的代码来代替它父类原有的方法运行。但今天要讲的是,继承是我们一直在使用的一种方式,它只是我们扩展现有类并为基类提供新功能的一种方式。这是面相对象编程中最重要的东西之一。

记住,当我们创建一个子类时,它将包含父类所有包含的一切。

另一个证明这个的好方法,我们在Entity类中有浮点数X和Y。我们继续,打印一个Entity对象的大小到我们的控制台。

image.png

F5运行我们的程序

image.png

可以看到Entity的大小是8,这就是我们所期望的,因为我们在Entity类中有两个浮点数X和Y。

现在我们继续打印Player的大小,如果Player类没有扩展Entity,Player只是一个单独的类

image.png

Player类只有这个const char*指针,在32位的引用程序中,这应该是4字节的内存。

我们F5运行程序

image.png

然而,Player类扩展了Entity类

image.png

这意味着Player继承了Entity类的所有变量,所以Player的实际大小是4+4+4为12。

我们F5运行程序

image.png

可以看到确实是12,可以看到Player类实际上继承了Entity拥有的所有东西。就好像我们复制粘贴了所有东西。那个Entity进入了Player类,因为Player类现在是一个Entity,而且还有些额外的功能,比如名字Name。

记住,这些类的大小和内存实际上可以变化,如果我们开始重写函数和Player类,那么我们就需要维护一个V表(虚函数表),也就是需要额外的内存需要占用,现在先不讲这个。



程序开发学习排行
最近发表
网站分类
标签列表