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模拟实现,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

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

目录
相关文章
|
23天前
|
C++ 容器
|
15天前
|
存储 程序员 C++
C++常用基础知识—STL库(2)
C++常用基础知识—STL库(2)
54 5
|
13天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
14 1
|
15天前
|
存储 自然语言处理 程序员
C++常用基础知识—STL库(1)
C++常用基础知识—STL库(1)
37 1
|
23天前
|
C++ 容器
|
23天前
|
C++ 容器
|
23天前
|
存储 C++ 容器
|
23天前
|
算法 安全 Linux
【C++STL简介】——我与C++的不解之缘(八)
【C++STL简介】——我与C++的不解之缘(八)
|
17天前
|
算法 数据处理 C++
c++ STL划分算法;partition()、partition_copy()、stable_partition()、partition_point()详解
这些算法是C++ STL中处理和组织数据的强大工具,能够高效地实现复杂的数据处理逻辑。理解它们的差异和应用场景,将有助于编写更加高效和清晰的C++代码。
14 0
|
19天前
|
C语言 C++
深度剖析C++string(中)
深度剖析C++string(中)
39 0