C++之 String 类的模拟实现

本文只简述string类模拟实现的重点,其余不再过多赘述

一、模拟实现string类的构造函数

本文主要实现下图两个构造函数,即string()和string(const string& str)

而关于string的底层,其实就是数组,在物理逻辑上是连续的空间:

cpp 复制代码
//string.h文件
namespace mxj
{
	class string
	{
	public:
		//不带参的构造函数,编译器默认生成的已经满足使用需求
		//无参的构造,就是字符串'\0',所以在string的模拟实现里,带参的构造函数包含了无参构造函数
		//string();
		
		//带参构造函数
		string(const char* str = "");//给缺省值"",
        //这样我们的mxj::string s1就不会报错,不然得写成mxj::string s1()

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

带参构造函数如下,以strlen为基础,计算开辟新空间的大小,通过开辟新的内存空间并将str字符串长度的下一个位置赋值'\0'

这样也很巧妙的解决了无参构造函数。

cpp 复制代码
//string.cpp文件
namespace mxj
{
		string::string(const char* str)
		:_str(new char[strlen(str) + 1])//这里为什么要加1?因为strlen不会计算\0
		, _size(strlen(str))
		, _capacity(strlen(str))
	{
		strcpy(_str, str);
	}
}

二、模拟实现string类的拷贝构造和赋值拷贝

拷贝构造和赋值拷贝可以通过交换函数来实现,拷贝构造和赋值拷贝都是深拷贝!!!

cpp 复制代码
/ s2(s1),拷贝构造
	string::string(const string& s)
	{
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}
	// s3=s2=s1=s,赋值拷贝,
	string& string::operator=(const string& s)
	{
		if (this != &s)
		{
			//先把原来的空间释放了,如果s1=s,s1原本的空间非常大,s的空间非常小,就非常容易造成空间浪费
			delete[] _str;
			//为什么要+1,因为还有\0,_size和_capacity都是不计算\0的
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

		return *this;
	}

当然,上面有关拷贝构造和赋值拷贝虽然直观,但是很啰嗦 ,通过swap函数我们也能实现

两者均不改变当前对象的资源,都是通过临时对象进行资源的交换

cpp 复制代码
//string.cpp
namespace mxj
{
    string::string(const string& s)
		{
			string tmp(s._str);//将s中的资源构造对象tmp
			swap(tmp);
		}

		string& string::operator=(string s)
		{
			swap(s);//临时对象s和*this进行交换资源
			return *this;
		}

		void swap(string& s1, string& s2)
		{
			s1.swap(s2);//通过库函数中的swap来实现
		}

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

三、模拟实现string类的析构函数

析构函数需要清理类对象的资源

cpp 复制代码
string.cpp
string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = 0;
		_capacity = 0;
	}

四、赋值运算符重载[ ]

赋值运算符重载[ ],使得string类有通过下标来访问字符串数据的功能

cpp 复制代码
char& operator[](size_t i)
		{
			return _str[i];
		}

五、获取字符串数据

string类底层是数组,接口直接返回字符串即可

cpp 复制代码
const char* c_str() const
		{
			return _str;
		}

六、获取当前对象元素个数

cpp 复制代码
size_t size() const
		{
			return _size;
		}

七、清理当前string类对象的数据置空

字符串的终止符号为'\0',所以将字符串第一个元素置为字符'\0',字符串有效个数置零即可

cpp 复制代码
void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

八、普通迭代器

迭代器是指针或者是像指针一样的东西,但在string类中,迭代器的底层是通过指针实现的

cpp 复制代码
using iterator = char*;//using类似于typedef的作用
iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

九、const修饰的迭代器

cpp 复制代码
using const_iterator = const char*;
const_iterator begin()const
{
	return _str;
}

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

十、string类一些常用接口的实现

cpp 复制代码
    void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];//保留n个位置实际上要保留n+1个,要给'\0'留位置
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;//_capacity是不计算'\0'的
		}
	}


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

	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (len + _size > _capacity)
		{
			size_t newcapacity = 2 * _capacity;
			//如果扩了两倍还是不满足,那就按串的大小来扩
			if (len + _size >newcapacity)
			{
				newcapacity = len + _size;
			}
			reserve(newcapacity);
		}
		strcpy(_str+_size, str);
		_size += len;
	}

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

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

	void string::insert(size_t pos, char ch)
	{
		//判断pos位置是否合法
		assert(pos <= _size);
		//判断是否需要扩容
		if (_size == _capacity) {
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		//pos+1位置往后挪
		size_t end = _size;
		while (end > pos) {
			_str[end] = _str[end-1];
			--end;
		}
		_str[pos] = ch;
		_size++;
	}

	void string::insert(size_t pos, const char* str)
	{
		size_t len = strlen(str);
		//判断是否需要扩容
		if (_size + len > _capacity) {
			size_t newCapacity = 2 * _capacity;
			if (newCapacity < _size + len)
			{
				newCapacity = _size + len;
			}
			reserve(newCapacity);
		}

		//数据往后挪:str的长度为len,pos位置每一个字符都需要向后挪len个,
		size_t end = _size+len;
		while (end > pos + len-1) {
			_str[end] = _str[end-len];
			--end;
		}

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

		//长度更新
		_size += len;
	}

	//删除
	void string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);
		if (len > _size - pos) {
			_str[pos] = '\0';
			_size = pos;
		}

		//挪数据
		else {
			size_t end = pos + len;
			while (end > pos) {
				_str[end-len] = _str[end];
				++end;
			}
			_size -= len;
		}
	}

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

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

	string string::substr(size_t pos, size_t len)
	{
		assert(pos < _size);
		if (len > (_size - pos)) {
			len = _size - pos;
		}
		mxj::string sub;
		sub.reserve(len);
		for (size_t i = pos; i < len; i++)
		{
			sub += _str[pos + i];//尾插
		}
		return sub;
	}

	//通过复用
	bool operator== (const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) == 0;
	}
	bool operator!= (const string& lhs, const string& rhs)
	{
		return !(lhs == rhs);
	}
	bool operator> (const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) > 0;
	}
	bool operator< (const string& lhs, const string& rhs)
	{
		return !(lhs >=rhs);
	}
	bool operator>= (const string& lhs, const string& rhs)
	{
		return (lhs > rhs)||(lhs==rhs);
	}
	bool operator<= (const string& lhs, const string& rhs)
	{
		return !(lhs > rhs);
	}

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

	istream& operator>>(istream& is, string& str)
	{
		str.clear();
		char ch;
		//is >> ch;
		ch = is.get();
		while (ch != ' ' && ch != '\n')
			//如果想实现getline的效果while( ch != '\n')
		{
			str += ch;
			ch = is.get();
		}

		return is;
	}
相关推荐
两个人的幸福online31 分钟前
记录一次 用php 调用ai用stream返回
开发语言·php
漂流瓶6666661 小时前
Scala的模式匹配变量类型
开发语言·后端·scala
夏天吃哈密瓜1 小时前
Scala中的正则表达式01
大数据·开发语言·后端·正则表达式·scala
2401_833788051 小时前
Scala的模式匹配(2)
java·开发语言
Lbs_gemini06031 小时前
C++研发笔记14——C语言程序设计初阶学习笔记12
c语言·开发语言·c++·笔记·学习
ac-er88881 小时前
GD库如何根据颜色生成纯色背景图
开发语言·php
悠悠龙龙3 小时前
框架模块说明 #05 权限管理_03
java·开发语言·spring
阿华的代码王国3 小时前
【算法】——前缀和(矩阵区域和详解,文末附)
java·开发语言·算法·前缀和
我的老子姓彭4 小时前
C++学习笔记
c++·笔记·学习
黑金IT4 小时前
使用Python和OpenCV自动检测并去除图像中的字幕
开发语言·python·opencv