C++的“魔法查找”:不用 `if` 或 `switch` 打印数字单词

C++递归与查表法打印数字单词

在C++中,如果你想把一个整数(比如 123)的每一位都转换成对应的英文单词(“one two three”),你最先想到的“笨办法”可能是使用一长串的 if-elseswitch 语句:

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 号书’。” 他会:

    1. 拿起“第 0 号书”,问:“这是 3 号吗?” (不是)
    2. 拿起“第 1 号书”,问:“这是 3 号吗?” (不是)
    3. 拿起“第 2 号书”,问:“这是 3 号吗?” (不是)
    4. 拿起“第 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)

  1. “探险家” printDigits(123) 接到任务。
  2. 他面临两个“子任务”:处理 12 和 处理 3
  3. 他决定把“更深”的任务(处理 12)“外包”给另一个“探险家”递归调用 printDigits(12))。
  4. 他**“暂停”自己手头的工作(处理 3),“等待”** printDigits(12) 完成。
  5. printDigits(12) 又**“暂停”**,“外包”给 printDigits(1)
  6. printDigits(1) 又**“暂停”**,“外包”给 printDigits(0)
  7. printDigits(0) 发现 n < 10(或者 n == 0,取决于实现),它击中了“基础情况”(“洞穴尽头”),它什么也不做,直接返回
  8. printDigits(1) “苏醒”printDigits(0) 已返回)。它现在才开始它自己的工作:1 % 10 = 1。它打印 "one "。然后返回。
  9. printDigits(12) “苏醒”printDigits(1) 已返回)。它现在才开始它自己的工作:12 % 10 = 2。它打印 "two "。然后返回。
  10. 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
  1. 设置断点:

    • 动作: 在VS Code中,把你的鼠标移动到 printDigits_v2 函数内部第52行cout << digitWords[n % 10] << " "; 那一行)的行号左边
    • 点击那个小 red dot,设置一个断点
  2. 启动“子弹时间”(F5):

    • 动作: 按下 F5 键,在终端输入 123 并回车。
    • 你会看到:
      1. main 调用 printDigits_v2(123)
      2. 123 < 10? false
      3. 程序递归调用 printDigits_v2(12)
      4. 12 < 10? false
      5. 程序递归调用 printDigits_v2(1)
      6. 1 < 10? true
      7. 程序执行 cout << digitWords[1] << " ";(打印 "one ")。
      8. printDigits_v2(1) 返回
      9. 程序**“回溯”printDigits_v2(12)第52行**!
      10. 程序“冻结”在断点处!
  3. 开启“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) 返回。
  4. 继续执行 (F5)。

  5. 第二次“冻结”(n=123):

    • 你会看到: 程序**“回溯”printDigits_v2(123)第52行**!
    • 开启“X光”:
      • 变量窗口: n: 123
      • 调用堆栈: printDigits_v2(int n=123) 👈 (你在这里), main()
      • 监视窗口: n % 10: 3
    • 动作: 按下 F10
    • 你会看到: 终端打印出 "three "。printDigits_v2(123) 返回。
  6. 程序返回 main 并结束。

    • 顿悟时刻: 你亲眼见证了!递归调用 (printDigits_v2(n / 10);) 必须打印 cout 之前执行,这强制程序“暂停”了当前的打印工作,先去处理“更深”的任务,从而自动实现了顺序的反转

动手试试!(终极挑战:你的“反向”打印机)

现在,你来当一次“逆序工程师”。

任务:

  1. 复制本教程的 recursive_print.cpp 代码(包含 digitWords 数组)。
  2. (关键!) 修改 printDigits_v2 函数(或 printDigits),颠倒“递归调用”和“cout 打印”的顺序
  3. 预测: 如果你cout printDigits(n / 10),会发生什么?
  4. 验证: 编译并运行你的修改版,并用 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 特性)是如何直接控制程序输出结果的!

假设你是一名魔法学院的新生,需要开发一个咒语学习管理系统。咒语必须 按照学习顺序记录在魔法手册(单链表)中,每个咒语用单个单词存储(字符串)。 设计菜单项,根据选项执行以下功能: 1.初始化空的魔法手册 2.按顺序记录咒语:火焰术、治愈术、隐身术、闪电术、冰冻术(用尾插法) 3.显示当前已学的全部咒语 4.统计已掌握的咒语数量 5.检查魔法手册是否为空 6.查看手册中的第i个咒语 7.查找咒语的位置(序号) 8.在第i个位置插入新咒语 9.遗忘手册中的第i个咒语 10.清空魔法手册内存 设计函数如下: void InitSpellBook(SpellNode*& book); 初始化魔法手册 void AddSpell(SpellNode*& book, string spell); 尾插法添加咒语 void ShowSpells(SpellNode* book); 显示所有咒语 int CountSpells(SpellNode* book); 统计咒语数量 bool IsBookEmpty(SpellNode* book); 判断手册是否为空 bool GetSpell(SpellNode* book, int i, string& target);获取第i个咒语 int FindSpell(SpellNode* book, string spell); 查找咒语位置 bool InsertSpell(SpellNode*& book, int pos, string spell); 在指定位置插入咒语 bool ForgetSpell(SpellNode*& book, int pos, string& forgotten); 删除指定位置咒语 void ClearBook(SpellNode*& book);清空手册void InitSpellBook(SpellNode*& book); 初始化魔法手册 void AddSpell(SpellNode*& book, string spell); 尾插法添加咒语 void ShowSpells(SpellNode* book); 显示所有咒语 int CountSpells(SpellNode* book); 统计咒语数量 bool IsBookEmpty(SpellNode* book); 用C语言实现
04-27
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_pk138132

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值