• 【C++】string类


    string是表示字符串的字符串类,该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。. string在底层实际是:basic_string模板类的别名,typedef basic_string string,在使用string类时,必须包含#include头文件以及using namespace std;

    本篇文章将重点讲解string类的使用和模拟实现


    1.string类常用接口说明

    1.1string类对象的常见构造

    string() 构造空的string类对象,即空字符串
    string(const char*s)用C-string来构造string类对象
    string(size_t n,char c)string类对象中包含n个字符C
    string(const string& s)拷贝构造
    string(const char* s,size_t n)用字符串s的前n个字符来构造

    1.2string类对象的容量操作

    size()        返回字符串的有效字符长度
    length()返回字符串的有效字符长度
    capacity()返回空间总大小
    empty()检测字符串是否为空,是返回true,否则返回false
    clear()清空有效字符
    reserve(int)为字符串预留空间
    resize(int,char)将有效字符的个数该成n个,多出的空间用字符c填充,如果只给了一个参数,默认初始化为'\0'

    注意:

    1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。

    2. clear()只是将string中有效字符清空,不改变底层空间大小。

    3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。

    4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小。

    1.3string类对象的访问及遍历操作

    1.operator[]

    1. string str = "hello word";
    2. int size = str.size();
    3. for (int i = 0; i < size; i++)
    4. {
    5. //str.operator[](i);
    6. cout << str[i] << endl;
    7. }

    2.迭代器

    正向迭代器

    1. string str = "hello word";
    2. string::iterator it = str.begin();//得到一个指向首元素的指针
    3. while (it != str.end())//str.end()得到一个指向'\0'的指针
    4. {
    5. cout << *it << endl;
    6. it++;
    7. }

    反向迭代器

    1. string str = "hello word";
    2. string::reverse_iterator it = str.rbegin();//得到一个指向最后一个有效元素的指针
    3. while (it != str.rend())//str.rend()得到一个指向首元素前一个的指针
    4. {
    5. cout << *it << endl;
    6. it++;
    7. }

     

     string::iterator得到的是可读可写的指针,可以通过该指针修改原来的字符串

    如果想保护原来的字符数,只读不让写string::const_iterator

    为了方便,我们可以用auto的方式来自动识别类型

     3.范围for

    1. string str = "hello word";
    2. for (auto e : str)
    3. {
    4. cout << e << endl;
    5. }
    6. return 0;

    范围for的底层原理还是迭代器

    1.4string类对象的修改操作

    push_back在字符串后尾插字符c
    append在字符后追加一个字符串
    operator+=(最常用的)在字符串后追加字符串str
    c_str返回C格式字符串
    find+npos从字符串pos位置开始往后找字符c,返回该字符在字符串的位置
    rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
    substr在str中从pos位置开始,截取n个字符,然后将其返回

     注意:

    1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

    2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

    1.5 string类非成员函数

    operator+尽量少用,因为船只返回,导致深拷贝效率低
    operator>>流输入运算符重载
    operator<<流提起运算符重载
    getline获取一行字符串
    relational operators比较运算符重载

    2.string类模拟实现

    2.1构造函数

     在标准库中没有对string变量赋初始值的话,会默认是空字符串,开15个空间

    1. string(const char* str = "")
    2. :_size(strlen(str))
    3. , _capacity(15)
    4. {
    5. if (_size>_capacity)
    6. {
    7. _capacity = _size;
    8. }
    9. _str = new char[_capacity + 1];
    10. strcpy(_str, str);
    11. }

    2.2 reserve

    reserve是string类中更改容量的一个成员函数,准确的来说是一个扩容函数,如果n

    1. void reserve(size_t n = 0)
    2. {
    3. if (n > _capacity)
    4. {
    5. char* temp = new char[n + 1];//需要多一个空间给'\0'
    6. strcpy(temp, _str);//将原数据拷贝
    7. delete[] _str;
    8. _str = temp;
    9. _capacity = n;
    10. }
    11. }

     2.3拷贝构造函数

    写拷贝构造函数前需要讲讲深浅拷贝问题

    如果没有写拷贝构造函数,那么编译器就会自动生成一个浅拷贝的拷贝构造函数

     

     如果是浅拷贝的话,str1和str2指向的字符串中间是同一片空间,会造成以下几个问题

    1.如果str1修改,会影响str2的值,同样str2修改会影响str1的值

    2.结束调用析构函数的时候会对同一片空间析构两次

    所以需要深拷贝,深拷贝会重新开辟一个空间给str2

    1. //s1(s2)
    2. //传统写法
    3. string(const string& s)
    4. :_size(s._size)
    5. ,_capacity(s._capacity)
    6. {
    7. _str = new char[_capacity + 1];
    8. strcpy(_str, s._str);
    9. }

    上面这个拷贝构造函数是传统的写法,现在的拷贝构造函数会复用构造函数

    1. void swap(string& s)
    2. {
    3. char* temp = {0};
    4. std::swap(temp, s._str);//用全局的swap函数对string底层的字符串进行交换
    5. std::swap(s._str, _str);
    6. std::swap(_str, temp);
    7. }
    8. //现在写法
    9. string(const string& s)
    10. :_size(s._size)
    11. ,_capacity(s._capacity)
    12. ,_str(nullptr)//需要赋一个空指针,交换后这个指针将会给temp,temp回收后会调用析构函数,没有给初始指针会报错
    13. {
    14. string temp(s._str);
    15. //swap(temp,this) 使用全局的swap函数会调用深拷贝效率低
    16. swap(temp);
    17. }

    2.4 operator=()

    1. //s1 = s2
    2. //传统版本
    3. string& operator=(const string& s)
    4. {
    5. if (&s != this)
    6. {
    7. _size = s._size;
    8. _capacity = s._capacity;
    9. _str = new char[_capacity + 1];
    10. strcpy(_str, s._str);
    11. }
    12. return *this;
    13. }

     和拷贝构造函数一样,

    2.4 c_str()

    获得c语言形式的字符串

    1. const char* c_str() const
    2. {
    3. return _str;
    4. }

    2.5 size()

     获得字符串长度

    1. size_t size()
    2. {
    3. return _size;
    4. }

     2.6 capacity()

    1. size_t capacity(const string& s)const
    2. {
    3. return s._capacity;
    4. }

    2.7 operator[]()

    []运算符重载,获取字符串中i位置的字符

    1. char& operator[](size_t i)
    2. {
    3. return _str[i];
    4. }

    同时需要考虑到只读的情况

    1. const char& operator[](size_t i)const
    2. {
    3. return _str[i];
    4. }

    2.8 operator=()

    1. //s1 = s2
    2. //传统版本
    3. string& operator=(const string& s)
    4. {
    5. if (&s != this)
    6. {
    7. char* temp = new char[s._capacity + 1];
    8. strcpy(temp, s._str);
    9. delete[] _str;
    10. _str = temp;
    11. _size = s._size;//最后放在后面,防止temp没申请到空间
    12. _capacity = s._capacity;
    13. }
    14. return *this;
    15. }

     同样赋值运算符重载也可以复用构造函数

    1. //现在写法
    2. string& operator=(string s)
    3. {
    4. swap(s);//用临时变量和*this交换
    5. return *this;
    6. }

    2.9relational operator

    比较函数的实现

    比较原则:比较第一个不匹配的字符的大小,如果都匹配就比较字符串的长度

    1. int compare(const string& s)const
    2. {
    3. return std::strcmp(_str, s._str);
    4. }
    5. bool operator<(string& str1, string& str2)
    6. {
    7. if (str1.compare(str2) < 0)
    8. {
    9. return true;
    10. }
    11. return false;
    12. }
    13. bool operator==(string& str1, string& str2)
    14. {
    15. if (str1.compare(str2) == 0)
    16. {
    17. return true;
    18. }
    19. return false;
    20. }
    21. bool operator<=(string& str1, string& str2)
    22. {
    23. if (str1
    24. {
    25. return true;
    26. }
    27. return false;
    28. }
    29. bool operator>(string& str1, string& str2)
    30. {
    31. return !(str1 <= str2);
    32. }
    33. bool operator>=(string& str1, string& str2)
    34. {
    35. return !(str1 < str2);
    36. }
    37. bool operator!= (string & str1, string & str2)
    38. {
    39. return !(str1 == str2);
    40. }

     2.10 insert()

     我们就实现两个重载,一个是从pos位置插入一个字符,一个是从pos位置插入字符串

    insert函数与顺序表的插入一样,可以参考下面这篇文章

    (4条消息) 【数据结构】顺序表_Patrick star`的博客-CSDN博客

    1. //插入一个字符
    2. void insert(size_t pos,char c)
    3. {
    4. assert(pos <= _size);
    5. size_t end = _size+1;
    6. while (end!=pos)
    7. {
    8. _str[end] = _str[end - 1];
    9. end--;
    10. }
    11. _str[pos] = c;
    12. }
    1. //插入一个字符串
    2. void insert(size_t pos, const char* str)
    3. {
    4. assert(pos <= _size);
    5. size_t len = strlen(str);
    6. if ((_size + len) > _capacity)
    7. {
    8. reserve(_size + len);
    9. }
    10. size_t begin1 = _size + 1;
    11. size_t begin2 = begin1 + len;
    12. while (begin1 != pos)
    13. {
    14. _str[begin2-1] = _str[begin1-1];
    15. begin1--;
    16. begin2--;
    17. }
    18. strncpy(_str+begin1, str, len);
    19. }

    2.11push_back()

    尾插一个字符,可以复用insert

    1. void push_back(const char c)
    2. {
    3. insert(_size, c);
    4. }

    2.12 append()

    尾插一个字符串,同样复用insert

    1. void append(const char* str)
    2. {
    3. insert(_size, str);
    4. }

    2.13 operator+=()

    复用push_back和append

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

    2.14 迭代器

    string类中的迭代器实际上就是char指针的别名

    1. typedef char* interator;
    2. typedef const char* const_interator;
    3. interator begin()
    4. {
    5. return _str;
    6. }
    7. interator end()
    8. {
    9. return _str+_size;
    10. }

    当我们使用范围for进行遍历的时候,编译器会自动调用迭代器,所以这也要求我们命名必须规范,如果命名不规范就不能使用范围for进行遍历

    2.15 流提取和流插入运算符重载

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

  • 相关阅读:
    06_es分布式搜索引擎2
    Set和Map及哈希表介绍
    k8s 中 Pod 的控制器
    PMP_第9章章节试题
    APP应用开发sdk版本过低可能性原因问题排查及解决方案
    软件设计开发笔记3:基于QT的Modbus RTU主站
    【3DMAX】教你如何在中秋建模出一个好看的月亮(最详细3DMAX教程)
    Cyber RT 使用
    psycopg2.pool.PoolError: connection pool exhausted
    【操作系统】文件系统之文件共享与文件保护
  • 原文地址:https://blog.csdn.net/holle_world_ldx/article/details/126063923