《程序员面试宝典》第三版,p95-96页邮到面试题,有兴趣的同学可以一起来看看:
#include "stdafx.h" #include <iostream> #include <vector> using namespace std; class Demo { public: Demo(const char* a) { str = new char[32]; strcpy(str,a); } ~Demo() { if (str) delete [] str; } private: char *str; }; int _tmain(int argc, _TCHAR* argv[]) { Demo d("Hello,World!"); vector<Demo> *p = new vector<Demo>(); p->push_back(d); delete p; return 0; }
问:这段代码有错误,请找出其中出错的原因。
代码自己定义了一个类,类中有一个变量,有构造函数,析构函数。在主函数中,实例化一个Demo类型的变量d,用Vector存放这个变量d,最后删除vector。执行这段代码之后,程序会出错,无法执行下去。
要明白这个程序,得先了解当执行vector的push_back(d)函数时,加入到vector中的是原来的d对象呢,还是d对象的副本?
答案是d对象的副本,当vector中添加元素的时候,是将元素值复制到容器里。就是说容器中存放的是原始元素的副本。被复制的原始值与新容器中的元素各不相关,此后,容器内元素值发生变化时,被复制的原值不会受到影响。
既然加入到vector中的对象是副本,那么就涉及到d对象的复制,程序中并没有写复制构造函数,但是类会调用默认的复制构造函数(或者称为合成复制构造函数),默认构造函数只是简单的将对象的每个非static成员,依次复制到正创建的对象。Demo类只有一个成员变量char * str,这是一个指针类型的变量,因此会执行this.str = d.str;其结果是两个指针共同指向了同一块儿内存区域。(这就是所谓的浅层复制,不清楚的可以看看)
因为当执行delete p;时,在销毁vector对象的同时,也会将vector中存储的对象同时销毁掉,d的副本str指向的区域被销毁掉,此时d的str成员就成了悬垂指针。当main执行完毕后,需要将d销毁掉,此时d的str成员是悬垂指针,因此会找不到要销毁的内容,程序就会出错。
这个问题实质上的问题在于,类的深层复制和浅层复制的区别。当有指针类型成员变量的时,应该采取深层复制。当执行复制操作时,保证每个对象都有自己的独立的内存空间。
程序应该修改如下:
#include "stdafx.h" #include <iostream> #include <vector> using namespace std; class Demo { public: Demo(const char* a) { str = new char[32]; strcpy(str,a); } Demo(const Demo& d) { this->str = new char[32]; strcpy(str,d.str); } ~Demo() { if (str) delete [] str; } private: char *str; }; int _tmain(int argc, _TCHAR* argv[]) { Demo d("Hello,World!"); vector<Demo> *p = new vector<Demo>(); p->push_back(d); delete p; return 0; }
通过这道题,我总结了以下几个知识点:
1. 为了管理具有指针成员的类,必须自己定义三个复制控制成员:复制构造函数,复制操作符,析
构函数。
2. vector中添加元素的时候,是将元素值复制到容器里。就是说容器中存放的是原始元素的副本。被
复制的原始值与新容器中的元素各不相关,此后,容器内元素值发生变化时,被复制的原值不会
受到影响,反之亦然。
3. 当删除vector对象时,会将vector中存储的对象一并删除掉。