我正在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");
}
答案 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");
}
}
一般来说,这段代码负责定义变量,但我们通过输入字符序列sqrt
、s
、{{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()