Ответ 1
1. Основы
Чтобы понять 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]...
2. Ввод
Чтобы прочитать символ, вы используете запятую ,
. Что он делает: читайте символ со стандартного ввода и записывайте его десятичный код ASCII в фактическую ячейку.
Посмотрите таблицу ASCII. Например, десятичный код !
равен 33
, а a
- 97
.
Хорошо, представьте себе, что ваша программная память BF выглядит так:
...[0][0][*0*][0][0]...
Предполагая, что стандартный ввод означает a
, если вы используете оператор comma ,
, то, что 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
шестнадцатеричный. Кажется знакомым? Конечно. Это первый байт ł
!
3. Выход
Чтобы напечатать символ, который вы используете dot .
Что он делает: Предполагая, что мы обрабатываем фактическое значение ячейки, как десятичный код ASCII, печатаем соответствующий символ стандартным выводам.
Хорошо, представьте себе, что ваша программная память BF выглядит так:
...[0][0][*97*][0][0]...
Если вы теперь используете оператор dot (.), то что BF делает, это print:
а
Поскольку a
десятичный код в ASCII равен 97
.
Итак, например, программа BF вроде этого (97 плюсов 2 точки):
+++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++..
Увеличит значение ячейки, на которую он указывает до 97, и распечатает его 2 раза.
аа
4. Петли
В цикле BF состоит из цикла begin [
и конца цикла ]
. Вы можете думать об этом как в 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)
Однако это прекрасная практика, с которой мы начали. Зачем? Поскольку если цикл завершает работу другой ячейки, мы не можем предположить, где будет указатель ячейки. Честно говоря, эта практика делает мозговой укол менее мозговым.