• C++ string类(包括深浅拷贝)


    目录

    一.字符码表

    一.为什么用string类

    二.使用标准库中的string类

    1.string类

    2.string中的常用接口说明

    (1)string类对象的常见构造

    (2)string类对象访问及遍历操作

    (3)string类对象遍历方式

    (4)string类对象的容量操作

    (5)string类对象的修改操作

    (6)string类非成员函数

    三.浅拷贝与深拷贝

    1.浅拷贝

    2.深拷贝

    四.string类的分部模拟实现(包括传统写法和现代写法)

    1.私有成员

    2.构造函数

    3.拷贝构造函数

    (1)传统写法

    (2)现代写法

    4.赋值运算符重载

    (1)传统写法

    (2)现代写法

    5.析构函数

    6.下标[]运算符重载

    7.有效字符个数和存储空间容量

    8.reserve函数

    9.resize函数

    10.尾插字符push_back函数

    11.尾插字符串append函数

    12.+=运算符重载

    13.插入函数

    14.删除函数

    15.查找函数

    16.清除函数

    17.C语言形式返回字符串

    18.迭代器

    19.>、<、==等运算符重载

    20.流插入、流提取运算符重载

    五.string类模拟实现总代码


    前言:从这里开始就正式进入STL了,STL对与C++是很重要的,接下来将会把STL的重要容器都学习一遍,并且也会包括STL的其它部分。 

    一.字符码表

            我们都知道ASCII码表,但是ASCII码表只有符号和英文字母,并没有中文汉字,或是其它语言的字符,因此就又有了其它的码表。

            例如:unicode(全世界语言) utf-8 utf-16 utf-32 gbk(国标)

    除此之外呢, 在我们搜索string时,会发现string有如下4个实例化,而这4个实例化的作用就是为了支持多种字符

    一.为什么用string类

            C语言中,字符串时以'\0'结尾的 一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,容易导致越界访问。

            因此有了string类的出现。

            在OJ题中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便。快捷。基本都使用string类,很少有人去使用C库中的字符串操作函数。

    二.使用标准库中的string类

    1.string类

    ①string是表示字符串的字符串类

    ②该类的接口与常规容器的接口基本相同,但添加了一些专门用来操作string的常规操作

    ③string在底层实际是:basic_string模板类的别名,typedef basic_string<char,  char_traits, allocator> string;

    ④不能操作多字节或者变长字符的序列。

    在使用string类时,必须包含#include <string>头文件。

    2.string中的常用接口说明

    (1)string类对象的常见构造

    ①string()        构造空的string类对象,即空字符串

    ②string(const char* s)        用C格式的string来构造string类对象

    ③string(size_t n, char c)        string类对象中包含n个字符c

    ④string(const string& s)        拷贝构造函数

    1. int main()
    2. {
    3. string s1; // 构造空的string类对象
    4. string s2("hello world"); // 用C格式的字符串构造
    5. string s3(s2); // 拷贝构造
    6. string s4 = s2; // 拷贝构造
    7. string s5("http://m.cplusplus.com/reference/string/string/string/", 4); // 用前4个字符构造
    8. cout << s1 << endl;
    9. cout << s2 << endl;
    10. cout << s3 << endl;
    11. cout << s4 << endl;
    12. cout << s5 << endl;
    13. return 0;
    14. }

    (2)string类对象访问及遍历操作

    ①operator[]        返回pos位置的字符,const string类对象调用

    ②iterator(begin + end )       begin获取一个字符的迭代器 +end获取最后一个字符下一个位置的迭代器

    ③reverse_iterator(rbegin + rend)       begin获取一个字符的迭代器 +end获取最后字符的下一个位置的迭代器

    ④范围for        C++11支持更简洁的范围for的新遍历方式    

    (3)string类对象遍历方式

    ①下标+[]

    1. for (size_t i = 0; i < s1.size(); i++)
    2. {
    3. cout << s1[i] << " ";
    4. }

    有一个与[]类似的at,使用方法相同,但有一点差别 

    1. s1.at(6); // 使用at看第6个元素
    2. s1[6] // 使用[]看第6个元素

    用[]失败了报断言错误,直接结束程序。

    而at相对温和,失败会抛异常。

    但是一般都是用operator[],几乎不会使用at。

    ②迭代器

    迭代器一共4种:iterator、reverse_iterator、const_iterator、const_reverse_iterator

    正向迭代器: 

    1. string s("hello world");
    2. string::iterator it = s.begin();
    3. while (it != s.end())
    4. {
    5. (*it) += 1;
    6. cout << *it << " ";
    7. ++it;
    8. }

    反向迭代器: 

    1. string::reverse_iterator rit = s.rbegin();
    2. while (rit != s.rend())
    3. {
    4. (*rit) += 1;
    5. cout << *rit << " ";
    6. ++rit;
    7. }

     这里我们可以发现正向迭代器和反向迭代器都可以对string类对象内部字符进行修改,因此迭代器还有两种:const_iteratorconst_reverse_iterator,这两种迭代器都只能遍历,而不能修改

    1. string::const_iterator it = rs.begin();
    2. while (it != rs.end())
    3. {
    4. //*it += 1; // 无法进行修改
    5. cout << *it << " ";
    6. ++it;
    7. }
    8. cout << endl;
    9. const_reverse_iterator rit = rs.rbegin(); // auto rit = rs.rbegin()
    10. while (rit != rs.rend())
    11. {
    12. //(*rit) -= 1; // 无法进行修改
    13. cout << *rit << " ";
    14. ++rit;
    15. }

    ③范围for(原理:替换成迭代器)

    1. for (auto ch : s1)
    2. {
    3. cout << ch << " ";
    4. }

    (4)string类对象的容量操作

    ①size        返回字符串有效字符长度

            length与size作用相同,只是length是最开始出现的,后面为了让string与其它STL容器差不多,便添加了size,但是length也不能删去

            max_size可以得到最大容量大小

    ②capacity        返回空间总大小

    ③empty        检测字符串是否为空串,是就返回true,不是就返回false

    ④clear        清空有效字符

    ⑤reverse        为字符串预留空间

    ⑥resize        将有效字符的个数改成n个,多余的空间用字符c填充

    1. int main()
    2. {
    3. string s("hello world");
    4. cout << s.length() << endl;
    5. cout << s.size() << endl;
    6. cout << s.max_size() << endl;
    7. cout << s.capacity() << endl;
    8. string s;
    9. //s.reserve(1000); // 扩空间
    10. s.resize(1000, 'x'); // 扩空间+初始化
    11. size_t sz = s.capacity(); // 得到当前容量大小
    12. cout << "making s grow:\n";
    13. cout << "capacity changed: " << sz << '\n';
    14. for (int i = 0; i < 1000; ++i)
    15. {
    16. s.push_back('c');
    17. if (sz != s.capacity())
    18. {
    19. sz = s.capacity(); // 容量满了
    20. cout << "capacity changed: " << sz << '\n';
    21. }
    22. }
    23. string s("hello");
    24. //s.reserve(100);
    25. s.resize(100, 'x');
    26. // vs下他们都不会缩容量
    27. s.reserve(10);
    28. s.resize(10);
    29. return 0;
    30. }

    (5)string类对象的修改操作

    ①push_back        在字符串后尾插字符

    ②append        在字符串后追加一个字符串

    ③operator+=        在字符串后追加字符串

    ④c str        返回C语言格式的字符串

    ⑤find + npos        (从前往后) 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

    ⑥rfind        (从后往前)

    ⑦substr        在str中从pos位置开始,截取n个字符,然后将其返回

    1. void test_string8()
    2. {
    3. string s;
    4. s.push_back('x');
    5. s.append("hello");
    6. string str("world");
    7. s.append(str);
    8. cout << s << endl;
    9. s += 'x';
    10. s += "hello";
    11. s += str;
    12. cout << s << endl;
    13. cout << str << endl;
    14. cout << str.c_str() << endl; // 以C语言的方式打印字符串
    15. }
    16. void test_string9()
    17. {
    18. string s1("hello world");
    19. string s2("string");
    20. // C++98
    21. s1.swap(s2); // 效率高
    22. swap(s1, s2); // 效率低
    23. cout << s1 << endl;
    24. cout << s1.c_str() << endl;
    25. // 要求取出文件的后缀
    26. //string file("string.cpp");
    27. //string file("string.c");
    28. string file("string.c.tar.zip");
    29. //size_t pos = file.find('.');
    30. size_t pos = file.rfind('.');
    31. if (pos != string::npos)
    32. {
    33. //string suffix = file.substr(pos, file.size() - pos);
    34. string suffix = file.substr(pos);
    35. cout << file << "后缀:" << suffix << endl;
    36. }
    37. else
    38. {
    39. cout << "没有后缀" << endl;
    40. }
    41. // 取出url中的域名
    42. string url1("http://www.cplusplus.com/reference/string/string/find/");
    43. string url2("https://leetcode.cn/problems/design-skiplist/solution/tiao-biao-probabilistic-alternative-to-b-0cd8/");
    44. string& url = url2;
    45. // 协议 域名 uri
    46. string protocol;
    47. size_t pos1 = url.find("://");
    48. if (pos1 != string::npos)
    49. {
    50. protocol = url.substr(0, pos1);
    51. cout << "protocol:" << protocol << endl;
    52. }
    53. else
    54. {
    55. cout << "非法url" << endl;
    56. }
    57. string domain;
    58. size_t pos2 = url.find('/', pos1 + 3);
    59. if (pos2 != string::npos)
    60. {
    61. domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
    62. cout << "domain:" << domain << endl;
    63. }
    64. else
    65. {
    66. cout << "非法url" << endl;
    67. }
    68. string uri = url.substr(pos2 + 1);
    69. cout << "uri:" << uri << endl;
    70. }

            在string尾部追加字符串,一般用+=比较多,+=既可以连接单个字符,还可以连接字符串。

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

    (6)string类非成员函数

    ①getline        获取一行字符串

    ②operator+        传值返回,导致深拷贝效率低,应尽量少用

    ③operator>>        输入运算符重载

    ④operator<<        输出运算符重载

    ⑤find + npos        (从前往后) 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

    三.浅拷贝与深拷贝

    1.浅拷贝

            浅拷贝也称位拷贝,编译器只是将对象中的值拷贝过来。如果在对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,尔此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进行操作时,就会发生了访问违规。要解决浅拷贝问题,就要用深拷贝

            如果没有显式定义构函数、拷贝构造函数、赋值运算符重载,那么编译器就会合成默认的,就是浅拷贝。浅拷贝会导致两个类对象s1、s2共用同一块内存空间,在释放时同一块空间被多次释放而引起程序崩溃。

    1. string(const char* str = "")
    2. : _size(strlen(str))
    3. , _capacity(_size)
    4. {
    5. // 浅拷贝
    6. _str = str;
    7. }

    2.深拷贝

            如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。给每个对象独立分配资源,保证多个对象之间不会因共享资源而造成多次释放造成程序崩溃问题。

    1. string(const char* str = "")
    2. : _size(strlen(str))
    3. , _capacity(_size)
    4. {
    5. // 深拷贝
    6. _str = new char[_capacity + 1];
    7. strcpy(_str, str);
    8. }

    四.string类的分部模拟实现(包括传统写法和现代写法)

    注意:模拟实现之前需要自己定义一个命名空间

    1. namespace hb
    2. {
    3. // ...
    4. }

    要在命名空间里面模拟实现 

    1.私有成员

    1. private:
    2. char* _str;
    3. size_t _size; // 有效字符个数
    4. size_t _capacity; // 实际存储有效字符的空间

    2.构造函数

             构造函数要采用深拷贝,防止因共用同一块内存空间,导致释放时多次释放而报错。

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

    3.拷贝构造函数

    (1)传统写法

            传统写法就如名字一样,很传统,该开空间就自己开空间,该拷贝数据就自己拷贝数据。同样是深拷贝。

    1. string(const string& s)
    2. : _size(s._size)
    3. , _capacity(s._capacity)
    4. {
    5. _str = new char[_capacity + 1];
    6. strcpy(_str, s._str);
    7. }

    (2)现代写法

            先实现一个swap为了方便所有的现代写法:

    1. void swap(string& s)
    2. {
    3. std::swap(_str, s._str);
    4. std::swap(_size, s._size);
    5. std::swap(_capacity, s._capacity);
    6. }

            这里为什么要自己实现一个swap,而不是用库里的swap函数呢?

            原因呢是库里的swap的函数在swap该string类成员时,每次都会进行深拷贝,效率很低。因此自己实现一个swap就可以避免这种损耗。

            现代写法是自己不想做,就让别人去做,这里就是定义一个tmp,然后让tmp去拷贝,之后再窃取tmp的成果之后,tmp自动析构。

    1. string(const string& s)
    2. : _str(nullptr)
    3. , _size(0)
    4. , _capacity(0)
    5. {
    6. string tmp(s._str);
    7. swap(tmp);
    8. }

    4.赋值运算符重载

    (1)传统写法

            在模拟实现赋值运算符重载时,要注意如果自己赋值自己的情况,通过一个if (this != &s)就是当自己赋值自己时,直接返回自己,可以避免报错。

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

    (2)现代写法

    现代写法有两种:

    ①第一种和传统写法比较相似

    1. string& operator=(const string& s)
    2. {
    3. if (this != &s)
    4. {
    5. string tmp(s._str);
    6. swap(tmp);
    7. }
    8. return *this;
    9. }

    ②第二种可以说是将现代写法发挥到了极致,在形参就开始拷贝,然后下面直接调用swap函数。

    1. string operator=(string s)
    2. {
    3. swap(s);
    4. return *this;
    5. }

    5.析构函数

    1. ~string()
    2. {
    3. if (_str)
    4. {
    5. delete[] _str;
    6. _str = nullptr;
    7. _size = _capacity = 0;
    8. }
    9. }

    6.下标[]运算符重载

            这里要实现两种,第一种是因为下标运算符要可以被修改,第二种是为了让const类型的string类对象可以使用下标运算符重载。

            这里的const放在函数后面是因为this指针自动隐藏,而无法加在this前,因此C++让本应该加在this前的const放在函数后面。

    实际相当于 const char& operator[](const string* this, size_t pos)

     

    1. char& operator[](size_t pos)
    2. {
    3. assert(pos < _size);
    4. return _str[pos];
    5. }
    6. const char& operator[](size_t pos) const
    7. {
    8. assert(pos < _size);
    9. return _str[pos];
    10. }

    7.有效字符个数和存储空间容量

            直接返回,加上const,让const类对象成员也可以调用。

    1. size_t size() const
    2. {
    3. return _size;
    4. }
    5. size_t capacity() const
    6. {
    7. return capacity();
    8. }

    8.reserve函数

            增容函数,实现时里面要创建一个临时变量,将临时变量变为新的容量,然后通过strcpy函数将_str转换为tmp,再delete掉tmp

    1. void reserve(size_t n)
    2. {
    3. if (n > _capacity)
    4. {
    5. char* tmp = new char[n + 1];
    6. strcpy(tmp, _str);
    7. delete _str;
    8. _str = tmp;
    9. _capacity = n;
    10. }
    11. }

    9.resize函数

            分情况讨论,想要resize的大小 < 当前有效字符个数时,可直接将n位置变为\0;当 n > 当前容量大小时,要先增容,然后再依次让n之后都变为传来的ch字符。

    1. void resize(size_t n, char ch = '\0')
    2. {
    3. if (n < _size)
    4. {
    5. _size = n;
    6. _str[_size] = '\0';
    7. }
    8. else
    9. {
    10. if (n > _capacity)
    11. {
    12. reserve(n);
    13. }
    14. for (size_t i = _size; i < n; ++i)
    15. {
    16. _str[i] = ch;
    17. }
    18. _size = n;
    19. _str[_size] = '\0';
    20. }
    21. }

    10.尾插字符push_back函数

            注意扩容

    1. void push_back(char ch)
    2. {
    3. if (_size == _capacity)
    4. {
    5. reserve(_capacity == 0 ? 4 : _capacity * 2);
    6. }
    7. _str[_size] = ch;
    8. ++_size;
    9. _str[_size] = '\0';
    10. }

    当insert字符函数实现之后,可直接复用:

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

    11.尾插字符串append函数

            注意扩容

    1. void append(const char* str)
    2. {
    3. size_t len = _size + strlen(str);
    4. if (len > _capacity)
    5. {
    6. reserve(len);
    7. }
    8. strcpy(_str + _size, str);
    9. _size = len;
    10. }

    当insert字符串函数实现后 ,可直接复用:

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

    12.+=运算符重载

            +=有两个,一个是+=字符的,另一个是+=字符串的,这里前面实现了puch_back和append函数,可以直接复用:

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

    13.插入函数

            插入有两个,一个是插入字符的,另一个是插入字符串的。

    这里插入函数的实现参考顺序表的插入函数实现。

            第一个:

            如果size_t end = _size时,下面写成end >= pos,_str[end + 1] = _str[end]时,会导致end变为-1,又因为end是size_t类型,就会变为一个很大的数4294967295导致报错。

            因此像我这种写法可以避免出现-1的情况。

    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. size_t end = _size + 1;
    9. while (end > pos)
    10. {
    11. _str[end] = _str[end - 1];
    12. --end;
    13. }
    14. _str[pos] = ch;
    15. _size++;
    16. return *this;
    17. }

            第二个:

            同上,需要注意size_t,这里用了strncpy去连接新插入的字符串。

    1. string& insert(size_t pos, const char* str)
    2. {
    3. assert(pos <= _size);
    4. size_t len = strlen(str);
    5. if (_size + len > _capacity)
    6. {
    7. reserve(_size + len);
    8. }
    9. size_t end = _size + len;
    10. while (end > pos + len - 1)
    11. {
    12. _str[end] = _str[end - len];
    13. --end;
    14. }
    15. strncpy(_str + pos, str, len);
    16. _size += len;
    17. return *this;
    18. }

    14.删除函数

            实现删除前,要先定义一个静态成员变量npos。

    1. private:
    2. char* _str;
    3. size_t _size; // 有效字符个数
    4. size_t _capacity; // 实际存储有效字符的空间
    5. const static size_t npos;
    6. };
    7. // 初始化
    8. const size_t string::npos = -1;

            npos是size_t类型的,定义其为-1,就相当于4294967295。

            删除也参考之前顺序表中的删除函数。

    1. void earse(size_t pos, size_t len = npos)
    2. {
    3. assert(pos < _size);
    4. if (len == npos || pos + len >= _size)
    5. {
    6. _str[pos] = '\0';
    7. _size = pos;
    8. }
    9. else
    10. {
    11. size_t begin = pos + len;
    12. while (begin <= _size)
    13. {
    14. _str[begin - len] = _str[begin];
    15. ++begin;
    16. }
    17. _size -= len;
    18. }
    19. }

    15.查找函数

            查找分为查找字符和字符串两种。

            第一种:

    1. size_t find(char ch, size_t pos = 0)
    2. {
    3. for (; pos < _size; ++pos)
    4. {
    5. if (_str[pos] == ch)
    6. {
    7. return pos;
    8. }
    9. }
    10. return npos;
    11. }

            第二种:

            可以直接用strstr去查找子字符串 。

    1. size_t find(const char* str, size_t pos = 0)
    2. {
    3. const char* p = strstr(_str + pos, str);
    4. if (p == nullptr)
    5. {
    6. return npos;
    7. }
    8. else
    9. {
    10. return p - _str;
    11. }
    12. }

    16.清除函数

    1. void clear()
    2. {
    3. _str[0] = '\0';
    4. _size = 0;
    5. }

    17.C语言形式返回字符串

            实现成const类型,const成员和非const成员都可以调用。

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

    18.迭代器

            要实现const类型和非const类型两种。

    1. typedef char* iterator;
    2. typedef const char* const_iterator;
    3. const_iterator begin() const
    4. {
    5. return _str;
    6. }
    7. const_iterator end() const
    8. {
    9. return _str + _size;
    10. }
    11. iterator begin()
    12. {
    13. return _str;
    14. }
    15. iterator end()
    16. {
    17. return _str + _size;
    18. }

    19.>、<、==等运算符重载

            这个要写在类外面。

    1. bool operator<(const string& s1, const string& s2)
    2. {
    3. return strcmp(s1.c_str(), s2.c_str()) < 0;
    4. }
    5. bool operator==(const string& s1, const string& s2)
    6. {
    7. return strcmp(s1.c_str(), s2.c_str()) == 0;
    8. }
    9. bool operator<=(const string& s1, const string& s2)
    10. {
    11. return s1 < s2 || s1 == s2;
    12. }
    13. bool operator>(const string& s1, const string& s2)
    14. {
    15. return !(s1 <= s2);
    16. }
    17. bool operator>=(const string& s1, const string& s2)
    18. {
    19. return !(s1 < s2);
    20. }
    21. bool operator!=(const string& s1, const string& s2)
    22. {
    23. return !(s1 == s2);
    24. }

    20.流插入、流提取运算符重载

    流插入:

            这里用范围for,而不是c_str是为了能识别出字符串中\0这种隐藏字符。

    1. ostream& operator<<(ostream& out, const string& s)
    2. {
    3. // 不能写成这种形式
    4. /*out << s.c_str();*/
    5. for (auto ch : s)
    6. {
    7. out << ch;
    8. }
    9. return out;
    10. }

    流提取:

    第一种:

            这里我们用ch = in.get()是为了在输入时,可以识别出 空格' ' 和 回车'\n' ,否则将无法结束输入。

    1. istream& operator>>(istream& in, string& s)
    2. {
    3. char ch;
    4. // 不能写成这种形式
    5. //in >> ch;
    6. ch = in.get();.
    7. while (ch != ' ' && ch != '\n')
    8. {
    9. s += ch;
    10. in >> ch;
    11. }
    12. return in;
    13. }

    第二种:

            这里的buff数组以及while循环是为了提高效率的。

            我们不知道输入的字符串是长还是短,当字符串比较短时,那么上面第一种方法是可以的。而当字符串很长时,每当输入一个字符就+=一次,会导致效率较低,因此这里是每满128个字符时,进行一次+=,可以提升一定的效率。

    1. istream& operator>>(istream& in, string& s)
    2. {
    3. char ch;
    4. ch = in.get();
    5. char buff[128] = { '\0' };
    6. size_t i = 0;
    7. while (ch != ' ' && ch != '\n')
    8. {
    9. buff[i++] = ch;
    10. if (i == 127)
    11. {
    12. s += buff;
    13. memset(buff, '\0', 128);
    14. i = 0;
    15. }
    16. ch = in.get();
    17. }
    18. s += buff;
    19. return in;
    20. }

    五.string类模拟实现总代码

    1. #pragma once
    2. #include <assert.h>
    3. namespace hb
    4. {
    5. class string
    6. {
    7. public:
    8. typedef char* iterator;
    9. typedef const char* const_iterator;
    10. const_iterator begin() const
    11. {
    12. return _str;
    13. }
    14. const_iterator end() const
    15. {
    16. return _str + _size;
    17. }
    18. iterator begin()
    19. {
    20. return _str;
    21. }
    22. iterator end()
    23. {
    24. return _str + _size;
    25. }
    26. string(const char* str = "")
    27. : _size(strlen(str))
    28. , _capacity(_size)
    29. {
    30. _str = new char[_capacity + 1];
    31. strcpy(_str, str);
    32. }
    33. // 传统写法:
    34. /*string(const string& s)
    35. : _size(s._size)
    36. , _capacity(s._capacity)
    37. {
    38. _str = new char[_capacity + 1];
    39. strcpy(_str, s._str);
    40. }
    41. string& operator=(const string& s)
    42. {
    43. if (this != &s)
    44. {
    45. char* tmp = new char[s._capacity + 1];
    46. strcpy(tmp, s._str);
    47. delete[] _str;
    48. _str = tmp;
    49. _size = s._size;
    50. _capacity = s._capacity;
    51. }
    52. return *this;
    53. }*/
    54. void swap(string& s)
    55. {
    56. std::swap(_str, s._str);
    57. std::swap(_size, s._size);
    58. std::swap(_capacity, s._capacity);
    59. }
    60. // 现代写法:
    61. string(const string& s)
    62. : _str(nullptr)
    63. , _size(0)
    64. , _capacity(0)
    65. {
    66. string tmp(s._str);
    67. swap(tmp);
    68. }
    69. // 1.
    70. /*string& operator=(const string& s)
    71. {
    72. if (this != &s)
    73. {
    74. string tmp(s._str);
    75. swap(tmp);
    76. }
    77. return *this;
    78. }*/
    79. // 2.
    80. string operator=(string s)
    81. {
    82. swap(s);
    83. return *this;
    84. }
    85. ~string()
    86. {
    87. if (_str)
    88. {
    89. delete[] _str;
    90. _str = nullptr;
    91. _size = _capacity = 0;
    92. }
    93. }
    94. const char* c_str() const
    95. {
    96. return _str;
    97. }
    98. char& operator[](size_t pos)
    99. {
    100. assert(pos < _size);
    101. return _str[pos];
    102. }
    103. const char& operator[](size_t pos) const
    104. {
    105. assert(pos < _size);
    106. return _str[pos];
    107. }
    108. size_t size() const
    109. {
    110. return _size;
    111. }
    112. size_t capacity() const
    113. {
    114. return capacity();
    115. }
    116. string& operator+=(const char* str)
    117. {
    118. append(str);
    119. return *this;
    120. }
    121. string& operator+=(char ch)
    122. {
    123. push_back(ch);
    124. return *this;
    125. }
    126. void reserve(size_t n)
    127. {
    128. if (n > _capacity)
    129. {
    130. char* tmp = new char[n + 1];
    131. strcpy(tmp, _str);
    132. delete _str;
    133. _str = tmp;
    134. _capacity = n;
    135. }
    136. }
    137. void resize(size_t n, char ch = '\0')
    138. {
    139. if (n < _size)
    140. {
    141. _size = n;
    142. _str[_size] = '\0';
    143. }
    144. else
    145. {
    146. if (n > _capacity)
    147. {
    148. reserve(n);
    149. }
    150. for (size_t i = _size; i < n; ++i)
    151. {
    152. _str[i] = ch;
    153. }
    154. _size = n;
    155. _str[_size] = '\0';
    156. }
    157. }
    158. void push_back(char ch)
    159. {
    160. /*if (_size == _capacity)
    161. {
    162. reserve(_capacity == 0 ? 4 : _capacity * 2);
    163. }
    164. _str[_size] = ch;
    165. ++_size;
    166. _str[_size] = '\0';*/
    167. insert(_size, ch);
    168. }
    169. void append(const char* str)
    170. {
    171. /*size_t len = _size + strlen(str);
    172. if (len > _capacity)
    173. {
    174. reserve(len);
    175. }
    176. strcpy(_str + _size, str);
    177. _size = len;*/
    178. insert(_size, str);
    179. }
    180. string& insert(size_t pos, char ch)
    181. {
    182. assert(pos <= _size);
    183. if (_size == _capacity)
    184. {
    185. reserve(_capacity == 0 ? 4 : _capacity * 2);
    186. }
    187. size_t end = _size + 1;
    188. while (end > pos)
    189. {
    190. _str[end] = _str[end - 1];
    191. --end;
    192. }
    193. _str[pos] = ch;
    194. _size++;
    195. return *this;
    196. }
    197. string& insert(size_t pos, const char* str)
    198. {
    199. assert(pos <= _size);
    200. size_t len = strlen(str);
    201. if (_size + len > _capacity)
    202. {
    203. reserve(_size + len);
    204. }
    205. size_t end = _size + len;
    206. while (end > pos + len - 1)
    207. {
    208. _str[end] = _str[end - len];
    209. --end;
    210. }
    211. strncpy(_str + pos, str, len);
    212. _size += len;
    213. return *this;
    214. }
    215. void earse(size_t pos, size_t len = npos)
    216. {
    217. assert(pos < _size);
    218. if (len == npos || pos + len >= _size)
    219. {
    220. _str[pos] = '\0';
    221. _size = pos;
    222. }
    223. else
    224. {
    225. size_t begin = pos + len;
    226. while (begin <= _size)
    227. {
    228. _str[begin - len] = _str[begin];
    229. ++begin;
    230. }
    231. _size -= len;
    232. }
    233. }
    234. size_t find(char ch, size_t pos = 0)
    235. {
    236. for (; pos < _size; ++pos)
    237. {
    238. if (_str[pos] == ch)
    239. {
    240. return pos;
    241. }
    242. }
    243. return npos;
    244. }
    245. size_t find(const char* str, size_t pos = 0)
    246. {
    247. const char* p = strstr(_str + pos, str);
    248. if (p == nullptr)
    249. {
    250. return npos;
    251. }
    252. else
    253. {
    254. return p - _str;
    255. }
    256. }
    257. void clear()
    258. {
    259. _str[0] = '\0';
    260. _size = 0;
    261. }
    262. private:
    263. char* _str;
    264. size_t _size; // 有效字符个数
    265. size_t _capacity; // 实际存储有效字符的空间
    266. const static size_t npos;
    267. };
    268. const size_t string::npos = -1;
    269. ostream& operator<<(ostream& out, const string& s)
    270. {
    271. /*out << s.c_str();*/
    272. for (auto ch : s)
    273. {
    274. out << ch;
    275. }
    276. return out;
    277. }
    278. istream& operator>>(istream& in, string& s)
    279. {
    280. //char ch;
    281. in >> ch;
    282. //ch = in.get();.
    283. //while (ch != ' ' && ch != '\n')
    284. //{
    285. // s += ch;
    286. // in >> ch;
    287. //}
    288. //return in;
    289. char ch;
    290. ch = in.get();
    291. char buff[128] = { '\0' };
    292. size_t i = 0;
    293. while (ch != ' ' && ch != '\n')
    294. {
    295. buff[i++] = ch;
    296. if (i == 127)
    297. {
    298. s += buff;
    299. memset(buff, '\0', 128);
    300. i = 0;
    301. }
    302. ch = in.get();
    303. }
    304. s += buff;
    305. return in;
    306. }
    307. bool operator<(const string& s1, const string& s2)
    308. {
    309. return strcmp(s1.c_str(), s2.c_str()) < 0;
    310. }
    311. bool operator==(const string& s1, const string& s2)
    312. {
    313. return strcmp(s1.c_str(), s2.c_str()) == 0;
    314. }
    315. bool operator<=(const string& s1, const string& s2)
    316. {
    317. return s1 < s2 || s1 == s2;
    318. }
    319. bool operator>(const string& s1, const string& s2)
    320. {
    321. return !(s1 <= s2);
    322. }
    323. bool operator>=(const string& s1, const string& s2)
    324. {
    325. return !(s1 < s2);
    326. }
    327. bool operator!=(const string& s1, const string& s2)
    328. {
    329. return !(s1 == s2);
    330. }
    331. }
  • 相关阅读:
    (二)《数字电子技术基础》——数制
    【LeetCode-简单】169. 多数元素(详解)
    数学建模-BP神经网络模型
    聊天机器人框架Rasa资源整理
    PHP生成二维码带图标代码实例
    文档参考888
    Python语言:字典的使用
    概率分析介绍
    AWS认证SAA-C03每日一题
    【React 报错】—Remove untracked files, stash or commit any changes, and try again.
  • 原文地址:https://blog.csdn.net/qq_60750110/article/details/125131075