c++领域展开第十五幕——STL(String类的模拟实现)超详细!!!!

文章目录

前言

上篇博客已经简单的介绍了string类的一些接口,并且做了一些了解

同时也刷了一些oj题目,熟练使用一些string的函数

今天我们来模拟实现 一下string类

fellow me

string类的模拟实现

首先,string类是在stl库实现之前出现的,后面的stl库的内容大部分都和string类的接口类似

string类比起以前的一些数据结构,多了很多东西

迭代器就是一方面,能够更好的耦合算法和类和对象

string类------迭代器的模拟

我们先来看迭代器以及,string类的成员变量

cpp 复制代码
class string
{
public:
	typedef char* iterator;     //迭代器    begin  end
	typedef const char* const_iterator;
	const char* c_str() const    //   返回字符常量  
	{
		return _str;
	}
	iterator begin()    
	{
		return _str;
	} 
	iterator end()
	{
		return _str + _size;
	}
	const_iterator begin() const   //  这里是const修饰的迭代器函数
	{							   //  在处理一些const修饰的对象时,如果直接访问上面的普通begin  
		return _str;				//   会引发权限的  放大 导致程序不能执行  所以这里复写了const的版本
	}
	const_iterator end() const
	{
		return _str + _size;
	}
	size_t size() const    //  返回大小   //  这里从const是修饰大小和容量不能被改变
	{										//    权限能缩小  不会放大
		return _size;
	}
	size_t capacity() const  //  返回容量
	{
		return _size;
	}
private:
	// 
	char* _str = nullptr;   //   字符串
	size_t _size = 0;		//   大小
	size_t _capacity = 0;//     容量

	const static size_t npos;   //  模拟string类里面缺省参数的   npos参数
};

string类------默认成员函数

构造函数、析构函数、拷贝构造、运算符重载

在string类标准库里面有好几个版本的构造函数

这里模拟实现了其中两个

析构函数还是比较简单的

主要是拷贝构造函数两个版本来实现

cpp 复制代码
string::string(size_t n, char ch)  //   初始化列表
	:_str(new char[n + 1])
	, _size(n)
	, _capacity(n)
{
	for (size_t i = 0; i < n; i++)
	{
		_str[i] = ch;
	}

	_str[_size] = '\0';
}
string::string(const char* str)   //  构造函数  
	:_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_size + 1];
	strcpy(_str, str);
}
string::~string()   //  析构函数
{
	delete[] _str;
	_str = nullptr;
	_size = 0;
	_capacity = 0;
}

string::string(const string& s)   //  拷贝构造   正常我们就是这样实现拷贝构造
{												//  防止在一些占用空间的参数  比如字符串
	_str = new char[s._capacity + 1];			//  调用系统的拷贝构造,导致浅拷贝,多次析构同一位置
	strcpy(_str, s._str);						//   引起程序崩溃
	_size = s._size;
	_capacity = s._capacity;
}

//  这里提供一种新的实现方法  同时也更简单明了
//  而且这里的swap函数还有其他的作用  能被复用
void string::swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
//   现代写法  直接复用
string::string(const string& s)  //  这里直接定义新的string  如果this和其交换
{										//  两种方法原理是一样的
	string tmp(s._str);
	swap(tmp);
}
// 另外还有一个  销毁函数
void clear()   //   销毁
{
	_str[0] = '\0';
	_size = 0;
}

下面就是运算符重载部分了

cpp 复制代码
// s1 = s2
// s1 = s1
void string::swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
string& string::operator=(const string& s)   //  赋值运算符重载
{
	if (this != &s)
	{
		string tmp(s._str);
		swap(tmp);   //   现代写法就是直接复用swap

		//delete[] _str;   //   传统的就是这样一步一步赋值给另外一个对象
		//_str = new char[s._capacity + 1];
		//strcpy(_str, s._str);
		//_size = s._size;
		//_capacity = s._capacity;

	}
	return *this;
}
//  处理string像访问数组一样  直接堆  [] 进行重载
char& operator[](size_t pos)   //  []重载  
{
	assert(pos < _size);
	return _str[pos];
}

const char& operator[](size_t pos) const  // const修饰  不能改变内容
{
	assert(pos < _size);
	return _str[pos];
}
//  下面就是一些判断字符串大小的函数   比较大小的函数还是比较简单的
bool string::operator==(const string& s) const
{
	return strcmp(_str, s._str) == 0;  //   这里复用了c语言里面的  strcmp  比较函数
}

bool string::operator!=(const string& s) const
{
	return !(*this == s);
}

bool string::operator<(const string& s) const
{
	return strcmp(_str, s._str) < 0;
}

bool string::operator<=(const string& s) const
{
	return *this < s || *this == s;
}

bool string::operator>(const string& s) const
{
	return !(*this <= s);
}

bool string::operator>=(const string& s) const
{
	return !(*this < s);
}

string类------常用函数接口

这里模拟实现一些string类的常用接口函数

reserve、push_back、append、insert、erase、find、substr等接口

实现string的增删改查功能

话不多说,上代码

cpp 复制代码
//  reserve函数  预留空间  
void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		//cout << "reserve:" << n << endl;
		char* tmp = new char[n + 1];   //  如果原本的空间没有到达指定大小 
		strcpy(tmp, _str);				// 扩容
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}
//  尾插函数   可以做  +=单个字符的复用函数
void string::push_back(char ch)
{
	if (_size + 1 > _capacity)   //  如果空间不够  扩容
	{
		// 扩容
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	_str[_size] = ch;   //  正常尾插  
	++_size;
	_str[_size] = '\0';  //  注意\0  结尾
}
//  append函数  在字符串尾部接入字符串  可以做+=字符串函数的复用
void string::append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		// 扩容
		size_t newCapacity = 2 * _capacity;
		if (_size + len > 2 * _capacity)
		{
			newCapacity = _size + len;
		}

		reserve(newCapacity);
	}

	strcpy(_str + _size, str);
	_size += len;
}
//  +=字符串 和  += 字符 函数的重载
string& string::operator+=(char ch)
{
	push_back(ch);
	return *this;
}
string& string::operator+=(const char* str)
{
	append(str);
	return *this;
}
//  插入函数   在pos 位置  插入 n 个  ch 字符
void string::insert(size_t pos, size_t n, char ch)
{
	assert(pos <= _size);
	assert(n > 0);
	if (_size + n > _capacity)   //  判断扩容
	{
		// 扩容
		size_t newCapacity = 2 * _capacity;
		if (_size + n > 2 * _capacity)
		{
			newCapacity = _size + n;
		}
		reserve(newCapacity);
	}
	// 挪动数据
/*	int end = _size;   //  pos为size_t  在和int比较时 int 会转为size_t  导致程序出错
	while (end >= (int)pos)
	{
		_str[end + n] = _str[end];
		--end;
	}*/
	size_t end = _size + n;    //  这样写代码更健壮   而且end不会到负数部分
	while (end > pos + n - 1)
	{
		_str[end] = _str[end - n];
		--end;
	}
	for (size_t i = 0; i < n; i++)
	{
		_str[pos + i] = ch;
	}
	_size += n;
	/*string tmp(n, ch);
	insert(pos, tmp.c_str());*/   //  这里复用insert的 在pos位置插入字符串
}
//  insert  在pos位置插入字符串
void string::insert(size_t pos, const char* str)
{
	//assert(pos <= _size);
	//size_t n = strlen(str);

	//if (_size + n > _capacity)
	//{
	//	// 扩容
	//	size_t newCapacity = 2 * _capacity;
	//	if (_size + n > 2 * _capacity)
	//	{
	//		newCapacity = _size + n;
	//	}
	//	reserve(newCapacity);
	//}
	//size_t end = _size + n;
	//while (end > pos + n - 1)
	//{
	//	_str[end] = _str[end - n];
	//	--end;
	//}   //  正常写法  
	size_t n = strlen(str); //  间接扩容
	insert(pos, n, 'x');   //  直接用前面的insert复用  先插入 n 个字符  然后再覆盖一下
	for (size_t i = 0; i < n; i++)
	{
		_str[pos + i] = str[i];//  直接覆盖   //  这样的代码就简洁很多  复用性高 好溯源
	}
}
//  删除函数  erase  指定位置删除长度为 len 的字符串
void string::erase(size_t pos, size_t len)
{
	if (len >= _size - pos)
	{
		// 删完了
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		size_t end = pos + len;  //  size_t防止int强转
		while (end <= _size)
		{
			_str[end - len] = _str[end];
			++end;
		}
		_size -= len;
	}
}
//  find查找函数  查找一个字符
size_t string::find(char ch, size_t pos)
{
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}
	return npos;
}
//  查找一个字符串
size_t string::find(const char* str, size_t pos)
{
	const char* p = strstr(_str + pos, str);   //  这里复用c里面的strstr  
	if (p == nullptr)
	{
		return npos;
	}
	else
	{
		return p - _str;
	}
}
//substr  截取字符串
string string::substr(size_t pos, size_t len)
{
	size_t leftlen = _size - pos;
	if (len > leftlen)
		len = leftlen;

	string tmp;//  构造tmp
	tmp.reserve(len);
	for (size_t i = 0; i < len; i++)
	{
		tmp += _str[pos + i];
	}
	return tmp;
}

string类------输入输出重载

剩下最后一个有点麻烦但又还好的接口

重载输入流和输出流,另外还有getline这个函数

cpp 复制代码
//   输出函数是比较简单的
ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}

	return out;
}
//   输入函数就有很多地方需要处理了
istream& operator>>(istream& in, string& s)
{
	s.clear();    // 先清除s里面的内容  防止意外
	// 输入短串,不会浪费空间
	// 输入长串,避免不断扩容
	const size_t N = 1024;   //  如果输入很长的字符串  一步一步输入的话会不断扩容
	char buff[N];				//  这里开一个字符数组存起来 后序直接 += 就行 大大减少了扩容一步到位
	int i = 0;
	char ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == N - 1)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}
		ch = in.get();
	}
	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}
//  getline  输入字符串 直到指定字符截止
istream& getline(istream& in, string& s, char delim)
{
	s.clear();
	const size_t N = 1024;
	char buff[N];
	int i = 0;
	char ch = in.get();
	while (ch != delim)   //  如果字符不是指定截止字符  就一直输入
	{						//  不管是空格还是\n
		buff[i++] = ch;
		if (i == N - 1)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}
		ch = in.get();
	}
	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

整个string类的模拟实现就差不多到这里啦

总结

今天把string类模拟实现了一遍

在模拟实现过程中,发现了很多的问题

比如哪些重复且作用相同的代码,复用问题

充分利用已有的一些东西,比如std::swap ,这个在拷贝构造和赋值重载 的时候用的很爽

还有就是一些优化,哪些地方一直构造会费时费力 ,哪些地方直接复用效果会更好而且效率高

还有就是一些简单的语法知识,比如不经意间的类型强转 过程会有意想不到的误区

总之,在模仿中一步一步优化,一步一步学习

借前人之鉴,涨己人之学识,加油

种一棵树最好的时间是十年前,其次是现在

相关推荐
重生之成了二本看我逆天改命走向巅峰24 分钟前
从0搭建Tomcat第二天:深入理解Servlet容器与反射机制
java·开发语言·笔记·学习·servlet·tomcat·idea
rkmhr_sef26 分钟前
Java进阶:Dubbo
java·开发语言·dubbo
数维学长98628 分钟前
【2025rust笔记】超详细,小白,rust基本语法
开发语言·笔记·rust
不止会JS31 分钟前
cursor使用经验分享(java后端服务开发向)
java·开发语言·经验分享·cursor
徐白117732 分钟前
Rust WebAssembly 入门教程
开发语言·rust·wasm
solomonzw1 小时前
C++ 学习(八)(模板,可变参数模板,模板专业化(完整模板专业化,部分模板专业化),类型 Traits,SFINAE(替换失败不是错误),)
c语言·开发语言·c++·学习
阳洞洞1 小时前
“nullptr“ should be used to denote the null pointer
开发语言·c++
攻城狮7号1 小时前
【第15节】C++设计模式(行为模式)-State(状态)模式
c++·设计模式·状态模式
牛马baby1 小时前
Java高频面试之集合-03
java·开发语言·面试
用手手打人1 小时前
多线程&JUC(二)
java·开发语言