string模拟

news2024/12/24 2:30:41

本章准备对string模拟进行讲解,以下是string的学习网址:

string - C++ Reference (cplusplus.com)

        string本质可以理解为储存char类型的顺序表,其中string的迭代器用一个char*就可以解决。所以string类成员变量如下:

这里用了一个命名空间是为了区分库里面的string。接下来就对需要实现的函数一一讲解。

目录

一、构造函数

二、迭代器

三、运算符重载

1.关系运算符重载

2.<<和>>的重载

四、Capacity

1.resize接口:

2.reserve接口:

五、增删查

六、源码


一、构造函数

        涉及到动态内存申请的类是不能直接用编译器提供的默认构造函数,因为它无法完成深拷贝等等问题,所以需要我们自己来完成这一部分。

1.默认构造

string(const char* st = "")
{
    size_t sz = strlen(st);
	_str = new char[sz+1];
	strcpy(_str, st);
	_size = sz;
	_capacity = sz;
}

        在写这个函数时需要注意,不能用sizeof来计算st的所占字节空间,这里sizeof只能计算到st这个变量所储存的内容占用的字节空间,而st所储存的是字符串的地址,所以占用的空间为4/8字节。 

        这里还要注意一个点strlen做计算时并没有算入'\0',所以在申请内存时需要加上1。

2.拷贝构造

string拷贝构造函数的最本质还是用了字符串拷贝函数strcpy,如下:

string(const string& st)
{
	_str = new char[st._capacity];
	strcpy(_str, st._str);
	_size = st._size;
	_capacity = st._capacity;
}

        当然还有更方便的写法,我们可以借助已写好的默认构造函数去构造一个对象然后与需要拷贝的对象交换,那么我们就得先写swap函数,而不能用库里面的swap,该方法称为现代写法在效率上并没有提升只是相当于让编译器去帮我们写,如下:

void swap(string& str)
{
	std::swap(_str, str._str);
	std::swap(_capacity, str._capacity);
	std::swap(_size, str._size);
}
string(const string& st)
{
	string sv(st._str);
	swap(sv);
}

3.赋值运算重载

该函数也一样可以用现代写法,如下:

string operator=(string st)
{
	swap(st);
	return *this;
}

        这里需要注意,因为这里用到swap直接对形参进行改变,所以这个不能加const和不能用引用传参。

4.析构函数

只要涉及到动态内存申请一定要自己写析构函数,默认生成的析构函数释放不了内存。如下:

~string()
{
	delete[] _str;
	_str = nullptr;
	_capacity = _size = 0;
}

        注意:对于一次性申请多个元素的内存空间要用delete[ ]去释放,用delete释放内存则会出现内存泄漏,或者不确定的问题。

二、迭代器

        每个容器都有迭代器,平时写代码我们并不用关心它们内部怎么实现,只要需要知道它的用法和功能,而且会用一个容器的迭代器就会用其他所有容器的迭代器,这就是封装的好处,而对于string的迭代器是相对比较简单的,因为string本质就是一个顺序表可以对数据随机访问

如下:

三、运算符重载

运算符重载方面我们依次来设计以下函数:

char operator[](const string& s);
bool operator<(const string& s);
bool operator==(const string& s);
bool operator<=(const string& s);
bool operator>(const string& s);
bool operator>=(const string& s);
bool operator!=(const string& s);
ostream& operator<<(ostream& out, string& s);
istream& operator>>(istream& put, string& s);

对于[ ]的重载我们直接返回一个_str[index]就可以解决

        char operator[](size_t index)
        {
                return _str[index];
        }

1.关系运算符重载

<符号重载的实现底层还是调用了strcmp函数,如下:
        bool operator<(const string& s)
        {
                return strcmp(_str, s._str) < 0;
        }
        bool operator==(const string& s)
        {
                return strcmp(_str, s._str) == 0;
        }

        对于剩下的关系运算符只需复用<和==运算符即可,具体实现在最后源码给出。对于+和+=重载会在下面增删查部分进行讲解。

2.<<和>>的重载

        需要注意因为每个类的成员函数的参数都隐藏着一个this指针,而且this指针处于第一个参数位置,对于<<和>>运算符都是双目运算符(即只能有两个操作数)所以重载的函数只能有两个参数,而如果把<<,>>重载成类的成员函数的话,this指针已经占用了第一个参数位置,最后在使用的时候只能这样:操作对象<<cout 或 操作对象>>cin,这样用上去是十分别扭的而且很容易出错,所以这两个重载函数就不能作为类成员,需要在类外声明或实现。

        对于<<,>>重载函数是避免不了访问类成员变量的,而类成员变量我们已经把它设置为私有,在类外是无法访问的,所以这里把这两个重载函数设为类的友元函数就可以很好的解决。

对于<<(即输出)重载比较简单,就不过多讲解,如下:

接下来是>>

        这里有一个细节,在给一个对象输入数据之前,是先要把原数据清空的,所以需要用一个clear函数,在等会我们会实现,这里先使用。

        为了处理频繁扩容可以会消耗效率的问题,我们可以先用一个数组储存用户输入的内容,然后最后一次性储入对象中,字符读取终止的条件我们可以用空格或换行,但是由于cin会自动忽略空格字符和换行符,所以可以用cin.get()读取,然后再用while循环做判断

        注意:在把所有读取到的数据储入对象后还需要手动添加'\0'

 这里的+=重载同样我们在后面再来实现。

四、Capacity

该部分我们主要实现库函数接口的以下红圈部分。

        对于size和capacity接口的实现比较简单直接返回相应的成员函数即可,empty的话返回_size==0即可,现在重点来看一看其它接口。 

1.resize接口:

        它的功能是改变string对象中的元素个数。如str.resize(n,m)表示把str字符串的元素改为n个,如n大于str的size那么多余部分用m字符填充。

void resize(size_t n,char m='?')
{
	while (_size < n)
	{
		_str[_size++] = m;
	}
    _size = n;
}

2.reserve接口:

        它的功能是预开空间,如str.reserve(n)表示把str的空间改为n个元素的空间大小,但它分有以下情况:

  • n > str._capacity,进行扩容。
  • str. _size < n < str._capacity,进行缩容。
  • n==str. _capacity,不做任何处理
  • n<str. _size,行为未定义(即没有明确的标准,具体取决于编译器,可能会把原数据缩小到n,也可能不做任何更改)

        那么这里为了方便当n<str. _size时我们就不做任何更改,注意这里_capacity的实现跟库里面保持一致并不用把'\0'占的空间算入。如下:

void reserve(size_t n)
{
	if (n < _size)
		return;
	char* st = new cha[n + 1];
	strcpy(st, _str);
	delete[] _str;
	_str = nullptr;
	_size = n;
	_capacity = n;
}

clear接口的话直接把_size置为0即可,不必要对其他数据进行改动数据。

五、增删查

该部分我们主要实现库函数接口的以下红圈部分。

        其中前三个接口核心在于push_back,可以先完成第三个接口,剩下两个接口对push_back复用即可。首先需要判断是否需要扩容,如果需要就进行扩容然后存放数据,不要忘记存放完数据后需要存入'\0'。如果函数是在类外实现的所以需要添加string::来指明类域。如下:

        swap函数在拷贝构造部分已经实现,pop_back函数的话直接把_size减减即可,现在重点来分析一下insert和erase。

        insert函数功能是在指定位置之前插入数据,erase的作用是删除指定位置的数据。string类本质是顺序表那么在做这个操作时就需要挪动数据。比如在pos位置之前插入数据那么就需要把pos位置及以后的所有数据都往后移动一位,从而把pos位置空出来填入新的位置。删除pos位置的数据就是要把pos以后的所有数据整体往前挪动一位,把pos位置覆盖

        需要注意的是这里会引发一个迭代器失效的问题,因为如果往pos位置之前插入数据,那么pos位置就不是原来的数据了,而是新插入的数据,那么也就是在无形中改变了pos的指向导致pos失效,删除数据也同理。而对于扩容并不会对pos的指向有影响因为pos表示的是数据的下标,扩容可能会换一块储存空间但是对应pos下标的数据并没变化。

        为考虑迭代器失效的问题库里面的规定是insert函数最后需要返回原pos指向的迭代器。esare函数最后要返回被删除数据的下一位数据的迭代器

实现如下:

string::iterator string::insert(size_t pos,char c)
{
	if (_size == _capacity)
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	int end = _size;
	while (end != pos - 1)
	{
		_str[end + 1] = _str[end];
		end--;
	}
	_str[pos] = c;
	_size++;
	return begin() + pos + 1;
}
string::iterator string::erase(size_t pos)
{
	int bin = pos;
	while (bin != _size)
	{
		_str[bin] = _str[bin + 1];
		bin++;
	}
	_size--;
	return begin() + pos;
}

六、源码

​
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
namespace byte
{
	class string
	{
	public:
		typedef char* iterator;
		friend ostream& operator<<(ostream& out, string& s);
		friend istream& operator>>(istream& put, string& s);
	public:
		string(const char* st = "")
		{
			size_t sz = strlen(st);
			_str = new char[sz + 1];
			strcpy(_str, st);
			_size = sz;
			_capacity = sz;
		}
		void swap(string& str)
		{
			std::swap(_str, str._str);
			std::swap(_capacity, str._capacity);
			std::swap(_size, str._size);
		}
		string(const string& st)
		{
			string sv(st._str);
			swap(sv);
		}
		string operator=(string& st)
		{
			swap(st);
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_capacity = _size = 0;
		}
		size_t size()
		{
			return _size;
		}
		size_t capacity()
		{
			return _capacity;
		}
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		iterator cbegin() const
		{
			return _str;
		}
		iterator cend() const
		{
			return _str + _size;
		}
		char operator[](size_t index)
		{
			return _str[index];
		}
		bool operator<(const string& s)
		{
			return strcmp(_str, s._str) < 0;
		}
		bool operator==(const string& s)
		{
			return strcmp(_str, s._str) == 0;
		}
		bool operator<=(const string& s)
		{
			return *this < s || *this == s;
		}
		bool operator>(const string& s)
		{
			return !(*this < s || *this == s);
		}
		bool operator>=(const string& s)
		{
			return !(*this < s);
		}
		bool operator!=(const string& s)
		{
			return !(*this == s);
		}
		void resize(size_t n, char m = '?')
		{
			if (n <= _size)
				return;
			while (_size < n)
			{
				_str[_size++] = m;
			}
		}
		void reserve(size_t n)
		{
			if (n < _size)
				return;
			char* st = new char[n + 1];
			strcpy(st, _str);
			delete[] _str;

			_str = st;
			_capacity = n;
		}
		void clear()
		{
			_size = 0;
		}
		void push_back(char c);
		string& operator+=(char c);
		void append(const char* str);
		string& operator+=(const char* str);
		iterator insert(size_t pos, char c);
		iterator erase(size_t pos);
	private:
		char* _str;
		size_t _capacity;
		size_t _size;
	};
}
namespace byte
{
	void string::push_back(char c)
	{
		if (_size == _capacity)
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		_str[_size++] = c;
		_str[_size] = '\0';
	}
	void string::append(const char* str)
	{
		reserve(_size + strlen(str) + 1);//提前开空间减少扩容带来的效率损耗
		for (int i = 0; str[i] != '\0'; i++)
		{
			push_back(str[i]);
		}
	}
	string& string::operator+=(char c)
	{
		push_back(c);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}
	ostream& operator<<(ostream& out, string& s)
	{
		for (auto vul : s)
		{
			out << vul;
		}
		return out;
	}
	istream& operator>>(istream& put, string& s)
	{
		s.clear();
		const size_t N = 256;
		char arr[N];
		char c;
		c = put.get();
		int i = 0;
		while (c != '\n' && c != ' ')
		{
			if (i != N - 1)
			{
				arr[i++] = c;
			}
			else
			{
				arr[i++] = '\0';
				s += arr;
				i = 0;
			}
			c = put.get();
		}
		if (i != 0)
		{
			arr[i] = '\0';
			s += arr;
		}
		return put;
	}
	string::iterator string::insert(size_t pos, char c)
	{
		if (_size == _capacity)
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		int end = _size;
		while (end != pos - 1)
		{
			_str[end + 1] = _str[end];
			end--;
		}
		_str[pos] = c;
		_size++;
		return begin() + pos + 1;
	}
	string::iterator string::erase(size_t pos)
	{
		int bin = pos;
		while (bin != _size)
		{
			_str[bin] = _str[bin + 1];
			bin++;
		}
		_size--;
		return begin() + pos;
	}
}
namespace byte
{
	void string_test1()
	{
		string x("123456");
		cout << x << endl;
		string k(x);
		cout << k << endl;
		string mstr;
		cin >> mstr;
		for (auto n : mstr)
		{
			cout << n;
		}
	}
	void string_test2()
	{
		//reserve
		string str("zxcvbnm");
		cout << "capacity:" << str.capacity() << endl;

		str.reserve(20);
		cout << "capacity:" << str.capacity() << endl;

		str.reserve(15);
		cout << "capacity:" << str.capacity() << endl;

		str.reserve(3);
		cout << "capacity:" << str.capacity() << endl;

		//resize
		str.resize(10, '0');
		cout << "size:" << str.size() << ' ' << str << endl;

		str.resize(3, '0');
		cout << "size:" << str.size() << ' ' << str << endl;
	}
	void string_test3()
	{
		string s("123456");

		s.push_back('x');
		cout << s << endl;

		s.append("vvv");
		cout << s << endl;

		s += "hhh";
		cout << s << endl;
	}

	void string_test4()
	{
		string s("12345678");

		s.erase(5);
		cout << s << endl;

		s.erase(2);
		cout << s << endl;

		s.insert(0,'0');
		cout << s << endl;
	}
}
int main()
{
	byte::string_test1();
	//byte::string_test2();
	//byte::string_test3();
	//byte::string_test4();
	return 0;
}

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

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

相关文章

PumpkinRaising靶机

端口扫描 目录扫描 访问80端口&#xff0c; 在页面上面发现提到了一个Jack&#xff0c;可能是一个用户名 f12查看源码 找到一个页面 拼接访问 查看源码 发现一个注释 解密 是一个目录 /scripts/spy.pcap 访问&#xff0c;自动下载了一个文件 wireshark打开流量包 找到第一个s…

Element plus部分组件样式覆盖记录

文章目录 一、el-button 样式二、Popconfirm 气泡确认框三、Popover 气泡卡片四、Checkbox 多选框五、Pagination 分页六、Form 表单七、Table 表格 一、el-button 样式 html&#xff1a; <el-button class"com_btn_style">button</el-button>样式覆盖…

端口隔离 Port isolation 华为交换机配置端口隔离

Port isolation 什么是端口隔离 如果用户想进行二层隔离&#xff0c;用户可以将不同的端口加入不同的VLAN&#xff0c;但这样会浪费有限的VLAN资源。采用端口隔离功能&#xff0c;可以实现同一VLAN内端口之间的隔离。用户只需要将端口加入到隔离组中&#xff0c;就可以实现隔离…

hyper-v连接显卡,hyper-v使用显卡能力、Hyper-V显卡虚拟化VMGpu设置

hyper-v连接显卡&#xff0c;hyper-v使用显卡能力、Hyper-V显卡虚拟化VMGpu设置 现在越来越多的软件在使用时&#xff0c;都会调用GPU获得更好的使用效果。如&#xff1a;浏览器的硬件加速模式。由于Nvidia和AMD都屏蔽了家用显卡虚拟化技术&#xff0c;常用的虚拟机也无法对显卡…

交互式散点图,快速提升你的PPT观赏性|每日科研绘图·24-08-17

一、散点图基础概念 散点图是一种非常直观且功能强大的图表&#xff0c;用于探索和展示两个数值变量之间的相关性。这种图表通过在二维平面上绘制数据点&#xff0c;使得观察者能够一眼看出变量间的潜在联系。 1-1&#xff1a;散点图的构成 X轴&#xff08;横轴&#xff09;&…

电话语音机器人优势很多

智能语音机器人近年来备受关注&#xff0c;受到很多个人或是企业的青睐&#xff0c;其广泛受到欢迎归因于智能语音机器人对电话销售提供了极大的帮助&#xff0c;其可以完美替代人工进行电销外呼服务&#xff0c;不间断的工作&#xff0c;不带有任何情绪色彩&#xff0c;且能实…

Hive:大数据时代的SQL魔法师

时间&#xff1a;2024年08月17日 作者&#xff1a;小蒋聊技术 邮箱&#xff1a;wei_wei10163.com 微信&#xff1a;wei_wei10 音频地址&#xff1a;https://xima.tv/1_ZRh54d?_sonic0 希望大家帮个忙&#xff01;如果大家有工作机会&#xff0c;希望帮小蒋内推一下&#x…

半岛体存储器常见类型简介

前言 个人邮箱&#xff1a;zhangyixu02gmail.com在学习 ESP32 的存储器结构时&#xff0c;发现 DRAM 是 Data RAM 而非 Dynamic RAM&#xff0c;IRAM 是 Instruction RAM 而非 Internal RAM 。突然发现自己对于这一块的知识还比较混乱&#xff0c;因此查阅相关资料进行学习整理…

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(一)---UnrealCV获取深度+分割图像

前言 本系列教程旨在使用UE5配置一个具备激光雷达深度摄像机的仿真小车&#xff0c;并使用通过跨平台的方式进行ROS2和UE5仿真的通讯&#xff0c;达到小车自主导航的目的。本教程使用的环境&#xff1a; ubuntu 22.04 ros2 humblewindows11 UE5.4.3python8 本系列教程将涉及以…

04-正弦波,衰减正弦波,正弦波脉冲冲串的产生

1.设置波形线宽 点击Waveforms a 2.添加Comment 3.添加正弦波 3.1先添加一个电压源 3.2增加波形窗口 3.3右键选择Advanced 3.31原始正弦波 名称含义①DC offset直流偏置②Amplitude幅值③Freq频率④Tdelay延迟⑤Theta衰减⑥Phi相位⑦Ncycles产生正弦波的个数 设置完成后&am…

数据结构与算法——BFS(广度优先搜索)

算法介绍&#xff1a; 广度优先搜索&#xff08;Breadth-First Search&#xff0c;简称BFS&#xff09;是一种遍历或搜索树和图的算法&#xff0c;也称为宽度优先搜索&#xff0c;BFS算法从图的某个节点开始&#xff0c;依次对其所有相邻节点进行探索和遍历&#xff0c;然后再…

第T8周:使用TensorFlow实现猫狗识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 文章目录 一、前期工作1.设置GPU&#xff08;如果使用的是CPU可以忽略这步&#xff09;2. 导入数据 二、数据预处理1、加载数据2、再次检查数据3. 配置数据集4…

低代码开发的崛起:机遇与挑战

近年来&#xff0c;“低代码”开发平台的迅速崛起&#xff0c;已经成为IT行业中不可忽视的趋势。这些平台承诺让非专业人士也能快速构建应用程序&#xff0c;通过减少代码编写的需求&#xff0c;大幅提高开发效率。对于许多企业而言&#xff0c;低代码开发工具成为了一个加速数…

Real-Time Open-Vocabulary Object Detection:使用Ultralytics框架进行YOLO-World目标检测

Real-Time Open-Vocabulary Object Detection&#xff1a;使用Ultralytics框架进行YOLO-World目标检测 前言相关介绍前提条件实验环境安装环境项目地址LinuxWindows 使用Ultralytics框架进行YOLO-World目标检测进行训练进行预测进行验证 扩展目标跟踪设置提示 参考文献 前言 由…

windows核心编程 第14章,虚拟内存:获取系统信息

windows核心编程 第14章&#xff0c;虚拟内存&#xff1a;获取系统信息 14,获取系统消息 文章目录 windows核心编程 第14章&#xff0c;虚拟内存&#xff1a;获取系统信息14.1 系统信息 14.1 系统信息 许多操作系统的值是根据主机而定的&#xff0c;比如页面的大小&#xff0…

无人机挂载垂直抛投灭火弹技术详解

随着城市化进程的加快&#xff0c;高层建筑、森林、化工园区等区域火灾防控难度日益增大。传统消防手段在面对复杂地形或高层火灾时&#xff0c;往往存在响应速度慢、作业难度大、人员安全风险高等问题。无人机挂载垂直抛投灭火弹技术的出现&#xff0c;为高效、安全、精准的火…

conda install vs pip install

1背景 最近使用pyinstaller打包python程序&#xff0c;启动程序的时候&#xff0c;发现了以下的报错信息 Failed to execute script "pyi_rth_pkgres" due to unhandled dll load failed while importing pyexpat后面查阅了相关文档&#xff0c;比如根据stackoverf…

Vue3+Ts封装下拉懒加载自定义指令

文件夹目录如下: 使用方式: <template><divclass="time-line"v-infinite-scroll="{loadMore: loadMoreItems,threshold: 100 // 当滚动到距离底部 100 像素时触发加载}"> </div> </template><script lang="ts" se…

7次多项式对若干个点进行拟合,并生成图像|MATLAB实现

文章目录 拟合运行结果完整代码拟合 MATLAB对数据进行拟合的意义是通过数学模型和统计方法对实际数据进行分析和预测。拟合可以帮助我们理解数据背后的规律和趋势,从而做出科学决策。 拟合的意义 揭示数据的规律 预测未来趋势 数据修正和异常检测 数据分析和模型验证 总之,…

Prometheus+Grafana保姆笔记(2)——监控Spring Boot微服务程序

Prometheus Grafana 的组合在微服务项目中可以完成许多DevOps任务&#xff0c;它们共同提供了强大的监控和可视化功能。 我们陆续介绍Prometheus Grafana 的相关用法。 上一期&#xff0c;我们介绍了PrometheusGrafana的安装&#xff0c; PrometheusGrafana保姆笔记&#…