(C++)string类的模拟实现

news2025/2/22 3:37:02

  愿所有美好如期而遇


前言

我们模拟实现string类不是为了去实现他,而是为了了解他内部成员函数的一些运行原理和时间复杂度,在将来我们使用时能够合理地去使用他们。

为了避免我们模拟实现的string类与全局上的string类冲突(string类也在std命名空间中),我们用自己的命名空间包起来。

namespace A
{
    class string
    {
        static void test()
        {

        }
    }    
}

int main()
{
    A::string::test();
}

接下来我们开始模拟实现string类。

私有的成员变量,我们设计无符号整型计算有效字符数量的_size,以及记录空间大小的_capacity,最后就是字符指针,我们将来开辟好一块空间后存放字符串,将由他来指向。

namespace A
{
    class string
    {


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

构造函数:

默认是空串,并且我们默认比有效字符多开一个字节的空间,之后进行拷贝。

string(const char* str = "")
{
	_size = _capacity = strlen(str);
	_str = new char[_capacity + 1];

	strcpy(_str, str);
}

 拷贝构造函数:

我们模拟实现string类,一定会有空间的开辟,一但对象涉及到拷贝,就不能是浅拷贝,否则会出现一块空间两次释放,直接崩掉。

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

	strcpy(_str, str.c_str());
}

 赋值运算符重载:

同拷贝构造,对象也是不能值拷贝,否则会出现一块空间释放两次。
 

string& operator=(const string& str)
{
	if (this != &str)
	{
		_capacity = str._capacity;
		_size = str._size;

		char* tmp = new char[_capacity + 1];
		strcpy(tmp, str.c_str());

		delete[] _str;
		_str = tmp;
	}	

    return *this;
}

析构函数:

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

 将string转换为const char*,我们会在strlen等C的接口处使用,比如strlen(s.c_str()),因为strlen他的参数是const char*类型的。
 

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

 四个迭代器

只有物理地址空间连续才可以像这样,如果是链表,我们不能这样做。

typedef char* iterator;
typedef const char* const_iterator;

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

const_iterator begin()const
{
	return _str;
}

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

 尾插单个字符,_capacity和_size都是只考虑有效字符,我们在创建空间时默认开空间比_capacity大一。

void push_back(char ch)
{
	if (_size == _capacity)
	{
		_capacity = 0 ? 4 : _capacity * 2;

		char* tmp = new char[_capacity + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
	}

	_str[_size++] = ch;
	_str[_size] = '\0';
}

 reserve函数,我们使用reserve重新开空间,也是只考虑有效字符。

我们实现的reserve不可以缩容。

void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = _str;
		//比需求多开一个字节的空间
		_str = new char[n + 1];
		strcpy(_str, tmp);
        //别忘记_capacity
		_capacity = n;
		delete[] tmp;
	}
}

 尾插字符串

void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

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

 赋值运算符+=的重载

//三个版本的+=运算符重载
string& operator+=(const string& str)
{
	append(str.c_str());
	return *this;
}

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

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

 成员函数swap

//不用进行深拷贝,直接交换地址和大小
void swap(string& s)
{
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
	std::swap(_str, s._str);
}

清空字符串函数

void clear()
{
	memset(_str, 0, _size);
}

返回有效字符数量 ,判断是否为空

size_t size()const
{
	return _size;
}

bool empty()const
{
	return _size == 0;
}

重载[]运算符,我们重载两个版本的,重载const版本是因为我们有时候只读,不写,所以需要这样的重载。

char& operator[](size_t index)
{
	return _str[index];
}

const char& operator[](size_t index)const
{
	return _str[index];
}

 find函数,找到后返回下标。strstr找到后返回的是找到的字符串的首地址,否则返回空。

size_t find(char c, size_t pos) const
{
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == c)
		{
			return i;
		}
	}

	return npos;
}

//hello world
size_t find(const char* s, size_t pos = 0) const
{
	char* tmp = strstr(_str, s);
	if (tmp == nullptr)
	{
		return npos;
	}

	return tmp - _str;
}

insert函数,从pos位置插入单个字符,以及字符串

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

	if (_size == _capacity)
	{
		_capacity = 0 ? 4 : _capacity * 2;

		char* tmp = new char[_capacity + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
	}

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

	return *this;
}

//pos是下标
string& insert(size_t pos, const char* str)
{
	assert(pos <= _size);

	int len = strlen(str);
	if (len + pos > _capacity)
	{
		reserve(len + pos);
	}

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

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

	return *this;
}

erase函数,删除从pos节点开始的len个字符

string& erase(size_t pos, size_t len)
{
	//如果pos == size就和没删除一样,相当于删\0
	assert(pos < _size);

	//从pos位置删除len个字符,也就是pos后的字符全部删掉
	//传的len值接近npos,pos+len可能溢出,也会有未知错误
	if (len == npos || pos + len >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}

	return *this;
}

substr,截取从pos位置开始长度为len的字符串 ,在他返回时,就是对象的值拷贝,会有临时对象产生,临时对象就需要我们自己写的拷贝构造函数了。

string substr(size_t pos = 0, size_t len = npos)
{
	assert(pos < _size);

	int end;
	string str;

	//hello world 2 3
	if (len == npos || pos + len > _size)
	{
		end = _size;
	}
	else
	{
		end = pos + len;
	}

	//记得给str扩容
	str.reserve(end - pos);
	while (end > pos)
	{
		str += _str[pos++];
	}

	return str;
}

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

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

相关文章

Redis序列化操作

目录 1.protostuff 的 Maven 依赖 2.定义实体类 3.序列化工具类 ProtostuffSerializer 提供了序列化和反序列化方法 4.测试 利用 Jedis 提供的字节数组参数方法&#xff0c;如&#xff1a; public String set(String key, String value) public String set(byte[] key…

【Springboot系列】SpringBoot整合WebSocket,既然如此简单(含源码)

文章目录 前言&#xff1a;什么是WebSocket&#xff1f;Spring Boot中的WebSocket支持WebSocket和HTTP优劣势WebSocket的优势&#xff1a;1.实时性&#xff1a;2.较低的延迟&#xff1a;3.较小的数据传输量&#xff1a;4.更好的兼容性&#xff1a; HTTP的优势&#xff1a;1.简单…

HDMI接口信号流向及原理图分析

1、HDMI的来源及发展 如今显示器上最常用的接口无非HDMI&#xff08;High Definition Multimedia Interface&#xff09;与DP&#xff08;DisplayPort&#xff09;两种&#xff0c;VGA与DVI已经很少使用&#xff0c;原因在于VGA传输的是模拟信号&#xff0c;在发送端需要将数字…

如果客户端同时有ipv4和ipv6,浏览器是如何选择用哪种ip

在互联网协议&#xff08;IP&#xff09;的发展历程中&#xff0c;IPv4和IPv6是两种主要的版本。对于一个客户端来说&#xff0c;同时拥有IPv4和IPv6的能力是常见的情况。那么&#xff0c;当一个客户端同时具有IPv4和IPv6的能力时&#xff0c;浏览器是如何选择使用哪种IP进行通…

数组栈的实现

1.栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作 进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底 栈中的数据元素遵守后进先出LIFO,&#xff08;Last In First Out&#xff09;的原则 压栈&…

Redis-Redis多级缓存架构

多级缓存架构 缓存设计 缓存穿透 缓存穿透是指查询一个根本不存在的数据&#xff0c; 缓存层和存储层都不会命中&#xff0c; 通常出于容错的考虑&#xff0c; 如果从存储层查不到数据则不写入缓存层。 缓存穿透将导致不存在的数据每次请求都要到存储层去查询&#xff0c; 失…

NX二次开发UF_CURVE_ask_offset_parms 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_offset_parms Defined in: uf_curve.h int UF_CURVE_ask_offset_parms(tag_t offset_curve_object, UF_CURVE_offset_data_p_t offset_data_pointer ) overview 概述 …

从0开始学习JavaScript--JavaScript中的对象原型

JavaScript中的对象原型是理解该语言核心概念的关键之一。本文将深入探讨JavaScript对象原型的作用、使用方法以及与继承相关的重要概念。通过详细的示例代码和全面的讲解&#xff0c;将能够更好地理解和运用JavaScript对象原型&#xff0c;提高代码的可维护性和扩展性。 Java…

windows下docker环境搭建与运行实战

背景 学习docker使用&#xff0c;需要环境&#xff0c;今天主要的目标是在windows环境下安装docker环境。 为什么要这么搞&#xff0c;主要是企业内部服务器&#xff0c;都是跟公网隔离的&#xff0c;没有访问公网权限&#xff0c;所以镜像什么的&#xff0c;从公网拉取完全没…

31 - MySQL调优之SQL语句:如何写出高性能SQL语句?

从今天开始&#xff0c;我将带你一起学习 MySQL 的性能调优。MySQL 数据库是互联网公司使用最为频繁的数据库之一&#xff0c;不仅仅因为它开源免费&#xff0c;MySQL 卓越的性能、稳定的服务以及活跃的社区都成就了它的核心竞争力。 我们知道&#xff0c;应用服务与数据库的交…

C语言:写一个函数,实现3*3矩阵的转置(指针)

分析&#xff1a; 在主函数 main 中&#xff0c;定义一个 3x3 的整型数组 a&#xff0c;并定义一个指向整型数组的指针 p。然后通过循环结构和 scanf 函数&#xff0c;从标准输入中读取用户输入的 3x3 矩阵的值&#xff0c;并存储到数组 a 中。 接下来&#xff0c;调用 mov…

MySQL的undo log 与MVCC

文章目录 概要一、undo日志1.undo日志的作用2.undo日志的格式3. 事务id&#xff08;trx_id&#xff09; 二、MVCC1.版本链2.ReadView3.REPEATABLE READ —— 在第一次读取数据时生成一个ReadView4.快照读与当前读 小结 概要 Undo Log&#xff1a;数据库事务开始之前&#xff0…

构建SQL Server链接服务器:实现跨服务器数据访问及整合

点击上方蓝字关注我 在SQL Server数据库管理中&#xff0c;链接服务器是一项强大的功能&#xff0c;允许在一个SQL Server实例中访问另一个SQL Server实例的数据。这种功能为数据库管理员提供了灵活性&#xff0c;使其能够跨不同服务器进行数据交互&#xff0c;开辟了更多的应用…

realname,soname和linkname

背景 当在看/lib下的一些文件时候&#xff0c;我们发现几乎都是三个动态库文件&#xff0c;为啥&#xff1f; 分析 当我发布一个动态库的时候&#xff0c;比如版本是maj.min.patch&#xff08;10.2.1&#xff09;的格式&#xff0c;当我改了小版本的号的时候&#xff08;10…

牛客网刷题笔记四 链表节点k个一组翻转

NC50 链表中的节点每k个一组翻转 题目&#xff1a; 思路&#xff1a; 这种题目比较习惯现在草稿本涂涂画画链表处理过程。整体思路是赋值新的链表&#xff0c;用游离指针遍历原始链表进行翻转操作&#xff0c;当游离个数等于k时&#xff0c;就将翻转后的链表接到新的链表后&am…

【Flutter 常见问题系列 第 1 篇】Text组件 文字的对齐、数字和字母对齐中文

TextStyle中设置height参数即可 对齐的效果 Text的高度 是根据 height 乘于 fontSize 进行计算的、这里指定heiht即可、不指定的会出现 无法对齐的情况&#xff0c;如下&#xff1a; 这种就是无法对齐的情况

成为AI产品经理——模型评估(混淆矩阵)

一、混淆矩阵 1.混淆矩阵的介绍 混淆矩阵有两个定义positive&#xff08;正例&#xff09;和negative&#xff08;反例&#xff09;。分别代表模型结果的好和坏。 下图就是一个分类问题的混淆矩阵。横行代表真实的情况&#xff0c;而竖行代表预测的结果。 为了便于理解&…

原神「神铸赋形」活动祈愿现已开启

亲爱的旅行者&#xff0c;「神铸赋形」活动祈愿现已开启&#xff0c;「单手剑静水流涌之辉」「法器碧落之珑」概率UP&#xff01; 活动期间&#xff0c;旅行者可以在「神铸赋形」活动祈愿中获得更多武器与角色&#xff0c;提升队伍的战斗力&#xff01; 〓祈愿时间〓 4.2版本更…

VS2022 17.8: Build Insights 中的函数视图

简述 今天&#xff0c;我们很高兴的宣布在 Visual Studio 中为 Build Insights 新增了一项新功能&#xff1a;函数视图&#xff08;Functions View&#xff09;。 此功能在 Visual Studio 2022 v17.8 版本中可用。函数视图可以为你的代码库中的函数和强制内联&#xff08;For…

C++学习之路(四)C++ 实现简单的待办事项列表命令行应用 - 示例代码拆分讲解

本期示例介绍: 本期示例《待办事项列表应用》展示了一个简单的任务管理系统&#xff0c;用户可以通过命令行界面执行添加任务、删除任务和显示任务列表等操作。 功能描述&#xff1a; 添加任务功能&#xff1a; 用户可以输入任务描述&#xff0c;将新的任务添加到任务列表中。…