【C++精华铺】10.STL string模拟实现

1. 序言

        STL(标准模板库)是一个C++标准库,其中包括一些通用的算法、容器和函数对象。STL的容器是C++ STL库的重要组成部分,它们提供了一种方便的方式来管理同类型的对象。其中,STLstring是一种常用的字符串类型。

        STLstring是一个类,它封装了字符串的操作,并提供了一组成员函数。STLstring的实现使用了动态的内存分配技术,这意味着字符串的大小可以随时改变。STLstring还提供了一些高效的成员函数,例如substr、find、replace等,这些函数可以对字符串进行快速的操作。

        STLstring的实现主要基于字符数组。字符数组是一种固定大小的数组,其中每个元素包含一个字符。STLstring使用一个字符数组来存储字符串,并通过动态的内存分配技术来管理数组的大小。当向一个空的STLstring对象中添加字符时,STLstring会自动调整数组的大小。

        STLstring还实现了一些常见的字符串操作,例如连接字符串、查找字符串、替换字符串和分割字符串等。这些操作使用了C++ STL库中的algorithm算法,可以高效地处理字符串。同时,STLstring也提供了迭代器的支持,允许用户使用STL算法来处理字符串。

2. string类的接口实现

        在实现接口之前先要给出我们的初始类,包括三个私有成员(_size,_capacity,_str),接下来我们会对这个初始类一步一步的完善(为了文章易读后续接口函数不会展示类的全貌,只会展示实现的接口函数内容,在文章的末尾会给出完整的string类代码)如下:

namespace zybjs
{class string{public:private:size_t _size;size_t _capacity;char* _str;};
}

2.1 构造函数

(1)默认构造和C串构造

        在STL库中将C串构造和默认构造分开实现,但是我们在模拟实现string类的时候可以将这俩个构造函数合并,这样的代码会更简洁,也方便我们自己的使用。在此之前我们先按库里面的思路走一趟:在我们给初始容量的时候即便字符串长度是0我们也不能给0,避免后续无法倍数扩容,这里我们给的是4。并且给字符串开空间的时候要比容量多一个,最后一个留给'\0'。(VS下实现思路不同,VS下给了一个16字节的字符数组_buf,如果要存储的字符串小于16字节就存放在字符数组_buf中,否则就重新开一个空间)

default (1)            string();

from c - string(2)  string(const char* s);

string():_size(0),_capacity(4)  //不能给0,如果给0后续无法倍数扩容 下同
{_str = new char[_capacity + 1]; //开空间的时候要比容量多一个字节留给'\0',因为容量的大小不算'\0',下同_str[0] = '\0';
}
string(const char* str)  //const类型接收右值:_size(strlen(str))
{_capacity = _size == 0 ? 4 : _size;_str = new char[_capacity + 1];strcpy(_str, str);
}

         但是我们在自己实现的时候大可不必这样去写,我们之前学习了缺省参数,并且全缺省的构造函数也可以作为默认构造,所以我们可以对上述代码进行优化,给str一个空串作为缺省参数,这样当我们调用的时候不给实参就会默认使用空串进行构造来完成库中”string();“的功能。如下:

string(const char* str = "")   //const类型接收右值:_size(strlen(str))
{_capacity = _size == 0 ? 4 : _size;_str = new char[_capacity + 1];strcpy(_str,str);  
}

(2)拷贝构造

        string类的拷贝构造很简单,要注意的点有俩个:其一,string类涉及到空间管理,所以在拷贝的时候要深拷贝,否则会导致同一空间多次析构导致报错;其二,传参不能传值传参,要使用传引用传参,否则会导致无穷递归,

string(const string& s):_size(s._size),_capacity(s._capacity)
{      //深拷贝_str = new char[_capacity + 1];  //重新开空间strcpy(_str,s._str);     //字符序列拷贝
}

2.2 析构函数

        string类因为涉及到内存的管理,所以析构函数不能使用默认生成的析构,需要我们自己去实现析构。在实现析构的时候将_size和_capacity全部置零,然后通过delete[]释放_str就可以了。如下:

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

2.3 size()、capacity()

        size()和capacity()很多人在实现的时候都觉得很简单反而会忽略一个点:就是const对象访问的时候是否会权限放大。因为这个俩个函数仅涉及到读取,没有修改的操作,所以我们只需要实现const版本来兼容const对象和非const对象。如下

size_t size() const  //兼容const对象和非const对象
{return _size;
}
size_t capacity() const//兼容const对象和非const对象
{return _capacity;
}

2.4 c_str()

        返回指向一个数组的指针,该数组包含以 null 结尾的字符序列(即 C 字符串),该序列表示字符串对象的当前值。只需完成const版本来兼容const对象和非const对象。指针的返回值也需要是const char *类型,防止通过指针对对象进行修改。

const char* c_str() const 
{return _str;
}

2.5 operator[]

        因为要实现类似于数组的访问方式,所以我们要实现[]的重载形式。[]的特性:能够随机访问元素,对于非const对象能够修改元素。所以operator[]要实现const版本和非const版本,const版本的返回值必须是const引用。

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

2.6 operator=

        赋值运算符的重载是对字符串对象的深拷贝,和拷贝构造的过程相同,但是‘=’的特性支持连续的赋值,所以我们在实现赋值重载的时候需要返回一个string对象来支持赋值重载的连续赋值。因为我们不涉及对传入参数的修改,所以我们需要传入一个const string& 类型。注意在赋值之前需要释放原来的空间。如下:

string& operator=(const string& s)
{if (s._str != _str){_size = s._size;_capacity = s._capacity;//深拷贝char* _tmp = new char[_capacity + 1]; strcpy(_tmp,s._str);//先释放原来的空间delete[] _str;_str = _tmp;}return *this;
}

测试:

zybjs::string s1("cacaca");
zybjs::string s2("dadada");
zybjs::string s3("bababa");
s3 = s2 = s1;
std::cout << s1.c_str() << std::endl;
std::cout << s2.c_str() << std::endl;
std::cout << s3.c_str() << std::endl;

2.7 字符串比较 

        字符串比较我们通过strcmp来进行比较(strcmp(srt1,str2)),返回的值为0,字符串相同;返回的值大于0,str1>str2;返回的值小于0,str1<str2。基于此便可以实现字符串的比较函数。

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

2.8 迭代器实现

        string的迭代器底层是一个指针,string类的迭代器是一种用于访问字符串中字符的对象,可以通过迭代器的运算符访问字符串中的字符。迭代器为C++容器提供了一种通用的访问手段。

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

2.9 reserve

        reserve()是为了给对象预留空间,如果我们提前得知字符串需要的空间我们就可以提前开好,避免频繁扩容带来的性能消耗。当reserve的参数小于string底层空间大小的时候,reserve就不会对容量进行处理。

void reserve(size_t n)
{if (n > _capacity){char* _tmp = new char[n+1];strcpy(_tmp, _str);delete[] _str;_str = _tmp;_capacity = n;}
}

2.10 resize

          修改字符有效个数为n,多出的空间用字符ch填充。如果n小于有效字符数,本质就是删字符操作。如果容量不够会扩容。

void resize(size_t n, char ch = '\0')
{//当n<有效字符数的时候本质上就是删字符//但是我们一般不会进行缩容,在原来的空间上将n位置的字符设置为'\0'if (n < _size){_size = n;_str[_size] = '\0';}else if (n > _size){if (n > _capacity)  //n>_capacity就进行扩容{reserve(n);        }size_t i = _size;while (i < n)       //将非有效字符初始化为ch{_str[i] = ch;i++;}_size = n;_str[_size] = '\0'; //设置终止位}
}

2.11 push_back、append、operator+=

        push_back尾插,append是在字符串后面追加一个字符串,实现比较简单,但要注意检查容量。

void push_back(char ch)
{if (_size + 1 > _capacity){reserve(2 * _capacity);}_str[_size] = ch;_size++;_str[_size] = '\0';
}
void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str+_size,str);_size += len;
}

        operator+=的功能可以由push_back和append的复用来实现:

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

2.12 insert

        insert是string类中支持pos位插入字符或者字符串的函数。其中,pos表示插入的位置,返回string的引用表示插入后的新字符串。

string& insert(size_t pos, char ch)
{if (_size + 1 > _capacity){reserve(2 * _capacity);}size_t n = pos;size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size += 1;_str[_size] = '\0';return *this;
}string& insert(size_t pos, const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_capacity + len);}size_t n = pos;size_t end = _size + 1;while (end > pos){_str[end - 1 + len] = _str[end - 1];end--;}strncpy(_str + pos, str, len);_size += len;_str[_size] = '\0';return *this;
}

2.13 erase

        C++中的string类提供了一个名为erase()的成员函数,用于删除字符串中的一部分字符并修改该字符串。该函数可以接受1个或2个参数,具体取决于要删除的字符数。如果没有显式的指定删除字符数,会使用默认的npos也就是无符号整型-1来作为缺省参数(65535),就会默认删除pos位后面所有的字符。然后返回删除字符后生成的新字符。

        首先我们要定义一个和库里相同的npos:

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

2.14 流插入和流提取

        

	std::ostream& operator<<(std::ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}std::istream& operator>>(std::istream& in, string& s){s.clear();        //输入之前要清空字符串char ch = in.get();//获取字符包括'\n'char buff[32];  //设置缓冲区来防止频繁扩容size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i] = ch;if (i == 30){buff[31] = '\0';s += buff;i = 0;}i++;ch = in.get();}buff[i] = '\0';s += buff;}

3. 完整代码(均调试通过)

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<cassert>
namespace zybjs
{class string{public: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;}//string()//	:_size(0)//	,_capacity(4)  //不能给0,如果给0后续无法倍数扩容 下同//{//	_str = new char[_capacity + 1]; //开空间的时候要比容量多一个字节留给'\0',因为容量的大小不算'\0',下同//	_str[0] = '\0';//}//string(const char* str)//	:_size(strlen(str))//{//	_capacity = _size == 0 ? 4 : _size;//	_str = new char[_capacity + 1];//	strcpy(_str, str);//}string(const char* str = "")   //const类型接收右值:_size(strlen(str)){_capacity = _size == 0 ? 4 : _size;_str = new char[_capacity + 1];strcpy(_str,str);  }string(const string& s):_size(s._size),_capacity(s._capacity){      //深拷贝_str = new char[_capacity + 1];  //重新开空间strcpy(_str,s._str);     //字符序列拷贝}~string(){delete[] _str;   //释放_str_str = nullptr;_size = _capacity = 0;}size_t size() const  //兼容const对象和非const对象{return _size;}size_t capacity() const//兼容const对象和非const对象{return _capacity;}const char* c_str() const {return _str;}const char& operator[](size_t pos) const{assert(pos < _size&& pos >= 0);return _str[pos];}char& operator[](size_t pos){assert(pos < _size && pos >= 0);return _str[pos];}string& operator=(const string& s){if (s._str != _str){_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) > 0;}bool operator==(const string& s) const{return strcmp(_str, s._str) == 0;}bool operator>=(const string& s) const{//return *this > s || *this == s;return *this > s || s == *this;}bool operator<(const string& s) const{return !(*this >= s);}bool operator<=(const string& s) const{return !(*this > s);}bool operator!=(const string& s) const{return !(*this == s);}void reserve(size_t n){if (n > _capacity){char* _tmp = new char[n+1];strcpy(_tmp, _str);delete[] _str;_str = _tmp;_capacity = n;}}void resize(size_t n, char ch = '\0'){//当n<有效字符数的时候本质上就是删字符//但是我们一般不会进行缩容,在原来的空间上将n位置的字符设置为'\0'if (n < _size){_size = n;_str[_size] = '\0';}else if (n > _size){if (n > _capacity)  //n>_capacity就进行扩容{reserve(n);        }size_t i = _size;while (i < n)       //将非有效字符初始化为ch{_str[i] = ch;i++;}_size = n;_str[_size] = '\0'; //设置终止位}}void push_back(char ch){if (_size + 1 > _capacity){reserve(2 * _capacity);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str+_size,str);_size += len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}string& insert(size_t pos, char ch){if (_size + 1 > _capacity){reserve(2 * _capacity);}size_t n = pos;size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size += 1;_str[_size] = '\0';return *this;}string& insert(size_t pos, const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_capacity + len);}size_t n = pos;size_t end = _size + 1;while (end > pos){_str[end - 1 + len] = _str[end - 1];end--;}strncpy(_str + pos, str, len);_size += len;_str[_size] = '\0';return *this;}string& erase(size_t pos,size_t len = npos){assert(pos < _size);if (len >= _size - pos - 1){_size = pos;_str[_size] = '\0';}else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}void clear(){_size = 0;_str[_size] = '\0';}private:size_t _size;size_t _capacity;char* _str;static const size_t npos;};const size_t string::npos = -1;std::ostream& operator<<(std::ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}std::istream& operator>>(std::istream& in, string& s){s.clear();        //输入之前要清空字符串char ch = in.get();//获取字符包括'\n'char buff[32];  //设置缓冲区来防止频繁扩容size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i] = ch;if (i == 30){buff[31] = '\0';s += buff;i = 0;}i++;ch = in.get();}buff[i] = '\0';s += buff;}
}

 

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

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

相关文章

Docker如何安装seafile

SQLite 方式 要在 Docker 中安装 Seafile&#xff0c;您可以按照以下步骤进行操作&#xff1a; 安装 Docker&#xff1a;确保您的系统上已经安装了 Docker。您可以根据您的操作系统类型&#xff0c;在官方网站上找到适合您系统的 Docker 版本并进行安装。 下载 Seafile 镜像&…

Unity设置TextMeshPro文本超出范围显示...

TextMtshPro文本超出范围&#xff0c;展示省略。选择Overflow为Ellipsis。

centroen 23版本换界面了

旧版本 新版本 没有与操作系统一起打包的ISO文件了&#xff0c;要么先安装系统&#xff0c;再安装Centreon&#xff0c;要么用pve导入OVF文件

Shell命令操作Linux文件系统

Shell命令操作Linux文件系统 文件夹介绍 文件夹常规命令 文件夹权限控制⭐ 文件类型和权限 修改文件权限 移动、复制、删除文件夹 文件夹介绍 Linux文件系统是计算机操作系统中的一个关键组成部分&#xff0c;它用于管理和组织计算机上的数据和信息。先到根目录&#xf…

支付宝使用OceanBase的历史库实践分享

为解决因业务增长引发的数据库存储空间问题&#xff0c;支付宝基于 OceanBase 数据库启动了历史库项目&#xff0c;通过历史数据归档、过期数据清理、异常数据回滚&#xff0c;实现了总成本降低 80%。 历史数据归档&#xff1a;将在线库&#xff08;SSD 磁盘&#xff09;数据归…

STDF-Viewer 解析工具说明

一、简介 1. 概述 STDF&#xff08;Standard Test Data Format&#xff09;&#xff08;标准测试数据格式&#xff09;是半导体测试行业的最主要的数据格式&#xff0c;包含了summary信息和所有测试项的测试结果&#xff1b;是半导体行业芯片测试数据的存储规范。 在半导体行业…

解决Nacos服务器连接问题:一次完整的排查经验分享

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【笔试强训选择题】Day35.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01; 文章目录 前言 一、Da…

l8-d7 实现TCP通信

一、TCP服务器的实现(理论) #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); -domain: 指定通信域&#xff08;通信地址族&#xff09;; -type: 指定套接字类型; -protocol: 指定协议; 套接字类型与协议 -type:…

2023高教社杯 国赛数学建模B题思路 - 多波束测线问题

1 赛题 B 题 多波束测线问题 单波束测深是利用声波在水中的传播特性来测量水体深度的技术。声波在均匀介质中作匀 速直线传播&#xff0c; 在不同界面上产生反射&#xff0c; 利用这一原理&#xff0c;从测量船换能器垂直向海底发射声波信 号&#xff0c;并记录从声波发射到信…

Ansible-roles学习

目录 一.roles角色介绍二.示例一.安装httpd服务 一.roles角色介绍 roles能够根据层次型结构自动装载变量文件&#xff0c;tasks以及handlers登。要使用roles只需在playbook中使用include指令即可。roles就是通过分别将变量&#xff0c;文件&#xff0c;任务&#xff0c;模块以…

layui实现数据列表的复选框回显

layui版本2.8以上 实现效果如图&#xff1a; <input type"hidden" name"id" id"id" value"{:g_val( id,0)}"> <div id"tableDiv"><table class"layui-hide" id"table_list" lay-filter…

点云切片的实现(PCL)C++

一、实现逻辑 1、通过PCL库的getMinMax3D得到xyz轴上的最大最小值&#xff1b; 函数原型&#xff1a; pcl::getMinMax3D(const pcl::PointCloud<PointT> &cloud, POintT &min_pt, PointT &max_pt) 2、设置切片厚度&#xff0c;计算某一轴方向上的切片数量&a…

【智慧工地源码】物联网和传感器技术在智慧工地的应用

物联网&#xff08;IoT&#xff09;和传感器技术在智慧工地中扮演着至关重要的角色。这些技术的应用&#xff0c;使得智慧工地能够实现对施工过程的精确监控、数据收集和分析&#xff0c;以及设备互联&#xff0c;从而提高工程效率、减少成本并改善工人的工作环境。 一、物联网…

ThreadLocal

ThreadLocal 参考&#xff1a;https://blog.csdn.net/u010445301/article/details/111322569 ThreadLocal简介 作用&#xff1a;实现线程范围内的局部变量&#xff0c;即ThreadLocal在一个线程中是共享的&#xff0c;在不同线程之间是隔离的。 原理&#xff1a;ThreadLocal存…

如何使用CSS画一个三角形

原理&#xff1a;其实就是规定元素的四个边框颜色及边框宽度&#xff0c;将元素宽高设置为0。如果要哪个方向的三角形&#xff0c;将对应其他三个方向的边框宽和颜色设置为0和透明transparent即可 1.元素设置边框&#xff0c;宽高&#xff0c;背景色 <style>.border {w…

单月打造8个10w+,情感类视频号如何爆火?

上月&#xff0c;腾讯公布了2023年Q2财报&#xff0c;其中&#xff0c;较为亮眼的是微信视频号的广告收入。据财报显示&#xff0c;二季度视频号用户使用时长与去年同期相比几乎翻倍&#xff0c;广告收入超过30亿元。作为微信生态的核心组件&#xff0c;视频号的内容生态呈现出…

NumPy模块:Python科学计算神器之一

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据开发、数据分析等。 🐴欢迎小伙伴们点赞👍🏻、收藏⭐️、…

【小沐学NLP】Python使用NLTK库的入门教程

文章目录 1、简介2、安装2.1 安装nltk库2.2 安装nltk语料库 3、测试3.1 分句分词3.2 停用词过滤3.3 词干提取3.4 词形/词干还原3.5 同义词与反义词3.6 语义相关性3.7 词性标注3.8 命名实体识别3.9 Text对象3.10 文本分类3.11 其他分类器3.12 数据清洗 结语 1、简介 NLTK - 自然…