历史

STL 最初是一套独立的泛型库(Alexander Stepanov 等人贡献),后来被吸纳进 C++ 标准库;std::basic_string 则是早期 C++ 标准(Cfront / ARM 时代)就存在的“字符串类”,并非 STL 原生物。

std::string 实际上是 std::basic_string<char> 的 typedef,提供了类似 STL 容器的接口(迭代器、begin()/end()、push_back()、size() 等),因此常被称作“近似 STL 容器”或“序列式容器”。

模拟实现 string 类

实现 string 类的构造、拷贝构造、赋值运算符重载、析构函数、以及对 string 对象的增删改查。

成员变量

private:char* _str;size_t _size;size_t _capacity;

构造函数

提供缺省参数的构造函数,使用常量字符串初始化;因为需要兼容 C 字符串的 '\0',所以为 _str 多开 1 字节的空间用以存放 '\0' 。

myString(const char* str = ""):_size(strlen(str)),_capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}

拷贝构造 

注意需要对成员 _str 指向的空间深拷贝;另外还有一种写时拷贝可以进一步节省空间并提高效率。

	myString(const myString& str):_str(nullptr),_size(str.size()),_capacity(str.capacity()){reserve(str.capacity());	// reserve 中已经为 '\0' 多开了 1 字节strcpy(_str, str.c_str());}

增 -- push_back

如果要向 _str 尾部增加元素,先要检查空间是否足够,如果不够,封装扩容逻辑至函数 reserve:

reserve

异地扩容至 n+1 字节,多出来的 1 字节是给 '\0' 的

	void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];	// 开新空间strcpy(tmp, _str);	// 拷贝数据,memcpy 也行swap(tmp, _str);	// 交换指针delete[] tmp;	// 释放旧空间}}

接着在 push_back 中检查空间是否足够,如果不够,使用一个 三目表达式 来决定扩容至多大空间;最后向开辟好的空间内写入字符,并将下一个位置设置为 '\0',以兼容 C 类型的字符串;

	void push_back(const char& ch){if (_size == _capacity){size_t new_capacity = _capacity == 0 ? 2 : _capacity * 2;reserve(new_capacity);}_str[_size] = ch;_str[_size + 1] = '\0';_size++;}

增 -- insert

标准库中实现了很多重载,我来模拟一部分:

myString& insert(size_t pos, const myString& str)

在 pos 位置,插入同类型 myString str;那么需要向后挪动 pos 位置之后的元素,每个元素挪动 str._size 个空间;这里需要获取形参 str 的私有变量,实现简单的函数来获取:

	size_t size(){return _size;}size_t capacity(){return _capacity;}

接下来向后挪动元素,但是要判断所需空间是否足够,不够就扩容:

	myString& insert(size_t pos, const myString& str){if (_capacity < _size + str.size())	// 判断接下来需要的空间是否足够{reserve(_size + str.size());}}

发现编译这段报错,原因是形参 str 对象被 const 修饰,不能调用非 const 成员函数,将成员函数也用 const 修饰其 this 指针即可:

	size_t size() const{return _size;}size_t capacity() const{return _capacity;}

扩容问题解决,从 pos 位置开始,向后挪动元素:

	myString& insert(size_t pos, const myString& str){if (_capacity < _size + str.size())	// 判断接下来需要的空间是否足够{reserve(_size + str.size());}size_t begin = pos, end = _size;	// _size 处的'\0'一起挪动while (end >= begin)	// 挪动数据{_str[end + str.size()] = _str[end];end--;}}

现在向 pos 处写入 str._str 的数据,但需要获取 str._str 私有指针变量以访问其数据:

	char* c_str() const{return _str;}

插入数据之后,返回原 myString 对象引用:

myString& insert(size_t pos, const myString& str){if (_capacity < _size + str.size())	// 判断接下来需要的空间是否足够{reserve(_size + str.size());}size_t begin = pos, end = _size;	// _size 处的'\0'一起挪动while (end >= begin)	// 挪动数据{_str[end + str.size()] = _str[end];end--;}for (size_t i = 0; i < str.size(); i++)	// 插入数据{_str[pos + i] = str.c_str()[i];}return *this;}

但是在挪动数据时,有个 bug :如果传入形参 pos 为 0 ,也就是头插;

那么 begin 为 0 ,挪动数据 while 的循环终止条件是 end < begin ,end 要等于 -1 循环才会终止;

但关键问题在于 end 变量类型是无符号整数 size_t,当 end = 0,再 end-- 之后,end 会变成 42亿多,这使 while 变成死循环;

解决办法:可以强制类型转换,不会有什么问题

		int begin = (int)pos, end = (int)_size;	// _size 处的'\0'一起挪动while (end >= begin)	// 挪动数据{_str[end + str.size()] = _str[end];end--;}

myString& insert (size_t pos, size_t n, char c)

在 pos 处插入 n 个 c 字符:判断空间容量是否足够 -- 挪动数据 -- 插入数据,与上面的实现类似,累了。

增 -- append

理解为什么大佬们会吐槽 string 的设计繁琐了(lll¬ω¬)

myString& append (const myString& str)

追加同类型 myString :检查容量,copy 该 myString 数据到 this->_str 末尾。

	myString& append(const myString& str){if (_capacity < _size + str.size()){reserve(_size + str.size());}strcpy(_str + _size, str.c_str());	// strcpy 会一并拷贝字符串末尾的 '\0'return *this;}

删 -- pop_back

删掉最后一个元素,这个接口是 C++11 增加的,可能是为了向 STL 的容器看齐吧

    void pop_back(){_size--;}

删 -- erase

myString& erase (size_t pos = 0, size_t len = npos)

从 pos 开始,删掉 len 个字符,npos 是无符号最大整数 0x7fffffff

实际上,是将从 pos+len 到 _size 的数据向前挪动 len 个位置进行覆盖,最后 _size-len 即可

注意,如果 pos+len > _size ,即 len > _size-pos ,就会越界访问,所以要处理 len 过大的问题

	myString& erase(size_t pos = 0, size_t len = string::npos){assert(pos < _size);if (len > _size - pos)	// 处理 len 过大造成越界访问{len = _size - pos;}for (size_t i = 0; i < _size-(pos+len)+1; i++)	// 向前覆盖数据{_str[pos + i] = _str[pos + len + i];}_size -= len;return *this;}

查 -- find

size_t find (const myString& str, size_t pos = 0) const

从 pos 位置开始,找子串,直接调用 C库函数 strstr 找,strstr 找到返回 该子串在原字符串中的指针,找不到会返回 NULL

	size_t find(const myString& str, size_t pos = 0) const{char* sub = strstr(_str + pos, str.c_str());if (sub){return sub - _str;}else{return string::npos;}}

size_t find(char c, size_t pos)

找字符首次出现位置

    size_t find(char c, size_t pos){for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;}

创建子串 -- substr

myString substr (size_t pos = 0, size_t len = npos) const

从字符串 pos 位置开始,取 len 个字符创建新的子串,并传值返回

为新对象开空间,拷贝子串内容

	myString substr(size_t pos = 0, size_t len = string::npos) const{assert(pos < _size);if (len > _size - pos)	// 处理 len 过大造成越界访问{len = _size - pos;}myString sub;sub.reserve(len);	// 存 len 字节memcpy(sub._str, _str + pos, len);	// memcpy可以拷贝固定大小sub._size = len;sub._str[_size] = '\0';	// len 不包括 '\0'return sub;}

赋值运算符重载

myString& operator= (const myString& str)

两个已存在的对象之间的赋值运算符重载,注意深拷贝

	myString& operator= (const myString& str){char* tmp = new char[str.capacity() + 1];strcpy(tmp, str._str);delete[] _str;	// 释放旧空间_str = tmp;	// 指向新空间_size = str._size;_capacity = str._capacity;}

析构函数

~myString()

注意释放对象中的资源即可

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

小结

基本没有复用,主要是练个手熟;要想快,还得当 CV programmer!

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

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

相关文章

Golang学习笔记--语言入门【Go-暑假学习笔记】

目录 基础语法部分相关概念 基础语法部分概念详解 可见性 导包 内部包 运算符 转义字符 函数 风格 函数花括号换行 代码缩进 代码间隔 花括号省略 三元表达式 数据类型部分相关概念 数据类型部分概念详解 布尔类型 整型 浮点型 复数类型 字符类型 派生类型…

linux中kill 命令使用详解

在Linux系统里&#xff0c;kill命令的主要功能是向进程发送信号&#xff0c;以此来控制进程的运行状态。下面为你详细介绍它的使用方法&#xff1a; 基础语法 kill [选项] [进程ID]进程ID也就是PID&#xff0c;可通过ps、pgrep或者top等命令来获取。 常用信号及其含义 信号可以…

Nginx 安装与 HTTPS 配置指南:使用 OpenSSL 搭建安全 Web 服务器

Nginx 安装与 HTTPS 配置指南:使用 OpenSSL 搭建安全 Web 服务器 一、Nginx安装 1. 安装依赖项 sudo yum groupinstall "Development Tools" -y # 非必须 sudo yum install pcre pcre-devel zlib zlib-devel openssl openssl-devel -y2.下载Nginx wget http://n…

写个 flask todo app,简洁,实用

- 此项目虽然看起来简单&#xff0c;实际上&#xff0c;修改成自己喜欢的样子&#xff0c;也是费时间的。 - 别人都搞AI 相关的项目&#xff0c;而我还是搞这种基础的东西。不要灰心。 - 积累。不论项目大小&#xff0c;不论难易&#xff0c;只看是否有用。项目地址&#xff1a…

4麦 360度定位

要在 ESP32 上用 4 个麦克风实现 360 声源定位&#xff0c;通常思路是通过 时延估计&#xff08;TDOA&#xff09; 几何计算&#xff0c;核心流程&#xff1a;阵列布置将 4 个麦克风等间距布置成正方形&#xff08;或圆形&#xff09;。记阵列中心为原点&#xff0c;麦克风编号…

使用yolov10模型检测视频中出现的行人,并保存为图片

一、使用yolov10模型检测视频中出现的行人&#xff0c;并保存为图片&#xff0c;detect_person.py代码如下&#xff1a;from ultralytics import YOLOv10 import glob import os import cv2 import argparsedef detect_person(videoPath, savePath):if not os.path.exists(save…

现在希望用git将本地文件crawler目录下的文件更新到远程仓库指定crawler目录下,命名相同的文件本地文件将其覆盖

git checkout main git pull origin main $source “D:\黑马大数据学习\crawler” $dest Join-Path (Get-Location) “crawler” if (-not (Test-Path $dest)) { New-Item -ItemType Directory -Path $dest | Out-Null } Copy-Item -Path $source* -Destination $dest -Recur…

网络调制技术对比表

&#x1f4ca; 网络调制技术全维度对比表​调制技术​​简称​​频谱效率​​抗噪性​​功率效率​​复杂度​​关键特性​​典型应用场景​​幅度键控​ASK低差高低/低电路简单&#xff0c;易受干扰遥控器、光通信(OOK)​频移键控​FSK低-中中中中/中抗噪较好&#xff0c;频谱…

优化 Elasticsearch JVM 参数配置指南

一、概述 Elasticsearch 是基于 JVM 的搜索和分析引擎。JVM 参数的合理配置直接影响着 Elasticsearch 的性能和稳定性。尽管 Elasticsearch 已经提供了默认的 JVM 设置&#xff0c;但在某些特定场景下&#xff0c;我们可能需要进行适当的调整和优化。 本文将详细讲述如何安全、…

Python, Go 开发如何进入心流状态APP

要开发一款基于Python和Go语言、帮助用户进入“心流”状态&#xff08;高度专注、高效愉悦的心理状态&#xff09;的应用&#xff0c;需结合两种语言的技术优势&#xff08;Go的高并发与性能、Python的灵活性与AI生态&#xff09;及心流触发机制&#xff08;清晰目标、即时反馈…

一文详解手机WiFi模块与连接

目录 1 硬件模块 1.1 Wifi射频模 1.2 电源管理模块 2 软件与协议栈 2.1 系统服务层 2.2 认证与协议处理 3 连接流程 3.1 开启WiFi与扫描 3.2 选择网络与认证 3.3 连接与IP分配 4 特殊连接方式 4.1 WPS快速连接 4.2 热点模式&#xff08;AP模式&#xff09; 4.3 U…

Java 网络编程详解:从基础到实战,彻底掌握 TCP/UDP、Socket、HTTP 网络通信

作为一名 Java 开发工程师&#xff0c;你一定在实际开发中遇到过需要与远程服务器通信、实现客户端/服务端架构、处理 HTTP 请求、构建分布式系统等场景。这时&#xff0c;Java 网络编程&#xff08;Java Networking&#xff09; 就成为你必须掌握的核心技能之一。Java 提供了丰…

Java面试题(中等)

1. 计算机网络传输层有哪些协议&#xff1f;分别适用于什么场景&#xff1f;TCP协议(传输控制协议)​&#xff1a;面向连接、可靠传输&#xff0c;流量控制、拥塞控制。适用于要求数据完整性的场景&#xff0c;如文件传输、网页浏览、电子邮件等。UDP协议 (用户数据报协议)​&a…

Apache 消息队列分布式架构与原理

消息队列 基本概念 定义 消息队列&#xff08;Message Queue, MQ&#xff09;是一种分布式中间件&#xff0c;通过异步通信、消息暂存和解耦生产消费双方的机制&#xff0c;提供消息的顺序性保证、可靠投递和流量控制能力&#xff0c;广泛应用于微服务解耦、大数据流处理等场景…

ModernBERT如何突破BERT局限?情感分析全流程解析

自2018年推出以来&#xff0c;BERT 彻底改变了自然语言处理领域。它在情感分析、问答、语言推理等任务中表现优异。借助双向训练和基于Transformer的自注意力机制&#xff0c;BERT 开创了理解文本中单词关系的新范式。然而&#xff0c;尽管成绩斐然&#xff0c;BERT 仍存在局限…

股票Level2逐笔成交及十档订单簿分钟级Tick历史行情数据详细解析

本地股票数据处理与分析实战指南 在量化投资与金融数据分析领域&#xff0c;高效处理本地存储的股票数据是核心能力之一。本文将从数据类型定义、解析流程及实际应用角度&#xff0c;系统介绍如何基于CSV文件管理股票分钟数据、高频Tick数据、逐笔数据、Level2历史行情等多样化…

面向互联网2C业务的分布式类Manus Java框架

本文介绍了阿里巴巴推出的分布式类ManusAgent框架——ali-langengine-dflow&#xff0c;旨在解决现有Agent架构在互联网2C业务场景中的局限性。文章从背景出发&#xff0c;分析了当前主流Agent架构&#xff08;如Manus、字节TARS、AutoGLM&#xff09;存在的问题&#xff0c;如…

Java-82 深入浅出 MySQL 内部架构:服务层、存储引擎与文件系统全覆盖

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; AI炼丹日志-30-新发布【1T 万亿】参数量大模型&#xff01;Kim…

开发避坑短篇(6):Vue+Element UI 深度选择器实现表单元素精准对齐的技术实践

需求 el-form 表单的el-input和el-select默认宽度度不一致&#xff0c;导致不对齐&#xff0c;如下图。那么如何设置让el-input和el-select的宽度度一致并对齐&#xff1f;<el-form class"page-form" :model"addForm" :rules"rules" :disable…

rust-参考与借用

参考与借用 在清单4-5中的元组代码的问题在于&#xff0c;我们必须将String返回给调用函数&#xff0c;这样我们才能在调用calculate_length之后继续使用String&#xff0c;因为String已经被移动到了calculate_length中。相反&#xff0c;我们可以提供一个对String值的引用。引…