库中是如何实现string类的?

news2024/11/26 18:48:43

在这里插入图片描述

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻推荐专栏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则为空,返回true
       return _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命名空间里面
 {
     //在实现了迭代器的情况下,可以使用范围for
     for (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();
     }
     
     //有效数据插入进s
     char 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类,而不是真正让我们去写一个库函数.
拜拜了.
在这里插入图片描述

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

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

相关文章

如何查看B站UP主数据?如何看懂B站数据?

bilibili是独特且稀缺的PUGC视频社区&#xff0c;拥有浓厚社区氛围的视频社区。有别于短视频&#xff0c;PUGC视频创作门槛高&#xff0c;视频内容更充实&#xff0c;bilibili是PUGC视频行业的领跑者&#xff0c;同时&#xff0c;bilibili拥有社区产品特有的高创作渗透率和高互…

SQL SERVER 如何实现UNDO REDO 和PostgreSQL 有近亲关系吗

开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,SQL Server&#xff0c;Redis &#xff0c;Oracle ,Oceanbase 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请加微信号 l…

分类任务评价指标

分类任务评价指标 分类任务中&#xff0c;有以下几个常用指标&#xff1a; 混淆矩阵准确率&#xff08;Accuracy&#xff09;精确率&#xff08;查准率&#xff0c;Precision&#xff09;召回率&#xff08;查全率&#xff0c;Recall&#xff09;F-scorePR曲线ROC曲线 1. 混…

配置Jenkins

主要是配置Jenkins和jdk,maven的插件

Spring Cloud Alibaba Nacos配置导入问题解决方案

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

论文复现--lightweight-human-pose-estimation-3d-demo.pytorch(单视角多人3D实时动作捕捉DEMO)

分类&#xff1a;动作捕捉 github地址&#xff1a;https://github.com/Daniil-Osokin/lightweight-human-pose-estimation-3d-demo.pytorch 所需环境&#xff1a; Windows10&#xff0c;conda 4.13.0&#xff1b; 目录 conda环境配置安装Pytorch全家桶安装TensorRT&#xff08;…

[数据集][目标检测]裸土识别裸土未覆盖目标检测数据集VOC格式857张2类别

数据集格式&#xff1a;Pascal VOC格式(不包含分割路径的txt文件和yolo格式的txt文件&#xff0c;仅仅包含jpg图片和对应的xml) 图片数量(jpg文件个数)&#xff1a;857 标注数量(xml文件个数)&#xff1a;857 标注类别数&#xff1a;2 标注类别名称:["luotu","n…

Python网络爬虫中这七个li标签下面的属性值,不是固定的,怎样才能拿到他们的值呢?...

点击上方“Python爬虫与数据挖掘”&#xff0c;进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 愚以为宫中之事&#xff0c;事无大小&#xff0c;悉以咨之&#xff0c;然后施行&#xff0c;必能裨补阙漏&#xff0c;有所广益。 大家好&#xff0c;我…

Java8实战-总结21

Java8实战-总结21 使用流归约元素求和无初始值 最大值和最小值 使用流 归约 到目前为止&#xff0c;见到过的终端操作都是返回一个boolean(allMatch之类的)、void(forEach)或optional对象(findAny等)。也见过了使用collect来将流中的所有元素组合成一个List。 如何把一个流中…

r7 7840u和r7 7840hs差距 锐龙r77840u和r77840hs对比

锐龙7 7840U 采用Zen3架构、8核心16线程&#xff0c;基准频率疑似3.3GHz&#xff0c;同样集成RDNA3架构核显Radeon 780M&#xff0c;也是12个CU单元 r7 7840U 的处理器在 Cinebench R23 中多核跑分 14825 分 选r7 7840u还是 R7 7840HS这些点很重要 http://www.adiannao.cn/dy …

小红书笔记爬虫

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

codesys可视化

可视化有2种&#xff1a;本地和网页 触摸屏的话&#xff0c;属于网页。 1先配置IDE 如果有些控件&#xff0c;别人有&#xff0c;而你却没有&#xff0c;原因是&#xff1a;你库里没有引用。 比如缺少3D轨迹的控制面板&#xff0c;你需要库内引用 VisuStruct3DControl编译报错…

C 风格文件输入/输出 (std::fopen)(std::freopen)(std::fclose)

文件访问 打开文件 std::fopen std::FILE* fopen( const char* filename, const char* mode ); 打开 filename 所指示的文件并返回与该文件关联的流。用 mode 确定文件访问模式。 参数 filename-要关联文件流到的文件名mode-确定文件访问模式的空终止字符串 文件访问模式字…

sql:SQL优化知识点记录(十一)

&#xff08;1&#xff09;用Show Profile进行sql分析 新的一个优化的方式show Profile 运行一些查询sql&#xff1a; 查看一下我们执行过的sql 显示sql查询声明周期完整的过程&#xff1a; 当执行过程出现了下面这4个中的时&#xff0c;就会有问题导致效率慢 8这个sql创建…

【图解RabbitMQ-3】消息队列RabbitMQ介绍及核心流程

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;CSDN实力新星&#xff0c;后端开发两年经验&#xff0c;曾担任甲方技术代表&#xff0c;业余独自创办智源恩创网络科技工作室。会点点Java相关技术栈、帆软报表、低代码平台快速开…

linux线程讲解

1.线程概述 一个进程在同一时刻只做一件事情&#xff0c;进程是程序执行的一个实例。 线程是操作系统能够进行运算调度的最小单位&#xff0c;一个进程中可以并发多个线程&#xff0c;每条线程并行执行不同的任务。 进程&#xff1a;资源分配的最小单位。线程&#xff0c;程…

【vue2第十四章】 插槽(普通插槽、具名插槽、作用域插槽语法)

插槽 插槽是什么&#xff1f; 在 Vue 2 中&#xff0c;插槽&#xff08;slot&#xff09;是一种用于定义组件内部内容分发的机制。它允许你将组件中的一部分内容替换为用户自定义的内容&#xff0c;并在组件内部进行渲染。 通过在组件模板中使用 <slot></slot> 标…

yml配置动态数据源(数据库@DS)与引起(If you want an embedded database (H2, HSQL or Derby))类问题

1&#xff1a;yml 配置 spring:datasource:dynamic:datasource:master:url: jdbc:mysql://192.168.11.50:3306/dsdd?characterEncodingUTF-8&useUnicodetrue&useSSLfalse&tinyInt1isBitfalse&allowPublicKeyRetrievaltrue&serverTimezoneUTCusername: ro…

Ceph PG Peering数据修复

ceph数据修复 当PG完成了Peering过程后&#xff0c;处于Active状态的PG就可以对外提供服务了。如果该PG的各个副本上有不一致的对象&#xff0c;就需要进行修复。 Ceph的修复过程有两种&#xff1a;Recovery和Backfill。 Recovery是仅依据PG日志中的缺失记录来修复不一致的对…

Vue进阶(六十七)页面刷新路由传参丢失问题分析及解决

文章目录 一、前言二、问题排查三、延伸阅读3.1 Apache服务器access_log日志3.2 浏览器的常见User Agent 各字段的解释 一、前言 问题描述&#xff1a;Vue项目上线后&#xff0c;在IE浏览器上&#xff0c;从A页面跳转至B页面&#xff0c;B页面通过data中接收来自A页面的参数信…