• 【C++学习手札】模拟实现string


                                                         🎬慕斯主页修仙—别有洞天

                                                           ♈️今日夜电波缶ビール—みゆな

                                                                    0:41━━━━━━️💟──────── 2:52
                                                                        🔄   ◀️   ⏸   ▶️    ☰ 

                                          💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


    目录

    一、string实际的底层原理

    二、string的模拟实现

            基本成员函数 

            构造函数 

            拷贝构造函数

            析构函数

            重载赋值运算符 

            迭代器

            迭代器的概念 

            begin()

            end()

            空间管理

            修改相关

            push_back()

            append ()​编辑

            重载+=运算符

            c_str ()

            clear()

            swap()

             重载[ ](最爱的运算符!!!)

             关系运算符的重载

             基本操作

            查找find() 

            前置知识substr()

            插入insert()

            删除 erase()

            流插入流提取

     总体代码


    一、string实际的底层原理

            string类型底层实现通常使用动态数组,因为它允许在运行时动态分配内存空间。这使得字符串的长度可以根据需要进行调整,而不需要提前分配固定大小的空间,并且可以避免缓冲区溢出和内存泄漏等问题。 string类型还提供了各种字符串操作函数,例如拼接、查找、替换、大小写转换等。

        底层实际的储存结构:

    1. class string
    2. {
    3. private:
    4. char* _str;
    5. size_t _capacity;
    6. size_t _size;
    7. };

    二、string的模拟实现

            基本成员函数 

            构造函数 

            通过设置缺省参数以及初始化列表来默认构造string对象

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

            在模拟实现string的构造函数时,很多人包括作者我一开始都会想着将他分为有参以及无参两部分,但是这种做法是会产生一定的问题的,如下为作者写出的错误的代码:

    1. class string
    2. {
    3. public:
    4. string()//无参构造函数
    5. //初始化列表
    6. :_str(nullptr)
    7. , _size(0)
    8. , _capacity(0)
    9. {
    10. }
    11. string(char* str)//带参构造函数->char a[] = "abc";string kk(a)
    12. :_str(str),
    13. _size(strlen(str)),
    14. _capacity(strlen(str))
    15. {
    16. }
    17. string(const char* str)//带参构造函数->string kk("abc")
    18. :_str(str),
    19. _size(strlen(str)),
    20. _capacity(strlen(str))
    21. {
    22. }
    23. private:
    24. char* _str;
    25. size_t _capacity;
    26. size_t _size;
    27. };

            对于以上代码,若调用如上的带参构造函数中string(const char* str)就会报错,因为str传给_str属于权限放大,现在有两种解决方法,一种是将它改为 string(char* str),但是这样就不能直接传递字符串,需要先生成字符串,还有一种方法是将_str改为const char*类型,但是此时就不能修改_str指向的内容,后续的一些操作会出现一些错误

             拷贝构造函数

            由于我们使用了开辟了空间来用于储存数据,因此我们需要进行深拷贝,防止对于另外一个对象的影响。

            传统写法: 

            开辟对应的空间,然后再给相应的值。

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

            较为推荐的写法:

            通过让构造函数帮我们创建一个构造相应的字符的对象,然后我们再交换两个对象的数据即可得。

    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. }
    7. string(const string& s)
    8. :_str(nullptr)
    9. ,_size(0)
    10. ,_capacity(0)
    11. {
    12. string tmp(s._str);
    13. swap(tmp);
    14. }

            析构函数

            避免内存泄漏需要自己写一个析构函数用以销毁new在堆区所创建的空间 

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

            重载赋值运算符 

             同上面的拷贝构造函数一样,我们对此也是需要深拷贝的

            传统写法: 

            开辟对应的空间,然后再给相应的值。

    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. //}

            较为推荐的写法:

            通过让构造函数帮我们创建一个构造相应的字符的对象,然后我们再交换两个对象的数据即可得。

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

            再简化一点:通过在传值时进行拷贝构造,然后交换数据。

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

             迭代器

            迭代器的概念 

             C++迭代器是一种用于遍历容器中元素的对象,它提供了一种统一的接口,使得不同类型的容器都可以使用相同的方式进行遍历。迭代器可以指向容器中的某个元素,并用于访问该元素的值或者进行修改。迭代器通常是一个类,它重载了指针操作符(*和->),以及自增(++)和自减(--)运算符,使得迭代器可以像指针一样进行移动。

            string的迭代器实际实现原理:

            string的迭代器实际上是一个指向string底层存储空间中char类型数据的指针,不同的迭代器类型标志着不同的访问权限。例如,一个string::iterator对象可以用来读写字符,而一个const string::iterator对象只能用来读取字符。

            于是我们定义出以下的迭代器:

    1. typedef char* iterator;
    2. typedef const char* const_iterator;

             begin()

            用于返回第一个字符的地址。

    1. iterator begin()
    2. {
    3. return _str;
    4. }
    5. const_iterator begin()const
    6. {
    7. return _str;
    8. }

          end()

            用于返回后一个字符的后一个字符的地址的地址。其实就是‘\0’的地址

    1. iterator begin()
    2. {
    3. return _str;
    4. }
    5. const_iterator begin()const
    6. {
    7. return _str;
    8. }

            空间管理

    1. size_t size()const//大小
    2. {
    3. return _size;
    4. }
    5. size_t capacity()const//空间
    6. {
    7. return _capacity;
    8. }
    9. bool empty()const//空?
    10. {
    11. return _size == 0;
    12. }
    13. void reserve(size_t n)//扩容
    14. {
    15. if (n > _capacity)
    16. {
    17. char* tmp = new char[n + 1];
    18. strcpy(tmp, _str);
    19. delete[]_str;
    20. _str = tmp;
    21. _capacity = n;
    22. }
    23. }
    24. void resize(size_t n, char c = '\0')//重设大小
    25. {
    26. if (n <= _size)
    27. {
    28. _str[n] = '\0';
    29. _size = n;
    30. }
    31. else
    32. {
    33. reserve(n);
    34. while (_size < n)
    35. {
    36. _str[_size] = c ;
    37. ++_size;
    38. }
    39. _str[_size] = '\0';
    40. }
    41. }

             修改相关

             push_back()

             根据原型可知此函数是在当前字符串尾插一个字符,其中需要额外注意容量。

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

            append ()

             根据原型可知此函数有很多的函数重载,但是我们最常用的就是尾插一段字符串,对此就不过多进行阐述。也需要额外注意容量。

    1. void append(const char* str)
    2. {
    3. size_t len = strlen(str);
    4. if (_size + len > _capacity)
    5. {
    6. reserve(_size + len);
    7. }
    8. strcpy(_str + _size, str);//拷贝到原字符串后
    9. _size += len;
    10. }

             重载+=运算符

             此处复用了以上的push_back和append函数,这也是根据C++中函数管用的复用习惯来做出的选择。

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

            c_str ()

            返回指向数组的指针,该数组包含以 null 结尾的字符序列(即 C 字符串),该字符表示字符串对象的当前值。简而言之就是将string对象转换为C语言风格类型的字符串。

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

            clear()

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

            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. }

             重载[ ](最爱的运算符!!!)

            这里其实就是按照数组的相关操作即可,但是要注意越界的判断!

    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. }

             关系运算符的重载

            关系运算符的重载的关键就是根据所需进行相应的数据变化以及做出值的返回。 

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

             基本操作

            查找find() 

            由原型知,分为按照字符查找以及字符串查找还有常量字符串查找,对此,此次实现字符以及字符串查找。

            对于字符查找: 

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

            对于字符串查找: 

            前置知识substr()

             他的主要作用就是在str中从pos位置开始,截取n个字符,然后将其返回。其中的npos实际上是一个全局的静态变量,实际的赋值为-1,都是由于类型为size_t,因此实际上为最大值!

    1. string substr(size_t pos, size_t len = npos)
    2. {
    3. string s;
    4. size_t end = pos + len;
    5. if (len == npos || pos + len >= _size) // 有多少取多少
    6. {
    7. len = _size - pos;
    8. end = _size;
    9. }
    10. s.reserve(len);
    11. for (size_t i = pos; i < end; i++)
    12. {
    13. s += _str[i];
    14. }
    15. return s;
    16. }
    17. // 返回子串s在string中第一次出现的位置
    18. size_t find(const char* s, size_t pos = 0) const
    19. {
    20. const char* p = strstr(_str + pos, s);
    21. if (p)
    22. {
    23. return p - _str;
    24. }
    25. else
    26. {
    27. return npos;
    28. }
    29. }

             插入insert()

            根据所插入的空间大小挪字符

    1. void 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. }
    17. void insert(size_t pos, const char* str)
    18. {
    19. assert(pos <= _size);
    20. size_t len = strlen(str);
    21. if (_size + len > _capacity)
    22. {
    23. reserve(_size + len);
    24. }
    25. // 挪动数据
    26. int end = _size;
    27. while (end >= (int)pos)
    28. {
    29. _str[end + len] = _str[end];
    30. --end;
    31. }
    32. strncpy(_str + pos, str, len);
    33. _size += len;
    34. }

            删除 erase()

             删除指定位置的字符,可根据调节删除的数量,默认是指定位置以后全删。

    1. void erase(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. }

            流插入流提取

             重载<<和>>是为了能够像内置一样调用,输入打印。

    1. ostream& operator<<(ostream& out, const string& s)
    2. {
    3. /*for (size_t i = 0; i < s.size(); i++)
    4. {
    5. out << s[i];
    6. }*/
    7. for (auto ch : s)
    8. out << ch;
    9. return out;
    10. }
    11. istream& operator>>(istream& in, string& s)
    12. {
    13. s.clear();
    14. char buff[129];
    15. size_t i = 0;
    16. char ch;
    17. ch = in.get();
    18. while (ch != ' ' && ch != '\n')
    19. {
    20. buff[i++] = ch;
    21. if (i == 128)
    22. {
    23. buff[i] = '\0';
    24. s += buff;
    25. i = 0;
    26. }
    27. //s += ch;
    28. ch = in.get();
    29. }
    30. if (i != 0)
    31. {
    32. buff[i] = '\0';
    33. s += buff;
    34. }
    35. return in;
    36. }

     总体代码

    1. #pragma once
    2. #define _CRT_SECURE_NO_WARNINGS 01
    3. #include
    4. #include
    5. using namespace std;
    6. namespace lt
    7. {
    8. class string
    9. {
    10. public:
    11. typedef char* iterator;
    12. typedef const char* const_iterator;
    13. friend ostream& operator<<(ostream& out, const string& s);
    14. friend istream& operator>>(istream& in, string& s);
    15. iterator begin()
    16. {
    17. return _str;
    18. }
    19. iterator end()
    20. {
    21. return _str + _size;
    22. }
    23. const_iterator begin() const
    24. {
    25. return _str;
    26. }
    27. const_iterator end() const
    28. {
    29. return _str + _size;
    30. }
    31. /*string()
    32. :_str(new char[1]{'\0'})
    33. ,_size(0)
    34. ,_capacity(0)
    35. {}*/
    36. string(const char* str = "")
    37. :_size(strlen(str))
    38. , _capacity(_size)
    39. {
    40. _str = new char[_capacity + 1];
    41. strcpy(_str, str);
    42. }
    43. // 传统写法
    44. // s2(s1)
    45. //string(const string& s)
    46. //{
    47. // _str = new char[s._capacity+1];
    48. // strcpy(_str, s._str);
    49. // _size = s._size;
    50. // _capacity = s._capacity;
    51. //}
    52. s2 = s3
    53. //string& operator=(const string& s)
    54. //{
    55. // if (this != &s)
    56. // {
    57. // char* tmp = new char[s._capacity + 1];
    58. // strcpy(tmp, s._str);
    59. // delete[] _str;
    60. // _str = tmp;
    61. // _size = s._size;
    62. // _capacity = s._capacity;
    63. // }
    64. // return *this;
    65. //}
    66. void swap(string& s)
    67. {
    68. std::swap(_str, s._str);
    69. std::swap(_size, s._size);
    70. std::swap(_capacity, s._capacity);
    71. }
    72. // s2(s1)
    73. string(const string& s)
    74. :_str(nullptr)
    75. , _size(0)
    76. , _capacity(0)
    77. {
    78. string tmp(s._str);
    79. swap(tmp);
    80. }
    81. // s2 = s3
    82. //string& operator=(const string& s)
    83. //{
    84. // if (this != &s)
    85. // {
    86. // string tmp(s);
    87. // //this->swap(tmp);
    88. // swap(tmp);
    89. // }
    90. // return *this;
    91. //}
    92. // s2 = s3
    93. string& operator=(string tmp)
    94. {
    95. swap(tmp);
    96. return *this;
    97. }
    98. ~string()
    99. {
    100. delete[] _str;
    101. _str = nullptr;
    102. _size = _capacity = 0;
    103. }
    104. char& operator[](size_t pos)
    105. {
    106. assert(pos < _size);
    107. return _str[pos];
    108. }
    109. const char& operator[](size_t pos) const
    110. {
    111. assert(pos < _size);
    112. return _str[pos];
    113. }
    114. size_t capacity() const
    115. {
    116. return _capacity;
    117. }
    118. size_t size() const
    119. {
    120. return _size;
    121. }
    122. const char* c_str() const
    123. {
    124. return _str;
    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. _str[n] = '\0';
    142. _size = n;
    143. }
    144. else
    145. {
    146. reserve(n);
    147. while (_size < n)
    148. {
    149. _str[_size] = ch;
    150. ++_size;
    151. }
    152. _str[_size] = '\0';
    153. }
    154. }
    155. size_t find(char ch, size_t pos = 0)
    156. {
    157. for (size_t i = pos; i < _size; i++)
    158. {
    159. if (_str[i] == ch)
    160. {
    161. return i;
    162. }
    163. }
    164. return npos;
    165. }
    166. size_t find(const char* sub, size_t pos = 0)
    167. {
    168. const char* p = strstr(_str + pos, sub);
    169. if (p)
    170. {
    171. return p - _str;
    172. }
    173. else
    174. {
    175. return npos;
    176. }
    177. }
    178. string substr(size_t pos, size_t len = npos)
    179. {
    180. string s;
    181. size_t end = pos + len;
    182. if (len == npos || pos + len >= _size) // 有多少取多少
    183. {
    184. len = _size - pos;
    185. end = _size;
    186. }
    187. s.reserve(len);
    188. for (size_t i = pos; i < end; i++)
    189. {
    190. s += _str[i];
    191. }
    192. return s;
    193. }
    194. void push_back(char ch)
    195. {
    196. if (_size == _capacity)
    197. {
    198. reserve(_capacity == 0 ? 4 : _capacity * 2);
    199. }
    200. _str[_size] = ch;
    201. ++_size;
    202. _str[_size] = '\0';
    203. }
    204. void append(const char* str)
    205. {
    206. size_t len = strlen(str);
    207. if (_size + len > _capacity)
    208. {
    209. reserve(_size + len);
    210. }
    211. strcpy(_str + _size, str);
    212. _size += len;
    213. }
    214. string& operator+=(char ch)
    215. {
    216. push_back(ch);
    217. return *this;
    218. }
    219. string& operator+=(const char* str)
    220. {
    221. append(str);
    222. return *this;
    223. }
    224. void insert(size_t pos, char ch)
    225. {
    226. assert(pos <= _size);
    227. if (_size == _capacity)
    228. {
    229. reserve(_capacity == 0 ? 4 : _capacity * 2);
    230. }
    231. size_t end = _size + 1;
    232. while (end > pos)
    233. {
    234. _str[end] = _str[end - 1];
    235. --end;
    236. }
    237. _str[pos] = ch;
    238. _size++;
    239. }
    240. void insert(size_t pos, const char* str)
    241. {
    242. assert(pos <= _size);
    243. size_t len = strlen(str);
    244. if (_size + len > _capacity)
    245. {
    246. reserve(_size + len);
    247. }
    248. // 挪动数据
    249. int end = _size;
    250. while (end >= (int)pos)
    251. {
    252. _str[end + len] = _str[end];
    253. --end;
    254. }
    255. strncpy(_str + pos, str, len);
    256. _size += len;
    257. }
    258. void erase(size_t pos, size_t len = npos)
    259. {
    260. assert(pos < _size);
    261. if (len == npos || pos + len >= _size)
    262. {
    263. _str[pos] = '\0';
    264. _size = pos;
    265. }
    266. else
    267. {
    268. size_t begin = pos + len;
    269. while (begin <= _size)
    270. {
    271. _str[begin - len] = _str[begin];
    272. ++begin;
    273. }
    274. _size -= len;
    275. }
    276. }
    277. bool operator<(const string& s) const
    278. {
    279. return strcmp(_str, s._str) < 0;
    280. }
    281. bool operator==(const string& s) const
    282. {
    283. return strcmp(_str, s._str) == 0;
    284. }
    285. bool operator<=(const string& s) const
    286. {
    287. return *this < s || *this == s;
    288. }
    289. bool operator>(const string& s) const
    290. {
    291. return !(*this <= s);
    292. }
    293. bool operator>=(const string& s) const
    294. {
    295. return !(*this < s);
    296. }
    297. bool operator!=(const string& s) const
    298. {
    299. return !(*this == s);
    300. }
    301. void clear()
    302. {
    303. _str[0] = '\0';
    304. _size = 0;
    305. }
    306. private:
    307. char* _str;
    308. size_t _size;
    309. size_t _capacity;
    310. //const static size_t npos = -1; // 特例
    311. //const static double npos = 1.1; // 不支持
    312. public:
    313. const static size_t npos;
    314. };
    315. const size_t string::npos = -1;
    316. ostream& operator<<(ostream& out, const string& s)
    317. {
    318. /*for (size_t i = 0; i < s.size(); i++)
    319. {
    320. out << s[i];
    321. }*/
    322. for (auto ch : s)
    323. out << ch;
    324. return out;
    325. }
    326. istream& operator>>(istream& in, string& s)
    327. {
    328. s.clear();
    329. //s.reserve(128);
    330. char buff[129];
    331. size_t i = 0;
    332. char ch;
    333. ch = in.get();
    334. while (ch != ' ' && ch != '\n')
    335. {
    336. buff[i++] = ch;
    337. if (i == 128)
    338. {
    339. buff[i] = '\0';
    340. s += buff;
    341. i = 0;
    342. }
    343. //s += ch;
    344. ch = in.get();
    345. }
    346. if (i != 0)
    347. {
    348. buff[i] = '\0';
    349. s += buff;
    350. }
    351. return in;
    352. }
    353. }

                          感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                           

                                                                             给个三连再走嘛~  

  • 相关阅读:
    从“pmp::indextype“转换到“const storageindex &“需要收缩转换
    Flask 学习-13.Flask-SQLAlchemy 新建模型和字段
    Paddle Graph Learning (PGL)图学习之图游走类模型[系列四]
    【DevOps系列】Docker数据卷(volume)详解
    电商API接口汇总,引领企业国际化
    【原创】基于SSM的体育场地预约管理系统(毕业设计源码)
    Docker快速入门到项目部署,docker自定义镜像
    MySQL - 普通索引
    【LeetCode刷题(数据结构与算法)】:将二叉搜索树转化为排序的双向链表
    BI系统的分布式部署原理和技术实现
  • 原文地址:https://blog.csdn.net/weixin_64038246/article/details/134422860