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

C++(string类的实现)

1. 迭代器、返回capacity、返回size、判空、c_str、重载[]和clear的实现

string类的迭代器的功能就类似于一个指针,所以我们可以直接使用一个指针来实现迭代器,但如下图可见迭代器有两个,一个是指向的内容可以被修改,另一个则是指向的内容不能被修改,那么我们就需要实现两个;这个实现其实很简单,我们只需要使用typedef或者using来实现,这里我就两个都实现出来,两个选其一即可;如下图和代码所示:      

#include<assert.h>
class string
{
public:using iterator = char*;typedef char* iterator;using const_iterator = const char*;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;}//返回capacitysize_t capacity()const{return _capacity;}//返回size值size_t size() const{return _size;}//判空bool empty()const{return _str[0] == '\0';}//c_strconst char* c_str() const{return _str;}//模拟方括号通过下表访问char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}//clearvoid clear(){_str[0] = '\0';//_capacity = 0;  //这个大小不能改,改了就废了,这是我犯过的一个错误_size = 0;}
private:char* _str;size_t _size;size_t _capacity;
};

 


2. 实现string类的构造和析构函数

一般实现类我们都需要先实现他的构造函数和析构函数,我们就先实现string类的构造函数,但string类的构造函数有很多个,我们就挑几个来实现,第一个是模拟 string s1("hello world");第二个是模拟string s1(s2)也就是拷贝构造;

2.1 string s1("hello world")的实现

string.h

class string
{
public://模拟string s1("hello world")string(const char* str = "");//模拟string s1(s2)string(const string& s);private:char* _str;size_t _size;size_t _capacity;
};

string.cpp

	string::string(const char* str):_size(strlen(str)){_capacity = _size;//一般都会多开一个空间给'\0'_str = new char[_size + 1];strcpy(_str, str);}//模拟string s1(s2)string::string(const string& s1){//我们要创建一个他的大小的空间_str = new char[s1._capacity + 1];strcpy(_str, s1._str);_capacity = s1._capacity;_size = s1._size;}

代码的解释:

先来说第一个,有的人问为什么我们不把_capacity和_size的初始化都放到初始化列表里面去,可以尝试着说_size(strlen(str));_capacity(_size);_str(new char[_size+1])这样按着这个顺序来,是因为初始化列表走的顺序不是按初始化列表里的顺序来的,而是按声明的顺序来,如果说我们_size不是第一个声明的话,那么_capacity和_str都错了,可能会取得错误的值,所以我们只把_size放到里面的话就不会出现上面的那种情况, 在函数里面要动态开辟一块空间进行深拷贝(不进行深拷贝的后果前面有说这里就不过多阐述),然后直接用一个strcpy把内容复制到_str来即可;

第二个参数要用传引用避免进行拷贝构造调用,然后需要创建一块和s1一样大的空间,但他并不会给'\0'的顺序算入,所以我们需要+1;

注意!:上面的缺省参数要给char* str="";我们不能给NULL。

析构函数的实现:

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

3. 实现reserve(不是reverse)操作

reserve就是提前开空间,需要看具体什么意思的话可以去看前一篇c++文章介绍string的使用的;如下代码所示:

	void string::reserve(size_t n){//开空间的话我们要判断n是否大于capacityif (n > _capacity){char* temp = new char[n + 1];strcpy(temp, _str);delete[]_str;_str = temp;_capacity = n;}}

注意:我们开空间后用一个temp来接收,把_str的内容复制到temp再释放掉_str,不然会造成内容泄漏;


4. 实现push_back和append操作

push_back:进行push_back操作前我们需要判断_str的空间是否足够,如果不够则需要扩容;扩容时按二倍扩容,所以我们需要用一个三目运算符来判断,如果_capacity=0的时候就无法进行二倍扩容操作,那就需要给他一直初始空间大小;如下代码所示:

	void string::push_back(char ch){//检查空间if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;}

append:append和push_back不一样,append可以插入一个字符串,但我们也是需要考虑空间问题,首先我们需要计算传入的字符串的长度,然后加上我们原有的长度看看是否大于我们的空间容量,如果大于则按二倍扩,但我们又需要考虑一个情况就是如果说二倍扩的空间还是没有我们传入字符串的长度加原有的长度大的话,我们就让他需要多少空间就给多少空间;

	void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){size_t NewCapacity = 2 * _capacity;if (NewCapacity < _size + len)NewCapacity = _size + len;reserve(NewCapacity);}strcpy(_str + _size, str);_size += len;}

5. 实现insert和erase操作

实现insert和erase的话我们需要对字符进行往前或者往后挪动,这也导致他们的效率不高;实现insert的时候也是需要和上面的一样扩容操作,当我们在hello world中间插入一个this的时候,我们把world挪到后面去给this腾出4个位置;如下代码所示:

	string& string::insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t Newcapacity = 2 * _capacity;if (_size + len > Newcapacity)Newcapacity = _size + len;reserve(Newcapacity);}size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;return *this;}

erase也同理,但erase有两种情况,第一种是在pos位置删除len长度的时候如果说len长度大于pos后面的长度的时候那就直接让pos位置为'\0',因为\0就是意味着结束,第二种情况则是len小于pos后面的长度,那就需要挪动数据;如下代码所示:

	string& string::erase(size_t pos, size_t len){assert(pos < _size);if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{size_t end = pos + len;while (end <= _size){_str[end - len] = _str[end];++end;}_size -= len;}return *this;}

6. 实现npos和find的操作

npos在string里面代表最大的值但他是静态成员、不能被修改、还是一个无符号整型,那就直接给他一个-1就可以;如下代码所示:

#include<assert.h>
class string
{
public:const static size_t npos = -1;private:char* _str;size_t _size;size_t _capacity;
};

注意!:这里为什么可以给缺省值-1是因为加了const,如果不加const是无法给的,因为被static修饰的变量是不走初始化列表,在定义的时候给值就是给缺省参数,缺省参数是给初始化列表使用的;

find操作实现:find有两个,一个是从指定位置开始找,另一个则是从头遍历到尾,直接进行暴力遍历就可以实现;如下代码所示:

	size_t string::find(char c, size_t pos )const{assert(pos <= _size);for (size_t i = pos; i < _size; i++){if (c == _str[i]){return i;}}return npos;}
	size_t string::find(const char* s, size_t pos )const{//我们要从pos位置找起那么就要_str+pos;assert(pos <= _size);const char* ptr = strstr(_str + pos, s);if (ptr == NULL){return npos;}elsereturn ptr - _str;}

注意,这里ptr-_str的操作是指针-指针得到的是中间元素的个数即位置;


7. 实现getline操作

getline的操作是一直往字符串里输入数据,直到碰到输入特定的字符的时候才终止,因为是输入数据,所以需要使用流插入类型,和重载cin一样,并且在进行一下操作的时候我们还需要清楚原有的数据,举个例子如果说你不清楚的话,原先本身s1对象里就有hello world的数据了,当你想输入一个year的时候就会打印hello world year, 而string类里的getline是不会保留hello world的,所以我们先要进行clear操作;如下代码所示:

	istream& getline(istream& is, string& s,char delim){s.clear();int i = 0;char buff[256];char ch;ch = is.get();//因为使用cin的时候当碰到' '和'\n'的时候就结束while (ch!=delim){buff[i++] = ch;if (i == 255){buff[i] = '\0';//因为是数组,模拟存储字符串的时候不会有\0s += buff;//因为用+=会自行进行扩容i = 0;}ch = is.get();}if (i > 0){buff[i] = '\0';s += buff;}return is;}

代码解释:char buff[255]想做的是减少扩容操作,因为如果进行过多的扩容操作就会导致运行效率降低,那我们就可以预先给他开辟256个空间,然后进行接收输入的数据,但我们不能使用cin,因为cin碰到空格的时候就会结束,所以我们可以使用输入流里的get,然后就对输入的字符开始判断是否是特定字符,再往buff里面放,当i==255的时候,也就是剩下最后一个空间就需要给'\0';


8. 比较运算符重载

很简单,只要实现了两个后面都可以套用,直接上代码;

	bool string::operator==(const string& s){return strcmp(this->c_str(), s.c_str());}bool string::operator!=(const string& s){return !((*this)==s);}	bool string::operator<(const string& s){return strcmp(this->c_str(), s.c_str()) < 0;}bool string::operator<=(const string& s){return *this < s || *this == s;}bool string::operator>(const string& s){return !(*this <= s);}bool string::operator>=(const string& s){return *this > s || *this == s;}

9.实现+=运算符重载

直接套用append和push_back即可;如下代码所示:

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

10.实现swap

std里面本身就有一个swap,为什么我们又要另外实现呢?原因是std域内的swap是一个模板,比如我们有两个自定义string类型s1 和s2, 那么传过去就成了会调用拷贝构造,还需要多次开辟空间,所以我们需要在类内实现;其实也是很简单的,我们直接调用std域内的swap对s1和s2的成员进行交换即可;如下代码所示:

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

11. 实现流插入>>和流提取运算符重载<<

流提取简单,其实就是直接打印_str里的值就可以;如下代码所示:

	ostream& operator<<(ostream& os, const string& str){for (size_t i = 0; i < str.size(); i++){os << str[i];}return os;}

流插入也简单,和append差不多,但是和append不同的一点是,append是遇到特定字符的时候就结束,而流插入cin的则是碰到空格或者换行符的时候结束;如下代码所示:

	istream& operator>>(istream& _cin, string& s){s.clear();int i = 0;char buff[255];char ch;ch = _cin.get();//因为使用cin的时候当碰到' '和'\n'的时候就结束while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 255){buff[i] = '\0';//因为是数组,模拟存储字符串的时候不会有\0s += buff[i];//因为用+=会自行进行扩容i = 0;}ch = _cin.get();}if (i > 0){buff[i] = '\0';s += buff;}return _cin;}

12. 赋值运算符的重载

赋值运算符的重载有两种情况,举个例子吧,string s1("hello world");string s2("man!");第一种是s1 = s2,第二种是s1 = s1(我也不知道这个写来有什么意义,但string类支持这个操作)我们需要分类讨论这两个,因为赋值运算符的重载是需要释放原空间接收新空间,例如s1 =  s2就需要释放s1的空间然后把s2的空间和值给s1,但如果是s1=s1的话,s1的空间释放了就没法进行,所以要分类讨论;如下代码所示:

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

上面那个太复杂了,下面这个是现代写法:

	//现代写法string& string::operator=( string s){swap(s);return *this;}

举个例子s1.swap(s2);s2出了函数栈帧自动销毁,然后这里是传值传参不会影响到实参;


END!

相关文章:

C++(string类的实现)

1. 迭代器、返回capacity、返回size、判空、c_str、重载[]和clear的实现 string类的迭代器的功能就类似于一个指针&#xff0c;所以我们可以直接使用一个指针来实现迭代器&#xff0c;但如下图可见迭代器有两个&#xff0c;一个是指向的内容可以被修改&#xff0c;另一个则是指…...

nrf 24l01使用方法

1、frequency 频率基础频率2.400G HZ RF_CH RF_CH10 CH2.4G0.01G2.41G 2、逻辑通道6个 pipe 时间片不同&#xff0c;占用同一个频率 发送时&#xff0c;只有一个pipe 接受时可以有6个pipe 3、通讯速率 air data rate rf_dr 寄存器设置 有两种速率 2M 1M RF_DR0 1M ,…...

C语言普及难度三题

先热个身&#xff0c;一个长度为10的整型数组&#xff0c;输出元素的差的max和min。 #include<stdio.h> int main() {int m[10],i0,max,min;for(i0;i<10;i){scanf("%d",&m[i]);}minm[0];maxm[0];for (i 0; i <10; i){if(min>m[i]) min m[i];i…...

10.4每日作业

C1 C2 C1 C2...

日常工作记录:服务器被攻击导致chattr: command not found

在深夜的寂静中&#xff0c;公司的服务器突然遭遇了一场突如其来的攻击。特别是nginx配置文件无法修改&#xff0c;仿佛预示着不祥的预兆&#xff0c;面对这突如其来的灾难&#xff0c;技术人员迅速响应。 这时候需要chattr&#xff0c;但是执行的chattr -i xxx的时候&#xf…...

多线程-初阶(1)

本节⽬标 • 认识多线程 • 掌握多线程程序的编写 • 掌握多线程的状态 • 掌握什么是线程不安全及解决思路 • 掌握 synchronized、volatile 关键字 1. 认识线程&#xff08;Thread&#xff09; 1.1 概念 1) 线程是什么 ⼀个线程就是⼀个 "执⾏流". 每个线…...

Spring Boot集成encache快速入门Demo

1.什么是encache EhCache 是一个纯 Java 的进程内缓存框架&#xff0c;具有快速、精干等特点&#xff0c;是 Hibernate 中默认的 CacheProvider。 Ehcache 特性 优点 快速、简单支持多种缓存策略&#xff1a;LRU、LFU、FIFO 淘汰算法缓存数据有两级&#xff1a;内存和磁盘&a…...

【C语言】数组练习

【C语言】数组练习 练习1&#xff1a;多个字符从两端移动&#xff0c;向中间汇聚练习2、二分查找 练习1&#xff1a;多个字符从两端移动&#xff0c;向中间汇聚 编写代码&#xff0c;演示多个字符从两端移动&#xff0c;向中间汇聚 练习2、二分查找 在⼀个升序的数组中查找指…...

微服务实战——ElasticSearch(保存)

商品上架——ElasticSearch&#xff08;保存&#xff09; 0.商城架构图 1.商品Mapping 分析&#xff1a;商品上架在 es 中是存 sku 还是 spu &#xff1f; 检索的时候输入名字&#xff0c;是需要按照 sku 的 title 进行全文检索的检索使用商品规格&#xff0c;规格是 spu 的…...

leetcode练习 路径总和II

给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum 22 输出&a…...

使用Three.js库创建的简单WebGL应用程序,主要用于展示具有不同透明度和缩放比例的圆环列

上述HTML文档是一个使用Three.js库创建的简单WebGL应用程序&#xff0c;主要用于展示具有不同透明度和缩放比例的圆环列。以下是代码的详细解释&#xff1a; HTML结构: 文档类型声明为HTML5。<html>标签设置了语言属性为英语&#xff08;lang"en"&#xff09;…...

Redis: 集群架构,优缺点和数据分区方式和算法

集群 集群指的就是一组计算机作为一个整体向用户提供一组网络资源 我就举一个简单的例子&#xff0c;比如百度&#xff0c;在北京和你在上海访问的百度是同一个服务器吗&#xff1f;答案肯定是不是的&#xff0c;每一个应用可以部署在不同的地方&#xff0c;但是我们提供的服务…...

负载均衡可以在网络模型的哪一层?

一、网络模型概述 网络模型是用于描述网络通信过程和网络服务的抽象框架。最常见的网络模型有两种&#xff1a;OSI&#xff08;开放式系统互联&#xff09;模型和TCP/IP模型。 OSI模型 OSI&#xff08;Open Systems Interconnection&#xff09;模型是由国际标准化组织&…...

YOLOv11改进 | 上采样篇 | YOLOv11引入CARAFE上采样

1. DySample介绍 1.1 摘要:特征上采样是许多现代卷积网络体系结构(如特征金字塔)中的关键操作。它的设计对于密集预测任务(如对象检测和语义/实例分割)至关重要。在本文中,我们提出了一个通用、轻量级、高效的特征重组算子CARAFE来实现这一目标.CARAFE有几个吸引人的特性…...

【Linux运维】grep命令粗浅学习

文章目录 1 背景介绍1.1 为什么要学习grep&#xff1f;1.2 grep是什么&#xff1f;1.3 grep可以做什么&#xff1f; 2 grep基本语法2.1 命令格式2.2 “PATTERN”部分中的正则表达式语法学习2.3 grep命令参数学习 3 典型案例3.1 匹配非空行&#xff0c;过滤纯空行3.2 匹配IPv4地…...

【Godot4.3】匀速和匀变速直线运动粒子

概述 本篇论述&#xff0c;如何用加速度在Godot中控制粒子运动。 匀速和匀变速直线运动的统一 以下是匀变速运动的速度和位移公式&#xff1a; v t v 0 a t x t v 0 t 1 2 a t 2 v_tv_0 at \\ x_tv_0t \frac{1}{2}at^2 vt​v0​atxt​v0​t21​at2 当a 0 时&#xf…...

基于Hive和Hadoop的用电量分析系统

本项目是一个基于大数据技术的用电量分析系统&#xff0c;旨在为用户提供全面的电力消耗信息和深入的用电量分析。系统采用 Hadoop 平台进行大规模数据存储和处理&#xff0c;利用 MapReduce 进行数据分析和处理&#xff0c;通过 Sqoop 实现数据的导入导出&#xff0c;以 Spark…...

一个简单的摄像头应用程序4

我们进一步完善了这个app01.py,我们优化了界面使其更人性化,下面介绍中包含了原有的功能及新增的功能: 创建和管理文件夹: create_folder 函数用于创建保存照片和视频的文件夹。 get_next_file_number 函数用于获取文件夹中下一个可用的文件编号。 图像处理: pil_to_cv 函…...

SpringBoot使用EasyPoi根据模板导出word or pdf

1、导出效果 1.1 wrod 1.2 pdf 2、依赖 <!--word--><dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-base</artifactId><version>4.3.0</version></dependency><dependency><groupId>cn.…...

NVIDIA Hopper 架构深入

在 2022 年 NVIDIA GTC 主题演讲中,NVIDIA 首席执行官黄仁勋介绍了基于全新 NVIDIA Hopper GPU 架构的全新 NVIDIA H100 Tensor Core GPU。 文章目录 前言一、NVIDIA H100 Tensor Core GPU 简介二、NVIDIA H100 GPU 主要功能概述1. 新的流式多处理器 (SM) 具有许多性能和效率…...

AWS IoT Core for Amazon Sidewalk

目录 1 前言2 AWS IoT2.1 准备条件2.2 创建Credentials2.2.1 创建user2.2.2 配置User 2.3 本地CLI配置Credentials 3 小结 1 前言 在测试Sidewalk时&#xff0c;device发送数据&#xff0c;网关接收到&#xff0c;网关通过网络发送给NS&#xff0c;而此处用到的NS是AWS IoT&am…...

今日指数项目项目集成RabbitMQ与CaffienCatch

今日指数项目项目集成RabbitMQ与CaffienCatch 一. 为什么要集成RabbitMQ 首先CaffeineCatch 是作为一个本地缓存工具 使用CaffeineCatch 能够大大较少I/O开销 股票项目 主要分为两大工程 --> job工程(负责数据采集) , backend(负责业务处理) 由于股票的实时性也就是说 ,…...

C0005.Clion中移动ui文件到新目录后,报错问题的解决

报错问题如下 AutoUic error ------------- "SRC:/confirmwizardpage.cpp" includes the uic file "ui_confirmwizardpage.h", but the user interface file "confirmwizardpage.ui" could not be found in the following directories"SRC…...

基于STM32的智能家居灯光控制系统设计

引言 本项目将使用STM32微控制器实现一个智能家居灯光控制系统&#xff0c;能够通过按键、遥控器或无线模块远程控制家庭照明。该项目展示了如何结合STM32的外设功能&#xff0c;实现对灯光的智能化控制&#xff0c;提升家居生活的便利性和节能效果。 环境准备 1. 硬件设备 …...

06.useEffect

在 React 开发中,正确使用 useEffect 钩子对于优化组件性能至关重要。一个常见但容易被忽视的性能问题是在依赖数组中使用对象作为依赖项。这可能导致不必要的效果重新执行,从而影响应用性能。通过优先使用原始值(如字符串、数字)作为依赖项,我们可以显著提高组件的效率。…...

【设计模式-中介者模式】

定义 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为设计模式&#xff0c;通过引入一个中介者对象&#xff0c;来降低多个对象之间的直接交互&#xff0c;从而减少它们之间的耦合度。中介者充当不同对象之间的协调者&#xff0c;使得对象之间的通信变得简单且…...

树和二叉树知识点大全及相关题目练习【数据结构】

树和二叉树 要注意树和二叉树是两个完全不同的结构、概念&#xff0c;它们之间不存在包含之类的关系 树的定义 树&#xff08;Tree&#xff09;是n&#xff08;n≥0&#xff09;个结点的有限集&#xff0c;它或为空树&#xff08;n 0&#xff09;&#xff1b;或为非空树&a…...

ajax的原理,使用场景以及如何实现

AJAX 原理 AJAX&#xff08;Asynchronous JavaScript and XML&#xff09;是一种在网页中实现异步通信的技术&#xff0c;允许网页在不重新加载整个页面的情况下与服务器交换数据。这使得网页应用可以更加响应式和动态&#xff0c;提升用户体验。 AJAX 的核心原理是在后台通过…...

lock_guard和unique_lock学习总结

1.std::lock_guard std::lock_guard其实就是简单的RAII&#xff08;Resource Acquisition Is Initialization&#xff09;封装&#xff0c;资源获取即初始化。在构造函数中进行加锁&#xff0c;析构函数中进行解锁&#xff0c;这样可以保证函数退出时&#xff0c;锁一定被释放…...

数据挖掘-padans初步使用

目录标题 Jupyter Notebook安装启动 Pandas快速入门查看数据验证数据建立索引数据选取⚠️注意&#xff1a;排序分组聚合数据转换增加列绘图line 或 **&#xff08;默认&#xff09;&#xff1a;绘制折线图。bar&#xff1a;绘制条形图。barh&#xff1a;绘制水平条形图。hist&…...