Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第十六 逻辑运算

逻辑运算(Logical Operation)是计算机科学和数学中最基础的运算之一,它处理的是布尔值(truefalse)之间的关系。在 Rust 中,逻辑运算不仅是条件判断的核心,还深刻影响着程序的控制流和性能。


一、布尔代数基础

1.1 什么是布尔代数

布尔代数(Boolean Algebra)由英国数学家乔治·布尔(George Boole)在 19 世纪中叶创立,是一种用于描述逻辑关系的代数系统。它只有两个值:true(真,记为 1)和 false(假,记为 0)。

布尔代数是现代数字电路、计算机逻辑设计和编程语言的理论基础。

1.2 基本逻辑运算

布尔代数定义了三种基本运算:

运算符号Rust 运算符含义
与(AND)∧ 或 ·&&两个条件同时为真,结果才为真
或(OR)∨ 或 +||两个条件至少一个为真,结果就为真
非(NOT)¬ 或 ¯!条件取反

1.3 真值表

真值表(Truth Table)是表示逻辑运算所有可能输入和对应输出的表格。

与运算(AND)真值表:

pqp ∧ q(p && q)
000
010
100
111

口诀:“有假则假,全真才真”

或运算(OR)真值表:

pqp ∨ q(p || q)
000
011
101
111

口诀:“有真则真,全假才假”

非运算(NOT)真值表:

p¬p(!p)
01
10

口诀:“真假互换”

1.4 复合逻辑运算

除了三种基本运算,布尔代数还定义了两种常用的复合运算:

异或(XOR,Exclusive OR)

pqp ⊕ q
000
011
101
110

口诀:“相同为假,不同为真”

异或在 Rust 中没有专门的逻辑运算符,但可以用 (p || q) && !(p && q) 或位运算符 ^ 实现。

同或(XNOR,Equivalence)

pqp ↔ q
001
010
100
111

口诀:“相同为真,不同为假”(异或的反面)

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 && truetrue
true && falsefalse
false && truefalse
false && falsefalse

2.3 逻辑或(||)

逻辑或运算符 || 要求两边的操作数至少有一个为 true,结果就为 true

fn main() {
    let score = 85;

    // 满足任一条件即可
    if score >= 90 || score >= 60 && score < 90 {
        println!("成绩合格");
    }

    // 更清晰的写法
    if score >= 60 {
        println!("成绩合格");
    }
}

逻辑或的真值表(Rust):

表达式结果
true || truetrue
true || falsetrue
false || truetrue
false || falsefalse

2.4 逻辑非(!)

逻辑非运算符 ! 对布尔值取反。

fn main() {
    let is_raining = false;

    if !is_raining {
        println!("今天不下雨,可以出门");
    }

    // 双重否定等于肯定
    assert_eq!(!!is_raining, is_raining);
}

逻辑非的真值表(Rust):

表达式结果
!truefalse
!falsetrue

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
}

六、总结

概念要点
布尔代数只有 truefalse 两个值,定义了与、或、非三种基本运算
基本定律交换律、结合律、分配律、德摩根定律等
Rust 逻辑运算符&&(与)、||(或)、!(非)
短路求值&& 左假则短路,|| 左真则短路,可提高性能并避免错误
与位运算区别逻辑运算符用于 bool,位运算符用于整数

逻辑运算是编程中最基础也是最重要的概念之一。掌握布尔代数的定律和 Rust 逻辑运算符的短路特性,可以帮助我们写出更简洁、更高效、更安全的代码。

练习建议:

  1. 尝试用德摩根定律简化复杂的条件表达式
  2. 利用短路求值避免不必要的计算和潜在的错误
  3. 在适当的场景下,用模式匹配替代复杂的逻辑判断