目录
4.1 算术运算符
一元运算符(单操作数):+、-
二元运算符(双操作数):+、-、*、/、%
注意事项:
1、操作数即运算符两端数值的个数
2、一元运算符+什么都不做,主要用于强调某数值常量是正的
3、除法运算符“/”的得到的结果是商,求余运算符“%”的得到的结果是余数
4、当两个操作数都是整数时,/运算符会丢掉分数部分(1/2的结果是0而非0.5)
5、%运算符的两个操作数需要是整数,否则程序无法编译通过
6、0不能作为/或%运算符的右操作数
7、除%以外,二元运算符即允许操作数是整数也允许操作数是浮点数,也可以是两者的混合,当int型操作数和float型操作数混合在一起时,运算结果是float型
当运算符/和运算符%用于负操作数时,其结果难以确定。 根据 C89 标准 ,如果两个操作数中有一个为负数,那么除法和取余的结果既可以向上舍入也可以向下舍入 ( -9/7 的结果既可以是-1 也可以是-2, -9%7 的值可能是-2 或者 5) 但是 在 C99 标准中 ,除法的结果总是 趋零截尾 的 (-9/7 的结果是-1), i%j 的值的符号与 i 的相同 (-9%7 的值是-2)
4.1.1 运算符的优先级和结合性
C 语言采用运算符优先级规则来解决在运算过程中可能出现的二义性问题 (编译器是把表达式 i+j*k 解释为(i+j)*k 还是 i+(j*k))
当两个或更多个运算符出现在同一个表达式中时,可以按运算符优先级从高到低的次序重
4.1.2 运算符的优先级与结合性顺序表
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
---|---|---|---|---|---|
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | |
() | 圆括号 | (表达式) | |||
. | 成员选择(对象) | 对象.成员名 | |||
-> | 成员选择(指针) | 对象指针->成员名 | |||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
(类型) | 强制类型转换 | (数据类型)表达式 | |||
++ | 自增运算符 | ++变量名 | 单目运算符 | ||
-- | 自减运算符 | --变量名 | 单目运算符 | ||
* | 取值运算符 | *指针变量 | 单目运算符 | ||
& | 取地址运算符 | &变量名 | 单目运算符 | ||
! | 逻辑非运算符 | !表达式 | 单目运算符 | ||
~ | 按位取反运算符 | ~表达式 | 单目运算符 | ||
sizeof | 长度运算符 | sizeof(表达式) | |||
3 | / | 除 | 表达式 / 表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | 双目运算符 | ||
% | 余数(取模) | 整型表达式%整型表达式 | 双目运算符 | ||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | 双目运算符 | ||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | 双目运算符 | ||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | 双目运算符 | ||
< | 小于 | 表达式<表达式 | 双目运算符 | ||
<= | 小于等于 | 表达式<=表达式 | 双目运算符 | ||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | 双目运算符 | ||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | |
/= | 除后赋值 | 变量/=表达式 | |||
*= | 乘后赋值 | 变量*=表达式 | |||
%= | 取模后赋值 | 变量%=表达式 | |||
+= | 加后赋值 | 变量+=表达式 | |||
-= | 减后赋值 | 变量-=表达式 | |||
<<= | 左移后赋值 | 变量<<=表达式 | |||
>>= | 右移后赋值 | 变量>>=表达式 | |||
&= | 按位与后赋值 | 变量&=表达式 | |||
^= | 按位异或后赋值 | 变量^=表达式 | |||
|= | 按位或后赋值 | 变量|=表达式 | |||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 |
4.2 赋值运算符
4.2.1 简单赋值
i = 5; /* i is now 5 */
j = i; /* j is now 5 */
k = 10 * i + j; /* k is now 55 */
int i;
float f;
i = 72.99f; /* i is now 72 */
f = 136; /* f is now 136.0 */
副作用:通常我们不希望运算符修改它们的操作数,简单赋值运算符是一个有副作用的运算符,它改变了运算符的左操作数(表达式 i = 0 求值产生的结果为 0,并(作为副作用)把 0 赋值给 i)
注意事项:由于存在类型转换,串在一起的赋值运算的结果可能不是预期的结果(int i; float r; r = i = 33.3f,首先把数值33赋值给变量i,然后把33.0而不是预期的33.3赋值给变量f)
4.2.2 左值
12 = i; /*** WRONG ***/
i + j = 0; /*** WRONG ***/
-i = j; /*** WRONG ***/
编译器会检测出这种错误,并给出 “invalid lvalue in assignment”(赋值中的左值无效/表达式必须是可修改的左值)这样的出错消息
4.2.3 复合赋值
4.3 自增运算符和自减运算符
自增运算符:++
自减运算符:--
自增和自减运算符既可为前缀运算符(++i 和--i)也可为后缀运算符( i++和 i--)
前缀自增/减:先加再用
后缀自增/减:先用再加
i = 1;
j = 2;
k = i++ + j++;
i = 2、j = 3、k = 4
注意事项:后缀++和后缀--比一元的正号和负号优先级高,且均为左结合。前缀++和前缀--与一元
的正号和负号优先级相同,且均为右结合。
4.4 表达式求值
对于复杂表达式a = b += c++ - d + --e / -f 的求值过程分析:
1、后缀++具有最高优先级,因此在后缀++和相关操作数的周围加上圆括号
a = b += (c++) - d + --e / -f
2、前缀--和一元负号运算符的优先级都为2,因此为这两个运算符加上括号
a = b += (c++) - d + (--e) / (-f)
3、 另一个负号的左侧紧挨一个操作数,因此它一定是减法运算符,而不是一元负号运算符
4、/运算符的优先级为3,故将/两侧的操作数用括号包裹
a = b += (c++) - d + ((--e) / (-f))
5、+和-为两个优先级为4的运算符,当两个具有相同优先级的运算符和同一操作数相邻时,需要注
意运算的结合性,-和+运算符均为自左向右结合,因此先括减号再括加号
a = b += (((c++) – d) + ((--e) / (-f)))
6、最后剩下的=和+=运算符,这两个运算符与b相邻,因此必须考虑运算结合性,赋值运算符从右向左结合,因此括号先加在表达式+=周围,然后加再=周围
(a = (b += (((c++) – d) + ((--e) / (-f)))))
4.4.1 子表达式的求值顺序
a = 5; //子表达式的值
c = (b = a + 2) – (a = 1);
第二条语句的执行结果是未定义的,C 标准没有规定。对大多数编译器而言,c 的值是6 或者 2。如果先计算子表达式(b = a + 2),那么 b 的值为 7,c 的值为 6。但是,如果先计算 子表达式(a = 1),那么 b 的值为 3,c 的值为 2
i = 2;
j = i * i++;
人们很自然地就会认定 j 赋值为 4。但是,该语句的执行效果是未定义的,j 也可能赋值为 6。该情况的执行步骤如下:(1) 取出第二个操作数(i 的原始值),然后 i 自增(2) 取出第一个操作数(i 的新值)(3) i 的原始值和新值相乘,结果为 6“取出”变量意味着从内存中获取它的值。变量的后续变化不会影响已取出的值,因为已取出的值通常存储在 CPU 中,称为寄存 器的一个特殊位置。
4.4.2 未定义的行为(小拓展)
根据 C 标准,类似 c = (b = a + 2) – (a = 1);和 j = i * i++;这样的语句会导致“未定义的行为”,这跟由实现定义的行为是不同的。当程序中出现未定义的行为时,后果是不可预料的。不同的编译器给出的编译结果可能是不同的,但这还不是唯一可能发生的事情:首先程序可能无法通过编译,就算通过了编译也可能无法运行,就算可以运行也有可能崩溃、不稳定或者产生无意义的结果。
4.5 表达式语句
表达式++i
语句++i;
执行这条语句时,i 先进行自增,然后把新产生的 i 值取出(与放在表达式中的效果一样)但
是,因为++i 不是更长的表达式的一部分,所以它的值会被丢弃,执行下一条语句(当然,对 i 的
改变是持久的)
~over~