结构化程序设计
自顶向下的分解,每个小问题设计为一个函数,公共小问题可以设计成一个库实现阶段
自底向上的实现
程序由3种基本结构组成
- 顺序
- 分支
- 循环
功能
- 提供游戏指南
- 计算机随机产生正反面,让用户猜,报告对错结果
- 重复玩游戏过程,直到用户不想玩了为止
顶层分解
程序要做两件事:显示程序指南;模拟玩游戏的过程。
void prn_instruction();
void play();
prn_instruction()
的实现
只要一系列的输出语句把程序指南显示一下就可以了
void prn_instruction()
{
cout << "这是一个猜硬币正反面的游戏.\n";
cout << "我会扔一个硬币,你来猜.\n";
cout << "如果猜对了,你赢,否则我赢。\n";
}
play()
函数的实现
随机产生正反面,让用户猜,报告对错结果,然后询问是否要继续玩
- 生成正反面:生成0和1两个随机数
- 输入用户的猜测:抽象成一个函数get_call_from_user。
void play()
{
char flag = ‘y’;
while ( flag == ‘Y’ || flag == ‘y’)
{
coin = 生成正反面;
输入用户的猜测;
if (用户猜测 == coin)
报告本次猜测结果正确;
else
报告本次猜测结果错误;
询问是否继续游戏
}
}
产生随机数
srand(time(NULL));
coin = rand() * 2 / RAND_MAX;
get_call_from_user()
的实现
该函数接收用户输入的一个整型数。如果输入的数不是0或1,则重新输入,否则返回输入的值
int get_call_from_user()
{
int guess;
do
{
cout << "\n输入你的选择(0表示正面,1表示反面):";
cin >> guess;
} while (guess != 0 && guess != 1);
return guess;
}
枚举类型与头文件
知识点
程序设计时,应先考虑总体,后考虑细节;先考虑全局目标,后考虑局部目标。不要一开始就过多追求众多的细节,先从最上层总目标开始设计,逐步使问题具体化。
石头、剪子、布游戏
要求
-
游戏规则:布覆盖石头;石头砸坏剪刀;剪刀剪碎布
-
游戏过程:
-
游戏者选择出石头、剪子或布
-
计算机也随机选择一个
-
输出输赢结果
-
继续游戏,直到游戏者选择结束为止
-
在此过程中,游戏者也可以阅读游戏指南或看看当前战况
-
第一层的分解:函数抽取(获取用户输入;获取机器输入;评判结果 ;报告结果并记录结果信息;显示目前战况;显示帮助信息)
类型定义格式
enum 枚举类型名 {元素表};
石头、剪子、布中的枚举类型
用户输入值的类型:enum p_r_s { paper, rock, scissor, game, help, quit } ;
比较结果类型:enum outcome { win, lose, tie } ;
枚举类型变量的定义
p_r_s select;
枚举类型变量的使用
赋值: select = paper;
比较:paper < rock 比较这两个值的内部表示
枚举类型不能直接输入输出
枚举类型的内部表示
-
采用编码表示:默认用0代表pape, 1代表rock ,…,5 表示quit
-
指定编码值
-
希望从1而不是0开始编号,可以这样定义
enum p_r_s { paper = 1, rock, scissor, game, help, quit } ;
-
可以从中间某一个开始重新指定,如
enum p_r_s { paper, rock = 5, scissor, game, help, quit } ;
-
选择题
1.
阅读下面的代码,写出输出结果
#include <bits/stdc++.h>
using namespace std;
enum fruits
{
apple,
banana,
orange,
grapes = 4,
strawberry,
pear
};
enum students
{
math,
chemistry,
cs,
physics,
biology
};
int main()
{
fruits a = apple, b = orange, c = strawberry;
students d = math, e = physics;
cout << (int)a << ' ' << (int)b << endl;
cout << ((a == d) ? "Yes" : "No") << endl;
cout << ((c <= e) ? "Yes" : "No") << endl;
return 0;
}
概念
把程序再分成几个小的源文件。每个源文件都包含一组相关的函数
一个源文件被称为一个模块
模块划分标准
同一模块中的函数比较类似
块内联系尽可能大,块间联系尽可能小
案例——石头、剪刀、布游戏
游戏规则
布覆盖石头,石头砸坏剪刀,剪刀剪碎布
游戏的过程
- 游戏者选择出石头、剪子或布
- 计算机也随机选择一个
- 输出输赢结果
- 继续游戏,直到游戏者选择结束为止
- 在此过程中,游戏者也可以阅读游戏指南或看看当前战况。
石头、剪子、布中的枚举类型定义
- 用户输入值的类型:enum p_r_s { paper, rock, scissor, game, help, quit } ;
- 比较结果类型:enum outcome { win, lose, tie } ;
模块划分
- 主模块:main函数
- 获取选择的模块: selection_by_player、selection_by_machine
- 比较模块:compare
- 输出模块: report、prn_game_status、prn_help
Select模块的设计
-
selection_by_player
功能:从键盘接收用户的输入并返回此输入值
原型:
p_r_s selection_by_player();
-
selection_by_machine
功能:由机器产生一个石头、剪子、布的值,并返回
原型:
p_r_s selection_by_machine();
Compare模块的设计
- 功能:compare函数比较用户输入的值和机器产生的值,确定输赢
- 参数:用户输入的值和机器产生的值,都是p_r_s类型的
- 返回值:判断的结果 , 是outcome类型
- 原型:
outcome compare( p_r_s, p_r_s );
print模块的设计
-
prn_help
功能:显示一个用户输入的指南,告诉用户如何输入他的选择。它没有参数也没有返回值
原型:
void prn_help();
-
Report
功能:函数报告输赢结果,并记录输赢的次数
参数:输赢结果、输的次数、赢的次数和平局的次数
返回值:无
原型:
void report(outcome result);
-
prn_game_status
功能:报告至今为止的战况
参数:输的次数、赢的次数和平的次数
返回值:无
原型:
void prn_game_status();
头文件
包含所有的符号常量定义、类型定义和函数原型声明
每个模块都include这个头文件
注意
链接时,编译器会发现这些类型定义、符号常量和函数原型的声明在程序中反复出现多次
解决方法
需要用到一个新的编译预处理命令:
#ifndef 标识符
…
#endif
头文件实现格式
#ifndef _name_h
#define _name_h
头文件真正需要写的内容
#endif
石头、剪刀、布游戏的头文件
// 文件:p_r_s.h
// 本文件定义了两个枚举类型,声明了本程序包括的所有函数原型
#ifndef P_R_S
#define P_R_S
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
enum p_r_s
{
paper,
rock,
scissor,
game,
help,
quit
};
enum outcome
{
win,
lose,
tie
};
outcome compare(p_r_s player_choice, p_r_s machine_choice);
void prn_game_status();
void prn_help();
void report(outcome result);
p_r_s selection_by_machine();
p_r_s selection_by_player();
#endif
1.在cpp
文件内include
自定义的头文件,例如myMath.h
,则代码是:#include ___________
2.为了防止对某个头文件的重复包含,需要在每个头文件的开头添加编译预处理指令,指令名是_______和_______
3.c++支持将cpp
实现代码混入头文件中,来简化代码编写,c++程序的头文件后缀名是 ____
4.在头文件.h
中如果要声明一个int
型变量x
,需要在代码int x;
之前添加 ______ 关键字,并在.cpp
文件中定义int x;
主模块的实现
// 文件:main.cpp
// 石头、剪子、布游戏的主模块
#include "p_r_s.h"
int main()
{
outcome result;
p_r_s player_choice, machine_choice;
// seed the random number generator
srand(time(NULL));
while ((player_choice = selection_by_player()) != quit)
switch (player_choice)
{
case paper:
case rock:
case scissor:
machine_choice = selection_by_machine();
result = compare(player_choice, machine_choice);
report(result);
break;
case game:
prn_game_status();
break;
case help:
prn_help();
}
prn_game_status();
return 0;
}
Select模块的实现
//文件:select.cpp
//包括机器选择selection_by_machine和玩家选择selection_by_player函数的实现
#include "p_r_s.h"
p_r_s selection_by_machine()
{
int select = (rand() * 3 / (RAND_MAX + 1));
cout << " I am ";
switch (select)
{
case 0:
cout << "paper. ";
break;
case 1:
cout << "rock. ";
break;
case 2:
cout << "scissor. ";
break;
}
return ((p_r_s)select);
}
p_r_s selection_by_player()
{
char c;
p_r_s player_choice;
prn_help();
cout << "please select: ";
cin >> c;
switch (c)
{
case 'p':
player_choice = paper;
cout << "you are paper. ";
break;
case 'r':
player_choice = rock;
cout << "you are rock. ";
break;
case 's':
player_choice = scissor;
cout << "you are scissor. ";
break;
case 'g':
player_choice = game;
break;
case 'q':
player_choice = quit;
break;
default:
player_choice = help;
break;
}
return player_choice;
}
Compare模块的实现
//文件:compare.cpp
//包括compare函数的实现
#include "p_r_s.h"
outcome compare(p_r_s player_choice, p_r_s machine_choice)
{
outcome result;
if (player_choice == machine_choice)
return tie;
switch (player_choice)
{
case paper:
result = (machine_choice == rock) ? win : lose;
break;
case rock:
result = (machine_choice == scissor) ? win : lose;
break;
case scissor:
result = (machine_choice == paper) ? win : lose;
}
return result;
}
Print模块的实现
//文件:print.cpp
//包括所有与输出有关的模块。
//有prn_game_status,prn_help和report函数
#include "p_r_s.h“
static int win_cnt = 0, lose_cnt = 0, tie_cnt = 0; //模块的内部状态
void report(outcome result)
{
switch (result)
{
case win:
++win_cnt;
cout << "You win. \n";
break;
case lose:
++lose_cnt;
cout << "You lose.\n";
break;
case tie:
++tie_cnt;
cout << "A tie.\n";
break;
}
}
void prn_game_status()
{
cout << endl;
cout << "GAME STATUS:" << endl;
cout << "win:" << win_cnt << endl;
cout << "Lose:" << lose_cnt << endl;
cout << "tie:" << tie_cnt << endl;
cout << "Total:" << win_cnt + lose_cnt + tie_cnt << endl;
}
void prn_help()
{
cout << endl
<< "The following characters can be used:\n"
<< " p for paper\n"
<< " r for rock\n"
<< " s for scissors\n"
<< " g print the game status\n"
<< " h help, print this list\n"
<< " q quit the game\n";
}
设计自己的库
库的概念
库:常用的工具
库的主题:同一个库中的函数都应该是处理同一类问题,自己设计的库也要有一个主题
库的通用性:在某一应用程序中提取库内容时应尽量考虑到兼容更多的应用,使其他应用程序也能共享这个库
库的设计和实现
设计库的接口:库的用户必须了解的内容,包括库中函数的原型、这些函数用到的符号常量和自定义类型,接口表现为一个头文件
设计库中的函数的实现:表现为一个源文件
库的这种实现方法称为信息隐藏
接口文件
头文件的格式
注释
头文件头上有段注释,说明库的主题、功能
每个函数声明前有一段注释,告诉用户如何使用这些函数
随机函数库接口文件
//文件:Random.h
//随机函数库的头文件
#ifndef _random_h
#define _random_h
//函数:RandomInit
//用法:RandomInit()
//作用:此函数初始化随机数种子
void RandomInit();
//函数:RandomInteger
//用法:n = RandomInteger(low, high)
//作用:此函数返回一个 low 到 high 之间的随机数,包括 low 和 high
int RandomInteger(int low, int high);
#endif
库的实现
实现文件名:与头文件的名字是相同
实现文件的格式
- 注释(这一部分简单介绍库的功能)
include
此cpp
文件所需的头文件- 每个实现要包含自己的头文件,以便编译器能检查函数定义和函数原型声明的一致性
- 每个函数的实现代码(在每个函数实现的前面也必须有一段注释)
随机函数库实现文件
//文件:Random.cpp
//该文件实现了Random库
#include <cstdlib>
#include <ctime>
#include "Random.h"
//函数:RandomInit
//该函数取当前系统时间作为随机数发生器的种子
void RandomInit()
{
srand(time(NULL));
}
// 函数:RandomInteger
// 该函数将0到RAND_MAX的区间的划分成high - low + 1 个 子区间。当产生的随机数落在第一个
// 子区间时,则映射成low。 当落在最后一个子区间时,映射成high。当落在第 i 个子区间时
//(i 从 0 到 high-low),则映射到low + i
int RandomInteger(int low, int high)
{
return (low + (high - low + 1) * rand() / (RAND_MAX + 1));
}
1.统计学的一些计算在计算机的帮助下变得准确和高效,下面请给出这个有关统计学的函数库的接口文件,其中需要包含的函数和要求如下。
- 算数平均值计算
Average()
:传入两个double
指针代表数组的起始位置和结束位置的后一位,返回一个double
表示答案 - 最值统计
MinMax()
:数组表示同上,同时通过两个double
的引用传递记录最小最大值的答案,无返回值 - 中位数统计
Median()
:数组表示同上,返回一个double
表示答案 - 众数统计
Mode()
:数组表示同上,返回一个double
表示答案
//文件:statistics.h
//统计学函数库
#ifndef _statistics_h
#define _statistics_h
//函数:Average
//用法:double ave = Average(double*, double*)
//作用:计算从数组中所有实数的算数平均值
double Average(double *begin, double *end);
//函数:MinMax
//用法:MinMax(double *, double *, double &, double &)
//作用:统计数组的最小最大值,并且通过引用传参得到结果
void MinMax(double *begin, double *end, double &Min, double &Max);
//函数:Median
//用法:double med = Median(double*, double*)
//作用:计算从数组中所有实数的中位数
double Median(double *begin, double *end);
//函数:Mode
//用法:double mod = Mode(double*, double*)
//作用:计算从数组中所有实数的众数
double Mode(double *begin, double *end);
#endif
龟兔赛跑规则
解题思路
- 分别用变量tortoise和hare代表乌龟和兔子的当前位置
- 时间用秒计算
- 用随机数来决定乌龟和兔子在每一秒的动作
- 根据动作决定乌龟和兔子的位置的移动
- 跑道的长度设为70个点
模块划分
主模块:main
移动模块:move_tortoise、move_hare
输出模块:print_position
实现
主模块
#include "Random.h" //包含随机数库
#include <iostream>
using namespace std;
const int RACE_END = 70; //设置跑道的长度
int move_tortoise();
int move_hare();
void print_position(int, int, int);
int main()
{
int hare = 0, tortoise = 0, timer = 0;
RandomInit(); //随机数初始化
cout << "timer tortoise hare\n"; //输出表头
while (hare < RACE_END && tortoise < RACE_END)
{
tortoise += move_tortoise(); //乌龟移动
hare += move_hare(); //兔子移动
print_position(timer, tortoise, hare);
++timer;
}
if (hare > tortoise)
cout << "\n hare wins!\n";
else
cout << "\n tortoise wins!\n";
return 0;
}
Move模块
用户行为生成方法
- 利用随机数的等概率生成0-9之间的随机数
- 当生成的随机数为0-4时,认为是第一种情况,5-6是第二种情况,7-9是第三种情况
// 文件名:move.cpp
#include "Random.h" //本模块用到了随机函数库
int move_tortoise()
{
int probability = RandomInteger(0, 9); //产生0到9之间的随机数
if (probability < 5)
return 3; //快走
else if (probability < 7)
return -6; //后滑
else
return 1; //慢走
}
int move_hare()
{
int probability = RandomInteger(0, 9);
if (probability < 2)
return 0; //睡觉
else if (probability < 4)
return -9; //大后滑
else if (probability < 5)
return 14; //快走
else if (probability < 8)
return 3; //小步跳
else
return -2; //慢后滑
}
Print模块
// 文件名:print.cpp
#include <iostream>
using namespace std;
void print_position(int timer, int t, int h)
{
if (timer % 6 == 0) //每隔6秒空一行
cout << endl;
cout << timer << '\t' << t << '\t' << h << '\n';
}