C++【STL】之string模拟实现

简介: C++ STL string类模拟实现,常用接口详细讲解,干货满满!

C++ string类模拟实现

上一篇讲解了string的使用,当然少不了string的模拟实现实现啦!这里依然是讲解常用接口的模拟实现,话不多说,下面正文直接开始!

@TOC

1. 成员变量

string本质就是一个存放字符的顺序表,是由指针大小容量组成的,并且加入了npos = -1,来当作size_t的上限,也起到当作查找是未找到的标准等功能。

namespace sakura    //命名空间
{
   
    class string
    {
   
    private:
        char* _str;    //数据指针
        size_t _size;    //大小
        size_t _capacity; //容量
        static const size_t npos;
    };
}

==注意:==

这里npos 的类型为 static const size_tstatic 修饰后,npos 只能被初始化一次,而加了 const 后,允许在类中赋予缺省值进行初始化,如果不加 const,则必需到类外手动初始化静态成员,const修饰的静态变量,只允许整型家族在类中设置缺省值

2. 默认成员函数

2.1 构造和析构

==构造函数==

使用缺省参数,当用户没有传参时,将 string 对象初始化为空串,字符串长度可以利用初始化列表进行初始化

string(const char* str = "")
    :_size(strlen(str))
{
   
    _capacity = _size == 0 ? 3 : _size;
    _str = new char[_capacity + 1]; //预留位置给'/0'
    strcpy(_str, str);
}

==析构函数==

析构函数 在释放内存时,是以 delete[] 的形式,所以在申请内存时,即使只申请一个 char,也要写成 new char[1] 的形式,目的就是为了销毁对应

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

2.2 拷贝和赋值

==拷贝构造==

此处的拷贝构造为深拷贝,先开辟一块和s空间相同大小的空间,然后拷贝数据到新空间,完成拷贝

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

注意:申请空间后,要使用strcap进行数据拷贝,否则操作无效

==赋值重载==

赋值重载的实现首先需要判断是否为同一对象赋值,然后再进行赋值操作

==注意:==

这里不使用_str = new char[s._capacity + 1]的方式,而是先将数据拷贝到一块新开辟的空间,成功后改变_str指向,释放掉原空间,这样可以保证若申请空间失败,原空间数据不被破坏

string& operator=(const string& s)
{
   
    if (this != &s)
    {
   
        char* tmp = new char[s._capacity + 1]; //防止new失败
        strcpy(tmp, s._str);

        delete[] _str;
        _str = tmp; //改变指针指向
        _size = s._size;
        _capacity = s._capacity;
    }

    return *this; //返回避免 a = b = c 的连续赋值情况
}

3. 容量操作

3.1 reserve方法

若传入的capacity不大于_capacity,不做任何操作,若大于_capacity,则正常进行扩容

void reserve(size_t n)
{
   
    if (n > _capacity)
    {
   
        char* tmp = new char[n + 1]; //防止new失败
        strcpy(tmp, _str);

        delete[] _str;
        _str = tmp; //改变指针指向
        _capacity = n;
    }
}

这里的_str = tmp 并不是使用的赋值重载(operator=),因为_strtmp都是类值类型(指针类型),这里只是改变了_str的指向

3.2 resize方法

大小调整分三种情况:

  • n < _size:缩容,直接调整'\0'的位置改变_size
  • n > capacity:扩容并初始化
  • n > _size && n < _capacity:不扩容只进行初始化
void resize(size_t n, char ch = '\0')
{
   
    if (n < _size) //比_size小就是缩容,直接添加终止符号'\0'
    {
   
        //删除数据 保留前n个
        _size = n;
        _str[_size] = '\0';
    }
    else if (n > _size) //扩容
    {
   
        if (n > _capacity)
        {
   
            reserve(n);
        }
        size_t i = _size;
        while (i < n)
        {
   
            _str[i] = ch;
            ++i;
        }
        _size = n;
        _str[_size] = '\0';
    }
}

3.3 查看大小容量、判空

size_t size() const
{
   
    return _size;
}

size_t capacity() const
{
   
    return _capacity;
}

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

类似获取数据这些,不修改成员变量数据的函数,最好都加上const

4. 数据访问

4.1 下标访问

类中的数据为私有,无法直接访问,但可以通过函数间接访问

char& operator[](size_t pos)
{
   
    assert(pos < _size);
    return _str[pos];
}
//const版本
const char& operator[](size_t pos) const
{
   
    assert(pos < _size);
    return _str[pos];
}

4.2 迭代器访问

获取原生指针来简单实现迭代器

typedef char* iterator;
typedef const char* const_iterator;

iterator begin()
{
   
    return _str;
}

iterator end()
{
   
    return _str + _size;
}
//const版本
const_iterator begin() const
{
   
    return _str;
}

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

5. 数据修改

5.1 push_back方法

这里的push_back方法对应于顺序表的尾插,相信友友们已经非常熟悉了,判断是否需要阔容后直接插入即可

void push_back(char ch)
{
   
    if (_size + 1 > _capacity) //检查扩容
    {
   

        reserve(_capacity * 2); //二倍扩
    }
    //尾插字符
    _str[_size] = ch;
    _str[_size] = '\0';
    ++_size;
}

5.2 append方法

首先是append尾插n个字符,这里直接复用push_back,来尾插n次即可实现

void append(size_t n, char ch)
{
   
    //提前开空间
    if (_size + n > _capacity)
    {
   
        reserve(_size + n);
    }
    while (n--)
    {
   
        push_back(ch);
    }
}

然后是append尾插字符串,这里判断是否需要扩容后,直接调用strcpy或者strcat即可实现

void append(const char* str)
{
   
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
   
        reserve(_size + len);
    }

    strcpy(_str + _size, str);
    //strcat(_str, str);
    _size += len;
}

5.3 insert方法

操作步骤:

  1. 检查要插入位置的下标是否合法
  2. 检查是否需要扩容
  3. 挪动数据 + 插入数据

首先是任意位置插入字符的实现

string& insert(size_t pos, char ch)
{
   
    assert(pos <= _size); //判断下标是否合法
    if (_size + 1 > _capacity) //判断扩容
    {
   
        reserve(2 * _capacity);
    }

    //挪动数据
    size_t end = _size + 1;
    while (end > pos)
    {
   
        _str[end] = _str[end - 1];
        --end;
    }   
    //错误写法
    //size_t end = _size;
    //while (end >= pos)
    //{
   
    //    _str[end + 1] = _str[end];
    //    --end;
    //}

    //插入数据
    _str[pos] = ch;
    ++_size;
    return *this;
}

这里的size_t end = _size写法错误,是因为endsize_t类型,当end = -1时,会发生整型提升,end = INT_MAX产生错误

然后是任意位置插入字符串的实现

string& insert(size_t pos, const char* str)
{
   
    assert(pos <= _size);
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
   
        reserve(_size + len);
    }
    //挪动数据
    size_t end = _size + len;
    while (end > pos + len - 1)
    {
   

        _str[end] = _str[end - len];
        --end;
    }

    //写法二:
    //size_t end = _size;
    //for (size_t i = 0; i < _size + 1; ++i)
    //{
   
    //    _str[end + len] = _str[end];
    //    --end;
    //}

    //拷贝插入
    strncpy(_str + pos, str, len); //只拷贝len个,避免将'\0'拷贝
    _size += len;
    return *this;
}

有了insert方法,我们可以直接将其复用给push_backappend方法

void push_back(char c)
{
   
    insert(_size, c);
}

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

5.4 erase方法

操作步骤:

  1. 检查下标是否合法
  2. 判断删除长度,若len == npospos + len >= _size则直接终止到pos处,反之将pos + len处的字符挪动覆盖到pos
string& erase(size_t pos, size_t len = npos)
{
   
    assert(pos < _size);
    if (len == npos || pos + len >= _size)
    {
   
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
   
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
    }
    return *this;
}

6. 运算符重载

6.1 +/+=运算符重载

这里的++=运算符重载,就是借助临时变量来进行字符或字符串的附加操作

string operator+(char ch) const
{
   
    string tmp(*this); //传值返回
    tmp.push_back(ch);

    return tmp;
}

string operator+(const string& s) const
{
   
    string tmp(*this);
    tmp.append(s._str);

    return tmp;
}

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

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

6.2 逻辑判断

string对象是借助ASCII值来进行大小的判断的,这里可以直接俄使用strcmp函数,只需要实现 >==,其他逻辑判断都可以复用

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

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

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

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

7. 其他

7.1 find方法

传入目标字符/字符串,遍历原字符串,找到返回下标,反之则返回npos,支持从传入的位置查找,默认从pos = 0的位置开始查找,查找字符串可以直接调用strstr库函数

//查找字符
size_t find(char ch, size_t pos = 0) //默认从0下标开始查找
{
   
    assert(pos < _size);
    for (size_t i = pos; i < _size; ++i)
    {
   
        if (_str[i] == ch)
        {
   
            return i; //找到返回下标
        }
    }
    return npos; //没找到返回npos
}
//查找字符串
size_t find(const char* str, size_t pos = 0)
{
   
    assert(pos < _size);
    char* p = strstr(_str + pos, str); //调用strstr库函数(查找子串)
    if (p == nullptr)
    {
   
        return npos;
    }
    else
    {
   
        return p - _str; //指针 - 指针
    }
}

==指针 - 指针就是实际找到的字符串的下标==

7.2 clean方法

clean方法用于清空字符串,实现只用将字符串结束标准设置到0下标位置,再将长度置零即可

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

7.3 swap方法

调用库中的swap函数,将string对象中的三个成员进行交换即可,此时是浅拷贝,效率很高

void swap(string& s)
{
   
    std::swap(_str, s._str); //交换指针
    std::swap(_capacity, s._capacity); /交换容量
    std::swap(_size, s._size); //交接大小
}

这里有个问题,为什么不直接交换对象,而是要交换三个成员变量呢?

这是因为直接交换对象的话,会发生多次拷贝构造的操作,而且还是深拷贝,效率很低

7.4 获取原生指针

_str为私有成员不能直接获取,只需要借助函数间接获取即可

const char* c_str()
{
   
    return _str;
}

8. 流操作

流操作是 string 中的类外成员函数,此时的左操作数为 ostreamistream

8.1 流插入

通过迭代器将string对象的内容输出到屏幕上

ostream& operator<<(ostream& out, const string& s)
{
   
    for (auto ch : s)
    {
   
        out << ch;
    }
    return out;
}

8.2 流提取

流提取的实现有两个问题需要解决:

  1. 在获取字符串前,不知道用户输入的字符串长度,无法提前开辟空间,开小了不够,开大了浪费
  2. 读取数据后,若对象中已有数据,会覆盖原数据

解决方案:

  1. 借助一个 数组存储数据,当数组装满时,将 数组内容拼接至字符串尾部,原数组则重新开始存储数据
  2. 调用 clear 函数先清理原对象数据,再进行输入
istream& operator>>(istream& in, string& s)
{
   
    s.clrar(); //清理原有数据
    char ch = in.get();
    char buff[128];
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
   
        buff[i++] = ch;
        if (i == 127)
        {
   
            buff[127] = '\0';
            s += buff;
            i = 0;
        }
        ch = in.get();
    }
    if (i != 0)
    {
   
        buff[i] = '\0';
        s += buff;
    }
    return in;
}

9. 完整代码

#include <iostream>
#include <assert.h>
#include <string.h>
using namespace std;

//模拟实现string
namespace sakura
{
   
    class string
    {
   
    public:
        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;
        }

        //string(const char* str = nullptr) //不可以
        //string(const char* str = "\0") //会多一个'\0'
        string(const char* str = "")
            :_size(strlen(str))
        {
   
            _capacity = _size == 0 ? 3 : _size;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }

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

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

                _size = s._size;
                _capacity = s._capacity;
            }

            return *this;
        }

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

        const char* c_str()
        {
   
            return _str;
        }

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

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

        string operator+(char ch) const
        {
   
            string tmp(*this); //传值返回
            tmp.push_back(ch);

            return tmp;
        }

        string operator+(const string& s) const
        {
   
            string tmp(*this);
            tmp.append(s._str);

            return tmp;
        }

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

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

        size_t size() const
        {
   
            return _size;
        }

        size_t capacity() const
        {
   
            return _capacity;
        }

        bool empty() const
        {
   
            return _size == 0;
        }
        // 不修改成员变量数据的函数,最好都加上const
        bool operator>(const string& s) const
        {
   
            return strcmp(_str, s._str) > 0;
        }

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

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

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

        void resize(size_t n, char ch = '\0')
        {
   
            if (n < _size) //比_size小就是缩容,直接添加终止符号'\0'
            {
   
                //删除数据 保留前n个
                _size = n;
                _str[_size] = '\0';
            }
            else if (n > _size) //扩容
            {
   
                if (n > _capacity)
                {
   
                    reserve(n);
                }
                size_t i = _size;
                while (i < n)
                {
   
                    _str[i] = ch;
                    ++i;
                }
                _size = n;
                _str[_size] = '\0';
            }
        }

        void reserve(size_t n)
        {
   
            if (n > _capacity)
            {
   
                char* tmp = new char[n + 1]; //防止new失败
                strcpy(tmp, _str);
                delete[] _str;
                _str = tmp; //改变指针指向
                _capacity = n;
            }
        }

        void push_back(char ch)
        {
   
            if (_size + 1 > _capacity) //检查扩容
            {
   
                _capacity == 0 ? _capacity = 1 : 0;
                reserve(_capacity * 2); //二倍扩
            }
            //尾插字符
            _str[_size] = ch;
            _str[_size] = '\0';
            ++_size;
        }

        void append(size_t n, char ch)
        {
   
            //提前开空间
            if (_size + n > _capacity)
            {
   
                reserve(_size + n);
            }
            while (n--)
            {
   
                push_back(ch);
            }
        }


        void append(const char* str)
        {
   
            size_t len = strlen(str);
            if (_size + len > _capacity)
            {
   
                reserve(_size + len);
            }

            strcpy(_str + _size, str);
            //strcat(_str, str);
            _size += len;
        }

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

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

        string& insert(size_t pos, char ch)
        {
   
            assert(pos <= _size); //判断下标是否合法
            if (_size + 1 > _capacity) //判断扩容
            {
   
                reserve(2 * _capacity);
            }

            //挪动数据
            size_t end = _size + 1;
            while (end > pos)
            {
   
                _str[end] = _str[end - 1];
                --end;
            }
            //错误写法
            //size_t end = _size;
            //while (end >= pos)
            //{
   
            //    _str[end + 1] = _str[end];
            //    --end;
            //}

            //插入数据
            _str[pos] = ch;
            ++_size;
            return *this;
        }

        string& insert(size_t pos, const char* str)
        {
   
            assert(pos <= _size);
            size_t len = strlen(str);
            if (_size + len > _capacity)
            {
   
                reserve(_size + len);
            }
            //挪动数据
            size_t end = _size + len;
            while (end > pos + len - 1)
            {
   
                _str[end] = _str[end - len];
                --end;
            }

            //写法二:
            //size_t end = _size;
            //for (size_t i = 0; i < _size + 1; ++i)
            //{
   
            //    _str[end + len] = _str[end];
            //    --end;
            //}

            //拷贝插入
            strncpy(_str + pos, str, len); //只拷贝len个,避免将'\0'拷贝
            _size += len;
            return *this;
        }

        string& erase(size_t pos, size_t len = npos)
        {
   
            assert(pos < _size);
            if (len == npos || pos + len >= _size)
            {
   
                _str[pos] = '\0';
                _size = pos;
            }
            else
            {
   
                strcpy(_str + pos, _str + pos + len);
                _size -= len;
            }
            return *this;
        }

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

        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);
            char* p = strstr(_str + pos, str);
            if (p == nullptr)
            {
   
                return npos;
            }
            else
            {
   
                return p - _str;
            }
        }

        void clrar()
        {
   
            _str[0] = '\0';
            _size = 0;
        }
    private:
        char* _str;
        size_t _capacity;
        size_t _size;

        static const size_t npos;
    };
    const size_t string::npos = -1;

    ostream& operator<<(ostream& out, const string& s)
    {
   
        for (auto ch : s)
        {
   
            out << ch;
        }
        return out;
    }

    istream& operator>>(istream& in, string& s)
    {
   
        s.clrar();
        char ch = in.get();
        char buff[128];
        size_t i = 0;
        while (ch != ' ' && ch != '\n')
        {
   
            buff[i++] = ch;
            if (i == 127)
            {
   
                buff[127] = '\0';
                s += buff;
                i = 0;
            }
            ch = in.get();
        }
        if (i != 0)
        {
   
            buff[i] = '\0';
            s += buff;
        }
        return in;
    }
}

C++【STL】之string模拟实现,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正!

目录
相关文章
|
9天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
24 7
|
26天前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
51 4
|
28天前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
65 5
|
28天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
49 2
|
1月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
51 0
|
12天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
22 0
|
2月前
|
存储 程序员 C++
C++常用基础知识—STL库(2)
C++常用基础知识—STL库(2)
83 5
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
29 1
|
2月前
|
存储 自然语言处理 程序员
C++常用基础知识—STL库(1)
C++常用基础知识—STL库(1)
77 1
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
98 5