下面问题的输出结果为?
a = 0;
cout << a++ << a++ << ++a << ++a << a++ << ++a << a++ << endl;
首先这个标题哗众取宠了,问题的实际意义也不大,只适合我这种渣渣分析学习。在学习类时,编写跟踪类的函数输出顺序的代码,发现一些奇怪的问题,然后抽象出来的问题。实际只是两方面的知识:cout的输出和前缀++和后缀++的重载。
先看下面的问题,这是我另一个问题缩减版的一段代码:
int test(int j){
cout << "joke " << j << endl;
return j * j;
}
int main(){
cout << "begin " << test(1) << test(2) << test(3) << " end\n";
return 0;
}
cout的顺序我们可以从下面的汇编语言看出(VS10查看反汇编代码的方法是:调试中断的时候,点击“Debug” - “windows” - “Disassembly”或者快捷键Alt+8)
虽然还没学汇编,看的不太懂,我们也能发现cout的自右向左,先调用test(3)、再test(2)、再test(1),最终结果为:
刚看到结果,我还很疑惑,joke怎么能输出在begin的上面,不过现在略微能理解点了。
现在我们再想上边的
a = 0;
cout << a++ << a++ << ++a << ++a << a++ << ++a << a++ << endl;
同样,我们可以查看它的汇编代码,但是看不懂,怎么办。作为一个数学党,那我就总结归纳吧,多看几个从结果找规律呗。
int a =0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0;
cout << a++ << endl;
cout << b++ << b++ << endl;
cout << c++ << c++ << c++ << endl;
cout << ++e << endl;
cout << ++f << ++f << endl;
cout << ++g << ++g << ++g << endl;
a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0;
cout << a++ << ++a << endl;
cout << ++b << b++ << endl;
cout << c++ << c++ << ++c << endl;
cout << ++d << ++d << d++ << endl;
cout << e++ << ++e << e++ << endl;
cout << ++g << g++ << ++g << endl;
输出结果为:
我们发现,对于++x而言,输出的总是最大的值,即最终的值,而x++,输出的是当前的值(当然了,还是自右向左)。
为什么这样呢,学习类中重载前缀++和后缀++运算符的时候,我们知道前缀++的返回值是当前值的引用,后缀++的返回值是当前值的副本。
不妨放一段代码上来:
class Byte{
public:
int b;
const Byte& operator++(){ //前缀++
b++;
return *this;
}
const Byte operator++(int){ //后缀++
Byte before(b);
b++;
return before;
}
Byte(int i = 0):b(i){}
};
我们看代码知道,
前缀++操作中:只需在++后,返回该对象的引用即可。在后缀++操作中,则需要在++之前创建当前对象的副本,在++之后返回新创建的副本对象(将返回值声明为const,防止编译器出现b++++这样的表达式)
我们注意到 前缀++返回的是对象的引用,后缀++返回的是对象递增之前的副本。
因此对于
cout << c++ << c++ << c++ << endl;
输出结果就为210,而对于
cout << ++g << ++g << ++g << endl;
输出结果就为333。
现在我们分析:
输出结果就为333。
现在我们分析: cout << a++ << a++ << ++a << ++a << a++ << ++a << a++ << endl;
自右向左,第一个为0(因为递增前a=0,操作后a=1),第二个为*this(操作后a=2),第三个为2(因为递增前a=2,操作后a=3),第四个为*this,第五个为*this,第六个为5,第七个为6(操作后结果为7),因此*this的结果为7。按照这种分析,最终输出的结果应该为6577270,我们编译一下验证是否正确
完全正确,末了可以使用这个方法再测试一下(结果放在最后面)
b = 0;
cout << b++ << ++b << b++ << b++ << ++b << ++b << b++ << endl;
但是这些都是根据类中重载运算符进行分析,从汇编代码中应该会看的更清楚,可以对比a++和++e之间不同的汇编代码。(不会汇编,就先放在这里,万一哪个大神看到了可以指点渣渣)
cout << a++ << endl;
013046E7 mov eax,dword ptr [a]
013046EA mov dword ptr [ebp-130h],eax
013046F0 mov ecx,dword ptr [a]
013046F3 add ecx,1
013046F6 mov dword ptr [a],ecx
013046F9 mov esi,esp
013046FB mov edx,dword ptr [__imp_std::endl (130A330h)]
01304701 push edx
01304702 mov edi,esp
01304704 mov eax,dword ptr [ebp-130h]
0130470A push eax
0130470B mov ecx,dword ptr [__imp_std::cout (130A334h)]
01304711 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (130A338h)]
01304717 cmp edi,esp
01304719 call @ILT+410(__RTC_CheckEsp) (130119Fh)
0130471E mov ecx,eax
01304720 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (130A320h)]
01304726 cmp esi,esp
01304728 call @ILT+410(__RTC_CheckEsp) (130119Fh)
cout << ++e << endl;
01304842 mov eax,dword ptr [e]
01304845 add eax,1
01304848 mov dword ptr [e],eax
0130484B mov esi,esp
0130484D mov ecx,dword ptr [__imp_std::endl (130A330h)]
01304853 push ecx
01304854 mov edi,esp
01304856 mov edx,dword ptr [e]
01304859 push edx
0130485A mov ecx,dword ptr [__imp_std::cout (130A334h)]
01304860 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (130A338h)]
01304866 cmp edi,esp
01304868 call @ILT+410(__RTC_CheckEsp) (130119Fh)
0130486D mov ecx,eax
0130486F call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (130A320h)]
01304875 cmp esi,esp
01304877 call @ILT+410(__RTC_CheckEsp) (130119Fh)
类似的还有这篇文章
C++输出流cout的顺序问题,我们可以发现因为返回的副本,所以其实就是cout << i++<<i++<<i++<<i++;结果为4321。