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

【C++修行之路】STL——模拟实现string类

文章目录

  • 前言
    • 类框架
    • 构造与析构
    • c_str
    • 迭代器
    • 操作符重载
      • []:
      • =:
      • > == >= < <= !=:
    • reverse与resize
      • reverse
      • resize
    • push_back与append
    • 复用实现+=
    • insert和erase
    • c_str与流插入、流提取
    • erase
    • swap(s1,s2)与s1.swap(s2)
    • 结语

前言

这次我们分几个部分来实现string类。具体请看目录。
说明:模拟实现只实现了string中最常用的功能。

类框架

首先我们要与库里的string类区分,因此我们定义一个命名空间,名字可以随意起,这里教学因此命名为Teacher

string.h:
namespace Teacher
{class string{public://函数实现private:char* _str;int _size;int _capacity;};
}

约定:在我们模拟实现过程中,_str存储字符串内容,_size表示现在字符串的大小(不包括\0)_capacity表示字符串一共有多大空间(不包括\0)
在这里插入图片描述

构造与析构

空字符串可以直接用缺省值处理,我们不必再写一个空参构造函数。

//函数实现
string(const char* str = "") : _size(strlen(str))
{_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);
}~string()
{delete[] _str;_str = nullptr;_capacity = _size = 0;
}

c_str

迭代器

用指针来模拟实现一下迭代器,唯一需要注意的就是一定要写成iterator和const_iterator,不然在使用范围for的时候会报错。

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

操作符重载

[]:

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

=:

赋值时,由于我们不知道子母串大小情况,所以我们把要覆盖的串直接清空,再把新的内容腾上去,但这又涉及一个问题,如果清空后覆盖失败怎么办?这样我们不仅没有拷贝成功,还失去了原有的串。因此我们采用一个临时数组先将我们的内容拷贝到这个临时串上,再清理原来的串。
代码如下:

string& operator=(const string& s)
{if (this != &s){_size = s._size;_capacity = s._capacity;char* tmp = new char[_capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;}return *this;
}

> == >= < <= !=:

由于和我们实现日期类的基本思路一致,我们按照原来的思路书写即可:

bool operator>(const string& s) const
{return strcmp(_str, s._str);
}
bool operator==(const string& s) const
{return strcmp(_str, s._str) == 0;
}
bool operator>=(const string& s) const
{return _str > s._str || _str == s._str;
}
bool operator<(const string& s) const
{return !(_str >= s._str);
}
bool operator<=(const string& s) const
{return !(_str > s._str);
}
bool operator!=(const string& s) const
{return !(_str == s._str);
}

reverse与resize

reverse

当我们对string对象进行增加操作时(不管是追加一个字符还是追加一个串)我们都需要判断当前对象的容量还够不够,如果不够,我们就要按需要扩容。至于啥时候需要扩容,我们在需要的函数里再判断。reverse只管扩容。

void reserve(size_t newsize)
{char* tmp = new char[newsize + 1];//按照我们的约定容量不包括\0,因此我们在这里加上strcpy(tmp,_str);delete[] _str;_str = tmp;_capacity = newsize;
}

resize

没什么好说的,注意缺省值是如何使用的即可

void resize(size_t n, char ch = '\0')
{if (n < _size){// 删除数据--保留前n个_size = n;_str[_size] = '\0';}else if (n > _size){if (n > _capacity){reserve(n);}size_t i = _size;while (i < n){_str[i] = ch;++i;}_size = n;_str[_size] = '\0';}
}

push_back与append

实现了reverse函数之后,我们就可以实现push_back和append函数了
push_back:

void push_back(char ch)
{if (_size + 1 > _capacity){reserve(_size * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';
}

append:

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

如果你对其中判断是否需要扩容感到疑惑,建议你再想想我们之前对_size和_capacity的约定。

复用实现+=

因为我们已经写好了上面的接口,直接复用即可。

string& operator+=(const char ch)
{push_back(ch);
}
string& operator+=(const char* str)
{append(str);
}

insert和erase

insert:可以在任意位置插入
在这里提供两个思路,但由于一些边界问题,第一个思路你要考虑判断坐标是否合法。
在这里插入图片描述

void insert(size_t pos, const char ch)
{//在pos处插入一个字节if (_size + 1 > _capacity){reserve(_capacity * 2);}size_t end = _size;//无符号数会出越界的bugwhile (end >= pos && end != -1){_str[end + 1] = _str[end];end--;}_str[pos] = ch;_size++;
}void insert2(size_t pos, const char ch)
{//在pos处插入一个字节if (_size + 1 > _capacity){reserve(_capacity * 2);}size_t end = _size+1;while (end > pos){_str[end] = _str[end-1];end--;}_str[pos] = ch;_size++;
}

erase:删除一部分数据,注意这里给出了npos的缺省值,我们在类成员函数处加上即可。

static const size_t npos;const size_t npos = -1;

上面这种给静态变量赋值的方式只能赋值成int,其他类型均不可以。

void erase(size_t pos, size_t len = npos)
{int begin = pos + len;while (begin <= _size){_str[begin - len] = _str[begin];begin++;}_size -= len;
}

c_str与流插入、流提取

由于自定义的类型不能直接输出打印,因此我们要拿到对象内的字符数组,这样才能按照C语言的方式来打印字符串。
代码如下:

const char* c_str()
{return _str;
}

而流插入和流提取即重载两个操作符:
为什么需要重载流插入和流提取?
流提取:

ostream& operator<<(ostream& out, const string& s)
{//需要写迭代器for (auto ch : s){out << s;}return out;
}

流插入:

istream operator>>(istream& is,string& s)
{char ch = in.get()char buff[128];size_t i = 0;while(ch != ' ' && ch != '\n'){buff[i] = ch;i++;if(i == 127){buff[127] = '\0';s += buff;i = 0;}ch = in.get();}if(i != 0){buff[i] = '\0';s+=buff;}
}

erase

erase可以分两种情况考虑,即把pos后面全删了,还是删除pos后有限个元素,我们可以单独处理,具体详见代码:

void erase(size_t pos,size_t len = npos)
{if (pos + len >= _size || len == npos){_str[pos] = '\0';_size = pos;}else{strcpy(_str+pos,_str+pos+len);_size -= len;}
}

因为设计到在同一个字符串里用strcpy,我们这里不用担心会覆盖的问题,因为左边是我们要删除的,右边的是我们的源头,所以覆盖的是无用的元素。可以直接使用。

swap(s1,s2)与s1.swap(s2)

最后来谈一下交换两个对象的函数。
更推荐使用第二种,

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

如果选择第一种会怎么样?其实会进行三次拷贝构造,这样是非常低效的,详细如图,采用第二种会好很多。1
在这里插入图片描述

结语

到这里,本篇文章就到此为止了,我们下次见~

相关文章:

【C++修行之路】STL——模拟实现string类

文章目录前言类框架构造与析构c_str迭代器操作符重载[]&#xff1a;&#xff1a;> > < < !:reverse与resizereverseresizepush_back与append复用实现insert和erasec_str与流插入、流提取eraseswap(s1,s2)与s1.swap(s2)结语前言 这次我们分几个部分来实现string类…...

CorelDRAW2023最新版序列号使用教程

CorelDRAW2023用起来非常顺手&#xff0c;旨在为用户解决因在工作上带来的问题&#xff0c;在业内可谓享有极高的声誉&#xff0c;是业内人士常用的一款工具&#xff0c;有了它&#xff0c;可以更好的帮助用户把握好各个方面的细节&#xff0c;减少其他方面的失误&#xff0c;让…...

【一天一门编程语言】Python 语言程序设计极简教程

文章目录 Python 语言程序设计极简教程一、Python语言简介1.1 Python的优势1.2 Python的应用二、Python基础语法2.1 Python基础2.1.1 注释2.1.2 变量2.1.3 运算符2.1.4 控制流2.1.5 函数2.2 Python数据类型2.2.1 数字2.2.2 字符串2.2.3 列表2.2.4 元组2.2.4.1 元组的基本操作创…...

14、KL散度

KL 散度&#xff0c;是一个用来衡量两个概率分布的相似性的一个度量指标。 现实世界里的任何观察都可以看成表示成信息和数据&#xff0c;一般来说&#xff0c;我们无法获取数据的总体&#xff0c;我们只能拿到数据的部分样本&#xff0c;根据数据的部分样本&#xff0c;我们会…...

TypeError: load() missing 1 required positional argument: ‘Loader‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…...

【设计模式】 观察者模式介绍及C代码实现

【设计模式】 观察者模式介绍及C代码实现 背景 在软件构建过程中&#xff0c;我们需要为某些对象建立一种“通知依赖关系”&#xff0c;即一个对象&#xff08;目标对象&#xff09;的状态发生改变&#xff0c;所有的依赖对象&#xff08;观察者对象&#xff09;都将得到通知。…...

01-Maven基础-简介安装、基本使用(命令)、IDEA配置、(写jar,刷新自动下载)、依赖管理

文章目录0、Maven1、Maven 简介2、Maven 安装配置安装配置步骤3、Maven 基本使用Maven 常用命令Maven 生命周期IDEA 配置 MavenMaven 坐标详解IDEA 创建 Maven 项目IDEA 导入 Maven 项目配置 Maven-Helper 插件 (非常实用的小插件)依赖管理使用坐标导入 jar 包依赖范围0、Maven…...

一、前端稳定性规约该如何制定

前言 稳定性是数学或工程上的用语&#xff0c;判别一系统在有界的输入是否也产生有界的输出。若是&#xff0c;称系统为稳定&#xff1b;若否&#xff0c;则称系统为不稳定。 前端稳定性的体系建设大约可以分为了发布前&#xff0c;发布后&#xff0c;以及事故解决后三个阶段…...

Docker(三)Docker网络

目录1 结论知识2 link3 自定义网络1 结论知识 每一个容器启动时都会被分配一个ip地址&#xff1b;宿主机可以ping通任何一个docker容器&#xff1b;启动docker之后&#xff0c;宿主机默认网卡docker0&#xff0c;启动容器在宿主机注册网卡&#xff0c;使用的evth-pair技术&…...

Js高级API

Decorator装饰器 针对属性 / 方法的装饰器 // decorator 外部可以包装一个函数&#xff0c;函数可以带参数function Decorator (type) {/*** 这里是真正的decorator* description: 装饰的对象的描述对象* target:装饰的属性所述类的原型&#xff0c;不是实例后的类。如果装饰…...

团队:在人身上,你到底愿意花多大精力?

你好&#xff0c;我是叶芊。 今天我们讨论怎么带团队这个话题&#xff0c;哎先别急着走&#xff0c;你可能跟很多人一样&#xff0c;觉得带团队离我还太远&#xff0c;或者觉得我才不要做管理&#xff0c;我要一路技术走到底&#xff0c;但是你知道吗&#xff1f;带团队做事&am…...

Linux-Poolkit提权

Linux-Poolkit提权 漏洞复现- Linux Polkit 权限提升漏洞&#xff08;CVE-2021-4034&#xff09; 0x00 前言 polkit是一个授权管理器&#xff0c;其系统架构由授权和身份验证代理组成&#xff0c;pkexec是其中polkit的其中一个工具&#xff0c;他的作用有点类似于sudo&#x…...

【React全家桶】React Hooks

React Hookshooks介绍useState(保存组件状态)useEffect()useCallback(记忆函数)useMemo() 记忆组件useRef(保存引用值)useReducer()useContext(减少组件层级)自定义hookshooks介绍 在react类组件&#xff08;class&#xff09;写法中&#xff0c;有setState和生命周期对状态进…...

CLIP论文阅读

Learning Transferable Visual Models From Natural Language Supervision 利用自然语言的监督信号学习可迁移的视觉模型 概述 迁移学习方式就是先在一个较大规模的数据集如ImageNet上预训练&#xff0c;然后在具体的下游任务上再进行微调。这里的预训练是基于有监督训练的&am…...

华为OD机试真题Python实现【身高排序】真题+解题思路+代码(20222023)

身高排序 题目 小明今年升学到了小学一年级, 来到新班级后,发现其他小朋友身高参差不齐, 然后就想基于各小朋友和自己的身高差,对他们进行排序, 请帮他实现排序 🔥🔥🔥🔥🔥👉👉👉👉👉👉 华为OD机试(Python)真题目录汇总 输入 第一行为正整数H…...

Spring Cache的使用--快速上手篇

系列文章目录 分页查询–Java项目实战篇 全局异常处理–Java实战项目篇 完善登录功能–过滤器的使用 更多该系列文章请查看我的主页哦 文章目录系列文章目录前言一、Spring Cache介绍二、Spring Cache的使用1. 导入依赖2. 配置信息3. 在启动类上添加注解4. 添加注解4.1 CacheP…...

(三十八)MySQL是如何支持4种事务隔离级别的?Spring事务注解是如何设置的?

上次我们讲完了SQL标准下的4种事务隔离级别&#xff0c;平时比较多用的就是RC和RR两种级别&#xff0c;那么在MySQL中也是支持那4种隔离级别的&#xff0c;基本的语义都是差不多的 但是要注意的一点是&#xff0c;MySQL默认设置的事务隔离级别&#xff0c;都是RR级别的&#x…...

【博学谷学习记录】大数据课程-学习第八周总结

Hadoop初体验 使用HDFS 1.从Linux本地上传一个文本文件到hdfs的/目录下 #在/export/data/目录中创建a.txt文件&#xff0c;并写入数据 cd /export/data/ touch a.txt echo "hello" > a.txt #将a.txt上传到HDFS的根目录 hadoop fs -put a.txt /2.通过页面查看…...

go cobra初试

cobra开源地址 https://github.com/spf13/cobra cobra是什么 Cobra is a library for creating powerful modern CLI applications. Cobra is used in many Go projects such as Kubernetes, Hugo, and GitHub CLI to name a few. This list contains a more extensive lis…...

【react全家桶】 事件处理

文章目录03 【事件处理】1.React事件2.类式组件绑定事件3.向事件处理程序传递参数4.收集表单数据5.受控和非受控组件5.函数的柯里化03 【事件处理】 React的事件是通过onXxx属性指定事件处理函数 React 使用的是自定义事件&#xff0c;而不是原生的 DOM 事件 React 的事件是通过…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

MyBatis中关于缓存的理解

MyBatis缓存 MyBatis系统当中默认定义两级缓存&#xff1a;一级缓存、二级缓存 默认情况下&#xff0c;只有一级缓存开启&#xff08;sqlSession级别的缓存&#xff09;二级缓存需要手动开启配置&#xff0c;需要局域namespace级别的缓存 一级缓存&#xff08;本地缓存&#…...