C++的复制构造函数三种用法

2023-11-15

前言

如果真的想明白,为什么你写的函数的返回值是对象时,有的时候调用了复制构造函数,而有的时候,没有调用复制构造函数。需要明白一件事:函数的返回值是对象时,什么情况下,函数的返回是return by value,即生成了临时对象。这两个问题是等价的,因为按照道理来讲,对象作为函数的返回值时,就应该生成临时对象,调用复制构造函数,初始化这个临时对象,但是,现在的编译器,对于return by value有优化。有时候,不生产临时对象。编译器总是将栈里面的 结果对象 直接给调用者,避免了多一次的析构和构造。即使在关闭编译器优化的时候,它依然给你做了这个动作。
现在的我,还不明白具体什么情况下,生成临时对象。什么情况下,不生成临时对象。
留给以后解决
可以参考的文章参考1参考2

三种用法

我们知道,类、struct结构体的 复制构造函数 在三种情况下会被调用,分别是:

1、创建对象a时,使用对象b去初始化对象a
2、函数fun( )的形参是对象时,传递参数时,形参 是用 复制构造函数 初始化的
3、函数的返回值是对象时,生成的临时对象,是用 复制构造函数初始化的

接下来依次看一下这三种情况。

首先是类的定义

class node {
    public:
        int x, y;
        
        node () {} // 默认构造函数
        node (int _x, int _y) : x(_x), y(_y) {} // 自定义的构造函数

        node (const node& temp) { // 自定义的复制构造函数
            x = temp.x;
            y = temp.y;
            printf("复制构造函数被调用\n"); 
            // 打印语句,只要该复制构造函数被调用,就会有输出
        }
};

1、创建对象a时,使用别的对象b去初始化a

int main()
{
    node b(8, 10); 
    // 创建了node对象 b,并且调用了构造函数,初始化对象b
    node a(b); // 创建对象a, 并且使用对象b来初始化a
    // node a = b; 
    // 这样也可以,只要是创建对象同时,用其余对象初始化就可以。
    cout<< a.x << " " << a.y << endl;

    return 0;
}

运行结果

复制构造函数被调用
8 10

可以看到,先输出了"复制构造函数被调用",之后又执行了打印a.x、a.y的语句。说明,在创建对象a时,使用对象b初始化对象a,是会调用复制构造函数的。调用了对象a的复制构造函数,复制构造函数的实参是对象 b。
但是下面的这种情况就不是创建对象的同时使用另一个对象初始化了。

int main()
{
    node b(8, 10);
    node a; // 先创建了对象 a,使用默认构造函数初始化了对象 a

    a = b; // 这是赋值,不是初始化
    
    cout << a.x << " " << a.y << endl;

    return 0;
}

运行结果

8 10

可见,首先定义一个对象a,之后再进行赋值,就不是初始化了。

2、函数fun( )的形参是对象时,传递参数时,形参 是用 复制构造函数 初始化的

void show(node temp) { // show()函数的参数是对象
    cout << temp.x << " " << temp.y << endl;
}

int main()
{
    node a(8, 10); 
    // 创建了node对象 a,并且调用了构造函数,初始化数据

    show(a);
    // 将对象a作为函数的实参传入

    return 0;
}

运行结果

复制构造函数被调用
8 10

可以看到,首先打印的是复制构造函数中的语句“复制构造函数被调用”,之后执行的是函数体里面的打印语句。所以,对象作为函数的参数时,传递参数时,是会调用复制构造函数的。即进行了值复制。形参temp是使用实参 a 初始化的。初始化形参temp时,调用了temp的复制构造函数,这个函数的参数是a。
更进一步,如果参数是引用:

void show(node& temp) { // show()函数的参数是 对象的引用
    cout << temp.x << " " << temp.y << endl;
}

int main()
{
    node a(8, 10); 
    // 创建了node对象 a,并且调用了构造函数,初始化数据

    show(a);
    // 将对象a作为函数的实参传入

    return 0;
}

此时函数的参数不是对象本身,而是对象的引用。再来看一下运行结果:

8 10

如果函数的参数是 对象的引用时,传入参数的时候,没有调用复制构造函数,没有输出复制构造函数中的打印语句“复制构造函数被调用”。
所以这种情况下,就没有复制这一步,因为传入的是实参的引用。如果大量数据要作为实参传入时,最好传入的是引用。速度快。

3、 函数的返回值是对象时,生成的临时对象,是用 复制构造函数 初始化的

我们知道,不同的函数,是在不同的内存空间中运行的,调用某个函数fun()时,去往另一片内存空间运行,当该函数执行结束时,这一片内存空间要被释放,其中的变量要消亡。但是,如果该函数有返回值(无论是基本类型,还是对象),则该函数要生成一个临时的值temp,用于返回给调用函数的地方。因为该函数执行结束之后,该函数里面的任何局部变量都要消失。
重点就是,该临时值temp,如果是对象的话,就是使用复制构造函数进行初始化的。

node create_node() { // 函数的返回值是对象
    node temp(3, 5); // 创建一个对象
    return temp; // 返回对象
}

int main()
{
    node a = create_node();// a 用于接收函数返回值

    cout << a.x << " " << a.y << endl;

    return 0;
}

运行结果:

3 5

可以看到,并没有想象的那样输出“复制构造函数被调用”,也就是说明,此种情况下,并没有调用复制构造函数。
原因在于现在的编译器对于“对象作为函数的返回值”这种情况有所优化。返回值为对象时,有的情况下不再产生临时对象,因而不再调用复制构造函数。因此也就不存在临时对象被初始化。

有几种情况下,还是会调用复制构造函数。
全局对象 cur 作为函数返回值,对象 a 在接收 返回值 cur 时,调用复制构造函数。

#include <bits/stdc++.h>
using namespace std;

class node {
    public:
        int x, y;

        node() {}
        node(int _x, int _y) : x(_x), y(_y) {}

        node(const node& temp) {
            x = temp.x, y = temp.y;
            printf("复制构造函数被调用\n");
        }
};

node cur(1, 2);

node fun() { // 函数的返回值是对象,并且是全局对象
    return cur;
}

int main()
{
    node a ; // a用于接收函数返回值
    a = fun(); // a 接收函数返回值
    cout << a.x << " " << a.y << endl;

    return 0;
}

运行结果

复制构造函数被调用
1 2

可以看到,当全局对象 cur 作为函数返回值时,对象 a 在接收 返回对象 cur时,调用了复制构造函数。

详细地分析一下这个例子。fun( ) 函数执行结束后,会生成一个临时对象,假设为temp,该对象是使用对象cur进行初始化的。在这个初始化过程中,调用了temp的复制构造函数,其实参是 return 语句中的 cur。在这之后,fun( )函数彻底消失,占用的内存被释放。
之后,在main( ) 函数中的 a = fun(); 语句,该临时对象temp,被赋值给 a, 这是一个赋值语句,并不是初始化语句。该语句执行结束之后,临时对象 temp 消亡。

此外,如果有一个函数,它的参数是对象,返回值也是对象,则,接收该函数的返回值时,调用了复制构造函数。

#include <bits/stdc++.h>
using namespace std;

class node {
    public:
        int x, y;

        node() {}
        node(int _x, int _y) : x(_x), y(_y) {}

        node(const node& temp) {
            x = temp.x, y = temp.y;
            printf("复制构造函数被调用\n");
        }
};

node fun(node& temp) { // 此处是引用,否则调用两次赋值构造函数。
    return temp;
}

int main()
{
    node a(1, 2), b;
    b = fun(a);
    cout << a.x << " " << a.y << endl;

    return 0;
}

运行结果

复制构造函数被调用
1 2

可以看到,fun( ) 函数的参数是对象、返回值也是对象,此时,对象 b 接收 返回值时,就调用了复制构造函数。

总结

复制构造函数,只在对象被 初始化 时被调用。而初始化意味着某对象在创建的同时,被初始化。
具体就三种情况:
1、对象被创建时,被其余对象初始化
2、函数fun( ) 的形参是对象,则调用函数 fun()时,形参会被初始化
3、函数的返回值是 对象,则生成的临时对象会被 return 语句中的数据初始化。

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

C++的复制构造函数三种用法 的相关文章

随机推荐

  • 小程序授权微信登录,获取微信用户名,头像,登录code

    getUserProfile e var that this wx getUserProfile desc 用于完善会员资料 success res gt if res 用户名 res userInfo nickName 头像res use
  • 【BERT类预训练模型整理】

    BERT类预训练模型整理 1 BERT的相关内容 1 1 BERT的预训练技术 1 1 1 掩码机制 1 1 2 NSP Next Sentence Prediction 1 2 BERT模型的局限性 2 RoBERTa的相关内容 2 1
  • 小程序(二十三)微信小程序左上角返回按钮触发事件

    微信并没有为我们提供左上角返回上一页触发的事件 但是有的时候这个操作我们还是需要监听一下 下图红框标注的返回上一页按钮 最后实现的效果 点击返回上一页的时候 我需要重新加载上一页的数据 返回上一页按钮只会触发上一页的onShow生命周期函数
  • 怎么将本地文件上传到远程git仓库

    怎么将本地文件上传到远程git仓库 1 先进入项目文件夹 通过命令 git init 把这个目录变成git可以管理的仓库 git init 2 把文件添加到版本库中 使用命令 git add 添加到暂存区里面去 不要忘记后面的小数点 意为添
  • 第二篇:UE4如何动态修改物体材质

    1 找到需要替换材质的物体 可以看到下方所有的材质 它是一个数组 前面是材质的下标 2 打开关卡蓝图 编写如下蓝图 第一步获取所有材质 创建动态材质接口 第二部设置材质 element Index代表要替换的材质下标 Material代表要
  • 米米商城项目

    米米商城 1 开发环境 2 项目功能 3 项目搭建步骤 4 配置文件 4 1 pom xml 4 2 jdbc properties 4 3 Mybaties配置文件 SqlMapConfig xml 4 4 Spring配置文件 4 4
  • 15.Mybatis 更新操作-update

    1 update 标签 update 标签是用于定义更新 语句的 1 1 常用属性 update 有几个常用的属性 但是通常只需要设置id 即可 id sql 片段在命名空间内的唯一标识 和mapper 中方法名保持一致 parameter
  • python使用keyboard库写的GUI键盘宏

    前言 之前和朋友玩游戏 需要一直按住两个按键 很麻烦 就像用python写个小脚本来方便自己 说干就干 用于学习 正文 用到的库 keyborad threading tkinter time 分析 由于需要监听键盘与运行可视化界面 所以要
  • 5.1 setfenv,但5.3可以使用lua_getglobal(l1, "_G");

    5 1使用法 lua State L luaL newstate luaL openlibs L dostring L function f1 my var 100 print var set end create func on stat
  • Springboot+Netty+Websocket实现消息推送实例

    Springboot Netty Websocket实现消息推送 文章目录 Springboot Netty Websocket实现消息推送 前言 一 引入netty依赖 二 使用步骤 1 引入基础配置类 2 netty服务启动监听器 3
  • windows 消息机制

    windows 操作系统是由事件驱动的 也叫消息机制 一般来说分为四步 用户动作也就是事件 gt windows 将事件翻译成消息 gt 将消息放入消息队列 gt 消息循环从消息队列中取出消息并发送给窗口处理程序 我们来看一下窗口最简单窗口
  • Elasticsearch 设置用户名密码认证(亲测)

    文章目录 第一步 在 elasticsearch yml 中添加如下配置 第二步 重启elasticsearch服务 第三步 设置elasticsearch密码 第四步 验证 修改密码 如果密码忘了怎么办 如何重置密码 1 修改elasti
  • 题目2-1 where is the flag

    对照启明星辰网络空间安全学院编著的 CTF安全竞赛入门 靶场和解题思路 考查点 网页源代码 右键查看源代码
  • 谁有用vue.js写的前端模板页面??? 急需!!!

    谁有用vue js写的前端模板页面 急需
  • Ubuntu切换root用户

    Ubuntu下root用户的默认密码 Ubuntu下root的默认密码是随机的 每次开机都会有一个新的密码 可以在中断输入命令 sudo passwd 然后输入当前用户的密码 按下回车 终端会提示输入新的密码并确认 此时的密码就是root新
  • Node.js 学习系列(五)—— 文件系统

    Node js 提供一组类似 UNIX POSIX 标准的文件操作API Node 导入文件系统模块 fs 语法如下所示 var fs require fs 异步和同步 Node js 文件系统 fs 模块 模块中的方法均有异步和同步版本
  • 如何使用SWC,如何发布,打包SWC

    SWC的使用方法 1 如果是FLEX的话就比较简单 直接在library中加入即可 2 是Flash的情况下 目前只有CS4可以直接导入 方法是 file gt gt publish settings gt gt flash gt gt s
  • 从零开始的Python编程

    目录 1 输出函数print 2 转义字符 3 Python中的标识符和保留字 4 变量的定义和使用 5 Python中常见的数据类型 6 数据类型转换 7 Python中的注释 8 input函数的使用 9 运算符 10 对象的布尔值 1
  • SQL优化 ----锁机制

    锁机制 解决因资源共享 而造成的并发问题 示例 当仓库中最后一件衣服时 A这时候下单 随后B也同一时间下单 这时候就会出现问题 到底这最后一件衣服卖给了谁 所以就要通过锁来解决这种问题 衣服 A来的时候加锁 gt 然后A就可以下单 付款 打
  • C++的复制构造函数三种用法

    前言 如果真的想明白 为什么你写的函数的返回值是对象时 有的时候调用了复制构造函数 而有的时候 没有调用复制构造函数 需要明白一件事 函数的返回值是对象时 什么情况下 函数的返回是return by value 即生成了临时对象 这两个问题