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标准库 《map容器详解》
C++ STL标准库 《map容器详解》
7 0
|
1天前
|
存储 C++ 容器
C++ STL标准库 《map容器详解》
C++ STL标准库 《map容器详解》
7 0
|
2天前
|
算法 搜索推荐 C++
C++之STL常用算法(遍历、查找、排序、拷贝、替换、算数生成、集合)
C++之STL常用算法(遍历、查找、排序、拷贝、替换、算数生成、集合)
11 0
|
2天前
|
C++ 容器
C++之map/multimap容器
C++之map/multimap容器
4 0
|
12天前
|
NoSQL 关系型数据库 Redis
Docker的通俗理解和通过宿主机端口访问Redis容器的实例
本文目标:引导初学者入门Docker,理解镜像、容器和宿主机概念,学习常用Docker命令,特别是如何创建并从Redis容器通过宿主机端口访问。 关键点: - Docker核心:镜像(类)、容器(实例)、宿主机(运行环境)。 - `docker pull` 拉取镜像,如 `redis:3.0`。 - `docker run -d --name` 后台运行容器,如 `my-redis`。 - `-p` 参数做端口映射,如 `6379:6379`。 - `docker exec -it` 交互式进入容器,如 `bash` 或执行命令。
|
9天前
|
前端开发 安全 数据库
Web架构&前后端分离站&Docker容器站&集成软件站&建站分配
Web架构&前后端分离站&Docker容器站&集成软件站&建站分配
|
5天前
|
NoSQL Redis Docker
使用 Docker Compose 接管现有容器的文档
使用 Docker Compose 接管现有容器的文档
21 2
|
8天前
|
Cloud Native 安全 Docker
云上攻防-云原生篇&Docker安全&系统内核&版本&CDK自动利用&容器逃逸
云上攻防-云原生篇&Docker安全&系统内核&版本&CDK自动利用&容器逃逸
|
5天前
|
存储 关系型数据库 MySQL
解读 MySQL 容器信息:`docker inspect` 字段详解
解读 MySQL 容器信息:`docker inspect` 字段详解
23 1
|
9天前
|
Linux Docker 容器
蓝易云 - net.ipv4.ip_forward=0导致docker容器无法与外部通信
完成以上步骤后,Docker容器应该能够正常与外部通信了。
14 2