• C++ string类的实现


    📋 个人简介

    • 💖 作者简介:大家好,我是菀枯😜

    • 🎉 支持我:点赞👍+收藏⭐️+留言📝

    • 💬格言:不要在低谷沉沦自己,不要在高峰上放弃努力!☀️

      v2-af3cfbda99d44f8eb114cc6c74c92253_720w

    前言

    在C语言中,没有专门用来表示字符串的类型。C语言的字符串是一系列以’\0’为结尾的字符的集合。虽然C语言为这样的字符串提供了一系列的库函数如strcpy, strcmp等等,但这些函数与字符串这个类型是分开的,这不太符合C++中面试对象的思想,所以在C++中封装了一个string类,来帮助我们操作字符串。string该如何使用,我这里就不做赘述了,大家可以去看看官方文档呀😄

    string - C++ Reference (cplusplus.com)

    string模拟实现

    string简单实现

    首先我们不考虑string类的增删查改,只是先给string类搭建一个最简单的框架出来。

    和C语言中相同,为了存储一个字符串,我们的string类需要一个char*的指针来指向字符像这个对象。作为一个对象,string还需要有构造函数,析构函数和拷贝构造。

    class string
    {
    private:
    	char *_str;
    public:
    	string(const char *str)
    		: _str(new char[strlen(str) + 1]) // +1 是给'\0'留出位置
    	{
    		strcpy(_str, str);
    	}
    
    	string(const string &str)
    		: _str(new char[strlen(str._str) + 1])
    	{
    		strcpy(_str, str._str);
    	}
    	~string()
    	{
    		if (_str)
    		{
    			delete[] _str;
    			_str = nullptr;
    		}
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    有的朋友可能会疑惑,这里的构造函数和拷贝构造函数为什么不用编译器自动生成的,直接将_str指向原本的字符串就可以了,为什么还要开辟空间呢?

    这是因为我们在日常使用中,假如有两个string类 a 和 b,b是由a拷贝构造而来,一般情况下我们在修改b的同时不希望a也被改。此外,如果直接将_str指向原本的字符串会导致的问题是当 a 和 b用完被销毁时,会对同一片空间调用两次析构函数,对同一片空间释放两次。所以在这里,我们需要重新开辟一片空间来给这个string。这也就是所谓的深拷贝

    然后,为了访问string类中的元素,我们需要对运算符[]进行重载。

    char& operator[](size_t pos)
    {
        assert(pos < strlen())
        return _str[pos];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样我们就实现了一个简单的string类。

    string完整实现

    构造函数,析构函数,拷贝构造

    之前我们实现的一个string类是一个最简单的string类,它没有办法进行增删查改,接下来我们就来一点一点完善它。

    要实现增删查改,我们还需要两个变量,一个记录string类当前长度,一个记录string类的容量大小。加入这两个变量后,我们原本的构造函数,拷贝构造和析构函数需要发生一点点变化。

    class string
    {
    private:
    	char *_str;
    	size_t _size;
    	size_t _capacity;
    
    public:
    	string(const char *str = "")
    		: _size(strlen(str)), _capacity(_size)
    	{
    		_str = new char[_capacity + 1];
    		strcpy(_str, str);
    	}
    
        string(const string &str)
            : _size(str._size), _capacity(str._capacity)
        {
            _str = new char[_size + 1];
            strcpy(_str, str._str);
        }
        
    	~string()
    	{
    		if (_str)
    		{
    			delete[] _str;
    			_str = nullptr;
    			_size = _capacity = 0;
    		}
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    运算符重载

    接下来我们来实现一下,string类的运算符。在实现运算符重载时,我们需要做的只是实现少数几个运算符即可,其他的运算符可复用前面实现的运算符来达到我们想要的效果。

    //关系运算符的重载
    bool operator>(const string &s)
    {
        return strcmp(_str, s.c_str());
    }
    
    bool operator==(const string &s)
    {
        return strcmp(_str, s.c_str()) == 0;
    }
    
    bool operator!=(const string &s)
    {
        return !(*this == s);
    }
    
    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);
    }
    //操作运算符的重载
    string &operator=(string& str)
    {
        if(*this != str)
        {
            char *tmp = new char[str._capacity + 1];
            strcpy(tmp,str._str);
            delete[] _str;
            _str = tmp;
            _size = str._size;
            _capacity = str._capacity;
        }
        return *this;
    }
    
    char &operator[](size_t pos)
    {
        assert(pos < _size);
    
        return *(_str + pos);
    }
    
    const char &operator[](size_t pos) const
    {
        assert(pos < _size);
        return *(_str + pos);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    string接口实现

    首先是比较简单的size(),empty(),capacity(),clear()。这些接口大部分直接访问string类的成员变量就可以得到结果。

    size_t size() const
    {
        return _size;
    }
    
    size_t capacity() const
    {
        return _capacity;
    }
    
    bool empty() const
    {
        return 0 == _size;
    }
    //后面添加const的目的是为了让所有对象都可以进行访问
    void clear()
    {
        _str[0] = '\0';
        _size = 0;
        _capacity = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    因为后面的接口大部分都需要进行空间的调整,所以首先我们将调整空间的接口,reserve和resize实现。

    void reserve(size_t n)
    {
        if (n > _capacity) //判断是否需要扩容
        {
            char *tmp = new char[n + 1];
            strcpy(tmp, _str);
            delete[] _str;
            _str = tmp;
            _capacity = n;
        }
    }
    
    //resize和reserve的区别在于,reserve只是开空间,而resize还要进行初始化
    void resize(size_t n, char c = '\0')
    {
        if (n > _capacity)
        {
            reserve(n); //开空间复用reserve
        }
        for (size_t i = _size; i < n; ++i)
        {
            _str[i] = c;
        }
        _size = n;
        _str[_size] = '\0';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    接下来是插入的实现,首先是push_back,这个比较简单,找到尾部进行插入即可。

    void push_back(char n)
    {
        if (_size == _capacity)
        {
            reserve(_capacity == 0 ? 4 : _capacity * 2); //开空间复用reserve
        }
        _str[_size++] = n;
        _str[_size] = '\0';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    接下来是insert,这个较push_back而言要麻烦一些,因为除了尾插,其他地方去插入数据你都需要挪动后面数据的位置。

    string &insert(size_t pos, const char *str)
    {
        //检查空间是否足够
        assert(pos <= _size);
        size_t len = strlen(str);
        if (len + _size > _capacity)
        {
            reserve(len + _size);
        }
    
       	//挪动后面的数据
        size_t end = _size + len;
        while (end != pos + len - 1)
        {
            _str[end] = _str[end - len];
            --end;
        }
    
        //数据插入
        strncpy(_str + pos, str, len);
        _size += len;
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    写完了插入,接下来当然就是删除接口:eraser

    string &eraser(size_t pos, size_t len = npos) //npos为静态变量,值为-1
    {
        assert(pos < _size);
        
        if (len == npos || pos + len >= _size) //将位置后的元素全部删除
        {
            _str[pos] = '\0';
            _size = pos;
        }
        else //删除位置后的部分元素
        {
            size_t begin = pos + len;
            while (begin <= _size)
            {
                _str[begin - len] = _str[begin];
                begin++;
            }
            _size = _size - len;
        }
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    迭代器的实现

    C++中的迭代器和指针类似。为什么要有迭代器呢?因为C++中有各种各样的容器,每个容器它背后的存储方式不同,访问方式也不同,为了让使用者的使用成本降低,使大部分容器可以以相同的方式去访问,就有了迭代器的产生。

    接下来我们来实现string的迭代器,其实string的迭代器就是一个指针。并不用去封装特别的东西。

    typedef char *iterator;
    typedef const char *const_iterator;
    
    const_iterator begin() const
    {
        return _str;
    }
    
    const_iterator end() const
    {
        return _str + _size;
    }
    
    iterator begin()
    {
        return _str;
    }
    
    iterator end()
    {
        return _str + _size;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    部分函数优化和完善

    前面在写运算符重载时,还有部分运算符未重载在此加上

    string &operator+=(const char *str)
    {
        append(str);
    }
    
    string &operator+=(char n)
    {
        push_back(n);
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    同时增加拷贝构造和operator=的现代写法,之前我们写拷贝构造和operator=时都需要自己去重新开空间,那么这个活可不可以让其他人帮我做呢?

    我们来看看下面这一段代码

    void swap(string& str)
    {
        std::swap(_str, str._str);
        std::swap(_size, str._size);
        std::swap(_capacity, str._capacity);
    }
    
    string(const string &s)
        : _str(nullptr), _size(0), _capacity(0)
    {
        string tmp(s._str);
        swap(tmp);
    }
    
    string &operator=(string s)
    {
        swap(s);
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上述代码同样可以帮我们完成拷贝构造和operator= ,原理如下:

    1. 首先是拷贝构造,我们在拷贝构造中使用构造函数去创建一个临时对象,这个临时对象在创建时,就帮我们开辟了空间。然后我们将临时对象和此对象的所有成员进行一个交换,这样此对象就可以接管临时对象创建的那块空间,我们的拷贝构造也就成功了
    2. 在operator=这,我们使用的是传值传参。好处在于由于我们的string类是自定义对象,所以在传参时会去调用拷贝构造,这样传过来的str参数也拥有了自己的空间,此时我们和拷贝构造一样,将str所开辟的那块空间接管,同时由于str是函数参数,当函数结束时,str会去调用析构函数进行一个空间释放。

    完整代码

    class string
    {
    public:
        typedef char *iterator;
        typedef const char *const_iterator;
    
        const_iterator begin() const
        {
            return _str;
        }
    
        const_iterator end() const
        {
            return _str + _size;
        }
    
        iterator begin()
        {
            return _str;
        }
    
        iterator end()
        {
            return _str + _size;
        }
    
        string(const char *s = "")
            : _size(strlen(s)),
              _capacity(_size)
        {
            _str = new char[_capacity + 1];
            strcpy(_str, s);
        }
    
        string(const string &s)
            : _str(nullptr),
              _size(0),
              _capacity(0)
        {
            string tmp(s._str);
            swap(tmp);
        }
    
        ~string()
        {
            delete[] _str;
            _str = nullptr;
            _size = _capacity = 0;
        }
    
        string &operator=(string s)
        {
            swap(s);
            return *this;
        }
    
        char &operator[](size_t pos)
        {
            assert(pos < _size);
    
            return *(_str + pos);
        }
    
        const char &operator[](size_t pos) const
        {
            assert(pos < _size);
            return *(_str + pos);
        }
    
        const char *c_str() const
        {
            return _str;
        }
    
        void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char *tmp = new char[n + 1];
                strcpy(tmp, _str);
                delete[] _str;
                _str = tmp;
                _capacity = n;
            }
        }
    
        void push_back(char n)
        {
            if (_size == _capacity)
            {
                reserve(_capacity == 0 ? 4 : _capacity * 2);
            }
            _str[_size++] = n;
            _str[_size] = '\0';
        }
    
        string &operator+=(char n)
        {
            push_back(n);
            return *this;
        }
    
        void append(const char *str)
        {
            size_t len = _size + strlen(str);
            if (len > _capacity)
            {
                reserve(len);
            }
            strcpy(_str + _size, str);
            _size = len;
        }
    
        string &operator+=(const char *str)
        {
            append(str);
        }
    
        void resize(size_t n, char c = '\0')
        {
            if (n > _capacity)
            {
                reserve(n);
            }
            for (size_t i = _size; i < n; ++i)
            {
                _str[i] = c;
            }
            _size = n;
            _str[_size] = '\0';
        }
    
        size_t size() const
        {
            return _size;
        }
    
        size_t capacity() const
        {
            return _capacity;
        }
    
        bool empty()
        {
            return 0 == _size;
        }
    
        bool operator>(const string &s)
        {
            return strcmp(_str, s.c_str());
        }
    
        bool operator==(const string &s)
        {
            return strcmp(_str, s.c_str()) == 0;
        }
    
        bool operator!=(const string &s)
        {
            return !(*this == s);
        }
    
        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);
        }
    
        string &insert(size_t pos, const char *str)
        {
            assert(pos <= _size);
            size_t len = strlen(str);
            if (len + _size > _capacity)
            {
                reserve(len + _size);
            }
    
            size_t end = _size + len;
            while (end != pos + len - 1)
            {
                _str[end] = _str[end - len];
                --end;
            }
    
            strncpy(_str + pos, str, len);
            _size += len;
            return *this;
        }
    
        string &eraser(size_t pos, size_t len = npos)
        {
            assert(pos < _size);
    
            if (len == npos || pos + len >= _size)
            {
                _str[pos] = '\0';
                _size = pos;
            }
            else
            {
                size_t begin = pos + len;
                while (begin <= _size)
                {
                    _str[begin - len] = _str[begin];
                    begin++;
                }
                _size = _size - len;
            }
            return *this;
        }
    
        void clear()
        {
            _size = 0;
            _str[0] = '\0';
            _capacity = 0;
        }
    
        void swap(string &s)
        {
            std::swap(_str, s._str);
            std::swap(_size, s._size);
            std::swap(_capacity, s._capacity);
        }
    
        size_t find(char c, size_t pos = 0) const
        {
            while (pos < _size)
            {
                if (_str[pos] == c)
                {
                    return pos;
                }
                ++pos;
            }
            return npos;
        }
    
        size_t find(char *s, size_t pos = 0) const
        {
            const char *p = strstr(_str + pos, s);
            if (p == nullptr)
            {
                return npos;
            }
            else
            {
                return p - _str;
            }
        }
    
    private:
        char *_str;
        size_t _size;
        size_t _capacity;
        const static size_t npos;
    };
    
    const size_t string::npos = -1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268

    结语

    1647941444633

    欢迎各位参考与指导!!!

  • 相关阅读:
    总结Java 并发编程 72 变
    vue-advanced-chat使用指南
    m基于PTS+TR的OFDM系统PAPR联合抑制算法matlab仿真
    杰理之充电仓UI 设置相关的接口 API【篇】
    链表专项之环形链表
    嵌入式通信协议----Wi-Fi协议详解(二)(基于STM32+有人物联网WIFI模块)
    Python3用OpenCV4连接图像
    【Unity3D】资源管理
    UDP通信程序的详细解析
    【特别提醒】订阅此专栏的用户请先阅读本文再决定是否需要购买此专栏
  • 原文地址:https://blog.csdn.net/m0_60447315/article/details/126448202