标准模板库(Standard Template Library,STL)是惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和David RMusser在惠普实验室工作时所开发出来的。虽说它主要表出现到C++中,但在被引入C++之前该技术就已经存在了很长时间。STL的代码从广义上讲分为三类:算法、容器和迭代器,细分的话还应当加上仿函数、空间配置器和配接器。几乎所有的代码都采用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。
上一篇博客我们讲述了模板,通过引入模板,C++ 引申出了泛型编程技术。简单的理解泛型编程,即使用该技术编写的代码,可以支持多种数据类型。也就是说,通过泛型编程,能编写出可重复利用的程序代码,并且其运行效率和针对某特定数据类型而设计的代码相同。由此可见,C++ 很需要泛型这种新的编程模式,可以减轻编程的工作量,增强代码的重用性。
在 C++ 支持模板功能,引入了泛型编程思想的基础上,C++ 程序员们想编写出很多通用的针对不同数据类型的算法,其中 STL 脱颖而出成为 C++ 标准,并被引入 C++ 标准程序库。
STL 是一个具有高度可用性、高效的模板库,该库包含了诸多在计算机科学领域中常用的基础数据结构和算法,掌握了 STL 标准,很多功能就无需自己费心费力的去实现了(不用重复的造轮子),直接拿来用即可。
总的来说,STL 模板库是 C++ 标准程序库的重要组成部分,为 C++ 程序员提供了大量的可扩展的程序框架,高度实现了代码的可重用性,并且它是内置的,不需要额外安装,使用非常方便。
后面很长的一段时间,我们将介绍STL容器,包含string、vector、list、stack、queue…
string是C++、java、VB等编程语言中的字符串,字符串是一个特殊的对象,属于引用类型。 在java、C#中,String类对象创建后,字符串一旦初始化就不能更改,因为string类中所有字符串都是常量,数据是无法更改,由于string对象的不可变,所以可以共享。对String类的任何改变,都是返回一个新的String类对象。 C++标准库中string类以类型的形式对字符串进行封装,且包含了字符序列的处理操作。
大部分小伙伴应该都是从C语言入门来的,应该知道C语言中有一个char的字符类型,在那个时候我们使用char[]即字符数组的形式来表达一个字符串,那这个字符串和sting有什么区别呢?C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP思想(面向对象编程),而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
string的出现让人能够以更简单、更方便、更快捷的方式来操作字符串。
在VS2019下,我们创建一个string变量然后转到string定义
可以看到string是basic_string实例化出的一个对象,那这个地方的basic_string又是个什么东西呢?我们再次选中转到定义:
这里可以看出basic_string实际上是一个类模板,string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits
和allocator作为basic_string的默认参数 。有关basic_string的具体实现这里就不再过多介绍,有兴趣的小伙伴可以自行了解。
这只是我们在VS中看到的现象,那怎么知道具体的细节呢?这个地方我们可以去C++官网或者cplusplus网页上查看string的具体实现。
我们一共查询到了7个String提供的默认构造函数,目前我们只需要了解其中3个常用并且掌握即可,其他几种默认构造函数可以了解。
上述的三种构造函数实际上就是简单的无参构造、带参构造以及拷贝构造、具体实例可查看下述代码:
#include
#include
using namespace std;
int main()
{
//无参构造
string s1;
//带参构造
string s2("hello");
//拷贝构造
string s3(s2);
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
return 0;
}
其中,关于带参构造的s2也可以写成:
string s2 = "hello";
为了验证我们直接运行下述代码:
#include
#include
using namespace std;
int main()
{
//无参构造
string s1;
//带参构造
string s2("hello");
string s4 = "hello";
//拷贝构造
string s3(s2);
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
return 0;
}
可以看到s2 s3 s4都成功输出了hello
1、[]+下标
int main()
{
string s1;
string s2("hello");
for (size_t i = 0; i < s2.size(); i++)
{
cout << s2[i] << " ";
}
return 0;
}
这种方式是最简单也是最容易理解的了(对于有一定C语言基础或者知道数组的小伙伴),就像访问一个数组一样即可。但是这个地方需要注意的是,这里的[]实际上是一个函数,其全程为operate[],即上述代码中的s2[i]在底层实际上会转化为:
s2.operate[](i)
即调用了operate[]这个函数,查询文档可以看到
operate[]的返回值是一个引用,这个时候有意思的地方就来了,引用返回说明了我们不仅可以读取里面的数据,还可以修改里面的数据,具体操作如下:
int main()
{
string s1;
string s2("hello");
for (size_t i = 0; i < s2.size(); i++)
{
s2[i] = 'x';
}
for (size_t i = 0; i < s2.size(); i++)
{
cout << s2[i] << " ";
}
return 0;
}
可以看到s2里面的数据已经被修改了。
当然,当string定义为const对象时,operate[]也会重载为const,即无法修改值了。
2、迭代器
同样先给出代码:
int main()
{
string s1;
string s2("hello");
//迭代器
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
it++;
}
return 0;
}
这里需要注意的是迭代器的区间实际上是左闭右开的,其原因是因为begin是返回的第一个元素位置,但是end返回的是最后一个数据的下一个位置,所以迭代器遍历的区间实际上是在[begin(),end()),实际上C++中凡是给迭代器的区间实际上都是左闭右开的的区间,后续的容器都是这样。其次迭代器是类似指针的一个东西,具体的话等下一篇模拟实现string的时候再谈吧,迭代器可能是指针也可能不是,但它的用法就是模拟的指针。
迭代器的意义:像string,vector支持[]遍历,但是list、map等容器不支持[],我们就要使用迭代器遍历,可以理解为迭代器才是统一使用的方式,[]的遍历方式只是极个别容器的特殊遍历。
3、C++11提供的范围for
int main()
{
string s1;
string s2("hello");
for (auto e : s2)
{
cout << e << " ";
}
return 0;
}
语法意义在于依次取容器中的数据赋值给e自动判断结束。后续模拟实现的时候可以知道实际上就是迭代器。需要注意的是如果需要修改的话e就需要定义为引用类型。(范围for的方式实际上可以应用到所有可以用迭代器的容器)
建议三种方式都要掌握!!!
这个接口其实看名字就应该知道实际上就是尾插,在末尾添加一个字符。
我就不另写代码了,直接用上述的代码添加push_back看结果:
int main()
{
string s1;
string s2("hello");
s2.push_back('x');
s2.push_back('x');
s2.push_back('x');
for (auto e : s2)
{
cout << e << " ";
}
return 0;
}
这个说实话适用性不是很高,一个字符一个字符插入未免有些呆,所以有了下面一个函数:
int main()
{
string s1;
string s2("hello");
s2.push_back('x');
s2.push_back('x');
s2.push_back('x');
s2.append("aaa");
for (auto e : s2)
{
cout << e << " ";
}
return 0;
}
跟上一个代码区别只添加了append语句:
这样相比上一个push_back一个一个插入方便了许多,但是吧,C++是支持函数重载以及运算符重载的,实际上string已经重载了+=运算符,所以s2.append(“aaa”)等价于s2+="aaa"并且+=当空间不足时自动扩容,正确与否小伙伴自行测试一下吧(懒得测试了)
上述的两个接口都只支持在尾部添加,那如果我想在最前面插入或者中间插入呢?这里就需要使用insert了:
但是insert提供了7个重载版本,但是原理都是差不多的,基本上都是在指定位置或者区间插入一个字符串。需要注意的是,尽量少用insert,应该有些小伙伴接触过数据结构并且手动实现过顺序表的插入方式,其原因就在于在执行insert时底层实现的数组需要挪动数据。