返回首页

dc:堆栈计算器上的编程

dc — 带有寄存器、宏和递归的 Unix 堆栈计算器编程语言。本文分解堆栈模型、条件构造、FizzBuzz 和 Peg Solitaire 示例,附完整代码。

在 dc 上编程 FizzBuzz:从堆栈到游戏
Advertisement 728x90

dc:面向开发者的计算器编程语言

dc 是一款经典的 Unix 计算器,比 C 语言还要古老,包含在大多数系统的标准发行版中,包括 macOS。在其简单的外表下,隐藏着一门功能齐全的基于栈的编程语言,拥有寄存器、宏、条件结构和递归功能。其核心模型是栈:数字和字符串通过命令压入栈中,运算符弹出栈顶元素,执行操作,并返回结果。

第一个计算:要计算 2 + 2,输入 2 2 + p。这里,2 2 将数字逐行压入栈,+ 将栈顶两个元素相加,p 打印结果。

2 2 +
p
4

栈是关键元素:每个字面量(数字或 [字符串])都会将一个值压入栈顶。注释以 # 开头。

Google AdInline article slot

使用寄存器和变量

dc 提供了 256 个寄存器(a-z、0-9 和其他字符),用于存储数字或字符串。命令 sR 将栈顶保存到寄存器 R 中,lR 将其加载回来。寄存器独立于主栈维护状态。

保存和恢复字符串的示例:

[你好,世界!] sx
lx
p
你好,世界!

寄存器适合用作全局变量。寄存器中的数字操作方式与栈类似:li 将 i 加载到栈上。

Google AdInline article slot

宏作为函数

dc 中的函数是宏:存储在寄存器中的字符串,通过命令 lRx 执行,其中 x 是寄存器。宏定义为 [代码] sR

求和寄存器 a 和 b 的函数示例:

[la lb +] sS
1 sa
2 sb
lSx
p
3

宏支持递归,并且可以相互调用,形成子程序。

Google AdInline article slot

条件结构

分支基于比较栈顶两个元素。运算符包括 =><!= 等。如果条件为真,则执行指定寄存器中的宏。

[[相等] p] sE
[[不相等] p] sN
1 0
=E
1 0
!=N
不相等

比较会消耗栈中的两个元素,留下结果(0 或 1)。

递归循环

没有循环,但通过宏自调用实现递归。打印数字 1 到 10 的示例:

[
	li p
	1 + si
	li 11 >C
] sC

1 si
lCx

宏 C 打印 i,递增它,并在 i < 11 时递归调用自身。进入时,当前的 i 已经在栈上。

  • dc 中递归的优点:简单,无需显式循环。
  • 限制:深度递归可能导致栈溢出。
  • 替代方案:尾递归可最小化栈使用。

用 dc 实现 FizzBuzz

实现到 100 的 FizzBuzz 展示了宏、条件和递归的组合。关键命令:P(打印不带换行),c(清空栈)。

完整代码:

[[Fizz] P 0 sd] sF
[[Buzz] P 0 sd] sB
[li 3 % 0 =F li 5 % 0 =B] sW
[10 P] sP
[li p c] sD
[
  1 sd
  lWx
  ld 0 =P
  ld 1 =D
  li 1 + si
  li 101 >M
] sM

1 si
lMx

逻辑:标志 d 决定是否打印数字。宏 W 检查是否能被 3/5 整除,设置 Fizz/Buzz。P 与 10(换行符)用于换行。

高级应用:孔明棋

dc 允许解决复杂任务,例如孔明棋——一种棋盘上的棋子跳跃谜题。棋盘状态编码为二进制数字,通过算术和位运算(移位和掩码可用)进行操作。

RPN 中的移动验证公式由于位检查而看起来复杂,但能实现完整游戏:从初始配置到最终棋子。

这种方法展示了 dc 作为无需编译的算法原型设计工具。

关键要点

  • 栈是所有操作的基础:字面量压入,运算符弹出/压入结果。
  • 寄存器(共 256 个)用于状态,宏用于子程序,通过 lRx 调用。
  • 条件消耗栈,递归替代循环。
  • p/P 用于输出,c 用于清空,% 用于取模。
  • 适用于脚本编写、原型设计和学习 RPN。

— Editorial Team

Advertisement 728x90

继续阅读