你好,C++(23) 4.4.2 工资程序成长记:用数组处理批量数据,用循环结构执行重复动作
4.4 从语句到程序
了解了各种表达式和语句之后,就相当于掌握了写作文要用到的词语和句子,但是,仅有词语和句子是无法构成一篇有意义的文章的。要完成一篇文章,先需要确定这篇文章的结构,是先分述再总述,还是逐层递进论述。定好结构后再按照结构的要求将词语和句子安排到合适的位置,这样才能写出一篇有意义的词句通顺的文章。编写程序就像写文章一样,也同样需要先根据需要处理的事务确定程序的流程控制结构,然后再将那些零散的语句串联起来描述一个完整的处理事务的过程,从而将多条零散的语句组织成可以完成一定功能的完整程序。
4.4.1 用三种流程控制结构将多条语句串联成程序
按照处理事务的流程的不同,C++中主要有下面三种程序流程控制结构:
1. 顺序结构
顺序结构是C++中最简单也最常用的一种流程控制结构。这种结构在执行的时候,它按照从上往下的顺序,依次逐条执行程序的语句。在整个过程中,执行顺序不会发生改变。所以,它通常被用来组织那些只需要一步步顺序执行就可以完成任务的语句。比如,一个简单的加法计算过程,从输入到计算再到输出,只需要依次顺序执行就可以完成,所以我们使用顺序结构来将那些分别负责输入(cin>>a>>b;)、计算(int c = a + b;)和输出(cout<<a<<" + "<<b<<" = "<<c<<endl;)的语句组织起来,就成了一个简单的加法计算程序:
// 顺序结构组织的加法计算程序
int main()
{
cout<<"请输入两个整数:"<<endl;
int a,b;
cin>>a>>b; // 输入
int c = a + b; // 计算
cout<<a<<" + "<<b<<" = "<<c<<endl; // 输出
return 0;
}
在执行的时候,从进入main()函数开始,首先执行输入语句(cin>>a>>b;),获得用户输入的两个数值a和b,然后依次往下执行计算语句(int c = a + b;),获得计算结果并保存到变量c,接下来执行输出语句(cout<<a<<" + "<<b<<" = "<<c<<endl;),将计算结果输出。这样,使用顺序结构组织的各条语句,只需要顺序执行就可以完成任务。
2. 选择结构
现实世界是复杂的,很多问题并不是只用顺序步骤就可以解决。还是以上面的求两个数之和的程序为例,如果要求输入的数必须大于0,也就是只有大于0的两个数才能计算它们的和。这时就需要先判断输入的数是否大于0,如果满足条件,就计算两个数的和;如果不满足,就提示用户输入的数不满足条件,无法计算。为了表达这种根据不同条件拥有不同处理方法的过程,C++提供了选择结构。选择结构依靠if等条件语句来实现,它可以根据不同条件做出判断,选择不同的执行路径,实现不同的处理过程。例如:
// 用选择结构实现的加法计算程序
int a,b;
cin>>a>>b;
// 选择结构,根据条件不同,执行的路径也不同
if( a > 0 && b > 0 )
{
// 如果用户输入的数据满足条件,
// 则计算结果并输出
int c = a + b;
cout<<a<<" + "<<b<<" = "<<c<<endl;
}
else
{
// 如果用户输入的数据不满足条件,
// 则提示用户输入错误
cout<<"请输入两个大于0的数。"<<endl;
}
利用选择结构,我们将对满足条件的情况进行处理的语句组织到if分支,将对不满足条件的情况进行处理的语句组织到else分支,这样程序在执行的时候,就会根据满足条件与否而执行不同的代码,实现了对“根据不同条件拥有不同处理方法”的表达。
3. 循环结构
再来看另外一种更复杂的情形:要求输入100个数后计算它们的总和。如果采用前面介绍的顺序结构,那么就要定义100个变量,用到100个输入语句来接收数据,同时也需要100个加和语句来将它们加到总和中。这么繁琐的计算过程显然是无法接受的,那么有什么方法可以简化一下这个计算过程呢?
人们发现这类计算过程有一个规律:这个计算过程中的多次输入和加和计算的代码是相似的,输入都是从cin获取输入数据到某个变量,而加和计算也都是将输入的数据累加到一个表示总和的变量上。正因为如此,在完成一次输入和加和计算后,可以利用相同的代码完成第二次输入和加和计算。这样,整个计算过程就成了一个输入和加和计算不断重复循环往复的过程。为了表达这一类的计算过程,C++提供了循环结构。循环结构依靠for等循环语句来实现,我们将那些需要重复多次执行的语句作为循环体语句,从而可以在循环过程中得到重复多次执行,以此来表达那些同一动作需要反复执行的计算过程。 在这里,负责输入(cin>>n;)和加和计算(nTotal += n;)的语句是需要重复多次执行的,所以我们将它作为for循环的循环体语句,然后在for循环中确定循环的起点和终点,以此串联成一个可以输入并计算100个数之和的程序:
// 用循环结构实现的连续加法计算程序
// 表示总和的变量
int nTotal = 0;
// for循环实现的循环结构
for(int i = 0; i < 100; ++i)
{
int n = 0;
cin>>n; // 输入语句
nTotal += n; // 加和计算语句
}
// 输出结果
cout<<"你输入的100个数的总和是:"<<nTotal<<endl;
在执行for循环的时候,会连续100次地执行循环体中的输入语句和加和计算语句,这样就省掉了使用顺序结构可能带来的繁琐,很好地表达了“某个动作需要反复多次执行”的计算过程。
顺序结构、选择结构和循环结构是三种最基本的程序控制结构。这三种结构不仅可以单独使用,更多时候,我们是将多种控制结构组合使用,以此来表达那些更加复杂的计算过程。例如在上面的例子中,如果我们只想要计算输入的所有正数的和,我们就可以在循环结构中嵌入一个选择结构,用循环结构来组织那些需要反复多次执行的动作,而每次循环执行的时候,都先用选择结构对数据进行筛选,只有符合条件的数才进行加和计算。循环结构和选择结构的嵌套就可以表达这一更加复杂的计算过程:
// 循环结构和选择结构的嵌套使用
// 循环结构,执行重复动作
for(int i = 0; i < 100; ++i)
{
int n = 0;
cin>>n;
// 选择结构,筛选数据,对符合条件的数据进行计算
if( n > 0)
{
nTotal += n;
}
}
通过这三种控制结构的组合,几乎可以描述任何复杂的程序执行流程。换句话说,也就是无论什么样的程序执行流程,无外乎是这三种结构或这三种结构的组合。而正是这三种控制结构将零散的实现简单操作的语句组织起来,表达一定的运算过程,最终才形成了实现一定功能的程序。
4.4.2 工资程序成长记:用数组处理批量数据,用循环结构执行重复动作
刚看过《你好,C++!》的前面几个章节,我们的“程序员”小陈就觉得自己的C++已经掌握得很不错了。很快,他就应聘到了一家软件公司做了一名程序员。
上班第一天,老板就给他分配了一个“大”任务:
“小陈啊,今天虽然是你上班的第一天,但公司现在有个大任务需要你去完成。你知道的,我们公司也是一个拥有近100000名员工的大公司。现在需要一个程序来对员工的工资进行统计和查询。统计就是需要知道所有员工的最低工资、最高工资和平均工资。查询就是需要可以根据员工的序号查询得到员工的工资。你的C++水平那么高,完成这样一个工资程序应该没有问题吧?”
“没,没,一点问题都没。”
小陈越听越心惊,到目前为止,虽然看过几天《你好,C++!》,但他还从来没有写过如此复杂的C++程序,心中实在没底,只好先答应下来,然后再回去看书想办法。等到小陈将《你好,C++!》再次仔细地看过一遍后,他惊喜地发现,要完成这个工资程序所需要的知识在书中早有论述:要对多个相同性质的批量数据(将近100000个工资数据)进行管理,可以采用数组;要多次重复执行某个动作(输入和查询工资数据),可以使用循环结构来组织语句;要根据不同条件执行不同的动作(如果输入特殊的数值-1,表示工资查询结束;如果输入的序号超出序号范围,提示用户重新输入;如果输入的序号在序号范围内,输出相应序号员工的工资),可以使用选择结构来实现不同的程序执行路径。经过简单的分析,小陈发现这个程序从整体而言是一个顺序结构:先输入数据,然后对数据进行查询;而每一个步骤又是一个循环结构:利用for循环多次输入数据,利用while循环多次查询数据;同时在循环结构中,又会嵌套选择结构,根据用户输入的不同执行不同的动作。按照这样的分析结果,小陈很快有了下面的工资程序:
// 工资程序 V1.0
#include <iostream>
#include <climits> // 为了使用整数最值宏INT_MAX,INT_MIN
using namespace std;
int main()
{
// 定义数组的最大数据元素量,
// 表示这个程序最多可以处理100000个工资数据
const int MAX = 100000;
// 定义数组并初始化,这个数组可以包含100000个int类型工资数据
int arrSalary[MAX] = {0};
// 定义记录工资总值、最小值和最大值的变量
// 因为min和max用于记录最小值和最大值,
// 所以我们分别将其初始化为int类型数据的最大值和最小值
int nTotal = 0;
int min = INT_MAX;
int max = INT_MIN;
// 输入的有效工资数据个数,计算平均工资和查询工资时需要
int nCount = 0;
// 利用for循环结构,重复执行输入动作,完成多个工资数据的输入
for(int i = 0; i < MAX; ++i)
{
// 提示用户输入
cout<<"请输入"<<i<<"号员工的工资(-1表示输入结束):"<<endl;
// 将输入的数据保存到arrSalary数组的第i个数据元素
cin>>arrSalary[i];
// 利用条件结构,检查是否输入了表示结束的特殊值
if(-1 == arrSalary[i] )
{
// 输入结束,输出统计结果
cout<<"工资输入结束,一共输入了"<<nCount<<"个工资数据。"<<endl;
// 如果输入的数据个数不为0,输出统计信息
if(0 != nCount)
{
cout<<"其中,"<<endl;
cout<<"最大值是"<<max<<endl;
cout<<"最小值是"<<min<<endl;
// 计算平均工资
float fAver = (float)nTotal/nCount;
cout<<"平均工资是"<<fAver<<endl;
}
// 输入结束,用break关键字结束整个输入循环
break;
}
// 如果是正常输入,则进行常规处理
++nCount; // 工资数据个数加1
// 累加工资总额
nTotal += arrSalary[i];
// 更新工资的最大值和最小值
if(arrSalary[i] < min) // 如果新输入的数值比已知的最小值min小
min = arrSalary[i];// 用新输入的值取代旧的最小值
if(arrSalary[i] > max) // 最大值相似处理
max = arrSalary[i];
}
// 输入过程结束,开始查询过程
// 如果数据个数为0,表示没有输入数据,不再进行查询而直接结束
if(0 == nCount)
{
cout<<"没有工资数据,无法进行查询。感谢使用!"<<endl;
return 0; // 直接结束程序
}
// 拥有工资数据,构造无限循环进行工资查询,在循环中根据条件退出循环
while(true)
{
// 输入的员工序号
int n = 0;
// 提示用户输入
cout<<"请输入要查询的员工序号(0-"<<nCount-1
<<",-1表示结束查询):"<<endl;
// 获取用户输入的员工序号并保存到n
cin>>n;
// 对用户输入进行检查
if(-1 == n) // 是否输入了表示结束的特殊值
{
// 查询结束,用break关键字结束整个查询循环
cout<<"查询完毕,感谢使用!"<<endl;
break;
}
else if(n < 0 || n >= nCount) // 是否超出序号范围
{
// 提示用户输入错误
cout<<"输入的序号"<<n<<"超出了序号范围0-"
<<nCount-1<<",请重新输入。"<<endl;
// 输入的序号超出范围, 用continue结束本次循环,开始下一次循环
continue;
}
// 输入合法,输出用户查询的员工工资
cout<<"员工序号:"<<n<<endl;
// 这里用输入的员工序号作为数组下标,直接得到对应位置上的工资数据
cout<<"员工工资:"<<arrSalary[n]<<endl;
}
return 0;
}
在简单试用几次之后,小陈对这个工资程序还算满意。于是他赶紧把这个程序拿去向老板交差。老板试了试发现,这个工资程序不仅能够处理足够多的工资数据,能够对工资数据进行统计和查询,同时还有非常好的用户操作提示,连三岁小孩子都会用。老板非常高兴,喜笑颜开地对小陈说:“干得不错,下个月,涨工资,啊哈哈哈…… ”听到这句话,小陈在心里乐开了花,心中暗想,辛亏手头有一本《你好,C++!》,问题才能够如此顺利地被解决。看来这真是一本好学又好用的C++参考书,下班回去之后一定接着往下看……