c++学习之string实现

简介: c++学习之string实现

字符串 - C++引用 (cplusplus.com)这里给出标准官方的string实现,可以看到设计还是较为复杂的,有成员函数,迭代器,修饰符,容量,元素访问,字符串操作等,将字符尽可能的需求都设计出来,我们这里实现string中比较常用且重要的。

成员变量

private:
  char* _str;
  size_t _size;
  size_t _capacity;
public:
  const static size_t npos;//声明一个npos 表示返回找不到的下标 或从-1的下一个开始寻找

1.迭代器实现

这里我们实现了常量迭代器与正常的迭代器,反向迭代器并未实现。对于string这里的迭代器我们可以看到本质上是一个char *类型的指针,在某些地方可能不是指针,且它的用法也是与指针如出一撤。这里对于反向迭代器后面再实现。

    typedef char* iterator;//迭代器
  const typedef 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;
  };

在这里, 我们将char*定义为一个新的类型,并且给出它的成员函数,也就是迭代器的各个位置,从而实现。利用迭代器我们可以对他进行遍历:

Mystring::iterator it = str1.begin();
  while (it != str1.end())
  {
    cout << *it ;
    ++it;
  }
 //范围for
  for (auto tmp : str1)
 {
   cout << tmp ;
 }

同时其实本质上范围for的底层就是利用迭代器实现的,编译器在遇到范围for这样的写法时就会将这一段代码替换成下面迭代器实现的这样的遍历。

2.成员函数

空构造

实际上并不需要实现,我们这里给出它的实现,是了解对于空字符串的构造函数,虽说是空字符串,但不可直接赋值nullptr,否则后面实例化对象调成员函数时就是解引用空指针,空字符串,故这里会报错,实际上实现会给几个字节空间,放\0

Mystring():_str(new char[1]),_capacity(0),_size(0)
  {
  }

有参构造

  //有参构造
  Mystring(const char* str="") : _size(strlen(str)), _capacity(_size)//缺省值直接给"",不给"\0",原先就有\0
  {
    //注意这里的字符串利用深拷贝,且初始化顺序遵循声明顺序
    _str = new char[_capacity + 1];//多开一个给\0
    strcpy(_str, str);
  }

拷贝构造

还是老问题,直接的赋值会造成两次析构,故这里需要进行深拷贝。

//拷贝构造
  //之前讲过,浅拷贝会析构两次,深拷贝解决
  Mystring(const Mystring& s)
  {
    _str = new char[s._capacity + 1];
    memset(_str, 0, s._capacity);
    strcpy(_str, s._str);
    this->_size = s._size;
    this->_capacity = s._capacity;
  }

析构

释放及初始化

//析构
  ~Mystring()
  {
    delete [] _str;
    _str = nullptr;
    _capacity = _size = 0;
  }

重载=

// 赋值重载
  Mystring& operator=(const Mystring& s)
  {
    if (*this != s)
    {
      char* temp = new char[s._capacity+1];
      strcpy(temp, s._str);
      delete []_str;
        _str = temp;
      this->_size = s._size;
      this->_capacity = s._capacity;
    }
    return *this;
  }

这里的赋值重载区分拷贝构造:

用现有对象初始化定义的对象---------拷贝构造

用现有对象赋值给现有对象------------赋值

3.字符串操作

这里实现几个比较重要的,常用的。

c_str

const char* c_str()const
  {
    return _str;
  }

find

这里给出了两种实现,字符寻找与字符串寻找

//寻找某个字符,返回其下标位置
  size_t find(char ch,size_t pos=0)
  {
    for (size_t i = pos; i < _size; i++)
    {
      if (ch == _str[i])
      {
        return i;
      }
    }
    return npos;//找不到,返回-1
  }
  //寻找某个字符串,返回其相同的个数
  size_t find(const char* ch, size_t pos = 0)
  {
    //暴力匹配
    const char* tmp = strstr(_str+pos, ch);
    if (tmp!= nullptr)
    {
      return tmp - _str;//指针相减,找到个数
    }
    else
    {
      return npos;
    }
  }

substr

//从pos位置处取n个字符,或n到 npos
  Mystring substr(size_t pos, size_t len=npos)
  {
    assert(pos < _size);
    Mystring s;
    size_t end = pos + len;
    if (len == pos || len + pos >= _size)
    {
      len = _size - len;
      end = _size;
    }
    s.reserve(len);
    for (size_t i = pos; i <pos+len; i++)
    {
        s += _str[i];//这里利用重载后的+=,本质上就是尾插
    }
    return s;
  }

5.修饰符

push_back

  //尾插字符
  void push_back(char ch)
  {
    //尾插的操作很简单,但是要考虑到扩容的问题
    if (_size == _capacity)
    {
      reserve(_capacity=0?4:_capacity * 2);//为了避免空间是0,扩容失败
    }
    _str[_size] = ch;
    ++_size;
    _str[_size] = '\0';
  }

append

//尾插字符串
  void append(const char*ch)
  {
    size_t num = strlen(ch);
    if (num + _size > _capacity)
    {
      reserve(num + _size);
    }
    strcpy(_str + _size, ch);
    _size += num;
    //注意这里直接运用strcpy拷贝过来,连同\0一起,之后变为新大小
  }

operator+=

这里重载的+=是利用了尾插的实现,故也作为字符串修饰

也是两种实现,+=字符或字符串

Mystring& operator+=(const char ch)
  {
    push_back(ch);
    return *this;
  }
  Mystring& operator+=(const char* ch)
  {
    append(ch);
    return *this;
  }

insert

也是有两种实现方式

注意注释的提示:若这里想要进行头插,此时end>=0。只有当end--到负数才停止,而size_t 无法去判断正负数,程序崩溃,解决方法,将类型转换为int ,或者修改后移顺序,_str[end] = _str[end-1];去掉条件=。

其次就是对于扩容需要考虑空间为0的情况。

//某个位置插入字符
  void insert(size_t pos,char ch)
  {
    assert(pos < _size);
    if (_size == _capacity)
    {
      reserve(_capacity = 0 ? 4 : _capacity * 2);//为了避免空间是0,扩容失败
    }
    int end = _size;
    //pos位置处后移
    while (end>=(int)pos)
    {
      //后移
      _str[end + 1] = _str[end];
      --end;
    }
    //插入
    _str[pos] = ch;
  }
  //某个位置插入字符串
  void insert(size_t pos,char *ch)
  {
    size_t len = strlen(ch);
    if (_size +len>_capacity)
    {
      reserve(_capacity = 0 ? 4 : _capacity * 2);//为了避免空间是0,扩容失败
    }
    size_t end = _size;
    //pos位置处后移
    while (end >= pos)
    {
      //后移
      _str[end + len] = _str[end];
      --end;
    }
    //插入
    strncpy(_str+pos, ch,len);
    _size += len;
  }

erase

删除pos位置后的npos个字符,这里的npos给的是缺省值,再没参数的情况下为-1,也就是pos后的全部删除。

//删除
  void erase(size_t pos,size_t len= npos)
  {
    assert(pos <= _size);
    if (len == npos || pos + len >= _size)
    {
      _str[pos] = '\0';
      _size = pos;
    }
    else
    {
      size_t begin = pos + len;
      while (begin <= _size)
      {
        _str[begin- len] = _str[begin ];
        ++begin;
      }
      _size -= len;
    }
  }

6.容量

reserve

//扩容
  void reserve(size_t n)
  {
    if (n > _size)
    {
      char* tmp = new char[n + 1];//这里还是留一个\0位置
      strcpy(tmp, _str);
      delete[]_str;
      _str = tmp;
      _capacity = n;
    }
  }

size

//返回大小
size_t size()const
  {
    return _size;
  }

capacity

//返回容量
size_t capacity()const
  {
    return _capacity;
  }

resize

//初始化大小为n,且可以赋值之后的
  void resize(size_t n,char c='\0')
  {
    if (n <= _size)
    {
      //删除
      _str[n] = '\0';
      _size = n;
    }
    else
    {
      //扩容
      reserve(n);
      //初始化值
      while (_size < n)
      {
        _str[_size] = c;
        _size++;
      }
      //末尾给\0
      _str[_size] = '\0';
    }
  }

7.元素访问

重载下标引用操作符

const char& operator[](int pos)const
  {
    assert(pos < _size);
    return _str[pos];
  }

8.运算符重载

对于这里的比较运算符,通过实现<与==,再取反实现> ,>=,!=.

重载<

bool operator<(const Mystring &s1)
  {
    return strcmp(this->_str, s1._str) <0;
  }

重载==

bool operator==(const Mystring& s)
  {
    return strcmp(this->_str, s._str) == 0;
  }

重载<=

bool operator<=(const Mystring& s)
  {
    return *this < s || *this == s;//由于<与=已实现,直接用
  }

重载>

bool operator>(const Mystring& s)
  {
    return !( *this <=s) ;//取反
  }

重载>=

bool operator>=(const Mystring& s)
  {
    return !(*this < s);//取反
  }

重载!=

bool operator!=(const Mystring& s)
  {
    return!(*this==s);
  }

重载输入流

//流插入
istream& operator>>(istream& in, Mystring& s)
{
  char ch;
  ch = in.get();//一个个拿字符,包括空格与\n
  while (ch != ' ' && ch != '\n')
  {
    s += ch;
    ch = in.get();
  }
  return in;
}

重载输出流

//流提取
ostream& operator<<(ostream& out, const Mystring& s)
{
  for (size_t i = 0; i < s.size(); i++)
  {
    out << s[i];
  }
  return out;
}


相关文章
|
6天前
|
算法 网络安全 区块链
2023/11/10学习记录-C/C++对称分组加密DES
本文介绍了对称分组加密的常见算法(如DES、3DES、AES和国密SM4)及其应用场景,包括文件和视频加密、比特币私钥加密、消息和配置项加密及SSL通信加密。文章还详细展示了如何使用异或实现一个简易的对称加密算法,并通过示例代码演示了DES算法在ECB和CBC模式下的加密和解密过程,以及如何封装DES实现CBC和ECB的PKCS7Padding分块填充。
27 4
2023/11/10学习记录-C/C++对称分组加密DES
|
26天前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
55 5
|
26天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
42 2
|
2月前
|
C++ 容器
|
2月前
|
编译器 C语言 C++
配置C++的学习环境
【10月更文挑战第18天】如果想要学习C++语言,那就需要配置必要的环境和相关的软件,才可以帮助自己更好的掌握语法知识。 一、本地环境设置 如果您想要设置 C++ 语言环境,您需要确保电脑上有以下两款可用的软件,文本编辑器和 C++ 编译器。 二、文本编辑器 通过编辑器创建的文件通常称为源文件,源文件包含程序源代码。 C++ 程序的源文件通常使用扩展名 .cpp、.cp 或 .c。 在开始编程之前,请确保您有一个文本编辑器,且有足够的经验来编写一个计算机程序,然后把它保存在一个文件中,编译并执行它。 Visual Studio Code:虽然它是一个通用的文本编辑器,但它有很多插
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
26 1
|
2月前
|
C++ 容器
|
2月前
|
C++ 容器
|
2月前
|
存储 C++ 容器
|
2月前
|
Java 编译器 C++
c++学习,和友元函数
本文讨论了C++中的友元函数、继承规则、运算符重载以及内存管理的重要性,并提到了指针在C++中的强大功能和使用时需要注意的问题。
28 1