模拟实现string类

一:四个默认成员函数

1.private 成员

 private:
        char* _str;
        size_t _size;
        size_t _capacity;
        const static size_t npos;
  • _str是存储字符串的指针,_size用来存储当前字符串的结尾位置,_capacity用于存储字符串的容量大小
  • npos的值被定义为-1,又因为npos的类型为size_t,是无符号整型,所以npos实际上的值是size_t类型的最大值4294967295
  • npos做参数时,通常表示字符串的结尾,因为我们认为一个字符串的长度不会超过npos,用做返回值时通常用于查找,表示未找到要查找的值
  • npos的初始化需要在类外,因为npos是静态成员

2.构造函数

 string(const char* str = "")
        {
            //防止传nullptr
            if (str == nullptr) str = "";
            _size = strlen(str);
            _capacity = _size;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }
  • 构造函数简单的来说就是传一个字符串用来初始化string,不过写缺省参数时不能写nullptr
  • 因为strlen计算字符串长度时不能传nullptr,所以缺省参数通常写为“”,表示空串
  • 还需要注意的是,_capacity是指存储有效字符的长度,不包括结尾的'\0',所以给_str分配空间时要比_capacity多一字节

3.析构函数

~string()
        {
            delete[] _str;
            _str = nullptr;
            _size = _capacity = 0;
        }
  • delete 针对nullptr什么也不做,所以可以不用判断_str是否为空

4.拷贝构造

<1> 传统方式

  string(const string& str)//拷贝构造,必须传引用,否则会无限递归
        :_str(new char[strlen(str._str)+1])
        {
           strcpy(_str, str._str);
        }
  • 拷贝构造和构造函数很相似,不过是用一个string对象的_str来构造一个新的string对象的_str,就是一个拷贝的过程,调用库函数即可
  • 需要注意的是:参数必须传引用
  • 如果不传引用,会造成无限递归

<2> 现代方式

void swap(string& str)
        {
            ::swap(_str, str._str);
            ::swap(_size, str._size);
            ::swap(_capacity, str._capacity);
        }
string(const string& str)
            :_str(nullptr)
            , _size(0)
            , _capacity(0)
        {
            string tmp(str._str);
            swap(tmp);
        }
  • 现在我们定义拷贝构造函数时可以不用strcpy了,我们先初始化一个空string对象,然后用构造函数构造一个和需要拷贝构造的string数值相同的string,再把它们交换,就完成了拷贝构造,并且也是深拷贝
  • 出了拷贝构造函数,我们临时构造的对象tmp会自动调用析构函数析构它
  • 这里注意我们需要对_str进行初始化,否则_str为随机值,交换后出了拷贝构造调用析构函数进行delete时会报错,因为此时_str的空间不是动态分配得来的
  • 这里的swap函数可以不自己写,调用库里的,但是库里的效率没有我们自己重新写的效率高,因为我们仅仅是交换,而库里的swap会新建变量进行三次的深拷贝

5.赋值运算符重载(operator=)

<1> 传统方式

 string& operator=(const string& str)
        {
            if (this!=&str)//防止s1=s1
            {
                delete[] _str;
                _str = new char[strlen(str._str) + 1];
                strcpy(_str, str._str);
            }
            return *this;
        }
  • 和拷贝构造不同的是,operator=需要delete释放原空间再申请和str一样大的空间再进行拷贝(因为要保证它们的空间一样大,小了能扩容,大了的话只能重新申请空间,不如直接重新申请空间)
  • 为了防止自己给自己赋值,加上if判断,当this!=&str时再进行赋值

<2> 现代方式

string& operator=(string str)//s1=s1时_str的地址会变
        {
            swap(str);
            return *this;
        }
  • 和拷贝构造一样,operator=也有现在方式,使代码更为简洁
  • 这里的参数是关键,并没有传引用,所以传参时就进行了拷贝构造,之后直接进行swap就完成了赋值
  • 缺点时是无法防止自己给自己赋值

6.迭代器与operator[]

  const char& operator[](int pos) const
  //专门重载用于const成员函数,防止修改_str的值
   {
       assert(pos < _size);
       return _str[pos];
   }
   char& operator[](int pos)
   {
       assert(pos < _size);
       return _str[pos];
   }
  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;
  }
  • 编写operator[]时别忘记加上assert防止越界访问
  • string类的迭代器其实就是char和const char指针的封装

二:reserve与resize

1.reserve函数

void reserve(size_t n)
{
   if (n > _capacity)
    {
       char* tmp = new char[n + 1];
       strncpy(tmp, _str, _size + 1);
       //不能用strcpy,有可能有多的\0没有被拷贝过来,拷贝_size+1个,\0也要拷贝
       delete[] _str;
       _str = tmp;
       _capacity = n;
    }
 }
  • n<_capacity时不扩容
  • 不能使用strcpy进行拷贝,因为string类中间可能含有`\0``,string类的结束标志是pos==_size
  • 所以我们使用strncpy进行拷贝,直接拷贝_size+1个字符,\0也要拷贝

2.resize函数

void resize(size_t n, char val = '\0')//别忘了缺省参数
        {
            if (n < _size)//分成n<_size和n>=size两种情况
            {
                _size = n;
                _str[_size] = '\0';
            }
            else
            {
                if (n > _capacity)
                {
                    reserve(n);
                }
                while (_size < n)
                {
                    _str[_size++] = val;
                }
                _str[_size] = '\0';
            }
        }
  • resize可以分为两种情况
  • (1) n<_size (2)n>=_size
  • 第一种情况比较简单,直接将第n个位置的字符改为\0,再修改_size的值即可
  • 第二种情况就要从_size一直尾插val字符,并在最后以\0结尾

三:插入删除和查找

1.push_back

void push_back(char c)
{
     if (_size == _capacity)
     {
         reserve(_capacity == 0 ? 4 : _capacity * 2);
     }
     _str[_size++] = c;
     _str[_size] = '\0';
 }
  • 每次新增字符前需要检查空间是否足够,不够需要扩容
  • 如果是空串,则分配4个字节的空间,否则增容2倍

2.append

 void append(const char* str)
 {
     int len = _size + strlen(str);
     if (len > _capacity)
     {
         reserve(len);
     }
     strcpy(_str + _size, str);
     _size = len;
 }
  • 和push_back一样,需要检查是否需要扩容

3.operator+=

string& operator+=(char c)
{
     push_back(c);
     return *this;
}
string& operator+=(const char* str)
 {
     append(str);
     return *this;
 }
  • 需要重载两个版本的operator+=,分别对应尾插一个字符和尾插一个字符串
  • 复用push_back和append即可

4.insert

//pos位置之前插入
 string& insert(size_t pos, char ch)
  {
      assert(pos <= _size);//只能在size及之前位置插入
      if (_size == _capacity)
      {
          reserve(_capacity == 0 ? 4 : _capacity * 2);//capacity可能为0
      }
      char* end = _str + _size;
      while (end >= _str + pos)
      {
          *(end + 1) = *end;
          end--;
      }
      _str[pos] = ch;
      _size++;
      return *this;
  }
 string& insert(size_t pos, const char* str)//不加const传常量字符串会有问题
 {
       assert(pos <= _size);
       size_t len = _size+strlen(str);
       if ( len > _capacity)
       {
           reserve(len);
       }
       char* end = _str + _size;
       while (end >= _str + pos)
       {
           *(end + len) = *end;
           end--;
       }
       _size += len;
       strncpy(_str + pos, str, len);
       return *this;
   }
  • 注意点:(1)插入的位置不能超过_size
  • (2)别忘了扩容
  • (3)如果不使用指针进行字符的移动的话,需要从_size+1进行赋值
  • 如果从_size开始,令_str[end+1]=_str[end]的话,循环条件写成while(end>=pos)
  • pos为0时,end最后会变为-1,但end的类型是size_t,会是一个很大的正数,就会导致无限循环
 string& insert(size_t pos, char ch)
        {
            assert(pos <= _size);//只能在size及之前位置插入
            if (_size == _capacity)
            {
                reserve(_capacity == 0 ? 4 : _capacity * 2);//capacity可能为0
            }
             size_t end = _size+1;//将end类型改为int也不行,会发生隐式类型转换
             while (end > pos)
             {
                 _str[end] = _str[end-1];
                 end--;
             }
            _str[pos] = ch;
            _size++;
            return *this;
        }

5.erase

 string& erase(size_t pos = 0, size_t len = npos)//不给参数默认全删
 {
      assert(pos < _size);//_size位置是\0,不能删
      //1.要删的字符数量大于剩余字符
      //2.要删的字符数量小于剩余字符
      size_t leftLen = _size - pos;
      if (len >= leftLen)//pos及以后全删
      {
          _str[pos] = '\0';
          _size = pos;
      }
      else
      {
          strcpy(_str + pos, _str + pos + len);
          _size -= len;
      }
      return *this;
  }
  • 注意点:(1)pos必须小于_size,_size位置是\0,也不能删除
  • (2)需要计算删除的长度和pos后剩余长度的大小,如果要删除的长度大于剩余长度,直接将pos后全部删除,否则只删除len长度的字符,可以通过strcpy来完成字符的移动

6.find

size_t find(char ch, size_t pos = 0)
{
     assert(pos < _size);
     for (int i = pos; i < _size; i++)
     {
         if (_str[i] == ch)
         {
             return i;
         }
     }
     return npos;
 }
        
size_t find(const char* str, size_t pos = 0)
{
    assert(pos < _size);
    const char* ret = strstr(_str + pos, str);
    if (ret)
    {
        return ret - _str;
    }
    return npos;
}
  • find函数也需重载,分为查找一个字符和查找一个字符串
  • 查找字符时遍历查找即可,查找字符串时可以通过调用库函数strstr实现
  • 需要注意的是,find返回的是下标,查找字符串时strstr返回的是指针,需要通过ret-_str计算出下标再返回

四:operator<<与operator>>

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;
     ch = in.get();
     while (ch != ' ' && ch != '\n')
     {
         s += ch;
         ch = in.get();
     }
     return in;
 }
  • 这里的operator<<和operator>>不用写成友元函数,因为并没有直接修改string的内部成员
  • 而是通过string的接口间接修改,比如operator>>就是先清除原字符串,再不断进行+=操作达成输入的目的
  • 输出则是通过范围for()

五:比较大小的运算符重载

	bool operator<(string& s1, string& s2)
    {
        return strcmp(s1.c_str(), s2.c_str()) < 0;
    }
    bool operator==(string& s1, string& s2)
    {
        return strcmp(s1.c_str(), s2.c_str()) == 0;
    }
    bool operator<=(string& s1, string& s2)
    {
        return s1 < s2 || s1 == s2;
    }
    bool operator>(string& s1, string& s2)
    {
        return !(s1 <= s2);
    }
    bool operator>=(string& s1, string& s2)
    {
        return !(s1 < s2);
    }
    bool operator!=(string& s1, string& s2)
    {
        return !(s1 == s2);
    }
  • 在对string类进行排序时需要比较string类的大小,所以我们需要对>,<,==等运算符进行重载
  • 在stl库中这些重载没有写成成员函数,所以编写时也可以写在类外面
  • 可以通过调用strcmp库函数快速实现

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340