Brainfuck,文如其名,为“烧脑”而生,简称BF。
BF由Urban Müller于1993年创建,是满足图灵完备性(Turing complete)的一种极小化的计算机语言。
小到什么程度?
BF一共只有8种符号 <>+-.,[] ,编译器也只有区区200多个字节。
用BF写的程序那叫一个不忍直视,好不烧脑!
不信?猜猜下面这行BF代码是干啥用的!
>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++…+++.[-]>++++++++[<++++>-]<.>+++++++++++[<+++++>-]<.>++++++[<++++>-]<.+++.------.--------.[-]>++++++++[<++++>-]<+.[-]++++++++++.
结果是输出下面这几个再熟悉不过的字符:
怎么突然感觉这世界有点陌生了?Oh, f**k!
指令符号
BF只有8种操作符号,对应8种指令:
字符 | 含义 | C语言描述 |
---|---|---|
> | 指针前进一位 | ++ptr; |
< | 指针后退一位 | --ptr; |
+ | 指针指向的字节值加一 | ++*ptr; |
- | 指针指向的字节值减一 | --*ptr; |
. | 输出指针所指向的字节值 | putchar(*ptr); |
, | 输入一字节值并存储到指针所指向的字节中 | *ptr = getchar(); |
[ | 如果指针的值为0,则跳过匹配的]继续向前执行 | while(*ptr){ |
] | 跳到前面匹配的[执行,直到指针的值为0 | } |
下面就是200多个字节的BF官方编译器代码(稍作修改),编译之后就可以运行“烧脑”的BF程序了~
#include <stdio.h>
#include <stdlib.h>int p, r, q;
char a[5000], f[5000], b, o, *s=f;void interpret(char *c)
{char *d;int tmp;r++;while(*c){switch(o=1,*c++){case '<': p--;break;case '>':p++;break;case '+':a[p]++;break;case '-':a[p]--;break;case '.':putchar(a[p]);fflush(stdout);break;case ',':tmp = getchar();if(tmp==EOF)a[p]=0;elsea[p]=tmp;break;case '[':for(b=1,d=c; b && *c; c++){if(*c=='[')b += 1;else if(*c==']')b -= 1;}if(b==0){*(c-1)=0;while(a[p])interpret(d);*(c-1)=']';break;}case ']':puts("UNBALANCED BRACKETS");exit(0);default:o=0;}if(p<0||p>100){puts("RANGE ERROR");exit(0);}}r--;
}int main(int argc, char* argv[])
{FILE* z;q=argc;if((z=fopen(argv[1], "r"))){while((b=getc(z))>0){*s++ = b;}*s=0;interpret(f);}return 0;
}
简单例子
1. 将指针当前位置的值清零:
[-]
2. 将当前指针以及之前位置的值都清零:
[[-]<]
3. 从键盘读取一个ASCII字符并输出到屏幕上:
,.
一位数的加减乘除
1. 加法
BF代码:
,>++++++[<-------->-],[<+>-]<.,.
说明:
BF指针每次只读写一个字节,即一个ASCII字符,当用户输入“a+b”(a,b均为个位数),BF将在字节中存入字符对应的ASCII值,所以这个字节中存储的值并不等于该数字,而是比数字大48,因为“0”的ASCII值是48,所以a+b要减去一个48才能得到正确的ASCII字符。
比如:“3”的ASCII值为51,“5”的ASCII值为53,51-48+53=3+53=56,这正是“8”的ASCII值。而如果直接把“3”和“5”的ASCII值相加,则得51+53=104,对应的ASCII字符是“h”。
你可以在BF代码里面写48个减号,但这样就太不雅观了而且很容易出错,考虑48=6x8,所以可以先定义一个字节c=6,然后用一个while循环,每次c减1, a减8,这样当c减为0时a刚好减了48,代码就是:>++++++[<-------->-],这时a的字节存储的值才等于真正的数字a,而b字节存储的仍是数字b的ASCII符号的值,这时把a,b两个字节的值相加,就能够得到数字a+b的正确ASCII字符:[<+>-]<.
最后的,.是读取换行符然后在打印结果后输出一个换行。
【测试】
2. 减法
BF代码:
,>,>++++++[<-------->-]<[<->-]<.,.
【测试】
3. 乘法
BF代码:
,>,>++++++++[<------<------>>-]<<[>[>+>+<<-]>>[<<+>>-]<<<-]>>>++++++++[<++++++>-]<.,.
【测试】
4. 除法
先写一个整除的BF程序:
,>,>++++++++[<------<------>>-]<<[>[<->->+<]>[<+>-]>+<<<]>>>>++++++++[<++++++>-]<.,.
【测试】
但是遇到不能整除的情况,结果就不对了。
因为BF的除法是用减法来实现的,比如计算a/b,不断地 --b, --a,当b为0的时候再复位b,并将一个计数位c加一,然后再次运行 --b, --a,这里有两个while循环,外循环的终止条件是a=0,内循环的终止条件是b=0,如果a,b同时为0(b整除a),此时内外循环同时结束,读取计数位c的值就是商。但是如果b不整除a,由于内循环只判断b是否为0而不判断a是否为0,所以会导致a为负数因而外循环不会正常结束或者出错,我们需要在内循环中每一步也要判断a是否为0,而BF语言并没有if-else这样的条件分支语句,这就是处理非整除情况的难点。
经过一番苦思冥想,我终于想到了一个办法。
先在输入开头预留5个字节(01000),考虑在内循环再嵌入一个微循环,该循环在做完 --a之后判断a是否为0,如果a不为0则进入微循环[>>>>>]指针右移5个字节至一个空字节,微循环结束,接着执行<<<<左移4个字节,整体效果就相当于右移了一个字节到b,为什么要这么折腾呢?因为当a=0时指针不会进入微循环,而是直接执行<<<<左移4个字节,这时候就定位到了开头预留的第二个字节1,而指针把这个位置的值当作b,减一之后变成b=0了,内循环结束,指针把第一个字节当作a,读入a=0,所以外循环也结束,然后定位到计数位c取值即可。
这个微循环就相当于goto语句,当a=0时就跳转到预留的位置,相当于提前结束了循环,当b整除a的时候,计算结果c比正确值少1,所以程序在读入用户输入的a值后先把a加1。
以下为修改后的除法程序:
>+>>>>,+>,>++++++++[<------<------>>-]<<[>[<-[>>>>>]<<<<->+<]>[<+>-]>+<<<]>>>>>>>>>++++++++[<++++++>-]<.,.
【测试】
整除:
非整除:
【结论】
一般来说,只要有分支判断语句和类似于数组的结构,这门语言就应该具备图灵完备性,BF语言就是一个很好的例证。
本文抛砖引玉,更多有趣的性质还有待大家一起研究。