string【2】模拟实现string类

news2024/11/17 17:42:23

string模拟实现

  • 引言(实现概述)
  • string类方法实现
    • 默认成员函数
      • 构造函数
      • 拷贝构造
      • 赋值运算符重载
      • 析构函数
    • 迭代器
      • begin
      • end
    • 容量
      • size、capacity、empty
      • reserve
      • resize
    • 访问元素
      • operator[]
    • 修改
      • insert
        • 插入字符
        • 插入字符串
      • append
      • push_back
      • operator+=
      • erase
      • clear
      • swap
    • 比较运算符重载
      • operator<
      • operator==
      • 其他
    • 查找
      • 查找字符
      • 查找字符串
    • 非成员函数
      • operator<<
      • operator>>
  • 源码概览
  • 总结

引言(实现概述)

在上一篇文章中,我们介绍了string类的使用:
戳我康string类的使用详解哦

在这里插入图片描述

在本篇文章中就要来模拟实现一下string类,以帮助我们更好的理解与使用string

在我们模拟实现的string中,要具备库中string所具有的主要接口,例如:默认成员函数、迭代器、容量、元素访问、运算符重载、非成员函数。其中只实现这些函数的常用重载形式。

我们将模拟实现的string类放在我们创建的命名空间内,以防止与库中的string发生命名冲突。在以后的STL模拟实现时,也会将其放在命名空间内。

string类的实现与之前C语言部分的顺序表类似,结合类和对象的知识,这个string类的属性有:指向堆中一块用于存放字符序列的空间的指针(_str)、字符序列的字符个数(_size)、字符序列的容量(_capacity

string类方法实现

默认成员函数

构造函数

在构造函数部分,就只实现用常量字符串构造string对象

在这个构造函数中,参数类型就是const char*,给这个参数一个空串""作为缺省值;
在函数内部首先assert判断参数是否为空指针;
然后令_size的值等于常量字符串str的长度,令_capacity等于_size的值;
然后new一块空间,将这块空间的指针赋给_str,这里需要注意的是,strlen计算字符串长度时,是以'\0'为结束标志的,所以在动态开辟空间时,需要开辟_capacity + 1个char的空间
最后使用memcpy将常量字符串中的数据拷贝到刚刚申请的空间中(这里拷贝时也需要将'\0'拷贝进去,所以要拷贝_size + 1 个字节):

    string(const char* str = "")
	{
		assert(str);
		_size = strlen(str);
		_capacity = _size;
		_str = new char[_capacity + 1];
		memcpy(_str, str, _size + 1);
	}

拷贝构造

拷贝构造是构造函数的重载,参数类型为const string&如果不是引用就会导致无穷递归调用

实现拷贝构造时,不能直接将原对象的_str直接赋值给新对象的_str,否则就会导致浅拷贝,这样在析构时,同一块空间就要被析构两遍,显然就会导致崩溃。所以我们需要新申请一块空间后将原对象的字符序列拷贝到新的空间

先将原对象的_size 与 _capacity 直接赋值给新对象
然后new一块空间,将这块空间的指针赋给_str,这里需要注意的是,在我们实现的string类中,_capacity是不包括'\0'的,所以在动态开辟空间时,需要开辟_capacity + 1个char的空间
最后,使用memcpy将原对象中的字符序列拷贝到刚刚新开辟的空间中。

这里需要注意的是,C字符串的结束标志为'\0',但string对象没有结束标志,它的数据个数就是_size的值,所以当这个字符序列的中间出现'\0'时,使用strcpy就不会拷贝'\0'后面的数据,所以这里使用包括后面都使用memcpy

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

赋值运算符重载

在实现赋值运算符重载时,也存在深浅拷贝的问题,我们当然可以像上面的拷贝构造那样申请一块空间然后完成拷贝,但是那样的写法有点麻烦,于是就有了现代版本:

现代版本的参数类型为string,而不是引用,这就使得string对象在传参时会生成一个临时对象,我们将这个临时对象与要替换的对象*this互换,就实现了将一个对象赋值到了*this,最后返回*this即可,临时对象会在函数栈帧销毁时析构。
(这里的交换需要用到swap函数,这个函数后面会实现)

    //string& operator=(const string& s);   //老版本
    string& operator=(string s)
    {
		swap(s);
		return *this;
	}

析构函数

析构函数只需使用delete[] 释放_str指向的堆中的空间即可,还可以顺带将_size_capacity置0:

    ~string()
	{
		_size = 0;
		_capacity = 0;
		delete[] _str;
	}

迭代器

前面提到过,string的迭代器就是原生指针,所以string中的 iterator就是char*const_iterator 就是const char* ,我们只需要使用typedefchar*const char* 重命名即可

    typedef char* iterator;
    typedef const char* const_iterator;

需要注意的是,因为在string类外也需要使用迭代器,所以这样的重命名应在pubilc中。

begin

begin获取的是字符序列首元素的地址,有两个重载版本,即对于非const对象返回iterator,对于const对象返回const_iterastor,首元素的地址就是_str

需要注意的是:const版本需要使用const修饰this指针

	string::iterator begin()
	{
		return _str;
	}
	string::const_iterator begin()  const
	{
		return _str;
	}

end

end获取的是字符序列最后一个元素下一个位置的地址,有两个重载版本,即对于非const对象返回iterator,对于const对象返回const_iterastor,最后一个元素下一个位置的地址就是_str + _size

	string::iterator end()
	{
		return _str + _size;
	}
	string::const_iterator end() const
	{
		return _str + _size;
	}

容量

size、capacity、empty

这三个成员函数的实现逻辑类似:
size用于获取string对象中字符序列的元素个数,返回_size即可;
capacity用于获取string对象的容量,返回_capacity即可;
empty用于判断string对象是否为空,若为空返回true,否则返回false:

	size_t size() const
	{
		return _size;
	}
	
	size_t capacity() const
	{
		return _capacity;
	}
	
	bool empty() const
	{
		if (_size == 0)
		{
			return true;
		}
		return false;
	}

reserve

reserve用于修改string对象的容量,这个函数只有一个参数n,表示要扩容到多少个char。

reserve在扩容时,当n小于当前对象的容量时,reserve会将容量调整为比n大的值,所以在模拟实现时,当n小于容量时,将不做任何事。所以先判断n是否大于_capacity

C++中使用new不能像realloc一样实现扩容,必须使用new新开一块空间(开空间时,由于_capacity没有包括'\0',所以要开n + 1个char的空间);
再将原空间中的数据拷贝到新空间,然后释放原空间;
然后使_str指向新空间;
最后将_capacity的值改为n

	void reserve(size_t n)
	{
		if (n > _capacity)			
		{
			char* newstr = new char[n + 1]{ 0 };
			memcpy(newstr, _str, _size + 1);
			delete[] _str;
			_str = newstr;
			_capacity = n;
		}
	}

resize

resize用于修改string对象中字符序列的个数,当参数n小于size就删,大于size则用指定的字符c补足。

首先判断,当n大于_size的值时,就需要扩容,复用reserve扩容至n
然后循环,将下标为_sizen - 1位置的元素改为指定的c
最后在末尾加上'\0',并更新_size的值

n小于_size的值时,直接在下标为n的位置加上'\0',并更新_size即可:

	void resize(size_t n, char c)
	{
		if (n > _size)
		{
			if (n > _capacity)
			{
				reserve(n + _size);
			}
			for (size_t i = _size; i < n; ++i)
			{
				_str[i] = c; //这里的[]是访问数组元素,并非运算符重载的调用,所以不会越界
			}
			_size = n;
			_str[_size] = '\0';
		}
		else
		{
			_size = n;
			_str[_size] = '\0';
		}
	}

访问元素

operator[]

通过重载[]可以实现像数组下标一样访问string对象中字符序列的元素,有两个重载版本,即对普通对象与const对象。函数有一个参数index即要访问元素的下标。

首先assert判断参数index是否越界;
然后返回_str[index]即可:

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

修改

insert

insert实现将一个字符或字符串插入到string对象的pos位置,实现两个重载版本,即在pos位置插入一个字符与一个字符串:

插入字符

首先assert判断pos是否越界,pos为无符号整数,所以只需要判断是否大于_size即可;
然后当_size等于_capacity时,即空间已满,需要扩容;
扩容时,当_capacity的值为0时扩容到4,不为0时二倍扩容;

然后就需要循环,将pos位置后的数据全部向后移动(需要注意的是循环的终止条件,当pos为0时,若end的初始值为_size且为endend + 1赋值,循环的终止条件就为end >= pos而pos为size_t,当end与pos比较时,会转化为size_t而永远不可能小于0,故end的初始值为_size + 1,将end + 1给前赋值,终止条件就为end > pos);
最后将c填充到pos位置,并更新_size

	string& insert(size_t pos, char c)
	{
		assert(pos <= _size);

		if (_size == _capacity)
		{
			if (_capacity != 0)
			{
				reserve(2 * _capacity);
			}
			else
			{
				reserve(4);
			}
		}
		size_t end = _size + 1;
		while (end > pos)   //pos为size_t,当end与pos比较时,会转化为size_t而永远不可能小于0.故将end+1,后给前赋值
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = c;
		++_size;
		return *this;
	}

插入字符串

插入字符串的逻辑与插入字符类似:

首先判断pos是否越界,并判断是否需要扩容,当容量小于_size + len时就需要扩容(len 为插入字符串的长度,这里可以直接调用reserve,因为reserve中会判断参数是否大于原容量);
然后将pos位置后的数据全部向后移动len个位置(依旧需要注意终止条件:必须为 end > pos + len - 1 ,若为end >= pos+len时,当在0位置插入一个空串就会导致死循环,因为无符号整型不可能小于0。当为end > pos + len - 1时,遇到上面的情况,0 - 1为 -1,对无符号整型就是一个很大的数,将直接不进入循环

然后循环将字符串中的数据拷贝到pos位置,并更新_size

	string& insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t len = strlen(str);
		reserve(len + _size);
		size_t end = _size + len;
		
		while (end > pos + len - 1)   //写成end >= pos+len就会有问题(在0位置插一个"")
		{
			_str[end] = _str[end - len];
			--end;
		}
		for (size_t i = 0; i < len; ++i)
		{
			_str[pos + i] = str[i];
		}
		_size += len;
		return *this;
	}

append

append实现在string对象后追加一个字符串:

首先判断str是否为空指针,并判断是否需要扩容(当newlenth > _capacity时即需要扩容,当然也可以交给reserve中判断);
然后使用strcpy将str中的数据拷贝到_str + _size的后面(这里不需要使用memcpy,因为这里的字符串拷贝就是按照'\0'为结束标志的);
最后更新_size

	//尾追加
    void append(const char* str)
	{
		assert(str);
		size_t newlenth = _size + strlen(str);
		if (newlenth > _capacity)
		{
			reserve(newlenth);
		}

		strcpy(_str + _size, str);
		_size = newlenth;
	}

push_back

push_back用于在string对象末尾添加一个字符

实现时首先判断是否需要扩容(与insert插入字符时的逻辑一致);
然后将c放在_str_size位置,并更新_size
最后需要手动补上'\0'

	//尾插
    void push_back(char c)
	{
		if (_size == _capacity)
		{
			if (_capacity != 0)
			{
				reserve(2 * _capacity);
			}
			else
			{
				reserve(4);
			}
		}
		
		_str[_size] = c;
		++_size;
		_str[_size] = '\0';
	}

operator+=

operator+=即在string对象的末尾追加数据,实现两个重载版本,即追加字符与追加字符串:

实现时,复用appendpush_back即可(当然,上面的append与push_back也可以借助insert实现)

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

erase

erase实现删除pos位置上的len个元素

len等于npos时,即将pos位置后全删,将pos位置改为'\0'并更新_size即可(npos为无符号整型的-1,即一个很大的数);
否则,就需要循环,将pos + len位置后的数据全部向前移动 len个位置,覆盖原数据实现删除,最后更新_size

	// 删除pos位置上的len个元素,并返回
	string& erase(size_t pos, size_t len)
	{
		if (len == npos)
		{
			_size = pos;
			_str[_size] = '\0';
		}
		for (size_t i = 0; i < _size - pos; ++i)
		{
			_str[pos + i] = _str[pos + len + i];
		}
		_size -= len;
		return *this;
	}

clear

clear即清空string对象中的数据,只需要将0位置改为'\0',并将_size更新为0即可:

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

swap

swap实现交换两个string对象

在之前的swap函数,包括算法库中的swap函数均是通过临时变量的方式交换的。但是对于string对象而言,要创建临时对象通过三次赋值来交换的话,就会产生三次深拷贝,十分影响效率

在实现交换时,其实没有必要出现深拷贝,string对象中有_str指向一块空间中存储数据,只要交换string对象中的这个存储数据的指针即可实现交换数据,所以将string的对象的属性分别实现交换即可,交换_size_capacity_str即可

    void swap(string& s)
	{
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
		std::swap(_str, s._str);
	}

比较运算符重载

在实现比较运算符重载时,我们其实只需要实现两种,即<==,其他的运算符重载通过复用这两种即可,此类函数都需要使用const修饰this以适配const对象:

operator<

operator<实现两个string对象的比较:当第一个对象小于第二个对象时,返回true,否则返回false

我们可以for循环逐字节判断,循环的终止条件为两个string对象_size的较小值;
当遇到对应字符不相等的情况时,直接返回truefalse
当循环结束,说明前面的元素都是相等的。此时,哪个对象的_size较大,则该对象较大:

	bool operator<(const string& s) const
	{
		for (size_t i = 0; i < (_size < s._size ? _size : s._size); ++i)
		{
			if (_str[i] < s._str[i])
				return true;
			if (_str[i] > s._str[i])
				return false;
		}
		if (_size < s._size)
			return true;
		else
			return false;
	}

operator==

operator==用于判断两个string对象是否相等,相等返回true,否则返回false

当两个string对象的_size不同时,直接返回false
然后循环遍历两个对象,遇到对应位置不相同的,直接返回false
最后,出循环说明均相等,返回true

	bool operator==(const string& s) const
	{
		if (_size != s._size)
			return false;

		for (size_t i = 0; i < _size; ++i)
		{
			if (_str[i] != s._str[i])
				return false;
		}
		return true;
	}

其他

其他函数,根据比较的逻辑复用即可:

	bool operator<=(const string& s) const
	{
		if (*this < s || *this == s)
			return true;
		else
			return false;
	}
	bool operator>(const string& s) const
	{
		if (!(*this <= s))
			return true;
		else
			return false;
	}
	bool operator>=(const string& s) const
	{
		if (!(*this < s))
			return true;
		else
			return false;
	}
	bool operator!=(const string& s) const
	{
		if (!(*this == s))
			return true;
		else
			return false;
	}

查找

find用于在string对象中查找是否存在某字符或某字符串,若存在就返回其第一次出现的位置,否则返回npos,有两个重载版本,即查找字符与查找字符串:

查找字符

查找字符时,即使用循环从pos位置开始遍历string对象中的数据,当遇到与c相等的字符时,就返回该位置。若出循环,就表示没有找到,返回npos

	// 返回c在string中第一次出现的位置
	size_t find(char c, size_t pos) const
	{
		assert(pos < _size);
		for (size_t i = pos; i < _size; ++i)
		{
			if (_str[i] == c)
			{
				return i;
			}
		}
		return npos;
	}

查找字符串

在string对象中查找字符串时,可以使用之前C语言时学过的strstr函数,用于查找字串。(当第2个参数为第1个参数的子串时,返回在其中的第一个位置的地址,否则返回空指针)

首先assert判断s是否为空指针,以及pos是否越界;
然后调用strstr,第一个参数为_str,第二个参数为s,并创建一个指针pchar来接收返回值;

pchar为空时,返回npos,当pchar - _str的值不小于pos时,返回该差,否则返回npos

	// 返回子串s在string中第一次出现的位置
	size_t find(const char* s, size_t pos) const
	{
		assert(s);
		assert(pos < _size);
		char* pchar = strstr(_str, s);
		if (pchar == nullptr)
		{
			return npos;
		}
		if ((size_t)(pchar - _str) >= pos)
		{
			return pchar - _str;
		}
		return npos;
	}

非成员函数

非成员函数中只实现流插入与流提取运算符的重载(operator<<operator>>):

在之前的日期类实现中,我们使用友元函数,实现在这两个函数中可以访问对象的属性。但在string类中,由于之前实现过访问元素的operator[],所以可以不使用友元就可以实现在这两个函数中访问string对象的元素

operator<<

operator<<中, 我们只需要将string对象s中的元素依次流入到ostream的对象_cout即可:

	ostream& operator<<(ostream& _cout, const string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			_cout << s[i];
		}
		return _cout;
	}

operator>>

在向内存中输入数据时,我们当然可以逐字符的+=,但是这样会造成多次的扩容而影响效率。

我们可以直接创建一个128字节的数组来转存数据,当在这个数组中存满后再将这个数组中的数据+=到string对象中,然后清空数据继续接收数据,等到全部接收完毕后,将其中剩余的元素再**+=**到string对象后即可:

	istream& operator>>(istream& _cin, string& s)
	{
		s.clear();
		char ch = _cin.get();
		//清除缓冲区中的空格与换行
		while (ch == ' ' || ch == '\n')
		{
			ch = _cin.get();
		}

		//定义一个128的字符数组
		char temp[128] = { 0 };
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			if (i == 127)
			{
				s += temp;
				i = 0;
			}
			temp[i] = ch;
			++i;

			ch = _cin.get();
		}

		//如果i>0 即temp中还有数据,将其转存即可
		if (i > 0)
		{
			temp[i] = '\0';
			s += temp;
		}

		return _cin;
	}

源码概览

#include<iostream>
#include<cassert>
using namespace std;

namespace qqq
{
    class string
    {
    public:
        typedef char* iterator;
        typedef const char* const_iterator;

    public:
        string(const char* str = "")
		{
			assert(str);
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];

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

			memcpy(_str, s._str, s._size + 1);
		}
        //string& operator=(const string& s);   //老版本

        string& operator=(string s)
		{
			swap(s);
			return *this;
		}
        ~string()
		{
			_size = 0;
			_capacity = 0;
			delete[] _str;
		}

        //
        // iterator
		string::iterator begin()
		{
			return _str;
		}
		string::iterator end()
		{
			return _str + _size;
		}
		string::const_iterator begin()  const
		{
			return _str;
		}
		string::const_iterator end() const
		{
			return _str + _size;
		}

        /
        // modify
        // 在pos位置上插入字符c/字符串str,并返回
		string& insert(size_t pos, char c)
		{
			assert(pos <= _size);

			if (_size == _capacity)
			{
				if (_capacity != 0)
				{
					reserve(2 * _capacity);
				}
				else
				{
					reserve(4);
				}
			}

			size_t end = _size + 1;
			while (end > pos)   //pos为size_t,当end与pos比较时,会转化为size_t而永远不可能小于0.故将end+1,后给前赋值
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = c;
			++_size;
			return *this;
		}
		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);

			size_t len = strlen(str);
			reserve(len + _size);

			size_t end = _size + len;
			while (end > pos + len - 1)   //写成end >= pos+len就会有问题(在0位置插一个"")
			{
				_str[end] = _str[end - len];
				--end;
			}
			for (size_t i = 0; i < len; ++i)
			{
				_str[pos + i] = str[i];
			}

			_size += len;
			return *this;
		}
		// 删除pos位置上的元素,并返回
		string& erase(size_t pos, size_t len)
		{
			if (len == npos)
			{
				_size = pos;
				_str[_size] = '\0';
			}
			for (size_t i = 0; i < _size - pos; ++i)
			{
				_str[pos + i] = _str[pos + len + i];
			}
			_size -= len;
			return *this;
		}
		//尾插
        void push_back(char c)
		{
			if (_size == _capacity)
			{
				if (_capacity != 0)
				{
					reserve(2 * _capacity);
				}
				else
				{
					reserve(4);
				}
			}

			_str[_size] = c;
			++_size;
			_str[_size] = '\0';
		}
        string& operator+=(char c)
		{
			push_back(c);
			return *this;
		}
		//尾追加
        void append(const char* str)
		{
			assert(str);
			size_t newlenth = _size + strlen(str);
			if (newlenth > _capacity)
			{
				reserve(newlenth);
			}

			strcpy(_str + _size, str);
			_size = newlenth;
		}
        string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
        void clear()
		{
			_size = 0;
			_str[_size] = '\0';
		}
        void swap(string& s)
		{
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
			std::swap(_str, s._str);
		}
        const char* c_str() const
		{
			return _str;
		}

        /
        // capacity
		size_t size() const
		{
			return _size;
		}
		size_t capacity() const
		{
			return _capacity;
		}
		bool empty() const
		{
			if (_size == 0)
			{
				return true;
			}
			return false;
		}
		void resize(size_t n, char c)
		{
			if (n > _size)
			{
				if (n > _capacity)
				{
					reserve(n + _size);
				}

				for (size_t i = _size; i < n; ++i)
				{
					_str[i] = c; //这里的[]是访问数组元素,并非运算符重载的调用,所以不会越界
				}

				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				_size = n;
				_str[_size] = '\0';
			}
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* newstr = new char[n + 1]{ 0 };
				memcpy(newstr, _str, _size + 1);
				//strcpy(newstr, _str);

				delete[] _str;
				_str = newstr;

				_capacity = n;
			}
		}

        /
        // access
		char& operator[](size_t index)
		{
			assert(index < _size);
			return _str[index];
		}
		const char& operator[](size_t index) const
		{
			assert(index < _size);
			return _str[index];
		}

        /
        //relational operators
		bool operator<(const string& s) const
		{
			for (size_t i = 0; i < (_size < s._size ? _size : s._size); ++i)
			{
				if (_str[i] < s._str[i])
					return true;
				if (_str[i] > s._str[i])
					return false;
			}
			if (_size < s._size)
				return true;
			else
				return false;
		}
		bool operator==(const string& s) const
		{
			if (_size != s._size)
				return false;

			for (size_t i = 0; i < _size; ++i)
			{
				if (_str[i] != s._str[i])
					return false;
			}
			return true;
		}
		bool operator<=(const string& s) const
		{
			if (*this < s || *this == s)
				return true;
			else
				return false;
		}
		bool operator>(const string& s) const
		{
			if (!(*this <= s))
				return true;
			else
				return false;
		}
		bool operator>=(const string& s) const
		{
			if (!(*this < s))
				return true;
			else
				return false;
		}
		bool operator!=(const string& s) const
		{
			if (!(*this == s))
				return true;
			else
				return false;
		}

		// 返回c在string中第一次出现的位置
		size_t find(char c, size_t pos) const
		{
			assert(pos < _size);
			for (size_t i = pos; i < _size; ++i)
			{
				if (_str[i] == c)
				{
					return i;
				}
			}
			return npos;
		}
		// 返回子串s在string中第一次出现的位置
		size_t find(const char* s, size_t pos) const
		{
			assert(s);
			assert(pos < _size);
			char* pchar = strstr(_str, s);
			if (pchar == nullptr)
			{
				return npos;
			}
			if ((size_t)(pchar - _str) >= pos)
			{
				return pchar - _str;
			}
			return npos;
		}

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

        const static size_t npos;
    };

	const size_t string::npos = -1;

	ostream& operator<<(ostream& _cout, const string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			_cout << s[i];
		}
		return _cout;
	}
	istream& operator>>(istream& _cin, string& s)
	{
		s.clear();
		char ch = _cin.get();
		//清除缓冲区中的空格与换行
		while (ch == ' ' || ch == '\n')
		{
			ch = _cin.get();
		}

		//定义一个128的字符数组
		char temp[128] = { 0 };
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			if (i == 127)
			{
				s += temp;
				i = 0;
			}
			temp[i] = ch;
			++i;

			ch = _cin.get();
		}

		//如果i>0 即temp中还有数据,将其转存即可
		if (i > 0)
		{
			temp[i] = '\0';
			s += temp;
		}

		return _cin;
	}
}

总结

到此,关于string类的模拟实现就介绍完了
相信通过模拟实现string类可以使我们更深入地理解string

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/814280.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python web实战 | 使用 Flask 实现 Web Socket 聊天室

概要 今天我们学习如何使用 Python 实现 Web Socket&#xff0c;并实现一个实时聊天室的功能。本文的技术栈包括 Python、Flask、Socket.IO 和 HTML/CSS/JavaScript。 什么是 Web Socket&#xff1f; Web Socket 是一种在单个 TCP 连接上进行全双工通信的协议。它是 HTML5 中的…

SAMBA 文件分享相关 笔记

目标说明 在Linux 安装Samba&#xff0c;然后在Windows端映射为网络硬盘 流程 Linux 端命令 apt install samba -y 默认情况下软件会询问是否迁移系统网络设置以搭建协议&#xff0c;选择迁移即可修改配置文件 vim /etc/samba/smb.conf Samba 的配置文件中会带一个名为 prin…

【Mybatis】Mybatis架构简介

文章目录 1.整体架构图2. 基础支撑层2.1 类型转换模块2.2 日志模块2.3 反射工具模块2.4 Binding 模块2.5 数据源模块2.6缓存模块2.7 解析器模块2.8 事务管理模块 3. 核心处理层3.1 配置解析3.2 SQL 解析与 scripting 模块3.3 SQL 执行3.4 插件 4. 接口层 1.整体架构图 MyBatis…

第5集丨webpack 江湖 —— 项目发布 和 source map

目录 一、webpack项目发布1.1 新增发布(build)命令1.2 优化js和图片文件的存放路径1.3 执行1.4 效果 二、clean-webpack-plugin插件2.1 安装2.2 配置2.3 执行 三、source map3.1 配置3.2 生成的source map文件 四、定义符4.1 配置4.2 使用 五、工程附件汇总5.1 webpack.config.…

大麦订单一键生成 仿大麦订单生成

后台一键生成链接&#xff0c;独立后台管理 教程&#xff1a;修改数据库config/Conn 不会可以看源码里有教程 下载程序&#xff1a;https://pan.baidu.com/s/16lN3gvRIZm7pqhvVMYYecQ?pwd6zw3

强化学习(EfficientZero)(应用于图像和声音)

目录 摘要 1.背景介绍 2.MCTS&#xff08;蒙特卡洛树搜索&#xff09;&#xff08;推理类模型&#xff0c;棋类效果应用好&#xff0c;控制好像也不错&#xff09; 3.MUZERO 4.EfficientZero&#xff08;基于MUZERO&#xff09; 展望 参考文献 摘要 在文中&#xff0c;基于…

【雕爷学编程】MicroPython动手做(20)——掌控板之三轴加速度5

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

数学建模 好文章和资源推荐

数学建模入门篇(0基础必看&#xff0c;全是自己的经验) 【竞赛|数学建模】Part 1&#xff1a;什么是数学建模和各模块介绍 0基础小白&#xff0c;如何入门数学建模&#xff1f; 数学建模入门篇(0基础必看&#xff0c;全是自己的经验) 什么是数学建模 重申了一下题目&#xff…

基于SpringBoot+Vue的地方废物回收机构管理系统设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

并发编程——线程池

1.概述 如果并发的线程过多&#xff0c;而且执行的时间都非常短&#xff0c;如果这样&#xff0c;每次都要创建线程就会大大降低效率&#xff0c;我们可以通过线程池来解决&#xff0c;JDK5增加了内置线程池ThreadPollExecutor。 2.线程池的优点 1.重复利用&#xff0c;降低…

【LeetCode】单链表——刷题

你曾经灼热的眼眶&#xff0c;是人生中少数的笨拙又可贵的时刻。 文章目录 1.反转单链表 题目思路及图解 代码中需要注意的问题 2.移除链表元素 题目思路及图解 代码中需要注意的问题 大家好&#xff0c;我是纪宁。 这篇文章分享给大家一些经典的单链表leetcode笔试题的…

【Unity 实用插件篇】 | 行为状态机StateMachine,规范化的管理对象行为

前言【Unity 实用插件篇】 | 行为状态机StateMachine 学习使用一、StateMachine行为状态机 介绍二、StateMachine 结构分析三、StateMachine状态机详细使用流程3.1 第一步:创建状态机Transition Table SO3.2 第二步:创建对应状态的 State SO3.3 第三步:创建状态的切换条件 C…

Hadoop学习指南:探索大数据时代的重要组成——运行环境搭建

Hadoop运行环境搭建&#xff08;开发重点&#xff09; 模板虚拟机环境准备 数据来源层 安装模板虚拟机&#xff0c;IP地址192.168.10.100、主机名称hadoop100、内存4G、硬盘50G hadoop100 虚拟机配置要求如下&#xff08;本文Linux系统全部以CentOS-7.5-x86-1804为例&#…

小研究 - Java 虚拟机实现原理分析

针对虚拟机的底层实现原理及相关实现过程&#xff0c;讨论了 Java 语言的跨平台原理以及相关工作机制&#xff0c;分析了 JVM 底层各数据区内存管理过程&#xff0c;阐述了 JVM 在 Java 语言中的核心作用以及重要地位。 目录 1 概述 2 Java 平台分层原理 3 虚拟机工作原理 …

CDC一键入湖:当 Apache Hudi DeltaStreamer 遇见 Serverless Spark

文章目录 1. 整体架构2. 环境准备3. 配置全局变量4. 创建专属工作目录和存储桶5. 创建 EMR Serverless Execution Role6. 创建 EMR Serverless Application7. 提交 Apache Hudi DeltaStreamer CDC 作业7.1 准备作业描述文件7.2 提交作业7.3 监控作业7.4 错误检索7.5 停止作业 8…

仿转转闲鱼链接后台生成

教程&#xff1a;修改数据库账号密码直接使用。 源码带有教程! 下载程序&#xff1a;https://pan.baidu.com/s/16lN3gvRIZm7pqhvVMYYecQ?pwd6zw3

TIA博途中通过UDT实现IO地址映射到DB块中的具体方法

TIA博途中通过UDT实现IO地址映射到DB块中的具体方法 如下图所示,打开TIA博途,新建一个项目,添加一个PLC UDT数据类型Iomap,数据类型为Array[0…49] of Byte, 如下图所示,再添加一个全局DB块,在DB块中添加一个变量map,数据类型为Iomap, 如下图所示,在PLC变量表中添加一…

iOS开发-NotificationServiceExtension实现实时音视频呼叫通知响铃与震动

iOS开发-NotificationServiceExtension实现实时音视频呼叫通知响铃与震动 在之前的开发中&#xff0c;遇到了实时音视频呼叫通知&#xff0c;当App未打开或者App在后台时候&#xff0c;需要通知到用户&#xff0c;用户点击通知栏后是否接入实时音视频的视频或者音频通话。 在…

【微服务】springboot整合mongodb使用详解

目录 一、mongodb简介 1.1 什么是mongodb 1.2 mongodb特点 二、mongodb中的核心术语 2.1 mogodb与数据库对比 2.2 mongodb中的核心概念 2.3 与关系数据库的差异 2.3.1 半结构化 2.3.2 支持多级嵌套 2.3.3 关系弱化 三、mongodb 技术优势和应用场景 3.1 mongodb 技术…

redis主从复制哨兵Cluster

目录 前言 一、模式介绍 1.1 主从复制 1.2 哨兵 1.3 集群 二、主从复制 2.1 主从复制的作用 2.2 主从复制流程 2.3 搭建Redis 主从复制 三、Redis 哨兵模式 3.1 哨兵模式原理 3.2 哨兵模式的作用 3.3 哨兵组成结构 3.4 哨兵故障转移机制 3.5 搭建Redis 哨兵模式…