C++ 类的默认成员函数- 拷贝构造函数

news2024/9/21 8:03:10

 个人主页:Jason_from_China-CSDN博客

所属栏目:C++系统性学习_Jason_from_China的博客-CSDN博客

所属栏目:C++知识点的补充_Jason_from_China的博客-CSDN博客

 概念概述

  • 默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。一个类,我们不写的情况下编译器会默认生成以下 6 个默认成员函数,需要注意的是这 6 个中最重要的是前 4 个,最后两个取地址重载不重要,我们稍微了解一下即可。其次就是 C++11 以后还会增加两个默认成员函数,移动构造和移动赋值,这个我们后面再讲解。默认成员函数很重要,也比较复杂,我们要从两个方面去学习。

包括

 拷贝构造函数

概念概述

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数。

同时C++规定了,这里是规定,传值传参必须调用拷贝构造

拷贝构造函数的特点

  1. 拷贝构造函数是构造函数的一个重载。
  2. 拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
  3. C++ 规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
  4. 若未显式定义拷贝构造,编译器会生成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝 / 浅拷贝 (一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。
  5. 像 Date 这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显式实现拷贝构造。像 Stack 这样的类,虽然也都是内置类型,但是 _a 指向了资源,编译器自动生成的拷贝构造完成的值拷贝 / 浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝 (对指向的资源也进行拷贝)。像 MyQueue 这样的类型内部主要是自定义类型 Stack 成员,编译器自动生成的拷贝构造会调用 Stack 的拷贝构造,也不需要我们显式实现 MyQueue 的拷贝构造。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要。
  6. 传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名 (引用),没有产生拷贝。但是如果返回对象是一个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于一个野引用,类似一个野指针一样。传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能用引用返回

拷贝函数的使用

拷贝构造的实现其实是很简单,这里先举出实例讲解,之后会进行解释,主要就是传参的时候,如果传递的是传值传参,那么需要用引用进行接收,不然会导致死循环从而导致崩溃

下面的的代码里面首先我们实现声明和实现的分离

//.h
//拷贝构造函数的实现
class Date
{
public:
	//构造函数的实现
	Date();
	//拷贝构造的实现
	Date(Date& d);

	//打印函数的实现
	void print();
private:
	int _year;
	int _month;
	int _day;
};

//构造函数的实现
Date::Date()
{
	_year = 2000;
	_month = 2;
	_day = 28;
	cout << "构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//拷贝构造的实现
Date::Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//.cpp
int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();
	return 0;
}

运行代码,可以清楚的看见,两个拷贝构造都实现成功,这两种形式,看看你喜欢哪一种拷贝构造实现形式

拷贝构造的过程

//.h

//拷贝构造函数的实现
class Date
{
public:
	//构造函数的实现
	Date();
	//拷贝构造的实现
	Date(Date& d);

	//打印函数的实现
	void print();
private:
	int _year;
	int _month;
	int _day;
};

//构造函数的实现
Date::Date()
{
	_year = 2000;
	_month = 2;
	_day = 28;
	cout << "构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//拷贝构造的实现
Date::Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}


//.cpp
void Func(Date& d)
{
	cout << "拷贝构造函数的调用" << endl;
	d.print();
}
int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();


	//传值传参的调用
	Func(d3);
	return 0;
}

这里我们观察拷贝构造是如何完成的

第一步,类类型的,传值传参

第二步,返回函数

全过程图解

在这个例子中,void Func(Date& d)中的&可以写也可以不写,但加上引用可以避免不必要的拷贝构造函数调用,提高效率。

如果写成void Func(Date d),在调用Func函数时,会调用拷贝构造函数将实参对象拷贝一份传递给函数参数d,可能会有一定的性能开销,尤其是当Date类比较复杂时。

如果写成void Func(Date& d),则是通过引用传递参数,不会调用拷贝构造函数,只是传递了对象的引用,效率更高。综上所述,从性能角度考虑,建议加上引用符号&。

所以可以进行与优化,我们函数依旧采取引用接收,减少拷贝

拷贝构造函数的原理解释

首先我们需要明确一点就是,C++在传值传参的时候会调用拷贝构造

什么是传值传参,顾名思义肯定是传递数值。

如果我们传值传参,但是接收的话不采取引用接收,就会导致一直情况

拷贝构造和指针

class MyClass
{
public:
	MyClass();
	MyClass(MyClass* M);
	//打印函数的实现
	void print()
	{
		cout << _year << "/" << _month << "/" << _day << endl << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
//构造函数
MyClass::MyClass()
{
	_year = 1999;
	_month = 2;
	_day = 28;
	cout << "MyClass构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}
//拷贝构造,和指针的联合实现
MyClass::MyClass(MyClass* M)
{
	_year = M->_year;
	_month = M->_month;
	_day = M->_day;
}

int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();


	//传值传参的调用
	Func(d3);

	//拷贝构造和指针的联合实现
	MyClass M1;
	MyClass M2 = M1;
	M2.print();
	return 0;
}

拷贝构造函数需要加上conts和不加const的区别

这里是有一点难度的

接下来 我们给出一个代码,但是这个代码是一个典型的错误代码:此时我们发现是报错的

这里解决报错的原因需要在拷贝构造函数上面加上const,因为这里存在权限放大的行为,之前我们就说过权限可以缩小,但是不能放大。

这里就产生了权限放大的问题,所以我们需要解决权限放大的问题

接下来我们上正确的代码

class MyClass
{
public:
	MyClass(MyClass* M);
	MyClass(int year, int month, int day);
	MyClass(const MyClass& M);
	//打印函数的实现
	void print()
	{
		cout << _year << "/" << _month << "/" << _day << endl << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
//构造函数
MyClass::MyClass(int year = 1999, int month = 2, int day = 28)
{
	_year = year;
	_month = month;
	_day = day;
	cout << "MyClass构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}
//拷贝构造,和指针的联合实现,这里就会导致这里只是一个普通的构造函数
//MyClass::MyClass(MyClass* M)
//{
//	_year = M->_year;
//	_month = M->_month;
//	_day = M->_day;
//}
//拷贝构造的实现
MyClass::MyClass(const MyClass& M)
{
	_year = M._year;
	_month = M._month;
	_day = M._day;
}


//拷贝构造函数的实现
class Date
{
public:
	//构造函数的实现
	Date();
	//拷贝构造的实现
	Date(const Date& d);

	//打印函数的实现
	void print();
private:
	int _year;
	int _month;
	int _day;
};

//构造函数的实现
Date::Date()
{
	_year = 2000;
	_month = 2;
	_day = 28;
	cout << "构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//拷贝构造的实现
//Date::Date(Date* const this, Date& d)
Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

void Func(Date& d)
{
	cout << "拷贝构造函数的调用" << endl;
	d.print();
}

Date F()
{
	Date d1;
	return d1;
}

MyClass f()
{
	MyClass M1(12, 12, 12);
	return M1;
}
int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();


	//传值传参的调用
	Func(d3);

	拷贝构造和指针的联合实现
	MyClass M1(2000, 1, 1);//初始化2000
	MyClass M2;//默认初始化 1999
	MyClass M3 = M1;
	//M2.print();
	M3.print();

	//const在拷贝构造函数里面的使用
	cout << "拷贝构造const的实现"<<endl;
	MyClass M4 = f();
	Date d4 = F();

	return 0;
}

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

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

相关文章

十四,在Spring Boot当中对应“ Tomcat 服务器的相关配置”和“服务器的切换”的详细说明

十四&#xff0c;在Spring Boot当中对应“ Tomcat 服务器的相关配置”和“服务器的切换”的详细说明 文章目录 十四&#xff0c;在Spring Boot当中对应“ Tomcat 服务器的相关配置”和“服务器的切换”的详细说明1. 基本介绍2. 准备工作&#xff1a;3. 内置 Tomcat 的配置3.1 第…

C++速通LeetCode中等第3题-盛最多水的容器

双指针法&#xff1a;两个指针分别指向左右边界&#xff0c;记录最大面积&#xff0c;由于面积由短板决定&#xff0c;两个指针中较短的短指针向内移动一格&#xff0c;再次记录最大面积&#xff0c; 直到两指针相遇&#xff0c;得出答案。 class Solution { public:int maxAr…

其他图嵌入方法(6)

前面写了图神经网络可以把节点或图映射到一个低维空间&#xff0c;我们将其称为图嵌入。然而&#xff0c;除了图神经网络还有许多的图嵌入方法。本节将介绍其他浅层图嵌入方法。早在图神经网络发明之前&#xff0c;图嵌入的概念就经常出现在流形学习和网络分析的研究中。相对于…

科技引领未来生活——“光影漫游者”展览馆应用—轻空间

随着科技的快速发展&#xff0c;展览馆作为展示文化、科技和艺术的场所&#xff0c;正逐渐从传统的静态展示向高科技互动体验转变。由轻空间打造的“光影漫游者”展览馆&#xff0c;凭借其前沿的气承式结构和智能化系统&#xff0c;将参观者带入了一个未来感十足、充满科技魅力…

海外服务器哪个速度最快且性能稳定

海外服务器的速度与性能稳定性受多种因素影响&#xff0c;包括地理位置、网络架构、基础设施质量以及用户网络路径等。在众多选择中&#xff0c;几个特定地区的服务器因其卓越表现而备受推崇。 首先&#xff0c;美国硅谷(加利福尼亚州)与纽约的服务器以其技术领先、网络连接稳定…

OceanBase 中 schema 的定义与应用

背景 经常在OceanBase 的问答社区 里看到一些关于 “schema 是什么” 的提问。 先纠正一些同学的误解&#xff0c; OceanBase 中的 Schema 并不简单的等同于 Database&#xff0c;本次分享将探讨 OceanBase 中的Schema是什么&#xff0c;及一些大家经常遇到的问题。 具体而…

2023 hnust 湖科大 毕业实习 报告+实习鉴定表

2023 hnust 湖科大 毕业实习 报告实习鉴定表 岗位 IT公司机房运维 实习报告 实习鉴定表 常见疑问 hnust 湖科大 毕业实习常见问题30问&#xff08;2021 年7月&#xff0c;V0.9&#xff09;-CSDN博客时间&#xff1a;大四开学第三四周毕业实习23年是企业&#xff08;黑马&am…

【H2O2|全栈】关于CSS(6)CSS基础(五)

目录 CSS基础知识 前言 准备工作 网页项目规范 创建项目 布局 补充一部分属性 outline border-radius 预告和回顾 后话 CSS基础知识 前言 本系列博客将分享层叠样式表&#xff08;CSS&#xff09;有关的知识点。 本期博客主要分享的是网页项目规范&#xff0c;ou…

算法训练——day16快乐数

202. 快乐数 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结果为…

STM32快速复习(十二)FLASH闪存的读写

文章目录 一、FLASH是什么&#xff1f;FLASH的结构&#xff1f;二、使用步骤1.标准库函数2.示例函数 总结 一、FLASH是什么&#xff1f;FLASH的结构&#xff1f; 1、FLASH简介 &#xff08;1&#xff09;STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&…

【无标题】Java_Se 数据变量与运算符

标识符、变量、常量、数据类型、运算符、基本数据类型的类型转换等。这些是编程中的“砖块”&#xff0c;是编程的基础。要想开始正式编程&#xff0c;还需要再学“控制语句”&#xff0c;控制语句就像“水泥”&#xff0c;可以把“砖块”粘到一起&#xff0c;最终形成“一座大…

react学习笔记一:react介绍

将view规划成一个个的组件&#xff0c;是一个响应式的声明式的设计。 虚拟dom&#xff0c;减少dom操作。vue的虚拟dom是在react的基础上拓展来的。 单向数据流&#xff1a;是一种数据流动的模式。数据流的方向是有上到下的&#xff0c;在react中主要是从父组件流向子组件。 …

Django后台管理复杂模型

【图书介绍】《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》_django 5企业级web应用开发实战(视频教学版)-CSDN博客 《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》(王金柱)【摘要 书评 试读】- 京东图书 (jd.com) Django框架…

C#学习(四)C#连接Mysql实现增删改查

博主刚开始接触C#&#xff0c;本系列为学习记录&#xff0c;如有错误欢迎各位大佬指正&#xff01;期待互相交流&#xff01; 文章目录 一、安装Mysql1.1 启用Mysql1.2 登录Mysql 二、安装Navicat2.1 建立连接2.2 新建数据库2.3 新建表 三、创建Winform实现增、删、改、查3.1 下…

医学数据分析实训 项目七 集成学习--空气质量指标--天气质量分析和预测

项目七&#xff1a;集成学习 实践目的 理解集成学习算法原理&#xff1b;熟悉并掌握常用集成学习算法的使用方法&#xff1b;熟悉模型性能评估的方法&#xff1b;掌握模型优化的方法。 实践平台 操作系统&#xff1a;Windows7及以上Python版本&#xff1a;3.8.x及以上集成开…

ubuntu安装wordpress(基于LNMP环境)

参考链接 Ubuntu安装LNMP 安装步骤 环境需要LNMP环境&#xff0c;如果没有安装可以参考ZATA—LNMP简单安装 在mysql中设置wordpress所用的用户名和密码 #1. 登录mysql mysql -uroot -p #2. 创建wordpress数据库 create database wordpress; #3. 创建新用户user&#xff0c;…

【有啥问啥】深入解析 OpenAI o1 模型家族:推理能力的跃升与应用场景

深入解析 OpenAI o1 模型家族&#xff1a;推理能力的跃升与应用场景 随着人工智能的不断发展&#xff0c;推理能力已经成为影响 AI 系统性能的关键因素。2024 年 9 月 12 日【好家伙&#xff0c;在笔者生日当天&#xff8d;(ー&#xff40;&#xff8d;)搞事情】&#xff0c;O…

腾讯百度阿里华为常见算法面试题TOP100(5):子串、堆

之前总结过字节跳动TOP50算法面试题: 字节跳动常见算法面试题top50整理_沉迷单车的追风少年-CSDN博客_字节算法面试题 子串 560.和为K的子数组

谷歌云推出全新区块链RPC服务:简化Web3开发

2024年9月&#xff0c;谷歌云&#xff08;Google Cloud&#xff09;宣布推出区块链RPC&#xff08;远程过程调用&#xff09;服务的预览版&#xff0c;进一步表明其支持Web3开发者的承诺。此次发布旨在简化开发者与区块链数据的交互&#xff0c;降低Web3应用开发的技术门槛。这…

制作U盘安装操作系统(启动盘、系统盘、Windows、Linux)

第一种&#xff08;Windows&#xff09; 官网windows制作启动盘 1. 打开Win11下载官网 下载 Windows 11https://www.microsoft.com/zh-cn/software-download/windows11 2. 下载制作操作系统工具 这里不要下载错了 3. 启动工具 选择U盘&#xff0c;选择你的U盘即可&#xf…