一、前言
知识点:字符串处理,栈和向量的使用,计算器的实现
二、题目
该程序会模拟一个解释运行的编译系统,每次从标准输入中读入一行指令,都要进行对应的操作,该程序中存在四种语句,语句最长200个字,每个语句古且仅古一行,每行最多一个语句。
四种语句分别如下:
1.输入语句,格式为"read+空格+<变量序列>+换行符",变量序列是以空格分隔的变量名称的组合,变量的名字只可能为单个的小写字母,变量不需要提前声明,紧接着下一行,输入若干十进制整数),每个整数和上一行的变量对应赋值
2.赋值语句、格式为"变量+等于符号+表达式+换行符",表达式中包含十进制整数,变量,+-*/()这六种计算符号,整个语句不包含空白符
3.输出语句,格式为"print+空格+<变量序列>+换行符",紧接着下一行,输出若干个变量的值,值以浮点数形式输出,保留两位小数,值之间以空格分隔最后一个值后面不跟空格跟换行符
4.结束语句,格式为"exit+换行符",该语句后直接结束程序运行
测试样例中,所有语句均不包含语法错误,所有变量在使用前均会赋值(不需要考虑输入错误的情况)
输入与输出:read a
10
b=20
c=(a+b)/4
print a b c
10.00 20.00 7.50
Exit
三、分析
首先需要对输入的字符串进行接收和处理,根据不同的关键字进行不同的处理,考虑分别定义相关函数进行调用,read、print、exit、expression相关功能的四个函数。其中,对read和print语句,一开始考虑整行读入,那么便需要按照空格分割字符串,后来改用了先读入一个字符串单词的方式,可以直接先行判断关键字。
此外,难点在于计算器的实现。
计算器的算法思想如下:
设立一个操作符栈op和一个操作数栈num。 op栈底放入结束符#。
• 从左到右扫描表达式,若为操作数则压入num栈,继续扫描;
若为操作符θ2,则将其与op栈顶元素θ1比较:
– 若θ2=θ1=‘#’ ,则运算结束,结果在opnd栈顶;
– 若θ2的优先级高,则θ2入op栈,继续扫描;
– 若优先级相等,则退出optr栈顶元素‘(’,继续扫描;
– 若θ2的优先级低,则从optr栈退出θ1 ,从opnd栈退出两个操作数b和a 作运算a θ1 b后将结果压入num栈。
PS: 表达式expression需要以“#”结尾,与前面op栈放入的结束符#呼应,来判断运算结束。此外, θ2为在表达式中,处于右边的运算符;θ1为处于左边的运算符。
计算器的算符优先级如下表:
根据四则运算法则,写出上表。并数字化表格,左>右,则为-1; 左 < 右,则为1;相等则为0;结束为2;空则非法,为-2。
四、代码实现
#include <iostream>
#include <algorithm>
#include <stack>
#include <string>
#include <vector>
#include <iomanip>
using namespace std;
double value[26]; // 存储变量值
void read_do();
void print_do();
void express(string expr);
double compute(vector<string> expression);
int get_level(string lop, string rop);
int get_op_num(string op);
int main() {
string str;
while (true) {
cin >> str;
if (str == "Exit") {
break;
}
else if (str == "read") {
read_do();
}
else if (str == "print") {
print_do();
}
else {
express(str);
}
}
return 0;
}
void read_do() {
string str_r;
int tmp;
getline(cin, str_r);
for (int i = 0; i < str_r.size(); i++) {
if (isalpha(str_r[i])) {
cin >> tmp;
value[str_r[i] - 'a'] = tmp;
}
}
}
void print_do() {
string str_p;
getline(cin, str_p);
for (int i = 0; i < str_p.size(); i++) {
if (isalpha(str_p[i])) {
cout << fixed << setprecision(2) << value[str_p[i] - 'a']; // 格式控制
if (i == (str_p.size() - 1)) cout << endl;
else cout << " ";
}
}
}
void express(string expr) {
vector<string> expression;
for (int i = 0; i < expr.length(); i++) {
string num = "";
int j = 0;
bool flag = false; // 二位以上连续数字为true;
while (isdigit(expr[i + j])) {
num += expr[i + j];
j++;
}
if (j > 1) {
expression.push_back(num);
i = i + j;
}
else {
string s = "";
expression.push_back( s+=expr[i]);
}
}
compute(expression);
}
double compute(vector<string> expression) {
// 如果是"b=3"这种长度为3的简单式,直接赋值
if (expression.size() == 3) {
value[expression[0][0] - 'a'] = stoi(expression[2]); //expression[0][0]字符串转字符
return 0;
}
stack<double> num; // 操作数栈
stack<string> op; // 操作符栈
//num.push("#");
//
double result = 0;
expression.push_back("#");
op.push("#");
for (int i = 2; i < expression.size(); i++) {
// 若为变量,则获取其值,压入操作数栈中
if (islower(expression[i][0])) {
num.push(value[expression[i][0] - 'a']);
}
// 若为数字,压入操作数栈
else if (isdigit(expression[i][0])) {
num.push(stoi(expression[i]));
}
// 操作符, 右
else {
string right_op = expression[i];
string left_op = op.top();
int level = get_level(left_op, right_op);
switch (level) {
case 2: // 运算结束
result = num.top();
break;
case 1: // 右运算符优先级高,压入操作符栈
op.push(right_op);
break;
case 0: // 优先级相等,弹出op栈顶的”(”
op.pop();
break;
case -1: { // 右运算符优先级低,弹出左运算符和两个操作数进行运算
op.pop();
double num_b, num_a;
num_b = num.top();
num.pop();
num_a = num.top();
num.pop();
double calculate = 0; // 存一次运算的结果
if (left_op == "+") {
calculate = num_a + num_b;
}
else if (left_op == "-") {
calculate = num_a - num_b;
}
else if (left_op == "*") {
calculate = num_a * num_b;
}
else if (left_op == "/") {
calculate = num_a / num_b;
}
num.push(calculate);
break;
}
case -2:
break;
}
}
}
value[expression[0][0] - 'a'] = num.top();
return 0;
}
// 比较算符优先级,若右算符优先级高,则返回1; 相等返回0; 右算符优先级低,则返回-1;非法-2;结束2
int get_level(string lop, string rop) {
//int table[7][7];
int level[49] = { -1,-1,1,1,1,-1,-1,
-1,-1,1,1,1,-1,-1,
-1,-1,-1,-1,1,-1,-1,
-1,-1,-1,-1,1,-1,-1,
1,1,1,1,1,0,-2,
-1,-1,-1,-1,-2,-1,-1,
1,1,1,1,1,-2,2
};
int lopNum = get_op_num(lop);
int ropNum = get_op_num(rop);
return level[lopNum * 7 + ropNum];
}
int get_op_num(string op) {
if (op == "+") return 0;
else if (op == "-") return 1;
else if (op == "*") return 2;
else if (op == "/") return 3;
else if (op == "(") return 4;
else if (op == ")") return 5;
else if (op == "#") return 6;
return -1; // 错误-1
}
五、总结
1. cctype库有函数isdigit判断字符是否数字,isalpha判断字符是否字母,islower判断是否小写,还有更多函数。
2. 输出格式控制,保持两位小数点。
cout << fixed << setprecision(2) << value[str_p[i] - 'a']; // 格式控制
3. 输入利用cin,自动按空格、换行分割,读出的不再留缓冲,而没读出的,包括空格,会留在输入缓冲中。
测试结果如图: