• C++ string模拟实现


    目录

            一.默认成员函数

            二.迭代器

            三.增

            四.容量

            五.[]的重载

            六.比较运算符的重载

            七.删

            八.查 

            九.改

            十.其他

            十一.输入输出函数


    string是C++用来表示字符串的类,下面我们来模拟实现一个string类的增删查改。

            一.默认成员函数

            4个主要默认成员函数,需要注意构造函数的初始化列表

    1. //默认构造函数
    2. string(const char* str = "")//缺省值为空
    3. :_capacity(strlen(str))
    4. , _size(_capacity)
    5. {
    6. _str = new char[_capacity + 1];//多开1存放'\0'
    7. strcpy(_str, str);//拷贝内容
    8. }
    9. //拷贝构造函数
    10. string(const string& s)
    11. :_str(nullptr)//VS会对内置进行处理,但其他编译器不会,防止未初始化_str导致delete崩溃
    12. , _size(0)
    13. , _capacity(0)
    14. {
    15. //传统写法
    16. /*_str = new char[s._size + 1];//多开1空间是存放'\0'
    17. strcpy(_str, s._str);
    18. _size = s._size;
    19. _capacity = s._capacity;*/
    20. //现代写法
    21. string tmp(s._str);
    22. swap(tmp);
    23. }
    24. //赋值重载函数
    25. string& operator=(string s)//传值传参已经拷贝了
    26. {
    27. //传统写法 参数为(const string& s)
    28. /*if (this != &s)
    29. {
    30. _str = new char[s._size + 1];//多开1空间是存放'\0'
    31. strcpy(_str, s._str);
    32. _size = s._size;
    33. _capacity = s._capacity;
    34. }
    35. return *this;*/
    36. //现代写法 参数为(string s)
    37. if (this != &s)
    38. {
    39. swap(s);
    40. }
    41. return *this;
    42. }
    43. //析构函数
    44. ~string()
    45. {
    46. delete[] _str;//释放_str指向的空间
    47. _str = nullptr;
    48. _size = 0;
    49. _capacity = 0;
    50. }

            二.迭代器

            string的迭代器就是原生指针包装成的。

    1. typedef char* iterator;//迭代器
    2. typedef const char* const_iterator;//const迭代器
    3. //迭代器是左闭右开
    4. iterator begin()
    5. {
    6. return _str;//指向第一个字符
    7. }
    8. iterator end()
    9. {
    10. return _str + _size;//指向'\0'
    11. }
    12. const_iterator begin() const
    13. {
    14. return _str;
    15. }
    16. const_iterator end() const
    17. {
    18. return _str + _size;
    19. }

            三.增

            这里只实现了增加字符和字符串的函数,不过基本涵盖到了大部分情况,对于增加string类的函数和增加字符串的基本一样。

    1. void push_back(char c)//增加字符
    2. {
    3. if (_size + 1 > _capacity)//判断是否超出当前容量
    4. {
    5. reserve(_capacity == 0 ? 4 : _capacity * 2);//扩两倍,需注意原始容量为0的情况
    6. }
    7. _str[_size] = c;//写入字符
    8. _size++;//增加当前字符个数
    9. _str[_size] = '\0';//补上'\0'
    10. }
    11. string& operator+=(char c)//增加字符
    12. {
    13. push_back(c);//复用即可
    14. return *this;
    15. }
    16. void append(const char* str)//增加字符串
    17. {
    18. size_t len = strlen(str);//算出增加的字符串的长度
    19. if (_size + len > _capacity)//判断是否超出当前容量
    20. {
    21. reserve(_size + len);//扩容
    22. }
    23. for (size_t i = 0; i < len; i++, _size++)
    24. {
    25. _str[_size] = str[i];//追加字符串
    26. }
    27. _str[_size] = '\0';//补上'\0'
    28. }
    29. string& operator+=(const char* str)//增加字符串
    30. {
    31. append(str);//复用即可
    32. return *this;
    33. }
    34. void clear()//清除string,只是修改_size
    35. {
    36. _size = 0;
    37. _str[_size] = '\0';
    38. }
    39. void swap(string& s)//交换两个string类的成员变量
    40. {
    41. std::swap(_str, s._str);
    42. std::swap(_size, s._size);
    43. std::swap(_capacity, s._capacity);
    44. }
    45. const char* c_str() const//返回_str的函数
    46. {
    47. return _str;
    48. }

            四.容量

            下面是两个扩容函数和一些基本函数。

    1. size_t size() const//返回当前string类中的字符个数
    2. {
    3. return _size;
    4. }
    5. size_t capacity() const//返回当前string类的容量大小
    6. {
    7. return _capacity;
    8. }
    9. bool empty() const//返回string是否为空
    10. {
    11. return _size == 0;
    12. }
    13. void resize(size_t n, char c = '\0')//让string类的_size变成n,多的会用c补足,少的会截断
    14. {
    15. if (n < _size)//如果是少的,直接截断
    16. {
    17. _size = n;
    18. _str[_size] = '\0';
    19. }
    20. else if (n > _size)
    21. {
    22. if (n > _capacity)//判断目标容量是否超出当前容量
    23. {
    24. reserve(n);//是就扩容
    25. }
    26. for (size_t i = _size; i < n; i++)
    27. {
    28. _str[i] = c;//用c补足n和_size之间的位置
    29. }
    30. _size = n;
    31. _str[_size] = '\0';//补充'\0'
    32. }
    33. }
    34. void reserve(size_t n)//容量扩容到n,小于当前容量就不理会
    35. {
    36. if (n > _capacity)//有点类似realloc扩容
    37. {
    38. char* tmp = new char[n + 1];//多开1空间存放'\0'
    39. strcpy(tmp, _str);
    40. delete[] _str;
    41. _capacity = n;
    42. _str = tmp;
    43. _str[_size] = '\0';
    44. }
    45. }

            五.[]的重载

            要针对const对象和非const对象写两个[]的重载,做到读和写分离。

    1. char& operator[](size_t index)//返回引用说明可以修改
    2. {
    3. assert(index < _size);//断言查询的下标有没有越界
    4. return _str[index];
    5. }
    6. const char& operator[](size_t index) const
    7. {
    8. assert(index < _size);//断言查询的下标有没有越界
    9. return _str[index];
    10. }

            六.比较运算符的重载

            比较运算符可以实现两个其他复用,复用和我之前的日期类完全一样。这里我比较大小使用了strcmp函数,这个函数在我之前的博客当中也有讲过实现原理和模拟实现,所以这里就不具体说明了。

    1. bool operator<(const string& s)//strcmp函数前者小于后者就会返回负数相等就会返回0,前者大于后者就会返回大于正数
    2. {
    3. return strcmp(this->_str, s._str) < 0;
    4. }
    5. bool operator==(const string& s)
    6. {
    7. return !strcmp(this->_str, s._str);
    8. }
    9. bool operator<=(const string& s)
    10. {
    11. return (*this) < s || *this == s;
    12. }
    13. bool operator>(const string& s)
    14. {
    15. return !((*this) <= s);
    16. }
    17. bool operator>=(const string& s)
    18. {
    19. return !((*this) < s);
    20. }
    21. bool operator!=(const string& s)
    22. {
    23. return !((*this) == s);
    24. }

            七.删

            删要诺数据,需要注意下标,稍有不慎就会越界或者出错。

    1. string& erase(size_t pos, size_t len)//删除pos位置后len个字符
    2. {
    3. assert(pos <= _size);//断言有没有越界
    4. if (pos + len >= _size)//如果要删除pos之后的所有的字符就像resize一样直接截断
    5. {
    6. _size = pos;
    7. _str[_size] = '\0';
    8. }
    9. else
    10. {
    11. //从前往后走,把右边的字符挪到左边去,这样不会出现覆盖的问题
    12. size_t end = pos + len;//这里要注意下标
    13. while (end <= _size)
    14. {
    15. _str[end - len] = _str[end];
    16. end++;
    17. }
    18. _size = pos;//更新当前字符的数量
    19. }
    20. return *this;
    21. }

            八.查 

            查找的函数有查找一个字符以及一个字符串,查找字符串复用了strstr函数,这个在我之前的博客有讲解以及实现。

            

    1. size_t find(char c, size_t pos = 0) const//找第一个出现在string中的c,找到了就返回下标,否则就返回npos(size_t -1)
    2. {
    3. for (size_t i = pos; i < _size; i++)
    4. {
    5. if (_str[i] == c)//找到就返回下标
    6. return i;
    7. }
    8. return npos;
    9. }
    10. size_t find(const char* s, size_t pos = 0) const//返回子串s在string中第一次出现的位置的下标
    11. {
    12. char* res = strstr(_str + pos, s);//在前者字符串中找后者字符串,返回找到的字符串的指针(前者),没有找到就会返回空指针
    13. if (res != nullptr)
    14. return res - _str;//堆中的数据是从下往上的!!!,所以是res-_str
    15. return npos;//没找到就返回npos
    16. }

            九.改

            insert函数是在指定下标处插入一个字符或字符串,需要注意挪数据的方向和下标。

    1. string& insert(size_t pos, char c)//在pos位置插入字符c
    2. {
    3. assert(pos <= _size);//断言有没有越界
    4. if (_size + 1 > _capacity)//判断有没有超过当前容量
    5. {
    6. reserve(_capacity == 0 ? 4 : _capacity * 2);//注意当前容量为0的情况
    7. }
    8. //从后往前走,从左往右挪数据
    9. size_t end = _size + 1;//注意起始位置的下标,这里是指向'\0'的下一个位置
    10. while (end > pos)//这里需注意如果end指向'\0'就得是end>=pos,假如pos为0就会死循环,因为pos和end都是size_t,就算把end改为int也会死循环,因为隐式类型转换,end和pos相比还是会转换成size_t
    11. {
    12. _str[end] = _str[end - 1];//从左往右挪数据
    13. end--;
    14. }
    15. _size++;//增加当前字符数量
    16. _str[pos] = c;//插入字符
    17. return *this;
    18. }
    19. string& insert(size_t pos, const char* str)//在pos位置插入字符串
    20. {
    21. assert(pos <= _size);//断言有没有越界
    22. size_t len = strlen(str);//记录str的长度
    23. if (_size + len > _capacity)//判断有没有超过当前容量
    24. {
    25. reserve(_size + len);//扩容,因为不知道len长度和2*_capacity谁长,所以干脆直接扩容插入后的长度
    26. }
    27. //这里和insert字符基本一样,挪数据的间隔不是1而是len
    28. size_t end = _size + len;//注意下标
    29. while (end > pos)
    30. {
    31. _str[end] = _str[end - len];//从左往右挪数据
    32. end--;
    33. }
    34. for (size_t i = 0; i < len; i++, pos++)
    35. {
    36. _str[pos] = str[i];//插入字符串
    37. }
    38. _size += len;//增加当前字符数量
    39. return *this;
    40. }

            十.其他

            substr函数就是截取一段字符串。

    1. string substr(size_t pos = 0, size_t len = npos) const//从pos位置往后len字符截取字符串
    2. {
    3. string s;//创建字符串
    4. size_t end = pos + len;//假设end是pos之后len个字符
    5. if (len == npos || pos + len >= _size)//如果len或len+pos的位置超过了当前字符数量就令end指向'\0'
    6. {
    7. end = _size;
    8. len = _size - pos;
    9. }
    10. s.reserve(len);//提前开好空间,防止多次扩容造成性能浪费
    11. for (size_t i = pos; i < end; i++)
    12. {
    13. s += _str[i];//截取字符串
    14. }
    15. return s;//返回
    16. }

            十一.输入输出函数

            cout的实现比较简单,就是打印字符串的每一个字符,cin稍微复杂。这里是实现成全局函数,并且是string类的友元。

    1. ostream& _string::operator<<(ostream& _cout, const _string::string& s)
    2. {
    3. for (auto ch : s)//范围for直接打印
    4. {
    5. _cout << ch;
    6. }
    7. return _cout;
    8. }
    9. istream& _string::operator>>(istream& _cin, _string::string& s)
    10. {
    11. char buff[129];//开一个字符数组
    12. char ch;
    13. ch = _cin.get();//从输入流中读取字符
    14. size_t size = 0;//记录buff数组存了几个字符
    15. while (ch != ' ' && ch != '\n')//没遇到空格和换行就继续
    16. {
    17. buff[size++] = ch;//读入字符
    18. if (size == 128)//如果读到上限就写入到s里
    19. {
    20. buff[129] = '\0';//记得要补上'\0'
    21. s += buff;
    22. size = 0;//重置size
    23. }
    24. ch = _cin.get();//读取字符
    25. }
    26. buff[size] = '\0';//buff可能还有数据,写入到s中即可
    27. s += buff;
    28. return _cin;
    29. }

            以上就是string的简单模拟实现,如有问题可在评论区提出。

  • 相关阅读:
    【支付漏洞】
    经典面试题-小于N的最大数
    Feign源码解析6:如何集成discoveryClient获取服务列表
    Linux-gitlab常用命令
    Flink 提交到 yarn 时 修改 yarn 的job 名称
    【前端之旅】响应式布局及Bootstrap框架
    Docker安装Bitbucket
    (附源码)基于微服务架构的餐饮系统的设计与实现-计算机毕设 86393
    MVVM架构下wpf的密码框绑定
    Vue2 常用用法
  • 原文地址:https://blog.csdn.net/ZM_QMZS/article/details/132924275