C++模拟实现string类

本章代码gitee仓库:string模拟实现

文章目录

  • [🌸0. 搭个框子](#🌸0. 搭个框子)
  • [💮1. 成员变量](#💮1. 成员变量)
  • [🪷2. 构造函数 & 析构函数 & 拷贝构造](#🪷2. 构造函数 & 析构函数 & 拷贝构造)
  • [🌺3. 字符串访问](#🌺3. 字符串访问)
  • [🌻4. 申请空间](#🌻4. 申请空间)
  • [🌼5. 增删查改](#🌼5. 增删查改)
  • [🌳6. 字符串比较](#🌳6. 字符串比较)
  • [🌴7. 深浅拷贝](#🌴7. 深浅拷贝)
  • [🌵8. 流插入 & 流提取](#🌵8. 流插入 & 流提取)
  • [🌾9. 写时拷贝(补充知识)](#🌾9. 写时拷贝(补充知识))

🌸0. 搭个框子

要模拟实现string,我们就要定义出一个自己的类域,然后引用一些头文件(这个可以边写边加,我这边就直接全写上了)

cpp 复制代码
#include<assert.h>
#include<iostream>
#include<string>
//我不是很喜欢全部展开,用到什么展开什么就行
//using namespace std;
using std::cout;
using std::cin;
using std::endl;
using std::string;
using std::ostream;
using std::istream;
namespace mystring
{
    class string
    {
    public:
        //	方法
    private:
    	//	成员变量
	public:
		const static size_t npos;
    }
    const size_t string::npos = -1;	//size_t类型最大值
}

💮1. 成员变量

string就像一个动态顺序表,记录当前字符的数量、空间的容量、字符串

cpp 复制代码
size_t _size;	//	大小
size_t _capacity;	//容量
char* _str;	//	字符串

🪷2. 构造函数 & 析构函数 & 拷贝构造

开口就的时候,多开一个为\0留一个空间,缺省参数""代表不传参的时候,默认\0

cpp 复制代码
string(const char* str = "")	//构造函数
	:_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_capacity + 1];	//为'/0'留一个空间
	memcpy(_str, str, _size + 1);
}

string(const string& s)		//拷贝构造
{
	_str = new char[s._capacity + 1];
	memcpy(_str, s._str, s._size + 1);
	_size = s.size();
	_capacity = s._capacity;
}
~string()	//析构函数
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}

🌺3. 字符串访问

🏵c_str

代码边写边测,写一个输出函数,方便测试代码

cpp 复制代码
const char* c_str()const
{
	return _str;
}

🏵operator[]

下标访问,重载[ ],也方便查看字符串

cpp 复制代码
char& operator[](size_t pos)		//读写
{
	assert(pos < _size);
	return _str[pos];
}

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

🌻4. 申请空间

预留空间的时候,每次都多开一个,为\0准备空间

防止出现xxxx\0xxx这样的字符,strcpy字符拷贝\0拷贝不过去;所以使用直接使用memcpy内存拷贝按要求拷贝完毕

cpp 复制代码
void reserve(size_t n = 0)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		memcpy(tmp, _str, _size + 1);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

🌼5. 增删查改

🌷增加字符/字符串

增添字符串首先要先看空间还够不够,不够就扩容

🪴push_back

string不提供头插,头插效率太低了

添加完毕之后,_size自增一下,结尾注意添加\0

cpp 复制代码
void push_back(char c)
{
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 12 : _capacity * 2);
	}
	_str[_size] = c;
	_str[++_size] = '\0';
}

🪴append

尾部增添字符串

cpp 复制代码
string& append(const char* s)
{
	size_t len = strlen(s);
	if (_size + len > _capacity)
	{
		reserve(len + _size);
	}
	memcpy(_str + _size, s, len + 1);
	_size += len;
	return *this;
}

🪴operator+=

用的最多的还是+=,所以重载+=,本质上还是push_backappend,这个看着更加舒服一点

cpp 复制代码
string& operator+=(char c)
{
	push_back(c);
	return *this;
}
string& operator+=(const char* s)
{
	append(s);
	return *this;
}

🪴insert

insert指定位置插入,某种意义上也可以说是实现了头插

cpp 复制代码
string& insert(size_t pos, size_t n, char c)
{
	assert(pos <= _size);
	if (_size + n > _capacity)
	{
		reserve(n + _size);
	}
	//防止整形提升
	size_t end = _size;
	while (end >= pos && end != npos)
	{
		_str[end + n] = _str[end];
		end--;
	}
	for (size_t i = 0; i < n; i++)
	{
		_str[pos + i] = c;
	}
	_size += n;
	return *this;
}
string& insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(len + _size);
	}
	size_t end = _size;
	while (end >= pos && end != npos)
	{
		_str[end + len] = _str[end];
		end--;
	}
	for (size_t i = 0; i < len; i++)
	{
		_str[pos + i] = str[i];
	}
	_size += len;
	return *this;
}

🌷删除

cpp 复制代码
string& erase(size_t pos = 0, size_t len = npos)
{
	assert(pos <= _size);
	if (len == npos || pos + len >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		size_t end = pos + len;
		while (end <= _size)
		{
			_str[pos++] = _str[end++];
		}
		_size -= len;
	}
	return *this;
}

🌷查找 & 修改

能找到这个字符,就代表可以修改

string类也提供了resize,这个可以修改字符串

cpp 复制代码
size_t find(const char c, size_t pos = 0) const
{
	assert(pos < _size);
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == c)
			return i;
	}
	return npos;
}

size_t find(const char* s, size_t pos = 0) const
{
	assert(pos < _size);
	const char* ptr = strstr(_str + pos, s);
	if (ptr)
	{
        //子字符串在原始字符串中的索引位置
		return ptr - _str;
	}
	return npos;
}

string substr(size_t pos = 0, size_t len = npos) const
{
	assert(pos < _size);	
	size_t n = len;
	if (len == npos || pos + len > _size)
	{
		n = _size - pos;
	}
	string tmp;
	tmp.reserve(n);
	for (size_t i = pos; i <pos+ n; i++)
	{
		tmp += _str[i];
	}
	return tmp;
}

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

🌳6. 字符串比较

cpp 复制代码
bool operator<(const string& s) const
{
	bool ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
	// "123"	"123"	f
	// "1234"	"123"	f
	//	"123"	"1234"	t
	return ret == 0 ? _size < s._size : ret;
}
bool operator==(const string& s) const
{
	return _size == s._size && memcmp(_str, s._str,_size) == 0;
}
//	直接复用
bool operator<=(const string& s)const
{
	return *this < s || *this == s;
}
bool operator>(const string& s)const
{
	return !(* this <= s);
}
bool operator>=(const string& s)const
{
	return *this > s || *this == s;
}

🌴7. 深浅拷贝

重载=赋值运算符,需要实现深拷贝,如果没有写,编译器默认实现浅拷贝,这不符合要求

深拷贝要做到,将赋值的数据给目标,然后再将原目标原本指向的空间释放

cpp 复制代码
//正常思路 全部手动完成
/*string& operator= (const string& str)
{
	if (this != &str)
	{
		char* tmp = new char[str._capacity + 1];
		memcpy(tmp, str._str, str._size + 1);
		_size = str._size;
		_capacity = str._capacity;
	}
	return *this;
}*/
void swap(string& str)
{
	std::swap(_str, str._str);
	std::swap(_size, str._size);
	std::swap(_capacity, str._capacity);
}
/*string& operator= (const string& str)
{
	if (this != &str)
	{
		string tmp(str);
		swap(tmp);
	}
	return *this;
}*/
//自动化写法, 交互完毕之后,自动调用析构函数,释放原目标指向的空间
string& operator= (string tmp)
{
	swap(tmp);
	return *this;
}

🌵8. 流插入 & 流提取

最开始写的c_str,是C语言样式的,遇见\0就停止,而string类是将对象字符全部输出

cpp 复制代码
//清理对象
void clear()
{
	_str[0] = '\0';
	_size = 0;
}
cpp 复制代码
ostream& operator<<(ostream& out, const string& s)
{
	for (size_t i = 0; i < s.size(); i++)
	{
		out << s[i];
	}
	return out;
}
istream& operator>>(istream& in, string& s)
{
    //清理原始对象
	s.clear();
	char ch=in.get();
	//清理缓冲区
	while (ch == ' ' || ch == '\n')
	{
		ch = in.get();
	}
	//避免频繁扩容
	char buff[128];
	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;
}

🌾9. 写时拷贝(补充知识)

写时拷贝是一种技术优化。用于在共享资源的情况下减少不必要的数据复制。它通常用于实现像字符串、向量等数据结构的副本,以避免在副本创建时立即复制整个数据。相反,只有在原始数据被修改时,才会执行实际的复制操作。

比如说在string类赋值的时候,Vs2022采用的就是深拷贝,每次都额外开空间

gcc/g++,则采用的是写时拷贝


那以上就将string类的基本功能就实现完毕了,完整的看我仓库代码吧

本期分享就到这里咯,我们下期再见,如果还有下期的话

相关推荐
卷到起飞的数分9 分钟前
Java零基础笔记07(Java编程核心:面向对象编程 {类,static关键字})
java·开发语言·笔记
YOLO大师19 分钟前
华为OD机试 2025B卷 - 小明减肥(C++&Python&JAVA&JS&C语言)
c++·python·华为od·华为od机试·华为od2025b卷·华为机试2025b卷·华为od机试2025b卷
谁他个天昏地暗20 分钟前
Java 实现 Excel 文件对比与数据填充
java·开发语言·excel
kaikaile199536 分钟前
使用Python进行数据可视化的初学者指南
开发语言·python·信息可视化
大P哥阿豪36 分钟前
Go defer(二):从汇编的角度理解延迟调用的实现
开发语言·汇编·后端·golang
意疏1 小时前
【Python篇】PyCharm 安装与基础配置指南
开发语言·python·pycharm
看到我,请让我去学习2 小时前
OpenCV编程- (图像基础处理:噪声、滤波、直方图与边缘检测)
c语言·c++·人工智能·opencv·计算机视觉
GuokLiu2 小时前
250708-通过两块硬盘拷贝DeepSeek两个满血版模型的bash脚本
开发语言·chrome·bash
iCxhust8 小时前
c# U盘映像生成工具
开发语言·单片机·c#
yangzhi_emo9 小时前
ES6笔记2
开发语言·前端·javascript