文章目录

  • ​​1.Scanner类的实现​​
  • ​​2.Scanner​​

1.Scanner类的实现

  • Scanner类的类图
    作用:一个字符一个字符扫描一个表达式,等级表达式当前的状态
    curPos_:当前扫描到的位置;
    token_:当前扫描到的状态;
    Scanner类方法如下:
    Number:扫描到数字,返回数字;
    Accept:扫描一个字符,登记当前的状态
    Token:返回当前状态;
    SkipWhite:跳过空白;

    表达式当前的状态如下:EToken是枚举量
    扫描完毕;
    ERROR;
    扫描到数字;
    扫描到加号;
    扫描到减号;
    扫描到乘号;
    扫描到除号;
    扫描到左括号;
    扫描到右括号;
    扫描到变量的标识符;(后面再讲。。。)

注意枚举量的startUML画法:这里还补充的Scanner类和Parse的类图,UML只是表示类之间的关系,没必要把数据成员写的很清楚。

(P41)面向对象版表达式计算器:Scanner类的实现 ,Scanner_派生类

2.Scanner

  • 字符串转double的实现
    strtod,意思:strtodouble
double strtod(const char* nptr, char **endptr);

endptr:
返回指针的指针,移动到一个不是数字的位置;
这里指针的指针的含义:指针传递本质上也是值传递,现在传递一个指针进来,我希望改变指针变量本身的地
址,而不是改变指针所指向的内容,这就需要指针的指针,因为指针本身也是一个值。

char *p;
number_=strtod(&buf_[curPos_], &p);

eg:将字符串3.5转换成一个double类型,且希望curPos_要移动到+号的位置,strtod第二个参数返回一个指针,移动到了+号的位置

(P41)面向对象版表达式计算器:Scanner类的实现 ,Scanner_Parse_02


P41\Scanner.h

#ifndef _SCANNER_H_
#define _SCANNER_H_
#include <string>

enum EToken
{
TOKEN_END;
TOKEN_ERROR;
TOKEN_NUMBER;
TOKEN_PLUS;
TOKEN_MINUS;
TOKEN_MULTIPLY;
TOKEN_DIVIDE;
TOKEN_LPARENTHESIS;
TOKEN_RPARENTHESIS;
TOKEN_IDENTIFIER;
TOKEN_ASSIGN;//eg:a=5
};

//Scanner类:只负责扫描,并且登记当前的状态
class Scanner
{
public:
Scanner(const std::string& buf);
void Accept();
double Number() const;
EToken Token() const;
private:
void SkipWhite();
const std:string buf_;
unsigned int curPos_;
EToken token_;//返回状态
double number_;//返回数字
};

#endif/*_SCANNER_H_*/

P41\Scanner.cpp

#include "Scanner.h"
#include <cctype>

Scanner::Scan(const std::string& buf) : buf_(buf), curPos_(0)
{
Accept();//一个字符一个字符的扫描
}

double Scanner::Number() const
{
return number_;
}

EToken Scanner::Token() const
{
return token_;
}

//忽略空白字符
void Scanner::SkipWhite()
{
while (isspace(buf_[curPos_]))
++curPos_;
}

void Scanner::Accept()
{
SkipWhite();//首先忽略空白字符
switch (buf_[curPos_])
{
case '+':
token_ = TOKEN_ADD;
++curPos_;
break;
case '-':
token_ = TOKEN_MINUS;
++curPos_;
break;
case '*':
token_ = TOKEN_MULTIPLY;
++curPos_;
break;
case '/':
token_ = TOKEN_DIVIDE;
++curPos_;
break;
case '(':
token_ = TOKEN_LPARENTHESIS;
++curPos_;
break;
case ')':
token_ = TOKEN_RPARENTHESIS;
++curPos_;
break;
case '0': case '1': case '2' : case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '.':
token_ = TOKEN_NUMBER;
char* p;//实际上这里的指针并没有指向
//buf_是一个字符串,buf_[curPos_]是一个字符,相当于得到了内部字符串的字符
//这里的指针p,指针变量p本身发送改变,也就是说它指向了其他地方,改变了指针的指向
number_ = strtod(&buf_[curPos_], &p);//返回第一个不是数字的位置
//将地址p转换为数字curPos_,用以更新curPos_
curPos_ = p - &buf[0];// &buf[0]是字符串的首地址
break;
case '\0' : case '\n' : case '\r' : case EOF://认为表达式结束了
token_ = TOKEN_END;
break;
default:
token_ = TOKEN_ERROR;
break;
}
}

P41\Parser.h

#ifndef _PARSER_H
#define _PARSER_H

//使用前向声明而不是包含Scanner的头文件的原因是,如果在cpp文件中多次包含了这样的头文件,使得生成的可执行文件增大
class Scanner;

//Parser类:根据扫描结果,进行扫描,递归下降解析,直到生成一颗树
class Parser
{
public:
Parser(Scanner& scanner);
void Parse();
double Calculate() const;
private:
Scanner& scanner_;
};



#endif /* _PARSER_H */

P41\Parser.cpp

#include "Parser.h"
#include "Scanner.h"//因为会使用到Scanner的一些接口进行扫描

//引用的初始化只能才初始化列表中进行初始化
Parser::Parser(Scanner& scanner) : scanner_(scanner)
{

}

void Parser::Parse()
{

}

//注意:带const的成员函数与不带const的成员函数可以构成重载
double Parser::Calculate() const
{
return 0.0;
}

P41\Node.h

#ifndef _NODE_H
#define _NODE_H

//(1)【采用】禁止对象拷贝的eg演示
//Noncopyable不能构造对象,因为构造对象没意义,仅仅用来继承
class Noncopyable
{
protected:
Noncopyable() {};
~Noncopyable() {};
private:
Noncopyable(const Noncopyable&);
const Noncopyable& operator=(const Noncopyable&);
};

//用private继承的原因是:并没有继承Noncopyable类的接口,即:这不是接口继承,而是实现继承
//实现继承:仅仅利用基类的内部函数,仅仅能在派生类的内部使用,并不能成为派生类额接口
//Node变成了对象语义,因为:要构造Node,要先构造Noncopyable,而Noncopyable既不能拷贝构造,也不能赋值了
class Node : private Noncopyable
{
public:
//每个节点都有一个计算的纯虚函数
//类Node用于多态,派生类都要实现Calc
//Calc声明为const的,因为Calc不会改变类的成员
virtual double Calc() const = 0;
//类Node是多态类,析构函数也要声明为虚析构函数,否则基类指针指向派生类对象,
//通过基类指针释放对象的时候,是不会调用派生类的析构函数
virtual ~Node() {};
};

//NumerNode要实现这个纯虚函数Calc,为具体类;若没实现,还是抽象类
class NumberNode : public Node
{
public:
NumberNode(double number) : number_(number) {}
double Calc() const;
private:
const double number_;//加const的原因:因为number_初始化后就不会改变
};

//BinaryNode节点有2个子节点
//BinaryNode类没有实现Calc方法,BinaryNode类仍然是抽象类,只有它的派生类,加、减、乘、除节点才知道该如何计算
class BinaryNode : public Node
{
public:
BinaryNode(Node* left, Node* right)
: left_(left), right_(right) {}
~BinaryNode();//记得要释放left_和right_节点
protected:
Node* const left_;//const的作用:指针不能改变(即指针不能指向其它的节点),而不是指针所指向的内容不能改变
Node* const right_;
};

//于BinaryNode相比,它只有1个孩子
//UnaryNod也是抽象类,因为它没有实现Calc方法
class UnaryNode : public Node
{
public:
UnaryNode(Node* child)
: child_(child) {}
~UnaryNode();
protected:
Node* child_;
}

//加法运算节点AddNode
class AddNode : public BinaryNode
{
public:
//构造函数初始化,要调用基类部分的构造函数
AddNode(Node* left, Node* right)
: BinaryNode(left, right) {}
//要实现Calc方法,AddNode类是具体类
double Calc() const;
};

class SubNode : public BinaryNode
{
public:
SubNode(Node* left, Node* right)
: BinaryNode(left, right) {}
double Calc() const;
};

class MultiplyNode : public BinaryNode
{
public:
MultiplyNode(Node* left, Node* right)
: BinaryNode(left, right) {}
double Calc() const;
};

class DivideNode : public BinaryNode
{
public:
DivideNode(Node* left, Node* right)
: BinaryNode(left, right) {}
double Calc() const;
};

class UminusNode : public UnaryNode
{
public:
UminusNode(Node* child)
: UnaryNode(child) {}
double Calc() const;
};



#endif/* _NODE_H */

P41\Node.cpp

#include "Node.h"
#include <cmath.h>
#include <iostream>

//数字节点的计算方法就等于数字节点本身
double NumberNode::Calc() const
{
return number_;
}
BinaryNode::~BinaryNode()
{
delete left_;
delete right_;
}

UnaryNode::~UnaryNode();
{
delete child_;
}

double AddNode::Calc() const
{
//AddNode节点的值等于左计算节点得到的值+右计算节点得到的值
return left_->Calc() + right_->Calc();
}

double SubNode::Calc() const
{
return left_->Calc() - right_->Calc();
}

double MultiplyNode::Calc() const
{
return left_->Calc() * right_->Calc();
}


double AddNode::Calc() const
{
double divisor = right_->Calc();
if (divisor != 0.0)
return left_->Calc() / divisor;
else
{
std::cout << "Error: Divisor by zero" <<std::endl;
return HUGE_VAL;
}
}


double UnaryNode::Calc() const
{
//孩子节点前面加一个负号,对照改进类继承体系的图看更好理解
return - child_->Calc();
}

P41\main.cpp

#include <iostream>
#include <string>
#include "Scanner.h"
#include "Parser.h"

int main(void)
{
do
{
std::cout<<">";
std::string buffer;
std::getline(std::cin, buffer);//输入一行表达式放到buf当中
Scanner scanner(buffer);
Parser parser(scanner);
parser.Parse();//实际上计算表达式的值,就是计算这颗树的根节点的值
paese.Calculate();
}while(1);
return 0;
}