【问题标题】:Adding the ! operator and sqrt(), pow(), etc. to a calculator example application添加!运算符和 sqrt()、pow() 等到计算器示例应用程序
【发布时间】:2009-05-16 00:28:55
【问题描述】:

我正在做 Stroustrup 的新书 "Programming Principles and Practice Using C++" 中的练习,想知道 StackOverflow 上是否有人做过这些练习并愿意分享知识?

具体说一下第6章和第7章开发的计算器。比如添加!操作符和sqrt()、pow()等问题。这些我都做过,不知道有没有我的解决方案是“好”的做事方式,Bjarne 的网站上没有发布的解决方案。我想知道我是否走在正确的轨道上。也许我们可以为练习制作一个 wiki?

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

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");
}

【问题讨论】:

  • 只需在此处发布您的代码,很多人会很乐意告诉您可以改进的地方。这个站点是您需要的所有 wiki。 :)
  • 没看过这个练习,但看起来很奇怪的是,你试图将遇到的每个数字和小数点都加倍。
  • 如果输入读取 'sin(2)',那么您的代码会将 'i'、'n' 和 '(' 读取到 q、r、t 中;然后它决定这些是不是统称为“q”、“r”和“t”,所以......它放弃了一切。我相信你需要更加努力地看待错误恢复。
  • 我相信 Bjarne Stroustrup 本人已经回答了您的问题。
  • @MichaelMyers:我认为他根本没有回答这个问题。

标签: c++


【解决方案1】:
  1. Stroustrup - Programming 上发布了一些解决方案,随着时间的推移还会有更多解决方案。

  2. 尝试仅使用本书迄今为止介绍的语言功能和图书馆设施来解决练习 - 真正的新手用户不能做任何其他事情。稍后再回来看看如何改进解决方案。

【讨论】:

  • 是的,我对地图有点作弊……直到后面的章节才会出现。顺便说一句,我真的很喜欢这本书。我认为计算器实际上可能在现实世界中很有用,这不是大多数介绍性书籍中提供的示例所能说的。
  • 你是真的吗? :P
  • @Swift:可能不会。太多简单的语法错误和其他错误(我认为真正的 Bjarne 不会那么粗心)。无论如何,这个未注册的帐户在过去 10 年中没有任何活动,并且登录 cookie 可能已经丢失。
  • 等等,用户 iammilind 引入了语法错误(或者更确切地说,改变了含义)。
【解决方案2】:

我认为字符串到函数指针的映射可能是一种简洁的方式来表示诸如 sqrt、sin、cos 等接受单个双精度并返回双精度的事物:

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

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

double result = funcs[str](arg);

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

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

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

【讨论】:

  • 可能,但 Bjarne Stroustrup 回答了您的问题。鞠躬。
【解决方案3】:

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

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();

如果将!+-等放入func,则可以使用1去掉开关; (为简单起见,它们应该在 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();
 }

所以基本上是某种函数指针,但没有杂乱的语法,并且您可以在其中存储计算之间的数据。

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

【讨论】:

  • 不错。我知道那里会有一个 OOP 解决方案!但是... cin>>s 不起作用,因为表达式不能包含空格,即 5+sqrt(144)/2。所以我认为仍然需要一次读取一个字符来检查像 sqrt 这样的函数名称。
  • 我觉得应该是peek,而不是peak!想知道Stroustrup为什么不用这个方法,好像比get()和putback()更合乎逻辑。
  • 我没有使用 peek() 以避免这么早引入另一个新工具,而且(更重要的是)因为“带有 putback() 的流”是一个非常普遍的想法,可以在许多地方使用 - peek() 涉及其他人为您做一些缓冲。另外,想想空格和错误处理——如果没有下一个字符可以看怎么办?我认为 peek() 更像是一种“聪明的技巧”,而不是一种通用技术。
  • 好点。这就说得通了。顺便说一句,感谢您抽出宝贵时间查看此线程,我很荣幸有作者本人提供意见!
【解决方案4】:

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

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

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

【讨论】:

  • 好的,我会尝试实现这个。这会很好地分离出函数的东西。
【解决方案5】:

我现在已经完成了这项任务,所以我是这样做的。 (下面大部分代码都是由 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");
    }
}

一般来说,这个代码块负责定义变量,但是我们通过输入一系列字符s,q,r,t来创建sqrt“关键字”。 如果有那个关键字,我们会返回一个代表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
}

如您所见,如果遇到root keychar,我们需要定义一个d变量作为函数expression()值。计算时expression()会遇到(字符,可以输入任意序列作为用户sqrt()函数的参数,得到合理的答案。

一些例子:

> let a = 5;
= 5
> sqrt(100);
= 10
> sqrt(a+20-a/2);
= 4.74342
> 1+2/3+sqrt(9);
= 4.66667
> sqrt(a/0);
divide by zero
> sqrt(-1);
sqrt(): non-positive argument

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-31
    相关资源
    最近更新 更多