《C++ Primer》导学系列:第 13 章 - 拷贝控制
13.1 拷贝、赋值与析构函数
拷贝控制是C++中类设计的重要组成部分,用于管理对象的复制、赋值和销毁过程。理解并正确实现拷贝控制函数(拷贝构造函数、拷贝赋值运算符和析构函数)对于编写健壮和高效的C++程序至关重要。
13.1.1 拷贝构造函数
拷贝构造函数用于创建对象的副本。它的声明形式如下:
ClassName(const ClassName &other);
当对象被复制时,拷贝构造函数会被调用。例如,当对象以值传递的方式作为参数传递或从函数返回时,拷贝构造函数将被调用。
示例代码
#include <iostream>class Example {
public:Example(int v) : value(v) {}Example(const Example &other) : value(other.value) {std::cout << "Copy constructor called" << std::endl;}int getValue() const { return value; }
private:int value;
};void printExample(Example e) {std::cout << "Value: " << e.getValue() << std::endl;
}int main() {Example ex1(10);Example ex2 = ex1; // 调用拷贝构造函数printExample(ex1); // 调用拷贝构造函数return 0;
}
13.1.2 拷贝赋值运算符
拷贝赋值运算符用于将一个对象的值赋给另一个对象。它的声明形式如下:
ClassName& operator=(const ClassName &other);
拷贝赋值运算符通常用于对象已经存在的情况下进行赋值操作。
示例代码
#include <iostream>class Example {
public:Example(int v) : value(v) {}Example(const Example &other) : value(other.value) {std::cout << "Copy constructor called" << std::endl;}Example& operator=(const Example &other) {if (this != &other) {value = other.value;std::cout << "Copy assignment operator called" << std::endl;}return *this;}int getValue() const { return value; }
private:int value;
};int main() {Example ex1(10);Example ex2(20);ex2 = ex1; // 调用拷贝赋值运算符std::cout << "ex2 value: " << ex2.getValue() << std::endl;return 0;
}
13.1.3 析构函数
析构函数用于释放对象使用的资源。它的声明形式如下:
~ClassName();
析构函数在对象的生命周期结束时被自动调用,例如当对象超出作用域或被显式删除时。
示例代码
#include <iostream>class Example {
public:Example(int v) : value(v) {}~Example() {std::cout << "Destructor called" << std::endl;}int getValue() const { return value; }
private:int value;
};int main() {Example ex1(10);std::cout << "ex1 value: " << ex1.getValue() << std::endl;return 0;
}
13.1.4 三/五法则
C++中有一个重要的设计原则,称为“三/五法则”,意思是如果一个类定义了析构函数、拷贝构造函数或拷贝赋值运算符中的一个,那么它很可能需要定义所有这三个函数。随着C++11标准引入移动语义,这个原则扩展为“五法则”,包括移动构造函数和移动赋值运算符。
示例代码
#include <iostream>
#include <utility>class Example {
public:Example(int v) : value(new int(v)) {}Example(const Example &other) : value(new int(*other.value)) {std::cout << "Copy constructor called" << std::endl;}Example& operator=(const Example &other) {if (this != &other) {delete value;value = new int(*other.value);std::cout << "Copy assignment operator called" << std::endl;}return *this;}~Example() {delete value;std::cout << "Destructor called" << std::endl;}// C++11移动构造函数Example(Example &&other) noexcept : value(other.value) {other.value = nullptr;std::cout << "Move constructor called" << std::endl;}// C++11移动赋值运算符Example& operator=(Example &&other) noexcept {if (this != &other) {delete value;value = other.value;other.value = nullptr;std::cout << "Move assignment operator called" << std::endl;}return *this;}int getValue() const { return *value; }
private:int* value;
};int main() {Example ex1(10);Example ex2 = std::move(ex1); // 调用移动构造函数Example ex3(20);ex3 = std::move(ex2); // 调用移动赋值运算符return 0;
}
13.1.5 阻止拷贝
在某些情况下,我们可能希望阻止对象被拷贝或赋值。可以通过将拷贝构造函数和拷贝赋值运算符声明为delete
来实现这一点。
示例代码
class NonCopyable {
public:NonCopyable() = default;NonCopyable(const NonCopyable &) = delete;NonCopyable& operator=(const NonCopyable &) = delete;
};int main() {NonCopyable obj1;// NonCopyable obj2 = obj1; // 错误:拷贝构造函数被删除// NonCopyable obj3;// obj3 = obj1; // 错误:拷贝赋值运算符被删除return 0;
}
重点与难点分析
重点:
- 拷贝构造函数:了解何时会调用拷贝构造函数,掌握其实现方法。
- 拷贝赋值运算符:理解拷贝赋值运算符的使用场景和实现细节。
- 析构函数:熟悉析构函数的作用和调用时机。
- 三/五法则:掌握何时需要同时实现拷贝构造函数、拷贝赋值运算符和析构函数,理解移动语义下的五法则。
- 阻止拷贝:了解如何通过
delete
关键字阻止对象的拷贝和赋值。
难点:
- 自我赋值检测:在拷贝赋值运算符中处理自我赋值的情况。
- 资源管理:确保在析构函数中正确释放资源,避免内存泄漏和其他资源管理问题。
- 编写健壮的拷贝控制函数:理解并正确实现拷贝控制函数,以确保对象在复制、赋值和销毁过程中行为一致且安全。
- 实现移动语义:正确实现移动构造函数和移动赋值运算符,提高程序性能。
练习题解析
- 练习13.1:编写一个类,包含拷贝构造函数、拷贝赋值运算符和析构函数,测试它们的调用情况。
-
- 示例代码:
#include <iostream>class Example {
public:Example(int v) : value(v) {}Example(const Example &other) : value(other.value) {std::cout << "Copy constructor called" << std::endl;}Example& operator=(const Example &other) {if (this != &other) {value = other.value;std::cout << "Copy assignment operator called" << std::endl;}return *this;}~Example() {std::cout << "Destructor called" << std::endl;}int getValue() const { return value; }
private:int value;
};int main() {Example ex1(10);Example ex2 = ex1; // 调用拷贝构造函数ex2 = ex1; // 调用拷贝赋值运算符return 0;
}
- 练习13.2:修改练习13.1的类,使其包含一个指向动态内存的指针,确保拷贝控制函数正确管理该内存。
-
- 示例代码
#include <iostream>class Example {
public:
Example(int v) : value(new int(v)) {}
Example(const Example &other) : value(new int(*other.value)) {std::cout << "Copy constructor called" << std::endl;
}
Example& operator=(const Example &other) {if (this != &other) {delete value;value = new int(*other.value);std::cout << "Copy assignment operator called" << std::endl;}return *this;
}
~Example() {delete value;std::cout << "Destructor called" << std::endl;
}
int getValue() const { return *value; }
private:
int* value;
};int main() {Example ex1(10);Example ex2 = ex1; // 调用拷贝构造函数ex2 = ex1; // 调用拷贝赋值运算符std::cout << "ex2 value: " << ex2.getValue() << std::endl;return 0;
}
- 练习13.3:编写一个类,包含移动构造函数和移动赋值运算符,测试它们的调用情况。
-
- 示例代码:
#include <iostream>class Example {
public:Example(int v) : value(new int(v)) {}Example(const Example &other) : value(new int(*other.value)) {std::cout << "Copy constructor called" << std::endl;}Example& operator=(const Example &other) {if (this != &other) {delete value;value = new int(*other.value);std::cout << "Copy assignment operator called" << std::endl;}return *this;}Example(Example &&other) noexcept : value(other.value) {other.value = nullptr;std::cout << "Move constructor called" << std::endl;}Example& operator=(Example &&other) noexcept {if (this != &other) {delete value;value = other.value;other.value = nullptr;std::cout << "Move assignment operator called" << std::endl;}return *this;}~Example() {delete value;std::cout << "Destructor called" << std::endl;}int getValue() const { return *value; }
private:int* value;
};int main() {Example ex1(10);Example ex2 = std::move(ex1); // 调用移动构造函数Example ex3(20);ex3 = std::move(ex2); // 调用移动赋值运算符return 0;
}
- 练习13.4:编写一个类,阻止其对象的拷贝和赋值。
-
- 示例代码:
#include <iostream>class NonCopyable {
public:NonCopyable() = default;NonCopyable(const NonCopyable &) = delete;NonCopyable& operator=(const NonCopyable &) = delete;
};int main() {NonCopyable obj1;// NonCopyable obj2 = obj1; // 错误:拷贝构造函数被删除// NonCopyable obj3;// obj3 = obj1; // 错误:拷贝赋值运算符被删除return 0;
}
总结与提高
本节总结:
- 掌握了拷贝控制的基本概念和操作,包括拷贝构造函数、拷贝赋值运算符和析构函数。
- 理解了拷贝控制函数在对象复制、赋值和销毁过程中的作用和调用时机。
- 学会了在类中正确实现拷贝控制函数,以确保对象在不同生命周期阶段的正确行为。
- 理解了“三/五法则”的重要性,学会了在类设计中同时实现必要的拷贝控制函数和移动语义。
- 掌握了如何通过
delete
关键字阻止对象的拷贝和赋值。
提高建议:
- 多练习拷贝控制函数的实现:通过编写更多涉及拷贝控制的类,熟悉各种管理方法的用法,提高对拷贝控制的理解和实现能力。
- 深入理解资源管理的原理:通过阅读文档和相关书籍,深入理解资源管理的实现原理和使用场景,提高编写高效代码的能力。
- 优先使用智能指针:在实际项目中,尽量使用智能指针管理动态内存,以减少手动内存管理带来的错误,提高代码的可读性和可维护性。
- 优化移动语义:在类设计中,合理运用移动构造函数和移动赋值运算符,提高程序的性能和资源利用效率。
13.2 拷贝控制与资源管理
拷贝控制函数在管理资源时非常重要,特别是当类涉及动态内存分配或其他资源(如文件句柄、网络连接等)时。通过正确实现拷贝控制函数,可以确保资源在对象复制、赋值和销毁过程中得到正确的管理和释放。
13.2.1 拷贝控制与动态内存
在涉及动态内存的类中,拷贝构造函数、拷贝赋值运算符和析构函数的实现需要特别注意,以确保内存安全。
示例代码
#include <iostream>
#include <algorithm> // for std::copyclass DynamicArray {
public:DynamicArray(size_t size) : size(size), data(new int[size]()) {}DynamicArray(const DynamicArray &other) : size(other.size), data(new int[other.size]) {std::copy(other.data, other.data + other.size, data);std::cout << "Copy constructor called" << std::endl;}DynamicArray& operator=(const DynamicArray &other) {if (this != &other) {
相关文章:
《C++ Primer》导学系列:第 13 章 - 拷贝控制
13.1 拷贝、赋值与析构函数 拷贝控制是C++中类设计的重要组成部分,用于管理对象的复制、赋值和销毁过程。理解并正确实现拷贝控制函数(拷贝构造函数、拷贝赋值运算符和析构函数)对于编写健壮和高效的C++程序至关重要。 13.1.1 拷贝构造函数 拷贝构造函数用于创建对象的副…...
c++ 图论2 深度优先算法和广度优先算法
修改一下深度优先算法和广度优先算法,标出每一个节点相对于遍历起始位置的层级,遍历起始起点为第一层,和第一层相连的节点为第二层,以此类推 定义一个新的结构 struct NodeWithLevel {TreeNode* node;int level;NodeWithLevel(T…...

【Qt】初识QtQt Creator
一.简述Qt 1.什么是Qt Qt 是⼀个 跨平台的 C 图形⽤⼾界⾯应⽤程序框架 。它为应⽤程序开发者提供了建⽴艺术级图形界⾯所需的所有功能。它是完全⾯向对象的,很容易扩展。Qt 为开发者提供了⼀种基于组件的开发模式,开发者可以通过简单的拖拽和组合来实现…...

Android 11.0 修改系统显示大小导航栏消失
Android 11.0 修改系统显示大小导航栏消失 1.显示大小设置为大时,导航栏图标不显示。 设置为大,较大,最大时,导航栏图标不显示。 2.开始怀疑是导航栏被隐藏了,各种折腾无效。 3.发现: frameworks/base/pa…...

RocketMQ源码学习笔记:Producer启动流程
这是本人学习的总结,主要学习资料如下 马士兵教育rocketMq官方文档 目录 1、Overview1.1、创建MQClientInstance1.1.1、检查1.1.1、MQClientInstance的ID 1.2、MQClientInstance.start() 1、Overview 这是发送信息的代码样例, DefaultMQProducer produ…...
Node.js 和浏览器环境中都使用 WebSocket
使用WebSocket为什么不适配双端 浏览器环境本身就支持 WebSocket,直接使用 JavaScript 内置的 WebSocket 对象来建立连接。 Node中本身并没有内置 WebSocket 协议的支持,所以需要使用第三方库 ws来实现 WebSocket 功能。 一. 使用跨平台 WebSocket 库 …...

css美化滚动条样式
效果展示 实现 滚动条宽,高度 /* 整体滚动条 */ ::-webkit-scrollbar {width: 10px; }/* 滚动条轨道 */ ::-webkit-scrollbar-track {background-color: #ffffff;border-radius: 6px; }/* 滚动条滑块 */ ::-webkit-scrollbar-thumb {background-color: #888;borde…...
由浅入深,走进深度学习(补充篇:转置卷积和FCN)
本期内容是针对神经网络层结构的一个补充,主要内容是:转置卷积和全连接卷积网络 相关内容: 由浅入深,走进深度学习(2)_卷积层-CSDN博客 由浅入深,走进深度学习(补充篇:…...

Linux基础篇——目录结构
基本介绍 Linux的文件系统是采用级层式的树状目录结构,在此结构中的最上层是根目录"/",然后在根目录下再创建其他的目录 在Linux中,有一句经典的话:在Linux世界里,一切皆文件 Linux中根目录下的目录 具体的…...
星际编码:Swifter.Json,.NET宇宙中的数据处理新星
概述 在数字化的星辰大海中,数据是宇宙的通用语言。在.NET这一广袤的星系中,JSON作为信息交换的媒介,扮演着至关重要的角色。今天,我们要探索的是一颗新星——Swifter.Json,一个功能全面且性能卓越的JSON序列化和反序列…...

python 压缩数据
requests 是 Python 中一个非常流行的 HTTP 库,用于发送各种 HTTP 请求。下面是一个使用 requests 库发送简单 GET 请求和 POST 请求的示例: 首先,确保你已经安装了 requests 库。如果还没有安装,可以使用 pip 进行安装ÿ…...

nacos在k8s上的集群安装实践
目录 概述实践nfs安装使用 k8s持久化nacos安装创建角色部署数据库执行数据库初始化语句部署nacos ingress效果展示问题修复 结束 概述 本文主要对 nacos 在k8s上的集群安装 进行说明与实践。主要版本信息,k8s: 1.27.x,nacos: 2.0.3。运行环境为 centos 7…...

数据结构—判断题
1.数据的逻辑结构说明数据元素之间的顺序关系,它依赖于计算机的存储结构。 答案:错误 2.(neuDS)在顺序表中逻辑上相邻的元素,其对应的物理位置也是相邻的。 答案:正确 3.若一个栈的输入序列为{1, 2, 3, 4, 5},则不…...
树莓派挂载的移动硬盘badblocks坏道屏蔽,以这个为准
!!!use 这里要设置块大小和磁盘相同 badblocks -b 4096 -s -c 512 -v -o /a/2/bads4.txt /dev/sda5 检测完重新检测跳过之前的记录 badblocks -i /a/2/bads4.txt -b 4096 -s -c 512 -v -o /a/2/bads5.txt /dev/sda5 可以查看磁盘具体block总数和大小 sudo dumpe2fs /dev/sda5 …...

Unity开箱即用的UGUI面板的拖拽移动功能
文章目录 👉一、背景👉二、效果图👉三、原理👉四、核心代码👉五,总结 👉一、背景 之前做PC项目时常常有面板拖拽移动的需求,今天总结封装一下,做成一个随时随地可复用的…...

春秋云境:CVE-2022-25411[漏洞复现]
根据题目提示和CNNVD优先寻找后台管理地址 靶机启动后,使用AWVS进行扫描查看网站结构 在这里可以看到后台管理的登录地址:/admin/,根据题目提示可知是弱口令 尝试admin、123456、admin666、admin123、admin888...等等常见弱口令 正确的账户…...

java基础知识点全集
JAVA的所有知识点 一、基础的数组、数据类型、输入输出二、类与对象1. 三大特征(1) 封装(2)继承(3)多态 2. 类的实例化(1) 类通过NEW来创建(2) 类的继承&…...

如何完成域名解析验证
一:什么是DNS解析: DNS解析是互联网上将人类可读的域名(如www.example.com)转换为计算机可识别的IP地址(如192.0.2.1)的过程,大致遵循以下步骤: 查询本地缓存:当用户尝…...
2024年6月个人工作生活总结
title: 2024年6月个人工作生活总结 urlname: code-for-2024-06 tags: 代码积累知识总结 categories:我的程序代码 date: 2024-06-30 00:00:00 photos:gallery/tech/c2.jpg 本文为 2024年6月工作生活总结。 研发编码 编码和注释 因某些需要,重拾了2019年的工程代码…...

Json与Java类
简介 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。JSON数据由键值对构成,并以易于阅读的文本形式展现,支持数组、对象、字符串、数字、布尔值…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...

关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...