一、STL概述
1. STL概述
(1)STL定义:STL(standard template library),标准模板库,是一个高效的c++程序库,重在提高了代码的复用性;主要包含了常用的数据结构和基本算法,为广大c++程序员提供了一个可扩展的应用框架;
体现了泛型化(模板化)程序设计的思想,在实现方面是以一种类型程序参数化的方式实现的;采用模板类实现,所以定义时需要执行存储元素的类型(基本类型或自定义类型);
2. STL的六大组件
(1)容器(container):各种基本数据结构
(2)迭代器(iterator):常使用在遍历容器,又可以用在连接容器和算法;
(3)算法(algorithm):提供了各种基本算法如sort、search等;
(4)适配器(adapter):可改变容器(containers)、迭代器(iterators)或函数对象(function object)接口的一种组件;
(5)函数对象(function object):用类型包装函数,将函数类型化;
(6)分配器(allocator):内部调用malloc分配空间;
二、容器
1. 分类:可分为三类:
(1)顺序容器(sequence containers),主要包括array、vector、deque、list、forward_list;
(2)关联容器(associative containers),内部机理是树、哈希表,元素是键/值对,特别适合做查找,因为其时间复杂度较低;且大都具有自动排序功能;主要包括set、multiset、map、multimap;
(3)无序(散列)容器(UNordered containers),内部机理是哈希表,c++11里推出:元素的位置不重要,重要的是这个元素是否在这个容器里面,主要适用于查找;主要包括:underde_set、unordered_multiset、unordered_map、unordered_multimap;
2. vector容器 头文件#include<vector>
(1)本质与特点
①vector是内置有动态数组,可以随机存取元素,可用[ ] 和 at()方法;
②vector尾部添加或移除元素非常快速;
③因为vector在中间插入会导致后面的所有元素都要重新析构与拷贝构造,效率很低;
④vector采用模板类实现,所以定义时需要执行存储元素的类型(基本类型或自定义类型)
(2)主要操作
vector对象的默认构造 | vector<int>v1; |
vector对象的带参数构造 | vector<int>v2(v1.begin()+1, v1.begin()+4); |
vector的插入 | push_back、insert |
vector数据存取 | v1.at(), v1[ ], v1.front(), |
vector的删除 | pop_back(), clear(), erase(), |
vector的迭代器 | begin(), end(), rbegin(), rend() |
vector的大小 | size(), empty(), capacity(), resize(), shrink_to_fit |
vector的赋值 | assign(), operator=, swap |
vector示例:
#include <vector>
#include <iostream>
using namespace std;
class Test
{
public:
Test()
{
// cout << "Test" << endl;
}
Test(int t) : m_t(t)
{
// cout << "Test int" << endl;
}
Test(const Test &other)
{
m_t = other.m_t;
// cout << "Test copy" << endl;
}
~Test()
{
// cout << "~Test" << endl;
}
friend ostream& operator<<(ostream&out, Test &t);
friend bool operator!=(const Test&t, int num);
friend int operator%(const Test&t, int num);
int m_t;
};
ostream& operator<<(ostream&out, Test &t)
{
out<<t.m_t;
return out;
}
bool operator!=(const Test&t, int num)
{
return (t.m_t != num);
}
int operator%(const Test&t, int num)
{
return (t.m_t % num);
}
template <typename T>
void print(vector<T> &vt)
{
for(int i = 0; i < vt.size(); i++)
{
cout<<vt.at(i)<<" ";
}
cout<<endl;
}
template <typename T>
void delete_node(vector<T> &vt)
{
for(auto it = vt.begin(); it != vt.end(); )
{
if(*it % 2 != 0)
{
// it = vt.erase(it);
vt.erase(it);
}
else
{
++it;
}
}
}
int main(int argc, char **argv)
{
vector<Test> vt;
vt.reserve(20); //提前预留20个元素值,避免了空间不够时,频繁的拷贝构造
for(int i = 0; i < 9; i++)
{
vt.push_back(Test(i+1)); //连续构造1-10
}
print(vt);
delete_node(vt); //删除其中是基数的元素值(test的m_t是奇数的)
print(vt);
cout<<"vt.capacity() = "<<vt.capacity()<<endl; //输出vt容器的可容纳元素的大小,20
vt.shrink_to_fit(); //回收未被利用的空间
cout<<"vt.capacity() = "<<vt.capacity()<<endl; //输出vt容器的可容纳元素的大小,4
vt.assign(7,7); //将7个7,赋给vt容器,注意是系统默认将vt原有的内容先清空
print(vt);
return 0;
}
3. list 容器 头文件 #include<list>
(1)本质及特点
①list是一个双向链表容器,适合高效地在头尾插入或者删除元素;
②list不支持随机存取元素,所以不支持at()方式与[ ]操作符;请注意迭代器方面it++可以,但是it+2这样就不支持了;只可遍历,不可跳跃;
(2)主要操作
list对象的默认构造 | list<int> l1;也可以是自定义类型; |
list对象的带参数构造 | list<int> l2 (l1.begin()+1, l1.end()); |
list的插入 | l2.push_back(); l2.pop_back(); l2.push_front(); l2.pop_front(); l2.emplace_back(); l2.emplace_front(); |
list元素删除 | l2.clear(); l2.erase(beg,end); l2.erase(pos); l2.remove(elem); l2.remove_if(); list.unique(); |
list的迭代器 | l2.begin(); l2.end(); l2.rbegin(); l2.rend(); |
list的元素存取 | l2.front(); l2.back(); |
list的赋值 | l2.assign(); l2.swap(l1); |
list的大小 | l2.size(); l2.empty(); l2.resiae(); l2.resize(); |
list的排列 | l2.sort(); l2.reverse(); |
list对容器的操作 | splice(); |
list示例:
#include <list>
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
class Deleteor
{
public:
bool operator()(const T &t)
{
return (t % 2) == 0;
}
};
template <typename T>
void print(list<T> & other)
{
for(auto it = other.begin(); it != other.end(); ++it)
{
cout<<*it<<" ";
}
cout<<endl;
}
int main(int argc,char **argv)
{
list<int> l1;
l1.emplace_back(1);
l1.emplace_back(2);
l1.emplace_back(3);
l1.emplace_back(4);
l1.emplace_back(5);
l1.emplace_back(6);
l1.emplace_back(7);
l1.emplace_front(8);
print(l1);
l1.insert(std::next(l1.begin(),3),9); //std::next(l1.begin(),3)是制定插入的位置
print(l1);
l1.remove(4); //删除值等于4的元素
print(l1);
l1.remove_if(Deleteor<int>()); //Deleteor<int>()是删除的规则,返回的是bool(true,false)
print(l1); //这里是一个函数对象,重载了函数运算符()
l1.sort(); //对list排序,默认是从小到大
print(l1);
l1.sort(greater<int>()); //内建函数(对象),实现从大到小排序
print(l1);
l1.reverse(); //对list逆序
print(l1);
list<int>l2 = {1,3,5,7,9};
list<int>l3 = {2,4,6,8,10};
l2.merge(l3); //将l3合并到l2上,并按照默认的从小到大方式排好序
cout<<"l2 : ";
print(l2);
cout<<"l3 : ";
print(l3); //l3会被清空
list<int>l4 = {7,7,7,7};
auto it = l2.begin();
std::advance(it,2); //将it迭代器后退2个位置,-2就是前进2个位置
l2.splice(it,l4);
cout<<"l2 : ";
print(l2);
return 0;
}
4. forward_list容器
本质及特点:优化list产生的,forward_list是单向链表,这样的话少存储一个指针,节省系统开销;
①只提供前向迭代器,不支持反向迭代器;
②不提供size()成员函数;
③没有指向最末元素的锚点,因此不提供back(),push_back(),pop_back();
④不提供随机访问,与list同;
⑤插入和删除元素不会造成“指向其他元素的”指针、引用、迭代器失效;
5. deque 容器,本质及特点:
①deque是双端数组,而vector是单端的;
②与vector一样,可以随机存取元素;且在头部和尾部移除元素都非常快速,但是在中间插入元素则比较费时;
③其他内容以vector一样;
6. set/multiset 容器 头文件:#include<set>
(1)本质及特点
①set是一个集合容器,其中包含的元素是唯一的,集合中的元素按一定的顺序排列;元素插入过程是按排序规则插入,所以不能指定插入位置;
②set采用红黑树变体的数据结构实现,红黑树属于平衡二叉树,在插入操作和删除操作上比vector快;时间复杂度是O(log n);
③set不可以直接存取元素(不可以使用at.(pos)与[ ] 操作符);
④multiset与set的区别是:set支持唯一键值,即每一个元素只能出现一次;而multiset中的元素可以出现多次;
⑤不可以直接修改set或multiset容器中的元素值,因为这类容器是自动排序的,如果要修改其中的某个元素值,必须先删除原有的元素,再插入新的元素;
程序示例:
#include <iostream>
#include <set>
#include <vector>
using namespace std;
template <typename T>
void print(const set<T> &other)
{
for(auto it = other.begin(); it != other.end(); ++it)
{
cout<<*it<<" ";
}
cout<<endl;
}
int main(int argc, char **argv)
{
set<int>si;
si.insert(1);
si.insert(3);
si.insert(7);
si.insert(5);
si.insert(2);
print(si);
// auto node = si.extract(3); //修改成员值,c++17,本系统不支持
// node.value = 9;
// si.insert(move(node));
// set<int>s2 = {2,4,6,8};
// si.merge(s2); //将s2合并到si,c++17,本系统不支持
cout<<"si.count(5) : "<<si.count(5)<<endl; //数值是5的元素有几个?
auto it = si.find(5);
if(it != si.end())
{
cout<<"找到了"<< *it <<endl;
}
auto it1 = si.lower_bound(5); //返回首个不小于5的元素的迭代器
auto it2 = si.upper_bound(5); //返回收个大于5的元素迭代器
if(it1 != si.end())
{
cout<<"it1 = "<<*it1<<endl;
}
if(it2 != si.end())
{
cout<<"it2 = "<<*it2<<endl;
}
return 0;
}
7. map/multimap 容器 #include<map>
(1)本质及特点
①map是标准的关联式容器,一个map是一个键值对(一个序列号,一个值,相当于数组的索引和对应的值),它提供基于key(键值)的快速检索能力;键值对中第一个元素是first,第二个元素是second,支持迭代器访问输出;
②map中的key值是唯一的;集合中的元素按一定的顺序排列,元素插入过程是按排序规则插入,所以不能指定插入位置;
③map的具体实现采用红黑树变体的平衡二叉树数据结构,在插入和删除操作上比vector块;
④map可以直接存取key所对应的value,支持[ ] 操作符,如map[key] = value;
⑤multimap 与 map 的区别:map支持唯一键值,每个键只能出现一次,而multimap中相同的键可以出现多次。但是multimap不支持[ ]操作符;
(2)示例
#include <iostream>
#include <map>
#include <string>
using namespace std;
template<typename key, typename value>
void print(const map<key, value> &other)
{
for(auto it = other.begin(); it != other.end(); ++it)
{
cout<<"key : "<<it->first<<" value : "<<it->second<<endl;
}
}
int main(int argc, char **argv)
{
map<int, string> m1; //和数组很像,索引+对应的值
m1[1] = "hello1";
m1[2] = "hello2";
m1[3] = "hello3";
print(m1);
return 0;
}
(3)一个面试题示例:给定一个数组,计算每个数值出现的次数,然后按字符出现次数的升序输出数值及其出现次数;并且单独输出个数大于数组个数一半的元素;
#include <iostream>
#include <map>
#include <set>
using namespace std;
template<typename key, typename value>
void print(const multimap<key, value> &other)
{
for(auto it = other.rbegin(); it != other.rend(); ++it)
{
cout<<"降序遍历 : 值"<<it->second<<" 次数:"<<it->first<<endl;
}
}
int main(int argc, char **argv)
{
int num[]={1,3,3,3,5,3};
int len = sizeof(num)/(sizeof(num[0]));
cout<<"len = "<<len<<endl; //输出元素的个数
map<int,int>mc;
for(auto &temp : num)
{
mc[temp]++;
}
// cout<<"mc[3] = "<<mc[3]<<endl; //输出数值3对应的次数
multimap<int,int>mi; //注意这里又新建了一个容器,在插入元素时,与mc相反,以次数为键值,这样就会自动根据次数排序,默认是升序,若要降序,则需要采用逆向迭代器
for(auto it = mc.begin(); it != mc.end(); ++it)
{
mi.insert(pair<int,int>(it->second,it->first));
}
for(auto &temp : mi)
{
cout<<"升序遍历 : 值"<<temp.second<<" 次数:"<<temp.first<<endl; //注意前后的输出顺序对照上
}
print(mi);
for(auto &temp : mc)
{
if(temp.second > (len/2))
{
cout<<"符合要求:"<<temp.first<<" 出现次数:"<<temp.second<<endl;
}
}
return 0;
}
结果:
8. 容器的选择原则总结
①vector是内置有动态数组,当容量满时会自动扩充,在末尾有定位指针,所以末尾插入十分快速,但中间插入则较慢,因为涉及到原有数组的拷贝;最大的特点是适合高效的随机访问;而deque则优化为可以既可以头插也可以末尾插;
②list是一个双向链表容器,访问指定元素的效率较低,不支持随机访问,最大的特点是中间插入的效率较快;
③set是一个集合容器,元素具有唯一性,因为排序功能,所以开销较大;若需要支持重复数据则需要使用multiset;
④map是关联式容器,内置键值对,且内部按照顺序排序,内部采用红黑树的数据结构,时间复杂度是O(log n ),查找与删除的效率很高;适合字典式存储数据;