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

在这里插入图片描述

文章目录

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

*** 本篇结束 ***

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

生成模型 -- GAN

文章目录 1. 生成模型与判别模型1.1 生成模型 2. VAE3. GAN3.1 GAN-生成对抗网络3.2 GAN-生成对抗网络的训练3.2.1 判别模型的训练&#xff1a;3.2.2 生成网络的训练&#xff1a; 4. LeakyReLU5. GAN代码实例 1. 生成模型与判别模型 生成模型与判别模型 我们前面几章主要介绍了…

UE4 材质学习笔记

CheapContrast与CheapContrast_RGB都是提升对比度的&#xff0c;一个是一维输入&#xff0c;一个是三维输入&#xff0c;让亮的地方更亮&#xff0c;暗的地方更暗&#xff0c;不像power虽然也是提升对比度&#xff0c;但是使用过后的结果都是变暗或者最多不变&#xff08;值为1…

Mybatis简单入门

星光下的赶路人star的个人主页 夏天就是吹拂着不可预期的风 文章目录 1、Mybatis介绍1.1 JDBC痛点1.2 程序员的诉求1.3 Mybatis简介 2、数据准备2.1 数据准备2.2 建工程2.3 Employee类2.4 Mybatis的全局配置2.5 编写要执行的SQL2.6 编写java程序2.7 稍微总结一下流程 3、解决属…

什么是安全测试报告,怎么获得软件安全检测报告?

安全测试报告 软件安全测试报告&#xff1a;是指测试人员对软件产品的安全缺陷和非法入侵防范能力进行检查和验证的过程&#xff0c;并对软件安全质量进行整体评估&#xff0c;发现软件的缺陷与 bug&#xff0c;为开发人员修复漏洞、提高软件质量奠定坚实的基础。 怎么获得靠谱…

Hadoop学习一(初识大数据)

目录 一 什么是大数据&#xff1f; 二 大数据特征 三 分布式计算 四 Hadoop是什么? 五 Hadoop发展及版本 六 为什么要使用Hadoop 七 Hadoop vs. RDBMS 八 Hadoop生态圈 九 Hadoop架构 一 什么是大数据&#xff1f; 大数据是指无法在一定时间内用常规软件工具对其内…

昌硕科技、世硕电子同步上线法大大电子合同

近日&#xff0c;世界500强企业和硕联合旗下上海昌硕科技有限公司&#xff08;以下简称“昌硕科技”&#xff09;、世硕电子&#xff08;昆山&#xff09;有限公司&#xff08;以下简称“世硕电子”&#xff09;的电子签项目正式上线。上线仪式在上海浦东和硕集团科研大楼举行&…

渗透测试方法论

文章目录 渗透测试方法论1. 渗透测试种类黑盒测试白盒测试脆弱性评估 2. 安全测试方法论2.1 OWASP TOP 102.3 CWE2.4 CVE 3. 渗透测试流程3.1 通用渗透测试框架3.1.1 范围界定3.1.2 信息搜集3.1.3 目标识别3.1.4 服务枚举3.1.5 漏洞映射3.1.6 社会工程学3.1.7 漏洞利用3.1.8 权…

Java课题笔记~ SpringBoot基础配置

二、基础配置 1. 配置文件格式 问题导入 框架常见的配置文件有哪几种形式&#xff1f; 1.1 修改服务器端口 http://localhost:8080/books/1 >>> http://localhost/books/1 SpringBoot提供了多种属性配置方式 application.properties server.port80 applicati…

jmeter HTTP请求默认值

首先&#xff0c;打开JMeter并创建一个新的测试计划。 右键单击测试计划&#xff0c;选择"添加" > “配置元件” > “HTTP请求默认值”。 在HTTP请求默认值中&#xff0c;您可以设置全局的HTTP请求属性&#xff0c;例如&#xff1a; 服务器地址&#xff1a…

神经网络简单理解:机场登机

目录 神经网络简单理解&#xff1a;机场登机 ​编辑 激活函数&#xff1a;转为非线性问题 ​编辑 激活函数ReLU 通过神经元升维&#xff08;神经元数量&#xff09;&#xff1a;提升线性转化能力 通过增加隐藏层&#xff1a;增加非线性转化能力​编辑 模型越大&#xff0c;…

uniapp日期选择组件优化

<uni-forms-item label="出生年月" name="birthDate"><view style="display: flex;flex-direction: row;align-items: center;height: 100%;"><view class="" v-

【图论】最短路的传送问题

一.分层图问题&#xff08;单源传送&#xff09; &#xff08;1&#xff09;题目 P4568 [JLOI2011] 飞行路线 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) &#xff08;2&#xff09;思路 可知背景就是求最短路问题&#xff0c;但难点是可以使一条路距离缩短至0&#xf…

一、数学建模之线性规划篇

1.定义 2.例题 3.使用软件及解题 一、定义 1.线性规划&#xff08;Linear Programming&#xff0c;简称LP&#xff09;是一种数学优化技术&#xff0c;线性规划作为运筹学的一个重要分支&#xff0c;专门研究在给定一组线性约束条件下&#xff0c;如何找到一个最优的决策&…

绿盾客户端字体库文件被加密了,预览不了

环境: 绿盾客户端7.0 Win10 专业版 问题描述: 绿盾客户端字体库文件被加密了,预览不了 预览不了 解决方案 1.打开控制台 2.进入规则中心 3.找到对应的操作员类型 4.选择自定义程序 5.右键新建程序,填最开始获得的程序名,可执行程序选择本地程序,我本地没有这个…

pytest之parametrize参数化

前言 我们都知道pytest和unittest是兼容的&#xff0c;但是它也有不兼容的地方&#xff0c;比如ddt数据驱动&#xff0c;测试夹具fixtures&#xff08;即setup、teardown&#xff09;这些功能在pytest中都不能使用了&#xff0c;因为pytest已经不再继承unittest了。 不使用dd…

PHP8中自定义函数-PHP8知识详解

1、什么是函数&#xff1f; 函数&#xff0c;在英文中的单词是function&#xff0c;这个词语有功能的意思&#xff0c;也就是说&#xff0c;使用函数就是在编程的过程中&#xff0c;实现一定的功能。即函数就是实现一定功能的一段特定代码。 在前面的教学中&#xff0c;我们已…

如何进行在线pdf转ppt?在线pdf转ppt的方法

在当今数字化时代&#xff0c;PDF文件的广泛应用为我们的工作和学习带来了巨大的便利。然而&#xff0c;有时候我们可能需要将PDF转换为PPT文件&#xff0c;以便更好地展示和分享内容。在线PDF转PPT工具因其操作简便、高效而备受欢迎。如何进行在线pdf转ppt呢?接下来&#xff…

kafka--技术文档-基本概念-《快速了解kafka》

学习一种新的消息中间键&#xff0c;卡夫卡&#xff01;&#xff01;&#xff01; 官网网址 Apache Kafka 基本概念 Kafka是一种开源的分布式流处理平台&#xff0c;由Apache软件基金会开发&#xff0c;用Scala和Java编写。它是一个高吞吐量的分布式发布订阅消息系统&#xf…

c++ qt--信号与槽(二) (第四部分)

c qt–信号与槽(二) &#xff08;第四部分&#xff09; 一.信号与槽的关系 1.一对一 2.一对多 3.多对一 4.多对多 还可以进行传递 信号->信号->槽 一个信号控制多个槽的例子&#xff08;通过水平滑块控制两个组件&#xff09; 1.应用的组件 注意这里最下面的组件…

(五)Docker 安装 redis镜像+启动redis容器(超详细)

输入&#xff1a;su root命令&#xff0c;切换到root 1、启动Docker 启动&#xff1a;sudo systemctl start docker 停止&#xff1a;systemctl stop docker 重启&#xff1a;systemctl restart docker 查看docker运行状态&#xff08;显示绿色代表正常启动&#xff09;&#x…