1. 什么是STL
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
2. STL的六大组件
3. 为什么学习string类
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
4. 标准库中的string类
4.1 string类
- 字符串是表示字符序列的类
- 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
- string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型。
- string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数。
- 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
在这里,我们需要了解一下编码这个概念:
像第一个编码就是我们所熟知的ASCII表:
为什么会有ASCII表呢?原因就是计算机是外国人发明的,它们所使用的英文为了更好的转换成二进制而作出ASCII表来映射。
然后,像我们中文就用不了ASCII表了,我们就搞了一个叫gbk的表。
还有叫做unicode,这个编码包含的更大,像中文,日文等都包含了,它分为utf-8,utf-16,utf-32,这些是因为比如一些复制的汉字,不仅仅是两个字符,而是需要3个字符,四个字符时,就需要utf-32,就代表一个char类型是4个字节。
所以,为什么会被设计成模板呢?原因就是因为编码的问题。
因为不同的编码,所对应的字符的字节是不一样的,而设计成模板,我们会更好的去管理。像ASCII码,utf-8的,我们就可以用char类型的:
像gbk,我们用wchar_t类型就会更好:
而unicode,也都有相对应的类:
总结:
- string是表示字符串的字符串类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
- string在底层实际是:basic_string模板类的别名。
- 不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std;
4.2 string类的常用接口说明
注意下面我只讲解最常用的接口。
4.2.1 string类对象的常见构造函数和拷贝构造
我们先学习这些常见的成员函数,如果我们以后遇到不认识的,我们可以去查文档。因为我们无法记住所有的成员函数。
首先,我们来看一下这些重点的使用方法:
这些都是string类的构造和拷贝构造的基本用法,比较简单。那么第三个是如何使用的呢?我们来看:
4.2.2 如何遍历string的每一个字符
这些简单的构造函数介绍完了。我们可以想一个问题:如何遍历string的每一个字符?
第一种方式:下标+[]
string类是自定义类型,它是不能像数组一样直接使用[]来访问,在string类里面,其实是用了运算符重载,让它可以像数组一样使用[]。
在string类中,还有求字符串长度的成员函数。
这样就可以遍历string的每一个字符了。
在上面,我们可以看到operator[]写了两种方式。第一种方式:可读可写。第二种方式:只能读不能写。
这是调用了第一种方式。
这是调用了第二种方式,加上const就不能写了。
第二种方式:迭代器
为什么要有迭代器呢?原因其实是在后面的一些单链表,树之类的结构,它们的访问不能使用下标了,所以采用了迭代器这样的方式。
我们先看一下它的使用,后面再详细的讲解。
在string类里面有一个叫做迭代器。迭代器里面有两个函数一个是begin,一个是end,begin是接收第一个位置,而end是接收最后一个元素的下一个位置。这样,迭代器定义一个sit来接收第一个位置,我们就可以遍历了。
迭代器我们可以认为是类似于指针一样的东西或者有时候就是指针。
第三种方式:范围for
范围for可以自动++,自动结束。其实范围for的底层原理就是它调用了迭代器。这个我们以后再说。
4.2.3 at函数
上面我们说了opearator[],在string类里面还有一个叫做at的函数:
它也是返回对字符串中位置 pos 处的字符的引用。那么它们有什么区别呢?
从这里,我们可以看到operator[]里面的底层实现是assert(断言)。
而at函数底层实现是用的抛异常。这就是二者的区别。
4.2.4 迭代器的种类
现在,我们就详细讲解一下迭代器有几种:
第一种:正向迭代器
像上面我们写的就是正向迭代器:
第二种:反向迭代器
这里的rbegin指向的是最后一个元素,而rend指向的是第一个元素的前一个位置。rit++是从后往前移,这个具体的实现,我们以后再说。
第三种:const修饰的正向迭代器
像上面的正向迭代器和反向迭代器都是可读可写的,而下面的迭代器只能读,不能写。
在这里,我们创建的s传给函数Func,它被const修饰了,所以普通的迭代器就不行了。那么编译器在底层是如何处理的呢?
我们可以看到在string类的迭代器里面,end函数和begin函数是有两个重载的。
而下面的那个重载函数就是面对的这种情况。所以,我们在使用迭代器时要这样:
这样,就可以遍历const修饰的对象了。
第四种:const修饰的反向迭代器
反向的写法也是一样的:
但是这里,有的人会说这个迭代器的名字太长了,我们可以这样写:
auto可以自动推导类型。我们可以不用自己去写了。
4.2.5 string类里面的capacity
下面,我们再来看一下string类里面的capacity
首先,size和length都是求字符串的长度,它们的功能是一样的。
但为什么会有两个呢?原因是string的产生比STL早,在早期,string求大小是用的length,但是在后面的STL里面的像数,哈希表等的数据结构,它们的大小不是叫做length,而是叫做size,为了和后面的STL的命名方式一样,string里面也加了size。
第五个算的是容量的大小,我们来看一下它的容量是如何变化的:
从结果来看,我们可以看出大约是1.5倍来增容的。但是这样不停的增容效率会很低,我们有没有什么其它的方式呢?
我们来看第六个函数:
这个函数是请求将字符串容量适应计划的大小更改为最多 n 个字符的长度。
在这里,我们已经知道需要1000个空间了,那么我们就先开辟好1000个空间。
从这里,我们就可以看到扩容的次数变少了。这次扩容的消耗就会减少许多。这样不是只扩容1000,因为它还有一些对齐的原因导致的。
那么我们在看一下第4个函数:
这个函数也是扩容,那么它和reserve有什么区别呢?
从这两幅图我们可以看出,resize不仅可以扩容,还将初始化了。而reserve并没有。resize函数还可以这样用:
在扩容的1000时,我们将它们初始化为x。
我们再来看一下resize的其它用法:
上面的对象s里面没有数据,这次我们加入数据试试:
从这里,我们可以看到resize扩容了,但并没有改变前面的数据,而是在后面添加了95个x。
从这两幅图我们可以看到:reserve和resize在vs编译器下都不能缩容,但是在resize下它的size只保留前10个。
4.2.6 append函数
在上面,我们进行尾部插入用的是push_back,但push_back只能一个一个字符的插入,如果我们需要尾部插入一整个字符串会怎么样子呢?
我们有这样的一个函数,它可以添加一个字符串,也可以添加一个字符串的对象。
4.2.7 operator+=函数
但这个函数没有operator+=函数好用。
这样用就会比append函数简单许多,我们以后也会经常这样用。
这个是我们尾部插入,那么有没有其它部位进行插入的函数呢?
4.2.8 insert和erase函数
这个函数可以实现在任意位置进行插入。比如说:我们想在头部插入一个字符,那么我们可以用上图中的第6个:
既然有插入,那么就会有删除:
如果,我们想删第一个字符,我们可以用第二个函数:
我们也可以指定位置删除,比如我们想删除第4个位置的字符:
我们也可以删除从某个位置开始到指定的多少字符。
这里的意思是在第二个位置开始,删除两个字符。但在上面我们可以看到,它还给了一个缺省值:npos, npos是string里面的一个静态成员常量
这个npos是什么意思呢?我们来看:
我们可以看到,npos是-1,但是它的类型是size_t,而且它的补码是全1,无符号类型它的值就是整数的最大值。
所以,如果我们没有给参数,默认为缺省值npos,就会把后面的字符全删了。
4.2.9 string类里的swap函数
最后,我们在看一下string类里的swap函数:
这个函数是交换两个string类对象的字符串:
那么在前面我们说过std库里面有一个全局的swap,它也可以这样去写:
我们看到都可以进行交换,那么这两个函数有什么区别呢?
在C++98中,swap(s1,s2)效率低,s1.swap(s2)效率高。
原因是:s1.swap(s2)是直接交换。而swap(s1,s2)的实现如下:
它是一种深拷贝交换。深拷贝交换它的效率会很低。
4.2.10 c_str函数
下面我们看一下这个函数:
它的功能是:返回C格式字符串。
它的目的主要是兼容C语言的一些东西。
4.2.11 find函数
我们继续看下面的这个问题:
现在,我们想取一个文件的后缀名,我们该怎么取?
我们先看这样的一个函数:
它的功能就是:在字符串中搜索由其参数指定的序列的第一个匹配项。
这里的条件为什么是string::npos原因就是如果我们找到了就会返回下标,没找到我们可以返回-1,而pos返回的无符号整型,-1也就是最大的整数,所以也可以这样去写。
找到了话,我们就需要打印,那么我们该如何取出后缀来打印呢?
4.2.12 substr和rfind函数
这个函数的功能是:返回一个新构造的对象,其值初始化为此对象的子字符串的副本。那么我们就可以这样去写:
这里,我们用的是缺省值npos,就把.后面的全复制给suffix。
但是这里会出现一个问题:
如果文件是一种压缩包的形式,那么这样取出来就不对了。
我们可以用这个函数:
它是一种倒着来找。
4.2.13 getline函数
我们在来看这样的一个问题:求字符串中最后的一个单词的长度?
我们一般会这样写:
但是这样去写会有一些问题:
你会发现这里的结果是5,不是2。原因是cin在缓冲区取字符遇到空格时会结束,它和C语言中的scanf是一样的。所以我们需要这样的一个函数:
它的意思是:从流提取中提取字符并将其存储到str 中,直到找到分隔字符 delim(或换行符 ‘\n’)。
5. string头文件中的转换函数
5.1 将字符串转换为整数
在C语言中,我们有将字符串转换为整数这个函数。
而C++有更好的方式:
5.2 将整数转换为字符串
在C语言中,我们有这个函数:
而C++有这样的函数:
这些就是string类的一些基本使用,我们先要学会如何使用它,后面我们会一点一点实现它,如果大家觉得有帮助,希望可以点赞收藏,谢谢大家!。