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