【从零开始学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类(常用接口的实现) 的相关文章

随机推荐

  • 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类常用的接口 以便加