string模拟

本章准备对string模拟进行讲解,以下是string的学习网址:

string - C++ Reference (cplusplus.com)

        string本质可以理解为储存char类型的顺序表,其中string的迭代器用一个char*就可以解决。所以string类成员变量如下:

这里用了一个命名空间是为了区分库里面的string。接下来就对需要实现的函数一一讲解。

目录

一、构造函数

二、迭代器

三、运算符重载

1.关系运算符重载

2.<<和>>的重载

四、Capacity

1.resize接口:

2.reserve接口:

五、增删查

六、源码


一、构造函数

        涉及到动态内存申请的类是不能直接用编译器提供的默认构造函数,因为它无法完成深拷贝等等问题,所以需要我们自己来完成这一部分。

1.默认构造

string(const char* st = "")
{size_t sz = strlen(st);_str = new char[sz+1];strcpy(_str, st);_size = sz;_capacity = sz;
}

        在写这个函数时需要注意,不能用sizeof来计算st的所占字节空间,这里sizeof只能计算到st这个变量所储存的内容占用的字节空间,而st所储存的是字符串的地址,所以占用的空间为4/8字节。 

        这里还要注意一个点strlen做计算时并没有算入'\0',所以在申请内存时需要加上1。

2.拷贝构造

string拷贝构造函数的最本质还是用了字符串拷贝函数strcpy,如下:

string(const string& st)
{_str = new char[st._capacity];strcpy(_str, st._str);_size = st._size;_capacity = st._capacity;
}

        当然还有更方便的写法,我们可以借助已写好的默认构造函数去构造一个对象然后与需要拷贝的对象交换,那么我们就得先写swap函数,而不能用库里面的swap,该方法称为现代写法在效率上并没有提升只是相当于让编译器去帮我们写,如下:

void swap(string& str)
{std::swap(_str, str._str);std::swap(_capacity, str._capacity);std::swap(_size, str._size);
}
string(const string& st)
{string sv(st._str);swap(sv);
}

3.赋值运算重载

该函数也一样可以用现代写法,如下:

string operator=(string st)
{swap(st);return *this;
}

        这里需要注意,因为这里用到swap直接对形参进行改变,所以这个不能加const和不能用引用传参。

4.析构函数

只要涉及到动态内存申请一定要自己写析构函数,默认生成的析构函数释放不了内存。如下:

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

        注意:对于一次性申请多个元素的内存空间要用delete[ ]去释放,用delete释放内存则会出现内存泄漏,或者不确定的问题。

二、迭代器

        每个容器都有迭代器,平时写代码我们并不用关心它们内部怎么实现,只要需要知道它的用法和功能,而且会用一个容器的迭代器就会用其他所有容器的迭代器,这就是封装的好处,而对于string的迭代器是相对比较简单的,因为string本质就是一个顺序表可以对数据随机访问

如下:

三、运算符重载

运算符重载方面我们依次来设计以下函数:

char operator[](const string& s);
bool operator<(const string& s);
bool operator==(const string& s);
bool operator<=(const string& s);
bool operator>(const string& s);
bool operator>=(const string& s);
bool operator!=(const string& s);
ostream& operator<<(ostream& out, string& s);
istream& operator>>(istream& put, string& s);

对于[ ]的重载我们直接返回一个_str[index]就可以解决

        char operator[](size_t index)
        {
                return _str[index];
        }

1.关系运算符重载

<符号重载的实现底层还是调用了strcmp函数,如下:
        bool operator<(const string& s)
        {
                return strcmp(_str, s._str) < 0;
        }
        bool operator==(const string& s)
        {
                return strcmp(_str, s._str) == 0;
        }

        对于剩下的关系运算符只需复用<和==运算符即可,具体实现在最后源码给出。对于+和+=重载会在下面增删查部分进行讲解。

2.<<和>>的重载

        需要注意因为每个类的成员函数的参数都隐藏着一个this指针,而且this指针处于第一个参数位置,对于<<和>>运算符都是双目运算符(即只能有两个操作数)所以重载的函数只能有两个参数,而如果把<<,>>重载成类的成员函数的话,this指针已经占用了第一个参数位置,最后在使用的时候只能这样:操作对象<<cout 或 操作对象>>cin,这样用上去是十分别扭的而且很容易出错,所以这两个重载函数就不能作为类成员,需要在类外声明或实现。

        对于<<,>>重载函数是避免不了访问类成员变量的,而类成员变量我们已经把它设置为私有,在类外是无法访问的,所以这里把这两个重载函数设为类的友元函数就可以很好的解决。

对于<<(即输出)重载比较简单,就不过多讲解,如下:

接下来是>>

        这里有一个细节,在给一个对象输入数据之前,是先要把原数据清空的,所以需要用一个clear函数,在等会我们会实现,这里先使用。

        为了处理频繁扩容可以会消耗效率的问题,我们可以先用一个数组储存用户输入的内容,然后最后一次性储入对象中,字符读取终止的条件我们可以用空格或换行,但是由于cin会自动忽略空格字符和换行符,所以可以用cin.get()读取,然后再用while循环做判断

        注意:在把所有读取到的数据储入对象后还需要手动添加'\0'

 这里的+=重载同样我们在后面再来实现。

四、Capacity

该部分我们主要实现库函数接口的以下红圈部分。

        对于size和capacity接口的实现比较简单直接返回相应的成员函数即可,empty的话返回_size==0即可,现在重点来看一看其它接口。 

1.resize接口:

        它的功能是改变string对象中的元素个数。如str.resize(n,m)表示把str字符串的元素改为n个,如n大于str的size那么多余部分用m字符填充。

void resize(size_t n,char m='?')
{while (_size < n){_str[_size++] = m;}_size = n;
}

2.reserve接口:

        它的功能是预开空间,如str.reserve(n)表示把str的空间改为n个元素的空间大小,但它分有以下情况:

  • n > str._capacity,进行扩容。
  • str. _size < n < str._capacity,进行缩容。
  • n==str. _capacity,不做任何处理
  • n<str. _size,行为未定义(即没有明确的标准,具体取决于编译器,可能会把原数据缩小到n,也可能不做任何更改)

        那么这里为了方便当n<str. _size时我们就不做任何更改,注意这里_capacity的实现跟库里面保持一致并不用把'\0'占的空间算入。如下:

void reserve(size_t n)
{if (n < _size)return;char* st = new cha[n + 1];strcpy(st, _str);delete[] _str;_str = nullptr;_size = n;_capacity = n;
}

clear接口的话直接把_size置为0即可,不必要对其他数据进行改动数据。

五、增删查

该部分我们主要实现库函数接口的以下红圈部分。

        其中前三个接口核心在于push_back,可以先完成第三个接口,剩下两个接口对push_back复用即可。首先需要判断是否需要扩容,如果需要就进行扩容然后存放数据,不要忘记存放完数据后需要存入'\0'。如果函数是在类外实现的所以需要添加string::来指明类域。如下:

        swap函数在拷贝构造部分已经实现,pop_back函数的话直接把_size减减即可,现在重点来分析一下insert和erase。

        insert函数功能是在指定位置之前插入数据,erase的作用是删除指定位置的数据。string类本质是顺序表那么在做这个操作时就需要挪动数据。比如在pos位置之前插入数据那么就需要把pos位置及以后的所有数据都往后移动一位,从而把pos位置空出来填入新的位置。删除pos位置的数据就是要把pos以后的所有数据整体往前挪动一位,把pos位置覆盖

        需要注意的是这里会引发一个迭代器失效的问题,因为如果往pos位置之前插入数据,那么pos位置就不是原来的数据了,而是新插入的数据,那么也就是在无形中改变了pos的指向导致pos失效,删除数据也同理。而对于扩容并不会对pos的指向有影响因为pos表示的是数据的下标,扩容可能会换一块储存空间但是对应pos下标的数据并没变化。

        为考虑迭代器失效的问题库里面的规定是insert函数最后需要返回原pos指向的迭代器。esare函数最后要返回被删除数据的下一位数据的迭代器

实现如下:

string::iterator string::insert(size_t pos,char c)
{if (_size == _capacity)reserve(_capacity == 0 ? 4 : _capacity * 2);int end = _size;while (end != pos - 1){_str[end + 1] = _str[end];end--;}_str[pos] = c;_size++;return begin() + pos + 1;
}
string::iterator string::erase(size_t pos)
{int bin = pos;while (bin != _size){_str[bin] = _str[bin + 1];bin++;}_size--;return begin() + pos;
}

六、源码

​
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
namespace byte
{class string{public:typedef char* iterator;friend ostream& operator<<(ostream& out, string& s);friend istream& operator>>(istream& put, string& s);public:string(const char* st = ""){size_t sz = strlen(st);_str = new char[sz + 1];strcpy(_str, st);_size = sz;_capacity = sz;}void swap(string& str){std::swap(_str, str._str);std::swap(_capacity, str._capacity);std::swap(_size, str._size);}string(const string& st){string sv(st._str);swap(sv);}string operator=(string& st){swap(st);return *this;}~string(){delete[] _str;_str = nullptr;_capacity = _size = 0;}size_t size(){return _size;}size_t capacity(){return _capacity;}typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}iterator cbegin() const{return _str;}iterator cend() const{return _str + _size;}char operator[](size_t index){return _str[index];}bool operator<(const string& s){return strcmp(_str, s._str) < 0;}bool operator==(const string& s){return strcmp(_str, s._str) == 0;}bool operator<=(const string& s){return *this < s || *this == s;}bool operator>(const string& s){return !(*this < s || *this == s);}bool operator>=(const string& s){return !(*this < s);}bool operator!=(const string& s){return !(*this == s);}void resize(size_t n, char m = '?'){if (n <= _size)return;while (_size < n){_str[_size++] = m;}}void reserve(size_t n){if (n < _size)return;char* st = new char[n + 1];strcpy(st, _str);delete[] _str;_str = st;_capacity = n;}void clear(){_size = 0;}void push_back(char c);string& operator+=(char c);void append(const char* str);string& operator+=(const char* str);iterator insert(size_t pos, char c);iterator erase(size_t pos);private:char* _str;size_t _capacity;size_t _size;};
}
namespace byte
{void string::push_back(char c){if (_size == _capacity)reserve(_capacity == 0 ? 4 : 2 * _capacity);_str[_size++] = c;_str[_size] = '\0';}void string::append(const char* str){reserve(_size + strlen(str) + 1);//提前开空间减少扩容带来的效率损耗for (int i = 0; str[i] != '\0'; i++){push_back(str[i]);}}string& string::operator+=(char c){push_back(c);return *this;}string& string::operator+=(const char* str){append(str);return *this;}ostream& operator<<(ostream& out, string& s){for (auto vul : s){out << vul;}return out;}istream& operator>>(istream& put, string& s){s.clear();const size_t N = 256;char arr[N];char c;c = put.get();int i = 0;while (c != '\n' && c != ' '){if (i != N - 1){arr[i++] = c;}else{arr[i++] = '\0';s += arr;i = 0;}c = put.get();}if (i != 0){arr[i] = '\0';s += arr;}return put;}string::iterator string::insert(size_t pos, char c){if (_size == _capacity)reserve(_capacity == 0 ? 4 : _capacity * 2);int end = _size;while (end != pos - 1){_str[end + 1] = _str[end];end--;}_str[pos] = c;_size++;return begin() + pos + 1;}string::iterator string::erase(size_t pos){int bin = pos;while (bin != _size){_str[bin] = _str[bin + 1];bin++;}_size--;return begin() + pos;}
}
namespace byte
{void string_test1(){string x("123456");cout << x << endl;string k(x);cout << k << endl;string mstr;cin >> mstr;for (auto n : mstr){cout << n;}}void string_test2(){//reservestring str("zxcvbnm");cout << "capacity:" << str.capacity() << endl;str.reserve(20);cout << "capacity:" << str.capacity() << endl;str.reserve(15);cout << "capacity:" << str.capacity() << endl;str.reserve(3);cout << "capacity:" << str.capacity() << endl;//resizestr.resize(10, '0');cout << "size:" << str.size() << ' ' << str << endl;str.resize(3, '0');cout << "size:" << str.size() << ' ' << str << endl;}void string_test3(){string s("123456");s.push_back('x');cout << s << endl;s.append("vvv");cout << s << endl;s += "hhh";cout << s << endl;}void string_test4(){string s("12345678");s.erase(5);cout << s << endl;s.erase(2);cout << s << endl;s.insert(0,'0');cout << s << endl;}
}
int main()
{byte::string_test1();//byte::string_test2();//byte::string_test3();//byte::string_test4();return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

普通人如何抓住AI这个风口?

最强AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频百万播放量https://aitools.jurilu.com/ AI不仅仅是提升办公效率的利器&#xff0c;更是普通人目前最容易上手和变现的工具&#xff01;对于风口&#xff0c;大家应该都听…

使用yolov5实现目标检测简单案例(测试图片)

一、前置 测试这个案例之前需要安装一些前置的东西&#xff0c;如果已经安装的可以忽略&#xff0c;下面我给出我跟着做的一些很好的博客提供大家参考&#xff0c;因为我们主要目的还是实现yolov5的目标检测。 1、安装nvidia显卡驱动 可以参考&#xff1a;【Windows】安装NV…

Unified 阻抗控制 architecture、framework、approach

Unified 阻抗控制&#xff08;Unified Impedance Control&#xff09;作为一种控制策略&#xff0c;其architecture&#xff08;架构&#xff09;、framework&#xff08;框架&#xff09;和approach&#xff08;方法&#xff09;为&#xff1a; 一、Unified 阻抗控制 Archite…

京东数据编织

计算引擎是Hbase 中间计算结果的物化【就是存下来】 自动物化 在这里插入图片描述

Python自动化:解锁高效工作与生产力的密钥

在当今快节奏的数字时代&#xff0c;自动化已成为提升工作效率、优化流程、减少人为错误的不可或缺的工具。Python&#xff0c;作为一种功能强大、易于学习且应用广泛的编程语言&#xff0c;在自动化领域扮演着举足轻重的角色。无论是数据处理、Web自动化、软件测试&#xff0c…

SQL注入(原理、分类、union、POST注入)

目录 【学习目标、重难点知识】 【学习目标】 【重难点知识】 SQL注入简介 SQL注入原理 SQL注入类型 MySQL与SQL注入的相关知识 information_schema 数据库的结构 数据库查询语句 limit的用法 需要记住的几个函数 注释符号 SQL注入探测方法 SQL注入漏洞攻击流程…

ssh免密码登陆设置时Authentication refused: bad ownership or modes错误解决方法

0.环境&#xff1a; 三个节点&#xff1a;node1,node2,node3 1.问题描述&#xff1a; 配置好免密登录后&#xff0c;免密登录失效&#xff0c;还需要输入密码&#xff0c;如下图&#xff1a; 2.原因查找&#xff1a; 去查看系统的日志文件 使用命令&#xff1a; sudo tail …

基于Java语言的光伏监控系统+光伏项目+光伏储能+光伏运维系统

介绍 基于Java语言的光伏监控系统光伏发电系统光伏软件系统光伏监控系统源码光伏发电系统源码 软件架构 部分软件截图

Tmagic-editor低代码底层拖拽库Moveable示例学习

在前面咱们的自研低代码海报制作平台学习分享计划中分享了自己开发的基本拖拽组件&#xff0c;也只是做了最简单的基本实现。真要写产品&#xff0c;更多还是依赖相关的开源优秀库。 文章目录 参考基本拖拽基本缩放基本Scalable基本旋转基于原点的拖拽和旋转关于练习源码 参考 …

TCP详解(二)滑动窗口/流量控制

本文解释了TCP为何能保证数据传输的可靠性&#xff0c;以及如何保证整个网络的顺畅。 1 网络分层模型 这是一切的本质。网络被设计成分层的&#xff0c;所以网络的操作就可以称作一个“栈”&#xff0c;这就是网络协议栈的名称的由来。在具体的操作上&#xff0c;数据包最终形…

20. OTA流程 - 2

1. 概述 BES蓝牙方案自带OTA功能,支持SPP和BLE。 建议采用BLE的功能,因为苹果手机默认不支持SPP。 2. OTA框架 OTA时,耳机端需要先进入OTA状态 2.1 SPP升级

Nginx--代理与负载均衡(扩展nginx配置7层协议及4层协议方法、会话保持)

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、代理原理 1、反向代理产生的背景 单个服务器的处理客户端&#xff08;用户&#xff09;请求能力有一个极限&#xff0c;当接入请求过多时&#…

【网络安全】SSO登录过程实现账户接管

未经许可,不得转载。 文章目录 正文正文 登录页面展示了“使用 SSO 登录”功能: 经分析,单点登录(SSO)系统的身份验证过程如下: 1、启动SSO流程:当用户点击按钮时,浏览器会发送一个GET请求到指定的URL: /idp/auth/mid-oidc?req=[UNIQUE_ID]&redirect_uri=[REDI…

Leetcode JAVA刷刷站(41)缺失的第一个正数

一、题目概述 二、思路方向 为了找到未排序整数数组中未出现的最小正整数&#xff0c;并满足时间复杂度为 O(n) 和只使用常数级别额外空间的要求&#xff0c;我们可以采用原地哈希&#xff08;也称为索引哈希&#xff09;的方法。这个方法的基本思想是将每个数字&#xff08;如…

[C++进阶]二叉树进阶的一些面试题(一)

首先我们先回忆我们过去学的二叉树和最近学的二叉搜索树,来完成下面的题目: 606. 根据二叉树创建字符串 这道题属于与基础题,首先我们观察输入输出样例可以得到如果root->left为空,root->right不为空时,我们的空格仍然需要保留,如果当前节点有两个孩子&#xff0c;那我…

人工智能在肿瘤亚型分类领域的研究进展|顶刊速递·24-08-13

小罗碎碎念 文献日推主题&#xff1a;人工智能在肿瘤亚型分类领域的研究进展 昨天晚上在研究鼻咽癌的病理学诊断指南&#xff0c;看到了下面这段话的时候&#xff0c;我问了自己一个问题——通过AI识别出肿瘤亚型的根本目的是什么&#xff1f;可以衔接哪些具体的下游任务&#…

TinyEngine是什么?

TinyEngine 是 OpenTiny 项目下的一个开源低代码引擎&#xff0c;旨在帮助开发者快速构建应用程序。它提供了可视化搭建页面的能力&#xff0c;支持在线实时构建和二次开发或集成&#xff0c;适用于多种场景的低代码平台开发&#xff0c;例如资源编排、服务端渲染、模型驱动、移…

拉取/启动kafka的docker镜像

拉取/启动kafka的docker镜像 1、拉取kafka镜像2、移除docker镜像(演示)3、查看镜像是否拉取成功4、通过docker启动kafka容器5、查看是否有启动的容器 1、拉取kafka镜像 因为一些原因&#xff0c;无法从dockerhub直接拉取kafka的docker镜像&#xff0c;我将原来拉到kafka3.7.0的…

后端开发刷题 | 寻找峰值【二分法】

描述 给定一个长度为n的数组nums&#xff0c;请你找到峰值并返回其索引。数组可能包含多个峰值&#xff0c;在这种情况下&#xff0c;返回任何一个所在位置即可。 1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于 2.假设 nums[-1] nums[n] −∞ 3.对于…

MQ死信对列

面试题&#xff1a;你们是如何保证消息不丢失的&#xff1f; 1、什么是死信 死信就是消息在特定场景下的一种表现形式&#xff0c;这些场景包括&#xff1a; 1. 消息被拒绝访问&#xff0c;即消费者返回 basicNack 的信号时 或者拒绝basicReject 2. 消费者发生异常&#xff0…