复制构造函数的用法及出现迷途指针问题
2017-03-25 16:08
302 查看
复制构造函数利用下面这行语句来复制一个对象:
从上面这句话可以看出,所有的复制构造函数均只有一个参数,及对同一个类的对象的引用
比如说我们有一个类A,定义如下:
?
在上面这个类的定义中我们定义了一个默认的构造函数(虽然默认构造函数一般是通过编译器自动定义的,但是这里我们模拟一下它的工作过程)。默认构造函数的工作方法应该如下面所示:
?
在这里我们模拟了一个默认复制构造函数是如何运行的。它通过别名t访问一个对象,并将该对象的成员赋给新对象的成员。这样就完成了复制工作。这样一来,我们如果再程序中定义了:
?
这个对象。这就表示,利用构造函数,对象a的数据成员a.n=2;a.m=4,如果我们利用下面这句话调用一个默认的复制构造函数:
?
那么它就会调用复制构造函数,即上面我们模拟的复制构造函数中所定义的“n=t.n;m=t.m”。这时对象b的数据成员b.n=2;b.m=4,这与对象a中的数据成员的值应该是一模一样的。
迷途指针产生的原因:“浅层复制构造函数”
一般地,编译器提供的默认复制构造函数的功能只是把传递进来的对象的每一个成员变量的值复制给了新对象的成员变量。但是,如果这个老对象是一个指针,那么新对象也是一个指针,且它指向的内存地址和老对象是一样的!这样就会产生2个显而易见的问题:
我们可以随意地对一个指针所指向的内存空间进行赋值操作。那么另外一个指针所指向的内存空间由于和前面那个指针式一模一样的,那么它就不可避免地被修改;
如果我们在程序中无意将老对象所指向的内存地址释放掉了,那么新对象的指针自然就变成了一个迷途指针。举一个形象的例子来说就是:如果B对象持用一个指针指向对象A, 现有一个B对象的拷贝C,那么它也持有一个指针p2指向A. 若某时, B释放了对象A, 但C是无法知晓的, C认为它持有的指针指向的A有效, 之后若C再调用A的方法就报错。
输出结果:
上面这个语句就会产生一个问题,即对象b的指针成员变量b.x和对象a的指针成员变量a.x所保存的值是同一块内存空间的地址。如果我们析构了对象a,那么编译器会自动释放该内存地址,而b并不知道,这样就产生了指针悬挂的问题。这就是由于浅层复制构造函数的运作机理产生的,它只是将旧对象的数据复制给新对象的数据,而如果是指针对象,它复制的就是指针所保存的地址。上面这句话是不是有点绕啊,我们用下图来解释一下:
从上面这个图就可以非常清楚地看到,当我 们析构掉对象a的时候,编译器会自动释放堆中所创建的内存空间。而对于对象b而言,它压根就不知道有编译器释放堆中内存这么一回事,所以自然b.x就变成迷途指针了。
深层复制构造函数:浅层构造赋值函数主要功能虽然也是是将传递进来的对象的成员变量的所有值赋值给新对象的成员变量,但是有一点不同之处在于,先看程序:
?
由上面的程序可以看出,其实深层复制构造函数中我们只添加了为成员指针指向的数据成员分配内存,同时在赋值的时候,我们是把旧对象中指针成员所指向的值复制给了新对象的指针成员,而不是地址,这样就可以避免指针悬挂的问题了。你可能会问,上面这么长一串话是啥意思哦?不解释,我们直接上图!
从上面这个图,我们就显而易见地看出深层复制构造函数的好处了!注意看如果我们析构了对象a,那么编译器只是会回收内存地址为A处得内存,而不会管内存地址为D处的值。这样就避免了利用浅层复制函数所产生的对象b的指针悬挂问题了。
A (A &a)
从上面这句话可以看出,所有的复制构造函数均只有一个参数,及对同一个类的对象的引用
比如说我们有一个类A,定义如下:
?
?
?
?
迷途指针产生的原因:“浅层复制构造函数”
一般地,编译器提供的默认复制构造函数的功能只是把传递进来的对象的每一个成员变量的值复制给了新对象的成员变量。但是,如果这个老对象是一个指针,那么新对象也是一个指针,且它指向的内存地址和老对象是一样的!这样就会产生2个显而易见的问题:
我们可以随意地对一个指针所指向的内存空间进行赋值操作。那么另外一个指针所指向的内存空间由于和前面那个指针式一模一样的,那么它就不可避免地被修改;
如果我们在程序中无意将老对象所指向的内存地址释放掉了,那么新对象的指针自然就变成了一个迷途指针。举一个形象的例子来说就是:如果B对象持用一个指针指向对象A, 现有一个B对象的拷贝C,那么它也持有一个指针p2指向A. 若某时, B释放了对象A, 但C是无法知晓的, C认为它持有的指针指向的A有效, 之后若C再调用A的方法就报错。
#include <iostream> using namespace std; class A { public: A(){x=new int;*x=5;} //创建一个对象的同时将成员指针指向的变量保存到 新空间中 ~A(){delete x;x = NULL;}//析构对象的同时删除成员指针指向的内存空间并将指针赋为空 A(A &a) { cout << "复制构造函数执行...\n" <<endl; x = a.x; //将旧对象的成员指针x指向的空间处的数据赋给新对象的成员指针x } void print(){cout<<*x<<endl;} void set(int i){*x=i;} private: int *x; }; int main() { A *a = new A(); //利用指针在堆中创建一个对象 cout<<"a:"; a->print(); cout<<endl; A b=(*a); //这里初始化的对象为指针a所指向的堆中的对象 //调用复制构造函数之后,将对象b变成对象a的一个拷贝,那么对象a和对象b所输出的值应该是一样的 cout<<"b:"; b.print(); cout<<endl; //利用对象a中的成员函数set()将指针x指向的内存区域中的值改成32 a->set(32); cout<<"a:"; a->print(); cout<<"b:"; b.print(); cout<<endl; //利用对象b中的成员函数set()将指针x指向的内存区域中的值改成99 b.set(99); cout<<"a:"; a->print(); cout<<"b:"; b.print(); cout<<endl; delete a; return 0; }
输出结果:
从上面这个图就可以非常清楚地看到,当我 们析构掉对象a的时候,编译器会自动释放堆中所创建的内存空间。而对于对象b而言,它压根就不知道有编译器释放堆中内存这么一回事,所以自然b.x就变成迷途指针了。
深层复制构造函数:浅层构造赋值函数主要功能虽然也是是将传递进来的对象的成员变量的所有值赋值给新对象的成员变量,但是有一点不同之处在于,先看程序:
?
从上面这个图,我们就显而易见地看出深层复制构造函数的好处了!注意看如果我们析构了对象a,那么编译器只是会回收内存地址为A处得内存,而不会管内存地址为D处的值。这样就避免了利用浅层复制函数所产生的对象b的指针悬挂问题了。
相关文章推荐
- 复制构造函数的用法及指针悬挂问题的产生和解决(上)——提出问题
- 复制构造函数的用法及指针悬挂问题的产生和解决(上)——提出问题
- 函数用参数为 结构体指针 在VC下出现的问题
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();出现空指针异常的问题
- 解决浅层复制中容易出现迷途指针的方法
- C语言堆内存管理上出现的问题,内存泄露,野指针使用,非法释放指针
- c++ 中有静态指针成员的链表析构出现的问题
- iText使用PdfCopy时出现的空指针问题
- java集合经常出现空指针问题的解决方案
- 解决MONKEY-TEST 测试出现的Spinner弹出后关闭时出现的空指针问题
- Properties出现空指针异常问题
- cocos2d-x CCArray使用中避免出现野指针问题
- 在框架中获取视图的指针出现的问题
- 指针的动态分配怎么运行后老出现这个问题
- 最近项目中出现的问题(结构体,指针,文件描述符)
- 解决MONKEY-TEST 测试出现的Spinner弹出后关闭时出现的空指针问题
- 对于静态成员指针的出现的问题
- Err 的用法出现的问题和解决方法
- 在servlet里面调用spring的备案出现空指针的问题
- Android绑定服务后出现空指针异常问题