
结对作业——四则运算 Part1. Core代码编写
PB15061303 刘梓轩
PB16061489 艾寅中
GITHUB 地址
目录 (因为内容较多,分为了三个部分,但作业系统中只能提交一次)
Part 1. Core代码编写部分
Part 2. 封装与对接相关问题
Part 3. 对于结对编程的总结与思考
项目简介
写一个能自动生成小学四则运算题目并给出答案的命令行 “软件”, 如果我们要把这个功能放到不同的环境中去(例如,命令行,Windows 图形界面程序,网页程序,手机App),就会碰到困难,因为目前代码的普遍问题是代码都散落在main ( )函数或者其他子函数中,我们很难把这些功能完整地剥离出来,作为一个独立的模块满足不同的需求。
建议大家把四则运算的计算功能包装在一个模块中(这个模块可以是一个类 Class, 一个DLL,等等), 为了方便起见,我们叫它 “计算核心”Core 模块, 这个模块至少在两个地方可以使用:
1.测试程序,这个可以是一个命令行的程序,或者是JUnit 的框架,或者是Visual Studio 单元测试的框架。这样,我们在算法层级保证了这个模块的正确性。
2.实际的软件,这是交付给最终用户的软件,有一定的界面和必要的辅助功能。
项目要求及需求分析
要求中,助教提出了一个基本的框架,就是将软件整体分为三个部分,分别为 Calc() 、Setting() 和Generate() 。现对每个部分进行需求分析。
1.Calc()
这个Calc 函数接受字符串的输入(字符串里就是算术表达式,例如 “5*3.5”,“7/8 - 3/8 ”,“3 + 90 * 0.3”等等),这个模块的返回值是一个字符串,例如,前面几个例子的结果就是(“17.5”,“1/2”,“30”)。
如果输入是有错误的,例如 “1 ++ 2”, 在数值范围是 -1000 .. 1000 的时候,传进去“10000 + 32768”,或者是“248 / 0” 怎么办?怎么告诉函数的调用者“你错了”?把返回的字符串定义为“-1” 来表示? 那么如果真的计算结果是“-1”又怎么处理呢?
建议这个时候,我们要定义各种异常(Exception),让Core在碰到各种异常情况的时候,能告诉调用者——你错了!定义要增加的功能 - 例如:支持 “运算式子格式错误” 异常
如上所述,Calc 承担了一个计算的功能,对于传进的字符串形式的表达式计算,并传出计算结果。对于如何进行表达式的计算,小组两人第一时间想到了数据结构中的利用栈进行计算的方式,但是,由于考虑到生成中缀表达式其实会带来相当程度的不便,所以最后笔者决定采取了另一种方式的表达式传入传出。避免了上述要求中第二点所提出的表达式不规范问题,详细在 Generate() 部分的需求分析及后面的代码详解中叙述。
2.Setting()
在生成四则运算题目之前,需要对一系列属性进行设置,例如生成题目的数量,操作数的数量,题目中操作数的数值的范围,运算符的种类,(+-*/,是否支持真分数运算,是否支持小数运算,是否支持乘方运算……
相较之下,Setting() 函数就较为简单,实际上为了简化函数中的参数传递,并且考虑到实际上最终提供的是一个封装好的代码,笔者们选择了采用全局变量作为控制相关选项的参数,而用多个bool 函数对 Setting() 进行设置,最终整合进一个 Setting() 中。
3.Generate()
进行一些设置之后,就可以开始生成所需要的题目了,题目增加以下限制
- 生成的题目计算过程中不能产生负数
例如1 - 2 + 3 =,3 + (4 - 5) =,因为计算过程中产生了负数,都是无效的题目
- 生成的题目中不能包括不必要的括号
例如 1 + (2 * 3),(1 + 2)+ 3
其中括号为不必要的括号,不能生产此类题目
- 程序一次运行生成的题目不能重复
即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
Generate() 可以说是整个函数的核心部分了。因要求中实际上对于整个表达式的要求略微复杂,即显示时的括号要求,所以如果直接生成中缀表达式,优先级判断来添加括号较为复杂,所以笔者们决定直接生成后缀表达式,大大简化了 Calc() 函数。同时,个人添加了 Output() 这样一个模块,用来传出经过调整的表达式 String 。
代码详解
首先对于表达式的生成与计算,笔者们定义了如下的结构体:
typedef struct node {
//the node of string
char c = ;
int num = ;
int flag = -;
int frac[] = { ,,, };
double xiao = ;
}Node, NNode[NUMSIZE], *N_ptr;
实际上生成的表达式,就是一个由该结构体组成的串。通过 falg 的值来决定类型,即为整数 / 小数 / 分数 / 运算符,大大简化了生成和运算。
其中,int num 用来储存整数,对应的 flag 为1
char c 用来储存运算符,对应的 flag 为2
int frac 用来储存分数,分别用三个来储存 整数部分、分子和分母
double xiao 用来储存小数
接下来便是参数部分,见代码:
int _sup = , _inf = ; //生成数的最大值和最小值
int _inf_len = , _sup_len = ; //生成表达式长度的最大值和最小值
int _symbol = ; //表达式的类型,是否分数,小数等
int _row = ; //一次生成表达式的数目
int _mi = ; //幂次的最大
char *_oper = "+-*/"; //支持的运算符
int flag_pow = ; //是否启用乘方运算
int _xi_disp = ; //小数的位数
int PowerType = ; //乘方的显示类型 ^ or ** bool set_range(int inf, int sup) {
if ( <= inf&&inf <= sup&&sup <= INT_MAX) {
_inf = inf;
_sup = sup;
return true;
}
return false; }//end set_range bool set_len(int inf, int sup) {
if (inf >= && sup >= inf&&sup <= MAX_LEN) {
_inf_len = inf;
_sup_len = sup;
return true; }//end if
return false;
}//end set_len bool set_mode(int symbol) {
if (symbol == || symbol == || symbol == ) {
_symbol = symbol;
return true;
}//end if
else return false;
}//end set_mode bool set_row(int row) {
if (row > && row <= ROWSIZE) {
_row = row;
return true;
}//end if
return false;
}//end set_row bool set_operator(bool add, bool min, bool mul, bool div, bool power) {
//set_operator
char temp[];
for (int i = ; i < ; i++) {
temp[i] = ;
}//end for
int count = ;
if (add == true) {
temp[count++] = '+';
}//end if+
if (min == true) {
temp[count++] = '-';
}//end if -
if (mul == true) {
temp[count++] = '*';
}//end if *
if (div == true) {
temp[count++] = '/';
}//end if/
if (power == true) {
flag_pow = ;
}//end if ^ if (count <= && count > ) return false;
else {
for (int i = ; i < count; i++) {
_oper[i] = temp[i];
}
_oper[count] = ; return true;
}
}//end set_operator bool set_file_app(int app) {
//set_file: out is true if need get result into file if (File_out == ) { if (app == ) {
File_app = ; }//end if
else if (app == ) {
File_app = ;
}//end else if
return true;
}//end if
else {
return false;
}//end else }//end set_file bool set_path(bool symbol, bool reset, string pathe, string pathk) {
if (symbol == true) {
File_out = ;
if (reset == true) {
path_exp = pathe; path_key = pathk;
}//end if if (path_exp.empty() || path_key.empty()) {
return false;
}//end if
else return true;
}//end if
else {
File_out = ;
return true;
}
}//end set_path bool set_Ngenapp(bool symbol) {
if (symbol == false) {
N_gen_app = ;
}//end if
else {
N_gen_app = ;
}//end else
return true;
}//end set_Ngenapp bool set_PowerType(bool symbol) {
//set_PowerType
if (symbol == true) {
PowerType = ;
return true;
}//end if
else {
PowerType = ;
return true;
}//end else
}//end set_Powertype bool set_xi_disp(int symbol) {
//set_xi_disp
if (symbol > && symbol <= Max_disp) {
_xi_disp = symbol;
return true;
}//end if
else return false; }//end set_xi_disp
参数设定
可见,实际上本组的参数是较多的。原因主要是因为对于要求不断的更新,实际代码的编写过程中,笔者们先进行了整数的加减乘生成及运算,除法、分数、乘方、小数都是在后来的一个个版本中不断的加入,又因为一开始没有给相关的参数留好接口,便只好新加一些参数和判断条件。
根据代码也可见,对于处理参数的 bool 函数,里面有较为复杂的安全性判断。虽然略微显得代码有些繁琐,但这一点大大的提高了健壮性。(此处特别赞美组中的轩轩,不亏是信息安全的dalao
接下来是生成与计算部分。(代码较长
void suffix_generator() {
//generate the suffix erpression //initialization
N_init_suffix(suffix); int symbol = _symbol; int temp = , temp1;//temp variable(use for case 3)
int i = , j = , k = ;// invariable of count char oper2[];
for (i = ; i < int(strlen(_oper)); i++) {
oper2[i] = _oper[i];
} oper2[i] = , i = ;
char* oper = oper2; bool flag = ; // flag of generating operator or num int sup = _sup;// the superior bound of the num
int inf = _inf; // the inferior bound of the num int count_num = ; //the number of num in the experssion
int count_oper = ; //the number of operator in the experssion int inf_len = _inf_len;//the inferior bound of the length of expression
int sup_len = _sup_len;//the superior bound of the length of expression int len_oper = strlen(oper); //the length of the operator set int length;
if (inf_len == sup_len) {
length = inf_len;
}
else {
length = random(inf_len, sup_len);//the length of the exp,randomized
} string string1 = oper; //end definition if (length % == ) length++; suffix[].flag = ; //3 ************* 3 means begin
suffix[].num = length;//store the length
//end initialization temp = string1.find('/');
if (symbol == && temp>) {
symbol = ;
char oper1[];
for (i = , j = ; i < len_oper; i++) {
if (oper[i] != '/') {
oper1[j] = oper[i];
j++;
}//end if }
oper1[j] = ;
oper = oper1;
len_oper = strlen(oper);
} switch (symbol) {
case :
//case of only intergar
suffix[].num = random(inf, sup);
suffix[].flag = ;
suffix[].num = random(inf, sup);
suffix[].flag = ;
count_num = ;
i = ; //end initialization while (count_num < ( + (length - ) / )) {
if (count_num <= count_oper + ) {
//the prefix is full(n numbers and n-1 operators)
suffix[i].flag = ;
suffix[i++].num = random(inf, sup);
count_num++;
}//end if
else if (count_oper == ((length - ) / - )) {
//if there is almost done,then num should not be the last
suffix[i].flag = ;
suffix[i++].num = random(inf, sup);
count_num++;
}
else {
flag = srand_generator(inf, sup, (inf + sup) / );
//generating num & operator with equal possibility
if (flag == ) {
//generating the number
suffix[i].flag = ;
suffix[i++].num = random(inf, sup);
count_num++;
}//end if
else {
//generating the operator
suffix[i].flag = ;
if (flag_pow == && suffix[i - ].flag == ) {
temp = random(, );
if (temp <= && suffix[i - ].num <= _mi) {
suffix[i++].c = '^';
}//end if get ^
else if (temp <= && suffix[i - ].num > _mi) {
if (random(, )) {
suffix[i++].c = oper[random(, len_oper - )];
}//end if get other
else {
suffix[i - ].num = random(, _mi);
suffix[i++].c = '^';
}//end else get '^' but need a change on suffix[i-1]
}//end else if possibly get '^'
else {
suffix[i++].c = oper[random(, len_oper - )];
}//end if no get '^'
}//end if can get ^
else {
suffix[i++].c = oper[random(, len_oper - )];
} count_oper++;
}//end else
}//end else
}//end while break;//end case 0 case :
//initialization
set_num_suffix(suffix[], inf, sup, oper);
set_num_suffix(suffix[], inf, sup, oper); count_num = ;
i = ;
//end initialization
while (count_num < ( + (length - ) / )) {
if (count_num <= count_oper + ) {
//the prefix is full(n numbers and n-1 operators)
set_num_suffix(suffix[i++], inf, sup, oper);
count_num++;
}//end if
else if (count_oper == ((length - ) / - )) {
//if there is almost done,then num should not be the last
set_num_suffix(suffix[i++], inf, sup, oper);
count_num++;
}
else {
flag = srand_generator(inf, sup, (inf + sup) / );
//generating num & operator with equal possibility
if (flag == ) {
//generating the number
set_num_suffix(suffix[i++], inf, sup, oper);
count_num++;
}//end if
else {
//generating the operator
suffix[i].flag = ;
if (flag_pow == && suffix[i - ].flag == ) {
temp = random(, );
if (temp <= && suffix[i - ].num <= _mi) {
suffix[i++].c = '^';
}//end if get ^
else if (temp <= && suffix[i - ].num > _mi) {
if (random(, )) {
suffix[i++].c = oper[random(, len_oper - )];
}//end if get other
else {
suffix[i - ].num = random(, _mi);
suffix[i++].c = '^';
}//end else get '^' but need a change on suffix[i-1]
}//end else if possibly get '^'
else {
suffix[i++].c = oper[random(, len_oper - )];
}//end if no get '^'
}//end if can get ^
else {
suffix[i++].c = oper[random(, len_oper - )];
}
count_oper++;
}//end else
}//end else
}//end while break; case :
//生成小数****************
suffix[].xiao = random_d(inf, sup);
suffix[].flag = ;
suffix[].xiao = random_d(inf, sup);
suffix[].flag = ;
count_num = ;
i = ; //end initialization while (count_num < ( + (length - ) / )) {
if (count_num <= count_oper + ) {
//the prefix is full(n numbers and n-1 operators)
suffix[i].flag = ;
suffix[i++].xiao = random_d(inf, sup);
count_num++;
}//end if
else if (count_oper == ((length - ) / - )) {
//if there is almost done,then num should not be the last
suffix[i].flag = ;
suffix[i++].xiao = random_d(inf, sup);
count_num++;
}
else {
flag = srand_generator(inf, sup, (inf + sup) / );
//generating num & operator with equal possibility
if (flag == ) {
//generating the number
suffix[i].flag = ;
suffix[i++].xiao = random_d(inf, sup);
count_num++;
}//end if
else {
//generating the operator
suffix[i].flag = ;
if (flag_pow == && suffix[i - ].flag == ) {
temp = random(, );
if (temp <= && suffix[i - ].xiao <= double(_mi)) {
suffix[i++].c = '^';
}//end if get ^
else if (temp <= && suffix[i - ].num > double(_mi)) {
if (random(, )) {
suffix[i++].c = oper[random(, len_oper - )];
}//end if get other
else {
suffix[i - ].xiao = random_d(, _mi);
suffix[i++].c = '^';
}//end else get '^' but need a change on suffix[i-1]
}//end else if possibly get '^'
else {
suffix[i++].c = oper[random(, len_oper - )];
}//end if no get '^'
}//end if can get ^
else {
suffix[i++].c = oper[random(, len_oper - )];
} count_oper++;
}//end else
}//end else
}//end while
break;//end case 0
break;
case :
//case of only intergar,with /
suffix[].num = random(inf, sup);
suffix[].flag = ;
suffix[].num = random(inf, sup);
suffix[].flag = ;
count_num = ;
i = ; //end initialization while (count_num < ( + (length - ) / )) {
if (srand_generator(, , )) {
if (count_num <= ((length - ) / - )) {
temp = random(inf, int(floor(sqrt(sup))));
temp1 = random(inf, int(floor(sqrt(sup))));
suffix[i].num = temp1*temp;
suffix[i++].flag = ;
suffix[i].num = temp;
suffix[i++].flag = ;
suffix[i].c = '/';
suffix[i++].flag = ;
count_num += ;
count_oper++; }//if no out of range
else continue; }//end if
else {
if (count_num <= count_oper + ) {
//the prefix is full(n numbers and n-1 operators)
suffix[i].flag = ;
suffix[i++].num = random(inf, sup);
count_num++;
}//end if
else if (count_oper == ((length - ) / - )) {
//if there is almost done,then num should not be the last
suffix[i].flag = ;
suffix[i++].num = random(inf, sup);
count_num++;
}
else {
flag = srand_generator(inf, sup, (inf + sup) / );
//generating num & operator with equal possibility
if (flag == ) {
//generating the number
suffix[i].flag = ;
suffix[i++].num = random(inf, sup);
count_num++;
}//end if
else {
//generating the operator
suffix[i].flag = ; if (flag_pow == && suffix[i - ].flag == ) {
temp = random(, );
if (temp <= && suffix[i - ].num <= _mi) {
suffix[i++].c = '^';
}//end if get ^
else if (temp <= && suffix[i - ].num > _mi) {
if (random(, )) {
suffix[i++].c = oper[random(, len_oper - )];
}//end if get other
else {
suffix[i - ].num = random(, _mi);
suffix[i++].c = '^';
}//end else get '^' but need a change on suffix[i-1]
}
else {
suffix[i++].c = oper[random(, len_oper - )];
}//end if no get '^'
}//end if can get ^
else {
suffix[i++].c = oper[random(, len_oper - )];
}//end if no get '^'
count_oper++;
}//end else
}//end else
}//end else
}//end while break;//end case 3
} while (count_oper < (length - ) / ) {
//fill the last operator
suffix[i].flag = ;
suffix[i++].c = oper[random(, len_oper - )];
count_oper++; }//end while
suffix[i].flag = -;// not need }//end suffix_generator
生成
int calculate() {
//calculate the suffix
//symbol decides the type of calculation
//未考虑分数,小数,真分数,返回整数
//未考虑中间出现负数
Nsqstack s;
Ninitstack(s); int symbol = _symbol; int length = suffix[].num;//the length of exp
int len_num = (length + ) / ;
int len_oper = (length - ) / ; if (length >= ) {
Npush(s, suffix[]);
Npush(s, suffix[]);
}
else return -; Node suffix_left, suffix_right, suffix_result;
init_suffix(suffix_left);
init_suffix(suffix_right);
init_suffix(suffix_result); int i = ;
int left = , right = ;//left number and right number use in case 0&1
double left_d = , right_d = ;//left number and right number use in case 2
char op = ; //operator
int result = -; //result use in case0&1
double result_d = -; switch (symbol) {
case :
//all the num is integar
while (i <= length) {
init_suffix(suffix_result);
if (suffix[i].flag == ) {
//get a num
Npush(s, suffix[i]);
i++;
}//end if else if (suffix[i].flag == ) {
//get a operator
Npop(s, suffix_right);
right = suffix_right.num;
Npop(s, suffix_left);
left = suffix_left.num; op = suffix[i].c; result = cal(left, right, op);
if (result <= -) return -;
else if (result == -) return -;
suffix_result.num = result;
suffix_result.flag = ;
Npush(s, suffix_result);
i++;
}//end else if }//end while Npop(s, _suffix_result);
result = suffix_result.num;
break;
//end case 0
case :
//the num includes fracs&integars
while (i <= length) {
init_suffix(suffix_result);
if (suffix[i].flag == || suffix[i].flag == ) {
//get a num
Npush(s, suffix[i]);
i++;
}//end if else if (suffix[i].flag == ) {
//get a operator
Npop(s, suffix_right); Npop(s, suffix_left);
op = suffix[i].c;
result = cal(suffix_left, suffix_right, suffix_result, op); if (result == -) return -;//error
else if (result == -) return -;//get <0
Npush(s, suffix_result);
i++;
}//end else if }//end while Npop(s, _suffix_result);
return ;
break;
//end case 1 case :
//get result ofxiaoshu
while (i <= length) {
init_suffix(suffix_result);
if (suffix[i].flag == ) {
//get a num
Npush(s, suffix[i]);
i++;
}//end if else if (suffix[i].flag == ) {
//get a operator
Npop(s, suffix_right);
right_d = suffix_right.xiao;
Npop(s, suffix_left);
left_d = suffix_left.xiao; op = suffix[i].c; result = cal(left_d, right_d, op, result_d);
if (result <= -) return -;
else if (result == -) return -;
suffix_result.xiao = result_d;
suffix_result.flag = ;
Npush(s, suffix_result);
i++;
}//end else if }//end while Npop(s, _suffix_result);
result = int(suffix_result.xiao);
break;
//end case 2 } return result;
}//end calculate ,return int*********
计算
可见,两个函数的参数都较为简单(明明就是没有参数啊喂)。原因还是因为前述的,所支持的类型不断的增加,参数也是改了又改。于是最后就决定利用一个全局变量来储存生成的式子的答案了。
实际的生成与计算函数思路较为明确,就是根据相关的参数,来决定是否调用不同的 random 函数。而计算由于直接生成了后缀表达式,所以只需要利用一个栈来计算就好了。
为了表达式的正确性,代码中仍有一些小的细节调整。即:对于一个后缀表达式来说,前两个一定是运算数,而不会是运算符;运算符的数目一定是运算数少1。因此,只要先生成两个运算数,接着再按照规定的数目生成足够的运算符和运算数,即一定可以保证表达式的正确性。
接下来的问题,就是如何把后缀表达式转换为一个中缀表达式的 string了,代码实现如下:
string needbracket(string a, char c, int which) {
//suffix get char by char
int len = a.length();
if (len < ) return a;
int flag = ; switch (c) {
case'+':
return a;
break;
case'-':
{
if (which == ) {
for (int i = ; i < len; i++) {
if (a[i] == '+' || a[i] == '-') flag = ;
if (a[i] == ')') flag = ;
}
if (flag == ) {
string c = "(";
c += a;
c += ")";
return c;
}
}
else {
return a;
}
}
; break;
case'*':
{
for (int i = ; i < len; i++) {
if (a[i] == '+' || a[i] == '-') flag = ;
if (a[i] == ')') flag = ;
}
if (flag == ) {
string c = "(";
c += a;
c += ")";
return c;
}
}
; break;
case'/':
{
if (which == ) {
for (int i = ; i < len; i++) {
if (a[i] > && a[i] < ) flag = ;
if (a[i] == ')') flag = ;
}
if (flag == ) {
string c = "(";
c += a;
c += ")";
return c;
}
}
else {
for (int i = ; i < len; i++) {
if (a[i] == '+' || a[i] == '-') flag = ;
if (a[i] == ')') flag = ;
}
if (flag == ) {
string c = "(";
c += a;
c += ")";
return c;
}
}
}
; break;
case'^':
{
for (int i = ; i < len; i++) {
if (a[i] > && a[i] < ) flag = ;
if (a[i] == ')') flag = ;
}
if (flag == ) {
string c = "(";
c += a;
c += ")";
return c;
}
}
; break; }
return a;
}//end needbracket void getnormalsuffix(int key, int NUM) {
stack<string> Expression;
string next;
string last;
string delete_zero;
char lastop = ;
int flag = ;
for (int i = ; i <= suffix[].num; i++) {
// if is a num/frac then change to string else change to mid
if (suffix[i].flag == -) break;
else if (suffix[i].flag == ) {
Expression.push(to_string(suffix[i].num));
flag++;
//cout << suffix[i].num << ' ';
}//end elseif
else if (suffix[i].flag == ) {
//cout << suffix[i].frac[1] << '`' << suffix[i].frac[2] << '/' << suffix[i].frac[3] << ' ';
string c;
if (suffix[i].frac[] != ) {
c += to_string(suffix[i].frac[]);
c += '`';
}//end if
c += to_string(suffix[i].frac[]);
c += '/';
c += to_string(suffix[i].frac[]);
Expression.push(c);
flag++;
}//end elseif
else if (suffix[i].flag == ) {
delete_zero = to_string(suffix[i].xiao);
for (int j = delete_zero.length(); delete_zero[j - ] == ''; j--) {
delete_zero.erase(j - );
}
Expression.push(delete_zero);
flag++;
}
else {
next = Expression.top();
Expression.pop();
last = Expression.top();
Expression.pop(); if (flag == ) {
next = needbracket(next, suffix[i].c, );
last = needbracket(last, suffix[i].c, );
}//end if
else {
if (flag == ) {
last = needbracket(last, suffix[i].c, );
}//end else
}//end else
last += ' ';
if (suffix[i].c == '^') {
if (PowerType == ) last += suffix[i].c;
else last += "**";
}
else {
last += suffix[i].c;
}
last += ' ';
last += next;
Expression.push(last);
lastop = suffix[i].c;
//cout << suffix[i].c << ' ';
flag = ;
}//end else }//end for
expression = Expression.top();
cout << endl << expression << " " << endl; }//end getnormalsuffix string quantize(string exp, int i) {
if (exp[i] < '') {
exp.erase(i);
return exp;
}
else {
exp.erase(i);
for (int len = exp.length(); i > ; ) {
if (exp[i - ] == '.') i--;
exp[--i]++;
if (exp[i] > '') {
exp[i] = '';
}//end if
else return exp;
}//end for
if (exp[i] == '') {
string temp = "";
exp[i] = '';
temp += exp;
return temp;
}
} }
转化为中缀表达式
同样使用了栈来辅助,其实思路和 Calc() 几乎是一样的,遍历表达式串,如果是数字,进栈,如果是运算符,从栈中取出两个元素进行运算,只不过这个地方是一个字符串的相加,然后利用的栈也是一个字符串类型的栈。
而 needbrack 函数,即是用来给表达式添加合适的括号。简单来说,其逻辑即为读到一个运算符,根据运算符的优先级,来加括号。遍历运算符两边的表达式,如果出现了低优先级的运算符没有被括号“保护”起来,才加括号,从而避免了多余的括号。
从头到尾最让我头疼的一个要求莫过于查重的要求了,但实际上静下心来看看这个要求其实没那么难,先贴上代码:
bool repeated() {
int len = suffix[].num; //suffix[0] 用来储存式子的长度
int char_location = (len + ) / ; //串中运算符开始的位置
for (int i = ; char_location <= len; char_location++, i++;) {
if (suffix[char_location].c == '+' || suffix[char_location].c == '*') {
for (int num_of_exp = ; num_of_exp < expressions.size(); num_of_exp++;) {
if (suffix[i] == expression[num_of_exp][i]) return -;
int flag = ;
for (int j = i; j <= len; j++) {
if (suffix[j] != expression[num_of_exp][j]) flag = ;
}//end for
if (flag == ) return -;
}//end for
}//end if
else continue;
}//end for
return
}
查重
实际可见代码并不长,但嵌套了三个循环。
一开始本身准备对于产生的中缀表达式来进行查重,但是,在转换中缀表达式的 string 的过程中,为了美观添加了大量的空格,加大了操作难度。后来笔者提出对于我们的表达式结构体串来进行操作。
首先,笔者们重新修改了表达式的生成函数,形成了一种更加完全的后缀表达式,例如:
普通的后缀表达式:
3 5 * 2 - 7 /
完全的后缀表达式:
7 2 3 5 * - /
两者实际表达的是同一个运算
对于重复的定义是,可以通过有限次的运算,转化为同一个式子。笔者们便有了如下思路,如果将每一个可以左右交换的运算符(+和*)两边的表达式遍历比较,如果完全相同,便可以得到结果。转换成上述的那种完全后缀的表达式,也更加方便了这样的操作。从代码可见,除了嵌套的循环多了点,其他都好。
同时,由于原始表达式中的数字都是用 int/double 来储存的,比起 string 的比较,速度有所提升。
测试结果
如图可见,测试结果良好。且对于小数位数的四舍五入效果较好
BUG相关
笔者们特意记录了编写过程中所出现的 BUG,现罗列如下:
在转化中缀表达式这个过程中,出现了一个较为难发现的问题。即“减号和除号后面要变号”。起初笔者们在给表达式添加括号时,仅仅判断是否新的运算符优先级更高,却忘了这一条,结果出现了这种如果不仔细看十分难以发现的问题
例如:3 2 1 - - 一式,如果仅仅由运算符的优先级遍历判断,- 号属于最低的优先级,因此出来的结果不会加括号,就出现了:3 - 2 - 1 = 1 这样的结果,一开始也没有想到这个问题。多亏了结对伙伴慧眼明察,后来负责这部分的笔者几乎重新写了整个 needbrack 函数,行数扩充了5倍
再如:在生成式子的时候,因为用户的需求,在写的过程中遇到了很多意想不到的bug。比如一开始我们并没有考虑到整数除法必须得到整除的结果的需求。而在获知该需求以后,我们经历了一些困难,代码的结构也因此发生了一定的变动,而正是这样的变动,使得我们接下来遇到了新的问题。
由于结构的改变,导致本来写好的乘方的计算又出现了问题。因为乘方的幂次和出现程度不能太高,否则的话很容易使得结果溢出,我们只得再次为了乘方对计算模块的结构作出改动。把本来读取字符串中的乘方字符改为用bool型指示变量,然而这个改变又带来了新的问题。由于乘方不在字串中,一旦用户只选择乘方来计算,那么我们的生成模块就会因为输入的是空串导致溢出。为此我们不得不再次对生成模块进行改动,最后原本很流畅的代码被改得面目全非,即使我们已经尽最大的努力使其维持原来的结构,但不得不说,这对我们的调试工作和维护代码工作都带来了很大的影响。