面向堆栈的语言提供的简单模型允许简单地解释表达式和程序,并且理论上可以更快地进行评估,因为不需要进行语法分析,而只需进行词法分析即可。
面向栈的语言在一个或多个栈上运行,每个栈可能有不同的用途。因此,可能需要修改其他编程语言中的编程构造,以用于面向栈的系统。此外,某些面向栈的语言以postfix或Reverse Polish表示法操作,即命令的任何参数或参数都在该命令之前声明。例如,将使用后缀表示法2, 3, multiply
代替multiply, 2, 3
(前缀或波兰语表示法)或2 multiply 3
(中缀表示法)
基于堆栈的算法[ 编辑]
考虑一种基于Postfix堆栈的语言,例如PostScript。要了解堆栈定向的工作原理,请在计算诸如的表达式时2 3 mul
,考虑一个简单的思维实验。
想象站在一个传送带(所述的端部输入),在其上已放置(在序列)标记的板2
,3
和mul
。一个人可以在传送带(2
)的末端拿走印版,但是直到刚拿走印版完成某些操作后,才能从传送带上看到或拿走其他印版。板的唯一存储方式是堆叠,只能从堆叠的顶部(而不是从中间或底部)添加或移除板。一个也提供空白板(和标记),并且可以丢弃板(这是永久性的)。尝试执行计算。
取盘子2
并将其放在堆栈上,然后取盘子3
并将其放在堆栈上。接下来,拿起mul
盘子。这是要执行的指令。然后,从堆栈中取出前两个板,将其标签(2
和3
)相乘,然后将结果(6
)写在新板上。丢弃两个旧板(2
和3
)和板mul
,然后将新板放在堆栈上。输送机上没有更多的板时,计算结果(6
)显示在堆栈顶部的板上。
这是一个非常简单的计算。如果需要更复杂的计算,例如(2 + 3) × 11 + 1
?如果首先以后缀形式(即)编写2 3 add 11 mul 1 add
,则可以完全相同的方式执行计算并获得正确的结果。下表显示了计算步骤。每列都显示一个输入元素(位于输送机末端的板),以及处理该输入后的堆栈内容。
输入项 | 2 | 3 | 加 | 11 | 多 | 1个 | 加 |
---|---|---|---|---|---|---|---|
堆 | 2 | 3 2 |
5 | 11 5 |
55 | 1 55 |
56 |
处理完所有输入后,堆栈包含56
,这就是答案。
由此可以得出以下结论:基于堆栈的编程语言只有一种处理数据的方法,即从堆栈顶部获取一条数据(称为弹出),然后将数据放回堆栈顶部(称为推入)。可以以常规方式或另一种编程语言编写的任何表达式都可以以后缀(或前缀)形式编写,因此可以被面向堆栈的语言解释。
堆栈操作[ 编辑]
由于堆栈是使用面向堆栈的语言操作数据的关键手段,因此此类语言通常提供某种堆栈操作运算符。通常提供的是dup
,用于复制堆栈顶部的元素exch
(或swap
),交换堆栈顶部的元素(第一个变为第二个,第二个变为第一个),roll
循环置换堆栈中或部分堆栈中的元素,pop
(或drop
),以丢弃堆栈顶部的元素(推式是隐式的),等等。这些成为研究程序的关键。
叠加效果图[ 编辑]
为了帮助理解语句的效果,使用简短的注释显示语句之前和之后的堆栈顶部。如果有多个项目,则堆栈的顶部在最右边。此表示法通常在Forth语言中使用,该说明用括号括起来。
(之前-之后)
例如,描述了基本的Forth堆栈运算符:
dup (a-aa)
下降(
a- )交换(ab-ba)
超过(ab-aba)
腐烂 (abc-bca)
并说明fib
以下功能:
fib (n-n')
它等效于Hoare逻辑中的前提条件和后置条件。这两个注释也可以引用为断言,在基于堆栈的语言的上下文中不一定要考虑。
PostScript堆栈[ 编辑]
PostScript和其他一些堆栈语言具有其他单独的堆栈,可用于其他目的。
变量和字典[ 编辑]
已经分析了不同表达式的评估。变量的实现对于任何编程语言都非常重要,但是对于面向堆栈的语言,它尤其需要关注,因为只有一种与数据交互的方式。
在面向堆栈的语言(如PostScript)中实现变量的方式通常涉及一个单独的专用堆栈,该堆栈包含键值对的字典。要创建变量,必须首先创建一个键(变量名),然后将其与一个值关联。在PostScript中,名称数据对象的前缀为/
,因此名称数据对象的前缀/x
也可以与数字关联42
。该define
命令是def
,使
/x 42 def
将名称x
与42
堆栈顶部的字典中的数字相关联。/x
和之间存在区别x
–前者是代表名称的数据对象,x
代表在下定义的内容/x
。
程序[ 编辑]
基于堆栈的编程语言中的过程本身就被视为数据对象。在PostScript中,过程在{
和之间表示}
。
例如,在PostScript语法中,
{ dup mul }
表示一个匿名过程,用于复制堆栈顶部的内容,然后将结果相乘-平方过程。
由于过程被视为简单的数据对象,因此可以定义过程的名称。检索它们后,将直接执行它们。
词典提供了一种控制作用域以及定义存储的方法。
由于数据对象存储在最顶层的字典中,因此自然会产生意外的功能:从字典中查找定义时,先检查最顶层的字典,然后再检查下一个,依此类推。如果定义的过程与另一个字典中已定义的另一个过程具有相同的名称,则将调用本地过程。
一些典型程序的剖析[ 编辑]
程序经常带有争论。它们由过程以非常特定的方式处理,这与其他编程语言不同。
要在PostScript中检查斐波那契数字程序:
/ FIB
{
DUP DUP 1 当量 EXCH 0 当量 或 不
{
DUP 1 子 FIB
EXCH 2 子 FIB
加
} 如果
} DEF
在堆栈上使用递归定义。斐波那契数函数采用一个参数。首先,将其测试为1或0。
假设计算如下,分解程序的每个关键步骤,以反映堆栈fib(4)
:
栈:4
杜普
栈:4 4
杜普
堆栈:4 4 4
1当量
堆栈:4 4错误
ch
堆栈:4错误4
0当量
堆栈:4假假
要么
堆栈:4错误
不
堆栈:4个真实
由于表达式的计算结果为true,因此将计算内部过程。
栈:4
杜普
栈:4 4
1个子
栈:4 3
纤维
(此处递归调用)
堆栈:4 F(3)
ch
堆栈:F(3)4
2子
堆栈:F(3)2
纤维
(此处递归调用)
堆栈:F(3)F(2)
加
堆栈:F(3)+ F(2)
这是预期的结果。
此过程不使用命名变量,仅使用堆栈。可以使用该/a exch def
构造创建命名变量。例如,{/n exch def n n mul}
是一个具有命名变量的平方过程n
。假定/sq {/n exch def n n mul} def
和3 sq
被调用,sq
将按以下方式分析该过程:
堆:3 / n
ch
堆栈:/ n 3
定义
堆栈:空(已定义)
ñ
栈:3
ñ
栈:3 3
多
栈:9
这是预期的结果。
控制和流程[ 编辑]
由于存在匿名过程,因此流量控制可以自然地发生。if-then-else语句需要三段数据:一个条件,如果条件为true则执行一个过程,如果条件为false则执行一个过程。以PostScript为例,
2 3 GT { (2大于3) = } { (2不大于3) = } ifelse
在C中执行几乎等效的操作:
if (2 > 3 ) { printf (“ 2大于三\ n ” ); } else { printf (“ 2不大于3 \ n ” ); }
循环和其他构造类似。
转自: