| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 3245 人关注过本帖, 1 人收藏
标题:[原创]一个支持四则运算的类
取消只看楼主 加入收藏
StarWing83
Rank: 8Rank: 8
来 自:仙女座大星云
等 级:贵宾
威 望:19
帖 子:3951
专家分:748
注 册:2007-11-16
结帖率:90%
收藏(1)
 问题点数:0 回复次数:6 
[原创]一个支持四则运算的类
啊……本来想用LR文法的,但是在状态量增加到三十多个以后,只好十分沮丧地放弃了………………LR文法版本的还在想办法。先发个随便写写的版本吧……实现的功能很简单,而且自定义运算符的功能还没有测试……所以大家帮帮忙来找Bug吧………………注释是后来补的Orz……反正写得很详细啦,大家可以来看看……

使用返回值而不是异常返回错误,改掉了几个Bug,明天来分离一目操作符的逻辑,今天就这样吧……
看样子,甚至可以把Sin,Cos这样的函数都加进去呢……

#include <iostream>
#include <string>
#include <stack>
#include <sstream>
using namespace std;

//状态枚举,由主函数及StackCalc返回
enum Status
{
   
sOK                =0,//正常返回
   
eDivByZero        =1,//被零除
   
eNumError        =2,//操作数错误
   
eOptError        =3,//运算符错误
   
eInvalidToken    =4,//非法字符
};

//运算符信息“配件”,用来规定所支持的运算符的数目,优先级,以及计算方法。
//所有“配件”都必须包含以下两个函数
struct CCalcInfo
{
   
//取得运算符优先级,如不是运算符,返回0,数字越大优先级越低
   
static int GetOpt(char op)
    {
        
switch (op)
        {
        
case '*':
        case '/':return 1;
        case '+':
        case '-':return 2;
        }
        
return 0;
    }

   
//根据堆栈栈顶的操作数及运算符计算
   
static Status StackCalc(stack<char>& os,stack<double>& ds)
    {
        
if (os.empty())return eOptError;
        if (ds.size() < 2)return eNumError;

        double rv=ds.top();ds.pop();
        switch (os.top())
        {
        
case '+':ds.top()+=rv;break;
        case '-':ds.top()-=rv;break;
        case '*':ds.top()*=rv;break;
        case '/':if (rv==0)return eDivByZero;ds.top()/=rv;break;
        default:return eOptError;
        }
        
os.pop();
        return sOK;
    }

}
;

//运算类
template<class TInfo>//TInfo定义了操作符和计算方法
struct CCalculateT : TInfo
{
   
using TInfo::GetOpt;
    using TInfo::StackCalc;

    //为方便使用,定义的运算符重载
   
void operator()(const char* str)
    {
        
double ans;

        switch (DoCalc(str,ans))
        {
        
case sOK:cout<<ans;break;
        case eDivByZero:cout<<"被零除";break;
        case eNumError:cout<<"操作数错误";break;
        case eOptError:cout<<"运算符错误";break;
        case eInvalidToken:cout<<"非法字符";break;
        }
        
cout<<endl;
    }

   
//运算主程序
   
static Status DoCalc(const char* str,double& ans)
    {
        
stack<double> ds;//操作数栈
        
stack<char> os;//符号栈
        
istringstream iss(str);//输入流

        
bool bNeedNum=true;//当前是否需要数字(比如操作符和括号后)
        
for (char ch;iss>>ch;)
        {
            
if (isdigit(ch) || ch=='.')//如果为数字直接入栈
            
{
               
double fv;
                iss.unget();iss>>fv;
                ds.push(fv);//数字入栈
                //计算取负操作符
               
while (!os.empty() && os.top() == '!')
                    ds.top()=-ds.top(),os.pop();
                bNeedNum=false;//不可连续出现两个数字
            
}
            
else if (ch == '(')
            {
               
if (bNeedNum == false)return eOptError;//这时应该是期待操作数的
               
os.push(ch);//括号入栈
               
bNeedNum=true;//括号后允许出现数字
            
}
            
else if (ch == ')')
            {
               
if (bNeedNum == true)return eOptError;//这时应该不期待操作数
                //遇到反括号,持续出栈直到遇到相应括号
               
while (!os.empty() && os.top()!='(')
                {
                    
Status s=StackCalc(os,ds);
                    if (s!=sOK)return s;
                }
               
if (os.empty())return eOptError;//如果找不到相应括号,出错
               
os.pop();//括号出栈,即消掉了一对括号
                //如果有取负操作符,计算
               
while (!os.empty() && os.top() == '!')
                    ds.top()=-ds.top(),os.pop();
                bNeedNum=false;//反括号后不允许出现数字
            
}
            
else if (GetOpt(ch))//如果是操作符
            
{
               
//如果不需要出现数字,并且操作符栈非空
                //并且栈顶元素不是括号,并且栈顶元素的优先级高于当前运算符
               
if (!bNeedNum && !os.empty() && GetOpt(os.top())
                        && GetOpt(os.top())<=GetOpt(ch))
                {
                    
//计算前一计算
                    
Status s=StackCalc(os,ds);
                    if (s!=sOK)return s;
                }
               
else if (bNeedNum)//如果需要数字,可以认为这时出现的是正负号
               
{
                    
if (ch == '-')//如果为负号
                        
ch='!';//栈内数字取反,s是自定义的取反操作符
                    
else if (ch == '+')//如果是正号
                        
continue;//无视掉
                    
else return eOptError;//其他字符,出错。
               
}
               
bNeedNum=true;//操作符后期待操作数
               
os.push(ch);//压入操作符
            
}
            
else if (!isspace(ch))//非空白字符,出错
               
return eInvalidToken;
        }
        
while (!os.empty())
        {
            
//计算栈内存留数字,直到操作符栈为空
            
Status s=StackCalc(os,ds);
            if (s != sOK)return s;
        }
        
if (ds.size()!=1) return eNumError;//如果操作数栈内还存有数字,出错。
        
ans = ds.top();//返回计算结果
        
return sOK;
    }
}
;


int main(void)
{
   
char str[1001];
    typedef CCalculateT<CCalcInfo> CCalc;//使用四则运算符的运算类
   
while (putchar('>'),gets(str))CCalc()(str);
    return 0;
}


[[it] 本帖最后由 StarWing83 于 2008-6-14 00:51 编辑 [/it]]
搜索更多相关主题的帖子: 四则运算 算符优先 源码 
2008-05-09 02:53
StarWing83
Rank: 8Rank: 8
来 自:仙女座大星云
等 级:贵宾
威 望:19
帖 子:3951
专家分:748
注 册:2007-11-16
收藏
得分:0 
异常有个好处,第一个是和返回值完全分开。所以就算是throw 0 都绝不会意义模糊:因为只有你扔异常嘛。然后可以在一个调用树里面随便扔,这样就不需要一层层的if(...)return ....了,最后,异常作为内部的消息传送使用(就是这种啦),再有上面的好处的同时,因为对外封闭,也不太容易看出来。当然,上面的程序不是很好,异常捕获应该在类的内部被封装,而不应该暴露在外面。这个我等会儿改改……
其实异常效率并不算很低。异常的实现方式是注册一个中断向量,然后在throw的时候触发中断。而且如果只是在错误的时候才异常的话,是不会影响到通常工作的效率的~~~

专心编程………
飞燕算法初级群:3996098
我的Blog
2008-05-09 16:11
StarWing83
Rank: 8Rank: 8
来 自:仙女座大星云
等 级:贵宾
威 望:19
帖 子:3951
专家分:748
注 册:2007-11-16
收藏
得分:0 
不,中学者我知道你的意思。你的意见是对的,直接扔int的确含义模糊。我正在改。我的想法是只是在类内部扔异常。这个异常的过程对使用者来说是透明的,他只需要cout<<CCalc("12+(12+4)");。至于是错误还是结果交给类来处理了……

飞燕是什么意思呢?不是很明白……扔异常本身会造成内存泄露吗?如果是的话我就改成返回值好了……因为设计了接口,如果要返回值的话会很麻烦……

专心编程………
飞燕算法初级群:3996098
我的Blog
2008-05-09 17:26
StarWing83
Rank: 8Rank: 8
来 自:仙女座大星云
等 级:贵宾
威 望:19
帖 子:3951
专家分:748
注 册:2007-11-16
收藏
得分:0 
是啊,不太明白,我已经没有改了,中学者先说说,估计会取消掉异常机制了……哎,那样的话接口机制也不能用了——因为要跨三层函数返回错误……

专心编程………
飞燕算法初级群:3996098
我的Blog
2008-05-09 17:43
StarWing83
Rank: 8Rank: 8
来 自:仙女座大星云
等 级:贵宾
威 望:19
帖 子:3951
专家分:748
注 册:2007-11-16
收藏
得分:0 
LS:的确不是纯C,但是仅仅使用了C++的少量内容。

语言不重要,关键是算法,而且这个是我随便写的……不用太XX吧…………

26#说的对,但是学习编译原理课嘛,要是都用工具岂不是没意义了……这个只不过是学习中的一点点研究而已。而且你的“二三十行”应该是指yacc的范式而不是源代码吧……

那个,这个帖子其实已经很久了,留下来纯粹当作存档而已……

专心编程………
飞燕算法初级群:3996098
我的Blog
2008-07-24 15:23
StarWing83
Rank: 8Rank: 8
来 自:仙女座大星云
等 级:贵宾
威 望:19
帖 子:3951
专家分:748
注 册:2007-11-16
收藏
得分:0 
回复 29# 蓝色神话 的帖子
我是在学习自己的东西,到底有没有学到我自己知道。您说的我同意,不过虽然学到的少,但仍然有启发。
Yacc是肯定要学习的,但现在并没有强烈的愿望去学,我的基础还有很大问题,准备好好补一补再去学习实用的编程。
的确,国内的教程偏向于理论。但是您知不知道?编译原理的主要任务不是研究CPU指令的组成,而是致力于一种形式语言到另外一种形式语言的转换。如果你对编译原理不满,你可以看嵌入式的教材,里面的关于CPU后端的内容一定会让你满意,但是请不要强求一门学科去研究本不属于他学科范畴的东西。

关于热兵器冷兵器我不想争论,因为没有意义。我只能说,我做的可能(事实上是的确)没有实际意义,但至少我能学习到我想要学习的东西。

还有,这个是老帖子了……

专心编程………
飞燕算法初级群:3996098
我的Blog
2008-07-25 10:07
StarWing83
Rank: 8Rank: 8
来 自:仙女座大星云
等 级:贵宾
威 望:19
帖 子:3951
专家分:748
注 册:2007-11-16
收藏
得分:0 
您说的对。的确是这样的。
以前写编译器,就是因为没有适当的优化手段而放弃的,后来才知道,使用GCC的前端,自己写一个后端就够了,毕竟我是做实际的开发。
当然,学习是一回事儿,实用是另外一回事儿。不过呢,总有些相似的地方。
也不用鄙视中国的教育,事实就是这样,要么逃脱,要么适应,fm26号的飞机去美国,不知道我以后有没有机会。
不管怎么样,我先学好自己的东西吧。
还是谢谢您。

专心编程………
飞燕算法初级群:3996098
我的Blog
2008-07-25 11:14
快速回复:[原创]一个支持四则运算的类
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.025751 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved