C++类指针类型的成员变量的浅复制与深复制

 

本篇文章旨在阐述C++类的构造,拷贝构造,析构机制,以及指针成员变量指针悬空问题的解决。需要读者有较好的C++基础,熟悉引用,const的相关知识。

引言:

            类作为C++语言的一种数据类型,是对C语言结构体的一种扩展。由于C++是面向过程与面向对象的混合语言,因此在使用面向对象思想解决现实问题模型时,设计好类是很重要的(跑题了)。关于类,这篇blog中有很好的介绍(链接http://blog.csdn.net/zqixiao_09/article/details/51474556)。我要介绍的是,关于创建一个空类,类体内都包含哪些成员函数呢?看下面例子 。

class MyClass { //创建一个空类MyClass

};
void main()
{MyClass c; //创建该类的对象c,此处会自动调用默认构造函数MyClass d(c); //创建一个对象d,并且用已经存在的同类对象c去初始化d,此处调用了默认拷贝构造函数MyClass e; //创建一个对象ee = c;  //此处是对象赋值,调用了默认赋值运算符成员函数
}

那么我们来运行一下

 可以看到是成功的。

 以上实例说明,对于用户定义的空类,该类会自动包含六个成员函数,分别是:

l  默认构造函数 A(){//空函数体}

l  默认拷贝构造函数(本次讲解重点)A(const A & ){//简单的对象成员变量赋值操作}

l  默认析构函数 ~A(){//空函数体}

l  赋值运算符重载成员函数(本次讲解重点) A & operator =(const A &){//也是简单的对象成员变量赋值操作}

l  取地址操作符重载成员函数

l  Const修饰的取地址操作符重载成员函数

前四个是本次讲解的内容,重点放在拷贝构造,赋值运算符重载这两个成员函数

拷贝构造函数:

         拷贝构造函数是一种特殊的构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显示使用拷贝构造函数。归结来说。有三个场合要用到拷贝构造函数:

l  对象作为函数的参数,以值传递的方式传给函数

l  对象作为函数的返回值,以值传递的方式从函数返回调用处

l  使用一个对象去初始化一个新建的对象

      即有拷贝构造函数的调用一定会有新对象生成

      还有一点需要注意的是,拷贝构造函数必须以引用的方式传递参数。这是因为,在值传递的方式传递给一个函数的时候,会调用拷贝构造函数生成函数的实参。如果拷贝构造函数的参数仍然是以值的方式,就会无限循环的调用下去,直到函数的栈溢出。

例子:

#include<iostream.h>
#include<string.h>
class Person{
public : Person(); //无参构造函数 Person(int age,char na[]); //重载一般构造函数 Person(const Person & p);//拷贝构造函数 ~Person(); // 析构函数 void disp(); private : int age; char *name; }; Person::Person(){ age=0; name=new char[2]; strcpy(name,"\0"); cout<<"default constructor\n";} Person::Person(int age,char na[]) { this->age=age; name=new char[strlen(na)+1]; //为指针变量动态分配空间 strcpy(name,na); //赋值 cout<<"constructor\n"; } Person::Person(const Person & p) { this->age=p.age; this->name=new char[strlen(p.name)+1]; strcpy(name,p.name); cout<<"copy constructor\n"; } Person::~Person() { delete [] name; cout<<"destroy\n"; } void Person::disp() { cout<<"age "<<age<<" name "<<name<<endl; } void f(Person p) { cout<<"enter f \n"; p.disp(); return ; } Person f1() { cout<<"enter f \n"; Person p; cout<<"next is return object of Person\n"; return p; } void main() { Person p1(21,"xiaowang");//调用一般构造函数  p1.disp(); Person p2(p1);//调用拷贝构造函数  p2.disp(); Person p3=p1;//调用拷贝构造函数  p3.disp(); cout<<"true\n"; cout<<"拷贝构造函数调用在函数形参是对象且值传递\n"; f(p1); //① cout<<"拷贝构造函数调用在函数返回值是对象且值传递\n"; f1(); //② cout<<"主函数结束,调用三次析构函数销毁对象\n"; }

运行结果

我们来分析一下源程序①②处以及运行结果的画线处

①  处是函数形参是对象,且是值传递的情况下调用了拷贝构造函数,我们可以看到该形参对象的生存期是只在函数f里面,当函数调用结束后,就自动被析构函数清理了。但是不会引起指针悬空问题,因为如下图所示。

         其中p对象是f的形参,它由主函数调用f开始存在,由函数f调用结束而撤销,但是析构p时不会将p1的name所指空间析构,因此最终主函数main救赎后析构p1时不会引起指针悬空问题

②  函数返回值是对象且值传递返回方式时会调用靠宝贝构造函数。

分析结果会看到有两次对象创建,在子函数f1里面先创建默认对象p,然后返回对象p到调用处,会自动调用拷贝构造,创建一个匿名的对象(记为pi),调用结束后会先析构p,在析构pi

 

赋值运算符重载成员函数

         拷贝构造函数和赋值运算符的行为比较相似,都是将一个对象的值复制给另一个对象;但是其结果却有些不同,拷贝构造函数使用传入对象的值生成一个新的对象的实例,而赋值运算符是将对象的值复制给一个已经存在的实例。这种区别从两者的名字也可以很轻易的分辨出来,拷贝构造函数也是一种构造函数,那么它的功能就是创建一个新的对象实例;赋值运算符是执行某种运算,将一个对象的值复制给另一个对象(已经存在的)。调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符。

         实例:

#include<iostream.h>
const int MAX=6;
class Array{double * data;
public:Array();Array(const Array &a);~Array();double & operator [](int i); //下标重载运算符Array & operator =(Array & a); //=重载赋值运算符Array & operator +(Array& a); //+运算符重载成员函数Array & operator -(Array & a); //-运算符重载成员函数void disp(); //输出一个数组
};
Array::Array()
{int i;data=new double[MAX];for(i=0;i<MAX;i++)data[i]=0;cout<<"construct"<<endl;
}
Array::Array(const Array &a)
{data=a.data;cout<<"copy construct \n";
}
Array::~Array()
{delete [] data;cout<<"destroy"<<endl;
}
double& Array::operator [](int i) //返回引用类型,可以是左值
{return *(data+i);
}
Array& Array::operator =(Array &a) //=重载赋值运算符
{int i;for(i=0;i<MAX;i++)data[i]=a.data[i];cout<<"对象赋值,调用赋值运算符重载函数\n";return *this;
}
Array & Array::operator +(Array& a)
{int i;static Array tmp;for(i=0;i<MAX;i++)tmp.data[i]=data[i]+a.data[i];return tmp;
}
Array & Array::operator -(Array & a)
{for(int i=0;i<MAX;i++)data[i]-=a.data[i];return *this;
}
void Array::disp()
{for(int i=0;i<MAX;i++)cout<<data[i]<<"  ";cout<<endl;
}void main()
{Array a,b,c,d;cout<<"创建四个数组对象\n";cout<<"给数组a赋部分值\n";a[0]=1;a[1]=2;a[2]=3;a[3]=4;cout<<"a=";a.disp();cout<<"执行b=a\n";b=a;cout<<"b=";b.disp();cout<<"执行c=a+b\n";c=a+b;cout<<"c=";c.disp();cout<<"执行c=a+b之后a,b结果:\n";cout<<"a=";a.disp();cout<<"b=";b.disp();cout<<"执行d=a-b\n";d=a-b;cout<<"d=";d.disp();cout<<"执行d=a-b之后a,b结果:\n";cout<<"a=";a.disp();cout<<"b=";b.disp();cout<<"主函数执行完毕,销毁四个对象和静态成员对象\n";
}

运行结果

分析:

从结果可以看出,如果函数的形参是对象,或者返回值是对象,但是是以引用传递的方式,那么靠诶构造函数就不会被调用,这也是引用的作用,即对同一个对象起别名。,但要注意在赋值运算符重载成员函数中,对象的定义为静态变量,这是为了防止子函数调用已结束就将析构该对象导致指针悬空问题。

 

深拷贝与浅拷贝

         深拷贝和浅拷贝主要是针对类中的指针动态分配的空间来说的,因为对于指针只是简单的值复制并不能分割开两个对象的关联,任何一个对象对该指针的操作都会影响到另一个对象。这时候就需要提供自定义的深拷贝的拷贝构造函数,消除这种影响。通常的原则是:

  • 含有指针类型的成员或者有动态分配内存的成员都应该提供自定义的拷贝构造函数
  • 在提供拷贝构造函数的同时,还应该考虑实现自定义的赋值运算符

对于拷贝构造函数的实现要确保以下几点:

  • 对于值类型的成员进行值复制
  • 对于指针和动态分配的空间,在拷贝中应重新分配分配空间
  • 对于基类,要调用基类合适的拷贝方法,完成基类的拷贝
  • 拷贝构造函数和赋值运算符的行为比较相似,却产生不同的结果;拷贝构造函数使用已有的对象创建一个新的对象,赋值运算符是将一个对象的值复制给另一个已存在的对象。区分是调用拷贝构造函数还是赋值运算符,主要是否有新的对象产生。
  • 关于深拷贝和浅拷贝。当类有指针成员或有动态分配空间,都应实现自定义的拷贝构造函数。提供了拷贝构造函数,最后也实现赋值运算符。

总结:

  • 拷贝构造函数和赋值运算符的行为比较相似,却产生不同的结果;拷贝构造函数使用已有的对象创建一个新的对象,赋值运算符是将一个对象的值复制给另一个已存在的对象。区分是调用拷贝构造函数还是赋值运算符,主要是否有新的对象产生。
  • 关于深拷贝和浅拷贝。当类有指针成员或有动态分配空间,都应实现自定义的拷贝构造函数。提供了拷贝构造函数,最后也实现赋值运算符。

 

转载于:https://www.cnblogs.com/gaochaochao/p/8370762.html

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

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

相关文章

21天学通c语言第四版pdf,21天学通Visual C++ PDF扫描版[116MB]

21天学通Visual C 内容简介&#xff1a;本书是Visual C语言的入门教程&#xff0c;较全面地介绍了Visual C编程的相关知识&#xff0c;然而&#xff0c;本书并没有泛讲Visual C语言的所有知识&#xff0c;而是突出重点&#xff0c;选择最重要的内容进行讲解。本书一共分为五篇&…

继续聊WPF——动态数据模板

我为啥称之为“动态数据模板”&#xff1f;先看看下面的截图&#xff0c;今天&#xff0c;我们就是要实现这种功能。 大概是这样的&#xff0c;我们定义的DataTemplate是通过触发器动态应用到 ComboBoxItem 上。 这个下拉列表控件绑定了一个Person集合&#xff0c;Person类的定…

刺激战场c语言,刺激战场:假车库、C字楼都是啥?学会吃鸡术语新手变大神!...

原标题&#xff1a;刺激战场&#xff1a;假车库、C字楼都是啥&#xff1f;学会吃鸡术语新手变大神&#xff01;刺激战场有越来越多的新玩家加入其中&#xff0c;然而新手想入门&#xff0c;却有时候找不对方法&#xff0c;其实吃鸡这个游戏其实还是非常简单的&#xff0c;不过想…

HTMLParser-实战

了解了HTMLParser库的知识后&#xff0c;选择做一个小训练&#xff0c;对https://www.python.org/events/python-events/这个网址进行分析&#xff0c;之后输出其中每次会议的题目、时间和地点。 如果要简单了解下HTMLParser库可以点击打开 下面进入正题 这是网站的源码&#x…

c++2015语言,2015年7月TIOBE编程语言排行榜:C++ 的复兴

2015年7月TIOBE编程语言排行榜&#xff1a;C 的复兴C是2015年上半年同比增长最快的编程语言。具体为C 增长3.1%&#xff0c;Java 增长2.0%&#xff0c;C#增长1.6%&#xff0c;Python增长1.6%。C大幅度增长的原因可能是引入了新的C11标准。这使得C被大范围的接受。C 11标准为C带…

android抽奖动画,Android App中实现简单的刮刮卡抽奖效果的实例详解

主要思想&#xff1a;将一个view设计成多层&#xff1a;背景层&#xff0c;含中奖信息等&#xff1b;遮盖层&#xff0c;用于刮奖&#xff0c;使用关联一个Bitmap的Canvas在该Bitmap上&#xff0c;使用它的canvas.drawPath的api来处理 手势滑动(类似刮奖的动作)使用paint.setXf…

如何提高英语听力(内容摘自NECCS)+ 乘法表

乘法表 print(\n.join([ .join([%s*%s%-2s%(y,x,x*y) for y in range(1,x1)]) for x in range(1,10)])) 如何提高英语听力 很喜欢这篇关于提高英语听力的文章&#xff0c;所以收藏下来和大家一同分享一下 人走路时要用两条腿&#xff0c;没有任何人会觉得走路费劲。可如果让人…

android新拟态实现方法,Android 新拟态UI (Neumorphism)

前言本文转自github&#xff0c;只是进行了图片的处理和部分翻译&#xff0c;已获作者授权截至首次发文前找到的原作者的文章地址-May,17,2020若找到本文章更新的时间节点&#xff0c;请私信我更新。原作者github地址文末附源码下载地址&#xff0c;免费。Android上的拟态化UIT…

codeforces 919E Congruence Equation

E. Congruence Equationtime limit per test3 secondsmemory limit per test256 megabytesinputstandard inputoutputstandard outputGiven an integer x. Your task is to find out how many positive integers n (1 ≤ n ≤ x) satisfy where a, b, p are all known co…

android 过滤数组中的重复元素,Flutter List数组避免插入重复数据的实现

List具有一定长度存在索引的对象集合(长度为0不存在索引,长度>0存在索引)常见列表1、定长列表默认值null例如:List fixedLengthList new List(2)、List fixedLengthList new List(8)List fixedLengthList new List(2);for(int i0;i<2;i){print("索引为${i}的值${…

[js]jquery里的jsonp实现ajax异源请求

同源请求-jquery <script type"text/javascript" src"http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script><script type"text/javascript">$.ajax({url: "data.txt",type: "get",dataType…

android listview remove 动画,给Android ListView添加删除item动画

给Android ListView添加删除item动画给listview删除一个item的时候加上一个折叠动画&#xff0c;感觉效果会好一点。步骤是当删除一个view&#xff0c;先用动画把view的高度改变&#xff0c;看上去就是折叠的效果。当动画完成的时候&#xff0c;再真正把item移除。private void…

Css 选择器 算法 规则

首先上图 css 命令在读取时是按照其优先级的高低的先后顺序来解读的&#xff0c;当优先级相同时是按照其css命令写的先后顺序来读取的&#xff0c;即后面的css覆盖&#xff08;前后css属性设置 不同时&#xff09;或继承&#xff08;前后css属性设置相同或后面未设置该css属性时…

Python算法——二叉树

一、二叉树 from collections import dequeclass BiTreeNode:def __init__(self, data):self.data dataself.lchild Noneself.rchild Nonea BiTreeNode(A) b BiTreeNode(B) c BiTreeNode(C) d BiTreeNode(D) e BiTreeNode(E) f BiTreeNode(F) g BiTreeNode(G)e.lchil…

艾伟_转载:.NET 4.0新特性-- Corrupted State Exceptions

作为程序员&#xff0c;我想很多人应该都有过跟异常打交道的经历。而且相信也有很多人也都写过catch(Exception e){//blabla}这种把所有未知异常一股脑儿捕获并处理掉的代码吧。不管是为敷衍客户也好&#xff0c;让程序继续运行以避免糟糕的用户体验也罢&#xff0c;在微软眼中…

艾伟_转载:使用Lambda表达式编写递归函数

前言 著名的牛顿同学曾经说过&#xff1a;如果说我比别人看得更远些,那是因为我站在了巨人的肩上. 原文&#xff1a;If I have been able to see further, it was only because I stood on the shoulders of giants. Whats Lambda表达式? 请参考msdn&#xff1a;Lambda 表达式…

腾讯测试鸿蒙系统,爆料:荣耀 30 Pro已开始测试华为鸿蒙系统

某数码博主今日放出了一张华为内部关于荣耀 30 Pro 测试 HarmonyOS 的截图&#xff0c;图片显示该机正运行基于 HarmonyOS 2.0 开发者测试版的系统。此外&#xff0c;他还透露荣耀 30 系列、V30 系列、Play4 Pro 下个月将升级到华为鸿蒙系统。华为在 2019 年开发者大会上正式推…

html多行文本框下拉,html基础-表单控件、密码框、单选按钮、复选框、多行文本框、下拉列表、按钮(提交、图片、重置)...

表单的介绍(将前端页面表单的值发送给后台&#xff0c;后台通过表单中name属性取值)可以获取客户端的信息(数据)&#xff0c;表单有各种各样的控件&#xff0c;输入框&#xff0c;复选框 按钮等表单的功能&#xff1a;交互功能表单的工作原理&#xff1a;浏览有表单的页面&…

Lync Server 2010的部署系列_第七章 部署边缘服务器(上)

一、配置边缘支持的内部DNS记录 1) 登录DC.Gianthard.com&#xff08;192.168.1.11&#xff09;。在相应的 DNS 服务器上&#xff0c;依次单击“开始”、“控制面板”、“管理工具”&#xff0c;然后单击“DNS”。 2) 在 SIP 域的控制台树中&#xff0c;展开“正向查找区域”&a…

html5 txt文件上传,JavaScript html5利用FileReader实现上传功能

本文实例为大家分享了H5利用FileReader上传文件的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下1. Html部分文件上传演练Browse...2. JS部分var result document.getElementById("result");var input document.getElementById("file_input");…