【C++精华铺】10.STL string模拟实现

这篇具有很好参考价值的文章主要介绍了【C++精华铺】10.STL string模拟实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 序言

        STL(标准模板库)是一个C++标准库,其中包括一些通用的算法、容器和函数对象。STL的容器是C++ STL库的重要组成部分,它们提供了一种方便的方式来管理同类型的对象。其中,STLstring是一种常用的字符串类型。

        STLstring是一个类,它封装了字符串的操作,并提供了一组成员函数。STLstring的实现使用了动态的内存分配技术,这意味着字符串的大小可以随时改变。STLstring还提供了一些高效的成员函数,例如substr、find、replace等,这些函数可以对字符串进行快速的操作。

        STLstring的实现主要基于字符数组。字符数组是一种固定大小的数组,其中每个元素包含一个字符。STLstring使用一个字符数组来存储字符串,并通过动态的内存分配技术来管理数组的大小。当向一个空的STLstring对象中添加字符时,STLstring会自动调整数组的大小。

        STLstring还实现了一些常见的字符串操作,例如连接字符串、查找字符串、替换字符串和分割字符串等。这些操作使用了C++ STL库中的algorithm算法,可以高效地处理字符串。同时,STLstring也提供了迭代器的支持,允许用户使用STL算法来处理字符串。

2. string类的接口实现

        在实现接口之前先要给出我们的初始类,包括三个私有成员(_size,_capacity,_str),接下来我们会对这个初始类一步一步的完善(为了文章易读后续接口函数不会展示类的全貌,只会展示实现的接口函数内容,在文章的末尾会给出完整的string类代码)如下:

namespace zybjs
{
	class string
	{
	public:
	
	private:
		size_t _size;
		size_t _capacity;
		char* _str;
	};
}

2.1 构造函数

(1)默认构造和C串构造

        在STL库中将C串构造和默认构造分开实现,但是我们在模拟实现string类的时候可以将这俩个构造函数合并,这样的代码会更简洁,也方便我们自己的使用。在此之前我们先按库里面的思路走一趟:在我们给初始容量的时候即便字符串长度是0我们也不能给0,避免后续无法倍数扩容,这里我们给的是4。并且给字符串开空间的时候要比容量多一个,最后一个留给'\0'。(VS下实现思路不同,VS下给了一个16字节的字符数组_buf,如果要存储的字符串小于16字节就存放在字符数组_buf中,否则就重新开一个空间)

default (1)            string();

from c - string(2)  string(const char* s);

string()
	:_size(0)
	,_capacity(4)  //不能给0,如果给0后续无法倍数扩容 下同
{
	_str = new char[_capacity + 1]; //开空间的时候要比容量多一个字节留给'\0',因为容量的大小不算'\0',下同
    _str[0] = '\0';
}
string(const char* str)  //const类型接收右值
	:_size(strlen(str))
{
	_capacity = _size == 0 ? 4 : _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

         但是我们在自己实现的时候大可不必这样去写,我们之前学习了缺省参数,并且全缺省的构造函数也可以作为默认构造,所以我们可以对上述代码进行优化,给str一个空串作为缺省参数,这样当我们调用的时候不给实参就会默认使用空串进行构造来完成库中”string();“的功能。如下:

string(const char* str = "")   //const类型接收右值
	:_size(strlen(str))
{
	_capacity = _size == 0 ? 4 : _size;
	_str = new char[_capacity + 1];
	strcpy(_str,str);  
}

(2)拷贝构造

        string类的拷贝构造很简单,要注意的点有俩个:其一,string类涉及到空间管理,所以在拷贝的时候要深拷贝,否则会导致同一空间多次析构导致报错;其二,传参不能传值传参,要使用传引用传参,否则会导致无穷递归,

string(const string& s)
	:_size(s._size)
	,_capacity(s._capacity)
{      
	//深拷贝
	_str = new char[_capacity + 1];  //重新开空间
	strcpy(_str,s._str);     //字符序列拷贝
}

2.2 析构函数

        string类因为涉及到内存的管理,所以析构函数不能使用默认生成的析构,需要我们自己去实现析构。在实现析构的时候将_size和_capacity全部置零,然后通过delete[]释放_str就可以了。如下:

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

2.3 size()、capacity()

        size()和capacity()很多人在实现的时候都觉得很简单反而会忽略一个点:就是const对象访问的时候是否会权限放大。因为这个俩个函数仅涉及到读取,没有修改的操作,所以我们只需要实现const版本来兼容const对象和非const对象。如下

size_t size() const  //兼容const对象和非const对象
{
	return _size;
}
size_t capacity() const//兼容const对象和非const对象
{
	return _capacity;
}

2.4 c_str()

        返回指向一个数组的指针,该数组包含以 null 结尾的字符序列(即 C 字符串),该序列表示字符串对象的当前值。只需完成const版本来兼容const对象和非const对象。指针的返回值也需要是const char *类型,防止通过指针对对象进行修改。

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

2.5 operator[]

        因为要实现类似于数组的访问方式,所以我们要实现[]的重载形式。[]的特性:能够随机访问元素,对于非const对象能够修改元素。所以operator[]要实现const版本和非const版本,const版本的返回值必须是const引用。

const char& operator[](size_t pos) const
{
	assert(pos < _size&& pos >= 0);
	return _str[pos];
}
char& operator[](size_t pos)
{
	assert(pos < _size && pos >= 0);
	return _str[pos];
}

2.6 operator=

        赋值运算符的重载是对字符串对象的深拷贝,和拷贝构造的过程相同,但是‘=’的特性支持连续的赋值,所以我们在实现赋值重载的时候需要返回一个string对象来支持赋值重载的连续赋值。因为我们不涉及对传入参数的修改,所以我们需要传入一个const string& 类型。注意在赋值之前需要释放原来的空间。如下:

string& operator=(const string& s)
{
	if (s._str != _str)
	{
		_size = s._size;
		_capacity = s._capacity;
		//深拷贝
		char* _tmp = new char[_capacity + 1]; 
		strcpy(_tmp,s._str);
		//先释放原来的空间
		delete[] _str;
		_str = _tmp;
	}
	return *this;
}

测试:

zybjs::string s1("cacaca");
zybjs::string s2("dadada");
zybjs::string s3("bababa");
s3 = s2 = s1;
std::cout << s1.c_str() << std::endl;
std::cout << s2.c_str() << std::endl;
std::cout << s3.c_str() << std::endl;

【C++精华铺】10.STL string模拟实现,STL,C++,c++,开发语言,stl,数据结构

2.7 字符串比较 

        字符串比较我们通过strcmp来进行比较(strcmp(srt1,str2)),返回的值为0,字符串相同;返回的值大于0,str1>str2;返回的值小于0,str1<str2。基于此便可以实现字符串的比较函数。

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

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

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

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

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

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

2.8 迭代器实现

        string的迭代器底层是一个指针,string类的迭代器是一种用于访问字符串中字符的对象,可以通过迭代器的运算符访问字符串中的字符。迭代器为C++容器提供了一种通用的访问手段。

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;
}

2.9 reserve

        reserve()是为了给对象预留空间,如果我们提前得知字符串需要的空间我们就可以提前开好,避免频繁扩容带来的性能消耗。当reserve的参数小于string底层空间大小的时候,reserve就不会对容量进行处理。

void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* _tmp = new char[n+1];
		strcpy(_tmp, _str);
		delete[] _str;
		_str = _tmp;
		_capacity = n;
	}
}

2.10 resize

          修改字符有效个数为n,多出的空间用字符ch填充。如果n小于有效字符数,本质就是删字符操作。如果容量不够会扩容。

void resize(size_t n, char ch = '\0')
{
	//当n<有效字符数的时候本质上就是删字符
	//但是我们一般不会进行缩容,在原来的空间上将n位置的字符设置为'\0'
	if (n < _size)
	{
		_size = n;
		_str[_size] = '\0';
	}
	else if (n > _size)
	{
		if (n > _capacity)  //n>_capacity就进行扩容
		{
			reserve(n);        
		}
		size_t i = _size;
		while (i < n)       //将非有效字符初始化为ch
		{
			_str[i] = ch;
			i++;
		}
		_size = n;
		_str[_size] = '\0'; //设置终止位
	}
}

2.11 push_back、append、operator+=

        push_back尾插,append是在字符串后面追加一个字符串,实现比较简单,但要注意检查容量。

void push_back(char ch)
{
	if (_size + 1 > _capacity)
	{
		reserve(2 * _capacity);
	}
	_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;
}

        operator+=的功能可以由push_back和append的复用来实现:

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

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

2.12 insert

        insert是string类中支持pos位插入字符或者字符串的函数。其中,pos表示插入的位置,返回string的引用表示插入后的新字符串。

string& insert(size_t pos, char ch)
{
	if (_size + 1 > _capacity)
	{
		reserve(2 * _capacity);
	}
	size_t n = pos;
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		end--;
	}
	_str[pos] = ch;
	_size += 1;
	_str[_size] = '\0';

	return *this;
}

string& insert(size_t pos, const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_capacity + len);
	}
	size_t n = pos;
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end - 1 + len] = _str[end - 1];
		end--;
	}
	strncpy(_str + pos, str, len);
	_size += len;
	_str[_size] = '\0';

	return *this;
}

2.13 erase

        C++中的string类提供了一个名为erase()的成员函数,用于删除字符串中的一部分字符并修改该字符串。该函数可以接受1个或2个参数,具体取决于要删除的字符数。如果没有显式的指定删除字符数,会使用默认的npos也就是无符号整型-1来作为缺省参数(65535),就会默认删除pos位后面所有的字符。然后返回删除字符后生成的新字符。

        首先我们要定义一个和库里相同的npos:

【C++精华铺】10.STL string模拟实现,STL,C++,c++,开发语言,stl,数据结构

string& erase(size_t pos,size_t len = npos)
{
	assert(pos < _size);
	if (len >= _size - pos - 1)
	{
		_size = pos;
		_str[_size] = '\0';
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
	return *this;
}

2.14 流插入和流提取

        

	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}

	std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();        //输入之前要清空字符串
		char ch = in.get();//获取字符包括'\n'
		char buff[32];  //设置缓冲区来防止频繁扩容
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i] = ch;
			if (i == 30)
			{
				buff[31] = '\0';
				s += buff;
				i = 0;
			}
			i++;
			ch = in.get();
		}
		buff[i] = '\0';
		s += buff;
	}

3. 完整代码(均调试通过)

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<cassert>
namespace zybjs
{
	class string
	{

	public:
		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()
		//	:_size(0)
		//	,_capacity(4)  //不能给0,如果给0后续无法倍数扩容 下同
		//{
		//	_str = new char[_capacity + 1]; //开空间的时候要比容量多一个字节留给'\0',因为容量的大小不算'\0',下同
		//	_str[0] = '\0';
		//}
		//string(const char* str)
		//	:_size(strlen(str))
		//{
		//	_capacity = _size == 0 ? 4 : _size;
		//	_str = new char[_capacity + 1];
		//	strcpy(_str, str);
		//}
		string(const char* str = "")   //const类型接收右值
			:_size(strlen(str))
		{
			_capacity = _size == 0 ? 4 : _size;
			_str = new char[_capacity + 1];
			strcpy(_str,str);  
		}
		
		string(const string& s)
			:_size(s._size)
			,_capacity(s._capacity)
		{      
			//深拷贝
			_str = new char[_capacity + 1];  //重新开空间
			strcpy(_str,s._str);     //字符序列拷贝
		}
		~string()
		{
			delete[] _str;   //释放_str
			_str = nullptr;
			_size = _capacity = 0;
		}
		size_t size() const  //兼容const对象和非const对象
		{
			return _size;
		}
		size_t capacity() const//兼容const对象和非const对象
		{
			return _capacity;
		}

		const char* c_str() const 
		{
			return _str;
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size&& pos >= 0);
			return _str[pos];
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size && pos >= 0);
			return _str[pos];
		}

		string& operator=(const string& s)
		{
			if (s._str != _str)
			{
				_size = s._size;
				_capacity = s._capacity;
				//深拷贝
				char* _tmp = new char[_capacity + 1]; 
				strcpy(_tmp,s._str);
				//先释放原来的空间
				delete[] _str;
				_str = _tmp;
			}
			return *this;
		}

		//字符串比较
		bool operator>(const string& s) const
		{
			return strcmp(_str, s._str) > 0;
		}

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

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

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

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

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

		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 ch = '\0')
		{
			//当n<有效字符数的时候本质上就是删字符
			//但是我们一般不会进行缩容,在原来的空间上将n位置的字符设置为'\0'
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else if (n > _size)
			{
				if (n > _capacity)  //n>_capacity就进行扩容
				{
					reserve(n);        
				}
				size_t i = _size;
				while (i < n)       //将非有效字符初始化为ch
				{
					_str[i] = ch;
					i++;
				}
				_size = n;
				_str[_size] = '\0'; //设置终止位
			}
		}
		void push_back(char ch)
		{
			if (_size + 1 > _capacity)
			{
				reserve(2 * _capacity);
			}
			_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)
		{
			if (_size + 1 > _capacity)
			{
				reserve(2 * _capacity);
			}
			size_t n = pos;
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}
			_str[pos] = ch;
			_size += 1;
			_str[_size] = '\0';

			return *this;
		}

		string& insert(size_t pos, const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_capacity + len);
			}
			size_t n = pos;
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end - 1 + len] = _str[end - 1];
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;
			_str[_size] = '\0';

			return *this;
		}

		string& erase(size_t pos,size_t len = npos)
		{
			assert(pos < _size);
			if (len >= _size - pos - 1)
			{
				_size = pos;
				_str[_size] = '\0';
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

		void clear()
		{
			_size = 0;
			_str[_size] = '\0';
		}

	private:
		size_t _size;
		size_t _capacity;
		char* _str;

		static const size_t npos;
	};
	const size_t string::npos = -1;


	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}

	std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();        //输入之前要清空字符串
		char ch = in.get();//获取字符包括'\n'
		char buff[32];  //设置缓冲区来防止频繁扩容
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i] = ch;
			if (i == 30)
			{
				buff[31] = '\0';
				s += buff;
				i = 0;
			}
			i++;
			ch = in.get();
		}
		buff[i] = '\0';
		s += buff;
	}
}

 文章来源地址https://www.toymoban.com/news/detail-698694.html

到了这里,关于【C++精华铺】10.STL string模拟实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STL中的string类的模拟实现【C++】

    构造函数设置为缺省参数,若不传入参数,则默认构造为空字符串。字符串的初始大小和容量均设置为传入C字符串的长度(不包括’\\0’) 在模拟实现拷贝构造函数前,我们应该首先了解深浅拷贝: 浅拷贝:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一

    2024年02月15日
    浏览(41)
  • C++ STL学习之【string的模拟实现】

    ✨个人主页: Yohifo 🎉所属专栏: C++修行之路 🎊每篇一句: 图片来源 The key is to keep company only with people who uplift you, whose presence calls forth your best. 关键是只与那些提升你的人在一起,他们的存在唤起了你最好的一面。 string 本质上就是一个专注于存储字符的顺序表,使用起来

    2023年04月09日
    浏览(70)
  • 【c++】:模拟实现STL模板中的string

        文章目录 前言 一.string的模拟实现 总结   上一篇文章我们详细介绍了STL中的string的一些常用的接口,这一篇文章我们将从底层实现string类,当然我们只是实现一些重要的,经常使用的接口,并且不是完全按照STL中的string去走的。   首先我们为了防止我们写的string类与库

    2024年01月20日
    浏览(55)
  • C++ stl容器string的底层模拟实现

    目录 前言: 1.成员变量 2.构造函数与拷贝构造函数 3.析构函数 4.赋值重载 5.[]重载 6.比较关系重载 7.reserve 8.resize 9.push_back,append和重载+= 10.insert 11.erase 12.find 14.迭代器 15.流插入,流提取重载 16.swap 17.c_str 18.完整代码+测试 总结: 1.成员变量 首先注意的就是_str,不能是const类型

    2024年04月23日
    浏览(43)
  • 【C++初阶】STL详解(二)string类的模拟实现

    本专栏内容为:C++学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C++。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C++ 🚚代码仓库:小小unicorn的代码仓库🚚 🌹🌹🌹关注我带你学习编程知识 注:为了防止与标准库当中的string类产生命名冲

    2024年02月05日
    浏览(57)
  • 【C++练级之路】【Lv.6】【STL】string类的模拟实现

    欢迎各位小伙伴关注我的专栏,和我一起系统学习C语言,共同探讨和进步哦! 学习专栏 : 《进击的C++》 关于 STL容器 的学习,我会采用 模拟实现 的方式,以此来更加清楚地了解其 底层原理和整体架构 。而string类更是有100多个接口函数,所以模拟实现的时候只会调重点和

    2024年01月18日
    浏览(49)
  • 【C++】:STL中的string类的增删查改的底层模拟实现

    本篇博客仅仅实现存储字符(串)的string 同时由于C++string库设计的不合理,我仅实现一些最常见的增删查改接口 接下来给出的接口都是基于以下框架: C++string标准库中,无参构造并不是空间为0,直接置为空指针 而是开一个字节,并存放‘\\0’ C++中支持无参构造一个对象后,直

    2024年02月05日
    浏览(52)
  • 【C++】STL——string的模拟实现、常用构造函数、迭代器、运算符重载、扩容函数、增删查改

    string使用文章   这里我们 实现常用的第四个string(const char* s)和析构函数     拷贝构造函数实现:   在堆上使用new为当前对象的成员变量_str分配内存空间,大小为s._capacity + 1字节,即字符串的容量加上一个结束符\\0的空间。   我们使用深拷贝而不是浅拷贝,

    2024年02月15日
    浏览(54)
  • 带你深入理解“栈”(c语言 c++和stl Stack三个版本的模拟实现)

    目录 一.栈的概念及结构 二.栈的实现(c语言版) 2.1静态增长的栈 2.2动态增长的栈 2.3动态栈的模拟实现    1.栈的初始化   2.入栈  3.出栈 4.获取栈顶元素 5.获取栈中有效数据个数 6.检查栈是否为空 7.栈的销毁 三.C++ 版本模拟实现栈  1.C++版本的源代码 四.c语言版本的源代码

    2024年02月08日
    浏览(45)
  • yo!这里是STL::string类简单模拟实现

    目录 前言 常见接口模拟实现 默认成员函数 1.构造函数 2.析构函数 3.拷贝构造函数 4.赋值运算符重载 迭代器 简单接口 1.size() 2.c_str() 3.clear() 操作符、运算符重载 1.操作符[] 2.运算符== 3.运算符 扩容接口 1.reserve() 2.resize() 增删查改接口 1.push_back() 2.append() 3.运算符+= 4.insert() 5.

    2024年02月15日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包