• 《STL容器篇》-string模拟实现


    一、本篇接口实现包括

    1. 成员函数包括:
    2. string(const char* str = "")
    3. ~string()
    4. void swap(string& s)
    5. string(const string& s)//传统写法
    6. string(const string& s)//现代写法
    7. string& operator=(const string& s)//传统写法
    8. string& operator=(string s)//现代写法
    9. void reserve(size_t n)
    10. void resize(size_t n, char ch = '\0')
    11. void push_back(char ch)
    12. void pop_back()
    13. void append(const char* str)
    14. const char* c_str()const
    15. size_t size()const
    16. size_t capacity()const
    17. iterator begin()
    18. iterator end()
    19. const_iterator begin()const
    20. const_iterator end()const
    21. bool empty()const
    22. void clear()
    23. char& operator[](size_t pos)
    24. const char& operator[](size_t pos)const
    25. string& operator+=(char ch)
    26. string& operator+=(const char* str)
    27. string& insert(size_t pos, char ch)
    28. string& insert(size_t pos, const char* str)
    29. string& erase(size_t pos, int n = npos)
    30. size_t find(char ch, size_t pos = 0)
    31. size_t find(const char* str, size_t pos = 0)
    32. 全局函数包括:
    33. std::ostream& operator<<(std::ostream& out, const string& s)
    34. std::istream& operator>>(std::istream& in, string& s)
    35. bool operator>(const string& s1, const string& s2)
    36. bool operator==(const string& s1, const string& s2)
    37. bool operator>=(const string& s1, const string& s2)
    38. bool operator<(const string& s1, const string& s2)
    39. bool operator<=(const string& s1, const string& s2)
    40. bool operator!=(const string& s1, const string& s2)

    这里我只挑选部分接口进行详细解析,因为其他接口实现起来实在容易。由于移动构造和移动赋值属于c++11的新增范围,这里就不涉及了,笔者会在c++11章节补充这部分的内容。

    二、接口全实现

    https://gitee.com/zxlfx/c-code-warehouse/tree/master/2022_7_19

    三、部分接口详解

    3.1拷贝构造的传统写法与现代写法

    1. string(const string& s)//传统写法
    2. {
    3. _str = new char[s._capacity + 1];
    4. _capacity = s._capacity;
    5. _size = s._size;
    6. strcpy(_str, s._str);
    7. }
    8. void swap(string& s)
    9. {
    10. std::swap(_str, s._str);
    11. std::swap(_size, s._size);
    12. std::swap(_capacity, s._capacity);
    13. }
    14. string(const string& s)//现代写法
    15. :_str(nullptr),
    16. _size(0),
    17. _capacity(0)
    18. {
    19. string temp(s._str);
    20. swap(temp);
    21. }

    传统写法是老老实实开空间,然后将数据拷贝至新空间,而现代写法是取出s的_str,再调用string(const char* str = "")生成临时对象temp,然后交换*this与temp的_str,_size,_capacity。这就相当于让temp去帮你创建一个拷贝对象,然后夺取temp的资源,但temp在销毁的时候会调用析构函数,此时它的_str是原本this的_str,因此在交换_str之前,需要将this的_str置为空,否则temp调用析构函数时会崩溃。

    3.2赋值重载的现代写法和传统写法

    1. string& operator=(const string& s)//传统写法
    2. {
    3. if (this != &s)
    4. {
    5. char* temp = new char[s._capacity + 1];
    6. strcpy(temp, s._str);
    7. delete[] _str;
    8. _str = temp;
    9. _size = s._size;
    10. _capacity = s._capacity;
    11. }
    12. return *this;
    13. }
    14. string& operator=(string s)//现代写法
    15. {
    16. swap(s);
    17. return *this;
    18. }

    相比传统写法,现代写法更简洁,只不过下面这种现代写法在给自己赋值的场景下会调用拷贝构造,效率不高,而传统写法加了不给自己赋值的判断,直接返回*this。

    3.3reserve和resize

    1. void reserve(size_t n)
    2. {
    3. if (n > _capacity)
    4. {
    5. char* temp = new char[n + 1];
    6. strcpy(temp, _str);
    7. delete[] _str;
    8. _str = temp;
    9. _capacity = n;
    10. }
    11. }
    12. void resize(size_t n, char ch = '\0')
    13. {
    14. if (n < _size)
    15. {
    16. _str[n] = '\0';
    17. _size = n;
    18. }
    19. else
    20. {
    21. if (n > _capacity)
    22. {
    23. reserve(n);
    24. }
    25. else
    26. {
    27. for (size_t i = _size; i < n; i++)
    28. {
    29. _str[i] = ch;
    30. }
    31. _str[n] = '\0';
    32. _size = n;
    33. }
    34. }
    35. }

    这两个函数的功能主要参考vs2019,对于reserve,如果n>capacity时就增容,ncapacity就扩容到n,然后不断插入ch至size==n。

    3.4insert和erase

    1. string& insert(size_t pos, char ch)
    2. {
    3. assert(pos <= _size);
    4. if (_size == _capacity)
    5. {
    6. reserve(_capacity == 0 ? 4 : _capacity * 2);
    7. }
    8. char* start = _str + pos;
    9. char* end = _str + _size;
    10. while (end >= start)
    11. {
    12. *(end + 1) = *end;
    13. end--;
    14. }
    15. _str[pos] = ch;
    16. _size++;
    17. return *this;
    18. }
    19. string& insert(size_t pos, const char* str)
    20. {
    21. assert(pos <= _size);
    22. size_t size = strlen(str);
    23. if (_size + size > _capacity)
    24. {
    25. reserve(_size + size);
    26. }
    27. char* start = _str + pos;
    28. char* end = _str + _size;
    29. while (end >= start)
    30. {
    31. *(end + size) = *end;
    32. end--;
    33. }
    34. strncpy(_str + pos, str, size);
    35. _size += size;
    36. return *this;
    37. }
    38. string& erase(size_t pos, int n = npos)
    39. {
    40. assert(pos < _size);
    41. if (pos + n >= _size)
    42. {
    43. _str[pos] = '\0';
    44. _size = pos;
    45. }
    46. else
    47. {
    48. char* start = _str + pos;
    49. char* end = _str + _size;
    50. while (start < end)
    51. {
    52. *start = *(start + n);
    53. start++;
    54. }
    55. _size -= n;
    56. }
    57. return *this;
    58. }

    这里建议while判断条件使用地址比较,如果使用的是下标比较,如while(num>=pos)当pos为0时,num为0时,num进入while,然后--,此时num变为整形的最大值,因为num是size_t类型的,那么依旧会进while,导致一直循环下去。而使用地址做while判断则可以很好的规避这样的问题。

    3.5<<和>>重载

    1. std::ostream& operator<<(std::ostream& out, const string& s)
    2. {
    3. for (auto& a : s)
    4. {
    5. out << a;
    6. }
    7. return out;
    8. }
    9. std::istream& operator>>(std::istream& in, string& s)
    10. {
    11. s.clear();
    12. int ch = 0;
    13. char buf[128] = { 0 };
    14. int i = 0;
    15. while ((ch = in.get()) != ' ' && ch != '\n')
    16. {
    17. buf[i++] = ch;
    18. if (i == 127)
    19. {
    20. s += buf;
    21. memset(buf, '\0', sizeof(char) * 128);
    22. i = 0;
    23. }
    24. }
    25. s += buf;
    26. return in;
    27. }

    这里重载<<时,为什么不直接cout<

    当s为"1 4 6 a b \0 2 3  4",打印的时候后面的元素就不能打印出来了,就只能打印1 4 6 a b。

    关于>>重载中的buf,buf在这里起缓冲作用,如果s不断+=ch,那么就可能频繁的扩容,影响效率,而如果把ch放进buf里,如果buf满了,就把buf的数据放入s中,同时清空buf的数据,并将i置为0,最后也不要忘了把还没满的buf的数据放入s中。

    3.6find(查找一个字符,和查找子字符串)

    1. size_t find(char ch, size_t pos = 0)
    2. {
    3. for (size_t i = pos; i < size(); i++)
    4. {
    5. if (_str[i] == ch)
    6. {
    7. return i;
    8. }
    9. }
    10. return npos;
    11. }
    12. size_t find(const char* str, size_t pos = 0)
    13. {
    14. char* ret = strstr(_str + pos, str);
    15. if (ret != nullptr)
    16. {
    17. return ret - _str;
    18. }
    19. return npos;
    20. }

    查找一个字符,遍历s即可。查找子字符串使用c语言的库函数strstr,由于find是返回下标,而strstr是返回子字符串的地址,那么pos等于ret-_str。

    四、最后

    以上实现只起参考作用,如有错误或者疑惑的地方,还请读友在评论区指正或者私聊我,感谢!

    关于实现细节,很多地方代码读起来还是比较轻松的,这里只挑选了部分需要注意的地方进行解析,后续的stl容器会依次介绍vector、list、deque、map、set、unorderedmap、unorderedset尽请期待。

  • 相关阅读:
    opencv 的腐蚀效果(3)
    pandas教程:String Manipulation 字符串处理和正则表达式re
    【攻破css系列——第九天】常规流
    并发实现实例
    从移动硬盘或U盘安装苹果笔记本电脑MacOS系统或叫解决安装MacOS系统时“准备安装时发生错误,请尝试重新运行此应用程序”的问题
    (转载)基于遗传算法的多目标优化算法(matlab实现)
    第七章:基于敏捷模式S公司质量搭建的第一阶段
    14:00面试,14:06就出来了,问的问题有点变态。。。
    9.基于粤嵌gec6818开发板小游戏2048的算法实现
    基于二次近似(BLEAQ)的双层优化进化算法_matlab程序
  • 原文地址:https://blog.csdn.net/m0_62171658/article/details/125865959