C++从入门到精通——类的6个默认成员函数之拷贝构造函数

拷贝构造函数

  • 前言
  • 一、拷贝构造函数概念
    • 理解
    • 定义
  • 二、拷贝构造函数的特征
  • 三、注意要点
    • 写法
    • 实践
    • 传址返回与引用返回的区别
      • 传址返回
      • 引用返回
    • 传值返回和传址返回的对比
    • 总结
    • 测试


前言

类的6个默认成员函数:如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

class Date {};

在这里插入图片描述


一、拷贝构造函数概念

理解

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。

在这里插入图片描述
那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?

定义

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

C++拷贝构造函数是一种特殊的构造函数,用于创建对象时,使用一个已有对象的内容来初始化新的对象。它接受一个同类对象作为参数,并按照该对象的数据成员的值来创建新的对象。

拷贝构造函数通常用于以下情况:

  • 在创建对象时,使用同类已有对象的值来初始化新对象。
  • 以值传递方式将对象传递给函数。
  • 以值返回方式从函数返回对象。

拷贝构造函数的定义形式为:


类名(const 类名&obj)
{// 构造函数的实现
}

其中,类名是要创建的对象的类名,obj是要拷贝的对象。

拷贝构造函数的工作原理是将obj的数据成员的值复制给新创建的对象。这意味着新对象的数据成员会与原对象具有相同的值,但是它们是独立的,改变其中一个对象的数据成员的值不会影响另一个对象的数据成员。

如果没有显式定义拷贝构造函数,编译器会提供一个默认的拷贝构造函数。默认的拷贝构造函数执行的是浅拷贝,即简单地将原对象的值复制给新对象的数据成员。如果类中包含指针类型的数据成员,需要自己定义拷贝构造函数,进行深拷贝,确保指针指向的对象也被复制。

注意,拷贝构造函数是类成员函数,通常定义在类的公有部分。拷贝构造函数是通过对象名来调用的,而不是通过函数名来调用。

二、拷贝构造函数的特征

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(const Date& d)   // 正确写法Date(const Date d)   // 错误写法:编译报错,会引发无穷递归{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);return 0;
}

在这里插入图片描述
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
和构造函数不一样,构造函数内置类型不会初始化,拷贝构造函数会初始化

class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数Date d2(d1);return 0;
}

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

  1. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
    当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
// 这里会发现下面的程序会崩溃掉?这里就需要深拷贝去解决。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
}

在这里插入图片描述
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

  1. 拷贝构造函数典型调用场景:
    • 使用已存在对象创建新对象
    • 函数参数类型为类类型对象
    • 函数返回值类型为类类型对象
class Date
{
public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}
private:int _year;int _month;int _day;
};
Date Test(Date d)
{Date temp(d);return temp;
}
int main()
{Date d1(2022, 1, 13);Test(d1);return 0;
}

在这里插入图片描述
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

三、注意要点

写法

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(const Date& d)   // 正确写法Date(const Date& d)   // 错误写法:编译报错,会引发无穷递归{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);return 0;
}

除了下面这种写法外

Data d2(d1);   

我们还可以写成,这种写法也是拷贝构造

Data d2 = d1;

实践

  • 如果没有管理资源,一般情况下不需要写拷贝构造函数,默认生成的拷贝构造函数就可以。如Date(日期类)
  • 如果都是自定义类型成员,内置类型成员没有指向资源,也类似默认生成的拷贝构造函数就可以。如MyQueue
  • 一般情况下,不需要显示写析构函数,就不需要写拷贝构造函数
  • 如果内部有指针或者一些值指向资源,需要显示写析构释放,通常就需要显示写构造函数完成深拷贝。如:Stack Queue List

传址返回与引用返回的区别

关于下面代码的展示,VS2022编译器可能显示不出来,因为编译器等级比较高,像下面的情况,编译器会自行优化,使代码运行效率更高,致使本来的结果显示不出来。

传址返回

传址返回会生成Date d 的临时拷贝进行返回
在这里插入图片描述
在这里插入图片描述

引用返回

引用返回会直接返回Date d的地址,而不会进入临时拷贝

~Date(){cout << "~Date()" << endl;_year = -1;_month = -1;_day = -1;}

出了函数作用域之后会调用析构函数,致使结果为-1,此时的ref类似野指针
在这里插入图片描述
依次类推,我们再定义一个fx()函数,在fx()函数的空间里存放一些变量,ret空间里的内容会被fx()函数里的内容给覆盖

在这里插入图片描述

当出了作用域,返回对象还在没有析构,那就可以用引用返回,减少拷贝,比如用static修饰

传值返回和传址返回的对比

Date operator=运用的下篇文章赋值运算符重载,可以看到传值和传址在遇到不同问题时有不同的表现,如下,在运算符重载的问题下,传址调用比传值调用的效率更高
在这里插入图片描述

总结

返回对象是一个局部对象或临时对象,出了当前func函数作用域,就析构销毁了,那么不能用引用返回,用引用返回时存在风险的,因为引用对象在func函数栈帧已经销毁了

虽然引用返回可以减少一次拷贝,但是出了函数作用域,返回对象还在,才能用引用返回

即下述情况

  • 返回对象生命周期到了,会析构,传值返回
  • 返回对象生命周期没到,不会析构,传引用返回

测试

#include<iostream>using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){cout<< "Date(int,int,int):" << this << endl;_year = year;_month = month;_day = day;}Date(const Date& d){cout << "Date(const Date& d)"<<this << endl;_year = d._year;_month = d._month;_day = d._day;}~Date(){cout << "~Date()" <<this<< endl;}
private:int _year;int _month;int _day;
};
Date func(Date d)
{Date temp(d);return temp;
}
//Date& fun()
//{
//	Date d(2024,4,14);
//	return d;
//}
int main()
{Date d;func(d);//const Date& ret = func();return 0;
}
//class Date
//{
//public:
//	Date(int year, int minute, int day)
//	{
//		cout << "Date(int,int,int):" << this << endl;
//	}
//	Date(const Date& d)
//	{
//		cout << "Date(const Date& d):" << this << endl;
//	}
//	~Date()
//	{
//		cout << "~Date():" << this << endl;
//	}
//private:
//	int _year;
//	int _month;
//	int _day;
//};
//Date Test(Date d)
//{
//	Date temp(d);
//	return temp;
//}
//int main()
//{
//	Date d1(2022, 1, 13);
//	Test(d1);
//	return 0;
//}

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

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

相关文章

抢占用户|AI助力企业高效挖掘潜在客户,推动高质量转化

随着人工智能&#xff08;AI&#xff09;技术的崛起&#xff0c;企业终于可以在这个数字化时代获得一种强大的工具&#xff0c;帮助企业迅速而准确地找到潜在客户。AI不仅能够处理海量的数据&#xff0c;还能自动分析和识别潜在客户的特征和行为模式&#xff0c;为企业营销提供…

母婴用品网站设计与实现 java母婴用品网站源代码+论文+ppt

母婴用品网站设计与实现:基于JSP与MySQL的实践探索 引言 随着信息化时代的到来,母婴用品网站作为信息获取和商品交易的平台,其开发与设计成为了一个迫切的课题。本文将探讨如何利用JSP技术和MySQL数据库构建一个功能完备、用户友好的母婴用品网站。 系统概述 背景与必要…

【WEEK11】 【DAY1】Employee Management System Part 2【English Version】

2024.5.6 Monday Continuing from 【WEEK10】 【DAY2】Employee Management System Part 1【English Version】 Contents 10.3. Page Internationalization10.3.1. Preparation10.3.2. Configuration File Writing10.3.2.1. Create an i18n (abbreviation for internationaliza…

YOLOv8深度剖析专栏导航

本专栏计划更新关于YOLOv8目标检测、实例分割、关键点检测、旋转目标检测任务的实践和理论知识。实践篇会包括训练自己的数据集、并对模型进行验证、预测和导出&#xff1b;理论篇会介绍各任务的预测流程和训练流程。下面是已更新的文章目录&#xff1a; 1.软件安装及YOLOv8环境…

系统守护者:揭秘限流的四大算法与实战攻略

在网络世界的广阔天地中&#xff0c;服务如同繁忙的港口&#xff0c;每天迎来送往数不尽的请求。然而&#xff0c;潮水般的流量背后隐藏着风险&#xff0c;稍有不慎&#xff0c;系统便会因不堪重负而倾覆。这时&#xff0c;"限流"便如同智慧的灯塔&#xff0c;指引着…

专业的保密网文件导入导出系统,让文件流转行为更可控安全

军工单位因其涉及国防安全和军事机密&#xff0c;对保密工作有极高的要求&#xff0c;通常会采取严格的网络隔离措施来保护敏感信息和提高网络安全性。常见的方式是通过物理隔离将网络彻底分隔开来&#xff0c;比如保密网和非保密网。网络隔离后&#xff0c;仍有数据交换的需求…

Linux命令--tcpdump命令--使用与详解

原文网址&#xff1a;Linux命令--tcpdump命令--使用与详解_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Linux的tcpdump命令的用法。 tcpdump可以输出网络的通信记录&#xff0c;可以用来排查问题、查看被攻击网站的详细情况等。 示例 捕获eth0的数据包 tcpdump -i ens33 捕…

GORM的常见命令

文章目录 一、什么是GORM&#xff1f;二、GORM连接mysql以及AutoMigrate创建表三、查询1、检索此对象是否存在于数据库&#xff08;First,Take,Last方法&#xff09;2、Find()方法检索3、根据指定字段查询 四、更新1、Save() 保存多个字段2、更新单个字段 五、删除 一、什么是G…

Python中设计注册登录代码

import hashlib import json import os import sys # user interface 用户是界面 UI """ 用户登录系统 1.注册 2.登陆 0.退出 """ # 读取users.bin def load(path): return json.load(open(path, "rt")) # 保存user.bin def save(dic…

Figma 高效技巧:设计系统中的图标嵌套

Figma 高效技巧&#xff1a;设计系统中的图标嵌套 在设计中&#xff0c;图标起着不可或缺的作用。一套便捷易用的图标嵌套方法可以有效提高设计效率。 分享一下我在图标嵌套上走过的弯路和经验教训。我的图标嵌套可以分三个阶段&#xff1a; 第一阶段&#xff1a;建立图标库 一…

目标检测实战(八): 使用YOLOv7完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)

文章目录 一、目标检测介绍二、YOLOv7介绍三、源码/论文获取四、环境搭建4.1 环境检测 五、数据集准备六、 模型训练七、模型验证八、模型测试九、错误总结9.1 错误1-numpy jas mp attribute int9.2 错误2-测试代码未能跑出检测框9.3 错误3- Command git tag returned non-zero…

【unity】(2)GameObject

GameObject类是基本的游戏对象类型&#xff0c;它可以用来代表场景中的任何实体。 基本属性 name 类型&#xff1a;string说明&#xff1a;GameObject的名称。用法&#xff1a; GameObject go new GameObject(); go.name "My GameObject";activeSelf 类型&#xf…

Apple OpenELM设备端语言模型

Apple 发布的 OpenELM&#xff08;一系列专为高效设备上处理而设计的开源语言模型&#xff09;引发了相当大的争论。一方面&#xff0c;苹果在开源协作和设备端AI处理方面迈出了一步&#xff0c;强调隐私和效率。另一方面&#xff0c;与微软 Phi-3 Mini 等竞争对手相比&#xf…

森林消防新利器:高扬程水泵的革新与应用/恒峰智慧科技

随着全球气候变化的加剧&#xff0c;森林火灾的频发已成为威胁生态安全的重要问题。在森林消防工作中&#xff0c;高效、快速的水源供给设备显得尤为重要。近年来&#xff0c;高扬程水泵的广泛应用&#xff0c;为森林消防工作带来了新的希望与突破。 一、高扬程水泵的技术优势 …

【Node.js】使用 PostgreSQL、Sequelize 和 Express.js 进行 Node.js 认证

使用 PostgreSQL、Sequelize 和 Express.js 进行 Node.js 认证 作者&#xff1a;Racheal Kuranchie 来源&#xff1a;https://medium.com/rachealkuranchie/node-js-authentication-with-postgresql-sequelize-and-express-js-20ae773da4c9 使用 PostgreSQL、Sequelize 和 Expr…

Linux上安装及卸载OpenJDK

Linux上安装Java Development Kit (JDK) 8的步骤如下&#xff1a; 1. 添加Java JDK 8的Yum源 首先&#xff0c;你需要添加Java JDK 8的Yum源到系统。这可以通过下载并安装Oracle JDK的方式完成&#xff0c;但由于Oracle JDK在某些情况下可能需要遵守特定的许可协议&#xff0c…

探索Baidu Comate:编程世界中的新利器

文章目录 Baidu Comate 介绍Baidu Comate的优势Baidu Comate安装过程Baidu Comate实战演练代码调优代码解释代码生成注释生成 总结 Baidu Comate 介绍 随着GPT的大火&#xff0c;衍生了各种AI工具&#xff0c;这些AI工具遍布在各行业各领域中&#xff0c;有AI写作、AI办公、AI…

[力扣题解] 216. 组合总和 III

题目&#xff1a;216. 组合总和 III 思路 回溯法 代码 class Solution { private:vector<vector<int>> result;vector<int> path;public:void function(int k, int n, int startindex, int sum){int i;// 剪枝// 超过了, 不用找了;if(sum > n){return…

向各位请教一个问题

这是菜鸟上的一道题目&#xff0c;单单拿出来问问大家&#xff0c;看看能不能解惑 &#xff0c;谢谢各位&#xff01; 题目25&#xff1a;求12!3!...20!的和 解题思路&#xff1a;这个题不知道为什么我用DEV C 5.11显示出来为0.000000&#xff0c;可能版本有问题&#xff1f;&a…

linux挂载数据盘详细步骤

在 Linux 上挂载数据盘通常涉及以下步骤&#xff1a; 1. **识别数据盘**&#xff1a;首先&#xff0c;你需要找到要挂载的磁盘设备。在命令行中使用 lsblk 或 fdisk -l 命令查看系统中的磁盘和分区。你会看到类似 sda, sdb, sdc 这样的设备名称&#xff0c;以及各自的分区。 l…