string(下)

前言

这一篇博客,我们主要讲一下string的模拟实现和一些练习题

1.基本框架

string.h

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
#define _CRT_SECURE_NO_WARNINGS 1

namespace bit
{
	class string
	{
	public:

	private:
		char* _str;
		size_t _size;
		size_t capacity;
	};
}

string.cpp

#include"string.h"

namespace bit
{

}

test.cpp

#include"string.h"
void test1()
{

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

相同的命名空间会合并

2.构造函数

2.1有参构造函数

		string(char* arr);
		char* c_str();
	string::string(char* arr)
		:_size(strlen(arr))
	{
		_str = new char[_size + 1];//因为size不包含\0
		strcpy(_str,arr);//iostream中有这些字符串函数,所以不用再包含了
		_capacity = _size;
	}
	char* string::c_str()
	{
		return _str;
	}
	string s1("123456");
	cout << s1.c_str() << endl;//对于字符串的地址,会直接打印字符串,而不是地址

在这里插入图片描述

2.2 无参构造函数

string();
	string::string()
	{
		_str = new char[1] {'\0'};
		strcpy(_str, arr);
		_capacity = _size=0;
	}

有参与无参合并

string(const char* arr="");
	string::string(const char* arr)//定义不能传缺省值
		:_size(strlen(arr))
	{
		_str = new char[_size + 1];
		strcpy(_str, arr);
		_capacity = _size;
	}

2.3 拷贝构造函数

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

3. 析构函数

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

4.赋值运算符重载

	string& string::operator=(string& s)
	{
		//std::swap(_str, s._str);
		//std::swap(_size, s._size);
		//std::swap(_capacity, s._capacity);//因为我们后面还会自己实现自己的swap,所以区分清楚//不是这样实现的
		_size = s._size;
		_capacity = s._capacity;
		_str = new char[_size + 1];
		strcpy(_str, s._str);
		return *this;
	}

5. 迭代器

在这里插入图片描述
因为范围for的底层是迭代器,所以没有begin,end的话,范围for是无法运行的

		typedef char* iterator;//因为迭代器类似于指针,所以在string中,可以定义为指针
		typedef const char* const_iterator;//因为迭代器类似于指针,所以在string中,可以定义为指针
	string::iterator string::begin()
	{
		return _str;
	}
	string::iterator string::end()
	{
		return _str + _size;
	}

这样就可以使用范围for了,因为这就是迭代器
然后我们测试的时候,为了防止与库里的string冲突,我们测试也在命名空间bit中

	string::const_iterator string::begin()const
	{
		return _str;
	}
	string::const_iterator string::end()const//说明this指针指向的内容不可修改
	{
		return _str + _size;
	}

6. size

	size_t string::size()
	{
		return _size;
	}

7. capacity

	size_t string::capacity()
	{
		return _capacity;
	}

8. resize

	void string::resize(size_t n)
	{
			_size = n;
			if (_size > _capacity)
			{
				//扩容
				char* tmp = new char[_size + 1];
				strcpy(tmp, _str);
				delete _str;
				_str = tmp;
				_capacity = _size;
			}
	}

9. reserve

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

10. operator[]

	char& string::operator[](size_t n)
	{
		assert(n <= _size);
		return _str[n];
	}

11. clear

	void string::clear()//clear的话,字符串变成空串,然后capacity不变,size变为0
	{
		_str[0] = '\0';
		_size = 0;
	}

12. operator+=

	string& string::operator+=(const string& s)//因为隐式类型转换是const类型的,所以要加const
	{
		if (s._size + _size > _capacity)
		{
			//扩容
			char* tmp = new char[s._size + _size+1];
			strcpy(tmp, _str);
			delete _str;
			_str = tmp;
			_capacity = s._size + _size;
		}
		strcpy(_str + _size, s._str);
		return *this;
	}

	string& string::operator+=(char c)
	{
		if (_size == _capacity)
		{
			//扩容
			char* tmp = new char[_capacity*2+1];
			strcpy(tmp, _str);
			delete _str;
			_str = tmp;
			_capacity *= 2;
		}
		_str[_size] = c;
		_size++;
		_str[_size] = '\0';
		return *this;
	}

13. append和push_back

	void string::append(const string& str)
	{
		(*this) += str;
	}
	void string::push_back(char c)
	{
		(*this) += c;
	}

14. insert

insert字符
在这里插入图片描述
这个只需要定义一个end,然后将end的数据往后移,end–就ok了

		int end = _size - 1;
		while (end >= pos)
		{

		}

如果这样定义end的话,如果pos为0,那么就完了,因为pos为size_t,所以int与size_t比较时,int会转换为size_t,那么end就永远>=pos了,循环不会停止,解决办法有两个,其中一个就是将pos强转为int,方法二就是,end一开始就指向_size
法一

		if (_size == _capacity)
		{
			//扩容
			char* tmp = new char[_capacity * 2 + 1];
			strcpy(tmp, _str);
			delete _str;
			_str = tmp;
			_capacity *= 2;
		}
		//开始插入
		//int pos = _size - 1;
		//while (pos >= _size)
		//{

		//}//如果这样定义end的话,如果pos为0,那么就完了,因为pos为size_t,所以int与size_t比较时,i
		//nt会转换为size_t,那么end就永远>=pos了,循环不会停止,
		//解决办法有两个,其中一个就是将pos强转为int,方法二就是,end一开始就指向_size
		//法1
		int end = _size-1;
		_str[_size + 1] = '\0';//别忘了\0,因为覆盖了
		while (end >= (int)pos)
		{
			_str[end + 1] = _str[end];
			end--;
		}
		_str[pos] = c;

法二

		int end = _size;
		_str[end + 1] = '\0';
		while (end > pos)//这些条件都要自己画图来得到
		{
			_str[end] = _str[end - 1];
			end--;
		}
		_str[pos] = c;

insert串
法一

	void string::insert(size_t pos, const string& s)
	{
		assert(pos <= _size);
		if (s._size + _size > _capacity)
		{
			char* tmp = new char[s._size + _size + 1];
			strcpy(tmp, _str);
			delete _str;
			_str = tmp;
			_capacity = s._size + _size;
		}
		//开始插入
		//这次还是两种方法,这次我们把\0也计入移动数据中
		//还是要自己画图,不然真的不好整
		int len = s._size;
		int end = _size;
		while (end >= (int)pos)
		{
			_str[end + len] = _str[end];
			end--;
		}
		for (int i = 0; i < len; i++)
		{
			_str[i + pos] = s._str[i];
		}
	}
		int len = s._size;
		int end = _size + len;
		while (end >= pos + len)
		{
			_str[end] = _str[end - len];
			end--;
		}
		for (int i = 0; i < len; i++)
		{
			_str[i + pos] = s._str[i];
		}

15. npos

在这里插入图片描述
在这里插入图片描述
npos是size_t整型的最大值,因为为-1嘛
然后静态成员变量的声明与定义也要分离
在一个文件中声明与定义要分离,声明在类内,定义在类外
在不同文件中声明与定义也要分离,但是定义不能在.h中,不然都重定义了,定义要在string.cpp中才行
在这里插入图片描述
在这里插入图片描述

然后就是只有静态的const类型的int类型的才可以初始化,一般的static没有缺省值,有了初始化,就不能在定义了

16. erase

	void string::erase(size_t pos, size_t len)
	{
		assert(pos <= _size);
		//if (len + pos >= _size)//这里的加法可能存在问题,因为size_t的最大值加1,就变为0了
		if (len==npos||len + pos >= _size)
		{
			//直接将pos后面的全部删掉
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			//直接将后面不用删的拷贝到前面去
			memcpy(_str + pos, _str + pos + len, _size - pos - len+1);//因为\0也要考过去
		}
	}

17. replace

	void string::replace(size_t pos, size_t len, const string& s)
	{
		assert(pos <= _size);
		//先删
		erase(pos, len);
		//再插入
		insert(pos, s);
	}

18. swap

	void string::swap(string& s)
	{
		//这个是我们自己定义的swap
		std::swap(_str, s._str);//这个是库里面的
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	}
	void swap(string& s1, string& s2) //这个函数也是要声明的哦
	{
		s1.swap(s2);
	}

要交换的话,就不要能用const修饰了,不然会出错

19. find

	size_t string::find(size_t pos, char c)
	{
		assert(pos < _size);
		for (int i = pos; i < _size; i++)
		{
			if (_str[i] == c)
			{
				return i;
			}
		}
		return -1;
	}

	size_t string::find(size_t pos, string s)
	{
		assert(pos < _size);
		char* ret = strstr(_str + pos, s._str);
		if (ret != nullptr)
		{
			return ret - _str;
		}
		else
		{
			return -1;
		}
	}

20. relational operators

在这里插入图片描述

21. substr

	string string::substr(size_t pos, size_t len) const
		//获取一个子串,并返回这个子串
	{
		string s;
		if (len == npos || pos + len >= _size)
		{
			s._str = new char[_size - pos + 1];
			strcpy(s._str, _str + pos);
			s._capacity = _size - pos;
			s._size = _size - pos;
		}
		else
		{
			s._str = new char[len + 1];
			memcpy(s._str,_str+pos,len);
			s._str[len] = '\0';
			s._capacity = len;
			s._size = len;
		}
		return s;
	}

22. 流插入和流提取

	ostream& operator<<(ostream& out, string& s)
		//流提取和流输入都定义在类外面
	{
		out << s.c_str();
		return out;
	}

流提取就比较难了
主要是这个你也无法知道要输入的数据有多大,所以无法定义一个数组,只能一个字符一个字符的读取,在插入

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

这样子不行,因为cin无法提取空格和\n

		char c = in.get();
		while (c != ' ' && c != '\n')
		{
			s += c;
			c = in.get();
		}
		return in;

cin.get()这个函数的话,就可以读取空格和换行符

		s.clear();//先清空
		char c = in.get();//cin.get()这个函数的话,就可以读取空格和换行符
		char arr[128] = { 0 };
		int i = 0;
		while (c != ' ' && c != '\n')
		{
			arr[i] = c;
			i++;
			if (i == 127)//最后一个\0
			{
				s += arr;
				i = 0;
			}
			c = in.get();
		}
		arr[i] = '\0';
		s += arr;
		return in;

这个是最终版本
要注意,有const和没有const是两个不同的类型,有引用和指针时,不改变所指向的的值时,最好加上const

23. 浅拷贝的问题

浅拷贝有两个问题,第一个问题就是同一块空间会被析构两次,第二个问题就是一个改变会引起另一个改变
所以我们引入引用计数的概念,但我们现在不实现,只是讲讲
所谓引用计数,就是对那一块我们开辟的空间计数,看有多少个变量指向它,释放一个对象,那么计数就少1,最后变为0,才是真正的释放
然后就是,如果要修改里面的值,那么这个对象就自己去拷贝,去深拷贝,然后自己的计数为1,原来的计数少1
所以说,这个引用计数,如果不修改值,那么很好,如果要修改值,那么也就一般般了

总结

下一篇我们讲vector

  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值