value函数使用?

摘要:1. 概述1.1 函数作用有时用户希望能直接在数据集中取出满足条件的行列数据,不必再将数据集字段拖拽到单元格后添加过滤条件取数,此时可以使用 value 函数。1.2 函数解释value 函数有多种写法

摘要:1. 概述1.1 应用场景有时候我们希望从目标字符串中提取出所有的数字,重新组合成一个字符串,如下图所示:1.2. 实现思路方案一:先将字符串分割成数组,再对数组每一项进行筛选,保留下数字,最后以字符串

摘要:1. 概述1.1 预期效果在单元格属性>样式>文本>格式里,可以设置数字格式,但只有百分位的数据显示格式,无法设置千分位数据格式,那么如果要将小数显示为千分位,则就需要通过公式来解决。

摘要:1. 概述1.1 预期效果如何实现类似 GitHub 的打码贡献图呢,如下图所示:1.2 实现思路通过公式结合单元格扩展属性来实现。注:由于公式中使用了 TODAY 函数,因此在使用时,你需要确保数据表

摘要:1. 概述1.1 预期效果用户需要需要根据某些条件对数据进行分类赋值处理,此时 IF 函数是一个比较好的选择。比如对客户名称进行分类:「品类描述」中包含「女士」字段的就等于「女士类商品」,如果包含「男士

摘要:1. CHARCHAR(number):根据指定数字代码返回对应的字符。CHAR 函数可将计算机其他类型的数字代码转换为字符。Number:用于指定字符的数字,介于 1 和 65535 之间(

摘要:1. 概述1.1 预期效果有时候要求出两个数的最大公约数和最小公倍数,帆软里面没有对应的公式,需要实现的效果如下图所示:1.2 实现思路使用多个公式组合实现。注:若您的设计器版本为 11.0.5 及以上

摘要:1. ABS-求绝对值1)概述语法ABS(number)返回指定数字的绝对值绝对值是指没有正负符号的数值参数number需要求出绝对值的任意实数返回值number-2)示例公式结果ABS(-1

摘要:1. 概述1.1 问题描述单元格有一大段文字的时候,如何设置按指定字数换行显示,如下图示例,备注信息每 20 字换行显示。1.2 实现思路利用公式分割字符串为数组,然后在数组下标为 20 倍数的数据项后

摘要:1. 概述1.1 版本设计器版本11.0.41.2 应用场景对数据进行排名,例如对成绩进行排名,如下图所示:1.2 功能简介11.0.4 版本支持用函数对数据进行排名。1.3 函数解释1)概述语法SOR

摘要:1. 概述在企业中 FineReport 的日常使用者大致可分为三类:IT人员、报表制作者、业务人员。报表制作者:主要根据业务人员的需求设计报表、大屏等。IT 人员:主要负责 Fin

摘要:1. 描述1.1 版本报表服务器版本 JAR 包版本插件版本10.V8.49.08.01.2 应用场景把一些常用的函数放到插件里面来,方便用户使用。2. 插件介绍2.1

摘要:一、概述1.1 版本設計器版本jar包版本插件版本10.V1.011.V1.01.2 应用场景FineReport目前預設的函式無法很好地滿足海外各區域的實際

摘要:1. 概述1.1 版本设计器版本11.0.41.2 应用场景sum 函数支持计算数组或数据区域求和,但有时我们需要对满足某些条件的数据求和。比如计算销量大于2000的销售员之和,如下图所示:1.3 功能

摘要:1. 概述1.1 版本设计器版本11.0.41.2 应用场景count 函数支持计算数组或数据区域中所含项的个数,但有时我们需要进行条件计数,比如计算销量大于2000的销售员个数,如下图所示:1.3 功

摘要:注:报表函数不支持在远程设计情况下模拟计算。1. COL-当前单元格的列号1)概述语法COL()返回当前单元格的列号2)示例公式结果备注col()如果当前单元格是 A5,在 A5 中写入:=col()返

摘要:1. 概述1.1 版本设计器版本11.0.41.2 应用场景要计算环比,比如计算每个季度生产总值的同比和环比,如下图所示:1.2 功能简介11.0.4 版本支持计算同比或者环比。1.3 函数解释1)概述

摘要:1. 概述1.1 版本设计器版本11.0.41.2 应用场景要计算占比,比如计算不同月份客运量占总客运量比重,如下图所示:1.2 功能简介11.0.4 版本支持计算扩展单元格的各个扩展值在汇总值中的占比

摘要:1. 概述1.1 版本设计器版本11.0.41.2 应用场景有时候需要根据不同分组的对数据进行累计计算。比如计算不同能源类型产量的累计值,如下图所示:或者对所有的能源进行累计值计算,如下图所示

摘要:1. 概述1.1 函数作用计算数组或数据区域中所含项的个数,例如统计「地区数」和「销售员个数」,如下图所示:也可与其他函数嵌套使用,例如进行「条件计数」,计算除孙林以外的销售员个数,如下图所示

摘要:1. 概述1.1 问题描述在票据、落款类报表中,为防止随意涂改作假,常需将金额数字转换为大写中文人民币形式,此时可使用 Cnmoney函数,但如果想要直接展示大写金额同时显示小数格式,不展示为

摘要:1. 概述1.1 预期效果在报表设计中,希望能够通过公式判断当月的工作日和周末,如下图所示:1.2 实现思路方案一:若您使用的设计器为 11.0.4 及之后的版本,可以使用 ISWORKDAY

摘要:1. 概述在对日期型数据进行操作时,经常会遇到要获取当前日期、获取当前时间、获取日期的年月日等等操作。本文介绍 FineReport 常用的处理日期函数。注:当日期型公式作为参数在超链中传参时,将会传过

摘要:1. 概述1.1 版本报表服务器版本功能变动11-11.0.4支持 jpg、png、bmp 常用格式的图片,分别缓存为该格式下的文件,不会出现导出文件时体积暴涨的情况1.2 函数作用TOIMAGE 函数

摘要:1. 概述1.1 版本报表服务器版本功能变更11.0--1.2 问题描述已知函数中有 WEEK() 可获取日期所在一年中的第几周,如何获取日期在当月的第几周呢?1.3 实现思路使用 DATEI

摘要:1. 概述1.1 应用场景将数据集中的数据列直接拖拽到单元格中使用时,如果想要「条件显示」某些数据列的值,那么可以使用数据集函数。1.2 注意事项1)参数面板中不支持使用。2)不支持模拟计算,

摘要:1. 概述1.1 函数作用在使用数据集函数 SELECT 的时候,由于每行都对整个数据集做了遍历的操作,这样会导致速度比较慢。FIND 函数不需要遍历,可以直接检索, ds1.fin

摘要:1. 概述1.1 应用场景在使用报表进行数据处理展示时,字段的截取、分列必不可少。例如数据集中有字段结构为用某些分隔符连接起来的字符串,使用时需要拆分开只提取固定位置的字符串;或者替换某个字符。此时即可

摘要:1. 概述1.1 版本报表服务器版本功能变动11.0.1语法报错优化公式编辑框增加「输入提示」和「关闭前检查」选项11.0.3对于多sheet、多报表块的模板公式报错时,公式报错具体到单元格所在shee

摘要:1. 概述1.1 预期效果在报表设计中,希望能够通过公式判断每月的「法定节假日和工作日」,如下图所示:「周末工作日」判断请参考文档:周末工作日判断1.2 实现思路由于法定节假日需要根据国家假日办安排执行

摘要:1. 概述1.1 预期效果用户有时候需要将字符串依据某个符号进行分列显示,即将字符串转换为数组。例如单元格 A1 中有为「苹果汁,牛奶,柳橙汁,巧克力,牛肉干」的字符串,希望将该字符串中的每个值在不同单

摘要:1. 常用函数函数名称作用SUM求一个指定单元格区域中所有数字之和。COUNT计算数组或数据区域中所含项的个数。AVERAGE返回指定数据的平均值。CHAR根据指定数字返回对应的字符。DATE返回一个表

摘要:1. 概述1.1 预期效果有时,用户需要将数据集「文本」或「数值」字段转换为「日期」字段类型,例如将 文本型「星期三1/15/07」字段转换为标准日期字段,转换后如下图所示:1.2 实现思路可以在报表单

摘要:1. 概述FineReport 中使用函数需要用到很多的操作符。操作符不仅包含很多运算符,还包括一些报表特有的操作符。FineReport 11.0 优化了公式2. 运算符类型运算符用于指定要对公式中的

摘要:1. 概述1.1 应用场景有时候我们需要用到正则表达式进行信息的校验。例如有一张使用了「文本控件」的查询报表,输入「销售员」姓名后可查询销售员的销售情况,此时希望设置销售员文本控件的填入信息校验内容为:

摘要:1. 概述在日常设计报表时候,最常用的控件之一就是日期控件,但是因为需求的各式各样,时间展示的形式也是各不相同的。本文分享一些日期控件中使用的公式。2. 显示当天时间在日期控件的高级>控件值处将公

摘要:1. 概述1.1 应用场景在制作报表时,某数据列如收入金额是数字类型,其中数据包含小数,且小数位数不超过 2 位,对该列进行求和(使用公式 sum)等处理时,会发现最终的结果如:99

摘要:1. 概述1.1 预期效果在对于分组模板进行页码统计时,希望页码统计只在当前分组进行,即分组了就重新进行编码。例如华东地区这个组内有三页数据而华北地区这个组内有两页数据,则分别标记「组内页码」,如下图所

摘要:1. 概述1.1 函数作用数据集函数 能够从数据集中直接进行条件取数,但是有的时候用户希望某个单元格能够直接获取到数据库中的某个值,而不是先要定义一个数据集后,再去取数据。这时就可以用 SQL

摘要:1. 概述1.1 问题描述在决策报表中,设置一个参数,使用一个控件作为参数输入值,在其他控件、报表块、图表块中使用 value 函数 获取参数值,当输入的参数值改变时,获取值不会随之改变,即无联动效果。

摘要:1. 概述1.1 版本报表服务器版本插件版本11.01.01.2 应用场景与oracle数据库的序列号功能类似。可用于生成数据库主键。代替数据库序列功能,方便报表在不同数据库的移植。1.3 功能描述用函

摘要:1. 概述1.1 应用场景想要将当月最后一天和 23:59:59 拼接在一起,显示在报表中。例如现在是 7 月,那么打开报表后,显示的日期如下图所示:1.2 实现思路需要解决的第一个问题,动态获取当月最

摘要:1. 概述本文介绍如何在 FineReport 中使用计算功能。将告诉用户何时可使用计算、如何使用公式编辑器。如果刚接触 FineReport 的用户,不了解函数,则非常适合从这里着手。2. 为什么使用

摘要:1. 概述1.1 问题描述当需要判断条件多的时候,使用 IF 函数 可能会觉得用要对每种情况都进行判断,比较麻烦,那么可以使用 switch 函数与NVL函数结合进行多条件赋值。例如希望对班级

摘要:1. 概述1.1 问题描述在报表设计中,经常需要获取本周、上周、下周的第一天和最后一天,如下图所示,那么该如何获取呢?2. 实现思路使用函数即可,如下表所示:计算方式公式说明国内当前日期 to

摘要:1. 概述1.1 问题描述若希望通过日期控件控制所选择的年月,点击查询自动扩展显示出所选年月中的每一天的日期,要如何实现呢?1.2 实现思路添加一个日期控件用于选择年月,然后使用 Range函数生成一个

摘要:1. 概述1.1 问题描述在平时设置报表过程中,可能会经常需要在单元格进行字符串拼接,可以使用字符串拼接符号「+」,但如果要拼接的内容是数字字符串,则不支持,例如希望拼接 001 和 1201110 ,

摘要:1. 概述在使用 FineReport 进行可视化展示时,经常会需要对日期数据进行处理,本文介绍几种日期类型数据的处理应用场景。2. 获取月份或日期的时候显示 2 位2.1 问题描述在使用公式month

摘要: 1. 概述1.1 版本报表服务器版本 JAR 包版本插件版本10.V1.11.2 应用场景把一些常用的自定义函数封装成插件,方便用户使用。2. 插件介绍2.1

摘要:1. 概述1.1 问题描述日期函数在平时工作与生活中使用非常广泛,在日期中添加或减去指定的时间间隔也显得尤为重要。目前官方提供了一些日期函数的运用。详情参见:日期和时间函数例如:dateadd()日期差

摘要:1. 概述1.1 问题描述对于数字结果要求保留指定位数的有效数字,例如从一个数的左边第一个非0数字起,到末位数字止,所有的数字都是这个数的有效数字,如下图所示:1.2 实现思路获取数字 1 到 9 第一

摘要:1. 概述1.1 问题描述当数据库中某张表有时间字段,且几乎每分每秒都会有一条数据生成,为减少报表显示数据量,可以通过时间间隔来筛选数据,仅显示部分数据。例如:在「开始时间」和「结束时间」控件内里面输入

摘要:1. 概述1.1 应用场景有时我们对两个字符串的比较,要忽略字符的排列顺序来比较字符串的内容是否相同,如下:1.2 实现思路先将字符串分割成数组,然后对数组进行排序,最后比较排序后的数组,输出结果。2.

摘要:1. 概述1.1 预期效果有时需要从最后一个指定的字符的位置,截取后面的字符串,例如要从路径中截取出文件名,如下图所示:1.2 实现思路通过函数反转将最后一个指定字符变成第一个指定字符;再找出这个字符的

摘要:1. 概述1.1 应用场景有时候我们要判断一个多项字符串是否至少有一项被另外个字符串包含。如:有一批货原本是属于"A 供货商,B 供货商"的,结果却被"B 供货商,C 供货

摘要:1. 概述1.1 应用场景有时候我们希望找出字符串中指定字符共有多少个,如下面,A 或AA 字符的个数:1.2 解决思路将字符转化为数组,筛选出指定的字符,然后统计数组长度,即为指定字符的个数。或者通过

摘要:1. 描述对于日期型数据求平均数,由于 average() 函数不支持对日期型数据求平均数因此求出的值是 0,但若希望对日期求平均数要如何实现呢?如下图所示:2. 思路通过使用 dateton

摘要:1. 函数作用在制作报表时,我们可能需要根据某一单元格的值对数据集进行检索并返回对应的值。此情况时,可使用 map() 函数,根据数据集的名字,找到对应的数据集,找到其中索引列的值为 key 所对应的返

摘要:1. 概述1.1 问题描述在数据库中存储了一列数据为日期,格式为 yyyy-MM-dd,如下图所示:如何将日期转化为英文简称格式呢,实现效果如下图所示:1.2 实现思路可以参考文档 公式形态

摘要:1. 概述1.1 应用场景在扩展出来的报表中,如何对奇数行的数据进行汇总,又如何对偶数行的数据进行汇总呢?如下:1.2 解决思路1)利用数组函数获取扩展后的值,再判断数组下标的奇偶进行汇总。2)利用层次

摘要:1. 正弦值-SIN1)概述语法SIN(number)返回指定角度的正弦值参数number待求正弦值的以弧度表示的角度如果参数的单位是度,将其乘以 PI()/180 即可转换成弧度返回值Num

摘要:1. 概述1.1 问题现象不论是普通报表、聚合报表还是决策报表,当涉及跨 sheet 页或跨报表块使用函数过滤时,若公式中包含了 {} 这种大括号的条件,则公式无效。例如:跨 sheet 页计数时,使用

摘要:1. 函数作用报表中,若使用到复杂的FR脚本表达式,如:=if (很长很长的公式 > 0,执行语句 1(很长很长的公式),执行语句 2(很长很长的公式)),首先想到的是:将很长很长的公式

摘要:1. 概述1.1 版本报表服务器版本JAR包版本增强公式编辑器插件版本10.V4.99.V4.71.2 问题描述FineReport 中使用公式时,都是采用结

摘要:1. 描述因政府、事业单位的正式文件中的落款日期都是中文的。如:在 FineReport 中制作填报模板,使用了日期控件,希望在做填报时,将当前日期控件中选择的日期值(FR 中默认是yyyy-MM-dd

摘要:1. 问题描述字符串与数组的相加有两种方式,一是直接使用+号,另外一种是使用字符串连接函数 concatenate,那么这两个有区别吗?2. 使用+号连接字符串及数组字符串会和每个数

摘要:1. 引言帆软作为报表工具,在扩展展现的时候,经常会用到数组的知识。我们可以将帆软报表中每一个可以扩展的单元格都作为一个数组来考虑进行计算。而我们选择将扩展单元格列表显示的时候,相当于直接用“,”逗号做

摘要:1. 描述大家都知道 Excel 功能非常强大,但是 FineReport 中有些 Excel 功能是无法实现的;比如一计算公式:E3*F1,我想要实现 E3 列单元格递增而 F1 单元格不变动 的计算

 当使用的参数 (参数:函数中用来执行操作或计算的值。参数的类型与函数有关。函数中常用的参数类型包括数字、文本、单元格引用和名称。)或操作数 (操作数:操作数是公式中运算符任意一侧的项。在 Excel 中,操作数可以是值、单元格引用、名称、标签和函数。
)类型错误时,出现这种错误。 当公式需要数字或逻辑值(例如 TRUE 或 FALSE)时,却输入了文本 Microsoft Excel 无法将文本转换为正确的数据类型。确认公式或函数所需的运算符或参数正确,并且公式引用的单元格中包含有效的数值。
例如,如果单元格 A5 中包含数字且单元格 A6 中包含文本“Not available”,则公式 =A5+A6 将返回错误值 #VALUE!。 输入或编辑了数组公式,然后按了 Enter 选定包含数组公式 (数组公式:数组公式对一组或多组值执行多重计算,并返回一个或多个结果。
数组公式括于大括号 ({ }) 中。按 Ctrl+Shift+Enter 可以输入数组公式。)的单元格或单元格区域,按 F2 编辑公式,然后按 Ctrl+Shift+Enter。 将单元格引用、公式或函数作为数组常量输入 确认数组常量 (常量:不进行计算的值,因此也不会发生变化。
例如,数字 210 以及文本“每季度收入”都是常量。表达式以及表达式产生的值都不是常量。)不是单元格引用、公式或函数。 为需要单个值(而不是区域)的运算符或函数提供了区域 更改数值区域,使其包含公式所在的数据行或列。
在某个矩阵工作表函数中使用了无效的矩阵 确认矩阵 (矩阵:数值或单元格区域的矩形数组,该数组与其他数组或区域相组合以计算出多个和或乘积。Excel 预先定义了可生成和或乘积的矩阵函数。)的维数对矩阵参数是正确的。 运行的宏程序所输入的函数返回 VALUE!

这是《逆袭进大厂》系列的第四期,本期是 C++ 重头戏,也就是标准模板库 STL的内容,本期是 24098个字。

按照自己经历过的三十多场校招面试来看,校招 C++岗区分度比较高的两个知识点就是 虚函数 和 STL知识。

说人话就是 虚函数 和 STL部分 答得好一点,面试评级就好一点,最后拿到 好offer 的希望就大一些。

你懂我意思叭?四字概括就是 "钱多,速来"!

如果还没看过前三期的小伙伴们可以去温习一下前面三篇文章

STL中的hashtable使用的是 开链法解决hash冲突问题,如下图所示。

如果插入hashtable的元素个数超过了bucket的容量,就要进行重建table操作,即找出下一个质数,创建新的buckets vector,重新计算元素在新hashtable的位置。

《STL源码解析》侯捷

traits技法利用“内嵌型别“的编程技巧与 编译器的template参数推导功能,增强C++未能提供的关于型别认证方面的能力。常用的有iterator_traits和type_traits。

被称为 特性萃取机,能够方面的让外界获取以下5中型别:

  • value_type:迭代器所指对象的型别
  • pointer:迭代器所指向的型别
  • reference:迭代器所引用的型别

dtor(析构函数),如果答案是否定的,可以采取直接操作内存的方式提高效率,一般来说,type_traits支持以下5中类型的判断:

由于编译器只针对class object形式的参数进行参数推到,因此上式的返回结果不应该是个bool值,实际上使用的是一种空的结构体:

这两个结构体没有任何成员,不会带来其他的负担,又能满足需求,可谓一举两得

当然,如果我们自行定义了一个Shape类型,也可以针对这个Shape设计type_traits的特化版本

189、STL的两级空间配置器

1、首先明白为什么需要二级空间配置器?

我们知道动态开辟内存时,要在堆上申请,但若是我们需要

频繁的在堆开辟释放内存,则就会 在堆上造成很多外部碎片,浪费了内存空间;

每次都要进行调用 malloc、free函数等操作,使空间就会增加一些附加信息,降低了空间利用率;

随着外部碎片增多,内存分配器在找不到合适内存情况下需要合并空闲块,浪费了时间,大大降低了效率。

于是就设置了二级空间配置器, 当开辟内存<=128bytes时,即视为开辟小块内存,则调用二级空间配置器。

关于STL中一级空间配置器和二级空间配置器的选择上,一般默认 选择的为二级空间配置器。如果大于128字节再转去一级配置器器。

1、直接allocate分配内存,其实就是malloc来分配内存,成功则直接返回,失败就调用处理函数

2、如果用户自定义了内存分配失败的处理函数就调用,没有的话就返回异常

3、如果自定义了处理函数就进行处理,完事再继续分配试试

1、维护16条链表,分别是0-15号链表,最小8字节,以8字节逐渐递增,最大128字节,你传入一个字节参数,表示你需要多大的内存,会自动帮你校对到第几号链表(如需要13bytes空间,我们会给它分配16bytes大小),在找到第你个链表后查看链表是否为空,如果不为空直接从对应的free_list中拔出,将已经拨出的指针向后移动一位。

2、对应的free_list为空,先看其内存池是不是空时,如果内存池不为空:

(1)先检验它剩余空间是否够20个节点大小(即所需内存大小(提升后) * 20),若足够则直接从内存池中拿出20个节点大小空间,将其中一个分配给用户使用,另外19个当作自由链表中的区块挂在相应的free_list下,这样下次再有相同大小的内存需求时,可直接拨出。

(2)如果不够20个节点大小,则看它是否能满足1个节点大小,如果够的话则直接拿出一个分配给用户,然后从剩余的空间中分配尽可能多的节点挂在相应的free_list中。

(3)如果连一个节点内存都不能满足的话,则将内存池中剩余的空间挂在相应的free_list中(找到相应的free_list),然后再给内存池申请内存,转到3。

3、内存池为空,申请内存

此时二级空间配置器会使用malloc从heap上申请内存,(一次所申请的内存大小为2 * 所需节点内存大小(提升后)* 20 + 一段额外空间),申请40块,一半拿来用,一半放内存池中。

在第三种情况下,如果malloc失败了,说明heap上没有足够空间分配给我们了,这时,二级空间配置器会从比所需节点空间大的free_list中一一搜索,从比它所需节点空间大的free_list中拔除一个节点来使用。如果这也没找到,说明比其大的free_list中都没有自由区块了,那就要调用一级适配器了。

释放时调用deallocate函数,若释放的n>128,则调用一级空间配置器,否则就直接将内存块挂上自由链表的合适位置。

STL二级空间配置器虽然解决了外部碎片与提高了效率,但它同时增加了一些 缺点

1.因为自由链表的管理问题,它会把我们需求的内存块自动提升为8的倍数,这时若你需要1个字节,它会给你8个字节,即浪费了7个字节,所以它又引入了内部碎片的问题,若相似情况出现很多次,就会造成很多内部碎片;

2.二级空间配置器是在堆上申请大块的狭义内存池,然后用自由链表管理,供现在使用,在程序执行过程中,它将申请的内存一块一块都挂在自由链表上,即不会还给操作系统,并且它的实现中所有成员全是静态的,所以它申请的所有内存只有在进程结束才会释放内存,还给操作系统,由此带来的问题有:1.即我不断的开辟小块内存,最后整个堆上的空间都被挂在自由链表上,若我想开辟大块内存就会失败;2.若自由链表上挂很多内存块没有被使用,当前进程又占着内存不释放,这时别的进程在堆上申请不到空间,也不可以使用当前进程的空闲内存,由此就会引发多种问题。

GC4.9之后就没有第一级了,只有第二级

有个自动调整的函数:你传入一个字节参数,表示你需要多大的内存,会自动帮你校对到第几号链表(0-15号链表,最小8字节 最大128字节)

allocate函数:如果要分配的内存大于128字节,就转用第一级分配器,否则也就是小于128字节。那么首先判断落在第几号链表,定位到了,先判断链表是不是空,如果是空就需要充值,(调节到8的倍数,默认一次申请20个区块,当然了也要判断20个是不是能够申请到,如果只申请到一个那就直接返回好了,不止一个的话,把第2到第n个挨个挂到当前链表上,第一个返回回去给容器用,n是不大于20的,当然了如果不在1-20之间,那就是内存碎片了,那就先把碎片挂到某一条链表上,然后再重新malloc了,malloc 2*20个块)去内存池去拿或者重新分配。不为空的话

vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。因此能高效的进行随机存取,时间复杂度为o(1);但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。连续存储结构:vector是可以实现动态增长的对象数组,支持对数组高效率的访问和在数组尾端的删除和插入操作,在中间和头部删除和插入相对不易,需要挪动大量的数据。它与数组最大的区别就是vector不需程序员自己去考虑容量问题,库里面本身已经实现了容量的动态增长,而数组需要程序员手动写入扩容函数进形扩容。

list是由双向链表实现的,因此内存空间是不连续的。只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n);但由于链表的特点,能高效地进行插入和删除。非连续存储结构:list是一个双链表结构,支持对链表的双向遍历。每个节点包括三个信息:元素本身,指向前一个元素的节点(prev)和指向下一个元素的节点(next)。因此list可以高效率的对数据元素任意位置进行访问和插入删除等操作。由于涉及对额外指针的维护,所以开销比较大。

vector的随机访问效率高,但在插入和删除时(不包括尾部)需要挪动数据,不易操作。list的访问要遍历整个链表,它的随机访问效率低。但对数据的插入和删除操作等都比较方便,改变指针的指向即可。list是单向的,vector是双向的。vector中的迭代器在使用后就失效了,而list的迭代器在使用之后还可以继续使用。

list不提供随机访问,所以不能用下标直接访问到某个位置的元素,要访问list里的元素只能遍历,不过你要是只需要访问list的最后N个元素的话,可以用反向迭代器来遍历:

191、STL 中vector删除其中的元素,迭代器如何变化?为什么是两倍扩容?释放空间?

size函数返回的是已用空间大小,capacity返回的是总空间大小,capacity-size则是剩余的可用空间大小。当size和capacity相等,说明vector目前的空间已被用完,如果再添加新元素,则会引起vector空间的动态增长。

由于动态增长会引起重新分配内存空间、拷贝原空间、释放原空间,这些过程会降低程序效率。因此,可以使用reserve(n)预先分配一块较大的指定大小的内存空间,这样当指定大小的内存空间未使用完时,是不会重新分配内存空间的,这样便提升了效率。只有当n>capacity时,调用reserve(n)才会改变vector容量。

resize成员函数只改变元素的数目,不改变vector的容量。

2、当空间大小不足时,新分配的空间大小为原空间大小的2倍。

3、使用reserve预先分配一块内存后,在空间未满的情况下,不会引起重新分配,从而提升了效率。

4、当reserve分配的空间比原空间小时,是不会引起重新分配的。

5、resize函数只改变容器的元素数目,未改变容器大小。

不同的编译器,vector有不同的扩容大小。在vs下是1.5倍,在GCC下是2倍;

空间和时间的权衡。简单来说, 空间分配的多,平摊时间复杂度低,但浪费空间也多。

使用k=2增长因子的问题在于,每次扩展的新尺寸必然刚好大于之前分配的总和,也就是说,之前分配的内存空间不可能被使用。这样对内存不友好。最好把增长因子设为(1,2)

对比可以发现采用采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到O(n)的时间复杂度,因此,使用成倍的方式扩容。

由于vector的内存占用空间只增不减,比如你首先分配了10,000个字节,然后erase掉后面9,999个,留下一个有效元素,但是内存占用仍为10,000个。所有内存空间是在vector析构时候才能被系统回收。empty用来检测容器是否为空的,clear可以清空所有元素。

但是即使clear,vector所占用的内存空间依然如故,无法保证内存的回收。

如果需要空间动态缩小,可以考虑使用deque。如果vector,可以用swap来帮助你释放内存。

将Vec的内存空洞清除;

192、容器内部删除一个元素

erase迭代器不仅使所指向被删除的迭代器失效,而且使被删元素之后的所有迭代器失效(list除外),所以不能使用erase(it++)的方式,但是erase的返回值是下一个有效迭代器;

erase迭代器只是被删除元素的迭代器失效,但是返回值是void,所以要采用erase(it++)的方式删除迭代器;

193、STL迭代器如何实现

1、 迭代器是一种抽象的设计理念,通过迭代器可以在不了解容器内部原理的情况下遍历容器,除此之外,STL中迭代器一个最重要的作用就是作为容器与STL算法的粘合剂。

2、 迭代器的作用就是提供一个遍历容器内部所有元素的接口,因此迭代器内部必须保存一个与容器相关联的指针,然后重载各种运算操作来遍历,其中最重要的是*运算符与->运算符,以及++、--等可能需要重载的运算符重载。这和C++中的智能指针很像,智能指针也是将一个指针封装,然后通过引用计数或是其他方法完成自动释放内存的功能。

194、map、set是怎么实现的,红黑树是怎么能够同时实现这两种容器?为什么使用红黑树?

1) 他们的底层都是以红黑树的结构实现,因此插入删除等操作都在O(logn时间内完成,因此可以完成高效的插入删除;

2) 在这里我们定义了一个模版参数,如果它是key那么它就是set,如果它是map,那么它就是map;底层是红黑树,实现map的红黑树的节点数据类型是key+value,而实现set的节点数据类型是value

3) 因为map和set要求是自动排序的,红黑树能够实现这一功能,而且时间复杂度比较低。

195、如何在共享内存上使用stl标准库?

1) 想像一下把STL容器,例如map, vector, list等等,放入共享内存中,IPC一旦有了这些强大的通用数据结构做辅助,无疑进程间通信的能力一下子强大了很多。

我们没必要再为共享内存设计其他额外的数据结构,另外,STL的高度可扩展性将为IPC所驱使。STL容器被良好的封装,默认情况下有它们自己的内存管理方案。

当一个元素被插入到一个STL列表(list)中时,列表容器自动为其分配内存,保存数据。考虑到要将STL容器放到共享内存中,而容器却自己在堆上分配内存。

一个最笨拙的办法是在堆上构造STL容器,然后把容器复制到共享内存,并且确保所有容器的内部分配的内存指向共享内存中的相应区域,这基本是个不可能完成的任务。

2) 假设进程A在共享内存中放入了数个容器,进程B如何找到这些容器呢?

一个方法就是进程A把容器放在共享内存中的确定地址上(fixed offsets),则进程B可以从该已知地址上获取容器。另外一个改进点的办法是,进程A先在共享内存某块确定地址上放置一个map容器,然后进程A再创建其他容器,然后给其取个名字和地址一并保存到这个map容器里。

进程B知道如何获取该保存了地址映射的map容器,然后同样再根据名字取得其他容器的地址。

4) 用数组方式插入数据

2) 存储时是根据key的hash值判断元素是否相同,即unordered_map内部元素是无序的,而map中的元素是按照二叉搜索树存储,进行中序遍历会得到有序遍历。

5) 如果需要内部元素自动排序,使用map,不需要排序使用unordered_map

8) 什么时候扩容:当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值---即当前数组的长度乘以加载因子的值的时候,就要自动扩容啦。

9) 扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。

198、vector越界访问下标,map越界访问下标?vector删除元素时会不会释放空间?

1) 通过下标访问vector中的元素时不会做边界检查,即便下标越界。

也就是说,下标与first迭代器相加的结果超过了finish迭代器的位置,程序也不会报错,而是返回这个地址中存储的值。

如果想在访问vector中的元素时首先进行边界检查,可以使用vector中的at函数。通过使用at函数不但可以通过下标访问vector中的元素,而且在at函数内部会对下标进行边界检查。

2) map的下标运算符[]的作用是:将key作为下标去执行查找,并返回相应的值;如果不存在这个key,就将一个具有该key和value的某人值插入这个map。

3) erase函数,只能删除内容,不能改变容量大小;

erase成员函数,它删除了itVect迭代器指向的元素,并且返回要被删除的itVect之后的迭代器,迭代器相当于一个智能指针;clear函数,只能清空内容,不能改变容量大小;如果要想在删除内容的同时释放内存,那么你可以选择deque容器。

1) map的下标运算符[]的作用是:将关键码作为下标去执行查找,并返回对应的值;如果不存在这个关键码,就将一个具有该关键码和值类型的默认值的项插入这个map。

2) map的find函数:用关键码执行查找,找到了返回该位置的迭代器;如果不存在这个关键码,就返回尾迭代器。

1) list不再能够像vector一样以普通指针作为迭代器,因为其节点不保证在存储空间中连续存在;

2) list插入操作和结合才做都不会造成原有的list迭代器失效;

3) list不仅是一个双向链表,而且还是一个环状双向链表,所以它只需要一个指针;

4) list不像vector那样有可能在空间不足时做重新配置、数据移动的操作,所以插入前的所有迭代器在插入操作之后都仍然有效;

5) deque是一种双向开口的连续线性空间,所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作;可以在头尾两端分别做元素的插入和删除操作;

6) deque和vector最大的差异,一在于deque允许常数时间内对起头端进行元素的插入或移除操作,二在于deque没有所谓容量概念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,deque没有所谓的空间保留功能。

1) 第一级配置器直接使用malloc、free和relloc,第二级配置器视情况采用不同的策略:当配置区块超过128bytes时,视之为足够大,便调用第一级配置器;当配置器区块小于128bytes时,为了降低额外负担,使用复杂的内存池整理方式,而不再用一级配置器;

2) 第二级配置器主动将任何小额区块的内存需求量上调至8的倍数,并维护16个free-list,各自管理大小为8~128bytes的小额区块;

3) 空间配置函数allocate,首先判断区块大小,大于128就直接调用第一级配置器,小于128时就检查对应的free-list。如果free-list之内有可用区块,就直接拿来用,如果没有可用区块,就将区块大小调整至8的倍数,然后调用refill,为free-list重新分配空间;

4) 空间释放函数deallocate,该函数首先判断区块大小,大于128bytes时,直接调用一级配置器,小于128bytes就找到对应的free-list然后释放内存。

1) hash table表格内的元素称为桶(bucket),而由桶所链接的元素称为节点(node),其中存入桶元素的容器为stl本身很重要的一种序列式容器——vector容器。之所以选择vector为存放桶元素的基础容器,主要是因为vector容器本身具有动态扩容能力,无需人工干预。

2) 向前操作:首先尝试从目前所指的节点出发,前进一个位置(节点),由于节点被安置于list内,所以利用节点的next指针即可轻易完成前进操作,如果目前正巧是list的尾端,就跳至下一个bucket身上,那正是指向下一个list的头部节点。

203、常见容器性质总结?

1.vector 底层数据结构为数组 ,支持快速随机访问

2.list 底层数据结构为双向链表,支持快速增删

3.deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问

每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品.

4.stack 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时

5.queue 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)

6.priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现

7.set 底层数据结构为红黑树,有序,不重复

8.multiset 底层数据结构为红黑树,有序,可重复

9.map 底层数据结构为红黑树,有序,不重复

10.multimap 底层数据结构为红黑树,有序,可重复

204、vector的增加删除都是怎么做的?为什么是1.5或者是2倍?

1) 新增元素:vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素;

2) 对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了 ;

4) 不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容。

对比可以发现采用采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到O(n)的时间复杂度,因此,使用成倍的方式扩容。

1) 考虑可能产生的堆空间浪费,成倍增长倍数不能太大,使用较为广泛的扩容方式有两种,以2二倍的方式扩容,或者以1.5倍的方式扩容。

2) 以2倍的方式扩容,导致下一次申请的内存必然大于之前分配内存的总和,导致之前分配的内存不能再被使用,所以最好倍增长因子设置为(1,2)之间:

3) 向量容器vector的成员函数pop_back可以删除最后一个元素.

4) 而函数erase可以删除由一个iterator指出的元素,也可以删除一个指定范围的元素。

5) 还可以采用通用算法remove来删除vector容器中的元素.

6) 不同的是:采用remove一般情况下不会改变容器的大小,而pop_back与erase等成员函数会改变容器的大小。

205、说一下STL每种容器对应的迭代器

vector是一种序列式容器,其数据安排以及操作方式与array非常类似,两者的唯一差别就是对于空间运用的灵活性,众所周知,array占用的是静态空间,一旦配置了就不可以改变大小,如果遇到空间不足的情况还要自行创建更大的空间,并手动将数据拷贝到新的空间中,再把原来的空间释放。vector则使用灵活的动态空间配置,维护一块 连续的线性空间,在空间不足时,可以自动扩展空间容纳新元素,做到按需供给。其在扩充空间的过程中仍然需要经历: 重新配置空间,移动数据,释放原空间等操作。这里需要说明一下动态扩容的规则:以原大小的两倍配置另外一块较大的空间(或者旧长度+新增元素的个数),源码:

运行上述代码,一开始配置了一块长度为2的空间,接下来插入一个数据,长度变为原来的两倍,为4,此时已占用的长度为3,再继续两个数据,此时长度变为8,可以清晰的看到空间的变化过程

需要注意的是,频繁对vector调用push_back对性能是有影响的,这是因为每插入一个元素,如果空间够用的话还能直接插入,若空间不够用,则需要重新配置空间,移动数据,释放原空间等操作,对程序性能会造成一定的影响

list是双向链表,而slist(single linked list)是单向链表,它们的主要区别在于:前者的迭代器是双向的Bidirectional iterator,后者的迭代器属于单向的Forward iterator。虽然slist的很多功能不如list灵活,但是其所耗用的空间更小,操作更快。

根据STL的习惯,插入操作会将新元素插入到指定位置之前,而非之后,然而slist是不能回头的,只能往后走,因此在slist的其他位置插入或者移除元素是十分不明智的,但是在slist开头却是可取的,slist特别提供了insert_after和erase_after供灵活应用。考虑到效率问题,slist只提供push_front操作,元素插入到slist后,存储的次序和输入的次序是相反的

slist的单向迭代器如下图所示:

slist默认采用alloc空间配置器配置节点的空间,其数据结构主要代码如下

《STL源码剖析》 侯捷

相比于vector的连续线型空间,list显得复杂许多,但是它的好处在于插入或删除都只作用于一个元素空间,因此list对空间的运用是十分精准的,对任何位置元素的插入和删除都是常数时间。list不能保证节点在存储空间中连续存储,也拥有迭代器,迭代器的“++”、“--”操作对于的是指针的操作,list提供的迭代器类型是双向迭代器:Bidirectional iterators。

list节点的结构见如下源码:

从源码可看出list显然是一个双向链表。list与vector的另一个区别是,在插入和接合操作之后,都不会造成原迭代器失效,而vector可能因为空间重新配置导致迭代器失效。

此外list也是一个环形链表,因此只要一个指针便能完整表现整个链表。list中node节点指针始终指向尾端的一个空白节点,因此是一种“前闭后开”的区间结构

list的空间管理默认采用alloc作为空间配置器,为了方便的以节点大小为配置单位,还定义一个list_node_allocator函数可一次性配置多个节点空间

vector是单向开口(尾部)的连续线性空间,deque则是一种双向开口的连续线性空间,虽然vector也可以在头尾进行元素操作,但是其头部操作的效率十分低下(主要是涉及到整体的移动)

deque和vector的最大差异一个是deque运行在常数时间内对头端进行元素操作,二是deque没有容量的概念,它是动态地以分段连续空间组合而成,可以随时增加一段新的空间并链接起来

deque虽然也提供随机访问的迭代器,但是其迭代器并不是普通的指针,其复杂程度比vector高很多,因此除非必要,否则一般使用vector而非deque。如果需要对deque排序,可以先将deque中的元素复制到vector中,利用sort对vector排序,再将结果复制回deque

deque由一段一段的定量连续空间组成,一旦需要增加新的空间,只要配置一段定量连续空间拼接在头部或尾部即可,因此deque的最大任务是如何维护这个整体的连续性

deque的数据结构如下:

deque内部有一个指针指向map,map是一小块连续空间,其中的每个元素称为一个节点,node,每个node都是一个指针,指向另一段较大的连续空间,称为缓冲区,这里就是deque中实际存放数据的区域,默认大小512bytes。整体结构如上图所示。

deque的迭代器数据结构如下:

T* cur; //迭代器所指缓冲区当前的元素

T* first; //迭代器所指缓冲区第一个元素

T* last; //迭代器所指缓冲区最后一个元素

从deque的迭代器数据结构可以看出,为了保持与容器联结,迭代器主要包含上述4个元素

deque迭代器的“++”、“--”操作是远比vector迭代器繁琐,其主要工作在于缓冲区边界,如何从当前缓冲区跳到另一个缓冲区,当然deque内部在插入元素时,如果map中node数量全部使用完,且node指向的缓冲区也没有多余的空间,这时会配置新的map(2倍于当前+2的数量)来容纳更多的node,也就是可以指向更多的缓冲区。在deque删除元素时,也提供了元素的析构和空闲缓冲区空间的释放等机制。

stack(栈)是一种先进后出(First In Last Out)的数据结构,只有一个入口和出口,那就是栈顶,除了获取栈顶元素外,没有其他方法可以获取到内部的其他元素,其结构图如下:

stack这种单向开口的数据结构很容易由 双向开口的deque和list形成,只需要根据stack的性质对应移除某些接口即可实现,stack的源码如下:

从stack的数据结构可以看出,其所有操作都是围绕Sequence完成,而Sequence默认是deque数据结构。stack这种“修改某种接口,形成另一种风貌”的行为,成为adapter(配接器)。常将其归类为container adapter而非container

stack除了默认使用deque作为其底层容器之外,也可以使用双向开口的list,只需要在初始化stack时,将list作为第二个参数即可。由于stack只能操作顶端的元素,因此其内部元素无法被访问,也不提供迭代器。

queue(队列)是一种先进先出(First In First Out)的数据结构,只有一个入口和一个出口,分别位于最底端和最顶端,出口元素外,没有其他方法可以获取到内部的其他元素,其结构图如下:

类似的,queue这种“先进先出”的数据结构很容易由双向开口的deque和list形成,只需要根据queue的性质对应移除某些接口即可实现,queue的源码如下:

同样,queue也可以使用list作为底层容器,不具有遍历功能,没有迭代器。

《STL源码剖析》 侯捷

heap(堆)并不是STL的容器组件,是priority queue(优先队列)的底层实现机制,因为binary max heap(大根堆)总是最大值位于堆的根部,优先级最高。

binary heap本质是一种complete binary tree(完全二叉树),整棵binary tree除了最底层的叶节点之外,都是填满的,但是叶节点从左到右不会出现空隙,如下图所示就是一颗完全二叉树

完全二叉树内没有任何节点漏洞,是非常紧凑的,这样的一个好处是可以使用array来存储所有的节点,因为当其中某个节点位于 处,其左节点必定位于

处,右节点位于 处,父节点位于 (向下取整)处。这种以array表示tree的方式称为隐式表述法。

因此我们可以使用一个array和一组heap算法来实现max heap(每个节点的值大于等于其子节点的值)和min heap(每个节点的值小于等于其子节点的值)。由于array不能动态的改变空间大小,用vector代替array是一个不错的选择。

那heap算法有哪些?常见有的插入、弹出、排序和构造算法,下面一一进行描述。

由于完全二叉树的性质,新插入的元素一定是位于树的最底层作为叶子节点,并填补由左至右的第一个空格。事实上,在刚执行插入操作时,新元素位于底层vector的end处,之后是一个称为percolate up(上溯)的过程,举个例子如下图:

新元素50在插入堆中后,先放在vector的end存着,之后执行上溯过程,调整其根结点的位置,以便满足max heap的性质,如果了解大根堆的话,这个原理跟大根堆的调整过程是一样的。

heap的pop操作实际弹出的是根节点吗,但在heap内部执行pop_heap时,只是将其移动到vector的最后位置,然后再为这个被挤走的元素找到一个合适的安放位置,使整颗树满足完全二叉树的条件。这个被挤掉的元素首先会与根结点的两个子节点比较,并与较大的子节点更换位置,如此一直往下,直到这个被挤掉的元素大于左右两个子节点,或者下放到叶节点为止,这个过程称为percolate down(下溯)。举个例子:

根节点68被pop之后,移到了vector的最底部,将24挤出,24被迫从根节点开始与其子节点进行比较,直到找到合适的位置安身,需要注意的是pop之后元素并没有被移走,如果要将其移走,可以使用pop_back。

一言以蔽之,因为pop_heap可以将当前heap中的最大值置于底层容器vector的末尾,heap范围减1,那么不断的执行pop_heap直到树为空,即可得到一个递增序列。

将一段数据转化为heap,一个一个数据插入,调用上面说的两种percolate算法即可。

《STL源码剖析》 侯捷

priority_queue,优先队列,是一个拥有权值观念的queue,它跟queue一样是顶部入口,底部出口,在插入元素时,元素并非按照插入次序排列,它会自动根据权值(通常是元素的实值)排列,权值最高,排在最前面,如下图所示。

默认情况下,priority_queue使用一个max-heap完成,底层容器使用的是一般为vector为底层容器,堆heap为处理规则来管理底层容器实现 。priority_queue的这种实现机制导致其不被归为容器,而是一种容器配接器。关键的源码如下:

priority_queue的所有元素,进出都有一定的规则,只有queue顶端的元素(权值最高者),才有机会被外界取用,它没有遍历功能,也不提供迭代器

《STL源码剖析》 侯捷

STL中的容器可分为序列式容器(sequence)和关联式容器(associative),set属于关联式容器。

set的特性是,所有元素都会根据元素的值自动被排序(默认升序),set元素的键值就是实值,实值就是键值,set不允许有两个相同的键值

标准的STL set以RB-tree(红黑树)作为底层机制,几乎所有的set操作行为都是转调用RB-tree的操作行为,这里补充一下红黑树的特性:

  • 每个节点不是红色就是黑色
  • 如果节点为红色,其子节点必为黑
  • 任一节点至(NULL)树尾端的任何路径,所含的黑节点数量必相同

关于红黑树的具体操作过程,比较复杂读者可以翻阅《算法导论》详细了解。

关联式容器尽量使用其自身提供的find函数查找指定的元素,效率更高,因为STL提供的find函数是一种顺序搜索算法。

《STL源码剖析》 侯捷

map的特性是所有元素会根据键值进行自动排序。map中所有的元素都是pair,拥有键值(key)和实值(value)两个部分,并且不允许元素有相同的key

标准STL map的底层机制是RB-tree(红黑树),另一种以hash table为底层机制实现的称为hash_map。map的架构如下图所示

map的在构造时缺省采用递增排序key,也使用alloc配置器配置空间大小,需要注意的是在插入元素时,调用的是红黑树中的insert_unique方法,而非insert_euqal(multimap使用)

需要注意的是sub(下标)操作既可以作为左值运用(修改内容)也可以作为右值运用(获取实值)。例如:

无论如何,sub操作符都会先根据键值找出实值,源码如下:

代码运行过程是:首先根据键值和实值做出一个元素,这个元素的实值未知,因此产生一个与实值型别相同的临时对象替代:

再将这个对象插入到map中,并返回一个pair:

pair第一个元素是迭代器,指向当前插入的新元素,如果插入成功返回true,此时对应左值运用,根据键值插入实值。插入失败(重复插入)返回false,此时返回的是已经存在的元素,则可以取到它的实值

由于这个实值是以引用方式传递,因此作为左值或者右值都可以

《STL源码剖析》 侯捷

set只提供一种数据类型的接口,但是会将这一个元素分配到key和value上,而且它的compare_function用的是 identity函数,这个函数是输入什么输出什么,这样就实现了set机制,set的key和value其实是一样的了。其实他保存的是两份元素,而不是只保存一份元素

map则提供两种数据类型的接口,分别放在key和value的位置上,他的比较function采用的是红黑树的comparefunction,保存的确实是两份元素。

multimap和map的唯一区别就是:multimap调用的是红黑树的insert_equal,可以重复插入而map调用的则是独一无二的插入insert_unique,multiset和set也一样,底层实现都是一样的,只是在插入的时候调用的方法不一样。

面试时候现场写红黑树代码的概率几乎为0,但是红黑树一些基本概念还是需要掌握的。

1、它是二叉排序树(继承二叉排序树特显):

  • 若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值。
  • 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值。
  • 左、右子树也分别为二叉排序树。

2、它满足如下几点要求:

  • 树中所有节点非红即黑。
  • 红节点的子节点必为黑(黑节点子节点可为黑)。
  • 从根到NULL的任何路径上黑结点数相同。

3、查找时间一定可以控制在O(logn)。

map支持键值的自动排序,底层机制是红黑树,红黑树的查询和维护时间复杂度均为 ,但是空间占用比较大,因为每个节点要保持父节点、孩子节点及颜色的信息

unordered_map是C++ 11新添加的容器,底层机制是哈希表,通过hash函数计算元素位置,其查询时间复杂度为O(1),维护时间与bucket桶所维护的list长度有关,但是建立hash表耗时较大

从两者的底层机制和特点可以看出:map适用于有序数据的应用场景,unordered_map适用于高效查询的应用场景

使用hash函数计算出的位置如果已经有元素占用了,则向后依次寻找,找到表尾则回到表头,直到找到一个空位

每个表格维护一个list,如果hash函数计算出的格子相同,则按顺序存在这个list中

发生冲突时使用另一种hash函数再计算一个地址,直到不冲突

使用hash函数计算出的位置如果已经有元素占用了,按照 、 、 …的步长依次寻找,如果步长是随机数序列,则称之为伪随机探测

一旦hash函数计算的结果相同,就放入公共溢出区

如果你能看到这里,是个狼人人。

说句实话,不管你是学生党还是工作党,这篇文章实用性都挺高的,收藏走起!

我要回帖

更多关于 value怎么解决 的文章

 

随机推荐