【C++笔记】string类的模拟实现

前言

各位读者朋友们大家好!上期我们讲解了string类的基础用法,这期我们来模拟实现一下string类。

目录

  • 前言
  • 一. string类的构造函数
    • 1. 1 无参构造
    • 2.2 带参构造
    • 1.3 无参和带参构造结合
    • 1.4 拷贝构造
    • 1.5 c_str
  • 二. string类的析构函数
  • 三. 字符串的遍历
    • 3.1 size接口
    • 3.2 operator[]
  • 四. 字符串的增删查改接口
    • 4.1 push_back
    • 4.2 operator +=
    • 4.3 append
    • 4.4 insert
    • 4.5 erase
    • 4.6 find
    • 4.7 substr
  • 五. string类的其他接口
    • 5.1 赋值运算符重载
    • 5.2 比较大小
    • 5.2 流插入和流提取重载
  • 六. string类的深拷贝的现代写法
  • 结语

string类的基本结构:

namespace Yuey
{class string{public:// 成员函数private:char* _str;size_t _size;size_t _capacity;};
}

一. string类的构造函数

1. 1 无参构造

string():_str(nullptr),_size(0),_capacity(0)
{}

在这里插入图片描述
通过初始化列表,将_str初始化为空指针, _size和 _capacity初始化为0。但是这样写是有bug的,在我们访问string的时候会对空指针解引用,正确的是

string():_str(new char[1] {'\0'}), _size(0), _capacity(0)
{}

2.2 带参构造

string(const char* str)
{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];// 给\0strcpy(_str, str);
}

如果我们通过初始化列表对string初始化,我们需要先对_str初始化,但是开空间又需要用到_size,就很麻烦,而且这些成员变量不是必须在初始化列表初始化的(引用成员变量、const成员变量、没有默认构造的类类型变量),所以我们在构造函数中对其初始化。

1.3 无参和带参构造结合

string(const char* str = "")
{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];// 给\0strcpy(_str, str);
}

1.4 拷贝构造

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

1.5 c_str

由于我们没有写流提取和流插入的重载函数,为了方便打印查看,我们模拟实现c_str函数,来返回初始化好的string类的字符串:

const char* c_str() const
{return _str;
}

二. string类的析构函数

~string()
{if (_str){delete[] _str;_str = nullptr;_size = _capacity = 0;}
}

将数组在堆上申请的空间释放,size和capacity置为0。

三. 字符串的遍历

3.1 size接口

size_t size()
{return _size;
}

返回字符串的字符个数。

3.2 operator[]

char& operator[](int pos)
{assert(pos < _size);return _str[pos];
}const char& operator[](int pos) const
{assert(pos < _size);return _str[pos];
}

在这里插入图片描述
这样就完成了字符串的遍历
这样支不支持范围for呢?
在这里插入图片描述
答案是不支持的,因为范围for的底层是迭代器,这里我们简单的实现一下:

typedef char* iterator;
iterator begin()
{return _str;
}
iterator end()
{return _str + _size;
}

在这里插入图片描述
这样就能使用范围for遍历字符串了,也能使用迭代器访问:

string::iterator it = s.begin();
while (it != s.end())
{cout << *it << " ";++it;
}

在这里用指针模拟迭代器是因为string类的底层是数组

四. 字符串的增删查改接口

4.1 push_back

void string::reserve(size_t n)// 预留空间
{if (n > _capacity){char* tmp = new char[n + 1]; // 给\0strcpy(tmp, _str);delete[] _str;// 释放原数组的空间_str = tmp;_capacity = n;}
}void string::push_back(char ch)
{if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size++] = ch;_str[_size] = '\0';
}

4.2 operator +=

  • string& string::operator += (char ch)
string& string::operator += (char ch)
{push_back(ch);return *this;
}
  • string& string::operator += (const char * str)
string& string::operator += (const char* str)
{append(str);return *this;
}

4.3 append

void append(const char * str);

void string::append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);// 如果_size + len > 2*_capacity要多少给多少,// 否则二倍扩容}strcpy(_str + _size, str);_size += len;
}

4.4 insert

  • void string::insert(size_t pos, char ch)
void string::insert(size_t pos, char ch)
{assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity); }size_t end = _size + 1;// 直接挪动\0while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;
}
  • void string::insert(size_t pos, const char * str)
void string::insert(size_t pos, const char* str)
{assert(pos < _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len > 2 * _capacity ? _size + len :2 * _capacity);}int end = _size + len;while (end > pos + len - 1) // pos + 3 - 1{_str[end] = _str[end - len];--end;}for (int i = pos; i < len; ++i){_str[i] = str[i];}_size += len;
}

在这里插入图片描述

4.5 erase

	void string::erase(size_t pos, size_t len)// 缺省值给到声明{if (len >= _size - pos)// 长度多于剩余字符的长度,就全删{_str[pos] = '\0';_size = pos;}else // 从后往前依次拷贝{for (int i = pos + len; i < _size; ++i){_str[i - len] = _str[i];}_size -= len;}}

在这里插入图片描述

4.6 find

  • size_t string::find(char ch, const size_t pos)
size_t string::find(char ch, const size_t pos)
{for (int i = pos; i < _size; ++i){if (_str[i] == ch){return i;}}return npos;
}

找单个字符仅需遍历字符串对比即可。

  • size_t string::find(const char * str, const size_t pos)
	size_t string::find(const char* str, const size_t pos){assert(pos < _size);const char* rptr = strstr(_str + pos, str);// 返回的是str在_str中第一次出现的地址if (rptr) // 如果找到了,即rprt非空{return rptr - _str; // 指针 - 指针是元素个数}else{return npos;}}

在这里插入图片描述

4.7 substr

string string::substr(size_t pos, size_t len)
{string tmp;if (len > _size - pos){len = _size - pos;}tmp.reserve(len);for (int i = 0; i < len; ++i){tmp += _str[pos + i];}return tmp;
}

如果len大于剩余的字符的个数,那就将len置为剩余的字符总个数,将剩余的字符全部来构造新的字符串,给新的字符串预留出len长度的空间,然后对其加等原字符串的pos后的len个字符。

五. string类的其他接口

5.1 赋值运算符重载

string& operator=(const string& s)
{if (&s != this)// 避免自己给自己赋值将空间释放{delete[] _str;_str = new char[s._capacity + 1]; // 给\0strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;
}

几乎相当于拷贝构造

5.2 比较大小

	bool operator<(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) < 0;// 在全局无法访问成员变量}bool operator<=(const string& s1, const string& s2){return s1 < s2 || s1 == s2;}bool operator>(const string& s1, const string& s2){return !(s1 <= s2);}bool operator>=(const string& s1, const string& s2){return !(s1 < s2);}bool operator==(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) == 0;}bool operator!=(const string& s1, const string& s2){return !(s1 == s2);}

在比较的时候,也可以进行字符串和string类的比较,因为字符串会走隐式类型转换,构造一个临时对象,但是不能两个字符串比较,因为运算符重载至少有一个类类型的对象。

5.2 流插入和流提取重载

  • 流插入
	ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}
  • 流提取
	void string::clear(){_str[0] = '\0';_size = 0;}istream& operator>>(istream& in, string& s){s.clear();const int N = 256;char buff[N];int i = 0;char ch;ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == N - 1){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}

这里使用get是因为流提取操作符不能提取空格和换行操作符。开一个buff数组是为了提高效率。

六. string类的深拷贝的现代写法

		void 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);// 拷贝构造一个_strswap(tmp);}

在这里插入图片描述
这段程序,用s1拷贝构造s2,对s2拷贝构造时,先走初始化列表,由于我们没有显示的对s2的成员变量初始化,并且编译器对于没有初始化的内置类型的成员变量的赋值是未定义的,因此我们需要给缺省值,防止释放野指针。
在这里插入图片描述
走完初始化列表之后,s2的_str是空指针, _size和 _capacity都是,这时候与用s构造出来的tmp进行成员变量的交换,就相当于拷贝构造得到了s1。

这样,赋值运算符重载也可以使用现代写法了

	string& operator=(const string& s){if (this != &s){string tmp(s);swap(tmp);}return *this;}

在这里插入图片描述
用s2拷贝构造一个tmp,然后s1与tmp交换,s1就跟s2一样了,而且将s1给tmp之后,作用域结束,tmp会释放,也就不需要我们手动释放s1了。
究极写法:

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

我们将s2传过来是传值传参会调用拷贝构造,构造出和s2相同的对象,然后我们将s1和tmp对象的内容进行交换,就相当于将s2赋值给s1了,由于将s1与tmp交换,所以出作用域后,tmp将s1释放。并且在这种写法下,也不用考虑s1和s2是否相同,即便相同我们也是传的s2的拷贝,对形参的改变也不会影响实参。

在这里插入图片描述

结语

以上我们就讲完了string类的模拟实现,希望对大家有所帮助,感谢大家阅读,欢迎批评指正!

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

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

相关文章

java中ArrayList的使用存储对象的易错点

ArrayList存储对象的易错点 上面这种写法是有逻辑问题的&#xff0c;因为只创建了一个Student对象&#xff0c;因此最后打印出来的结果是三个最后赋值的结果。 下面我们来形象看下存储关系 集合中存储的始终是第一个对象的地址&#xff0c;而每次输入新的名字和年龄&#xf…

栈和队列(Java)

一.栈&#xff08;Stack&#xff09; 1.定义 栈是限定仅在表尾进行插入或删除操作的线性表 一般的表尾称为栈顶 表头称为栈底 栈具有“后进先出”的特点 2.对栈的模拟 栈主要具有以下功能&#xff1a; push(Object item)&#xff1a;将元素item压入栈顶。 pop()&am…

Angular 和 Vue2.0 对比

前言 &#xff1a;“业精于勤&#xff0c;荒于嬉&#xff1b;行成于思&#xff0c;毁于随” 很久没写博客了&#xff0c;大多记录少进一步探查。 Angular 和 Vue2.0 对比&#xff1a; 一.概念 1.1 Angular 框架&#xff1a; 是一款由谷歌开发的开源web前端框架&#xff08;核…

基于Multisim数字电子秒表0-60S电路(含仿真和报告)

【全套资料.zip】数字电子秒表电路Multisim仿真设计数字电子技术 文章目录 功能一、Multisim仿真源文件二、原理文档报告资料下载【Multisim仿真报告讲解视频.zip】 功能 1.秒表最大计时值为60秒&#xff1b; 2. 2位数码管显示&#xff0c;分辨率为1秒&#xff1b; 3.具有清零…

安卓智能指针sp、wp、RefBase浅析

目录 前言一、RefBase1.1 引用计数机制1.2 设计目的1.3 主要方法1.4 如何使用1.5 小结 二、sp和wp2.1 引用计数机制2.2 设计目的2.3 主要方法2.3.1 sp2.3.2 wp 2.4 如何使用2.5 小结 四、参考链接 前言 安卓底层binder中&#xff0c;为什么 IInterface要继承自RefBase &#x…

【论文笔记】Prefix-Tuning: Optimizing Continuous Prompts for Generation

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Prefix-Tuning: Optimizin…

【Web前端】从回调到现代Promise与Async/Await

异步编程是一种让程序能够在等待某些操作完成的同时继续执行其他任务的关键技术&#xff0c;打破了传统编程中顺序执行代码的束缚。这种编程范式允许开发者构建出能够即时响应用户操作、高效处理网络请求和资源加载的应用程序。通过异步编程&#xff0c;JavaScript 能够在执行耗…

【CSS】“flex: 1“有什么用?

flex 属性的组成 flex 属性是一个复合属性&#xff0c;包含以下三个子属性&#xff1a; flex-grow&#xff1a;决定元素在容器中剩余空间的分配比例。默认值为 0&#xff0c;表示元素不会扩展。当设置为正数时&#xff0c;元素会按照设定比例扩展。flex-shrink&#xff1a;决…

计算机课程管理:Spring Boot与工程认证的协同创新

3系统分析 3.1可行性分析 通过对本基于工程教育认证的计算机课程管理平台实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本基于工程教育认证的计算机课程管理平…

【SpringBoot】18 上传文件到数据库(Thymeleaf + MySQL)

Git仓库 https://gitee.com/Lin_DH/system 介绍 使用 Thymeleaf 写的页面&#xff0c;将&#xff08;txt、jpg、png&#xff09;格式文件上传到 MySQL 数据库中。 依赖 pom.xml <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j --><depende…

Sharding运行模式、元数据、持久化详解

运行模式 单机模式 能够将数据源和规则等元数据信息持久化&#xff0c;但无法将元数据同步至多个Sharding实例&#xff0c;无法在集群环境中相互感知。 通过某一实例更新元数据之后&#xff0c;会导致其他实例由于获取不到最新的元数据而产生不一致的错误。 适用于工程师在本…

挖掘web程序中的OAuth漏洞:利用redirect_uri和state参数接管账户

本文探讨了攻击者如何利用OAuth漏洞&#xff0c;重点是滥用redirect_uri和state参数以接管用户账户。如果redirect_uri参数验证不严&#xff0c;可能会导致未经授权的重定向到恶意服务器&#xff0c;从而使攻击者能够捕获敏感信息。同样&#xff0c;state参数的错误实现可能使O…

Python世界:力扣题解1712,将数组分成三个子数组的方案数,中等

Python世界&#xff1a;力扣题解1712&#xff1a;将数组分成三个子数组的方案数&#xff0c;中等 任务背景思路分析代码实现测试套件本文小结 任务背景 问题来自力扣题目1712. Ways to Split Array Into Three Subarrays&#xff0c;大意如下&#xff1a; A split of an intege…

Java集合基础——针对实习面试

目录 Java集合基础什么是Java集合&#xff1f;说说List,Set,Queue,Map的区别&#xff1f;说说List?说说Set?说说Map&#xff1f;说说Queue?为什么要用集合&#xff1f;如何选用集合&#xff1f; Java集合基础 什么是Java集合&#xff1f; Java集合&#xff08;Java Collect…

基于单片机的客车载客状况自动检测系统(论文+源码)

1系统整体设计 本课题为客车载客状况自动检测系统&#xff0c;在此以STM32单片机为核心控制器&#xff0c;结合压力传感器、红外传感器、蜂鸣器、语音提示模块、继电器、液晶等构成整个客车载客状况自动检测系统&#xff0c;整个系统架构如图2.1所示&#xff0c;在此通过两个红…

渗透测试(socket,namp,scapy)

socket:可以用来实现不同虚拟机或者不同计算机之间的通信。 socket常用函数&#xff1a; sock.bind(host,port) //host可接受client范围&#xff0c;以及连接的端口 sock.listen()//sever开启监听连接 sock.accpet()//返回 sock&#xff0c;addr 用来接受和发送数据 addr…

【mongodb】数据库的安装及连接初始化简明手册

NoSQL(NoSQL Not Only SQL )&#xff0c;意即"不仅仅是SQL"。 在现代的计算系统上每天网络上都会产生庞大的数据量。这些数据有很大一部分是由关系数据库管理系统&#xff08;RDBMS&#xff09;来处理。 通过应用实践证明&#xff0c;关系模型是非常适合于客户服务器…

内网对抗-信息收集篇SPN扫描DC定位角色区域定性服务探针安全防护凭据获取

知识点&#xff1a; 1、信息收集篇-网络架构-出网&角色&服务&成员 2、信息收集篇-安全防护-杀毒&防火墙&流量监控 3、信息收集篇-密码凭据-系统&工具&网站&网络域渗透的信息收集&#xff1a; 在攻防演练中&#xff0c;当完成边界突破后进入内…

OpenWebUI,RAG+外部知识库+AI写文的开源应用

引言 自从去年AI火起来之后&#xff0c;很多人便热衷于寻找适合自用的AI开源项目&#xff0c;把各家大模型API接入到自己的AI程序里&#xff0c;便可以通过AI辅助完成一系列日常任务&#xff0c;比如内容翻译/润色/总结/撰写、格式转换、数据分类、代码分析、角色扮演等等。 …

qt QErrorMessage详解

1、概述 QErrorMessage是Qt框架中用于显示错误消息的一个对话框类。它提供了一个简单的模态对话框&#xff0c;用于向用户显示错误或警告消息。QErrorMessage通常用于应用程序中&#xff0c;当需要向用户报告错误但不希望中断当前操作时。它提供了一个标准的错误消息界面&…