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 直接編譯


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM