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

2023-11-11

也许很多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类为例说明,如何定义这个复制构造函数。

例题 例10-11
  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,缺省的复制构造函数被调用。
  最好的方法是创建自己的复制构造函数而不要指望编译器创建,这样就能保证程序在我们自己的控制之下。

 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

复制构造函数(拷贝构造函数) 的相关文章

  • 起订量要求?违背了目的?

    是否需要虚拟化您想要模拟的所有属性访问器就违背了模拟的目的 我的意思是 如果我必须修改我的对象并虚拟化我想要模拟的每个访问器 我难道不能继承我的类并自己模拟它吗 你的问题非常有效 但如果你仔细想想 没有其他方法可以模拟课程 如果你采用一个接
  • Accept() 是线程安全的吗?

    我目前正在用 C 语言为我正在做的课程编写一个简单的网络服务器 我们的一项要求是实现一个线程池来使用 pthread 处理连接 我知道我将如何粗略地执行此操作 在主线程中调用accept并将文件描述符传递给freee线程 但是我的朋友建议了
  • 为什么 VB.NET 和 C# 中针对值检查 null 存在差异?

    In VB NET http en wikipedia org wiki Visual Basic NET有时候是这样的 Dim x As System Nullable Of Decimal Nothing Dim y As System
  • 键盘加速器在 UWP 应用中停止工作

    我正在尝试将键盘加速器添加到 UWP 应用程序中的 CommandBar 菜单项 当应用程序启动时 这工作正常 但在我第一次打开溢出菜单后 加速器停止工作 这似乎不会发生在主要命令 菜单之外 上 只有溢出菜单内的辅助命令才会发生 此外 单击
  • 使用API​​隐藏程序标题栏

    它可以使用 c 和 windows api 删除窗口控制台标题栏 如果是的话如何 请 这个简单的应用程序隐藏并显示其所在控制台的标题栏 它会立即将控制台标题更改为 guid 以查找窗口句柄 然后 它使用 ToggleTitleBar 使用找
  • 在Application_AquireRequestState事件中用POST数据重写Url

    我有一个在其中注册路线的代码Application AcquireRequestState应用程序的事件 注册路由后 我会在 Http 运行时缓存中设置一个标志 这样我就不会再次执行路由注册代码 在此事件中注册路线有特定原因Applicat
  • 静态类变量与外部变量相同,只是具有类作用域吗?

    在我看来 静态类变量与外部变量相同 因为你只需要declare它在static int x extern int x语句 并在其他地方实际定义它 通常在 cpp 文件中 静态类变量 h file class Foo static int x
  • 如何在不实例化一个类的情况下检查它是否继承了另一个类? [复制]

    这个问题在这里已经有答案了 假设我有一个如下所示的类 class Derived some inheritance stuff here 我想在我的代码中检查类似的内容 Derived is SomeType 但看起来像is运算符需要 De
  • 无法从 Web api POST 读取正文数据

    我正在尝试从新的 Asp Net Web Api 中的请求中提取一些数据 我有一个像这样的处理程序设置 public class MyTestHandler DelegatingHandler protected override Syst
  • 有没有办法使用 i387 fsqrt 指令获得正确的舍入?

    有没有办法使用 i387 fsqrt 指令获得正确的舍入 除了改变精确模式在 x87 控制字中 我知道这是可能的 但这不是一个合理的解决方案 因为它存在令人讨厌的重入型问题 如果 sqrt 操作中断 精度模式将出错 我正在处理的问题如下 x
  • 如何使用 wpf webbrowser 将数据发布到 Web 服务器

    我想从数据库获取数据并使用它来让用户登录到网站 我有一个包含 Web 浏览器控件的 wpf 页面 我有这样的代码 用于将用户登录到用 php 编写的网站
  • 单线程公寓问题

    从我的主窗体中 我调用以下命令来打开一个新窗体 MyForm sth new MyForm sth show 一切都很好 但是这个表单有一个组合框 当我将其 AutoCompleteMode 切换为建议和追加时 我在显示表单时遇到了这个异常
  • dropdownlist DataTextField 由属性组成?

    有没有一种方法可以通过 C 使 asp net 中的下拉列表的 datatextfield 属性由对象的多个属性组成 public class MyObject public int Id get set public string Nam
  • 如何用 C 语言练习 Unix 编程?

    经过五年的专业 Java 以及较小程度上的 Python 编程并慢慢感觉到我的计算机科学教育逐渐消失 我决定要拓宽我的视野 对世界的一般用处 并做一些 对我来说 感觉更重要的事情就像我真的对机器有影响一样 我选择学习 C 和 Unix 编程
  • 使用 foreach 循环和 XmlNodeList C# 将新节点附加到节点列表

    目前我处理的是这样的XML类型 XML FILE http 20drive google com open id 0By5BxgNi9eGcRldxcEZNU0FDTzQ 参考XML文件 我想检查一个节点 如果找不到该节点 我必须将该节点附
  • 如何访问窗口?

    我正在尝试使用其句柄访问特定窗口 即System IntPtr value Getting the process of Visual Studio program var process Process GetProcessesByNam
  • TPL 数据流块下游如何获取源生成的数据?

    我正在使用 TPL Dataflow 处理图像 我收到处理请求 从流中读取图像 应用多次转换 然后将生成的图像写入另一个流 Request gt Stream gt Image gt Image gt Stream 为此 我使用块 Buff
  • #pragma pack(16) 和 #pragma pack(8) 的效果总是相同吗?

    我正在尝试使用来对齐数据成员 pragma pack n http msdn microsoft com en us library 2e70t5y1 28v vs 100 29 aspx 以下面为例 include
  • 无需时间即可生成随机字符串?

    我知道如何使用 Runes 和播种 rand Init 在 go 中生成随机字符串time UnixNano 我的问题是 是否可以 使用 stdlib 在不使用当前时间戳 安全 的情况下播种 rand 此外 我问 因为仅仅依靠时间来为敏感操
  • 新的 .NET 6 控制台模板中的 C# 函数重载不起作用

    我在尝试重载该函数时遇到错误Print object in the 新的 NET 6 C 控制台应用程序模板 https learn microsoft com en us dotnet core tutorials top level t

随机推荐

  • 理清概念:同步与异步

    广义的同步与异步 在广义上 同步和异步是描述两个或多个事件 操作或进程之间的关系 同步意味着事件 操作或进程是有序的 一个操作必须在另一个操作完成后开始执行 异步则意味着事件 操作或进程是独立的 可以在不等待其他操作完成的情况下开始执行 同
  • CentOS7 RPM包管理功能总结及示例

    RPM是红帽软件包管理器 主要用来对RPM包进行安装 升级 卸载 查询 校验和数据库维护的管理操作 安装 语法 rpm i install install options PACKAGE FILE i 安装一个新包 PACKAGE FILE
  • 关于C#中get和set

    转自 http blog sina com cn s blog 82526aa60100txtx html 在程序中经常碰到get set 不甚明白 在网上查询时也说的迷迷糊糊 所以整理下 以学的明白透彻点 有两个类person publi
  • stm32串口驱动和esp8266的使用

    写在前面 本文并不对相关知识进行讲解 只是这次的实验课要实现的任务有些复杂 我也踩了一些坑 对代码实现思路进行复现和记录 并不是技术科普性文章 基础知识还是要自己有所掌握 1 stm32的串口通讯 开发板 stm32f407zgt6课程学习
  • 一文搞懂深度优先搜索(DFS)

    一 原理 深度优先搜索再得到一个新节点时立即对新节点进行遍历 从节点 0 出发开始遍历 得到到新节点 6 时 立马对新节点 6 进行遍历 得到新节点 4 如此反复以这种方式遍历新节点 直到没有新节点了 此时返回 返回到根节点 0 的情况是
  • 数据结构与算法(三):栈与队列

    一 栈 1 基本概念 2 栈的顺序存储结构 3 两栈共享空间 4 栈的链式存储结构 5 栈的应用 递归 二 队列 1 基本概念 2 队列的顺序存储结构 三 总结 上一篇 数据结构与算法 二 线性表 中介绍了数据结构中线性表的两种不同实现 顺
  • 反转链表+交换两个链表的节点

    目录 编辑 一 反转链表 1 题目描述 2 例子 3 题目接口 4 分析以及解题代码 1 迭代法 2 递归写法 二 两两交换两个链表中的节点 1 题目描述 2 例子 3 题目接口 4 题目分析以及解法 一 反转链表 1 题目描述 首先来看看
  • Chapter One : 开启 Python 之旅

    目录 1 计算机的组成 了解 2 计算机如何处理程序的 了解 3 编程语言 了解 4 了解 Python 4 1 Python 的版本 4 2 Python 的应用领域 5 搭建 Python 开发环境 5 1 下载并安装 Python 2
  • linux c 打印调用某函数的文件名与行

    只做记录 供自己翻 http www voidcn com article p ruuoijoc mn html
  • win全盘重分- DiskGenius

    因为最近找不到了太好的素材 来整理一下全盘重分的素材 全盘分区最好的是制作U盘启动器 在U盘的PE里进行分区 使用工具是相当简单的 PE使用的是冰封U启动 第一步打开DiskGenius软件 算是系统重装分区用的最多的也是最普遍的工具之一
  • 【概率论与数理统计】猴博士 笔记 p26-28 F、f的性质、一、二维连续型求期望、方差

    F f的性质 做法 上述公式的原理 做一些题目来练习套公式 例1 解 P X 2 F
  • Pycharm中如何配置已有的环境

    这篇是关于在pycharm中配置已经在anaconda中存在的虚拟环境 1 File settings 2 在设置弹窗中选择Project Interpreter 然后点击add 3 根据图操作 4 随后找到Anaconda中envs中想要
  • 2022年苹果开发者账号/AppleID如何更改绑定的手机号

    2022年苹果开发者账号 AppleID如何更改绑定的手机号 1 打开Apple ID 网址 登陆进去 https appleid apple com cn 2 点击编辑选项 将原来的手机号删除 然后添加受信任号码 点击完成 修改密码 修改
  • sendto: Network is unreachable问题的解决

    在用busybox生成根文件系统之后 进入系统ping外网是不能用的 因此需要修改一下几个文件 配置ip为固定 并且开机自动启动网卡 在 etc init d rcS中添加一下内容 网络开机自启动设置 ifconfig eth0 up ud
  • RabbitMQ整合SpringBoor以及RabbitMQ的延迟队列

    延迟队列的概念 前面介绍了死信队列 针对于ttl进入死信队列的情况 假如我们把前面的消费者一关闭 然后对所有的消息都进行设置过期时间 这样是不是就形成了一个延迟队列了 使用场景 比如订单超时关闭 假如我们使用定时任务 假如数据量很大的情况下
  • SpringBoot —— 搭建SpringBoot+Maven项目

    文章目录 前言 一 创建步骤 1 创建SpringBoot项目 选择JDK版本 2 填写包名和项目名 3 创建web项目 4 创建web项目 二 测试 1 配置maven 2 创建测试方法 前言 SpringBoot系列Demo代码 使用i
  • 【复盘与分享】第十一届泰迪杯B题:产品订单的数据分析与需求预测

    文章目录 题目 第一问 第二问 2 1 数据预处理 2 2 数据集分析 2 2 1 训练集 2 2 2 预测集 2 3 特征工程 2 4 模型建立 2 4 1 模型框架和评价指标 2 4 2 模型建立 2 4 3 误差分析和特征筛选 2 4
  • python设置绘图大小_解决Python图形界面中设置尺寸的问题

    Python有自己内置的标准GUI库 Tkinter 只要安装好Python就可以调用 今天学习到了图形界面设计的问题 刚开始就卡住了 为啥呢 就是用geometry size 设置窗口尺寸大小 如800X600 X 从哪里来成了问题 首先
  • 你若有心,我怎会无情

    人和人之间 全靠一颗心 情与情之间 全看一份真 一生很长 什么样的人都会遇到 有的人口蜜腹剑 嘴上跟你说着甜言蜜语 心里却盘算着利用你得到些什么 有的人虚伪自私 表面上跟你掏心掏肺 等你需要帮助的时候却转身离开 所以 看人 不能只看表面 也
  • 复制构造函数(拷贝构造函数)

    也许很多C 的初学者都知道什么是构造函数 但是对复制构造函数 copy constructor 却还很陌生 对于我来说 在写代码的时候能用得上复制构造函数的机会并不多 不过这并不说明复制构造函数没什么用 其实复制构造函数能解决一些我们常常会