PL真有意思(七):数据抽象和面向对象

前言

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

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

面向对象程序设计

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

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

可是很显然,第三点在实践中却很难作到。由于或许咱们有一个原先作好的模块几乎具备当前某个新应用所需的所有性质,可是并不彻底适用。假如咱们有一个队列抽象,可是须要的倒是可以在两端插入删除,那么就不彻底符合了安全

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

封装和继承

封装机使程序员能够将数据和操做它们的子程序组织在一块儿,对抽象的用户隐藏起各类可有可无的实现细节。设计

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

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

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

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

  • 若是一个派生类经过将基类声明的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#则只提供了一种受限的多重继承方式。

总结

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

相关文章
相关标签/搜索