《string的模拟实现》

news2024/12/24 1:00:33

本文主要介绍库里面string类的模拟实现

文章目录

  • 前言
  • 一、string的构造函数
      • ①无参的构造函数
      • ②带参的构造函数
      • ③修改构造函数
  • 二、析构函数
  • 三、拷贝构造
  • 四、赋值重载
  • 五、返回size 、capacity和empty
  • 六、[]的运算符重载
  • 七、迭代器
    • ① 正向迭代器。
    • ② 正向const迭代器
  • 八、string比较大小(运算符重载)
  • 九、扩容
    • ① reserve扩容
    • ② resize扩容
  • 十、尾插数据
    • ①push_back
    • ②append
  • 十一、+=的运算符重载(替换尾插数据的函数)
  • 十二、在固定为插入,删除(insert和erase)
    • ①插入一个字符
    • ② 插入一个字符串
    • ③ 删除
  • 十三、交换swap
  • 十四、find
    • ① 找字符
    • ②找子串
  • 十五、流插入,流提取
    • ① 流插入
    • ②流提取
  • 十六、全部源码
    • string.h
    • test.c


前言

首先创建两个文件

string.h :用来定义和声明类里面的成员函数和成员变量,以及测试函数。使用命名空间包裹
test.cpp:用来运行代码


一、string的构造函数

由于我们要模拟实现string,为了不和库里面的string冲突,我们需要使用命名空间将其封装起来。
其实底层其实和我们的顺序表很类似,也是三个成员变量(字符指针)

//定义一个命名空间,防止和库里面的sring类冲突
namespace mwq
{
	class string
	{
	public:
	
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
};

我们先把准备工作做好,在test.cpp里面写一个主函数,然后包含这个string.h头文件。

注意:我们还要包含#include <iostream> 以及using namespace std,因为我们后面需要在头文件的命名空间里面写测试函数,需要用到cout!!

#include <iostream>
using namespace std;

#include "string.h"

int main()
{
	mwq::test1();
	return 0;
}

①无参的构造函数

现在我们开始写一个无参的构造函数

//无参构造函数
string()
	:_str(nullptr)
	, _size(0)
	,_capacity(0)
{}

其实对于我们的无参构造函数看着没什么问题,其实有点问题。那我举一个例子大家看看什么问题。
我们前面了解c_str(返回C形式的字符串),我们如果要实现这个要求我们返回这个字符串的地址。

//返回C形式的字符串
const char* c_str()const
{
	return _str;
}

好了现在我们写一个test1测试一下(直接在命名空间里面写就行了)
在test.cpp里

#include <iostream>
using namespace std;

#include "string.h"

int main()
{
	mwq::test1(); //test1在string.h文件里面的mwq命名空间下
	return 0;
}

在string.h里

//定义一个命名空间,防止和库里面的sring类冲突
namespace mwq
{
	class string
	{
	public:
		//无参构造函数
		string()
		:_str(nullptr)
		, _size(0)
		,_capacity(0)
		{}
	
		//返回C形式的字符串
		const char* c_str()
		{
			return _str;
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
	
	void test1()
	{
		string s1;	 
		cout << s1.c_str() << endl;
};

注意:这里使用了cout,所以test.cpp里面必须有#include<iostream>以及using namespace std
因为头文件包含了#include<iostream>以及using namespace std,当我们预处理时候会将头文件展开在链接时,cout就会去他的定义,如果我们没有定义就会报错
所以在test.cpp里面必须写#include<iostream>以及using namespace std(cout是在这个里面定义的)

我们如果想打印我们的字符串,就会发现直接报错,那么出现了什么问题呢?
在这里插入图片描述

大家先思考一下
我们s1进行无参的构造函数,并且在初始化列表中初始化了空指针。我们先通过s1找到了c_str的成员函数,这时候是没有崩溃的,并且将空指针返回也是没有问题的。问题就出在我们通过这个空指针打印,因为cout在打印字符串时候会进行解引用,因为要找\0,这时就涉及到了空指针的解引用。就会出现问题。

②带参的构造函数

现在我们开始写一个带参的构造函数

string(const char* str)
	:_str(str)
	,_size(strlen(str))
	,_capacity(strlen(str))
{}

在这里插入图片描述

其实他也是有问题的
我们设置成带参数,str的类型是const char*,但是我们成员变量_str是char*类型,我们在初始化列表中传参数的时候就涉及到权限的放大。

那有没有解决办法呢?
我们可以想到一种解决办法就是将我们的成员参数也设置为const。
在这里插入图片描述

这样虽然可以运行,但是,这样的操作无疑是局限的,那么我们后续的操作就都不可以改变我们的_str。(因为你变成了const的类型)

例如:我们再实现是个[]运算符重载去访问这个字符串

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

此时去访问s2的数据并试图修改就会报错,这里是因为我们的_str是const,但是返回是char,
在这里插入图片描述
那我们再修改一下返回值,将返回值修改成const char&
在这里插入图片描述
还是不可以的,其实这个报错很容易看懂,因为我们的返回的是const char,这个是不能修改的
所以:我们的这个带参的构造函数是有问题的。

综上所述:我们的有无参数的构造函数的问题如下:

  1. 无参构造函数:不能初始化为nullptr,因为我们打印字符串需要解引用
  2. 带参构造函数:不能用str直接初始化,因为类型不匹配我们的_str要设置为char*类型,因为后续还要修改。

③修改构造函数

我们先改我们带参数的构造函数,我们的直接将我们的常量字符串给他,肯定不可以,那么我们就自己开一块空间,将字符串的内容拷贝过来。这样我们自己开辟的空间就可以自己随意修改,而不是像我们上面将成员变量修改为const。

我们开辟空间,就没有必要在初始化列表开空间,就直接在函数中开就可以。具体的细节我们开代码注释。

string(const char* str)
	:_size(strlen(str))
{
	//为什们将capacity的初始化也放到函数中?
	//在初始化列表,初始化的顺序跟定义的顺序有关,
	//如果我们先定义的capacity,我们size还没定义就是随机值。
	_capacity = _size;
	
	//因为capacity不包括字符的结束标准 \0 
	//所以在开辟空间的时候,我们多开一位。
	_str = new char [_capacity + 1];
	
	//将我们字符串的内容拷贝过来。
	strcpy(_str, str);
}

修改无参数的构造函数,我们防止空指针的解引用,我们可以也给他开一个字节的空间,放\0,也就是字符串的结束标志,表示空字符串。

string()
	//我们初始化为什们要用new[]呢?
	//为了跟我们带参构造函数保持一致,
	//在析构的时候用一样的类型。delete[] _str;
	:_str(new char[1])
	, _size(0)
	,_capacity(0)
{
	_str[0] = '\0'; //字符串结束标志,不写的话,直接打印s1.c_str()会崩溃,因为找不到\0
}

我们在类和对象中,我们知道,无参的构造函数可以用缺省参数代替,所以我们将带参数的构造函数用缺省参数,就可以将这两个合二为一。

//下面两种形式都可以,我们""里面就有\0。
//string(const char* str = "\0")
string(const char* str = "")
	:_size(strlen(str))
{
	_capacity = _size == 0 ? 3 : _size; //防止一开始_capacity = 0,对后面的代码有影响
	_str = new char [_capacity + 1];
	strcpy(_str, str);
}

二、析构函数

这个很简单

~string()
{
//注意的点就是,delete一定要和new保持一致
	delete[]_str;
	_str = nullptr;
	_size = _capacity = 0;
}

三、拷贝构造

编译器会自动生成我们的拷贝构造,但是我们在类和对象的章节学过,它完成的是浅拷贝。
对于我们的字符串,我们_str是一个指针,指向一块数组空间(new出来的),当我们完成浅拷贝的时候,我们拷贝出来的指针只是将_str的四个字节拷贝了过去,所以两块空间用指针指向同一块空间。

当我们进行析构函数的时候,我们同一块空间析构了两次,这显然是由问题的,所以会直接报错,我们不能直接用默认拷贝构造,我们要自己写。

深拷贝:

//拷贝构造
string(const string& s)
	: _size(s._size)
	, _capacity( s._capacity)
{
	//防止new失败,我们先创建一个临时变量开辟空间
	//拷贝完后,在给_str
	char* tmp = new char[s._capacity + 1];
	strcpy(tmp, s._str);
	_str = tmp;
}

四、赋值重载

这个需要考虑的情况比较多,我们看图

这个也是默认成员函数,编译器会自动生成,跟拷贝构造一样进行浅拷贝,和我们上面出现的情况一样,所以我们要进行深拷贝,自己写一个赋值重载。
在这里插入图片描述

我们可以看到,上面有三个问题

  1. 首先就得自己写一个深拷贝,和上面拷贝构造问题一样
  2. 当新空间>原空间时,需要扩容,还需要小心扩容失败了
  3. 当新空间=原空间,直接扩容
  4. 当新空间<原空间,直接拷贝过去会造成资源浪费

基于上面的这么多复杂的原因,我们编译器直接统一处理,直接new一个空间temp,将需要拷贝的数据拷给temp空间,然后释放原空间,再让原空间指向temp即可解决。

注意:这里为了满足连续赋值,我们使用引用返回

//赋值重载
string& operator=(string& s)
{
	//防止自己和自己赋值
	if (this != &s)
	{
		// 防止new失败,我们先创建一个临时变量开辟空间
		//拷贝完后,在给_str
		char* tmp = new char[s._capacity + 1];
		strcpy(tmp, s._str);
		delete[]_str;
		_str = tmp;
		_size = s._size;
		_capacity = s._capacity;
	}
	
	return *this;
}

五、返回size 、capacity和empty

就是将我们的size直接返回,需要注意的一点就是我们返回size,一般都是不可修改的,不可修改一般就设置成const
这几个我们都不希望他修改,统一用const修饰,这样普通对象可以调用,const对象也可以调用。

//获取字符串元素个数
size_t size()const
{
	return _size;
}

//返回空间容量大小
size_t capacity()const
{
	return _capacity;
}

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

六、[]的运算符重载

这个也是比较容易,就是返回字符串的位置,我们可以通过引用返回改变我们字符串的值。
但是当我们想要打印字符串的时候,我们并不想修改字符串,我们就可以用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];
		}

七、迭代器

我们暂时将其考虑为指针,就可以。
像用指针一样,用迭代器。我们先写一个正向迭代器。

① 正向迭代器。

//迭代器,//我们先知道怎么用就可以
typedef char* itterator;
itterator begin()
{
	return _str;
}
itterator end()
{
	return _str + _size;
}

用迭代器遍历字符串。

//测试迭代器
void test_string4()
{
	string s1("hello world");
	string::itterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//范围for的底层就是迭代器
	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
}

② 正向const迭代器

//正向const迭代器
typedef const char* const_itterator;
const_itterator begin() const
{
	return _str;
}
const_itterator end() const
{
	return _str + _size;
}

八、string比较大小(运算符重载)

注意:这里我们不需要修改字符串,所以使用const修饰

我们比较的不是两个对象的大小,而是两个字符串。使用strcmp即可

int strcmp ( const char * str1, const char * str2 );
str1<str2结果小于0
str1=str2结果等于0
str1>str2结果大于0

		//s1>s2
		bool operator>(const string& s) const
		{
			return strcmp(_str, s._str) > 0;
		}

		//s1==s2
		bool operator==(const string& s) const
		{
			return strcmp(_str, s._str) == 0;
		}

		//s1>=s2
		bool operator>=(const string& s) const
		{
			return strcmp(_str, s._str) >= 0;
		}

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

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

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

九、扩容

扩容我们知道有两种方式:一种是reserve,另外一种是resize

注意:reserve和resize都不支持缩容

① reserve扩容

首先介绍一下reserve

void reserve (size_t n = 0);

函数功能:将空间增加为n个,不会改变有效元素的个数(_size)

  1. 当参数n小于原空间时,啥也不干,空间大小不变
  2. 当参数n大于原空间时,将空间大小增加为n+1(为\0留一个空间)
void reserve(size_t n)
{
	//reserve 不支持缩容
	//所以我们自己加个条件
	if (n > _capacity)
	{
		//给\0开一个空间
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[]_str;
		_str = tmp;
		_capacity = n;
	}
}

② resize扩容

void resize (size_t n);
void resize (size_t n, char c);

函数功能:都是将字符串的有效字符改变到n个。不同的是当字符个数增多时:resize(n)用\0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。
注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

详细解释:

  1. 当n<size时,void resize (size_t n);将有效字符个数缩减为n个,全部初始化为\0
  2. 当size <= n <= capacity时,将有效字符个数增加为n个
    void resize (size_t n);将n-size个字符初始化为\0
    void resize (size_t n, char c);,将将n-size个字符初始化为字符c
    其实这种情况就是在有效字符结尾后面添加\0或者字符c
  3. n>capacity,将容量扩展至n+1个空间将n-size个字符初始化为\0或者字符c
    void resize (size_t n);将n-size个字符初始化为\0
    void resize (size_t n, char c);,将将n-size个字符初始化为字符c
    我们发现2和3其实差不多,3就先扩容在初始化后面的字符

综上所述:其实我们会发现,void resize (size_t n);,其实有点多余,我们直接使用缺省参数知识将
void resize (size_t n, char c = '\0');即可。

		//不写第二个参数默认改为\0,使用缺省参数就可以将其合二为一
		//1:n <= size
		//2:size < n <= capacity
		//3:n>capacity
		void resize(size_t n, char c = '\0')
		{
			if (n < _size)
			{
				memset(_str, c, n);
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				//if (n < _capacity)
				//{
				//	memset(_str + _size, c, n - _size); //size后面的n个字符
				//	_size = n;
				//	_str[_size] = '\0';
				//}
				//else //扩容
				//{
				//	reserve(n);
				//	memset(_str + _size, c, n - _size); //size后面的n个字符
				//	_size = n;
				//	_str[_size] = '\0';
				//}
				
				//第3种情况和第2种差不多
				if (n > _capacity)
				{
					reserve(n);//扩容
				}
				memset(_str + _size, c, n - _size); //size后面的n个字符
				_size = n;
				_str[_size] = '\0';//将即为填上\0,防止出错
			}
		}

注意:我们以后改变字符串的时候,

  1. 都要考结尾有没有\0,防止出错
  2. 记得改变字符串的size

十、尾插数据

增加数据有两种方式,
一种是push_back,尾插一个字符
一种是append,尾插一个字符串

①push_back

		//尾插字符
		string& push_back(char ch)
		{
			if (_size + 1 > _capacity)
			{
				//if (_capacity == 0)
				//{
					//_capacity = 3;
				//}
				reserve(_capacity * 2); //二倍扩容,
			}
			_str[_size] = ch;
			++_size; //不要忘记改变size了
			_str[_size] = '\0'; //结尾加上\0

			return *this;
		}

注意:我们这里会发现,当_size + 1 > _capacity时,我们才扩容至capacity * 2,但是我们构造函数如果一开始为0的话,这里显然就会出问题了,直接把空间干为0了。

解决办法:

  1. 我们在构造函数里面处理,一开始就给capacity一个空间,防止为0
  2. 我们可以在这个再判断一下,如果capacity为0,那我们就给他一个空间也可以

②append

我们这里处理扩容和push_back不一样,我们插多少,扩多少,因为如果原数组太小,知识扩二倍之后还可能放不下

//增加一个字符串
void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		//如果原数组太小,二倍之后还可能放不下
		reverse(_size + len);
	}
	strcpy(_str + _size, str);
	_size += len;//不要忘记改变size了
	
	//最后加上\0
	_str[_size] = '\0';

十一、+=的运算符重载(替换尾插数据的函数)

这个超级容易,其实就是直接复用我们的push_back和append。
利用运算符重载直接写两个即可

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

十二、在固定为插入,删除(insert和erase)

这时我们应该用insert
在我们的插入也分为插入一个字符,和插入一个字符串。

其实思路大致相同,都是先判断是否需要扩容,然后挪数据,插数据

注意:这里挪数据时,很容易出错
在这里插入图片描述
总结:以后挪数据都将结尾定位最后一个位置的下一个位置,然后挪动。

①插入一个字符

		//再pos
		string& insert(size_t pos, char ch)
		{
			//1:判断是否需要扩容
			assert(pos <= _size && pos >= 0);
			if (_size + 1 > _capacity)
			{
				reserve(_capacity * 2);
			}
			//2:挪数据
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			//3:插数据
			_str[pos] = ch;
			++_size;//不要忘记改变size了

			return *this;
		}

② 插入一个字符串

string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size && pos >= 0);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);//提前开空间
			}


			//挪动数据(包括\0)
			//循环size-pos+1次
			size_t end1 = _size;
			size_t end2 = _size + len;
			for (size_t i = 0; i <= _size - pos; ++i)
			{
				_str[end2--] = _str[end1--];
			}

			//插入数据
			strncpy(_str + pos, str, len);
			_size += len;//不要忘记改变size了

			return *this;
		}

我们这里解释一下挪数据的思路:我们只要考虑挪多少次就行了
在这里插入图片描述

插入数据直接使用strncpy将新的字符串从pos位置插入即可
char * strncpy ( char * destination, const char * source, size_t num );

③ 删除

string& erase (size_t pos = 0, size_t len = npos);
函数功能:就是删除从pos位置开始数,长度为len的字符(包括pos)

  1. 当len=npos时(npos是整形的最大值)或者要删除的len大于pos后面字符串的长度,直接将pos以及pos后面的所有字符删除,并且给pos位置赋值为\0
  2. 当len小于pos后面的字符串长度时候1,直接挪数据覆盖即可

注意:别忘了改变size

		//将pos位置的字符,横跨len个长度的串删除(包括pos)
		string& erase(size_t pos, size_t len = npos)
		{
			//要删除的个数比pos之后的字符串长
			if (len == npos || len >= _size - pos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

十三、交换swap

将我们两个指针进行交换。
所以比我们库里的交换函数快,少了一次拷贝构造,和两次赋值重载。

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

十四、find

就是在指定位置找。但是查找有两种方式
一种是查找字符
第二种是查找子串

函数原型:
size_t find(char c, size_t pos = 0)const;从pos位置(包括pos位置)开始找字符
size_t find(const char* str, size_t pos = 0)const;从pos位置(包括pos位置)开始找字符串

函数功能:找字符或者字符串,找到返回pos位置,找不到npos

① 找字符

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

②找子串

这里我们使用strstr
函数功能:函数就是在一个字符串中找另外一个字符串是否存在,找到了就返回匹配的字符串的首元素地址,找不到就返回空指针

函数原型:
const char * strstr ( const char * str1, const char * str2 );返回const型指针,对其解引用不能修改
char * strstr ( char * str1, const char * str2 );

//查找一个子串
size_t find(const char* str, size_t pos = 0)
{
	assert(pos < _size);

	char * p = strstr(_str + pos, str);
	if (p == nullptr)
	{
		return npos;
	}
	else
	{
		//指针相减就是中间的个数
		return p - _str;
	}
}

十五、流插入,流提取

① 流插入

我们的流插入不一定就是友元函数,我们也可以用迭代器,和[]来找到他的数据

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

②流提取

我们看下面这个流提取,并分析他为什么是错的

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

cin的缓冲区,输入多个数据,两个数据分割就是空格或者换行,编译器认为我们是要多个字符,会将所以我们的空格换行我们就拿不到,它一直在读数据。所以cin和scanf识别不了' '和'\n'

所以我们可以提取数据需要不区分空格的操作符。
正好C++种get就不会区分,每一个字符我们都可以拿到。

int get();
istream& get (char& c);

istream& operator>>(istream& in, string& s)
{
	char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;
	或者
	//char ch;
	//in.get(ch);
	
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		in.get(ch);
	}
	return in;
}

这个代码对于对象里面没有内容是没问题的,但是当对象里面一开始有数据时候就不可以了。
比如这种:

	void test10()
	{
		string s1("0123456789");
		s1 += '\0';
		s1 += "xxxxxxxx";

		cout << s1 << endl;
		cout << s1.c_str() << endl;

		cin >> s1;
		cout << s1 << endl;
	}

在这里插入图片描述

解释:我们调试会发现是扩容的原因,具体是strcpy导致的!!

我们发现中间出现了一堆乱码,因为在输入123后,cin开始拿数据,首先拿到1,尾插数据,没问题。当我们再拿2尾插的时候,此时我们的size+1 > capacity.我们就需要扩容。
在这里插入图片描述
加上1后。此时里面的数据是这个样子的0123456789\0xxxxxxxx1\0
此时我们的capacity=20,size=20,size+1>capacity,需要二倍扩容

在这里插入图片描述

但是,我们这里的扩容使用的是strcpy,他遇到第一个\0就会停下来。导致拷贝过去数据其实是
0123456789\0,后面的都没有拷贝过去
,但是我们的size没有变,仍然是20,只是中间的数字全部是随机值了。我们添加数字2和数字3仍然是在末尾处添加,最后打印发现中间都是乱码
在这里插入图片描述

解决:我们可以使用s.clear(),将字符串清空,这样就不会出现这种问题了。
void clear();

	//会有频繁扩容的问题
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;
		或者
		//char ch;
		//in.get(ch);
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			in.get(ch);
		}
		return in;
	}

但是这个代码其实还可以再优化一下,因为当我们输入的字符串太长时,会发生频繁扩容的问题。这个我们其实也可以解决的

	//使用一个数组,将数据填到数组里面,填满了再加一次,然后将i置为0,再重投开始填数组
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;
		char buffer[128];
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buffer[i++] = ch;
			if (i == 127)
			{
				buffer[127] = '\0';
				s += buffer;
				i = 0;
			}
			ch = in.get();
		}
		if (i != 0)
		{
			buffer[i] = '\0';//防止加了上一次后面遗留的数据
			s += buffer;
			i = 0;
		}
		return in;
	}

十六、全部源码

string.h

#pragma once


#include <assert.h>

//定义一个命名空间,防止和库里面的sring类冲突
namespace mwq
{
	class string
	{
	public:
		typedef char* iterator;  //要放在公共区域
		typedef const char* const_iterator;

		//迭代器begin和end
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

		//const迭代器
		const iterator begin() const
		{
			return _str;
		}
		const iterator end() const
		{
			return _str + _size;
		}

		//string()
		//	:_str(new char[1])
		//	, _size(0)
		//	, _capacity(0)
		//{
		//	_str[0] = '\0'; //字符串结束标志,不写的话,直接打印s1.c_str()会崩溃,因为找不到\0
		//} 

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

		//string(const char* str = nullptr)  不可以
		//string(const char* str = '\0')
		//string(const char* str = "\0")
		//构造函数
		string(const char* str = "")
			: _size(strlen(str))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}


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

		//赋值运算符重载,所有情况统一处理,全部新开一个空间,然后将字符串拷贝到新空间,再销毁_str,再将新空间赋值给_str
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* temp = new char[s._capacity + 1]; //定义一个新空间,防止扩容失败,多开一个放\0
				strcpy(temp, s._str);
				delete[] _str;
				_str = temp;
				_size = s._size;
				_capacity = s._capacity;
			}
			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];
		}

		//获取字符串元素个数
		size_t size()const
		{
			return _size;
		}

		//返回空间容量大小
		size_t capacity()const
		{
			return _capacity;
		}

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


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

		//返回C形式的字符串
		const char* c_str() const
		{
			return _str;
		}

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

		//s1>s2
		bool operator>(const string& s) const
		{
			return strcmp(_str, s._str) > 0;
		}

		//s1==s2
		bool operator==(const string& s) const
		{
			return strcmp(_str, s._str) == 0;
		}

		//s1>=s2
		bool operator>=(const string& s) const
		{
			return strcmp(_str, s._str) >= 0;
		}

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

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

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

		//尾插字符
		string& push_back(char ch)
		//void push_back(char ch)
		{
			if (_size + 1 > _capacity)
			{
				if (_capacity == 0)
				{
					_capacity = 3;
				}
				reserve(_capacity * 2);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';

			return *this;
			//insert(_size, ch);
		}

		//尾插字符串
		string& append(const char* str)
		//void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;

			return *this;
			//insert(_size, str);
		}

		//+=字符
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

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


		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size && pos >= 0);
			if (_size + 1 > _capacity)
			{
				reserve(_capacity * 2);
			}

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

			return *this;
		}

		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size && pos >= 0);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);//提前开空间
			}


			//挪动数据(包括\0)
			//循环size-pos+1次
			size_t end1 = _size;
			size_t end2 = _size + len;
			for (size_t i = 0; i <= _size - pos; ++i)
			{
				_str[end2--] = _str[end1--];
			}

			//插入数据
			strncpy(_str + pos, str, len);
			_size += len;

			return *this;
		}

		//将pos位置的字符,横跨len个长度的串删除(包括pos)
		string& erase(size_t pos, size_t len = npos)
		{
			//要删除的个数比pos之后的字符串长
			if (len == npos || len >= _size - pos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

		//reserve和resize
		//reserve不支持缩容
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* temp = new char[n + 1];
				strcpy(temp, _str);
				delete[] _str;
				_str = temp;
				_capacity = n;
			}
		}

		//不写第二个参数默认改为\0,使用缺省参数就可以将其合二为一
		//1:n <= size
		//2:size < n <= capacity
		//3:n>capacity
		void resize(size_t n, char c = '\0')
		{
			if (n < _size)
			{
				memset(_str, c, n);
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				//if (n < _capacity)
				//{
				//	memset(_str + _size, c, n - _size); //size后面的n个字符
				//	_size = n;
				//	_str[_size] = '\0';
				//}
				//else //扩容
				//{
				//	reserve(n);
				//	memset(_str + _size, c, n - _size); //size后面的n个字符
				//	_size = n;
				//	_str[_size] = '\0';
				//}

				if (n > _capacity)
				{
					reserve(n);
				}
				memset(_str + _size, c, n - _size); //size后面的n个字符
				_size = n;
				_str[_size] = '\0';
			}
		}

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

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


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

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

		static const size_t npos = -1;
	};

	void test1()
	{
		string s1;
		string s2("hello yaya");

		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;

		s2[0]++;

		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
	}

	void test2()
	{
		string s1;
		string s2("hello yaya");
		string s3(s2);

		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		cout << s3.c_str() << endl;

		string s4;
		s4 = s3;
		cout << s4.c_str() << endl;
	}


	void test3()
	{
		string s1("hello yaya");

		//1:使用[]
		for (size_t i = 0; i < s1.size(); ++i)
		{
			cout << s1[i] << " ";
		}
		cout << endl;

		//2:迭代器
		string::iterator it1 = s1.begin();  //iterator是类型,所以需要typedef一下
		while (it1 != s1.end())
		{
			cout << *it1 << " ";
			++it1;
		}
		cout << endl;

		//不能修改的版本
		/*string::const_iterator it2 = s1.begin();
		while (it2 != s1.end())
		{
			(*it2)++;
			cout << *it2 << " ";
			++it2;
		}*/

		//3:范围for,不用实现,因为底层就是调用迭代器
		for (auto ch : s1)
		{
			cout << ch << " ";
		}
	}


	void test4()
	{
		string s1;
		string s2("hello yaya");

		cout << (s1 > s2) << endl;  //0
		cout << (s1 == s2) << endl;//0
		cout << (s1 >= s2) << endl;//0
		cout << (s1 < s2) << endl;//1
		cout << (s1 != s2) << endl;//1
		cout << (s1 <= s2) << endl;//1
	}

	void test5()
	{
		string s1("hello yaya");

		s1.push_back('a');
		s1.push_back('b');

		s1.append("haha");
		s1 += 'm';
		s1 += "xxxxxx";
	}

	void test6()
	{
		string s1("hello yaya");

		/*s1.insert(0, 'x');
		cout << s1.c_str() << endl;
		s1.insert(2, 'x');
		cout << s1.c_str() << endl;
		s1.insert(10, 'x');
		cout << s1.c_str() << endl;*/

		s1.insert(0, "123"); //第0个位置
		cout << s1.c_str() << endl;

		s1.insert(2, "qqq"); //第2个位置
		cout << s1.c_str() << endl;

		s1.insert(10, "mmm"); //第3个位置 
		cout << s1.c_str() << endl;

		s1.erase(12, 20);
		cout << s1.c_str() << endl;

		s1.clear();
		cout << s1.c_str() << endl;

		s1.insert(0, "123"); //第0个位置
		cout << s1.c_str() << endl;
	}

	void test7()
	{
		string s1("hello yaya");
		s1.resize(5, 'x');
		cout << s1.c_str() << endl;

		s1.resize(20, 'a');
		cout << s1.c_str() << endl;
	}

	void test8()
	{
		string s1("hello yaya");
		cout << s1.find('o', 0) << endl;
		cout << s1.find("yaya", 0) << endl;
	}

	//直接在函数外面定义全局函数,使用迭代器和[]也能找到数据,不一定非要用友元
	ostream& operator<<(ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			out << s[i] << "";
		}
		return out;

		/*for (auto ch : s)
		{
			out << ch << "";
		}
		return out; */

	}

	//不行,因为字符换字符之间是空格或者换行作为截至的,但是这里面从缓存区读取空格或者换行时,将其看成是一个字符了,导致无休止的输入
	//istream& operator>>(istream& in,string s)
	//{
	//	char ch;
	//	in >> ch; //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据
	//	while (ch != ' ' && ch != '\n')
	//	{
	//		s += ch;
	//		in >> ch;
	//	}
	//	return in;
	//}

	//会有频繁扩容的问题
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;
		或者
		//char ch;
		//in.get(ch);
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			in.get(ch);
		}
		return in;
	}


	使用一个数组,将数据填到数组里面,填满了再加一次,然后将i置为0,再重投开始填数组
	//istream& operator>>(istream& in, string& s)
	//{
	//	s.clear();
	//	char ch = in.get(); //当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin对象直接从输入缓冲区中取数据;
	//	char buffer[128];
	//	size_t i = 0;
	//	while (ch != ' ' && ch != '\n')
	//	{
	//		buffer[i++] = ch;
	//		if (i == 127)
	//		{
	//			buffer[127] = '\0';
	//			s += buffer;
	//			i = 0;
	//		}
	//		ch = in.get();
	//	}
	//	if (i != 0)
	//	{
	//		buffer[i] = '\0';//防止加了上一次后面遗留的数据
	//		s += buffer;
	//		i = 0;
	//	}
	//	return in;
	//}
	void test9()
	{
		string s1("hello yaya");
		cout << s1 << endl;;
	}

	void test10()
	{
		string s1("0123456789");
		s1 += '\0';
		s1 += "xxxxxxxx";

		cout << s1 << endl;
		cout << s1.c_str() << endl;

		cin >> s1;
		cout << s1 << endl;
	}


};

test.c

#define _CRT_SECURE_NO_WARNINGS 1



#include <iostream>
using namespace std;



#include "string.h"

int main()
{
	mwq::test10();
	return 0;
}

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

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

相关文章

PointPillars点云编码器代码运行过程中的问题及解决

PointPillars:点云编码器&#xff0c;编码特征可以与任何标准的 2D 卷积检测架构一起使用。任务是目标检测。来自CVPR2019 论文地址&#xff1a;https://arxiv.org/pdf/1812.05784.pdf 代码地址&#xff1a;GitHub - nutonomy/second.pytorch: PointPillars for KITTI object…

【LeetCode】297. 二叉树的序列化与反序列化

1.问题 序列化是将一个数据结构或者对象转换为连续的比特位的操作&#xff0c;进而可以将转换后的数据存储在一个文件或者内存中&#xff0c;同时也可以通过网络传输到另一个计算机环境&#xff0c;采取相反方式重构得到原数据。 请设计一个算法来实现二叉树的序列化与反序列…

Css如何优雅的实现抽奖转盘

如图&#xff0c;抽奖转盘&#xff0c;可以拆分为几部分&#xff1a; 1.底部大圆&#xff1b; 2.中间小圆&#xff1b; 3.扇形区&#xff1b; 4.扇形内部奖品区&#xff1b; 5.抽奖按钮&#xff1b; 6.点击抽奖按钮时旋转动效及逻辑&#xff1b; 这其中&#xff0c;扇形区&am…

集成灶/小家电语音提示芯片方案-WTN6040-8S唯创知音自主研发

集成灶一直是厨房中常用设备之一&#xff0c;而现代技术的不断发展&#xff0c;为集成灶的升级提供了更多的可能性。深圳唯创知音为了让家用电器更加便民&#xff0c;专门为集成灶开发了一款语音IC方案——WTN6040语音芯片方案&#xff0c;这款芯片可以满足集成灶对语音提示功能…

独家专访丨TheStage.ai :当 AI 邂逅 Web3

随着2022年末ChatGPT走红&#xff0c;AI再次成为人们关注的焦点。上一次AI掀起舆论热潮&#xff0c;还是2016年AlphaGo以4:1战胜世界顶级围棋棋手李世石。 但与上次不同的是&#xff0c;这次AI更强大了。在接受强化训练后&#xff0c;AI可以对用户的需求创造新内容。 面对AI的快…

关于API数据接口的使用说明

API&#xff08;Application Programming Interface&#xff09;是一种让不同软件之间进行数据交换和通信的技术&#xff0c;使用API可以减少开发者的工作量&#xff0c;提高软件应用的效率和可靠性。本文将介绍API数据接口的使用方式和注意事项。 使用API数据接口的方式&…

Python一行代码实现文件共享【内网穿透公网访问】

目录 1. 前言 2. 视频教程 3. 本地文件服务器搭建 3.1 python的安装和设置 3.2 cpolar的安装和注册 4. 本地文件服务器的发布 4.1 Cpolar云端设置 4.2 Cpolar本地设置 5. 公网访问测试 6. 结语 转载自内网穿透工具的文章&#xff1a;Python一行代码实现文件共享【内网…

PPOCRV3文本识别模型精度损失问题解决

PPOCRV3文本识别模型精度损失问题解决 1. 得到可用的ncnn模型2. 先看问题3. 快速解决4. 问题分析5. 最终效果6. 结语 1. 得到可用的ncnn模型 paddleocr文本识别模型(ch_PPOCRv3_rec_infer)转ncnn模型&#xff0c;我参考了这位大神的博客&#xff0c;基本包括了我遇到的所有问题…

操作系统八股文知识点汇总

1. 程序编译过程 gcc HelloWorld.c -E -o HelloWorld.i 预处理&#xff1a;加入头文件&#xff0c;替换宏。gcc HelloWorld.c -S -c -o HelloWorld.s 编译&#xff1a;包含预处理&#xff0c;将 C 程序转换成汇编程序。gcc HelloWorld.c -c -o HelloWorld.o 汇编&#xff1a;包…

在linux下搭建clash服务

下载clash并配置 clash安装包 一般下载名称中带clash-linux-amd64的包 下载完用gunzip解压&#xff0c;解压后重命名或者链接到系统环境变量目录都行 下载配置信息 wget -O config.yaml [订阅链接] wget -O Country.mmdb https://www.sub-speeder.com/client-download/Coun…

深元边缘计算盒子在社区的应用,提高社区的安全性和生活质量

近年来&#xff0c;随着人工智能技术的不断发展和普及&#xff0c;越来越多的社区开始应用边缘计算盒子AI视觉分析技术&#xff0c;以提高社区的安全性和管理效率。本文将介绍边缘计算盒子AI视觉分析技术在社区中的应用及其优势。 一、边缘计算盒子AI视觉在社区中的应用 1.安防…

uniapp中实现自定义导航栏

整个小程序默认配置存在系统内置导航和tabbar&#xff0c;项目中需求存在自定义的导航。 uniapp中vue封装组件&#xff08;顶部导航、底部tabbar&#xff09;&#xff0c;按照vue的相关语法使用。 在page.json文件中修改配置&#xff1a; 自定义导航组件&#xff1a; 给自定义…

PLM听过很多遍,却依旧不知道是什么?看完这篇你就懂

上周参加展会&#xff0c;很多客户在现场了解到e企拆图解决方案后&#xff0c;向我们咨询了很多问题&#xff0c;发现有几个名词经常被提及&#xff0c;比如PLM、PDM、BOM等。随着技术的爆炸发展&#xff0c;新的名词概念也与日俱增&#xff0c;对于这些名词&#xff0c;可能我…

工贸企业重大事故隐患判定标准,自2023年5月15日起施行

应急管理部发布了《工贸企业重大事故隐患判定标准》&#xff08;自2023年5月15日起施行&#xff09;&#xff0c;适用于判定冶金、有色、建材、机械、轻工、纺织、烟草、商贸等工贸企业重大事故隐患。新修改的安全生产法对建立健全重大事故隐患治理督办制度、督促生产经营单位消…

关于ffmpeg的使用过程中遇到的点(php)

有段日子没更新&#xff0c;最近使用ffmepg&#xff0c;这里记录一下 我这边就直说一下我工作中遇到的注意事项和使用方法&#xff0c;就不太详细说了 首先是安装的问题&#xff0c;windwos的话比较简单&#xff0c;官网下载安装文件&#xff0c;解压之后。设置环境变量 系统…

【react全家桶学习】react中组件定义及state属性(超详/必看)

函数式组件定义及特点 定义&#xff08;核心就是一个函数&#xff0c;返回虚拟dom&#xff09;&#xff1a; import React from reactexport default function index() {return <div>index</div> }特点&#xff1a; 1、适用于【简单组件】的定义2、是一个函数&a…

【三维重建】NeRF原理+代码讲解

文章目录 一、技术原理1.概览2.基于神经辐射场&#xff08;Neural Radiance Field&#xff09;的体素渲染算法3.体素渲染算法4.位置信息编码&#xff08;Positional encoding&#xff09;5.多层级体素采样 二、代码讲解1.数据读入2.创建nerf1.计算焦距focal与其他设置2.get_emb…

1690_Python中的复数数据类型

全部学习汇总&#xff1a;GreyZhang/python_basic: My learning notes about python. (github.com) 之前总结的知识中设计的数据类型有整形、浮点、字符串等&#xff0c;这些类型表示的都是一个单独的独立数据对象。在Python有也有表示复数改变的数据类型&#xff0c;也就是下…

Gradio入门到进阶全网最详细教程[二]:快速搭建AI算法可视化部署演示(侧重参数详解和案例实践)

常用的两款AI可视化交互应用比较&#xff1a; Gradio Gradio的优势在于易用性&#xff0c;代码结构相比Streamlit简单&#xff0c;只需简单定义输入和输出接口即可快速构建简单的交互页面&#xff0c;更轻松部署模型。适合场景相对简单&#xff0c;想要快速部署应用的开发者。 …

千云物流 -测试服务器准备 -iotdb,redis

服务器准备 准备CentOS-7-x86_64-DVD-2009.iso镜像 链接&#xff1a;https://pan.baidu.com/s/1rNkfoeHOuYv0OmitWVDNsQ?pwdjanl 提取码&#xff1a;janl 安装服务器需要的命令yum update yum install net-tools.x86_64 -y yum install zip unzip -y ## 安装jdk到当前机器&am…