C++ 是一门功能强大的语言。它既兼容了 C 中的最重要的内容——指针,又有面向对象的功能,如使用继承组合以及虚函数实现多态等等。如果使用者对这些内容了解不够深入具体。则常常在使用 C++ 的指针时会出现不少问题。现举一些例子如下。(其中全部例子来自书本或者网上)。
例 1 、数组与继承的问题。 本例来自 《 C++ 高效编程:内存与性能优化》
问题代码如下:
上面的代码编译都能通过,但是运行的结果却是出人意料的。问题出在哪里呢? SetPetAge ()函数看似没有问题,第一个参数虽然是 Pet * ,但是对于传入的派生类参数可以自动向上转化成 Pet * 类型。但是问题恰恰是在这个函数中产生的。因为类 Pet 的大小是 4byte ( int m_age 的大小)。而 Pet 的派生类 Dog 的大小是 8byte (基类的大小加上 int m_weight 的大小)。按实际的意图 dogs 指针应该偏移 8 个字节修改 dogs[1].m_age 的值,但是实际情况是 dogs 指针偏移了 4 个字节修改了 dogs[0].m_weight 的值。
m_age |
m_weight |
表 1 对象 Dog 变量的存储方式。
这种问题说明,在使用对象数组与对象的继承过程中可能存在的隐患。解决的办法,可以将 SetPetAge() 作为 Pet 类的一个成员函数封装起来。函数如下:
这样就不用传递对象数组指针,就不会造成错误。
2、 指针与强制类型转换的问题。
3、 前几天在看到一个关于虚函数表的例子,在明白了程序的作用。在修改程序时,却犯了一个小小的错误例子。代码如下:本例来源于帖子: http://topic.youkuaiyun.com/u/20090904/15/c6cf71a8-9615-4131-b647-8e470ce9cf25.html
上面的函数虽然编译没有问题,但是一运行就弹出“程序遇到问题需要关闭”对话框。对比正确的程序发现在求 pfun2 函数的地址是与正确的存在一点点的差别。正确的写法如下:
与正确的代码相比,错误的代码在此处 *(int *)(&b) + 1 加了一个括号。问题也正是在于此。在此处加括号表示先加 1 ,再转换成 int 的指针。而 (int *)*(int *)(&b) + 1 表示先转变为 int 的指针,指针所指的地址再加 1 。而实际上 int 占有 4 个字节。所有正确的是地址值加了 4 而不是 1 。
以上两例都是指针所指的类型发生了变化而产生的问题。所以,在使用指针的时候一定要注意指针的类型,防止一些故意或者不经意的转化而带来的错误或隐患。
4、 关于指针的释放问题。
在《 C++ 编程思想》这本书中 Bruce Eckel 写道:“如果正在删除的对象的指针是 NULL ,将不发生任何事情。为此人们经常建议在删除指针后立即把指针赋值为 NULL 以免对它删除两次。对一个对象删除两次可能会产生某些问题。”的确在实际的编程过程中,对象删除两次在 VC6.0 的 Debug 模式下运行会弹出“ Debug Assertion Failed !”对话框。 VC 编译器不允许我们将一个对象删除两遍。虽然我们可以强调注意这个问题,但是有时候在设计类以及使用类的对象时可能不经意就会出现上面的一个物理地址释放两次的问题。最常见的问题就是浅拷贝的问题。见如下代码:(本例来源于网上内容的关于悬浮指针的代码)
上面的代码编译没有问题。但是在运行时会出现问题。因为类 CMyString 没有重新拷贝构造函数,也没有重载 = 运算符。所以 在 CMyString str3 = str1; 这句调用的是默认拷贝构造函数,这是浅拷贝,即 str3.m_pData 与 str1.m_pData 指向了同一个物理内存。所以在 str3 于 str1 析构时,同一个物理内存释放了两遍。解决的办法是:在类 CMyString 中增加拷贝构造函数,重装 = 运算符。
即在类中声明
CMyString(CMyString& pMy);
operator=(const CMyString& pMy);
定义:
指针的应用是非常灵活的,所以也常常出现问题。这就好比一把双刃剑。掌握得当,灵活使用,指针将会发挥巨大的作用。如果掌握不清楚,在使用过程中就难免会出现很多问题。在此总结一些例子,希望自己以及看博的人都不再犯此类的错误。