当前位置: 首页 > article >正文

C++—string类的模拟实现

目录

1、string构造和析构函数

1.1 构造

1.2 拷贝构造

1.3 赋值构造

1.4 析构

2、对const对象获取容器的大小和容量的处理

3、遍历访问接口的实现

3.1 下标访问

3.2 迭代器

4、改变容量(reserve&resize)

5、插入、删除、查找

5.1 push_back、append、+=

5.2 insert

5.3 erase

5.4 find

5.4 截取子串

6、流插入和流提取

参考代码


1、string构造和析构函数

1.1 构造

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

构造函数参数用缺省值,这样的话空串也能构造。

根据C++中string的特性,这里的_size和_capacity都不算'\0'。

为了兼容C语言,_str的空间要多开一个,用于储存'\0'。

1.2 拷贝构造

//现代写法中需要用到swap函数,建议一开始就实现
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;
	string tmp(s._str);
	swap(tmp);
}

这种处理方法是通过构造一个临时对象,将这个临时对象的成员与*this交换。

拷贝构造要先将_str初始化为nullptr,避免交换后tmp._str变成随机值,导致找不到这块空间,析构出错。

1.3 赋值构造

赋值构造与上面的拷贝构造类似,也是通过一个临时变量完成赋值,避免了自己给自己赋值的情况。

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

1.4 析构

析构函数没什么好说的,按照常规思路写即可

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

2、对const对象获取容器的大小和容量的处理

除了普通对象,我们还需要考虑const对象对接口的调用。

为了解决const对象的调用,我们通常为这类函数this指针加上const修饰。

size_t size()const
{
	return _size;
}

size_t capacity()const
{
	return _capacity;
}

const char* c_str()const
{
	return _str;
}

//清理字符
void clear()
{
	_size = 0;
	_str[0] = '\0';
}
//string 的判空
bool empty()const
{
	return (_size == 0);
}

3、遍历访问接口的实现

3.1 下标访问

//const对象,不能修改,仅读
const char& operator[](size_t pos) const
{
	assert(pos <= _size);
	return _str[pos];
}
//普通对象,访问和修改都需要,可读可写
char& operator[](size_t pos)
{
	assert(pos <= _size);
	return _str[pos];
}

需要重载operator[]函数,也需要注意const对象的处理。

3.2 迭代器

//迭代器
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
	return _str;
}
iterator end()
{
	return (_str + _size);
}

const_iterator begin()const
{
	return _str;
}
const_iterator end()const
{
	return (_str + _size);
}

string迭代器底层其实就是字符指针。

既然这里支持迭代器,那么肯定有人又想使用范围for了,那么这里能实现范围for吗?

这里是可以使用的,但当我们换一个迭代器名字,不用begin()和end()时。

这里报错的原因是因为范围for底层只认begin()和end(),更改名字后无法使用,也就是说如果自己实现的迭代器不叫begin()和end(),范围for就无法使用。

4、改变容量(reserve&resize)

//修改字符串长度和容量
void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

void resize(size_t n, char c = '\0')
{
	if (n > _size)
	{
		reserve(n);
		for (size_t i = _size; i < n; i++)
		{
			_str[i] = c;
		}
		_str[n] = '\0';
		_size = n;
	}
	else
	{
		_str[n] = '\0';
		_size = n;
	}
}

reserve函数的实现注意事项

1、​容量调整条件
在请求的容量大于当前容量时才扩容。若请求的容量小于或等于当前容量,直接忽略,不会缩容。

2、内存分配大小

分配的内存大小为 请求的容量 + 1,额外的一个字节用于存储末尾的\0,用来支持c_str()的实现。

resize函数的实现注意事项

1、容量不足时需要扩容

若n大于当前容量,先调用reserve扩容。

2、增大字符串长度时

将新增部分(_size到n-1)填充指定字符(默认'\0'),需要在n处写入'\0',确保字符串可以正确结束。

3、缩短字符串长度时

直接更新_size并在n处写入\0,无需修改其他数据。

5、插入、删除、查找

5.1 push_back、append、+=

//插入删除
void push_back(char ch)
{
	if (_size == _capacity)
	{
		size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newCapacity);
	}

	_str[_size] = ch;
	_size++;
	_str[_size] = '\0';
}

void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	strcpy(_str + _size, str);
	_size += len;
}

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

string& operator+=(const char* str)
{
	append(str);
	return *this;
}

5.2 insert

//在pos位置插入字符
string& insert(size_t pos, char ch)
{
	assert(pos <= _size);

	if (_size == _capacity)
	{
		size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newCapacity);
	}
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}

	_str[pos] = ch;
	_size++;
	return (*this);
}
//在pos位置插入字符串
string& insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	int end = _size;
	while (end >= (int)pos)
	{
		_str[end + len] = _str[end];
		--end;
	}

	strncpy(_str + pos, str, len);
	_size += len;

	return (*this);
}

5.3 erase

//删除在pos位置的元素
string& erase(size_t pos, size_t len)
{
	assert(pos < _size);
    //若要删除的字符数比后面剩余的字符数多
    //就把pos位置其后面的字符都删完
	if (len == npos || pos + len >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
	return (*this);
}

5.4 find
 

//查找字符在string中第一次出现的位置
size_t find(char ch, size_t pos)
{
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}

	return npos;
}

//查找字符串s在string中第一次出现的位置
size_t find(const char* str, size_t pos)
{
	const char* ptr = strstr(_str + pos, str);
	if (ptr == nullptr)
	{
		return npos;
	}
	else
	{
		return ptr - _str;
	}
}

5.4 截取子串

string substr(size_t pos, size_t len)
{
	assert(pos < _size);
	size_t end = pos + len;
	if (len == npos || pos + len >= _size)
	{
		end = _size;
	}

	string str;
	str.reserve(end - pos);
	for (size_t i = pos; i < end; i++)
	{
		str += _str[i];
	}

	return str;
}

6、流插入和流提取

//流插入和流提取
ostream& operator<<(ostream& out, const string& s)
{
	for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'结束打印
	{
		out << s[i];
	}
	return out;
}

istream& operator>>(istream& in, string& s)
{
	s.clear();
    //以128为一组,避免多次扩容
	char buff[128];
	char ch = in.get();
	int i = 0;
    //跳过空白符
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}

		ch = in.get();
	}

	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

流插入运算符 (operator<<) 的实现的注意事项

1、输出内容范围
输出字符串时,应严格按照_size确定输出长度(而非依赖\0终止符),因为自定义的string类可能包含中间的空字符(\0)。
2、直接访问数据指针
需要直接访问内部数据指针data_,并确保其以'\0'终止(为了兼容c_str)。

流提取运算符 (operator>>) 的实现注意事项

1、跳过空白字符

需要按照标准行为,默认跳过输入流的前面的空白字符。

2、动态内存管理

读取字符时需动态扩展内存(类似于vector的push_back机制)。

使用临时缓冲区逐步读取,避免频繁分配内存。

3、终止条件
遇到空白字符或流结束时停止读取。

参考代码

#include<iostream>
#include<assert.h>

using namespace std;

namespace MyString
{
	class string
	{
	public:
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//构造
		string(const char* str="")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

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

		//拷贝现代写法
		string(const string& s)
		{
			_str = nullptr;
			string tmp(s._str);
			swap(tmp);
		}

		//赋值构造
		string& operator=(string s)
		{
			swap(s);
			return (*this);
		}

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

		//给const修饰的对象写的获取_size和_capacity
		size_t size()const
		{
			return _size;
		}

		size_t capacity()const
		{
			return _capacity;
		}

		const char* c_str()const
		{
			return _str;
		}
		void clear()
		{
			_size = 0;
			_str[0] = '\0';
		}
		bool empty()const
		{
			return (_size == 0);
		}

		//遍历访问接口的实现
		//const对象,不能修改,仅读
		const char& operator[](size_t pos) const
		{
			assert(pos <= _size);
			return _str[pos];
		}
		//普通对象,访问和修改都需要,可读可写
		char& operator[](size_t pos)
		{
			assert(pos <= _size);
			return _str[pos];
		}

		//迭代器
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return (_str + _size);
		}

		const_iterator begin()const
		{
			return _str;
		}
		const_iterator end()const
		{
			return (_str + _size);
		}

		//修改字符串长度和容量
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}

		void resize(size_t n, char c = '\0')
		{
			//n > _size,扩容后用c填满容器
			if (n > _size)
			{
				reserve(n);
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = c;
				}
				_str[n] = '\0';
				_size = n;
			}
			else
			{
				//n <= _size的情况,直接把下标n的位置改为'\0',修改_size即可
				_str[n] = '\0';
				_size = n;
			}
		}

		//插入删除
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);
			}

			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}

		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
		}

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

		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);

			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);
			}
			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* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + len] = _str[end];
				--end;
			}

			strncpy(_str + pos, str, len);
			_size += len;

			return (*this);
		}

		string& erase(size_t pos, size_t len)
		{
			assert(pos < _size);

			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return (*this);
		}

		size_t find(char ch, size_t pos)
		{
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}

			return npos;
		}

		size_t find(const char* str, size_t pos)
		{
			const char* ptr = strstr(_str + pos, str);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}
		//查找子串
		string substr(size_t pos, size_t len)
		{
			assert(pos < _size);
			size_t end = pos + len;
			if (len == npos || pos + len >= _size)
			{
				end = _size;
			}

			string str;
			str.reserve(end - pos);
			for (size_t i = pos; i < end; i++)
			{
				str += _str[i];
			}

			return str;
		}

		
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		//只有const static整型类型,指针成员变量才可以在类中定义
		const static size_t npos = -1;
	};

	//流插入和流提取
	ostream& operator<<(ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'结束打印
		{
			out << s[i];
		}
		return out;
	}

	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char buff[128];
		char ch = in.get();
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}

			ch = in.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
}

http://www.kler.cn/a/575876.html

相关文章:

  • 【ThreeJS Basics 09】Debug
  • PyTorch全精度训练和混合精度训练简单对比:全精度训练一定比混合精度训练更准确吗?
  • STM32之软件SPI
  • uv:高性能 Python 包和项目管理工具使用教程
  • 【MATLAB源码-第271期】基于matlab的雷达发射回波模拟,包括匹配滤波,加窗旁瓣控制,以及MTD处理。
  • 如何实现区域灰质体积、皮层厚度、低频振幅等影像学特征的病例-对照分析差异分析
  • 基于JavaScript的PDF翻译、PDF文档解析系统开发实践,二次开发可商业化,目前包含PDF加载、放大、缩小、翻译、旋转、创建等功能
  • Java 中操作 R:深度整合与高效应用
  • 【认知管理1:从疾病中获得启发 关键字摘取】
  • git-filter-repo 清除大文件教程
  • 【Proteus仿真】【51单片机】智能家居检测与控制系统
  • mfc140u.dll是什么?当程序遭遇mfc140u.dll问题:快速恢复正常的秘诀
  • 【基础1】冒泡排序
  • 微信小程序注册组件
  • GaussDB安全配置指南:从认证到防御的全方面防护
  • Centos操作系统大全(附ISO镜像下载)
  • 【附源码】Java动漫视频网站源码【带弹幕系统】+SpringBoot+VUE+前后端分离
  • 遵义市招生管理信息系统的开发与实现
  • CentOS7安装Mysql5.7(ARM64架构)
  • 基于SNR估计的自适应码率LDPC编译码算法matlab性能仿真,对比固定码率LDPC的系统传输性能