STL——string模拟实现(一)

news2024/10/7 14:25:06

目录

构造函数的实现

拷贝构造

赋值重载

 const问题

 迭代器打印

范围for打印

运算符重载 

reserve模拟

插入数据

push_back

 append


构造函数的实现

先贴出一段错误代码:

#include<iostream>
#include<assert.h>
namespace zzl//避免与库冲突
{
	class string
	{
	public:
		string()
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{}
		string(const char* str)
			:_str(str)//_str要为const类型
			, _size(strlen(_str))
			, _capacity(strlen(_str))
		{}
	private:
		const char* _str;
		size_t _size;
		size_t _capacity;
	};
}

输出c风格的字符串:

    const char* c_str()//外部不加const,指针可以改变
		{
			return _str;//返回常量字符串
		}

11bafbfef1bf42e3bdfe44e73dd10d66.png大家能根据string.h文件的代码说出错误的地方在哪吗?

这个错误涉及对空指针解引用,也就是说,在我们通过c_str得到字符串内容的过程中,涉及对空指针解引用引发了这样的错误。

当我们使用cout输出一个指针变量时,默认情况下会输出该指针变量所指向的内存地址,而不是指针本身的值。
如果我们想输出指针的地址,可以将其强制转为(void*)使其打印地址。

除此之外,它还存在一些问题,我们再增加两个成员函数

    char& operator[](size_t pos)/支持改变返回值a[i]++
		{
			assert(pos < size)//\0不存有效数据
			return _str[pos];
		}
		size_t size()//模拟size()
		{
			return _size;
		}

在定义_str时,为了成功初始化也加上了const,但这样的问题就是无法改变其值。

我们定义的构造函数不能扩容这是问题之二。

对单参构造函数的改动:

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

这里我只初始化了_size,目的是避免依赖带来的初始化顺序不匹配问题,没有多次用strlenO(n)提高了效率,接着扩容(不要忘了\0!),并复制内容。而我们的const成员变量现在也可以去掉啦。

对无参构造的改动:

    string()
			:_str(new char[1])//匹配析构
			,_size(0)
			,_capacity(0)
		{
			_str[0] = '\0';
		}
		~string()
		{
			delete[] _str;
			_capacity = _size = 0;
		}

为了析构与扩容匹配,这里用[ ]。因为无参不存储有效数据,所以我们用\0初始化

成功输出: 

923450916ebc4ca38ba7029eb9fd9913.png

 根据我们学过的缺省知识,构造函数还可以再进行简化

如果我们不传参,默认传空串

string(const char* str = "\0")//""也可以
			:_size(strlen(str))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

可以看到逻辑是没有错的。为空串时,capacity和size都为0 

注意这里不要把值定为nullptr,后面strlen会解引用出错,也不要传递单个字符(strlen用来计算字符串)

拷贝构造

 先来看看编译器默认生成的拷贝构造 677b9e904820475893054e66fe6e620e.png

 成功实现了浅拷贝!

两大问题:1.析构两次程序崩溃 2.指针指向同一块空间,有篡改的风险

实现拷贝构造:

string(const string& s)
			:_size(s._size)
			,_capacity(s._capacity)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
		}

 成功实现深拷贝!

赋值重载

与拷贝构造稍微有点不同的是,赋值重载是对两个已经初始化后的操作数的运算,这就存在一个问题,两个操作数数据元素不同的问题,可能一方的数据远大于另一方,一方数据远小于另一方,当然也不排除两者数据差不多的情况。如果是前两种情况,会造成空间的大量浪费,所以我们可以使用提前释放空间的方式进行赋值。

string& operator=(const string& s)
		{
			delete[] _str;
			_str = new char[s._capacity + 1];
			_size = s._size;
			_capacity = s._capacity;//释放后更改的capacity
			strcpy(_str, s._str);
			return *this;
		}

这段代码还存在一个问题:

c994cf8b60cc4515a44c4dfc33be6478.png 由于释放了空间。自己给自己赋值将会是随机值

 所以我们加上判断:

	    if (this != &s)
			{}
			return *this;

注意不要用 ==判断,因为我们没有写对应的重载,用地址判断十分巧妙(引用的特性)。

开辟空间失败怎么办?可以抛异常的方式解决,抛异常意味着空间已经被delete,这样的后果是原空间不能被正常使用了。如果想避免这种情况也可以通过中间指针的方式解决。

string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;//指向堆区
				_size = s._size;
				_capacity = s._capacity;
			}

运行结果:

1071a5c685514b698cf7efc49312925d.png

 const问题

现在我们给定一个函数,让它打印string的值,观察现象

89d6987eb57d46d398cd75c61179b38c.png

 在函数体中调用[ ]重载发生了典型的权限放大问题,我们在学习库里的string时发现[ ]重载有两种类型,const和非const,这样做的好处就是支持一些情况下可读,可写

    const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}

注意这里两个const的用法:第一个const与引用构成常量引用返回,返回值_str[pos]作为一块堆上的空间(不在堆上申请也存在),在返回其别名后就不能修改了,而第二个const修饰this指针,与可写[]构成重载,且不能在体内改变其指向对象的值。

编译器会根据所传递的参数选择最适合的对象。

这提醒我们:在写一些可写类型的成员函数时,要注意是否需要提供它的仅读版本,这点很重要!

 迭代器打印

类内:

	typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str+size();
		}

因为是一块连续的空间,所以我们可以暂且用指针的方式模拟迭代器。
类外:

zzl::string::iterator it = s2.begin();
	while (it != s2.end())
	{
		cout << *it << ' ';
		++it;
	}
	cout << endl;
}

 需要添加 string::表明使用的是string内重定义的char*

范围for打印

和内置类型不同,我们使用auto输出自定义类型的数据时,它所调用的底层是调用我们自己写的迭代器。

        for (auto& ch : s)
			{
				cout << ch << ' ';
			}

类似define的直接替换,如果我将begin换成start它就会报错。

如果我把他放进我们刚才写的Print函数内又会出现一个新的问题——

权限放大

我们观察这段代码更容易理解:

	void Print(const string& s)
		{
            string::iterator it = s2.begin();
        }    

char* = const char*,明显的权限放大,所以迭代器也支持const版本进行readonly

typedef const char* const_iterator;
        const_iterator begin()const
		{
			return _str;
		}
		const_iterator end()const
		{
			return _str + size();
		}

运行结果: 

eec6f9b705d04c7099a5fd6bbbc967de.png

 注意这里的const修饰的是指针指向的内容,所以指针是可以++或--进行迭代的。

运算符重载 

思考一下,string的比较是比较二者的size大小还是capacity?

答案都不是。它们甚至连\0也不放过 ~_~

正确答案是比较ascii码值,我们可以用c中的strcmp函数复用,省区我们写的功夫。

66e52a56ded54933b6fe050aaf165344.png

	bool operator==(const string& s)const
		{
			return !strcmp(_str, s._str);
			//return strcmp(_str, s._str) == 0;
		}
		bool operator>(const string& s) const
		{
			return strcmp(_str, s._str) > 0;
		}
		bool operator>=(const string& s) const
		{
			//return *this > s || *this == s;
			return *this > s || s == *this;
		}

		bool operator<(const string& s) const
		{
			return !(*this >= s);
		}

		bool operator<=(const string& s) const
		{
			return !(*this > s);
		}

		bool operator!=(const string& s) const
		{
			return !(*this == s);
		}

希望大家在复用strcmp完后不要跟我一样比较错对象,后面的比较是两个string对象的比较而不是两个str指针的比较。同时注意加上const,以应付不时之需。

运行结果:

 注意流提取优先级高于比较运算符优先级得加上括号

reserve模拟

void reserve(size_t n)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	

推荐使用中间变量的方法扩容,扩容失败不至于原数据丢失。

插入数据

push_back

	void push_back(char ch)
		{
			if (_size + 1 > _capacity)
			{
				reserve(_capacity * 2);//复用
			}
	        _str[_size] = ch;//\0处插入
			_size++;
			_str[_size] = '\0';
		}

这里采用2倍扩策略,注意当用这种方式尾插时需要手动添加\0,以免无界出现乱码

这里还有一个隐藏很深的问题,如果一开始是无参的string传递的capacity为0就会造成capacity*2=0的情况,以至于我们添加的\0造成数组越界。为了避免这种情况,有两种解决方式

在构造时判断

_capacity = _size==0? 3:_size;

 在传参时判断

	reserve(_capacity == 0 ? 3: _capacity * 2);
    //reserve(_capacity*2+1);

 append

	void append(const char* s)
		{
			int len = strlen(s);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
            strcpy(_str + _size, s);
			_size += len;
		}

由于不知道字符串大小,就开辟相应字符串长度的大小即可,由于我们在reserve里为\0预留了空间,所以这里不再加1。

这里不推荐用strcat遍历数组查找\0的方式追加,我们知道要追加的位置直接用strcpy更便捷。

 运行结果:

 +=

直接复用我们这里实现的两个尾插接口:

string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* s)
		{
			append(s);
			return *this;
		}

今天就先讲到这,下次我们对string模拟进行一个收尾。

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

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

相关文章

Servlet 详解

目录 什么是 servlet? Servlet 是做甚的? 如何编写一个 Servlet 程序? 解析访问出错情况 Servlet 的运行原理 1. 接收请求 2. 根据请求计算响应 3. 返回响应 Servlet API 详解 HTTPServlet HttpServletRequset HttpServletResponse 什么是 servlet? Servlet 是…

String模拟实现(二)

resize resize的特点是扩容加初始化&#xff0c;如果所给的长度小于空间大小就会删除多余的数据。前面我们实现了reserve&#xff0c;但有这样一个问题&#xff0c;如果reserve的长度小于空间就会导致缩容&#xff0c;而我们知道&#xff0c;string中缩容用的是shrink_to_fit&a…

外观设计模式解读

目录 问题引进 传统方式解决影院管理 外观模式基本介绍 概念 外观模式原理类图 分类外观模式的角色 外观模式解决影院管理 传统方式解决影院管理说明 外观模式应用实例 外观模式的注意事项和细节 s统的内部细节 > 外观模式 外观模式基本介绍 概念 1) 外观模式&…

XGBoost的介绍

一、XGBoost的介绍 1.什么是XGBoost&#xff1f; XGBoost&#xff08;eXtreme Gradient Boosting&#xff09;是一种基于梯度提升树的机器学习算法&#xff0c;它在解决分类和回归问题上表现出色。它是由陈天奇在2014年开发的&#xff0c;如今已成为机器学习领域中最流行和强…

集合框架知识汇总

集合框架 集合 概念&#xff1a;对象的容器&#xff0c;定义了对多个对象进行操作的常用方法。可以实现数组功能 和数组的区别 数组长度固定&#xff0c;集合长度不固定 数组可以存储基本类型和引用类型&#xff0c;集合只能存储引用类型 总结 List集合 有序&#xff0c;有…

软考网工易混淆知识点总结(持续更新中,按照知识点先后排序)

1.数据编码--原码、反码和补码 原码 数值前面增加了一位符号位(即最高位为符号位)&#xff0c;该位为0时表示正数&#xff0c;为1时则表示负数&#xff0c;其余各位表示数值的大小反码 正数的反码与原码相同&#xff0c;负数的反码符号位为1&#xff0c;其余各位为该数绝对值的…

大型语言模(LLM) : 提示词工程(一)

今天我学习了DeepLearning.AI的 Prompt Engineering 的在线课程&#xff0c;我想和大家一起分享一下该门课程的一些主要内容。 下面是我们访问大型语言模(LLM)的主要代码&#xff1a; import openaiopenai.api_key XXXXXXXXXdef get_completion(prompt, model"gpt-3.5-…

高性能分布式API网关Kong

目录 1 kong网关简介2 为什么需要 API 网关2.1 和Spring Cloud Gateway区别 3 为什么要使用kong3.1 kong的组成部分3.2 Kong网关的特性 4 kong网关架构4.1 Kong网关请求流程 5 kong 部署5.1 搭建网络5.2 搭建数据库环境5.3 kong网关部署5.3.1 初始化kong数据5.3.2 启动Kong容器…

【项目】树莓派4B镜像安装

本文主要记录下如何使用Raspberry Pi image 软件进行树莓派镜像进行安装。 官网&#xff1a;Raspberry Pi OS – Raspberry Pi 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1G7z1Fdvk5Chmhj894WPU3A 提取码&#xff1a;xnzw 一、格式化SD卡 若SD卡存在…

释放潜能——解读新时代OEM竞争规则,打造精雕细琢的用户体验

从消费零售全领域的实践观察来看&#xff0c;仅仅凭借产品赢得竞争的时代已经过去&#xff0c;商业模式创新体现在越来越多企业向“产品服务”转变&#xff0c;向用户全生命周期需求挖掘转变。企业与消费者之间的关系从过去的一次性、断点式产品交易&#xff0c;转向持续性、覆…

读写ini配置文件(C++)

文章目录 1、为什么要使用ini或者其它(例如xml,json)配置文件&#xff1f;2、ini文件基本介绍3、ini配置文件的格式4、C读写ini配置文件5、 代码示例6、 配置文件的解析库 文章转载于&#xff1a;https://blog.csdn.net/weixin_44517656/article/details/109014236 1、为什么要…

基于 Web 和 Deep Zoom 的高分辨率大图查看器的实践

基于 Web 和 Deep Zoom 的高分辨率大图查看器的实践 高分辨率大图像在 Web 中查看可以使用 Deep Zoom 技术&#xff0c;这是一种用于查看和浏览大型高分辨率图像的技术&#xff0c;它可以让用户以交互方式浏览高分辨率大图像&#xff0c;并且能够在不影响图像质量的情况下进行…

搭建个人网站 保姆级教程(四)Navicat链接mySql 失败

长时间没有折腾云服务器上的mysql了&#xff0c;今天再次使用Navicat连接云服务器上的mysql时&#xff0c;输入密码报错&#xff01; 1130 - Host ‘119.130.212.168’ is not allowed to connect to this MySQL server 1.于是Royal TSX 远程服务器查看mysql的状态 systemctl …

通达信标记文字中可能用到的特殊符号大全

特殊符号是难以直接输入的符号&#xff0c;特殊符号是符号的一种&#xff0c;比如说圆圈&#xff08;〇&#xff09;、叉号&#xff08;✕、✖、✘&#xff09;、五角星&#xff08;★、☆&#xff09;、勾号&#xff08;✓、✔&#xff09; 。 01.特殊符号简表 ♠♣♧♡♥❤…

chatgpt赋能python:Python强制取整:如何在Python中正确进行取整操作

Python强制取整&#xff1a;如何在Python中正确进行取整操作 Python是一种广泛使用的编程语言&#xff0c;有许多不同的用途&#xff0c;包括数据分析、web开发、机器学习、科学计算等等。Python语言非常容易学习和使用&#xff0c;但有时候它的行为可能会出人意料&#xff0c…

alpa概览

文章目录 背景alpa简介DeviceMesh跨 DeviceMeshes 的 GPU Buffer管理Ray CollectivePipeline parallelism runtime orchestration运行时 背景 LLM训练有3D并行的需求&#xff08;alpa将数据并行视为张量并行&#xff0c;即张量沿batch切分&#xff09; 算子间并行的通信成本小…

【Vue】父子组件传参 孙子调用爷爷的方法 provide inject

一. 父传子 父组件先在data中定义要传给子组件的属性名父组件在中引入子组件在components中注册使用步骤 3 中注册好的子组件在 3 中&#xff0c;父传子 &#xff08;1&#xff09;利用 : 将父组件的对象、数组、字符串等传给子组件&#xff0c;供子组件使用 &#xff08;2&am…

Rocky Linux9安装教程

序言 Centos废了&#xff0c;最近在考虑将服务器迁移至Rockylinux系统&#xff0c;在这里记录下安装过程 当前安装版本RockyLinux9.2&#xff08;minimal版本&#xff09;&#xff0c;VMware Fusion专业版13.0.2 创建虚拟机 第一步&#xff1a; 先下载好ISO文件&#xff0c…

python-高级特性

文章目录 1.生成式2.生成器3.闭包4.装饰器&#xff08;1&#xff09;万能装饰器的实现&#xff08;2&#xff09;含参数的装饰器&#xff08;3&#xff09;多装饰器 5.内置高阶函数 1.生成式 列表生成式就是一个用来生成列表的特定语法形式的表达式。是Python提供的一种生成列表…

2023.6.7小记——什么是FPGA?

最近打算开始继续做一些个人分享&#xff0c;已经太久时间没有写文章了&#xff0c;感觉这样下去肯定不是个好事&#xff0c;当程序员当然是要保持分享~ 标题就暂时先以每天我认为最重要的一点来取&#xff0c;内容不仅限于此。 1. 什么是FPGA&#xff1f; 全称是Field-Progra…