【C++】详解string类

目录

简介

框架

构造

全缺省构造函数

​编辑

传对象构造函数

拷贝构造

析构函数

容量

size()

capacity()

empty()

clear()

reserve()

​编辑

resize()

遍历

检引用符号"[ ]"的重载

迭代器

begin()

end()

rbegin()

rend()

修改

push_back()

"+="运算符重载

c_str

find()

其他

赋值运算符重载

现在式写法

比较运算符重载

"<"运算符重载

"="运算符重载

复用


简介

因为世界上有很多种语言,所以在STL库里有一个管理字符的模板类:basic_string模板类,而本篇要讲的string类是basic_string模板类的一个实例化,类型为char

string类的接口设计是比较冗余的,是因为string类有一些历史包袱。在STL出现之前,就已经有string类了。在STL被惠普实验室开源之后,string类既要符合STL的标准又要向前兼容,所以string类的接口比较冗余。

string类的底层是顺序表,它在堆上开辟连续的空间,并将代码段中的常量字符串进行拷贝。通过顺序表实现对字符串的增删查改。可参考C++的动态内存管理一文:http://t.csdnimg.cn/qHEzj

小编会介绍一些常见的接口,解释它们的功能,参数。然后模拟实现一下。为了方便,小编会调用C语言库中的一些字符串操作函数。头文件string.h

模拟实现不是造一个更好的轮子,而是为了帮助我们更好的理解string类。模拟实现的代码是禁不住在各种场景下测试的,一定会出现各种bug。小编会指出一些能想的到的bug。庖丁解牛,恢恢乎游刃有余

C++是一门面向对象的语言,大家不必对每一个接口都有细致的了解。小编写本篇的目的是为了让大家能对string类的主体框架有一定的了解。让大家在查文档时游刃有余。


框架

为了让类名和接口的写法和库里的保持一致,需要封一层命名空间,名字为MyString

数据设计:

顺序表的有效数据:size_t _size

顺序表的空间大小:size_t _capacity

指向顺序表的指针:char* _str

static const size_t npos = -1; 

nposstring类中的一个静态成员变量,它表示一个无效的位置或者未找到的位置。在string类中,当查找某个子字符串或字符时,如果未找到,则返回npos

namespace MyString //命名空间
{class string  //string类
{public://接口(方法)private:size_t _size; //数据有效个数size_t _capacity;  //空间大小char* _str;  //指向的空间 static const size_t npos = -1; };}

构造

可参考详解构造函数一文:http://t.csdnimg.cn/8Hl5K

功能:构造一个string类对象,并将其初始化

在C++11的标准中,有非常多的构造函数,如下图

模拟实现:

小编先问大家一个问题,当我们用"x\0xxx"字符串构造对象时,有效数据_size应该给成多少呢,1还是5?

可以参考一下标准库里的实现

标准库的逻辑是:在初始化对象时,以第一个"\0'为准。我们就可以调用库中的strlen(),帮我们计算一下要初始化_size的大小。strlen()计算长度时也是以第一个"\0"为准。

后面会有"x\0xxx",但_size是5的情况。因为标准库中的string类的有效数据是以_size为准,而不是以"\0"为准。这是C++的string类和C语言中字符串函数不一样的地方。

全缺省构造函数

代码如下

//全缺省构造
string(const char* str = "")  :_size(strlen(str))                  ,_capacity(_size + 1)          ,_str(new char[_capacity])  
{strcpy(_str, str);
}

传对象构造函数

代码如下

	string(const string& str):_size(str._size), _capacity(str._capacity)    , _str(new char[str._capacity])                       {memcpy(_str, str._str, str._capacity); //这里用strcpy()拷贝会有很坑的bug}

为什么小编会说用strcpy()拷贝数据会有一个很坑的bug呢?

因为string()是以"\0"为基准作为结束条件的。而string类的顺序表可能会存放"x\0xxx"这样的字符串,数据可能会拷贝不全。

拷贝构造

可参考详解拷贝构造一文:http://t.csdnimg.cn/oJCAU

代码如下

	//拷贝构造string(const string& str) {_size = str._size;  //有效数据个数_capacity = str._capacity;  //空间_str = new char[str._capacity]; //开空间,深拷贝    memcpy(_str, str._str, str._capacity);  //这里建议用memcpy()防止中间有"\0"的问题}

现在式写法,代码如下

void swap(string& s) //交换数据函数
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
string(const string& s) : _str(nullptr) //初始化数据,_size(0),_capacity(0)
{string Tmp(s._str);  //调用构造函数构造一个与s对象一样的临时对象swap(Tmp);     //调用交换函数交换两个对象
}

传统写法:需要自己开空间,自己拷贝数据

现在写法:把事情交给别的函数或方法去做,具有面向对象思维

析构函数

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

容量

size()

功能:

返回字符串有效长度

模拟实现:

size_t size() const
{return _size;    
}

capacity()

功能:

返回string类的顺序表的空间

模拟实现:

size_t capacity() const
{return _capacity;
}

empty()

功能:

判断字符串是否为空

模拟实现:

bool empty() const
{if (_size == 0)return true;elsereturn false;
}

clear()

功能:

清空有效字符

模拟实现:

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

_str[0] = '\0';这句代码是为了兼容C语言的一些接口

reserve()

功能:

将容量调整至n
此函数不会影响到字符串
n大于_capacity,编译器会将容量调整n(可能更大,不同版本的STL具体实现不同)
n小于或等于_capacity,编译器可能会缩容,也可能不会,不具有强制性

参数:

无符号整型n,代表要扩容的大小

模拟实现:

void reserve(size_t n = 0)
{if (n <= _capacity)return;char* ptr = new char[n + 1]; //开辟新空间, 多开一个是为了存放‘\0’                       memcpy(ptr, _str, _size + 1); //把旧空间的值拷贝给新空间  delete[] _str; //释放旧空间   _str = ptr; //_str指向新空间          _capacity = n + 1;                                         ptr = nullptr;      
}

resize()

功能:

将字符串长度调整为n个字符的长度
n < _size 时,删除多余字符(删掉_size - n个字符)
n > _size 时,会先扩容,如果指定了c,会将其初始化为字符c,没有指定,会将其初始化为"\0"

参数:

无符号整型n,代表要调整字符的长度
字符型c,代表要初始化的字符

模拟实现:库中给了我们两个接口,但我们只需要实现一个全缺省即可

思路也很简单,只需要处理n的不同情况即可。代码如下,配有详细的注释

void resize(size_t n, char c = '\0')//c为全缺省参数,不指定c时会将新开辟的空间初始化为"\0"
{if ( n < 0) //n小于为非法值,直接返回return;if (n > _size) //n大于有效字符时,会扩容{  reserve(n); //扩容,复用reserve,后面凡是扩容都会复用reservefor (size_t i = _size; i < _capacity - 1; i++) //对开辟的空间初始化{_str[i] = c;}_str[_capacity - 1] = '\0';  //给最后一块空间赋值为\0_size = n; //不要忘了调整有效数据return;}_str[n] = '\0'; //当n小于或等于0时删数据_size = n;
}

遍历

检引用符号"[ ]"的重载

功能:

检索第pos位置的下标

参数:

无符号整型pos,代表下标

返回值:

char类型,返回string类中顺序表的的第pos位置的字符

模拟实现:库中给了两个,一个非成员函数,一个成员函数。代码如下

char& operator[] (size_t pos)
{assert(pos >= 0);assert(pos <= _size);  //检查下标的合法性return _str[pos];  //返回顺序表中该下标的值}
const char& operator[] (size_t pos) const
{assert(pos >= 0); assert(pos <= _size); return _str[pos]; 
}

迭代器

需要把返回值的类型取别名为迭代器的通用类型,代码如下

typedef char* iterator; //正向迭代器,可修改typedef char* reverse_iterator; //反向迭代器,可修改typedef const char* const_iterator; //正向迭代器,不可修改typedef const char* const_reverse_iterator; //反向迭代器,不可修改

因为string类型的底层是基于顺序表实现的,所以string类型的迭代器是指针

begin()

获取有效字符的第一个位置
iterator begin() //获取第一个字符的位置
{return _str;
}
const_iterator begin() const
{return _str;}

end()

获取最后一个有效字符的下一个位置
iterator end() //最后一个字符的下一个位置
{return _str + _size; 
}
const_iterator end() const
{return _str + _size;}

rbegin()

获取最后一个有效字符的位置
reverse_iterator rbegin() //获取最后一个字符的位置
{return _str + _size - 1;}
const_reverse_iterator rbegin() const
{return _str + _size - 1;}

rend()

获取第一个有效字符的前一个位置
reverse_iterator rend()//获取第一个字符位置的前一个位置
{return _str - 1;
}
const_reverse_iterator rend() const
{return _str - 1;}

修改

push_back()

功能:

将字符c尾插至string类的字符串后面

参数:

字符型c,代表要尾插的字符

模拟实现:

void push_back(char c)
{if ((_size + 1) == _capacity || _size == _capacity) //判断是否要扩容{reserve(2 * _capacity);}_str[_size] = c;  //尾插_str[_size + 1] = '\0'; //插入\0++_size;  //更改有效数组的值}

"+="运算符重载

功能:

尾插一个类的字符串
尾插一个字符串
尾插一个字符

返回值:

string类的引用

这三类运算符重载互相构成函数重载

实现代码如下

string& operator+= (const string& str)         
{size_t len = str._size + 1;  if ((_size + len) >= _capacity)  //扩容{reserve(2 * (_capacity + _size + len));}strcpy(_str + _size, str._str);   //拷贝数据return *this; }

上述代码中拷贝数据用了strcpy(),在一般场景下是没问题的。但禁不住测试,大家能调试出来吗?这个bug前文已经提过很多次了。

string& operator+= (const char* s)
{while (*s){push_back(*s);s++;}return *this;}
string& operator+= (char c)
{push_back(c);return *this;}

小编这里复用了push_back(),小编懒得再造轮子了(害羞)

c_str

功能:

返回C形式的字符串

模拟实现:

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

find()

功能:

查找字符串序列在string类的顺序表中第一次出现的位置

参数:

string类类型的引用,表示要查找的字符串序列为string类型的顺序表
无符号整型pos,表示从哪一个下标开始找
字符型c,表示要查找的字符
字符型指针s,表示要查找的字符串

返回值:

找到:返回该位置的下标
没找到:返回npos

模拟实现:小编只实现其中的一种

size_t find(const string& str, size_t pos = 0) const
{if (pos < 0 || pos > _size) //检查坐标合法性return npos;char* ptr = strstr(_str + pos, str._str); //strstr()是一种暴力匹配,
if (ptr == NULL)
{return npos;
}
else
{return ptr - _str;  
}
}

我们不是为了造更好的轮子,用strtstr()即可。


其他

赋值运算符重载

现在式写法

string& operator=(string tmp)
{swap(tmp);return *this;
}

比较运算符重载

只需实现"<"和"="即可,其他可以复用

"<"运算符重载

bool operator<(const string& s) 
{size_t i = 0;size_t j = 0;while (i < _size && j < s._size)//按长度小的字符串比较{if (_str[i] < s._str[j]){return true;}else if (_str[i] > s._str[j]){return false;}else{++i;++j;}}return _size < s._size;//如果相等长度下相等,长度小的字符串就小
}

"="运算符重载

bool operator==(const string& s)
{size_t i = 0;size_t j = 0;while (i < _size && j < s._size){if (_str[i] < s._str[j] || _str[i] > s._str[j]){return false;}else{++i;++j;}}return _size == s._size ? true : false;
}

复用

	bool operator<=(const string& s){return *this <= s;      }bool operator>(const string& s){return !(*this <= s);  }bool operator>=(const string& s) {return *this >= s; }bool operator!=(const string& s){return !(*this == s); }

本文到这里就结束啦

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

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

相关文章

【触摸案例-控件不能响应的情况 Objective-C语言】

一、接下来,我们来说这个“控件不能响应的情况”, 1.素材里边,有一个“不接受用户交互的情况”,这么一个代码,把它打开, 把这个项目啊,复制过来,改一个名字,叫做“04-控件不能响应的情况”, 打开之后,command + R,运行一下, 在storyboard上,你也可以看得出来,我…

智慧农业设备——虫情监测系统

随着科技的不断进步和农业生产的日益现代化&#xff0c;智慧农业成为了新时代农业发展的重要方向。其中&#xff0c;虫情监测系统作为智慧农业的重要组成部分&#xff0c;正逐渐受到广大农户和农业专家的关注。 虫情监测系统是一种基于现代传感技术、图像识别技术和大数据分析技…

链表-----返回倒数第K个节点回文结构的判断相交链表

目录 1.返回倒数第K个节点 2.回文结构的判断 3.相交链表的判断&#xff0c;返回交点 1.返回倒数第K个节点 &#xff08;1&#xff09;返回链表的第k个节点&#xff0c;我们这里的做法是定义两个指针&#xff0c;这两个指针之间相差的是k这个长度&#xff1b;这个过程的实现就…

Android手势识别面试问题及回答

问题 1: 如何在Android中实现基本的手势识别&#xff1f; 答案: 在Android中&#xff0c;可以通过使用GestureDetector类来实现基本的手势识别。首先需要创建一个GestureDetector的实例&#xff0c;并实现GestureDetector.OnGestureListener接口来响应各种手势事件&#xff0c…

创建SpringBoot和RabbitMQ的整合项目

文章目录 创建SpringBoot和RabbitMQ的整合项目首先快速创建一个maven项目引入SpringBoot整合rabbitMQ的依赖在src/main目录下创建resources目录并引入配置文件写消息发送者MessageSender写消息接收者MessageReceiver写RabbitMQConfig配置类写SpringBoot启动主类CommandLineRunn…

小剧场短剧影视小程序源码_后端PHP

项目运行截图 源码贡献 https://githubs.xyz/boot?app42 部署说明 linux/win任选 PHP版本&#xff1a;7.3/7.2&#xff08;测试时我用的7.2要安装sg扩展 &#xff09; 批量替换域名http://video.owoii.com更换为你的 批量替换域名http://120.79.77.163:1更换为你的 这两个…

代码随想录算法训练营第60天|84.柱状图中最大的矩形

84. 柱状图中最大的矩形 题目链接&#xff1a;柱状图中最大的矩形 题目描述&#xff1a;给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 解题思路&#…

24 JavaScript学习:this

this在对象方法中 在 JavaScript 中&#xff0c;this 的值取决于函数被调用的方式。在对象方法中&#xff0c;this 引用的是调用该方法的对象。 让我们看一个简单的例子&#xff1a; const person {firstName: John,lastName: Doe,fullName: function() {return this.firstN…

【webrtc】MessageHandler 3: 基于线程的消息处理:以sctp测试为例

消息处理可以用于模拟发包处理G:\CDN\rtcCli\m98\src\net\dcsctp\socket\dcsctp_socket_network_test.cc 这个实现中,onMessage还是仅对了一种消息进行处理,就是接收则模式下,打印带宽。当然,可能程序有多个消息,分别在不同的onmessage中执行?SctpActor:以一个恒定的速率…

C语言贪吃蛇项目

今天给大家带来一款简单的贪吃蛇游戏&#xff0c;一起随我来看看吧 游戏效果&#xff1a; 实现基本的功能&#xff1a; • 贪吃蛇地图绘制 • 蛇吃⻝物的功能&#xff1a;&#xff08;上、下、左、右⽅向键控制蛇的动作&#xff09; • 蛇撞墙死亡 • 蛇撞⾃⾝死亡 • 计算得分…

Flink 实时数仓(一)【实时数仓离线数仓对比】

前言 昨天技术面的时候&#xff0c;面试官说人家公司现在用的都是最新的技术&#xff0c;比如 Doris 等一些最新的工具&#xff0c;确实这些课是学校永远不会开设的&#xff0c;好在他说去了会带着我做一做。可是 ...... 学院这边确实不允许放人&#xff0c;唉&#xff0c;可惜…

Kubernetes 弃用Docker后 Kubelet切换到Containerd

containerd 是一个高级容器运行时&#xff0c;又名 容器管理器。简单来说&#xff0c;它是一个守护进程&#xff0c;在单个主机上管理完整的容器生命周期&#xff1a;创建、启动、停止容器、拉取和存储镜像、配置挂载、网络等。 containerd 旨在轻松嵌入到更大的系统中。Docke…

python项目入门新手攻略

最近工作需要接手了代码量比较大的python开发的项目&#xff0c;平时写python不多&#xff0c;记录一下如何熟悉项目。 分析调用流程-pycallgraph 因为代码量比较大&#xff0c;所以希望通过工具生成代码调用流程&#xff0c;因此用到了pycallgraph。 pycallgraph&#xff0…

windows Jenkins运行python+selenium打开浏览器一直无响应,运行中,还没有打开浏览器

一开始解决办法是把打开服务把Jenkins给禁用了 但是没有用&#xff0c;然后找到安装目录 C:\Program Files\Jenkins 在这个路径下&#xff0c;在地址栏输入cmd打开命令窗口运行Jenkins启动命令 java -jar jenkins.war --httpPort8080 打开浏览器进入链接 http://localhost:…

【Unity学习笔记】第十四 Prefab 概念解惑

目录 1 prefab、prefab变体、prefab覆盖和prefab 嵌套2 connect 与unpack3 prefab到底是什么&#xff0c;它和gameobject又有什么区别&#xff1f;4 为什么要用prefab&#xff1f;5 代码动态加载prefab6 为什么我unity PrefabUtility.InstantiatePrefab() 得到的是null7 Prefab…

Redis基本命令

目录 一、包含String、Set数据类型的基本命令 1、添加一个键值对 2、获取key所关联的字符串值 3、同时设置多个key-value 4、获取多个key对应的值 运行结果 5、将给定的value追加到原值的末尾 追加后效果 6、删除单个key 7、同时删除多个key 8、查询包含某个字符的k…

ubuntu入门

基础命令 cd 切换命令 ls 查看当前目录下所有的文件 cp a.c b.c 拷贝a.c 到 b.c touch a.c 创建a.c文件 mkdir file 创建文件夹file rm file 删除文件 rmdir 删除test文件夹 rmdir test/ mv 移动文件 mv a.c b.c 把a.c 替换成b.c ifconfig 查看电脑网络信息 rm xx 删…

Mybatis进阶(动态SQL)

文章目录 1.动态SQL1.基本介绍1.为什么需要动态SQL2.基本说明3.动态SQL常用标签 2.环境搭建1.新建子模块2.删除不必要的两个文件夹3.创建基本结构4.父模块的pom.xml5.jdbc.properties6.mybatis-config.xml7.MyBatisUtils.java8.MonsterMapper.java9.MonsterMapper.xml10.测试Mo…

工业互联网通讯协议—欧姆龙(Fins tcp)

一、场景 近期公司要对欧姆龙CP系列设备的数据采集&#xff0c;于是就研究了下欧姆龙的Fins Tcp协议。 二、Fins Tcp 组成字节说明固定头446494E53 FINS对应的ASCII码的十六进制长度4后面剩余指令的长度命令4 握手固定为&#xff1a;00000000 读写固定为&#xff1a;0000000…

Unity 实现新手引导遮罩

Unity 复写OnPopulateMesh 实现新手引导遮罩、包含点击事件触发区域判断 https://download.csdn.net/download/shenliang34/89247117