c语言自加自减三道题
int x , y,z;
x = 0;
y = z = -1;
x += -z ---y;
printf(“x=%d\n”,x)
x = 2
为什么?
x + = -z - - -y 相当于
x = x + ((-z)--)-y; 这里-z—是先用-z然后再(-z)- -运算
这里需要注意的是操作符结合的顺序是自左至右,而运算顺序是自右至左!
也就是 –z - - -y 表示的是 ((-z)--)-y 而不是 (-z)-(--y)
#include <stdio.h>
int main()
{
struct st
{
int n;
struct st *next;
};
struct st a[3],*p1,*p2,*p3,*p4;
a[0].n = 5;
a[0].next = &a[1];
a[1].n = 7;
a[1].next = &a[2];
a[2].n = 9;
a[2].next = '\0';
p1 = p2 = p3 =p4 = &a[0];
printf("p1++->n = %d\n",p1++->n);
printf("p1->n = %d\n",p1->n);
//printf("p2->n++ = %d\n",p2->n++);
//printf("p2->n = %d",p2->n);
//printf("(*p3).n++ = %d\n",(*p3).n++);
//printf("(*p3).n = %d\n",(*p3).n);
//printf("++p4->n = %d\n",++p4->n);
}
第一组
printf("p1++->n = %d\n",p1++->n); //p1++>n = a[0].n = 5
printf("p1->n = %d\n",p1->n); //此时在上一句执行完之后 p1++->n中的p1执行了p1++操作,p1是指针,它每增加一个单位就是增加一个指针位,所以p1++就使p1指向了a[0]下一个变量的地址,即a[1]的地址,此时p1->n就是a[1].n的值,所以此时 p1->n = a[1].n = 7
第二组
printf("p2->n++ = %d\n",p2->n++); //p2->n++ = 5 注意 p2也是指向a[0]的,所以p2->n = a[0].n,然后执行完只一句之后,就执行了p2->n++ 执行加加操作, 此时其实操作的a[0].n的值自加1,那么 p2->n++之后,a[0].n的值由原来的5就变成了6
printf("p2->n = %d",p2->n); //p2->n = 6, 这里等于6 是因为上面加加了。
第三组
printf("(*p3).n++ = %d\n",(*p3).n++); //(*p3).n++ = 5 这里的 *p3 就相当于a[0],所以(*p3).n = a[0].n 也就等于5 了
printf("(*p3).n = %d\n",(*p3).n); //这里在上面的(*p3).n++之后 a[0].n 变成了6.
第四组
printf("++p4->n = %d\n",++p4->n); //这里的 ++p4->n = 6 为什么? 明显 p4->n相当于a[0].n 因为这里的 –> 的优先级高于 ++ , -- 等操作符,所以要先结合 ->操作,所以++p4->n 就相当于++(p4->n)
printf("a[0].n = %d\n",a[0].n); 这个语句在上面的p4测试之后加测的一句,证明a[0].n = 6 也就是证明 ++(p4->n) 是分析正确的。
上面这个问题其实就是操作符优先级的问题,具体可以参考c语言操作符优先级问题
#include <stdio.h>
int main()
{
int x = 2,y,z;
int i = 2;
printf("++i=%d,i++=%d,++i=%d\n",++i,i++,++i);
printf(“I = %d”,i);
y = (++x)+(x++)+(++x);
z = x--+--x+x--;
printf("x=%d,y=%d,z=%d",x,y,z);
return(0);
}
printf("++i=%d,i++=%d,++i=%d\n",++i,i++,++i); // 5, 3, 5
printf(“I = %d\n”,i); // 6
输出结果是
++I = 5,i++ =3, ++I = 5 这表明 先是是第一个 ++I 和和 第三个++I 自加之后,然后此时i是5,再printf()时就是 5,3,5,然后printf()之后, i又自加了,所以后面那个I = 6
printf("x=%d,y=%d,z=%d",x,y,z); // x = 2, y = 12,z = 12;
首先明白:
++在前边是,先自加1再使用
++在后边是先使用数值,再自加1
y=(++x)+(x++)+(++x);
i++是先使用i本身的值,再把i+1;
++i是先把i+1,再使用i的值运算。
y=(++x)+(x++)+(++x) :先处理括号内的运算
++x,x的值+1变为3
x++,暂时不变
++x,x的值+1变为4
现在相当于y=x+x+x=4+4+4=12;x再加1=5;
z=(x--)+(--x)+(x--):先处理括号内的运算
x--,暂时不变
--x,x变为4
x--,暂时不变
z=x+x+x=4+4+4=12
然后x自减两次变为2。
y=++x;y+=x++;y+=++x;z=x--;z+=--x;z+=x--;这样才是正确的使用
补充:
别算了,在TC里面结果是2,12,12,;在VC里面是2,10,12,
补充:
自加自减在复杂表达式中的处理方法
3. 1 前加分解法
设i1, i2, ?, in , s 均为变量, op 为运算符, 且有表达式s = (+ + i1)op (+ + i2)op ?op (+ + in) (3—1)
若op 为逻辑与运算符(&&) , 且存在im (1 ≤m ≤n) ,使得im + 1 = 0, 但ik + 1 ≠ 0 (1 ≤ k ≤m ) , 则表达
式(3—1) 可分解为
i1 = i1 + 1; i2 = i2 + 1; ...; im = im + 1;
s = 0
否则, 表达式(3—1) 可分解为
i1 = i1 + 1; i2 = i2 + 1; ...; in = in + 1;
s = 1
若op 为逻辑或运算符(‖) , 且存在im (1 ≤m ≤ n) ,使得im + 1 ≠ 0, 但ik + 1 = 0 (1 ≤ k ≤m ) , 则表达
式(3—1) 可分解为
i1 = i1 + 1; i2 = i2 + 1; ...; im = im + 1;
s = 1
否则, 表达式(3—1) 可分解为
i1 = i1 + 1; i2 = i2 + 1; ...; in = in + 1;
s = 0
若op 双目算术运算符, 则表达式(3—1) 可分解为
i1 = i1 + 1; i2 = i2 + 1; ...; in = in + 1;
s = i1 op i2 op ... op in;
3. 2 后加分解法
设i1, i2, ..., in , s 均为变量, op 为运算符, 且有表达式s = ( i1 + + )op ( i2 + + )op ...op ( in + + ) (3—2)
若op 为逻辑与运算符(&&) , 且存在im (1 ≤m ≤n) ,使得im = 0, 但ik ≠ 0 (1 ≤ k ≤m ) , 则表达式(3—2)
可分解为
s = 0
i1 = i1 + 1; i2 = i2 + 1; ...; im = im + 1;
否则, 表达式(3—2) 可分解为
s = 1
i1 = i1 + 1; i2 = i2 + 1; ...; in = in + 1;
若op 为逻辑或运算符(‖) , 且存在im (1 ≤m ≤ n) ,使得im ≠ 0, 但ik = 0 (1 ≤ k ≤m ) , 则表达式(3—2)
可分解为
s = 1
i1 = i1 + 1; i2 = i2 + 1; ...; im = im + 1;
否则, 表达式(3—2) 可分解为
s = 0
i1 = i1 + 1; i2 = i2 + 1; ...; in = in + 1;
若op 双目算术运算符, 则表达式(3—2) 可分解为
s = i1 op i2 op ... op in;
i1 = i1 + 1; i2 = i2 + 1; ...; in = in + 1;
3. 3 前减分解法
设i1, i2, ..., in , s 均为变量, op 为运算符, 且有表达式
s = (- - i1)op (- - i2)op ...op (- - in) (3—3)
若op 为逻辑与运算符(&&) , 且存在im (1 ≤m ≤n) ,使得im - 1 = 0, 但ik - 1 ≠ 0 (1 ≤ k ≤m ) , 则表达
式(3—3) 可分解为
i1 = i1 - 1; i2 = i2 - 1; ...; im = im - 1;
s = 0
否则, 表达式(3—3) 可分解为
i1 = i1 - 1; i2 = i2 - 1; ...; in = in - 1;
s = 1
若op 为逻辑或运算符(‖) , 且存在im (1 ≤m ≤ n) ,使得im - 1 ≠ 0, 但ik - 1 = 0 (1 ≤ k ≤m ) , 则表达
式(3—3) 可分解为
i1 = i1 + - 1; i2 = i2 - 1; ...; im = im - 1;
s = 1
否则, 表达式(3—3) 可分解为
i1 = i1 - 1; i2 = i2 - 1; ...; in = in - 1;
s = 0
若op 双目算术运算符, 则表达式(3—3) 可分解为
i1 = i1 - 1; i2 = i2 - 1; ...; in = in - 1;
s = i1 op i2 op ...op in;
3. 4 后减分解法
设i1, i2, ..., in , s 均为变量, op 为运算符, 且有表达式
s = ( i1 - - )op ( i2 - - )op ...op ( in - - ) (3—4)
若op 为逻辑与运算符(&&) , 且存在im (1 ≤m ≤n) ,使得im = = 0, 但ik ≠ 0 (1 ≤ k ≤ m ) , 则表达式(3—4) 可分解为
s = 0
i1 = i1 - 1; i2 = i2 - 1; ...; im = im - 1;
否则, 表达式(3—4) 可分解为
s = 1
i1 = i1 - 1; i2 = i2 - 1; ...; in = in - 1;
若op 为逻辑或运算符(‖) , 且存在im (1 ≤m ≤ n) ,使得im ≠ 0, 但ik = 0 (1 ≤ k ≤m ) , 则表达式(3—4)可分解为
s = 1
i1 = i1 - 1; i2 = i2 - 1; ...; im = im - 1;
否则, 表达式(3—4) 可分解为
s = 0
i1 = i1 - 1; i2 = i2 - 1; ...; in = in - 1;
若op 双目算术运算符, 则表达式(3—4) 可分解为
s = i1op i2op ...op in;
i1 = i1 - 1; i2 = i2 - 1; ...; in = in - 1;
若op 为其他C 运算符, 则根据其优先级、结合方向和前(后) 加(减) 依次进行运算即可。一个表达式可能是上述几种表达式的复合, 但只要按上述分解方法, 不管表达式中有多少个自加自减运算, 无论含自加自减运算的表达式多么复杂,即使初学者也会运用自如。
4 举例
例1 若有说明
in t k = 3, s;
则语句
s = (+ + k ) + (+ + k) + (k - - ) + (k + + ) ;
可分解为
k = k + 1; k = k + 1;
s = k + k + k + k;
k = k - 1; k = k + 1;
由此可知, k 的值为5, s 的值为20。
例2 复合语句
{s = 3
p 1 + + ; 3
p 1 = 3
p 2 - - ; 3
p 2 = s; }
可分解为
{s = 3
p 1; p 1 = + + ; 3
p 1 = 3
p 2; p 222; 3
p 2 = s; }
补充:
y=(++x)+(x++)+(++x);/*x=x+1;x=x+1;y=x+x+x;x=x+1;*/z=(x--)+(--x)+(x--);/*x=x-1;z=x+x+x;x=x-1;x=x-1*/说明:++x的优先级最高,其次到(),最后才是x++。
总结
分两种情况:
一种是在一个表达式中出现多个自增自减及加减混合运算,此时怎么算?如下:
x = 2;
y = ++x+x--+(++x)+x+++(--x) 等于多少?最后x等于多少?
面对這样的式子运算方向:先计算所有的前置自增及自减运算,此时计算出来的x值就是式子中每个x的值,此时去掉所有的自增自减符号计算y的值,最后计算x的后置自增自减值,具体计算过程如下:
还有一种就是:
I = 2;
printf(“%d,%d,%d”,++I,i++,++i); 这输出的结果到底是什么? 答案是 5,3,5
在这里是先计算最右边的那个 ++I 那么此时的I 就变成了3,然后计算中间那个i++,当碰到后置运算时,编译器会申请一个零时寄存器把当前的i为3的值保存起来,当前记为寄存器1,然后紧接着进行i++计算,此时I的值为4,然后计算最左边那个++i,此时i加出来的值为5, 此时i值被保存为5,然后入栈,在入栈中保持两个++i的寄存器的值都是重新通过mov指令将当前的i值mov到这些寄存器中然后入栈的,所以此时第一个++i和最后一个++i入栈的值都为5, 而中间那个i++入栈的值则是从零时寄存器1直接入栈,所以此时的i++位置的值为3,而printf(“%d,,,,”,++I,i++,i++)中这后面的参数表示++I,i++,++i所代表的地址中的值,然printf时,会去这些地址中取值,当时之前入栈的时候,往这些地址中写入值的时候是写入的5,3,5,所以这里取出来时也会是5,3,5,所以打印出来的就是,5,3,5.
c语言在函数参数的入栈方向上是自右向左的,如下printf()是先计算++j,然后再计算j++,然后再计算i++,等…….
printf("i++=%d,i++=%d,i++=%d,j++=%d,++j=%d\n",i++,i++,i++,j++,++j);
00F237BD mov eax,dword ptr [j]
00F237C0 add eax,1
00F237C3 mov dword ptr [j],eax
00F237C6 mov ecx,dword ptr [j]
00F237C9 mov dword ptr [ebp-0E8h],ecx
00F237CF mov edx,dword ptr [j]
00F237D2 add edx,1
00F237D5 mov dword ptr [j],edx
00F237D8 mov eax,dword ptr [i]
00F237DB mov dword ptr [ebp-0ECh],eax
00F237E1 mov ecx,dword ptr [i]
00F237E4 add ecx,1
00F237E7 mov dword ptr [i],ecx
00F237EA mov edx,dword ptr [i]
00F237ED mov dword ptr [ebp-0F0h],edx
00F237F3 mov eax,dword ptr [i]
00F237F6 add eax,1
00F237F9 mov dword ptr [i],eax
00F237FC mov ecx,dword ptr [i]
00F237FF mov dword ptr [ebp-0F4h],ecx
00F23805 mov edx,dword ptr [i]
00F23808 add edx,1
00F2380B mov dword ptr [i],edx
00F2380E mov esi,esp
00F23810 mov eax,dword ptr [j]
00F23813 push eax
00F23814 mov ecx,dword ptr [ebp-0E8h]
00F2381A push ecx
00F2381B mov edx,dword ptr [ebp-0ECh]
00F23821 push edx
00F23822 mov eax,dword ptr [ebp-0F0h]
00F23828 push eax
00F23829 mov ecx,dword ptr [ebp-0F4h]
00F2382F push ecx
00F23830 push offset string "i++=%d,i++=%d,i++=%d,j++=%d,++j="... (0F25A60h)
00F23835 call dword ptr [__imp__printf (0F282B4h)]
00F2383B add esp,18h
00F2383E cmp esi,esp
00F23840 call @ILT+305(__RTC_CheckEsp) (0F21136h)
总结
当计算式为一个表达式,表达式中有前置/后置自增自减/加减混合运算时:
Step1:从表达式左边到右边用括号括出前置/后置自增自减运算符
Step2:从表达式左边到右边依次计算所有前置运算符,计算某个变量x的最终值为当前表达式中所有这个变量x的当前值
Step3:从表达式左边至右边进行加减运算符计算,计算所得值为当前表达式的值
Step4:从表达式左边到右边分别计算各个变量的后置运算符,计算所得变量值,为各变量最终值
例如:
x = 1 y =2;
z = ++x+x--+(--y)+(++x)+y+++(--x)+(++y);
下面分步解析:
Step1:用括号自左至右括出自增自减前置后置运算符,下面分步解析:
1. z = (++x)+x--+(--y)+(++x)+y+++(--x)….
2. z = (++x)+(x--)+(--y)+(++x)+y+++(--x)….
3. z = (++x)+ (x--)+(--y)+(++x)+y+++(--x)….
……
Step1最终形式:
z = (++x)+(x--)+(--y)+ (++x)+ (y++)+(--x)+ (++y);
Step2:自表达式左边至右边计算所有的前置运算:
所有的前置运算如:
z = (++x)+(x--)+(--y)+ (++x)+ (y++)+(--x)+ (++y);
注意自左至右计算,x初始值为1,先算++x 得到x=2,然后算中间的++x 此时得到x = 3,然后再算最后的—x 得到x =2,至此变量x的所有前置运算结束,此时z表达式中出现变量x的地方,它们的值都是2.,此时z表达式可“简化”为:
z = 2+2+(--y)+ 2+ (y++)+2+ (++y);
计算y的所有前置运算:y初始值为2,自左至右计算,先算—y得到y=1,再算++y得到y=2,此时z表达式中所有y的值均表示为2,此时z表达式可进一步“简化“为:
z = 2+2+2+ 2+ 2+2+ 2;
Step3:从表达式左边到右边计算加减运算来计算z表达式的值,显然z = 2*7 =14
Step4: 从表达式左边到右边分别计算各个变量的后置运算符,计算所得变量值,为各变量最终值
所有前置运算之后,x=2,y=2,此时为”简化”的z表达式中后置运算如:
z = (++x)+(x--)+(--y)+ (++x)+ (y++)+(--x)+ (++y);
对x进行后置计算 得x = 1,对y进行后置运算得y=3
从上面可以看出表达式计算一直都是从左边计算到右边的!
下面是函数参数的运算:这个是从右边到左边的!
I = j = 2;
printf("++i=%d ,--i=%d,i++=%d,i++=%d, ++i=%d ,j++=%d,++j=%d\n",++i,--i,i++,i++,++i,j++,++j);
这里先计算 ++j,然后计算j++,然后依次计算++i,i++,i++,--I,++i
这里需要注意的是,在前置运算时,对变量进行的前置运算会是全局的,就是说按照上面的计算对i的前置计算顺序是 先++I,然后—I,最后++I,這样计算之后,在最后计算++i之后得到的i值将是这”++I,--I,++i”三个位置的值,就是这三个位置的值是一样的,而碰到后置运算时,会先申请一个临时地址或寄存器把当前变量值存储起来,然后再计算其后置运算。
下面详细分析其计算过程
printf("++i=%d ,--i=%d,i++=%d,i++=%d, ++i=%d , --j=%d ,j++=%d,++j=%d\n",++i,--i,i++,i++,++i,--j,j++,++j);
c语言函数参数入栈顺序是自右向左,所以这里的计算顺序是自右向左,所以从++j开始计算。
首先I = j = 2 初始化都为2
第一步计算++j 是前置计算,计算值直接复制给j, 所以第一步的结果是 j = 3
第二步计算j++,这里遇到后置计算,计算前先甚至一个临时寄存器假设为Reg1,保留当前j的值,然后再进行后置计算,所以这里又分两步:第一步申请临时寄存器Reg1保存j=3的值,然后j后置加加,结果是 Reg1 = 3 ,j = 4
第三步计算--j,这里是前置计算,所以直接计算 –j 得到j = 3
注:这里j的三个表达式计算完了,这里第三步计算出来的j的值将”赋值”给第一步的计算值,也就是输出结果时第一步和第三步是一样的, 当然这里刚好第一步自身计算的值也和第三步是一样的。第二步输出的值就是那个Reg1的值,所以
printf(“……--j=%d,j++=%d,++j=%d”,…..,--j,j++,++j)实际输出是
printf(“……--j=%d,j++=%d,++j=%d”,…..,j=3,Reg1=3,j=3)
下面接着分析:
第四步计算++i, 前置运算直接算,得到 i = 3
第五步计算i++,后置计算,要先申请临时寄存器保存当前值,然后再计算,申请Reg2 = i =3,然后i++ 得i=4,
第六步计算i++,还是后置计算,还是先申请临时寄存器保存当前i=4的值,然后再进行后置计算,那么就是Reg3 = i = 4, 然后i++ ,得到i = 5
第七步计算,--i 前置计算,直接计算得到i = 4
第八步计算++i,前置计算,直接计算得到 i = 5
之前说了,所有的前置计算具有全局性,就是变量i有多个前置运算表达式,那么计算完所有的变量i的前置运算后,所有前置运算表达式中的i值就是这个最终计算的值,第八步计算的i= 5, 而i的前置运算分布在第四步,第七步,第八步,那么这三步目前的表达式中i都是i = 5,这就是所谓的”全局”性,而后置运算表达式的i则是其临时寄存器中的值,第五步中i++的变量i的值现在是其临时寄存器Reg2的值,也就是i = 3, 第六步中的i值也就是Reg3中的值,也就是i = 4
,所以最终输出结果是:
gcc运行结果:
vs2010运行结果:
运行结果正如我们所示。