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

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常用接口后&#xff0c;接下来的任务就是模拟实现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存在四大问题&#xff1a; 1.Redis持久化 Redis有两种持久化方案&#xff1a; RDB持久化AOF持久化 1.1.RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;&#xf…...

【C语言】可变参数列表

本篇博客让我们来认识一下C语言学习过程中往往被忽略的可变参数列表 所谓可变参数&#xff0c;就是一个不限定参数数量的函数&#xff0c;我们可以往里面传入任意个数的参数&#xff0c;以达成某些目的。 关联&#xff1a;C11可变模板参数&#xff1b;本文首发于 慕雪的寒舍 …...

目标检测的旋框框文献学习

这是最近打算看完的文献&#xff0c;一天一篇 接下来将记录一下文献阅读笔记&#xff0c;避免过两天就忘了 RRPN 论文题目&#xff1a;Arbitrary-Oriented Scene Text Detection via Rotation Proposals 论文题目&#xff1a;通过旋转方案进行任意方向的场景文本检测&#x…...

Hive 在工作中的调优总结

总结了一下在以往工作中&#xff0c;对于Hive SQL调优的一些实际应用&#xff0c;是日常积累的一些优化技巧&#xff0c;如有出入&#xff0c;欢迎在评论区留言探讨~ EXPLAIN 查看执行计划 建表优化 分区 分区表基本操作&#xff0c;partitioned二级分区动态分区 分桶 分…...

每天一道大厂SQL题【Day09】充值日志SQL实战

每天一道大厂SQL题【Day09】充值日志SQL实战 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&#…...

MATLAB 遗传算法

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…...

探讨 Java 中 valueOf 和 parseInt 的区别

前言 在编程中&#xff0c;遇到类型转换&#xff0c;好像会经常用到 parseInt 和 valueOf&#xff0c;当然这里只拿 Integer 类型进行陈述&#xff0c;其他类型也是雷同的&#xff1b; 想必有读者也跟我一样&#xff0c;经常交叉使用这两个方法&#xff0c;但却不知道这两者到…...

JSON学习笔记

♥课程链接&#xff1a;【狂神说Java】一小时掌握JSON_哔哩哔哩_bilibili配套的当然还要学习ajax不管是前端后端&#xff0c;感觉这部分内容是必须的&#xff0c;不然真的做项目的时候云里雾里。总体json的内容不多&#xff0c;具体就&#xff1a;1. 列表、对象等语法格式2. js…...

家政服务小程序实战教程07-轮播图组件

小程序中首页一般显示轮播图的功能&#xff0c;点击轮播图会跳转到具体的一篇文章或者是产品&#xff0c;本篇我们就介绍一下轮播图功能的开发 01 设计数据源 我们轮播图组件需要两个字段&#xff0c;一个是展示的图片&#xff0c;一个是跳转页面传入的参数。打开数据源&…...

MySQL之索引创建、删除、唯一索引、普通索引、及命名规则、注意事项

一、MySQL 索引 定义 索引是一个数据结构&#xff0c;用于加速数据库表中数据的查询。索引存储了一些数据表中的列值&#xff0c;以及这些列值在数据表中的位置&#xff0c;这样就可以通过索引来快速查找到数据表中的某一行数据。 MySQL 支持多种索引类型&#xff0c;包括普通…...

【C++设计模式】学习笔记(3):策略模式 Strategy

目录 简介动机(Motivation)模式定义结构(Structure)要点总结笔记结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~ ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计算机专业,获得过国家奖学金…...

Java——聊聊JUC中的ThreadLocal

文章目录&#xff1a; 1.什么是ThreadLocal&#xff1f; 1.1 api介绍 1.2 最简单的案例认识ThreadLocal 1.3 线程池结合ThreadLocal案例 2.Thread &ThreadLocal & ThreadLocalMap 3.ThreadLocal内存泄漏问题 3.1 四大引用之强引用 3.2 四大引用之软引用 3.3 四…...

软件工程(4)--螺旋模型

前言 这是基于我所学习的软件工程课程总结的第四篇文章。 在软件开发过程中必须及时识别和分析风险&#xff0c;并且采取适当措施以消除或减少风险的危害。构建原型是一种能使某些类型的风险降至最低的方法。为了降低交付给用户的产品不能满足用户需要的风险&#xff0c;一种行…...

图解LeetCode——剑指 Offer 50. 第一个只出现一次的字符

一、题目 在字符串 s 中找出第一个只出现一次的字符。如果没有&#xff0c;返回一个单空格。 s 只包含小写字母。 二、示例 2.1> 示例 1: 【输入】s "abaccdeff" 【输出】b 2.2> 示例 2: 【输入】s "" 【输出】 限制&#xff1a; 0 < s 的…...

《HTML 5与CSS 3核心技法》读书笔记

目录前言第1章 写在前面第2章 HTML 语法基础第3章 布局类元素 &#xff0c;房子的楼板、柱子和大梁第4章 功能类元素&#xff0c;房子的门、窗、水管和电气第5章 CSS基础第6章 选择器&#xff0c;确定样式的作用范围选择器类型选择器的组合使用第7章 权重&#xff0c;样式发送冲…...

【沐风老师】3DMAX几何投影插件Geometry Projection使用详解

【几何投影插件】 描述 3DMAX几何投影插件Geometry Projection&#xff0c;将一个或多个对象或它的顶点选择沿全局或局部 x、y 或 z 轴投影到另一个对象上。 适用版本 3dMax2013或更高版本 安装设置 插件的安装非常简单&#xff0c;解压后把插件脚本 “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注解区别&#xff0c;feign客户端的注解…...

“区块链60人”2022赋能中国区块链创新人物名单公布

2022年11月5日&#xff0c;“2022第五届全国高校人工智能大数据区块链教育教学创新论坛”在京隆重召开。此次论坛公布了“区块链60人”2022赋能中国区块链创新人物评选活动获评名单。 本次评选活动通过媒体报道、第三方推荐、专家评选等环节&#xff0c;坚持“公开、公平、公正…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

Go 并发编程基础:通道(Channel)的使用

在 Go 中&#xff0c;Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式&#xff0c;用于在多个 Goroutine 之间传递数据&#xff0c;从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案

引言 在分布式系统的事务处理中&#xff0c;如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议&#xff08;2PC&#xff09;通过准备阶段与提交阶段的协调机制&#xff0c;以同步决策模式确保事务原子性。其改进版本三阶段提交协议&#xff08;3PC&#xf…...

聚六亚甲基单胍盐酸盐市场深度解析:现状、挑战与机遇

根据 QYResearch 发布的市场报告显示&#xff0c;全球市场规模预计在 2031 年达到 9848 万美元&#xff0c;2025 - 2031 年期间年复合增长率&#xff08;CAGR&#xff09;为 3.7%。在竞争格局上&#xff0c;市场集中度较高&#xff0c;2024 年全球前十强厂商占据约 74.0% 的市场…...

2025年- H71-Lc179--39.组合总和(回溯,组合)--Java版

1.题目描述 2.思路 当前的元素可以重复使用。 &#xff08;1&#xff09;确定回溯算法函数的参数和返回值&#xff08;一般是void类型&#xff09; &#xff08;2&#xff09;因为是用递归实现的&#xff0c;所以我们要确定终止条件 &#xff08;3&#xff09;单层搜索逻辑 二…...