学习了C++简易计算器的程序设计过程后,觉得有必要对其进行一个梳理,其中的知识点和思想还是很重要的。
OpenJudge上已经把所学内容浓缩成了两个题目,下面主要是对这两个题目的参考代码进行解析。
A
简单计算器之支持符号
总时间限制: 1000ms 内存限制: 65536kB
描述
计算器程序的输入是一个单词序列,如(1.5+4)*11
我们希望实现一个简单计算器,能够支持对加减乘除的运算,正如你在练习1中做过的那样。
但是这一次,我们不再限定输入是3个操作符,而是一个表达式, 表达式中包含括号的使用,比如(3-2.4)/(7+9)
除此之外,我们还希望在程序中增加对于{}的支持,另其作用与()一致,这样,{(4+5)*6/(3*4)}就是一个合法的表达式
输入
一个字符串表达式,能够保证所有的测试测试样例的表达式都是合理的,只包含+, -, *, /,(), {}以及数字,数字仍然都是double型的
输出
输出计算结果,计算结果保留小数点后6位,如果计算结果没有6位,也保留到六位后为0
样例输入
(3-2.4)/(7+3)
样例输出
0.060000
代码逐段分析:
#include <iostream>
#include <iomanip>
using namespace std;const char number = '8';
const char quit ='\n';inline void error(const string& s)
{throw runtime_error(s);
}
这一段是一些初始操作,包括定义表达式的部分分词的类型:number
和quit
。而error
函数则抛出运行时的异常问题。
接下来是第一个类:分词类。
将表达式拆分为基本单元,每个单元就是一个分词。分词具有两方面的属性:类型和值,其中值只对数字有意义,其构造函数的初始化是很常见的。
class token
{
public:char kind;double value;token(char ch):kind(ch), value(0) {}token(char ch, double val):kind(ch), value(val) {}
};
仅有分词类是不够的,输入流的处理是本问题的第二个难点。输入流需要完成以下功能:读取输入,对不合理的输入进行putback
操作。我们将输入流写成类,得到如下的代码:
class token_stream
{
public:token_stream();token get();void putback(token t);
private:token buffer;// 缓冲分词bool full;
};
token_stream::token_stream():full(false), buffer(0) // no Token in buffer
{
}token_stream ts;
下面是对相关函数的具体实现:
putback()
函数
void token_stream::putback(token t){if (full) error("putback() into a full buffer");buffer = t; // copy t to bufferfull = true; // buffer is now full
}
get()
函数
token token_stream::get()
{if (full){full = false;return buffer;// 返回缓冲内容}char ch;cin >>noskipws>> ch;switch (ch){case quit:case '+':case '-':case '*':case '/':case '(':case ')':case '{':case '}':return token(ch);case '0': case '1': case '2': case '3': case '4': case '5':case '6': case '7': case '8': case '9':case '.':{cin.putback(ch);// 小数点放回缓冲区double val;cin >> val;return token(number, val);}default:error("bad token");return token(ch);}
}
下面是对表达式的计算。这里采用文法的方式进行运算,有兴趣可以阅读原书,重点是将表达式分解为基本单元,并利用其文法完成对表达式的计算。要用到以下三个函数:expression()
,term()
,primary()
。
double expression();
double term();
double primary();
//循环调用
下面是对其的实现:
expression()
函数
double expression()
{double left = term();token t = ts.get();while (true){switch (t.kind){case '+':left += term();t = ts.get();break;case '-':left -= term();t = ts.get();break;default:ts.putback(t);return left;}}
}
term()
函数
double term()
{double left = primary();token t = ts.get();while (true){switch (t.kind){case '*':left *= primary();t = ts.get();break;case '/':{double d = primary();if (d == 0) error("divide by zero");left /= d;t = ts.get();break;}default:ts.putback(t);return left;}}
}
primary()
函数
double primary()
{token t = ts.get();while (true){switch (t.kind){case number:return t.value;case '(':{double d = expression();t = ts.get();if (t.kind != ')') error("')' expected");return d;}case '{':{double d = expression();t = ts.get();return d;}default:error("primary expected");return 0;}}
}
最后到了收尾阶段,在主函数里创建分词对象,并进行输入,解析,输出。
int main()
{try {double val = 0;while (cin) {token t = ts.get();if (t.kind == quit) {cout << fixed << setprecision(6) << val << endl;break;}else {ts.putback(t);val = expression();}}}catch (exception & e) {cerr << e.what() << endl;return 1;}catch (...) {cerr << "exception \n";return 2;}
}
根据try{}
与catch()
尝试并处理抛出异常,这部分内容在后续的学习中会陆续补全。首先我们创建一个分词对象,利用输入流读入,当读到\n
时,表达式结束,此时其值也计算完毕,输出即可,否则则将其存入缓冲区,进行表达式的计算。
程序完整的代码如下:
/*
Author:Zac.
Date:2025.3.20
Description:Homework3.A
*/
#include <iostream>
#include <iomanip>
using namespace std;const char number = '8';
const char quit ='\n';inline void error(const string& s)
{throw runtime_error(s);
}class token
{
public:char kind;double value;token(char ch):kind(ch), value(0) {}token(char ch, double val):kind(ch), value(val) {}
};class token_stream
{
public:token_stream();token get();void putback(token t);
private:token buffer;bool full;
};token_stream::token_stream():full(false), buffer(0) // no Token in buffer
{}token_stream ts;void token_stream::putback(token t){if (full) error("putback() into a full buffer");buffer = t; // copy t to bufferfull = true; // buffer is now full
}token token_stream::get()
{if (full){full = false;return buffer;}char ch;cin >>noskipws>> ch;switch (ch){case quit:case '+':case '-':case '*':case '/':case '(':case ')':case '{':case '}':return token(ch);case '0': case '1': case '2': case '3': case '4': case '5':case '6': case '7': case '8': case '9':case '.':{cin.putback(ch);double val;cin >> val;return token(number, val);}default:error("bad token");return token(ch);}
}double expression();
double term();
double primary();
//循环调用double expression()
{double left = term();token t = ts.get();while (true){switch (t.kind){case '+':left += term();t = ts.get();break;case '-':left -= term();t = ts.get();break;default:ts.putback(t);return left;}}
}double term()
{double left = primary();token t = ts.get();while (true){switch (t.kind){case '*':left *= primary();t = ts.get();break;case '/':{double d = primary();if (d == 0) error("divide by zero");left /= d;t = ts.get();break;}default:ts.putback(t);return left;}}
}double primary()
{token t = ts.get();while (true){switch (t.kind){case number:return t.value;case '(':{double d = expression();t = ts.get();if (t.kind != ')') error("')' expected");return d;}case '{':{double d = expression();t = ts.get();return d;}default:error("primary expected");return 0;}}
}int main()
{try {double val = 0;while (cin) {token t = ts.get();if (t.kind == quit) {cout << fixed << setprecision(6) << val << endl;break;}else {ts.putback(t);val = expression();}}}catch (exception & e) {cerr << e.what() << endl;return 1;}catch (...) {cerr << "exception \n";return 2;}
}
这样,我们完成了对简易计算器的第一版实现,在命令行里输入表达式,即可以进行计算。在第二版中,我们将实现支持变量和常量的简易计算器。