烧脑语言Brainfuck及其加减乘除

本文介绍了极小化的编程语言Brainfuck,它仅有8种操作符号,包括指针移动、增减、输出等。通过示例展示了如何用Brainfuck进行加减乘除运算,探讨了其图灵完备性。文章还分享了一个简单的Brainfuck编译器代码,以及处理非整除情况的除法运算技巧,鼓励读者探索这种烧脑语言的更多可能性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
				else
					a[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语言就是一个很好的例证。
本文抛砖引玉,更多有趣的性质还有待大家一起研究。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值