【C++心愿便利店】No.12---C++之探索string底层实现

文章目录

  • 前言
  • 一、写实拷贝(了解)
  • 二、string类常用接口实现
    • 2.1 成员变量
    • 2.2 默认构造函数
    • 2.3 拷贝构造函数
    • 2.4 operator==
    • 2.5 operator[]
    • 2.6 c_str
    • 2.7 size()
    • 2.8 capacity()
  • 三、迭代器的实现
    • 3.1 begin()和end()
    • 3.2 范围for
  • 四、string类增删查改
    • 4.1 reserve():增容函数
    • 4.2 push_back():尾插字符
    • 4.3 append():追加字符串
    • 4.4 operator+=
    • 4.5 insert
    • 4.6 erase
    • 4.7 resize
    • 4.8 find
    • 4.9 substr
  • 五、string类运算符重载
    • 5.1 operator< == <= > >= !=
    • 5.2 operator<<
    • 5.3 operator>>


前言

在这里插入图片描述

👧个人主页:@小沈YO.
😚小编介绍:欢迎来到我的乱七八糟小星球🌝
📋专栏:C++ 心愿便利店
🔑本章内容:探索string底层实现
记得 评论📝 +点赞👍 +收藏😽 +关注💞哦~


提示:以下是本篇文章正文内容,下面案例可供参考

一、写实拷贝(了解)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

二、string类常用接口实现

2.1 成员变量

class string
{
public:private:char* _str;size_t _size;size_t _capacity;const static size_t npos;——————————————————————————const static size_t npos=-1;//静态的成员变量是不可以给缺省值,必须在类外面进行初始化但是const静态的整形可以(特例)
};
const size_t string::npos = -1;//支持在类外面初始化

在这里插入图片描述

2.2 默认构造函数

string():_str (new char[1]{'\0'}),_size(strlen(0)),_capacity(0)
{}
//常量字符串规定后面默认就有\0
//strlen 计算的是字符串中有效字符的个数,不算 '\0',而常量字符串的结尾默认有一个 '\0',用 new开辟空间的时候需要多开一个用来存储结尾的 \0
string(const char* str=""):_size(strlen(str))//0,_capacity(_size)//0
{_str = new char[_capacity + 1];//1strcpy(_str, str);
}

对于上述代码中形参上必须加 const 修饰,这样才能用 C 语言中的常量字符串来初始化 string 类对象,上面两种初始化的方式都可以一个是无参的一个是有缺省值的,形参的的缺省值直接给一个空字符串即可如上述,要注意初始化列表是按照声明的顺序来初始化的。_capacity表示的是可以存储有效字符的容量,而字符串结尾默认的 ‘\0’ 并不算作有效字符,因此最初的 _capacity 就是形参 str 的长度。
🌟对于为什么不可以用 ‘\0’ “\0” 和nullptr当缺省值?
答案:

  • 首先对于’\0’:str是一个char*类型,而’\0’是一个char类型的 类型不匹配
  • 其次对于给缺省值nullptr:strlen是不会去检查空的,它是一直找到 \0为止的,也就相当于直接对这个字符串进行解引用了,这里的字符串又是空,所以会引发空指针问题。
  • 最后对于"\0":它表示该字符串有一个字符 ‘\0’ ,它的结尾还有一个默认的 ‘\0’,因此有两个 ‘\0’

2.3 拷贝构造函数

//传统写法
string(const string& s)//拷贝构造
{_str = new char[s._size+1];strcpy(_str,s._str);_size = s._size;_capacity = s._capacity;
}

上述我们称为传统写法下面这种我们称为现代写法,对于现代写法不需要亲自去申请空间初始化,而是调用构造函数去完成。最后再将初始化好的 tmp 交换过来
还要注意:一定要通过初始化列表对 *this 进行初始化,不然交换给 tmp 后,里面都是随机值(不同编译器有的会处理有的不会),最终出了作用域 tmp 去销毁的时候就会出问题。

//现代写法:下面代码是两种不同的现代写法
不过要注意如果 string 对象中有 '\0',只会把 '\0' 前面的字符拷贝过去
void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
string(const string& s):_str(nullptr)//拷贝构造,_size(0),_capacity(0)//不处理tmp里面是随机值析构就会发合适呢个错误
{string tmp(s._str);//构造swap(tmp);
}
___________________________________________________________________________________________
void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
string(const string& s)//拷贝构造
{string tmp(s._str);swap(_str, tmp._str);swap(_size, tmp._size);swap(_capacity, tmp._capacity);
}

2.4 operator==

//传统写法
string& operator=(const string& s)
{if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;
}

对比于上述代码写法,下述这种写法通过调用拷贝构造来申请空间,在利用局部对象出了作用就会被销毁的特点,将需要释放的资源通过 swap 交换给这个局部变量,让这个局部变量帮我们销毁。

//现代写法:这里不能直接用 swap 交换两个 string 类对象,会导致栈溢出
void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
string& operator=(const string& s)
{if (this != &s){//string tmp(s);//调用拷贝构造string tmp(s._str);//调用构造swap(tmp);//这里s2换给了tmp本来s2要析构现在tmp出了作用域调用析构也就意味着原始的s2的空间释放了}return *this;
}

下述这种写法不用我们去调用构造或者拷贝构造,直接通过形参去调用,传值传参会调用拷贝构造,tmp是它的实参调用拷贝构造构造的一个一摸一样的空间。

//现代写法的优化版本
void swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
string& operator=(string tmp)
{swap(tmp);return *this;
}

2.5 operator[]

这两个运算符重载函数构成函数重载,对象在调用的时候会找最匹配的,非const对象会调用非const版本,const 对象会调用const版本。

//可读可写版本
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}
//只可以读版本
const char& operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}

2.6 c_str

返回的是一个const char*的数组指针,只读不写,这个数组包含的字符序列与string对象的值相同,另外还包含一个以空字符(‘\0’)结尾的字符串,加上 const,这样普通的 string 类对象可以调用,const 类型的 string 类对象也可以调用,普通对象来调用就是权限的缩小

const char* c_str()
{return _str;
}

2.7 size()

size_t size()const
{return _size;
}

2.8 capacity()

size_t capacity()const
{return _capacity;
}

三、迭代器的实现

iterator 是 string 类的内嵌类型,也可以说是在 string 类里面定义的类型,在一个类里面定义类型有两种方法,typedef 和 内部类。

3.1 begin()和end()

//非const调用
typedef char* iterator;//string 类的 iterator 是通过typedef来实现的
iterator begin()
{return _str;
}
iterator end()
{return _str+_size;
}
——————————————————————————————————————————————————————————
//const调用
typedef const char* const_iterator;
const_iterator begin()const
{return _str;
}
const_iterator end()const
{return _str + _size;
}

3.2 范围for

支持范围for写法,范围for的底层是迭代器实现的,但是范围for不是万能的,范围for遇上const类型的对象,会报错,因此要提供const迭代器:typedef const char* const_iterator;

string s1("hello world");
for (auto ch : s1)
{ch++;cout << ch << " ";
}
cout << endl;

四、string类增删查改

4.1 reserve():增容函数

reserve 函数不会进行缩容,因此在扩容前要先进程判断,只有当形参 n 大于当前容量的时候才扩容。

void reserve(size_t n)
{if (n > _capacity){char* tmp = new char[n + 1];//开n+1个是因为n个有效字符,另一个是'\0'strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
}

4.2 push_back():尾插字符

对于尾插首先检查是否需要增容,如果需要就调用我们上面实现的 reserve 函数来进行扩容(选择2倍扩容),
扩容后将ch加到str上,然后 _size++ 最后手动添加一个新的 \0 。

void push_back(char ch)
{if (_size == _capacity){//reserve(_capacity * 2);//对于这里可以采用三目来判断_capacity是否为0,若不进行判断空串的_capacity是0,进行扩容0*0=0就会发生越界访问reserve(_capacity==0?4:_capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';
}

4.3 append():追加字符串

void append(const char * str)
{size_t len = strlen(str);if (_size +len> _capacity){reserve(_size+len);//这里就不需要担心_capacity为0的情况}strcpy(_str + _size, str);//strcpy会把'\0'也拷贝过去size += len;
}

4.4 operator+=

+=要有返回值返回*this

//追加一个字符复用push_back()
string& operator+=(char ch)
{push_back(ch);return *this;
}//追加一个字符串复用append()
string& operator+=(const char* str)
{append(str);return *this;
}

4.5 insert

在pos位置插入字符

void insert(size_t pos, char ch)
{assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size;while (end >= pos){_str[end] = _str[end-1];--end;}_str[pos] = ch;_size++;
}

注意:上述代码挪动数据时的判断条件中,end 和 pos 都是 sizt_t 类型,例如当 pos = 0 的时候 end >= pos,end–,一直减到end=-1但是end是一个无符号整形,所以循环条件一直成立还可以进入循环, 所以下面有两种修改方式:

为了防止整型提升有以下两种写法:
void insert(size_t pos, char ch)
{assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size+1;while (end > pos){_str[end] = _str[end-1];--end;}_str[pos] = ch;_size++;
}
——————————————————————————————————————————————————————————————————————————————————
void insert(size_t pos, char ch)
{assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}int end = _size;换成有符号也会报错因为操作符的两边两个数据的类型不同时会发生类型提升,end变成有符号类型也会被提升到无符号类型因为pos是无符号类型,所以可以强转pos->(int)poswhile (end >=(int) pos){_str[end] = _str[end-1];--end;}_str[pos] = ch;_size++;
}

在pos位置插入字符串

void insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size+len;while (end >= pos+len){_str[end] = _str[end-len];--end;}strncpy(_str + pos, str,len);_size += len;}
}

4.6 erase

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

4.7 resize

size_t find(char ch,size_t pos=0)//这里给了一个半缺省
{for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;
}

4.8 find

size_t find(const char* str, size_t pos = 0)//半缺省
{const char* p = strstr(_str+pos, str);//strstr找到返回所在位置指针否则返回空if (p){return p - _str;//返回下标}else{return npos;}
}

4.9 substr

这里就表明我们一定要手写一个拷贝构造,编译器默认生成的拷贝构造是一个浅拷贝,会发生调用两次析构的问题所以要手写一个深拷贝

string substr(size_t pos, size_t len = npos)
{string s;size_t end = pos + len;if (len == npos || pos + len >= _size){len = _size - pos;end = _size;}s.reserve(len);for (size_t i = pos; i < pos + len; i++){s += _str[i];}return s;//s是一个浅拷贝出了作用域s销毁
}

五、string类运算符重载

5.1 operator< == <= > >= !=

bool operator<(const string& s)const
{return strcmp(_str, s._str) < 0;
}bool operator==(const string& s)const
{return strcmp(_str, s._str) == 0;
}bool operator<=(const string& s)const
{return *this < s || *this == s;
}bool operator>(const string& s)const
{return !(*this <= s );
}bool operator>=(const string& s)const
{return !(*this < s);
}bool operator!=(const string& s)const
{return !(*this == s);
}

5.2 operator<<

因为类函数有this指针传参数容易发生错误匹配原因,>>和<<运算符重载要写在类外面
无论是形参还是返回值,只要涉及到 ostream 或 istream 都必须要用引用

//有以下两种写法:
ostream& operator<<(ostream& out,const string& s)
{for (size_t i = 0; i < s.size(); i++){out << s[i];}return out;
}
_______________________________________________________________________
范围for(这里使用范围for要调用const迭代器)
ostream& operator<<(ostream& out,const string& s)
{for (auto ch : s)//s是一个const对象要用const迭代器out << ch;return out;
}

5.3 operator>>

空格符 ’ ’ 和换行符 \n不能直接用 istream 对象来读取的,in >> ch 是读不到空格符和换行符。需要借助 get() 成员函数才能读取到空格符和换行符。

istream& operator>>(istream& in, string& s)
{s.clear();//清掉原始数据不然就变成尾插了char ch;//in >> ch;//拿不到空格或者换行例如sacnf拿不到空格所以出现了getcharch = in.get();while (ch!=' '&&ch!='\n'){s += ch;ch = in.get();}return in;
}

对于上面这种写法,在输入的字符串很长的情况下会多次调用 reserve 进行扩容,所以可以采用下述优化版本来实现:先开辟一个数组,将输入的字符存储到数组中,然后从数组中拷贝到string对象中,数组出了作用域就会销毁

istream& operator>>(istream& in, string& s)
{s.clear();//清掉数据不然就变成尾插了char buff[128];size_t i = 0;char ch;ch = in.get();while (ch!=' '&&ch!='\n'){buff[i++] = ch;if (i == 128){buff[i] = '\0';s += buff;i = 0;}}if (i != 0){buff[i] = '\0';s += buff;}return in;
}

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

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

相关文章

偶数科技携Skylab实时湖仓数据平台亮相2023全国中小企业数字化转型大会

2023全国中小企业数字化转型大会于10月28日至30日在安徽省合肥市举行&#xff0c;本次大会以“数实融合 赋能万企”为主题&#xff0c;由工业和信息化部、安徽省人民政府主办。会议期间&#xff0c;偶数科技等典型企业的数字化转型新技术、新产品、新应用、新模式集聚亮相&…

【Git】安装和常用命令的使用与讲解及项目搭建和团队开发的出现的问题并且给予解决

目录 Git的简介 介绍 Git的特点及概念 Git与SVN的区别 图解 ​编辑 命令使用 安装 使用前准备 搭建项目环境 ​编辑 团队开发 Git的简介 介绍 Git 是一种分布式版本控制系统&#xff0c;是由 Linux 之父 Linus Torvalds 于2005年创建的。Git 的设计目标是为了更好地管…

【LeetCode: 54. 螺旋矩阵 | 模拟】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

react 修改less文件后保存,内存溢出,项目崩溃问题解决

一、完整报错 一个很老的react项目&#xff0c;因为没有package-lock.json版本锁&#xff0c;导致拉下来的时候&#xff0c;安装的依赖版本冲突&#xff0c;好不容易启动起来&#xff0c;修改less文件后只要一保存&#xff0c;项目就会崩溃&#xff0c;需要重启&#xff0c;报…

【uni-app + uView】CountryCodePicker 国家区号组件

1. 效果图 2. 组件完整代码 <template><u-popup class="country-code-picker-container" v-if="show" :show

2013年108计网

第33题 在 OSI 参考模型中, 下列功能需由应用层的相邻层实现的是()A. 对话管理B. 数据格式转换C. 路由选择D. 可靠数据传输 很显然&#xff0c;题目所问的应用层的相邻层是表示层。该层实现与数据表示相关的功能。选项a中的对话管理属于会话层。选项c中的路由选择属于网络层。…

机器学习 - 决策树:技术全解与案例实战

目录 一、引言二、决策树基础决策树模型概述构建决策树的关键概念特征选择决策树的生成 决策树的剪枝 三、算法研究进阶提升树和随机森林提升树&#xff08;Boosted Trees&#xff09;随机森林&#xff08;Random Forests&#xff09; 进化算法与决策树决策树结构的进化 多目标…

吃啥大转盘

经常跟朋友出去吃饭&#xff0c;选择太困难了所以写了个简单的转盘&#xff0c;直接copy到txt文本然后把文件后缀改成html即可。 需要换食物直接在文件中找到 list 值按照格式替换一下即可。 效果&#xff1a; 代码块&#xff1a; <html><head><meta http-…

【方法】如何取消PDF文件的“打开密码”?

我们知道&#xff0c;PDF文件可以设置“打开密码”&#xff0c;保护文件不被随意打开&#xff0c;那如果后续不需要了&#xff0c;要怎么取消“打开密码”呢&#xff1f;不清楚的小伙伴可以试试小编分享的3种方法&#xff01; 方法1&#xff1a;使用PDF编辑器 PDF编辑器不仅可…

技术分享 | Spring Boot 异常处理

Java 异常类 首先让我们简单了解或重新学习下 Java 的异常机制。 Java 内部的异常类 Throwable 包括了 Exception 和 Error 两大类&#xff0c;所有的异常类都是 Object 对象。 Error 是不可捕捉的异常&#xff0c;通俗的说就是由于 Java 内部 JVM 引起的不可预见的异常&#…

银行电子回单p图软件,建设农业邮政工商招商,易语言回执单快照截图

这次分享的还是通过易语言的画板自动绘画一个回执单的功能&#xff0c;套用的是网上一个回执单模版&#xff0c;我加了水印&#xff0c;防止被别有用心的人利用&#xff0c;然后一共我插入了5个图片资源&#xff0c;单选框选定后画板上面的图片会自动被替换为对应的图片模版&am…

星岛专栏|从Web3发展看金融与科技的融合之道

11月起&#xff0c;欧科云链与香港主流媒体星岛集团开设Web3.0安全技术专栏&#xff0c;该专栏主要面向香港从业者、交易机构、监管机构输出专业性的安全合规建议&#xff0c;旨在促进香港Web3.0行业向安全与合规发展。 出品&#xff5c;欧科云链研究院 自2016年首届香港金融…

降维·预测·救命:PCA、随机森林与乳腺癌

一、引言 乳腺癌作为女性健康领域的一大挑战&#xff0c;对全球范围内的女性健康产生了深远影响。据世界卫生组织&#xff08;WHO&#xff09;统计&#xff0c;乳腺癌已成为全球女性恶性肿瘤发病率的最高者&#xff0c;且呈现逐年上升的趋势。在中国&#xff0c;乳腺癌也是女性…

k8s存储卷 PV和PVC

目录 emptyDir存储卷 hostPath存储卷 nfs共享存储卷 PVC 和 PV 生命周期 一个PV从创建到销毁的具体流程如下&#xff1a; 静态pvc 动态pvc 3、定义PVC 4、测试访问 搭建 StorageClass NFS&#xff0c;实现 NFS 的动态 PV 创建 1、在stor01节点上安装nfs&#xff0…

电商大促演变:拼多多百亿补贴的消费升级体验

出品| 大力财经 文 | 魏力 拼多多已经够便宜了&#xff0c;双十一还能怎么玩&#xff1f;作为一个曾经被认为是深耕五环外消费者的电商平台&#xff0c;这几年拼多多从五环外杀到市中心&#xff0c;现在的国人&#xff0c;不管是中产&#xff0c;还是职场小白&#xff0c;人人…

混沌系统在图像加密中的应用(小波混沌神经网络)

混沌系统在图像加密中的应用&#xff08;小波混沌神经网络&#xff09; 前言一、小波混沌神经网络模型二、拓展三、python代码 前言 小波混沌神经网络是一种神经网络模型&#xff0c;结合了小波变换和混沌理论&#xff0c;用于信号处理、分类和预测。该模型基于多层前向神经网…

VINS-Mono-后端优化 (三:视觉雅可比推导)

用逆深度是因为这样可以在优化中从优化3个变量降低到1个&#xff0c;降低优化的维度加快求解速度 用逆深度是因为当距离很远的时候&#xff0c; 1 x \frac{1}{x} x1​ x x x 就会无穷大&#xff0c;而3D点很近的情况也一般不会有&#xff0c;这也是为了数值稳定性 用逆深度的…

C语言求解:有n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位(约瑟夫问题)

完整代码&#xff1a; /* 有n个人围成一圈&#xff0c;顺序排号。从第一个人开始报数&#xff08;从1到3报数&#xff09;&#xff0c;凡报到3的人 退出圈子&#xff0c;问最后留下的是原来第几号的那位*/ #include<stdio.h>//约瑟夫问题 //递推关系f(n)(f(n-1)2)\mod n…

(动手学习深度学习)第13章 计算机视觉---图像增广与微调

13.1 图像增广 总结 数据增广通过变形数据来获取多样性从而使得模型泛化性能更好常见图片增广包裹翻转、切割、变色。 图像增广代码实现

线性代数 | 矩阵运算 加减 数乘 矩阵的幂运算

文章目录 1 矩阵加减和数乘2 矩阵与矩阵的乘法2.1 相乘条件&#xff1a;看中间&#xff0c;取两头2.2 相乘计算方法 3 矩阵的幂3.1 观察归纳法3.2 邻项相消法3.3 化为对角 4 判断是否可逆&#xff08;证明题或者要求求出逆矩阵&#xff09;4.1 直接观察4.2 由定义式推得4.2.1 待…