有人将此邮件发送给我,并声称这是Brainfuck的世界(我希望如此...)
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.
我知道它可以通过移动指针并递增和递减的东西来工作...
但是我仍然想知道,它实际上是如何工作的?首先如何在屏幕上打印任何内容?它如何编码文本?我一点都不明白...
有人将此邮件发送给我,并声称这是Brainfuck的世界(我希望如此...)
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.
我知道它可以通过移动指针并递增和递减的东西来工作...
但是我仍然想知道,它实际上是如何工作的?首先如何在屏幕上打印任何内容?它如何编码文本?我一点都不明白...
Answers:
要了解Brainfuck,您必须想象0
每个单元初始化的无限单元格数组。
...[0][0][0][0][0]...
当Brainfuck程序启动时,它指向任何单元。
...[0][0][*0*][0][0]...
如果向右>
移动指针,则将指针从单元格X移动到单元格X + 1
...[0][0][0][*0*][0]...
如果增加单元格值,+
则会得到:
...[0][0][0][*1*][0]...
如果再次增加单元格值,+
则会得到:
...[0][0][0][*2*][0]...
如果减少单元格值,-
则会得到:
...[0][0][0][*1*][0]...
如果向左<
移动指针,则将指针从单元格X移动到单元格X-1
...[0][0][*0*][1][0]...
要读取字符,请使用逗号,
。它的作用是:从标准输入读取字符,并将其十进制ASCII码写入实际单元格。
看一下ASCII表。例如,!
is的十进制代码33
,而a
is 97
。
好吧,假设您的BF程序存储器如下所示:
...[0][0][*0*][0][0]...
假设标准输入代表a
,如果您使用逗号,
运算符,则BF所做的是将a
十进制ASCII码读取97
到内存中:
...[0][0][*97*][0][0]...
您通常希望以这种方式思考,但是事实要复杂一些。事实是BF不会读取字符,而是读取一个字节(无论该字节是什么)。让我向您展示示例:
在Linux中
$ printf ł
印刷品:
ł
这是特定的波兰字符。此字符未通过ASCII编码进行编码。在这种情况下,它是UTF-8编码,因此它过去在计算机内存中占用一个以上的字节。我们可以通过进行十六进制转储来证明这一点:
$ printf ł | hd
这表明:
00000000 c5 82 |..|
零偏移。82
是第一个c5
字节,第二个字节代表ł
(我们将按顺序读取它们)。|..|
是图形表示,在这种情况下是不可能的。
好吧,如果您将ł
输入作为传递给读取单个字节的BF程序的输入,程序存储器将如下所示:
...[0][0][*197*][0][0]...
为什么197
呢 那么197
十进制为c5
十六进制。看起来很熟悉?当然。它是第一个字节ł
!
要使用点打印字符,.
它的作用是:假设我们将实际单元格值视为十进制ASCII码,则将相应字符打印到标准输出。
好吧,假设您的BF程序存储器如下所示:
...[0][0][*97*][0][0]...
如果现在使用点(。)运算符,则BF会执行以下打印:
一个
因为a
ASCII中的十进制代码是97
。
因此,例如这样的BF程序(97加2点):
++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++ ..
将其指向的单元格的值增加到97,并打印两次。
a
在BF中,循环包括循环开始[
和循环结束]
。您可以认为这就像在C / C ++中,条件是实际单元格值。
看看下面的BF程序:
++[]
++
将实际单元格值增加两次:
...[0][0][*2*][0][0]...
而[]
像while(2) {}
,所以它的无限循环。
假设我们不希望这个循环是无限的。我们可以例如:
++[-]
因此,每次循环循环都会减少实际单元格值。一旦实际单元格值被0
循环结束:
...[0][0][*2*][0][0]... loop starts
...[0][0][*1*][0][0]... after first iteration
...[0][0][*0*][0][0]... after second iteration (loop ends)
让我们考虑另一个有限循环的例子:
++[>]
此示例显示,我们尚未在循环开始于的单元格处完成循环:
...[0][0][*2*][0][0]... loop starts
...[0][0][2][*0*][0]... after first iteration (loop ends)
但是,结束我们的起点是个好习惯。为什么呢 因为如果循环结束了它开始的另一个单元格,则我们无法假定单元格指针将位于何处。老实说,这种做法可以减少脑筋。
Wikipedia有此代码的注释版本。
+++++ +++++ initialize counter (cell #0) to 10
[ use loop to set the next four cells to 70/100/30/10
> +++++ ++ add 7 to cell #1
> +++++ +++++ add 10 to cell #2
> +++ add 3 to cell #3
> + add 1 to cell #4
<<<< - decrement counter (cell #0)
]
> ++ . print 'H'
> + . print 'e'
+++++ ++ . print 'l'
. print 'l'
+++ . print 'o'
> ++ . print ' '
<< +++++ +++++ +++++ . print 'W'
> . print 'o'
+++ . print 'r'
----- - . print 'l'
----- --- . print 'd'
> + . print '!'
> . print '\n'
为了回答您的问题,,
和.
字符用于I / O。文本为ASCII。
在维基百科的文章接着在一些更深入,以及。
第一行
a[0] = 10
通过从0递增十倍来初始化。第二行的循环有效地设置了数组的初始值:(a[1] = 70
接近72,字符'H'的ASCII码),a[2] = 100
(接近101或'e' ),a[3] = 30
(接近32个空格代码)和a[4] = 10
(换行符)。该循环通过向单元格中添加7、10、3和1来实现a[1]
,a[2]
,a[3]
和a[4]
分别各一次通过循环-在总共10个加法针对每个小区(给予a[1]=70
等)。循环完成后,a[0]
为零。>++.
然后将指针移到a[1]
,保持70,在其上加上两个(产生72,即为大写字母H的ASCII字符代码),并将其输出。下一行将数组指针移至该数组指针并向
a[2]
其添加一个,产生101,即小写字母“ e”,然后将其输出。由于“ l”恰好是“ e”之后的第七个字母,因此要输出“ ll”,还要再加上七个(
+++++++
),a[2]
结果将输出两次。'o'是'l'之后的第三个字母,因此
a[2]
再增加3次并输出结果。程序的其余部分以相同的方式进行。对于空格和大写字母,将选择不同的数组单元,并根据需要递增或递减。
,
并.
用于I / O,就像使用进行C打印一样putchar
。它是由编译器处理的实现细节。
为了回答如何知道要打印什么的问题,我在打印发生的代码的右侧添加了ASCII值的计算:
> just means move to the next cell
< just means move to the previous cell
+ and - are used for increment and decrement respectively. The value of the cell is updated when the increment/decrement happens
+++++ +++++ initialize counter (cell #0) to 10
[ use loop to set the next four cells to 70/100/30/10
> +++++ ++ add 7 to cell #1
> +++++ +++++ add 10 to cell #2
> +++ add 3 to cell #3
> + add 1 to cell #4
<<<< - decrement counter (cell #0)
]
> ++ . print 'H' (ascii: 70+2 = 72) //70 is value in current cell. The two +s increment the value of the current cell by 2
> + . print 'e' (ascii: 100+1 = 101)
+++++ ++ . print 'l' (ascii: 101+7 = 108)
. print 'l' dot prints same thing again
+++ . print 'o' (ascii: 108+3 = 111)
> ++ . print ' ' (ascii: 30+2 = 32)
<< +++++ +++++ +++++ . print 'W' (ascii: 72+15 = 87)
> . print 'o' (ascii: 111)
+++ . print 'r' (ascii: 111+3 = 114)
----- - . print 'l' (ascii: 114-6 = 108)
----- --- . print 'd' (ascii: 108-8 = 100)
> + . print '!' (ascii: 32+1 = 33)
> . print '\n'(ascii: 10)
Brainfuck
和它的名字一样。它仅使用8个字符> [ . ] , - +
,是学习最快的编程语言,但最难实现和理解。
…。最后使您最终陷入瘫痪。
它将值存储在数组中:[72] [101] [108] [111]
让,最初指向数组的单元格1的指针:
>
向右移动指针1
<
向左移动指针1
+
将单元格的值增加1
-
将element的值增加1
.
当前单元格的打印值。
,
输入当前单元格。
[ ]
循环,+++ [-]的3个计数器对bcz进行计数,它前面有3个'+',并且-将count变量减1。
存储在单元格中的值是ascii值:
所以参考上面的数组:[72] [101] [108] [108] [111]如果您匹配ascii值,您会发现它是Hello写法
恭喜!您已经了解了BF的语法
--- 更多的东西 ---
让我们编写第一个程序,即Hello World,之后您就可以用这种语言写下您的名字了。
+++++ +++++[> +++++ ++ >+++++ +++++ >+++ >+ <<<-]>++.>+.+++++ ++..+++.++.+++++ +++++ +++++.>.+++.----- -.----- ---.>+.>.
分为几部分:
+++++ +++++[> +++++ ++
>+++++ +++++
>+++
>+
<<<-]
制作一个由4个单元格组成的数组(数量>),并设置一个10的计数器,例如:— —伪代码—
array =[7,10,3,1]
i=10
while i>0:
element +=element
i-=1
因为计数器值存储在单元格0中,并且>移至单元格1将其值更新+7>移至单元格2将其先前值递增10,依此类推。
<<<
返回单元格0并将其值减1
因此,在循环完成之后,我们得到了数组:[70,100,30,10]
>++.
移动到第一个元素并将其值增加2(两个'+'),然后使用该ascii值打印('。')字符。例如在python中:chr(70 + 2)#打印'H'
>+.
移动到第二个单元格增量1到其值100 + 1并打印('。')其值,即chr(101)chr(101)#打印'e'现在下一个中没有>或<因此它取现值的最新元素并仅递增
+++++ ++..
最新元素= 101,因此101 + 7并打印两次(因为有两个'..')chr(108)#prints l可以用作两次
for i in array:
for j in range(i.count(‘.’)):
print_value
——— 它在哪里使用?——-
它只是一种挑战程序员的玩笑语言,几乎没有在任何地方使用。
所有答案都是彻底的,但它们缺少一个微小的细节:打印。在构建您的Brainfuck转换器时,您还要考虑字符.
,这实际上是Brainfuck中的打印语句。因此,您的大脑翻译器应该做的是,每遇到一个.
字符,便打印当前指向的字节。
例:
假设您有-> char *ptr = [0] [0] [0] [97] [0]
...如果这是一个让人难以理解的声明: >>>.
您的指针应向右移3个空格:[97]
,所以现在*ptr = 97
,在您的翻译器遇到a之后.
,它应该调用
write(1, ptr, 1)
或任何等效的打印语句以打印当前指向的字节,其值为97,a
然后将字母打印在上std_output
。