自增运算符(++)

自增\自减运算符分为前缀形(++a)和后缀形(a++),这里重点分析自增

大部分人对前缀和后缀的理解一般是,前缀形式是先++再使用(先变后用),后缀形式是先使用再++(先用后变)

 

(tips:自增运算符只能作用于变量,而不能作用于变量或表达式,例:(i+j)++就是非法的)

先来说一下一般情况

 main()
{
int a = ;
int b; b = a++;
printf("%d", b);
}

上面这种应该大部分人都会,属于常规情况,是先把a的值赋值给b,再a++,最后输出值为3(大多数应该都是这么理解的)

但是按照优先级的说法,这里就不太好解释,'++'的优先级比'='高,按理说'++'应该是在'='之前发生的,但表面上看来是'='先与'++'发生

类似的有

 main()
{
int a = ;
int b;
b = -a++;
printf("%d\n", b);
}

按照运算符优先级和结合性的说法,取反运算符'-'和'++'运算符属于同一优先级,结合性是右结合

所以-a++应该等价于-(a++),但是实际运行结果是-3

笔者在网上找了一些解释,如下

第一种解释:

(图片来源:http://forum.ubuntu.org.cn/viewtopic.php?t=301915

第二种解释:

(图片来源:https://blog.csdn.net/SunXiWang/article/details/78553933

第三种解释:

(图片来源:https://www.cnblogs.com/weiyinfu/p/4836334.html

上述三种解释中第二种和第三种解释很相似,笔者也比较倾向于第二种和第三种解释

按照这种解释方式来对上面的代码,重新梳理思路

 int a = ;
int b;
b = a++;
printf("%d", b);

这里b=a++的确是,先a++然后再赋值,但是a++后的返回值是改变之前的值,

可以把a++理解为一个函数,函数的返回值是改变之前的值

 int temp = a;
a = a+;
return temp;

这样用优先级来解释就说得通了,并且这个返回值是一个常量不是变量例如就是错误的

下面将自增运算符引入一些更复杂的表达式中

 main()
{
char *p = "hello"; printf("%c", *p++);
}

按照优先级来解释,'*'和'++'属于同一优先级,结合性为右结合,所以说*p++等价于*(p++),先地址++,然后返回改变前的地址,然后*对p解引用得到p[0]的值,输出值应该为h

(注意:这里很容易误解为括号优先内的地址先++,然后取移动后值,不要被括号误导了,在这里*p++和*(p++)效果是一样的)

现在我们来对*(p++)进行验证,代码如下

 main()
{
char *p = "hello"; printf("%c", *(p++)); }

运行结果为,到这里可以确定*p++和*(p++)效果相同

(tips:上面的代码char *p,不能改成char p[10]之类的char型数组,因为对char p[10]来说p指的是p[10]的首地址,是一个常量,常量值是不可修改的,如果还这么写编译器会报错,

现在进入下一个阶段,下面的代码就有点迷惑性了,请读者注意

 main()
{
char p[10] = "hello"; printf("%c", (*p)++);
}

(这里不能再用char *p了,因为用char *p的话,"hello"就是常量,常量的值是不可更改的,继续用(*p)++的话,编译不会报错,但是程序无法运行) 

请读者想一下输出的结果应该是什么?这里用括号()将*p括起来了,括号优先级最高,*先与p结合即解引用得到p[0]的值,看起来输出结果应该是i,常规思维一般是p[0]+1即值+1

但实际的输出结果是

表面上看起来貌似括号没有起作用,其实不然,现在重新理解下(*p)++的过程

第一步:括号优先级最高*与p结合,解引用得到p[0]的值

第二步:*p的值(也就是p[0]的值)++,这里p[0]的值的确是+1了,但是返回值是+1之前的值,%c打印的返回值所以为h

到这里,就能够解释为什么输出的值为h而不是i了

再用下述代码对p[0]值进行检查

 main()
{
char p[] = "hello"; printf("%c***%c\n", (*p)++, *p);
}

运行结果为

这里的*p++可以改为p[0]++,效果是一样的(到这里读者应该基本明白了*p++、*(p++)、(*p)++的区别和运算顺序)

下面进入最终阶段,先上完整代码

 #include <stdio.h>
#include <stdlib.h> void fun(char *t, char *s)
{
while(*t != '\0') t++;
while((*t++ = *s++) != '\0');
} main()
{
char ss[] = "ppp",aa[] = "abcd";
fun(ss, aa);
printf("%s\n%s\n", ss, aa);
}

在继续往下看之前,读者可以先想一下输出的结果应该是什么?

上述代码中,将ss和aa数组的首地址传入fun函数中,ss和aa分别用char *t和char *s接收,然后while(*t != '\0')t++;就是将t指向ss数组的最后一个位置的地址该位置存储的是'\0',

关键是下一句

while((*t++ = *s++) != '\0');

先分析下(*t++ = *s++),'*'和'++'的优先级相同,而且都是右结合,即等价于*(t++) = *(s++);

步骤如下:

第一步:t先与++结合,地址+1,此时t的值已经改变,但是t++返回值的是t+1前的值,即t[3]的地址(&t[3])

第二步:*与t++的返回值结合,得到t[3]的值(注意这里说的是返回值,而不是*与t结合--虽然不知道这样说有没有问题,为了方便理解先这样说)

第三、四步:这里*s++的操作与*t++的操作相同这里就不再赘述了

第五步:将*s的值赋给*t,然后判断*t是否不等于'\0'

最后输出结果为

补充:C/C++ 语言的规定告诉我们,任何依赖于特定计算顺序、依赖于在顺序点之间实现修改效果的表达式,其结果都没有保证。程序设计中应该贯彻的规则是:

如果在任何“完整表达式”(形成一段由顺序点结束的计算)里存在对同一“变量”的多个引用,那么表达式里就不应该出现对这一“变量”的副作用。否则就不能保证得到预期结果。

参考链接:

https://bbs.csdn.net/topics/370153775

http://forum.ubuntu.org.cn/viewtopic.php?t=301915

https://blog.csdn.net/SunXiWang/article/details/78553933

https://blog.csdn.net/studyvcmfc/article/details/5630674

https://blog.csdn.net/frank_jb/article/details/52244318

https://bbs.csdn.net/topics/390722765

https://www.cnblogs.com/weiyinfu/p/4836334.html

最后提一下一个比较坑的地方:int a  = 0; a=a++;这条语句无论执行多少次,a的值是肯定不会变的,但是在Microsoft Visual C++ 2010 Express这个编译器中a的值是在不断变化的(就是一直在+1),在其他在线编译器上结果都是0

a=a++;相关的分析的链接:深入剖析C函数参数的结合顺序及a++和++a的区别(该篇使用了反汇编对a++、++a进行了分析)

              a = a++ 与 a = ++a

*p++与(*p)++与*(p++)------自增运算符常见误区的更多相关文章

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

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

  2. Python的自增运算符

    今天在写一个合并两个有血list的时候,使用了while循环,不自觉的使用了i++,自测的时候发现有语法错误,还检查了好几遍,觉得应该没啥错误啊,后来google了一把,恍然大悟,原来Python早就 ...

  3. 一个由自增运算符以及C语法顺序细节引起的bug

     一.问题描述 在编写modbus代码时发生一件由语法细节引起的bug,起因是自增运算符以及C语法顺序. 输入的数据是2233=0X08B9,高低字节顺序是0x08 0xB9, 使用modbus po ...

  4. C#自增运算符(++)

    一.C#自增运算符(++) 自增运算符(++)是将操作数加1. 1. 前缀自增运算符 前缀自增运算符是“先加1,后使用”.它的运算结果是操作数加1之后的值. 例如: ++x;  // 前缀自增运算符 ...

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

    C++和C#的前自增++n和后自增n++,都是先自增后取值和先取值后自增的含义,但在复杂一点的赋值语句中,我发现细节上有很大的差异. 发现这个问题主要是一个无聊的晚上,我想搞清楚后自增是什么时候结算, ...

  6. 根据字节码探讨java自增运算符的原理

    public class Test { static int x, y; public static void main(String args[]) { x++; myMethod(); Syste ...

  7. java位运算符常见用法

    1. 判断int型变量a是奇数还是偶数 a&1 = 0 偶数 a&1 = 1 奇数 2. 求平均值,比如有两个int类型变量x.y,首先要求x+y的和,再除以2,但是有可能x+y的结果 ...

  8. 【转】 C语言自增自减运算符深入剖析

    转自:http://bbs.csdn.net/topics/330189207 C语言的自增++,自减--运算符对于初学者来说一直都是个难题,甚至很多老手也会产生困惑,最近我在网上看到一个问题:#in ...

  9. C++自增和自减运算符(--和++)

    在C和C++中,常在表达式中使用自增(++)和自减(--)运算符,他们的作用是使变量的值增1或减1,如:++i(在使用i之前,先使i的值加1,如果i的原值为3,则执行j=++i后,j的值为4)--i ...

随机推荐

  1. [Jmeter]Xpath获取元素某个属性的值,以及获取最后一个元素某个属性的值

    XPath获取元素某个属性的值 XPath query:  clients/attribute::total XPath获取最后一个元素某个属性的值 XPath query:   /clients/c ...

  2. 【Maven】安装及配置(Win)

    Maven Maven是一款自动化构建的工具软件,它是基于项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具. 检查环境 maven是基于Java的工具软件, ...

  3. linxu ssh 双端认证 不成功之authorized_keys

    linxu ssh 双端认证 不成功之authorized_keys liunx双端认证可以让我们更简便的在两台服务器之间传输文件,配置暂且不说,网上有大部分的文章可以搜索到,今天我要说的是在不成功的 ...

  4. Shiro ini 过滤器

    http://shiro.apache.org/web.html#Web-WebINIconfiguration Filter Name Class anon org.apache.shiro.web ...

  5. 使用ASI传递post表单..参数是数组

    你可以使用addPostValue方法来发送相同name的多个数据(梦维:服务端会以数组方式呈现): ASIFormDataRequest *request = [ASIFormDataRequest ...

  6. java web前端easyui(layout+tree+双tabs)布局+树+2个选项卡tabs

    1.列出要实现的样式: 2.实现的代码: 分三大部分: 1):页面主体部分:mian.vm <html> <head> <title>Ks UI</title ...

  7. 四则运算 Java 实现 刘丰璨,王翠鸾

    四则运算 GitHub仓库 功能实现 [x] 使用 -n 参数控制生成题目的个数,并且根据解空间限制用户设定的范围(如 range == 2 时,用户却要求生成 10000 道题目,这明显不合理) [ ...

  8. [诈骗]“中国移动”发送诈骗短信,china mobile 是骗子吗?

    今年,这是我遇到的第二次短信诈骗了,两次的手法都是完全一样的.第一次,冒充的是招商银行的积分活动,结果我还真的输入了银行卡与取款密码. 输入完之后,我才醒悟,然后立刻打招商客服电话咨询,改行是否在进行 ...

  9. Firemonkey里触发home按键被按下的事件

    吾八哥我最近在使用Delphi里的Firemonkey平台写一个叫“由由密码管家”的APP工具,是跨多平台的,如ios/android/windows/macOs.由于是用于密码管理的,那么在手机里操 ...

  10. C99 中 main 函数的写法

    今天在论坛看见有人讨论 C 语言中 main 函数的写法,看到结论才知道 main 函数的正确写法. 被老谭酸菜坑了这么多年,还是记录下吧,或许以后某天不搞 .net,回去折腾 C 语言了. 写法1: ...