【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.rhkb.cn/news/318725.html

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

相关文章

SpirngBoot整合快递100

目录 一、注册快递100 二、技术文档地址 三、需要认证的key和comcumer 四、spring boot 整合快递 100使用 4.1 引入快递100和hutool的依赖 4.2 将key和comcumer写入application.properties文件中 4.3 新建一个modle,用于将查出来的json数据转成对象 4.4 新建一个controll…

网络安全实训Day16

网络空间安全实训-渗透测试 漏洞扫描 定义 扫描和探测目标范围内的主机存在哪些安全漏洞&#xff0c;或扫描目标范围内的那些主机存在某个指定的漏洞 漏扫工具 AWVS APPScan MSF 使用MSF扫描漏洞并利用 1.搜索需要的攻击模块 search ms17-010 2.使用攻击模块 use 模块名称…

Python 植物大战僵尸

文章目录 效果图项目结构实现思路源代码 效果图 项目结构 实现思路 下面是代码的实现思路&#xff1a; 导入必要的库和模块&#xff1a;首先&#xff0c;我们导入了Python的os、time库以及pygame库&#xff0c;还有植物大战僵尸游戏中用到的各个植物和僵尸的类。 初始化游戏和…

ElasticSearch01(ES简介,安装ES,操作索引,操作文档,RestAPI)【全详解】

目录 一、ES简介 1. 数据库查询的问题 2. ES简介 1 ElasticSearch简介 2 ElasticSearch发展 3. 倒排索引【面试】 1 正向索引 2 倒排索引 4. ES和MySql 5. 小结 二、安装ES 1. 方式1:使用docker安装 1 准备工作 2 创建ElasticSearch容器 3 给ElasticSearch配置i…

有限单元法-编程与软件应用(崔济东、沈雪龙)【PDF下载】

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

vue2项目webpack3.x打包文件分割优化加载

vue2项目webpack3.x打包文件分割优化加载 0. 项目目录和依赖信息1. 开启 gzip&#xff08;建议&#xff09;2. vue2项目配置懒加载&#xff08;建议&#xff09;3. 拆分 vendor 包注意&#xff1a;webpack3使用CommonsChunkPlugin实现 本文使用 3 种方案进行叠加优化 优先级按以…

postman一直转圈圈,无法启动

解决 地址栏输入%appdata%进入此目录&#xff0c;删除%appdata%目录下的postman文件可以解决问题。

node.js中path模块-路径处理,语法讲解

node中的path 模块是node.js的基础语法&#xff0c;实际开发中&#xff0c;我们通过使用 path 模块来得到绝对路径&#xff0c;避免因为相对路径带来的找不到资源的问题。 具体来说&#xff1a;Node.js 执行 JS 代码时&#xff0c;代码中的路径都是以终端所在文件夹出发查找相…

未来科技的前沿:深入探讨人工智能的进展、机器学习技术和未来趋势

文章目录 一、人工智能的定义和概述1. 人工智能的基本概念2. 人工智能的发展历史 二、技术深入&#xff1a;机器学习、深度学习和神经网络1. 机器学习2. 深度学习3. 神经网络 三、人工智能的主要目标和功能1. 自动化和效率提升2. 决策支持和风险管理3. 个性化服务和预测未来 本…

SpringBoot中实现发送邮件

概要 在Spring Boot中发送电子邮件相对简单。你可以使用Spring的邮件支持来实现这一点。 步骤&#xff1a; 1.添加依赖&#xff1a;首先&#xff0c;需要在你的pom.xml文件中添加Spring Boot的邮件发送器依赖。 2. 配置邮件服务器&#xff1a;在application.properties或app…

网络相关知识总结

1、网口设置 网口设置IP&#xff0c;即操作/etc/sysconfig/network-scripts路径下的ifcfg-xx文件 主要参数详解&#xff1a; DEVICE:网口名 ONBOOT&#xff1a;表示启动系统时是否激活网卡&#xff0c;yes为激活&#xff0c;no不激活 HWADDR:mac值 DEFROUTE://默认路由设置…

RTMP 直播推流 Demo(一)—— 项目配置与视频预览

音视频编解码系列目录&#xff1a; Android 音视频基础知识 Android 音视频播放器 Demo&#xff08;一&#xff09;—— 视频解码与渲染 Android 音视频播放器 Demo&#xff08;二&#xff09;—— 音频解码与音视频同步 RTMP 直播推流 Demo&#xff08;一&#xff09;—— 项目…

linux 服务器利用阿里网盘API实现文件的上传和下载

文章目录 背景脚本初始化 阿里云盘API工具 aligo安装aligoaligo教程实战parse.py 演示上传文件上传文件夹下载文件下载文件夹 背景 最近在用ubuntu系统做实验&#xff0c;而ubuntu 系统的文件上传和下载操作很麻烦&#xff1b; 于是便打算使用阿里网盘的API 进行文件下载与上传…

Docker - 修改服务的端口

1. 测试 新建一个httpd服务 docker run -itd -p 1314:80 --name test -h test httpd 2. 先停止容器和 docke r服务 docker stop test #停止容器3. 修改配置 cd /var/lib/docker/containers ls 找到需要修改的 cd 1fc55f0d24014217cff68c9a417ca46cf50312caa5c9e6bb24085126…

为什么 IP 地址通常以 192.168 开头?(精简版)

网络通讯的本质就是收发数据包。如果说收发数据包就跟收发快递一样。IP地址就类似于快递上填的收件地址和发件地址一样&#xff0c;路由器就充当快递员的角色&#xff0c;在这个纷繁复杂的网络世界里找到该由谁来接收这个数据包&#xff0c;所以说&#xff1a;IP地址就像快递里…

django搭建一个AI博客进行YouTube视频自动生成文字博客

文章目录 一、生成Django框架二、项目代码&#xff08;前端&#xff09;1、编写前端代码&#xff08;正文界面&#xff09;1.1、生产html框架1.2、添加live preview扩展1.3、更改title元素中文本1.4、添加CDN&#xff08;CSS&#xff09;样式链接1.5、nav标签1.6、在body标签中…

OpenCV(三)—— 车牌筛选

本篇文章要介绍如何对从候选车牌中选出最终进行字符识别的车牌。 无论是通过 Sobel 还是 HSV 计算出的候选车牌都可能不止一个&#xff0c;需要对它们进行评分&#xff0c;选出最终要进行识别的车牌。这个过程中会用到两个理论知识&#xff1a;支持向量机和 HOG 特征。 1、支…

华为机考入门python3--(19)牛客19- 简单错误记录

分类&#xff1a;字符串 知识点&#xff1a; 分割字符串 my_str.split(\\) 字符串只保留最后16位字符 my_str[-16:] 列表可以作为队列、栈 添加元素到第一个位置 my_list.insert(0, elem) 增加元素到最后一个位置 my_list.append(elem) 删除第一个 my_list.pop(0)…

C/C++开发环境配置

配置C/C开发环境 1.下载和配置MinGW-w64 编译器套件 下载地址&#xff1a;https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/ 下载后解压并放至你容易管理的路径下&#xff08;我是将其放在了D盘的一个software的文件中管理&#xff09; 2.…

奈氏准则和香农定理

一、奈奎斯特和香农 哈里奈奎斯特&#xff08;Harry Nyquist&#xff09;(左) 克劳德艾尔伍德香农&#xff08;Claude Elwood Shannon&#xff09;(右) 我们应该在心里记住他们&#xff0c;记住所有为人类伟大事业做出贡献的人&#xff0c;因为他们我们的生活变得越来越精彩&…