C++ string模拟实现

news2024/12/26 21:21:22

目录

  • 模拟实现string的结构
  • 接口函数的实现
    • 构造函数和析构函数
    • 迭代器的实现
    • operator[]
    • reserve和resize
    • 三种尾插函数
    • insert
    • find
    • erase
    • substr
    • 赋值重载
    • 拷贝构造
    • 比较大小
    • 流提取,流插入
  • 完整代码

模拟实现string的结构

前面我们知道了string的结构比较复杂,这里我们实现简单一点的

string类的成员变量有
char* _str是指向堆中字符串的指针
_size表示有效字符的长度
_capacity表示空间容量

这里的实现方式类似于顺序表

我们还需定义一个静态成员npos
同时,我们也要把自己实现string类放在一个命名空间my_string

此时,就有了一个初步的一个框架:

namespace my_string
{
	class string
	{
		public:
		//成员函数
		private:
			char* _str;
			size_t _size;
			size_t _capacity;
		public:
			const static size_t npos;
	};
	const size_t string::npos = -1;
	
}

接口函数的实现

现在我们有三个成员变量,就可以实现3个函数:c_str(),size()capacity()

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

size_t size() const
{
	return _size;
}

size_t capacity() const
{
	return _capacity;
}

这三个函数不会修改成员函数,所以可以用const修饰this指针

构造函数和析构函数

这里我们只实现无参构造和用字符串构造
完全可以把这两种写成一个含有缺省参数的构造函数

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

_size的值就是str的长度,_capacity的值和_size相同
然后new出_capacity+1大小的空间

之所以要开辟_capacity大小的空间,是因为_size表示有效字符长度,_capacity表示可容纳有效字符的容量,都不包含’\0',但是在存储上还是需要存储'\0‘的,所以要在底层多开辟一个字符空间

然后这里最主要的一处就是memmove(_str, str, _size + 1)
把str中的值拷贝到新空间中,这是深拷贝

如果写成_str = str就成为浅拷贝了,后面析构时就会发生一块空间析构2次,报错

析构函数

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

迭代器的实现

string里的迭代器其实就是指针,我们可以把char*typedefiterator,const迭代器同理

typedef char* iterator;
typedef const char* const_iterator;

然后实现begin()end()

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

const_iterator begin()const
{
	return _str;
}

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

因为范围for的底层也是调用迭代器,所以此时实现了迭代器之后,我们的string也支持范围for了


operator[]

[]运算符的重载需要重载2个函数,一个函数是即可读也可写,一个是只可以读

这里我们可以用assert断言判断一下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];
}

reserve和resize

void reserve(size_t capacity)
{
	if (capacity > _capacity)
	{
		char* tmp = new char[capacity + 1];
		memmove(tmp, _str,_size+1);
		delete[] _str;
		_str = tmp;
		_capacity = capacity;
		tmp = nullptr;
	}
}

这里需要注意的一点是,把_str中的数据拷贝到tmp中时,需要使用memcpy或者memmove函数,虽然_strtmp是指向字符串的,本应使用strcpy但是strcpy会拷贝直到'\0',如果字符串中间有字符'\0'的话,数据挪动只挪动到'\0',会有数据缺失
所以必须要用memcpymrmmove,以_size+1为终止条件

所以要注意,在模拟实现string中,要用memcpymemmove替代所有strcpy

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

三种尾插函数

push_back尾插一个字符
需要先判断容量是否满,如果满了要扩容,我们这里指定的扩容策略是:二倍扩容。并且如果当前容量为0,就设置容量为4
接着就在_str[_size]处放入字符c,再在它后面加上\0

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

}

下面是2个append函数,一种参数是string对象,一种是字符串

首先判断是否需要扩容,接着就深拷贝数据过去

void append(const string& str)
{
	size_t len = str.size();
	if (size() + len > capacity())
	{
		reserve(size() + len);
	}
//	strcpy(_str+size(),str._str);
	memmove(_str + _size, str._str,len+1);
	_size += len;
}

void append(const char* c)
{
	size_t len = strlen(c);
	if (len + size() > capacity())
	{
		reserve(size() + len);
	}
	/*strcpy(_str + size(), c);*/
	memmove(_str + _size, c,len+1);
	_size += len;

}

operator+=函数
operator+=函数可以复用前面的push_back函数和append函数

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

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

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

insert

先实现插入n个字符chinsrt函数

首先还是判断是否需要扩容

assert(pos <= size());
if (n + size() > capacity())
{
	reserve(n + size());
}

接着需要挪动数据,把pos包括pos后面的数据向后挪动n个距离

size_t end = size();			
while (pos <= end)
{
	_str[end + n] = _str[end];
	end--;
}

这里其实有个错误,在判断条件中需要加上end != npos
因为end的类型为size_t无符号整形
假如pos的值为0时,pos==end==0时循环继续,end减去1后变为整形的最大值,仍然大于pos,死循环
所以才要加上判断end != npos

size_t end = size();
while (pos <= end && end != npos)
{
	_str[end + n] = _str[end];
	end--;
}

接着就是插入n个字符ch

for (int i = 0; i < n; i++)
{
	_str[pos + i] = ch;
}
_size += n;

完整代码:

void insert(size_t pos, size_t n, char ch)
{
	assert(pos <= size());
	if (n + size() > capacity())
	{
		reserve(n + size());
	}

	size_t end = size();

	//这里判断条件多了end != npos,是因为end的类型是size_t,无符号整形
	//假如pos的值为0时,pos==end==0时循环继续,end减去1后变为整形的最大值,仍然大于pos,死循环
	//所以才要加上判断end != npos
	while (pos <= end && end != npos)
	{
		_str[end + n] = _str[end];
		end--;
	}

	for (int i = 0; i < n; i++)
	{
		_str[pos + i] = ch;
	}
	_size += n;

}

然后我们就可以根据上面的写法,完成插入字符串和插入一个string对象的insert函数

void insert(size_t pos, const string& str)
{
	assert(pos <= size());
	int len = str.size();
	if (len + size() > capacity())
	{
		reserve(len + size());
	}

	size_t end = size();
	while (pos <= end && end != npos)
	{
		_str[end + len] = _str[end];
		end--;
	}

	for (int i = 0; i < len; i++)
	{
		_str[pos + i] = str._str[i];
	}

	_size += len;

}

void insert(size_t pos, const char* str)
{
	assert(pos <= size());

	int len = strlen(str);
	if (len + size() > capacity())
	{
		reserve(len + size());
	}

	size_t end = size();

	while (pos <= end && end != npos)
	{
		_str[end + len] = _str[end];
		end--;
	}

	const char* cur = str;
	int i = 0;
	while (*cur != '\0')
	{
		_str[pos + i] = *cur;
		cur++;
		i++;
	}
	
	_size += len;
}

find

这里实现查找一个字符和查找一个字符串的find函数

查找一个字符很简单,遍历一遍,看有没有和要查找一样的字符的,如果找到,返回下标,如果没找到,就返回npos

size_t find(char ch,size_t pos  = 0)
{
	assert(pos < size());
	for (int i = pos; i < size(); i++)
	{
		if (_str[i] == ch)
			return i;
	}

	return npos;
}

查找一个字符串:
我们使用C中的strstr查找字符串,如果找到了,会返回指向找到的字符串首个元素的指针,否则返回NULL

找到时,需要返回字符位置,这里可以使用指向同一块空间上的2个指针相减,就可以得到中间的元素个数,也就是字符位置的下标

size_t find(const char* str, size_t pos = 0)
{
	assert(pos < size());
	char* rp = strstr(_str + pos, str);
	if (rp == NULL)
	{
		return npos;
	}
	else
	{
		return rp - _str;
	}
}

erase

void erase(size_t pos, size_t n = npos)

如果n的值为npos或者pos+n>size(),就表示删除pos位置(包括pos)后所有字符

我们就直接把_str[pos]值改为'\0',接着改变_size的值为pos就可以

剩下的情况就是正常删除就可以了

void erase(size_t pos, size_t n = npos)
{
	assert(pos <= size());

	if (n == npos || pos + n > size())
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		int cur = pos + n;
		while (cur != size() + 1)
		{
			_str[pos] = _str[cur];
			pos++;
			cur++;
		}
		_size -= n;
	}
}

substr

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

如果len==npospos+len>size()就表示取从pos位置后所有字符

string substr(size_t pos = 0,size_t len  =npos) const
{
	assert(pos < _size);
	size_t n = len;
	if (len == npos || pos + len > _size)
	{
		n = _size - pos;
	}

	string tmp;
	tmp.reserve(n);
	for (size_t i = pos; i < n + pos; i++)
	{
		tmp += _str[i];
	}

	return tmp;
}

赋值重载

赋值重载有2中写法,一种是传统写法,一种是现代写法

传统写法很简单,就是实现一个深拷贝就可以了

string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[s._capacity + 1];
		memmove(tmp, s._str, s._size + 1);
		delete[] _str;
		_str = tmp; 
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

现代写法的思想:
a = b;是一个赋值重载的调用,以往在赋值重载中需要写将b的_size,_capacity拷贝给a

最主要的时,需要先delete掉a._str在堆上的空间,再开辟一个和b._str一样的空间给a

那么在函数已经拷贝出了一个临时对象tmp,既然这个临时对象在函数结束后会自动销毁,不如交换this和tmp的_str各个成员变量的值

这样在函数结束后,tmp自动被销毁,tmp._str指向的空间被销毁,这块空间就是之前this->_str指向的空间
我们不必再去手动销毁这段空间了

在这里插入图片描述

string& operator=(const string& s)
{
	if (this != &s)
	{
		string tmp(s);

		std::swap(_str, tmp._str);
		std::swap(_size, tmp._size);
		std::swap(_capacity, tmp._capacity);
	}

	return *this;
}

我们也可以把三个std::swap函数封装起来写成一个函数

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

string& operator=(const string& s)
{
	if (this != &s)
	{
		string tmp(s);

		std::swap(_str, tmp._str);
		std::swap(_size, tmp._size);
		std::swap(_capacity, tmp._capacity);
	}

	return *this;
}

下面还有一种更简单的写法:

上面的那个写法还需创建一个tmp临时对象,这里直接用一个非引用类型的形参
这个形参在函数结束后也就自行调用析构函数被清理了

string& operator=(string tmp)
{
	swap(tmp);

	return *this;
}

拷贝构造

拷贝构造也有传统写法和现代写法

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

拷贝构造函数现代写法

这种写法的拷贝构造需要写初始化列表,对this的成员进行初始化
因为对于拷贝构造对于成员的默认初始化,在不同的平台,不同版本的编译器是不同的,有可能会进行初始化,也有可能不会初始化
如果没有进行初始化,和tmp swap完之后tmp的成员都是随机值
然后tmp调用析构函数时,销毁一块随机空间,会报错

string(const string& s)
      :_str(nullptr)
	  ,_size(0)
	  ,_capacity(0)
{
	string tmp(s._str);
	swap(tmp);
}

而且对于现代写法,遇到中间有’\0’的字符串,只会拷贝到’\0’以前,所以对于拷贝构造,传统写法更好一点


比较大小

字符串是根据每个字符的ASCII来判断大小

我们逐个字符判断大小

while (i1 < _size && i2 < s._size)
{
	if (_str[i1] < s._str[i2])
	{
		return true;
	}
	else if (_str[i1] > s._str[i2])
	{
		return false;
	}
	else
	{
		i1++;
		i2++;
	}
}

然后如果知道某一字符串遍历完了还没有判断出哪个字符串小,我们就需要判断字符串长度来判断,字符串长的就大
比如:“hello”就比"helloworld"小

bool operator<(const string& s)const
{
	size_t i1 = 0;
	size_t i2 = 0;
	while (i1 < _size && i2 < s._size)
	{
		if (_str[i1] < s._str[i2])
		{
			return true;
		}
		else if (_str[i1] > s._str[i2])
		{
			return false;
		}
		else
		{
			i1++;
			i2++;
		}
	}
	
	if (_size < s._size)
	{
		return true;
	}
	else
	{
		return false;
	}
}

operator==:

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

接着就可以复用这2个函数,完成其余比较大小的运算符重载

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

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

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

流提取,流插入

流提取,流插入我们要写在类外


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

	//C的字符数组,以\0算长度
	//string不看\0,以_size为终止长度
}
void clear() //在流提取重载中清理缓冲区使用
{
		_str[0] = '\0';
		_size = 0;
}

std::istream& operator>>(std::istream& in, my_string::string& str)
{
	str.clear();//清除掉同一个string对象中缓冲区中的内容


	//使用in.get()是因为,get()函数可以读取任何字符包括:' '和'\n'
	//而使用in>>ch读到' '或 '\n'时会卡住(阻塞)
	char ch = in.get();

	//下面这么写是处理前缓冲区前面的空格或者换行
	//保证输入:"空格空格空格空格hello" 的时候可以正常读取到"hello"
	//以及输入"换行换行换行换行换行hello" 时可以正常读取到"hello"
	while (ch == ' ' || ch == '\n')
	{
		ch = in.get();
	}

	//设置一个char类型的数组,是因为如果没读取一个字符就+=到str中,会频繁开空间,有消耗
	//在栈上开辟一个数组消耗小,所以先把读取的字符放到buff里,等buff"满"的时候,=再把这个数组+=到str中
	char buff[128];
	int i = 0;

	//每读取到一个字符,如果不是空格或换行,就+=到string里,继续读取
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;

		//如果buff"满"的时候,要在最后加上'\0',把buff加到str里
		//此时还需继续读取,就把i赋为0,让buff再从头存储
		if (i == 127)
		{
			buff[i] = '\0';
			str += buff;
			i = 0;
		}
		//str += ch;
		ch = in.get();
	}


	//如果读取的字符个数没有超过128,同时也读取到了字符,就在当前i下标处赋'\0'
	//把buff加到str中
	if (i != 0)
	{
		buff[i] = '\0';
		str += buff;
	}


	return in;
}

完整代码

#pragma once
#include <iostream>
#include<assert.h>

namespace my_string
{
	class string
	{

	public:


		typedef char* iterator;
		typedef const char* const_iterator;
		//范围for的底层其实就是使用迭代器,所以这里实现了迭代器之后,就自然而然地支持范围for循环了


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


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



		//拷贝构造函数现代写法

		//这种写法的拷贝构造需要写初始化列表,对this的成员进行初始化
		//因为对于拷贝构造对于成员的默认初始化,在不同的平台,不同版本的编译器是不同的,有可能会进行初始化,也有可能不会初始化
		//如果没有进行初始化,和tmp swap完之后tmp的成员都是随机值
		//然后tmp调用析构函数时,销毁一块随机空间,会报错

		/*string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}*/

		//而且对于现代写法,遇到中间有'\0'的字符串,只会拷贝到'\0'以前
		//所以对于拷贝构造,传统写法更好一点


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

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

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}


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

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

		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 capacity)
		{
			if (capacity > _capacity)
			{
				char* tmp = new char[capacity + 1];
				//strcpy(tmp, _str);
				memmove(tmp, _str,_size+1);
				delete[] _str;
				_str = tmp;
				_capacity = capacity;
				tmp = nullptr;
			}
		}

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


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

		}

		void append(const string& str)
		{
			size_t len = str.size();
			if (size() + len > capacity())
			{
				reserve(size() + len);
			}
		//	strcpy(_str+size(),str._str);
			memmove(_str + _size, str._str,len+1);
			_size += len;
		}

		void append(const char* c)
		{
			size_t len = strlen(c);
			if (len + size() > capacity())
			{
				reserve(size() + len);
			}
			/*strcpy(_str + size(), c);*/
			memmove(_str + _size, c,len+1);
			_size += len;

		}

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

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

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

		void insert(size_t pos, const string& str)
		{
			assert(pos <= size());
			int len = str.size();
			if (len + size() > capacity())
			{
				reserve(len + size());
			}

			size_t end = size();
			while (pos <= end && end != npos)
			{
				_str[end + len] = _str[end];
				end--;
			}

			for (int i = 0; i < len; i++)
			{
				_str[pos + i] = str._str[i];
			}

			_size += len;

		}

		void insert(size_t pos, const char* str)
		{
			assert(pos <= size());

			int len = strlen(str);
			if (len + size() > capacity())
			{
				reserve(len + size());
			}

			size_t end = size();

			while (pos <= end && end != npos)
			{
				_str[end + len] = _str[end];
				end--;
			}

			const char* cur = str;
			int i = 0;
			while (*cur != '\0')
			{
				_str[pos + i] = *cur;
				cur++;
				i++;
			}
			
			_size += len;
		}


		void insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= size());
			if (n + size() > capacity())
			{
				reserve(n + size());
			}

			size_t end = size();

			//这里判断条件多了end != npos,是因为end的类型是size_t,无符号整形
			//假如pos的值为0时,pos==end==0时循环继续,end减去1后变为整形的最大值,仍然大于pos,死循环
			//所以才要加上判断end != npos
			while (pos <= end && end != npos)
			{
				_str[end + n] = _str[end];
				end--;
			}

			for (int i = 0; i < n; i++)
			{
				_str[pos + i] = ch;
			}
			_size += n;

		}

		void erase(size_t pos, size_t n = npos)
		{
			assert(pos <= size());

			if (n == npos || pos + n > size())
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				int cur = pos + n;
				while (cur != size() + 1)
				{
					_str[pos] = _str[cur];
					pos++;
					cur++;
				}
				_size -= n;
			}
		}
		
		size_t find(char ch,size_t pos  = 0)
		{
			assert(pos < size());
			for (int i = pos; i < size(); i++)
			{
				if (_str[i] == ch)
					return i;
			}

			return npos;
		}

		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < size());
			char* rp = strstr(_str + pos, str);
			if (rp == NULL)
			{
				return npos;
			}
			else
			{
				return rp - _str;
			}
		}
		
		string substr(size_t pos = 0,size_t len  =npos) const
		{
			assert(pos < _size);
			size_t n = len;
			if (len == npos || pos + len > _size)
			{
				n = _size - pos;
			}

			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < n + pos; i++)
			{
				tmp += _str[i];
			}

			return tmp;
		}


		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				memmove(tmp, s._str, s._size + 1);
				delete[] _str;
				_str = tmp; 
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}*/


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

		//赋值重载现代写法
		
		// a = b;是一个赋值重载的调用,以往在赋值重载中需要写将b的_size,_capacity拷贝给a
		// 最主要的时,需要先delete掉a._str在堆上的空间,再开辟一个和b._str一样的空间给a
		// 那么在函数已经拷贝出了一个临时对象tmp,既然这个临时对象在函数结束后会自动销毁,不如交换this和tmp的_str各个成员变量的值
		// 这样在函数结束后,tmp自动被销毁,tmp._str指向的空间被销毁,这块空间就是之前this->_str指向的空间
		// 我们不必再去手动销毁这段空间了
		// 
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		string tmp(s);

		//		std::swap(_str, tmp._str);
		//		std::swap(_size, tmp._size);
		//		std::swap(_capacity, tmp._capacity);

		//		//swap(tmp);
		//	}

		//	return *this;

		//}


		//这种写法更加简介
		//上面的那个写法还需创建一个tmp临时对象,这里直接用一个非引用类型的形参
		//这个形参在函数结束后也就自行调用析构函数被清理了
		string& operator=(string tmp)
		{
			swap(tmp);

			return *this;
		}

		void clear() //在流提取重载中清理缓冲区使用
		{
			_str[0] = '\0';
			_size = 0;
		}


		bool operator<(const string& s)const
		{
			size_t i1 = 0;
			size_t i2 = 0;
			while (i1 < _size && i2 < s._size)
			{
				if (_str[i1] < s._str[i2])
				{
					return true;
				}
				else if (_str[i1] > s._str[i2])
				{
					return false;
				}
				else
				{
					i1++;
					i2++;
				}
			}
			
			if (_size < s._size)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		
		bool operator==(const string& s)const
		{
			return _size == s._size && memcmp(_str, s._str, _size)==0;
		}

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


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

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


	private:

		char* _str;
		size_t _size;
		size_t _capacity;
	public:
		const static size_t npos;
	};

	const size_t string::npos = -1;


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

		//C的字符数组,以\0算长度
		//string不看\0,以_size为终止长度
	}


	std::istream& operator>>(std::istream& in, my_string::string& str)
	{
		str.clear();//清除掉同一个string对象中缓冲区中的内容


		//使用in.get()是因为,get()函数可以读取任何字符包括:' '和'\n'
		//而使用in>>ch读到' '或 '\n'时会卡住(阻塞)
		char ch = in.get();

		//下面这么写是处理前缓冲区前面的空格或者换行
		//保证输入:"空格空格空格空格hello" 的时候可以正常读取到"hello"
		//以及输入"换行换行换行换行换行hello" 时可以正常读取到"hello"
		while (ch == ' ' || ch == '\n')
		{
			ch = in.get();
		}

		//设置一个char类型的数组,是因为如果没读取一个字符就+=到str中,会频繁开空间,有消耗
		//在栈上开辟一个数组消耗小,所以先把读取的字符放到buff里,等buff"满"的时候,=再把这个数组+=到str中
		char buff[128];
		int i = 0;

		//每读取到一个字符,如果不是空格或换行,就+=到string里,继续读取
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;

			//如果buff"满"的时候,要在最后加上'\0',把buff加到str里
			//此时还需继续读取,就把i赋为0,让buff再从头存储
			if (i == 127)
			{
				buff[i] = '\0';
				str += buff;
				i = 0;
			}
			//str += ch;
			ch = in.get();
		}


		//如果读取的字符个数没有超过128,同时也读取到了字符,就在当前i下标处赋'\0'
		//把buff加到str中
		if (i != 0)
		{
			buff[i] = '\0';
			str += buff;
		}
		return in;
	}
};

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

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

相关文章

将 Pandas 换为交互式表格的 Python 库

Pandas是我们日常处理表格数据最常用的包&#xff0c;但是对于数据分析来说&#xff0c;Pandas的DataFrame还不够直观&#xff0c;所以今天我们将介绍4个Python包&#xff0c;可以将Pandas的DataFrame转换交互式表格&#xff0c;让我们可以直接在上面进行数据分析的操作。 Piv…

阿里云ECS服务器安装PostgreSQL

1. 概述 PostgreSQL是一个功能强大的开源数据库&#xff0c;它支持丰富的数据类型和自定义类型&#xff0c;其提供了丰富的接口&#xff0c;可以自行扩展其功能&#xff0c;支持使用流行的编程语言编写自定义函数 PostgreSQL数据库有如下优势&#xff1a; PostgreSQL数据库时…

浅尝OpenResty

文章目录 1. 写在前面2. 下载安装openresty2.1 下载Openresty2.2 设置nginx启动 3. 嵌入lua脚本4. 实践5. 小结 1. 写在前面 当一个域名中衍生出多个服务的时候&#xff0c;如果想要保持对外服务始终是一个域名&#xff0c;则需要通过nginx反向代理来实现。如果在转发的时候需…

Pixar、Adobe 和苹果等成立 OpenUSD 联盟推行 3D 内容开放标准

导读Pixar、Adobe、Apple、Autodesk 与 NVIDIA 联手 Linux 基金会旗下的联合开发基金会&#xff08;JDF&#xff09;宣布建立 OpenUSD 联盟&#xff08;AOUSD&#xff09;以推行 Pixar 创建的通用场景描述技术的标准化、开发、进化和发展。 联盟寻求通过推进开放式通用场景描述…

[附源码]计算机毕业设计-JAVA火车票订票管理系统-springboot-论-文-ppt

PPT论文 文章目录 前言一、主要技术javaMysql数据库JSP技术 二、系统设计三、功能截图总结 前言 本论文主要论述了如何使用JAVA语言开发一个火车订票管理系统 &#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想…

走嵌入式还是纯软件?学长告诉你怎么选

最近有不少理工科的本科生问我&#xff0c;未来是走嵌入式还是纯软件好&#xff0c;究竟什么样的同学适合学习嵌入式呢&#xff1f;在这里我整合一下给他们的回答&#xff0c;根据自己的经验提供一些建议。 嵌入式领域也可以分为单片机方向、Linux方向和安卓方向。如果你的专业…

魏副业而战:闲鱼卖货怎么取得更大的成就

我是魏哥&#xff0c;与其躺平&#xff0c;不如魏副业而战&#xff01; 社群成员小H又办证了&#xff0c;他想干什么&#xff1f; 这是他办了的第3个证了&#xff0c;这就意味这他有9家闲鱼店铺了。 之前有跟他聊过闲鱼卖货&#xff0c;想要在闲鱼上取得更大的成就&#xff…

【zabbix企业级监控】

目录 Zabbix Zabbix特点 实验环境准备 Server端 agent端 server端 配置阿里云yum源 启动LAMP对应服务 准备java环境 源码安装zabbix Mariadb数据库授权 创建zabbix程序用户并授权防止权限报错 修改zabbix配置文件 配置php与apache web安装zabbix Zabbix页面优化…

YOLOv8+BoT-SORT多目标跟踪(行人车辆计数与越界识别)

课程链接&#xff1a;https://edu.csdn.net/course/detail/38919 BoT-SORT是发表于2022年的先进的多目标跟踪算法&#xff0c;它结合了运动和外观信息、相机运动补偿和更准确的卡尔曼滤波状态向量&#xff0c;并把这些改进集成到ByteTrack&#xff0c;从而在MOTA、IDF1和HOTA性…

数据库结构差异对比工具

简介 前几年写了一个数据库对比工具&#xff0c;但是由于实现方式的原因&#xff0c;数据库支持有限&#xff0c;所以重新设计了一下&#xff0c;便于支持多种数据库&#xff0c;并且更新了UI。 新版地址&#xff1a;https://gitee.com/xgpxg/db-diff 旧版地址&#xff1a;h…

一文入门最热的LLM应用开发框架LangChain

在人工智能领域的不断发展中&#xff0c;语言模型扮演着重要的角色。特别是大型语言模型&#xff08;LLM&#xff09;&#xff0c;如 ChatGPT&#xff0c;已经成为科技领域的热门话题&#xff0c;并受到广泛认可。 在这个背景下&#xff0c;LangChain 作为一个以 LLM 模型为核…

【CC精品教程】ContextCapture 10.20安装教程(附CC10.20安装包下载)

文章目录 一、ContextCapture 10.20安装1. 安装主程序2. 打补丁3. 安装中文包二、ContextCapture 10.20下载一、ContextCapture 10.20安装 订阅《无人机航空摄影测量精品教程》专栏(可以免费获取安装包,也可以查看更多专业航测教程)。 1. 安装主程序 下载打开安装包,如下图…

漏洞复现 || 某我行CRM系统SQL注入

漏洞描述 某任我行 CRM SmsDataList 接口处存在SQL注入漏洞&#xff0c;未经身份认证的攻击者可通过该漏洞获取数据库敏感信息及凭证&#xff0c;SenderTypeId参数存在注入&#xff0c;最终可能导致服务器失陷。 免责声明 技术文章仅供参考&#xff0c;任何个人和组织使用网…

如何找到一个数的所有质因数,以及如何快速判断一个数是不是质数

前情介绍 今天遇到一个需求&#xff1a;找到一个数所有的质因数。 初步解决 先定义一个判断质数的函数&#xff1a; def is_Prime(number):i 2count 0while i < number:if number % i 0 :count 1i 1if count > 0:return Falseelse:return True 接着定义一个寻找质…

信看课堂笔记—电路若只如初见

本节课结合我们模块经常遇到的电子元器件和电路讲解下原理和方案选型 认识电阻、电容和电感 以下是电阻、电容和电感的作用的简要对比表格&#xff1a; 作用 电阻 电容 电感 限制电流 通过阻碍电流流动&#xff08;欧姆定律IU/R&#xff09; 阻止直流电流通过 随频…

JDBCTEMPLATE 的基本使用----查询操作26

1、再讲一下聚合查询&#xff0c;首先我们先讲一下查询全部&#xff0c;copy一下代码&#xff1a;ROWapper是一个接口&#xff0c;返回的数据是List 1.1 Row 是行&#xff0c; Mapper映射&#xff0c;我们要用接口帮我们去完成数据实体类的封装这件事情&#xff1a; 2&#xf…

轻量级 Bean 实体校验器

简介 概述 利用 Spring 自带校验器结合 JSR 注解实现轻量级的 Bean 实体校验器。轻捷、简单、很容易上手&#xff0c;也容易扩展。 三个核心类ValidatorInitializing、ValidatorImpl、ValidatorEnum去掉注释不超过共200行源码实现 10多m 的 Hibernate Validator 多数功能。 …

使用VS code 编辑器 导出、导入和运行Excel中的VBA代码

使用VS code 编辑器 导出、导入和运行Excel中的VBA代码 前言 Excel自带的 Microsoft Visual Basic for Applications 编辑器常被人称为上古编辑器&#xff0c;的确不适合代码编辑&#xff0c;这是其一&#xff0c;其二是当系统语言与Excel的安装语言不一致时&#xff0c;往往出…

网络安全---负载均衡案例

一、首先环境配置 1.上传文件并解压 2.进入目录下 为了方便解释&#xff0c;我们只用两个节点&#xff0c;启动之后&#xff0c;大家可以看到有 3 个容器&#xff08;可想像成有 3 台服务器就成&#xff09;。 二、使用蚁剑去连接 因为两台节点都在相同的位置存在 ant.jsp&…

从关键新闻和最新技术看AI行业发展(2023.7.10-7.23第三期) |【WeThinkIn老实人报】

Rocky Ding 公众号&#xff1a;WeThinkIn 写在前面 【WeThinkIn老实人报】本栏目旨在整理&挖掘AI行业的关键新闻和最新技术&#xff0c;同时Rocky会对这些关键信息进行解读&#xff0c;力求让读者们能从容跟随AI科技潮流。也欢迎大家提出宝贵的优化建议&#xff0c;一起交流…