面向对象(一)
面向对象(一)
相信各位学习各种语言的时间都不短了,那么为什么感觉自己总是只能做点小脚本或者小工具呢?
那是因为你没有找到对象啊!(笑)
面向对象是现代软件工程的一个基本工具,几乎所有的现代高级语言都支持面向对象编程。本篇文章分上下篇,以C++为例介绍面向对象这一计算机世界中最基本的元素。
一、什么是面向对象
我们先来看看权威的定义:
面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的编程典范,同时也是一种程序开发的抽象方针。它可能包含数据、特性、代码与方法。对象则指的是类(class)的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象[1][2]。
概念总是很玄乎的,需要注意的是这段概念中的几个名词:
对象、类、程序与数据
与面向对象(Procedure Oriented 简称PO)相对的就是面向过程(Procedure Oriented 简称PO)
1.一个小例子
下来我将用通俗易懂的语言描述一下面向对象(还是推荐有一定编程基础的童鞋们看哦):
让我们假设一个场景,我们需要烧一顿饭;
如果是面向过程:
1.执行淘米方法
2.执行煮饭方法
3.执行煮肉方法
4.执行煮菜汤方法
可见,面向过程的思想就是把一整个解决方案拆解成一个个分动作,再通过一定的顺序执行,最终得到结果
如果是面向对象:
1.人.淘米
2.电饭锅.煮饭
3.锅.煮肉
4.锅.煮菜汤
可见,在解决同一个问题时,面向对象思想就是先把执行的对象抽象出来,然后通过执行对象的动作来完成整个解决方案。
明显地看出,面向对象是以功能来划分问题,面向对象以步骤来划分问题。
2.有何区别
那么面向对象和面向过程有何优劣呢?
还是以上面的例子为例,明显第一种面向过程的方法思路更加清晰,但是如果下次烧另一顿饭呢?或者下次想先烧汤呢?或者我们下次用电饭锅煮汤呢?
我们可能需要修改整段代码来达到我们的目的。
如果是面向对象则要方便地多,比如我们下次用电饭锅煮汤,我们只需要重新调用封装在电饭锅类里的煮汤就行了。
回归到编程本身,我们知道结构化编程中程序=算法+数据结构,在实际编程过程中,面向过程只是一个“方法”或是“算法”,投入数据后根据特定过程得出结果。而面向对象不仅包含“过程”也包含“数据”,且以功能来划分问题,这意味着下次若是换一种问题,我们需要的只是把功能重新组合,而不是想面向过程一样重新设计。
那么面向过程就一无是处吗?
也不尽然,面向对象面临着需要先定义对象,在把对象实例化的过程,这意味着对于性能的开销将会提升。而对于面向过程来说,性能开销则会小的多。比如单片机、嵌入式开发、 Linux/Unix等性能是最重要的因素,一般采用面向过程开发。
总结一下:
面向对象
优点:易于维护,易于扩展,易于复用,同时由于面向对象的三大特性(封装、继承、多态性),使得通过面向对象开发的程序在维护和灵活性方面具有巨大的优势
缺点:性能开销大
面向过程
优点:性能开销较小
缺点:维护性、扩展性、复用性相对较差
大家在不同的场景下可以选择不同的开发思维。
最后配上一张梗图(笑)
二、面向对象的特性
三大基本特性:封装,继承,多态性
下面我将一一介绍
1.封装
封装从字面意思来说很好理解,我们平时把一堆东西打包时,也可以用“封装”这个词。
在面向对象编程方法中,封装(英语:Encapsulation)是指,一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。同时,它也是一种防止外界调用端,去访问对象内部实现细节的手段,这个手段是由编程语言本身来提供的。封装被视为是面向对象的四项原则之一。
适当的封装,可以将对象使用接口的程序实现部分隐藏起来,不让用户看到,同时确保用户无法任意更改对象内部的重要资料,若想接触资料只能通过**公开接入方法(Publicly accessible methods)**的方式( 如:“getters” 和"setters")。它可以让代码更容易理解与维护,也加强了代码的安全性。
简而言之就是,为了放置破坏对象内部的实现的方法,外部只能调取“公开接入方法”来执行对象。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。例如我们上面的例子中的:电饭锅.煮饭,为了防止我们破坏电饭锅的一些内部方法,我们只能通过煮饭这个方法执行这个对象,而他内部的(例如:煮饭的温度、时长?)我们无法接触到。
可见这种特性使得代码的安全性大大提高,同时在执行对象时也不需要考虑对象内部,使得代码可读性也提高。
2.继承
继承从字面意思也很好理解,我们的某些对象可以从一些对象中继承他的方法或者数据,使得编程的重复性降低。
继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类的行为在编译期就已经决定,无法在执行期扩展。
某些对象的方法可能与其他对象相似,例如我定义了一个人这个类,那么如果我要重新新建一个中国人这个类,很明显中国人可以像人一样拥有走路、思考等方法,那么中国人这个类就可以从人这个类继承,这样就避免我们重新写一些重复的方法。
那么人这个类就叫做中国人的“基类”、“父类”或“超类”;中国人就是人的“子类”或“派生类”。可见
同时我们也可以在中国人这个类里加上一些独特的数据或方法,例如中华文化等。可见,继承的过程,就是从一般到特殊的过程。
当然继承还有很多细节性问题,例如方法的重写、实现继承与接口继承,这些会在以后仔细讲述。
3.多态
有趣的是,“多态”是来自一个生物学概念:多态(Polymorphism)这个概念最早来自于生物学,表示的是同一物种在同一种群中存在两种或多种明显不同的表型。
在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口[1],或使用一个单一的符号来表示多个不同的类型[2]。
这一特性比较复杂,无法形象化描述,““多态”则是多样性、可扩展性的体现。面对丰富的和可能不断变化的问题域,让我们的程序能够有更大的容纳性去模拟和适应这些变化。它表达的是**’变化‘** ”。
三、C++类 & 对象
1.类定义
定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
以下这个例子定义了一个Box类。有长宽高三个属性,成员函数get()用于得到对象的体积,成员函数set()用于定义对象的三个属性
1 | class Box |
几点注意:
- 私有的成员(
private
)和受保护的成员(protect
)不能使用直接成员访问运算符 (.) 来直接访问 - 类的对象的公共数据成员可以使用直接成员访问运算符
.
来访问,例如Box a; printf("%d",a.length);
2.成员函数
成员函数可以简单理解为这个类的一个“方法”
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。
例如下面的getVolume()
就有如下两种定义方式:
1 | class Box |
以及:
1 | class Box |
3.类访问修饰符
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。关键字 public、private、protected 称为访问修饰符。
public修饰的方法和数据可以从外部访问,而private、protected不可以,至于private、protected有何区别,在下一章的“继承”中会说到。
看下面的例子:
1 |
|
输出:
1 | Box1 的体积:210 |