C++:模拟实现string

前言:

        为了更好的理解string底层的原理,我们将模拟实现string类中常用的函数接口。为了与std里的string进行区分,所以用命名空间来封装一个自己的strin类。

string.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<assert.h>
#include<string>
using namespace std;
namespace manbo
{
	class string
	{
		public:
			//迭代器
			typedef char* iterator;
		
			static size_t npos;

			//构造函数
			string(const char* s);
			
			//默认构造
			string();

			//拷贝构造
			string(const string& s);

			//析构函数
			~string();
			
			//返回字符串
			const char* c_str()const ;

			char& operator[](size_t pos);
			const char& operator[](size_t pos)const ;

			const size_t size()const;

			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 = 0);
			void push_back(char ch);
			string& append(const char*s);
			string& operator+=(const char* s);
			string& operator+=(char ch);
			string& insert(size_t pos, const char* s);
			string& erase(size_t pos, size_t len = npos);

			size_t find(const char* s, size_t pos = 0) const;
			size_t find(char c, size_t pos = 0) const;

			string substr(size_t pos, size_t len =npos) const;

			void resize(size_t n, char ch = '\0');
			void clear();

			bool operator<(const string& s) const;
			bool operator<=(const string& s) const;

			bool operator>(const string& s) const;
			bool operator>=(const string& s) const;

			bool operator==(const string& s) const;
			bool operator!=(const string& s) const;

			void swap(string& s2);
			string& operator=(string temp);
			
		private:
			size_t _size;
			size_t _capaticy;
			char* _str;
	};

	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s);
}

        string里有三个私有成员变量 size,capacity,*str,分别表示字符数组的有效数据个数,容量,以及指向字符数组的指针。另外还有一个公有的char类型的指针iterator(迭代器)以及size_t类型的公有静态成员变量npos,并初始化值为-1实则会整型的最大值。

string.cpp

namespace manbo
{
	string::string(const char* s)
		:_size(strlen(s))
		, _capaticy(_size)
		, _str(new char[_size + 1])
	{
		memcpy(_str, s,_size+1);
	}

	string::string()
		:_size(0)
		, _capaticy(0)
		, _str(new char[1])
	{
		_str[0] = '\0';
	}

	string::string(const string& s)
	{
		_str = new char[s._size + 1];
		_size = s._size;
		_capaticy = s._capaticy;
		memcpy(_str,s._str,s._size+1);
	}

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

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

	char& string::operator[](size_t pos)
	{
		assert(pos < _size);
		return _str[pos];
	}

	const char& string::operator[](size_t pos)const
	{
		assert(pos < _size);
		return _str[pos];
	}

	const size_t string ::size()const
	{
		return _size;
	}


	void string::reserve(size_t n)
	{
		if (n>_capaticy)
		{
			char* temp = new char[n + 1];
			memcpy(temp, _str,_size+1);
			_capaticy = n;
			delete[] _str;
			_str = temp;
		}
	}

	void string::push_back(char ch)
	{
		if (_size==_capaticy)
		{
			reserve(_capaticy == 0 ? 4 : 2 * _capaticy);
		}
		_str[_size++] = ch;
		_str[_size] = '\0';
	}

	string& string:: append(const char*s)
	{
		size_t len = strlen(s);
		if (_size+len>_capaticy)
		{
			reserve(2 * (_size + len));
		}
		memcpy(_str + _size,s,len+1);
		_size += len;
		return *this;
	}

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

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

	string& string::insert(size_t pos, const char* s)
	{
		assert(pos >= 0 && pos <= _size);
		size_t n = strlen(s);
		size_t len = _size + n;
		_size += n;
		if (len>_capaticy)
		{
			reserve(2 *len);
		}
		size_t end = _size;
		
		while (end>=pos&&end!=npos)
		{
			_str[end + n] = _str[end];
			end--;
		}
		
		for (int i = 0; i <n ; i++)
		{
			_str[pos + i] = s[i];

		}
		return *this;
	}

	string& string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);;
		if (len==npos||pos+len>=_size)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			int sum = 0;
			size_t begin = pos + len;
			while (begin<=_size)
			{
				sum++;
				_str[pos] = _str[begin];
				pos++, begin++;
			}
			_size = pos + sum;
		}
		return *this;
	}

	size_t string::find(char c, size_t pos) const
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == c)
				return i;
		}
		return npos;
	}
	size_t string:: find(const char* s, size_t pos) const
	{
		char* temp = strstr(_str, s);
		if (!temp)
		{
			return npos;
		}
		else
		{
			return temp-_str ;
		}
	}

	string string::substr(size_t pos, size_t len) const
	{
		assert(pos < _size);
		string temp;
		if (len == npos || pos + len >= _size)
		{
			temp.reserve(_capaticy);
			for (size_t i = pos; i < _size; i++)
			{
				temp += _str[i];
			}
		}
		else
		{
			temp.reserve(len + 1);
			for (size_t i = pos; i < len+pos; i++)
			{
				temp += _str[i];
			}
		}
		return temp;
	}
	
	size_t string::npos = -1;

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

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

	// hello hello		false
	// hello*** hello	false
	// hello hello**	true
	bool string:: operator<(const string& s) const
	{
		size_t s1, s2;
		s1 = s2 = 0;
		while (s1<_size&&s2<s._size)
		{
			if (_str[s1] < s._str[s2])
				return true;
			else if (_str[s1] > s._str[s2])
				return false;
			else
			{
				s1++, s2++;
			}
		}
		if (_size>s.size())
		{
			return false;
		}else if(_size == s.size())
		{
			return false;
		}
		return true;
	}

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

	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 || *this > s;
	}

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

	void string:: swap( string& s)
	{
		std::swap(_str, s._str);
		std::swap(_capaticy, s._capaticy);
		std::swap(_size, s._size);
	}
	//实现深拷贝
	string& string::operator=(string temp)
	{
		swap(temp);
		return *this;
	}
}

ostream& manbo:: operator<<(ostream& out, const string& s)
{
	for (auto ch:s)
	{
		out << ch;
	}
	cout << endl;
	return out;
}
istream&manbo:: operator>>(istream& in, string& s)
{
	s.clear();

	char ch;
	ch = in.get();
	while (ch == '\0' || ch == '\n')
	{
		ch = in.get();
	}
	char buff[128];
	int i = 0;

	while (ch!='\0'&&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;
}

string::string(const char* s)

        以下是函数的解释:

        1.这是string类的构造函数,它接受一个const char*类型的参数s。这个参数代表一个 C语言 风格的字符串(即以 null 终止的字符数组)。

        2.初始化列表用于在构造函数体执行之前初始化类的成员变量。这里对三个成员变量进行了初始化:

                2.1strlen(s)计算字符串s的长度(不包括 null 终止符)。将这个长度赋值给_size成员变量,表示字符串的实际字符数。

                2.2_capaticy是字符串的容量。在这个实现中,容量被设置为与字符串长度相同,即_size

                2.3new char[_size + 1]分配了一块动态内存,大小为_size+1。这里+1是为了存储字符串的 null 终止符('\0')。将这块内存的指针赋值给_str,_str用于存储实际的字符串内容。

        3.构造函数体的内容是将源字符串 s 的内容复制到新分配的内存区域_str中。memcpy函数用于内存块的复制:复制从s开始的_size+1字节到_str。这里的_size+1是因为我们要复制整个字符串,包括 null 终止符。

    string::string()        

        以下是函数解释

        1.这是string类的默认构造函数。它不接受任何参数,并用于创建一个空的 string对象。    

        2.初始化列表用于在构造函数体执行之前初始化类的成员变量。这里对三个成员变量进行了初始化:

                2.1初始化_size成员变量为 0,表示字符串的长度为零。因为这是一个空字符串,所以长度为零。

                2.2初始化_capaticy成员变量为 0,表示字符串的容量也为零。

                2.3使用new char[1]分配了一块大小为 1 字节的动态内存。这块内存用于存储字符串及其终止符'\0'。

        

string::string(const string& s)

        以下是关于函数的解释

        这段代码是一个拷贝构造函数,用于创建一个string类的新对象,它是现有string对象的副本

  string::~string()      

        以下是函数的解释

        1.释放_str指针在堆上申请的空间,并将_str置为空指针

        2.将_size与_capaticy重新置为0;

const char* string::c_str()const 

         以下是函数的解释

        1.返回_str所指向的字符串。

char& string::operator[](size_t pos)

        以下是函数的解释

        1.这段代码定义了string类的一个成员函数operator[ ],用于访问字符串中的字符。这个函数是一个重载的下标运算符,允许通过下标直接访问字符串中的字符。

        2.assert是一个宏,用于在调试阶段检查条件是否为真。如果条件不为真,程序会中断并输出错误信息。这里检查pos是否在有效范围内,如果是则程序继续执行。

        3.return _str[pos];如果索引pos合法,函数返回_str[pos]。 _str是一个指向字符数组的指针,因此_str[pos] 表示数组中第 pos个位置的字符,并且返回的是引用所以可以对返回的值进行修改会影响到_str[pos]里的值。

const char& string::operator[](size_t pos)const

        以下是函数的解释

        与上个函数类似,但传入的对象以及返回的引用都被const进行修饰,此函数可以传const对象,因为这是一个权限的平移,而上个函数不能传const对象因为会产生权限的放大。并且对返回的引用只能进行读取而不能进行修改。

 const size_t string ::size()const   

   

        以下是函数的解释

        因为_size是成员变量默认是私有的所以不能直接返回,并且也不能随意修改,通过size() 函数来获取_size的值。

void string::reserve(size_t n)

                以下是函数的解释

                1.这段代码是一个成员函数reserve,用于调整string类的内部字符数组的容量,以便容纳至少n个字符的数据;

                2.判断n > _capaticy,检查n是否大于当前已分配的容量_capaticy,如果大于就扩容;

                3.new char[n + 1],在堆上分配一个新的字符数组,大小为n+1。因为额外的 1 个字符位置用于存储字符串的终止符'\0',并temp指针进行接收.

                4.memcpy(temp, _str, _size + 1)使用memcpy函数将旧字符数组_str的内容(包括终止符所以要+1)复制到新分配的内存temp中

                5.释放原_str的内容

                6.将_str重新指向新分配内存的指针temp的地址

void string::push_back(char ch)

         以下是函数的解释

        1.这个push_back函数用于向string对象的末尾添加一个字符,并处理容量的扩展。

        2.if (_size == _capaticy):检查当前字符串的大小是否等于当前容量。如果等于,说明字符串需要扩展容量,那么则调用创建好的reserve函数进行扩容,如果一开始是给空字符串那么_capacity会等于0,0乘任何数都得0,所以要加以进行判断。

        3.将字符ch添加到当前字符串的末尾,在赋值后递增_size,更新字符串的当前长度;

        4.在字符串末尾添加终止符'\0',标志着字符串的结束

    string& string:: append(const char*s)

        以下是关于函数的解释

        1.append函数用于将一个 C 风格的字符串(const char* s)追加到string对象的末尾

        2.strlen(s):计算要追加的字符串s的长度(不包括终止符 '\0'),并且赋值给len

        3.检查当前字符串的总长度(包括要追加的部分)是否超过当前容量,如果超过则调用reserve函数进行扩容

        4.拷贝字符串s从原string对象末尾('\0')的位置,并更新_size的值。

    string& string::operator+=(const char* s)

         以下是关于函数的解释

        operatir+=运算符重载,其参数为字符串,使用此operatir+=会复用append函数,在原字符串末尾追加字符串s,并返回*this(原对象)的引用。

        string& string::operator+=(char ch)

          以下是关于函数的解释

          operatir+=运算符重载,其参数为单个字符,使用此operatir+=会复用push_backd函数,在原字符串末尾追加字符ch,并返回*this(原对象)的引用。      

        string& string::insert(size_t pos, const char* s)

                以下是关于函数的解释

                1.insert函数用于在string对象中的指定位置插入字符串

                2.assert:检查插入位置pos是否在有效范围内(即不小于 0 且不大于当前字符串的_size)

                3.strlen(s)计算待插入的字符串s的长度,并将其赋值给n

                4.更新当前字符串的大小_size,将更新后的_size赋值给len,并判断是否需要扩容

                5.end:初始化为新的字符串长度_size,这是待会插入操作开始前的字符串末尾。        

                6.while循环:从字符串的末尾向插入位置pos移动字符,为腾出空间插入新字符串,_str[end + n] = _str[end]:将字符移动到新的位置并将end--.。注意,npos是一个静态成员变量,通常定义为size_t(-1),表示无效位置。在这里,它的作用是防止end变成负值,从而避免无限循环。

                7.for循环:将待插入的字符串s 的字符逐个复制到目标位置pos开始的位置

                8.返回 *this(当前对象)的引用,以支持链式调用。

string& string::erase(size_t pos, size_t len)

        以下是关于函数的介绍

        1.erase函数用于从string对象中删除指定位置的字符,删除的长度由 len 参数指定(缺省值为npos)。它的实现包括了处理不同情况的逻辑,以确保删除操作正确地更新字符串的内容和大小。

        2.assert:检查pos 是否在有效范围内(即 pos 应小于当前字符串的大小_size)

        3.if语句:处理特殊情况

                3.1如果len的值是npos(表示删除到字符串的末尾)或者pos + len超过了 _size,则删除从pos位置到字符串末尾的所有字符。

                3.2_str[pos] = '\0';:将删除位置设置为字符串结束符'\0',这会将字符串从pos位置截断,并更新字符串的大小 _size,以表示新的字符串长度。

        4.else语句:处理len小于字符串剩余长度的情况

                4.1 int sum = 0;:初始化一个计数器sum,用于记录实际移动的字符数。

                4.2 size_t begin = pos + len;:确定开始移动的字符位置。

                4.3 while (begin <= _size)遍历从begin到字符串末尾的所有字符,并将它们向前移动到删除的区域(这里字符'\0'也会向前移动,所以不需要再添加'\0'),以覆盖掉删除的字符。

        5._size = pos + sum;:更新字符串的大小_size,它应该等于新末尾的位置加上实际移动的字符数。

    

size_t string::find(char c, size_t pos) const

        以下是关于函数的介绍

        通过遍历来查找传入的字符c,如果找到就返回下标,如果没找到则返回npos。

size_t string:: find(const char* s, size_t pos) const

        以下是关于函数的介绍

        1.运用c语言的库函数strstr查找子串并用char类型的指针temp进行接受

        2.如果temp是NULL那么就没找到返回npos,如果找到了则将temp的地址-_str的首地址,最终会算出子串的第一个字符的下标位置。

    string string::substr(size_t pos, size_t len) const

                

        以下是关于函数的介绍

        1.substr函数用于生成当前字符串对象的子字符串。它的实现包括对子字符串的提取和处理

        2.assert(pos < _size);确保pos 在有效范围内。这里_size表示当前字符串的实际大小

        3.string temp;创建一个名为 temp的 string 对象,用于存储提取的子字符串。

        4.if (len == npos || pos + len >= _size),如果len是npos(表示提取从pos到字符串末尾)或者pos + len 超过了当前字符串的大小 _size,则处理这两种情况:

                4.1预先为temp对象开好_capaticy个空间,目的是避免频繁的扩容

                4.2 for循环,从pos位置开始遍历到字符串末尾,将每个字符添加到temp字符串中。

        5 当len小于等于字符串的剩余长度时,提取从pos开始的长度为len的子字符串

                5.1为temp字符串扩容,len+1是为了确保有足够的空间来存储len个字符和一个'\0'

                5.2 for循环从pos位置开始,遍历len个字符,将每个字符添加到 temp 字符串中。

注意:这里并不需要专门再结尾添加'\0',因为temp +=会去调用operator+=的运算符重载,而operator+=里又会调用push_back函数,再push_back函数里会对字符末尾进行添加'\0'的操作

void string::resize(size_t n, char ch)

        以下是关于函数的解释

        1.resize函数用于调整string对象的大小,并且可以用特定的字符填充新增的部分。

        2.如果传入的n是小于_size的那么直接将字符串_str[n]替换为'\0',并将_size更新为n;

        3.如果是n大于_size那么先将string对象的内存进行扩容,确保字符串的存储空间足够,然后从_size位置到n 位置填充字符ch。接着在_str[n]插入终结符 '\0',并更新_size为n。

void string::clear()

         以下是关于函数的解释

        clear顾名思义是清理的意思,那么直接在_str[0]的位置插入终止符'\0',并把_size更新为0,这里没有将_capacity置空是为了怕对象还要进行插入数据,防止多次扩容消耗性能。

bool string:: operator<(const string& s) const

        以下是关于函数的解释

        .operator<函数定义了一个 <操作符,用于比较当前 string 对象与另一个 string对象的大小。它主要用于ASCII比较(即按字母顺序比较)

        

其他的operator比较符重载

string& string::operator=(string temp)

        以下是关于函数的解释  

        1.接受一个string对象temp的形参

        2.在函数体内调用swap(temp),这将当前对象的内容与temp对象的内容交换。

        3.交换后,当前对象就变成了 temp 的内容,temp变成了当前对象的旧内容,并且因为temp是局部参数,在函数调用的时候自动进行析构,防止了内存泄漏

ostream& manbo:: operator<<(ostream& out, const string& s)

        以下是关于函数的解释

        1.operator<<函数是一个重载的输出流操作符,用于将string对象的内容输出到输出流

        2.范围for循环,auto ch自动推导ch的类型为s中的字符类型,循环遍历s中的每一个字符,并将其逐个输出到流out中

        3.返回流out以支持链式操作。比如:out<<s1<<s2;

istream&manbo:: operator>>(istream& in, string& s)

        以下是关于函数的解释

        1.operator>>函数是一个重载的输入流操作符,用于从输入流读取字符串,并将其存储到string对象s中。

        2.s.clear();使s变为空字符串,以确保读取的内容覆盖之前的内容.

        3.char ch;ch = in.get();使用in.get()从流中读取一个字符并存储在ch中。

        4.while循环跳过开头无用的字符如'\0'或'\n',直到读取到第一个有用的字符

        5.使用一个字符数组buff作为缓冲区来临时存储字符,i用于确认buff数组的位置。

        6.while循环, 只要字符不是'\0'和'\n',就将字符存入缓冲区buff。缓冲区buff里有 127 个字符时(留一个位置给终止符'\0'),将缓冲区内容+=到s中,并重置i。

        7.如果i>0表示buff里有剩余的字符,将这些字符+=到s对象中

        8. 返回流in的引用,以支持链式输入操作。比如:cin>>s1>>s2;

        

测试文件:

void test01()
{
	manbo::string s1;
	manbo::string s2="Hello world";
	cout << s2.c_str() << endl;
	cout << s1.c_str() << endl;
	manbo::string const s3 = "asdddd";
}

void test02()
{
	manbo::string const s2 = "Hello world";
	manbo::string::iterator it = s2.begin();
	while (it!=s2.end())
	{
		cout << *it;
		it++;
	}
	cout << endl;
}

void test03()
{
	manbo::string  s2 = "Hello world";
	s2.insert(6, "******");
	cout << s2.c_str() << endl;
	s2.erase(0);
	cout << s2.c_str() << endl;
}

void test04()
{
	manbo::string  s2 = "Hello world";
	size_t pos1 = s2.find("world");
	size_t pos2 = s2.find('e');
	cout << pos2 << endl;
}

void test05()
{

	manbo::string s1 = "https://www.bilibili.com/video/BV1fW421X7gD/?spm_id_from=333.1007.tianma.6-1-19.click";
	//解析网址,分协议   域名  资源
	size_t pos1 = s1.find("://");
	if (pos1 == manbo::string::npos)
	{
		cout << "No Find";
		exit(1);
	}
	manbo::string agreement = s1.substr(0, pos1);
	cout << "协议:" << agreement.c_str()<< endl;

	size_t pos2 = s1.find('/', pos1 + 3);
	if (pos2 == manbo::string::npos)
	{
		cout << "No Find";
		exit(1);
	}

	manbo::string domain = s1.substr(pos1 + 3, pos2 - (pos1 + 3));
	cout << "域名:" << domain.c_str()<< endl;

	manbo::string resource = s1.substr(pos2 + 1);
	cout << "资源:" << resource.c_str() << endl;
}

void test06()
{
	string s1 = "hello world";
	manbo::string s2 = "hello world";
	//s1.resize(4,'x');
	//s2.resize(4,'x');
	s1 += '\0';
	s1 += "******";

	s2 += '\0';
	s2 += "******";
	cout << s1 << endl;
	cout << s2<< endl;

	manbo::string s3;

	cin >> s3;
	cout << s3;

	cin >> s3;
	cout << s3;
}

void test07()
{
	manbo::string s1="hello";
	manbo::string s2="hello world";
	cout << (s1 < s2) << endl;
	cout << (s1 == s2) << endl;
	cout << (s1 > s2) << endl << endl;

	manbo::string s3 = "hello world";
	manbo::string s4 = "hello";
	cout << (s3 < s4) << endl;
	cout << (s3 == s4) << endl;
	cout << (s3 > s4) << endl<<endl;

	manbo::string s5 = "hello";
	manbo::string s6 = "hello";
	cout << (s5 < s6) << endl;
	cout << (s5 == s6) << endl;
	cout << (s5 > s6) << endl << endl;
}

void test08()
{
	manbo::string s1 = "hello";
	manbo::string s2 = "hello world";
	s1 = s2;
	cout << s1;

}

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值