C 语言中常用的运算符,如下表所示
一、算术运算符
1.1. 加减乘除
1 | int a = 10; |
注意:乘法 ab,并不是 axb;除法 a/b,并不是 a÷b*
1 | int a = 10; |
1.1.1. 整数与整数进行运算,结果依然是整数:
1 | printf("%d\n", 7 / 4); // 输出:1 |
整数除以整数时,直接剔除小数部分,不会四舍五入
1.1.2. 浮点数与浮点数进行运算,结果依然是浮点数:
1 | printf("%f\n", 7.0 / 4.0); // 输出:1.750000 |
1.1.3. 整数与浮点数进行运算,结果是浮点数:
1 | // 以下结果全部输出:1.750000 |
1.2. 模
模运算符的作用是求余数,所以,也叫做求余运算符、取余运算符、余数运算符。
1 | printf("%d\n", 20 % 5); // 输出:0 |
1.2.1. 模运算符不能用在浮点数上,否则编译器会报错:
1 | printf("%d\n", 10.0 % 5); |
1.2.2. 同样的,0 不能作为被除数取余:
1 | printf("%d", 10 / 0); |
1.2.3. 模运算结果的正负性跟随运算符左边的操作数:
1 | printf("%d\n", 10 % 7); // 输出:3 |
案例:提示用户输入秒数,输出格式 00 时 00 分 00 秒
1 | printf("请输入秒数:"); |
1.3. 隐式类型转换
1.3.1. 在进行一些算数运算时,小类型会被隐式转换成大类型
1 | char < short < int < long < long long < float < double < long double |
案例:
1 | double d = 10.5; |
1.3.2. 任何小于 int 的整数类型,在运算时会隐式转换为 int 类型
案例:
1 | char c = 97; |
经典案例:
1 | printf("%zd\n", sizeof('A')); // 输出:4 |
为什么会输出 4 呢?因为在 C 语言中,字符的本质是整数,而我们没有声明要把这个字符当 char 来使用,所以是 4 个字节。
1.4. 运算符的优先级
当一个表达式中同时使用多个运算符时,会根据运算符的优先级和结合性,来决定运算符的执行顺序。
1 | printf("%d\n", 4 + 2 - 1); // 输出:5 |
- 优先级越高(优先级值越小),越先被执行;
- 优先级一样,根据结合性决定执行顺序。
案例:
可以使用小括号来调整优先级
1 | int a = 1, b = 2, c = 3; |
当自己记不住(不确定)运算符的优先级时,多使用小括号即可,根本不用刻意去背运算符的优先级。当然,为了代码的可读性,也应该多使用小括号。
1 | int a = 1, b = 2, c = 3; |
二、赋值运算符
案例:
1 | int a = 40, b = 30, c = 20, d = 10; |
2.1. 自增/自减运算符
自增/自减运算符包括:
- 前缀
- 自增运算符:++a
- 自减运算符:–a
- 后缀
- 自增运算符:a++
- 自减运算符:a–
1 | int a = 1; |
案例一:
1 | int a = 1, b = 3; |
案例二:
1 | int a = 1, b = 3; |
注意:以下代码不容易理解,可读性差,结果具有不确定性(有些编译器会报警告),所以不建议在实际开发中编写此类代码。
1 | int a = 1; |
2.2. 最大吞噬规则
当多个运算符紧挨在一起时,编译器会按照最大吞噬规则去解析。
案例一:
1 | int a = 1, b = 2; |
案例二(错误代码):
1 | int a = 1, b = 2; |
三、比较运算符
比较运算符,也称为关系运算符,运算结果只可能 shi 整数 0 和 1。
- 当指定关系成立时,就返回整数 1,表明是真的,正确的;
- 当指定关系不成立时,就返回整数 0,表明是假的,错误的。
案例:
1 | int a = 10; |
四、逻辑运算符
在 C 语言中,任何值都有真假性:
- 任何非 0 的值都为“真”(比如 10、1、19、-92、”123”、”kk”、’a’等);
- 只有数值 0 才为“假”(比如 0、0.0、’\0’(空字符)等)。
逻辑运算符的运算数可以是任何值,运算结果要么是真、要么是假:
- 运算结果为真,返回整数 1
- 运算结果为假,返回整数 0
4.1. 逻辑非(!)
!a
(对 a 的真假进行取反,就是说,真的变假,假的变真):
- 如果 a 是真,则!a 的运算结果是假,返回整数 0;
如果 a 是假,则!a 的运算结果是真,返回整数 1; - 优先级:逻辑非 > 加减乘除 > 关系运算符;
- 连续偶数个
!!
相当于没有做任何取反; - 字符串不管是否包含字符(即长度>=0),一定是真。
1 | int a = 10; |
4.2. 逻辑与(&&)
a && b
- 只有 a、b 都为真时,运算结果才为真,返回整数 1
- 只要 a、b 其一为假,运算结果就为假,返回整数 0
- 在表示范围时,逻辑与(&&)是取交集(∩),
exp(a) ∩ exp(b)
- 短路现象:如果多个条件同时判断,只要前面条件判断为假,后面条件不会继续执行,直接跳过
- 优先级:加减乘除 > 关系运算符 > 逻辑与
案例一:
1 | int a = 10; |
案例二(逻辑与的短路现象):
1 | int a = 1, b =2, c = 3, d = 4, m = 2, n = 2; |
4.3. 逻辑或(||)
a || b
- 只要 a、b 其一为真,运算结果就为真,返回整数 1;
- 只有 a、b 都为假时,运算结果才为假,返回整数 0;
- 在表示范围时,逻辑或(||)是取并集(∪),
exp(a) ∪ exp(b)
; - 短路现象:如果多个条件同时判断,只要前面条件判断为真,后面条件不会继续执行,直接跳过;
- 优先级:加减乘除 > 关系运算符 > 逻辑与 > 逻辑或。
案例一:
1 | int a = 10; |
案例二(逻辑或的短路现象):
1 | int a = 1, b =2, c = 3, d = 4, m = 2, n = 2; |
五、条件运算符
条件运算符,一般也叫做三目运算符或三元运算符。
格式:
1 | a ? b : c |
- 如果 a 为真,返回 b
- 如果 a 为假,返回 c
案例一:
1 | int a = 5 ? 1 : 2; |
案例二:
1 | int a = 1, b = 2, c = 3; |
案例三:
1 | /* |
六、逗号运算符
表达式(a, b, ...)
,从左到右依次执行表达式,返回最后一个表达式的运算结果。
1 | int a = (10, 20, 30); |
七、位运算符
位运算符属于算术运算符,位运算符的运算数只能是整数。
7.1. 位移运算符
位移运算符包含按位左移(也叫逐位左移)和按位右移(也叫逐位右移),移动的位数不能是负数。
7.1.1. 按位左移(也叫逐位左移)
格式:a << b
特点:a 左移 b 位,低位补 0
案例一:
1 | int a = 5; |
整数 5 向左移 3 位后
$$2^0 + 2^2 => 2^3 + 2^5 = (2^0 + 2^2) * 2^3$$
案例二:
1 | int a = -5; |
a << b
结果:$a*2^b$
7.1.2. 按位右移(也叫逐位右移)
格式:a >> b
特点:a 右移 b 位,高位用符号位填充
案例一:
1 | int a = 40; |
整数 40 向右移 3 位后
$$2^3 + 2^5 => 2^0 + 2^2 = (2^3 + 2^5) * 2^{-3} = (2^3 + 2^5) / 2^3$$
案例二:
1 | int a = -40; |
a >> b
结果:$a/2^b$
7.2. 按位逻辑运算符
7.2.1. 按位非
按位非也叫做按位取反。
格式:~a
特点:将 a 的所有二进制位(包括符号位)取反,即 0 变 1,1 变 0。
案例:
1 | int c = 0; |
~a
结果:$-(a+1)$
7.2.2. 按位与
格式:a & b
特点:
- 只有当 2 个二进制位都为 1 时,运算结果才为 1
- 只要有 1 个二进制位为 0,运算结果就为 0
示例:
1 | int a = 140; |
7.2.3. 按位或
格式:a | b
特点:
- 只要有 1 个二进制位为 1,运算结果就为 1
- 只有当 2 个二进制位都为 0 时,运算结果才为 0
示例:
1 | int a = 140; |
7.2.4. 按位异或
格式:a ^ b
特点:
- 当 2 个二进制位的值不相等时,运算结果为 1
- 当 2 个二进制位的值相等时,运算结果为 0
运算顺序:
a ^ b == b ^ a
(a ^ b) ^ c == a ^ (b ^ c) == (a ^ c) ^ b
示例:
1 | int a = 140; |
经典案例:
a ^ 0
的结果是 a
1 | int a = 10; |
a ^ a
的结果是 0
1 | int a = -10; |
7.2.5. 场景练习
在开发中,尽量使用位运算取代乘除模运算,因为位运算的效率比他们高很多。
场景一:用a & 1
判断a
的奇偶性
1 | // 方法一:利用对2取余结果为0或1进行判断,0为偶数,1为奇数 |
场景二:用a << b
取代a * 2^b(2的b次方)
1 | // 编译器可能会警告,加减乘除模的运算级别优先于位移运算 |
场景三:用a >> b
取代a / 2^b(2的b次方)
1 | int a = 25; |
场景四:不使用第三方变量交换 2 个整形变量的值
1 | int a = 10; |
思考:上面由先加后减方法推出了先减后加,能否根据先乘后除推出先除后乘呢?
答案是否定的,因为两数相除的话会影响数据的精度(a / b 是取整操作)。