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

文章目录
- 一、为什么程序员需要逻辑?因为自然语言太 “模糊”
- 二、逻辑基础:命题、真 / 假与 “无遗漏、无重复”
- 三、核心逻辑运算:用 “真 / 假” 组合出所有规则
- 1. 逻辑非(NOT):“反过来”
- 2. 逻辑与(AND):“同时满足”
- 3. 逻辑或(OR):“至少满足一个”
- 4. 逻辑异或(XOR):“二选一”
- 5. 逻辑蕴涵(IMPLY):“如果… 那么…”
- 四、德・摩根定律:简化复杂逻辑的 “神器”
- 五、卡诺图:简化多条件逻辑的 “地图”
- 六、三值逻辑:处理 “未定义” 的情况
- 七、小结:逻辑是编程的 “严谨之魂”
欢迎回到 “程序员的数学” 系列专栏。上一篇我们聊了 “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 岁处是两个实心圆,重叠(重复)。

编程中的应用: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
这里if和else正好覆盖所有年龄,没有遗漏;且age >=6和age <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 != "")—— 读起来很绕。
这时候就需要 “德・摩根定律” 来简化,它是逻辑运算的 “变形公式”,能把 “与” 和 “或” 互相转换:
德・摩根定律的两个核心公式
-
¬(A ∧ B) = ¬A ∨ ¬B
“不是 A 且 B” 等价于 “不是 A,或者不是 B”;
-
¬(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:黄灯亮),满足以下情况时按按钮:
- 绿灯灭,黄灯亮(¬A ∧ B);
- 绿灯灭,黄灯灭(¬A ∧ ¬B);
- 绿灯亮,黄灯亮(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)三个灯,满足以下情况时按按钮:
- 三灯都灭(¬A∧¬B∧¬C);
- 黄灯灭、红灯亮(A∧¬B∧C 或 ¬A∧¬B∧C);
- 绿灯灭、黄灯亮(¬A∧B∧¬C 或 ¬A∧B∧C);
- 三灯都亮(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)就不会执行,避免了空指针异常 —— 这就是三值逻辑在编程中的实际价值。
七、小结:逻辑是编程的 “严谨之魂”
今天我们从 “消除歧义” 出发,把逻辑讲透了:
- 逻辑基础:命题要能判断真 / 假,范围划分要满足 “完整性、排他性”,避免边界值 bug;
- 核心运算:非、与、或、异或、蕴涵,用真值表能验证所有结果;
- 简化工具:德・摩根定律简化嵌套逻辑,卡诺图简化多变量逻辑;
- 三值逻辑:处理未定义情况,避免空指针异常。
逻辑不仅是写 if-else 的工具,更是排查 bug 的 “显微镜”—— 比如遇到条件判断 bug 时,用真值表枚举所有情况,很快就能定位问题;也是代码优化的 “指南针”—— 用德・摩根定律和卡诺图,能把臃肿的条件判断简化成一两行。
下一篇我们将学习 “余数”—— 它和逻辑的 “真 / 假” 结合,能解决很多 “周期性” 问题(比如星期计算、奇偶校验)。比如用余数判断 “今天是星期几”,用逻辑判断 “是否需要执行周循环任务”—— 两者结合,能写出更高效的循环代码。
如果你在逻辑判断中踩过坑,或者有简化逻辑的小技巧,欢迎在评论区分享!
- 点赞
- 收藏
- 关注作者

评论(0)