面向过程与面向对象的区别

电子说

1.2w人已加入

描述

说起面向对象,大家可能会想到其他的,但是此对象非彼对象哈哈,不必惊慌,也特别好搞定。

在听到面向对象之前大家也一定会接触学习到面向过程相关的,那么它们两个之间到底是有什么关系呢,我先给咱唠唠。

面向过程与面向对象的区别

面向过程—步骤化

当软件规模超过一定的尺度后,采用结构化程序设计,其开发和维护就越来越难控制。其根本原因就在于面向过程的结构化程序设计的方法与现实世界往往都不一致,结构化程序设计的思想往往很难贯彻到底。

在结构化程序设计中,采用的是“ 自顶向下,逐步细化 ”的思想。具体操作方法是模块化,是按功能来分的,所以也称功能块。在C++中称为一个函数,一个函数解决一个问题,即实现一个功能或一个操作。

在模块化的思想中已经出现了封装的概念,这个封装是把数据封装到模块中,即局部变量。但这是很不彻底的,因为模块是功能的抽象,而数据则是具有其个性的,一旦发生哪怕是一点变化,抽象的功能模块就不再适用了。可维护性差成了制约机构化程序设计应用的瓶颈。

总的来说,面向过程就是分析出实现需求所需要的步骤,通过函数一步一步实现这些步骤,接着依次调用即可。

面向对象—行为化

面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成类(类实例化后才是对象),创建了对象不是为了完成某一个步骤,而是描述某个事务在解决问题的步骤中的行为。

对象是面向对象技术的核心所在,面向对象技术中的对象就是现实世界中某个具体的物理实体在计算机逻辑中的映射和体现。

编程

面向过程和面向对象的优缺点及用途

编程

总结:

  • 类是一组相关的 属性(变量)行为(方法) 的集合。是一个抽象概念设计的产物。
  • 对象是该类事物的具体表现形式。具体存在的实体。
  • 成员变量是对象的属性(可以使变量、指针、数组等),属性的值确定对象的状态。
  • 成员函数是对象的方法,确定对象的行为。

状态和行为是对象的主要属性

对象的状态又称为对象的静态属性,主要指对象内部所包含的各种信息,也就是变量。每个对象个体都有自己专有的内部变量,这些变量的值标明了对象所处的状态。

对象的方法(行为)一方面把对象的内部变量包裹,封装,保护起来,使得只有对象自己的方法才能操作这些内部变量,另一方面,对象的方法还是对象与外部环境和其他对象交互,通信的接口,对象的环境和其他对象可以通过这个接口来调用对象的方法,操纵对象的行为和改变对象的状态。

对象是现实世界的实体或概念在计算机逻辑中的抽象表示。具体地,对象是具有唯一对象名和固定对外界接口的一组属性和操作的集合,用来模拟或影响现实世界问题的一个或一组因素。

相对于传统的面向过程的程序设计方法,而面向对象的程序设计具有的优点

对象的数据封装特性彻底消除了传统结构方法中数据与操作分离所带来的种种问题,提高了程序的可复性和可维护性,降低了程序员保持数据与操作相容的负担。

对象的数据封装特性还可以把对象的私有数据和公共数据分离开,保护了私有数据,减少了可能的模块间干扰,达到降低程序复杂性,提高可控性的目的。

对象作为独立的整体具有良好的自恰性,即它可以通过自身定义的操作来管理自己。一个对象的操作可以完成两类功能,一是修改自身的状态,二是向外界发布消息。

在具有自恰性的同时,对象通过一定的接口和相应的消息机制与外界联系。这个特性与对象的封装性结合在一起,较好地实现了信息的隐藏。

通过继承可以很方便地实现应用的扩展和已有代码的重复使用。

总结:

面向对象程序设计是将数据及数据的操作封装在一起,成为一个不可分割的整体,同时将具有相同特性的实体抽象成为一种新的数据类型—类。通过对象间的消息传递是整个系统运转。

类和对象关系和理解

类:一组相关的属性和行为的集合,是一个抽象的概念。

对象:该类事务的具体表现形式,具体存在的个体。

上面这几个概念,应该怎么理解的呢?

类就是对一些具有共性特征,并且行为相似的个体的描述。

比如小杨和小秦都有姓名、年龄、身高、体重等一些属性,并且两人都能够进行聊天、运动等相似的行为。

由于这两个人具有这些共性的地方,所以我们就把它抽象出来,定义为一个类—人类,而小杨、小秦正是这个类中的个体,而每一个个体才是真正的具体的存在,光提到人类,你只知道应该有哪些属性行为,但你不知道他具体的一些属性值。比如你知道他属于“人类”,所以他应该拥有姓名,年龄等属性,但你并不知道他具体叫什么,年龄多大了。而小杨和小秦这两个具体的对象,却能够实实在在的知道小杨今年23岁了,体重80kg等值。

总结:

类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而根据类实例化具体的对象,就需要占用内存空间了。

面向对象的三大主要特征

封装

常见的两层含义 :一是把对象的属性和行为看成一个密不可分的整体,将这两者“封装”在一个不可分割的独立单元中;另一个是指“ 信息隐藏 ”,把不需要让外界知道的信息隐藏起来,有些对象的属性及行为允许外界用户知道或使用,但不允许更改,而另一些属性或行为,则不允许外界知晓,或只允许使用对象的功能,而尽可能隐藏对象的功能实现细节。

封装的好处:

  • 可以对成员变量进行更精确的控制。
  • 类内部的结构可以自由修改。
  • 好的封装能够减少耦合,符合程序设计“高内聚,低耦合”。
  • 隐藏实现细节。
  • 提供公共的访问方式。
  • 提高代码的复用性。
  • 提高安全性。

继承

继承时面向对象编程技术的一块基石,因为它允许创建分等级层次的类。

继承就是在一个已有类的基础上派生出新类(例如动物类可以派生出狗类和猫类),子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

继承的好处:

  • 提高代码的维护性。
  • 提高类代码的复用性。
  • 使得类和类产生了关系,是多态的前提(是继承的一个弊端,让类的耦合性增强了)。

**继承概念的实现方式有三类:实现继承、接口继承、可视继承

**

实现继承:使用基类的属性和方法而无序额外编码的能力。

接口继承:仅使用属性和方法的名称,但是子类必须提供实现的能力。

可视继承:子窗体使用基窗体的外观和实现代码的能力。

继承的特点:

  • 子类拥有父类非private的属性,方法。
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法,即重写父类方法。
  • 提高了类之间的耦合性。

访问权限修饰符public、 protected 、private:

编程

多态

多态是同一个行为具有多个不同表现形式或形态的能力,例如:黑白打印机和彩色打印机相同的打印行为却有着不同的打印效果。

  • 对象类型和引用类型之间存在着继承(类)/ 实现(接口)的关系。
  • 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
  • 如果子类重写了父类的方法,最终执行的是子类覆盖的方法,如果没有则执行的是父类的方法。

多态的实现: 静态多态和动态多态

静态多态:通过函数重载和模板来实现,是在编译期间确定,也称早起绑定。

动态多态:通过虚函数和继承来实现,是在运行期确定,也称晚期绑定(动态绑定)。

动态多态的实现:

当编译器发现类中有虚函数时,会创建一张虚函数表,把虚函数的函数入口地址放到虚函数表中,并且在对象中增加一个指针vptr,用于指向类的虚函数表。当派生类覆盖基类的虚函数时,会将虚函数表中对应的指针进行替换,从而调用派生类中覆盖后的虚函数,从而实现动态绑定。

虚表:虚表中放置的是虚函数的入口地址,如果能拿到虚表中的内容,即拿到了虚函数的入口地址,则可以将该函数调用起来。

基类虚表构建过程 :编译期间在编译期间,按照虚函数在类中声明的先后次序依次添加到虚表中。

派生类虚表的构建过程

  1. 将基类虚表中内容拷贝一份放置到子类虚表中。
  2. 如果子类重写了基类的某个虚函数,则使用子类自己的虚函数替换相同偏移量位置的基类虚函数地址。
  3. 对于子类新增的虚函数,按照在类中声明次序,依次放在虚表的后面。

函数重载、重写、同名隐藏的对比:

函数重载:两个或多个函数在同一作用域,函数名相同、参数列表不同。

重写:两个函数分别在基类和派生类的作用域中,函数名、参数、返回值类型都必须相同,基类虚函数必须为虚函数,派生类函数最好也为虚函数。

同名隐藏:两个函数分别在基类和派生类的作用域中,函数名相同,基类和派生类同名函数不是重写就是同名隐藏。

多态的优点:

消除类型之间的耦合关系、可替换性、可扩充性、接口性、灵活性、简化性。

动态多态的作用:

  • 隐藏实现细节,使代码模块化,提高代码的可复用性。
  • 接口重用,使派生类的功能可以被基类的指针/引用所调用,即向后兼容,提高代码的可扩充性和可维护性。

动态多态的必****要条件:继承、虚函数覆盖、基类指针/引用指向子类对象。

总之,C++多态的核心,就是用一个更通用的基类指针指向不同的子类实例,为了能调用正确的方法,我们需要用到虚函数和虚继承。在内存中,通过虚函数表来实现子类方法的正确调用;通过虚基类指针,仅保留一份基类的内存结构,避免冲突。

面向对象的五大原则

** 单一职责原则SRP** (Single Responsibility Principle):是指 一个类的功能要单一,不能包罗万象 。如同一个人一样,分配的工作不能太多,否则一天到晚虽然忙忙碌碌的,但效率却高不起来。

**  开放封闭原则OCP** (Open-Close Principle):一 个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的 。比如:一个网络模块,原来只服务端功能,而现在要加入客户端功能,那么应当在不用修改服务端功能代码的前提下,就能够增加客户端功能的实现代码,这要求在设计之初,就应当将服务端和客户端分开,公共部分抽象出来。

** 里式替换原则LSP** (the Liskov Substitution Principle LSP): 子类应当可以替换父类并出现在父类能够出现的任何地方 。比如:公司搞年度晚会,所有员工可以参加抽奖,那么不管是老员工还是新员工,也不管是总部员工还是外派员工,都应当可以参加抽奖,否则这公司就不和谐了。

**  依赖倒置原则DIP** (the Dependency Inversion Principle DIP): 高层模块(稳定)不应该依赖低层模块(变换)都应该依赖于抽象(稳定)。 抽象(稳定)不应该依赖于实现细节(变化),实现细节(变化)应该依赖于抽象(稳定) 。假设B是较A低的模块,但B需要使用到A的功能,这个时候,B不应当直接使用A中的具体类:而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口:这样就达到了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能 造成循环依赖。一个常见的问题就是编译A模块时需要直接包含到B模块的cpp文件,而编译B时同样要直接包含到A的cpp文件。

**  接口分离原则ISP** (the Interface Segregation Principle ISP): 模块间要通过抽象接口隔离开 ,而不是通过具体的类强耦合起来。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分