【STL模版库】string类:模拟实现string类

news2024/10/7 12:18:00

一、成员变量

private:
	char *_str;
	size_t _size;
	size_t _capacity;
	
public:
	static size_t npos = -1; //编译报错,不能在类中初始化
	const static size_t npos = -1; //[1]

	const char* c_str() const{ //[2]
    return _str;
  	}

 	size_t size() const{
    return _size;
	} 
  
  	size_t capacity() const{
    return _capacity;
  	}

解释:

  • [1] static静态变量需要在类中声明,类外定义
  • [1] 特殊语法:const static成员可以在类中直接定义初始化
  • [1] npos的值是无符号整形的最大值;1.在删除、截取等操作中表示取到字符串末尾;2.find中表示找不到该字符、字符串
  • [2] const修饰成员函数,即修饰this指针。使const对象也可以调用该成员函数。
  • [2] 指针、引用做函数参数,如果不在函数内修改引用对象的值最好要加const修饰。使函数兼容const对象。

二、四个默认成员函数

2.1 构造

  Mystring(const char *str = "") //[1]
  {
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity+1]; //[2]
    strcpy(_str, str);
  }

解释:

  • [1] 空串""后有隐式的 ‘\0’ 结尾,空串做缺省值strlen值为0,开1字符空间用于存储’\0’,符合缺省要求。
  • [2] _capacity表示最多容纳的有效字符个数(不包括’\0’),多开1字符空间用于存储’\0’。

2.2 拷贝构造

  //传统写法:传统做法直接进行数据拷贝。
  Mystring(const Mystring &str){
    _size = str._size;
    _capacity = str._capacity;
    _str = new char[_capacity+1];
    memcpy(_str, str._str, str._size+1); //[1]
  }
  
  //现代写法:(有问题)
  //复用构造函数;在函数内用目标对象的字符串构造临时对象,与调用对象做浅交换,
  //函数返回前析构临时对象(原调用对象数据),而调用对象得到拷贝数据。
  Mystring(const Mystring &str)
    :_str(nullptr), //[2]
    _size(0),
    _capacity(0)
  {
    Mystring tmp(str._str); //[3]
    swap(tmp);
  }
  
  void swap(Mystring &str){
    ::swap(_str, str._str); //[4]
    ::swap(_size, str._size);
    ::swap(_capacity, str._capacity);
  }

解释:

  • [1] 此处不能用strcpy,因为string字符串的拷贝不以’\0’结束。
  • [1] 拷贝字节数为_size+1,包括结尾的’\0’。
  • [2] 必须将_str初始化为nullptr;否则交换后,析构临时对象时会释放随机指针而导致程序崩溃。
  • [3] 这里使用目标对象的字符串构造临时对象的过程中遇到’\0’结束拷贝,实际上是存在问题的。
  • [3] 但这种现代写法用在其他容器的实现中会使代码更加简洁。
  • [4] 这里调用的是库里的全局swap函数,只对指针_str做浅交换。

2.3 operator=

  //传统写法:传统做法直接进行数据拷贝。
  Mystring& operator=(const Mystring &str){
    if(this != &str) //[1]
    {
      char *tmp = new char[str._capacity+1]; //[2]
      memcpy(tmp, str._str, str._size+1);
      
      delete[] _str; //[3]
      _str = tmp;
      _size = str._size;
      _capacity = str._capacity;
    }
    return *this; //[4]
  }
  
  //现代写法1:(有问题)
  //复用构造函数;在函数内用目标对象的字符串构造临时对象,与调用对象做浅交换,
  //函数返回前析构临时对象(原调用对象数据),而调用对象得到拷贝数据。
  Mystring& operator=(const Mystring &str)
  {
    if(this != &str)
    {
      Mystring tmp(str._str); //[5]
      swap(tmp);
    }
    return *this;
  }
  
  //现代写法2:(正确)
  //复用拷贝构造函数;在函数内用目标对象拷贝构造临时对象,与调用对象做浅交换,
  //函数返回前析构临时对象(原调用对象数据),而调用对象得到拷贝数据。
  Mystring& operator=(Mystring str){ //[6]
    swap(str);
    return *this;
  }

解释:

  • [1] 比较两个对象的指针,避免出现自己给自己赋值而出错的情况
  • [2] 先开新空间拷贝数据,再释放旧空间;防止出现开空间失败而造成原数据丢失的情况。
  • [3] 必须释放旧空间,防止内存泄漏;匹配使用new T[N]和delete[]
  • [4] 返回调用对象的引用,使运算符支持连续赋值。
  • [5] 同上,调构造函数遇到’\0’结束拷贝,有问题。
  • [6] 参数是一个string对象,调用函数时先用实参拷贝构造形参。因此现代写法2复用拷贝构造函数,正确。

2.4 析构

  ~Mystring(){
    delete[] _str;
  }

注意:匹配使用new T[N]和delete[],否则程序运行崩溃。


三、访问遍历

3.1 operator[ ]

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

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

解释:

  • 一定要检查pos的范围,防止越界访问。
  • const对象也要提供下标访问方法,const对象返回const引用。

3.2 iterator

  typedef char *iterator; //[1]
  typedef const char *const_iterator; //[2]

  iterator begin(){
    return _str;
  }

  const_iterator begin() const{
    return _str;
  }

  iterator end(){
    return _str+_size; //[3]
  }

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

解释:

  • [1] 对于string类,迭代器实际就是原生指针。
  • [2] const对象也要提供迭代器访问方法,const对象返回const指针。
  • [3] begin()返回首元素的地址;end()返回尾元素下一个位置的地址。

四、容量操作

reserve

  void reserve(size_t newcapacity){
    if(newcapacity > _capacity) //[1]
    {
      char *tmp = new char[newcapacity+1]; //[2]
      memcpy(tmp, _str, _size+1); //[3]
      delete[] _str;
      _str = tmp;
      _capacity = newcapacity;
    }
  }

解释:

  • [1] 只有指定的新容量>旧容量时才会进行扩容
  • [2] 要多开1字符空间,用于存储’\0’;newcapacity的大小不包括’\0’。
  • [3] 不能使用strcpy,string对象不以’\0’为结束标志。拷贝字节数为_size+1,包括结尾的’\0’。

resize

  void resize(size_t newsize, char ch = '\0'){ //[1]
    if(newsize > _size) //[2]
    {
      reserve(newsize); //[3]
      for(size_t i = _size; i<newsize; ++i) //[4]
      {
        _str[i] = ch;
      }
    }
    _str[newsize] = '\0'; //[5]
    _size = newsize;
  }

解释:

  • [1] 第二个参数缺省,默认以’\0’初始化字符串。
  • [2] 只有当newsize>_size时才会可能进行扩容和初始化赋值操作。
  • [3] 只有当newsize>_capacity时才会进行扩容。
  • [4] 遍历从_size开始,保证字符串原有的数据保持不变;新增的字符用指定的字符或’\0’进行初始化。
  • [5] 字符串最后一个位置以’\0’结尾。如果newsize<_size则截断原字符串。

clear

  void clear(){
    _str[0] = '\0';
    _size = 0;
  }

五、增删查改

push_back;

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

解释:

  • [1] 判满扩容
  • [2] 字符串以’\0’结尾,多开的1字符空间用于存储’\0’

append

  void append(const char *str){
  	size_t newsize = _size+strlen(str);
    reserve(newsize);
    strcpy(_str+_size, str);
    _size=newsize;
  }

 void append(const Mystring &str){                                                                                                                                                        
    size_t newsize = _size+str._size;
    reserve(newsize);
    memcpy(_str+_size, str._str, str._size+1);
    _size=newsize;          
  }

  void append(size_t n, char ch){
    reserve(_size+n);
    for(size_t i = 0; i<n; ++i)
    {
      push_back(ch);
    }
  }

解释:

  • 对比尾插常量字符串和string对象可以明显看出二者的区别:常量字符串以’\0’为结束标志;而string字符串以_size为结束标志。

operator+=

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

解释:

  • operator+= 复用 push_back 和 append函数。

insert

  Mystring& insert(size_t pos, char ch){
    assert(pos <= _size); //[1]
    if(_size == _capacity)
    {
      size_t newcapacity = _capacity == 0? 5:_capacity*2;
      reserve(newcapacity);
    }

    size_t end = _size+1; //[2]
    while(end > pos)
    {
      _str[end] = _str[end-1];
      end--;
    }
    _str[pos] = ch;
    ++_size;
    return *this;
  }

  Mystring& insert(size_t pos, const char* str){
    assert(pos <= _size);
    size_t len = strlen(str);
    if(len == 0) //[3]
    {
      return *this;
    }
    reserve(len + _size);
    size_t end = _size+len; //[4]
    while(end >= pos+len)
    {
      _str[end] = _str[end-len];
      end--;
    }
    strncpy(_str+pos,str,len); //[5]
    _size += len;
    return *this;
  }

解释:

  • [1] 检查pos的范围,防止越界。注意:pos==_size表示尾插

  • [2] 从后往前挪数据,包括’\0’;

  • [2] end的移动范围是(pos, size+1] 等于pos结束,不能是[pos,size]小于pos结束

  • [2] 由于pos和end是size_t无符号整形,所以当pos==0时(end<0结束)会造成死循环,越界访问。

  • [3] 如果插入的是空串不做任何处理直接返回。否则会造成资源的浪费,且当pos == 0时程序崩溃。

  • [4] 如图,在字符a后插入b,c,d:
    其中红色表示end的移动范围;蓝色表示待插入的位置;
    在这里插入图片描述

  • [5] 不能使用strcpy,因为strcpy会将结尾的’\0’也拷贝进去。

  • [5] 使用strncpy,从pos位置向后拷贝len个字符。

erase

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

解释:

  • 如果缺省第二个参数或者给定的长度大于字符串剩余长度,默认将字符串截断到pos
  • 起始位置+长度 = 结束位置的下一个位置。

find

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

  size_t find(const char *sub, size_t pos = 0) const{
	assert(sub!=nullptr);
	assert(pos < _size);
    size_t len = strlen(sub);
    for(size_t i = pos; i < _size; ++i)
    {
      if(_str[i] == sub[0])
      {
        size_t j = 1;
        for(; j<len && i+j<_size; ++j) //[3]
        {
          if(_str[i+j] != sub[j])
          {
            break;
          }
        }
        if(j == len) //[4]
        {
          return i;
        }
      }
    }
    return npos;
  }

解释:

  • [1] 从pos位置开始向后查找。如果pos缺省,默认从0开始。
  • [2] 如果找不到,返回npos
  • [3] 内循环的过程中,不仅要保证j在sub的范围内,还要保证i+j在_str的范围内,防止越界访问。
  • [4] 内循环结束后,如果j == len表示sub子串连续完全匹配,即可返回起始位置i,否则++i继续查找。

substr

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

解释:

  • [1] 如果缺省第二个参数或者给定的长度大于字符串剩余长度,默认取到字符串结尾。
  • [1] 万能公式:起始位置+长度 = 结束位置的下一个位置。

六、字符串比较

operator==

 bool operator==(const Mystring &str) const{
    if(_size != str._size)
      return false;
    for(size_t i = 0; i<_size; ++i)
    {
      if(_str[i] != str._str[i])
      {
        return false;
      }
    }
    return true;
  }

operator>

  bool operator>(const Mystring &str) const{
    size_t i = 0;
    while(i < _size && i < str._size)
    {
      if(_str[i] != str._str[i]) //[1]
      {
        return _str[i] > str._str[i];
      }
      ++i;
    }
    if(i < _size) //[2]
    {
      return true;
    }
    else{
      return false;
    }
  }

解释:

  • [1] 字符串比较大小即比较第一个不同字符的ASCII码的大小
  • [2] 如果所含字符全都相同,哪个字符串长哪个就比较大。

其他关系运算符重载

  bool operator!=(const Mystring &str) const{
    return !(*this==str);
  }
  
  bool operator>=(const Mystring &str) const{
    return (*this>str) || (*this==str);
  }

  bool operator<(const Mystring &str) const{
    return !(*this>=str);
  }

  bool operator<=(const Mystring &str) const{
    return !(*this>str);
  }

提示:只需要自己实现出>, == 或 <, ==的运算符重载,其他的关系运算符根据逻辑关系复用即可。


七、输入输出

operator<<

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

注意:string字符串不以’\0’为字符串结束的标志,以_size为结束的标志。

operator>>

//优化前:
istream& operator>>(istream& in, Mystring &str){
  str.clear(); //[1]
  char ch;
  ch = in.get(); //[2]
  while(ch != ' ' && ch != '\n')
  {
    str+=ch;
    ch = in.get();
  }
  return in; //[3]
}

解释:

  • [1] 输入是赋值操作,要覆盖原数据:即清空字符串从头插入字符。
  • [2] 不能使用cint >> ch,因为普通的输入会将空格和换行当做输入分割,不会录入到变量中。
  • [3] 返回标准输入流对象的引用,使符号>>支持连续输入。
//优化后:
istream& operator>>(istream& in, Mystring &str){
  str.clear();
  const size_t N = 32;
  char buff[N+1]; //[1]
  size_t i = 0;
  char ch;
  ch = in.get();
  while(ch != ' ' && ch != '\n')
  {
    buff[i++] = ch; //[2]
    if(i == N)
    {
      buff[i] = '\0'; //最后一个位置存储'\0'
      str+=buff;
      i = 0; //刷新缓冲区,从头开始存储数据
    }
    ch = in.get();
  }
  if(i!=0)
  {
  	buff[i] = '\0'; //[3]
 	str+=buff;
  }
  
  return in;
}

解释:

  • [1] 在栈上开辟可以容纳N个有效字符的缓冲区,多开辟1字符空间用于存储’\0’。
  • [2] 先将输入的字符插入到缓冲区中,待缓冲区满再一次性插入到string对象中,这样可以避免频繁的扩容操作。
  • [3] 如果输入的字符数量不是N的整数倍,就需要在最后将缓冲区中的数据插入到string对象中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/513982.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智慧水务云平台助力“十四五”水安全保障规划!

一、《“十四五”水安全保障规划》 水利部印发《“十四五”水安全保障规划》&#xff0c;规划中指出&#xff0c;“十四五”期间要抓好8个方面重点任务。 一是实施国家节水行动&#xff0c;强化水资源刚性约束。 二是加强重大水资源工程建设&#xff0c;提高水资源优化配置能…

Mongo执行计划explain分析

3.0+的explain有三种模式,分别是:queryPlanner、executionStats、allPlansExecution。现实开发中,常用的是executionStats模式。 1.使用方式 在查询语句后面加上explain("executionStats") db.user.find({"roleCodes":"xsbj","status&…

详细操作Selenium自动化测试之中的断言

Selenium常用的断言包括 页面属性断言&#xff1a;断言标题、url或页面源码中是否包含或不包含特定字符元素存在断言&#xff1a;断言指定元素存在图片及链接断言&#xff1a;断言图片正常显示、链接可以正常打开 页面属性断言 这是最常用的断言方式&#xff0c;可以用来断言…

TTL转HDMI 1.4,性能提升,pin to pin 芯片LT8618SXB

1. 描述 LT8618SX 是 Lontium 的低功耗版本 HDMI 发射器&#xff0c;其基于 ClearEdgeTM 技术。它支持 24 位色深 HDMI 1.4&#xff08;高清多媒体接口&#xff09;规范。它们与 Lontium 的第一代 HDMI 发射器 LT8618EX 完全向后兼容。 LT8618SX 是一款高性能、低功耗器件…

干货分享!9大Python常用技巧!

介绍 Python 炫酷功能&#xff08;例如&#xff0c;变量解包&#xff0c;偏函数&#xff0c;枚举可迭代对象等&#xff09;的文章层出不穷。但是还有很多 Python 的编程小技巧鲜被提及。因此&#xff0c;本文会试着介绍一些其它文章没有提到的小技巧&#xff0c;这些小技巧也是…

csgo搬砖项目,时间自由,项目包下车,包落地

Steam是一款全球较大的综合性数字游戏软件发行平台。steam同时在线飙到3300万&#xff01;超越你说熟悉的王者&#xff0c;吃鸡&#xff01;用户多&#xff0c;竞争者少&#xff0c;连我自己都没想到&#xff0c;有一天我居然可以靠着steam游戏搬砖来赚钱养活自己。 实话实说&a…

计算机基础--->数据结构(1)【图的存储和遍历】

文章目录 图图的存储图的搜索&#xff08;无向无权图&#xff09;代码演示 图 图中包含 顶点、边、度&#xff0c;无向图&#xff0c;有向图&#xff0c;无权图&#xff0c;带权图&#xff0c;其中 度表示一个顶点包含多少条边&#xff0c;有出度和入度。 图的存储 邻接矩阵 代…

【LeetCode】13,罗马数字转整数。 难度等级:简单。知识点:map和unordered_map的区别

文章目录 一、题目二、初级解法&#xff1a;顺序遍历字符串我的解法&#xff08;语法平平无奇&#xff09;语法接近 三、精妙解法&#xff1a;逆序遍历字符串四、知识点&#xff1a;map和unordered_map的区别 LeetCode 第13题&#xff0c;罗马数字转整数&#xff1b;难度等级&a…

大厂视频面试,因为截屏作废

大厂视频面试现在这么严格了么&#xff1f;无意间按到截屏直接显示面试作废&#xff0c;好在最后和HR解释了下&#xff0c;再约时间重新面。 作为一个面试过3、4家大厂&#xff0c;现在在鹅厂工作的过来人来说&#xff0c;上面遇到的这个问题是AI面&#xff0c;不用太担心&…

React项目总结:上一步的终点,下一步的起点

项目简介 本人利用 react18.2 json-server 做了一个后台管理系统。 包含&#xff1a; 用户管理权限管理站内信审核管理站内信发布管理 等内容。 其中涉及到react-router V6.0的使用以及一些权限控制等内容。 更多精彩内容&#xff0c;请微信搜索“前端爱好者“&#xff…

Makefile基础教程(函数的使用)

文章目录 前言一、自定义函数1.使用示例2.注意事项 二、预定义函数1.call函数2.abspath函数 总结 前言 在Makefile中也是存在函数的&#xff0c;在 Makefile 中&#xff0c;可以使用函数调用来处理变量、字符串和路径等操作。那么下面就来看看是如何在makefile中使用函数的吧。…

计算机网络基础(四)—— 什么是TCP/IP协议?是两种网络协议?

文章目录 01 | &#x1f353; 概念 \color{red}{概念} 概念&#x1f353;02 | &#x1f34a; T C P / I P 分层模型 \color{orange}{TCP/IP分层模型} TCP/IP分层模型&#x1f34a;03 | &#x1f34b; 数据链路层协议 \color{yellow}{数据链路层协议} 数据链路层协议&#x1f34…

景区剧本杀小程序

景区剧本杀具有以下几个方面的前景&#xff1a; 景区旅游升级&#xff1a;随着人们对于景区旅游体验的多样化需求增加&#xff0c;景区剧本杀作为一种互动性强、参与感强的旅游体验项目&#xff0c;将会得到越来越多游客的喜爱和关注。 移动互联网应用&#xff1a;景区…

C++ 有元 内部类 匿名对象

有元 使用有元就可以突破封装&#xff0c;可以直接对类当中 私有的 成员 成员函数等等进行访问&#xff0c;在某一次上提供了遍历&#xff0c;但是这增大的 耦合性&#xff0c;破坏了封装&#xff0c;所以建议有元不要多用。 所谓耦合性就是 &#xff0c;某两个 东西的 关系&a…

程序员开发Linux常用命令

本文对程序开发过程中常用的Linux命令进行总结&#xff0c;随时进行补充&#xff0c;属于科普篇&#xff0c;希望对大家有所帮助 file命令 该命令用于查看文件的基本信息&#xff0c;比如编码格式&#xff0c;文件类型等信息&#xff0c;对于可执行程序或者动态链接库文件&am…

【Python入门知识】类和对象,要想学的好基础得打好

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! Python 类/对象 Python 是一种面向对象的编程语言。 Python 中的几乎所有东西都是对象&#xff0c;拥有属性和方法。 类&#xff08;Class&#xff09;类似对象构造函数&#xff0c;或者是用于创建对象的“蓝图”。 创建…

Java 判空的常见方法

一、 对象判空 if (obj ! null) {// 进行对象非空判断 }Object obj null; // 或者 obj new Object(); if (obj null) {// 对象为空 }另外&#xff0c;Guava 库还提供了一个更方便的方法&#xff0c;使用方式如下&#xff1a; import com.google.common.base.Objects;if (O…

SOLIDWORKS 30个实用小技巧

很多人在学习SolidWorks时&#xff0c;会有很多疑问&#xff0c;都不知道如何解答&#xff0c;所以走了很多弯路。今天&#xff0c;我们就来讲讲在学习SolidWorks中的那些小技巧吧&#xff01; 1、SOLIDWORKS技巧之按“空格键&#xff1a;”弹出快捷菜单双击某一视图&#xff0…

webSocket介绍及项目实战【在线聊天系统】

文章目录 一&#xff1a;消息推送常用方式介绍1.1 轮询&#xff1a;浏览器以指定的时间间隔向服务器发出HTTP请求&#xff0c;服务器实时返回数据给浏览器1.2 长轮询&#xff1a;浏览器发出ajax请求&#xff0c;服务器端接收到请求后&#xff0c;会阻塞请求直到有数据或者超时才…

学习之-Spring Cache缓存框架应用本地缓存

此文章用于个人学习记录&#xff0c;原文地址&#xff1a;https://zhuanlan.zhihu.com/p/452315531 如果想了解springCache与redis的交互请看其他文章 缓存是web项目不可或缺的一部分&#xff0c;通过缓存能够降低服务器数据库压力&#xff0c;提高服务器的稳定性及响应速度。…