C++库函数——String类的模拟实现

news2024/12/23 19:05:36

目录

①String类的主体

②String类的具体实现

1.构造函数、拷贝构造函数、赋值运算符、析构函数

⑴构造函数

⑵拷贝构造函数

⑶赋值运算符

⑷析构函数

2.迭代器(范围for的实现原理)

3.修改:push_back, apppend, +=, clear, swap, c_str

⑴push_back

⑵apppend

⑶+=

⑷clear

⑸swap

⑹c_str

4.容量:size, capacity, empty, resize, reverse

⑴size

⑵capacity

⑶empty

⑷resize

⑸reverse

5.下标[]重载

6.逻辑运算符:<, <=, >, >=, ==, !=

7.查找:find

8.固定位置插入元素:insert

9.清除固定位置元素:erase

10.流插入、流提取运算符重载:<<, >>

⑴流插入<<

⑵流提取>>


①String类的主体

为了与库函数里面的string分开,我们将模拟实现的string类存放在自己定义的命名空间中,同时尽可能的将对应功能函数的名字与库函数一一对应,即

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

    public:
        typedef char* iterator;
        typedef const char* const_iterator;
    public:
        // 构造函数
        string(const char* str = "");

        // 拷贝构造函数
        string(const string& s);

        // 赋值运算符重载
        string& operator=(const string& s);

        // 析构函数
        ~string();

        // 迭代器
        iterator begin();
        iterator end();
        const_iterator begin() const;
        const_iterator end() const;

        // 修改
        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;

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

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

        // 逻辑运算符
        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;
    };

};

②String类的具体实现

1.构造函数、拷贝构造函数、赋值运算符、析构函数

⑴构造函数

// 构造函数
string(const char* str = "")
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];// 字符串后有'\0'因此要+1
    memcpy(_str, str, sizeof(char) * (_size + 1));// 与上述同理
}

⑵拷贝构造函数

// 拷贝构造函数
string(const string& s)
{
    _size = s._size;
    _capacity = s._capacity;
    // 这里不能直接让_str=s._str(即浅拷贝),
    // 不然会导致两个string最后析构时对它们指向的空间析构两次
    // 此外,如果其中一个对象对这片空间的元素进行修改,会对另一个对象造成影响
    // 因此,此处只能使用深拷贝

    _str = new char[s._capacity + 1];
    memcpy(_str, s._str, sizeof(char) * (s._size + 1));
}

⑶赋值运算符

在模拟实现赋值运算符时,有两种写法,一种是与拷贝构造逻辑类似的写法,即

// 赋值运算符重载
string& operator=(const string& s)
{
    if (this != &s)// 与拷贝构造逻辑相同
    {
        _size = s._size;
        _capacity = s._capacity;
        _str = new char[s._capacity + 1];
        memcpy(_str, s._str, sizeof(char) * (s._size + 1));
    }

    return *this;
}

另一种则是通过拷贝构造一个临时对象tmp,将this与tmp所指向的空间交换,在结束时tmp还能顺便释放自己所指向的空间,即

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

// 赋值运算符重载
string& operator=(const string& s)
{
    if (this != &s)
    {
        string tmp(s);
        swap(tmp);
    }

    return *this;
}

图示如下

 从图中我们可以明显看出,在此函数调用完后,tmp也会顺带将s1先前所指向的空间释放 

⑷析构函数

// 析构函数
~string()
{
    _size = _capacity = 0;
    delete[] _str;
    _str = nullptr;
}

2.迭代器(范围for的实现原理)

从string类的主体中我们不难看出,string类的迭代器其实就是指针,即

这是因为string类的对象本身处在一个连续的空间内,可以直接通过指针来操控,因此我们便可以得到

// 迭代器
iterator begin()
{
    return _str;
}

iterator end()
{
    return _str + _size;
}

const_iterator begin() const
{
    return _str;
}

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

在这里我们可以看看范围for是如何做到的,首先我们直接使用有

而当我们将任意一个迭代器(begin或end注释掉后可以发现) 

因此我们可以发现,其实所谓的范围for不过就是将这段代码固定的替换成如下这段代码

my_string::string::iterator it = s.begin();
while (it != s.end())
{
	cout << *it;
	it++;
}

而且范围for只会固定的寻找end和begin名字的迭代器,略微修改名字也会报错

3.修改:push_back, apppend, +=, clear, swap, c_str

⑴push_back

void push_back(char c)
{
    // 当size与capacity相等时需要扩容
    if (_size == _capacity)
    {
        // 扩容逻辑与reserve类似
        char* tmp = new char[_capacity * 2];
        memcpy(tmp, _str, sizeof(char) * _size);
        delete[] _str;
        _str = tmp;

        _capacity *= 2;
    }


    _str[_size++] = c;
}

⑵apppend

void append(const char* str)
{
    // 与push_back的插入逻辑类似
    // 但是扩容大小需要作出变化
    size_t len = strlen(str);
    if (len + _size >= _capacity)
    {
        reserve(2 * (len + _capacity));
    }

    for (size_t i = 0; i < len; i++)
    {
        push_back(str[i]);
    }
}

⑶+=

// 直接复用即可
string& operator+=(char c)
{
    push_back(c);

    return *this;
}

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

    return *this;
}

⑷clear

void clear()
{
    // 容量一般不缩小,以防重复开辟空间降低效率
    _str[0] = '\0';
    _size = 0;
}

⑸swap

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

⑹c_str

const char* c_str()const
{
    return _str;
}

4.容量:size, capacity, empty, resize, reverse

⑴size

size_t size()const
{
    return _size;
}

⑵capacity

size_t capacity()const
{
    return _capacity;
}

⑶empty

bool empty()const
{
    return _size == 0;
}

⑷resize

在这里分为多种情况,如图

实现如下

void resize(size_t n, char c = '\0')
{
    if (n < _size)
    {
        _str[_size] = '\0';// 直接将截止位置提前到size的位置即可
    }
    else
    {
        // 如果是情况三先进行扩容,然后统一插入数据
        if (n > _capacity)
        {
            reserve(n);
        }

        for (int i = _size; i < n; i++)
        {
            _str[i] = c;
        }
    }

    _size = n;
}

⑸reverse

在这里,我们一般不选择将capacity缩小,因为缩小也是需要代价的(可能会开辟一片新的空间),因此实现如下

void reserve(size_t n)
{
    if (n > _capacity)
    {
        // 扩容可能会开辟新空间
        char* tmp = new char[n + 1];
        memcpy(tmp, _str, sizeof(char) * (_size + 1));

        delete[] _str;
        _str = tmp;
        _capacity = n;
    }
}

5.下标[]重载

// 下标
char& operator[](size_t index)
{
    assert(index < _size);//此处因为传入参数为size_t,因此不需要判断index是否小于0

    return *(_str + index);
}

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

    return *(_str + index);
}

6.逻辑运算符:<, <=, >, >=, ==, !=

字符串比较大小的规则是逐字符比较ASCII码值,相等的话就比较下一个,在这里两个字符串相比较也会有多种情况,如下图所示

我们可以实现任一比较大小和相等之后,不断复用来做到简化,即

// 逻辑运算符
bool operator<(const string& s)
{
    int i = 0;
    // 一直迭代到两字符不相等或下标i越界
    while (i < _size && i < s.size() && s[i] == _str[i])
    {
        i++;
    }

    // 如果下标i超过了string1的size,需要进行特殊判断
    if (i >= _size)
    {
        //如果两个字符串的大小相等说明此时它们均相同
        if (_size == s.size())
        {
            return false;
        }
        else//反之表示string1截止在i这个下标处,string1<string2
        {
            return true;
        }
    }

    // 如果下标i超过了string2的size,此时string1始终是>或=string2的
    // 因此返回false
    if (i >= s.size())
    {
        return false;
    }

    if (_str[i] < s[i])
    {
        return true;
    }
    else
    {
        return false;
    }
}

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 || *this == s);
}

bool operator==(const string& s)
{
    int i = 0;
    while (i < _size && i < s.size() && s[i] == _str[i])
    {
        i++;
    }

    // 此时,下标i要么越界,要么就在两个字符串中分别指向不同的字符
    // 因此,只有当i越界,且两个字符串大小相等时,才能相等,其余情况都不行
    if ((i >= _size || i >= s.size()) && _size == s.size())
    {
        return true;
    }
    else
    {
        return false;
    }
}

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

7.查找:find

// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const
{
    assert(pos < _size);


    int i = 0;
    // 寻找到与c相同的字符或者越界为止
    while (i < _size && _str[i] != c)
    {
        i++;
    }

    if (_str[i] == c)
    {
        return i;
    }
    else
    {
        return -1;
    }
}

// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const
{
    assert(pos < _size);

    const char* tmp = strstr(_str + pos, s);
    if (tmp != nullptr)
    {
        return tmp - s;
    }
    else
    {
        return -1;
    }
}

8.固定位置插入元素:insert

这里先实现插入单个字符

// 在pos位置上插入字符c,并返回该字符
string& insert(size_t pos, char c)
{
    assert(pos <= _size);

    // 检查是否需要扩容
    if (_size == _capacity)
    {
        reserve(2 * _capacity);
    }

    // 从最后一个数据开始将每个数据向后挪动
    int i = 0;
    for (i = _size; i > pos; i--)
    {
        _str[i] = _str[i - 1];
    }
    _str[i] = c;
    _size++;

    return *this;
}

然后是实现插入一串子串,举例如下

// 在pos位置上插入字符串str,并返回该字符的位置
string& insert(size_t pos, const char* str)
{
    assert(pos <= _size);

    int len = strlen(str);
    if (_size + len >= _capacity)
    {
        reserve(2 * (_size + len));
    }

    // 向后挪位置腾出空间
    int i = 0;
    // 在这里i与pos比较时会发生隐式类型转换,因此还需要添加一个条件
    for (i = _size - 1; i != -1 && i >= pos; i--)
    {
        _str[i + len] = _str[i];
    }

    // 从pos位置开始将str的内容拷贝到string中
    memcpy(_str + pos, str, sizeof(char) * len);
    _size += len;

    return *this;
}

9.清除固定位置元素:erase

// 删除pos位置上的元素,并返回字符
string& erase(size_t pos, size_t len)
{
    assert(pos < _size);

    // 如果pos+len超过了_size,则表示要将pos及其之后的字符全部删除
    if (pos + len >= _size)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
        int i = 0;
        // 从第pos+len的位置开始将数据分别向前挪动覆盖
        for (i = pos+len; i < _size; i++)
        {
            _str[i - len] = _str[i];
        }

        _size -= len;
    }

    return *this;
}

10.流插入、流提取运算符重载:<<, >>

⑴流插入<<

friend ostream& operator<<(ostream& _cout, const my_string::string& s)
{
    for (char c : s)
    {
        cout << c;
    }
    cout << endl;

    return _cout;
}

⑵流提取>>

friend istream& operator>>(istream& _cin, my_string::string& s)
{
    // 在每次输入前需要对缓冲区作出处理
    s.clear();

    // 删除存在于最前方的空格与换行符
    char ch = _cin.get();
    while (ch == ' ' || ch == '\n')
    {
        ch = _cin.get();
    }

    // 在这里,我们创建一个临时的缓冲器来存放s
    // 这样的话每次插入字符数只有超过了128时,将其尾插到s中
    // 之后归零i,从而避免扩容,以此提高插入效率
    char buff[128] = { 0 };
    int i = 0;
    while (ch != ' ' && ch != '\n')
    {
        buff[i++] = ch;

        // 当i到达127时将此位置置为'\0',然后将i重置为0
        if (i == 127)
        {
            buff[i] = '\0';
            s += buff;
            i = 0;
        }
                
        ch = _cin.get();
    }

    if (i != 0)
    {
        buff[i] = '\0';
        s += buff;
    }

    return _cin;
}

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

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

相关文章

【BASH】回顾与知识点梳理(七)

【BASH】回顾与知识点梳理 七 七.前六章知识点总结及练习7.1 总结7.2 练习 七.前六章知识点总结及练习 7.1 总结 由于核心在内存中是受保护的区块&#xff0c;因此我们必须要透过『 Shell 』将我们输入的指令与 Kernel 沟通&#xff0c;好让 Kernel 可以控制硬件来正确无误的…

【Spring】深究SpringBoot自动装配原理

文章目录 前言1、main入口2、SpringBootApplication3、EnableAutoConfiguration4、AutoConfigurationImportSelector4.1、selectImports()4.2、getAutoConfigurationEntry()4.3、getCandidateConfigurations()4.4、loadFactoryNames() 5、META-INF/spring.factories6、总结 前言…

以beam search为例,详解transformers中generate方法(下)

以beam search为例&#xff0c;详解transformers中generate方法&#xff08;下&#xff09; 1. beam search原理回顾2. 代码流程概览3. BeamSearchScorer4. BeamHypotheses5. beam_search过程5.1 beam score初始化5.2 准备输入5.3 前向forward5.4 计算下一个step每个token的得分…

网络安全知识点整理(作业2)

目录 一、js函数声明->function 第一种 第二种 第三种 二、this关键字 this使用场合 1.全局环境 2.构造函数 3.对象的方法 避免多层this 三、js的同步与异步 定时器 setTimeout和setInterval 同步与异步的例子 四、宏任务与微任务 分辨宏任务与微任务 一、js…

深度学习——划分自定义数据集

深度学习——划分自定义数据集 以人脸表情数据集raf_db为例&#xff0c;初始目录如下&#xff1a; 需要经过处理后返回 train_images, train_label, val_images, val_label 定义 read_split_data(root: str, val_rate: float 0.2) 方法来解决&#xff0c;代码如下&#xff1a…

【C++】开源:matplotlib-cpp静态图表库配置与使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍matplotlib-cpp图表库配置与使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&…

RK3588开发板 (armsom-w3) 之 USB摄像头图像预览

硬件准备 RK3588开发板&#xff08;armsom-w3&#xff09;、USB摄像头&#xff08;罗技高清网络摄像机 C93&#xff09;、1000M光纤 、 串口调试工具 v4l2采集画面 v4l2-ctl是一个用于Linux系统的命令行实用程序&#xff0c;用于控制视频4 Linux 2&#xff08;V4L2&#xff0…

晚读“散文”一篇之随感

近来天气太热&#xff0c;上网写作的激情锐减&#xff0c;午后“昏睡百年”至近5点半才睡眼惺忪地起床。因深陷上网日日写作长达14年之久&#xff0c;也便如同“吸粉成瘾”的“瘾君子”戒不了毒瘾一样管束不了自己的“鼠标手”&#xff0c;就打开了电脑。 恍惚间步入了网络上的…

Dockerfile构建apache镜像(源码)

Dockerfile构建apache镜像&#xff08;源码&#xff09; 1、建立工作目录 [rootdocker ~]# mkdir apache [rootdocker ~]# cd apache/ 2、编写Dockerfile文件 [rootdocker apache]# vim Dockerfile #基于的基础镜像 FROM centos:7#镜像作者信息 MAINTAINER Huyang <133…

Java通过freemark创建word文档

创建freemarker模板 创建Freemarker模板&#xff1a;在您的Java项目中&#xff0c;创建一个Freemarker模板文件&#xff08;例如template.ftl&#xff09;&#xff0c;其中包含您想要生成的Word文档的内容。您可以在模板中使用Freemarker的标记来插入动态内容。 <!DOCTYPE…

Spring如何通过三级缓存解决循环依赖问题?

目录 一、什么是Spring 二、循环依赖问题 三、三级缓存机制 四、如何通过三级缓存解决循环依赖问题 一、什么是Spring Spring框架是一个开源的Java应用程序开发框架&#xff0c;提供了一种全面的、一致的编程模型&#xff0c;用于构建企业级应用程序和服务。它由Rod Johnso…

如何压缩高清PDF文件大小?将PDF文件压缩到最小的三个方法

PDF格式是一种非常常用的文档格式&#xff0c;但是有时候我们需要将PDF文件压缩为更小的大小以便于传输和存储。在本文中&#xff0c;我们将介绍三种PDF压缩的方法&#xff0c;包括在线PDF压缩、利用软件PDF压缩以及使用WPS缩小pdf。 首先&#xff0c;在线PDF压缩是最常用的方…

人体大脑神经元运行模拟器!让你直观体验大脑的运行方式

首先&#xff0c;宣布沾花把玖正式回归&#xff01;&#xff01;&#xff01; 最近沾花在网上看到一个神奇的网站&#xff1a;A Neural Network Playground 经过沾花的亲手测试&#xff0c;发现这玩意儿能模拟人体大脑神经元的运行&#xff01; 下面是网址&#xff1a; A N…

干货!机器视觉基础知识汇总

现如今,中国已经成为世界机器视觉发展最为活跃地区,应用范围涵盖了工业、农业、医药、军事、航天、气象等国民经济各个行业。虽然机器视觉的成长速度非常快,但是还是有很多人对机器视觉并不了解,今天我们来了解下机器视觉。 机器视觉就是用机器代替人眼来做测量和判断。机器…

一条自由游动的鲸鱼

先看效果&#xff1a; 再看代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>鲸鱼</title><style>#canvas-container {width: 100%;height: 100vh;overflow: hidden;}&l…

Linux(二)---------网络命令学习(ifconfig命令)

1.ifconfig命令 用于配置网卡ip地址信息&#xff0c;等网络参数信息&#xff0c;或者查看显示网络接口信息&#xff0c;类似于windows的ipconfig命令&#xff0c;还能够临时性的配置ip地址&#xff0c;子网掩码&#xff0c;广播地址&#xff0c;网关信息等。 注意ifconfig命令…

配置GIt账号、配置公钥

1.设置账号和邮箱 打开终端输入以下命令&#xff1a; git config --global --unset-all user.name git config --global --unset-all user.email然后输入以下命令来设置新的账号和邮箱&#xff1a; git config --global user.name "your_username" git config --glo…

整理了250个shell脚本,拿来即用!

无论是系统运维&#xff0c;还是应用运维&#xff0c;均可分为“纯手工”→ “脚本化”→ “自动化”→“智能化”几个阶段&#xff0c;其中自动化阶段&#xff0c;主要是将一些重复性人工操作和运维经验封装为程序或脚本&#xff0c;一方面避免重复性操作及风险&#xff0c;另…

【音视频SDK测评】线上K歌软件开发技术选型

摘要 在线K歌软件的开发有许多技术难点&#xff0c;需考虑到音频录制和处理、实时音频传输和同步、音频压缩和解压缩、设备兼容性问题等技术难点外&#xff0c;此外&#xff0c;开发者还应关注音乐版权问题&#xff0c;确保开发的应用合规合法。 前言 前面写了几期关于直播 …

qt系列-qt6在线安装慢的问题

.\qt-unified-windows-x64-online.exe --mirror https://mirrors.aliyun.com/qt/下载速度飞快