string类的模拟实现

本文详细介绍了C++中的string类的模拟实现,包括构造函数、拷贝构造函数、析构函数、赋值运算符、增删查改方法、运算符重载以及与输入输出流的交互。展示了如何处理字符串的内存管理、大小调整和操作优化。
摘要由CSDN通过智能技术生成

string类的模拟实现

完整代码:string模拟实现/string模拟实现/string.cpp · swi/c语言 - 码云 - 开源中国 (gitee.com)

class string
{

        friend ostream& operator<<(ostream& _cout, const bit::string& s);

        friend istream& operator>>(istream& _cin, bit::string& s);

public:
        static const int npos;
        typedef char* iterator;
        typedef const char* const_iterator;
       //成员函数

private:
      //成员变量

        char* _str = nullptr;  

        size_t _capacity = 0;  //容量

        size_t _size = 0;  //大小,不含\0

};

const int string::npos = -1;

一、默认成员函数

构造函数

string(const char* str = "")
   :_size(strlen(str))   //初始化列表先初始化了 _size
{
   _capacity = _size;               //初始化容量
   _str = new char[_capacity + 1];  //开空间
   strcpy(_str, str);               //拷贝数据
}

注意:_str  多开了一个空间是用来存放  \0  的,缺省参数处的  ""  表示空串。

拷贝构造函数

(1)传统写法

传统写法就是老老实实自己开空间,拷贝数据。

string(const string& s)
{
    //初始化容量和大小
    _size = s._size;
    _capacity = s._capacity;

    //开空间,拷贝数据
    _str = new char[_capacity + 1];
    strcpy(_str, s._str);
}

(2)现代写法

用传入参数的  _str  作为参数运行构造函数,让  tmp  帮我们开空间、拷贝数据,然后再让tmp和this交换,如此便实现了this的构造,同时在退出拷贝构造时,tmp会自己调用析构函数。

string(const string& s)
{
     //使用默认构造
   string tmp(s._str);
     //交换两者地址(this与tmp)
   swap(tmp);
}

析构函数

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

赋值运算符重载

(1)传统写法

传统写法要注意判断  this != &s  否则  delete [ ]  后就无法进行  strcpy(_str,s._str)  

string& operator=(const string& s)
{
    if(this != &s)
    {
        _size = s._size;
        _capacity = s._capacity;

        //因为是赋值,所以原来的数据就不要了
        delete[] _str;
        _str = new char [_capacity + 1];
        strcpy(_str,s._str);
    }
    return *this;
}

(2)现代写法

现代写法还是让  tmp  帮忙初始化,交换后还能顺便让  tmp  析构不要的数据,且不需要判断  this 等不等于 &s。

string& operator=(const string& s)
{
    string tmp(s._str);
    swap(tmp);
    return *this;
}

二、增删查改

扩容函数

注意,只有  n  大于原来的容量时才扩容,并且扩容不影响大小 _size。

void reserve(size_t n)
{
    if (n > _capacity)
    {
       //更新容量
       _capacity = n;
       
       //开空间,拷贝数据
       char* tmp = new char[n + 1];
       strcpy(tmp, _str);

       //回收空间
       //赋值,使_str指向tmp申请的空间
       delete[] _str;
       _str = tmp;
    }
}

(1)增

指定位置插入一个字符

将数据往后挪动,腾出一个位置插入新字符即可。但写挪动数据时一定要注意细节,若pos = 0时,因为循环条件是(end > pos),此时如果出现end跑到-1的情况那么循环无法停止。因为 pos 和 end 的类型都是size_t(无符号),而 -1 用二进制表示是32位1,所以导致条件恒成立。

//指定位置插入一个字符
void insert(size_t pos, char ch)
{
    assert(pos <= _size);

      // 扩容2倍

    if (_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : 2 * _capacity);
    }


      //挪动数据

    size_t end = _size + 1;
    while (end > pos)
    {
        _str[end] = _str[end - 1];
        --end;
    }


      //赋值

    _str[pos] = ch;
    ++_size;
}

        

 指定位置插入一串字符串

将数据往后挪动,腾出  len  个位置插入新字符串即可。(因为要腾出  len  个位置,所以挪动数据时要注意处理好映射关系)

//指定位置插入一串字符串
string& insert(size_t pos, const char* str)
{
    assert(pos <= _size);
    int len = strlen(str);

    //扩容
    if (_size + len > _capacity)
    {
        reserve(_size + len);
    }

    //挪动数据        
    int end = _size + len;
    while (end > pos + len - 1)
    {
         _str[end] = _str[end - len];
         --end;
    }
           
     
    //拷贝数据 
    strncpy(_str + pos, str, len);
    //更新长度
    _size += len;

    return *this;
}

尾插一个字符串

void append(const char* str)
{
    //复用insert函数
    insert(_size, str);
}



string& operator+=(const char* str)
{
    //insert(_size, str);
    append(str);
    return *this;
}

尾插一个字符

//尾插
void push_back(char c)
{
    //复用insert函数
    insert(_size, c);
}


//重载+=
string& operator+=(char c)
{
   push_back(c);
   return *this;
}

 (2)删

string& erase(size_t pos, size_t len = npos)
{
    assert(_size > pos);
 
    //len为npos 或者 从pos开始加len的长度
    //大于_size就直接删除pos后面所有数据

    if (len == npos || _size - pos <= len)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    { 
        //否则拷贝数据到pos的位置覆盖原数据并更新_size
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
    }

    return *this;
}

(3)查 

在string中查找一个字符(找到返回其下标,反之返回npos)

// 返回c在string中第一次出现的位置

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

        

在string中查找一个字串 (找到返回其首元素下标,反之返回npos)

// 返回子串s在string中第一次出现的位置

size_t find(const char* s, size_t pos = 0) const
{
     assert(pos < _size);
     const char* p = strstr(_str + pos, s);
     if (p)
     {
         return p - _str;
     }
     else
     {
         return npos;
     }
}

(4)改 

返回值的类型为char&,不仅可以减少一次拷贝,还能使其可读可写。

//返回值可读可写
char& operator[](size_t index)
{
    assert(index < _size);
    return _str[index];
}

//返回值仅可读
const char& operator[](size_t index)const
{
    assert(index < _size);
    return _str[index];
}

三、运算符重载

(1)大于、小于、等于......

注意事项:像这类的运算符一般会重载到类外,在类中实现有一个问题,必须以  string类  的对象作为第一个参数。若出现  "abc" == s  这种情况则无法使用。

//下面的运算符重载定义在类外无法直接访问类内的成员变量
//使用c_str()加以辅助(c_str定义在类内)
const char* c_str()const
{
    return _str;
}

//因为其本质是字符串,所以使用strcmp函数进行判断

bool operator==(const string& s1, const string& s2)
{
    return strcmp(s1.c_str(), s2.c_str()) == 0;
}

bool operator<(const string& s1, const string& s2)
{
    return strcmp(s1.c_str(), s2.c_str()) < 0;
}

bool operator>(const string& s1, const string& s2)
{
    return strcmp(s1.c_str(), s2.c_str()) > 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 || s1 > s2;
}

bool operator!=(const string& s1, const string& s2)
{
    return !(s1 == s2);
}

(2)输入流/输出流

      依旧是重载在类外,还是跟上面一样的问题,在类内实现第一个参数必须是string类的对象,使用时要写成:  s << cout  (属于是倒反天罡)。

      输入流中使用缓存数组可以避免频繁开辟和销毁空间,减少消耗。(提前开空间,开多了浪费,少了要频繁申请)

//输出流

ostream& operator<<(ostream& _cout, const bit::string& s)
{
    for (auto ch : s)
    {
        cout << ch;
    }
    return _cout;
}

//输入流
istream& operator>>(istream& _cin, bit::string& s)
{
    s.clear();
    char ch = cin.get();

    //buff 缓存数组,避免频繁扩容(毕竟扩容也是有消耗的)
    char buff[128];

    int i = 0;

    //遇到空格或回车停止
    while (ch != ' ' && ch != '\n')
    {
        buff[i++] = ch;
        ch = cin.get();

       //数组满了就写入一次,并重置i
       if (i == 127)
       {
           buff[127] = '\0';
           i = 0;
          s += buff;
       }
    }

    //判断缓存数组中是否还有未写入的数据
    //有则将buff[i]赋值成\0标记尾部,并向s写入数据
    if (i != 0)
    {
        buff[i] = '\0';
        s += buff;
    }

    return cin;
}

四、其他功能

(1)begin和end函数

返回起始地址和结束地址,以实现迭代器。

/*typedef char* iterator;
  typedef const char* const_iterator;*/

const_iterator begin()const
{
    return _str;
}

const_iterator end()const
{
    return _str + _size;
}

iterator begin()
{
    return _str;
}

iterator end()
{
    return _str + _size;
}

(2)getline

获取带空格字符串。

 写法与输入流基本无差异,循环的判断条件处去掉了不是空格的判断。

istream& getline(istream& in, string& s)
{
    s.clear();
    char ch = cin.get();
    char buff[128];
    int i = 0;
    while (ch != '\n')
    {
        buff[i++] = ch;
        ch = cin.get();
        if (i == 127)
        {
            buff[127] = '\0';
            i = 0;
            s += buff;
        }
    }

    if (i != 0)
    {
        buff[i] = '\0';
        s += buff;
    }

    return cin;
}

(3)substr 

在_str中获取从pos开始到pos+len的子串

string substr(size_t pos = 0, size_t len = npos)
{
    string sub;
    
    //pos+len可能会溢出,所以判断条件改为  len >= _size - pos
    //满足条件则获取pos到结尾的字串
    if (len >= _size - pos)
    {
       for (size_t i = pos; i < _size; i++)
       {
           sub += _str[i];
       }
   }
   //否则获取从pos开始到pos+len的子串
   else
   {
       for (size_t i = pos; i < pos + len; i++)
       {
           sub += _str[i];
       }
   }

   return sub;
}

(4)resize 

改变_str的大小

void resize(size_t n, char c = '\0')
{ 
     //n小于原_size,则删除至n,不缩容
     if (n < _size)
     {
         _size = n;
         _str[_size] = '\0';
     }

     //否则就扩容至n,并且扩容部分赋值成c
     else
     {
         reserve(n);
         for (int i = _size; i < n; i++)
         {
             _str[i] = c;
         }
         _size = n;
         _str[_size] = '\0';
     }
}

(5)部分小功能

//清空(不缩容)
void clear()
{
    _size = 0;
    _str[0] = '\0';
}

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

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

//判断是否为空
bool empty()const
{
    return _size == 0;
}

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值