# 运算符

网道（WangDoc.com），互联网文档计划

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

## 算术运算符 #

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

- `+`：正值运算符（一元运算符）
- `-`：负值运算符（一元运算符）
- `+`：加法运算符（二元运算符）
- `-`：减法运算符（二元运算符）
- `*`：乘法运算符
- `/`：除法运算符
- `%`：余值运算符

（1）`+`，`-`

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

    int x = -12;

上面示例中，`-`将`12`这个值变成`-12`。

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

    int x = -12;
    int y = +x;

上面示例中，变量`y`的值还是`-12`，因为`+`不会改变正负值。

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

    int x = 4 + 22;
    int y = 61 - 23;

（2）`*`

运算符`*`用来完成乘法。

    int num = 5;
    printf("%i\n", num * num); // 输出 25

（3）`/`

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

    float x = 6 / 4;
    printf("%f\n", x); // 输出 1.000000

上面示例中，尽管变量`x`的类型是`float`（浮点数），但是`6 / 4`得到的结果是`1.0`，而不是`1.5`。原因就在于 C 语言里面的整数除法是整除，只会返回整数部分，丢弃小数部分。

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

    float x = 6.0 / 4; // 或者写成 6 / 4.0
    printf("%f\n", x); // 输出 1.500000

上面示例中，`6.0 / 4`表示进行浮点数除法，得到的结果就是`1.5`。

下面是另一个例子。

    int score = 5;
    score = (score / 20) * 100;

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

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

    score = (score / 20.0) * 100;

（4）`%`

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

    int x = 6 % 4; // 2

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

    11 % -5 // 1
    -11 % -5 // -1
    -11 % 5 // -1

上面示例中，第一个运算数的正负号（`11`或`-11`）决定了结果的正负号。

（5）赋值运算的简写形式

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

- `+=`
- `-=`
- `*=`
- `/=`
- `%=`

下面是一些例子。

    i += 3;  // 等同于 i = i + 3
    i -= 8;  // 等同于 i = i - 8
    i *= 9;  // 等同于 i = i * 9
    i /= 2;  // 等同于 i = i / 2
    i %= 5;  // 等同于 i = i % 5

## 自增运算符，自减运算符 #

C 语言提供两个运算符，对变量自身进行`+ 1`和`- 1`的操作。

- `++`：自增运算符
- `--`：自减运算符

    i++; // 等同于 i = i + 1
    i--; // 等同于 i = i - 1

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

    int i = 42;
    int j;

    j = (i++ + 10);
    // i: 43
    // j: 52

    j = (++i + 10)
    // i: 44
    // j: 54

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

    /* 写法一 */
    j = (i + 10);
    i++;

    /* 写法二 */
    i++;
    j = (i + 10);

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

## 关系运算符 #

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

- `>` 大于运算符
- `<` 小于运算符
- `>=` 大于等于运算符
- `<=` 小于等于运算符
- `==` 相等运算符
- `!=` 不相等运算符

下面是一些例子。

    a == b;
    a != b;
    a < b;
    a > b;
    a <= b;
    a >= b;

关系表达式通常返回`0`或`1`，表示真伪。C 语言中，`0`表示伪，所有非零值表示真。比如，`20 > 12`返回`1`，`12 > 20`返回`0`。

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

    if (x == 3) {
      printf("x is 3.\n");
    }

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

    if (x = 3) ...

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

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

    if (3 == x) ...

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

    /* 报错 */
    if (3 = x) ...

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

    i < j < k

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

    (i < j) < k

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

    i < j && j < k

## 逻辑运算符 #

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

- `!`：否运算符（改变单个表达式的真伪）。
- `&&`：与运算符（两侧的表达式都为真，则为真，否则为伪）。
- `||`：或运算符（两侧至少有一个表达式为真，则为真，否则为伪）。

下面是与运算符的例子。

    if (x < 10 && y > 20)
      printf("Doing something!\n");

上面示例中，只有`x < 10`和`y > 20`同时为真，`x < 10 && y > 20`才会为真。

下面是否运算符的例子。

    if (!(x < 12))
      printf("x is not less than 12\n");

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

对于逻辑运算符来说，任何非零值都表示真，零值表示伪。比如，`5 || 0`会返回`1`，`5 && 0`会返回`0`。

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

    if (number != 0 && 12/number == 2)

上面示例中，如果`&&`左侧的表达式（`number != 0`）为伪，即`number`等于`0`时，右侧的表达式（`12/number == 2`）是不会执行的。因为这时左侧表达式返回`0`，整个`&&`表达式肯定为伪，就直接返回`0`，不再执行右侧的表达式了。

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

    while ((x++ < 10) && (x + y < 20))

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

## 位运算符 #

C 语言提供一些位运算符，用来操作二进制位（bit）。

（1）取反运算符`～`

取反运算符`～`是一个一元运算符，用来将每一个二进制位变成相反值，即`0`变成`1`，`1`变成`0`。

    // 返回 01101100
    ~ 10010011

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

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

（2）与运算符`&`

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

    // 返回 00010001
    10010011 & 00111101

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

与运算符`&`可以与赋值运算符`=`结合，简写成`&=`。

    int val = 3;
    val = val & 0377;

    // 简写成
    val &= 0377;

（3）或运算符`|`

或运算符`|`将两个值的每一个二进制位进行比较，返回一个新的值。两个二进制位只要有一个为`1`（包含两个都为`1`的情况），就返回`1`，否则返回`0`。

    // 返回 10111111
    10010011 | 00111101

或运算符`|`可以与赋值运算符`=`结合，简写成`|=`。

    int val = 3;
    val = val | 0377;

    // 简写为
    val |= 0377;

（4）异或运算符`^`

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

    // 返回 10101110
    10010011 ^ 00111101

异或运算符`^`可以与赋值运算符`=`结合，简写成`^=`。

    int val = 3;
    val = val ^ 0377;

    // 简写为
    val ^= 0377;

（5）左移运算符`<<`

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

    // 1000101000
    10001010 << 2

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

左移运算符相当于将运算数乘以2的指定次方，比如左移2位相当于乘以4（2的2次方）。

左移运算符`<<`可以与赋值运算符`=`结合，简写成`<<=`。

    int val = 1;
    val = val << 2;

    // 简写为
    val <<= 2;

（6）右移运算符`>>`

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

    // 返回 00100010
    10001010 >> 2

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

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

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

右移运算符`>>`可以与赋值运算符`=`结合，简写成`>>=`。

    int val = 1;
    val = val >> 2;

    // 简写为
    val >>= 2;

## 逗号运算符 #

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

    x = 10, y = 20;

上面示例中，有两个表达式（`x = 10`和`y = 20`），逗号使得它们可以放在同一条语句里面。

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

    int x;

    x = (1, 2, 3);

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

## 运算优先级 #

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

    3 + 4 * 5;

上面示例中，表达式`3 + 4 * 5`里面既有加法运算符（`+`），又有乘法运算符（`*`）。由于乘法的优先级高于加法，所以会先计算`4 * 5`，而不是先计算`3 + 4`。

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

    5 * 6 / 2;

上面示例中，`*`和`/`的优先级相同，它们都是左结合运算符，所以从左到右执行，先计算`5 * 6`，再计算`30 / 2`。

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

- 圆括号（`()`）
- 自增运算符（`++`），自减运算符（`--`）
- 一元运算符（`+`和`-`）
- 乘法（`*`），除法（`/`）
- 加法（`+`），减法（`-`）
- 关系运算符（`<`、`>`等）
- 赋值运算符（`=`）

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

    int x = (3 + 4) * 5;

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

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

本教程采用知识共享 署名-相同方式共享 3.0协议。

分享本文