【C++】学习笔记——string_4

news2024/11/16 15:46:37

文章目录

  • 六、string类
    • 7. string类的模拟实现
  • 未完待续


六、string类

7. string类的模拟实现

我们在上文简单实现了string类的构造函数。不知道大家有没有发现一个问题,我们在进行实现无参的构造函数时,初始化列表将 _str 初始化为 nullptr 了,但是这里真的可以初始化为 nullptr 吗?要仔细想一想,当不提供参数的时候,我们是应该得到一个空指针还是一个空串?对,我们应该是得到一个空串,空串末尾是携带 ‘\0’ 的。我们在修改上次的问题的同时,再将两个函数给合并成一个缺省函数。

// string.h 头文件下
#pragma once
#include<iostream>

namespace my
{
	class string
	{
	public:
		// 默认是空串而不是空指针
		string(const char* str = "")
			:_size(strlen(str))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

OK,接下来实现析构函数

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

析构函数没什么好说的,接下来我们来实现一下字符串的遍历
首先要实现的就是 size 函数了,遍历需要知道字符串的长度。

// 加 const 使其成为 const 成员函数,使 const 对象也能调用这个函数
size_t size() const
{
	return _size;
}

遍历方法①:下标 + [] 遍历

// 引用返回,可读可写
inline char& operator[](size_t pos)
{
	// 越界检查
	// assert函数需要: #include<assert.h>
	assert(pos < _size);
	// _str是字符数组
	return _str[pos];
}

写了这么多了,我们来使用一下。

// test.cpp 源文件下
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
using namespace my;

int main()
{
	string s("hello,world");
	for (size_t i = 0; i < s.size(); ++i)
	{
		std::cout << s[i];
	}
	std::cout << std::endl;
	return 0;
}

在这里插入图片描述
nice!但是,我们要知道,下标访问支持可读可写,但是const对象不支持,所以刚刚那个函数不支持const对象,于是我们得重载一个针对于const对象的下标访问函数。

// 针对 const对象 的可读不可写,加 & 是为了减少拷贝
inline const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}

访问容量函数:

size_t capacity() const
{
	return _capacity;
}

遍历方法②:迭代器。我们之前介绍了,迭代器是类似指针的东西,但是它不一定是指针! 但是我们这里可以就把它当作指针来实现。

// string.h 头文件下
#pragma once
#include<iostream>
#include<assert.h>

namespace my
{
	class string
	{
	public:
		// 底层是原生指针的迭代器
		typedef char* iterator;
		
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		// 默认是空串而不是空指针
		string(const char* str = "")
			:_size(strlen(str))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

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

		// 加 const 使其成为 const 成员函数,使 const 对象也能调用这个函数
		size_t size() const
		{
			return _size;
		}

		// 引用返回,可读可写
		inline char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		// 针对 const对象 的可读不可写,加 & 是为了减少拷贝
		inline const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}

		size_t capacity() const
		{
			return _capacity;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

来跑一跑:

// test.cpp源文件下
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
using namespace my;

int main()
{
	string s("hello,world");
	//for (size_t i = 0; i < s.size(); ++i)
	//{
	//	std::cout << s[i];
	//}
	//std::cout << std::endl;
	for (auto e : s)
	{
		std::cout << e;
	}
	std::cout << std::endl;
	return 0;
}

由于 范围for 底层就是替换成迭代器的形式,所以这里使用 范围for 来验证迭代器的实现。
在这里插入图片描述
没问题,接下来就是为 const对象 来实现专门的 const_iterator 了。

typedef const char* const_iterator;

const_iterator begin() const
{
	return _str;
}

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

反向迭代器咱们就不去实现了,咱们学到后面了在谈。
接下来实现 string类 的修改。①push_back 尾插。首先先实现扩容函数 reserve

void reserve(size_t n)
{
	// 只有要扩容的大小比当前容量大才能扩容
	if (n > _capacity)
	{
		// 开辟新空间,考虑 '\0' 的空间
		char* tmp = new char[n + 1];
		// 拷贝
		strcpy(tmp, _str);
		// 释放旧空间
		delete[] _str;
		// 更改指针指向
		_str = tmp;
		
		_capacity = n;
	}
}
void push_back(char ch)
{
	if (_size == _capacity)
	{
		// 扩容2倍
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}

	_str[_size] = ch;
	++_size;
	_str[_size] = '\0';
}

接下来是②append追加函数。

void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	// 从末尾开始,拷贝新字符串
	strcpy(_str + _size, str);
	_size += len;
}

接下来是③重载 +=

string& operator+=(char ch)
{
	push_back(ch);
	return *this;
}

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

由于我们还没有实现流插入和流提取函数,所以我们无法直接输出我们的字符串,但是我们可以实现 c_str() 函数,这个函数的作用是将 string类型 的字符串转换成C语言的字符数组。

char* c_str()
{
	// 返回字符数组就好
	return _str;
}

再来跑跑:

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
using namespace my;

int main()
{
	string s("hello,world");
	//for (size_t i = 0; i < s.size(); ++i)
	//{
	//	std::cout << s[i];
	//}
	//std::cout << std::endl;
	/*for (auto e : s)
	{
		std::cout << e;
	}
	std::cout << std::endl;*/
	s += '!';
	s += "YYYYYYYYY";
	std::cout << s.c_str() << std::endl;
	return 0;
}

在这里插入图片描述
inserterase

// 在 pos 位置插入一个字符
void insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		// 扩容2倍
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}
	// 小细节
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	++_size;
}

注意细节:end 选择的是每次右移的移动终点,如果是起点,会写成 end = _size; end >= pospos 等于 0 时,由于end和pos都是无符号整形,end不可能比 0 小,所以就会死循环,当end处于右侧时则完美解决了这个问题。

// 从 pos 开始,删除 len 个字符,如果 len 是 npos ,则全删
void erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	// pos + len >= _size 可能会溢出
	if (len == npos || len >= _size - pos)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	strcpy(_str + pos + len, _str + pos);
	_size -= len;
}

跑跑看:

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
using namespace my;

int main()
{
	string s("hello,world");
	//for (size_t i = 0; i < s.size(); ++i)
	//{
	//	std::cout << s[i];
	//}
	//std::cout << std::endl;
	/*for (auto e : s)
	{
		std::cout << e;
	}
	std::cout << std::endl;*/
	//s += '!';
	//s += "YYYYYYYYY";
	//std::cout << s.c_str() << std::endl;
	s.insert(5, 'T');
	std::cout << s.c_str() << std::endl;
	s.erase(8);
	std::cout << s.c_str() << std::endl;
	return 0;
}

在这里插入图片描述

接下来是 resize

void resize(size_t n, char ch = '\0')
{
	// 比 size 小则删除
	if (n <= _size)
	{
		_str[n] = '\0';
		_size = n;
	}
	else
	{
		// 比size大则填充
		reserve(n);
		for (size_t i = _size; i < n; ++i)
		{
			_str[i] = ch;
		}
		_str[n] = '\0';
		_size = n;
	}
}

再看看:

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
using namespace my;

int main()
{
	string s("hello,world");
	//for (size_t i = 0; i < s.size(); ++i)
	//{
	//	std::cout << s[i];
	//}
	//std::cout << std::endl;
	/*for (auto e : s)
	{
		std::cout << e;
	}
	std::cout << std::endl;*/
	//s += '!';
	//s += "YYYYYYYYY";
	//std::cout << s.c_str() << std::endl;
	//s.insert(5, 'T');
	//std::cout << s.c_str() << std::endl;
	//s.erase(8);
	//std::cout << s.c_str() << std::endl;
	s.resize(5);
	std::cout << s.c_str() << std::endl;
	s.resize(20, 'Q');
	std::cout << s.c_str() << std::endl;
	return 0;
}

在这里插入图片描述
写了这么多成员函数,但是没有写拷贝构造函数。有人可能会说哈,拷贝构造函数是默认成员函数,不写编译器也会自动生成,我们不需要写,但是真的不需要写吗?我们之前说过,编译器默认生成的拷贝构造都是浅拷贝,按字节一个一个拷贝,我们来看看下图:

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
using namespace my;

int main()
{
	string s("hello,world");
	// 拷贝构造
	string s2(s);
	return 0;
}

在这里插入图片描述
我们发现这里拷贝构造出来的 s2s_str 指向同一块空间,这就是浅拷贝导致的,这样的情况会使:操作其中一个,另一个也会改变,同一块空间会被析构两次,产生报错。所以说默认的拷贝构造函数不一定好,我们需要自己实现一个深拷贝的拷贝构造函数。

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

在这里插入图片描述
不一样了。


未完待续

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

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

相关文章

GAI工具哪家强?(ChatGPT 4 vs 文心一言)

开始之前&#xff0c; 先来看看 GAI和AI的区别和关系。 AI 和GAI AI 和GAI的概念 AI&#xff08;Artificial Intelligence&#xff09;是人工智能的缩写&#xff0c;是计算机科学的一个分支&#xff0c;旨在使机器像人类一样进行学习和思考。AI技术的研究领域包括机器人、语…

【Mac】Axure RP 9(交互原型设计软件)安装教程

软件介绍 Axure RP 9是一款强大的原型设计工具&#xff0c;广泛用于用户界面和交互设计。它提供了丰富的功能和工具&#xff0c;能够帮助设计师创建高保真的交互原型&#xff0c;用于展示和测试软件应用或网站的功能和流程。以下是Axure RP 9的主要特点和功能&#xff1a; 交…

基于t972 Android9 AP6256,如何在设置中添加5G热点选项,并使其正常打开

通过设置的的WiFi热点选项可以知道关键词“2.4GHz”&#xff0c;因此可以其全局搜索&#xff0c;在packages\apps\Settings\res\values\strings.xml文件下找到如下图所示&#xff0c; 从上面注释可以知道&#xff0c;选项按键选择2.4GHz触发的按键关键词是“wifi_ap_choose_2G…

9U_VPX信号处理机,传感器大数据异构计算平台

9U_VPX信号处理机 1 介绍 1.1 产品概述 9U_VPX信号处理机是一款面向前端射频系统的高速记录、存储和处理系统。信号处理机为应对军用电子信息系统面临的目标种类多样化、战场环境复杂化、执行任务多元化等多重难题&#xff0c;而研发出来的***数据记录存储系统。信号处理机担…

基于改进遗传优化的BP神经网络金融序列预测算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 遗传算法&#xff08;GA&#xff09;原理 4.2 BP神经网络原理 4.3 遗传优化BP神经网络结合应用 4.4 遗传算法简要改进 5.完整程序 1.程序功能描述 基于改进遗传优化的BP神经网络金融…

【计算机网络】FTP站点配置搭建教程以及相关问题解决方案(超详细)

文章目录 1、安装Window Server 20082、搭建FTP环境&#xff08;1&#xff09;安装FTP服务器&#xff08;2&#xff09;配置FTP服务器&#xff08;3&#xff09;测试FTP连接 3、遇到的问题以及解决方案&#xff08;1&#xff09;Windows无法访问此文件夹&#xff08;2&#xff…

Github 2024-05-01 开源项目月报Top20

根据Github Trendings的统计,本月(2024-05-01统计)共有20个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目13TypeScript项目5C项目2非开发语言项目1C++项目1JavaScript项目1Rust项目1Go项目1Shell项目1Svelte项目1编程面试大学:成为软件工程…

UDP/TCP

udp/tcp特征 udp&#xff1a; 无连接不可靠传输面向数据包全双工 tcp&#xff1a; 有连接可靠传输面向字节流全双工 解释&#xff1a; 有连接/无连接&#xff1a;发送消息时&#xff0c;对方是否必须要在线 比如我们聊天程序&#xff0c;我们给对方发送消息&#xff0c;是不管现…

HTML5实用大全(Part.1)

引言&#xff1a; 哈喽&#xff0c;各位小伙伴们&#xff0c;在本篇博客我将带领大家走进前端中的HTML5,利用HTML我们将可以在网页上自我创作内容&#xff0c;现在学起来&#xff0c;不久后自己也能制作一个花哨的项目了呢&#xff0c;那么&#xff0c;我们开始吧&#xff01; …

使用c++类模板和迭代器进行List模拟实现

List 一、创建节点结构二、创建迭代器类1、类的结构2、一系列的运算符重载 三、创建list1、细节把握2、迭代器函数3、构造函数和析构函数4、增删查改的成员函数 一、创建节点结构 template <class T>//节点结构 struct ListNode {ListNode<T>* _next;ListNode<…

交通 | 电动汽车车辆路径问题及FRVCP包的调用以及代码案例

编者按&#xff1a; 电动汽车的应用给车辆路线问题带来了更多的挑战&#xff0c;如何为给定路线行驶的电动汽车设计充电决策是一个需要解决的难题&#xff0c;本文介绍了开源python包frvcpy使用精确式算法对该问题求解。 文献解读&#xff1a;Aurelien Froger, Jorge E Mendo…

python 怎么调用R

如何在python中调用R&#xff1f;这其中包括了如何调用R的对象&#xff08;函数和包&#xff09;&#xff0c;R和python的对象如何互相转换&#xff0c;以及如何调用R的脚本&#xff08;外界参数的输入&#xff09;。python提供了一个模块rpy2&#xff0c;可以较好地完成这项工…

【深耕 Python】Data Science with Python 数据科学(18)Scikit-learn机器学习(三)

写在前面 关于数据科学环境的建立&#xff0c;可以参考我的博客&#xff1a; 【深耕 Python】Data Science with Python 数据科学&#xff08;1&#xff09;环境搭建 往期数据科学博文一览&#xff1a; 【深耕 Python】Data Science with Python 数据科学&#xff08;2&…

什么是发售?

什么是发售? 很多人不知道什么是发售,因为这个词刚被广而告之,在这里普及一下什么是发售? 发售,它是通过一套流程,把你的产品疯狂大卖的一种技术。通常有三个步骤,就是造势、预售、发售。那么这三个词怎么理解呢? 第一步:造势 造势的核心是引发关注,但是不做销售…

【开源设计】京东慢SQL组件:sql-analysis

京东慢SQL组件&#xff1a;sql-analysis 一、背景二、源码简析三、总结 地址&#xff1a;https://github.com/jd-opensource/sql-analysis 一、背景 开发中&#xff0c;无疑会遇到慢SQL问题&#xff0c;而常见的处理思路都是等上线&#xff0c;然后由监控报警之后再去定位对应…

06 - 步骤 add constants

简介 Add Constants 步骤是用于在数据流中添加常量字段的步骤。它允许用户在数据流中插入一个或多个常量字段&#xff0c;并为这些字段指定固定的数值、字符串或其他类型的常量值。 使用 场景 我需要在数据清后&#xff0c;这个JSON 字符串有一个固定的行流数据。 1、拖拽…

如何判断自己是不是偏执型人格障碍

什么是偏执性人格障碍&#xff1f; 偏执型人格障碍是比较常见的一种人格障碍类型&#xff0c;其特征是偏执&#xff0c;和一般的固执&#xff0c;顽固有所不同。通常我们说一个人很固执或顽固&#xff0c;更多是因为其坚持己见&#xff0c;不受他人的思想左右&#xff0c;其本…

代谢组数据分析七:从质谱样本制备到MaxQuant搜库

前言 LC-MS/MS Liquid Chromatography-Mass Spectrometry&#xff08;LC-MS/MS &#xff0c;液相色谱-质谱串联&#xff09;可用于残留化合物检测、有机小分子检测、鉴定和定量污染物以及在医药和食品领域添加剂检测和生物小分子等检测。 LC-MS/MS一般包含五个步骤&#xff…

yolov5口罩检测实战

学习资料提要&#xff1a;手把手教你使用YOLOV5训练自己的目标检测模型-口罩检测-视频教程_搭建yolo目标检测的环境. 使用yolo-air模块来做实验-CSDN博客 在B站上有这个UP主的实操视频 一 环境安装 1.先在anaconda prompt 里面 (1)conda activate 会转为&#xff08;base&…

【LeetCode刷题】875. 爱吃香蕉的珂珂

1. 题目链接 875. 爱吃香蕉的珂珂 2. 题目描述 3. 解题方法 简单的用我自己的理解来解释一下这道题的意思。 所以也就是说找到一个速度k&#xff0c;看还有没有比k更小的速度能吃完数组中的香蕉&#xff0c;如果有则继续寻找&#xff0c;没有则是k这个速度。就好比上面的解释…