• C++ 【模板和string模拟实现】


    string模拟实现

    构造函数+析构函数

    拷贝构造函数:深拷贝

    赋值运算符拷贝

    resize和reserve

    push_back && append

    insert

    erase

    << && >>

    迭代器

    迭代器模拟实现

    c_str(cpp中使用了string,要和c语言兼容,用c去读文件等)

    find和substr

    getline练习题

    运算符重载

    to_string:从字符串转换-其他转字符串函数


    string模拟实现

    构造函数+析构函数

    注意:1.空对象初始化时开一个空间:\0

    2.写全缺省,隐含了一个\0,只要是常量字符串,后面都已\0为结尾

    1. /*string() //无参构造函数
    2. :_str(new char[1])
    3. ,_size(0)
    4. ,_capacity(0)
    5. {
    6. _str[0] = '\0';
    7. }*/
    8. //string(const char* str = "")//strlen时间复杂度O(N)
    9. // :_str(new char[strlen(str)+1])
    10. // ,_size(strlen(str))
    11. // , _capacity(strlen(str))//capacity不包含\0,只统计有效字符
    12. //{
    13. // strcpy(_str, str);
    14. //}
    15. string(const char* str = "")//全缺省,隐含了一个\0,只要是常量字符串,后面都已\0为结尾
    16. {
    17. _size = strlen(str);
    18. _capacity = _size;
    19. _str = new char[_capacity + 1];
    20. strcpy(_str, str);
    21. }
    22. ~string()
    23. {
    24. delete[] _str;
    25. _str = nullptr;
    26. _size = _capacity = 0;
    27. }


    拷贝构造函数:深拷贝

    默认的拷贝构造,代码崩溃原因:浅拷贝指向同一块空间并析构两次(默认生成拷贝构造对内置类型值拷贝) 

    1. void test_string2()
    2. {
    3. string s1("hello world");
    4. string s2(s1);
    5. cout << s1.c_str() << endl;
    6. cout << s2.c_str() << endl;
    7. }

    正确的普通写法

    1. string(const string& s)//s2(s1)
    2. :_str(new char[s._capacity+1])//capacity存储有效数据空间,每次都多开一个\0
    3. ,_size(s._size)
    4. ,_capacity(s._capacity)
    5. {
    6. strcpy(_str, s._str);
    7. }

    正确的现代写法 --利用构造函数swap

    把随机数换给tmp导致程序崩溃--析构释放随机空间 

    1. string(const string& s)//s2(s1)
    2. {
    3. string tmp(s._str);//tmp是打工人,s._str是const字符串,相当于调用构造函数,s2想要一样大的空间和一样大的值
    4. swap(_str, tmp._str);
    5. swap(_size, tmp._size);
    6. swap(_capacity, tmp._capacity);
    7. }

    再改进:放nullptr,delete nullptr不会报错

    1. void swap(string& tmp)
    2. {
    3. ::swap(_str, tmp._str);//::调用全局swap
    4. ::swap(_size, tmp._size);
    5. ::swap(_capacity, tmp._capacity);
    6. }
    7. string(const string& s)//s2(s1)
    8. :_str(nullptr)
    9. ,_size(0)
    10. ,_capacity(0)
    11. {
    12. string tmp(s._str);//tmp是打工人,s._str是const字符串,相当于调用构造函数
    13. swap(tmp);//s2和tmp交换
    14. }


    赋值运算符拷贝

     默认的赋值代码崩溃原因:同样的浅拷贝报错,只是依次拷贝给s1,内存泄漏+析构两次

    1. void test_string3()
    2. {
    3. string s1("hello world");
    4. string s3("ww");
    5. s1 = s3;
    6. }

    正确的普通写法(直接释放旧空间重新开辟)

      自己给自己赋值(释放后随机值拷贝给自己)解决方法:判断相等

    1. string& operator=(const string& s)
    2. {
    3. if (this != &s)
    4. {
    5. char* tmp = new char[s._capacity + 1];
    6. //先开空间再释放,防止new失败抛异常破坏原数据
    7. strcpy(tmp, s._str);
    8. delete[] _str;
    9. _str = tmp;
    10. _size = s._size;
    11. _capacity = s._capacity;
    12. }
    13. return *this;
    14. }

    现代写法

    1.错误的写法,此处swap包含赋值,死循环

    1. string& operator=(const string& s)
    2. {
    3. if (this != &s)
    4. {
    5. string tmp(s);//调拷贝构造
    6. ::swap(*this,tmp);
    7. }
    8. return *this;
    9. }

    2.直接使用全局中的swap包含了一次拷贝构造,两次赋值,一共三次深拷贝,代价极高

    正确的写法,swap成员变量换

    1. void swap(string& tmp)
    2. {
    3. ::swap(_str, tmp._str);//::调用全局swap
    4. ::swap(_size, tmp._size);
    5. ::swap(_capacity, tmp._capacity);
    6. }
    7. string& operator=(const string& s)
    8. {
    9. if (this != &s)
    10. {
    11. //string tmp(s._str);//调用构造函数
    12. string tmp(s);//调拷贝构造
    13. swap(tmp); //this->swap(tmp)
    14. }
    15. return *this;
    16. }

    第二种正确的写法

    1. string& operator=(string s)//传值拷贝顶替tmp,s就是深拷贝的s3
    2. {
    3. swap(s);
    4. return *this;
    5. }


    resize和reserve

    resize默认用0来填充多出的元素空间,resize改变size大小,开空间+初始化。resize新的字符原有的值不动,在后面新增字符。resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变

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

    reserve不改变有效元素个数,只开空间。当reserve的参数小于string的底层空间总大小时,不会改变容量大小,比当前空间大完成扩容

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


    push_back && append

    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. }
    11. void append(const char* str)
    12. {
    13. size_t len = strlen(str);
    14. if (_size + len > _capacity)
    15. {
    16. reserve(_size + len);
    17. }
    18. strcpy(_str + _size, str);//strcpy也会拷贝\0
    19. _size += len;
    20. }
    21. string& operator+=(char ch)
    22. {
    23. push_back(ch);
    24. return *this;
    25. }
    26. string& operator+=(const char* str)
    27. {
    28. append(str);
    29. return *this;
    30. }

    insert

     注意边界

    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];//防止pos位置为0时--end越界
    12. --end;
    13. }
    14. _str[pos] = ch;
    15. ++_size;
    16. return *this;
    17. }
    18. string& insert(size_t pos, const char* str)
    19. {
    20. assert(pos <= _size);
    21. size_t len = strlen(str);
    22. if (_size + len > _capacity)
    23. {
    24. reserve(_size + len);
    25. }
    26. size_t end = _size + len;
    27. while (end >= pos + len)
    28. {
    29. _str[end] = _str[end - len];
    30. --end;
    31. }
    32. strncpy(_str + pos, str, len);
    33. _size += len;
    34. return *this;
    35. }


    erase

    1. void erase(size_t pos, size_t len = npos)
    2. {
    3. assert(pos < _size);
    4. if (len == npos || pos + len >= _size)//>=size删完
    5. {
    6. _str[pos] = '\0';
    7. _size = pos;
    8. }
    9. else//删除需要挪动数据,挪动pos+len往前覆盖
    10. {
    11. strcpy(_str + pos, _str + pos + len);
    12. _size -= len;
    13. }
    14. }


    << && >>

    注意:空格和换行默认作为数据之间的间隔,in>>ch获取会忽略掉空格或者换行,会认定为字符之间的换行输入、多个字符之间的空格或者换行

    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. return out;
    8. }
    9. void clear()//类里
    10. {
    11. _str[0] = '\0';
    12. _size = 0;
    13. }
    14. istream& operator>>(istream& in,string& s)
    15. {
    16. s.clear();//跟库中保持一致
    17. //char ch;
    18. //in >> ch;
    19. //while(ch != ' ' && ch != '\n')
    20. //{
    21. // s+= ch;
    22. // in >> ch;
    23. //}
    24. //拿不到空格和换行,程序无法终止,cin自动忽略空格或者换行,默认认为输入字符之间间隔
    25. char ch;
    26. ch = in.get();//get获取每一个对应字符,不会忽略\n或者空格
    27. const int N = 32;//类似缓冲区,避免多次重复扩容
    28. char buff[N];
    29. size_t i = 0;
    30. while(ch != ' ' && ch != '\n')
    31. {
    32. buff[i++] = ch;
    33. if (i == N - 1)
    34. {
    35. buff[i] = '\0';
    36. s += buff;
    37. i = 0;
    38. }
    39. ch = in.get();
    40. }
    41. buff[i] = '\0';
    42. s += buff;
    43. return in;
    44. }


    迭代器

    所有迭代器区间都是左闭右开;迭代器用法类似;迭代器用法类似指针

    迭代器需要begin(返回第一个位置的类似用法指针)和end(最后一个位置的下一个位置)

    1. void test_string2()
    2. {
    3. string s1("hello");
    4. //普通用法
    5. for (size_t i = 0; i < s1.size(); ++i)
    6. {
    7. cout << s1[i] << " ";
    8. }
    9. //string迭代器写法
    10. string::iterator it = s1.begin();
    11. while (it != s1.end())
    12. {
    13. cout << *it;
    14. it++;
    15. }
    16. cout << endl;
    17. //list迭代器用法
    18. list<int> lt(10, 1);
    19. list<int>::iterator lit = lt.begin();
    20. while (lit != lt.end())
    21. {
    22. cout << *lit<< " ";
    23. ++lit;
    24. }
    25. }

    迭代器模拟实现

    string的迭代器就是原生指针

    1. namespace MyString
    2. {
    3. class string
    4. {
    5. public:
    6. typedef char* iterator;
    7. iterator begin()
    8. {
    9. return _str;//返回第一个位置
    10. }
    11. iterator end()
    12. {
    13. return _str+_size;//end返回的迭代器是最后一个数据的下一个位置(\0)
    14. }
    15. void test_string1()
    16. {
    17. string s1("hello world");
    18. string s2;
    19. /*for (size_t i = 0; i < s1.size(); ++i)
    20. {
    21. cout << s1[i];
    22. }*/
    23. string::iterator it = s1.begin();
    24. while (it != s1.end())
    25. {
    26. cout << *it << " ";
    27. it++;
    28. }
    29. cout << endl;
    30. for (auto ch : s1)
    31. {
    32. cout << ch << " ";
    33. }
    34. }
    35. }
    36. }

    范围for的底层就是迭代器,优化写法


    c_str(cpp中使用了string,要和c语言兼容,用c去读文件等)

    打印test.cpp中的数据读取到屏幕中

    1. void test_string5()
    2. {
    3. string filename("test.cpp");
    4. //用c去读文件
    5. FILE* fout = fopen(filename.c_str(), "r");
    6. assert(fout);
    7. char ch = fgetc(fout);
    8. while (ch != EOF)
    9. {
    10. cout << ch;
    11. ch = fgetc(fout);
    12. }
    13. }
    14. int main()
    15. {
    16. test_string5();
    17. return 0;
    18. }

    注意,两个字符串打印有区别,c语言以\0为结尾.cpp以string对象的size为结尾

    1. cout << filename << endl;
    2. cout << filename.c_str() << endl;


    append迭代器版本

    模板(迭代器区间)

    1. string s1("hello");
    2. string s2("world");
    3. s1.append(s2.begin(), s2.end());//左闭右开迭代器区间数据添加进去
    4. cout << s1;


    find和substr

     取文件后缀

    1. void test_string6()
    2. {
    3. string s("test.a.cpp");
    4. size_t pos = s.rfind('.');
    5. if (pos != string::npos)
    6. {
    7. string suf = s.substr(pos);
    8. cout << suf << endl;
    9. }
    10. }

     取协议、网站域名、uri

    1. void DealUrl(const string& url)
    2. {
    3. size_t pos1 = url.find("://");
    4. if (pos1 == string::npos)
    5. {
    6. cout << "非法url" << endl;
    7. return;
    8. }
    9. string protocol = url.substr(0, pos1);
    10. cout << protocol<
    11. size_t pos2 = url.find('/', pos1 + 3);
    12. if (pos2 == string::npos)
    13. {
    14. cout << "非法url" << endl;
    15. return;
    16. }
    17. string domain = url.substr(pos1 + 3,pos2-pos1-3);
    18. cout << domain << endl;
    19. string uri = url.substr(pos2+1);
    20. cout << uri << endl;
    21. }
    22. int main()
    23. {
    24. string s1("https://www.baidu.com/baidu?tn=monline_3_dg&ie=utf-8&wd=%5C0%E7%AD%89%E4%BA%8E0");
    25. DealUrl(s1);
    26. return 0;
    27. }

    注意:find是搜素字符串匹配字符的第一个位置,要求全部匹配;

    find_first_of是匹配整个字符串中出现的对应字符

    find模拟实现

    1. size_t find(char ch, size_t pos = 0)//找一个字符,从pos位置开始
    2. {
    3. assert(pos < _size);
    4. for (size_t i = pos; i < _size; ++i)
    5. {
    6. if (ch == _str[i])
    7. {
    8. return i;
    9. }
    10. }
    11. return npos;
    12. }
    13. size_t find(const char* sub, size_t pos = 0)
    14. {
    15. const char* tmp = strstr(_str + pos, sub);
    16. if (tmp == nullptr)
    17. {
    18. return npos;
    19. }
    20. else
    21. {
    22. return tmp - _str;//要返回下标,指针-指针
    23. }
    24. }

    从pos位置开始,取len个字符构造新的字串:substr模拟实现

    1. string substr(size_t pos, size_t len = npos) const
    2. {
    3. assert(pos < _size);
    4. size_t reallen = len;
    5. if (len == npos || pos + len > _size)
    6. {
    7. reallen = _size - pos;//只要是左闭右开,右开-左闭就是长度
    8. }
    9. string tmp;
    10. for (size_t i = 0; i < reallen; ++i)
    11. {
    12. tmp += _str[pos+i];//剩余数据拷贝
    13. }
    14. return tmp;
    15. }


    getline练习题

    字符串最后一个单词的长度_牛客题霸_牛客网

     以下写法错误的原因在于cin

    1. #include
    2. #include
    3. using namespace std;
    4. int main(){
    5. string s;
    6. cin>>s;
    7. //getline(cin,s);
    8. size_t pos = s.rfind(' ');
    9. if(pos != string::npos)
    10. {
    11. cout<size()-pos-1<
    12. }
    13. else
    14. {
    15. cout<size()<
    16. }
    17. }

    cin和scanf都一样,如果输入多个值或者多个字符串,多个值之间是用:空格,换行来间隔。

    现在我们输入的一行里面,本身带有空格,用cin接收不到,只能接收到空格前的一串字符,剩下的字符在缓冲区中,此时使用getline

     

    1. #include
    2. #include
    3. using namespace std;
    4. int main(){
    5. string s;
    6. getline(cin,s);
    7. size_t pos = s.rfind(' ');
    8. if(pos != string::npos)
    9. {
    10. cout<size()-pos-1<
    11. }
    12. else
    13. {
    14. cout<size()<
    15. }
    16. }


    运算符重载

    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 *this > s || *this == s;
    8. }
    9. bool operator==(const string& s) const
    10. {
    11. return strcmp(_str, s._str) == 0;
    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. }


    to_string:从字符串转换-其他转字符串函数

    1. void test_string7()
    2. {
    3. const char* s1 = "123";
    4. cout<<atoi(s1);
    5. int ival;
    6. double dval;
    7. cin >> ival >> dval;
    8. string istr = to_string(ival);
    9. string dstr = to_string(dval);
    10. cout << istr << endl;
    11. cout << dstr << endl;
    12. istr = "9999";
    13. dstr = "88.88";
    14. ival = stoi(istr);
    15. dval = stod(dstr);
    16. }

     

  • 相关阅读:
    deepstream6.2部署yolov5详细教程与代码解读
    闭包的详细认识与实例
    入门力扣自学笔记149 C++ (题目编号1608)
    HelloGitHub 社区动态,开启新的篇章!
    大一新生常踩的5大坑,轻则损失钱财,重则影响毕业
    模块及分类
    阿拉伯数字转汉子数字
    docker安装部署nginx
    最大似然函数 损失函数 逻辑回归与线性回归的比较
    驱动开发:通过MDL映射实现多次通信
  • 原文地址:https://blog.csdn.net/weixin_63543274/article/details/126288655