帮忙?为什么输出是这样的?

3
#include <iostream>
using namespace std;

int a = 8;

int g()
{
    a++; 
    return a - 1;
}

int f()
{
    a++;
    return a;
}

int main()
{
    cout << g() << " " << f() << " " << g() + f() << endl;
    system("PAUSE");
    return 0;
}

输出结果为“11 11 18”。

1
将来,提及你所期望的输出可能会有所帮助。 - Chris Frederick
1
这个问题与这段代码是否定义良好?有关...请查看@Johannes的解释。 - Nawaz
6个回答

9
在C++中,函数的求值顺序是不确定的。在下面这段代码中:
cout << g() << " " << f() << " " << g() + f() << endl;

编译器可以生成调用f()、f()、g()和g()的代码,然后将结果相加。或者它可以做其他事情。
顺便说一下,这与使用cout无关 - 如果您编写以下代码:
x = a() + b() * c();

无法保证a、b和c的调用顺序。这是全局变量不好的原因之一 - 你通常无法预测修改它们的函数将如何被调用。


+1:我本来想发这个帖子的,但是花了太多时间从标准中找引用和章节。 - John Dibling
2
"评估顺序...没有明确定义" 标准中明确未指定评估顺序。从形式上讲,它甚至可能会动态地发生变化,在每次执行语句时都有不同的顺序。实际上,它将根据编译器和编译优化级别而变化。 - James Kanze
@James:我认为这正是@Neil想要表达的。 - John Dibling

4

这与求值顺序有关。在这种情况下,先计算 g() + f(),因此得到 8 + 10 = 18。之后 a == 10,计算 f() 得到 11 并将 a 设为 11 等等。


2
这似乎是在这种情况下发生的,但是 C++ 并不保证它是这样的。 - user2100815
1
@user 错误,它未被定义! - user2100815
@Neil:收到。我已经编辑了我的帖子。谢谢。 - ralphtheninja
2
@Joe:这并不是“未定义行为”,而是“未指定行为”。这两者之间有很大的区别。 - John Dibling
1
@Neil,@Joe:“未定义行为”=格式错误的代码,编译器可以做任何事情。 “未指定行为”=格式良好的代码,编译器必须执行某些确定性操作,但不需要记录该行为。 - John Dibling
显示剩余6条评论

4
对于这个特定的结果,首先计算g() + f(),这将导致最终将a递增为10,结果为18。不管那个和式中的g()还是f()先执行都是这种情况。如果先执行g(),则结果为8+10;否则结果为9+9
然后计算f(),将a设为11并返回11
接着计算g(),将a设为12并返回11
换句话说,它从右向左依次调用cout的每一位。
现在您会注意到我在上面的讲话中提到了“对于这个特定的结果”这个短语。我不确定标准是否规定了这一点(意思是我现在无心查找),但根据经验,我非常怀疑它会规定。
因此,尽管它实际上按正确的顺序输出了项目,但副作用可能因许多事情而异。这就是为什么全局变量(或更正确地说,对它们的副作用)很少是一个好主意,您应该重新考虑使用它们的原因之一 :-)

1
无论是在C还是C++中,它都没有被定义。 - user2100815
这就是问题所在,但你确定这对于每个编译器都是标准的吗? - Xiaolong
谢谢!我会在其他编译器中测试它以进行验证! - Xiaolong
@user752501,在另一个编译器中测试它并不能证明什么!你需要知道的是,唯一定义文档是标准,而不是两个(甚至一百个)实现所做的事情。 - paxdiablo
@Neil Butterworth的代码中并没有未定义行为,只是未指定行为。因此,“未定义”听起来像是未定义行为,但实际上不是。 - James Kanze
@James,是的,John Dibling已经提出了这个观点。我总是把这两个混淆。我已经修改了我的答案以反映这一点,但我无法编辑旧评论。 - user2100815

3

如果表达式中没有序列点,则执行顺序不被保证。如果想要知道执行顺序,必须将其分解为单独的语句。

cout << g() << " ";
cout << f() << " ";
int temp = g();
temp += f();
cout << temp << endl;

非常感谢,我对此很困惑。我确信()和<<是从左到右读取的。现在正在阅读序列点相关内容。 - flumpb

2

a是全局变量,因此被所有人访问。
另外,operator<<从右到左访问,所以:
g() + f() = 8+10=18 (之后a为10)
f() = 11,a为11
g() = 11,a为12


我认为像这样的操作顺序实际上在规范中是未定义的,因此它可以是从左到右或从右到左。 - Joe

0
cout << g() << " " << f() << " " << g() + f() << endl;

同上

cout.operator<<(operator<<(operator<<(operator<<(operator<<(operator<<(endl), g() + f()), " "), f()), " "), g());

函数被调用的顺序是导致这种情况发生的原因。


@Jordonias,所以我们都错了吗?函数调用的顺序实际上是在C++标准中指定的吗?太好了!现在请引用标准中说明这一点的部分。 - user2100815
1
对于点赞者,如果你对C++一窍不通,请不要投票给C++的答案。 - user2100815
@Neil 或者你可以去看一下如何重载 operator<<,函数的参数是什么以及 << 操作符如何工作。我不是说你错了,只是给出了所有先前答案的最字面的解释。 你能同意这个说法吗?cout << "Hello";等价于cout.operator("Hello"); - Jordonias
1
@Jordonias,你没有解释答案,而且你是错的。用户代码中调用g()和f()的顺序是未指定的,就这么简单。 - user2100815
1
简化来说,在 x( y( a(), b() ), c() ) 中,必须先调用 y、a、b 和 c,但这就是你能说的全部。调用 a、b 和 c 的顺序是未指定的。 - user2100815

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接