【C++和C#的区别杂谈】后自增运算符的结算时机
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#的区别杂谈】后自增运算符的结算时机的更多相关文章
- C语言杂谈(二)自增运算符++与间接访问运算符*的结合关系和应用模式
自增运算符++有前缀和后缀两种,在搭配间接访问运算符*时,因为顺序.括号和结合关系的影响,很容易让人产生误解,产生错误的结果,这篇文章来详细分析一下这几种运算符的不同搭配情况. ++.--和*的优先级 ...
- 为什么mysql事务回滚后, 自增ID依然自增
事务回滚后,自增ID仍然增加,回滚后,自增ID仍然增加.比如当前ID是7,插入一条数据后,又回滚了.然后你再插入一条数据,此时插入成功,这时候你的ID不是8,而是9.因为虽然你之前插入回滚,但是ID还 ...
- js中script的上下放置区别 , Dom的增删改创建
回顾 javascript分为三部分: 1.ECMAScript5.0 es6(阮一峰) es7 es8 es6中有类的概念 声明变量 var let(es6中语法) 内置函数 Date Math.r ...
- Swift3.0 更新后出现比较运算符方法
在将项目更新到swift3.0之后,在一些controller头部会出现 比较运算符的方法 // FIXME: comparison operators with optionals were rem ...
- 用Jdbc连接数据库后实现增删改查功能
增删改用的都是executeUpdate()方法: 查用的是executeQuery()方法 package cn.lideng.dbc; import java.lang.management.Ma ...
- python之while循环用法举例,break与continue的区别,格式化输出及运算符
一.while循环的基本结构 while 条件: 代码块(循环体) else: 当上面的条件为假. 才会执行 执行顺序:判断条件是否为真. 如果真. 执行循环体. 然后再次判断条件....直到循环条件 ...
- [转]说说C语言运算符的“优先级”与“结合性”
补充自己的一点理解: 1.关于++i 与 i++的区别. ++i 和 i++如果是单独使用的语句,即二者后面均加上分号,或者其他单独使用的语句,没有任何区别.例如: for(i=0;i<100; ...
- C++回顾day02---<运算符重载>
一:运算符重载的限制 (一)可以重载的运算符: + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << > ...
- 说说C语言运算符的“优先级”与“结合性”
论坛和博客上常常看到关于C语言中运算符的迷惑,甚至是错误的解读.这样的迷惑或解读大都发生在表达式中存在着较为复杂的副作用时.但从本质上看,仍然是概念理解上的偏差.本文试图通过对三个典型表达式的分析,集 ...
随机推荐
- Chisel3-Intellij IDEA安装Scala插件
https://mp.weixin.qq.com/s/xTk5ucvSNuwsh8C6E362cg 后续开启RISC-V开发相关内容. RISC-V开发推荐使用Chisel编程语言.Chise ...
- (Java实现) 图的m着色问题
图的m着色问题 [问题描述] 给定无向连通图G和m种不同的颜色.用这些颜色为图G的各顶点着色,每个顶点着一种颜色.如果有一种着色法使G中每条边的2个顶点着不同颜色,则称这个图是m可着色的.图的m着色问 ...
- Java实现 蓝桥杯VIP 算法提高 交换Easy
算法提高 交换Easy 时间限制:1.0s 内存限制:512.0MB 问题描述 给定N个整数组成的序列,每次交换当前第x个与第y个整数,要求输出最终的序列. 输入格式 第一行为序列的大小N(1< ...
- Java实现第十届蓝桥杯质数
试题 D: 质数 本题总分:10 分 [问题描述] 我们知道第一个质数是 2.第二个质数是 3.第三个质数是 5--请你计算 第 2019 个质数是多少? [答案提交] 这是一道结果填空的题,你只需要 ...
- JavaScript如何调用Python后端服务
本篇文章旨在通过一段JavaScript来演示如何调用python后端服务的,这是我开发的一个构建测试数据的工具. 第一部分:html 代码部分 第二部分:JavaScript代码部分 第三部分:Py ...
- 「MoreThanJava」机器指令到汇编再到高级编程语言
「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...
- Php-webdriver 的安装与使用教程
Php-webdriver 是 Facebook 开发的基于 PHP 语言实现的 Selenium WebDriver 客户端组件,可以用它来操作浏览器.常见的操作包括:自动化测试.采集数据等. 安装 ...
- 描述一下 JVM 加载 class 文 件的原理机制?
JVM 中类的装载是由 ClassLoader 和它的子类来实现的, Java ClassLoader 是一个重要的 Java 运行时系统组件.它负责在运行时查找和装入类文件的类.
- 若linux 的分区硬盘满,如何处理?
一.确定是不是真的是磁盘空间不足 输入命令:df –lh 查看磁盘信息 二.如何定位最大文件目录 输入命令:cd / 进入根目录. 输入命令:du -h max-depth=1 寻找当前目录,哪个文件 ...
- 解决Celery 在Windows中搭建和使用的版本
官网:http://docs.celeryproject.org/en/latest/faq.html#does-celery-support-windows 描述如下:表示Celery 4.0版本以 ...