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

库中是如何实现string类的?

在这里插入图片描述

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:讲解如何模拟实现C++中的string类.
金句分享:
✨你要做多大的事情,就要承受多大的压力!✨

前言

我们先认识一下string类的框架.

class string{public://成员函数private:char* _str;                 //字符串指针 size_t _capacity;           //容量size_t _size;               //当前字符有效个数}:

在这里插入图片描述

框架图:
在这里插入图片描述

目录

  • 前言
  • 一、构造函数与析构函数
    • (1) 无参构造:
    • (2) 使用常量字符串构造
    • (3) 拷贝构造
    • (4) 析构函数
  • 二、capacity相关操作
    • (1) reserve函数
    • (2) resize函数
    • (3) empty函数
    • (4) size和capacity函数
  • 三、访问与遍历
    • (1) 迭代器
    • (2)下标访问符 方括号`[ ]`重载
  • 四、修改与查找
    • (1) push_back函数
    • (2) append函数
    • (3) find函数
    • (4) insert函数
    • (5) erase函数
  • 五、运算符重载
    • (1) 流运算符重载
      • 流插入运算符
      • 流提取运算符
    • (2) 比较运算符重载

一、构造函数与析构函数

在这里插入图片描述

(1) 无参构造:

我们可以试着看一下库里面是如何赋值的?

	std::string s1;cout << "s1= " << s1 << endl;

在这里插入图片描述
所以,对于无参构造,我们只需要将*str赋值为空串就行了.

注意:
""(中间没有空格)

(2) 使用常量字符串构造

  1. 先计算字符串的长度.
  2. 将长度值赋值给_size_capacity .
  3. 申请一块为_capacity+1大小的空间.(+1是为了存储'\0')
  4. 将字符串中的值按字节拷贝至string类中的_str.

代码实现:

//全缺省构造函数,,默认初始化为空字符
string(const char* str = "")//无参也是调用这个 {_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];//里面其实有一个字符'\0'// strcpy(_str, str);              //遇到'\0'结束拷贝,也会吧'\0'拷贝过去memcpy(_str, str, _size + 1);}

(3) 拷贝构造

这里注意实现深拷贝即可.

string(const string& s)       //注意这里+const  普通对象可以调用,const对象也可以调用
{_size = s._size;_capacity = s._capacity;_str = new char[_capacity + 1];//需要多一个字节存放,字符'\0'memcpy(_str,s._str,s._size+1);      
}

(4) 析构函数

析构函数注意释放动态申请的空间即可.

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

二、capacity相关操作

(1) reserve函数

reserve():请求改变容量的大小,类似于扩容从操作.

  1. 向堆区申请一块n+1大小的新空间.
  2. 将旧空间的数据拷贝到新空间,
  3. 释放旧空间
  4. _str指向新空间
  5. 更新容量capacity
void reserve(size_t n)
{if (n > _capacity){//申请一块新空间char* tmp = new char[n + 1];memcpy(tmp, _str, _size + 1);//不建议使用strcpy,可能存在中间有'\0'的强开delete[] _str;//释放旧空间_str = tmp;_capacity = n;}
}

(2) resize函数

resize():用于改变字符串的有效字符长度.不够的地方用第二个参数填充.

在这里插入图片描述

步骤:

  1. 如果n<size(即n<当前长度):
    ①直接在n位置处赋值为’\0’.
    _size更新为n

  2. 如果n>=size:
    ①调用扩容函数,扩容至n大小.
    ②超出部分用字符'c'填充
    ③更新_size,并在最后一个位置设置为’\0’

代码实现:

  void resize(size_t n, char c = '\0'){if (n < _size){_str[n] = '\0';_size = n;}else{reserve(n);//无论需不要扩容,不需要扩容时reserve内部会什么也不做,需要扩容时,reserve会扩容.memset(_str + _size, c, n - _size);_size = n;_str[_size] = '\0';}}

(3) empty函数

如果_size值为0,则为空,返回true.
否则返回false;

  bool empty()const{//size=0则为空,返回truereturn _size == 0 ? true : false;}

(4) size和capacity函数

这两个函数,直接返回值即可.

	size_t size()const{return _size;}size_t capacity()const{return _capacity;}

三、访问与遍历

(1) 迭代器

迭代器的介绍

C++迭代器是一个用于遍历容器(如vector、list、set等)中的元素的对象。迭代器的作用类似于指针,可以通过解引用操作符(*)获取容器中的元素值,也可以通过自增操作符(++)移动迭代器指向下一个元素。迭代器可以访问容器中的元素,也可以修改容器中的元素值。

在这里插入图片描述

注意迭代器的定义,迭代器是左闭右开的区间.

public:typedef char* iterator;typedef const char* const_iterator;//普通迭代器iterator begin(){return _str;             //返回第一个字符位置}iterator end(){return _str + _size;     //返回最后一个有效字符的下一个位置}//常类迭代器const const_iterator begin()const{return _str;             //返回第一个字符位置}const const_iterator end()const{return _str + _size;     //返回最后一个有效字符的下一个位置}

(2)下标访问符 方括号[ ]重载

返回_str中第index的位置

 char& operator[](size_t index){//判断位置是否合法assert(index >= 0 && index < _size);return *(_str + index);}const char& operator[](size_t index)const{assert(index >= 0 && index < _size);return *(_str + index);}

四、修改与查找

(1) push_back函数

push_back尾部插入一个字符

在进行插入操作之前,要先考虑扩容的情况.

需要注意的是,如果采用无参构造,刚开始容量是0.
这就导致是初次扩容,容量开始是0,所以这里要判断扩容前,容量是否是0,再考虑1.5倍或者二倍扩容.

void push_back(char c)
{if (_size + 1 > _capacity){//如果capacity是0,则无法进行1.5倍扩容//reserve(_capacity * 1.5);reserve(_capacity == 0 ? 4 : _capacity * 1.5);//扩容多少没有标准,2倍或者1.5倍扩容都可,}_str[_size] = c;_size++;        //有效数据+1_str[_size] = '\0';
}

(2) append函数

append尾部追加字符串

 void append(const char* str){int sz=strlen(str);//如果容量不够,就申请新空间if (_size + sz > _capacity){reserve(_capacity +sz);}//追加新的字符串memcpy(_str + _size, str, sz);_size += sz;_str[_size] = '\0';}

(3) find函数

string中查找目标字符,通过遍历比较.
第二个参数表示从pos位置开始查找.

顺序查找即可

size_t find(char c, size_t pos = 0) const
{assert(pos < _size);for (int i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;
}

字符串匹配:查找string类的中的目标字串

字符串匹配算法,这里简化,直接调用库函数strstr,就不手撕算法了.

 // 返回子串s在string中第一次出现的位置size_t find(const char* s, size_t pos = 0) const{assert(pos < _size);const char* ptr = strstr(_str + pos, s);//通过调用库函数strstr找到字符串出现的位置的指针if (ptr){return ptr - _str;}else{return npos;}}

(4) insert函数

pos位置插入一个字符:
老规矩,先扩容,学过数据结构的小伙伴应该知道,需要先移动数据再进行插入数据操作.

顺序表任意位置插入

// 在pos位置上插入字符c/字符串str,并返回该字符的位置string& insert(size_t pos, char c)
{assert(pos >= 0 && pos<= _size);if (_size + 1 > _capacity){reserve(_capacity * 1.5);}int i = 0;//移动数据for (i = _size; i > pos - 1; i--){_str[i] = _str[i - 1];}_str[pos - 1] = c;_size++;return *this;
}

pos位置插入一段字符串:

这里在移动数据时,注意0号位置插入时移动.
如果我们只是将前面的数据直接往后移动字符串长度大小的位置,则到插入0号位置时,前面的数据是非法的,此处设计时,需要注意.

string& insert(size_t pos, const char* str)
{assert(pos >= 0 && pos <= _size);int sz = strlen(str);//如果容量不够,就申请新空间if (_size + sz > _capacity){reserve(_capacity + sz);}int i = 0;//移动数据,这里需要注意0号位置插入时是否移动数据非法.size_t end = _size;while (end >= pos && end != npos){_str[end + sz] = _str[end];--end;}//插入字符串for (i = pos; i <pos+sz; i++){_str[i] = str[i-pos];}_size+=sz;return *this;
}

(5) erase函数

erase:删除从pos位置开始往后len长度的元素,并返回删除后的string.

在这里插入图片描述

在这里插入图片描述

 // 删除pos位置上的元素,并返回该元素的下一个位置string& erase(size_t pos, size_t len=npos){assert(pos <= _size);if (len == npos || pos + len >= _size)//如果要求删除的长度+pos超过了string中有效字符的长度{_size = pos;_str[_size] = '\0';}else{size_t end = pos + len;while (end <= _size){_str[pos++] = _str[end++];}_size -= len;}return *this;}

五、运算符重载

(1) 流运算符重载

采用友元函数的方式,实现流提取与流插入运算符重载.

流插入运算符

 ostream& operator<<(ostream& _cout, const cjn::string& s)//记得包在cjn命名空间里面{//在实现了迭代器的情况下,可以使用范围forfor (auto& in : s)      //依次取出string类中的全部字符,插入进流{_cout << in;}return _cout;   //返回输出流}

流提取运算符

 istream& operator>>(istream& in, string& s){s.clear();//如果s中本身有数据,先将有效数据清除char ch = in.get();//处理缓冲区的空格和换行,因为可能有人先输入了空格或者换行导致读取数据失败while (ch == ' ' || ch == '\n'){ch = in.get();}//有效数据插入进schar buff[128];//为了避免从小的容量一次次扩容int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;//将读取到的数据先存放进临时数组if (i == 127)//只有等数据满127时,才将数据插入进s{buff[i] = '\0';s += buff;i = 0;}ch = in.get();//继续获取有效数据}if (i != 0)//最后,如果buff数组中还有数据,则将这些剩余数据插入{buff[i] = '\0';s += buff;}return in;}

(2) 比较运算符重载

两个字符串比较,我们利用memcmp函数比较两字符串中较短字符串的长度位数.
然后根据memcmp返回值进行进一步判断.

 bool operator<(const string& s){int length = s._size;//谁短,length就等于谁if (_size < length)   length = _size;int ret=memcmp(_str, s._str, length);if (ret == 0)//如果短的 与长的前半部分相等{if (_size< s._size)//比比较的字符串短,则<成立,true{return true;}return false;}if(ret==-1)//表示右操作数大,<满足return true;if (ret == 1)//表示左操作数大,<不满足return false;}

三目运算符写起来可能不大好理解,但是代码看起来很简洁

 bool operator<(const string& s) const{int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);//按短的进行比较//如果ret==0,则比较长度,s长则返回真,否则返回假//ret!=0则-1表示真,1表示假return ret == 0 ? _size < s._size : ret < 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){if (memcmp(_str, s._str, _size) == 0){if (_size == s._size){return true;}return false;}}bool operator!=(const string& s){return !(*this == s);}

博主能力有限,无法严格按照库中的方法实现,比如采用内存池等技术,还有部分函数并未实现,模拟实现string的目的只是为了我们更好的理解string类,而不是真正让我们去写一个库函数.
拜拜了.
在这里插入图片描述

相关文章:

库中是如何实现string类的?

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f…...

无涯教程-JavaScript - WORKDAY.INTL函数

描述 WORKDAY.INTL函数返回带有自定义周末参数的指定工作日数之前或之后的日期的序列号。周末参数指示哪些和多少天是周末。周末和指定为假期的任何日子均不视为工作日。 语法 WORKDAY.INTL (start_date, days, [weekend], [holidays])争论 Argument描述Required/OptionalS…...

STM32--蓝牙

本文主要介绍基于STM32F103C8T6和蓝牙模块实现的交互控制 简介 蓝牙&#xff08;Bluetooth&#xff09;是一种用于无线通信的技术标准&#xff0c;允许设备在短距离内进行数据交换和通信。它是由爱立信&#xff08;Ericsson&#xff09;公司在1994年推出的&#xff0c;以取代…...

java 实现原型模式

原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;它允许创建对象的副本&#xff0c;而无需暴露对象的创建细节。在Java中&#xff0c;原型模式通常通过克隆对象来实现。要实现原型模式&#xff0c;需要满足以下条件&#xff1a; 被克隆的对…...

maven本地安装jar包install-file,解决没有pom的问题

背景&#xff1a; 公司因为权限问题&#xff0c;没有所有的代码&#xff0c;内部maven还在搭建&#xff0c;所以需要拿到同事的jar包&#xff0c;本地install&#xff1a; mvn install:install-file -DgroupIdcom..framework -DartifactIdcloud-api -Dversion1.0.0-SNAPSHOT …...

【C++学习笔记】5、变量作用域

文章目录 【 1、局部变量 】【 2、全局变量 】【 3、局部变量和全局变量的初始化 】 作用域是程序的一个区域&#xff0c;一般来说有三个地方可以定义变量&#xff1a; 在函数或一个代码块内部声明的变量&#xff0c;称为局部变量。 在函数参数的定义中声明的变量&#xff0c;称…...

Python中的装饰器

迷途小书童的 Note 读完需要 5分钟 速读仅需 2 分钟 装饰器是一个非常有用而又常被误解的功能&#xff0c;可以让我们在不修改函数或类的源代码情况下给它们提供扩展功能。本文将通过具体示例带你深入理解 Python 装饰器的用法。 1 装饰器基础 装饰器本质上是一个函数&#xff…...

什么是RESTful API,Spring MVC如何支持RESTful架构

文章目录 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 ✨收录专栏&#xff1a;Java框架 ✨文章内容&#xff1a;Spring MVC支持RESTful架构 &#x1f91d;希望作者的文章能对你有所帮助&#xf…...

cin、cin.getline()、getline()的用法【C++】

一、cin>> 用法1&#xff1a;输入一个数字或字符 #include <iostream> using namespace std; int main () {int a,b;cin>>a>>b;cout<<ab<<endl;return 0; } 用法2&#xff1a;接收一个字符串&#xff0c;遇“空格”、“TAB”、“回车”…...

单向链表(c/c++)

链表是一种常见的数据结构&#xff0c;其中运用到了结构体指针&#xff0c;链表可以实现动态存储分配&#xff0c;换而言之&#xff0c;链表是一个功能强大的数组&#xff0c;可以在某个节点定义多种数据类型&#xff0c;可以实现任意的添加&#xff0c;删除&#xff0c;插入节…...

像linux 一样清理Windows C盘

像 linux 有命令 du -sh 查看文件夹大小 但是windows 可就没有这个命令了&#xff0c;就算有命令&#xff0c;也不能扫描子目录里面的文件 但是windows 可以借助 软件来清理&#xff0c;和linux 一样 文件上面是目录&#xff0c;下面是文件所占用空间大小的图&#xff0c;咋…...

在Linux 下制作启动盘以及dd命令使用

在Linux 下制作启动盘以及dd命令使用 1、在Linux 下制作启动盘&#xff0c;可使用如下命令&#xff1a;2、Linux dd 命令(1)参数说明: 3、dd应用实例(1)将本地的/dev/hdb整盘备份到/dev/hdd(2)将/dev/hdb全盘数据备份到指定路径的image文件(3)将备份文件恢复到指定盘(4)备份/de…...

C语言插入排序

前言&#xff1a; 本文主要讲解插入排序中的直接插入排序和希尔排序。 1、直接插入排序&#xff1a; 1.1基本思想 直接插入排序是一种简单的插入排序法&#xff0c;其基本思想是把待排序的数值按照大小顺序逐个插入到一个已经排好序的有序序列中&#xff0c;直到将所有记录…...

SQL-DCL

DCL-管理用户 1.查询用户 USE mysql&#xff1b; SELECT * FROM user&#xff1b; 2.创建用户 CREATE USER “用户名”“主机名” IDENTIFIED BY "密码“&#xff1b; 3.修改用户密码 ALTER USER “用户名”“主机名” IDENTIFIED WITH mysql_native_password BY &quo…...

Elasticsearch 中的向量搜索:设计背后的基本原理

作者&#xff1a;ADRIEN GRAND 实现向量数据库有不同的方法&#xff0c;它们有不同的权衡。 在本博客中&#xff0c;你将详细了解如何将向量搜索集成到 Elastisearch 中以及我们所做的权衡。 你有兴趣了解 Elasticsearch 用于向量搜索的特性以及设计是什么样子吗&#xff1f; …...

Jquery会议室布局含门入口和投影位置调整,并自动截图

一、关于下载 1、文章中罗列了主要代码&#xff0c;如需使用&#xff0c;请前往CSDN下载进行下载&#xff0c;包中包含所有文件素材&#xff0c;开箱即用 2、下载链接&#xff1a;https://download.csdn.net/download/zlxls/88305636 二、有这么一个需求 1、会场进行布局&a…...

高精度乘法模板(fft)

正常高精度复杂度是o(n^2)&#xff0c;fft复杂度o(nlogn) #define int long long//__int128 2^127-1(GCC) #define PII pair<int,int> #define f first #define s second using namespace std; const int inf 0x3f3f3f3f3f3f3f3f, N 3e5 5, mod 1e9 7; const doubl…...

C# 现状简单说明

文章目录 环境框架图形界面后端游戏 环境 .net framework 老版本.net版本&#xff0c;只能在windows环境下运行 .net core 新版.net版本。可以跨linux,mac平台运行 框架 图形界面 Winfrom 很老的图形界面。特点是丑&#xff0c;但是能用&#xff0c;学起来快 WPF 使用Xaml…...

el-table滚动加载、懒加载(自定义指令)

我们在实际工作中会遇到这样的问题&#xff1a; 应客户要求&#xff0c;某一个列表不允许分页。但是不分页的话&#xff0c;如果遇到大量的数据加载&#xff0c;不但后端响应速度变慢&#xff0c;前端的渲染效率也会降低&#xff0c;页面出现明显的卡顿。 那如何解决这个问题…...

不关闭Tamper Protection(篡改保护)下强制卸载Windows Defender和安全中心所有组件

个人博客: xzajyjs.cn 背景介绍 由于微软不再更新arm版本的win10系统&#xff0c;因此只能通过安装insider preview的镜像来使用。而能找到的win10 on arm最新版镜像在安装之后由于内核版本过期&#xff0c;无法打开Windows安全中心面板了&#xff0c;提示如下&#xff1a; 尝…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】

1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件&#xff08;System Property Definition File&#xff09;&#xff0c;用于声明和管理 Bluetooth 模块相…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点&#xff1a; 多级缓存&#xff0c;先查本地缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...

《Docker》架构

文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器&#xff0c;docker&#xff0c;镜像&#xff0c;k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...