作业描述 | 详情 |
---|---|
这个作业属于哪个课程 | 班级链接 |
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 实践题: 新建一个github仓库,使用git,或者github desktop把接下去的编程题的代码及测试脚本传到这个仓库。 编程题: 1. 优化架构,一般要求每个函数长度不超过15行。 2. 优化规范,尤其是命名规范。 3. 制作一个编译脚本,运行该脚本可以编译你的代码,可选的脚本语言,python(2.7),windows批处理,powershell,shell。 4. 进行单元测试,即测试每一个函数,并制作一个测试脚本,运行该脚本可以进行测试,并显示测试结果。 5. 在作业一编程题的基础上添加以下功能:通过命令行读取一个文件,然后运行这个文件。 |
作业正文 | 2020面向对象程序设计寒假作业2 题解 |
其他参考文献 | GitHub Guides 班级Git仓库-帮助 git设置忽略文件.gitignore C++按行读取和写入文件 argc和argv的用法 |
实践题
创建 GitHub 仓库的步骤参考了 原网站的文章
GitHub Desktop 的使用参考了 班级Git仓库-帮助
最后使用了 GitHub Desktop 来创建仓库并上传作业
参考了 该博客 的方法写了.gitignore 文件
编程题
代码已上传至本人 GitHub仓库
本次编程试着使用了骆驼命名法与帕斯卡命名法
题目要求
-
实现以 汉字 为变量名的,范围为 零至九十九 的 整数
-
实现 整数......等于 操作,使得上述变量得以申请与赋初值
-
实现 增加 与 减少 操作,使得上述变量得以修改
-
实现 看看 操作,使得上述变量的值得以输出
可能存在的要求:
-
实现 整数 操作,使得申请上述变量
-
实现 等于 操作,给上述变量赋值
-
设计错误抛出,避免程序崩溃
需求分析
-
输入输出全为中文,需要实现汉字与阿拉伯数字的互相转化
-
对于非法的输入,需要实现对非法输入的发现与错误的抛出
-
对于定义、增加、减少、赋值语句的实现
-
对语句的识别
思考过程
本次作业,本人试着用面向对象编程的思路完成。
第一个模块
首先,考虑到变量的个数不限制。因此考虑一个类为变量库(VariableRepository),实现变量的储存、查找与修改模块。
1.为了减少代码量,使用了 STL 中的 string 类来储存变量名
2.变量个数未知,因此使用了 STL 中的 vector 类来存储变量的值(VariableValue)
3.为了查询方便,使用了 STL 中的 map 类来记录从变量名(string)到变量在 vector 中地址,这一映射(VariableMap)
为了使该类中可以给定变量名与值,然后对变量进行操作,需要实现以下五种方法:
1.变量申请(VariableApply)
2.变量赋值(VariableAssign)
3.变量增加(VariableAdd)
4.变量减少(VariableMinus)
5.变量输出(VariableShow)
而第一个方法需要判定变量是否是不存在的,其它四个方法需要先判断变量是否是存在的,因此再增添一个方法实现变量的查询
6.查询变量地址(VariableFind)
第二个模块
其次,考虑到中文与数字之间的转换占了很大一部分,因此再开一个类为数字库(NumberRepository),实现中文与数字的互相转换
由于 gbk 码的“零”到“十”无规律可言,因此使用了一个 vector 来储存 0-10 所对应的汉字
由其基础功能,考虑到需要实现两个方法:
1.数字转汉字(ToChar)
2.汉字转数字(ToNumber)
而按照我们的储存方法只能实现单向的从数字识别汉字,所以需要一个新的方法实现它的逆向:
3.汉字查找(FindChar)
另外,再考虑到用户的输入方法并不相同,例如“十六”,的输入方法还包括“一六,一十六”等,因此在考虑一个方法实现它们的规范化:
4.汉字格式化(FormatChar)
第三个模块
除了上文两个类的功能以外,还需要实现的就是对语句的识别、执行以及错误的抛出了。由于这两个功能不便分开,因此只定义一个新的类世界(World)来实现
细分语句的执行与错误的抛出,包括了以下几个方法:
1.变量修改(Update)
2.变量申请(Apply)
3.变量输出(Print)
4.抛出错误变量不存在(NotExist)
5.抛出错误变量已申请(Applied)(指申请的变量名)
6.抛出错误数字错误(NumberError)(指右值无效)
7.抛出错误语句无法识别(Dontknow)(指语法错误)
8.抛出错误关键字冲突(ConflictError)(指申请的变量名)
加上语句的识别,以及读入、运行,就是再考虑以下三个方法
9.指令输入(Input)
10.指令识别(Understand)
11.运行(Run)
而为了实现是否与关键字冲突,还需要再实现一个方法:
12.是否与关键字冲突(IsConflict)
至此,以上的类足以实现所有要求的功能
总结
全过程涉及到的类,包括:变量库(VariableRepository)、数字库(NumberRepository)、世界(World)
下面总结各个类的属性与方法(下面提到的方法不涉及构造方法与析构方法):
类名 | 属性 | 方法 |
---|---|---|
变量库 (VariableRepository) |
1. 用 vector 储存的变量值(variableValue) 2. 用 map 实现的,从变量名到变量地址的映射(variableMap) |
1. 查询变量地址(VariableFind) 2. 申请新变量(VariableApply) 3. 变量赋值(VariableAssign) 4. 变量增加(VariableAdd) 5. 变量减少(VariableMinus) 6. 变量输出(VariableShow) |
数字库 (NumberRepository) |
1. 用 vector 实现的数字汉字转化(numberChar) | 1. 汉字格式化(FormatChar) 2. 汉字查找(FindChar) 3. 汉字转数字(ToNumber) 4. 数字转汉字(ToChar) |
世界 (World) |
1. 变量库(variable) 2. 数字库(number) |
1. 指令输入(Input) 2. 指令识别(Understand) 3. 变量修改(Update) 4. 变量申请(Apply) 5. 变量输出(Print) 6. 抛出错误变量不存在(NotExist) 7. 抛出错误变量已申请(Applied) 8. 抛出错误数字错误(NumberError) 9. 抛出错误语句无法识别(DontKnow) 10. 抛出错误关键字冲突(ConflictError) 11. 运行(Run) 12. 是否与关键字冲突(IsConflict) |
实现过程
变量库(VariableRepository)
其它类可以调用的变量库的方法,应该只有表中的2-6方法,而对于属性与方法1则可以考虑使用 private 保护
先构造其属性、构造函数与虚构函数
class VariableRepository{
private:
vector<int> variableValue;
map<string,int> variableMap;
public:
VariableRepository() {}
~VariableRepository() {}
};
其次实现方法1:没申请的变量一定在 map 中一定找不到
int VariableFind(string name){
if(variableMap.find(name)==variableMap.end()) return -1;//找不到
return variableMap[name];
}
接下来实现变量的申请:先判断是否已存在该变量,不存在则附初始值为0
bool VariableApply(string name){
if( VariableFind(name)!=-1 ) return false;//变量存在
variableMap[name]=variableValue.size();//存地址
variableValue.push_back(0);//赋初始值为0
return true;
}
其次实现增加、减少、赋值操作,由于功能类似,以赋值操作为例:先判断是否存在该变量,其次用引用修改,一定程度上可以增加代码可读性
bool VariableAssign(string name,int value){
if( VariableFind(name)==-1 ) return false;//变量不存在,下同
int &variable=variableValue[ VariableFind(name) ];//引用变量
variable=value;
return true;
}
变量查看的实现较简单,这里就不单独放代码了
这里放出 VariableRepository.h
的总代码
#include<vector>
#include<string>
#include<map>
using namespace std;
#ifndef VARIABLEREPOSITORY
#define VARIABLEREPOSITORY
class VariableRepository{
private:
vector<int> variableValue;
map<string,int> variableMap;
int VariableFind(string name){
if(variableMap.find(name)==variableMap.end()) return -1;//找不到
return variableMap[name];
}
public:
VariableRepository() {}
~VariableRepository() {}
bool VariableApply(string name){
if( VariableFind(name)!=-1 ) return false;//变量存在
variableMap[name]=variableValue.size();//存地址
variableValue.push_back(0);//赋初始值为0
return true;
}
bool VariableAssign(string name,int value){
if( VariableFind(name)==-1 ) return false;//变量不存在,下同
int &variable=variableValue[ VariableFind(name) ];//引用变量
variable=value;
return true;
}
bool VariableAdd(string name,int value){
if( VariableFind(name)==-1 ) return false;
int &variable=variableValue[ VariableFind(name) ];
variable+=value;
return true;
}
bool VariableMinus(string name,int value){
if( VariableFind(name)==-1 ) return false;
int &variable=variableValue[ VariableFind(name) ];
variable-=value;
return true;
}
int VariableShow(string name){
if( VariableFind(name)==-1 ) return -1;
return variableValue[ VariableFind(name) ];
}
};
#endif
数字库(NumberRepository)
同样地,先构造其属性、构造函数与析构函数
class NumberRepository{
private:
vector<string> numberChar;
public:
NumberRepository(){//初始化赋值
numberChar.push_back("零");
numberChar.push_back("一");
numberChar.push_back("二");
numberChar.push_back("三");
numberChar.push_back("四");
numberChar.push_back("五");
numberChar.push_back("六");
numberChar.push_back("七");
numberChar.push_back("八");
numberChar.push_back("九");
numberChar.push_back("十");
}
~NumberRepository() {}
};
其方法中也同样只有转汉字、转数字两个函数可供其它类调用即可,其它的都用 private 保护
先实现比较简单的转汉字:分别考虑1-10,11-19,几十以及其它形式
string ToChar(int number){
if(number<0||number>99) return "数字超限";
if(number<=10) return numberChar[number];
if(number<20) return numberChar[10]+numberChar[number-10];//十几的形式
if(number%10==0) return numberChar[number/10]+numberChar[10];//几十的形式
return numberChar[number/10]+numberChar[10]+numberChar[number%10];
}
其次是汉字查找:
int FindChar(string num){
for(int i=0;i<=10;i++) if( numberChar[i]==num ) return i;//遍历寻找
return -1;//找不到
}
接着是汉字的格式化:
- 汉字如果是有效数字,肯定每一位都是“零”到“十”的形式,因此我们应先进行字符的合法性检验,并顺便将其化为两个字的形式
- 如果汉字包含一个字,我们就格式化为“零*”的形式。由于“十”的特殊性,“零十”对于程序也是合法的
- 如果汉字包含两个字,那么一定不同时为“十”。然后分别讨论,第一个为“十”的,改为“一”;第二个为十的改为“零”
- 如果汉字包含三个字,那么一定是中间为“十”且首尾不为“十”。然后只要去除中间的“十”
实现如下:
bool FormatChar(string &num){
for(int i=0;i<=num.size()-2;i+=2) if( FindChar(num.substr(i,2))==-1 ) return false;//字符合法性检验
if( num.size()==2 ) num=numberChar[0]+num;//一个字,前补零,十因为特殊性也可以这样处理
else if( num.size()==4 ){
if( FindChar(num.substr(0,2))==10 && FindChar(num.substr(2,2))==10 ) return false;//两个数不同时为十
else if( FindChar(num.substr(0,2))==10 ) num=numberChar[1]+num.substr(2,2);//十几的形式
else if( FindChar(num.substr(2,2))==10 ) num=num.substr(0,2)+numberChar[0];//几十的形式
}
else if( num.size()==6 ){
if( FindChar(num.substr(0,2))==10 || FindChar(num.substr(2,2))!=10 || FindChar(num.substr(4,2))==10 )
return false;//首尾不能为十,中间一定要为十
num=num.substr(0,2)+num.substr(4,2);//去掉中间
}
return true;
}
最后对于转数字就简单了:先检验可否格式化成功,如果可以,则直接查表即可实现:
int ToNumber(string sentence){
if( !FormatChar(sentence) ) return -1;//失败
return FindChar( sentence.substr(0,2) )*10+FindChar( sentence.substr(2,2) );
}
下面给出 NumberRepository.h
的总代码
#include<string>
#include<vector>
using namespace std;
#ifndef NUMBERREPOSITORY
#define NUMBERREPOSITORY
class NumberRepository{
private:
vector<string> numberChar;
int FindChar(string num){
for(int i=0;i<=10;i++) if( numberChar[i]==num ) return i;//遍历寻找
return -1;//找不到
}
bool FormatChar(string &num){
for(int i=0;i<=num.size()-2;i+=2) if( FindChar(num.substr(i,2))==-1 ) return false;//字符合法性检验
if( num.size()==2 ) num=numberChar[0]+num;//一个字,前补零,十因为特殊性也可以这样处理
else if( num.size()==4 ){
if( FindChar(num.substr(0,2))==10 && FindChar(num.substr(2,2))==10 ) return false;//两个数不同时为十
else if( FindChar(num.substr(0,2))==10 ) num=numberChar[1]+num.substr(2,2);//十几的形式
else if( FindChar(num.substr(2,2))==10 ) num=num.substr(0,2)+numberChar[0];//几十的形式
}
else if( num.size()==6 ){
if( FindChar(num.substr(0,2))==10 || FindChar(num.substr(2,2))!=10 || FindChar(num.substr(4,2))==10 )
return false;//首尾不能为十,中间一定要为十
num=num.substr(0,2)+num.substr(4,2);//去掉中间
}
return true;
}
public:
NumberRepository(){//初始化赋值
numberChar.push_back("零");
numberChar.push_back("一");
numberChar.push_back("二");
numberChar.push_back("三");
numberChar.push_back("四");
numberChar.push_back("五");
numberChar.push_back("六");
numberChar.push_back("七");
numberChar.push_back("八");
numberChar.push_back("九");
numberChar.push_back("十");
}
~NumberRepository() {}
string ToChar(int number){
if(number<0||number>99) return "数字超限";
if(number<=10) return numberChar[number];
if(number<20) return numberChar[10]+numberChar[number-10];//十几的形式
if(number%10==0) return numberChar[number/10]+numberChar[10];//几十的形式
return numberChar[number/10]+numberChar[10]+numberChar[number%10];
}
int ToNumber(string sentence){
if( !FormatChar(sentence) ) return -1;//失败
return FindChar( sentence.substr(0,2) )*10+FindChar( sentence.substr(2,2) );
}
};
#endif
世界(World)
同样的先写好它的属性、构造函数与析构函数
class World{
private:
NumberRepository number;
VariableRepository variable;
public:
World() {}
~World() {}
};
其功能中,实际上只要运行(Run)方法可被调用即可,其实现也简单
void Run(){
string order;
int ans;
while( Input(order) ){
if(order=="退出") break;
ans=Understand(order);
if(ans==0) continue;
else if(ans==6) NotExist();
else if(ans==7) Applied();
else if(ans==8) NumberError();
else if(ans==9) DontKnow();
else if(ans==10) ConflictError();
else Unknown();
}
}
这里使用了返回值来判别错误类型,返回值与表中方法的序号相对应,0代表没有错误
指令输入方法用 getline(cin,s) 判断,各类错误抛出都用 cout 加具体内容即可实现,这里就不展示了
而关于是否和关键字冲突的除了要判断是否与“增加”、“减少”、“等于”、“整数”、“看看”冲突,还需要判断是否会与数字产生歧义
bool IsConflict(string name){
if( number.ToNumber(name)!=-1 ) return 1;
return (name=="增加")||(name=="减少")||(name=="看看")||(name=="等于")||(name=="整数");
}
现在实现对变量的修改,假定能确定是将名称为 var 的变量通过指令 order 修改为 value 的值
则先判定 value 是否是数字或者变量的值,再根据指令分为增加、减少、赋值以及未知指令的抛出,变量库自带了判别变量是否存在的功能:
int Update(string var,string order,string value){
int data=number.ToNumber(value);
if(data==-1) data=variable.VariableShow(value);//右值不是数字
if(data==-1) return 8;//右值也不是变量,返回8(数字错误)
if(order=="等于")
return variable.VariableAssign(var,data)?0:6;//赋值成功则返回0,否则返回6(变量不存在),下同
else if(order=="增加")
return variable.VariableAdd(var,data)?0:6;
else if(order=="减少")
return variable.VariableMinus(var,data)?0:6;
else
return 9;//未知指令,返回9(语句无法识别)
}
然而,常常用于输入的实际上是一个长串,新构造一个 Update_string 方法对长串分解了再用分解的串进行 Update
int Update_string(string name,string sentence){
if( sentence.find(" ")==string::npos ) return 9;//无空格,语句无法识别
string order=sentence.substr(0, sentence.find(" ") );//增加、减少、赋值指令
sentence=sentence.substr( sentence.find(" ")+1 );
if( sentence.find(" ")!=string::npos ) return 9;//仍有空格,语句无法识别
return Update(name,order,sentence);
}
申请变量实现比较简单:
int Apply(string name){
if( variable.VariableApply(name) ) return 0;//申请成功,返回0
else return 7;//申请失败,返回7(变量已申请)
}
而同样考虑往往输入会是一长串,所以同样构造 Apply_string 方法,它不仅需要能分解长串并执行 Apply,更是要判别是否冲突,以及后续可能的赋初值操作的处理
int Apply_string(string sentence){
if( sentence.find(" ")==string::npos )
return IsConflict(sentence)?10:Apply(sentence);//没有空格,如不冲突,只申请变量
string name=sentence.substr(0, sentence.find(" ") );
sentence=sentence.substr( sentence.find(" ")+1 );
if( sentence.find(" ")==string::npos ) return 9;//不含空格,返回9(语句无法识别)
string order=sentence.substr(0, sentence.find(" ") );
sentence=sentence.substr( sentence.find(" ")+1 );
if(order!="等于") return 9;//申请变量后不赋初值,无效,返回9(语句无法识别)
if( IsConflict(name) ) return 10;//发生冲突,不能作为变量名,返回10(关键字冲突)
if( number.ToNumber(sentence)==-1&&variable.VariableShow(sentence)==-1 )
return 8;//sentence 不为值或变量的值,返回8(数字错误)
if( Apply(name)!=0 ) return 7;//申请失败,返回7(变量已申请)
return Update(name,order,sentence);
}
输出也同上面两个方法一样,需要一个 Print_string 方法进行对长串的处理,因为比较好实现,这里就跳过了
如此一来,指令识别方法就很好实现了,先判定指令可否执行,可以则执行指令并返回执行结果
int Understand(string sentence){
if(sentence.find(" ")==string::npos) return 9;//不含空格,指令非法
string head=sentence.substr(0, sentence.find(" ") );
sentence=sentence.substr( sentence.find(" ")+1 );
if(head=="看看")
return Print_string(sentence);
else if(head=="整数")
return Apply_string(sentence);
else//其它类型的指令都有可能为变量开头
return Update_string(head,sentence);
}
给出 World.h
的总代码
#include "NumberRepository.h"
#include "VariableRepository.h"
#include<string>
#include<iostream>
using namespace std;
#ifndef WORLD
#define WORLD
class World{
private:
NumberRepository number;
VariableRepository variable;
bool IsConflict(string name){
if( number.ToNumber(name)!=-1 ) return 1;
return (name=="增加")||(name=="减少")||(name=="看看")||(name=="等于")||(name=="整数");
}
bool Input(string &sentence){
return getline(cin,sentence);
}
int Update(string var,string order,string value){
int data=number.ToNumber(value);
if(data==-1) data=variable.VariableShow(value);//右值不是数字
if(data==-1) return 8;//右值也不是变量,返回8(数字错误)
if(order=="等于")
return variable.VariableAssign(var,data)?0:6;//赋值成功则返回0,否则返回6(变量不存在),下同
else if(order=="增加")
return variable.VariableAdd(var,data)?0:6;
else if(order=="减少")
return variable.VariableMinus(var,data)?0:6;
else
return 9;//未知指令,返回9(语句无法识别)
}
int Update_string(string name,string sentence){
if( sentence.find(" ")==string::npos ) return 9;//无空格,语句无法识别
string order=sentence.substr(0, sentence.find(" ") );//增加、减少、赋值指令
sentence=sentence.substr( sentence.find(" ")+1 );
if( sentence.find(" ")!=string::npos ) return 9;//仍有空格,语句无法识别
return Update(name,order,sentence);
}
int Apply(string name){
if( variable.VariableApply(name) ) return 0;//申请成功,返回0
else return 7;//申请失败,返回7(变量已申请)
}
int Apply_string(string sentence){
if( sentence.find(" ")==string::npos )
return IsConflict(sentence)?10:Apply(sentence);//没有空格,如不冲突,只申请变量
string name=sentence.substr(0, sentence.find(" ") );
sentence=sentence.substr( sentence.find(" ")+1 );
if( sentence.find(" ")==string::npos ) return 9;//不含空格,返回9(语句无法识别)
string order=sentence.substr(0, sentence.find(" ") );
sentence=sentence.substr( sentence.find(" ")+1 );
if(order!="等于") return 9;//申请变量后不赋初值,无效,返回9(语句无法识别)
if( IsConflict(name) ) return 10;//发生冲突,不能作为变量名,返回10(关键字冲突)
if( number.ToNumber(sentence)==-1&&variable.VariableShow(sentence)==-1 )
return 8;//sentence 不为值或变量的值,返回8(数字错误)
if( Apply(name)!=0 ) return 7;//申请失败,返回7(变量已申请)
return Update(name,order,sentence);
}
int Print(string name){
int data=variable.VariableShow(name);
if(data==-1) return 6;//输出的变量不存在,返回6(变量不存在)
cout<<number.ToChar(data)<<endl;//存在,输出变量值的中文
return 0;
}
int Print_string(string sentence){
if( sentence.find(" ")!=string::npos ) return 9;//仍有空格,语句无法识别
return Print(sentence);
}
void NotExist(){
cout<<"变量不存在"<<endl;
}
void Applied(){
cout<<"变量已申请"<<endl;
}
void NumberError(){
cout<<"数字错误"<<endl;
}
void DontKnow(){
cout<<"语句无法识别"<<endl;
}
void ConflictError(){
cout<<"与关键字冲突"<<endl;
}
void Unknown(){
cout<<"未知错误"<<endl;
}
int Understand(string sentence){
if(sentence.find(" ")==string::npos) return 9;//不含空格,指令非法
string head=sentence.substr(0, sentence.find(" ") );
sentence=sentence.substr( sentence.find(" ")+1 );
if(head=="看看")
return Print_string(sentence);
else if(head=="整数")
return Apply_string(sentence);
else//其它类型的指令都有可能为变量开头
return Update_string(head,sentence);
}
public:
World() {}
~World() {}
void Run(){
string order;
int ans;
while( Input(order) ){
if(order=="退出") break;
ans=Understand(order);
if(ans==0) continue;
else if(ans==6) NotExist();
else if(ans==7) Applied();
else if(ans==8) NumberError();
else if(ans==9) DontKnow();
else if(ans==10) ConflictError();
else Unknown();
}
}
};
#endif
主方法代码
main.cpp
#include "World.h"
World w;
int main(){
w.Run();
return 0;
}
运行效果截图:
编译脚本的制作
编译脚本使用了 Windows 自带的 cmd 语句
先判断是否程序已经编译,然后依次查看 *.h 文件或源文件是否存在
确认无误后再进行编译
@echo off
title 编译脚本
if exist main.exe (
echo 程序 main.exe 已存在
pause>nul
exit
)
if not exist main.cpp (
echo 源代码 main.cpp 丢失
pause>nul
exit
)
if not exist World.h.gch (
if not exist World.h (
echo 源代码 World.h 丢失
pause>nul
exit
)
if not exist VariableRepository.h.gch (
if exist VariableRepository.h (g++ VariableRepository.h) else (
echo 源代码 VariableRepository.h 丢失
pause>nul
exit
)
)
if not exist NumberRepository.h.gch (
if exist NumberRepository.h (g++ NumberRepository.h) else (
echo 源代码 NumberRepository.h 丢失
pause>nul
exit
)
)
g++ World.h
)
g++ -o main.exe main.cpp
echo 编译完成
pause>nul
运行效果截图:
单元测试
由于部分方法被封装,且本人采用了程序输出与标准答案逐一比对的方法
于是先考虑了用来单元测试的几个方法,然后针对每个方法,手动构造了数据(已上传至 仓库-testing )
因而写了部分程序来实现单元测试:
- NumberRepository的ToChar
- NumberRepository的ToNumber
- VariableRepository的VariableAssign与VariableShow
- VariableRepository的VariableAdd、VariableMinus与VariableShow
- VariableRepository的VariableApply与VariableShow
- World的Run
具体的测试数据也已经上传至仓库了,这里对于一些较大的数据只给出了其中的几行
下面给出各个测试程序的主方法、类的实例化与部分测试数据
1.ToChar.cpp
NumberRepository n;
int main(){
int number;
while( cin>>number ) cout<<n.ToChar(number)<<endl;
return 0;
}
测试数据中 ToChar.in
包含了 \(-1\)~\(100\) 的所有整数,这里只展示部分
0
1
...
10
11
...
20
21
...
98
99
100
-1
对应的 ToChar.ans
标准输出
零
一
...
十
十一
...
二十
二十一
...
九十八
九十九
数字超限
数字超限
2.ToNumber.cpp
NumberRepository n;
int main(){
string s;
while( getline(cin,s) ){
int num=n.ToNumber(s);
if(num==-1) cout<<"识别失败"<<endl;
else cout<<num<<endl;
}
return 0;
}
ToNumber.in
中包括了 \(0\)~\(99\) 的所有正规写法和非正规(但生活中可能常用)写法。至于非法输入,程序本身识别不了的会返回“识别失败”,因此便不作为测试内容了
零
...
九
零十零
零十一
...
零十九
十
十一
...
十九
一十
一十一
...
一十九
二十
二十一
...
九十九
零零
零一
零二
...
零九
一零
一一
...
九九
十零
零十零
一十零
二十零
...
九十零
对应的 ToNumber.ans
0
...
9
0
1
...
9
10
11
...
19
10
11
...
19
20
21
...
99
0
1
2
...
9
10
11
...
99
10
0
10
20
...
90
3.Assign_Show.cpp
VariableRepository v;
int main(){
v.VariableApply("var");
char c;
int num;
while( cin>>c ){
if(c=='s'||c=='S') cout<<v.VariableShow("var")<<endl;
else if(c=='a'||c=='A'){
cin>>num;
v.VariableAssign("var",num);
}
}
return 0;
}
为方便测试,我指定形如 s
的语句表示输出,形如 a *
的语句表示赋值
因此设置了 Assign_Show.in
s
a 3
s
a 51
s
a 93
a 26
s
对应的输出 Assign_Show.out
0
3
51
26
4.Add_Minus_Show.cpp
VariableRepository v;
int main(){
v.VariableApply("var");
char c;
int num;
while(cin>>c){
if(c=='s'||c=='S') cout<<v.VariableShow("var")<<endl;
else if(c=='a'||c=='A'){
cin>>num;
v.VariableAdd("var",num);
}
else if(c=='m'||c=='M'){
cin>>num;
v.VariableMinus("var",num);
}
}
return 0;
}
跟上一组测试单元一样,将指令以更加便捷的方式输入,方便测试:设定形如 s
的语句表示输出,形如 a *
的语句表示增加,m *
的表示减少
设置的输入数据 Add_Minus_Show.in
s
a 3
s
a 5
s
a 10
s
m 5
s
对应的输出 Add_Minus_Show.out
0
3
8
18
13
5.Apply_Show.cpp
VariableRepository v;
int main(){
char c;
int num;
string s,number,order;
while(cin>>c){
if(c=='s'||c=='S'){
getline(cin,s);
if( s[0]=' ' ) s=s.substr( s.find(" ")+1 );
num=v.VariableShow(s);
if(num<0) cout<<"变量不存在"<<endl;
else cout<<num<<endl;
}
else if(c=='a'||c=='A'){
getline(cin,s);
if( s[0]=' ' ) s=s.substr( s.find(" ")+1 );
if(!v.VariableApply(s)) cout<<"变量已申请"<<endl;
}
}
return 0;
}
这一块的测试思路与前面类似:形如 s
的语句表示输出,形如 a *
的表示申请变量
不一样的是,在源程序中,这一块因为申请、输出的不成功,会将返回值交给 World 类中的方法处理。现在单独将其单元测试,便只能在主方法中体现错误抛出
设置的 Apply_Show.in
s var
a var
a asd
s asd
s var
对应的 Apply_Show.out
变量不存在
0
0
6.Run.cpp
(总测试)
World w;
int main(){
w.Run();
return 0;
}
总测试,测试了整个程序的运行情况
设置的 Run.in
整数 钱包 等于 零
钱包 增加 四
钱包 减少 三
看看 钱包
整数 支付宝 等于 二
看看 支付宝
支付宝 增加 钱包
整数 微信 等于 支付宝
看看 微信
微信 等于 一六
看看 微信
对应的 Run.ans
一
二
三
十六
测试脚本
其次给出测试用的脚本:
脚本放于"oop-homework/面向对象程序设计寒假作业2/"目录下,而测试点索引 _index
与包含数据的文件夹、包含程序的文件夹放置于脚本所在目录的子文件夹下
于是实现脚本时,先进入该子文件夹,然后调出索引
其次待用户输入测试点信息,并核查测试的的情况
接着运行程序并通过比对标准答案来显示结果
最后安插一个返回的 goto
语句方便重复使用
具体用 cmd 实现如下:
@echo off
title 单元测试脚本
cd testing\
:start
cls
set line=-------------------------------
::编译
echo 可供测试的单元有:
type _index
echo.
echo %line%
echo.
echo 请选择用以测试的数据点名称:
set /p name=
::核查数据点信息
if not exist testing_data\%name%.in (
echo 数据点不存在或丢失
pause>nul
goto back
)
if not exist testing_data\%name%.ans (
echo 数据点不存在或丢失
pause>nul
goto back
)
if not exist testing_program\%name%.exe (
echo 程序不存在或丢失
pause>nul
goto back
)
::测试模式
echo 输入小写"h"进行手动测试,任意其它按键自动测试
set /p mode=
if %mode% equ h goto hand
::拷贝数据
copy testing_data\%name%.in data.in>nul
copy testing_data\%name%.ans data.ans>nul
copy testing_program\%name%.exe prog.exe>nul
::执行数据
prog.exe<data.in>data.out
::比对结果并判定
echo.
echo %line%
echo.
echo 执行结果:
fc data.out data.ans>nul
if not errorlevel 1 (echo %name% 测试通过) else (
echo %name% 测试不通过
fc data.out data.ans
)
::收尾处理
del data.in
del data.out
del data.ans
del prog.exe
::回溯
:back
echo.
echo %line%
echo.
echo 输入小写“b”重新开始,任意其他内容退出
set /p key=
if %key% equ b (goto start) else exit
:hand
testing_program\%name%.exe
goto back
运行效果截图
添加功能
十分抱歉,本人之前理解错题目了,现在进行修正
通过查询 相关资料 后发现,实际上需要增加的功能就是使用传递给 main 函数的参数 argc 和 argv
由于个人觉得上面那份资料讲得不清晰,便又查询了 argc 与 argv 的用法
再结合 这篇文章 后确认:
- main 函数可以通过设定传入参数,读取命令行的指令
- 当命令行只是运行 main 函数,没有其它指令时, argc=1,argv[0]=main
- 当命令行除了 main 函数,含有其它 \(n\) 个指令时, argc=n+1,分别存在 argv[1] 至 argv[n]
例如假设自己的程序名为 main.exe ,读入的文件名为 testdata.txt ,两者在一个目录下
那么,在程序与文件的目录中调用 cmd ,输入 main testdata.txt
后,在程序 main.exe 中
argc=2,argv[0]="main",argv[1]="testdata.txt"
因此,为了实现题目需要的增加功能,只需要使用 main 函数的参数传递即可
而如果传入为 testdata.txt ,则视为从中读取需要运行的“程序”,实现时只需要将输入改为从该文件中读取即可
当然,单独调用时, cmd 中输入的为 main
,此时 argc=1,argv[0]="main" ,这个时候不需要从文件读入
所以我使用了 if 特判实现了程序
main.cpp
代码修改如下:
#include "World.h"
World w;
int main(int argc,char *argv[]){
if(argc!=1)
freopen(argv[1],"r",stdin);
w.Run();
return 0;
}
运行效果截图:
testdata.txt 中数据为样例
已解决的问题
- 关于 STL 中 string,vector,map 的一些用法
- 了解了一些面向对象编程的思路与实现
- 了解了骆驼命名法与帕斯卡命名法
- main 函数参数传递的使用
仍存在的问题
- 只能支持0-99的输入输出,普适性较低
- 按照复杂度估计,代码运行效率不高
- *.h 后缀的程序似乎无法通过 VS Code 直接编译