C 语言的运算符非常多,一共有 50 多种,可以分成若干类。

算术运算符专门用于算术运算,主要有下面几种。

  • :正值运算符(一元运算符)
  • :负值运算符(一元运算符)
  • :加法运算符(二元运算符)
  • :减法运算符(二元运算符)
  • :乘法运算符
  • :除法运算符
  • :余值运算符

(1),

和既可以作为一元运算符,也可以作为二元运算符。所谓“一元运算符”,指的是只需要一个运算数就可以执行。一元运算符用来改变一个值的正负号。


上面示例中,将这个值变成。

一元运算符对正负值没有影响,是一个完全可以省略的运算符,但是写了也不会报错。


上面示例中,变量的值还是,因为不会改变正负值。

二元运算符和用来完成加法和减法。


(2)

运算符用来完成乘法。


(3)

运算符用来完成除法。注意,两个整数相除,得到还是一个整数。


上面示例中,尽管变量的类型是(浮点数),但是得到的结果是,而不是。原因就在于 C 语言里面的整数除法是整除,只会返回整数部分,丢弃小数部分。

如果希望得到浮点数的结果,两个运算数必须至少有一个浮点数,这时 C 语言就会进行浮点数除法。


上面示例中,表示进行浮点数除法,得到的结果就是。

下面是另一个例子。


上面的代码,你可能觉得经过运算,会等于,但是实际上等于。这是因为是整除,会得到一个整数值,所以乘以后得到的也是。

为了得到预想的结果,可以将除数改成,让整除变成浮点数除法。


(4)

运算符表示求模运算,即返回两个整数相除的余值。这个运算符只能用于整数,不能用于浮点数。


负数求模的规则是,结果的正负号由第一个运算数的正负号决定。


上面示例中,第一个运算数的正负号(或)决定了结果的正负号。

(5)赋值运算的简写形式

如果变量对自身的值进行算术运算,C 语言提供了简写形式,允许将赋值运算符和算术运算符结合成一个运算符。

下面是一些例子。



C 语言提供两个运算符,对变量自身进行和的操作。

  • :自增运算符
  • :自减运算符

这两个运算符放在变量的前面或后面,结果是不一样的。和是先执行自增或自减操作,再返回操作后的值;和则是先返回操作前的值,再执行自增或自减操作。


上面示例中,自增运算符的位置差异,会导致变量得到不同的值。这样的写法很容易出现意料之外的结果,为了消除意外,可以改用下面的写法。


上面示例中,变量的自增运算与返回值是分离的两个步骤,这样就不太会出错,也提高了代码的可读性。

C 语言用于比较的表达式,称为“关系表达式”(relational expression),里面使用的运算符就称为“关系运算符”(relational operator),主要有下面6个。

  •  大于运算符
  •  小于运算符
  •  大于等于运算符
  •  小于等于运算符
  •  相等运算符
  •  不相等运算符

下面是一些例子。


关系表达式通常返回或,表示真伪。C 语言中,表示伪,所有非零值表示真。比如,返回,返回。

关系表达式常用于或结构。


注意,相等运算符与赋值运算符是两个不一样的运算符,不要混淆。有时候,可能会不小心写出下面的代码,它可以运行,但很容易出现意料之外的结果。


上面示例中,原意是,但是不小心写成。这个式子表示对变量赋值,它的返回值为,所以判断总是为真。

为了防止出现这种错误,有的程序员喜欢将变量写在等号的右边。


这样的话,如果把误写成,编译器就会报错。


另一个需要避免的错误是,多个关系运算符不宜连用。


上面示例中,连续使用两个小于运算符。这是合法表达式,不会报错,但是通常达不到想要的结果,即不是保证变量的值在和之间。因为关系运算符是从左到右计算,所以实际执行的是下面的表达式。


上面式子中,返回或,所以最终是或与变量进行比较。如果想要判断变量的值是否在和之间,应该使用下面的写法。



逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,主要有下面三个运算符。

  • :否运算符(改变单个表达式的真伪)。
  • :与运算符(两侧的表达式都为真,则为真,否则为伪)。
  • :或运算符(两侧至少有一个表达式为真,则为真,否则为伪)。

下面是与运算符的例子。


上面示例中,只有和同时为真,才会为真。

下面是否运算符的例子。


上面示例中,由于否运算符具有比更高的优先级,所以必须使用括号,才能对表达式进行否运算。当然,合理的写法是,这里只是为了举例。

对于逻辑运算符来说,任何非零值都表示真,零值表示伪。比如,会返回,会返回。

逻辑运算符还有一个特点,它总是先对左侧的表达式求值,再对右边的表达式求值,这个顺序是保证的。如果左边的表达式满足逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为“短路”。


上面示例中,如果左侧的表达式()为伪,即等于时,右侧的表达式()是不会执行的。因为这时左侧表达式返回,整个表达式肯定为伪,就直接返回,不再执行右侧的表达式了。

由于逻辑运算符的执行顺序是先左后右,所以下面的代码是有问题的。


上面示例中,执行左侧表达式后,变量的值就已经变了。等到执行右侧表达式的时候,是用新的值在计算,这通常不是原始意图。

C 语言提供一些位运算符,用来操作二进制位(bit)。

(1)取反运算符

取反运算符是一个一元运算符,用来将每一个二进制位变成相反值,即变成,变成。


上面示例中,对每个二进制位取反,就得到了一个新的值。

注意,运算符不会改变变量的值,只是返回一个新的值。

(2)与运算符

与运算符将两个值的每一个二进制位进行比较,返回一个新的值。当两个二进制位都为,就返回,否则返回。


上面示例中,两个八位二进制数进行逐位比较,返回一个新的值。

与运算符可以与赋值运算符结合,简写成。


(3)或运算符

或运算符将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位只要有一个为(包含两个都为的情况),就返回,否则返回。


或运算符可以与赋值运算符结合,简写成。


(4)异或运算符

异或运算符将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位有且仅有一个为,就返回,否则返回。


异或运算符可以与赋值运算符结合,简写成。


(5)左移运算符

左移运算符将左侧运算数的每一位,向左移动指定的位数,尾部空出来的位置使用填充。


上面示例中,的每一个二进制位,都向左侧移动了两位。

左移运算符相当于将运算数乘以2的指定次方,比如左移2位相当于乘以4(2的2次方)。

左移运算符可以与赋值运算符结合,简写成。


(6)右移运算符

右移运算符将左侧运算数的每一位,向右移动指定的位数,尾部无法容纳的值将丢弃,头部空出来的位置使用填充。


上面示例中,的每一个二进制位,都向右移动两位。最低的两位被丢弃,头部多出来的两位补,所以最后得到。

注意,右移运算符最好只用于无符号整数,不要用于负数。因为不同系统对于右移后如何处理负数的符号位,有不同的做法,可能会得到不一样的结果。

右移运算符相当于将运算数除以2的指定次方,比如右移2位就相当于除以4(2的2次方)。

右移运算符可以与赋值运算符结合,简写成。



逗号运算符用于将多个表达式写在一起,从左到右依次运行每个表达式。


上面示例中,有两个表达式(和),逗号使得它们可以放在同一条语句里面。

逗号运算符返回最后一个表达式的值,作为整个语句的值。


上面示例中,括号里面的逗号运算符,返回最后一个表达式的值,所以变量等于。

优先级指的是,如果一个表达式包含多个运算符,哪个运算符应该优先执行。各种运算符的优先级是不一样的。


上面示例中,表达式里面既有加法运算符(),又有乘法运算符()。由于乘法的优先级高于加法,所以会先计算,而不是先计算。

如果两个运算符优先级相同,则根据运算符是左结合,还是右结合,决定执行顺序。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符()。


上面示例中,和的优先级相同,它们都是左结合运算符,所以从左到右执行,先计算,再计算。

运算符的优先级顺序很复杂。下面是部分运算符的优先级顺序(按照优先级从高到低排列)。

  • 圆括号()
  • 自增运算符(),自减运算符()
  • 一元运算符(和)
  • 乘法(),除法()
  • 加法(),减法()
  • 关系运算符(、等)
  • 赋值运算符()

由于圆括号的优先级最高,可以使用它改变其他运算符的优先级。


上面示例中,由于添加了圆括号,加法会先于乘法进行运算。

完全记住所有运算符的优先级没有必要,解决方法是多用圆括号,防止出现意料之外的情况,也有利于提高代码的可读性。