【C++】学习笔记——string_5

简介: 【C++】学习笔记——string_5

六、string类

7. string类的模拟实现

我们之前讲了实现 insert ,但是那个插入函数仅仅是在 pos 位置插入一个字符而且,我们并没有实现在 pos 位置插入一个字符串。所以我们现在将其补充上。

// 在 pos 位置插入一个字符串
void insert(size_t pos, const char* str)
{
  assert(pos <= _size);
  size_t len = strlen(str);
  // 空间不够需要扩容
  if (_size + len > _capacity)
  {
    reserve(_size + len);
  }
  size_t end = _size + len;
  while (end >= pos + len)
  {
    _str[end] = _str[end - len];
    --end;
  }
  strncpy(_str + pos, str, len);
}

当难以理解的时候,记得画图哦,我们来看看结果:

#include"string.h"
using namespace my;
int main()
{
  string s("hello,world");
  s.insert(4, "AAAAAAA");
  std::cout << s.c_str() << std::endl;
  return 0;
}

没问题。其实我们发现,有了 insert 之后,前面的 push_back()append 好像可以复用这的代码,我们来调整一下。

void push_back(char ch)
{
  //if (_size == _capacity)
  //{
  //  // 扩容2倍
  //  reserve(_capacity == 0 ? 4 : 2 * _capacity);
  //}
  //_str[_size] = ch;
  //++_size;
  //_str[_size] = '\0';
  insert(_size, ch);
}
void append(const char* str)
{
  //size_t len = strlen(str);
  //if (_size + len > _capacity)
  //{
  //  reserve(_size + len);
  //}
   从末尾开始,拷贝新字符串
  //strcpy(_str + _size, str);  
  //_size += len;
  insert(_size, str);
}

接下来我们再把 swap 给实现一下,有人会说哈,swap 在库里面就有,为啥还要我们手动实现呢?我给大家看看库里的 swap 是怎样的。

我们发现,库里的swap整整调用了3次拷贝和1次析构,虽然能用,但是它非常搓,所以我们要实现一个对我们来说更加好用的 swap

void swap(string& s)
{
  // 调用库里的swap,直接交换成员即可,不需要创建一个新的对象
  std::swap(_str, s._str);
  // 加上std:: 是让其直接去std域找,避免找到当前的成员函数swap
  std::swap(_size, s._size);
  std::swap(_capacity, s._capacity);
}
#include"string.h"
using namespace my;
int main()
{
  string s("hello,world");
  string s1("1234567890");
  
  s.swap(s1);
  std::cout << s.c_str() << std::endl << s1.c_str() << std::endl;
  return 0;
}

既然库里的 swap 对我们来说很挫,但是我们应该怎样才能防止别人使用库里的函数呢?

库里面有个 swap ,成员函数有个 swap ,这里怎么还有一个非成员函数的 swap

这里的 swap 原来直接调用的是成员函数 swap ,那么非成员函数的 swap 是全局的,库里的 swap 是全局的,为啥没发生冲突?为啥就会先用非成员函数的 swap ?我在模板那篇提到过,因为 库里的 swap 是模板当有现成的函数时,优先使用现成的。这下就完美解决了使用库里的swap了。

// string类外
void swap(string& x, string& y)
{
  x.swap(y);
}

接下来实现 find 函数。

// 查找一个字符
size_t find(char ch, size_t pos = 0) const
{
  for (size_t i = pos; i < _size; ++i)
  {
    if (_str[i] == ch)
      return i;
  }
  return npos;
}
// 查找一个字串
size_t find(const char* sub, size_t pos = 0) const
{
  assert(pos < _size);
  const char* p = strstr(_str + pos, sub);
  // 找到了
  if (p)
  {
    return p - _str;
  }
  // 没找到
  return npos;
}

接下来实现 substr 函数。

string substr(size_t pos = 0, size_t len = npos)
{
  string sub;
  if (len >= _size - pos)
  {
    for (size_t i = pos; i < _size; ++i)
    {
      sub += _str[i];
    }
  }
  else
  {
    for (size_t i = pos; i < pos + len; ++i)
    {
      sub += _str[i];
    }
  }
  return sub;
}

验证验证:

#include"string.h"
using namespace my;
int main()
{
  string s("hello,world");
  
  size_t pos = s.find(',');
  std::cout << s.substr(pos, 3).c_str() << std::endl;
  return 0;
}

再就是重载比较。

// 重载成全局函数
bool operator==(const string& s1, const string& s2)
{
  // strcmp 若是相等则返回0
  int ret = strcmp(s1.c_str(), s2.c_str());
  return ret == 0;
}
bool operator<(const string& s1, const string& s2)
{
  int ret = strcmp(s1.c_str(), s2.c_str());
  return ret < 0;
}
bool operator<=(const string& s1, const string& s2)
{
  return s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2)
{
  return !(s1 <= s2);
}
bool operator>=(const string& s1, const string& s2)
{
  return !(s1 < s2);
}
bool operator!=(const string& s1, const string& s2)
{
  return !(s1 == s2);
}

实现流插入和流提取:

using namespace std;
ostream& operator<<(ostream& out, const string& s)
{
  // 没有访问私有成员,不需要友元
  for (auto ch : s)
  {
    out << ch;
  }
  return out;
}
istream& operator>>(istream& in, string& s)
{
  // 需要将其内容清空
  s.clear();
  char ch;
  // cin 读取不到 ' ' 和 '\n'
  ch = in.get();
  // 减少扩容
  char buff[128];
  size_t i = 0;
  while (ch != ' ' && ch != '\n')
  {
    buff[i++] = ch;
    if (i == 127)
    {
      buff[127] = '\0';
      s += buff;
      i = 0;
    }
    ch = in.get();
  }
  if (i > 0)
  {
    buff[i] = '\0';
    s += buff;
  }
  return in;
}

顺便实现 clear

// 成员函数
void clear()
{
  _size = 0;
  _str[_size] = '\0';
}

检验检验:

#include"string.h"
using namespace my;
int main()
{
  // 刚刚展开了std,这里避免冲突
  my::string s;
  cin >> s;
  cout << s;
  return 0;
}

很好,空格前的都被读取到了。

再来实现 getline 。getline就是读取一行嘛,相信实现了流提取运算符,实现一个 getline 肯定非常轻松。

istream& getline(istream& in, string& s)
{
  // 需要将其内容清空
  s.clear();
  char ch;
  // cin 读取不到 ' ' 和 '\n'
  ch = in.get();
  // 减少扩容
  char buff[128];
  size_t i = 0;
  while (ch != '\n')
  {
    buff[i++] = ch;
    if (i == 127)
    {
      buff[127] = '\0';
      s += buff;
      i = 0;
    }
    ch = in.get();
  }
  if (i > 0)
  {
    buff[i] = '\0';
    s += buff;
  }
  return in;
}

再来检验:

#include"string.h"
using namespace my;
int main()
{
  my::string s;
  getline(cin, s);
  cout << s;
  return 0;
}

在实现拷贝构造函数时,我们写了一个非常传统的写法,这里再给大家实现一种新式写法:

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

这里新式写法本质上就是调用构造函数,然后让 this指针 指向新的构造的 sring 类 。同样,赋值重载也能使用新式写法。

// 传统写法
string& operator=(const string& s)
{
  char* tmp = new char[s._capacity + 1];
  strcpy(tmp, s._str);
  
  delete[] _str;
  _str = tmp;
  _size = s._size;
  _capacity = s._capacity;
  return *this;
}
// 新式写法
string& operator=(const string& s)
{
  string tmp(s);
  swap(tmp);
  return *this;
}

这里赋值重载的新式写法还能优化(行数),既然在函数内部要调用拷贝构造,为什么不在传参的时候直接调用拷贝构造呢?

string& operator=(string s)
{
  swap(s);
  return *this;
}

8. string类的模拟实现的完整代码

string.h头文件

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace my
{
  class string
  {
  public:
    typedef char* iterator;
    typedef const char* const_iterator;
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }
    const_iterator begin() const
    {
      return _str;
    }
    const_iterator end() const
    {
      return _str + _size;
    }
    // 默认是空串而不是空指针
    string(const char* str = "")
      :_size(strlen(str))
    {
      _capacity = _size;
      _str = new char[_capacity + 1];
      strcpy(_str, str);
    }
    //string(const string& s)
    //{
    //  _str = new char[s._capacity + 1];
    //  strcpy(_str, s._str);
    //  _size = s._size;
    //  _capacity = s._capacity;
    //}
    string(const string& s)
    {
      string tmp(s._str);
      swap(tmp);
    }
    //string& operator=(const string& s)
    //{
    //  char* tmp = new char[s._capacity + 1];
    //  strcpy(tmp, s._str);
    //  
    //  delete[] _str;
    //  _str = tmp;
    //  _size = s._size;
    //  _capacity = s._capacity;
    //  return *this;
    //}
    string& operator=(string s)
    {
      swap(s);
      return *this;
    }
    ~string()
    {
      delete[] _str;
      _str = nullptr;
      _size = _capacity = 0;
    }
    // 加 const 使其成为 const 成员函数,使 const 对象也能调用这个函数
    size_t size() const
    {
      return _size;
    }
    // 引用返回,可读可写
    inline char& operator[](size_t pos)
    {
      assert(pos < _size);
      return _str[pos];
    }
    // 针对 const对象 的可读不可写,加 & 是为了减少拷贝
    inline const char& operator[](size_t pos) const
    {
      assert(pos < _size);
      return _str[pos];
    }
    size_t capacity() const
    {
      return _capacity;
    }
    void reserve(size_t n)
    {
      // 只有要扩容的大小比当前容量大才能扩容
      if (n > _capacity)
      {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
      }
    }
    void push_back(char ch)
    {
      //if (_size == _capacity)
      //{
      //  // 扩容2倍
      //  reserve(_capacity == 0 ? 4 : 2 * _capacity);
      //}
      //_str[_size] = ch;
      //++_size;
      //_str[_size] = '\0';
      insert(_size, ch);
    }
    void append(const char* str)
    {
      //size_t len = strlen(str);
      //if (_size + len > _capacity)
      //{
      //  reserve(_size + len);
      //}
       从末尾开始,拷贝新字符串
      //strcpy(_str + _size, str);  
      //_size += len;
      insert(_size, str);
    }
    string& operator+=(char ch)
    {
      push_back(ch);
      return *this;
    }
    string& operator+=(const char* str)
    {
      append(str);
      return *this;
    }
    char* c_str() const
    {
      return _str;
    }
    // 在 pos 位置插入一个字符
    void insert(size_t pos, char ch)
    {
      assert(pos <= _size);
      if (_size == _capacity)
      {
        // 扩容2倍
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
      }
      size_t end = _size + 1;
      while (end > pos)
      {
        _str[end] = _str[end - 1];
        --end;
      }
      _str[pos] = ch;
      ++_size;
    }
    // 在 pos 位置插入一个字符串
    void insert(size_t pos, const char* str)
    {
      assert(pos <= _size);
      size_t len = strlen(str);
      // 空间不够需要扩容
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
      size_t end = _size + len;
      while (end >= pos + len)
      {
        _str[end] = _str[end - len];
        --end;
      }
      strncpy(_str + pos, str, len);
    }
    // 从 pos 开始,删除 len 个字符,如果 len 是 npos ,则全删
    void erase(size_t pos, size_t len = npos)
    {
      assert(pos < _size);
      // pos + len >= _size 可能会溢出
      if (len == npos || len >= _size - pos)
      {
        _str[pos] = '\0';
        _size = pos;
      }
      strcpy(_str + pos + len, _str + pos);
      _size -= len;
    }
    void resize(size_t n, char ch = '\0')
    {
      if (n <= _size)
      {
        _str[n] = '\0';
        _size = n;
      }
      else
      {
        reserve(n);
        for (size_t i = _size; i < n; ++i)
        {
          _str[i] = ch;
        }
        _str[n] = '\0';
        _size = n;
      }
    }
    void swap(string& s)
    {
      // 直接交换成员即可
      std::swap(_str, s._str);
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }
    size_t find(char ch, size_t pos = 0) const
    {
      for (size_t i = pos; i < _size; ++i)
      {
        if (_str[i] == ch)
          return i;
      }
      return npos;
    }
    size_t find(const char* sub, size_t pos = 0) const
    {
      assert(pos < _size);
      const char* p = strstr(_str + pos, sub);
      // 找到了
      if (p)
      {
        return p - _str;
      }
      // 没找到
      return npos;
    }
    string substr(size_t pos = 0, size_t len = npos)
    {
      string sub;
      if (len >= _size - pos)
      {
        for (size_t i = pos; i < _size; ++i)
        {
          sub += _str[i];
        }
      }
      else
      {
        for (size_t i = pos; i < pos + len; ++i)
        {
          sub += _str[i];
        }
      }
      return sub;
    }
    void clear()
    {
      _size = 0;
      _str[_size] = '\0';
    }
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
  public:
    static const int npos;
  };
  // 静态成员变量在类外部定义
  const int string::npos = -1;
  // string类外
  void swap(string& x, string& y)
  {
    x.swap(y);
  }
  bool operator==(const string& s1, const string& s2)
  {
    // strcmp 若是相等则返回0
    int ret = strcmp(s1.c_str(), s2.c_str());
    return ret == 0;
  }
  bool operator<(const string& s1, const string& s2)
  {
    int ret = strcmp(s1.c_str(), s2.c_str());
    return ret < 0;
  }
  bool operator<=(const string& s1, const string& s2)
  {
    return s1 < s2 || s1 == s2;
  }
  bool operator>(const string& s1, const string& s2)
  {
    return !(s1 <= s2);
  }
  bool operator>=(const string& s1, const string& s2)
  {
    return !(s1 < s2);
  }
  bool operator!=(const string& s1, const string& s2)
  {
    return !(s1 == s2);
  }
  ostream& operator<<(ostream& out, const string& s)
  {
    // 没有访问私有成员,不需要友元
    for (auto ch : s)
    {
      out << ch;
    }
    return out;
  }
  istream& operator>>(istream& in, string& s)
  {
    // 需要将其内容清空
    s.clear();
    char ch;
    // cin 读取不到 ' ' 和 '\n'
    ch = in.get();
    // 减少扩容
    char buff[128];
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
      buff[i++] = ch;
      if (i == 127)
      {
        buff[127] = '\0';
        s += buff;
        i = 0;
      }
      ch = in.get();
    }
    if (i > 0)
    {
      buff[i] = '\0';
      s += buff;
    }
    return in;
  }
  istream& getline(istream& in, string& s)
  {
    // 需要将其内容清空
    s.clear();
    char ch;
    // cin 读取不到 ' ' 和 '\n'
    ch = in.get();
    // 减少扩容
    char buff[128];
    size_t i = 0;
    while (ch != '\n')
    {
      buff[i++] = ch;
      if (i == 127)
      {
        buff[127] = '\0';
        s += buff;
        i = 0;
      }
      ch = in.get();
    }
    if (i > 0)
    {
      buff[i] = '\0';
      s += buff;
    }
    return in;
  }
}

test.c源文件

#include"string.h"
using namespace my;
int main()
{
  // 
  return 0;
}

9. string收尾

我们来看看下面一段程序:

#include"string.h"
using namespace my;
int main()
{
  std::string s("11111");
  cout << sizeof(s) << endl;
  return 0;
}

库里的 string 是多少空间呢?

嗯?如果是我们写的 string ,那么应该是 12 啊(32位下,64位是 24),为什么库里的 string 是 28呢?其实是因为编译器做了相关的优化,它在原有的基础上还新增了一个 buff[16] 数组,当数据较小的时候会存到 buff 里,比较大才会存到 _str 在堆上开辟的空间里。因为栈上开辟空间比在堆上开辟空间快。这仅仅是在 vs 编译器下是这样的,其他编译器不一定是这样。

写时拷贝

我们知道,拷贝构造不能是浅拷贝,假如是浅拷贝,那么会导致两个指针指向同一块空间,一个修改另一个也会发生变化,多次析构的问题。所以只能是深拷贝。但是库里的 string 类真的是这样的吗?库里的 string 类的构造函数其实是浅拷贝,但是它有个引用计数,代表有几个对象指向这块空间,如果有对象要修改(写),则重新深拷贝一个空间给其使用,析构的时候则判断引用计数是否为1,不为1则不析构。这样做其实就是赌有的对象不去修改内容,只要有这样的对象,那么就算是一种优化。


未完待续

目录
相关文章
|
2天前
|
C++ 容器
C++字符串string容器(构造、赋值、拼接、查找、替换、比较、存取、插入、删除、子串)
C++字符串string容器(构造、赋值、拼接、查找、替换、比较、存取、插入、删除、子串)
12 1
|
3天前
|
C++
【C++】学习笔记——继承_2
【C++】学习笔记——继承_2
11 1
|
1天前
|
存储 算法 编译器
程序与技术分享:C++模板元编程学习笔记
程序与技术分享:C++模板元编程学习笔记
|
2天前
|
C语言 C++
【c++】string模拟实现(2)
【c++】string模拟实现(2)
6 0
|
2天前
|
算法 C++
【c++】string模拟实现(1)
【c++】string模拟实现(1)
6 0
|
3天前
|
存储 C++ 容器
【C++】学习笔记——map和set
【C++】学习笔记——map和set
7 0
|
3天前
|
C++
【C++】学习笔记——二叉搜索树
【C++】学习笔记——二叉搜索树
6 0
|
3天前
|
编译器 C++
【C++】学习笔记——多态_2
【C++】学习笔记——多态_2
5 0
|
3天前
|
编译器 C++
【C++】学习笔记——多态_1
【C++】学习笔记——多态_1
5 0
|
3天前
|
程序员 编译器 C++
【C++】学习笔记——继承_1
【C++】学习笔记——继承_1
5 0