C++初阶:string类的模拟自实现

目录

  • 1. 引子
  • 2. 自实现string类功能模块
  • 3. string类功能模块的具体实现
    • 3.1 默认成员函数
    • 3.2 遍历访问相关成员函数
    • 3.3 信息插入相关成员函数
    • 3.4 信息删除
    • 3.5 信息查找
    • 3.6 非成员函数
    • 3.7 杂项成员函数
  • 4. 补充知识

1. 引子

  1. 通过对string类的初步学习,没有对知识进行较深度学习了解剖析,只是囫囵吞枣地学会其的使用方式。
  2. 仅仅掌握string类的使用方式,对我们熟练掌握使用STL的相关容器及其背后的深层知识是远远不够的。
  3. 因此,我们来通过模拟尝试自实现的方式,对string类背后的知识与编程思想做一个更深入的学习掌握。

2. 自实现string类功能模块

  1. 默认成员函数:构造,拷贝构造,析构,operator=运算符重载
  2. 杂类成员函数:size,c_str,swap,substr
  3. 遍历访问相关成员函数:operator[],const_operator[],iterator(迭代器)
  4. 信息插入,扩容相关成员函数:append,push_back,operator+=,reserve,insert
  5. 信息删除相关成员函数:erase
  6. 信息查找相关成员函数:find
  7. 非成员函数:operator+,<<,>>

3. string类功能模块的具体实现

3.1 默认成员函数

string类的结构,成员变量

class string
{
private://指向存储数据地址的指针char* _str;//能够存储有效字符的容量int _capacity;//有效字符的长度int _size;//正常定义方式:类内声明,类外声明类域定义初始化//特殊定义方式const static size_t npos = -1;
};

1. 构造函数

  1. 字符串构造
    <1> 使用初始化列表的构造方式,初始化容量与size都需要使用strlen函数计算一遍字符串长度
    <2> strlen计算字符串长度的方式为暴力遍历法,多次调用会使得效率低下
    <3> 先用strlen初始化capacity,再使用capacity初始化其他两个成员变量的方式,会使得成员变量的定义方式变得死板
//字符串构造
string(const char* str)
{//开辟空间int len = strlen(str);_str = new char[len + 1];//拷贝strcpy(_str, str);//处理尾部_str[len] = '\0';_capacity = len;_size = len;
}
  1. 无参构造
    <1> 无参构造时,初始化str不能使用空指针,当我们使用流插入操作符对str的内容进行打印时,cout会将其自动默认为字符串进行打印,对其进行解引用,而对空指针进行解引用会出现bug
    <2> 无参构造时,不意味着指针为空,无参构造时只是有效字符的长度为0,因,我们可以在空间中存储一个’\0’字符进行标识
//无参构造,空字符串
string()
{_str = new char[1]{ '\0' };_capacity = 0;_size = 0;
}

2. 析构函数

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

3. 拷贝构造函数

//string(const string& str)
//{
//	_str = new char[str._capacity + 1];
//	strncpy(_str, str._str, str._size);
//	
//	_size = str._size;
//	_capacity = str._capacity;
//
//	_str[_size] = '\0';
//}string::string(const string& str)
{//现代写法string tmp(str._str);swap(tmp);
}

4. operator=运算符重载

//string& string::operator=(const string& str)
//{
//	//优化:
//	//参数变为string
//	//再将拷贝形参与当前string进行交换//	char* tmp = new char[str._size + 1];
//	strcpy(tmp, str._str);//	_size = str._size;
//	_capacity = str._capacity;//	delete[] _str;
//	_str = tmp;//	return *this;
//}string& string::operator=(string str)
{//现代写法swap(str);return *this;
}

3.2 遍历访问相关成员函数

1. operator[]

  1. operator[]的访问方式需要支持普通string类与const修饰的string类的调用,因此,要进行重载两次
//普通string类调用
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}
//const修饰的string类调用
const char& operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}

2. iterator

typedef char* iterator;iterator begin()
{return _str;
}iterator end()
{//最后一个字符后一位return _str + _size;
}

3. const修饰的iterator

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

4. 范围for的访问方式

  1. 此遍历方式的底层实现为,将迭代器的遍历方式进行替换,当迭代器线管函数的名与库中不同时,范围for的遍历方式也不能正常使用(err:Begin())
for(auto e : s)
{cout << e;
}
cout << endl;

3.3 信息插入相关成员函数

1. 扩容成员函数reserve

  1. 将string类的空间容量进行调整,当指定参数大于当前空间容量时进行扩容,小于时不会进行缩容
//插入相关
void reserve(int size)
{if (size > _capacity  ){//申请一段额外的空间//失败抛异常char* tmp = new char[size + 1];//将值进行拷贝strncpy(tmp, _str, _size);//销毁原空间delete[] _str;//调整_str = tmp;_capacity = size;}
}

2. push_back

void push_back(const char& c)
{if (_capacity == _size){reserve(_size + 1);_str[_size] = c;_size++;_str[_size] = '\0';}
}

3. append

  1. 拼接一串字符串
void append(const char* str)
{int len = strlen(str);if (len + _size > _capacity){reserve(len + _size);}strncpy(_str + _size, str, len);_size += len;_str[_size] = '\0';
}
  1. 拼接参数string类的内容
void append(const string& s)
{int len = s.size();if (len + _size > _capacity){reserve(len + _size);}strncpy(_str + _size, s.c_str(), len);_size += len;_str[_size] = '\0';
}

4. insert

  1. 给指定位置插入一个字符
//在pos位置插入一个字符
void insert(size_t pos, const char& c)
{assert(pos < _size);if (_size == _capacity){reserve(_size + 1);}//当pos为0时int end = _size;//pos类型为size_t,比较时,end隐式类型转换为size_t类型//当插入位置为0时,会出现bugwhile (end >= (int)pos){_str[end + 1] = _str[end];end--;}_str[pos] = c;_size++;_str[_size] = '\0';
}
  1. 给指定位置插入一串字符串
//在pos位置插入一串字符串
void insert(size_t pos, const char* str)
{assert(pos < _size);int len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}//当pos为0时int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];end--;}strncpy(_str + pos, str, len);_size += len;_str[_size] = '\0';
}

5. operator+=

  1. 在当前string类后拼接参数string类的内容
string& operator+=(const string& str)
{if (_size + str._size > _capacity){reserve(_size + str._size + 1);}strncpy(_str + _size, str._str, str._size);_size += str._size;_str[_size] = '\0';return *this;
}
  1. 在当前string类后拼接参数字符串
string& string::operator+=(const char* str)
{append(str);return *this;
}
  1. 拼接字符(复用)
string& string::operator+=(const char c)
{push_back(c);return *this;
}

3.4 信息删除

1. erase

  1. 从指定下标开始,向后删除指定个元素
//在pos位置插入一串字符串
void string::erase(size_t pos, size_t len)
{assert(pos < _size);//直接添加'\0'if (len == npos || pos + len > _size){_str[pos] = '\0';_size = pos;}else{//连'\0'一起拷贝strcpy(_str + pos, _str + pos + len);_size -= len;}
}

3.5 信息查找

1. find

  1. 从指定位置开始向后查找指定的字符
size_t find(const char& c, size_t pos = 0)
{assert(pos < _size);int i = pos;while (i < _size){if (_str[i] == c){return i;}i++;}return npos;
}
  1. 从指定位置开始,向后查找指定字符串
//查找字符串
size_t find(const char* str, size_t pos = 0)
{assert(pos < _size);char* cur = strstr(_str + pos, str);if (cur == nullptr){return npos;}return cur - _str;
}

3.6 非成员函数

1. operator<<

  1. 流插入操作符重载,非成员函数,非友元,非静态成员函数
  2. 间接访问方式,不直接访问成员变量
ostream& operator<<(ostream& out, const string& str)
{//将每一个字符的拷贝插入到流中for (auto e : str){out << e;}return out;
}

2. operator>>

  1. 流提取操作重载
istream& operator>>(istream& in, string& str)
{str.clear();char data[128] = "";//从流中获取字符char ch = in.get();int i = 0;while (ch != ' ' && ch != '\n'){data[i++] = ch;//溢出,截断if (i == 127){data[i] = '\0';str += data;break;}ch = in.get();}if (i < 127){data[i] = '\0';str += data;}return in;
}

3. operator+

  1. 返回一个临时的string类,其内容为当前string类与参数字符串拼接
string operator+(const string& str1, const string& str2)
{string tmp(str1);return tmp += str2;
}string operator+(const string& str1, const char* str2)
{string tmp(str1);return tmp += str2;
}

4. swap

  1. 非成员函数swap,交换两个string类,优先级高于算法库中swap模板
void swap(string& str1, string& str2)
{//会生成中间变量string tmp = str1;str1 = str2;str2 = tmp;
}

3.7 杂项成员函数

1. c_str

  1. 以C语言类型的字符串方式返回string类的内容
const char* c_str() const
{return _str;
}

2. substr

  1. 返回内容为当前string类子串的string类
string string::substr(size_t pos, size_t len)
{assert(pos < _size);string tmp;int end = pos + len;if (pos + len > _size || len == npos){end = _size;}//先预先扩容reserve(len + 1);for (int i = pos; i < end; i++){tmp += _str[i];}return tmp;
}

3. swap

  1. 成员函数swap,交换两者数据指针,效率高
void swap(string& str)
{//直接调用库函数std::swap(_str, str._str);std::swap(_capacity, str._capacity);std::swap(_size, str._size);
}

4. 补充知识

  1. Linux下g++编译器对于string类的实现
    <1>string类的结构
    <2>拷贝构造方式:浅拷贝 + 引用计数 + 写时拷贝

1. Linux环境g++编译器string类的结构

  1. string类的成员函数只有一个指针(8字节),指针指向的结构,如下图:

在这里插入图片描述
2. 引用计数与浅拷贝

  1. <1> 当我们进行拷贝构造时,先只进行浅拷贝,使得拷贝而得的新对象的数据指针与原对象指向同一块数据空间
    <2> 当新创建的对象的被调用数据需要修改时再进行拷贝,而后修改(写实拷贝)
    <3> 当一块数据空间的引用计数清零时,才会进行指针的销毁
    <4> 这样的拷贝方式,如果拷贝构造得到对象不被修改时会提高效率,较少开销

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

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

相关文章

MRP(VBA系列):6.SIOP:将PO的信息加入到Net后面

这个比较简单&#xff0c;就是将两个表格的信息组合起来。 Tips&#xff1a;所有代码都是为目前任职公司编写&#xff0c;极大概率不适合其他公司&#xff0c;在这里发布&#xff1a;首先是记录&#xff1b;其次才是分享&#xff0c;望理解&#xff01; 效果图&#xff1a; 思…

聚合音乐网-播放器网站源码

源码简介 MKOnlineMusicPlayer 是一款全屏的音乐播放器 UI 框架&#xff08;为避免侵权&#xff0c;已移除所有后端功能&#xff09;。 前端界面参照 QQ 音乐网页版进行布局&#xff0c;同时采用了流行的响应式设计&#xff0c;无论是在PC端还是在手机端&#xff0c;均能给您…

初识 linux

什么是linux Linux&#xff0c;一般指GNU/Linux&#xff08;单独的Linux内核并不可直接使用&#xff0c;一般搭配GNU套件&#xff0c;故得此称呼&#xff09;&#xff0c;是一种免费使用和自由传播的类UNIX操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦兹&#xff08;Linu…

【Linux】shell命令运行原理---认识Linux基本指令

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;Linux_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.shell命令以及运行原理 1.1 shell命令 1.2 Linux内核权限 1.3 图示Linux shell和bash的区别 2.认识Linux基本指令 2.1 指令的…

寻找可能认识的人

给一个命名为&#xff1a;friend.txt的文件 其中每一行中给出两个名字&#xff0c;中间用空格分开。&#xff08;下图为文件内容&#xff09; 题目&#xff1a;《查找出可能认识的人 》 代码如下&#xff1a; RelationMapper&#xff1a; package com.fesco.friend;import or…

【测试开发学习历程】MySQL条件查询与通配符 + MySQL函数运算(上)

前言&#xff1a; 18日08&#xff1a;56&#xff0c;总要先写完明天的博客&#xff0c;才能安心准备今天或者明天的学习。 半夜爬起来写博客真的好辛苦&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 回归…

AI - 集成学习

目录 集成学习概念 集成学习器性能评估 随机森林 AdaBoost &#x1f606;&#x1f606;&#x1f606;感谢大家的阅读&#x1f606;&#x1f606;&#x1f606; 集成学习概念 &#x1f48e;集成学习是机器学习中的一种思想&#xff0c;它通过多个模型的组合形成一个精度…

BUUCTF-----[CISCN 2019 初赛]Love Math

<?php error_reporting(0); //听说你很喜欢数学&#xff0c;不知道你是否爱它胜过爱flag if(!isset($_GET[c])){show_source(__FILE__); }else{//例子 c20-1$content $_GET[c];if (strlen($content) > 80) {die("太长了不会算");}$blacklist [ , \t, \r, \n…

由于找不到kvpvbsext64.dll,无法继续执行代码。解决办法,

kvpvbsext64.dll 是一个动态链接库文件&#xff0c;通常作为某个软件的一部分存在。具体来说&#xff0c;它可能为某个程序的特定功能提供支持&#xff0c;在软件运行时被调用和使用。因此&#xff0c;当出现与该文件相关的错误时&#xff0c;可能会影响到相应软件的正常运行。…

k8s集群部署elk

一、前言 本次部署elk所有的服务都部署在k8s集群中&#xff0c;服务包含filebeat、logstash、elasticsearch、kibana&#xff0c;其中elasticsearch使用集群的方式部署&#xff0c;所有服务都是用7.17.10版本 二、部署 部署elasticsearch集群 部署elasticsearch集群需要先优化…

【ZooKeeper】1、基本介绍

本文基于 Apache ZooKeeper Release 3.7.0 版本书写 作于 2022年3月6日 14:22:11 转载请声明 1、Zookeeper是什么&#xff1f; 由ZooKeeper的官网介绍可知&#xff1a; ZooKeeper 是Apache原子基金会下一个开源的、用于提供可靠的分布式协同的服务器。 ZooKeeper 可以用来 配置…

此站点的连接不安全,怎么解决?

有部分的网站用户在打开的时候会被提示“此站点的连接不安全”这种现象为什么会出现&#xff0c;大概率是因为没有安装SSL证书或者SSL证书出现了错误&#xff0c;小编在这里面将展开讲解为大家分析其中的原因以及解决方法。 一&#xff1a;遇到该情况的时候该怎么办&#xff1…

7-LINUX--库文件的生成与使用

一.什么是库文件 库是一组预先编译好的方法的集合。Linux系统存储的库的位置一般在&#xff1a;/lib 和 /usr/lib。 在 64 位的系统上有些库也可能被存储在/usr/lib64 下。库的头文件一般会被存储在 /usr/include 下或其子目录下。 库有两种&#xff0c;一种是静态库&#x…

计算机网络——物理层(物理传输介质和物理层的设备)

计算机网络——物理层&#xff08;物理传输介质和物理层的设备 物理传输介质导向性传输介质双绞线同轴电缆光纤 非导向性传输介质无线电波多径效应 微波地面微波通信ISM 频段 卫星通信 物理层设备中继器集线器中继器和集线器的区别 我们今天进入物理层的物理传输介质和物理层的…

AI将如何影响我们的生活?

1. AI 会如何影响你的生活 通用聊天场景&#xff1a;也即 ChatGPT 本身&#xff0c;或者用 gpt-3.5 的 api 实现的各类网站或小程序。他们没有明确的问题场景&#xff0c;但反而可以解决非常多的问题&#xff0c;比如搜索一些常见问题的答案、编个笑话等&#xff0c;可以当个搜…

linux常用命令指南

什么是Linux命令&#xff1f; Linux命令是在Linux操作系统中用于执行特定任务的命令行工具。它们被用于管理文件和目录、执行程序、配置系统设置等。Linux命令通常由一个命令名称和一些选项或参数组成&#xff0c;并且可以通过命令行界面&#xff08;CLI&#xff09;或脚本文件…

图片上传语法

图片上传 步骤 <!-- 文件选择元素 --><input type"file" class"upload"><br><!-- 上传的图片出于安全不能使用url使用&#xff0c;智能做背景使用 --><img src"" alt""><script src"https://c…

图论02-并查集的实现(Java)

2.并查集理论基础 并查集的作用 将两个元素添加到一个集合中。 判断两个元素在不在同一个集合并查集的实现 1.DSU 类定义&#xff1a;DSU 类中包含一个整型数组 s 用来存储元素的父节点信息。2.DSU 构造函数&#xff1a; 构造函数 DSU(int size) 接受一个参数 size&#xff0…

欧拉角与横滚-俯仰-偏航角(RPY)

围绕欧拉角和横滚-俯仰-偏航角这两个术语存在很多混淆。这源于教科书和论文中截然不同的、看似权威的定义。 欧拉旋转定理&#xff08;1775 年&#xff09;指出&#xff0c;一个 3D 坐标系相对于另一个坐标系的方向可以用“围绕三个轴的连续旋转来描述&#xff0c;因此没有两个…

泰迪智能科技携手华北电力大学理学院共建“校外实践基地”

3月15日&#xff0c;华北电力大学数理学院教学副主任史会峰、科研副主任王涛、概率教研室副主任解西阳莅临泰迪智能科技产教融合实训基地开展“华北电力大学校外实践教学基地”签约揭牌仪式。泰迪智能科技董事长张良均、支持中心负责人王宏刚、外联部吴桂锋进行接待。 活动伊始…