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.

相关文章
|
1月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
95 10
|
1月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
51 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
1月前
|
存储 程序员 C++
C++常用基础知识—STL库(2)
C++常用基础知识—STL库(2)
69 5
|
1月前
|
存储 自然语言处理 程序员
C++常用基础知识—STL库(1)
C++常用基础知识—STL库(1)
52 1
|
1月前
|
算法 安全 Linux
【C++STL简介】——我与C++的不解之缘(八)
【C++STL简介】——我与C++的不解之缘(八)
|
1月前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
56 2
|
1月前
|
算法 数据处理 C++
c++ STL划分算法;partition()、partition_copy()、stable_partition()、partition_point()详解
这些算法是C++ STL中处理和组织数据的强大工具,能够高效地实现复杂的数据处理逻辑。理解它们的差异和应用场景,将有助于编写更加高效和清晰的C++代码。
23 0
|
4天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
24 5
|
11天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
40 4
|
12天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
35 4
下一篇
无影云桌面