• 201920201学期 20192406 《网络空间安全专业导论》第四周学习总结


    第八章 抽象数据类型与子程序

    8.1 抽象数据类型

    • 抽象数据类型:属性(数据和操作)明确地与特定实现分离的容器
    • 数据结构:一种抽象数据类型中的复合数据域的实现
    • 容器:存放和操作其他对象的对象

     栈

    栈和队列是抽象复合结构,二者经常被同时提及。
    栈是一种抽象复合结构,只能从一端访问栈中的元素。可以在第一个位置插入元素,也可以删除第一个元素。 这种设计模拟了日常生活中的很多事情。会计师称它为LIFO,即后进先出(Last In First Out)的缩写。
    另一种描述栈的访问行为的说法是删除的项总是在栈中时间最短的项目。从这个角度观察栈就更加抽象。插入操作没有任何约束;整个LIFO行为都体现在删除操作上。
    把栈比作自助餐厅的餐具架,使它的插人和删除操作有了个惯用语,插入操作叫作Push (推进),删除操作叫作Pop (弹出)。我们把项目推进栈,从栈中弹出项目。栈没有长度属性,所以没有返回栈中项目个数的操作。我们需要的是确定栈是否为空(Is Empty)的操作,因为当栈空的时候再弹出项目会出错。

    8.3 队列

    队列也是种抽象结构,队列中的项目从一端入,从另一端出。会计师称之为FIFO,即先进先出( First In First Out)的缩写。事实上,队列就是用来模拟这种情况的。插人操作在队列的rear(尾部)进行,删除操作在对列的front(头部)进行。
    另一种描述队列的访问行为的说法是删除的 总是在队列中时间最长的项目。从这个角度观察队列就更加抽象。与栈一样,插入操作没有任何约束;整个FIFO行为都体现在删除操作上。遗憾的是,插人和删除操作没有标准的相关术语。Enqueue、 Enque、 Enq、 Enter 和Insert都可以表示插人操作。Dequeue 、Deque、Deq、Delete和Remove都可以表示删除操作。

    8.4 列表

    列表在程序中与在现实生活中一样很自然地出现了。列表的列是无止境的。列表有三个属性特征:项目是同构的,项目是线性的,列表是变长的。线性的意思是,每个项目除了第一个都有一个独特的组成部分在它之前,除了最后一个也都有一个独特的组成部分在它之后。例如,如果一个列表中至少有三个项目,则第二项在第一项之后,在第三项之前。
    而栈和队列对于删除操作都有全部的定义,列表通常提供插人一个项目的操作( Insert)、删除一个项目的操作(Delete)、检索一个项目是否存在(IsThere)以及报告列表中项目数量( GetLength)。此外,它们有一些机制允许用户查看序列中的每一项(Reset, GetNext, Moreltems)。 因为项目可以被删除和检索,所以列表中的项目必须能够相互比较。
    不要把列表误认为是数组,数组是内嵌结构,列表是抽象结构。然而列表应用于数组中。
    列表也可以被形象化为链式结构,链式结构以节点的概念为基础。一个节点由两部分构成:用户的数据和指向列表的下一个节点的链接或指针。列表的最后的一个节点的指针变量存放的是表示列表结束的符号,通常为null,用“/”表示。

    • 链式结构:一个将数据项和找到下一项位置的信息保存到同一容器的实现方法。

    无序列表的顺序并不重要,项目只是随意被放入其中。有序列表中,项目之间具有语义关系。除了第一个项目之外所有项目都存在某种排序关系。除了最后一个项目,所有项目都有着相同的关系。
    我们使用Reset、MoreItems 和GetNext遍历该列表,返回序列中的每一项。如果是一个无序列表,项目将按它们插人的顺序打印。如果是有序列表,项目将被有序地打印出来。这个算法不用考虑列表的实现方式。

    8.5 树

    像列表、栈和队列这样的抽象结构本质上都是线性的,只模拟了一种数据关系。列表中的项目一个挨着一个,栈和队列中的项目从时间上来说也是一个挨着一个的。更复杂的关系需要更复杂的结构来表示,如家族关系。
    这种分层体系结构叫作树。关于树有大量的数学理论,但在计算领域,我们所说的通常是二叉树,即每个节点最多有两个子节点的树。

    8.5.1 二叉树

    二叉树是一种抽象结构,其中每个节点可以有两个后继节点,叫作子女。每个子女仍然是二叉树的节点,因此也可以有两个子节点,而这些子女又可以有自己的子女,依此类推,这就形成了树的分支结构。树的头部是一个起始节点,叫作根,它不是任何节点的子女。
    树中的每个节点可以有0个、1个或2个子女。如果一个节点左边的子节点存在,则这个子节点叫作左子女。如果一个节点右边的子节点存在,这个子节点便叫作右子女。如果一个节点只有一个子女,则这个子女可以位于任何一边,不过它一定会位于某一边。如果一个节点没有子女,则这个节点叫作树叶。例如,存放值7、8、9和10的节点就是叶节点。

    • 二叉树( binary tree):具有唯一起始节点(根节点)的抽象复合结构,其中每个节点可以有两个子女节点,根节点和每个节点之间都有且只有一条路径。

    • 根(root):树中唯一的开始节点。

    • 叶节点(leaf node):没有子女的树节点。
      除了规定每个节点至多有两个子女外,二叉树的定义还说明了,根节点和每个节点之间有且只有一个路径。这就是说,除了根外,每个节点都有只一个父母节点。

    8.5.2 二叉检索树

    树类似于一个无序列表。要在树上找到一个项目,我们必须检查每个节点,直到找到想要的那个,或者发现它并不在树上。二叉检索树就像已排序的列表,节点间存在语义排序。
    二叉检索树具有二叉树的形状属性,也就是说,二叉检索树中的节点可以具有0个、1个或2个子女。此外,二叉检索树还具有语义属性来刻画树中节点上的值,即任何节点的值都要大于它的左子树中的所有节点的值,并且要小于它的右子树中的所有节点的值。

    在二叉检索树中搜素

    让我们在图8-7所示的树中搜索值18。首先比较18和根节点的值15。18大于15,因此可以知道,如果18在这个树中,那么它定在根的右子树中。注意这种搜索法与线性结构的二分检索法之间的相似性。在线性结构中,通过一次比较操作,就排除了很大一部分数据。
    接下来比较18和右子树的根的值17。18大于17,因而可以知道,如果18在这个树中,那么它一定在根的右子树中。比较18和右子树的根的值19。18 小于19,因而可以知道,如果18在这个树中,那么它一定在根的左子树中。比较18和左子树的根的值18,这样就找到了匹配的值。
    下面来看看要搜索的值不在树中的情况。让我们查找值4。首先比较4和15。4小于15,因此,如果4在这个树中,它一定在根的左子树中。比较4和左子树的根的值7。4小于7,因此,如果4在这个树中,它一定在7的左子树中。比较4和5。4小于5,因此,如果4在这个树中,它一定在5的左子树中。比较4和1。4大于1,因此,如果4在这个树中,它一定在1的右子树中。但1的右子树是空的,因而可知4不在这个树中。

    如果current指向一个节点,那么info ( current)指的就是这个节点中的用户数据,left( current)指向的是current的左子树的根节点,right ( current)指向的是current的右子树的根节点。null是一个特殊值,说明指针指向空值。因此,如果一个指针是null,那么这个子树就是空的。

    利用这些符号,就可以编写搜索算法了。我们从树的根节点开始,沿着根的后继子树前进,直到找到了要找的项目或发现一个空子树为止。该算法的参数是要搜索的项目和树(子树)的根节点,这些都是子算法执行时所需的信息。

    每次比较操作,不是找到了要搜索的项目,就是把树减小了一半。当然,说一-半并不精确。二叉树的形状并不总是平衡的。显然,二叉检索树的搜索效率与树的形状有直接关系。树的形状是如何形成的呢?树的形状是由项目插入树的顺序决定的。如图8-8所示,在a部分中,四级树是比较平衡的。节点可以以几个不同的顺序输入以形成这棵树。相比之下,b部分的十级树只有值按顺序进人才可能形成。

    构造二叉检索树

    我们刚使用过的搜索算法为如何构造二叉检索树提供了线索。如果在搜索路径中没有找到要找的项目,那么最后达到的就是这个项目应该在的位置。下面用字符串john、phil、lila、kate、becca、 judy、 june、 mari、 jim 和sue构造- 一个二叉检索树。
    因为john是第一一个插入的值,所以它是根节点。第二个值phil大于john,因此它将成为右子树的根节点。lila 大于john,但小于phil,因此它将成为phil的左子树的根节点。此时该树如下图所示。
    kate大于john,小于phil和lila,因此kate将成为lila的左子树的根节点。becca小于john,因此它将成为john的左子树的根节点。judy 大于john,小于phil、lila和kate,因此judy将成为kate的左子树的根节点。june的路径与judy一样。june 大于judy,因此它将成为judy的右子树的根节点。mari 将成为lila的右子树的根; jim将成为becca的右子树的根; sue 将成为phil的右子树的根。整个树如图8-9所示。

    输出二叉检索树中的数据

    要输出根的值,必须先输出它的左子树中的所有值,即所有比根的值小的值。输出了根的值后,还必须输出它的右子树中的所有值,即所有比根的值大的值。这样就结束了吗?那左子树和右子树中的值怎么办?如何输出它们?当然是采用相同的方法。毕竟,它们都是二叉检索树。

    8.5.3 其他操作

    现在你应该意识到了,二叉检索树其实是和列表具有同样功能的对象,它们的区别在于操作的有效性,而行为是相同的。我们没有介绍Remove算法,因为它对于本书来说太复杂了。此外,我们还忽略了length的概念,它在用于实现列表时必然与树相伴。与其在构造树的时候记录其中的项目个数,不如编写一个算法,计算树中的节点数。

    一个空树中有多少个节点?当然是0个。那么任意一个树有多少个节点呢? 就是1加上左子树中的节点个数和右子树中的节点个数。树的定义引出了length操作的递归定义。

    8.6 图

    • 图(graph):由一-组节点和一-组把节点相互连接起来的边构成的数据结构。
    • 顶点(vertex):图中的节点。
    • 边(弧)(edge (arc)):表示图中两个节点的连接的顶点对。
    • 无向图(undirected graph):其中的边没有方向的图。
    • 有向图(directed graph (digraph)): 其中的边是从一个顶点指向另一个顶点(或同一个顶点)的图。
    • 邻顶点:通过边连接起来的两个顶点
    • 路径:连接图中两个顶点的一系列顶点

    8.6.1 创建图

    列表、栈、队列和树都是可容纳元素的容器。用户根据特定的问题选择最合适的容器。不被纳人检索过程的算法是没有固定语义的:栈返回的元素是在其中停留时间最少的元素;队列返回的是在其中停留时间最长的元素。队列和树返回的信息都是被请求的。然而不同的是,在图中定义的算法可以解决实际的问题。首先我们来探讨创建一个图, 然后讨论利用图可以解决的问题。

    许多信息可以被呈现在图上:顶点、边和权值。让我们利用航班连接数据来把这些结构可视化为一张表。表8-3中的行和列标有城市的名字。单元格中的零代表从该行城市到该列城市不存在可以直达的航班。表格中的数值为从该行城市至该列城市的公里数。

    创建一一个表格需要以下操作:

    • 在表格中添加一个顶点
    • 在表格中添加一条边
    • 在表格中添加一个权值

    我们在表格中通过行名和列名来寻找位置。例如(Atlanta, Houston) 间有一 条800公里的航线。(Houston, Austin) 间为零,即没有从Houston直飞Austin的航线。

    8.6.2 图算法

    这里有三种经典的图搜索算法,每一种可以解决不同的问题。

    • 我能否搭乘喜爱的航线从城市x前往城市Y?
    • 我怎样能用最少的停顿从城市X前往城市Y ?
    • 从城市X到城市Y最短的航程(公里数)是什么?

    这三个问题的答案包括了深度优先搜索、广度优先搜索和单源最短路搜索。

    深度优先搜索

    怎样能够搭乘喜爱的航线从城市X前往城市Y?给定一个起点和一个终点,我们来构造一种从起点( start Vertex)到终点( end Vertex)的路径的算法。我们需要一个系统化的方法来调查并跟踪这些城市。当我们试图在两个顶点间寻找路径时,用栈来储存访问的顶点。用深度优先搜索来检查第一个与起点相邻的顶点。如果是它是终点,则搜索结束。否则,检查所有与第一一个顶点相邻的顶点。

    同时,我们需要存储其他和起点相邻的顶点,随后需要的时候会用到它们。如果不存在一条从与起点相邻的第一个顶点出发的路径,那么我们回到顶点,尝试第二个顶点、第三个顶点,以此类推。因为我们想要沿着一条路径尽可能深地访问各个节点,如果没有找到终点则回溯,因此栈是种存储顶点的合适的数据结构。

    一旦我们把一个顶点的所有相邻顶点都放到栈内,就标记这个顶点为访问过。 如果我们处理了一个已经访问过的顶点,将会把一个同样的顶点一次又一次地放人栈内。那么这个算法根本不能称之为算法,因为它可能无法终止。所以我们不能多次处理同一个顶点。

    这种搜索叫作深度优先搜索,因为我们走向最深的分支。在返回Dallas继续搜索前,要检查从Houston开始的所有路径。当你必须回溯时,选择离你无法走通位置的最近的分支继续搜索。也就是说,相比于更早时候可选的其他分支,你会选择一条尽可能可以走远的路。

    广度优先搜索

    怎样用最少的停顿次数从城市x前往城市Y?广度优先遍历可以解决这个问题。在深度优先搜索中,当来到一个无法继续走通的位置,我们尽可能少回溯。我们尝试从距此顶点最近的路径开始,也就是那些在栈顶的路径。在广度优先搜索中,我们想要回溯到尽可能远,以找到从最初的顶点出发的路径。因此栈不再是一个适合寻找较早路径的数据结构。它是按照元素出现的相反顺序来保存元素,即最晚的路径在顶部。我们采用队列来保存元素的顺序可以按照它们出现的先后顺序。在队列前部的路径来自较早出现的顶点,而在队列后部的路径则来自较晚出现的顶点。因此,如果我们将栈替换为队列,就可以为这个问题找到答案。

    正如你所看到的,深度优先搜索算法从起点出发尽可能地往更远的路径检查,而不是优先选择检查与起点相邻的第二个顶点。相反,广度优先搜索会优先检查所有与起点相邻的顶点,而不是检查与这些顶点相连的其他顶点。

    单源最短路搜索

    什么是从Austin飞往某个其他城市的最短航线(公里数) ?在刚刚的讨论中,存在多条路径可以从某个顶点前往另一个。假设我们想要找到从Austn出发乘坐喜爱的航线到其他城市的最短路径。这里的“最短路径”指的是将路径上的边的值(权值)加在一起得到的和最小。

    让我们来构造一种可以显示从一个设计好的起点城市到任意一个城市的最短路算法。这次我们不会搜索一个起点城市和一个终点城市间的路径。像前两种图搜索所描述的一样,我们需要一个辅助的数据结构来存储此后处理的城市。通过检索最常被放人此结构的城市,深度优先搜索尝试不断向“前”,它尝试了直飞的方式、两程航班、三程航班等方式。它只在遇到无法继续走通的时候,回溯到较少次数的航班。通过检查在数据结构中停留最长时间的城市,广度优先搜索尝试了所有直飞、两程航班、三程航班等方式。广度优先搜索算法可以找到最小换乘次数的航线。

    当然,最小行程次数的航线并不意味着是最短的总距离。最短路遍历必须说明在搜索过程中城市间的公里数(边权值的和),而不是像深度优先搜索和广度优先搜索那样。我们想要检索离当前顶点最近的顶点,也就是说,与此顶点相连的边权值最小的顶点。我们称这种抽象容器为优先队列 ,被检索的元素是在队列中拥有最高优先度的元素。如果我们让公里数最为优先,则可以从队列弹出一系列包括两个顶点和顶点间距离的元素。

    8.7 子程序

    Insert操作需要一个 列表和一个插入其中的值。Reset 操作需要一个用来重置的列表。MoreItems操作需要一个列表来查看是否有更多元素等待被返回。GetNext操作需要一个输人列表并且返回列表中的下一个元素。这些信息的交流是通过参数列表的概念实现的。

    8.7.1 参数传递

    • 参数列表( parameter list):程序中两部分之间的通信机制。
    • 形参(parameter):列在子程序名后的括号中的标识符。
    • 实参(argument):子程序调用中列在括号中的标识符。

    调用子程序时传递的实参个数必须与子程序定义中的形参个数相同。由于实参和形参是根据位置匹配的,所以它们的名字不必一致。当需要多次调用一个子程序而每次调用的实参又不同时,这一点非常有用。以这种方式传递的形参通常称为位置形参。

    8.7.2 值参与引用参数

    • 值参(value parameter):由调用单元传人实参的副本(写在留言板上)的形参。
    • 引用参数(reference parameter):由调用单元传入实参的地址(写在留言板上)的形参。

    第九章 面向对象设计与高级程序设计语言

    9.1 面向对象方法

    前面说过,之所以先介绍自顶向下设计,是因为它更能反映人们解决问题的方式。如你所见,自顶向下的解决方案对任务进行了分层。每个任务或指定的动作通过其参数列表操作传递给它的数据来生成想要的输出。自顶向下设计的重点就是任务。相反地,面向对象的设计方法是用叫作对象的独立实体生成解决方案的问题求解方法,对象由数据和处理数据的操作构成。面向对象设计的重点是对象以及它们在问题中的交互。一旦收集到了问题中的所有对象,它们就能构成问题的解决方案。

    9.1.1 面向对象

    • 对象(object):在问题背景中相关的事物或实体。
    • 对象类(object class)或类(class): 一组具有相似的属性和行为的对象的描述。
    • 域(field):类中的特定项,可以是数据或子程序。
    • 方法(method):定义了类的一种行为的特定算法。

    9.1.2 设计方法

    我们提出的分解过程有四个阶段。集体讨论(头脑风暴)是确定问题中的类的第一个阶段。在过滤这个阶段中,将回顾集体讨论阶段确定的类,看哪些类是可以合并的以及还缺少哪些类。过滤阶段保留下来的类将在下一个阶段仔细研究。

    场景阶段将确定每个类的行为。由于每个类只对自己的行为负责,所以我们称类的行为为责任。这个阶段探讨的是“如果......将会怎么样”的问题,以确保所有的情况都被分析到。当每个类的责任都确定后,它们将被记录下来,同时记录的还有它们必须与之协作(交互)才能履行责任的类的名字。

    最后是责任算法阶段,这个阶段将为列出的所有类的责任编写算法。CRC卡就是用来记录这一阶段的类信息的工具。

    集体讨论

    什么是集体讨论?字典把它定义为一种集体问题求解的方法,包括集体中的每个成员的自由发言。随着计算机变得越来越强大,能够解决的问题也变得越来越复杂,这种把自己锁在没有窗户的房间中的场景已经过时了。复杂问题的求解需要集思广益,以得到具有创新性的解决方案。
    在面向对象的问题求解背景中,集体讨论是一种集体行为, 为的是生成解决某个特定问题要用到的候选类的列表。在进行广告标语的集体讨论之前,所有参加者都要先了解产品,同样,在对类进行集体讨论前,参加者也必须了解问题。每个进人集体讨论会议的成员都应该清楚地了解要解决的问题。毫无疑问,在准备过程中,每个成员都会草拟出自己的类列表。

    过滤

    集体讨论会生成一份暂时的类列表。下一阶段要根据这个暂时的列表,确定问题解决方案中的核心类。在这份列表中,也许有两个类其实是相同的。这种类重复的现象很常见,因为一个公司不同部门的人员会对相同的概念或实体采用不同的名字。另外,也许列表中有两个类有许多共同的属性和行为,这些共同的属性和行为可以组合在一起。

    在这份列表中,也许有的类根本不属于问题的解决方案。例如,如果我们要模拟一一个计算器,那么可能会把用户列为一个可能的类。但实际上用户并不是模拟器中的一一个类,用户只是这个问题之外的一个实体,用于给模拟器提供输人而已。另一个可能的类是“开”按钮。但仔细想一下就会发现,“开”按钮也不是模拟器的一部分,它仅用于启动模拟程序。

    在完成过滤之后,这个阶段保留下来的所有类将被传递到下一阶段。

    场景

    这个阶段的目标是给每个类分配责任。最终,责任将被实现为子程序。在这个阶段,我们感兴趣的只是“任务是什么”,而不是“如何执行任务”。

    责任的类型有两种,即类自身必须知道什么(知识)和类必须能够做什么(行为)。类把它的数据(知识)封装了起来,使得一个类的对象不能直接访问另一个类中的数据。所谓封装,就是把数据和动作集中在一起,使数据和动作的逻辑属性与它们的实现细节分离。封装是抽象的关键。不过,每个类都有责任让其他类访问自己的数据(知识)。因此,每个类都有责任了解自身。

    • 封装(encapsulation):把数据和动作集中在一起,使数据和动作的逻辑属性与它们的实现细节分离。

    行为的责任看起来更像自顶向下设计中的任务。例如,学生类的一个责任可能是计算它的年级平均成绩(GPA)。在自顶向下设计中,我们会说这个任务是计算特定数据的GPA.在面向对象设计中,我们会说学生类要负责计算自己的GPA。这之间的差别微妙而深奥。尽管最后的计算代码可能看起来相同,但是它的执行方式却完全不同。在以自顶向下设计为基础的程序中,程序调用计算GPA的子程序,把学生对象作为参数传递。在面向对象的程序中,将给学生类的对象发送消息来计算GPA。这里没有参数,因为得到消息的对象知道自己的数据。

    这个阶段的名字提示了如何给类分配责任。整个小组(或个人)描述涉及类的不同处理场景。场景是讲述“如果....将会怎么样”的剧本,使参与者能够把每种情况都表演或思考一次。

    这个阶段输出的是一套分配了责任的类,可能写在CRC卡上。卡上列出了每个类的责任以及每个责任需要协作的类。

    责任算法

    最终必须为责任编写算法。由于在面向对象的设计观念中,重点是数据而不是动作,所以执行责任的算法一般都相当短。 例如,知识责任通常只返回一个对象的变量的内容,或者给另一个对象发送消息来检索它。动作责任复杂一些, 通常涉及计算。因此,自顶向下设计算法的方法通常也适用于设计动作责任算法。

    总结

    让我们做个总结:
    自顶向下的设计方法重点在于把输人转化成输出的过程,结果将生成任务的体系结构。
    面向对象设计的重点是要转换的数据对象,结果生成的是对象的体系结构。

    Grady Booch用这样的方法分类:“阅读要构造的软件的说明。如果要编写程序性的代码,就用下划线标出动词;如果要编写面向对象的程序,请标出名词。”

    我们建议用波浪线标出名词,用下划线标出动词。名词可以成为对象,动词可以成为操作。在自顶向下设计中,动词是重点;在面向对象设计中,名词是重点。

    9.1.3 一个计算机示例

    责任算法

    • Person类
      有两个责任需要分解,即初始化和输出。由于姓名是一个类, 可以让这些类自己进行初始化并输出自身。在面向对象设计中,调用子程序的方法是用对象名加点号再加要调用的方法。
    • Name类
      这个类有两个责任,即初始化和输出,它们的算法不同。初始化责任必须提示用户输人姓名,并且算法必须读入姓名。输出责任则必须输出姓和名,并给出合适的标签。

    9.2 翻译过程

    9.2.1 编译器

    • 编译器:把用高级语言编写的程序翻译成机器码的程序

    9.2.2 解释器

    • 解释器:输入用高级语言编写的程序,指导计算机执行每个语句指定的动作的程序
    • 字节码:编译Java源代码使用的标准机器语言

    9.3 程序设计语言的范型

    9.3.1 命令式范型

    冯·诺依曼顺序指令模型在内存中操作教值,这给了编程语言所用的绝大多数常用模型巨大的影响:命令式模型。在计算软件历史上,行业里具有统治地位的语言都是这种范型,这些语言包括FORTRAN、BASIC、 C、Pascal、C++。在这些范型中,这些程序描述了解决问题的必要处理。因此,这些命令式范型具有顺序执行指令的特征,变量的使用代表了内存地址,而使用赋值语句则改变这些变量的值。

    面向过程的范型

    面向过程编程是一种命令式模型,在这里语句被分组为子程序。一个程序是子程序分层次构成的,每一层执行整个问题求解的一个必要 的特定任务。伪代码示例描述了这种模型。我们编写子程序并且通过向其传递所需数据来完成它们的功能。

    面向对象的范型

    面向对象视角是与对象交互的一种方式。每个对象负责执行它自己的动作。在面向过程的范型中,数据被认为是被动并且被程序所操控的。在面向对象的范型中,数据对象是活跃的。对象和操作对象的代码绑定在一起,使得每个对象负责控制自己的操作。SIMULA和Smalltalk是最先支持面向对象编程的语言。Java和Python是两种新式的面向对象编程语言。

    C++和Java是命令式的语言,但就它们的范型而言又是混合的。尽管Java被认为是面向对象的,但是它还是有些面向过 程的特性。C++被认为是面向过程的, 但是它又有面向对象的特征

    9.3.2 声明式范型

    声明式范型是一个描述结果的模型,但是完成结果的过程则不被描述。在这种范型中有两种基本模型:函数式和逻辑式。

    函数式模型

    函数式模型基于函数的数学概念。计算通过对函数求值来实现,而问题求解通过函数调用来实现。因此基本的原理是函数的求值,而不是变量和赋值语句。

    逻辑编程

    逻辑编程基于象征逻辑的原则。这个模型包括了一系列关于对象的事实和一系列关于对象间关系的规则。一个程序包括了向这些对象和关系询问可以通过事实和规则推演的问题。解决潜在问题的算法用逻辑的规则来推演出事实的规则的答案。

    9.4 高级程序设计语言的功能性

    在高级语言中,选择和迭代操作非常简单,但子程序和参数传递则较为复杂。

    9.4.1 布尔表达式

    一个布尔表达式可以是:

    • 一个布尔变量
    • 一个算术表达式加一个关系运算符,再加一个算术表达
    • 一个布尔表达式加一个布尔运算符,再加一一个布尔表达式

    迄今为止,在示例中,变量存放的都是数值。布尔变量是内存中的一一个地址,由存放true或false的标识符引用。

    • 布尔表达式( Boolean expression):一个标识符序列,标识符之间由相容的运算符分隔,求得的值是true或false。

    9.4.2 数据归类

    • 强类型化(strong typing):每个变量都有一个类型, 只有这种类型 的值才能存储到该变量中。
    • 数据类型(datatype):一组值以及能够应用于这种类型的值的基本操作集合的说明。

    数据类型

    数据是表示信息的物理符号。在计算机内部,数据和指令都是二进制位的组合。计算机能够执行一条指令,是因为这条指令的地址被载人了程序计数器,而指令被载入了指令寄存器。被执行的位组合同样可以表示整数、实数、字符或布尔值,关键看计算机如何解释位组合。

    大多数高级语言都固有四种数据类型,即整数、实数、字符和布尔型。

    整数

    整数数据类型表示的是一个整数值的范围,这个范围由表示整数值的字节数决定。有些高级语言提供几种范围不同的整数类型,允许用户根据特定问题选择最适合的类型。

    实数

    实数数据类型表示的是特定精度的数的范围,与整数数据类型一样,这个范围由表示实数值的字节数决定。许多高级语言有两种大小的实数。应用于实数的操作与应用于整数的一样。但在对实数应用关系运算符时要小心,因为实数通常不精确。

    字符

    第3章介绍过,表示ASCII字符集中的字符需要-一个字节,表示Unicode字符集中的字符则需要两个字节。ASCII字符集包括英语字符,是Unicode字符集的子集。对字符进行算术运算是毫无意义的,许多强类型化的语言都不允许进行这种运算。但比较字符却是有意义的,所以可以对字符进行关系运算。在字符的关系运算中,“小于”和“大于”的意思是这个字符在字符集中“在.....之前” 和“在....之后”。例如,字符‘A’小于‘B',字符‘B’小于‘C',等等。字符‘1’小于字符‘2',‘2’小于‘3’,等等。如果要比较‘A’和“1’, 必须在使用的字符集中查找这两个字符间的关系。

    布尔型

    布尔数据类型只有两个值——true和false。还可以为布尔变量指定一个布尔表达式。

    整数、实数、字符和布尔型称为简单数据类型或原子数据类型,因为每个值都是独立的,不能再分割。上一章讨论了 复合数据类型,即由一组值构成的数据类型。字符串是一种具有复合数据类型的特征的数据类型,但通常被看作简单数据类型。

    字符串

    字符串是一个字符序列,在某些语言中这个序列通常被看作一个数据值。

    声明

    • 声明(declaration):把变量、动作或语言中的其他实体与标识符关联起来的语句,使程序员可以通过名字引用这些项目。
    • 保留字(reserved word): 一种语言中具有特殊意义的字,不能用它作为标识符。
    • 区分大小写(case sensitive):大写字母和小写字母被看作是不同的;两个拼写方法相同但大小写形式不同的标识符被看作是两个不同的标识符。

    9.4.3 输入/输出结构

    在非强类型语言中,输入的格式决定了类型。如果输人出现在引号之间,则它被假定为一个字符串,并以字符串的形式存储。如果输人的是一个数字, 它将被存储为数字。

    输出语句创建字符流。输出语句中列出的项目可以是文字值或变量名。文字值是直接在输出语句中写的数字或字符串(或任何语句)。一次处理一个将要输出的值,从而找到标识符或文字的类型。类型确定了如何解释位模式。如果该类型是字符串,则将字符写人输出流。如果该位模式为数字,则该数字将被转换为表示数字的字符,并将字符输出。

    在强类型语言中,不管输人输出语句的语法或输人输出流在哪儿,处理的关键在于数据类型,数据类型确定字符是如何被转换为位模式(输人)以及如何被转换为字符(输出)。在非强类型语言中,输人的格式决定了位模式是如何转换的。

    9.4.4 控制结构

    • 控制结构:确定程序中的其他指令的执行顺序的指令

    嵌套逻辑

    在任何控制语句中被执行或跳过的语句可以是简单的语句或块(一组语句),对于这些语句没有任何限制。事实上,被跳过或重复的语句可以包含一个控制结构。选择语句可以在循环结构中被嵌套。循环结构可以在选择语句中被嵌套。选择和循环语句可以在子程序中被嵌套,而子程序可以在循环或选择结构中被嵌套。

    异步

    • 异步(asynchronous): 不与计算机中的其他操作同时发生;换句话说,与计算机的动作不同步。

    异步处理也叫作事件驱动处理。换句话说,这样的处理是被程序指令序列以外发生的事件所控制。
    异步处理经常被用在Java和VB.NET中,但是很少被其他语言所使用。

    9.5 面向对象语言的功能性

    9.5.1 封装

    • 封装(encapsulation);实施信息隐蔽的语言特性。
    • 对象类或类(问题求解阶段) ( object class or class ( problem solving phase)):属性和行为相似的一组对象的说明。
    • 对象(问题求解阶段)(object (problem- solving phase)):与问题背景相关的事物或实体。
    • 对象(实现阶段)( object (implementation phase):类的一个实例。
    • 类(实现阶段)(class (implementation phase)):对象的模式。

    9.5.2 类

    从语法上来说,类像前面介绍的记录,它们都是异构复合数据类型。但记录通常被认为是被动结构,只是近年来才采用子程序作为域。而类则是主动结构,一直都把子程序 用作域。操作类的数据域的唯一方式是通过类中定义的方法(子程序)。

    • 实例化(instantiate): 创建类的对象。

    算法声明类的对象只能通过类的子程序(称为方法)访问类的域。
    默认情况下,类中的域是私有的,也就是说,除非一个类的某 个域被标识为public (公有),否则任何其他对象都不能访问这个类的对象的域(无论是数据还是方法)。如果一个类想让其他类的对象调用自己的方法,就必须明确地声明这个方法是public的。Person 类中的方法被标记为public,以便用程序可以调用。

    9.5.3 继承

    继承是面向对象语言的一种属性, 即一个类可以继承另一个类的数据和方法。这种关系是一种is-a关系。超类是被继承的类,派生类是继承的类。类构成了继承的体系结构。在这种体系结构中,所处的层次越低,对象越专门化。下级的类会继承其父类的所有行为和数据。

    • 继承(inheritance):类获取其他类的属性(数据域和方法)的机制。

    继承通过允许应用程序使用已经被测试过的类和从一一个类中继 承应用所需的属性来促进重用。其他必要的属性和方法可以在之后加人派生类中。

    9.5.4 多态

    假设Person类和Student类都具有名为Print和Initialize的方法。在Person类中,这个方法将输出Person类中定义的地址,在Student类中,该方法则输出Student类中定义的地址。这两个方法名字相同,但实现不同。程序设计语言处理这种明显二义性的能力叫作多态性。语言如何知道调用单元调用的是哪个Print方法还是Initialize方法呢?调用单元将把类的方法应用于类的一一个实例,应用这个方法的对象的类可以确定使用的是Print 或Initialize的哪个版本。

    • 多态( polymorphism):一种语言的继承体系结构中具有两个同名方法且能够根据对象应用合适的方法的能力。

    9.6 过程设计与面向对象设计的区别

    在面向过程的版本中,列表被呈现为传递给子程序的记录,以便子程序可以对其操作。操作它的数据结构和子程序是用户程序的一部分。
    在面向对象的版本中,类对象的实现通过封装实现对用户的隐藏。

  • 相关阅读:
    iframe跨页面调用函数
    $.extend()
    tab标签 插件 by 腾讯 jianminlu
    click事件多次触发 jQuery
    vertical-align
    display:inline-block
    在父页面访问iframe的东西
    2019牛客多校第三场
    2019HDU多校第一场
    2019江苏省赛
  • 原文地址:https://www.cnblogs.com/lj2406/p/11767851.html
Copyright © 2020-2023  润新知