【C++】学习笔记——string_5

文章目录

  • 六、string类
    • 7. string类的模拟实现
    • 8. string类的模拟实现的完整代码
      • string.h头文件
      • test.c源文件
    • 9. string收尾
      • 写时拷贝
  • 未完待续


六、string类

7. string类的模拟实现

我们之前讲了实现 insert ,但是那个插入函数仅仅是在 pos 位置插入一个字符而且,我们并没有实现在 pos 位置插入一个字符串。所以我们现在将其补充上。

// 在 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);
}

当难以理解的时候,记得画图哦,我们来看看结果:

#include"string.h"
using namespace my;int main()
{string s("hello,world");s.insert(4, "AAAAAAA");std::cout << s.c_str() << std::endl;return 0;
}

在这里插入图片描述
没问题。其实我们发现,有了 insert 之后,前面的 push_back()append 好像可以复用这的代码,我们来调整一下。

void push_back(char ch)
{//if (_size == _capacity)//{//	// 扩容2倍//	reserve(_capacity == 0 ? 4 : 2 * _capacity);//}//_str[_size] = ch;//++_size;//_str[_size] = '\0';insert(_size, ch);
}void append(const char* str)
{//size_t len = strlen(str);//if (_size + len > _capacity)//{//	reserve(_size + len);//} 从末尾开始,拷贝新字符串//strcpy(_str + _size, str);	//_size += len;insert(_size, str);
}

接下来我们再把 swap 给实现一下,有人会说哈,swap 在库里面就有,为啥还要我们手动实现呢?我给大家看看库里的 swap 是怎样的。
在这里插入图片描述
我们发现,库里的swap整整调用了3次拷贝和1次析构,虽然能用,但是它非常搓,所以我们要实现一个对我们来说更加好用的 swap

void swap(string& s)
{// 调用库里的swap,直接交换成员即可,不需要创建一个新的对象std::swap(_str, s._str);// 加上std:: 是让其直接去std域找,避免找到当前的成员函数swapstd::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
#include"string.h"
using namespace my;int main()
{string s("hello,world");string s1("1234567890");s.swap(s1);std::cout << s.c_str() << std::endl << s1.c_str() << std::endl;return 0;
}

在这里插入图片描述
既然库里的 swap 对我们来说很挫,但是我们应该怎样才能防止别人使用库里的函数呢?
在这里插入图片描述
库里面有个 swap ,成员函数有个 swap ,这里怎么还有一个非成员函数的 swap
在这里插入图片描述
这里的 swap 原来直接调用的是成员函数 swap ,那么非成员函数的 swap 是全局的,库里的 swap 是全局的,为啥没发生冲突?为啥就会先用非成员函数的 swap ?我在模板那篇提到过,因为 库里的 swap 是模板当有现成的函数时,优先使用现成的。这下就完美解决了使用库里的swap了。

// string类外
void swap(string& x, string& y)
{x.swap(y);
}

接下来实现 find 函数。

// 查找一个字符
size_t find(char ch, size_t pos = 0) const
{for (size_t i = pos; i < _size; ++i){if (_str[i] == ch)return i;}return npos;
}
// 查找一个字串
size_t find(const char* sub, size_t pos = 0) const
{assert(pos < _size);const char* p = strstr(_str + pos, sub);// 找到了if (p){return p - _str;}// 没找到return npos;
}

接下来实现 substr 函数。
在这里插入图片描述

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

验证验证:

#include"string.h"
using namespace my;int main()
{string s("hello,world");size_t pos = s.find(',');std::cout << s.substr(pos, 3).c_str() << std::endl;return 0;
}

在这里插入图片描述
再就是重载比较。

// 重载成全局函数
bool operator==(const string& s1, const string& s2)
{// strcmp 若是相等则返回0int ret = strcmp(s1.c_str(), s2.c_str());return ret == 0;
}bool operator<(const string& s1, const string& s2)
{int ret = strcmp(s1.c_str(), s2.c_str());return ret < 0;
}bool operator<=(const string& s1, const string& s2)
{return s1 < s2 || s1 == s2;
}bool operator>(const string& s1, const string& s2)
{return !(s1 <= s2);
}bool operator>=(const string& s1, const string& s2)
{return !(s1 < s2);
}bool operator!=(const string& s1, const string& s2)
{return !(s1 == s2);
}

实现流插入和流提取:

using namespace std;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;// cin 读取不到 ' ' 和 '\n'ch = in.get();// 减少扩容char buff[128];size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[127] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;
}

顺便实现 clear

// 成员函数
void clear()
{_size = 0;_str[_size] = '\0';
}

检验检验:

#include"string.h"
using namespace my;int main()
{// 刚刚展开了std,这里避免冲突my::string s;cin >> s;cout << s;return 0;
}

在这里插入图片描述
很好,空格前的都被读取到了。
再来实现 getline 。getline就是读取一行嘛,相信实现了流提取运算符,实现一个 getline 肯定非常轻松。

istream& getline(istream& in, string& s)
{// 需要将其内容清空s.clear();char ch;// cin 读取不到 ' ' 和 '\n'ch = in.get();// 减少扩容char buff[128];size_t i = 0;while (ch != '\n'){buff[i++] = ch;if (i == 127){buff[127] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;
}

再来检验:

#include"string.h"
using namespace my;int main()
{my::string s;getline(cin, s);cout << s;return 0;
}

在这里插入图片描述

在实现拷贝构造函数时,我们写了一个非常传统的写法,这里再给大家实现一种新式写法:

// 传统写法
string(const string& s)
{_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}// 新式写法
string(const string& s)
{string tmp(s._str);swap(tmp);
}

这里新式写法本质上就是调用构造函数,然后让 this指针 指向新的构造的 sring 类 。同样,赋值重载也能使用新式写法。

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

这里赋值重载的新式写法还能优化(行数),既然在函数内部要调用拷贝构造,为什么不在传参的时候直接调用拷贝构造呢?

string& operator=(string s)
{swap(s);return *this;
}

8. string类的模拟实现的完整代码

string.h头文件

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;namespace my
{class string{public: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;}// 默认是空串而不是空指针string(const char* str = ""):_size(strlen(str)){_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}//string(const string& s)//{//	_str = new char[s._capacity + 1];//	strcpy(_str, s._str);//	_size = s._size;//	_capacity = s._capacity;//}string(const string& s){string tmp(s._str);swap(tmp);}//string& operator=(const string& s)//{//	char* tmp = new char[s._capacity + 1];//	strcpy(tmp, s._str);//	//	delete[] _str;//	_str = tmp;//	_size = s._size;//	_capacity = s._capacity;//	return *this;//}string& operator=(string s){swap(s);return *this;}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}// 加 const 使其成为 const 成员函数,使 const 对象也能调用这个函数size_t size() const{return _size;}// 引用返回,可读可写inline char& operator[](size_t pos){assert(pos < _size);return _str[pos];}// 针对 const对象 的可读不可写,加 & 是为了减少拷贝inline const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}size_t capacity() const{return _capacity;}void reserve(size_t n){// 只有要扩容的大小比当前容量大才能扩容if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){//if (_size == _capacity)//{//	// 扩容2倍//	reserve(_capacity == 0 ? 4 : 2 * _capacity);//}//_str[_size] = ch;//++_size;//_str[_size] = '\0';insert(_size, ch);}void append(const char* str){//size_t len = strlen(str);//if (_size + len > _capacity)//{//	reserve(_size + len);//} 从末尾开始,拷贝新字符串//strcpy(_str + _size, str);	//_size += len;insert(_size, str);}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}char* c_str() const{return _str;}// 在 pos 位置插入一个字符void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){// 扩容2倍reserve(_capacity == 0 ? 4 : 2 * _capacity);}size_t end = _size + 1;while (end > 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);}// 从 pos 开始,删除 len 个字符,如果 len 是 npos ,则全删void erase(size_t pos, size_t len = npos){assert(pos < _size);// pos + len >= _size 可能会溢出if (len == npos || len >= _size - pos){_str[pos] = '\0';_size = pos;}strcpy(_str + pos + len, _str + pos);_size -= len;}void resize(size_t n, char ch = '\0'){if (n <= _size){_str[n] = '\0';_size = n;}else{reserve(n);for (size_t i = _size; i < n; ++i){_str[i] = ch;}_str[n] = '\0';_size = n;}}void swap(string& s){// 直接交换成员即可std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}size_t find(char ch, size_t pos = 0) const{for (size_t i = pos; i < _size; ++i){if (_str[i] == ch)return i;}return npos;}size_t find(const char* sub, size_t pos = 0) const{assert(pos < _size);const char* p = strstr(_str + pos, sub);// 找到了if (p){return p - _str;}// 没找到return npos;}string substr(size_t pos = 0, size_t len = npos){string sub;if (len >= _size - pos){for (size_t i = pos; i < _size; ++i){sub += _str[i];}}else{for (size_t i = pos; i < pos + len; ++i){sub += _str[i];}}return sub;}void clear(){_size = 0;_str[_size] = '\0';}private:char* _str;size_t _size;size_t _capacity;public:static const int npos;};// 静态成员变量在类外部定义const int string::npos = -1;// string类外void swap(string& x, string& y){x.swap(y);}bool operator==(const string& s1, const string& s2){// strcmp 若是相等则返回0int ret = strcmp(s1.c_str(), s2.c_str());return ret == 0;}bool operator<(const string& s1, const string& s2){int ret = strcmp(s1.c_str(), s2.c_str());return ret < 0;}bool operator<=(const string& s1, const string& s2){return s1 < s2 || s1 == s2;}bool operator>(const string& s1, const string& s2){return !(s1 <= s2);}bool operator>=(const string& s1, const string& s2){return !(s1 < s2);}bool operator!=(const string& s1, const string& s2){return !(s1 == s2);}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;// cin 读取不到 ' ' 和 '\n'ch = in.get();// 减少扩容char buff[128];size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[127] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}istream& getline(istream& in, string& s){// 需要将其内容清空s.clear();char ch;// cin 读取不到 ' ' 和 '\n'ch = in.get();// 减少扩容char buff[128];size_t i = 0;while (ch != '\n'){buff[i++] = ch;if (i == 127){buff[127] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}
}

test.c源文件

#include"string.h"
using namespace my;int main()
{// return 0;
}

9. string收尾

我们来看看下面一段程序:

#include"string.h"
using namespace my;int main()
{std::string s("11111");cout << sizeof(s) << endl;return 0;
}

库里的 string 是多少空间呢?
在这里插入图片描述
嗯?如果是我们写的 string ,那么应该是 12 啊(32位下,64位是 24),为什么库里的 string 是 28呢?其实是因为编译器做了相关的优化,它在原有的基础上还新增了一个 buff[16] 数组,当数据较小的时候会存到 buff 里,比较大才会存到 _str 在堆上开辟的空间里。因为栈上开辟空间比在堆上开辟空间快。这仅仅是在 vs 编译器下是这样的,其他编译器不一定是这样。
在这里插入图片描述

写时拷贝

我们知道,拷贝构造不能是浅拷贝,假如是浅拷贝,那么会导致两个指针指向同一块空间,一个修改另一个也会发生变化,多次析构的问题。所以只能是深拷贝。但是库里的 string 类真的是这样的吗?库里的 string 类的构造函数其实是浅拷贝,但是它有个引用计数,代表有几个对象指向这块空间,如果有对象要修改(写),则重新深拷贝一个空间给其使用,析构的时候则判断引用计数是否为1,不为1则不析构。这样做其实就是赌有的对象不去修改内容,只要有这样的对象,那么就算是一种优化。


未完待续

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

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

相关文章

二、nodejs连接数据库实现登录功能

// 数据库下载可以用这个 DBeaver安装与使用教程&#xff08;超详细安装与使用教程&#xff09;&#xff0c;好用免费的数据库管理工具_dbeaver使用教程-CSDN博客 模块化 &#xff08;1&#xff09;新建一个叫Dao的文件夹&#xff0c;存接数据库代码 // 参考数据库 &#xf…

自动驾驶-第02课软件环境基础(ROSCMake)

1. 什么是ros 2. 为什么使用ros 3. ROS通信 3.1 Catkin编译系统

prometheus的安装与部署及其有点

一、Prometheus 的优点 1、非常少的外部依赖&#xff0c;安装使用超简单&#xff1b; 2、已经有非常多的系统集成 例如&#xff1a;docker HAProxy Nginx JMX等等&#xff1b; 3、服务自动化发现&#xff1b; 4、直接集成到代码&#xff1b; 5、设计思想是按照分布式、微服…

Flutter笔记:Widgets Easier组件库(10)快速处理承若型对话

Flutter笔记 使用Widgets Easier组件库快速处理承若型对话 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://…

小程序账号设置以及request请求的封装

一般开发在小程序时&#xff0c;都会有测试版和正式版&#xff0c;这样在开发时会比较方便。 在开发时。产品经理都会给到测试账号和正式账号&#xff0c;后端给的接口也都会有测试环境用到的接口和正式环境用到的接口。 这里讲一讲我这边如何去做的。 1.在更目录随便命名一…

一机游领航旅游智慧化浪潮:借助前沿智能设备,革新旅游服务效率,构建高效便捷、生态友好的旅游服务新纪元,开启智慧旅游新时代

目录 一、引言 二、一机游的定义与特点 &#xff08;一&#xff09;一机游的定义 &#xff08;二&#xff09;一机游的特点 三、智能设备在旅游服务中的应用 &#xff08;一&#xff09;旅游前的信息查询与预订支付 &#xff08;二&#xff09;旅游中的导航导览与互动体…

MATLAB中功率谱密度计算pwelch函数使用详解

MATLAB中功率谱密度计算pwelch函数使用详解 目录 前言 一、pwelch函数简介 二、pwelch函数参数说明 三、pxx pwelch(x)示例 四、[pxx,f]pwelch(x,window,noverlap,nfft,fs)示例 四、[pxx,f] pwelch(x,window,noverlap,nfft,fs,freqrange,spectrumtype)示例 五、多通道功…

Springboot(SSM)项目实现数据脱敏

目录 一、引入hutool的依赖 二、sql脚本 三、自定义注解代码 3.1 自定义注解 3.2 自定义一个枚举,用于定义脱敏的类型 3.3 序列化 四、使用脱敏注解 4.1 Person.java 4.2 controller 4.3 dao 五、源代码参考 一、引入hutool的依赖 <dependency><groupId>…

FIFO Generate IP核使用——Native读写接口信号详解

Native FIFO接口信号是用于FIFO IP核与外部电路进行通信的信号。当FIFO支持独立的写和读时钟时&#xff0c;这些信号可以包括标准端口和可选端口。 1 当FIFO具有独立时钟时的接口信号 当FIFO具有独立的时钟时&#xff0c;其接口信号会相应地有所变化。特别是关于复位信号rst…

计算机等级考试2级(Python)知识点整理

计算机等级考试2级&#xff08;Python&#xff09;知识点整理 1.基础知识点&#xff08;记忆、理解&#xff09; 第1讲Python概述 01. 源代码 02. 目标代码 03. 编译和解释 04. 程序的基本编写方法 第2讲 Python语言基础&#xff08;一&#xff09; 01. 用缩进表示代码…

[数据结构]——非比较排序—计数排序

该篇文章 所涉及代码收录仓库&#xff1a;登录 - Gitee.com 目录 1.非比较排序——计数排序 2.最终实现 1.解析 2.以int a[] { 1,3,9,1,5,1,2,3,-5,-5,-2 };为例&#xff0c;手撕分析 3.代码实现 4.计数排序具有以下主要特性&#xff1a; 1.非比较排序——计数排序 思想…

Linux IP Forwarding路由转发实验

linux 路由转发功能 Linux 操作系统具备路由转发功能&#xff0c;路由功能是指 Linux 操作系统提供的路由管理和转发功能&#xff0c;它允许 Linux 主机在网络中正确地转发数据包&#xff0c;并确保数据包能够达到其目的地。 出于安全考虑&#xff0c;Linux系统默认是禁止数据…

ICode国际青少年编程竞赛- Python-1级训练场-for循环入门

ICode国际青少年编程竞赛- Python-1级训练场-for循环入门 1、 for i in range(4):Dev.step(4)Dev.turnLeft()2、 for i in range(3):Dev.step(6)Dev.turnRight()3、 for i in range(3):Dev.turnRight()Dev.step(2)Dev.turnLeft()Dev.step(-3)4、 for i in range(4):Dev…

数字旅游以科技创新为核心竞争力:推动旅游服务的智能化、高效化,满足游客日益增长的旅游需求

一、引言 随着科技的飞速发展&#xff0c;数字旅游作为旅游业与信息技术结合的产物&#xff0c;正以其独特的魅力改变着传统旅游业的格局。科技创新作为数字旅游的核心竞争力&#xff0c;不仅推动了旅游服务的智能化、高效化&#xff0c;更满足了游客日益增长的旅游需求。本文…

香港理工大学内地事务总监陆海天教授确认出席“边缘智能2024 - AI开发者峰会”并发表主题演讲

隨著AI技術的日新月異&#xff0c;我們正步入一個邊緣計算智能化與分布式AI相互融合的新紀元。這一變革不僅推動了分布式智能創新應用的飛速發展&#xff0c;還使得邊緣智能——這一結合邊緣計算和智能技術的新興領域&#xff0c;逐漸成為引領AI發展的重要力量。通過其分布式和…

clang:在 Win10 上编译 MIDI 音乐程序(二)

先从 Microsoft C Build Tools - Visual Studio 下载 1.73GB 安装 "Microsoft C Build Tools“ 访问 Swift.org - Download Swift 找到 Windows 10&#xff1a;x86_64 下载 swift-5.10-RELEASE-windows10.exe 大约490MB 建议安装在 D:\Swift\ &#xff0c;安装后大约占…

走进香港美食宛如走进香港电影

&#xff08;1&#xff09; 过去蔡澜有个节目&#xff0c;专门介绍香港美食&#xff0c;身边美女相伴、眼里美景相随。 过去离香港海关近&#xff0c;有时候散步都能走到那里&#xff0c;打车时车都不蹦字儿。那时候精神头儿真好&#xff0c;周六一早6点就起来拖着大箱子过关&a…

软件测试(实验五)——Jmeter的使用

目录 实验目的 一、使用JMeter演示取样器、监听器、配置元件、断言的使用&#xff1b; 1、取样器 2、监听器 3、配置元件的使用 ① 用户定义的变量 ②HTTP信息头管理器 ③HTTP请求默认值 ④CSV数据文件设置 4、断言 ①响应断言 ②JSON断言 ③断言持续时间 二、使用…

「 网络安全常用术语解读 」SBOM主流格式SWID详解

国际标准化组织&#xff08;ISO&#xff09;和国际电工委员会&#xff08;International Electrotechnical Commission&#xff0c;IEC&#xff09;发布了ISO/IEC 19770-2软件标识&#xff08;Software Identification&#xff0c;SWID&#xff09;标签标准&#xff0c;该标准定…

Flask教程2:flask高级视图

文章目录 add_url_rule类视图的引入装饰器的自定义与使用蓝图的使用url_prefix设置蓝图前缀 add_url_rule 欲实现url与视图函数的绑定&#xff0c;除了使用路由装饰器app.route&#xff0c;我们还可以通过add_url_rule(rule,endpointNone,view_funcNone)方法&#xff0c;其中&…