C++容器类库-2017-7-18
容器类库
常见的容器,如vector、list、map、set、queue、stack、deque、multimap、multiset等,基本可以满足日常的开发需要。C++11引入了定长数组array,具有C风格数据的特性,更有C++的便利操作;还有unordered_set、unordered_map等新的非排序容器,可以适应不需要排序的场景。
Vector:
在c++中,vector是一个十分有用的容器,下面对这个容器做一下总结。
1 基本操作
(1)头文件#include<vector>.
(2)创建vector对象,vector<int> vec;
(3)尾部插入数字:vec.push_back(a);
(4)使用下标访问元素,cout<<vec[0]<<endl;记住下标是从0开始的。
(5)使用迭代器访问元素.
vector<int>::iterator it; for(it=vec.begin();it!=vec.end();it++) cout<<*it<<endl;
(6)插入元素: vec.insert(vec.begin()+i,a);在第i+1个元素前面插入a;
(7)删除元素: vec.erase(vec.begin()+2);删除第3个元素
vec.erase(vec.begin()+i,vec.end()+j);删除区间[i,j-1];区间从0开始
(8)向量大小:vec.size();
(9)清空:vec.clear();
2
vector的元素不仅仅可以使int,double,string,还可以是结构体,但是要注意:结构体要定义为全局的,否则会出错。下面是一段简短的程序代码:
C++ operator关键字(重载操作符)
一、为什么使用操作符重载?
对于系统的所有操作符,一般情况下,只支持基本数据类型和标准库中提供的class,对于用户自己定义的class,如果想支持基本操作,比如比较大小,判断是否相等,等等,则需要用户自己来定义关于这个操作符的具体实现。比如,判断两个人是否一样大,我们默认的规则是按照其年龄来比较,所以,在设计person 这个class的时候,我们需要考虑操作符==,而且,根据刚才的分析,比较的依据应该是age。那么为什么叫重载呢?这是因为,在编译器实现的时候,已经为我们提供了这个操作符的基本数据类型实现版本,但是现在他的操作数变成了用户定义的数据类型class,所以,需要用户自己来提供该参数版本的实现。
#include <stdio.h>
#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;
typedef struct rect{ //类型别名,结构体,结构体的定义必须是全局的
int id;
int length;
int width;
//对于向量元素是结构体的,可在结构体内部定义比较函数,下面按照id,length,width升序排序。
//操作符重载
bool operator< (const rect &a) const{
if (id!= a.id)
return id < a.id;
else{
if (length != a.length)
return length < a.length;
else
return width < a.width;
}
}
}Rect;
int main(){
vector<Rect> vec;
Rect rect;
rect.id = 1;
rect.length = 2;
rect.width = 3;
vec.push_back(rect);
vector<Rect>::iterator it = vec.begin();
cout << (*it).id << ' ' << (*it).length << ' ' << (*it).width << endl;
return 0;
}
3 算法
(1) 使用reverse将元素翻转:需要头文件#include<algorithm>
reverse(vec.begin(),vec.end());将元素翻转(在vector中,如果一个函数中需要两个迭代器,
一般后一个都不包含.)
(2)使用sort排序:需要头文件#include<algorithm>,
sort(vec.begin(),vec.end());(默认是按升序排列,即从小到大).
可以通过重写排序比较函数按照降序比较,如下:
定义排序比较函数:
bool Comp(const int &a,const int &b)
{
return a>b;
}
调用时:sort(vec.begin(),vec.end(),Comp),这样就降序排序。
list:
list是一个线性双向链表结构,它的数据由若干个节点构成,每一个节点都包括一个信息块(即实际存储的数据)、一个前驱指针和一个后驱指针。它无需分配指定的内存大小且可以任意伸缩,这是因为它存储在非连续的内存空间中,并且由指针将有序的元素链接起来。由于其结构的原因,list 随机检索的性能非常的不好,因为它不像vector 那样直接找到元素的地址,而是要从头一个一个的顺序查找,这样目标元素越靠后,它的检索时间就越长。检索时间与目标元素的位置成正比。虽然随机检索的速度不够快,但是它可以迅速地在任何节点进行插入和删除操作。因为list 的每个节点保存着它在链表中的位置,插入或删除一个元素仅对最多三个元素有所影响,不像vector 会对操作点之后的所有元素的存储地址都有所影响,这一点是vector 不可比拟的。
list 的特点:
(1) 不使用连续的内存空间这样可以随意地进行动态操作;
(2) 可以在内部任何位置快速地插入或删除,当然也可以在两端进行push 和pop 。
(3) 不能进行内部的随机访问,即不支持[ ] 操作符和vector.at() ;
(4) 相对于verctor 占用更多的内存。
初学list:需要掌握的知识:
(1)定义一个list
(2)向list中加入元素
(3)如何知道list是否为空
(4)如何使用for循环来遍历一个list
(5)如何使用STL的通用算法for_each来遍历list
(6)list成员函数begin() 和 end() 以及它们的意义
(7)iterator范围的概念和一个范围的最后一个位置实际上并不被处理这一事实
第一:定义,插入,遍历打印。
代码实现如下:
#include<stdio.h>
#include<iostream>
#include<list>
#include<string>
#include<algorithm>
using namespace std;
void PrintIt(string& StringToPoint) //字符串
{
cout << StringToPoint << endl;
}
int main()
{
list<string> test; //声明一个列表
list<string>::iterator testiterator; //声明一个迭代器
test.push_back("no");
test.push_back("march");
test.push_front("ok");
test.push_front("loleina");
test.push_front("begin");
test.push_back("end");
for (testiterator = test.begin(); testiterator != test.end(); ++testiterator)
{
cout << *testiterator << endl;
}
cout << "-------------" << endl;
for_each(test.begin(), test.end(), PrintIt); //使用了STL的通用算法for_each()来遍历,操作被很好的打包了,代码更加清晰了
cout << "-------------" << endl;
system("PAUSE");
return 0;
}
定义了一个字符串类型的list。需要包含提供STL的 list类的头文件#include <list>即可;list的成员函数push_back()把一个对象放到一个list的后面,而 push_front()把对象放到前面。
我们想要遍历一个list,比如打印一个list中的所有对象来看看list上不同操作的结果。要一个元素一个元素的遍历一个list, 可以这样做:
A. 这个程序定义了一个iterator(类似指针),testiterator。它指向了这个list的第一个元素。 这可以调用testiterator.begin()来做到,它会返回一个指向list开头的iterator。然后把它和testiterator.end()的 返回值来做比较,到了那儿的时候就停下来。 容器的end()函数会返回一个指向容器的最后一个位置的iterator。 在上面的例子中,每一次执行for循环,我们就重复引用iterator来得到我们打印的字符串。
注意:不能用testiterator.begin()+2来指向list中的第三个对象,因为STL的list是以双链的list来实现的,所有的数据存放不一定是连续存放的。 它不支持随机存取。
B.使用STL的通用算法for_each()来遍历一个iterator的范围,然后调用PrintIt()来处理每个对象。 不需要初始化、比较和给iterator增量。for_each()完成了这些工作。执行于对象上的操作被很好的 打包在这个函数以外了,不用再做那样的循环了,代码更加清晰了。
第二:count()和count_if() 的基本使用
STL的通用算法count()和count_it()用来给容器中的对象记数。就象for_each()一样,count()和count_if() 算法也是在iterator范围内来做的。
#include<stdio.h>
#include<iostream>
#include<list>
#include<string>
#include<algorithm>
using namespace std;
class IsLoleina //判别类
{
public:
bool operator()(string& name) //返回bool型
{
return name == "loleina";
}
};
int main()
{
list<string> test; //sting类型的list
list<int> score; //int类型的list
list<string>::iterator testiterator; //迭代器
test.push_back("no");
test.push_back("march");
test.push_front("ok");
test.push_front("loleina");
test.push_front("begin");
test.push_back("end");
score.push_back(100);
score.push_back(90);
score.push_back(80);
score.push_back(70);
score.push_back(100);
score.push_back(20);
int countNum(0); //初始化
//cout << countNum << endl;
countNum = count(score.begin(), score.end(), 100);
cout << "there are " << countNum << " scores of 100" << endl;
cout << "-------------" << endl;
int countLoleina(0); //初始化
countLoleina = count_if(test.begin(), test.end(), IsLoleina());
cout << "there are " << countLoleina << " loleina" << endl;
system("PAUSE");
return 0;
}
count()算法统计等于某个值的对象的个数。count_if() 带一个函数对象的参数。函数对象是一个至少带有一个operator()方法的类。有些STL算法作为参数接收函数对象并调用这个函数对象的operator()方法。函数对象被约定为STL算法调用operator时返回true或false。它们根据这个来判定这个函数。举个例子会 说的更清楚些。count_if()通过传递一个函数对象来作出比count()更加复杂的评估以确定一个对象是否应该被记数。
--------------------------函数对象的概念出现了----------------------------------------------------------
#include<algorithm>:algorithm意为"算法",是C++的标准模版库(STL)中最重要的头文件之一,提供了大量基于迭代器的非成员模版函数。
C++中的STL中map用法详解
Map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据 处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。这里说下map内部数据的组织,map内部自建一颗红黑树(一 种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的,后边我们会见识到有序的好处。
1、map简介
map是一类关联式容器。它的特点是增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。
对于迭代器来说,可以修改实值,而不能修改key。
2、map的功能
自动建立Key - value的对应。key 和 value可以是任意你需要的类型。
根据key值快速查找记录,查找的复杂度基本是Log(N),如果有1000个记录,最多查找10次,1,000,000个记录,最多查找20次。
快速插入Key -Value 记录。
快速删除记录
根据Key 修改value记录。
遍历所有记录。
3、使用map
使用map得包含map类所在的头文件
#include <map> //注意,STL头文件没有扩展名.h
map对象是模板类,需要关键字和存储对象两个模板参数:
std:map<int,string> personnel;
这样就定义了一个用int作为索引,并拥有相关联的指向string的指针.
为了使用方便,可以对模板类进行一下类型定义,
typedef map<int,CString> UDT_MAP_INT_CSTRING;
UDT_MAP_INT_CSTRING enumMap;
4、 map的构造函数
map共提供了6个构造函数,这块涉及到内存分配器这些东西,略过不表,在下面我们将接触到一些map的构造方法,这里要说下的就是,我们通常用如下方法构造一个map:
map<int, string> mapStudent;
5、 数据的插入
在构造map容器后,我们就可以往里面插入数据了。这里讲三种插入数据的方法:
第一种:用insert函数插入pair数据,下面举例说明(以下代码虽然是随手写的,应该可以在VC和GCC下编译通过,大家可以运行下看什么效果,在VC下请加入这条语句,屏蔽4786警告 #pragma warning (disable:4786) )
//数据的插入--第一种:用insert函数插入pair数据
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent; //声明一个MAP
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
map<int, string>::iterator iter;
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << iter->first << ' ' << iter->second << endl;
}
第二种:用insert函数插入value_type数据,下面举例说明
//第二种:用insert函数插入value_type数据,下面举例说明
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent; //声明
mapStudent.insert(map<int, string>::value_type(1, "student_one"));
mapStudent.insert(map<int, string>::value_type(2, "student_two"));
mapStudent.insert(map<int, string>::value_type(3, "student_three"));
map<int, string>::iterator iter;
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << iter->first << ' ' << iter->second << endl;
}
第三种:用数组方式插入数据,下面举例说明
//第三种:用数组方式插入数据,下面举例说明
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent[1] = "student_one";
mapStudent[2] = "student_two";
mapStudent[3] = "student_three";
map<int, string>::iterator iter;
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << iter->first << ' ' << iter->second << endl;
}
以上三种用法,虽然都可以实现数据的插入,但是它们是有区别的,当然了第一种和第二种在效果上是完成一样的,用insert函数插入数据,在数据的 插入上涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作是插入数据不了的,但是用数组方式就不同了,它可以覆盖以前该关键字对 应的值,用程序说明
mapStudent.insert(map<int, string>::value_type (1, "student_one"));
mapStudent.insert(map<int, string>::value_type (1, "student_two"));
上面这两条语句执行后,map中1这个关键字对应的值是“student_one”,第二条语句并没有生效,那么这就涉及到我们怎么知道insert语句是否插入成功的问题了,可以用pair来获得是否插入成功,程序如下
pair<map<int, string>::iterator, bool> Insert_Pair;
Insert_Pair = mapStudent.insert(map<int, string>::value_type (1, "student_one"));
我们通过pair的第二个变量来知道是否插入成功,它的第一个变量返回的是一个map的迭代器,如果插入成功的话Insert_Pair.second应该是true的,否则为false。
下面给出完成代码,演示插入成功与否问题
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
pair<map<int, string>::iterator, bool> Insert_Pair;
Insert_Pair = mapStudent.insert(pair<int, string>(1, "student_one"));
if (Insert_Pair.second == true)
cout << "Insert Successfully" << endl;
else
cout << "Insert Failure" << endl;
Insert_Pair = mapStudent.insert(pair<int, string>(1, "student_two"));
if (Insert_Pair.second == true)
cout << "Insert Successfully" << endl;
else
cout << "Insert Failure" << endl;
map<int, string>::iterator iter;
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << iter->first << ' ' << iter->second << endl;
}
大家可以用如下程序,看下用数组插入在数据覆盖上的效果
//验证数组形式插入数据的效果
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent[1] = "student_one";
mapStudent[1] = "student_two";
mapStudent[2] = "student_three";
map<int, string>::iterator iter;
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << iter->first << ' ' << iter->second << endl;
}
6、 map的大小
在往map里面插入了数据,我们怎么知道当前已经插入了多少数据呢,可以用size函数,用法如下:
Int nSize = mapStudent.size();
7、 数据的遍历
这里也提供三种方法,对map进行遍历
第一种:应用前向迭代器,上面举例程序中到处都是了,略过不表
第二种:应用反相迭代器,下面举例说明,要体会效果,请自个动手运行程序
//第二种,利用反向迭代器
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;//声明MAP
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
map<int, string>::reverse_iterator iter; //反向迭代器
for (iter = mapStudent.rbegin(); iter != mapStudent.rend(); iter++)
cout << iter->first << " " << iter->second << endl;
}
第三种,用数组的形式,程序说明如下:
//第三种:用数组方式,程序说明如下
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
int nSize = mapStudent.size();
//此处应注意,应该是 for(int nindex = 1; nindex <= nSize; nindex++)
//而不是 for(int nindex = 0; nindex < nSize; nindex++)
for (int nindex = 1; nindex <= nSize; nindex++)
cout << mapStudent[nindex] << endl;
}
8、 查找并获取map中的元素(包括判定这个关键字是否在map中出现)
在这里我们将体会,map在数据插入时保证有序的好处。
要判定一个数据(关键字)是否在map中出现的方法比较多,这里标题虽然是数据的查找,在这里将穿插着大量的map基本用法。
这里给出三种数据查找方法
第一种:用count函数来判定关键字是否出现,其缺点是无法定位数据出现位置,由于map的特性,一对一的映射关系,就决定了count函数的返回值只有两个,要么是0,要么是1,出现的情况,当然是返回1了
第二种:用find函数来定位数据出现位置,它返回的一个迭代器,当数据出现时,它返回数据所在位置的迭代器,如果map中没有要查找的数据,它返回的迭代器等于end函数返回的迭代器。
查找map中是否包含某个关键字条目用find()方法,传入的参数是要查找的key,在这里需要提到的是begin()和end()两个成员,
分别代表map对象中第一个条目和最后一个条目,这两个数据的类型是iterator.
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
map<int, string>::iterator iter;
iter = mapStudent.find(3);
if (iter != mapStudent.end())
cout << "Find, the value is " << iter->second << endl;
else
cout << "Do not Find" << endl;
return 0;
}
通过map对象的方法获取的iterator数据类型是一个std::pair对象,包括两个数据 iterator->first和 iterator->second分别代表关键字和存储的数据。
9、 从map中删除元素
移除某个map中某个条目用erase()
该成员方法的定义如下:
iterator erase(iterator it);//通过一个条目对象删除
iterator erase(iterator first,iterator last)//删除一个范围
size_type erase(const Key&key);//通过关键字删除
clear()就相当于enumMap.erase(enumMap.begin(),enumMap.end());
这里要用到erase函数,它有三个重载了的函数,下面在例子中详细说明它们的用法
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
map<int, string> mapStudent;
mapStudent.insert(pair<int, string>(1, "student_one"));
mapStudent.insert(pair<int, string>(2, "student_two"));
mapStudent.insert(pair<int, string>(3, "student_three"));
//如果你要演示输出效果,请选择以下的一种,你看到的效果会比较好
//如果要删除1,用迭代器删除
map<int, string>::iterator iter;
iter = mapStudent.find(1);
//mapStudent.erase(iter);
//如果要删除1,用关键字删除
//int n = mapStudent.erase(1);//如果删除了会返回1,否则返回0
//用迭代器,成片的删除
//一下代码把整个map清空
//cout << n << endl;
//mapStudent.erase(mapStudent.begin(), mapStudent.end());
mapStudent.clear();
////成片删除要注意的是,也是STL的特性,删除区间是一个前闭后开的集合
////自个加上遍历代码,打印输出吧
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << iter->first << " " << iter->second << endl;
}
10、 map中的swap用法
map中的swap不是一个容器中的元素交换,而是两个容器所有元素的交换。
11、 排序 · map中的sort问题
map中的元素是自动按Key升序排序,所以不能对map用sort函数;
这里要讲的是一点比较高深的用法了,排序问题,STL中默认是采用小于号来排序的,以上代码在排序上是不存在任何问题的,因为上面的关键字是int 型,它本身支持小于号运算,在一些特殊情况,比如关键字是一个结构体,涉及到排序就会出现问题,因为它没有小于号操作,insert等函数在编译的时候过 不去,下面给出两个方法解决这个问题。
第一种:小于号重载,程序举例。
#include <iostream>
#include <string>
#include <map>
using namespace std;
typedef struct tagStudentinfo
{
int niD;
string strName;
bool operator < (tagStudentinfo const& _A) const
{ //这个函数指定排序策略,按niD排序,如果niD相等的话,按strName排序 ,重点:操作符重载
if (niD < _A.niD) return true;
if (niD == _A.niD)
return strName.compare(_A.strName) < 0;
return false;
}
}Studentinfo, *PStudentinfo; //学生信息 //类型别名,同时声明了一个变量,和一个指针变量
int main()
{
int nSize; //用学生信息映射分数
map<Studentinfo, int>mapStudent; //声明MAP
map<Studentinfo, int>::iterator iter;
Studentinfo studentinfo;
studentinfo.niD = 1;
studentinfo.strName = "student_one";
mapStudent.insert(pair<Studentinfo, int>(studentinfo, 90));
studentinfo.niD = 2;
studentinfo.strName = "student_two";
mapStudent.insert(pair<Studentinfo, int>(studentinfo, 80));
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << iter->first.niD << ' ' << iter->first.strName << ' ' << iter->second << endl;
return 0;
}
第二种:仿函数的应用,这个时候结构体中没有直接的小于号重载,程序说明(略)
由于STL是一个统一的整体,map的很多用法都和STL中其它的东西结合在一起,比如在排序上,这里默认用的是小于号,即less<>,如果要从大到小排序呢,这里涉及到的东西很多,在此无法一一加以说明。
还要说明的是,map中由于它内部有序,由红黑树保证,因此很多函数执行的时间复杂度都是log2N的,如果用map函数可以实现的功能,而STL Algorithm也可以完成该功能,建议用map自带函数,效率高一些。
下面说下,map在空间上的特性,否则,估计你用起来会有时候表现的比较郁闷,由于map的每个数据对应红黑树上的一个节点,这个节点在不保存你的 数据时,是占用16个字节的,一个父节点指针,左右孩子指针,还有一个枚举值(标示红黑的,相当于平衡二叉树中的平衡因子),我想大家应该知道,这些地方 很费内存了吧,不说了……
12、 map的基本操作函数:
C++ maps是一种关联式容器,包含“关键字/值”对
begin() 返回指向map头部的迭代器
clear() 删除所有元素
count() 返回指定元素出现的次数
empty() 如果map为空则返回true
end() 返回指向map末尾的迭代器
equal_range() 返回特殊条目的迭代器对
erase() 删除一个元素
find() 查找一个元素
get_allocator() 返回map的配置器
insert() 插入元素
key_comp() 返回比较元素key的函数
lower_bound() 返回键值>=给定元素的第一个位置
max_size() 返回可以容纳的最大元素个数
rbegin() 返回一个指向map尾部的逆向迭代器
rend() 返回一个指向map头部的逆向迭代器
size() 返回map中元素的个数
swap() 交换两个map
upper_bound() 返回键值>给定元素的第一个位置
value_comp() 返回比较元素value的函数
1.关于set
C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、排序、删除、查找等。让用户在STL使用过程中,并不会感到陌生。
关于set,必须说明的是set关联式容器。set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。应该注意的是set中数元素的值不能直接被改变。C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。
关于set有下面几个问题:
(1)为何map和set的插入删除效率比用其他序列容器高?
大部分人说,很简单,因为对于关联容器来说,不需要做内存拷贝和内存移动。说对了,确实如此。set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。结构图可能如下:
A
/ \
B C
/ \ / \
D E F G
因此插入的时候只需要稍做变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍做变换后把指向删除节点的指针指向其他节点也OK了。这里的一切操作就是指针换来换去,和内存移动没有关系。
(2)为何每次insert之后,以前保存的iterator不会失效?
iterator这里就相当于指向节点的指针,内存没有变,指向内存的指针怎么会失效呢(当然被删除的那个元素本身已经失效了)。相对于vector来说,每一次删除和插入,指针都有可能失效,调用push_back在尾部插入也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程中可能已经被其他内存覆盖或者内存已经被释放了。即使时push_back的时候,容器内部空间可能不够,需要一块新的更大的内存,只有把以前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。特别时在和find等算法在一起使用的时候,牢记这个原则:不要使用过期的iterator。
(3)当数据元素增多时,set的插入和搜索速度变化如何?
如果你知道log2的关系你应该就彻底了解这个答案。在set中查找是使用二分查找,也就是说,如果有16个元素,最多需要比较4次就能找到结果,有32个元素,最多比较5次。那么有10000个呢?最多比较的次数为log10000,最多为14次,如果是20000个元素呢?最多不过15次。看见了吧,当数据量增大一倍的时候,搜索次数只不过多了1次,多了1/14的搜索时间而已。你明白这个道理后,就可以安心往里面放入元素了。
2.set中常用的方法
begin() ,返回set容器的第一个元素
end() ,返回set容器的最后一个元素
clear() ,删除set容器中的所有的元素
empty() ,判断set容器是否为空
max_size() ,返回set容器可能包含的元素最大个数
size() ,返回当前set容器中的元素个数
rbegin ,返回的值和end()相同
rend() ,返回的值和rbegin()相同
写一个程序练一练这几个简单操作吧:
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s; //声明一个集合
s.insert(1);
s.insert(2);
s.insert(3);
s.insert(1);
cout << "set 的 size 值为 :" << s.size() << endl;
cout << "set 的 maxsize的值为 :" << s.max_size() << endl;
cout << "set 中的第一个元素是 :" << *s.begin() << endl;
//cout << "set 中的最后一个元素是:" << *s.end() << endl;
s.clear();
if (s.empty())
{
cout << "set 为空 !!!" << endl;
}
cout << "set 的 size 值为 :" << s.size() << endl;
cout << "set 的 maxsize的值为 :" << s.max_size() << endl;
return 0;
}
小结:插入3之后虽然插入了一个1,但是我们发现set中最后一个值仍然是3哈,这就是set 。还要注意begin() 和 end()函数是不检查set是否为空的,使用前最好使用empty()检验一下set是否为空.
count() 用来查找set中某个某个键值出现的次数。这个函数在set并不是很实用,因为一个键值在set只可能出现0或1次,这样就变成了判断某一键值是否在set出现过了。
示例代码:
1 #include <iostream> 2 #include <set> 3 4 using namespace std; 5 6 int main() 7 { 8 set<int> s; 9 s.insert(1); 10 s.insert(2); 11 s.insert(3); 12 s.insert(1); 13 cout<<"set 中 1 出现的次数是 :"<<s.count(1)<<endl; 14 cout<<"set 中 4 出现的次数是 :"<<s.count(4)<<endl; 15 return 0; 16 }
equal_range() ,返回一对定位器,分别表示第一个大于或等于给定关键值的元素和 第一个大于给定关键值的元素,这个返回值是一个pair类型,如果这一对定位器中哪个返回失败,就会等于end()的值。具体这个有什么用途我还没遇到过~~~
erase(iterator) ,删除定位器iterator指向的值
erase(first,second),删除定位器first和second之间的值
erase(key_value),删除键值key_value的值
find() ,返回给定值值得定位器,如果没找到则返回end()。
insert(key_value); 将key_value插入到set中 ,返回值是pair<set<int>::iterator,bool>,bool标志着插入是否成功,而iterator代表插入的位置,若key_value已经在set中,则iterator表示的key_value在set中的位置。
inset(first,second);将定位器first到second之间的元素插入到set中,返回值是void.
示例代码:
lower_bound(key_value) ,返回第一个大于等于key_value的定位器
upper_bound(key_value),返回最后一个大于等于key_value的定位器