文章目录
1.string简介
< string > 是C++中的自带库,这个库中我们使用最多的就是string类。
为什么会有这个string类?因为C语言中,并没有为字符串单独设置一个内置类型,C语言中的字符串是通过字符数组的方式实现的,因此在C++中,专门引入一个string类。
需要注意的是,我们所使用的string类实质上是一个模板类,即由模板basic_string实例化出的类再加以重命名后得到的:
下图是我们简单模拟实现的string类中的成员变量:
2.string的成员函数
由于一些历史原因,string中的接口设置得相当繁杂,成员函数多,同时一个成员函数又重载为多个函数,所以以下仅介绍string中常用的成员函数。
2.1 构造与析构
string中最常用的构造是通过字符串去初始化以及拷贝构造。 下图是C++11标准下的所有构造函数:
考虑到string类实例化出的对象是会在堆区中申请资源的,因此string类的拷贝构造是通过深拷贝实现的,以下是对string类部分构造的简单实现:
注: 这里拷贝构造涉及到传统写法和现代写法。从本质上讲,无论是传统还是现代写法,都是需要去动态开辟空间,只不过传统写法中,开辟空间是自己实现的;而在现代写法中,开辟空间是通过复用其它函数实现的。 所以,现代写法在呈现上会更加简洁,除此之外,与传统写法差别不大。
对于析构函数,既然申请了资源,那么析构肯定要我们显式实现,释放动态开辟的空间即可:
2.2 访问和修改
2.2.1 string的访问
2.2.1.1 使用下标访问操作符(中括号)
string对象可以像C语言中的数组一样进行访问,这本质上是通过运算符重载实现的。
2.2.1.2 使用迭代器进行访问
迭代器,即iterator,是string中实现的一个内部类,对于string中的Iterator,我们可以理解为是一个指针,而我们模拟实现string类时,也是将iterator简单实现为指针。
、注: 其中迭代器的操作与指针完全相同,但要清楚,iterator本质上不是指针,上述类似的操作是通过运算符重载实现的。
2.2.1.3 使用范围for
范围for是C++中一种极为方便的遍历方式,但范围for的底层实质上是通过迭代器实现的。
2.2.1.4 大小、容量和C-String
string对象的大小即string中实际存储的有效字符个数,string的容量即最多能存储几个有效字符。需要注意的是,string底层是通过类似顺序表的方式实现的,所以动态开辟空间时,会比容量多开一个空间用以存储\0.
C++中的string是兼容C的,因为string中实现了一个成员函数 c_str() 用以返回字符串首元素的地址:
2.2.2 string的修改
2.2.2.1 reserve与resize
string类中实现了reserve和resize两个成员函数。
reserve主要是用于扩容的,但也可以用于缩容,但是否一定缩容,C++标准是没有规定的,不同的平台下reserve的实现方式不同,缩容也就呈现不同的结果。在vs下,reserve是基本不会缩容的,主要用于扩容。
resize适用于改变有效数据个数的,它可以执行删除以及添加的操作。删除不必多说,添加操作分为两步——首先检查容量是否够存储,如果够,则执行下一步操作,如果不够,则进行扩容;然后,将增加的有效数据处用给定字符填充,如未给定,则使用默认字符c(缺省参数)。
2.2.2.2 添加操作
添加操作中使用最多的无疑是重载的+=运算符,其次则是尾插push_back以及指定位置的插入Insert.
考虑到实际对string对象增添操作的复杂性,上述的成员函数均进行了一定程度的重载以满足不同要求,以下是部分重载的简单实现:
2.2.2.3 删除操作
删除操作最常用的是清空操作clear以及指定位置开始删除指定长度的erase.
不过,这些删除操作都是删除有效数据,即改变size的大小,但不改变capacity,即不会释放动态开辟的空间。
erase中会涉及到一个npos.这个是stiring中私有的静态成员变量,类型是无符号整型,大小是无符号整型的最大值。
当调用erase函数时,未指定长度,长度便是npos,此时显然长度过长,在这种情况下,便会将从指定位置(默认是起始位置)开始的数据(包括指定位置)全部删除;而若主动给定的长度过长,也会执行这样的操作。
2.3 简单的迭代器实现
我们此处使用指针来简单模拟实现string类中的迭代器。
既然将迭代器实现为指针,那么begin()和end()简单返回字符串的起始和结束地址即可。注意,这里的结束地址是\0的地址。
2.4 查找功能
查找功能最常见的是两种:查找第一次出现的某个字符以及查找子串。
查找第一次出现的某个字符简单遍历比较即可,而查找子串则方法比较多,我们这里模拟实现仅简单复用库中中的strstr() 进行查找,这个函数使用的是暴力查找法(BF)。
2.5 关系运算符重载
string对象中的比较与C中strcmp比价的原理相同,这些关系运算符的重载也是通过复用strcmp以及相互复用实现的,比较简单,不作赘述,看下图:
2.6 其它非成员函数
string库中还有一些非成员函数,最典型的就是重载在全局的流插入和流提取,以及特殊的用于获取输入字符串的函数getline。
流插入实现较为简单,见下图:
此处,着重讲以下流提取的重载以及getline函数。
输入时,读取并存储最简单的方式就是读一个字符,然后便存储这个字符到string对象中。 但是,这样做会有一个弊端,如果读取字符很多的话,会频繁扩容,损耗较大。因此,我们使用一个buffer数组,先暂时存储这些读取的字符,直到满足相应条件的时候,再把buffer数组中的内容一次性全给到string对象中,这样就可以避免频繁扩容,以减小损耗。
流提取和getline都可以用于读取字符串,它们两个最大的区别在于delimiter的不同,即定界符,或者说分隔符的不同。
流提取默认以空格或者换行符作为不同字符串的分隔,也就是说流提取一旦读到空格或者换行符就结束,不会再继续读取。所以,如果想要读取一个内部带空格的字符串,使用流提取是无法实现的。
而getline,顾名思义,是用来读取一行字符串的。这么说,也不完全正确,准确地说,默认情况下,getline以换行符作为结束标志,一旦读取到换行符便不再读取。 但是,getline函数是可以指定delimiter的,如果你不指定,delimiter就是换行符;你若指定,delimiter便是你指定的字符。