在C++中,如果你想把一个整数(比如 123)的每一位都转换成对应的英文单词(“one two three”),你最先想到的“笨办法”可能是使用一长串的 if-else 或 switch 语句:
int digit = n % 10;
if (digit == 0) { cout << "zero "; }
else if (digit == 1) { cout << "one "; }
else if (digit == 2) { cout << "two "; }
// ... 一直写到 9 ...
这非常繁琐、重复且难以维护。这个挑战的目标是:不使用 if, else, 或 switch 语句来完成这个 0-9 的“映射”。
一个简单的比喻:“慢图书馆” vs “魔法书架”
-
if-else链 (慢图书馆):就像一个“糊涂的图书管理员”。你问他:“请给我‘第 3 号书’。” 他会:- 拿起“第 0 号书”,问:“这是 3 号吗?” (不是)
- 拿起“第 1 号书”,问:“这是 3 号吗?” (不是)
- 拿起“第 2 号书”,问:“这是 3 号吗?” (不是)
- 拿起“第 3 号书”,问:“这是 3 号吗?” (是的!)
- 他做了 4 次“比较” (
if) 才找到。
-
数组查找表 (Lookup Table - “魔法书架”):
- 你创建了一个**“魔法书架”(一个数组),这个书架天生就是按顺序**摆放的:
string digits[] = {"zero", "one", "two", "three", ...}; - 你想找“第 3 号书”?你不需要问任何问题。
- 你直接走到“书架”的第 3 个位置(
digits[3]),“直接取出”了 “three”! - 这是一种**“O(1)”(常数时间)的操作,用一次“内存访问”** (
[]) 代替了多次“逻辑比较” (if)。
- 你创建了一个**“魔法书架”(一个数组),这个书架天生就是按顺序**摆放的:
但是,这个技巧还有一个“陷阱”:123 % 10 会先给你 3,然后 12 % 10 给你 2… 你的输出会变成 “three two one” —— 顺序是反的!
我们将使用递归 (Recursion) 的“魔法”来自动解决这个“反转”问题。
在本教程中,你将学会:
✅“魔法书架”:如何使用数组作为“查找表”(Lookup Table, LUT)。✅%和/运算符:如何“剥离”出一个整数的最后一位。✅“反转”问题:为什么简单的循环会打出 “three two one”。✅“递归”的解决方案:如何利用“调用栈” (Call Stack) 自动反转打印顺序。✅实战演练:编写一个printDigits函数来解决问题。✅“X光透视”:用调试器“亲眼目睹”递归是如何“暂停”和“回溯”的。
前置知识说明 (100% 自洽):
- 变量 (Variable):理解存储数据的“盒子”,如
int n = 123;。 string(字符串):C++标准库提供的“魔法弹性盒子”,用于处理文本。你需要#include <string>。- 数组 (Array):存储一系列相同类型元素的“固定盒子排”。
string arr[10]; %(取模):123 % 10得到余数3(“剥离”最后一位)。/(除法):123 / 10得到整数12(“扔掉”最后一位)。- 函数 (Function):可重复使用的“代码积木”,知道什么是函数调用和函数返回。
- 递归 (Recursion):一个函数调用其自身。
- 调用栈 (Call Stack):程序用来管理函数调用的内存区域,遵循“后进先出”(LIFO)原则(“叠盘子”的比喻)。
- 编译 (Compile):C++代码(“食谱”)必须被“编译”(“烘焙”),才能变成电脑可执行的程序(“蛋糕”)。
第一部分:“魔法书架” (Lookup Table)
这是解决“映射”问题的核心。我们不需要 if,我们只需要一个数组:
// 我们的“魔法书架”
// 索引 0 对应 "zero", 索引 1 对应 "one", ...
string digitWords[10] = {
"zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine"
};
现在,如果你有一个数字 int digit = 7,你不需要 if (digit == 7),你只需要 cout << digitWords[digit];。
第二部分:“反转”问题——递归登场
如果我们用“循环”来“剥离”数字,我们会得到反向的输出:
123 -> 剥离 3 (打印 “three”) -> 剩下 12
12 -> 剥离 2 (打印 “two”) -> 剩下 1
1 -> 剥离 1 (打印 “one”) -> 剩下 0
输出: “three two one”
我们如何先处理 1,再处理 2,最后处理 3 呢?
使用递归!
递归的逻辑 (比喻:深入洞穴):
printDigits(123)
- “探险家”
printDigits(123)接到任务。 - 他面临两个“子任务”:处理
12和 处理3。 - 他决定先把“更深”的任务(处理
12)“外包”给另一个“探险家”(递归调用printDigits(12))。 - 他**“暂停”自己手头的工作(处理
3),“等待”**printDigits(12)完成。 printDigits(12)又**“暂停”**,“外包”给printDigits(1)。printDigits(1)又**“暂停”**,“外包”给printDigits(0)。printDigits(0)发现n < 10(或者n == 0,取决于实现),它击中了“基础情况”(“洞穴尽头”),它什么也不做,直接返回。printDigits(1)“苏醒”(printDigits(0)已返回)。它现在才开始它自己的工作:1 % 10 = 1。它打印 "one "。然后返回。printDigits(12)“苏醒”(printDigits(1)已返回)。它现在才开始它自己的工作:12 % 10 = 2。它打印 "two "。然后返回。printDigits(123)“苏醒”(printDigits(12)已返回)。它现在才开始它自己的工作:123 % 10 = 3。它打印 "three "。然后返回。
结果: "one two three "
第三部分:“实战演练”—— printDigits 函数
recursive_print.cpp
#include <iostream>
#include <string>
#include <vector> // 只是为了展示,这个程序不需要
using namespace std;
// “魔法书架” (设为全局或静态,方便递归函数访问)
string digitWords[10] = {
"zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine"
};
// “厨师”:递归打印数字
// “行内预警”:GFG 的文章使用 `if(n/10)` 作为“非基础情况”
// 这是一个“技巧性”的 if,用于*控制递归*,
// 而*不是*用于*映射数字*(0-9),所以符合挑战精神
void printDigits(int n) {
// 递归步骤:
// 如果 n = 123, (n / 10) = 12 (非 0,即 true)
// 如果 n = 1, (n / 10) = 0 (即 false)
if (n / 10 != 0) { // “只要 n 还有‘前缀’ (>=10)...”
// ...就“外包”那个“前缀”
printDigits(n / 10);
}
// “基础情况” / “回溯”步骤:
// 当所有“外包”都返回后,*才* 处理 *自己* 的“最后一位”
// 1. 获取最后一位数字
int digit = n % 10;
// 2. 使用“魔法书架”打印,*没有* if-else!
cout << digitWords[digit] << " ";
}
// (一个更清晰的、带明确“基础情况”的版本)
void printDigits_v2(int n) {
// 基础情况
if (n < 10) {
cout << digitWords[n] << " ";
return; // 停止
}
// 递归步骤
printDigits_v2(n / 10); // 先处理“前缀”
cout << digitWords[n % 10] << " "; // 再处理“最后一位”
}
// “经理”的主程序
int main() {
int number;
cout << "请输入一个整数 (例如 12345): ";
cin >> number;
cout << "\n--- GFG (v1) 版本输出 ---" << endl;
if (number == 0) { // “行内预警”:GFG 的 v1 版无法处理 0
cout << digitWords[0] << " ";
} else {
printDigits(number);
}
cout << endl;
cout << "\n--- v2 版本输出 (能处理 0) ---" << endl;
printDigits_v2(number);
cout << endl;
return 0;
}
“手把手”终端模拟:
PS C:\MyCode> g++ recursive_print.cpp -o recursive_print.exe -std=c++11
PS C:\MyCode> .\recursive_print.exe
请输入一个整数 (例如 12345): 9042
--- GFG (v1) 版本输出 ---
nine zero four two
--- v2 版本输出 (能处理 0) ---
nine zero four two
第四部分:“X光透视”——亲眼目睹“递归暂停”
让我们用“X光眼镜”(调试器)来观察 printDigits_v2(123) 是如何工作的。
“X光”实战(基于 printDigits_v2)
-
设置断点:
- 动作: 在VS Code中,把你的鼠标移动到
printDigits_v2函数内部的第52行(cout << digitWords[n % 10] << " ";那一行)的行号左边。 - 点击那个小 red dot,设置一个断点。
- 动作: 在VS Code中,把你的鼠标移动到
-
启动“子弹时间”(F5):
- 动作: 按下
F5键,在终端输入123并回车。 - 你会看到:
main调用printDigits_v2(123)。123 < 10?false。- 程序递归调用
printDigits_v2(12)。 12 < 10?false。- 程序递归调用
printDigits_v2(1)。 1 < 10?true!- 程序执行
cout << digitWords[1] << " ";(打印 "one ")。 printDigits_v2(1)返回。- 程序**“回溯”到
printDigits_v2(12)的第52行**! - 程序“冻结”在断点处!
- 动作: 按下
-
开启“X光”(第1次“回溯”,n=12):
- 动作: 仔细看那个“变量”(VARIABLES)窗口。
- 你会看到:
n: 12 - 观察“调用堆栈”(CALL STACK)窗口:
printDigits_v2(int n=12)👈 (你在这里)printDigits_v2(int n=123)main()
- (关键!) 在“监视”(WATCH)窗口中,添加
n % 10。 - 你会看到:
n % 10: 2 - 动作: 按下
F10键(“Step Over”,步过)。 - 你会看到: 终端打印出 "two "。
printDigits_v2(12)返回。
-
继续执行 (F5)。
-
第二次“冻结”(n=123):
- 你会看到: 程序**“回溯”到
printDigits_v2(123)的第52行**! - 开启“X光”:
- 变量窗口:
n: 123 - 调用堆栈:
printDigits_v2(int n=123)👈 (你在这里),main() - 监视窗口:
n % 10: 3
- 变量窗口:
- 动作: 按下
F10键。 - 你会看到: 终端打印出 "three "。
printDigits_v2(123)返回。
- 你会看到: 程序**“回溯”到
-
程序返回
main并结束。- 顿悟时刻: 你亲眼见证了!递归调用 (
printDigits_v2(n / 10);) 必须在打印cout之前执行,这强制程序“暂停”了当前的打印工作,先去处理“更深”的任务,从而自动实现了顺序的反转。
- 顿悟时刻: 你亲眼见证了!递归调用 (
动手试试!(终极挑战:你的“反向”打印机)
现在,你来当一次“逆序工程师”。
任务:
- 复制本教程的
recursive_print.cpp代码(包含digitWords数组)。 - (关键!) 修改
printDigits_v2函数(或printDigits),颠倒“递归调用”和“cout打印”的顺序。 - 预测: 如果你先
cout,再printDigits(n / 10),会发生什么? - 验证: 编译并运行你的修改版,并用
9042测试,看看输出是不是 "two four zero nine " (反向)?
reverse_print.cpp (你的 TODO):
#include <iostream>
#include <string>
using namespace std;
// (在此处粘贴 digitWords 数组)
string digitWords[10] = { /* ... */ };
// --- TODO 1 & 2: 修改函数,颠倒顺序 ---
void printDigitsInReverse(int n) {
// 基础情况 (可以简化,甚至不需要)
if (n < 10) {
cout << digitWords[n] << " ";
return;
}
// --- “颠倒”的逻辑 ---
// 1. *先* 打印“最后一位”
// cout << digitWords[n % 10] << " ";
// 2. *再* 递归处理“前缀”
// printDigitsInReverse(n / 10);
}
int main() {
int number = 9042;
cout << "--- 递归反向打印 ---" << endl;
// --- TODO 4: 调用 ---
// printDigitsInReverse(number);
cout << endl;
return 0;
}
这个挑战让你通过简单地交换两行代码,深刻理解了递归的执行顺序(“调用栈”的 LIFO 特性)是如何直接控制程序输出结果的!
C++递归与查表法打印数字单词
4099

被折叠的 条评论
为什么被折叠?



