【C++】string容器模拟实现

1.浅拷贝问题解决

1.1浅拷贝存在的问题

在这里插入图片描述
对于浅拷贝,主要存在于拷贝构造和赋值运算符重载的过程中,下面给出一段代码结合分析

#pragma warning(disable:4996)
#include<iostream>

using namespace std;

class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
		{
			str = " ";
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}

	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}

private:
	char* _str;
};

void StringTest()
{
	String s1("Hello");
	String s2(s1);
}

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

执行时程序直接崩溃

在这里插入图片描述
下面分析一下原因:
上述的崩溃时由于浅拷贝导致的多次释放问题
在这里插入图片描述
再来看下面的代码快:
在这里插入图片描述
分析:
本次报错是因为执行s2 = s1时,导致内存泄漏以及多次释放的问题,具体看下面的分析
在这里插入图片描述
总结一下:
浅拷贝会导致①内存泄露②多次释放同一块空间
这些错误都是极其严重的,我们务必要避免!

接下来就来探讨一下如何解决这些问题:

1.2通过深拷贝的方式解决浅拷贝问题

本质:让每一个对象都拥有一份独立的资源

传统版解决方式

  1. 解决拷贝构造
    在这里插入图片描述
  2. 解决赋值运算符重载
    在这里插入图片描述
    下面给出解决的完整代码
#pragma warning(disable:4996)
#include<iostream>

using namespace std;

class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
		{
			str = " ";
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}

	String(const String& s)
		:_str(new char[strlen(s._str)+1])
	{
		strcpy(_str, s._str);
	}

	String& operator=(const String& s)
	{
		if (this != &s)
		{
			char* temp = new char[strlen(s._str) + 1];
			strcpy(temp, s._str);
			delete[] _str;
			_str = temp;
		}
		return *this;
	}

	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}

private:
	char* _str;
};

void StringTest()
{
	String s1("Hello");
	String s2("World");
	s2 = s1;

	//String s2(s1);
}

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

现代版解决方式

仔细观察上面的代码,我们发现其实重复的操作很多,比如每次申请新空间,拷贝元素。
现代版的方式就是采用巧妙地代码复用,将繁琐的操作简洁化
具体修改在拷贝构造和赋值运算符重载处

下面我们一一解决

  1. 拷贝构造的优化
    在这里插入图片描述
  2. 赋值运算符重载的优化
    在这里插入图片描述
    以上便是对之前代码的一些优化!

1.3通过写时拷贝解决

理解写时拷贝方法

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。
在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,
当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,
如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;
否则就不能释放,因为还有其他对象在使用该资源。
下面使用图解的方式解释一下:
在这里插入图片描述
OK,看了上面的解法,有没有感觉有点问题呢?
上面的过程并没有涉及到对象内容修改,假设我现在想将上面s2对象的内容修改为“World”;由于s1 s2共用一块内存空间,所以修改s2的同时s1的内容也被修改了
但是很明显,这不是我的本意,也不符合规矩。那如何解决这个棘手问题呢?

其实上面的这个过程还没有涉及到我们的写时拷贝。
所谓的写时拷贝是在上面的基础上解决修改(写)内容的时候发生的问题
具体步骤如下:
假设我们现在已经处于上面图示的场景,即s1和s2共用同一块内存空间,计数器此时为2
现在我想要将s2的内容改为“World”,需要执行以下步骤:
(1)为s2对象开辟新的空间
(2)将原来的空间的计数器值减减
(3)将s1对象的内容拷贝至为s2新开辟的空间中,并将s2中计数器值设置为1
(4)s2在新空间内进行修改操作
下面通过图示的方式再次演示该过程:
在这里插入图片描述
以上便是写时拷贝的粗略思路,具体细节内容等到后续总结,目前处于扫盲状态。

1.4验证不同平台的string类是通过什么方式解决浅拷贝的

验证思路:
(1)实例化一个对象s1,长度大于15;
注意:长度必须大于15,否则无法测试出正确的结果。因为在Windows下,string类中维护着一个空间为16的字符数组,因此小于等于15的字符串直接被存储在数组内,并不会申请空间,也就无法验证。
(2)通过拷贝构造实例化对象s2
(3)打印s1和s2对象的地址,观察地址值是否一样
如果一样 -----> 写时拷贝
如果不一样 ----> 深拷贝

验证代码如下:

void TestCopy()
{
	string s1(20, 'A');
	string s2(s1);

	printf("&s1 is %p\n",s1.c_str());
	printf("&s2 is %p\n", s2.c_str());
}

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

1、在Windows平台下的VS2013环境中
在这里插入图片描述
结论:vs2013中string是按照深拷贝实现的
2、在Linux平台下
在这里插入图片描述
结论:Linux中string是按照写时拷贝实现的

2、string类实现

有了前面的知识做铺垫,这里直接给出代码,模拟实现只是对主要的方法进行模拟,并不是完全实现一个string容器

//mystring.h
#pragma once

#include<iostream>

namespace gyj
{
	class string
	{
		friend std::ostream& operator<<(std::ostream& _cout, const gyj::string& s);
	public:
		typedef char* iterator;
	public:
		/构造和析构///
		string(const char* s = "");
		string(const string& str);
		string& operator=(const string& str);
		~string();
		///迭代器相关/
		iterator begin();
		iterator end();
		///容量相关//
		size_t size()const;
		size_t capacity()const;
		bool empty()const;
		void resize(size_t n, char c = '\0');
		void reserve(size_t n);

		///元素访问相关//
		char& operator[](size_t index);
		const char& operator[](size_t index)const;
		///修改相关的///
		void push_back(char c);
		string& operator+=(char c);
		void append(const char* str);
		string& operator+=(const char* str);
		void clear();
		void swap(string& s);
		const char* c_str()const;
		/其他//
		bool operator<(const string& s);
		bool operator<=(const string& s);
		bool operator>(const string& s);
		bool operator>=(const string& s);
		bool operator==(const string& s);
		bool operator!=(const string& s);


		// 返回c在string中第一次出现的位置
		size_t find(char c, size_t pos = 0) const;
		// 返回子串s在string中第一次出现的位置
		size_t find(const char* s, size_t pos = 0) const;
		// 在pos位置上插入字符c/字符串str,并返回该字符的位置
		string& insert(size_t pos, char c);
		string& insert(size_t pos, const char* str);


		// 删除pos位置上的元素,并返回该元素的下一个位置
		string& erase(size_t pos, size_t len);
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};

}
extern void stringTest1();
extern void stringTest2();

接下来是mystring.cpp

#pragma warning(disable:4996)
#include<iostream>
#include"mystring.h"
#include<assert.h>

gyj::string::string(const char* s)
{
	if (nullptr == s)
	{
		assert(0);
	}
	_size = strlen(s);
	_capacity = _size;
	_str = new char[_capacity+ 1];
	strcpy(_str, s);
}
gyj::string::string(const string& str)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
	string temp(str._str);
	std::swap(_str, temp._str);
	_size = str._size;
	_capacity = str._capacity;
}

gyj::string& gyj::string::operator=(const string& str)
{
	if (this != &str)
	{
		string temp(str._str);
		std::swap(_str, temp._str);
		_size = str._size;
		_capacity = str._capacity;
	}
	return *this;
}

gyj::string::~string()
{
	if (_str)
	{
		delete[] _str;
		_str = nullptr;
	}
}
///迭代器相关/
gyj::string::iterator gyj::string::begin()
{
	return _str;
}

gyj::string::iterator gyj::string::end()
{
	return _str + _size;
}

///容量相关//
size_t gyj::string::size()const
{
	return _size;
}
size_t gyj::string::capacity()const
{
	return _capacity;
}

bool gyj::string::empty()const
{
	if (0 == _size)
	{
		return true;
	}
	return false;
}
void gyj::string::resize(size_t n, char c)
{
	if (n > _size)
	{
		if (n > _capacity)
		{
			reserve(n);
		}
		memset(_str + _size, c, n - _size);
	}
	_size = n;
	_str[_size] = '\0';
}
void gyj::string::reserve(size_t n)
{
	if (n > _capacity)
	{
		char* temp = new char[n + 1];
		strcpy(temp, _str);
		delete[] _str;
		_str = temp;
		_capacity = n;
	}
}
///元素访问相关//
char& gyj::string::operator[](size_t index)
{
	assert(index < _size);
	return _str[index];
}
const char& gyj::string::operator[](size_t index)const
{
	assert(index < _size);
	return _str[index];
}
///修改相关的///
void gyj::string::push_back(char c)
{
	if (_size == _capacity)
	{
		//扩容
		reserve(_capacity * 2);
	}
	_str[_size++] = c;
	_str[_size] = '\0';
}
gyj::string& gyj::string::operator+=(char c)
{
	push_back(c);
	return *this;
}
void gyj::string::append(const char* str)
{
	if (_size+strlen(str) >= _capacity)
	{
		//扩容
		size_t newcapacity = _capacity * 2 > _size + strlen(str) ? _capacity * 2 : _size + strlen(str) + 1;
		reserve(newcapacity);
	}
	strcat(_str, str);
	_size += strlen(str);
}
gyj::string& gyj::string::operator+=(const char* str)
{
	append(str);
	return *this;
}
void gyj::string::clear()
{
	_str[0] = '\0';
	_size = 0;
}
void gyj::string::swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
const char* gyj::string::c_str()const
{
	return _str;
}




// <<重载
std::ostream& gyj::operator<<(std::ostream& _cout, const gyj::string& s)
{
	_cout << s._str;

	return _cout;
}

/其他//
bool gyj::string::operator<(const string& s)
{
	int ret = strcmp(_str, s._str);
	if (ret < 0)
	{
		return true;
	}
	return false;
}
bool gyj::string::operator<=(const string& s)
{
	return !(*this>s);
}
bool gyj::string::operator>(const string& s)
{
	int ret = strcmp(_str, s._str);
	if (ret > 0)
	{
		return true;
	}
	return false;
}
bool gyj::string::operator>=(const string& s)
{
	return !(*this < s);
}
bool gyj::string::operator==(const string& s)
{
	int ret = strcmp(_str, s._str);
	if (ret == 0)
	{
		return true;
	}
	return false;
}
bool gyj::string::operator!=(const string& s)
{
	return !(*this == s);
}

// 返回c在string中第一次出现的位置
size_t gyj::string::find(char c, size_t pos) const
{
	for (size_t i = pos; i < _size; i++)
	{
		if (c == _str[i])
		{
			return i;
		}
	}
	return -1;
}
// 返回子串s在string中第一次出现的位置
size_t gyj::string::find(const char* s, size_t pos) const
{
	assert(s);
	assert(pos < _size);
	const char* src = _str + pos;
	while (*src)
	{
		const char* match = s;
		const char* cur = src;
		while (*match && *match == *cur)
		{
			++match;
			++cur;
		}
		if (*match == '\0')
		{
			return src - _str;
		}
		else
		{
			++src;
		}
	}
	return -1;
}
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
gyj::string& gyj::string::insert(size_t pos, char c)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		//扩容
		char *newstr = new char[_capacity * 2 + 1];
		strcpy(newstr, _str);
		delete[] _str;
		_str = newstr;
		_capacity *= 2;
	}
	//移数据
	for (int i = _size; i >= (int)pos; --i)
	{
		_str[i + 1] = _str[i];

	}
	_str[pos] = c;
	_size++;

	return *this;

}
gyj::string& gyj::string::insert(size_t pos, const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)//扩容
	{
		//扩容
		char *newstr = new char[_capacity * 2 + 1];
		strcpy(newstr, _str);
		delete[] _str;
		_str = newstr;
		_capacity *= 2;
	}

	//后移数据
	for (int i = _size; i >= (int)pos; --i)
	{
		_str[len + i] = _str[i];
	}

	//拷贝字符串
	while (*str != '\0')
	{
		_str[pos++] = *str++;
	}
	_size += len;
	return *this;
}

// 删除pos位置上的元素,并返回该元素的下一个位置
gyj::string& gyj::string::erase(size_t pos, size_t len)
{
	assert(pos < _size);

	if (pos + len >= _size)//pos位置之后全为0
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
	return *this;

}


void stringTest1()
{
	gyj::string s1("hello");
	gyj::string s2(s1);
	gyj::string s3;
	s3 = s2;

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

	s1.resize(3);
	s2.resize(10);
	s3.resize(15, 'A');

	s1.reserve(4);
	s2.reserve(20);
	s3.reserve(100);

	std::cout << s1[0] << std::endl;
	std::cout << s2[3] << std::endl;
	const gyj::string s4("aaaaaa");
	char ch = s4[3];

	//_CrtDumpMemoryLeaks();
}

void stringTest2()
{
	gyj::string s1("Hello");

	s1.push_back(' ');
	s1.append("World");
	s1 += '!';
	s1 += "~~~~";
	
	std::cout << s1.c_str() << std::endl;

	gyj::string s2("12345");
	s1.swap(s2);
	s1.clear();
	_CrtDumpMemoryLeaks();

}

具体代可以参考我的码云

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Suk-god

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值