Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2315350
  • 博文数量: 527
  • 博客积分: 10343
  • 博客等级: 上将
  • 技术积分: 5565
  • 用 户 组: 普通用户
  • 注册时间: 2005-07-26 23:05
文章分类

全部博文(527)

文章存档

2014年(4)

2012年(13)

2011年(19)

2010年(91)

2009年(136)

2008年(142)

2007年(80)

2006年(29)

2005年(13)

我的朋友

分类: C/C++

2010-08-01 09:57:30

1. 运算符优化级
需要记吗? 不需要吗? 需要吗?...

关于这个问题最觉的忠告是: 使用括号, 但在这本书中, page 20, 作者说:

用添加括号的方法虽然可以完全避免这类问题, 但是表达中有了太多的括号反而不容易理解. 因此, 记住C语言中运算符的优先级是有益的.

a+((b/c)*d) 更可读, 还是
a + b / c * d
更清楚, 可能不同的人有不同的感受, 但我绝对同意作者所说太我括号反而不容易理解, 尤其是那些被大多数人广泛知晓的运算符之间的优先关系.

同样是运算符, 但它们的优先关系, 对于绝大多数程序员, 其实事实上是分有轻重缓急的, 比如
我相信绝大多数C程序员都知道乘除的优先级高于加减, 但很可能就顶不准 | 与 == 之间的优先级关系.

关于运算符优先级, K&R的C圣经中列出了15种, 但, C: A reference Manual中列出的更多, 原因并不是两者之中必有一错, 而是从语法分析的角度看, 完全有可能不同的产生式描述的却是同一个语言. 有兴趣的可以自己去比较, K&R圣经中的优先级表格出现在
Operators Associativity
() [] -> .  left to right
! ~ ++ -- + - * (type) sizeof  right to left
* / %  left to right
+ -  left to right
<<  >>  left to right
< <= > >=  left to right
== !=  left to right
&  left to right
^  left to right
|  left to right
&&  left to right
||  left to right
?:  right to left
= += -= *= /= %= &= ^= |= <<= >>=  right to left
,  left to right



蓝色边框中的是扫描不清楚, 我重新敲上去的等价的文字. 值得为C: A reference manual说的是它的重要性和在C语言书籍中的地位, 这是C语言的实现者角度, 可以用作一个精确的参考的手册, 而K&R虽然经典, 却不中心据此写出一个C编译器. 可以说它比K&R精确全面, 比标准本身易读.

很高兴看到在C 陷阱与缺陷一书中, 作者深入到程序员如何记忆的学习心理细节, 来条分缕析总结规律, 让优先顺序更容易记住.

另外一点细节, 在C陷阱与缺陷的中文版(个人认为翻译的很好)中, 第21页列出的优化级表格, 我上面没有给出, 对应K&R表格中的第二行, 少了一个一元+号运算符. 可能是原作者的遗漏, 也可能是中文成书过程中的疏忽.

那么, 运算符优先级有多少种可能的组合让程序员去判断呢, 我用下面的小技巧得到全部的组合数(bash):
echo {a,b}{c,d}
我们会得到
ac ad bc bd

下面我把1元的*运算符用1*表示, 2元的乘法运算符用2*表示, 以示区分.

[zrf@zrfpc pcre-7.2] echo {'(func)','[]','.','->','!','1+','1-','++','--','~','(type)','1*','1&','sizeof','2*','/','%','2+','2-','<<','>>','<=','<','>','>=','!=','==','2&','^','|','&&','||','?:','=','+=','-=','*=','/=','%=','<<=','>>=','~=','&=','|=',','}"      "{'(func)','[]','.','->','!','1+','1-','++','--','~','(type)','1*','1&','sizeof','2*','/','%','2+','2-','<<','>>','<=','<','>','>=','!=','==','2&','^','|','&&','||','?:','=','+=','-=','*=','/=','%=','<<=','>>=','~=','&=','|=',','}  | gvim -

用这种技巧, 把所有的运算符用bash进行组合, 得到2025(45 * 45)种不同的两两组合, 其中包括同一个运算符与它自己的组合, 这种不需要记, 减去45种, 也有1980种. 硬来是不行的, 正如Koenig所说, 对它们进行分组归纳, 可以让优先级的掌握变得很容易.

下面是书中总结的优先级的规律以及我自己的一些记忆规则:
1. 最高优先级的运算符, 其实不是真正意义上的运算符, 的确, 把()表示的强制优先级, 和函数调用中出现的(), 以及a.b, p->i 这样的东西叫做运算符多少有点怪, 因为它们不是对某物进行运算. 完整的列表包括:
   (用于强制优先级), (用于函数调用), []用于数组下标, .和->用来选择结构或union的成员.

   { (), [],., -> }

   注意虽然只写了一个(), 它同时代表强制优先级和函数调用中出现的()

2. 然后是一元运算符, 所有的一元运算符优先级高于二元, 三元的(没有4元以及上的运算符), 一生二, 二生三,  三生万物, 少即是多, 小既是高, less is better than more, 下面&和|的等级也高于&&和||, 算是个纯偶然的巧合. 这包括10个运算符, 是同优先级运算符个数第二多的了:

{ ~, !, +, -, ++, --, (type), sizeof, *, & }

  注意++和--同时代表prefix和postfix的版本.

3. 接下来是乘除类, 说乘除类, 是因为把取余操作看作是乘除的同类操作是很自然的事, 毕竟C语言中对余数的操作满足:
   a = b / c
   d = b % c
  则 b = a * c + d

  {*, /, %}

4. 由于地球人都知道乘除的优先级高于加减, 所以紧接着就是+-操作符

  { +, - }

5. 至少让我感到意外的是, 移位操作>> 和 << 优先级在+-之后, 因为C程序员都知道有时可以用移位代替乘除, 所以在心理上把它们认为是等价的同类操作是很自然的. 这样去想或许会容易接受一些, +-*/是我们在小学都学到的操作符, 根植于我们一般人性的正常思维, 恐怕小学不会教整数的移位运算吧. 所以移位运算排在四则运算之后.

   {<<, >>}

6. 接下来是关系运算符, 也叫比较运算符.

   比较数值的大小关系也是我们最基本数学逻辑的一部分. 跟+-*/一样的近于人的本性. 几乎无需学习.

  { >,<,>=, <=}

7. 不太容易理解的, 6中不包括相等性和不等性的比较==和!=
   6和7中的关系运算符, 它产生的结果都是一种二值逻辑, 或真或假, 只要记住下面常见的一个表达式, 就容易记住==和!= 低于大小比较的原因:
   a1 < b1 == a2 < b2
  
   这样一个表达式, 它为真的条件是a1 小于 b1 且a2 < b2, 或者a1 >= b1且a2 >= b2.

   只有这样的优先级安排, 才能让上面的表达式不加括号也具有最自然的语意.

   这个例子是C陷阱与缺陷第22页(中文版)中的, 我把a, b, c, d换成a1, b1, a2, b2
   {==, !=}

8. 然后是位操作符 &, ^, 和 |

   记住&高于|的可以借助于&& 高于||的类比, 而&&高于|| 是所有C教材都能教的很好的知识.
   记住^位于&和|之间, 只需想象^的形象, 就是用于插在两个字符之间.

    {&}, {^}, {|}

9. 根据前面提到的less is more原则, && 和 || 排在& ^ |之后, 有相似性(哪怕仅仅是符号字面上)的运算符其优先级也紧邻着.

   {&&},  {||}

10. 几乎走到最后了, 最后三个也要关联起来记, ?:, 这个3目运算符优先级高于它下面的赋值运算符(2级), 我不能拿一生二, 二生三乱盖了, 的确, ?:的优先级高于=, 这可以用下面的常见表达式说服你这样的安排最合理:
    int i = ok ? 1 : 0;

   如果?: 低于=, 那这个表达式该怎么写呢? 那得有多别扭!

   {?:}

11. 倒数第二的了, 11个赋值运算符, 是同优先级运算符最多的一圫了:
 
  {=, +=, -=, *=, /=, %=, >>=, <<=, &=, |=, ^= }

  此处有两个心理误区, 乘除高于加减, 所以*=和/=自然高于+=和-=, 不是这样, 应该记住它们都归类为赋值运算符, 同样优先级.
  其二: 我尝试使用过&&=和 ||=, 毕竟在某些情况下, 你可能也需要这样的东西, 但C语言里没有, 它的强大的面向对象后辈C++也没有!

12. 最后的是逗号了, 它用来让表达式自左至右顺序求值

  从技术上讲, 这个逗号运算符应该区别于:
  struct, union, 初始化和 enum 定义中的逗号分隔符, 在那些地方逗号用来分隔不同的token
  函数声明/定义/调用时的参数列表. 尤其是函数调用时.

15级运算符, 只有3个是自右向左结合的, 而这3级如此安排, 都有最常见的用法:

a = b = c;

int ret = ok?0: err?-1: -2;

int i = **p;

以上是给定正确的优先级表, 然后去找其中规律的路子, 三日五日, 或一周一月之后, 看到任意两个运算符扎在同一个表达式里, 还能不能根据这些已建立的头绪, 马上确定它们的优先顺序, 是另一码事, 下面是我用前面的办法得到的45个运算符的两两组合, 有兴趣可以任挑一对试试.


太长了, 放到下一页:

http://blog.chinaunix.net/u/8681/showart.php?id=2287142

阅读(995) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~