十年前的笔记更新到微博——之二:C运算符的结合性、序列点、副作用的概念

相同优先级的运算符按照结合性来确定表达式的值。首先确定结合性的定义:
运算符的结合性指同一优先级的运算符在表达式中操作的组织方向, 即: 当一个运算对象的多个运算符优先级别相同时, 运算对象与运算符的结合顺序, C 语言规定了各种运算符的结合方向( 结合性) 。
如果在一个运算对象两侧的运算符的优先级别相同,如a-b+c,则按规定的“结合方向”处理。C语言规定了各种运算符的结合方向(结合性),算术运算符的结合方向都是“自左至右”,即先左后右,因此b先与减号相结合,执行a-b的运算,然后再执行加c的运算。“自左至右的结合方向”又称“左结合性”,即运算对象先与左面的运算符结合。以后可以看到有些运算符的结合方向为“自右至左”,即右结合性(例如,赋值运算符,若有a=b=c,按从右到左的顺序,先把变量c的值赋给变量b,然后变量b的值赋给a)。
例子1:
#include <stdio.h>
int main(int argc, char *argv[])
{
int i=5;
int v1,v2;
int *p1,*p2;

p1 = &i;
p2 = &i;
		
v1 = *++p1;
v2 = *p2++;	

printf("v1 = %d\n",v1);
printf("v2 = %d\n",v2);
return 0;

}
输出结果为:
v1 = 4195392
v2 = 5
*与++同为单目运算符,且具有相同的优先级,单目运算符均为右结合的。所以程序中的表达式:
v1 = ++p1;
p1先与++结合,即p1指向的下一个存储单元地址,再与
结合,取出存储单元里的数据。而由于该存储单元没有预先赋值,4195392这个数应该可能是随机的。
再看表达式:
v1 = *p1++;
运算符右结合,等价于:
v1 = (p1++);
先取p1指针,再取指针指向的值。但这个表达式里又涉及到自加运算符++,p1++只有遇到";"分号才会影响p1的值,所以
p1++输出的值还是5。后自增表达式的结果值就是被自增之前的那个值,然后这个结果值被确定之后,操作数的值会被自增。而这种“自增”的副作用会在上一个“序列点”跟下一个“序列点”之间完成。
关于副作用和序列点。C语言中,副作用(side effect)是指对数据对象或者文件的修改。例如,以下语句:
var = 99;
的副作用是把var的值修改成 99。对表达式求值也可能产生副作用,例如:
sec = 100;
对这个表达式求值所产生的副作用就是 sec 的值被修改成 100。
序列点(sequence point)是指程序运行中的一个特殊的时间点,在该点之前的所有副作用已经结束,并且后续的副作用还没发生。
C语句结束标志->分号(;)是序列点。也就是说,C语句中由赋值、自增或者自减等引起的副作用在分号之前必须结束。任何完整表达式(full expression)运算结束的那个时间点也是序列点。所谓完整表达式,就是说这个表达式不是子表达式。而所谓的子表达式,则是指表达式中的表达式。例如:
f = ++e % 3;
这整个表达式就是一个完整表达式。这个表达式中的 ++e、3 和 ++e % 3 都是它的子表达式。C标准规定,在两个序列点之间,一个对象所保存的值最多只能被修改一次.
序列点是一个时间点,在整个表达式全部计算完毕之后或在 ||、 &&、 ? : 或逗号运算符处, 或在函数调用之前。此刻尘埃落定,所有的副作用都已确保结束。ANSI/ISO C标准这样描述:“在上一个和下一个序列点之间,一个对象所保存的值至多只能被表达式的计算修改一次。而且前一个值只能用于决定将要保存的值。”第二句话比较费解。它说在一个表达式中如果某个对象需要写入, 则在同一表达式中对该对象的访问应该只局限于直接用于计算将要写入的值。这条规则有效地限制了只有能确保在修改之前才访问变量的表达式为合法。例如 i = i+1 合法,而 a[i] = i++ 则非法。为什么这样的代码:a[i] = i++; 不能工作?子表达式 i++ 有一个副作用它会改变 i 的值,由于 i 在同一表达式的其它地方被引用,这会导致无定义的结果,无从判断该引用(左边的 a[i] 中)是旧值还是新值。那么,对于 a[i] = i++; 我们不知道 a[] 的哪一个分量会被改写,但 i 的确会增加 1,对吗?不一定!如果一个表达式和程序变得未定义,则它的所有方面都会变成未定义。为什么&& 和 || 运算符可以产生序列点呢?这些运算符在此处有一个特殊的例外:如果左边的子表达式决定最终结果 (即,真对于 || 和假对于 && ) ,则右边的子表达式不会计算。因此,从左至右的计算可以确保,对逗号表达式也是如此。而且,所有这些运算符 (包括 ? : ) 都会引入一个额外的内部序列点。

包含序列点的符号:分号(;)
包含序列点的运算符:逻辑与(&&)、逻辑或(||)、条件运算符(?:)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值