1
0
wiki/docs/开发/C/运算符.md

466 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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位相当于乘以42的2次方
左移运算符`<<`可以与赋值运算符`=`结合,简写成`<<=`。
```c
int val = 1;
val = val << 2;
// 简写为
val <<= 2;
```
6右移运算符`>>`
右移运算符`>>`将左侧运算数的每一位,向右移动指定的位数,尾部无法容纳的值将丢弃,头部空出来的位置使用`0`填充。
```c
// 返回 00100010
10001010 >> 2
```
上面示例中,`10001010`的每一个二进制位,都向右移动两位。最低的两位`10`被丢弃,头部多出来的两位补`0`,所以最后得到`00100010`。
注意,右移运算符最好只用于无符号整数,不要用于负数。因为不同系统对于右移后如何处理负数的符号位,有不同的做法,可能会得到不一样的结果。
右移运算符相当于将运算数除以2的指定次方比如右移2位就相当于除以42的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;
```
上面示例中,由于添加了圆括号,加法会先于乘法进行运算。
完全记住所有运算符的优先级没有必要,解决方法是多用圆括号,防止出现意料之外的情况,也有利于提高代码的可读性。