赋值运算符和拷贝构造函数的区别与联系

转载:http://blog.csdn.net/hebbely/article/details/65437510

简述:

C++中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法。拷贝构造函数使用已有的对象创建一个新的对象,赋值运算符是将一个对象的值复制给另一个已存在的对象。区分是调用拷贝构造函数还是赋值运算符,主要是否有新的对象产生。


1、构造函数



① 构造函数是一种特殊的类成员函数,是当创建一个类的对象时,它被调用来对类的数据成员进行初始化和分配内存(构造函数的命名必须和类名完全相同

② 首先说一下一个C++的空类,编译器会加入哪些默认的成员函数

·默认构造函数和拷贝构造函数

·析构函数

·赋值函数(赋值运算符)

·取值函数

**即使程序没定义任何成员,编译器也会插入以上的函数! 

注意:构造函数可以被重载,可以多个,可以带参数;析构函数只有一个,不能被重载,不带参数

 

③ 而默认构造函数没有参数,它什么也不做。当没有重载无参构造函数时,

  Person man就是通过默认构造函数来创建一个对象

④下面代码为构造函数重载的实现


[cpp] view plain copy
  1. class Person    
  2. {      
  3. public:  
  4.     Person()     
  5.     {    
  6.         qDebug()<<”无参构造函数”<<endl;    
  7.     }    
  8.     Person(int i):m_i(i) {}  //初始化列表    
  9. private:  
  10.     int m_i;  
  11. }  


2、初步认识拷贝构造函数和赋值运算符


① 拷贝构造函数是C++独有的,它是一种特殊的构造函数,用基于同一类的一个对象构造和初始化另一个对象。

② 在默认情况下(用户没有定义,但是也没有显式的删除),编译器会自动的隐式生成一个拷贝构造函数和赋值运算符。但用户可以使用delete来指定不生成拷贝构造函数和赋值运算符,这样的对象就不能通过值传递,也不能进行赋值运算。


[cpp] view plain copy
  1. class Person  
  2. {  
  3. public:  
  4.   
  5.     Person(const Person& p) = delete//不生成拷贝构造函数  
  6.   
  7.     Person& operator=(const Person& p) = delete//不生成赋值运算符  
  8.   
  9. private:  
  10.     int age;  
  11.     string name;  
  12. };  

上面的定义的类Person显式的删除了拷贝构造函数和赋值运算符,在需要调用拷贝构造函数或者赋值运算符的地方,会提示_无法调用该函数,它是已删除的函数_。

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


3、调用场合


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

② 调用拷贝构造函数主要有以下场景:

    • 对象作为函数的参数,以传递的方式传给函数。 
    • 对象作为函数的返回值,以的方式从函数返回
    • 使用一个对象给另一个对象初始化

③ 什么时候编译器会生成默认的拷贝构造函数:

A. 如果用户没有自定义拷贝构造函数,并且在代码中使用到了拷贝构造函数,编译器就会生成默认的拷贝构造函数。但如果用户定义了拷贝构造函数,编译器就不在生成。

B. 如果用户定义了一个构造函数,但不是拷贝构造函数,而此时代码中又用到了拷贝构造函数,那编译器也会生成默认的拷贝构造函数。


④ 样例代码如下:


[cpp] view plain copy
  1. class Person  
  2. {  
  3. public:  
  4.     Person(){}  
  5.     Person(const Person& p)  
  6.     {  
  7.         cout << "Copy Constructor" << endl;  
  8.     }  
  9.   
  10.     Person& operator=(const Person& p)  
  11.     {  
  12.         cout << "Assign" << endl;  
  13.         return *this;  
  14.     }  
  15.   
  16. private:  
  17.     int age;  
  18.     string name;  
  19. };  
  20.   
  21. void f(Person p)  
  22. {  
  23.     return;  
  24. }  
  25.   
  26. Person f1()  
  27. {  
  28.     Person p;  
  29.     return p;  
  30. }  
  31.   
  32. int main()  
  33. {  
  34.     Person p;  
  35.     Person p1 = p;    // A  
  36.     Person p2;  
  37.     p2 = p;           // B  
  38.     f(p2);            // C  
  39.   
  40.     p2 = f1();        // D  
  41.   
  42.     Person p3 = f1(); // E  
  43.   
  44.     getchar();  
  45.     return 0;  
  46. }  

上面代码中定义了一个类Person,显式的定义了拷贝构造函数和赋值运算符。然后定义了两个函数:f(),以值的方式参传入Person对象;f1(),以值的方式返回Person对象。在main中模拟了5中场景,测试调用的是拷贝构造函数还是赋值运算符。执行结果如下:


分析如下:

A. 这是虽然使用了"=",但是实际上使用对象p来创建一个新的对象p1。也就是产生了新的对象,所以调用的是拷贝构造函数

B. 首先声明一个对象p2,然后使用赋值运算符"=",将p的值复制给p2,显然是调用赋值运算符,为一个已经存在的对象赋值 。

C. 以值传递的方式将对象p2传入函数f内,调用拷贝构造函数构建一个函数f可用的实参

D. 这条语句拷贝构造函数和赋值运算符都调用了。函数f1以值的方式返回一个Person对象,在返回时会调用拷贝构造函数创建一个临时对象tmp作为返回值;返回后调用赋值运算符将临时对象tmp赋值给p2.

E. 按照4的解释,应该是首先调用拷贝构造函数创建临时对象;然后再调用拷贝构造函数使用刚才创建的临时对象创建新的对象p3,也就是会调用两次拷贝构造函数。不过,编译器也没有那么傻,应该是直接调用拷贝构造函数使用返回值创建了对象p3。


4、深拷贝、浅拷贝


① 通常,默认生成的拷贝构造函数和赋值运算符,只是简单的进行值的复制。例如:上面的Person类,字段只有intstring两种类型,这在拷贝或者赋值时进行值复制创建的出来的对象和源对象也是没有任何关联对源对象的任何操作都不会影响到拷贝出来的对象。反之,假如Person有一个对象为int *,这时在拷贝时还只是进行值复制,那么创建出来的Person对象的int *的值就和源对象的int *指向的是同一个位置。任何一个对象对该值的修改都会影响到另一个对象,这种情况就是浅拷贝

② 系统提供的默认拷贝构造函数工作方式是内存拷贝,也就是浅拷贝。如果对象中用到了需要手动释放的对象,则会出现问题,这时就要手动重载拷贝构造函数,实现深拷贝。


③ 深拷贝与浅拷贝:

浅拷贝:如果复制的对象中引用了一个外部内容(例如分配在堆上的数据),那么在复制这个对象的时候,让新旧两个对象指向同一个外部内容,就是浅拷贝。(指针虽然复制了,但所指向的空间内容并没有复制,而是由两个对象共用,两个对象不独立,删除空间存在)

深拷贝:如果在复制这个对象的时候为新对象制作了外部对象的独立复制,就是深拷贝。

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

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

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

    • 对于值类型的成员进行值复制
    • 对于指针和动态分配的空间,在拷贝中应重新分配分配空间
    • 对于基类,要调用基类合适的拷贝方法,完成基类的拷贝

5、总结



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

      对象不存在,且用别的对象来初始化,就是拷贝构造函数;

      对象存在,用别的对象来给它赋值,就是赋值函数。


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

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

相关文章

硬件有没有35岁危机?

大家好&#xff0c;我篇文章是我的朋友记得诚写的&#xff0c;分享给大家&#xff01;35岁危机指的是&#xff0c;很多企业停止招聘35岁及以上的员工&#xff0c;甚至会裁掉年纪大的人。我们经常说程序员&#xff0c;说互联网从业者有35岁危机。这个危机一方面是自己的焦虑&…

深入理解ES6之迭代器与生成器

迭代器 迭代器 iterator&#xff0c;在 Javascript 中&#xff0c;迭代器是一个对象&#xff08;也可称作为迭代器对象&#xff09;&#xff0c;它提供了一个 next() 方法&#xff0c;用来返回迭代序列中的下一项。 next 方法的定义&#xff0c;next 方法是一个函数&#xff0c…

强大的独立日期选择器(date picker)插件 - Kalendae

日期&#xff1a;2012-4-16 来源&#xff1a;GBin1.com 在线演示 本地下载 今天分享一个独立的日期选择插件Kalendae&#xff0c;Kalendae是 一个强大健壮的独立日期选择器。如果你不想使用重量的jQuery UI类库的话&#xff0c;这个插件肯定是一个不错的备选。Kalendae包含了…

35家名企嵌入式/软件秋招岗位等你来!

大家好&#xff0c;我是写代码的篮球球痴。前两天一个小伙伴在微信让我帮忙推荐嵌入式招聘岗位。我实话实说&#xff0c;是有认识的人让我推荐简历的&#xff0c;但是相对于专业的招聘网站&#xff0c;我手里能拿出来的岗位实在是少之又少&#xff0c;专业的招聘网站会对不同的…

javascript实现平滑的锚点

转到源文件 转载于:https://www.cnblogs.com/flat_peach/archive/2008/07/22/1248653.html

Windows下窗口的生命周期

程序的执行 当执行Windows程序的时候&#xff0c;加载器加载该程序&#xff0c;然后调用C startup code&#xff0c;再调用程序中WinMain()。 初始化 WinMain函数首先通过CreateWindow函数创建窗口&#xff0c;并对窗口进行初始化配置; 消息的处理 程序通过循环GetMessage函数不…

尝试梳理下ARM处理器的发展历史

大家好&#xff0c;这篇文章是我的朋友Michael Yao写的&#xff0c;我觉得非常不错&#xff0c;分享给大家。1. 前言本文尝试简单梳理下ARM处理器的发展历史、架构的演进&#xff0c;包括不同处理器的应用方向&#xff0c;但我们重点还是围绕Cortex-A系列展开&#xff0c;也会介…

linux下的CPU、内存、IO、网络的压力测试

一、对CPU进行简单测试&#xff1a; 1、通过bc命令计算特别函数 例&#xff1a;计算圆周率 echo "scale5000; 4*a(1)" | bc -l -q MATH LIBRARY If bc is invoked with the -l option, a math library is preloaded and the default scale is set to 20.…

清空sql server日志

1、打开查询分析器&#xff0c;输入命令DUMP TRANSACTION 数据库名 WITH NO_LOG2、打开企业管理器--右键你要压缩的数据库--所有任务--收缩数据库--收缩文件--选择日志文件--在收缩方式里选择收缩至: ,这里会给出一个允许收缩到的最小M数,直接输入这个数,确定就可以了。3、在数…

find与grep的区别

find 功能&#xff1a;在目录结构中搜索文件&#xff0c;并执行指定的操作。此命令提供了相当多的查找条件&#xff0c;功能很强大。 语法: find 查找位置 匹配文件名 说明&#xff1a;find命令从指定的起始目录开始&#xff0c;递归地搜索其各个子目录&#xff0c;查找满足寻…

给你这张图,你能搜索到来历吗

如果我们想让搜索引擎帮我们找到这张图的来历&#xff0c;可能吗&#xff1f; 这是目前搜索引擎做不到的。如果能做到呢&#xff1f; 转载于:https://www.cnblogs.com/shangge/archive/2008/07/23/1249767.html

晚上读内核代码

最近因为要解决一个bug&#xff0c;需要在内核的f_hid.c里面做一些适配&#xff0c;需要把这部分代码研究透彻。在这几天之前我是根本不知道什么是In端点&#xff0c;Out端点&#xff0c;以及什么是endpoint 0的&#xff0c;而且最近的一段时间&#xff0c;我一直是在写应用方面…

我的家庭私有云计划-16

嗯&#xff0c;上午测试S2S的稳定性&#xff0c;改掉几个bug。还挺忙的。这会儿让机器跑测试去&#xff0c;腾出点时间&#xff0c;我们接着聊。 呵呵&#xff0c;昨天哪&#xff0c;已经有朋友批评我了&#xff0c;说我有点贪大求全&#xff0c;这个论坛什么的没必要自己实现&…

采用存储复制方式同步数据,实现数据库安全升级

2017年年初&#xff0c;海天起点为某省中行机房搬迁工作保驾护航。在机房搬迁过程中发现有多套数据库需要升级到11g&#xff0c;但由于有些数据库比较老旧&#xff0c;升级过程不能一次性完成&#xff0c;需要先升级到一个中间版本&#xff0c;再升级到最终目标版本。 但由于银…

【转】娱乐八卦——关于孙悟空的授业师傅的分析

一&#xff0e;《西游记》和《封神演义》联系密切  提起明朝著作《西游记》和《封神演义》&#xff0c;我想你一定不会陌生。从两部作品中派生出来的《孙悟空大闹天宫》、《哪吒闹海》等神话就更是家喻户晓&#xff0c;人人皆知了。两部小说虽然讲述的是不同朝代的故事&#…

为啥要xargs

转载&#xff1a;http://blog.csdn.net/yhcharles/article/details/44875865 为啥要xargs? 通常linux命令可以用|首尾相连&#xff0c;上一个命令的 stdout 连接到下一个命令的 stdin。但是有些命令&#xff0c;比如ls、rm等&#xff0c;是从命令行参数接受输入的。这时候如果…

Linux进程调度与性能优化 | 真货

作者简介&#xff1a;张毅峰&#xff0c;某主机厂架构师。一、eBPF安全可观测性的前景展望本次分享将从监控和可观测性、eBPF安全可观测性分析、内核安全可观测性展望三个方面展开。1.监控(Monitoring)vs可观测性(Observability)从上图可以看到&#xff0c;监控只是可观测性的冰…

教师生涯由此开始

招聘会时间&#xff1a;2012-03-28 09:00 招聘会地址&#xff1a;就业办507 为加强教师队伍建设&#xff0c;建设教育强县&#xff0c;经研究&#xff0c;我县决定面向全国“985”、“211”工程高等师范院校应届毕业生选聘普通高中和初中教师38名。现将有关事项公告如下&#x…

AutoCAD .net 二次开发官方教程及源码C#版(4)-(源码下载)

示例源码第4章 数据库基础2:添加自定义数据在这一章中&#xff0c;我们将创建一个新的字典对象&#xff0c;它用来表示我们雇员就职的 ‘Acme 公司‘&#xff08;呵呵&#xff0c;当然是虚构的一家公司&#xff09;的部门。这个“部门”字典对象将包含一个表示部门经理的记录。…

二叉树前序、中序、后序遍历非递归写法的透彻解析

前言 在前两篇文章二叉树和二叉搜索树中已经涉及到了二叉树的三种遍历。递归写法&#xff0c;只要理解思想&#xff0c;几行代码。可是非递归写法却很不容易。这里特地总结下&#xff0c;透彻解析它们的非递归写法。其中&#xff0c;中序遍历的非递归写法最简单&#xff0c;后序…