【C++初阶】学习string类的模拟实现

目录

  • 前言:
  • 一、创建文件和类
  • 二、实现string类
    • 2.1 私有成员和构造函数
    • 2.2 析构函数
    • 2.3 拷贝构造函数
      • 2.3.1 写法1
      • 2.3.2 写法2
    • 2.4 赋值重载函数
      • 2.4.1 写法1
      • 2.4.2 写法2
    • 2.5 迭代器遍历访问
    • 2.6 下标遍历访问
    • 2.7 reserve
    • 2.8 resize
    • 2.9 判空和清理
    • 2.10 尾插
      • 2.10.1 尾插字符
      • 2.10.2 尾插字符串
    • 2.11 +=运算符重载
    • 2.12 插入
      • 2.12.1 插入字符
      • 2.12.2 插入字符串
    • 2.13 删除
    • 2.14 查找
    • 2.15 截取子串
    • 2.16 关系运算符重载
    • 2.17 >>和<<
  • 三、全部代码
    • 3.1 string.h
    • 3.2 test.cpp

前言:

前面已经学习了string类的用法,这篇文章将更深入的学习string类,了解string类的底层是怎么实现的。当然,这里只是模拟一些常用的,不常用的可以看文档学习。

一、创建文件和类

我们一共创建两个文件,一个是test.cpp文件,用于测试;另一个是string.h文件,用于声明和定义要模拟的string类。模拟的string类会与C++标准库里的string类冲突,所以这里可以使用命名空间来解决这个问题。

namespace yss
{
	class string
	{
	public:
	private:
	};
}

我们要模拟的string类在yss这个命名空间里,所以等会使用这个string类就去yss里找。

注:声明和定义都在头文件里写

二、实现string类

2.1 私有成员和构造函数

私有成员变量主要有:

_str——字符数组
_size——字符有效个数
_capacity——数组总空间大小(总空间大小体现在数组开辟的空间大小)
npos——静态成员常量,无符号整型的最大值

private:
		size_t _size;
		size_t _capacity;
		char* _str;
		const static size_t npos = -1;

为什么声明顺序不是先字符串,这个等会再讨论,先来看下构造函数

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

这里我们定义_capacity 与_size 相同,方便后续开辟空间。_size 可以用strlen计算字符串个数,为什么_capacity 不用?因为再使用一个strlen又要遍历一遍字符串,加大了时间复杂度。

注意:strlen计算字符串长度不包括斜杠0

前面因为总空间大小和有效字符个数一样,又因为字符串末尾要有斜杠0,所以实际开辟的字符数组的空间大小比_capacity 多一个位置,用于放斜杠0;_str 字符数组的空间开辟好后,就把参数字符串拷贝过来即可。

注意:strcpy会连同斜杠0一起拷贝

在这里插入图片描述

如果参数为空字符串该咋办?可以给缺省值,但是不能为斜杠0,因为斜杠0是字符类型,而我们这里的形参是字符串类型,虽然可以发生类型转换,但毕竟不好;nullptr指针呢?也不行,因为如果我们没有传参那么它就是空指针,最后要打印对空指针解引用不就报错了吗。所以这里给个空字符串即可。

回到前面的声明私有成员变量的问题,我们知道声明的顺序就是初始化列表的顺序,但是前面的代码没有用到初始化列表。其实前面的代码只是一种写法,构造函数也可以使用初始化列表,或者初始化列表与函数体混着使用,如果用初始化列表就得注意声明顺序了,所以为了防止出错,声明顺序就与我们在构造函数里成员变量定义的顺序相同。

验证一下前,我们还要实现一个返回C格式的函数,顺便把返回字符有效个数、交换和返回容量也写下,方便后面操作:

//C格式返回
const char* c_str() const
{
	return _str;
}
//返回字符个数
const size_t size() const
{
	return _size;
}
//返回容量
const size_t capacity() const
{
	return _capacity;
}
//交换
void swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

运行结果:
在这里插入图片描述

总结:
一:缺省值给空字符串
二:多开一个位置放斜杠0

2.2 析构函数

析构函数与我们以前的destroy()函数差不多,作用是清理空间。要注意下这里使用的是与new[] 匹配的delete[] ,不能把中括号漏了。

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

2.3 拷贝构造函数

2.3.1 写法1

之前的文章提过,我们不写编译器会默认生成它的拷贝构造,但这是浅拷贝,对于非指针变量没有太大关系,但如果是指针成员变量,导致两个指针指向同一块空间,会有一块内存重复释放的风险,所以要用深拷贝,额外开一块空间,自己来实现。

先上代码:

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

深拷贝意味着要临时多开一块空间,这里多开的一块空间就是_str的,因为_str是刚被初始化的对象的字符串,所以给它开辟空间,方式与前面的构造函数相同,_size 与_capacity 值拷贝。

str指的是s._str
在这里插入图片描述
运行结果:
在这里插入图片描述

2.3.2 写法2

这个写法更简化:

	string(const string& s)
	{
		string tmp(s._str);
		swap(tmp);
	}

也要额外开辟一块空间,定义一个string类型的变量tmp,构造的内容与要拷贝的内容相同,然后将s2的内容与tmp交换就行了。

图示:
在这里插入图片描述
s2就是用s1拷贝构造出来的对象

因为tmp是局部变量,出函数作用域要被销毁掉,拷贝构造结束调用析构清理tmp。(交换后tmp指向空,释放空指针不进行任何操作)

2.4 赋值重载函数

2.4.1 写法1

赋值重载类似拷贝构造,也要有一个额外空间。

代码:

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

返回值是string&可以减少拷贝,效率高;开始的 if 语句是为了判断this指针与要赋值的对象是否相同(函数参数列表里的取地址符是引用,if 判断里的是取地址),如果相同,就没必要赋值了,因为是自己赋值自己。如果不同,才可以进行赋值。

创建一个临时的空间,把要赋值的内容拷贝到临时空间里,然后把this指针指向的原来的空间销毁掉(this指针不显示写,还有this指针的字符串原来是有自己的一块空间的),然后指向这块临时的空间,其他成员变量值拷贝。最后返回this指针。

图:
在这里插入图片描述
运行结果:
在这里插入图片描述

2.4.2 写法2

赋值重载的另一个写法也很简洁:

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

形参s没有引用,说明它是实参的一份拷贝,独立占一块空间,正因为独立占一块空间,所以它就可以作为this指针的_str交换的临时对象。
在这里插入图片描述

如果传的是引用,形参s与原来的被赋值的对象共用一块空间,交换的话不就把被赋值的对象的内容改变了吗
在这里插入图片描述
好了,既然如此,那么不加引用,可是为什么不加const了呢?注意,不是什么情况都能加const。如果这里加上const,那么s就不可以改变了,交换就是改变s,会报错。
在这里插入图片描述

注意一下:拷贝构造函数的参数列表里也可以不加const,但是某些情况就必须加const,这“某些情况”后面会提到。此时这里加不加const都没关系,但为了保险起见,一般带上const较好

2.5 迭代器遍历访问

有两种写法,先来看第一种,没有const的

typedef char* iterator;
iterator begin()
{
	return _str;
}
iterator end()
{
	return _str + _size;
}

iterator是迭代器,我们以重命名的方式让它成为字符指针的别名。begin返回的是指向首元素地址的指针,end返回的是指向斜杠0的指针。
在这里插入图片描述
遍历方式是定义一个指针刚开始指向字符串首元素的位置,然后循环不等于斜杠0就打印,直到遇到斜杠0跳出。

	yss::string s1("hello world");
	yss::string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;

运行结果:
在这里插入图片描述

有const的写法:

typedef const char* const_iterator;
const_iterator begin() const
{
	return _str;
}
const_iterator end() const
{
	return _str + _size;
}

用法是上面的一样,只不过不可以修改值,只能打印。
在这里插入图片描述

2.6 下标遍历访问

也有const和非const的写法,使用方式就是常见的下标遍历

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

使用assert可以预防pos下标越界的问题

在这里插入图片描述

2.7 reserve

如果要插入字符或字符串,当字符数组的空间大小不满足需求时,就要进行扩容。模拟实现的函数是reserve。

reserve:
1.不改变有效字符个数,不影响字符串内容,只影响空间大小
2.当参数大于空间总大小时,空间总大小增加;小于等于时不变
3.不会缩容

代码:

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

扩容的也需要开辟一块临时空间tmp,tmp的实际空间大小为n+1(多一个放斜杠0),将_str的内容拷贝到tmp临时空间里,然后销毁掉原来的空间,指向新的空间,原来的空间大小_capacity扩到n,完成扩容。

在这里插入图片描述

2.8 resize

resize可以改变字符串的有效字符个数,当传入的参数n小于原来有效字符个数时,有效字符个数为n,同时字符串的内容也发生改变;如果大于,在原来字符串的末尾填充字符,填充的个数为参数减去原来的有效字符个数。

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

n小于等于_size,就在下标为n的位置改为斜杠0,(等于_size就是原来斜杠0的位置放斜杠0)。n大于有效字符个数的情况:如果n大于空间大小,就要扩容;只要大于不管有没有扩容都要在末尾逐个插入字符,然后在下标为n处放置斜杠0(这里不用担心越界问题,有或者没有扩容,实际字符数组都有多一个位置来处理这个斜杠0)。最后有效字符个数要改为n。

运行结果:
在这里插入图片描述

2.9 判空和清理

如果有效字符个数为0返回真,否则假(打印结果真为1,假为0)。清理的是有效字符个数,对空间大小没有影响

// 判空
bool empty()
{
	return _size == 0;
}
// 清理
void clear()
{
	_size = 0;
	_str[0] = '\0';
}

在这里插入图片描述

注意:清理函数里如果字符串的首元素不改为斜杠0,清理完后再打印它依然可以打印出这个字符串,因为返回C格式字符串是有带斜杠0返回的,也就是说正常情况下打印这个字符串到它的斜杠0位置结束,所以这里的意思是函数里如果字符串的首元素不改为斜杠0,在实际的空间里这个字符串于原来没被清理的状态一样,只是有效字符个数_size改变了而已。在字符串的首元素改为斜杠0,那么打印时遇到斜杠0停下,即什么都没有打印出来,这个字符串就与被清理的效果一样了。

2.10 尾插

2.10.1 尾插字符

尾插字符要注意空间是否够用,不够要扩容

代码:

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

当_size等于_capacity就要扩容,扩到原来_capacity的两倍大小;然后尾插字符,有效字符个数加1,再最后放斜杠0
在这里插入图片描述
运行结果:
在这里插入图片描述

2.10.2 尾插字符串

先要计算要插入的字符串的字符个数,如果原来字符串的有效个数加上插入的字符串的字符个数大于空间大小,就要扩容。然后进行尾插,有效字符个数为相加的结果。

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

为什么这里是_size + len > _capacity而不是>=
假设原来的字符串的_size=8,_capacity=9,新插入的字符串就一个字符。此时_capacity还有一个位置刚好可以放这一个字符,所以这种情况就没必要扩容。还有一种情况是要插入的字符串为空串,那么最后得到的还是自己原来的字符串,就算原来的字符串_size == _capacity,也不需要扩容,因为此时又没有要插入的东西。(或者说插入空串相当于啥也没插入)

在这里插入图片描述

2.11 +=运算符重载

+=运算符可以实现两种尾插方式,既可以尾插字符,也可以尾插字符串

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

复用前面的函数,然后返回this指针

在这里插入图片描述

2.12 插入

2.12.1 插入字符

插入操作都要考虑是否要扩容,扩容与前面尾插字符一样。插入的位置可能是头也可能是中间,所以要挪动数据。

void insert(char ch, size_t pos)
{
	assert(pos < _size);
	//插入要考虑是否要扩容
	if (_size == _capacity)
	{
		size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
		reserve(newcapacity);
	}
	//挪动数据
	int end = _size;
	while (end >= (int)pos)//强转类型
	{
		_str[end + 1] = _str[end];
		--end;
	}
	_str[pos] = ch;
	_size++;
}

在这里插入图片描述
运行结果:
在这里插入图片描述

2.12.2 插入字符串

主要这几点,考虑是否扩容,挪动数据,拷贝字符串

代码:

void insert(const char* str, size_t pos)
{
	assert(pos < _size);
	size_t len = strlen(str);
	//考虑是否扩容
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	//挪动数据
	int end = _size;
	while (end >= (int)pos)
	{
		_str[end + len] = _str[end];
		--end;
	}
	strncpy(_str + pos, str, len);
	_size++;
}

这里的拷贝字符串就跟前面的不一样了,strncpy会把要插入的字符串拷贝到中间空出来的位置去,并且会控制个数,即在中间某个位置插入一个字符串,这个位置之后原来的字符串没有被覆盖掉,如果是用strcpy,那么插入的位置之后原来的字符串就都被要插入的字符串覆盖。

在这里插入图片描述
运行结果:
在这里插入图片描述

2.13 删除

如果要删除的个数len是默认的缺省值,从pos位置开始后面全部删除;或者pos的值加上len大于等于字符个数,也是从pos位置开始后面全部删除,pos位置就放斜杠0,有效字符个数变为pos的值。从pos开始后面不全删,就把pos位置加len后的字符串覆盖到pos位置之后,_size减去len。
在这里插入图片描述

代码:

void erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	// 从pos开始后面都删除
	if (len == npos || pos + len >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	//从pos开始后面不全删
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
}

运行结果:
在这里插入图片描述

2.14 查找

查找字符/字符串
从左往右查找字符,找到了返回该字符的下标,否则返回npos

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

从左往右查找字符串,使用字符串函数strstr,定义一个变量得到字符串函数的返回结果,这个返回结果如果是空,说明没找到,就返回npos,否则返回该指针的下标,即找到字符串的第一个字符下标。

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

运行结果:
在这里插入图片描述

2.15 截取子串

代码:

string substr(size_t pos = 0, size_t len = npos)
{
	assert(pos < _size);
	size_t end = pos + len;
	if (len == npos || pos + len >= _size)
	{
		end = _size;
	}
	string ss;
	ss.reserve(end - pos);
	for (size_t i = pos; i < end; i++)
	{
		ss += _str[i];
	}
	return ss;
}

先定义一个变量end确定截取的字符串在源字符串的最后位置,假设pos+len的值就是end指向的下标处。如果len是缺省值,或者pos加上要len大于_size,end就等于_size;不满足条件,就是假设的值。定义一个对象,使其尾插原字符串从pos位置到end位置的字符,最后返回的就是截取的字符串。

运行一下:
在这里插入图片描述

这里就可以解决前面的一个问题了:拷贝构造函数的第二种写法某些情况必须加const

刚刚写的截取字符串是传值返回,返回的是返回值的临时拷贝,这个临时对象具有常属性,用其他对象接收时要带上const

图示:
在这里插入图片描述
可能有人会想这里为什么要用引用接收,不用引用行不行?答案是绝对不行,因为没有引用,拷贝构造会无限递归,一直循环调用。

2.16 关系运算符重载

代码:

		//大于
		bool operator>(string& s) const
		{
			return strcmp(_str, s._str) > 0;
		}
		//等于
		bool operator==(string& s) const
		{
			return strcmp(_str, s._str) == 0;
		}
		//大于等于
		bool operator>=(string& s) const
		{
			return (*this == s && *this > s);
		}
		//小于
		bool operator<(string& s) const
		{
			return !(*this >= s);
		}
		//小于等于
		bool operator<=(string& s) const
		{
			return !(*this > s);
		}
		//不等于
		bool operator!=(string& s) const
		{
			return !(*this == s);
		}

在这里插入图片描述

2.17 >>和<<

之前我们写输出的函数要用友元函数,现在有个方法可以不用友元:

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

范围for直接打印对象里面的字符。

在这里插入图片描述

输入的函数也不需要友元:

istream& operator>>(istream& in, string& s)
{
	char ch;
	in >> ch;
	while (ch != '\n' && ch != ' ')
	{
		s += ch;
		in >> ch;
	}
	return in;
}

运行一下:
在这里插入图片描述
结果发现好像停不下来了,因为cin和scanf一样,读取时对空格或者换行会进行忽略,所以一直在循环里

解决方法:使用cin的get函数,可以读取到空格或者换行

	istream& operator>>(istream& in, string& s)
	{
		s.clear();//清理之前的字符串
		char ch = in.get();
		while (ch != '\n' && ch != ' ')
		{           
			s += ch;
			ch = in.get();
		}
		return in;
	}

在这里插入图片描述

三、全部代码

3.1 string.h

#include <iostream>
#include <assert.h>
#include<string.h>
using namespace std;

namespace yss
{
	class string
	{
	public:
		//迭代器遍历
		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;
		}
		//构造
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		//析构
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}
		//C格式返回
		const char* c_str() const
		{
			return _str;
		}
		//返回字符个数
		const size_t size() const
		{
			return _size;
		}
		//返回容量
		const size_t capacity() const
		{
			return _capacity;
		}
		//交换
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//拷贝构造
		/*string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}*/
		string(const string& s)
		{
			string tmp(s._str);
			swap(tmp);
		}
		//赋值重载
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}*/
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}
		//下标遍历访问
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}

		//扩容
		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 ch)
		{
			if (n <= _size)
			{
				_str[n] = '\0';
			}
			else
			{
				if (n > _capacity)
				{
					reserve(n);
				}
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_str[n] = '\0';
			}
			_size = n;
		}
		// 判空
		bool empty()
		{
			return _size == 0;
		}
		// 清理
		void clear()
		{
			_size = 0;
			_str[0] = '\0';
		}
		//尾插字符
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
				reserve(newcapacity);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		//尾插字符串
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
		}
		// +=
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		//插入
		void insert(char ch, size_t pos)
		{
			assert(pos < _size);
			//插入要考虑是否要扩容
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
				reserve(newcapacity);
			}
			//挪动数据
			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + 1] = _str[end];
				--end;
			}
			_str[pos] = ch;
			_size++;
		}
		void insert(const char* str, size_t pos)
		{
			assert(pos < _size);
			size_t len = strlen(str);
			//考虑是否扩容
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			//挪动数据
			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + len] = _str[end];
				--end;
			}
			strncpy(_str + pos, str, len);
			_size++;
		}
		//删除
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			// 从pos开始后面都删除
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			//从pos开始后面不全删
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}
		//查找字符
		size_t find(char ch, size_t  pos = 0)
		{
			assert(pos < _size);
			for (size_t i = 0; i < _size; i++)
			{
				if (ch == _str[i])
				{
					return i;
				}
			}
			return npos;
		}
		//查找字符串
		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);
			char* ptr = strstr(_str, str);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}
		
		//截取子串
		string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);
			size_t end = pos + len;
			if (len == npos || pos + len >= _size)
			{
				end = _size;
			}
			string ss;
			ss.reserve(end - pos);
			for (size_t i = pos; i < end; i++)
			{
				ss += _str[i];
			}
			return ss;
		}
		//大于
		bool operator>(string& s) const
		{
			return strcmp(_str, s._str) > 0;
		}
		//等于
		bool operator==(string& s) const
		{
			return strcmp(_str, s._str) == 0;
		}
		//大于等于
		bool operator>=(string& s) const
		{
			return (*this == s && *this > s);
		}
		//小于
		bool operator<(string& s) const
		{
			return !(*this >= s);
		}
		//小于等于
		bool operator<=(string& s) const
		{
			return !(*this > s);
		}
		//不等于
		bool operator!=(string& s) const
		{
			return !(*this == s);
		}

	private:
		size_t _size;
		size_t _capacity;
		char* _str;
		const static size_t npos = -1;
	};
	// <<
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto e : s)
		{
			out << e;
		}
		return out;
	}
	// >>
	/*istream& operator>>(istream& in, string& s)
	{
		char ch;
		in >> ch;
		while (ch != '\n' && ch != ' ')
		{
			s += ch;
			in >> ch;
		}
		return in;
	}*/
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		while (ch != '\n' && ch != ' ')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}
}

3.2 test.cpp

#include "string.h"

int main()
{
	/*yss::string s1("hello yss");
	cout << s1.c_str() << endl;*/

	/*yss::string s1("hello yss");
	yss::string s2(s1);
	cout << s2.c_str() << endl;*/

	/*yss::string s1("hello yss");
	yss::string s2 = s1;
	cout << s2.c_str() << endl;*/

	//yss::string s1("hello world");
	//yss::string::iterator it = s1.begin();
	//while (it != s1.end())
	//{
	//	cout << *it;
	//	++it;
	//}
	//cout << endl;

	/*yss::string s1("hello world");
	yss::string::const_iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;*/

	/*yss::string s1("hello world");
	for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << " ";
	}
	cout << endl;*/

	/*yss::string s1("hello yss");
	cout << s1.c_str() << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.resize(12,'q');
	cout << s1.c_str() << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;*/

	/*yss::string s1("hello yss");
	cout << s1.empty() << endl;
	cout << s1.size() << endl;
	cout << s1.c_str() << endl;
	cout << s1.capacity() << endl;
	s1.clear();
	cout << s1.empty() << endl;
	cout << s1.size() << endl;
	cout << s1.c_str() << endl;
	cout << s1.capacity() << endl;*/

	/*yss::string s1("hello yss");
	s1.push_back('a');
	cout << s1.c_str() << endl;*/

	/*yss::string s1("hello yss");
	s1.append("abcd");
	cout << s1.c_str() << endl;*/

	/*yss::string s1("hello yss");
	s1 += 'a';
	cout << s1.c_str() << endl;
	yss::string s2("hello yss");
	s2 += "abcd";
	cout << s2.c_str() << endl;*/

	/*yss::string s1("hello yss");
	s1.insert('a', 3);
	cout << s1.c_str() << endl;*/

	/*yss::string s1("hello yss");
	s1.insert("abcd", 3);
	cout << s1.c_str() << endl;*/

	/*yss::string s1("hello yss");
	s1.erase(3);
	cout << s1.c_str() << endl;
	yss::string s2("hello");
	s2.erase(2, 2);
	cout << s2.c_str() << endl;*/

	/*yss::string s1("hello yss");
	size_t p = s1.find('o');
	cout << p << endl;
	p = s1.find("ss");
	cout << p << endl;*/

	/*yss::string s1("hello yss");
	yss::string s2 = s1.substr(1, 7);
	cout << s2.c_str() << endl;*/

	/*yss::string s1("hello yss");
	cout << s1 << endl;*/

	/*yss::string s1;
	cin >> s1;
	cout << s1 << endl;*/

	/*yss::string s1("hello");
	cout << s1 << endl;
	cin >> s1;
	cout << s1 << endl;*/

	yss::string s1("hello");
	yss::string s2("aabbc");
	cout << (s1 > s2) << endl;
	cout << (s1 == s2) << endl;
	cout << (s1 >= s2) << endl;
	cout << (s1 < s2) << endl;
	cout << (s1 <= s2) << endl;
	cout << (s1 != s2) << endl;

	return 0;
}

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

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

相关文章

HTML CSS 进度条

1 原生HTML标签 <meter>&#xff1a;显示已知范围的标量值或者分数值<progress>&#xff1a;显示一项任务的完成进度&#xff0c;通常情况下&#xff0c;该元素都显示为一个进度条 1.1 <meter> <html><head><style>meter{width:200px;}…

新能源车企年底冲刺KPI,只能抓住“价格战”做文章?

新能源汽车行业的价格战似乎看不到尽头。 自特斯拉吹响号角后&#xff0c;今年以来&#xff0c;业内已然开启了几轮颇具规模的价格战。 如今进入年底&#xff0c;价格战不仅没有消停&#xff0c;还愈打愈烈。据不完全统计&#xff0c;12月&#xff0c;已有20多家车企先后开启…

Nginx快速入门:访问日志access.log参数详解 |访问日志记录自定义请求头(三)

0. 引言 在企业的生产环境中&#xff0c;我们时常需要通过nginx的访问日志来统计流量、排查调用问题等&#xff0c;而nginx默认的日志格式所包含的信息远无法满足我们使用&#xff0c;因此常常需要对日志进行自定义&#xff0c;所以今天我们就来看如何自定义nginx的访问日志格…

anaconda 安装 使用 pytorch onnx onnxruntime

一&#xff1a;安装 如果不是 x86_64&#xff0c;需要去镜像看对应的版本 安装 Anaconda 输入命令 bash Anaconda3-2021.11-Linux-x86_64.sh 然后输入 yes 表示同意 确认安装的路径&#xff0c;一般直接回车安装在默认的 /home/你的名字/anaconda3 很快就安装完毕。输入 yes…

星融元中标华夏银行项目,助力金融数据中心可视网建设工作

近日&#xff0c;星融元成功入围华夏银行国产品牌网络流量汇聚分流器&#xff08;TAP&#xff09;设备供应商&#xff0c;在助力头部金融机构构建数据中心可视网络的建设工作中&#xff0c;星融元又一次获得全国性股份制银行客户的青睐。 华夏银行作为全国性股份制商业银行积极…

如何在Ubuntu系统中安装VNC并结合内网穿透实现远程访问桌面

文章目录 前言1. ubuntu安装VNC2. 设置vnc开机启动3. windows 安装VNC viewer连接工具4. 内网穿透4.1 安装cpolar【支持使用一键脚本命令安装】4.2 创建隧道映射4.3 测试公网远程访问 5. 配置固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址5.3 测试…

3d云渲染动画、效果图的速度,对比本地电脑渲染速度区别

与使用个人电脑进行渲染相比&#xff0c;3D云渲染服务擁有其无可比拟的优势。云端的服务器配置通常超出个人电脑&#xff0c;具有更强大的运算力和多任务并行处理的能力&#xff0c;使得同时执行多个渲染作业成为可能。这一点在处理图形复杂度高和数据量巨大的渲染项目时尤为显…

CEC2013(python):五种算法(OOA、WOA、GWO、DBO、HHO)求解CEC2013(python代码)

一、五种算法简介 1、鱼鹰优化算法OOA 2、鲸鱼优化算法WOA 3、灰狼优化算法GWO 4、蜣螂优化算法DBO 5、哈里斯鹰优化算法HHO 二、5种算法求解CEC2013 &#xff08;1&#xff09;CEC2013简介 参考文献&#xff1a; [1] Liang J J , Qu B Y , Suganthan P N , et al. Pro…

图片编辑文字用什么软件?带你了解这5个

图片编辑文字用什么软件&#xff1f;在当今数字化的时代&#xff0c;图片编辑已经成为我们日常生活中不可或缺的一部分。有时候&#xff0c;我们需要在图片上添加文字&#xff0c;以增强图片的视觉效果或传达特定的信息。那么&#xff0c;有哪些可以在图片上编辑文字的软件呢&a…

Java数据结构-模拟ArrayList集合思想,手写底层源码(1),底层数据结构是数组,编写add添加方法,正序打印和倒叙打印

package com.atguigu.structure; public class Demo02_arrayList {public static void main(String[] args) {MyGenericArrayListV1 arrayListV1 new MyGenericArrayListV1();//arr.add(element:100,index:1);下标越界&#xff0c;无法插入//初始化&#xff08;第一次添加&…

Spring Cloud Gateway请求路径修改指南:详解ServerWebExchange的完美解决方案及代码示例

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

python实现贪吃蛇游戏

文章目录 1、项目说明2、项目预览3、开发必备4、贪吃蛇代码实现4.1、窗口和基本参数实现4.2、绘制背景4.3、绘制墙壁4.4、绘制贪吃蛇4.5、绘制食物4.6、实现长度信息显示4.7、定义游戏暂停界面4.8、定义贪吃蛇死亡界面4.9、实现贪吃蛇碰撞效果4.10、实现添加食物功能4.11、实现…

jQuery —— 自定义四位数验证弹框

在提交表单发送请求前&#xff0c;想要校验下&#xff0c;但不想用第三方插件&#xff0c;就自己写了个自定义数字校验码弹框&#xff0c;更稳定些&#xff0c;样式有点low&#xff0c;记录下。 没什么硬性要求的话&#xff0c;可以使用第三方插件&#xff0c;会方便许多样式也…

SQL学习笔记+MySQL+SQLyog工具教程

文章目录 1、前言2、SQL基本语言及其操作2.1、CREATE TABLE – 创建表2.2、DROP TABLE – 删除表2.3、INSERT – 插入数据2.4、SELECT – 查询数据2.5、SELECTDISTINCT – 去除重复值后查询数据2.6、SELECTWHERE – 条件过滤2.7、AND & OR – 运算符2.8、ORDER BY – 排序2…

科研院校和研究所都在用功率放大器做哪些实验

科研院校和研究所在科研工作中常常使用功率放大器进行实验。功率放大器是一种电子设备&#xff0c;其主要功能是将输入信号的功率增加到预定的输出功率水平&#xff0c;并保持信号的波形不失真。它在各个学科领域都有广泛的应用&#xff0c;包括通信、无线电、雷达、生物医学等…

Mac安装Nginx

一起学习 1、确认你的电脑是否安装homebrew&#xff0c;打开电脑终端 输入&#xff1a; /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"2、确认homebrew是否安装成功&#xff0c;在终端输入&#xff1a; br…

Linux中使用HTTP协议进行网络通信的示例——你的“网络信使”

大家好&#xff0c;今天我们要聊聊在Linux中如何使用HTTP协议进行网络通信。听起来有点高大上&#xff0c;但其实并不难&#xff0c;让我们一起来看看&#xff01; 首先&#xff0c;我们要明白HTTP协议是什么。HTTP&#xff0c;全名为超文本传输协议&#xff08;Hypertext Tra…

操作系统课设-银行家算法VS2022

目录 1 目的和要求 2 银行家算法的数据结构 3 进程请求时的资源处理 4 安全性算法的设计思路 5 调试与分析 6 C语言源代码 7 心得体会 1 目的和要求 银行家算法是避免死锁的一种重要方法&#xff0c;能够有效的在资源分配的过程中&#xff0c;对系统的安全性进行检测。通…

搭建自动化 Web 页面性能检测系统 —— 设计篇

页面性能对于用户体验、用户留存有着重要影响&#xff0c;当页面加载时间过长时&#xff0c;往往会伴随着一部分用户的流失&#xff0c;也会带来一些用户差评。性能的优劣往往是同类产品中胜出的影响因素&#xff0c;也是一个网站口碑的重要评判标准。 一、名称解释 前端监控…

猫罐头哪个牌子好性价比高?五大性价比高的品牌推荐

很多猫奴担心猫咪天天吃干猫粮可能会导致营养不足&#xff0c;所以想给猫咪换换口味&#xff0c;改善一下饮食。这时&#xff0c;选择猫罐头是个不错的选择。不过&#xff0c;喂猫罐头也是有一些讲究的。 作为从业6年的宠物护理师来说&#xff0c;作为早在几年就开始接触猫罐头…
最新文章