STL之string类的模拟实现

news2025/2/21 2:32:03

目录

1. string的成员变量

2. string的成员函数 

2.1 string类的c_str()和swap()函数

2.2 string类的构造 

 2.3 string类的拷贝构造

2.3.1传统写法:

2.3.2现代写法: 

2.4string类的运算符重载 

 2.4.1传统写法:

2.4.2现代写法 

2. 5 string析构函数 

2.6 string的迭代器 

2.7 string的容量操作 

2.7.1. 有效长度与容量大小 

2.7.2. 容量操作 

2.8. string的访问操作 

2.9 string的修改操作 

2.9.1. 字符串的添加

2.9.2. 字符串的删除 

3.0 find()与substr()函数。

3.1. string的非成员函数


💓 博客主页:C-SDN花园GGbond

⏩ 文章专栏:玩转c++

为了让我们更加深入理解string,接下来我们将模拟实现一个·简易版的string。而为了和STL库中的string以示区分,我们将使用命名空间HTD对其封装。 

1. string的成员变量

string简单来说就是一个被封装可动态增长的字符数组,这与我们在数据结构中学的串非常类似,所以我们可以借助实现串的思路来大致模拟string的结构。 

下面是string的成员变量:

namespace betty
{
	class string 
    {
	public:
		//...
	private:
        char* _str;//存储的字符串
		size_t _size;//当前有效字符的个数
		size_t _capacity;//当前容量的大小,方便扩容
	};
}

注意的是\0既不占据有效长度的大小,也不占据容量的大小。 

2. string的成员函数 

2.1 string类的c_str()和swap()函数

在实现基本的构造,拷贝构造,赋值运算符重载,析构,和其他成员函数之前我们先实现c_str() 便于后续在类外使用_str成员变量来打印字符串的测试和swap()便于进行实现拷贝构造和赋值运算符重载现代写法

注意:非成员swap()函数在底层实际也是调用成员函数swap()函数

const char* c_str()const
{
	return _str;
}
//交换字符串
void swap(string& s)
{
	std::swap(_str, s._str);//浅拷贝,没有开空间,只是改变指针指向
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

2.2 string类的构造 
class string
{
public:
	//无参构造
	/*string()
	{
		_str = nullptr;
		_size = _capacity = 0;
	}*/
	string(const char* str = "")
	{
		_size = strlen(str);
		_capacity = _size;
		_str = new char[_capacity + 1];
		strcpy(_str, str);
	}
private:
	char* _str;
	size_t _size;
	size_t _capacity;

};

 2.3 string类的拷贝构造
2.3.1传统写法:

传统的思路就是拷贝,也就是我们先根据被拷贝的对象的_capacity开空间,然后再进行拷贝 

//拷贝构造(深拷贝)
string(const string& s)
	:_size(s._size)
	, _capacity(s._capacity)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
}
2.3.2现代写法: 

现代的思路就是,尝试去复用,比如说我们可不可以直接去利用前面的构造函数去构造一个新对象,然后再窃取新对象的成果(利用swap)

string(const string&s)
	:_str(nullptr)
{
	string tmp(s._str);
	swap(tmp);
}
2.4string类的运算符重载 
 2.4.1传统写法:

传统的思路就是,先释放掉原空间,开跟s一样的空间   拷贝字符串,更改新的_szie,_capacity

//赋值运算符重载传统写法
string& operator=(const string& s)
{
	if (this != &s)
	{
		delete[]_str;
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;

	}
	return *this;
}

注意:要注意自赋值情况!!否则s就被释放了 

2.4.2现代写法 

 现代的思路就是,既然被赋值这个空间不想要,那就和形参直接交换吧!!但是要注意的是,这里就不能像传统的一样用const引用了,否则不想要的空间就给到我们的赋值对象了,这边就得用传值传参,这样被交换的就只是一个临时拷贝,不想要的空间随着栈帧的结束被销毁。 

//赋值运算符重载现代写法
string& operator=(string s)
{
	swap(s);
	return *this;
}
2. 5 string析构函数 

 们实现析构函数,只需要清理资源即可

//析构函数
~string()
{
	delete[]_str;
	_size = _capacity = 0;
}
2.6 string的迭代器 

首先我们来模拟实现一下迭代器iterator,而在string中迭代器iterator就是一个指针。所以我们直接使用typedef实现

 

typedef char* iterator;
typedef const char* const_iterator;

接下来我们来实现begin()end(),其中begin()指向的是字符串的起始位置即_strend()指向有效字符最后的下一位即\0的位置。 

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

实现完普通迭代器之后,我们可以顺便重载一个const_iterator的版本。 

const_iterator begin() const
{
	return _str;
}

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

我们知道在string中还有一个反向迭代器,这个我们在之后会统一实现。 

2.7 string的容量操作 
2.7.1. 有效长度与容量大小 

首先我们先实现返回字符串有效长度的size() 与容量大小的capacity()。并且为了适配const对象,最后用const修饰this指针。

size_t size() const
{
	return _size;
}
size_t capacity() const
{
	return _capacity;
}
2.7.2. 容量操作 
首先我们实现判断字符串是否为空的empty()以及清理字符串的clear()。其中emty()
不需要修改,可以加上const
void clear()
{
	_str[0] = '\0';
	_size = 0;//clear()函数只会将字符串的内容设置为空,而不会改变其容量不考虑capacity
}
bool empty()const
{
	return _size == 0;
}

接下来我们来实现扩容函数reserve()与·resize(),其中reserve()最简单,只要新容量大于旧容量就发生扩容。 

void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		char*tmp= new char[n + 1];
		memcpy(tmp, _str, _size);
		delete[]_str;
		_str = tmp;
		_capacity = n;
	}
}
void string::resize(size_t n)
{
	if (n > _size)
	{
		if (n > _capacity)
		{
			reserve(n);
		}
		memset(_str + _size, '\0', n - _size);
	}
	
		_size = n;
		_str[_size] = '\0';
	
}
2.8. string的访问操作 

为了符合我们C语言访问数组的习惯,我们可以先重载operator[]。当然我们也要提供两种不同的接口:可读可写与可读不可写。并且使用引用返回,减少不必要的拷贝。 

// 可读可写
char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}
// 可读不可写
const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}

同理我们也可以实现front()back()函数。 

// 可读可写
char& front()
{
	return _str[0];
}
char& back()
{
	return _str[_size - 1];
}
// 可读不可写
const char& front()const
{
	return _str[0];
}
const char& back()const
{
	return _str[_size - 1];
}
2.9 string的修改操作 
2.9.1. 字符串的添加

首先我们将实现两个常用的修改函数:push_back()append()

void push_back(char c)
{
	// 如果数据满了,则需要进行扩容
	if (_size == _capacity)
	{ 	
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size++] = c;
	_str[_size] = '\0';
}
// 追加字符串
void append(const char* str)
{
	int len = strlen(str);// 获取字符串的长度
	// 如果大于原来容量,则就需要扩容
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	// 将字符串拷贝到末尾的_size位置
	memcpy(_str + _size, str, len + 1);
	_size += len;
}

而后我们可以复用前两个函数实现operator+=()。 

//追加一个字符
string& operator+=(char ch)
{
	push_back(ch);
	return *this;
}
//追加一个字符串
string& operator+=(const char* s)
{
	append(s);
	return *this;
}

最后我们来实现随机插入insert()函数。将pos位置和pos后所有字符移动len个单位,如果为字符len=1,否则len=字符串长度 

//添加一个字符
void insert(size_t pos, char ch)
{
    //防止越界访问
	assert(pos <= _size);
    //检查是否需要扩容
	if (_size == _capacity)
	{
		size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newCapacity);
	}
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	_size++;
}
//添加一个字符串
void insert(size_t pos, const char* s)
{
    //防止越界访问
	assert(pos <= _size);
    //检查是否需要扩容
	size_t len = strlen(s);
	if (_size+len > _capacity)
	{
		reserve(_size+len);
	}
	size_t end = _size + len;
	while (end > pos)
	{
		_str[end] = _str[end - len];
		--end;
	}
	memcpy(_str + pos, s, len);
}
2.9.2. 字符串的删除 

 字符串的删除我们需要实现pop_back()erase()两个函数。

void pop_back()
{
	_str[_size - 1] = '\0';
    --_size;
}

而随机删除erase()需要再定义一个静态类成员变量npos来实现,它为无符号数的-1,一般为整型的最大值

// 类内声明
static size_t npos;
// 类外初始化
size_t string::npos = -1;

pos位置后所有字符往前移动len个单位,如果为字符 len=1,否则len=字符串长度 

void erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
    //判断是否将后面字符之间删除完
	if (len == npos || pos + len >= _size)
	{
		_str[0] = '\0';
		_size = pos;
	}
	else
	{
        //往前移len个字符
		size_t begin = pos + len;
		while (begin <= _size)
		{
			_str[begin - len] = _str[begin];
			++begin;
		}
		_size -= len;
	}
}
3.0 find()substr()函数。
const char* c_str()const		
{		
    return _str;        
}
//找字符
size_t find(char ch, size_t pos = 0) const
{
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}
	return npos;
}
//找字符串
size_t find(const char* s, size_t pos = 0) const
{
	const char* p = strstr(_str + pos, s);
	if (p)
	{
		return p - _str;
	}
	return npos;
}
//截取一段字符串
string substr(size_t pos, size_t len = npos)
{
    string s;
    size_t end = pos + len;
    //判断是否截取到最后
    if (len == npos || pos + len >=_size)
    {
        len = _size - pos;
        end = _size;
    }
    //提前开辟空间
    s.reserve(len);
    for (size_t i = pos; i < end; i++)
    {
        s += _str[i];
    }
    return s;
}
3.1. string的非成员函数
bool operator<(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator==(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str())==0;
}
bool operator<=(const string& s1, const string& s2)
{
	return s1.c_str() == s2.c_str() || s1.c_str() < s2.c_str();
}
bool operator>(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) > 0;
}
bool operator>=(const string& s1, const string& s2)
{
	return s1.c_str() == s2.c_str() || s1.c_str() >s2.c_str();
}

 接下来让我们实现流插入operator<<()与流提取operator>>()。但是我们要注意普通istream对象无法提前空格与\n。这是我们就需要一个函数get()来提取

ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}
// 流提取
istream& operator>>(istream& in, string& s)
{
	s.clear();//先清空原字符串
	char ch = in.get();
	char buf[128];
	int i = 0;
	while (ch != '\n')//以换行为分隔符
	{
		buf[i++] = ch;
		// 为\0留空间
		if (i == 127)
		{
			buf[i] = '\0';
			s += buf;
			i = 0;
		}
		ch = in.get();
	}
	//将buf中剩余数据直接填入
	if (i != 0)
	{
		buf[i] = '\0';
		s += buf;
	}
	return in;
}

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

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

相关文章

深度学习笔记——LSTM

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍面试过程中可能遇到的LSTM知识点。 文章目录 LSTM&#xff08;Long Short-Term Memory&#xff09;LSTM 的核心部件LSTM 的公式和工作原理(1) 遗忘门&a…

基于SpringBoot的“食物营养分析与推荐网站”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“食物营养分析与推荐网站”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 系统首页界面 系统注册…

建筑兔零基础自学python记录22|实战人脸识别项目——视频人脸识别(下)11

这次我们继续解读代码&#xff0c;我们主要来看下面两个部分&#xff1b; 至于人脸识别成功的要点我们在最后总结~ 具体代码学习&#xff1a; #定义人脸名称 def name():#预学习照片存放位置path M:/python/workspace/PythonProject/face/imagePaths[os.path.join(path,f) f…

React之旅-02 创建项目

创建React项目&#xff0c;常用的方式有两种&#xff1a; 官方提供的脚手架&#xff0c;官网&#xff1a;https://create-react-app.dev/。如需创建名为 my-app 的项目&#xff0c;请运行如下命令&#xff1a; npx create-react-app my-app 使用Vite包&#xff0c;官网&…

uniapp 滚动尺

scale组件代码&#xff08;部分class样式使用到了uview1.0的样式&#xff09; <template><view><view class"scale"><view class"pointer u-flex-col u-col-center"><u-icon name"arrow-down-fill" size"26&qu…

Windows 启动 SSH 服务

Windows 启动 SSH 服务 一、OpenSSH Server 安装 以 Win10 系统为例 打开设置 -> 系统 -> 可选功能 在 添加的功能 查看是否安装了 OpenSSH 服务 或者 OpenSSH Server 如果没有安装&#xff0c;找到 系统->添加可选功能 -> 查看功能->搜索 OpenSSH 服务 ->…

MySQL 的存储引擎有哪些?它们之间有什么区别? MySQL InnoDB 引擎中的聚簇索引和非聚簇索引有什么区别? MySQL 的索引类型有哪些?

MySQL 的存储引擎有哪些&#xff1f;它们之间有什么区别&#xff1f; 先来回顾以下我们业务场景下一般的数据库访问的过程应用——>server层 ——>存储引擎层——>磁盘 官网描述&#xff1a; InnoDB: MySQL 8.4 中的默认存储引擎。 InnoDB 是事务安全&#xff08;符…

【Linux探索学习】第二十六弹——进程通信:深入理解Linux中的进程通信

Linux探索学习&#xff1a; https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言&#xff1a; 在Linux操作系统中&#xff0c;进程通信&#xff08;IPC&#xff09;是操作系统的一项核心功能&#xff0c;用于在不同进程之间交换数据或…

netcore https配置

一、生成证书 1. 安装 OpenSSL 如果尚未安装 OpenSSL&#xff0c;可以通过以下命令安装&#xff1a;Ubuntu/Debian:sudo apt update sudo apt install openssl CentOS/RHEL:sudo yum install openssl 2. 生成私钥 使用以下命令生成私钥文件&#xff08;private.key&#xff09…

遥感影像目标检测:从CNN(Faster-RCNN)到Transformer(DETR)

我国高分辨率对地观测系统重大专项已全面启动&#xff0c;高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成&#xff0c;将成为保障国家安全的基础性和战略性资源。未来10年全球每天获取的观测数据将超过10PB&#xff0c;遥感大数据时…

使用vscode调试transformers源码

简要介绍如何使用vscode调试transformers源码 以源码的方式安装transformers&#xff08;官方手册为Editable install&#xff09; 优先参考官方手册 git clone https://github.com/huggingface/transformers.git cd transformers pip install -e .以下展示transformers/exa…

LeetCode39

LeetCode39 目录 题目描述示例思路分析代码段代码逐行讲解复杂度分析总结的知识点整合总结 题目描述 给定一个无重复元素的整数数组 candidates 和一个目标整数 target&#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复选…

PostgreSQL 添加索引导致崩溃,参数调整需谨慎--文档未必完全覆盖场景

开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, OceanBase, Sql Server等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;&#xff08;共2720人左右 1 …

【Linux Redis】关于用docker拉取Redis后,让虚拟机运行起来redis,并使得其可以连接到虚拟机外的navicat。

步骤一&#xff1a;拉取Redis镜像 docker pull redis 这个命令会下载最新版本的Redis镜像到你的本地Docker仓库中。你也可以指定一个具体的版本号&#xff0c;例如docker pull redis:6.2.6&#xff0c;来拉取特定版本的Redis镜像。 如果拉取遇到问题请参考【Linux AnolisOS】关…

【含文档+PPT+源码】基于微信小程序的校园志愿者管理系统的设计与实现

项目介绍 本课程演示的是一款 基于微信小程序的校园志愿者管理系统的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本…

LED灯闪烁实验:实验介绍

文章目录 1 实验目标2 工具链2.1 硬件2.2 软件 3 实验流程 1 实验目标 本实验结合Matlab/Simulink工具链和STM工具链&#xff0c;实现STM32开发板上的LED灯闪烁功能。 2 工具链 2.1 硬件 STM32F103C8T6最小系统板 STM32F103C8T6最小系统板是基于STM32F103C8T6微控制器的开发…

亲测Windows部署Ollama+WebUI可视化

一. Ollama下载 登录Ollama官网(Ollama)点击Download进行下载 如果下载很慢可用以下地址下载&#xff1a; https://github.com/ollama/ollama/releases/download/v0.5.7/OllamaSetup.exe 在DeepSeek官网上&#xff0c;你可以直接点击【model】 到达这个界面之后&#xff0c;…

Docker 部署 Nacos (图文并茂超详细)

部署 Nacos ( Docker ) [Step 1] : 拉取 Nacos 镜像 docker pull nacos/nacos-server:v2.1.1[Step 2] : 创建目录 ➡️ 创建容器 ➡️ 拷贝文件 ➡️ 授权文件 ➡️ 删除容器 # 创建目录 mkdir -p /data/nacos/{conf,logs,data}# 创建容器 docker run -p 8848:8848 --name …

petalinux高版本设置自动登录和开机自启动配置

petalinux-config -c rootfs 依次选择 Image Features -> serial-autologin-root 这是配置 进来就是root权限 创建并安装名为 myapp-init 的新建应用程序 petalinux-create -t apps --template install -n myapp-init --enable 编辑 project-spec/meta-user/recipes-…

MySQL 的存储引擎简介

使用show engines可以查看安装的MySQL的引擎相关信息 MySQL 的引擎不止这么多&#xff0c;这只是安装的这个版本有的引擎。可以看到&#xff0c;只有 InnoDB 支持事务&#xff0c;其他的引擎都是不支持事务的。 说几个我了解的比较多的引擎&#xff1a; InnoDB InnoDB 是MyS…