类的函数成员(三):拷贝构造函数
一.什么是拷贝构造函数?
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。
所以,从始至终,没有调用过拷贝构造函数。
相关文章:

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

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

Centos 7 安装通过yum安装google浏览器
在CentOS 7上使用yum安装Google Chrome浏览器稍微复杂一些,因为Chrome并不直接包含在默认的Yum仓库中。按照以下步骤来操作: 1、添加Google Chrome仓库 首先,您需要手动添加Google Chrome的Yum仓库。打开终端,并使用文本编辑器&a…...
题目:学习使用按位与 。
题目:学习使用按位与 & 。 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 主从模式想必大家都不陌生,大家在学习过程中为了学习方便都在自己本地搭建了 jenkins 环境,然后通过 javaweb 方式实现,对于 docker 下实现主从模式大家好像兴趣挺大。 今天就通过这篇文章给大家讲讲怎么玩,希望对大家有…...
WebSocket 对于手游的意义
WebSocket作为一个HTTP的升级协议,其实对HTTP协议用的不多,主要是消息头相关部分,WebScoket协议最初的动机应该是给网页应用增加一个更贴近实时环境的通讯方式,让某些网页应用得到更佳的通讯质量(双工,低延…...
安卓APP的技术质量:如何提高
安卓APP的技术质量:如何提高 技术质量包括稳定性和性能,还有资源工具化程序.你的APP 的技术质量能够影响你的用户体验.一个高质量的体验不仅 最小化了技术问题的存在,而且也最大化地利用了安卓操作 系统和设备硬件的能力. 为了构建一个高质量的APP,遵循如下的指导原则: 形式因…...

二分查找 -- 力扣(LeetCode)第704题
题目 https://leetcode.cn/problems/binary-search/description/ 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 示例…...
Windows下如何确定虚函数在虚函数表中的位置
我需要用c#调用 c 的 类的函数, 虽然可以通过头文件的顺序,但是如果可以打印出虚函数在虚表中的Offset更好。 测试要求: Windows, x86 只有1层虚函数,没有被override过 虚函数调用如下 auto a_reqCreditDetail &XtTraderApi::reqCreditDetail; (a…...

C++设计模式:观察者模式(三)
1、定义与动机 观察者模式定义:定义对象间的一种1对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生比改变时,所有依赖于它的对象都得到通知并且自动更新 再软件构建过程中,…...
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代码分享给大家,记得点赞哦 #!/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优化提示效果
编者按:欢迎阅读“科研上新”栏目!“科研上新”汇聚了微软亚洲研究院最新的创新成果与科研动态。在这里,你可以快速浏览研究院的亮点资讯,保持对前沿领域的敏锐嗅觉,同时也能找到先进实用的开源工具。 提示词的好坏决…...

测开面经(pytest测试案例,接口断言,多并发断言)
pytest对用户登录接口进行自动化脚本设计 a. 创建一个名为"test_login.py"的测试文件,编写以下测试脚本 import pytest import requests# 测试用例1:验证登录成功的情况 # 第一个测试用例验证登录成功的情况,发送有效的用户名和密…...

Golang 开发实战day09 - package Scope
🏆个人专栏 🤺 leetcode 🧗 Leetcode Prime 🏇 Golang20天教程 🚴♂️ Java问题收集园地 🌴 成长感悟 欢迎大家观看,不执着于追求顶峰,只享受探索过程 Golang 教程09 - package Sc…...

24考研-东南大学916经验贴
文章目录 一、个人情况二、初试备考经验1.政治 67,客观382.英语 60,客观大概40左右3.数学 136,客观应该满分4.专业课 数据结构计网 114小分不清楚 三、复试备考经验笔试:C面试复试流程 附一下成绩单: 一、个人情况 本…...
【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(B-Tree) 1.1、B-Trees的特点 二叉树缺点:顺序插入时,会形成一个链表,查询性能大大降低。大数据量情况下,层级较深,检索速度慢。红黑树:大数据量情况下,层…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...

Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...

从零开始了解数据采集(二十八)——制造业数字孪生
近年来,我国的工业领域正经历一场前所未有的数字化变革,从“双碳目标”到工业互联网平台的推广,国家政策和市场需求共同推动了制造业的升级。在这场变革中,数字孪生技术成为备受关注的关键工具,它不仅让企业“看见”设…...

Qt的学习(二)
1. 创建Hello Word 两种方式,实现helloworld: 1.通过图形化的方式,在界面上创建出一个控件,显示helloworld 2.通过纯代码的方式,通过编写代码,在界面上创建控件, 显示hello world; …...