【C++】手撕STL系列——string篇

文章导读

本章我们将参照STL源码,来模拟实现string类,但不一定非要与库中完全相同。我们将其中重要的、常用的接口进行模拟实现,旨在加深string类的学习与记忆。

为了代码更好地复用,本篇模拟的函数接口的顺序大概为构造类——》内存类——》迭代器——》修改类——》构造类

定义string类

为了区别于标准库中的string类,我们这里应该使用自己的命名空间来进行定义

string类包含以下三种成员

  • char*_str  字符数值
  • size_t _size  大小
  • size_t _capacity  容量

注意:capacity的大小不包含\0,_size指的是\0的位置

另外还需要一个static的size_t成员npos,值为-1,表示数组末尾

构造函数

string的构造函数有很多种写法,由前面类和对象的学习中了解到全缺省的构造函数是最优的写法,所以这里我们也采纳全缺省的写法

注意:初始化列表是根据成员的定义顺序来进行初始化的,所以这里_str不能放到初始化列表进行初始化,因为放到初始化列表中会第一个初始化_str,但这是还不知道_capacity的大小

        /*string():_str(new char[1]{ '\0' }),_size(0),_capacity(0){}*///全缺省的构造函数更优string(const char* s=""):_size(strlen(s)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, s);}

内存类函数接口

size_t size()const——返回大小
size_t size()
{return _size;
}
size_t capacity()const——返回容量大小
size_t capacity()const
{return _capacity;
}
void reserve(size_t n)——扩容函数

n大于capacity才会发生扩容,开辟另一块空间并将原来的空间拷贝过去,再销毁原来的空间;n小于等于capacity的时候不会有所作为

不会修改_size值

void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];//多出来的一个位置放\0strcpy(tmp, _str);delete[]_str;_str = tmp;_capacity = n;}}

void resize(size_t n,char ch)——修改大小

如果n<=size就减小_size的值,并在对应处放'\0'

如果n>size位置,就扩容(复用reserve函数),并将size 位置到n位置的元素初始化为ch,最后一个位置放\0

void resize(size_t n,char c='\0'){if (n <= _size){_str[n] = '\0';_size = n;}else{reserve(n);while (_size < n){_str[_size] = c;_size++;}_str[_size] = '\0';}}

迭代器

先实现迭代器是为了方便遍历数组(范围for)并将元素打印,方便我们后期进行调试并检验其他函数接口的正确性

这里的迭代器可以简单地认为是原生指针(其他类可能不是原生指针)

        iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str+_size;}

为了使string类可以像数组一样可以访问,增强代码的可读性,可以先实现一个[]的运算符重载

实现两个,一个是只能读不能修改,一个是可读可修改

        char operator[](size_t i){assert(i <= _size);return *(_str + i);}const char operator[](size_t i)const{assert(i <= _size);return *(_str + i);}

修改类

注意:strcpy以\0为结束标志,会拷贝\0;strncpy自己决定拷贝个数,不会自动拷贝\0

经过初阶数据结构与算法的学习,我们知道顺序表的优势在与尾插尾删以及随机访问,所以第一个修改类的实现当然是尾插push_back啦

push_back
       void push_back(const char ch)//插入一个字符{//满了先扩容if (_size = _capacity){reserve(_capacity==0?4:2*_capacity);}_str[_size] = ch;_size++;_str[_size] = '\0';}

除了插入一个字符,肯定还有插入一个字符串的需求,这里就交给append

如果插入的字符串长度+原本的长度>capacity,就需要扩容

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

用函数实现尾插当然可以,但是现实中更多人使用的是+=运算符重载,因为这样可读性很高,也十分生动形象

有了以上两个接口,我们的+=运算符重载当然是手到擒来啦,直接复用即可

+=运算符重载
        string& operator+=(const char ch){push_back(ch);return *this;}string& operator+=(const char* s){append(s);return *this;}

实现了尾插,接下来就是头插(insert函数取第一个位置即可)啦

我们可以头插一个字符,也可以头插一个字符串,也可以在任意位置插入(写完insert后尾插其实就可以复用insert了,取最后一个位置即可)

注意插入位置pos要在合法区间,如果大于容量则要扩容

insert
void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}//size位置指的是‘\0’,也要移动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* str){assert(pos <= _size);int len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}int end = _size;//防止发生整形提升while (end >=(int) pos){_str[end+len] = _str[end];--end;}strncpy(_str + pos, str, sizeof(char) * len);_size += len;}

实现完插入当然就是删除啦

void clear()——删除所有(在开头处放\0,并不用真正地销毁)

clear()
        void clear(){_str[0] = '\0';_size = 0;}

  void erase(size_t pos = 0, size_t len = npos)——删除在pos以及以后的len个元素

   如果没指定len的大小,默认将pos以后的元素全部删完

   指定了就删除len个

 void erase
        void erase(size_t pos = 0, size_t len = npos){assert(pos < _size);if (pos + len >= _size){_str[pos] = '\0';_size = pos;}else{int end = pos + len;while (end <_size){_str[end - len] = _str[end];end++;}_size -= len;}}

输入输出运算符重载

为了不破坏权限以及耦合度,这里不使用友元实现,将输入输出的运算符重载放在string的类外,自己的类域里面实现

cout重载

就是一个简单的范围for遍历

    ostream& operator<< (ostream& out, const string& str){for (auto ch : str)out << ch;return out;}
cin重载

为了避免输入过多字符,s不断扩容,先定义一个buff数组,用空间换取时间

如果buff数组满了,先将数组里的内容尾插到string中,再将buff数组清空,继续往buff数组里输入值,如此反复

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

比较函数

正如字符串可以比较一样,string类(封装的字符串)我们应该也设置成让他们可以比较

只要实现了==和>或者(==和<),其他的函数接口都可以复用这两个函数接口

        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);}bool operator<(const string& s){return !(*this <= s);}bool operator!=(const string& s){return !(*this == s);}

构造类

拷贝构造
        //拷贝构造函数string(const string& s, size_t pos=0, size_t len = npos){if (len == npos){_str = new char[s._size-pos];strcpy(_str, s._str + pos);_size = _capacity = s._size - pos;}else{_str = new char[len];strncpy(_str, s._str + pos, len);_capacity = _size = len;_str[_size] = '\0';}}
=运算符的重载

这里有两种写法,一种是老老实实地自己写,一种是复用拷贝构造

       //老实写法string& operator=(const string& tmp){if (*this != tmp){//char* s = new char[tmp._capacity ]——这样写下面的delete会报错,越界多拷贝了一个,释放空间的时候会出问题char* s = new char[tmp._capacity+1];strcpy(s, tmp._str);delete[]_str;_str = s;_size = tmp._size;_capacity = tmp._capacity;}return *this;}

复用写法

先需要一个swap函数,调用标准库里的swap函数,由函数重载自动识别是哪种类型并操作

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

传参的时候调用了拷贝构造函数,然后再交换,即可完成拷贝,十分简洁,但效率一样

注意:几乎每个类的赋值重载都可以这样写,十分通用又简洁的写法

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

至此,string的大概的接口就实现的差不多了,希望对大家能有所帮助

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

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

相关文章

京东商品价格监控API 高并发实时数据

随着电子商务的飞速发展&#xff0c;商品价格监控变得越来越重要。对于电商平台而言&#xff0c;实时监控商品价格不仅可以及时调整市场策略&#xff0c;还可以优化库存管理&#xff0c;提高销售额。本文以“京东商品价格监控API高并发实时数据”为标题&#xff0c;将探讨如何使…

序列化与反序列化And存入redis中的数据为什么要序列化

一、序列化与反序列化 序列化&#xff1a;指堆内存中的java对象数据&#xff0c;通过某种方式把对存储到磁盘文件中&#xff0c;或者传递给其他网络节点&#xff08;网络传输&#xff09;。这个过程称为序列化&#xff0c;通常是指将数据结构或对象转化成二进制的过程。 即将…

【STM32单片机】防盗报警器设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用STM32F103C8T6单片机控制器&#xff0c;使用按键、动态数码管、蜂鸣器、指示灯、热释电人体红外传感器等。 主要功能&#xff1a; 系统运行后&#xff0c;默认处于布防状态&#xff0c;D1指示灯…

Excel 规范录入数据

文章目录 录入日期录入百分比 快捷键&#xff1a; tab&#xff1a;向右切换单元格 enter&#xff1a;向下切换行 shift tab&#xff1a;向左切换单元格 shiftenter&#xff1a;向上切换行 录入日期 输入今天的日期的快捷键&#xff1a;Ctrl ; 输入当时的时间的快捷键&a…

Java反射获取抽象类方法属性问题讲解

Java反射获取抽象类方法属性问题讲解 结论一、案例准备二、测试方法&#xff1a;使用反射获取抽象类私有方法和私有属性具体操作&#xff08;获取私有方法&#xff09;具体操作&#xff08;获取私有属性&#xff09; 结论 Java 通过反射可以获得抽象类的任何修饰符&#xff08…

Gin框架中的Cookie怎么搞(会话控制)

参考地址 设置和获取 Cookie | Gin Web Framework (gin-gonic.com)https://gin-gonic.com/zh-cn/docs/examples/cookie/ 什么是cookie cookie在互联网上随处可见,具体体现如下: 保持登录状态 保存浏览器的历史记录 大数据随心配,按喜好推送讯息 购物网站加入购物车 都会…

flink中使用外部定时器实现定时刷新

背景&#xff1a; 我们经常会使用到比如数据库中的配置表信息&#xff0c;而我们不希望每次都去查询db&#xff0c;那么我们就想定时把db配置表的数据定时加载到flink的本地内存中&#xff0c;那么如何实现呢&#xff1f; 外部定时器定时加载实现 1.在open函数中进行定时器的…

【RealTek sdk-3.4.14b】RTL8197FH-VG+RTL8367+RTL8812F WiFi to LAN 和WiFi to WAN吞吐量

LAN <----------> 2.4G WiFi Throughput 天线频宽模式协议连接速率TX(Mbps)RX(Mbps)TX&RX(Mbps)2X240MHz802.11nTCP300Mbps2051922112X240MHz802.11nUDP300Mbps224234231 LAN <----------> 5G WiFi Throughput 天线频宽模式协议连接速率TX(Mbps)RX(Mbps)TX&…

IntelliJ IDEA Maven 项目的依赖分析

在一个 maven 的项目中&#xff0c;我们需要知道我们的项目中使用的包可能有哪些冲突。 这个在 IntelliJ IDEA 中提供了贴心的查看。 选择 Maven 项目中的分析依赖。 随后&#xff0c;IntelliJ IDEA 将会打开一个依赖分析的标签页。 在这个标签页中&#xff0c;我们可以看到…

用例图 UML从入门到放弃系列之三

1.说明 关于用例图&#xff0c;这篇文章我将直接照搬罗伯特.C.马丁老爷子在《敏捷开发》一书种的第17章&#xff0c;并配上自己的理解&#xff0c;因为这一章写的实在是太精彩了&#xff0c;希望能够分享给大家&#xff0c;共勉。以下是老爷子的原文中文翻译以及豆芽的个人解读…

Ultra-Fast-Lane-Detection 车道线学习资料整理

目录 官方版本 两个优化 数据标注,降低参数量 1 数据标注 2降低参数量

代码随想录算法训练营Day50|动态规划9

代码随想录算法训练营Day50|动态规划9 文章目录 代码随想录算法训练营Day50|动态规划9一、198.打家劫舍二、213.打家劫舍II三、337.打家劫舍 III 一、198.打家劫舍 class Solution {public int rob(int[] nums) {if (nums null || nums.length 0) return 0;if (nums.length …

C++程序员入门怎么学?

1 背景 经常听到不少朋友在吐槽C太难学了&#xff0c;说什么从入门到放弃等等&#xff1b;做为一名C老兵&#xff0c;从2003年开始入坑C到今年2023年&#xff0c;整整20年的C开发从业者的我&#xff0c;今天尝试给有兴趣从事C开发的程序员提些建议&#xff0c;希望可以帮到大家…

C++ (Chapter 1)

C (一) 1.C的命名空间 先来看一个C语言的例子: 下面这段代码是可以正常运行的. #include<stdio.h> int rand 0; int main() {printf("%d \n", rand);return 0; }但是,包含了一个头文件之后,甚至无法通过编译. #include<stdio.h> #include<stdli…

力扣每日一题35:搜索插入的位置

题目描述&#xff1a; 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 示例 1: 输入: nums [1,3,5,6], target 5…

大数据Doris(八):启动FE步骤

文章目录 启动FE步骤 一、配置环境变量 二、​​​​​​​创建doris-mate

排序算法-插入排序法(InsertSort)

排序算法-插入排序法&#xff08;InsertSort&#xff09; 1、说明 插入排序法是将数组中的元素逐一与已排序好的数据进行比较&#xff0c;先将前两个元素排序好&#xff0c;再将第三个元素插入适当的位置&#xff0c;也就是说这三个元素仍然是已排序好的&#xff0c;接着将第…

剖析伦敦银最新价格走势图

国际金融市场瞬息万变&#xff0c;伦敦银的价格走势会受到诸多因素的影响&#xff0c;比如重要经济数据的公布&#xff0c;国际间的政治博弈&#xff0c;突发的政经大事&#xff0c;都可以令白银价格的走势&#xff0c;在短时间内暴涨暴跌的情况。 要在伦敦银市场实现良好的收益…

Linux文件目录总结

众所周知&#xff0c;Linux系统文件目录是树状结构&#xff0c;如下图所示&#xff1a; 英文缩写的目录下到底存放的是什么文件&#xff0c;善于做归纳总结的逍遥哥哥来解释一下&#xff1a; /bin&#xff1a;bin是Binary的缩写&#xff0c;这个目录存放着最经常使用的命令。 …

oracle 数据库删除序列

oracle 数据库删除序列 要删除 Oracle 数据库中的序列&#xff0c;你可以使用以下的 SQL 命令&#xff1a; DROP SEQUENCE sequence_name;其中&#xff0c;sequence_name 是你想删除的序列的名称。你需要确保当前用户对序列拥有适当的权限。 请注意&#xff0c;删除序列将永…