• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

【C++和C#的区别杂谈】后自增运算符的结算时机

开发技术 开发技术 1周前 (06-25) 17次浏览

C++和C#的前自增++n和后自增n++,都是先自增后取值先取值后自增的含义,但在复杂一点的赋值语句中,我发现细节上有很大的差异。

发现这个问题主要是一个无聊的晚上,我想搞清楚后自增是什么时候结算,自己捣鼓了一下之后我把我自己的测试结果告诉了朋友,结果学java的朋友和我争论了半天,最后发现同样的代码,大家输出是不一样的。之后我又用了C#写了同样的测试代码,发现输出和他java是一样的,这让我豁然开朗,遂写下这篇博客希望分享我的结论,如果有错误的话,欢迎评论区指正。

前排提醒:C++和C#我都是用的VS2017,不同编译环境的C++代码输出结果可能不一样

先说我的结论:

C++:i++遇到顺序点(逗号,分号)之后i才自增,即我一直以来认为的一条语句结束后结算。但++i前自增的值会影响赋值语句所有i的值。

C#:C#不允许使用‘,’逗号运算符,但也并非到’;’分号才结算,赋值语句是从左到右,按序取值,i++在取完i的值之后马上就自增了,但不影响之前取的i的值,只影响后续i的取值。

心得:实际开发还是尽量自增单行写,第一个是可读性好,第二个是不容易出问题。(应该也就只有笔试会出现以下实验的代码了)

结论看不明白的可以看看我无聊的小实验:

最简单常见的版本:

int arr[] = { 0,0,0 };
int n = 0;
arr[n] = n++;//语句a
for (int num : arr)
{
	cout << num << ' ';
}
//输出是0 0 0,语句a结束后n自增到1

上面这段代码其实在C#也是一样的输出,但如果下面这样写,结果就不一样了。

C++版:

int arr[] = { 0,0,0 };
int n = 0;
arr[n++] = n;//语句a
for (int num : arr)
{
	cout << num << ' ';
}
//输出是0 0 0,语句a结束后n自增到1,C++结果和上面的一样

C#版:

int[] arr = { 0, 0, 0 };
int n = 0;
arr[n++] = n;//语句a
foreach (int num in arr)
{
	Console.Write(num + " ");
}
//输出是1 0 0,现在应该能理解结论了,从左到右,先取n作为索引值,然后自增之后影响到了等号右边的n的值
//语句a则为arr[0] = 1;

就是这样的差异开始引申出了问题,也是让我迷惑的开始,接下来我会通过更复杂的例子和反汇编的指令来解释和证明我的结论,先列举C++的部分,之后再C#,感兴趣的可以往下看。

C++部分:

//代码01
int arr[] = { 0,0,0 };
int n = 0;
arr[n++] = n + (n++) + ++n;
for (int num : arr)
{
	cout << num << ' ';
}
//输出为0 3 0,具体操作流程为语句先结算了++n,使得n自增到1.而出现的两个n++都在赋值语句结束后结算
//所以编译结果为arr[1] = 1 + 1 + 1; 然后i自增两次到3

以下为反汇编的结果:

【C++和C#的区别杂谈】后自增运算符的结算时机

一开始看到这个我有点懵逼,但是通过比对下面这段代码的反汇编指令,就能看出来了。

//代码02
int[] arr = { 0, 0, 0 };
int n = 0;
arr[++n] = n + (n++) + ++n;//把索引处的n++改为了++n
foreach (int num in arr)
{
	Console.Write(num + " ");
}
//输出为0 0 6,具体操作流程为语句先结算两次++n,使得n自增到2.而n++在赋值语句结束后结算
//所以编译结果为arr[2] = 2 + 2 + 2; 然后i自增到3
//这下应该发现n在这条语句中取值的时候都是同样的值了

以下为上面代码02的反汇编结果:

【C++和C#的区别杂谈】后自增运算符的结算时机

通过和上面的指令比对可以看出来了,具体差异在00251F3A这段指令,arr[++n]这段代码02在累加前多进行了一次对n的自增,然后将自增后的值赋给了n,然后开始进行累加,而上面arr[n++]那段代码01是在累加结束之后,在01161F4A那条指令对n++进行自增的结算,所以就算看不懂全部的指令,也应该能通过比对这两段代码看出他们的差异,从而证明了C++对于后自增的处理,是在语句结束之后结算

说到语句结束,我之前写了一篇关于逗号运算符的博客,可以结合今天这个结论看看下面这段代码的输出结果。

int arr[] = { 0,0,0 };
int n = 0;
arr[n] = (n++, n + (n++) + ++n);//在逗号左边添加了n++的语句
for (int num : arr)
{
	cout << num << ' ';
}
//输出为0 0 6
//原因为逗号也属于一条语句结束的标志,所以结算了n++,n=1,然后执行新的语句n + (n++) + ++n
//逗号右边的语句先结算了++n,n=2,所以最后赋值语句为arr[2] = 2 + 2 + 2; 然后n++到3

至此C++的部分结束。

接下来C#的测试结果将颠覆上面的结论,如果不用java或者C#的话,下面的内容可能没有用(朋友java的输出结果和我C#一样,不过也是希望大家自己试试,我自己没有试过)

C#部分:

//代码01 C#版
int[] arr = { 0, 0, 0 };
int n = 0;
arr[n++] = n + (n++) + ++n;
foreach (int num in arr)
{
	Console.Write(num + " ");
}
//输出为5 0 0 和VC++完全不一样对吧
//具体原因为C#的赋值语句是从左到右,先取了n的值作为索引,然后马上对n自增,n=1
//来到等号右边,第一个n取值为1,第二个n取值为1,然后自增到2,第三个n先自增到3,然后取值为3
//所以最终编译结果为arr[0] = 1 + 1 + 3;  即5 0 0的原因
//为了节省篇幅,我直接告诉你arr[++n] = n + (n++) + ++n的结果为0 5 0,如果你上面看懂了这个应该也懂

贴一下C#这段代码的反汇编结果:

【C++和C#的区别杂谈】后自增运算符的结算时机

可以看到第一行是取n的值,然后把n作为索引值,之后inc指令对n自增1。证明了上面的结论。

即:C#的赋值语句为从左到右,按序取值,n++在取完n的值之后马上自增,但不影响之前取的值,只影响后续n的取值

小结:

所以++n 先自增后取值n++ 先取值后自增是能直接套在C#上的,而对于VC++来说,后自增的结算是非常“缓慢”的,到语句结束才结算。

感谢您的观看。


喜欢 (0)