string类模拟实现
了解过string常用接口后,接下来的任务就是模拟实现string类。

目录
VS下的string结构
默认成员函数和简单接口
string结构
c_str()、size()、capacity()、clear()、swap()
构造函数
拷贝构造函数
赋值重载
析构函数
访问及遍历
容量操作
reserve
resize
修改操作
push_back
append
operator+=
insert
earse
输入/输出运算符重载
VS下的string结构
■首先测试下string结构的大小(注:下述测试是在32位平台上进行的,指针占4个字节)
int main()
{string s;cout << sizeof(s) << endl;return 0;
}

通过调试窗口理解string的结构:
int main()
{string s("Hello");return 0;
}

通过测试知道string总共占28个字节,调试发现结构中有一个联合体,用来定义string中字符串的存储空间: 1.当字符串长度小于16时,使用内部固定的字符数组来存放 。2.当字符串长度大于等于16时,从堆上开辟空间。
union _Bxty
{ char _Buf[16];char* _Ptr;char _Alias[16];
} _Bx;

除了联合体,还有一个size_t字段保存字符串长度,还有一个size_t字段记录容量!此外,还有一个做其它事情的指针。总大小:16+4+4+4 = 28 !!
默认成员函数和简单接口
下述接口的模拟实现仅仅是一种写法,可能不是最优的,也可能存在一些BUG,仅供参考!
string结构
c_str()、size()、capacity()、clear()、swap()
为了测试方便,先将上述几个常用且简单的接口进行实现:
//返回指向_str的指针const char* c_str(){return _str;}//返回_sizesize_t size() const{return _size;}//返回_capacitysize_t capacity() const{return _capacity;}//清空有效字符void clear(){_size = 0;_str[0] = '\0';}复习时间:const修饰返回值的作用是避免返回值被修改,const写在成员函数后面修饰的是隐藏的this指针,不能对类的成员进行修改!
swap:string类中提供一个swap接口用来交换两个string对象。
void swap(string& s){std::swap(_str, s._str);std::swap(_size,s._size);std::swap(_capacity, s._capacity);}构造函数
分析:对于string s1(“Hello”)/ string s2 都能处理;
逻辑思路:通过strlen计算str的字符个数,确定size和capacity的值,空间一般多开一个,因为要算上\0。在将str 拷贝到开辟好的空间中。
//构造 string(const char* str = "") {_size = strlen(str);_capacity = _size;_str = new char[_capacity+1];strcpy(_str,str);cout << "调用构造函数!" << endl; }测试代码:
//构造函数测试void TestString1(){string s1("zhang");string s2;cout << s1.c_str() << endl;}
拷贝构造函数
分析:string s1(s2);
按照拷贝对象的容量申请空间,对拷贝对象进行拷贝。
传统写法:
//拷贝构造string(const string& s){_str = new char[s._capacity];_size = s._size;_capacity = s._capacity;strcpy(_str,s._str);cout << "调用拷贝构造!" << endl;}测试代码:
//拷贝构造测试void TestString2(){string s2("Hi zxy!");cout << s2.c_str() << endl;string s3(s2);cout << s3.c_str() << endl;}
现代写法:这里注意在初始化列表对s进行初始化,否则当tmp对象析构的时候,会出现析构野指针的问题。
//现代写法string(const string& s):_str(nullptr),_size(0),_capacity(0){string tmp(s._str);//this->swap(tmp);swap(tmp);cout << "拷贝构造" << endl;}赋值重载
传统写法:
注意:不要见到&就是引用,在下述判断代码中,&s取出的s的地址,和this指针进行比较,如果地址不相同则进行赋值工作!
赋值的过程需要将右操作数赋值给做左操作数,这时从新根据右操作数的容量申请一块空间,将左操作数空间释放。_str指向刚刚开好的空间,将右操作数中的字符串拷贝到_str中。
//赋值重载string& operator=(const string& s){//两操作数地址不相等if (this != &s){//申请一块能容纳右操作数的空间char* tmp = new char[s._capacity];delete[] _str;_str = tmp;strcpy(_str,s._str);_size = s._size;_capacity = s._capacity;}return *this;}测试代码:
//赋值重载测试void TestString3(){string str1("Hello World!");string str2("Hi!");cout <<"赋值前str1:"<< str1.c_str() << " str2:" << str2.c_str() << endl;str1 = str2;cout <<"赋值后str1:"<< str1.c_str() << " " << str2.c_str() << endl;}测试结果:
现代写法:
第二种相比较第一种写法省去了一次拷贝构造,传值调用形参是实参的临时拷贝,所以第二种写法更加的简洁,第一种写法更容易理解。
//赋值重载现代写法1string& operator=(string& s){if (this != &s){//string tmp(s._str);string tmp(s);swap(tmp);}return *this;}//赋值重载现代写法2string& operator=(string s){swap(s);return *this;}析构函数
//析构~string(){_capacity = _size = 0;delete[] _str;_str = nullptr;cout << "调用析构函数!" << endl;}测试代码:
//析构函数测试void TestString4(){string str("zxy");cout << str.c_str() << endl;}
访问及遍历
operator[ ]
char& operator[](size_t pos){assert(pos < _size);return _str[pos];}char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}begin/end
typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}迭代器:访问string对象、vector等对象元素的一种通用机制。迭代器类似于指针类型(不一定是指针),使用迭代器可以访问某个元素。范围for的支持就和迭代器有关:
void TestString2(){string s2("My name is zxy!");string::iterator it1 = s2.begin();while (it1 != s2.end()){cout << *it1;it1++;}cout << endl;string::iterator it2 = s2.begin();while (it2 != s2.end()){(*it2)++;cout << *it2;it2++;}}
证明范围for是依赖于迭代器实现的:
1.屏蔽掉string中的begin或者end.
2.放开屏蔽,更改begin的命名为Begin。一样的报错
3.提供小写的begin和end
综上所述:便捷神秘的范围for,运用迭代器这种通用的机制来实现元素的遍历和访问。
容量操作
reserve
扩容:申请一块n大小的空间,将_str指向的字符串拷贝到tmp指向的空间中,清理_str指向的字符串并将_str指向新空间,更新容量的大小。
//扩容void reserve(size_t n){//新开一块空间char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
void TestString6(){string ss("Hello World!");cout << "size:" << ss.size() << endl;cout << "capacity:" << ss.capacity() << endl;cout << "str:" << ss.c_str() << endl;cout << "--------------reserve--------------" << endl;ss.reserve(100);cout << "size:" << ss.size() << endl;cout << "capacity:" << ss.capacity() << endl;cout << "str:" << ss.c_str() << endl;}
resize
改变字符串中有效字符的个数,当n>_size时,其余位置用ch占位。当n<_size时只有前n个字符是有效字符。
//resize,更改有效元素个数的大小void resize(size_t n,char ch = '!'){if (n > _size){reserve(n);for (size_t i = _size;i < n; i++){_str[i] = ch;}_size = n;_str[n] = '\0';}else{_size = n;_str[n] = '\0';}}上述代码中,当n小于_size时,在n位置填入\0,有效字符的个数正好是n,因为字符数组的下标是从0开始的。
void TestString7(){string ss("Hello");cout << "str:" << ss.c_str() << endl;cout << "size:" << ss.size() << endl;//字符串有效字符个数增多ss.resize(10,'x');cout << "str:" << ss.c_str() << endl; cout << "size:" << ss.size() << endl;//字符串有效字符个数变少ss.resize(3);cout << "str:" << ss.c_str() << endl;cout << "size:" << ss.size() << endl;}
修改操作
push_back
尾插操作,也就是在字符串末尾追加一个字符:
//追加字符void push_back(char ch){if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;//扩容reserve(newcapacity);}_str[_size++] = ch;_str[_size] = '\0';}append
在_str末尾追加一个字符串:
//追加字符串void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size+len+1);}strcpy(_str+_size,str);_size += len;}operator+=
重载+=运算符,使+=既可以追加字符右可以追加字符串,实现思路和上述基本一样,这里直接调用上面的接口。
//运算符重载string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}测试push_back/append/+=性能
void TestString3(){string ss("Hi");cout << "str:" <<ss.c_str() << endl;ss.append("zxy"); cout << "append(zxy):" <<ss.c_str() << endl;ss.push_back('!');cout << "push_back(!):" <<ss.c_str()<< endl;ss += '!';cout << "+=字符(!):" <<ss.c_str() << endl;ss += "bd";cout << "+=字符串(bd):" <<ss.c_str() << endl;}
insert
insert这里重载了两个接口,分别是用来插入字符和插入字符串:
●在pos位置插入字符
![]()
//插入字符string& insert(size_t pos,char ch){//检查pos是否越界assert(pos <= _size);if (_capacity == _size){//扩容size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newcapacity);}//挪动数据size_t end = _size + 1;while (end > pos){_str[end] = _str[end-1];end--;}_str[pos] = ch;_size++;return *this;}void TestString9(){string ss("Helloworld!");ss.insert(6, ' ');cout << ss.c_str() << endl;}
●在pos位置插入字符串:
//插入字符串 string& insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _size){//扩容size_t newcapacity = _capacity == 0 ? 4 : _size+len;reserve(newcapacity);}//挪动数据size_t end = _size + len;while (end > pos+len-1){_str[end] = _str[end - len];end--;}strncpy(_str+pos,str,len);_size += len;return *this;}void TestString10(){string ss("Hi Lisi!");ss.insert(3, "zxy I am ");cout << ss.c_str() << endl;}
earse
删除pos后的len个字符,如果len == npos,将pos后的数据全部删除!
//删除字符string& earse(size_t pos,size_t len = npos){assert(pos <= _size);//如果pos后面的数据不够删或者=npos,pos后数据全部删除if (len == npos || len + pos >= _size-pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}
输入/输出运算符重载
小提问:输入和输出的重载为了和我们的使用习惯保持一致一般会定义在全局,那么一定要在对应自定义类型中声明友元吗?
答:不一定,声明友元的原因是要访问类中的私有成员,如果在重载的过程中不涉及私有成员的访问,就不用写友元声明!
//流插入ostream& operator<<(ostream& out,string& s){for (size_t i = 0; i < s.size(); i++){out << s[i];}return out;}流提取的实现使用了一个字符数组,in.get()用于读取字符,当数组满时追加到_str中,没满时将数据存放到buff数组中,最后输入结束时如果buff中还有数据的话将剩余数据追加到_str中,不过要注意在buff的数据末尾添加一个\0。
//流提取istream& operator>>(istream& in, string& s){s.clear();char ch = in.get();size_t i = 0;char buff[128] = {'\0'};while (ch != ' ' && ch != '\n'){if (i == 127){s += buff;i = 0;}buff[i++] = ch;ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}
相关文章:
string类模拟实现
了解过string常用接口后,接下来的任务就是模拟实现string类。 目录 VS下的string结构 默认成员函数和简单接口 string结构 c_str()、size()、capacity()、clear()、swap() 构造函数 拷贝构造函数 赋值重载 析构函数 访问及遍历 容量操作 reserve resize …...
cadence SPB17.4 S032 - allegro - 保存/载入光绘层定义
文章目录cadence SPB17.4 S032 - allegro - 保存/载入光绘层定义概述保存光绘层在新板子中载入已经保存的相同类型老板子定义好的光绘层定义文件碎碎念ENDcadence SPB17.4 S032 - allegro - 保存/载入光绘层定义 概述 以前布线完成, 准备出板厂文件时, 总是要手工重新建立光绘…...
微服务实战--高级篇:分布式缓存 Redis
分布式缓存 – 基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题: 1.Redis持久化 Redis有两种持久化方案: RDB持久化AOF持久化 1.1.RDB持久化 RDB全称Redis Database Backup file(Redis数据备份文件)…...
【C语言】可变参数列表
本篇博客让我们来认识一下C语言学习过程中往往被忽略的可变参数列表 所谓可变参数,就是一个不限定参数数量的函数,我们可以往里面传入任意个数的参数,以达成某些目的。 关联:C11可变模板参数;本文首发于 慕雪的寒舍 …...
目标检测的旋框框文献学习
这是最近打算看完的文献,一天一篇 接下来将记录一下文献阅读笔记,避免过两天就忘了 RRPN 论文题目:Arbitrary-Oriented Scene Text Detection via Rotation Proposals 论文题目:通过旋转方案进行任意方向的场景文本检测&#x…...
Hive 在工作中的调优总结
总结了一下在以往工作中,对于Hive SQL调优的一些实际应用,是日常积累的一些优化技巧,如有出入,欢迎在评论区留言探讨~ EXPLAIN 查看执行计划 建表优化 分区 分区表基本操作,partitioned二级分区动态分区 分桶 分…...
每天一道大厂SQL题【Day09】充值日志SQL实战
每天一道大厂SQL题【Day09】充值日志SQL实战 大家好,我是Maynor。相信大家和我一样,都有一个大厂梦,作为一名资深大数据选手,深知SQL重要性,接下来我准备用100天时间,基于大数据岗面试中的经典SQL题&#…...
MATLAB 遗传算法
✅作者简介:人工智能专业本科在读,喜欢计算机与编程,写博客记录自己的学习历程。 🍎个人主页:小嗷犬的个人主页 🍊个人网站:小嗷犬的技术小站 🥭个人信条:为天地立心&…...
探讨 Java 中 valueOf 和 parseInt 的区别
前言 在编程中,遇到类型转换,好像会经常用到 parseInt 和 valueOf,当然这里只拿 Integer 类型进行陈述,其他类型也是雷同的; 想必有读者也跟我一样,经常交叉使用这两个方法,但却不知道这两者到…...
JSON学习笔记
♥课程链接:【狂神说Java】一小时掌握JSON_哔哩哔哩_bilibili配套的当然还要学习ajax不管是前端后端,感觉这部分内容是必须的,不然真的做项目的时候云里雾里。总体json的内容不多,具体就:1. 列表、对象等语法格式2. js…...
家政服务小程序实战教程07-轮播图组件
小程序中首页一般显示轮播图的功能,点击轮播图会跳转到具体的一篇文章或者是产品,本篇我们就介绍一下轮播图功能的开发 01 设计数据源 我们轮播图组件需要两个字段,一个是展示的图片,一个是跳转页面传入的参数。打开数据源&…...
MySQL之索引创建、删除、唯一索引、普通索引、及命名规则、注意事项
一、MySQL 索引 定义 索引是一个数据结构,用于加速数据库表中数据的查询。索引存储了一些数据表中的列值,以及这些列值在数据表中的位置,这样就可以通过索引来快速查找到数据表中的某一行数据。 MySQL 支持多种索引类型,包括普通…...
【C++设计模式】学习笔记(3):策略模式 Strategy
目录 简介动机(Motivation)模式定义结构(Structure)要点总结笔记结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~ ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计算机专业,获得过国家奖学金…...
Java——聊聊JUC中的ThreadLocal
文章目录: 1.什么是ThreadLocal? 1.1 api介绍 1.2 最简单的案例认识ThreadLocal 1.3 线程池结合ThreadLocal案例 2.Thread &ThreadLocal & ThreadLocalMap 3.ThreadLocal内存泄漏问题 3.1 四大引用之强引用 3.2 四大引用之软引用 3.3 四…...
软件工程(4)--螺旋模型
前言 这是基于我所学习的软件工程课程总结的第四篇文章。 在软件开发过程中必须及时识别和分析风险,并且采取适当措施以消除或减少风险的危害。构建原型是一种能使某些类型的风险降至最低的方法。为了降低交付给用户的产品不能满足用户需要的风险,一种行…...
图解LeetCode——剑指 Offer 50. 第一个只出现一次的字符
一、题目 在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。 二、示例 2.1> 示例 1: 【输入】s "abaccdeff" 【输出】b 2.2> 示例 2: 【输入】s "" 【输出】 限制: 0 < s 的…...
《HTML 5与CSS 3核心技法》读书笔记
目录前言第1章 写在前面第2章 HTML 语法基础第3章 布局类元素 ,房子的楼板、柱子和大梁第4章 功能类元素,房子的门、窗、水管和电气第5章 CSS基础第6章 选择器,确定样式的作用范围选择器类型选择器的组合使用第7章 权重,样式发送冲…...
【沐风老师】3DMAX几何投影插件Geometry Projection使用详解
【几何投影插件】 描述 3DMAX几何投影插件Geometry Projection,将一个或多个对象或它的顶点选择沿全局或局部 x、y 或 z 轴投影到另一个对象上。 适用版本 3dMax2013或更高版本 安装设置 插件的安装非常简单,解压后把插件脚本 “geometry_projectio…...
面试问题整理
20200422面试题 1、有nginx为什么还要用gateway 2、factorybean和beanfactory有什么区别 https://www.cnblogs.com/leeego-123/p/12159574.html 2、aop原理 3、ioc原理 4、注解requestbody和responsebody区别。pathvireable和requestparam注解区别,feign客户端的注解…...
“区块链60人”2022赋能中国区块链创新人物名单公布
2022年11月5日,“2022第五届全国高校人工智能大数据区块链教育教学创新论坛”在京隆重召开。此次论坛公布了“区块链60人”2022赋能中国区块链创新人物评选活动获评名单。 本次评选活动通过媒体报道、第三方推荐、专家评选等环节,坚持“公开、公平、公正…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...


















