string讲解和实现

认识string

string是将basic_string<char>重新定义了

basic_string是一个类模板,里面包括了一些列的有关字符的函数

注意:insert/erase/replace能不要就不用,他们都涉及挪动数据,效率不高

size_t注意

面对无符号整形size_t在使用--时,在使用进判断条件时,需要考虑到最后--到0后减不到负的的情况,判断条件还是否成立

解决方法:

将该判断语句的所有无符号整形部分换成或者强转成int类型

构造、析构、拷贝函数

namespace tt//避免与std里的string识别错误,采用命名空间封装  tt->test
{
	class string 
	{
	public:
		//构造函数
		string()
			:_str(new char[1])
			, _size(0)
			, _capacity(0)
			//构造时开一个空间存放\0,用于标记
			//_size和_capacity=0此时没有字符存入
		{
			_str[0] = '\0';
		}

		//析构函数
		~string()
		{
			_size = _capacity = 0;
			delete[] _str;
			_str = nullptr;//记住在delete后要将指针置空
		}

		//拷贝构造
		//之前的拷贝构造格式是:A(const A& ct),此时我们拷贝的是同一个类型的A,所以用A&去接收
		//此时我们拷贝的是字符串,所以里面也用能接收字符串的表示。而char* 类似A&
		string(const char* str)
			:_size(strlen(str))//算出str长度,可用于改变_size、_capacity
			, _capacity(_size )
		{
			_str = new char[_capacity + 1];//比容量多1用于存放\0,之前构造的\0已经被覆盖
			strcpy(_str, str);//会自动拷贝\0
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};

	void Test1()
	{
		string s1("hello world");
		//直接调用拷贝。"hello world"常量字符串传递过去的是他的地址
	}
}

int main()
{
	tt::Test1();
	return 0;
}

迭代器Iterator

迭代器一共有四种

begin()

返回第一个字符的地址

1.返回第一个字符的地址

2.迭代器是string类中的一个函数

3.iterator是一个类模板的类名,可自动识别不同类型(类似int,一种新定义的对象名称)

测试原函数效果:

#include <iostream>
#include <string>

int main()
{
   std::string str("Test string");
   std::string::iterator it = str.begin();
   //迭代器的类名iterator,对象名it->接受begin返回的地址
   std::cout << *it;//begin()返回第一个字符的地址,读取时需要解引用
   std::cout << '\n';
   return 0;
}

模拟实现:

	namespace tt
{
	class string 
	{
	public:
		string()
			:_str(new char[1])
			, _size(0)
			, _capacity(0)
		{
			_str[0] = '\0';
		}
		~string()
		{
			_size = _capacity = 0;
			delete[] _str;
			_str = nullptr;
		}
		string(const char* str)
			:_size(strlen(str))
			, _capacity(_size )
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}



		//迭代器
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}


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

	void Test1()
	{
		string s1("hello world");
		string::iterator it = s1.begin();//it拿到s1第一个字符的地址
		std::cout << *it;
	}
}

int main()
{
	tt::Test1();
	return 0;
}

end()

返回最后一个有效字符的下一个字符的地址

测试原函数效果:

注意:using namespace std;==解开全部限制

using  std::cout;解开(using)单独的限制

模拟实现:

public:	
	iterator end()
		{
			return _str + _size;
		}
string类外:
	void Test2()
	{
		string s1("hello world");
		string::iterator it = s1.end();
		std::cout << *it;
	}
结果:

范围for

底层还是迭代器

自动判断结束,自动++,自动赋值

能用下标+方括号[]就能用范围for

auto

麻烦长的话,可以用auto识别,因为begin()返回的就他他的类型,能自动识别

字符串长度size()

计算字符串长度,不包括\0,只计算有效字符(单位:字节)

测试原函数效果:

模拟实现:

public:
		//计算有效字符字节数
		size_t size()const
		{
			return _size;
		}
	//测试size
	void Test3()
	{
		string s1("hello world");
		size_t num = s1.size();
		std::cout << num;
	}

修改容量reserve()

测试原函数效果:

改变后有时会比reserve的n要大有时刚好。

模拟实现:

		//修改容量reserve
		void reseve(size_t n = 0)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];//开n+1多的1存放\0
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}

修改字符串长度resize()

1.resize多加的无法打印出来,加的都是\0,可以通过监视窗口查看

2.加实际值则会显示出来

3.如果比容量大会自动扩容

4.比容量少相当于保留前n个字符(删除的意义)

测试原函数效果:

模拟实现:

		//修改字符串长度
		//复杂
		void resize(size_t n)
		{
			if (n <= _size)
			{
				_size = n;
			}
			else if (n > _size && n < _capacity)
			{
				while (_size<n)
				{
					_str[_size] = '\0';
					_size++;
				}
				_str[_size] = '\0';
			}
			else if (n >= _capacity)
			{
				_capacity = n;
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				
				while (_size < n)
				{
					_str[_size] = '\0';
					_size++;
				}
				_str[_size] = '\0';
			}
		}
		//修改容量reserve
		void reserve(size_t n = 0)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];//开n+1 多的1存放\0
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		//发现两个可以合二为一,但是在扩大capacity中我们需要另外一个函数reserve
		void resize(size_t n, char c = '\0')//函数重载
		{
			if (n < _size)
			{
				_str[n] = '\0';
				_size = n;
			}
			else//>分两种,在capacity里和超出capacity
			{
				reserve(n);//不管大小,直接进行重新拷贝
				while (_size<n)
				{
					_str[_size] = c;//存第n个数下标为n-1就可以存
					++_size;//++后存的是第n+1个数
				}
				_str[_size] = '\0';//n+1存\0,下标为n
			}
		}

清楚clear()

容量不变,size改变

测试原函数效果:

模拟实现:

		//清空内容
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

重载【】

测试原函数效果:

模拟实现:

		//重载【】
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);

			return _str[pos];
		}

附加append()

模拟实现:

//附加append
		string& append(const char* s)
		{
			size_t len = strlen(s);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, s);//从末尾附加新字符串
			_size += len;
			return *this;
		}

重载+=

测试原函数效果:

模拟实现:

//重载+=
		string& operator+=(const char* s)
		{
			append(s);
			return *this;
		}
		//尾插push_back
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
				//新开空间要注意为0的情况
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		string& operator+=(const char c)
		{
			push_back(c);
			return *this;
		}

返回字符串地址(指针)c_str

模拟实现:

		//返回字符串地址(指针)
		const char* c_str() const
		{
			return _str;
		}

npos

查找find()

1.返回的是查找到的第一个字符或字符串出现位置的下标

2.pos是下标

3.从pos的位置向后查找

模拟实现:


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

			return npos;
		}

		size_t find(const char* sub, size_t pos = 0)
		{
			const char* p = strstr(_str + pos, sub);
			if (p)
			{
				return p - _str;
			}
			else
			{
				return npos;
			}
		}

使用:

cin注意

scanf和cin面对空格和\0都是间隔,读取时一个变量就无法读取间隔后面的内容,所以使用getline

识别到!才结束

>>输入重载

//输入重载
	istream& operator>>(istream& in, string& s)
	{
		s.clear();//将原s中的字符串清空

		char ch;
		//in >> ch;
		ch = in.get();//先从这里拿一个字符,用于下面while判断
		s.reserve(128);

		while (ch != ' ' && ch != '\n')//修改结束条件
		{
			s += ch;
			//in >> ch;
			ch = in.get();//一个字符一个字符拿
		}

		return in;
	}

巧用数组当缓冲

理解想法就行

如果直接用reserve可能会造成输入的数少但是开的空间大,而用数组的话,在拿取时更加符合输入内容的空间大小,并且在出了作用域就会销毁

    istream& operator>>(istream& in, string& s)
	{
		s.clear();
		//s.reserve(128);

		char buff[129];//开一个数组,存储128个字符,第129位存储\0,当做缓存
		size_t i = 0;

		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')//识别到空格或者换行出循环后,判断if
		{
			buff[i++] = ch;
            //输入的字符大于128个时,将缓存部分全部放入字符串中,
            //继续读取之后的字符并覆盖之前buff的字符
			if (i == 128)//可以存储129为对应下标为128,同时避免输入的超出数组
			{
				buff[i] = '\0';
				s += buff;
				i = 0;//标志
			}

			//s += ch;

			ch = in.get();
		}

		if (i != 0)//说明buff数组没满,保险
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}

<<输出重载

	ostream& operator<<(ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); i++)
		{
			out << s[i];
		}
		return out;//为了连续<<时可用
	}

赋值的现代写法(swap)

swap数组交换优点核心:

不仅交换了,还将自己要释放的交换给了别人

老版本:打工人自己搬砖

         //传统写法
		 s2(s1)
		string(const string& s)
		{
			_str = new char[s._capacity+1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}


		// s2 = s3
		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;
		}

现代版本:

          //拷贝构造1       
        string(const char* str)
 			:_size(strlen(str))//算出str长度,可用于改变_size、_capacity
			, _capacity(_size )
		{
			_str = new char[_capacity + 1];//比容量多1用于存放\0,之前构造的\0已经被覆盖
			strcpy(_str, str);//会自动拷贝\0
		}

		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		// s2(s1)拷贝构造2
        //两个拷贝构造构成重载
		string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}
		// s2 = s3
	    string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s);
				//this->swap(tmp);
				swap(tmp);
			}

			return *this;
		}

         //更极致的现代写法
		string& operator=(string tmp)
		{
			swap(tmp);

			return *this;
		}

	void Test7()
	{
		string s1("hello world");
		string s2;
		s2 = s1;
		cout << s1 << endl;
		cout << s2 << endl;
	}

这一部分转换的swap在s2转移给了tmp,而tmp又是在局部的变量,出了作用域会销毁(析构函数),顺便就解决了

并且通过简单地一个局部变量tmp,再利用swap,将拷贝和赋值简化

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

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

相关文章

静态住宅代理 IP 的影响

在不断发展的在线业务和数字营销领域&#xff0c;保持领先地位势在必行。在业界掀起波澜的最新创新之一是静态住宅代理 IP 的利用。这些知识产权曾经是为精通技术的个人保留的利基工具&#xff0c;现在正在成为各行业企业的游戏规则改变者。 一、静态住宅代理IP到底是什么&…

互联网轻量级框架整合之HibernateMyBatis

持久层框架 Hibernate 假设有个数据表&#xff0c;它有3个字段分别是id、rolename、note, 首先用IDEA构建一个maven项目Archetype选择org.apache.maven.archetypes:maven-archetype-quickstart即可&#xff0c;配置如下pom <project xmlns"http://maven.apache.org/…

应用FMEA打造零风险供应链的关键因素有哪些?

当下&#xff0c;构建零风险的供应链已成为企业竞争的核心要素。其中&#xff0c;FMEA&#xff08;故障模式与影响分析&#xff09;作为一种预防性的质量工具&#xff0c;对于识别和消除潜在风险&#xff0c;优化供应链流程至关重要。本文&#xff0c;天行健六西格玛管理培训公…

sklearn的make_blobs函数

make_blobs是一个用于生成随机数据点的实用函数&#xff0c; from sklearn.datasets import make_blobs X,Y make_blobs(n_samples2000,n_features2,centers12,cluster_std0.05,center_box[-5,5],random_state21)n_samples: 要生成的样本数量。centers: 要生成的簇&#xff0…

linux文本三剑客之awk

目录 1、特点与应用场景 2、awk命令执行流程 3、awk行与列 1)awk取行 2)awk取列 3)awk行与列综合使用 4、awk模式匹配-正则匹配 5、awk模式匹配-范围模式 6、awk模式匹配-特殊模式 7、awk数组* 1) 用途 2&#xff09;格式对比 8、awk循环与判断 1、特点与应用场景…

App测试基本流程以及注意事项

1 APP测试基本流程 1.1流程图 1.2测试周期 测试周期可按项目的开发周期来确定测试时间&#xff0c;一般测试时间为两三周&#xff08;即15个工作日&#xff09;&#xff0c;根据项目情况以及版本质量可适当缩短或延长测试时间。 1.3测试资源 测试任务开始前&#xff0c;检查…

Neo4j+LLM+RAG 环境配置报错处理

开发KGLLMRAG程序时遇到以下报错&#xff0c;记录下处理方案&#xff1a; ValueError: Could not use APOC procedures. Please ensure the APOC plugin is installed in Neo4j and that ‘apoc.meta.data()’ is allowed in Neo4j configuration 这个参考文章&#xff1a;link…

【平台开发】MTK6833——cache操作记录

CPU Cache 用的是一种叫 SRAM&#xff08;Static Random-Access Memory&#xff0c;静态随机存储器&#xff09; 的芯片。 通常分为L1&#xff0c;L2&#xff0c;L3三层缓存。 CPU 并不会直接和每一种存储器设备直接打交道&#xff0c;而是每一种存储器设备只和它相邻的存储器…

vue2 Avoided redundant navigation to current location

再次点击同一个链接会报错 每次使用 push 方法时带上两个回调函数 this.$router.push({name: item.name}, ()>{}, ()>{}) //第二、第三个参数分别为成功和失败的回调函数重写 Vue-router 原型对象上的 push 函数不行 https://blog.csdn.net/weixin_43615570/article/d…

python入门demo实例-个人信息收集页面实现

dd 今天是python入门day2&#xff0c;先看一下本案例demo的样子吧~ 一个简单得html页面&#xff0c;个人信息收集界面。 案例介绍常用得input 元素 文本框&#xff0c;密码&#xff0c;邮箱。文件上传等实现。 资源下载&#xff1a;python案例demo个人信息收集页面实现资源-…

微信公众号有哪些类型,微信服务号和订阅号有哪些区别

什么是微信公众号&#xff1f; 微信公众号是国内社交媒体平台微信上的公众账号。它们是允许公司发布内容&#xff0c;收集追随者并推广其产品或服务的商业帐户。微信公众号主要有两种类型&#xff1a;订阅账号和服务账号。 微信月活跃用户突破12亿 微信最近达到了平台上的月活…

jmeter利用自身代理录制脚本

在利用代理录制脚本时一定要安装java jdk&#xff0c;不然不能录制的。 没有安装过java jdk安装jmeter后打开时会提示安装jdk&#xff0c;但是mac系统中直接打开提示安装jdk页面后下载的java并不是jdk&#xff08;windows中没有试验过&#xff0c;笔者所说的基本全部指的是在ma…

区块链 | NFT 水印:Review on Watermarking Techniques(二)

&#x1f34d;原文&#xff1a;Review on Watermarking Techniques Aiming Authentication of Digital Image Artistic Works Minted as NFTs into Blockchains 1 半脆弱和可逆水印 鲁棒性好的水印技术通常会产生非常低透明度。正如前面所述&#xff0c;由于透明度在处理数字…

智慧公厕:一个让城市公共厕所更智能、更便利的信息化方案

公共厕所一直是城市管理中的一个难题。但是&#xff0c;随着科技的不断发展&#xff0c;智慧公厕正在成为解决这个问题的全新方案。智慧公厕不仅具备传统公厕的基本功能&#xff0c;更是通过信息化技术&#xff0c;实现了空余智能引导、环境监测、资源消耗监测、安全防范管理、…

【数据分析面试】38.更新图书馆数据(Python)

题目 作为一名精通Python的图书管理员&#xff0c;你正在搭建一个更高效地更新图书数据的系统。 编写一个名为 update_availability 的函数&#xff0c;用于更新数据表中特定 book_id 的 availability 值&#xff0c;并返回更新后的数据表。 注意: 如果找不到 book_id&#…

如何缩小图片大小kb?6个压缩图片大小的软件教你快速压缩

如何缩小图片大小kb&#xff1f;6个压缩图片大小的软件教你快速压缩 当需要缩小图片的大小&#xff08;KB&#xff09;时&#xff0c;可以利用一系列专门设计的工具和软件来帮助完成这一任务。这些工具可以有效地减小图片的文件大小&#xff0c;而又不会明显降低图片的质量。以…

小麦穗检测数据集VOC+YOLO格式6508张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;6508 标注数量(xml文件个数)&#xff1a;6508 标注数量(txt文件个数)&#xff1a;6508 标注…

MyBatis(XML映射器操作)

文章目录 XML映射器操作&#xff08;XxxMapper.xml&#xff09;文件目录1.基本介绍1.优点2.常用顶级元素 2.环境配置1.在原来的父模块下创建一个子模块2.删除没用的两个文件夹3.创建基本目录4.父模块的pom.xml5.jdbc.properties6.mybatis-config.xml7.测试使用MonsterMapperTes…

诛吕政变后,为何会是代王刘恒登上皇位?

公元前179年——这一年对于汉帝国而言是非常特殊的一年。在这一年&#xff0c;汉惠帝的所有儿子被秘密杀害。也就在这一年&#xff0c;远在北疆的诸侯王——代王刘恒在汉朝功臣周勃、丞相陈平等人的支持下在长安登基。 自此&#xff0c;汉朝皇位完成了一次“乾坤大挪移”——汉…

10、算数运算符(以 ‘/’、‘%’、‘++’为主去讲解)(Java超详细版本)

算数运算符 一、算数运算符二、“ / ”的使用三、“ % ”的使用四、“ ”的使用⭐ 一、算数运算符 算数运算符是对数值类型的变量进行运算的&#xff0c;在Java程序中使用的非常多的。 二、“ / ”的使用 1、Java中 “ / ” 的运算结果是省略小数部分的整数&#xff0c;不存…