STL(Standard Template Library)的设计理念是基于泛型编程,即通过模板类和函数实现通用的数据类型和算法,使得程序员能够更加方便地实现各种数据结构和算法。STL的设计思想有三个核心要点:容器、算法和迭代器,其中容器是中心,算法和迭代器是围绕容器开发的。
在C++ STL中,容器可以被视为一种数据结构,它们用于存储和管理一定数量的同类数据。容器为数据提供了一种组织方式,它们保持着数据的结构和顺序,并提供了一系列对数据进行操作的接口。
1.STL容器种类
C++STL中的容器大致可以被分类为三类:序列容器(Sequence Containers)、关联容器(Associative Containers)和无序关联容器(Unordered Associative Containers)。
序列容器:序列容器是一种线性数据结构,包括vector、deque、list、forward_list和array。这些容器中的元素都有确定的顺序,可以通过位置直接访问。
关联容器:关联容器是一种非线性数据结构,包括set、multiset、map和multimap。这些容器中的元素是根据键(key)来排序和访问的。
无序关联容器:无序关联容器也是一种非线性数据结构,包括unordered_set、unordered_multiset、unordered_map和unordered_multimap。这些容器中的元素是通过哈希函数进行组织,因此元素的顺序是不确定的。
2.STL容器特性
每种容器都有其独特的特性,适用于不同的应用场景。
序列容器:vector是最常用的动态数组,适用于需要频繁访问元素但不需要经常在中间插入和删除元素的场景。deque是一个双端队列,可以在首尾两端高效地插入和删除元素。list和forward_list是链表,支持在任意位置高效地插入和删除元素。array是静态数组,其大小在编译时就需要确定。
关联容器:set和map在内部通过红黑树实现,能保证元素的有序性。set中的元素都是唯一的,而map则是存储键值对,键是唯一的。multiset和multimap则允许键的重复。
无序关联容器:unordered_set和unordered_map使用哈希表实现,能在平均情况下实现常数时间复杂度的查找。与set和map相似,unordered_set中的元素唯一,而unordered_map存储键值对,键是唯一的。unordered_multiset和unordered_multimap则允许键的重复。
3.常用容器用法
3.1.vector、list、deque
(1) vector(向量容器),可以从末尾快速地插入与删除元素,快速的随机访问元素,但是中间插入、删除元素较慢,因为需要移动插入或删除位置后面的所有元素。
vector<int> v1{10,2}; //初始化赋值
empty(); //判断当前向量容器是否为空
size(); //返回当前向量容器中的实际元素个数
[]; //返回指定索引的元素
reserve(n); //为当前向量容器预分配n个元素的存储空间
capacity(); //返回当前向量容器在重新进行内存分配以前所能容纳的元素个数
resize(n); //调整当前向量容器的大小,使其能容纳n个元素
push_back(item); //在当前向量容器尾部添加一个元素
insert(pos, elem); //在pos位置插入元素elem,即将元素elem插入到迭代器pos指定的元素之前
front(); //获取当前向量容器的第一个元素
back(); //获取当前向量容器的最后一个元素
erase(iter, pos); //删除当前向量容器中某个迭代器或者迭代器区间指定的元素
clear(); //删除当前向量容器中的所有元素
begin(); //该函数的两个版本分别返回iterator,引用容器的第一个元素
end(); //该函数的两个版本分别返回iterator,引用容器的最后一个元素后面的一个位置
rbegin(); //该函数的两个版本分别返回reverse_iterator,引用容器的最后一个元素
rend(); //该函数的两个版本分别返回reverse_iterator,引用容器的第一个元素前面的第一个位置
emplace_back(); // 该方法采用完美转发,所以插入字面值如myv.emplace("hello")效率比push_back高很多,只要没有遭遇完美转发的限制就可以通过emplace_back传递任意型别的任意数量和任意组合的实参
e.g.:
int main() {
std::vector<int> vec1;
vec1.push_back(1);
vec1.push_back(2);
vec1.push_back(3);
vec1.emplace_back(4); // 效率高,不会涉及任何临时对象
// 正向输出所有元素
vector<int>::iterator it;
vec1.insert(vec1.begin(), 0);
for (it = vec1.begin(); it != vec1.end(); ++it)
{
std::cout << *it << std::endl;
}
// 反向输出所有元素
vector<int>::reverse_iterator it1;
for (it1 = vec1.rbegin(); it1 != vec1.rend(); it1++)
{
std::cout << *it1 << std::endl;
}
return 0;
}
(2) list是双向链表类模板,它的每个结点之间通过指针连接,不能随机访问元素,为了访问表容器中特定的元素,必须从第一个位置开始,随着指针从一个指向下一个元素,直至找到要找的元素;但是list容器插入元素比vector快,对每个元素单独分配空间,所以不存在空间不够需要重新分配的情况。
empty(); // 判断链表容器是否为空
size(); // 返回链表容器中的实际元素个数
push_back(elem); // 在链尾尾部插入元素elem
emplace_back(elem); //非拷贝构造元素插入元素
pop_back(); // 删除链表容器最后一个元素
remove(ele); // 删除链表容器中所有指定值的元素
remove_if(cmp); // 删除链表容器中满足条件的元素
erase(); // 从链表容器中删除一个或几个元素
unique(); // 删除链表容器中相邻的重复元素
clear(); // 删除链表容器中的所有元素
insert(pos, elem); // 在pos位置插入元素elem,即将元素elem插入到迭代器pos指定的元素之前
insert(pos, n, elem); // 在pos位置插入n个元素elem
insert(pos, pos1, pos2); // 在迭代器pos处插入[pos1,pos2)的元素 lst.inset(it,start,end)
reverse(); // 反转链表
sort(); // 对链表容器中的元素排序
begin(); // 该函数的两个版本返回iterator或const_iterator,引用容器的第一个元素
end(); // 该函数的两个版本返回iterator或const_iterator,引用容器的最后一个元素后面的一个位置
rbegin(); // 该函数的两个版本返回reverse_iterator或const_reverse_iterator,引用容器的最后一个元素
rend(); // 该函数的两个版本返回reverse_iterator或const_reverse_iterator,引用容器的第一个元素前面的一个位置
e.g.:
int main() {
// 定义std::list容器的几种方式
std::list<int> l1; // 定义元素为int的链表l1
std::list<int> l2(10); // 指定链表l2的初始大小为10个int元素
std::list<double> l3(10, 1.23); // 指定l3的10个初始元素的初值为1.23
std::list<int> l4(a, a+5); // 用数组a[0...4]共5个元素初始化l4
std::list<int> l5 = {-2, 78, 9, 76, 10}; //初始化列表
l5.remove(76); //直接删除元素
l5.remove([](int n){return n % 3 == 0;}) //按条件删除元素
}
void disp(std::list<int> &lst){
std::list<int>::iterator iter;
for (iter = lst.begin(); it != lst.end(); ++it)
{
std::cout << *it << std::endl;
}
}
需要注意的是,STL提供的sort()排序算法主要用于支持随机访问的容器,而list容器不支持随机访问,为此list容器提供了sort()成员函数用于元素排序,类似的还有unique(),reverse(),merge()等STL算法。
(3)deque(双端队列容器),是双向队列类模板,可以从前面或后面快速的插入与删除元素,并可以快速地随机访问元素,但在中间位置插入和删除元素速度较慢;不像vector那样把所有的元素保存在一个连续的内存块,而是采用多个连续的存储块存放数据元素,所以空间的重新分配要比vector快,因为重新分配空间后原有的元素不需要复制。
实际上,deque类似于一个动态的二维数组,最主要的中控指针数组中存放的是指向缓冲区buffer的指针,是动态开辟的二维数组,先malloc一个指针数组,指针数组的每个位置存放一维数组buffer的地址。
// 定义std::deque双端队列容器的几种方式
std::deque<int> dp1; // 定义元素为int的双端队列dp1
std::deque<int> dp2(10); // 指定dp2的初始大小为10个int元素
std::deque<double> dq3(10,1.23); // 指定dq3的10个初始元素的初始值为1.23
std::deque<int> dp4(dp2.begin(),dp2.end()) // 用dp2的所有元素初始化dp4
// std::deque常用函数
empty(); //判断双端队列容器是否为空队
size(); //返回双端队列容器中的元素个数
front(); //取队头元素
back(); //取队尾元素
push_front(elem); //在队头插入元素elem
emplace_front(elem);
push_back(elem); //在队尾插入元素elem
emplace_back(elem);
pop_front(); //删除队头一个元素
pop_back(); //删除队尾一个元素
erase(); //从双端队列容器中删除一个或几个元素
clear(); //删除双端队列容器中的所有元素
begin(); //该函数的两个版本返回iterator或const_iterator,引用容器的第一个元素
end(); //该函数的两个版本返回iterator或const_iterator,引用容器的最后一个元素后面的一个位置
rbegin(); //该函数的两个版本返回reverse_iterator或const_reverse_iterator,引用容器的最后一个元素
rend(); //该函数的两个版本返回reverse_iterator或const_reverse_iterator,引用容器的第一个元素前面的一个位置
3.2.set/multiset、map/multimap
set和multiset都是集合类模板,其元素值称为关键字,set中元素的关键字是唯一的,multiset中元素的关键字可以不唯一,而且默认情况下会对元素按关键字自动进行升序排列,所以查找速度比较快,同时支持交、差、并等集合上的运算,如果需要集合中的元素允许重复,那么使用multiset。set中没有相同关键字的元素,在向set中插入元素时,如果已经存在则不插入,multiset中允许存在两个相同关键字的元素,在删除操作时删除multiset中值等于elem的所有元素,若删除成功返回删除个数,否则返回0。
// set/multiset常用成员函数
empty(); //判断容器是否为空
size(); //返回容器中的实际元素个数
insert(); //插入元素
erase(); //从容器中删除一个或几个元素
clear(); //删除所有元素
count(k); //返回容器中关键字k出现的次数
find(k); //如果容器中存在关键字为k的元素,返回该元素的迭代器,否则返回end()值
upper_bound(); //返回一个迭代器,指向关键字大于k的第一个元素
lower_bound(); //返回一个迭代器,指向关键字不小于k的第一个元素
begin(); //用于正向迭代,返回容器的第一个元素
end(); //用于正向迭代,返回容器的最后一个元素后面的一个位置
rbegin(); //用于反向迭代,返回容器的最后一个元素的位置
rend(); //用于反向迭代,返回容器的第一个元素前面的一个位置
map和multimap都是映射类模板,映射是实现关键字与值关系的存储结构,可以使用一个关键字key来访问相应的数据值value,set/multimap中的key和value都是key类型,而map/multimap中的key和value是一个pair类结构。
map[key]; //返回关键字为key的元素的引用,如果不存在这样的关键字,则以key作为关键字插入一个元素(不适合multimap)
insert(elem); //插入一个元素elem并返回该元素的位置
clear(); //删除所有元素
find(); //在容器中查找元素
count(); //容器中指定关键字的元素个数(map中只有1或者0)
begin(); //用于正向迭代,返回容器中的第一个元素位置
end(); //用于正向迭代,返回容器中最后一个元素的位置
rbegin(); //用于反向迭代,返回容器中最后一个元素的位置
rend(); //用于反向迭代,返回容器中第一个元素前面的一个位置
// 在map中修改元素
map<char,int> mymap;
mymap['a'] = 1;
// 获取map中的值
int ans = mymap['a'];
#include<map>
map<char, int> mymap;
mymap['a'] = 3;
map<char, int>::iterator it;
for (it = mymap.begin(); it != mymap.end(), ++it) {
cout << it->first, it->second;
}
3.3.stack、queue
stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可 以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有 push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和 queue默认选择deque作为其底层容器,主要是因为:stack和queue不需要遍历(stack和queue没有迭代器),只需要在固定的一端或者两端进行操作;在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,使用deque作为底层默认容器,不仅效率高,而且内存使用率高。stack和queue结合了deque的优点,而完美的避开了其缺陷
//stack
empty(); //判断栈容器是否为空
size(); //返回栈容器中的实际元素个数
push(elem); //元素elem进栈
top(); //返回栈顶元素
pop(); //元素出栈
#include<stack>
using namespace std;
void main(){
stack<int> st;
st.push(1);st.push(2);st.push(3);
cout << st.top() << endl;
while(!st.empty()){
cout << st.pop() <<endl;
st.pop();
}
}
//queue
// 主要成员函数如下:
empty(); //判断队列容器是否为空
size(); //返回队列容器中的实际元素个数
front(); //返回队头元素
back(); //返回队尾元素
push(elem); //元素elem进队
pop(); //元素出队
#include<queue>
using namespace std;
void main(){
queue<int> qu;
qu.push(1);qu.push(2);qu.push(3);
cout << "队头元素" << qu.front();
cout << "队尾元素" << qu.back();
while(!qu.empty()){
cout << qu.front() << endl;
qu.pop();
}
}