STL Standard Template Library - 标准模板库又称 C++ 泛型库
C++ STL(标准模板库)是一套功能强大的 C++模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。
c++标准模板库的核心包括以下三个组件:
组件 | 描述 |
---|---|
容器(Containers) | 容器是用来管理某一类对象的集合。c++提供了不同类型的容器,比如queue、vector、map等 |
算法(Algorithms) | 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换登操作 |
迭代器(iterators) | 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集 |
这三个组件都带有丰富的预定义函数,帮助我们通过简单地方式处理复杂的任务。
C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、排序、删除、查找等。
迭代器是一种检查容器内元素并遍历元素的数据类型。C++更趋向于使用迭代器而不是下标操作,因为标准库为每一种标准容器(如vector)定义了一种迭代器类型,而只有少数容器(如vector)支持下标操作访问容器元素。
使用方法: 容器类型<变量类型>::iterator 迭代器名;
vector<int>::iterator p;
//p就是储存int 类型数据的vector容器的迭代器
迭代器的遍历
vector <int> v;
vector<int>::iterator p;
for(p=v.begin();p!=v.end();p++)
cout << *p <<' ';
可以看到迭代器指向的元素是与指针类似,可以使用 “++” 自增运算符。但是注意,迭代器只能使用 p++/p-- 不能使用 ++p/- -p 。因为 p++ 实际上是一个特殊的操作,相当于链表中的 p = p->next 在迭代器操作中的简写。而 p 本身也可以用 *p 的形式来表示指向的元素,包括后面的【映射容器】中有两个元素,也可以使用 p->first 和 p->second 来指向其中的内容,与结构体指针也是有异曲同工之妙,关于多个内容的容器,后续会有具体的例子
迭代器实质上是指针,也就是指向的数据的地址,因此会有【迭代器地址】的概念。
但是这个迭代器是有储存类型的,因此并不与普通的“指针变量”完全通用,只能说“形式上类似”,“实质上相同”。再次强调,使用方法是不完全相同的。
向量(vector)是一个封装了动态大小数组的顺序容器,你可以将 vector 容器理解为一个高级的数组,与数组一样能够通过下标来随机存取,但并不需要提前声明容器容量(当然也可以提前声明)。跟其他类型的容器一样,它能够存放各种类型的对象。可以简单地认为,向量是一个能够存放任意类型的动态数组。
什么是方法
“方法”是面对对象思想中的一个成分,与“函数”类似,也有传入变量,返回变量的定义。“函数”面对的是整个程序的使用,“方法”针对的是“对象”的操作
“对象”也可以理解为一个“变量”,“对象”可以使用的“专属的函数”就是“方法”
函数:函数名(参数);
方法:对象变量名.方法名(参数);
v.push_back(e); //从容器末端追加一个元素,容器末端自动扩张
v.begin(); //返回向量容器的首地址,也就是第一个元素的迭代器地址
v.end(); //返回向量容器的尾地址,注意尾地址是最后一个元素的下一个位置的迭代器地址
v.insert(v.begin() + n, e); //往指定地址的位置插入一个元素,如例:在 n 号元素**前**插入num
v.erase(p0[, p1]); //删除一个元素,或删除一段元素,p 为迭代器地址
v.size() //返回 v 的大小(元素个数)
v.empty() //如果容器为空,返回“真”,如果不为空,返回“假”
v.clear() //清空容器内容
迭代器遍历,如上例;
string 是 C++ 中直接提供的一种新的【数据类型】,字符串变量不同于字符数组构成的字符串,string 型的变量不需要提前定义长度,也就是说它的长度是 可变的
它是一种顺序表的结构,元素是char类型的字符使用 null 字符 ‘\0’ 终止的一维字符数组。
string 型变量的使用并不需要特殊的头文件,但是一部分string字符串的操作需要头文件 #include< string >
s.append(str); //从字符串末端追加一个字符串,注意只能是字符串不能是字符
s.begin(); //返回字符串的首地址,也就是第一个字符的迭代器地址
s.end(); //返回字符串的尾地址,也就是最后一个字符的下一个位置的迭代器地址
s.insert(p, str); //往指定位置插入一个字符串,p 是整数下标
s.insert(p, c); //往指定迭代器位置插入一个字符,p 是迭代器
s.erase(p0[, p1]); //删除迭代器所指的元素,或删除一个区间的所有元素,左闭右开
s.replace(step, len, str); //从 step 开始,将写下来的 len 个字符替换成一个字符串 str。step 和 len 都是整数,str 是字符串。
s.compare(str); //比较两个字符串大小,两个字符串相等返回 0 ;s < str 返回一个负数;s > str 返回一个正数
s.find(c/str); //返回字符串 s 中第一次出现字符 c 或字符串 str 的下标值。如果没有找到返回 -1
s.length(); //返回字符串的长度
s.empty(); //判断字符串是否为空
s.clear(); //清空字符串,但通常直接赋值空字符串来清空字符串即可: s = "";
s.c_str(); //返回一个与 C 语言中的字符数组同类型的指针,使 string 类型的变量可以实用 C 中的 printf() 函数和 sscanf() 函数
s1 < / <= / > / >= / == / != s2 //两个字符串可以进行逻辑判
s1 = s2 //字符串之间可以互相赋值
s1 += s2 //在 s1 尾部追加字符串(或字符)
s1 = s2 + s3 //两个字符串可以相加,结果与顺序有关,如例:s2 在前面 s3 在后面
字符数组可以直接赋给string变量,但是反过来不可以
char s2[2000] = "abcd";
string s1 = s2;
string字符串的遍历
string s;
for(i=0;s[i]!='\0';i++) //这是我常用的遍历的方法
for(char c : s) //高级for循环遍
int main()
{
int i,j,k;
string s; //定义一个字符串
s.append("lidonghai"); //在字符串末端添加一个字符串,append只能添加字符串
cout <<s<<endl;
s.insert(0,"i`m"); //在指定位置前插入一个字符串
cout <<s<<endl;
s.insert(s.begin()+3,' '); //在指定迭代器位置插入一个字符串
cout <<s<<endl;
s.erase(s.begin() + 2); //删除指定迭代器位置的字符
cout << s << endl;
s.erase(s.begin() + 2, s.begin() + 6); //删除指定迭代器区间的字符
cout << s <<endl;
s.replace(1, 3, "ooo"); //从 1 号元素开始,将连续的 3 个字符替换成一个字符串 "ooo"
cout <<s <<endl;
k =s.length(); //获取字符串长度并赋给 k
cout << "find 'a' = " << s.find('a') << endl;//查找字符返回下标
cout << "find \"abc\" = " << s.find("5645") << endl; //查找字符串返回首个下标,若无则返回随机数
if (s.find("def") == -1)
cout << "Not find" << endl; //查找固定
else cout << "OK" << endl;
string::iterator p; //声明一个字符串的迭代器
for (p = s.begin(); p != s.end(); p++) //迭代器循环遍历
cout << *p<<' ';
cout << endl;
for(i=0;s[i]!='\0';i++)
cout << s[i]<<' '; //下标循环泵遍历
return 0;
}
运行结果
lidonghai
i`mlidonghai
i`m lidonghai
i` lidonghai
i`onghai
ioooghai
find 'a' = 6
find "abc" = 4294967295
Not find
i o o o g h a i
i o o o g h a i
堆栈是一个线性表,插入和删除只在表的一端进行。就像一个一个桶,只能从桶口往里放或者把最接近桶口的物品拿出。stack堆栈是一个 后进先出的数据存储结构,元素只能从栈顶入栈,从栈顶出栈,只能读取栈顶的元素
需要头文件 #include < stack >
s.push(e); //从栈顶将元素入栈
s.pop(); //将栈顶元素出栈
s.top(); //读取栈顶元素
s.empty(); //判断队列是否为空
栈不可迭代遍历!!!所以不能使用begin() 和end()方法
#include
#include
using namespace std;
int main() {
stack<int> s; //定义一个空栈
s.push(0);
s.push(1); //从栈顶入栈 3 个元素
s.push(2);
cout << s.top() << endl;//读取栈顶元素
s.pop(); //将栈顶出栈
return 0;
}
队列是一个【先进先出(First In First Out,FIFO)】的储存结构,元素只能从队尾插入,只能从队首删除。
queue与stack模版非常类似,queue模版也需要定义两个模版参数,一个是元素类型,一个是容器类型,元素类型是必要的,容器类型是可选的,默认为dqueue类型。
需要头文件 #include < queue >
q.push(); //后端入队
q.pop(); //前端出队
q.front(); //读取队首元素
q.back(); //读取队尾元素
q.empty(); //判断队列是否为空
队列不可迭代遍历!!!所以不能使用begin() 和end()方法
#include
#include
using namespace std;
int main ()
{
queue<int> q; //声明一个空的队列容器
q.push(0); //从尾部插入元素 0
q.push(1); //从尾部插入元素1
cout << q.front() << endl; //读取队首元素
q.pop(); //最前端的元素出队
cout << q.front() << endl; //读取队首元素
return 0;
}
运行结果为
0
1
顾名思义是一个可以从两端进行出入操作的队列。内部数据存储结构与 vector 相同,采用线性表顺序存储结构,也可以用下标进行存取。
deque块在头部和尾部都可以插入和删除。而不需要移动任何元素,而不需要移动其他元素(使用push_back()方法在尾部插入元素,会扩张队列,而使用push_front()方法在首部插入元素和使用insert()方法在中间插入元素,只是将原位置上的元素进行覆盖,不会增加新元素)一般来说,当考虑到容器元素的内存分配策略和操作的性能时deque相当于vector更有优势。
deque采用分块的线性存储结构来存储数据,每块的大小一般为512B,将其之称为deque块,所有的deque块使用一个map块进行管理,每个map数据项记录各个deque块的首地址,这样的话,deque块在头部和尾部都可以插入和删除。
deque <int> d; //int是容器内元素变量的类型,也可以是其他
vector<double> d(8, 2.33); //定义一个 d ,容量是 8 ,每个元素的值是2.33
d.push_back(e); //后端插入元素e,尾部扩充
d.push_front(e) //前端插入元素e,头部扩充
d.pop_back(e); //尾部出队
d.pop_front() //前端出队
d.back(); //读取队尾元素
d,front(); //读取队首元素
d.insert(p,e); //在迭代器位置之前插入,p是迭代器
d.erase(p,[p1]); //删除迭代器位置或一段区间中的元素
int main()
{
deque <int> d;
d.push_back(0);
d.push_back(1); //后端插入
d.push_back(2);
for(int i=0;i<d.size();i++)cout <<d[i]<<' '; //下标方式遍历查看
cout <<'\n';
d.push_front(4);
d.push_front(5); //前端插入
d.insert(d.begin()+2,2); //中间插入
deque<int>::iterator p; //定义迭代器
for(p=d.begin();p!=d.end();p++) //迭代器遍历查看
{
cout << *p << ' '; cout << endl;
}
d.erase(d.begin()+2); //删除第二个元素;
}
运行结果
0 1 2
5 4 2 0 1 2
5 4 0 1 2
set是关联式容器,set作为一个容器也是个用来存储同一类数据类型的数据集。
在其中每个元素的值都唯一,而且系统能根据元素的值自动排序
set 集合容器实现了红黑树(Red-Black Tree)的平衡二叉检索树的数据结构,在插入元素时,set 容器会自动调整二叉树,生成一个左子节点小于根节点小于右子节点的排序树,并且保证左右子树高度相同,以获得最快的检索速度。
set 容器中不能储存重复的元素,如果插入已经存在的元素,将会没有效果,multiset可以插入重复的元素。
C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树
需要头文件 #include < set >
set<T> s; // T 是容器内元素的变量类型
multiset<T> ms; // T 是容器内元素的变量类型
//set 容器的方法
s.insert(e); //将 e 加入集合,自动排序,默认升序,不可重复插入
s.erase(k); //删除键值为 k 的元素,返回删除元素总数(只有 0 或 1 )
s.find(k); //查找键值为 k 的元素的位置,返回迭代器地址,若没找到返回 s.end()
//multiset 容器的方法
ms.insert(e); //将 e 加入集合,自动排序,默认升序,可重复插入
ms.erase(k); //删除所有键值为 k 的元素,返回删除元素总数
ms.find(k); //查找第一个键值为 k 的元素的位置,返回迭代器地址,若没找到返回 ms.end()
find会挨个查找set,当到达set.end()时,也就是一个也没找到,返回end
int main()
{
set<int> s; //声明一个set容器 s
s.insert(2); //插入元素
s.insert(1); //插入元素
s.insert(5); //插入元素
s.insert(2); //重复插入,无效
set<int>::iterator p; //定义set的迭代器 p
for(p=s.begin();p!=s.end();p++)
{
cout <<*p<<' '; //迭代器遍历
}
cout <<endl;
cout << s.erase(3)<<endl; //删除元素3,若存在并删除返回1,否则返回0
if(s.find(2)==s.end())
cout <<"not found 2"<<endl; //查找元素2,找到返回迭代器位置,没找到返回 set 容器尾地址
else cout <<"found "<<*s.find(2)<<endl;
return 0;
}
运行结果
1 2 5
0
found 2
映照容器是居于数据对的,可以通过 键 ,来索引到 值 的容器,其中的元素是以 键-值对 的形式存储的(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值).
这里说下map内部数据的组织,map内部是自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的(默认升序)。
对于map中的每个节点存储的是一对信息,包括一个键和一个值,各个节点之间的键值不能重复。
什么是“对(pair)”
pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。 pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。
需要头文件 #include < map >
//因为 map 储存的是 [键-值对] 所以要定义两个数据类型
map<T, T> m; // T 是容器内元素的变量类型
multimap<T, T> mm; // T 是容器内元素的变量类型
//map 容器的方法
m.insert(pair<T, T>(k, v)); //将一个键-值对 k: v 加入集合,自动按键值排序,默认升序,不可重复插入
m.erase(k); //删除键值为 k 的元素,返回删除元素总数(只有 0 或 1 )
m.find(k); //查找键值为 k 的元素的位置,返回迭代器地址,若没找到返回 m.end()
//multimap 容器的方法
mm.insert(pair<T, T>(k, v)); //将一个键-值对 k: v 加入集合,自动按键值排序,默认升序,可重复插入
mm.erase(k); //删除所有键值为 k 的元素,返回删除元素总数
mm.find(k); //查找第一个键值为 k 的元素的位置,返回迭代器地址,若没找到返回 mm.end()
m[k] = v; //可以直接使用这种方式来插入新的键值对,如果此键已经存在则会修改此键对应的值
int main()
{
map <int ,string> m; //声明一个map类型的容器m
m.insert(pair<int,string>(2000,"ldh"));
m.insert(pair<int,string>(915,"ngup")); //insert() 方法插入 4 个 键-值对
m.insert(pair<int, string>(10, "Shell"));
m.insert(pair<int,string>(2000,"ldh")); //在map中重复插入无效
m[12] = "ace"; //使用 键 - 值 的方式直接添加新元素
m[915] = "ng"; //通过键坐下标,访问元素并直接改变对应值
map<int,string>::iterator p; //声明一个map的迭代器
for (p=m.begin();p!=m.end();p++) //迭代器类型要对应
cout <<(*p).first<<" : "<<(*p).second<<endl;//使用迭代器遍历查看元素
cout << m.erase(915) << endl;//删除键值为 915 的元素,返回删除元素总数(只有 0 或 1 )
//通过键值查找元素,找到返回迭代器位置,没找到返回 map 容器尾地址
if (m.find(16) == m.end()) cout << "Not found 16" << endl;
else cout << "Found " << (*m.find(16)).second << endl;
if (m.find(12) == m.end()) cout << "Not found 12" << endl;
else cout << "Found " << (*m.find(12)).second << endl;
return 0;
}
运行结果
10 : Shell
12 : ace
915 : ng
2000 : ldh
1
Not found 16
Found ace
上面介绍的所有容器都适用,使用方法 容器变量名.empty() 如果容器为空,返回布尔值(bool)真(true),如果容器不为空返回假(false).
上面介绍的所有容器都适用,使用方法 容器变量名.size() 可以获取容器内的元素的个数,字符串变量 string 可以获取字符串长度。
上面介绍的所有容器除了 栈和队列 都适用,使用方法 容器变量名.clear() 可以将容器内的内容全部清空。
上面介绍的所有容器除了 栈和队列 都适用,使用方法 容器变量名.begin() 容器变量名.end() 可以获得容器的首地址,尾地址。
栈和队列不能使用有些方法的原因是,它们并不是【可迭代对象】。
STL基础部分完结,感谢H_on学长提供的模板~