[C++]string类的模拟实现和相关函数的详解

news2024/9/25 9:37:29

目录

  • string总体架构
  • 具体实现
    • 默认成员函数
      • 构造函数
      • 构造拷贝函数
      • 析构函数
      • 赋值重载
        • =
        • []
        • +=
    • 相关操作函数
      • c_str() && size()
      • reserve() && resize()
      • push_back() && append()
      • find()
      • insert
      • erase() && clear
    • 其余操作符重载
      • <
      • == 、 <=、 >、 >=、 !=
      • << >>(流提取,流插入)
  • 完整代码

string总体架构

首先由于自定义类不能和已有类重名,所以在自定义命名空间中进行string类的模拟

namespace aiyimu
{
	//string类的模拟
	class string
	{
	public:
		//成员函数的实现
	private:
		//成员变量的定义
		char* _str;
		size_t _size;//开辟空间大小
		size_t _capacity;//有效容量
	};
	//非成员函数的实现
}

具体实现

默认成员函数

构造函数

构造函数进行成员变量的初始化:给_str申请空间,并将_size和_capacity初始化

参数使用缺省值:

  1. 如果是无参构造则将_size,_capacity初始化为0,给_str开辟空间后将*str拷贝给_str
  2. 如果传入char* 类型的字符串,如string str("hello");,此时会设置好相应的大小/容量,然后将传入的数据拷贝给_str(即创建的str)
string(const char* str = "")//用'\0'也可
	:_size(strlen(str))
	,_capacity(_size)
{
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

构造拷贝函数

传统写法*

传统写法中直接将传入的字符串的相应变量赋值给_str,最后进行new空间给_str

但该写法有一个缺点:没有对内存分配过程进行异常处理,在调用new运算符分配内存时发生了内存不足或者其他错误,程序会抛异常,可能会导致程序崩溃等。

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

现代写法*

现代写法在此处进行了优化:

  1. 在构造函数中使用了一个临时的string对象tmp来存储参数s的_str字符串内容
  2. 通过调用成员函数swap来交换临时对象和当前对象之间的_str、_size和_capacity变量

该方法避免了构造函数和赋值运算符重载中内存分配和释放的重复操作,提高了代码的效率。

//交换两个字符串
void swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

string(const string& s)
	:_str(nullptr)
	,_size(0)
	,_capacity(0)
{
	string tmp(s._str);//tmp临时存储s._str字符串的内容

	//this->swap(tmp)
	swap(tmp);//直接交换
}

注:自行实现的swap()函数后续依然有用,所以建议写成函数

析构函数

析构函数进行资源的清理释放:

  1. 释放掉_str指向的空间并置空_str
  2. 将_size,_capacity赋值0
~string()
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}

赋值重载

  • 由于 =、 []、 +=是strig类对自身进行操作的操作符,所以写为成员函数
  • 而 <、== 等是判断两个string类的时候使用,所以写成非成员函数

=

传统写法*

string& operator=(const string& s)
{
	//自己赋值自己,不进入if语句,直接返回
	if (this != &s)
	{
		//开辟空间
		char* tmp = new char[s._capacity + 1];
		strcpy(tmp, s._str);//将字符串拷贝到开辟的空间
		_str = tmp;
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

现代写法*

赋值就是赋值,直接赋值即可

既然已有自定义的swap函数,那就用吧:

此处调用先前拷贝构造时写的swap,直接把s的成员变量给_str

string& operator=(string s)
{
	//this->swap(s);
	//直接交换
	swap(s);
	return *this;
}

[]

方括号用于取下标,在不越界的前提下返回_str[pos]即可

//可读 + 可修改
char& operator[](size_t pos)
{
	//防止越界
	assert(pos < _size);
	return _str[pos];
}

//只读
const char& operator[](size_t pos) const
{
	//防止越界
	assert(pos < _size);
	return _str[pos];
}

+=

+=单个字符

  1. +=执行push_back()(尾插)的操作即可
  2. 执行+=运算时,需要返回+=后的值,所以返回*this
string& operator+=(char ch)
{
	push_back(ch);
	return *this;
}

+=字符串

  1. 同单个字符串一样,执行apped()(追加字符串)即可
string& operator+=(const char* str)
{
	append(str);
	return *this;
}

push_back(), append()都在下文

相关操作函数

c_str() && size()

c_str()

该函数返回一个指向字符串中第一个字符的常量指针,用来兼容c的字符串,同时便于我们测试打印

const char* c_str() const //不改变_str的函数推荐后const,const和非const都能用
{
	return _str;
}

size()

size()用于得到此时string的大小(元素个数)

size_t size() const
{
	return _size;
}

reserve() && resize()

reserve()

  1. reserve()是扩容函数:预留出一定的空间
  2. 当输入n小于此时容量时就不执行操作,所以使用if语句
  3. 创建tmp开辟n+1(多开一个存’\0’)的空间
  4. 将_str拷贝给tmp,销毁_str指向的空间,将tmp给_str
  5. 最后更新_capacity

在这里插入图片描述

void reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];//多开辟一个存'\0'
			strcpy(tmp, _str);
			delete[] _str;

			_str = tmp;
			
			//_capacity += n;	//预留n个元素的内存空间
			_capacity = n;
		}
	}

_capacity = n将空间扩容为n
_capacity += n预留出n个元素的空间

resize()

  1. 如果将reserve()理解为扩容,那么resize()可以认为扩容+初始化 / 缩容
  2. reserve()只改变_capacity的大小,而resize()会改变_size,_capacity的大小,支持增容和缩容。
  3. 具体思路:代码+注释
void resize(size_t n, char ch = '\0')
{
	//缩容
	//若要改变的空间大小小于原本空间容量,直接缩小把最后一位给'\0'
	if (n <= _size)
	{
		_size = n;
		_str[_size] = '\0';
	}
	else
	{
		//判断是否需要扩容
		if (n > _capacity)
		{
			reserve(n);//直接利用reserve扩容
		}

		//将多出的空间全部设为ch,共有n - _size个元素
		memset(_str + _size, ch, n - _size);
		_size = n;//更新_size
		_str[_size] = '\0';
	}
}

push_back() && append()

push_back()

  1. 尾插函数,向字符串中插入一个字符
  2. 先进行扩容,后直接在尾部(_size位)赋值ch(插入的字符)即可,
  3. 最后更新_size:将_size扩一位,补’\0’
void push_back(char ch)
{
	//判断是否需要增容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	_str[_size] = ch;
	//尾插后把最后一位置'\0'
	++_size;
	_str[_size] = '\0';
}

append()

  1. 和push_back()思路一致:
  2. 判断扩容(扩容大小为 待追加的字符串str长度+_size)
  3. 直接把str拷贝到字符串最后位置,更新_size
void append(const char* str)
{
	size_t len = strlen(str);
	
	//判断扩容
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	strcpy(_str + _size, str);//把str拷贝到_str最后位置
	_size += len;
}

find()

找到指定字符

  1. 遍历_str,如果存在ch,返回下标
  2. 不存在ch,返回npos(设npos = -1)
size_t find(char ch)
{
	for (size_t i = 0; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}

	return npos;
}

找到指定字符串

  1. 利用c库函数strstr()(找字符串的子串)
  2. strstr():找不到返回空,找得到返回该位置的指针(即该位置到后面所有的字符)
  3. find()找不到我们返回npos,找到返回该位置下标
size_t find(const char* s, size_t pos = 0)
{
	//_str的第pos位找是否有子串s
	const char* ptr = strstr(_str + pos, s);

	if (ptr == nullptr)
	{
		//没有该子串
		return npos;
	}
	else
	{
		return ptr - _str;
	}
}

insert

指定位置插入字符

  1. 在pos位插入元素前,需要将pos后的所有元素后移一位
  2. 然后在pos位插入元素,更新size即可
string& insert(size_t pos, char ch)
{
	//防止pos越界
	assert(pos <= _size);
	
	//判断扩容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	//需要将pos后的元素后移,记录size后一位位置
	size_t end = _size + 1;

	while (end >= pos)
	{
		//把pos后的所有元素后移一位
		_str[end] = _str[end - 1];
		--end;
	}

	_str[pos] = ch;//插入
	++_size;//更新size
	return *this;
}

指定位置插入字符串

  1. 和前面append()push_back()的区别一致
  2. 先记录待插入字符串长度len,将pos后的元素后移len位
    ,再插入s即可
string& insert(size_t pos, const char* s)
{
	assert(pos <= _size);
	
	//记录插入字符串长度
	size_t len = strlen(s);
	//判断扩容
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	size_t end = _size + len;
	while (end > pos)
	{
		_str[end] = _str[end - len];//把pos后的元素后移len位,给将要插入的字符串留空
		--end;
	}

	strncpy(_str + pos, s, len);//将s插入到留的空中

	return *this;
}

erase() && clear

erase()

  1. erase()删除指定位置pos 长度为len的字符(串)
  2. pos + len > _size时,此时要从pos位删除的数据已经超出pos位后的所有元素,直接将pos位置为’\0’,然后更新_size即可
  3. 反之直接将pos位后面len位拷贝给pos位,然后更新_size
string& erase(size_t pos = 0, size_t len = npos)
{
	//防止pos越界
	assert(pos < _size);

	//pos位置后面的全部删除
	if (pos + len >= _size)
	{
		_str[pos] = '\0';
		_size = pos;			
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}

	return *this;
}

clear

清空s的元素,用于流插入

void clear()
{
	_str[0] = '\0';
	_size = 0;
}

其余操作符重载

<

法一

  1. 分别定义s1,s2的下标i1,i2
  2. while循环中依次比较两个字符串中每个位置上字符的大小,如果发现在某个位置上s1[i1] < s2[i2],说明s1比s2小,返回true;如果s1[i1] > s2[i2],说明s1比s2大,返回false;如果s1[i1] == s2[i2],说明当前位置上两个字符串相同,向后移动下标继续比较。
  3. 当任意一个字符串遍历完毕时,此时哪个字符串还有元素哪个字符串就大。(这里直接假设i2)
bool operator<(const string& s1, const string& s2)
{
	size_t i1 = 0, i2 = 0;
	
	while (i1 < s1.size() && i2 < s2.size())
	{
		//比较每个位置元素的大小
		if (s1[i1] < s2[i2])
		{
			return true;
		}
		else if (s1[i1] > s2[i2])
		{
			return false;
		}
		else//该下标元素相同向后走
		{
			++i1;
			++i2;
		}
	}
	//其中一个走完,还有元素的字符串更大
	return i2 < s2.size() ? true : false;
}

法二

  • 由于前面实现了c_str()函数,可以兼容c的字符串,这里直接用c库函数strcmp()比较即可
bool operator<(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}

== 、 <=、 >、 >=、 !=

==

< 的法二相同,调用strcmp()即可

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 !(s1 == s2);
}

<< >>(流提取,流插入)

完整代码

string类的模拟实现+测试用例

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

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

相关文章

【系统集成项目管理工程师】项目整体管理

&#x1f4a5;十大知识领域&#xff1a;项目整体管理 项目整体管理包括以下 6 个过程: 制定项目章程定项目管理计划指导与管理项目工作监控项目工作实施整体变更控制结束项目或阶段过程 一、制定项目章程 制定项目章程。编写一份正式文件的过程&#xff0c;这份文件就是项目章程…

某程序员哀叹:月薪四五万,却每天极度焦虑痛苦,已有生理性不适,又不敢裸辞,怎么办?

高薪能买来快乐吗&#xff1f; 来看看这位程序员的哀叹&#xff1a; 实在是扛不住了&#xff0c;每天都在极度焦虑和痛苦中度过&#xff0c;早上起来要挣扎着做心理建设去上班&#xff0c;已经产生生理性的头晕恶心食欲不振。有工作本身的原因&#xff0c;更多是自己心态的问…

OpenCV+FFmpeg 实现人脸检测Rtmp直播推流(Python快速实现)

实现效果 windows平台笔记本摄像头视频采集、人脸识别&#xff0c;识别后将视频推流到RTMP流媒体服务器&#xff0c;在任意客户端可以进行RTMP拉流播放。 效果如图&#xff1a; 使用VLC播放器进行拉流。 准备工作 需要先安装OpenCV的python包以及FFmpeg。 对于ffmpeg有两…

Java——删除链表中重复的节点

题目链接 牛客在线oj题——删除链表中重复的节点 题目描述 在一个排序的链表中&#xff0c;存在重复的结点&#xff0c;请删除该链表中重复的结点&#xff0c;重复的结点不保留&#xff0c;返回链表头指针。 例如&#xff0c;链表 1->2->3->3->4->4->5 处…

【Vue】学习笔记-数据代理

数据代理 Object.defineproperty方法 <script type"text/javascript">let number18let person{name:张三,sex:男,}//age属性 不参与遍历Object.defineProperty(person,age,{//value:18,//enumerable:true, //控制属性是否可以枚举&#xff0c;默认值是false//…

科技成果评价最新攻略,你确定不来看看?

一、什么是科技成果评价&#xff1f; 是指按照委托者的要求&#xff0c;由具有评价资质的第三方专业机构聘请专家&#xff0c;坚持实事求是、科学民主、客观公正、注重质量、讲求实效的原则&#xff0c;依照规定的程序和标准&#xff0c;对被评价科技成果进行审查与辨别&#…

[Java Web]VUE | vue:一项Java Web开发中不可或缺的前端技术

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;Java Web ⭐如果觉得文章写的不错&#xff0c;欢迎点个关注一键三连&#x1f609;有写的不好的地方也欢迎指正&a…

AD19 基础应用技巧(快速定义PCB板框,CAD中DWG转DXF格式导入)

【B站一个假的攻城狮】导入CAD图纸到PCB&#xff0c;Altium Designer 21教程&#xff0c;第九节。 http://www.keyboard-layout-editor.com/ http://builder.swillkb.com/ 1、打开中望CAD&#xff0c;并打开一张图纸文件&#xff0c;为了能把孔表达清楚&#xff0c;开孔断面图…

React(六) —— redux

&#x1f9c1;个人主页&#xff1a;个人主页 ✌支持我 &#xff1a;点赞&#x1f44d;收藏&#x1f33c;关注&#x1f9e1; 文章目录⛳Redux&#x1f346;redux定义&#x1f490;redux使用原则&#x1f370;redux使用场景&#x1f9ca;redux工作流程&#x1f96b;redux基本创建…

14.创建组件

组件可以理解为页面的拼图块&#xff0c;一个完整的页面是由若干个组件拼成的 在vue中规定&#xff0c;组件的后缀名为vue&#xff0c;每一个vue文件中应该包含三个大标签 template 组件的模板结构&#xff0c;可以理解为htmlscript 组件的JS&#xff0c;控制组件要执行什么动…

区域检验管理系统(云LIS)源码

1、区域检验管理系统&#xff08;云LIS&#xff09;概述 云LIS是为区域医疗提供临床实验室信息服务的计算机应用程序&#xff0c;可协助区域内所有临床实验室相互协调并完成日常检验工作&#xff0c;对区域内的检验数据进行集中管理和共享&#xff0c;通过对质量控制的管理&am…

Java每日一练(20230418)

目录 1. N皇后 II &#x1f31f;&#x1f31f;&#x1f31f; 2. 字符串相乘 &#x1f31f;&#x1f31f; 3. 买卖股票的最佳时机 &#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一…

“Natural Earth II“ === “Natural Earth II“是false?你知道空 格的四种写法吗?

前言 有一回对我说道&#xff0c;“你学过前端么&#xff1f;”我略略点一点头。他说&#xff0c;“学过前端&#xff1f;……我便考你一考。html 里面的空格&#xff0c;怎样 coding 的&#xff1f;”我想&#xff0c;讨饭一样的人&#xff0c;也配考我么&#xff1f;便回过脸…

计算机网络 - 网络中的基本概念

前言 本篇介绍网络的一些基本概念&#xff0c;认识IP地址&#xff0c;端口号&#xff0c;协议&#xff1b;了解常用的网络协议模型&#xff0c;知道数据如何封装与分用的&#xff1b;为以后学习计算机网络其它知识做铺垫&#xff0c;如有错误&#xff0c;请在评论区指正&#…

Java数据结构 二叉树基本知识 二叉树遍历

二叉树很简单的&#xff0c;试试呗~ 文章目录 Java数据结构 & 二叉树基本知识 & 二叉树遍历1. 树的基本定义2. 树的基本概念2.1 例子2.2 树的代码表示&#xff1a; 3. 二叉树3.1 特殊节点3.2 特殊的二叉树3.3 二叉树的性质3.3.1 证明第三点3.3.2 证明第四点 4. 二叉树遍…

MySQL-MHA高可用(一)

目录 &#x1f341;同步概念 &#x1f341;工作原理 &#x1f343;环境拓扑 &#x1f341;环境准备 &#x1f342;manager &#x1f342;master1 &#x1f342;master2 &#x1f342;slave &#x1f343;配置半同步复制 &#x1f341;master1 &#x1f341;master2 &#x1f34…

函数 tcgetpgrp tcsetpgrp 和 tcgetsid

① tcgetpgrp & tcsetpgrp 函数 tcgetpgrp函数是用来获取前台进程组的ID #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> int main() {printf("我的ID&#xff1a;%d---我…

【MySQL学习】MySQL库的操作

目录一、查看数据库的连接二、数据库的创建三、字符集和校验规则3.1 查看数据库默认的字符集以及校验规则3.2 查看数据库支持的字符集以及校验规则3.3 校验规则对数据库的影响四、操纵数据库4.1 查看数据库4.2 显示创建语句4.3 修改数据库4.4 数据库的删除五、数据库的备份与恢…

Nginx中的location规则与rewrite

nginx正则表达式 ^$空行 \d数字 \D代表非数字 \s 匹配空白符 \S 非空白字符 \w匹配任意单词符包括下划线[A-Za-z0-9_] {n} 匹配起那面字符n次 .* 除换行符\n匹配任意字符多次 {n,m}匹配前面字符5到10次 [abc] 匹配一次a,b,c ( )表达式的开始和结束 | 或运算符 &#…

【uniapp】sigmob广告加载失败:-5005、500422没有imei的解决办法(原创可用)

问题 最近打算将开发的uniapp应用对接uni-ad广告中去&#xff0c;在对接sigmob的时候出现了以下问题&#xff1a;①错误码-5005&#xff0c;查询了以下官方文档&#xff0c;发现是说我频繁调用&#xff0c;可是并没有&#xff0c;我是在真机上测试的没成功就没在弄了&#xff…