在 C++ 程序设计中,有以下 3 类错误:
- 语法错误;
- 运行错误;
- 语义错误;
我们结合具体案例,来讨论这三种错误。
1. 语法错误
语法错误又分为两类: 编译错误 (Compile error),连接错误 (link error).
前者是编译过程中发生的错误,导致无法生成目标文件,
可能的原因有:分号遗漏或多余,括号不匹配,标识符未定义,访问类的 private 成员等。
后者是生成目标文件后,连接过程中发生错误,是针对有多个源文件的情况,
可能的原因有:全局变量重复定义,函数名冲突等。
根据报错的信息,我们修改代码,再次编译和连接,直到没有语法错误为止。
例如,我们有如下代码段:
#include <stdio.h>
class Cat {
// ...
}
int main() {
cat* pCat = new cat;
delete pCat;
return scanf("%*s");
}
编译会提示,未定义标识符 cat,因为,C++ 严格区分大小写,cat 应该写成 Cat。
另一个案例:
#include <stdio.h>
int main() {
int a = 3;
double b = 2:
printf("%d\n", a % b);
return scanf("%*s");
}
取模运算,只适用于整型数据,而我们对浮点数 b 取摸,因此会报错。
有时候,我们遇到的只是 Warning ,可以生成目标文件,但为了保险,我们还是应该修改代码,尽量达到 0 error, 0 warning。
2. 运行错误 (Runtime error)
运行过程中,程序可能出现错误。这些错误属于运行错误,不会在编译过程中显现。
例如,我们有以下代码:
#include <stdio.h>
int main() {
int a = 1;
int b = 1;
scanf("%d %d", &a, &b);
printf("%d\n", a / b);
return scanf("%*s");
}
如果在运行过程中,输入的第 2 个数为0,则会引发除数为0的异常。
我们可以在做除法前,判断除数是否为 0,从而避免这种异常。
或者使用 try-catch 语句,来处理可能发生的异常。
#include <stdio.h>
int main() {
int a = 1;
int b = 1;
scanf("%d %d", &a, &b);
try {
printf("%d\n", a / b);
}
catch (...) {
printf("Runtime error!\n");
scanf("%*s");
return 1;
}
return scanf("%*s");
}
引发运行错误的原因,不仅有除数为0,还有下标越界,栈溢出等。
下面这段代码, 大家不要轻易运行!
#include <stdio.h>
int main() {
int* p = NULL;
while (true) {
p = new int[1024];
}
return scanf("%*s");
}
它可以通过编译,但运行时会无限开辟内存,导致内存耗尽,发生严重错误。
我在 x64 Win10 系统中运行,约 80s 后出现黑屏,系统无响应的情况。
如果增加 try-catch 机制,则可以及时结束这种局面:
#include <stdio.h>
int main(int argc, char** argv) {
int* p = 0;
try {
while (true) {
p = new int[1024];
}
}
catch (...) {
printf("Runtime error!\n");
scanf("%*s");
return 1;
}
return scanf("%*s");
}
3. 语义错误
这种情况,编译没有报错,运行也没有抛出异常,但是输出的结果不正确。
我们看这个案例:
#include <stdio.h>
#define LEN 8
int main() {
int i = 0;
int* ar = new int[LEN];
for (i = 0; i > LEN; ++i) {
if (1 > scanf("%d", ar + i)) {
scanf("%*s");
}
}
while (--i >= 0) {
printf("%d,", ar[i]);
}
delete[] ar;
return scanf("%*s");
}
第 7 行的 i > LEN 应改为 i < LEN,否则程序不会进入循环,不输出任何内容。
我们再看另一个案例:
#include <stdio.h>
class Date {
private:
int year;
int month;
int day;
public:
Date(int y, int m, int d) {
year = y;
m = month;
day = d;
}
// Other functions...
}
int main() {
Date* date = new Date(2001, 6, 10);
// ...
delete date;
return scanf("%*s");
}
在 Date 的构造函数里,m = month 应改为 month = m;
否则将导致 month 成员没有被初始化,运行结果错误。
如果用严格检查的编译软件,例如 VS 2019,它会在编译时给出 warning,说 month未初始化,从而提示我们,这里可能有错误。
不过,有些语义错误,并不引发 warning ,只在运行过程中表现,很难被察觉。
想要避免这些语义错误,我们就必须认真分析代码。
之后我会总结更多案例。^_^