C++类的拷贝(复制)构造函数深入理解

2023-11-12

目录

一、拷贝构造函数的基本了解和使用

二、拷贝构造函数的一些注意事项

三、 拷贝构造函数的调用时机

①使用一个对象 “直接构造(显式构造 )” 或 “初始化(隐式构造 )”另一个同类对象

关于拷贝构造函数与赋值函数的区别与联系

②作为函数的形式参数时

 ③作为函数的返回值

 四、深拷贝与浅拷贝的区别与联系

五、总结:拷贝构造函数的难点主要是:


一、拷贝构造函数的基本了解和使用

①拷贝构造函数又被称为复制构造函数,是一种特殊的构造函数,它被/编辑器/在一个对象初始化另一个对象时调用。

②拷贝构造函数的形参列表上需要加上const来防止被引用的对象的值发生改变,不加也可以,但是不加const,会使代码显得不规范。

代码示例:

class Test{
 public:
 /*const 为了防止引用对象被修改
 也可以不写const,而是直接写 Test(Test &obj){}
 但是写const更加规范一些*/
 Test(const Test&obj)//拷贝构造函数无返回值,参数为本类对象的引用。
 {
     //函数体里面一般只写赋值语句。
 }   
}

二、拷贝构造函数的一些注意事项

①拷贝构造可以形成重载,但必须第一个形参为当前类的对象的引用(不建议对其
函数进行加参数重载)。

②如果类设计者没有显式写出拷贝构造函数,编辑器会给出一个默认拷贝构造函数,但是这个默认拷贝构造函数只能够进行浅拷贝的操作,如果类设计者需要进行深拷贝的操作,那么需要自行设计。

三、 拷贝构造函数的调用时机

①使用一个对象 “直接构造(显式构造 )” 或 “初始化(隐式构造 )”另一个同类对象

#include<iostream>

using namespace std;

class test
{
public:
    test() {
        cout << "调用默认构造函数" << " "<<this<<endl;/*利用this指针更清晰的观察编辑器创建对象和析构的具体过程*/
    }
    test(const test& obj)//写const标准一些
    {
        cout << "调用了拷贝构造函数" << " "<<this<<endl;/*一般里面只写赋值语句,但是初学者可以在拷贝构造函数体里面写这条语句以便观察什么时候调用了拷贝构造函数。*/
    }
    ~test() {
        cout << "调用了析构函数" << " "<<this<<endl;
    }
};
int main()
{
    //直接定义obj_1对象,会调用构造函数test()
    test obj_1;
    //用obj_1对象显式构造(直接构造)obj_2对象,会调用拷贝构造函数test(const test& obj)
    test obj_2(obj_1);
    //用obj_1对象隐式构造(初始化)obj_3对象,会调用拷贝构造函数test(const test& obj)
    test obj_3 = obj_1;
    //这里涉及到拷贝构造函数与赋值函数的知识点 
    return 0;
}

关于拷贝构造函数与赋值函数的区别与联系

只要记住拷贝构造函数是在对象构建的时候调用,而赋值函数是在对象已经构建结束的时候调用。

运行结果参考: 

 

 

 

②作为函数的形式参数时

#include<iostream>
#include<cstdlib>
using namespace std;

class Test {
public:
	Test() {
		cout << "调用默认构造函数" <<" "<< this << endl;/*利用this指针来将编辑器对于对象构造与析构的隐式过程显示化*/
	}
	Test(const Test& obj)
	{
		cout << "调用复制构造函数" << " "<<this << endl;

	}

	~Test() { cout << "调用析构函数" <<" "<<this << endl; }
};
void fun(Test oj)//作为函数形式参数
{
	cout << "形参是类对象" << endl;
}
int main()
{
	Test obj_1;
	system("pause");//用于观察什么时候调用这些类成员函数
	fun(obj_1);//构造临时对象
	system("pause");
	return 0;
}
 

运行结果参考:

 ③作为函数的返回值

#include<iostream>
#include<cstdlib>
using namespace std;

class Test {
public:
	Test() {
		cout << "调用了默认构造函数" << " "<<this<<endl;
		system("pause");
	}
	Test(Test& obj)
	{
		cout << "调用了复制构造函数" << " "<<this<<endl;
		system("pause");

	}
	~Test() {
		cout << "调用了析构函数" << " " << this << endl;
		system("pause");

	}
};
Test fun()//对象作为返回值时调用拷贝构造函数
{
	Test obj;
	system("pause");
	return obj;
}
int main()
{
	Test obj_1;
	obj_1 = fun();//这里涉及到一个函数返回值调用拷贝构造函数创建临时对象的次数的问题
	return 0;
}

 运行结果参考:

 调用拷贝构造函数创建临时对象的次数的问题参考:@http://t.csdn.cn/TzQpg

 四、深拷贝与浅拷贝的区别与联系

以题目来引入深拷贝:

创建一个类,数据属性中包含一个在堆中new创建的含5个大小的整型数组,然后在主函数中创建一个对象obj_1给其数组赋值,再在主函数中创建一个对象obj_2,通过对象赋值操作初始化obj_2(提醒要用拷贝构造!!!),最后用obj_2将数组里的值逆向输出 并且最后记得释放堆中内存(很重要,不然会导致内存泄漏,系统是不会帮你释放堆中内存的,提醒用析构函数来实现!!!)
 
运行例子: 
输入:1 2 3 4 5 
输出:5 4 3 2 1 

 一般情况下:(浅拷贝)

#include<iostream>
using namespace std;
class test
{
public:
 int *p;
public:
 test()
 {
 p = new int[5];
 cout << "调用构造函数test()" << endl;
 }
 test(const test& obj)
 {
 this->p = obj.p;
 cout << "调用拷贝构造函数test(const test& obj)" << endl;
 }
 ~test()
 {
 if (p != nullptr)
 {
 delete p;
 p = nullptr;
 }
 cout << "调用析构函数test()" << endl;
 }
};
int main()
{
 //创建对象obj_1
 test obj_1;
 for (int i = 0; i < 5; i++)//给对象obj_1输入数组值
 {
 cin >> obj_1.p[i];
 }
 //创建对象obj_2,并用obj_1对象初始化(隐式构造)obj_2,将obj_1堆中数组初始化obj_2堆中数
组(相当于将数组值赋给obj_2数组)
 test obj_2 = obj_1;
 for (int i = 4; i >= 0; i--)//通过对象obj_2,逆向输出数组值
 {
 cout << obj_2.p[i] << " ";
 }
 cout << endl;
 return 0;
}
 
 

 运行结果:(错误)

 分析:为什么会出现这种问题?

会报错的根本原因在于拷贝构造函数为浅拷贝的直接赋值 

test(const test& obj)
{
this->p = obj.p;
cout << "调用拷贝构造函数test(const test& obj)" << endl;
}

this->p = obj.p 这个赋值操作,直接导致堆中内存的复用,释放时出现释放同一内存的危险操作;

正确的解决方案

test(const test& obj)
 {
 this->p = new int[5];//先自己申请独立内存空间,就可以防止同一内存复用
        for(int i=0;i<5;i++)/*再将对象obj.p里的数组值,逐一赋给需被拷贝对象自己申请的独立
内存空间,完成拷贝构造操作*/
       {
          this->p[i]= obj.p[i];
       } 
 cout << "调用拷贝构造函数test(const test& obj)" << endl;
 }
 

运行结果参考: 

 完整代码:

#include<iostream>
using namespace std;
class test
{
public:
	int* p;
public:
	test()
	{
		p = new int[5];
		cout << "调用构造函数test()" << endl;
	}
	test(const test& obj)
	{
		this->p = new int[5]; 
		for (int i = 0; i < 5; i++) 
		{
		   this->p[i] = obj.p[i];
		}
		cout << "调用拷贝构造函数test(const test& obj)" << endl;
	}
	 

	~test()
	{
		if (p != nullptr)
		{
			delete p;
			p = nullptr;
		}
		cout << "调用析构函数test()" << endl;
	}
};
int main()
{
	//创建对象obj_1
	test obj_1;
	for (int i = 0; i < 5; i++)//给对象obj_1输入数组值
	{
		cin >> obj_1.p[i];
	}
	/*创建对象obj_2, 并用obj_1对象初始化(隐式构造)obj_2,将obj_1堆中数组初始化obj_2堆中数
	组(相当于将数组值赋给obj_2数组)*/
		test obj_2 = obj_1;
	for (int i = 4; i >= 0; i--)//通过对象obj_2,逆向输出数组值
	{
		cout << obj_2.p[i] << " ";
	}
	cout << endl;
	return 0;
}

五、总结:拷贝构造函数的难点主要是:

①拷贝构造函数作为返回值和函数形参时,它被编辑器调用的次数。

②拷贝构造函数的深拷贝和浅拷贝的问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++类的拷贝(复制)构造函数深入理解 的相关文章

随机推荐

  • gitee项目克隆到本地并运行

    首先电脑上要先安装node js和git 配置一下 在gitee上找到需要克隆到本地的项目 点击克隆 下载 在点击HTTPS下面的复制 github上也是类似的方式 把项目地址复制下来 在本机电脑 新建一个文件夹 命名任意 打开新建的文件夹
  • 神经元模型介绍

    一 深度学习的背景 目前 深度学习 Deep Learning 简称DL 在算法领域可谓是大红大紫 深度学习是机器学习领域中的一个新的研究方向 模仿生人类神经网络 学习样本数据的内在规律的一种方法 神经网络属于监督学习的过程 可以处理 回归
  • SQL 语句学习总结:

    1 四范式 范式好处 数据库范式是数据表设计的规范 在范式规范下 数据库里每个表存储的重复数据降到最少 这有助于数据的一致性维护 同时在数据库范式下 表和表之间不再有很强的数据耦合 可以独立的增长 ie 比如汽车引擎的增长和汽车的增长是完全
  • 接口响应时间长,前端返回请求超时解决

    在前端代码设置axios响应时间 1 全局设置 axios defaults timeout 时间 单位为毫秒 或 2 封装的http请求 const service axios create 公共接口 这里注意后面会讲 baseURL p
  • 解决Flutter输入框限制最大输入长度时,从中间输入会自动截取掉后面的内容

    一 问题原由 当限制输入框最大输入长度时 将光标移动到中间输入 当输入的内容长度加上已经输入内容的长度大于限制的输入长度时 Flutter会将光标后面的内容进行截取掉 而当我在原生Android上验证时却是自动截取输入的内容原有的内容不动
  • 输出数组中最大、小值和下标

    详细看代码 package exp 4 public class Array01 public static void main String args int arrs 1 2 2 12 7 5 声明数组并赋值 int max arrs
  • Python实现目录文件扫描功能

    日常程序编写中常常遇到需要获取目录下文件的功能 对该功能做个简单整理 供大家参考 实现遍历目录文件最常用的方法是os listdir 还有一种os walk方法 一 os listdir方法 源码中对该方法的描述 Return a list
  • 史上最详细黑盒测试用例方法总结(等价类、边界值、因果图等)

    黑盒测试用例设计方法 一 等价类 等价类划分法原理 1 把程序的输入域划分成若干部分 然后从每个部分中选取少数代表性数据作为测试用例 2 每一类的代表性数据在测试中的作用等价于这一类中的其他值 如果某一类中的一个例子发现了错误 这一等价类中
  • Flutter FutureBuilder

    FutureBuilder 是 Flutter 中的一个小部件 用于根据 Future 的结果构建用户界面 它接受一个 Future 对象和一个构建函数作为参数 FutureBuilder 将监听 Future 对象的变化 并相应地更新用户
  • useCallback 作用,useMemo ,memo作用 浅显理解

    项目中看到别人代码基本上每个函数都写了useCallback 于是去查了查到底有什么作用 快看睡着了还是没太明白 直接同事请教了一下 大概浅显的理解一点 useCallback 简单来说就是返回一个函数 只有在依赖项发生变化的时候才会更新
  • Vue 点击导航栏滑动到指定位置

    效果图 assignBlock gif 方法1
  • 前后端分离接口

    前后端分离接口的意义 目前现有前后端开发模式 后端为主的MVC时代 如下图所示 代码可维护性得到明显好转 MVC 是个非常好的协作模式 从架构层面让开发者懂得什么代码应该写在什么地方 为了让 View 层更简单干脆 还可以选择 Veloci
  • 现代密码学期末总结

    文章目录 写在前面 1 引言 知识点 习题 2 流密码 知识点 习题 3 分组密码 知识点 习题 4 公钥密码 知识点 习题 5 数字签名 知识点 习题 6 哈希函数 知识点 7 认证技术 知识点 8 密钥分配与密钥管理 知识点 习题 9
  • 大数据:对大数据的理解

    学了这么久大数据 学了好多主流大数据框架 从来没有主观意义上去深度地思考过大数据 现在对于大数据我讲讲自己的想法 1 大数据的产生 为什么会有大数据 随着互联网的发展 现在大部分日常生活都通过网络变得十分便捷 吃穿住行等等的日常生活都离不开
  • 【三十九、MySql】进阶篇--存储过程--循环(while、repeat、loop)

    1 while循环 语法 先判定循环是有条件的循环控制语句 满足条件后 再执行循环体中的sql语句 while 条件 do sql语句 end while 练习 传入一个参数n 计算1 2 3 n的值 create procedure p5
  • 卸载centos自带jdk&安装指定版本jdk

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 删除jdk 1 查看java安装情况 2 查看 3 卸载 4 rpm总结 5 安装 1 查看java安装情况 java version 2 查看 rpm qa grep jd
  • js实现贪吃蛇小游戏

  • 2023年电子设计大赛E题——省一设计报告

    2023 年全国大学生电子设计竞赛 运动目标控制与自动追踪系统 E 题 本科组 设计报告 目录 一 系统方案 4 一 主控模块的论证与选择 4 1 方案一 4 2 方案二 5 3 总结 5 二 巡线模块的论证与选择 5 1 方案一 5 2
  • 大数据工程师面试经验

    全中国的IT公司只想去阿里 因为阿里真的是中国程序员的朝圣地 进去真的能学很多 最终经历了5轮面试 顺利拿到了offer 这里和大家分享一下我的面试经验 一面过程 首先是一次不记名面试 这里我也真的很感谢这场不记名面试了 如果没有这场不记名
  • C++类的拷贝(复制)构造函数深入理解

    目录 一 拷贝构造函数的基本了解和使用 二 拷贝构造函数的一些注意事项 三 拷贝构造函数的调用时机 使用一个对象 直接构造 显式构造 或 初始化 隐式构造 另一个同类对象 关于拷贝构造函数与赋值函数的区别与联系 作为函数的形式参数时 作为函