dc:面向开发者的计算器编程语言
dc 是一款经典的 Unix 计算器,比 C 语言还要古老,包含在大多数系统的标准发行版中,包括 macOS。在其简单的外表下,隐藏着一门功能齐全的基于栈的编程语言,拥有寄存器、宏、条件结构和递归功能。其核心模型是栈:数字和字符串通过命令压入栈中,运算符弹出栈顶元素,执行操作,并返回结果。
第一个计算:要计算 2 + 2,输入 2 2 + p。这里,2 2 将数字逐行压入栈,+ 将栈顶两个元素相加,p 打印结果。
2 2 +
p
4
栈是关键元素:每个字面量(数字或 [字符串])都会将一个值压入栈顶。注释以 # 开头。
使用寄存器和变量
dc 提供了 256 个寄存器(a-z、0-9 和其他字符),用于存储数字或字符串。命令 sR 将栈顶保存到寄存器 R 中,lR 将其加载回来。寄存器独立于主栈维护状态。
保存和恢复字符串的示例:
[你好,世界!] sx
lx
p
你好,世界!
寄存器适合用作全局变量。寄存器中的数字操作方式与栈类似:li 将 i 加载到栈上。
宏作为函数
dc 中的函数是宏:存储在寄存器中的字符串,通过命令 lRx 执行,其中 x 是寄存器。宏定义为 [代码] sR。
求和寄存器 a 和 b 的函数示例:
[la lb +] sS
1 sa
2 sb
lSx
p
3
宏支持递归,并且可以相互调用,形成子程序。
条件结构
分支基于比较栈顶两个元素。运算符包括 =、>、<、!= 等。如果条件为真,则执行指定寄存器中的宏。
[[相等] 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
暂无评论。