C chapter4 操作符和表达式


操作符

算术操作符

C提供了所有常用的算术操作符:

1
+ - * / %

除了%操作符,其余几个操作符都是即适用于浮点类型又适用于整数类型。当/操作符的两个操作数都是整数时,它执行整除运算,在其他情况下则执行浮点数除法。%取模操作符,它返回余数。

移位操作符

在左移位中,值最左边的几位被丢弃,右边空出来的几个空位则由0补齐。

右移位可以选择逻辑移位或算术移位。

逻辑移位,左边移入的位用0填充;

算术移位,左边移入的位由原先该值的符号位决定,符号位为1则移入的位均为1,符号位为0则移入的位均为0,这样能够保持原数的正负形式不变。

算术左移和逻辑左移是相同的,它们只在右移时不同,而且只有当操作数是负值时才不一样。

左移位操作符为<<,右移位操作符为>>。左操作树的值将移动由右操作数指定的位数。两个操作数必须是整型类型。

警告:标准说明无符号值执行的所有移位操作都是逻辑移位,但对于有符号值,到底是采用逻辑移位还是算术移位取决于编译器。

位操作符

位操作符对它们的操作数各个位执行 AND、OR 和 XOR(异或)等逻辑操作。

它们要求操作数为整数类型,它们对操作数对应的位进行指定的操作,每次对左右操作数的各一位进行操作。

位的操纵

下面的表达式显示了如何通过移位操作符和位操作符来操纵一个整型值中的单个位。

将指定的位设置为1。

1
value=value &1 << bit_number;

把指定的为清0

1
value=value &~ (1<<bit_number);

对指定的位进行测试,如果该位被置为1,则表达式的结果为非零值。

1
value & 1 << bit_number

赋值

赋值操作符用一个等号表示。赋值是表达式的一种,而不是某种类型的语句。

只要运行出现表达式的地方,都允许进行赋值。

复合赋值符

到目前为止所介绍的操作符都还有一种复合赋值的形式:

1
2
+= -= *= /= %=
<<= >>= &= ^= |=

单目操作符

C具有一些单目操作符,也就是只接受一个操作数的操作符。

1
2
! ++ - & sizeof
~ -- + * (类型)
  • ! 操作符对它的操作数进行逻辑反操作。
  • ~ 操作符整型的操作数进行求补操作。
  • + 操作符产生操作数的值。
  • - 操作符产生操作数的负值。
  • ++ 自增操作符
  • -- 自减操作符
  • & 操作符产生它的操作数的地址
  • * 操作符是间接访问操作符,用于访问指针所指向的值。
  • sizeof 操作符判断它的操作数的类型长度,以字节为单位。操作数既可以是表达式,也可以是加上括号的类型名。
  • (类型) 操作符被称为强制类型转换(cast),它用于显示地把表达式的值转换为另外的类型。

关系操作符

1
> >= < <= != ==

这些操作符产生的结果都是一个整型值,而不是布尔值。

表达式的结果如果是0,它就认为是假;表达式的结果如果是任何非零值,它被认为是真。

逻辑操作符

逻辑操作符有&&||

它们对于表达式求值,测试它们的值是真还是假。

&&操作符的左操作数总是首先进行求值,如果它的值为真,然后就紧接着对右操作数进行求值。

||操作符也具有相同的特点,它首先对左操作符进行求值,如果它的值是真,右操作数变不再求值,因为整个表达式的值此时已经确定。这个行为常常被称为 “短路求值”。

条件操作符

条件操作符接受三个操作数。它也会控制子表达式的求值顺序。

语法:

1
expression1 ? expression2 : expression3

条件操作符的优先级非常低,所以它的各个操作数即使不加括号,一般也不会有问题。

1
a>5 ? a-- : a++

逗号操作符

1
expression1,expression2,...,expressionN

逗号操作符将两个表达式或多个表达式分隔开来。这些表达式自左向右逐个进行求值,整个逗号表达式的值就是最后那个表达式的值。

1
2
3
while(a=get_value(),count_value(a),a>0){
...
}

下标引用、函数调用和结构成员

下标引用操作符是一对方括号。下标引用操作符接受两个操作数:一个数组名和一个索引值。事实上,下标引用并不仅限于数组名。

除了优先级不同之外,下标引用操作和间接访问表达式是等价的。

1
2
array[下标];
*(array+(下标));

下标引用实际上是以后面这种形式实现的。

函数调用操作符接受一个或多个操作数。它的第1个操作数是你希望调用的函数名,剩余的操作数就是传递给函数的参数。把函数调用以操作符的意味着 “表达式” 可以代替 “常量” 作为函数名,事实也确实如此。

.->操作符用于访问一个结构的成员。如果s是个结构变量,那么s.a就访问s中名叫a的成员。当你拥有一个指向结构的指针而不是结构本身,且欲访问它的成员时,就需要使用->操作符而不是.操作符。

布尔值

C并不具备显示的布尔类型,所以使用整数来代替。

其规则是:零是假,任何非零值皆为真。

警告:尽管所有的非零值都被认为是真,但是当你在两个真值之间相互比较时必须小心,因为许多不同的值都可以代表真。

提示:解决所有这些问题的方法是避免混合使用整型值和布尔值。如果一个变量包含了一个任意的整型值,你应该显示地对它进行测试:

1
2
if(number)
if(!number)

左值和右值

为了理解有些操作符存在的限制,你必须理解左值(L-value)右值(R-value) 之间的区别。

左值就是那些能够出现在复制符号左边的东西。右值就是那些可以出现在复制符号右边的东西。

表达式求值

表达式的求值顺序一部分是由它所包含的操作符的优先级和结合性决定。同样,有些表达式的操作数在求值过程中可能需要转换为其他类型。

隐式类型转换

C 的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符型和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

1
2
char a,b,c;
a=b+c;

b和c的值被提升为普通整型,然后再指向加法运算。加法运算的结构将被截短,然后再存储于a中。

下面这个例子中,由于存在求补和左移操作,所以8位的精度是不够的。标准要求进行完整的整型求值,所以对于这类表达式的结构,不会存在歧义性。

1
a=(~a^b<<1)>>1;

算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另外一个操作数的类型,否则操作就无法进行。下面的层次体系称为**寻常算术转换(usual arithmetic conversion)**。

1
2
3
4
5
6
7
long double
double
float
unsigned long int
long int
unsigned int
int

如果某个操作数的类型在上面这个列表中排名较低,那么它首先将转换为另外一个操作数的类型然后执行操作。

操作符的属性

复杂表达式的求值顺序是3个因素决定的:操作符的优先级、操作符的结合性以及操作符是否控制执行的顺序。

两个相邻的操作符哪个先执行取决于它们的优先级,如果两者的优先级相同,那么它们的执行顺序由它们的结合性决定。结合性就是一串操作符是从左向右依次执行还是从右向左逐个执行。有4个操作符,它们可以对整个表达式的求值顺序施加控制,它们或者保证某个子表达式能够在另一个子表达式的所有求值过程完成之前进行求值,或者可能使某个表达式被完全跳过不再求值。

优先级和求值的顺序

两个相邻的操作符的执行顺序由它们的优先级决定。如果它们的优先级相同,它们的执行顺序由它们的结合性决定。除此之外,编译器可以自由决定使用任何顺序对表达式进行求值,只要它不违背逗号、&&||?:操作符所施加的限制。

换句话说,表达式中操作符的优先级只决定表达式的各个组成部分在求值过程中如何进行聚组。