目录
一:认识STL
1.什么是STL
在日常的程序编写当中,假如我们需要交换两个数据就必须手动书写一个交换函数,之后再进行传参。这样才可以实现两个数据的交换。在很多情况下也是如此,我们通常需要的功能还得自己来写,写完之后才可以使用。相信大多数人都想过:要是我们的各种功能都像已经封装好的库当中的函数那样该有多好,这样我们就可以直接使用了。我们的STL就是这样的一个工具。
STL是一个封装好的具有各种各样功能的标准库,我们通过调用STL当中的函数可以直接实现我们想要的结果。其中包括我们之前辛辛苦苦所创建好的各种数据结构,这些都在STL当中已经被封装完毕。我们直接使用即可。在STL当中还存在各种各样的算法,比如字符串的逆序等操作。
2.STL当中的各种功能
在STL当中一共存在以下的功能:
其中的迭代器类似于遍历数组等操作。仿函数就是模拟我们正常使用的函数,我们不需要自己定义直接使用即可,算法也就是我们修改数据等操作所需要使用的方法,比如字符串逆序等操作。空间配置器就是自动为我们开辟空间的一个装置,我们只需要像该部分传入一定的参数即可达到自动开辟空间的效果。之后的容器其实就是我们的数据结构当中的各种各样存储和管理数据的方式,比如vector表示顺序表,map表示图,set表示集合等。最上面的配置器我们目前还用不到,到后面我们会进行更加详细的讲解。
3.STL的重要性
由于使用STL可以免去很多不必要的使用上的麻烦,所以当我们在C++代码的编写上面就很提倡使用STL进行各种操作。也正因如此我们在工作面试的时候经常会遇到对于STL标准库知识点的考察,希望大家可以多多积累和学习。
二:认识string类
1.什么是string
在C++语言当中我们还引申加入了很多的额外的功能,比如我们现在需要进行学习的字符串string。大家可能会很好奇为什么会新加入一个字符串呢?我们C语言当中不是已经有过字符串了吗?需要大家注意的是在C语言当中我们的字符串的样子是以下这样的:
char ch[20]="just test for string"
我们再来详细的观察上面的代码:变量名ch前面的char表示的是ch的类型也就是字符类型,后面的 [ ] 表示的是我们代表一个数组,所以实质上在C语言当中的字符串实质上就是一个字符数组。并不是真正意义上的字符串。并且我们经常会遇到针对于字符串的修改等操作,所以在C++当中我们就专门定义了一个类,用于管理字符串相关的一系列的操作。这就是我们string诞生的原因。
2.string类相关的使用方法
首先我们来一点一点的了解以下string当中相关的功能以便于日后我们手动实现他们。
在我们的string类当中一共存在以上的功能是不是这么看起来我们的string类的功能还是很齐全的呢?我们一个一个来详细的认识并了解。
Tips1 :constructor
constructor的意思是建造者,该部分的功能其实就跟命名一样,用于创建我们的字符串的。这一部分的功能类似于我们在类当中所使用的构造函数,为了使我们的使用更加的方便,所以在string类当中我们创建了很多的构造函数,让我们可以以多种形式创建字符串对象。如下图:
第一个参数为空的代表的就是我们的默认构造,也就是创建一个内容为空的字符串以便于我们以后的使用。如下图:
下面一个参数当中是一个字符串,所以代表的就是使用一个字符串直接构造我们的字符串,这样就可以省去我们后面的赋值等操作。如下图:
第三个表示的利用一个字符串当中的下标截取某一段的字符串进行创建显得字符串对象,使用方式如下图:
需要向大家介绍的是,假如我们使用这一部分进行新的字符串对象构建新的字符串的时候。第二个参数表示的是开始截取的下标的位置,第三个参数是向后面所截取的字符的个数,就像是我们上面代码中所展示的那样:从下标为6的位置开始(也就是 ‘w’ )一共向后截取5个字符,也就是得到(“world”)。
如果仔细观察的话我们会发现第三个构造函数存在一个默认参数,如果第三个参数被省略的话就会默认取得后面所有的字符进行创建新的字符串。如下图:
我们会发现就像是我们所介绍的那样取得了后面的所有的字符。
由于string类当中所需要使用的功能有很多,所以我们在介绍的时候只采用最常用的几种进行详细的介绍,那么最后我们再来看一种构造函数:
std::string s1(5,'*');
这个构造函数的功能就是使用后面的一个字符进行创建,产生长度为前面参数个的字符串的对象,有时候会产生意想不到的优势。代码如下图:
运行我们上面的代码,我们会发现产生了五个 ‘ * ’ 长度的字符串。那么经过上面的介绍我们最常用的构造函数就介绍完毕了,下面我们再来向大家介绍下一部分内容。
Tips2:destructor
destructor的意思就是析构函数,相信这样就很清楚了吧,这一部分就是string类的析构函数,其实这一部分也没什么好讲解的。通常情况下不需要我们自动调用string类的函数,系统会自动进行相应的处理。
Tips3:iterator
iterator的意思迭代器,通常用于修改或者遍历我们的数组。实质上迭代器的原理就类似于我们的指针,在类当中通常会记录下字符串的尾指针放在end当中,同样会记录下字符串的首指针放在begin当中。我们的迭代器就是通过判断首指针和尾指针是否相同进而判断遍历是否结束的。
想要创建一个可以正常接收迭代器类型的变量就需要在特定的作用空间域下面进行创建,例如begin和end的创建变量的方式为:
我们需要通过两层作用空间域进行指出目标。即 std::string::iterator 如果已经展开了作用空间域就不需要在重复指定。
一般情况下,每一个类当中都会存在一个特定的迭代器,我们只需要在使用的时候调用相应的迭代器即可。使用的方式如下:
我们创建一个新的迭代器变量it用于接收首指针,之后经过遍历和修改可以修改字符串当中的数据,将原本的 “abcdefg” 每一个字符都加一修改成为 “bcdefgh”。迭代器的优点就在于增加了string类的封装性,是我们不需要自己访问字符串就可以使用并遍历和修改字符串。
同样的我们要是想要反向的修改并访问字符串也是可以的,这个时候就需要使用到我们的rbegin和rend了。和begin和end的使用方法似乎完全相同,但是唯一不同的就是我们创建接收变量it的方式,对于rbegin和rend因为是反向输出并修改字符串所以我们就需要调用反向的迭代器进行操作。也就是通过 std::string::reverse_iterator 进行创建变量。代码示例如下:
我们会发现,创建好反向迭代器之后我们其他所有的使用和操作和普通的迭代器都是完全相同的。那么到此为止我们的迭代器相关的内容也就讲解完毕了。
Tips4:capacity
由于这一部分大多数都是针对于字符串进行一些简单的判断的操作所以我们直接可以从示例当中进行学习string类当中的相关内容的使用。示例代码如下:
#include<iostream>
#include<string>
int main()
{
//使用string类创建一个字符串对象,并验证capacity当中的各种操作
std::string s1("hello world");
std::cout << "缩小之前的字符串:" << s1 << std::endl << std::endl;
//返回字符串所占字节的大小
std::cout <<"最初的size:"<< s1.size() << std::endl << std::endl;
//返回字符串的长度
std::cout <<"字符串的长度:"<< s1.length() << std::endl << std::endl;
//返回字符串所能存储的最大的字符长度
std::cout <<"最大字符串的长度:"<< s1.max_size() << std::endl << std::endl;
//返回字符当前字符串的容量
std::cout <<"字符串的容量:"<< s1.capacity() << std::endl << std::endl;
//对于字符串的长度进行修改,小于目标值就扩容,大于目标值就缩小
s1.resize(20);
std::cout <<"调用resize之后修改完毕的size(扩大):"<< s1.size() << std::endl << std::endl;
s1.resize(5);
std::cout << "缩小容量之后的字符串:" << s1 << std::endl;
std::cout << "调用resize之后修改完毕的size(缩小):" << s1.size() << std::endl << std::endl;
//对字符串的容量进行修改
std::cout << "修改之前的字符串的容量:" << s1.capacity() << std::endl;
s1.reserve(55);
//存在内存对齐的现象,所以可能会比原有的空间大
std::cout << "修改之后的字符串的容量:" << s1.capacity() << std::endl<<std::endl;
//判断一个字符串是否为空
std::cout << "判断一个字符串是否为空:" << s1.empty() << std::endl << std::endl;
//清空一个字符串
s1.clear();
std::cout <<"清空之后的字符串:" <<s1 << std::endl;
std::cout << "清空之后再次进行判空操作:" << s1.empty() << std::endl << std::endl;
//自动调整size和capacity的大小
std::cout << "调整之前的字符串的大小:" << s1.size() << std::endl;
std::cout << "调正之前的字符串的容量:" << s1.capacity() << std::endl;
s1.shrink_to_fit();
std::cout << "调整之后的字符串的大小:" << s1.size() << std::endl;
std::cout << "调正之后的字符串的容量:" << s1.capacity() << std::endl;
return 0;
}
我们string类的创建其实类似于我们之前写过的顺序表,为了避免不必要的空间的浪费所以我们会创建一个capacity进行作为创建出的空间是否已满的标志。
在这一部分当中,size表示字符串所占空间的大小,capacity表示已经为字符串所开辟好的空间,length表示字符串的长度,max_size表示一个字符串所能占据的最大的空间,clear可以清空一个字符串,empty可以对一个字符串进行判空操作,如果字符串为空就返回1,不为空就返回0。这些只需要了解并会使用即可,并不难。
在这一部分需要着重进行介绍的是:reserve功能和resize功能。
reseve可以修改一个字符串的容量。也就是针对于空间的申请进行修改,(并不会改变字符串的大小)需要向其中传递一个参数,即新调整之后容量的大小。可能产生两种情况:修改的空间大于原本的容量,修改的空间小于原本的容量。
如果需要进行修改的空间大于原本的容量,那么我们就会重新进行内存申请,并修改容量。修改之后的空间可能等于我们修改的空间也可能大于我们修改的空间,因为可能存在内存对其的现象。
如果需要进行修改的空间小于原本的容量,那么之后的操作有可能不被响应,或者交给我们的编译器进行处理。依旧处于一种不可控的状态,所以我们使用reverse调整容量的时候最好向大进行调整,需要避免向小进行调整。
resize功能可以调整一个字符串的长度,是真正意义上作用于我们的字符串的。同样分为两种情况一种是将字符串的长度缩小,一种是将字符串的长度扩大。 对于resize同样需要我们传入一些参数如下图所示:
我们可以直接传入一个数字,表示修改空间的大小,还可以传入一个数字和一个字符用于补充该字符到完整的字符串大小。
当 n 的大小小于原本的字符串长度的时候就会自动删去一部分,删去之后的字符串的长度为n。
当 n 的大小大于原本的字符串长度的时候看起来没有任何改变,但是由于字符串变长了,会间接影响到capacity的大小,形成扩容的效果。如果不跟后面的字符的话会默认补充 '\0' 。运行效果如下:
Tips5:Element access
这一部分的功能就是对字符串进行指定的修改。就像是平时我们使用的指针类型的数组。我们可以通过解引用或者 [ ] 进行指定位置元素的读取或者修改。
就像我们之前说到过的,我们一般的操作符只可以作用于内置类型的数据不能作用于自定义类型的数据。所以 [ ] 不能直接作用于string类。要想修改特定位置的值我们就需要重载 [ ] 。我们可以尝试一下:
我们可以发现字符串当中下标为1的字符已经被修改成为了 * 。同样的,由于特殊的位置我们还特意设置了首元素的修改和尾元素的修改:
通过调用front和back函数我们可以发现,字符串当中的首元素和尾元素得到了修改。
在string类当中也有很多功能发生了重复,比如说at。我们实际的作用也是遍历字符串以及修改字符串所产生的,我们只需要进行简单的了解即可:
Tips6:Modifiers
这一部分的作用是修改,也是我们字符串处理当中最常使用的功能。比如像是字符串拼接,字符串数据的增加,尾插等一系列的操作。我们依次来认识一下:
+=操作和+操作字符串拼接
首先是将两个字符串拼接的操作符,由于自定义类型不能被相加或者相减所以这一部分照常使用到了我们的运算符重载。通过运算符重载之后就可以将自定义类型进行相加或者相减操作了。示例代码如下:
使用+拼接的字符串并不会对原字符串产生修改,所以我们需要创建一个新的空字符串进行接收拼接好的新字符串。而使用+=拼接好的字符串会对原字符串造成修改,所以我们只需要直接打印即可。
append字符串拼接
之后我们要介绍的是作用很相似的append函数。append函数的作用就是在字符串的后面拼接好另一个字符串。使用append拼接的内容会自动加入到字符串的末尾,不会对原字符串当中的内容造成覆盖。和我们的构造函数很像的是使用append函数拥有很多字符串拼接的形式。
其中包括使用一个新的string对象进行拼接,直接使用一个字符串进行拼接,使用某个字符串的一部分进行拼接,使用一个字符进行拼接等。由于在构造函数当中我们已经介绍过一遍了,所以我们在这一部分就不过多缀叙了。直接通过代码进行测试:
同样的使用部分拼接的时候使用的方式和构造函数当中的相同,参数可以省略, 但是作用是完全不同的,参数不省略的话就表示从第n个位置开始截取m个字符。如果省略的话就变成了默认从首元素开始截取,截取m个字符了。如上图所示。
push_back尾插操作
使用push_back可以实现字符串的尾插操作。参数为一个字符,作用就是将字符插入到字符串的末尾。代码如下:
操作如上图所示。
assign覆盖原字符串,创建新的字符串
我们在上面说到过:既然有直接增加不覆盖的字符串操作形式,那么就一定会有覆盖字符串的操作性是。assign的作用就是将原本字符串当中的内容覆盖掉,然后将我们新的字符串填入该字符串当中。其使用方式和append操作完全相同,只不过产生的效果不同。代码如下:
使用的方式和我们的append完全相同,只不过执行的效果不再是添加而是覆盖原对象而已。
insert在某个位置上插入指定字符串
既然有了头插和尾插的操作肯定会有在任意位置插入字符串的功能。我们的insert函数就是用来实现这一部分功能的。insert函数主要需要两个参数,一个是我们想要插入位置的下标,第二个参数就是我们想要插入的字符串。甚至我们还可以让我们的插入函数更加灵活,我们还可以插入指定字符串的一部分,传参的方式和我们之前使用的方式一样。代码示例如下:
erase删除某段字符串
erase的功能是删除字符串当中的指定的部分,通常情况下有两个参数,第一个是一个下标位置,第二个是要删除的元素的个数。这两个都有一个缺省值,第一个参数的缺省值是0,第二个参数是最大值。这也就是说,当我们不向函数传任何参数的时候,函数就会自动删除字符串的所有的内容。测试如下:
replace将某段字符串替换为另外一段字符串
replace的作用就是使用一个字符串替代原字符串当中部分内容,这个函数拥有很多的参数,使用的方法也多种多样,最多可以拥有五个参数。分别是(原字符串开始替换的下标,原字符串替换的长度,需要进行替换的字符串,需要替换字符串开始替换的位置,需要替换字符串的长度)。其中,最后两个针对于需要替换字符串的开始位置和替换长度可以省略,如果省略的话就代表替换使用全部字符串。测试代码如下:
swap交换两个字符串当中所存储的内容
这一部分其实可以被replace所替代,因为我们只需要指定好参数的话就可以实现指定部分的替换了。测试代码如下:
实际上就是交换两个字符串当中所存储的内容,只要会使用即可。
pop_back字符串的尾删操作
这个函数就是将我们字符串最后一个字符进行删除,可以简化我们代码的编写,因为针对于一个未知长度的字符串,要想删除最后一个字符需要先求出他的字符串的长度,这样会降低我们代码运行的效率。
Tips7:重要部分的使用演示
在这一部分我们将最后最常使用的几个部分一起向大家演示其使用的方法.
c_str的使用的方式即原因
c_str的作用就是将字符串对象当中存储的内容转换成为一个可以被编译器所接受的C语言形式的字符。也许string类的存储形式之前或许对大家来说很神秘,其实他就是一个字符串,一个由指针所指向的字符串,我们可以通过c_str得到这个指针,进而打印出相关的对象。测试代码如下:
实质上这也是我们自定义的类对象可以被cout输出的原因,据我们所知cout无法输出自定义的对象。但是可以输出内置类型的对象,因此我们只需要重定向一下cout输出,将输出的对象转化为字符串的输出打印即可,这个时候也会需要用到c_str函数的接口。
find查找成员函数的使用和rfind逆序查找成员函数的使用
这是最后较为重要的一部分内容了,在一个字符串对象当中我们想要查找一个对象的下标位置,我们就可以手动调用find成员函数进行查找。如果找到的话就会返回第一次匹配上的下标的位置。如果没有找到就会返回一个极大值。代码运行的效果如下:
但是我们可以发现一个弊端:那就是假如我们的字符串当中有好几个相同的内容呢?我们想要查找到的是中间的一部分,或者说是最后的那一部分呢?
想要查找中间的部分的话我们就需要手动增加参数了,这个成员函数最多可以有三个参数,分别表示所要匹配的字符串,想要开始匹配的位置,以及所要匹配字符串的长度。如果只有一个数字参数的话就会默认为所要开始匹配的字符串的下标,这样我们就可以查找到中间匹配的字符串啦。
对于最后部分的匹配的字符串,我们可以调用rfind成员函数进行直接从后向前的查找。示例代码如下:
那么此上就是我们本次博客的全部内容了,感谢您的观看。