文章目录

  • 1.让表达式计算器支持变量赋值
  • 2.Calc类实现

1.让表达式计算器支持变量赋值

  • eg:a=5,解析成一个表达式树应该如下,其中a变量的值应该从Storage类中去获取
    AssignNode节点的Calc方法,比如:x=5,=号节点的值,其实等于x节点,也等于5节点,首先取出右节点的值,然后调用左节点的Assign方法

  • 类图如下:
    新增变量节点VariableNode,VariableNode继承至Node,在Node中新增Assign方法,用以变量赋值,IsValue判断变量是左值,其他节点都不是左值;
    新增赋值运算节点AssignNode,它是二元运算节点,其左节点是变量,右节点是数字节点,所以继承至二元运算节点BinaryNode;

  • startUML,只画新增的


    P46\Node.h

#ifndef _NODE_H
#define _NODE_H#include <vector>
#include <cassert>class Storage;//(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 bool IsLvalue() const{return false;}//只有变量节点有赋值的方法,其他节点都没有virtual void Assign(double){assert(!"Assign called incorrectlly");//“XX”字符串是真的,取一个!为假的,默认情况下断言为假的}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;
};//MultiplyNode没有实现Calc方法,还是一个抽象类
class MultipleNode : public Node
{public:MultipleNode(Node*){//添加第一个节点。第一个节点总是正的,比如-7-5+1,该-7就是第一个节点,在解析的时候它已经不是NumberNode//它应该是UminusNode,它里面已经能够处理负号了,应该把它当成是正的(+)-7AppendChild(node, true);}void AppendChild(Node* child, bool positive){childs_.push_back(child);child_.push_back(positive);}~MultipleNode();
private://有很多子代,将其放入向量中std::vector<Node*> childs_;std::vector<bool> positive;//节点的正负性
};class SumNode : public : MultipleNode
{public:SumNode(Node* node): MultipleNode(node) {}double Calc() const;
};//乘积节点
class ProductNode : public : MultipleNode
{public:SumNode(Node* node): MultipleNode(node) {}double Calc() const;
};class VariableNode: public Node
{public:VariableNode(unsigned int id, Storage& storage): id_(id), storage_(storage) {}double Calc() const;bool IsLvalue() const;void Assign(double val);
private:const unsigned int id_;Storage& storage_;//变量节点的值从Storage类中获取,Storage类中存储了某个变量的值,所以需要Storage类的一个引用
};class AssignNode : public BinaryNode
{public:
//构造函数初始化,要调用基类部分的构造函数AssignNode(Node* left, Node* right): BinaryNode(left, right) {assert(left->IsLvalue());//断言是一个左值节点,能够被赋值的 }//要实现Calc方法double Calc() const;
};#endif/* _NODE_H */

P46\Node.cpp

#include "Node.h"
#include "Storage.h"
#include <cmath.h>
#include <cassert>
#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();
}MultipleNode::~MultipleNode()
{std::vector<Node*>::const_iterator it;for (it = children_.begin(); it != children_.end(); ++it){//it实际上是Node*的指针的指针,它存放的类型是Node*,*it取出它里面存放的元素Node*delete *it;}
}//把它的子节点计算一下,然后把它+起来
double SumNode::Calc() const
{double result = 0.0;std::vector<Node*>::const_iterator childIt = childs_.begin();std::vector<bool>::const_iterator positiveIt = positives_.begin();for (; childIt != childs_.end(); ++childIt, ++positive){//实际上childs_和positives_的元素个数是一样的,childs_没有遍历到结尾,那么positives_也没有遍历到结尾assert(positiveIt != positives_.begin();double val = (*childIt)->Calc();//*childIt的类型是Node*if (*positiveIt)result + =val;elseresult -= val;}//childs_遍历到结尾,那么positives_也遍历到结尾assert(positiveIt == positives_.end());return result;
}//把它的子节点计算一下,然后把它*起来
double ProductNode::Calc() const
{double result = 1.0;std::vector<Node*>::const_iterator childIt = childs_.begin();std::vector<bool>::const_iterator positiveIt = positives_.begin();for (; childIt != childs_.end(); ++childIt, ++positive){//实际上childs_和positives_的元素个数是一样的,childs_没有遍历到结尾,那么positives_也没有遍历到结尾assert(positiveIt != positives_.begin();double val = (*childIt)->Calc();//*childIt的类型是Node*if (*positiveIt)result * =val;else if (val != 0.0)result /= val;else{std::cout<<"Division by zero"<<std::endl;return HUGE_VAL;}}//childs_遍历到结尾,那么positives_也遍历到结尾assert(positiveIt == positives_.end());return result;
}double VariableNode::Calc() const
{double x = 0.0;if (storage_.IsInit(id_)){x = storage_.GetValue(id_);}else{std::cout<<"Use of uninitialized variable"<<std::endl;}return x;
}bool VariableNode::IsLvalue() const
{return true;
}void VariableNode::Assign(double val)
{storage_.SetValue(id_, val)
}double AssignNode::Calc() const
{//首先取出右节点的值double x = 0.0;x = right->Calc();left_->Assign(x);
}

P46\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;string GetSymbol() const;EToken Token() const;
private:void SkipWhite();const std:string buf_;unsigned int curPos_;EToken token_;//返回状态double number_;//返回数字std::string symbol_;
};
#endif/*_SCANNER_H_*/

P46\Scanner.cpp

#include "Scanner.h"
#include <cctype>Scanner::Scanner(const std::string& buf) : buf_(buf), curPos_(0)
{Accept();//一个字符一个字符的扫描
}double Scanner::Number() const
{return number_;
}string Scanner::GetSymbol() const
{return symbol_;
}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_ASSIGN;++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://字母或者下划线打头的才是标识符if (isalpha(buf_[curPos_]) || buf_[curPos_] == '_'){token_ = TOKEN_IDENTIFIER;symbol_.erase();char ch = buf_[curPos_];do{symbol_ += ch;++curPos_;ch = buf_[curPos_];} while (isalnum(ch) || ch == '_');}else{token_ = TOKEN_ERROR;}break;}
}

2.Calc类实现

  • 将SymbolTable类和Storage类正常应该放到Parser类中(直接放进去应该也行),现在组合到Calc类中,SymbolTable类,Storage类与Calc类的关系是组合关系。
    解析器Parser需要符号表,函数表,存储表,现在只支持符号表SymbolTable,存储表Storage

  • startUML,关系就不画了,线条太多了,麻烦

    Calc类中的FindSymbol,AddSymbol,GetStorage成员函数按照Parse类中解析的需要所写的接口,也就是说,先写Parse类中对标识符的解析处理,然后再写的这里的这仨接口
    P46\Calc.h

#ifndef _CALC_H_
#define _CALC_H_#include "SymbolTable.h"
#include "Storage.h"
#include <string>class Parser;class Calc
{//将Parser类声明为友元,可以方便Parser类方便访问Calc类中的私有成员friend class Parser;
public:Calc() : storage_(symTbl_) {}
private://因为这里不是引用,需要将其头文件include进来,而不能使用前向声明了//SymbolTable类,Storage类与Calc类的关系是组合关系,其生命周期由Calc类来管理//此外,SymbolTable对象要在Storage对象之前构造,SymbolTable对象构造完毕后传递给Storage对象SymbolTable symTbl_;Storage storage_;unsigned int FindSymbol(const std::string& str) const;unsigned int AddSymbol(const std::string& str);Storage GetStorage()//不写成const Storage GetStorage() const的原因是:能够去更改storage_{ return storage_; }
};#endif /* _CALC_H_ */

P46\Calc.cpp

#include "Calc.h"unsigned int Calc::FindSymbol(const std::string& str) const
{return symbol_.Find(str);
}unsigned int Calc::AddSymbol(const std::string& str)
{return symbol_.Add(str);
}

P46\Parser.h

#ifndef _PARSER_H
#define _PARSER_H//使用前向声明而不是包含Scanner的头文件的原因是,如果在cpp文件中多次包含了这样的头文件,使得生成的可执行文件增大
class Scanner;
class Node;//解决在 Node* Expr();中没有定义Node
class Calc;enum STATUS
{STATUS_OK;STATUS_ERROR;STATUS_QUIT;
};//Parser类:根据扫描结果,进行扫描,递归下降解析,直到生成一颗树
//Parser类与Scanner类之间的关系是什么?
//依赖关系:一个类作为另一个类成员函数的参数,或者内部的局部变量调用了某个类的静态成员函数
//而Scanner作为Parser类的成员,而且是引用: Scanner& scanner_;
//说明:Scanner类与Parser类具有一定的固定关系,在Parser类的生命周期内都要对scanner_的引用固定关系
//所以把他们看成关联关系
class Parser
{public:Parser(Scanner& scanner, Calc& calc);void Parse();Node* Expr();Node* Term();Node* Factor();double Calculate() const;
private:Scanner& scanner_;//这里是引用,即使Parser类销毁了,Scanner类也不不一定销毁,Parser类不负责Scanner类的生命周期//若这里不是引用,Parser对象销毁,Scanner对象也跟着销毁,这就是组合关系了//这里也可以用引用,但是会拷贝一个Scanner类对象拷贝到Parser类内部,组合的方式效率低一些,没必要用了Node* tree_;STATUS status_;Calc& calc_;
};#endif /* _PARSER_H */

P46\Parser.cpp

#include "Parser.h"
#include "Scanner.h"//因为会使用到Scanner的一些接口进行扫描
#include "Node.h"
#include "Calc.h"#include <cassert>
#include <iostream>//引用的初始化只能才初始化列表中进行初始化
Parser::Parser(Scanner& scanner, Calc& calc) : scanner_(scanner), tree_(0), calc_(calc)
{}//解析表达式:
Node* Parser::Expr()
{Node* node = Term();EToken token = scanner_.Token();// if (token == TOKEN_PLUS)//扫描到+// {//     scanner_.Accept();//accept+号,扫描下一个字符,看看是不是一个Expression//     Node* nodeRight = Expr();//     node = new AddNode(node, nodeRight);//返回的是加法节点,(左节点,右节点),Expression is Term + Expression// }// else if (token == TOKEN_MINUS)// {//     scanner_.Accept();//     Node* nodeRight = Expr();//     node = new SubNode(node, nodeRight);//Expression is Term - Expression// }if (token == TOKEN_PLUS || token == TOKEN_MINUS){//此时的表达式,Expr := Term{ ( '+'| '-' ) Term}MultipleNode* multipleNode = New SumNode(node);//子节点有多个,所以用循环do{scanner_.Accept();Node* nextNode = Term();multipleNode->AppendChild(nextNode, (token == TOKEN_PLUS));token = scanner_.Token();//更新当前扫描的状态,即:看下一个字符是加法还是减法,直到遇到其他负号,就不是SumNode的子代了}while(token == TOKEN_PLUS || token == TOKEN_MINUS)node = multipleNode;}return node;//Expression is Term
}//解析项
Node* Parser::Term()
{Node* node = Factor();EToken token = scanner_.Token();// if (token == TOKEN_MULTIPLY)// {//     scanner_.Accept();//     Node* nodeRight = Term();//     node = new MultiplyNode(node, nodeRight);//Term is Factory * Term// }// else if (token == TOKEN_DIVIDE)// {//     scanner_.Accept();//     Node* nodeRight = Term();//     node = new DivideNode(node, nodeRight);//Term is Factory / Term// }if (token == TOKEN_MULTIPLY || token == TOKEN_DIVIDE){//此时的表达式,Expr := Factor{ ( '*'| '/' ) Factor}MultipleNode* multipleNode = New SumNode(node);//子节点有多个,所以用循环do{scanner_.Accept();Node* nextNode = Factor();multipleNode->AppendChild(nextNode, (token == TOKEN_MULTIPLY));token = scanner_.Token();//更新当前扫描的状态,即:看下一个字符是加法还是减法,直到遇到其他负号,就不是SumNode的子代了}while(token == TOKEN_MULTIPLY || token == TOKEN_DIVIDE)node = multipleNode;}else if (token == TOKEN_ASSIGN) //项还能够解析等号,比如x=5,x是项,5是表达式;x=y=5,x是项,y=5是表达式{//Expr:=Term=Expr, :=这是一种BNF式的表达法scanner_.Accept();Node* nodeRight = Expr();if (node->IsLvalue()){node = new AssignNode(node, nodeRight);//这里是对创建的变量节点,进行的赋值操作}else{status_ = STATUS_ERROR;std::cout<<"The left-hand side of an assignment must be a variable"<<std::endl;//必须是左值节点//Todo 抛出异常}}return node;//Expression is Factory
}//解析因式
Node* Parser::Factor()
{//or (Expression)Node* node = 0;EToken token = scanner_.Token();if (token == TOKEN_LPARENTHESIS) {scanner_.Accept(); //accept '('node = Expr();//先解析表达式,右边应该有个右括号if (scanner_ == TOKEN_RPARENTHESIS){scanner_.Accept(); //accept ')'}else{status = STATUS_ERROR;//to do:抛出异常std::cout<<"missing parenthesis"<<std::endl;node = 0;}else if (token == STATUS_NUMBER){node = new NumberNode(scanner_.Number());//新建一个数字节点scanner_.Accept();}else if(token == STATUS_MINUS){scanner_.Accept();//接受一个负号,目的是指针偏移到下一个字符,让下一次的解析做准备node = new UminusNode(Factor());//传递一个子节点进去,这个子节点就是因式}else if (token == TOKEN_IDENTIFIER)//解析到一个标识符{std::string symbol = scanner_.GetSymbol();unsigned int id = calc_.FindSymbol(symbol);scanner_.Accept();if (id == SymbolTable::IDNOTFOUND){id = calc_.AddSymbol(symbol);}node = new VariableNode(id, calc_.GetStorage());//创建一个变量节点}else{   status = STATUS_ERROR;//to do:抛出异常std::cout<<"Not a valid expression"<<std::endl;node = 0;}return node;}
}void Parser::Parse()
{tree_ = Expr();//解析完后,将整个表达式赋值给tree_
}//注意:带const的成员函数与不带const的成员函数可以构成重载
double Parser::Calculate() const
{assert(tree_ != 0);//0,NULL都行//求表达式的值,实际上就是求其根节点的值return tree_->Calc();
}

P46\main.cpp

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

没变化的有:
P46\SymbolTable.h

#ifndef _SYMBOL_TABLE_H_
#define _SYMBOL_TABLE_H_
#include <map>
#include <string>class SymbolTable
{//直接放在public上面是私有的
public://枚举量enum { INNOTFOUND = 0xffffffff }SymbolTable() : curId_(0) {}unsigned int Add(const std::string& str);unsigned int Find(const std::string& str) const;void Clear();std::string GetSymbolName(unsigned int id) const;private:std::map<std::string, unsigned int> dictionary_;unsigned int curId_;
};#endif /* _SYMBOL_TABLE_H_ */

P46\SymbolTable.cpp

#include "SymbolTable.h"unsigned int SymbolTable::Add(const std::string& str)
{dictionary_[str] = curId_;return curId_++;//返回值是当前符号的id,然后再++
}unsigned int SymbolTable::Find(const std::string& str) const
{map<const std::string, unsigned int>::const_iterator it;it = dictionary_.find(str);if (it != dictionary_.end() )return it->second;return INNOTFOUND;
}//不等于end,说明找到了
void SymbolTable::Clear()
{dictionary_.clear();curId_ = 0;
}//函数对象function object,反函数functor
//目的:让一个类对象使用起来像一个函数,本质上是重载括号运算符
/*
IsEqualId ie;
is();此时ie使用起来像一个函数
*/
//这是STL6大组件
//迭代器pair有2个要素:fist是string,second是unsigned int
class IsEqualId
{public:IsEqualId(unsigned int id) : id_(id) {}bool operator(const std::pair<std::string,unsigned int>& it) const{return it.second == id_;}
private:unsigned int id_;
};std::string SymbolTable::GetSymbolName(unsigned int id) const
{//find_if(XX,XX,类)//将类传递进去,将类看成是一个函数map<const std::string, unsigned>::const_iterator it;it = find_if(dictionary_begin(), dictionary_end(), IsEqualId(id));return it->first;
}

P46\Storage.h

#ifndef _STORAGE_H
#define _STORAGE_H#include <vector>
class SymbolTable;//这里是前向声明,需要在cpp文件中包含#include "SymbolTable.h"class Storage
{public:Storage(SymbolTable& tbl);void Clear();bool IsInit(unsigned int id) const;void AddConstant(SymbolTable& tbl);double GetValue(unsigned int id) const;void SetValue(unsigned int id, double val);void AddValue(unsigned int id, double val);
private:std::vector<double> cells_;std::vector<bool> inits_;
};#endif /* _STORAGE_H */

P46\Storage.cpp

#include "Storage.h"
#include "SymbolTable.h"
#include <cmath>
#include <cassert>Storage::Storage(SymbolTable& tbl)
{//先添加常量到符号表中AddConstant(tbl);
}void Storage::Clear()
{cells_.clear();inits_.clear();
}bool Storage::IsInit(unsigned int id) const
{//id不能超过cells_元素中的最大值//下标就是id,第0变量是否初始化了,那么就是inits_[0]return id < cells.size() && inits_[id];
}//添加2个常量
void Storage::AddConstant(SymbolTable& tbl)
{unsigned int id = tbl.Add("e");AddValue(id, exp(1.0));//exp(1.0):代表e的1一次方id = tbl.Add("pi");AddValue(id, 2.0*acos(0.0));//pi=2*acos(0),acos是反余弦函数
}double Storage::GetValue(unsigned int id) const
{assert(id < cells_.size());return cells_[id];
}void Storage::SetValue(unsigned int id, double val)
{assert(id <= cells_.size());if (id < cells_.size())//id已经存在重新赋值{cells_[id] = val;inits_[id] = true;}else if (id == cells_.size())//id超过size,也就是说id所对应的变量不存在{AddValue(id, value);}}void Storage::AddValue(unsigned int id, double val)
{//为啥不使用push_back?/*比如cells_空间里面有6个元素了,此时cells_.push_back(val);此时向量里面的空间并不等于7,有可能是12,会导致size为12,实际上并没存放12个变量或常量,我们希望向量cells_存放多少变量或常量,其空间就为多少,即size就为多少,所以不用*///先扩充一个容量cells_.resize(id+1);inits_.resize(id+1);cells_[id] = value;inits_[id] = true;
}
  • 测试:

(P46)面向对象版表达式计算器:让表达式计算器支持变量赋值 ,Calc类实现相关推荐

  1. 七、使用栈实现综合计算器(中缀表达式)

    使用栈实现综合计算器(中缀表达式) 1.栈的实际需求 请输入一个表达式,计算式:[722-5+1-5+3-3] ,计算出结果 计算机底层是如何运算得到结果的? 注意不是简单的把算式列出运算,因为我们看 ...

  2. 字符串算术表达式求值-简单计算器实现(栈)-数据结构和算法(Java)

    1 字符串算术表达式分类 字符串算术表达式分为前缀表达式.中缀表达式和后缀表达式.其中前缀表达式又称波兰表达式,后缀表达式基于前缀表达式,又称逆波兰表达式.下面给出百度百科关于几种表达式的定义: 前缀 ...

  3. C语言 科学计算器 后缀表达式 解析字符串 仿JS的eval函数

    C语言 利用后缀表达式解析字符串 最近用98标准的C语言写了个解析字符串,类似于JavaScript中的eval函数,感觉挺实用(移植到了计算器上,可以画F(X,Y)==0这种图像了),特此分享一下, ...

  4. C语言利用前缀表达式实现复杂科学计算器

    用C语言实现的科学计算器,支持2种常量,10种基本函数,Ans寄存器.相对来说拓展性应该是不错的,思路是首先化简复杂名称的函数名和常量名,然后把表达式转换成前缀表达式,再直接处理前缀表达式即可.因此对 ...

  5. 数据结构 - 栈 (逆波兰计算器)(栈的三种表达式)(前缀、中缀和后缀表达式,后缀也叫逆波兰表达式)(中缀表达式转后缀表达式实现步骤及完整代码)

    栈的三种表达式:前缀.中缀和后缀表达式,后缀也叫逆波兰表达式 前缀(波兰表达式) 中缀(对人来讲很好理解,对于计算机来讲就方便了,一般会把中缀表达式转换成后缀表达式) 后缀(逆波兰表达式) 计算过程 ...

  6. [.net 面向对象程序设计进阶] (7) Lamda表达式(三) 表达式树高级应用

    [.net 面向对象程序设计进阶] (7) Lamda表达式(三) 表达式树高级应用 本节导读:讨论了表达式树的定义和解析之后,我们知道了表达式树就是并非可执行代码,而是将表达式对象化后的数据结构.是 ...

  7. “中序表达式”转换为“前序表达式”、“后序表达式”

    上周末参照书本写了个"计算器"的程序,其中最令我费解的就是"前序表达式"."后续表达式",好像记得老师在上课的时候讲过,估计当时也没听懂,看 ...

  8. python数字计算器_Python作为计算器使用(一)——数字

    [摘要]Python作为一种面向对象的动态类型语言,其实用性多种多样,python作为计算器使用就是其中的一种,在很多编程系统中,作为计算器使用都是基础,那么在其中,数字的使用就显得尤为重要,那么今天 ...

  9. 前缀、中缀和后缀表达式详解,中缀表达式到后缀表达式的转换规则,以及后缀表达式的计算规则,附计算代码

    1. 中缀.前缀和后缀表达式 1.1 中缀表达式 首先,中缀表达式的这个"缀"指运算符在两个操作数的位置.中缀表达式其实就是我们常用的算术表达式,比如 2 + 9 - (32 * ...

  10. php混合运算计算器,混合运算计算器

    用计算器怎样计算加减乘除混合运算 第一点,有一个简单的计算器,显示屏幕比较大,而且按键比较大,比较简单,可以非常快的使用乘除混合运算. 第二点,可以将如下的一道题进行一次性运算:"(101* ...

最新文章

  1. CSS3动画过渡的jquery动态弹出框插件
  2. Android 自定义View的使用纪要!!!
  3. 网页设计中的默认字体样式详解
  4. 怎么给网站加js_网站站内SEO优化实操细节详解,权重上升嗖嗖的
  5. hdu5387(模拟)
  6. centos7.8中源码编译安装redis
  7. 数据结构与算法专题——第一题 Bitmap算法
  8. ajax同步获得数据字典的值,使用ajax加载数据字典,生成select(示例代码)
  9. colab显示没有gpu的解决方法
  10. Hadoop源码分析28 JobTracker 处理JobClient请求
  11. 精选| 2019年7月R新包推荐(第32期)
  12. Proj.4库的编译及使用
  13. ubuntu16 下安装freeswitch 1.8.3
  14. 关于wineQQ8.9.19983deepin23版本提升不能在使用请升级,Linux偷懒升级方法
  15. 软件测试-微信红包测试点
  16. 毛星云opencv第二章总结
  17. Python为什么叫爬虫?Python与爬虫有什么关系?
  18. android dm 分区异常,硬盘分区时损坏,无法使用PQ和DM,如何修复
  19. 厦门大学计算机研究生2020专业目录,报录比|厦门大学各院系专业2020年硕士生报考录取数据统计表...
  20. c语言中 静态变量的默认值为,C中静态变量的默认值

热门文章

  1. Java阿拉伯数字转换为中文大写
  2. 基于物联网的无线温度系统在钢铁行业的应用
  3. android 自动安装 解析包错误,安卓android手机安装包频繁提示解析错误解决方法...
  4. 计算机专业29岁研究生毕业,26岁,你还会考研吗?毕业后都奔三了!_计算机考研科目...
  5. 自动化接口实战(一)
  6. 智能化的Conversational UI是移动发展的一个趋势
  7. BugKu CTF(杂项篇MISC)---哥哥的秘密
  8. ​以数据科学家的眼光投资,你可能会一夜暴富
  9. 我的人生观、爱情观和世界观
  10. 非负数的正则表达式匹配