STL讲解——模拟实现string

news2024/12/24 11:38:27

STL讲解——模拟实现string

经典的string类问题

大厂在面试中,面试官总喜欢让学生自己来模拟实现string类,最主要是实现string类的增、删、查、改、构造、拷贝构造、赋值运算符重载以及析构函数。大家看下自己可不可以写一个string类?

class string
{
public:
 string(const char* str = "")
 {
 // 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
 if(nullptr == str)
 {
 assert(false);
 return;
 }
 
 _str = new char[strlen(str) + 1];
 strcpy(_str, str);
 }
 
 ~string()
 {
 if(_str)
 {
 delete[] _str;
 _str = nullptr;
 }
 }
 
private:
 char* _str;
};

大家肯定会想想我刚刚这样设计一个string类吧?可是你不觉得少了好多东西吗?

缺少什么呢:
1.缺少拷贝构造、赋值构造函数(虽然可以默认生成,但是都是浅拷贝
这种开辟空间的类肯定是不行的,析构函数会多次析构同一片区域)。
2.增删查改一个都没有。
3.析构函数需要自己编写。
4.iterator和re_iterator也没有编写(还有const形式的)。

仔细讲解一下为什么浅拷贝会引起报错:

在这里插入图片描述
就是说浅拷贝是:有个同学的抄你作业,把你的名字都给抄上了,这肯定有问题呀,一个班有两个你,老师一定要批评叫你家长呢!回到编译器方面,一个地址被释放一次变为空,可是还要再释放一次(该地址就变成了野指针了)释放野指针肯定会报错呀。(free(nullptr)是没问题的哦,但是释放野指针就会报错了

浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共
享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为
还有效,所以 当继续对资源进项操作时,就会发生发生了访问违规。要解决浅拷贝问题,C++中引入了深拷
贝。

深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情
况都是按照深拷贝方式提供。

在这里插入图片描述
也就是说我单独开一个空间,把我要复制的内容都复制过来,复制到了一个新空间(容器)中,等于这两个除了里面的内容一样,但是空间地址不一样了。

开始写正确的string类

先写一个命名空间,把自己设计的类放到你自己写的命名空间中,防止你有时候和std中的string冲突了。
以后自己写代码时最好不要把std库里面的东西都释放出来,自己写东西也设计一个命名空间。

传统版本:

namespace tom
{
	class string
	{
	public:
		//构造函数
		string(const char* str="")
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str=new char[_capacity+ 1];
			strcpy(_str, str);
		}

		/*拷贝构造*/
		string(const string& s)
			:_str(new char[strlen(s._str) + 1])
		{
			strcpy(_str, s._str);
		}
		//赋值构造
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* Str = new char[strlen(s._str) + 1];
				if (Str)
				{
					strcpy(Str, s._str);
					delete[] _str;
					_str = Str;
					_size = s._size;
					_capacity = s._capacity;
				}
				else
				{
					cout << "赋值失败" << endl;
				}
				
			}
		}

		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = NULL;
			}
			
		}
	private:
		char* _str;
		size_t _capacity;
		size_t _size;
	};

这里的构造函数最好是把size和capacity在初始化列表中就初始化了,但是呢,
开空间还在构造函数中完成,不是说不能在初始化列表中完成,而是当你没有初始化string 是传一个‘\0’,
所以给一个缺省值“”,没错不写任何东西,默认里面只有一个‘\0’

析构函数
设计一个判断如果是空指针就不用处理了。

现代版本:

namespace tom
{
	class string
	{
	public:
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		//构造函数
		string(const char* str="")
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str=new char[_capacity+ 1];
			strcpy(_str, str);
		}

		/*拷贝构造*/
		/*string(const string& s)
			:_str(new char[strlen(s._str) + 1])
		{
			strcpy(_str, s._str);
		}*/
		string(const string& s)//现代写法
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			/*this->swap(tmp);*/
			swap(tmp);
		}

		//赋值构造
		string& operator=(string s)
		{
			/*delete[] _str;
			_str = new char[strlen(s._str) + 1];
			strcpy(_str, s._str);*/

			//现代写法
			swap(s);
			return *this;
		}

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

		static const size_t Npos=-1;
	};

swap()
需要自己写一个string::swap()函数,为什么非要写一个类的swap呢,用函数库(algorithm.h)内的swap会有三次深拷贝,会降低效率。可是类内部的swap只用交换内置类型就可以了代价小很多。

然后利用传值拷贝形成临时拷贝变量,和this指针内的所有内容交换一下,由于是临时拷贝,出了作用域就会调用析构函数自动析构临时变量。太方便了!(要善于利用特性与机制)

增加关键细节

size()

设计一个string的size()函数 这个函数虽然很容易,但是相当重要。

//size
		size_t size()const
		{
			return _size;
		}

size()和lenth()是一样的所以就不写lenth()了。

[ ]方括号函数重载

就和字符串,数组的随机访问一样——arr[n] 或者str[n]
其实就是传元素的引用(因为是可以修改的,这时候是不是觉得引用的设计太棒了)

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

也一定要设计一个const类型

扩容

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

		void resize(size_t n, char ch = '\0')
		{
			if (n <= _size)
			{
				_str[n] = '\0';
				_size = n;
			}
			else 
			{
				if (n > _capacity)
				{
					reserve(n);
				}
				
				memset(_str + _size, ch, n - _size);
				_size = n;
				_str[n] = '\0';
			}
			
		}

reserve:就是普通的扩容,但是不能初始化。
resize:可以扩容,并且初始化你想要的字符。(还可以缩容)

迭代器

namespace tom
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		typedef char* reverse_iterator;

		const_iterator begin() const
		{
			return _str;
		}

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

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		reverse_iterator rbegin()
		{
			return _str + _size;
		}

		reverse_iterator rend()
		{
			return _str;
		}
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = NULL;
			}
			
		}
	private:
		char* _str;
		size_t _capacity;
		size_t _size;

		static const size_t Npos=-1;
	};
}

增、删、查、改

增加的设计

尾插
可以设计成一个尾插一个字母,在设计一个尾插一个字符串
于是乎设计一个push_back()尾插一个字母.
设计一个append()尾插一个字符串。
有了append()既可以设计一个运算符重载“+=”。


		void push_pack(char ch)
		{
			if (_size == _capacity)
			{
				reserve(_capacity==0?4:_capacity * 2);
				//扩容一定要写这个判断,
				//因为刚开始是一个空字符串的话capacity=0,
				//乘二还是0.
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

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

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

这里的“+=”函数一定要重载一下(pushback()和append())。

删除

//删除
		string& erase(size_t pos=0 , size_t len=Npos)
		{
			assert(pos < _size);
			if (len==Npos||pos+len>=_size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

有了erase函数就可以函数服用设计一个pop_back()函数

void pop_back()
		{
			if (_size > 0)
			{
				this->erase(_size - 1, 1);
			}
		}

查找

查找一个字符还是很简单的,循环判断就可以了。

		size_t find(char ch)
		{
			for (size_t i = 0; i < _size; i++)
			{
				if (ch == _str[i])
				{
					return i;
				}
			}
			return Npos; 
		}
		

还可以设计一个查找字符串。再加一个小功能,指定位置开始查找,如果不给位置,再给个缺省值也行。(缺省值pos为0)

//查询整个字符串是否存在
		size_t find(const char* s,size_t pos=0)
		{
			const char* ptr = strstr(_str + pos, s);
			if(ptr==nullptr)
			{
				return Npos;
			}
			return ptr - _str;
		}

这个返回的位置就是ptr(肯定是大于等于_str)减去_str的值。.

更改

可以插入一个字符或者一个字符串。

//插入
		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			size_t end = _size+1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = ch;
			++_size;

			return *this;
		}
		string& insert(size_t pos, const char* s)
		{
			assert(pos <= _size);
			size_t len = strlen(s);
			if (_size+len >= _capacity)
			{
				reserve(_size+len);
			}
			size_t end = _size +len;
			while (end > pos)
			{
				_str[end] = _str[end - len];
				--end;
			}
			//strcpy(_str + pos, s); 绝不能用,因为会把\0 也复制过去的。
			strncpy(_str + pos, s,len);
			_size+=len;
			return *this;
		}

有了这个插入函数就可以再设计一下push_back():

		void push_pack(char ch)
		{
			insert(_size,ch);
		}

		void append(const char* str)
		{
			insert(_size,str);
		}

这就简洁许多了,要学会复用。
其实学会服用,面试的时候真的可以在十分钟之内写完string类。

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

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

相关文章

第七章 - 聚合函数(count,avg,sum,max,min)和一些数学函数

第七章 - 聚合函数使用别名 ascount() 计数avg() 平均值sum() 求和max() 最大值min() 最小值一些数学计算函数Abs()Cos()Exp()Mod()Pi()radians()Sin()Sqrt()Power()Ceil()Floor()使用别名 as 在SQL中可以使用 as 来为一个字段或者一个值设置新的别名下面聚合函数的使用中就会…

chatgpt-api使用指南【官方泄露版】

chatgpt-api是 OpenAI ChatGPT 的非官方的 Node.js 包装器。 包括 TS 类型定义。 chatgpt-api不再需要任何浏览器破解——它使用泄露出来的OpenAI官方ChatGPT 在后台使用的模型。 &#x1f525; 推荐&#xff1a;使用 NSDT场景设计器 快速搭建 3D场景。 ✨你可以使用它开始构建…

dbeaver工具连接达梦数据库

、一 概述 DBeaver 是一个基于 Java 开发&#xff0c;免费开源的通用数据库管理和开发&#xff0c;DBeaver 采用 Eclipse 框架开发&#xff0c;支持插件扩展&#xff0c;并且提供了许多数据库管理工具&#xff1a;ER 图、数据导入/导出、数据库比较、模拟数据生成等&#xff0…

贝叶斯分析法在市场调研中的应用

一、市场调研的需求场景 在营销活动的用研调研时,我们经常会去问用户在不同平台的品类付费情况,以对比大促期间本品和竞品分别在哪些品类上具有市场优势,他们之间的差距具体在哪里、差距有多大。假如根据调研问卷结果,我们知道拼多多用户有30%的人在大促购买生鲜类,而淘宝…

7个营销人员常见的社交媒体问题以及解决方法

在如今的数字营销时代&#xff0c;许多营销人员都害怕在社交媒体上犯错。他们担心他们的社交媒体中的失误会演变成一场公关危机。面对一些常见的社交媒体问题&#xff0c;您需要知道如何避免和解决。对于数字营销人员来说&#xff0c;在现在这个信息互通&#xff0c;每时每刻都…

死锁检测组件-设想

死锁检测组件-设想 现在有三个临界资源和三把锁绑定了&#xff0c;三把锁又分别被三个线程占用。&#xff08;不用关注临界资源&#xff0c;因为锁和临界资源是绑定的&#xff09; 但现在出现这种情况&#xff1a;线程1去申请获取锁2&#xff0c;线程2申请获取锁3&#xff0c;…

【23种设计模式】行为型模式详细介绍(下)

前言 本文为 【23种设计模式】行为型模式 相关内容介绍&#xff0c;下边将对访问者模式&#xff0c;模板模式&#xff0c;策略模式&#xff0c;状态模式&#xff0c;观察者模式&#xff0c;备忘录模式&#xff0c;中介者模式&#xff0c;迭代器模式&#xff0c;解释器模式&…

面试官:熔断和降级有什么区别?

熔断和降级都是系统自我保护的一种机制&#xff0c;但二者又有所不同&#xff0c;它们的区别主要体现在以下几点&#xff1a; 概念不同触发条件不同归属关系不同 1.概念不同 1.1 熔断概念 “熔断”一词早期来自股票市场。熔断&#xff08;Circuit Breaker&#xff09;也叫自…

为SQL Server配置连接加密

前言很多客户在对数据库做安全审计时要求配置连接加密&#xff0c;本文就如何配置加密以及使用证书做一个系统的整理。连接加密首先&#xff0c;连接加密不是透明数据加密&#xff0c;很多人经常把两个概念混淆。连接加密是指客户端程序和SQL Server通信时的加密&#xff0c;保…

aws codebuild 自定义构建环境和本地构建

参考资料 Extending AWS CodeBuild with Custom Build Environments Docker in custom image sample for CodeBuild codebuild自定义构建环境 在创建codebuild项目的时候发现 构建环境是 Docker 映像&#xff0c;其中包含构建和测试项目所需的所有内容的完整文件系统 用ru…

实现一个简易koa2(一)— 基础架构

Koa 是一个新的 web 框架&#xff0c;由 Express 幕后的原班人马打造&#xff0c; 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数&#xff0c;Koa 帮你丢弃回调函数&#xff0c;并有力地增强错误处理。 Koa 并没有捆绑任…

从一致性角度考虑推荐冷启动长尾推荐问题(一)

前言&#xff1a;目前中长尾推荐的方法有很多&#xff0c;主流的方法有几类比如:1)在没有项目ID嵌入的情况下提高推荐模型的鲁棒性&#xff0c;2)利用有限的交互数据提高学习效率&#xff0c;如使用元学习方法;3)利用物品侧面信息&#xff0c;便于物品ID嵌入的初始化&#xff0…

如何将美国主机与电子邮件绑定

对于使用美国主机的网站所有者来说&#xff0c;将电子邮件与其主机服务绑定非常重要。这是因为绑定电子邮件可以帮助网站所有者更好地管理他们的网站和维护网站的安全&#xff0c;便于接收网站通知和警报、通过电子邮件管理网站以及更好地保护网站的安全。本文将就美国主机如何…

FileZilla Client(客户端)下载安装教程

FileZilla Client(客户端)下载安装教程 目录FileZilla Client(客户端)下载安装教程一、下载1.官网下载地址2.点击 Download FileZilla Client3.点击Download二、安装1.双击安装包2.点击 I Agree3.选择 “Anyone who uses this computer”4.全选&#xff08;勾选桌面图标&#x…

javaEE 初阶 — 传输层 TCP 协议中的异常情况与面向字节流的粘包问题

文章目录1 粘包问题1.1 什么是粘包问题1.2 如何解决粘包问题2 异常情况TCP 的十个特性&#xff1a;确认应答机制 超时重传机制 连接管理机制 滑动窗口 流量控制与拥塞控制 延迟应答与捎带应答 1 粘包问题 1.1 什么是粘包问题 面向字节流引入了一个比较麻烦的粘包问题。 …

BXC6332A第二代智能头盔方案助力电动车市场,为安全保驾护航

随着2020年6月1日起&#xff0c;公安部交管局在全国开展“一盔一带”安全守护行动&#xff0c;摩托车、电动车驾驶人乘车人按照规定正确使用头盔&#xff0c;是保障司乘安全的一道重要屏障&#xff0c;据统计&#xff0c;摩托车、电动自行车驾乘人员死亡事故中约80%为颅脑损伤致…

基于RK3588的嵌入式linux系统开发(三)——Uboot镜像文件合成

本章uboot镜像文件的合成包括官网必备文件rkbin下载和uboot镜像文件合成两部分内容&#xff0c;具体分别如下所述。 &#xff08;一&#xff09;下载rkbin文件包 以上uboot编译生成的uboot镜像不能直接烧录到板卡中运行&#xff0c;需要与atf、bl31、ddr配置文件等必备文件合成…

自动化测试工具_Jmeter

【课程简介】 接口测试是测试系统组件间接口的一种测试,接口测试天生为高复杂性的平台带来高效的缺陷监测和质量监督能力,平台越复杂&#xff0c;系统越庞大&#xff0c;接口测试的效果越明显。在接口测试大行其道的今天,测试工具也愈发重要,Jmeter作为一款纯 Java 开发的测试…

【刷题笔记】--二分查找binarysearch

当给一个有序的数组&#xff0c;在其中查找某个数&#xff0c;可以考虑用二分查找。 题目1&#xff1a; 二分查找的思路&#xff1a; 设置left和right指针分别指向要查找的区间。mid指针指向这个区间的中间。比较mid指针所指的数与target。 如果mid所指的数小于target&…

C语言——自定义类型:结构体,枚举,联合(详解)

1.结构体 1.1结构体类型的基础知识 结构体类型是一些值的集合&#xff0c;这些值被称为成员变量&#xff0c;成员变量可以是不同类型的变量。 1.2结构体类型的声明 结构体的声明格式如下&#xff1a; struct tag//tag表示标签名 {member-list;//成员列表//由1或者多个成员组成…