yo!这里是STL::string类简单模拟实现

目录

前言

常见接口模拟实现

默认成员函数

1.构造函数

2.析构函数

3.拷贝构造函数

4.赋值运算符重载

迭代器

简单接口

1.size()

2.c_str()

3.clear()

操作符、运算符重载

1.操作符[]

2.运算符==

3.运算符>

扩容接口

1.reserve()

2.resize()

增删查改接口

1.push_back()

2.append()

3.运算符+=

4.insert()

5.erase()

6.find()

7.substr()

流插入&流提取

后记


前言

        我们知道,熟练使用STL是c++程序员的必备技能之一,今天我们来了解STL中的string类,即字符串类型,与c语言中的字符串类似,c++中只是对此做了封装,以及一些接口,基础的知识点这里不再赘述,全部接口功能包括但不限于默认成员函数、运算符操作符重载、reserve、push、pop、insert等等,更多接口介绍参考cplusplus.com/reference/string/string/ ,下面介绍一些常见或者主要接口的实现。

常见接口模拟实现

  • 默认成员函数

1.构造函数

        如下图可知,库中string构造函数很多,适合接受不同参数列表的初始化方式,我们实现一种参数列表全缺省的构造函数,可以满足构造基本要求:

①传c类字符串就是用此字符串给类型初始化;

②不传就是用空字符串初始化对象,

注意:初始化时_size和_capacity都是初始化为字符串的长度,而new申请空间时多申请一个用来存放'\0'。

代码:

String(const char* str = "")
	{
		_size = strlen(str);  //初始化列表有依赖关系时建议不用初始化列表,在大括号内初始化
		_capacity = _size;
		_str = new char[_capacity + 1];

		strcpy(_str, str);
	}

2.析构函数

        析构函数在库中只有下面一种,无参数无返回值,符合特性,我们知道,此类涉及申请内存,所以在析构函数中一定要释放内存,将变量置0,代码如下:

代码:

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

3.拷贝构造函数

        我们知道,拷贝构造函数是构造函数的一种,与普通构造函数写法类型,这是一种传统写法,代码如下,但是,这里讲一种现代写法,也是一种的复用的方法,就是定义一个临时对象,调用其自己的构造函数初始化,之后将初始化的成员交换给目标对象,而临时对象会自动调用析构函数释放,完美地将目标对象初始化完成。

代码:

//现代写法(老板思维)
String(const String& s)
	:_str(nullptr)
	,_size(0)
	,_capacity(0)
{
	String tmp(s._str);
	swap(_str, tmp._str);
	swap(_size, tmp._size);
	swap(_capacity, tmp._capacity);
}

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

4.赋值运算符重载

        库中的赋值运算重载如图,这里实现第一种,传统写法就是赋值_sieze、_capacity之后开空间传元素,考虑还用一下现代写法,去定一个临时对象调用拷贝构造初始化,再将其成员交换给目标对象,目标对象完美完成其初始化。

注意:考虑自己给自己赋值的情况

代码:

	//现代写法(老板思维)
	String& operator=(const String& s)
	{
		if (&s != this)
		{
			String tmp(s);
			/*delete[] _str;
			_str = nullptr;*/  //无需在此释放,交换给tmp后,出了作用域自动调用析构清理
			swap(_str, tmp._str);
			swap(_size, tmp._size);
			swap(_capacity, tmp._capacity);
		}
		return *this;
	}

    //传统写法
	//String& operator=(const String& s)
	//{
	//	if (&s == this)   //注意:自己给自己赋值的情况
	//		return *this;
	//	_size = s._size;
	//	_capacity = _size;

	//	/*delete[] _str;  //先释放的话万一new失败了就导致释放但未开辟空间成功
	//	_str = new char[_capacity + 1];*/
	//	
	//	char* tmp = new char[_capacity + 1];  //所以先开空间,开成功了再释放原空间
	//	delete[] _str;
	//	_str = tmp;
	//	strcpy(_str, s._str);
	//	return *this;
	//}
  • 迭代器

        迭代器有四种,这里实现两种以及其const型迭代器,string的迭代器就是原生指针(其他类型不一定),所以实现很容易,代码如下。 

代码:

	typedef char* iterator;  //字符串类中迭代器就是原生指针
	typedef const char* const_iterator;
	iterator begin()
	{
		return _str;
	}
	iterator end()
	{
		return _str + _size;
	}
	const_iterator begin() const
	{
		return _str;
	}
	const_iterator end() const
	{
		return _str + _size;
	}
  • 简单接口

1.size()

        size()就是获取字符串字符个数,很简单不过多赘述,设置成const成员函数,是因为可以接受普通对象,也可以接受const对象。 

代码: 

	size_t size() const
	{
		return _size;
	}

2.c_str()

        c_str()是获取c类型字符串,有一些场合可能需要传递指针型字符串,但是其是私有成员不能直接访问,此接口可以实现访问。

代码: 

	char* c_str() const
	{
		return _str;
	}

3.clear()

        clear()是清除字符串中字符,使其成为空串。 

代码: 

	void clear()
	{
		_str[0] = '\0';
		_size = 0;
	}
  • 操作符、运算符重载

1.操作符[]

        操作符[]重载是很重要的重载,实现字符串对象可以像数组一样使用[]加下标访问,代码实现也很简单,注意普通对象和const对象权限不同,需要分开写。

代码: 

	char& operator[](size_t pos)  //普通对象,[]访问元素可读可写
	{
		assert(pos < _size);

		return _str[pos];
	}
	const char& operator[](size_t pos) const  //const对象,[]访问元素只可读
	{
		assert(pos < _size);

		return _str[pos];
	}

2.运算符==

        看过笔者上篇文章(http://t.csdn.cn/xT4nq)可以知道,先重载==、>,其他关系运算符可以复用它们方便的实现出来,不多赘述,不理解的可以翻翻看。

代码: 

    bool operator==(const String& s) const
	{
		return strcmp(_str, s._str) == 0;
	}

3.运算符>

        参考==运算符介绍。

代码: 

	bool operator>(const String& s) const
	{
		return strcmp(_str, s._str) > 0;
	}
  • 扩容接口

1.reserve()

        reverse()是预留空间,但不初始化,即只改变_capacity,传进想要预留的字符个数n,比_capacity大就会释放空间重新申请并拷贝元素。

注意:为string申请空间一定要多申请一个空间放'\0'

代码: 

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

2.resize()

        resize()也是预留空间,并且初始化,即改变_capacity和_size,不传需要初始化的字符则默认是'\0',实现过程比reserve()多考虑一个赋值情况即可。

代码: 

    void resize(size_t n, char ch = '\0')
	{
		if (n > _size)
		{
			reserve(n + 1);
			for (size_t i = _size; i < n; i++)
			{
				_str[i] = ch;
			}
			_str[n] = '\0';
			_size = n;
		}
		else
		{
			_str[n] = '\0';
			_size = n;   //只需要改变size,不需要改变capacity
		}
	}
  • 增删查改接口

1.push_back()

        push_back()是在字符串结尾插入指定字符,首先检查是否需要扩容,需要扩容则要看是不是刚初始化的空字符串,空字符串默认先给4个地址空间,不是空字符串则扩容空间至二倍。

注意:

        ①莫忘_size加一;

        ②莫忘加'\0'在结尾。        

代码: 

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

2.append()

        append()是在字符串尾部插入一个字符串,实现逻辑很简单,可以直接复用push_back,但避免重复向操作系统申请空间,提前预留好空间用来插入字符,代码如下。

代码: 

	void append(const char* str)
	{
		size_t i = 0;
		size_t n = strlen(str);
		if (_size + n > _capacity)
			reserve(_size + n);   //要尽量少的重复申请空间
		for (i = 0; i < n; i++)
		{
			push_back(str[i]);
		}
	}

3.运算符+=

        +=运算符重载则是在字符串尾部既可以插入字符,也可以插入字符串,调用上面俩函数即可。

代码: 

	String& operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	String& operator+=(const char* str)
	{
		append(str);
		return *this;
	}

4.insert()

        上面的插入操作只能作用在字符串尾部,而insert()函数是指定位置插入字符或者字符串,极端位置可以复用上面函数,普通位置需要移动元素,经历过c语言的学习,实现此功能并不难。

注意:代码中的标记点处是向后移动元素,注意移动过程中对下标的控制,不要越界访问。

代码: 

	String& insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
			reserve(_capacity ? _capacity * 2 : 4);
		if (pos == _size)
			push_back(ch);
		else
		{
			size_t end = _size + 1;
			while (end > pos)   //标记点
			{
				_str[end] = _str[end - 1];
				end--;
			}
			_str[pos] = ch;
			_size++;
		}
		return *this;
	}
	String& insert(size_t pos, const char* str)
	{
		size_t i = 0;
		size_t n = strlen(str);
		if (_size + n > _capacity)
			reserve(_size + n);
		if (pos == _size)
			append(str);
		else
		{
			size_t end = _size + n;
			while (end >= pos + n)   //标记点
			{
				_str[end] = _str[end - n];
				end--;
			}
			for (i = 0; i < n; i++)
			{
				_str[i + pos] = str[i];
			}
			_size += n;
		}
		return *this;
	}

5.erase()

        erase()则是删除指定位置指定长度的字符串,比插入的实现要简单,其中可以调用strcpy函数,npos是size_t类型的最大值,作为长度缺省值,实现不指定长度时默认是删除至结尾,注意删除完以后莫忘改变_size变量,其他并无难度。

代码: 

    void erase(size_t pos, size_t len = npos)
	{
		assert(pos < _size);
		if (_size - pos <= len)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			strcpy(_str + pos, _str + pos + len);
			_size -= len;
		}
	}

6.find()

        find()是查找指定字符或字符串第一次出现的位置下标,找不到返回npos,即无符号整型最大值,查找字符从头向后遍历查找即可,查找字符串调用strstr函数,注意此函数找到了是返回字符串地址,返回空是没找到,而find()是需要返回下标,注意控制。

代码: 

	size_t find(char ch, size_t pos = 0) const
	{
		assert(pos < _size);
		size_t i = 0;
		for (i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
				return i;
		}
		return npos;
	}
	size_t find(const char* substr, size_t pos = 0) const
	{
		assert(substr);
		assert(pos < _size);
		char* ptr = strstr(_str + pos, substr);
		if (ptr == nullptr)
			return npos;
		return ptr - _str;
	}

7.substr()

        substr()是提取指定位置指定长度的子串,形成返回string类对象,复用+=运算符简化了实现逻辑,大体实现逻辑与其他函数并无不同,不多赘述。

代码: 

	String substr(size_t pos, size_t len = npos)
	{
		assert(pos < _size);
		String tmp;
		if (_size - pos <= len)
		{
			tmp += (_str + pos);
		}
		else
		{
			for (size_t i = 0; i < len; i++)
			{
				tmp += _str[pos + i];
			}
		}
		return tmp;
	}
  • 流插入&流提取

        在上一篇文章Date类型的简单实现中,使用友元的方式将流插入、流提取编写成了全局函数,因为当时是因为需要访问私有成员变量,那么这里我们发现,可以不需要访问私有成员变量,所以这里不在需要成为友元。

        对于流插入,定义一个临时数组,大小固定为32个元素的地址空间,循环从键盘获取字符插入,当临时数组满了之后,就整体插入到对象中,当获取到空格或者回车就停止,符合流插入的特性。

注意:

        ①临时数组没满时就遇到空格或回车的情况;

        ②这里使用istream类的get函数获取字符,不仅可以接收到普通字符也可以收到空格和回车,用以判断,而不直接使用cin<<,是因为cin输入过程中遇到空格或回车就停止了,接收不到它们。

代码: 

istream& operator>>(istream& in, String& s)   //无需成为友元函数,因为不需要访问私有成员
{
	s.clear();

	const size_t N = 32;
	char tmp[N];
	size_t i = 0;

	char ch;
	ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		tmp[i++] = ch;
		if (i == N - 1)
		{
			tmp[i] = '\0';
			s += tmp;
			i = 0;
		}
		ch = in.get();
	}
	tmp[i] = '\0';
	s += tmp;

	return in;
}

ostream& operator<<(ostream& out, const String& s)   //无需成为友元函数,因为不需要访问私有成员
{
	for (size_t i = 0; i < s.size(); i++)
	{
		out << s[i];
	}
	return out;
}

后记

        今天介绍了一些string常见常用接口的使用及底层实现,希望对于以上接口的使用可以熟练掌握,对于不常见的可以了解,知道有这样一个函数,在后面需要使用的时候,也可以查阅文档即时了解一下(cplusplus.com/reference/string/string/),对于以上接口的使用或者实现不懂的地方,欢迎可以在评论区评论或者私我,一起学习进步哦!


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

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

相关文章

【数字IC基础】竞争与冒险

竞争-冒险 1. 基本概念2. 冒险的分类3. 静态冒险产生的判断4. 毛刺的消除使用同步电路使用格雷码增加滤波电容增加冗余项&#xff0c;消除逻辑冒险引入选通脉冲 1. 基本概念 示例一&#xff1a; 如上图所示的这个电路&#xff0c;使用了两个逻辑门&#xff0c;一个非门和一个与…

mybatis-spring

简介 通过简化实现流程&#xff0c;把MyBatis的最核心的内容展示出 mybatis的加载过程 执行流程 类图 核心流程 public class ApiTest {Testpublic void test_queryUserInfoById() {String resource "mybatis-config-datasource.xml";Reader reader;try {reader…

工业平板电脑优化汽车工厂的生产流程

汽车行业一直是自动化机器人系统的早期应用领域之一。通过使用具有高负载能力和远程作用的大型机械臂&#xff0c;汽车装配工厂可以实现点焊、安装挡风玻璃、安装车轮等工作&#xff0c;而较小的机械手则用于焊接和安装子组件。使用机器人系统不仅提高了生产效率&#xff0c;还…

STM32+FPGA的导常振动信号采集存储系统

摘 要 &#xff1a; 针 对 工 厂 重 要 设 备 运 输 途 中 可 能 损 坏 的情 况 &#xff0c; 本 文 设计 了一 套 采 用 &#xff33;&#xff34;&#xff2d;&#xff13;&#xff12;&#xff26;&#xff11;&#xff10;&#xff13;&#xff0b;&#xff26;&#xff3…

nginx mirror代码分析

实现方式 mirror逻辑的工作阶段&#xff1a; ngx在log phase之后&#xff08;在ngx_http_free_request处调用&#xff09;已完成向client端返回response&#xff0c;在log phase之后完成close connection&#xff08;短链接&#xff09;&#xff0c;在该阶段处理mirror逻辑不…

Godot 4 源码分析 - 获取属性信息

在管道通信基础上&#xff0c;可进行宿主程序与Godot的双向通信。 先拿属性信息试试手。 DrGraph端 static UnicodeString command "Book.position"; if (InputQuery("输入窗口", "请输入待获取的属性信息", command)) {TDrStream_Get drGet…

Vue2.x和Vue3.x面试常问知识点-面试题

SPA单页面的理解&#xff0c;它的优缺点分别是什么&#xff1f; 是什么 SPA&#xff08; single page application &#xff09;仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。 一旦页面加载完成&#xff0c;SPA 不会因为用户的操作而进行页面的重新加载或跳转 而…

数据结构和算法——表排序(算法概述、物理排序、复杂度分析,包含详细清晰图示过程)

目录 算法概述 物理排序 复杂度分析 算法概述 表排序用于 待排元素都为一个庞大的结构&#xff0c;而不是一个简单的数字&#xff0c;例如&#xff1a;一本书&#xff0c;一部电影等等。 如果这些待排元素都用之前的排序方法&#xff0c;元素需要频繁互换&#xff0c;那么…

内网穿透远程查看内网监控摄像头

内网穿透远程查看内网监控摄像头 在现代社会中&#xff0c;大家总是奔波于家和公司之间。大部分时间用于工作中&#xff0c;也就很难及时知晓家中的动态情况&#xff0c;对于家中有老人、小孩或宠物的&#xff08;甚至对居住环境安全不放心的&#xff09;&#xff0c;这已然是…

01)docker学习 centos7离线安装docker

docker学习 centos7离线安装docker 在实操前可以先看下docker教程,https://www.runoob.com/docker/docker-tutorial.html , 不过教程上都是在线安装方式,很方便,离线安装肯定比如在线麻烦点。 一、什么是Docker 在学习docker时,在网上看到一篇博文讲得很好,自己总结一下…

NAT协议(网络地址转换协议)详解

NAT协议&#xff08;网络地址转换协议&#xff09;详解 为什么需要NATNAT的实现方式静态NAT动态NATNAPT NAT技术的优缺点优点缺点 NAT协议是将IP数据报头中的IP地址转换为另外一个IP地址的过程&#xff0c;主要用于实现私有网络访问公有网络的功能。这种通过使用少量的IP地址代…

一百三十三、Hive——Hive外部表加载含有JSON格式字段的CSV文件数据

一、目标 在Hive的ODS层建外部表&#xff0c;然后加载HDFS中的CSV文件数据 注意&#xff1a;CSV文件中含有未解析的JSON格式的字段数据&#xff0c;并且JSON字段中还有逗号 二、第一次建外部表&#xff0c;直接以&#xff0c;分隔行字段&#xff0c;结果JSON数据只显示一部分…

(树) 剑指 Offer 07. 重建二叉树 ——【Leetcode每日一题】

❓剑指 Offer 07. 重建二叉树 难度&#xff1a;中等 输入某二叉树的 前序遍历 和 中序遍历 的结果&#xff0c;请构建该二叉树并返回其根节点。 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 示例 1: Input: preorder [3,9,20,15,7], inorder [9,3,15,20,7] …

[NLP]Huggingface模型/数据文件下载方法

问题描述 作为一名自然语言处理算法人员&#xff0c;hugging face开源的transformers包在日常的使用十分频繁。在使用过程中&#xff0c;每次使用新模型的时候都需要进行下载。如果训练用的服务器有网&#xff0c;那么可以通过调用from_pretrained方法直接下载模型。但是就本人…

5.2.tensorRT基础(2)-使用onnx解析器来读取onnx文件(源码编译)

目录 前言1. ONNX解析器2. libnvonnxparser.so3. 源代码编译4. 补充知识总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 t…

已解决:多线程环境中,新线程在使用cout函数打印输出到显示器出现数据混乱的情况

错误展示错误原因解决办法1. 在本问题情况下&#xff1a;使用printf函数替代cout&#xff1a;2. 使用互斥锁使 cout函数线程保持原子状态 什么是原子操作&#xff1f; 错误展示 最近学习多线程的时候&#xff0c;创建了一堆线程&#xff0c;然后每个线程都运行这个方法&#x…

大数据Flink(五十二):Flink中的批和流以及性能比较

文章目录 Flink中的批和流以及性能比较 ​​​​​​​​​​​​​​一、Flink中的批和流

LeetCode 2500. Delete Greatest Value in Each Row【数组,排序】简单

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

基于深度学习的裂纹图像分类研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

消息队列(一)-- RabbitMQ入门(1)

初识 RabbitMQ 核心思想&#xff1a;接收并转发消息。可以把它想象成一个邮局。 producer&#xff1a;生产者 queue&#xff1a;队列 consumer&#xff1a;消费者什么是消息队列 MQ&#xff08;Message Queue&#xff09;&#xff1a;本质是队列&#xff0c;FIFO先入先出&…
最新文章