【c++_containers】string的模拟实现

news2024/11/24 19:26:51

前言

        在学习数据结构时,如何证明自己彻底掌握了一个容器的各种特性?最直接的办法就是自己写一个。下面我们将围绕下图展开对与string的深度了解:


一.string的成员变量

string是表示字符序列的对象,同时增加了专门用于操作单字节字符的字符串的功能。

所以他的成员变量应该为

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

二、初始化和清理

2.1构造函数

其中最为常用的是1,2,4这几种,而第一种又可以通过第二种加上缺省参数而写成。

作为构造函数最重要的就是开辟空间和将成员变量初始化。

        string(const char* str = "")
        {
            _size = strlen(str);
            _capacity = _size;
            _str = new char[_capacity + 1];
            memcpy(_str, str, _size + 1);
        }
        string(const string& s)
        {
            _str = new char[s._capacity + 1];
            _size = s._size;
            _capacity = s._capacity;
            memcmp(_str, s._str, _size + 1);
        }

这里使用memcmp而不是直接使用赋值,涉及深浅拷贝的问题,这里简单提一下

1.深拷贝和浅拷贝一样,都会创建一个新的容器对象(compound object)

2.和浅拷贝的不同点在于,深拷贝对于对象中的元素,深拷贝都会重新生成一个新的对象

比如这个string如果是浅拷贝,拷贝前后两个string中的_str(指针)所存的地址一样,也就是指向同一片空间,这明显不符合我们的预期。

2.2析构函数

string的析构函数较为简单,但是由于string是一个动态开辟空间的容器,所以空间需要自己手动释放,不能依赖系统自己生成的析构函数

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

三、string的增加

3.1扩容(reserve)

作为一个动态容器,在使用过程中不可避免的就是容量的变化,在string中直接集成成了一个函数。也就是reserve。 

从上面我们可以看出当n>_capacity时直接扩容到n,而在n<=_capacity时,编译器自行优化但不能改变字符串的长度。也就是这个函数不会发生截断的情况,无法改变_size.

这里我们当:n < _size时将其容量缩小到_size.

 void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];
                memcpy(tmp, _str, _size + 1);
                delete[] _str;
                _str = tmp;
                _capacity = n;
            }
            //理论上这个函数也能缩小capacity,但是不能发生截断的情况,_size不能变
            else if (n < _capacity)
            {
                if (n >= _size)
                {
                    char* tmp = new char[n + 1];
                    memcpy(tmp, _str, n);
                    tmp[n] = '\0';
                    delete[] _str;
                    _str = tmp;
                    _capacity = n;
                }
                
                else
                {
                    char* tmp = new char[_size + 1];
                    memcpy(tmp, _str, _size);
                    tmp[_size] = '\0';
                    delete[] _str;
                    _str = tmp;
                    _capacity = n;
                }
            }
        }

3.2插入

作为线性的容器,在扩容实现后,插入就变的十分轻松了。只要确定插入的位置,其他的向后跟着遍历一遍就可以了。而在这之前我们可以先实现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];
        }

当所有前置条件都集齐后,插入的就变得十分好写了。

        void push_back(char c)
        {
            if (_size == _capacity)
            {
                reserve(_capacity == 0 ? 4 : _capacity * 2);
            }
            _str[_size] = c;
            ++_size;
            _str[_size] = '0'; 
        }
        //在字符串尾添加字符串
        void append(const char* str)
        {
            size_t len = strlen(str);
            if (_size + len > _capacity)
            {
                // 至少扩容到_size + len
                reserve(_size + len);
            }

            //strcpy(_str + _size, str);
            memcpy(_str + _size, str, len + 1);

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

        string& operator+=(const char* str)
        {
            append(str);
            return *this;
        }
        //在pos位置插入n个字符
        void insert(size_t pos, size_t n, char ch)
        {
            assert(pos <= _size);
            if (_size + n > _capacity) 
            {
                reserve(_size + n);
            }
            size_t end = _size;
            while (end > pos && end != npos)
            {
                _str[end + n] = _str[end];
                end--;
            }
            for (size_t i = 0; i < n; i++)
            {
                _str[pos + i] = ch;
            }
            _size += n;
        }
        //在pos位置插入字符串
        void insert(size_t pos,  char* ch)
        {
            assert(pos <= _size);
            size_t n = strlen(ch);

            if (_size + n > _capacity)
            {
                reserve(_size + n);
            }
            size_t end = _size;
            while (end > pos && end != npos)
            {
                _str[end + n] = _str[end];
                end--;
            }
            for (size_t i = 0; i < n; i++)
            {
                _str[pos + i] = ch[i];
            }
            _size += n;
        }

四、string的删除

在删除前,我们要先清楚string的本质,即他是一个字符串。而对于一个字符串来说,终止是用“\0”

所以在删除字符串时我们只需要替换与将结尾替换成“\0”就可以了。

 //从pos向后删除len个字符
        void esase(size_t pos,size_t len=npos)
        {
            assert(pos <= _size);
            if (len == npos || pos + len >= _size)
            {
                //_str[pos] = '\0';
                _size = pos;

                _str[_size] = '\0';
            }
            else
            {
                size_t end = pos + len;
                while (end <= _size)
                {
                    _str[pos++] = _str[end++];
                }
                _size -= len;
            }
        }

五、string的查找

作为一个容器最重要的就是精确的找到所储存的数据,而string的查找其实就是直接遍历

//从pos开始查找ch
        size_t find(char ch, size_t pos = 0)
        {
            assert(pos < _size);

            for (size_t i = pos; i < _size; i++)
            {
                if (_str[i] == ch)
                {
                    return i;
                }
            }

            return npos;
        }
        size_t find(const char* str, size_t pos = 0)
        {
            assert(pos < _size);

            const char* ptr = strstr(_str + pos, str);
            if (ptr)
            {
                return ptr - _str;
            }
            else
            {
                return npos;
            }
        }

六、string的operator

对于自定义类来说,将必要的符号重载成我们所需要的操作可以大大方便代码的编写。

          bool operator==(const string& s)const
        {
            return _size == s._size && memcmp(_str, s._str, _size) == 0;
        }
        bool operator<(const string& s) const
        {
            int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);

            // "hello" "hello"   false
            // "helloxx" "hello" false
            // "hello" "helloxx" true
            return ret == 0 ? _size < s._size : ret < 0;
        }
        bool operator<=(const string& s) const
        {
            return *this < s || *this == s;
        }

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

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

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

七、迭代器

对于string的迭代器只需要使用char*就可以了。

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

当迭代器完成后,for的自动变历就可以实现了。

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

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

相关文章

2023Node.js零基础教程(小白友好型),nodejs新手到高手,(一)NodeJS入门

写在开始前 在无尽的代码汪洪中&#xff0c;闪耀着一抹绚丽的光芒。它叫做Web前端开发&#xff01; HTML是我们的魔法笔&#xff0c;是创造力的源泉。它将我们的思绪化为标签&#xff0c;将我们的想象变为元素。 在无尽的标签组合中&#xff0c;我们创造出独特的网页&#xff…

分享:大模型定向培养学徒来啦

模型定向培养学徒招募对象&#xff1a; 1.在读研究生学硕专硕均可 2.大数据、人工智能、统计学计算机、软件、数学等相关研究方向 3.具备Python编程基础机器学习基础 4.有深度学习自然语言处理基础优先 大模型定向培养学徒目标&#xff1a; 1.培养大模型prompt能力&#…

位图(bitmap)原理以及实现

大家好&#xff0c;我是蓝胖子&#xff0c;我一直相信编程是一门实践性的技术&#xff0c;其中算法也不例外&#xff0c;初学者可能往往对它可望而不可及&#xff0c;觉得很难&#xff0c;学了又忘&#xff0c;忘其实是由于没有真正搞懂算法的应用场景&#xff0c;所以我准备出…

Linux搭建Apache(秒懂超详细)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️努力不一定有回报&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xf…

C++ Primer (第五版)第一章习题部分答案

在我自学C过程中&#xff0c;我选择了CPrimer这本书&#xff0c;并对部分代码习题进行了求解以及运行结果。接下来几个月我将为大家定时按章节更新习题答案与运行结果: 目录 1.9编写程序,使用while循环将50到100的整数相加 1.10 除了运算符将运算对象的值增加1之外,还有一个…

leetcode522. 最长特殊序列 II(java)

最长特殊序列 题目描述枚举法代码演示 题目描述 难度 - 中等 leetcode522. 最长特殊序列 II 给定字符串列表 strs &#xff0c;返回其中 最长的特殊序列 的长度。如果最长特殊序列不存在&#xff0c;返回 -1 。 特殊序列 定义如下&#xff1a;该序列为某字符串 独有的子序列&a…

【STM32】IWDG—独立看门狗

基于stm32f103 基于《零死角玩转STM32—F103指南者》 一个12bit的递减计数器 STM32 有两个看门狗&#xff0c;一个是独立看门狗另外一个是窗口看门狗&#xff0c;独立看门狗号称宠物狗&#xff0c;窗口看门狗号称警犬。 独立看门狗是一个 12 位的递减计数器&#xff0c;当计…

数字孪生:助力机载软件构型管理

飞机机载软件具有研发周期长、版本更新频繁、相关工程资料密集等特性。由于各个系统的软件分别由不同供应商开发&#xff0c;其设计保障等级、设计架构、实现方法等方面都各有不同&#xff0c;对机载软件进行高效、规范的构型管理显得尤为重要。 Q&#xff1a;什么是构型管理&…

【张兔兔送书第一期:考研必备书单】

考研书单必备 《数据结构与算法分析》《计算机网络&#xff1a;自顶向下方法》《现代操作系统》《深入理解计算机系统》《概率论基础教程&#xff08;原书第10版》《线性代数&#xff08;原书第10版&#xff09;》《线性代数及其应用》赠书活动 八九月的朋友圈刮起了一股晒通知…

Idea新建项目配置Java3D 环境配置

&#xff08;一&#xff09;JDK配置 安装包的下载&#xff1a;1.可以去官网&#xff1a;JDK下载&#xff0c;进入官网页面Java Downloads | Oracle&#xff0c;然后点击Download Java。2.搜博客&#xff0c;可能会内含安装包链接。 ​ 2.在这个界面可以选择我们要安装的版本…

IEEE802.2之LLC(逻辑链路控制)

一、概念 IEEE 802.2 是一种用于局域网&#xff08;LAN&#xff09;和都会区域网&#xff08;MAN&#xff09;的数据链路层逻辑链路控制&#xff08;LLC&#xff09;的标准。它是 IEEE 802 系列标准中的一个组成部分&#xff0c;专门用于定义如何在数据链路层内进行帧的多路复用…

按摩软件仿东郊到家系统开发,上门预约系统;

按摩软件仿东郊到家系统开发&#xff0c;上门预约系统&#xff1b; 用户端、技师端、商家端&#xff0c;以及管理后台。上门预约的操作 1、技师管理。 技师满意度进行统一跟踪评估&#xff0c;进行分级管理&#xff0c;分级评估&#xff1b; 2、订单管理。 按订单状态分类筛选&…

Vue3动态显示时间

使用的是moment.js插件 关键字 vue3的 onMounted 和 onBeforeUnmount 声明周期 一个用来创建 一个用来销毁 创建什么呢? setInterval 函数 销毁时使用时 clearInterval清除定时器 1上代码 function formatTime() {const date moment();const year date.year();const m…

【STM32】SDIO—SD 卡读写01

基于stm32f103 基于零死角玩转STM32—F103指南者 简介 1.SD 卡总共有 8 个寄存器&#xff0c;用于设定或表示 SD 卡信息。 2.SD卡的寄存器不能像STM32那样访问&#xff0c;而是利用命令访问&#xff0c;SDIO 定义了 64 个命令。SD 卡接收到命令后&#xff0c;根据命令要求对…

今天的消费情况

1、今天消费1710元 意外险 住院--集中参保 校---******----服 1220 rmB lunch 240Rmb

若依前端使用

初始化页面时&#xff0c;路由上加参数 多个菜单对应一个页面&#xff0c;默认查询的数据状态不一样 vue 页面上 通过 debugger; 查看所有的参数&#xff0c; 最后取到了

【Android知识笔记】FrameWork中的设计模式

一、FrameWork中有哪些设计巧妙之处 例如: Binder调用,模糊进程边界: 屏蔽跨进程IPC通信的细节,让开发者把精力放在业务上面,无需关心进程之间的通信。Bitmap大图传输,高性能: 只传递Binder句柄,到目标进程后做内存映射,不用做大量数据拷贝,速度非常快。Zygote创建进…

想要定制一个自己的ChatGPT?三步就可以搞定!

2022年底ChatGPT上线&#xff0c;短短两个月用户就超过一亿&#xff0c;成为了人工智能界当之无愧的超级大网红。ChatGPT凭借着自身强大的拟人化及时应答能力迅速破圈&#xff0c;引起了各行各业的热烈讨论。简单来说ChatGPT就是可以基于用户文本输入自动生成回答的AI聊天机器人…

数据结构——KD树

KD树&#xff08;K-Dimensional Tree&#xff09;是一种用于多维空间的二叉树数据结构&#xff0c;旨在提供高效的数据检索。KD树在空间搜索和最近邻搜索等问题中特别有用&#xff0c;允许在高维空间中有效地搜索数据点。 重要性质 1.分割K维数据空间的数据结构 2.是一颗二叉树…

中期科技:智慧公厕是智慧城市管理智慧化的至佳表现

智慧公厕&#xff0c;作为智慧城市建设的一部分&#xff0c;正逐渐成为城市管理的新标杆。它不仅为城市居民提供便利&#xff0c;还深刻地改善了城市管理的效率。如中期科技「智慧公厕-智慧厕所」通过运用先进的科技手段&#xff0c;智慧公厕在优化城市管理、提升城市服务、促进…