这是篇转载文章,出处:点击打开链接,翻译自点击打开链接 ---- 伯乐在线 — 陈舸
一般情况下,现在的编译器可以帮助程序员寻找到解决编译错误的方法并修正。但是还是存在这些BUG难以跟踪和调试,这些都会导致严重的后果,比如不正确的输出、数据破坏、以及程序崩溃。以下的示例都在VS2005 Express上测试过,(作者同时在vs2010上进行了测试)根据自己选择的编译器,你得到的结果可能会有所不同。我强烈建议所有的程序员朋友都采用最高等级的告警级别!有一些编译提示在默认告警级别下可能不会被标注为一个潜在的问题,而在最高等级的告警级别下就会被捕捉到!
1)变量未初始化
class Foo
{
private:
int m_nValue;
public:
Foo();
int GetValue() { return m_bValue; }
};
Foo::Foo()
{
// Oops, 我们忘记初始化m_nValue了
}
int main()
{
Foo cFoo;
if (cFoo.GetValue() > 0)
// do something
else
// do something else
}
2)整数除法
int nX = 7;
int nY = 2;
float fValue = nX / nY; // fValue = 3(不是3.5哦!)
3)= vs ==
这是个老问题,但很有价值。许多C++新手会弄混赋值符号和相等操作符号的意义。4)混用有符号和无符号数
之前提到,如果操作数是不同类型,其中一个操作数将提升自己的类型以及匹配另一个操作数。当混用有符号和无符号数时则会导致出现一些非预期性的结果!考虑如下的例子:cout << 10 – 15u; // 15u是无符号整数
cout << 10u – 15u;
5)delete vs delete[]
Foo *pScalar = new Foo;
delete pScalar;
Foo *pArray = new Foo[10];
delete[] pArray;
6)复合表达式或函数调用的副作用
副作用是指一个操作符、表达式、语句或函数在该操作符、表达式、语句或函数完成规定的操作后仍然继续做了某些事情。void multiply(int x, int y)
{
using namespace std;
cout << x * y << endl;
}
int main()
{
int x = 5;
std::cout << multiply(x, ++x);
}
if (x == 1 && ++y == 2)
// do something
7)不带break的switch语句
当switch表达式计算出的结果同case的标签值相同时,执行序列将从满足的第一个case语句处执行。执行序列将继续下去,直到要么到达switch语句块的末尾,或者遇到return、goto或break语句。其他的标签都将忽略掉!8)在构造函数中调用虚函数
考虑如下程序:class Base
{
private:
int m_nID;
public:
Base()
{
m_nID = ClassID();
}
// ClassID 返回一个class相关的ID号
virtual int ClassID() { return 1;}
int GetID() { return m_nID; }
};
class Derived: public Base
{
public:
Derived()
{
}
virtual int ClassID() { return 2;}
};
int main()
{
Derived cDerived;
cout << cDerived.GetID(); // 打印出1,不是2!
return 0;
}
在这个程序中,程序员在基类的构造函数中调用了虚函数,期望它能被决议为派生类的Derived::ClassID()。但实际上不会这样——程序的结果是打印出1而不是2。当从基类继承的派生类被实例化时,基类对象先于派生类对象被构造出来。这么做是因为派生类的成员可能会对已经初始化过的基类成员有依赖关系。结果就是当基类的构造函数被执行时,此时派生类对象根本就还没有构造出来!所以,此时任何对虚函数的调用都只会决议为基类的成员函数,而不是派生类。
根据这个例子,当cDerived的基类部分被构造时,其派生类的那一部分还不存在。因此,对函数ClassID的调用将决议为Base::ClassID()(不是Derived::ClassID()),这个函数将m_nID设为1。一旦cDerived的派生类部分也构造好时,在cDerived这个对象上,任何对ClassID()的调用都将如预期的那样决议为Derived::ClassID()。
注意到其他的编程语言如C#和Java会将虚函数调用决议为继承层次最深的那个class上,就算派生类还没有被初始化也是这样!C++的做法与这不同,这是为了程序员的安全而考虑的。这并不是说一种方式就一定好过另一种,这里仅仅是为了表示不同的编程语言在同一问题上可能有不同的表现行为。