基本要求
用户输入一串表达式字符串,能在txt文本以及控制台输出表达式及其计算结果。
部分结果:
参照
参考内容为数据结构(c语言版,清华大学出版社)P52~P54 。
思路
总体思路为:字符串经过条件判断后,分别压入两个栈中,然后不断计算得到最终结果输出。
创建栈的结构:
//队列结构
typedef struct Sqstack
{
char* base;
char* top;
int stacksize;
}SqC;
typedef struct V_Sqstack
{
int* base;
int* top;
//计数器
int stacksize;
}SqV;
基础思路
1.依次判别字符串中字符的类型:数字或运算符。
根据类型不同将字符入不同栈。
读取字符使用的语句是getchar()
,整个读取循环操作的基础
。
根据输入流不断向下读取:
//不断向下读取字符,直到读取到设置的结束字符,程序结束,得到最终成果。
char c = getchar();
判别函数:
//辅助函数:用于判断字符进栈分类
bool InOP(char c)
{
if (c == '*' || '(' == c || ')' == c || '/' == c || '+' == c || '-' == c || '#' == c)
return true;
else
return false;
};
判别函数的应用:
//读取数字
if (!op.InOP(c))
{
//对数字的操作
...
}
else
{
...//对运算符的操作
}
需要注意:
基本四则运算式的顺序并不是一致的,即并不是都从左往右或都从右往左的。因为,运算符有优先级。
例如: 3- 2*2 与 (3-2)*2 运算顺序明显不同。
但两个数的四则运算是固定的。 因此,可以将一个运算式分解成若干个两数的四则运算再进行组合。
例如:
a.在3-2*2中,运算符“ - ”的优先级低于“ * ”,故而,运算顺序为:
2*2 得到 4,然后3 - 4 得到 -1。
b.在(3-2)*2中,由于( )的存在,优先计算内部算式
,故而,运算顺序为:
3 - 2 得到 1,然后1*2得到 2。
//计算四则运算式
int OpOp(int b, char theta, int a)
{
if (theta == '*')
return a * b;
if (theta == '/')
return a / b;
if (theta == '+')
return a + b;
if (theta == '-')
return a - b;
}
由此,得到下一步思路:
2.栈顶元素运算:
根据运算符优先级的比较,对输入的表达式中数字进行处理。
例如:
3-2*2中,运算符“-”与“*”相比较,运算优先级为“-”小于“ * ”,因此,先处理2与2的运算。
因此,在两个栈中,总是需要根据运算符优先级比较,对栈顶元素进行操作
(加入,脱出,更新)。
// 运算符优先级比较表
char OP::Precede(char c1, char c2)
{
//+ -运算符等级
if (c1 == '+')
if (c2 == '+' || c2 == '-' || c2 == ')' || c2 == '#')
return '>';
else if (c2 == '*' || c2 == '/' || c2 == '(')
return '<';
else
return ERROR;
else if (c1 == '-')
if (c2 == '+' || c2 == '-' || c2 == ')' || c2 == '#')
return '>';
else if (c2 == '*' || c2 == '/' || c2 == '(')
return '<';
else
return ERROR;
//* /运算符等级
else if (c1 == '*')
if (c2 == '+' || c2 == '-' || c2 == '*' || c2 == '/' || c2 == ')' || c2 == '#')
return '>';
else if (c2 == '(')
return '<';
else
return ERROR;
else if (c1 == '/')
if (c2 == '+' || c2 == '-' || c2 == '*' || c2 == '/' || c2 == ')' || c2 == '#')
return '>';
else if (c2 == '(')
return '<';
else
return ERROR;
// ( )运算符等级
else if (c1 == '(')
if (c2 == ')')
return '=';
else if (c2 == '#')
return ERROR;
else
return '<';
else if (c1 == ')')
if (c2 == ')')
return ERROR;
else
return '>';
else if (c1 == '#')
if (c2 == '#')
return '=';
else
return '<';
else
return ERROR;
}
注明:其中“#”为最后结束输入的判断字符。 相应优先级为最低。
3.输入字符串转化为整型数字
需要注意到,输入的表达式为一串字符串,而在第一步,已经完成了运算符与非运算符的判别,如今需要对类型进行转化。
可以知道的是数字 “0”在阿斯克码中排行"48" 。
则字符型自然数转化为整型的函数可以得到:
int OP::c_to_int(char c)
{
return int(c - '0');
}
但这种函数现在只能处理一位数字,需要得到连续数字,即大数字转化,设立循环。
因为整数的每一位都由自然数构成。
循环代码:
//将一串自然数转化为int类型数字
int count = 0;
//创建字符数组储存转化数字
const int arr_max = 5;
char c_to_i[arr_max];
int to_i[arr_max];
while (!op.InOP(c))
{
c_to_i[count] = c;
to_i[count++] = op.c_to_int(c);
//向下移动
c = getchar();
To_File(c);
}
//转化为数字
int result = 0;
for (int i = 1; i <= count; i++)
{
int num = to_i[count - i];
for (int m = 1; m < i; m++)
num *= 10;
result += num;
}
4.根据优先级进行的操作:
优先级根据运算符的不同分为三类:大于,小于,等于。
例如:
大于:“*”>“-”,“*”>“(”。
小于与大于正好相反。
等于:“(”=“)”。
因此根据上述第二步的优先级比较表,使用switch语句
进行分支运行。
switch (op.Precede(op.GetTop(), c))
{
case '<':
op.PushStack(c); c = getchar();
To_File(c);
break;
case'=':
//在运算符栈中,将前括号去掉
op.Pop();
c = getchar();
To_File(c);
break;
case'>':
//记录运算符
int op_ab =op.Pop();
//两个数字运算
int a = op.Pop_V();
int b = op.Pop_V();
//将两个数字运算后加入栈顶
op.PushStack(op.OpOp(a, op_ab, b));
break;
}
注明:就如代码中注明的,不同的两个运算符比较,会对栈顶进行不同操作,分为三类:
栈顶元素的加入,运算并更改栈顶值,脱出栈顶元素。
类创建
确认了基本思路后,开始根据思路创建函数。(由于博主更习惯于使用类解决问题,所以程序代码会有所不同。)
类基础数据
数字栈与运算符栈:
//队列结构
typedef struct Sqstack
{
char* base;
char* top;
int stacksize;
}SqC;
typedef struct V_Sqstack
{
int* base;
int* top;
//计数器
int stacksize;
}SqV;
类中私有数据:
private:
static const int MAX_SIZE = 20;
//运算符栈
SqC oper;
//运算后得到值的储存栈
SqV Value;
类中公有函数:
public:
OP();
~OP();
//辅助函数:用于判断字符进栈分类
bool InOP(char c)
{
if (c == '*' || '(' == c || ')' == c || '/' == c || '+' == c || '-' == c || '#' == c)
return true;
else
return false;
};
//将输入加入到栈队列中
//数字进入数字队列,字符进入字符队列
void PushStack(char c);
//数字栈
void PushStack(int c);
//得到栈顶元素
char GetTop()const
{
if (oper.top == oper.base)
return ERROR;
return *(oper.top - 1);
}
int GetPop_V()
{
if (Value.top == Value.base)
return ERROR;
return *(Value.top - 1);
}
//栈顶元素脱出
char Pop()
{
if (oper.top == oper.base)
return ERROR;
return *(--oper.top);
}
int Pop_V()
{
if (Value.top == Value.base)
return ERROR;
return *(--Value.top);
}
//运算符比较函数
char Precede(char c, char ch);
//辅助函数
//将char 转化为 int
int c_to_int(char c);
//计算四则运算式
int OpOp(int b, char theta, int a)
{
if (theta == '*')
return a * b;
if (theta == '/')
return a / b;
if (theta == '+')
return a + b;
if (theta == '-')
return a - b;
}
实现方法
对需要用到的类函数进行实现构造。
类实现方法
构造函数:
创建运算符栈以及数字栈,并初始化类数据
。
OP::OP()
{
//运算符栈
oper.base = new char[MAX_SIZE];
oper.top = oper.base;
oper.stacksize = 0;
//数字栈
Value.base = new int[MAX_SIZE];
Value.top = Value.base;
Value.stacksize = 0;
}
析构函数:
不做操作。
/析构函数
OP::~OP() {};
元素加入栈顶函数:
由于两个栈只是类型不同,构造相似,故而使用函数重载
。
//运算符元素加入栈顶
void OP::PushStack(char c)
{
//保障安全性
if (oper.top - oper.base >= MAX_SIZE)
{
//复制原栈队列储存值,并且将栈扩容
oper.base = (char*)realloc(oper.base, (oper.stacksize + MAX_SIZE) * sizeof(char));
//保证安全性
if (!oper.base)
exit(OVERFLOW);
oper.top = oper.base + oper.stacksize;
oper.stacksize += MAX_SIZE;
}
*oper.top++ = c;
oper.stacksize++;
}
//int 类型
void OP::PushStack(int c)
{
if (Value.top - Value.base >= MAX_SIZE)
{
//复制原栈队列储存值,并且将栈扩容
Value.base = (int*)realloc(Value.base, (Value.stacksize + MAX_SIZE) * sizeof(char));
if (!Value.base)
exit(OVERFLOW);
Value.top = Value.base + Value.stacksize;
Value.stacksize += MAX_SIZE;
}
*Value.top++ = c;
Value.stacksize++;
}
转换函数:
//转化函数:将char型转化为int型
int OP::c_to_int(char c)
{
return int(c - '0');
}
运算符比较函数:
依次对使用到的所有运算符进行优先级比较。
// 运算符优先级比较表
char OP::Precede(char c1, char c2)
{
//+ -运算符等级
if (c1 == '+')
if (c2 == '+' || c2 == '-' || c2 == ')' || c2 == '#')
return '>';
else if (c2 == '*' || c2 == '/' || c2 == '(')
return '<';
else
return ERROR;
else if (c1 == '-')
if (c2 == '+' || c2 == '-' || c2 == ')' || c2 == '#')
return '>';
else if (c2 == '*' || c2 == '/' || c2 == '(')
return '<';
else
return ERROR;
//* /运算符等级
else if (c1 == '*')
if (c2 == '+' || c2 == '-' || c2 == '*' || c2 == '/' || c2 == ')' || c2 == '#')
return '>';
else if (c2 == '(')
return '<';
else
return ERROR;
else if (c1 == '/')
if (c2 == '+' || c2 == '-' || c2 == '*' || c2 == '/' || c2 == ')' || c2 == '#')
return '>';
else if (c2 == '(')
return '<';
else
return ERROR;
// ( )运算符等级
else if (c1 == '(')
if (c2 == ')')
return '=';
else if (c2 == '#')
return ERROR;
else
return '<';
else if (c1 == ')')
if (c2 == ')')
return ERROR;
else
return '>';
else if (c1 == '#')
if (c2 == '#')
return '=';
else
return '<';
else
return ERROR;
}
类中的内联函数:
判断函数:
//辅助函数:用于判断字符进栈分类
bool InOP(char c)
{
if (c == '*' || '(' == c || ')' == c || '/' == c || '+' == c || '-' == c || '#' == c)
return true;
else
return false;
};
返回栈顶元素:
主要用于栈顶的运算符优先级比较。
//得到栈顶元素
char GetTop()const
{
//保障正确性
if (oper.top == oper.base)
return ERROR;
return *(oper.top - 1);
}
int GetPop_V()
{
if (Value.top == Value.base)
return ERROR;
return *(Value.top - 1);
}
栈顶元素的脱出:
用于计算过后的字符脱出栈列,以及运算符"(“和”)"的消去。
//栈顶元素脱出
char Pop()
{
if (oper.top == oper.base)
return ERROR;
return *(--oper.top);
}
int Pop_V()
{
if (Value.top == Value.base)
return ERROR;
return *(--Value.top);
}
四则运算:
两数的四则运算是组成计算式的基础。
int OpOp(int b, char theta, int a)
{
if (theta == '*')
return a * b;
if (theta == '/')
return a / b;
if (theta == '+')
return a + b;
if (theta == '-')
return a - b;
}
类外函数
输入文件函数:
在txt文件中展示:
其中,设立了一个静态变量,通过它控制额外输入:(计算式)
void To_File(char c)
{
using namespace std;
//计数器
static int Count_V = 1;
ofstream FileOut;
FileOut.open("OutPut_OP.txt", ios::out | ios::app);
if (Count_V == 1)
{
FileOut << "计算式:\n";
Count_V++;
}
//创建录入数字
if (!FileOut.is_open())
{
cout << "ERROR";
exit(EXIT_FAILURE);
}
//不让结束字符输入到文件中
if (c != '#')
FileOut << c;
};
主函数
由思路搭建框架,函数完成具体实现
。
int main()
{
using namespace std;
//创造对象
OP op;
//加入字符栈底
op.PushStack('#');
ofstream FileOut;
FileOut.open("OutPut_OP.txt", ios::out | ios::app);
//创建录入数字
if (!FileOut.is_open())
{
cout << "ERROR";
exit(EXIT_FAILURE);
}
char c = getchar();
//将除'#'以外的字符都输入到文件中
To_File(c);
while (c != '#' || op.GetTop() != '#')
{
//读取数字
if (!op.InOP(c))
{
//将一串自然数转化为int类型数字
int count = 0;
//创建字符数组储存转化数字
const int arr_max = 5;
char c_to_i[arr_max];
int to_i[arr_max];
while (!op.InOP(c))
{
c_to_i[count] = c;
to_i[count++] = op.c_to_int(c);
//向下移动
c = getchar();
To_File(c);
}
//转化为数字
int result = 0;
for (int i = 1; i <= count; i++)
{
int num = to_i[count - i];
for (int m = 1; m < i; m++)
num *= 10;
result += num;
}
op.PushStack(result);
//读取符号(已经有了getchar());
}
else
//开始运算
switch (op.Precede(op.GetTop(), c))
{
case '<':
op.PushStack(c); c = getchar();
To_File(c);
break;
case'=':
//在运算符栈中,将前括号去掉
op.Pop();
c = getchar();
To_File(c);
break;
case'>':
//记录运算符
int op_ab =op.Pop();
//两个数字运算
int a = op.Pop_V();
int b = op.Pop_V();
//将两个数字运算后加入栈顶
op.PushStack(op.OpOp(a, op_ab, b));
break;
}
}
cout << "结果为:\n";
FileOut << '='<<op.GetPop_V()<<endl;
return op.Pop_V();
}
至此,程序完成。
程序结果截图
控制台结果输出:
txt结果输出保存:
代码仅供参考,如有错误欢迎指正,欢迎留言。