类的函数成员(三):拷贝构造函数

一.什么是拷贝构造函数?

1.1 概念

        同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制或称拷贝是完全可行的。这个拷贝过程只需要拷贝数据成员,而函数成员是共用的(只有一份拷贝)。
        在建立对象时可用同一类的另一个对象来初始化该对象,这时所用的构造函数称为拷贝构造函数( Copy Constructor)。

        拷贝构造函数的参数必须采用引用类型,但并不限制为const,一般普遍的会加上const限制。如果以类对象作为参数传递到拷贝构造函数,会引起无穷递归。

1.2 代码示例

        代码示例如下:

#include <iostream>using namespace std;class CStudent
{
public:CStudent(int age = 0,int score = 0);~CStudent();//拷贝构造函数 CStudent(const CStudent &stu);private:int age;int score;
};CStudent::CStudent(int age,int score)
{cout<<"Constructor!"<<endl;this->age = age;this->score = score;
}CStudent::~CStudent()
{cout<<"Desconstructor!"<<endl;
}CStudent::CStudent(const CStudent &stu)
{cout<<"Copy constuctor!"<<endl;this->age = stu.age;this->score = stu.score;
}

二.如何实现?

2.1 缺省拷贝构造函数

2.1.1 概念

        如果类中没有给出定义,系统会自动提供缺省拷贝构造函数。

        缺省的拷贝构造函数会按成员语义,依次拷贝每个类成员,亦称为缺省的按成员初始化。

        按成员作拷贝是通过依次拷贝每个数据成员实现的,而不是对整个类对象按位拷贝。

2.1.2 代码示例

        示例代码如下:

#include <iostream>using namespace std;class CStudent
{
public:CStudent(int age = 0,int score = 0);~CStudent();void print_info(void);private:int age;int score;
};CStudent::CStudent(int age,int score)
{cout<<"Constructor! "<<this<<endl;this->age = age;this->score = score;
}CStudent::~CStudent()
{cout<<"Desconstructor! "<<this<<endl;
}void CStudent::print_info(void)
{cout<<"age("<<this<<"): "<<age<<endl;cout<<"score("<<this<<"): "<<score<<endl;	
}int main(int argc, char** argv)
{CStudent stu1(8,90);CStudent stu2(stu1);stu2.print_info();return 0;
}

        运行结果如下图所示。       

         由上图可知:

(1)只调用了一次普通构造函数,用来构造对象stu1。表明,在构造stu2时调用了一个缺省的构造函数,这个函数就是拷贝构造函数。

(2)对象stu2的所有数据成员被初始化为stu1对应数据成员的值。

(3)最后,调用了两次析构函数,用于析构stu1和stu2。

2.2 自定义拷贝构造函数

2.2.1 概念

        通常按成员语义支持已经足够。但在某些情况下,它对类与对象的安全性和处理的正确性还不够,这时就要求类的设计者提供特殊的拷贝构造函数定义。

2.2.2 代码示例

        示例代码如下:

#include <iostream>using namespace std;class CStudent
{
public:CStudent(int age = 0,int score = 0);~CStudent();//拷贝构造函数 CStudent(const CStudent &stu);void print_info(void);
private:int age;int score;
};CStudent::CStudent(int age,int score)
{cout<<"Constructor!"<<endl;this->age = age;this->score = score;
}CStudent::~CStudent()
{cout<<"Desconstructor!"<<endl;
}CStudent::CStudent(const CStudent &stu)
{cout<<"Copy constuctor!"<<endl;this->age = stu.age;this->score = stu.score;
}void CStudent::print_info(void)
{cout<<"age: "<<age<<endl;cout<<"score: "<<score<<endl;	
}int main(int argc, char** argv)
{CStudent stu1(8,90);CStudent stu2(stu1);stu2.print_info();return 0;
}

        运行结果如下图所示。

        由上图可知:

(1)构造stu2对象时,调用了一次自定义的拷贝构造函数。

(2)关注一下自定义构造函数代码,发现在函数域内可通过引用对象访问私有数据成员age和score。

        从逻辑上讲,每个对象有自己的成员函数,访问同类其他对象的私有数据成员应通过该对象的公有函数,不能直接访问。但在物理上只有一个成员函数拷贝,所以直接访问是合理的。

        即,C++有个原则:类的成员函数可以访问私有数据成员。

CStudent::CStudent(const CStudent &stu)
{cout<<"Copy constuctor!"<<endl;this->age = stu.age;this->score = stu.score;
}

三.何时调用?

3.1 用对象初始化对象

        以下两种形式都是用已存在的对象初始化对象:

CStudent stu1(8,90);CStudent stu2(stu1);
或者
CStudent stu2 = stu1;

        以上两种形式是等价的,只是写法上不同。

3.2 给函数传递类的对象参数

       当函数的形参是类的对象时, 一旦调用函数,要在内存新建立一个局部对象,并把实参拷贝到新的对象中。

        代码示例(部分)如下:

void func(CStudent stu)
{cout<<"func"<<endl;	
}int main(int argc, char** argv)
{CStudent stu1(8,90);func(stu1);return 0;
}

        运行结果如下图所示。

        由上图可知。调用func函数时,会调用拷贝构造函数构造一个临时对象传给func。

3.3 函数返回类的对象(部分编译器)

        很多资料提到:如果函数的返回值是类的对象,那么函数执行完成后,返回调用者时会调用拷贝构造函数。其实这不严谨。

        有些编译器在函数返回类的对象时,不会调用拷贝构造函数。下面单独一节详细分析。

四.函数返回类的对象但不调用拷贝构造函数

        本次实验使用64位TDM-GCC 4.9.2编译器。

4.1 示例代码        

#include <iostream>using namespace std;class CStudent
{
public:CStudent(int age = 0,int score = 0);~CStudent();//拷贝构造函数 CStudent(const CStudent &stu);void print_info(void);
private:int age;int score;
};CStudent::CStudent(int age,int score)
{cout<<"Constructor!"<<endl;this->age = age;this->score = score;
}CStudent::~CStudent()
{cout<<"Desconstructor!"<<endl;
}CStudent::CStudent(const CStudent &stu)
{cout<<"Copy constuctor!"<<endl;this->age = stu.age;this->score = stu.score;
}void CStudent::print_info(void)
{cout<<"age: "<<age<<endl;cout<<"score: "<<score<<endl;	
}CStudent func(void)
{CStudent tmp(11,88);return tmp;	
}int main(int argc, char** argv)
{CStudent stu1(8,90);CStudent stu2;stu2 = func();stu2.print_info();return 0;
}

4.2 运行结果

        如下图所示。

        由下图可知:

(1)func函数的返回值是类的对象,但并没有调用拷贝构造函数。

(2)从stu2打印的信息来看,func函数中创建的tmp对象,的确“赋值”给了stu2。这怎么理解?下面看看汇编代码。

4.3 汇编代码

        汇编代码中r8d是指r8寄存器的低32位。

4.3.1 func函数汇编代码    

        完整的汇编代码如下:

push   %rbp
mov    %rsp,%rbp
sub    $0x20,%rsp
mov    %rcx,0x10(%rbp) //rcx存储了对象tmp的地址
mov    $0x58,%r8d   //r8d的低32位初始化为88
mov    $0xb,%edx    //edx初始化为11
mov    0x10(%rbp),%rcx //即是tmp对象地址
callq  0x401530 <CStudent::CStudent(int, int)>
nop
mov    0x10(%rbp),%rax
add    $0x20,%rsp
pop    %rbp
retq   

        如上图中的注释,func函数里的对象tmp的地址是由调用者main函数传入的,即tmp对象是在main函数的堆栈里存储,而不是在func函数的堆栈里。

4.3.2 构造函数汇编代码

           CStudent::CStudent(int, int)函数的完整汇编代码如下:

push   %rbp
mov    %rsp,%rbp
sub    $0x20,%rsp
mov    %rcx,0x10(%rbp)//rcx存储了对象tmp的地址
mov    %edx,0x18(%rbp) //初始化tmp.score的值为11
mov    %r8d,0x20(%rbp) //初始化tmp.age的值为88
lea    0x86ab6(%rip),%rdx        # 0x488000
mov    0x8b17f(%rip),%rcx        # 0x48c6d0 <.refptr._ZSt4cout>
callq  0x46ee10 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
mov    0x8b183(%rip),%rdx        # 0x48c6e0 <.refptr._ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_>
mov    %rax,%rcx
callq  0x44d500 <_ZNSolsEPFRSoS_E>
mov    0x10(%rbp),%rax
mov    0x18(%rbp),%edx
mov    %edx,(%rax)
mov    0x10(%rbp),%rax
mov    0x20(%rbp),%edx
mov    %edx,0x4(%rax)
add    $0x20,%rsp
pop    %rbp
retq   
retq  

        注意第4~5行代码的注释。构造函数里,初始化了tmp对象的数据成员。

 4.3.3 main函数汇编代码

        main函数的完整汇编代码如下:

push   %rbp
push   %rbx
sub    $0x58,%rsp
lea    0x80(%rsp),%rbp
mov    %ecx,-0x10(%rbp)
mov    %rdx,-0x8(%rbp)
callq  0x40e950 <__main>
lea    -0x50(%rbp),%rax //堆栈偏移0x50的空间,分配给对象stu1.这里rax存储了stu1的地址
mov    $0x5a,%r8d    	//r8的低32位初始化为90
mov    $0x8,%edx     	//edx寄存器初始化为8
mov    %rax,%rcx     	//传递stu1的地址给构造函数
callq  0x401530 <CStudent::CStudent(int, int)>
lea    -0x60(%rbp),%rax //堆栈偏移0x60的空间,分配给对象stu2.这里rax存储了stu2的地址
mov    $0x0,%r8d
mov    $0x0,%edx
mov    %rax,%rcx   		//传递stu2的地址给构造函数
callq  0x401530 <CStudent::CStudent(int, int)>
lea    -0x40(%rbp),%rax	//堆栈偏移0x40的空间,分配给了一个临时对象,暂时命名为m_tmp.这里rax存储了m_tmp的地址
mov    %rax,%rcx		//传递m_tmp的地址给func函数
callq  0x401685 <func()> //func函数里的tmp对象直接使用了main函数创建的m_tmp
mov    -0x40(%rbp),%rax  
mov    %rax,-0x60(%rbp)  //将m_tmp赋值给stu2
lea    -0x40(%rbp),%rax
mov    %rax,%rcx
callq  0x40157e <CStudent::~CStudent()> //析构m_tmp
lea    -0x60(%rbp),%rax
mov    %rax,%rcx
callq  0x401606 <CStudent::print_info()>
mov    $0x0,%ebx
lea    -0x60(%rbp),%rax
mov    %rax,%rcx
callq  0x40157e <CStudent::~CStudent()>
lea    -0x50(%rbp),%rax
mov    %rax,%rcx
callq  0x40157e <CStudent::~CStudent()>
mov    %ebx,%eax
jmp    0x401770 <main(int, char**)+192>
mov    %rax,%rbx
lea    -0x60(%rbp),%rax
mov    %rax,%rcx
callq  0x40157e <CStudent::~CStudent()>
jmp    0x401759 <main(int, char**)+169>
mov    %rax,%rbx
lea    -0x50(%rbp),%rax
mov    %rax,%rcx
callq  0x40157e <CStudent::~CStudent()>
mov    %rbx,%rax
mov    %rax,%rcx
callq  0x40f670 <_Unwind_Resume>
add    $0x58,%rsp
pop    %rbx
pop    %rbp
retq   

        如代码中的注释:

(1)main函数在调用func函数前,创建了一个临时对象,这里给它命名为m_tmp。

(2)m_tmp对象的地址传递给func函数,func函数里的tmp对象直接使用了m_tmp的地址。因此,可以认为,tmp就是m_tmp的别名。

(3)func函数返回后,将m_tmp对象的数据赋值给stu2对象。

(4)最后,析构m_tmp。

        所以,从始至终,没有调用过拷贝构造函数。

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

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

相关文章

Linux 文件相关命令

一、查看文件命令 1&#xff09;浏览文件less 默认查看文件的前 10 行。 less /etc/services ##功能说明&#xff1a; #1.默认打开首屏内容 #2.按【回车】按行访问 #3.按【空格】按屏访问 #4.【从上向下】搜索用/111,搜索包含111的内容&#xff0c;此时按n继续向下搜&#x…

leetcode.203. 移除链表元素

题目 题意&#xff1a;删除链表中等于给定值 val 的所有节点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5] 示例 2&#xff1a; 输入&#xff1a;head [], val 1 输出&#xff1a;[] 示例 3&#xff1a; 输入&#…

LeetCode 热题 100 | 贪心算法

目录 1 121. 买卖股票的最佳时机 2 55. 跳跃游戏 3 45. 跳跃游戏 II 4 763. 划分字母区间 菜鸟做题&#xff0c;语言是 C 1 121. 买卖股票的最佳时机 解题思路&#xff1a; 维护一个变量 max_pricemax_price 用于存储排在 i 天之后的股票最高价格第 i 天的最高利润 …

【MySQL】:深入解析多表查询(上)

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; MySQL从入门到进阶 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一. 多表关系1.1 一对多1.2 多对多1.3 一对一 二. 多表查询概述2.1 概述2.2 分类…

升级一下电脑,CPU换I5-14600K,主板换华硕B760M

刚给自己电脑升级了一下&#xff0c;CPU从 AMD R5 5600X 换成 Intel I5-14600K&#xff0c;主板换成了华硕的 TUF GAMING B760M-PLUS WIFI D4。 因为我现有的两根内存是DDR4的&#xff0c;所有我选了个支持DDR4内存的主板。 我发现用AMD处理器时将系统从Win10升级到Win11后变…

基于SSM的邮票鉴赏系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的邮票鉴赏系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spri…

frp内网穿透,让外网可以访问内网

需求 我们的svn部署在内网&#xff0c;用的一直没问题&#xff0c;但是有时候有需求在外网访问svn&#xff0c;进行提交更新等操作&#xff0c;这时候就有了内网穿透这个需求。 当然&#xff0c;我们也可以借助花生壳等软件进行内网穿透&#xff0c;傻瓜化操作&#xff0c;也…

Yarn的安装和使用(2):使用及问题解决

Yarn是JavaScript的依赖管理工具&#xff0c;它与npm类似&#xff0c;但提供了一些额外的性能优化和一致性保证。 Yarn的使用&#xff1a; 初始化项目&#xff1a; yarn init 此命令会引导您创建一个新的package.json文件&#xff0c;用于记录项目的元信息和依赖。 添加依赖&…

如何更新Code::blocks的MinGW

前言 LVGL V9版本更新了很多新特性&#xff0c;其中windows平台部分也进行了优化&#xff0c;如果你是用的是Code::blocks体验LVGL那么在编译时会不通过&#xff1b;因为如果你使用的是 Code::blocks 20.03并且使用内置的MinGW&#xff0c;那么就会因为MinGW版本过低遇到下面所…

c++的学习之路:12、vector(1)

这章主要是根据cplusplus中的文档进行使用Vector&#xff0c;文章末附上测试代码。 目录 一、什么是vector 二、vector的简单使用 三、代码 一、什么是vector 下图是cplusplus的简介&#xff0c;上面一共有六点&#xff0c;如下&#xff1a; 1、vector是表示可变大小数组…

Ant Design Vue table固定列失效问题解决

问题描述&#xff1a;项目中封装好的公共table组件&#xff0c;基于Ant Design Vue table封装&#xff1b;使用中&#xff0c;用到了列固定&#xff0c;但是没生效&#xff0c;找了好久的原因。。。最后是因为外层容器标签导致&#xff1b; 解决方法&#xff1a;如果a-table组件…

Linux: linux常见操作指令

目录 01.ls 指令 02. pwd命令 03. cd 指令 04. touch指令 05.mkdir指令&#xff08;重要&#xff09; 06.rmdir指令 && rm 指令&#xff08;重要&#xff09; 07.man指令&#xff08;重要&#xff09; 07.cp指令&#xff08;重要&#xff09; 08.mv指令&#…

理解 SQL 数据添加:从基础到实践

引言&#xff1a; 在现代软件开发中&#xff0c;数据库是不可或缺的一部分。而 SQL 作为结构化查询语言的代表&#xff0c;广泛应用于数据库管理系统中&#xff0c;为我们提供了强大的数据管理和查询能力。 主题&#xff1a; 我们将从基础的 SQL INSERT INTO 语句开始&…

EVM Layer2 主流解决方案

深度解析主流 EVM Layer 2 解决方案&#xff1a;zk Rollups 和 Optimistic Rollups 随着以太坊网络的不断演进和 DeFi 生态系统的迅速增长&#xff0c;以太坊 Layer 2 解决方案日益受到关注。 其中&#xff0c;zk Rollups 和 Optimistic Rollups 作为两种备受瞩目的主流 EVM&…

地质地貌卫星影像集锦(三 矿产资源篇)

1. 元古代沉积岩的抬升 这个地区位于Leigh Creek中部&#xff0c;距离澳大利亚南部的阿德莱德约500km&#xff0c;弗林德斯山脉的北面是Gawler克拉通。弗林德斯山脉是由元古代沉积岩抬升后形成的块体&#xff0c;在其之下的是寒武纪的岩石&#xff0c;它座落在距阿德莱德北…

How to install JDK on mac

文章目录 1. Install JDK on mac2. zshenv, zshrc, zprofile3. 查看java环境变量配置 1. Install JDK on mac Installation of the JDK on macOS 2. zshenv, zshrc, zprofile How Do Zsh Configuration Files Work? 3. 查看java环境变量配置 open Terminal&#xff0c;cd…

HTTP 常见面试题(计算机网络)

HTTP 基本概念 一、HTTP 是什么&#xff1f; HTTP(HyperText Transfer Protocol) &#xff1a;超文本传输协议。 HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。 「HTTP 是用于从互联网服务器传输超文本到本…

虚拟机打不开

问题 另一个程序已锁定文件的一部分&#xff0c;进程无法访问 打不开磁盘“G:\centeros\hadoop104kl\hadoop100-cl2.vmdk”或它所依赖的某个快照磁盘。 模块“Disk”启动失败。 未能启动虚拟机。 原因 前一次非正常关闭虚拟机导致.lck 文件是VMWare软件的一种磁盘锁文件&…

【java探索之旅】逻辑控制掌握 顺序结构 分支语句

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; Java编程秘籍 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一、逻辑控制的概念二、顺序结构三、分支结构3.1 if语句3.2 if习题巩固3.3 细节注意项…

【QT+QGIS跨平台编译】056:【pdal_lepcc+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

点击查看专栏目录 文章目录 一、pdal_lepcc介绍二、pdal下载三、文件分析四、pro文件五、编译实践一、pdal_lepcc介绍 pdal_lepcc 是 PDAL(Point Data Abstraction Library)的一个插件,用于点云数据的压缩。它基于 EPCC(Entwine Point Cloud Compression)算法,提供了对点…