C++ STL:各类容器的特点和优缺点比较

简介: C++ STL:各类容器的特点、优势、劣势比较

The C++ Standard Library has a rich collection of containers. We have sequence and associative containers. Associative containers can be classified as ordered or unordered associative containers.

1 序列容器(sequence container)
Each of the sequence containers has a unique domain. Still, in 95 % of the use cases, std::vector is the right choice. std::vector can dynamically adjust its size, automatically manages its memory, and provides you with outstanding performance. In contrast, std::array is the only sequence container that cannot adjust its size at runtime. It is optimized for minimal memory and performance overhead. While std::vector is good at putting new elements at its end, you should use std::deque to put an element also at the beginning. With std::list being a doubly-linked list and std::forward_list as a singly linked list, we have two additional containers optimized for operations at arbitrary positions in the container, with high performance.

1.1 std::array

The std::array combines the memory and runtime characteristic of a C array with the interface of std::vector. std::array is a homogeneous container of fixed length.

// array.cpp
...

include

...
std::array arr{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (auto a: arr)
std::cout << a << " " ; // 1 2 3 4 5 6 7 8 9 10
double sum= std::accumulate(arr.begin(), arr.end(), 0);
std::cout << sum << '\n'; // 55
double mean= sum / arr.size();
std::cout << mean << '\n'; // 5.5
std::cout << (arr[0] == std::get<0>(arr)); // true
1.2 std::vector

std::vector is a homogeneous container, which length is automatically adjusted at runtime.

// vector.cpp
...

include

...
std::vector intVec1(5, 2011);
intVec1.reserve(10);
std::cout << intVec1.size() << '\n'; // 5
std::cout << intVec1.capacity() << '\n'; // 10
intVec1.shrink_to_fit();
std::cout << intVec1.capacity() << '\n'; // 5
std::vector intVec2(10);
std::cout << intVec2.size() << '\n'; // 10
std::vector intVec3{10};
std::cout << intVec3.size() << '\n'; // 1
std::vector intVec4{5, 2011};
std::cout << intVec4.size() << '\n'; // 2
1.3 std::deque

std::deque, which typically consists of a sequence of fixed-sized arrays, is quite similar to std::vector.

// deque.cpp
...

include

...
struct MyInt{
MyInt(int i): myInt(i){};
int myInt;
};
std::deque myIntDeq;
myIntDeq.push_back(MyInt(5));
myIntDeq.emplace_back(1);
std::cout << myIntDeq.size() << '\n'; // 2
std::deque intDeq;
intDeq.assign({1, 2, 3});
for(auto v: intDeq)
std::cout << v << " "; // 1 2 3
intDeq.insert(intDeq.begin(), 0);
for(auto v: intDeq)
std::cout << v << " "; // 0 1 2 3
intDeq.insert(intDeq.begin()+4, 4);
for(auto v: intDeq)
std::cout << v << " "; // 0 1 2 3 4
intDeq.insert(intDeq.end(), {5, 6, 7, 8, 9, 10, 11});
for(auto v: intDeq)
std::cout << v << " "; // 0 1 2 3 4 5 6 7 8 9 10 11
for(auto revIt= intDeq.rbegin(); revIt != intDeq.rend(); ++revIt)
std::cout << *revIt << " "; // 11 10 9 8 7 6 5 4 3 2 1 0
intDeq.pop_back();
for(auto v: intDeq)
std::cout << v << " "; // 0 1 2 3 4 5 6 7 8 9 10
intDeq.push_front(-1);
for(auto v: intDeq)
std::cout << v << " "; // -1 0 1 2 3 4 5 6 7 8 9 10
1.4 std::list

std::list is a doubly linked list.

// list.cpp
...

include

...
std::list list1{15, 2, 18, 19, 4, 15, 1, 3, 18, 5,
4, 7, 17, 9, 16, 8, 6, 6, 17, 1, 2};
list1.sort();
for(auto l: list1)
std::cout << l << " ";
// 1 1 2 2 3 4 4 5 6 6 7 8 9 15 15 16 17 17 18 18 19
list1.unique();
for(auto l: list1)
std::cout << l << " ";
// 1 2 3 4 5 6 7 8 9 15 16 17 18 19
std::list list2{10, 11, 12, 13, 14};
list1.splice(std::find(list1.begin(), list1.end(), 15), list2);
for(auto l: list1)
std::cout << l << " ";
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
1.5 std::forward_list

std::forward_list is a singly linked list.

// forwardList.cpp
...

include

...
using std::cout;
std::forward_list forw;
std::cout << forw.empty() << '\n'; // true
forw.push_front(7);
forw.push_front(6);
forw.push_front(5);
forw.push_front(4);
forw.push_front(3);
forw.push_front(2);
forw.push_front(1);
for(auto i: forw)
cout << i << " "; // 1 2 3 4 5 6 7
forw.erase_after(forw.before_begin());
cout<< forw.front(); // 2
std::forward_list forw2;
forw2.insert_after(forw2.before_begin(), 1);
forw2.insert_after(++forw2.before_begin(), 2);
forw2.insert_after(++(++forw2.before_begin()), 3);
forw2.push_front(1000);
for(auto i= forw2.cbegin();i != forw2.cend(); ++i)
cout << i << " ";
// 1000 1 2 3
auto IteratorTo5= std::find(forw.begin(), forw.end(), 5);
forw.splice_after(IteratorTo5, std::move(for2));
for(auto i= forw.cbegin(); i != forw.cend(); ++i)
cout <<
i << " ";
// 2 3 4 5 1000 1 2 3 6 7
forw.sort();
for(auto i= forw.cbegin(); i != forw.cend(); ++i)
cout << i << " ";
// 1 2 2 3 3 4 5 6 7 1000
forw.reverse();
for(auto i= forw.cbegin(); i != forw.cend(); ++i)
cout <<
i << " ";
// 1000 7 6 5 4 3 3 2 2 1
forw.unique();
for(auto i= forw.cbegin(); i != forw.cend(); ++i)
cout << *i << " ";
// 1000 7 6 5 4 3 2 1
2 associative container
All eight ordered and unordered containers have in common that they associate a key with a value. You can use the key to get the value. To classify the associative containers, you have to answer three simple questions:

• Are the keys sorted?

• Does the key have an associated value?

• Can a key appear more than once?

The following table with 2^3= 8 rows gives the answers to the three questions.

There are special rules for the key and the value of an associative container.

Ordered

associative container

Unordered

associative container

The key has to be

sortable (by default <),

equal comparable,

available as hash value,

copyable and moveable.

copyable or moveable.

The value has to be

default constructible,

default constructible,

copyable and moveable.

copyable or moveable.

Associative containers are containers of key-value pairs. They provide their values by their respective key. A typical use case for an associative container is a phone book, where you use the key family name to retrieve the value phone number. C++ has eight different associative containers. On one side are the associative containers with ordered keys: std::set, std::map, std::multiset and std::multimap. On the other side, there are the unordered associative containers: std::unorderedset, std::unordered- map, std::unordered_multiset, and std::unordered_multimap.

2.1 std::map

Often, std::map is called an associative array, because std::map supports the index operator like a sequence container. The subtle difference is that its index is not restricted to a number like in the case of std::vector. Its index can be almost any arbitrary type.

/
template < class key, class val, class Comp= less,
class Alloc= allocator >
class map;
/

include

...
std::map> int2Str{
{5, "five"}, {1, "one"}, {4, "four"}, {3, "three"},
{2, "two"}, {7, "seven"}, {6, "six"} };
for (auto p: int2Str)
std::cout << "{" << p.first << "," << p.second << "} ";
// {7,seven} {6,six} {5,five} {4,four} {3,three} {2,two} {1,one}
2.2 std::unordered_map

std::unordered_map's keys are not sorted, but the key is hashed to an array's index.

template< class key, class val, class Hash= std::hash,
class KeyEqual= std::equal_to,
class Alloc= std::allocator>>
class unordered_map;

For user-defined types used as a key for an unordered associative container, you must keep two requirements in mind. They need a hash function and have to be comparable to equal.

// unorderedMapHash.cpp
...

include

...
struct MyInt{
MyInt(int v):val(v){}
bool operator== (const MyInt& other) const {
return val == other.val;
}
int val;
};
struct MyHash{
std::size_t operator()(MyInt m) const {
std::hash hashVal;
return hashVal(m.val);
}

};
std::ostream& operator << (std::ostream& st, const MyInt& myIn){
st << myIn.val ;
return st;
}
typedef std::unordered_map MyIntMap;
MyIntMap myMap{ {MyInt(-2), -2}, {MyInt(-1), -1}, {MyInt(0), 0}, {MyInt(1), 1}};
for(auto m : myMap)
std::cout << "{" << m.first << "," << m.second << "} ";
// {MyInt(1),1} {MyInt(0),0} {MyInt(-1),-1} {MyInt(-2),-2}
std::cout << myMap[MyInt(-2)] << '\n'; // -2
Since C++98, C++ has ordered associative containers; with C++11, C++ has, in addition, unordered associative containers. Both classes have a very similar interface.

// orderedUnorderedComparison.cpp
/
template < class key, class val, class Comp= less,
class Alloc= allocator >
class map;
template < class T, class Comp = less,
class Alloc = allocator >
class set;
/
...

include

include

// std::map
std::map m { {"Dijkstra", 1972}, {"Scott", 1976}};
m["Ritchie"]= 1983;
std::cout << m["Ritchie"]; // 1983
for(auto p : m)
std::cout << "{" << p.first << "," << p.second << "}";
// {Dijkstra,1972},{Ritchie,1983},{Scott,1976}
m.erase("Scott");
for(auto p : m)
std::cout << "{" << p.first << "," << p.second << "}";
// {Dijkstra,1972},{Ritchie,1983}
m.clear();
std::cout << m.size() << '\n'; // 0

// std::unordered_map
std::unordered_map um { {"Dijkstra", 1972}, {"Scott", 1976}};
um["Ritchie"]= 1983;
std::cout << um["Ritchie"]; // 1983
for(auto p : um)
std::cout << "{" << p.first << "," << p.second << "}";
// {Ritchie,1983},{Scott,1976},{Dijkstra,1972}
um.erase("Scott");
for(auto p : um)
std::cout << "{" << p.first << "," << p.second << "}";
// {Ritchie,1983},{Dijkstra,1972}
um.clear();
std::cout << um.size() << '\n'; // 0
3 Container adapter
Container adapters provide a simplified interface to the sequence containers. C++ has std::stack, std::queue, and std::priority_queue.

3.1 std:stack

The std::stack follows the LIFO principle (Last In First Out).

/
template >
class stack;
/
// stack.cpp
...

include

...
std::stack myStack;
std::cout << myStack.empty() << '\n'; // true
std::cout << myStack.size() << '\n'; // 0
myStack.push(1);
myStack.push(2);
myStack.push(3);
std::cout << myStack.top() << '\n'; // 3
while(!myStack.empty()){
std::cout << myStack.top() << " ";
myStack.pop();
} // 3 2 1
std::cout << myStack.empty() << '\n'; // true
std::cout << myStack.size() << '\n'; // 0
3.2 std::Queue
//代码效果参考:http://www.zidongmutanji.com/bxxx/164948.html

The std::queue follows the FIFO principle (First In First Out).

// queue.cpp
...

include

...
std::queue myQueue;
std::cout << myQueue.empty() << '\n'; // true
std::cout << myQueue.size() << '\n'; // 0
myQueue.push(1);
myQueue.push(2);
myQueue.push(3);
std::cout << myQueue.back() << '\n'; // 3
std::cout << myQueue.front() << '\n'; // 1
while(!myQueue.empty()){
std::cout << myQueue.back() << " ";
std::cout << myQueue.front() << " : ";
myQueue.pop();
} // 3 1 : 3 2 : 3 3
std::cout << myQueue.empty() << '\n'; // true
std::cout << myQueue.size() << '\n'; // 0
3.3 std::priority_queque

The std::priority_queue is a reduced std::queue. The difference to the std::queue is that their greatest element is always at the top of the priority queue.

相关文章
|
20天前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
7月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
194 2
|
7月前
|
存储 算法 C++
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
378 73
|
8月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
7月前
|
存储 算法 C++
【c++丨STL】set/multiset的使用
本文深入解析了STL中的`set`和`multiset`容器,二者均为关联式容器,底层基于红黑树实现。`set`支持唯一性元素存储并自动排序,适用于高效查找场景;`multiset`允许重复元素。两者均具备O(logN)的插入、删除与查找复杂度。文章详细介绍了构造函数、迭代器、容量接口、增删操作(如`insert`、`erase`)、查找统计(如`find`、`count`)及`multiset`特有的区间操作(如`lower_bound`、`upper_bound`、`equal_range`)。最后预告了`map`容器的学习,其作为键值对存储的关联式容器,同样基于红黑树,具有高效操作特性。
303 3
|
8月前
|
存储 算法 C++
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
438 1
|
9月前
|
C++ 容器
【c++丨STL】stack和queue的使用及模拟实现
本文介绍了STL中的两个重要容器适配器:栈(stack)和队列(queue)。容器适配器是在已有容器基础上添加新特性或功能的结构,如栈基于顺序表或链表限制操作实现。文章详细讲解了stack和queue的主要成员函数(empty、size、top/front/back、push/pop、swap),并提供了使用示例和模拟实现代码。通过这些内容,读者可以更好地理解这两种数据结构的工作原理及其实现方法。最后,作者鼓励读者点赞支持。 总结:本文深入浅出地讲解了STL中stack和queue的使用方法及其模拟实现,帮助读者掌握这两种容器适配器的特性和应用场景。
204 21
|
8月前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
8月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
4月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
109 0