466 lines
13 KiB
Markdown
466 lines
13 KiB
Markdown
|
---
|
|||
|
id: 运算符
|
|||
|
title: 运算符
|
|||
|
sidebar_position: 4
|
|||
|
data: 2022年3月30日
|
|||
|
---
|
|||
|
|
|||
|
C 语言的运算符非常多,一共有 50 多种,可以分成若干类。
|
|||
|
|
|||
|
## 算术运算符
|
|||
|
|
|||
|
算术运算符专门用于算术运算,主要有下面几种。
|
|||
|
|
|||
|
- `+`:正值运算符(一元运算符)
|
|||
|
- `-`:负值运算符(一元运算符)
|
|||
|
- `+`:加法运算符(二元运算符)
|
|||
|
- `-`:减法运算符(二元运算符)
|
|||
|
- `*`:乘法运算符
|
|||
|
- `/`:除法运算符
|
|||
|
- `%`:余值运算符
|
|||
|
|
|||
|
(1)`+`,`-`
|
|||
|
|
|||
|
`+`和`-`既可以作为一元运算符,也可以作为二元运算符。所谓“一元运算符”,指的是只需要一个运算数就可以执行。一元运算符`-`用来改变一个值的正负号。
|
|||
|
|
|||
|
```c
|
|||
|
int x = -12;
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,`-`将`12`这个值变成`-12`。
|
|||
|
|
|||
|
一元运算符`+`对正负值没有影响,是一个完全可以省略的运算符,但是写了也不会报错。
|
|||
|
|
|||
|
```c
|
|||
|
int x = -12;
|
|||
|
int y = +x;
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,变量`y`的值还是`-12`,因为`+`不会改变正负值。
|
|||
|
|
|||
|
二元运算符`+`和`-`用来完成加法和减法。
|
|||
|
|
|||
|
```c
|
|||
|
int x = 4 + 22;
|
|||
|
int y = 61 - 23;
|
|||
|
```
|
|||
|
|
|||
|
(2)`*`
|
|||
|
|
|||
|
运算符`*`用来完成乘法。
|
|||
|
|
|||
|
```c
|
|||
|
int num = 5;
|
|||
|
printf("%i\n", num * num); // 输出 25
|
|||
|
```
|
|||
|
|
|||
|
(3)`/`
|
|||
|
|
|||
|
运算符`/`用来完成除法。注意,两个整数相除,得到还是一个整数。
|
|||
|
|
|||
|
```c
|
|||
|
float x = 6 / 4;
|
|||
|
printf("%f\n", x); // 输出 1.000000
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,尽管变量`x`的类型是`float`(浮点数),但是`6 / 4`得到的结果是`1.0`,而不是`1.5`。原因就在于 C 语言里面的整数除法是整除,只会返回整数部分,丢弃小数部分。
|
|||
|
|
|||
|
如果希望得到浮点数的结果,两个运算数必须至少有一个浮点数,这时 C 语言就会进行浮点数除法。
|
|||
|
|
|||
|
```c
|
|||
|
float x = 6.0 / 4; // 或者写成 6 / 4.0
|
|||
|
printf("%f\n", x); // 输出 1.500000
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,`6.0 / 4`表示进行浮点数除法,得到的结果就是`1.5`。
|
|||
|
|
|||
|
下面是另一个例子。
|
|||
|
|
|||
|
```c
|
|||
|
int score = 5;
|
|||
|
score = (score / 20) * 100;
|
|||
|
```
|
|||
|
|
|||
|
上面的代码,你可能觉得经过运算,`score`会等于`25`,但是实际上`score`等于`0`。这是因为`score / 20`是整除,会得到一个整数值`0`,所以乘以`100`后得到的也是`0`。
|
|||
|
|
|||
|
为了得到预想的结果,可以将除数`20`改成`20.0`,让整除变成浮点数除法。
|
|||
|
|
|||
|
```c
|
|||
|
score = (score / 20.0) * 100;
|
|||
|
```
|
|||
|
|
|||
|
(4)`%`
|
|||
|
|
|||
|
运算符`%`表示求模运算,即返回两个整数相除的余值。这个运算符只能用于整数,不能用于浮点数。
|
|||
|
|
|||
|
```c
|
|||
|
int x = 6 % 4; // 2
|
|||
|
```
|
|||
|
|
|||
|
负数求模的规则是,结果的正负号由第一个运算数的正负号决定。
|
|||
|
|
|||
|
```c
|
|||
|
11 % -5 // 1
|
|||
|
-11 % -5 // -1
|
|||
|
-11 % 5 // -1
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,第一个运算数的正负号(`11`或`-11`)决定了结果的正负号。
|
|||
|
|
|||
|
(5)赋值运算的简写形式
|
|||
|
|
|||
|
如果变量对自身的值进行算术运算,C 语言提供了简写形式,允许将赋值运算符和算术运算符结合成一个运算符。
|
|||
|
|
|||
|
- `+=`
|
|||
|
- `-=`
|
|||
|
- `*=`
|
|||
|
- `/=`
|
|||
|
- `%=`
|
|||
|
|
|||
|
下面是一些例子。
|
|||
|
|
|||
|
```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`的操作。
|
|||
|
|
|||
|
- `++`:自增运算符
|
|||
|
- `--`:自减运算符
|
|||
|
|
|||
|
```c
|
|||
|
i++; // 等同于 i = i + 1
|
|||
|
i--; // 等同于 i = i - 1
|
|||
|
```
|
|||
|
|
|||
|
这两个运算符放在变量的前面或后面,结果是不一样的。`++var`和`--var`是先执行自增或自减操作,再返回操作后`var`的值;`var++`和`var--`则是先返回操作前`var`的值,再执行自增或自减操作。
|
|||
|
|
|||
|
```c
|
|||
|
int i = 42;
|
|||
|
int j;
|
|||
|
|
|||
|
j = (i++ + 10);
|
|||
|
// i: 43
|
|||
|
// j: 52
|
|||
|
|
|||
|
j = (++i + 10)
|
|||
|
// i: 44
|
|||
|
// j: 54
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,自增运算符的位置差异,会导致变量`j`得到不同的值。这样的写法很容易出现意料之外的结果,为了消除意外,可以改用下面的写法。
|
|||
|
|
|||
|
```c
|
|||
|
/* 写法一 */
|
|||
|
j = (i + 10);
|
|||
|
i++;
|
|||
|
|
|||
|
/* 写法二 */
|
|||
|
i++;
|
|||
|
j = (i + 10);
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,变量`i`的自增运算与返回值是分离的两个步骤,这样就不太会出错,也提高了代码的可读性。
|
|||
|
|
|||
|
## 关系运算符
|
|||
|
|
|||
|
C 语言用于比较的表达式,称为“关系表达式”(relational expression),里面使用的运算符就称为“关系运算符”(relational operator),主要有下面6个。
|
|||
|
|
|||
|
- `>` 大于运算符
|
|||
|
- `<` 小于运算符
|
|||
|
- `>=` 大于等于运算符
|
|||
|
- `<=` 小于等于运算符
|
|||
|
- `==` 相等运算符
|
|||
|
- `!=` 不相等运算符
|
|||
|
|
|||
|
下面是一些例子。
|
|||
|
|
|||
|
```c
|
|||
|
a == b;
|
|||
|
a != b;
|
|||
|
a < b;
|
|||
|
a > b;
|
|||
|
a <= b;
|
|||
|
a >= b;
|
|||
|
```
|
|||
|
|
|||
|
关系表达式通常返回`0`或`1`,表示真伪。C 语言中,`0`表示伪,所有非零值表示真。比如,`20 > 12`返回`1`,`12 > 20`返回`0`。
|
|||
|
|
|||
|
关系表达式常用于`if`或`while`结构。
|
|||
|
|
|||
|
```c
|
|||
|
if (x == 3) {
|
|||
|
printf("x is 3.\n");
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
注意,相等运算符`==`与赋值运算符`=`是两个不一样的运算符,不要混淆。有时候,可能会不小心写出下面的代码,它可以运行,但很容易出现意料之外的结果。
|
|||
|
|
|||
|
```c
|
|||
|
if (x = 3) ...
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,原意是`x == 3`,但是不小心写成`x = 3`。这个式子表示对变量`x`赋值`3`,它的返回值为`3`,所以`if`判断总是为真。
|
|||
|
|
|||
|
为了防止出现这种错误,有的程序员喜欢将变量写在等号的右边。
|
|||
|
|
|||
|
```c
|
|||
|
if (3 == x) ...
|
|||
|
```
|
|||
|
|
|||
|
这样的话,如果把`==`误写成`=`,编译器就会报错。
|
|||
|
|
|||
|
```c
|
|||
|
/* 报错 */
|
|||
|
if (3 = x) ...
|
|||
|
```
|
|||
|
|
|||
|
另一个需要避免的错误是,多个关系运算符不宜连用。
|
|||
|
|
|||
|
```c
|
|||
|
i < j < k
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,连续使用两个小于运算符。这是合法表达式,不会报错,但是通常达不到想要的结果,即不是保证变量`j`的值在`i`和`k`之间。因为关系运算符是从左到右计算,所以实际执行的是下面的表达式。
|
|||
|
|
|||
|
```c
|
|||
|
(i < j) < k
|
|||
|
```
|
|||
|
|
|||
|
上面式子中,`i < j`返回`0`或`1`,所以最终是`0`或`1`与变量`k`进行比较。如果想要判断变量`j`的值是否在`i`和`k`之间,应该使用下面的写法。
|
|||
|
|
|||
|
```c
|
|||
|
i < j && j < k
|
|||
|
```
|
|||
|
|
|||
|
## 逻辑运算符
|
|||
|
|
|||
|
逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,主要有下面三个运算符。
|
|||
|
|
|||
|
- `!`:否运算符(改变单个表达式的真伪)。
|
|||
|
- `&&`:与运算符(两侧的表达式都为真,则为真,否则为伪)。
|
|||
|
- `||`:或运算符(两侧至少有一个表达式为真,则为真,否则为伪)。
|
|||
|
|
|||
|
下面是与运算符的例子。
|
|||
|
|
|||
|
```c
|
|||
|
if (x < 10 && y > 20)
|
|||
|
printf("Doing something!\n");
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,只有`x < 10`和`y > 20`同时为真,`x < 10 && y > 20`才会为真。
|
|||
|
|
|||
|
下面是否运算符的例子。
|
|||
|
|
|||
|
```c
|
|||
|
if (!(x < 12))
|
|||
|
printf("x is not less than 12\n");
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,由于否运算符`!`具有比`<`更高的优先级,所以必须使用括号,才能对表达式`x < 12`进行否运算。当然,合理的写法是`if (x >= 12)`,这里只是为了举例。
|
|||
|
|
|||
|
对于逻辑运算符来说,任何非零值都表示真,零值表示伪。比如,`5 || 0`会返回`1`,`5 && 0`会返回`0`。
|
|||
|
|
|||
|
逻辑运算符还有一个特点,它总是先对左侧的表达式求值,再对右边的表达式求值,这个顺序是保证的。如果左边的表达式满足逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为“短路”。
|
|||
|
|
|||
|
```c
|
|||
|
if (number != 0 && 12/number == 2)
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,如果`&&`左侧的表达式(`number != 0`)为伪,即`number`等于`0`时,右侧的表达式(`12/number == 2`)是不会执行的。因为这时左侧表达式返回`0`,整个`&&`表达式肯定为伪,就直接返回`0`,不再执行右侧的表达式了。
|
|||
|
|
|||
|
由于逻辑运算符的执行顺序是先左后右,所以下面的代码是有问题的。
|
|||
|
|
|||
|
```c
|
|||
|
while ((x++ < 10) && (x + y < 20))
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,执行左侧表达式后,变量`x`的值就已经变了。等到执行右侧表达式的时候,是用新的值在计算,这通常不是原始意图。
|
|||
|
|
|||
|
## 位运算符
|
|||
|
|
|||
|
C 语言提供一些位运算符,用来操作二进制位(bit)。
|
|||
|
|
|||
|
(1)取反运算符`~`
|
|||
|
|
|||
|
取反运算符`~`是一个一元运算符,用来将每一个二进制位变成相反值,即`0`变成`1`,`1`变成`0`。
|
|||
|
|
|||
|
```c
|
|||
|
// 返回 01101100
|
|||
|
~ 10010011
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,`~`对每个二进制位取反,就得到了一个新的值。
|
|||
|
|
|||
|
注意,`~`运算符不会改变变量的值,只是返回一个新的值。
|
|||
|
|
|||
|
(2)与运算符`&`
|
|||
|
|
|||
|
与运算符`&`将两个值的每一个二进制位进行比较,返回一个新的值。当两个二进制位都为`1`,就返回`1`,否则返回`0`。
|
|||
|
|
|||
|
```c
|
|||
|
// 返回 00010001
|
|||
|
10010011 & 00111101
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,两个八位二进制数进行逐位比较,返回一个新的值。
|
|||
|
|
|||
|
与运算符`&`可以与赋值运算符`=`结合,简写成`&=`。
|
|||
|
|
|||
|
```c
|
|||
|
int val = 3;
|
|||
|
val = val & 0377;
|
|||
|
|
|||
|
// 简写成
|
|||
|
val &= 0377;
|
|||
|
```
|
|||
|
|
|||
|
(3)或运算符`|`
|
|||
|
|
|||
|
或运算符`|`将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位只要有一个为`1`(包含两个都为`1`的情况),就返回`1`,否则返回`0`。
|
|||
|
|
|||
|
```c
|
|||
|
// 返回 10111111
|
|||
|
10010011 | 00111101
|
|||
|
```
|
|||
|
|
|||
|
或运算符`|`可以与赋值运算符`=`结合,简写成`|=`。
|
|||
|
|
|||
|
```c
|
|||
|
int val = 3;
|
|||
|
val = val | 0377;
|
|||
|
|
|||
|
// 简写为
|
|||
|
val |= 0377;
|
|||
|
```
|
|||
|
|
|||
|
(4)异或运算符`^`
|
|||
|
|
|||
|
异或运算符`^`将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位有且仅有一个为`1`,就返回`1`,否则返回`0`。
|
|||
|
|
|||
|
```c
|
|||
|
// 返回 10101110
|
|||
|
10010011 ^ 00111101
|
|||
|
```
|
|||
|
|
|||
|
异或运算符`^`可以与赋值运算符`=`结合,简写成`^=`。
|
|||
|
|
|||
|
```c
|
|||
|
int val = 3;
|
|||
|
val = val ^ 0377;
|
|||
|
|
|||
|
// 简写为
|
|||
|
val ^= 0377;
|
|||
|
```
|
|||
|
|
|||
|
(5)左移运算符`<<`
|
|||
|
|
|||
|
左移运算符`<<`将左侧运算数的每一位,向左移动指定的位数,尾部空出来的位置使用`0`填充。
|
|||
|
|
|||
|
```c
|
|||
|
// 1000101000
|
|||
|
10001010 << 2
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,`10001010`的每一个二进制位,都向左侧移动了两位。
|
|||
|
|
|||
|
左移运算符相当于将运算数乘以2的指定次方,比如左移2位相当于乘以4(2的2次方)。
|
|||
|
|
|||
|
左移运算符`<<`可以与赋值运算符`=`结合,简写成`<<=`。
|
|||
|
|
|||
|
```c
|
|||
|
int val = 1;
|
|||
|
val = val << 2;
|
|||
|
|
|||
|
// 简写为
|
|||
|
val <<= 2;
|
|||
|
```
|
|||
|
|
|||
|
(6)右移运算符`>>`
|
|||
|
|
|||
|
右移运算符`>>`将左侧运算数的每一位,向右移动指定的位数,尾部无法容纳的值将丢弃,头部空出来的位置使用`0`填充。
|
|||
|
|
|||
|
```c
|
|||
|
// 返回 00100010
|
|||
|
10001010 >> 2
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,`10001010`的每一个二进制位,都向右移动两位。最低的两位`10`被丢弃,头部多出来的两位补`0`,所以最后得到`00100010`。
|
|||
|
|
|||
|
注意,右移运算符最好只用于无符号整数,不要用于负数。因为不同系统对于右移后如何处理负数的符号位,有不同的做法,可能会得到不一样的结果。
|
|||
|
|
|||
|
右移运算符相当于将运算数除以2的指定次方,比如右移2位就相当于除以4(2的2次方)。
|
|||
|
|
|||
|
右移运算符`>>`可以与赋值运算符`=`结合,简写成`>>=`。
|
|||
|
|
|||
|
```c
|
|||
|
int val = 1;
|
|||
|
val = val >> 2;
|
|||
|
|
|||
|
// 简写为
|
|||
|
val >>= 2;
|
|||
|
```
|
|||
|
|
|||
|
## 逗号运算符
|
|||
|
|
|||
|
逗号运算符用于将多个表达式写在一起,从左到右依次运行每个表达式。
|
|||
|
|
|||
|
```c
|
|||
|
x = 10, y = 20;
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,有两个表达式(`x = 10`和`y = 20`),逗号使得它们可以放在同一条语句里面。
|
|||
|
|
|||
|
逗号运算符返回最后一个表达式的值,作为整个语句的值。
|
|||
|
|
|||
|
```c
|
|||
|
int x;
|
|||
|
x = 1, 2, 3;
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,逗号的优先级低于赋值运算符,所以先执行赋值运算,再执行逗号运算,变量`x`等于`1`。
|
|||
|
|
|||
|
## 运算优先级
|
|||
|
|
|||
|
优先级指的是,如果一个表达式包含多个运算符,哪个运算符应该优先执行。各种运算符的优先级是不一样的。
|
|||
|
|
|||
|
```c
|
|||
|
3 + 4 * 5;
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,表达式`3 + 4 * 5`里面既有加法运算符(`+`),又有乘法运算符(`*`)。由于乘法的优先级高于加法,所以会先计算`4 * 5`,而不是先计算`3 + 4`。
|
|||
|
|
|||
|
如果两个运算符优先级相同,则根据运算符是左结合,还是右结合,决定执行顺序。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符(`=`)。
|
|||
|
|
|||
|
```c
|
|||
|
5 * 6 / 2;
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,`*`和`/`的优先级相同,它们都是左结合运算符,所以从左到右执行,先计算`5 * 6`,再计算`6 / 2`。
|
|||
|
|
|||
|
运算符的优先级顺序很复杂。下面是部分运算符的优先级顺序(按照优先级从高到低排列)。
|
|||
|
|
|||
|
- 圆括号(`()`)
|
|||
|
- 自增运算符(`++`),自减运算符(`--`)
|
|||
|
- 一元运算符(`+`和`-`)
|
|||
|
- 乘法(`*`),除法(`/`)
|
|||
|
- 加法(`+`),减法(`-`)
|
|||
|
- 关系运算符(`<`、`>`等)
|
|||
|
- 赋值运算符(`=`)
|
|||
|
|
|||
|
由于圆括号的优先级最高,可以使用它改变其他运算符的优先级。
|
|||
|
|
|||
|
```c
|
|||
|
int x = (3 + 4) * 5;
|
|||
|
```
|
|||
|
|
|||
|
上面示例中,由于添加了圆括号,加法会先于乘法进行运算。
|
|||
|
|
|||
|
完全记住所有运算符的优先级没有必要,解决方法是多用圆括号,防止出现意料之外的情况,也有利于提高代码的可读性。
|