[C++] string类常用接口的模拟实现

news2024/12/24 7:13:54

在这里插入图片描述

文章目录

  • 1、前言
  • 2、遍历
    • 2.1 operator[ ]+下标方式
    • 2.2 迭代器
    • 2.3 范围for
    • 2.4 c_str
  • 3、容量相关
    • 3.1 size(大小)
    • 3.2 capacity(容量)
    • 3.3 empty(判空)
    • 3.4 clear(清理)
    • 3.5 reserve
    • 3.6 resize
  • 4、增
    • 4.1 push_back(尾插)
    • 4.2 operator+=(char ch)
    • 4.3 append
    • 4.4 operator+=(char* str)
    • 4.5 insert(任意位置插入)
      • 4.5.1 任意位置插入字符
      • 4.5.2 任意位置插入字符串
  • 5、删
  • 6、查
    • 6.1 查找一个字符
    • 6.2 查找一个字符串
  • 7、字符串比较
  • 8、流插入、流提取重载
    • 8.1 流提取重载
    • 8.2 流插入重载

1、前言

string上篇我们实现了 各类构造与赋值重载 接口,点这里看string类的介绍与构造的模拟实现
本篇我们对string常用的接口 增删查改+遍历 模拟实现一下。

以下就是要实现的接口:

namespace s
{

  class string
  {
    friend ostream& operator<<(ostream& _cout, const bit::string& s);
    friend istream& operator>>(istream& _cin, bit::string& s);

  public:
    typedef char* iterator;

  public:
    //
    // iterator
    iterator begin();
    iterator end()/
    // modify
    void push_back(char c);
    string& operator+=(char c);
    void append(const char* str);
    string& operator+=(const char* str);
    void clear();
    void swap(string& s);
    const char* c_str()const;

    /
    // capacity
    size_t size()const
    size_t capacity()const
    bool empty()const
    void resize(size_t n, char c = '\0');
    void reserve(size_t n);

    /
    // access
    char& operator[](size_t index);
    const char& operator[](size_t index)const;

    /
    //relational operators
    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);

    // 返回c在string中第一次出现的位置
    size_t find (char c, size_t pos = 0) const;
    // 返回子串s在string中第一次出现的位置
    size_t find (const char* s, size_t pos = 0) const;
    // 在pos位置上插入字符c/字符串str,并返回该字符的位置
    string& insert(size_t pos, char c);
    string& insert(size_t pos, const char* str);    
    // 删除pos位置上的元素,并返回该元素的下一个位置
    string& erase(size_t pos, size_t len);

  private:
    char* _str;
    size_t _capacity;
    size_t _size;
  }
}

2、遍历

2.1 operator[ ]+下标方式

这种方式是我们最喜欢使用的一种,使用下标将字符逐个打印出来。

char& operator[](size_t pos)
{
    assert(pos < _size);

    return _str[pos];
}

//只读
const char& operator[](size_t pos) const
{
    assert(pos < _size);

    return _str[pos];
}

int main()
{
    string s1("hello world");

    for (int i = 0; i < s1.size(); ++i)
    {
    	cout << s1[i] << " ";
    }
    cout << endl;
    
    return 0;
}

在这里插入图片描述

2.2 迭代器

string类的迭代器的底层是一个 char 原生指针*,string的迭代器使用方法就像是使用指针一样来用就可以了,但是不是所有的迭代器都是指针。(list,对于顺序表来说,并不是连续的空间,因此底层就不是指针)。

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

我们来试一下迭代器的遍历:

int main()
{
    string s1("hello world");
    string::iterator it = s1.begin();
    while (it != s1.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    
    return 0;
}

2.3 范围for

我们在之前就接触过范围for,使用起来很简单,这次我们来深究一下范围for。

int main()
{
    string s1("hello world");
    for (auto ch : s1)//范围for的底层就是迭代器,这里很严格,begin大小写变了都会出问题
    {
        cout << ch << " ";
    }
    cout << endl;
    
    return 0;
}

我们来看看汇编代码中迭代器与范围for。
在这里插入图片描述
在这里插入图片描述

我们可以看到在汇编代码中,迭代器与范围for都是调用了begin与end函数**,这里我们就能看到范围for的底层就是范围for。**

注意:范围for是傻瓜式调用,我们将迭代器的名称字符改为大写范围for都使用不了。
在这里插入图片描述
在这里插入图片描述

2.4 c_str

在这里插入图片描述
由此我们知道,c_str返回的就是字符串以及末尾的 ‘\0’ ,因此我们就可以使用c_str来打印字符串。

const char* c_str() const
{
    return _str;
}
int main()
{
	cout << c_str << endl;
    return 0;
}

在这里插入图片描述

3、容量相关

3.1 size(大小)

直接返回字符串的大小,没什么讲的秒杀。

size_t size() const//对this不修改,加const保证安全
{
    return _size;
}

3.2 capacity(容量)

size_t capacity() const//对this不修改,加const保证安全
{
    return _capacity;
}

3.3 empty(判空)

bool empty() const//对this不修改,加const保证安全
{
    return _size == 0;
}

3.4 clear(清理)

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

这里我们不需要删掉字符串内容,直接将字符串首元素改为 ‘\0’,大小置为0即可。

3.5 reserve

在这里插入图片描述

由此我们可以得到,reserve函数特点是只扩不缩的。扩大到容量为n。

因此我们在实现的时候,先判断 n是否大于capacity,如果小于就不处理,大于进行扩容。

void reserve(size_t n)//reserve只扩不缩
{
    if (n > _capacity)
    {
        char* tmp = new char[n + 1];//多一个是\0的位置
        strcpy(tmp, _str);//strcpy会将\0一同拷贝过去
        delete[] _str;
        _str = tmp;

        _capacity = n;
    }
}

3.6 resize

在这里插入图片描述

由此我们可以看出resize是对字符串的size进行扩充的。
当n小于当前字符串的大小时,将字符串缩短到n,保留字符串前n个字符。
当n大于当前字符串的大小时,字符串长度就增加,如果有指定字符,就用指定字符对大于n的空间初始化,如果没有指定,就初始化为 ‘\0’。
我们对比一下resize与reserve:
resize是对size的更改,可扩可缩,并支持初始化,会间接影响容量的大小;
reserve是对capacity的更改,只能扩容,不支持初始化。

思路:
先判断 n是否大于size
如果小于,就是缩小,直接将n位置改为 ‘\0’,size改为n即可;
如果大于,还需要判断n是否超过capacity,不超过原地修改,超过直接复用reserve,对超出当前字符串长度的空间初始化为c,并将最后一个位置置为 ‘\0’(reserve只负责扩容,不做初始化)。

void resize(size_t n, char c = '\0')//c给为缺省最合适
{
    if (n <= _size)
    {
        _str[n] = '\0';
        _size = n;
    }
    else
    {
        if(n > _capacity)
	        reserve(n);
        
        while (_size < n)
        {
            _str[_size] = c;
            ++_size;
        }

        _str[_size] = '\0';
    }
}

测试:

int main()
{
    string s1("hello world");
    s1.resize(5);
    cout << s1 << endl;

    s1.resize(8, 'x');
    cout << s1 << endl;
    
    return 0;
}

在这里插入图片描述

4、增

4.1 push_back(尾插)

思路:
先判满,如果满了(_size == _capacity),就扩容(二倍方式扩容),再进行尾插字符;
没有满,直接尾插字符。

void push_back(char ch)
{
    if (_size == _capacity)
    {
        //reserve(2 * _capacity);//直接给二倍的话,对于空字符串来说,
                                 //二倍还是0,扩容里面有释放空间,会出问题
        reserve(_capacity == 0 ? 4 : 2 * _capacity);//三目才正确
    }

    _str[_size] = ch;
    _size++;
    _str[_size] = '\0';//别忘了还有\0,size指的是最后一个字符的下一个位置
}

注意:对于扩容的传参我们使用三目操作符,防止直接对空字符串进行尾插时给二倍,相当于没有扩容。

4.2 operator+=(char ch)

+=字符比尾插更常用,很有必要实现。逻辑与尾插是一致的,复用push_back就可以。

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

4.3 append

在这里插入图片描述

由此我们知道,append函数是在原有的字符串基础上追加字符串。

此函数不常用,我们仅实现第三个接口就可以了。
思路:
先判断容量是否足够,不够先扩容,但是append的扩容与尾插的扩容的大小不一样,尾插在原容量基础上扩二倍一定足够,这里是插入字符串,扩二倍不一定足够,我们来分析一下:
判断容量是否足够其实不难,我们对追加的字符串计算长度(strlen(str)),看看被追加的字符串+追加的字符串长度(_size+len)是否大于_capacity,大于就扩容,复用reserve,传值 size+len;
再使用strcpy函数,从字符串的_size位置开始,将追加的字符串拷贝到_str后面。
代码实现:

void append(const char* str)
{
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        reserve(_size + len);//这里扩容不能是二倍,因为插入的字符串长度可能大于二倍
    }

    strcpy(_str + _size, str);//strcpy会将\0一起拷贝过去
    _size += len;
}

4.4 operator+=(char* str)

+=很常用,尾插字符串,直接复用刚写的append即可。

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

4.5 insert(任意位置插入)

在这里插入图片描述

虽然insert的函数很多,但是常用的就两个,在任意位置插入字符或字符串,我们就来实现这两个。

4.5.1 任意位置插入字符

思路:
1、判断pos位置是否合法。
2、先判断是否需要扩容,与尾插一样;
3、挪动数据,从_size位置开始,依次往后挪,直到到pos位置停下来;
4、在pos位置插入数据,再++_size。
在这里插入图片描述

代码实现:

string& insert(size_t pos, char ch)
{
    assert(pos <= _size);
    if (_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    }
    
    size_t end = _size + 1;
    while (end > pos)//size_t是无符号整型,头插时end走到-1还是会进去,形成死循环
    {
        _str[end] = _str[end - 1];
        --end;
    }
    _str[end] = ch;
    _size++;

    return *this;
}

4.5.2 任意位置插入字符串

思路与任意位置插入字符是类似的,只需要注意一下边界就好了。
在这里插入图片描述

代码实现:

string& insert(size_t pos, const char* str)
{
    assert(pos <= _size);
    size_t len = strlen(str);

    if (_size + len > _capacity)
    {
        reserve(_size + len);
    }

    //挪动数据
    int end = _size;
    while (end >= (int)pos)//这里也存在提升,end变为-1仍然进去循环,因此需要强转
    {
        _str[end + len] = _str[end];
        --end;
    }
    strncpy(_str + pos, str, len);
    _size += len;

    return *this;
}

5、删

在这里插入图片描述

这里的npos我们再来看看
在这里插入图片描述

我们可以看到文档中nops是size_t类型(无符号整型),值为-1,无符号整型的-1就是int类型的max。
对于erase函数,我们仅实现常用的第一个接口就可以。
文档中可以看到,erase是从pos位置开始往后删长度为len个字符。
这里erase我们要分情况来讨论:
情况一:len == npos 或者 pos+len >= _size,那么就是从pos位置到末尾全部删除,我们直接将pos位置改为 ‘\0’,_size改为pos即可。
在这里插入图片描述

情况二:删除其中的一段,我们定义 begin=pos+len,_str[begin-len] = _str[begin],begin++不断挪动数据,当 begin==_size 的时候挪动完数据。因此循环结束的条件是 begin<=_size,这时把 ‘\0’ 也就挪过去了。
在这里插入图片描述

string& erase(size_t pos, size_t len = npos)
{
    assert(pos < _size);

    if (len == npos || pos + len >= _size)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
        size_t begin = pos + len;
        while (begin <= _size)
        {
            _str[begin - len] = _str[begin];
            ++begin;
        }
        _size -= len;
    }

    return *this;
}

6、查

在这里插入图片描述

6.1 查找一个字符

由文档中我们可以知道,找到后返回的是字符的位置,即就是下标。如果没有找到返回npos。

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

    return npos;
}

6.2 查找一个字符串

我们使用strstr来找。

size_t find(const char* s, size_t pos = 0) const
{
    const char* p = strstr(_str + pos, s);
    if (p)
    {
        return p - _str;
    }
    else
    {
        return npos;
    }
}

7、字符串比较

比较的本质是ASCII码值,因此我们套用strcmp就可以。

bool operator<(const string& s)
{
    return strcmp(_str, s._str) < 0;
}

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

写好<与==,其他的直接复用就可以。

8、流插入、流提取重载

8.1 流提取重载

流提取使用在赋值或初始化上,因此,在赋值前我们先对空间清理一下,在对变量赋值。

istream& operator>>(istream& _cin, string& s)
{
    s.clear();

    char buff[129];//这样可以避免扩容问题
    size_t i = 0;

    char ch;
    //_cin >> ch//本身是拿不到' '或'\n'的,因此循环条件就判断不了
    ch = _cin.get();

    while (ch != ' ' && ch != '\n')
    {
        buff[i++] = ch;
        if (i == 128)
        {
            buff[i] = '\0';
            s += buff;
            i = 0;//下一轮
        }
        //_cin >> ch;
        ch = _cin.get();
    }
    if (i != 0)
    {
        buff[i] = '\0';
        s += buff;
    }

    return _cin;
}

8.2 流插入重载

ostream& operator<<(ostream& _cout, const string& s)
    //for (int i = 0; i < s.size(); ++i)
    //{
    //	_cout << s[i];
    //}
    for (auto ch : s)
    {
        _cout << ch;
    }

    return _cout;
}

*** 本篇结束 ***

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

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

相关文章

ZooKeeper各服务器角色介绍

1 Leader Leader服务器是整个ZooKeeper集群工作机制中的核心&#xff0c;其主要工作有以下两个。 事务请求的唯一调度和处理者&#xff0c;保证集群事务处理的顺序性。 集群内部各服务器的调度者。 请求处理链 使用责任链模式来处理每一个客户端请求是ZooKeeper的一大特色。…

如何制作gif动态图片?多图片转gif的简单方法

看到网上各种各样的表情包&#xff0c;自己也想试试&#xff0c;怎么将多张图片进行gif合成&#xff08;https://www.gif.cn&#xff09;呢&#xff1f;可以在线生成gif动图的方法&#xff0c;打开浏览器就可以完成gif图片制作&#xff0c;下面是具体步骤。 打开网站&#xff…

亿发智能制造MES系统-实现互联网时代下贵州制造业全流程高效生产

在工业4.0和智能制造的推动下&#xff0c;贵州省制造类企业对于MES系统的建设需求逐年增长&#xff0c;日益认识到智能制造MES系统的重要性。不单是行业的助推器&#xff0c;更是企业走向智能化制造的重要环机。选择适合企业特点的智能制造MES系统&#xff0c;助力贵州省制造业…

Spring6.0官方文档示例:(28)多种方式添加BeanPostProcessor

一、定义三个BeanPostProcessor package cn.edu.pku;import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component;Component public class MyScannedBeanPostPr…

RHCE——五、Web服务器及内网穿透(实验篇)

Web服务器篇 一、快速搭建网站二、更改网页启动目录三、内网穿透1、准备工作2、搭建网站3、测试&#xff1a;浏览器中输入ip进行测试4、使用花生壳进行内网穿透4.1 注册并登陆4.2 点击添加映射4.3 Linux安装花生壳客户端 5、注意 四、搭建具有身份验证功能网站1、准备工作2、编…

发票的种类

目录 增值税专用发票 增值税普通发票 增值税电子普通发票 机打式发票 定额发票 机动车销售统一发票 增值税专用发票 填写增值税发票时&#xff0c;单位全称、纳税人识别号、统一信用代码必填项且正确无误。专票必须填写&#xff0c;普票可不填写&#xff0c;若填写则必须…

CNN-LSTM选A股牛股(代码+数据+一键可运行)

在当前金融市场中&#xff0c;投资者对于高效的股票推荐系统需求不断增加。为了满足这一需求&#xff0c;我们开发了一款基于人工智能的牛股推荐器V1.0&#xff0c;其技术路线和方法在本文中将得到详细阐述。 全代码和数据关注公众号《三个篱笆三个班》免费提供&#xff01;一…

CentOS7安装Oracle11g 11.2.0.4

一、安装环境 CentOS Linux release 7.2.1511 (Core) Oracle Database 11g Release 2 (11.2.0.4) 二、安装前准备 2.1 修改主机名 修改/etc/sysconfig/network配置文件中的HOSTNAME变量 [rootxqzt ~]# hostnamectl set-hostname oracledb####永久性修改[rootxqzt ~]#vi /etc/sy…

茶叶病害识别(Python代码,pyTorch框架,深度卷积网络模型,很容易替换为其它模型,带有GUI识别界面)

代码运行要求&#xff1a;Torch库>1.13.1即可 1.茶叶病害数据集(7类病害和1种正常) 1.茶叶病害数据集介绍(这个茶病数据集包含茶叶&#xff0c;显示了茶的7种常见疾病&#xff1a; 红叶斑 藻类叶斑 bird eye spot&#xff1b; 灰枯病&#xff1b; 白点&#xff1b; 炭…

ps怎么布尔运算多个图层合并?

我们经常使用Photoshop制作大型海报类&#xff0c;也可以用ps进行一些简单icon小图标的制作&#xff0c;这些icon图标多数应用在工具按钮上&#xff0c;比较小巧美观。但是对于ps对图形的操作经常会用到布尔运算的使用&#xff0c;今天小编就给大家详细讲解下ps布尔运算多个图层…

npm、yarn和pnpm

1 node_modules安装方式 在npm3之前是以嵌套结构方式安装依赖包&#xff0c;存在两个问题&#xff1a; 依赖路径太长多个包依赖一个相同包时&#xff0c;本地磁盘会存储多个相同的包 npm3和yarn使用扁平化结构&#xff0c;node_modules变成所有包放在同一层级 注意&#xf…

零代码ETL+聚水潭,实现销售出库单同步到数仓

一、聚水潭单据同步需求 聚水潭作为领先的电商ERP有很多快销、零售企业使用&#xff0c;同时作为以订单为核心的电商ERP系统企业还需要在本地配合其他业务系统一起使用完全整个业务的协同和财务结算&#xff0c;作为中大型企业随着业务发展企业会在聚水潭中沉淀大量的业务数据…

技术债务的深度探索:从累积到偿还的全景视角

1. 技术债务的定义与起源 什么是技术债务 技术债务&#xff0c;这个词汇在软件开发领域中经常被提及。但是&#xff0c;什么是技术债务呢&#xff1f;简单来说&#xff0c;技术债务是指为了短期的收益而做出的技术上的妥协&#xff0c;这些妥协可能会在未来导致更多的工作。它…

详解VAE(变分自编码器)

变分自编码器-VAE 前言一、AE&#xff08;auto-encoders&#xff09;-自编码器1.AE整体结构及公式推导2.AE的特点 二、 VAE(Variational auto-encoder)-变分自编码器1.VAE模型结构2.理论推导2.1变分下界&#xff08;Variational Lower bound&#xff09;/变分推理最小化KL散度最…

孤注一掷中的黑客技术

最近孤注一掷电影很火&#xff0c;诈骗团伙的骗术实在厉害&#xff0c;就连电影中的黑客潘生都未能幸免。电影中的陆经理说&#xff1a;不是我们坏&#xff0c; 是他们贪。这句话我觉得有一部分是对的&#xff0c;诈骗分子抓住了人的本性贪婪&#xff0c;才使得被骗的人逐步走向…

HTML概述

1.HTML介绍&规范 1.1介绍 HTML 指的是超文本标记/标签语言 (Hyper Text Markup Language) 普通的文本就是英文单词&#xff0c;英文字母一样的存在。 超文本的意思是有一些单词或字母&#xff0c;在网页浏览器的世界中被赋予了特殊的权利。 比如:我们都是普通人&#x…

Java进阶(6)——抢购问题中的数据不安全(非原子性问题) Java中的synchronize和ReentrantLock锁使用 死锁及其产生的条件

目录 引出场景&#xff1a;大量请求拥挤抢购事务的基本特征ACID线程安全的基本特征 加锁(java)synchronized锁ReentrantLock锁什么是可重入锁&#xff1f;如何保证可重入 滥用锁的代价&#xff1f;&#xff08;死锁&#xff09;死锁的四个必要条件死锁的案例 总结 引出 1.大量请…

pytorch安装VAE项目详解

安装VAE项目 一、 基本环境二、代码来源三、搭建conda环境四、下载数据集五、启动项目六、其他相关问题 一、 基本环境 工具版本号OSwin 11pycharm2020.1GPU3050 二、代码来源 github地址为&#xff1a; https://github.com/AntixK/PyTorch-VAE/blob/8700d245a9735640dda458d…

Mybatis-动态sql和分页

目录 一.什么是Mybatis动态分页 二.mybatis中的动态SQL 在BookMaaper.xml中写sql BookMapper BookBiz接口类 BookBizImpl实现接口类 demo测试类 ​编辑 测试结果 三.mybatis中的模糊查询 mybatis中的#与$有是什么区别 在BookMapper.xml里面建立三个模糊查询 ​编辑 …

校园人员进出入登记系统 微信小程序

利用eclipse编译器和微信开发者工具进行运行高校人员进出管理系统&#xff0c;用户需要登录完成之后才可以进行申请进出学校。管理员在登录系统之后具有的功能包括个人中心&#xff0c;学生管理&#xff0c;教师管理&#xff0c;申请出校管理&#xff0c;出校批准管理&#xff…