程序员的数学(二)逻辑:消除歧义的编程思维工具

举报
倔强的石头_ 发表于 2025/12/06 15:23:49 2025/12/06
【摘要】 文章目录一、为什么程序员需要逻辑?因为自然语言太 “模糊”场景 1:产品经理的需求描述场景 2:程序员的代码逻辑二、逻辑基础:命题、真 / 假与 “无遗漏、无重复”1. 什么是命题?能判断 “真 / 假” 的陈述句2. 最容易踩坑的点:边界值与 “完整性、排他性”例子:巴士车费规则设计用数轴直观理解 “完整性、排他性”编程中的应用:if 语句的 “无遗漏、无重复”三、核心逻辑运算:用 “真 ...

223d74346013f80be0b0acccf8e3ecd5.png


文章目录


欢迎回到 “程序员的数学” 系列专栏。上一篇我们聊了 “0 的故事”,知道了二进制的 0 和 1 是计算机的 “母语”—— 而今天要讲的 “逻辑”,正是操控这两个 “母语” 的核心规则。


你可能会问:“逻辑不就是 if-else 里的条件判断吗?这有什么好深入的?” 但实际编程中,很多 bug 都藏在 “逻辑歧义” 里:比如 “或者” 到底是 “二选一” 还是 “两者都算”?“边界值”(比如 6 岁整的孩子算 “6 岁以上” 还是 “不到 6 岁”)该怎么处理?这些问题的答案,都需要用严谨的逻辑思维来明确。

今天我们就从 “消除歧义” 出发,用生活和编程中的例子,把逻辑讲透 —— 从基础的 “真 / 假” 判断,到复杂逻辑的简化技巧,最后落地到代码实战,让你写出无歧义、易维护的条件判断。

一、为什么程序员需要逻辑?因为自然语言太 “模糊”

先看两个生活中的对话场景,感受一下逻辑的重要性:

场景 1:产品经理的需求描述

产品经理说:“用户登录时,输入手机号或者邮箱,并且密码正确,就允许登录。”你觉得 “或者” 是哪种意思?

  • 意思 A:手机号和邮箱二选一,再加上密码正确;
  • 意思 B:可以用 “手机号 + 密码”,也可以用 “邮箱 + 密码”,还可以用 “手机号 + 邮箱 + 密码”?

显然产品想表达的是意思 A,但自然语言的 “或者” 有歧义。如果没理清逻辑,写出来的登录代码可能出现 bug(比如允许用户只输手机号 + 邮箱就登录)。

场景 2:程序员的代码逻辑

你写了一段判断用户是否符合优惠的代码:

def is_eligible(age, is_vip):
    # 需求:6岁以下儿童,或者VIP用户,可享受优惠
    if age < 6 or is_vip:
        return True
    else:
        return False

这段代码有问题吗?假设用户是 “6 岁整的 VIP”,代码返回 True(正确);但如果需求是 “6 岁以下儿童,或者非儿童的 VIP”,这段代码就错了 —— 因为它没明确 “6 岁以上的 VIP” 才符合,这里的 “边界值” 和 “逻辑范围” 需要更严谨的定义。

这就是逻辑的核心作用:把模糊的自然语言,转化为计算机能理解的、无歧义的规则。计算机不像人能 “猜” 需求,它只会严格执行逻辑规则,所以逻辑是程序员和计算机的 “沟通桥梁”。

二、逻辑基础:命题、真 / 假与 “无遗漏、无重复”

要学好逻辑,先从最基础的 “命题” 开始 —— 这是逻辑判断的最小单位。

1. 什么是命题?能判断 “真 / 假” 的陈述句

命题是能明确判断 “真(True)” 或 “假(False)” 的句子,比如:

  • “Alice 今年 13 岁” → 能判断真假(如果 Alice 确实 13 岁,就是真);
  • “Bob 的年龄在 6 岁以上” → 能判断真假(Bob4 岁就是假);
  • “今天天气很好” → 不能判断真假(“很好” 是主观感受,没有统一标准)。

编程中的条件判断,本质就是 “判断命题的真假”。比如if age < 6,其实就是在判断命题 “age 小于 6” 是否为真。

2. 最容易踩坑的点:边界值与 “完整性、排他性”

逻辑判断最容易出错的地方,是 “边界值” 和 “范围划分”。我们用 “巴士车费规则” 这个经典例子,拆解这两个核心原则:

例子:巴士车费规则设计

假设巴士公司的规则是:

  • 6 岁以上乘客:100 元;
  • 不到 6 岁乘客:0 元。

我们来分析这个规则是否严谨:

  • 边界值:6 岁整的乘客算哪类?规则里 “6 岁以上” 包含 6 岁,所以 6 岁乘客车费 100 元(正确,无歧义);
  • 完整性:是否覆盖所有情况?所有乘客都有年龄,要么≥6 岁,要么 <6 岁,没有遗漏(比如不会出现 “既不≥6 也不 < 6” 的乘客);
  • 排他性:是否有重复情况?一个乘客不可能 “既≥6 岁又 < 6 岁”,没有重复(不会出现同一乘客同时符合两个规则)。

如果规则改成下面这样,就有问题了:

  • 规则 B:“大于 6 岁” 100 元,“不到 6 岁” 0 元 → 遗漏了 “6 岁整” 的情况(不完整);
  • 规则 C:“6 岁以上” 100 元,“6 岁以下” 0 元 → “6 岁整” 同时符合两个规则(重复,矛盾)。

用数轴直观理解 “完整性、排他性”

我们可以用数轴画出年龄范围,避免遗漏和重复:

  • 规则 A(正确):

    左边空心圆(○)表示 “不包含 6 岁”,右边实心圆(●)表示 “包含 6 岁”,覆盖所有年龄,无重叠。

  • 规则 B(遗漏):

    6 岁处是两个空心圆,中间有 gap(遗漏)。

  • 规则 C(重复):

    6 岁处是两个实心圆,重叠(重复)。
    image.png

编程中的应用:if 语句的 “无遗漏、无重复”

写 if 语句时,只要保证 “所有情况都覆盖(完整性),且不重复(排他性)”,就能避免逻辑 bug。比如判断车费的代码:

def calculate_fare(age):
    if age >= 6:  # 包含6岁(边界值)
        return 100
    else:  # 只剩“age < 6”的情况,无遗漏、无重复
        return 0

# 测试边界值:6岁→100,5岁→0,7岁→100(全部正确)
print(calculate_fare(6))  # 100
print(calculate_fare(5))  # 0

这里ifelse正好覆盖所有年龄,没有遗漏;且age >=6age <6没有重叠,没有重复 —— 这就是逻辑严谨的 if 语句。

三、核心逻辑运算:用 “真 / 假” 组合出所有规则

掌握了命题和范围划分,接下来我们学习 “逻辑运算”—— 这是组合多个命题的工具。就像数学中的 “+、-、×、÷”,逻辑也有四个核心运算:非(NOT)、与(AND)、或(OR)、异或(XOR)

每个运算都可以用 “真值表” 表示(真值表是判断逻辑运算结果的 “万能工具”,表中 T 表示真,F 表示假)。

1. 逻辑非(NOT):“反过来”

逻辑非是最简单的运算:把 “真” 变成 “假”,“假” 变成 “真”。比如命题 “A:age < 6”,逻辑非就是 “NOT A:age ≥ 6”。

真值表

A(原命题) ¬A(逻辑非)
T F
F T

编程中的应用:否定条件

比如判断 “用户不是儿童”(儿童定义为 age < 6):

python

age = 7
is_child = age < 6  # 命题A:是否是儿童(F)
is_adult = not is_child  # 逻辑非:是否是成人(T)
print(is_adult)  # 输出True

注意:逻辑非不能滥用,比如if not (age < 6)不如if age >=6直观,尽量用 “正向命题” 写条件。

2. 逻辑与(AND):“同时满足”

逻辑与要求 “所有命题都为真,结果才为真”—— 比如 “手机号正确,并且密码正确”,只有两个条件都满足,才能登录。

真值表

A B A ∧ B(逻辑与)
T T T
T F F
F T F
F F F

编程中的应用:多条件校验

比如实现登录逻辑,要求 “用户名不为空,并且密码不为空”:

python

def can_login(username, password):
    # 两个条件必须同时满足
    if username != "" and password != "":
        return True
    else:
        return False

# 测试:都不为空→True,一个为空→False
print(can_login("alice", "123456"))  # True
print(can_login("", "123456"))        # False

3. 逻辑或(OR):“至少满足一个”

逻辑或要求 “至少一个命题为真,结果就为真”—— 比如 “用手机号登录,或者用邮箱登录”,满足一个就能登录。

真值表

A B A ∨ B(逻辑或)
T T T
T F T
F T T
F F F

关键提醒:逻辑或的 “包容性”

自然语言的 “或者” 可能有 “二选一” 的歧义,但逻辑或默认是 “包容性或”—— 即 “两个都满足也可以”。比如:

python

# 需求:手机号或邮箱任一不为空
def has_contact(phone, email):
    if phone != "" or email != "":
        return True
    else:
        return False

# 测试:两个都不为空→True(逻辑或允许这种情况)
print(has_contact("13800138000", "alice@example.com"))  # True

如果需要 “排他性或”(二选一,不能都满足),就要用 “异或”。

4. 逻辑异或(XOR):“二选一”

逻辑异或要求 “两个命题真假不同,结果才为真”—— 比如 “要么选 A 套餐,要么选 B 套餐”,不能同时选,也不能都不选。

真值表

A B A ⊕ B(逻辑异或)
T T F
T F T
F T T
F F F

编程中的应用:二选一场景

比如实现 “套餐选择” 逻辑,只能选一个:

python

def is_valid_choice(choose_a, choose_b):
    # 异或:只能选一个
    return choose_a ^ choose_b

# 测试:选A不选B→True,都选→False,都不选→False
print(is_valid_choice(True, False))  # True
print(is_valid_choice(True, True))   # False
print(is_valid_choice(False, False)) # False

5. 逻辑蕴涵(IMPLY):“如果… 那么…”

蕴涵是稍微抽象的运算,表达 “如果 A 为真,那么 B 必须为真”—— 比如 “如果是 VIP 用户(A),那么可享受优惠(B)”。

真值表

A B A → B(逻辑蕴涵)
T T T
T F F
F T T
F F T

关键理解:“假命题可以推出任何结论”

比如命题 A 是 “age < 0”(假,年龄不可能为负),那么 “如果 age < 0,那么可以免费乘车” 这个蕴涵命题是真的 —— 因为 A 本身是假的,无论 B 是什么,蕴涵都成立。

编程中的应用:条件承诺

比如 “如果用户积分≥1000(A),那么送优惠券(B)”:

python

def give_coupon(points):
    # 蕴涵:A→B(如果积分≥1000,就送券)
    if points >= 1000:
        return "送50元券"  # A真时,B必须真
    else:
        return "不送券"    # A假时,B可真可假

# 测试:积分1000→送券,积分500→不送券(都符合蕴涵)
print(give_coupon(1000))  # 送50元券
print(give_coupon(500))   # 不送券

四、德・摩根定律:简化复杂逻辑的 “神器”

当逻辑表达式嵌套很多 “非” 和 “与 / 或” 时,代码会变得臃肿。比如判断 “不是(用户名不为空且密码不为空)”,直接写就是not (username != "" and password != "")—— 读起来很绕。

这时候就需要 “德・摩根定律” 来简化,它是逻辑运算的 “变形公式”,能把 “与” 和 “或” 互相转换:

德・摩根定律的两个核心公式

  1. ¬(A ∧ B) = ¬A ∨ ¬B

    “不是 A 且 B” 等价于 “不是 A,或者不是 B”;

  2. ¬(A ∨ B) = ¬A ∧ ¬B

    “不是 A 或 B” 等价于 “不是 A,并且不是 B”。

用真值表验证(以第一个公式为例)

A B A ∧ B ¬(A ∧ B) ¬A ¬B ¬A ∨ ¬B
T T T F F F F
T F F T F T T
F T F T T F T
F F F T T T T
可以看到,¬(A ∧ B)¬A ∨ ¬B的结果完全一致,证明公式成立。

编程中的应用:简化嵌套逻辑

我们用两个例子看德・摩根定律如何简化代码:

例子 1:简化 “不是(A 且 B)”

原逻辑:not (username != "" and password != "")用德・摩根定律简化:username == "" or password == ""(¬A 是 “用户名为空”,¬B 是 “密码为空”,用或连接)。

简化前后的代码对比:

python

# 简化前:嵌套逻辑,绕口
def cannot_login(username, password):
    if not (username != "" and password != ""):
        return True  # 不能登录
    else:
        return False

# 简化后:直接判断,直观
def cannot_login_simple(username, password):
    if username == "" or password == "":
        return True  # 不能登录
    else:
        return False

# 测试:结果一致
print(cannot_login("", "123456"))        # True
print(cannot_login_simple("", "123456")) # True

例子 2:简化 “不是(A 或 B)”

原逻辑:not (age < 6 or is_vip)(不是 “儿童或 VIP”)用德・摩根定律简化:age >= 6 and not is_vip(“不是儿童” 且 “不是 VIP”)。

代码对比:

python

# 简化前
def no_discount(age, is_vip):
    if not (age < 6 or is_vip):
        return True  # 无优惠
    else:
        return False

# 简化后
def no_discount_simple(age, is_vip):
    if age >= 6 and not is_vip:
        return True  # 无优惠
    else:
        return False

# 测试:6岁非VIP→True(无优惠)
print(no_discount(6, False))        # True
print(no_discount_simple(6, False)) # True

德・摩根定律的核心价值:把 “否定的嵌套逻辑” 拆成 “正向的简单逻辑”,代码更易读,也更少出错。

五、卡诺图:简化多条件逻辑的 “地图”

当条件超过 2 个时(比如 3 个变量的逻辑判断),光用德・摩根定律可能不够 —— 这时候 “卡诺图” 就是简化逻辑的好工具。它像一张 “逻辑地图”,把所有可能的条件组合画成格子,然后通过 “合并格子” 来简化表达式。

我们用 “二灯游戏” 和 “三灯游戏” 两个例子,带你上手卡诺图。

例子 1:二灯游戏(2 个变量)

游戏规则:

有绿、黄两个灯(A:绿灯亮,B:黄灯亮),满足以下情况时按按钮:

  1. 绿灯灭,黄灯亮(¬A ∧ B);
  2. 绿灯灭,黄灯灭(¬A ∧ ¬B);
  3. 绿灯亮,黄灯亮(A ∧ B)。

步骤 1:画卡诺图(2 个变量→4 个格子)

卡诺图的横轴是 A(绿灯),纵轴是 B(黄灯),每个格子代表一个条件组合:

A\B ¬B(黄灯灭) B(黄灯亮)
¬A(绿灯灭) ¬A∧¬B(按) ¬A∧B(按)
A(绿灯亮) A∧¬B(不按) A∧B(按)

步骤 2:合并 “按按钮” 的格子

我们把需要 “按按钮” 的格子(√)用框圈起来:

  • 纵向合并:¬A∧¬B 和 ¬A∧B → 合并后是 “¬A”(不管 B 是什么,只要 A 灭就按);
  • 横向合并:¬A∧B 和 A∧B → 合并后是 “B”(不管 A 是什么,只要 B 亮就按)。

合并后,逻辑表达式简化为:¬A ∨ B(绿灯灭,或者黄灯亮)。

编程实现:

python

def should_press(A, B):
    # 简化后的逻辑:绿灯灭(not A)或黄灯亮(B)
    return not A or B

# 测试:符合规则的情况都返回True
print(should_press(False, True))  # 绿灯灭、黄灯亮→True
print(should_press(False, False)) # 绿灯灭、黄灯灭→True
print(should_press(True, True))  # 绿灯亮、黄灯亮→True
print(should_press(True, False)) # 绿灯亮、黄灯灭→False(正确)

例子 2:三灯游戏(3 个变量)

游戏规则:

有绿(A)、黄(B)、红(C)三个灯,满足以下情况时按按钮:

  1. 三灯都灭(¬A∧¬B∧¬C);
  2. 黄灯灭、红灯亮(A∧¬B∧C 或 ¬A∧¬B∧C);
  3. 绿灯灭、黄灯亮(¬A∧B∧¬C 或 ¬A∧B∧C);
  4. 三灯都亮(A∧B∧C)。

步骤 1:画卡诺图(3 个变量→8 个格子)

3 个变量的卡诺图,横轴是 AB(绿灯 + 黄灯),纵轴是 C(红灯):

AB\C ¬C(红灯灭) C(红灯亮)
¬A¬B ¬A¬B¬C(按) ¬A¬B¬C(按)
¬AB ¬AB¬C(按) ¬AB¬C(按)
AB ABC¬C(不按) ABC¬C(按)
A¬B AB¬B¬C(不按) AB¬B¬C(按)

步骤 2:合并格子

  • 纵向合并左两列(¬A¬B 和 ¬AB):不管 B 是什么,只要 A 灭(¬A),就按;
  • 横向合并右两列的 C 行(AB∧C 和 A¬B∧C):不管 B 是什么,只要 A 亮且 C 亮(A∧C),就按。

合并后,逻辑表达式简化为:¬A ∨ (A∧C) → 进一步简化为 ¬A ∨ C(因为 A∧C 已经包含在 “C” 里)。

编程实现:

python

def should_press_3lights(A, B, C):
    # 简化后的逻辑:绿灯灭(not A)或红灯亮(C)
    return not A or C

# 测试:三灯都灭→True,黄灯灭红灯亮→True(正确)
print(should_press_3lights(False, False, False)) # 三灯灭→True
print(should_press_3lights(True, False, True))   # 黄灯灭红灯亮→True
print(should_press_3lights(True, True, False))   # 红灯灭→False(正确)

卡诺图的核心思想:相邻的格子(条件)可以合并,减少判断条件的数量。变量越多,卡诺图的简化效果越明显 —— 比如 4 个变量的逻辑,用卡诺图可能从 8 个条件简化到 2 个。

六、三值逻辑:处理 “未定义” 的情况

前面我们讨论的都是 “二值逻辑”(只有真和假),但编程中常会遇到 “未定义” 的情况(比如变量为 None、null,或者函数返回异常)。这时候就需要 “三值逻辑”,新增一个值 “undefined”(未定义)。

1. 三值逻辑的核心运算

三值逻辑中,除了 True(T)和 False(F),还有 Undefined(U)。我们重点讲编程中常用的三个运算:

(1)带条件的逻辑与(&&)

和二值逻辑不同,三值逻辑的 && 有 “短路特性”:如果第一个条件是 False 或 Undefined,就不再判断第二个条件。

A B A && B 说明
T T T 都真→真
T F F 第二个假→假
T U U 第二个未定义→未定义
F 任意 F 第一个假→假(短路)
U 任意 U 第一个未定义→未定义(短路)

(2)带条件的逻辑或(||)

同样有短路特性:如果第一个条件是 True,就不再判断第二个条件。| A | B | A || B | 说明 ||—|—|--------|------|| T | 任意 | T | 第一个真→真(短路) || F | T | T | 第二个真→真 || F | F | F | 都假→假 || F | U | U | 第二个未定义→未定义 || U | 任意 | U | 第一个未定义→未定义(短路) |

(3)逻辑非(!)

A !A
T F
F T
U U

2. 编程中的应用:避免空指针异常

比如判断 “列表非空,并且第一个元素大于 10”,如果列表是 None(未定义),直接判断会报错,用三值逻辑的 && 就能避免:

python

def is_first_gt10(lst):
    # 短路特性:如果lst是None,就不判断lst[0](避免报错)
    if lst is not None and len(lst) > 0 and lst[0] > 10:
        return True
    else:
        return False

# 测试:lst为None→False,lst为空→False,lst[0]=15→True
print(is_first_gt10(None))       # False(不报错)
print(is_first_gt10([]))         # False
print(is_first_gt10([15, 20]))   # True

这里lst is not None是第一个条件,如果为 False(lst 是 None),后面的len(lst)就不会执行,避免了空指针异常 —— 这就是三值逻辑在编程中的实际价值。

七、小结:逻辑是编程的 “严谨之魂”

今天我们从 “消除歧义” 出发,把逻辑讲透了:

  1. 逻辑基础:命题要能判断真 / 假,范围划分要满足 “完整性、排他性”,避免边界值 bug;
  2. 核心运算:非、与、或、异或、蕴涵,用真值表能验证所有结果;
  3. 简化工具:德・摩根定律简化嵌套逻辑,卡诺图简化多变量逻辑;
  4. 三值逻辑:处理未定义情况,避免空指针异常。

逻辑不仅是写 if-else 的工具,更是排查 bug 的 “显微镜”—— 比如遇到条件判断 bug 时,用真值表枚举所有情况,很快就能定位问题;也是代码优化的 “指南针”—— 用德・摩根定律和卡诺图,能把臃肿的条件判断简化成一两行。

下一篇我们将学习 “余数”—— 它和逻辑的 “真 / 假” 结合,能解决很多 “周期性” 问题(比如星期计算、奇偶校验)。比如用余数判断 “今天是星期几”,用逻辑判断 “是否需要执行周循环任务”—— 两者结合,能写出更高效的循环代码。

如果你在逻辑判断中踩过坑,或者有简化逻辑的小技巧,欢迎在评论区分享!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。