当前位置: 首页 > ds >正文

C++初阶-string类的模拟实现1

1.模拟实现string类的原因

在面试中,面试官总喜欢让学生自己来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。而且我们之后需要用到string类的地方还是比较多的,所以这里就相当于更加深入的了解它如何实现的了,总之是有益无害的!

2.string类模拟实现注意的点

在模拟实现时,我们需要学习一部分底层,只是把比较重要的函数实现了其他不重要的函数就不做过多讲解了,函数实现的时候可能顺序并不是按照C++官网的分类来的了。

由于string类可能会有命名冲突问题,我们需要把string类的模拟实现和测试函数都放到一个命名空间里面,而且我们可能不能把这个std的命名空间展开,之后我们需要比较二者的不同之处的。

//.h文件
namespace td
{class string{private:char* _str;size_t _size;size_t _capacity;};
}

这是基本结构了(少了npos和之前额外补充的buff,之后我们都会围绕这个结构来进行讲解,当然这个是放到.h文件中的,因为之后我们实现的时候也需要放到两个不同的文件中,所以可能需要声明在.h文件中,而定义在.cpp文件中,但是有些比较短小的函数我们可以在.h文件中定义,使它成为内联函数。

3.string::string(const char* s)的模拟实现

3.1初始版本

我们用初始化列表来进行初始化,并且我们需要用strlen函数来计算它的字符个数。由于还要申请空间,所以我们还需要用new函数来申请内存空间,并把所有的数据拷贝到_str里面去,所以得到了这个:

//.h
#include<string.h>
namespace td
{class string{public:string(const char* str = " ");private:char* _str;size_t _size;size_t _capacity;};
}
//.cpp
#include"test.h"
#define _CRT_SECURE_NO_WARNINGS 1
namespace td
{string::string(const char* str):_str(new char[strlen(str)+1]),_size(strlen(str)),_capacity(strlen(str)){strcpy(_str, str);}
}

如果按照声明顺序进行初始化的情况下,那么这个代码是没有任何问题的,但是这样写起来很麻烦,因为总是用strlen(str)函数,我们知道这个函数的时间复杂度是O(n)的,很影响效率,所以我们要用_size或者_capacity进行初始化。

3.2进阶版本

在.h文件中_size要在第一个位置声明,其他的随意,则:

//.cpp
#include"test.h"
#define _CRT_SECURE_NO_WARNINGS 1
namespace td
{string::string(const char* str):_str(new char[_size+1]),_size(strlen(str)),_capacity(_size){strcpy(_str, str);}
}

但是这样的方式会有问题:

所以我们如果这样写就会导致这个运行出错,至于原因可能是声明的顺序必须是开始的顺序那样,但是那样会导致在_str定义时,而_size未初始化,可能会导致用这个函数时崩溃(因为随机值的话如果空间没有开够,就会越界)。那么如何解决呢?

3.3最终版本

//.cppstring::string(const char* str):_size(strlen(str)){_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);}

我们可以声明的时候和开始版本一样,但是我们不在初始化列表里面初始化,我们直接在函数体里面初始化,这样就不会报错了。之前我们是建议在初始化列表初始化,但是这只是一个建议,我们在函数体内初始化一些就不会有这么多事情了!

4.string::c_str()的模拟实现

这个比较简单,我们直接在那个.h文件中实现这个函数就可以了:

//.h
const char* c_str() const
{return _str;
}

5.string::string()的模拟实现

5.1初始版本

//.cppstring::string():_str(nullptr),_size(0),_capacity(0){}

我们需要把之前的那个默认构造函数注释掉,因为不能有多个默认构造函数(在using namespace std的情况下),然后测试如下代码:

//.cppvoid  test1() {string s2;cout << s2.c_str() << endl;}

这个不是放在成员函数里面的,而是只在命名空间td里面的,因为如果用在主函数里面那么就还要那个指定命名空间,所以这样更好一些,则运行结果为:

这样是因为它崩溃了。

为什么会出现这种情况?

cout打印char*的时候比较特殊,因为char*被认为是字符串,所以这样打印出来的结果不是指针类型,而是按字符串打印。但是这样打印就会直接对char*的指针进行解引用,直到遇到'\0',但我们在实例化对象时,这个是空指针(_str),空指针解引用会崩溃。

但是如果我们改为如下形式:

//.cpp
void test1()
{std::string s2;cout << s2.std::string::c_str() << endl;
}

那么运行结果如下:

我们发现如果用库里面的函数就不会崩溃,之前我们如果不指定类域默认用的是本类域里面的函数,所以会崩溃。因为库里面不是用nullptr去初始化_str,而是开了一个字符大小的空间(一个字节)。

5.2最终版本

//.cpp
string::string():_str(new char[1]{'\0'}),_size(0),_capacity(0)
{}

那么运行结果如下:

当然这是在已经写了析构函数的情况下运行的结果,之前都没有写析构函数,所以运行出来就会报错。

6.string::~string()的模拟实现

这个函数很简单,只要先释放内存后再把_str置为nullptr且_size,_capacity都置为0即可,代码如下:

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

用之前的测试代码,调试有:

运行结果是没有问题的。

但是之后我会把这个string()的构造函数直接删掉,因为之后用的也不多,就用const char*的即可。

7.总结

这里就先把那些比较重要的构造函数(除拷贝构造外)已经讲了,下一节将进行讲解:string类的增删查改的模拟实现,那些才是比较重要的部分,理解起来也比较难,所以下一节需要重点关注,喜欢的可以一键三连哦!

 

http://www.xdnf.cn/news/5609.html

相关文章:

  • C++题题题题题题题题题踢踢踢
  • 《Go小技巧易错点100例》第三十二篇
  • Redis 缓存
  • C 语言数据结构基石:揭开数组名的面纱与计算数组大小
  • AQS(AbstractQueuedSynchronizer)解析
  • m1 安装 Elasticsearch、ik、kibana
  • 树莓派5+UPS电源 5v
  • 快速搭建一个vue前端工程
  • 大疆卓驭嵌入式面经及参考答案
  • 理解微积分 | 概念 / 定义 / 性质 / 关系
  • Kafka的基本概念和Dokcer中部署Kafka
  • 从0开始学linux韦东山教程第三章问题小结(3)
  • Python-3.14.0|Win英文|python编译器|安装教程
  • NoSQL数据库技术与应用复习总结【看到最后】
  • 第四节第一部分:继承,使用继承的好处
  • Web开发—Vue工程化
  • Redis设计与实现——数据结构与对象
  • 【iOS】SDWebImage源码学习
  • 深入理解反序列化攻击:原理、示例与利用工具实战
  • 计算机网络——以太网交换机
  • .Net HttpClient 发送Http请求
  • PyTorch:深度学习的 powerful 库
  • Spyglass:在batch/shell模式下运行目标的顶层是什么?
  • 理想闯入智驾“无人区”
  • 湖北理元理律师事务所债务优化体系拆解:科学规划如何实现“还款不降质”
  • Lua再学习
  • 拓扑学在天体物理学的应用:python 示例
  • HTTP 响应状态码总结
  • k8s的节点是否能直接 curl Service 名称
  • I2C通讯