复制构造函数的用法及出现迷途指针问题

 复制构造函数利用下面这行语句来复制一个对象

  A (A &a)

 从上面这句话可以看出,所有的复制构造函数均只有一个参数,及对同一个类的对象的引用

 

比如说我们有一个类A,定义如下:

 

?
1
2
3
4
5
6
7
8
9
10
class A
{
public:
A(int i,int j){n=i;m=j;} //构造函数带两个参数,并将参数的值分别赋给两个私有成员
A(A &t); //我们自己定义一个默认构造函数
void print(){cout<<n<<m;}//输出两个成员的值
private:
int n;  //数据成员n
int m; //数据成员m
};

 

      在上面这个类的定义中我们定义了一个默认的构造函数(虽然默认构造函数一般是通过编译器自动定义的,但是这里我们模拟一下它的工作过程)。默认构造函数的工作方法应该如下面所示:

 

?
1
2
3
4
A(A &t)
{
        n=t.n;m=t.m;
}

 

       在这里我们模拟了一个默认复制构造函数是如何运行的。它通过别名t访问一个对象,并将该对象的成员赋给新对象的成员。这样就完成了复制工作。这样一来,我们如果再程序中定义了:

 

?
A a(2,4);

 

       这个对象。这就表示,利用构造函数,对象a的数据成员a.n=2;a.m=4,如果我们利用下面这句话调用一个默认的复制构造函数:

 

?
A b(a)

 

       那么它就会调用复制构造函数,即上面我们模拟的复制构造函数中所定义的“n=t.n;m=t.m”。这时对象b的数据成员b.n=2;b.m=4,这与对象a中的数据成员的值应该是一模一样的。

 

迷途指针产生的原因:“浅层复制构造函数”

      一般地,编译器提供的默认复制构造函数的功能只是把传递进来的对象的每一个成员变量的值复制给了新对象的成员变量。但是,如果这个老对象是一个指针,那么新对象也是一个指针,且它指向的内存地址和老对象是一样的!这样就会产生2个显而易见的问题:

  1. 我们可以随意地对一个指针所指向的内存空间进行赋值操作。那么另外一个指针所指向的内存空间由于和前面那个指针式一模一样的,那么它就不可避免地被修改;
  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指向的内存区域中的值改成32a->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.set(32)来改变a中指针x所指向的内存空间的值,可以看出b的指针x所指向的内存空间的值也跟着变成了32。绿色框代表了用b.set(99)来改变b中指针x所指向的内存空间的值,可以看出a的指针x所指向的内存空间的值也跟着变成了99。而在程序崩溃对话框(白底黑字那个)中指明了程序的第52行引发了一个错误,大意就是那个内存区域是非法的(如上图中蓝色框所示)。出现这个错误的原因其实就是由迷途指针造成的:当main函数结束(右大括号)并析构对象b的时候,由于指针成员x所指向的内存区域已经在第52行被释放了,这样一来,对象b中的指针x就变成了迷途指针了,我们再次释放这块内存空间的时候肯定就会导致程序的崩溃。

上篇文章末尾谈到了指针悬挂的问题,这主要是由于浅层复制构造函数的原因。为了解决这个指针悬挂的问题,这时候我们就需要引进一个新的概念:深层复制构造函数。

      下面,我们来介绍一下浅层复制构造函数深层复制构造函数之间的区别与联系......

  • 浅层复制构造函数:浅层构造赋值函数主要是将传递进来的对象的成员变量的所有值赋值给新对象的成员变量。
    x=a.x;//把对象a中的指针成员变量x的值复制给了对象b中的指针成员变量x
          上面这个语句就会产生一个问题,即对象b的指针成员变量b.x和对象a的指针成员变量a.x所保存的值是同一块内存空间的地址。如果我们析构了对象a,那么编译器会自动释放该内存地址,而b并不知道,这样就产生了指针悬挂的问题。这就是由于浅层复制构造函数的运作机理产生的,它只是将旧对象的数据复制给新对象的数据,而如果是指针对象,它复制的就是指针所保存的地址。上面这句话是不是有点绕啊,我们用下图来解释一下:       从上面这个图就可以非常清楚地看到,当我 们析构掉对象a的时候,编译器会自动释放堆中所创建的内存空间。而对于对象b而言,它压根就不知道有编译器释放堆中内存这么一回事,所以自然b.x就变成迷途指针了。
  • 深层复制构造函数:浅层构造赋值函数主要功能虽然也是是将传递进来的对象的成员变量的所有值赋值给新对象的成员变量,但是有一点不同之处在于,先看程序:
    ?
    1
    2
    x=new int;//创建一块新空间
    *x=*(a.x);//把对象a中指针成员x所指向的值赋值给了利用深层赋值构造函数所创建的对象b的指针成员x
    由上面的程序可以看出,其实深层复制构造函数中我们只添加了为成员指针指向的数据成员分配内存,同时在赋值的时候,我们是把旧对象中指针成员所指向的值复制给了新对象的指针成员,而不是地址,这样就可以避免指针悬挂的问题了。你可能会问,上面这么长一串话是啥意思哦?不解释,我们直接上图!

      从上面这个图,我们就显而易见地看出深层复制构造函数的好处了!注意看如果我们析构了对象a,那么编译器只是会回收内存地址为A处得内存,而不会管内存地址为D处的值。这样就避免了利用浅层复制函数所产生的对象b的指针悬挂问题了。

转载于:https://www.cnblogs.com/wft1990/p/6617416.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401305.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux下压缩某个文件夹(文件夹打包)

为什么80%的码农都做不了架构师&#xff1f;>>> tar -zcvf /home/xahot.tar.gz /xahot tar -zcvf 打包后生成的文件名全路径 要打包的目录 例子&#xff1a;把/xahot文件夹打包后生成一个/home/xahot.tar.gz的文件。 zip 压缩方法&#xff1a; 压缩当前的文件夹 zi…

解决Warning: Cannot modify header information - headers already sent b...

解决Warning: require(E:\testwwwroot\cc06\wp-admin/wp-includes/compat.php) [function.require]: failed to open stream: No such file or directory in E:\testwwwroot\cc06\wp-admin\wp-settings.php on line 246Fatal error: require() [function.require]: Failed open…

GoJS 使用笔记

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 作为商业软件&#xff0c;GoJs很容易使用&#xff0c;文档也很完备&#xff0c;不过项目中没有时间系统地按照文档学习&…

do while的使用

while循环&#xff1a;while(条件){循环体;} do while循环&#xff1a;do{循环体;}while(条件); //注意do while 有分号 while循环和do while循环只有一个差别&#xff0c;就是&#xff1a;while循环先判断条件&#xff0c;成立才做循环体&#xff1b;do while循环则是先做循环…

Android学习笔记:TabHost 和 FragmentTabHost

2019独角兽企业重金招聘Python工程师标准>>> Android学习笔记&#xff1a;TabHost 和 FragmentTabHostTabHost命名空间&#xff1a;android.widget.TabHost初始化函数&#xff08;必须在addTab之前调用&#xff09;&#xff1a;setup(); 包含两个子元素&#xff1a;…

转HTML、CSS、font-family:中文字体的英文名称

宋体 SimSun 黑体 SimHei 微软雅黑 Microsoft YaHei 微软正黑体 Microsoft JhengHei 新宋体 NSimSun 新细明体 PMingLiU 细明体 MingLiU 标楷体 DFKai-SB 仿宋 FangSong 楷体 KaiTi 仿宋_GB2312 FangSong_GB2312 楷体_GB2312 KaiTi_GB2312 宋体&#xff1a;SimSuncss中中文字体…

PostgreSQL VACUUM 之深入浅出 (二)

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 AUTOVACUUM AUTOVACUUM 简介 PostgreSQL 提供了 AUTOVACUUM 的机制。 autovacuum 不仅会自动进行 VACUUM&#xff0c;也…

基本概念-数据类型

参考&#xff1a;http://edu.51cto.com/roadmap/view/id-59.html5.数据类型5.1 数据类型可以使变量知道如何分配内存空间。例如&#xff0c;char类型占用1个字符&#xff0c;int通常占用4个字节5.2 C 语言常用的数据类型有 int sort 浮点型 double float字符串 char指针&#x…

android webview控件的缩放问题 隐藏缩放控件

利用java的反射机制 public void setZoomControlGone(View view) { Class classType; Field field; try { classType WebView.class; field classType.getDeclaredField("mZoomButtonsController"); field.setAccessible(true); ZoomButtonsController mZoomButton…

分布式概念与协议

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 分布式协议 分布式理论概念 1. 分布式数据一致性 分布式数据一致性&#xff0c;指的是数据在多个副本中存储时&#xff…

C语言中的转义字符

在字符集中&#xff0c;有一类字符具有这样的特性&#xff1a;当从键盘上输入这个字符时&#xff0c;显示器上就可以显示这个字符&#xff0c;即输入什么就显示什么。这类字符称为可显示字符&#xff0c;如a、b、c、$、和空格符等都是可显示字符。 另一类字符却没有这种特性。它…

最常被程序员们谎称读过的计算机书籍

英文原文&#xff1a;Books Programmers Claim to Have Read 马克吐温曾经说过&#xff0c;所谓经典小说&#xff0c;就是指很多人希望读过&#xff0c;但很少人真正花时间去读的小说。这种说法同样适用于“经典”的计算机书籍。 在 Stack Overflow (以及其它很多软件论坛)上&…

java Web监听器导图详解

监听器是JAVA Web开发中很重要的内容&#xff0c;其中涉及到的知识&#xff0c;可以参考下面导图&#xff1a; Web监听器 1 什么是web监听器&#xff1f; web监听器是一种Servlet中的特殊的类&#xff0c;它们能帮助开发者监听web中的特定事件&#xff0c;比如ServletContext,H…

Linux C/C++ UDP Socket 网络通信

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 昨晚 Vv 让我给她讲讲网络编程&#xff0c;于是我就傻乎乎的带她入了门… 以下内容为讲课时制作的笔记&#xff5e; 1. sock…

PCB布线规则

PCB布线有单面布线、双面布线及多层布线。布线的方式也有两种&#xff1a;自动布线及交互式布线&#xff0c;在自动布线之前&#xff0c;可以用交互式预先对要求比较严格的线进行布线&#xff0c;输入端与输出端的边线应避免相邻平行&#xff0c;以免产生反射干扰。必要时应加地…

strtok和strtok_r

strtok和strtok_r原型&#xff1a;char *strtok(char *s, char *delim); 功能&#xff1a;分解字符串为一组字符串。s为要分解的字符串&#xff0c;delim为分隔符字符串。 说明&#xff1a;首次调用时&#xff0c;s指向要分解的字符串&#xff0c;之后再次调用要把s设成NULL。 …

ConTeXt 标题前后的空白

由于标题字一般都挺大&#xff0c;所以默认时标题之间的空白比较大&#xff0c;尤其是当多个标题在一起的时&#xff0c;空白就显得格外地大&#xff01; 要去除空白可以这样做&#xff1a;\setuphead[chapter][before\nowhitespace,after\nowhitespace] 当然&#xff0c;我们也…

PHP 实现简单的 倒计时 时分秒

// 以 YII框架为例&#xff1a; C 层代码public function actionIndex(){//php的时间是以秒算。js的时间以毫秒算date_default_timezone_set("Asia/Hong_Kong");//地区//配置每天的活动时间段$starttimestr "18:53:00";//转换为时间戳$starttimestr …

芯片封装名称说明

1、BGA(ball grid array)   球形触点陈列&#xff0c;表面贴装型封装之一。在印刷基板的背面按陈列方式制作出球形凸点用以代替引脚&#xff0c;在印刷基板的正面装配LSI 芯片&#xff0c;然后用模压树脂或灌封方法进行密封。也称为凸点陈列载体(PAC)。引脚可超过200&#xf…

Django ORM

Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/course/detail/35475 目录* Django ORM ORM实操之数据库迁移 ORM实操之字段的修改 ORM实操之数据的增删改查 数据库同步 ORM创建表关系 Dja…