C++string容器模拟实现

Posted Suk-god

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)以上是关于C++string容器模拟实现的主要内容,如果未能解决你的问题,请参考以下文章

[C/C++]详解STL容器1--string的功能和模拟实现(深浅拷贝问题)

C++从青铜到王者第八篇:STL之string类的模拟实现

C++初阶:string类string类 | 浅拷贝和深拷贝(传统写法和现代写法) | string类的模拟实现

C++初阶:STL —— stringstring类 | 浅拷贝和深拷贝(传统写法和现代写法) | string类的模拟实现

C++初阶:STL —— stringstring类 | 浅拷贝和深拷贝(传统写法和现代写法) | string类的模拟实现

C++STL之string类的使用和实现