添加!运算符和sqrt(),pow()等到计算器示例应用程序

时间:2009-05-16 00:28:56

标签: c++

我正在Stroustrup的新书"Programming Principles and Practice Using C++"中进行练习,并且想知道SO上是否有人做过这些练习并愿意分享这些知识?特别关于在第6章和第7章中开发的计算器。例如关于添加的计算器!运营商和sqrt(),pow()等我已经完成了这些,但我不知道我所拥有的解决方案是否是“好”的做事方式,并且Bjarne的网站上没有已发布的解决方案。我想知道我是否走上正轨。也许我们可以为练习制作一个维基?

基本上我有一个令牌解析器。它从cin一次读取一个字符。它意味着标记像5 * 3 + 1这样的表达式,它的效果非常好。其中一个练习是添加一个sqrt()函数。所以我修改了令牌化代码以检测“sqrt(”然后返回表示sqrt的Token对象。在这种情况下,我使用char的'。这是其他人会怎么做的?如果我需要实现sin()怎么办?案件陈述会变得混乱。

char ch;
cin >> ch;    // note that >> skips whitespace (space, newline, tab, etc.)

switch (ch) {
    case ';':    // for "print"
    case 'q':    // for "quit"
    case '(': 
    case ')': 
    case '+': 
    case '-': 
    case '*': 
    case '/': 
    case '!':
        return Token(ch);        // let each character represent itself
    case '.':
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
        {    
            cin.putback(ch);         // put digit back into the input stream
            double val;
            cin >> val;              // read a floating-point number
            return Token('8',val);   // let '8' represent "a number"
        }
    case 's': 
        {
            char q, r, t, br;
            cin >> q >> r >> t >> br;
            if (q == 'q' && r == 'r' && t == 't' && br == '(') {
                cin.putback('(');   // put back the bracket
                return Token('s');  // let 's' represent sqrt
            }

        }

    default:
        error("Bad token");
}

5 个答案:

答案 0 :(得分:211)

  • Stroustrup - Programming上发布的解决方案很少,而且会有更多的解决方案。

  • 尝试使用目前为止在书中提供的语言功能和库设施来解决练习 - 真正的新手用户不能做任何其他事情。然后返回以查看如何改进解决方案。

答案 1 :(得分:10)

我认为函数指针的字符串映射可能是一种简洁的方式来表示像sqrt,sin,cos等的东西,只需要一个double并返回一个double:

map<std::string, double (*)(double)> funcs;
funcs["sqrt"] = &sqrt;
funcs["sin"] = &sin;
funcs["cos"] = &cos;

然后当解析器检测到正确的字符串(str)时,它可以使用参数(arg)调用函数,如下所示:

double result = funcs[str](arg);

使用此方法,单个调用可以处理所有函数(该类型的函数)。

实际上我不确定这是否是正确的语法,任何人都可以确认吗?

这看起来像是一种有用的方法吗?

答案 2 :(得分:4)

使用派生类和虚函数更容易:每个专门的类读取自己的输入......

class base{public:
virtual double calc()=0;
};
class get_sqrt:public base{
int useless;
public:
virtual double calc(){cin>>number;return sqrt(number);}
}get_sqrt;

现在我们在地图中组织这些,我们只会使用他们的指针:

map<string,base*> func;
func["sqrt"]=&get_sqrt;

还有一种专门的方法只会查看下一个字符:peek();

char c=cin.peek();

你可以通过使用1来放弃开关,如果放! + - ...在func; (为简单起见,它们应该在left_param上运行

 if (c>='0'&&c<='9') cin>>right_param; //get a number, you don't have to put the character back as it hasn't been removed
 else{string s; cin>>s;right_param=func[s]->calc();}

所以基本上是某种函数指针,但没有凌乱的sintax,而且你可以在计算之间存储数据。

编辑:我想到了空白问题;它可以在开始计算之前添加,我也认为可以设置不同的分隔符,比如数字,但我不知道如何。

答案 3 :(得分:0)

我会将'sqrt'检测移动到另一种功能检测方法中。基本上,我会删除's'检测并在默认情况下添加一些内容,将字符串读取为'('。

如果没有检测到'(',那么错误。

如果您成功读取了一个字符串,请将其传递给使用字符串比较的函数名称解析器,以生成表示对sqrt或sin或您喜欢的任何函数的调用的标记。检查函数名称的方法如果读取了无法识别的字符串,也会出错。

答案 4 :(得分:0)

我现在已经完成了这项任务,所以我是这样做的。 (以下大部分代码最初由 Stroustrup 编写,只有我对有效 sqrt() 计算的看法)

首先,需要分配一个符号常量,表示有一个 sqrt() 调用。 const char root = 's';

现在让我们对您的代码进行一些更改:

switch (ch) {
// here were your case: blocks of code
default:
        if (isalpha(ch)) { 
            string s;
            s += ch;
            while (cin.get(ch) && (isalpha(ch) || isdigit(ch) || ch == '_')) s += ch;
            cin.putback(ch);

            if (s == "sqrt") return Token(root); // sqrt() define
            return Token(var_name, s); 
        }
        error("Bad token");
    }
}

一般来说,这段代码负责定义变量,但我们通过输入字符序列sqrts、{{1}来创建q“关键字” },r。 如果存在该关键字,我们将返回一个表示 t 的标记。

现在,我们只需要在 sqrt 函数中对语法进行一些更改。

primary()

如您所见,如果遇到 double primary() { Token t = ts.get(); switch (t.kind) { case root: case '(': { double d = expression(); if (t.kind == root) { if (d < 0) error("sqrt(): non-positive argument"); return sqrt(d); // calculate square root otherwise } t = ts.get(); if (t.kind != ')') error("')' expected"); return d; } // further case blocks } keychar,我们需要将 root 变量定义为函数 d 值。在计算时,expression() 会遇到 expression() 字符,可以输入任何序列作为用户 ( 函数的参数并得到合理的答案。

一些例子:

sqrt()