C++基础:string底层的实现

server/2024/11/29 9:46:20/

在这里插入图片描述

文章目录

  • 1. 类的创建和简单函数的实现
    • 1.1 类的创建
    • 1.2 简单函数的实现
  • 2.string增删查改的实现
    • 2.1 增加字符
    • 2.2 增加字符串
    • 2.3 任意位置插入删除字符字符串
      • 2.3.1 插入字符
      • 2.3.2 插入字符串
      • 2.3.3 删除字符/字符串
    • 2.4 查找字符/字符串
    • 2.5 字符串的拷贝(C++深浅拷贝的分析)
  • 3. 字符串的运算符重载
    • 1. 重载比较运算符
    • 2.重载io流
      • 空间扩容分析

1. 类的创建和简单函数的实现

1.1 类的创建

在使用初始化列表时候要注意:初始化的顺序为变量声明的顺序而不是初始化列表的顺序

1730256669810

当然也可以不用初始化列表初始化

1730257031503

1730259112050

将析构函数写出来后,我们进行测试就会发现,如果我们什么都不输入,进行输出的时候程序就会崩溃掉,

所以直接初始化需要进行修改

1730259215709

当然也可以将两个合到一起写一个缺省

1730259362962

这里需要注意也不能给空指针,防止对空指针的解引用程序崩掉

1.2 简单函数的实现

简单的函数并且频繁调用不用声明和定义分离,在string有size()以及下标访问也就是重定义的[]

实现方法就比较简单了:

1730268197916

测试一下:

1730268251764

但是我们的范围for却无法生效,提示缺少begin函数

1730269065131

我们可以通过对char*进行重定义来实现iterator,仅仅返回字符串的头和尾即可

1730269312970

1730269527789

这里我们就会发现所有的容器中的iterator 基本上都是typedef 得来的

迭代器屏蔽了底层的实现细节,提供了访问容器的方式,不需要关心底层结构和实现细节,也就是封装的体现

同理const_iterator 迭代器实现:

1730270678880

2.string增删查改的实现

2.1 增加字符

  1. 先来写一个空间扩容的函数

1730271651998

  1. push_back的实现

1730271668071

写完push_back后就能将运算符重载写出来了:

1730272117509

但是这是运行会发现字符访问越界了

1730272104456

这里是因为添加时没有加\0导致访问越界了

1730272269030

在后面追加字符\0后,就可以正常实现了

1730272303118

2.2 增加字符串

同理我们写一下append函数:

这里空间扩容需要注意,可以直接按照上面的直接扩容,但是还是建议用下面的防止频繁扩容

而追加字符串可以用strcat但是让需要函数在重新去找’\0’

也可以我么自己从后面直接拷贝

1730273522067

运算符重载也是能够直接写出来了:

1730273643116

后面我们测试一下就可以了

1730273153765

2.3 任意位置插入删除字符字符串

2.3.1 插入字符

逻辑很简单,空间不够扩容,够了先进性挪动数据,然后进行增加字符就行了

1730277476086

1730277458496

但是这样写有bug,当我们输入0的时候程序就挂掉了

1730278313414

我们进行调试:

1730278897934

原因是size_t 类型的遇到0-1就会变为整数的最大值

那我们将size_t改为int是不是就可以了呢?

1730279055699

也不会,但是end变为-1为什么还没有停下来呢?

当操作符两边的操作数类型不同的时候,编译器会进行类型转换

而比较运算符一般都会进行类型提升

因为pos为size_t类型,所以将end也进行了类型转换到了size_t类型

所以将pos的类型也改为int就可以了

1730279443507

当然也可以用指针的方式来完成,也能让end的值赋为_size+1_

2.3.2 插入字符串

这里我们直接按照之前的思路将代码写出来

image-20241031085212235

但是这时候仍然有bug

image-20241031085125714

通过调试我们发现应该是在挪动的时候出现了问题

image-20241031090337496

如果按照之前那样写就在移动的时候少挪动以为导致的bug

改完之后就可以了:

image-20241031090511358

所以在解决问题的时候不能只跟着自己的感觉走,应该多分析分析

2.3.3 删除字符/字符串

删除的逻辑就是如果后面的参数比后面的字符多就将pos后面的全部删除,否则就仅仅删除中间的部分

image-20241031094957418

测试一下:

image-20241031094817527

2.4 查找字符/字符串

image-20241031095700811

image-20241031100222927

2.5 字符串的拷贝(C++深浅拷贝的分析)

image-20241031105408604

但是我们运行的话,程序就挂掉了,原因如下:

image-20241031105330119

所以当编译器优化激进一点就会让这个程序变正确,但是浅拷贝始终是个坑,改为深拷贝就可以了

image-20241031105514028

这时程序就正常了

image-20241031105204202

那么拷贝是否正常呢?

我们来测一下:

image-20241031111038138

也是有问题的,那我们将赋值也写一下:

image-20241031111145035

这时候测一下:

image-20241031111134493

但是如果我们自己和自己赋值就又有问题了:

image-20241031111350226

直接自己先将自己释放了,导致出现随机值

那再改一下:

image-20241031111631985

测试:

image-20241031111617338

这样就完成了

3. 字符串的运算符重载

1. 重载比较运算符

我们接下来完成以下函数:

image-20241101104135223

还是和之间一样,我们先写两个,后面就直接调用我么之前写的函数就行了

比较就直接调用strcmp 就行了,也可以自己按照自己的方法进行比较

image-20241101110753458

测试:

image-20241101111510097

2.重载io流

image-20241101123957951

image-20241101124011155

image-20241101124024133

image-20241101125430205

cincout会将空格和换行当作为字符的分隔符,将其直接忽略掉,所以提取不到,导致程序一直运行

我们用get()函数就能解决:

image-20241101125718947

image-20241101125614781

空间扩容分析

我们这里有个问题:

image-20241101131559795创建一个字符数组buff[],现将字符放入buff中,
假设有N个空位置,就可以放(N-1)个字符【最后一个位置放’\0’】
如果buff满了,就直接让s+=buff,然后重新放到buff中
等buff再次满了,就再次扩容

image-20241101133043144

image-20241101133123038

文章代码:

String.h:

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
#include <string>
using namespace std;namespace test1
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}iterator begin() const{return _str;}iterator end() const{return _str + _size;}//string()//	//:_str(nullptr) 这里不能使用NULL指针进行初始化,以防对空指针的解引用//	:_str(new char[1]{'\0'})//	,_size(0)//	,_capacity(0)//{}//string (const char* str)//	:_str(new char[strlen(str)+1])//	,_size(strlen(str))//不能先初始化size,再用size初始化_str,初始化列表初始化的顺序为变量声明的顺序//	,_capacity(strlen(str))//{}string(const char* str = ""){_size = strlen(str);_capacity = _size;//capacity不包含'\0'_str = new char[_capacity + 1];strcpy(_str, str);}//拷贝构造string(const string& s){_str = new char[s._capacity+1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}//赋值string& operator=(const string& s){if (this != &s){delete[] _str;_str = new char[s._capacity];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;return *this;}}const char* c_str() const{return _str;}~string(){delete[]_str;_str = nullptr;_capacity = _size = 0;}size_t size() const{return _size;}size_t capacity() const{return _capacity;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}void reserve(size_t n);void push_back(char ch);void append(const char* ch);string& operator+=(char ch);string& operator+=(const char* ch);void insert(size_t pos, char ch);void insert(size_t pos, const char* ch);void erase(size_t pos, size_t len = npos);//缺省参数size_t find(char ch, size_t pos = 0);size_t find(char* str, size_t pos = 0);string substr(size_t pos = 0, size_t len = npos);private:char* _str;size_t _size;size_t _capacity;static const size_t npos;};bool operator<(const string& s1, const string& s2);bool operator<=(const string& s1, const string& s2);bool operator>(const string& s1, const string& s2);bool operator>=(const string& s1, const string& s2);bool operator==(const string& s1, const string& s2);bool operator!=(const string& s1, const string& s2);ostream& operator<<(ostream& out, string& s);istream& operator>>(istream& in, string& s);
}

String.cpp:

#include "string.h"namespace test1
{const size_t string::npos = -1;void string::reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];//存放\0strcpy(tmp, _str);delete[]_str;_str = tmp;_capacity = n;}}void string::push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}string& string::operator+=(char ch){push_back(ch);return *this;}void string::append(const char* ch){size_t len = strlen(ch);if (_size + len > _capacity){//reserve(_size + len);reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);//大于2倍直接开空间,否则按照2倍扩容}strcpy(_str + _size, ch);//同时会将'\0'拷贝到后面_size += len;}string& string::operator+=(const char* ch){append(ch);return *this;}void string::insert(size_t pos, char ch){assert(pos < _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}//挪动数据int end = _size;while (end >= (int)pos){//条件断点if (end == 0){int i = 0;}_str[end + 1] = _str[end];end--;}_str[pos] = ch;++_size;}void string::insert(size_t pos, const char* ch){size_t len = strlen(ch);if (_size+len > _capacity){reserve(_size + len >= 2 * _capacity ? _size + len : 2 * _capacity);}size_t end = len + _size;while (end > pos + len - 1)//end >= pos+len{_str[end] = _str[end - len];end--;}for (size_t i = 0; i < len; i++){_str[pos + i] = ch[i];}_size += len;}void 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;}}size_t string::find(char ch, size_t pos){for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t string::find(char* str, size_t pos){assert(pos < _size);const char* ptr = strstr(_str + pos, _str);if (ptr == nullptr){return npos;}else{ptr - _str;//两个指针相减就是下标}}string string::substr(size_t pos, size_t len){assert(pos < _size);if (len > _size - pos){len = _size - pos;//大于就更新pos}string sub;sub.reserve(len);//先开空间防止+=在开空间了for (size_t i = 0; i < len; i++){sub += _str[i + pos];}return sub;}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 strcmp(s1.c_str(), s2.c_str()) == 0;}bool operator!=(const string& s1, const string& s2){return !(s1 == s2);}ostream& operator<<(ostream& out, string& s){for (auto i : s){cout << i;}return out;}istream& operator>>(istream& in, string& s){char ch;//in >> ch;ch = in.get();const int N = 256;char buff[N];int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == N - 1)//buff满了{buff[i] = '\0';s += buff;i = 0;}s += ch;//in >> ch;ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}
}

http://www.ppmy.cn/server/137628.html

相关文章

基于SpringBoot的助农商超管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

git入门教程4:git工作流程

一、初始化仓库 新建或选择项目目录&#xff1a;首先&#xff0c;你需要在你的计算机上创建一个新的项目目录&#xff0c;或者选择一个已有的项目目录作为Git仓库的根目录。初始化仓库&#xff1a;打开终端&#xff08;在Windows上可以是Git Bash&#xff09;&#xff0c;切换…

Python画图3个小案例之“一起看流星雨”、“爱心跳动”、“烟花绚丽”

源码如下&#xff1a; import turtle # 导入turtle库&#xff0c;用于图形绘制 import random # 导入random库&#xff0c;生成随机数 import math # 导入math库&#xff0c;进行数学计算turtle.setup(1.0, 1.0) # 设置窗口大小为屏幕大小 turtle.title("流星雨动画&…

chrome商店下载的插件转crx安装包

获取插件ID 2. 构造下载链接 https://clients2.google.com/service/update2/crx?responseredirect&oswin&archx64&os_archx86_64&nacl_archx86-64∏chromecrx&prodchannel&prodversion77.0.3865.90&acceptformatcrx2,crx3&xid%3D[插件ID]%…

React.js教程:从JSX到Redux的全面解析

文章目录 介绍react脚手架jsx语法和react组件jsx的基本语法jsx的行内样式jsx的类名classNameif条件渲染map循环渲染创建组件方法 可视区渲染 (React- virtualized)React-redux 介绍 javascript库&#xff0c;起源于Facebook的内部项目&#xff0c;类似于vue特点 声明式组件化 …

第二章:C语言基础(一)

作业 1> 打印输出一棵圣诞树 #include<stdio.h> int main(int argc, const char *argv[]) {printf(" * \n");printf(" ** \n");printf(" ******** \n"…

SpringBoot节奏:Web音乐网站构建手册

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

linux TOP命令解析

top 命令的输出提供了有关系统总体负载、内存使用情况以及各个进程资源使用的实时信息。以下是详细的解释&#xff1a; 1. 系统运行时间和负载信息 top - 11:13:58 up 28 days, 23:59, 3 users, load average: 2.77, 1.98, 1.63时间&#xff1a;当前系统时间 11:13:58。 运…