前言

在之前的名字、作用域那篇提到模块类型,它使程序员可以从一个给定抽象出发,通过实例化产生多个实例;再后面是类,它使程序员可以定义一族相关的抽象。

在这一篇里,我们会来看一下面向对象程序设计及其三个基本概念、动态方法约束、多重继承等等

面向对象程序设计

随着软件变得越来越复杂,数据抽象已经变成了软件工程中最重要的部分。由模块和模块类型提供的这种抽象至少带来了如下三个好处:

  • 它可以减少程序员必须同时考虑的细节量,减少了人的概念负担
  • 它起到一种故障遏制作用,可以防止程序员以不适当的方式使用程序的各种部件。也限制了查找程序错误必须考虑的程序的部分
  • 它为程序部件之间的独立性提供了一个重要的层次,使得将程序的构造分配到各个单独的部分更加容易,在修改部件的内部实现就可以避免改动使用它们的外部代码

但是很显然,第三点在实践中却很难做到。因为或许我们有一个原先做好的模块几乎具有当前某个新应用所需的全部性质,但是并不完全适用。假如我们有一个队列抽象,但是需要的却是能够在两端插入删除,那么就不完全符合了

面向对象的程序设计可以看作是这个方向上的一种努力,它使我们能够更容易的扩展或精化现有抽象的方式定义新抽象,也就是继承。

封装和继承

封装机使程序员可以将数据和操作它们的子程序组织在一起,对抽象的用户隐藏起各种无关紧要的实现细节。

随着继承的引入,面向对象语言不但要支持基于模块的语言的作用域规则,还需要处理另外一些问题,比如基类中的私有成员对于派生类的方法应该是可见的吗?基类中的公用成员在派生类中也总是公用的吗?

在C++中可见性规则背后的基本原则可以总结如下

  • 任何一个类都可以限制其成员的可见性。只要该类声明在作用域中,其公用成员就都是可见的。私用成员只在本类的方法中可见。保护成员在本类及其派生类中可见

  • 派生类可以显式其基类成员的可见性,但是不能提升它们的可见性。基类私用成员在派生类中根本不可见。公用基类的保护成员和公用成员,在派生类中仍然分别是保护的和公用的成员。

  • 如果一个派生类通过将基类声明的protected或private而限制了基类成员的可见性,那么在这个派生类中还可以一个一个恢复基类成员的可见性。

嵌套类

许多语言都允许类声明的嵌套。这带来了一个直接的问题:如果Inner是Outer的成员,那么Inner的方法能够看到Outer的成员吗?

在C++和C#中,只允许访问外层类的静态成员。而Java中则更复杂,它允许嵌套类访问外层类的任意成员。因此内层类的每个实例都必须属于外层类的一个实例。

初始化和终结处理

我们将对象的生存期定义为它占据空间并因此能保存数据的那段时间。大多数面向对象的语言都提供了某种特殊机制,用以在对象的生存期开始时自动做初始化。也有几种语言提供了类似的析构函数机制,用于在对象生存期结束时自动来终结它。

构造函数的选择

C++、Java和C#都允许程序为一个类指定多个构造函数。多个构造函数的行为方式就像是重载的子程序,必须能根据其参数的个数和类型区分它们。

执行顺序

C++强调每个对象都要在使用前初始化,进一步说,如果该对象的类派生自另一个类,C++强调必须在调用派生类的构造函数之前调用基类的构造函数。以保证派生类不会看到它所继承的域处于不一致的状态。

在Java中

super(args);

super关键字用于引用当前类的基类。如果没有这种对super的调用,Java编译器就会自动插入一个对基类的无参构造函数的调用。

废料收集

当一个C++对象被销毁时,首先会调用它所在派生类的析构函数,然后按照与派生类相反的顺序调用各基类的析构函数。在C++中,析构函数最常见的用途就是手工释放存储空间。

但是现在的许多语言都提供了自动废料收集

动态方法约束

假设我们现在有三个类:

class Persopn {}
class Student : public Persion {}
class Professor : public Persion {} Student s;
Professor p; Persopn *x = &s;
Persopn *y = &p

现在假设三个类都有一个print_mailing_label方法,那么对x,y调用这个方法将会调用的是基类的Persion的方法,还是根据现在变量引用的s、p的类型来做选择呢?

第一种选择是静态方法约束,而第二种方法是动态方法约束。动态方法约束是面向对象程序设计的核心概念

虚方法和非虚方法

C++和C#默认使用静态方法约束,但是程序员可以将特定的方法标记为virtual,要求对它使用动态约束。对虚方法的调用将在运行时根据对象的类而不是引用的类型指派适当的方法实现

抽象类

在大多数面向对象语言中,基类中都可以不给出virtual方法的体。在Java和C#中,做这件事的方法是将类或没有体的方法都标记为abstract

无论用什么语法形式,如果一个类中包含了至少一个抽象方法,这个类就称为是抽象的。我们不能够声明抽象类的对象

成员查找

对于静态方法约束,编译器总可以基于所引用的变量的类型确定应该调用相应的方法的哪个版本。然而,对于动态约束,被引用或指针变量所引用的对象中就必须包含足够的信息,使编译器生成的代码能够在运行时找到正确的方法版本。

最常见的实现方式是用记录的形式表示每个对象,这种记录中第一个域是一个指针,指向该对象的类的虚方法表。虚表也就是一个数组,其中的第i个项指明该对象的第i个虚方法的代码地址。同一个类的所有对象共享同一个虚表。

多态性

动态方法约束将多态性引入到期望某个基类foo的对象引用的所有代码中。只要派生类的对象支持这个基类的操作,这些代码对于基类的任何派生类的对象都可以很好的工作。

有人可能会认为,有了继承和动态方法约束后就不再需要泛型了,但实际情况并非如此,为了访问这些派生类的特殊内容,就必须进行强制转换,并且得到的代码仍然是不安全的,但是泛型能够解决这些问题

多重继承

有些时候,让一个派生类继承多个基类的特征也是非常有用的。例如我们需要一个学生类,又希望能够方便进行增加删除,那么就可能希望从Person类和链表类派生出一个类来。

C++和Python都有多重继承。Java、C#则只提供了一种受限的多重继承方式。

总结

在这一篇的一开始我们指出了面向对象程序设计的三大基本概念:封装、继承和多态。在之后我们讨论了对象的初始化和终结操作、动态方法约束和多重继承。

PL真有意思(七):数据抽象和面向对象的更多相关文章

  1. PL真有意思(三):名字、作用域和约束

    前言 这两篇写了词法分析和语法分析,比较偏向实践.这一篇来看一下语言设计里一个比较重要的部分:名字.在大部分语言里,名字就是标识符,如果从抽象层面来看名字就是对更低一级的内存之类的概念的一层抽象.但是 ...

  2. PL真有意思(四):控制流

    前言 对大多数计算模型而言,顺序都是基本的东西,它确定了为完成所期望的某种工作,什么事情应该最先做,什么事应该随后做,我们可以将语言规定顺序的机制分为几个类别: 顺序执行 选择 迭代 过程抽象 递归 ...

  3. PL真有意思(一):引言

    前言 断断续续学编译原理到之前发过写一个编译器和正则表达式引擎系列文章也有一段时间了,然后最近看完PLP这本书,这本书应该算是入门书,但是对我这种半吊子收获很大.所以为了弥补最近学操作系统和接外包摸的 ...

  4. PL真有意思(五):数据类型

    前言 现在大多数程序设计语言中都有表达式和/或对象的类型概念.类型起着两种主要作用: 为许多操作提供了隐含的上下文信息,使程序员可以在许多情况下不必显示的描述这种上下文.比如int类型的两个对象相加就 ...

  5. PL真有意思(六):子程序和控制抽象

    前言 在之前我们把抽象定义为一种过程,程序员可以通过它将一个名字与一段可能很复杂的程序片段关联起来.抽象最大的意义就在于,我们可以从功能和用途的角度来考虑它,而不是实现. 在大多数程序设计语言中,子程 ...

  6. PL真有意思(二):程序设计语言语法

    前言 虽然标题是程序语言的语法,但是讲的是对词法和语法的解析,其实关于这个前面那个写编译器系列的描述会更清楚,有关语言语法的部分应该是穿插在整个设计当中的,也看语言设计者的心情了 和英语汉语这些自然语 ...

  7. PL真有意思(八):其它程序设计模型

    前言 在之前几篇我们讨论的语法.语义.命名.类型和抽象适用于所有语言.然而我们的注意力都主要集中在命令式语言上,现在这篇来看看其它范式的语言.函数式和逻辑式语言是最主要的非命令式语言. 函数式语言 命 ...

  8. 简学Python第七章__class面向对象高级用法与反射

    Python第七章__class面向对象高级用法与反射 欢迎加入Linux_Python学习群  群号:478616847 目录: Python中关于oop的常用术语 类的特殊方法 元类 反射 一.P ...

  9. 【python自动化第七篇:面向对象进阶】

    知识点概览: 静态方法,类方法,属性方法 类的特殊方法 反射 异常处理 socket开发基础 一.静态方法:@staticmethod 只是名义上归类管理,实际上在静态方法里访问不了类或者实例中的任何 ...

随机推荐

  1. Redis备忘(二)

    内存回收: 有时候发现10g的Redis删掉1g的key,内存占用没啥变化,因为内存页分配,有的页面可能还存在key,整个页面不能回收. 主从同步: CAP原理:一致性 可用性 分区容忍性 redis ...

  2. TCP/IP和Socket开发经验分享

    当前与网络相关的业务主要是基于tcp/ip或http,熟悉j2ee的同学一定会对http场景下的开发比较了解.但是,精通tcp/ip以及如何构建一个直接基于tcp/ip层通讯的知识却不太多见.恰巧,最 ...

  3. 过滤器、拦截器和AOP的分析与对比

    目录 一.过滤器(Filter) 1.1 简介 1.2 应用场景 1.3 源码分析 二.拦截器(Interceptor) 2.1 简介 2.2 应用场景 2.2 源码分析 三.面向切面编程(AOP) ...

  4. Java对象"后事处理"那点事儿——垃圾回收(一)

    1.Dead Or Alive 我们都知道对象死亡的时候需要进行垃圾回收来回收这些对象从而释放空间,那么什么样的对象算是死亡呢,有哪些方法可以找出内存中的死亡对象呢?一般来说,我们可以这样认为:如果内 ...

  5. 前端技术之:JS开发几个有意思的东东

    一. 查看性能分析报告 npm run build:prod --report ​ 二.vue ui工具 ​ ​ 三.vue-element-admin https://panjiachen.gite ...

  6. mock和axios常见的传参方式

    第一次接手项目,传参方式还有些吃力,因此做一下总结. 首先我们需要会看swagger中的接口.里面写了某个接口需要接收什么样的值,前端怎么传递这个值 在mock中的传参方式: mock中传参的方式有两 ...

  7. API gateway 之 kong 基本操作 (三)

    一.演示环境准备 1.nginx配置 [root@nginx conf.d]# pwd /etc/nginx/conf.d [root@nginx conf.d]# ls conf_bak kong_ ...

  8. NOIP原题 斗地主(20190804)

    题目描述 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的A到K加上大小王的共54张牌来进行的扑克牌游戏.在斗地主中,牌的大小关 系根据牌的数码表示如下:3<4&l ...

  9. Nginx 的进程结构,你明白吗?

    Nginx 进程结构 这篇文章我们来看下 Nginx 的进程结构,Nginx 其实有两种进程结构: 单进程结构 多进程结构 单进程结构实际上不适用于生产环境,只适合我们做开发调试使用.因为在生产环境中 ...

  10. linux下 驱动模块编译步骤

    本文将直接了当的带你进入linux的模块编译.当然在介绍的过程当中,我也会添加一些必要的注释,以便初学者能够看懂.之所以要写这篇文章,主要是因为从书本上学的话,可能要花更长的时间才能学会整个过程,因为 ...