【C++】string类(2)

news2024/10/19 22:45:48

🥳个人主页: 起名字真南
🥳个人专栏:【数据结构初阶】 【C语言】 【C++】

请添加图片描述

目录

  • 引言
  • 1 模拟实现string类基本框架
  • 2 实现string类中的主要成员函数
    • 2.1 Push_Back 函数
    • 2.2 reserve 函数
    • 2.3 append 函数
    • 2.4 c_str 函数
    • 2.5 begin ,end 函数
    • 2.5 operator= 函数
    • 2.6 operator+= 函数
    • 2.7 insert 函数
    • 2.8 erase 函数
    • 2.9 find 函数
    • 2.10 substr 函数
  • 3 实现string类中的非成员函数
    • 3.1 双目运算符的重载
    • 3.2 输出流
    • 3.3 输入流

引言

C++中的std::string类是标准库提供的高级字符串处理工具,支持动态内存管理和丰富的操作函数。为了加深对字符串类的理解,我们将从零开始模拟实现一个简化版的String类,涵盖字符串的创建、拷贝、连接、查找等基本功能。本篇文章的主要目的是展示如何设计和实现一个类似于std::string的类,并探讨其中涉及的内存管理和操作细节。

1 模拟实现string类基本框架

为了和库函数中的 string 做出区分,所以模拟实现的类名是 String
用一个字符串构造一个函数时,我们在初始化的时候给一个默认参数,默认值为 ""空字符串),不能是单引号。这样可以让我们即使不传入任何参数的情况下,也可以构造一个空的 String 对象

#include<iostream>
#include<assert.h>
using namespace std;
namespace wzr
{
	class String
	{
	public:
		String(const char* str = "")
		{
			size_t len = strlen(str);
			//实际上要存储的数据是len个因为是字符数组所以最后一位留给'\0'
			//char* tmp = new char[len + 1] 
			//_arr = tmp;
			//上面的写法是错误写法因为他只是开辟了空间并没有将内容复制过去
			_arr = new char[len + 1];
			//使用strcpy可以将str的内容(包括'\0'都统一复制到_arr里面)
			strcpy(_arr,str);
			//都没有包含\0
			_size = _capacity = len;
		}
		
		//拷贝构造函数 要求新构造的函数与原函数一样
		String(const String& str)
		{
			//首先获取str中的元素个数
			_size = str._size;
			//开辟空间
			//_arr = new char[_size + 1];  实际空间大小不是原函数的空间大小
			_arr = new char[str._capacity + 1];
			//复制字符串的内容到新构造的字符串中
			strcpy(_arr,str._arr)
			//同步capacity
			//不同于使用字符串构造函数 _capacity 需要与原函数保持一致
			//_capacity = _size;
			_capacity = str._capacity;
		}
		
		//我们现在已经初步实现了构造函数接下来实现析构函数
		~String()
		{
			//delete [] _arr;
			//delete和[]之间没有空格
			delete[] _arr;
			_arr = nullptr;
			_capacity = _size = 0;
		}
	private:
		char* _arr;
		size_t _size;
		size_t _capacity;

		const static size_t npos;
	}
}

2 实现string类中的主要成员函数

2.1 Push_Back 函数

内存不够需要开辟空间,这时候就需要用到 reserve 函数来开辟空间。在进行分配内存大小的时候,我们一般按照原来大小的二倍扩容 reserve(2 * _capacity),但是这个时候出现了一个问题:如果我们的字符串是一个空字符串,空间大小是 0,那么二倍以后依旧是 0。所以我们在这里需要用到三目操作符,并且给一个初始值是 4

	void String::push_back(const char ch)
	{
		//在进行尾插之前我们首先要检查内存大小是否足够
		if(_capacity == _size)
		{
			reserve(_capacity == 0 ? 4 : 2 * _capacity)
		}
		// 内存空间足够可以尾插
		_arr[_size] = ch;
		_size++;
		//尾插过后因为是字符数组所以需要将最后一位置为0
		_arr[_size] = '\0';
	}

2.2 reserve 函数

不管是尾插还是插入字符串,只要是涉及到增加数据都需要扩容,所以我们实现 reserve 函数。在进行扩容之前,首先要判断 n 和原内存空间的大小

  • 如果 n 大于原内存空间,那么我们可以直接扩容
  • 如果 n 小于原内存空间,就需要判断是否小于数据空间。如果小于数据空间直接减少内存会导致数据泄露
  • 如果 n 在这之间,则进行缩小到 n
	//不管是尾插还是插入字符串只要是涉及到增加数据都都需要扩容
	//所以我们实现reserve函数
	void String::reserve(size_t n)
	{

		if(n > _capacity)
		{
			//开辟空间大小为n
			//新建一个临时空间用于拷贝
			char* tmp = new char[n + 1];
			//将原函数arr的数据拷贝到tmp变量里
			strcpy(tmp,_arr);
			//释放原空间的大小
			delete[] _arr;
			_arr = tmp;
			_capacity = n;
			//_size不发生变化
			//进行缩小
		}else if(n < _capacity && n >= _size)
		{
			char* tmp = new char[n + 1];
			//我们在进行数据拷贝的时候一定要注意当我们在进行拷贝的时候
			//_arr的空间大小是大于tmp的所以我们只需要拷贝我们需要的字符数
			strncpy(tmp, _arr, _size);
			//strcpy(tmp, _arr);
			tmp[_size] = '\0';
			delete[] _arr;
			_arr = tmp;
			_capacity = n;
			//缩小_size不会发生变化
		}
	}

2.3 append 函数

void String::append(const char* str)
{
	//断言追加的字符串不能回空
	assert(str);
	//首先进行判断内存是否足够
	//使用len来记录添加字符串的字符数
	size_t len = strlen(str);

	if (_size + len > _capacity)
	{
		//如果原来的数据加上新的字符串的个数大于原空间内存的大小
		//我们需要进行扩容
		reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
	}
	//扩容完成现在要考试移动数据
	strcpy(_arr + _size, str);
	_size += len;

	//由于strcpy会把'\0'自动拷贝过来所以我们不需要手动添加

}

2.4 c_str 函数

因为我们设置的 _arr 是私有变量,所以为了方便访问以及保护私有变量的安全,我们模拟实现了 c_str 函数。实现这个函数的同时还有一个目的,就是可以在 test.cpp 函数内输出 String 类型对象数组的内容
并且像这种短小但是调用频繁的函数,我们直接写在头文件中,因为在类中定义的函数默认为内联函数,可以减少函数的调用,从而提高程序的运行效率内联函数在编译器的编译阶段就会被替换,所以函数体积不宜过大

		char* c_str()
		{
			return _arr;
		}
//我们不仅需要返回普通数据还要返回常量
		const char* c_str() const
		{
			return _arr;
		}

2.5 begin ,end 函数

//其实迭代器的底层逻辑就是通过typedef来定义的所以我们这里的数据类型都是char类型
//所以在定义的时候只需要定义char* 和 const char*
	typedef char* iterator;
	typedef const char* const_iterator;
	
		iterator begin()
		{
			return _arr;
		}

		iterator end()
		{
			return _arr + _size;
		}

		const_iterator begin() const
		{
			return _arr;
		}

		const_iterator end() const
		{
			return _arr + _size;
		}

2.5 operator= 函数

重载赋值操作符的时候,把它放在 String 类的里面作为成员函数有以下好处:

  • 可以直接访问私有成员变量
  • 固定左操作数(即当前对象),因为赋值操作符的左操作数总是当前对象。
  • 同时返回 *this 可以支持链式赋值(例如 a = b = c)。

并且 C++ 也规定了赋值操作符必须是成员函数

		String& operator=(const String& str)
		{
			_arr = new char[_capacity + 1];
			strcpy(_arr, str._arr);
			_size = str._size;
			_capacity = str._capacity;
			return *this}

2.6 operator+= 函数

在进行模拟实现的时候,因为 ++=Push_Backappend 函数的功能相同
所以我们可以直接调用已经实现好的两个函数

		String& String::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}

	String& String::operator+=(const char* ch)
	{
		append(ch);
		return *this;
	}

2.7 insert 函数

//insert 函数在指定位置增加添加数据
	void String::insert(size_t pos, char ch)
	{
//判断pos指针是否有效
		assert(pos >= 0 && pos <= _size);
		//判断内存空间大小
		if (_size + 1 >= _capacity)
		{
		//注意原内存可能空间为0的时候
			reserve(_capacity ==0 ? 1 : 2 * _capacity);
		}
		//将pos以及pos以后的位置向后移位
		//定义一个end变量用来记录最后一个位置
		size_t end = _size;
		while (end >= pos)
		{
			_arr[end + 1] = _arr[end];
			end--;
		}
//因为我们在移动数据的时候已经将\0向后移位所以不需要在进行赋值
		_arr[pos] = ch;
		_size += 1;
//当参数为一个字符串的时候
	void String::insert(size_t pos, const char* ch)
	{
		assert(pos >= 0 && pos <= _size);
		//判断内存空间大小
		size_t len = strlen(ch);
		if (_size + len >= _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		//将pos以及pos以后len个长度的位置向后移位
		size_t end = _size;
		while (end >= pos)
		{
			_arr[end + len] = _arr[end];
			end--;
		}
		//插入数据
		for (size_t i = 0; i < len; i++)
		{
			_arr[pos + i] = ch[i];
		}
		_size += len;
	}
	}

2.8 erase 函数

	void String::erase(size_t pos)
	{
		assert(this->_arr);
		assert(pos >= 0 && pos <= _size);

		//移除某一位置的元素只需要将该位置后面的元素直接向前移位
		//因为是删除数据所以不涉及到开辟空间内存的问题

		for (size_t i = 0; i < _size - pos; i++)
		{
			//遍历从pos位置一直到最后一个字符
			//将pos位置后面的字符向前移动一位
			_arr[pos + i] = _arr[pos + 1 + i];

		}
		_size -= 1;

	}

2.9 find 函数

使用 npos 的前提是String 类中声明 npos 变量为静态变量,这样所有该类的对象都可以共享这个变量,并且可以通过类名直接访问。同时,静态成员变量的定义要在类的外面进行

// 在 String 类内部声明静态变量
class String {
public:
    static const size_t npos; // 声明静态常量成员变量
    // 其他成员函数和变量...
};

// 在类的外部定义静态变量
const size_t String::npos = -1; // 赋值为 -1,表示未找到

这样,npos 可以被所有 String 对象共享,并通过 String::npos 访问。

	const size_t String::npos = -1;
	size_t String::find(char c)
	{
		assert(this->_arr);
		for (size_t i = 0; i < _size; i++)
		{
			if (c == _arr[i])
			{
				return i;
				//这里是找到了返回下标
			}
		}
		//C++中npos代表无,所以这里没有找到返回的时无
		return npos;
	}
		size_t String::find(const char* ch, size_t pos)
	{
		//从pos位置寻找一个字符串并返回首字符的坐标
		size_t len = strlen(ch);
		//调用strstr来寻找
		const char* ptr = strstr(_arr + pos, ch);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - _arr;
		}

	}

2.10 substr 函数

	String String::substr(size_t pos, size_t len)
	{
		//从当前字符的n位置出提取len个字符形成一个新的String类对象	
		//判断如果len过大可能会多开空间
		if (len > _size - pos)
		{
			len = _size - pos;
		}
		String sub;
		sub.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += _arr[pos + i];
		}
		return sub;
	}

3 实现string类中的非成员函数

3.1 双目运算符的重载

	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 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);
	}

3.2 输出流

	ostream& operator<<(ostream& out, const String& s)
	{
		out << s.c_str() << endl;
		return out;
	}

3.3 输入流

istream& operator >> (istream& in, String& s1)
{
	//首先清空s1
	s1.clear();
	//提前开辟一块空间用来存储输入的数据
	const size_t N = 256;
	char buff[N];

	//获取字符
	char ch;
	ch = in.get();
	//作为buff的指针用来存储数据
	int i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == N - 1)
		{
			//+=会找0的位置
			buff[i] = '\0';
			s1 += buff;
			i = 0;
		}
		ch = in.get();
	}
	if (i > 0)
	{
		buff[i] = '\0';
		s1 += buff;
	}
	return in;
	
}

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

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

相关文章

FileLink内外网文件交换——致力企业高效安全文件共享

随着数字化转型的推进&#xff0c;企业之间的文件交流需求日益增加。然而&#xff0c;传统的文件传输方式往往无法满足速度和安全性的双重要求。FileLink作为一款专注于跨网文件交换的工具&#xff0c;致力于为企业提供高效、安全的文件共享解决方案。 应用场景一&#xff1a;项…

Python酷玩之旅_数据分析入门(matplotlib)

导览 前言matplotlib入门1. 简介1.1 Pairwise data1.2 Statistical distributions1.3 Gridded data1.4 Irregularly gridded data1.5 3D and volumetric data 2. 实践2.1 安装2.2 示例 结语系列回顾 前言 翻看日历&#xff0c;今年的日子已划到了2024年10月19日&#xff0c;今天…

网络空间安全之一个WH的超前沿全栈技术深入学习之路(一:渗透测试行业术语扫盲)作者——LJS

欢迎各位彦祖与热巴畅游本人专栏与博客 你的三连是我最大的动力 以下图片仅代表专栏特色 [点击箭头指向的专栏名即可闪现] 专栏跑道一 ➡️网络空间安全——全栈前沿技术持续深入学习 专栏跑道二➡️ 24 Network Security -LJS ​ ​ ​ 专栏跑道三 ➡️ MYSQL REDIS Advanc…

vue登录页面

这里写目录标题 登录业务流程表单如何进行校验自定义校验规则整个表单的统一内容校验 封装登录接口axios的二次封装整个项目api的统一管理 调用接口 登录业务流程 表单如何进行校验 ElementPlus表单组件内置了表单校验功能&#xff0c;只需要按照组件要求配置必要参数即可 1.…

【880线代】线性代数一刷错题整理

第一章 行列式 2024.8.20日 1. 2. 3. 第二章 矩阵 2024.8.23日 1. 2024.8.26日 1. 2. 3. 4. 5. 2024.8.28日 1. 2. 3. 4. 第四章 线性方程组 2024.9.13日 1. 2. 3. 4. 5. 2024.9.14日 1. 第五章 相似矩阵 2024.9.14日 1. 2024.9.15日 1. 2. 3. 4. 5. 6. 7. 2024.9.…

蚂蚁华东师范大学:从零开始学习定义和解决一般优化问题LLMOPT

&#x1f3af; 推荐指数&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f; &#x1f4d6; title&#xff1a;LLMOPT: Learning to Define and Solve General Optimization Problems from Scratch &#x1f525; code&#xff1a;https://github.com/caigaojiang/LLMOPT &am…

YOLOv11改进-卷积-空间和通道重构卷积SCConv

本篇文章将介绍一个新的改进模块——SCConv&#xff08;小波空间和通道重构卷积&#xff09;&#xff0c;并阐述如何将其应用于YOLOv11中&#xff0c;显著提升模型性能。为了减少YOLOv11模型的空间和通道维度上的冗余&#xff0c;我们引入空间和通道重构卷积。首先&#xff0c;…

如何开启华为交换机 http

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目…

pc轨迹回放制作

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;pc轨迹回放制作 主要内容&#xff1a;制作车辆轨迹操作页&#xff0c;包括查询条件、动态轨迹回放、车辆轨迹详情表单等 应用场景&#xff1a;车辆…

14.归一化——关键的数据预处理方法

引言 在人工智能&#xff08;AI&#xff09;和机器学习中&#xff0c;归一化&#xff08;Normalization&#xff09;是一个重要的预处理步骤。它的主要目的是将数据转换到某个特定的范围。归一化可以帮助模型更高效地学习和提高预测的准确性。归一化在数据预处理方法中占据核心…

Jupyter Notebook中 Save and Export Notebook As不显示选项

问题 Jupyter Notebook中 Save and Export Notebook As 不显示选项&#xff08;保存和导出没有选项&#xff09; 解决 在jupyter notebook所在环境卸载jupyter_contrib_nbextensions&#xff0c;这是我之前安装的一个扩展工具集&#xff0c;从而导致上面的问题。 pip unin…

自动化数据处理:使用Selenium与Excel打造的数据爬取管道

随着互联网信息爆炸式增长&#xff0c;获取有效数据成为决策者的重要任务。人工爬取数据不仅耗时且效率低下&#xff0c;因此自动化数据处理成为一种高效解决方案。本文将介绍如何使用Selenium与Excel实现数据爬取与处理&#xff0c;结合代理IP技术构建一个可稳定运行的数据爬取…

Nodejs使用http模块创建Web服务器接收解析RFID读卡器刷卡数据

本示例使用设备&#xff1a; https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.1d292c1buHvw58&ftt&id22173428704 Javascript源码 //引用http模块创建web服务器&#xff0c;监听指定的端口获取以GET、POST、JSON等方式上传的数据&#xff0c;并回应驱动读卡…

图像梯度-Sobel算子、scharrx算子和lapkacian算子

文章目录 一、认识什么是图像梯度和Sobel算子二、Sobel算子的具体使用三、scharrx算子与lapkacian(拉普拉斯)算子 一、认识什么是图像梯度和Sobel算子 图像的梯度是指图像亮度变化的空间导数&#xff0c;它描述了图像在不同方向上的强度变化。在图像处理和计算机视觉中&#x…

CUDA error: out of memory问题

加载模型时&#xff0c;模型也不大&#xff0c;GPU内存也完全够&#xff0c;但就是出现这个CUDA内存溢出问题。 究其原因&#xff0c;在于model.load_state_dict(torch.load(‘pretrain-model.pth’, map_locationdevice))这个代码省略了map_locationdevice 通过torch.load加载…

YOLOv11来了 | 自定义目标检测

概述 YOLO11 在 2024 年 9 月 27 日的 YOLO Vision 2024 活动中宣布&#xff1a;https://www.youtube.com/watch?vrfI5vOo3-_A。 YOLO11 是 Ultralytics YOLO 系列的最新版本&#xff0c;结合了尖端的准确性、速度和效率&#xff0c;用于目标检测、分割、分类、定向边界框和…

问题清除指南|alimama-creative/FLUX-Controlnet-Inpainting 运行注意事项

前言&#xff1a;近日验证想法需要用到inpainting技术&#xff0c;选择了https://github.com/alimama-creative/FLUX-Controlnet-Inpainting进行测试&#xff0c;在实现过程中遇到几个小问题&#xff0c;在此分享一下解决经验。 1. 下载预训练模型到本地 由于在huggingface官网…

React Agent 自定义实现

目录 背景 langchin 中的 agent langchin 中 agent 的问题 langchain 的 agent 案例 自定义 React Agent 大模型 工具定义 问题设定 问题改写&#xff0c;挖掘潜在意图 React Prompt 下一步规划 问题总结 代码 背景 之前使用过 langchian 中的 agent 去实现过一些…

WordPress监控用户行为回放插件

在数字营销的世界里&#xff0c;了解用户行为是提升用户体验和转化率的关键。nicen-replay 插件&#xff0c;它能够让您轻松回放用户在网站上的每一步操作&#xff0c;从点击到滚动&#xff0c;再到表单填写&#xff0c;每一个细节都清晰可见 nicen-replay&#xff0c;是一款可…

C#从零开始学习(类型和引用)(4)

类型 本章所有的代码都放在 https://github.com/hikinazimi/head-first-Csharp 整型 byte: 0~255sbyte: -128~127short: -32768~32767int: -2147483648~2147483647long: -9223372036854775808~9223372036854775807 以u开头的无符号整数 ushort,uint,ulong 浮点 float: (6~9…