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类的迭代器的功能就类似于一个指针,所以我们可以直接使用一个指针来实现迭代器,但如下图可见迭代器有两个,一个是指向的内容可以被修改,另一个则是指…...
nrf 24l01使用方法
1、frequency 频率基础频率2.400G HZ RF_CH RF_CH10 CH2.4G0.01G2.41G 2、逻辑通道6个 pipe 时间片不同,占用同一个频率 发送时,只有一个pipe 接受时可以有6个pipe 3、通讯速率 air data rate rf_dr 寄存器设置 有两种速率 2M 1M RF_DR0 1M ,…...
C语言普及难度三题
先热个身,一个长度为10的整型数组,输出元素的差的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
在深夜的寂静中,公司的服务器突然遭遇了一场突如其来的攻击。特别是nginx配置文件无法修改,仿佛预示着不祥的预兆,面对这突如其来的灾难,技术人员迅速响应。 这时候需要chattr,但是执行的chattr -i xxx的时候…...

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

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

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

微服务实战——ElasticSearch(保存)
商品上架——ElasticSearch(保存) 0.商城架构图 1.商品Mapping 分析:商品上架在 es 中是存 sku 还是 spu ? 检索的时候输入名字,是需要按照 sku 的 title 进行全文检索的检索使用商品规格,规格是 spu 的…...

leetcode练习 路径总和II
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。 示例 1: 输入:root [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum 22 输出&a…...
使用Three.js库创建的简单WebGL应用程序,主要用于展示具有不同透明度和缩放比例的圆环列
上述HTML文档是一个使用Three.js库创建的简单WebGL应用程序,主要用于展示具有不同透明度和缩放比例的圆环列。以下是代码的详细解释: HTML结构: 文档类型声明为HTML5。<html>标签设置了语言属性为英语(lang"en")…...

Redis: 集群架构,优缺点和数据分区方式和算法
集群 集群指的就是一组计算机作为一个整体向用户提供一组网络资源 我就举一个简单的例子,比如百度,在北京和你在上海访问的百度是同一个服务器吗?答案肯定是不是的,每一个应用可以部署在不同的地方,但是我们提供的服务…...
负载均衡可以在网络模型的哪一层?
一、网络模型概述 网络模型是用于描述网络通信过程和网络服务的抽象框架。最常见的网络模型有两种:OSI(开放式系统互联)模型和TCP/IP模型。 OSI模型 OSI(Open Systems Interconnection)模型是由国际标准化组织&…...

YOLOv11改进 | 上采样篇 | YOLOv11引入CARAFE上采样
1. DySample介绍 1.1 摘要:特征上采样是许多现代卷积网络体系结构(如特征金字塔)中的关键操作。它的设计对于密集预测任务(如对象检测和语义/实例分割)至关重要。在本文中,我们提出了一个通用、轻量级、高效的特征重组算子CARAFE来实现这一目标.CARAFE有几个吸引人的特性…...
【Linux运维】grep命令粗浅学习
文章目录 1 背景介绍1.1 为什么要学习grep?1.2 grep是什么?1.3 grep可以做什么? 2 grep基本语法2.1 命令格式2.2 “PATTERN”部分中的正则表达式语法学习2.3 grep命令参数学习 3 典型案例3.1 匹配非空行,过滤纯空行3.2 匹配IPv4地…...

【Godot4.3】匀速和匀变速直线运动粒子
概述 本篇论述,如何用加速度在Godot中控制粒子运动。 匀速和匀变速直线运动的统一 以下是匀变速运动的速度和位移公式: 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 vtv0atxtv0t21at2 当a 0 时…...

基于Hive和Hadoop的用电量分析系统
本项目是一个基于大数据技术的用电量分析系统,旨在为用户提供全面的电力消耗信息和深入的用电量分析。系统采用 Hadoop 平台进行大规模数据存储和处理,利用 MapReduce 进行数据分析和处理,通过 Sqoop 实现数据的导入导出,以 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) 具有许多性能和效率…...

Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...