B语言经典教程现代化重构

基于 Ken Thompson & Dennis Ritchie 原始设计,Brian Kernighan 经典教程现代化重构


目录

  1. B 语言历史背景(#1-b语言历史背景)

  2. 快速入门:第一个 B 程序(#2-快速入门第一个b程序)

  3. 变量与数据类型(#3-变量与数据类型)

  4. 运算符与表达式(#4-运算符与表达式)

  5. 函数定义与调用(#5-函数定义与调用)

  6. 控制流语句(#6-控制流语句)

  7. 向量(数组)与指针(#7-向量数组与指针)

  8. 字符串处理(#8-字符串处理)

  9. 输入输出系统(#9-输入输出系统)

  10. B 语言设计理念(#10-b语言设计理念)

  11. B 语言 vs C 语言对比(#11-b语言-vs-c语言对比)

  12. 附录:完整参考(#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 语言?

  1. 理解 C 语言的根源 --- 理解 C 为什么是现在这个样子

  2. 系统编程思想起源 --- UNIX 哲学的语言载体

  3. 极简主义设计美学 --- 最纯粹的表达式导向语言

  4. 历史价值 --- 计算机语言演化史的关键节点


2. 快速入门:第一个 B 程序

2.1 程序基本结构

复制代码
/* 这是注释,和C语言一样 */
​
main() {
    auto a, b, sum;    /* 声明局部变量 */
    
    a = 1;
    b = 2;
    sum = a + b;
    
    putnumb(sum);      /* 输出数字 */
    putchar('*n');     /* 输出换行 */
}

输出结果: 3

2.2 结构要点

  1. 必须有 ** main() 函数** --- 程序入口

  2. auto** 声明局部变量** --- 所有声明必须在可执行语句之前

  3. { }** 包裹函数体** --- 复合语句

  4. 语句以 ** ; 结束**

  5. 自由格式 --- 空格、换行不影响语法

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 位机器字。

  • 没有 intcharfloat 等类型关键字

  • 所有变量都是 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. 结构化控制流

  • if else while switch

  • 复合语句 { }

  • 提供 goto 但不鼓励滥用

5. 直接映射硬件

  • 位运算符原生支持

  • 指针就是机器地址

  • 字节 / 字操作直接对应硬件

10.2 B 语言的历史局限性

  1. 无类型系统 --- 大型程序难以维护

  2. 字导向 --- 不适合字节寻址的 PDP-11

  3. 36 位绑定 --- 移植性差

  4. 没有结构体 --- 复杂数据建模困难

  5. 运算符歧义 --- 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 语言问题:

  1. 类型系统 --- char, int, long, float, double

  2. 结构体 --- struct 支持复杂数据

  3. 字节寻址 --- 完美适配 PDP-11

  4. 运算符修正 --- += 替代 =+ 消除歧义

  5. 标准库 --- 更完整的运行时库

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


参考文献:

  1. S. C. Johnson, A User's Reference to B on MH-TSS, Bell Laboratories

  2. B. W. Kernighan, A Tutorial Introduction to the Language B, Bell Laboratories, 1973

  3. D. M. Ritchie, The Development of the C Language, ACM HOPL-II, 1993

相关推荐
凤山老林1 小时前
JDK 11 升级至 JDK 17
java·开发语言·jdk17·jdk升级·jdk11
Cobyte1 小时前
20.Vue Vapor 的应用初始化
前端·javascript·vue.js
指令集梦境1 小时前
图解:单调栈算法模板(Java语言)
java·开发语言·算法
乘风gg1 小时前
手把手带你实践历时一年总结的 AI Code Review 最佳工作流!
前端·ai编程·cursor
禅思院1 小时前
POST请求发两次?一次讲透CORS预检机制,面试不再翻车
前端·架构·前端框架
IT_陈寒1 小时前
SpringBoot自动配置这么智能,为啥我写的Bean注入不了?
前端·人工智能·后端
LT10157974441 小时前
2026年Web自动化测试工具选型指南:多浏览器兼容解决方案
前端·测试工具·自动化
Hello-FPGA1 小时前
Camera Link 与 CoaXPress 技术对比 如何选择你的相机接口
单片机·嵌入式硬件
IronMurphy1 小时前
多线程问!
java·jvm·spring