今天,我总结了一些 C++ 程序常见的错误,大家尽量避免.
1. 运算顺序错误
我们来看一个,判断奇偶数的程序:
#include <stdio.h>
int main(int argc, char** argv) {
int n = 0;
printf("Input a natural number:\n");
while (1 == scanf("%d", &n)) {
if (1 & n == 0) { // Notice here!
printf("Even\n");
} else {
printf("Odd\n");
}
}
return scanf("%*s%*s");
}
编译没有错,但运行得到错误的结果;
只有输入 0 的时候,才会输出 "Even",其他情况都输出 "Odd".
我们注意到,== 属于关系运算,而 & 是位运算,前者优先于后者.
"1 & n == 0" 其实相当于 "1 & (n == 0)",它等价于 "n == 0";
所以,我们应该将 if 中的条件,改为 "(1 & n) == 0",或者 "!(1 & n)".
一些严格检查的 IDE,会在这样的地方给出 warning,建议我们加圆括号.
2. 下标越界错误
在使用数组时,我们可能使用了错误的下标,导致非法访问内存.
下面这个案例,编译不会报错,运行也没有抛出异常,但存在问题:
#include <stdio.h>
#include <time.h>
#include <random>
#define LEN 10
int main(int argc, char** argv) {
srand(time(NULL));
int* p = new int[LEN];
int i = 0;
for (; i < LEN; ++i) {
p[i] = rand() % 50;
}
while (1 == scanf("%d", &i)) {
// There should be something extra;
printf("p[%d]= %d\n", i, p[i]);
}
delete[] p;
return scanf("%*s");
}
这段程序的本意,是生成 10 个一定范围内的随机数,放在数组中,并通过下标来访问;
但是,输入下标后,并没有检查其合法性,这样可能会越界;
所以,正确的写法,应该在 printf 之前,判断 i 是不是合法的下标.
还有另一种方法,
我们用向量 (vector) 代替数组,用 at 函数来代替下标,向量定义在 <vector> 中,我们需要添加相应的指令;
当下标越界时,我们可以捕获一个叫 out_of_range 的异常,它定义在 <stdexcpt> 头文件中,我们同样需要添加包含指令;
注意,在这些 #include 指令后面,需要加上 "using namespace std;",否则会编译出错.
#include <stdio.h>
#include <random>
#include <stdexcept>
#include <vector>
using namespace std;
#define LEN 10
int main(int argc, char** argv) {
srand(time(NULL));
vector<int> vec;
int i = 0;
for (; i < LEN; ++i) {
vec.push_back(rand() % 50);
}
while (1 == scanf("%d", &i)) {
try {
printf("p[%d]= %d\n", i, vec.at(i));
}
catch(out_of_range e) {
printf("Wrong index!\n");
}
}
return scanf("%*s");
}
3. 未初始化的错误
有时候,我们没有给变量初始化,导致运行出奇特的结果.
#include <stdio.h>
#define LEN 10
int main(int argc, char** argv) {
int* p = new int[LEN];
int i = LEN;
for (; --i; p[i] = i); // Wrong code;
for (i = 0; i < LEN; ++i) {
printf("%d, ", p[i]);
}
delete[] p;
return scanf("%*s");
}
我们看第一个 for 循环,它的条件是 --i,相当于 --i != 0;
当 i = 0 时,循环结束; 我们只对 p[1] 到 p[9] 进行了初始化,而没有对 p[0] 初始化;
因此,这段程序输出的结果,第一个数一般都很奇怪.
正确的做法是,把 --i 改为 --i >= 0.
当然,我们也可以,把 int* p = new int[LEN] 改为 int p[LEN] = {};
然后去掉后面的 delete[] p;
定义数组时,如果使用 new 运算,属于动态分配内存,使用完毕后,需要用 delete[] 来释放内存;这种情况下,数组元素的值,可能没有被初始化.
而如果使用大括号,对数组进行初始化,属于静态分配内存,不用再写 delete.
当大括号内容为空时,数值型的元素都初始化为 0,其他类型的元素为默认初始值.
当然,我们可以在大括号里,直接初始化,例如:
int ar[6] = {
1, 5, 2
};
对于前 3 个元素,我们直接定义了初始值,对于后面的元素,会被设为 0.
以上写法相当于:
int ar[6] = {
1, 5, 2, 0, 0, 0
};
4. 类型转换错误
我们看一个例子:
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char** argv) {
string str = "AB";
int num = 6;
str = (num + '0') + str;
// Error here!
printf("%s\n", str.c_str());
num = 3;
str += (num + '0');
cout << str << endl;
return scanf("%*s");
}
在 str = (num + '0') + str 这里,编译出错,这涉及到类型转换的问题;
因为 num 是整型, '0' 是字符型,两者同时存在于一个表达式,则将后者也转为整型,
故 (num + '0') 结果是 int 型,它与 string 型之间不支持 + 运算符,于是报错.
正确的写法,是显式转换类型:
str = (char)(num + '0') + str;
后面的 str += (num + '0') ,不需要写 (char),编译和运行都正常,
这是因为,string 的 += 运算符,会把后面的 int 理解为字符的 ASCII 码.
类型转换分为两种,显式转换,隐式转换.
显式转换,就是在需要转换的变量前,加一对圆括号,括号内写出类型,例如:
int n = 10;
float a = (float)n;
我们对 a 赋值的时候,使用了 int 型变量 n 的值,此时需要将其转换为 float 型.
隐式转换,则是什么都不加,让编译器去自动转换,例如:
int n = 10;
float a = n;
这种情况下,编译器会自动将第 2 行的 n 转换为 float 型.
不过,我建议大家用显式转换,不推荐隐式转换,因为后者容易出错.
今天,我就介绍到这里,以后继续更新. ^_^