操作符和表达式
操作符
算术操作符
+,-,*,/,%
,除了%
操作符其它几个操作符既可以用于浮点型,也可以用于整型。/
的两个操作数是整型时它执行整除运算,其它气矿下执行浮点运算。%
操作符的两个操作数必须是整型。
移位操作符
左移时,值最左边的几位被丢弃,右边多出来的几个空位由0补齐。
右移时,左边移入的新位有两种填充方案:
逻辑移位:左边移入的位用0填充。
算术移位:左边移入的位的值由原先该值的符号决定,符号位为1则移入的位都是1,符号位为0则移入的位都是0,这样可以保证原数的正负形式不变。
移位操作的操作数必须是整数。
标准中说对于无符号数,所有的移位操作都是逻辑移位,对于有符号数,移位操作采用逻辑移位还是算术移位是由编译器所决定的。如果一个程序使用了有符号数的移位操作,那么它是不可移植的。
单目操作符
~
操作符对整型类型的操作数执行求补操作,即操作数原来为1 的位变成0,原来为0的位变成1。sizeof
操作符判断它的操作数的类型长度,以字节为单位。(类型)
操作符被称为强制类型转换操作符,它用于显示把表达式的值转换为另外的类型。前缀++/--
,操作数的值被增加/减少,表达式的值就是操作数增加/减少后的值。后缀++/--
,操作数的值被增加/减少,表达式的值就是操作数增加/减少之前的值。
逻辑操作符
&&
,||
都具有短路特性:&&
首先会对左操作数求值,如果它的值为真,然后紧接着对右操作数求值;如果左操作数的值为假,那么右操作数便不再进行求值,因为整个表达式的值肯定是假的,右操作数的值也无关紧要。||
操作符首先对做操作符进行求值,如果它的值为真,右操作数便不再求值,因为整个表达式的值此时已经确定。
逗号操作符
expression1,expression2,...expressionN
逗号操作符将两个或多个表达式分隔开来,这些表达式自左向右逐个进行求值,整个逗号表达式的值就是最好那个表达式的值。
布尔值
C并不具备显式的布尔类型,所以使用整数来代替:
零是假,任何非零值皆为真。
左值和右值
左值就是那些能够出现在赋值符号左边的东西。
右值就是那些可以出现在赋值符号右边的东西。
字面值常量都不是左值。
间接访问和下标引用,它们的结果是个左值,其余操作符的结果则是右值。
表达式求值
隐式类型转换
C的整型算术运算总是至少以缺省整型的精度来进行。为了获得这个精度,表达式中的字符型和短整型操作数使用之前被转换为普通整型,这种转换称为整型提升。
char a,b,c;
...
a = b + c;
b
和c
的值被提升为普通整型,然后再执行加法运算。加法运算的结果被截断,然后再存储于a中。
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另外一个操作数的类型,否则操作无法进行。
下面的层次体系称为寻常算术转换:
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么它首先将转换成另外一个操作数的类型然后再执行操作:
操作符的属性
复杂表达式的求值顺序是由3个因素决定的:操作符的优先级、操作符的结合性以及操作符是否控制执行的顺序。
两个相邻的操作符哪个先执行取决于他们的优先级
如果两者的优先级相同,那么它们的执行顺序由它们的结合性决定
结合性就是一串操作符是从左向右执行还是从右向左执行。
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
---|---|---|---|---|---|
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | -- |
1 | () | 圆括号 | (表达式)/函数名(形参表) | 左到右 | -- |
1 | . | 成员选择(对象) | 对象.成员名 | 左到右 | -- |
1 | -> | 成员选择(指针) | 对象指针->成员名 | 左到右 | -- |
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
2 | ~ | 按位取反运算符 | ~表达式 | 右到左 | 单目运算符 |
2 | ++ | 自增运算符 | ++变量名/变量名++ | 右到左 | 单目运算符 |
2 | -- | 自减运算符 | --变量名/变量名-- | 右到左 | 单目运算符 |
2 | * | 取值运算符 | *指针变量 | 右到左 | 单目运算符 |
2 | & | 取地址运算符 | &变量名 | 右到左 | 单目运算符 |
2 | ! | 逻辑非运算符 | !表达式 | 右到左 | 单目运算符 |
2 | (类型) | 强制类型转换 | (数据类型)表达式 | 右到左 | 单目运算符 |
2 | sizeof | 长度运算符 | sizeof(表达式) | 右到左 | 单目运算符 |
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
3 | * | 乘 | 表达式*表达式 | 左到右 | 双目运算符 |
3 | % | 余数(取模) | 整型表达式%整型表达式 | 左到右 | 双目运算符 |
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
4 | - | 减 | 表达式-表达式 | 左到右 | 双目运算符 |
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
5 | >> | 右移 | 变量>>表达式 | 左到右 | 双目运算符 |
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
6 | >= | 大于等于 | 表达式>=表达式 | 左到右 | 双目运算符 |
6 | < | 小于 | 表达式<表达式 | 左到右 | 双目运算符 |
6 | <= | 小于等于 | 表达式<=表达式 | 左到右 | 双目运算符 |
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
7 | != | 不等于 | 表达式!= 表达式 | 左到右 | 双目运算符 |
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1?表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | -- |
14 | /= | 除后赋值 | 变量/=表达式 | 右到左 | -- |
14 | *= | 乘后赋值 | 变量*=表达式 | 右到左 | -- |
14 | %= | 取模后赋值 | 变量%=表达式 | 右到左 | -- |
14 | -= | 减后赋值 | 变量-=表达式 | 右到左 | -- |
14 | <<= | 左移后赋值 | 变量<<=表达式 | 右到左 | -- |
14 | >>= | 右移后赋值 | 变量>>=表达式 | 右到左 | -- |
14 | &= | 按位与后赋值 | 变量&=表达式 | 右到左 | -- |
14 | ^= | 按位异或后赋值 | 变量^=表达式 | 右到左 | -- |
14 | |= | 按位或后赋值 | 变量|=表达式 | 右到左 | -- |
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 | -- |
考虑表达式:
c + --c
操作符的优先级规则要求自减运算在加法运算之前进行,但是并无法得知加法操作符的左操作数是在右操作数之前还是之后进行求值,所以表达式的结果在两种情况下将会不同。
标准说明,类似这种表达式的值的是未定义的,尽管每种编译器都会为这个表达式产生某个值,但到底哪个正确并无标准答案,因此,像这样的表达式是不可移植的,应该予以避免。