string常见功能模拟

news2024/11/28 18:42:41

    学到string终于就不用像c语言一样造轮子了,接下来我们就模拟一下string方便我们更好理解string,首先我们都知道库里有个string,所以为了避免我们的string和库里的冲突,要用命名空间my_string将我们写的string包含在内。string的成员变量和我之前写的顺序表差不多,首先是一个指针指向一块存放字符串的空间,还有_size记录该空间内的有效字符个数,不包括\0,然后就是_capcity记录该空间能存放有效字符的个数,当然,实际上由于我们末尾要放一个\0,所以我们在实现的时候实际开辟空间比容量稍微大一点。库里的容量则更加充裕。

     

 一:构造函数,析构函数和赋值运算符重载

1.构造函数,析构函数实现:

#pragma once
#include<iostream>
#include<string>
using namespace std;
namespace my_string
{
	class string
	{
	public:
		
		//构造函数
		string(const char*str="")//默认用空串初始化
		{
          都是内置类型,在函数体内初始化也挺方便,免得初始化列表的
            书写顺序和声明顺序不同导致出错

			int len = strlen(str);计算要开辟的空间
			_str = new char[len + 1];
			_capcity = len;
			_size = len;
			strcpy(_str, str);
			_str[_size] = '\0';末尾补\0
		}
		~string()
		{
			delete[]_str;要注意的是new[]对应delete[],new对应delete,原因不好解释。
			_size = 0;
			_capcity = 0;
			_str = nullptr;
		}

		const char* c_str()const将string以c语言的字符串形式返回
		{
			return _str;
		}
	private:
		size_t _capcity;
		size_t _size;
		char* _str;
	};
};

2.拷贝构造函数

string(const string& Src)
{
	_str = new char[Src._size+1];
	_size = Src._size;
	_capacity = _size;
	memcpy(_str, Src._str, Src._size+1);//将\0也一同拷贝
}

3.测试构造函数

void TestString1()
{
	my_string::string s1("hello world");
    cout << s1.c_str() <<endl;
}

  首先c._str()函数将string s1以字符串的形式返回,也就是返回指针,并且cout内部有内置类型的输出重载,所以可以将其打印出来

    const修饰对象只会限制成员变量不被修改,却并不限制修改其成员指向的空间,也就是说如果c_str()返回指针不加const修饰,我们可以在外部用该指针修改const对象,这是不合理的,又为了保证const对象和普通对象都可以调用这个函数,所以对c_str()这个函数用了两个const修饰

4.赋值运算符重载


string.h

string& operator=(string tmp)
{
	swap(tmp);

	return (*this);//支持连续赋值
}


main.c
string s1;
string s2;
s1=s2;	

   此时s1=s3被编译器转为s1.operator(s2), s2是传值传参,要调用拷贝构造,原因我在我的博客类的六大成员函数中曾提及,tmp是s2的深拷贝对象,我们交换了s1和tmp的成员,这样s1指向的空间就是s3深拷贝后的了,也就完成了赋值,最妙的是我们把s1要析构的空间给了局部对象tmp,让其在函数调用结束时销毁,不用我们手动delete[]。

​void swap(string s)
{
	//复用库的swap函数
	std::swap(_str, s._str);//直接交换两个对象成员
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

要注意的是我们不是直接调用库里的swap函数交换两个string对象,而是自己写个swap,内部调用库里的swap函数来交换string对象的成员来达到交换对象的目的。原因是库里的swap函数是如下的:

class T
swap(T a,T b)
T c(a);
a=b;
b=c;

当我们调用赋值的时候,用了swap函数,swap内部又是赋值,赋值内部又用swap,无穷递归调用,死循环,千万注意。

二:string的遍历

1:实现迭代器和范围for

在第一次使用迭代器时是std::iterator it,现在我才意识到原来iterator实质上是char*的typedef,下面的begin()函数如果被const修饰,const对象可以调用,非const对象也可以调用,但是这样的话返回类型就只能是const char*了,那对于普通对象来说就无法修改了,所以要再实现一个const修饰的begin()函数,下面还重定义了const char*就是为了服务const对象。

public:
		typedef char* iterator;
		typedef const char* const_iterator;
        
        char& operator[](int i)
		{
			return _str[i];
		}
		const char& operator[](int i)const
		{
            //_size,_str,_cap不可修改,因为this指针此时是const string*const
			 //若是const不修饰char&,_str指向的字符串可修改
			return _str[i];
		}

		iterator begin()  对const对象不适用,所以要再写一个
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}
        
	不会修改成员变量的成员函数都可以用const修饰,这样就方便const对象和普通对象的调用了。

		size_t size()const//获取string中的有效字符个数
		{
			return _size;
		}

		char* c_str()const//将string以c语言的字符串形式返回
		{
			return _str;
		}

  如下为const对象的begin()和end()函数,返回类型是const_char*,为了和char*的重命名作区分,命名为const_iterator,注意:const对象的迭代器不可修改,所以返回的指针要用const修饰,否则该指针同样在外部会被用来修改const对象。

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

2.测试遍历功能

在准备好了成员函数后,就可以测试遍历功能了,下面提供三种遍历方式。

   for (size_t i = 0; i < s1.size(); i++)//测试size()和[]重载
	{
		cout << s1[i];//测试[]重载
	}
	cout << endl;

	//迭代器
	//my_string::string::iterator it = s1.begin();
      auto it = s1.begin();
 是不是感觉it的类型名特别长,这个时候auto自动推导类型名的作用就很舒服了

	while (it != s1.end())
	{
		cout << *it;
		it++;
	}
	cout << endl;

	范围for,范围for在底层被替换成上述的迭代器,而且是按照既定格式来替换的,
如果我们模拟的begin函数名改为Begin,范围for就无法运行,因为范围for被替换后是用begin函数名访问。

	for (auto ch : s1)
	{
		cout << ch;
	}
	cout << endl;

三:string的添加字符

1.push.back尾插一个字符

void Reserve(size_t n=0)//扩容
{
	char*tmp = new char[n + 1];//预留一个位置给\0
	memcpy(tmp, _str, _size);
	delete[]_str;
	_str = tmp;
	_capcity = n;
}

上面的扩容函数中我们用memcpy来把原先存在的字符串复制到tmp中,之所以不用strcpy,是因为我们考虑string中可能会存入"hello \0world'。

void push_back(char c)//尾插一个字符
{
	if (_size == _capcity)//扩容
   {
		Reserve(_capcity==0?4:2*_capcity);
                                    当string对象为空串时,给个初始容量
				                     其余情况扩大二倍   
   }
	   _str[_size++] = c;
	   _str[_size] = '\0';//末尾补充\0
}
		
string& operator+=(char s)
{
	push_back(s);
	return (*this);
}

2.append尾插一个字符串

void append(const char* s)//尾插一个字符串
{
	int len = strlen(s);
	if (_size + len > _capcity)先判断size+len是否会超出容量
	{
		Reserve(_size+len);  扩容到_size+len而不是2*_capcity,
                           2*_capcity不一定大于_size+len 
	}
	else
	{
		for (int i = 0; i < len; i++)
	    {  
			_str[_size++] = s[i];//插入字符
		}
	}
}

+=运算符重载,便于调用,且代码看起来更加规整
string& operator+=(const char* s)
{
	append(s);
	return (*this);
}

代码中使用+=运算符重载比直接append和push_back函数更加美观,如下。

s += 'w';
s += 'o';
s += "hello ";
s += "world";

对比
s.push_back('w');
s.push_back('o');
s.append("hello");
s.push_back("world");

3.在pos位置添加字符或者字符串

 string& insert(size_t pos,char ch,size_t n)
{
	assert(pos <= _size);
	//判断容量
	if (_size + n > _capacity)
   {
	   Reserve(_size + n);
   }
	//挪动数据
	size_t end = _size;//从\0开始移动
	while (end>=pos && end!=npos)    end为size_t,当pos=0时,end无法跳出循环
                                            ,所以添加一个判断条件
   {
	  _str[end+n] = _str[end];
	  end--;
   }

	     //插入数据
	 _size += n;
	while (n)
	{
	    _str[pos++] = ch;
		n--;
	}
	return (*this);
}


 string& insert(size_t pos,const char*src)
{
	assert(pos <= _size);
	int len = strlen(src);
	//判断容量
	if (_size + len> _capacity)
   {
	  Reserve(_size + len);
   }
	   //挪动数据
	size_t end = _size;//从\0开始移动
	while (end >= pos && end != npos)
   {
		str[end + len] = _str[end];
		end--;
   }
		//插入数据
	 _size += len;
	for (int i = 0; i < len; i++)
	{
		_str[pos++] = src[i];
	}
		return (*this);
}

四:删除string对象的字符

len的缺省值为npos,由于npos为-1,对于无符号数len来说是四十几亿,而实际上字符串都不会这么大,也就表示删除pos位置后全部的字符。

      void erase(size_t pos=0,size_t len=npos)   删除pos位置以及之后len个字符
                                          
  		{
			if (len==npos||pos + len>= _size)//删到末尾
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
               挪动覆盖数据
				size_t begin = pos + len;
				while (begin <= _size)    将后面未删除字符以pos为起点往后放
				{                         包括了\0
					_str[pos++] = _str[begin++];
				}
				_size -= len;
			}
		}

五:查找字符

1.从指定位置查找字符串,返回字符串第一次出现位置的首字符地址

size_t find(const char*goal,size_t pos=0)
		{
			if (pos >= _size)
				return npos;//下标非法,返回-1
			char*begin = strstr(_str+pos, goal);//复用库函数
			if (begin == NULL)
				return npos;//找不到,返回-1

			return begin - _str;
		}
	

2.从指定位置查找字符,返回字符下标

	size_t find(char goal, size_t pos = 0)
		{
			if (pos >= _size)
				return npos;//下标非法,返回-1
			for (size_t i =pos; i < _size; i++)//遍历判断
			{
				if (_str[i] == goal)
					return i;
			}
				return npos;//找不到,返回-1
		}

六:截取字符串

string substr(size_t pos=0,size_t len=npos)
{
    assert(pos <= _size);
    string ret;
	if (len==npos||pos + len > _size)//截取到末尾
   {

        此处有个细节是先判断len是否等于npos,可以免得当len=npos时,len+pos出现栈溢出
        erase函数处同理
			
		len = _size - pos;//处理len
   }
   for (size_t i = pos; i < len+pos; i++)
  {
		ret += _str[i];//ret无需处理\0,+=会处理
  }
	return ret;
}

七:流插入流提取

   1.流插入

可以选择将流提取和流插入函数放在全局域或者命名空间内,最好直接放在命名空间内,免得冲突,但是放在命名空间的时候,若在命名空间内声明,外写定义,定义处要指定命名空间域,否则函数调用时编译器看到命名空间内有个声明,会认为有个定义,虽然没找到,而全局处的函数定义又没有指定命名空间,就不会认为是命名空间中那段声明对应的函数定义,这样函数调用时就会出现调用不明确。

ostream& my_string::operator<<(ostream& out, const my_string::string& s)
{
	for (size_t i = 0; i < s.size(); i++)
	{
		out << s[i]; s[i]调用的是类的公有函数,
                        用公有函数访问string对象的字符不受访问限定符限制
	}
	out << endl;
	return out;
}

2 流提取

istream& my_string::operator>>(istream& in, my_string::string& s)
{
	s.clear();
	char ch = 0;
	ch = in.get();
	while (ch == ' ' || ch == '\n')
	{
		ch = in.get();  处理有效字符前的分隔符
	}
	int i = 0;
	char arr[128] = { 0 };  模拟缓冲区,防止s频繁扩容
	while (ch != ' ' && ch != '\n')//当ch读到分隔符,一个string对象读取结束
	{
		arr[i++] = ch;//先存到数组中去
		ch = in.get();
		if (i == 127)//数组满了后,一次性填入s中
		{
			arr[i] = '\0';
			i = 0;
			s += arr;
		}
	}           可能i小于127,此时又读到了分隔符,要在外面判断是否要处理数组中剩下的字符
	if (i != 0)
		s += arr;
	return in;
}

字数有点多,但是string是我们学习c++的关键,对于理解vector和list有着非常大的作用,个人的一些理解希望对大家有帮助。

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

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

相关文章

精准医学时代:探索人工智能在DCA曲线下的临床医学应用

一、引言 在当今医学领域中&#xff0c;精准医学作为一种以个体差异为基础的医疗模式逐渐受到重视和应用[1]。精准医学基于个体基因组、环境和生活方式因素的综合分析&#xff0c;旨在实现个体化的预防、诊断和治疗方案&#xff0c;从而提供更好的临床结果[2]。与传统医学相比&…

MACD进阶版指标公式,提前一天判断MACD金叉

MACD是一种常用的技术分析指标&#xff0c;用于判断价格的趋势和动能&#xff0c;其原理是基于两条指数移动平均线的比较和对价格的平滑处理&#xff0c;MACD金叉是指MACD指标中的快线DIF从下方向上穿过慢线DEA。快线、慢线都是根据收盘价计算出来的&#xff0c;如果想提前一天…

STM32基础知识点总结

一、基础知识点 1、课程体系介绍 单片机概述arm体系结构STM32开发环境搭建 STM32-GPIO编程-点亮世界的那盏灯 STM32-USART串口应用SPI液晶屏 STM32-中断系统 STM32-时钟系统 STM32-ADC DMA 温湿度传感器-DHT11 2.如何学习单片机课程 多听理论、多理解、有问题及时提问 自己多…

论文阅读:基于深度学习的大尺度遥感图像建筑物分割研究

一、该网络中采用了上下文信息捕获模块。通过扩大感受野&#xff0c;在保留细节信息的同时&#xff0c;在中心部分进行多尺度特征的融合&#xff0c;缓解了传统算法中细节信息丢失的问题&#xff1b;通过自适应地融合局部语义特征&#xff0c;该网络在空间特征和通道特征之间建…

时间序列预测 | Matlab基于粒子群算法(PSO)优化径向基神经网络(PSO-RBF)的时间序列预测

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 时间序列预测| Matlab基于粒子群算法(PSO)优化径向基神经网络(PSO-RBF)的时间序列预测 评价指标包括:MAE、MBE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %% 清空环境变量 warni…

2023年开放式蓝牙耳机选购指南!多款热门开放式蓝牙耳机品牌盘点

前言 大家好&#xff0c;作为专注耳机研究多年的发烧级爱好者&#xff0c;毫不夸张地说我为耳机花的钱比买衣服还多&#xff0c;很多人都在问我开放式耳机到底有没有必要买&#xff1f;答案毫无疑问是有必要&#xff01;开放式耳机佩戴舒适又安全的特质让它在耳机届风靡&#…

zabbix server is not running错误解决方法

1.错误&#xff1a;zabbix server is not running 打开zabbix server的时候&#xff0c;底部飘着一行黄色的警告字 2.解决方法 (1)关闭selinux (2)查看日志文件 #tail -f /var/log/zabbix/zabbix_server.log 发现内存溢出了 __zbx_mem_realloc(): out of memory 那…

vitepress使用

vitepress使用 初始化项目 pnpm init pnpm add vitepress vue 创建一个docs文件夹 在docs下新建index.js文件 # Hello VitePress在package.json中增加打包以及运行的指令 "scripts": {"docs:dev": "vitepress dev docs", // 本地运行调试&qu…

springboot高校党务系统

开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9

实力认可丨通付盾上榜《嘶吼2023网络安全产业图谱》31项细分领域

7月10日&#xff0c;嘶吼安全产业研究院联合国家网络安全产业园区&#xff08;通州园&#xff09;正式发布《嘶吼2023网络安全产业图谱》。通付盾入围本次图谱的基础技术与通用能力、网络与通信安全、安全服务、应用与产业安全、数据安全、开发与应用安全六大类别&#xff0c;3…

day32-Oracle+servlet

0目录 Oraclejdbcjspservlet 1.准备物料 1.1 创建Maven工程&#xff0c;导入依赖 方法1&#xff1a;在maven本地仓库repo中放入下载好的jar包 方法2&#xff1a;换版本&#xff0c;引入依赖 <dependency> <groupId>com.oracle.database.jdbc</groupId>…

x3550M5服务器,2008r2系统,关机后再开机,提示需要系统修复

问题现象&#xff1a; x3550M5服务器&#xff0c;2008r2系统&#xff0c;关机后再开机&#xff0c;提示需要修复&#xff0c;选择语言&#xff0c;点击下一步&#xff0c;选择操作系统一栏是空白的 &#xff08;加载前的图忘记拍&#xff09; 问题分析&#xff1a; 根据网上…

关于c/c++中的isdigit()函数(判断一个字符是不是数字字符)

1&#xff1a;做用&#xff1a;判断一个字符是不是数字字符&#xff08;即&#xff1a;相当于&#xff1a;s[i]>0&&s[i]<9&#xff09; 2&#xff1a;使用方式 char cA; string s"123fgv"; if(isdigit(c)); if(isdigit(s[i]))//返回bool类型 3&…

服务端⾼并发分布式结构演进之路

1.前置概念 应⽤&#xff08;Application&#xff09;/系统&#xff08;System&#xff09; 为了完成一整套服务的一个程序或相互配合的程序群 模块&#xff08;Module&#xff09;/组件&#xff08;Component&#xff09; 当应⽤较复杂时&#xff0c;为了分离职责&#xf…

我爱学QT-制作一个最简单的QT界面

1.qt基础 qt的移植性非常强&#xff0c;一套代码不用我们改太多&#xff0c;直接通用所有平台。不久的将来&#xff0c;qt会被用到MCU上&#xff0c;学习QT还是非常有意义的。 2.做一个简单的QT界面 首先新建工程 注意这个不一样 工程文件分析&#xff1a; #--------------…

day32-存储过程、存储函数、触发器

0目录 Oracle 存储过程、存储函数、触发器 1. 1.1 序列 &#xff08;1&#xff09;语法 创建序列&#xff1a;create sequence 序列名; 启动序列&#xff1a;select 序列名.nextval from dual;&#xff08;每次执行会1&#xff09; 使用序列插入数据&#xff1a;insert…

echarts饼图设置颜色的两种方式

1. 直接写在color数组中 option {color:[#fac858,#e0504b,#e6e9ee],series: {type: pie,radius: [40%, 70%],data: [{ value: 1048, name: Search Engine, },{ value: 735, name: Direct},{ value: 580, name: Email },]} };2. 在series.data.itemStyle.color中 option {se…

Android 14 的 8 个重要新特性深度解析

Android 14 的 8 个重要新特性深度解析 每年一次的Android升级从不缺席。今年的版本名为倒置蛋糕&#xff08;Upside Down Cake&#xff09;&#xff0c;简称U&#xff0c;对外的版本号是Android 14。 通常来说&#xff0c;升级任务可以从两个角度来考虑&#xff1a;ROM角度和…

【thinkPHP】数据库查询

最近在做一个小程序项目&#xff0c;后端使用php&#xff0c;php的优点是快速开发&#xff0c;简单&#xff0c;对服务器资源使用少&#xff08;没什么钱买服务器&#xff09;&#xff0c;相对于java那种动不动就2g起步的内存。 说明&#xff1a;在使用Db时需要引入Db的facade…