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

【C++ STL】你真的了解string吗?浅谈string的底层实现

文章目录

  • 底层结构概述
  • 扩容机制
  • 浅拷贝与深拷贝
  • 插入和删除的效率
  • 浅谈VS和g++的优化
  • 总结

底层结构概述

string可以帮助我们很好地管理字符串,但是你真的了解她吗?事实上,string的设计是非常复杂的,拥有上百个接口,但最常用的就那几个。如果不了解string的底层,就很难优雅地写出高效的代码!

要想高效地管理一个string类,至少需要3个成员变量,分别是:

char* _str;
size_t _size;
size_t _capacity;

比如要存储字符串"abcde",那么_str指向了a,_size=5表示有5个有效字符(不包括’\0’),_capacity=8表示当前空间最多存储8个字符(实际上是9个,因为有’\0’)。此时,_str就是c_str的返回值,_size就是size的返回值,_capacity就是capacity的返回值;堆区上的空间总大小是9个字节,最多保存除了’\0’之外的8个字符,换句话说,当前再插入3个字符,空间就满了,需要扩容。
在这里插入图片描述

扩容机制

_str指向的空间是动态开辟出来的,当容量不够用时,会扩容。扩容的步骤是:

  1. 申请新空间。
  2. 把旧空间的数据拷贝到新空间中。
  3. 释放旧空间。

在这里插入图片描述

设想一下,当字符串很长时,第2步的拷贝代价就会非常大。所以,我们要想方设法地减少甚至避免扩容

假设我们要反复地插入字符,插入100次,容量会怎么变化呢?

#include <iostream>
#include <string>
using namespace std;int main()
{string s;size_t capacity = s.capacity();cout << "init: capacity = " << capacity << endl;for (size_t i = 0; i < 100; i++){s.push_back('x');if (s.capacity() != capacity){capacity = s.capacity();cout << "new: capacity = " << capacity << endl;}}return 0;
}

VS2022运行结果:

在这里插入图片描述

可以观察到,一开始容量是15,第一次扩容为原来容量的2倍,后面每次扩容都为原来容量的1.5倍。

g++运行结果:

在这里插入图片描述

可以观察到,每次扩容都是原来容量的2倍。

如果我们能提前知晓,即将插入100个字符,就可以调用reserve,提前保留足够的空间,从而避免扩容的消耗

#include <iostream>
#include <string>
using namespace std;int main()
{string s;// 提前开空间,从而避免扩容的消耗!s.reserve(100);size_t capacity = s.capacity();cout << "init: capacity = " << capacity << endl;for (size_t i = 0; i < 100; i++){s.push_back('x');if (s.capacity() != capacity){capacity = s.capacity();cout << "new: capacity = " << capacity << endl;}}return 0;
}

VS2022运行结果:

在这里插入图片描述

g++运行结果:

在这里插入图片描述

浅拷贝与深拷贝

string是如何拷贝的呢?

如果不写拷贝构造函数,编译器会生成默认的拷贝构造函数,对内置类型按照字节拷贝,这种拷贝称作浅拷贝

举个例子,有一个string s1的结构如下:

在这里插入图片描述

此时来了另一个string s2,把s1的_str,_size和_capacity都拷贝过去,此时两个string的_str就指向了同一块空间!

在这里插入图片描述
此时,如果我们修改其中一个string,另一个string也会同时被修改!更可怕的是,当对象的生命周期结束时,会调用析构函数,由于两个string中的_str存储的是同一个地址,这个地址就会被delete两次,从而导致进程崩溃!

为了解决这个问题,string必须实现深拷贝!也就是说,我们需要重新申请一块空间,把"abcde"拷贝过去,让s2的_str指向新的空间!

在这里插入图片描述
这样,修改其中一个string就不会影响另一个string,而且两个string的_str指向不同的空间,不会出现同一块空间释放两次的问题了!

插入和删除的效率

如果要在字符串尾部插入一个字符,底层是如何实现的呢?只需要在_str[_size]的位置插入字符,再让_size++,最后再填一个’\0’即可!

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
当然,如果插入前,_size==_capacity,说明空间不够用了,要扩容!扩容的逻辑前面讲过,这里不再重复。

但是如果要在中间插入一个字符呢?甚至在头部插入呢?就要先挪动数据腾出空间,才能插入!

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
比起在尾部插入数据,多出了挪动数据的消耗,所以应尽可能地少在string的头部或中间插入数据

同理,如果要删除头部或中间的数据,也要挪动数据覆盖删除,所以应尽可能地避免删除头部或中间的数据

浅谈VS和g++的优化

VS2022的X86环境下,一个string类对象的大小是28字节;X64环境下,大小是40个字节。32位环境下,char*大小是4字节,size_t大小是4字节,那么_str,_size,_capacity的总大小是12字节;64位环境下,char*大小是8字节,size_t大小是8字节,那么_str,_size,_capacity的总大小是24字节。那么,剩下还有16字节去哪了呢?

观察一下监视窗口:

在这里插入图片描述

注意到有一个char[16]类型的数组_Buf。也就是说,VS在栈区上也申请了一块空间,长度是16个字节,当字符串的size<=15时,就存储在这个数组中;当size>15时,才会存储到堆区,这是为了减少堆区的内存碎片,因为字符串的长度一般不会超过15。

g++的X86环境下,一个string对象的大小是4字节;X64环境下,大小是8字节。这是由于底层只存储了一个指针,指针指向的空间中,存储了引用计数,_size和_capacity,以及C-string的数据。

这个引用计数又是啥玩意呢?这是g++对string做的优化,实现了写时拷贝(Copy On Write),创建对象时,把引用计数cnt初始化成1,拷贝的时候,cnt++。这样析构的时候,如果cnt不是1,就cnt--;如果cnt是1,再释放空间。当要对对象写入数据时,再进行深拷贝。这样极大地提升了拷贝的效率!

总结

  1. string的底层可以理解为一个指针和两个无符号整形变量,分别代表了c_str,size和capacity的返回值。
  2. 扩容是有代价的,尽可能使用reserve减少甚至避免扩容。
  3. string底层实现了深拷贝。
  4. 尽可能少地在string头部或者中间插入、删除数据。
  5. VS和g++对string做了一些优化。

相关文章:

【C++ STL】你真的了解string吗?浅谈string的底层实现

文章目录 底层结构概述扩容机制浅拷贝与深拷贝插入和删除的效率浅谈VS和g的优化总结 底层结构概述 string可以帮助我们很好地管理字符串&#xff0c;但是你真的了解她吗&#xff1f;事实上&#xff0c;string的设计是非常复杂的&#xff0c;拥有上百个接口&#xff0c;但最常用…...

17.3.1.3 灰度

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 灰度的算法主要有以下三种&#xff1a; 1、最大值法: 原图像&#xff1a;颜色值color&#xff08;R&#xff0c;G&#xff0c;B&a…...

基于CAS操作的atomic原子类型

在上一节的卖票程序中&#xff0c;我们讲解了如何在多线程中保证临界资源的正确访问——使用互斥锁&#xff0c;即 lock_guard<mutex> lock(mtx); count;lock_guard<mutex> lock(mtx); count--; 从汇编角度解释线程间互斥-mutex互斥锁与lock_guard的使用-CSDN博客…...

Rust HashMap详解及单词统计示例

在Rust中&#xff0c;HashMap是一种非常有用的数据结构&#xff0c;用于存储键值对。本文将深入介绍HashMap的特性&#xff0c;以及通过一个单词统计的例子展示其用法。 HashMap简介 HashMap是Rust标准库提供的用于存储键值对的数据结构。它允许通过键快速查找对应的值&#…...

命令执行讲解和函数

命令执行漏洞简介 命令执行漏洞产生原因 应用未对用户输入做严格得检查过滤&#xff0c;导致用户输入得参数被当成命令来执行 命令执行漏洞的危害 1.继承Web服务程序的权限去执行系统命会或读写文件 2.反弹shell&#xff0c;获得目标服务器的权限 3.进一步内网渗透 远程代…...

外包实在是太坑了,划水三年,感觉人都废了

先说一下自己的情况&#xff0c;专科生&#xff0c;19年通过校招进入杭州某个外包软件公司&#xff0c;干了接近3年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了3年的功…...

代码随想录算法训练营第19天

77. 组合 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 class Solution:def combine(self, n: int, k: int) -> List[List[int]]:path []res []def dfs(n,k,index):if len(path) k:res.append(path[:])returnfor i in range(index,n1):…...

树莓派5 EEPROM引导加载程序恢复镜像

树莓派5不能正常启动&#xff0c;可以通过电源led灯的闪码来判断错误发生的大致情形。 LED警告闪码 如果树莓派由于某种原因无法启动&#xff0c;或者不得不关闭&#xff0c;在许多情况下&#xff0c;LED会闪烁特定的次数来指示发生了什么。LED会闪烁几次长闪烁&#xff0c;然…...

循序渐进-讲解Markdown进阶(Mermaid绘图)-附使用案例

Markdown 进阶操作 查看更多学习笔记&#xff1a;GitHub&#xff1a;LoveEmiliaForever Mermaid官网 由于CSDN对某些Mermaid或Markdown语法不支持&#xff0c;因此我的某些效果展示使用图片进行 下面的笔记内容全部是我根据Mermaid官方文档学习的&#xff0c;因为是初学者所以…...

寒假作业2月6号

第五章 静态成员与友元 一、填空题 1、一个类的头文件如下所示&#xff0c;num初始化值为5&#xff0c;程序产生对象T&#xff0c;且修改num为10&#xff0c;并使用show()函数输出num的值10。 #include <iostream.h> class Test { private: static int num; publi…...

ChatGPT绘图指南:DALL.E3玩法大全(一)

一、 DALLE.3 模型介绍 1、什么是 DALLE.3 模型&#xff1f; DALLE-3模型&#xff0c;是一种由OpenAI研发的技术&#xff0c;它是一种先进的生成模型&#xff0c;可以将文字描述转化为清晰的图片。这种模型的名称"DALLE"实际上是"Deep Auto-regressive Latent …...

计算机服务器中了_locked勒索病毒怎么办?Encrypted勒索病毒解密数据恢复

随着网络技术的不断发展&#xff0c;数字化办公已经成为企业生产运营的根本&#xff0c;对于企业来说&#xff0c;数据至关重要&#xff0c;但网络威胁无处不在&#xff0c;近期&#xff0c;云天数据恢复中心接到很多企业的求助&#xff0c;企业的计算机服务器遭到了_locked勒索…...

VueCLI核心知识3:全局事件总线、消息订阅与发布

这两种方式都可以实现任意两个组件之间的通信 1 全局事件总线 1.安装全局事件总线 import Vue from vue import App from ./App.vueVue.config.productionTip false/* 1.第一种写法 */ // const Demo Vue.extend({}) // const d new Demo()// Vue.prototype.x d // 把Dem…...

Redis中缓存问题

缓存预热 Redis缓存预热是一项关键任务&#xff0c;可帮助提升应用程序的性能和响应速度。在高流量的应用程序中&#xff0c;Redis缓存预热可以加速数据查询和读取&#xff0c;从而改善用户体验。本文将介绍一种快速、稳定的Redis缓存预热方案&#xff0c;并提供相应代码实现。…...

数码管扫描显示-单片机通用模板

数码管扫描显示-单片机通用模板 一、数码管扫描的原理二、display.c的实现1、void Display(void) 各模式界面定义数据2、void BackupRamToDisRam(void)从缓存区刷新显示映射Ram3、void FreshDisplay(void) 映射显示Ram到主控的IO口4、void LcdDisplay_8bit(void) 映射显示Ram到…...

IDEA中的神仙插件——Smart Input (自动切换输入法)

IDEA中的神仙插件——Smart Input &#xff08;自动切换输入法&#xff09; 设置 更多功能详见官方文档&#xff1a;Windows版SmartInput使用入门...

shell编程:求稀疏数组中元素的和(下标不连续)

#!/bin/basharr([2]3 [5]2 [6]2 [9]1)for i in "${!arr[]}" dosum$((sumarr[i])) doneecho $sumBash 脚本中&#xff0c;* 和 符号在数组上下文中有不同的用途。当使用它们来遍历数组时&#xff0c;必须了解它们之间的区别。 * (无前置感叹号 !)&#xff1a; 在索引…...

Rust 学习笔记 - 详解数据类型

前言 任何一门编程语言几乎都脱离不了&#xff1a;变量、基本类型、函数、注释、循环、条件判断&#xff0c;这是一门编程语言的语法基础&#xff0c;只有当掌握这些基础语法及概念才能更好的学习 Rust。 标量类型&#xff08;Scalar Types&#xff09; 在 Rust 中&#xff…...

构建本地yum源

下载repo数据文件 根据需要修改下载路径和reposync参数 #!/bin/bashlocal_path/repo/remote/rhel9 enabled_repos$(yum repolist enabled | awk NR>3{print $1}) tempfile$(mktemp -t reposync.XXXX)check() {echo "目标目录剩余空间: $(df -h ${local_path} | awk …...

常用的正则表达式,收藏必备!!!

正则表达式是一种强大的文本模式匹配工具&#xff0c;用于在字符串中查找、替换和验证特定模式的文本。下面是一些常用的正则表达式示例&#xff1a; 匹配Email地址&#xff1a; ^[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,}$匹配URL&#xff1a; ^(https?|ftp)://[^\s/$.?#…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...