连续赋值与求值顺序
看到一个2010 年的帖子[1],里面讨论了一段 JavaScript 代码:
1 2 3 | var a = {n:1}; a.x = a = {n:2}; alert(a.x); // --> undefined |
帖子里有很多的讨论来分析为何 a.x 是 undefined,下面是我的一些思考。
首先,在JavaScript中,有以下几点需要明确的:
1 2 3 4 5 6 7 8 9 10 11 | a.x = a = {n: 2}; // => a.x = (a = {n: 2}); /* 1. 首先,按 = 号的结合性做个分组; 2. 然后,由于从左到右的求值顺序和运算符优先级,先计算 a.x,获得指向内存中的某个地址,假定为 A; 3. 接着计算第一个等号的右边,其为第二个等号的求值结果: 把变量 a 指向对象 {n: 2}, 然后返回该引用; 4. 然后把该引用赋给 A,即 A 位置存储的值是 {n: 2}; 在第三个步骤计算第二个等号的过程中,把 a 重新指向了另一个对象 {n: 2},而不在是 {n: 1} 了, 这个时候 a.x 指向的位置已不在是 A 了,故最后得到的 a.x 是 undefined。 */ |
不同于一些用于精简代码显示技术的 Trick,连续赋值应该说是较为常用的一种语句,但需要注意的是,尽量少写出这种修改、引用并存的语句。
在 JavaScript 中还好,规定了从左往右求值,但在 C/C++ 中,并没有明确规定求值顺序,在某些情况下尤其是在函数参数中,可能会带来很大的问题。
翻了下邮件,在 2010 年 4 月的时候,向师兄请教了一个简单的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #include <iostream> using namespace std; int add1(int x) { x++; return x; } int add2(int& a) { a++; return a; } int main() { int x=0; int y=0; cout << add1(x) << " " << x <<endl; cout << add2(y) << " " << y <<endl; /* cout << "调用前: x y"<<endl; cout <<"\t"<< x << " " << y <<endl; cout << "函数结果" << add1(x) <<" "<< add2(y) << endl; cout << "调用后: x y"<<endl; cout << "\t"<<x << " " << y <<endl; */ system("pause"); return 0; } |
在 VC 6.0,2008,2010,g++ for windows,还有在 Ubuntu下用 g++ 编译运行都是得到 1 0 1 0 (此处不显示换行)的结果;只有在采用了 g++ 编译器的 DEV C++ 5.0 中得到的是 1 0 1 1。
咨询和查资料[5][6][7][8]的结论是:C/C++ 函数参数的求值顺序没有明确的规范,具体依赖于编译器的实现,因此不要写出那种带副作用的连续操作语句。
References