| 作業描述 | 詳情 |
|---|---|
| 這個作業屬於哪個課程 | 班級鏈接 |
| 這個作業要求在哪里 | 作業要求 |
| 這個作業的目標 | 問答題: 1-1. 如果你不了解C++請回答以下問題:你認為C語言有什么缺陷(你覺得哪里用的不順手)。 1-2. 如果你已經了解C++請回答以下問題:你覺得C++和C語言比有什么優點。 2. 查閱相關資料,簡述一下C語言/C++的編譯過程。 實踐題: 1. 查看自己的C++編譯器版本。 2. 使用命令行編譯一份C語言/C++代碼。 編程題: 編寫一個程序,輸入滿足以下語法要求的一段文字,輸出運行后的結果。 變量定義: 整數 錢包 等於 零 運算(加法): 錢包 增加 四 運算(減法): 錢包 減少 四 輸出: 看看 錢包 |
| 作業正文 | 2020面向對象程序設計寒假作業1 題解 |
| 其他參考文獻 | C++ 編譯過程簡介 |
問答題
-
因為本人是轉專業進來的學生,以前奧賽也未學習C語言,所以很抱歉,無法完成比較。
-
查詢了相關資料后,發現C/C++的編譯過程分為以下步驟:(1)預處理 (2)編譯 (3)匯編 (4)鏈接
其中,預處理階段將先讀取原程序,再處理偽指令,接着刪除所有注釋,然后添加行號和文件名標識,最后保留所有的 #pragma 編譯器指令。
而編譯過程將預處理完的文件進行一系列詞法分析、語法分析、語義分析及優化后,產生相應的匯編代碼文件。
匯編過程將編譯完的匯編代碼文件翻譯成機器指令,並生成可重定位目標程序(*.o)文件,該文件為二進制文件,字節編碼是機器指令。
最后的鏈接過程通過鏈接器將一個個目標文件(*.obj)與庫文件(*.lib)鏈接在一起生成一個完整的可執行程序。
因為正常而言,一個比較完備、大型的可執行程序,其代碼量較多,很難將所有的代碼寫在一個 cpp 內,或者寫在一些頭文件內。一是在於這么寫很容易產生命名上的沖突以及宏定義上的沖突,而且加大了代碼的冗余量;二則在於如此大量的代碼,編譯速度極慢,當程序出現錯誤,需要 debug 時,將會耗費大量的時間在編譯上,降低了 debug 的效率;三則這樣的寫法不利於模塊化處理程序,當程序需求需要修改時,需要耗費極大量的時間在修改代碼上。
實踐題
1.查看自己的C++編譯器版本
Windows+R 打開運行窗口
輸入 cmd 進入命令行
輸入 g++ -v 查看版本

2.使用命令行編譯一份 C++ 代碼
Windows+R 打開運行窗口
輸入 cmd 進入命令行
使用 cd 指令進入代碼所在的文件夾
使用指令 g++ {file}.cpp 編譯文件,生成 a.exe 文件

編程題
前言:又遇到了最討厭的模擬題(碼力低是真的傷不起啊),幸好最后被我調出來了......
預處理
首先,作為設計人員,很重要的是要清楚用戶可能輸入什么。而對於非法的輸入一定要學會處理,而不是任由程序崩潰
所以,我先想到的就是設計錯誤拋出:
inline void error_output(){//錯誤拋出
puts("輸入格式錯誤");
}
其次,題目保證了輸入的代表數字的漢字一定為零-十,關鍵字保證只有以下五類:
整數:定義新變量
等於:對變量賦值
整數...等於...:定義新變量並賦初值
增加:實現加法
減少:實現減法
看看:輸出變量值
於是,我們先把他們放到map中,初始化,方便后面處理:
vector<int> var_ar;
map<string,int> read_gbk,comd,var_id;
map<int,string> output_gbk;
/*
var_ar變量值
var_id從變量名到變量地址的映射
read_gbk將漢字轉為數字
output_gbk將數字轉成漢字
*/
inline void pre(){//初始化設置
read_gbk["零"]=0;
read_gbk["一"]=1;
read_gbk["二"]=2;
read_gbk["三"]=3;
read_gbk["四"]=4;
read_gbk["五"]=5;
read_gbk["六"]=6;
read_gbk["七"]=7;
read_gbk["八"]=8;
read_gbk["九"]=9;
read_gbk["十"]=10;
output_gbk[0]="零";
output_gbk[1]="一";
output_gbk[2]="二";
output_gbk[3]="三";
output_gbk[4]="四";
output_gbk[5]="五";
output_gbk[6]="六";
output_gbk[7]="七";
output_gbk[8]="八";
output_gbk[9]="九";
output_gbk[10]="十";
comd["增加"]=1;
comd["減少"]=-1;
comd["等於"]=2;
comd["看看"]=3;
comd["整數"]=4;
}
由於變量個數未知,我使用了vector來儲存變量的值,開一個map作為變量名到它在vector中地址的映射
主體
好的,預處理部分結束,我們來講主體部分
主體功能較簡單,無非以下工作:
輸入語句、分析與執行語句、輸出
由於語句條數未知,本人使用 while 循環來執行:
while( getline(cin,s) ) carry(s);//讀入、分析並執行指令
其中 carry(string) 函數為分析與執行語句的主體
carry(string) 函數中包括了對輸入的語句進行分析
首先,合法的語句一定是含有空格的,我們把語句按空格分成前半部分和后半部分;否則記得錯誤的拋出
前半部分如果不是指令,則是已經申請的變量,否則也是需要拋出的錯誤
而這樣一來,后面跟着的一定是 加、減、賦值的語句+空格+值 的形式,而值可能是已申請的變量或者數字對應的漢字
一樣先分析是否含有空格,否則拋出;然后讀取前半部分是否是加、減、賦值的語句,否則拋出;接着判斷是否最后那個是值,否則拋出;最后進行相應的運算
當然,判斷前半部分是否是那三種指令之前,更要先判斷是否是合法指令。如果連合法指令都不算,就更不要說那三種指令了。
實現起來代碼如下:
if( !iscomd(tmp) ){//不是指令作為開頭
if( var_id.find(tmp)==var_id.end() ){//也不是變量(例如數字),或變量還未申請,語句非法
error_output();
return ;
}
id=var_id[tmp];//變量的地址
if( s.find(" ")==string::npos ){//不含空格,語句非法
error_output();
return ;
}
tmp=s.substr(0, s.find(" ") );
s=s.substr( s.find(" ")+1 );
if( !iscomd(tmp) ){//不是指令,語句非法
error_output();
return ;
}
int command=comd[tmp];
if(command!=-1&&command!=1&&command!=2){//不是加減或者賦值,語句非法
error_output();
return ;
}
int num=number(s);
if(num<0){//語句非法
error_output();
return ;
}
if(command==-1) var_ar[id]-=num;
else if(command==1) var_ar[id]+=num;
else if(command==2) var_ar[id]=num;
}
而判斷是否是值,我用一個函數 number 進行了模塊化處理:有限判定是否是數字對應的漢字;如果不是,再判定是否是已申請的變量;最后返回的如果是非負數,則代表值
inline int number(string s){//讀取用於運算的數字或變量
int num=gbk_read(s);
if(num>=0) return num;//先判定是否是數字
if( var_id.find(s)!=var_id.end() ) return var_ar[ var_id[s] ];//再判定是否為變量
return -1;//否則非法
}
而將數字對應的漢字轉化為數字,我將它們最后全部整合成了兩個字的形式,再處理。整合分為以下幾種情況:
- 零-九:前補零
- 十:前補一
- 十一-十九:不變
- 十的整數倍:不變
- 其它:去掉中間的十
當然,不保證用戶的輸入一定合法,所以考慮了以下幾種情況:
- 三個漢字但中間不為十,或首尾為十:非法
- 含前導零非一個漢字的數字:去掉所有前導零后,按1處理
- 一十一-一十九:同5
- 一一-九九:不變
- 一零、二零、三零...九零:不變
整理以下上述情況,就是:先去除非一個漢字的前導零,若是一個漢字,則前補零;三個漢字則依次執行情況6與5;剩下兩個漢字的情況分為以下幾類:
1.僅有十開頭的形式
2.僅有十結尾的形式
3.十開頭結尾的形式
4.其它形式
對於3需要錯誤拋出,4可以直接處理
為了高效地處理1與2,本人在計算第一位后,先判定是否為十,如果不為十,在乘十實現十進制下的左移;而最后一位加上對應數字對10取模即可解決2
實現代碼如下:
inline int gbk_read(string s){
// return -1 means the input string is illegal
int num=-1;
string tmp;
while( s.substr(0,2)=="零"&&s.size()>2 ) s=s.substr(2);
if( s.size()==2 ){
if( !islegal(s) ) return -1;
if(read_gbk[s]<10) s="零"+s;
else s="一"+s;
}//一位數和十,均湊成兩位數
if( s.size()==6 ){
tmp=s.substr(2,2);
if( !islegal(tmp) ) return -1;
if( read_gbk[tmp]!=10 ) return -1;//查詢第二個字是否為十,否則非法
if( read_gbk[s.substr(0,2)]==10||read_gbk[s.substr(4,2)]==10 ) return -1;//查詢一三是否為十,是則非法
s=s.substr(0,2)+s.substr(4,2);//去掉中間,拼成兩位數
}
if( s.size()==4 ){
tmp=s.substr(0,2);
if( !islegal(tmp) ) return -1;
num=read_gbk[tmp];
tmp=s.substr(2,2);
if( !islegal(tmp) ) return -1;
if(num<10) num*=10;//十* 的形式
else if(tmp=="十") return -1;
num+=read_gbk[tmp]%10;//*十 的形式
}
return num;
}
至此非指令開頭情況已解釋完畢。下文解釋指令開頭的情況:
指令作為開頭則一定為定義變量(並申請初值)或輸出的形式,否則都是需要拋出的
輸出則一定是輸出值,否則需要拋出。如果是的話,用一個函數專門負責將數字轉化成漢字。轉化分為以下情況:
- 0-9
- 10
- 11-19
- 10的其它整數倍
- 其它數字
總結起來,針對兩位數,當十位大於1時需要輸出十位數,再輸出十,若個位不為零則還要輸出各位
針對一位數,直接用map輸出即可
本人用 gbk_output(int) 封裝了以下,實現過程如下:
inline string gbk_output(int num){//數字轉漢字(只輸出0-99)
string tmp="";
if(num/10){
if( (num/10)>1 ) tmp+=output_gbk[num/10];//不輸出 一十* 的形式
tmp+=output_gbk[10];
if(num%10) tmp+=output_gbk[num%10];//*十的形式,不輸出為 *十零 的形式
}
else tmp+=output_gbk[num%10];
return tmp;
}
而如果是定義變量,則后面的語句先判定是否含有空格,若含有,則分為前半段與后半段;不含有,則干脆全部作為前半段,后半段放空
考慮前半段雖然保證不是關鍵字,但仍可能申請到沖突的變量名:例如數字或者已申請的變量
但考慮到上文所提,若是數字或已申請的變量,可以用 number(string) 函數取出值,所以依此判斷即可
定義語句可能后跟賦值語句,思維跟上述相同,只不過地址要直接在上述步驟中保存好即可
實現過程如下:
else{//指令開頭
int command=comd[tmp];
if(command==-1||command==1||command==2){//加減或賦值開頭,語句非法
error_output();
return ;
}
if(command==3){
int num=number(s);
if(num<0){//輸出未申請的變量,語句非法
error_output();
return ;
}
cout<<gbk_output(num)<<endl;
}
else{
if( s.find(" ")==string::npos ){
tmp=s;
s="";
}
else{
tmp=s.substr(0, s.find(" ") );
s=s.substr( s.find(" ")+1 );
}
if( number(tmp)>=0 ){//變量已申請或者是數字對應的漢字,語句非法
error_output();
return ;
}
int id=var_id[tmp]=var_ar.size();
var_ar.push_back(0);
if(s=="") return ;//不賦初值,直接跳出
if( s.find(" ")==string::npos ){//語句非法
error_output();
return ;
}
tmp=s.substr(0, s.find(" ") );
s=s.substr( s.find(" ")+1 );
if( iscomd(tmp)&&comd[tmp]==2 ){//賦初值
int num=number(s);
if(num>=0) var_ar[id]=num;
else error_output();
}
else error_output();//語句非法
}
}
完整代碼
#include<cstdio>
#include<string>
#include<iostream>
#include<map>
#include<vector>
using namespace std;
vector<int> var_ar;
map<string,int> read_gbk,comd,var_id;
map<int,string> output_gbk;
/*
var_ar變量值
var_id從變量名到變量地址的映射
read_gbk將漢字轉為數字
output_gbk將數字轉成漢字
*/
inline void pre(){//初始化設置
read_gbk["零"]=0;
read_gbk["一"]=1;
read_gbk["二"]=2;
read_gbk["三"]=3;
read_gbk["四"]=4;
read_gbk["五"]=5;
read_gbk["六"]=6;
read_gbk["七"]=7;
read_gbk["八"]=8;
read_gbk["九"]=9;
read_gbk["十"]=10;
output_gbk[0]="零";
output_gbk[1]="一";
output_gbk[2]="二";
output_gbk[3]="三";
output_gbk[4]="四";
output_gbk[5]="五";
output_gbk[6]="六";
output_gbk[7]="七";
output_gbk[8]="八";
output_gbk[9]="九";
output_gbk[10]="十";
comd["增加"]=1;
comd["減少"]=-1;
comd["等於"]=2;
comd["看看"]=3;
comd["整數"]=4;
}
inline void error_output(){//錯誤拋出
puts("輸入格式錯誤");
}
inline string gbk_output(int num){//數字轉漢字(只輸出0-99)
string tmp="";
if(num/10){
if( (num/10)>1 ) tmp+=output_gbk[num/10];//不輸出 一十* 的形式
tmp+=output_gbk[10];
if(num%10) tmp+=output_gbk[num%10];//*十的形式,不輸出為 *十零 的形式
}
else tmp+=output_gbk[num%10];
return tmp;
}
inline bool islegal(string s){ return read_gbk.find(s)!=read_gbk.end(); }//查詢讀入是否為數字對應的漢字
inline int gbk_read(string s){
// return -1 means the input string is illegal
int num=-1;
string tmp;
if( s.size()==2 ){
if( !islegal(s) ) return -1;
if(read_gbk[s]<10) s="零"+s;
else s="一"+s;
}//一位數和十,均湊成兩位數
if( s.size()==6 ){
tmp=s.substr(2,2);
if( !islegal(tmp) ) return -1;
if( read_gbk[tmp]!=10 ) return -1;//查詢第二個字是否為十,否則非法
if( read_gbk[s.substr(0,2)]==10||read_gbk[s.substr(4,2)]==10 ) return -1;//查詢一三是否為十,是則非法
s=s.substr(0,2)+s.substr(4,2);//去掉中間,拼成兩位數
}
if( s.size()==4 ){
tmp=s.substr(0,2);
if( !islegal(tmp) ) return -1;
num=read_gbk[tmp];
tmp=s.substr(2,2);
if( !islegal(tmp) ) return -1;
if(num<10) num*=10;//十* 的形式
else if(tmp=="十") return -1;
num+=read_gbk[tmp]%10;//*十 的形式
}
return num;
}
inline int number(string s){//讀取用於運算的數字或變量
int num=gbk_read(s);
if(num>=0) return num;//先判定是否是數字
if( var_id.find(s)!=var_id.end() ) return var_ar[ var_id[s] ];//再判定是否為變量
return -1;//否則非法
}
inline bool iscomd(string s) { return comd.find(s)!=comd.end(); }//判定是否為合法指令
inline void carry(string s){
string tmp="";
int id;
if( s.find(" ")==string::npos ){//不含空格,語句非法
error_output();
return ;
}
tmp=s.substr(0, s.find(" ") );
s=s.substr( s.find(" ")+1 );
if( !iscomd(tmp) ){//不是指令作為開頭
if( var_id.find(tmp)==var_id.end() ){//也不是變量(例如數字),或變量還未申請,語句非法
error_output();
return ;
}
id=var_id[tmp];//變量的地址
if( s.find(" ")==string::npos ){//不含空格,語句非法
error_output();
return ;
}
tmp=s.substr(0, s.find(" ") );
s=s.substr( s.find(" ")+1 );
if( !iscomd(tmp) ){//不是指令,語句非法
error_output();
return ;
}
int command=comd[tmp];
if(command!=-1&&command!=1&&command!=2){//不是加減或者賦值,語句非法
error_output();
return ;
}
int num=number(s);
if(num<0){//語句非法
error_output();
return ;
}
if(command==-1) var_ar[id]-=num;
else if(command==1) var_ar[id]+=num;
else if(command==2) var_ar[id]=num;
}
else{//指令開頭
int command=comd[tmp];
if(command==-1||command==1||command==2){//加減或賦值開頭,語句非法
error_output();
return ;
}
if(command==3){
int num=number(s);
if(num<0){//輸出未申請的變量,語句非法
error_output();
return ;
}
cout<<gbk_output(num)<<endl;
}
else{
if( s.find(" ")==string::npos ){
tmp=s;
s="";
}
else{
tmp=s.substr(0, s.find(" ") );
s=s.substr( s.find(" ")+1 );
}
if( number(tmp)>=0 ){//變量已申請或者是數字對應的漢字,語句非法
error_output();
return ;
}
int id=var_id[tmp]=var_ar.size();
var_ar.push_back(0);
if(s=="") return ;//不賦初值,直接跳出
if( s.find(" ")==string::npos ){//語句非法
error_output();
return ;
}
tmp=s.substr(0, s.find(" ") );
s=s.substr( s.find(" ")+1 );
if( iscomd(tmp)&&comd[tmp]==2 ){//賦初值
int num=number(s);
if(num>=0) var_ar[id]=num;
else error_output();
}
else error_output();//語句非法
}
}
}
int main(){
pre();
string s;
while( getline(cin,s) ) carry(s);//讀入、分析並執行指令
return 0;
}
