当前位置: 首页 > 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;层…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

Mobile ALOHA全身模仿学习

一、题目 Mobile ALOHA&#xff1a;通过低成本全身远程操作学习双手移动操作 传统模仿学习&#xff08;Imitation Learning&#xff09;缺点&#xff1a;聚焦与桌面操作&#xff0c;缺乏通用任务所需的移动性和灵活性 本论文优点&#xff1a;&#xff08;1&#xff09;在ALOHA…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP

编辑-虚拟网络编辑器-更改设置 选择桥接模式&#xff0c;然后找到相应的网卡&#xff08;可以查看自己本机的网络连接&#xff09; windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置&#xff0c;选择刚才配置的桥接模式 静态ip设置&#xff1a; 我用的ubuntu24桌…...

Java求职者面试指南:计算机基础与源码原理深度解析

Java求职者面试指南&#xff1a;计算机基础与源码原理深度解析 第一轮提问&#xff1a;基础概念问题 1. 请解释什么是进程和线程的区别&#xff1f; 面试官&#xff1a;进程是程序的一次执行过程&#xff0c;是系统进行资源分配和调度的基本单位&#xff1b;而线程是进程中的…...