当前位置: 首页 > 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;坚持“公开、公平、公正…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解

在 C/C 编程的编译和链接过程中&#xff0c;附加包含目录、附加库目录和附加依赖项是三个至关重要的设置&#xff0c;它们相互配合&#xff0c;确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中&#xff0c;这些概念容易让人混淆&#xff0c;但深入理解它们的作用和联…...

关于uniapp展示PDF的解决方案

在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项&#xff1a; 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库&#xff1a; npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官

。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量&#xff1a;setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...

在 Spring Boot 中使用 JSP

jsp&#xff1f; 好多年没用了。重新整一下 还费了点时间&#xff0c;记录一下。 项目结构&#xff1a; pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...