C++STL---模拟实现string

news2024/12/29 14:26:36

我们这篇文章进行string的模拟实现。

为了防止标准库和我们自己写的string类发生命名冲突,我们将我们自己写的string类放在我们自己的命名空间中:

我们先来搭一个class string的框架:

namespace CYF{
public:
    //各种成员函数        
private:
    char _str;//存储字符串数组的指针
    size_t _size;//记录当前字符串的有效长度
    size_t _capacity;//记录当前字符串的容量
    static const size_t npos;//静态成员变量,很多地方的缺省值
}

默认成员函数

构造函数

		string(const char* str="")
		{
			_size = strlen(str);//初始字符串有效长度
			_capacity = _size;//初始字符串容量设为字符串有效长度
			_str = new char[_capacity + 1];//为存储字符串开辟空间,+1是为了保存'\0'
			strcpy(_str, str);//将str字符串拷贝到开好的空间
		}

拷贝构造函数

关于拷贝构造函数,我们首先需要了解一个知识点:深浅拷贝

浅拷贝就是拷贝出来的对象和原先的对象指向的是同一块空间,这样的话,其中一个对象对这块空间做了改变,也会影响另外一个对象。

深拷贝就是拷贝出来的对象跟原来的对象,指着的是两块不同的空间,两者相同指的是不同空间中的内容是相同的。

下图是深浅拷贝区别的形象化表现:

而在这里,显然我们并不想两者之间相互影响,所以我们要用到的是深拷贝。

所以我们要先开辟块容纳原有对象字符串的空间,然后将字符串拷贝过去,再将其他成员变量赋值过去即可,这是传统写法

		string(const string& str)//拷贝构造函数的传统写法
			:_size(0)
			,_capacity(0)
			,_str(new char[_capacity + 1])
		{
			strcpy(_str, str._str);
			_size = str._size;
			_capacity = str._capacity;
		}

我们还有一种现代写法

我们先根据原有字符串通过构造函数构造出一个tmp对象,然后再将tmp对象和拷贝对象的数据交换即可,这样的话,通过构造函数构造出来的tmp对象指向的空间和原对象的空间不同,并且交换之后,tmp是一个局部变量,出了作用域就会自动调用析构函数销毁,也就将tmp此时自身里拷贝对象原有的无用的数据全部删除了,一举两得:

		string(const string& str)//拷贝构造函数的现代写法
			:_str(new char[str._capacity+1])
			,_size(0)
			,_capacity(0)
		{
			string tmp(str._str);//调用构造函数,构造出一个C字符串为str._str的对象
			swap(tmp);//交换这两个对象,我们在后面会介绍
		}

关于拷贝构造函数我们还需要注意一点就是:传参的时候一定要传引用,如果传值的话,会再次调用拷贝构造函数,进而导致无限循环的调用拷贝构造函数。

赋值运算符重载

与拷贝构造函数一样,赋值运算符重载也涉及深浅拷贝问题,我们同样需要深拷贝,下面还是介绍传统和现代两种写法:

传统写法

我们首先要防止自己给自己赋值,然后释放原空间,开辟新空间,而后操作跟拷贝构造函数一样,最后返回值返回左值*this,以保证连续赋值。

		string& operator=(const string& str)//赋值运算符重载的传统写法
		{
			if (this != &str)
			{
				delete[] _str;//释放原来的空间
				_str = new char[str._capacity + 1];//开辟新的空间
				strcpy(_str, str._str);//拷贝赋值
				_size = str._size;
				_capacity = str._capacity;
			}
			return *this;//返回左值(支持连续赋值)
		}

现代写法

也和拷贝构造函数的现代写法类似,只不过我们这里可以直接采取传值的方式传参,在传参的过程中拷贝构造出tmp对象,因为拷贝构造函数要防止无限调用拷贝构造函数的错误,所以必须采用引用传参,而这里我们只需要用传值传参即可:

string& operator=(string str)//赋值运算符重载的现代写法1
{
	swap(str);//交换两个对象
	return *this;//返回左值(支持连续赋值)
}

但是这样做的弊端就是无法防止为自己赋值,当我们使用上面的operator+给自己赋值的时候,虽然操作后,对象的_str指向的字符串的内容不变,但是字符串的地址发生了改变,我们想改变的话就用下面的写法:

		string& operator=(const string& str)//赋值运算符重载的现代写法2
		{
			if (this != &str)//防止给自己赋值
			{
				string tmp(str);//用str拷贝构造出对象tmp
				swap(tmp);//交换两个对象
			}
			return *this;//返回左值(支持连续赋值)
		}

析构函数

由于string内的成员对象_str指向一块从堆区开辟的空间,当对象销毁时,堆区对应的空间并不会自动销毁,所以为了避免内存泄漏,我们需要手动delete释放:

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

迭代器有关函数

string类的迭代器实际上就是字符指针,只是将char* typedef成iterator而已:

typedef char* iterator;
typedef const char* const_iterator;

begin && end

string中的begin和end函数实现的很简单:

	string::iterator string::begin()
	{
		return _str;//返回字符串第一个字符的地址
	}

	string::const_iterator string::begin()const
	{
		return _str;//返回字符串第一个字符的const对象的地址
	}

	string::iterator string::end()
	{
		return _str + _size;//返回'\0'的地址
	}

	string::const_iterator string::end()const
	{
		return _str + _size;//返回'\0'的const对象的地址             
	}

所以我们在这就明白了,用迭代器遍历string对象的时候,实际上就是在用指针遍历字符数组而已:

string::iterator it = s.begin();
while (it != s.end())
{
	cout <<*it;
	it++;
}
cout << endl;

而且,实际上,范围for本质上也是通过迭代器来工作的,在代码编译的时候,编译器会自动将范围for替换成迭代器的形式,所以说要有迭代器的容器才会支持范围for,我们此时已经实现了我们自己的string的迭代器,所以我们可以实现范围for的使用:

	for (auto& e : s)
	{
		cout << e;
	}
	cout << endl;

与容量大小有关的函数

size && capacity

size()返回的是当前字符串的有效长度,capacity()返回的是字符串的容量:

		size_t size()
		{
			return _size;
		}

		size_t capacity()
		{
			return _capacity;
		}

直接将_size和_capacity返回即可。

reserve && resize

我们首先要对这两个函数做一下区分

我们先看reserve函数:

  • 当n大于对象当前capacity时,将capacity扩大到n或者大于n
  • 当n小于对象当前capacity时,什么操作都不做
		void reserve(size_t n)//若n>容量,才会扩容:则什么都不做
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];//+1是为了放'\0'
				strncpy(tmp, _str, _size + 1);//为了防止对象中有有效的字符'\0',strcpy无法拷贝
				delete[]_str;
				_str = tmp;
				_capacity = n;
			}
		}

resize函数:

  • 当n小于当前_size时,将_size缩小到n
  • 当n大于当前_size时,将_size扩大到n,后面补的字符为c,c的缺省值为'\0'
		void resize(size_t n, char c = '\0')
		{
			if (n <= _size)//n小于_size
			{
				_size = n;//_size调整为n
				_str[_size] = '\0';//在第_size个字符后加\0
			}
			else
			{
				if (n > _capacity)//先看看是否用扩容
				{
					reserve(n);
				}
				for (size_t i = _size; i < n; i++)//将原先有效字符后直到第n个字符全都赋值成c
				{
					_str[i] = c;
				}
				_size = n;
				_str[_size] = '\0';//字符串后面放上\0
			}
		}

empty

判断string对象是否为空,我们比较两个字符串的时候使用strcmp来实现,使用strcmp函数时若两个字符串大小相等返回0,两个字符串比较的时候不能使用==。

		bool empty()//判断是否为空
		{
			return strcmp(_str, "") == 0;//两个字符串比较要用strcmp,不能直接用==
		}

修改字符串相关函数

push_back

push_back的作用就是尾插一个字母,我们需要先判断是否需要增容,然后再进行尾插,而且我们需要在该字符的后面设置'\0',否则打印字符串的时候就很可能会非法越界,因为尾插的字符后面不一定就是'\0'。

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

append

append的作用就是尾插一个字符串,我们依旧是需要先判断是否需要扩容,而后尾插。这里我们不需要在最后设置'\0',因为尾插的字符串最后自带'\0'。

		void append(const char* str)
		{
			if (_capacity < _size + strlen(str))//若容量不够,则扩容
			{
				reserve(_size + strlen(str));
			}
			strcpy(_str + _size, str);
			_size += strlen(str);
		}

operator+=

operator+=的重载实现了字符串后面尾插字符和字符串的作用,我们可以直接调用上面实现的push_back和append函数:

		string& operator+=(const string& str)//传string对象
		{
			append(str._str);
			return *this;
		}

		string& operator+=(const char* str)//传C类型字符串
		{
			append(str);
			return *this;
		}

		string& operator+=(char c)//传一个字符
		{
			push_back(c);
			return *this;
		}

insert

insert函数的目的是在任意位置插入字符或字符串,我们首先要判断pos的合法性,而后判断capacity是否能容纳插入字符或字符串后的内容,若不能则调用reserve函数进行扩容,而后进行插入:

        //插入字符
		string& insert(size_t pos, char c)
		{
			assert(pos <= _size);//检测pos是否合法
			if (_size == _capacity)//判断是否需要增容
			{
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			}
			size_t i = _size;
			while (i >= pos)
			{
				_str[i + 1] = _str[i];
				i--;
			}
			_str[pos] = c;
			_size++;
			return *this;
		}

        //插入字符串
		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);//检测pos是否合法
			if (_size + strlen(str) > _capacity)//判断是否需要增容
			{
				reserve(_size + strlen(str));
			}
			char* end = _str + _size;
			while (end >= _str + pos)
			{
				*(end + strlen(str)) = *end;
				end--;
			}
			strncpy(_str + pos, str, strlen(str));
			_size += strlen(str);
			return *this;
		}

我们要注意插入字符串的时候,要用strncpy,不能用strcpy,否则会将'\0'也拷贝进去。

erase

我们依然首先要判断pos是否合法,而后分两种情况进行操作。

我们这里只模拟实现下面这一种形式的erase函数:

string& erase (size_t pos = 0, size_t len = npos);

1.当pos位置及后面的有效字符都需要被删除时:

我们在pos位置上放置'\0'即可。

2.当pos位置及后面的有效字符只需要被删除一部分时:

我们将后面需要保留的有效字符覆盖前面需要删除的字符即可,此时也不用在字符串后面加'\0',因为字符串末尾有'\0'。

		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);//判断pos是否合法
			size_t n = _size - pos;
			if (len >= n)//说明pos后面的字符全部删除
			{
				_size = pos;
				_str[_size] = '\0';//字符串后面放上'\0'
			}
			else//说明pos后面还有一部分字符保留着
			{
				strcpy(_str + pos, _str + pos + len);//用需要保留的字符覆盖掉需要删除的字符
				_size -= len;
			}
			return *this;
		}

clear

clear函数用于将字符串清空

		void clear()//将对象中存储的字符串置空
		{
			_size = 0;
			_str[_size] = '\0';
		}

swap

这里的swap函数是我们用于交换两个对象的数据,我们直接调用库里的swap模板函数将对象的各个成员变量进行交换即可,但是这样的话我们就需要在swap函数前加上std::,告诉编译器这是在std中的swap函数,否则根据就近原则,编译器会以为是我们正在实现的swap函数。

		void swap(string& str)//交换两个string对象
		{
			std::swap(_size, str._size);//使用库函数
			std::swap(_capacity, str._capacity);
			std::swap(_str, str._str);
		}

c_str

用于获取string对象中的C类型字符串

		const char* c_str()
		{
			return _str;
		}

用于访问字符串的函数

operator[ ]

operator[ ]是为了让string对象能够通过下标的方式进行随机访问。

1.我们通过operator[ ]的方式可能会需要进行读取和修改操作

		char& operator[](size_t i)//可读可写
		{
			assert(i < _size);//检测下标的合法性
			return _str[i];
		}

2.某些场景下,我们只需要通过operator[ ]的方式读取字符而不冷修改。例如我们对一个const的string类对象进行[ ]+下标操作时就只能读,不能写。

		const char& operator[](size_t i)const//只读
		{
			assert(i < _size);//检查下标的合法性
			return _str[i];
		}

find

find函数用于正向查找一个字符或者字符串,返回找到的字符或者字符串下标

1、查找第一个字符

首先要判断pos的合法性,然后遍历的从前往后找目标字符,若找到返回下标,没找到,返回npos。

		size_t find(char c, size_t pos = 0)//正向寻找
		{
			assert(pos < _size);
			for (size_t i = pos; i < _size; i++)//从pos位置开始向后找目标字符
			{
				if (_str[i] == c)
				{
					return i;
				}
			}
			return npos;
		}

2.查找第一个字符串

首先还是判断pos的合法性,然后我们通过strstr函数进行查找。strstr函数若找到了会返回目标字符串的起始地点,否则返回一个空指针。

		size_t find(const char* str, size_t pos = 0)//正向寻找
		{
			assert(pos < _size);
			const char* ret = strstr(_str + pos, str);//用strstr进行查找
			if (ret)//若找到子字符串,则返回子字符串的起始位置
			{
				return ret - _str;//返会字符串第一个字符的下标
			}
			else//找不到就返回nullptr
			{
				return npos;//返回npos
			}
		}

关系运算符重载>,<,<=,>=,==,!=

>,<,<=,>=,==,!=这六个关系运算符很好模拟,我们只写几个,剩下的复用其他的即可。

		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->operator>(s) || this->operator==(s));
		}

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

<<,>>运算符重载及getline函数

>>运算符重载

>>运算符重载是为了让我们能够使用>>直接进行输入。输入前我们需要先将对象中的C字符串置空,然后从标准输入流中读取字符,直到读到' '或'\n'停止。

	std::istream& operator>>(std::istream& in, CYF::string& str)
	{
		str.clear();//先清空字符串
		char ch = in.get();//读取一个字符
		while (ch != ' ' && ch != '\n')//若读取的字符不是空格或\n的话,尾插到str后面后继续读
		{
			str.push_back(ch);
			ch = in.get();
		}
		return in;//支持连续赋值
	}

<<运算符重载

这是为了我们能直接用<<运算符进行输出,我们直接进行遍历即可。

	std::ostream& operator<<(std::ostream& out, CYF::string& str)
	{
		for (size_t i = 0; i < str.size(); i++)
		{
			out << str[i];
		}
		return out;
	}

getline

getline函数用于读取一行含有空格的字符串。直到读到'\n'的时候停下来,其余跟operator>>一样。

	//getline跟>>基本相同,只不过是读取含有空格的字符串,知道读到\n的时候才停
	std::istream& getline(std::istream& in, CYF::string& str)
	{
		str.clear();
		char ch = in.get();
		while (ch != '\n')
		{
			str.push_back(ch);
			ch = in.get();
		}
		return in;
	}

下面贴上完整代码:

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <string>
#include <cassert>
#include <iostream>

namespace CYF
{
	class string
	{
	public:

		//string类的迭代器实际上就是字符指针
		typedef char* iterator;
		typedef const char* const_iterator;
		string(const char* str="")
		{
			_size = strlen(str);//初始字符串有效长度
			_capacity = _size;//初始字符串容量设为字符串有效长度
			_str = new char[_capacity + 1];//为存储字符串开辟空间,+1是为了保存'\0'
			strcpy(_str, str);//将str字符串拷贝到开好的空间
		}

		string(const string& str)//拷贝构造函数的传统写法
			:_size(0)
			,_capacity(0)
			,_str(new char[_capacity + 1])
		{
			strcpy(_str, str._str);
			_size = str._size;
			_capacity = str._capacity;
		}

		//string(const string& str)//拷贝构造函数的现代写法
		//	:_str(new char[str._capacity+1])
		//	,_size(0)
		//	,_capacity(0)
		//{
		//	string tmp(str._str);//调用构造函数,构造出一个C字符串为str._str的对象
		//	swap(tmp);//交换这两个对象
		//}

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

		string& operator=(const string& str)//赋值运算符重载的传统写法
		{
			if (this != &str)
			{
				delete[] _str;//释放原来的空间
				_str = new char[str._capacity + 1];//开辟新的空间
				strcpy(_str, str._str);//拷贝赋值
				_size = str._size;
				_capacity = str._capacity;
			}
			return *this;//返回左值(支持连续赋值)
		}

		//string& operator=(string str)//赋值运算符重载的现代写法1
		//{
		//	swap(str);//交换两个对象
		//	return *this;//返回左值(支持连续赋值)
		//}

		//string& operator=(const string& str)//赋值运算符重载的现代写法2
		//{
		//	if (this != &str)//防止给自己赋值
		//	{
		//		string tmp(str);//用str拷贝构造出对象tmp
		//		swap(tmp);//交换两个对象
		//	}
		//	return *this;//返回左值(支持连续赋值)
		//}

		void swap(string& str)//交换两个string对象
		{
			std::swap(_size, str._size);//使用库函数
			std::swap(_capacity, str._capacity);
			std::swap(_str, str._str);
		}

		iterator begin();
		const_iterator begin()const;
		iterator end();
		const_iterator end()const;

		size_t size()
		{
			return _size;
		}

		size_t capacity()
		{
			return _capacity;
		}

		void reserve(size_t n)//若n>容量,才会扩容:则什么都不做
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];//+1是为了放'\0'
				strncpy(tmp, _str, _size + 1);//为了防止对象中有有效的字符'\0',strcpy无法拷贝
				delete[]_str;
				_str = tmp;
				_capacity = n;
			}
		}

		void resize(size_t n, char c = '\0')
		{
			if (n <= _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				if (n > _capacity)
				{
					reserve(n);
				}
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = c;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

		bool empty()//判断是否为空
		{
			return strcmp(_str, "") == 0;//两个字符串比较要用strcmp,不能直接用==
		}

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

		void append(const char* str)
		{
			if (_capacity < _size + strlen(str))//若容量不够,则扩容
			{
				reserve(_size + strlen(str));
			}
			strcpy(_str + _size, str);
			_size += strlen(str);
		}

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

		string& insert(size_t pos, char c)
		{
			assert(pos <= _size);//检测pos是否合法
			if (_size == _capacity)//判断是否需要增容
			{
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			}
			size_t i = _size;
			while (i >= pos)
			{
				_str[i + 1] = _str[i];
				i--;
			}
			_str[pos] = c;
			_size++;
			return *this;
		}
		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);//检测pos是否合法
			if (_size + strlen(str) > _capacity)//判断是否需要增容
			{
				reserve(_size + strlen(str));
			}
			char* end = _str + _size;
			while (end >= _str + pos)
			{
				*(end + strlen(str)) = *end;
				end--;
			}
			strncpy(_str + pos, str, strlen(str));
			_size += strlen(str);
			return *this;
		}

		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);//判断pos是否合法
			size_t n = _size - pos;
			if (len >= n)//说明pos后面的字符全部删除
			{
				_size = pos;
				_str[_size] = '\0';//字符串后面放上'\0'
			}
			else//说明pos后面还有一部分字符保留着
			{
				strcpy(_str + pos, _str + pos + len);//用需要保留的字符覆盖掉需要删除的字符
				_size -= len;
			}
			return *this;
		}

		void clear()//将对象中存储的字符串置空
		{
			_size = 0;
			_str[_size] = '\0';
		}

		const char* c_str()
		{
			return _str;
		}

		char& operator[](size_t i)//可读可写
		{
			assert(i < _size);//检测下标的合法性
			return _str[i];
		}

		const char& operator[](size_t i)const//只读
		{
			assert(i < _size);//检查下标的合法性
			return _str[i];
		}

		size_t find(char c, size_t pos = 0)//正向寻找
		{
			assert(pos < _size);
			for (size_t i = pos; i < _size; i++)//从pos位置开始向后找目标字符
			{
				if (_str[i] == c)
				{
					return i;
				}
			}
			return npos;
		}

		size_t find(const char* str, size_t pos = 0)//正向寻找
		{
			assert(pos < _size);
			const char* ret = strstr(_str + pos, str);//用strstr进行查找
			if (ret)//若找到子字符串,则返回子字符串的起始位置
			{
				return ret - _str;//返会字符串第一个字符的下标
			}
			else//找不到就返回nullptr
			{
				return npos;//返回npos
			}
		}

		void reverse(iterator left, iterator right)
		{
			right = right - 1;
			while (left < right)
			{
				char c = '\0';
				c = *left;
				*left = *right;
				*right = c;
				left++;
				right--;
			}
		}

		//size_t rfind(char c, size_t pos = npos)
		//{
		//	string tmp(*this);
		//	reverse(tmp.begin(), tmp.end());
		//	if (pos > _size)
		//	{
		//		pos = _size - 1;//若pos大于等于字符串有效长度时,看作pos为字符串最后一个字符的下标
		//	}
		//	pos = _size - 1 - pos;//将pos改为镜像对称后的位置
		//	size_t ret = tmp.find(c, pos);
		//	if (ret != npos)
		//		return _size - 1 - ret;//若找到了,返回ret镜像对称后的位置
		//	else
		//		return npos;//若没找到,返回npos
		//}

		//size_t rfind(const char* str, size_t pos= npos)
		//{
		//	string tmp(*this);//拷贝构造对象tmp
		//	reverse(tmp.begin(), tmp.end());//逆置tmp的C字符串
		//	size_t len = strlen(str);//待查找的字符串长度
		//	char* arr = new char[len + 1];//开辟空间,用于拷贝待查找的字符串
		//	strcpy(arr, str);
		//	std::cout << arr << std::endl;
		//	//逆置待查找的字符串
		//	size_t left = 0;
		//	size_t right = len - 1;
		//	while (left < right)
		//	{
		//		std::swap(arr[left], arr[right]);
		//		left++;
		//		right--;
		//	}
		//	if (pos >= _size)//pos大于字符串有效长度,pos设为字符串最后一个字符的下标
		//	{
		//		pos = _size - 1;
		//	}
		//	pos = _size - 1 - pos;//将pos改为镜像对称后的位置
		//	size_t ret = tmp.find(arr, pos);//复用find函数正向查找
		//	delete[]arr;
		//	if (ret != npos)
		//	{
		//		return _size - ret - len;//找到了,返回ret再镜像逆置回去的位置
		//	}
		//	else
		//	{
		//		return npos;//没找到,返回npos
		//	}
		//}

		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->operator>(s) || this->operator==(s));
		}

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


	private:
		char* _str;//存储字符串
		size_t _size;//字符串当前有效长度
		size_t _capacity;//当前字符串最大容量
		static const size_t npos;//整形最大值(很多地方的默认值)
	};

	const size_t string::npos =  (size_t) - 1;

	string::iterator string::begin()
	{
		return _str;//返回字符串第一个字符的地址
	}

	string::const_iterator string::begin()const
	{
		return _str;//返回字符串第一个字符的const地址
	}

	string::iterator string::end()
	{
		return _str + _size;//返回'\0'的地址
	}

	string::const_iterator string::end()const
	{
		return _str + _size;//返回'\0'的const地址             
	}

	std::istream& operator>>(std::istream& in, CYF::string& str)
	{
		str.clear();//先清空字符串
		char ch = in.get();//读取一个字符
		while (ch != ' ' && ch != '\n')//若读取的字符不是空格或\n的话,尾插到str后面后继续读
		{
			str.push_back(ch);
			ch = in.get();
		}
		return in;//支持连续赋值
	}

	std::ostream& operator<<(std::ostream& out, CYF::string& str)
	{
		for (size_t i = 0; i < str.size(); i++)
		{
			out << str[i];
		}
		return out;
	}

	//getline跟>>基本相同,只不过是读取含有空格的字符串,知道读到\n的时候才停
	std::istream& getline(std::istream& in, CYF::string& str)
	{
		str.clear();
		char ch = in.get();
		while (ch != '\n')
		{
			str.push_back(ch);
			ch = in.get();
		}
		return in;
	}

}

大家可能会发现,我的代码中还实现了一个rfind,但是rfind函数我一直没找到错在了哪里,因为他会在析构函数处报内存泄漏的错误,如果大家发现哪里出错了,欢迎大家在评论区留言或私信我!!谢谢大家!

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

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

相关文章

【VTKExamples::Utilities】第十五期 ShepardMethod

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例ShepardMethod,并解析接口vtkShepardMethod,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ…

【自动化运营】PlugLink 1.0开源版发布

什么是PlugLink&#xff1f; PlugLink&#xff0c;顾名思义&#xff0c;就是插件的链接。它旨在帮助个人和小微企业实现运营自动化&#xff0c;通过链接脚本、API、AI大模型等&#xff0c;实现全自动工作流程。你可以把PlugLink看作一个巨大的拼装积木&#xff0c;每一个插件都…

基于springboot实现医疗挂号管理系统项目【项目源码+论文说明】

基于springboot实现医疗挂号管理系统演示 摘要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何一个企业或者个人会忽视&#xff0c;如何让信息急速传递&#xff0c;并且归档储存查询&#xff0c;采用之前的纸张记录模式已经不符合当前使用要求了。所以&…

计算机tcp/ip网络通信过程

目录 &#xff08;1&#xff09;同一网段两台计算机通信过程 &#xff08;2&#xff09;不同网段的两台计算机通信过程 &#xff08;3&#xff09;目的主机收到数据包后的解包过程 &#xff08;1&#xff09;同一网段两台计算机通信过程 如果两台计算机在同一个局域网中的同…

AlexNet神经网络训练

导包 import tensorflow as tffrom tensorflow.keras import datasets, layers, models 加载Fashion-MNIST数据集 (train_images, train_labels), (test_images, test_labels) datasets.fashion_mnist.load_data() 归一化像素值到[0, 1]区间 train_images, test_images t…

校园周边美食探索及分享平台,基于 SpringBoot+Vue+MySQL 开发的前后端分离的校园周边美食探索及分享平台设计实现

目录 一. 前言 二. 功能模块 2.1. 前台首页功能模块 2.2. 用户功能模块 2.3. 管理员功能模块 三. 部分代码实现 四. 源码下载 一. 前言 美食一直是与人们日常生活息息相关的产业。传统的电话订餐或者到店消费已经不能适应市场发展的需求。随着网络的迅速崛起&#xff0…

Aria2下载安装使用

这里写目录标题 下载Aria2 配置创建 aria2.conf 文件创建 aria2.session 文件 Aria2的使用基础使用多源下载多线程下载后台下载配置文件启动 AriaNg下载安装AriaNg配置AriaNg使用 Tracker 列表 aria2 是一款免费开源跨平台且不限速的多线程下载软件&#xff0c;其优点是速度快、…

CentOS配置DNS

1.打开/etc/resolv.conf文件 sudo vi /etc/resolv.conf2.添加配置 nameserver 114.114.114.1143.保存并关闭文件。 4.为了确保配置生效&#xff0c;重启网络服务或重启系统。例如&#xff1a; 重启网络&#xff1a; sudo systemctl restart network重启系统&#xff1a; …

【C++题解】1321. 时钟旋转(2)

问题&#xff1a;1321. 时钟旋转&#xff08;2&#xff09; 类型&#xff1a;字符串 题目描述&#xff1a; 时钟从时间&#xff1a;xx:xx&#xff08;xx时xx分&#xff09;&#xff0c;走到时间&#xff1a;xx:xx&#xff08;xx时xx分&#xff09;&#xff0c;时针共旋转了多…

ESXI8.0虚拟机和主机之间进行粘贴复制

1&#xff1a;默认情况下新建一个虚拟机是无法和主机之间进行粘贴复制操作的&#xff0c;主要是为了安全。 2&#xff1a;可以参考下面的文档进行操作&#xff0c;操作成功也只能复制粘贴数据&#xff0c;而无法复制粘贴文件或文件夹 https://knowledge.broadcom.com/externa…

[C#]使用C#部署yolov8的目标检测tensorrt模型

【测试通过环境】 win10 x64 vs2019 cuda11.7cudnn8.8.0 TensorRT-8.6.1.6 opencvsharp4.9.0 .NET Framework4.7.2 NVIDIA GeForce RTX 2070 Super 版本和上述环境版本不一样的需要重新编译TensorRtExtern.dll&#xff0c;TensorRtExtern源码地址&#xff1a;TensorRT-CShar…

华为WLAN无线组网技术与解决方案

WLAN无线组网技术与解决方案 网络拓扑采用AP和AC旁挂式无线组网 配置思路&#xff1a; 1.让AP上线 1.1&#xff0c;使得AP能够获得IP地址 配置步骤&#xff1a; 1.把AC当作一个一个有管理功能的三层交换机 sys Enter system view, return user view with CtrlZ. [AC6605]vlan …

玩转STM32-I2C通信协议(详细-慢工出细活)

文章目录 一、I2C总线原理&#xff08;掌握&#xff09;1.1 硬件构成1.2 传输位1.3数据传输格式 二、STM32的I2C特性和结构三、STM32的I2C通信实现&#xff08;硬件实现方式&#xff09;3.1 I2C主模式 四、应用实例 一、I2C总线原理&#xff08;掌握&#xff09; 1.1 硬件构成…

【C++ QT项目实战-02】---- C++ QT系统实现基于QT调用RESTful接口访问JSON文件中数据

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a;C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &#…

股票交易vip快速通道有什么门槛?vip交易通道的开通流程!

证券公司的VIP通道通常是为了满足高端客户或高频交易客户的需求而设立的&#xff0c;提供更快速、更便捷的交易服务。证券公司VIP通道适用于有追涨停板需求的投资者&#xff0c;以及一些喜爱高频交易的投资者&#xff0c;总的来说就是快速&#xff0c;在交易主机排队靠前。 VI…

视频集中存储LntonCVS视频监控汇聚平台智慧园区应用方案

智慧园区&#xff0c;作为现代化城市发展的重要组成部分&#xff0c;承载着产业升级的使命&#xff0c;是智慧城市建设的重要体现。在当前产业园区竞争日益激烈的情况下&#xff0c;越来越多的用户关注如何将项目打造成完善的智慧园区。 在智慧园区的建设过程中&#xff0c;各类…

离线deb安装下载及安装实例

1、使用apt download下载deb安装包(不包括依赖包) 1.1仅下载deb安装包 sudo apt download lrzsz 1.2安装载deb安装包 sudo dpkg -i lrzsz_0.12.21-10kylin0k2_arm64.deb 注&#xff1a;dpkg安装deb包&#xff0c;部分存在depends关系&#xff0c;需要使用apt-get -f instal…

【赠书第25期】C#项目开发实战(微视频版)

文章目录 前言 1 项目构思与需求分析 1.1 项目构思 1.2 需求分析 2 系统设计 2.1 系统架构设计 2.2 数据库设计 2.3 接口设计 3 编码实现 3.1 环境搭建 3.2 编码规范 3.3 编码实现 4 测试与部署 4.1 单元测试 4.2 系统测试 4.3 部署与上线 5 总结与展望 6 推…

信息安全法规和标准

《全国人民代表大会常务委员会关于维护互联网安全的决定》规定&#xff0c;威胁互联网运行安全的行为&#xff1a;&#xff08;1&#xff09;侵入国家事务、国防建设、尖端科学技术领域的计算机信息系统&#xff0c;&#xff08;2&#xff09;故意制作、传播计算机病毒等破坏性…

气膜建筑的运行保障:应对停电的解决方案—轻空间

气膜建筑作为一种现代化的建筑形式&#xff0c;以其独特的结构和多样的应用赢得了广泛关注。这种建筑依靠风机不断往内部吹气来维持其结构形态&#xff0c;那么如果遇到停电的情况&#xff0c;该如何确保其正常运行呢&#xff1f; 气膜建筑的供风系统 气膜建筑内部的气压维持依…