从C语言到C++_13(string的模拟实现)深浅拷贝+传统/现代写法(下)

简介: 从C语言到C++_13(string的模拟实现)深浅拷贝+传统/现代写法

从C语言到C++_13(string的模拟实现)深浅拷贝+传统/现代写法(中):https://developer.aliyun.com/article/1513673

4.7 find() 的实现

find:查找字符

如果遍历完整个字符串都没找到,就返回 npos(找到库的来)。

这个 npos 我们可以在成员变量中定义:

    size_t find(const char ch) const
    {
      for (size_t i = 0; i < _size; i++) 
      {
        if (ch == _str[i])// 找到了
        {
          return i;    // 返回下标
        }
      }
      return npos;// 找不到
    }
 
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
  public:
    const static size_t npos = -1;// const static 语法特殊处理,直接可以当成定义初始化

find:查找字符串

这里我们可以用 strstr 去找子串,如果找到了,返回的是子串首次出现的地址。

如果没找到,返回的是空。所以我们这里可以做判断,如果是 nullptr 就返回 npos。

如果找到了,就返回对应下标,子串地址 - 开头,就是下标了。

    size_t find(const char* str, size_t pos = 0) const
    {
      const char* ptr = strstr(_str + pos, str);
      if (ptr == nullptr) 
      {
        return npos;
      }
      else 
      {
        return ptr - _str;  // 减开头
      }
    }

4.8 erase() 的实现

    string& erase(size_t pos, size_t len = npos) 
    {
      assert(pos < _size);
      if (len == npos || pos + len >= _size)// 如果pos后面的都删完了,注意len == npos 不能忽略,因为npos + len 有可能重回到 1
      {
        _str[pos] = '\0';
        _size = pos;
      }
      else
      {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
      }
      return *this;
    }

测试find 和 erase:

5. 传统写法和现代写法

对于拷贝构造的深拷贝,传统写法就是本本分分分地去完成深拷贝:

    string(const string& s)/传统写法
      :_str(new char[s._capacity + 1])
      , _size(s._size)
      , _capacity(s._capacity)
    {
      strcpy(_str, s._str);
    }

5.1 拷贝构造的现代写法

现在我们来介绍一种现代写法,它和传统写法本质工作是一样的,即完成深拷贝。

现代写法的方式不是本本分分地去按着 Step 一步步干活,而是 "投机取巧" 地去完成深拷贝

    void swap(string& s)// s和*this换
    {
      ::swap(s._str, _str);//注意这里要加域作用符,默认是全局的,不然就是自己调自己了
      ::swap(s._size, _size);
      ::swap(s._capacity, _capacity);
 
    }
    string(const string& s)/现代写法
      :_str(nullptr)
      , _size(0)
      , _capacity(0)
    {
      string tmp(s._str);
      swap(tmp);// tmp和*this换
    }

现代写法的本质就是复用了构造函数。


我想拷贝,但我又不想自己干,我把活交给工具人 tmp 来帮我干。


值得注意的是如果不给原_str赋空指针,那么它的默认指向会是个随机值。和tmp交换后,tmp 是一个局部对象,我们把 s2 原来的指针和 tmp 交换了,那么 tmp 就成了个随机值了。tmp 出了作用域要调用析构函数,对随机值指向的空间进行释放,怎么释放?都不是你自己的 new / malloc 出来的,你还硬要对它释放,就可能会引发崩溃。但是 delete / free 一个空,是不会报错的,因为会进行一个检查。所以是可以 delete 一个空的,我们这里初始化列表中把 nullptr 给 _str,是为了交换完之后, nullptr 能交到 tmp 手中,这样 tmp 出了作用域调用析构函数就不会翻车了。

5.2 赋值重载的现代写法

赋值重载的传统写法:

    string& operator=(const string& s)/传统写法
    {
      if (this != &s)
      {
        char* tmp = new char[s._capacity + 1];// 开辟新的空间
        strcpy(tmp, s._str);// 赋值到tmp
        delete[] _str;// 释放原有空间
 
        _str = tmp;// tmp赋值到想要的地方,出去tmp就销毁了
        _size = s._size;
        _capacity = s._capacity;
      }
      return *this;
    }   

赋值重载的现代写法:

    string& operator=(const string& s)/现代写法
    {
      if (this != &s)
      {
        string tmp(s);
        swap(tmp);// tmp和*this换
      }
      return *this;
    }

比上面的拷贝构造的现代写法还要压榨tmp,交换完之后,正好让 tmp 出作用域调用析构函数,属实是一石二鸟的美事。把 tmp 压榨的干干净净,还让 tmp 帮忙把屁股擦干净(释放空间)。

用上面的测试简单测一下:


总结:

现代写法在 string 中体现的优势还不够大,因为好像和传统写法差不多。

但是到后面实现 vector、list 的时候,会发现现代写法的优势真的是太大了。

现代写法写起来会更简单些,比如如果是个链表,传统写法就不是 strcpy 这么简单的了,

你还要一个一个结点拷贝过去,但是现代写法只需要调用 swap 交换一下就可以了。

现代写法更加简洁,只是在 string 这里优势体现的不明显罢了,我们后面可以慢慢体会。

6. operator 运算符重载

6.1 六个比较运算符重载

学日期类的时候就说过,只需实现 > 和 ==,剩下的都可以复用解决:

而且> 和 ==可以直接用strcmp ,剩下的复用,你不想用strcmp时剩下的也不用改了:

      bool operator>(const string& s) const
    {
      return strcmp(_str, s._str) > 0;
    }
    bool operator==(const string& s) const
    {
      return strcmp(_str, s._str) == 0;
    }   
    bool operator>=(const string& s) const// 养成this指针写在前面的习惯
    {
      return *this > s || *this == s;
    }
    bool operator<(const string& s) const
    {
      return !(*this >= s);
    }   
    bool operator<=(const string& s) const
    {
      return !(*this > s);
    }   
    bool operator!=(const string& s) const
    {
      return !(*this == s);
    }

6.2 流插入和流提取重载

我们当时实现日期类的流插入和流提取时,也详细讲过这些,当时讲解了友元。

在友元那一章我们说过 "占参问题" ,这里就不再多做解释了。

如果我们重载成成员函数,第一个位置就会被隐含的 this 指针占据。

这样实现出来的流插入必然会不符合我们的使用习惯,所以我们选择在全局实现。

在全局里不存在隐含的 this 指针了。

而且我们已经有operator [ ] 可以访问私有成员了,所以不需要设置成友元函数:

流插入很简单:

  ostream& operator<<(ostream& out, string& s)
  {
    for (size_t i = 0;i < s.size();++i)
    {
      out << s[i];
    }
    return out;
  }

但是流提取是这么简单吗?下面的代码有什么问题?:

  istream& operator>>(istream& in, string& s)
  {
    char ch;
    in >> ch;
    while (ch != ' ' && ch != '\n')
    {
      s += ch;
      in >> ch;
    }
    return in;
  }

我们发现这样输入空格和换行也终止不了程序,因为cin会自动忽略空格和换行。

有什么办法?cin有一个get的成员函数,可以获取每一个字符,现在我们查下文档会用就行,

后面我们还会详细的讲解IO流,流提取普通实现:

  istream& operator>>(istream& in, string& s)
  {
    char ch;
    ch = in.get();
    while (ch != ' ' && ch != '\n')
    {
      s += ch;
      ch = in.get();
    }
    return in;
  }

这样实现的流提取有一个缺陷:频繁的 += 效率低,能想到什么办法优化?

以下是类似库里面的实现:(思路类似缓冲区)

  istream& operator>>(istream& in, string& s)// 流插入优化(类似库里面的)
  {
    char ch;
    ch = in.get();
 
    const size_t N = 32;
    char buff[N];// C++11支持的变长数组
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
      buff[i++] = ch;
      if (i == N - 1)// 如果buff的容量满了
      {
        buff[i] = '\0';// 在后面放\0,
        s += buff;// += 到 s 上
        i = 0;// 把 i 重新变成0 用来再次使用buff数组
      }
      ch = in.get();
    }
    buff[i] = '\0';// 处理一下buff剩余的
    s += buff;
 
    return in;
  }

简单测试下:

  void test_string6()
  {
    string s1;
    string s2;
    cin >> s1 >> s2;
    cout << s1 << endl << s2 << endl;
 
    cout << (s1 > s2) << endl;
    cout << (s1 == s2) << endl;
    cout << (s1 >= s2) << endl;
    cout << (s1 < s2) << endl;
    cout << (s1 <= s2) << endl;
    cout << (s1 != s2) << endl;
  }

7. 完整代码:

string.h:

#pragma once
 
#include<iostream>
#include<string>
#include<assert.h>
using namespace std;
 
namespace rtx
{
  class string
  {
  public:
    string(const char* s  = "")
    {
      _size =strlen(s);// 因为要算多次strlen 效率低 且放在初始化列表关联到声明顺序 所以不用初始化列表
      _capacity = _size;
      _str = new char[_size + 1];// 开_size+1大小的空间(多开一个放\0)
      strcpy(_str, s);
    }
 
    //string(const string& s)/传统写法
    //  :_str(new char[s._capacity + 1])
    //  , _size(s._size)
    //  , _capacity(s._capacity)
    //{
    //  strcpy(_str, s._str);
    //}
    void swap(string& s)// s和*this换
    {
      ::swap(s._str, _str);//注意这里要加域作用符,默认是全局的,不然就是自己调自己了
      ::swap(s._size, _size);
      ::swap(s._capacity, _capacity);
 
    }
    string(const string& s)/现代写法
      :_str(nullptr)
      , _size(0)
      , _capacity(0)
    {
      string tmp(s._str);
      swap(tmp);// tmp和*this换
    }
 
    //string& operator=(const string& s)/传统写法
    //{
    //  if (this != &s)
    //  {
    //    char* tmp = new char[s._capacity + 1];// 开辟新的空间
    //    strcpy(tmp, s._str);// 赋值到tmp
    //    delete[] _str;// 释放原有空间
 
    //    _str = tmp;// tmp赋值到想要的地方,出去tmp就销毁了
    //    _size = s._size;
    //    _capacity = s._capacity;
    //  }
    //  return *this;
    //}   
    string& operator=(const string& s)/现代写法
    {
      if (this != &s)
      {
        string tmp(s);
        swap(tmp);// tmp和*this换
      }
      return *this;
    }
 
    ~string() 
    {
      delete[] _str;
      _str = nullptr;
    }
 
    const char* c_str() const 
    {
      return _str;
    }
 
    size_t size() const
    {
      return _size;
    }
 
    char& operator[](size_t pos)
    {
      assert(pos < _size);
      return _str[pos];
    }
 
    const char& operator[](size_t pos) const
    {
      assert(pos < _size);
      return _str[pos];
    }
 
    typedef char* iterator;
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }
 
    typedef const char* const_iterator;
    const_iterator begin() const 
    {
      return _str;
    }
    const_iterator end() const
    {
      return _str + _size;
    }
 
    void reserve(size_t new_capacity)
    {
      if (new_capacity > _capacity)
      {
        char* tmp = new char[new_capacity + 1];// 开新空间
        strcpy(tmp, _str);// 搬运
        delete[] _str; // 释放原空间
 
        _str = tmp;// 没问题,递交给_str
        _capacity = new_capacity;// 更新容量
      }
    }
 
    void push_back(const char ch)
    {
      if (_size == _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
 
      _str[_size++] = ch;// 在_size位置放字符后++
      _str[_size] = '\0';// 易漏
 
      //insert(_size, ch);
    }
 
    void append(const char* str)
    {
      int len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
 
      strcpy(_str + _size, str);// 首字符+_size大小就是\0位置
      _size += len;
 
      //insert(_size, str);
    }
 
    string& operator+=(const char ch)
    {
      push_back(ch);
      return *this;
    }
    string& operator+=(const char* str)
    {
      append(str);
      return *this;
    }
 
    string& insert(size_t pos, const char ch)
    {
      assert(pos <= _size);
      if (_size == _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
 
      for (size_t i = _size + 1;i > pos; --i)// 挪动数据,+1是挪动\0
      {
        _str[i] = _str[i - 1];
      }
 
      _str[pos] = ch;
      ++_size;
      return *this;
    }
    string& insert(size_t pos, const char* str)
    {
      assert(pos <= _size);
      int len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
 
      for (size_t i = _size + len ;i > pos + len - 1; --i)// 挪动数据,画图注意边界,参考上面inser字符的len == 1
      {
        _str[i] = _str[i - len];// 首先看\0 _size+len-len就是\0的位置
      }
 
      strncpy(_str + pos, str, len);
      _size += len;
      return *this;
    }
 
    void resize(size_t new_capacity, const char ch = '\0')
    {
      if (new_capacity > _size)// 插入数据
      {
        reserve(new_capacity);
        //for (size_t i = _size; i < new_capacity; ++i)
        //{
        //  _str[i] = ch;
        //}
        memset(_str + _size, ch, new_capacity - _size);// 上面的for循环即memset的功能
        _str[new_capacity] = '\0';
        _size = new_capacity;
      }
      else// 删除数据
      {
        _str[new_capacity] = '\0';
        _size = new_capacity;
      }
    }
 
    size_t find(char ch) const
    {
      for (size_t i = 0; i < _size; i++) 
      {
        if (ch == _str[i])// 找到了
        {
          return i;    // 返回下标
        }
      }
      return npos;// 找不到
    }
    size_t find(const char* str, size_t pos = 0) const
    {
      const char* ptr = strstr(_str + pos, str);
      if (ptr == nullptr) 
      {
        return npos;
      }
      else 
      {
        return ptr - _str;  // 减开头
      }
    }
 
    string& erase(size_t pos, size_t len = npos) 
    {
      assert(pos < _size);
      if (len == npos || pos + len >= _size)// 如果pos后面的都删完了,注意len == npos 不能忽略,因为npos + len 有可能重回到 1
      {
        _str[pos] = '\0';
        _size = pos;
      }
      else
      {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
      }
      return *this;
    }
 
    bool operator>(const string& s) const
    {
      return strcmp(_str, s._str) > 0;
    }
    bool operator==(const string& s) const
    {
      return strcmp(_str, s._str) == 0;
    }   
    bool operator>=(const string& s) const// 养成this指针写在前面的习惯
    {
      return *this > s || *this == s;
    }
    bool operator<(const string& s) const
    {
      return !(*this >= s);
    }   
    bool operator<=(const string& s) const
    {
      return !(*this > s);
    }   
    bool operator!=(const string& s) const
    {
      return !(*this == s);
    }
 
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
  public:
    const static size_t npos = -1;// const static 语法特殊处理,直接可以当成定义初始化
  };
 
  ostream& operator<<(ostream& out, string& s)
  {
    for (size_t i = 0;i < s.size();++i)
    {
      out << s[i];
    }
    return out;
  }
 
  //istream& operator>>(istream& in, string& s)// 流插入普通实现
  //{
  //  char ch;
  //  ch = in.get();
  //  while (ch != ' ' && ch != '\n')
  //  {
  //    s += ch;
  //    ch = in.get();
  //  }
  //  return in;
  //}
  istream& operator>>(istream& in, string& s)// 流插入优化(类似库里面的)
  {
    char ch;
    ch = in.get();
 
    const size_t N = 32;
    char buff[N];// C++11支持的变长数组
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
      buff[i++] = ch;
      if (i == N - 1)// 如果buff的容量满了
      {
        buff[i] = '\0';// 在后面放\0,
        s += buff;// += 到 s 上
        i = 0;// 把 i 重新变成0 用来再次使用buff数组
      }
      ch = in.get();
    }
    buff[i] = '\0';// 处理一下buff剩余的
    s += buff;
 
    return in;
  }
 
  void test_string1()
  {
    string s1("hello world");
    string s2(s1);
 
    string s3("!!!");
    s1 = s3;
 
    cout << s1.c_str() << endl;
    cout << s2.c_str() << endl;
    cout << s3.c_str() << endl;
 
    string s4;
    cout << s4.c_str() << endl;
  }
  void test_string2() 
  {
    string s1("hello world");
    string s2;
 
    for (size_t i = 0; i < s1.size(); i++) 
    {
      cout << s1[i] << " ";
    }
    cout << endl;
 
    s1[0] = 'x';
    for (size_t i = 0; i < s1.size(); i++)
    {
      cout << s1[i] << " ";
    }
    cout << endl;
  }
 
  void test_string3()
  {
    string s1("hello world");
 
    string::iterator it = s1.begin();
    while (it != s1.end())
    {
      cout << *it << " ";// 读
      it++;
    }
    cout << endl;
 
    it = s1.begin();
    while (it != s1.end())
    {
      (*it)++;// 写,++的优先级比*高,+=就低,可以 *it += 1;
      cout << *it << " ";
      it++;
    }
    cout << endl;
 
    for (auto& e : s1)
    {
      cout << e << " ";
    }
    cout << endl;
  }
 
  void test_string4() 
  {
    string s1("hello world");
    cout << s1.c_str() << endl;
 
    s1.push_back('!');
    cout << s1.c_str() << endl;
 
    s1.push_back('R');
    cout << s1.c_str() << endl;
 
    s1.append("abcd");
    cout << s1.c_str() << endl;
 
    s1 += 'e';
    s1 += "fgh";
    cout << s1.c_str() << endl;
 
    s1.insert(0, 'x');
    s1.insert(6, 'T');
    cout << s1.c_str() << endl;
 
    s1.insert(6, "PPPPPPPPPP");
    cout << s1.c_str() << endl;
    s1.insert(0, "PPPPPPPPPP");
    cout << s1.c_str() << endl;
 
    s1.resize(100,'x');
    cout << s1.c_str() << endl;
  }
 
  void test_string5()
  {
    string s1("hello world");
    string s2(s1);
    string s3 = s1;
    cout << s2.c_str() << endl << s3.c_str() << endl;
 
    cout << s1.find('d') << endl;// 打印d的下标:6
    cout << s1.find("world") << endl;// 打印了w的下标:10
    cout << s1.find("wold") << endl;// 打印了npos:4294967295
    cout << s1.find("world", 9) << endl;// 打印了npos:4294967295
 
    s1.erase(9, 2);// 从下标9开始删除2个字符
    cout << s1.c_str() << endl;
 
    s1.erase(s1.find('o'), 2);// 找到o,其下标为4,从下标4开始删除2个字符
    cout << s1.c_str() << endl;
 
    s1.erase(5);// 从下标5开始删完
    cout << s1.c_str() << endl;
  }
 
  void test_string6()
  {
    string s1;
    string s2;
    cin >> s1 >> s2;
    cout << s1 << endl << s2 << endl;
 
    cout << (s1 > s2) << endl;
    cout << (s1 == s2) << endl;
    cout << (s1 >= s2) << endl;
    cout << (s1 < s2) << endl;
    cout << (s1 <= s2) << endl;
    cout << (s1 != s2) << endl;
  }
}

Test.c:

#define _CRT_SECURE_NO_WARNINGS 1
 
#include "string.h"
 
int main()
{
  try
  {
    rtx::test_string6();
  }
  catch (const exception& e)
  {
    cout << e.what() << endl;
  }
 
  return 0;
}

本章完。

这篇博客两万多个字了......刚开始接触确实有点累der

不过STL的实现都是类似的,以后的学习就轻松多了,

想笑~ 来伪装掉下的眼泪~

目录
相关文章
|
29天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
19 1
|
1月前
|
C++ 容器
|
1月前
|
C++ 容器
|
1月前
|
C语言 C++
深度剖析C++string(中)
深度剖析C++string(中)
45 0
|
1月前
|
存储 编译器 程序员
深度剖析C++string(上篇)(2)
深度剖析C++string(上篇)(2)
35 0
|
1月前
|
存储 Linux C语言
深度剖析C++string(上篇)(1)
深度剖析C++string(上篇)(1)
29 0
|
1月前
|
C++
|
5天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
28 4
|
6天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
23 4
|
29天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
26 4