string的模拟实现(下)

news2024/11/26 5:55:08

目录

string的模拟实现下

析构函数:

完善+=函数

空对象的构造函数:

头插函数的一些修正:

构造函数的完善:

实现append

插入函数:

插入函数(字符串)

erase删除函数:

实现find函数:

resize函数:

容量函数:

完善[]函数:

<<流插入:

>>流提取:

clear函数:

设计深拷贝

赋值重载:


string的模拟实现下

析构函数:

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

首先,我们先释放_str指向的内存空间,为了防止对已经释放过的内存进行访问,我们把_str置为空指针,既然已经对类对象完成了析构,那我们就把对象的容量和有效元素置为0.
 

完善+=函数

 我们并没有实现对于空对象的构造函数,我们现在进行实现:

空对象的构造函数:

string()
		{
			_str = new char[1];
			_str[0] = '\0';
			_size = _capacity = 0;
		}

我们首先申请一个字节的空间给_str,然后把_str的首位置置为\0,这个\0是字符串结束的标志,并不计入有效元素和容量的个数。

头插函数的一些修正:

void test_string1()
	{
		string s1;
		s1 += 'x';
	}

 当我们使用空对象s1+=一个字符时,我们先查看具体的函数调用:

 我们首先调用+=函数,函数内部调用尾插函数

 尾插函数需要扩容,当有效元素和容量相等时,我们把容量扩容到原来的二倍即可。

但是因为我们的对象s1的容量本身就为0,无法实现扩容:因为0的2倍也为0.

所以我们要对尾插函数的扩容进行优化,我们可以这样写:

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

当容量和有效元素相等时,我们进行判断,假如对象的容量为0时,我们把4赋给新的容量,假如对象的容量不为0时,我们把原容量扩容到原来的二倍赋给新容量,调用reserve函数进行扩容。

构造函数的完善:

我们需要考虑无参和有参的构造函数:

我们可以使用缺省值的方法:

一些同学会这样写构造函数,这样写对吗?

 不行,因为strlen是查找到str指向字符串的\0为止,而对于空指针str,无法使用strlen函数。

一些同学又会这样写:

 不行:因为char*表示字符类型的指针,所以不能用来接收'\0',但是我们可以取'\0'的ascll码,'\0'的ascll码是0,所以这里相当于str是空指针。对于空指针,我们是无法调用strlen函数。

我们可以这样写:

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

""就表示一个空字符串。

实现append

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

append表示追加一个字符串,我们对代码进行分析:

首先我们调用strlen函数求出追加字符串的字符个数,然后进行判断,如果原来的元素个数加上新的字符个数大于我们的容量,我们就调用reserve函数修改容量,然后调用strcpy函数,把要追加的字符串str拷贝到原字符串的末尾。

插入函数:

我们可以先实现在某一个为止插入一个字符。

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

我们对代码进行分析:

因为库里面的insert函数接口是这些:

 所以我们按照库里面走,返回值是string对象的引用。

这串代码的意思是我们从pos为止插入一个字符ch,我们首先需要断言,假如我们的pos大于string对象的元素个数时,会造成越界访问。

因为我们插入字符之后,string的元素个数增加,我们需要进行判断是否需要扩容。

假如我们的_size==_capacity表示我们的string对象的元素已满,我们要插入的话需要扩容,扩容分为两种情况,当string对象不为空时,我们可以直接扩容二倍,当string对象为空时,我们可以先给string对象四个字节的容量。

接下来,我们画图进行解释:

 假如我们想要在如图所示的pos位置插入一个元素z,我们需要做的就是把pos位置之后的全部元素都后置一位,然后把z插入到pos位置处,然后增加元素的有效元素的个数,我们可以设置一个循环,我们可以设置字符串的最后一个元素的下一个位置为end

 当pos小于end时,我们把end位置的元素挪到end+1位置,当循环终止时,我们在pos位置插入元素z即可。

还要注意一个问题:当我们进行头插时,我们的pos为0,又因为我们的end是无符号数,所以end始终满足>=0,所以会死循环,我们该如何解决呢?

有些同学会想:我们直接把end的类型换成有符号数就行了,这样就不会死循环了。

如图:

这样做是不行的,因为无符号数的范围比有符号数的范围大,当有符号数end和无符号数pos进行比较时,我们会把有符号数end提升为无符号数,所以又会导致死循环。

我们的解决方法如下:

我们可以在比较时,把end强制类型转换为有符号数,那么end就会小于0了,就不会导致死循环了。

插入函数(字符串)

string&insert(size_t pos, const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			size_t end = _size;
			while (end >= pos)
			{
				_str[end + len] = _str[end];
				--end;
			}
			strncpy(_str + pos, str, len);
			_size += len;
			return*this;
		}

我们对代码进行分析:既然插入字符串,我们首先要判断是否需要扩容,我们先求出插入的字符串str的元素个数,我们进行判断,假如string对象的元素和str的元素大于string的容量,我们调用reserve函数进行扩容,接下来我们画图进行分析:

我们要把字符串str插入到pos位置处,还是之前的思路,我们设置最后一个元素的下一个元素为end

 

 我们需要求出字符串str的元素个数len,然后进行判断,当pos<=end时,我们把end位置的元素挪到end+len位置,在循环中让end--,最后达到的结果是这样:

然后我们使用strncpy,不使用strcpy的原因是strcpy会把字符串末尾的\0也拷贝过去,会导致字符串提前读取结束,使用strncpy,我们把str的len个字节拷贝到_str+pos位置处,然后让_size+len,更新元素个数,返回*this。

erase删除函数:

 我们要实现erase函数就需要创建一个npos参数,这个npos类型是无符号整型的最大值,也就是-1

静态成员变量在类里面定义,只能在类外面初始化,因为静态成员变量是属于整个类的,我们这样写对吗?

正确,这里存在特例:const修饰的静态成员变量可以在类里面完成初始化(只针对整型) 

我们来完成erase函数:

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

我们首先进行断言:我们要保证删除的位置在string对象内,接下来,我们分情况进行讨论:

当要删除的元素大于等于剩余的元素,我们直接在pos位置处放\0,修改元素个数即可。

当要删除的元素小于剩余的元素时,我们直接调用strcpy,把后面的元素拷贝到前面即可,然后修改元素个数,我们画图进行演示:

1:假如我们要删除npos(缺省值)个元素时,我们直接把pos位置元素置空,然后修改元素个数。

2: 

 假如我们要删除的元素个数大于等于_size-pos时,我们把pos位置置空,修改元素个数。

3:假如我们要删除的元素个数小于等于_size-pos时,我们调用strcpy函数,我们把pos+len位置后的元素拷贝到pos位置处即可,然后修改元素个数。

实现find函数:

我们可以实现两个函数,一个函数是查找字符,一个函数是查找字符串

size_t find(char ch, size_t pos = 0)
		{
			assert(pos < _size);
			while (pos < _size)
			{
				if (_str[pos] == ch)
				{
					return pos;
				}
				++pos;
			}
			return npos;
		}

find函数:在pos(缺省值为0)位置处开始找ch,找到了返回对应的下标,找不到返回npos

我们首先进行断言防止产生越界问题。

我们从pos位置到_size位置进行逐个遍历,找到与ch相等的元素,返回对应元素的下标,否则的话返回npos

strstr表示从字符串str1找str2,如果找到了返回指针指向str1的位置,如果没找到返回空指针,但是我们的find函数要的是下标,我们可以把指针转换为下标的形式:

size_t find(const string& str, size_t pos = 0)
		{
			assert(pos < _size);
			const char*ptr = strstr(_str + pos, str);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}

我们先断言,防止越界,我们调用strstr函数,从_str+pos位置的字符串开始查找str字符串,我们进行判断,如果是空指针,我们返回npos,如果不是空指针,我们返回ptr-_str就是对应的下标。

resize函数:

 resize是对函数的元素数量进行修改

 这个函数有三种情况:

1:当n小于_size时,我们会进行尾删,删到只剩n个元素,并且把元素个数进行修改

2:当n大于_size且小于_capacity时,我们补全元素到n位,多余的这些元素用c来替代,然后修改_size。

3:当n大于_capacity时,我们先进行扩容,再把元素补全到n位,然后修改_size。

我们可以先把reserve函数进行修改,以配合我们的resize函数使用:

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

我们的修改是当n比容量大时,我们再进行扩容,我们不再需要缩容的操作,因为缩容没有意义。

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

首先进行判断,n是否大于我们的元素数量,如果大于的话,就调用reserve函数进行扩容,reserve函数内部也有条件判定,所以我们不需要考虑n大于容量的情况。

扩容完毕之后,我们对元素进行填充,使用for循环把多出来的元素全部赋值为ch,然后对元素的_size进行修改,在_size位置处放置\0,进行终止。

如果n不大于我们的元素数量,我们不需要进行缩容操作,把_size位置处的元素置为’\0',然后修改_size即可。

容量函数:

size_t capacity()
		{
			return _capacity;
		}

完善[]函数:

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

我们也写一个[],第一个针对的是可读可写的string对象,第二个针对的是只读的string对象。

<<流插入:

ostream& operator<<(ostream& out, const string&s)
{
	for (size_t i = 0; i < s.size(); ++i)
	{
		out << s[i];
	}
	return out;
}

注意:这里的流插入函数只能写在类的外面或者使用友元函数写在类的里面,写在类外面的原因是写在类里面this指针会默认抢占第一个参数的位置,而第一个参数是out。

我们可以这样理解:首先out是ostream类型的对象,我们把每一个元素都流插入到out,然后返回out,为了防止浅拷贝,需要用ostream的引用。

ostream& operator<<(ostream&out, const string&s)
{
	for (size_t i = 0; i < s.size(); i++)
	{
		out << s[i];
	}
	return out;
}

>>流提取:

istream& operator>>(istream&in, string&s)
{
	char ch;
	cin >> ch;
	while (ch != ' '&&ch != '\n')
	{
		s += ch;
        cin>>ch;
	}
	return in;
}

这里参数不用const是因为我们是要把元素写入到string对象中,所以s一定是可读的。

我们创建字符ch,流提取字符,写入字符到ch,当ch不为空格或者ch不为回车时,我们让s+=ch即可,然后返回in。

这样写可以吗?

答:不行原因是:我们的cin默认就是以空格或者换行结束的,所以ch不可能等于空格或者换行

所以我们无法接收到空格或者换行,我们可以使用get函数。

istream&operator>>(istream&in, string&s)
{
	char ch = in.get();
	while (ch != ' '&&ch != ' ')
	{
		s += ch;
		ch = in.get();
	}
	return in;
}

get函数同样是得到一个字符,但是get函数不受空格和换行的限制。

但是当我们输入很多个字符时,我们又需要大量的扩容。

istream&operator>>(istream&in, string&s)
{
	char buff[128] = { '\0' };
	size_t i = 0;
	char ch = in.get();
	while (ch != ' '&&ch != ' ')
	{
		if (i == 127)
		{
			s += buff;
			i = 0;
		}
		buff[i++] = ch;
		ch = in.get();
	}
	if (i >= 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

我们可以设置一个数组buff,我们可以把get得到的元素放到数组buff中,当buff数组满时,我们让s+=该数组,然后把i置为0,当buff未满,流提取的元素结束时,我们把buff数组的第i个元素置为/0,因为后面还有一些元素,我们能提前结束字符串,然后让s+=字符串即可。

clear函数:

void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

有了clear函数,我们可以完善流提取:对于已经有元素的string对象,不能直接流提取,我们可以先调用一个clear函数再进行流提取。

istream&operator>>(istream&in, string&s)
{
	s.clear();
	char buff[128] = { '\0' };
	size_t i = 0;
	char ch = in.get();
	while (ch != ' '&&ch != '/n')
	{
		if (i == 127)
		{
			s += buff;
			i = 0;
		}
		buff[i++] = ch;
		char ch = in.get();
	}
	if (i >= 0)
	{
		buff[i] = 0;
		s += buff;
	}
	return in;
}

设计深拷贝

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

浅拷贝的本质是同一块地址的相同数据,深拷贝是不同的地址的相同数据,调用函数完毕后,函数内部栈帧销毁,同一块地址的两个数据全部销毁,深拷贝不受影响。

完成深拷贝,首先要开辟一块空间,这里的+1是为\0预留的空间,然后修改容量,修改元素个数,最后进行内存拷贝即可。

赋值重载:

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;
			}
			return *this;
		}

this指针指向的是调用赋值重载函数的对象,我们先进行判断,参数与源对象是否相同,不同的话才做处理,我们的思路是这样的:我们首先创建一个临时变量tmp,为临时变量开辟空间,我们把参数对应的对象的内存拷贝到临时变量位置,然后删除掉源对象空间上的对象,把tmp上的内容拷贝给_str,修改成员变量即可。

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

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

相关文章

【读论文】Spiking-YOLO Spiking Neural Network for Energy-Efficient Object Detection

AAAI-20 摘要 本文提出两个新方法为深度SNN提供快速、准确的信息传递&#xff1a;通道归一化和具有不平衡阈值的带符号神经元&#xff1b;本文也提出了一个基于脉冲的目标检测模型&#xff1a;Spiking-YOLO&#xff0c;并且在non-trivial datasets, PASCALVOC 和 MS COCO数据…

指针与数组

目录指针运算&#xff08;补&#xff09;指针指针指针的关系运算&#xff08;补&#xff09;指针与数组数组名二级指针指针数组指针运算&#xff08;补&#xff09; 指针指针 上一篇博客我们介绍了指针运算中的三种常见运算&#xff1a;指针整数&#xff0c;指针关系运算&…

23.1.27打卡 Codeforces Round #846 (Div. 2) A~D

https://codeforces.com/contest/1780A题给你一个长度为n的数组, 问你是否能找出三个数字, 使得这三个数字之和为奇数简单的小学数学奇偶奇偶偶偶所以我们只要找到三个奇数或者两个偶数一个奇数就好了/* ⣿⣿⣿⣿⣿⣿⡷⣯⢿⣿⣷⣻⢯⣿⡽⣻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿…

简单复现 残差网络、Googlenet、mobilenet、SqueezeNet、ShuffleNet

1.残差网络 1&#xff09;网络结构 当对x求偏导的时候&#xff0c;F&#xff08;x&#xff09;对x求偏导的值很小的时候&#xff0c;对整体求x的偏导会接近于1 这样解决了梯度消失问题&#xff0c;我们可以对离输入很近的层进行很好的更新。 要注意的是F&#xff08;x&#…

【REACT-redux】

1. redux介绍 1.1 描述 Redux最主要是用作应用状态的管理。简言之&#xff0c;Redux用一个单独的常量状态树&#xff08;state对象&#xff09;保存这一整个应用的状态&#xff0c;这个对象不能直接被改变。当一些数据变化了&#xff0c;一个新的对象就会被创建&#xff08;使…

JDK SPI 和 Dubbo SPI

SPI &#xff08;Service Provider Interface&#xff09;&#xff0c;简单翻译就是服务提供接口&#xff0c;这里的“服务”泛指任何一个可以提供服务的功能、模块、应用或系统&#xff0c;会预留一些口子或者扩展点&#xff0c;只要按照既定的规范去开发&#xff0c;就可以动…

MES和金蝶云星空接口打通对接实战

四化智造MES&#xff08;WEB&#xff09;和金蝶云星空接口打通对接实战数据源平台:四化智造MES&#xff08;WEB&#xff09;MES建立统一平台上通过物料防错防错、流程防错、生产统计、异常处理、信息采集和全流程追溯等精益生产和精细化管理&#xff0c;帮助企业合理安排生产排…

机器视觉_HALCON_HDevelop用户指南_2.Getting Started

文章目录前言二、Getting Started2.1. 运行HDevelop2.2. 运行示例程序前言 标题本来想用“开始使用”或“快速上手”&#xff0c;不过感觉怪怪的&#xff0c;干脆就叫Getting Started吧&#xff0c;因为许多开发手册&#xff0c;开始上手的那节就叫这个名字。 本文是接上一篇…

【人工智能原理自学】LSTM网络:自然语言处理实践

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本文讲解LSTM网络&#xff1a;自然语言处理实践&#xff0c;一起卷起来叭&#xff01; 目录一、“RNN”二、编程实验一、“RNN” 上节课我们利用词嵌入把句子转化为词向量序列…

手把手本地搭建服务器笔记1

需要的下载的东西&#xff1a; vmware (百度网盘)银河麒麟镜像xshell,xftp安装vmware&#xff1a; 下载的包里有密钥&#xff0c;安装的时候就直接把密钥扔里面就好了 镜像处理&#xff1a; vmware左上角文件-新建虚拟机-典型&#xff0c;下一步 -安装程序光盘映像文件&am…

基于嵌入式物联网技术的智慧病房方案设计

文章目录前言1、要求2、系统设计3、功能模块3、系统功能模块图一、stm32控制模块原理图二、各功能模块的实现1、整个系统的基本配置2、RTOS多任务1、设计线程2、配置主函数代码3、温湿度读取模块(I2C)4、LED定时开关灯(pwm)5、按键实现报警信号6、脉搏&血氧数据读取7、UART…

【HTML】基础的入门学习

HTML 菜鸟教程 简介 一般结构&#xff1a; <!DOCTYPE html> 声明为 HTML5 文档<html> 元素是 HTML 页面的根元素<head> 元素包含了文档的元&#xff08;meta&#xff09;数据&#xff0c;如 <meta charset"utf-8"> 定义网页编码格式为 ut…

proteus仿真软件中芯片的命名规则与封装方法(详细版)

第一&#xff1a;PCB封装库命名规则 1、集成电路&#xff08;直插&#xff09; 用DIP-引脚数量尾缀来表示双列直插封装​ 尾缀有N和W两种,用来表示器件的体宽​ 为体窄的封装&#xff0c;体宽300mil,引脚间距2.54mm​ 为体宽的封装, 体宽600mil,引脚间距2.54mm​ 如&#…

11、关联数据库

文章目录11、关联数据库11.1 常规方式11.2 常规操作【尚硅谷】idea实战教程-讲师&#xff1a;宋红康 生活是属于每个人自己的感受&#xff0c;不属于任何别人的看法 11、关联数据库 11.1 常规方式 找到数据库选项&#xff1a; 添加指定数据库&#xff1a; 配置MySQL数据库…

5. 网络编程之UDP编程

1. UDP协议的特点 相比与TCP协议来说&#xff0c;UDP协议就显得相对比较简单了。 (1) UDP是无连接的   即发送数据之前不需要建立连接(当然&#xff0c;发送数据结束时也没有连接可释放)&#xff0c;因此减少了开销和发送数据之前的时延。 (2) UDP使用尽最大努力交付   即…

78、Points2NeRF: Generating Neural Radiance Fields from 3D point cloud

简介 github&#xff1a;https://github.com/gmum/points2nerf 由于点云的大小和复杂性&#xff0c;处理这些点云具有挑战性&#xff0c;现有的方法通过将网格拟合到点云并渲染来解决这个问题&#xff0c;这种方法导致结果可视化的保真度降低&#xff0c;并遗漏了在计算机图形…

HashTable HashMap ConcurrentHashMap 的介绍以及区别

目录 &#x1f407;今日良言:投资自己才是最好的投资 &#x1f409;一.HashMap. &#x1f415;二.HashTable &#x1f40d;三.ConcurrentHashMap &#x1f402;四.三者的区别 &#x1f407;今日良言:投资自己才是最好的投资 时隔四十多天,今天博主要更新了. 后续内容也是精…

[机器学习]损失函数DLC

一、损失函数的概念 损失函数(Loss Function)是用于评估预测结果和真实结果之间差距的一个公式&#xff0c;为模型优化指明方向。在模型优化过程中一般表述为&#xff1a;或 与针对整个训练集的代价函数(Cost Function)不同&#xff0c;损失函数通常仅针对单个训练样本。可以归…

RK3568平台开发系列讲解(驱动基础篇)Linux 内核源码介绍

🚀返回专栏总目录 文章目录 一、目录树概览二、快速确定主板关联代码2.1、基础代码2.2、驱动代码沉淀、分享、成长,让自己和他人都能有所收获!😄 📢进行嵌入式 Linux 产品开发,往往需要对内核进行裁剪和定制,以满足嵌入式产品的功能和性能需求。 一、目录树概览 解压…

Python---学生管理系统(pyinstaller)

专栏&#xff1a;python 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;本专栏主要更新一些python的基础知识&#xff0c;也会实现一些小游戏和通讯录&#xff0c;学时管理系统之类的&#xff0c;有兴趣的朋友可以关注一下。 学生管理系统前言创建入口函数新增学生insert展…