基于 Ken Thompson & Dennis Ritchie 原始设计,Brian Kernighan 经典教程现代化重构
目录
-
B 语言历史背景(#1-b语言历史背景)
-
快速入门:第一个 B 程序(#2-快速入门第一个b程序)
-
变量与数据类型(#3-变量与数据类型)
-
运算符与表达式(#4-运算符与表达式)
-
函数定义与调用(#5-函数定义与调用)
-
控制流语句(#6-控制流语句)
-
向量(数组)与指针(#7-向量数组与指针)
-
字符串处理(#8-字符串处理)
-
输入输出系统(#9-输入输出系统)
-
B 语言设计理念(#10-b语言设计理念)
-
B 语言 vs C 语言对比(#11-b语言-vs-c语言对比)
-
附录:完整参考(#12-附录)
1. B 语言历史背景
1.1 B 语言是什么?
B 语言是 1969 年由Ken Thompson 在贝尔实验室设计、Dennis Ritchie后续改进的一种极简主义系统编程语言。它是 C 语言的直接前身,也是 UNIX 操作系统最初的开发语言。
B 语言的名字来源于:
-
BCPL(Basic Combined Programming Language)------ Martin Richards 设计的先驱语言
-
取 BCPL 的首字母 B,寓意 "简化版 BCPL"
1.2 发明者与时间线
| 年份 | 事件 | 人物 |
|---|---|---|
| 1966 | BCPL 语言诞生 | Martin Richards |
| 1969 | B 语言初版设计与实现 | Ken Thompson |
| 1970 | B 语言用于最早的 UNIX 开发 | Ken Thompson, Dennis Ritchie |
| 1971 | B 语言改进与完善 | Steve Johnson |
| 1972 | C 语言从 B 语言演化诞生 | Dennis Ritchie |
| 1973 | UNIX 内核用 C 语言重写 | Ken Thompson, Dennis Ritchie |
1.3 B 语言与 C 语言的传承关系
BCPL (1966)
↓
B语言 (1969) — Ken Thompson
↓
NB语言 (1971) — "New B",Dennis Ritchie的过渡版本
↓
C语言 (1972) — Dennis Ritchie
核心演进脉络:
-
B 语言:无类型、字导向、36 位主机(GE-635/Honeywell 6070)
-
NB 语言:尝试加入类型系统
-
C 语言:完整类型系统、字节寻址、PDP-11 架构优化
1.4 为什么学习 B 语言?
-
理解 C 语言的根源 --- 理解 C 为什么是现在这个样子
-
系统编程思想起源 --- UNIX 哲学的语言载体
-
极简主义设计美学 --- 最纯粹的表达式导向语言
-
历史价值 --- 计算机语言演化史的关键节点
2. 快速入门:第一个 B 程序
2.1 程序基本结构
/* 这是注释,和C语言一样 */
main() {
auto a, b, sum; /* 声明局部变量 */
a = 1;
b = 2;
sum = a + b;
putnumb(sum); /* 输出数字 */
putchar('*n'); /* 输出换行 */
}
输出结果: 3
2.2 结构要点
-
必须有 **
main()函数** --- 程序入口 -
auto** 声明局部变量** --- 所有声明必须在可执行语句之前 -
{ }** 包裹函数体** --- 复合语句 -
语句以 **
;结束** -
自由格式 --- 空格、换行不影响语法
2.3 编译运行(历史环境)
# 1. 创建源文件
SYSTEM? filsys cf bsource,b/1,100/
# 2. 创建可执行文件空间
SYSTEM? filsys cf bhs,b/6,6/,m/r/
# 3. 编译
SYSTEM? ./bj (w) bsource bhs
# 4. 运行
SYSTEM? /bhs
3. 变量与数据类型
3.1 核心特性:无类型语言
B 语言是无类型的! 所有数据都是 36 位机器字。
-
没有
int、char、float等类型关键字 -
所有变量都是 36 位有符号整数
-
字符、指针、数字统一用 36 位表示
-
没有类型检查 --- 完全由程序员负责
3.2 变量声明
局部变量(auto):
main() {
auto x, y, z; /* 声明多个变量 */
auto count; /* 单个变量 */
/* 可执行语句从这里开始 */
}
外部变量(extrn,类似 C 的 static/global):
main() {
extrn message; /* 声明使用外部变量 */
putstr(message);
}
message "Hello, B!*n"; /* 外部变量定义与初始化 */
3.3 变量命名规则
-
长度:1-8 个字符
-
允许字符:
A-Z,a-z,0-9,.,_ -
首字符不能是数字
-
建议: 前 6 个字符唯一(链接器限制)
3.4 常量
十进制数字:
a = 42;
b = -123;
八进制数字(以 0 开头):
mask = 0777; /* 十进制 511 */
all_bits = 0777777777777; /* 全1 */
字符常量(单引号,1-4 个字符):
c = 'A'; /* 单个字符 */
s = 'abc'; /* 3个字符,右对齐 */
s = 'hi!'; /* 3个字符 */
转义序列:
'*n' /* 换行 newline */
'*t' /* 制表符 tab */
'*e' /* 字符串结束符 EOT */
'*"' /* 双引号 */
'*'' /* 单引号 */
'**' /* 星号本身 */
4. 运算符与表达式
4.1 算术运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
+ |
加法 | a + b |
- |
减法 / 负号 | a - b, -x |
* |
乘法 | a * b |
/ |
整数除法 | a / b |
% |
取余 | a % b |
4.2 关系运算符
| 运算符 | 含义 | C 语言等价 |
|---|---|---|
== |
等于 | == |
!= |
不等于 | != |
> |
大于 | > |
< |
小于 | < |
>= |
大于等于 | >= |
<= |
小于等于 | <= |
4.3 位运算符
| 运算符 | 含义 | 示例 | ||
|---|---|---|---|---|
& |
按位与 | x & 077 |
||
| ` | ` | 按位或 | `x | mask` |
^ |
按位异或 | x ^ y |
||
~ |
按位取反 | ~x |
||
<< |
左移 | x << 3 |
||
>> |
右移 | x >> 2 |
4.4 自增自减运算符(B 语言首创!)
++k; /* 前缀自增:先加1,后使用 */
k++; /* 后缀自增:先使用,后加1 */
--k; /* 前缀自减 */
k--; /* 后缀自减 */
经典示例:
/* 统计输入字符数 */
count = 0;
while (getchar() != '*n')
++count;
4.5 赋值运算符(B 语言特色)
注意空格!这是 B 语言最容易踩坑的地方!
x =+ 10; /* x = x + 10 (注意空格)*/
x =- 5; /* x = x - 5 */
x =* 2; /* x = x * 2 */
x =/ 3; /* x = x / 3 */
x =% 7; /* x = x % 7 */
x =& mask; /* x = x & mask */
x =| flag; /* x = x | flag */
⚠️ 陷阱警告:
x =-10; /* ❌ 这是 x = -10,赋值-10 */
x =- 10; /* ✅ 这是 x = x - 10,减10 */
4.6 条件表达式(三元运算符)
/* 求最小值 */
min = a < b ? a : b;
/* 求绝对值 */
abs = x >= 0 ? x : -x;
4.7 运算符优先级(从高到低)
1. () []
2. ! ~ ++ -- * & - (一元)
3. * / %
4. + -
5. << >>
6. < <= > >=
7. == !=
8. &
9. ^
10. |
11. ?:
12. = =+ =- =* =/ =% =& =| =^ =<< =>>
5. 函数定义与调用
5.1 函数定义语法
函数名(参数1, 参数2, ...) {
auto 局部变量;
语句...
}
示例:两数相加
add(x, y) {
return(x + y);
}
main() {
auto result;
result = add(3, 5);
putnumb(result); /* 输出 8 */
}
5.2 return 语句
return; /* 无返回值 */
return(表达式); /* 返回表达式的值 */
极简函数写法:
/* 可以省略 {} */
max(a, b) return(a > b ? a : b);
abs(x) return(x >= 0 ? x : -x);
5.3 参数传递:传值调用
B 语言采用传值调用,函数内修改参数不影响外部!
错误示例(无效交换):
/* ❌ 这样写无效! */
swap_wrong(x, y) {
auto t;
t = x;
x = y;
y = t;
}
正确示例(传地址):
/* ✅ 使用地址运算符 & */
swap(x, y) {
auto t;
t = *x;
*x = *y;
*y = t;
}
main() {
auto a, b;
a = 1; b = 2;
swap(&a, &b); /* 传地址 */
/* 现在 a=2, b=1 */
}
5.4 递归函数
经典示例:递归打印数字
putnumb(n) {
auto a;
if (a = n / 10) /* 先打印高位 */
putnumb(a); /* 递归调用 */
putchar(n % 10 + '0');/* 打印当前位 */
}
执行流程 ** putnumb(123):**
putnumb(123)
→ a = 12, 调用 putnumb(12)
→ a = 1, 调用 putnumb(1)
→ a = 0, 不递归
→ putchar('1')
→ putchar('2')
→ putchar('3')
输出: 123
6. 控制流语句
6.1 if 语句
基础形式:
if (条件)
语句;
if-else 形式:
if (a < b)
min = a;
else
min = b;
复合语句:
if (a < b) {
t = a;
a = b;
b = t;
}
else if 链式:
if (x < 0)
sign = -1;
else if (x == 0)
sign = 0;
else
sign = 1;
6.2 while 循环
while (条件)
语句;
示例 1:复制一行输入
main() {
auto c;
while ((c = getchar()) != '*n')
putchar(c);
}
示例 2:无限循环
while (1) {
/* 永远执行 */
if (应该退出)
break;
}
空语句循环:
/* 跳过所有空格 */
while ((c = getchar()) == ' ')
; /* 空语句,什么也不做 */
6.3 goto 和标签
loop:
c = getchar();
putchar(c);
if (c != '*n')
goto loop;
💡 设计哲学: B 语言提供 goto,但鼓励使用结构化语句。现代风格尽量避免 goto。
6.4 switch 多分支
switch (表达式) {
case 常量1:
语句;
break;
case 常量2:
语句;
break;
default:
语句;
}
示例:简单命令解析器
main() {
auto c;
loop:
switch (c = getchar()) {
case 'q':
case 'Q':
goto end; /* 退出 */
case 'p':
case 'P':
print();
goto loop;
case 'h':
case 'H':
help();
goto loop;
case '*n':
goto loop; /* 忽略空行 */
default:
putstr("Unknown command*n");
goto loop;
}
end:;
}
6.5 break 语句
-
跳出
switch -
跳出
while循环
7. 向量(数组)与指针
7.1 向量声明
main() {
auto v[10]; /* 分配 11 个元素:v[0] 到 v[10] */
v[0] = 1;
v[1] = 2;
}
⚠️ 注意: v[N] 分配 N+1 个元素!索引从 0 到 N。
7.2 向量初始化(外部向量)
main() {
extrn digits;
}
digits[9] '0', '1', '2', '3', '4', '5', '6', '7', '8', '9';
7.3 向量求和示例
/* 计算向量元素和 */
sum_vector(v, n) {
auto sum, i;
sum = 0;
i = 0;
while (i <= n)
sum =+ v[i++];
return(sum);
}
7.4 指针本质
向量名就是指针!
v指向v[0]的地址
等价关系:
v[0] 等价于 *v
v[i] 等价于 *(v + i)
&v[0] 等价于 v
地址运算符:
-
&x--- 获取变量 x 的地址 -
*p--- 指针 p 指向的内容
7.5 指针陷阱!
⚠️ 重要警告:向量赋值是指针赋值,不是拷贝!
main() {
auto u[10], v[10];
v[0] = 100;
v[1] = 200;
u = v; /* ❌ u 现在指向 v 的数据!*/
/* u 原来的内存丢失了!*/
v[0] = 999; /* 修改 v[0] */
putnumb(u[0]); /* 输出 999,不是 100!*/
}
8. 字符串处理
8.1 字符串字面量
"Hello, World!"
内部表示:
-
双引号包裹
-
4 个字符打包成一个 36 位字
-
左对齐(与字符常量的右对齐不同)
-
以
*e(ASCII EOT, 0x04) 结尾
8.2 字符串 vs 字符常量
| 特性 | 字符串 "abc" |
字符常量 'abc' |
|---|---|---|
| 引号 | 双引号 | 单引号 |
| 对齐 | 左对齐 | 右对齐 |
| 结尾 | *e 结束符 |
无,0 填充 |
| 长度 | 任意 | 1-4 字符 |
8.3 字符串初始化
main() {
extrn greeting, prompt;
putstr(greeting);
}
greeting "Welcome to B Language!*n";
prompt "Enter command: ";
8.4 字符串库函数
char(s, n)** --- 获取第 n 个字符:**
c = char("hello", 1); /* c = 'e' */
lchar(s, n, c)** --- 设置第 n 个字符:**
lchar(s, 0, 'H'); /* 将 s[0] 设为 'H' */
getstr(s)** --- 读取一行输入:**
auto buf[20];
getstr(buf); /* 读取一行到 buf */
putstr(s)** --- 输出字符串:**
putstr("Hello*n"); /* 输出字符串 */
concat(dest, s1, s2, ...)** --- 字符串连接:**
auto result[20];
concat(result, "Hello", ", ", "World!");
/* result = "Hello, World!" */
8.5 字符串复制函数
strcopy(dest, src) {
auto i;
i = 0;
while (lchar(dest, i, char(src, i)) != '*e')
i++;
}
9. 输入输出系统
9.1 基础 I/O 函数
字符输入输出:
c = getchar(); /* 读一个字符 */
putchar(c); /* 写一个字符 */
数字输出:
putnumb(42); /* 输出数字 42 */
字符串 I/O:
getstr(buffer); /* 读一行 */
putstr("Hello*n"); /* 写字符串 */
9.2 格式化输出 printf
printf(format, arg1, arg2, ...);
格式说明符:
-
%c--- 字符 -
%d--- 十进制数字 -
%o--- 八进制数字 -
%s--- 字符串
示例:
printf("Name: %s, Age: %d*n", "Alice", 25);
/* 输出: Name: Alice, Age: 25 */
printf("%d + %o = %d*n", 1, -1, 0);
/* 输出: 1 + 777777777777 = 0 */
9.3 文件 I/O
打开文件读:
openr(5, "data/file"); /* 单元5打开文件读 */
/* 之后 getchar() 从文件读取 */
打开文件写:
openw(6, "output.txt"); /* 单元6打开文件写 */
/* 之后 putchar() 写入文件 */
强制输出:
putstr("Continue? [y/n] ");
flush(); /* 强制输出,不换行 */
getstr(answer);
9.4 获取命令行参数
main() {
auto cmdline[10];
reread(); /* 回溯到命令行 */
getstr(cmdline); /* 读取命令行参数 */
putstr("Command: ");
putstr(cmdline);
}
9.5 调用系统命令
system("./rj (w) compile;run");
/* 执行 TSS 命令 */
10. B 语言设计理念
10.1 极简主义核心原则
1. 表达式优先
一切皆表达式。语句也是有值的表达式。
/* 赋值有值,可以嵌套 */
while ((c = getchar()) != '*n')
/* 函数调用是表达式 */
putchar(getchar())
2. 无类型的自由
类型是程序员的心智负担,不是机器的。
-
统一 36 位字表示
-
没有隐式转换
-
没有类型检查
-
完全信任程序员
3. 正交的运算符设计
-
++--独立发明,后来被 C 语言继承 -
赋值运算符
=+=-等(C 语言改成+=-=避免歧义) -
三元运算符
?:首创
4. 结构化控制流
-
ifelsewhileswitch -
复合语句
{ } -
提供
goto但不鼓励滥用
5. 直接映射硬件
-
位运算符原生支持
-
指针就是机器地址
-
字节 / 字操作直接对应硬件
10.2 B 语言的历史局限性
-
无类型系统 --- 大型程序难以维护
-
字导向 --- 不适合字节寻址的 PDP-11
-
36 位绑定 --- 移植性差
-
没有结构体 --- 复杂数据建模困难
-
运算符歧义 ---
x=-1的坑
→ 这些问题催生了 C 语言!
11. B 语言 vs C 语言对比
11.1 核心语法对比表
| 特性 | B 语言 | C 语言 |
|---|---|---|
| 类型系统 | 无类型,统一 36 位字 | 强类型,int/char/... |
| 变量声明 | auto x, y; |
int x, y; |
| 自增运算符 | ++x x++ |
++x x++ (继承) |
| 赋值运算符 | x =+ 1 |
x += 1 (修正歧义) |
| 注释 | /* */ |
/* */ (继承) |
| 字符串结尾 | *e (EOT) |
'\0' (NUL) |
| 换行符 | '*n' |
'\n' |
| 函数定义 | f(a,b) { } |
int f(int a, int b) { } |
| 数组大小 | a[N] 有 N+1 元素 |
a[N] 有 N 元素 |
| I/O 函数 | getchar/putchar |
继承并扩展 |
| printf | %d %o %c %s |
继承并扩展 |
11.2 演进的关键改进
C 语言解决的 B 语言问题:
-
✅ 类型系统 ---
char,int,long,float,double -
✅ 结构体 ---
struct支持复杂数据 -
✅ 字节寻址 --- 完美适配 PDP-11
-
✅ 运算符修正 ---
+=替代=+消除歧义 -
✅ 标准库 --- 更完整的运行时库
11.3 代码对比示例
B 语言版本:
main() {
auto c;
while ((c = getchar()) != '*n')
putchar(c);
}
C 语言版本:
int main() {
int c;
while ((c = getchar()) != '\n')
putchar(c);
return 0;
}惊人的相似!90% 的语法直接继承。
12. 附录
12.1 编译器错误码
| 错误码 | 含义 |
|---|---|
$) |
{ } 不匹配 |
() |
( ) 不匹配 |
*/ |
/* */ 注释不匹配 |
[] |
[ ] 不匹配 |
ex |
表达式语法错误 |
lv |
需要左值(地址) |
rd |
重复声明 |
sx |
语句语法错误 |
un |
未定义符号 |
12.2 完整转义序列表
| 序列 | 含义 | ASCII |
|---|---|---|
*0 |
空字符 | NUL (0) |
*e |
字符串结束 | EOT (4) |
*t |
制表符 | TAB (9) |
*n |
换行 | LF (10) |
*' |
单引号 | ' (39) |
*" |
双引号 | " (34) |
** |
星号 | * (42) |
*( |
左括号 | ( (40) |
*) |
右括号 | ) (41) |
12.3 标准库函数速查
I/O 函数:
-
getchar()- 读字符 -
putchar(c)- 写字符 -
getstr(s)- 读字符串 -
putstr(s)- 写字符串 -
putnumb(n)- 写数字 -
printf(fmt, ...)- 格式化输出 -
openr(u, name)- 打开读 -
openw(u, name)- 打开写 -
flush()- 刷新输出 -
reread()- 重读输入
字符串函数:
-
char(s, n)- 取字符 -
lchar(s, n, c)- 设字符 -
concat(d, s1, s2, ...)- 连接
系统函数:
-
exit()- 退出程序 -
system(cmd)- 执行系统命令 -
callf(name, ...)- 调用 Fortran
结语
B 语言虽然已经成为历史,但它的基因在 C 语言、C++、Java、JavaScript 等几乎所有现代编程语言中延续。理解 B 语言,就是理解整个编程语言演进链条中承前启后的关键一环 ------ 从 BCPL 到 B,再到 C,最终塑造了我们今天的软件世界。
"C is B with types." --- Dennis Ritchie
参考文献:
-
S. C. Johnson, A User's Reference to B on MH-TSS, Bell Laboratories
-
B. W. Kernighan, A Tutorial Introduction to the Language B, Bell Laboratories, 1973
-
D. M. Ritchie, The Development of the C Language, ACM HOPL-II, 1993