C++ Primer 交换操作

欢迎阅读我的 【C++Primer】专栏

专栏简介:本专栏主要面向C++初学者,解释C++的一些基本概念和基础语言特性,涉及C++标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级程序设计技术。希望对读者有帮助!

在这里插入图片描述
在这里插入图片描述

目录

  • 13.3交换操作
    • 编写我们自己的swap函数
    • swap函数应该调用swap,而不是std::swap
    • 在赋值运算符中使用swap

13.3交换操作

除了定义拷贝控制成员,管理资源的类通常还定义一个名为swap的函数。对于那些与重排元素顺序的算法一起使用的类,定义swap是非常重要的。这类算法在需要交换两个元素时会调用swap。

如果一个类定义了自己的swap,那么算法将使用类自定义版本。否则,算法将使用标准库定义的swap。虽然与往常一样我们不知道swap是如何实现的,但理论上很容易理解,为了交换两个对象我们需要进行一次拷贝和两次赋值。例如,交换两个类值HasPtr对象的代码可能像下面这样:

HasPtr temp=v1;//创建v1的值的一个临时副本
v1 = v2}//将v2的值赋予v1
v2=temp;//将保存的v1的值赋子v2

这段代码将原来v1中的string拷贝了两次一一第一次是HasPtr的拷贝构造函数将v1拷贝给temp,第二次是赋值运算符将temp赋予v2。将v2赋予v1的语句还拷贝了原来v2中的string。如我们所见,拷贝一个类值的HasPtr会分配一个新string并将其拷贝到HasPtr指向的位置。

理论上,这些内存分配都是不必要的。我们更希望swap交换指针,而不是分配string的新副本。即,我们希望这样交换两个HasPtr:

string*temp=v1.ps;//为v1.ps中的指针创建一个副本
v1.ps=v2.ps;//将v2.ps中的指针赋孙v1.ps
v2.ps=temp;//将保孙的v1.ps中原来的指针赋子v2.ps

编写我们自己的swap函数

可以在我们的类上定义一个自己版本的swap来重载swap的默认行为。swap的典型实现如下:

class HasPtr{
friend void swap(HasPtr&,HasPtr&);
//其他成员定义
};
inline
void swap(HasPtr&lhs,HasPtr&rhs)
{
using std::swap;
swap(lhs.ps,rhs.ps);//交换指针,而不是string数据
swap(lhs.i,rhs.i);//交换int成员
}

我们首先将swap定义为friend,以便能访问HasPtr的(private的)敏据成员。由于swap的存在就是为了优化代码,我们将其声明为inline函数。swap的函数体对给定对象的每个数据成员调用swap。我们首先swap绑定到rhs和lhs的对象的指针成员,然后是int成员。

与拷贝控制成员不同,swap并不是必要的。但是,对于分配了资源的类,定义swap可能是一种很重要的优化手段。

swap函数应该调用swap,而不是std::swap

此代码中有一个很重要的微妙之处:虽然这一点在这个特殊的例子中并不重要,但在一般情况下它非常重要一一swap函数中调用的swap不是std::swap。在本例中,数据成员是内置类型的,而内置类型是没有特定版本的swap的,所以在本例中,对swap的调用会调用标准库std::swap。

但是,如果一个类的成员有自己类型特定的swap函数,调用std::swap就是错误的了。例如,假定我们有另一个命名为Foo的类,它有一个类型为HasPtr的成员h。如果我们未定义Foo版本的swap,那么就会使用标准库版本的swap。如我们所见,标准库swap对HasPtr管理的string进行了不必要的拷贝。

我们可以为Foo编写一个swap函数,来避免这些拷贝。但是,如果这样编写Foo版本的swap:

void swap(Foo& lhs,Foo &rhs)
{
//错误:这个函数使用了标准库版本的swap,而不是HasPtr版本
std::swap(lhs.h,rhs.h);
//交换类型Foo的其他成员
}

此编码会编译通过,且正常运行。但是,使用此版本与简单使用默认版本的swap并没有任何性能差异。问题在于我们显式地调用了标准库版本的swap。但是,我们不希望使用std中的版本,我们希望调用为HasPtr对象定义的版本。

正确的swap函数如下所示:

void swap(Foo &lhs,Foo &rhs){
using std::swap;
swap(lhs.h,rhs.h);//使用HasPtr版本的swap
//交换类型Foo的其他成员
}

每个swap调用应该都是未加限定的。即,每个调用都应该是swap,而不是std::swap。如果存在类型特定的swap版本,其匹配程度会优于std中定义的版本。因此,如果存在类型特定的swap版本,swap调用会与之匹配。如果不存在类型特定的版本,则会使用std中的版本(假定作用域中有using声明)。

非常代细的读者可能会奇怪为什么swap函数中的using声明没有隐藏HRasPtr版本swap的声明。

在赋值运算符中使用swap

定义swap的类通常用swap来定义它们的赋值运算符。这些运算符使用了一种名为拷贝并交换(copy and swap)的技术。这种技术将左侧运算对象与右侧运算对象的一个副本进行交换:

//注意rhs是按值传递的,意味着HasPtr的拷贝构造函数
//将右侧运算对象中的string拷贝到rhs
HasPtr& HasPtr::operator=(HasPtr rhs)
{
//交换左侧运算对象和局部变量rhs的内部
swap(*this,rhs);//rhs现在指向本对象曾经使用的内存
return*this;//rhs被销毁,从而delete了rhs中的指针
}

在这个版本的赋值运算符中,参数并不是一个引用,我们将右侧运算对象以传值方式传递给了赋值运算符。因此,rhs是右侧运算对象的一个副本。参数传递时拷贝HasPtr的操作会分配该对象的string的一个新副本。

在赋值运算符的函数体中,我们调用swap来交换hs和this中的数据成员。这个调用将左侧运算对象中原来保存的指针存入rhs中,并将zhs中原来的指针存入this中。因此,在swap调用之后,*this中的指针成员将指向新分配的string一一右侧运算对象中string的一个副本。

当赋值运算符结束时,rhs被销毁,HasPtr的析构函数将执行。此析构函数delete rhs现在指向的内存,即,释放掉左侧运算对象中原来的内存。

这个技术的有趣之处是它自动处理了自赋值情况且天然就是异常安全的。它通过在改变左侧运算对象之前拷贝右侧运算对象保证了自赋值的正确,这与我们在原来的赋值运算符中使用的方法是一致的。它保证异常安全的方法也与原来的赋值运算符实现一样。代码中唯一可能抛出异常的是拷贝构造函数中的new表达式。如果真发生了异常,它也会在我们改变左侧运算对象之前发生。

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

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

相关文章

go切片定义和初始化

1.简介 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。切片的使用和数组类似,遍历切片、访问切片的元素和切片的长度都一样。。切片的长度是可以变化的,因此切片是一个可以动态变化的数…

huggingface镜像站hf-mirror的各大AI模型文件下载

一、说明 huggingface地址:https://huggingface.co 但是由于需要国外网络,国内网络延迟较大或无法下载,所以使用国内镜像站: hf-mirror地址:https://hf-mirror.com/ 二、下载方法 1.本机安装了GIT 2.打开HF-Mirro…

Unity Shader 学习15:可交互式雪地流程

本质是 利用顶点变换实现的: 通过一个俯视整个场地的正交摄像机,根据绑定在移动物体身上的粒子系统,来获取物体移动过的位置,记录到一张RenderTexture上作为轨迹图,再通过这张图来对雪地做顶点变换。 1. 由于顶点变换需…

自由学习记录(42)

可能会出现到后面没有教程可以看,走不动,,但还是尝试吧 过程远比想象的要多 那连Live2d的这些脚本怎么控制的都要了解一下 ------------ 文件类型和扩展名 | 编辑手册 | Live2D Manuals & Tutorials 全部导入之后 在这下载SDK Live2D…

LeetCode - 28 找出字符串中第一个匹配项的下标

题目来源 28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode) 题目解析 暴力解法 本题如果采用暴力解法的话,可以定义两个指针 i,j,其中 i 指针用于扫描 S(haystack)串,j 指针…

windows下使用msys2编译ffmpeg

三种方法: 1、在msys2中使用gcc编译 2、在msys2中使用visual studio编译(有环境变量) 3、在msys2中使用visual studio编译(无环境变量) 我的环境: 1、msys2-x86_64-20250221 2、vs2015 3、ffmpeg-7.1…

【Python 数据结构 9.树】

我装作漠视一切,其实我在乎的太多,但我知道抓得越紧越容易失去 —— 25.3.6 一、树的基本概念 1.树的定义 树是n个结点的有限集合,n0时为空树。当n大于0的时候,满足如下两个条件: ① 有且仅有一个特定的结点&#xff…

数据结构--AVL树

一、二叉搜索树(Binary Search Tree, BST) 基本性质 对于树中的每个节点,其左子树中的所有节点值均小于该节点值。其右子树中的所有节点值均大于该节点值。左右子树也分别是二叉搜索树。 极端场景 在极端情况下,如插入节点顺序…

C语言——链表

大神文献:https://blog.csdn.net/weixin_73588765/article/details/128356985 目录 一、链表概念 1. 什么是链表? 1.1 链表的构成 2. 链表和数组的区别 数组的特点: 链表的特点: 二者对比: 二…

Qt:多线程

目录 初识Qt多线程 QThread常用API QThread的使用 Qt中的锁 条件变量和信号量 初识Qt多线程 Qt 多线程 和 Linux 中的线程本质是一个东西 Linux 中学过的 多线程 APl,Linux 系统提供的 pthread 库 Qt 中针对系统提供的线程 API 重新封装了 C11 中,…

如何用Kimi生成PPT?秒出PPT更高效!

做PPT是不是总是让你头疼?😩 快速制作出专业的PPT,今天我们要推荐两款超级好用的AI工具——Kimi 和 秒出PPT!我们来看看哪一款更适合你吧!🚀 🥇 Kimi:让PPT制作更轻松 Kimi的生成效…

计算机毕业设计SpringBoot+Vue.js车辆管理系统(源码+文档+PPT+讲解)

温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…

机器学习4-PCA降维

1 降维 在数据处理过程中,会碰到维度爆炸,维度灾难的情况,为了得到更精简更有价值的信息,我们需要进一步处理,用的方法就是降维。 降维有两种方式:特征抽取、特征选择 特征抽取:就是特征映射…

探秘沃尔什-哈达玛变换(WHT)原理

沃尔什-哈达玛变换(WHT)起源 起源与命名(20世纪早期) 数学基础:该变换的理论基础由法国数学家雅克哈达玛(Jacques Hadamard)在1893年提出,其核心是哈达玛矩阵的构造。扩展与命名&…

使用 vxe-table 导出 excel,支持带数值、货币、图片等带格式导出

使用 vxe-table 导出 excel,支持带数值、货币、图片等带格式导出,通过官方自动的导出插件 plugin-export-xlsx 实现导出功能 查看官网:https://vxetable.cn gitbub:https://github.com/x-extends/vxe-table gitee:htt…

uniapp实现的个人中心页面(仿小红书)

采用 uniapp 实现的一款仿小红书个人中心页面模板,支持vue2、vue3, 同时适配H5、小程序等多端多应用。 简约美观大方 可到插件市场下载尝试: https://ext.dcloud.net.cn/plugin?id22516 示例

步进电机软件细分算法解析与实践指南

1. 步进电机细分技术概述 步进电机是一种将电脉冲信号转换为角位移的执行机构,其基本运动单位为步距角。传统步进电机的步距角通常为 1.8(对应 200 步 / 转),但在高精度定位场景下,这种分辨率已无法满足需求。细分技术…

tauri-plugin-shell插件将_blank的a标签用浏览器打开了,,,解决办法

不要使用这个插件,这个插件默认会将网页中a标签为_blank的使用默认浏览器打开,但是这种做法在我的程序里不是很友好,我需要自定义这种行为,当我点击我自己的链接的时候,使用默认浏览器打开,当点击别的链接的…

ESP8266 NodeMCU 与 Atmega16 微控制器连接以发送电子邮件

NodeMCU ESP8266 AVR 微控制器 ATmega16 的接口 Atmega16 是一款低成本的 8 位微控制器,比以前版本的微控制器具有更多的 GPIO。它具有所有常用的通信协议,如 UART、USART、SPI 和 I2C。由于其广泛的社区支持和简单性,它在机器人、汽车和自动化行业有广泛的应用。 Atmega1…

C++查看动态库导出哪些函数以及动态库导出形式

1、查看动态库导出哪些函数 1.1、在 Windows 和 Linux 上,可以使用不同的方法来查看动态库(.dll 或 .so)导出的函数 Windows:使用 dumpbin:Windows 提供了 dumpbin 工具(Visual Studio 自带)&…