当前位置: 首页 > 编程日记 > 正文

复制构造函数(拷贝构造函数)

也许很多C++的初学者都知道什么是构造函数,但是对复制构造函数(copy constructor)却还很陌生。对于我来说,在写代码的时候能用得上复制构造函数的机会并不多,不过这并不说明复制构造函数没什么用,其实复制构造函数能解决一些我们常常会忽略的问题。
       为了说明复制构造函数作用,我先说说我们在编程时会遇到的一些问题。对于C++中的函数,我们应该很熟悉了,因为平常经常使用;对于类的对象,我们也很熟悉,因为我们也经常写各种各样的类,使用各种各样的对象;对于指针的操作,我们也不陌生吧?嗯,如果你还不了解上面三个概念的话,我想这篇文章不太适合你,不过看看也无碍^_^。我们经常使用函数,传递过各种各样的参数给函数,不过把对象(注意是对象,而不是对象的指针或对象的引用)当作参数传给函数的情况我们应该比较少遇见吧,而且这个对象的构造函数还涉及到一些内存分配的操作。嗯,这样会有什么问题呢?
       把参数传递给函数有三种方法,一种是值传递,一种是传地址,还有一种是传引用。前者与后两者不同的地方在于:当使用值传递的时候,会在函数里面生成传递参数的一个副本,这个副本的内容是按位从原始参数那里复制过来的,两者的内容是相同的。当原始参数是一个类的对象时,它也会产生一个对象的副本,不过在这里要注意。一般对象产生时都会触发构造函数的执行,但是在产生对象的副本时却不会这样,这时执行的是对象的复制构造函数。为什么会这样?嗯,一般的构造函数都是会完成一些成员属性初始化的工作,在对象传递给某一函数之前,对象的一些属性可能已经被改变了,如果在产生对象副本的时候再执行对象的构造函数,那么这个对象的属性又再恢复到原始状态,这并不是我们想要的。所以在产生对象副本的时候,构造函数不会被执行,被执行的是一个默认的构造函数。当函数执行完毕要返回的时候,对象副本会执行析构函数,如果你的析构函数是空的话,就不会发生什么问题,但一般的析构函数都是要完成一些清理工作,如释放指针所指向的内存空间。这时候问题就可能要出现了。假如你在构造函数里面为一个指针变量分配了内存,在析构函数里面释放分配给这个指针所指向的内存空间,那么在把对象传递给函数至函数结束返回这一过程会发生什么事情呢?首先有一个对象的副本产生了,这个副本也有一个指针,它和原始对象的指针是指向同块内存空间的。函数返回时,对象的析构函数被执行了,即释放了对象副本里面指针所指向的内存空间,但是这个内存空间对原始对象还是有用的啊,就程序本身而言,这是一个严重的错误。然而错误还没结束,当原始对象也被销毁的时候,析构函数再次执行,对同一块系统动态分配的内存空间释放两次是一个未知的操作,将会产生严重的错误。
       上面说的就是我们会遇到的问题。解决问题的方法是什么呢?首先我们想到的是不要以传值的方式来传递参数,我们可以用传地址或传引用。没错,这样的确可以避免上面的情况,而且在允许的情况下,传地址或传引用是最好的方法,但这并不适合所有的情况,有时我们不希望在函数里面的一些操作会影响到函数外部的变量。那要怎么办呢?可以利用复制构造函数来解决这一问题。复制构造函数就是在产生对象副本的时候执行的,我们可以定义自己的复制构造函数。在复制构造函数里面我们申请一个新的内存空间来保存构造函数里面的那个指针所指向的内容。这样在执行对象副本的析构函数时,释放的就是复制构造函数里面所申请的那个内存空间。
       除了将对象传递给函数时会存在以上问题,还有一种情况也会存在以上问题,就是当函数返回对象时,会产生一个临时对象,这个临时对象和对象的副本性质差不多。
拷贝构造函数,经常被称作X(X&),是一种特殊的构造函数,他由编译器调用来完成一些基于同一类的其他对象的构件及初始化。它的唯一的一个参数(对象的引用)是不可变的(因为是const型的)。这个函数经常用在函数调用期间于用户定义类型的值传递及返回。拷贝构造函数要调用基类的拷贝构造函数和成员函数。如果可以的话,它将用常量方式调用,另外,也可以用非常量方式调用。 
在C++中,下面三种对象需要拷贝的情况。因此,拷贝构造函数将会被调用。 
1). 一个对象以值传递的方式传入函数体 
2). 一个对象以值传递的方式从函数返回 
3). 一个对象需要通过另外一个对象进行初始化 
以上的情况需要拷贝构造函数的调用。如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说,初始化和赋值的不同含义是构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作赋共同实现的。描述拷贝构造函数和赋值运算符的异同的参考资料有很多。 
拷贝构造函数不可以改变它所引用的对象,其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环。 
除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。换句话说,你从函数返回得到的只是对象的一份拷贝。但是同样的,拷贝构造函数被正确的调用了,你不必担心。 
如果在类中没有显式的声明一个拷贝构造函数,那么,编译器会私下里为你制定一个函数来进行对象之间的位拷贝(bitwise copy)。这个隐含的拷贝构造函数简单的关联了所有的类成员。许多作者都会提及这个默认的拷贝构造函数。注意到这个隐式的拷贝构造函数和显式声明的拷贝构造函数的不同在于对于成员的关联方式。显式声明的拷贝构造函数关联的只是被实例化的类成员的缺省构造函数除非另外一个构造函数在类初始化或者在构造列表的时候被调用。 
拷贝构造函数是程序更加有效率,因为它不用再构造一个对象的时候改变构造函数的参数列表。设计拷贝构造函数是一个良好的风格,即使是编译系统提供的帮助你申请内存默认拷贝构造函数。事实上,默认拷贝构造函数可以应付许多情况。
附另外一篇关于复制构造函数的文章:

对一个简单变量的初始化方法是用一个常量或变量初始化另一个变量,例如:


int m = 80;int n = m;

我们已经会用构造函数初始化对象,那么我们能不能象简单变量的初始化一样,直接用一个对象来初始化另一个对象呢?答案是肯定的。我们以前面定义的Point类为例:


 Point pt1(15, 25);Point pt2 = pt1;

后一个语句也可以写成:
  Point pt2( pt1);
它是用pt1初始化pt2,此时,pt2各个成员的值与pt1各个成员的值相同,也就是说,pt1各个成员的值被复制到pt2相应的成员当中。在这个初始化过程当中,实际上调用了一个复制构造函数。当我们没有显式定义一个复制构造函数时,编译器会隐式定义一个缺省的复制构造函数,它是一个内联的、公有的成员,它具有下面的原型形式:
 Point:: Point (const Point &);


可见,复制构造函数与构造函数的不同之处在于形参,前者的形参是Point对象的引用,其功能是将一个对象的每一个成员复制到另一个对象对应的成员当中。
  虽然没有必要,我们也可以为Point类显式定义一个复制构造函数:
Point:: Point (const Point &pt){xVal=pt. xVal;yVal=pt. yVal;} 


  如果一个类中有指针成员,使用缺省的复制构造函数初始化对象就会出现问题。为了说明存在的问题,我们假定对象A与对象B是相同的类,有一个指针成员,指向对象C。当用对象B初始化对象A时,缺省的复制构造函数将B中每一个成员的值复制到A的对应的成员当中,但并没有复制对象C。也就是说,对象A和对象B中的指针成员均指向对象C,实际上,我们希望对象C也被复制,得到C的对象副本D。否则,当对象A和B销毁时,会对对象C的内存区重复释放,而导致错误。为了使对象C也被复制,就必须显式定义复制构造函数。下面我们以string类为例说明,如何定义这个复制构造函数。

class String
{public:String(); //构造函数String(const String &s); //复制构造函数~String(); //析构函数// 接口函数void set(char const *data);char const *get(void);private:char *str; //数据成员ptr指向分配的字符串
};
String ::String(const String &s)
{str = new char[strlen(s.str) + 1];strcpy(str, s.str);
}


我们也常用无名对象初始化另一个对象,例如:

Point pt = Point(10, 20);
  类名直接调用构造函数就生成了一个无名对象,上式用左边的无名对象初始化右边的pt对象。
  构造函数被调用通常发生在以下三种情况,第一种情况就是我们上面看到的:用一个对象初始化另一个对象时;第二种情况是当对象作函数参数,实参传给形参时;第三种情况是程序运行过程中创建其它临时对象时。下面我们再举一个例子,就第二种情况和第三种情况进行说明:

Point foo(Point pt) { … return pt;}void main(){Point pt1 = Point(10, 20);Point pt2;…pt2=foo(pt);…}


  在main函数中调用foo函数时,实参pt传给形参pt,将实参pt复制给形参pt,要调用复制构造函数,当函数foo返回时,要创建一个pt的临时对象,此时也要调用复制构造函数。

缺省的复制构造函数
  在类的定义中,如果没有显式定义复制构造函数,C++编译器会自动地定义一个缺省的复制构造函数。下面是使用复制构造函数的一个例子:

例10-12
#include <iostream.h>
#include <string.h>
class withCC
{public:withCC(){}withCC(const withCC&){cout<<"withCC(withCC&)"<<endl;}
};
class woCC
{enum{bsz = 100};char buf[bsz];
public:woCC(const char* msg = 0){memset(buf, 0, bsz);if(msg) strncpy(buf, msg, bsz);}void print(const char* msg = 0)const{if(msg) cout<<msg<<":";cout<<buf<<endl;}
};
class composite
{withCC WITHCC;woCC WOCC;
public:composite() : WOCC("composite()"){}void print(const char* msg = 0){WOCC.print(msg);}
};
void main()
{composite c;c.print("contents of c");cout<<"calling composite copy-constructor"<<endl;composite c2 = c;c2.print("contents of c2");
}

类withCC有一个复制构造函数,类woCC和类composite都没有显式定义复制构造函数。如果在类中没有显式定义复制构造函数,则编译器将自动地创建一个缺省的构造函数。不过在这种情况下,这个构造函数什么也不作。
  类composite既含有withCC类的成员对象又含有woCC类的成员对象,它使用无参的构造函数创建withCC类的对象WITHCC(注意内嵌的对象WOCC的初始化方法)。
  在main()函数中,语句:
  composite c2 = c;
通过对象C初始化对象c2,缺省的复制构造函数被调用。
  最好的方法是创建自己的复制构造函数而不要指望编译器创建,这样就能保证程序在我们自己的控制之下。

转载于:https://www.cnblogs.com/lgh1992314/archive/2012/11/18/5835307.html

相关文章:

VMware上实现LVS负载均衡(NAT)

本文LVS的实现方式採用NAT模式。关于NAT的拓扑图请參照我的上一篇文章。本文纯粹实验。NAT在生产环境中不推荐使用。原因是Load Balancereasy成为瓶颈&#xff01; 1.VMware9上安装CentOS-6.5-x86_64-minimal版 2.安装完毕后将其hostname设置为LVS-master hostname LVS-master …

java se13安装教程_在Linux发行版中安装Java 13/OpenJDK 13的方法

本文介绍在Linux发行版Ubuntu 18.04/16.04、Debian 10/9、CentOS 7/8、Fedora 31/30/29中安装Java 13/OpenJDK 13、Java SE Development Kit 13的方法。在Ubuntu 18.04/16.04、Debian 10/9、CentOS 7/8、Fedora 31/30/29中安装OpenJDK 13访问JDK 13版本页面以下载最新的版本&am…

Java api 入门教程 之 JAVA的IO处理

IO是输入和输出的简称&#xff0c;在实际的使用时&#xff0c;输入和输出是有方向的。就像现实中两个人之间借钱一样&#xff0c;例如A借钱给B&#xff0c;相对于A来说是借出&#xff0c;而相对于B来说则是借入。所以在程序中提到输入和输出时&#xff0c;也需要区分清楚是相对…

如何编辑PDF文件,PDF编辑器如何使用

如何编辑PDF呢&#xff1f;其实大多数人都不知道该如何下手&#xff0c;部分人会选择将PDF文件转换成Word然后进行编辑&#xff0c;其实这种方法比较麻烦&#xff0c;大大拉低了我们的工作效率。如果想要提高工作效率更加快速的编辑PDF文件&#xff0c;就可以选择迅捷PDF编辑器…

hdu 4366 Card Collector (容斥原理)

http://acm.hdu.edu.cn/showproblem.php?pid4336题意&#xff1a;有 n 张卡片 &#xff0c;每张卡片出现的 概率 是 pi 每包至多有 一张卡片 &#xff0c;也有可能没有 卡片 。求 需要买多少包 才能集齐 n 张卡片 &#xff0c;求包数的 期望 。题解 &#xff1a; 容斥原理…

java语言二维数组转置_java实现二维数组转置的方法示例

本文实例讲述了java实现二维数组转置的方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;这里在文件中创建Test2、Exchange、Out三个类在Exchange类中编写exchange()方法&#xff0c;在方法中创建两个数组arraryA、arraryB&#xff0c;arraryB[j][i]arraryA[i][j]实…

使用Apache cxf 和Spring在Tomcat下发布Webservice指南

转载 http://blog.csdn.net/zhangzhaokun/article/details/4750021 最近学习了如何使用apache cxf和Spring发布webservice&#xff0c;虽然网上的资料很多&#xff0c;但是没有一个文档可以让读者按照操作步骤来实现完整的发布流程&#xff0c;都需要多篇文件杂合在一起&#x…

srcache_nginx redis 构建缓存系统应用一例

为什么80%的码农都做不了架构师&#xff1f;>>> srcache_nginx模块相关参数介绍&#xff0c;可以参见 《memc_nginxsrcache_nginxmemcached构建透明的动态页面缓存》。 redis是一种高效的key-value存储。 下面举一例应用&#xff0c;看配置&#xff1a; upstream r…

mysql 删除 修改密码_Mysql数据库root密码忘记了,如何在不删除Mysql的情况下修改密码...

1.cmd中使用 net stop mysql 命令停掉正在运行的mysql 数据库。2.在本地中复制Mysql数据库的安装路径一直到bin路径下。3.到cmd执行 "pushd 步骤2复制路径" 的命令&#xff0c;就会到Mysql数据库安装的bin路径下。4.紧接著执行 mysqld --skip-grant-tables 命令…

通用权限管理系统组件 (GPM - General Permissions Manager) 权限管理以前我们都是自己开发,可是到下一个系统又不适用,又改,加上人员流动大,管理很混乱...

为什么80%的码农都做不了架构师&#xff1f;>>> 权限管理以前我们都是自己开发&#xff0c;可是到下一个系统又不适用&#xff0c;又改&#xff0c;加上人员流动大&#xff0c;管理很混乱 Ψ吉日嘎拉 采用通用权限管理系统&#xff0c;这些烦恼就少了很多了&#x…

【小白的CFD之旅】16 流程

那天听了小牛师兄关于CFD应用的四种境界的说法后&#xff0c;小白发现自己连第一种境界都算不上&#xff0c;自己对于CFD还只是停留在做了少数几个案例的基础上&#xff0c;可以说是对其一无所知。不过小白不是那种遇到挫折就退缩的人&#xff0c;他决定沿着黄师姐的方法从软件…

XWiki 4.3 正式版发布

XWiki 4.3 正式版发布了&#xff0c;工作空间、扩展管理器、分发向导和 REST API 做了很多改进&#xff0c;改进了翻译和新的体验的 Solr 搜索。 XWiki是一个由Java编写的基于LGPL协议发布的开源wiki和应用平台。它的开发平台特性允许创建协作式Web应用&#xff0c;同时也提供了…

新建异常并处理java_java – 动态创建异常的工厂模式

我创建了Exception xml并动态创建并抛出异常.com.package.CheckedExceptionChecked Exception Messagecom.package.UnCheckedExceptionUnChecked Exception Message我根据异常键使用反射动态创建异常对象.public static void throwException(final String key) throws CheckedE…

React navtive

http://www.linuxidc.com/Linux/2015-09/123239.htm 转载于:https://www.cnblogs.com/chenzhenfj/p/5203685.html

c# Pdf 转换图片

1&#xff0c;引入 dll itextsharp.dll、 PDFView.dll、 把 gsdll32.dll 拷贝在项目 bin目录下 &#xff0c;注意&#xff1a;它不能 直接引用 直接上代码&#xff1a; 1 /// <summary>2 /// 将PDF 相应的页转换为图片3 /// </summary>4 …

Entity Framework 约定

约定&#xff0c;类似于接口&#xff0c;是一个规范和规则&#xff0c;使用Code First 定义约定来配置模型和规则。在这里约定只是记本规则&#xff0c;我们可以通过Data Annotaion或者Fluent API来进一步配置模型。约定的形式有如下几种&#xff1a; 类型发现约定主键约定关系…

java用构造方法定义book类_JAVA基础学习之路(三)类定义及构造方法

类的定义及使用一&#xff0c;类的定义classBook {//定义一个类intprice;//定义一个属性intnum;public static int getMonney(int price, intnum) {//定义一个方法return price*num;}}public classtest2 {public static voidmain(String args[]) {Book monney newBook();//声明…

Linux下如何查看文档的内容

查看文档内容的命令有&#xff1a;cat tac head nl tail more less odcat命令显示文档的全部内容&#xff0c;当文档较大的时候只显示最后的部分&#xff0c;所以cat命令适合查看内容较少的文档。可加选项-n显示行数(此时空白行也会显示行号)。-b空白行则不显示行号。tac与cat显…

Java中getResourceAsStream的用法

Java中getResourceAsStream的用法 首先&#xff0c;Java中的getResourceAsStream有以下几种&#xff1a;1. Class.getResourceAsStream(String path) &#xff1a; path 不以’/开头时默认是从此类所在的包下取资源&#xff0c;以’/开头则是从 ClassPath根下获取。其只是通过p…

Asp.Net MVC3 简单入门详解过滤器Filter

为什么80%的码农都做不了架构师&#xff1f;>>> 前言 在开发大项目的时候总会有相关的AOP面向切面编程的组件&#xff0c;而MVC&#xff08;特指&#xff1a;Asp.Net MVC&#xff0c;以下皆同&#xff09;项目中不想让MVC开发人员去关心和写类似身份验证&#xff0…

mysql 锁语句_mysql-笔记 事务 锁 语句

Start Transaction 或 begin [work] 开始一个事务&#xff0c;开始一个事务&#xff0c;引起其他未提交的事务提交&#xff0c;引起表锁释放commit 提交事务&#xff0c;永久修改rollback 回滚事务&#xff0c;撤消修改set autocommit 在当前会话状态下 启用或不启用 autocommi…

【收藏】Java多线程/并发编程大合集

&#xff08;一&#xff09;、【Java并发编程】并发编程大合集-兰亭风雨 【Java并发编程】实现多线程的两种方法 【Java并发编程】线程的中断 【Java并发编程】正确挂起、恢复、终止线程 【Java并发编程】守护线程和线程阻塞 【Java并发编程】Volatile关键字&#xff08;上&…

《深入理解Android:Wi-Fi,NFC和GPS》章节连载[节选]--第六章 深入理解wi-Fi Simple Configuration...

为什么80%的码农都做不了架构师&#xff1f;>>> 首先感谢各位兄弟姐妹们的耐心等待。本书预计在4月上市发售。从今天开始&#xff0c;我将在博客中连载此书的一些内容。注意&#xff0c;此处连载的是未经出版社编辑的原始稿件&#xff0c;所以样子会有些非专业。 …

iOS 的本地化使用和创建过程

在使用本地化语言之前&#xff0c;来看看本地化语言文件内容的结构&#xff08;这里我以Chinese为例&#xff09;&#xff1a;"Cancel""取消";"OK""确定";"Tip""信息提示";"Login Faild""登陆失败…

MySQL中改变相邻学生座位_力扣——换座位(数据库的题

小美是一所中学的信息科技老师&#xff0c;她有一张 seat 座位表&#xff0c;平时用来储存学生名字和与他们相对应的座位 id。其中纵列的 id 是连续递增的小美想改变相邻俩学生的座位。你能不能帮她写一个 SQL query 来输出小美想要的结果呢&#xff1f;示例&#xff1a;------…

AnsiToUtf8 和 Utf8ToAnsi

在服务端数据库的处理当中&#xff0c;涉及中文字符的结构体字段&#xff0c;需要转为Utf8后再存储到表项中。从数据库中取出包含中文字符的字段后&#xff0c;如果需要保存到char *类型的结构体成员中&#xff0c;需要转为Ansi后再保存。从数据库中取出类型数字的字段后&#…

常见面试题:重写strcpy() 函数原型

已知strcpy函数的原型是 char* strcpy(char* strDest,const char* strSrc); 1.不调用库函数&#xff0c;实现strcpy函数 2.解释为什么要返回char*; 1.strcpy的实现代码 char* strcpy(char* strDest,const char* strSrc) { if((strDest NULL) || (strSrc NULL)) //[1] thro…

mongodb 系列 ~ journal日志畅谈

一 简介 我们来聊聊Journal日志二 核心观点 WAL 日志先行策略三 开启journal流程 在开启journal的系统中&#xff0c;写操作从请求到写入磁盘共经历5个步骤&#xff0c;在serverStatus()中已经列出各个步骤消耗的时间。 1 Write to privateView 2 prepLogBuffer …

java striptrailingzeros_java – 为什么不BigDecimal.stripTrailingZeros()总是删除所有尾随零?...

我做了以下事情MathContext context new MathContext(7, RoundingMode.HALF_UP);BigDecimal roundedValue new BigDecimal(value, context);// Limit decimal placestry {roundedValue roundedValue.setScale(decimalPlaces, RoundingMode.HALF_UP);} catch (NegativeArrayS…

box-shadow属性

一、定义和用法 box-shadow属性 向框添加一个或多个阴影。 二、语法 box-shadow: h-shadow v-shadow blur spread color inset; h-shadow必需。水平阴影的位置。允许负值。 v-shadow必需。垂直阴影的位置。允许负值。 blur可选。模糊距离。 spread可选。阴影的尺寸。 color可选…