- 示例工程
- 教程
【免费下载链接】learngo
❤️ 1000+ Hand-Crafted Go Examples, Exercises, and Quizzes. 🚀 Learn Go by fixing 1000+ tiny programs.
本篇文章围绕 learngo 仓库中 15-project-retro-led-clock 项目的GOAL 2: Printing the Clock展开:在上一阶段“打印数字”的基础上,把当前时间拆成小时、分钟、秒,用placeholder数组拼出[8][5]string的时钟数组并逐行打印,同时加入冒号分隔符。读完本篇,你将掌握time.Now()的取时用法、用整除与取模从两位数中拆出十位/个位数字、以及用二维数组组合渲染终端图形的完整思路,并可直接运行本仓库中的答案代码查看效果。
关联文档与本步目标
指定文档 15-project-retro-led-clock/02-printing-the-clock/README.md 给出了本步的 6 个挑战步骤:
- 获取当前时间;
- 从当前时间中取出当前的小时、分钟和秒;
- 通过从 digits 数组中取数字来创建 clock 数组;
- 使用 clock 数组打印时钟;
- 创建一个分隔符(separator)数组(它同样是一个 placeholder 类型);
- 把分隔符放入 clock 数组的正确位置。
本目录下的 main.go 保存的是上一步(打印数字)的答案,solution/main.go 才是本步的完整解法。整条学习路径由 15-project-retro-led-clock/README.md 串联:第 1 步打印数字、第 2 步打印时钟、第 3 步动画化、第 4 步(Bonus)让分隔符闪烁,最后是 05-full-annotated-solution/main.go 全量注释版。如果你在某一小步卡住,可以只查看下一步的答案而不必看最终整体解。
前置知识:placeholder 类型与 digits 数组
本步延续第 1 步(见 01-printing-the-digits/README.md)打下的基础:定义一个命名数组类型,再为 0~9 各准备一个 5 行 × 3 列的像素字符块。
// 为了易读和类型安全,定义命名类型 type placeholder [5]string每个数字就是placeholder的一个字面量,行内用█表示“亮”,空格表示“灭”。例如zero:
zero := placeholder{ "███", "█ █", "█ █", "█ █", "███", }随后把它们装进 digits 数组:
digits := [...]placeholder{ zero, one, two, three, four, five, six, seven, eight, nine, }关于类型,01-printing-the-digits/solution/main.go 的注释讲得很清楚:digits的类型“像是”[10][5]string,但placeholder是一个命名类型(named type),与未命名类型[5]string在类型层面并不相等,只是底层类型(underlying type)相同,都是[5]string。这正是本系列用命名类型封装数据的原因——防止把任意[5]string混入数字库。
本步的核心变化在于:不再静态地打印全部 10 个数字,而是把time.Now()取到的当前时间切成 8 个placeholder元素(时十位、时个位、冒号、分十位、分个位、冒号、秒十位、秒个位),动态组装成 clock 数组。
获取当前时间并拆分小时、分钟、秒
本步第一步是获取当前时间,并取出其中的时、分、秒。解法在 solution/main.go 中体现:
import ( "fmt" "time" ) now := time.Now() hour, min, sec := now.Hour(), now.Minute(), now.Second() fmt.Printf("hour: %d, min: %d, sec: %d\n", hour, min, sec)几个关键点:
time.Now()返回当前本地时间的time.Time值;Hour()、Minute()、Second()分别返回 0~23、0~59、0~59 的整数;- 这里用多返回值短声明一次性拿到三个字段;
fmt.Printf的调试输出便于先确认时间解析是否正确,再进入渲染环节。若在 Go Playground 中运行,输出会是 Playground 的固定时间;本地go run则显示你的系统当前时间。
用整除与取模拆出十位和个位
这是本步最有价值的技巧:一个两位数要拆成两个数字字符,只需一次整除和一次取模。例如 17 变为 1 和 7:
hour/10 // 十位 hour%10 // 个位hour/10:整除得到十位。17/10 = 1;hour%10:取模得到个位。17%10 = 7。
为什么同时需要两者?因为hour的取值范围是 0~23,min/sec是 0~59,当它们是一位数(如 7)时,7/10 = 0,7%10 = 7,正好补出一个“0”占位符,保证时钟始终以两位数渲染(如07)。这也是digits[0](零的数字块)在这个上下文里反复被用到的原因。
组装 clock 数组并加入分隔符
挑战步骤 3 与 6 要求:从 digits 数组中取数字创建 clock 数组,并把分隔符放到正确位置。分隔符同样是一个placeholder(步骤 5),用░字符表示“灭但可见”的弱亮度像素:
colon := placeholder{ " ", " ░ ", " ", " ░ ", " ", }░与数字用的█相对:它是半亮字符,视觉上像复古 LED 显示屏未完全熄灭的灯点。第 1 步 README(01-printing-the-digits/README.md)也明确给出了这两个字符的角色:Digit character 用█,Separator character 用░。
然后把时、分、秒按“十位/个位/冒号”的节奏铺进一个[8][5]string形状的数组:
// [8][5]string clock := [...]placeholder{ // extract the digits: 17 becomes, 1 and 7 respectively digits[hour/10], digits[hour%10], colon, digits[min/10], digits[min%10], colon, digits[sec/10], digits[sec%10], }整个clock数组的布局如下(8 个元素依次是):
| 下标 | 内容 | 含义 |
|---|---|---|
| 0 | digits[hour/10] | 小时十位 |
| 1 | digits[hour%10] | 小时个位 |
| 2 | colon | 冒号分隔符 |
| 3 | digits[min/10] | 分钟十位 |
| 4 | digits[min%10] | 分钟个位 |
| 5 | colon | 冒号分隔符 |
| 6 | digits[sec/10] | 秒钟十位 |
| 7 | digits[sec%10] | 秒钟个位 |
由此,时钟字符串17:05:09会被逐位映射成对应的像素块。若此刻是 17:05:09,你会看到:1、7、冒号、0、5、冒号、0、9。
逐行打印 clock 数组
挑战步骤 4 是打印。渲染逻辑与第 1 步一致,只是循环对象从digits换成clock:
for line := range clock[0] { for digit := range clock { fmt.Print(clock[digit][line], " ") } fmt.Println() }- 外层循环
for line := range clock[0]遍历行(0~4)。因为所有 placeholder 长度相同(5 个元素),只取clock[0]的长度即可; - 内层循环
for digit := range clock遍历 8 个元素,打印每个元素当前行; - 每个数字块后打印两个空格,作为数字之间的间隔;
- 每行结束打印换行,让 8 个数字块按行对齐。
外层 range 遍历数组时只取索引(下标),写法等价于for line := 0; line < 5; line++(solution/main.go 处的注释也做了说明)。fmt.Print不追加换行,fmt.Println负责行尾换行,二者配合才能形成 5 行 × N 列的整齐块状输出。
可以想象输出效果(以 10 为例,见 01-printing-the-digits/README.md 中的示例):
██ ███ <--- 每打印完一行就换行 █ █ █ █ █ █ █ █ █ ███ ███ ^^ ++----> 数字之间加空格本步运行后,终端会显示类似下面这样的一帧静态时钟(实际数字取决于运行时刻):
███ ██ ░ ███ ███ ░ ███ ███ █ █ █ ░ █ █ █ █ ░ █ █ █ █ █ █ █ █ █ ███ █ █ ███ █ █ █ ░ █ █ █ ░ █ █ █ ███ ███ ███ ███ ███ ███运行与验证方式
在仓库的15-project-retro-led-clock/02-printing-the-clock/solution目录下直接运行:
go run main.go即可看到当前时间被渲染成两行像素块时钟。若想先观察调试信息,保留解法中的fmt.Printf("hour: %d, min: %d, sec: %d\n", hour, min, sec)即可在渲染前打印一次数值时间。
注意事项:
- 本步是“静态帧”:程序运行一次后即退出,时钟不会自动刷新。要让它每秒更新并产生动画效果,是下一个目标(03-animating-the-clock)的内容——那里会用
for {}无限循环、screen.Clear()与screen.MoveTopLeft()以及time.Sleep(time.Second)实现动态刷新,完整版见 05-full-annotated-solution/main.go; - Go Playground 无法执行无限循环,也不建议在其中运行会清屏的代码;本地终端运行效果最佳;
- 终端需支持 Unicode 块字符(
█、░)才能正确显示,绝大多数现代终端默认支持。
与后续步骤的衔接
本步是整条 Retro LED Clock 学习链的承上启下环节:
- GOAL 3: Animate the Clock:把本步的取时与组装逻辑放进无限循环,每隔 1 秒更新一次,并用
screen包清屏、把光标移回左上角; - GOAL 4: Blink the Separators:让冒号每 2 秒闪烁一次(1 秒可见、1 秒不可见),提示两种实现路径:直接改 clock 数组,或在打印时按需决定是否输出分隔符;
- exercises 中还有四个进阶练习:把 placeholder 重构到多个文件(01-refactor)、每 10 秒打印 " ALARM! "(02-alarm)、把刷新周期缩短到 1/10 秒以显示分秒(03-split-second),以及难度最高的从右到左滑动时钟动画(04-ticker)。
本步沉淀的核心知识点
time.Now()与Hour()/Minute()/Second()的取时组合;- 用
x/10与x%10把两位数拆成十位、个位,配合digits[0]补齐个位数显示; - 命名数组类型
placeholder与[...]placeholder数组字面量的组合使用; - 冒号分隔符的设计:同构的
placeholder类型、░字符表意半亮; - 双层 for-range 逐行逐块渲染:外层管 5 行,内层管 8 个元素,
fmt.Print+ 双空格 +fmt.Println控制排版。
掌握了这些,你既能在终端里渲染出任意的数字字形,也为后续动画化、闪烁与项目重构(练习 01-refactor)打下了坚实基础。
- 示例工程
- 教程
【免费下载链接】learngo
❤️ 1000+ Hand-Crafted Go Examples, Exercises, and Quizzes. 🚀 Learn Go by fixing 1000+ tiny programs.
相关推荐
learngo 复古 LED 时钟项目四道数组实战练习:重构、闹钟、百分秒与滚动动画
learngo 复古 LED 时钟项目四道数组实战练习:重构、闹钟、百分秒与滚动动画 导读 本指南围绕 learngo 仓库中 15 project retro
示例工程教程WLED word-clock-matrix usermod:用 8x8 LED 矩阵点亮一块文字时钟(Segment 分段 + 预设机制深度解析)
WLED word clock matrix usermod:用 8x8 LED 矩阵点亮一块文字时钟(Segment 分段 + 预设机制深度解析) 本文以 W
物联网嵌入式智能硬件简易时钟项目指南:Simple-Clock 开源应用详解
简易时钟项目指南:Simple Clock 开源应用详解 1. 项目目录结构及介绍 Simple Clock 是一个集时钟、闹钟、秒表和计时器功能于一身的应用,
移动开发桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考