6
社区成员
发帖
与我相关
我的任务
分享堆栈在编程中无处不在,在幕后。
每当你用几乎任何语言调用任何函数时,它的参数都会被压入堆栈,一些关于程序在完成函数后应该返回到哪里的信息也是如此。该函数从堆栈中弹出该数据,完成后,它会弹出下一步应该去哪里的信息。这种从堆栈中推入和弹出是计算机所做的相当大的一部分。但是,很少有语言以任何方式公开堆栈。
第四是当堆栈是语言的核心时会发生什么。
不幸的是,我们已经遇到了第一个问题。Forth 诞生于 1970 年代,当时人们不相信 Strings。Forth Stack 由小单元格组成,每个单元格都足够容纳一个数字或一个地址,而字符串放不下。因此,字符串需要大量的特殊情况处理,而不是适合 Forth 计算模型。
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>#! /usr/local/bin/gforth
." Hello, World!"
cr
bye
</code></span></span>
这里发生了很多事情:
#!shebang 行,有一个 hack 可以绕过它,这涉及在后面添加一个空格#!以使 Unix 和 Forth 都满意。cr命令打印新行bye命令退出——你需要这样做,否则 Forth 将进入 repl;这是一个不寻常的默认值。."意思是“直到下一个引号并打印那里的任何东西”。这个字符串实际上不像通常的编程语言那样是一个对象。之后有额外的空格.",这是语法的一部分。您当然可以将所有内容放在同一行,换行符在 Forth 中没有任何意义。
让我们朝着构建真正的功能迈出一小步。
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>#! /usr/local/bin/gforth
( Function to double a number )
: double
dup +
;
21 double .
cr bye
</code></span></span>
这里发生了一些事情:
( ... )是一个评论 - 再一次,你需要那个空间(: name ... ;定义一个函数 - 函数不接受参数或返回值,它们只是对堆栈做一些事情double函数做了两件事:dup复制栈顶,并将+栈顶的两个数字相加因此,让我们逐步跟踪发生了什么:
21- 压栈21(栈现在21)double- 调用double函数double-dup复制栈顶(栈现在21 21)double-+添加前两个数字(堆栈现在42)double返回(堆栈仍然42).打印栈顶的值(栈现在是空的)cr打印新行(堆栈仍然是空的)bye出口这可能看起来真的很复杂,但像这样的步骤是在许多编程语言的幕后发生的。
让我们做一些更复杂的事情,斐波那契函数。
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>#! /usr/local/bin/gforth
: fib recursive
dup 2 <= if
drop
1
else
dup 1 - fib
swap
2 - fib
+
endif
;
20 fib . cr
bye
</code></span></span>
那是很多!可能是我们迄今为止最复杂的斐波那契函数。
您可能已经了解它的20 fib . cr bye作用 - 它使用fib参数调用20,然后打印结果,打印新行,然后退出。
让我们看看fib功能是做什么的。它有一个很大的if ... else ... endif,所以让我们看看检查这两种情况。fib只会在堆栈的顶部操作,我会指出...它下面可能有更多的东西,但fib不会触及任何东西。
如果栈顶为 1:
fib开始,堆栈是 ( ... 1)dup复制最高值 ( ... 1 1)2推动2( ... 1 1 2)<=比较堆栈上的前两个值并推送0false,或者令人惊讶的-1是 true ( ... 1 -1)if去第一个分支 ( ... 1)drop丢弃堆栈的顶部值 ( ...)1推动1( ... 1)1将被视为返回值如果栈顶是 20:
fib开始,堆栈是 ( ... 20)dup复制最高值 ( ... 20 20)2推动2( ... 20 20 2)<=比较堆栈上的前两个值并推送0false,或者令人惊讶的-1是 true ( ... 20 0)if去第二个分支(... 20)dup复制最高值 ( ... 20 20)1推动1( ... 20 20 1)-减去前两个值 ( ... 20 19)fib用19( ... 20 4181)的参数调用自己swap交换栈顶的两个值 ( ... 4181 20)2推动2( ... 4181 20 2)-减去前两个值 ( ... 4181 18)fib用18( ... 4181 2584)的参数调用自己+添加前两个值 ( ... 6765)6765将被视为返回值在前往不可避免的 FizzBuzz 的路上,让我们首先做一个简单的循环打印数字 1 到 11:
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>#! /usr/local/bin/gforth
: ten-numbers
11 1 do i . cr loop
;
ten-numbers
bye
</code></span></span>
do ... loop是循环。堆栈上的前两个数字是 limit 和 start。Limit 是循环停止的地方,所以它需要比最后一个索引高 1——有点像 Python 的range(1,11).
i将当前循环索引压入堆栈。然后我们打印它.并添加一个换行符cr。
Forth 只支持函数内部的 this,因此我们必须将它包装在ten-numbers函数中。如果您尝试将其直接放入源代码或代表中,您会得到一个神秘的“解释仅编译词”。同样的故事if等等。
最后是 FizzBuzz!
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>#! /usr/local/bin/gforth
: divisible
mod 0=
;
: fizz-buzz
dup 15 divisible if
drop
." FizzBuzz"
else
dup 5 divisible if
drop
." Buzz"
else
dup 3 divisible if
drop
." Fizz"
else
.
endif
endif
endif
;
: fizz-buzz-loop
101 1 do i fizz-buzz cr loop
;
fizz-buzz-loop
bye
</code></span></span>
fizz-buzz-loopfizz-buzz-loop是一个从 1 到 100 的循环,fizz-buzz以索引作为参数调用divisible返回 true (-1) 或 false (0) 取决于堆栈中的第二个数字是否可以被顶部的数字整除fizz-buzz是一个三层嵌套if/else/endif——我不认为 Forth 有任何更简洁的方法来做到这一点dup 15 divisible if如果堆栈顶部可以被 15 整除,则采用第一个分支,在其他地方进行第二个分支(对于 5 和 3 以此类推)drop ." FizzBuzz"落在堆栈顶部并"FizzBuzz"改为打印.取栈顶并打印Forth 可能是“深奥语言”的有趣切入点。Forth 本身是一种真正严肃的语言,有一段时间在某些利基市场中获得了一定程度的普及,主要是在尺寸非常昂贵的情况下,如微控制器。使用像 Forth 这样的基于堆栈的语言进行一些编码练习可以让你为各种疯狂的深奥语言做好准备,比如 Befunge、Brainfuck 等等,因为基于堆栈的编程在那里很流行。
如果您想深入研究基于堆栈的虚拟机,如 JVM(和大多数虚拟机)或计算机体系结构(几乎所有虚拟机),稍微玩一下 Forth 也可能会很有趣。甚至大多数其他基于堆栈的系统都比 Forth 更少基于堆栈,但它可能是有用的实践。
对于严肃的编程,绝对不是。