【从零开始学c++】———模拟实现string类(常用接口的实现)

2023-11-01

在这里插入图片描述

1.前言

之前学到了string类常用接口的后,我很好奇string类在底层是怎样实现的,在由之前学习到c++有关的之后,所以我打算模拟实现一下string类常用的接口,以便加深我对之前的知识的理解,和更好的去使用string类,比如在哪些场景使用哪些接口较为合适。接下来我会用c++来模拟实现各个接口。

2.string类常用接口实现

string类就是存储字符的顺序表,它的实现与与顺序表的实现是非常相似,主要接口为增删查改。实现如下

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;

//定义一个属于自己的域
namespace sjp
{

	class string
	{
	private:
		char* _str;
		size_t _size;//字符串中的有效的字符个数
		size_t _capacity;//字符串中的最大容量
		static const size_t npos;//npos是string类对象中的一个静态成员变量,为整数的最大值
	public:
	    //构造函数
		string(const char* str ="")
		{
			_size = strlen(str);//计算出初始化字符串的大小
			_capacity = _size;
			_str = new char[_capacity+1];//开辟空间多开辟一块空间,这块空间留给\0
			strcpy(_str, str);
		}
		
		//交换函数,可以与另一个sting对象交换数据
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

       //析构函数,清理资源
		~string()
		{
			delete _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}

       //拷贝构造,对象创建时,可以将另一个string对象的数据拷贝给要创建的对象
		string(const string& s)
		{
			sjp::string tmp(s._str);
			swap(tmp);//函数调用结束时tmp会被析构函数给清理掉
		}

         //迭代器,string的迭代器的底层就是指针
		typedef char* iterator;

		iterator begin()
		{
			return this->_str;//返回字符串首元素的地址
		}

		iterator end()
		{
			return this->_str + _size;//返回字符串最后一个地址
		}

          // 返回字符的个数
		size_t size()
		{
			return _size;
		}

         //预留字符串的空间,只改变_capacity,不改变初始化空间
		void reserve(size_t capacity)
		{
		//如果预留的空间大于原本的空间,直接开辟一块capacity的空间,并把数据拷贝给这块空间
			if (capacity > _capacity)
			{
				char* str= new char[capacity];
				strcpy(str, _str);
				_str = str;
				_capacity = capacity;
			}
			//如果capacity小于_size,则_capacity不做改变,_size改变即可
			else if(capacity<_size)
			{
				_size = capacity;
				_str[_size] = '\0';
			}
		}


        //预留空间,并且初始化这块空间,既改变capacity和size
		void resize(size_t capacity,char ch='\0')
		{
			if (capacity < _size)
			{
				_size = capacity;
				_str[_size] = '\0';
			}
			else
			{
				if (capacity > _capacity)
				{
					reserve(capacity);
				}
				memset(_str + _size, ch, (capacity-_size));//memset是创建一块空间,并初始化这块空间
				_str[_size] = '\0';
				_size = capacity;
			}
		}
 
      //尾插一个字符
		void push_back(char ch)
		{
			if (_size == _capacity)//判断字符串的空间是否满了
			{
				size_t newcapacity = _capacity == 0 ? 5 : _capacity * 2;
				reserve(newcapacity);
				_capacity = newcapacity;
			}
			_str[_size] = ch;
			_size += 1;
			_str[_size] = '\0';//最后记得加上\0
		}
		//尾插一个字符串
		void append(const char* str)
		{
			size_t len = strlen(str);//尾插的字符串的个数
			if (len + _size > _capacity)//原来的字符串的个数和要插入的字符的个数大于字符串的空间
			{
				reserve(len + _size);//增容
			}
			strcpy(_str + _size, str);
			     _size += len;
		}
		  //操作符+=重载,尾插一个字符串
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
      //操作符+=重载,尾插一个string类对象
		string& operator+=(const string& s)
		{
			append(s._str);
			return *this;
		}
       //操作符+=重载,尾插一个字符
		string& operator+=(const char ch)
		{
			push_back(ch);
			return *this;
		}
      //操作符[]重载,访问字符串中的某个字符,既可读也可以写,const的string不能调用它
		char& operator[](size_t i)
		{
			return _str[i];
		}
     //操作符[]重载,访问字符串中的某个字符,只能读不能写,const调用它
		const char& operator[](size_t i)const
		{
			return _str[i];
		}
		//在某个位置插入一个字符
		string& insert(size_t pos,char ch)
		{
			assert(pos < _size);
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 5 : _capacity * 2;
				reserve(newcapacity);
				_capacity = newcapacity;
			}
			
			

			//不能这样实现,pos为0时,end减到-1时,由于end和pos是无符号,所以end会变成最大整数,一直无限循环
			/*size_t end = _size - 1;
			while (end >= pos)
			{
				_str[end - 1] = _str[end];
				end--;
			}
		*/
          size_t end = _size+1;
			while(end>pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}
			_str[pos] = ch;
			_size++;
			return *this;
		}
		//在某个位置插入一个字符串
		string& insert(size_t pos,const char* s)
		{
			assert(pos < _size);
			size_t len = strlen(s);
			if (len + _size > _capacity)
			{
				reserve(len + _size);
			}
			char*  end = _str+_size;
			while (end >=_str+pos)
			{
				*(end + len) = *end;
				end--;
			}
			size_t cur = pos;
			strncpy(_str + pos, s, len);
			_size += len;
			return *this;
		}

               //在某个位置去掉多少个字符
		string& erase(size_t pos,size_t n=npos)
		{
			assert(pos < _size);
			size_t leftlen = _size - pos;//leftlen是尾上
			if (leftlen <= n)
			{
				_size = pos;
				_str[_size] = '\0';//注意
			}
			else
			{
				strcpy(_str + pos, _str + pos + n);
				_size -= n;
			}
			return *this;
		}
		//从pos位置开始查找查找一个字母
		size_t find(const char ch,size_t pos=0)
		{
			assert(pos < _size);
			for (int i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}

			}
			//找不到返回npos
			return npos;
		}
    
       //从某个位置开始,查找一个字符串,如果找到,返回该字符串的位置,找不到返回npos
		size_t find(const char* ch, size_t pos = 0)
		{
			assert(pos < _size);
			//strstr(s1,s2)的功能是判断s2是否为s1的字串,并且返回s2在s1中的地址,找不到返回nullptr
			const char* ret = strstr(_str+pos, ch);

			/*char* cur = _str;
			while (cur < _str+_size)
			{
				if (cur == tmp)
				{
					return cur - _str;
				}
				cur++;
			}*/

			if (ret)
			{
				return ret - _str;
			}
			else
			{
				return npos;
			}
			return npos;
		}
		
     //访问对象中的字符串
		char* s_str()const
		{
			return _str;
		}
     //判断字符串是否为空
		bool empty()const
		{
			return _size == 0;
		}
     //清空字符串
		void clear()
		{
			_size = 0;
			_str[_size] = '\0';
		}

	
	};
	const size_t string::npos = -1;
  
    //下面这几个为比较两个string对象的大小的操作符重载
	bool operator==(const sjp::string& s1, const sjp::string& s2)
	{
		return strcmp(s1.s_str(), s2.s_str()) == 0;
	}
	bool operator>(const sjp::string& s1, const sjp::string& s2)
	{
		return strcmp(s1.s_str(), s2.s_str()) > 0;
	}


	bool operator<(const sjp::string& s1, const sjp::string& s2)
	{
		return !(s1 == s2) || !(s1 > s2);
	}

	bool operator>=(const sjp::string& s1, const sjp::string& s2)
	{
		return !(s1 < s2);
	}


	bool operator<=(const sjp::string& s1, const sjp::string& s2)
	{
		return !(s1 > s2);
	}

	bool operator!=(const sjp::string& s1, const sjp::string& s2)
	{
		return !(s1 == s2);
	}

    //操作符<<重载,使cout<<能够输出string对象
	ostream& operator<<(ostream& out, string& s)
	{
		for (auto ch: s)//范围for的使用
		{
			out << ch;
		}
		return out;
	}
  //操作符>>重载使cin>>能够输入string对象
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();//in.get()能够获取空格和回车字符
		while (ch != ' ' && ch != '\n')//如果ch为空格或回车,将跳出输入
		{
			s += ch;
			ch = in.get();
		}

		return in;
	}
}
  

3.总结

1.strstr(char* s1,char* s2)判断s2是否为s1的字串,如果是,则返回该字串在s1中的地址位置,如果不是返回空。
strcpy(char* s1,char* s2)将s2中的字符拷贝给s1,包括\0;
memset(void* str, ch, size) 从str的位置开始,开辟size个字节的大小,并将开辟的空间初始为ch
2.写istream和iostream的重载时,记得返回的值是istream&或iostream&(注意是引用),参数也是istream&或iostream&
3.写构造函数需要多开辟一个空间给\0,
4.如果没使用strcpy,删除或则增加需要记得在结尾_str[_size]=‘\0’.
5.operator[]需要实现两个,一个为const类型的,一个非const类型的,在操作函数后面加const是为了修饰this指针指向的成员变量,若为const类型的对象,它只能读,不能写,那么它在调用时会自动调用const类型的重载函数,若不是从const类型的对象,那么调用的时候会调用不是const类型的函数。
6.对于string的实现,c++标准只规定string要实现的接口功能,但各个编译器具体怎样实现,都是不一样的,例如,在vs编译器中,string的对象的大小是28个字节,在gcc中string对象的大小为12个字节。

模拟实现string常用接口后,我们可以知道string的迭代器的底层是指针,修改数据尽量少用insert,erase,因为它们的使用需要挪动数据,时间复杂度为O(n^2),效率太低了,reserve与resize的区别。

点个赞呗~!!!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【从零开始学c++】———模拟实现string类(常用接口的实现) 的相关文章

  • 用于代数简化和求解的 C# 库 [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 网络上有很多代数求解器和简化器 例如 algebra com 上不错的代数求解器和简化器 然而 我正在
  • 每个托管线程是否都有自己对应的本机线程?

    我想知道是否在 Net 中创建托管线程 通过调用Thread Start 导致在后台创建一个本机线程 那么托管线程是否有对应的本机线程呢 如果是 当托管线程等待或睡眠时 是否意味着相应的本机线程也在等待或睡眠 是的 NET 线程映射到所有当
  • 注销租约抛出 InvalidOperationException

    我有一个使用插件的应用程序 我在另一个应用程序域中加载插件 我使用 RemoteHandle 类http www pocketsilicon com post Things That Make My Life Hell Part 1 App
  • 如何在 .NET Framework 2.0 中模拟“Func<(Of <(TResult>)>) 委托”?

    我尝试使用这个类代码项目文章 http www codeproject com KB threads AsyncVar aspx在 VB NET 和 NET Framework 2 0 中 除了这一行之外 所有内容似乎都可以编译Privat
  • 如何将 protobuf-net 与不可变值类型一起使用?

    假设我有一个像这样的不可变值类型 Serializable DataContract public struct MyValueType ISerializable private readonly int x private readon
  • 计算 Richtextbox 中所有单词的最有效方法是什么?

    我正在编写一个文本编辑器 需要提供实时字数统计 现在我正在使用这个扩展方法 public static int WordCount this string s s s TrimEnd if String IsNullOrEmpty s re
  • C中的malloc内存分配方案

    我在 C 中尝试使用 malloc 发现 malloc 在分配了一些内存后浪费了一些空间 下面是我用来测试 malloc 的一段代码 include
  • 单个对象的 Monogame XNA 变换矩阵?

    我读过一些解释 XNA Monogame 变换矩阵的教程 问题是这些矩阵应用于 SpriteBatch Begin matrix 这意味着所有 Draw 代码都将被转换 如何将变换矩阵应用于单个可绘制对象 就我而言 我想转换滚动背景 使其自
  • 使用接口有什么好处?

    使用接口有什么用 我听说它用来代替多重继承 并且还可以用它来完成数据隐藏 还有其他优点吗 哪些地方使用了接口 程序员如何识别需要该接口 有什么区别explicit interface implementation and implicit
  • 如何使用 LINQ2SQL 连接两个不同上下文的表?

    我的应用程序中有 2 个数据上下文 不同的数据库 并且需要能够通过上下文 B 中的表的右连接来查询上下文 A 中的表 我该如何在 LINQ2SQL 中执行此操作 Why 我们正在使用 SaaS 产品来跟踪我们的时间 项目等 并希望向该产品发
  • 由 IHttpClientFactory 注入时模拟 HttpClient 处理程序

    我创建了一个自定义库 它会自动为依赖于特定服务的 Polly 策略设置HttpClient 这是使用以下方法完成的IServiceCollection扩展方法和类型化客户端方法 一个简化的例子 public static IHttpClie
  • C#:帮助理解 UML 类图中的 <>

    我目前正在做一个项目 我们必须从 UML 图编写代码 我了解 UML 类图的剖析 但我无法理解什么 lt
  • Azure 辅助角色“请求输入之一超出范围”的内部异常。

    我在辅助角色中调用 CloudTableClient CreateTableIfNotExist 方法 但收到一个异常 其中包含 请求输入之一超出范围 的内部异常 我做了一些研究 发现这是由于将表命名为非法表名引起的 但是 我尝试为我的表命
  • 调用堆栈中的“外部代码”是什么意思?

    我在 Visual Studio 中调用一个方法 并尝试通过检查调用堆栈来调试它 其中一些行标记为 外部代码 这到底是什么意思 方法来自 dll已被处决 外部代码 意味着该dll没有可用的调试信息 你能做的就是在Call Stack窗口中单
  • 如果没有抽象成员,基类是否应该标记为抽象?

    如果一个类没有抽象成员 可以将其标记为抽象吗 即使没有实际理由直接实例化它 除了单元测试 是的 将不应该实例化的基类显式标记为抽象是合理且有益的 即使在没有抽象方法的情况下也是如此 它强制执行通用准则来使非叶类抽象 它阻止其他程序员创建该类
  • WebSocket安全连接自签名证书

    目标是一个与用户电脑上安装的 C 应用程序交换信息的 Web 应用程序 客户端应用程序是 websocket 服务器 浏览器是 websocket 客户端 最后 用户浏览器中的 websocket 客户端通过 Angular 持久创建 并且
  • 如何从 ODBC 连接获取可用表的列表?

    在 Excel 中 我可以转到 数据 gt 导入外部数据 gt 导入数据 然后选择要使用的数据源 然后在提供登录信息后 它会给我一个表格列表 我想知道如何使用 C 以编程方式获取该列表 您正在查询什么类型的数据源 SQL 服务器 使用权 看
  • 当从finally中抛出异常时,Catch块不会被评估

    出现这个问题的原因是之前在 NET 4 0 中运行的代码在 NET 4 5 中因未处理的异常而失败 部分原因是 try finallys 如果您想了解详细信息 请阅读更多内容微软连接 https connect microsoft com
  • 从列表中选择项目以求和

    我有一个包含数值的项目列表 我需要使用这些项目求和 我需要你的帮助来构建这样的算法 下面是一个用 C 编写的示例 描述了我的问题 int sum 21 List
  • 当我使用 OpenSSL1.1.0g 根据固定的 p 和 g 值创建 Diffie Hellman 密钥协议密钥时,应该执行哪些检查?

    您好 我尝试通过这段代码使用修复 p 和 g 参数来制作 Diffie Hellman Keysanswer https stackoverflow com a 54538811 4706711 include

随机推荐

  • HDFS常见的问题和处理方法积累

    Hadoop常见问题与解决办法 问题1 reduce预处理阶段shuffle时获取已完成的map的输出失败次数超过上限 问题描述 问题剖析 解决方案 问题2 Too many fetch failures 问题描述 问题剖析 解决方案 问题
  • 解决el-select下拉框有值但是无法选中的问题

    问题描述 在某次开发项目时 发现el select组件无法选中数据了 下拉框中数据可以正常展示 数据是通过接口获取的 解决方案 在 el select 中加一个 change 的事件刷新一下 代码如下
  • top命令的使用和查看某个进程占用的系统内存大小

    一 top指令查看CPU状态和内存使用状态 1 查看CPU占用率 CPU 上次更新到现在的CPU时间占用百分比 2 查看内存占用率 MEM 进程使用的物理内存百分比 3 RES 进程使用的 未被换出的物理内存大小 单位kb RES CODE
  • go通过数组(切片)构建菜单树结构

    有这样的一组节点 每个节点包含自己的Id 还有父Id Parent Id 包含children指针数组 但是children是空 需要根据id和parentId把cihldren填充上 实现了如下的方法 type TreeNode inte
  • JAVA TCP客户端和服务器端简单实例

    客户端 package com example demo import java io BufferedReader import java io InputStreamReader import java io PrintWriter i
  • 详解Java中的byte类型(补充内容)

    写在前面 大家有时候可能会对Java中出现的byte类型有些疑惑 今天就来内化这些知识点 这个算是对其他博客的一个补充 内容很少 byte 在Java中 byte占据一个字节 也就是8的bite位 public static void ma
  • Kaiwii

    http blog csdn net kaiwii article details 7478038
  • qt 16进制字符串 转换为 二进制 字节流

    代码 include
  • 找出10个被打乱的数中被拿出的一个数

    include
  • RuoYi-弹出新窗口选择数据回显到父页面

    这里只贴出关键代码 其他代码需要自行编写 返回值处理根据实际需求来 目前是只取第一条 因为选择页面是单选行 表单页面新加方法 选择社区 function selectCommunity var url ctx community selec
  • 深度

    解码区块链 专题文章三 区块链的安全基础架构及构想 近期 国家发改委明确 区块链 被纳入新基建定义和范围 作为一项能够打通各个技术及领域的基础技术 区块链被认为将在各行业深度融合 新领域拓展 新场景新应用开发等方面潜力无限 解码区块链 内容
  • js 小技巧 ( 根据不同的状态生成不同的颜色和状态 )

    HTML 解决办法 动态绑定 color 然后 根据 三元表达式 进行处理 js 解决办法 动态绑定 color 然后 根据在每个数据的后面添加color属性 可能有的人会问vueb不能用不然不会双向绑定 确实vue要使用vueset 但是
  • 他人工作多年后的总结

    1 找一个好公司 精通至少一门语言及其框架 专注做5到10年 先有深度再有广度 不要为了高工资过早转向管理角色 2 从长远来看 拥有个人项目 阅读 写博客和参加访谈都会有助于你成为一个更好的开发人员 3 成为开发者社区的一部分 努力参加线上
  • qt写C++(引用的妙处,内联函数)

    首先看什么是引用 引用和取地址很像 容易混淆 单独出现 a就是对a这个变量取地址 如果是int a 就是声明这是一个引用 引用 include
  • 基于SpringBoot+redis实现一个简单的点赞功能

    点赞操作比较频繁 而且比较随意 所以数据变更很快 如果用mysql 会对mysql产生很大的压力 于是决定使用Redis 防止数据丢失 所以会定期将数据持久化同步到mysql中 一 Redis 缓存设计及实现 1 1 Redis 安装及运行
  • mysql约束之_外键约束 foreign key

    外键约束 foreign key 创建一个员工表employee 员工编号id 员工的姓名name 部门名称dept name 员工所在的地址address CREATE TABLE employee id INT PRIMARY KEY
  • R语言学习笔记

    参考 W N Venables D M Smith and the R DCT Introduction to R Notes on R A Programming Environment for Data Analysis and Gra
  • java集合Map介绍及具体使用

    目录 Map 双例集合 存储 键值对 key value 的数据 1 基本介绍 2 HashMap 2 1源码介绍 2 2 HashMap源码中的重要常量 2 3面试题 3 LinkHashMap 3 1LinkHashMap源码分析 4
  • Pandas数据处理

    数据预览 首先 调用read excel 方法读取数据 import pandas as pd df pd read excel data xlsx df 姓名 班级 语文 数学 0 张三 一班 99 90 1 李四 一班 78 NaN 2
  • 【从零开始学c++】———模拟实现string类(常用接口的实现)

    string 1 前言 2 string类常用接口实现 3 总结 1 前言 之前学到了string类常用接口的后 我很好奇string类在底层是怎样实现的 在由之前学习到c 有关的之后 所以我打算模拟实现一下string类常用的接口 以便加