C++ 类学习总结(三) 类的拷贝赋值操作

2023-11-06

拷贝赋值操作

  1. 基本概念

    ①.拷贝构造函数:是一种构造函数,用同类型的对象初始化本对象的操作,即将新对象初始化为同类型另一个对象的副本。

    ②.拷贝赋值运算符:接收一个本类型对象的赋值运算符版本,返回本对象的引用。

  2. 类的默认函数

    ①.默认合成函数

    当我们定义了一个空类后,C++ 会为我们默认生成一个构造函数、一个拷贝构造函数、一个拷贝赋值运算符、一个析构函数,并且默认都是 public 的;一旦我们定义了带参数的构造函数,那么编译器就不会再生成默认的无参构造函数了。

    class Empty { };//定义了一个空类,无任何成员
    //等同于以下定义
    class Empty
    {
    public:
        Empty();//默认构造函数
        Empty( const Empty& emp);//拷贝构造函数
        Empty& operator= (const Empty& emp);//拷贝赋值运算符
        ~Empty();//默认析构函数
    };
    

    ②.显示使用默认函数

    我们可用通过将拷贝构造函数定义为 =default 来显示的要求编译器生成默认拷贝构造函数;只能对具有合成版本的成员函数使用 =default 。

    class Example
    {
    public:
        Example() = default;//默认构造函数,显示生成
        ~Example() = default;//默认析构函数
        Example( Example & emp) = default ;//拷贝构造函数
        Example& operator= (const Example& emp) = delete;//拷贝赋值运算符
    private:
        
    };
    

    ③.拒绝默认函数

    若不想编译器为我们生成默认的拷贝构造函数,可以将拷贝构造函数声明为 private ,并且不定义实现即可。

    class Example
    {
    public:
        Example();//默认构造函数
        ~Example();//默认析构函数
    private:
        Example( const Example& emp);//拷贝构造函数,声明但不定义
        Example& operator= (const Example& emp);//拷贝赋值运算符
    };
    

    在新标准下,也可以通过将拷贝构造函数定义为删除的函数来阻止拷贝;可以对任何函数使用 =delete,包含成员函数和非成员函数。

    class Example
    {
    public:
        Example();//默认构造函数
        ~Example();//默认析构函数
        Example( Example & emp) = delete;//拷贝构造函数,定义为删除的函数
        Example& operator= (const Example& emp) = delete;//拷贝赋值运算符
    private:
        
    };
    
  3. 拷贝构造函数

    ①.定义

    如果一个构造函数的第一个参数是自身类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数;默认的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。

    class Example
    {
    public:
        Example() ;//构造函数
        ~Example() ;//析构函数
        Example( Example & emp)  ;//拷贝构造函数
        Example& operator= (const Example& emp) = delete;//拷贝赋值运算符
    private:
        int a;
        string b;
    };
    
    Example::Example( Example Empty& emp): //等价默认拷贝构造函数
    a(emp.a),//内置类型直接拷贝
    b(emp.b)//使用 string 的拷贝构造函数
    {
        //空函数体
    }
    

    ②.直接初始化

    在定义对象时,实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数:可以是普通的构造函数,也可以是拷贝构造函数;直接初始化最基本的特征就是使用 ( ) 来定义对象。

    class Example
    {
    public:
        Example() ;//构造函数
        Example( string s) :b(s){};//构造函数
        ~Example() ;//析构函数
        Example( Example & emp)  ;//拷贝构造函数
        Example& operator= (const Example& emp) = delete;//拷贝赋值运算符
    private:
        int a;
        string b;
    };
    
    Example exp1;//直接初始化,调用默认构造函数
    Example exp2("hello");//也是直接初始化,调用带一个参数的构造函数
    Example exp3(exp2);也是直接初始化,调用拷贝构造函数
    

    ③.拷贝初始化

    在定义对象时,拷贝初始化实际上是要求编译器将右侧运算对象拷贝到正在创建的对象中,通常用拷贝构造函数来完成。

    拷贝初始化首先使用指定构造函数创建一个临时对象,然后用拷贝构造函数将那个临时对象拷贝到正在创建的对象,一般发生在用 = 来定义对象。

    class Example
    {
    public:
        Example() ;//构造函数
        Example( string s) :b(s){};//构造函数
        ~Example() ;//析构函数
        Example( Example & emp)  ;//拷贝构造函数
        Example& operator= (const Example& emp) = delete;//拷贝赋值运算符
    private:
        int a;
        string b;
    };
    
    Example exp4 = exp3;//拷贝初始化,由于 exp3 已经存在,直接调用拷贝构造函数
    Example exp5 = “hello”;//拷贝初始化,先调用构造函数将 string 转成 临时对象,然后调用拷贝构造函数
    Example exp6 = Example("hello");//包括初始化,先调用构造函数创建临时对象,然后调用拷贝构造函数
    

    以下情况也会发生拷贝初始化:

    将一个对象作为实参传递给非引用类型的形参时

    class Example {};
    
    void f1( Example exp)
    {
        //函数体
    };
    Example exp1;
    f1(exp1);//会调用拷贝构造函数创建临时对象,临时对象用于拷贝实参,并传入函数
    
    

    将一个返回类型为非引用类型的函数返回一个对象时

    class Example {};
    
    Example f2( )
    {
        Example exp1;
        return exp1;
    };
    f2();//会调用拷贝构造函数创建临时对象,临时对象用于存储返回值
    

    用花括号列表初始化一个数组或者一个聚合类中的成员时

    class Example {};
    
    vector<Example> vec = {exp1,exp2,exp3};//拷贝初始化没个元素
    vec.push_back("hello");//拷贝初始化
    vec.emplace("hello");//直接初始化
    
  4. 拷贝赋值运算符

    ①.定义

    重载运算符本质上是函数,其名字由 operator 关键字后表示要定义的运算符的符号组成,即赋值运算符就是一个名为 operator= 的函数;赋值运算符通常应该返回一个指向左侧运算对象的引用。

    class Example
    {
    public:
        Example() ;//构造函数
        ~Example() ;//析构函数
        Example( Example & emp)  ;//拷贝构造函数
        Example& operator= (const Example& emp) ;//拷贝赋值运算符
    private:
        int a;
        string b;
    };
    
    Example& //返回对象的引用
    Example::operator=( Example & emp) //等价默认拷贝赋值运算符
    {
        a = emp.a;//内置类型,直接拷贝
        b = emp.b;// 调用 string 的拷贝赋值运算符
        return *this;// 返回指向左侧对象的引用
    }
    

    ②.必须返回左侧对象的引用

    返回值类型:函数返回的是一个对象的副本;返回引用类型:函数返回的是一个真实对象的别名。

    operator= 函数返回引用,可用提高效率:

    class Example
    {
    public:
        Example() ;//构造函数
        ~Example() ;//析构函数
        Example( Example & emp)  ;//拷贝构造函数
        Example operator= (const Example& emp) ;//拷贝赋值运算符
    private:
        int a;
        string b;
    };
    
    Example //返回对象副本
    Example::operator=( Example & emp) 
    {
        a = emp.a;//内置类型,直接拷贝
        b = emp.b;// 调用 string 的拷贝赋值运算符
        return *this;// 调用拷贝构造函数生成临时对象
    }
    
    Example exp1,exp2,exp3;
    exp1 = exp2 = exp3;//会比返回引用多调用两次拷贝构造函数
    
    

    为了支持另一种形式的联锁赋值:

    class Example ;
    Example exp1,exp2,exp3;
    // 若非返回引用,将无法支持以下操作
    (exp1 = exp2 ) = exp3;//exp3 将作用于 exp1 的副本
    

    ③.自我赋值及异常安全

    赋值运算符通常组合了析构函数和构造函数的操作:类似析构函数,赋值操作会销毁左侧运算对象、类型拷贝构造函数,赋值操作会从右侧运算对象拷贝数据。

    我们需要确保赋值操作已正确的顺序执行,即使将一个对象赋值给自身也确保正确,如果有异常发生也要确保左侧对象的状态没有影响。

    一般通过先拷贝右侧运算对象再销毁左侧对象的顺序,可用实现自我赋值以及异常安全。

    class Example
    {
    public:
        Example(string s ):b(new string(s)) {}  ;//构造函数
        ~Example() ;//析构函数
        Example( Example & emp) :b( new string(*emp.b)) {};//拷贝构造函数
        Example& operator= (const Example& emp) ;//拷贝赋值运算符
    private:
        int a;
        string *b;
    };
    
    Example& //返回对象的引用
    Example::operator=( Example & emp) 
    {
        if( *this == emp ) return;//判断自复制
        string * temp = new string(*emp.b);//拷贝底层 string 数据
        delete b;//释放旧对象,因为拷贝操作已经安全的执行,释放是安全的
        b = temp;//从右侧对象拷贝数据
        a = emp.a;
        return *this;// 返回指向左侧对象的引用
    }
    
  5. 深拷贝和浅拷贝

    ①.浅拷贝

    如果用默认的赋值运算符函数去赋值有指针成员变量的对象,就会使得两个对象的指针地址也是一样的,也就是两个对象的指针成员变量指向的地址是同一个地方,这种方式就是浅拷贝。

    浅拷贝副本和原对象共享底层数据,即仅仅复制了指针本身的值,这时当一个对象释放了指针成员变量时,那么另外一个对象的指针成员变量指向的地址就是空的了,再次使用这个对象时,程序就会奔溃。

    class Example
    {
    public:
        Example(string s ):b(new string(s)) {}  ;//构造函数
        ~Example() ;//析构函数
        Example( Example & emp) :b( new string(*emp.b)) {};//拷贝构造函数
        Example& operator= (const Example& emp) = default;//默认拷贝赋值运算符
    private:
        int a;
        string *b;
    };
    Example emp1("hello"); 
    Example emp2("world");
    emp1 = emp2;// emp1.b 和 emp2.b 都指向了 world
    

    ②.深拷贝

    深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。

    class Example
    {
    public:
        Example(string s ):b(new string(s)) {}  ;//构造函数
        ~Example() ;//析构函数
        Example( Example & emp) :b( new string(*emp.b)) {};//拷贝构造函数
        Example& operator= (const Example& emp) ;//拷贝赋值运算符
    private:
        int a;
        string *b;
    };
    Example& //返回对象的引用
    Example::operator=( Example & emp) //拷贝底层数据
    {
        if( *this == emp ) return;//判断自复制
        string * temp = new string(*emp.b);//拷贝底层 string 数据
        delete b;//释放旧对象,因为拷贝操作已经安全的执行,释放是安全的
        b = temp;//从右侧对象拷贝数据
        a = emp.a;
        return *this;// 返回指向左侧对象的引用
    }
    Example emp1("hello"); 
    Example emp2("world");
    emp1 = emp2;// emp1.b 和 emp2.b 都指向的内存地址不同
    
  6. 三/五法则

    ①.类内动态分配内存,必须实现析构函数

    如果一个成员变量是别的对象的指针,而且这个指针不是传进来的地址,而是这个指针指向的对象是在本类中在堆中开辟的空间创建的,则必须实现析构函数,在析构函数中进行动态内存释放

    ②.如果一个类需要析构函数,那么也需要实现拷贝、赋值操作

    ③.如果一个类需要拷贝操作,那么也需要实现赋值操作
    在这里插入图片描述

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

C++ 类学习总结(三) 类的拷贝赋值操作 的相关文章

  • 在哪里使用 EF6 订阅 ObjectMaterialized?

    我正在尝试将我的上下文订阅到以下 OnjectMaterialized 事件this https stackoverflow com a 3756842 2835713 像这样 IObjectContextAdapter this Obje
  • 将列表数组中的值绑定到列表框

    任何机构都可以给出一个简短的示例 用于将列表数组中的值绑定到 c net 中的列表框 这取决于您的列表数组的情况 让我们从一个简单的示例开始 List
  • Windows 10 UWP 中的视觉状态管理器未在页面加载时应用初始状态

    我有一个带有相关面板的页面 可以根据宽度重新组织 但是 除非宽度 gt 720px 否则它似乎不会在加载时应用任何状态 如果我在加载页面后调整页面大小 则两种状态都有效 解决方法是检查加载页面上的窗口大小并手动选择状态 但我相信这应该自动处
  • 是否可以通过引用以基类作为参数的函数来传递派生类

    假设我们有一个抽象基类IBase使用纯虚方法 接口 然后我们推导出CFoo CFoo2来自基类 我们有一个知道如何使用 IBase 的函数 Foo IBase input 这些情况下通常的场景是这样的 IBase ptr static ca
  • 为什么在 OpenCV 中访问该矩阵时出现内存错误?

    我只是想写入给定大小的矩阵 当我在 Valgrind 中运行该程序时 出现内存错误 如下所示 主要 cpp include
  • 使用 pthread_cond_signal 优雅地终止线程被证明是有问题的

    我需要发射一堆线程 并希望优雅地将它们拉下来 我正在尝试使用pthread cond signal pthread cond wait实现这一目标 但遇到了问题 这是我的代码 首先是thread main static void thrma
  • 是否可以获取指向装箱非托管值类型的指针?

    是否可以获取指向装箱非托管值类型的指针 而无需编写对每个支持的类型进行强制转换的大型 switch 语句 就像是 object val Contains a boxed unmanaged value such as int long by
  • 为什么 .Net 框架指南建议您不要使用 ref/out 参数?

    显然 他们很 混乱 这是认真的原因吗 你还能想到其他的吗 你见过有多少开发人员并不真正理解 ref out 吗 我在真正需要的地方使用它们 但在其他地方则不然 它们通常仅在您想有效返回两个或多个值时才有用 在这种情况下它至少值得thinki
  • Docker 不遵循构建目录中的符号链接

    我正在对一个应用程序进行 Docker 化 其中涉及通过 Clang 将二进制文件与其他 C 文件链接 我们维护二进制文件的符号链接版本 因为它们在整个代码库中使用 我的 Docker 构建目录包含整个代码库 包括源文件以及这些源文件的符号
  • Ajax 函数在重定向后不保存滚动位置

    正如标题所述 我编写了一个 ajax 函数 该函数应该滚动到用户在重定向之前所在的位置 我写了一个alert对于测试场景 它确实触发了 但滚动不断回到顶部 我在这里做错了什么 JavaScript ajax type GET url Adm
  • 为什么我从 c# 到 js 得到不同的 MD5 哈希值?

    我有一个用于加密密码的 C 函数 System Security Cryptography MD5CryptoServiceProvider md5Provider new System Security Cryptography MD5C
  • 更改成员资格、角色等的默认连接字符串

    默认情况下 我的网络应用程序似乎正在使用LocalSqlServer作为用于任何应用程序服务 例如成员资格 角色 身份验证 等 的连接字符串 有什么方法可以更改默认连接字符串应该是什么 默认值是 LocalSqlServer 似乎很随意 我
  • 将华氏温度转换为摄氏度的 C 程序始终打印零

    我需要一些关于用 C 语言将华氏温度转换为摄氏度的程序的帮助 我的代码如下所示 include
  • 使用 Node.js 访问用 C++ 编写的 SDK

    我有一个用 C 语言编写的 SDK 可以与我的扫描仪设备进行通信 我需要开发一个可以访问扫描仪设备的电子应用程序 我知道有很多库可用于扫描仪 但我想使用这个 SDK 因为它允许我访问设备的完整功能 而且它是由设备制造商提供的 那么 有没有什
  • 您的 C++ 程序中是否仍然存在内存分配失败问题 [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我正在为公司写一些指导方针 我需要回答一些棘手的问题 这一项是相当困难的 解决方案可以是 根本不跟踪 确保使用 new 分配对象 这会在分配失败
  • 使texture2D在运行时/脚本Unity3D中可读[重复]

    这个问题在这里已经有答案了 我有一个插件 可以让我访问 Android 手机图库中的图片 这给了我一个Texture2D类型的纹理 然后我想使用 GetPixels 函数对其进行编辑 但默认情况下它未设置为可读 如何使纹理可读 以便我可以在
  • 如何在您的网站中连接两个人

    有一款名为 Verbosity 的游戏 这是一款有目的的游戏 位于此链接上www gwap com 在游戏中 他们随机连接两个玩家互相玩 游戏是玩家1应该向他的搭档 玩家2 描述一个单词 而玩家2应该猜测这个单词 我正在尝试建立一个网站来执
  • 使用 Crypto++ 和 .NET 的 CFB 模式下的 TripleDES

    我正在尝试使用 TripleDES 使用 C 应用程序获得相同的结果 该应用程序具有Crypto https www cryptopp com 和 NET应用程序使用三重DESCryptoServiceProvider https msdn
  • 清理 TPL 中的 CallContext

    根据我使用的是基于 async await 的代码还是基于 TPL 的代码 我在逻辑清理方面得到了两种不同的行为CallContext 我可以设置和清除逻辑CallContext如果我使用以下异步 等待代码 正如我所期望的 class Pr
  • File.Move 的原子性

    我想将目录中的文件重命名为原子事务 该文件不会更改目录 该路径作为 NTFS 文件系统的 UNC 路径提供 可能位于服务器 03 或 08 上 File Move 对于这些目的来说是原子的吗 例如 它要么成功完成 要么失败 以使原始文件仍然

随机推荐

  • H5如何直接跳转小程序?

    1 云开发方式 不推荐 不推荐理由 1 要钱 2 麻烦 需要兼容 参考链接 https developers weixin qq com miniprogram dev wxcloud guide staticstorage jump mi
  • 稀疏重构算法详解

    引入 在室内环境中 多径信号具有天然的空间稀疏性 根据压缩感知理论可知 如果信号是可压缩的或者在某个变换域是稀疏的 可以采用一个随机测量矩阵将高维信号映射到一个低维空间上 通过求解优化问题 以很高的概率重构出原始信号 因此 在该理论框架下
  • 带分数

    标题 带分数 100 可以表示为带分数的形式 100 3 69258 714 还可以表示为 100 82 3546 197 注意特征 带分数中 数字1 9分别出现且只出现一次 不包含0 类似这样的带分数 100 有 11 种表示法 题目要求
  • 小心踩雷,一次Java内存泄漏排查实战

    问题出现 晚上七点多开始 我就开始不停地收到报警邮件 邮件显示探测的几个接口有超时情况 多数执行栈都在 java io BufferedReader readLine BufferReader java 389 java io Buffer
  • c++ 变量常量指针练习题

    Q1 在win32 x86模式下 int p int pp double q 请说明p pp q各占几个字节的内存单元 p 占 4 个字节 pp 占 4 个字节 q 占 4 个字节 Q2常量1 1 0 1 的数据类型是什么 1 是 整形 i
  • YOLOv7中的数据集处理【代码分析】

    本文章主要是针对yolov7中数据集处理部分代码进行解析 和yolov5是一样的 也是可以更好的理解训练中送入的数据集到底是什么样子的 数据集的处理离不开两个类 一个是Dataset from torch utils data import
  • python3 -sorted函数 对所有可迭代的对象进行排序操作 sorted(corr_list,key=lambda x: -abs(x[0]))

    sorted 函数对所有可迭代的对象进行排序操作 返回重新排序的列表 sort 与 sorted 区别 sort 是应用在 list 上的方法 sorted 可以对所有可迭代的对象进行排序操 作 list 的 sort 方法返回的是对已经存
  • linux export 的作用

    功能说明 设置或显示环境变量 语 法 export fnp 变量名称 变量设置值 补充说明 在shell中执行程序时 shell会提供一组环境变量 export可新增 修改或删除环境变量 供后续执行的程序使用 export的效力仅及于该此登
  • 我在腾讯做测10年,总结的7条生存经验

    简单做个自我介绍 我是一名测试工程师 从15年毕业到现在工作了6年 一路走过来 觉得自己很幸运遇到了很多伯乐 教会了我很多道理和职场经验 也非常荣幸在阿里工作过4年 搭建过蚂蚁金服的platuo测试框架 thrift测试框架 自动化测试平台
  • React源码分析(一)=> scheduler分析

    文章目录 1 前言 2 getCurrentTime 3 unstable scheduleCallback函数 4 scheduleHostCallbackIfNeeded 4 1 flushWork 4 2 flushFirstCall
  • 学习笔记实操手册

    https note youdao com s KP25iMDf https note youdao com s GAmVO7V 使用yum安装php72 https www cnblogs com JahanGu p 10439472 h
  • 编写一个使用指针的C函数,交换数组a和数组b的对应元素

    编写一个使用指针的C函数 交换数组a和数组b的对应元素 int a 5 1 2 3 4 5 int b 5 10 20 30 40 50 输出格式要求 a d 2d b d 2d 程序运行示例如下 a 0 10 a 1 20 a 2 30
  • QT应用部署流程

    参考链接 https www shuzhiduo com A LPdo07AGz3 1 Windows系统 Windows下使用QT自带工具windeployqt exe部署 windows gt command 切换到QT的工具目录 在c
  • signature=a195252fc5196d0fb82cccccc68b06b3,Gene signatures in wound tissue as evidenced by molecular...

    Wound induction in the chicken CAM Chick embryos were cultured for 10 days and CAMs were inflicted by parallel scalpel s
  • linux 数组里面是json,将JSON解析为shell脚本中的数组

    小编典典 如果您确实无法使用适当的JSON解析器 例如 1 请尝试 基于的解决方案 jq awk Bash 4 x readarray t values lt 3 print 4 myfile json Bash 3 x IFS n rea
  • lua 3.0 中 普通方法延时

    local delayTime cc DelayTime create 1 local callFunND cc CallFunc create function self pushjoystick end local seq cc Seq
  • 微信企业付款至零钱,状态处理中,status=PROCESSING的解决办法

    前段时间腾讯因为支付系统异常 更新了一些东西 然后就开始出现了这个问题 时不时的就会有一个两个状态为 处理中 的交易 但文档中并没有给出解决办法 尝试咨询了客服 给出了两个解决方案 1 把该笔交易当做失败处理 但以后这笔订单就不要再去折腾它
  • ESP8266 RTOS SDK 移植 u8g2 移植代码

    LED屏驱动ssd1306 屏幕128x64大小 1 移植代码核心 方法1 port c define SCL Pin GPIO SCL define SDA Pin GPIO SDA void delay us uint32 t time
  • Flink学习20:算子介绍reduce

    1 reduce简介 按照指定的方式 把每个元素进行累计执行 比如实现累加计算 示例 import keyByNameTest StockPrice import org apache flink api scala createTypeI
  • C++ 类学习总结(三) 类的拷贝赋值操作

    拷贝赋值操作 基本概念 拷贝构造函数 是一种构造函数 用同类型的对象初始化本对象的操作 即将新对象初始化为同类型另一个对象的副本 拷贝赋值运算符 接收一个本类型对象的赋值运算符版本 返回本对象的引用 类的默认函数 默认合成函数 当我们定义了