**自然语言**是人类讲话使用的语言,如英语、西班牙语和法语等。虽然人们总要给自然语言加上一些规则,但自然语言并非人类设计,它们是自然演化而来的。
**形式语言**是人们为特定应用设计的语言。例如,数学家使用的记号就是一种便于表示数字与符号关系的形式语言。化学家也使用一种形式语言来表示分子的化学结构。最重要的是:
> **编程语言是人为设计的用来表达计算的形式语言。**
前面也提到过,形式语言有严格的语法规则。比如3+3=6是符合语法的数学语句,而3=+6$则不是。同样*H*2*O*是符合语法的化学式,但2*Zz*不是。
> 这里的两个2都是下标,在Markdown语法中无法表示。下段同。——译者注
语法规则包含两个方面:标识符与结构。标识符是语言的基本元素,像单词、数字以及化学元素等。3=+6$的一个错误是,至少据我所知$不是数学上合法的标识符。类似的,2*Zz*也是非法的,因为没有缩写为*Zz*的化学元素。
第二种语法错误是句子结构上的,即标识符的排列方式。语句3=+6$结构上也是非法的,因为加号不能直接放在等号后面。类似地,化学式中的下标必须在元素名后面,而不能在前面。
阅读英语的句子或者形式语言的语句时,必须分析句子结构(使用自然语言时,你会下意识地这样处理)。这个过程叫做**解析**。
例如,当你听到“The other shoe fell”这句话时,你会知道“the other shoe”是主语而“fell”是动词。分析完句子结构,你就理解了它的意思,即句子的语义。假设你知道“shoe”是什么,也知道“fall”的意义,你就能理解句子的大体含义。
虽然形式语言和自然语言有很多共同点,如标识符、结构、语法和语义,但是它们仍然有很多不同点:
**歧义**:自然语言常有歧义,人们需要根据上下文和其他信息来理解。而形式语言天生就是清晰无二义的,也就是说不管上下文是什么,任何语句都有一个精确的意义。
**冗余**:为了弥补歧义问题并减少误解,自然语言引入了很多冗余,结果就是语言常常很冗长。形式语言冗余少些,更加简洁。
**字面意义**:自然语言有很多成语和隐喻。比如我说“The other shoe fell”,可能不是说鞋,也没有什么东西掉下来。而形式语言语句的含义和字面意义是完全一致的。
说着自然语言长大的我们,通常都要经历一段痛苦的时期才能适应形式语言。从某些方面来说,自然语言和形式语言的差别就像诗歌和散文的差别,而且可能还有过之无不及:
**诗歌**:选词既要求发音,又要求含义,整首诗营造出一种效果或情感响应。歧义不仅常见,很多时候是有意为之。
**散文**:词汇的字面意思更加重要,而且句子结构也更能表意。相对于诗歌,散文更经得起推敲,但仍然会存在歧义。
**程序**:计算机程序的含义是无歧义的,和语句的字面意思一致,通过对标识符和结构的分析可以完整地理解。
关于阅读程序(或其他形式语言)提几点建议:首先,形式语言比自然语言难懂得多,所以读起来会花费更长的时间。其次,结构非常重要,从上到下、从左到右地阅读并不见得管用。相反,要学会在头脑中分析程序,识别标识符并解读清楚句子结构。最后,细节很重要,像拼写或者标点符号错误,在自然语言中有可能无伤大雅,但在形式语言中可能造成天壤之别。
- 第1章 编程之路
- 1.1 什么是编程语言
- 1.2 什么是程序
- 1.3 什么是调试
- 1.4 形式语言与自然语言
- 1.5 第一个程序
- 1.6 术语表
- 第2章 变量和类型
- 2.1 更多的输出
- 2.2 值
- 2.3 变量
- 2.4 赋值
- 2.5 输出变量
- 2.6 关键字
- 2.7 操作符
- 2.8 操作顺序
- 2.9 操作符
- 2.10 组合
- 2.11 术语表
- 第3章 函数
- 3.1 浮点数
- 3.2 double到int的转换
- 3.3 数学函数
- 3.4 函数组合
- 3.5 添加新函数
- 3.6 定义与使用
- 3.7 多函数编程
- 3.8 参数与参数值
- 3.9 参数和变量的局部性
- 3.10 多参函数
- 3.11 有返回值的函数
- 3.12 术语表
- 第4章 条件和递归
- 4.1 取模操作符
- 4.2 条件执行
- 4.3 选择执行
- 4.4 链式条件
- 4.5 嵌套条件
- 4.6 return语句
- 4.7 递归
- 4.8 无穷递归
- 4.9 递归函数的栈图
- 4.10 术语表
- 第5章 有返回值的函数
- 5.1 返回值
- 5.2 程序开发
- 5.3 组合
- 5.4 重载
- 5.5 布尔值
- 5.6 布尔变量
- 5.7 逻辑操作符
- 5.8 布尔函数
- 5.9 从main函数返回
- 5.10 深入递归
- 5.11 思路跳跃
- 5.12 又一个例子
- 5.13 术语表
- 第6章 迭代
- 6.1 多次赋值
- 6.2 迭代
- 6.3 while语句
- 6.4 制表
- 6.5 二维表
- 6.6 封装和泛化
- 6.7 函数
- 6.8 再说封装
- 6.9 局部变量
- 6.10 再说泛化
- 6.11 术语表
- 第7章 字符串那些事儿
- 7.1 字符串的容器
- 7.2 apstring变量
- 7.3 从字符串中提取字符
- 7.4 字符串长度
- 7.5 遍历
- 7.6 一个运行时错误
- 7.7 find函数
- 7.8 我们自己的find版本
- 7.9 循环与计数
- 7.10 增量与减量操作符
- 7.11 字符串连接
- 7.12 apstring是可变的
- 7.13 apstring是可比较的
- 7.14 字符分类
- 7.15 其他apstring函数
- 7.16 术语表
- 第8章 结构体
- 8.1 复合值
- 8.2 Point对象
- 8.3 访问实例变量
- 8.4 对结构体的操作
- 8.5 作为参数的结构
- 8.6 传值调用
- 8.7 传引用调用
- 8.8 矩形
- 8.9 作为返回值的结构
- 8.10 按引用传递其他类型
- 8.11 获取用户输入
- 8.12 术语表
- 第9章 再谈结构体
- 9.1 Time结构体
- 9.2 printTime函数
- 9.3 对象函数
- 9.4 纯函数
- 9.5 const参数
- 9.6 修改函数
- 9.7 填充函数
- 9.8 哪个最佳?
- 9.9 增量开发vs高屋建瓴
- 9.10 泛化
- 9.11 算法
- 9.12 术语表
- 第10章 向量
- 10.1 元素访问
- 10.2 向量的复制
- 10.3 for循环
- 10.4 向量的长度
- 10.5 随机数
- 10.6 统计
- 10.7 随机数的向量
- 10.8 计数
- 10.9 检查其他值
- 10.10直方图
- 10.11一次遍历的方案
- 10.12随机种子
- 10.13术语表
- 第11章 成员函数
- 11.1 对象和函数
- 11.2 print
- 11.3 隐式变量访问
- 11.4 另一个例子
- 11.5 再一个例子
- 11.6 更复杂的例子
- 11.8 初始化还是构造?
- 11.7 构造函数
- 11.9 最后一个例子
- 11.10 头文件
- 11.11 术语表
- 第12章 对象的向量
- 12.1 组合
- 12.2 纸牌对象(Card)
- 12.3 printCard函数
- 12.4 equals函数
- 12.5 isGreater函数
- 12.6 纸牌的向量
- 12.7 printDeck函数
- 12.8 查找
- 12.9 二分查找
- 12.10 牌堆与子牌堆
- 12.11 术语表
- 第13章 基于向量的对象
- 13.1 枚举类型
- 13.2 switch语句
- 13.3 牌堆
- 13.4 另一个构造函数
- 13.5 Deck成员函数
- 13.6 洗牌
- 13.7 排序
- 13.8 子牌堆
- 13.9 洗牌与发牌
- 13.10 归并排序
- 13.11 术语表
- 第14章 类与不变式
- 14.1 私有数据和私有类
- 14.2 什么是类?
- 14.3 复数
- 14.4 访问函数(Accessor functions)
- 14.5 输出
- 14.6 复数相关函数(一)
- 14.7 复数相关函数(二)
- 14.8 不变式
- 14.9 先决条件
- 14.10 私有函数
- 14.11 术语表
- 第15章 文件输入/输出与apmatrix类
- 15.1 流
- 15.2 文件输入
- 15.3 文件输出
- 15.4 解析输入
- 15.5 解析数字
- 15.6 集合数据结构Set
- 15.7 apmatrix类
- 15.8 距离矩阵
- 15.9 一个更合理的距离矩阵
- 15.10 术语表