当前位置: 首页 > 文档资料 > C++大学教程 >

2.9 构造算法与自上而下逐步完善:实例研究2(标记控制重复)

优质
小牛编辑
131浏览
2023-12-01

下面将全班平均成绩问题一般化,考虑如下问题:

开发一个计算全班平均成绩的程序,在每次程序运行时处理任意个成绩数。
在第一个全班平均成绩例子中,成绩个数(10)是事先预置的。而本例中,则不知道要输入多少个成绩,程序要处理任意个成绩数。程序怎么确定何时停止输入成绩呢?何时计算和打印全班平均成绩呢?

一种办法是用一个特殊值作为标记值(sentinelvalue),也称信号值(signalvalue)、哑值(dummy value)或标志值(flag value),表示数据输入结束(“end of data entry”)用户输入成绩,直到输入所有合法成绩。然后用户输入一个标记值,表示最后一个成绩已经输入。标记控制重复(sentinel-controlled repetition)也称为不确定重复(indefinite repetition),因为执行循环之前无法事先知道重复次数。
显然,标记值不能与可接受的输入值混淆起来。由于考试成绩通常是非负整数,因此可以用-1作标记值。这样,全班平均成绩程序可以处理95、96、75、74、89和-l之类的输人流。程序计算并打印成绩95、96、75、74和89的全班平均成绩(不计入-1,因为它是标记值)。

常见编程错误2.8

将选择的标记值与可接受的输入值混淆时会造成逻辑错误。

我们用自上而下逐步完善(top-down,stepwise refinement)的方法开发计算全班平均成绩的程序,这是开发结构化程序的重要方法。我们首先生成上层伪代码表示:

Determine the class avcraqe for the quiz

上层伪代码只是一个语句,表示程序的总体功能。这样.上层等于是程序的完整表达式。但上层通常无法提供编写C++程序所需的足够细节。因此要开始完善过程。我们将上层伪代码分解为一系列的小任务,按其需要完成的顺序列出。这个结果就是下列第一步完善(first,refinement):

Initialize variables
Input,sum,and count the quiz grades
Calculate and print the class average

这里只用了顺序结构,所有步骤按顺序逐步执行。

软件工程视点2.4

上层伪代码及每一步完善都是算法的完整定义,只是详细程度不同而已。

软件工程视点2.5

许多程序可以在逻辑上分为三个阶段:初初化阶段将程序变量初始化,处理阶段输入数据值和相应调整程序变量,结束阶段计算和打印最后结果。

上述“软件工程视点”通常是自上而下过程第一步完善的全部工作。要进行下一步完善(即第二步完善,second refinement),我们要指定特定变量,要取得数字的动态和以及计算机处理的数值个数,用一个变量接收每个输入的成绩值,一个变量保存计算平均值。下列伪代码语句:

Initialize variables

可以细化成:

Initialize total to zero
Initialize counter to zero

注意,只有total和counter变量要先初始化再使用,average和grade变量(分别计算平均值和用户输入)不需要初始化.因为它们的值会在计算或输入时重定义。
下列伪代码语句:

Input, sum, and count the quiz grades

需要用重复结构(即循环)连续输入每个成绩。由于我们事先不知道要处理多少个成绩,因此使用标记控制重复。用户一次一项地输入合法成绩。输入最后一个合法成绩后,用户输人标记值。程序在每个成绩输入之后测试其是否为标记值.如果用户输入标记值,则顺序循环终止。上述伪代码语句的第二步完善如下:

Input the first grade (possibly the sentinel)
While the user has not as yet entered the sentinel
Add this grade into the running total
Add one to the grade counter
Input the next grade (possibly the sentinel)

注意,在这个伪代码中,我们没有在while结构体中使用花括号,只是在while下面将这些语句缩排表示它们属于while。伪代码只是非正式的程序开发辅助工具。
下列伪代码语句可以完善如下:

If the counter is not equal to zero
Set the average to the total divided by the counter
Print the average
else
Print "No grades were entered"

注意我们这里要测试除数为0的可能性,这是个致命逻辑错误,如果没有发现,则会使程序失败(通常称为爆炸或崩溃)。图2.8显示了全班平均成绩问题第二步完善的完整伪代码语句。

常见编程错误2.9

除数为0是个致命逻辑错误。

编程技巧2.9

进行除法时,要测试除数为0的可能性,并在程序中进行相应处理(如打印一个错误消息).而不是让致命逻辑错误发生。

图2.6和图2.8的伪代码中增加了一些空行,使伪代码更易读。空行将程序分成不同阶段。

图2.8所示的伪代码算法解决更一般的全班平均成绩问题,这个算法只进行了第二步完善,还需要进一步完善。

Initialize total to zero
Initialize counter to zeroInput the first grade (possibly the sentinel)
While the user has not as yet entered the sentinel
Add this grade into the running total
Add one to the grade counter
Input the next grade (possibly the sentinel)if the counter is not rqual to zero
Set the average to the total divided by the counter
Print the average
else
Print "No grades were entered"

图2.8  用标记符控制重复解决全班平均成绩问题的伪代码算法

软件工程视点2.6

伪代码算法的细节足以将伪代码变为C++代码时,程序员即可停止自上而下逐步完善的过程,然后就可方便地实现C++程序。

图2.9显示了C++程序和示例执行结果。尽管只输入整数成绩,但结果仍然可能产生带小数点的平均成绩,即实数。int类型无法表示实数,程序中引入float数据类型处理带小数点的数(也称为浮点数,floatingpoint number),并引入特殊的强制类型转换运算符(cast operator)处理平均值计算。这些特性将在程序之后详细介绍。

// Fig. 2.9: fig02_09.cpp
// Class average program with sentinel-controlled repetition.
#include 
#include 

int main(){
  int total,    // sum of grades
  gradeCounter, // number of grades entered
  grade;      // one grade
  float average;  // number with decimal point for average

  // initialization phase
  total = 0;
  gradeCounter = 0;

  // processing phase
  cout << "Enter grade, -1 to end: "; cin >> grade;

  while ( grade !=-1 ) {
    total = total + grade;
    gradeCounter = gradeCounter + 1;
    cout << "Enter grade, -1 to end: "; cin >> grade;
  }

  // termination phase
  if ( gradeCounter != 0 ) {
    average - static_cast< float >( total ) / gradeCounter;
    cout << "Class average is "<< setprecision{ 2 )
    << setiosflags( ios::fixed | ios::showpoint )
    << average << endl;
    }
    else
    cout << "NO grades were entered" << endl;

    return 0;        // indicate program ended successfully
  }
}

输出结果:

Enter grade, -1 to end: 75
Enter grade, -1 to end: 94
Enter grade, -1 to end: 97
Enter grade,-1 to end: 88
Enter grade, -1 to end: 70
Enter grade, -1 to end: 64
Enter grade, -1 to end: 83
Enter grade, -1 to end: 89
Enter grade, -1 to end: -1
Class average is 82.50

图2.9  用标记符控制重复解决全班平均成绩问题的C++程序和示例执行结果

注意图2.9中while循环中的复合语句。如果没有花括号,则循环体中的最后三条语句会放到循环以外,使计算机错误地理解如下代码:

while { grade ! = -1 )
total - total + grade;
gradeCounter = gradeCounter + 1;
cout << "Enter grade, -1 to end:";
cin >> grade;

如果用户输入的第一个成绩不是-l,则会造成无限循环。

注意下列语句:

cin >> grade;

前面用一个输出语句提示用户输入。

编程技巧2.10

提示用户进行每个键盘输入。提示应表示输入形式和任何特殊输入值(如用户终止循环时输入的标记值)。

编程技巧2.11

在标记控制循环中,提示请求输入数据项目时应显式指定标记值是什么值。

平均值并不一定总是整数值,而常常是包含小数的值,如7.2或-93.5。这些值称为浮点数,用数据类型float表示。变量average声明为数据类型float,以获得计算机结果中的小数。但total/gradeCounter的计算结果是整数,因为total和gradeCounter都是整数变量。两个整数相除是整除(integer division),小数部分丢失(即截尾,truncated)。由于先要进行计算,因此小数部分在

将结果赋给average之前已经丢失。要用整数值进行浮点数计算,就要先生成用于计算的临时浮点数值。

C++提供了一元强制类型转换运算符(unary cast operator)。下列语句:

average = static cast< float >(total) / gradeCounter;

包括一元强制类型转换运算符static_cast<float>(),生成用于计算的临时浮点数值(total)。这样使用强制类型转换运算符称为显式类型转换(explicit conversion)。total中存放的值还是整数,而计算时则用浮点数值(total的临时float版本)除以整数gradcCounter。

c++编译器只能对操作数的数据类型一致的表达式求值。要保证操作数的数据类型一致,编译器对所选择的操作数进行提升(promotion)操作(也称为隐式类型转换,implicit conversion)。例如,在包含数据类型float和int的表达式中,int操作数提升为float。

本例中,gradeCounter提升为float之后进行计算,将浮点数除法得到的结果赋给average。本章稍后将介绍所有标准数据类型及其提升顺序。任何数据类型都可用强制类型转换运算符,static_cast运算符由关键字statlc cast加尖括号(<>)中的数据类型名组成。

强制类型转换运算符是个一元运算符(unary perator),即只有一个操作数的运算符。第1章曾介绍过二元算术运算符。C++也支持一元正(+)、负(-)运算符,程序员可以编写-7、+5之类的表达式。强制类型转换运算符从右向左结合,其优先级高于正(+)、负(-)运算符等其他一元运算符,该优先级高于运算符*、/和%,但低于括号的优先级。优先级表中用static_cast<type>()表示强制类型转换运算符。

图2.9中格式化功能将在第11章详细介绍,这里先做一简要介绍。下列输出语句中调用

setpreclslon(2):
cout<<"Class average is" << setprecision(2)
<< Setiosflaqs(iOS::fixed |iOS::showpoint)
<<averaqe<<endl;

表示float变量average打印小数点右边的位数为两位精度(precision),例如92.37,这称为参数化流操纵算子(parameterized stream manipulator)。使用这些调用的程序要包含下列预处理指令:

#include<iomanip.h>

注意endl是非参数化流操纵算子(nonparameterized stream manipulator),不需要iomanip.h头文件。如果不指定精度,则浮点数值通常输出六位精度(即默认精度,default precision),但稍后也会介绍一个例外。

上述语句中的流操纵算子setiosflags(ios::fixed | ios::showpoInt)设置两个输出格式选项ios::fixed和ios::showpoint。垂直条(1)分隔setiosflags调用中的多个选项(垂直条将在第16章详细介绍)。选项ios::fixed使浮点数值以浮点格式(而不是科学计数法,见第ll章)输出。即使数值为整数,ios::showpoInt选项也会强制打印小数点和尾部O,如88.OO。如果不用ios::showpoint选项,则C++将该整数显示为88,不打印小数点和尾部o。程序中使用上述格式时,将打印的值取整,表示小数点位数,但内存中的值保持不变。例如,数值87.945和67.543分别输出为87.95和67.54。

常见编程错误2.10

如莱在使用浮点敷时认为其精确地表示了敷值.则全得到不正确的蛄柬。浮点敷雇大多数计算机上都采用近似表示。

编程技巧2.12

不要比较浮点数值的相等和不等性,而要测试差值绝对值是否小于指定的值。

尽管浮点数算不总是100%精确,但其用途很广。例如,我们说正常体温98.6(华氏温度)时,并不需要精确地表示,如果温度计上显示98.6度.实际上可能是98.5999473210643度。这里显示98.6对大多数应用已经足够了。

另一种得到浮点数的方法是通过除法。10除以3得到3.333333……,是无限循环小敷。计算机只分配固定空间保存这种值,因此只能保存浮点值的近似值。