string模拟实现

前言

上一期我们对STL进行了简单的介绍以及学习了string常用API的基本使用!本期我们来探索它的底层实现!自己对string的常用的接口进行模拟实现!

本期内容介绍

常用成员函数模拟实现

常用非成员函数模拟实现

成员函数

构造函数

在进行模拟实现前我们得先定义一下底层吧!它的底层其实很简单,一个字符指针、一个容量(不包含\0)、一个字符的个数!和我们数据结构介绍的差不多!不同的是这是类的属性所以我们搞成私有的就可以了!!

char* _str;
size_t _size;
size_t _capacity;

这里的构造最常用的是空构造和一个常量字符串构造!可以两个函数实现,但这里最好的方式是利用缺省参数,空构造就是只有一个表示结束的\0而字符串的结尾默认有一个\0,所以只需给一个空的字符串当缺省参数即可!

实现思路:size和capacity是当前常量的长度,开比容量多1的空间(存放\0的)给_str,然后把常量字符串str的数据给拷贝到_str!

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

拷贝构造

实现思路:给_str开字符串的容量+1的空间,然后把字符串的数据拷贝到_str,_str的size和capacity都和字符串的相等!

string(const string& str)
{
	_str = new char[str._capacity + 1];//多开一个存\0
	strcpy(_str, str.c_str);

	_size = str._size;
	_capacity = str._capacity;
}

赋值拷贝

实现思路:开一块和字符串str一样大的空间(包含\0)tmp,把str的数据拷贝给tmp,然后释放掉this的那块空间,在让this的_str指向tmp的空间,_size和_capacity都等于字符串的即可

string& operator=(const string& str)
{
	if (this != &str)//防止自己给自己赋值
	{
		char* tmp = new char[str._capacity + 1];//多开一个存\0
		strcpy(tmp, str._str);
		delete[] _str;
		_str = tmp;

		_size = str._size;
		_capacity = str._capacity;
	}

	return *this;
}

析构函数

实现思路:释放掉(delete[])空间,然后将size和capacity置为0

~string()
{
	delete[] _str;//释放空间
	_size = _capacity = 0;//属性置为0
}

c_str

实现思路:因为底层就是C语言的指针,所以直接返回底层的指针即可!由于只是返回不惜要修改,所以用const修饰

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

这个上面已经用过了,这里不在演示了!

迭代器

日常开发中正向迭代器用的居多,一般如果反向遍历的时候,因为string支持[]所以不用反向迭代器,所以这里只模拟实现正向的和const正向的!

实现思路:

因为底层是连续的,所以可以直接返回底层的指针_str作为begin,_str +_size就是end。如果是const的不让修改直接在后面加上const。

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

OK,我们在使用的时候就说过,支持迭代器就支持范围for,因为范围for的底层就是迭代器的替换,我们先来看看这里可不可以支持范围for?

OK,没问题!我们这可以通过看反汇编验证范围for的底层是否真的是迭代器的替换:

而且自己实现的迭代器的命名要和库里面的一致范围for才可以替换!我们可以换个名字试试:

这里只是把begin换成了Begin他就不认识了!!!所以范围for就是傻瓜式的替换迭代器!!!

size

实现思路:size是返回元素的个数,这里直接返回_size即可,又因为不需要修改所以可加上const

size_t size() const
{
	return _size;
}

capacity

实现原理:和上面的size一样,直接返回_capacity即可,因为不需要修改加上const

size_t capacity() const
{
	return _capacity;
}

我们在实现构造的时候size和capacity是等于常量字符串的长度的!所以这里相等!

empty

实现原理:直接判断size 是否等于0,不需要修改_size所以可以加const

bool empty() const
{
	return _size == 0;
}

clear

实现原理:由于只是把字符清理了,不清理空间!所以直接把_str[0]的位置置为\0即可!然后size 置为0!

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

reserve

实现思路:这个函数的作用是预开空间!所以我们只需开指定大小的空间把原来的_str的数据拷贝到新空间即可!(但注意的是到多开一个存\0)然后将原来的空间释放,让_str指向新的空间,新的_capacity就是n。因为reserve不支持缩容,所以我们只有在n>capacity的时候才开空间!!!!

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

		_str = tmp;
		_capacity = n;
	}
}

resize

实现思路:当n小于size时,保存前n个元素也就是在n下标处直接处理成\0(这里只是减小size不是缩容),如果是n > size的情况的话可能要扩容(n > capacity时扩,size < n < capacity不扩),而上面的reserve是当n>capacity时才扩容,所以我们在n>=size时直接先调用一下reserve,然后就一定有空间插入了!!最后再把要插入的字符尾插到n

void resize(size_t n, char c = '\0')
{
	if (n < _size)
	{
		_str[n] = '\0';
		_size = n;
	}
	else
	{
		reserve(n);
		for (size_t i = _size; i < n; i++)
		{
			_str[i] = c;
		}
		_str[n] = '\0';//记得最后补一个\0
	}
}

operator[]

[]是有const和非const的,这里都得来实现一下!具体原因在上一期已经说了,权限问题!

实现思路:我们返回pos位置元素的引用即可!但注意的是要对pos位置的合法性进行判断,这里采用断言暴力检查!!!

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

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

front

实现思路:返回_str[0]的元素即可

char front()
{
	return _str[0];
}

back

实现思路:因为_size是最后一个元素的下一个位置,所以最后一个元素是_size-1,即返回_str[_size-1]的元素即可!

char back()
{
	return _str[_size - 1];
}

push_back

实现思路:在_size位置把\0替换成要插入的字符,然后在让_size++,最后补上个\0即可!但注意插入的时候一定要注意:有空间才能插入,所以在插入前一定要检查是否要扩容,我们这里的扩容可以是2倍也可以是1.5倍,如果正是个空串,我们第一次扩容的时候给他4个空间即可!

void push_back(char c)
{
	//判断是否扩容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

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

append

实现思路:将要插入的字符串可已利用strcpy拷贝到_size的位置!最后记得_size += len 

void append(const char* str)
{
	int len = strlen(str);
	if (len + _size > _capacity)
	{
		reserve(len + _size);
	}

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

operator+=

实现思路:直接复用push_back和append,最后返回*this即可

insert

这个可以在pos下标处插入一个字符,也可以在pos位置插入一个字符串!

实现思路:

插入一个元素:从最后一个元素的下一个位置(\0开始挪动)开始到pos位置的元素都依次往后挪动一个位置!挪完后再把插入的元素插入即可!

插入一个字符串:从_size往后len 个位置即end=_size +len开始依次将end-len 的元素往后挪到end 的位置, 挪动pos +len - 1个元素,然后再把字符串插入进去即可!

void insert(size_t pos, char c)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}

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

这里插入单个元素的话您的想法可能和我的不一样!例如你是从左后一个元素往后挪的!这个思路没问题!!!但是这种思路写的时候有坑,有时候不好检查!我再来把这种思路写一下:


void insert(size_t pos, char c)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	size_t end = _size;
	while (end >= pos)
	{
		_str[end + 1] = _str[end];
		--end;
	}

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

这样写看似是正确的!但在0下标处插入的时候会死循环!看结果:

原因我就不带大家调试看了,大家可以自己调试看!你会看到-1>0!!!没错,你没看错这段代码调试就是-1>0原因是end是size_t无符号整型,等减到0后就又开始从最大开始了!!!所以死循环!你可能会想把end换成int可以了吧,但是你不要忘了pos也是size_t的,C\C++是支持隐式类型转换的!低的会向高转!!所以不行,这里的解决方案有两种:一种是你把pos换成Int但是不推荐因为人家库里面是size_t 第二种是:你把end换成int把pos在循环挪动的时候强转成int!!!

void insert(size_t pos, char c)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	int end = _size;
	while (end >= (int)pos)
	{
		_str[end + 1] = _str[end];
		--end;
	}

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

OK,我们继续来实现在pos位置插入一个字符串!!!

void insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	int len = strlen(str);
	if (len + _size > _capacity)
	{
		reserve(_size + len);
	}

	size_t end = _size + len;
	while (end >= pos + len - 1)
	{
		_str[end] = _str[end - len];
		end--;
	}

	strncpy(_str + pos, str, len);
	_size += len;
}

erase

实现思路:从指定的pos+len的位置开始往前依次挪动len 和即可!

void erase(size_t pos = 0, size_t len = npos)
{
	assert(pos < _size);
	if (len == pos || len  > _size - pos)//这里的len + pos > _size得写成这样防止溢出报错
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		for (size_t i = pos; i < _size; i++)
		{
			_str[i] = _str[i + len];
		}

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

当然你可以这样移动,也可以直接调用C语言的库函数strcpy直接把pos 到pos+len的len个字符移动到pos位置!

void erase(size_t pos = 0, size_t len = npos)
{
	assert(pos < _size);
	if (len == pos || len > _size - pos)//这里的len + pos > _size得写成这样防止溢出报错
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
}

pop_back

这里的实现思路有两种:1、可以直接调用erase  2、判断_size如果不为0则将_size--然后再将\0放到_size即可

void pop_back()
{
	assert(_size > 0);//确保有元素可删
	erase(_size-1, 1);
}

void pop_back()
{
	assert(_size > 0);//确保有元素可删
	--_size;
	_str[_size] = '\0';
}

swap

这个swap和算法库里面的不一样!算法库里面的那个会形成拷贝,代价较大!这个我们使用的时候介绍过他是直接交换指针的!

实现思路:我们可以把两个字符串对象的属性通过库函数的 swap给依次交换了!而一个属性至多8个字节(64位下指针为8个字节)拷贝代价小了很多!!!

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

OK,再来通过调试看看空间和size

find

find可以查找一个字符,也可以找一个字符串!找到了返回第一个字符的下标否则返回npos

实现思路:

找一个字符:遍历一遍字符串,逐一比较一遍,找到了返回下标,最后没找到返回npos

找一个字符串:可以利用KMP算法,但是KMP说实话在日常开发中的效率不咋地。我们这里可以利用C语言的时候的一个函数strstr,如果他返回的是空指针直接返回npos,否则返回,strstr返回的指针减去_str就是第一次出现的下标!

size_t find(char c, size_t pos = 0)
{
    assert(pos < _size);//保证查找的位置合法
	for (size_t i = pos; i < _size; i++)
	{
		if (c == _str[i])
			return i;
	}

	return npos;
}

size_t find(const char* str, size_t pos = 0)
{
    assert(pos < _size);//保证查找的位置合法
	const char* ret = strstr(_str, str);
	return ret == nullptr ? npos : ret - _str;
}

substr

实现思路:如果是pos位置开始的长度比_size大的话那就直接从pos位置开始把所有的字符都给子串,否则的话就把pos位置开始后的len个长度的字符给子串!

string substr(size_t pos = 0, size_t len = npos)
{
	string sub;
	if (len >= _size - pos)
	{
		for (size_t i = pos; i < _size; i++)
		{
			sub += _str[i];
		}
	}
	else
	{
		for (size_t i = pos; i < pos + len; i++)
		{
			sub += _str[i];
		}
	}

	return sub;
}

这里介绍了swap后就可以介绍一下拷贝构造和赋值拷贝的现代写法了!上面的那个时传统写法!

拷贝构造的现代写法

string(const string& str)
{
	string tmp(str.c_str());
	swap(tmp);
}

这里起始就是很好的利用了swap,先让一个字符串tmp来用str.c_str()来构造,然后再让this与tmp交换资源即可实现拷贝构造!!!

赋值拷贝的现代写法

string& operator=(string str)
{
	swap(str);
	return *this;
}

这里也是直接把形参的引用去掉,让其形成是实参的拷贝,然后让this与str直接交换即可达到赋值的目的!!!

非成员函数

逻辑比较相关

这里的逻辑比较主要有6个,分别是: > >= < <= == !=六个,我们可以实现两个然后其他复用即可!例如可以实现>和==

实现思路:

>  : 我们可以利用C语言的库函数strcmp根据它的返回值来判断是否大于,

== :和上面的同理!判断返回值是否==0

bool operator>(const string& s1, const string& s2)
{
	int ret = strcmp(s1.c_str(), s2.c_str());
	return ret > 0;
}

bool operator==(const string& s1, const string& s2)
{
	int ret = strcmp(s1.c_str(), s2.c_str());
	return ret == 0;
}

OK,其他的直接复用即可!大于并且等于就是>=,不是大于等于就是<,不是大于就是<=,不是等于就是不等于!

bool operator>=(const string& s1, const string& s2)
{
	return (s1 > s2) && (s1 == s2);
}

bool operator<(const string& s1, const string& s2)
{
	return !(s1 >= s2);
}

bool operator<=(const string& s1, const string& s2)
{
	return !(s1 > s2);
}

bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}

swap

这里有个swap的原因是防止误操作的!因为即使类成员提供了swap但是有时候不排除操作失误去掉算法库里面的那个函数!因此设计者考虑到了这一点,于是在全局重载了算法库的那个swap!这有朋友会想调用的时候难道编译器不会去和算法库的swap错调吗?显然是不会的!因为算法库的那个是模板,调用时需要实例化,而这个是已经实例化好的!编译器有最优匹配原则的即有现成吃现成,否则在实例化!这就和人一样,假设你今天在家学习一天饿了,有两种选择一是拿着你妈留的钱去买饭或点外卖;另一是自己做!你肯定是拿着钱 点外卖嘛~!

实现思路:直接用成员函数swap即可!

void swap(string& s1, string& s2)
{
	s1.swap(s2);
}

operator<<

实现思路:虽然<<不支持自定义类型,但支持内置类型!字符串中是一个个的字符,所以直接将字符串的内容按字符逐个输出,最后返回ostream的流对象即可!

这里有个问题就是:为什么最后还要返回ostream对象呢?原因很简单:因为把当前内容插入到输出流中后还有可能继续向流中插入其他东西!例如插入完字符串后还有可能插入endl去换行!!!!即是为了来连续的输出!!!

ostream& operator<<(ostream& out, const string& str)
{
	for (auto c : str)
	{
		out << c;
	}
	return out;
}

operator>>

实现思路:我们可以逐个获取字符,然后将这些字符尾插到字符串中!但是cin 和C语言的scanf是读取不到\n和空格的这就会导致一直不结束等着你输入!所以不能直接用它两读取字符,可以用C语言的getchar但这里不推荐,虽然C++兼容C语言但它两的缓冲区毕竟不一样,在有些极端情况下会有bug,所以在这里推荐使用C++istream的get方法来读取,这个可以读取到空格和\n

istream& operator>>(istream& in, string& str)
{
	str.clear();//流提取本质是一种覆盖,下面是直接尾插的,所以这里要清空一下
	char c;
	c = in.get();
	while (c != ' ' && c != '\n')
	{
		str += c;
		c = in.get();
	}

	return in;
}

OK这里可以与STL库中的对比一下:

显然效果一样!!!这里还有的人觉得这样实现不太好!原因是每次读取一个字符都要频繁的尾插到后面,也就是要频繁的扩容!效率大打折扣!所以有人进行了对这种方法的优化!

优化思路:用一个定长的数组来先把字符从起来,最后当满了之后一次性尾插,这样就大大的减少了扩容到来的消耗!即使你比较短或前几次的内容已经为尾插到了str里面,但时短的不足数组长度的这些也是直接可以判断尾插到后面的!

istream& operator>>(istream& in, string& str)
{
	str.clear();//流提取本质是一种覆盖,下面是直接尾插的,所以这里要清空一下
	char c;
	c = in.get();
	char buff[128];
	size_t i = 0;
	while (c != ' ' && c != '\n')
	{
		buff[i++] = c;
		if (i == 127)
		{
			buff[i] = '\0';
			str += buff;//当够127个字符时时将其添上\0并尾插到后面!
			i = 0;//为继续插入准备
		}
		c = in.get();
	}

	if (i > 0)//当兵输入的较短时,直接把当前的字符串尾插到str
	{
		buff[i] = '\0';
		str += buff;
	}

	return in;
}

getline

实现思路:和>>的实现原理基本一致!!我们只需要判断当c等于\n的时候结束掉即可!!!

istream& getline(istream& in, string& str)
{
	str.clear();//流提取本质是一种覆盖,下面是直接尾插的,所以这里要清空一下
	char c;
	c = in.get();
	while (c != '\n')
	{
		str += c;
		c = in.get();
	}

	return in;
}

istream& getline(istream& in, string& str)
{
	str.clear();//流提取本质是一种覆盖,下面是直接尾插的,所以这里要清空一下
	char c;
	c = in.get();
	char buff[128];
	size_t i = 0;
	while (c != '\n')
	{
		buff[i++] = c;
		if (i == 127)
		{
			buff[i] = '\0';
			str += buff;//当够127个字符时时将其添上\0并尾插到后面!
			i = 0;//为继续插入准备
		}
		c = in.get();
	}

	if (i > 0)//当兵输入的较短时,直接把当前的字符串尾插到str
	{
		buff[i] = '\0';
		str += buff;
	}

	return in;
}

全部源码:


#pragma once

#include <assert.h>

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

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

		/*string(const string& str)//传统写法
		{
			_str = new char[str._capacity + 1];//多开一个存\0
			strcpy(_str, str._str);

			_size = str._size;
			_capacity = str._capacity;
		}*/

		string(const string& str)//现代写法
		{
			string tmp(str.c_str());
			swap(tmp);
		}
		 
		/*string& operator=(const string& str)//传统写法
		{
			if (this != &str)//防止自己给自己赋值
			{
				char* tmp = new char[str._capacity + 1];//多开一个存\0
				strcpy(tmp, str._str);
				delete[] _str;
				_str = tmp;

				_size = str._size;
				_capacity = str._capacity;
			}

			return *this;
		}*/

		string& operator=(string str)//现代写法
		{
			swap(str);
			return *this;
		}

		~string()
		{
			delete[] _str;//释放空间
			_size = _capacity = 0;//属性置为0
		}

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

		iterator begin()
		{
			return _str;
		}		

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

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

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

		bool empty() const
		{
			return _size == 0;
		}

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

		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 c = '\0')
		{
			if (n < _size)
			{
				_str[n] = '\0';
				_size = n;
			}
			else
			{
				reserve(n);
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = c;
				}
				_str[n] = '\0';//记得最后补一个\0
			}
		}

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

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

		char front()
		{
			return _str[0];
		}

		char back()
		{
			return _str[_size - 1];
		}

		void push_back(char c)
		{
			//判断是否扩容
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

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

		void append(const char* str)
		{
			int len = strlen(str);
			if (len + _size > _capacity)
			{
				reserve(len + _size);
			}

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

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

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

		void insert(size_t pos, char c)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}

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

		/**void insert(size_t pos, char c)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + 1] = _str[end];
				--end;
			}

			_str[pos] = c;
			_size++;
		}*/

		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			int len = strlen(str);
			if (len + _size > _capacity)
			{
				reserve(_size + len);
			}

			size_t end = _size + len;
			while (end > pos + len - 1)
			{
				_str[end] = _str[end - len];
				end--;
			}

			strncpy(_str + pos, str, len);
			_size += len;
		}

		/**void erase(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);
			if (len == pos || len  > _size - pos)//这里的len + pos > _size得写成这样防止溢出报错
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				for (size_t i = pos; i < _size; i++)
				{
					_str[i] = _str[i + len];
				}

				_size -= len;
				_str[_size] = '\0';
			}
		}*/

		void erase(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);
			if (len == pos || len > _size - pos)//这里的len + pos > _size得写成这样防止溢出报错
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}

		void pop_back()
		{
			assert(_size > 0);//确保有元素可删
			erase(_size-1, 1);
		}

		/*void pop_back()
		{
			assert(_size > 0);//确保有元素可删
			--_size;
			_str[_size] = '\0';
		}*/

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

		size_t find(char c, size_t pos = 0)
		{
			assert(pos < _size);//保证查找的位置合法
			for (size_t i = pos; i < _size; i++)
			{
				if (c == _str[i])
					return i;
			}

			return npos;
		}

		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);
			const char* ret = strstr(_str, str);
			return ret == nullptr ? npos : ret - _str;
		}

		string substr(size_t pos = 0, size_t len = npos)
		{
			string sub;
			if (len >= _size - pos)
			{
				for (size_t i = pos; i <= _size; i++)
				{
					sub += _str[i];
				}
			}
			else
			{
				for (size_t i = pos; i < pos + len; i++)
				{
					sub += _str[i];
				}
			}

			return sub;
		}

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

		const static size_t npos = -1;//注意这里是特例,一般的我们前面介绍的static成员都是在类里面声明,类外面定义,这里是对这个开了个特权
	};

	bool operator>(const string& s1, const string& s2)
	{
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret > 0;
	}

	bool operator==(const string& s1, const string& s2)
	{
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret == 0;
	}

	bool operator>=(const string& s1, const string& s2)
	{
		return (s1 > s2) && (s1 == s2);
	}

	bool operator<(const string& s1, const string& s2)
	{
		return !(s1 >= s2);
	}

	bool operator<=(const string& s1, const string& s2)
	{
		return !(s1 > s2);
	}

	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}

	void swap(string& s1, string& s2)
	{
		s1.swap(s2);
	}

	ostream& operator<<(ostream& out, const string& str)
	{
		for (auto c : str)
		{
			out << c;
		}
		return out;
	}

	/*istream& operator>>(istream& in, string& str)
	{
		str.clear();//流提取本质是一种覆盖,下面是直接尾插的,所以这里要清空一下
		char c;
		c = in.get();
		while (c != ' ' && c != '\n')
		{
			str += c;
			c = in.get();
		}

		return in;
	}*/

	/*istream& getline(istream& in, string& str)
	{
		str.clear();//流提取本质是一种覆盖,下面是直接尾插的,所以这里要清空一下
		char c;
		c = in.get();
		while (c != '\n')
		{
			str += c;
			c = in.get();
		}

		return in;
	}*/


	istream& operator>>(istream& in, string& str)
	{
		str.clear();//流提取本质是一种覆盖,下面是直接尾插的,所以这里要清空一下
		char c;
		c = in.get();
		char buff[128];
		size_t i = 0;
		while (c != ' ' && c != '\n')
		{
			buff[i++] = c;
			if (i == 127)
			{
				buff[i] = '\0';
				str += buff;//当够127个字符时时将其添上\0并尾插到后面!
				i = 0;//为继续插入准备
			}
			c = in.get();
		}

		if (i > 0)//当兵输入的较短时,直接把当前的字符串尾插到str
		{
			buff[i] = '\0';
			str += buff;
		}

		return in;
	}

	istream& getline(istream& in, string& str)
	{
		str.clear();//流提取本质是一种覆盖,下面是直接尾插的,所以这里要清空一下
		char c;
		c = in.get();
		char buff[128];
		size_t i = 0;
		while (c != '\n')
		{
			buff[i++] = c;
			if (i == 127)
			{
				buff[i] = '\0';
				str += buff;//当够127个字符时时将其添上\0并尾插到后面!
				i = 0;//为继续插入准备
			}
			c = in.get();
		}

		if (i > 0)//当兵输入的较短时,直接把当前的字符串尾插到str
		{
			buff[i] = '\0';
			str += buff;
		}

		return in;
	}
}

OK,好兄弟本期内容就介绍到这里!我们下期再见~!

结束语:

我不相信寒冬已至,因为火把在我手中!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络 谢希仁(001-2)

计算机网络-方老师 总时长 24:45:00 共50个视频&#xff0c;6个模块 此文章包含1.5到1.7的内容 1.5计算机网络类别 连通 共享 分类方法 广域网是边缘部分和核心部分的核心部分 以前是拨号连接 现在是光纤 总线型 星型 环形网 1.6计算机网络的性能 带上单位之后就不是…

git bash 命令行反应慢、卡顿(定位出根本原因)

参考该博主&#xff1a; https://blog.csdn.net/weixin_50212044/article/details/131575987?utm_mediumdistribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-131575987-blog-130024908.235v43pc_blog_bottom_relevance_base4&spm1001.210…

IP证书有什么作用?怎么申请?

关于IP地址证书&#xff0c;它的主要作用有这么几个点&#xff1a; 1.验明正身&#xff1a;就像身份证一样&#xff0c;它可以证明某个服务器的IP地址是真的、合法的&#xff0c;让咱知道咱们连接的就是正确的服务器&#xff0c;而不是冒牌货。这样一来&#xff0c;就可以降低像…

使用OpenCV实现人脸特征点检测与实时表情识别

引言&#xff1a; 本文介绍了如何利用OpenCV库实现人脸特征点检测&#xff0c;并进一步实现实时表情识别的案例。首先&#xff0c;通过OpenCV的Dlib库进行人脸特征点的定位&#xff0c;然后基于特征点的变化来识别不同的表情。这种方法不仅准确度高&#xff0c;而且实时性好&am…

塑料工厂5G智能制造数字孪生可视化平台,推进塑料行业数字化转型

塑料工厂5G智能制造数字孪生可视化平台&#xff0c;推进塑料行业数字化转型。塑料制造行业作为重要的工业领域&#xff0c;亟需借助这一平台实现产业升级与转型&#xff0c;以适应市场的变化和提高生产效率。传统的塑料制造过程往往存在生产效率低下、资源浪费、环境污染等问题…

鸿蒙车载原生开发,拓展新版图

一天内连发“五弹”、HiCar 4.0首次上车 华为鸿蒙狂扩“汽车朋友圈”-上游新闻 汇聚向上的力量 3月15日&#xff0c;在“华为云&华为终端云服务创新峰会2024”上&#xff0c;华为首批汽车行业伙伴广汽传祺、岚图汽车、零跑汽车、凯翼汽车加入鸿蒙生态合作&#xff0c;华为…

Python - 应用篇 :ChatGPT +Pycharm 序列号自动生成

前言&#xff1a; 客户要求在产品外壳上新增可追溯的二维码贴花&#xff0c;二维码信息内容如下&#xff1a; 编码格式&#xff1a;SBD 零部件代码 控制盒序列号 控制盒厂家 例如&#xff1a;[)>06P725-18428S24031410001ZJL SBD 零部件代码&#xff1a;[)>06P725-184…

考研复习C语言进阶(3)

结构体 1 结构体的声明 1.1 结构的基础知识 结构是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 1.2 结构的声明 struct tag { member-list; }variable-list; 例如描述一个学生&#xff1a; struct Stu { char name[20];//名字 int ag…

java-ssm-jsp-基于java的客户管理系统的设计与实现

java-ssm-jsp-基于java的客户管理系统的设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全

Spring Cloud Gateway针对指定接口做响应超时时间限制

背景&#xff1a;我做的这个服务中存在要对大数据量做自定义统计的接口和大文件上传接口&#xff0c;接口响应用时会超过gateWay配置的全局用时&#xff0c;如果调整网关全局的超时时间和服务的全局超时时间是不合理的&#xff0c;故此想能否单独针对某个接口进行细粒度超时限制…

【应急响应靶场web2】

文章目录 前言 一、应急响应 1、背景 2、webshell查杀 3、日志排查 1&#xff09;apache日志 2&#xff09;nginx日志 3&#xff09;ftp日志 4、隐藏账户 5、文件筛选 二、漏洞复现 总结 前言 靶场来源&#xff1a;知攻善防实验室 一、应急响应 1、背景 小李在某…

【C语言】—— 指针一 : 初识指针(上)

【C语言】—— 指针一 &#xff1a; 初识指针&#xff08;上&#xff09; 一、内存和地址1.1、如何理解内存和地址1.2、 如何理解编址 二、指针变量和地址2.1、取地址操作符 &2.2、 指针变量2.3、 解引用操作符 ∗ * ∗2.4、指针变量的大小 三、指针变量类型的意义3.1、 指…

LeetCode每日一题——两数之和

两数之和OJ链接&#xff1a;1. 两数之和 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 思路&#xff1a; 在读懂题目后很多人觉得这种题目很简单&#xff0c;但是不管怎么写&#xff0c;在VS等其他编译器上能跑成功&#xff0c;但是在LeetCode上就是没办法通过。…

Java项目:54 springboot工资信息管理系统453

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本系统的使用角色可以被分为用户和管理员&#xff0c; 用户具有注册、查看信息、留言信息等功能&#xff0c; 管理员具有修改用户信息&#xff0c;发…

【回归预测】基于SSA-RF(麻雀搜索算法优化随机森林)的回归预测 多输入单输出【Matlab代码#66】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】1. 随机森林RF算法2. 麻雀搜索算法3. 实验模型4. 部分代码展示5. 仿真结果展示6. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章第6节&#xff1a;资源获取】 1. 随机森林RF算法 …

微信小程序-webview分享

项目背景 最近有个讨论区项目需要补充分享功能&#xff0c;希望可以支持在微信小程序进行分享&#xff0c;讨论区是基于react的h5项目&#xff0c;在小程序中是使用we-view进行承载的 可行性 目标是在打开web-view的页面进行分享&#xff0c;那就需要涉及h5和小程序的通讯问…

重学SpringBoot3-函数式Web

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-函数式Web 函数式Web编程简介RouterFunctionRequestPredicateServerRequestServerResponse 好处示例结论 随着响应式编程范式的兴起和 Java 函数式编程能…

Golang中map数据结构字段解析

Golang里map底层数据结构具体如下图所示&#xff1a; map其实就是一个指向 hmap 的指针&#xff0c;占用了8个字节 hmap各自段存放的字段意义如下&#xff1a; 字段含义countmap中元素的个数&#xff0c;对应len (map)的值flags状态标志位&#xff0c;标记map的一些状态B桶数…

基于SpringBoot和Echarts的全国地震可视化分析实战

目录 前言 一、后台数据服务设计 1、数据库查询 2、模型层对象设计 3、业务层和控制层设计 二、Echarts前端配置 1、地图的展示 2、次数排名统计 三、最终结果展示 1、地图展示 2、图表展示 总结 前言 在之前的博客中基于SpringBoot和PotsGIS的各省地震震发可视化分…

Hive-技术补充-初识ANTLR

一、背景 要清晰的理解一条Hql是如何编译成MapReduce任务的&#xff0c;就必须要学习ANTLR。下面是ANTLR的官方网址&#xff0c;下面让我们一起来跟着官网学习吧&#xff0c;在学习的过程中我参考了《antlr4权威指南》&#xff0c;你也可以读下这本书&#xff0c;一定会对你有…