当前位置: 首页 > backend >正文

【C++初阶】模拟实现string(二):swap优化与写时拷贝机制

文章目录

  • 前言
  • 一、swap
    • 1.1 算法库中的swap
    • 1.2 string的成员函数swap
    • 1.3 string 全局的swap
  • 二、拷贝构造和赋值的简洁写法
    • 2.1 拷贝构造
    • 2.2 赋值
  • 三、写时拷贝
    • 3.1 引用计数
    • 3.2 写时拷贝
  • 四、编码


前言

我们之前模拟实现了 string类的一些常用接口,今天我们将继续看看 string 背后的一些实现细节,比如 swap 的不同写法、拷贝构造与赋值运算符的简洁实现,以及“写时拷贝”的相关概念。

一、swap

在学习string的过程中,我们发现:

string既自己实现了一个swap

又在全局实现了一个swap

算法库中还有一个swap

我们有一个疑惑,为什么要搞这么多swap呢?

1.1 算法库中的swap

我们先看一看算法库中的swap

然后写一个代码测试一下:

void test1()
{zhangsan::string s1("hello world");zhangsan::string s2("xxxxxxxxxxxxxxxxxxxxx");//swap(s1, s2);s1.swap(s2);cout << s1 << endl;cout << s2 << endl;
}

我们发现,使用算法库中的 swap ,交换前的地址和交换后的地址均不相同,说明开了新空间

简单分析一下:

如果我们用的是算法库中的swap函数来交换 s1,s2 这两个string对象,那么需要

1)先用 s1 构造一个 c,让 c 开一个和 s1 一样大的空间

​2)然后让 s2 赋值给 s1:让 s1 释放旧空间,并开一个和 s2 一样大的空间

3)最后让 c 赋值给s2 :让 s2 释放旧空间,并开一个和 c 一样大的空间

4)最后出作用域,c 会析构,释放空间

使用这个swap,我们申请了三次空间,释放了三次空间,这样的代价是很大的

1.2 string的成员函数swap

实际上,交换两个string对象不需要这么复杂,我们只需要将两个string对象的字符指针交换,_size 和 _capacity交换

我们可以自己试着实现一个string 成员函数的 swap:

void swap(string& s)
{//交换内置类型调用算法库中的swap即可std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}

细节:

如果直接写swap 默认会优先调用我们自己写的swap,即zhangsan这个命名空间的swap,所以我们需要指定命名空间std,调用算法库中的swap

再测试一下:

void test1()
{zhangsan::string s1("hello world");zhangsan::string s2("xxxxxxxxxxxxxxxxxxxxx");//swap(s1, s2);s1.swap(s2);cout << s1 << endl;cout << s2 << endl;
}

我们发现 swap 交换前后地址没变,说明并没有开新空间,只是交换了指针

总结: 使用算法库中的swap交换 string对象 代价大,使用string成员函数中的swap 代价小

1.3 string 全局的swap

我们现在已经了解了算法库中的swap和string成员函数中的swap的差异,但是string在全局还有一个 swap,这个swap设计出来又有什么意义呢?

//string全局的swap
void swap(string& s1, string& s2)
{s1.swap(s2);
}

string全局的swap其实是对成员函数的封装,是普通函数,而算法库中的swap是函数模版。

我们之前学过,如果既写了函数模版,又写了普通函数,如果传的参数和普通函数的参数类型匹配,编译器会优先调用普通函数。

也就是说string实现全局的swap后,无论我们是通过 对象.swap的方式调用成员函数的swap,还是直接调用swap函数,都不会调用算法库中的swap

补充说明一下:

C++11提供的右值引用和移动语义后,即使string不提供全局的swap,也不会调用算法库中的swap,而是调用string成员函数中深拷贝的swap

二、拷贝构造和赋值的简洁写法

传统写法和简洁写法没有效率的提升,只是简洁一点,本质是复用

2.1 拷贝构造

string 拷贝构造的传统写法:

传统写法是自己开空间,自己拷贝数据

//s2(s1)
string(const string& s)
{_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}

string 拷贝构造的简洁写法:

//s2(s1)
string(const string& s)
{string tmp(s._str);swap(tmp);
}

先用 s1 的 _str 构造一个 tmp 对象,再让 s2 和 tmp 交换

2.2 赋值

string赋值运算符重载的传统写法:

// s1 = s3
string& operator=(const string& s)
{if (this != &s) // 防止出现自己给自己赋值的情况{delete[] _str;_str = new char[s._capacity + 1];strcpy(_str, s._str);_capacity = s._capacity;_size = s._size;}return *this;
}

string赋值运算符重载的简洁写法:

// s1 = s3
string& operator=(const string& s)
{if (this != &s) // 防止出现自己给自己赋值的情况{string tmp(s._str);swap(tmp);}return *this;
}

先用 s3 的 _str 构造一个 tmp,再让 s1 和 tmp 交换。传统写法中,我们需要手动释放s1的空间,但在现代写法中,由于tmp是局部变量,出作用域就调用析构函数,相当于把之前s1的空间销毁了。

这个写法还可以进一步改进:

//s1 = s3
string& operator=(string s)
{swap(s);return *this;
}

直接传值传参,调用拷贝构造,让 s 和 s3 有一样大的空间和一样的值,然后让 this 指向的对象和 s 进行交换

三、写时拷贝

先看一个场景:
s2(s1),用 s1 拷贝构造 s2,使用的是深拷贝,但是可能过一会其中一个就要销毁,我们觉得代价太大了,但是如果使用浅拷贝,会有两个问题 1.会析构两次 2.如果修改了其中一个,另一个也会修改。我们可以利用引用计数和写时拷贝解决这个两个问题

3.1 引用计数

引用计数就是在s1指向这块空间的时候,额外开辟一块空间,这个空间存储一个整型值,这个整型值叫引用计数。引用计数是来记录有多少个对象指向这块空间,析构的时候就 --引用计数 ,当减到0 就释放 s1 指向的这块空间。

通过引用计数,解决了多个对象指向同一块空间,析构多次的问题

3.2 写时拷贝

写时拷贝是在浅拷贝的基础上增加了引用计数的方式来实现的。写时拷贝可以解决使用浅拷贝的另一个问题,即如果修改了其中一个对象,其它的对象也会修改。

  • 当只有一个对象指向这块空间的时候,修改这个对象就直接在这块空间修改
  • 如果有多个对象指向这块空间,那么哪个对象要修改的时候,哪个对象就进行深拷贝,并且引用计数–

举个例子:

s2(s1) ,假如我们现在要把s1下标为0对应位置的字符改为字符 x

写时拷贝就是谁写谁进行深拷贝

如果想了解更多关于写时拷贝的知识,可以看下面两篇大佬写的文章

https://coolshell.cn/articles/1443.html

https://coolshell.cn/articles/12199.html

四、编码

我们之前在string的基本介绍和使用中中提到过string类是一个模版,原模版叫basic_string,除了有string外,C11还新增了u16string 和 u32string,也是字符数组,

  • u16string:每个字符是16位的char,占两个字节
  • u32string:每个字符是4个字节

由于这涉及到编码,所以我们简单学习一下编码相关的知识

编码:一些比特位的值 <-----映射-----> 文字符号

1)字符存储到计算机中,需要将字符对应的码值(整数)找出来。
     比如 ‘a’
     存储:‘a’ —> 码值97 —> 对应的二进制 ----> 存储
     读取:对应的二进制 ----> 码值97 —> ‘a’ —> 显示

2)字符和码值的对应关系是通过字符编码表决定的(规定好的)

UTF-16、UTF-32和UTF-8

  • UTF-16 :以两个字节为一个单位
  • UTF-32 :以四个字节为一个单位
  • UTF-8 :以一个字节为单位,是一种变长编码,兼容ASCII

除了 string、u16string 和 u32string 之外,标准库还提供了 wstring,它使用 wchar_t 作为字符类型。wchar_t 通常是 2 个字节

http://www.xdnf.cn/news/9096.html

相关文章:

  • 知识宇宙-职业篇:互联网产品经理PM
  • Debian操作系统全面解析:从起源到应用
  • MPI与多线程(如OpenMP)混合编程注意事项与性能优化
  • React与Vue的内置指令对比
  • 【C/C++】如何在一个事件驱动的生产者-消费者模型中使用观察者进行通知与解耦
  • [TriCore] 01.QEMU 虚拟化 TriCore 架构中的寄存器 指令
  • 小红书文章内容提取免费API接口教程
  • java基础(面向对象进阶高级)内部类
  • leetcode hot100刷题日记——17.搜索插入位置
  • Linux中logger命令的使用方法详解
  • 嵌入式开发STM32 -- 江协科技笔记
  • window 显示驱动开发-呈现开销改进(二)
  • c++进阶——智能指针
  • maven中的grpc编译插件protobuf-maven-plugin详解
  • SQL进阶之旅 Day 4:子查询与临时表优化
  • C/C++语言中成双成对出现的都有哪些?
  • STM32程序运行不了,仿真功能也异常,连断点和复位都异常了
  • 网络流学习笔记(基础)
  • Beckhoff PLC 功能块 FB_CTRL_ACTUAL_VALUE_FILTER (模拟量滤波)
  • vSphere 7.0 client 提示HTTP状态 500- 内部服务器错误
  • GROUP BY SQL
  • 【动态规划】子数组系列(一)
  • 【备战秋招】C++音视频开发经典面试题整理
  • 学校住宿管理系统——仙盟创梦IDE
  • OpenGL Chan视频学习-7 How I Deal with Shaders in OpenGL
  • 0基础学习Linux之揭开朦胧一面:环境基础开发工具
  • java8函数式接口(函数式接口的匿名实现类作为某些方法的入参)
  • 2025年5月系统架构设计师考试真题回忆版
  • 7.安卓逆向2-frida hook技术-介绍
  • 重学计算机网络之命令整理