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

STL之string类的模拟实现

目录

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

相关文章:

STL之string类的模拟实现

目录 1. string的成员变量 2. string的成员函数 2.1 string类的c_str()和swap()函数 2.2 string类的构造 2.3 string类的拷贝构造 2.3.1传统写法&#xff1a; 2.3.2现代写法&#xff1a; 2.4string类的运算符重载 2.4.1传统写法&#xff1a; 2.4.2现代写法 2. 5 …...

定期自动统计大表执行情况

一、创建用户并赋权 create user dbtj identified by oracle default tablespace OGGTBS;grant connect,resource to dbtj;grant select any dictionary to dbtj;grant create job to dbtj;grant manage scheduler to dbtj; 二、创建存储表 1、连接到新建用户 conn dbtj/or…...

学习next.js的同时的一些英语单词记录

skip &#xff1a;跳过 optional&#xff1a;可选的 previous&#xff1a;以前的 lesson&#xff1a;课程 directory&#xff1a;目录 identical&#xff1a;相同的 instruction&#xff1a;说明 development server&#xff1a;开发服务器 client-side&#xff1a;客户…...

ok113i平台——qt+tslib支持usb触摸屏热插拔功能实现

问题&#xff1a;重新插拔设备&#xff0c;需要软件重启才能接收到触摸事件 愿因&#xff1a;是因为qt程序的tslib库的操作逻辑是在构造函数里面连接一次usb触摸设备&#xff0c;具体看如下文件内容&#xff1a; /home/forlinx/OK113i-linux-sdk/buildroot/buildroot-201902/dl…...

游戏引擎学习第112天

黑板&#xff1a;优化 今天的内容是关于优化的&#xff0c;主要讨论了如何在开发中提高代码的效率&#xff0c;尤其是当游戏的帧率出现问题时。优化并不总是要将代码做到最快&#xff0c;而是要确保代码足够高效&#xff0c;以避免性能问题。优化的过程是一个反复迭代的过程&a…...

深度学习笔记——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 系统展示 系统功能结构图 系统首页界面 系统注册…...

23种设计模式 - 工厂方法模式

模式定义 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;定义用于创建对象的接口&#xff0c;让子类决定实例化哪个类&#xff0c;从而将对象创建过程延迟到子类。其核心目的是解耦对象的创建与使用&#xff0c;增强系统的扩展…...

【ISO 14229-1:2023 UDS诊断(ECU复位0x11服务)测试用例CAPL代码全解析①】

ISO 14229-1:2023 UDS诊断【ECU复位0x11服务】_TestCase01 作者&#xff1a;车端域控测试工程师 更新日期&#xff1a;2025年02月16日 关键词&#xff1a;UDS诊断协议、ECU复位服务、0x11服务、ISO 14229-1:2023 TC11-001测试用例 用例ID测试场景验证要点参考条款预期结果TC…...

Discuz! X3.5 根目录权限设置

在 Discuz! X3.5 中,根目录的权限设置是确保网站安全性和功能正常运行的关键。如果权限设置不当,可能会导致文件无法访问、安全问题(如文件被篡改)或功能异常。以下是关于 Discuz! X3.5 根目录权限设置的详细说明和建议: 1. 根目录位置 Discuz! X3.5 的根目录通常是网站的…...

建筑兔零基础自学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…...

Redux中间件redux-thunk和redux-saga的具体区别是什么?

Redux 中间件是增强 Redux 功能的重要工具&#xff0c;redux-thunk 和 redux-saga 是两个常用的中间件&#xff0c;它们在处理异步操作和副作用时提供了不同的方式和理念。以下是两者的具体区别&#xff1a; 1. 概念与设计理念 redux-thunk 简洁&#xff1a;redux-thunk 是一…...

Windows 启动 SSH 服务

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

rust笔记1-学习资料推荐

学习Rust的Trait、生命周期和模式确实需要一些时间&#xff0c;尤其是当这些概念在其他语言中不常见时。以下是一些学习资料和建议&#xff0c;帮助你更好地理解这些概念&#xff1a; 1. 官方文档与书籍 《The Rust Programming Language》&#xff08;俗称“The Book”&…...

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;遥感大数据时…...

rtcwake - Linux下定时唤醒计算机

rtcwake 是一个用于通过实时时钟&#xff08;RTC&#xff09;唤醒计算机的工具。它常用于在 Linux 系统中设置计算机在指定时间自动唤醒或关闭。以下是对命令 rtcwake -m off -s ${sleep_time} 的详细解析&#xff1a; 命令解析 bash复制 rtcwake -m off -s ${sleep_time} 1…...

使用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 中的数字可以无限制重复选…...

sql not in 优化

sql优化 1、not in 的优化 not in和not exists不会命中索引&#xff0c;可以优化为通过left join实现&#xff1b; 例如要查询存在于a表但不存在与b表的数据&#xff0c;比较容易理解的sql写法&#xff1a; SELECT * FROM table_a WHERE id NOT IN (SELECT aid FROM table_b)…...

同花顺C++面试题及参考答案

对 C 和 C++ 哪个更熟悉? 在编程语言的学习与实践中,我对 C++ 更为熟悉。C 语言作为一门经典的编程语言,以其高效、灵活和接近硬件的特性,在系统编程、嵌入式开发等领域占据着重要地位。它提供了丰富的底层操作能力,如指针操作、内存管理等,为开发者直接控制计算机资源提…...

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】关…...

Python常见面试题的详解8

1. 变量作用域和查找规则&#xff08;LEGB&#xff09; 作用域层级&#xff1a; Local&#xff1a;函数内部作用域 Enclosing&#xff1a;闭包函数外层作用域 Global&#xff1a;模块全局作用域 Built-in&#xff1a;内置命名空间 查找顺序&#xff1a;L → E → G → B关…...

【含文档+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微控制器的开发…...