现在很多非常0基础的初学者都搞不清楚i++和++i的区别,打集训题的同时偶遇此题,想起了五年级时理解i++的痛苦经历,其实说白了就是那时太小,还没理解参数传递、赋值、内存等。我相信这也是一些年纪比较小的初学者会遇到的问题。
于是写此文以试用多种角度解释变量自加运算符间区别,以及实际使用时注意事项。如遇大佬请狠狠批评指正,以免误人子弟。
内存限制:128 MB时间限制:1.000 S
对于给定的整数a,输出 a++,a--,++a,--a的值
输入一个整数a
输出 a++,a--,++a,--a的值,每个输出值中间用空格隔开。
0
0 0 1 -1
题本身基本无需再解释.但此处我想借此题用易于吸收的大白话讲清楚a++,a--,++a,--a的区别.
如果按“字面意思”去赋值输出,那么结果会是这样的:
#include
#include
using namespace std;
int main()
{
int a,b,c,d,e;
cin >> a;
b = a++;
c = a--;
d = ++a;
e = --a;
cout << b <<" "<< c<< " " << d <<" " << e;
return 0;
}
上面的结果不尽如人意,很明显,这样直接给b,c,d,e赋值是不行的.这也给很多初学者造成了困扰.
同样使人困扰的操作有下:
所以,我们应该模拟一次a++...的运算过程,然后输出对应结果。
所有的C++入门书都会说,i++和++i(i--和--i)的区别在于:
自增运算符:
后置运算符:x++表示在使用x之后,再使x的值加1,即x=x+1;
前置运算符:++x表示在使用x之前,先使x的值加1,即x=x+1.
这个前后之差是非常细微的区别,那么它们怎么执行呢?
举个栗子,我们使x=1;
//x++
x=1;
//使用x
x=x;
//x值+1;
x+1;
//——————————————————————————
//++x
x=1;
//x值+1
x=2;
//使用x
x=x;//x=2
这样看结果是一样的,还是没有区别,原因在于,x两次都是对自身使用了x,但实际当有其他变量、一个表达式内有其他运算时,情况就可能不太一样了。(以下详说)
所以一般实际使用时,在如此的单个i变量的for循环里使用两种自加都没有区别:
++i:
i++:
换句话说,如果x不是在自身上被使用的,我们能更直观地看清其中的顺序。
再往白话里说,把x想成一碟原料,找一口火锅a,x++和++x就是做菜顺序。
那我们开始做这两道菜!我们设输入a,x=1,首先做一次a=x++;
int a,x=1;
//x++:
a=x++;
//首先,x++会使用一次x!也就是说1会先赋给x一次,此时x=1;
//注意,1赋给了x一次,那a也赋了一次x,此时a=1;
//此时在x身上,再+1;
//猜一猜,试一试,a等于几?
没错,a=1!
后来的那个1,根本没有加到a上去!
这是因为刚刚a=x++的时候,先做了一次a=x的赋值运算。之后执行的x=x+1,虽然x已经变化但不影响a。
如果a是一个火锅,那x的碟子先下了菜进去,之后在x的碟子里加了一份菜,当然和火锅a没有关系了。
但++x就不一样了:
int a,x=1;
//x++:
a=++x;
//首先,++x会使x=x+1!也就是说x自己会加一次1,此时x=2;
//那a也赋了一次x,此时a=2;
//此时在x身上,再赋一次x=x;
//猜一猜,试一试,a等于几?
是的,a=2。
看上去非常顺理成章,x的碟子在下进火锅a前就加了一份料,下进火锅a,我们很快能看到满盛的2份菜捞出......(写着写着就饿了)
所以只要不下火锅,就在自己的碟子里,++x和x++无论怎么加都是两份菜。
好了,x++和++x的执行顺序问题已经非常直观地说清楚了,接下来就是关于原题的错解原因回答。
再贴一次错题:
#include
#include
using namespace std;
int main()
{
int a,b,c,d,e;
cin >> a;
b = a++; c = a--; d = ++a; e = --a;
cout << b <<" "<< c<< " " << d <<" " << e;
return 0;
}//输出0 1 1 0
我记得我初学的时候有个很大的疑惑,就是为什么x++不能打包发给a?
这是该运算符的执行特性导致的。
后置运算符的特点就是先使用变量的值再运算,前置运算符则相反。
实际上当你在a=x++使用了这个后置运算符后,编译器会生成一个临时空间去存放x的值,最后也会返回这个值;然后x++输出的结果就是原值,但此时x本身也已更新。此时再赋值给a。这个过程是不违反运算符优先级顺序的。
顺便贴一张优先级图:
注意,它稍后还有大用!
在使用循环这样变量持续变化的环境下它们的用途比较突出;但是在变量不需要持续变化的单个表达式中使用,它对输出我们想要的值就可能产生干扰,甚至干扰运行速度(因为x++后置运算符需要编译器的临时内存)。
就比如上题,a已经在每次自加运算后更新了自身,内存中的a已经更新。但初学者可能会以为用的还是输入的a值。
输出b/c/d/e |
执行运算后系统存储的a |
所执行的操作 |
|
输入a |
/ |
0 |
cin>>a; a=0; |
b=a++ |
0 |
1 |
b=a; a=a+1; |
c=a-- |
1 |
0 |
c=a; a=a-1 |
d=++a |
1 |
1 |
a=a+1; d=a; |
e=--a |
0 |
0 |
a=a-1; e=a; |
所以就错了。
很有意思的另外的一个情况,也会输出不同答案,重新贴一下:
这里又会涉及一个知识点。如果你觉得暂时OK了,那么可以先退出,或者转至文末看我提供的两种方法解答,一种是数组,一种是信竞一本通上的方法改编。
在C、C++语言中,参数是自右向左传递的。(本人掌握语言不多,不知道是不是所有语言都是这样,求轻喷,此处只强调C,C++)
无论是在cout流中还是printf函数涉及多变量输出或存在多个运算式需要计算,一律从后往前进行输出,从右往左进行计算。注意,我不是说打印的顺序会颠倒,而是执行顺序。
举个栗子,
#include
#include
using namespace std;
int main()
{
cout << 3 << 4 << 5 << 6;
return 0;
}
你最后看到的还是3456,但是编译器是根据6——5——4——3的顺序进行输出的。
好了,回到刚刚输出错解-1 0 0 0的情况:我刚刚说运算符优先级图有大用,这就派上用场来了。
年少初识C语言的我对这张图是非常不上心的,或许是因为当时学习强调编程作为思维培养工具的问题,这些细节并没有得到重视,但如果以后有志于C、C++语言开发,这些也当然是必须掌握的。在此有一口诀:算数运算符>移位运算符>条件运算符>按位运算符>逻辑运算符>赋值。CSDN上已经有了许多大佬总结的口诀,读者可以自行查阅。
所以,查阅这张图我们发现,后置运算符优先于前置运算符输出,从右往左算,则是:
运算:--a——++a——a--——a++
输出:a--——a++——补充--a和++a
也就是说,编译器仍然会先向左传递一次参数,传递即处理了运算符;
然后开始输出。
1.--a
等次 |
1 |
2 |
3 |
4 |
传递参数后的a |
/ |
/ |
/ |
-1 |
输出结果 |
/ |
/ |
/ |
/ |
执行操作 |
--a; |
2.++a
等次 |
1 |
2 |
3 |
4 |
传递参数后的a |
/ |
/ |
0 |
/ |
输出结果 |
/ |
/ |
/ |
/ |
执行操作 |
++a |
3.a--
等次 |
1 |
2 |
3 |
4 |
传递参数后的a |
/ |
-1 |
/ |
/ |
输出结果 |
/ |
0 |
/ |
/ |
执行操作 |
a-- |
4.a++
等次 |
1 |
2 |
3 |
4 |
传递参数后的a(对应等次) |
0 |
/ |
/ |
/ |
输出结果 |
-1 |
0 |
/ |
/ |
执行操作 |
a++ |
5.补充输出
等次 |
1 |
2 |
3 |
4 |
传递参数后的a(对应等次) |
0 |
/ |
/ |
/ |
输出结果 |
-1 |
0 |
0 |
0 |
执行操作 |
输出 |
输出 |
以上说明完毕.
#include
#include
using namespace std;
int main()
{
int a[5]={0}, n;
cin >> n;
for (int i = 1; i <= 4; i++)
{
a[i] = n;
}
cout << a[1]++<<" " << a[2]-- << " " << ++a[3] << " " << --a[4] << endl;
return 0;
}
以上做法利用数组的独立性,使四组数据独立开来;
#include
#include
using namespace std;
int main()
{
int a;
cin >> a;
cout << a << " " << a << " " << a + 1 <<" "<< a - 1 << endl;
return 0;
}
以上做法直接利用前后置运算符的运算特性.
——————————————————END—————————————————————