代码位置:

Github-Calculator

具体介绍:

最近看完了《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 的分析处理过程如下

  1. 第一步进行表达式分析,将表达式分析为符号表,分析后的结果如下。
原表达式 符号 类型
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>流程,这个表达式的分析过程如下
  1. (2+34-5) 找到的运算符序列为 + * -,对的运算结果如下 3*4=12,运算后的表达式为 (2+12-5)
  2. (2+12-5) 找到的运算符序列为 ( + -,对+的运算结果如下 2+12 =14,运算后的表达式为 (14-5)
  3. (14-5) 找到的运算符序列为 ( - ),对 - 的运算结果如下 14-5=9,运算后的表达式为 (9)
  4. 符合条件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,说明此类不提供虚函数的实现,且不可以进行实例化。