2020面向对象程序设计寒假作业1 题解
作业描述 | 详情 |
---|---|
这个作业属于哪个课程 | 班级链接 |
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 问答题: 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;
}