容器
顺序容器
主要靠下标和迭代器进行操作。顺序性的主要靠下标,链式的靠迭代器访问。
包含了顺序型的容器和链式的容器。
连续型的包括:
vector
:向量,可以快速扩展和删除元素,在队尾的操作有优势!deque
:双端队列,可以快速的从队首和队尾添加或者删除元素,适合双向的操作。array
:数组,大小必须是固定的。string
:字符串
链式的包括:
list
:双向链表forward_list
:单向链表。没有size
操作。
关联容器
简介
主要靠关键字进行访问。关联容器主要是分为两类:set
类型的和map
类型的。同时也可以按照有序和无序分类,也可以按照顺序保存和无序保存分类。
总共有8中类型额关联容器:
map
:关键字-值,关键字唯一set
:关键字,关键字唯一multimap
:关键字-值,关键字可以重复multiset
:关键字:关键字可以重复unordered_map
:哈希函数组织的map
,关键字唯一unordered_set
:哈希函数组织的set
,关键字唯一unordered_multimap
:哈希函数组织的multimap
,关键字不唯一unordered_multiset
:哈希函数组织的multiset
,关键字不唯一
上述以unordered
开头的都是无序的关联容器。关联容器都是支持模板类型操作的。
关联容器不支持顺序容器的那种与位置有关的操作,比如push_back
、push_front
等,这些操作无意义。关联容器的迭代是双向的。
有序关联容器的关键字必须定义比较函数,否则无法放入容器,因为容器需要靠<
比较函数才能进行数据结构的构建。使用有序容器存储自定义的数据类型时,除了给出数据类型的模板之外,还要给出自定义的操作类型。不过我们一般都是重载运算符<
进行操作。
#include <iostream>
#include <set>
using namespace std;
class Data1{
public:
Data1(int _a,int _b=0,int _c=0):a(_a),b(_b),c(_c){}
bool operator<(const Data1 &d){
return a<d.a;
}
int a,b,c;
};
int main(){
set<Data1>s1;
return 0;
}
pair
数据类型,存在于utility
头文件。该数据类型用于map
大类的操作,具体有一下几个操作:
pair<T1, T2> p;
声明一个pair
pair<T1, T2> p(v1, v2);
声明并初始化pair<T1, T2>p{v1, v2} ;
声明并初始化auto p=make_pair(v1,v2);
初始化,编译器自动推断所有类型。p.first
:返回键值p.second
:返回对应的数据p1 < p2
:比较的是键值,对于其他的运算符同理。
主要操作
几个特性:
key_type
:容器的关键字类型mapped_type
:每个关键字关联的类型,只适用于map
类型value_type
:数据类型- 对于
set
类型,这与key_type
相同 - 对于
map
类型,为pair<const key_value, mapped_type>
- 对于
上述说的类型是大类。
迭代操作
一般使用自动推导得到迭代器,迭代器是value_type
类型的指针。
对于set
来说,就是元素模板的类型;对于map
来说,是pair<const key_value, mapped_type>
类型。
注意,set
的迭代器是const
类型的,只能读不能修改,这很好理解,因为一旦修改键值,所有的排列顺序都要进行改变;而map
的不能修改键值,但是可以修改键值对应的数据:
#include <iostream>
#include <map>
using namespace std;
int main(){
map<int,string>m{{1,"a"},{2,"b"}};
for(auto it=m.begin();it!=m.end();it++){
it->second="new"; // 在这里修改对应的数据
}
cout<<m[1]<<endl; // 输出new
return 0;
}
对于迭代操作,只要是有序容器,都是按照字典序输出的。
一般来说,关联容器的迭代器都是只读类型的,我们平时用的sort
等的算法都不适用这种容器,但是可以使用find
的成员给键值定位。也可以使用copy
进行复制操作。
插入操作
单个键值的插入:
使用内置的insert
成员进行操作。几个常用的操作:
c.insert(v)
,插入对应的valut_type
类型的数据c.emplace(args)
,对于单键值的关联容器,只有元素不存在的时候进行插入,多键值的直接插入;返回一个pair
类型的数据,第一个是指向含有关键字的元素,第二个是bool
,表示是否插入成功。c.insert(b,e)
b和e是迭代器,表示插入一个区间的value_type
类型数据c.insert(il)
表示使用花括号的类型
上述的返回值都是一个pair
,作用同上。
多个键值的插入:
不返回任何数据,因为总是可以进行数据插入。
删除操作
使用erase
进行删除操作:
c.erase(k)
:删除关键字为k
的元素,返回删除的个数,不存在返回0c.erase(p)
:删除迭代器指向的元素,必须是内部的,不能是end()
c.erase(b,e)
:删除区间内的元素
map的下标操作
下标操作只适用于map
和unordered_map
,不能用于多键值的;同时,元素必须是非const
类型的。set
没有下标操作。两种数据类型:
c[k]
:返回关键字是k
的元素,如果k
不存在,进添加这个关键字,并初始化c.at(k)
:返回关键为k
的元素,不存在抛出out_of_range
异常
上述返回的都是mapped_type
类型的数据。
访问操作
c.find(k)
:返回第一个迭代器,指向关键字是k
的,不存在返回end()
c.count(k)
:返回关键字为k
的个数c.lower_bound(k)
:返回第一个迭代器,指向第一个关键字不小于k
元素,不适于无序容器!c.upper_bound(k)
:返回第一个迭代器,指向第一个关键字大于k
的元素,不适于无序容器!c.equal_range(k)
:返回一个pair
,表示范围。如果不存在,成员均为end()
无序容器
无序容器使用hash
函数和==
进行运算。主要处理元素没有明显有序关系的情况,理论上有更好的性能,但是实际上需要多次进行工程验证效率。
无序容器的插入删除和有序容器基本一致,只是没有和序列有关的操作。无序容器使用了一个链式的桶状结构,性能取决于哈希函数的质量和桶的数量与大小。如果是自定义的数据类型,我们需要自己来说明哈希函数,否则不能直接使用。
#include <iostream>
#include <unordered_set> // 注意头文件
#include <string>
#include <functional>
using namespace std;
class Data{
public:
Data(int a,string s):n(a),str(s){}
int n;
string str;
};
size_t hasher(const Data &d){
return hash<string>()(d.str);
}
bool cmp(const Data& d1, const Data &d2){
return d1.str==d2.str;
}
int main(){
using SD_multiset=unordered_multiset<Data,
decltype(hasher)*, decltype(cmp)*>;
// 桶的大小,哈希函数,比较函数
SD_multiset data_set(10,hasher,cmp);
Data d(1,"hello");
data_set.insert(d);
return 0;
}
迭代器
迭代器是一个泛型的指针类型,只要是可迭代的容器,都可以使用迭代器。适配器不可以使用,比如stack
、queue
和priority_queue
等的数据类型。
begin()
是首元素end()
是尾后元素- 若容器是空的,则
begin()==end()
迭代器的标准运算成员:
*iter
取内容iter->item
取出内部的成员++iter
指向下一个元素--iter
指向上一个iter1==iter2
判断是否指向同一个元素iter1!=iter2
不指向同一个元素的判定
使用了迭代器的循环体,不要向迭代器所属的容器添加元素!!!
非标准迭代器成员的运算符号根据具体的容器确定。
容器的适配器
容器的适配器是一个机制,使得某种事物看起来像另一种事物。给出基本类型和操作:
size_type
:保证当前类型的最大对象的大小value_type
:元素的类型container_type
:实现适配器底层容器的类型A a
:创建一个A类型的空适配器aA a(c)
:创建一个名为a的适配器,带有容器c
的一个拷贝!!!a.empty()
:判断是否是空,空返回true
,否则是false
a.size()
:返回元素的个数swap(a,b)
:交换a,b的内容,要求比素食同类型的元素,底层容器类型也必须相同a.swap(b)
:同上
栈默认使用deque
作为底层,队列默认使用deque
,priority_queue
默认使用vector
作为底层。也可以自定义底层容器。需要注意的是:适配器不能进行迭代!!