((*strDest++=*strSrc++)!='\0');哪儿前辈可以解释下里面具体执行的步骤呢?
对于这样的表达式,我们通常会有这样三种看法:
1.这种写法不但没有错误(当然也没有BUG),而且写法紧凑。
2.这种写法虽然没有错误,但是不够直观,理解起来有点麻烦,可能还会导致理解错误。
3.这种写法中存在未定义的地方,执行结果可能是错误的。
粗略来看,这三种说法都有点道理。我顿时有了刨根问底的兴趣,想对这个问题进行一次深入的分析。对于这种组合表达式,在分析的时候我们应该抓住两个关键的概念:优先级(Precedence)和关联性(Associativity)。
1.优先级(Precedence)。优先级决定了那些表达式的值先被评估,那些表达式的值后被评估。通常情况下,优先级高的表达式的值先被评估出来后,然后用评估的结果再去评估那些优先级低的表达式。所以如果我们将优先级搞反了,评估出来的结果是错误的。
2.(Associativity)。对于二目表达式,关联性决定了左边的表达式还是右边的表达式先被评估,先被评估出来的结果再用来评估另外的表达式。
再抓住这两个关键的同时,我们还应该分清什么是表达式的值,什么是变量的值。我们在评估表达式的时候,我们感兴趣的是表达式的值,而不是构成表达式的某些变量的值。在很多情况下,表达式的值和某些变量的值是一致的,所以我们很容易混淆表达式的值和变量的值。要知道,在有些情况下,表达式的值并不和某些变量的值相同。
有了上面的理论来武装我们,对表达式的分析就显得游刃有余了:
1. 很明显,上面的表达式是一个组合表达式。组合表达式由子表达式组成,子表达式又可能是组合表达式,这样就形成了一个树状的数据结构。对表达式的评估就类似于对树结点的遍历。首先我们应该注意到"()"操作符,它具有最高的优先级,所以从整体来看,整个表达式应该是个"!="操作。"!="左边又是一个组合表达式,而右边是一个常量"\0',很明显下面的工作就是评估(*strDest++=*strSrc++)。
2.在这一步,我们要对表达式(*strDest++=*strSrc++)进行评估。由于赋值表达式具有较低的优先级,所以表达式又可以写成:(*strDest++) = (*strSrc++),所以整个表达式是个"="操作,"="左边又是一个组合表达式,右边也是一个组合表达式,这里就需要从关联性来判断左边还是右边也被评估。由于"="的关联性是从右到左,所以(*strSrc++)先被评估,(*strDest++)后被评估。
2.1 在这一步,我们要对表达式(*strSrc++)进行评估。由于"++"的优先级大于"*",所以表达式又可以写成:*(strSrc++)。我们要先对表达式strSrc++进行评估,然后用表达式的值再去评估*(strSrc++)的值。对于表达式strSrc++,这里要需要注意区分变量的值和表达式的值。对于"后增1"表达式,表达式的值是变量strSrc的值,然后变量strSrc的值会"加1",也就是说表达式的值是strSrc变化前的值,而strSrc的值会发生变化。值得注意的是,我们知道strSrc的值会发生变化,但是我们却不知道strSrc的值发生变化的具体时间,这个变化具体的执行时间由编译器决定了,这就决定了任何依赖strSrc的表达式的值是不确定的,具体的值依赖编译器的实现。完成了对strSrc++的评估后,取值操作符就对表达式的值所对应的内存空间进行取值操作。
2.2 在这一步,我们要对表达式(*strDest++)进行评估。具体的评估的分析完全和2.1中的分析一致。
2.3 在这一步,我们要对表达式(*strDest++) = (*strSrc++)进行评估,这是个赋值表达式,将右表达式的值赋给左边表达式的值。值得注意的是,对于赋值表达式,表达式本身的值等于左边子表达式的值。
3.由于"!="表达式左边的子表达式的值已经被评估出来了,下面就执行"!="操作。"!="表达式的是一个布尔值。
通过以上深入的分析,我们知道这个表达式完成了以下多个功能:
1.对于指针strDest, strSrc,将strSrc所指的内存空间的值赋给由strDest所指的内存空间。
2.判断赋值后的strDest所指的内存空间的指是否等于0。
3.对于指针strDest,strSrc,他们的值分别加1,即指向下一个元素。
我们可以看出,一个表达式完成了三个功能,表达式写的确实"相当紧凑"。而且这个表达式的值是可以确定的,因为所有的分析都是建立在C 标准的基础上。对于能否在实践的代码中使用这样的代码,这就智者见智了,关键一点就是要遵循项目的代码规范。 优先级 | 运算符 | 名称或含义 | 结合方向 | 说明 |
1 | [] | 数组下标 | 左到右 | |
() | 圆括号 | |||
. | 成员选择(对象) | |||
-> | 成员选择(指针) | |||
2 | - | 负号运算符 | 右到左 | 单目运算符 |
(类型) | 强制类型转换 | |||
++ | 自增运算符 | 单目运算符 | ||
-- | 自减运算符 | 单目运算符 | ||
* | 取值运算符 | 单目运算符 | ||
& | 取地址运算符 | 单目运算符 | ||
! | 逻辑非运算符 | 单目运算符 | ||
~ | 按位取反运算符 | 单目运算符 | ||
sizeof | 长度运算符 | |||
3 | / | 除 | 左到右 | 双目运算符 |
* | 乘 | 双目运算符 | ||
% | 余数(取模) | 双目运算符 | ||
4 | + | 加 | 左到右 | 双目运算符 |
- | 减 | 双目运算符 | ||
5 | << | 左移 | 左到右 | 双目运算符 |
>> | 右移 | 双目运算符 | ||
6 | > | 大于 | 左到右 | 双目运算符 |
>= | 大于等于 | 双目运算符 | ||
< | 小于 | 双目运算符 | ||
<= | 小于等于 | 双目运算符 | ||
7 | == | 等于 | 左到右 | 双目运算符 |
!= | 不等于 | 双目运算符 | ||
8 | & | 按位与 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 左到右 | 双目运算符 |
10 | | | 按位或 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 右到左 | |
/= | 除后赋值 | |||
*= | 乘后赋值 | |||
%= | 取模后赋值 | |||
+= | 加后赋值 | |||
-= | 减后赋值 | |||
<<= | 左移后赋值 | |||
>>= | 右移后赋值 | |||
&= | 按位与后赋值 | |||
^= | 按位异或后赋值 | |||
|= | 按位或后赋值 | |||
15 | , | 逗号运算符 | 左到右 | 从左向右顺序运算 |