【C++】---模拟实现string类

【C++】模拟实现string类

  • 引言:在面试中,很有可能面试官一上去就会让你手撕代码,比如写一个简单的string类,这也是一个点击率超高的考点,那么我们今天来探索一下什么是string类,以及如何模拟实现string类并且解决一些常遇到的问题。

一、String类的六大默认成员函数

1.构造函数

	String(const char* str = "")//构造函数
	{

		_size = strlen(str);		
		_str = new char[_size + 1];//  +1是因为要保存‘/0’
		strcpy(_str, str);
		_capacity = _size+1;
	}

构造函数是为我们创建了一个类的对象,给_str变量申请内存的,然后再设置_size和_capacity变量。

class String{
public:
   //成员函数
private:
   //成员变量
   char* _str;
   size_t _size;
   size_t _capacity;
};

在这里插入图片描述
2.拷贝构造函数

	String(const String& s)//拷贝构造

		:_str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;

	}

string类用到是的深拷贝,如果拷贝复制的对象中没有引用数据类型,就可以使用浅拷贝,实现方法就是遍历复制,然后返回一个新的都对象。首先需要给_str重新开辟一段空间,然后使用strcpy函数进行复制,如下图所示。
在这里插入图片描述
3.运算符重载
随着时代的发展,出现了现代写法和传统写法两种,各有优劣罢了。
先来讲一下三种较为先进简洁但有点难懂的现代写法
方法1

//现代写法1
String& operator=(String s) {
	if (this != &s) {
    	    //传入进来的参数是传值的,函数结束后会销毁,跟上面的临时变量一样
	    swap(_str, s._str);
	   	_capacity = s._capacity;
	    _size = s._size;
    }
    return *this;
}	
  • :该方法则是利用了函数传参时候是传值的原理,不需要手动去创建临时变量,直接由编译器拷贝构造一份临时变量,然后拿来用就OK。

方法2

//现代写法2
String& operator=(const String& s) {
	if (this != &s) {
	    //创建一个临时变量,这里是用的拷贝构造函数,也可以用构造函数
	    String tmp(s);
	    //然后使用swap函数交换
	    swap(_str, tmp._str);
	    //tmp在函数结束时直接销毁
	    _capacity = tmp._capacity;
	    _size = tmp._size;
    }
    
    return *this;
}
  • :该种方法实则临时创建了一个对象,运用拷贝构造函数进行拷贝构造,然后使用库函数swap函数交换临时对象和此时的赋值对象,最后把_size和_capacity赋值过去。因为临时变量在函数结束时调用析构函数就直接销毁了,所以不用再去对其对象手动释放,大大增加了程序的效率。

方法3

//现代写法3
void Swap(String& s) {
	char* tmp = s._str;
	s._str = _str;
	_str = tmp;
}
String& operator=(String& s) {
	if (this != &s) {
	    //这种方法也是先创建一个临时变量		
            String tmp(s._str);
	    //然后把两个对象的_str指针交换
	    Swap(tmp);
	    //然后更新_size和_capacity
	    _size = tmp._size;
	    _capacity = tmp._capacity;
    }
    return *this;
}
  • :这种方法显而易见,增大了代码量,自己实现了一个swap函数,直接交换两个_ste指针就完了。这就好比别人做的饭,你直接把锅端到你家里,还说这是你自己做的一样。

传统方法

//传统运算符重载
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			delete[] _str;
			_str = new char[s._capacity];
			strcpy(_str, s._str);
			_capacity = s._capacity;
			_size = s._size;
		}
		return *this;
	}

4.析构函数

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

二、增删查改

  • 增删查改是数据结构的灵魂,曾的话,很可能遇到内存(_capacity)不足的情况,我们要提前考虑好申请内存进行扩容(reserve)这一步,在扩容的时候,我们要注意的是,提前多开一个空间为’/0’留好,这也是最最容易忽略的地方。把旧的空间释放,然后让_str指向新申请开辟的内存空间。

代码如下

	void reserve(size_t n)//扩容
	{
		if (n > _capacity)
		{
		    //先申请大小为n+1的内存空间
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[]_str;
			_str = tmp;
			_capacity = n;
		}
	}

在这里插入图片描述

  • 1.头插字符、字符串。
    2.尾插字符、字符串。
    3.在指定位置插入一个字符、一个字符串。

①尾插字符

void PushBack(char c)//尾插字符
	{
		if (_size == _capacity)
		{
			reserve(_capacity * 2);//调用扩容
		}
		_str[_size++] = c;
		_str[_size] = '\0';
	}
  • :_size永远指向’\0’的位置,相当于计算着数组元素的个数,_capacity表示了数组的内存空间的大小,那么我们需要注意的是,它所指向申请内存空间的是最后一个元素的下一个位置,因为是从0开始计算内存大小的。当_size+1和_capacity相等的时候,即下图黑色的情况,则需要重新开辟一段新的空间来存放,如果要插入的元素在下图红色的位置时,只需要把_size的位置往后挪动一位就好了。

如下图
在这里插入图片描述
②尾插一个字符串

void PushBack(const char* str)//尾插字符串
	{
		size_t str_size = strlen(str);
		//扩容
		if (_size + str_size >= _capacity)
		{
			reserve(_capacity * 2);
		}

		//拷贝
		while (*str != '\0')
		{
			_str[_size++] = *str++;
		}

		_str[_size] = '\0';
	}
  • :其实尾插字符串和字符差不多,都需要先计算插入字符串的大小,判断空间是否够,不够就进行扩容,然后接在父串后边就好了。

在这里插入图片描述
③头插一个字符和字符串

//头插字符
void PushFront(char ch)
	{
	//如果要插入的字符的长度加上原有的字符长度大于已有的容量,就进行扩容
		if (_size + 1 >= _capacity)
		{
			reserve(_capacity * 2);//扩容
		}

		_str[_size + 1] = '\0';
		for (int i = _size; i > 0; --i)
		{
			_str[i] = _str[i - 1];
		}

		_str[0] = ch;
		_size += 1;
	}
//头插字符串
void String::PushFront(const char* str) 
{
     //计算出要插入的字符串的长度
	size_t str_size = strlen(str);
	//如果要插入的字符串的长度加上原有的字符串长度大于已有的容量,就进行扩容
	if (str_size + _size + 1 >= _capacity) 
	{
		_capacity = str_size + _size + 1;
		//扩容
		reserve(_capacity);
	}
	  //_capacity表示容量,总共可以有多少个元素
	 //事实上,我们可以认为_size是_str字符数组的最后一个元素了,因为它存的是'\0'
	    _str[_size + str_size + 1] = '\0';
        //所以,最后一个元素应该是在_capacity-2的位置
        size_t index_prev = _size;
        size_t index_last = _size + str_size - 1;
        while (index_prev)
         {
            _str[index_last--] = _str[--index_prev];
        }
        //index_prev当前一定是0
        while (*str != '\0') 
        {
            _str[index_prev++] = *str++;
        }
        _size += str_size;
}
  • :此处和上边尾插一样,均是先求出字符或字符串的长度,判断是否需要扩容,把当前字符或字符串往后挪动到可以插入一个字符或者str_str大小的位置,插入字符或字符串,然后给插入成功后的字符串末尾加上’\0’。(个人感觉还是很好理解的,但是前提是你得把数据结构里面的顺序表弄得明明白白)。

在这里插入图片描述
④在指定位置插入一个字符或字符串

void Insert(size_t pos, char ch)//在任意位置插入字符
	{
	   if (pos > _size)
	   {
	     cout << "pos位置有误!" << endl;
	   }
	  else
	  {
	    if (_size + 1 >= _capacity)
	      {
	         reserve(_capacity * 2);
	      }

	     _str[_size + 1] = '\0';
	     for (int i = _size; i >(int)pos; --i)
	     {
	       _str[i] = _str[i - 1];
	     }

	     _str[pos] = ch;
	     _size++;
	  }

	}

	void Insert(size_t pos, const char* str)//在指定位置插入字符串
	{
		if (pos > _size)
		{
			cout << "pos位置有误" << endl;
			return;
		}
		else
		{
			size_t str_size = strlen(str);
			
			if (_size + str_size+1 >= _capacity)//+1是因为要给'\0'留出位置
			{
				reserve(_capacity * 3);
			}
			int Last_size = _size + str_size;
			_str[Last_size--] = '\0';

			for (int i = _size-1 ; i > (int)pos; --i)//pos后的字符串向后移动str_size个位置
			{
				_str[Last_size--] = _str[i];
			}

			//插入字符串
			while (*str != '\0')
			{
				_str[pos++] = *str++;
			}
			_size += str_size;
		}
		
	}
  • : 第一步要注意判断传过来插入的位置是否正确。 第二步跟上边还是一样先判断是否需要扩容,然后把pos后边的元素都向后移动一个字符或者一个字符串大小的位置。注意循环的判断条件。

①尾删一个字符或字符串

	void PopBack()//尾删字符
	{
		if (_size == 0)
		{
			cout << "该字符串为空" << endl;
			return;
		}

		_size--;
		_str[_size] = '\0';
	}

  • :删除字符或字符串很简单,_size–即可。我们需要考虑的时怎么样删除一个指定位置的指定大小的字符串呢。首先要考虑到的是如果要删除的n个字符个数大于pos为之后的元素个数,那么就是直接全部删除pos为之后的元素。如果小于的话,我们将pos后的n个字符删除,然后将剩余元素挪到前边拼接起来,末尾加上’\0’。
//// 删除pos后的len个字符
	void String::Erase(size_t pos, size_t len)
	{
		if (pos > _size)
		{
			cout << "pos位置有误<<endl";
		}
		if (pos + len < _size)
		{
			size_t n = pos + len;
			while (n != _size)
			{
				_str[pos++] = _str[n++];
			}
		}
		_str[pos] = '\0';
		_size = pos;
	}

①查找字符


    // 返回c在string中第一次出现的位置
	size_t Find(char c, size_t pos = 0) const
	{
		if (_size == 0)
		{
			return -1;
		}

		for (size_t i = 0; i < _size; ++i)
		{
			if (_str[i] == c)
			{
				return i;
			}
		
		}
		return -1;
	}

②查找字符串

// 返回子串s在string中第一次出现的位置
	size_t Find(const char* str, size_t pos = 0) const 
	//(两个指针,字串和父串从第一个开始字符匹配,如果第一个字符匹配后,
	//开始逐个字符串匹配到最后一个,中途若有不同,字串退回开始,父串不变,继续重复操作,直至找到为止)
	{
		if (pos > _size)
		{
			cout << "pos位置有误报<<endl";
		}
		size_t index_str = 0;

		//若循环条件退出,要么找到了,要么找到结尾也没找到
		while (_str[index_str] != '\0')
		{
			if (_str[index_str] == *str)//从开头开始匹配字符
			{
				size_t  Find_index = index_str;
				size_t  str_index = 0;
				while (1)
				{
					//如果遍历完了str没有停下来,就表示找到了
					if (str[str_index] == '\0')
					{
						return index_str;
					}

					//如果不相等就结束循环
					if (_str[Find_index] != str[str_index])
					{
						break;
					}
					Find_index++;
					str_index++;
				}
				//跳出循环
			}
			//不匹配继续向前找
			index_str++;
		}

		return -1;
	}
  • :逻辑思路是,用两个指针,字串和父串从第一个开始字符匹配,如果第一个字符匹配后,开始逐个字符串匹配到最后一个,中途若有不同,字串上的指针退回初始位置重新开始,父串上的指针位置不变,继续重复匹配操作,直至找到为止,具体看代码注释。

运算符重载

  • :这一模块相比较于上边的增删查改要简单许多,有些地方需要注意,+=是给自身加等,所以此处返回值类型是Sring&。运算符重载大于号时候,若要查找的字符串先走完则为大于,若一起走完或者_str先走完则返回false。

所有代码如下
Sting.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

class String
{
public:
	typedef const char* const_iterator;
	typedef char* iterator;

	const_iterator begin() const
	{
		return _str;
	}

	iterator begin()
	{
		return _str;
	}

	iterator end()
	{
		return _str + _size;
	}

	String(const char* str = "")//构造函数
	{

		_size = strlen(str);		
		_str = new char[_size + 1];
		strcpy(_str, str);
		_capacity = _size+1;
	}


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

	String(const String& s)//拷贝构造

		:_str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;

	}
	//运算符重载
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			delete[] _str;
			_str = new char[s._capacity];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}
		return *this;
	}


	char* String_str()
	{
		return _str;
	}

	char& operator[](size_t  pos)
	{
		return _str[pos];
	}

	size_t String_size()
	{
		return _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 PushBack(char c)//尾插字符
	{
		if (_size == _capacity)
		{
			reserve(_capacity * 2);
		}


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

	void PushBack(const char* str)//尾插字符串
	{
		size_t str_size = strlen(str);
		//扩容
		if (_size + str_size >= _capacity)
		{
			reserve(_capacity * 2);
		}

		//拷贝
		while (*str != '\0')
		{
			_str[_size++] = *str++;
		}

		_str[_size] = '\0';
	}

	void PushFront(char ch)//头插字符
	{
		if (_size + 1 >= _capacity)
		{
			reserve(_capacity * 2);
		}

		_str[_size + 1] = '\0';
		for (int i = _size; i > 0; --i)
		{
			_str[i] = _str[i - 1];
		}

		_str[0] = ch;
		_size += 1;
	}
	void PopBack()//尾删
	{
		if (_size == 0)
		{
			cout << "该字符串为空" << endl;
			return;
		}

		_size--;
		_str[_size] = '\0';
	}

	void PopFront()//头删
	{
		if (_size == 0)
		{
			cout << "该字符串为空" << endl;
		}

		for (int i = 0; i<(_size+1); ++i)
		{
			_str[i] = _str[i+1];
		}
		_size--;
		_str[_size] = '\0';

	}


	void Insert(size_t pos, char ch)//在任意位置插入字符
	{
	   if (pos > _size)
	   {
	     cout << "pos位置有误!" << endl;
	   }
	  else
	  {
	    if (_size + 1 >= _capacity)
	      {
	         reserve(_capacity * 2);
	      }

	     _str[_size + 1] = '\0';
	     for (int i = _size; i >(int)pos; --i)
	     {
	       _str[i] = _str[i - 1];
	     }

	     _str[pos] = ch;
	     _size++;
	  }

	}

	void Insert(size_t pos, const char* str)//在指定位置插入字符串
	{
		if (pos > _size)
		{
			cout << "pos位置有误" << endl;
			return;
		}
		else
		{
			size_t str_size = strlen(str);
			
			if (_size + str_size+1 >= _capacity)//+1是因为要给'\0'留出位置
			{
				reserve(_capacity * 3);
			}
			int Last_size = _size + str_size;
			_str[Last_size--] = '\0';

			for (int i = _size-1 ; i > (int)pos; --i)//pos后的字符串向后移动str_size个位置
			{
				_str[Last_size--] = _str[i];
			}

			//插入字符串
			while (*str != '\0')
			{
				_str[pos++] = *str++;
			}
			_size += str_size;
		}
		
	}

	//s1.append("11111")
	void append(size_t n, char c)
	{

		for (size_t i = 0; i < n; ++i)
		{
			PushBack(c);
		}
	}

	const  String& operator+=(char ch)
	{
		PushBack(ch);
		return *this;
	}
	
	bool operator<(const String& s)
	{
		int i = 0;
		while (_str[i] == s._str[i] && i<_size)
		{
			i++;
		}
		if (i == _size)
		{
			return false;
		}
		return _str[i]>s._str[i] ? true : false;
	}

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

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

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

	bool operator==(const String& s)
	{
		int i = 0;
		while (_str[i] == s._str[i] && i < _size)
		{
			i++;
		}
		if (i == _size && s._str[i] == '\0')
		{
			return true;
		}
		else
		{
			return false;
		}
	}

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

    // 返回c在string中第一次出现的位置
	size_t Find(char c, size_t pos = 0) const
	{
		if (_size == 0)
		{
			return -1;
		}

		for (size_t i = 0; i < _size; ++i)
		{
			if (_str[i] == c)
			{
				return i;
			}
		
		}
		return -1;
	}
	
	// 返回子串s在string中第一次出现的位置
	size_t Find(const char* str, size_t pos = 0) const 
	//(两个指针,字串和父串从第一个开始字符匹配,如果第一个字符匹配后,
	//开始逐个字符串匹配到最后一个,中途若有不同,字串退回开始,父串不变,继续重复操作,直至找到为止)
	{
		if (pos > _size)
		{
			cout << "pos位置有误报<<endl";
		}
		size_t index_str = 0;

		//若循环条件退出,要么找到了,要么找到结尾也没找到
		while (_str[index_str] != '\0')
		{
			if (_str[index_str] == *str)//从开头开始匹配字符
			{
				size_t  Find_index = index_str;
				size_t  str_index = 0;
				while (1)
				{
					//如果遍历完了str没有停下来,就表示找到了
					if (str[str_index] == '\0')
					{
						return index_str;
					}

					//如果不相等就结束循环
					if (_str[Find_index] != str[str_index])
					{
						break;
					}
					Find_index++;
					str_index++;
				}
				//跳出循环
			}
			//不匹配继续向前找
			index_str++;
		}

		return -1;
	}


	//// 删除pos后的len个字符
	void String::Erase(size_t pos, size_t len)
	{
		if (pos > _size)
		{
			cout << "pos位置有误<<endl";
		}
		if (pos + len < _size)
		{
			size_t n = pos + len;
			while (n != _size)
			{
				_str[pos++] = _str[n++];
			}
		}
		_str[pos] = '\0';
		_size = pos;
	}

	void Show()
	{
		printf("%s", _str);
	}

private:

	char* _str;
	size_t _size;
	size_t _capacity;
};
void test2()
{
	String s1;
	String s2("hello bit");
	String s3(s2);
	s1 = s3;
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;
	cout << s3.c_str() << endl;
}


//测试尾插  尾删
void test1()
{
	String s1("hello");
	s1.Show();

	s1.PushBack('w');
	s1.PushBack('o');
	s1.PushBack('r');
	s1.PushBack('l');
	s1.PushBack('d');
	s1.Show();

	s1.append(5,'q');
	s1.Show();

	s1 += 'b';
	s1.Show();

	s1.PushBack("world");
	s1.Show();

     s1.PopBack();
	 s1.PopBack();
	 s1.PopBack();
	 s1.Show();
}

//测试头插   头删
void test3()
{
	String s1("ello");
	/*s1.Show();*/

	s1.PushFront('h');
	s1.Show();

	s1.PopFront();
	s1.Show();

}


//测试查找   随机插入  在指定位置插入字符串
void test4()
{
	String s1("world");
	/*s1.Show();*/

	s1.Insert(0, 'h');
	s1.Show();

	s1.Insert(4, 'd');
	s1.Show();

	size_t  pos = s1.Find('l');
	printf("%d\n", pos);

	size_t pos = s1.Find("llo");
	printf("%d\n", pos);

	s1.Insert( 2,"hel");
	s1.Show();

	s1.SubStr(3, 2);
	s1.Show();

	//s1.Erase(2, 3);
	//s1.Show();
}

int main()
{

	//Test2();
	//test1();
	//test3();
	test4();
	system("pause");
	return 0;
}
发布了45 篇原创文章 · 获赞 271 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/L19002S/article/details/102823430