2020面向对象程序设计寒假作业2 题解

作业描述 详情
这个作业属于哪个课程 班级链接
这个作业要求在哪里 作业要求
这个作业的目标 实践题:
新建一个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仓库

本次编程试着使用了骆驼命名法与帕斯卡命名法

题目要求

  1. 实现以 汉字 为变量名的,范围为 零至九十九整数

  2. 实现 整数......等于 操作,使得上述变量得以申请与赋初值

  3. 实现 增加减少 操作,使得上述变量得以修改

  4. 实现 看看 操作,使得上述变量的值得以输出

可能存在的要求:

  1. 实现 整数 操作,使得申请上述变量

  2. 实现 等于 操作,给上述变量赋值

  3. 设计错误抛出,避免程序崩溃

需求分析

  1. 输入输出全为中文,需要实现汉字与阿拉伯数字的互相转化

  2. 对于非法的输入,需要实现对非法输入的发现与错误的抛出

  3. 对于定义、增加、减少、赋值语句的实现

  4. 对语句的识别

思考过程

本次作业,本人试着用面向对象编程的思路完成。

第一个模块

首先,考虑到变量的个数不限制。因此考虑一个类为变量库(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;//找不到
        }

接着是汉字的格式化:

  1. 汉字如果是有效数字,肯定每一位都是“零”到“十”的形式,因此我们应先进行字符的合法性检验,并顺便将其化为两个字的形式
  2. 如果汉字包含一个字,我们就格式化为“零*”的形式。由于“十”的特殊性,“零十”对于程序也是合法的
  3. 如果汉字包含两个字,那么一定不同时为“十”。然后分别讨论,第一个为“十”的,改为“一”;第二个为十的改为“零”
  4. 如果汉字包含三个字,那么一定是中间为“十”且首尾不为“十”。然后只要去除中间的“十”

实现如下:

        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

因而写了部分程序来实现单元测试:

  1. NumberRepository的ToChar
  2. NumberRepository的ToNumber
  3. VariableRepository的VariableAssign与VariableShow
  4. VariableRepository的VariableAdd、VariableMinus与VariableShow
  5. VariableRepository的VariableApply与VariableShow
  6. 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 的用法

再结合 这篇文章 后确认:

  1. main 函数可以通过设定传入参数,读取命令行的指令
  2. 当命令行只是运行 main 函数,没有其它指令时, argc=1,argv[0]=main
  3. 当命令行除了 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 中数据为样例

已解决的问题

  1. 关于 STL 中 string,vector,map 的一些用法
  2. 了解了一些面向对象编程的思路与实现
  3. 了解了骆驼命名法与帕斯卡命名法
  4. main 函数参数传递的使用

仍存在的问题

  1. 只能支持0-99的输入输出,普适性较低
  2. 按照复杂度估计,代码运行效率不高
  3. *.h 后缀的程序似乎无法通过 VS Code 直接编译
posted @ 2020-01-29 11:44  JustinRochester  阅读(622)  评论(0编辑  收藏  举报