C++初阶:string类的模拟自实现

目录

  • 1. 引子
  • 2. 自实现string类功能模块
  • 3. string类功能模块的具体实现
    • 3.1 默认成员函数
    • 3.2 遍历访问相关成员函数
    • 3.3 信息插入相关成员函数
    • 3.4 信息删除
    • 3.5 信息查找
    • 3.6 非成员函数
    • 3.7 杂项成员函数
  • 4. 补充知识

1. 引子

  1. 通过对string类的初步学习,没有对知识进行较深度学习了解剖析,只是囫囵吞枣地学会其的使用方式。
  2. 仅仅掌握string类的使用方式,对我们熟练掌握使用STL的相关容器及其背后的深层知识是远远不够的。
  3. 因此,我们来通过模拟尝试自实现的方式,对string类背后的知识与编程思想做一个更深入的学习掌握。

2. 自实现string类功能模块

  1. 默认成员函数:构造,拷贝构造,析构,operator=运算符重载
  2. 杂类成员函数:size,c_str,swap,substr
  3. 遍历访问相关成员函数:operator[],const_operator[],iterator(迭代器)
  4. 信息插入,扩容相关成员函数:append,push_back,operator+=,reserve,insert
  5. 信息删除相关成员函数:erase
  6. 信息查找相关成员函数:find
  7. 非成员函数:operator+,<<,>>

3. string类功能模块的具体实现

3.1 默认成员函数

string类的结构,成员变量

class string
{
private:
	//指向存储数据地址的指针
	char* _str;
	//能够存储有效字符的容量
	int _capacity;
	//有效字符的长度
	int _size;
	
	//正常定义方式:类内声明,类外声明类域定义初始化

	//特殊定义方式
	const static size_t npos = -1;
};

1. 构造函数

  1. 字符串构造
    <1> 使用初始化列表的构造方式,初始化容量与size都需要使用strlen函数计算一遍字符串长度
    <2> strlen计算字符串长度的方式为暴力遍历法,多次调用会使得效率低下
    <3> 先用strlen初始化capacity,再使用capacity初始化其他两个成员变量的方式,会使得成员变量的定义方式变得死板
//字符串构造
string(const char* str)
{
	//开辟空间
	int len = strlen(str);
	_str = new char[len + 1];
	//拷贝
	strcpy(_str, str);
	//处理尾部
	_str[len] = '\0';
	_capacity = len;
	_size = len;
}
  1. 无参构造
    <1> 无参构造时,初始化str不能使用空指针,当我们使用流插入操作符对str的内容进行打印时,cout会将其自动默认为字符串进行打印,对其进行解引用,而对空指针进行解引用会出现bug
    <2> 无参构造时,不意味着指针为空,无参构造时只是有效字符的长度为0,因,我们可以在空间中存储一个’\0’字符进行标识
//无参构造,空字符串
string()
{
	_str = new char[1]{ '\0' };

	_capacity = 0;
	_size = 0;
}

2. 析构函数

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

3. 拷贝构造函数

//string(const string& str)
//{
//	_str = new char[str._capacity + 1];
//	strncpy(_str, str._str, str._size);
//	
//	_size = str._size;
//	_capacity = str._capacity;
//
//	_str[_size] = '\0';
//}

string::string(const string& str)
{
	//现代写法
	string tmp(str._str);

	swap(tmp);
}

4. operator=运算符重载

//string& string::operator=(const string& str)
//{
//	//优化:
//	//参数变为string
//	//再将拷贝形参与当前string进行交换

//	char* tmp = new char[str._size + 1];
//	strcpy(tmp, str._str);

//	_size = str._size;
//	_capacity = str._capacity;

//	delete[] _str;
//	_str = tmp;

//	return *this;
//}

string& string::operator=(string str)
{
	//现代写法
	swap(str);

	return *this;
}

3.2 遍历访问相关成员函数

1. operator[]

  1. operator[]的访问方式需要支持普通string类与const修饰的string类的调用,因此,要进行重载两次
//普通string类调用
char& operator[](size_t pos)
{
	assert(pos < _size);
	
	return _str[pos];
}
//const修饰的string类调用
const char& operator[](size_t pos) const
{
	assert(pos < _size);

	return _str[pos];
}

2. iterator

typedef char* iterator;

iterator begin()
{
	return _str;
}

iterator end()
{
	//最后一个字符后一位
	return _str + _size;
}

3. const修饰的iterator

typedef const char* const_iterator;

const_iterator begin()const
{
	return _str;
}

const_iterator end() const
{
	return _str + _size;
}

4. 范围for的访问方式

  1. 此遍历方式的底层实现为,将迭代器的遍历方式进行替换,当迭代器线管函数的名与库中不同时,范围for的遍历方式也不能正常使用(err:Begin())
for(auto e : s)
{
	cout << e;
}
cout << endl;

3.3 信息插入相关成员函数

1. 扩容成员函数reserve

  1. 将string类的空间容量进行调整,当指定参数大于当前空间容量时进行扩容,小于时不会进行缩容
//插入相关
void reserve(int size)
{
	if (size > _capacity  )
	{
		//申请一段额外的空间
		//失败抛异常
		char* tmp = new char[size + 1];

		//将值进行拷贝
		strncpy(tmp, _str, _size);

		//销毁原空间
		delete[] _str;

		//调整
		_str = tmp;
		_capacity = size;
	}
}

2. push_back

void push_back(const char& c)
{
	if (_capacity == _size)
	{
		reserve(_size + 1);
		_str[_size] = c;
		_size++;
		_str[_size] = '\0';
	}
}

3. append

  1. 拼接一串字符串
void append(const char* str)
{
	int len = strlen(str);
	if (len + _size > _capacity)
	{
		reserve(len + _size);
	}

	strncpy(_str + _size, str, len);

	_size += len;
	_str[_size] = '\0';
}
  1. 拼接参数string类的内容
void append(const string& s)
{
	int len = s.size();
	if (len + _size > _capacity)
	{
		reserve(len + _size);
	}

	strncpy(_str + _size, s.c_str(), len);

	_size += len;
	_str[_size] = '\0';
}

4. insert

  1. 给指定位置插入一个字符
//在pos位置插入一个字符
void insert(size_t pos, const char& c)
{
	assert(pos < _size);
	
	if (_size == _capacity)
	{
		reserve(_size + 1);
	}
	
	//当pos为0时
	int end = _size;
	//pos类型为size_t,比较时,end隐式类型转换为size_t类型
	//当插入位置为0时,会出现bug
	while (end >= (int)pos)
	{
		_str[end + 1] = _str[end];
		end--;
	}
	
	_str[pos] = c;
	
	_size++;
	_str[_size] = '\0';
}
  1. 给指定位置插入一串字符串
//在pos位置插入一串字符串
void insert(size_t pos, const char* str)
{
	assert(pos < _size);

	int len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	//当pos为0时
	int end = _size;
	while (end >= (int)pos)
	{
		_str[end + len] = _str[end];
		end--;
	}

	strncpy(_str + pos, str, len);
	_size += len;
	_str[_size] = '\0';
}

5. operator+=

  1. 在当前string类后拼接参数string类的内容
string& operator+=(const string& str)
{
	if (_size + str._size > _capacity)
	{
		reserve(_size + str._size + 1);
	}

	strncpy(_str + _size, str._str, str._size);
	
	_size += str._size;
	_str[_size] = '\0';

	return *this;
}
  1. 在当前string类后拼接参数字符串
string& string::operator+=(const char* str)
{
	append(str);

	return *this;
}
  1. 拼接字符(复用)
string& string::operator+=(const char c)
{
	push_back(c);

	return *this;
}

3.4 信息删除

1. erase

  1. 从指定下标开始,向后删除指定个元素
//在pos位置插入一串字符串
void string::erase(size_t pos, size_t len)
{
	assert(pos < _size);

	//直接添加'\0'
	if (len == npos || pos + len > _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		//连'\0'一起拷贝
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
}

3.5 信息查找

1. find

  1. 从指定位置开始向后查找指定的字符
size_t find(const char& c, size_t pos = 0)
{
	assert(pos < _size);
	int i = pos;
	while (i < _size)
	{
		if (_str[i] == c)
		{
			return i;
		}
		i++;
	}

	return npos;
}
  1. 从指定位置开始,向后查找指定字符串
//查找字符串
size_t find(const char* str, size_t pos = 0)
{
	assert(pos < _size);
	char* cur = strstr(_str + pos, str);
	if (cur == nullptr)
	{
		return npos;
	}

	return cur - _str;
}

3.6 非成员函数

1. operator<<

  1. 流插入操作符重载,非成员函数,非友元,非静态成员函数
  2. 间接访问方式,不直接访问成员变量
ostream& operator<<(ostream& out, const string& str)
{
	//将每一个字符的拷贝插入到流中
	for (auto e : str)
	{
		out << e;
	}

	return out;
}

2. operator>>

  1. 流提取操作重载
istream& operator>>(istream& in, string& str)
{
	str.clear();

	char data[128] = "";
	//从流中获取字符
	char ch = in.get();
	int i = 0;
	while (ch != ' ' && ch != '\n')
	{
		data[i++] = ch;
		//溢出,截断
		if (i == 127)
		{
			data[i] = '\0';
			str += data;
			break;
		}

		ch = in.get();
	}

	if (i < 127)
	{
		data[i] = '\0';
		str += data;
	}

	return in;
}

3. operator+

  1. 返回一个临时的string类,其内容为当前string类与参数字符串拼接
string operator+(const string& str1, const string& str2)
{
	string tmp(str1);

	return tmp += str2;
}

string operator+(const string& str1, const char* str2)
{
	string tmp(str1);

	return tmp += str2;
}

4. swap

  1. 非成员函数swap,交换两个string类,优先级高于算法库中swap模板
void swap(string& str1, string& str2)
{
	//会生成中间变量
	string tmp = str1;
	str1 = str2;
	str2 = tmp;
}

3.7 杂项成员函数

1. c_str

  1. 以C语言类型的字符串方式返回string类的内容
const char* c_str() const
{
	return _str;
}

2. substr

  1. 返回内容为当前string类子串的string类
string string::substr(size_t pos, size_t len)
{
	assert(pos < _size);

	string tmp;
	int end = pos + len;
	if (pos + len > _size || len == npos)
	{
		end = _size;
	}

	//先预先扩容
	reserve(len + 1);
	for (int i = pos; i < end; i++)
	{
		tmp += _str[i];
	}

	return tmp;
}

3. swap

  1. 成员函数swap,交换两者数据指针,效率高
void swap(string& str)
{
	//直接调用库函数
	std::swap(_str, str._str);
	std::swap(_capacity, str._capacity);
	std::swap(_size, str._size);
}

4. 补充知识

  1. Linux下g++编译器对于string类的实现
    <1>string类的结构
    <2>拷贝构造方式:浅拷贝 + 引用计数 + 写时拷贝

1. Linux环境g++编译器string类的结构

  1. string类的成员函数只有一个指针(8字节),指针指向的结构,如下图:

在这里插入图片描述
2. 引用计数与浅拷贝

  1. <1> 当我们进行拷贝构造时,先只进行浅拷贝,使得拷贝而得的新对象的数据指针与原对象指向同一块数据空间
    <2> 当新创建的对象的被调用数据需要修改时再进行拷贝,而后修改(写实拷贝)
    <3> 当一块数据空间的引用计数清零时,才会进行指针的销毁
    <4> 这样的拷贝方式,如果拷贝构造得到对象不被修改时会提高效率,较少开销

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大数据面试题 —— Zookeeper

目录 ZooKeeper 的定义ZooKeeper 的特点ZooKeeper 的应用场景你觉得Zookeeper比较重要的功能ZooKeeper 的选举机制 ***zookeeper主节点故障&#xff0c;如何重新选举&#xff1f;ZooKeeper 的监听原理 ***zookeeper集群的节点数为什么建议奇数台 ***ZooKeeper 的部署方式有哪几…

牛客题霸-SQL进阶篇(刷题记录一)

本文基于前段时间学习总结的 MySQL 相关的查询语法&#xff0c;在牛客网找了相应的 MySQL 题目进行练习&#xff0c;以便加强对于 MySQL 查询语法的理解和应用。 由于涉及到的数据库表较多&#xff0c;因此本文不再展示&#xff0c;只提供 MySQL 代码与示例输出。 部分题目因…

C语言之我对结构体与联合体的认识

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

Huggingface 笔记:大模型(Gemma2B,Gemma 7B)部署+基本使用

1 部署 1.1 申请权限 在huggingface的gemma界面&#xff0c;点击“term”以申请gemma访问权限 https://huggingface.co/google/gemma-7b 然后接受条款 1.2 添加hugging对应的token 如果直接用gemma提供的代码&#xff0c;会出现如下问题&#xff1a; from transformers i…

基于Spring Boot的社区垃圾分类管理平台的设计与实现

摘 要 近些年来&#xff0c;随着科技的飞速发展&#xff0c;互联网的普及逐渐延伸到各行各业中&#xff0c;给人们生活带来了十分的便利&#xff0c;社区垃圾分类管理平台利用计算机网络实现信息化管理&#xff0c;使整个社区垃圾分类管理的发展和服务水平有显著提升。 本文拟…

WordPress自动生成原创文章插件

WordPress作为最受欢迎的内容管理系统之一&#xff0c;为博客和网站的搭建提供了便捷的解决方案。而在内容创作方面&#xff0c;自动生成原创文章的插件为WordPress用户提供了更为高效的选项。 什么是WordPress自动生成原创文章插件&#xff1f; WordPress自动生成原创文章插件…

Rust 错误处理入门和进阶

Rust 错误处理入门和进阶 引用 Rust Book 的话&#xff0c;“错误是软件中不可避免的事实”。这篇文章讨论了如何处理它们。 在讨论 可恢复错误和 Result 类型之前&#xff0c;我们首先来谈谈 不可恢复错误 - 又名恐慌(panic)。 不可恢复错误 恐慌(panic)是程序可能抛出的异…

C++第七弹---类与对象(四)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、拷贝构造函数 1.1、概念 1.2、特征 2、运算符重载 2.1、等号运算符重载 总结 1、拷贝构造函数 1.1、概念 在现实生活中&#xff0c;可能…

学习Python,需要知道的经典案例

文章目录 一、Python简介二、Python经典案例1. 猜数字游戏2. 文本文件处理3. 网络爬虫4. 数据可视化5. 电子邮件发送6. 实现一个简单的Web服务器。 三、Python处理IP相关知识点1. 处理IP地址2. 网络编程&#xff08;TCP/IP&#xff09;3. 使用第三方库处理IP信息 四、相关链接 …

Java安全 反序列化(1) URLDNS链原理分析

Java安全 反序列化(1) URLDNS链原理分析 文章目录 Java安全 反序列化(1) URLDNS链原理分析前置知识应用分析payload1.新建HashMap类2.新建URL类3.获取URL 的 Class对象4.通过反射访问URL内部变量5.通过反射为URL中类赋值6.调用HashMap#put方法传入key和value7.再次通过反射为UR…

基于单片机的智能台灯设计1.42

摘 要 社会在发展&#xff0c;时代在进步&#xff0c;人们对生活质量需求更加膨胀&#xff0c;是否拥有高科技技术也最终决定着产品是否可以满足人们的欲望&#xff0c;只有性价比更高&#xff0c;才可以得到更好的青睐。现在的电子产品愈来愈多&#xff0c;龙蛇混杂&#xff…

vue使用element-ui 实现自定义分页

element-ui文档截图&#xff0c;plus大同小异。 可以通过插槽实现自定义的分页。在layout里面进行配置。 全部代码 //page.js export default {name:Cuspage,props:{total:Number,},data(){return {currentPage:1,pageSize:10,}}methods: {setslot (h) {return(<div cla…

tinyrenderer-Bresenham绘制直线算法

如何画线段 第一种尝试 求x&#xff0c;y起始点的差值&#xff0c;按平均间隔插入固定点数 起始点平均插入100个点&#xff1a; void line(int x0, int y0, int x1, int y1, TGAImage& image, TGAColor color) {for (float t 0.; t < 1.; t .01) {int x x0 (x1 -…

力扣每日一题 2024/3/19 好子数组的最大分数

题目描述 用例说明 思路讲解 好子数组的下标在i<k<j,即nums[k]必须在好子数组当中&#xff0c;将数组以k为分界点分为左右两半&#xff0c;左半left从k-1向左移动&#xff0c;右半right从k1开始出发向右移动。 当left大于等于分界点的值时左移一位&#xff0c;当right大…

tp8 mpdf 导出pdf

1. 安装mpdf composer require mpdf/mpdf 2. 然后 使用 use mpdf\Mpdf; 或者 require_once __DIR__ . /vendor/autoload.php; 官方文档 mPDF – mPDF 手册 文档里有很多东西 可以自己去研究 3. 编写代码 下载 (支持中文) $mpdf new Mpdf([mode > utf-8,"autoS…

CMeet系列技术生态沙龙---《探索未来:生成式AI赋能千行百业·杭州》

当前数字化浪潮下&#xff0c;生成式AI技术正成为推动产业升级、提升竞争力的关键力量。为深入探索未来AI技术的赋能作用&#xff0c;促进技术生态的繁荣与发展&#xff0c;CSDN-CMeet系列沙龙活动旨在搭建一个交流与探索的平台&#xff0c;通过分享前沿研究成果和应用案例&…

服务器数据恢复—光纤环境互斥不当导致存储VMFS卷损坏的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某公司的信息管理平台&#xff0c;通过3台虚拟机共享了一台存储设备供企业内部使用&#xff0c;存储设备中存放了公司内部重要的数据文件。 由于业务增长的需要&#xff0c;管理员又在这个存储网络上连接了一台Windows server服务器&a…

媒体邀约:推广方法

1.媒体邀约推广媒体邀约推广是一种通过与商务合作以增强曝光度和名气的营销手段。积极与商务合作&#xff0c;公司能将产品和服务推广给更大范围受众人群&#xff0c;以达到增加销量和市场占有率目地。 1.1 选择适合自己的新闻媒体选择适合自己的新闻媒体是媒体邀约推广的关键…

sqllab第35-45关通关笔记

35关知识点&#xff1a; 宽字节注入数值型注入错误注入 payload:id1andextractvalue(1,concat(0x7e,database(),0x7e))0--联合注入 payload:id0unionselect1,database(),version()-- 36关知识点&#xff1a; 字符型注入宽字节注入错误注入 payload:id1%df%27andextractvalue(…

Qt实现简单的五子棋程序

Qt五子棋小程序 Qt五子棋演示及源码链接登陆界面单机模式联机模式联网模式参考 Qt五子棋 参考大佬中国象棋程序&#xff0c;使用Qt实现了一个简单的五子棋小程序&#xff0c;包含了单机、联机以及联网三种模式&#xff1b;单机模式下实现了简易的AI&#xff1b;联机模式为PtoP…