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. [05]HTML基础之表格标签

    1. <table>标签 表格容器,尽量避免用属性书写样式,而是用CSS来表达 border: 数字 //表格边框宽度 2. <caption>标签 表格的标题,一般出现在表格 ...

  2. prism.js——让网页中的代码更好看

    粗放的代码展示 有时候,网页中会插入代码.直接把代码放入<pre></pre>标签和<code></code>标签里,也算是可以在页面中显示出来. 比如 ...

  3. 如何提交本地代码到git仓库

    首先要先注册一个自己的GitHub账号,注册网址:https://github.com/join 有了自己的账号以后,就可以进行登录,开始创建一个新的项目 创建一个新的项目,填写项目名称,描述 我是和 ...

  4. (Java实现) 洛谷 P1605 迷宫

    题目背景 迷宫 [问题描述] 给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过.给定起点坐标和 终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案.在迷宫 中移动有上下 ...

  5. Java实现 LeetCode 257 二叉树的所有路径

    257. 二叉树的所有路径 给定一个二叉树,返回所有从根节点到叶子节点的路径. 说明: 叶子节点是指没有子节点的节点. 示例: 输入: 1 / \ 2 3 \ 5 输出: ["1->2 ...

  6. Java实现 LeetCode 124 二叉树中的最大路径和

    124. 二叉树中的最大路径和 给定一个非空二叉树,返回其最大路径和. 本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列.该路径至少包含一个节点,且不一定经过根节点. 示例 1: 输入: ...

  7. Java中抽象类与接口的详细说明

    首先简单的介绍一下抽象类: 定义是很简单的,我们这里不写官方的语言,我自己看着都烦,我们就用白话介绍,抽象类本质是一个类,没问题,那么类里面一般都是有方法的,方法包括方法名和方法体,这是常识对不对,那 ...

  8. java代码(5) ---guava之Multiset

    guava之Multiset  一.概述 Guava提供了一个新集合类型Multiset,它可以多次添加相等的元素,且和元素顺序无关,Multiset继承于JDK的Collection接口,而不是Se ...

  9. centos7 yum源更新

    先进入到yum源文件cd /etc/yum.repo.d/  1.创建一个repo_bak目录,用于保存系统中原来yum的repo文件. sudo mkdir repo_bak 2.备份yum源文件至 ...

  10. 为什么阿里巴巴Java开发手册中强制要求接口返回值不允许使用枚举?

    在阅读<阿里巴巴Java开发手册>时,发现有一条关于二方库依赖中接口返回值不允许使用枚举类型的规约,具体内容如下: 在谈论为什么之前先来科普下什么是二方库,二方库也称作二方包,一般指公司内 ...