【C++】string类(2)

🥳个人主页: 起名字真南
🥳个人专栏:【数据结构初阶】 【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;
	
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IDEA开发工具使用技巧积累

一、IDEA 工具设置默认使用maven的settings.xml文件 第一步&#xff1a;打开idea工具&#xff0c;选中 File ——> New Projects Setup ——> Settings for New Projects 第二步&#xff1a;先设置下自动构建项目这个选项 第三步&#xff1a;选中 Build Tools ——>…

word删除空白页 | 亲测有效

想要删掉word里面的末尾空白页&#xff0c;但是按了delete之后也没有用 找了很久找到了以下亲测有效的方法 1. 通过鼠标右键在要删除的空白页面处显示段落标记 2. 在字号输入01&#xff0c;按ENTER&#xff08;回车键&#xff09; 3.成功删除了&#xff01;&#xff01; PS…

Selenium爬虫技术:如何模拟鼠标悬停抓取动态内容

介绍 在当今数据驱动的世界中&#xff0c;抓取动态网页内容变得越来越重要&#xff0c;尤其是像抖音这样的社交平台&#xff0c;动态加载的评论等内容需要通过特定的方式来获取。传统的静态爬虫方法难以处理这些由JavaScript生成的动态内容&#xff0c;Selenium爬虫技术则是一…

基于SSM大学校医院信息管理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;校医管理&#xff0c;用户管理&#xff0c;在线问诊管理&#xff0c;线上挂号管理&#xff0c;病例记录管理&#xff0c;系统管理 校医账号功能包括&#xff1a;系统首页&#xff0c;个人中心&#xf…

用Java爬虫API,轻松获取taobao商品SKU信息

在电子商务的世界里&#xff0c;SKU&#xff08;Stock Keeping Unit&#xff0c;库存单位&#xff09;是商品管理的基础。对于商家来说&#xff0c;SKU的详细信息对于库存管理、价格策略制定、市场分析等都有着重要作用。taobao作为中国最大的电子商务平台之一&#xff0c;提供…

uniapp 微信小程序分包操作

1. 在项目根目录创建一个新的目录&#xff0c;名称为分包名称 2. 打开manifest.json&#xff0c;选择源码视图&#xff0c;加入以下代码 "optimization" : {"subPackages" : true } 3. 在pages.json中&#xff0c;pages后面添加分包代码 "subPackag…

Linux基础命令(入门)

linux 用户 root 用户 一个特殊的管理帐户 也被称为超级用户 root已接近完整的系统控制 对系统损害几乎有无限的能力 除非必要,不要登录为 root 普通&#xff08; 非特权 &#xff09; 用户权限有限 造成损害的能力比较有限 linux的哲学思想&#xff08;优点&#xf…

【Canvas与图标】制作电脑桌面图标

【成图】 制成的三种图标&#xff0c;都是120*120的。 制作时观察的大图 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>desk…

操作系统学习笔记2.1进程与线程

文章目录 概念 组成 特征状态与转换进程控制1. 进程的状态2. 进程控制块&#xff08;PCB&#xff09;3. 进程的创建与终止进程的创建进程的终止 4. 进程调度5. 进程间通信&#xff08;IPC&#xff09;6. 上下文切换 进程通信1. **管道&#xff08;Pipes&#xff09;**2. **信号…

C++ | Leetcode C++题解之第502题IPO

题目&#xff1a; 题解&#xff1a; typedef pair<int,int> pii;class Solution { public:int findMaximizedCapital(int k, int w, vector<int>& profits, vector<int>& capital) {int n profits.size();int curr 0;priority_queue<int, vect…

Prometheus 告警

github alertmanager 文档 Prometheus Alerting rules Prometheus alerting configuration 前几篇文章讲了Prometheus的监控&#xff0c;这一篇文章&#xff0c;讲通过监控指标触发告警 常用的告警方式有发邮件、调用指定接口(webhook) 等。本文讲解webhook方式&#xff0c;完…

ARL 灯塔 | ARL 灯塔 — 字典替换

关注这个工具的其它相关内容&#xff1a;自动化信息收集工具 —— ARL 灯塔使用手册 - CSDN 博客 0x01&#xff1a;ARL 字典替换 —— 理论篇 ARL&#xff08;Asset Reconnaissance Lighthouse&#xff09;在进行目标域名探测时&#xff0c;依赖的是其内置的默认字典集。然而在…

从零开始:Python与Jupyter Notebook中的数据可视化之旅

目录 **理解数据与数据可视化的基本流程****了解Python与其他可视化工具****掌握Anaconda、Jupyter Notebook的常用操作方法****原理** 环境配置1. **安装Anaconda软件&#xff0c;创建实验环境**2. **安装Jupyter Notebook**3. **创建第一个Jupyter Notebook文本**&#xff08…

nnUnet 大模型学习笔记(续):训练网络(3d_fullres)以及数据集标签的处理

目录 1. 数据集处理 1.1 实现脚本 1.2 json文件 2. 设置读取路径 2.1 设置路径 2.2 数据集转换 2.3 数据集预处理 2.4 训练&#xff08;3d_fullres) 3. 训练结果展示 关于nnUnet 数据集的处理和环境搭建&#xff0c;参考上文&#xff1a;第四章&#xff1a;nnUnet大模…

94、Python之异常:自定义异常以满足业务个性化需求

引言 前面介绍了Python中内置的异常类的继承体系&#xff0c;通常来说&#xff0c;这些异常类已经能够满足各种异常的场景需要。但是&#xff0c;有时还是需要自定义异常&#xff0c;来满足一些个性化的需求&#xff0c;以及更加可控、精细化的异常管理。 本文就来介绍一下如…

使用InternVL、LMDeploy和GTE搭建多模态RAG系统

如何将视觉大模型&#xff08;VLM&#xff09;与 多模态RAG 结合起来&#xff0c;创建服装搜索和搭配推荐&#xff01;本文展示了InternVL模型在分析服装图像和提取颜色、款式和类型等关键特征方面的强大功能。 InternVL2是国内首个在MMMU(多学科问答)上突破60的模型&#xff0…

MT-Pref数据集:包含18种语言的18k实例,涵盖多个领域。实验表明它能有效提升Tower模型在WMT23和FLORES基准测试中的翻译质量。

2024-10-10&#xff0c;由电信研究所、里斯本大学等联合创建MT-Pref数据集&#xff0c;它包含18种语言方向的18k实例&#xff0c;覆盖了2022年后的多个领域文本。通过在WMT23和FLORES基准测试上的实验&#xff0c;我们展示了使用MT-Pref数据集对Tower模型进行对齐可以显著提高翻…

【云从】十、常见安全问题与云计算的计费模式

文章目录 1、常见安全问题1.1 DDoS攻击1.2 病毒攻击1.3 木马攻击1.4 代码自身漏洞 2、安全体系3、云计算的计费模式4、常见云产品的计费方案5、云产品计费案例 1、常见安全问题 1.1 DDoS攻击 通过分布在各地的大量终端&#xff0c;同时向目标发送恶意报包&#xff0c;以占满目…

【C++贪心】1536. 排布二进制网格的最少交换次数|1880

本文涉及知识点 C贪心 决策包容性 LeetCode1536. 排布二进制网格的最少交换次数 给你一个 n x n 的二进制网格 grid&#xff0c;每一次操作中&#xff0c;你可以选择网格的 相邻两行 进行交换。 一个符合要求的网格需要满足主对角线以上的格子全部都是 0 。 请你返回使网格满…

精通CSS布局:探索经典的网页布局样式和技术

一、经典两列布局样式 1.概念 许多网站有一些特点&#xff0c;如页面顶部放置一个大的导航或广告条&#xff0c;右侧是链接或图片&#xff0c;左侧放置主要内容&#xff0c;页面底部放置版权信息等。 一般情况下&#xff0c;页面布局的两列都有固定宽度&#xff0c;而且从内容…