【C++】string类(2)

🥳个人主页: 起名字真南
🥳个人专栏:【数据结构初阶】 【C语言】 【C++】

请添加图片描述

目录

  • 引言
  • 1 模拟实现string类基本框架
  • 2 实现string类中的主要成员函数
    • 2.1 Push_Back 函数
    • 2.2 reserve 函数
    • 2.3 append 函数
    • 2.4 c_str 函数
    • 2.5 begin ,end 函数
    • 2.5 operator= 函数
    • 2.6 operator+= 函数
    • 2.7 insert 函数
    • 2.8 erase 函数
    • 2.9 find 函数
    • 2.10 substr 函数
  • 3 实现string类中的非成员函数
    • 3.1 双目运算符的重载
    • 3.2 输出流
    • 3.3 输入流

引言

C++中的std::string类是标准库提供的高级字符串处理工具,支持动态内存管理和丰富的操作函数。为了加深对字符串类的理解,我们将从零开始模拟实现一个简化版的String类,涵盖字符串的创建、拷贝、连接、查找等基本功能。本篇文章的主要目的是展示如何设计和实现一个类似于std::string的类,并探讨其中涉及的内存管理和操作细节。

1 模拟实现string类基本框架

为了和库函数中的 string 做出区分,所以模拟实现的类名是 String
用一个字符串构造一个函数时,我们在初始化的时候给一个默认参数,默认值为 ""空字符串),不能是单引号。这样可以让我们即使不传入任何参数的情况下,也可以构造一个空的 String 对象

#include<iostream>
#include<assert.h>
using namespace std;
namespace wzr
{class String{public:String(const char* str = ""){size_t len = strlen(str);//实际上要存储的数据是len个因为是字符数组所以最后一位留给'\0'//char* tmp = new char[len + 1] //_arr = tmp;//上面的写法是错误写法因为他只是开辟了空间并没有将内容复制过去_arr = new char[len + 1];//使用strcpy可以将str的内容(包括'\0'都统一复制到_arr里面)strcpy(_arr,str);//都没有包含\0_size = _capacity = len;}//拷贝构造函数 要求新构造的函数与原函数一样String(const String& str){//首先获取str中的元素个数_size = str._size;//开辟空间//_arr = new char[_size + 1];  实际空间大小不是原函数的空间大小_arr = new char[str._capacity + 1];//复制字符串的内容到新构造的字符串中strcpy(_arr,str._arr)//同步capacity//不同于使用字符串构造函数 _capacity 需要与原函数保持一致//_capacity = _size;_capacity = str._capacity;}//我们现在已经初步实现了构造函数接下来实现析构函数~String(){//delete [] _arr;//delete和[]之间没有空格delete[] _arr;_arr = nullptr;_capacity = _size = 0;}private:char* _arr;size_t _size;size_t _capacity;const static size_t npos;}
}

2 实现string类中的主要成员函数

2.1 Push_Back 函数

内存不够需要开辟空间,这时候就需要用到 reserve 函数来开辟空间。在进行分配内存大小的时候,我们一般按照原来大小的二倍扩容 reserve(2 * _capacity),但是这个时候出现了一个问题:如果我们的字符串是一个空字符串,空间大小是 0,那么二倍以后依旧是 0。所以我们在这里需要用到三目操作符,并且给一个初始值是 4

	void String::push_back(const char ch){//在进行尾插之前我们首先要检查内存大小是否足够if(_capacity == _size){reserve(_capacity == 0 ? 4 : 2 * _capacity)}// 内存空间足够可以尾插_arr[_size] = ch;_size++;//尾插过后因为是字符数组所以需要将最后一位置为0_arr[_size] = '\0';}

2.2 reserve 函数

不管是尾插还是插入字符串,只要是涉及到增加数据都需要扩容,所以我们实现 reserve 函数。在进行扩容之前,首先要判断 n 和原内存空间的大小

  • 如果 n 大于原内存空间,那么我们可以直接扩容
  • 如果 n 小于原内存空间,就需要判断是否小于数据空间。如果小于数据空间直接减少内存会导致数据泄露
  • 如果 n 在这之间,则进行缩小到 n
	//不管是尾插还是插入字符串只要是涉及到增加数据都都需要扩容//所以我们实现reserve函数void String::reserve(size_t n){if(n > _capacity){//开辟空间大小为n//新建一个临时空间用于拷贝char* tmp = new char[n + 1];//将原函数arr的数据拷贝到tmp变量里strcpy(tmp,_arr);//释放原空间的大小delete[] _arr;_arr = tmp;_capacity = n;//_size不发生变化//进行缩小}else if(n < _capacity && n >= _size){char* tmp = new char[n + 1];//我们在进行数据拷贝的时候一定要注意当我们在进行拷贝的时候//_arr的空间大小是大于tmp的所以我们只需要拷贝我们需要的字符数strncpy(tmp, _arr, _size);//strcpy(tmp, _arr);tmp[_size] = '\0';delete[] _arr;_arr = tmp;_capacity = n;//缩小_size不会发生变化}}

2.3 append 函数

void String::append(const char* str)
{//断言追加的字符串不能回空assert(str);//首先进行判断内存是否足够//使用len来记录添加字符串的字符数size_t len = strlen(str);if (_size + len > _capacity){//如果原来的数据加上新的字符串的个数大于原空间内存的大小//我们需要进行扩容reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}//扩容完成现在要考试移动数据strcpy(_arr + _size, str);_size += len;//由于strcpy会把'\0'自动拷贝过来所以我们不需要手动添加}

2.4 c_str 函数

因为我们设置的 _arr 是私有变量,所以为了方便访问以及保护私有变量的安全,我们模拟实现了 c_str 函数。实现这个函数的同时还有一个目的,就是可以在 test.cpp 函数内输出 String 类型对象数组的内容
并且像这种短小但是调用频繁的函数,我们直接写在头文件中,因为在类中定义的函数默认为内联函数,可以减少函数的调用,从而提高程序的运行效率内联函数在编译器的编译阶段就会被替换,所以函数体积不宜过大

		char* c_str(){return _arr;}
//我们不仅需要返回普通数据还要返回常量const char* c_str() const{return _arr;}

2.5 begin ,end 函数

//其实迭代器的底层逻辑就是通过typedef来定义的所以我们这里的数据类型都是char类型
//所以在定义的时候只需要定义char* 和 const char*typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _arr;}iterator end(){return _arr + _size;}const_iterator begin() const{return _arr;}const_iterator end() const{return _arr + _size;}

2.5 operator= 函数

重载赋值操作符的时候,把它放在 String 类的里面作为成员函数有以下好处:

  • 可以直接访问私有成员变量
  • 固定左操作数(即当前对象),因为赋值操作符的左操作数总是当前对象。
  • 同时返回 *this 可以支持链式赋值(例如 a = b = c)。

并且 C++ 也规定了赋值操作符必须是成员函数

		String& operator=(const String& str){_arr = new char[_capacity + 1];strcpy(_arr, str._arr);_size = str._size;_capacity = str._capacity;return *this}

2.6 operator+= 函数

在进行模拟实现的时候,因为 ++=Push_Backappend 函数的功能相同
所以我们可以直接调用已经实现好的两个函数

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

2.7 insert 函数

//insert 函数在指定位置增加添加数据void String::insert(size_t pos, char ch){
//判断pos指针是否有效assert(pos >= 0 && pos <= _size);//判断内存空间大小if (_size + 1 >= _capacity){//注意原内存可能空间为0的时候reserve(_capacity ==0 ? 1 : 2 * _capacity);}//将pos以及pos以后的位置向后移位//定义一个end变量用来记录最后一个位置size_t end = _size;while (end >= pos){_arr[end + 1] = _arr[end];end--;}
//因为我们在移动数据的时候已经将\0向后移位所以不需要在进行赋值_arr[pos] = ch;_size += 1;
//当参数为一个字符串的时候void String::insert(size_t pos, const char* ch){assert(pos >= 0 && pos <= _size);//判断内存空间大小size_t len = strlen(ch);if (_size + len >= _capacity){reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}//将pos以及pos以后len个长度的位置向后移位size_t end = _size;while (end >= pos){_arr[end + len] = _arr[end];end--;}//插入数据for (size_t i = 0; i < len; i++){_arr[pos + i] = ch[i];}_size += len;}}

2.8 erase 函数

	void String::erase(size_t pos){assert(this->_arr);assert(pos >= 0 && pos <= _size);//移除某一位置的元素只需要将该位置后面的元素直接向前移位//因为是删除数据所以不涉及到开辟空间内存的问题for (size_t i = 0; i < _size - pos; i++){//遍历从pos位置一直到最后一个字符//将pos位置后面的字符向前移动一位_arr[pos + i] = _arr[pos + 1 + i];}_size -= 1;}

2.9 find 函数

使用 npos 的前提是String 类中声明 npos 变量为静态变量,这样所有该类的对象都可以共享这个变量,并且可以通过类名直接访问。同时,静态成员变量的定义要在类的外面进行

// 在 String 类内部声明静态变量
class String {
public:static const size_t npos; // 声明静态常量成员变量// 其他成员函数和变量...
};// 在类的外部定义静态变量
const size_t String::npos = -1; // 赋值为 -1,表示未找到

这样,npos 可以被所有 String 对象共享,并通过 String::npos 访问。

	const size_t String::npos = -1;size_t String::find(char c){assert(this->_arr);for (size_t i = 0; i < _size; i++){if (c == _arr[i]){return i;//这里是找到了返回下标}}//C++中npos代表无,所以这里没有找到返回的时无return npos;}size_t String::find(const char* ch, size_t pos){//从pos位置寻找一个字符串并返回首字符的坐标size_t len = strlen(ch);//调用strstr来寻找const char* ptr = strstr(_arr + pos, ch);if (ptr == nullptr){return npos;}else{return ptr - _arr;}}

2.10 substr 函数

	String String::substr(size_t pos, size_t len){//从当前字符的n位置出提取len个字符形成一个新的String类对象	//判断如果len过大可能会多开空间if (len > _size - pos){len = _size - pos;}String sub;sub.reserve(len);for (size_t i = 0; i < len; i++){sub += _arr[pos + i];}return sub;}

3 实现string类中的非成员函数

3.1 双目运算符的重载

	bool operator==(const String& s1, const String& s2){return strcmp(s1.c_str(), s2.c_str()) == 0;}bool operator>(const String& s1, const String& s2){return strcmp(s1.c_str(), s2.c_str()) > 0;}bool operator>=(const String& s1, const String& s2){return (s1 > s2 || s1 == s2);}bool operator<(const String& s1, const String& s2){return !(s1 >= s2);}bool operator<=(const String& s1, const String& s2){return !(s1 > s2);}bool operator!=(const String& s1, const String& s2){return !(s1 == s2);}

3.2 输出流

	ostream& operator<<(ostream& out, const String& s){out << s.c_str() << endl;return out;}

3.3 输入流

istream& operator >> (istream& in, String& s1)
{//首先清空s1s1.clear();//提前开辟一块空间用来存储输入的数据const size_t N = 256;char buff[N];//获取字符char ch;ch = in.get();//作为buff的指针用来存储数据int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == N - 1){//+=会找0的位置buff[i] = '\0';s1 += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s1 += buff;}return in;}

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

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

相关文章

VScode写Java项目的教程

VScode写Java项目的教程 1.首先必选先安装Java解释器2.安装插件Java Extension Pack3.创建项目创建项目结构选择项目类型 4.测试结果源码内容 今天用一台老式笔记本写代码&#xff0c;IDEA跑不动就准备用VScode突然间就蒙了&#xff0c;怎么创建项目啊&#xff1f;于是就有了这…

自动驾驶系列—加速自动驾驶系统开发:多型号SoC快速适配的最佳实践

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

Python编程探索:从基础语法到循环结构实践(下)

文章目录 前言&#x1f377;四、 字符串拼接&#xff1a;连接多个字符串&#x1f378;4.1 使用 操作符进行字符串拼接&#x1f378;4.2 使用 join() 方法进行字符串拼接&#x1f378;4.3 使用 format() 方法进行格式化拼接&#x1f378;4.4 使用 f-string&#xff08;格式化字…

OpenWRT 和 Padavan 路由器配置网络打印机 实现远程打印

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 之前有给大家介绍过 Armbian 安装 CUPS 作为打印服务器&#xff0c;像是 N1 盒子、玩客云&#xff0c;甚至是随身 WiFi 都可以通过 CUPS 来进行打印。但是有些朋友不想专门为打印机添置一个设备&#xff0…

每天5分钟玩转C#/.NET之C#语言详细介绍

C#语言介绍 C# 语言是适用于 .NET 平台&#xff08;免费的跨平台开源开发环境&#xff09;的最流行语言。 C# 程序可以在许多不同的设备上运行&#xff0c;从物联网 (IoT) 设备到云以及介于两者之间的任何设备。 可为手机、台式机、笔记本电脑和服务器编写应用。C# 是一种跨平…

iba Data Export 导出面板选项

时间线选择真实时间“Absolute date / time” 时间间隔选择0.5Sec.&#xff08;最小为0.01Sec.&#xff09; 右侧数据根据需要选择

数学建模算法与应用 第15章 预测方法

目录 15.1 微分方程模型 Matlab代码示例&#xff1a;求解简单的微分方程 15.2 灰色预测模型&#xff08;GM&#xff09; Matlab代码示例&#xff1a;灰色预测模型 15.3 自回归模型&#xff08;AR&#xff09; Matlab代码示例&#xff1a;AR模型的预测 15.4 指数平滑法 M…

1997-2022年各省农作物总播种面积数据(无缺失)

1997-2022年各省农作物总播种面积数据 1、时间&#xff1a;1997-2022年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;农作物总播种面积(千公顷) 4、范围&#xff1a;31省 5、缺失情况&#xff1a;无缺失 6、指标解释&#xff1a;农作物播种面积指农业生…

SCI英文文献阅读工具【全文翻译】【逐句翻译】

关注B站可以观看更多实战教学视频&#xff1a;hallo128的个人空间 SCI英文文献阅读工具【全文翻译】【逐句翻译】 1. 全文翻译【DeepL】 适用于泛读网址&#xff1a;https://www.deepl.com/zh/translator/files 1.1 前提 文档大小&#xff1a;pdf文档不超过5M&#xff08;可先…

Java实现邮件发送功能

目录 一、准备工作 二、简易文本邮件发送的实现 2.1 步骤 2.2 代码 三、复杂文件内容的发送 3.1 文件构成解析 3.2 包含图片的发送 3.3 包含附件的发送 四、实战 4.1 jsp动态页面 4.2 实体类POJO 4.3 Servlet 4.4 注册Servlet 4.5 发送邮箱核心类 一、准备工作 1、…

快速创建一个vue项目并运行

前期准备工作: 1.安装node 2.安装npm 3.设置淘宝镜像 4.全局安装webpack 5.webpack 4.X 开始&#xff0c;需要安装 webpack-cli 依赖 6.全局安装vue-cli 正文开始: 1.创建项目 ,回车 vue init webpack vue-svg > Project name vue-demo 项目名称 回车 > Pro…

harmonyOS next之实现时间打卡定时器

需求&#xff1a;实现一个时间打卡签到按钮。 实现方法&#xff1a;每隔一秒钟获取一下当前时间。 实现代码如下&#xff1a; Column(){Text(this.curTime).fontColor(#FFFFFF).fontWeight(600).fontSize(32vp)Text(上班打卡).fontColor(#FFFFFF) } .width(170vp) .height(170…

⭐️苹果电脑安装windows10双系统【详细图文步骤保姆级教程】【本教材适用于MAC台式机、笔记本MacBook air和pro】

苹果电脑安装windows10双系统【详细图文步骤保姆级教程】【本教材适用于MAC台式机、笔记本MacBook air和pro】 苹果电脑安装windows10双系统一、准备工作准备项1&#xff1a;U盘作为系统安装盘准备项2&#xff1a;您需要安装的系统镜像 二、启动转换助理步骤1&#xff1a;找到启…

【CSS in Depth 2 精译_050】7.3 CSS 响应式设计中的流式布局原则(Fluid layout)

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 【第七章 响应式设计】&#xff08;概述&#xff09; 7.1 移动端优先设计原则&#xff08;上篇&#xff09; 7.1.1 创建移动端菜单&#xff08;下篇&#xff09;7.1.2 给视口添加 meta 标签&#xf…

Dockerfile样例

一、基础jar镜像制作 ## Dockerfile FROM registry.openanolis.cn/openanolis/anolisos:8.9 RUN mkdir /work ADD jdk17.tar.gz fonts.tar.gz /work/ RUN yum install fontconfig ttmkfdir -y && yum clean all && \chmod -R 755 /work/fonts ADD fonts.conf …

《Sui区块链:重塑去中心化应用的新星与未来潜力》

目录 引言 一、Sui 1、 技术架构 2、 编程语言 3、Move起源 4、Move的几个关键点&#xff1a; 5、Move 智能合约编程语言 6、智能合约编程语言可以做什么 7、和其他编程语言有什么不同 8、 安全性 9、开发者体验 10、生态系统 11、 未来发展 总结 引言 在区块链技…

vue后台管理系统从0到1(5)

文章目录 vue后台管理系统从0到1&#xff08;5&#xff09;完善侧边栏修改bug渲染header导航栏 vue后台管理系统从0到1&#xff08;5&#xff09; 接上一期&#xff0c;我们需要完善我们的侧边狼 完善侧边栏 我们在 element 组件中可以看见&#xff0c;这一个侧边栏是符合我们…

Linux驱动开发——platform平台总线

bus_type 一、主要作用 设备管理 bus_type负责管理连接在特定总线上的设备。它维护一个设备链表&#xff0c;其中包含了所有注册到该总线上的设备。通过这个链表&#xff0c;内核可以方便地遍历和管理连接在该总线上的设备。例如&#xff0c;对于 PCI 总线&#xff0c;bus_typ…

无人机之视觉技术篇

一、视觉传感器的类型 摄像头&#xff1a; 最常见的视觉传感器&#xff0c;能够捕捉可见光图像和视频。 通过单目、双目或多目摄像头的组合&#xff0c;无人机能够实现立体视觉&#xff0c;从而估算距离、深度&#xff0c;并进行物体识别和追踪。 红外传感器&#xff1a; …

【汇编语言】寄存器(内存访问)(七)—— CPU提供的栈机制

文章目录 前言1. CPU提供的栈机制2. push指令3. 问题4. 问题的分析与解答5. pop指令结语 前言 &#x1f4cc; 汇编语言是很多相关课程&#xff08;如数据结构、操作系统、微机原理&#xff09;的重要基础。但仅仅从课程的角度出发就太片面了&#xff0c;其实学习汇编语言可以深…