代码位置:
具体介绍:
最近看完了《Effective Morden C++》,里面主要讲解了 C++11/14
的一些的用法,但是暂时想不到用什么项目来写,就把大学的时候实现的一个计算器又做了一遍,顺便用到了刚刚学到的新知识,算是学以致用吧。这篇文章主要对这个计算器的项目做一个介绍,方便大家理解代码和进行扩展,闲话少说,言归正传。
## 1. 整体设计
1.1 逻辑设计
计算器的整体设计思路采用的算符优先文法,也就是确定算符的优先级,然后对该算符两侧的运算数进行运算,结果为一个运算数,然后采用递归的方法进行。
算符优先表
左侧算符 | 右侧算符 | 优先级 |
---|---|---|
ADD_LEVEL | ADD_LEVEL | LEFT_HIGH |
ADD_LEVEL | MUL_LEVEL | RIGHT_HIGH |
ADD_LEVEL | LEFT_BRACKET | RIGHT_HIGH |
ADD_LEVEL | RIGHT_BRACKET | LEFT_HIGH |
——- | ——– | —— |
MUL_LEVEL | ADD_LEVEL | LEFT_HIGH |
MUL_LEVEL | MUL_LEVEL | LEFT_HIGH |
MUL_LEVEL | LEFT_BRACKET | RIGHT_HIGH |
MUL_LEVEL | RIGHT_BRACKET | LEFT_HIGH |
——- | ——– | —— |
LEFT_BRACKET | ADD_LEVEL | RIGHT_HIGH |
LEFT_BRACKET | MUL_LEVEL | RIGHT_HIGH |
LEFT_BRACKET | LEFT_BRACKET | RIGHT_HIGH |
LEFT_BRACKET | RIGHT_BRACKET | EQUAL |
——- | ——– | —— |
RIGHT_BRACKET | ADD_LEVEL | LEFT_HIGH |
RIGHT_BRACKET | MUL_LEVEL | LEFT_HIGH |
RIGHT_BRACKET | LEFT_BRACKET | ERROR |
RIGHT_BRACKET | RIGHT_BRACKET | LEFT_HIGH |
1.2 代码的设计
代码的整体架构的设计如下:
1.3 整体流程
- 解析运算运算式为Tokens。
- 是否为
(
Number
)
的形式 - 是,结束运算,结果为Number。
- 不是,从左到右寻找三个连续的运算符,他们的优先级为
Left Medium
和Medium > Right
,然后对Medium
运算符左右的运算数进行运算,运算的结果替代LeftNum
Medium
RightNum
的位置。 然后跳转到第2步。
2. 举例说明
*举个例子 *
例如对于 2+3*4-5
的分析处理过程如下
- 第一步进行表达式分析,将表达式分析为符号表,分析后的结果如下。
原表达式 | 符号 | 类型 |
---|---|---|
2 | 2 | 运算数 |
+ | ADD | 加法运算符 |
3 | 3 | 运算数 |
* | MUL | 乘法运算符 |
4 | 4 | 运算数 |
- | SUB | 减法运算符 |
5 | 5 | 运算数 |
为了方便运算,将表达式分析结果放在括号里,即最后的分析结果为
原表达式 | 符号 | 类型 |
---|---|---|
( | ( | 左括号 |
2 | 2 | 运算数 |
+ | ADD | 加法运算符 |
3 | 3 | 运算数 |
* | MUL | 乘法运算符 |
4 | 4 | 运算数 |
- | SUB | 减法运算符 |
5 | 5 | 运算数 |
) | ) |
- 按照 em>1.3 /em>流程,这个表达式的分析过程如下
- (2+34-5) 找到的运算符序列为 + * -,对的运算结果如下 3*4=12,运算后的表达式为 (2+12-5)
- (2+12-5) 找到的运算符序列为 ( + -,对+的运算结果如下 2+12 =14,运算后的表达式为 (14-5)
- (14-5) 找到的运算符序列为 ( - ),对 - 的运算结果如下 14-5=9,运算后的表达式为 (9)
- 符合条件
2
,运算结束,结果为9。
3. 代码展示
3.1 CToken的代码
namespace calculater {
class CToken
{
public:
CToken(){};
virtual TokenType getTokenType()=0;
virtual std::string toString()=0;
virtual ~CToken(){};
};
}
对于CToken来说,主要有两个作用
- 将字符串形式的表达式,表示为解析后的Token列表。
- 对数字和运算符提供抽象。
3.2 CTokenNumber的代码
namespace calculater {
class TokenNumber:public CToken
{
public:
explicit TokenNumber(const std::string& str);
explicit TokenNumber(const double value );
double getValue() const;
virtual TokenType getTokenType() override final;
virtual std::string toString() override final;
private:
double m_value;
std::string m_orgStr;
};
}
对于CTokenNumber来说,主要是用来表示表达式中的数字,用到的C++的知识点。
namespace
—-因为继承自CToken
,所以将其放在与CToken
相同的命名空间下。overrite
—-因为CTokenNumber
,重写了CToken
的toString()
方法,使用override
关键字,可以让编译器进行检查,以防止重写错误。final
—-因为toString()
没有被重写的需求,所以此处将方法声明为final
,防止被子类重写。
3.3 TokenOperator的代码
namespace calculater {
class TokenOperator:public CToken
{
public:
explicit TokenOperator();
virtual ~TokenOperator();
TokenType getTokenType() override final;
virtual std::string toString() override;
virtual OperatorType getOperatorType()=0;
virtual OperatorPriority getOperatorPriority()=0;
virtual TokenNumber compute(const TokenNumber& lhs,const TokenNumber& rhs)=0;
};
}
TokenOperator为所有运算符的父类,相对于CToken,这个类多提供三个函数。
- getOperatorType() ,获取运算符类型,最初的设计是为了做运算符的优先级的比较的。现在只作为运算符的类型标识。
- getOperatorPriority(),获取运算符的优先级级别,用于运算符优先级的比较,确定需要进行计算的运算符。
- TokenNumber compute(const TokenNumber& lhs,const TokenNumber& rhs),对运算数进行计算。
virtual func()=0
—-声明虚函数=0,说明此类不提供虚函数的实现,且不可以进行实例化。