SlideShare a Scribd company logo
STL Container Usage Tips
Yongquan
2015-5
std::vector object model
关于vector这种容器,需要澄清的几个概念或者认识:
1. 对于vector来说,一个元素是否属于某个给定的vector,并不是以该元素的值为评判标准的,而是以
它的地址为标准。这就是为什么vector没有find函数的原因;
2. 迭代器失效的真正含义有这样几种情况:(1) 它指向的对象被彻底销毁了,它成了野指针;(2) 它虽然
不是野指针,但是它指向的对象已经不是原来的那个对象了;(3) 它虽然不是野指针,但是它指向的对
象已经不属于原来的那个容器了,这个就是归属问题;
3. 在调用insert、erase、assign、resize、swap等函数之后都可能使先前获得的迭代器、指针、引用
失效。例如,它们都可能使先前获得的迭代器end()失效,即不再指向现在的end()。特别是当发生内
存重分配的情况下以及调用swap()函数以后,所有已经获得的迭代器等都将失效;
4. 具体地,凡是增加元素的操作都可能使先前获得的迭代器等失效,但是站在我们开发人员的角度,我
们并不清楚哪次增加会导致内存重分配,所以为保险起见还是重新获取迭代器为妙。但是,删除元素
操作不会导致容量缩减,因此不需要内存重分配,先前获得的迭代器、指针、引用等可能并不真的失
效,不过它们未必仍然指向原来的元素对象(如果是删除末尾元素,则原来指向它的迭代器失效)。
T1 T2 T3 T4 T5 T6
start
finish
end_of_storage
std::vector<T>
3
std::vector
如果需要在遍历vector的过程中删除特定的元素,那么下面的代码有没有问题?
std::vector<int> V; // 假设有一个vector容器
………… // 添加很多元素
std::vector<int>::iterator itFirst = V.begin();
std::vector<int>::iterator const itLast = V.end();
while (itFirst != itLast)
{
if (*itFirst == 100)
itFirst = V.erase(itFirst); // 返回逻辑上的下一个元素位置
else
++itFirst;
}
4
std::vector
这样呢?
std::vector<int> V; // 假设有一个vector容器
………… // 添加很多元素
for (std::vector<int>::iterator itFirst = V.begin();
itFirst != V.end(); ) // 每次循环都重新获取end()
{
if (*itFirst == 100)
V.erase(itFirst++); // 先指向下一个元素位置,然后删除当前元素
else
++itFirst;
}
5
std::vector
正确的方法:
std::vector<int> V; // 假设有一个vector容器
………… // 添加很多元素
for (std::vector<int>::iterator itFirst = V.begin();
itFirst != V.end(); ) // 每次循环都重新获取end()
{
if (*itFirst == 100)
itFirst = V.erase(itFirst); // 返回逻辑上的下一个元素位置
else
++itFirst;
}
6
std::vector
5. vector是序列式容器(sequenced container)或顺序容器。所谓序列式容器,从概
念上讲就是说元素的逻辑顺序由它们的相对位置关系确定而不是由值的大小关系
来确定,所以向其中添加元素和删除元素时都必须指出具体操作位置(或者说,
不能单纯以值来定位元素,必须用下标或者地址来定位元素。操作位置和地址具
体指迭代器)。正因为如此,所以在序列式容器中说"上一个元素"和"下一个元
素"是合理的,有明确的含义;
6. 关于函数erase()的返回类型。vector的erase()函数为什么要返回“下一个元素”
的iterator?其一是它确实能够(如第5条所述);其二是它返回“下一个元素”的
位置并不费事,复杂度是O(1)的;其三是能带来方便性;
7. begin()和end()也是最常用的函数,所以STL标准要求它们的时间复杂度是O(1);
rbegin()/rend()也一样;
8. iterator++/--的时间复杂度也是O(1)的;reverse_iterator也一样;
9. 在非尾端的insert和erase操作都会导致若干元素的拷贝动作(调用拷贝构造函数)
,因此效率比较差(O(n))。如果需要频繁地在容器的开头或中间进行insert和
erase等操作,就应该避免使用vector,应该使用哪种容器?
10. Iterator的归属问题和范围问题,示例如下:
7
typedef std::vector<int> IntVector;
IntVector A(10, 5), B(5, 10);
IntVector::iterator ix = A.begin();
B.erase(ix); // ix并不属于B!
B.insert(ix, 20); // ix并不属于B!
IntVector::iterator it = B.end();
it += 2;
B.erase(it); // it不在有效范围内!
B.insert(it, 20); // it不在有效范围内!
int orphan = 30;
IntVector::iterator io(&orphan);
B.erase(io); // io不属于B,更不在有效范围内!
B.insert(io, 20); // io不属于B,更不在有效范围内!
这个问题编译器无能为力,只能在runtime时进行检查。SafeSTL以及较新的MSVC++提供的
STL都具备这个能力,并且只在Debug版本中有效。Release build时自动去掉检查代码。
std::vector
5 5 5 5 5 5 5 5 5 5A:
10B: 10 10 10 10
it
ix
8
std::list object model
关于list这种顺序容器,它很多方面都和vector是一样的。不同的方面有:
1. List在任何位置的插入、删除元素动作的复杂度是O(1)的;
2. List的增加元素的操作不会使任何迭代器失效;删除元素的操作除了使指向被删除元素的
迭代器、指针、引用失效外,其他元素的迭代器等都不会失效;特别地,end()始终不会失
效,而且只要list对象没有销毁它的end()就是不变的,因此我们在循环处理list中的元素时
就不需要反复重新计算end();
list<T> lst1;
_head ●
size
allocator
存储分配器
空白
●
●
data
●
●
data
●
●
data
●
●...
...
lst1.end() lst1.begin() list::Node
  
list容器中的有效元素
●
next
●
prev
Heap
可静态创建也
可动态创建
9
有些实现版本将空白结点直接包含在list对象内部,
并且没有data域,如SGI的实现;而有的是用一个
指针指向空白结点,如MSVC++的实现。这都是实
现细节,并不影响list的接口语义和性能要求。
std::list
在遍历list的过程中删除特定的元素,下面的做法都是可以的:
std::list<int> L; // 假设有一个list容器
………… // 添加很多元素
std::list<int>::iterator itFirst = L.begin(); // 只需一次
std::list<int>::iterator const itLast = L.end(); // 只需一次
while (itFirst != itLast)
{
if (*itFirst == 100)
itFirst = L.erase(itFirst); // 返回逻辑上的下一个元素位置
else
++itFirst;
}
10
std::list
std::list<int> L; // 假设有一个list容器
………… // 添加很多元素
std::list<int>::iterator itFirst = L.begin();
std::list<int>::iterator const itLast = L.end();
while (itFirst != itLast)
{
if (*itFirst == 100)
L.erase(itFirst++);
else
++itFirst;
}
11
std::list
std::list<int> L; // 假设有一个list容器
………… // 添加很多元素
for (std::list<int>::iterator itFirst = L.begin();
itFirst != V.end(); )
{
if (*itFirst == 100)
L.erase(itFirst++);
else
++itFirst;
}
12
std::list
std::list<int> L; // 假设有一个list容器
………… // 添加很多元素
L.remove(100);
3. List实现了一个双向环状链表;
4. Header结点的作用:方便了begin()/end()/rbegin()/rend()的实现,并且保证了它们的复杂
度为O(1);
5. List专门提供 的 函数:sort(), reverse(), merge(), unique(), remove_if(), remove(),
splice();
13
std::set/std::map/rb_tree object model
S.end()
allocator
size
predication
set<int> S;
20
header ●
15
25
root
S.begin() 10
23
_Color
_L _P _R
<NodeBase>
bool _Color
NodeBase *_L
NodeBase *_P
NodeBase *_R
<Tree Node>
_ValueType _V
_Color
_L _P _R
5
_Color
_L _P _R
30
14
关于set/map这种关联式容器,需要澄清的几个概念或者认识:
1. Set其实对应的是数学上的集合概念,而我们知道集合的元素是无所谓顺序的。例如任意
一个整数集合S={5, 20, 30, -2, 6, -100},元素是无序的,你不能说元素20排在元素30的
前面,也不能说元素-100排在元素6的后面。因此,erase()函数的标准形式是返回void,
而不是所谓的“指向下一个元素的iterator”;
2. map就是一张‘n行2列’的表格,或者说是pair<key, value>的一个集合,它的pair同样
没有先后顺序;
3. 基于二叉平衡搜索树的实现,按照用户指定的比较函数对元素排序(比如std::less<T>或
者std::greater<T>),这纯粹是为了提高查找性能,要不然树的空间开销如此之大又是何
必呢?计算当前节点的下一个节点即iterator++的时间复杂度并不是O(1)的,而是
O(log2N)的(即接近于二叉树的深度,这一点与顺序容器不同);iterator--也是一样。
4. 此处,‘下一个’‘上一个’均是指元素自动排序后的逻辑顺序,不是在Tree上的位置顺
序(实际上,Tree中的结点也没有什么所谓的位置顺序),这是实现需要,与set/map的概
念并不矛盾;
5. Set/map并非一定要用rb-tree实现,其实也可以用list实现;
6. 只要set/map对象还在,其end()就是不变的,begin()虽然会变但是复杂度总是O(1)的。
因此,就像list那样,在遍历时也不需要反复计算end();
std::set/std::map
15
std::set/std::map
基本的复杂度计算
在遍历set的过程中删除特定的元素:
std::set<int> S; // 假设有一个set容器
………… // 添加很多元素
std::set<int>::iterator itFirst = S.begin(); // 只需一次
std::set<int>::iterator const itLast = S.end(); // 只需一次
while (itFirst != itLast)
{
if (*itFirst == 100)
S.erase(itFirst++); // 返回排序逻辑上的下一个元素位置
else
++itFirst;
}
std::set/std::map
17
std::set/std::map
关于容器嵌套:
STL容器的实现基于元素对象的deep-copy语义和自身的deep-copy语义,因此在拷贝、赋值、传递、排序、
合并、拆分时会有很大的overhead。所以,当元素类型是大对象时,应该避免在容器中直接存放大对象,
改为存放它们的智能指针(auto_ptr<>除外),特别是需要嵌套容器的时候。
例如:
std::map<std::string/*name*/, std::list<BigObject> > M; // 假设有一个嵌套容器
std::list<BigObject> L;
BigObject a, b, c, d;
L.push_back(a); // 添加很多元素
L.push_back(b);
L.push_back(c);
L.push_back(d);
M[“张三”] = L;
BigObject A[10];
L.assign(A, A+10);
M[“李四”] = L;
……
M[“张三”].sort();
18
std::set/std::map
建议的做法:
typedef std::list<BigObject> BigObjectList;
typedef boost::shared_ptr<BigObjectList> BigObjectListSmartPtr;
std::map<std::string/*name*/, BigObjectListSmartPtr> M;
BigObjectListSmartPtr pL(new BigObjectList);
BigObject a, b, c, d;
pL->push_back(a); // 添加很多元素
pL->push_back(b);
pL->push_back(c);
pL->push_back(d);
M[“张三”] = pL;
BigObject A[10];
BigObjectListSmartPtr pT(new BigObjectList);
pT->assign(A, A+10);
M[“李四”] = pT;
……
M[“张三”]->sort();
19
Hashtable/unordered_set/unordered_map object model
1. SGI STL hashtable的实现使用开链法来解决hash函数的结果冲突问题;
2. Hashtable没有reverse_iterator,因此不支持反向遍历;
3. begin()的复杂度是O(N);end()为O(1);iterator++的复杂度接近O(1);
4. Hashtable对元素不进行自动排序,因此hashset/hashmap在C++最新标准中被命名
为unordered_set/unordered_map;
20
5. Hash函数的质量好坏直接影响到find()/insert()/erase()函数的性能;
6. 好的Hash函数能使得hashtable的find()/insert()/erase()等函数的性能接近
O(1),也就是说任何一个单链表的长度都接近1,而且在buckets中的分布比较均匀;
最坏的Hash函数使得任何key的hash值都相等即落到同一个bucket里,因此使整个
hashtable退化为一个单链表;
7. 为了避免任何一个单链表过长,hashtable实现采取了自动扩张策略,即当hashtable
中包含的元素总数即将超过当前bucket的数量时,就会重建buckets vector(仅需重建
vector即可)。因此最坏情况下,任何一个单链表的长度都不会超过当前bucket的数量;
8. Hashtable初始化时,取下表中不小于用户给定的size的最小素数作为buckets的实际
大小(不同的实现使用不同的素数集合):
static const unsigned long __stl_prime_list[28] =
{
53ul, 97ul, 193ul, 389ul, 769ul,
1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
1610612741ul, 3221225473ul, 4294967291ul
};
9. 当Hashtable需要重建buckets vector时,有些实现直接按照2倍于原来的size的策
略来扩展(如MSVC++),而SGI实现按照上表取下一个素数为新的size。
Hashtable/unordered_set/unordered_map
10. 当重建buckets vector后,由于‘模(%)运算’的base变大了,所以必须对所有元素
重新hash并计算新的bucket编号,这样的话原来的单链表就会被打散重新排列;
11. 在元素是大对象以及相同规模的元素数量并且规模达到一定数量级的情况下 比较
hashtable、std::vector和binary_tree的性能(仅针对最常用的三类操作):
12. Example:ndm_db_conn.hh,_SQL_STMT_MAP_
Hashtable/unordered_set/unordered_map
operations hashtable std::vector binary_tree
insert(V) 如果不需要重建buckets,则接近
O(1);
如果需要扩展和重建buckets,则所
有元素对象需要重新hash并连接到
新的bucket上,但是没有拷贝每个
元素对象的开销。所以,这种情况
下的开销仍然比vector要好很多
如果不需要重建vector,那么
只有在末尾插入(push_back)
时不需要搬移元素对象(拷贝元
素);
如果需要重建vector,那么搬
移元素的开销巨大;
平均为O(log2N),
但是有时需要重
新平衡树结构,
因此会伴随子树
旋转和红黑结点
转换等复杂操作
find(key) 平均接近O(1); 不直接提供find,但平均O(N);O(log2N)
erase(key) 平均接近O(1); 只有在末尾删除元素时不需要
搬移后面的元素;
同insert()操作
Quiz
 相同元素规模的情况下,上述各个容器类型中,哪种的空间开销最大?其次
是谁?
 如果不考虑std::map/std::set实现中的排序要求,它们是否可以用hash
table来实现?
 在结构上,std::set<T>  rb_tree<T>, 那么std::map<key, value> 
rb_tree< ??? > ?
 同样道理,hash_set<T>  hashtable<T>, hash_map<key, value> 
hashtable< ??? > ?
 对于给定的hash_map/hash_set(hash函数已经给定),具有相同hash值的
不同key或不同元素一定会落入同一个bucket里。那么具有不同hash值的不
同key或不同元素是否一定落入不同的bucket里?
 对于hash_multiset/hash_multimap,当insert()操作导致buckets需要重建
时,原来具有相同value/key的那些元素在新的hashtable里面是否仍然位于
同一个bucket里面并且挨在一起?[不一定,因为mod的base变了]
 使用开链法实现Hashtable时,为什么不使用双向链表?
23
Q&A
24

More Related Content

What's hot

functional-scala
functional-scalafunctional-scala
functional-scala
wang hongjiang
 
Scala function-and-closures
Scala function-and-closuresScala function-and-closures
Scala function-and-closureswang hongjiang
 
jQuery源码学习
jQuery源码学习jQuery源码学习
jQuery源码学习fangdeng
 
Java Script 引擎技术
Java Script 引擎技术Java Script 引擎技术
Java Script 引擎技术
bigqiang zou
 
Java Collections中的Fail Fast机制
Java Collections中的Fail Fast机制Java Collections中的Fail Fast机制
Java Collections中的Fail Fast机制yiditushe
 
5, initialization & cleanup
5, initialization & cleanup5, initialization & cleanup
5, initialization & cleanup
ted-xu
 
第三章 栈和队列
第三章 栈和队列第三章 栈和队列
第三章 栈和队列
Wang Yizhe
 
第四章 串操作应用举例
第四章 串操作应用举例第四章 串操作应用举例
第四章 串操作应用举例
Wang Yizhe
 
第01章 绪论(java版)
第01章  绪论(java版)第01章  绪论(java版)
第01章 绪论(java版)Yan Li
 
進階主題
進階主題進階主題
進階主題
Justin Lin
 
第三章 栈和队列(新)
第三章 栈和队列(新)第三章 栈和队列(新)
第三章 栈和队列(新)
Wang Yizhe
 
Cypher 查询语言
Cypher 查询语言Cypher 查询语言
Cypher 查询语言
zernel
 
Python元組,字典,集合
Python元組,字典,集合Python元組,字典,集合
Python元組,字典,集合
吳錫修 (ShyiShiou Wu)
 
twMVC#27 | C# 7.0 新功能介紹
twMVC#27 | C# 7.0 新功能介紹twMVC#27 | C# 7.0 新功能介紹
twMVC#27 | C# 7.0 新功能介紹
twMVC
 
Python 迴圈作業
Python 迴圈作業Python 迴圈作業
Python 迴圈作業
吳錫修 (ShyiShiou Wu)
 
Arrays的Sort算法分析
Arrays的Sort算法分析Arrays的Sort算法分析
Arrays的Sort算法分析
Zianed Hou
 
Swift编程语言入门教程 中文版
Swift编程语言入门教程 中文版Swift编程语言入门教程 中文版
Swift编程语言入门教程 中文版
Harvey Zhang
 
Python分支作業
Python分支作業Python分支作業
Python分支作業
吳錫修 (ShyiShiou Wu)
 
認識 C++11 新標準及使用 AMP 函式庫作平行運算
認識 C++11 新標準及使用 AMP 函式庫作平行運算認識 C++11 新標準及使用 AMP 函式庫作平行運算
認識 C++11 新標準及使用 AMP 函式庫作平行運算
建興 王
 

What's hot (20)

functional-scala
functional-scalafunctional-scala
functional-scala
 
Scala function-and-closures
Scala function-and-closuresScala function-and-closures
Scala function-and-closures
 
Sun java
Sun javaSun java
Sun java
 
jQuery源码学习
jQuery源码学习jQuery源码学习
jQuery源码学习
 
Java Script 引擎技术
Java Script 引擎技术Java Script 引擎技术
Java Script 引擎技术
 
Java Collections中的Fail Fast机制
Java Collections中的Fail Fast机制Java Collections中的Fail Fast机制
Java Collections中的Fail Fast机制
 
5, initialization & cleanup
5, initialization & cleanup5, initialization & cleanup
5, initialization & cleanup
 
第三章 栈和队列
第三章 栈和队列第三章 栈和队列
第三章 栈和队列
 
第四章 串操作应用举例
第四章 串操作应用举例第四章 串操作应用举例
第四章 串操作应用举例
 
第01章 绪论(java版)
第01章  绪论(java版)第01章  绪论(java版)
第01章 绪论(java版)
 
進階主題
進階主題進階主題
進階主題
 
第三章 栈和队列(新)
第三章 栈和队列(新)第三章 栈和队列(新)
第三章 栈和队列(新)
 
Cypher 查询语言
Cypher 查询语言Cypher 查询语言
Cypher 查询语言
 
Python元組,字典,集合
Python元組,字典,集合Python元組,字典,集合
Python元組,字典,集合
 
twMVC#27 | C# 7.0 新功能介紹
twMVC#27 | C# 7.0 新功能介紹twMVC#27 | C# 7.0 新功能介紹
twMVC#27 | C# 7.0 新功能介紹
 
Python 迴圈作業
Python 迴圈作業Python 迴圈作業
Python 迴圈作業
 
Arrays的Sort算法分析
Arrays的Sort算法分析Arrays的Sort算法分析
Arrays的Sort算法分析
 
Swift编程语言入门教程 中文版
Swift编程语言入门教程 中文版Swift编程语言入门教程 中文版
Swift编程语言入门教程 中文版
 
Python分支作業
Python分支作業Python分支作業
Python分支作業
 
認識 C++11 新標準及使用 AMP 函式庫作平行運算
認識 C++11 新標準及使用 AMP 函式庫作平行運算認識 C++11 新標準及使用 AMP 函式庫作平行運算
認識 C++11 新標準及使用 AMP 函式庫作平行運算
 

Viewers also liked

20151213 參與式預算 松山區 市民場
20151213 參與式預算 松山區 市民場20151213 參與式預算 松山區 市民場
20151213 參與式預算 松山區 市民場
恩恩 許
 
Най - бедна в европейския свят
Най   - бедна в европейския святНай   - бедна в европейския свят
Най - бедна в европейския свят
realandtender
 
Digital design 8209600
Digital design 8209600Digital design 8209600
Digital design 8209600
Thuy Hang
 
「審議民主」讀書會—人都會說謊:民主沒有意義嗎?
「審議民主」讀書會—人都會說謊:民主沒有意義嗎?「審議民主」讀書會—人都會說謊:民主沒有意義嗎?
「審議民主」讀書會—人都會說謊:民主沒有意義嗎?
Wan-Tzu Chang
 
SK-KD Bhs. Arab Prog. Pilihan SMA-MA
SK-KD Bhs. Arab Prog. Pilihan SMA-MASK-KD Bhs. Arab Prog. Pilihan SMA-MA
SK-KD Bhs. Arab Prog. Pilihan SMA-MA
SMA Negeri 9 KERINCI
 
Christmas update 2016
Christmas update 2016Christmas update 2016
Christmas update 2016
Duality Blockchain Solutions
 
4 Pillars of IT - a Baltimore Techies for Good webinar
4 Pillars of IT - a Baltimore Techies for Good webinar4 Pillars of IT - a Baltimore Techies for Good webinar
4 Pillars of IT - a Baltimore Techies for Good webinar
NetSquared
 
「審議民主」讀書會—審議民主之病徵及其原因
「審議民主」讀書會—審議民主之病徵及其原因「審議民主」讀書會—審議民主之病徵及其原因
「審議民主」讀書會—審議民主之病徵及其原因
Wan-Tzu Chang
 
Qué es un proyecto
Qué es un proyectoQué es un proyecto
Qué es un proyecto
Adrian Barrios
 
Respirasi
RespirasiRespirasi
Mengenali ciri - ciri usahawan
Mengenali ciri - ciri usahawanMengenali ciri - ciri usahawan
Mengenali ciri - ciri usahawan
Syahremie Teja
 
1.1 Introducción a redes - Sistemas 2016
1.1 Introducción a redes  - Sistemas 20161.1 Introducción a redes  - Sistemas 2016
1.1 Introducción a redes - Sistemas 2016
David Narváez
 

Viewers also liked (12)

20151213 參與式預算 松山區 市民場
20151213 參與式預算 松山區 市民場20151213 參與式預算 松山區 市民場
20151213 參與式預算 松山區 市民場
 
Най - бедна в европейския свят
Най   - бедна в европейския святНай   - бедна в европейския свят
Най - бедна в европейския свят
 
Digital design 8209600
Digital design 8209600Digital design 8209600
Digital design 8209600
 
「審議民主」讀書會—人都會說謊:民主沒有意義嗎?
「審議民主」讀書會—人都會說謊:民主沒有意義嗎?「審議民主」讀書會—人都會說謊:民主沒有意義嗎?
「審議民主」讀書會—人都會說謊:民主沒有意義嗎?
 
SK-KD Bhs. Arab Prog. Pilihan SMA-MA
SK-KD Bhs. Arab Prog. Pilihan SMA-MASK-KD Bhs. Arab Prog. Pilihan SMA-MA
SK-KD Bhs. Arab Prog. Pilihan SMA-MA
 
Christmas update 2016
Christmas update 2016Christmas update 2016
Christmas update 2016
 
4 Pillars of IT - a Baltimore Techies for Good webinar
4 Pillars of IT - a Baltimore Techies for Good webinar4 Pillars of IT - a Baltimore Techies for Good webinar
4 Pillars of IT - a Baltimore Techies for Good webinar
 
「審議民主」讀書會—審議民主之病徵及其原因
「審議民主」讀書會—審議民主之病徵及其原因「審議民主」讀書會—審議民主之病徵及其原因
「審議民主」讀書會—審議民主之病徵及其原因
 
Qué es un proyecto
Qué es un proyectoQué es un proyecto
Qué es un proyecto
 
Respirasi
RespirasiRespirasi
Respirasi
 
Mengenali ciri - ciri usahawan
Mengenali ciri - ciri usahawanMengenali ciri - ciri usahawan
Mengenali ciri - ciri usahawan
 
1.1 Introducción a redes - Sistemas 2016
1.1 Introducción a redes  - Sistemas 20161.1 Introducción a redes  - Sistemas 2016
1.1 Introducción a redes - Sistemas 2016
 

STL Container Usage Tips