看到“4-24打印九九口诀表”这个题目时,我脑子里第一反应就是:这八成是某本实训教材第4章的第24题。不是因为它独特,而是因为“打印九九乘法表”实在是太经典的练手题目了,凡是学过一点编程的人,基本都写过。别看它简单,它其实串起了三层循环、格式化输出、边界条件三个基本功,很多新手在这里栽跟头,也有很多老手在这里翻车。
先解释一下这里的“打印”。题目里的打印,是编程术语,指把内容输出到屏幕,不是让你按Ctrl+P调出打印机。如果你学的是控制台程序,你看到的是文本表格;如果你做的是网页,那还能真的通过浏览器的打印功能变成纸质文档。这两种“打印”,后面我都会讲到。
这篇文章适合谁?适合正在学循环结构、准备期末上机考试、或者想复习格式化输出的同学。你不需要懂高深算法,会基本的变量和循环就够了。我会给出C、Java、Python、JavaScript四种语言的完整写法,再讲几个常用的变体,最后把大家实际跑代码时遇到的常见问题列出来,包括“系统打印服务已关闭”这种听起来和代码无关但又会卡住你的坑。全程都是我一个一个踩过、排过的经验,直接拿去用就行。
1. 题目全貌与解题思路
1.1 标准的九九口诀表长什么样
先把目标说清楚。题目要求打印的九九口诀表,通常长这样:
1*1=1 1*2=2 2*2=4 1*3=3 2*3=6 3*3=9 1*4=4 2*4=8 3*4=12 4*4=16 …… 1*9=9 2*9=18 3*9=27 …… 9*9=81仔细观察这张表,你会发现一个规律:第1行有1个等式,第2行有2个等式,第3行有3个等式……第9行就有9个等式。每一行里,第一个数(被乘数)从1开始,一直增加到和行号相等;第二个数(乘数)就是行号。比如第5行,式子就是“15、25、35、45、5*5”。这种只有左下半部分的表格,专业说法叫“下三角”。
有些教材的题目还会要求在每一行末尾都不留多余空格,或者要求整个表格用空格严格对齐。这些问题都不难,关键是你得先搞清楚输出结构,否则后面怎么写都不对。
1.2 双层循环的边界到底怎么定
有了上面的规律,代码结构就很清楚了:需要两层循环。外层循环控制行,也就是乘数,从1到9;内层循环控制列,也就是被乘数,从1到当前行号。如果用变量 i 表示行号(乘数),用变量 j 表示列号(被乘数),那么核心逻辑就是:
for i = 1 to 9: for j = 1 to i: 输出 j * i = i*j 换行为什么内层循环的结束条件是j <= i,而不是j <= 9?这就是这道题的精髓。如果是j <= 9,那么每一行都会输出9个式子,得到的是一张完整的9x9方阵,而不是下三角。更重要的是,方阵里会出现大量重复,比如“19=9”和“91=9”都会出现,这就不符合九九口诀表的日常习惯。我们平时背口诀,只背“一九得九”,不会把“九一得九”单独背一遍。
所以,内层循环的边界是由外层循环的值决定的,这一点是解出这道题的关键。很多同学在这里想不明白,写成固定值9,结果输出出来总觉得怪怪的。
1.3 输出格式里的三个隐形考点
第一,内层循环结束后必须换行。在C和Java里,printf、print默认不会换行;Python 的print默认换行,但如果你把内层循环也写成print,每个式子都会独占一行。所以一定要在每一行循环结束的位置单独输出一个换行符,或者让内层循环只输出数据、外层循环再负责换行。
第二,式子之间要有分隔符。最常见的是制表符\t或者空格。如果你直接连着写,输出会变成“11=112=2 2*2=4”,根本没法看。这事看起来小,但几乎所有第一次写这道题的同学都会踩一次。
第三,涉及字符串拼接时要注意类型转换。比如Java里写System.out.println(j + "*" + i + "=" + j * i),这里j * i是整数,会先计算结果再拼接成字符串,没问题。但如果你写成了j + "*" + i + "=" + i * j,运算顺序正常,也还行。真正容易错的是在别的地方想把整数当字符串直接用,结果得到字符的ASCII码之类的诡异数据。记住一个原则:先把算好的数值放到字符串表达式里,让它自动转换,别把两个整数直接拼,不然结果可能就是数字相加后的值。
2. 多语言实现:同一套逻辑,不同脾气
2.1 C语言版:printf的格式化就是标准答案
先给一个能直接跑的C语言版本:
#include <stdio.h> int main(void) { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= i; j++) { printf("%d*%d=%d\t", j, i, i * j); } printf("\n"); } return 0; }这里printf里的%d是整数占位符,后面依次对应 j、i、i*j。为什么这么写?因为C语言没有内置的字符串拼接,一切输出都靠格式化函数。\t是制表符,在控制台里通常能起到对齐作用,但注意不同终端下制表符宽度不一样,所以真正的“像素级对齐”需要靠宽度占位符,后面第三节再细讲。
这段代码的输出是:
1*1=1 1*2=2 2*2=4 1*3=3 2*3=6 3*3=9 ……C语言的坑在于:如果你在printf里少写了一个%d,或者参数数量和占位符数量对不上,轻则输出乱码,重则出现未定义行为。所以在循环体里写printf("%d*%d=%d\t", j, i, i * j);时,一定要数清楚:三个占位符,三个参数,一个不多一个不少。还有一个细节:内层循环里的变量建议用j表示列号,i表示行号,这样阅读代码的人能一眼看懂。别用a、b,代码短的时候无所谓,代码一长就乱了。
2.2 Java版:System.out.printf与println的配合
Java的写法跟C非常接近,但用的是System.out.printf:
public class MultiplicationTable { public static void main(String[] args) { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= i; j++) { System.out.printf("%d*%d=%d\t", j, i, i * j); } System.out.println(); } } }注意最后一行的System.out.println(),它的作用是换行。如果你写成System.out.print("\n"),效果也一样。很多Java初学者分不清print、printf、println:
print:不换行地输出字符串。printf:按格式化字符串输出,不换行,和C语言的printf类似。println:输出内容后换行。
这三个是不同的方法,不是同一种写法的手滑。如果只用println,你就得自己拼字符串,比如System.out.println(j + "*" + i + "=" + (i * j));,这样也能实现,但没有printf直观。我建议你两个方法都掌握,因为很多面试官就喜欢问你“println和printf的区别”。
Java版的另一个常见问题是字符串拼接里忘记括号。例如System.out.println(j + "*" + i + "=" + i * j);,由于运算符优先级,*会先算,所以i * j结果是乘积,没问题。但如果我把+和*混在一起,比如System.out.println(j + i * j);,那结果就是整数加法,不是拼接。遇到这种问题,最简单的方法就是把要展示的结果用一个变量先存起来,再拼接,避免被优先级坑到。
2.3 Python版:end参数与f-string
Python的代码是最短的,但陷阱也不少:
for i in range(1, 10): for j in range(1, i + 1): print(f"{j}*{i}={i * j}", end="\t") print()这里range(1, 10)会生成从1到9的整数,注意右边界10是不包含的。所以外层 i 是1到9。内层range(1, i + 1)表示从1到i,刚好就是每一行的列数。如果写成range(1, i),那第9行就只有1到8,少了9,特别隐蔽。
再说print的end参数。Python的print默认在输出末尾加换行符,如果你直接写print(f"{j}*{i}={i*j}"),那每个式子都会单独占一行,完全不是表格的样子。解决办法就是设置end="\t",让每个式子之间用制表符分隔。内层循环结束后,单独写一个print(),利用默认的换行符实现换行。
f-string是Python 3.6以后才有的特性,写法很简洁:f"{j}*{i}={i * j}",花括号里可以直接写变量或表达式。如果你的环境还是Python 3.5,就得改用"{0}*{1}={2}".format(j, i, i * j)。现在绝大多数机器都是3.8以上了,直接用f-string没问题。
2.4 JavaScript版:从控制台到网页
如果你在浏览器控制台调试,可以这么写:
for (let i = 1; i <= 9; i++) { let line = ''; for (let j = 1; j <= i; j++) { line += `${j}*${i}=${i * j}\t`; } console.log(line); }这里我用let line把每一行的内容先拼成一个字符串,再一次性console.log输出。为什么不直接在内层console.log?因为控制台每条log都会自动换行,直接打印会得到一列表达式,根本不是表格。拼接的好处是你可以完全控制换行时机。
如果要在网页上显示这张表,那就得操作DOM:
let html = ''; for (let i = 1; i <= 9; i++) { for (let j = 1; j <= i; j++) { html += `${j}*${i}=${i * j} `; } html += '<br>'; } document.getElementById('table').innerHTML = html;在HTML里,用空格分隔可能还不够清晰,常见的做法是把每个式子放进<span>标签里,设置等宽字体和间距。这里为了省事直接在字符串里加空格。这个版本稍后跟真实打印那一节联动,能直接生成一张可以在浏览器里打印成A4纸的九九口诀表。
3. 进阶玩法:把口诀表玩出花来
3.1 对齐的艺术:让表格真正像表格
用\t做分界很方便,但不同终端、不同字体下制表符的宽度不一致,输出的表格可能歪歪扭扭。想让表格漂亮,就要用固定宽度占位符。
以C语言为例,printf("%d*%d=%-3d", j, i, i * j);这个语句中的%-3d表示整数左对齐,占3个字符宽度。如果数字只有1位,就补两个空格;如果数字是10、12这种两位数,就占满3位,不需要补空格。这样每个式子结束后的宽度基本一致,整体效果比制表符稳定得多。
对应的Python写法是:
for i in range(1, 10): for j in range(1, i + 1): print(f"{j}*{i}={i * j:<4}", end="") print(){i * j:<4}表示把乘积左对齐,占4个字符宽度。如果不足4位,就在右侧补空格。运行结果里,每一列的起始位置能对齐,看起来就是一张整齐的表格。右对齐则用:>4,效果会是数字的个位对齐,这两种风格在程序输出里都很常见。
3.2 输出完整方阵,而不是只有下三角
有些题目不只要下三角,而是要求输出完整的9x9方阵。这时候内层循环要固定从1到9,外层循环也是1到9。问题是当列号大于行号时,没有对应表达式,必须补空格占位。
C语言实现:
#include <stdio.h> int main(void) { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= 9; j++) { if (j <= i) { printf("%d*%d=%-4d", j, i, i * j); } else { printf("%-8s", " "); } } printf("\n"); } return 0; }这里的技巧是:占位空格的宽度要跟正常输出一个东西的宽度相等,否则表格会错位。我用了%-8s来输出一个空格并左对齐占8位,就是为了抵消正常输出“1*1=1 ”所占的宽度。这个玩法看起来很麻烦,但能帮你更深刻地理解“占位符”“宽度”这些概念。实际做题时,如果题目没要求方阵,老老实实输出下三角就够了,别画蛇添足。
3.3 倒序输出:看似简单,其实容易错
还有一种常见变体:第一行输出9个式子,第二行输出8个……最后一行只输出1个。思路是把外层循环倒过来:
#include <stdio.h> int main(void) { for (int i = 9; i >= 1; i--) { for (int j = 1; j <= i; j++) { printf("%d*%d=%d\t", j, i, i * j); } printf("\n"); } return 0; }这里最容易犯的错是:把内层循环也写成for (int j = i; j >= 1; j--)。这样做不是不对,而是输出的顺序会变成“99、89、7*9……”,每一行内的算式顺序反过来了。如果你只是想让行数递减,但行内依然从1开始递增,内层循环就不要动,只改外层。这种题往往考的是你能不能准确区分行和列的独立控制,很多老手也会在这上面马虎。
3.4 网页版与真实打印:九九口诀表上A4纸
既然题目带“打印”,我们就不能只聊控制台。九九口诀表也常被做成学习海报,在浏览器里显示出来后,用打印功能输出到纸上。
一个最简单的HTML页面可以这样写:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>九九口诀表</title> <style> body { font-family: "Courier New", monospace; } @media print { .no-print { display: none; } table { border-collapse: collapse; } td { padding: 4px 8px; border: 1px solid #ccc; } } </style> </head> <body> <div id="table"></div> <button class="no-print" onclick="window.print()">打印口诀表</button> <script> let html = '<table>'; for (let i = 1; i <= 9; i++) { html += '<tr>'; for (let j = 1; j <= i; j++) { html += `<td>${j}*${i}=${i * j}</td>`; } html += '</tr>'; } html += '</table>'; document.getElementById('table').innerHTML = html; </script> </body> </html>把这段代码存成.html文件,用浏览器打开,点“打印口诀表”按钮,就能看到打印预览。这里用到了一个关键知识点:打印样式要写在@media print里面,这样屏幕上可以只显示数据,打印时却带表格边框。很多网页打印出来没有边框、内容错位,十有八九是CSS里没写打印样式。
4. 常见问题与排查实录
4.1 输出全挤成一团,数字全粘在一起
这是新手翻车率最高的问题。比如C语言里写下printf("%d*%d=%d", j, i, i * j);,而没有加制表符或空格,输出就会变成这样:
1*1=11*2=2 2*2=4……原因很好理解:每条式子之间没有分隔符,数据一个接一个地输出。解决办法就是在格式串末尾加上\t,或者在每个式子后面打印一个空格。如果你同时调整了宽度占位符,例如%-4d,其实也自带右侧空格,能起到分隔效果。记住一个口诀:“不怕多打一个分隔符,就怕少打一个换行符。”
4.2 换行位置不对,表格变成一条长龙
如果你在Python里这样写:
for i in range(1, 10): for j in range(1, i + 1): print(f"{j}*{i}={i*j}", end="")运行结果就是一行超长的字符串,因为两个print都用了end="",没有换行。或者说你在内层循环里用了print(),每个式子都单独成行。这些都是“换行时机”没掌握好。
正确的做法是:内层循环负责输出一行里的所有式子,结束后立刻换行。在最直观的控制台程序里,换行指令写在外层循环体的末尾。如果你把换行指令写在外层循环的开头,那第一行前面会冒出一个空行,而且最后一行的换行会缺失。这个细节在输出到文件的时候尤其明显。
4.3 出现重复算式:19和91同时出现
有同学写完循环后,发现表格里同时出现了“19=9”和“91=9”,第一反应是代码写错了。其实如果你的循环是这样写的:
for (int i = 1; i <= 9; i++) { for (int j = 1; j <= 9; j++) { printf("%d*%d=%d\t", j, i, i * j); } printf("\n"); }那输出的就是完整方阵,当然会有重复。因为当 i 为9时,j 会从1到9,于是“19”“29”……“99”全都有;而当 i 为1时,j 也是1到9,于是“11”“21”“31”……也全都有。题目如果要求的是口诀表,通常只需要下三角,也就是j <= i这个边界条件。
如果题目真的要求全方阵,那就不是重复,而是题意如此。做题前一定要看清楚:是“九九乘法口诀表”还是“九九乘法表方阵”。这两个词在外观上区别很大,我见过有人因为没看清要求,最后被扣分。
4.4 系统打印服务已关闭 / 浏览器打印闪退怎么处理
标题里的“打印”如果是想让九九口诀表真的从打印机里出来,那就会遇到和代码无关的坑。很多人辛辛苦苦写完HTML,点打印按钮却发现浏览器没反应,或者弹窗一闪就没了,又或者提示“系统打印服务已关闭”。
我整理了几种最容易碰到的情况:
| 现象 | 可能原因 | 排查与处理 |
|---|---|---|
| 提示系统打印服务已关闭 | Windows的Print Spooler服务被停止 | 按Win+R输入services.msc,找到Print Spooler,重启该服务 |
| 浏览器点击打印就闪退 | 打印机驱动异常或浏览器设置损坏 | 先把默认打印机改成“Microsoft Print to PDF”,看能否进入预览;能进入则说明驱动有问题 |
| 网页打印没有边框、内容错位 | 缺少打印样式 | 在CSS里写@media print,给表格设置边框和页面边距 |
| 打印预览空白 | 表格使用了display: none或元素没有加载完 | 检查JS是否在DOM加载后才填充数据,别在head里直接执行DOM操作 |
这些都是实际会碰到的问题。尤其是“系统打印服务已关闭”,很多人的第一反应是重装驱动,其实进服务管理器把它重新启动就行。不过也别高兴太早,这个服务如果频繁自动停止,往往是打印机驱动冲突,最好把不用的虚拟打印机都删掉。
4.5 程序员口中的“打印”不止一种
在写九九口诀表的时候,我们说的“打印”是输出到控制台。但在实际开发中,你还经常听到“打印日志”“打印SQL”“打印调试信息”这些说法,它们本质上也是“把信息输出到某个地方”,可能是控制台、日志文件或者调试窗口。比如项目里配置MyBatis打印SQL,就是让框架把执行的SQL语句输出到控制台;Visual Studio里把调试信息保存到日志文件并同时打印显示,也是类似思路。
所以不用把“打印”这个词想得太狭窄。它既可能是控制台输出,也可能是把网页渲染成PDF,还可能是日志系统里的一条记录。只要理解了“把数据变成可见文本”这个本质,你在不同场景里都能快速迁移。回到九九口诀表这个题,它的核心价值其实是让你学会用最朴素的方式理解输出机制,无论将来做后端日志还是前端页面,都能用上。
我个人实际写这段代码时,体会最深的一点是:九九乘法表几乎是“输出控制”的最小完整样本。项目再复杂,也逃不开“循环—拼接—格式化—输出—换行”这套动作。你可以把每一行看成一条日志记录,把每个式子看成一个数据字段,就能明白为什么很多人说“会九九乘法表,就摸到了编程输出的门槛”。
最后再分享一个小技巧:几乎每种主流语言都支持类似C语言里的printf格式化占位符。你只要掌握了%d、%s、%-3d、\t这几个基础写法,不管以后用Java的System.out.printf,还是Python的f-string控制宽度,思路都是相通的。遇到输出不对齐的问题,先想想:是不是宽度占位符少了,或者换行位置错了。这两个原因能解决九成以上和“打印”有关的代码问题。