第六章 低级程序设计语言与伪代码
1.计算机操作
计算机是能够存储、检索和处理数据的可编程电子设备。
2.机器语言
计算机语言(machine language):由计算机直接使用的二进制编码指令构成的语言。
Pep/8:一台虚拟机
虚拟机(virtual computer(machine)):为了模拟真实机器的重要特征而设计的假想机器。
Pep/8有39个机器语言指令。
Pep/8反映的重要特性。
Pep/8的内存单元由65536字节的存储空间构成。这些字节从0到65536(十进制)进行编号。
寄存器:
- 程序计数器(PC),其中包含下一条即将被执行的指令的地址。
- 指令寄存器(IR),其中包含正在被执行指令的一个副本。
- 累加器(是一个寄存器)。
累加器是用来保存操作的数据和结果。
指令格式
一条指令由两部分组成,即8位的指令说明符和(可选的)16位的操作数说明符。说明指令符(指令的第一个字节)说明了要执行什么操作(如把一个数加到一个已经存储在寄存器中的值上)和如何解释操作数的位置。操作数说明符(指令的第二和第三字节)存放的是操作数本身或者操作数的地址。有些指令没有操作数说明符。
指令说明符的格式根据表示一个具体操作所用的比特数的不同而不同。在Pep/8中,操作代码(称为操作码)的长度从4比特到8比特不等。我们在这里所用的操作码长度是4比特或5比特,4比特操作码的第5位用来指定使用哪个寄存器。
3比特的寻址模式说明符表示了怎样解析指令中的操作数部分。如果寻址模式是000,那么指令的操作数说明符中存储的就是操作数。这种寻址模式称为立即寻址(i)。如果寻址模式是001,那么操作数说明符中存储的是操作数所在的内存地址名称。这种寻址模式称为直接寻址(d)。
一些示例指令
操作码 | 指令含义 |
---|---|
0000 | 停止执行 |
1100 | 将操作数载入寄存器A中 |
1110 | 将寄存器A的内容存储到操作数中 |
0111 | 将操作数加到寄存器A中 |
1000 | 在寄存器A的值中减去操作数的值 |
01001 | 把字符输入操作数 |
01010 | 从操作数输出字符 |
3.手工模拟
1)从程序计数器指定的位置读取下一条指令
2)译解指令(并且更新程序计数器)
3)如果需要,获取数据(操作数)
4)执行指令
4.汇编语言
汇编语言(assembly language):一种低级语言,用助记码表示特定计算机的机器语言部分。
汇编器(assembler):把汇编语言程序翻译成机器代码的程序。
5.Pep/8汇编语言
助记忆码 | 操作数、寻址模式说明符 | 指令的含义 |
---|---|---|
STOP | 停止执行 | |
LDA | 0x008B, i | 把008B载入寄存器A |
LDA | 0x008B, d | 把内存单元8B中的内容载入寄存器A |
STA | 0x008B, d | 把寄存器A中的内容存入内存单元8B |
ADDA | 0x008B, i | 把008B加到寄存器A中 |
ADDA | 0x008B, d | 把内存单元8B中的内容加到寄存器A中 |
SUBA | 0x008B, i | 从寄存器A中减去008B |
SUBA | 0x008B, d | 从寄存器A中减去内存单元8B中的内容 |
BR | 分支到操作数说明符中指定的位置 | |
CHARI | 0x008B, d | 读取一个字符,把它存入内存单元8B中 |
CHARO | 0x008B, i | 输出字符B |
CHARO | 0x008B, d | 输出存储在内存单元8B中的字符 |
DECI | 0x008B, d | 读取一个十进制数,把它存储在内存单元8B中 |
DECO | 0x008B, i | 输出十进制数139(即十六进制的8B) |
DECO | 0x008B, d | 输出存储在内存单元8B中的十进制数 |
汇编器指令
汇编器指令(assembler directive):翻译程序使用的指令。
伪操作 | 参数 | 含义 |
---|---|---|
.ASCII | "Str\x00" | 表示一个ASCII字节的字符串 |
.BLOCK | 字节数 | 创建一个字节块 |
.WORD | 值 | 创建一个字,并存值进去 |
.END | 表示汇编语言的终点 |
Hello程序的汇编语言版本
注释(comment):为程序读者提供的解释性文字。
汇编语言的重要性:
它通过将指令抽象为单词,从而删除了机器语言编程的很多细节。虽然在执行一个程序时增加了一个步骤(汇编语言翻译成机器代码),但是这个额外的步骤是非常值得的,这大大简化了程序员的工作。
具有分支的程序
助记忆码 | 操作数、寻址模式说明符 | 指令的含义 |
---|---|---|
BRLT | i | 如果寄存器A是负数,则将PC设置成操作数 |
BREQ | i | 如果寄存器A为零,则将PC设置为操作数 |
在把num1载入累加器时,如果它的值是负数,则把PC设置为lessThan的内存地址。如果num1的值不为负数,那么PC保持不变。 | ||
如果三个数的和是正数,则输出和,如果和是负数,则输出错误消息。在要把计算出的结果存入地址sum之前,可以测试寄存器A,如果它是负数,则输出“E”。 | ||
可以用BRLT指令测试和是否为负数。如果寄存器A是负数,那么PC的内容将被BRLT后面的操作数代替,使下一条指令从操作数指定的内存单元开始。必须给这条指令一个名字,我们称之为negMsg。当显示出错误信息后,必须跳转到让程序结束的STOP行,也就是说,必须给这个代码行一个名字。我们称之为finish。 |
具有循环的程序
如果我们想读入4个值并求其和怎么办?五个值呢?任意数量的值呢?我们可以想输入多少值求和就输入多少值,并编写代码读取这些值并求和。我们通过创建记数循环代码来实现这个功能,这部分代码可以重复指定次数。在代码循环中一个值被读取并累加。如何知道已经读取了多少个值呢?可以在每次重复循环时建立一个散列标记,之后比较散列标记的和与我们希望循环的次数。实际上,这个散列标记就是在内存中一个为0的存储单元,我们称之为计数器(counter)。每次循环重复时,我们在该存储单元中加1,即计数器加1.当计数器等于我们想输入的数量,就完成读取和记数。
6.表达算法
算法(algorithm):解决方案的计划概要,或解决问题的逻辑步骤顺序。
伪代码(pseudocode):一种表达算法的语言。
伪代码的功能:
变量
出现在伪代码算法中的名字,应用的是内存中存储值的位置。这些名字要能反映出它存放的值在算法中的角色。
赋值
把值放入变量
例:
1)Set sum to 0
2)sum <—— 1
访问变量中的值
例:
1)Set sum to sum + num
2)sum <—— sum + num
输入/输出
我们可以使用Write语句进行输出,使用Read语句进行输入。
例:
Write“Enter the number of values to read and sum”
Read num
双引号之间的字符叫作字符串,它们告诉用户要输入什么或者要输出什么。究竟采用Display还是Print是无关紧要的,它们都等价于Write,Get和Input都与Read同义。
选择
用选择结构可以执行或跳过某项操作。另外,用选择结构还可以在亮相操作之间进行选择。选择结构使用括号中的条件决定执行哪项操作。
例:
//Read and sum three numbers
IF (sum < 0)
Print error message
ELSE
Print sum
//Stop or whatever comes next
重复
使用重复结构可以重复执行指令。比如在求和问题中,计数器被初始化、检验并增加。伪代码允许我们概述算法,所以这部分就变得易于理解。和选择结构一样,在WHILE旁边的圆括号中的表达式是一个判断,如果判断成立,缩进中的语句将被执行,如果不成立,就会跳过缩进中的语句,直接执行下一个非缩进语句。
例:
Set limit to number of values to sum
WHILE(counter < limit)
Read num
Set sum to sum + num
Set counter to counter + 1
//Rest of program
WHILE和IF旁边的括号里的表达式是布尔表达式,其结果可为真或假。在IF中如果表达式为真,则执行接下来的缩进代码块,若表达式为假,则跳过缩进代码块。在WHILE中,若果表达式为真,则执行缩进代码块。如果表达式为假则跳到下一个不缩进的执行语句。将WHILE、IF和ELSE大写是因为这些语句通常直接使用在很多编程语言中,在计算领域中它们有特殊的含义。
布尔表达式(boolean expression):评价为真或假的表达式。
执行伪代码算法
写伪代码算法
1)读入一些正数数对,然后按序输出这些数对。如果数对多于一对,就必须使用循环。下面是该算法的初稿:
WHILE(not done)
Write“Enter two values separated by a blank;press return”
Read number1
Read number2
Print them in order
如何知道何时停止呢?也就是说,如何终止程序中所说的not done呢?可以要求用户告诉程序要输入多少个数对。下面是算法的第二稿:
Write “How many pairs of values are to be entered?”
Read numberOfPairs
Set pairsRead to 0
WHILE (pairsRead < numberOfPairs)
Write “Enter two values separated by a blank;press return”
Read number1
Read number2
Print them in order
如何判断数对的顺序呢?可以用条件结构比较它们的值。如果number1小于number2,则先输出number1,再输出number2.否则,就先输出number2,再输出number1.在完成算法前,必须增加number1的值。下面是算法的终稿:
Write “How many pairs of values are to be entered?”
Read numberOfPairs
Set pairsRead to 0
WHILE (pairsRead < numberOfPairs)
Write “Enter two values separated by a blank;press return”
Read number1
Read number2
IF (number1 < number2)
Print number1,“ ”,number2
ELSE
Print number2,“ ”,number1
Set numberRead to numberRead + 1
桌面检查:
我们坐在桌子前,用纸和笔走查整个设计。在推理设计时,采用真实的数据来跟踪发生的情况非常有用。这种方法虽然简单,但却极其有效。
翻译伪代码算法
7.测试
测试计划(test plan):说明如何测试程序的文档。
代码覆盖(明箱)测试法(code-coverage(clear-box)testing):通过执行代码中的所有语句测试程序或子程序的测试方法。
数据覆盖(暗箱)测试法(data-coverage(black-box)testing):把代码作为一个暗箱,基于所有可能的输入数据测试程序或子程序的测试方法。
测试计划实现(test-plan implementation):用测试计划中规定的测试用例验证程序是否输出了预期的结果。
测试计划实现要运行测试计划中列出的所有测试用例,并记录运行结果。如果结果与预期不符,则必须重新审查设计,找出并纠正其中的错误。当每种测试用例都给出了预期的结果时,这个过程将结束。
8.小结
- 计算机能够存储、检索和处理数据。
- 计算机的机器语言是一套机器的硬件能够识别并执行的指令。
- Pep/8汇编语言是一种使用助记忆代码而不是二进制数表示的指令。
- 伪代码是人们为了表示算法而使用的一种便捷形式的语言。
- 与算法一样,程序也需要测试。
9.个人问题
1)程序的开始和终止结构如何书写
2)必要的程序书写语言有哪些
3)为什么书上没有提到FOR循环
4)如何定义输入值的类型
第七章 问题求解与算法设计
1.如何解决问题
提出问题
典型问题:
- 对这个问题我了解多少?
- 解决方案是什么样的?
- 存在什么特例?
- 我如何知道已经找到解决方案了?
Polya的“如何解决它”列表:
第一步 必须理解问题。
第二步 找到信息和解决方案之间的联系。如果找不到直接的联系,则可能需要考虑辅助问题。最终,应该得到解决方案。
第三步 执行方案。
第四步 分析得到的解决方案。
寻找熟悉的方法
分治法
算法
算法(algorithm):在有限的时间内用有限的数据解决问题或者子问题的明确指令集合。
计算机问题求解过程
- 分析和说明阶段
分析:
理解(定义)问题
说明:
说明程序要解决的问题 - 算法开发阶段
开发算法:
开发用于解决问题的逻辑步骤序列
测试算法:
执行列出的步骤,看它们能否真正地解决问题 - 实现阶段
编码:
用程序语言翻译算法(通用解决方案)
测试:
让计算机执行指令序列。检查结果,修改程序,知道的得到正确答案 - 维护阶段
使用:
使用程序
维护:
修改程序,使它满足改变了的要求,或者纠正其中的错误
方法总结
1)分析问题
2)列出主要任务
3)编写其余的模块
4)根据需要进行重组和改写
测试算法
算法的测试通常都是在编码算法的各种条件下运营程序,然后分析结果以发现问题。不过,这种测试只能在程序完成或至少部分完成时进行,这种测试太迟了,所以不能依赖。越早发现和修正问题,解决问题就越容易,代价也越小。
显然,需要在开发过程的更早阶段执行测试。特别是算法必须在实现之前进行测试。
2.有简单参数的算法
带有选择的算法
表达在给定的室外温度情况下穿什么衣服合适的算法:
Write“Enter the temperature”
Read temperature
Determine dress
IF(temperature>90)
Write “Texas weather:wear shorts”
ELSE IF(temperature>70)
Write “Ideal weather:shorts sleeves are fine”
ELSE IF(temperature>50)
Write “A little chilly:wear a light jacket”
ELSE IF(temperature>32)
Write “Philadelphia weather:wear a heavy coat”
ELSE
Write“Stay inside”
带有循环的算法
技术控制循环
这类循环有三个不同的部分,使用一个特殊的变量叫做循环控制变量。第一部分是初始化:循环控制变量初始化为某个初始值。第二部分是测试:循环控制变量是否已经达到特定值?第三部分是增量:循环控制变量以1递增。以下算法重复过程limit次:
Set count to 0
WHILE(count < limit)
...
Set count to count + 1
...
循环控制变量count在循环外已被设置为0.测试表达式count<limit,如果表达式为真则执行循环。循环中的最后一句使得控制循环变量count递增。循环会执行多少次呢?循环执行时count为0,1,2...limit-1。因此,循环执行了limit次。循环控制变量的初始值和布尔表达式中的关系运算符共同决定了循环执行的次数。
while循环被称为前测试循环,因为在循环开始前就测试了。如果最初条件为假,将不进入循环。如果省略增量语句时会发生什么?布尔表达式从不改变。如果表达式开始时为假,那就什么也不会发生,循环也就不执行;如果表达式开始时为真,表达式将从不改变,所以循环将一直执行。实际上,大多数计算机系统都有一个计时器,所以程序不会真的一直运行下去。相反,程序将停止于一条错误信息。永远不会停止的循环称为一个无线循环。
例:
Write “How many pairs of values are to be entered?”
Read numberOfPairs
Set numberRead to 0
WHILE(numberRead < numberOfpairs)
//Body of loop
...
Set numberRead to numberRead + 1
Pep/8使用分号来表明之后的部分是注释,而不是程序的一部分。在我们的伪代码中,使用两个斜杠来开始注释。
事件控制循环
循环中重复的次数是由循环体自身内发生的事件控制的循环被称为事件控制循环。当使用while语句来实现事件控制循环时,这一过程仍分为三个部分:事件必须初始化,事件必须被测试,事件必须更新。
例:
Write “Enter the new base”
Read newBase
Write “Enter the number to be converted”
Read decimalNumber
Set answer to 0
Set quotient to 1
WHILE(quotient is not zero)
Set quotient to decimalNumber DIV newBase
//Rest of loop body
Write “The answer is”,answer
技术控制循环是非常直接的,它指定了循环的次数,而在事件控制循环中则不太清楚,并不显而易见。
嵌套结构(nested structure):控制结构嵌入另一个控制结构的结构,又称为嵌套逻辑(nested logic)。
平方根
给出一个你想要计算平方根的数,猜测一个可能的答案,然后把这个答案乘方。如果你猜测的正确,这个平方值就等于原始值,如果不正确,则调整你的猜测,重新开始。
算法:
Read in square
Set guess to square/4
Set epsilon to 1
WHILE (epsilon > 0.001)
Calculate new guess
Set epsilon to abs(square-guess*guess)
Write out square and the guess
抽象步骤(abstract step):细节仍未明确的算法步骤。
具体步骤(concrete step):细节完全正确的算法步骤。
3.复杂变量
数组
数组是同构项目的有名集合,可以通过单个项目在集合中的位置访问它们。
例:
integer number[10]
//Declares numbers to hold 10 integer values
Write “Enter 10 integer numbers,one per line”
Set position to 0//Set variable position to 0
WHILE(position < 10)
Read in numbers[position]
Set position to position + 1
//Continue with processing
与数组相关的算法分为三类:搜索、排序和处理。搜索就像它的字面意思一样,搜索数组中的项,一次寻找一个特定的值。排序是按顺序将元素放入数组中。如果项是字符或字符串,将以字母顺序排序。一个已排序的数组中的项已经排好顺序。处理是一种捕捉短语,包含了对数组中的项所做的所有其他计算。
记录
记录是异构项目的有名集合,可以通过名字单独访问其中的项目。所谓异构,就是指集合中的元素可以不必相同。集合可以包含整数、实数、字符串或其他类型的数据。记录可以把与一个对象相关的各种项目绑定在一起。
4.搜索算法
顺序搜索
我们依次查找每一个元素并将其与我们需要搜索的元素进行比较。如果匹配,则找到了这个元素,如果不匹配,则继续找下一个元素。什么时候停止?当我们发现了元素或者查找所有元素后都没有找到匹配项就停止。
例:
Set position to 0
WHILE(position < 10 AND found is FALSE)
IF(number[position]equals searchltem)
Set found to TRUE
ELSE
Set position to position + 1
布尔操作符包括特殊操作符AND、OR和NOT。AND操作符只有在表达式都为真时返回值才是TRUE,否则返回FALSE。OR操作符只有在表达式都为假时返回FALSE,其余返回TRUE。NOT操作符改变表达式的值。
有序数组中的顺序搜索
二分检索
二分检索算法假设要检索的数组是有序的,其中每次比较操作可以找到要找的项目或把数组减少一半。二分检索不是从数组开头开始顺序前移,而是从数组中间开始。如果要检索的项目小于数组的中间项,那么可以知道这个项目一定不会出现在数组的后半部分,因此只需要搜索数组的前半部分即可。
然后再检测数组的“中间”项(即整个数组1/4处的项目)。如果要检索的项目大于中间项,搜索将在数组的后半部分继续。如果中间项等于正在搜索的项目,搜索将终止。每次比较操作都会将搜索范围缩小一半。当要找的项目找到了,或可能出现在这个项目的数组为空的情况,整个过程将终止。
例:
Boolean Binary Search
Set first to 0
Set last to length-1
Set found to FALSE
WHILE(first<=last AND NOT found)
Set middle to (first + last)/2
IF(item equals data[middle])
Set found to TRUE
ELSE
IF(item < data[middle])
Set last to middle - 1
ELSE
Set first to middle + 1
Return found
5.排序
选择排序
对具有5个元素的数组排序,可以把这个数组看作由两个部分构成,即无序部分和有序部分。每当把一个项目放到正确的位置,无序部分就缩小了,而有序部分则扩展了。排序开始时,所有的项目都位于无序部分;排序结束时,所有项目都位于有序部分。
冒泡排序
冒泡排序也是一种选择排序法,只是在查找最小值时采用了不同的方法。它从数组的最后一个元素开始,比较相邻的元素对,如果下面的元素 小于上面的元素,就交换这两个元素的位置。通过这种方法,最小的元素就会“冒”到数组的顶部。每次都会把未排序的最小元素放到它的正确位置,不过这同时会改变数组中其他元素的位置。
插入排序
如果数组中只有一个元素,那么它就是有序的。如果有两个元素,需要的话可以进行比较和交换。现在,这两个元素是有序的,根据这两个元素把第三个元素放在合适的位置。现在相当于彼此前三个元素就是有序的。将元素加入有序部分类似于冒泡排序中冒泡的过程。如果找到一个位置,要插入的元素比数组中这个位置的元素小,那么就将新元素插入这个位置。
6.递归算法
递归(recursion):算法调用它本身的能力。
递归算法的两种情况:
- 基本情况
基本情况是答案已知的情况。 - 一般情况
一般情况则是调用自身来解决问题的更小版本的解决方案。
子程序语句
子程序有两种形式,一种是只执行特定任务的命名代码,一种是不仅执行任务,还返回给调用单元一个值(值返回子程序)。第一种形式的子程序在调用单元中用作语句,第二种则用作表达式,返回的值被用来评估表达式。
递归阶乘
数的阶乘的定义是这个数与0和它自身之间的所有数的乘积,即:
N!=N(N - 1)!
0的阶乘是1.尺寸系数就是要计算阶乘的数。
基本情况是:
Factorial(0)=1
一般情况是:
Factorial(N)=NFactorial(N - 1)
递归二分检索
快速排序
7.几个重要思想
信息隐蔽
信息隐蔽(information hiding):隐蔽模块的细节以控制对这些细节的访问的做法。
抽象
抽象(abstraction):复杂系统的一种模型,只包括对观察者来说的必须章节。
数据抽象(data abstraction):把数据的逻辑视图和它的实现分离开。
过程抽象(procedural abstraction):把动作的逻辑视图和它的现实分离开。
控制抽象(control abstraction):把控制结构的逻辑视图和它的现实分离开。
控制结构(control structure):用于改变正常的顺序控制流的语句。