C++教你如何模拟实现string,如何实现string写时拷贝

文章目录

  • 前言
  • 成员变量
  • 默认成员函数
    • 默认构造函数
    • 拷贝构造函数
    • 析构函数
    • 赋值运算符重载
  • 容量相关函数(Capacity)
    • reserve函数
    • resize函数
    • size函数
    • capacity 函数
    • clear函数
  • 修改函数(Modifiers)
  • swap函数
    • insert函数
      • 字符插入
      • 字符串插入
    • append函数
    • push_back函数
    • erase函数
    • +=重载
  • 元素获取函数(Element access)
  • operator[]
  • 操作函数(String operations)
    • c_str函数
    • substr函数
    • find函数
  • 运算符重载
    • 流插入运算符<<重载
    • 流提取运算符>>重载
  • 完整代码
  • 写实拷贝
    • 写时拷贝完整代码

前言

  本文将要对STL容器string进行模拟实现,将要实现string常用构造函数,析构函数,拷贝构造函数以及常用增删查改接口,介绍如何通过函数复用以达到简化代码,如何通过写实拷贝提高程序效率,通过模拟实现达到加深对string的理解,提高自身编程技巧的效果。

注:本文在读者拥有string相关知识储备的基础下更易于理解,可跳转链接阅读博主的另一篇文章掌握如何使用string后再来阅读


成员变量

	private:		
		char* _str=nullptr;//字符串
		size_t _capacity=0;//容量
		size_t _size=0;//有效字符个数
		static const size_t npos;
		//static const size_t npos=-1  vs下int类型支持给static和const同时修饰的变量用缺省值

默认成员函数

默认构造函数

  默认构造函数(无参构造函数)是构造一个空的字符串。

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

  像上面这样写如何?可以,但是可以更简洁一些。

  像下面这样写更加简洁并且好处在于C-string构造函数可以同时担任默认构造函数与C-string构造函数的角色,这一点利用了语言的语法规则,如果我们显示定义构造函数后编译器则不会再生成默认构造函数

		string(const char* str="")//对象实例化不传递参数则默认用空字符串拷贝构造
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

  注:实现string(const char* str=“”)后不可以再显示实现默认构造函数,否则会造成调用歧义。
  在调用方面,如果我们想要实例化一个空字符串的string对象要以以下方式调用

string s1;

  绝对不可以用以下的方式调用,错误示例如下:
  在这种情况下编译器会把它识别为函数声明

string s1();

拷贝构造函数

  拷贝构造函数是用一个类对象实例化另一个类对象

		string(const string& str)
		{
			_size = str._size;
			_capacity = str._capacity;
			_str = str._str;
		}

  如果以以上方式编写后进行调用会发生什么
  在调用之前我们先实现一个方便我们观察运行时现象的析构函数

析构函数

  清理对象占用内存资源

		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
			//cout<<~string()<<endl;此语句仅为观察使用
		}

  运行程序在这里插入图片描述
  监视窗口在这里插入图片描述

  RUN:运行错误
在这里插入图片描述   以上为典型浅拷贝引起的delete内存释放错误,浅拷贝使得两个对象共享同一块内存资源,在内存释放时对同一内存空间进行多次释放引起错误。
  默认拷贝构造函数同样是浅拷贝,仅对对象的成员变量的值进行拷贝。

   什么是浅拷贝?
  浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。其实我们可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。

  什么是深拷贝

深拷贝是指在进行对象拷贝时,不仅复制对象本身的成员变量,还复制对象所指向的动态分配的资源(例如堆内存)到新的对象中。这意味着拷贝后的对象和原对象拥有独立的资源副本,彼此之间不会相互影响。
当对象中含有动态分配的资源,如指针指向的内存块,或者其他动态分配的资源(文件句柄、网络连接等),进行深拷贝是非常重要的,以避免多个对象共享同一块资源导致释放重复、悬挂指针(悬挂指针:指的是一个指针变量指向了曾经被分配的内存地址,但该内存已经被释放或者回收了。在这种情况下,指针仍然指向原来的内存地址,但那个地址现在可能已经被操作系统重新分配给了其他程序或变量,或者已经被标记为不可用。)等问题。
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供

  拷贝构造正确编写方法

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

  通过函数复用进行优化

		void swap(string& str)//交换对象内容
		{
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
			std::swap(_str, str._str);
		}
		string(const string& str)
		:_str(nullptr)//_str用nullptr初始化是为了确保与之交换的指针不会成为野指针,导致不必要错误
        ,_size(0)
        ,_capacity(0)
		{
			string temp(str._str);
			swap(temp);
			//this->swap(temp);编译器为每个非静态的成员函数配备一个this指针
			//这个this指针可以显示使用,也可以不显示使用
		}

  这样设计的原理是调用C-string构造函数后再交换两个对象的内容,temp对象出作用域之后自动销毁。

  这种方法虽然简化了代码但存在着一定问题,像_size 与_capacity的大小可能与被拷贝对象的值不相同,但问题可忽略。
  关于为什么不使用标准库的swap函数进行数据交换
   在C++之前swap是以下形式实现
在这里插入图片描述   它首先会拷贝构造一个对象,然后再进行赋值拷贝,这种实现方法十分低效
  C++11之后新增的运行移动语义使得其实现更高效,如果使用C++之后的swap,它的效率与我们模拟实现的方法的效率相比更高效,它省去了一切资源的开辟。
  此内容需要大篇幅讲解才能理清逻辑,因本文重点不在此,故不进行详细讲解。在这里插入图片描述

赋值运算符重载

		string& operator =(const string& s)
		{
			if (this != &s)//如果“自己”给“自己”赋值则直接跳过
			{
				char* temp = new char[s._capacity + 1];
				strcpy(temp, s._str);
				delete[] _str;
				_str = temp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}

  优化

		string& operator=(const string& s)
		{
			if (this != &s)
			{
				string temp(s);
				swap(temp);
			}
			return *this;
		}

  再优化

		string& operator=(string s)
		{
			swap(s);
			return *this;
		}

  通过值拷贝的方式传递参数,直接生成一个临时对象,再交换对象内容,达到简化代码的效果。


容量相关函数(Capacity)

reserve函数

  根据Windows平台下的reserve规则

n > _capacity时对对象的容量进行扩容
n<=_capacity时不对对象的容量进行修改

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

				_str = tmp;
				_capacity = n;
			}
		}

  开辟新空间,转移数据,释放原空间,修改成员变量

resize函数

  根据Windows平台下的reserve规则

n>_size
  首先判断n > _capacity是否成立,成立则首先进行扩容操作
  再将[_size,n)区间内容填入指定字符ch
n<=_size时对对象的有效字符进行缩减,将有效字符缩减至指定个数

		void resize(size_t n, char ch = '\0')
		{
			if (n > _size)
			{
				reserve(n);
				for (int i = _size; i < n; ++i)
				{
					_str[i] = ch;
				}
			}
			_str[n] = '\0';//必须给_str[n]赋值'\0',以做字符串结束标志
			_size = n;
		}

size函数

  返回字符串有效字符长度

size_t size() const
{
	return _size;
}

capacity 函数

  返回空间总大小

size_t capacity() const
{
	return _capacity;
}

clear函数

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

  清空字符串内容只需要修改字符串结束标志,下次再对字符串进行操作会覆盖式写入内容。
在这里插入图片描述


修改函数(Modifiers)

swap函数

  交换两个对象内容

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

insert函数

字符插入

  在指定位置插入一个字符

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

	// 判断是否需要扩容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	size_t end = _size + 1;
	while (end > pos)//将插入位置 pos 之后的字符依次向后移动一个位置。
	{
		_str[end] = _str[end - 1];
		--end;
	}

	_str[pos] = ch;//将字符 ch 插入到指定的插入位置 pos
	++_size;//插入字符后,将字符串的实际大小 _size 增加 1

	return *this;
}

字符串插入

  在指定位置插入一个字符串

string& insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	// 挪动数据
	size_t end = _size + len;
	while (end >= pos + len)//将插入位置 pos 之后的字符依次向后移动 len 个位置,为新字符串的插入留出空间
	{
		_str[end] = _str[end - len];
		--end;
	}

	strncpy(_str + pos, str, len);//将字符串拷贝到指定位置
	_size += len;//插入字符后,将字符串的实际大小 _size 增加 len

	return *this;
}

append函数

  在字符串后追加一个字符串

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

// 判读是否需要扩容
	if (_size + len > _capacity)
	{
		reserve(_size+len);
	}

	strcpy(_str + _size, str);
	_size += len;
}

  通过复用insert实现

void append(const char* str)
{
	insert(_size, str);
}

push_back函数

  字符串尾插一个字符

void push_back(char ch)
{
	// 判读是否需要扩容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	_str[_size] = ch;
	++_size;
	_str[_size] = '\0';//作为字符串必须要字符串结束标志
}

  通过复用insert实现

void push_back(char ch)
{
	insert(_size, ch);
}

erase函数

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);

			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}

  如果len 等于缺省值 npos则将pos及以后字符全部删除
  如果删除字符个数超过pos位置后字符总和则同样将pos及以后字符全部删除
  如果以上两种情况不成立则直接将从位置 pos + len 开始的字符复制到位置 pos,覆盖掉要删除的字符。

+=重载

  += 运算符在字符串的操作中通常被用作连接(拼接)操作。
  连接一个字符

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

  连接一个字符串

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

元素获取函数(Element access)

operator[]

  返回pos位置的字符,可修改pos位置字符

		char& operator[](size_t pos)
		{
			assert(pos < _size);

			return _str[pos];
		}

  返回pos位置的字符,不可修改pos位置字符

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);

			return _str[pos];
		}

注意断言,下标位置不能等于_size,_size指向最后一个字符的下一个也就是字符0,但是实际库中的string的字符0我们是可以访问到的,这里主要表示有效数据的访问。


操作函数(String operations)

c_str函数

  返回C格式字符串

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

substr函数

  获取一个子字符串

		string substr(size_t pos, size_t len = npos) const
		{
			assert(pos < _size);
			string str;
			if (len == npos || pos + len >= _size)
			{
				str.reserve(_size-pos);
				str._size = _size - pos;
				strcpy(str._str, _str + pos);
			}
			else
			{
				str.reserve(len);
				str._size = len;
				strncpy(str._str,_str + pos,len);
				str._str[_size] = '\0';
			}
			return str;
		}

  如果len长度超过pos位置后字符总和或者为缺省值则将pos及以后字符全部复制,反之则复制其区间到目标字符串str,然后将字符串末尾添加字符串结束标志’\0’.
  优化

		string substr(size_t pos, size_t len = npos) const
		{
			assert(pos < _size);
			size_t end = pos+len;
			if (len == npos || pos + len >= _size)
			{
				end = _size;
			}

			string str;
			str.reserve(end - pos);
			for (size_t i = pos; i < end; ++i)
			{
				str += _str[i];
			}

			return str;
		}

  确定被拷贝字符串的末尾位置,然后将被拷贝字符串的字符依次连接到目标字符串中。

find函数

  查找一个字符查找成功返回其下标,查找一个字符串查找成功返回其字符串的起始下标,查找失败均返回npos值

size_t find(const char ch, size_t pos = 0)
{
    assert(pos < _size);
	while (pos < _size)
    {
        if (_str[pos] == ch)
        {
            return pos;
		}
        ++pos;
	}
    return npos;
}
size_t find(const char* str, size_t pos = 0)
{
    const char* ptr = strstr(_str + pos, str);
    if (ptr == nullptr)
    {
        return npos;
    }
    else
    {
        return ptr - _str;
    }
}

  strstr 是一个库函数,用于在一个字符串中查找另一个字符串的首次出现位置

char *strstr(const char *haystack, const char *needle);

  haystack 是要在其中进行搜索的字符串,也被称为主字符串。
  needle 是要在 haystack 中查找的子字符串。
  如果 needle 在 haystack 中找到,则 strstr 返回指向 haystack 中 needle 首次出现位置的指针。如果未找到 needle,则返回 NULL。

运算符重载

流插入运算符<<重载

  流插入运算符<<重载要定义为全局函数,因为定义为全局函数,因此在其实现中不能直接访问类的私有成员,而需要通过类的公有接口进行访问,或者将其定义为类的友元函数进而访问其私有成员。

	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			out << s[i];
		}
		return out;
	}

流提取运算符>>重载

	std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}

  使用istream的成员函数get,每次读入一个字符
在这里插入图片描述  以上设计方法存在频繁扩容的问题,如果我们频繁输入就会频繁进行扩容操作,频繁进行函数调用会降低效率,因此我们可以创建一个”输入缓冲区buff“当缓冲区填满则将缓冲区内容刷新出去,当输入结束再将未刷新的缓冲区进行刷新,该策略在语言层面和操作系统层面有着广泛应用。

	std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();
		char buff[128] = { '\0' };
		size_t i = 0;
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			if (i == 127)
			{
				s += buff;
				i = 0;
			}
			buff[i++] = ch;
			ch = in.get();
		}
		if (i >= 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

完整代码

#pragma once
#include<Cassert>
namespace zyc
{
	class string
	{
		//friend std::ostream& operator<<(std::ostream& out, const zyc::string& s);//设置为友元函数直接访问类的私有成员变量
		typedef char* iterator;
		typedef const char* const_iterator;
	public:
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		void swap(string& str)
		{
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
			std::swap(_str, str._str);
		}
		string(const string& str)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string temp(str._str);
			swap(temp);
		}
	
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;

				_str = tmp;
				_capacity = n;
			}
		}
		void resize(size_t n, char ch = '\0')
		{
			if (n > _size)
			{
				reserve(n);
				for (int i = _size; _size < n; i++)
				{
					_str[i] = ch;
				}
			}
			_str[n] = '\0';
			_size = n;
		}
		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);

			// 满了就扩容
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}

			_str[pos] = ch;
			++_size;

			return *this;
		}
		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

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

			strncpy(_str + pos, str, len);
			_size += len;

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

			// 满了就扩容
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			strcpy(_str + _size, str);
			_size += len;
		}
		/*void append(const char* str)
		{
			insert(_size, str);
		}*/
		void push_back(char ch)
		{
			// 满了就扩容
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);

			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}
		/*string substr(size_t pos, size_t len = npos) const
		{
			assert(pos < _size);
			string str;
			if (len == npos || pos + len >= _size)
			{
				str.reserve(_size-pos);
				str._size = _size - pos;
				strcpy(str._str, _str + pos);
			}
			else
			{
				str.reserve(len);
				str._size = len;
				strncpy(str._str,_str + pos,len);
				str._str[_size] = '\0';
			}
			return str;
		}*/
		string substr(size_t pos, size_t len = npos) const
		{
			assert(pos < _size);
			size_t end = pos+len;
			if (len == npos || pos + len >= _size)
			{
				end = _size;
			}

			string str;
			str.reserve(end - pos);
			for (size_t i = pos; i < end; ++i)
			{
				str += _str[i];
			}

			return str;
		}

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

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

		
		const char* c_str() const
		{
			return _str;
		}
		size_t size() const
		{
			return _size;
		}
		size_t capacity() const
		{
			return _capacity;
		}
		size_t find(const char ch, size_t pos = 0)
		{
			assert(pos < _size);
			while (pos < _size)
			{
				if (_str[pos] == ch)
				{
					return pos;
				}
				++pos;
			}
			return npos;
		}
		size_t find(const char* str, size_t pos = 0)
		{
			const char* ptr = strstr(_str + pos, str);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

	private:
		char* _str;//字符串
		size_t _capacity;//容量
		size_t _size;//有效字符个数
		static const size_t npos = -1; //vs下int类型支持给static和const同时修饰的变量用缺省值
	};
	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			out << s[i];
		}
		return out;
	}
/*std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}*/

	std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();
		char buff[128] = { '\0' };
		size_t i = 0;
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			if (i == 127)
			{
				s += buff;
				i = 0;
			}
			buff[i++] = ch;
			ch = in.get();
		}
		if (i >= 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

}

写实拷贝

写时拷贝(Copy-on-Write,简称COW)是一种计算机程序设计领域的优化策略,用于延迟复制资源的实际发生,直到真正需要修改资源时。在写时拷贝的场景中,多个引用(或“视图”)最初指向同一份资源。当某个引用尝试修改资源时,系统才会创建该资源的一个副本,并让修改的引用指向这个新的副本,而其他的引用仍然指向原始的资源。

  写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的,既然是引用计数,必然有一个变量类是某些类所共有的。当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时,这个计数会减一,直到最后一个类析构时,此时的引用计数变为1或0,这时对对象的空间进行释放。
  关于这个引用计数怎么设计,我在这里介绍两种设计思路:
  1:在类内部设置一个指针指向一片开辟的内存空间。
  2:在string成员变量_str指向的堆内存空间中多开辟一个整形的空间进行计数(这里博主采用第二种设计思路)。
在这里插入图片描述
  当我们知道引用计数的设计思路后,就要考虑什么时候应该进行写时拷贝

  写时拷贝发生在对string对象进行修改操做时,比如insert,push_back,append,erease等成员函数+=,[],=赋值操作,以及析构操作时发生写时拷贝。
  对于写时拷贝为主要实现了以下三个私有成员函数,方便发生写时拷贝时进行调用。

//获得引用计数
		int& GetRefCount()
		{
			return *((int*)(_str - 4));
		}
//写时拷贝
		void Sub(size_t n)
		{
			char* tmp = new char[n + 5];
			tmp += 4;
			strcpy(tmp, _str);
			Release();

			_str = tmp;
			GetRefCount() = 1;
			_capacity = n;
		}

  申请新空间,释放旧空间,在交换前前对引用计数–,交换后将新空间的引用计数置为1.

//检查是否需要对空间进行释放
		void Release()
		{
			if (--GetRefCount() == 0)
			{
				delete[](_str - 4);
			}
		}

写时拷贝完整代码

#pragma once
#include<Cassert>
#include<iostream>
namespace zyc
{
	class string
	{
		typedef char* iterator;
		typedef const char* const_iterator;
	public:
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 5];
			_str += 4;
			GetRefCount()=1;
			strcpy(_str, str);
		}

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

		string(const string& str)
			:_str(str._str)
			,_size(str._size)
			,_capacity(str._capacity)
		{	
			++GetRefCount();
		}
		
		string& operator=(const string& s)
		{
			_str = s._str;
			_size = s._size;
			_capacity = s._capacity;
			++GetRefCount();
			return *this;
		}
		~string()
		{
			Release();
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				Sub(n);
			}
		}
		void resize(size_t n, char ch = '\0')
		{
			if (n > _size)
			{
				reserve(n);
				for (int i = _size; _size < n; i++)
				{
					_str[i] = ch;
				}
			}
			_str[n] = '\0';
			_size = n;
		}
		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);

			Sub(_capacity);
			// 满了就扩容
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}

			_str[pos] = ch;
			++_size;

			return *this;
		}
		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			Sub(_capacity);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

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

			strncpy(_str + pos, str, len);
			_size += len;

			return *this;
		}
		void append(const char* str)
		{
			insert(_size, str);
		}
		void push_back(char ch)
		{
			insert(_size, ch);
		}
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			Sub(_capacity);
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}
	
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

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

		string substr(size_t pos, size_t len = npos) const
		{
			assert(pos < _size);
			size_t end = pos + len;
			if (len == npos || pos + len >= _size)
			{
				end = _size;
			}

			string str;
			str.reserve(end - pos);
			for (size_t i = pos; i < end; ++i)
			{
				str += _str[i];
			}

			return str;
		}

		const char* c_str() const
		{
			return _str;
		}
		size_t size() const
		{
			return _size;
		}
		size_t capacity() const
		{
			return _capacity;
		}
		size_t find(const char ch, size_t pos = 0)
		{
			assert(pos < _size);
			while (pos < _size)
			{
				if (_str[pos] == ch)
				{
					return pos;
				}
				++pos;
			}
			return npos;
		}
		size_t find(const char* str, size_t pos = 0)
		{
			const char* ptr = strstr(_str + pos, str);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			Sub(_size);
			return _str[pos];
		}
		void clear()
		{
			Sub(_capacity);
			_str[0] = '\0';
			_size = 0;
		}
		int count()
		{
			return GetRefCount();
		}
	private:
		void Release()
		{
			if (--GetRefCount() == 0)
			{
				delete[](_str - 4);
			}
		}
		int& GetRefCount()
		{
			return *((int*)(_str - 4));
		}
		void Sub(size_t n)
		{
			char* tmp = new char[n + 5];
			tmp += 4;
			size_t len = _size;
			strcpy(tmp, _str);
			Release();

			_str = tmp;
			GetRefCount() = 1;
			_capacity = n;
			_size = len;
		}
		char* _str;//字符串
		size_t _capacity;//容量
		size_t _size;//有效字符个数
		static const size_t npos = -1; //vs下int类型支持给static和const同时修饰的变量用缺省值
	};
	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			out << s[i];
		}
		return out;
	}

	std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();
		char buff[128] = { '\0' };
		size_t i = 0;
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			if (i == 127)
			{
				s += buff;
				i = 0;
			}
			buff[i++] = ch;
			ch = in.get();
		}
		if (i >= 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

}

  注意:我们实现的写时拷贝存并不成熟,像以下修改策略,发生修改操作时,这种操作根本就不会被我们所发现。

string str1 = "hello";
	char& ref = str1[0];
	string str2 = str1;
	ref = 'y';

  并且库的string也存在着缺陷,详情我在这里推荐一篇文章,推荐大家阅读
C++的STD::STRING的“读时也拷贝”技术!


本章到此结束,感谢您的阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/552833.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

论文阅读-Federated-Unlearning-With-Momentum-Degradation

论文阅读-Federated Unlearning With Momentum Degradation 联邦忘却与动量退化 Yian Zhao IEEE Internet of Things Journal 2023 年 10 月 2 日 CCF-C momentum degradation-MoDe 动量退化 memory guidance-记忆引导 knowledge erasure-知识擦除 Deep-learning neural n…

【笔记】vscode debug进入site-packages包源码

选择左侧栏第三个图标&#xff0c;点击创建 launch.json 文件 选择 Python Debugger 选择Python文件 这里可以看到launch.json 文件 在configurations中添加键值对 "justMyCode": false在文件中打上断点&#xff0c;点击"三角符"号开始调试 按F11或者红框…

ROS 2边学边练(27)-- 创建一个launch文件

前言 ROS 2中的启动系统负责帮助用户描述其系统的配置&#xff0c;然后按描述执行。系统的配置包括运行什么程序&#xff0c;在哪里运行&#xff0c;传递什么参数&#xff0c;以及ROS特定的约定&#xff0c;这些约定通过为每个组件提供不同的配置&#xff0c;使其易于在整个系统…

[stm32]DMA使用

自动重装和M2M(软件trig)不能一起使用&#xff0c;否则会停不下来 void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size){RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);DMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddrAddrA;//外…

go语言并发实战——日志收集系统(三) 利用sarama包连接KafKa实现消息的生产与消费

环境的搭建 Kafka以及相关组件的下载 我们要实现今天的内容&#xff0c;不可避免的要进行对开发环境的配置&#xff0c;Kafka环境的配置比较繁琐&#xff0c;需要配置JDK,Scala,ZoopKeeper和Kafka&#xff0c;这里我们不做赘述&#xff0c;如果大家不知道如何配置环境&#x…

STM32芯片flash被锁导致Error Flash Download failed Cortex-M4,解决办法(全)亲测有效

STM32芯片flash被锁导致Error: Flash Download failed - "Cortex-M4"&#xff0c;解决办法&#xff08;全&#xff09;亲测有效&#x1f929;&#xff01; 方法1&#xff1a;由于Keil 中debug的仿真器配置出错导致的下载失败&#xff08;这种问题虽然是低级错误&…

友思特应用 | 红外视角的延伸:短波红外相机的机器视觉应用

导读 短波红外SWIR在不同波段针对不同材料的独特成像特征为各领域检测应用的拓宽提供了基础。本文将展现短波红外成像技术在水分检测、塑料检测、太阳能电池板检查和矿场开采等领域的丰富应用案例&#xff0c;讨论短波红外相机在未来的发展方向。 SWIR 背景简介 短波红外 &am…

基于SpringBoot+Vue的IT技术交流平台(源码+文档+包运行)

一.系统概述 我国科学技术的不断发展&#xff0c;计算机的应用日渐成熟&#xff0c;其强大的功能给人们留下深刻的印象&#xff0c;它已经应用到了人类社会的各个层次的领域&#xff0c;发挥着重要的不可替换的作用。信息管理作为计算机应用的一部分&#xff0c;使用计算机进行…

PHP货运搬家/拉货小程序二开源码搭建的功能

运搬家/拉货小程序的二次开发可以添加许多功能&#xff0c;以增强用户体验和提高业务效率。以下是一些可能的功能&#xff1a; 用户端功能&#xff1a; 注册登录&#xff1a;允许用户创建个人账户并登录以使用应用程序。货物发布&#xff1a;允许用户发布他们需要搬运的货物信息…

OpenHarmony实战开发-如何通过分割swiper区域,实现指示器导航点位于swiper下方的效果。

介绍 本示例介绍通过分割swiper区域&#xff0c;实现指示器导航点位于swiper下方的效果。 效果预览图 使用说明 1.加载完成后swiper指示器导航点&#xff0c;位于显示内容下方。 实现思路 1.将swiper区域分割为两块区域&#xff0c;上方为内容区域&#xff0c;下方为空白区…

HAL STM32 I2C方式读取MT6701磁编码器获取角度例程

HAL STM32 I2C方式读取MT6701磁编码器获取角度例程 &#x1f4cd;相关篇《Arduino通过I2C驱动MT6701磁编码器并读取角度数据》&#x1f388;《STM32 软件I2C方式读取MT6701磁编码器获取角度例程》&#x1f4cc;MT6701当前最新文档资料&#xff1a;https://www.magntek.com.cn/u…

生产服务器变卡怎么排查

服务器变卡怎么排查&#xff0c;可以从以下四个方面去考虑 生产服务器变卡怎么排查 1、网络2、cpu的利用率3、io效率4、内存瓶颈 1、网络 可以使用netstat、iftop等工具查看网络流量和网络连接情况&#xff0c;检查是否网络堵塞、丢包等问题 2、cpu的利用率 1、用top命令定…

VMWare Ubuntu压缩虚拟磁盘

VMWare中ubuntu会越用越大&#xff0c;直到占满预分配的空间 即使系统里没有那么多东西 命令清理 开机->open Terminal sudo vmware-toolbox-cmd disk shrink /关机-> 编辑虚拟机设置->硬盘->碎片整理&压缩 磁盘应用 开机->disk usage analyzer(应用) …

【LLM】认识LLM

文章目录 1.LLM1.1 LLM简介1.2 LLM发展1.3 市面常见的LLM1.4 LLM涌现的能力 2.RAG2.1 RAG简介2.2 RAG 的工作流程2.3 RAG 和 Finetune 对比2.4 RAG的使用场景分析 3. LangChain3.1 LangChain简介3.2 LangChain的核心组件3.3 LangChain 入门 4.开发 RAG 应用的整体流程5. 环境配…

stm32中的中断优先级

在工作中使用到多个定时器中断,由于中断的中断优先级不熟悉导致出错,下面来写一下中断的一些注意事项。 一、中断的分类 1、EXTI外部中断:由外部设备或外部信号引发,例如按键按下、外部传感器信号变化等。外部中断用于响应外部事件,并及时处理相关任务。 2、内部中断:…

搭建Zookeeper完全分布式集群(CentOS 9 )

ZooKeeper是一个开源的分布式协调服务&#xff0c;它为分布式应用提供了高效且可靠的分布式协调服务&#xff0c;并且是分布式应用保证数据一致性的解决方案。该项目由雅虎公司创建&#xff0c;是Google Chubby的开源实现。 分布式应用可以基于ZooKeeper实现诸如数据发布/订阅…

Jmeter 测试-跨线程调用变量

1、Jmeter中线程运行规则 ①各个线程组是完全独立的&#xff0c;每个线程组是不同的业务&#xff0c;互不影响 ②线程组中的每个线程也是完全独立 ③线程组中的每个线程&#xff0c;都是从上往下执行&#xff0c;完成一轮循环后&#xff0c;继续下一轮循环 ④存在业务流或者…

考察自动化立体库应注意的几点

导语 大家好&#xff0c;我是智能仓储物流技术研习社的社长&#xff0c;老K。专注分享智能仓储物流技术、智能制造等内容。 整版PPT和更多学习资料&#xff0c;请球友到知识星球 【智能仓储物流技术研习社】自行下载 考察自动化立体仓库的关键因素&#xff1a; 仓库容量&#x…

python爬虫之爬取微博评论(4)

一、获取单页评论 随机选取一个微博&#xff0c;例如下面这个 【#出操死亡女生家属... - 冷暖视频的微博 - 微博 (weibo.com) 1、fnf12&#xff0c;然后点击网络&#xff0c;搜索评论内容&#xff0c;然后预览&#xff0c;就可以查看到网页内容里面还有评论内容 2、编写代码…

稀碎从零算法笔记Day51-LeetCode:最小路径和

题型&#xff1a;DP、数组、矩阵 链接&#xff1a;64. 最小路径和 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为…