C++ STL -->模拟实现string

文章目录

  • string模拟实现
    • 模拟实现string类函数接口
    • 默认成员函数
      • 构造函数
      • 析构函数
      • 拷贝构造函数
      • 赋值运算符重载函数
    • 迭代器相关函数(iterator)
      • begin()
      • end()
    • capacity(容量和大小函数)
      • size函数
      • capacity函数
      • reserve函数
      • resize函数
    • modfiy(修改字符串函数)
      • push_back(尾插)
      • append(尾插一个字符串)
      • +=赋值运算符重载
      • clear
      • find
      • insert
      • erase
    • 访问字符串相关函数(access)
      • []运算符重载
      • front && back函数
    • 关系运算符重载函数(relational operators)
    • >>和<<的重载
      • <<重载
      • >>重载
  • 参考源码

在之前的文章中 C++ STL–>String类详解,介绍了一下STL中string类的基本使用 这篇文章将模拟实现string类的常用函数

string模拟实现

模拟实现的目的就是为了更好的使用STL

模拟实现string类函数接口

namespace ding
{
   class string
   {
   public:
        string(const char* str = "");
        string(const string& s);
        string& operator=(const string& s);
        ~string();
        //=======================================================
        // iterator
        typedef char* iterator;
        iterator begin();
        iterator end();
        //=======================================================
        // modify
        void push_back(char c);
        void append(const char* str);
        string& operator+=(char c);
        string& operator+=(const char* str);
        void clear();
        void swap(string& s);
        const char* c_str()const;
        //=======================================================
        // capacity
        size_t size()const;
        size_t capacity()const;
        bool empty()const;
        void resize(size_t n, char c = '\0');
        void reserve(size_t n);
        //=======================================================
        // access
        char& operator[](size_t index);
        const char& operator[](size_t index)const;
        //=======================================================
        //relational operators
        bool operator<(const string& s);
        bool operator<=(const string& s);
        bool operator>(const string& s);
        bool operator>=(const string& s);
        bool operator==(const string& s);
        bool operator!=(const string& s);
        // 返回c在string中第一次出现的位置
        size_t find(char c, size_t pos = 0) const;
        // 返回子串s在string中第一次出现的位置
        size_t find(const char* s, size_t pos = 0) const;
        // 在pos位置上插入字符c/字符串str,并返回该字符的位置
        string& insert(size_t pos, char c);
        string& insert(size_t pos, const char* str);
        // 删除pos位置上的元素,并返回该元素的下一个位置
        string& erase(size_t pos, size_t len);
    private:
        char* _str;       //存储字符串
        size_t _capacity; //记录字符串当前的容量
        size_t _size;     //记录字符串当前的有效长度
   };
   ostream& operator<<(ostream& _cout, const ding::string& s);
   stream& operator>>(istream& _cin, ding::string& s);
};

为了解决和标准库中string命名发生冲突,这里使用自己的命名空间解决

默认成员函数

构造函数

无参构造函数:构造空字符串 空字符串里面是有一个\0的 并不是什么都没有

string::string()
	:_str(new char [1])
	,_capacity(0)
	,_size(0)
{
	_str[0] = '\0';
}

带参构造函数

string::string(const char* str)
	:_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_capacity + 1];//多开辟一块空间存放\0
	strcpy(_str, str);
}

省值可以将无参的和带参的构造函数写成一个函数

函数声明:string(const char* str = "");

注意 这里缺省值只能给函数声明,定义的时候不能再使用缺省值

实现:

string::string(const char* str )
{
	_size = strlen(str);
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

析构函数

这里的构造函数用new从堆区申请空间,所以析构函数需要自己实现去主动释放资源,编译器默认生成的无法满足需求

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

拷贝构造函数

这里如果使用编译器默认生成的拷贝构造函数去初始化对象 在对象生命周期结束时 c++编译器自动调用析构函数会出错 如下图

image.png
原因:这里是浅拷贝 s1和s2共用同一块地址空间,在对象s1,s2生命周期结束时,调用析构函数。对s1进行析构时,已经将资源释放掉了,而s2还不知道资源已经被释放,再次对其资源进行释放时,就会出现访问违规。
image.png
关于深浅拷贝

浅拷贝:编译器只会将对象中的值拷贝过来。拷贝的对象和源对象共用同一块地址空间,对其中一个对象的修改会影响到另一个对象
深拷贝:拷贝的对象与源对象使用不同的地址空间,二者互不干扰。

image.png

image.png
在这里,我们应该实现的是深拷贝,编译器默认生成的不能完成。需要自己实现一个拷贝构造函数
代码实现

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

赋值运算符重载函数

赋值时,同样应该采用深拷贝的方式进行赋值
首先将原来的空间释放掉,然后申请新空间进行拷贝
注意:如果自己给自己赋值,应该进行判断。否则将自己释放后,在进行赋值,会出错

string& string::operator=(const string& s)
{
    if(this != &s)
    {
        char* tmp = new char[s._capacity + 1];
        strcpy(tmp, s._str);
        delete[] _str;
        _str = tmp;
        _size = s._size;
        _capacity = s._capacity;
    }
    return *this;
}

这里申请临时对象是为了防止申请空间失败,对原来的数据造成破坏

迭代器相关函数(iterator)

迭代器在string类中的实现很简单 就是给字符指针取了个高大上的名字
typedef char* iterator;
typedef const char* const_iterator;

begin()

返回字符串的起始位置的地址

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

end()

返回字符串结束位置的地址 即\0位置的地址

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

利用迭代器遍历字符串
在底层 用迭代器遍历字符串,实际上就是用指针的方式去遍历

string s1("Hello world");
auto it = s1.begin();
while (it != s1.end())
{
        cout << *it << endl;
        it++;
}

capacity(容量和大小函数)

size函数

返回字符串的元素个数不包含\0;

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

capacity函数

返回字符串的有效空间大小

size_t string::capacity()const
{
        return _capacity;
}

reserve函数

扩容
reserve函数规则:

  • 大于当前对象的capacity时,将capacity扩大到n或者大于n
  • 当n小于当前对象的capacity时,什么也不做。(不会缩容)
void string::reserve(size_t n)
{
        _capacity = n + _capacity;
        char* tmp = new char[_capacity];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        //_size 不用处理
        //_size = strlen(_str);
}

resize函数

扩容+初始化
resize函数的规则:

  • 当n大于当前对象的size时 将size扩大到n 若未指定字符 默认为’\0’
  • 当n小于当前对象的size时 将size缩小到n
void string::resize(size_t n, char c = '\0')
	{
		if (n <= _size)
		{
			_size = n;
			_str[_size] = '\0';
		}
		else
		{
			if (n > _capacity)
			{
				reserve(n);
			}
			//加数据
			size_t i = _size;
			while (i < n)
			{
				_str[_size] = c;
				i++;
			}
			_size = n;
			_str[_size] = '\0';
		}
	}

modfiy(修改字符串函数)

push_back(尾插)

在字符串结尾插入一个字符
插入之前首先要考虑一下容量是否足够 不够需要扩容

void string::push_back(char c)
{
        //扩容
        if (_size >= _capacity)
        {
                _capacity = _capacity == 0 ? 2 : _capacity;//处理空串
                reserve(_capacity * 2);
        }
        //push_back
        _str[_size] = c;
        ++_size;
        //处理\0
        _str[_size] = '\0';
}

注意:插入一个字符后需要自己处理一下字符串结束标志\0

append(尾插一个字符串)

在字符串尾部插入一个字符串
在插入之前还是首先要检查一下容量是否足够

void string::append(const char* str)
{
    //扩容
    size_t len = strlen(str);
    if (_size + len >= _capacity)
    {
        _capacity = _capacity == 0 ? len : _capacity;//处理空串
        reserve(_capacity + len);
    }

    strcpy(_str+_size, str);
    _size += len;
}

注意:这里使用strcat也可以完成。但是strcat需要遍历找尾,效率太低,使用strcpy即可。strcpy会处理字符串结束标志\0,不用自己手动处理

+=赋值运算符重载

+=运算符重载实现很简单了 直接函数复用即可

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

这里的c_str()函数就体现出价值了。append函数的形参是const char* 类型 而str是string类型 直接传参会导致参数类型不匹配错误。使用c_str转换一下类型即可

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

注意:这里的c_str()实现一个const成员函数即可,cosnt和非const对象都可以调用。如果只实现非cosnt版本的话,const对象去调用c_str函数则会出错。

clear

清空字符串中的有效字符,不会改变底层空间的大小(不会影响容量的大小)

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

find

默认从0(下标)位置开始查找,返回第一个匹配项的下标位置,找不到返回npos
nposstatic const size_t npos = -1;

size_t string::find(char c, size_t pos) const
{
    for (size_t i = 0; i < _size; i++)
    {
        if (_str[i] == c)
        {
            return i;
        }
    }
    //没找到
    return npos;
}
size_t string::find(const char* s, size_t pos) const
{
    const char* ret = strstr(_str + pos, s);
    if (ret)
    {
        return ret - _str;
    }
    else
    {
        return npos;
    }

}

insert

在指定位置插入n个字符

string& string::insert(size_t pos, size_t n,char c)
{
        //扩容
        if (_size + n >= _capacity)
        {
                reserve(_capacity+n);
        }
        //挪动数据
        int end = _size;//会把\0挪动
        while (end >= (int)pos)
        {
                _str[end + n ] = _str[end];
                --end;
        }
        while (n--)
        {
                _str[pos] = c;
                pos++;
        }
        _size += n;
        return *this;
}

在指定位置插入字符串

string& string::insert(size_t pos, const char* str)
{
        assert(pos <= _size);
        size_t len = strlen(str);
        //扩容
        if (_size + len >= _capacity)
        {
                reserve(_capacity + len);
        }
        //挪动数据
        int end = _size;
        while (end >= (int)pos)
        {
                _str[end + len] = _str[end];
                --end;
        }
        strncpy(_str + pos, str, len);//不会处理\0
        return *this;
}

pos可以通过find函数查找指定
比如

string s2("Hello");
size_t pos2 = s2.find('e');
s2.insert(pos2, 5, 'x');//在e的前面插入5个x
cout << s2.c_str() << endl;

erase

删除指定位置指定长度的字符(不指定长度全部删除)

  • 当长度大于字符串长度时,直接在pos位置赋值\0 即可
    PixPin_2023-12-04_21-21-09.gif
  • 其他情况挪动数据删除即可。

PixPin_2023-12-04_21-25-01.gif

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

访问字符串相关函数(access)

[]运算符重载

[]运算符重载函数目的是为了能让string对象像字符数组一样,使用[]+下标的方式进行访问
通过[]+下标,可以访问对应下标位置的元素。模拟实现时把对应位置的引用返回即可 这样可读可写

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

front && back函数

获取字符串有效元素第一个元素和最后一个元素(\0之前的)

image.png

char& string::front()
{
        return _str[0];
}
const char& string::front() const
{
        return _str[0];
}
char& string::back()
{
        return _str[_size - 1];
}
const char& string::back() const
{
        return _str[_size - 1];
}

关系运算符重载函数(relational operators)

关系运算符有 == 、!=、 <、 <=、 >、 >= 六个
实现其中一个==和> 或者< 函数复用一下,其他的就全部能实现了

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

>>和<<的重载

<<重载

<< 的重载是为了让string对象 像内置类型一样,直接使用<<进行输出
重载时就是遍历一遍string对象,string对象存的是char类型,在使用<<输出即可

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

注意: cout的类型是ostream类类型;返回ostream的引用是为了连续输出

>>重载

<<的重载是为了让string对象可以像内置类型一样,直接使用cin进行输入。
输入时应该注意,先清空对象中的数据在进行输入。

//>>运算符的重载
istream& operator>>(istream& in, string& s)
{
    s.clear(); 
    char ch = in.get(); 
    while (ch != ' '&&ch != '\n') 
    {
            s += ch; 
            ch = in.get(); 
    }
    return in; 
}

注意这里不能使用>>直接输入char类型 要用cin对象的成员函数get()
因为>> 会将空格或者\n当成默认的分隔符 ,ch不会向缓冲区中拿空格或者\n。
导致循环一直无法终止,显然不符合要求。C++ cin对象有一个成员函数get(),会忽略空格和\n。都会从缓冲区中读取。

参考源码

  • gitee源码 码云 - 开源中国
  • 欢迎在评论区提出问题或留下你的观点!

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

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

相关文章

怎么在 Ubuntu 22.04 上使用 SSH

目录 How to Enable SSH on Ubuntu 22.04How to connect the computer by using the SSHConclusion SSH 是一种安全外壳网络协议&#xff0c;用于通过互联网在两台计算机之间进行通信&#xff0c;一台称为客户端计算机&#xff0c;另一台称为主机计算机。 openSSH 的包可以使用…

Vue开发实例(七)Axios的安装与使用

说明&#xff1a; 如果只是在前端&#xff0c;axios常常需要结合mockjs使用&#xff0c;如果是前后端分离&#xff0c;就需要调用对应的接口&#xff0c;获取参数&#xff0c;传递参数&#xff1b;由于此文章只涉及前端&#xff0c;所以我们需要结合mockjs使用&#xff1b;由于…

vxe-table编辑单元格动态插槽slot的使用

业务场景&#xff1a;表格中只有特定某一行的的单元格可以编辑&#xff0c;列很多&#xff0c;为每个列写个插槽要写很多重复代码&#xff0c;所以这里使用动态插槽&#xff0c;简化代码量。显示编辑图标&#xff0c;点击编辑图标隐藏。失去焦点保存调后台接口。 解决办法&…

Docker知识点总结二

四、 Docker 架构 Docker使用客户端-服务器(C/S)架构模式&#xff0c;使用远程API来管理和创建Docker容器。 介绍&#xff1a; 1、Docker的客户端client&#xff0c;我们在命令行发送一些信息(命令)给Docker服务端。2、中间这个就是Docker的服务端&#xff0c;在这个服务端里面…

我在代码随想录|写代码Day31 | 贪心算法总结篇 | 贪心终结一题

&#x1f525;博客介绍&#xff1a; 27dCnc &#x1f3a5;系列专栏&#xff1a; <<数据结构与算法>> << 算法入门>> << C项目>> &#x1f3a5; 当前专栏: << 算法入门>> 专题 : 数据结构帮助小白快速入门算法 &#x1f4…

Spring Cloud Gateway-系统保护Sentinel集成

文章目录 Sentinel介绍Spring Cloud Gateway集成Sentinelpom依赖Sentinel配置Sentinel集成Nacos作为数据源自定义降级响应 Sentinel介绍 ​ 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&a…

FastAPI 的 quickstart

从这一章往后我们就正式开始学习 FastAPI 了 代码 FastAPI 环境安装 python 环境安装 根据要求至少需要 python 3.8及其以上&#xff0c;可以去 python 官网 自行下载安装, 本文中我们用 python 3.11 FastAPI 环境安装 pip install fastapi pip install "uvicorn[sta…

Julia语言中的位运算符、赋值运算符、算术运算符

算术运算符 # 使用基本的赋值运算符 a 10 println("a 的初始值是: $a") # 使用加法赋值运算符 a 5 println("a 加上 5 后的值是: $a") # 使用减法赋值运算符 - a - 3 println("a 减去 3 后的值是: $a") # 使用乘法赋值运算符…

CSRF跨站请求伪造(一)

★★免责声明★★ 文章中涉及的程序(方法)可能带有攻击性&#xff0c;仅供安全研究与学习之用&#xff0c;读者将信息做其他用途&#xff0c;由Ta承担全部法律及连带责任&#xff0c;文章作者不承担任何法律及连带责任。 1、CSRF简介 CSRF&#xff0c;全称&#xff1a;Cross-S…

HarmonyOS Stage模型 用程序运行切换 验证UIAbility 启动模式(下) 验证:specified启动模式 Ability间切换

上文 HarmonyOS Stage模型 用程序运行切换 验证UIAbility 启动模式(上) 验证:singleton、multiton、standard启动模式 我们已经验证完了 singleton multiton standard 三种启动模式 留下了毕竟复杂的 specified 这里 首先 我们要写两个不同的界面 index 编写代码如下 import…

2024最新算法:鳑鲏鱼优化算法(Bitterling Fish Optimization,BFO)求解23个基准函数(提供MATLAB代码)

一、鳑鲏鱼优化算法 鳑鲏鱼优化算法&#xff08;Bitterling Fish Optimization&#xff0c;BFO&#xff09;由Lida Zareian 等人于2024年提出。鳑鲏鱼在交配中&#xff0c;雄性和雌性物种相互接近&#xff0c;然后将精子和卵子释放到水中&#xff0c;但这种方法有一个很大的缺…

Linux系统源代码数据防泄密加密软件

数据防泄密系统 是一套从源头上保障数据安全和使用安全的软件系统。包含了文件透明加解密、内部文件流转功能、密级管控、离线管理、文件外发管理、灵活的审批流程、工作模式切换、服务器白名单等功能。从根本上严防信息外泄&#xff0c;保障信息安全。 www.weaem.com 功能介绍…

手写模拟器,解放双手!效果炸裂的生产工具

手写模拟器是一款基于Handright的仿手写图片生成软件&#xff0c;可以让你的电脑和手机也能写出漂亮的手写字&#xff0c;你只需要输入你想要写的内容&#xff0c;选择你喜欢的字体和背景&#xff0c;就可以生成一张高仿真的手写图片&#xff0c;用于各种场合&#xff0c;比如做…

CloudCompare使用-点云手动分割操作

点云手动分割操作 一、概述二、分割步骤1. 点击分割按钮2. 分割标题栏 一、概述 我们有时候需要对点云进行局部分割&#xff0c;就想对下面这个四棱锥和立方体的组合体给分离出来。 分离的效果如下&#xff1a; 二、分割步骤 1. 点击分割按钮 有两个入口 1. 菜单栏 2. 快…

如何根据玩家数量和游戏需求选择最合适的服务器配置?

根据玩家数量和游戏需求选择最合适的服务器配置&#xff0c;首先需要考虑游戏的类型、玩家数量、预计的在线时间以及对内存和CPU性能的需求综合考虑。对于大型多人在线游戏&#xff0c;如MMORPG或MOBA等&#xff0c;由于需要更多的CPU核心数来支持更复杂的游戏逻辑和处理大量数…

k8s-001-Centos7内核升级

1. 查看内核 [rootlocalhost ~]# uname -a 2. 执行的命令(安装最新版内核): 下载: rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org 安装: rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm &#xff08; 查看最新版内核&…

深度学习的onnx模型插入新节点构建新模型

import numpy as np import onnx import onnxruntime import onnxruntime.backend as backendmodel onnx.load(test.onnx) node model.graph.node graph model.graph# 1.2搜索目标节点 # for i in range(len(node)): # if node[i].op_type Conv: # node_rise …

Java中的图数据库应用:Neo4j入门

第1章&#xff1a;引言 在数据驱动的时代&#xff0c;咱们处理的不仅仅是数字和文本&#xff0c;还有复杂的关系和网络。想象一下社交网络中人与人之间错综复杂的联系&#xff0c;或者是互联网上网页之间的链接关系&#xff0c;传统的表格数据库已经难以高效地处理这些关系密集…

Linux-基础命令(黑马学习笔记)

Linux的目录结构 Linux的目录结构 Linux的目录结构是一个树形结构 Windows系统可以拥有多个盘符&#xff0c;如C盘、D盘、E盘 Linux没有盘符这个概念&#xff0c;只有一个根目录 /&#xff0c;所有文件都在它下面 Linux路径的描述方式 ● 在Linux系统中&#xff0c;路径之…

抖音视频批量下载软件|视频评论采集工具

抖音视频评论采集软件是一款基于C#开发的高效、便捷的工具&#xff0c;旨在为用户提供全面的数据采集和分析服务。用户可以通过关键词搜索抓取视频数据&#xff0c;也可以通过分享链接进行单个视频的抓取和下载&#xff0c;从而轻松获取抖音视频评论数据。 批量视频提取模块&a…