调试的重要性
软件错误普遍存在。制造它们很容易,发现它们却很难。在本章中,我们将探讨与在我们C++程序中查找和移除错误相关的主题,包括学习如何使用我们集成开发环境(IDE)中包含的集成调试器。
尽管调试工具和技术不属于C++标准的一部分,但学习如何在您编写的程序中查找和移除错误是成为一名成功程序员极其重要的部分。因此,我们将花费一些时间来涵盖这些主题,以便随着您编写的程序变得更加复杂,您诊断和解决问题的能力也以相似的速度提高。
如果您有在其他编程语言中调试程序的经验,这些内容对您来说将非常熟悉。
语法错误
编程可能具有挑战性,而C++是一种有些古怪的语言。将这两者结合起来,就有很多制造错误的方式。错误通常分为两类:语法错误和语义错误(逻辑错误)。
当您编写的语句根据C++语言的语法不合法时,就会发生语法错误。这包括诸如缺少分号、括号或大括号不匹配等错误。例如,以下程序包含相当多的语法错误:
#include <iostream>
int main( // 缺少闭合大括号
{
int 1x; // 变量名不能以数字开头
std::cout << "Hi there"; << x +++ << '\n'; // 多余的分号,不存在的运算符++++
return 0 // 语句末尾缺少分号
}
幸运的是,编译器会检测到语法错误,并发出编译警告或错误,因此您可以轻松地识别和修复问题。然后,您只需要重新编译,直到消除所有错误。
语义错误
语义错误是含义上的错误。当一个语句在语法上有效,但违反了语言的其他规则,或者没有做程序员打算做的事情时,就会发生这种错误。
一些语义错误可以被编译器捕获。常见的例子包括使用未声明的变量、类型不匹配(当我们在某个地方使用错误类型的对象时)等。
例如,以下程序包含几个编译时语义错误:
int main()
{
5 = x; // x未声明,不能给5赋值
return "hello"; // "hello"不能转换为int
}
运行时语义错误
其他语义错误只在运行时表现出来。有时这些错误会导致您的程序崩溃,例如除以零的情况:
#include <iostream>
int main()
{
int a { 10 };
int b { 0 };
std::cout << a << " / " << b << " = " << a / b << '\n'; // 在数学中,除以0是未定义的
return 0;
}
未定义行为错误
更多时候,这些错误只会生成错误的值或行为:
#include <iostream>
int main()
{
int x; // 未提供初始化器
std::cout << x << '\n'; // 使用未初始化的变量会导致未定义的结果
return 0;
}
逻辑错误示例
#include <iostream>
int add(int x, int y) // 这个函数应该执行加法
{
return x - y; // 但由于使用了错误的运算符,它没有做到
}
int main()
{
std::cout << "5 + 3 = " << add(5, 3) << '\n'; // 应该产生8,但产生了2
return 0;
}
代码执行顺序错误
#include <iostream>
int main()
{
return 0; // 函数在这里返回
std::cout << "Hello, world!\n"; // 所以这个永远不会执行
}
在上面的例子中,错误相当容易发现。但在大多数非平凡的程序中,运行时语义错误不是通过查看代码就能轻易找到的。这就是调试技巧可以派上用场的地方。