探索C++标准模板库(STL):String接口的底层实现(下篇)

news2025/6/8 7:12:59

前引:在C++的面向对象编程中,对象模型是理解语言行为的核心。无论是类的成员函数如何访问数据,还是资源管理如何自动化,其底层机制均围绕两个关键概念展开:this指针与六大默认成员函数。它们如同对象的“隐形守护者”,默默支撑着代码的健壮性与效率。本文将从技术底层出发,结合内存布局、编译器行为与实际案例,深入探讨!

本文目的:

在上篇我们学了各种string接口,此篇用来模拟实现各种接口功能,深入底层了解它的实现,来加深对它的理解,以及功能的熟悉

目录

String模拟实现

打印 

访问字符

迭代器

插入字符/字符串

从pos位置插入字符

从pos位置插入字符串

从pos位置删除字符

从pos位置删除一定字符

寻找标记指定位置

截取指定区间

调整指定大小的空间

调整指定大小的空间+初始化


String接口模拟实现

打印 

我们可以利用流提取来打印,返回字符串的指针就OK了,可以加上const更完美一点,如下:

//打印
const char* Read()const
{
	return _allocator;
}

cout提取到char *会默认当做字符串处理 

访问字符

在库里面这种[ ]访问是有两个版本的,一种是只读的,一种是可读可写的,如下:

所以我们要实现两个版本,编译器也会自己去匹配对应的函数:

左边的const是防止返回的对象被修改;右边的const是防止this指针指向的对象被修改

const char& operator[](int size)const
{
	//检查
	assert(size <= _size && size >= 0);
	return _allocator[size];
}
char& operator[](int size)
{
	//检查
	assert(size <= _size && size >= 0);
	return _allocator[size];
}

迭代器

迭代器有两种形式,一种是从前往后访问,一种是从后往前访问,我们这里来实现第一种

 其实iterator是char *类型的,只是在库里面被重定义了,然后实现 begin、end即可

iterator begin()
{
	return _allocator;
}
iterator end()
{
	return _allocator + _size;
}
const iterator begin()const
{
	return _allocator;
}
const iterator end()const
{
	return _allocator + _size;
}

效果展示:

auto 的底层其实就是迭代器,“换汤不换药”,例如:

插入字符/字符串

开空间:

插入字符/字符串只是插入的个数不同,所以我们可以根据插入的个数来选择扩容+拷贝原来字符串

//开空间
void reserve(size_t pos)
{
	char* tmp = nullptr;
	try
	{
		//开空间(传来的是最小空间,还要有\o)
		tmp = new char[pos + 1];
	}
	catch (const exception& _allocator)
	{
		cout << "空间增容失败" << endl;
	}
	//拷贝原字符
	strcpy(tmp, _allocator);
	//添加\0
	size_t size = strlen(tmp);
	tmp[size + 1] = '\0';
	//转移指向
	delete[]_allocator;
	_allocator = tmp;
}
void push_back(char c)
{
	size_t pos = strlen(_allocator) + 1;
	reserve(pos);
}
void append(const char* stl)
{
	size_t pos = strlen(_allocator) + strlen(stl);
	reserve(pos);
}

 存储:

根据传过来的字符/字符串在空间末尾添加上就行

//存储
void operator+=(char p1)
{
	_allocator[_size++] = p1;
}
void operator+=(const char* p2)
{
	strcpy(_allocator + _size, p2);
}

效果展示:

从pos位置插入字符

在某个位置插入字符很简单,我们来实现从pos位置插入 n 个字符

插入流程:

判断pos的合法性、开空间、移动原来字符、插入新字符

注意:

(1)在里面需要注意下标的使用,传的pos究竟是下标还是字符个数

(2)开空间需要注意给\0留位置,以及自动设置末尾的\0

(3)需要更新元素个数

//指定位置插入字符
void insert_t(int pos, int n , char c)
{
	//pos是个数不是下标
	//检查位置的合法性
	assert(pos >= 1 && pos <= _size);
	//开空间
	char* tmp = new char[n + _size +1 ];
	strcpy(tmp, _allocator);
	tmp[n + _size] = '\0';
	delete[]_allocator;
	_allocator = tmp;
	//更新_size
	_size += n;
	//移动原来的字符
	for (int i = (_size + n); (i-n) >= pos ;i--)
	{
		_allocator[i - 1] = _allocator[i - n - 1];
	}
	//插入新字符
	for (int i = pos; n > 0; i++)
	{
		_allocator[i - 1] = c;
		n--;
	}
}

效果展示:

从pos位置插入字符串

这个较于插入字符比较简单一些,开空间、插入字符串都没有什么大的变化,如下:

这种需要调试的多,重要的是下标关系,新手可能需要在草稿纸上演示

//指定位置插入字符串
void insert(int pos, const char* _stl)
{
	//pos是个数不是下标
	//检查位置的合法性
	assert(pos >= 1 && pos <= _size);
	//开空间
	int n = strlen(_stl);
	char* tmp = new char[n + _size + 1];
	strcpy(tmp, _allocator);
	tmp[n + _size] = '\0';
	delete[]_allocator;
	_allocator = tmp;
	//更新_size
	_size += n;
	//移动原来的字符
	for (int i = (_size ); (i - n) >= pos; i--)
	{
		_allocator[i - 1] = _allocator[i - n - 1];
	}
	//插入新字符
	int j = 0;
	for (int i = pos; n > 0; i++)
	{
		_allocator[i-1] = _stl[j++];
		n--;
	}
}

从pos位置删除字符

模拟成员函数:

删除字符不需要去开空间,可以在目标位置插入\0即可,记得更新字符个数

//指定位置删除字符
void erase(size_t pos)
{
	//判断位置有效性
	assert(pos >= 1 && pos <= _size);
	//删除
	_allocator[pos - 1] = '\0';
    _size--;
}

从pos位置删除一定字符

这个接口的实现需要挪动后面的元素,整体来说还是很简单的

注意:更新字符个数,新字符串的末尾添加\0

void erase_t(size_t pos, size_t n)
{
	//判断位置有效性
	assert(pos >= 1 && (pos+n-1)<=_size);
	_size -= n;
	//挪动元素
	int i = 1;
	for (i = pos ; _allocator[i + n -1] != '\0' ;  i++)
	{
		_allocator[i - 1] = _allocator[i + n - 1];
	}
	//插上\0
	_allocator[i-1] = '\0';
}

寻找标记指定位置

模拟库函数:

此函数有多种形式,我们来实现最简单的一种:

给一个位置,从此位置开始寻找目标,如果找到了就返回目标开始的下标位置,否则返回npos

实现思路:

假设给了一个字符串,和开始寻找的位置,从开始位置开始搜寻,遇到可能相同的目标开始确认,找到则返回开始的下标位置,否则返回npos(npos是无符号整型的最大值)

//找指定字符串
size_t find(size_t pos, const char* _stl)
{
	//判断位置的合法性
	assert(pos >= 1 && pos <= _size);
	//从指定位置开始找目标
	const char* pc = _stl;
	while (1)
	{
		//找到可能目标
		if (_allocator[pos - 1] == *_stl)
		{
			//确认目标
			int tmp = pos;
			pc = _stl;
			while (pc)
			{
				//开始逐一比较字符
				if (*pc == _allocator[tmp - 1])
				{
					pc++;
					if (*pc == '\0')
					{
						return pos;
					}
					tmp++;
					if (pos >= _size)
					{
						return npos;
					}
				}
				else
				{
					//如果不相等
					pos++;
					tmp = pos;
					break;
				}
			}
		}
		else
		{
			//如果没有找到,就继续,直到
			pos++;
			if (pos >= _size)
			{
				return npos;
			}
		}
	}
	return npos;
}

效果展示:

截取指定区间

此函数通常是与上面标记的函数同时使用,由标记的函数先找指定位置,然后再截取,需要注意给的区间的有效性

原理:

截取一端区间打印即可

//截取指定位置
void substr(size_t p1, size_t p2)
{
	//判断范围
	assert(p2 <= _size && p1 >= 1);
	size_t pos = 1;
	//找左区间
	while (_allocator[p1 - 1] != _allocator[pos-1])
	{
		pos++;
	}
	//开始打印
	while ((pos-1) != p2)
	{
		cout << _allocator[pos-1];
		pos++;
	}
	cout << endl;
}

效果展示:

调整指定大小的空间

我们模拟的resize,输入空间大小,将自动开辟指定大小的空间,没有初始化操作

//开空间
void reserve(size_t pos)
{
	char* tmp = nullptr;
	try
	{
		//开空间(传来的是最小空间,还要有\o)
		tmp = new char[pos + 1];
	}
	catch (const exception& _allocator)
	{
		cout << "空间增容失败" << endl;
	}
	//拷贝原字符
	strcpy(tmp, _allocator);
	//添加\0
	size_t size = strlen(tmp);
	tmp[pos] = '\0';
	//转移指向
	delete[]_allocator;
	_allocator = tmp;
}
调整指定大小的空间+初始化

调整规则:

           如果 size_t 大于当前字符串长度,则将剩余的字符初始化为指定的内容,否则转为空格

           如果 size_t 小于当前字符串长度,则缩短为其前 n 个字符,并删除超出第 n 个字符的字符

如果小于_size直接缩短即可,如果大于我们又要麻烦的分情况,所以我们统一处理为开空间

void resize(size_t n, char c = '\0')
{
	//对n判断
	assert(n >= 1);
	//判断n的大小
	if (n < _size)
	{
		//缩短字符串
		_allocator[n - 1] = '\0';
	}
	else
	{
		//开空间
		reserve(n);
		//初始化
		for (size_t i = _size; i < n; i++)
		{
			_allocator[i] = c;
		}
	}
}

效果展示:

                                               【雾非雾】期待与你的下次相遇!

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

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

相关文章

Flutter知识点汇总

Flutter架构解析 1. Flutter 是什么?它与其他移动开发框架有什么不同? Flutter 是 Google 开发的开源移动应用开发框架,可用于快速构建高性能、高保真的移动应用(iOS 和 Android),也支持 Web、桌面和嵌入式设备。。它与其他移动开发框架(如 React Native、Xamarin、原…

​线性注意力 vs. 传统注意力:效率与表达的博弈新解

​核心结论​&#xff1a;线性注意力用计算复杂度降维换取全局建模能力&#xff0c;通过核函数和结构优化补足表达缺陷 一、本质差异&#xff1a;两种注意力如何工作&#xff1f; ​特性​传统注意力&#xff08;Softmax Attention&#xff09;线性注意力&#xff08;Linear At…

YOLO在QT中的完整训练、验证与部署方案

以下是YOLO在QT中的完整训练、验证与部署方案&#xff1a; 训练方案 准备数据集&#xff1a; 收集数据&#xff1a;收集与目标检测任务相关的图像数据集&#xff0c;可以是公开数据集如COCO、Pascal VOC&#xff0c;也可以是自定义数据集。标注数据&#xff1a;使用标注工具如…

增量式网络爬虫通用模板

之前做过一个项目&#xff0c;他要求是只爬取新产生的或者已经更新的页面&#xff0c;避免重复爬取未变化的页面&#xff0c;从而节省资源和时间。这里我需要设计一个增量式网络爬虫的通用模板。可以继承该类并重写部分方法以实现特定的解析和数据处理逻辑。这样可以更好的节约…

【JVM】三色标记法原理

在JVM中&#xff0c;三色标记法是GC过程中对象状态的判断依据&#xff0c;回收前给对象设置上不同的三种颜色&#xff0c;三色分为白色、灰色、黑色。根据颜色的不同&#xff0c;决定对象是否要被回收。 白色表示&#xff1a; 初始状态&#xff1a;所有对象未被 GC 访问。含义…

【uniapp开发】picker组件的使用

项目uniapp&#xff0c;结合fastadmin后端开发 picker组件的官方文档说明 https://en.uniapp.dcloud.io/component/picker.html#普通选择器 先看效果&#xff1a; 1、实现设备类型的筛选&#xff1b;2、实现设备状态的筛选&#xff1b; 前端代码&#xff08;节选&#xff0…

【HarmonyOS Next之旅】DevEco Studio使用指南(三十一) -> 同步云端代码至DevEco Studio工程

目录 1 -> 同步云函数/云对象 1.1 -> 同步单个云函数/云对象 1.2 -> 批量同步云函数/云对象 2 -> 同步云数据库 2.1 -> 同步单个对象类型 2.2 -> 批量同步对象类型 3 -> 一键同步云侧代码 1 -> 同步云函数/云对象 说明 对于使用DevEco Studio…

go-zero微服务入门案例

一、go-zero微服务环境安装 1、go-zero脚手架的安装 go install github.com/zeromicro/go-zero/tools/goctllatest2、etcd的安装下载地址根据自己电脑操作系统下载对应的版本&#xff0c;具体的使用自己查阅文章 二、创建一个user-rpc服务 1、定义user.proto文件 syntax &qu…

Python控制台输出彩色字体指南

在Python开发中&#xff0c;有时我们需要在控制台输出彩色文本以提高可读性或创建更友好的用户界面。本文将介绍如何使用colorama库来实现这一功能。 为什么需要彩色输出&#xff1f; 提高可读性&#xff1a;重要信息可以用不同颜色突出显示更好的用户体验&#xff1a;错误信息…

开源之夏·西安电子科技大学站精彩回顾:OpenTiny开源技术下沉校园,点燃高校开发者技术热情

开源之夏2025编程活动正在如火如荼的进行中&#xff0c;当前也迎来了报名的倒计时阶段&#xff0c;开源之夏组织方也通过高校行系列活动进入各大高校&#xff0c;帮助高校开发者科普开源文化、开源活动、开源技术。 6月4日 开源之夏携手多位开源技术大咖、经验型选手走进西安电…

解决数据库重启问题

最近部署软件时&#xff0c;发现mysql会一直在重启&#xff0c;记录下解决办法&#xff1a; 1.删除/home/dataexa/install/docker/datas/mysql路径下的data文件夹 2.重新构建mysql docker-compose up -d --build mysql 3.停掉所有应用&#xff0c;在全部重启&#xff1a; do…

前后端交互过程中—各类文件/图片的上传、下载、显示转换

前后端交互过程中—各类文件/图片的上传、下载、显示转换 图片补充&#xff1a;new Blob()URL.createObjectURL()替代方案&#xff1a;FileReader.readAsDataURL()​​对比&#xff1a; tiff文件TIFF库TIFF转换通过url转换tiff文件为png通过文件选择的方式转换tiff文件为png 下…

数据库同步是什么意思?数据库架构有哪些?

目录 一、数据库同步是什么 &#xff08;一&#xff09;基本概念 &#xff08;二&#xff09;数据库同步的类型 &#xff08;三&#xff09;数据库同步的实现方式 二、数据库架构的类型 &#xff08;一&#xff09;单机架构 &#xff08;二&#xff09;主从复制架构 &a…

【数据结构】详解算法复杂度:时间复杂度和空间复杂度

&#x1f525;个人主页&#xff1a;艾莉丝努力练剑 ❄专栏传送门&#xff1a;《C语言》、《数据结构与算法》 &#x1f349;学习方向&#xff1a;C/C方向 ⭐️人生格言&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平 前言&…

Rest-Assured API 测试:基于 Java 和 TestNG 的接口自动化测试

1. 右键点击项目的文件夹&#xff0c;选择 New > File。 2. 输入文件名&#xff0c;例如 notes.md&#xff0c;然后点击 OK。 3. 选择项目类型 在左侧的 Generators 部分&#xff0c;选择 Maven Archetype&#xff0c;这将为你生成一个基于 Maven 的项目。 4. 配置项目基…

react public/index.html文件使用env里面的变量

env文件 ENVdevelopment NODE_ENVdevelopment REACT_APP_URL#{REACT_APP_URL}# REACT_APP_CLIENTID#{REACT_APP_CLIENTID}# REACT_APP_TENANTID#{REACT_APP_TENANTID}# REACT_APP_REDIRECTURL#{REACT_APP_REDIRECTURL}# REACT_APP_DOMAIN_SCRIPT#{REACT_APP_DOMAIN_SCRIPT}#pu…

chili3d 笔记17 c++ 编译hlr 带隐藏线工程图

这个要注册不然emscripten编译不起来 --------------- 行不通 ---------------- 结构体 using LineSegment std::pair<gp_Pnt, gp_Pnt>;using LineSegmentList std::vector<LineSegment>; EMSCRIPTEN_BINDINGS(Shape_Projection) {value_object<LineSegment&g…

创建一个纯直线组成的字体库

纯直线组成的字体&#xff0c;一个“却”由五组坐标点组成&#xff0c;存储5个点共占21字节&#xff0c;使用简单&#xff0c;只要画直线即可&#xff0c; “微软雅黑”&#xff0c;2个轮廓&#xff0c;55坐标点&#xff0c;使用复杂&#xff0c;还填充。 自创直线字体 “微软…

Linux进程(中)

目录 进程等待 为什么有进程等待 什么是进程等待 怎么做到进程等待 wait waitpid 进程等待 为什么有进程等待 僵尸进程无法杀死&#xff0c;需要进程等待来消灭他&#xff0c;进而解决内存泄漏问题--必须解决的 我们要通过进程等待&#xff0c;获得子进程退出情况--知…

【计算机组成原理】计算机硬件的基本组成、详细结构、工作原理

引言 计算机如同现代科技的“大脑”&#xff0c;其硬件结构的设计逻辑承载着信息处理的核心奥秘。从早期程序员手动输入指令的低效操作&#xff0c;到冯诺依曼提出“存储程序”概念引发的革命性突破&#xff0c;计算机硬件经历了从机械操控到自动化逻辑的蜕变。本文将深入拆解…