【C++ STL】深入理解string类的底层实现

string类的模拟实现

  • 一.string的构造与析构函数
    • 1.普通构造函数与析构函数
    • 2.拷贝构造的浅拷贝所带来的问题
    • 3.如何实现深拷贝
  • 二.运算符重载
    • 1.赋值运算符重载
    • 2.大小比较相关的运算符重载
  • 三.迭代器的实现
  • 四.string常用操作的实现
    • 1.静态const成员npos的定义
    • 2.插入操作
    • 3.查找操作
    • 4.删除操作

一.string的构造与析构函数

1.普通构造函数与析构函数

我们首先实现一下string类的基本框架,包括成员变量,构造以及析构函数

string底层其实是一个字符数组,与普通意义上的数组不同的是,string支持自动扩容,并且可以通过断言(assert)的方式来更加严格的检查越界问题,使用起来要更加的安全有效

class string
{
public://默认构造函数string(const char* str=""):_size(strlen(str)){_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);}//析构函数~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}
private:char* _str;size_t _size;size_t capacity;
};

分析代码:

  1. 构造函数:申请空间,并初始化string
  2. 析构函数:释放空间,避免出现内存泄露

2.拷贝构造的浅拷贝所带来的问题

我们接下来再考虑拷贝构造如何实现?
首先,通过类和对象部分的学习.我们知道:我们不实现,编译器为我们实现的默认的拷贝构造函数完成的是值拷贝也就是浅拷贝,我们的string类如果浅拷贝的话,程序就会直接崩溃.

浅拷贝的危害:
1. 对同一块空间会析构两次,第二次1析构时程序会崩溃,因为此时的空间已经被释放,是未被申请利用的.
2.如果我们对其中一个string进行修改,那么另一个string对象也会受到影响.

3.如何实现深拷贝

经过上述分析,为了避免浅拷贝带来的问题,我们需要在拷贝构造函数中实现深拷贝。深拷贝确保每个对象都有自己独立的内存空间,不会与其他对象共享内存。

代码示例:

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

上述是传统写法(我们自己手动释放旧空间,申请新空间,并完成内容拷贝),下面我们介绍一下现代写法:

	void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}string(const string& s){string tmp(s._str);swap(tmp);}

现代写法就是借助构造函数来完成空间的申请和内容的拷贝,再利用swap函数来交换,这样当函数调用完成即函数栈帧销毁时,由于tmp是临时对象,会调用析构函数完成资源的清理.

二.运算符重载

1.赋值运算符重载

在C++中,当我们将一个对象赋值给另一个对象时,同拷贝构造,默认情况下,编译器会为我们生成一个浅拷贝的赋值运算符。这意味着赋值后的对象和原对象会共享同一个内存空间,这会导致和浅拷贝相同的潜在问题,特别是在一个对象被销毁时,另一个对象继续使用该内存区域会引发错误。

因此这里依然会出现浅拷贝的问题,我们都思路和拷贝构造一样,完成深拷贝.

代码示例:

string& operator=(const string& s)
{char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete _str;_str = tmp;_size = s._size;_capacity = s._capacity;return *this;
}

整体思路和拷贝构造相似,都是先创建新空间,拷贝数据,释放旧空间.
下面我们看看令人惊叹的现代写法,只需要一行代码即可搞定:

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

代码分析:由于此时参数不再是引用,所以s就是一个临时对象,直接交换,出了作用域s销毁自动调用析构函数.

2.大小比较相关的运算符重载

string的大小比较是按照ASCII码来进行,依次取两个string的第一个字符,第二个字符以此类推,直至出现大小不同.
例如:
aaaabc > aaaaaaaa

*为了方便,我们主要实现<和==,其他的比较都可以复用这两个函数
代码示例:

	bool operator<(const string& s) const{return strcmp(_str, s._str) < 0;}bool operator>(const string& s) const{return !(*this <= s);}bool operator<=(const string& s) const{return *this < s || *this == s;}bool operator>=(const string& s) const{return !(*this < s);}bool operator==(const string& s) const{return strcmp(_str, s._str) == 0;}bool operator!=(const string& s) const{return !(*this == s);}

三.迭代器的实现

迭代器是我们访问容器的一种通用方式,它可以让我们在不了解具体某个数据结构的底层的情况下,依旧完成对容器的遍历,通常情况下,我们可以将迭代器理解为像"指针"一样的东西.

这里由于string的底层是连续的物理空间,我们直接使用char* 作为string的迭代器即可.当然,在Linux和VS环境下官方库中的string的迭代器并不是简答的指针,而是将指针封装之后的一个类

这里我们可以打印迭代器的类型看一下:

int main()
{cout << typeid(string::iterator).name() << endl;return 0;
}

在VS下,这段程序的运行结果是:

class std::_String_iterator<class std::_String_val<struct std::_Simple_types<char>> >

可以看出,是经过复杂封装之后的模板类.

我们自己实现的迭代器代码示例:

class string
{public:typedef char* iterator;typedef const char* const_iterator;iterator string::begin(){return _str;}iterator string::end(){return _str + _size;}const_iterator string::begin() const{return _str;}const_iterator string::end() const{return _str + _size;}
};

在定义了begin和end函数之后,我们自己的string也就支持了C++11中的范围for遍历.

四.string常用操作的实现

1.静态const成员npos的定义

通过查看文档,我们会发现在插入,删除,查找等函数中,都用了一个缺省值npos,它的类型是const static size_t,值为-1,也就是说它表示无符号整数的最大值,大约是42亿多.

对于静态成员变量的声明与定义,之前的类和对象篇有过讲解,如有需要可以前往:类和对象(下).

代码示例:

class string {
public:static const size_t npos = -1;  // 可以在类内初始化
};

2.插入操作

	void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}
void insert(size_t pos, char ch)
{assert(pos < _size);if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;
}

这里注意一个点即可: size_t(无符号整数) 与 0 相比较作为循环条件时,容易出现死循环
例如:

size_t end = _size ;while (end >= pos){_str[end + 1] = _str[end];end--;}
//当pos等于0时,会出现死循环!!!

3.查找操作

代码示例:

	//查找一个子串size_t find(const char* str, size_t pos) const{char* sub = strstr(_str + pos, str);return sub - _str;}//查找一个字符size_t find(char ch, size_t pos) const{for (int i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}

4.删除操作

代码示例:

	//从pos位置开始,删除len个字符void erase(size_t pos, size_t len){assert(pos < _size);if (len > _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}

希望能让你对string的理解更加透彻,感谢观看!!!

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

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

相关文章

江协科技STM32学习- P19 TIM编码器接口

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

C# ReoGrid使用记录

ReoGrid是个类似于Excel的表格控件&#xff0c;作者在WinForm上使用&#xff0c;下面是使用记录。 一、获取当前工作表 Worksheet worksheet dgv.CurrentWorksheet; 二、设置行/列总数&#xff08;用于增删行&#xff09; worksheet.Columns columnCount; worksheet.Rows…

Redis篇(Java操作Redis)

目录 讲解一&#xff1a;简介 讲解二&#xff1a;Jedis Github 一、创建项目、 二、添加依赖 三、配置文件 四、Java连接Redis 五、通过Redis连接池获取连接对象并操作服务器 六、封装JedisUtil对外提供连接对象获取方法 七、Java操作Redis五种数据类型 1. 连接与释放…

助农小程序|助农扶贫系统|基于java的助农扶贫系统小程序设计与实现(源码+数据库+文档)

助农扶贫系统小程序 目录 基于java的助农扶贫系统小程序设计与实现 一、前言 二、系统功能设计 三、系统实现 5.1.1 农户管理 5.1.2 用户管理 5.1.3 订单统计 5.2.1 商品信息管理 5.3.1 商品信息 5.3.2 订单信息 5.3.3 商品评价 5.3.4 商品退货 四、数据库设计 1、…

深入理解华为仓颉语言的数值类型

解锁Python编程的无限可能&#xff1a;《奇妙的Python》带你漫游代码世界 在编程过程中&#xff0c;数据处理是开发者必须掌握的基本技能之一。无论是开发应用程序还是进行算法设计&#xff0c;了解不同数据类型的特性和用途都至关重要。本文将深入探讨华为仓颉语言中的基本数…

在Linux实时监控某个应用是否运行,未运行,执行运行命令

1、shell脚本(每隔30秒检测一次) 脚本要注意的地方是&#xff1a;在Nodepad编辑的时候要使用Unix&#xff08;LF&#xff09;格式&#xff0c;避免在Linux无法执行命令 #!/bin/bash# RabbitMQ进程名称&#xff08;可能需要根据你的安装进行调整&#xff09; RABBITMQ_PROCE…

代码随想录算法训练营第34天|46. 携带研究材料、416. 分割等和子集

文章目录 46. 携带研究材料416. 分割等和子集 46. 携带研究材料 卡码网 46. 携带研究材料 代码随想录 dp[i][j]表示&#xff0c;考虑到第i个物品的情况下&#xff0c;背包容量为j的最大价值。 m, n map(int, input().split(" ")) costs list(map(int, input().spl…

水波荡漾效果+渲染顺序+简单UI绘制

创建场景及布置 创建新场景Main,在Main场景中创建一个plane物体&#xff0c;命名为WaterWavePla,具体数值及层级面板排布如下&#xff1a; 编写脚本 创建一个文件夹&#xff0c;用于存放脚本&#xff0c;命名Scripts,创建一个子文件夹Effect,存放特效相关脚本&#xff0c;创建…

WAF,全称Web Application Firewall,好用WAF推荐

WAF&#xff0c;全称Web Application Firewall&#xff0c;即Web应用防火墙&#xff0c;是一种网络安全设备&#xff0c;旨在保护Web应用程序免受各种Web攻击&#xff0c;如SQL注入、跨站脚本&#xff08;XSS&#xff09;、跨站请求伪造&#xff08;CSRF&#xff09;等。 WAF通…

前端面试经验总结2(经典问题篇)

谈谈你对前端的理解 前端主要负责产品页面部分的实现&#xff0c;是最贴近于用户的程序员。 基本工作要求&#xff1a; 1.参与项目&#xff0c;通过与团队成员&#xff0c;UI设计&#xff0c;产品经理的沟通&#xff0c;快速高质量的实现效果图&#xff0c;并能够精确到1px 2.做…

Conda 虚拟环境使用指南,python,anaconda,miniconda

文章目录 前言1. 安装 Conda2. 创建虚拟环境创建默认虚拟环境创建到指定路径的虚拟环境 3. 激活虚拟环境激活默认环境激活指定路径的环境 4. 安装包5. 查看已安装的包6. 退出虚拟环境7. 删除虚拟环境删除默认环境删除指定路径的环境 结语 前言 Conda 是一个开源的包管理和环境…

数学建模研赛总结

目录 前言进度问题四分析问题五分析数模论文经验分享总结 前言 本文为博主数学建模比赛第五天的内容记录&#xff0c;希望所写的一些内容能够对大家有所帮助&#xff0c;不足之处欢迎大家批评指正&#x1f91d;&#x1f91d;&#x1f91d; 进度 今天已经是最后一天了&#xf…

Ruby 多线程

Ruby 多线程 概述 在当今的软件开发领域,多线程已经成为提高应用程序性能和响应速度的关键技术之一。Ruby,作为一种现代编程语言,提供了丰富的多线程支持,允许开发者轻松地创建和管理线程,以实现高效的任务并发处理。本文将深入探讨Ruby中的多线程概念、用法及其在实践中…

COMP 6714-Info Retrieval and Web Search笔记week2

tokenizer&#xff1a;分词器 右半部分&#xff1a;倒排索引 Westlaw AND&#xff08;&&#xff09;&#xff1a; 要搜索必须同时出现在文档中的两个或多个词语&#xff0c;请使用 AND&#xff08;&&#xff09;。例如&#xff0c;输入 narcotics & warrant&#x…

Android 添加禁止下拉菜单和实现控制下拉通知栏功能

overlay/vendor/mediatek/proprietary/packages/apps/MtkSettings/res/values-zh-rCN/strings.xml <string name"pull_down_switch">禁用通知下拉菜单</string> <string name"pull_down_switch2">打开&#xff1a;禁止下拉菜单 \n关…

基于单片机的催眠电路控制系统

** 文章目录 前言一 概要功能设计设计思路 软件设计效果图 程序文章目录 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主…

【2024工业3D异常检测文献】CMDIAD: 基于跨模态蒸馏驱动的多模态工业异常检测

Incomplete Multimodal Industrial Anomaly Detection via Cross-Modal Distillation 1、Background 近年来&#xff0c;基于3D点云和RGB图像的多模态工业异常检测(IAD)研究强调了利用模态间的冗余性和互补性对于精确分类和分割的重要性。 在项目中&#xff0c;提出了CMDIAD方…

如何在算家云搭建MVSEP-MDX23(音频分离)

一、MVSEP-MDX23简介 模型GitHub网址&#xff1a;MVSEP-MDX23-music-separation-model/README.md 在 main ZFTurbo/MVSEP-MDX23-音乐分离模型 GitHub 上 在音视频领域&#xff0c;把已经发布的混音歌曲或者音频文件逆向分离一直是世界性的课题。音波混合的物理特性导致在没有…

js列表数据时间排序和取唯一值

1.取唯一值[...new Set(array)] const array [1, 2, 3, 2, 4, 5, 3, 5]; // 使用Set去除重复元素 const uniarray [...new Set(array)]; console.log(uniarray); // 输出: [1, 2, 3, 4, 5] 2.排序 var u [1,3,2,5,4]; var uu u.sort(); console.log(uu); var u [1,3…

【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第四篇-着色器投影-接收阴影部分】

上一章中实现了体积渲染的光照与自阴影&#xff0c;那我们这篇来实现投影 回顾 勘误 在开始本篇内容之前&#xff0c;我已经对上一章中的内容的错误进行了修改。为了确保不会错过这些更正&#xff0c;同时也避免大家重新阅读一遍&#xff0c;我将在这里为大家演示一下修改的…