面向对象程序设计(OOP)是一种计算机编程思想,主要目标是为了实现代码的重用性、灵活性和扩展性。面向对象程序设计以对象为核心,程序由一系列对象组成。对象间通过消息传递(一个对象调用了另一个对象的函数)相互通信,来模拟现实世界中不同事物间的关系。

面向对象程序设计有三大特性:封装,继承,多态。

封装的基本体现就是对象,封装使得程序的实现“高内聚、低耦合”的目标。封装就是把数据和数据处理包围起来,对数据的处理只能通过已定义的函数来实现。封装可以隐藏实现细节,使得代码模块化。继承允许我们依据一个类来定义另一个类。当创建一个类时,不需要重新编写新的数据成员和函数成员,只需继承一个已有类即可。这个已有类称为基类(父类),新建的类称为派生类(子类)。派生类就自然拥有了基类的数据成员和函数成员。这样做可以重用代码功能和提高执行效率。多态性是指不同的对象接收到同一个的消息传递(函数调用)之后,所表现出来的行为是各不相同的。多态是构建在封装和继承的基础之上的。多态就是允许不同类的对象对同一函数名的调用后的结果是不一样的。多态虽然概念有些复杂,但是只要理解了继承,多态就非常简单了。

我们创建一个怪物的父类,创建Monster.h和Monster.cpp文件,用于该类的声明和实现。以下是Monster.h 内容:

#pragma once
#include <iostream>
#include <string>
using namespace std;// 定义一个怪物类
class Monster {protected:int id;            // IDstring name;   // 名称int attack;        // 攻击值public:// 声明默认构造方法Monster();// 声明有参构造方法Monster(int _id, string _name, int _attack);// 声明战斗方法void battle();
};

以下是Monster.cpp内容:

#include "Monster.h"// 默认构造方法
Monster::Monster() {this->id = 1;this->name = "monster";this->attack = 0;
}// 有参构造方法
Monster::Monster(int _id, string _name, int _attack) {this->id = _id;this->name = _name;this->attack = _attack;
}// 定义战斗方法
void Monster::battle() {cout << name << " attack " << attack << " !" << endl;
}

类的声明和定义,其实就是函数的声明和定义的区别,当然我们也可以在头文件中定义函数的实现。Monster类主要定义了怪物的攻击特征,因为游戏中的怪物都会具备这样的共性。然后我们再声明蜘蛛Spider类和蛇Snake类,分别继承这个怪物Monster父类,这样他们就拥有了父类的攻击特征。为了简单方便,我们直接在Monster.h中定义这两个类,代码如下:

// 定义个蜘蛛(继承怪物)类
class Spider : public Monster {};// 定义个蛇(继承怪物)类
class Snake : public Monster {};

注意,C++默认继承是private,也就是说子类不能访问父类的数据和函数,因此我们这里改用public,这样我们就能访问父类的数据和函数了。在我们的主文件ConsoleApplication.cpp中,我们可以这样使用这两个新类:

// 实例化一个Spider对象
Spider spider;
spider.battle();// 实例化一个Snake对象
Snake snake;
snake.battle();

我们一般使用类去声明一个变量(对象)的时候,称之为实例化,这个变量(对象)也称之为类的一个实例。继承既简化了我们的代码,又能实现对应功能。另外一点,构造方法是不能被继承的。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,我们需要手动完成子类的构造方法,并在其中也可以调用父类的构造方法。调用方式就是在Monster类的构造函数后,加一个冒号(:),然后加上父类的带参数的构造函数。这样,在子类的构造函数被调用时,系统就会去调用父类的带参数的构造函数去完成数据的初始化。这里面比较特殊的是父类默认构造方法,它是自动被子类的默认构造方法调用的,当然这个影响在实际开发中影响不大。我们给子类添加构造方法:

// 定义个蜘蛛(继承怪物)类
class Spider : public Monster {public:// 调用父类的构造函数Spider() : Monster() {}Spider(int _id, string _name, int _attack) : Monster(_id, _name,_attack){}
};// 定义个蛇(继承怪物)类
class Snake : public Monster {public:// 调用父类的构造函数Snake() : Monster() {}Snake(int _id, string _name, int _attack) : Monster(_id, _name, _attack) {}
};

在我们的主文件ConsoleApplication.cpp中,我们可以使用新类的构造函数了:

// 实例化一个Spider对象
Spider spider2(1, "Spider", 100);
spider2.battle();// 实例化一个Snake对象
Snake snake2(2, "Snake", 200);
snake2.battle();

函数重写(override):在基类中定义了一个普通函数,然后在派生类中又定义了一个同名同参数同返回类型的函数,这就是重写了。在派生类对象上直接调用这个函数名,只会调用派生类中的那个。例如我们重写父类的战斗方法,先在Monster.h做声明,然后在Monster.cpp文件中完成即可:

// 子类重写战斗方法(Monster.h)
void battle();// 子类重写Spider类的战斗方法(Monster.cpp)
void Spider::battle() {attack += 100;cout << "Spider attack " << attack << "!" << endl;
}

在我们的主文件ConsoleApplication.cpp中,我们可以使用重写函数了:

// 实例化一个Spider对象
Spider spider;
spider.battle();

函数重载(overload):在基类中定义了一个普通函数,然后在派生类中定义一个同名,但是具有不同的形参表的函数,这就是重载。在派生类对象上调用这几个函数时,用不同的参数会调用到不同的函数,如果没有则仍然去父类寻找。例如我们重载父类的战斗方法,先在Monster.h做声明,然后在Monster.cpp文件中完成即可:

// 子类重载Spider类的战斗方法(Monster.h)
void battle(int _attack);// 子类重载Spider类的战斗方法(Monster.cpp)
void Spider::battle(int _attack) {cout << "Spider attack " << _attack << "!" << endl;
}

在我们的主文件ConsoleApplication.cpp中,我们可以使用重载函数了:

// 实例化一个Spider对象
Spider spider;
spider.battle(500);

备注:派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。多继承即一个子类可以有多个父类,它继承了多个父类的特性。C++中一个派生类中允许有两个及以上的基类,我们称这种情况为多继承。使用多继承可以描述事物之间的组合关系,但是如此一来也可能会增加命名冲突的可能性。因此,在其他高级语言中,C#和Java是不允许多继承的!

C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。简单的说,重写是父类与子类之间多态性的体现,而重载是同一个类的行为的多态性的体现。C++支持两种多态性:编译时多态性和运行时多态性(也称为静态多态和动态多态)。一般情况下,我们所说的多态都是指的运行时多态,也就是动态多态。

C++编译器在编译的时候,要确定每个对象调用的函数的地址。这种绑定关系是根据对象的数据类型来决定的。如果想要系统在运行时再去确定对象的类型以及正确的调用函数,就要在基类中声明函数时使用virtual关键字,这样的函数我们称为虚函数。我们说多态是在程序进行动态绑定得以实现的,而不是编译时就确定对象的调用方法的静态绑定。程序运行到动态绑定时,通过基类的指针所指向的对象类型,然后调用其相应的方法,即可实现多态。

构成多态还有两个条件:

1. 调用函数的对象必须是指针或者引用。

2. 被调用的函数必须是虚函数,且完成了虚函数的重写。

多态一般的用法,就是用父类的指针指向子类,然后用父类的指针去调用子类中被重写的虚函数。通过父类指针调用子类的虚函数,可以让父类指针有多种形态。在我们的实例中,我们首先需要在父类Monster中使用 virtual 来修饰战斗函数,如下所示:

// 父类声明战斗方法
virtual void battle();

如果父类Monster中的battle方法是一个普通的方法,即没有使用virtual修饰的话,那么虽然spiderPointer和snakePointer里面存储的的确是Spider类和Snake类的指针,但是他们依然只会执行父类Monster的battle方法。只有我们使用virtual修饰父类battle方法后,才能得到我们想要的正确执行结果。在我们的主文件ConsoleApplication.cpp中,我们可以使用多态了:

// 实例化一个Spider对象
Spider spider3(1, "Spider", 100);
// 实例化一个Snake对象
Snake snake3(2, "Snake", 200);// 定义怪物对象指针
Monster* spiderPointer = &spider3;
spiderPointer->battle(); // 执行子类函数
Monster* snakePointer = &snake3;
snakePointer->battle();      // 执行父类函数

因为Snake类并没有重写battle函数,因此它只能调用父类的battle函数了。但是Spider类重新了battle函数,在重写的函数中,我们在原来的攻击值上增加了100。

备注:封装性是基础 ,继承性是关键 ,多态性是补充 ,多态性又存在于继承的环境之中 ,所以这三大特征是相互关联的 ,相互补充的。

接下来要理解的抽象的特性。面向对象程序设计中一切都是对象,对象都是通过类来描述的,但并不是所有的类都可以来描述对象的。如果一个类没有足够的信息来描述一个具体的对象,而需要其他具体的类来实现它,那么这样的类我们称它为抽象类。比如游戏怪物类Monster,它没有一个具体肖像,只是一个概念,需要一个具体的实体,如一只蜘蛛,一条蛇来对它进行特定的描述,我们才知道它的具体呈现。抽象类就是实现多态的一种机制。它定义了一组抽象的方法,至于这组抽象方法的具体内容由派生类来实现。同时抽象类提供了继承的概念,它的出发点就是为了继承,否则它没有存在的任何意义。在 C#和Java 中,可以通过两种形式来体现面向对象编程的抽象:抽象类(abstract)和接口(interface)。C++语言并没有像C#和Java那样对抽象类和接口有显式的支持。C++中只能使用virtual关键字来声明虚函数。C++语言中也没有抽象类的概念,但是可以通过虚函数实现抽象类。如果类中至少有一个函数被声明为虚函数,则这个类就是抽象类。抽象类只能用作父类被继承,子类必须实现虚函数的具体功能。C++ 接口则是使用抽象类来实现的。该类中没有定义任何的成员变量,所有的成员函数都是虚函数。接口就是一种特殊的抽象类。

最后还有讲一个组合的概念。类是一种构造数据类型,在类中可以其他类定义数据成员,这种数据成员称为对象成员。类中包含对象成员的形式,称为组合(Composition)。类之间的组合关系称为has-a关系,是指一个类拥有另一个类的实例对象。含有对象成员的类在调用构造函数对其数据成员进行初始化,其中的对象成员也需要调用其构造函数赋初值,语法如下:

<类名> :: <构造函数名> ([<形参表>]) : [对象成员1](<实参表1>) , [对象成员2](<实参表2>){...}

单冒号之后用逗号分隔的是类中对象成员和传递的实参,称为成员初始化列表。普通的数据成员既可以在构造函数中对其赋值,也可以在成员初始化列表中完成。对象成员只能在初始化列表中初始化,并且对象成员的构造函数的调用先于主类的构造函数。

首先我们构造两个类Sword.h(武器)和Player.h(玩家),代码如下:

#pragma once
#include <iostream>
#include <string>
using namespace std;// 定义一把武器
class Sword {protected:int id;          // 唯一标示string name; // 名称int attack;        // 攻击值public:// 定义默认构造函数Sword() {id = 1;name = "Sword";attack = 10;}// 定义有参构造方法Sword(int _id, string _name, int _attack) {this->id = _id;this->name = _name;this->attack = _attack;}// 定义攻击方法void battle() {cout << name << " Sword attack " << attack << " !" << endl;}
};

然后是Player.h(玩家):

#pragma once
#include <iostream>
#include <string>
#include "Sword.h"
using namespace std;// 定义一个玩家
class Player {protected:int id;             // 唯一标示string name;     // 名称Sword weapon;      // 武器类public:// 默认构造函数,调用武器类的构造函数Player() : id(1), name("Player"), weapon() {}// 定义有参构造函数,调用武器类的构造函数Player(int pid, string pname, int sid, string sname, int sattact) : weapon(sid, sname, sattact) {this->id = pid;this->name = pname;}// 战斗函数,调用Sword的战斗函数void battle() {weapon.battle();}
};

然后在我们的主文件ConsoleApplication.cpp中,我们可以使用类的组合了:

// 类的组合使用
Player player(1, "小菜鸟", 1, "木剑", 10);
player.battle();

类的组合在程序开发过程中经常使用。面向对象程序开发中,一切皆为对象。每一个对象都代表了一个封装好的数据和功能集合。一个复杂的对象可以通过继承,组合等多种方式来实现。在Unity中,场景中所有的物体都视为游戏对象(GameObject),一个复杂的游戏对象由不同的组件对象(component )构成。我们可以给一个游戏对象添加各种不同的组件来让该游戏对象具有不同的功能。这其实就是类组合的应用。

本课程的所有代码案例下载地址:

C++示例源代码(配合教学课程使用)-C/C++文档类资源-CSDN下载

备注:这是我们游戏开发系列教程的第一个课程,主要是编程语言的基础学习,优先学习C++编程语言,然后进行C#语言,最后才是Java语言,紧接着就是使用C++和DirectX来介绍游戏开发中的一些基础理论知识。我们游戏开发系列教程的第二个课程是Unity游戏引擎的学习。课程中如果有一些错误的地方,请大家留言指正,感激不尽!

第十一章 C++ 封装/继承/多态相关推荐

  1. Java继承_Hachi君浅聊Java三大特性之 封装 继承 多态

    Hello,大家好~我是你们的Hachi君,一个来自某学院的资深java小白.最近利用暑假的时间,修得满腔java语言学习心得.今天小宇宙终于要爆发了,决定在知乎上来一场根本停不下来的Hachi君个人 ...

  2. python多态的三种表现形式_python小结----面向对象的三大特征(封装,继承,多态)

    面向对象的三大特征: 封装,继承,多态 面向对象的编程思想核心:高类聚,低耦合–程序的设计模式范畴 封装 什么是封装: 在面向对象编程的思想中,对代码进行高度封装,封装又叫包装 封装就是指将数据或者函 ...

  3. python 参数类型的多态_【Python】面向对象:类与对象\封装\继承\多态

    六.Python面向对象--类与对象\封装\继承\多态 1.什么是面向对象编程 1.1 程序设计的范式:程序可控,易于理解 1.2 抽象并建立对象模型 1.3 程序是不同对象相互调用的逻辑.每个对象在 ...

  4. python--编写程序:实现乐手弹奏乐器,乐手可以弹奏不同的乐器而发出不同的声音------使用类的封装继承多态的问题/使用面向对象的思想,设计自定义类,描述出租车和家用轿车的信息

    编写程序:实现乐手弹奏乐器,乐手可以弹奏不同的乐器而发出不同的声音 ------使用类的封装继承多态的问题 class Instrumnet():#乐器类def make_sound(self):pa ...

  5. c语言编程 菲薄拉,C语言设计模式-封装-继承-多态

    快过年了,手头的工作慢慢也就少了,所以,研究技术的时间就多了很多时间,前些天在CSDN一博客看到有大牛在讨论C的设计模式,正好看到了,我也有兴趣转发,修改,研究一下. 记得读大学的时候,老师就告诉我们 ...

  6. 大数据笔记8—java基础篇4(面向对象-封装-继承-多态)

    面向对象 一.面向对象 1.面向过程 1.2.举例 1.3.总结 二.面向对象 1.简述 2.举例 3.思想特点 2.1.类的定义格式 2.1.1.简述 2.2.2.格式 2.3.3.示例 三.类的使 ...

  7. 小白理解——封装继承多态

                                      一.封装 是什么:首先是抽象,把事物抽象成一个类,其次才是封装.对外表示为一个对象,隐藏对象的属性和动作实现的细节,仅对外公开接口. ...

  8. Day55-每日一道Java面试题-Java 面向对象编程三大特性: 封装 继承 多态

    Java 面向对象编程三大特性: 封装 继承 多态 封装 封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问.但是如果一个类没有 ...

  9. 面向对象 编程及面向对象三大属性:封装 继承 多态

    面向对象 面向对象(Object Oriented,OO)是软件开发方法.面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统.交互式界面.应用结构.应用平台.分布式系统.网络管理结构. ...

最新文章

  1. javaweb_JSP 中文字符处理程序
  2. libpng error: Not a PNG file
  3. csu 1008 - Horcrux
  4. 解决:Intellij idea 启动项目报错 error:java: 无效的源发行版: 8
  5. 蚂蚁金服 Service Mesh 大规模落地系列 - 运维篇
  6. oracle字段规则,Oracle的基本操作+Oracle字段类型(zz)
  7. 由内鬼事件看企业的内部安全威胁
  8. PHP常用函数总结(一):
  9. HTTP协议格式详解(总结)
  10. 结对-人机对战象棋游戏-测试过程
  11. linux xbrowser 安装包,xmanager安装包
  12. Win10常用Win快捷键
  13. WIN10 企业版 LTSC 激活
  14. Excel表格的基本操作,包含制作一个表格的全部知识
  15. VMware 配置虚拟机固定IP指南
  16. 互联网摸鱼日报(2022-11-07)
  17. 腾讯滑块JS破解/本地识别DLL
  18. 三角形边长求高的c语言函数公式,c 求,已知三角形三边边长为abc,利用公式求面积...
  19. 多套头像/壁纸/背景图资源微信小程序源码 粉色UI 带流量主
  20. java stringbuilder 替换字符串_StringBuilder修改字符串内容,增,删,改,插

热门文章

  1. CGB2009-京淘项目DAY06
  2. SylixOS内存屏障
  3. BigDecimal.setScale用法
  4. 自己有工厂,怎样接外贸订单?
  5. 京东数科研发效率和质量提升实践
  6. JavaScript 对象如此神通广大,我要为它打Call
  7. 【嵌入式linux】Linux串口通信
  8. dede产生.php,DEDE织梦系统实现Tag标签静态化插件下载
  9. 树莓派入门操作及VNC显示 cannot currently show the desktop 解决方法
  10. 参数估计:极大似然估计、矩估计的基本概念及应用方法