C++ STL的三大组件:Containers, Iterators, Algorithms,即容器,迭代器和算法。容器负责存储数据,算法负责计算结果,而算法调用容器的数据必须通过迭代器去访问,而每一种容器都提供了一种访问该容器中数据的迭代器。STL主要提供有两类容器跟容器适配器,两类容器分别为:序列式容器<向量(vector)、双端队列(deque)、链表(list)>以及关联式容器<集合(set(multiset))及映射(map(multimap))>;容器适配器为:栈(stack)和队列(queue),容器的示意图如下图所示:
基本接口
vector
特点:
(1)拥有一段连续的内存空间,因此它能非常好的支持随机访问,即 [] 操作符和 .at(),随机访问快。(优点)
(2)当向其头部或中间插入或删除元素时,为了保持原本的相对次序,插入或删除点之后的所有元素都必须移动,所以插入或删除的效率比较低。(缺点)
(3)在后面插入删除元素最快,此时一般不需要移动内存。(优点)
总结:相当于可拓展的数组(动态数组),随机访问快,在头部和中间插入或删除效率低,但在尾部插入或删除效率高。基本操作:
v.empty(); // 判断容器是否为空
v.capacity(); // 容器容量
v.size(); // 容器大小
v.at(int idx); // 用法和[]运算符相同
v.front(); // 获取头部元素
v.back(); // 获取尾部元素
v.begin(); // 头元素的迭代器
v.end(); // 尾部元素的迭代器
v.push_back(elem); // 尾部插入
v.pop_back(); // 尾部删除
v.insert(pos, elem); //在位置pos插入elem元素
v.insert(pos, n, elem); //在位置pos上插入n个元素elem
v.insert(pos, begin, end); //在位置pos上插入begin至end区间的数据
v.erase(pos); //移除pos位置上的元素,返回下一个数据的位置
v.erase(begin, end); //移除[begin, end)区间的数据,返回下一个元素的位置
v.clear(); // 清空元素
v.swap(vec); // 将vec与自身元素互换
v.reseve(int len); //预留空间,由于每次往vector中添加元素时都会检查空间是否够用,不够就会重新换一块大的内存,这样耗时耗力。deque
特点:
(1)一旦要在 deque 的头部和尾部增加新空间,便配置一段定量连续空间,串在整个 deque 的头部或尾部,因此不论在头部或尾部插入元素都十分迅速。 (优点)
(2)在中间部分安插元素则比较费时,因为必须移动其它元素。(缺点)
(3)deque 是 list 和 vector 的折中方案。兼有 list 的优点,也有 vector 随机访问效率高的优点。
总结:支持随机访问,但效率没有 vector 高,在头部和尾部插入或删除效率高,但在中间插入或删除效率低。基本操作:deque几乎支持vector所以的操作,但由于daque是双向队列可以双向插入跟删除,所以都有自己独有的两个操作如下:
d.push_front(elem); //头部插入
d.pop_front(); //头部删除list
特点:
(1)内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随机存取变得非常没有效率,因此它没有提供 [] 操作符的重载。(缺点)
(2)由于链表的特点,在任意位置的插入和删除效率都较高。(优点)
(3)只支持首尾两个元素的直接存取,想获取其他元素(访问时间一样),则需要遍历链表。(缺点)
总结:不支持随机访问,在任意位置的插入和删除效率都较高。基本操作:
l.size(); // 链表大小
l.empty(); // 判断链表是否为空
l.resize(num); // 重新制定链表长度,若长度比之前大,则用默认值填充,若小于之前大小,则超出的元素被删除
l.resize(num, elem); // 指定值填充
l.front(); // 返回第一个元素
l.back(); // 返回最后一个元素
l.begin(); // 头元素的迭代器
l.end(); // 尾部元素的迭代器
l.push_back(elem); // 尾部插入
l.pop_back(); // 尾部删除
l.push_front(elem); // 头部插入
l.pop_front(); // 头部删除
l.insert(pos,elem); // 在位置pos插入elem元素
l.insert(pos, n, elem) // 在位置pos上插入n个元素elem
l.insert(pos, begin, end); // 在位置pos上插入begin至end区间的数据
l.erase(pos); // 移除pos位置上的元素,返回下一个数据的位置
l.erase(begin, end); // 移除[begin, end)区间的数据,返回下一个元素的位置
l.clear(); // 清空元素
l.remove(elem); // 删除容器中所有与elem匹配的元素
l.swap(lst); // 将lst与自身元素互换
l.reverse(); // 链表反转
l.sort(); // 链表排序,默认升序,如果不为升序许自己实现排序规则注:不支持随机访问的迭代器的容器不可以用标准算法,其内部会提供对应的一些算法(比如排序算法)。
set(multiset)
特点:
(1)使用红黑树实现,其内部元素依据其值自动排序,set中每个元素值只能出现一次,不允许重复,multiset中元素可以重复。
(2)每次插入值的时候,都需要调整红黑树,效率有一定影响。(缺点)
(3)map(multimap) 和 set(multiset) 的插入或删除效率比用其他序列容器高,因为对于关联容器来说,不需要做内存拷贝和内存移动。(优点)
总结:由红黑树实现,其内部元素依据其值自动排序,且插入和删除效率比用其他序列容器高。基本操作:
s.size(); // 容器大小
s.empty(); // 判断容器是否为空
s.find(key); // 返回元素的迭代器
s.count(key); // 返回元素的个数
s.insert(elem); // 插入elem元素
s.erase(pos); // 移除pos迭代器所指的位置
s.erase(begin, end); // 移除[begin, end)区间的数据,返回下一个元素的位置
s.erase(elem); // 删除容器中所有与elem匹配的元素
s.clear(); // 清空元素
s.swap(set); // 将set与自身元素互换map(multimap)
特点:
(1)map中每个元素都有一个键,且只能出现一次,不允许重复。而multimap中可以出现重复键值。
(2根据 key 值快速查找记录,查找的复杂度基本是 O(logN),如果有 1000 个记录,二分查找最多查找 10次(1024)。(优点)
(3)每次插入值的时候,都需要调整红黑树,效率有一定影响。(缺点)
(4)增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。(优点)
(5)对于迭代器来说,可以修改实值,而不能修改 key。
总结:元素为键值对,key 和 value 可以是任意你需要的类型,根据 key 快速查找记录。基本操作:
m.size(); // 容器大小
m.empty(); // 判断容器是否为空
m.find(key); // 返回元素的迭代器
m.count(key); // 返回元素的个数
m.insert(elem); // 插入elem元素
m.erase(pos); // 移除pos迭代器所指的位置
m.erase(begin, end); // 移除[begin, end)区间的数据,返回下一个元素的位置
m.erase(elem); // 删除容器中所有与elem匹配的元素
m.clear(); // 清空元素
m.swap(set); // 将set与自身元素互换
基本操作案例
将STL的容器及其常用的算法做了如下案例测试,具体代码如下所示:
/************************************** 作 者 : lijd 生成日期 : 2021年03月02日 功能描述 : C++中STL容器、算法基本操作测试 **************************************/ #include <iostream> #include <string> using namespace std; #include <vector> #include <list> #include <set> #include <map> #include <algorithm> #include <numeric> #include <functional> #include <ctime> // 自定义函数输出 void lijd_print(int val) { cout<< val <<" "; } // 仿函数类模板打印输出 template <class T> class MyPrint { public: void operator()(T val) { cout<< val <<" "; } }; void test01() { cout<<"----------------------------------------test 01--------------------------------------"<<endl; vector<int> v1; // 生产随机种子 srand((unsigned int)time(NULL)); for(int i = 0; i < 10; i++) { v1.push_back(rand()%50); } cout << "排序前:" << endl; // 调用用户定义函数打印 for_each(v1.begin(), v1.end(), lijd_print); cout << endl; cout<<"集合中元素的总和为:"<<accumulate(v1.begin(), v1.end(), 0/*基础累加值*/)<<endl; // sort默认从小到大排序,调用了关系仿函数 template <class T> less<T> sort(v1.begin(), v1.end()); cout << "从小到大排序后:" << endl; // 调用用户自定义仿函数打印 for_each(v1.begin(), v1.end(), MyPrint<int>()); cout << endl; // 1、sort的最后一个参数是谓词(返回值为bool的仿函数),下面例子为其创建一个模板类实例化的匿名对象 // 2、functional提供了一堆基于模板的比较函数对象:equal_to<Type>、not_equal_to<Type>、 // greater<Type>、greater_equal<Type>、less<Type>、less_equal<Type> sort(v1.begin(), v1.end(), greater<int>()); cout << "从大到小排序后:" << endl; for_each(v1.begin(), v1.end(), MyPrint<int>()); cout << endl; } class Person { friend ostream& operator<<(ostream &p_cout, Person &p); public: Person(string name, int age):s_name(name), s_age(age){}; int GetAge(){return this->s_age;}; // 排序的时候需要调用my_sort_desc仿函数及list自带的sort函数,两者都需要重载 < // functional提供的模板类的谓词必须为const函数 bool operator<(const Person &p) const { return (this->s_age < p.s_age ? true : false); }; // 排序时调用list自带的sort函数时参数为谓词greater需要重载 >; bool operator>(const Person &p) const { return (this->s_age > p.s_age ? true : false); }; // set集合调用find查找时需要重载== bool operator==(const Person &p) const { return (this->s_age == p.s_age ? true : false); } private: string s_name; int s_age; }; // 输出需要调用MyPrint仿函数,需重载"<<" ostream& operator<<(ostream &p_cout, Person &p) { p_cout<<"name:"<<p.s_name<<" age:"<<p.s_age<<"; "; return p_cout; }; // 仿函数类模板年龄从大到小 template <class T> class my_sort_desc { public: bool operator()(T val1, T val2) const { return val1 > val2; } }; // 仿函数类模年龄从大到小 class age_great_num { public: age_great_num(int num):s_num(num){}; bool operator()(Person &p) const { return p.GetAge() > this->s_num; } private: int s_num; }; void test02() { cout<<"----------------------------------------test 02--------------------------------------"<<endl; cout<<"---------------------vector 测试---------------------"<<endl; vector<Person> v1; v1.push_back(Person("唐僧", 30)); v1.push_back(Person("孙悟空", 1000)); v1.push_back(Person("猪八戒", 900)); v1.push_back(Person("沙和尚", 800)); cout<<"vector 排序前:"<<endl; for_each(v1.begin(), v1.end(), MyPrint<Person>()); cout<<endl; cout<<"vector 从小到大排序后:"<<endl; // 调用系统自带的less<T>模板类 sort(v1.begin(), v1.end(), less<Person>()); for_each(v1.begin(), v1.end(), MyPrint<Person>()); cout<<endl; cout<<"---------------------list 测试---------------------"<<endl; // 注:不支持随机访问的迭代器的容器不可以用标准算法,其内部会提供对应的一些算法(比如排序算法) list<Person> lst; lst.push_back(Person("唐僧", 30)); lst.push_back(Person("孙悟空", 1000)); lst.push_back(Person("猪八戒", 900)); lst.push_back(Person("沙和尚", 800)); cout<<"list 排序前:"<<endl; for_each(lst.begin(), lst.end(), MyPrint<Person>()); cout<<endl; // list需要调用自带sort排序,默认调用less仿函数,该参数调用自己实现的my_sort_desc<T>模板类 lst.sort(my_sort_desc<Person>()); cout<<"list 从大到小排序后:"<<endl; for_each(lst.begin(), lst.end(), MyPrint<Person>()); cout<<endl; } void test03() { cout<<"----------------------------------------test 03--------------------------------------"<<endl; set<int> s1; // 如果为内置数据类型,默认有序,从小到大 // 生产随机种子 srand((unsigned int)time(NULL)); for(int i = 0; i < 10; i++) { s1.insert(rand()%50); } cout<<"set 插入内置数据类型从小到大:"<<endl; for_each(s1.begin(), s1.end(), MyPrint<int>()); cout<<endl; // 此处定义multiset自定义数据类型,年龄从大到小;尝试过用set年龄相同的会去重! multiset<Person, greater<Person>> myset; myset.insert(Person("唐僧", 30)); myset.insert(Person("孙悟空", 1000)); myset.insert(Person("猪八戒", 900)); myset.insert(Person("沙和尚", 800)); myset.insert(Person("红孩儿", 500)); myset.insert(Person("牛魔王", 1200)); myset.insert(Person("白骨精", 800)); myset.insert(Person("玉皇大帝", 1000)); cout<<"set 插入自定义数据类型年龄从大到小:"<<endl; for_each(myset.begin(), myset.end(), MyPrint<Person>()); cout<<endl; multiset<Person, greater<Person>>::iterator it = find(myset.begin(), myset.end(), Person("二郎神", 900)); if(it != myset.end()) { cout<<"find 查找到与二郎神年龄相同的为:"<<*it<<endl; } // 最后一个参数为其创建一个实例化的匿名对象 it = find_if(myset.begin(), myset.end(), age_great_num(Person("王母娘娘", 500).GetAge())); if(it != myset.end()) { cout<<"find_if 查找第一个年龄大于王母娘娘的为:"<<*it<<endl; } int num = count(myset.begin(), myset.end(), Person("太白金星", 800)); cout<<"count 查找与太白金星年龄相同的个数有:"<<num<<endl; num = count_if(myset.begin(), myset.end(), age_great_num(Person("太白金星", 800).GetAge())); cout<<"count 查找比太白金星年龄大的个数有:"<<num<<endl; // 生产随机种子 srand((unsigned int)time(NULL)); // 定义key值从大到小的map对象 map<int, Person, greater<int>> m1; m1.insert(make_pair(rand()%50, Person("唐僧", 30))); m1.insert(make_pair(rand()%50, Person("孙悟空", 1000))); m1.insert(make_pair(rand()%50, Person("猪八戒", 900))); m1.insert(make_pair(rand()%50, Person("沙和尚", 800))); cout<<"map m1的元素遍历为:"<<endl; map<int, Person, greater<int>>::iterator it1; for(it1 = m1.begin(); it1 != m1.end(); it1++) { cout<<"key: "<<it1->first<<" value: ["<<it1->second<<"]"<<endl; } map<int, Person> m2; m2.insert(make_pair(rand()%50, Person("红孩儿", 500))); m2.insert(make_pair(rand()%50, Person("牛魔王", 1200))); m2.insert(make_pair(rand()%50, Person("白骨精", 800))); m2.insert(make_pair(rand()%50, Person("玉皇大帝", 1000))); cout<<"map m2的元素遍历为:"<<endl; map<int, Person>::iterator it2; for(it2 = m2.begin(); it2 != m2.end(); it2++) { cout<<"key: "<<it2->first<<" value: ["<<it2->second<<"]"<<endl; } } void test04() { cout<<"----------------------------------------test 04--------------------------------------"<<endl; vector<int> v1, v2, vtag; for(int i = 0; i < 10; i++) { v1.push_back(i); v2.push_back(i+5); } cout<<"v1 数据元素:"<<endl; for_each(v1.begin(), v1.end(), MyPrint<int>()); cout<<endl; cout<<"v2 数据元素:"<<endl; for_each(v2.begin(), v2.end(), MyPrint<int>()); cout<<endl; // 目标集合申请空间 vtag.resize(min(v1.size(), v2.size())); vector<int>::iterator itEnd = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), vtag.begin()); cout<<"v1 和 v2 集合的交集为:"<<endl; for_each(vtag.begin(), itEnd, MyPrint<int>()); cout<<endl; vtag.resize(v1.size() + v2.size()); itEnd = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vtag.begin()); cout<<"v1 和 v2 集合的并集为:"<<endl; for_each(vtag.begin(), itEnd, MyPrint<int>()); cout<<endl; vtag.resize(v1.size()); itEnd = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vtag.begin()); cout<<"v1 和 v2 集合的非集为(v1中存在v2不存在):"<<endl; for_each(vtag.begin(), itEnd, MyPrint<int>()); cout<<endl; swap(v1, v2); cout<<"交换后 :"<<endl; cout<<"v1 数据元素:"<<endl; for_each(v1.begin(), v1.end(), MyPrint<int>()); cout<<endl; cout<<"v2 数据元素:"<<endl; for_each(v2.begin(), v2.end(), MyPrint<int>()); cout<<endl; } int main() { test01(); test02(); test03(); test04(); system("pause"); return 0; }
运行结果如下图: