第四章——选择结构 第二节——逻辑运算

第四章——选择结构 第二节——逻辑运算

亓翎_Re-Tikara Lv5

逻辑运算

  在之前的关系表达式中,只能描述单一条件,那如何描述a条件同时b条件之类的呢?此处就要用到逻辑表达式。

逻辑运算符

种类

符号 名称 功能描述 示例
&& 逻辑与 当两个操作数都为真时,结果才为真;否则为假 int a = 5, b = 10; if(a > 0 && b > 0)
|| 逻辑或 当两个操作数中至少一个为真时,结果为真;都为假时才为假 int x = 3, y = -1; if(x != 0 || y != 0)
! 逻辑非(取反) 单目运算符,将其操作数的真假值取反:真变假,假变真 int flag = 0; if(!flag)

💡 在C语言中,任何非0值都被视为“真”(true),而只有 0 被视为“假”(false)。

1
2
3
4
int a = 5;
if(a) {
// 会执行,因为5是非0值
}

关于if(a),此处的判断条件是a是否是非0值。

逻辑运算真值表

a b !a !b a && b a || b

逻辑运算符的优先级

  在C语言中,!、&&,||的优先级依次降低。

示例:优先级的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 优先级最高:逻辑非!
int a = 1, b = 0;
if (!a && b) {
// 等价于 (!a) && b
// 先计算 !a=0,再 0 && 0 → 0,不执行
}

// 优先级其次:逻辑与&&
int x = 1, y = 0;
if (x && y || 1) {
// 先计算 x && y → 0,再 0 || 1 → 1,执行
}

// 优先级最低:逻辑或||
int m = 0, n = 1;
if (m || n && !m) {
// 先计算 n && !m → 1 && 1 → 1,再 m || 1 → 1,执行
}
逻辑运算符 优先级 示例说明
! 最高 !a && b 先计算 \!a 再计算 &&
&& 其次 a && b || c 先计算 && 再计算 ||
|| 最低 a || b && c 先计算 b && c 再计算 ||

逻辑运算符的结合方向

示例:结合方向的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 从右向左结合:逻辑非!
int flag = 0;
if (!flag && 5) {
// 等价于 (!flag) && 5 → 1 && 1 → 执行
}

// 从左向右结合:逻辑与&&
int a = 1, b = 1, c = 0;
if (a && b && c) {
// 等价于 ((a && b) && c) → (1 && 1) && 0 → 0,不执行
}

// 从左向右结合:逻辑或||
int x = 0, y = 1, z = 0;
if (x || y || z) {
// 等价于 ((x || y) || z) → (0 || 1) || 0 → 1,执行
}
逻辑运算符 结合方向 示例说明
! 从右向左 !flag && x 等价于 (!flag) && x
&& 从左向右 a && b && c 等价于 (a && b) && c
|| 从左向右 x || y || z 等价于 (x || y) || z

短路特性:

  短路特性(Short-circuit Evaluation)是逻辑运算符(&&||)在C语言中的行为规则:
  当逻辑运算的结果已经可以确定时,跳过后续操作数的计算。这种机制提高了程序效率,并能避免潜在错误。


1. 短路特性的工作原理

(1) 逻辑与(&&

  • 规则
    • 如果第一个操作数为假(0),整个表达式的结果必为假,跳过第二个操作数的计算
  • 真值表
    a b a && b
    0 任意 0
    1 0 0
    1 1 1

(2) 逻辑或(||

  • 规则
    • 如果第一个操作数为真(非0),整个表达式的结果必为真,跳过第二个操作数的计算
  • 真值表
    | a | b | a || b |
    |——|——|——–|
    | 1 | 任意 | 1 |
    | 0 | 0 | 0 |
    | 0 | 1 | 1 |

2. 短路特性的应用场景

(1) 避免运行时错误

空指针检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

void process(int *ptr) {
if (ptr && *ptr > 0) { // 先检查 ptr 是否为 NULL
printf("值为 %d\n", *ptr);
} else {
printf("指针为空或值无效\n");
}
}

int main() {
int *p = NULL;
process(p); // 输出:指针为空或值无效
return 0;
}
  • 原理ptr && *ptr > 0 中,如果 ptrNULL&& 会短路,跳过 *ptr 的解引用,避免崩溃。

避免除零

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main() {
int a = 5;
int b = 0;

if (b != 0 && a / b > 1) { // 如果 b 为 0,直接跳过除法
printf("结果有效\n");
} else {
printf("除数不能为 0\n"); // 输出:除数不能为 0
}

return 0;
}

(2) 优化性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <time.h>

int expensive_check() {
// 模拟耗时计算
for (int i = 0; i < 100000000; ++i);
return 0; // 返回 false
}

int main() {
clock_t start = clock();

if (0 && expensive_check()) { // 短路跳过 expensive_check()
// ...
}

double duration = (double)(clock() - start) / CLOCKS_PER_SEC;
printf("耗时: %.6f 秒\n", duration); // 耗时极低
return 0;
}

(3) 默认值赋值

1
2
3
4
5
6
7
8
#include <stdio.h>

int main() {
int *ptr = NULL;
int value = (ptr != NULL) ? *ptr : 0; // 安全获取值
printf("value = %d\n", value); // 输出:0
return 0;
}

4. 短路特性的注意事项

(1) 条件顺序的重要性

  • 逻辑与(&&:将更可能为 的条件放在左侧。

    1
    if (is_valid && is_ready) { ... }  // 如果 is_valid 为假,直接跳过 is_ready
  • 逻辑或(||:将更可能为 的条件放在左侧。

    1
    if (is_cached || fetch_from_database()) { ... }  // 如果缓存存在,直接跳过数据库查询

(2) 副作用的影响

1
2
3
int i = 0;
int result = (i++ > 0) && (i++ > 1); // i 最终为 1
printf("%d", i); // 输出 1
  • 解释i++ > 0 为假,i++ > 1 不会被执行,因此 i 只自增一次。

(3) 优先级问题

1
if (a || b && c) { ... }  // 等价于 if (a || (b && c))
  • 建议:使用括号明确优先级:
    1
    if ((a || b) && c) { ... }

5. C语言与其他语言的差异

语言 逻辑与 逻辑或 特性说明
C && `
Python and or 返回实际值(非布尔值)
JavaScript && `
C++ && `

6. 总结

优点

  • 提高性能:跳过不必要的计算。
  • 防止错误:避免空指针、除零等运行时异常。
  • 简化代码:如默认值赋值(value = a || b)。

潜在问题

  • 副作用:依赖短路跳过的表达式可能导致逻辑错误。
  • 可读性:复杂条件可能导致代码难以理解。

最佳实践

  1. 显式括号:明确逻辑运算的优先级。
  2. 避免副作用:不依赖短路跳过的表达式中的副作用。
  3. 条件顺序优化:将更可能触发短路的条件放在左侧。

位运算符

按位异或运算符

运算符种类

符号 名称 功能描述 示例
& 按位与 对两个操作数的每一位执行逻辑000与操作 int a = 5 & 3; // 1
| 按位或 对两个操作数的每一位执行逻辑或操作 int b = 5 | 3; // 7
^ 按位异或 当两个操作数的对应位不同时为1,相同时为0 int c = 5 ^ 3; // 6
~ 按位取反 单目运算符,将操作数的每一位取反 int d = ~5; // 依赖于系统位数
<< 左移 将操作数的二进制位向左移动指定的位数 int e = 5 << 1; // 10
>> 右移 将操作数的二进制位向右移动指定的位数 int f = 5 >> 1; // 2

按位异或特性

1
2
3
4
5
// 交换两个变量的值(不使用临时变量)
int a = 10, b = 20;
a = a ^ b; // a becomes 26 (11010)
b = a ^ b; // b becomes 10 (1010)
a = a ^ b; // a becomes 20 (10100)

典型应用场景

  1. 数据加密:使用异或进行简单加密
1
2
3
4
5
char data = 'A';
char key = 'K';
data = data ^ key; // 加密
// 重复异或操作可解密
data = data ^ key; // 恢复为'A'
  1. 位掩码操作
1
2
3
4
// 提取第3位和第1位(从0开始计数)
int flags = 0b101010;
int mask = 0b1010; // 二进制掩码
int result = flags & mask; // 保留指定位置

注意事项

  • 逻辑运算 vs 位运算
    • &&|| 返回布尔值(0或1)
    • &| 返回整数值
  • 移位陷阱
    1
    2
    int x = -1;
    int y = x >> 1; // 取决于系统实现(算术右移或逻辑右移)
  • 结合性问题
    1
    int a = 5 ^ 3 & 0x0F;  // 等价于 5 ^ (3 & 0x0F) 因为&优先级高于^
  • 标题: 第四章——选择结构 第二节——逻辑运算
  • 作者: 亓翎_Re-Tikara
  • 创建于 : 2026-02-07 02:21:28
  • 更新于 : 2026-02-10 02:48:46
  • 链接: https://re-tikara.fun/Blog/posts/ae06b0ca/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。