[C++]string类模拟实现

news2024/9/29 13:20:13

目录

前言:

1. string框架构造

2. 默认函数

2.1 构造函数

2.2 析构函数

2.3 拷贝构造

2.4 赋值重载

3. 迭代器

 4. 整体程序


前言:

        本篇文章模拟实现了C++中string的部分功能,有助于大家了解和熟悉string类,虽然这个类不难实现,但是其中有些小细节还是可以细细品味的。

1. string框架构造

        我们得知道编写一个类的首要步骤不是直接开写代码,而是需要思考这个类需要用什么样的数据结构?需要准备哪些参数,这个类的作用域在哪里等等内容。

        首先,咱们知道string类是有多个版本的,不同版本下的string类的变量是不同的,就我所知,在vs下string的实现是下方的格式,实际上比我写的要复杂,但是能够表示。

class string{
 private:

        size_t _size;

        size_t _capacity;

        char*  _buf;

        char   _arr[16];

};

        这样做的用处主要是空间换取时间,当我们只是一个小字符串,那么就直接将数据存到了_arr的数组当中,因为是直接写在类里面的数组,所以就省去了向堆申请空间这一过程,如果数据大于了_arr数组大小,那么系统才会向堆申请空间。所以在vs下的string类有28个字节。

        在Linux下则不同,string类有8个字节,结构如下:至于为什么是8个字节,那是因为Linux下跑C++代码默认是64位机哦,我现在还不会改为32位运行,请原谅博主。

class string{

private:

        m_string* _point;

}

         其中m_string是一个自定义类型,里面存了和vs下差不多样式的变量,只不过没有数组的存在,如下:

class m_string{

        size_t _size;

        size_t _capacity;

        char* _buf;

};

         这样做是因为Linux有一个特性,那就是赌,它赌我们在拷贝时不需要更改,不改那么他就直接将指针指向这个空间,一旦需要更改,那他就会触发写时拷贝这个牛技术。如下图:

        对此我只能是表示,这些大佬们写代码是真牛哇,为了提升代码效率都能卷成这样了,我们自己的实现就不用这么高级的操作了,太难写了。既然是为了理解string类,所以我决定用下方的结构:(简单清楚好写)

class string{   

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

        还有一点,我们的这个类尽量写在一个自己的命名空间当中,防止与库中的string起冲突。

如下:

namespace yf
{
    class string
    {   
     private:
        char* _buf;
        size_t _size;
        size_t _capacity;
    };
}

2. 默认函数

2.1 构造函数

        先看一下C++库中提供的构造函数:

        真是有够恐怖的,这提供的接口有点猛,不过我们呢也不需要写这么多,写几个常用的就好。还有就是这些接口设计得有一些不合理,比如说default和from c-string两个函数很明显是可以写为一个函数的,以缺省参数来表示的,不过也能理解,毕竟C++是为了兼容C的。

代码:

//构造函数
 string(const char* str = "")
	:_size(strlen(str))
{
	_capacity = _size;			
	_buf = new char[_capacity + 1];
	strcpy(_buf, str);
}

        上方我讲解了可以用缺省参数来合并两个函数接口,但是大家再不看我的代码之前能想出来吗?除此之外,我还想了两个方法,一个是 ‘ ’,另一个是“\0”,有人看出不对了吗,这两个方式都是不行的,第二种倒也不是不行,而是太多余了,本身“”里面就已经有了一个‘\0’了显得我们不够专业,那这可不行。第一种用字符方式缺省那是真的错得离谱,我们之后可是要用字符串呢,用字符接收?这很明显不行哇,所以这种情况是一定要避免的。不然以后去公司面试,让我们写个类都乱写,简历上还写着熟悉C++,这不扯淡嘛。还有一点,为了保持new空间和delete保持一致,

        然后我们的构造函数当中还用了初始化列表这一概念,通过我们传入的字符串长度去初始化_size,然后_capacity又在下方被赋值,再为_buf向堆上申请_capacity+1个字节大小的空间,为什么加1,当然是为了存我们的'\0'哇,小笨蛋,最后再使用strcpy函数将字符拷贝到我们的空间当中。

 测试用例:

void test1()
{
	yf::string str1;
	yf::string str2("hello");
	yf::string str3("good morning xxxxxxxxxxxxx");
}

2.2 析构函数

        大伙们回忆一下,为什么我们要有析构函数?因为我们向堆空间申请了空间了,所以需要释放,避免造成内存泄漏的过程。

//析构
~string()
{
	delete[] _buf;
	_size = 0;
	_capacity = 0;
}

        看着这里释放内存的方式是delete[] _buf,也就与我们的构造函数对应起来了,如果构造函数的缺省参数是‘’的话,这里就不好析构,就得分情况,这不是脱了裤子放屁——多此一举嘛。

2.3 拷贝构造

        拷贝构造和构造差不多,我也就不多做解释了,只是要注意参数得用引用。

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

2.4 赋值重载

        如果是没有数据的重载,好,简单,但是呢,我的这个字符串本来是有数据的,另外被拷贝对象有大于的情况,有小于的情况,有等于的情况,如果拷贝错误怎么办?原来的数据这么办?这一些列的问题问下来还简单嘛?依然简单,只不过多数人在一时间想不到完全实现功能的代码罢了。

//赋值重载
string& operator=(const string& str)
{
	//不能自己拷贝自己
	if (this != &str)
	{
		//当前容量大于被拷贝对象并不多时
		if (_capacity - str._capacity < 10)
		{
			strcpy(_buf, str._buf);
			_capacity = str._capacity;
			_size = str._size;
		}
		else
		{
			//考虑到new失败的情况
			char* temp = new char[str._capacity + 1];
			strcpy(temp, str._buf);
			delete[] _buf;
			_capacity = str._capacity;
			_size = str._size;
		}
	}
	return *this;
}

         请看我的代码,首先我们不能直接赋值给自己,这样会让我们把数据给delete掉了,导致整个程序出BUG了还不知道,这是很危险的。然后呢,我还考虑到了,当当前对象的容量大于被拷贝对象,并且大于的值并不多时,可以直接拷贝,不用delete后再整个赋值,节省了时间。

        在下方需要delete的过程中,我们需要考虑到当new失败之后会怎么样,所以不能先释放掉原空间,需要创建一个变量去接收被拷贝对象的数据,如果new失败了,外部的try会直接接收到。

3. 迭代器

        说起string类等一系列库类,里面都是提供了迭代器的,我们也模仿着在我们的类里面实现迭代器,我们呢还是从简,用指针来实现迭代器,不过我们不能将迭代器理解为指针,只是指针能够实现。

定义:

typedef char* iterator;
typedef const char* const_iterator;

对应接口: 

//迭代器begin
iterator begin()
{
	return _buf;
}
const_iterator begin() const
{
	return _buf;
}

//迭代器end
iterator end()
{
	return _buf + _size;
}

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

        此时,我们的string类就能实现范围for这个语法糖了,至于为什么能实现,我只能说,范围for底层就是迭代器。

测试:

void test2()
{
	const yf::string str1("hello world");
	yf::string::const_iterator it = str1.begin();
	while (it != str1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : str1)
	{
		cout << e << " ";
	}
	cout << endl;
}

 4. 整体程序

        其余的函数都是对于string类的补充,比如reserve、resize、+=、[]、<<、>>函数等等,博主很懒,并且认为这些大家都是有基础能实现的所以就不做讲解了,附下代码:

#pragma once

#include<iostream>
#include<string>
#include<assert.h>
using std::cout;
using std::cin;
using std::endl;


namespace yf
{
	class string
	{
		friend std::ostream& operator<<(std::ostream& out, const string& str);
		friend std::istream& operator>>(std::istream& in, yf::string& str);
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		//构造函数
		string(const char* str = "")
			:_size(strlen(str))
		{
			_capacity = _size;			
			_buf = new char[_capacity + 1];
			strcpy(_buf, str);
		}

		//拷贝构造
		string(const string& str)
		{
			_capacity = str._capacity;
			_size = str._size;
			_buf = new char[str._capacity + 1];
			strcpy(_buf, str._buf);
		}

		//赋值重载
		string& operator=(const string& str)
		{
			//不能自己拷贝自己
			if (this != &str)
			{
				//当前容量大于被拷贝对象并不多时
				if (_capacity - str._capacity < 10)
				{
					strcpy(_buf, str._buf);
					_capacity = str._capacity;
					_size = str._size;
				}
				else
				{
					//考虑到new失败的情况
					char* temp = new char[str._capacity + 1];
					strcpy(temp, str._buf);
					delete[] _buf;
					_capacity = str._capacity;
					_size = str._size;
				}
			}
			return *this;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _buf[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _buf[pos];
		}

		//tidy_capacity
		void reserve(size_t n);
		void resize(size_t n, char c = '\0');

		//add
		void push_back(char c);
		void append(const string& str);
		void append(const char* s);
		string& operator+=(char c)
		{
			push_back(c);
			return *this;
		}
		string& operator+=(const string& str)
		{
			append(str);
			return *this;
		}
		string& operator+=(const char* s)
		{
			append(s);
			return *this;
		}

		//insert
		string& insert(size_t pos, size_t n, char c);
		string& insert(size_t pos, const char* s);
		string& insert(size_t pos, const string& str);

		//迭代器begin
		iterator begin()
		{
			return _buf;
		}
		const_iterator begin() const
		{
			return _buf;
		}

		//迭代器end
		iterator end()
		{
			return _buf + _size;
		}

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

		//输出
		void print() const
		{
			cout << _buf << endl;
		}
		const char* c_str() const
		{
			return _buf;
		}

		//大小
		size_t size() const 
		{
			return _size;
		}


		//容量
		size_t capacity() const
		{
			return _capacity;
		}

		//匹配字符
		size_t find_first_of(char c, size_t pos = 0)
		{
			const_iterator it = begin();
			it += pos;
			while (it != end())
			{
				if (*it == c)
				{
					return (it - begin());
				}
				it++;
			}
			return -1;
		}
		
		//获取字串
		string substr(size_t pos = 0, size_t len = -1) const;

		//析构
		~string()
		{
			delete[] _buf;
			_size = 0;
			_capacity = 0;
		}

		const size_t npos = -1;
	private:
		char* _buf;
		size_t _size;
		size_t _capacity;
	};
}
#define _CRT_SECURE_NO_WARNINGS 1

#include"string.h"

//tidy_capacity->reserve
void yf::string::reserve(size_t n)
{
	if (n > _capacity)
	{
		char* temp = new char[n + 1];
		strcpy(temp, _buf);
		delete[] _buf;
		_buf = temp;
		_capacity = n;
	}
}

//tidy_capacity->resize
void yf::string::resize(size_t n, char c)
{
	//长度小于_size时,需要删除n位置后的数据
	if (n < _size)
	{
		_size = n;
		_buf[_size] = '\0';
	}
	else
	{
		//当n>_size有两种情况,大于capacity和小于capacity
		if (n > _capacity)
		{
			//大于需要扩容操作
			reserve(n);					//复用reserve
		}
		//二者都有同样的需求,对后面的数据初始化为c
		while (_size != n)
		{
			_buf[_size] = c;
			++_size;
		}
		_buf[_size] = '\0';
	}
}

//add
void yf::string::push_back(char c)
{
	if (_size + 1 > _capacity)
	{
		//加一预防_capacity初始为0
		reserve(2 * _capacity + 1);
	}
	_buf[_size++] = c;
	_buf[_size] = '\0';
}

void yf::string::append(const string& str)
{
	if (_size + str._size > _capacity)
	{
		reserve(_size + str._size);
	}
	size_t len = str._size;
	int i = 0;
	while (i!=len)
	{
		_buf[_size++] = str[i];
		i++;
	}
	_buf[_size] = '\0';
}

void yf::string::append(const char* s)
{
	size_t len = strlen(s);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	while (*s != '\0')
	{
		_buf[_size++] = *s;
		s++;
	}
	_buf[_size] = '\0';
}

//insert 
yf::string& yf::string::insert(size_t pos, size_t n, char c)
{
	if (_size + n > _capacity)
	{
		reserve(_size + n);
	}
	size_t end = _size + n;
	while (end - n + 1 > pos)
	{
		_buf[end] = _buf[end - n];
		--end;
	}
	for (size_t i = 0; i < n;++i)
	{
		_buf[pos + i] = c;
	}
	_size += n;
	return *this;
}

//插入字符串
yf::string& yf::string::insert(size_t pos, const char* s)
{
	size_t len = strlen(s);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	size_t end = _size + len;
	while (end - len + 1 > pos)
	{
		_buf[end] = _buf[end - len];
		--end;
	}
	strncpy(_buf + pos, s, len);
	_size += len;
	return *this;
}

yf::string& yf::string::insert(size_t pos, const yf::string& str)
{
	size_t len = str._size;
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	size_t end = _size + len;
	while (end - len + 1 > pos)
	{
		_buf[end] = _buf[end - len];
		--end;
	}
	strncpy(_buf + pos, str._buf, len);
	_size += len;
	return *this;
}

//获取子串
yf::string yf::string::substr(size_t pos, size_t len)  const
{
	yf::string::const_iterator it = begin();
	yf::string temp;
	it += pos;
	if (len > size())
		len = size();

	while (it != begin() + len)
	{
		temp += *it;
		++it;
	}
	return temp;
}

//流插入
std::ostream& yf::operator<<(std::ostream& out, const yf::string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}

//流提取
std::istream& yf::operator>>(std::istream& in, yf::string& s)
{
	//从缓冲区获取字符
	char ch = in.get();
	while (ch != '\n')
	{
		s += ch;
		//记录上一次位置,拿到下一个字符
		ch = in.get();
	}
	return in;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SSM知识快速复习

SSM知识快速复习SpringIOCDIIOC容器在Spring中的实现常用注解Autowired注解的原理AOP相关术语作用动态代理实现原理事务Transactional事务属性&#xff1a;只读事务属性&#xff1a;超时事务属性&#xff1a;回滚策略事务属性&#xff1a;事务隔离级别事务属性&#xff1a;事务…

JSP+ACCESS网上拍卖平台系统

技术&#xff1a;Java、JSP等摘要&#xff1a;电子商务就是通过互联网来进行的各项商务活动&#xff0c;包括广告、交易、支付、服务等活动。电子商务源于英文Electronic Commerce,简写为EC或E-Commerce。它是人类社会、经济、科学、文化发展的必然产物&#xff0c;是信息化社会…

【计算机考研408】置换选择排序 + 代码 PAT 甲级 1171 Replacement Selection

王道-置换选择排序b站教学视频 置换选择排序的流程 图片比文字更好理解&#xff0c;故不加文字解释 当当前工作区已经满了并且找不到合适的放入当前归并段的元素的时候&#xff0c;开始生成下一个归并段。 此后重复上述过程。 计算机考研可能考察置换选择排序的知识点 …

网易蜗牛读书产品体验报告(1.9.6版本)

本文导览本文是网易蜗牛读书的产品体验报告&#xff0c;在移动阅读市场&#xff0c;网易蜗牛读书以其独特的深度阅读和领读人的阅读机制吸引了一大批的阅读爱好者和忠实用户。本文通过对网易蜗牛读书的体验分析&#xff0c;希望进一步加深对网易蜗牛读书产品的认识了解。产品概…

详细的IO面试题汇总

IO 流简介 IO 即 Input/Output&#xff0c;输入和输出。数据输入到计算机内存的过程即输入&#xff0c;反之输出到外部存储&#xff08;比如数据库&#xff0c;文件&#xff0c;远程主机&#xff09;的过程即输出。数据传输过程类似于水流&#xff0c;因此称为 IO 流。IO 流在…

pom依赖产生的各种问题

文章目录问题一(org.apache.ibatis.session.Configuration)解决方法问题二(ERROR StatusLogger No log4j2)解决方法问题三(com.google.common.util.concurrent)解决方法问题四(start bean documentationPluginsBootstrapper)解决方法问题五(Unable to infer base url. )解决办法…

(01)Unity 中使用 HDRP

概述Unity在2019.2版本中推出HDRP&#xff08;高清渲染管线&#xff09;&#xff0c;目的是为了提高图形质量&#xff0c;实现从照片写实到风格化的图像。先看一下官方对HDRP的概述&#xff1a;高清渲染管线 &#xff08;HDRP&#xff09; 是由 Unity 构建的高保真脚本化渲染管…

【golang/go语言】Go语言之反射

本文参考了李文周的博客——Go语言基础之反射。 一、反射初识 1. 什么是反射 在计算机科学中&#xff0c;反射是指计算机程序在运行时&#xff08;run time&#xff09;可以访问、检测和修改它本身状态和行为的一种能力。用比喻来说&#xff0c;反射就是程序在运行的时候能够…

【genius_platform软件平台开发】第八十八讲:arm公司的V架构和内核发展史(整理)

1. ARM公司简介 ARM&#xff08;Advanced RISC Machines&#xff09;有三种含义&#xff1a;它是一个公司的名称、它是一类微处理器的通称、它是一种技术的名称。 ARM 公司是微处理器行业的一家知名企业&#xff0c;它是知识产权供应商&#xff0c;设计基于ARM体系的处理器&a…

5-Azidopentanoic acid,79583-98-5,5-Azidopentanoic COOH具有高效稳定,高特异性

5-Azidopentanoic acid&#xff0c;5-Azidopentanoic COOH&#xff0c;5-叠氮基戊酸产品规格&#xff1a;1.CAS号&#xff1a;79583-98-52.分子式&#xff1a;C5H9N3O23.分子量&#xff1a;143.074.包装规格&#xff1a;1g&#xff0c;5g&#xff0c;10g&#xff0c;包装灵活&a…

Spring Data JPA 之 理解 Persistence Context 的核心概念

21 理解 Persistence Context 的核心概念 21.1 Persistence Context 相关核心概念 21.1.1 EntityManagerFactory 和 Persistence Unit 按照 JPA 协议⾥⾯的定义&#xff1a;persistence unit 是⼀些持久化配置的集合&#xff0c;⾥⾯包含了数据源的配置、EntityManagerFacto…

WideDeep模型

google提出的Wide&deep模型&#xff0c;将线性模型与DNN很好的结合起来&#xff0c;在提高模型泛化能力的同时&#xff0c;兼顾模型的记忆性。wide&deep这种将线性模型与DNN的并行连接模式&#xff0c;后来称为推荐领域的经典模式&#xff0c;奠定了后面深度学习模型的…

Containerd容器运行时将会替换Docker?

文章目录一、什么是Containerd&#xff1f;二、Containerd有哪些功能&#xff1f;三、Containerd与Docker的区别四、Containerd是否会替换Docker&#xff1f;五、Containerd安装、部署和使用公众号&#xff1a; MCNU云原生&#xff0c;欢迎微信搜索关注&#xff0c;更多干货&am…

一条 SQL 查询语句是如何执行的?

MySQL是典型的C/S架构&#xff08;客户端/服务器架构&#xff09;&#xff0c;客户端进程向服务端进程发送一段文本&#xff08;MySQL指令&#xff09;&#xff0c;服务器进程进行语句处理然后返回执行结果。 问题来了。服务器进程对客户端发送的请求究竟做了什么处理呢&#…

【大数据基础】Hadoop3.1.3安装教程

来源&#xff1a; https://dblab.xmu.edu.cn/blog/2441/ 前言&#xff1a;重装解决一切bug&#xff01;事实上&#xff0c;问题中的绝大部分衍生问题都可以通过重装解决。 实验内容 创建Hadoop用户 首先按 ctrlaltt 打开终端窗口&#xff0c;输入如下命令创建新用户 : sudo…

【Spring6】| Spring对IoC的实现(核心重点)

目录 一&#xff1a;Spring对IoC的实现 1. IoC 控制反转 2. 依赖注入 2.1 set注入 2.2 构造注入 3. set注入专题 3.1 注入外部Bean 3.2 注入内部Bean 3.3 注入简单类型 3.4 级联属性赋值&#xff08;了解&#xff09; 3.5 注入数组 3.6 注入List集合和Set集合 3.7…

17- TensorFlow中使用Keras创建模型 (TensorFlow系列) (深度学习)

知识要点 Keras 是一个用 Python 编写的高级神经网络 API数据的开方: np.sqrt(784) # 28代码运行调整到 CPU 或者 GPU: import tensorflow as tf cputf.config.list_physical_devices("CPU") tf.config.set_visible_devices(cpu) 模型显示: model.summary()…

Tik Tok品牌营销,如何做好内容打法

TikTok 上做好品牌营销&#xff0c;并不能只关注品牌所获得的视频浏览量和点赞量&#xff0c;根据潜在客户需求生成的内容策略同样至关重要。通过建立营销漏斗模型&#xff0c;可以将 TikTok 策略分为三种不同类型的内容&#xff0c;从具有广泛吸引力的内容转变为具有高度针对性…

Vue组件是怎样挂载的

我们先来关注一下$mount是实现什么功能的吧&#xff1a; 我们打开源码路径core/instance/init.js: export function initMixin (Vue: Class<Component>) {......initLifecycle(vm)// 事件监听初始化initEvents(vm)initRender(vm)callHook(vm, beforeCreate)initInject…

FastDDS-3. DDS层

3. DDS层 eProsima Fast DDS公开了两个不同的API&#xff0c;以在不同级别与通信服务交互。主要API是数据分发服务&#xff08;DDS&#xff09;数据中心发布订阅&#xff08;DCPS&#xff09;平台独立模型&#xff08;PIM&#xff09;API&#xff0c;简称DDS DCPS PIM&#xf…