19.3 C++STL标准模板库大局观-容器的说明和简单应用例续

19.1 C++STL标准模板库大局观-STL总述、发展史、组成与数据结构谈
19.2 C++STL标准模板库大局观-容器分类与array、vector容器精解
19.3 C++STL标准模板库大局观-容器的说明和简单应用例续
19.4 C++STL标准模板库大局观-分配器简介、使用与工作原理说
19.5 C++STL标准模板库大局观-迭代器的概念和分类
19.6 C++STL标准模板库大局观-算法简介、内部处理与使用范例
19.7 C++STL标准模板库大局观-函数对象回顾、系统函数对象与范例
19.8 C++STL标准模板库大局观-适配器概念、分类、范例与总结

3.容器的说明和简单应用例续

请添加图片描述

  3.1 deque和stack

(1)deque

    deque这种顺序容器是一个双端队列(双向开口),deque是double-ended queue的缩写。这种容器数据存储结构的感觉如图所示。
在这里插入图片描述
    该队列相当于一个动态数组,因为它是双端的,所以无论在头部还是在尾部插入和删除数据都会很快,但是若要在中间插入数据,因为要移动其他元素,效率就会比较低。看看如下范例。

class A
{
public:
	int m_i;
	A(int tmpv) :m_i(tmpv) //构造函数
	{
		cout << "A::A()构造函数执行" << endl;
	}
	A(const A& tmpA)
	{
		m_i = tmpA.m_i;
		cout << "A::A()拷贝构造函数执行" << endl;
	}
	~A()
	{
		cout << "A::~A()析构函数执行" << endl;
	}
};
int main()
{
	deque<A> mydeque;
	for (int i = 0; i < 5; ++i)
	{
		cout << "--------------begin----------------" << endl;
		mydeque.push_front(A(i));
		cout << "--------------end----------------" << endl;
	}
	for (int i = 0; i < 5; ++i)
	{
		cout << "--------------begin2----------------" << endl;
		mydeque.push_back(A(i));
		cout << "--------------end2----------------" << endl;
	}
	for (int i = 0; i < mydeque.size(); ++i)
	{
		cout << "下标为" << i << "的元素的m_i值为:" << mydeque[i].m_i << endl;
		printf("对象mydeque[%d]的地址为%p \n", i, &mydeque[i]);
	}
}

在这里插入图片描述
    通过结果可以看到,虽然看起来图19.8绘制的deque容器的内存是连续的,但是测试结果表明它的内存并不连续。
    deque其实是一个分段数组。当插入元素多的时候,它就会把元素分到多个段中去,当然,每一段的内存是连续的(所以只能说内存是分段连续)。当然,每一段能存多少个元素或者说每一段内存有多大,笔者并没有进一步深入研究。根据上面的结果,似乎每一段能保存4个元素。

(2)stack

    stack这种顺序容器和deque类似,stack称为栈或者堆栈都可以。
    栈是一种比较基本的数据结构,特点是后进先出。deque是两端都有开口,而stack只有一端有开口。stack这种容器数据存储结构的如图所示。

请添加图片描述
    有的读者可能觉得stack和vector有点类似,但请注意,vector还支持insert、erase,也就是说,vector支持从中间插入和删除元素的操作。但是stack只支持往栈顶放入元素和从栈顶取出元素(删除元素),因为stack这种容器设计的初衷就要求具备这种特性。不难看出,deque其实是包含stack功能的

  3.2 queue

    前面讲过的deque是双端队列,这里要讲的queue是普通队列(简称队列)。队列是一种比较基本的数据结构,其特点是先进先出。也就是说,元素从一端进入,从另一端取出(删除元素),queue容器设计的初衷就要求具备这种特性。
    queue这种容器数据存储结构的感觉如图所示。

在这里插入图片描述
    可以看到,queue这个普通队列,元素是从一端入,从另一端出。所以,deque其实也是包含queue功能的。

  3.3 list

    list这种顺序容器是一个双向链表。这种容器数据存储结构的如图所示
在这里插入图片描述
    list从图中不难看到,因为list是链表,所以各个元素之间就不需要紧挨在一起,只需要用指针通过指向把元素关联起来即可。
    那么,这个list双向链表有什么特点呢?查找元素要沿着链来找,所以查找的效率并不突出,但因为它是一个双向链,所以在任意位置插入和删除元素都非常迅速——几个元素中的指针一改变指向就可以了。
    在C++面试过程中,经常被问到vector和list这两个容器的区别。通过这里的学习,它们非常明显的区别:
    vector类似于数组,它的内存空间是连续的,list是双向链表,内存空间并不连续。
  · vector插入、删除元素效率比较低,但list插入、删除元素效率就非常高。
  · vector当内存不够时,会重新找一块内存,对原来内存中的元素做析构,在新找的内存重新建立这些对象(容器中的元素)。
  · vector能进行高效的随机存取(跳转到某个指定的位置存取),而list做不到这一点。例如,要访问第5个元素,vector因为内存是连续的,所以极其迅速,它一下就能定位到第5个元素(一个指针跳跃一定数量的字节就可以到达指定位置)。反观list,要找到第5个元素,得顺着这个链逐个地找下去,一直找到第5个。所以说vector随机存取非常快,而list随机存取比较慢。

  3.4 其他

(1)forward_list

    这是C++11新增加的顺序容器,是一个单向链表。这种容器数据存储结构的感觉如图所示
在这里插入图片描述
    不难看出,forward_list比list少了一个方向的链(指针),官方称它为受限的list。少了一个方向的指针,会造成一定的访问不便,但是少了一个方向的指针后,一个容器中的元素就能节省下4字节的内存(在x86平台下)。容器中的元素若是很多,则省下的内存也很可观。
    另外,很多容器都有push_back成员函数,但是forward_list容器只有push_front,这说明这个容器最适合的是往前头插入元素。看看如下范例。

forward_list<A> myforlist;
myforlist.push_frout(A(1));
myforlist.push_frout(A(2));

(2)map和set

    map和set都是关联容器。
    map容器数据存储结构的感觉如图所示。
    观察图可以看到,该图的外观像一棵树,根据资料记载,map和set这类容器内部的实现多为红黑树(树的一种),红黑树这种数据结构本身内部有一套很好的保存数据的机制,但在这里无须研究红黑树到底是怎样保存数据的。向这种容器中保存数据的时候不需要指定数据的位置,这种容器会自动给加入的元素根据内部算法安排位置。
    另外注意到,图所示的map容器的每个元素(树节点)包含两项(图中用两个方块代表),也就是说,每个元素其实都是一个键值对(key/value)。一般来讲,这类容器的使用通常都是通过key来查找value。这种容器通过key查找value的速度非常快,但不允许在一个map容器中出现两个相同的key,所以,如果key有可能重复,请使用multimap容器。

在这里插入图片描述

{
	map<int, string> mymap; 
	mymap.insert(std::make_pair(1, "老王"));  //通过make_pair创建一个键值对,作为一个元素插入到map中
	mymap.insert(std::make_pair(2, "老李"));
	mymap.insert(pair<int, string>(3, "老赵"));
	mymap.insert(pair<int, string>(3, "老白")); //如果键重复了,则这行等于没有执行
	auto iter = mymap.find(3); //查找key为3的元素
	if (iter != mymap.end())
	{
		//找到
		printf("编号为%d,名字为%s\n", iter->first, iter->second.c_str());
	}
}

在这里插入图片描述
    再看一看set。set容器数据存储结构的感觉如图
在这里插入图片描述
    set容器中的元素没有key和value之分,每个元素就是一个value。元素保存到容器中后,容器会自动把这个元素放到一个位置,每个元素的值不允许重复,重复的元素插入进去也没有效果。如果想插入重复的元素,请使用multiset容器。
    分析上面的这种关联容器,可以总结出一些特点:插入元素的时候,因为这类容器要给插入的元素找一个合适的位置,所以插入的速度可能会慢一些,但是,得到的好处是查找的时候快,所以对于需要快速找到元素的应用场景,重点考虑使用map、set这类容器。

(3)unordered_map与unordered_set等

    以往的诸如hash_set、hash_map、hash_multiset、hash_multimap,这些老的容器也能使用,但并不推荐使用了,新版本的容器一般都是以unordered_开头了。
    以unordered_开头的容器属于无序容器(关联容器的一种),无序容器内部一般是使用哈希表(散列表)来实现的。哈希表,上一节也大概介绍了一下工作原理。其数据存储结构图如图所示。
    unordered_map和unordered_multimap每个元素同样是一个键值对,unordered_map中保存的键是不允许重复的,而unordered_multimap中保存的键可以重复。对于内部采用哈希表这种数据结构存储数据的容器,在理解的时候就可以把这种容器中的元素理解成是无序的。所以,这两个容器的数据存储结构图也可以如图所示。
    unordered_set和unordered_multiset的每个元素都是一个值,unordered_set中保存的值不允许重复,而unordered_multiset中保存的值可以重复。这两个容器的数据存储结构图如图所示。

在这里插入图片描述
在这里插入图片描述

{
	unordered_set<int> myset;
	cout << "bucket_count() = " << myset.bucket_count() << endl; //篮子数量:8
	for (int i = 0; i < 8; ++i)
	{
		myset.insert(i);
	}
	cout << "bucket_count() = " << myset.bucket_count() << endl; //8
	myset.insert(8); //装第9个元素,看篮子数量是否增加
	cout << "bucket_count() = " << myset.bucket_count() << endl; //64,突然变成64个了,挺狠(有的资料说2倍增长,这里却8倍增长)
	cout << "max_bucket_count() = " << myset.max_bucket_count() << endl; //最大篮子数量:536870911

	printf("所有篮子(本容器)里有的元素数量为%d\n", myset.size());//9
	//打印每个篮子里的元素个数
	for (int i = 0; i < myset.bucket_count(); ++i)
	{
		printf("第%d个篮子里有的元素数量为%d\n", i, myset.bucket_size(i)); //从0开始 
	}
	auto pmyfind = myset.find(5); //对于查找这种操作,如果容器本身提供,一定要用容器本身提供的,效率最高;如果容器不提供,可以考虑使用一个全局的find函数,全局find函数也是stl的组成部分,是属于算法里的
	if (pmyfind != myset.end())
	{
		cout << "元素5存在于容器中" << endl;
	}
	if (find(myset.begin(), myset.end(), 5) != myset.end()) //全局find函数(算法)
	{
		cout << "元素5存在于容器中" << endl;
	}
}

    至此,容器的话题就告一段落了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值