《C++覆辙录》——2.3:(运算符)优先级问题

2.3:(运算符)优先级问题

本条款不讨论到底是伯爵夫人还是男爵夫人该在晚宴时坐在大使的旁座(此问题无解)。我们要讨论的是在C++语言中的多层级化的运算符优先级如何带来一些令人困扰的问题。

优先级和结合性
在一种程序设计语言中引入不同层级的运算符优先级通常来说是好事一桩,因为这样就可以不必使用多余的、分散注意力的括号而能把复杂表达式简化。(但是请注意,在复杂的或是比较晦涩的、亦即并非所有代码读者都能很好理解的表达式中显式地加上括号以表明意义,这是正确的想法。当然了,在那些平凡的、众人皆知的情况下一般来说还是不加不必要的括号反而最让人觉得清楚。)

a = a + b * c;
在上面的表达式中,我们知道乘法运算符具有最高的优先级,或者说最高的绑定强度,所以我们先执行那个乘法操作。赋值运算符的优先级是最低的,所以我们最后做赋值操作。

b = a = a + b + c;
这种情况下,我们知道加法操作会比赋值操作先执行,因为加法运算符的优先级比赋值运算符的优先级要高。但是哪个加法会先执行,又是哪个赋值会先执行呢?这就迫使我们去考察运算符的结合性了。在C++语言中,一个运算符要么是左结合的,要么是右结合的。一个左结合的运算符,比如加法运算符,会首先绑定它左边的那个实参。是故,我们先算出a、b之和,然后才把它加到c上去。

赋值运算符是右结合的,所以我们首先把a+b+c的结果赋给a,然后才把a的值赋给b。有些语言里有非结合的运算符:如果@是一个非结合的运算符,那么形如a@b@c的表达式就是不合法的。合情起见,C++语言里没有非结合的运算符。

优先级带来的问题
iostream库的设计初衷是允许软件工程师使用尽可能少的括号:

cout << "a+b=" << a+b << endl;
加法运算符的优先级比左移位运算符要高,所以我们的解析过程是符合期望的:a+b先被评估求值,然后结果被发送给了cout

cout << a ? f() : g();
这里,C++语言中唯一的三目运算符给我们惹了麻烦,但不是因为它是三目运算符的关系,而是因为它的优先级比左移运算符要低。所以,照编译器的理解,我们是产生了执行代码让cout左移a位,然后把这个结果用作该三目运算符所需的一个判别表达式。可悲的是,这段代码居然是完全合法的!(一个像cout这样的输出流对象有一个隐式型别转换运算符operator void ,它能够隐式地把cout << a的计算结果转型为一个void 型别的指针值。而根据这个指针值为空与否,它又可以被转型为一个truefalse。)10这是一个我们非加括号不可的情况:

cout << (a? f() :g());
如果你想被别人觉得精神方面无懈可击,你还可以再进一步:

if (a)
  cout << f();
else
  cout << g();```
这种写法也许不如前一种写法那么令人浮想联翩,但是它的确有着又清楚、又容易维护的优点。

很少有采用C++语言的软件工程师会在处理指涉到classes的指针时遭遇运算符优先级带来的问题,因为大家都知道`operator ->`和运算符.具有非常高的优先级。是故,像“`a =++ptr->mem;`”的意思就是要一个将ptr指涉的对象含有的成员mem自增后的结果。如果我们是想让这个ptr指针先自增,我们原本会写“a = (++ptr)->mem`;”,或也许`“++ptr; a = ptr->mem;`”,或哪天心情特别糟的话,一怒之下写成“``a = (++ptr, ptr->mem);”`。

指涉到成员的指针则完全是另一回事了。它们必须在一个class对象的语境里做提领操作(参见常见错误46)。为了这个,两个专用提领运算符被引入了语言:operator ->*用来从指涉到一个class对象的指针提领一个指涉到该对象的class成员的指针,运算符.*用来从一个class对象提领一个该对象的class成员的指针。

指涉到成员函数的指针通常用起来会比较头疼,但是它们一般不会造成特别严重的语法问题:

class C {
  // ...
  void f( int );
  int mem;
};
void (C::*pfmem)(int) = &C::f
int C::*pdmem = &C::mem; ⑤
C *cp = new C;
// ...
cp->*pfmem(12); // 错误! `
⑤译者注:这些是C++语言里不常用的声明语法,牢记。
我们的代码通不过编译,因为函数调用运算符operator()的优先级高于operator ->*。问题在于,将函数提领之前(此时其地址尚未决议),我们无法调用它。这里,加括号是必须的:

(cp->*pfmem)(12); ⑥
⑥译者注:指涉到成员的指针除了包括一个运算符,还有一个名字。
指涉到数据成员的指针相对来说更容易出问题,考虑以下的表达式:

a = ++cp->*pdmem```
变量cp和上面那个是同一个指涉到class对象的指针,`pdmem`不是一个`class`成员的名字,而是一个指涉到成员的指针的名字。在这种情况下,由于`operator ->*`的优先级不如运算符++高,cp会在指涉到成员的指针被提领前实施自增。除非cp指涉到的是一个`class`对象的数组,否则这个提领动作肯定不知道会得到什么结果11。

指涉到class成员的指针是一个好多C++软件工程师都没理解透的概念。为了让你代码的维护工程师未来的日子好过些,我们还是本着平淡是真的精神使用它吧:

++cp;
a = cp->*pdmem;`
结合性带来的问题
大多数C++运算符是左结合的,而且C++语言里没有非结合的运算符。但这并不能阻止有些聪明过头的软件工程师以下面的方式来使用这些运算符:

int a = 3, b =2, c = 1;
// ...
if (a > b > c) // 合法,但很有可能是错的…… ```
这段代码完全合法,但极有可能辞不达意。表达式`“3 > 2 > 1`”的结果是false。就像大多数C++运算符一样,operator >是左结合的,所以我们先计算子表达式“3>2”,结果是`true`。然后余下的就是计算`“true>1`”。为了计算这个,我们首先对true实施目标为整数型别的型别转换,结果实际就是在对“1>1”评估求值,其结果显然是`false`。
时间: 2024-08-01 12:06:56

《C++覆辙录》——2.3:(运算符)优先级问题的相关文章

《C++覆辙录》——导读

前言 C++覆辙录 本书之渊薮乃是近20年的小小挫折.大错特错.不眠之夜和在键盘的敲击中不觉而过的无数周末.里面收集了普遍的.严重的或有意思的C++常见错误,共计九十有九.其中的大多数,(实在惭愧地说)都是我个人曾经犯过的. 术语"gotcha"1有其云谲波诡的形成历史和汗牛充栋的不同定义.但在本书中,我们将它定义为C++范畴里既普遍存在又能加以防范的编码和设计问题.这些常见错误涵盖了从无关大局的语法困扰,到基础层面上的设计瑕疵,再到源自内心的离经叛道等诸方面. 大约10年前,我开始在

运算符优先级

运算   JScript 中的运算符优先级是一套规则.该规则在计算表达式时控制运算符执行的顺序.具有较高优先级的运算符先于较低优先级的运算符执行.例如,乘法的执行先于加法. 下表按从最高到最低的优先级列出 JScript 运算符.具有相同优先级的运算符按从左至右的顺序求值. 运算符 描述 . [] () 字段访问.数组下标.函数调用以及表达式分组 ++ -- - ~ ! delete new typeof void 一元运算符.返回数据类型.对象创建.未定义值 * / % 乘法.除法.取模 +

JScript 运算符优先级

js|jscript|运算 JScript 中的运算符优先级是一套规则.该规则在计算表达式时控制运算符执行的顺序.具有较高优先级的运算符先于较低优先级的运算符执行.例如,乘法的执行先于加法. 下表按从最高到最低的优先级列出 JScript 运算符.具有相同优先级的运算符按从左至右的顺序求值. 运算符 描述 . [] () 字段访问.数组下标.函数调用以及表达式分组 ++ - - ~ ! delete new typeof void 一元运算符.返回数据类型.对象创建.未定义值 * / % 乘法.

Java编程那些事儿28—运算符优先级

4.7 运算符优先级 在实际的开发中,可能在一个运算符中出现多个运算符,那么计算时,就按照优先级级别的高低进行计算,级别高的运算符先运算,级别低的运算符后计算,具体运算符的优先级见下表: 运算符优先级表 优先级 运算符 结合性 1 ()[]. 从左到右 2 !+(正) -(负)~++-- 从右向左 3 */% 从左向右 4 +(加)-(减) 从左向右 5 <<>>>>> 从左向右 6 <<=>>=instanceof 从左向右 7 == !

c/c++系列的运算符优先级总结

经常写程序的时候,遇到运算符优先级的问题,令我汗颜的是,查书的次数挺多的--狠狠心,总结下.不过还要结合大量的编程实践来深入脑海. 1.首先永远忘不了的是,逗号运算符级别最低,毫无争议的还有()括起来的,人工设定了最高优先级,先算括号里的. 2.非人为的,就是四个,函数调用(),[]数组下标,点运算符,间接->运算符.他们是优先级最高的,从左到右.拿->记忆结合性 3.还有最起码知道,所有的单目运算符具有相同级别的优先级,记住是所有的.且记住都是从右到左 比如:正负号+i和-i,自增自减++i

js利用与或运算符优先级实现if else条件判断表达式_javascript技巧

复制代码 代码如下: <script type="text/javascript"> /******************************************************************* 利用运算符优先级实现ifelse表达式 result = expression1 && expression2 当且仅当两个表达式的值都等于 True 时, result 才是 True. 如果任一表达式的值等于 False, 则 res

C++ 运算符优先级列表

运算符是告诉编译程序执行特定算术或逻辑操作的符号.C语言的运算范围很宽,把除了控制语句和输入输出以外的几乎所有的基本操作都作为运算符处理.主要分为三大类:算术运算符. 关系运算符与逻辑运算符.除此之外,还有一些用于完成特殊任务的运算符. 运算符的优先级与结合性     优先级:C语言中,运算符的运算优先级共分为15级.1级最高,15级最低.在表达式中,优先级较高的先于优先级较低的进行运算.而在一个运算量两侧的运算符优先级相同时,则按运算符的结合性所规定的结合方向处理.     结合性:C语言中各

c语言-关于运算符优先级的问题。急求指导。

问题描述 关于运算符优先级的问题.急求指导. 1. struct num {int a,b;}s[]={{1,6},{2,3}},*p=s; printf("%d",p->b/s[1].a*++p->a); 为什么输出6?先算p->b再算s[1].a,之后是++p->a,最后算乘除对吗. ++p->a为什么是2?->的优先级不是比++高么,为什么不是先取值再++ 2. static int a[10]={1,3,5,7,9}; int *p=a; p

《C++覆辙录》——常见错误1:过分积极的注释

第1章 基础问题 C++覆辙录 说一个问题是基础的,并不就是说它不是严重的或不是普遍存在的.事实上,本章所讨论的基础问题的共同特点比起在以后章节讨论的技术复杂度而言,可能更侧重于使人警醒.这里讨论的问题,由于它们的基础性,在某种程度上可以说它们普遍存在于几乎所有的C++代码中. 常见错误1:过分积极的注释 很多注释都是画蛇添足,它们只会让源代码更难读,更难维护,并经常把维护工程师引入歧途.考虑下面的简单语句: a = b; // 将b赋值给a 这个注释难道比代码本身更能说明这个语句的意义吗?因而