zoukankan      html  css  js  c++  java
  • 关于“算术表达式”、“优先级”和“结合性”的胡扯

    3.算术表达式和运算符的优先级和结合性
          用算术运算符和括号将运算对象(也称操作数)连接起来的、符合C语法规则的式子,
    称为C算术表达式。运算对象包括常量、变量、函数等。例如,下面是一个合法的C算术表
    达式:
          a*b/c-1.5+'a'
          C语言除了规定了运算符的优先级外,还规定了运算符的结合性。在表达式求值时,先
    按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-b*c,b的左侧为减号,右
    侧为乘号,而乘号的优先级高于减号,因此,相当于a-(b*c)。
          如果在一个运算对象两侧的运算符的优先级别相同,如a-b+c,则按规定的“结合方
    向”处理。C语言规定了各种运算符的结合方向(结合性),算术运算符的结合方向都是“自
    左至右”,即先左后右,因此b先与减号相结合,执行a-b的运算,然后再执行加c的运算。
    “自左至右的结合方向”又称“左结合性”,即运算对象先与左面的运算符结合。以后可以看
    到有些运算符的结合方向为“自右至左”,即右结合性(例如,赋值运算符,若有a=b=c,按
    从右到左的顺序,先把变量c的值赋给变量b,然后变量b的值赋给a)。关于“结合性”
    的概念在其他一些高级语言中是没有的,是C语言的特点之一,希望能弄清楚。附录D列
    出了所有运算符以及它们的优先级别和结合性。

    ————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p54

    =============================================================

        首先,来看一下引用部分小标题里的第一个词——“算术表达式”。
        这个词很给人一种“亲切感”特别有迷惑力,然而它确是一个模糊的、似是而非而且毫无用处的概念。
        据我所知,这个词是老谭自创的。C语言中并没有这样的概念。
        C语言中只有算术类型(arithmetic types)和算术运算符(arithmetic operators)这样的概念,并没有“算术表达式”这种概念。
        没有这样的概念,难道不可以自己创造概念吗?当然可以。但必须知道的是,创造概念是有前提的:

    创造者要给出概念的定义;

    概念要科学严谨;

    这个概念有用,或者方便简洁地描述了一个道理,或者帮助别人认识了一类现象或规律。

        这样才可以创造新概念。
        不满足这三个前提,自创概念不是吃饱了撑的就是假行家故弄玄虚的蒙人行为。考察一下“算术表达式”这个概念。
        作者给出了定义:“用算术运算符和括号将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C算术表达式
        (很好,老谭自创概念不给定义的例子比比皆是,这次很有进步)
        然而,这个概念并不科学,也不严谨。为什么这么说呢?简单地考察一下下面的表达式就会知道了:
            1+(2+3)
        在这里,如果把“+”说成是“连接”“操作数”还是勉强说得过去的,但是“()”的意义则绝对不是为了“连接”“操作数”。“()”的意义是为了表明其内部的子表达式作为一个整体——仿佛一个独立的操作数参与运算,这与“连接”是八竿子打不着的。再比如“(1)”这个表达式中,“()”连接了什么呢?其实它什么也没连接,它只表明它与其扩起来的部分是一个完整的整体而已。
        所以说,这里的“()”是一种“界定”(to delimit)范围的符号,把它与表示运算的运算符并列在一起是很荒唐的。
        作者接着补充道:“运算对象包括常量、变量、函数等”。
        这就让人迷惑不解了,这里的“函数”究竟是指的什么呢?是“函数调用”还是“函数名”本身呢?如果是“函数调用”,就成了自己打自己耳光,因为函数调用一定是通过函数调用运算符“()”实现的(注意这里这个"()"不是括号),这和作者在前面说的“用算术运算符和括号将运算对象(也称操作数)连接起来的”自相矛盾。如果这里的所谓“函数”是“函数名”的话,众所周知,“函数名”这种数据类型怎么可能进行算术运算呢?
        所以必须感谢作者的及时补充,他成功地使用“函数”这个词戳穿了他自己创造的伪概念——“算术表达式”,这个概念是经不起推敲的。
        紧接着的“例如”也很成问题。权且承认他给出“a*b/c-1.5+'a' ”是合法的,但是这个表达式即不是“用算术运算符和括号将运算对象(也称操作数)连接起来的”(因为没有括号),“运算对象”也不“包括常量、变量、函数”(“函数”在哪?),作者举例到底想说明什么呢?无厘头么!
        或许,有人很不以为然:“括号”和“函数”都是随手写的,不算大错。好,至少现在你已经承认这书很不严谨了。问题是我没提到这些错误之前,你发现这些错误了吗?就算你也和我一样发现了这些错误,那些把这本书作为教材的初学者怎么可能发现这些错误呢?你老人家胡写一通不要紧,你让那些初学者情何以堪呢?
        又或许有人觉得这段文字只要像下面那样修改一下就可以了

       “用算术运算符和将运算对象(也称操作数)连接起来的、符合C语法规则的式子,称为C算术表达式。运算对象包括常量、变量等。例如,下面是一个合法的C算术表达式:
          a*b/c-1.5+'a'  ”

        Sound good!这段文字似乎基本没什么问题了。然而我们还是要问,这个自创的概念有什么用吗?继续往下看。下面一段文字是
       “C语言除了规定了运算符的优先级外,还规定了运算符的结合性。在表达式求值时,先按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-b*c,b的左侧为减号,右侧为乘号,而乘号的优先级高于减号,因此,相当于a-(b*c)。
         我在读这一段第一句话的时候被骇了一大跳。这句型,简直太惊人了。前面压根没提过“优先级”,抽冷子就来了一句“C语言除了规定了运算符的优先级外,……”,有这么说话的吗?如果连话都说不利索,但却非要写在教科书里,您老人家又成天把这个“1100万”挂在嘴上,您说这和一只著名的苍蝇成天炫耀自己污染过很多食物有区别吗?     
         也许有人觉得这只是语文问题,不是C语言方面的问题。好吧,我让步!问题是这段文字和前面作者自创的“算术表达式”没有任何关系,没有人能否认这一点吧?
         再看引用文字的最后一段。这段文字同样和“算术表达式”这个伪概念没有任何关系。唯一可能牵强地和“算术表达式”扯上一点关系的是“算术运算符的结合方向都是‘自左至右’”,然而这句话本身就是错误的,因为作者在书中的第52页明确地说明一元“+”、“-”都是算术运算符,然而我们都知道一元“+”、“-”的结合性是“自右至左”,更何况“算术运算符”和“算术表达式”分明就是两个不同的概念。
         而且,优先级和结合性的规律是对所有运算符而言的,算术运算符并没有什么特殊的优先级和结合性规律;而且所谓的“算术表达式”在这方面也没有任何特殊性而言,创造“算术表达式”比画蛇添足还要无聊。不仅无聊,而且有害。这个概念甚至无法比喻成懒婆娘的裹脚布,最多只能称得上是懒婆娘丢弃的裹脚布而已。
         问题出来了,谭大爷为什么要自创这个不伦不类而且漏洞百出的伪概念——“算术表达式”,他到底想说什么?我估计他自己也不清楚。
        话说到这里,我想我已经证明了这段引文的“胡扯性”。但是,根据我的经验,这时候一定会有无耻之徒跳出来无赖地狡辩:“毕竟还介绍了优先级和结合性么”——没办法不让他们跳出来,“卑鄙是卑鄙者的通行证”。然而,在这里,即使是这种最无耻的狡辩也是不可能得逞的,因为谭书中对“优先级”和“结合性”的讲解也是错的!
        关于优先级,作者是这样写的:
        “在表达式求值时,先按运算符的优先级别顺序执行,例如先乘除后加减。如表达式a-b*c,b的左侧为减号,右侧为乘号,而乘号的优先级高于减号,因此,相当于a-(b*c)。
        这段文字的错误在于作者把运算符的优先级和表达式求值的执行次序混为了一谈。尤其是“先乘除后加减”更是对优先级的曲解。实际上,优先级和表达式求值的执行次序基本上是不相干的两回事情。
        优先级的意义在于表明表达式的含义(结合性也是),而求值的执行次序则是编译器的自选动作。只要不违反表达式的含义,编译器可以按照自己的爱好安排求值次序,编译器也没有义务告诉你它是按照什么次序求值的。这种自选动作叫implementation-defined behavior。
        举例来说,对于表达式“ 1+2-3*4 ” ,“*”的优先级高于“-”,其意义在于表明减去的是(3*4)而不是减3。只要你清楚地表明了这一点,你的任务就完成了。至于计算次序,编译器至少有两种选择:
        1)  1+2-3*4  => 1+2-12 => 3-12 => -9   
        2)  1+2-3*4  => 3-3*4   => 3-12 => -9
        按照外交部的惯用句型来说就是,怎么选择求值次序是编译器自己的事,程序员无权干涉(甚至无权了解,好奇也不行,编译器没义务告诉你)。 程序员的责任在于把表达式的含义写清楚,写正确;编译器的义务仅仅在于给出正确的结果而已。至于算出结果的过程(次序),对不起,关您程序员什么事啊?您管的也太宽了吧。
        总之,“优先级”的意思就是优先级高的运算符先选“对象”——运算对象。谭大爷明显是不明白这点小道理,可笑地把优先级和求值次序“绑定”到了一起,一相情愿地拉郎配。
        如果优先级高的运算符选完了“对象”,剩下的运算符优先级相同,怎么办呢?
        答案也简单,按照结合性挑选“对象”,如果是从左到右的结合方向,就左边的先挑,依次向右进行;如果是从右到左,则顺序相反。例如:
        1+2-3*4
        由于“*”的优先级最高,所以先挑运算对象,表达式的含义为
        1+2-(3*4)
        剩下的两个运算符“+”和“-”的优先级相同,所以看结合性,这两个运算符的结合性是从左到右,因此左面的先挑,表达式的含义可以进一步明确为
        (1+2) - (3*4)
        最后,可以确定“-”的运算对象分别为 (1+2) 和 (3*4)。
        这就是优先级和结合性的全部含义。如此而已,非常简单。再来看看老谭是怎么讲的。
        “如果在一个运算对象两侧的运算符的优先级别相同”,看到这句我差点没吐出来。老谭居然给来了个“如果在”“运算对象”“两侧”的设问,这跟优先级、结合性有什么关系吗?如果运算符在运算对象的同一侧怎么办?比如 - - i (不是"--",两个“-”之间有空格),这应该怎么办呢?再比如 a[3],那个3应该算是在运算符的哪一侧呢?
        所以看老谭的书,如果你不能发现他的愚蠢,那就只剩下了两种可能:1.你自己愚蠢;2.你正在变得愚蠢。
        再往下看:
        “算术运算符的结合方向都是'自左至右”,即先左后右'”,这句话本身就是错误的,前面已经提到过。
        “因此b先与减号相结合”,这句更可笑,纯粹是望文生义式的妄断臆测。老谭把左结合性理解成了“运算对象先与左面的运算符结合”,荒唐透顶。结合性,即使从字面上来说,也不是简单地指操作数与左面或右面的运算符相“结合”。简单举个例子就说明老谭的荒谬,"[]"运算符的结合性是从左至右,那么表达式
    a [ b[0] ] 中 ,无论是a或是b,怎么与左面的运算符结合?所以“运算对象先与左面的运算符结合”这样的昏话绝非是荒谬,而是荒谬绝伦。
        “即右结合性(例如,赋值运算符,若有a=b=c,按从右到左的顺序,先把变量c的值赋给变量b,然后变量b的值赋给a)。”,这里也是错的。根据结合性,a=b=c 的含义是 a=(b=c) ,也就是说是把b=c的值赋给a,而不是老谭所说的“然后变量b的值赋给a”。
        “关于“结合性”的概念在其他一些高级语言中是没有的,是C语言的特点之一”,这是井底之蛙坐井观天式思考的结果,根本不用反驳。
        “希望能弄清楚”,谢谢!“希望”很好,问题是您老人家自己弄清楚没有啊?您总这么瞎忽悠,让学习者怎么弄清楚啊?以您的“昏昏”让学习者“昭昭”?
        “附录D列出了所有运算符以及它们的优先级别和结合性。”,第一,附录D并没有列出“所有”的运算符,第二,列出的部分不少是错误的。

         实事求是地讲,谭书这段中也并非一无是处。 “C语言除了规定了运算符的优先级外,还规定了运算符的结合性”这句,尽管从文法上来说突兀怪异、不伦不类,但表述的内容却的确是事实。为对老谭公平起见,我必须指出这点。

  • 相关阅读:
    golang删除数组某个元素
    golang用通道实现信号量,控制并发个数
    什么是ScaleIO中的forwards rebuild和backwards rebuild?
    SQL Server中的database checkpoint
    如何将thick provision lazy zeroed的VMDK文件转换为thick provision eager zeroed?
    LoadTestAgentResultsLateException in VS2010
    SQL Server Instance无法启动了, 因为TempDB所在的分区没有了, 怎么办?
    VMware vCenter中, 如何辩认虚机上Raw Device Mapping过了的一块物理磁盘?
    SQL Server AlwaysOn Setup Step-By-Step Guide
    TPC-E在populate测试Database时需要注意的一些事项
  • 原文地址:https://www.cnblogs.com/pmer/p/1955694.html
Copyright © 2011-2022 走看看