目录
1.3.2 不能打印向量对象;不能打印空向量中的元素,因为空向量中无元素可打印
2.2.2 不能打印deque对象;不能打印空deque中的元素,因为空deque中无元素可打印
7.3.5 priority_queue元素的插入、弹出、遍历、显示综合操作
8.4.3 在不为空的map中插入已存在的键所对应的新值,则会覆盖掉该键所对应的旧值
前言
STL 是“Standard Template Library”的缩写,中文译为“标准模板库”。STL 是 C++ 标准库的一部分,不用单独安装。
在前一篇博文C++模板-CSDN博客中,我们已经学习了 C++ 模板的概念。C++ STL(标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。
C++ 对模板(Template)支持得很好,STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离。例如,vector 的底层为顺序表(数组),list 的底层为双向链表,deque 的底层为循环队列,set 的底层为红黑树,hash_set 的底层为哈希表。
C++ 标准模板库的核心包括以下六个组件:
组件 | 描述 |
---|---|
容器(Containers) | 容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 双端队列(deque)、链表(list)、向量(vector)、映射(map)、队列(queue)、集合(set)、优先队列(priority_queue) 等。这些容器提供了不同的数据结构,以适应不同的需求。 |
算法(Algorithms) | 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。 |
迭代器(iterators) | 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集。无论是顺序访问、随机访问还是反向访问。使用迭代器,可以通过类似指针的方式对容器进行遍历和操作。 |
仿函数(Functors) | 函数对象是可调用对象的抽象,它们可以像普通函数一样使用,也可以用于STL算法中的自定义操作。STL提供了多个函数对象类,如谓词(Predicate)、比较器(Comparator)等。 |
适配器(Adapters) | 适配器是STL中的一种扩展机制,用于将现有的组件进行包装,以改变其接口或行为。STL提供了多种适配器,如堆栈适配器(stack adapter)、队列适配器(queue adapter)和优先队列适配器(priority queue adapter),用于修改容器的行为特征。 |
空间配置器(allocator) | STL中的空间配置器(allocator)是一种用于管理内存分配和释放的工具。它主要用于动态分配容器中的元素的内存空间,并在需要时进行扩展或收缩。 |
一、容器
STL(Standard Template Library)是C++标准库中的一部分,提供了丰富的容器类模板,用于存储和管理数据。下面是一些常用的STL容器:
- vector:动态数组,可以高效地随机访问元素,并支持在尾部插入和删除元素。
- list:双向链表,支持在任意位置插入和删除元素,但是访问元素的速度相对较慢。
- deque:双端队列,类似于vector,但是还支持在头部添加或删除元素。
- array:固定大小的数组,大小在编译时确定。
- forward_list:单向链表,与list相比,只支持单向迭代器,节省内存空间。
- set:有序集合,不允许重复元素。
- multiset:有序集合,允许重复元素。
- map:键值对集合,根据键进行排序,不允许重复键。
- multimap:键值对集合,根据键进行排序,允许重复键。
- unordered_set:无序集合,基于哈希表实现,不允许重复元素。
- unordered_multiset:无序集合,基于哈希表实现,允许重复元素。
- unordered_map:键值对集合,基于哈希表实现,根据键进行哈希,不允许重复键。
- unordered_multimap:键值对集合,基于哈希表实现,根据键进行哈希,允许重复键。
除了上述容器外,STL还提供了一些适配器容器,如stack(栈)、queue(队列)、priority_queue(优先队列)等,它们是基于其他容器实现的。
每个容器都有自己的特点和适用场景,选择合适的容器可以提高代码的效率和可读性。具体选择哪个容器取决于数据的特性、操作的需求和性能要求等因素。
使用STL容器需要包含相应的头文件,并根据需要使用相应的命名空间。例如,使用vector需要包含<vector>
头文件,并使用std::vector
命名空间。
1 向量
vector是C++标准模板库中的部分内容,它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。
向量可将元素存储在连续的内存位置中,并在运行时根据需要分配内存。
1.1 向量(Vector)和数组(array)之间的区别
数组遵循静态方法,这意味着在运行时不能更改其大小,而vector实现动态数组意味着在添加元素时会自动调整其大小。
1.2 语法
创建向量 'v1'。语法为:
vector<object_type> v1;
为了可以使用vector,必须在你的头文件中包含下面的代码:
#include <vector>
1.3 示例
1.3.1 创建 vector 对象
#include<vector>
#include<iostream>
#include <algorithm>
void out(int a) {
std::cout << a << " ";
}
int main() {
// 一.创建 vector 对象:
// 可以使用默认构造函数创建一个空的vector,
// 也可以在创建时指定初始大小和初始值。例如:
std::vector<int> numbers; // 创建一个空的vector
std::vector<int> numbers2(5); // 创建包含5个元素的vector,初始值为0
std::vector<int> numbers3(5, 10); // 创建包含5个元素的vector,初始值都为10
std::cout << "vector numbers 的大小为:" << numbers.size() << std::endl;
std::cout << "vector numbers2 的大小为:" << numbers2.size() << std::endl;
// 打印向量numbers2元素
std::cout << "向量numbers2元素:{ ";
for_each(numbers2.begin(), numbers2.end(), out);
std::cout << "}" << std::endl;
std::cout << "vector numbers3 的大小为:" << numbers3.size() << std::endl;
// 打印向量numbers2元素
std::cout << "向量numbers3元素:{ ";
for_each(numbers3.begin(), numbers3.end(), out);
std::cout << "}" << std::endl;
return 0;
}
输出结果:
打印向量元素的方法:
方法1:使用for循环打印
#include<vector>
#include<iostream>
int main() {
// 创建 vector 对象:
std::vector<int> numbers2(5); // 创建包含5个元素的vector,初始值为0
// 打印向量元素
for (int i = 0; i < numbers2.size(); i++) {
std::cout << numbers2[i] << " ";
}
return 0;
}
输出结果:
方法2:使用STL中的算法组件(algorithm)中的for_each
#include<vector>
#include<iostream>
#include <algorithm>
void out(int a) {
std::cout << a << " ";
}
int main() {
// 创建 vector 对象:
std::vector<int> numbers2(5); // 创建包含5个元素的vector,初始值为0
std::cout << "向量numbers2元素:{ ";
for_each(numbers2.begin(), numbers2.end(), out);
std::cout << "}" << std::endl;
return 0;
}
输出结果:
1.3.2 不能打印向量对象;不能打印空向量中的元素,因为空向量中无元素可打印
#include<vector>
#include<iostream>
int main() {
// 一.创建 vector 对象:
// 可以使用默认构造函数创建一个空的vector,
// 也可以在创建时指定初始大小和初始值。例如:
std::vector<int> numbers; // 创建一个空的vector
std::cout << numbers << std::endl; // 该语句报错,不能打印向量对象
std::cout << numbers[0] << std::endl; // 该语句报错,空向量中无元素可打印
return 0;
}
1.3.3 添加和删除元素
#include<iostream>
#include<vector>
// 打印向量元素
void print_vector(std::vector<int> vec) {
// vec 目标向量
for (int i = 0; i < vec.size(); i++) {
std::cout << vec[i] << ", ";
}
std::cout << std::endl;
}
int main(){
std::vector<int> numbers;
numbers.push_back(5); // 在向量numbers尾部添加一个元素
numbers.push_back(4); // 在向量numbers尾部添加一个元素
numbers.push_back(3); // 在向量numbers尾部添加一个元素
numbers.push_back(2); // 在向量numbers尾部添加一个元素
numbers.push_back(1); // 在向量numbers尾部添加一个元素
std::cout << "vector numbers的元素:\n";
print_vector(numbers);
numbers.pop_back(); // 删除尾部元素
std::cout << "vector numbers删除尾部元素后剩余的元素:\n";
print_vector(numbers);
numbers.insert(numbers.begin()+2,10); // 2表示下标,10表示要插入的元素
std::cout << "vector numbers在第三个位置上插入指定元素:\n";
print_vector(numbers);
numbers.erase(numbers.begin() + 3); // 3表示要删除的下标
std::cout << "vector numbers删掉第四个位置上的元素:\n";
print_vector(numbers);
}
输出结果:
1.4 C ++向量函数
函数 | 描述 |
---|---|
at(idx) | 传回索引idx所指的数据,如果idx越界,抛出out_of_range。 |
back() | 返回最后一个原始,不检查这个数据是否存在。 |
front() | 返回第一个元素。 |
swap() | 交换两个Vector。 |
push_back() | 在Vector最后添加一个元素。 |
pop_back() | 它从向量中删除最后一个元素。 |
empty() | 判断Vector是否为空(返回true时为空) |
insert() | 它将在指定位置插入新元素。 |
erase() | 删除指定的元素。 |
resize() | 它修改向量的大小。 |
clear() | 它从向量中删除所有元素。 |
size() | 返回Vector元素数量的大小。 |
capacity() | 返回vector所能容纳的元素数量(在不重新分配内存的情况下) |
assign() | 它将新值分配给向量。 |
operator=() | 它将新值分配给向量容器。 |
operator[]() | 它访问指定的元素。 |
end() | 返回最末元素的迭代器(实指向最末元素的下一个位置) |
emplace() | 它将在位置pos之前插入一个新元素。 |
emplace_back() | 它在末尾插入一个新元素。 |
rend() | 它指向向量的第一个元素之前的元素。 |
rbegin() | 它指向向量的最后一个元素。 |
begin() | 返回第一个元素的迭代器。 |
max_size() | 返回Vector所能容纳元素的最大数量(上限)。 |
cend() | 它指向量中的last-last-element。 |
cbegin() | 它指向量的第一个元素。 |
crbegin() | 它指的是向量的最后一个字符。 |
crend() | 它指的是向量的第一个元素之前的元素。 |
data() | 它将向量的数据写入数组。 |
shrink_to_fit() | 它减少了容量并使它等于向量的大小。 |
1.5 总结
STL中的vector
(向量)是一个动态数组,提供了在尾部高效地插入和删除元素,并支持随机访问元素的功能。下面是关于vector
的一些重要特点和常用操作:
-
特点:
- 连续的内存空间:
vector
中的元素在内存中是连续存储的,可以通过指针算术和迭代器来访问元素。 - 动态大小:
vector
的大小可以根据需要增长或缩小,可以动态地插入或删除元素。 - 快速随机访问:由于元素的连续存储,可以使用索引进行快速的随机访问,时间复杂度为O(1)。
- 尾部插入和删除:在尾部插入或删除元素的操作非常高效,时间复杂度为O(1)。
- 连续的内存空间:
2. 双端队列Deque
它概括了队列数据结构,即可以从前端或后端的两端进行插入和删除,如下图所示。
2.1 创建双端队列对象的语法
deque<object_type> deque_name;
2.2 示例
2.2.1 创建 deque 对象
可以使用默认构造函数创建一个空的 deque,也可以在创建时指定初始大小和初始值。
#include<iostream>
#include<deque>
// 打印deque元素
void print_deque(std::deque<int> deq) {
// vec 目标向量
for (int i = 0; i < deq.size(); i++) {
std::cout << deq[i] << ", ";
}
std::cout << std::endl;
}
int main() {
// 一.创建 vector 对象:
// 可以使用默认构造函数创建一个空的vector,
// 也可以在创建时指定初始大小和初始值。例如:
std::deque<int> numbers; // 创建一个空的deque
std::deque<int> numbers2(5); // 创建包含5个元素的deque,初始值为0
std::deque<int> numbers3(5, 10); // 创建包含5个元素的deque,初始值都为10
std::cout << "deque numbers 的大小为:" << numbers.size() << std::endl;
std::cout << "deque numbers2 的大小为:" << numbers2.size() << std::endl;
// 打印向量numbers2元素
std::cout << "deque numbers2 的元素为:";
print_deque(numbers2);
std::cout << "deque numbers3 的大小为:" << numbers3.size() << std::endl;
// 打印向量numbers3元素
std::cout << "deque numbers3 的元素为:";
print_deque(numbers3);
return 0;
}
输出结果:
2.2.2 不能打印deque对象;不能打印空deque中的元素,因为空deque中无元素可打印
#include<iostream>
#include<deque>
int main(){
std::deque<int> numbers;
std::cout << numbers; // 该语句报错,不能打印deque对象
std::cout << numbers[0]; // 该语句报错,空deque中无元素可打印
}
2.2.3 deque 删除和插入元素
#include<iostream>
#include<deque>
// 打印deque元素
void print_deque(std::deque<int> deq) {
for (int i = 0; i < deq.size(); i++) {
std::cout << deq[i] << ", ";
}
std::cout << std::endl;
}
int main(){
std::deque<int> numbers;
numbers.push_back(5); // 在deque numbers尾部添加一个元素
numbers.push_front(4); // 在deque numbers头部添加一个元素
numbers.push_back(3); // 在deque numbers尾部添加一个元素
numbers.push_front(2); // 在deque numbers头部添加一个元素
numbers.push_back(1); // 在deque numbers尾部添加一个元素
std::cout << "deque numbers的元素:\n";
print_deque(numbers);
numbers.pop_back(); // 删除尾部元素
std::cout << "deque numbers删除尾部元素后剩余的元素:\n";
print_deque(numbers);
numbers.pop_front(); // 删除头部元素
std::cout << "deque numbers删除头部元素后剩余的元素:\n";
print_deque(numbers);
numbers.insert(numbers.begin()+2,10); // 2表示下标,10表示要插入的元素
std::cout << "deque numbers在第三个位置上插入指定元素:\n";
print_deque(numbers);
numbers.erase(numbers.begin() + 3); // 3表示要删除的下标
std::cout << "deque numbers删掉第四个位置上的元素:\n";
print_deque(numbers);
}
输出结果:
2.3 C ++双端队列相关函数
方法 | 描述 |
---|---|
assign() | 它分配新内容并替换旧内容。 |
emplace() | 它将在指定位置添加一个新元素。 |
emplace_back() | 它在末尾添加一个新元素。 |
emplace_front() | 它在双端队列的开头添加一个新元素。 |
insert() | 它在指定位置之前添加一个新元素。 |
push_back() | 它在容器的末尾添加一个新元素。 |
push_front() | 它在容器的开头添加一个新元素。 |
pop_back() | 它从双端队列中删除最后一个元素。 |
pop_front() | 它从双端队列中删除第一个元素。 |
swap() | 它交换两个双端队列的内容。 |
clear() | 它将删除双端队列的所有内容。 |
empty() | 它检查容器是否为空。 |
erase() | 它删除元素。 |
max_size() | 它确定双端队列的最大大小。 |
resize() | 它改变了双端队列的大小。 |
shrink_to_fit() | 它减少了内存以适合双端队列的大小。 |
size() | 它返回元素数。 |
at() | 它访问位置pos处的元素。 |
operator[]() | 它访问位置pos处的元素。 |
operator=() | 它将新的内容分配给容器。 |
back() | 它访问最后一个元素。 |
begin() | 它将迭代器返回到双端队列的开头。 |
cbegin() | 它向双端队列的开头返回一个常量迭代器。 |
end() | 它将迭代器返回到末尾。 |
cend() | 它将常量迭代器返回到末尾。 |
rbegin() | 它将反向迭代器返回到开头。 |
crbegin() | 它将常量反向迭代器返回到开头。 |
rend() | 它将反向迭代器返回到末尾。 |
crend() | 它将常量反向迭代器返回到末尾。 |
front() | 它访问最后一个元素。 |
2.4 总结
STL中的deque(双端队列)是一种序列式容器,它提供了类似于vector的随机访问和在两端高效插入或删除元素的功能。deque与vector不同之处在于,它允许在容器的两端进行插入和删除操作,并且不会失去随机访问的能力。下面是关于deque的一些重要特点和常用操作:
-
特点:
- 双端队列:deque 是一个双端队列,支持在队列的两端进行高效的插入和删除操作。
- 动态扩容:deque 内部维护了多个固定大小的块,每个块都是一个连续的内存空间,采用分段连续存储的方式来实现动态扩容。
- 高效的随机访问:deque 支持类似于数组的随机访问,可以通过下标或迭代器快速访问任意位置的元素。
3.双向链表(list)【也有叫列表的】
3.1 定义
STL(Standard Template Library)中的list
是C++标准库提供的容器之一,用于储存元素的双向链表。list
的特点是可以高效地在任意位置插入和删除元素,但无法通过索引直接访问元素,因此在查找特定元素时效率较低。
以下是list
容器的一些特点和常用操作:
-
list
是双向链表:每个元素都包含指向前一个元素和后一个元素的指针,因此在插入和删除操作上具有较好的性能。与vector
和deque
等连续存储容器不同,list
的元素在内存中可以是分散存储的。 -
可以在任意位置插入或删除元素:使用
insert()
、erase()
函数可以在指定位置插入或删除元素。相比于vector
,list
执行插入和删除操作的时间复杂度更低。 -
无法通过索引访问元素:由于
list
不支持随机访问,因此不能像数组或vector
那样使用索引来访问元素。如果需要按索引访问元素,请考虑使用其他容器,如vector
。 -
提供双向迭代器:可以使用双向迭代器遍历
list
容器,并对元素进行操作。例如,可以使用迭代器进行查找、修改或交换元素。
3.2 示例
#include <iostream>
#include <list>
int main() {
std::list<int> myList;
// 在list末尾插入元素
myList.push_back(10);
myList.push_back(20);
myList.push_back(30);
// 在list开头插入元素
myList.push_front(5);
// 遍历list元素
for (auto it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " ";
}
// 输出结果:5 10 20 30
// 在list中间插入元素
auto it = myList.begin();
++it; // 指向第二个元素
myList.insert(it, 15);
// 删除list中的元素
myList.erase(myList.begin()); // 删除第一个元素
// 判断list是否为空
if (myList.empty()) {
std::cout << "list为空" << std::endl;
} else {
std::cout << "list不为空" << std::endl;
}
return 0;
}
在上述代码中,我们首先创建了一个list
对象myList
,并使用push_back()
函数将元素插入到链表末尾。然后,使用push_front()
函数在链表开头插入元素。接下来,使用迭代器遍历list
并输出元素。
之后,使用insert()
函数在链表中间插入元素。在示例中,我们将元素15插入到第二个位置。最后,使用erase()
函数删除链表中的第一个元素。
最后,通过empty()
函数判断list
是否为空。
需要注意的是,为了使用list
容器,需要包含头文件<list>
。另外,list
与其他容器一样,支持迭代器以及各种操作函数,如size()
、clear()
等。
3.3 list元素的遍历
在STL中,可以使用迭代器来遍历`list`容器的元素。`list`容器提供了双向迭代器,可以从容器的起始点或末尾开始遍历。
以下是几种遍历`list`容器元素的常用方式:
1. 使用迭代器遍历:
std::list<int> myList = {1, 2, 3, 4, 5};
// 从头到尾遍历
for (auto it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " ";
}
// 输出结果:1 2 3 4 5
// 从尾到头遍历
for (auto it = myList.rbegin(); it != myList.rend(); ++it) {
std::cout << *it << " ";
}
// 输出结果:5 4 3 2 1
在上述代码中,使用双向迭代器`begin()`和`end()`可以从头到尾遍历`list`容器的元素。使用`rbegin()`和`rend()`则可以从尾到头遍历。
2. 使用范围-based for 循环:
std::list<int> myList = {1, 2, 3, 4, 5};
// 从头到尾遍历
for (const auto& element : myList) {
std::cout << element << " ";
}
// 输出结果:1 2 3 4 5
// 从尾到头遍历(需要使用引用类型)
for (const auto& element : reverse(myList)) {
std::cout << element << " ";
}
// 输出结果:5 4 3 2 1
在上述代码中,使用范围-based for 循环可以方便地遍历`list`容器的元素。如果需要从尾到头遍历,可以使用一个简单的辅助函数`reverse()`来翻转容器,然后进行遍历。
无论是使用迭代器还是范围-based for 循环,都可以遍历`list`容器中的元素。根据具体情况选择合适的遍历方式。需要注意的是,遍历过程中请勿对`list`容器进行增删操作,以免造成迭代器失效。
3.4 C ++list相关的函数
以下是 list 的成员函数:
方法 | 描述 |
---|---|
insert() | 它将新元素插入到迭代器指向的位置之前。 |
push_back() | 它在容器的末尾添加了一个新元素。 |
push_front() | 它在前面增加了一个新元素。 |
pop_back() | 删除最后一个元素。 |
pop_front() | 删除第一个元素。 |
empty() | 它检查列表是否为空。 |
size() | 它查找列表中存在的元素数。 |
max_size() | 它找到列表的最大大小。 |
front() | 它返回列表的第一个元素。 |
back() | 它返回列表的最后一个元素。 |
swap() | 当两个列表的类型相同时,它将交换两个列表。 |
reverse() | 它反转了列表的元素。 |
sort() | 它以递增的顺序对列表中的元素进行排序。 |
merge() | 它合并两个排序的列表。 |
splice() | 它将新列表插入到调用列表中。 |
unique() | 它从列表中删除所有重复的元素。 |
resize() | 它更改列表容器的大小。 |
assign() | 它将一个新元素分配给列表容器。 |
emplace() | 它将在指定位置插入一个新元素。 |
emplace_back() | 它将在容器的末尾插入一个新元素。 |
emplace_front() | 它将在列表的开头插入一个新元素。 |
4.集合(set)
4.1 定义
STL中的set
是一种关联容器,用于存储唯一的元素集合,并按照一定的顺序进行排序。它基于红黑树实现,插入、查找和删除操作的平均时间复杂度都是O(log N)。
set
的特点和用法如下:
-
特点:
- 元素是唯一的,不会存在重复。
- 默认情况下,元素按照升序排列,也可以通过自定义比较函数来指定排序规则。
- 不支持修改元素的值,可以通过删除旧元素并插入新元素的方式进行替换。
4.2 常用操作示例
4.2.1 创建集合对象
std::set<int> s; // 创建一个存储int类型的集合
4.2.2 插入元素
s.insert(10); // 插入元素10
s.insert(5); // 插入元素5
4.2.3 查找元素
auto it = s.find(10); // 查找元素10的迭代器
if (it != s.end()) {
// 元素找到
} else {
// 元素未找到
}
在STL的set
容器中,可以使用set
的成员函数find()
来确定元素在集合中的位置。find()
函数接受要查找的元素值作为参数,并返回一个指向该元素的迭代器。如果元素存在于集合中,则返回指向该元素的迭代器;如果元素不存在,则返回指向集合末尾的迭代器。
下面是一个示例代码,演示了如何使用find()
函数在set
中查找元素,并判断是否存在:
#include <iostream>
#include <set>
int main() {
std::set<int> s = {10, 20, 30, 40};
int target = 30;
// 使用 find() 函数查找元素
auto it = s.find(target);
if (it != s.end()) {
std::cout << "元素 " << target << " 存在于集合中" << std::endl;
} else {
std::cout << "元素 " << target << " 不存在于集合中" << std::endl;
}
return 0;
}
在上述代码中,我们创建了一个set
对象s
,并插入了4个元素。然后,我们定义了要查找的目标元素target
为30。使用find()
函数在set
中查找target
,并将返回的迭代器存储在it
中。如果it
等于s.end()
,则表示target
不存在于集合中;否则,表示target
存在于集合中。
根据上述示例代码,当目标元素为30时,输出结果为"元素 30 存在于集合中"。
4.2.4 删除元素
s.erase(10); // 删除元素10
4.2.5 判断集合是否为空
bool emptyStatus = s.empty(); // 判断集合是否为空
4.2.6 获取集合中元素的个数
int size = s.size(); // 获取集合中元素的个数
4.2.7 集合元素的遍历
遍历STL中的`set`容器可以使用迭代器来实现。下面是两种常用的遍历方式:
1. 使用迭代器遍历:
std::set<int> s = {10, 20, 30, 40};
// 方法一:使用迭代器遍历
for (auto it = s.begin(); it != s.end(); ++it) {
int element = *it; // 获取当前元素的值
// 对当前元素进行操作
std::cout << element << " ";
}
// 输出结果:10 20 30 40
2. 使用范围-based for 循环遍历(C++11及以上版本):
std::set<int> s = {10, 20, 30, 40};
// 方法二:使用范围-based for 循环遍历
for (const int& element : s) {
// 对当前元素进行操作
std::cout << element << " ";
}
// 输出结果:10 20 30 40
无论是使用迭代器遍历还是使用范围-based for 循环遍历,都能够按照`set`容器中元素的升序顺序遍历。需要注意的是,`set`中的元素是按照键值有序存储的,而不是插入的顺序。因此,遍历结果会按照升序输出。
4.3 set的成员函数
以下是set的所有成员函数的列表:
4.3.1 构造函数/析构函数
函数 | 描述 |
---|---|
(constructor) | 构造集 |
(destructor) | set析构函数 |
operator= | 将集合的元素复制到另一个集合。 |
4.3.2 迭代器
函数 | 描述 |
---|---|
begin | 返回一个指向集合中第一个元素的迭代器。 |
cbegin | 返回指向集合中第一个元素的const迭代器。 |
end | 返回指向末尾的迭代器。 |
cend | 返回指向末尾的常量迭代器。 |
rbegin | 返回指向末尾的反向迭代器。 |
rend | 返回指向起点的反向迭代器。 |
crbegin | 返回指向末尾的常量反向迭代器。 |
crend | 返回指向起点的常量反向迭代器。 |
4.3.3 容量
函数 | 描述 |
---|---|
empty | 如果set为空,则返回true。 |
size | 返回集合中元素的数量。 |
max_size | 返回集合的最大大小。 |
4.3.4 修饰符
函数 | 描述 |
---|---|
insert | 在集合中插入元素。 |
erase | 从集合中擦除元素。 |
swap | 交换集合的内容。 |
clear | 删除集合中的所有元素。 |
emplace | 构造新元素并将其插入到集合中。 |
emplace_hint | 通过提示构造新元素并将其插入到集合中。 |
4.3.5 观测比较
函数 | 描述 |
---|---|
key_comp | 返回一个用于元素间值比较的函数。 |
value_comp | 返回一个用于比较元素间的值的函数。 |
4.3.6 操作查找
函数 | 描述 |
---|---|
find | 搜索具有给定键的元素。 |
count | 获取与给定键匹配的元素数。 |
lower_bound | 返回指向大于(或等于)某值的第一个元素的迭代器。 |
upper_bound | 返回大于某个值元素的迭代器。 |
equal_range | 返回集合中与给定值相等的上下限的两个迭代器。 |
4.3.7 分配器
函数 | 描述 |
---|---|
get_allocator | 返回用于构造集合的分配器对象。 |
4.4 非成员重载函数
函数 | 描述 |
---|---|
operator== | 检查两组是否相等。 |
operator!= | 检查两组是否相等。 |
operator< | 检查第一组是否小于其他组。 |
operator<= | 检查第一组是否小于或等于其他。 |
operator> | 检查第一个集合是否大于其他集合。 |
operator>= | 检查第一个集合是否大于其他集合。 |
swap() | 交换两组元素。 |
4.5 总结
set
常用于需要存储唯一元素且需要有序遍历的场景,例如去重、范围查找等。它提供了高效的插入和查找操作,但不支持随机访问和按索引访问元素。如果需要这些功能,可以考虑使用vector
或deque
等容器。
5.栈(stack)
5.1 定义
STL中的stack
是一种栈容器,遵循后进先出(Last-In-First-Out,LIFO)的原则。它提供了在栈顶插入元素和从栈顶删除元素的操作。
stack
的特点和用法如下:
-
特点:
- 栈的元素按照插入的顺序相反的顺序排列。
- 只能在栈顶插入元素,并且只能从栈顶删除元素。
5.2 常用操作
5.2.1 创建栈对象
std::stack<int> s; // 创建一个存储int类型的栈
5.2.2 插入元素
s.push(10); // 在栈顶插入元素10
s.push(5); // 在栈顶插入元素5
5.2.3 访问栈顶元素
int topElement = s.top(); // 获取栈顶元素
5.2.4 删除栈顶元素
s.pop(); // 删除栈顶元素
5.2.5 判断栈是否为空
bool emptyStatus = s.empty(); // 判断栈是否为空
5.2.6 获取栈中元素个数
int size = s.size(); // 获取栈中元素的个数
5.3 stack的相关函数
借助函数,可以在编程领域中使用对象或变量。堆栈提供了大量可以在程序中使用或嵌入的函数。相同的列表如下:
函数 | 描述 |
---|---|
(constructor) | 该函数用于构造堆栈容器。 |
empty | 该函数用于测试堆栈是否为空。如果堆栈为空,则该函数返回true,否则返回false。 |
size | 该函数返回堆栈容器的大小,该大小是堆栈中存储的元素数量的度量。 |
top | 该函数用于访问堆栈的顶部元素。该元素起着非常重要的作用,因为所有插入和删除操作都是在顶部元素上执行的。 |
push | 该函数用于在堆栈顶部插入新元素。 |
pop | 该函数用于删除元素,堆栈中的元素从顶部删除。 |
emplace | 该函数用于在当前顶部元素上方的堆栈中插入新元素。 |
swap | 该函数用于交换引用的两个容器的内容。 |
relational operators | 非成员函数指定堆栈所需的关系运算符。 |
uses allocator<stack> | 顾名思义,非成员函数将分配器用于堆栈。 |
5.4 总结
stack
常用于需要后进先出的场景,例如表达式求值、深度优先搜索等。它提供了简单、高效的操作,适用于大部分栈需求。需要注意的是,stack
不支持随机访问和查找特定位置元素的操作,如果需要这些功能,可以考虑使用vector
或deque
等容器。
6.队列(queue)
6.1 定义
STL中的queue
是一种队列容器,遵循先进先出(First-In-First-Out,FIFO)的原则。它提供了在队列尾部插入元素和在队列头部删除元素的操作。队列插入、删除元素示意如下图所示。
queue
的特点和用法如下:
-
特点:
- 队列的元素按照插入的顺序排列。
- 只能在队列的尾部插入元素,并且只能从队列的头部删除元素。
6.2常用操作示例
6.2.1 创建队列对象
std::queue<int> q; // 创建一个存储int类型的队列
6.2.2 插入元素
q.push(10); // 在队尾插入元素10
q.push(5); // 在队尾插入元素5
访问队头元素
int frontElement = q.front(); // 获取队头元素
删除队头元素
q.pop(); // 删除队头元素
判断队列是否为空
bool emptyStatus = q.empty(); // 判断队列是否为空
获取队列中的元素个数
int size = q.size(); // 获取队列中元素的个数
6.3 queue的相关函数
借助函数,可以在编程领域中使用对象或变量。队列提供了大量可以在程序中使用或嵌入的函数。相同的列表如下:
函数 | 描述 |
---|---|
(constructor) | 该函数用于构造队列容器。 |
empty | 该函数用于测试队列是否为空。如果队列为空,则该函数返回true,否则返回false。 |
size | 该函数返回队列中元素的个数。 |
front | 该函数返回第一个元素。元素起着非常重要的作用,因为所有的删除操作都是在front元素上执行的。 |
back | 该函数返回最后一个元素。该元素起着非常重要的作用,因为所有插入操作都在后面元素上执行。 |
push | 该函数用于在末尾插入一个新元素。 |
pop | 该函数用于删除第一个元素。 |
emplace | 该函数用于在当前后元素上方的队列中插入新元素。 |
swap | 该函数用于交换参考中两个容器的内容。 |
relational operators | 非成员函数指定队列所需的关系运算符。 |
uses allocator<queue> | 顾名思义,非成员函数将分配器用于队列。 |
6.4 总结
queue
常用于需要处理先进先出场景的情况,例如任务调度、广度优先搜索等。它提供了简单、高效的操作,适用于大部分队列需求。需要注意的是,queue
不支持随机访问和查找特定位置元素的操作,如果需要这些功能,可以考虑使用vector
或deque
等容器。
7.优先队列(priority_queue)
7.1 定义
C ++中的优先队列是STL中的派生容器,它仅考虑最高优先级元素。队列遵循FIFO策略,而优先队列根据优先级弹出元素,即,优先级最高的元素首先弹出。
它在某些方面类似于普通队列,但在以下方面有所不同:
-
在优先队列中,队列中的每个元素都与某个优先级相关联,但是优先级在队列数据结构中不存在。
-
优先队列中具有最高优先级的元素将被首先删除,而队列遵循FIFO(先进先出)策略,这意味着先插入的元素将被首先删除。
-
如果存在多个具有相同优先级的元素,则将考虑该元素在队列中的顺序。
注意:优先队列是普通队列的扩展版本,但优先级最高的元素将首先从优先队列中删除。
7.2 语法
priority_queue<int> variable_name;
7.3 示例
7.3.1 创建优先队列对象
std::priority_queue<int> pq; // 创建一个存储int类型的优先队列,默认降序排序
7.3.2 插入元素
pq.push(10); // 插入元素10
pq.push(5); // 插入元素5
7.3.3 弹出元素
int topElement = pq.top(); // 获取最高优先级的元素,不会删除
pq.pop(); // 弹出最高优先级的元素
7.3.4 遍历元素
STL中的`priority_queue`是一种适配器容器,封装了底层的堆(Heap)数据结构。由于堆并不提供迭代器(iterator)接口,因此无法像其他容器那样直接使用迭代器进行遍历。
要遍历一个`priority_queue`,可以借助其特性和操作进行操作:
1. 弹出元素:通过重复调用`pop()`方法,可以逐个弹出优先队列中的元素。这会按照优先级从高到低的顺序弹出每个元素。
std::priority_queue<int> pq;
// 添加元素...
while (!pq.empty()) {
int topElement = pq.top(); // 获取最高优先级的元素
pq.pop(); // 弹出最高优先级的元素
// 处理 topElement
}
2. 拷贝到其他容器:将`priority_queue`中的元素依次弹出,并添加到其他容器中进行遍历。
std::priority_queue<int> pq;
// 添加元素...
std::vector<int> vec;
while (!pq.empty()) {
int topElement = pq.top(); // 获取最高优先级的元素
pq.pop(); // 弹出最高优先级的元素
vec.push_back(topElement); // 添加到其他容器中
}
// 遍历其他容器 vec
for (const auto& element : vec) {
// 处理 element
}
需要注意的是,`priority_queue`在弹出元素时是按照优先级从高到低的顺序进行的,而不按照插入的顺序。因此,在遍历时需要根据具体需求来决定是否需要保留原始顺序。
另外,如果需要对`priority_queue`中的元素进行修改,可以通过借助其他数据结构进行遍历、更改,再重新构建一个新的`priority_queue`。
7.3.5 priority_queue元素的插入、弹出、遍历、显示综合操作
#include<iostream>
#include<queue>
void print_priority_queue(std::priority_queue<int> p) {
while (!p.empty())
{
std::cout << p.top() << ", ";
p.pop();
}
std::cout << std::endl;
}
int main() {
std::priority_queue<int>pq;
pq.push(1);
pq.push(4);
pq.push(2);
std::cout << "可用元素的数量:" << pq.size() << std::endl;
print_priority_queue(pq);
pq.pop();
std::cout << "第一次弹出元素之后剩余可用元素的数量:" << pq.size() << std::endl;
print_priority_queue(pq);
pq.push(3);
std::cout << "第二次插入元素之后可用元素的数量:" << pq.size() << std::endl;
print_priority_queue(pq);
pq.pop();
std::cout << "第二次弹出元素之后剩余可用元素的数量:" << pq.size() << std::endl;
print_priority_queue(pq);
}
输出结果:
7.4 优先队列的成员函数
函数 | 描述 |
---|---|
push() | 它将新元素插入优先队列。 |
pop() | 它将优先级最高的元素从队列中删除。 |
top() | 此函数用于寻址优先队列的最顶层元素。 |
size() | 返回优先队列的大小。 |
empty() | 它验证队列是否为空。基于验证,它返回队列的状态。 |
swap() | 它将优先队列的元素与具有相同类型和大小的另一个队列交换。 |
emplace() | 它在优先队列的顶部插入一个新元素。 |
7.5 总结
STL中的priority_queue
是一个优先队列容器,也是一种队列(Queue)的实现方式。与普通队列不同的是,优先队列中的元素具有优先级,每次弹出的都是具有最高优先级的元素。
priority_queue
的特点如下:
-
特点:
- 每次弹出的元素都是具有最高优先级的元素。
- 默认情况下,优先队列中的元素按照降序排列,即最大的元素排在队列的前面。可以通过自定义比较函数或使用自定义类型的重载
<
运算符来改变排序方式。 - 内部使用堆(Heap)实现,保证了对于插入和弹出操作的高效性。
优先队列非常适用于需要处理按照优先级排序的任务或元素的场景,例如任务调度、最小/最大K个元素等。在实际应用中,常常配合自定义类型和自定义比较函数来满足特定需求。需要注意的是,默认情况下,priority_queue
不支持随机访问和更改元素的操作。如需更灵活的操作,可以考虑使用multiset
、set
等容器结合自定义排序方式。
8.映射(map)
8.1 定义
STL中的map是一种关联式容器,它提供了一种键-值(key-value)对的映射关系。map内部使用红黑树(Red-Black Tree)实现,保证了快速的查找和插入操作,并且按照键值的有序性进行存储。
8.2 语法
template < class Key, //map::key_type
class T, //map::mapped_type
class Compare = less, //map::key_compare
class Alloc = allocator<pair> //map::allocator_type
> class map;
参数
key:要存储在map中的键的数据类型。
type:要存储在map中的值的数据类型。
compare:一个比较类,它接受两个bool类型相同的参数,并返回一个值。此参数是可选的,二进制谓词less <“ key”>是默认值。
alloc:分配器对象的类型。此参数是可选的,默认值为分配器。
8.3 创建map
使用以下语句可以轻松创建map:
typedef pair<const Key, T> value_type;
上面的语句将用于创建一个键类型为Key类型,且value值类型为 value_type 的map。重要的一点是,map的键和相应的值始终成对插入,您不能在map中仅插入键或仅插入值。
8.4 示例
8.4.1 创建map对象
std::map<int, std::string> studentMap; // 创建一个空的map,键的类型为int,值的类型为std::string
8.4.2 插入、删除和访问元素
studentMap.insert(std::make_pair(1, "Alice")); // 插入一对键值对
studentMap[2] = "Bob"; // 使用下标操作符插入或更新元素
// 访问元素
std::string name1 = studentMap[1]; // 使用下标操作符获取指定键的值
auto iter = studentMap.find(2); // 使用find函数查找指定键的迭代器
std::string name2 = iter->second; // 通过迭代器访问值
studentMap.erase(1); // 根据键删除指定元素
8.4.3 在不为空的map中插入已存在的键所对应的新值,则会覆盖掉该键所对应的旧值
#include<iostream>
#include<map>
// 打印map元素
void print_map(std::map<int,std::string> Map) {
for (const auto& pair : Map) {
int key = pair.first;
std::string value = pair.second;
// 对每个键值对进行操作
std::cout << "key-value:" << key << "-" << value << std::endl;
}
std::cout << std::endl;
}
int main() {
std::map<int, std::string>studentMap; // 创建一个空map,键的类型为int,值的类型为std::string
studentMap.insert(std::make_pair(1, "Alex"));
studentMap.insert(std::make_pair(2, "Join"));
studentMap.insert(std::make_pair(3, "Jore"));
studentMap.insert(std::make_pair(4, "Daive"));
std::cout << "没有更换之前的studentMap中的元素为:\n";
print_map(studentMap);
std::cout << "在studentMap的第二个位置上更换新值:\n";
studentMap[2] = "Bob";
print_map(studentMap);
}
输出结果:
8.4.4 打印map中的元素
// 打印map元素
void print_map(std::map<int,std::string> Map) {
for (const auto& pair : Map) {
int key = pair.first;
std::string value = pair.second;
// 对每个键值对进行操作
std::cout << "key-value:" << key << "-" << value << std::endl;
}
std::cout << std::endl;
}
该函数传入的参数是创建的map对象。
上述函数中的for循环解析:
这是C++11引入的一种新的for循环语法结构,叫做“范围for循环”(Range-based for loop),也称增强型for循环。它可以用于遍历一个序列式容器(例如vector、list、set等)或一个关联式容器(例如map、unordered_map、multimap等)中的每个元素,语法如下:
for (const auto& element : container) {
// do something with element
}
其中,auto
是C++11的类型推导关键字,表示编译器会自动推导出变量类型。const
表示element
是只读的,不允许修改。
对于关联式容器(例如map),element
实际上是一个键值对(key-value pair)。因此,如果使用范围for循环遍历一个map,语法如下:
std::map<Key, Value> myMap;
for (const auto& pair : myMap) {
const Key& key = pair.first;
const Value& value = pair.second;
// do something with key and value
}
其中,pair.first
表示当前元素的键(key),pair.second
表示当前元素的值(value)。在迭代过程中,由于元素是以有序的形式插入到map中的,因此它们会按照键的顺序被遍历到。
8.5 map的成员函数
以下是map的所有成员函数的列表:
8.5.1 构造函数/析构函数
函数 | 描述 |
---|---|
constructors | 构造map |
destructors | map析构函数 |
operator= | 将map元素复制到另一个map容器。 |
8.5.2 迭代器
函数 | 描述 |
---|---|
begin | 返回指向map中第一个元素的迭代器。 |
cbegin | 返回指向map中第一个元素的const迭代器。 |
end | 返回指向末尾的迭代器。 |
cend | 返回指向末尾的常量迭代器。 |
rbegin | 返回指向末尾的反向迭代器。 |
rend | 返回指向起点的反向迭代器。 |
crbegin | 返回指向末尾的常量反向迭代器。 |
crend | 返回指向起点的常量反向迭代器。 |
8.5.3 容量
函数 | 描述 |
---|---|
empty | 如果map为空,则返回true。 |
size | 返回map中的元素数。 |
max_size | 返回map的最大容量。 |
8.5.4 元素访问
函数 | 描述 |
---|---|
operator[] | 用给定的键检索元素。 |
at | 用给定的键检索元素。 |
8.5.5 修饰符
函数 | 描述 |
---|---|
insert | 在map中插入元素。 |
erase | 从map上擦除元素。 |
swap | 交换map内容。 |
clear | 删除map的所有元素。 |
emplace | 构造新元素并将其插入map。 |
emplace_hint | 通过提示构造新元素并将其插入map。 |
8.5.6 观测器
函数 | 描述 |
---|---|
key_comp | 返回键比较对象的副本。 |
value_comp | 返回值比较对象的副本。 |
8.5.7 操作方式
函数 | 描述 |
---|---|
find | 搜索具有给定键的元素。 |
count | 获取与给定键匹配的元素数。 |
lower_bound | 返回迭代器的下限。 |
upper_bound | 返回一个迭代器到上限。 |
equal_range | 返回与给定键匹配的元素范围。 |
8.5.8 分配器
函数 | 描述 |
---|---|
get_allocator | 返回用于构造map的分配器对象。 |
8.5.9 非成员重载函数
函数 | 描述 |
---|---|
operator== | 检查两个map是否相等。 |
operator!= | 检查两个map是否相等。 |
operator< | 检查第一个map是否小于其他map。 |
operator<= | 检查第一个map是否小于或等于其他map。 |
operator> | 检查第一个map是否大于其他map。 |
operator>= | 检查第一个map是否大于其他map。 |
swap() | 交换两个map的元素。 |
8.6 总结
STL中的map是一种关联式容器,它提供了一种键-值(key-value)对的映射关系。map内部使用红黑树(Red-Black Tree)实现,保证了快速的查找和插入操作,并且按照键值的有序性进行存储。下面是关于map的一些重要特点和常用操作:
-
特点:
- 键值对映射:map中存储的数据是一组键值对,每个键唯一对应一个值。
- 有序存储:map会根据键的排序规则自动将键值对按升序排序,并且支持根据键进行快速的查找和插入操作。
- 动态扩容:map在插入元素时会自动进行内存扩容。
map 是一个非常有用的关联式容器,适用于需要根据键快速查找特定值的场景,并且要求按照键的顺序进行有序存储。它提供了高效的插入、查找和删除操作,适用于大部分需要快速获取键值对的应用场景。需要注意的是,map中的键是唯一的,因此插入相同键的多个键值对只会保留最后一个。如果你需要存储多个相同键的值,可以考虑使用multimap。
二、算法
STL(Standard Template Library)提供了一组强大的算法,用于在容器上执行各种操作。这些算法包括查找、排序、修改、删除等,可以应用于多种STL容器,如vector
、list
、map
等。
下面是一些常用的STL算法示例:
1. std::find()
:在容器中查找特定值。
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = std::find(vec.begin(), vec.end(), 3);
if (it != vec.end()) {
std::cout << "找到了3" << std::endl;
} else {
std::cout << "未找到3" << std::endl;
}
2. std::sort()
:对容器进行排序。
std::vector<int> vec = {5, 3, 1, 4, 2};
std::sort(vec.begin(), vec.end());
for (const auto& element : vec) {
std::cout << element << " ";
}
// 输出结果:1 2 3 4 5
3. std::transform()
:对容器中的元素进行转换。
std::vector<int> vec = {1, 2, 3, 4, 5};
std::vector<int> result;
std::transform(vec.begin(), vec.end(), std::back_inserter(result), [](int x) {
return x * 2;
});
for (const auto& element : result) {
std::cout << element << " ";
}
// 输出结果:2 4 6 8 10
4. std::remove_if()
:移除满足条件的元素。
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.erase(std::remove_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0; // 移除偶数
}), vec.end());
for (const auto& element : vec) {
std::cout << element << " ";
}
// 输出结果:1 3 5
这些算法只是STL算法中的一小部分,还有很多其他算法可以实现不同的操作。在使用STL算法时,需要包含相应的头文件<algorithm>
,并根据具体需求选择合适的算法进行操作。
请注意,这里使用了C++11提供的lambda表达式来定义匿名函数,方便对容器元素进行操作。
三、迭代器
STL(Standard Template Library)中的迭代器是一种抽象的概念,它为用户提供一种统一的方式来遍历和访问容器中的元素,而不直接暴露容器内部的数据结构。
迭代器工作的基本原理如下:
1. 迭代器是一个类似指针的对象,它包含了指向容器内部元素的指针或引用,以及一些与元素访问和操作相关的成员函数。
2. 每个迭代器类型都定义了一组迭代器操作,如解引用、递增、递减、比较等。这些操作允许用户以统一的方式使用迭代器进行遍历和对元素的访问。
3. 容器类提供了begin()和end()等成员函数,用于返回迭代器。begin()函数返回指向容器中第一个元素的迭代器,end()函数返回指向容器末尾标记(即尾后迭代器)的迭代器。
4. 用户可以通过迭代器进行循环遍历容器中的元素,使用解引用操作符(*)获取当前迭代器指向的元素值,使用递增操作符(++)将迭代器移动到下一个位置。迭代器的递增操作可能会根据具体迭代器类型的特点而有所不同。
5. 迭代器的比较操作用于判断两个迭代器是否相等,或者某个迭代器在另一个迭代器之前、之后。
通过使用迭代器,用户可以以一种统一的方式访问不同类型的容器,而无需关心容器内部的实际数据结构。这种抽象性使得用户可以更方便地编写通用的、与容器无关的算法,同时提高了代码的可重用性和可扩展性。
需要注意的是,迭代器的有效性取决于容器的操作。当容器进行插入、删除等结构修改操作时,迭代器可能会失效,导致未定义行为。因此,在对容器进行修改操作时,需要谨慎处理相关迭代器的有效性,并避免在迭代器失效的情况下继续使用它们。
STL(Standard Template Library)提供了几种不同类型的迭代器,用于遍历和操作STL容器中的元素。迭代器是指向容器中元素的对象,它允许用户以统一的方式访问容器的元素。
以下是STL中常见的迭代器类型:
1. 前向迭代器 (Forward Iterator):可以逐个访问容器中的元素,并支持向前迭代。如`std::forward_list`中的迭代器。
2. 双向迭代器 (Bidirectional Iterator):除了支持前向迭代器的功能外,还可以反向迭代,即向后迭代。如`std::list`中的迭代器。
3. 随机访问迭代器 (Random Access Iterator):具有双向迭代器的所有功能,并额外支持随机访问、跳跃访问等操作。如`std::vector`中的迭代器。
4. 输入迭代器 (Input Iterator):只能进行单向顺序读取,并且只能使用一次。如使用算法`std::find`时的迭代器。
5. 输出迭代器 (Output Iterator):只能进行单向顺序写入,并且只能使用一次。如使用算法`std::copy`时的迭代器。
迭代器的使用方式类似指针,可以使用解引用操作符`*`获取当前迭代器所指向的元素,使用递增操作符`++`将迭代器移动到下一个位置。此外,迭代器还支持其他操作,如比较运算符、算术运算符等。
以下是一个使用迭代器遍历容器的示例:
std::vector<int> vec = {1, 2, 3, 4, 5};
// 使用迭代器遍历容器
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
// 输出结果:1 2 3 4 5
在上述代码中,`vec.begin()`返回指向容器首元素的迭代器,`vec.end()`返回指向容器尾元素下一个位置的迭代器。通过使用迭代器进行循环遍历,我们可以访问并操作容器中的元素。
需要注意的是,迭代器的使用要遵循迭代器的有效性规则,即当容器结构发生改变时(如插入或删除元素),迭代器可能会失效,导致未定义行为。因此,在对容器进行修改操作时,请确保相关迭代器仍然有效。
四、仿函数
STL中的仿函数(Functors)是一种特殊的对象,它可以像函数一样调用并产生一个结果。在STL中,仿函数主要用于算法中的比较、排序、查找等操作,以及容器类中的元素插入和删除等操作。
STL提供了多个仿函数类,如谓词(Predicate)、比较器(Comparator)等。其中,谓词是一种特殊的仿函数,它的返回值为bool类型,用于表示某个条件是否成立。比较器是另一种常见的仿函数,它用于比较两个值的大小,并返回一个bool值表示它们之间的关系。
STL中的仿函数可以按照参数个数分为以下几类:
-
一元仿函数:只接受一个参数的仿函数,如
std::negate
、std::logical_not
等。 -
二元仿函数:接受两个参数的仿函数,如
std::plus
、std::minus
、std::multiplies
、std::divides
等。 -
关系仿函数:用于比较两个值的关系,如
std::less
、std::greater
、std::equal_to
、std::not_equal_to
等。 -
逻辑仿函数:用于逻辑运算的仿函数,如
std::logical_and
、std::logical_or
、std::logical_not
等。
使用仿函数可以很方便地实现各种操作,例如,在排序时可以使用比较器仿函数来指定排序规则:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = { 3, 1, 4, 1, 5, 9, 2, 6, 5 };
std::sort(numbers.begin(), numbers.end(), std::greater<int>()); // 使用关系仿函数进行排序
for (const auto& number : numbers) {
std::cout << number << " ";
}
std::cout << std::endl;
return 0;
}
// 输出结果:
// 9 6 5 5 4 3 2 1 1
const auto& number : numbers
是一个范围基于循环(range-based for loop)的语法,用于遍历一个容器或者其他可迭代的对象中的元素。
在这个语法中,numbers
是一个可迭代的对象,例如一个数组、向量(vector)、列表(list)等容器。 number
则是迭代过程中每一个元素的引用,其类型由编译器自动推断。 使用 const auto&
的方式可以确保在迭代过程中不会修改元素的值,并且通过引用的方式访问元素可以提高效率。
五、适配器
STL(Standard Template Library)提供了几种适配器,用于在现有容器或算法的基础上进行功能扩展或改变行为。适配器是一种包装器,它接受一种类型的对象,并以新的接口或行为提供给用户使用。
以下是STL中常见的适配器:
1. 容器适配器(Container Adapters):
- 栈(Stack)适配器:提供LIFO(Last In, First Out)的访问方式,支持push、pop和top操作。基于deque、list或vector实现。
- 队列(Queue)适配器:提供FIFO(First In, First Out)的访问方式,支持push、pop和front操作。基于deque或list实现。
- 优先队列(Priority Queue)适配器:按照元素的优先级进行排序,支持push、pop和top操作。基于vector或deque实现。
2. 迭代器适配器(Iterator Adapters):
- 反向迭代器(Reverse Iterator)适配器:将一个迭代器的方向反转,使得反向遍历成为可能。例如,使用`std::reverse_iterator`可以从容器的末尾向前遍历。
- 插入迭代器(Insert Iterator)适配器:用于在容器中的特定位置插入元素,如`std::back_insert_iterator`和`std::front_insert_iterator`。
- 流迭代器(Stream Iterator)适配器:将输入输出流转化为迭代器,使得可以通过迭代器的方式读取和写入流中的数据。例如,使用`std::istream_iterator`从输入流中逐个读取数据。
3. 函数适配器(Function Adapters):
- 函数指针适配器(Function Pointer Adapter):将函数指针转化为函数对象(函数符),以便在需要函数对象的地方使用。如`std::ptr_fun`、`std::mem_fun`等。
- 函数对象适配器(Function Object Adapter):对现有的函数对象进行包装或修饰,以改变其行为。如`std::bind1st`、`std::bind2nd`、`std::not1`、`std::not2`等。
这些适配器提供了一种灵活的方式来改变容器的行为或扩展算法的功能,同时遵循STL的一致性和可组合性原则。通过使用适配器,我们可以快速实现一些常见的功能,而无需重新设计或编写复杂的代码。
六、空间配置器
STL中的空间配置器(allocator)是一种用于管理内存分配和释放的工具。它主要用于动态分配容器中的元素的内存空间,并在需要时进行扩展或收缩。
STL中的空间配置器遵循了一种通用的接口,使得容器可以独立于具体的内存管理实现。它提供了以下几个主要的接口函数:
-
allocate
:用于在自由存储区(heap)上分配指定数量的内存空间,并返回一个指向该内存空间的指针。allocate
函数的声明如下:pointer allocate(size_type n);
其中,
n
是需要分配的内存块数量,size_type
是整数类型,通常定义为std::size_t
。pointer
是指向要分配内存的指针类型,通常定义为T*
。 -
deallocate
:用于释放先前分配的内存空间。 -
construct
:用于在已分配的内存空间上构造对象。construct
函数的声明如下:void construct(pointer p, const T& val);
其中,
p
是一个指向已分配内存空间的指针,val
是用于构造对象的值。T
是要构造的对象的类型,pointer
是指向T
的指针类型。使用
construct
函数可以在已分配的内存空间上构造对象,而不需要显式地调用对象的构造函数。 -
destroy
:用于销毁已构造的对象。
使用空间配置器可以将内存管理与容器的其他操作(如插入、删除等)解耦,使得容器的实现更加灵活和可扩展。
下面是一个使用空间配置器的示例,演示了如何使用空间配置器来分配和释放内存空间,并在其中构造和销毁对象:
#include <iostream>
#include <memory>
#include <vector>
int main() {
std::allocator<int> myAllocator;
// 分配内存空间
int* p = myAllocator.allocate(5);
// 在已分配的内存空间上构造对象
for (int i = 0; i < 5; ++i) {
myAllocator.construct(p + i, i + 1);
}
// 输出构造后的对象
for (int i = 0; i < 5; ++i) {
std::cout << p[i] << " ";
}
std::cout << std::endl;
// 销毁对象
for (int i = 0; i < 5; ++i) {
myAllocator.destroy(p + i);
}
// 释放内存空间
myAllocator.deallocate(p, 5);
return 0;
}
上述代码使用了std::allocator
作为空间配置器,通过allocate
函数分配了一块大小为5的内存空间,并使用construct
函数在其中构造了5个整数对象。然后,通过循环输出构造后的对象,并使用destroy
函数销毁了这些对象。最后,使用deallocate
函数释放了先前分配的内存空间。
需要注意的是,STL中的容器默认使用的是std::allocator
作为空间配置器,因此在大多数情况下,我们无需显式地指定空间配置器,而是由容器自己管理内存分配和释放的过程。
持续更新!!!