C++编译器无法捕捉到的8种错误实例分析(3)
long double (最高)
double
float
unsigned long int
long int
unsigned int
int (最低)
因为int类型比unsigned int要低,因此int要提升为unsigned int。幸运的是,10已经是个正整数了,因此类型提升并没有使解释这个值的方式发生改变。因此,上面的代码相当于:
cout << 10u – 15u;
好,现在是该看看这个小把戏的时候了。因为都是无符号整型,因此操作的结果也应该是一个无符号整型的变量!10u-15u = -5u。但是无符号变量不包括负数,因此-5这里将被解释为4,294,967,291(假设是32位整数)。因此,上面的代码将打印出4,294,967,291而不是-5。
这种情况可以有更令人迷惑的形式:
int nX; unsigned int nY; if (nX – nY < 0) // do something
由于类型转换,这个if语句将永远判断为假,这显然不是程序员的原始意图!
5) delete vs delete []
许多C++程序员忘记了关于new和delete操作符实际上有两种形式:针对单个对象的版本,以及针对对象数组的版本。new操作符用来在堆上分配单个对象的内存空间。如果对象是某个类类型,该对象的构造函数将被调用。
Foo *pScalar = new Foo;
delete操作符用来回收由new操作符分配的内存空间。如果被销毁的对象是类类型,则该对象的析构函数将被调用。
delete pScalar;
现在考虑如下的代码片段:
Foo *pArray = new Foo[10];
这行代码为10个Foo对象的数组分配了内存空间,因为下标[10]放在了类型名之后,许多C++程序员没有意识到实际上是操作符new[]被调用来完成分配空间的任务而不是new。new[]操作符确保每一个创建的对象都会调用该类的构造函数一次。相反的,要删除一个数组,需要使用delete[]操作符:
delete[] pArray;
这将确保数组中的每个对象都会调用该类的析构函数。如果delete操作符作用于一个数组会发生什么?数组中仅仅只有第一个对象会被析构,因此会导致堆空间被破坏!
6) 复合表达式或函数调用的副作用
副作用是指一个操作符、表达式、语句或函数在该操作符、表达式、语句或函数完成规定的操作后仍然继续做了某些事情。副作用有时候是有用的:
x = 5;
赋值操作符的副作用是可以永久地改变x的值。其他有副作用的C++操作符包括*=、/=、%=、+=、-=、<<=、>>=、&=、|=、^=以及声名狼藉的++和—操作符。但是,在C++中有好几个地方操作的顺序是未定义的,那么这就会造成不一致的行为。比如:
void multiply(int x, int y) { using namespace std; cout << x * y << endl; } int main() { int x = 5; std::cout << multiply(x, ++x); }
因为对于函数multiply()的参数的计算顺序是未定义的,因此上面的程序可能打印出30或36,这完全取决于x和++x谁先计算,谁后计算。
另一个稍显奇怪的有关操作符的例子:
int foo(int x) { return x; } int main() { int x = 5; std::cout << foo(x) * foo(++x); }
因为C++的操作符中,其操作数的计算顺序是未定义的(对于大多数操作符来说是这样的,当然有一些例外),上面的例子也可能会打印出30或36,这取决于究竟是左操作数先计算还是右操作数先计算。
另外,考虑如下的复合表达式:
if (x == 1 && ++y == 2) // do something
程序员的本意可能是说:“如果x是1,且y的前自增值是2的话,完成某些处理”。但是,如果x不等于1,C++将采取短路求值法则,这意味着++y将永远不会计算!因此,只有当x等于1时,y才会自增。这很可能不是程序员的本意!一个好的经验法则是把任何可能造成副作用的操作符都放到它们自己独立的语句中去。
7)不带break的switch语句
另一个新手程序员常犯的经典错误是忘记在switch语句块中加上break:
switch (nValue) { case 1: eColor = Color::BLUE; case 2: eColor = Color::PURPLE; case 3: eColor = Color::GREEN; default: eColor = Color::RED; }
- 上一篇:MFC程序执行过程深入剖析
- 下一篇:C++直接初始化与复制初始化的区别深入解析