C++——string的模拟实现(上)

news2024/10/27 23:06:20

目录

引言

成员变量

1.基本框架

成员函数

1.构造函数和析构函数

2.拷贝构造函数

3.容量操作函数

3.1 有效长度和容量大小

3.2 容量操作

3.3 访问操作

(1)operator[]函数

(2)iterator迭代器

3.4 修改操作

(1)push_back()和append()

(2)operator+=函数


引言

在 C++——string的了解和使用 中,我们学习了string的一些基础用法。接下来我们可以试着模拟实现string。

在C++中,std::string是一个功能强大且广泛使用的类,用于处理字符串。然而,了解其内部实现原理对于深入理解C++和编写高效代码至关重要。通过模拟实现一个简单的string类,我们可以更好地理解字符串的存储、管理以及操作。这不仅有助于我们更好地使用std::string,还能让我们在遇到特定需求时,能够自定义字符串类来满足这些需求。

成员变量

1.基本框架

为了与STL库中的string区分开来,我们要使用命名空间namespace进行封装。

char* _str:指向字符数组的指针,用于存储字符串的实际内容。

size_t  _size:表示字符串中有效字符的数量。

size_t  _capacity:表示字符数组的容量,即可以存储的最大字符数量(包括结尾的空字符\0)。

namespace My_string
{
    class string 
	{
    public:
        // ...
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

成员函数

老规矩,我们在 string.h 中,声明函数;在 string.cpp 中,实现函数的功能。

1.构造函数和析构函数

构造函数:接受一个C风格字符串作为参数,计算其长度,分配足够的内存来存储该字符串及其结尾的空字符,并复制字符串内容。

析构函数:释放分配给字符串的内存,并将指针设置为nullptr,以避免悬挂指针问题。同时,将_size和_capacity设置为0,表示对象已销毁。

string.h:

namespace My_string
{
    class string 
	{
    public:
		string(const char* str);//构造函数
		~string();				//析构函数
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

string.cpp:

#include"string.h"
namespace My_string
{
	// 构造函数
    string::string(const char* str)
    {
	    _size = strlen(str);
	    _capacity = _size;
	    _str = new char[_capacity + 1];    // +1用于储存'\0'
	    strcpy(_str, str);
    }

    // 析构函数
    string::~string()
    {
	    delete[] _str;
	    _str = nullptr;
	    _size = _capacity = 0;
    }
}

我们可以测试一下:

通过调试观察一下:

调用构造函数:

调用析构函数:

2.拷贝构造函数

拷贝构造函数:接受一个string对象作为参数,分配足够的内存来存储原对象的字符串内容,并复制该内容。

这里提供了三种实现方式,包括直接复制、使用临时对象进行深拷贝以及使用swap函数进行资源转移。

string.h:

string(const string& str);		//拷贝构造函数

string.cpp:

// 拷贝构造函数(1)
string::string(const string& str)
{
	_str = new char[str._capacity + 1];	//额外多给一个空间,用于存放'/0'
	strcpy(_str, str._str);		//拷贝数据
	_capacity = str._capacity;	//设置容量
	_size = str._size;			//设置有效数据个数
}

我们在这里也有其他的方法可以实现拷贝构造:

// 拷贝构造函数(2)
string::string(const string& str)
{
	string tmp(str._str);
	std::swap(tmp._str, _str);
	std::swap(tmp._size, _size);
	std::swap(tmp._capacity, _capacity);
}

以上代码还可以接着简化:

string::string(const string& str)
{
	string tmp(str._str);
	swap(tmp);			// 这里的swap我们接下来会定义
}

3.容量操作函数

3.1 有效长度和容量大小

我们先写这两个函数:

size()和capacity():分别返回字符串的有效长度和字符数组的容量。

string.h:

		size_t size() const;			// size()函数
		size_t capacity() const;		// capacity()函数

string.cpp:

// size()函数
size_t string::size() const
{
	return _size;
}

// capacity()函数
size_t string::capacity() const
{
	return _capacity;
}
3.2 容量操作

c_str():返回一个指向以空字符结尾的字符数组的指针,该数组包含与string对象相同的字符序列。这允许将string对象与接受C风格字符串的函数一起使用。

empty():检查字符串是否为空(即长度为0)。

erase():删除字符串中指定位置的字符或子字符串。

string.h:

const char* c_str() const;		// c_str()函数
bool empty() const;				// empty()函数
void erase(size_t pos = 0, size_t len = npos);    // erase()函数

string.cpp:

// c_str()函数
const char* string::c_str() const
{
	return _str;
}
// empty()函数
bool string::empty() const
{
	return _size == 0;
}
// erase()函数
void string::erase(size_t pos,size_t len)
{
	assert(pos < _size);				 

	if (len == npos || len >= _size - pos)
	{
		_str[pos] = '\0';		// 位置pos置为'\0'
		_size = pos;			// 有效元素个数为pos个
	}
	else	// len小于后面的字符个数
	{
		// 将后面的字符拷贝到pos位置
		strcpy(_str + pos, _str + pos + len);
		_size -= len;			// 更新有效元素
	}
}

接下来再来实现扩容函数reserve()和resize():

reserve():增加字符数组的容量,以确保可以存储至少n个字符。如果当前容量不足,则分配新的内存并复制现有内容。

resize():改变字符串的大小。如果新大小大于当前大小,则添加新字符(默认为\0);如果新大小小于当前大小,则删除多余的字符。

string.h:

// 预留空间
void reserve(size_t n);
// resize()函数
void resize(size_t n, char ch = '\0');

string.cpp:

// 预留空间
void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}
// resize()函数
void string::resize(size_t n, char ch)
{
	if (n > _size)
	{
		if (n > _capacity)
		{
			reserve(n);
		}
		// 使用 memset 函数将字符 ch 
		// 填充到新添加的空间中
		memset(_str + _size, ch, n - _size);
	}
	_size = n;
	_str[n] = '\0';
}
3.3 访问操作
(1)operator[]函数

operator[]函数的功能:返回pos位置的字符

string.h:

// 非const版本
char& operator[](size_t pos);	//operator[]函数
// const版本
const char& operator[](size_t pos)const;

string.cpp:

// operator[]函数
char& string::operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}
// const版本
const char& string::operator[](size_t pos)const
{
	assert(pos < _size);
	return _str[pos];
}

来个简单的代码测试一下:

(2)iterator迭代器

迭代器:提供begin()和end()函数来返回指向字符串开头和结尾的迭代器。这里简化了迭代器的实现,将其视为指向字符数组的指针。然而,在实际应用中,迭代器通常是一个更复杂的类,提供了更多的功能和安全性检查。

string.h:

//const版本的iterator
const_iterator begin() const;	//提供const_iterator begin()函数
const_iterator end() const;		//提供const_iterator end()函数

//非const版本的iterator
iterator begin();				//提供iterator begin()函数
iterator end();					//提供iterator end()函数

string.cpp:

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

还是老样子,我们使用一个简单的函数测试一下:

void test3()
{
	My_string::string str("hello");

	for (auto i : str)
	{
		cout << i << " ";
	}
	cout << endl;

	My_string::string::iterator it1 = str.begin();
	while (it1 != str.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;

	My_string::string::iterator it2 = str.end();
	if (it2 != str.begin()) 
	{ // 检查避免直接解引用 end()  
		--it2; // 先移动到一个有效的位置  
		while (it2 != str.begin())
		{
			std::cout << *it2 << " ";
			--it2;
		}
		std::cout << *it2 << " "; // 输出最后一个字符(begin() 之前的字符)  
	}
	std::cout << std::endl;
}

输出结果为:

3.4 修改操作
(1)push_back()和append()

string.h:

// 尾插一个字符
void push_back(char ch);
// 尾插一个字符串
void append(const char* str);

string.cpp:

//尾插一个字符
void string::push_back(char ch)
{
	if (_capacity == _size)
	{
		size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
		reserve(newcapacity);
	}
	_str[_size] = ch;
	_str[_size + 1] = '\0';
	_size++;
}

//尾插一个字符串
void string::append(const char* str)
{
	size_t len = strlen(str);

	if (_size + len > _capacity) 
	{
		reserve(_size + len);
	}
	strcpy(_str+_size, str);
	_size += len;			
}
(2)operator+=函数

我们可以借助上面两个函数实现operator+=函数。

string.h:

//operator+=函数可以构成重载,函数名相同,参数不同
string& operator+=(char ch);			// 字符相加
string& operator +=(const char* str);	// 字符串相加

string.cpp:

string& string::operator+=(char ch)
{
	// 调用push_back()函数
	push_back(ch);		
	return *this;
}
string& string::operator+=(const char* str)
{
	append(str);
	return *this;
}

来测试一下:

operator+=函数返回的是对象本身。

内置类型的+=运算符返回值的副本。

自定义类型的+=运算符通常返回对象的引用(即*this),以支持链式操作和避免复制。

———————————————————————————————————————————

以上为string模拟实现的第一篇

求点赞收藏评论关注!!!

感谢各位大佬!!!

第二篇链接:C++——string的模拟实现(下)

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

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

相关文章

GoFly快速开发框架已集成了RTSP流媒体服务器(直播、录播)代码插件-开发者集成流媒体服务器更加方便

温馨提示&#xff1a;我们分享的文章是给需要的人&#xff0c;不需要的人请绕过&#xff0c;文明浏览&#xff0c;误恶语伤人&#xff01; 说明 本代码包是音视频流媒体服务器&#xff0c;支持RTSP推流到服务器、服务端录像与回放。在视频直播、安防及车辆监控视频上云、无人…

解读AVL树:平衡二叉搜索树的奥秘

✨✨小新课堂开课了&#xff0c;欢迎欢迎~✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C&#xff1a;由浅入深篇 小新的主页&#xff1a;编程版小新-CSDN博客 前言&#xff1a; 前面我们已经介绍了二叉搜…

编译却出现Error: C9555E: Failed to check out a license.问题解决

学stm32的时候&#xff0c;照着操作突然发现编译报错。 去网上搜索答案的时候发现了这篇文章&#xff1a;参考博客:KEI5许可证没到期&#xff0c;编译却出现Error: C9555E: Failed to check out a license.问题解决 根据网上找的资料发现应该是版本问题导致的。在上一篇博客…

Prompt提示词设计:如何让你的AI对话更智能?

Prompt设计&#xff1a;如何让你的AI对话更智能&#xff1f; 在人工智能的世界里&#xff0c;Prompt&#xff08;提示词&#xff09;就像是一把钥匙&#xff0c;能够解锁AI的潜力&#xff0c;让它更好地理解和响应你的需求。今天&#xff0c;我们就来聊聊如何通过精心设计的Pr…

【纯血鸿蒙】安装hdc工具

这里我先写Mac版的,Windows的在下面 首先要知道你的SDK安装在哪里了,不知道的话,可以打开DevEco Studio,打开设置页面里的HarmonyOS SDK,这个我们之前配置环境变量的时候用过。 其实主要是用到这里toolchains下的hdc命令。 所以我们需要配置环境变量。 1、打开Mac下的…

关于OpenFeign中@GetMapping不支持Java对象传递

原因&#xff1a;在使用openfeign远程调用的时候&#xff0c;使用get请求时候发现&#xff0c;如果feign里面传的是实体&#xff0c;当然接口里面也是需要传实体&#xff0c;但是发现出错了&#xff0c;后来去官网找了一下&#xff0c;发现get请求不支持传入Java实体对象&#…

网络原理之 TCP解释超详细!!!

TCP 有连接的 可靠传输 面向字节流 全双工 其中最核心的是可靠传输 那么 TCP 如何使用可靠传输的 ??? 我们尽可能传过去, 如果传不过去,发送方至少知道自己没传过去, 所以在于接收方, 收到或者没有收到, 都会有应答的操作 1. 确认应答 实现可靠性最核心的机制!!! 引出 …

一种解决Leaflet中Divicon城市气温标注空间重叠的办法

目录 前言 一、一些解决办法 1、marker的聚类 2、使用leaflet-canvas-label 3、使用Zoom和样式控制 二、基于rbush和Leaflet.LayerGroup.Collision的解决办法 1、关于rbush 2、Leaflet.LayerGroup.Collision.js 三、解决标签重叠的具体实现 1、添加元数据 2、添加到…

理解OAuth2与用户账户与授权UAA的关系

目录 关于OAuth2OAuth2的核心组件授权流程授权模式使用场景优点与缺点 关于UAA技术解释 UAA与OAuth2的关系 关于OAuth2 ‌‌OAuth2&#xff08;开放授权2.0&#xff09;是一个开放标准&#xff0c;用于授权第三方应用程序访问用户资源&#xff0c;而无需共享用户的用户名和密码…

c语言中值调用(call by value)方式和引用调用(call by reference)

在C语言中参数传递主要有两种方式&#xff1a;通过值调用&#xff08;call by value&#xff09;和通过引用调用&#xff08;call by reference&#xff09;。 通过值调用&#xff08;Call by Value&#xff09; 说明&#xff1a;当使用值调用时&#xff0c;函数接收到的是参数…

(三)第一个Qt程序“Qt版本的HelloWorld”

一、随记 我们在学习编程语言的时候&#xff0c;各种讲解编程语言的书籍中通常都会以一个非常经典的“HelloWorld”程序展开详细讲解。程序虽然简短&#xff0c;但是“麻雀虽小&#xff0c;五脏俱全”&#xff0c;但是却非常适合用来熟悉程序结构、规范&#xff0c;快速形成对编…

Linux--学习笔记

第一章、简单使用Linux 1. Linux系统基本概念 多用户的系统&#xff1a;允许同时有很多个用户登录系统&#xff0c;使用系统里的资源多任务的系统&#xff1a;允许同时执行多个任务严格区分大小写&#xff1a;命令&#xff0c;选项&#xff0c;参数&#xff0c;文件名&#x…

从零实现数据结构:一文搞定所有排序!(下集)

1.快速排序 思路框架&#xff1a; 在有了前面冒泡选择插入希尔排序之后&#xff0c;人们就在想能不能再快一点&#xff0c;我们知道排序算法说人话就是把大的往后放小的往前放&#xff0c;问题就在于如何更快的把大的挪到数组队尾小的挪到数组前面。这里我们先总结一下上集前…

jenkins 自动化部署Springboot 项目

一、安装docker 1.更新yum命令 yum -y update2.查看机器有残留的docker服务&#xff0c;有就卸载干净 查看docker 服务 rpm -qa |grep docker卸载docker sudo yum remove docker-ce docker-ce-cli containerd.io sudo rm -rf /var/lib/docker sudo rm -rf /var/lib/contai…

算法的学习笔记—二叉树的深度(牛客JZ55)

&#x1f600;前言 在二叉树的相关操作中&#xff0c;计算树的深度是一个非常基础但十分重要的操作。本文将详细解析如何计算一棵二叉树的深度&#xff0c;并通过代码实现来展示具体的解决方案。 &#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录 &#x1f49d;二叉树的深度…

了解 .NET 8 中的定时任务或后台服务:IHostedService 和 BackgroundService

IHostedService.NET 8 引入了使用和管理后台任务的强大功能BackgroundService。这些服务使长时间运行的操作&#xff08;例如计划任务、后台处理和定期维护任务&#xff09;可以无缝集成到您的应用程序中。本文探讨了这些新功能&#xff0c;并提供了实际示例来帮助您入门。您可…

HarmonyOS开发 - 本地持久化之实现LocalStorage实例

用户首选项为应用提供Key-Value键值型的数据处理能力&#xff0c;支持应用持久化轻量级数据&#xff0c;并对其修改和查询。数据存储形式为键值对&#xff0c;键的类型为字符串型&#xff0c;值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型。 说明&#x…

同步电机不同电流参考方向下的功率计算

同步电机的功率计算有时候会看见两种表达方式&#xff1a; 当以发电机惯例&#xff0c;即电流方向输出时&#xff0c;功率计算式为&#xff1a; { P s 3 2 ( u s d i s d u s q i s q ) Q s 3 2 ( u s q i s d − u s d i s q ) \left\{\begin{array}{l} P_{\mathrm{s}}\fr…

PostgreSQL(十三)pgcrypto 扩展实现 AES、PGP 加密,并自定义存储过程

目录 一、pgcrypto 简介1.1 安装 pgcrypto 扩展1.2 pgcrypto 包含的函数 二、用法①&#xff1a;对称加密&#xff08;使用 AES、Blowfish 算法&#xff09;2.1 密钥2.2 密钥偏移量 三、用法②&#xff1a;PGP加解密3.1 什么是PGP算法&#xff1f;3.2 使用 GPG 生成密钥对3.3 列…

【AI大模型】深入解析 存储和展示地理数据(.kmz)文件格式:结构、应用与项目实战

文章目录 1. 引言2. 什么是 .kmz 文件&#xff1f;2.1 .kmz 文件的定义与用途2.2 .kmz 与 .kml 的关系2.3 常见的 .kmz 文件使用场景 3. .kmz 文件的内部结构3.1 .kmz 文件的压缩格式3.2 解压缩 .kmz 文件的方法3.3 .kmz 文件的典型内容3.4 .kml 文件的结构与主要元素介绍 4. 深…