c语言强化训练——简易计算器
一、设计要求
实现一个简单的计算器,要求可以求解表达式,支持基本的运算并有扩展能力和基本的容错能力
二、设计思路
程序需要定义两个工作栈,分别保存表达式计算过程中的运算符与运算数,通过一个优先级表来判定运算顺序。通过判定输入的运算符来调用不同的函数,实现支持基本的运算符号。可以通过定义一个运算符表和一个函数指针表,通过查找方式调用函数,实现运算符的可扩展性。
三、详细设计
1、首先需要编写一个栈,这个栈需要支持浮点数和字符,编写这个栈stack.h文件
typedef struct { char * buffer; int typesize; int top; int max; } Stack; Stack * CreateStack(int max, int typesize); void DestroyStack(Stack *); void ClearStack(Stack *); int Push(Stack *, void *); int Pop(Stack *, void *); int GetElement(Stack*, unsigned int n, void *); int GetTop(Stack *, void *); int IsEmpty(Stack *); int IsFull(Stack *); Stack * CreateStack(int max, int typesize) { Stack * s = (Stack*)malloc(sizeof(Stack)); if (!s) return 0; s->buffer = (char *)malloc(sizeof(char) * max * typesize); if (!s->buffer) return 0; s->top = -1; s->max = max; s->typesize = typesize; return s; } void DestroyStack(Stack* s) { free(s->buffer); free(s); } void ClearStack(Stack * s) { s->top = -1; } int Push(Stack * s, void * data) { if (IsFull(s)) return 0; s->top++; memcpy(s->buffer + s->top*s->typesize, data, s->typesize); return 1; } int Pop(Stack * s, void * data) { if (IsEmpty(s)) return 0; memcpy(data, s->buffer + s->top*s->typesize, s->typesize); s->top--; return 1; } int GetTop(Stack *s, void * data) { if (IsEmpty(s)) return 0; memcpy(data, s->buffer + s->top*s->typesize, s->typesize); return 1; } int IsEmpty(Stack * s) { return s->top == -1; } int IsFull(Stack * s) { return s->top == s->max-1; }
2、下面编写calcu.c文件,实现计算器功能,首先引入需要的头文件
#include "stdlib.h" #include "stack.h"
3、对于用户输入的字符串要进行解析成运算符和数值,可以通过一个数组A来保存,再定义一个标记位数组B,标记数组A中哪一位是数据,哪一位是运算符。为了更明确数据与标记位的关系,这里把二者定义成一个结构体,通过结构体数据接受解析结果
#define fldouble 0 #define flchar 1 typedef struct { double data; char flag; } cal;
4、定义支持的运算符,通过一个函数查找某个字符在运算符表中的位置,若不存在则返回-1
int SearchCode(char ch) { char * code = "+-*/^()#"; int n = 0; while (code[n]) { if (code[n] == ch) return n; n++; } return -1; }
5、定义优先级表,控制运算过程中运算顺序,传入的参数为sch:栈中字符,nch下一个字符,返回优先级顺序
char GetPerferen(char sch, char nch) { char perferen[8][8] = { ">><<<<>>", ">><<<<>>", ">>>><<>>", ">>>><<>>", ">>>>><>>", "<<<<<<=E", ">>>>>E>>", "<<<<<<E=" }; int s = SearchCode(sch); int n = SearchCode(nch); if (s==-1 || n==-1) return 'E'; return perferen[s][n]; } rs; } double calcu(double a, char ch, double b) { double (*function[5])(double,double) = {add,sub,mul,ddiv,mi}; return function[SearchCode(ch)](a,b); }
6、定义支持的运算函数,和一个查找函数指针列表的函数,函数指针列表中的运算函数与运算符表一一对应
double add(double a, double b) { return a+b; } double sub(double a, double b) { return a-b; } double mul(double a, double b) { return a*b; } double ddiv(double a, double b) { return a/b; } double mi(double a, double b) { int n = 1; double rs = a; for (n=1; nreturn
7、编写函数检查用户输入的字符串是否合法,若含有结束符#,或不是有效运算符,不是数字且不是小数点,则视为非法字符,返回0。若字符串合法,在字符串末尾加入运算结束符#
int CheckStr(char * buffer) { int n = -1; while (buffer[++n]) { if ((SearchCode(buffer[n]) != -1 || buffer[n] == '.' || (buffer[n] >= '0' && buffer[n] <= '9')) && buffer[n] != '#') continue; else return 0; } buffer[n] = '#'; buffer[n+1] = 0; return 1; }
8、解析过程是遍历字符串的过程,首先判断当前字符是否为运算符,若不是运算符调用一个函数GetNextValue获取这个数值。若是运算符但为加号或减号,需要判断是正负号还是加减号,再分别处理
首先编写GetNextValue函数,这个函数需要字符串指针,和指向解析过程中计数器的指针,接收结果的指针。若数值中有多个小数点或者小数点出现在第一位或最后一位,则非法,返回0,成功则返回1
int GetNextValue(char * buffer, int * n, double * rs) { char str[30]; int ii=0,ij = 0; while (SearchCode(buffer[*n]) == -1) { str[ii++] = buffer[*n]; (*n)++; } str[ii] = 0; ii=0; while (str[ii]) { if (str[ii++] == '.') ij++; } if (ij>1 || str[ii-1] == '.' || str[0] == '.') return 0; *rs = atof(str); return 1; }
9、解析函数需要传入用户输入的字符串指针和接受解析结果的指针,成功返回0,失败返回1
int resolu(char * buffer, cal * calstr) { int bn = 0, cn = 0; cal c; while (buffer[bn]) { if (SearchCode(buffer[bn]) == -1) { if (!GetNextValue(buffer,&bn, &c.data)) return 0; c.flag = fldouble; calstr[cn++] = c; } else { if ((buffer[bn] == '-' && buffer[bn-1] == '(') || (bn==0 && buffer[0] == '-')) { bn++; if (!GetNextValue(buffer,&bn, &c.data)) return 0; c.data = 0 - c.data; c.flag = fldouble; calstr[cn++] = c; } if ((buffer[bn] == '+' && buffer[bn-1] == '(') || (bn==0 && buffer[0] == '+')) { bn++; if (!GetNextValue(buffer, &bn, &c.data)) return 0; c.flag = fldouble; calstr[cn++] = c; } else { c.data = (double)buffer[bn++]; c.flag = flchar; calstr[cn++] = c; } } } }
10、运算函数
运算过程需要两个工作栈,当符号栈顶和解析串的下一个字符均为#时,表示运算结束。结束后数值栈应该只有一个元素,这个元素就是结果。这个函数需要传入指向解析结果的指针,和一个接收运算结果的指针,返回计算是否成功
int result(cal * calstr, double * rs) { Stack * pst = CreateStack(100,sizeof(char)); Stack * pnd = CreateStack(100,sizeof(double)); double num1,num2; int n = 0; char ch = '#'; Push(pst, &ch); while(ch != '#' || !(calstr[n].flag == flchar && (char)calstr[n].data == '#')) { if (calstr[n].flag == fldouble) { Push(pnd, &(calstr[n].data)); n++; } else { switch( GetPerferen(ch, (char)calstr[n].data)) { case '<': ch = (char)calstr[n].data; Push(pst, &ch); n++; break; case '=': if (!Pop(pst, &ch)) return 0;; n++; break; case '>': if (!(Pop(pnd,&num2) && Pop(pst,&ch) && Pop(pnd,&num1))) return 0;; num1 = calcu(num1,ch,num2); Push(pnd, &num1); break; case 'E': return 0; } } if (!GetTop(pst, &ch)) return 0; } if (GetSize(pnd) == 1 && GetTop(pnd,rs)) { DestroyStack(pst); DestroyStack(pnd); return 1; } else { return 0; } }
11、编写与用户交互的函数
void treatment() { char buffer[100]; cal calstr[50]; double rs; printf("calcu>"); gets(buffer); while (!(buffer[0]=='e' && buffer[1]=='x' && buffer[2]=='i' && buffer[3]=='t')) { if (CheckStr(buffer) && resolu(buffer,calstr) && result(calstr,&rs)) { printf("\n%f\n",rs); } else { printf("\nError!\n"); } printf("\ncalcu>"); gets(buffer); } printf("\nbye\n"); }
12、主函数
main() { printf("Copyright\n\n"); treatment(); }
四、测试
程序在TC2.0环境下测试通过
(I)正常检测:
(1)运算情况是否正确检测:
1.12+3*(3*4^3+3.12542*(1+2))/(4.2-1)+2.1 = 192.01024375
(2)负数运算检测:
-2.1+((-2)^2)= 1.9
(3)正数运算检测:
+2.1+((+2)^2)
(4)单数字输入运算检测:
1 = 1.00000
(II)容错检测:
(1)小数点输入错误检测:
1.22.+3..234..
.1+2
(2)括号是否匹配检测:
((2+3)/(5/4.1)))
(3)运算符输入错误检测:
2.1--2
2.1++2
2.1**2
2.1//2
2.1^^2
(4)字符串输入错误检测:
1+3.1+5=
1#3.3
#
(())
回车
空格