C++初阶:string类的模拟实现

✨✨小新课堂开课了,欢迎欢迎~✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++:由浅入深篇

小新的主页:编程版小新-CSDN博客

 前言:

前面已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。这里我们也会介绍一些常见接口的模拟实现。

string类各函数接口总览: 

#pragma once
#include<iostream>
#include<assert.h>
#include<string>
using namespace std;namespace fu
{class string{//短小又频繁调用的函数可以直接定义在类内,默认是inlinetypedef char* iterator;typedef const char* const_iterator;//默认成员函数string(const char* str = "");//构造函数string(string& s);//拷贝构造string& operator=(const string& s);//赋值运算符重载~string();//析构函数//迭代器相关的函数iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//容量和大小相关的函数size_t size();size_t capacity();void reserve(size_t n);void resize(size_t n, char ch = '\0');bool empty();//修改字符串相关的函数void push_back(char ch);void append(const char* str);  string& operator+=(char ch);string& operator+=(const char* str);string& insert(size_t pos, char ch);string& insert(size_t pos, const char* str);string substr(size_t pos = 0, size_t len = npos);string& erase(size_t pos, size_t len);void clear();void swap(string& s);const char* c_str()const;//为啥没有参数?//访问字符串相关函数char& operator[](size_t i);const char& operator[](size_t i)const;size_t find(char ch, size_t pos = 0) const;size_t find(const char* str, size_t pos = 0) const;//关系运算符重载bool operator>(const string& s)const;bool operator>=(const string& s)const;bool operator<(const string& s)const;bool operator<=(const string& s)const;bool operator==(const string& s)const;bool operator!=(const string& s)const;private:char* _str;  //储存字符串size_t size;//记录字符串当前有效长度的大小size_t capacity;//记录字符串当前的容量static const size_t npos;};//<<和>>运算符重载istream& operator >> (istream& in, string& s);ostream& operator << (ostream & out, const string& s);istream& getline(istream& in, string& s);
}

注意:这里我们把string类放在一个命名空间域里,防止与标准库库里的string类产生命名冲突。

默认成员函数

构造函数

给缺省值的时候,不能给成nulllptr,因为strlen(str)这里会让程序崩溃崩溃。可以给成\0,但是没有必要,这样字符串就会有两个\0了,常量字符串默认会带有\0,给成空字符串是最合适的。

string(const char* str="")//构造函数
{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];//多开一个位置给'\0'strcpy(_str, str);
}

除了上面这种写法,我们也可以分开写,这里也有需要注意的点。

	//默认成员函数string():_str(nullptr), _size(0), _capacity(0){}string(const char* str){_size = strlen(str);//初始化时,长度为有效字符的长度_capacity = _size;//初始化时,容量为有效字符的长度_str = new char[_capacity + 1];//多开一个位置给'\0'strcpy(_str, str);}

 上面的程序是存在错误的,我们在初始化_str时不能给成nullptr,因为_str是char*,我们要输出_str时存在对空指针解引用,会使得程序崩溃。

#include"string.h"namespace fu
{void test_string1(){string s1;string s2("hello world");cout << s1.c_str() << endl;cout << s2.c_str() << endl;}
}

s1会调用无参的构造函数,被初始化为nullptr (因为还没有模拟实现流插入和流提取,先用C字符串输出也没有太大区别),在输出的时候,就会对空指针解引用。

解决法案:

string():_str(new char[1]{'\0'}), _size(0), _capacity(0)
{}string(const char* str)
{_size = strlen(str);//初始化时,长度为有效字符的长度_capacity = _size;//初始化时,容量为有效字符的长度_str = new char[_capacity + 1];//多开一个位置给'\0'strcpy(_str, str);
}

拷贝构造函数

在模拟实现拷贝构造之前,我们要先了解深拷贝和浅拷贝的区别。

浅拷贝:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间。其中一个对象的改动会对另一个对象造成影响。


深拷贝:深拷贝是指源对象与拷贝对象互相独立。其中任何一个对象的改动不会对另外一个对象造成影响。

 如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

下面我们给出两种深拷贝的写法。

传统写法:

开辟一个能足够容纳源字符串大小的空间,将源对象指向的字符串拷贝过去,并将其容量和大小信息进行更新。

string(string& s)//拷贝构造
{_str = new char[s._capacity+ 1];strcpy(_str, s._str);_capacity = s._capacity;_size = s._size;
}

现代写法:

先根据源字符串的C字符串格式调用构造函数构造一个tmp对象,然后再将tmp对象与拷贝对象的数据交换即可。

//现代写法
string(string& s)//拷贝构造
{string tmp(s._str);//中间变量确保原始对象的状态不会改变,也保证了自赋值的正确性swap(tmp);
} 

赋值运算符重载函数

与拷贝构造类似,在模拟实现的过程中涉及资源的管理,我们也要采用深拷贝。

下面提供两种深拷贝的写法。

传统写法:

不是自己给自己赋值的情况下,除了要先释放原来的旧空间,其他的与拷贝构造的模拟实现并无不同。

string& operator=(const string& s)//赋值运算符重载
{if (this != &s){delete[]_str;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;//支持连续赋值
}

 现代写法:

这里的写法与拷贝构造的现实写法十分类似,不同的是,拷贝构造是先通过调用构造函数实例化出一个对象,然后用该对象与拷贝的对象进行交换;而赋值运算符重载则是采用“值传递”接收右值的方法,让编译器自动调用拷贝构造函数,然后我们再将拷贝出来的对象与左值进行交换即可。

	//现代写法string& operator= ( string& s){swap(s);return *this;//支持连续赋值}

析构函数

由于string对象的成员变量_str指向一块空间,我们需要手动写析构函数,避免内存泄漏。

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

迭代器相关的函数

在实现string类是,我们用到的迭代器就是字符指针,只是给字符指针起了一个新的名字。要注意的是并不是所有的迭代器本质上都是指针类型。

typedef char* iterator;
typedef const char* const_iterator;

begin

begin函数的作用就是返回字符串中第一个字符的地址:

iterator begin()
{return _str;
}​
const_iterator begin() const
{return _str;
}

end

end函数的作用就是返回字符串最后一个字符的地址,即‘\0’的地址。

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

容量和大小相关的函数

size和capacity

由于_size和_capacity都是成员变量,受访问限定符的限制,在类外不能访问,size和capacity函数就是为了在类外获取成员变量而设置的。

size_t size()
{return _size;
}
size_t capacity()
{return _capacity;
}

 reserve和resize

reserve和resize的区别一定要区分。

reserve:

当n>capacity的时候,要扩容

当n<capacity的时候,不会缩容,capacity不会缩为n

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:

当n>capacity的时候,会扩容多出来的部分用字符ch填充,如果ch 为给,默认是\0。

当n<capacity的时候,也不会缩容,但是字符串的有效长度会减到n。

void string::resize(size_t n, char ch )
{if (n < _capacity){_size = n;_str[_size] = '\0';}else{if (n > _capacity){reserve(n);//扩容}for (size_t i = _size; i < n; i++){_str[i] = ch;//填充数据}_size = n;//更新_size_str[n] = '\0';}
}

empty

字符串的有效长度为0时就为空了,不需要容量capacity也为0。

bool empty()
{return _size  == 0;
}

修改字符串的相关函数

push_back

这个实现逻辑就是尾插一个字符,尾插前要先判断空间是否足够,不够进行扩容,十分重要的一点的是不要忘记更新size,并且要手动添加\0。

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

append

这里的实现逻辑就是尾插一个字符串,插入的第一步还是要先判断空间大小,不够就扩容,之后将要插入对的字符串插入到源对象的字符串末尾即可,这里因为要插入的字符串带有\0,就不需要我们手动添加\0了。

void string::append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){//小于2倍按2倍扩,大于2倍,按需扩reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);}strcpy(_str + len, str);_size += len;
}

 operator+=

这里直接复用上面已经实现好的功能即可。

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

 insert

在指定位置插入一个字符或者字符串。第一步判断pos位置的合法性和空间是否够用,第二步挪动数据,第三步,插入数据,第四步更新size。

string& string::insert(size_t pos, char ch)
{assert(pos <= _size);//等于的时候有意义,尾插if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;//从\0开始挪while (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;
}

 在指定位置插入一个字符串。首先也是判断pos的合法性,再判断是否需要扩容。插入字符串时,先将pos位置及其后面的字符统一向后挪动len位(len为待插入字符串的长度),给待插入的字符串留出位置,然后将其插入字符串即可。

string& string::insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){//小于2倍按2倍扩,大于2倍,按需扩reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);}size_t end = _size + len;while (end > pos+len-1){_str[end] = _str[end - 1];end--;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;}

erase 

erase函数的作用是删除字符串任意位置开始的n个字符。删除字符前也需要判断pos的合法性,删除字符串有两种情况。

第一种是pos位置及其之后的有效字符都需要被删除。这时我们只需在pos位置放上’\0’,然后将对象的size更新即可。

第二种是pos位置及其之后的有效字符只需删除一部分。这时我们可以用后方需要保留的有效字符覆盖前方需要删除的有效字符,此时不用在字符串后方加’\0’,因为在此之前字符串末尾有’\0’了。

string& string::erase(size_t pos, size_t len)
{assert(pos < _size);if (len > _size - pos){_str[pos] = '\0';_size = pos;}else{for (size_t i = pos + len; i < _size; i++){_str[i - len] = _str[i];}_size -= len;}return *this;
}

clear 

将源对象的字符串的第一个字符改为\0,就能达到清空字符串的效果了,不要忘了更新size哦。

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

swap 

调用库里面的交换函数即可。

void swap(string& s)
{std::swap(_str, s._str);std::swap(_capacity, s._capacity);std::swap(_size, s._size);
}

c_str 

返回一个C格式的字符串。

const char* c_str()const
{return _str;
}

访问字符串相关函数

operator[]

通过下标获取字符串对应位置的字符,也可以对其进行修改操作,当不需要修改时,我们就走下面那个,这里也要注意下标的合法性。

char& string:: operator[](size_t i)//可读可写
{assert(i < _size);return _str[i];
}const  char& string:: operator[](size_t i)const//只读
{assert(i < _size);return _str[i];
}

find

用于在字符串中查找一个字符或者字符串。find是从指定位置开始往后找,找到了返回对应位置的下标,找不到就返回npos。

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

 关系运算符重载

bool string::operator<(const string& s)const
{return strcmp(_str, s._str) < 0;
}bool string::operator<=(const string& s)const
{return _str < s._str || _str == s._str;
}bool string::operator>(const string& s)const
{return !(_str <= s._str);
}
bool string::operator>=(const string& s)const
{return  !(_str < s._str);
}
bool string::operator==(const string& s)const
{return strcmp(_str, s._str) == 0;
}
bool string::operator!=(const string& s)const
{return !(_str == s._str);
}

<<和>>运算符重载

>>

重载>>运算符是为了让string对象能够像内置类型一样使用>>运算符直接输入。输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到’ ‘或是’\n’便停止读取。

istream& operator >> (istream& in, string& s)
{s.clear();//清空字符串char ch = in.get();//读取一个字符while (ch != ' ' || ch != '\n')//遇到空格或者换行结束{s += ch;//尾插ch = in.get();//继续读}return in;//支持连续读取
}

<<

重载<<运算符是为了让string对象能够像内置类型一样使用<<运算符直接输出打印。实现时我们可以直接使用范围for对对象进行遍历即可。

ostream& operator << (ostream& out, const string& s)
{for (auto ch : s)//范围for{out << ch;}return out;//支持连续输出
}

 getline 

getline与scanf,getchar等不同的是,getline能够读取空格,遇到换行符才会停止。与>>的实现逻辑相同。

istream& getline (istream& is, string& str, char delim);在string类的里的这个delim也可以指定字符,当遇到该指定的字符时停止。
istream& getline(istream& in, string& s)
{s.clear(); //清空字符串char ch = in.get(); //读取一个字符while (ch != '\n') //遇到换行结束{s += ch; //尾插ch = in.get(); //继续读取字符}return in;}

总结:

除了string类模拟实现比较重要的默认成员函数,我们也实现了很多常见的接口,感兴趣的话也可以自己对着string类的将剩下的接口模拟实现一下

感谢各位的观看,创作不易,还请一键三连哦 ~

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

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

相关文章

7系列FPGA HR/HP I/O区别

HR High Range I/O with support for I/O voltage from 1.2V to 3.3V. HP High Performance I/O with support for I/O voltage from 1.2V to 1.8V. UG865&#xff1a;Zynq-7000 All Programmable SoC Packaging and Pinout

【mechine learning-六-supervise learning之线性回归模型】

监督学习之线性回归模型 线性回归模型线性模型回归模型 如何使用线性模型实现智能化预测呢寻找数据训练模型输入、特征、目标、预测值、模型代价函数 线性模型是人工智能监督学习中最广泛的应用&#xff0c;所以有必要先学习一下这个基础模型&#xff0c;做好基石。 线性回归模…

使用 ShuffleNet 模型在 CIFAR-100 数据集上的图像分类

简介 在深度学习领域&#xff0c;图像分类任务是衡量算法性能的重要基准。本文将介绍我们如何使用一种高效的卷积神经网络架构——ShuffleNet&#xff0c;来处理 CIFAR-100 数据集上的图像分类问题。 CIFAR-100 数据集简介 CIFAR-100 数据集是一个广泛使用的图像分类数据集&…

Python爱心射线(完整代码)

目录 系列目录 写在前面​ 完整代码 下载代码 代码分析 写在后面 系列目录 序号直达链接表白系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3

人工智能领域各方向顶级会议和期刊

会议 人工智能基础与综合&#xff1a;AAAI、CICAI (!)、UAI、IJCAI 机器学习&#xff1a;COLT、ICLR、ICML、NeurIPS 模式识别与计算机视觉&#xff1a;ECCV、CVPR、ICCV 语言与语音处理&#xff1a;ACL、EMNLP 知识工程与数据挖掘&#xff1a;SIGKDD、SIGMOD、ICDE、SIGIR、V…

web知识

sql注入的万能密码:1’ or true#如果页面没有什么东西可见&#xff0c;首先可以用diresearch看看有没有什么隐藏的目录&#xff0c;或者检查源代码&#xff0c;如果这些都没成功可以用 dirsearch如果没有找到东西&#xff0c;可能需要调低线程 dirsearch.py -u url -e * --ti…

语音转文字工具全解析

无论是学生群体记录课堂笔记&#xff0c;职场人士整理会议纪要&#xff0c;还是自媒体创作者捕捉灵感火花&#xff0c;录音转文字软件都以其独特的便利性和高效性赢得了广泛的好评。今天&#xff0c;就让我们一起探索那些深受大家喜爱的录音转文字工具吧。 1.365在线转文字 链…

【Python】由二维列表初始化导致修改元素时会修改相同位置元素的引用问题f = [[0] * len(matrix[0])] * len(matrix)

背景&#xff1a; 在刷Leetcode过程中&#xff0c;需要初始化一个与另一个矩阵&#xff08;如 matrix&#xff09;尺寸相同的二维列表&#xff08;如 f&#xff09;&#xff0c;并填充初始值&#xff08;如 0&#xff09;。一开始用的是这种方法试图创建一个所有元素均为 0 的…

django自用教程

编程软件: pycharm django介绍:django是Pythonweb的一个框架&#xff0c;是用来构建网站的工具。 要想使用django&#xff0c;首先需要下载django模块&#xff0c;通过使用以下代码实现: pip install django 安装完成后&#xff0c;在django的目录下有一个文件django-admin&am…

docker基础知识-docker0网桥

文章目录 示意图Docker 网桥的工作原理Docker 网桥的优势Docker 网桥的局限性自定义网桥网络 Docker 网桥&#xff08;Docker bridge network&#xff09;是 Docker 默认的一种网络模式&#xff0c;它允许 Docker 容器之间通过一个虚拟的交换机进行通信。Docker 网桥网络为容器…

Linux shell编程学习笔记79:cpio命令——文件和目录归档工具(下)

在 Linux shell编程学习笔记78&#xff1a;cpio命令——文件和目录归档工具&#xff08;上&#xff09;-CSDN博客https://blog.csdn.net/Purpleendurer/article/details/142095476?spm1001.2014.3001.5501中&#xff0c;我们研究了 cpio命令 的功能、格式、选项说明 以及 cpi…

计算机视觉的应用32-基于Swin Transformer模型的嵌入混合注意力机制的人脸表情识别的应用

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用32-基于Swin Transformer模型的嵌入混合注意力机制的人脸表情识别的应用。随着深度学习技术的不断演进&#xff0c;计算机视觉领域迎来了诸多变革&#xff0c;其中 Transformer 架构的引入&#xf…

verilog vscode 与AI 插件

Verilog 轻量化开发环境 背景 笔者常用的开发环境 VIAVDO, 体积巨大&#xff0c;自带编辑器除了linting 能用&#xff0c;编辑器几乎不能用&#xff0c;仿真界面很友好&#xff0c;但是速度比较慢。Sublime Text, 非常好用的编辑器&#xff0c;各种插件使用verilog 非常方便…

sqlite在Windows环境下安装、使用、node.js连接

sqlite在Windows环境下安装、使用、node.js连接 前言&#xff1a;2024年9月10日 1. 下载安装 sqlite 的安装非常简单 去官网下载对应压缩包 将两个压缩包解压&#xff0c;并将解压出来的文件放在同一目录下 将上面的目录路径配置到环境变量 path 中 2. 执行 sql sqlite …

opencv羊群计数,动态目标检测跟踪

OpenCV&#xff08;开源计算机视觉库&#xff09;是一个功能强大的计算机视觉和图像处理库&#xff0c;广泛应用于各种视觉任务中&#xff0c;包括但不限于目标检测与跟踪。如果你正在考虑一个基于OpenCV的羊群计数项目&#xff0c;那么下面是对这样一个项目的概述&#xff1a;…

ThinkPHP Email功能如何配置才能发送邮件?

ThinkPHP Email发送流程&#xff1f;使用ThinkPHP发Email方法&#xff1f; ThinkPHP作为一款流行的PHP框架&#xff0c;提供了强大的Email功能&#xff0c;使得开发者能够轻松实现邮件发送。AokSend将详细介绍如何配置ThinkPHP Email功能&#xff0c;以确保邮件能够顺利发送。…

计算机毕业设计 智能推荐旅游平台 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

暴雨液冷服务器硬刚液冷放量元年

AI&#xff08;人工智能&#xff09;不断向前&#xff0c;作为AI三驾马车之一&#xff0c;算力需求始终如影随形。 近日&#xff0c;财经记者走访了河南郑州多家服务器厂商、大模型公司和算力中心。在走访中&#xff0c;记者发现&#xff0c;液冷技术正被算力行业青睐&#xf…

直播相关02-录制麦克风声音,QT 信号与槽,自定义信号和槽

一 信号与槽函数 #include "mainwindow.h" #include <QPushButton> #include <iostream> using namespace std;//我们的目的是在 window中加入一个button&#xff0c;当点击这个button后&#xff0c;关闭 MainWindow 。 MainWindow::MainWindow(QWidget …

0.3 学习Stm32经历过的磨难

文章目录 用库函数传参 能否按位或STM32库函数XXX_GetFlagStatus和XXX_GetITStatus的区别关于MDK导入文件后报错 Browse information of one files is not available用exti中断读取按键 忘记消抖 &#xff08;更离谱的是&#xff0c;我忘记开启afio的时钟了 Damn!&#xff09;D…