【C++从0到王者】第十站:手撕string

news2024/11/18 3:26:29

文章目录

  • 一、String的基本结构
  • 二、String的构造函数
    • 1.string(const char* str)
    • 2.string()
    • 3.string(const char* str = " ")
    • 4.string(const string& s)
  • 二、String的析构函数
  • 三、获取String中的字符串
  • 四、获取有效元素个数
  • 五、operator[]运算符重载
  • 六、增删查改之增
    • 1.reserve()
    • 2.pushback(char ch)
    • 3.append(const char* str)
    • 4.operator+=
    • 5.insert
  • 七、增删查改之删
    • 1.erase
    • 2.clear
  • 八、增删查改之查
  • 九、获取子串
  • 十、resize
  • 十一、流插入
    • 1.流插入
    • 2. s.c_str()与cout<<s的区别
  • 十二、修改由于字符函数导致的bug
    • 1.修改构造函数
      • 1>string(const char* str = "")
      • 2>string(const string& s)
    • 2.对reserve的修改
    • 3.append的修改
    • 4.对于迭代器的修改
  • 十三、流提取
  • 十四、比较大小
    • 1.operator<
    • 2.operator==(const string& s)
    • 3.其他比较
  • 十五、赋值运算符重载
    • 1.传统写法
    • 2.现代写法
  • 十六、拷贝构造的现代写法
  • 十七、string模拟实现完整代码

一、String的基本结构

string在库里面实现的是比较冗余的,我们这里就实现一个最基本的string,该string采用类似顺序表的方式来进行实现,总体来说比较简单。
我们使用三个私有成员变量,一个指向string内容的指针,一个记录当前有效字符个数的变量size,一个记录当前容量的变量capacity,如下所示

char* _str;
size_t _size;
size_t _capacity;

二、String的构造函数

对于string的构造函数,在库里面有很多个写法。里面的显得过于冗余,我们采用比较简单的方式来实现。

1.string(const char* str)

如下代码所示,在这个构造函数中,我们使用了初始化列表来为_str开空间,_size和_capacity赋初值,然后再使用strcpy函数即可将字符串的内容放入数据中。
这里值得注意的是我们的size是记录的有效字符个数,他是不包括\0字符的。所以我们给_size赋初值的时候要切记注意不要加1。同样的,对于_capacity这个变量而言,它代表的的容量也是不能考虑\0字符的,所以它也是相比实际容量要少一个的,而在开容量的时候,一定要多开一个这个是用来存放字符\0的

		//给一个字符串去构造一个string
		string(const char* str)
			:_str(new char[strlen(str) + 1])
			, _size(strlen(str))
			, _capacity(strlen(str))
		{
			strcpy(_str, str);
		}

2.string()

这是一个无参的默认构造函数,由于我们一开始不知道要方多少个字符,不妨我们就一开始给16个空间,0个有效字符,15个容量即可。初始化的时候,我们直接初始化一些\0字符即可

		//无参的默认构造函数
		string()
			:_str(new char[16])
			,_size(0)
			,_capacity(15)
		{
			memcpy(_str, "\0\0\0\0", 4);
		}

3.string(const char* str = " ")

这个构造函数实际上是不能与前面的重合的。因为他是一个默认构造函数。不过是带了缺省参数的缘故。它的思路与前面的都是一致的

		//以上合二为一的构造函数写法
		string(const char* str = "")
			:_str(new char[strlen(str) + 1])
			, _size(strlen(str))
			, _capacity(strlen(str))
		{
			strcpy(_str, str);
		}

4.string(const string& s)

显然这是一个拷贝构造函数,我们的串中涉及到了开空间的问题,必须得需要深拷贝,所以我们需要自己写一个拷贝构造,而不能依赖于编译器自己生成的拷贝构造函数

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

二、String的析构函数

这个函数就比较简单了,我们直接去delete掉字符串,然后置空,清空数据即可

		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}

三、获取String中的字符串

这个接口我们也是比较熟悉的,它可以直接获取string中的字符串,注意它需要加上const进行修饰,因为我们是不会破坏对象里面的数据的,所以需要后置const,并且我们返回这个字符串的时候,它也是肯定不会被修改的,所以也需要加上const

		//获取string中的字符串
		const char* c_str() const 
		{
			return _str;
		}

四、获取有效元素个数

这个接口的意思也比较直白,就是获取_size的值,直接返回即可

		//获取当前有效元素个数
		size_t size() const 
		{
			return _size;
		}

五、operator[]运算符重载

这个运算符重载,我们需要实现两个,一个是针对普通对象的,一个是针对于const的对象的。我们注意到[]这个的特性,它既可以作为右值(读取),又可以作为左值(修改),我们想要去修改读或者修改某一处的值,那么我们应该是得知道该处的地址的,我们可以直接用引用,将该处空间的别名返回,这样一来,即可避免更大的开销,因为传引用返回可以节省开销,又可直接根据该处的别名去访问该处。从而达到读写的目的。
对于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];
		}

六、增删查改之增

1.reserve()

我们首先需要知道的就是这个函数了,这个函数的功能就是扩容,但凡涉及到增的时候,必然就需要进行扩容了。它的具体实现如下
这个函数它接收一个参数n,也就是需要扩到多大,注意到最好不要进行缩容,所以我们为了简洁,默认使他只能扩容。我们先扩充n+1个空间,这多出来的一个是为\0字符做出打算的

扩容的具体逻辑是这样的,首先先开辟n+1个空间,然后将原来的字符内容交给这个新空间,最好在修改容量,和释放之前的空间。

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

2.pushback(char ch)

这段代码是这样的,我们首先检查容量是否足够,如果不足够,那么我们就扩容,然后就是类似顺序表的操作进行插入数据。不要忘记最后添加一个\0字符

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

		}

3.append(const char* str)

这个函数的逻辑和上一个是差不多的,我们需要做的就是注意扩容多大空间,我们是扩容到len+_size个大小即可。最后直接字符串拷贝即可

		void append(const char* str)
		{
			int len = strlen(str);
			if (len + _size > _capacity)
			{
				int newcapacity = (len+_size) ;
				reserve(newcapacity);
			}
			//while (len--)
			//{
			//	_str[_size++] = *str++;
			//}
			//_str[_size] = '\0';
			strcpy(_str + _size, str);
			_size += len;
		}

4.operator+=

这是一个运算符重载,我们有两种形式,一种是加字符,一种是加字符串。注意我们需要一个返回值了,因为我们+=之后其实是有一个返回值的,这个返回值就是我们这个被修改后的对象。我们可以直接复用接口,然后返回this指针即可

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

5.insert

对于insert这个接口,他是在某个位置处插入n个字符或者插入一个字符串。那么这样的话我们首先需要做的就是扩容,然后就是移动原来的数据,最后就是插入数据。这里的移动数据我们下面这两个接口采用的是计数的方式来实现的

		void insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);
			if (_size + n > _capacity)
			{
				int newcapacity = _size + n;
				reserve(newcapacity);
			}
			int count = _size - pos + 1;
			int size = _size;
			while (count--)
			{
				_str[size + n] = _str[size];
				size--;
			}
			int i = pos;
			for (i = pos; i < pos + n; i++)
			{
				_str[i] = ch;
			}
			_size += n;
		}
		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			int len = strlen(str);
			if (_size + len > _capacity)
			{
				int newcapacity = _size + len;
				reserve(newcapacity);
			}
			int count = _size - pos + 1;
			int size = _size;
			while (count--)
			{
				_str[size + len] = _str[size];
				size--;
			}
			int i = pos;
			for (i = pos; i < pos + len; i++)
			{
				_str[i] = *str;
				str++;
			}
			_size += len;
		}

上面的两种方法是最容易理解的方法,但是我们来看一下这个写法
在这里插入图片描述

上面这种写法看似正确,实际上也有一些问题,当pos为0的时候,由于pos为size_t类型,导致end也被强制类型转化了,故代码崩溃。

为了进行修改,我们可以这样做,进行一次强制类型转换
在这里插入图片描述我们还可以这样做,定义一个静态成员变量npos,使之不可以等于npos
在这里插入图片描述注意上面这种写法我们需要注意的一点是,c++中虽然上面的是正确的,但是我们还可以这样写,但是不建议这样写:
在这里插入图片描述
这样写编译器是可以通过的,但是要记住只有整型类型可以这样写,浮点类型都不可以,这是一个特殊的语法。我们一般不会这样使用的

七、增删查改之删

1.erase

这段代码我们的思想就是挪动数据即可,当长度超出剩余的字符串长度时候直接将后面全部删完即可,也就是给该位置加上一个\0字符。其余情况直接将后面的挪动到前面即可。

		//增删查改之删
		void erase(int pos, size_t len = npos)
		{
			assert(pos <= _size);
			if (len == npos || pos + len > _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				int end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}

2.clear

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

八、增删查改之查

查找我们一般是查找一个字符或者查找一个字符串,找字符简单,找字符串的话我们可以直接调用strstr这个函数,暴力匹配即可。或者kmp算法和BM算法也是可以的

		//查
		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* ptr = strstr(_str + pos, str);
			if (ptr)
			{
				return (ptr - str);
			}
			else
			{
				return npos;
			}
		}

九、获取子串

如下代码所示,我们想要获取一部分串,思路是这样的,先看len是否已经超出了串的长度,如果没有超出,那么可以,如果超出了,那就意味着从pos开始全部截取,那么修改len的值直至合适为止。然后我们定义一个空串。我们对这个串的容量刚好就是这个修改后的len的大小,我们从pos位置开始,一直加len次。这样我们九获取了我们希望获得的子串,我们现在返回它即可

在这里我们需要注意两点:

  1. 我们在定义这个空串的时候,不能将其设置为静态的。因为静态的串只会定义一次。以后再次调用这个函数是会出现问题的
  2. 我们的返回值不可以是引用返回,因为我们返回的串出了作用域就不在了
  3. 这个函数必须得有拷贝构造函数,因为其必须为传值返回。且该串涉及到开空间的问题,我们必须得自己写一个深拷贝构造。否则会对同一块空间调用两次析构函数而产生错误
		//提取出指定位置的串
		string substr(size_t pos = 0, size_t len = npos)
		{
			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 < pos + n; i++)
			{
				tmp += _str[i];
			}
			return tmp;
		}

十、resize

resize的功能是扩充size,当容量足够的时候,若新的size小于原来的size,那么就相当于删除数据,如果容量足够且size大于原来的数据,那么就为其补充数据直至size。如果容量不够size的值,那么就先扩容,然后再填数据

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

十一、流插入

1.流插入

首先我们在之前写日期类的时候,我们就知道了流插入的样子大概是这样写的
在这里插入图片描述

这里必须在类外进行定义,这是为了改变变量顺序,符合使用习惯
其次必须加上引用,因为ostream有防拷贝设计
在这里插入图片描述对于流插入,我们有几种方式可以去实现,要么直接定义友元函数,然后直接访问s里面的字符串
要么就是不适用友元,使用循环逐个打印

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

甚至我们还可以使用范围for
在这里插入图片描述

2. s.c_str()与cout<<s的区别

这两个我们在打印的时候,看似没有什么太大区别,其实还是差距蛮大的。
首先c_str()它是一个字符串,它遇到’\0’就结束了。而直接打印s的话看的并非是’\0’字符,而是size的大小。有多少size打印多少个字符。这样一来有可能在存储的字符串里面恰巧有一个是’\0’的话,那么这两者打印的结果是不一样的。

如下面所示,是我们自己写的string演示
在这里插入图片描述

在这里插入图片描述

下方是库里面的string演示
在这里插入图片描述

需要注意到’\0’这个字符vs2022中是不会被打印出来的,而在2013下是可以打印出来的
这样一想确实挺合理。我们自己写的与库里面的一样是我们基于循环写出来的。但是倘若我们将这个循环换位范围for,那么我们发现全乱了。

在这里插入图片描述在这里插入图片描述
那么这是为什么呢?为什么范围for不符合我们的期望呢?我们想要知道答案,那么我们就得去看迭代器是如何实现了
在这里插入图片描述注意到我们的迭代器事实上都是通过字符串的长度来实现的。这样一来的话,就无形之中将’\0’之后的数据没有给考虑进去。所以事实上我们的迭代器有bug。其实在我们的string类中,只要涉及到string.h相关的函数中,大部分都需要重新修改一下了

十二、修改由于字符函数导致的bug

我们在上面中,发现前面的大部分代码事实上是存在很大的问题的,存在很大的隐患。我们现在就先来进行修改,我们可以将原来的str系列的函数全部替换为mem系列的函数,这样就能解决掉这个问题了

1.修改构造函数

1>string(const char* str = “”)

这是我们原来的构造函数

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

其实这个代码使用memcpy和strcpy都是可以的,但是我们为了统一以下,我们现在使用memcpy来进行修改

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

需要特殊注意的是,这里是size+1

2>string(const string& s)

前面那个其实还好,但是对于拷贝构造函数就要小心了。它必须得换了
下面是我们之前写的

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

我们试想一下,如果不换的话,一旦出现’\0’字符,那么后面的一部分全都出现问题了。
所以我们得用memcpy

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

2.对reserve的修改

下面是我们之前的函数,可见实际上是存在很多问题的

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

我们修改后的为

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

3.append的修改

这是原来的,虽然确实没有什么太大的影响,但是我们为了统一还是进行处理一下

		void append(const char* str)
		{
			int len = strlen(str);
			if (len + _size > _capacity)
			{
				int newcapacity = (len+_size) ;
				reserve(newcapacity);
			}
			//while (len--)
			//{
			//	_str[_size++] = *str++;
			//}
			//_str[_size] = '\0';
			strcpy(_str + _size, str);
			_size += len;
		}

处理后为

		void append(const char* str)
		{
			int len = strlen(str);
			if (len + _size > _capacity)
			{
				int newcapacity = (len+_size) ;
				reserve(newcapacity);
			}
			//while (len--)
			//{
			//	_str[_size++] = *str++;
			//}
			//_str[_size] = '\0';
			//strcpy(_str + _size, str);
			memcpy(_str + _size, str, len+1);
			_size += len;
		}

4.对于迭代器的修改

这是我们原来的迭代器代码

		//迭代器
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + strlen(_str);
		}

		//const 迭代器
		typedef const char* const_iterator;
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + strlen(_str);
		}

修改后为

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

		//const 迭代器
		typedef const char* const_iterator;
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			//return _str + strlen(_str);
			return _str + _size;
		}

十三、流提取

经历了前面修改后的bug以后,我们在回过头来继续实现流提取
我们先看如下代码

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

这段代码是可以实现我们的目的,首先我们知道我们得先清楚之前s中的数据,否则插入就有问题。会保留原来的数据
其次,我们输入的时候需要用in.get()这个函数,必须用这个,如果我们使用的是in的话,它本身就是无法读取空格和‘\n’的,因为这两个字符被用作分隔符,分割读取的数据。故后面的条件恒成立或恒不成立。而get是可以读取这两个字符的
然后我们用一个循环,先清除一开始可能会输入的分隔符。清楚了一开始的分隔符以后,我们就开始读取数据,我们可以直接调用+=这个运算符重载。当遇到分隔符的时候结束读取即可

同样的这个代码假如我们想要改为getline也是很容易的,我们直接将空格这个标识符删掉即可。

上面的代码虽然也确实可以解决问题,但由于我们连续的使用+=这个函数,我们需要不断的进行扩容,这是一种极大的消耗。我们也不能一开始直接对其扩容很大,这样是伤敌一千自损八百的策略

为了解决这个问题,我们可以这样做

	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		while (ch == ' ' || ch == '\n')
		{
			ch = in.get();
		}
		char buff[128] = { 0 };
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			//s += ch;

			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}

在栈上运用一个buff数组,这个数组它起到一个缓冲的作用,先将数据都存到这个数组里面,当这个数组存满的时候,一次性将数据全部给s,然后结束的时候如果还有数据,那么也将他给放进去即可。这样的代价就很小了

十四、比较大小

1.operator<

如下代码所示,我们先进行依次比较,若前面可以分出胜负自然好说,但若分不出胜负,那么就只有一种情况是满足小于的情况,即第一个没有值了,第二个还有值,其余皆为不满足小于的情况

		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++;
				}
			}
			return (i1 == _size) && (i2 != s._size);
			//return _size<s._size;
		}

上面是我们自己手撕的代码,同样的我们也可以利用c语言中的一些库来更方便的完成这个代码

下面这串代码虽然短小,但是可能不是很好理解,我们第一行的目的就是先求出两个对象最小的size,然后让两个对象的前size个进行比较,如果相等的话,那么ret就是0,我们就只需要确认一下第一个的size是否小于第二个的size即可。如果不相等,那么我们看ret是否小于0,小于既是正确的

bool operator<(const string& s) const
{
	int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);

	return ret == 0 ? _size < s._size : ret < 0;
}

2.operator==(const string& s)

这个就比较简单了,我们先比较size,如果相等的条件下,两个串也相等,那么就是相等了

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

3.其他比较

解决了前两个,其实其他的就可以直接进行复用了,如下代码所示

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

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

十五、赋值运算符重载

1.传统写法

如下所示,是我们比较传统的写法,它跟拷贝构造函数是差不多的,我们不管原来的是什么样子,反正总归最后基本就是把右边的复制了一份。那么我们就直接开空间,释放原来的空间,随之拷贝数据即可。它与拷贝构造函数的区别就在于,它是已经存在的两个变量之间的赋值拷贝,这个过程是需要释放掉被赋值的对象原来的数据的。而拷贝构造函数是用一个已经存在的去构造一个不存在的,以前这个也不存在,所以就不需要释放空间。

总归就是一个深拷贝的写法

		//赋值运算符重载
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				delete[] _str;
				_str = tmp;
				memcpy(_str, s._str, s._size + 1);
				_size = s._size;
				_capacity = _capacity;
			}
			return *this;
		}

2.现代写法

如下代码所示,这段代码就充分利用了拷贝构造函数,然后移花接木即可,顺便原来的空间也可以因为tmp的析构而带走,充分的榨干了tmp的作用。

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

但是要注意,我们不可以这样写:
在这里插入图片描述程序会崩溃的
这是因为swap函数中是通过三次赋值操作来实现的,而赋值操作又通过swap来实现,实现了死递归。导致栈溢出了

事实上,库里面也自己提供了两个string对象的交换
在这里插入图片描述而我们如果需要自己手动实现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);
				swap(tmp);
			}
			return *this;
		}

上面这段代码还可以继续进行简化

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

这段代码我们就厉害了,直接在传参的过程中调用拷贝构造,可谓精简到极致了

十六、拷贝构造的现代写法

如下代码所示,是我们较为传统的写法,也是上面的写法。

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

上面这段代码,其实也可以进一步的进行简化。即现代写法

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

注意在这段代码中,初始化列表必须得写法,否则在调用赋值运算符重载的过程中,由于this所指向的对象未初始化,导致其的_str随意指向一块空间,交换以后,析构的过程中直接崩溃。故必须得加初始化
其次在这段代码中,还有一个问题是如果s中的_str中间有一个’\0’字符,那么也会出现问题。导致后面的部分无法拷贝上去。

综上所述,对于拷贝构造函数,还是传统写法更优一些

十七、string模拟实现完整代码

下面是类实现的具体代码

#pragma once
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
namespace String
{
	class string
	{
	public :
		/*********************************************/
		//迭代器
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			//return _str + strlen(_str);
			return _str + _size;
		}

		//const 迭代器
		typedef const char* const_iterator;
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			//return _str + strlen(_str);
			return _str + _size;
		}
		/**************************************************************************************/
		//分开的构造函数写法
		给一个字符串去构造一个string
		//string(const char* str)
		//	:_str(new char[strlen(str) + 1])
		//	, _size(strlen(str))
		//	, _capacity(strlen(str))
		//{
		//	strcpy(_str, str);
		//}
		无参的默认构造函数
		//string()
		//	:_str(new char[16])
		//	,_size(0)
		//	,_capacity(15)
		//{
		//	memcpy(_str, "\0\0\0\0", 4);
		//}
		/**************************************************************************************/
		//以上合二为一的构造函数写法
		string(const char* str = "")
			:_str(new char[strlen(str) + 1])
			, _size(strlen(str))
			, _capacity(strlen(str))
		{
			//strcpy(_str, str);
			memcpy(_str, str, _size + 1);
		}


		//拷贝构造函数
		string(const string& s)
		{
			_str = new char[s._capacity + 1];
			memcpy(_str, s._str, s._size + 1);
			_size = s._size;
			_capacity = s._capacity;
		}
		//string(const string& s)
		//	:_str(nullptr)
		//	,_size(0)
		//	,_capacity(0)
		//{
		//	string tmp(s._str);
		//	swap(tmp);
		//}
		/**************************************************************************************/
		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}
		/**************************************************************************************/
		//获取string中的字符串
		const char* c_str() const 
		{
			return _str;
		}
		/**************************************************************************************/
		//获取当前有效元素个数
		size_t size() const 
		{
			return _size;
		}
		/**************************************************************************************/
		//operator[]

		//读写接口
		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);
				memcpy(tmp, _str, _size + 1);
				_capacity = n;
				delete[] _str;
				_str = tmp;
			}
		}

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

		}
		
		void append(const char* str)
		{
			int len = strlen(str);
			if (len + _size > _capacity)
			{
				int newcapacity = (len+_size) ;
				reserve(newcapacity);
			}
			//while (len--)
			//{
			//	_str[_size++] = *str++;
			//}
			//_str[_size] = '\0';
			//strcpy(_str + _size, str);
			memcpy(_str + _size, str, len + 1);
			_size += len;
		}

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


		void insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);
			if (_size + n > _capacity)
			{
				int newcapacity = _size + n;
				reserve(newcapacity);
			}
			int count = _size - pos + 1;
			int size = _size;
			while (count--)
			{
				_str[size + n] = _str[size];
				size--;
			}
			int i = pos;
			for (i = pos; i < pos + n; i++)
			{
				_str[i] = ch;
			}
			_size += n;
		}
		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			int len = strlen(str);
			if (_size + len > _capacity)
			{
				int newcapacity = _size + len;
				reserve(newcapacity);
			}
			int count = _size - pos + 1;
			int size = _size;
			while (count--)
			{
				_str[size + len] = _str[size];
				size--;
			}
			int i = pos;
			for (i = pos; i < pos + len; i++)
			{
				_str[i] = *str;
				str++;
			}
			_size += len;
		}
		void insert1(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);
			if (_size + n > _capacity)
			{
				int newcapacity = _size + n;
				reserve(newcapacity);
			}
			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + n] = _str[end];
				end--;
			}
			int i = pos;
			for (i = pos; i < pos + n; i++)
			{
				_str[i] = ch;
			}
			_size += n;
		}
		void insert2(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);
			if (_size + n > _capacity)
			{
				int newcapacity = _size + n;
				reserve(newcapacity);
			}
			int end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + n] = _str[end];
				end--;
			}
			int i = pos;
			for (i = pos; i < pos + n; i++)
			{
				_str[i] = ch;
			}
			_size += n;
		}



		/*****************************************************************************/
		//增删查改之删
		void erase(int pos, size_t len = npos)
		{
			assert(pos <= _size);
			if (len == npos || pos + len > _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				int end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}


		/*****************************************************************************/
		//查
		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* ptr = strstr(_str + pos, str);
			if (ptr)
			{
				return (ptr - _str);
			}
			else
			{
				return npos;
			}
		}

		//提取出指定位置的串
		string substr(size_t pos = 0, size_t len = npos)
		{
			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 < pos + n; i++)
			{
				tmp += _str[i];
			}
			return tmp;
		}

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

		
		//比较大小
		//bool operator<(const string& s)
		//{
		//	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++;
		//		}
		//	}
		//	return (i1 == _size) && (i2 != s._size);
		//	//return _size<s._size;
		//}
		bool operator<(const string& s) const 
		{
			int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);

			return ret == 0 ? _size < s._size : ret < 0;
		}

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

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

		//赋值运算符重载
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		char* tmp = new char[s._capacity + 1];
		//		delete[] _str;
		//		_str = tmp;
		//		memcpy(_str, s._str, s._size + 1);
		//		_size = s._size;
		//		_capacity = _capacity;
		//	}
		//	return *this;
		//}

		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);
		//		swap(tmp);
		//	}
		//	return *this;
		//}
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	public:

		const static size_t npos;
	};
	const size_t string::npos = -1;

	ostream& operator<<(ostream& out, const string& s)
	{
		//for (size_t i = 0; i < s.size(); i++)
		//{
		//	out << s[i];
		//}
		//out << endl;
		//return out;
		for (auto ch : s)
		{
			out << ch;
		}
		out << endl;
		return out;
	}


	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		while (ch == ' ' || ch == '\n')
		{
			ch = in.get();
		}
		char buff[128] = { 0 };
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			//s += ch;

			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}

}

下面是测试的代码

#define _CRT_SECURE_NO_WARNINGS 1
#include "String.h"
#include <string>

void teststring1()
{
	String::string s1("hello world");
	cout << s1.c_str() << endl;

	String::string s2;
	cout << s2.c_str() << endl;
	//string s;
	//cout << s << endl;

	int i = 0;
	//for (i = 0; i < s1.size(); i++)
	//{
	//	s1[i]++;
	//}
	//cout << endl;

	for (i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << ' ';
	}
	cout << endl;


	//const String::string s3("hello hello");
	//for (i = 0; i < s3.size(); i++)
	//{
	//	s3[i]++;
	//}
	//cout << endl;

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

	for (auto ch : s1)
	{
		cout << ch << ' ';
	}

}
void teststring2()
{
	const String::string s1("hello world");
	String::string::const_iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;

	for (auto ch : s1)
	{
		cout << ch << ' ';
	}
}

void teststring3()
{
	String::string s1("hello world");
	cout << s1.c_str() << endl;
	s1.pushback('!');
	cout << s1.c_str() << endl;
	s1.append("hello world");
	cout << s1.c_str() << endl;

	String::string s2("hello world");
	cout << s2.c_str() << endl;
	s2 += '!';
	cout << s2.c_str() << endl;
	s2 += "hello world";
	cout << s2.c_str() << endl;


	String::string s3("hello world");
	cout << s3.c_str() << endl;
	s3.insert(0, 5, 'x');
	cout << s3.c_str() << endl;
	s3.insert1(0, 5, 'e');
	cout << s3.c_str() << endl;

	s3.erase(5, 4);
	cout << s3.c_str() << endl;

}


void teststring4()
{
	String::string url = "ftp://www.baidu.com/?tn=65081411_1_oem_dg";

	size_t pos1 = url.find("://");
	if (pos1 != String::string::npos)
	{
		String::string protocol = url.substr(0, pos1);
		cout << protocol.c_str() << endl;
	}

	size_t pos2 = url.find('/', pos1 + 3);
	if (pos2 != String::string::npos)
	{
		String::string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
		String::string uri = url.substr(pos2 + 1);

		cout << domain.c_str() << endl;
		cout << uri.c_str() << endl;
	}
}

void teststring5()
{
	String::string s("hello world");
	s.resize(8);
	cout << s.c_str() << endl;
	s.resize(13, 'x');
	cout << s.c_str() << endl;

	s.resize(20, 'y');
	cout << s.c_str() << endl;

	cout << s;
}

void teststring6()
{
	String::string s("hello world");
	s += '\0';
	s += "xxxxxx";

	cout << s.c_str() << endl;
	cout << s << endl;
}
void teststring7()
{
	String::string s;
	cin >> s;
	cout << s << endl;
	cin >> s;
	cout << s << endl;

}
void teststring8()
{
	//String::string s1("hello world");
	//s1 += '\0';
	//s1 += "xxxxx";

	//String::string s2("hello world");
	//s2 += '\0';
	//s2 += "yyyyy";
	//cout << (s2 < s1) << endl;

	//cout << (s2 == s1) << endl;
	String::string s1("hello");
	String::string s2("hello");
	cout << (s1 < s2) << endl;
	cout << (s1 > s2) << endl;
	cout << (s1 == s2) << endl << endl;


	String::string s3("hello");
	String::string s4("helloxxx");
	cout << (s3 < s4) << endl;
	cout << (s3 > s4) << endl;
	cout << (s3 == s4) << endl << endl;


	String::string s5("helloxxx");
	String::string s6("hello");
	cout << (s5 < s6) << endl;
	cout << (s5 > s6) << endl;
	cout << (s5 == s6) << endl << endl;

	s6 = s5;
	cout << s6 << endl;

}
int main()
{
	//teststring1();
	//teststring2();
	//teststring3();
	//teststring4();
	teststring8();

	return 0;
}

好了本期内容就到这里了
本期内容确实比较硬核,希望读者能够认真消化
好了我们下期内容再见!!!

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

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

相关文章

Fastify系列-从0到1超详细手把手教你使用Fastify构建快速的API

什么是Fastify&#xff1f; Fastify是一个web框架&#xff0c;高度专注于以最少的开销和强大的插件架构提供最佳的开发体验。它的灵感来自于Hapi和Express&#xff0c;据我们所知&#xff0c;它是运行在Node.js上的最快的Web框架之一。 为什么使用Fastify&#xff1f; 这些是…

PostgreSQL--实现数据库备份恢复详细教学

前言 这是我在这个网站整理的笔记&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;RodmaChen PostgreSQL--实现数据库备份恢复详细教学 一. 数据库备份二. 数据库恢复三. 存留问题 数据库备份恢复功能是每个产品所需的&#xff0c;以下是简单的脚本案例&a…

遇到了一个存在XSS(存储型)漏洞的网站

第一个漏洞self xss&#xff08;存储型&#xff09; 存在漏洞的网站是https://www.kuangstudy.com/ 然后点击个人设置 在编辑主页中&#xff0c;我们可以用最简单的script语句进行注入&#xff0c;提交&#xff1b; 出现弹窗&#xff0c;说明它已经把代码进行解析&#x…

【设计模式学习1】什么是单例模式?单例模式的几种实现。

一、什么是单例模式 单例模式是在内存中只创建一个对象的模式&#xff0c;它保证一个类只有一个实例。 二、单例模式的几种实现 &#xff08;一&#xff09;懒汉式单例模式 /*** 懒汉式单例模式* &#xff08;懒加载&#xff0c;需要的时候在去加载&#xff09;* 优点&…

递归——另类加法、走方格的方案数

大家好&#xff0c;这里是bang_bang&#xff0c;今天来记录2道递归的典型题目 目录 1.另类加法 2.走方格的方案数 1.另类加法 另类加法__牛客网 (nowcoder.com) 给定两个int A和B。编写一个函数返回AB的值&#xff0c;但不得使用或其他算数运算符。 测试样例&#xff1a;…

【Linux】system V IPC原理分析

目录 System V IPC分类 key_t 键和ftok函数 ipc_perm结构 创建和打开IPC通道 IPC权限问题 理解IPC工作原理 System V IPC分类 System V 消息队列System V 共享内存System V 信号量 之所以称为System V IPC是因为这三种IPC机制都是来源于System V Unix的实现。 消息队列信…

一款好用的思维导图软件drawio

最近需要画思维导图&#xff0c;结果发现既然被人用来收费了。所以记录一下&#xff0c;免得大家上当。 首先说明&#xff0c;这个东东在github上是免费开源的&#xff0c;收费的是一些不法分子搞得。下面是收费版本得界面。 开源地址&#xff1a; https://github.com/jgraph…

坑爹的shadow -- 总结 与 各种坑

作者&#xff1a;snwrking 最近公司来了新UX总监, 很喜欢给设计添加浓重的, 而且是好几层的阴影. 这下就苦了我们Android开发了. 因为是Android不支持啊, 巧妇也难为无米之炊啊. (折中方法也不是没有, 就是自己把阴影做个view, 但它的blur这些比较麻烦, 做过Android的都知道这个…

DAY1,Qt [ 手动实现登录框(信息调试类,按钮类,行编辑器类,标签类的使用)]

1.手动实现登录框&#xff1b; ---mychat.h---头文件 #ifndef MYCHAT_H #define MYCHAT_H#include <QWidget> #include <QDebug> //打印信息 #include <QIcon> //图标 #include <QPushButton> //按钮 #include <QLineEdit> //行编辑器类 #in…

ERC20 allowance,approve 和 transferFrom

allowance&#xff0c;approve 和 transferFrom&#xff0c;这几个函数提供了一些高级功能&#xff0c;用于授权其他以太坊地址的所有者(spender)代表你使用你的token。这个“其他以太坊地址”可能是一个智能合约&#xff0c;也可能只是一个普通token账户。 ● approve函数。T…

output delay 约束

output delay 约束 一、output delay约束概述二、output delay约束系统同步三、output delay约束源同步 一、output delay约束概述 特别注意&#xff1a;在源同步接口中&#xff0c;定义接口约束之前&#xff0c;需要用create_generated_clock 先定义送出的随路时钟。 二、out…

labview 多线程同步

所谓通讯的同步是指多个线程同时进行或严格按照顺序执行&#xff0c;数据的严格性是指发送多少数据接收多少数据&#xff0c;不能出现数据丢失或重复接收的现象。 labview的同步机制有事件发生、集合点、通知器、信号量。 可以这么来记忆&#xff1a;事急&#xff08;集&…

kotlin高阶函数

kotlin高阶函数 函数式API:一个函数的入参数为Lambda表达式的函数就是函数式api 例子: public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {return filterTo(ArrayList<T>(), predicate) }上面这段函数: 首先这个函…

Nginx配置server_name讲解

文章目录 1.Nginx配置中没有server_name会怎样&#xff1f;2.Nginx配置server_name的匹配规则3.正则表达式规则 1.Nginx配置中没有server_name会怎样&#xff1f; 此时Nginx会自动设置成 server_name ""; 它不会匹配任何域名&#xff0c;导致Nginx会优先将HTTP请求交…

2023年7月第3周大模型荟萃

2023年7月第3周大模型荟萃 2023.7.25版权声明&#xff1a;本文为博主chszs的原创文章&#xff0c;未经博主允许不得转载。 1、华为发布大模型时代 AI 存储新品 7 月 14 日华为在深圳发布了大模型时代 AI 存储新品&#xff0c;为基础模型训练、行业模型训练&#xff0c;细分场…

连锁反应开始了!Linux 发行版迎新变化!

任何企业都有合法权利捍卫其模型和产品。撇开大量不真正了解开源许可证如何工作的人不谈&#xff0c;我们的印象是&#xff0c;有很多人觉得仅仅因为这是Linux&#xff0c;他们就有某种权利免费获得它。但事实上&#xff0c;他们没有。这不是自由软件中的“自由”的意思&#x…

【VCS】(5)Fast RTL-level Verification

Fast RTL-level Verification General Coding GuidlinesLab --- simprofile$display() 输出彩色内容 前面的内容都是在说怎样进行仿真和验证&#xff0c;即如何使用 VCS 。 但是&#xff0c;仿真和验证是不是也有所讲究&#xff1f; 有没有一些标准来衡量设计代码和验证代码的质…

面向初学者的APP开发教程:开始你的编程之旅

不管你是已经在 APP开发行业中工作了很长时间&#xff0c;还是正在学习该领域的知识&#xff0c;都有必要开始学习如何编写一个应用程序。对于初学者来说&#xff0c;编写应用程序的第一步是使用 HTML和 CSS构建一个漂亮的UI。 一旦你学会了这些基本技能&#xff0c;你就可以开…

全志F1C200S嵌入式驱动开发(解决spi加载过慢的问题)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 之前的几个章节当中,我们陆续解决了spi-nor驱动的问题、uboot支持spi-nor的问题。按道理来说,下面要做的应该就是用uboot的loady命令把kernel、dtb、rootfs这些文件下载到ddr,然…

【Milvus】记录一次基于milvus-backup做的Milvus备份与恢复

文章目录 环境代码准备备份构建/运行验证 恢复遇到的问题 环境 milvus&#xff1a;v2.2.4 go&#xff1a;1.20.2 darwin/amd64 milvus-backup&#xff1a;v0.2.2 代码准备 https://github.com/zilliztech/milvus-backup/releases 如果你的milvus是2.2.9版本及以上&#xf…