小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
c++系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
在这里插入图片描述



前言

在学习string的模拟实现的时候一定要了解string的各个接口是什么作用,所以在学习本文前,请读者友友们先阅读【c++】STL容器-string的使用—书接上文 详情请点击<—,本文会在已经学习了string的使用的基础上进行讲解
本文由小编为大家介绍—【c++】STL容器-string的模拟实现


一、基本框架

对于string的实现,我们要实现的是一个模拟版本,小编会带领读者友友们实现string的基本接口,帮助读者友友们更好的理解string的底层,并且更深一步的掌握string

  1. 我们知道在STL容器中,都是使用模板来实现的,string的basic_string模板实例化出来的,那么既然设计到模板,为了更好的熟悉和适应以后模板声明和定义不分离的写法,本文string的实现,小编也对于string的模拟实现采用声明和定义不分离的写法
  2. 并且注意由于我们在test.cpp中需要包含头文件#include<iostream>和展开std命名空间,那么我们自己实现的string就会和库里的string重名,所以我们在stirng.h中开辟一个我们自己的命名空间,在这个命名空间中用于我们string的模拟实现

那么我们就需要两个文件,string.h用于放我们的声明和定义,test.cpp文件放main函数,包#include "string.h"的头文件,进行模拟实现的测试,在测试的时候声明命名空间域即可使用我们自己实现的string
在这里插入图片描述

二、模拟实现

注意事项:

  1. 对于绝对不可能为负数的整型值,我们使用size_t进行定义,例如用于访问数组下标的pos位置
  2. 同时当涉及到使用pos来接收要访问的数组下标位置,那么这个位置不应该大于_size这样会造成越界,还不应该等于_size这样访问了标识字符串结束的’\0’,'\0’这个位置不是有效字符,我们要访问的是有效字符位置,所以我们使用assert进行断言一下让pos的值小于_size即可
  3. 为了兼容c语言,string规定在有效字符的下一个位置处放置一个 ‘\0’,并且这个‘\0’不计入有效字符和容量大小
  4. string进行遍历看的是有效数据个数_size,不看’\0’,c语言字符串进行变量看的是’\0’,使用’\0’作为字符串结束的标识,c++是为了兼容c语言才在在有效字符的下一个位置处放置一个 ‘\0’,同时照顾一下c_str类似的接口,这种接口是为了更好的在某些场景下兼容并且使用c语言的接口

铺垫

  1. 我们知道string的作用其实就是对字符数组进行管理,那么可以实现对字符数组的增删查改和空间容量的扩容等操作
  2. 那么我们就需要有一个指针指向一段空间,这段空间存放字符,这些字符序列构成字符串,为了兼容c语言,string规定在有效字符的下一个位置处放置一个 ‘\0’,并且这个‘\0’不计入有效字符和容量大小,需要容量用于确定当前空间能存放多少个有效数据,并且用于判断下次插入数据的时候空间是否需要扩容,就要有当前有效字符的大小,并且数组下标是从0开始,在进行尾插的操作插入的时候,就是直接从下标为size位置直接插入
  3. 并且我们还需要一个静态成员变量类型为size_t的npos并且在类外初始化为-1用于表示整形的最大值,这个npos后期我们会用于判断结束条件,这里先进行铺垫一下
  4. 我们要用到的函数strlen和strstr和memcpy和memcmp的头文件是#include <string.h>不要忘记包含在string.h文件中
namespace wzx
{
	class string
	{
	public:
	
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	public:
		static size_t npos;
	};
	size_t string::npos = -1;

构造函数 string

  1. 这里我们实现一个全缺省的构造函数,使用初始化列表进行初始化,在使用的时候不要忘记初始化列表初始化的顺序和成员变量声明的顺序是对应的即可
  2. 默认我们使用new[]给_str指向的空间开字符串大小加1个空间,因为为了兼容c语言,我们还要在最后一个有效字符的后面放一个’\0’用于标识字符串的结束
  3. 对于_size的大小我们采用strlen直接去求即可,并且_capacity可以直接使用_size的值,因为_size成员变量的声明顺序早于_capacity
  4. 上面我们开好了空间,初始化了个数和容量,但是我们的值是不是还没有进行拷贝,那么我们调用一下memcpy进行拷贝即可,这里有一点需要注意,在拷贝str的时候,_size是有效字符个数,规定是不将’\0’的个数计入在内,由于我们的字符数组的最后一个有效字符后面还必须放一个’\0’,所以说我们要想连并将str字符串中的\0’进行拷贝的话应该使用_size+1的值作为memcpy用于拷贝的个数
string(const char* str="")
		: _str(new char[strlen(str) + 1])
		, _size(strlen(str))
		, _capacity(_size)
	{
		memcpy(_str, str, _size + 1);
	}

析构函数 ~string

  1. 由于我们使用的是new[]开的空间,所以搭配new[],我们采用delete[]进行释放_str的空间
  2. 释放完之后将_str指针置空以及将_size和_capacity置为0即可完成我们析构函数的所有步骤
~string()
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}

拷贝构造 string

  1. 拷贝构造这里我们采用深拷贝的形式进行拷贝,如果是编译器默认生成的方式是按照字节进行的浅拷贝,那么就会出现同一块空间被连续析构两次的问题等,具体原因请点击<—查看链接文章中三、拷贝构造函数2.要点介绍 大概是中间一段即可有小编编写的详细原因介绍
  2. 那我我们就要开和string类对象s一样大的空间并且这个空间默认多开一个用于放置’\0’,同时使用memcpy完成对数据的拷贝并且用于拷贝的大小也默认多拷贝一个同样是要在最后一个有效数据的后面拷贝上’\0’的缘故
  3. 接下来让容量和有效数据大小和s的对应容量和对应有效数据大小相等即可完成我们的拷贝构造
string(const string& s)
{
	_str = new char[s._capacity+1];
	memcpy(_str, s._str,s._size+1);
	_capacity = s._capacity;
	_size = s._size;
}

c_str

  1. 这个接口用于返回c语言形式的字符串即可
  2. 由于我们不对数据进行修改,所以我们将this指针用const进行修饰,这样普通对象和const对象都可以进行调用
		const char* c_str() const
		{
			return _str;
		}
测试
  1. 我们使用如下代码进行测试,观察s1是否能被构造出来,s1调用的c_str能否正常打印,观察s2能否拷贝s1,观察当局部对象s1和s2在出了作用域之后自动调用的析构函数能否完成资源清理的工作
void string_test1()
{
	wzx::string s1("hello cpp");
	cout << s1.c_str() << endl;

	wzx::string s2(s1);
	cout << s2.c_str() << endl;
}

测试无误,成员函数编写正确
在这里插入图片描述
在这里插入图片描述
出了作用域编译器自动调用了析构函数完成了s1和s2对象资源清理的工作在这里插入图片描述

operator[] 和 由const修饰的operator []

  1. 我们知道string的底层实际上是一个字符数组,那么在类对象的私有成员变量中有指向字符数组的指针_str,使用_str[pos]的方式可以访问pos位置的元素在底层等于*(_str+pos)的方式,那么_str有是私有成员变量,在类外无法访问,但是在类内可以突破访问限定符进行访问,所以我们直接返回_str[pos]即可,并且由于出了作用域这个元素还在,这个元素不属于全局变量或局部变量,而是属于对象,只要对象不销毁这个元素就一直存在,所以我们使用返回引用的方式进行返回即可
  2. 同时我们还应该提供一个const版本的operator[],专门用于const对象调用,权限是只读不可修改,普通版本的operator[]是专门用于不被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];
}

begin end 对应的 iterator 和 const_iterator

  1. 迭代器是被typedef出来的,可能是指针也可能不是指针,但是在stirng这里小编实现的iterator迭代器为char类型的指针,同时还有一个const_iterator类型的迭代器就是使用const修饰char指针,iterator是提供给普通对象使用,权限为可读可写,const_iterator是提供给const对象使用,权限为只读
  2. 并且编译器会根据对象的类型,自动调用最为适合类型的迭代器,当遇到普通对象的时候会去调用普通的迭代器当没有对应的普通迭代器才会去调用const类型的迭代器,当遇到const对象的时候会去调用const类型的迭代器
    在这里插入图片描述
  3. 那么我们根据上图的位置关系,返回迭代器的对应指针位置即可
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;
}
测试
  1. 普通对象可以使用operator[]和迭代器进行可读可写,const对象只能使用其进行读,那么我们依次测试一下普通对象和const对象即可
void string_test2()
{
	wzx::string s1("hello cpp");
	cout << s1.c_str() << endl;

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

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

	const wzx::string s2("hello cpp");

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

	wzx::string::const_iterator cit = s2.begin();
	while (cit != s2.end())
	{
		cout << *cit;

		cit++;
	}
	cout << endl;
}

测试结果如下,正确
在这里插入图片描述

范围for

  1. 我们可以使用范围for对string类对象进行遍历,并且范围for的底层是迭代器,在我们使用范围for语法的时候,编译器自动将范围for转换为迭代器进行遍历,同时如果容器支持迭代器语法,那么这个容器就一定支持范围for,这里如果要对元素进行修改不要忘记使用auto推导类型的时候加引用
  2. 当编译器遇到范围for的时候,会无脑的将范围for替换为迭代器,并且按照c++标准库的标准去寻找对应的begin和end,并且它只认这两个名称,当我们进行修改了begin和end的名称之后,范围for会失效

那么我们使用范围for对我们的s1进行修改

void test()
{
	wzx::string s1("hello cpp");
	cout << s1.c_str() << endl;

	for (auto& e : s1)
	{
		e++;
	}
	cout << s1.c_str() << endl;
}

此时迭代器名称正常
在这里插入图片描述
可以正常进行修改
在这里插入图片描述
并且我们打开返回反汇编观察,范围for确实是调用的我们的迭代器
在这里插入图片描述
那么接下来小编略施小计,修改begin为Begin,并且注意,这里的修改不只修改普通版本的迭代器,当我们的普通版本的迭代器不匹配的时候,编译器会去调用const类型的迭代器(我们这里的对象是普通对象调用const类型的迭代器属于权限的缩小),要将const版本的迭代器一并修改(其实不修改也没事,因为const类型的迭代器去调用了也无法进行修改,这里我们的需求是修改数据,调用了之后无法修改一样报错),那么范围for能否正常运行呢?
在这里插入图片描述
如下显示,调用失败
在这里插入图片描述

size

capacity

  1. 针对size和capacity直接返回对应的值即可

empty

  1. 针对empty这里我们直接使用_size是否等于0进行判断
  2. 如果等于0那么就是string中没有数据,==返回结果为1,也就是不为空,函数判断为真
  3. 如果不等于0那么就是string中有数据,==返回结果为0,也就是为空,函数判断为假

同时针对size和capacity和empty这三个函数,我们只是获取对应的值或进行判断,并不进行修改成员变量的值,那么我们可以都将其使用const进行修饰,这样不仅是普通对象可以调用const对象也可以进行调用

size_t size() const
{
	return _size;
}

size_t capacity() const
{
	return _capacity;
}

bool empty() const
{
	return _size == 0;
}
测试
  1. 我们分别进行有数据对应普通对象和没数据对应const对象的测试即可
void test1()
{
	wzx::string s1("hello cpp");

	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1.empty() << endl;

	wzx::string s2;

	cout << s2.size() << endl;
	cout << s2.capacity() << endl;
	cout << s2.empty() << endl;
}

测试如下,正确
在这里插入图片描述

reserve

  1. 针对reserve,我们默认采取的策略是只扩不缩,所以在使用reserve的时候,要先判断一下用于接收扩容大小的n和容量的关系,如果n小于等于容量,那么我们不进行处理,如果c大于容量,我们才进行扩容
  2. 在c++中的扩容机制不同于c语言使用realloc的扩容机制关于realloc的扩容机制详情请点击<—,在c++中进行扩容,我们统一采用异地扩容,即使用new[]开辟新空间,拷贝原空间的数据到新空间上,之后释放掉原空间,让指向原空间的指针指向新空间并且调整容量即可
  3. 同时这里关于开空间和拷贝空间涉及到’\0’的问题,小编在上文已多次进行讲解,这里就不再过多赘述
void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		memcpy(tmp, _str, _size + 1);
		delete[] _str;
		_str = tmp;

		_capacity = n;
	}
}

push_back

  1. push_back是尾插一个字符,那么在尾插的时候我们要先检查容量如果容量不够,那么我们要先进行扩容,这个扩容我们默认采用扩容2倍的策略,并且注意当我们要尾插的string对象中没有数据,_str指向的是空字符串的时候,那么此时的容量为0,那么扩2倍仍然为0,就可能会出现bug,那么此时我们给一个初始4即可,由于要插入的是一个字符,4个字节的空间肯定够用
  2. 那么接下来扩完容之后,容量肯定是够用的,接下来我们进行插入数据,我们知道string实际上管理的就是一个字符数组,那么有效数据个数_size的值对应就问字符数组有效数据的最后一个数据的下一个位置,那么我们尾插一个字符直接在_size位置进行插入即可,插入完之后_size加1,有效个数加1,这时候还没完,为了兼容c语言同时标识字符串的结束,我们还应该在最后一个有效数据的下一个位置即_size位置处放一个’\0’即可完成我们的尾插工作
void push_back(char ch)
{
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	_str[_size++] = ch;
	_str[_size] = '\0';
}

append

  1. append是尾插字符串,其实主逻辑和push_back类似
  2. 那么首先同样的我们要先检查是否需要扩容,那么就要先求出要插入的字符串的长度len,接下来当当前有效字符个数的大小加上即将插入字符串的长度len如果小于或等于容量,那么说明容量是足够的,如果大于容量,那么说明当前容量剩余空间不足以放下要尾插的字符串,那么需要扩容,这里我们不能盲目的在当前容量大小的基础上扩二倍,如果要插入的字符串长度len很长很长为100,而容量很小,只有15,那么扩二倍后,也无法满足需求,为了一步到位并且满足需求,这里我们使用reserve扩容,扩容的值采用当前有效字符个数加上要插入字符串的长度len,这里注意不需要在扩容的值的基础上加1给’\0’留空间,reserve会帮我们给’\0’的留空间的
  3. 那么空间开好了之后,接下来就是遍历要插入的字符串,遍历len次,依次从string指向的字符数组的有效字符的下一个位置开始放置即可(本来这个地方为’\0’,不要忘记结束之后更新当前有效字符的个数,再在字符数组的有效字符的下一个位置开始方式放置一个’\0’)
void append(const char* str)
{
	size_t len = strlen(str);

	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	for (size_t i = 0; i < len; i++)
	{
		_str[_size + i] = str[i];
	}
	
	_size += len;
	_str[_size] = '\0';
}

operator+=

  1. 由于可能会出现连续加等的情况,所以返回值要返回进行string对象,这里返回就为this指针的解引用就为string对象,由于string对象出了加等重载函数仍然存在,所以采用传引用的方式返回
  2. 那么会有加等字符调用push_back即可,加等字符串调用append即可
string& operator+=(char ch)
{
	push_back(ch);

	return *this;
}

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

	return *this;
}
测试
  1. 我们依次使用string对象调用push_back,append,+=即可完成我们的测试
void string_test3()
{
	wzx::string s("hello cpp");

	s.push_back('#');
	cout << s.c_str() << endl;
	s.append("hello wzx");
	cout << s.c_str() << endl;

	s += '#';
	cout << s.c_str() << endl;
	s += "hello wzx";
	cout << s.c_str() << endl;
}

测试如下,正确
在这里插入图片描述

insert n个字符

  1. 在pos位置插入n个字符,不同于上面断言的常规情况,我们插入的时候可以头插,中间插,尾插(此时数组下标为_size),任意位置插入,由于插入也支持尾插那么pos就应该也支持等于_size,在结合之前pos常规插入的时候取值范围本就该小于_size,所以综合一下,断言pos小于等于_size
  2. 在插入的时候要先检查容量问题,和append的扩容检查方式类似,这里小编就不再过多赘述
  3. 接下来容量是足够的,那么我们要在pos位置处进行插入数据,就要确保原数据不丢失,那么这时候我们就需要将pos位置及其向后位置的数据,统一向后挪n个位置,这里的挪动我们采用从最后一个有效数据的下一个位置开始即’\0’位置处开始进行挪动,一直挪动到pos位置数据挪动完之后结束,这样我们就不再需要额外再补’\0’了
    4,这里由于我们操作的都是size_t无符号整形的情况,当我们要插入的位置pos是0的时候,进行挪动数据进行判断会出现特殊情况,具体原因小编已经注释在insert的实现代码中了
  4. 那么接下来我们从pos位置处开始依次填充n个数据ch即可
  5. 填充完毕我们调整有效字符的个数,并且返回this指针的解引用(因为要返回的string对象不是局部对象而是出了插入函数还存在,生命周期未结束),即返回string对象插入之后的结果即可
    在这里插入图片描述
string& insert(size_t pos,size_t n, char ch)
{
	assert(pos <= _size);

	if (n + _size > _capacity)
	{
		reserve(n + _size);
	}

	//由于是size_t类型,当pos位置为0的时候,无符号类型无法减到为0,那么
	//就会一直大于,一直循环,出现栈溢出的问题,当为无符号类型减到-1的时候
	//,-1在内存中的补码全为1,那么转换为无符号类型就为整形的最大值,默认
	//我们认为字符串的长度不会大于整形最大值,所以当pos位置为0的时候要特殊判断
	for (size_t i = _size; i >= pos && i != npos; i--)
	{
		_str[i + n] = _str[i];
	}

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

	_size += n;

	return *this;
}

insert 字符串

  1. insert插入字符串其实和插入n个字符的操作类似,这里小编就不再过多赘述
string& insert(size_t pos, const char* str)
{
	assert(pos <= _size);

	size_t len = strlen(str);

	if (len + _size > _capacity)
	{
		reserve(len + _size);
	}

	for (size_t i = _size; i >= pos && i != npos; i--)
	{
		_str[i + len] = _str[i];
	}

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

	_size += len;

	return *this;
}
测试
  1. 这里我们依次对string对象s依次插入n个字符和插入字符串即可
void string_test4()
{
	wzx::string s("hello cpp");

	s.insert(5,5,'x');
	cout << s.c_str() << endl;
	s.insert(0, "######");
	cout << s.c_str() << endl;
}

测试结果如下,正确
在这里插入图片描述

find 从pos位置处开始查找字符

  1. 基础准备:当用户传入pos位置的时候,就从pos位置开始查找,如果没有传入,那么我们默认从0位置处开始进行查找,由于find只是查找字符起始的下标位置,并不修改对象中的成员变量数据,所以我们可以给find函数加上const修饰,让普通对象和const对象都可以进行调用
  2. 这里的查找字符策略我们采用暴力遍历匹配即可,如果找到了则返回下标位置,找不到则返回npos,npos表示整形的最大值,我们默认字符串的长度并不会达到npos,所以用npos来代指找不到
size_t find(char ch, size_t pos = 0) const
{
	assert(pos < _size);

	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}

	return npos;
}

find 从pos位置处开始查找字符串

  1. 基础准备和find查找字符相同,这里小编就不过多赘述
  2. 这里小编由于是查找字符串,那么小编这里采用调用c语言库中的strstr进行匹配查找(这时候就可以突出我们兼容c语言在有效字符的最后一个位置处放置’\0’的重要性),查找传参是时候,由于是从pos位置处开始,所以要给_str+pos,查找完成之后,如果strstr没有找到则会返回NULL(这时候就是没有找到,我们返回npos即可),如果查找到了则会返回匹配的字符串中的第一个字符元素的地址,由于我们是要返回下标位置,那么采用_str是字符数组的首元素的地址,tmp - _str就可以得出下标位置
size_t find(const char* str, size_t pos = 0) const
{
	assert(pos < _size);

	char* tmp = strstr(_str + pos, str);

	if (tmp == NULL)
	{
		return npos;
	}
	else
	{
		return tmp - _str;
	}
}

substr

  1. 这个函数是用于生成子串并利用子串构造出一个string对象的
  2. 对于pos我们给出缺省参数,如果用户没有给出pos位置,那么我们默认从字符数组0位置处开始构造,对于len我们给出缺省参数,len是长度,我们默认认为字符串的长度不超过整形的最大值,而npos表示的又是整形的最大值,所以这里的缺省值我们给npos表示,如果用户不给出len,那么我们默认将当前string对象指向的字符数组的pos位置开始一直拷贝到当前string对象指向的字符数组的结束即可
  3. 由于用户给出的len长度不确定,我们先使用一个n作为长度,当用户没有给出len的长度,或len长度加pos位置的大于字符数组有效个数的时候(注意这里的判断一定要是让len == npos在前,如果要是pos + len > _size在前,那么如果len为npos的时候加上pos就会越界,又从0继续开始了,所以这里一定让len==npos在前),如果盲目的使用len就会出现越界,那么此时我们需要调整一下长度,让n为从pos位置开始到_size位置处(前闭区间后开区间)的字符个数即可,即_size - pos
  4. 创建一个string对象tmp,使用reserve让其空间为n
  5. 那么接下来空间足够,那么我们从pos位置处开始遍历n次字符数组,让tmp加等即可
  6. 不要忘记调整tmp对象的有效字符个数的值为n,并且返回tmp,这里一定要使用拷贝构造传值返回,因为tmp是临时对象,出了作用域生命周期结束就调用析构函数清理资源后就不存在了
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 = 0; i < n; i++)
	{
		tmp += _str[pos + i];
	}

	tmp._size = n;

	return tmp;
}
测试
  1. 这里我们巧妙的采用一个分离任意网址为对应的协议,域名,资源名的代码进行测试,既可以测试find查找字符串,又可以测试find查找字符,又可以测试substr生成子串对象
void string_test5()
{
	wzx::string s("https://cplusplus.com/reference/string/string/find/");

	size_t pos1 = s.find("://", 0);
	if (pos1 != wzx::string::npos)
	{
		wzx::string s1(s.substr(0, pos1));//由于下标从0开始,这里的pos1表示的就为前面字符的个数
		cout << s1.c_str() << endl;
	}

	size_t pos2 = s.find('/', pos1 + 3);
	if (pos2 != wzx::string::npos)
	{
		wzx::string s2(s.substr(pos1 + 3, pos2 - (pos1 + 3)));
		cout << s2.c_str() << endl;

		wzx::string s3(s.substr(pos2 + 1));
		cout << s3.c_str() << endl;
	}
}

测试结果如下,正确
在这里插入图片描述

operator<<

  1. 我们定义string对象s,根据我们的使用习惯通常是cout<<s的形式,当定义在类中的时候进行调用只能采用s<<cout的形式进行调用,会出现this指针(s对象)抢占第一个操作数,cout只能被迫成为第二个操作的情况并且不符合我们的操作习惯,为了符合我们的操作习惯cout<<s的形式并且让cout成为第一个操作数,s对象成为第二个操作数的形式,我们将operator<<定义在我们自己的命名空间中的模拟实现的string类外即可
  2. string进行遍历看的是有效数据个数_size,不看’\0’,c语言字符串进行变量看的是’\0’,使用’\0’作为字符串结束的标识
  3. 那么我们的流插入<<进行重载采用的策略就是遍历有效数据个数_size次string对象管理的字符数组进行逐个打印即可,这里采用范围for即可实现遍历并逐个打印
  4. 传参的时候用于接收cout对象的类型为ostream的out一定要采用引用的方式,由于涉及到内存缓冲区的一些问题,在c++11中不允许对ostream类型的对象进行拷贝,在拷贝构造函数后加= delete可以限制用户进行拷贝的操作,所以这里我们只能进行引用ostream类型的对象
    在这里插入图片描述
  5. 并且由于要支持连续打印string对象管理的字符数组,所以我们要返回ostream类型的对象,并且返回的时候,由于cout是全局对象,出了作用域还在,所以我们采用引用返回的方式返回ostream类型的对象out,注意这里由于防拷贝的限制不能进行值返回所以也只能采用传引用返回的方式
  6. 由于我们不修改string对象的成员变量的数据,所以我们
ostream& operator<<(ostream& out,const string& s)
{
	for (auto e : s)
	{
		out << e;
	}

	return out;
}

clear

  1. clear成员函数的作用是清除,这里没有必要将string对象指向的字符数组中的每一个字符都赋值为’\0’,因为可能本来就放置的是’\0’
  2. 我们知道string对象指向的字符数组的访问是看作的有效字符的个数_size,这里我们仅需要将_size修改为0,并且在字符数组的_size位置放置一个’\0’即可
void clear()
{
	_size = 0;
	_str[_size] = '\0';
}

operator>>

  1. 流提取的参数参数中的istream类型的in对象也要使用引用原因以及返回值传引用的具体原因同operator<<中的ostream,以及要定义在命名空间内的我们模拟实现的string类外的具体原因同operator<<中的ostream,这里小编就不过多赘述,这里由于是要向stirng类型的s中写入,所以s必须是string类型不能被const类型修饰
    在这里插入图片描述

  2. istream类型的in在进行提取并且将数据放置到string对象中的时候的时候,会默认将原string对象中的数据进行覆盖,这里要先调用clear函数对string对象的数据先进行清除,如果不进行清除会一直在string对象的字符数组后一直进行放置数据,这并不是我们想要的结果

  3. 定义一个char类型的变量ch,使用istream类型的对象in逐个放置数据(其实是调用的get成员函数进行放置)

  4. 我们知道istream类型的对象in是以空格或换行进行划分要提取的数据,那么就代表其并不会提取空格或换行的数据,那么我们就没有办法结束读取,那么我们想要连并空格或换行一并读取这时候就要使用istream类型的中的一个成员函数get,其可以将空格或换行一并进行提取
    在这里插入图片描述

  5. 在真正读取数据的时候,首先istream类型的in对象会清除缓冲区内的空格或者换行直到读取到除了空格或换行以外其它的数据,这里我们可以采用一个while循环,当读取的是空格或换行的时候我们对这个数据不做处理,并且继续进行读取直到读取到除了空格或换行以外其它的数据即可

  6. 我们知道初始的时候,string类型的对象s容量一般都为0,即不放置数据的情况,如果我们要放置的字符串较大,如果是采用+=逐个放置字符的方式,就必然要经历频繁的扩容,扩容根据c++的扩容机制就要异地扩容,其中的效率消耗还是挺大的,那么如何减小频繁扩容带来的消耗呢?

  7. 这里我们可以事先开一个有128个空间的字符数组,并且定义一个变量i用于控制字符数组,将istream类型的in对象放置到ch中的数据逐个进行读入,当i等于127的时候,此时128个空间中127个空间被放满的时候,我们在最后一个位置放置一个‘\0’用于表示字符串的结束,这时候我们使用s对象调用+=字符串一并写入,这时候会扩容的空间较大,减少频繁扩容的消耗,并且写入完成之后将i置为0,此时有可能缓冲区的数据还没有读取完全,继续进行读取,直到读取到空格或换行结束,此时退出循环,判断,当i恰好为0的时候,说明数据全部被读取+=完毕,当i不为0的时候,说明我们的字符数组buff中还有数据未进行+=到s中,将i位置处放置一个‘\0’用于表示字符串的结束,使用s对象调用+=进行写入即可

  8. 调用完之后返回in即可,返回传引用,具体原因同operator<<中的ostream

istream& operator>>(istream& in, string& s)
{
	s.clear();

	char ch;

	in.get(ch);

	while (ch == ' ' || ch == '\n')
	{
		in.get(ch);
	}

	char buff[128];
	int i = 0;
	while (ch != ' ' && ch != '\n')
	{
		//s += ch;

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

		in.get(ch);
	}

	if (i != 0)
	{
		buff[i] = '\0';
		s += buff;
	}

	return in;
}
测试
  1. 这里我们进行测试一下c语言和c++的string关于字符串结束的不同,并且先输入几个换行并且连续输入数据测试s1中的数据是否正确
void string_test6()
{
	wzx::string s("hello");

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

	s += '\0';
	s += "cpp";

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

	wzx::string s1;
	cin >> s1;
	cout << s1 << endl;
	cin >> s1;
	cout << s1 << endl;
}

测试结果如下,正确
在这里插入图片描述

resize

  1. 针对resize的策略,如果用户不传入指定字符的时候,默认采用’\0’进行初始化空间数据
  2. 当用户传入的n的大小,小于有效字符个数的时候,此时不需要进行扩容,由于string对象访问字符数组是看的有效字符的大小,那么_size有效字符的个数调整为n,并且在字符数组_size位置处放置一个’\0’即可
  3. 当用户传入的n的大小,大于有效字符的个数的时候,此时大概率需要扩容了,当n恰好大于_size,并且小于等于容量的时候不需要进行扩容,当n大于容量的时候是一定需要进行扩容,但是想一下,这两种情况本质还是一种情况,因为本质上都可能需要连续插入数据,所以我们统一处理,调用reserve开空间为n,当调用reserve传入的n小于等于容量的时候,其不会进行处理,所以这里我们大可放心使用reserve进行扩容
  4. 此时我们在size位置处开始进行放置字符ch,放置n次即可,并且在最后要更新_size的值,并且在_size位置处放置一个’\0’,注意,这里一定要有一个放置’\0’的操作,如果未进行放置,是那么用户没有传入字符的情况下ch为’\0’不会出问题,但是当用户显示传入字符ch之后,ch的值大概率就不是’\0’了,此时我们的字符数组的有效数据的最后一个位置的下一个位置处就缺失了’\0’,那么就没有办法更好的兼容c语言并且表示字符串的结束了,所以要在_size位置处放置一个’\0’
void resize(size_t n, char ch = '\0')
{
	if (n < _size)
	{
		_size = n;
		_str[_size] = '\0';
	}
	else
	{
		reserve(n);

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

		_size = n;
		_str[_size] = '\0';
	}
}
测试
  1. 这里我们依次测试调用resize给出的n小于有效字符的情况,以及等于容量的情况,以及大于容量的情况,并且使用+=一个字符’\0’之后看是否resize能否完成工作
  2. 最后测试一下判空函数和清除函数
void string_test7()
{
	wzx::string s("xxx");

	cout << s << endl;
	s.resize(2);
	cout << s << endl;
	s.resize(3, 'y');
	cout << s << endl;
	s.resize(12, '#');
	cout << s << endl;

	s += '\0';
	s += "h";
	cout << s << endl;
	s.resize(20, 'x');
	cout << s << endl;

	cout << s.empty() << endl;
	s.clear();
	cout << s.empty() << endl;
}

测试结果如下,正确
在这里插入图片描述

需要注意:

  1. 比较关系重载,小编这里只实现比较常用的string对象和string对象之间的比较
  2. 这些比较关系我们统一使用const进行修饰,因为不改变string类型对象中的成员变量的数据,使用const进行修饰,const对象和普通对象都可以进行调用
  3. 我们仅需要实现operator<和operator==即可,其余比较关系直接取反进行复用即可

operator<

  1. 这里小编调用一下c语言库里的比较函数memcmp,进行比较字符串的大小比较是从左向右将字符逐个按照ASCII码表的大小进行比较,对于返回值小于使用负数表示,等于使用0表示,大于使用正数表示
    在这里插入图片描述

  2. 并且进行比较的时候传入有效数据较小的个数为界限进行比较,因为当采用有效数据较大的个数去比较的时候,当有效数据较大的那个字符数据还有数据需要进行比较的时候,那么有效数据较小的个数的字符数组已经早就已经越界了,我们是绝对不能干越界访问的事情的
    在这里插入图片描述

  3. 使用一个int类型的值来接收memcpy的返回值,并且这里小编嵌套使用了三个三目运算符来作为返回值进行判断

  4. 因为传入的是有效数据较小的个数为界限进行比较,那么就有可能出现一个已经结束,另一个还没有结束,并且已经结束的那个的字符串的中逐个字符的ASCII表中对应的值都和还没有结束的那个字符串的中的前n个字符的ACSCII码表中对应的值相同,即出现了hello helloxxx或helloxxx hello这种情况

  5. 那么接下来我们进行判断返回,当ret等于0的时候,这时候就需要根据两个string对象中的有效字符的个数来进行判断大小了,有效字符个数大的那一个字符串的长度才长,当ret等于0的时候,判断是否_size<s._size是否成立,如果成立为真满足我们重载的operator<返回1为真否则返回0为假,如果ret不为0,那么同样判断ret<0是否成立,如果成立说明this指针指向的对象中指向的的字符数组小于类型为string的s对象中指向的的字符数组,符合我们重载的operator<,那么返回1为真否则返回0为假

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

	//hello helloxxx  1
	//hello hello     0
	//helloxx hello   0

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

operator ==

  1. 当两个string对象中的指针指向的字符数组的长度相同并且调用memcmp比较的返回值为0的时候才相等,这里直接进行判断返回即可
bool operator==(const string& s) const
{
	return _size == s._size &&
		memcmp(_str, s._str, _size < s._size ? _size : s._size) == 0;
}

operator<=

  1. 这里复用operator<和operator==即可
bool operator<=(const string& s) const
{
	return *this < s || *this == s;
}

operator>

  1. 这里先使用operator<=判断*this和s的大小取反,返回即可
bool operator>(const string& s) const
{
	return !(*this <= s);
}

operator>=

  1. 这里先使用operato<判断*this和s的大小取反,返回即可
bool operator>= (const string & s) const
{
	return !(*this < s);
}

operator!=

  1. 这里先使用operato==判断*this和s的大小取反,返回即可
bool operator!=(const string& s) const
{
	return !(*this == s);
}
测试
  1. 比较关系直接看比较结果进行验证即可
void string_test8()
{
	wzx::string s1("hello");
	wzx::string s2("helloxxx");

	cout << (s1 < s2) << endl;
	cout << (s1 == s2) << endl;
	cout << (s1 <= s2) << endl;
	cout << (s1 > s2) << endl;
	cout << (s1 >= s2) << endl;
	cout << (s1 != s2) << endl << endl;

	wzx::string s3("hello");
	wzx::string s4("hello");

	cout << (s3 < s4) << endl;
	cout << (s3 == s4) << endl;
	cout << (s3 <= s4) << endl;
	cout << (s3 > s4) << endl;
	cout << (s3 >= s4) << endl;
	cout << (s3 != s4) << endl << endl;

	wzx::string s5("helloxxx");
	wzx::string s6("hello");

	cout << (s5 < s6) << endl;
	cout << (s5 == s6) << endl;
	cout << (s5 <= s6) << endl;
	cout << (s5 > s6) << endl;
	cout << (s5 >= s6) << endl;
	cout << (s5 != s6) << endl << endl;
}

测试结果如下,正确
在这里插入图片描述

operator= 传统写法

  1. 赋值的特点是两个已经存在的对象之间的赋值并且可以支持连续赋值,所以这里我们的返回值为thie指针指向的string类型的对象赋值后的结果,并且自己给自己赋值没有意义,所以这里我们使用this指针和传入的s对象的地址进行比较一下判断即可
  2. 这里并不适用reserve去开空间,reserve开空间是将原空间的数据拷贝,释放原空间,开新空间,将原空间数据再拷贝到新空间上,但是这里的_str需要是并不是自己的原空间上的数据,并且通常进行要被赋值的对象所管理的字符数组的有效字符的个数通常为0,这里调用reserve没有必要
  3. 所以我们创建一个临时的字符指针tmp,使用new[]开的个数在要赋值的对象的容量空间的基础上加1,给’\0’预留空间,并且让tmp指向这段使用new[]开好的空间
  4. 接下来使用memcpy将要赋值的对象中的指针指向的字符串的值拷贝到临时指针tmp指针的空间空,同样的传入的空间大小要在有效字符的大小的基础上加1,即连同’\0’一并进行拷贝
  5. 接下来使用delete[]释放要被赋值的空间_str,释放完之后让_str指向tmp指向的空间即可
  6. 接下来直接赋值有效字符的个数和容量即可
  7. 返回this指针的解引用即为被赋值的string对象
string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[s._capacity + 1];
		memcpy(tmp, s._str, s._size + 1);
		delete[] _str;
		_str = tmp;

		_size = s._size;
		_capacity = s._capacity;
	}
	
	return *this;
}

operator= 过渡写法

  1. 这个版本是过渡写法,使用了一个临时对象打工的思路
  2. 创建临时对象tmp调用拷贝构造拷贝去开空间,复制的对象s
  3. 那么此时临时对象tmp中的就为我们想要的
  4. 此时将交换_str指针和临时对象中指针,交换_size和临时对象中的有效字符个数,交换_capacity和临时对象中容量大小,那么我们this指针指向的对象此时就得到了它想要得到的值,临时对象tmp得到了原要被赋值的对象中的值
  5. 返回this指针的解引用即为被赋值的string对象
  6. 此时临时对象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

  1. 这里的需要进行交换的参数中的string类型的对象s要采用传引用的方式,对象s就为要进行交换的对象的别名
  2. 此时调用库中的交换函数去进行内置类型的交换,交换_str指针和对象s中指针,交换_size和对象s中的有效字符个数,交换_capacity和对象s中容量大小就是实现了一个交换函数
void swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

operator= 现代写法

  1. 在接收赋值对象的时候就采用值拷贝,即调用拷贝构造构造出一个string类型的s对象,此时s对象的值和赋值对象的值完全相同,我们的this指针指向的对象想要的值就为这个s对象,调用交换函数直接进行交换即可拿到想要的值,此时对象s拿到了原要被赋值的对象中的值
  2. 返回this指针的解引用即为被赋值的string对象
  3. 此时对象s中就为原被赋值对象的值,此时临时对象tmp的生命周期结束,自动调用析构函数完成资源清理的工作
string& operator=(string s)
{
	swap(s);

	return *this;
}
测试
  1. 这里直接构建两个对象进行赋值即可
  2. 注意赋值的前提必须是两个已经存在的对象之间进行的赋值
void string_test9()
{
	wzx::string s1("hello wzx");
	wzx::string s2;

	s2 = s1;
	cout << s1 << endl;
	cout << s2 << endl;
}

测试结果如下,无误
在这里插入图片描述

erase

  1. erase成员函数实现的是对pos位置处开始向后len个字符的删除
  2. 对于pos我们给出缺省参数,pos如果用户没有显示传值,那么我们默认从对象中指针指向的字符数组的起始位置即数组下标位置为0的位置开始进行删除,对于len我们给出缺省参数,len是要进行删除的长度,我们默认认为字符串的长度不超过整形的最大值,而npos表示的又是整形的最大值,所以这里的缺省值我们给npos表示,如果用户不给出len,那么我们默认将当前string对象指向的字符数组的pos位置处开始一直删除到当前string对象指向的字符数组的结束
  3. 由于用户给出的len长度不确定,我们先使用一个n作为长度,当用户没有给出len的长度,或len长度加pos位置的大于字符数组有效个数的时候(注意这里的判断一定要是让len == npos在前,如果要是pos + len > _size在前,那么如果len为npos的时候加上pos就会越界,又从0继续开始了,所以这里一定让len==npos在前),如果盲目的使用len就会出现越界,那么此时我们需要调整一下长度,让n为从pos位置开始到_size位置处(前闭区间后开区间)的字符个数即可,即_size - pos
  4. 当用户没有给出len的长度,或len长度加pos位置的大于字符数组有效个数的时候,我们需要从当前string对象指向的字符数组的pos位置处开始一直删除到当前string对象指向的字符数组的结束,并且我们知道string对象指向的字符数组的访问是看作的有效字符的个数_size,这里我们仅需要将_size修改为n,并且在字符数组的_size位置放置一个’\0’即可
  5. 当用户给出的len长度加pos位置的小于字符数组有效个数的时候,为了保证除了要进行删除的数据以外的其它数据不丢失,这里我们需要进行挪动数据
  6. 挪动数据需要从(变量i = pos+n)pos+n 位置的字符开始挪动(赋值)到pos位置,并且逐个挪动直至变量i到_size位置处即可,这样连并将_size位置的’\0’一并挪动了,这时候不需要我们人为添加’\0’了
  7. 更新_size有效数据的个数即可
  8. 返回删除完成之后的值即为this指针的解引用,即进行删除的string的对象删除后的值,由于进行删除的string的对象不属于局部对象,出了erase函数还在生命周期未结束,所以我们可以采用传引用返回的方式进行返回
string& erase(size_t pos = 0, size_t len = npos)
{
	assert(pos < _size);

	size_t n = len;
	if (len == npos || pos + len > _size)
	{
		n = _size - pos;
	}

	if (len == npos || pos + len > _size)
	{
		_size = pos;
		_str[_size] = '\0';
	}
	else
	{
		for (size_t i = pos + n; i <= _size; i++)
		{
			_str[i - n] = _str[i];
		}

		_size -= n;
	}

	return *this;
}
测试
  1. 我们在s1对象中的指针指向的字符数组的第五个位置处删除一个字符,接下来采用调用erase不传值的方式删除s1对象中的指针指向的字符数组中的全部字符
void string_test10()
{
	wzx::string s1("hello wzx");

	s1.erase(5, 1);
	cout << s1 << endl;

	s1.erase();
	cout << s1 << endl;
}

测试结果如下,正确
在这里插入图片描述

string.h 源代码

#pragma once

#include <string>
#include <assert.h>

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

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

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

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

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

		//		_size = s._size;
		//		_capacity = s._capacity;
		//	}
		//	
		//	return *this;
		//}

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

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

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

			return *this;
		}

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

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

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

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

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

		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 resize(size_t n, char ch = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				reserve(n);

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

				_size = n;
				_str[_size] = '\0';
			}
		}

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

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			_str[_size++] = ch;
			_str[_size] = '\0';
		}

		void append(const char* str)
		{
			size_t len = strlen(str);

			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			for (size_t i = 0; i < len; i++)
			{
				_str[_size + i] = str[i];
			}
			
			_size += len;
			_str[_size] = '\0';
		}

		string& operator+=(char ch)
		{
			push_back(ch);

			return *this;
		}

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

			return *this;
		}

		string& insert(size_t pos,size_t n, char ch)
		{
			assert(pos <= _size);

			if (n + _size > _capacity)
			{
				reserve(n + _size);
			}

			//由于是size_t类型,当pos位置为0的时候,无符号类型无法减到为0,那么
			//就会一直大于,一直循环,当为无符号类型减到-1的时候,-1在内存中的补
			//码全为1,那么转换为无符号类型就为整形的最大值,默认我们认为字符串并
			//不会大于整形最大值,所以当pos位置为0的时候要特殊判断
			for (size_t i = _size; i >= pos && i != npos; i--)
			{
				_str[i + n] = _str[i];
			}

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

			_size += n;

			return *this;
		}

		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);

			size_t len = strlen(str);

			if (len + _size > _capacity)
			{
				reserve(len + _size);
			}

			for (size_t i = _size; i >= pos && i != npos; i--)
			{
				_str[i + len] = _str[i];
			}

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

			_size += len;

			return *this;
		}

		string& erase(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);

			size_t n = len;
			if (len == npos || pos + len > _size)
			{
				n = _size - pos;
			}

			if (len == npos || pos + len > _size)
			{
				_size = pos;
				_str[_size] = '\0';
			}
			else
			{
				for (size_t i = pos + n; i <= _size; i++)
				{
					_str[i - n] = _str[i];
				}

				_size -= n;
			}

			return *this;
		}

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

		size_t find(char ch, size_t pos = 0) const
		{
			assert(pos < _size);

			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}

			return npos;
		}

		size_t find(const char* str, size_t pos = 0) const
		{
			assert(pos < _size);

			char* tmp = strstr(_str + pos, str);

			if (tmp == NULL)
			{
				return npos;
			}
			else
			{
				return tmp - _str;
			}
		}

		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 = 0; i < n; i++)
			{
				tmp += _str[pos + i];
			}

			tmp._size = n;

			return tmp;
		}

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

		//	//hello helloxxx  1
		//	//hello hello     0
		//	//helloxx hello   0

		//	//if (_size < s._size)
		//	//{
		//	//	return true;
		//	//}
		//	//else
		//	//{
		//	//	return false;
		//	//}

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

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

			//hello helloxxx  1
			//hello hello     0
			//helloxx hello   0

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

		bool operator==(const string& s) const
		{
			return _size == s._size &&
				memcmp(_str, s._str, _size < s._size ? _size : s._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);
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	public:
		static size_t npos;
	};
	size_t string::npos = -1;


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

		return out;
	}

	istream& operator>>(istream& in, string& s)
	{
		s.clear();

		char ch;

		in.get(ch);

		while (ch == ' ' || ch == '\n')
		{
			in.get(ch);
		}

		char buff[128];
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			//s += ch;

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

			in.get(ch);
		}

		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}
}

test.cpp 源代码

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>

using namespace std;

#include "string.h"

void string_test1()
{
	wzx::string s1("hello cpp");
	cout << s1.c_str() << endl;

	wzx::string s2(s1);
	cout << s2.c_str() << endl;
}

void string_test2()
{
	wzx::string s1("hello cpp");
	cout << s1.c_str() << endl;

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

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

	const wzx::string s2("hello cpp");

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

	wzx::string::const_iterator cit = s2.begin();
	while (cit != s2.end())
	{
		cout << *cit;

		cit++;
	}
	cout << endl;
}

void test()
{
	wzx::string s1("hello cpp");
	cout << s1.c_str() << endl;

	for (auto& e : s1)
	{
		e++;
	}
	cout << s1.c_str() << endl;
}

void test1()
{
	wzx::string s1("hello cpp");

	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1.empty() << endl << endl;

	const wzx::string s2;

	cout << s2.size() << endl;
	cout << s2.capacity() << endl;
	cout << s2.empty() << endl;
}

void string_test3()
{
	wzx::string s("hello cpp");

	s.push_back('#');
	cout << s.c_str() << endl;
	s.append("hello wzx");
	cout << s.c_str() << endl;

	s += '#';
	cout << s.c_str() << endl;
	s += "hello wzx";
	cout << s.c_str() << endl;
}

void string_test4()
{
	wzx::string s("hello cpp");

	s.insert(5,5,'x');
	cout << s.c_str() << endl;
	s.insert(0, "######");
	cout << s.c_str() << endl;
}

void string_test5()
{
	wzx::string s("https://cplusplus.com/reference/string/string/find/");

	size_t pos1 = s.find("://", 0);
	if (pos1 != wzx::string::npos)
	{
		wzx::string s1(s.substr(0, pos1));//由于下标从0开始,这里的pos1表示的就为前面字符的个数
		cout << s1.c_str() << endl;
	}

	size_t pos2 = s.find('/', pos1 + 3);
	if (pos2 != wzx::string::npos)
	{
		wzx::string s2(s.substr(pos1 + 3, pos2 - (pos1 + 3)));
		cout << s2.c_str() << endl;

		wzx::string s3(s.substr(pos2 + 1));
		cout << s3.c_str() << endl;
	}
}

void string_test6()
{
	wzx::string s("hello");

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

	s += '\0';
	s += "cpp";

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

	wzx::string s1;
	cin >> s1;
	cout << s1 << endl;
	cin >> s1;
	cout << s1 << endl;
}

void string_test7()
{
	wzx::string s("xxx");

	cout << s << endl;
	s.resize(2);
	cout << s << endl;
	s.resize(3, 'y');
	cout << s << endl;
	s.resize(12, '#');
	cout << s << endl;

	s += '\0';
	s += "h";
	cout << s << endl;
	s.resize(20, 'x');
	cout << s << endl;

	cout << s.empty() << endl;
	s.clear();
	cout << s.empty() << endl;
}

void string_test8()
{
	wzx::string s1("hello");
	wzx::string s2("helloxxx");

	cout << (s1 < s2) << endl;
	cout << (s1 == s2) << endl;
	cout << (s1 <= s2) << endl;
	cout << (s1 > s2) << endl;
	cout << (s1 >= s2) << endl;
	cout << (s1 != s2) << endl << endl;

	wzx::string s3("hello");
	wzx::string s4("hello");

	cout << (s3 < s4) << endl;
	cout << (s3 == s4) << endl;
	cout << (s3 <= s4) << endl;
	cout << (s3 > s4) << endl;
	cout << (s3 >= s4) << endl;
	cout << (s3 != s4) << endl << endl;

	wzx::string s5("helloxxx");
	wzx::string s6("hello");

	cout << (s5 < s6) << endl;
	cout << (s5 == s6) << endl;
	cout << (s5 <= s6) << endl;
	cout << (s5 > s6) << endl;
	cout << (s5 >= s6) << endl;
	cout << (s5 != s6) << endl << endl;
}

void string_test9()
{
	wzx::string s1("hello wzx");
	wzx::string s2;

	s2 = s1;
	cout << s1 << endl;
	cout << s2 << endl;
}

void string_test10()
{
	wzx::string s1("hello wzx");

	s1.erase(5, 1);
	cout << s1 << endl;

	s1.erase();
	cout << s1 << endl;
}

int main()
{
	string_test10();

	return 0;
}


总结

以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!

Logo

「智能机器人开发者大赛」官方平台,致力于为开发者和参赛选手提供赛事技术指导、行业标准解读及团队实战案例解析;聚焦智能机器人开发全栈技术闭环,助力开发者攻克技术瓶颈,促进软硬件集成、场景应用及商业化落地的深度研讨。 加入智能机器人开发者社区iRobot Developer,与全球极客并肩突破技术边界,定义机器人开发的未来范式!

更多推荐