C++中的面向对象特性思考

前言

虽然了解c++是面向对象的语言,由于程序规模的原因,自己写的大多数C++代码里却很少用到这些面向对象的特性,比如多态、继承之类的,于是写C++变成了写"C with Class"…

本文主要从面向对象的特性来对C++重新进行思考,主要内容:

  1. 面向对象提出的背景,主要是用来解决什么问题?
  2. C++中的具体实现机制
  3. C++与其他语言的比较
  4. 面试中问到的C++

面向对象背景

面向过程的程序设计

与“面向对象”相对的是“面向过程”程序设计,“面向过程”的特点是以过程为主,在分析问题的时候,着重分析解决该问题的步骤,并对这些步骤进行实现

需要注意的是,以“过程”为目的方法重点在实现某些步骤或方法上,并不会对问题有进一步的抽象,换句话说,看到一个问题,脑子里最先想到的是如何去解决、实现它,这样就导致了“一千个问题就有一千种具体实现”。

举个简单的例子,计算a+b的值,由于a和b的类型不确定,可能会有以下的实现:

//1
int sum(int a, int b){
	return a+b;
}
//2
float sum(float a, float b){
	return a+b;
}
....

我们可以看到,实现一个简单的加法功能便需要如此多的函数来支撑,而且这些函数中的关键代码居然是重复的。因此这也引出面向过程的程序设计的一个缺点:代码重复

所以面向过程的程序设计适合在程序规模比较小的时候使用,譬如实现一个简单功能的脚本。

而在较大规模程序设计中,面向过程的程序设计会使得程序设计变得更为困难,尤其是会有大量的重复代码出现(与以上sum程序类似,函数实现的是同一个求和功能,却不得不同多种函数进行实现),而函数之间的调用关系会变的越来越复杂、耦合

面向对象的程序设计

在此背景下,面向对象的程序设计便被提出了,与面向过程相比,面向对象在“问题分析”的阶段便有所不同,这也是与面向过程最本质的区别:对问题进行抽象这是面向对象最最核心的地方。

针对一个问题,面向对象将该问题进行分解,把构成问题的事务分解成对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。 --百度百科

以上面的求和为例,具体实现的功能是对两个对象的值进行求和,我们可以把这些函数抽象成一个函数:

C sum (A a , B b){
	return a+b;
}

上面的函数中,我们可以看到“求和”的基本功能并没有变,主要是对输入和返回值进行了抽象。而这也正是“面向对象”分析问题的关键,将问题分解为组成各种对象的行为以及交互

习惯于面向过程的同学看到这里可能会有很大的疑问,道理我都懂,但这里A a , B b,到底具体怎么实现

在具体实现的时候,不同的语言有不同的实现机制,但具体来讲,一般面向对象的语言会有“封装、继承、多态”这三种实现。

封装、继承、多态

封装

首先是封装,我们使用面向对象的方法对问题进行分析,抽象成对象后,对对象进行进一步的分析。

一般而言,一个对象可能有1. 属性 2. 行为。其中一些属性、行为可能是对象内部使用,因此实现的时候要将对象进行封装。来控制外部的访问以及隐藏内部具体实现

譬如,将人作为一个对象后,其年龄只能因为时间而增长,不能因为外界对人这个对象的调用或者访问而影响

继承

封装解决了对象的访问以及控制权限,而继承解决代码复用的问题。

设想一个王者荣耀的游戏场景,里面的人物有各种职业(法师、射手、坦克等),甚至具体的英雄(后裔、鲁班、亚瑟),如果用面向过程的方法来设计,设计对象就变成了每个特定的人物,针对每个人物(后裔、鲁班)都会存在一个自己的结构,而结构里都会存在很多相似的属性(如法术攻击、法术防御等),这样会导致代码的大量重复

而使用面像对象的思想对游戏人物进行分析、抽象后,我们可以对这些人物的共性进行一个抽象,比如共性(属性:法术攻击、物理攻击等,行为:移动、攻击、被击飞等),在抽象后我们可以将其单独作为一个类,然后让具体的人物类来从它这里继承,得到具体人物对象。那么在实现的时候,我们就可以忽略这些共性属性、行为,而把重点放在其他不同特性的实现上。

如:假设抽象出的基本人物类为BaseAvatar,那么一些共有的属性为法术攻击、物理攻击等,还可能有“攻击”的行为(为了简化这里把具体参数略去),那么具体子类在继承的时候就可以忽略基本属性,把重点放在不同行为的实现上了。

class BaseAvatar{
private:
	float magic_attack;//法术攻击
	float physical_attack;//物理攻击
	float speed;//移动速度
	...
public: 
	virtual void attack(){}
}

假设鲁班、后裔继承了BaseAvatar,可以根据自己的攻击行为进行实现,如下:

class LuBan : public BaseAvatar{
public: 
	virtual void attack(){
		//发射子弹
	}
}
class HouYi : public BaseAvatar{
public:
	virtual void attack(){
		//发射箭矢
	}
}

多态

多态的含义是同一条语句,在执行的时候会展示出不同的行为

从具体实现来讲,一般在继承发生后,父类类型的指针在指向子类对象时并调用子类中的虚函数方法时,会调用子类中重新实现的虚函数方法(而不是父类的虚函数方法)。

还是以上面的王者荣耀游戏场景为例,假设抽象出的基本人物类为BaseAvatar,里面的存在一个“攻击”的行为,子类在继承的时候需要重写这个方法。

class BaseAvatar{
public: 
	virtual void attack(){}
}

假设鲁班、后裔继承了BaseAvatar,如下:

class LuBan : public BaseAvatar{
public: 
	virtual void attack(){
		//发射子弹
	}
}
class HouYi : public BaseAvatar{
public:
	virtual void attack(){
		//发射箭矢
	}
}

在具体渲染攻击这个行为的时候,为了提高效率、简化运算,可能只是会将攻击的效果显示出来并不会将攻击直接作用到对手身上(譬如鲁班在一个人的时候也可以发射子弹,遇到敌方英雄也可以发射子弹,但只是将子弹发射到敌方英雄的方向,但具体敌方英雄在收到攻击后的碰撞反应可能并不会做,如流血等)

因此在渲染攻击行为的时候,我们可以抽象出一个函数Render,并进行简化,将具体的攻击行为作为Render的参数指的是要渲染的人物的攻击行为

void Render(BaseAvatar * p_avatar){
	...
	p_avater->attack();
	...
}

我们使用基类指针作为参数类型,在多态的机制下,可以通过控制传入参数的类型来使这个函数渲染出不同的效果。如:

LuBan luban;//创建鲁班
HouYi houyi;//创建后裔

Render(&luban);//渲染发射子弹
Render(&houyi);//渲染发射箭矢

如果说封装完整的包装了对象,继承解决了代码重复的问题,那么多态则使得代码变得更易扩展。


未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值