几天前我见过的小而且非常讨厌的问题,在接受采访时问我的朋友。
最初的面试问题是:“以下代码的输出是什么?”
int i = 2;
i = i++ + i++;
正确的答案是((2 + 2)+ 1)+ 1 = 6,即在分配之前应用后增量两次,但是在添加之后。
然后我想创建一个带有一个整数和重载运算符+()和operator ++(int)的简单类,以便在日志中查看执行运算符的确切顺序。
这就是我得到的:
class A
{
public:
A(int _data) : data(_data) { }
A &operator=(const A& _rhs)
{
data = _rhs.data;
cout<<" -- assign: "<<data<<endl;
}
A operator++(int _unused)
{
A _tmp = data;
data++;
cout<<" -- post-increment: "<<data<<endl;
return _tmp;
}
A operator+(const A &_rhs)
{
A _tmp = data + _rhs.data;
cout<<" -- addition: "<<data<<"+"<<_rhs.data<<endl;
return _tmp;
}
inline operator int() const { return data; }
private:
int data;
};
结果非常令人沮丧:
-- post-increment: 3
-- post-increment: 4
-- addition: 3+2
-- assign: 5
对于不太复杂的构造,例如(A _dt2 = a ++;),它应该按原样运行,但是运算符执行的顺序不是整数类型。
这可能是编译器特定的问题,我想:
$ gcc --version
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
所以,我有点迷失了:)
答案 0 :(得分:14)
最初的面试问题是:“以下代码的输出是什么?”
int i = 2;
i = i++ + i++;
正确的答案是 undefined behaviour ,因为您在多次修改同一个变量时没有序列点。
C ++ 03标准§5 [expr] p4
:
除非另有说明,否则单个运算符的操作数和各个表达式的子表达式的评估顺序以及副作用发生的顺序是未指定的。
这可能无法解答您的真实问题,但即使您创建类似整数的类并重载operator++(int)
和operator+(A const&)
,它也会相似。函数参数的计算顺序是未指定的,它可以按编译器喜欢的任何顺序完成,因此结果未指定。
答案 1 :(得分:7)
除了其他人已经指出的内容:单独考虑问题的标题 - “创建C ++整数类以完全与整数整数类型相同” - 我应该指出一个完全不同的原因,为什么它不可能强>
(据我所知)不可能模拟 || 和&amp;&amp; 运算符的快捷行为,即无论如何都会评估操作数的两面。
修改 查看评论。 “据我所知”似乎还不够。但是,Steve Jessop有一个不同的例子,它使整体观点有效。
这与你的增量问题完全无关,但主题是你的问题标题,所以我认为应该提到它。
答案 2 :(得分:4)
正确的答案是((2 + 2)+ 1)+ 1 = 6,即在分配之前应用后增量两次,但是在添加之后。
这不是正确答案:
除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的评估顺序以及副作用发生的顺序是未指定的。在前一个和下一个序列点之间,标量对象应通过表达式的计算最多修改其存储值一次。此外,只能访问先前值以确定要存储的值。对于完整的子表达式的每个允许排序,应满足本段的要求 表达;否则行为未定义。
- ISO-IEC-14882
答案 3 :(得分:1)
实际上,你早就犯了一个非常糟糕的错误。
最初的面试问题是:“以下代码的输出是什么?”
int i = 2;
i = i++ + i++;
这个问题的正确答案是“输出未定义。”
通过修改和读取没有中间序列点的变量,您将调用未定义的行为。
更具体地说,在这种情况下,你在屁股中咬你的是评估+
运算符的参数的顺序是不确定的;在运算符和函数的一般情况下都是如此,除了一些值得注意的例外,即短路逻辑运算符。