拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用 Go 数组构建复古 LED 数字时钟:打印实时时钟(Retro LED Clock 项目第 2 步详解)

用 Go 数组构建复古 LED 数字时钟:打印实时时钟(Retro LED Clock 项目第 2 步详解)
  • 示例工程
  • 教程

【免费下载链接】learngo

❤️ 1000+ Hand-Crafted Go Examples, Exercises, and Quizzes. 🚀 Learn Go by fixing 1000+ tiny programs.

项目地址:https://gitcode.com/gh_mirrors/le/learngo
点击查看免费下载

本篇文章围绕 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 个挑战步骤:

  1. 获取当前时间;
  2. 从当前时间中取出当前的小时、分钟和秒;
  3. 通过从 digits 数组中取数字来创建 clock 数组;
  4. 使用 clock 数组打印时钟;
  5. 创建一个分隔符(separator)数组(它同样是一个 placeholder 类型);
  6. 把分隔符放入 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 个元素依次是):

下标内容含义
0digits[hour/10]小时十位
1digits[hour%10]小时个位
2colon冒号分隔符
3digits[min/10]分钟十位
4digits[min%10]分钟个位
5colon冒号分隔符
6digits[sec/10]秒钟十位
7digits[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.

项目地址:https://gitcode.com/gh_mirrors/le/learngo
点击查看免费下载

相关推荐

上一篇:Luxon 时区完全指南:从偏移量与 IANA 概念到 DST 边界行为
下一篇:DevToys 内存管理:优化工具运行时资源占用

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表