• C++string类重要函数模拟实现


    为了和C++标准库区分,以下代码除主函数外均在namespace空间

    目录

    一.成员

    二、带参构造函数

    三、拷贝构造函数和赋值运算符重载

    四、析构函数

     五、重要成员函数实现

    1. c_str函数

    2. operator[]重载

    3. size函数和capacity函数

    4.reverse函数

    5. push_back和append函数

    6. operator+=重载

    7. insert函数

    8. erase函数 

    9. clear函数 

    10.resize函数 

    11.find函数 

    12.substr函数 

    六、迭代器

    七、全局函数

    1.operator判断符重载

    2.流输入和流输出运算符重载


    一.成员

    首先一定要有个字符指针 _str,指向所存放的字符串。

    还需要有字符串的大小_size,能够快速的知道这个字符串多大。

    再需要有字符串的容量_capacity,知道字符串追加后是否需要扩容。

    因此我们定义

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

    二、带参构造函数

    构造函数使用的是字符串来构造,并且给缺省值  ""  ,注意里面没有空格,为了能够进行默认构造,并且这是一个字符串,虽然没有给到什么内容,但会存放一个'\0'。

    _size和_capacity的大小就是str的长度。

    _str需要使用new来开辟空间,开辟空间的大小是_capacity+1,因为之前strlen(str)算出来的长度都没有算'\0',因此你需要多开辟一个空间来存放'\0';

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

    三、拷贝构造函数和赋值运算符重载

    为了实现深拷贝,就需要自己写拷贝构造和赋值运算符重载。

    这里的拷贝构造函数和赋值我们选用了很简单的写法,使用带参构造函数构造出一个临时的类对象tmp,这个tmp对象的值正好是当前对象需要的,因此将他们两个交换一下,当前对象就完成了深拷贝了,同时tmp也会出了作用域也会自动调用析构函数进行析构(析构函数马上就写)。

    赋值运算符重载也是利用的这种思想,甚至更加简单粗暴,不用为了节约而传 const string& s了,直接传string  tmp,这样会调用一次拷贝构造,我要的就是拷贝出来的临时对象跟我互换,换了之后返回*this 即可

    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. }
    15. string& operator=(string tmp)
    16. {
    17. swap(tmp);
    18. return *this;
    19. }

    四、析构函数

    析构函数非常简单,只需要将_str里面的内容delete掉即可,注意是需要delete[] ,顺便将_str置空, _size  和 _capacity赋值给0。

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

     五、重要成员函数实现

    1. c_str函数

    c_str()是将string类对象转化为字符串的格式,方便我们打印操作.

    代码只需返回_str即可。

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

    2. operator[]重载

    为了让string类对象像数组一样访问,我们需要重载 [] 

    代码也很简单。这里写了两个版本,一个普通版一个const版,这为了让普通对象调用可以修改,const类对象的调用不能修改

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

     3. size函数和capacity函数

    让类外访问到private成员_size和_capacity的值。

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

     4.reverse函数

    reverse是扩容,如果n比_capacity小就不管,比_capacity打就按照n的大小来扩容。

    流程为开辟空间,拷贝数据,释放空间,指向新空间,_capacity变为0

    1. void reverse(size_t n)
    2. {
    3. if (n > _capacity)
    4. {
    5. char* tmp = new char[n+1];//要多开一个留给'\0'
    6. strcpy(tmp, _str);
    7. if(_str!=nullptr)
    8. delete[] _str;
    9. _str = tmp;
    10. _capacity = n;
    11. }
    12. }

    5. push_back和append函数

    push_back是在尾部插入一个字符,插入之前判断空间是否足够,不足就扩容再插入,_size需要++,注意后面要置为'\0'。

    append是在尾部插入一个字符串,依然要判断是否扩容,使用了库函数来进行拷贝,strcpy会拷贝'\0',因此无需关心'\0',将_size+=len即可

    1. void push_back(char ch)
    2. {
    3. if (_size == _capacity)
    4. {
    5. reverse(_capacity == 0 ? 4 : 2 * _capacity);
    6. }
    7. _str[_size] = ch;
    8. _size++;
    9. _str[_size] = '\0';
    10. }
    11. void append(const char* str)
    12. {
    13. size_t len = strlen(str);
    14. if (_size + len > _capacity)
    15. {
    16. reverse(_size + len);
    17. }
    18. strcpy(_str + _size, str);
    19. _size += len;
    20. }

     6. operator+=重载

    使用+=比push_back和append爽的多,还不需要关心是字符还是字符串,因为我们运算符重载加函数重载了。

    代码部分分别调用即可。

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

     7. insert函数

    insert函数运用了重载,可以在你想要的位置插入字符和字符串。

    他的主要思想是从后往前遍历,并且将数据往后面挪动,直到到了pos位置才会停止,然后将要插入的字符或者字符串放在pos位置即可。

    只需要注意字符移动一格,字符串移动len格。

    1. void insert(size_t pos,char ch)
    2. {
    3. assert(pos <= _size && pos >= 0);
    4. if (_size == _capacity)
    5. {
    6. reverse(_capacity == 0 ? 4 : 2 * _capacity);
    7. }
    8. for(size_t i = _size + 1; i > pos; i--)
    9. {
    10. _str[i] = _str[i - 1];
    11. }
    12. _str[pos] = ch;
    13. _size++;
    14. }
    15. void insert(size_t pos, const char* str)
    16. {
    17. assert(pos <= _size && pos >= 0);
    18. size_t len = strlen(str);
    19. if (len+_size > _capacity)
    20. {
    21. reverse(len + _size);
    22. }
    23. for (size_t i = _size + len; i > pos; i--)
    24. {
    25. _str[i] = _str[i - len];
    26. }
    27. memcpy(_str + pos, str, len);
    28. _size += len;
    29. }

    8. erase函数 

    erase函数要分两种情况

    一种是len的长度要大于等于_size-pos,也就是说要将pos后面的内容全部删除,这种我们处理起来很简单,将pos位置直接置为'\0',同时_size = pos即可。

    另一种是后面还有字符需要保留,需要将后面的字符挪动到pos位置这里来,再_size -= len;

    这里参数缺省值npos是静态成员函数,类型为size_t,值为-1,代表int的最大值

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

     9. clear函数 

    clear函数简单,不需要处理_capacity,只需要第一个字符给到'\0',将_size给0。

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

    10.resize函数 

    resize也是两种情况

    n小于等于_size时,证明_size要减小,直接在n这个位置给到'\0',_size给0就好

    另一种情况就可能需要扩容了,我们直接暴力处理,不管你扩不扩容,先来个reverse(n),要扩容我就扩容,不需要我就返回继续执行后续代码,同时给后面的空间都赋值ch。不要忘记最后_size给0,还有最后给'\0'。

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

    11.find函数 

    find函数重载了,可以从某位位置往后找字符,或者字符串。

    找字符很简单,一个循环完事。

    找字符串用到了strstr()函数,不为空代表找到了,就返回找到的指针- _str指针,就能算出他们两个的差值,就是索引,指针为空就返回 npos。

    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. const char* p = strstr(_str + pos, str);
    15. if (p)
    16. {
    17. return p - _str;
    18. }
    19. return npos;
    20. }

    12.substr函数 

    substr是从pos位置,截取n个长度的字符串,返回类型为string

    如果n == pos||n>=_size-pos即代表pos后面的长度都要截取到,算出pos后面还有多少个字符,给s开辟好空间,遍历直接直接+=即可。

    另一种情况,就遍历到 pos + n,再进行+=

    1. string substr(size_t pos, size_t n = npos)
    2. {
    3. string s;
    4. if (n == npos || n >= _size - pos)
    5. {
    6. n = _size - pos;
    7. s.reverse(n);
    8. for (size_t i = pos; i < _size; i++)
    9. {
    10. s += _str[i];
    11. }
    12. }
    13. else
    14. {
    15. s.reverse(n);
    16. for (size_t i = pos; i < pos + n; i++)
    17. {
    18. s += _str[i];
    19. }
    20. }
    21. return s;
    22. }

    六、迭代器

    迭代器是STL的特性,string类虽然比STL要早一点,还后面还是支持了迭代器,只不过因为该有的功能都有了,迭代器没有那么重要,但是可以为我们后续学其他容器打好基础。

    string类的迭代器非常简单,就是一个指针,只需要写好begin() 和 end()就可以进行范围for的遍历了,指针本身就支持++和解引用,因此不需要重载。

    我们写了两个迭代器,普通迭代器可以修改,const 迭代器不能修改

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

    七、全局函数

    1.operator判断符重载

    调用strcmp,再加复用

    下面6种判断符重载,可以写在类里面,但string类为了支持宽字符等其他字符,写出了全局函数,这里只支持char类型,但还是写出了全局函数

    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 strcmp(s1.c_str(), s2.c_str()) == 0;
    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
    20. }
    21. bool operator!=(const string& s1, const string& s2)
    22. {
    23. return !(s1 == s2);
    24. }

     2.流输入和流输出运算符重载

    流输出非常简单,直接遍历即可。

    这里使用get()函数才可以获取到' '(空格)和'\n'(换行) 

    我们使用了临时数组来存储,目的是为了减少扩容,因为扩容的消耗很大。一个一个放到数组里面,等获取到' '(空格)和'\n'(换行) ,或者数据满了之后,再从数组里面将数据提取出来。

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

    到此,我们程序终于完成了,最后放上总代码 

    string.h

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #pragma once
    3. #include
    4. #include
    5. using namespace std;
    6. namespace kky
    7. {
    8. class string
    9. {
    10. public:
    11. typedef char* iterator;
    12. typedef const char* const_iterator;
    13. iterator begin()
    14. {
    15. return _str;
    16. }
    17. iterator end()
    18. {
    19. return _str + _size;
    20. }
    21. const_iterator begin() const
    22. {
    23. return _str;
    24. }
    25. const_iterator end() const
    26. {
    27. return _str + _size;
    28. }
    29. string(const char* str = "")
    30. :_size(strlen(str))
    31. ,_capacity(_size)
    32. {
    33. _str = new char[_capacity + 1];
    34. strcpy(_str, str);
    35. }
    36. //传统
    37. //string(const string& s)
    38. // :_size(s._size)
    39. // ,_capacity(s._capacity)
    40. //{
    41. // _str = new char[_capacity + 1];
    42. // strcpy(_str, s._str);
    43. //}
    44. //string& operator=(const string& s)
    45. //{
    46. // if (this != &s)
    47. // {
    48. // char* tmp = new char[s._capacity + 1];
    49. // strcpy(tmp, s._str);
    50. // delete[] _str;
    51. // _str = tmp;
    52. // _size = s._size;
    53. // _capacity = s._capacity;
    54. // }
    55. // return *this;
    56. //}
    57. void swap(string& s)
    58. {
    59. std::swap(_str, s._str);
    60. std::swap(_size, s._size);
    61. std::swap(_capacity, s._capacity);
    62. }
    63. string(const string& s)
    64. :_str(nullptr)
    65. ,_size(0)
    66. ,_capacity(0) //保证编译器一定初始化,析构才不会出错
    67. {
    68. string tmp(s._str);
    69. swap(tmp);
    70. }
    71. string& operator=(string tmp)
    72. {
    73. swap(tmp);
    74. return *this;
    75. }
    76. const char* c_str() const
    77. {
    78. return this->_str;
    79. }
    80. ~string()
    81. {
    82. delete[] _str;
    83. _str = nullptr;
    84. _size = _capacity = 0;
    85. }
    86. size_t size() const
    87. {
    88. return _size;
    89. }
    90. size_t capacity() const
    91. {
    92. return _capacity;
    93. }
    94. char& operator[](size_t pos)
    95. {
    96. assert(pos >= 0 && pos <= _size);
    97. return _str[pos];
    98. }
    99. const char& operator[](size_t pos) const
    100. {
    101. assert(pos >= 0&&pos<=_size);
    102. return _str[pos];
    103. }
    104. size_t find(char ch,size_t pos = 0)
    105. {
    106. for (size_t i = pos; i < _size; i++)
    107. {
    108. if (_str[i] == ch)
    109. {
    110. return i;
    111. }
    112. }
    113. return npos;
    114. }
    115. size_t find(const char* str, size_t pos = 0)
    116. {
    117. const char* p = strstr(_str + pos, str);
    118. if (p)
    119. {
    120. return p - _str;
    121. }
    122. return npos;
    123. }
    124. string substr(size_t pos, size_t n = npos)
    125. {
    126. string s;
    127. if (n == npos || n >= _size - pos)
    128. {
    129. n = _size - pos;
    130. s.reverse(n);
    131. for (size_t i = pos; i < _size; i++)
    132. {
    133. s += _str[i];
    134. }
    135. }
    136. else
    137. {
    138. s.reverse(n);
    139. for (size_t i = pos; i < pos + n; i++)
    140. {
    141. s += _str[i];
    142. }
    143. }
    144. return s;
    145. }
    146. void reverse(size_t n)
    147. {
    148. if (n > _capacity)
    149. {
    150. char* tmp = new char[n + 1];//要多开一个留给'\0'
    151. strcpy(tmp, _str);
    152. if (_str != nullptr)
    153. delete[] _str;
    154. _str = tmp;
    155. _capacity = n;
    156. }
    157. }
    158. void push_back(char ch)
    159. {
    160. if (_size == _capacity)
    161. {
    162. reverse(_capacity == 0 ? 4 : 2 * _capacity);
    163. }
    164. _str[_size] = ch;
    165. _size++;
    166. _str[_size] = '\0';
    167. }
    168. void append(const char* str)
    169. {
    170. size_t len = strlen(str);
    171. if (_size + len > _capacity)
    172. {
    173. reverse(_size + len);
    174. }
    175. strcpy(_str + _size, str);
    176. _size += len;
    177. }
    178. string& operator+=(char ch)
    179. {
    180. push_back(ch);
    181. return *this;
    182. }
    183. string& operator+=(const char* str)
    184. {
    185. append(str);
    186. return *this;
    187. }
    188. void insert(size_t pos,char ch)
    189. {
    190. assert(pos <= _size && pos >= 0);
    191. if (_size == _capacity)
    192. {
    193. reverse(_capacity == 0 ? 4 : 2 * _capacity);
    194. }
    195. for(size_t i = _size + 1; i > pos; i--)
    196. {
    197. _str[i] = _str[i - 1];
    198. }
    199. _str[pos] = ch;
    200. _size++;
    201. }
    202. void insert(size_t pos, const char* str)
    203. {
    204. assert(pos <= _size && pos >= 0);
    205. size_t len = strlen(str);
    206. if (len+_size > _capacity)
    207. {
    208. reverse(len + _size);
    209. }
    210. for (size_t i = _size + len; i > pos; i--)
    211. {
    212. _str[i] = _str[i - len];
    213. }
    214. memcpy(_str + pos, str, len);
    215. _size += len;
    216. }
    217. void erase(size_t pos,size_t len = npos)
    218. {
    219. assert(pos < _size && pos >= 0);
    220. if (len >= _size - pos)
    221. {
    222. _size = pos;
    223. _str[_size] = '\0';
    224. }
    225. else
    226. {
    227. for (size_t i = pos + len; i <= _size; i++)
    228. {
    229. _str[i - len] = _str[i];
    230. }
    231. _size -= len;
    232. }
    233. }
    234. void clear()
    235. {
    236. _str[0] = '\0';
    237. _size = 0;
    238. }
    239. void resize(size_t n,char ch = '\0')
    240. {
    241. if (n <= _size)
    242. {
    243. _str[n] = '\0';
    244. _size = n;
    245. }
    246. else
    247. {
    248. reverse(n);
    249. for (size_t i = _size; i < n; i++)
    250. {
    251. _str[i] = ch;
    252. }
    253. _size = n;
    254. _str[_size] = '\0';
    255. }
    256. }
    257. private:
    258. char* _str;
    259. size_t _size;
    260. size_t _capacity;
    261. //const static size_t npos = -1; //特例
    262. const static size_t npos;
    263. };
    264. const size_t string::npos = -1;
    265. ostream& operator<<(ostream& out, const string& s)
    266. {
    267. //out << s.c_str() << endl;
    268. for (auto e : s)
    269. {
    270. out << e;
    271. }
    272. return out;
    273. }
    274. istream& operator>>(istream& in,string& s)
    275. {
    276. s.clear();
    277. char tmp[129];
    278. size_t i = 0;
    279. char ch;
    280. ch = in.get();
    281. while (ch != ' ' && ch != '\n')
    282. {
    283. tmp[i++] = ch;
    284. if (i == 128)
    285. {
    286. tmp[i] = '\0';
    287. s += tmp;
    288. i = 0;
    289. }
    290. ch = in.get();
    291. }
    292. if (i != 0)
    293. {
    294. tmp[i] = '\0';
    295. s += tmp;
    296. }
    297. return in;
    298. }
    299. bool operator<(const string& s1, const string& s2)
    300. {
    301. return strcmp(s1.c_str(), s2.c_str()) < 0;
    302. }
    303. bool operator>(const string& s1, const string& s2)
    304. {
    305. return strcmp(s1.c_str(), s2.c_str()) > 0;
    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 !(s1>s2);
    314. }
    315. bool operator>=(const string& s1, const string& s2)
    316. {
    317. return !(s1
    318. }
    319. bool operator!=(const string& s1, const string& s2)
    320. {
    321. return !(s1 == s2);
    322. }
    323. void test01()
    324. {
    325. string s("123456");
    326. cout << s.c_str() << endl;
    327. s[0] = 'c';
    328. cout << s.c_str() << endl;
    329. /*for (int i = 0; i < s.size(); i++)
    330. {
    331. cout << s[i] << " ";
    332. }
    333. cout << endl;
    334. string::iterator it = s.begin();
    335. while (it != s.end())
    336. {
    337. cout << *it << " ";
    338. it++;
    339. }
    340. cout << endl;
    341. for (auto e : s)
    342. {
    343. cout << e << " ";
    344. }*/
    345. }
    346. void test02()
    347. {
    348. string s1("hello world");
    349. cout << s1.c_str() << endl;
    350. s1.push_back('s');
    351. s1.append("tring");
    352. cout << s1.c_str() << endl;
    353. s1 += 'w';
    354. s1 += "odema";
    355. cout << s1.c_str() << endl;
    356. }
    357. void test03()
    358. {
    359. string s1("hello world");
    360. cout << s1.c_str() << endl;
    361. s1.insert(5, 'c');
    362. cout << s1.c_str() << endl;
    363. s1.insert(s1.size(), 'c');
    364. cout << s1.c_str() << endl;
    365. s1.insert(0, 'c');
    366. cout << s1.c_str() << endl;
    367. s1.insert(5, "hhh");
    368. cout << s1.c_str() << endl;
    369. s1.erase(0, 3);
    370. cout << s1.c_str() << endl;
    371. s1.erase(s1.size()-1, 3);
    372. cout << s1.c_str() << endl;
    373. }
    374. void test04()
    375. {
    376. string s1("hello world");
    377. cout << s1.c_str() << endl;
    378. string s2("hello worldl");
    379. cout << (s1 != s2) << endl;;
    380. cout << s2 << endl;
    381. cin >> s1;
    382. cout << s1.c_str() << endl;
    383. }
    384. void test05()
    385. {
    386. string s1("hello world");
    387. cout << s1.c_str() << endl;
    388. string s2("hello worldl");
    389. cout << s2 << endl;
    390. s1.resize(5);
    391. cout << s1 << endl;
    392. s1.resize(15,'c');
    393. cout << s1 << endl;
    394. }
    395. void test06()
    396. {
    397. string s1("hello world");
    398. string s2(s1);
    399. cout << s1 << endl;
    400. cout << s2 << endl;
    401. string s3 = "wasg1";
    402. s3 = s1;
    403. cout << s3 << endl;
    404. }
    405. }

    test.cpp  (调用测试接口即可) 

    1. #include"string.h"
    2. int main()
    3. {
    4. kky::test06();
    5. }

  • 相关阅读:
    1.创建Django项目
    koa框架(二) mvc 模式及实现一个koa框架的web服务
    Typora设置代码块Mac风格三个圆点
    通过生成指标功能从非指标数据中分析趋势
    2022年,软件测试还能学吗?别学了,软件测试岗位饱和了...
    十大运动蓝牙耳机品牌排行榜,六款值得买的运动耳机推荐
    高并发编程:并发容器
    MFC ExtTextOut函数学习
    Excel的VLOOKUP函数的用法
    运营版uniapp多商户商城小程序+H5+APP+商家入驻短视频社区种草直播阶梯拼团
  • 原文地址:https://blog.csdn.net/kkbca/article/details/133886604