C++ 标准模板库 STL 概述
泛型程序设计
C++ 的特点:
C++ 的核心优势之一就是便于软件重用,而软件的重用在 C++ 中主要体现在以下两个方面:
- 面向对象的思想:继承、多态和标准类库
- 泛型程序设计的思想:模板机制和标准模板库 STL
泛型程序设计:
泛型程序设计通俗地讲就是使用模板的程序设计方法。泛型程序设计中将一些常用的数据结构(例如链表、数组和二叉树等)和算法(例如排序和查找等)写成模板,这样在后续的使用中不管数据结构中存放的是什么数据对象,算法应用于什么类型的数据对象,都不需要重新实现数据结构和算法,极大的提高了代码重用性。
标准模板库 (Standard Template Library) 就是最为常用的数据结构和算法的模板集合,使用 STL 可以直接重用大多的标准数据结构和算法,而且能够获得相对较高的性能。
STL 中的基本概念
容器:可容纳各种数据类型的通用数据结构,都是类模板:
迭代器:可用于依次存取容器中的元素,类似指针
算法:用来操作容器中元素的函数模板,例如可以使用 sort()
来对一个 vector 中的数据进行排序,使用 find()
来搜索一个 list 中的对象。算法的实现和它操作的数据对象无关,所以可以将算法应用于几乎任何数据结构。
int array[100]; // 容器
int* parr = array; // 使用 int* 类型的指针变量作为迭代器
sort(parr, parr+70); // 使用 sort() 算法作用于该容器
容器
容器是可容纳各种数据类型(基本数据类型、对象等)的通用数据结构,都是类模板,分为三种类型:
- 顺序容器:vector, deque, list
- 关联容器:set, multiset, map, multimap
- 容器适配器:stack, queue, priority_queue
使用容器时需要注意两点:
-
对象被插入容器中时,被插入的是对象的一个复制品
-
有些容器本身是存在顺序关系的即是已排序的,所以放入容器的对象所属的类一般需要重载 == 和 < 运算符。STL 中相等
==
和大小<
的概念是可以自定义的,不局限于数值大小,示例如下:#include <iostream> #include <algorithm> #include <vector> using namespace std; class A{ int v; public: A(int n):v(n){ }; bool operator<(const A& a2) const{ cout << v << "<" << a2.v << "?" << endl; return false; } bool operator==(const A& a2) const{ cout << v << "==" << a2.v << "?" <<endl; return v == a2.v; } }; int main(){ A a[] = { A(1),A(2),A(3),A(4),A(5)}; cout << binary_search(a,a+4,A(9)); // 折半查找 return 0; } /*output: 说明 binary_search()方法没有使用 == 运算符只使用了< 3<9? 2<9? 1<9? 9<1? 1 */
顺序容器:
顺序容器是非排序的,而且其元素的插入位置与元素自身的值无关,数组便于查找,链表便于操作。
- vector
#include <vector>
一维动态数组:其元素在内存中是连续存放的,随机存取任何元素都可以在常数时间内完成,在该容器的尾部增删元素也几乎能够在常熟时间内完成具有较好的性能。 - deque
#include <deque>
双向队列:其元素在内存中是连续存放的,随机存取任何元素都可以在常数时间内完成,在该容器的两端增删元素也几乎能够在常熟时间内完成具有较好的性能。 - list
#include <list>
双向链表:其元素在内存中是不连续存放的,不支持随机存取,在该容器的任何位置增删元素几乎都能够在常熟时间内完成具有较好的性能。
关联容器:
关联容器的元素是排序的,插入元素需要根据相应的排序规则来确定插入位置的,在查找时具有较好的性能。关联容器通常使用平衡二叉树实现,其插入和检索的时间都是 O ( l o g ( N ) ) O(log(N)) O(log(N)) 。
- set/multiset
#include <set>
集合:set 集合中不允许存在相同的元素,multiset 集合中允许存在相同的元素。 - map/multimap
#include <map>
键值对集合:map 和 set 的不同在于前者存放的元素有且仅有两个成员变量(first,second)
,一个名为first
,另一个名为second
,first
的值用来对整体元素进行从小到大的排序,并可以通过first
快速检索元素。和 multiset 类似 multimap 和 map 的区别中允许存在相同first
值的元素。
容器适配器:
- stack
#include <stack>
栈:栈是项都有限序列,并满足序列中被删除、检索和修改的项都只能是最近插入序列的项,即栈顶的项。满足后进先出规则 - queue
#include <queue>
队列:(入队)插入只允许在尾部进行,(出队)删除、检索和修改只允许在头部进行。满足先进先出规则 - priority_queue
#include <queue>
优先级队列:优先级最高的元素总是第一个出队列
顺序容器和关联容器的成员函数:
成员函数 | 函数作用 |
---|---|
begin() |
返回指向容器中第一个元素位置的迭代器 |
end() |
返回指向容器中最后一个元素后面的位置的迭代器 |
rbegin() |
返回指向容器中最后一个元素位置的迭代器 |
rend() |
返回指向容器中第一个元素前面的位置的迭代器 |
erase() |
从容器中删除一个或几个元素 |
clear() |
从容器中删除所有元素 |
顺序容器的常用成员函数:
成员函数 | 函数作用 |
---|---|
front() |
返回容器中第一个元素的引用 |
back() |
返回容器中最后一个元素的引用 |
push_back() |
在容器末尾增加新元素 |
pop_back() |
删除容器末尾的元素 |
erase() |
删除迭代器指向的元素,或删除一个区间,返回被删除元素后面的那个元素的迭代器 |
迭代器
迭代器的用法和指针类似,用于指向顺序容器和关联容器中的元素。迭代器分为 const 和非 const 两种,通过迭代器可以读取它指向的元素,通过非 const 迭代器可以修改其指向的元素。
容器类的迭代器的定义方式如下,并使用 *
运算符访问迭代器所指向的元素:
容器类名::iterator 变量名; // 非const迭代器
容器类名::const_iterator 变量名; // const迭代器
* 迭代器变量名;
迭代器上可以执行 ++
操作来移动迭代器,使其指向容器中的下一个元素。如果迭代器到达了容器中的最后一个元素的后面,此时在使用该迭代器就会出错,类似于使用了未被初始化的指针。
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> v; // 空数组
v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4);
vector<int>::const_iterator cit; // const 迭代器
for(cit = v.begin(); cit != v.end(); ++cit){
cout << *cit << ',';
}
cout << endl;
vector<int>::reverse_iterator rit; // 反向迭代器
for(rit = v.rbegin(); rit != v.rend(); ++rit){
cout << *rit << ',';
}
cout << endl;
vector<int>::iterator it; // 非 const 迭代器
for(it = v.begin(); it != v.end(); ++it){
*it += 1;
cout << *it << ',';
}
cout << endl;
return 0;
}
双向迭代器:
可以使用 ++/--
运算符使迭代器指向容器中的下一个元素或上一个元素,同样也使用 *
运算符取迭代器所指向的元素。除此之外,双向迭代器可以用其他双向迭代器进行赋值 =
,和与其他双向迭代器进行相等判定 ==/!=
随机访问迭代器:
除了具备双向迭代器的所有操作,随机访问迭代器还可以指定步长随机移动,例如 p +=/-= i
表示将该迭代器向后或向前移动 i
个元素;p +/- i
表示指向该迭代器之后或之前的第 i
个元素的迭代器;p[i]
表示该迭代器之后的第 i
个元素的引用。除了双向迭代器的相等判定之外,随机访问迭代器可以进行大小判定 <, <=, >, >=
。
容器和迭代器:
容器 | 容器上的迭代器类别 |
---|---|
vector |
随机访问迭代器 |
deque |
随机访问迭代器 |
list |
双向迭代器 |
set/multiset |
双向迭代器 |
map/multimap |
双向迭代器 |
stack |
不支持迭代器 |
queue |
不支持迭代器 |
priority_queue |
不支持迭代器 |
容器支持的迭代器也直接限制其支持的算法,有的算法需要使用随机访问迭代器访问容器中的元素,例如 sort(), binary_search()
算法,那么 list 以及关联容器就不支持该算法。
算法
STL 中提供了能在各种容器中通用的算法,算法就是函数模板集合,大多数算法都定义在头文件 <algorithm>
中,算法不仅仅只可以对容器进行操作,也可以处理普通数组。
算法通过迭代器来操作容器中的元素,一些算法不仅可以操作整个容器中的元素,也可以只操作容器中的一个局部区间,例如排序和查找算法,所以这类算法一般会带有两个参数,即起始元素的迭代器和终止元素的后一个元素的迭代器。有的算法的返回值是一个迭代器,例如 find()
算法的作用就是在容器中查找一个元素,并返回指向该元素的迭代器。
template <class Inlt, class T>
Inlt find(Inlt first, Inlt last, const T& val); // 查找区间左闭右开 [first, last),在该区间中找到等于 val 的元素并返回指向该元素的迭代器
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main(){
int array[10] = {
10,20,30,40};
vector<int> v;
v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4);
vector<int>::iterator it;
it = find(v.begin(), v.end(), 3);
if(it != v.end()){
cout << *it << endl;
}
// find() 没有找到元素时,返回容器末尾迭代器
it = find(v.begin(), v.end(), 9);
if(it == v.end()){
cout << "not found" << endl;
}
it = find(v.begin()+1,v.end()-2,2); // 完整容器:[1,2,3,4],查找的局部区间:[2,3)
if(it != v.end()){
cout << *it << endl;
}
// 算法操作普通数组
int *parr = find(array,array+4,20);
cout<< *parr << endl;
return 0;
}
转载:https://blog.csdn.net/qq_41773806/article/details/115770653