C++中string的简单实现

news2024/9/25 9:37:20

string的简单实现中一些函数的实现可以复用一些其他的函数来实现;比较重要的是在实现是深浅拷贝问题

目录

string的结构

实现构造和析构 

reserve扩容

容量 

push_back和append

insert和erase的实现

swap的实现(不是成员函数但是string类的友元)

赋值运算符的重载和+=运算符的重载 

c_str和substr

关系运算符的重载

流插入和流提取运算符的重载(跟swap一样是string的友元)


string的结构

    class string
	{	
    private:
        typedef char* iterator;//普通迭代器
        typedef const char* const_iterator;//const迭代器
		char* _str;
		size_t _size;//长度,大小
		size_t _capacity;//容量
		static const size_t npos = -1;//npos相当于一个很大的数
	};

为啥用char* 指针就可以充当迭代器?

因为可以直接通过char*来访问string中的字符数组。在库中的实现可能不是用的char*。



实现构造和析构 

		string(const char* s = "");//构造 默认参数即使不传参也可以构造一个空字符串
		string(const string& s);//拷贝构造
		~string();//析构

 为什么需要显示实现析构函数?

因为_str指向的空间是new出来的,是资源,所以需要用delete手动释放该资源。

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

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

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

实现没有什么就是简单的对字符串的操作。


reserve扩容

	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n];
			strcpy(tmp, _str);
			delete[] _str;//释放原来的空间
			_str = tmp;
			_capacity = n;
		}
	}

注意一定要释放原来的空间否则会内存泄漏 

 操作简图


容量 

	size_t string::size()
	{
		return _size;
	}
	size_t string::capacity()
	{
		return _capacity;
	}
	size_t string::size()const//const修饰的string会调用该函数
	{
		return _size;
	}
	size_t string::capacity()const
	{
		return _capacity;
	}

 


push_back和append

	void string::push_back(char ch)
	{
        //扩容
		if (_size == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newcapacity);
		}
		_str[_size++] = ch;
        //别忘了在最后加上‘\0’
		_str[_size] = '\0';

	}

	string& string::append(const char* s)
	{
		int len = strlen(s);
        //扩容
		if (_size + len >= _capacity)
		{
			reserve(_size + len+1);//需要多扩一个字节来存放‘\0’
		}
		strcpy(_str + _size, s);
		_size = _size + len;
		return *this;//返回调用append这个函数的string对象本身
	}

注意:向string中插入新的数据,就有可能需要扩容,扩容正好可以复用之前实现的reserve函数。 


insert和erase的实现

	string& string::insert(size_t pos, const char* s)
	{
		assert(pos >= 0 && pos < _size);//pos这个要插入的位置要合法
		int len = strlen(s);
        //扩容
		if (_size + len >= _capacity)
		{
			reserve(_size + len +1);
		}
		int end = _size + len;
        //将pos位置之后的字符整体向后移动len(插入字符串的长度)个字节
		while (end - len >= pos)
		{
			_str[end] = _str[end - len];
			end--;
		}
        //插入字符串
		for (int i = 0; i < len; i++)
		{
			_str[pos + i] = s[i];
		}
        //strcpy(_str+pos,s);也可以直接这样写
		_size = _size + len;
		return *this;
	}

	string& string::insert(size_t pos,char ch)
	{
		assert(pos >= 0 && pos < _size);
		if (_size + 1 >= _capacity)
			reserve(_size + 1 +1);
		int end = _size;
        //将pos位置之后的字符整体向后移动1个字节
		while (end >= pos)
		{
			_str[end + 1] = _str[end];
			end--;
		}
		_str[pos] = ch;
		_size++;
		return *this;
	}
	string& string::erase(size_t pos, size_t len)
	{
        //如果长度很长就直接将pos位置设置为‘\0’
		if (pos + len > _size)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
            //如果长度不是很长,那么就pos+len位置之后的字符整体前移
			for (size_t i = pos; i+len <= _size; i++)
			{
				_str[i] = _str[i + len];
			}
            _size-=len;
		}
		return *this;
	}

 

swap的实现(不是成员函数但是string类的友元)

	void swap(string& x, string& y)
	{
		std::swap(x._str, y._str);
		std::swap(x._capacity, y._capacity);
		std::swap(x._size, y._size);
	}

 

 在标准库中有实现的一个swap的函数模板

//标准库中的swap的实现
template<class T>
void swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

如果使用std::swap来交换两个字符串效率不高:tmp需要调用拷贝构造,重新拷贝一份a;将b赋值给a和将tmp赋值给b都会调用赋值运算符,重新开辟空间,释放掉原来的空间。总结,需要完成三次深拷贝,性能不高。

 而为string专门实现的swap就只需要交换资源就可以。效率很高。


赋值运算符的重载和+=运算符的重载 

 

	string& 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;
	}

赋值重载的另一个版本 

	string& string::operator=(string s)
	{
        
		swap(*this,s);//直接调用swap交换两个*this和s两个资源就可以了
		return *this;
	}

直接将临时变量的资源与*this对象的资源互换,并且临时变量在销毁时也会delete释放资源。

跟上面的代码效率差不多,但如果是自己赋值自己,那么下面就会进行一次深拷贝,而上面啥也不做,效率不如上面。 


c_str和substr

	//返回字符串首元素地址
    const char* string::c_str()
	{
		return _str;
	}
    //返回字符串中的一段字符构造的string
    string string::substr(size_t pos, size_t len)
	{
		string tmp;
        //len的长度很长直接将pos后的所有字符用来构造string
		if (pos + len >= _size)
		{
			for (int i = pos; i <= _size; i++)
			{
				tmp += _str[i];
			}
			return tmp;
		}
		int i;
		for (i = 0; i < len; i++)
		{
			tmp += _str[pos+i];
		}
		tmp += '\0';
		return tmp;
	}

这里的substr的实现直接复用了+=运算符


关系运算符的重载

	bool string::operator==(string& s)
	{
		return strcmp(_str, s._str)==0;
	}
    
	bool string::operator!=(string& s)
	{
		return !(*this == s);
	}

	bool string::operator>(string& s)
	{
		return strcmp(_str, s._str) > 0;
	}

	bool string::operator>=(string& s)
	{
		return (*this == s) || (*this > s);
	}

	bool string::operator<(string& s)
	{
		return !(*this >= s);
	}

	bool string::operator<=(string& s)
	{
		return (*this < s) || (*this == s);
	}

有的运算符的重载可以复用已经实现的运算符

流插入和流提取运算符的重载(跟swap一样是string的友元)

	istream& operator>>(istream& in,string& s)
	{
		s.clear();
		char ch = in.get();//get从流(string)中读入一个char
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}

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

完整的string实现在gitee中: 

刷题记录: 用来调试刷题是写的代码 (gitee.com)

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

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

相关文章

【c++】类和对象详解

✅博客主页:爆打维c-CSDN博客​​​​​​ &#x1f43e; &#x1f539;分享c语言知识及代码 来都来了! 点个赞给博主个支持再走吧~&#xff01; 一.类的定义 &#xff08;1&#xff09;类定义格式 class为类定义的关键字&#xff0c;定义一个类格式如下: class 类名{//代码…

kubelet 探针

目录 1 k8s中kubelet 探针的介绍 1.1 探针是由 kubelet 对容器执行的定期诊断: 1.2 Kubelet 可以选择是否执行在容器上运行的三种探针执行和做出反应&#xff1a; 1.3 ReadinessProbe 与 LivenessProbe 的区别 1.4 StartupProbe 与 ReadinessProbe、LivenessProbe 的区别 2 实…

CCRC-DSA数据安全评估师:网络安全风险评估

1.网络安全风险评估概述 1.1概念 在当今信息化时代&#xff0c;网络安全成为了组织不可或缺的一部分。 风险评估作为一种科学方法&#xff0c;其目的是对网络系统的保密性、完整性、可控性和可用性这四个核心安全属性进行深入分析。 这一过程不仅包括识别网络系统中存在的脆…

Snipaste无法使用F1、F3等快捷键的保姆级解决方法

在Snipaste中按F1、F3等快捷键无效的可能原因&#xff1a; 1. 软件设置&#xff1a; 检查Snipaste的设置&#xff0c;确保F1被正确设置为截屏热键&#xff0c;并确认没有其他软件占用或冲突。 2. 热键冲突&#xff1a; 笔记本电脑的功能键&#xff08;F1-F12&#xff09;通常…

MySQL 数据库:原理、应用与发展

摘要&#xff1a;本文深入探讨了 MySQL 数据库相关内容。首先介绍了 MySQL 作为开源关系型数据库管理系统的显著特点&#xff0c;包括易用性、跨平台性、高性能、可扩展性、开源免费以及数据安全性等方面。接着详细阐述了其安装与配置过程&#xff0c;涵盖在不同操作系统上的安…

STM32使用 :串口的接收与发送

一、串口 在 STM32 中&#xff0c;串口&#xff08;UART&#xff0c;通用异步收发传输器&#xff09;是用于串行通信的外设。它在嵌入式系统中的作用非常广泛&#xff0c;主要包括几个方面 数据通信 串口用于微控制器与其他设备之间的数据传输。这些设备可以是其他微控制器、…

F12抓包08:查看网站Cookie

课程大纲 1、查看Cookie 1. 应用界面查看&#xff1a;按F12进入浏览器的开发者模式 - “应用”&#xff08;Application&#xff09; - Cookie&#xff0c;可查看Cookie并进行增、删、改、查操作。 2. 控制台命令行查看&#xff1a;按F12进入浏览器的开发者模式 - “控制台”&…

【无标题】nginx服务器代码信息、数据库连接信息、敏感文件的路径、服务器版本信息发起有针对性的攻击

Nginx敏感文件的路径、服务器版本信息 Nginx 403、404、500等错误时&#xff0c;返回详细错误信息。报错信息中可能会包含服务器代码信息、数据库连接信息、敏感文件的路径、服务器版本信息等&#xff0c;攻击者可以利用这些信息来寻找已知的漏洞&#xff0c;从而发起有针对性…

『功能项目』管理器基类【38】

我们打开上一篇37单例模式框架的项目&#xff0c; 本章要做的事情是编写管理器基类 首先创建脚本&#xff1a;ManagerBase.cs using UnityEngine; public abstract class ManagerBase : MonoBehaviour{public virtual void Init() { } } public class ManagerBase<T> : …

【Qt系列样式表】探索Qt Widget的艺术化设计与应用(Macos风格)(持续更新中...)

✨✨ Rqtz 个人主页 : 点击✨✨ &#x1f308;Qt系列专栏:点击 &#x1f388;PyQt系列专栏:点击&#x1f388; &#x1f388;Qt智能车上位机专栏: 点击&#x1f388; &#x1f388;Qt串口助手专栏:点击&#x1f388; &#x1f4ab;宗旨:共享IT之美,共创机器未来 目录 界面…

毕业论文word页眉页脚和页码的问题

多找B站视频查看视频操作方法。 问题1&#xff1a; 不同章节的页眉显示不同内容。 解决办法&#xff1a; “布局”里面插入分节符 &#xff08;非 分页符。&#xff09; 问题2&#xff1a; 页眉需要奇偶页内容不同 解决办法&#xff0c;编辑页眉部分&#xff0c;设置 奇偶页…

Qt (15)【Qt窗口 —— 字体对话框 QFontDialog | 输入对话框 QInputDialog】

阅读导航 引言一、字体对话框 QFontDialog1. 简介2. 基本用法3. 示例代码 二、输入对话框 QInputDialog1. 简介2. 基本用法&#xff08;1&#xff09;双精度浮点型输入数据对话框&#xff08;2&#xff09;整型输入数据对话框&#xff08;3&#xff09;选择条目型输入数据框 3.…

李沐对大模型趋势的几点判断

李沐是上海交通大学 2011 届计算机科学与工程系本硕系友。他曾担任亚马逊资深首席科学家&#xff0c;加州大学伯克利分校和斯坦福大学的访问助理教授&#xff0c;是前 Marianas Labs 联合创始人&#xff0c;深度学习框架 Apache MXNet 的创始人之一。目前是 BosonAI 联合创始人…

【数据分析】标准误差与标准差的区别

标准误差&#xff08;Standard Error, SE&#xff09;和标准差&#xff08;Standard Deviation, SD&#xff09;是两个在统计学中非常重要的概念&#xff0c;但它们的含义和用途有所不同。以下是它们之间的主要区别&#xff1a; 定义&#xff1a; 标准差&#xff1a;衡量单个数…

【mybatis】使用模糊查询时报错:Encountered unexpected token: “?“ “?“

报错信息如下&#xff1a; Mapper.xml报错代码&#xff1a; AND HILIST_NAME like %#{hilistName}% 解决方案&#xff1a; 把模糊查询的 sql 语句改为使用 CONCAT 命令拼接, 就不会报错了。 AND HILIST_NAME like CONCAT(%, #{hilistName},%)

深入探讨-JavaScript-逻辑赋值运算符

null && 100**2 // null undefined && 100**2 // undefined 逻辑赋值运算符 && || ?? 这个运算符将赋值与条件逻辑运算符结合在一起&#xff0c;因此命名为**“逻辑赋值”** 。它们只是一种简写&#xff0c; 例如&#xff0c;x && y是x …

Windows本地部署ollama并实现无公网IP远程运行qwen大语言模型

文章目录 前言1. 运行Ollama2. 安装Open WebUI2.1 在Windows系统安装Docker2.2 使用Docker部署Open WebUI 3. 安装内网穿透工具4. 创建固定公网地址 前言 本文主要介绍如何在Windows系统快速部署Ollama开源大语言模型运行工具&#xff0c;并安装Open WebUI结合cpolar内网穿透软…

打造智能数据分析平台:基于 Flask 的数据处理与模型精度验证系统

数据分析和机器学习已成为企业和科研中不可或缺的核心技术。在这个数据驱动的时代&#xff0c;能够快速处理海量数据&#xff0c;并通过智能算法提取出有用信息&#xff0c;成为了提升竞争力的关键。为了解决这些需求&#xff0c;我基于 Flask 开发了一款功能强大、模块化的数据…

【蓝桥杯单片机-0.基于定时器的时钟程序设计】

蓝桥杯单片机-0.基于定时器的时钟程序设计 题目小注意点按键三行矩形按键按键功能时间更新显示界面的小数点LED灯闪烁其他功能 完整代码 链接: 视频搭配视频&#xff0c;这里只是一些笔记&#xff0c;并不完整 题目 小注意点 按键三行 Key_Val Key_Read();//实时读取键码值…

JDBC知识点总结概括(day29)

1 学习目标 了解JDBC的概念重点掌握JDBC的CRUD重点掌握JDBC的各个对象的使用 2 GIT 查看安装手册 3 JDBC概述 3.1 数据的持久化 持久化(persistence)&#xff1a;把数据保存到可掉电式存储设备中以供之后使用。大多数情况下&#xff0c;特别是企业级应用&#xff0c;数据持…