当前位置: 首页 > article >正文

从零开始的C++之旅——string类的模拟实现

1.string类是什么

        在C++中,stirng 类是标准库提供的一个非常强大的字符串处理工具。相比于C语言中的字符
        数组(char[ ]) , string 类提供了更多方便且安全的操作。同时也更符合我们面向对象语言的特
        点。

2.stirng类的模拟实现

        string类的模拟实现我们就模拟实现一些高频使用的容器操作,在逐步的实现过程中了解其底
        层的实现方式。需要注意地是,我们需要将直接实现的string类与系统中自带的string类分开,
        使用,我将其包在一个自定义的命名空间里面

        同时由于定义声明分离,我们在定义函数时需要指定类域

2.1 定义成员变量

        string类型的成员变量其实跟我们之前数据结构定义的顺序表十分相似,同时一些操作的实现
        也与顺序表类似,因此实现方式也基本类似,无非就是多了类和对象的相关只是,在实现
        string类的过程中也可以帮助我们对c++的一些相关知识有更好的掌握

        既然和顺序表类似类似,那么其自然是三个成员变量,一个是大小size,一个是存储空间大小
        capacity,一个是char*类型的指针

                

2.2 构造函数

string(const char* str = "")

        构造函数我们实现一个带缺省值的构造函数,使得我们实例化一个对象时,若没有传参则会
        调缺省值生成一个空串,若传参了则按吗传的参数进行初始化。

        所以首先我们的参数类型应该是接受字符串的首元素地址所以是char*类型,同时保证传递参
        数不被修改加上const

        对_str的初始化,自然是使用new 去申请一块空间,需要注意的是申请空间的大小应比字符串
        的大小大一位,因为还需要存储 “ \0  ”。size和capacity自然是初始化为0。

        同时,要调用strcpy函数将传入的字符串拷贝到string类当中

	string::string(const char* str)
		:_str(new char[strlen(str) + 1])
		, _size(strlen(str))
		, _capacity(strlen(str))
	{
		strcpy(_str, str);
	}

2.3 析构函数 

        有了构造函数,我们自然可以实现对应的析构函数。只需要使用delete[ ]释放掉new[ ]申请的
        空间,并将str=nullptr,size,capacity=0即可

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

2.4 开辟空间函数

        在实现pushback之前,我们要实现一个函数reserve,实现空间的扩容,避免空间不足。

        首先我们函数的参数是一个size_t类型的参数n,即表示我们需要扩容的大小。我们使用if条件
        判断一下当前扩容的空间n是否是否小于当前的有空间capacity,避免产生缩容

        扩容的第一步自然是申请一片空间,大小为n+1,随后使用strcpy将原对象中的数据拷贝到新
        开辟的空间,随后释放掉原空间申请的内存,再使_str指向新开辟的空间,最后更新capacity

	void string::reserve(size_t n)
	{
		if (n > _capacity)//判断,因为reserve功能不是缩容
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
    }

2.5 迭代器

        string类的迭代器相对比较简单,一位他的数据无论是逻辑顺序还是逻辑顺序都是连续的,因
        此我们简单的用指针的遍历来模拟迭代器。

		typedef char* iterator;
		typedef const char* const_iterator;

普通迭代器

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

const 迭代器

	    const_iterator begin() const
		{
			return _str;
		}

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

2.6 插入函数

        有了reserve函数之后,我们便可以实现尾插函数,而尾插函数又分为尾插一个字符和一个字
        符串。插入函数的逻辑与顺序表的插入大致类似。熟悉了顺序表的插入这自然也得心应手。

尾插一个字符(push_back)

        尾插一个字符逻辑非常简单,只需要先判断一下空间是否需要扩容,之后再尾部加上要插入
        的字符,随后更新size即可

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

尾插一个字符串(append)

        由于我们扩大逻辑一般为二倍扩容,因此若插入的字符串有可能出现及时扩容后空间依然不
        足的情况,所以我们需要一个if条件判断扩容后的空间只否足够插入字符串。若扩容后的空间
        依然不足插入字符串,则直接扩容至size+len的大小。扩容完毕后只需要使用strcpy将需要插
        入的字符串拷贝到str的末尾即可

	void string::append(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);
		}
		strcpy(_str + _size, str);
		_size += len;
	}

 指定位置插入(insert)

指定位置插入单个字符

        首先自然是扩容,其逻辑与append一致,不再过多赘述。

        在扩容完成之后,需要定义一个指针end,将pos位置以后的数据依次往后挪动一位,再将
        字符插入到pos位置即可

	void string::insert(size_t pos, char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		size_t end = _size + 1;

		while (end != pos)
		{
			_str[end] = _str[end - 1];
			end--;
		}
		_str[pos] = ch;
		_size++;
	}
插入一个字符串

        插入字符串则是再扩容之后,需要将一段区间的数据往后挪,这时候我们就需要定义指针end
        end指向末尾之后的len个长度(len为插入字符串的大小),在将依次将原数据的末尾字符从
        头往前挪动,最后使用strcpy将要插入的字符串拷贝到pos位置。

insert的复用

        在有了指定位置插入insert函数之后,我们便可以复用我们的insert来实现pushback和
        apped。

	void string::push_back(char ch)
	{

		insert(_size, ch);
	}

	void string::append(const char* str)
	{

		insert(_size, str);
	}

2.7 删除函数 

指定位置删除(erase)

        指定位置删除我们要实现的是删除从pos位置开始的n个字符

        因此我们首先要计算出我们需要前移几个数据,,再定义一个end指针指向要移除的元素区间
        的下一元素,将其赋值给(end-n)位置的元素,再让end++,直到将剩余元素移完

	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;
			while (end + len <= _size)
			{
				_str[end] = _str[end + len];
				end++;
			}
			_size -= len;
		}
	}

2.8 交换函数

        库中虽然有交换函数,但是其行为对于类来说要进行多次深拷贝,代价非常的大,因此我们
        自己实现一个函数,进行拷贝构造,提高其效率。

swap(string& s)

        在一开始的时候我们就定义了类的成员变量,那么其实我们只要交换两个类的成员变量就可
        以实现两个类的交换,这时候就可以调用库中的swap来进行成员变量的交换,因为其只涉及
        浅拷贝,代价较小

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

2.9 拷贝构造

        由于我们的stirng在构造时候申请了资源,因此编译器自动生成的拷贝构造浅拷贝行为无法完
        成我们的需要,这回导致两个对象指向同一片空间从而析构两次而报错。因此我们就需要自
        己写拷贝构造。

string(const string& s)

        有了类的交换函数之后我,就可以轻松的实现拷贝构造,我们只需用被拷贝的对象创建一个
        临时变量tmp,再调用我们自己实现的swap函数将其与我们要创建的对象交换即可,并且tmp
        这个临时对象也会在出函数的时候自动销毁不需要我们去手动销毁,一举两得

	string::string(const string& s)
	{
		string tmp(s._str);
		swap(tmp);
	}

2.10 赋值重载

        同理于拷贝构造,我们自然也要自己实现那赋值重载。

string& operator=(string s)

        由于这里的函数参数并不是对象的引用,因此是传递的参数锁构造出来的一个临时对象,我
        们只需调用我们实现的swap函数进行交换最后返回*this即可,临时对象s也会在出函数之后自
        己销毁。


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

2.11 运算符重载

        运算符的重载可以复用,这意味着我们只需要实现一两个运算符的重载即可复用从而实现
        其他的。

        由于运算符重载的函数其第一个参数并不是累的this指针,因此我们将其实现在类的外部
        又因为我们的string类是实现在自己的命名空间中,因此不需要将其再重载为友元函数,编译
        器会自己在命名空间中寻找。

 重载+=

        +=的逻辑与插入函数一致,我们只需要复用我的的两个插入函数即可

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

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

 重载==

        ==运算符的逻辑是字符串的比较,因此我们只需要调用哭函数中的strcmp即可

	bool bit::operator==(const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) == 0;
	}

 重载>

        >的重载本质也是比较字符串,调用strcmp即可实现

	bool bit::operator>(const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) > 0;
	}

 重载>= , != , < , <= 

	bool bit::operator<(const string& lhs, const string& rhs)
	{
		return !(lhs >= rhs);
	}
	bool bit::operator>=(const string& lhs, const string& rhs)
	{
		return lhs == rhs || lhs > rhs;
	}
	bool bit::operator<=(const string& lhs, const string& rhs)
	{
		return !(lhs > rhs);
	}
	bool bit::operator!=(const string& lhs, const string& rhs)
	{
		return !(lhs == rhs);
	}

2.12 流插入流提取重载

         

	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();

		int i = 0;
		char buff[256];//建立临时数组,避免频繁开空间,并且栈上开空间比堆上开空间效率高

		char ch;
		ch = is.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 255)
			{
				buff[255] = '\0';
				str += buff;
				i = 0;
			}
			ch = is.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}
		return is;
	}

3. 源代码 

stirng.h

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

namespace bit
{

	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		string(const char* str = "");
		~string();
		string(const string& s);

		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);
		string& operator=(string s);

		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);

		string substr(size_t pos, size_t len = npos);
		void swap(string& s);

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

		size_t size() const
		{
			return _size;
		}

		char& operator[](size_t i)
		{
			assert(i < _size);
			return _str[i];
		}

		char& operator[](size_t i) const
		{
			assert(i < _size);
			return _str[i];
		}

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

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

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

	private:
		size_t _size;
		size_t _capacity;
		char* _str;
	public:
		//static const size_t npos = -1;
		//静态成员变量不能给缺省值必须类内定义类外声明
		// 但const 整形静态成员可以直接给缺省值,算是特殊处理
		static const size_t npos;
	};

	void swap(string& s1, string& s2);

	bool operator==(const string& lhs, const string& rhs);
	bool operator!=(const string& lhs, const string& rhs);
	bool operator>(const string& lhs, const string& rhs);
	bool operator<(const string& lhs, const string& rhs);
	bool operator>=(const string& lhs, const string& rhs);
	bool operator<=(const string& lhs, const string& rhs);

	ostream& operator<<(ostream& os, const string& str);
	istream& operator>>(istream& is, string& str);
	istream& getline(istream& is, string& str, char delim = '\n');
}


string.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"

namespace bit
{
	const size_t string::npos = -1;

	string::string(const char* str)
		:_str(new char[strlen(str) + 1])
		, _size(strlen(str))
		, _capacity(strlen(str))
	{
		strcpy(_str, str);
	}

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

	//b2(b1)
	string::string(const string& s)
	{
		string tmp(s._str);
		swap(tmp);
	}

	void string::reserve(size_t n)
	{
		if (n > _capacity)//判断,因为reserve功能不是缩容
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

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

	void string::append(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);
		//}
		//strcpy(_str + _size, str);
		//_size += len;
		insert(_size, str);
	}


	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)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		size_t end = _size + 1;

		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);
		}

		len = strlen(str);
		size_t end = _size + len;
		while (end != pos + len - 1)
		{
			_str[end] = _str[end - len];
			end--;
		}
		for (size_t i = 0; i < len; 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;
			while (end + len <= _size)
			{
				_str[end] = _str[end + len];
				end++;
			}
			_size -= len;
		}
	}

	size_t string::find(char ch, size_t pos)
	{
		assert(pos < _size);
		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(_str + pos, str);
		if (ptr == nullptr)
			return npos;
		else
			return ptr - _str;
	}

	string string::substr(size_t pos, size_t len)
	{
		if (len > (_size - pos))
		{
			len = _size - pos;
		}

		bit::string sub;
		sub.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}

		return sub;
	}

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

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

	void swap(string& s1, string& s2)
	{
		s1.swap(s2);
	}


	bool bit::operator==(const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) == 0;
	}
	bool bit::operator!=(const string& lhs, const string& rhs)
	{
		return !(lhs == rhs);
	}
	bool bit::operator>(const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) > 0;
	}
	bool bit::operator<(const string& lhs, const string& rhs)
	{
		return !(lhs >= rhs);
	}
	bool bit::operator>=(const string& lhs, const string& rhs)
	{
		return lhs == rhs || lhs > rhs;
	}
	bool bit::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();

		int i = 0;
		char buff[256];//建立临时数组,避免频繁开空间,并且栈上开空间比堆上开空间效率高

		char ch;
		ch = is.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 255)
			{
				buff[255] = '\0';
				str += buff;
				i = 0;
			}
			ch = is.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}
		return is;
	}

	istream& getline(istream& is, string& str, char delim)
	{
		str.clear();

		int i = 0;
		char buff[256];

		char ch;
		ch = is.get();
		while (ch != delim)
		{
			buff[i++] = ch;
			if (i == 255)
			{
				buff[255] = '\0';
				str += buff;
				i = 0;
			}
			ch = is.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}
		return is;
	}
}


http://www.kler.cn/a/374924.html

相关文章:

  • 在canon的生活
  • smuge error
  • 纯CSS实现UI设计中常见的丝带效果(5)
  • NLP segment-01-聊一聊分词 AI 的基础
  • JavaScript语法基础——变量,数据类型,运算符和程序控制语句(小白友好篇,手把手教你学会!)
  • 多线程和线程同步基础篇学习笔记(Linux)
  • 【Git】Git常用命令
  • (蓝桥杯C/C++)——常用库函数
  • 【Deno运行时】深入解析Deno:下一代JavaScript和TypeScript运行时
  • cisco网络安全技术第4章测试及考试
  • 高效扶贫:SpringBoot精准扶贫系统
  • 笔记整理—linux驱动开发部分(4)驱动框架
  • 【Nginx】编译安装(Centos)
  • Windows下Jenkins自动启动jar包
  • 技术总结(十九)
  • unity后端kbengine用DOTween让 移动同步丝滑
  • HJ106 字符逆序
  • 发布 NPM 包时,终端显示发布成功但实际上版本并没有更新,可能是由于以下原因
  • 基于 Postman 和 Elasticsearch 测试乐观锁的操作流程
  • Java的多态
  • LEADTOOLS 版本 23 现已发布,引入了 Excel API等众多新功能!
  • 就业市场变革:AI时代,我们将如何评估人才?
  • Python之groupby()及aggregate()方法
  • 手机实时提取SIM卡打电话的信令声音-新的篇章(三、Android虚拟声卡探索)
  • 每日互动基于 Apache DolphinScheduler 从容应对ClickHouse 大数据入库瓶颈
  • 巨好看的登录注册界面源码