C语言++表达式的求解探讨

来源 :电脑知识与技术 | 被引量 : 0次 | 上传用户:jxy_su261314
下载到本地 , 更方便阅读
声明 : 本文档内容版权归属内容提供方 , 如果您对本文有版权争议 , 可与客服联系进行内容授权或下架
论文部分内容阅读
  摘要:C语言中的++运算符是一种特殊的运算符,其特殊性在于一个运算符包含两个操作,不同的编译器对这两个操作在复合表达式中的处理机制不同,而同一个编译器对其在程序中的不同位置也有不同的处理。教材中仅仅对++运算符作了最简单的介绍,并没有对++运算的左连接和右连接同时参与运算时的处理规则作介绍,本文对++运算在不同情况下的求解进行了深入研究,得出对++运算符的运算要根据一定的程序环境和编译器环境而定。
  关键词:C语言;表达式;++运算符
  中图分类号:TP301 文献标识码:A文章编号:1009-3044(2007)06-11675-02
  
  1 引言
  C语言中的++运算符是一种特殊的运算符,++运算主要用来代替循环结构程序设计中步长为1的循环变量的增值运算,即对于i=i+1,在C语言中可以用i++或者++i简化表示,因此,大多数教材中将++运算符也称作自增或者自加运算符。但是,i++或者++i运算并不完全等价于i=i+1,它是C语言中用一个运算符号表示两步不同操作的符号。C语言程序设计的教材中都是简单介绍它的基本运算,并没有深入讲解++运算在复合表达式中的求解。因此,在具体的表达式求解中经常出错,究其原因不外乎这两种情况:一是与表达式在程序中的位置有关(将其称之为软件相关性),二是不同的编译器对++运算在复合表达式中的处理机制有关(将其称之为硬件相关性)。下面我们通过对具有代表性的表达式的计算来说明++运算的求解。
  在介绍++运算的时候,经常以赋值表达式j=(i++)+(i++)+(i++)或者j=(++i)+(++i)+(++i)作为例子来讲解其运算规则,在《C程序设计》(谭浩强主编)一书中也对其求解过程作了详细介绍,但是,如果将上面赋值表达式的++表达式作一变换,如j=(i++)+(++i)+(i++)或者j=(++i)+(i++)+(++i),书中并没有给出求解过程。
  甚至在输出函数中的这两种形式:
  在《C程序设计》(王柏盛主编)一书中作者给出“在教学和科研中发现Tubro C在表达式中存在严重的不一致性,这实在是一个遗憾”。那么对于++表达式的复合运算究竟该如何求解呢?
  随着计算机编程语言功能的不断完善和强大,C语言编译器出现了多种不同版本,由于面向对象以及可视化编程环境的影响,一些《C程序设计》也开始使用Microsoft VC++ 6.0或者Dev C++编译器作为C源程序的编程环境。那么在不同的编程环境中上面表达式的求解结果是否一样?经过在程序中编译运行,笔者发现在不同的编译器中求解的结果有所差异,现将同一个表达式在Tubro C 3.0环境下在程序中不同位置的运算结果作一比较。如下表所示。
  说明:
  (1)作为表达式是指++表达式在程序中作为单独的一条赋值表达式,程序如下:
  (2)作为printf参数是指++表达式在程序中作为输出函数的输出列表参数,程序如下:
  从表格中可以看出,同一个++表达式在Tubro C 3.0的编程环境中作为不同的成分得到的结果是不尽相同的,那么如何解释这种现象呢?下面我们对不同的结果进行分析。
  
  2 单个++运算在表达式中的求解
  我们知道,++运算在C语言中规定为优先级是第2级的单目运算符,而且++运算符要求只能用于单变量,不能用于常量或表达式,++运算符与单变量的结合方式有两种:i++和++i,分别成为左连接和右连接。只有一个++运算符的操作分为两步,即:
  左连接的第二步与右连接的第一步被称为自增或自加运算。在表达式中不论那种形式,只有单个i能直接参与整个表达式的运算,而i=i+1仅仅对i的值产生影响,并不直接参与整个表达式的运算。因此对于赋值表达式,j=i++其运算分为 ,即先取出变量i的原值赋值给变量j,然后使i的值变为加1后的值,即实现i的自增;但是对于赋值表达式j=i++,其运算分为,即先使i的值变为加1后的值,即实现i的自增,然后才取出i变化后的值赋值给变量j。
  单个++表达式的求解比较简单,i的自增对整个赋值表达式的影响也较明显,但是如果一个表达式中既包含左连接也包含右连接,那么整个表达式的求解就会变得复杂。
  
  3 Tubro C 3.0环境下对++运算表达式的求解
  经过对不同的++表达式在程序的不同位置得到的结果进行分析,我发现造成结果不同的原因在于编译器对++表达式作为不同成分的两步运算的处理顺序不同造成的。
  3.1 第一步优先法
  对于包含有多个++表达式,在特定的编译器(如Tubro C 3.0),对任何一个++表达式的两步运算严格按照第一步比第二步优先处理,我将其成为“第一步优先法”或者“全局处理法”。即编译器对第二个++表达式的第一步处理比第一个++表达式的第二步处理优先。
  现在我们对上表中的++表达式在不同环境和程序中的不同成分利用表格法进行求解分析。假设我们将++运算的两步用一个2行1列的表格表示,并按照顺序将第一步放在第一行,第二步放在第二行。
  如果i的原值为3,那么j=(i++)+(i++)+(i++)的值为多少。
  分析:首先将赋值表达式j=(i++)+(i++)+(i++)表示成如下形式:
  对于这样的一个赋值表达式,j的值实际上是三个i的值相加而得到。但是对于用表格法表示的式子中第二个和第三个i的值该如何取值呢?根据在Tubro C3.0环境下++运算作为表达式的运行结果来看,i=3时,j=9。那么最好的解释就是编译器首先扫描赋值表达式,在对整个表达式求解一开始时先执行每个++运算的第一步,即先把每个i的原值(3)取出来,作为整个表达式中i的值,相加得9。然后再实现i的自增,i最后得6。因此,对程序1中的++表达式的处理实际上是按照下图中123456的顺序执行的。
  同样,对于j=(++i)+(++i)+(++i),我们也可以把它表示成如下形式:
  整个赋值表达式还是求三个变量i相加的和。但是从表中来看j=18,若i的原值为3,i每次只能加1,那么要想得到18,从3开始怎么也不可能得到,对于这个整个赋值表达式只能是三个6相加得到,即编译器在处理整个表达式时,先对i执行三次自增得6,然后进行j=6+6+6的运算,故得18。
  上面这两个表达式都是由三个相同格式的++表达式组成的,其结果比较容易理解,我们也可以理解成“第一步优先法”,即j=(i++)+(i++)+(i++)中第二个i++的第一步比第一个i++的第二步先运算。问题是如果整个表达式是由多个不同的表达式组成,那整个表达式的结果该如何求解?如j=(i++)+(++i)+(i++)或者j=(++i)+(i++)+(++i)。我们先来看在不同的环境得到的结果分别为j=12和j=15。对于用表格表示的表达式形式:
  同样在Tubro C 3.0环境下i=3,编译器先扫描整个表达式,按照“第一步优先法”,可以看出第二个++表达式的第一步并不是取i的原值,而是要对i进行一次自增运算,因此,按照+(加法)运算符的结合性为自左至右的结合方向,第一个++表达式的i取值为3,第三个++表达式的i就应该取4,这是因为第二个++表达式的i已经进行了一次自增运算,最后对第二行扫描时,第一个++表达式的i再进行一次自增运算,使得i的值变为5,这样第二个++表达式的i取值就为5,加上第三个++表达式的i最后进行一次自增运算,i的值最终为6。那么整个表达式的结果j=3+4+5=12。同理,对j=(++i)+(i++)+(++i)进行“第一步优先法”分析,j=4+5+6=15。
  经过对其他形式的赋值表达式进行“第一步优先法”分析,都能得到对应的结果。这说明在《C程序设计》(谭浩强主编)一书中对++复合表达式是以Tubro C 2.0/3.0为编译器并按照“第一步优先法”求解的。
  3.2 第二步优先法
  对于包含有多个++表达式,在特定的编译器(如Tubro C 3.0),对扫描到的++表达式按照顺序依次进行两步运算处理,我将其成为“第二步优先法”或者“局部处理法”。即编译器对第一个++表达式的第二步处理比第二个++表达式的第一步处理优先。
  下面我们来看j=(i++)+(++i)+(i++)的求解过程。
  分析:当j=(i++)+(++i)+(i++)作为printf()函数的参数时,编译器扫描整个表达式,并对扫描到得每个++表达式按照“第二步优先法”依次进行处理,即:对程序2中的++表达式的处理实际上是按照上图中所示123456的顺序执行。
  ①先取出i的原值3;
  ②执行i=i+1,i的值变为4;
  ③执行i=i+1,i的值变为5;
  ④取出i的值5;
  ⑤取出i的值5;
  ⑥执行i=i+1,i的值变为6。
  最后执行j=3+5+5=13。
  经过对其他表达式作为printf()函数的参数进行分析,均能得到相应的结果。
  因此,在Tubro C 2.0/3.0环境下,对++运算的处理是与编译器软件的编写有关,编译器对含有++运算符的表达式进行整体扫描,并将整个表达式的操作数和运算符按照一定的原理压入堆栈,最后求得整个表达式的结果。
  
  4 Microsoft VC++ 6.0环境下对++运算表达式的求解
  由于Tubro C 2.0/3.0是基于DOS系统下的编译环境,随着计算机的不断发展,DOS系统已经被整合到Windows操作系统中,DOS命令也不再被讲授,同时,面向对象和可视化编译系统的出现,使得现在的《C程序设计》也开始使用高版本的编译器(如Microsoft VC++ 6.0或者Dev C++)。那么对上表中的++表达式是否在Microsoft VC++ 6.0或者Dev C++也能得到相同的结果呢?下面我将同一个表达式在Microsoft VC++ 6.0环境下在程序中不同位置的运算结果作一比较。如下表所示。
  从表中可以看出,在Microsoft VC++ 6.0环境下不管作为表达式还是作为printf()参数,得到的结果是相同的,这说明在Microsoft VC++ 6.0环境下对++运算的处理是统一的,但是与Tubro C 2.0/3.0环境下的结果相比却出现一些差异。
  造成上述结果出现差异的原因我认为Microsoft VC++ 6.0编译器并没有对表达式进行求值处理,而是将其交给操作系统的硬件加法器。下面我们来看一个++表达式在Microsoft VC++ 6.0中是如何处理的。
  若i=3,则j=(++i)+(i++)+(++i)的结果是多少?
  用表格法将该赋值表达式表示成下面的形式。
  从表中可以看出j=13,实际上j等于三个i相加的结果,而已知i的初值为3,怎么才能得到13呢?经过研究发现j只能是对i分别取4,4和5相加得到的,下面对其求解决过程进行说明。
  “i+1优先法”:在硬件加法器所要求存取的操作数(如按照加法运算符要求两个)中,若对存取的i之前有自增运算则先对所有的自增运算进行处理,然后再存取所有i的值;若对存取的i之前没有任何一步自增运算,则直接存取所有i的值,并将对应++运算的自增操作放在整个表达式求解结束后再进行。
  编译器将整个表达式的求解交给硬件加法器,硬件加法器根据+(加法)运算规则首先取硬件规定的操作数(如两个),因为最终参与整个表达式运算的变量是i,所以,硬件加法器扫描前两个++运算,并结合“i+1优先法”,对整个表达式进行运算。
  根据硬件加法器原理将运算的结果并入一个存储器中,继续取下一个操作数,按照“i+1优先法”依次取下去,直到运算结束。所以上面的赋值表达式就可以理解为:首先扫描要求的操作数,按照“i+1优先法”,发现在存取第一个++运算的i值前有自增运算,所以先进行i自增运算得4,然后取出i的值4,这时对于第二个++运算,自增运算是在第二步,并不影响i的取值,因此i直接取值4,并将自增运算放在最后求解;根据硬件加法器,求解完前两个变量的和以后,继续取操作数,对于第三个++运算,第一步为自增运算,所以先进行i自增运算得5,然后取出i的值5参与第二次求和运算,得j的值为4+4+5=13。因此,对上面赋值表达式的求解可以认为按照上图中所示123564的顺序执行的。再比如,对j=(i++)+(++i)+(i++)的求解按照下图中的314526的顺序。
  经过对其他赋值表达式进行上述分析,均得到相应的结果。
  
  5 结束语
  因此,在Microsoft VC++ 6.0环境下,对++运算的处理是与操作系统的硬件加法器有关,并结合“i+1优先法”,如果存取的操作数中第一步既有取i值的操作,又有改变i值的自增操作(如j=(i++)+(++i)+(i++) 或者j=(++i)+(i++)+(++i),或者存取的操作数中都要求先对i进行自增操作(如j=(++i)+(++i)+(++i),则优先进行第一步改变i值的自增操作,然后再进行取i值的操作,并且取i值时不分顺序,但将第二步中改变i值的自增操作放在整个表达式的值求解完以后再进行;如果对存取的操作数中都要求先对i进行取值操作(如j=(i++)+(i++)+(i++)),则直接取i的值参与对整个表达式求值的运算,并把改变i值的自增操作放在整个表达式的值求解完以后再进行。
  参考文献:
  [1] 谭浩强. C程序设计[M]. 清华大学出版社,1998.3.
  [2] 蒋春蕾,等. C语言中的函数应用时易出现的错误[J]. 西昌学院学报(自然科学版),2005.9.
  [3] 王柏盛. C程序设计[M]. 高等教育出版社, 2005.1.
  本文中所涉及到的图表、注解、公式等内容请以PDF格式阅读原文。
其他文献
摘要:关于窗体的卸载往往被程序设计者尤其是一些初学者所忽视。本文从事件Queryunload和Unload参数的作用及事件的使用两方面对事件Queryunload和Unload进行了详细的探讨。并且给出了两个比较重要的观点。  关键词:Queryunload;Unload;参数  中图分类号:TP311文献标识码:A 文章编号:1009-3044(2007)06-11665-01    1 引言 
期刊
摘要:文章主要介绍一种简易通用的UART IP核的设计。UART作为一种短距离、低成本通信的串行传输接口,随着嵌入式系统的迅速发展,已成为SoC(System on Chip)芯片中的一个重要部件,在数字通信中得到了广泛的应用。本设计在对UART的串行通信协议进行详细分析的基础上,采用Verilog HDL语言对ALTERA的Cyclone系列FPGA进行设计,用一片FPGA实现了UART的发送、
期刊
摘要:通过对气象信息综合分析处理系统(MICAPS)中第十三类数据格式和Windows系统的BMP位图格式的结构进行分析研究,将只能在MICAPS中显示的卫星云图转换为位图图像,从而扩展了卫星运图的查看方式,方便了工作。  关键词:MICAPS;位图;数据格式;转换  中图分类号:TP311文献标识码:A文章编号:1009-3044(2007)06-11603-01    1 引言  MICAPS
期刊
摘要:首先介绍了组件技术和框架技术的思想和设计原则,并在此基础上提出了设计Agent结构应该遵循的原则;依照文中所述的各项原则,提出了一种基于组件的Agent框架,并对框架中的组件及工作原理进行了详细地描述。  关键词:框架;组件;Agent结构模型  中图分类号:TP311文献标识码:A文章编号:1009-3044(2007)06-11637-03    1 引言  多Agent系统(MAS)能
期刊
摘要:μC/OS-II是一个基于抢占式的实时多任务内核,可固化、可剪裁、具有高稳定性和可靠性,μC/OS-II作为一个操作系统内核,它没有设备驱动程序管理部分。文章着重阐述了如何为μC/OS-II添加设备驱动程序管理模块,并给出了在NEC PD78F0376 平台下串口驱动程序的范例。  关键词: μC/OS-II;驱动设备管理;串口驱动  中图分类号:TP316文献标识码:A文章编号:1009-
期刊
摘要:当前基于P2P模式的开发与应用引起人们的广泛关注,JXTA是SUN公司推出的,提供了一个构建跨平台、跨操作系统和跨编程语言的P2P网络应用程序的通用平台。文章介绍了JXTA的功能,详细分析JXTA平台的环境配置,及其基础上的HelloWorld程序的运行。  关键词:JXTA;P2P;JXTA configurator tool  中图分类号:TP393 文献标识码:A文章编号:1009-3
期刊
摘要:J2EE是目前流行且成熟的分布式应用开发模型,结合J2EE在开发分布式应用方面的优势,设计了一种基于J2EE平台的电子邮件系统,并利用JavaMail强大的邮件处理功能,对系统进行了实现。  关键词:电子邮件;J2EE;JavaMail  中图分类号:TP311文献标识码:A文章编号:1009-3044(2007)06-11647-02    1 引言  电子邮件是Internet上应用最广
期刊
摘要:通过对低端FPGA的设计,完成数据量不大的光口通信功能;并对传输中的协议进行了说明,实现了数据的有效传输。  关键词:FPGA;SPARTAN3E;光通信  中图分类号:TP311 文献标识码:A文章编号:1009-3044(2007)06-11669-02    1 引言  当今,在电子设计领域,FPGA因为其强大的功能、低功耗、高速并行处理等优势越来越受到广大设计人员的青睐。无论在工业控
期刊
摘要:文章对网络图形建模语言(Java 3D)进行了研究,通过Java 3D构建三维世界,并在网页上显示三维图像,用Java 3D的图形技术,把转换形成的图形格式直接用于仿真之中,不需要利用编程方式进行建模。使得数控加工仿真技术达到缩短加工编程的周期、提高生产效率、降低生产成本的意义。  关键词:Java 3D;三维绘图;数控仿真;网页  中图分类号:TP391 文献标记码:A 文章编号:1009
期刊
摘要:本文探讨利用Matlab来解决高等数学中的二维图形问题,并对其中的初等函数、极坐标、统计图进行实例分析,对于这些很难用手工绘制的图形,利用Matlab则很轻易地解决。  关键词:Matlab;高等数学;二维图形  中图分类号:TP319文献标识码:A文章编号:1009-3044(2007)06-11677-01    1 引言  Matlab是将计算、可视化和编程功能集成在非常便于使用的环境
期刊