C++ 类和对象 拷贝构造函数

一 拷贝构造函数的概念:

拷贝构造函数是一种特殊的构造函数,用于创建一个对象是另一个对象的副本。当需要用一个已存在的对象来初始化一个新对象时,或者将对象传递给函数或从函数返回对象时,会调用拷贝构造函数。

二 拷贝构造函数的特点:

1:拷贝构造函数是构造函数的一个重载形式。

2:拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。

3:若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

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

4:编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。

2.1 代码示例:

class Time 
{
public:// 普通构造函数Time(int hour = 0, int minute = 0, int second = 0) {_hour = hour;_minute = minute;_second = second;}// 拷贝构造函数,使用引用传递Time(const Time& other) {_hour = other._hour;_minute = other._minute;_second = other._second;}void Print() const {std::cout << _hour << ":" << _minute << ":" << _second << std::endl;}private:int _hour;int _minute;int _second;
};int main()
{Time t1(10, 20, 30);   // 使用普通构造函数//构造函数的重载Time t2 = t1;          // 使用拷贝构造函数//Time t2(t1);        // 拷贝构造的另一种写法t1.Print();t2.Print();return 0;
}

输出:

2.2 为什么要使用引用呢?

我们在 increment 函数中改变x的值并没有间接性改变a,这是因为传过去的只是编译器创建实参的一个副本,而修改副本怎么可能可以改变a呢?

#include <iostream>void increment(int x) 
{x = x + 1;  // 修改的是副本,不影响实参
}int main() 
{int a = 5;increment(a);  // 传递a的副本std::cout << a << std::endl;  // 输出5,原始值a未被修改return 0;
}

知道传值传参的本质之后,再来想一想为什么要用引用?咱们先来说说如果没用用引用的后果会是怎么样,当把自定义类型传出去后且不用引用或者指针来接收,它会

调用 Time(const Time other),其中 othert1 的按值传递副本。

为了按值传递,编译器需要创建 other 的副本。

创建 other 的副本时,再次调用 Time(const Time other)

这个新调用的 Time(const Time other) 又需要创建自己的 other 副本,再次调用 Time(const Time other)

如此反复,导致无限递归调用,最终导致栈溢出。

图:

C++规定,自定义类型的拷贝,都会调用拷贝构造

那为什么要引用呢?

首先我们来回顾一下引用 :

1:引用是现有变量的另一个名字。

2:它们不创建新对象,只是指向已有对象。

3:引用只是指向现有对象,不创建新副本

因为引用就是它本身,所以何来创建新副本这一说法,创建新副本是怕改变副本从而导致改变实参值

2.3 总结:

1:按值传递会递归:每次传递对象会复制对象,导致无限递归。

2:引用传递避免递归:引用只是指向对象本身,不会复制对象

三 默认拷贝构造:

当你没有显式定义拷贝构造函数时,编译器会为你自动生成一个默认的拷贝构造函数。这个默认拷贝构造函数会逐个拷贝对象的所有成员变量。

3.1 内置类型与自定义类型的拷贝:

内置类型:如 int, char, float 等,拷贝时直接按照字节方式进行复制,也就是直接复制其值。

自定义类型:如类和结构体,拷贝时会调用该类型的拷贝构造函数。

3.2 代码示例:

内置类型:

#include <iostream>class MyClass 
{
public:int x;  // 内置类型成员
};int main() 
{MyClass obj1;obj1.x = 10;MyClass obj2 = obj1;  // 使用编译器生成的默认拷贝构造函数std::cout << "obj1.x: " << obj1.x << std::endl; std::cout << "obj2.x: " << obj2.x << std::endl;return 0;
}

输出:

对于一个类里面只有内置类型成员那编译器生成的默认拷贝构造会自动复制其值。

自定义类型:

#include <iostream>class Time 
{
public:// 默认构造函数Time() {  _hour = 0;_minute = 0;_second = 0;}// 拷贝构造函数Time(const Time& other) {_hour = other._hour;_minute = other._minute;_second = other._second;std::cout << "Time::Time(const Time& other)" << std::endl;}private:int _hour;int _minute;int _second;
};class MyClass 
{
public:int x;  // 内置类型成员Time t; // 自定义类型成员
};int main() 
{MyClass obj1;obj1.x = 10;MyClass obj2 = obj1;  // 使用编译器生成的默认拷贝构造函数std::cout << "obj1.x: " << obj1.x << std::endl;std::cout << "obj2.x: " << obj2.x << std::endl; return 0;
}

当执行MyClass obj2 = obj1; 因obj1类里面有自定义类型 t 所以编译器生成的默认拷贝构造会自动调用Time(const Time& other) 来完成

3.3 总结:

内置类型:编译器默认拷贝构造函数会直接复制其值。

自定义类型:编译器默认拷贝构造函数会调用该类型的拷贝构造函数来复制其内容。

四 内存分区:

要理解好深拷贝与浅拷贝那就得先了解内存是怎么样分区的。

计算机程序运行时,内存通常被分为四个主要区域:栈区、堆区、全局静态区和只读区(常量区和代码区)。

4.1 栈区:

局部变量:函数内部定义的变量。

形参(函数参数):函数定义时的参数。

返回地址:函数调用后的返回地址。

特点:

栈区中访问速度快且栈的内存连续分配

因存储的都是 局部/形参/返回地址 所以栈区空间小,存储的生命周期短

在我们局部变量所在的函数执行完成时,它会自动释放内存

4.2 堆区:

动态分配的数据:通过 newmalloc 等动态分配函数分配的内存。

特点:

因存储的都是new 或者malloc开辟的空间所以堆区空间大,所以访问速度慢

堆中的内存分配和释放是通过指针进行的,可能不是连续的。

堆区的内存需要程序员手动管理,必须手动释放动态分配的内存,否则会导致内存泄漏。

4.3 全区/静态区:

全局变量:在所有函数外部定义的变量。

静态变量:使用 static 关键字定义的变量。

特点:

全局变量和静态变量在程序的整个运行期间一直存在,直到程序结束。

全局变量可以在程序的所有函数中访问,静态变量在声明的作用域内共享

4.4 只读常量区:

常量:程序中定义的常量。

代码:程序的指令代码。

特点:

常量区的数据在程序运行期间不能被修改,保证了数据的安全性和稳定性。

代码区存储程序的指令代码,在程序运行时被载入内存以执行。

五 浅拷贝:

首先我们来回顾C语言里面的基本类型指针类型

5.1 基本类型:

基本类型是C语言内置的数据类型,它们用于存储最基本的数值数据。常见的基本类型包括:int float char……

5.2 指针类型:

指针类型是存储内存地址的数据类型。指针用于指向其他变量或对象在内存中的位置。

5.3 基本类型代码示例:

#include <iostream>class BasicType 
{
public:int value;// 构造函数BasicType(int v) {value = v;}// 拷贝构造函数BasicType(const BasicType& other) {value = other.value;}
};int main() 
{BasicType obj1(10);BasicType obj2 = obj1;  // 浅拷贝,复制基本类型的值std::cout << "改变前: " << std::endl;std::cout << "obj1.value: " << obj1.value << std::endl;std::cout << "obj2.value: " << obj2.value << std::endl;obj2.value = 20;  // 修改obj2的值std::cout << "改变后: " << std::endl;std::cout << "obj1.value: " << obj1.value << std::endl;std::cout << "obj2.value: " << obj2.value << std::endl;return 0;
}

输出:

值会被复制但修改新对象的值不会影响原对象。

5.3 指针类型代码示例:

#include <iostream>class SimplePointer 
{
public:int* ptr;  // 成员变量 ptr// 构造函数SimplePointer(int value)
{ptr = (int*)malloc(sizeof(int));  // 动态分配内存并初始化if (ptr != nullptr) {*ptr = value;}
}SimplePointer(const SimplePointer& other) {this->ptr = other.ptr;  // 浅拷贝,复制内存地址}void print() const {std::cout << "Value: " << *ptr << std::endl;}
};int main() 
{SimplePointer obj1(10);  // 创建第一个对象,并将值初始化为10SimplePointer obj2(obj1);  // 使用拷贝构造函数(浅拷贝)// 打印初始值std::cout << "Initial values:" << std::endl;obj1.print();obj2.print();// 修改obj2的值*obj2.ptr = 20;// 打印修改后的值std::cout << "After change:" << std::endl;obj1.print();obj2.print(); return 0;
}

输出:

复制内存地址,共享同一块内存,修改会互相影响

六 深拷贝:

#include <iostream>
#include <cstdlib>
#include <cstring>class SimpleClass 
{
public:int* ptr;// 默认构造函数SimpleClass(int value) {ptr = (int*)malloc(sizeof(int));  // 动态分配内存并初始化if (ptr != nullptr) {*ptr = value;}}// 深拷贝构造函数SimpleClass(const SimpleClass& other) {ptr = (int*)malloc(sizeof(int));  // 分配新内存if (ptr != nullptr) {*ptr = *(other.ptr);  // 复制内容}}// 析构函数~SimpleClass() {if (ptr != nullptr) {free(ptr);  // 释放内存}}void Print() const {if (ptr != nullptr) {std::cout << "Value: " << *ptr << std::endl;}}
};int main() 
{SimpleClass obj1(10);  // 创建对象,ptr 指向的值为 10SimpleClass obj2 = obj1;  // 使用深拷贝构造函数obj1.Print();obj2.Print();// 修改 obj2 的值if (obj2.ptr != nullptr) {*(obj2.ptr) = 20;}obj1.Print();obj2.Print();return 0;
}

输出:

深拷贝不仅复制对象的指针成员,还为指针指向的内容分配新的内存,并复制原对象的数据。这样,两个对象拥有独立的内存,修改一个不会影响另一个。

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

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

相关文章

打卡第6天----哈希表

每天进步一点点,滴水石穿,日积月累,不断提升。 数组和链表章节告一段落。开启哈希表相关的。 哈希表能解决什么问题呢,一般哈希表都是用来快速判断一个元素是否出现集合里 一、有效的字母异位词 leetcode题目编号:242 题目描述: 给定两个字符串 s 和 t ,编写一个函数…

Linux忘记密码重置root密码、重置普通用户密码

重启看到选项按e reboot 或 init 62、移动到Linux开头的行在末尾添加 rw init/bin/bash3、按下Ctrlx引导启动 mount -o remount,rw /输入命令回车更改密码,输入新密码&#xff0c;别用小键盘&#xff0c;容易出错 passwd输入两次校验&#xff0c;出现updated successfully就…

OceanBase 配置项系统变量实现及应用详解(1):配置项的定义及使用方法

《OceanBase 配置项&系统变量实现及应用详解》专题导读 在使用OceanBase的过程中&#xff0c;看到大家经常会遇到“参数”、“配置项”、“系统变量”等概念&#xff0c;却不太清楚它们是不是同一个东西&#xff0c;以及应该如何使用。一些对数据库开发感兴趣的朋友&#…

【面向就业的Linux基础】从入门到熟练,探索Linux的秘密(九)-git(1)

Git是一个版本管理控制系统&#xff08;缩写VCS&#xff09;&#xff0c;它可以在任何时间点&#xff0c;将文档的状态作为更新记录保存起来&#xff0c;也可以在任何时间点&#xff0c;将更新记录恢复回来。 文章目录 前言 一、git是什么 二、git基本概念 三、git基本命令 总结…

电商项目中分与元金额单位互转实战

在Java开发中&#xff0c;可能遇到金额单位的转换&#xff0c;比如本系统用分作为金额的基本单位&#xff0c;对方系统用元作为金额的基本单位&#xff0c;这就需要进行单位转换&#xff0c;记录下来&#xff0c;方便备查。 一、分转元 分转元&#xff0c;分到元相差两位&…

PHP源码:新闻门户系统(附管理后台+前台)

一. 前言 今天小编给大家带来了一款可学习&#xff0c;可商用的&#xff0c;新闻门户系统 源码&#xff0c;支持二开&#xff0c;无加密。项目可以扩展为个人博客&#xff0c;和一些社交论坛网址。主要功能&#xff1a;支持文章管理&#xff0c;评论管理&#xff0c;分类管理等…

Kotlin linkedMapOf filterKeys

Kotlin linkedMapOf filterKeys fun main(args: Array<String>) {val lhm linkedMapOf<String, Any>(Pair("name", "phil"), //因为key相同都为 name&#xff0c;被后面的覆盖。Pair("year", 2024),Pair("name", "f…

大语言模型的应用探索AI Agent初探!

前言 大语言模型的应用之一是与大语言模型进行聊天也就是一个ChatBot&#xff0c;这个应用已经很广泛了。 接下来的一个应用就是AI Agent。 AI Agent是人工智能代理&#xff08;Artificial Intelligence Agent&#xff09;的概念&#xff0c;它是一种能够感知环境、进行决策…

消防认证-防火窗

一、消防认证 消防认证是指消防产品符合国家相关技术要求和标准&#xff0c;且通过了国家认证认可监督管理委员会审批&#xff0c;获得消防认证资质的认证机构颁发的证书&#xff0c;消防产品具有完好的防火功能&#xff0c;是住房和城乡建设领域验收的重要指标。 二、认证依据…

Websocket在Java中的实践——整合Rabbitmq和STOMP

大纲 Rabbitmq开启STOMP支持 服务端依赖参数参数映射类配置类逻辑处理类 测试测试页面Controller测试案例 在《Websocket在Java中的实践——STOMP通信的最小Demo》一文中&#xff0c;我们使用enableSimpleBroker启用一个内置的内存级消息代理。本文我们将使用Rabbitmq作为消息代…

【Unity2D 2022:Particle System】添加拾取粒子特效

一、创建粒子特效游戏物体 二、修改粒子系统属性 1. 基础属性 &#xff08;1&#xff09;修改发射粒子持续时间&#xff08;Duration&#xff09;为3s &#xff08;2&#xff09;取消勾选循环&#xff08;Looping&#xff09; &#xff08;2&#xff09;修改粒子存在时间&…

面试常考题---128陷阱(详细)

1.问题引入 分别引入了int和Integer变量&#xff0c;并进行比较 int b 128; int b1 128;Integer d 127; Integer d1 127;Integer e 128; Integer e1 128;System.out.println(bb1); System.out.println(dd1); System.out.println(ee1); System.out.println(e.equals(e1)…

刷题(day01)

1、leetcode485.最大连续1的个数 给定一个二进制数组 nums &#xff0c; 计算其中最大连续 1 的个数。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,0,1,1,1] 输出&#xff1a;3 解释&#xff1a;开头的两位和最后的三位都是连续 1 &#xff0c;所以最大连续 1 的个数是 3.…

昇思第18天打卡|ShuffleNet图像分类

ShuffleNet网络介绍 ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型&#xff0c;和MobileNet, SqueezeNet等一样主要应用在移动端&#xff0c;所以模型的设计目标就是利用有限的计算资源来达到最好的模型精度。ShuffleNetV1的设计核心是引入了两种操作&#xff1a;Pointw…

张大哥笔记:你一旦开窍,就会发现遍地都是钱

大家有没有发现&#xff0c;穷人总是追逐眼前的利益&#xff0c;总是在追着钱跑&#xff0c;却总是赚不到钱。而富人有着长远的见识&#xff0c;追着问题跑&#xff0c;最后却赚的盆满钵满。 我们听过这样一句话&#xff0c;钱不是赚来的&#xff0c;而是帮助别人解决问题后给你…

Qt/C++编写地图应用/离线地图下载/路径规划/轨迹回放/海量点/坐标转换

一、前言说明 这个地图组件写了很多年了&#xff0c;最初设计的比较粗糙&#xff0c;最开始只是为了满足项目需要&#xff0c;并没有考虑太多拓展性&#xff0c;比如最初都是按照百度地图写死在代码中&#xff0c;经过这几年大量的现场实际应用&#xff0c;以及大量的用户提出…

Django 新增数据 save()方法

1&#xff0c;添加模型 Test/app11/models.py from django.db import modelsclass Book(models.Model):title models.CharField(max_length100)author models.CharField(max_length100)publication_date models.DateField()price models.DecimalField(max_digits5, decim…

软件工程(上)

目录 软件过程模型&#xff08;软件开发模型&#xff09; 瀑布模型 原型模型 V模型 构件组装模型 螺旋模型&#xff08;原型瀑布&#xff09; 基于构件的软件工程&#xff08;CBSE&#xff09; 快速应用开发模型&#xff08;RAD&#xff09; 统一过程&#xff08;UP&a…

Linux学习看这一篇就够了,超超超牛的Linux基础入门

引言 小伙伴们&#xff0c;不管是学习c还是学习其他语言在我们学的路上都绕不过操作系统&#xff0c;而且&#xff0c;老生常谈的Linux更是每个计算机人的必修&#xff0c;那么我们对Linux的了解可能只是从别人那听到的简单的这个系统很牛&#xff0c;巴拉巴拉的&#xff0c;但…