STL库 —— string 类的编写

目录

一、成员函数

1.1 构造函数

1.1.1 无参构造

1.1.2 传参构造

1.1.3 优化

 1.2 析构函数

1.3 拷贝构造函数

1.4 赋值运算符重载

二、容量成员

2.1 size 函数

2.2 capacity 函数

2.3 reserve 函数

2.3 resize 函数

2.4 clear 函数 

三、元素访问成员

3.1 [] 的重载

四、修饰符成员

4.1 push_back 函数

4.2 append 函数

4.3 += 的重载

4.4 insert 函数 

4.5 erase 函数

4.6 swap 函数

五、迭代器成员

5.1 begin 函数

5.2 end 函数

六、字符串操作成员

6.1 find 函数

6.2 substr 函数

七、非成员函数重载

7.1 << 的重载

7.2 << 的重载


由于历史遗留问题, string 早于 STL 出现,所以 string 并不能属于 STL 库,但是由于其特性和 STL 中的其他容器类似,所以我把它当作 STL 的成员之一。

首先先创建一个 .h 头文件 和 .cpp 源文件:

在 .h 文件中使用命名空间

一、成员函数

1.1 构造函数

1.1.1 无参构造

my_string()
{
	_str = nullptr;
	_size = 0;
	_capacity = 0;
}    

1.1.2 传参构造

传参构造还可以像无参构造这样直接赋值吗?

my_string(const char* str)
{
	_str = str;
	_size = strlen(str);
	_capacity = _size + 1;
}

很显然是不行的,如下,如果我们一开始就传入字符串,字符串是 const char* 类型的,明显不可以用 char* 类型的 _str 接收,那我们应该怎么办?写一个 const char* 类型的类显然是不可取的。

下面来看我们的解决方法:

my_string(const char* str)
{
    _str = new char[strlen(str)];
	_str = strcpy(_str, str);
	_size = strlen(str);
	_capacity = strlen(str);
}

1.1.3 优化

我们可以通过给缺省值完成对无参构造和传参构造的优化:

my_string(const char* str = "")
{
	_str = new char[strlen(str) + 1];
    strcpy(_str, str);

	_size = strlen(str);
	_capacity = _size;
}

 1.2 析构函数

~my_string()
{
	_str = nullptr;
	_size = 0;
	_capacity = 0;
}

1.3 拷贝构造函数

my_string(const my_string& s)
{
    //注意这里的s._size + 1
	_str = new char[s._size + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}

1.4 赋值运算符重载

赋值运算符重载需要我们使用深拷贝的方式进行赋值,这就需要我们重新开空间,而且我们要事先完成对原类中 _str 的清空。

my_string operator=(const my_string& s)
{
	char* tmp = new char[s._capacity + 1];
	strcpy(tmp, s._str);
			
	delete[] _str;
	_str = tmp;
	_size = s._size;
	_capacity = s._capacity;
}

二、容量成员

2.1 size 函数

size_t size() const //加const可以扩大适用范围
{
	return _size;
}

2.2 capacity 函数

size_t capacity() const
{
	return _capacity;
}

2.3 reserve 函数

reserve 的主要目的是为了优化性能。如果你知道将来会要在字符串后附加大量字符,通过 reserve 函数提前分配足够的内存可以避免多次的内存重新分配,从而提高性能。
也就是说 reserve 函数只是让 _str 指向了更大的内存空间,并不改变字符串本身的值。

void reserve(size_t n)
{
	if (n > _capacity)
	{
        //为'/0'预留空间
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

2.3 resize 函数

void resize(size_t n, char ch = '\0')
{
	if (n <= _size)
	{
		_str[n] = '\0';
		_size = n;
	}
	else
	{
		reserve(n);
		for (size_t i = _size; i < n; i++)
		{
			_str[i] = ch;
		}
		_str[n] = '\0';
		_size = n;
	}
}

2.4 clear 函数 

void clear()
{
	_size = 0;
	_str[_size] = '\0';
}

三、元素访问成员

3.1 [] 的重载

我们首先要清楚的是,我们的 _str 定义的数组是 new 出来的,它的数据存放在堆中,析构函数调用前一直存在,所以我们可以直接返回它的引用。 
而且我们可以使用 assert 有效地检查是否越界!

char& operator[](size_t pos)
{
    assert(pos > _size);
	return _str[pos];
}

但是如果我们创建了一个 const 修饰的 string 对象,我们上面的函数就不能用了,所以我们还要再写一个使用 const 修饰的函数

const char& operator[](int& pos) const
{
	assert(pos > _size);
	return _str[pos];
}

其中,第一个 const 防止我们修改返回的字符,而第二个 const 保证了函数不会修改其所属的对象。

四、修饰符成员

4.1 push_back 函数

push_back 函数要考虑到扩容问题,由于每个编译器的扩容机制不同,这里默认二倍扩容,且要考虑到 capacity 为 0 时的情况! 

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

4.2 append 函数

void append(const char* s)
{
	int len = strlen(s);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	strcpy(_str + _size, s);
	_size += len;
}

4.3 += 的重载

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

4.4 insert 函数 

insert 函数涉及到数据挪动的问题,如果我们在 pos 处插入 ch 字符,那么从最后一个元素开始直到 pos 位置的元素都要依次挪到后一位后,再将 ch 插入。

//插入普通字符
void insert(size_t pos, char ch)
{
    assert(pos <= _size)
	if (_size <= _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}
	for (size_t i = _size; i > pos; i--)
	{
		_str[i + 1] = _str[i];
	}
	_str[pos] = ch;
    _size += 1;
}
//插入字符串
void insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size <= _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}
    for (size_t i = _size; i > pos; i--)
	{
		_str[i + len] = _str[i];
	}
	strncpy(_str + pos, str, len);
	_size += len;
}

4.5 erase 函数

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

4.6 swap 函数

void swap(my_string& s)
{
	// 交换 _str,_size,_capacity
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

五、迭代器成员

如果要实现遍历元素,我们就必不可少的要提供迭代器。
为了向官版靠拢,我们也 typedef 一下

5.1 begin 函数

typedef char* iterator;
typedef const char* const_iterator;
const_iterator begin() const
{
	return _str;
}

iterator begin()
{
	return _str;
}

5.2 end 函数

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

iterator end()
{
	return _str + _size;
}

六、字符串操作成员

6.1 find 函数

//查找字符
size_t find(char ch, size_t pos = 0) const
{
	assert(pos < _size);

	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
{
	assert(pos < _size);

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

6.2 substr 函数

my_string substr(size_t pos = 0, size_t len = npos) const
{
	my_string sub;
	if (len >= _size - pos)
	{
		for (size_t i = pos; i < _size; i++)
		{
			sub += _str[i];
		}
	}
	else
	{
		for (size_t i = pos; i < pos + len; i++)
		{
			sub += _str[i];
		}
	}
	return sub;
}

七、非成员函数重载

7.1 << 的重载

重载 ostream 的 << 运算符一般不能在类内部完成,原因是 << 运算符的左侧是一个 ostream 对象,而不是你正在定义的类的对象。在类内部定义的成员函数,其隐含的 this 指针总是指向类的一个对象,这就是为什么我们通常需要在类外部,但在同一个命名空间内,定义这样的函数。

不过,可以在类内部声明这个函数为友元函数,然后在类外部提供定义。

friend std::ostream& operator<<(std::ostream& out, const my_string& s);

函数定义:

std::ostream& operator<<(std::ostream& out, const my_string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}

	return out;
}

7.2 << 的重载

我们可以直接使用 += ,将输入的字符新增到原字符串中:

std::istream& operator>>(std::istream& in, my_string& s)
{
	s.clear();
	char ch;
	in >> ch;
	while (ch != '\n' && ch != ' ')
	{
		s += ch;
		ch = in.get();
	}

	return in;
}

但是我们的 in 在这里是 cin 的别名,而 cin 不支持对 [空格] 和 [换行] 的读取,它们被认为是分隔的标志,所以默认不读取,这样的话,我们的 while 循环条件就不成立了,会陷入死循环的境地!

然后我们去到 istream 的类中,观察到其还有一个 get 函数成员: get 函数有多种形式,但其基本用法是从输入流中读取一个字符,包括空白字符(如空格、制表符和换行符)。
注意:仅读取单个字符时包含 '\0' 、 ' ' 与 '\n' !

std::istream& operator>>(std::istream& in, my_string& s)
{
	s.clear();
	char ch;
	ch = in.get();
	while (ch != '\n' && ch != ' ')
	{
		s += ch;
		ch = in.get();
	}

	return in;
}

但是有前辈发明了更好的写法,他们模拟了一个缓冲区,当缓冲区满了才会把数据刷新

std::istream& operator>>(std::istream& in, my_string& s)
{
	s.clear();
	char ch;
	ch = in.get();
	char buff[128];
	size_t i = 0;
	while (ch != '\n' && ch != ' ')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}
	}
	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

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

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

相关文章

希尔排序解读

在算法世界中&#xff0c;排序算法是至关重要的一部分。而希尔排序&#xff08;Shell Sort&#xff09;作为一种基于插入排序的改进算法&#xff0c;通过允许交换非相邻元素&#xff0c;从而在一定程度上提高了排序效率。本文将深入探讨希尔排序的原理、实现方式以及它的性能特…

InternLM2-Chat-1.8B 模型测试

在interStudio进行InternLM2-Chat-1.8B模型访问&#xff0c;进入开发机后 配置基础环境 新建conda环境并且进入 conda create -n demo python3.10 -y conda activate demo 下载pytorch等相关包 conda install pytorch2.0.1 torchvision0.15.2 torchaudio2.0.2 pytorch-cuda11.…

力扣 76.最小覆盖子串

题目&#xff1a; 题目理解&#xff1a;这题属于最小滑动窗口。所求得的连续滑动窗口包含来t中的字符&#xff0c;不一定要按照t中的顺序。 class Solution {public String minWindow(String s, String t) {// table表示字符串t里的字符if (s null || s.length() 0 || t n…

ThingsBoaed、系统模块层级讲解

系统管理员能够使用租户配置文件为多个租户配置通用设置。每个租户在单个时间点都拥有唯一的个人资料。 让我们一一查看租户配置文件中的可用设置。 配置文件配置 这些设置允许系统管理员配置对租户创建的实体数量的限制&#xff0c;设置每月最大消息数、API 调用数的限制&…

Java集合详解(一)-- List集合

1.集合简介 java集合可分为Set、List、Queue和Map四种体系。 Java集合就像一种容器&#xff0c;可以把多个对象&#xff08;实际上是对象的引用&#xff0c;但习惯上都称对象&#xff09;“丢进”该容器中。从Java 5 增加了泛型以后&#xff0c;Java集合可以记住容器中对象的数…

02-JDK新特性-try-with-resources自动管理资源关闭

try-with-resources 为什么要介绍这个了 看看一下以下代码&#xff1a; public static void fileCopyByTryWithResources(File src, File des) throws IOException {try (FileInputStream fis new FileInputStream(src); FileOutputStream fos new FileOutputStream(des);…

SecureCRT防止超时自动断开

Options——>Session Options——>Terminal——>选择 Send protocol NO-OP ——>60seconds&#xff08;每一分钟发送一次请求&#xff09;

【考研数学】《1800》《660》还是《880》?怎么刷效果最好?

刷题吃不透&#xff0c;做了再多也没用&#xff01; 你目前连1800都没法拿下&#xff0c;你还着急要做660和880&#xff0c;是认真的吗&#xff1f; 这《接力题典1800》有啥特色呢&#xff1f;知识点全面覆盖&#xff0c;题目中规中矩&#xff0c;配合汤老师的视频看效果更佳…

【二分查找】Leetcode 二分查找

题目解析 二分查找在数组有序可以使用&#xff0c;也可以在数组无序的时候使用&#xff08;只要数组中的一些规律适用于二分即可&#xff09; 704. 二分查找 算法讲解 当left > right的时候&#xff0c;我们循环结束&#xff0c;但是当left和right缩成一个点的时候&#x…

【Java SE】继承与组合

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. 再谈初始化2. 再谈protected关键字2.1 子类可见性2.2 访问修饰限定符的选择 3. 继承与组合 1. 再谈初始化…

Python实现获取某手视频评论【自动生成did】

今天在获取某手视频评论的时候&#xff0c;总是会出发风控导致web_did失效&#xff0c;就算登录了也没用&#xff0c;还会导致账号被风控&#xff0c;app端抓包和逆向难度又大&#xff0c;那么有没有一种不需要登录而且不会出发风控的方法来获取评论列表呢&#xff1f;当然有&a…

Python球球大作战

文章目录 写在前面球球大作战程序设计注意事项写在后面 写在前面 安装pygame的命令&#xff1a; pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pygame球球大作战 《球球大作战》是一款简单易上手、充满趣味性和竞技性的休闲手游。游戏的核心玩法可以用一句话概…

机器学习 | 基于Scikit-learn中手写数字集的交叉验证

在本文中&#xff0c;我们将讨论交叉验证及其在手写数字集上的使用。此外&#xff0c;我们将看到使用手写数字集的代码实现。 什么是交叉验证&#xff1f; 手写数字集的交叉验证将允许我们选择最佳参数&#xff0c;避免过度拟合训练数据集。它是一个试验的尝试程序&#xff0…

【Python】Tkinter模块(巨详细)

专栏文章索引&#xff1a;Python 有问题可私聊&#xff1a;QQ&#xff1a;3375119339 目录 一、窗口设计 1.创建窗口 2.窗口属性 3.窗口位置 4.Widget 一、窗口设计 1.创建窗口 实例-创建空白窗口&#xff1a; from tkinter import * # 导入tkinter模块win Tk() # 通…

算法(二分查找)

我们有三种方式可以使用二分查找 1.朴素的二分查找&#xff0c;这种方式可能存在局限性 2.查找左边界的二分查找 3.查找右边界的二分查找 1.二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums…

JVM调优参数介绍

堆配置 -Xms:初始堆大小 -Xms&#xff1a;最大堆大小 -XX:NewSizen:设置年轻代大小 -XX:NewRation:设置年轻代和年老代的比值。如&#xff1a;为3表示年轻代和年老代比值为1&#xff1a;3&#xff0c;年轻代占整个年轻代年老代和的1/4 -XX:SurvivorRation:年轻代中Eden区与…

英语学习笔记-元音

元音 什么是元音呢&#xff1f;简单来说就是&#xff0c;在发音时&#xff0c;气流非常通畅&#xff0c;没有阻碍&#xff0c;想发多大声都可以。 元音分为&#xff1a; 单元音双元音 总共有20个元音 如何发音 根据上图&#xff0c;发音可以分为两类&#xff1a; 黑色部分…

链式二叉树经典OJ题目(二)

目录 结构体及头文件&#xff1a; 1.二叉树的前序遍历 题目描述&#xff1a; 思路分析&#xff1a; 源码&#xff1a; 2.二叉树的翻转 题目描述&#xff1a; 思路分析&#xff1a; 源码&#xff1a; 3.另一颗子树 题目描述&#xff1a; 思路分析&#xff1a; 源码&…

00-JAVA基础-动态编译

动态编译 JAVA 6 引入了动态编译机制。Java 动态编译是指在运行时将Java源代码编译成可执行的字节码。这通常使用Java的内置编译器API javax.tools.JavaCompiler 来实现。 动态编译的应用场景 可以做一个浏览器编写java代码&#xff0c;上传服务器编译和运行的在线测评系统服…

我为什么会选择Vim来开发Go项目及Vim IDE安装配置和操作

你好&#xff0c;我是孔令飞&#xff0c;字节跳动云原生资深研发、前腾讯云原生技术专家。《企业级 Go 项目开发实战》、《从零开发企业级 Go 应用》作者&#xff0c;欢迎加入 孔令飞的云原生实战营&#xff0c;助你进阶 Go 云原生高级开发工程师。 作为一名 Golang 开发&…