## 1.3 什么是调试
编程本身是一个复杂的过程,并且由人类而不是机器完成,所以经常会发生一些错误。由于一些奇怪的原因,程序中的错误称为bug,而追踪定位bug并且将其修正的过程则称为调试(Debug)。
程序中发生的错误有不同的种类,知道如何分辨不同的错误可以更快速地定位bug的位置。
### 1.3.1 编译时错误
编译器只能编译语法正确的程序,否则会导致编译过程失败,无法运行程序。__语法__指的是程序结构以及与该结构相关的规则。
以英语语法为例,一个句子必须以大写字母开头,句号结尾。诸如“this sentence contains a syntax error.”和“So does this one”这样的两个句子都包含语法错误。
对大多数读者来说,少量语法错误并不是什么大问题。这就是为什么我们可以毫无障碍地阅读E.E.卡明斯的诗歌。
但是编译器并不是如此的宽容。如果你的程序中出现一处语法错误,编译器就会输出错误消息并且退出,而你就无法再运行自己的程序。
更糟糕的是,C++中具有比英语更多的语法规则,并且大多数时候你从编译器得到的错误消息都没有太大帮助。在你刚开始学习编程的时候,你很可能会花费大量的时间查找语法错误。不过随着你经验日益丰富,发生和查找错误需要的时间都会越来越少。
### 1.3.2 运行时错误
第二种错误是运行时错误。将其称为运行时错误,是因为该错误只有在程序运行时才会发生。
接下来的几周我们要写的各种程序中,运行时错误很少发生。所以你可能需要一段时间才会遇到1。
> 注释:1这不是个好事情吗?——译者注
### 1.3.3 逻辑和语义错误
第三种错误是__逻辑__和__语义__错误。如果程序中出现逻辑和语义错误,计算机不会产生任何错误消息,编译和运行过程都会成功。但是程序并没有做它应该做的,而是做了其他的事。只有在极少情况下它才会做你让它做的。
问题在于你写的程序不是你本意想写的程序,程序的意义(语义)有错误。识别逻辑错误是一件很棘手的事情,因为它需要你回头查看程序的输出并且尝试发现哪里出错了。
### 1.3.4 实验调试
在本书的学习过程中,你应当获得的最重要的技能之一就是调试。尽管错误的出现让你沮丧,但是调试是编程过程中最需要脑力、最富有挑战性而且最有趣的部分了。
在某些方面调试就像侦察。你需要面对线索,推断出具体过程和事件,这些过程和事件能够得到你所看到的结果。
同时,调试又像是科学实验。一旦你想出来哪里可能出错了,你就会修改你的程序再次尝试。如果你的假设成立,你可以预测到修改后的结果并离可工作的程序更近一步。如果假设失败了,你需要提出一个新的假设。正如福尔摩斯所说,“当你排除了一切不可能的因素之后,剩下的无论看起来有多么不合理,也一定是事实。”(来自柯南道尔的《四个人的签名》)。
对一些人来说,编程和调试是同一件事情。也就是说,编程的过程就是逐步调试直到程序完成你想要的功能的过程。这种观点表明,任何时候你都应该从一个可以正常运行的程序入手,然后进行小的改动并调试通过,这样你的程序可以一直工作。
比如,Linux操作系统包含成千上万行代码,但是它最开始也只是 Linux Torvalds用于探索英特尔 80386芯片的简单程序。Larry Greenfield说:“Linus早期的工程之一就是一段在输出AAAA和BBBB之间切换的程序。然后进化成了Linux。”(来自Linux用户指导测试版1)。
在稍后的章节里,我会提出关于调试和编程练习的更多建议。
- 译者简介
- 作者简介
- 第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 apstrings的可比较性
- 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章 vector
- 10.1 访问元素
- 10.2 复制vector
- 10.3 for循环
- 10.4 vector的长度
- 10.5 随机数
- 10.6 统计
- 10.7 随机数的vector
- 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.7 结构体
- 11.8 初始化还是构造
- 11.9 最后一个例子
- 11.10 头文件
- 11.11 术语
- 第12章 包含对象的vector
- 12.1 复合形式
- 12.2 Card对象
- 12.3 printCard函数
- 12.4 equals函数
- 12.5 isGreater函数
- 12.6 包含Card对象的vector
- 12.7 printDeck函数
- 12.8 搜索
- 12.9 二分查找
- 12.10 vector和子 vector
- 12.11 术语
- 第13章 向量对象
- 13.1 枚举类型
- 13.2 switch语句
- 13.3 Deck
- 13.4 另一个构造函数
- 13.5 Deck成员函数
- 13.6 洗牌
- 13.7 排序
- 13.8 subdeck
- 13.9 洗牌和处理
- 13.10 合并排序
- 13.11 术语
- 第14章 类和不变式
- 14.1 私有数据和私有类
- 14.2 什么是类
- 14.3 复数
- 14.4 访问器函数
- 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 术语
- 附录A AP类的快速参考
- 版权声明
- 版权