[c语言]运算符的优先级与结合性

c语言中运算符的优先级和结合性常常被人混淆一谈,本文目的在于简单谈谈两者的区别。本文举几个简单的例子说明,这些运算符也特别常用。

 

首先要明白的是:优先级决定表达式中各种不同的运算符起作用的优先次序;而结合性则在相邻的运算符的具有同等优先级时,决定表达式的结合方向。

 

[赋值运算符“=”]

对于赋值运算符来说,常会用到的是连续赋值的表达式。比如“a=b=c”。

这里的变量b的两边都是赋值运算,优先级当然是相同的,那么应该怎么理解这个表达式呢?我们知道,赋值表达式具有“向右结合”的特性,这就表示这个表达式的语意结构是“a=(b=c)”,而不是“(a=b)=c”。这意味着首先完成c向b赋值,然后将表达式“b=c”的值再赋给a。这个区别特别重要!因为可能会涉及到强制类型转换、初值不同等情况,所以不同的理解得到的答案是不一样的。

这里我们再来看一般的二元运算符,为了说明方便,我们现在不妨记作@。如果它是“向左结合”的,那么表达式“x@y@z”表达的意思就应该是“(x@y)@z”;如果是“向右结合”的,那么应该表达的是“x@(y@z)”。这里值得注意的是,这里的二元运算符可以不是同一种运算符,只要有同等优先级,以上结论就是适用的。比如“a*b/c”表达的就是“(a*b)/c”。

 

[自增运算符“++”与解引用运算符“*”]

这一节我们以例子“*p++”引出。下面这个据说是烂大街的实现strcpy函数的示例代码:

char* strcpy( char* dest, const char* src ){char*p = dest;while(*p++ = *src++);return dest;
}

 

我们很快发现,理解这一小段程序的关键就在于怎么理解这个循环条件“*p++”的含义。

首先,解引用运算符“*”的优先级低于后面的自增运算符“++”,所以这个表达式在语义上等价于“*(p++)”,而不是“(*p)++”。这里从语义上来说,括号是多余的,当然从程序的可读性来说建议还是加上括号。

还有一个问题常让人糊涂,就是自增运算符“++”的语义。很多书上写“后自增是先取值,后加1”。这样讲是没有错的,但在一些特定的语境上容易让人无解,比如上面这个while语句。

才开始学习的时候肯定有这样的疑惑:当一个表达式同时包含自增、解引用、赋值,且最终作为控制循环的条件的时候,这里的“前取值”到底“先”到什么程度呢?这时候我们需要查阅一下c语言标准。以下摘自C99标准:ISO/IEC 9899:1999:
6.5.2.4-2:The result of the postfix ++ operator is the value of the operand. After the result is obtained, the value of the operand is incremented. …… The side effect of updating the stored value of the operand shall occur between the previous and the next sequence point.

也就是说,后自增表达式的结果值就是被自增之前的那个值,然后这个结果值被确定之后,操作数的值会被自增。而这种“自增”的副作用会在上一个“序列点”跟下一个“序列点”之间完成。
本文不打算详细讨论序列点。有兴趣的读者可以阅读一下标准。需要指出的是:赋值运算在C语言中并不是一个序列点,所以,上面的while语句中,src的自增效果无需是在赋值之前完成。但while的整个控制表达式的结束却是一个序列点。

我们可以这样解读“while(*p++=*src++);”:首先while的条件变量是一个赋值表达式,左侧操作数是“*p++”,右侧操作数是“*src++”,整个表达式的值将是赋值完成后左侧项的值。而左右两侧是对两个后自增表达式解引用,由前面的说明可以知道,解引用作用于整个后自增表达式而不仅仅作用于p或src本身,那么根据上面引用的标准,他们“取用”的人别是指针p和src的当前值。而自增的副作用只需要在下一个序列点之前完成即可。

简单地说,编译器分别取得指针p和src的当前值,基于这个值完成“*src”向“*p”的赋值;同时这个赋值结果也将作为整个赋值表达式的值,用来决定是否退出循环。然后,在整个表达式结束时的某一个时刻(在不影响之前叙述的前提下),p和src人别加1。

也就是说,我们基于p和src的旧值所进行赋值和循环条件判断,然后完成p和src的自增。

另外,这里有关于后自增(后自减)运算的另外两种表述,虽然与c语言标准上的说法并不完全一致,但在最终的语义效果如出一辙:

(1)后自增“x++”相当于一个逗号表达式:“tmp=x,++x,tmp”;

(2)后自增就是把操作数加1,然后返回加1之前的值作为整个表达式的值。

这里值得一提的是,在c++语言中需要重载后自增运算符时,往往采用的机制就是基于这两种说法。

再举一个据说还是烂大街的实现:

size_t strlen(const char* str){const char* p = str;while(*p++);return p - str - 1;
}

 

我们发现函数最后有一个减1的操作,这是因为当循环条件不满足而退出循环时,会在“正式”退出之前,后自增运算符“++”加1的副作用。可以这么理解:所谓“退出循环”,指的是“不再执行循环体”,但控制表达式并不是循环体的一部分,它的所有副作用在整个表达式结束之前都会生效。

这一节的最后,重要的事情再说一遍:*p++就是*(p++),两者除了可读性以外没有任何区别。那种认为加上括号就可以实现先加1再解引用的想法是错误的,要想实现那样的效果,可以用“*++p”。

 

[三目元算符“ ? : ”]

先给出一个例子:

int x = 3;
int y = 2;
int z = x > y ? 100 : ++y > 2 ? 20 : 30;

 

我们会关心z的值是多少。

这里是两个三目运算符的嵌套,有“向右结合”的特性。许多人认为基于这个性质,右侧的内层条件运算“++y>2?20:30”应该先求值。即y先加1,大于2的条件成立,从而使这个表达式取得结果“20”;然后求整个表达式的值,这时y的值是3,所以“x>y”为假,故整个结果是刚刚求得的20。

然而事实并不是这样…… 这种思路是错误的!!!

这里的错误在于:把优先级、结合性与求值次序完全混为一谈。

首先,在大多数情况下,c语言对表达式中各个子表达式的求值次序并没有严格的规定;其次,即使是求值次序确定的场合,也是要先确定了表达式的语意结构,在获得确定的语义之后才谈得上“求值次序”。

对于上面的例子,条件运算符“向右结合”这一个特性,并没有决定内层的条件表达式先被求值,而是决定了上面表达式的语意结构等价于“x>y?100:(++y>2?20:30)”,而不是“(x>y?100:++y)>2?20:30”。这才是“向右结合”的真正含义。

编译器确定了表达式的结构之后,就可以准确地为它产生运行时的行为了。条件运算符是c语言中为数不多的对求值次序有着明确规定的运算符之一(另外还有三个,分别是逻辑与“&&”、逻辑或“||”和逗号运算符“,”)。

c语言规定:条件表达式首先对条件部分求值,如果条件部分为真,则对问号之后冒号之前的部分求值(表达式2),并将求得的结果作为整个表达式的值;否则对冒号之后的部分(表达式3)求值并作为整个表达式的值。

因此,对于表达式“x>y?100:(++y>2?20:30)”,首先看x大于y是否成立,在本例中它是成立的,因此整个表达式的值为100。也就是说,表达式3根本就不会被执行,其中包含的自增运算符的副作用也不会生效。

 

[最后再说几句]

本文主要阐述了以下几点:

(1)优先级决定表达式中各种不同的运算符起作用的优先次序,而结合性则在相邻的两个运算符的具有同等优先级时,决定表达式的结合方向;
(2)后自增(后自减)从语义效果上可以理解为在做完自增(自减)之后,返回自增(自减)之前的值作为整个表达式的结果值;
(3)准确来讲,优先级和结合性确定了表达式的语义结构,不能跟求值次序混为一谈。

 

PS.

1、本文参考博文:http://blog.csdn.net/steedhorse/article/details/5903974

2、维基百科上有C/C++语言运算符表:http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B

3、曾在新浪微博上见benbearchen提到有的公司在代码规范中要求:如果while的循环体为空语句,那么必需以continue语句代替,不准只写一个分号。我本人很赞成这个。上面strcpy和strlen的两个例子之所以没那么用,只是为了“随大流”,因为这两个函数的示例实现,许多人、许多书上都这么写。

 

转载于:https://www.cnblogs.com/CQBZOIer-zyy/p/5303741.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/294905.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学计算机所需要的英语单词,学计算机最少要懂的英语单词是什么

PC:个人计算机Personal ComputerCPU:中央处理器Central Processing UnitCPU Fan:中央处理器的“散热器”(Fan)MB:主机板MotherBoardRAM:内存Random Access Memory,以PC-代号划分规格,如PC-133,PC-1066,PC-2700HDD&…

程序内存一直在泄漏,原来是异步死循环了 !

一:背景 1. 讲故事上个月有位朋友找到我,说他的程序出现了内存泄漏,不知道如何进一步分析,截图如下:朋友这段话已经说的非常言简意赅了,那就上 windbg 说话吧。二:Windbg 分析 1. 到底是哪一方面…

每天一个linux命令(1):ls命令

ls命令是linux下最常用的命令。ls命令就是list的缩写缺省下ls用来打印出当前目录的清单如果ls指定其他目录那么就会显示指定目录里的文件及文件夹清单。 通过ls 命令不仅可以查看linux文件夹包含的文件而且可以查看文件权限(包括目录、文件夹、文件权限)查看目录信息…

太巧了!学霸夫妻携手进入武大读博,两人的硕士导师也是一对夫妻

全世界只有3.14 % 的人关注了爆炸吧知识本文募格学术撰写。参考来源:湖北日报、双一流大学网、募格学术此前报道等科研人的爱情是什么样子?或许是在学术的路上他们相识相知,往后也将共同拼搏,在科研领域擦出更多火花~前段时间&…

TOMCAT常用优化

Tomcat的性能,对比Weblogic或者Websphere,自然是差了不少。但是Weblogic或者Websphere的价格都比较昂贵,一些创业级网站恐怕没有能力支付,毕竟钱要用在刀刃上。免费开源的Tomcat,对付一般的网站,还是够用的…

github怎么隐藏自己的pr记录_记便签的软件哪个好?怎么及时记录自己的想法

我们在平时的工作和生活中,如果遇到一些需要及时记下来的东西时,很多人都会选用在便签中记录下来的方式。对于记便签的软件来说,不同的品牌有不同的特点,要想在众多便签软件中选择出一款适合自己的,就需要下点功夫了&a…

bkwin设置文本控件为多行模式

2019独角兽企业重金招聘Python工程师标准>>> 指定textmode&#xff0c; 给到文本控件的区域 <class name"xxxxx" textmode"40A011"/> textmode是DT_FROMT位或值 DrawText api对应的formt 转载于:https://my.oschina.net/u/2436679/bl…

高端智能阿里手机 黑色 ZOPO C2 出售1499

手机在市场上&#xff0c;每个人的注意力放在这些国际品牌三星&#xff0c;HTC 推出的旗舰模型的时间&#xff0c;与一般阿里相结合的内部电话系统最近开展了自主开发的高端智能手机&#xff0c;此名称是卓 Pu 黑色国内高端智能手机的迅速火起来&#xff0c;抓住最佳的国际厂商…

性能测试组件CodeBenchmark V2发布

CodeBenchmark是一款可视化的性能测试组件&#xff0c;通过组件可以对一个或多个功能代码进行一个并发测试&#xff1b;最终通过详细的测试结果来对比不同代码的性能差异。组件的使用非常简单&#xff0c;构建一个控制台程序然后引入BeetleX.CodeBenchmark组件编写几个代码即可…

计算机ftp怎么登陆新用户,多用户登录ftp

第1步:建立虚拟FTP用户数据库文件。第2步:创建FTP根目录及虚拟用户映射的系统用户。第3步:建立支持虚拟用户的PAM认证文件。第4步:在vsftpd.conf文件中添加支持配置。第5步:为虚拟用户设置不同的权限。第6步:重启vsftpd服务&#xff0c;验证实验效果。第1步:建立虚拟FTP用户数据…

一所传闻要被“降级”的211高校,让这位网红教授“救活了”

全世界只有3.14 % 的人关注了爆炸吧知识本文来源&#xff1a;量子位&#xff08; ID: QbitAI&#xff09; 作者&#xff1a;金磊 发自 凹非寺太原理工大学&#xff0c;最近着实有点火。先是11月12日至13日&#xff0c;其官网一口气更新了3位「杰青」副校长&#xff0c;在高校任…

加载elementor时出现问题_不锈钢管在焊接时出现问题要怎么解决?

佛山不锈钢装饰管焊接时会出现各种问题&#xff0c;今天佛山不锈钢装饰管厂家喜有沃小编就简单的整理了一些常见问题及解决方法&#xff0c;希望能对大家有所帮助。佛山不锈钢装饰管焊接制作护栏1&#xff0c; 表面气孔佛山不锈钢装饰管在焊接时产生表面气孔的原因一般为使用了…

Android之玩转MPAndroidChart让(折线图、柱形图、饼状图、散列图、雷达图)优雅的舞动

第一步:不废话,先爆照 我的github地址:https://github.com/changechenyu/MPAndroidChartTest 第二步:介绍MPAndroidChart适用场景并把它的库文件导入我们开发的项目 介绍: MPAndroidChart是一款基于Android的开源图表库,MPAndroidChart不仅可以在Android设备上绘制各种…

在PowerDesigner中设计物理模型1——表和主外键

在PD中建立物理模型由以下几种办法&#xff1a; 直接新建物理模型。设计好概念模型&#xff0c;然后由概念模型生成物理模型。设计好逻辑模型&#xff0c;然后由逻辑模型生成物理模型。使用逆向工程的方法&#xff0c;连接到现有的数据库&#xff0c;由数据库生成物理模型。物理…

.NET 6新特性试用 | 无需配置开发人员异常页

前言在.NET 6之前&#xff0c;我们需要在“Startup.cs”文件中手工配置开发人员异常页&#xff1a;if (env.IsDevelopment()) {app.UseDeveloperExceptionPage();app.UseSwagger();app.UseSwaggerUI(c > c.SwaggerEndpoint("/swagger/v1/swagger.json", "Web…

.NET平台下几种SOCKET模型的简要性能供参考

.NET平台下几种SOCKET模型的简要性能供参考 这个内容在cnblogs中也讨论过很多次了&#xff0c;这两天大概看了一些资料&#xff0c;看到一些简单的性能指标拿出来和大家讨论一下。 Socket Threads/ThreadPool 大概性能&#xff1a;小于1500个连接 实现&#xff1a;Accept一个…

SQL中truncate table和delete的区别 --转

内容: http://www.cnblogs.com/GT_Andy/archive/2010/01/28/1921871.html 感谢博主的分享!!!转载于:https://www.cnblogs.com/ry123/archive/2012/10/23/2735297.html

html5中表格如何等分,纯css3饼图五等分

先看效果图&#xff1a;HTML代码如下&#xff1a;pie良好优秀未提交需努力加油98%得分率css代码如下&#xff1a;.pinOfStudent{background-color: #ffffff;width: 100%;position: relative;}#tipZone{position:relative;left:0;right:0;top: 1em;width:12.5em;height:12.5em;m…

Android之开源框架NineOldAndroids动画库

1.介绍 Android3.0推出了全新的AnimationAPI&#xff0c;使用起来很方便&#xff0c;但是不能在3.0以下版本使用&#xff0c;NineOldAndroids是一个可以在任意Android版本上使用的AnimationAPI&#xff0c;API和Android3.0中的类似。 2.常用类 ObjectAnimator ValueAnimator A…

python keyerror not in index_python – 带有索引的Pandas Plot导致’KeyError []不在索引中...

我是Python中Pandas概念的新手.通常情节不是问题.但是,我现在面临的是包含索引的数据框.不知何故什么都没有了.我想要实现的目标&#xff1a;为每个列[Plant1,Plant2,Plant3]创建一个特定柱[Trafo1]的子图.这是我的代码&#xff1a;import numpy as npimport datetimeimport nu…