『C++成长记』string模拟实现

news2024/12/23 19:57:09

🔥博客主页:小王又困了

📚系列专栏:C++

🌟人之为学,不日近则日退

❤️感谢大家点赞👍收藏⭐评论✍️

目录

一、存储结构

二、默认成员函数

📒2.1构造函数

📒2.2析构函数

📒2.3拷贝构造

📒2.4赋值重载

三、容量操作

📒3.1获取有效字符长度

📒3.2获取对象空间大小

📒3.3使用reserve扩容

四、字符串的遍历

📒4.1下标访问

📒4.2迭代器访问

五、修改操作

📒5.1尾插字符

📒5.2尾插字符串

📒5.3任意位置插入字符

📒5.4任意位置插入字符串

📒5.5+=重载

六、其他操作

📒6.1删除操作

📒6.2查找操作

📒6.3交换操作

📒6.4获取字符串

📒6.5运算符重载

📒6.6清理字符串

📒6.7流操作


🗒️前言:

在上一篇中我们对string类进行了简单的介绍,介绍了各个接口的作用和使用方法,今天我们将为大家介绍string常用接口的模拟实现。

一、存储结构

        string本质上是一个char类型的顺序表,所以结构上和顺序表类似。

namespace bit
{
    class string
    {
    public:

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

        const static size_t npos;
    };
}

结构上使用命名空间 bit 进行封装,防止与库冲突,其中:

  • _str :指向存放字符串存空间的指针
  • _size :表示当前所存储的有效字符个数
  • _capacity :表示当前可存储的最大容量
  • nops:此值设置为 -1,无符号整型转换就是42亿,且此值为const和静态参数具有全局效应,这个值常常被用来当作循环结束判断条件和查找时未找到的标志,某些函数设置其为缺省参数。

nops的初始化:

#include"string.h"

namespace bit
{
	 const  size_t string::nops = -1;
}

小Tips:我们使用声明与定义分离实现,nops只能在CPP文件中定义,因为类里面的静态成员变量相当于全局变量,在.h文件中定义会出现链接错误。我们还要通过类名::成员(函数/变量) 定义和实现函数!

二、默认成员函数

📒2.1构造函数

string.h
string(const char* str = "");  //给缺省值 构造空串

string.cpp
string::string(const char* str)
    :_size(strlen(str))
{
    _str = new char[_size + 1];
    _capacity = _size;
    strcpy(_str, str);
}

构造函数的思路:

  • 构造函数可以接收字符串,如果没有参数,默认构造一个空串
  • 通过strlen先计算出字符串的长度,并通过初始化列表初始化_size
  • 使用new开辟空间,这里我们要多开一个空间存放‘\0’
  • 最终将字符串中的字符拷贝到我们所开的空间中

小Tips:因为_size的大小没有包含‘\0’,所以我们要多开辟一个空间。

📒2.2析构函数

        我们开辟内存是使用 new[ ] 申请的,所以对应使用 delete[ ]释放

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

📒2.3拷贝构造

        拷贝构造函数,如果我们不写,编译器会默认生成一个,但是默认生成的拷贝构造函数只支持浅拷贝,新构造的对象只是拷贝了_str的指针地址,两个对象都指向同一块空间,最终两个对象析构时释放同一片空间的资源势必会导致程序崩溃

我们需要新构造的对象通过拷贝构造开辟一片新的空间将数据复制过去,也就是深拷贝,需要我们自己写一个拷贝构造。

 🌟传统写法:

string::string(const string& s)
{
    _str = new char[s._size + 1];
    strcpy(_str, s._str);
    _size = s._size;
    _capacity = s._capacity;
}

 🌟现代写法:

string::string(const string& s)
{
    string tmp(s._str);
    swap(tmp);
}
//string s2(s1);

 通过复用构造函数,构造出tmp对象,在将两个对象进行交换,就可以实现拷贝构造。

📒2.4赋值重载

        赋值重载需要注意自己给自己赋值这种冗余的行为,同时也要控制空间大小

  🌟传统写法:

string& string::operator=(const string& s)
{
    if(this != &s)
    {
        char* tmp = new char[s._size + 1];
        strcpy(tmp, s._str);
        delete[] _str;
        _str = tmp;
        _size = s._size;
        _capacity = s._capacity;
    }
    return *this;
}

 🌟现代写法:

string& string::operator=(const string& s)
{
    if (this != &s)
    {
        string tmp(s._str);
        swap(tmp);
    }
    return *this;
}

string& string::operator=(string tmp)
{
    swap(tmp);
    return *this;   
}

三、容量操作

📒3.1获取有效字符长度

size_t string::size() const
{
    return _size;
}

小Tips

  • 这个函数比较小,可以写在类中形成内联函数。 
  • 对于不涉及对字符串的增删查改的函数,使用const修饰this增强安全性。

📒3.2获取对象空间大小

        与size函数规则一致。

size_t string::capacity() const
{
    return _capacity;
}

📒3.3使用reserve扩容

void string::reserve(size_t n)
{
    char* tmp = new char[n + 1];
    strcpy(tmp, _str);    //将原字符串的数据拷贝到新空间上
    delete[] _str;        //释放原字符串的空间
    _str = tmp;
    _capacity = n;
}

四、字符串的遍历

📒4.1下标访问

        下标访问是通过重载 [ ] 运算符实现的,在下标pos正确的情况下,返回当前下标字符的引用,否则assert报错。

char& string::operator[](size_t pos)
{
    assert(pos < _size);
    return _str[pos];
}

📒4.2迭代器访问

        现在我们可以简单认为迭代器是指针对象,就是对指针的封装。

//迭代器的声明
typedef char* iterator;
typedef const char* const_iterator;  //对数据无法修改

迭代器的begin返回字符串的地址,end返回字符串末端的下一个即‘\0’

string::iterator string::begin()
{
    return _str;
}

string::iterator string::end()
{
    return _str + _size;
}

string::const_iterator string::begin()const
{
    return _str;
}

string::const_iterator string::end()const
{
    return _str + _size;
}

五、修改操作

📒5.1尾插字符

        在插入字符前,先要判断是否需要扩容。

void string::push_back(char ch)
{
    if (_size == _capacity)
    {
        size_t newcapacity = _capacity == 0 ? 4: _capacity * 2;
        reserve(newcapacity);
    }
    _str[_size] = ch;
    _str[_size + 1] = '\0';
    _size++;
}

📒5.2尾插字符串

void string::append(char* s)
{
    size_t len = strlen(s);
    if (_size +len > _capacity)
    {
        reserve(_size + len);
    }
    strcpy(_str + _size, s);
    _size += len;
}

📒5.3任意位置插入字符

        我们要考虑头插的位置,endpos的类型都是size_t,代码会陷入死循环,这里我们提供两种解决方法。

🌟方法一:

        将end的类型定为int,同时将pos强转为int型。

void string::insert(size_t pos, char ch)
{
    assert(pos <= _size);
		
    if (_size == _capacity)
    {
        size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
        reserve(newcapacity);
    }

    int end = _size;
    while (end >= (int)pos)
    {
        _str[end + 1] = _str[end];
        --end;
    }
    _str[pos] = ch;
    ++_size;
}

 🌟方法二:

        将end定位到‘\0’的下一位。

void string::insert(size_t pos, char ch)
{
    assert(pos <= _size);
	
    if (_size == _capacity)
    {
        size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
        reserve(newcapacity);
    }

    size_t end = _size + 1;
    while (end > pos)
    {
        _str[end] = _str[end - 1];
        --end;
    }
    _str[pos] = ch;
    ++_size;
}

我们可以通过复用来实现尾插

void string::push_back(char ch)
{
    insert(_size, ch);
}

📒5.4任意位置插入字符串

void string::insert(size_t pos, const char* s)
{
    assert(pos <= _size);
    size_t len = strlen(s);
    if (_size + len > _capacity)
    {
        reserve(_size + len);
    }
    //方法一:
    //int end = _size;
    //while (end >= (int)pos )
    //{
    //    _str[end + len] = _str[end];
    //    --end;
    //}
    
    //方法二:
    size_t end = _size+len;
    while (end > pos+len-1)
    {
        _str[end] = _str[end - len];
        --end;
    }		
    memcpy(_str + pos, s, len);
    _size += len;
}

我们也可以通过复用来实现尾插字符串

void string::append(const char* str)
{
    insert(_size, str);
}

📒5.5+=重载

        +=运算符可以在当前字符串尾部追加字符或字符串,我们可以通过复用push_backappend函数来实现。

//追加字符
string& string::operator+=(char ch)
{
    push_back(ch);
    return *this;
}

//追加字符串
string& string::operator+=(const char* str)
{
    append(str);
    return *this;
}

六、其他操作

📒6.1删除操作

        erasepos下标开始删除len个字符,其中len是一个缺省参数,默认是npos如果没有传值或len超过从pos位置开始到字符串尾部的字符个数则默认从pos位置开始删除后面的所有字符,且不允许在空串的情况下进行删除!

void string::erase(size_t pos, size_t len)
{
    assert(pos < _size);
    if (len >= _size - pos)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
    }
}

📒6.2查找操作

        查找函数是find,是从pos位置开始查找,可以查找一个字符或一个子串,查找到后字符返回下标,字符串返回首字符的地址,如果有多个重复的字符或字符串,返回查找到的第一个字符的下标或字符串首的下标;如果没找到则返回npos

🌟查找一个字符:

size_t string::find(char ch, size_t pos)
{
    for (size_t i = pos; i < _size; i++)
    {
        if (_str[i] == ch)
        return i;
    }
    return npos;
}

🌟查找一个子串

size_t string::find(const char* str, size_t pos )
{
    char* p = strstr(_str + pos, str);
    return  p - _str;
}

📒6.3交换操作

void string::swap(string& s)
{
    std::swap(_str, s._str);
    std::swap(_size, s._size);
    std::swap(_capaicty, s._capaicty);
}

📒6.4获取字符串

string string::substr(size_t pos, size_t len)
{
    assert(pos < _size);
    if (len > _size - pos)
    {
        string sub(_str + pos);
        return sub;
    }
    else
    {
        string sub;
        sub.reserve(len);
        for (size_t i = 0; i < len; i++)
        {
            sub += _str[pos + i];
        }
        return sub;
    }
}

📒6.5运算符重载

        逻辑判断运算符只需要实现两个,其余的通过复用就可以全部实现。 

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

bool string::operator>(const string& s) const
{
    return !(*this <= s);
}

bool string::operator<=(const string& s) const
{
    return *this < s || *this == s;
}

bool string::operator>=(const string& s) const
{
    return !(*this < s);
}

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

bool string::operator!=(const string& s) const
{
    return !(*this == s);
}

📒6.6清理字符串

        clear函数支持清空一个字符串,但不是释放对象,区别于析构函数。clear函数清理字符串并不会引起缩容,只是在下标0位置置为 \0 _size置为0即可。

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

📒6.7流操作

        流操作属于iostream中的对象,所以不需要定义在类中作为成员函数,也不需要声明为友元,因为使用流体去和流插入需要ostreamistream对象作为左操作参数。

🌟流插入

ostream& operator<< (ostream& os, const string& str)
{
    for (size_t i = 0; i < str.size(); i++)
    {
        os << str[i];
    }
    return os;
}

🌟流提取

istream& Mystring::operator>>(istream& in, string& s)
{
    s.clear(); 
    char buff[256] = {0}; 
    char ch = in.get(); 
    size_t sub = 0; 
	
    while (ch != ' ' && ch != '\n') //当缓冲区中有空格和换行就结束提取
    {
        buff[sub++] = ch; 
        if (sub == 255) 
        {
            buff[sub] = '\0'; 
            s += buff; 
            sub = 0; 
        }
        ch = in.get(); 
    }
    if (sub != 0) 
    {
        buff[sub] = '\0'; 
        s += buff;
    }

    return is; 
}

小Tips:我们定义一个缓冲区buff,先将字符串输入到缓冲区中,如果字符串很长则分批写入string字符串中,每次写入string后就刷新缓冲区再继续接收,这样就避免了频繁开辟空间。


🎁结语: 

     本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者三连支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。你们的支持就是博主最大的动力。

 

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

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

相关文章

C语言调试技巧

叠甲&#xff1a;以下文章主要是依靠我的实际编码学习中总结出来的经验之谈&#xff0c;求逻辑自洽&#xff0c;不能百分百保证正确&#xff0c;有错误、未定义、不合适的内容请尽情指出&#xff01; 文章目录 1.bug 是什么2.debug 是什么3.两种模式的区别4.IDE 调试介绍4.1.模…

满足信创环境运行的国产FTP服务器是什么样的?

2018 年以来&#xff0c;受“华为、中兴事件”影响&#xff0c;我国科技尤其是上游核心技术受制于人的现状对我 国经济发展提出了严峻考验。在全球产业从工业经济向数字经济升级的关键时期&#xff0c;中国明确 “数字中国”建设战略&#xff0c; 抢占数字经济产业链制高点。 在…

RK3588编译rkmpp,拉取海康威视网络摄像头264码流并运行yolo

硬件&#xff1a;EVB评估版 SOC&#xff1a;Rockchip RK3588 背景&#xff1a; 由于项目需要&#xff0c;需要拉取264码流&#xff0c;并通过将yolov5s.pt将模型转化为rknn模型&#xff0c;获取模型分析结果。取流可以通过软件解码或者硬件解码&#xff0c;硬件解码速度更快&…

tesseract-ocr 字库训练(提高识别率进阶版)

github字库地址&#xff1a;https://github.com/tesseract-ocr/tessdata 一、tesseract-ocr字库训练 1、配置 jdk 环境变量 步骤&#xff08;略&#xff09; 2、安装 tesseract-ocr 并配置环境变量 下载地址&#xff1a;https://digi.bib.uni-mannheim.de/tesseract/ 配置环境…

无人机群辅助边缘计算系统的任务卸载和资源分配联合优化

源自&#xff1a;系统工程与电子技术 作者&#xff1a;刘世豪 黄仰超 胡航 司江勃 韩蕙竹 安琪 注&#xff1a;若出现无法显示完全的情况&#xff0c;可 V 搜索“人工智能技术与咨询”查看完整文章 摘 要 为提升无人机群辅助边缘计算系统在负载不均衡场景下的性能, 构…

红黑树模拟实现

概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出俩倍&#xff0c;因而是接近平衡…

使用GZip对npm run build打包的vendor.js文件进行压缩

vue-cli项目 安装npm i compression-webpack-plugin -D npm i compression-webpack-plugin -D使用&#xff1a;在vue.config.js文件中 const CompressionPlugin require(compression-webpack-plugin) module.exports {configureWebpack: {plugins: [new CompressionPlugin…

html+css+js贪吃蛇游戏

贪吃蛇游戏&#x1f579;四个按钮控制方向&#x1f3ae; 源代码在图片后面 点赞❤️关注&#x1f64f;收藏⭐️ 互粉必回&#x1f64f;&#x1f64f;&#x1f60d;&#x1f60d;&#x1f60d; 源代码&#x1f4df; <!DOCTYPE html> <html lang"en"&…

【2024最新】Arduino通过Python进行串口通信控制电机

1. 背景 最近想研究一下用 Python 控制 Arduino 的技术&#xff0c;通过上网查询&#xff0c;发现可以用 Python 中的 serial 库来实现和 Arduino 主板的串口通信&#xff0c;从而控制 Arduino。 特此记录一下这个小项目的过程及出现的问题。 2. 基础准备 主板&#xff1a;…

中仕公考:“三支一扶”岗位分别做什么工作?

“三支一扶”计划旨在招募应届毕业生或近两年内毕业的毕业生&#xff0c;部分省份还考虑技工院校高级工班、预备技师班毕业生。在湖北省&#xff0c;报考支医岗位不限制毕业年限&#xff0c;安徽和云南等省对支医类岗位取消了开考比例要求。为解决招人留人难题&#xff0c;艰苦…

毛绒玩具音乐芯片:OTP语音芯片WTN6040方案解析

随着科技的不断发展&#xff0c;智能化和互动性已经成为玩具设计中的关键因素。在毛绒玩具市场中&#xff0c;集成音乐播放功能的毛绒玩具因其趣味性和互动性而备受欢迎。本文将详细介绍OTP&#xff08;One Time Programmable&#xff09;语音芯片WTN6040在毛绒玩具音乐芯片中的…

网页UI:被客户说不大气!大气能当饭吃?真能,最起码保住你饭碗

大气的网页UI设计可以带来以下几个好处&#xff1a; 提升品牌形象&#xff1a;大气的设计能够给用户留下深刻的印象&#xff0c;增强品牌的认知度和形象。通过精心设计的元素、色彩和排版&#xff0c;可以传达出品牌的专业、高端和可信赖的形象。强化用户体验&#xff1a;大气…

SpringBoot开发实用篇(二)

目录 一&#xff1a;Redis 1&#xff1a;SpringBoot整合Redis 2&#xff1a;SpringBoot读写Redis的客户端 3&#xff1a;SpringBoot操作Redis实现技术切换&#xff08;jedis&#xff09; 二&#xff1a;Mongodb 1&#xff1a;Mongodb基础操作 2&#xff1a;SpringBoot整合…

【DataSophon】DataSophon1.2.1 ranger usersync整合

目录 一、简介 二、实现步骤 2.1 ranger-usersync包下载编译 2.2 构建压缩包 2.3 编辑元数据文件 2.4 修改源码 三、重新安装 一、简介 如下是DDP1.2.1默认有的rangerAdmin&#xff0c; 我们需要将rangerusersync整合进来 ,实现将Linux机器上的用户和组信息同步到Ranger…

『粽享端午』交互小程序 小游戏 案例赏析

在这片古老而又年轻的土地上&#xff0c;地域的差异孕育了丰富多彩的饮食文化。粽子&#xff0c;作为端午节的象征&#xff0c;承载着南咸北甜的口味之争&#xff0c;自古便在人们舌尖上演绎着不同的风味传奇。 然而&#xff0c;在快节奏的现代生活洪流中&#xff0c;我们渐渐失…

家谱管理系统

《家谱管理系统》 一个家谱关系由若干家谱记录构成&#xff0c;每个家谱记录由父亲、母亲和子女姓名构成&#xff0c;其中姓名是关 键字。设计并实现一个简单的家谱管理系统。定义一个主菜单&#xff0c;界面友好&#xff0c;演示程序以用户和计算机的对话方式进行&#xff0c…

开关电源——调制模式和工作模式

一、开关电源的调制模式 开关电源作为一种广泛应用于电子设备中&#xff0c;用于将一定电压和电流转换为另一种电压和电流的技术&#xff0c;以下是开关电源三种常见的调制模式&#xff1a; 脉冲宽度调制&#xff08;Pulse Width Modulation&#xff09; 脉冲频率调制&#xff…

触发器编程-创建(CREATE TRIGGER)、删除(DROP TRIGGER)

一、定义 1、触发器&#xff08;Trigger&#xff09;是用户对某一表中的数据做插入、更新和删除操作时被处罚执行的一段程序&#xff0c;通常我们使用触发器来检查用户对表的操作是否合乎整个应用系统的需求&#xff0c;是否合乎商业规则以维持表内数据的完整性和正确性 2、一…

从nginx返回404来看http1.0和http1.1的区别

序言 什么样的人可以称之为有智慧的人呢&#xff1f;如果下一个定义&#xff0c;你会如何来定义&#xff1f; 所谓智慧&#xff0c;就是能区分自己能改变的部分&#xff0c;自己无法改变的部分&#xff0c;努力去做自己能改变的&#xff0c;而不要天天想着那些无法改变的东西&a…

AI视频教程下载-使用ChatGPT成为全栈JavaScript开发者

学习使用Express JS和React JS进行全栈JavaScript开发 ChatGPT Express JS MongoDB React JS Tailwind 解锁全栈网页开发的世界&#xff0c;我们为初学者和中级学习者设计了全面的课程。在这段沉浸式的旅程中&#xff0c;你将深入前端和后端开发的基本概念&#xff0c;为自…