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

以下为反汇编的结果:

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

//代码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的反汇编结果:

通过和上面的指令比对可以看出来了,具体差异在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#这段代码的反汇编结果:

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

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

小结:

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

感谢您的观看。

【C++和C#的区别杂谈】后自增运算符的结算时机的更多相关文章

  1. C语言杂谈(二)自增运算符++与间接访问运算符*的结合关系和应用模式

    自增运算符++有前缀和后缀两种,在搭配间接访问运算符*时,因为顺序.括号和结合关系的影响,很容易让人产生误解,产生错误的结果,这篇文章来详细分析一下这几种运算符的不同搭配情况. ++.--和*的优先级 ...

  2. 为什么mysql事务回滚后, 自增ID依然自增

    事务回滚后,自增ID仍然增加,回滚后,自增ID仍然增加.比如当前ID是7,插入一条数据后,又回滚了.然后你再插入一条数据,此时插入成功,这时候你的ID不是8,而是9.因为虽然你之前插入回滚,但是ID还 ...

  3. js中script的上下放置区别 , Dom的增删改创建

    回顾 javascript分为三部分: 1.ECMAScript5.0 es6(阮一峰) es7 es8 es6中有类的概念 声明变量 var let(es6中语法) 内置函数 Date Math.r ...

  4. Swift3.0 更新后出现比较运算符方法

    在将项目更新到swift3.0之后,在一些controller头部会出现 比较运算符的方法 // FIXME: comparison operators with optionals were rem ...

  5. 用Jdbc连接数据库后实现增删改查功能

    增删改用的都是executeUpdate()方法: 查用的是executeQuery()方法 package cn.lideng.dbc; import java.lang.management.Ma ...

  6. python之while循环用法举例,break与continue的区别,格式化输出及运算符

    一.while循环的基本结构 while 条件: 代码块(循环体) else: 当上面的条件为假. 才会执行 执行顺序:判断条件是否为真. 如果真. 执行循环体. 然后再次判断条件....直到循环条件 ...

  7. [转]说说C语言运算符的“优先级”与“结合性”

    补充自己的一点理解: 1.关于++i 与 i++的区别. ++i 和 i++如果是单独使用的语句,即二者后面均加上分号,或者其他单独使用的语句,没有任何区别.例如: for(i=0;i<100; ...

  8. C++回顾day02---<运算符重载>

    一:运算符重载的限制 (一)可以重载的运算符: + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << > ...

  9. 说说C语言运算符的“优先级”与“结合性”

    论坛和博客上常常看到关于C语言中运算符的迷惑,甚至是错误的解读.这样的迷惑或解读大都发生在表达式中存在着较为复杂的副作用时.但从本质上看,仍然是概念理解上的偏差.本文试图通过对三个典型表达式的分析,集 ...

随机推荐

  1. 【算法】Tarjan算法求强连通分量

    概念: 在有向图G中,如果两个定点u可以到达v,并且v也可以到达u,那么我们称这两个定点强连通. 如果有向图G的任意两个顶点都是强连通的,那么我们称G是一个强连通图. 一个有向图中的最大强连通子图,称 ...

  2. AUTOSAR-软件规范文档中的UML

    https://mp.weixin.qq.com/s/vm5vWNSpbNIYh25-LjJfYg   AUTOSAR软件规范文档中存在两种UML图: Sequence diagrams Config ...

  3. git可视化打包更新文件

    每次当我们修改了项目代码的时候,总需要理出来一个更新包发给测试部门或者给客户更新.当我们一次的修改的代码多了之后,我们就很难按照文件夹一个一个的去提交出来哪些的更新的,哪些是未修改的.于是乎就在度娘能 ...

  4. Multiple annotations found at this line:- The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path

    解决办法: 右键所在项目 build path configure build path java build path Add Library server Run time (Apache Tom ...

  5. 【JVM】垃圾回收器总结(2)——七种垃圾回收器类型

    七种垃圾回收器类型 GC的约定参数 DefNew——Default New Generation Tenured——Serial Old ParNew——Parallel New Generation ...

  6. java中装箱和拆箱的详细使用(详解)

    一.什么是装箱?什么是拆箱? 在前面的文章中提到,Java为每种基本数据类型都提供了对应的包装器类型,至于为什么会为每种基本数据类型提供包装器类型在此不进行阐述,有兴趣的朋友可以查阅相关资料.在Jav ...

  7. java实现填写算式

    ** 填写算式** 看这个算式: ☆☆☆ + ☆☆☆ = ☆☆☆ 如果每个五角星代表 1 ~ 9 的不同的数字. 这个算式有多少种可能的正确填写方法? 173 + 286 = 459 295 + 17 ...

  8. Java实现第八届蓝桥杯青蛙跳杯子

    青蛙跳杯子 题目描述 X星球的流行宠物是青蛙,一般有两种颜色:白色和黑色. X星球的居民喜欢把它们放在一排茶杯里,这样可以观察它们跳来跳去. 如下图,有一排杯子,左边的一个是空着的,右边的杯子,每个里 ...

  9. Linux链接命令及软链接、硬链接详解

    命令ln详解 命令ln,所在路径为: 可以看到,它的路径为:/usr/bin/ln,因此,它的执行权限是所有用户 命令的基本功能是创建链接文件(硬链接),例如:ln /etc/issue /tmp 选 ...

  10. 机器学习——十大数据挖掘之一的决策树CART算法

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是机器学习专题的第23篇文章,我们今天分享的内容是十大数据挖掘算法之一的CART算法. CART算法全称是Classification ...