第四部分 程序设计层


第6章 低级程序设计语言和伪代码

6.1 计算机操作

  计算机是能够存储检索处理数据的可编程电子设备。
  数据和操作数据的指令逻辑上是相同的,它们存储在相同的地方。这就是“可编程”的意义所在。操作数据的指令和数据一起存储在机器中。要改变计算机对数据的处理,只需要改变指令即可。
  存储、检索和处理是计算机能够对数据执行的动作。控制单元执行的指令能够把数据存储到机器的内存中,在机器内存中检索数据,在算术逻辑单元中以某种方式处理数据。词语“处理”非常通用。在机器层,处理涉及在数据值上执行算术和逻辑操作。

6.2 机器语言

  • 机器语言(machine labguage):由计算机直接使用的二进制编码指令构成的语言。
      每种处理器都有自己专用的机器指令集和。这些指令是处理器唯一真正能够执行的指令。指令数量有限,设计者给每个指令分配一个二进制代码;来表示
  • Pep/8:一台虚拟机

  虚拟机(virtual computer (machine)):为了模拟真实机器的重要特征而设计的假想机器。

  • Pep8反映的重要特性
      Pep/8有七个寄存器,重点研究其中三个:
    • 程序计数器(PC):其中包含下一条即将被执行的指令的地址。
    • 指令寄存器(IR):其中包含正在被执行的指令的一个副本。
    • 累加器(是一个寄存器)。
        累加器是用来保存操作的数据和结果。
  • 指令格式
      一条指令由两部分组成,8位的指令说明符和(可选的)16位的操作数说明符。指令说明符(指令的第一个字节)说明了要执行什么操作和如何解释操作数的位置。操作数说明符(指令的第二和第三个字节)存放的是操作数本身或者操作数的地址。有些指令没有操作数说明符。
      指令说明符的格式根据表示一个具体操作所用的比特数的不同而不同。Pep/8中,操作代码(称为操作码)的长度从4比特到8比特不等,4比特操作码的第五位用来指定使用哪个寄存器。寄存器A(累加器)的寄存器说明符是0,这是唯一一个我们要用到的寄存器。   3比特的寻址模式说明符表示了怎样解析指令中的操作数部分。
      没有操作数(要处理的数据)的指令被称为一元指令,这些指令没有操作数说明符,长度是一个字节。
  • 一些示例指令
![](https://img2018.cnblogs.com/blog/1813636/201910/1813636-20191022234833919-1878185821.jpg)

6.3 一个程序实例

6.3.1 手工模拟

![](https://img2018.cnblogs.com/blog/1813636/201910/1813636-20191022234935753-900057701.jpg)

  程序中有6条指令,假设存放在内存的连续单元中。第一条指令存储在内存的0000~0002存储单元中。执行的第一步是把0000载入PC。注意,一旦指令已被访问,PC就会递增。

6.3.2 Pep/8模拟程序

  单击Pep/8图标启动程序,屏幕可能显示多种情况的一种,但每种都包含一个标有“Object Code(结果代码)"的部分。在这个窗口中输入之前的程序,现在去菜单栏。
  点击三个图标中中间的那个,装入程序((loader):软件用于读取机器语言并把它载入内存的部分),你的程序将被加载到Pep/8的内存中。确保终端I/O按钮是被选中的。点击最右边的图标,即执行程序。
  下面是一个程序已加载后的CPU截图
![](https://img2018.cnblogs.com/blog/1813636/201910/1813636-20191022235614634-839812022.jpg)

  当“Trace Program(跟踪程序)”被选中后,单击“Single Step(单步执行)”,第一个指令将被执行。

6.4 汇编语言

  • 汇编语言(assembly labguage):一种低级语言,用助记码表示特定计算机的机器语言指令。
  • 汇编器(assembler):把汇编语言程序翻译成机器代码的程序。

6.4.1 Pep/8汇编语言

  输出只被定义为字符,所以不做运算。

6.4.2 汇编器指令

  汇编器指令(assembler directive):翻译程序使用的指令。   下面是Pep/8汇编器中的几条有用的汇编器指令,这些指令也叫伪操作。

6.4.3 Hello程序的汇编语言版本

  注释(comment):为程序读者提供的解释性文字。
  运行一个汇编语言程序的过程:

6.4.4 一个新程序

  读入三个数据,然后输出它们的和。把和设置为0,然后输入第一个数,把它加到总值上,以此类推。如果把数据放在程序最后,就要知道程序自身占用多少个内存单元,所以把数据放在程序之前。

6.4.5 具有分支的程序

已经表明,-个将程序计数器设为下一条被执行的指令地址的BR指令可以改变程序计 数器。是否有其他的方式来改变程序的控制流程?我们可以问一个问题并在答案的基础上采 取一定行动吗?当然。下面是两个有用的操作码及其含义:
举个例子:
LDA numl,d; Load num1 into A register
BRLT lessThan; Branch to lessThan if num1 is less than0

在把num1载人累加器时,如果它的值是负数,则把PC设置为lessThan的内存地 址。如果num1的值不为负数,那么PC保持不变。 让我们更改一下前面的程序,如果三个数的和是正数,则输出和,如果和是负数,则输 出错误消息。在哪里进行测试呢?就在要把计算出的结果存入地址sum之前,可以测试寄 存器A,如果它是负数,则输出“E”。 可以用BRLT指令测试和是否为负数。如果寄存器A是负数,那么PC的内容将被 BRLT后面的操作数代替,使下一条指令从操作数指定的内存单元开始。必须给这条指令一个名字,我们称之为negMsg。当显示出错误消息后,必须跳转到让程序结束的STOP行,也就是说,必须给这个代码行一个名字。 我们称之为finish。 下面是被更改程序的源代码。请注意,我们已经减少了注释的数量。如果注释只是重复指令,这就不是好事了。

6.4.6 具有循环的程序

我们可以想输人多少值求和就输人多少值,并编写代码读取这些值并求和。我们通过创建计数循环代码来实现这个功能,这部分代码可以重复指定次数。在代码循环中-个值被读取并累加。如何知道已经读取了多少个值呢?可以在每次重复循环时建立一个散列标记,之后比较散列标记的和与我们希望循环的次数。实际上,这个散列标记就是在内存中一个为0的存储单元,我们称之为计数器(counter)。 每次循环重复时,我们在内存中该存储单元中加1,即计数器加1。当计数器等我们想输人的数量,就完成读取和计数。

6.5 表达算法

算法(algorithm):解决方案的计划或概要,或解决问题的逻辑步骤顺序。 伪代码(pseudocode):一种表达算法的语言。

6.5.1 伪代码的功能

  • 变量
  • 赋值
  • 输入/输出
  • 重复(迭代、循环)
  • if——then选择
  • if——then——else选择

布尔表达式(boolean expression):评价为真或假的表达式。

6.5.2 执行伪代码算法

6.5.3 写伪代码算法

桌面检查(desk checking):在纸上走查整个设计。

6.5.4 翻译伪代码算法

如何翻译伪代码算法取决于我们将算法翻译成哪种语言。由于汇编语言的范围是有限的,所以一个伪代码语句需要几个Pep/8语句。

6.6 测试

  • 测试计划:说明如何测试程序的文档。
  • 代码覆盖(明箱)测试法:通过执行代码中的所有语句测试程序或子程序的测试方法。
  • 数据覆盖(暗箱)测试法:把代码作为一个暗箱,基于所有可能的输入数据测试程序或子程序的测试方法。
  • 测试计划实现:用测试计划中规定的测试用例验证程序是否输出了预期的结果。

第七章 问题求解与算法设计

7.1 如何解决问题

7.1.1 提出问题

7.1.2 寻找熟悉的情况

  永远不要彻底重新做一件事。如果解决方案已经存在了,就用这种方案。如果以前曾经解决过相同或相似的问题,只需要再次使用那种成功的解决方案即可。

7.1.3 分治法

  把一个大问题划分为几个能解决的小单元。

7.1.4 算法

  算法:在有限的时间内用有限的数据解决问题或子问题的明确指令集合。

7.1.5 计算机问题求解过程

  • 分析和说明阶段
  • 算法开发阶段
  • 实现阶段
  • 维护阶段 下图展示了这些阶段的交互

7.1.6 方法总结

  • 分析问题
  • 列出主要任务
  • 编写其余的模块
  • 根据需要进行重组和改写

7.1.7 测试算法

7.2 有简单参数的算法

7.2.1 带有选择的算法

写一个算法来表达在给定的室外温度情况下穿什么衣服合适。
顶级(主要)模块只是表达任务。
Wnite "Enter the temperature"
Read temperature
Determine dress
最后一个语句需要进一步分解。我们可以为它编写伪代码。
IF(cemperature > 90)
  Write "Texas weather: wear shorts"
ELSE IF (temperature > 70)
  Write "ideal weather:short sleeves are fine
ELSE IF (temperature >50)
  Write "A little chilly: wear a ight jacket"
ELSE IF (temperture >32)
  Write Philadelphia weather: wear a heavy coat"
ELSE
  Write "Stay inside"
到达第二个问语句的唯一方式是第一 一个 i表达式是不真实的。所以如果第二个表达式为真, 则你可以确定温度在7到90.如果前两个表达式都为假而第三个为真,则温度在51到70.依照同样的逻辑得出的结论是费城的温度在33到50.“待在家里”是指温度在32以下或等于32。任何一个分支都包含连串的语句。

7.2.2 带有循环的算法

  • 计数控制循环
    • 初始化
    • 测试
    • 增量

while循环被称为前测试循环,因为在循环开始前就开始测试了。永远不会终止的循环称为无限循环。

  • 事件控制循环   循环中重复的次数是由循环体自身内发生的事件控制的循环被称为事件控制循环。当使用while语句来实现事件控制循环时,这一过程仍分为三个部分:事件必须初始化,事件必须被测试,事件必须更新。
  • 嵌套结构:控制结构嵌入另一个控制结构的结构,又称为嵌套逻辑。
  • 抽象步骤:细节仍未明确的算法步骤。
  • 具体步骤:细节完全明确的算法步骤。

7.3 复杂变量

7.3.1 数组

数组是同构项目的有名集合,可以通过单个项目在集合中的位置访问它们。项目在集合中的位置叫作索引。虽然人们通常从1开始计数,但多数程序设计语言从0开始,因此这里也采用这种方。图7-5中是一个具有从0到9这10个元索的数组。 如果数组叫numbers,则通过表达式numbers[position]来访国数组中的每个值,其中position就是索引,是一个从0到9之间的数。与数组有关的算法分为三类:搜索、排序和处理。

7.3.2 记录

记录是异构项目的有名集合。异构是指集合中的元素可以不必相同。记录可以把与一个对象相关的各种项目绑定在一起。

7.4 搜索算法

7.4.1 顺序搜索

  依次查找每一个并将其与我们需要搜索的元素进行比较。如果匹配,则找到了这个元素,否则即继续寻找。

7.4.2 有序数组中的顺序搜索

如果知道数组中的项目是有序的,那么在查找时,如果我们需要的项目在数组中,到了这个数可能在数组中的位置时就可以停止查找了。

7.4.3 二分检索

二分检索:在有序列表中查找项目的操作,通过比较操作排除大部分检索范围。 有时候之所以不用二分检索,因为为了计算中间项的索引,每个比较操作都需要更多的计算。

7.5 排序

  • 选择排序
  • 冒泡排序 冒泡排序是非常慢的排序算法,因为它要对数组中除最后一个元素的所有元素进行一次迭代。但只要对它稍加修改,就能够让他成为某些情况的最佳选择。
  • 插入排序 选择排序的每次迭代后,一个元素被放置到它的永久位置。而插入排序的每次迭代后,一个元素将被放在相对于其他元素来说适当的位置上。

7.6 递归算法

递归(recursion)算法调用它本身的能力。 这种算法使用一个选择语句来确定是否重复算法来调用一遍或停止这一过程,而不是使用一个循环语句执行一个算法。

  • 子程序语句
    我们可以给一段代码一个名称,然后程序另一部分的一个语句使用这个名称。遇到这个名称时,这个进程的其他部分将会终止,等待这个命名代码被执行。当命名代码执行完毕,将会继续处理下面的语句。命名代码出现的地方被称为调用单元。

    子程序有两种形式,-种是只执行特定任务的命名代码,一种 是不仅执行任务,还返回给调用单元一个值(值返回子程序)。第一种形式的子程序在调用单元中用作语句,第二种则用作表达式,返回的值被用来评估表达式,

    子程序是抽象的一种强力工具。 命名的子程序列表允许程序的读者了解到任务已经完成并且不被任务实现的细节所打扰。如果一个子程序需要信息去执行它的任务,便把数据值的名字放在f程序标题的括号中。如果子程序返回一个值给调用单元,它在将要返回的数据名称后面使用单词 RETURN。

  • 递归阶乘 每次调用Factorial时N都会减小,每次给出的数据称为参数。如果参数是负数会出现什么情况?子程序将不断地调用自身,直到运行时间支持系统耗尽了内存为止。这种情况叫作无限递归,与无限循环等价。
  • 递归二分检索
  • 快速排序
    基本思想是对两个小列表排序比对一个大列表排序更快更容易。如果数据排列是随机的,则快速排序是一个很好的排序方法。

如果问题陈述逻辑上分为两种情况(基本情况、一般情况),则递归是一种可行的选择。

7.7 几个重要思想

7.7.1 信息隐蔽

信息隐蔽(information hiding):隐蔽模块的细节以控制对这些细节的访问的做法。
如果一个设计者知道一个模块的低层细节,他或她就可能会以这些细节为基础设计这个模块的算法。但是这些低层的细节很可能会发生变化,一旦它们改变了,那么整个模块都要重写。

7.7.2 抽象

抽象:复杂系统的一种模型,只包括对观察者来说必须的细节。 - 数据抽象:把数据的逻辑视图和它的实现分离开。 - 过程抽象:把动作的逻辑视图和它的实现分离开。 - 控制抽象:把控制结构的逻辑视图和它的实现分离开。 - 控制结构:用于改变正常的顺序控制流的语句。

7.7.3 事物命名

在编写算法时,我们使用速记短语表示要处理的任务和信息,也就是说,给数据和过程一个名字,这些名字叫做标识符。

7.7.4 测试

白盒测试,基于代码本身;黑盒测试,基于测试所有可能的输入值。

Copyright © 2024 罗雪峰
Powered by .NET 8.0 on Kubernetes