IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> stl容器使用中的经验(六)--考虑 map::opreator[]和map::insert()的效率问题 -> 正文阅读

[系统运维]stl容器使用中的经验(六)--考虑 map::opreator[]和map::insert()的效率问题

结论 :当效率至关重要时,如果要更新一个容器的值,优先选择map::operatot[],如果需要插入一个新的元素,则优先选择map::insert()

假设定义如下类:

class Widget{
public:
    Widget();
    Widget(const double& val);
    Widget& opeartor=(double val);
    ...
}

创建一个从int到map的映射容器。

std::map<int, Widget> m;

m[1] = 1.5;
m[2] = 0.14;
m[10] = 2.45;

上面的例子使用了map的[]操作符,我们知道,map的[]操作符和其他的一些容器,比如vector、deque等的[]操作符是不一样的。map的map::operator[]操作符被设计的目的是为了提供方便更新和插入的功能

map<key, val> m;

m[k] = v;

也就是说对于上面的容器而言,表达式m[k] = v;首先会检查容器m中是否存在键为k的元素,如果存在,则会更新该元素的值,如果不存在,则会在容器中插入一个键值对pair<k, v>

_Tp& operator[](const key_type& __k) {
    iterator __i = lower_bound(__k);
    // __i->first is greater than or equivalent to __k.
    if (__i == end() || key_comp()(__k, (*__i).first))
      __i = insert(__i, value_type(__k, _Tp()));
    return (*__i).second;
}

查看源码我们可以发现,map::operator[]会返回一个与 k 相关联的值元素的引用,如果容器中不存在该 k 对应的元素,则会向容器中插入一个默认的值类型的空对象。

所以,我们对最上面开始的例子做分析。

std::map<int, Widget> m;
m[1] = 1.5;

m[1]m.opeartor[](1)的缩写。该函数必须会返回一个Widget的引用,如果当容器中原本没有键 1 对应的元素时,容器会首先插入一个默认的对象,并返回该对象的引用。所以上面表达式在功能上等同于:

std::map<int, Widget> m;

typedef std::map<int, Widget> IntWidgetMap;
pair<IntWidgetMap::iterator, bool> iter = m.insert(IntWidgetMap::value_type(1, Widget()));

iter.first->second = 1.5;

我们通过上面的代码片段能够看出来,使用map::operator[]向容器中插入新元素时,首先会插入一个新的默认空对象,然后将值赋给该空对象。在这个过程中,我们其实已经使用了insert方法。

所以,在向容器中插入新元素时,使用insert()方法会节省开销。直接调用:

m.insert(IntWidgetMap::value_type(1, 1.5));

这样的效果和上面的函数是一样的,但是会节约三个函数的调用,一个是使用默认函数构造的临时对象,一个是对该临时对象的析构,一个是调用Widget的复制操作符。

上面我们已经看了像容器中插入元素时,使用map::inset()会比使用map::operatot[]效率更高,那么如果容器中已经存在键为k的元素,又会怎样呢?

下面是一个测试用例。

#include<iostream>
#include<map>
using namespace std;

int main()
{
	map<int, int> m;
	typedef map<int, int> IntMap;
	
	m.insert(IntMap::value_type(1, 5));
	pair<IntMap::iterator, bool> rst = m.insert({1, 4});
	
	cout << m[1] << " " << rst.second;	// 5 0
	
	rst.first->second = 7;
	
	cout << m[1] << " " << rst.second;	// 7 0
	return 0;
}

我们发现,如果已经存在,只是调用 map::insert 的话是不会修改其值的,必须要对map::insert 返回的元素的引用进行赋值操作。

m.insert({1, 4}).first->second = 7;

m[1] = 9;

从语法上我们就已经给更倾向于使用map::operator[]。并且呢,map::insert 方法的参数是一个 IntMap::value_type(也就是pair<int, int>)类型的对象,所以当调用时,我们需要创建好析构一个它的对象,如果元素值是类对象的话,还会有其他对象的创建和析构的开销。而map::operator[]操作不使用 pair 对象,因此会节省开销。

上面的两种方式选择权完全在开发者的手中,那么有没有一种可能,stl提供一个新的方法,自己判断是否存在键为k的元素,如果存在,则更新元素的值,如果不存在,则进行插入操作。

#include<iostream>
#include<map>

using namespace std;

template<typename MapType, 
		 typename KeyType, 
		 typename ValueType > 
		 typename MapType::iterator
efficientAddorUpdate(MapType& map, const KeyType& key, const ValueType& val)
{
	typename MapType::iterator iter = map.lower_bound(key);
	if(iter != map.end() && !(map.key_comp()(key, iter->first)))
	{
		iter->second = val;
		return iter;
	}
	else
	{
		typedef typename MapType::value_type MVT;
		return map.insert(iter, MVT(key, val));
	}
}

int main()
{
	map<int, int> m;
	typedef map<int, int> IntMap;
	
	m.insert(IntMap::value_type(1, 5));
	pair<IntMap::iterator, bool> rst = m.insert({1, 4});
	
	cout << m[1] << " " << rst.second;	// 5 0
	
	rst.first->second = 7;
	
	cout << m[1] << " " << rst.second;	// 7 0
	
	IntMap::iterator iter = efficientAddorUpdate(m, 1, 6);

	cout << m[1] << " " << iter->second;	// 6 6

	return 0;
}

上面的逻辑是,首先先判断容器中是否已经存在k的值,如果在,找到他的位置并进行值的修改,如果不在,找到它应该被插入的位置。而面对已经是排序的 map 容器,lower_bound方法可直观的找到它的位置。然后对其找到的元素和我们给定的键做等价对比,判断容器中是否已经存在 k 。

在这个函数中,执行上述的操作,也就意味着如果我们需要插入元素的时候,其实已经确定了元素被插入的位置,也就是说,插入元素时间复杂度是常数级。

以最开始的容器为例,调用下面的操作:

IntWidgetMap::iterator iter = efficientAddorUpdate(m, 1, 6.0);

也能够进行数值的推导,直接将6.0赋值给其中为 k 为1的元素,调用Widget类中的 Widget::operator=(double)方法,而不是重新进行类临时变量的创建和析构。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-10-12 23:53:40  更:2021-10-12 23:56:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 18:20:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码