C++番外篇——string类的实现

简介: C++番外篇——string类的实现

0. 写在前面的话

C++入门7——string类使用说明书中,我们已经了解了string类如何使用的一些情况,string类无非就是存在三个私有变量:

一个是string类的容量:size_t   _capacity;

一个是string类有效字符的长度:size_t    _size;

最后一个是string类的字符数组:char*    _str;

现在我们就依据此来模拟实现一下string类的若干需求。

1. string类构造函数的实现

在实现string类构造函数之前,我们应该明确一个问题:string类是需要进行增删查改的,所以对string类构造函数的无参初始化不能简简单单的就将_size_capacity置为0、将_str只开1字节的空间存'\0',正如下面这个错误示范:

//error:
class
{
public:
    string()
      :_str(new char[1])
      ,_size(0)
      ,_capacity(0)
    {
      _str[0] = '\0';
    }
private:
}

因此我们采用先动态开辟空间,再调用strcpy函数将实参_str拷贝进形参str,以便于后面的增删查改。

1.1 字符串常量初始化

string(const char* str = " ")
    {
      _size = strlen(str);//调用strlen函数计算str的长度
      _capacity = _size;
      _str = new char[_capacity + 1];//_str开空间比_capacity多1是为了存'\0'
      //先动态开辟空间,再将str写进_str,便于后期的增删查改
      strcpy(_str, str);//调用strcpy函数将str拷贝进_str
    }

1.2 拷贝构造

拷贝构造一:

string(const string& s)
    {
      _str = new char[s._capacity + 1];
      strcpy(_str, s._str);
      _size = s._size;
      _capacity = s._capacity;
    }

拷贝构造二:(=运算符重载)

string& operator=(const string& s)
    {
      if (this != &s)
      {
        char* tmp = new char[s._capacity + 1];
        strcpy(tmp, s._str);
        delete[] _str;
 
        _str = tmp;
        _size = s._size;
        _capacity = s._capacity;
      }
      return *this;
    }

2. 析构函数和计算size、capacity简单函数的实现

①析构函数:

~string()
    {
      delete[] _str;
      _str = nullptr;
      _size = 0;
      _capacity = 0;
    }

②计算size:

size_t size() const
    {
      return _size;
    }

③计算capacity:

size_t capacity() const
    {
      return _capacity;
    }

3. c_str函数

首先我们要知道的是:

c_str()就是将C++的string转化为C的字符串数组,c_str()生成一个const char *指针,指向字符串的首地址。

因为在c语言中没有string类型,必须通过string类对象的成员函数 c_str() 把 string 转换成c中的字符串样式。

所以为了省去重载string类的流插入和流提取运算符,我们直接实现c_str函数,进而利用c_str函数实现打印功能。

//将const string类型转化为const char*类型
    const char* c_str() const
    {
      return _str;
    }

4. 访问函数实现

4.1 迭代器iterator

我们知道,迭代器的begin和end是:

begin获取一个有效字符的迭代器 +,end获取最后一个有效字符下一个位置的迭代器

而在C++入门7——string类使用说明书中,我们用到迭代器时:

iterator是一个像指针一样的类型,*it 可以理解为指针的解引用,那么就可以使用解引用来修改字符:

int main()
{
  string s1("hello world!");
    string::iterator it = s1.begin();
  while (it != s1.end())
  {
    cout << *it << endl;
    ++it;
  }
  return 0;
}

依据此我们就可以将string类的iterator理解为指针(当然其底层不完全是指针):

①普通迭代器可读可写:

typedef char* iterator;
 
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }

②const迭代器可读不可写:

typedef const char* const_iterator;
 
    const_iterator begin() const
    {
      return _str;
    }
    const_iterator end() const
    {
      return _str + _size;
    }

4.2 下标+[]访问

①普通函数,可读可写:

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

②const函数,可读不可写:

const char& operator[](size_t pos) const
    {
      assert(pos <= _size);//检查pos是否合法
      return _str[pos];
    }

5. reserve(提前开空间函数实现)

void reserve(size_t n)
    {
      if (n > _capacity)
      {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
      }
    }

6. 插入函数

6.1 尾插

①push_back函数实现(尾插字符):

void push_back(char ch)
    {
      if (_size == _capacity)
      {
        size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
        reserve(newCapacity);
      }
      _str[_size] = ch;
      _size++;
      _str[_size] = '\0';
    }

②append插入实现(尾插字符串):

void append(const char* str)
    {
      size_t len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
      strcpy(_str + _size, str);//将str拷贝进_str的末尾
      _size += len;
    }

6.2 +=运算符重载

①插入字符:

string& operator+=(char ch)
    {
      push_back(ch);//复用push_back
      return *this;
    }

②插入字符串:

string& operator+=(const char* str)
    {
      append(str);//复用append
      return *this;
    }

6.3 insert插入实现

①插入字符:

void insert(size_t pos, char ch)
    {
      assert(pos <= _size);
      if (_size == _capacity)
      {
        size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
        reserve(newCapacity);
      }
      size_t end = _size + 1;
      while (end > pos)
      {
        _str[end] = _str[end - 1];
        --end;
      }
      _str[pos] = ch;
      _size++;
    }

②插入字符串:

void insert(size_t pos, const char* str)
    {
      assert(pos <= _size);
      size_t len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
      int end = _size;
      while (end >= (int)pos)
      {
        _str[end + len] = _str[end];
        --end;
      }
      strncpy(_str + pos, str, len);//用memcpy也可,注意不能再用strcpy,因为strcpy会导致目标缓冲区溢出!
      _size += len;
    }

7.删除函数

7.1 pop_back函数实现(尾删)

void pop_back()
    {
      assert(_size > 0);
      _size--;
      _str[_size] = '\0';
    }

7.2 erase函数实现(删除指定位置)

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

8. 查找函数

①查找字符:

size_t find(char ch, size_t pos = 0)
    {
      for (size_t i = pos; i < _size; i++)
      {
        if (_str[i] = ch)
        {
          return i;
        }
      }
      return npos;
    }

②查找字符串:

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

9.取string子串(substr)函数实现

string substr(size_t pos = 0, size_t len = npos)
    {
      assert(pos < _size);
      size_t end = pos + len;
      if (len == npos || pos + len >= _size)
      {
        end = _size;
      }
      string str;
      str.reserve(end - pos);
      for (size_t i = pos; i < end; i++)
      {
        str += _str[i];
      }
      return str;
    }

10. 修改(swap)

void swap(string& s)
    {
      //调用std库中的swap来实现string的交换函数
      std::swap(_str, s._str);
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }

源代码(string.h):

#pragma once
 
#include <iostream>
#include <assert.h>
 
using namespace std;
 
namespace xxk
{
  class string
  {
  public:
 
    //构造函数实现
 
    //字符串常量初始化
    string(const char* str = " ")
    {
      _size = strlen(str);//调用strlen函数计算str的长度
      _capacity = _size;
      _str = new char[_capacity + 1];//_str开空间比_capacity多1是为了'\0'
      //先动态开辟空间,再将str写进_str,便于后期的增删查改
      strcpy(_str, str);//调用strcpy函数将str拷贝进_str
    }
    //拷贝构造①
    string(const string& s)
    {
      _str = new char[s._capacity + 1];
      strcpy(_str, s._str);
      _size = s._size;
      _capacity = s._capacity;
    }
    //拷贝构造②
    string& operator=(const string& s)
    {
      if (this != &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()
    {
      delete[] _str;
      _str = nullptr;
      _size = 0;
      _capacity = 0;
    }
 
    //计算size函数实现
    size_t size() const
    {
      return _size;
    }
    //计算capacity函数实现
    size_t capacity() const
    {
      return _capacity;
    }
 
    //将const string类型转化为const char*类型
    const char* c_str() const
    {
      return _str;
    }
 
    //访问函数实现
 
    //迭代器访问
    //①普通迭代器
    typedef char* iterator;
 
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }
    //②const迭代器
    typedef const char* const_iterator;
 
    const_iterator begin() const
    {
      return _str;
    }
    const_iterator end() const
    {
      return _str + _size;
    }
 
    //下标+[]访问
    //①const成员函数,可读不可写
    const char& operator[](size_t pos) const
    {
      assert(pos <= _size);//检查pos是否合法
      return _str[pos];
    }
    //②普通成员函数,可读可写
    char& operator[](size_t pos)
    {
      assert(pos <= _size);
      return _str[pos];
    }
 
    //reserve(提前开空间函数实现)
    void reserve(size_t n)
    {
      if (n > _capacity)
      {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
      }
    }
 
    //插入函数
 
    //尾插
    //①push_back函数实现(尾插字符)
    void push_back(char ch)
    {
      if (_size == _capacity)
      {
        size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
        reserve(newCapacity);
      }
      _str[_size] = ch;
      _size++;
      _str[_size] = '\0';
    }
    //②append插入实现(尾插字符串)
    void append(const char* str)
    {
      size_t len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
      strcpy(_str + _size, str);//将str拷贝进_str的末尾
      _size += len;
    }
    //+=插入实现
    //①插入字符
    string& operator+=(char ch)
    {
      push_back(ch);//复用push_back
      return *this;
    }
    //②插入字符串
    string& operator+=(const char* str)
    {
      append(str);//复用append
      return *this;
    }
    //insert插入实现
    //①插入字符
    void insert(size_t pos, char ch)
    {
      assert(pos <= _size);
      if (_size == _capacity)
      {
        size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
        reserve(newCapacity);
      }
      size_t end = _size + 1;
      while (end > pos)
      {
        _str[end] = _str[end - 1];
        --end;
      }
      _str[pos] = ch;
      _size++;
    }
    //②插入字符串
    void insert(size_t pos, const char* str)
    {
      assert(pos <= _size);
      size_t len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
      int end = _size;
      while (end >= (int)pos)
      {
        _str[end + len] = _str[end];
        --end;
      }
      strncpy(_str + pos, str, len);//用memcpy也可,注意不能再用strcpy,因为strcpy会导致目标缓冲区溢出!
      _size += len;
    }
 
    //删除函数
 
    //①pop_back函数实现(尾删)
    void pop_back()
    {
      assert(_size > 0);
      _size--;
      _str[_size] = '\0';
    }
    //erase函数实现(删除指定位置)
    void erase(size_t pos, size_t len = npos)
    {
      assert(pos < _size);
      if (len == npos || pos + len >= _size)
      {
        _str[pos] = '\0';
        _size = pos;
      }
      else
      {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
      }
    }
 
    //string中swap函数的实现
    void swap(string& s)
    {
      //调用std库中的swap来实现string的交换函数
      std::swap(_str, s._str);
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }
 
    //查找函数find函数实现
 
    //①查找字符
    size_t find(char ch, size_t pos = 0)
    {
      for (size_t i = pos; i < _size; i++)
      {
        if (_str[i] = ch)
        {
          return i;
        }
      }
      return npos;
    }
    //②查找字符串
    size_t find(const char* str, size_t pos = 0)
    {
      const char* ptr = strstr(_str + pos, str);
      if (ptr == nullptr)
      {
        return npos;
      }
      else
      {
        return ptr - _str;
      }
    }
 
    //取string子串(substr)函数实现
    string substr(size_t pos = 0, size_t len = npos)
    {
      assert(pos < _size);
      size_t end = pos + len;
      if (len == npos || pos + len >= _size)
      {
        end = _size;
      }
      string str;
      str.reserve(end - pos);
      for (size_t i = pos; i < end; i++)
      {
        str += _str[i];
      }
      return str;
    }
  private:
    size_t _capacity;
    size_t _size;
    char* _str;
    const static size_t npos = -1;
  };
}
相关文章
|
1天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
1天前
|
C++
模拟实现c++中的string
模拟实现c++中的string
|
1月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
68 19
|
1月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
50 13
|
1月前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
50 5
|
1月前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
40 5
|
1月前
|
Serverless 编译器 C++
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
本任务要求设计一个矩形类、圆形类和图形基类,计算并输出相应图形面积。相关知识点包括纯虚函数和抽象类的使用。 **目录:** - 任务描述 - 相关知识 - 纯虚函数 - 特点 - 使用场景 - 作用 - 注意事项 - 相关概念对比 - 抽象类的使用 - 定义与概念 - 使用场景 - 编程要求 - 测试说明 - 通关代码 - 测试结果 **任务概述:** 1. **图形基类(Shape)**:包含纯虚函数 `void PrintArea()`。 2. **矩形类(Rectangle)**:继承 Shape 类,重写 `Print
48 4
|
1月前
|
设计模式 IDE 编译器
【C++面向对象——类的多态性与虚函数】编写教学游戏:认识动物(头歌实践教学平台习题)【合集】
本项目旨在通过C++编程实现一个教学游戏,帮助小朋友认识动物。程序设计了一个动物园场景,包含Dog、Bird和Frog三种动物。每个动物都有move和shout行为,用于展示其特征。游戏随机挑选10个动物,前5个供学习,后5个用于测试。使用虚函数和多态实现不同动物的行为,确保代码灵活扩展。此外,通过typeid获取对象类型,并利用strstr辅助判断类型。相关头文件如&lt;string&gt;、&lt;cstdlib&gt;等确保程序正常运行。最终,根据小朋友的回答计算得分,提供互动学习体验。 - **任务描述**:编写教学游戏,随机挑选10个动物进行展示与测试。 - **类设计**:基类
32 3
|
3月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
139 5
|
3月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
89 2