String [中]

news2025/1/18 7:29:53

目录

一、 string 的深浅拷贝

0x00 构造函数与析构函数的实现

0x01 拷贝构造

 0x02 赋值

 0x03 整体代码

二、 string的实现

0x01 引入

 0x02 c_str

 0x03 默认构造函数

三、size()与operator[]的实现

0x01 size()的实现

0x02  operator[]的实现

0x03 遍历实现

 四、迭代器

0x01 迭代器的实现

0x02 再分析范围for


                                       

一、 string 的深浅拷贝

0x00 构造函数与析构函数的实现

#pragma once
#include<iostream>
using namespace std;

namespace yh
{
	class string
	{
	public:
        //进行构造函数,即进行初始化
		string(const char* str)//str不能改变,所以加const
			:_str(new char[strlen(str)+1])//为了能使_str之后能够修改,所以先分配空间
		{
			strcpy(_str,str);//再进行拷贝
		}
		//析构函数,即清理资源
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;//string应该是一个指针
	};
	void test_string1()
	{
		string s1("hello world");
	}
}

注意点: strlen:计算的是有效字符的个数,+1表示的是'\0'的空间

0x01 拷贝构造

问题引入:

默认的拷贝构造对于内置类型会进行浅拷贝,对于自定义类型会调用它的拷贝构造

假设是s2(s1),s1对s2的初始化; 如果成员变量是内置类型char* ,那么s2会和s1所指向的空间地址是相同的,但是在析构的时候,后创建的先析构,也就是先析构s2,而s1也是指向这块空间的,再析构不就会出现err了嘛

问题解决:

那么此时,就该到深拷贝出场了

	//拷贝构造
	string(const string& str1)
	    :_str(new char[strlen(str1._str)+1])
	{
		strcpy(_str,str1._str);
	}

从此图中可以看出,s1与s2所指向的空间是不同的,所以也就解决了上述的问题

 0x02 赋值

问题分析:

①如果s3空间比s1大或者相等,那没问题,但是如果比它小,还要考虑其他情况所以不如释放空间,重新创建和s1一样大的空间,再进行赋值

②如果自己给自己赋值,如s3 = s3,而开始s3这块空间已经被释放了,之后又去开一块这样的空间,然后又去访问被释放掉的空间中的值,这样难免会出错,所以判断一下,防止自己给自己赋值

//赋值重载 s3 = s1;
string& operator=(const string& str2)
{
	if (this != &str2)//防止自己赋值给自己
	{
		delete[] _str; //释放s3空间
		_str = new char[strlen(str2._str) + 1];//重新创建空间
		strcpy(_str, str2._str);//拷贝
	}
        return *this;
}

 但是上面的代码又会有一下小问题,虽然delete不会出现什么问题,但是new(开空间)是可能没有这么大的空间的,所以可能需要抛异常,所以我们可以对以上的代码进行一下调整,如下:

    if (this != &str2)//防止自己赋值给自己
	{
		char* temp = new char[strlen(str2._str) + 1];
		strcpy(temp, str2._str);
		delete[] _str;
		_str = temp;
	}

 0x03 整体代码

#pragma once
#include<iostream>
using namespace std;

namespace yh
{
	class string
	{
		//进行构造函数,即进行初始化
	public:
		string(const char* str)//str不能改变,所以加const
			:_str(new char[strlen(str)+1])//为了能使_str之后能够修改,所以先分配空间
		{
			strcpy(_str,str);//再进行拷贝
		}
		//析构函数,即清理资源
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
		//拷贝构造
		string(const string& str1)
			:_str(new char[strlen(str1._str)+1])
		{
			strcpy(_str,str1._str);
		}
		//赋值重载 s3 = s1;
		string& operator=(const string& str2)
		{
			//if (this != &str2)//防止自己赋值给自己
			//{
			//	delete[] _str; //释放s3空间
			//	_str = new char[strlen(str2._str) + 1];//重新创建空间
			//	strcpy(_str, str2._str);//拷贝
			//}
			if (this != &str2)//防止自己赋值给自己
			{
				char* temp = new char[strlen(str2._str) + 1];
				strcpy(temp, str2._str);
				delete[] _str;
				_str = temp;
			}

			return *this;
		}
	private:
		char* _str;//string应该是一个指针
	};
	void test_string1()
	{
		string s1("hello world");
		string s2(s1);
		string s3("hello");
		s3 = s1;
	}
}

二、 string的实现

0x01 引入

以上是未考虑增删查改的情况,所以现在我们需要增加一下成员变量,进行增删查改的学习

char* _str;
int _size;//有效字符的个数
int _capacity;//有效字符的个数,不算'\0'

优化之后代码:

    class string
    {
		//进行构造函数,即进行初始化
	public:
		string(const char* str)
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str,str);//再进行拷贝
		}
		//析构函数,即清理资源
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
		//拷贝构造
		string(const string& str1)
			:_size(str1._size)
			,_capacity(str1._capacity)
		{
			_str = new char[_capacity + 1];
			strcpy(_str,str1._str);
		}
		//赋值重载 s3 = s1;
		string& operator=(const string& str2)
		{
			if (this != &str2)//防止自己赋值给自己
			{
				char* temp = new char[str2._capacity + 1];
				strcpy(temp, str2._str);
				delete[] _str;
				_str = temp;
				_size = str2._size;
				_capacity = str2._capacity;//容量和大小记得更新
			}

			return *this;
		}
	private:
		char* _str;
		int _size;
		int _capacity;
	};

 0x02 c_str

c_str():返回当前字符串的首字符地址,会去寻找'\0',可读不可写

c_str的实现:

	const char* c_str() const 
	{
		return _str;
	}

 0x03 默认构造函数

问题引入:
①如果创建一个string s3;那么系统回去调用它的默认构造函数,所以我们还需要补充一个默认构造函数

② 因为c_str()是去找'\0'的,找不到'\0'可能会出现错误

不带参默认构造函数:

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

全缺省构造函数:

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

此时,string s4;也可以去使用这个全缺省的构造函数,空字符串中默认有'\0',有效字符个数和容量还是为0,拷贝的时候将'\0'拷贝过去

 此时用全缺省和不带参的构造函数的效果是一样的

三、size()与operator[]的实现

0x01 size()的实现

size_t size()const//因为这个this指针不改变,所以加const
{
	return _size;
}

0x02  operator[]的实现

char& operator[](size_t pos)
{
	return _str[pos];//返回每个位置的字符
}

 同时,operator[]也可以充当写的功能:

s3[0] = 'w';
for (size_t i = 0;i < s3.size();i++)
{
	cout << s3[i] << " " ;
}
cout << endl;

问题引入:

此时普通对象是可以调用的,但是const对象呢?所以此时,我们在重载一个const版本,可读不可写

const char& operator[](size_t pos)const
{
	return _str[pos];//返回每个位置的字符
}

问题引入:

如何调用这个const版本的operator[]呢?

const char& operator[](size_t pos)const
{
	return _str[pos];
}
void f(const string& s3)
{
	cout << s3[0]	<< endl;
}
int main()
{
	string s3("hello");
	f(s3);
}

此时,s3传参可以当做是权限的缩小,变为了可读不可写,也就会调用带const版本的operator[]

0x03 遍历实现

size_t size()const
{
	return _size;
}
char& operator[](size_t pos)
{
    assert(pos < _size);//进行检验是否合理
	return _str[pos];
}
const char& operator[](size_t pos)const
{
    assert(pos < _size);
	return _str[pos];
}
int main()
{
	string s3("hello");
	for (size_t i = 0;i < s3.size();i++)
	{
		cout << s3[i] << " " ;
	}
	cout << endl;
}

 四、迭代器

迭代器:像指针一样的东西,但是可能并非指针,但是模拟了指针的部分功能

0x01 迭代器的实现

class string
{
	typedef char* iterator;
	iterator begin()
	{
		return _str;//首元素地址
	}
	iterator end()
	{
		return _str + _size;//最后一个元素的下一个位置
	}
}
int main()
{
	string s1("hello world");
	string::iterator it = s1.begin();//内嵌类型,表示string的迭代器
	string::iterator temp = it;
	while (it != s1.end())//迭代器写
	{
		*it += 1;
		it++;
	}
	it = temp;
	while (it != s1.end())//迭代器读
	{
		cout << *it << " ";
		it++;
	}
}

其次,就是const迭代器,只能读,不能写

typedef const char* iterator;
const iterator begin()const
{
	return _str;//首元素地址
}
const iterator end()const
{
	return _str + _size;//最后一个元素的下一个位置
}

0x02 再分析范围for

for (auto e:s1)
{
	cout << e << " ";
}

范围for在编译的时候被替换为了迭代器,只要把迭代器实现好,那么范围for自然而然就可以运用了

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

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

相关文章

同步任务、异步任务、宏任务、微任务、任务的执行过程实例详解、setTimeout()是同步还是异步

一、前言 JavaScript是单线程语言&#xff0c;也就是说&#xff0c;只有一条通道&#xff0c;且js中任务是按顺序依次执行的&#xff0c;但若有一个任务时间过长&#xff0c;就会让后续任务一直等待。为了解决这个问题&#xff0c;将任务分为同步任务和异步任务&#xff0c;异…

文案把卖点被埋没?如此挖掘电商产品卖点,让你轻松获客

绝大部分电商卖家开店面临的最大问题就是不知道如何写文案&#xff0c;直接复制品牌的文案容易被告Q权&#xff0c;自己写的又不吸引人&#xff0c;复制竞争对手的更是无法脱颖而出。同时你也不知道这个文案到底好不好&#xff0c;在别人那里可行的文案&#xff0c;可能你就完全…

CTF-PHP反序列化漏洞3-构造POP链

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他所拥有的。可以不学无术&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关注WEB安全、网络攻防。我的…

宽表 VS 多表关联,谁才是大数据分析的最佳选择?

各位数据的朋友&#xff0c;大家好&#xff0c;我是老周道数据&#xff0c;和你一起&#xff0c;用常人思维数据分析&#xff0c;通过数据讲故事。 前段时间和一个客户就数据中台搭建的一些问题进行了交流&#xff0c;其中讨论最多的是到底是用宽表来实现业务需求&#xff0c;…

Lecture 13(Preparation):Network Compression

目录 Network Pruning Knowledge Distillation Parameter Quantization Architecture Design Dynamic Computation 下面介绍五个network compression的技术。这五个技术都是以软体为导向的&#xff0c;在软体上面对network进行压缩&#xff0c;不考虑硬体加速部分。 Netwo…

springboot+vue校园宿舍管理系统

项目简介 分享一个SpringBootvue所做的一个项目&#xff0c;有需要的私信 1.项目描述 访问地址 http://localhost:8088/login.html?redirect_urlhttp://localhost:8087/myproject 超级管理员账户 账户名&#xff1a;admin 密码&#xff1a;123456 系统管理员账户 账户名…

【系统集成项目管理工程师】计算题专题一

一、决策树和期望货币值 1、项目经理向客户推荐了四种供应商选择方案。每个方案损益值已标在下面的决策树上。根据预期收益值&#xff0c;应选择设备供应商 A.供应商1B.供应商2C.供应商3D.供应商4 解题&#xff1a; 供应商 1&#xff1a;60% * 10000 &#xff08;-30000&am…

DDR基础

欢迎关注我的博客网站nr-linux.com&#xff0c;图片清晰度和&#xff0c;排版会更好些&#xff0c;文章优先更新至博客站。 DDR全称Double Data Rate Synchronous Dynamic Random Access Memory&#xff0c;是当代处理器必不可少的存储器件之一。本文关于DDR介绍的核心点如下&…

Hadoop 3:YARN

YARN简介 Apache Hadoop YARN &#xff08;Yet Another Resource Negotiator&#xff0c;另一种资源协调者&#xff09;是一种新的Hadoop资源管理器。 YARN是一个【通用资源管理系统和调度平台】&#xff0c;可为上层应用提供统一的资源管理和调度。 它的引入为集群在利用率、…

数据结构学习记录——堆的小习题(对由同样的n个整数构成的二叉搜索树(查找树)和最小堆,下面哪个说法是不正确的)

目录 习题一 习题二 习题三 答案区 解析区 习题一 习题二 习题三 习题一 一、下列序列中哪个是最小堆&#xff1f; .2&#xff0c;55&#xff0c;52&#xff0c;72&#xff0c;28&#xff0c;98&#xff0c;71 .2&#xff0c;28&#xff0c;71&#xff0c;72&#x…

排序(数据结构系列13)

目录 前言&#xff1a; 排序算法的引言&#xff1a; 1.插入排序 1.1直接插入排序 1.2希尔排序 2.选择排序 2.1直接选择排序 2.2堆排序 3.交换排序 3.1冒泡排序 3.2快速排序 3.2.1Hoare版 3.2.2挖坑法 3.2.3前后指针法 4.归并排序 5.排序总结 结束语: 前言&…

docker容器日常操作命令

1.docker日常命令 文章目录 1.docker日常命令1.1.运行一个容器&#xff08;run&#xff09;1.1.1.创建contos 7.6容器 1.2.查询容器列表(ps)1.3.容器命名&#xff08;--name&#xff09;1.4.容器删除命令&#xff08;rm&#xff09;1.5.容器命令&#xff08;inspect&#xff09…

【Vue2.0源码学习】变化侦测篇-Array的变化侦测

文章目录 1. 前言2. 在哪里收集依赖3. 使Array型数据可观测3.1 思路分析3.2 数组方法拦截器3.3 使用拦截器 4. 再谈依赖收集4.1 把依赖收集到哪里4.2 如何收集依赖4.3 如何通知依赖 5. 深度侦测6. 数组新增元素的侦测7. 不足之处8. 总结 1. 前言 上一篇文章中我们介绍了Object…

5 大分区管理器 - 最好的硬盘分区软件

分区是一个计算机术语&#xff0c;指的是在硬盘上创建多个区域&#xff0c;以允许操作系统和分区管理器软件有效且单独地管理每个区域中的信息。拥有大量计算机使用历史的人最有可能受益于多个分区。在硬盘中进行分区的好处之一是可以更轻松地将操作系统和程序文件与用户文件分…

node.js (fs文件系统模块,path路径模块,http模块web服务器)

node.js是js的后端运行环境 浏览器是js的前端运行环境 node.js是无法调用DOM和BOM和ajax等浏览器内置API node.js是一个基于ChromeV8引擎的JavaScript运行环境 目录 node.js可以做什么&#xff1f; node.js的学习路径 node安装 在node.js环境中执行javaScript代码 fs文…

Flask搭建api服务-生成API文档(Taobao/jd/1688API 调用文档说明)

API是给别人用的&#xff0c;就要告诉别人如何发现api&#xff0c;以及api的用途、名称、出参、入参&#xff0c;生成api文档的做法有好多种&#xff0c;本文选了一种最简单的方式。 核心就是通过app.view_functions 这个字典找到每个API 的endpoint所绑定的方法&#xff0c;然…

flutter的环境搭建步骤(MacBook Pro)

1.下载Flutter SDK包 地址&#xff1a;https://docs.flutter.dev/get-started/install/macos 2.配置环境变量 vim ~/.bash_profile //在打开的文件里增加一行代码&#xff0c;意思是配置flutter命令在任何地方都可以使用。 export PATH/app/flutter/bin:$PATH // 从新加载 sou…

JS 实现区块链同步和共识

JS 实现区块链同步和共识 之前实现了区块链和去中心化网络&#xff0c;这里实现区块链的同步和共识&#xff0c;不过本质上来说使用的的方法和 register & broadcast 的方法是一样的。 这个也是目前学习中倒数第二篇笔记了&#xff0c;最后两个部分学完&#xff0c;block…

机器视觉之线缆字符检测

在生活当中&#xff0c;随处可见与印刷字符有关的产品&#xff0c;比如&#xff1a;线缆上字符&#xff0c;键盘上的字符&#xff0c;衣物上的标签字符&#xff0c;电器上的字符等等。 而这些产品的外观由于字符在印刷时产生的一些瑕疵&#xff0c;如字符拉丝、移位、多墨、缺失…

身为管理层总是被下属怼,自己毫无威严,如何改变这样的现状?

身为一名管理层&#xff0c;被下属怼的感觉无疑是相当不爽的。毕竟&#xff0c;作为领导者&#xff0c;我们希望能够得到下属的尊重和信任&#xff0c;而不是被他们视为“摆设”。如果你也有类似的经历&#xff0c;那么不妨试试以下几种方法&#xff0c;来改变这种局面。 首先…