Re:从零开始的 C++ STL篇(三)string的疑难问题详细解析:深拷贝,写时拷贝,三个swap

◆ 博主名称: 晓此方-CSDN博客

大家好,欢迎来到晓此方的博客。

⭐️C++系列个人专栏:

Re:从零开始的C++_晓此方的博客-CSDN博客

⭐️踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰


目录

0.1概要&序論

一,深拷贝的代码优化

1.1深拷贝和浅拷贝

1.2深拷贝的传统写法与现代写法

二,三个swap

2.1C++自带的全局Swap模板

2.2成员函数Swap

[2.3 重载的全局Swap函数](#2.3 重载的全局Swap函数)

2.4一张表格看懂区别

三,引用计数与写时拷贝

四,VS下和g++下string的结构不同

五,一些转换接口

六,实现构造函数的时候出现的一些问题


0.1概要&序論

这里是此方,久しぶりです! 本篇文章是string的最后一篇 ,在前面我们主要讲解了string的各种接口的使用,本片,我将带领大家解决string的一些疑难杂症 ,帮助大家更好的理解string。这里是「此方」。让我们现在开始吧!

一,深拷贝的代码优化

1.1深拷贝和浅拷贝

**浅拷贝就是按字节拷贝,深拷贝就是创建并拷贝给临时对象再拷贝。**通俗的讲,浅拷贝拷贝指针,深拷贝拷贝指针指向的内容。

两张图看懂:

1.2深拷贝的传统写法与现代写法

深拷贝离不开拷贝构造,我们来看看string的拷贝构造函数时怎么实现的。

cpp 复制代码
string(const string& s)
{
    _str = new char[s._capacity + 1];
    strcpy(_str, s._str);
    _size = s._size;
    _capacity = s._capacity;
}

这是一种传统的深拷贝方式我们用一张图解释它:

创建一个新的空间并指向,将原来空间的数据拷贝给新空间。修改_size和_capacity等数据。

这样做还是太麻烦了,于是出现了现代写法:

cpp 复制代码
string(const string& s)
{
    string tmp(s._str);
    swap(tmp);
}

我们同样用一张图来解释它:

用s._str指针指向的字符串构造一个新的tmp对象,并将这个tmp对象的指针与我们的目标对象的指针互换。相比传统方法更省事,将"本应该由_str完成的事情"让"tmp代理完成"。

二,三个swap

2.1C++自带的全局Swap模板

头文件:<algorithm> (C++98),<utility>(C++11)。

时间复杂度 :O(n),因为涉及拷贝构造和赋值操作(对大对象效率低)

用途 :适用于所有类型,但没有特别优化

测试代码:

cpp 复制代码
std::string a = "hello", b = "world";
std::swap(a, b); // 使用通用版本,除非有特化

为什么效率低下?

这是这个swap函数的底层。它一共进行了三次深拷贝。如果对于内置类型来说,影响不算什么,但是对于自定义或者是STL中的类型,效率一定会大打折扣。

2.2成员函数Swap

这是底层最高效的交换方式。

作用:交换当前字符串与另一个字符串的内容。

实现方式:交换内部指针,不复制字符。

时间复杂度:O(1) ------ 常数时间,非常高效。

头文件<string>

特点:是类的成员函数,只能通过对象调用。

测试代码

cpp 复制代码
a.swap(b); // a 是 this,b 是参数

2.3<string>重载的全局Swap函数

作用 :交换两个 string 对象的值。

实现方式 :通常直接调用 x.swap(y),即委托给成员函数。

时间复杂度:O(1)

测试代码

cpp 复制代码
std::swap(a, b); // 优先使用这个特化版本

头文件<string>

关键点 :这是对通用**std::swap** 的重载 ,使得当参数为 **string**时,自动选择此版本,避免使用慢的通用模板。(当一个模板和一个现成的函数同时存在时编译器会选择现成函数。)

2.4一张表格看懂区别

特性 std::swap(T&, T&)(通用模板) std::string::swap(string&)(成员函数) std::swap(string&, string&)(非成员特化)
定义位置 <algorithm> / <utility> <string> <string>
是否模板 否(特化)
调用方式 swap(a, b) a.swap(b) swap(a, b)
实现机制 拷贝构造 + 赋值(O(n)) 交换内部指针(O(1)) 通常调用 x.swap(y)(O(1))这里利用了编译器的优化
性能 慢(尤其对大字符串) 快(O(1)) 快(O(1))
是否推荐使用 不推荐用于**string** 推荐 推荐(更自然)

三,引用计数与写时拷贝

++难度较高,我们这里仅作了解。++

深拷贝消耗大,浅拷贝又会出现"连带修改"和"多次析构"的问题。于是介于两者之间,引用计数与写时拷贝出现了。(这个技术C++11的移动语义完爆,现已过时,但是算法思想值得学习)

通俗的讲:引用计数记录的是同一块内存空间被多少个指针/引用指着。

写时拷贝的意思是**"受控共享 + 写前分离":**通俗的讲就是:我一般情况我不进行深拷贝,在要写入数据的时候进行深拷贝。以此实现性能优化。

VS下没有写时拷贝,但是Linux下有写时拷贝。在VS下引入写时拷贝也很容易,加入以下类似代码(不细讲):

四,VS下和g++下string的结构不同

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

vs下string的结构 string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string中字符串的存储空间:

  • 当字符串长度小于16时,使用内部固定的字符数组来存放。
  • 当字符串长度大于等于16时,从堆上开辟空间。
cpp 复制代码
union _Bxty
{   // storage for small buffer or pointer to larger one
    value_type _Buf[_BUF_SIZE];
    pointer _Ptr;
    char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建 好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量 最后:还有一个指针做一些其他事情。 故总共占16+4+4+4=28个字节。

这样我们看原始视图就看得懂了:

g++下,string是通过写时拷贝实现的 ,string对象总共占4个字节,内部只包含了一个 指针, 该指针将来指向一块堆空间,内部包含了如下字段:

  • 空间总大小
  • 字符串有效长度
  • 引用计数
cpp 复制代码
struct _Rep_base
{
    size_type               _M_length;
    size_type               _M_capacity;
    _Atomic_word            _M_refcount;
};

如上,是string在g++下的一个示意图,我们的指针str如果要访问到h字符,就必须采用str+sizeof(_Rep_base);

五,一些转换接口

激进一点来说,以下圈出来的有用,其他全部冗余。

  • stoi接口是string转化成int类型。
  • stod接口时string转化成double类型。
  • to_string是将类型转化成string类型。

六,实现构造函数的时候出现的一些问题

cpp 复制代码
string(const char* str = "")
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];
    strcpy(_str, str);
}

这里我们在初始化的时候使用的是"",这表示的是传递给字符串初始化的是一个'\0'。

为什么不初始化成nullptr?

因为虽然我们访问数据有operator[],但是在一些时候我们也需要解引用,如果在没有数据插入的时候解引用会导致空指针访问错误。


好了,本期内容到此结束,我是此方,我们下期再见。バイバイ!

相关推荐
Linux猿7 小时前
基于Python的图书管理系统(可执行源码+详细报告+详细注释+运行步骤)
开发语言·python·毕业设计·课程设计·管理系统·图书管理系统项目
lanbing7 小时前
在Mac OS系统中安装Go语言环境教程
开发语言·后端·golang
sensen_kiss7 小时前
Python安装与环境配置全程详细教学(包含Windows版和Mac版)
开发语言·python·pycharm
程序员敲代码吗7 小时前
嵌入式C++开发注意事项
开发语言·c++·算法
Dr.Kun7 小时前
【鲲码园Python】基于yolov11的番茄成熟度检测系统
开发语言·python·yolo
无心水7 小时前
17、Go协程通关秘籍:主协程等待+多协程顺序执行实战解析
开发语言·前端·后端·算法·golang·go·2025博客之星评选投票
洛克大航海8 小时前
Python面向对象
开发语言·python·面向对象
君义_noip8 小时前
信息学奥赛一本通 1463:门票
c++·算法·哈希算法·信息学奥赛·csp-s
草莓熊Lotso8 小时前
Qt 控件美化与交互进阶:透明度、光标、字体与 QSS 实战
android·java·开发语言·c++·人工智能·git·qt