作業描述 | 詳情 |
---|---|
這個作業屬於哪個課程 | 班級鏈接 |
這個作業要求在哪里 | 作業要求 |
這個作業的目標 | 實踐題: 新建一個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 直接編譯