当前位置: 首页 > news >正文

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

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

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。

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

相关文章:

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

一.什么是拷贝构造函数&#xff1f; 1.1 概念 同一个类的对象在内存中有完全相同的结构&#xff0c;如果作为一个整体进行复制或称拷贝是完全可行的。这个拷贝过程只需要拷贝数据成员&#xff0c;而函数成员是共用的&#xff08;只有一份拷贝&#xff09;。 在建立对象…...

C#操作MySQL从入门到精通(8)——对查询数据进行高级过滤

前言 我们在查询数据库中数据的时候,有时候需要剔除一些我们不想要的数据,这时候就需要对数据进行过滤,比如学生信息中,我只需要年龄等于18的,同时又要家乡地址是安徽的,类似这种操作专栏第7篇的C#操作MySQL从入门到精通(7)——对查询数据进行简单过滤简单过滤方法就无法…...

Centos 7 安装通过yum安装google浏览器

在CentOS 7上使用yum安装Google Chrome浏览器稍微复杂一些&#xff0c;因为Chrome并不直接包含在默认的Yum仓库中。按照以下步骤来操作&#xff1a; 1、添加Google Chrome仓库 首先&#xff0c;您需要手动添加Google Chrome的Yum仓库。打开终端&#xff0c;并使用文本编辑器&a…...

题目:学习使用按位与 。

题目&#xff1a;学习使用按位与 & 。   There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about being cheated shoul…...

逐步分解,一文教会你如何用 jenkins+docker 实现主从模式

jenkins 主从模式想必大家都不陌生&#xff0c;大家在学习过程中为了学习方便都在自己本地搭建了 jenkins 环境&#xff0c;然后通过 javaweb 方式实现&#xff0c;对于 docker 下实现主从模式大家好像兴趣挺大。 今天就通过这篇文章给大家讲讲怎么玩&#xff0c;希望对大家有…...

WebSocket 对于手游的意义

WebSocket作为一个HTTP的升级协议&#xff0c;其实对HTTP协议用的不多&#xff0c;主要是消息头相关部分&#xff0c;WebScoket协议最初的动机应该是给网页应用增加一个更贴近实时环境的通讯方式&#xff0c;让某些网页应用得到更佳的通讯质量&#xff08;双工&#xff0c;低延…...

安卓APP的技术质量:如何提高

安卓APP的技术质量:如何提高 技术质量包括稳定性和性能,还有资源工具化程序.你的APP 的技术质量能够影响你的用户体验.一个高质量的体验不仅 最小化了技术问题的存在,而且也最大化地利用了安卓操作 系统和设备硬件的能力. 为了构建一个高质量的APP,遵循如下的指导原则: 形式因…...

二分查找 -- 力扣(LeetCode)第704题

题目 https://leetcode.cn/problems/binary-search/description/ 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例…...

Windows下如何确定虚函数在虚函数表中的位置

我需要用c#调用 c 的 类的函数, 虽然可以通过头文件的顺序&#xff0c;但是如果可以打印出虚函数在虚表中的Offset更好。 测试要求: Windows, x86 只有1层虚函数&#xff0c;没有被override过 虚函数调用如下 auto a_reqCreditDetail &XtTraderApi::reqCreditDetail; (a…...

C++设计模式:观察者模式(三)

1、定义与动机 观察者模式定义&#xff1a;定义对象间的一种1对多&#xff08;变化&#xff09;的依赖关系&#xff0c;以便当一个对象&#xff08;Subject&#xff09;的状态发生比改变时&#xff0c;所有依赖于它的对象都得到通知并且自动更新 再软件构建过程中&#xff0c…...

CentOS运行Py脚本报错illegal instruction故障处理

测试Python脚本运行环境及依赖 [root@localhost network]# python3 devops_ping_test1.py Illegal instruction ①、illegal instruction报错 由于本人第一次测试时运行是正常的,但是在测试过程中多次修改、覆盖代码运行后提示Illegal instruction(非法指令),所以不能单…...

软件设计师——1.备考提纲

知识点说明比例软件工程基础知识11开发模型、设计原则、测试方法、质量特性、CMM、Pert图、风险管理14.67%面向对象12面向对象基本概念、面向对象分析与设计、UML、设计模式16.00%数据结构与算法10数组、栈、队列、树与二叉树、图、查找与排序、常见算法13.33%程序设计语言6文法…...

[开源] 基于GRU的时间序列预测模型python代码

基于GRU的时间序列预测模型python代码分享给大家&#xff0c;记得点赞哦 #!/usr/bin/env python # coding: utf-8import time time_start time.time() import numpy as np import matplotlib.pyplot as plt import pandas as pd import math from keras.models import Sequent…...

SQL SERVER 备份

目录 1.备份概念 1.1 为何备份? 1.2 SQL Server 备份模式 2.SQL Server 数据库备份 2.1 借助SSMS备份数据库 2.2 借助 T-SQL 备份数据库 2.3 创建加密备份 2.4 备份文件和文件组 权限 步骤 2.5 备份事务日志 3.维护计划 3.1 完整备份 3.2 差异备份...

提示词专场:从调整提示改善与LLMs的沟通,到利用LLMs优化提示效果

编者按&#xff1a;欢迎阅读“科研上新”栏目&#xff01;“科研上新”汇聚了微软亚洲研究院最新的创新成果与科研动态。在这里&#xff0c;你可以快速浏览研究院的亮点资讯&#xff0c;保持对前沿领域的敏锐嗅觉&#xff0c;同时也能找到先进实用的开源工具。 提示词的好坏决…...

测开面经(pytest测试案例,接口断言,多并发断言)

pytest对用户登录接口进行自动化脚本设计 a. 创建一个名为"test_login.py"的测试文件&#xff0c;编写以下测试脚本 import pytest import requests# 测试用例1&#xff1a;验证登录成功的情况 # 第一个测试用例验证登录成功的情况&#xff0c;发送有效的用户名和密…...

Golang 开发实战day09 - package Scope

&#x1f3c6;个人专栏 &#x1f93a; leetcode &#x1f9d7; Leetcode Prime &#x1f3c7; Golang20天教程 &#x1f6b4;‍♂️ Java问题收集园地 &#x1f334; 成长感悟 欢迎大家观看&#xff0c;不执着于追求顶峰&#xff0c;只享受探索过程 Golang 教程09 - package Sc…...

24考研-东南大学916经验贴

文章目录 一、个人情况二、初试备考经验1.政治 67&#xff0c;客观382.英语 60&#xff0c;客观大概40左右3.数学 136&#xff0c;客观应该满分4.专业课 数据结构计网 114小分不清楚 三、复试备考经验笔试&#xff1a;C面试复试流程 附一下成绩单&#xff1a; 一、个人情况 本…...

【AI面试】YOLO 如何通过 k-means 得到 anchor boxes的?Yolo、SSD 和 faster rcnn 的正负样本定义

如果你的项目中有目标检测相关的内容,那么本篇内容就一定要好好看看。不会的看到了理解下,会的看看是不是和自己理解的一样。 一、YOLO 如何通过 k-means 得到 anchor boxes的? YOLOv2 和 YOLOv3是目标检测领域中非常流行的算法,它们都使用了anchor boxes来提高检测的准确…...

MySQL高级篇(B-Tree、Btree)

目录 1、Btree&#xff08;B-Tree&#xff09; 1.1、B-Trees的特点 二叉树缺点&#xff1a;顺序插入时&#xff0c;会形成一个链表&#xff0c;查询性能大大降低。大数据量情况下&#xff0c;层级较深&#xff0c;检索速度慢。红黑树&#xff1a;大数据量情况下&#xff0c;层…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

如何在网页里填写 PDF 表格?

有时候&#xff0c;你可能希望用户能在你的网站上填写 PDF 表单。然而&#xff0c;这件事并不简单&#xff0c;因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件&#xff0c;但原生并不支持编辑或填写它们。更糟的是&#xff0c;如果你想收集表单数据&#xff…...