【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做了一些优化。

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

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

相关文章

【简写MyBatis】01-简单映射器

前言 新开一个坑&#xff0c;为了学习一下MyBatis的源码&#xff0c;写代码是次要的&#xff0c;主要为了吸收一下其中的思想和手法。 目的 关联对象接口和映射类的问题&#xff0c;把 DAO 接口使用代理类&#xff0c;包装映射操作。 知识点 动态代理简单工厂模式Invocati…

HMI界面:感官与体验俱佳的智能家居界面分享

Hello&#xff0c;我是大千UI工场&#xff0c;本期分享HMI人机交互界面在智能家居领域的案例&#xff0c;关注大千&#xff0c;学习N多UI干货&#xff0c;有设计需求&#xff0c;可以联络。 设计感官和体验俱佳智能家居的UI界面时&#xff0c;可以考虑以下几个方面&#xff1a;…

Slider滑动输入条(antd-design组件库)简单使用

1.Slider滑动输入条 滑动型输入器&#xff0c;展示当前值和可选范围。 2.何时使用 当用户需要在数值区间/自定义区间内进行选择时&#xff0c;可为连续或离散值。 组件代码来自&#xff1a; 滑动输入条 Slider - Ant Design 3.本地验证前的准备 参考文章【react项目antd组件-de…

OpenCV-40 绘制直方图

一、使用matplotlib画直方图 可以利用matplotlib把OpenCV统计得到的直方图绘制出来 示例代码如下&#xff1a; import cv2 import matplotlib.pyplot as pltlena cv2.imread("beautiful women.png") # 变为黑白图片 gray cv2.cvtColor(lena, cv2.COLOR_BGR2GRAY…

XUbuntu22.04之apt与snap如何重装软件(二百一十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

SpringBoot实现OneDrive文件上传

SpringBoot实现OneDrive文件上传 源码 OneDriveUpload: SpringBoot实现OneDrive文件上传 获取accessToken步骤 参考文档&#xff1a;针对 OneDrive API 的 Microsoft 帐户授权 - OneDrive dev center | Microsoft Learn 1.访问Azure创建应用Microsoft Azure&#xff0c;使…

《汇编语言》- 读书笔记 - 实验9 根据材料编程

《汇编语言》- 读书笔记 - 实验9 根据材料编程 需求所需的相关知识属性字节每一位的含义 分析字符位置属性 解答思路代码 效果 需求 在屏幕中间分别显示 绿色、绿底红色、白底蓝色 的字符串 welcome to masm!。 所需的相关知识 80x25 彩色字符模式显示缓冲区(以下简称为显示…

关于DVWA靶场Could not connect to the database service的几种解决办法

总的来说这个问题都是 config 配置文件没有修改正确 一般修改数据库的用户名和密码与 phpstudy 一致并且添加了 key 就能初始化成功的 但是我还遇到过另一种情况&#xff0c;修改了上面的东西依旧无法连接到数据库 Could not connect to the database service. Please check …

如果马斯克投资OpenAI,而不是收购推特会如何?

从去年年底开始&#xff0c;AIGC就开始快速流行&#xff0c;生成式人工智能技术的不断演变&#xff0c;以OpenAI受到微软的追投开始&#xff0c;再以ChatGPT的发布快速在互联网市场蔓延为标志性事件&#xff0c;进而开始引领了一场新兴人工智能的快速发展之路。 去年&#xff…

Javaweb之SpringBootWeb案例之AOP通知类型的详细解析

3.1 通知类型 在入门程序当中&#xff0c;我们已经使用了一种功能最为强大的通知类型&#xff1a;Around环绕通知。 Around("execution(* com.itheima.service.*.*(..))") public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {//记录方法执行开始…

第13讲我创建的投票列表实现

新建我创建的投票页面 {"path": "pages/createVoteList/createVoteList","style": {"navigationBarTitleText": "我创建的投票"}}个人中心页面&#xff0c;加下 点击 “我创建的投票”跳转列表页面 goVoteList:function(){u…

阿里云服务器租用价格2024年新版活动报价和租用收费标准

2024年最新阿里云服务器租用费用优惠价格表&#xff0c;轻量2核2G3M带宽轻量服务器一年61元&#xff0c;折合5元1个月&#xff0c;新老用户同享99元一年服务器&#xff0c;2核4G5M服务器ECS优惠价199元一年&#xff0c;2核4G4M轻量服务器165元一年&#xff0c;2核4G服务器30元3…

《剑指Offer》笔记题解思路技巧优化 Java版本——新版leetcode_Part_3

《剑指Offer》笔记&题解&思路&技巧&优化_Part_3 &#x1f60d;&#x1f60d;&#x1f60d; 相知&#x1f64c;&#x1f64c;&#x1f64c; 相识&#x1f622;&#x1f622;&#x1f622; 开始刷题1. LCR 138. 有效数字——表示数值的字符串2. LCR 139. 训练计划…

MATLAB导出图程序

本文将以代码的形式快速介绍MATLAB导出图到Paper 1 从simulation导出数 2 与simulation同源文件夹下创建导图m文件 代码如下&#xff1a; % 实验后的数据处理用 M-文件 % clear all % 清空工作空间 % close all      % 关闭所有图形窗口 % load adp.mat …

算法中关于数学的题目练习

算法中关于数学的题目练习 1、买不到的数目题目信息思路题解 2、蚂蚁感冒题目信息思路题解 3、饮料换购题目信息思路题解 1、买不到的数目 题目信息 思路 数学结论&#xff08;证明略&#xff09;&#xff1a; p、q为正整数且互质&#xff0c;不能由p、q凑出来的最大的数为(p…

java生态环境评价Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java 生态环境评价管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysq…

RK3399平台开发系列讲解(USB篇)USB控制传输方式介绍

🚀返回专栏总目录 文章目录 一、控制传输详解二、Setup阶段和Data阶段三、Setup 事务格式沉淀、分享、成长,让自己和他人都能有所收获!😄 📢USB控制传输是USB通信中的一种基本传输类型,用于控制USB设备的配置和操作。它由 Setup 阶段和 Data 阶段组成,可用于发送命令…

SIFT 2D/3D检测原理

一、SIFT 2D 二、SIFT 3D SIFT 3D关键点检测以及SAC-IA粗配准-CSDN博客

轻资产上班族副业,steam搬砖项目新手1周出结果

作为一个80后社畜&#xff0c;我打从上大学就喜欢倒腾各种赚钱的事情&#xff0c;不管操作网络上还是现实中的任何项目我都会亲自去摸索其中的赚钱原理&#xff0c;实践才能出真知。steam搬砖项目是我实操了3年多的项目&#xff0c;这个steam搬砖项目长期稳定又老牌阳光&#x…

深入理解lambda表达式

深入理解ASP.NET Core中的中间件和Lambda表达式 var builder WebApplication.CreateBuilder(args); var app builder.Build(); app.Use(async (context, next) > { // Add code before request. await next(context);// Add code after request.}); 这段C#代码是用于设…