【C++拷贝构造函数】动态分配与成员对象

系列文章目录

🌈座右铭🌈:人的一生这么长、你凭什么用短短的几年去衡量自己的一生!

💕个人主页:清灵白羽 漾情天殇_计算机底层原理,深度解析C++,自顶向下看Java-CSDN博客


目录

系列文章目录

一、拷贝构造函数是什么?

1、基本概念

2、触发时机

        1、作为函数参数

        2、作为函数返回值

        3、对象接收

3、参数类型

4、递归调用

 二、拷贝构造的深浅拷贝

 1.什么是深拷贝

        1、浅拷贝

        2、深拷贝

          3、成员对象与深浅拷贝

        4、引用与拷贝构造

 总结


一、拷贝构造函数是什么?

1、基本概念

        拷贝构造函数是构造函数的一种重载形式没有返回值,只有一个参数,用于利用已有的一个对象去创建另一个对象,拷贝构造当中还涉及到深拷贝和浅拷贝,相信这些概念大家已经都了解了,我这里就不做过多的讲解了,我今天结合代码专门来为大家展示一下拷贝构造函数的用法。

        首先请大家看一段代码:

#include"Yangon.h"
using namespace std;class Member {
private:int value;
public:Member(int value):value(value) {cout << "Member()" << endl;}Member(const Member& source):value(source.value) {cout << "Member(const Member& source)" << endl;}~Member() {cout << "~Member()" << endl;}
};
class MyClass {
private:Member member;
public:MyClass(Member member) :member(member) {cout << "MyClass()" << endl;}MyClass(const MyClass& source) :member(source.member){cout << "MyClass(const MyClass& source)" << endl;}~MyClass() {cout << "~MyClass()" << endl;}
};
int main() {Member member(2024);cout << "==============" << endl;MyClass myClassObj(member);cout << "==============" << endl;return 0;
}

        这是这段代码最终的运行结果,针对这个结果我只提一个问题,请问这里的拷贝构造函数为什么会调用两次?其他的关于构造函数和析构函数调用次序的问题我在上一篇文章当中已经详细地解释过了,不了解的小伙伴可以看我之前的文章,这篇文章不做讲解。

        我直接说答案,因为MyClass类里面是有一个member成员的,我们利用已有的member对象为MyClass类对象当中的member成员赋值是需要调用一次拷贝构造函数的,而且最重要的是在这一行代码当中member是作为函数的一个参数的,这里传参也是需要一次拷贝构造函数的,编译器会利用拷贝构造函数生成一个临时对象传递给这个函数的参数,然后对MyClass类对象当中的member成员赋值又需要一次拷贝构造函数所以这里一共需要调用两次拷贝构造函数。

MyClass myClassObj(member);

2、触发时机

        1、作为函数参数

#include"Yangon.h"
using namespace std;
class MyClass {
private:int age;string name;
public:MyClass(int age, string name) :age(age), name(name) {}MyClass(const MyClass& source) :age(source.age), name(source.name) {}void PrintInfo() {cout << "age:" << this->age << endl;cout << "name:" << this->name << endl;}
};
void Func(MyClass myClassOBj) {myClassOBj.PrintInfo();
}
int main() {MyClass myClassObj(21, "张三");Func(myClassObj);return 0;
}

        2、作为函数返回值

class MyClass {
private:int age;string name;
public:MyClass(int age, string name) :age(age), name(name) {}MyClass(const MyClass& source) :age(source.age), name(source.name) {}void PrintInfo() {cout << "age:" << this->age << endl;cout << "name:" << this->name << endl;}
};
void Func(MyClass myClassOBj) {myClassOBj.PrintInfo();
}
MyClass Func2() {MyClass myClassObj(33, "李四");return myClassObj;
}
int main() {MyClass myClassObj(21, "张三");Func(myClassObj);MyClass myClassObj2 = Func2();Func(myClassObj2);return 0;
}

        3、对象接收

        当我们使用一个已有的对象去创建另外一个对象的时候,就会需要用到拷贝构造函数。

class MyClass {
private:int age;string name;
public:MyClass(int age, string name) :age(age), name(name) {}MyClass(const MyClass& source) :age(source.age), name(source.name) {}void PrintInfo() {cout << "age:" << this->age << endl;cout << "name:" << this->name << endl;}
};
void Func(MyClass myClassOBj) {myClassOBj.PrintInfo();
}
MyClass Func2() {MyClass myClassObj(33, "李四");return myClassObj;
}
int main() {MyClass myClassObj(21, "张三");Func(myClassObj);cout << "1=========" << endl;MyClass myClassObj2 = Func2();Func(myClassObj2);cout << "1=========" << endl;MyClass myClassObj3 = myClassObj;Func(myClassObj3);cout << "1=========" << endl;return 0;
}

3、参数类型

        构造函数的参数类型应该使用const MyClass& source 类型的,那么为什么要这么使用呢?主要还是为了防止一个对象对另外一个对象的修改,我们只是希望用一个已有的对象去构造另外一个对象但是我们并不希望使用一个已有的对象去修改另外一个对象。

class MyClass {
private:int age;string name;
public:MyClass(int age, string name) :age(age), name(name) {}MyClass(const MyClass& source) :age(source.age), name(source.name) {}void PrintInfo() {cout << "age:" << this->age << endl;cout << "name:" << this->name << endl;}
};

        我给大家写一段代码:

class MyClass {
private:int age;string name;
public:MyClass(int age, string name) :age(age), name(name) {}MyClass(MyClass& source) {source.age = 21;source.name = "张三";}void PrintInfo() {cout << "age:" << this->age << endl;cout << "name:" << this->name << endl;}
};
int main() {MyClass myClassObj(50, "李四");MyClass myClassObj2 = myClassObj;myClassObj.PrintInfo();
}

        大家可以看一下运行结果,我原本用Obj对象构造Obj2对象,Obj对象有自己的成员变量值,可是如果我们拷贝构造函数没有使用const修饰的话就意味着MyClassObj2对象可以任意修改MyClassObj对象,这样就乱套了,所以拷贝构造函数当中必须要使用const来进行修饰。

4、递归调用

        为什么拷贝构造函数当中一定要使用引用成员进行传参呢?这里就是为了防止产生递归调用,因为如果我们这么定义的话:

class MyClass{
public:MyClass(const MyClass source){}
}

        我们要知道传值传参的话本身就需要调用拷贝构造函数,所以当我们调用拷贝构造函数的时候,拷贝构造函数需要传参、然后传参就需要调用拷贝构造函数,然后拷贝构造函数又需要传参、传参又需要调用拷贝构造函数,所以就会没完没了的一直递归调用下去、所以我们这里使用引用的方式就可以避免递归调用。


 二、拷贝构造的深浅拷贝

 1.什么是深拷贝

        深拷贝是指在进行时对象拷贝,不仅复制对象本身的数据,还复制了对象所指向的动态分配的内存,并且创建一个新的内存块存储相同的数据,这样原始对象和拷贝对象是完全独立的,这对其中一个对象的修改不会影响另外一个对象,在C++当中如果一个对象使用new关键字来分配内存的话,通常需要进行深拷贝。

        例如指针,如果一个类当中涉及到指针那么拷贝构造函数就必须使用深拷贝,如果是浅拷贝的话仅仅是简单地将指针的值复制给了另外一个指针,那么也就意味着两个指针的值相同所以两个指针指向了同样的一块区间,当这两个指针进行释放的时候,同一块区间被释放两次编译器就会报错,所以也就引入了深拷贝。

        1、浅拷贝

class ShallowCopy {
public:int* data;// 构造函数ShallowCopy(int value) : data(new int(value)) {}// 拷贝构造函数(浅拷贝)ShallowCopy(const ShallowCopy& source) : data(source.data) {}// 析构函数~ShallowCopy() {delete data; // 这里会导致编译不通过,多个对象共享同一块内存}
};int main() {ShallowCopy obj1(42);ShallowCopy obj2 = obj1; // 浅拷贝,两个对象共享相同的内存std::cout << *obj1.data << std::endl; // 输出 42std::cout << *obj2.data << std::endl; // 输出 42// 修改其中一个对象的数据*obj1.data = 99;// 由于浅拷贝,两个对象的数据都被修改std::cout << *obj1.data << std::endl; // 输出 99std::cout << *obj2.data << std::endl; // 输出 99return 0;
}

        2、深拷贝

class DeepCopy {
public:int* data;// 构造函数DeepCopy(int value) : data(new int(value)) {}// 拷贝构造函数(深拷贝)DeepCopy(const DeepCopy& source) : data(new int(*(source.data))) {}// 析构函数~DeepCopy() {delete data; // 每个对象都有自己的数据副本,安全释放内存}
};int main() {DeepCopy obj1(42);DeepCopy obj2 = obj1; // 深拷贝,每个对象有自己的数据副本std::cout << *obj1.data << std::endl; // 输出 42std::cout << *obj2.data << std::endl; // 输出 42// 修改其中一个对象的数据*obj1.data = 99;// 由于深拷贝,两个对象的数据独立std::cout << *obj1.data << std::endl; // 输出 99std::cout << *obj2.data << std::endl; // 输出 42return 0;
}

          两段代码的结果我已经为大家展示出来了,如果对于指针变量采用浅拷贝的话导致两个为指针指向同一块空间的话,修改其中一个也吧必然会修改另外一个。   

          3、成员对象与深浅拷贝

        这段代码为大家展示了当一个类当中包含成员对象的时候,深浅拷贝要如何进行。

class Member {
private:int* data;
public:Member(int* data) {this->data = new int(*data);}Member(const Member& source) {this->data = new int(*source.data);}~Member() {delete data;}
};
class MyClass {
private:Member member;
public:MyClass(Member member):member(member) {}MyClass(const MyClass& source) :member(source.member) {}
};
int main() {int value = 2025;int* ptr = &value;Member member(ptr);MyClass myClassObj(member);MyClass myClassObj2 = myClassObj;return 0;
}

        大家看完代码之后我主要提一个问题,请问MyClass对象调用拷贝构造函数的时候,因为MyClass对象内部包含成员对象,那么MyClass类当中需不需要专门针对成员对象调用一个深拷贝构造函数呢?因为大家知道这行代码其实主要是浅拷贝也就是值拷贝,直接将一个MyClass对象当中的member复制给另一个MyClass对象,那么这里需不需要定义一个深拷贝呢?答案是不需要!因为再拷贝构造函数当中的初始化列表当中MyClass对象会自动调用Member类对象的拷贝构造函数,只要Member类当中是深拷贝就可以了(前提是Member类当中有指针变量,否则内置类型成员不需要定义深拷贝)

MyClass(const MyClass& source) :member(source.member) {}

        4、引用与拷贝构造

        首先声明一点,引用成员是不需要定义深拷贝的,但是也要注意,如果类当中定义了引用成员那么再进行拷贝构造函数的时候采用值传递,将导致多个对象共同持有一个相同的引用,而且引用定义好之后也不可以进行修改,代码如下:

class Member {
public:int& data;
public:Member(int value) :data(value) {}Member(const Member& source) :data(source.data){}
};
int main() {Member member(2025);Member member2 = member;Member member3 = member2;cout << member.data << endl;cout << member2.data << endl;cout << member3.data << endl;return 0;
}

 总结

        拷贝构造函数当中的主要知识点就已经为大家介绍完了,这部分还有其它的一部分内容等到了后续的异常和C++11当中再详细为大家讲解。

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

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

相关文章

Factory Method工厂模式(对象创建)

Factory Method&#xff08;对象创建&#xff09; 链接&#xff1a;工厂模式实例代码 解析 目的 在软件系统中&#xff0c;经常面临着创建对象的工作&#xff1b;由于需求的变化&#xff0c;需要创建的对象的具体类型经常变化。 如何应对这种变化&#xff1f;如何绕过常规的…

Django(四)

1.数据库操作 MySQL数据库 pymysql import pymysql# 1.连接MySQL conn pymysql.connect(host"127.0.0.1", port3306, userroot, passwd"root123", charsetutf8, dbunicom) cursor conn.cursor(cursorpymysql.cursors.DictCursor)# 2.发送指令 cursor.…

《整机柜服务器通用规范》由OCTC正式发布!浪潮信息牵头编制

近日&#xff0c;中国电子工业标准化技术协会开放计算标准工作委员会&#xff08;OCTC&#xff09;正式批准发布了《整机柜服务器通用规范》&#xff0c;该标准由浪潮信息牵头&#xff0c;中国工商银行、中国质量认证中心、英特尔、中国计量科学研究院等十余家单位联合编制&…

SLAM PnP问题以及相关基础知识

目标泛函 目标泛函是在优化问题中使用的一种数学工具&#xff0c;目标泛函是一个函数&#xff0c;它将一个或多个函数映射到一个实数。它常用于描述需要最小化或最大化的函数。在优化问题中&#xff0c;我们通常希望找到使得某个特定函数取得最大值或最小值的变量值。目标泛函…

Java—Throwing Exceptions

一、指定方法引发的异常 上一节展示了如何为ListOfNumbers类中的writeList&#xff08;&#xff09;方法编写异常处理程序。有时&#xff0c;代码捕获可能在其中发生的异常是适当的。然而&#xff0c;在其他情况下&#xff0c;最好让调用堆栈更上层的方法处理该异常。例如&…

51系列--数码管显示的4X4矩阵键盘设计

本文介绍基于51单片机的4X4矩阵键盘数码管显示设计&#xff08;完整Proteus仿真源文件及C代码见文末链接&#xff09; 一、系统及功能介绍 本设计主控芯片选用51单片机&#xff0c;主要实现矩阵键盘对应按键键值在数码管上显示出来&#xff0c;矩阵键盘是4X4共计16位按键&…

系列十二、Linux中安装Zookeeper

一、Linux中安装Zookeeper 1.1、下载安装包 官网&#xff1a;Index of /dist/zookeeper/zookeeper-3.4.11 我分享的链接&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/14Hugqxcgp89f2hqGWDwoBw?pwdyyds 提取码&#xff1a;yyds 1.2、上传至/opt目录 1.3、解…

ffmpeg 解码文件时的时间戳问题

实时流和普通文件 1 实时流 实时流编码时&#xff0c;我们一般不进行b帧编码&#xff0c;但是文件存储时为了减小大小&#xff0c;会增加b帧&#xff0c;实时流只带了I&#xff0c;P帧&#xff0c;那就会好很多 2 普通文件 很多文件带了b帧&#xff0c;所以要使用解码时间去同…

SpringMVC之视图和RESTful

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

Linux 线程安全 (2)

文章目录 线程同步概念条件变量使用生产消费模型信号量的使用读写锁的使用 Linux 线程安全 &#xff08;1&#xff09; 线程同步概念 竞态条件&#xff1a;因为时序问题&#xff0c;而导致程序异常. 饥饿问题&#xff1a;只使用互相锁保证线程安全时&#xff0c;锁资源总被某…

每天坐在电脑前10小时的投资者的现货黄金投资秘密

很多人在现货黄金市场中苦作舟&#xff0c;希望通过交易、实践来找出市场中的奥秘。笔者最近看了一个每天坐在电脑面前十个小时以上做分析和投资的投资者的经验介绍&#xff0c;他道出了一些投资的秘密&#xff0c;笔者认为&#xff0c;这是适合现货黄金投资者借鉴和学习的&…

java SSM课程平台系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM课程平台系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S…

【方法】Word文档如何设置密码?

Word文档可以设置密码保护&#xff0c;如果想要保护文档不被随意打开&#xff0c;可以设置“打开密码”&#xff1b;如果想保护文档不被随意编辑&#xff0c;可以设置“限制密码”&#xff1b;如果当心自己不小心修改了文档&#xff0c;可以设置“只读模式”密码&#xff0c;使…

一篇文章带你入门PHP魔术方法

PHP魔术方法 PHP 中的"魔术方法"是一组特殊的方法&#xff0c;它们在特定情况下自动被调用。这些方法的名称都是以两个下划线&#xff08;__&#xff09;开头。魔术方法提供了一种方式来执行各种高级编程技巧&#xff0c;使得对象的行为可以更加灵活和强大。以下是一…

DSG YashanDB数据交互解决方案:更稳、更快、更安全

近期&#xff0c;深圳计算科学研究院&#xff08;简称“深算院”&#xff09;携手迪思杰&#xff08;北京&#xff09;数据管理技术有限公司&#xff08;简称“DSG”&#xff09;重磅推出基于崖山数据库的数据交互解决方案&#xff0c;具备双向迁移同步、性能稳定、支持复杂对象…

余弦相似度算法

余弦相似度算法 是什么 余弦距离&#xff0c;也称为余弦相似度&#xff0c;是用向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小的度量。 余弦值越接近1&#xff0c;就表明夹角越接近0度&#xff0c;也就是两个向量越相似&#xff0c;这就叫"余弦相似性&q…

AD20PCB笔记(写给第三次重学PCB的自己)

readme&#xff1a;我曾以为自己本科毕业以后&#xff0c;再也不会用到PCB了&#xff0c;因为本科毕设的时候自己设计的PCB开发板出现了严重的设计问题&#xff0c;在实际测试的过程中&#xff0c;电源一上电&#xff0c;板子芯片直接炸飞&#xff0c;当时真的让我很害怕&#…

c++学习笔记(10)-可变参数模板

1、概念 可变参数模板&#xff08;Variable Template Parameters&#xff09;是 C11 中引入的一种语法&#xff0c;它允许函数或类模板接受可变数量的参数。这样可以方便地定义操作适用于多个类型和/或值的函数或类模板。 使用可变参数模板时&#xff0c;可以在模板参数列表中…

机器学习距离度量方法

1. 机器学习中为什么要度量距离&#xff1f; 机器学习算法中&#xff0c;经常需要 判断两个样本之间是否相似 &#xff0c;比如KNN&#xff0c;K-means&#xff0c;推荐算法中的协同过滤等等&#xff0c;常用的套路是 将相似的判断转换成距离的计算 &#xff0c;距离近的样本相…

K-means 聚类算法分析

算法简述 K-means 算法原理 我们假定给定数据样本 X &#xff0c;包含了 n 个对象 &#xff0c;其中每一个对象都具有 m 个维度的属性。而 K-means 算法的目标就是将 n 个对象依据对象间的相似性聚集到指定的 k 个类簇中&#xff0c;每个对象属于且仅属于一个其到类簇中心距离…