第十六 逻辑运算
逻辑运算(Logical Operation)是计算机科学和数学中最基础的运算之一,它处理的是布尔值(true 或 false)之间的关系。在 Rust 中,逻辑运算不仅是条件判断的核心,还深刻影响着程序的控制流和性能。
一、布尔代数基础
1.1 什么是布尔代数
布尔代数(Boolean Algebra)由英国数学家乔治·布尔(George Boole)在 19 世纪中叶创立,是一种用于描述逻辑关系的代数系统。它只有两个值:true(真,记为 1)和 false(假,记为 0)。
布尔代数是现代数字电路、计算机逻辑设计和编程语言的理论基础。
1.2 基本逻辑运算
布尔代数定义了三种基本运算:
| 运算 | 符号 | Rust 运算符 | 含义 |
|---|---|---|---|
| 与(AND) | ∧ 或 · | && | 两个条件同时为真,结果才为真 |
| 或(OR) | ∨ 或 + | || | 两个条件至少一个为真,结果就为真 |
| 非(NOT) | ¬ 或 ¯ | ! | 条件取反 |
1.3 真值表
真值表(Truth Table)是表示逻辑运算所有可能输入和对应输出的表格。
与运算(AND)真值表:
| p | q | p ∧ q(p && q) |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
口诀:“有假则假,全真才真”
或运算(OR)真值表:
| p | q | p ∨ q(p || q) |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
口诀:“有真则真,全假才假”
非运算(NOT)真值表:
| p | ¬p(!p) |
|---|---|
| 0 | 1 |
| 1 | 0 |
口诀:“真假互换”
1.4 复合逻辑运算
除了三种基本运算,布尔代数还定义了两种常用的复合运算:
异或(XOR,Exclusive OR)
| p | q | p ⊕ q |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
口诀:“相同为假,不同为真”
异或在 Rust 中没有专门的逻辑运算符,但可以用
(p || q) && !(p && q)或位运算符^实现。
同或(XNOR,Equivalence)
| p | q | p ↔ q |
|---|---|---|
| 0 | 0 | 1 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
口诀:“相同为真,不同为假”(异或的反面)
1.5 布尔代数的基本定律
布尔代数遵循一系列重要的定律,这些定律在简化逻辑表达式和优化程序条件时非常有用。
交换律(Commutative Laws)
p ∧ q = q ∧ p
p ∨ q = q ∨ p
结合律(Associative Laws)
(p ∧ q) ∧ r = p ∧ (q ∧ r)
(p ∨ q) ∨ r = p ∨ (q ∨ r)
分配律(Distributive Laws)
p ∧ (q ∨ r) = (p ∧ q) ∨ (p ∧ r)
p ∨ (q ∧ r) = (p ∨ q) ∧ (p ∨ r)
同一律(Identity Laws)
p ∧ true = p
p ∨ false = p
零律(Domination Laws)
p ∧ false = false
p ∨ true = true
幂等律(Idempotent Laws)
p ∧ p = p
p ∨ p = p
补律(Complement Laws)
p ∧ ¬p = false
p ∨ ¬p = true
¬(¬p) = p
德摩根定律(De Morgan’s Laws)
德摩根定律是最重要的布尔代数定律之一,在编程中经常用于简化复杂的条件表达式:
¬(p ∧ q) = ¬p ∨ ¬q
¬(p ∨ q) = ¬p ∧ ¬q
用 Rust 代码表示:
#![allow(unused)]
fn main() {
// 德摩根定律示例
fn de_morgan_example() {
let p = true;
let q = false;
// ¬(p ∧ q) = ¬p ∨ ¬q
let left1 = !(p && q);
let right1 = !p || !q;
assert_eq!(left1, right1);
// ¬(p ∨ q) = ¬p ∧ ¬q
let left2 = !(p || q);
let right2 = !p && !q;
assert_eq!(left2, right2);
}
}
德摩根定律的通俗理解:
- “不是(A 且 B)” 等价于 “不是 A 或不是 B”
- “不是(A 或 B)” 等价于 “不是 A 且不是 B”
吸收律(Absorption Laws)
p ∧ (p ∨ q) = p
p ∨ (p ∧ q) = p
二、Rust 中的逻辑运算符
2.1 逻辑运算符概览
Rust 提供了三种逻辑运算符,分别对应布尔代数的三种基本运算:
#![allow(unused)]
fn main() {
&& // 逻辑与(conditional AND)
|| // 逻辑或(conditional OR)
! // 逻辑非(NOT)
}
fn main() {
let a = true;
let b = false;
println!("a && b = {}", a && b); // false
println!("a || b = {}", a || b); // true
println!("!a = {}", !a); // false
}
2.2 逻辑与(&&)
逻辑与运算符 && 要求两边的操作数都为 true,结果才为 true。
fn main() {
let x = 5;
let y = 10;
// 两个条件都满足
if x > 0 && y > 0 {
println!("x 和 y 都是正数");
}
// 一个条件不满足
if x > 0 && y < 0 {
println!("这不会被执行");
}
}
逻辑与的真值表(Rust):
| 表达式 | 结果 |
|---|---|
true && true | true |
true && false | false |
false && true | false |
false && false | false |
2.3 逻辑或(||)
逻辑或运算符 || 要求两边的操作数至少有一个为 true,结果就为 true。
fn main() {
let score = 85;
// 满足任一条件即可
if score >= 90 || score >= 60 && score < 90 {
println!("成绩合格");
}
// 更清晰的写法
if score >= 60 {
println!("成绩合格");
}
}
逻辑或的真值表(Rust):
| 表达式 | 结果 |
|---|---|
true || true | true |
true || false | true |
false || true | true |
false || false | false |
2.4 逻辑非(!)
逻辑非运算符 ! 对布尔值取反。
fn main() {
let is_raining = false;
if !is_raining {
println!("今天不下雨,可以出门");
}
// 双重否定等于肯定
assert_eq!(!!is_raining, is_raining);
}
逻辑非的真值表(Rust):
| 表达式 | 结果 |
|---|---|
!true | false |
!false | true |
2.5 逻辑运算符与位运算符的区别
Rust 中有两套看似相似但功能不同的运算符,初学者容易混淆:
| 运算符 | 类型 | 操作数 | 用途 | 示例 |
|---|---|---|---|---|
&& | 逻辑与 | 布尔值 | 逻辑判断 | true && false |
|| | 逻辑或 | 布尔值 | 逻辑判断 | true || false |
! | 逻辑非 | 布尔值 | 逻辑取反 | !true |
& | 位与 | 整数 | 按位运算 | 0b1010 & 0b1100 |
| | 位或 | 整数 | 按位运算 | 0b1010 | 0b1100 |
~ | 位非 | 整数 | 按位取反 | ~0b1010 |
^ | 位异或 | 整数 | 按位运算 | 0b1010 ^ 0b1100 |
fn main() {
// 逻辑运算:操作数和结果都是 bool
let logical = true && false;
println!("逻辑与: {}", logical); // false
// 位运算:操作数和结果都是整数
let bitwise = 0b1010 & 0b1100;
println!("位与: {:04b}", bitwise); // 1000
// 注意:& 和 | 也可以用于 bool,但不会短路
let a = true & false; // false,但两边都会求值
let b = true | false; // true,但两边都会求值
}
关键区别:
&&和||是短路运算符,只用于bool类型&和|是非短路运算符,可用于整数位运算,也可用于bool(但两边都会求值)
三、短路求值(Short-Circuit Evaluation)
3.1 什么是短路求值
短路求值是逻辑运算的一个重要特性:
- 对于
&&(逻辑与):如果左边的表达式为false,右边的表达式不会执行,因为无论右边是什么,结果都一定是false。 - 对于
||(逻辑或):如果左边的表达式为true,右边的表达式不会执行,因为无论右边是什么,结果都一定是true。
fn main() {
let a = false;
// 由于 a 是 false,expensive_function() 不会被执行
if a && expensive_function() {
println!("不会执行到这里");
}
let b = true;
// 由于 b 是 true,expensive_function() 不会被执行
if b || expensive_function() {
println!("会执行到这里");
}
}
fn expensive_function() -> bool {
println!("执行了耗时操作...");
true
}
3.2 短路求值的实际应用
应用一:避免不必要的计算
fn main() {
let data: Vec<i32> = vec![];
// 先检查是否为空,避免对空数组进行索引操作
if !data.is_empty() && data[0] > 0 {
println!("第一个元素是正数");
}
// 如果 data 为空,data[0] 不会被执行,避免了 panic
}
应用二:安全地进行空值检查
fn main() {
let maybe_value: Option<i32> = None;
// 先检查 Some,再解包
if maybe_value.is_some() && maybe_value.unwrap() > 0 {
println!("值是正数");
}
// 更地道的 Rust 写法
if let Some(value) = maybe_value {
if value > 0 {
println!("值是正数: {}", value);
}
}
}
应用三:设置默认值
#![allow(unused)]
fn main() {
fn get_config_value(user_value: Option<i32>) -> i32 {
// 如果 user_value 为 None,则使用默认值 42
user_value.unwrap_or(42)
}
// 或者用逻辑运算的思路(虽然 Rust 中更常用 unwrap_or)
fn get_config_value_alt(user_value: Option<i32>) -> i32 {
if user_value.is_some() {
user_value.unwrap()
} else {
42
}
}
}
3.3 短路求值的注意事项
虽然短路求值通常是有益的,但在某些情况下需要注意:
fn main() {
let mut counter = 0;
// 由于短路,counter 只增加一次
let result = increment(&mut counter) || increment(&mut counter);
println!("counter = {}", counter); // 输出 1,不是 2
// 如果需要两边都执行,使用非短路运算符 |
counter = 0;
let result2 = increment(&mut counter) | increment(&mut counter);
println!("counter = {}", counter); // 输出 2
}
fn increment(counter: &mut i32) -> bool {
*counter += 1;
true
}
注意: 当需要确保所有副作用(如函数调用、变量修改)都执行时,应该使用非短路的
&和|运算符。
四、逻辑运算在 Rust 中的高级应用
4.1 复杂的条件表达式
在实际编程中,我们经常需要组合多个条件:
fn can_vote(age: u32, is_citizen: bool, has_criminal_record: bool) -> bool {
// 必须成年、是公民、没有犯罪记录
age >= 18 && is_citizen && !has_criminal_record
}
fn main() {
println!("可以投票: {}", can_vote(20, true, false)); // true
println!("可以投票: {}", can_vote(16, true, false)); // false
println!("可以投票: {}", can_vote(20, false, false)); // false
println!("可以投票: {}", can_vote(20, true, true)); // false
}
4.2 利用德摩根定律简化条件
德摩根定律可以帮助我们简化复杂的否定条件:
fn main() {
let a = true;
let b = false;
// 原始写法:判断"不是(a 和 b 都为真)"
let condition1 = !(a && b);
// 用德摩根定律简化:"a 为假 或 b 为假"
let condition2 = !a || !b;
assert_eq!(condition1, condition2);
// 实际应用:检查用户输入是否有效
let has_username = true;
let has_password = true;
// 原始写法
let is_invalid1 = !(has_username && has_password);
// 简化写法(更易读)
let is_invalid2 = !has_username || !has_password;
assert_eq!(is_invalid1, is_invalid2);
}
4.3 逻辑运算与类型系统
Rust 的类型系统与逻辑运算有深刻的联系:
// Rust 中没有隐式类型转换,逻辑运算必须作用于 bool
fn main() {
let x = 1;
// let result = x && true; // 错误!x 是 i32,不是 bool
// 必须显式转换
let result = (x != 0) && true;
println!("{}", result); // true
}
4.4 逻辑运算与模式匹配
在 Rust 中,模式匹配往往比复杂的逻辑运算更清晰:
fn describe_number(n: i32) -> &'static str {
// 使用逻辑运算
if n > 0 && n % 2 == 0 {
"正偶数"
} else if n > 0 && n % 2 != 0 {
"正奇数"
} else if n < 0 {
"负数"
} else {
"零"
}
}
fn describe_number_match(n: i32) -> &'static str {
// 使用模式匹配(更地道)
match n {
0 => "零",
n if n > 0 && n % 2 == 0 => "正偶数",
n if n > 0 => "正奇数",
_ => "负数",
}
}
fn main() {
println!("{}", describe_number(4)); // 正偶数
println!("{}", describe_number_match(4)); // 正偶数
}
五、逻辑运算的实际案例
5.1 用户权限校验
struct User {
name: String,
is_active: bool,
is_admin: bool,
age: u32,
}
impl User {
fn can_access_admin_panel(&self) -> bool {
// 必须同时满足:账号活跃、是管理员、已成年
self.is_active && self.is_admin && self.age >= 18
}
fn can_view_content(&self) -> bool {
// 满足任一条件即可:账号活跃 或 是管理员
self.is_active || self.is_admin
}
}
fn main() {
let user = User {
name: String::from("张三"),
is_active: true,
is_admin: false,
age: 25,
};
println!("可以访问管理后台: {}", user.can_access_admin_panel()); // false
println!("可以查看内容: {}", user.can_view_content()); // true
}
5.2 数据验证
fn is_valid_password(password: &str) -> bool {
// 密码必须同时满足:
// 1. 长度至少 8 位
// 2. 包含至少一个大写字母
// 3. 包含至少一个小写字母
// 4. 包含至少一个数字
let has_min_length = password.len() >= 8;
let has_uppercase = password.chars().any(|c| c.is_ascii_uppercase());
let has_lowercase = password.chars().any(|c| c.is_ascii_lowercase());
let has_digit = password.chars().any(|c| c.is_ascii_digit());
has_min_length && has_uppercase && has_lowercase && has_digit
}
fn main() {
println!("'Hello123' 有效: {}", is_valid_password("Hello123")); // false(长度不够)
println!("'HelloWorld1' 有效: {}", is_valid_password("HelloWorld1")); // true
println!("'helloworld1' 有效: {}", is_valid_password("helloworld1")); // false(没有大写)
}
5.3 游戏状态判断
struct GameState {
player_alive: bool,
has_key: bool,
door_open: bool,
enemies_defeated: bool,
}
impl GameState {
fn can_exit_level(&self) -> bool {
// 可以退出关卡的条件:玩家存活,且(门已开 或 所有敌人被击败)
self.player_alive && (self.door_open || self.enemies_defeated)
}
fn game_over(&self) -> bool {
// 游戏结束条件:玩家死亡
!self.player_alive
}
}
fn main() {
let state = GameState {
player_alive: true,
has_key: true,
door_open: false,
enemies_defeated: true,
};
println!("可以退出关卡: {}", state.can_exit_level()); // true
println!("游戏结束: {}", state.game_over()); // false
}
六、总结
| 概念 | 要点 |
|---|---|
| 布尔代数 | 只有 true 和 false 两个值,定义了与、或、非三种基本运算 |
| 基本定律 | 交换律、结合律、分配律、德摩根定律等 |
| Rust 逻辑运算符 | &&(与)、||(或)、!(非) |
| 短路求值 | && 左假则短路,|| 左真则短路,可提高性能并避免错误 |
| 与位运算区别 | 逻辑运算符用于 bool,位运算符用于整数 |
逻辑运算是编程中最基础也是最重要的概念之一。掌握布尔代数的定律和 Rust 逻辑运算符的短路特性,可以帮助我们写出更简洁、更高效、更安全的代码。
练习建议:
- 尝试用德摩根定律简化复杂的条件表达式
- 利用短路求值避免不必要的计算和潜在的错误
- 在适当的场景下,用模式匹配替代复杂的逻辑判断