容器分类

C++中常用的容器可以分为顺序容器、关联容器和容器适配器三大类。以下是详细的介绍:

1.顺序容器

顺序容器将单一类型元素聚集起来成为容器,然后根据位置来存储和访问这些元素。常用的顺序容器有:

  1. vector:动态数组,可以高效存储和访问元素,支持随机访问。元素在内存中是连续存储的,因此可以通过下标运算符“[]”直接访问元素。vector在添加元素时,如果当前容量不足,会自动分配更大的内存空间并将原有元素复制到新的内存空间中。
  2. list:双向链表,允许快速插入和删除元素,但随机访问较慢。list由若干个节点构成,每个节点都包括一个信息块、一个前驱指针和一个后驱指针,可以向前也可以向后进行访问。
  3. deque(双端队列):支持高效的插入和删除操作,可以在两端进行快速的插入和删除。deque的内存空间是由一段一段等长的连续空间构成,各段空间之间并不一定是连续的,但使用中控器(如指针数组)来管理这些空间,使得deque在使用上感觉像是连续的。

2.关联容器

关联容器支持通过键来高效地查找和读取元素。常用的关联容器有:

  1. set:根据值进行排序的集合,不包含重复元素。set底层通常是由红黑树来实现的,因此具有高效的查找、插入和删除操作。
  2. map:根据键进行排序的关联容器,每个键映射到一个值。map同样底层通常是由红黑树来实现的,支持高效的查找、插入和删除操作。map中的键是唯一的,而值可以是重复的。
  3. multiset:允许包含重复元素的set。
  4. multimap:允许包含重复键的map,即一个键可以映射到多个值。
  5. unordered_set:基于哈希表实现的集合,不支持元素排序,但查找、插入和删除操作的时间复杂度通常为O(1)。unordered_set中的元素不能修改,只能插入和删除。
  6. unordered_map:基于哈希表实现的关联容器,支持高效的查找、插入和删除操作。unordered_map中的键是唯一的,而值可以是重复的。与map不同的是,unordered_map中的元素不是按照键的顺序存储的。

3.容器适配器

容器适配器是对顺序容器或关联容器进行封装,提供了一组不同的接口。常用的容器适配器有:

  1. stack:后进先出(LIFO)数据结构,通常使用deque或vector作为底层容器。stack只提供了基本的压栈(push)、出栈(pop)和查看栈顶元素(top)等操作。
  2. queue:先进先出(FIFO)数据结构,通常使用deque或list作为底层容器。queue提供了基本的入队(push)、出队(pop)和查看队头元素(front)等操作。
  3. priority_queue:优先队列,通常使用vector作为底层容器,并使用堆排序算法来维护元素的优先级。priority_queue提供了插入元素(push)和获取最高优先级元素(top)等操作。注意,priority_queue不支持随机访问和删除操作。

一、迭代器

在C++中,迭代器(Iterator)是一种可以访问和遍历容器中元素的对象,它类似于指针,但具有更多的功能和灵活性。以下是关于C++迭代器的详细解释:

1.迭代器的定义

迭代器是一种抽象的数据类型,它可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。迭代器相当于容器和操作容器的算法之间的桥梁,使得算法可以适用于不同类型的容器。

2.迭代器的功能

  1. 访问元素:迭代器可以访问容器中的元素,通过解引用迭代器(使用*运算符)可以获取迭代器指向的元素的值或引用。
  2. 遍历容器:迭代器可以遍历容器中的所有元素,通过递增(使用++运算符)或递减(对于支持双向迭代的迭代器,使用--运算符)迭代器的位置,可以依次访问容器中的每个元素。
  3. 比较迭代器:可以使用==!=运算符来比较两个迭代器是否相等或不等,即是否指向同一个元素。

3.迭代器的分类

C++中的迭代器根据功能的不同,可以分为以下几类:

  1. 输入迭代器(Input Iterator):只能以只读方式访问对象,例如istream迭代器。
  2. 输出迭代器(Output Iterator):只能对元素进行写操作,例如ostream迭代器。
  3. 前向迭代器(Forward Iterator):可以在一个正确的区间中进行读写操作,它拥有输入迭代器的所有特性,以及单步向前迭代元素的能力。
  4. 双向迭代器(Bidirectional Iterator):在前向迭代器的基础上提供了单步向后迭代元素的能力,例如listsetmultisetmapmultimap等容器的迭代器。
  5. 随机访问迭代器(Random Access Iterator):可以像指针那样进行算术计算,例如vectordequestringarray等容器的迭代器。

4.迭代器的使用

要使用一个容器的迭代器,首先需要定义一个迭代器变量,其一般形式为:

容器类型::iterator 迭代器名;

例如,要定义一个vector容器的迭代器,可以写为:

std::vector<int>::iterator it;

也可以使用auto关键字来自动推断迭代器的类型,如:

auto it = v.begin(); // v 是一个 vector 容器

要获取一个容器的迭代器,可以使用容器的成员函数begin()end(),它们分别返回指向容器第一个元素和最后一个元素之后位置的迭代器。例如:

vector<int> v = {1, 2, 3, 4, 5};
auto it1 = v.begin(); // it1 指向 v 的第一个元素
auto it2 = v.end();   // it2 指向 v 的最后一个元素之后的位置

也可以使用全局函数std::begin()std::end()来获取容器的迭代器,它们的功能和成员函数相同。

5.迭代器的注意事项

  1. 在使用迭代器遍历容器时,要注意迭代器的有效性。如果在遍历过程中修改了容器(如插入或删除元素),可能会导致迭代器失效。
  2. 不同类型的容器提供的迭代器类型可能不同,例如vector提供的是随机访问迭代器,而list提供的是双向迭代器。因此,在使用迭代器时,要根据容器的类型选择合适的迭代器操作。
  3. 迭代器解引用后得到的是元素的值或引用,如果需要修改元素的值,应该使用引用类型(通过*运算符解引用后加&)来获取元素的引用。

综上所述,迭代器是C++中一种非常重要的抽象数据类型,它使得算法可以适用于不同类型的容器,并提供了高效、灵活的方式来访问和遍历容器中的元素。

二、unordered_set容器 (唯一,不能修改)

std::unordered_set 是 C++ 标准库中的一个容器,用于存储唯一元素。与 std::set 不同,std::unordered_set 基于哈希表实现,因此其插入、删除和查找操作在平均情况下具有常数时间复杂度 O(1)。

unordered_set中的元素不能修改,只能插入和删除

以下是一些常用的 std::unordered_set<int> 的使用方法:

1. 包含头文件

首先,你需要包含 <unordered_set> 头文件:

#include <unordered_set>

2. 创建和初始化

你可以通过几种方式创建和初始化 std::unordered_set<int>

std::unordered_set<int> mySet; // 创建一个空的unordered_set
 
std::unordered_set<int> mySetWithElements = {1, 2, 3, 4, 5}; // 使用初始化列表
 
std::unordered_set<int> anotherSet(mySetWithElements); // 使用另一个unordered_set进行初始化

3. 插入元素

你可以使用 insert 方法向 std::unordered_set 中插入元素:

mySet.insert(6);
mySet.insert(7);

insert 方法返回一个 std::pair,其中第一个元素是指向插入元素或已存在元素的迭代器,第二个元素是一个布尔值,表示插入是否成功(即元素是否已存在)。

4. 删除元素

你可以使用 erase 方法删除元素:

mySet.erase(6); // 删除值为6的元素

erase 方法接受一个迭代器或一个值作为参数。如果传入的是一个值,它会删除所有匹配的元素。

5. 查找元素

你可以使用 find 方法查找元素:

auto it = mySet.find(7);
if (it != mySet.end()) {
    std::cout << "Element 7 found!" << std::endl;
} else {
    std::cout << "Element 7 not found!" << std::endl;
}

find 方法返回一个迭代器,如果元素存在则返回指向该元素的迭代器,否则返回 end() 迭代器。

6. 遍历元素

你可以使用范围 for 循环或迭代器来遍历 std::unordered_set 中的元素:

// 使用范围for循环
for (int elem : mySet) {
    std::cout << elem << " ";
}
std::cout << std::endl;
 
// 使用迭代器
for (auto it = mySet.begin(); it != mySet.end(); ++it) {
    std::cout << *it << " ";
}
std::cout << std::endl;

7. 获取元素数量

你可以使用 size 方法获取 std::unordered_set 中的元素数量:

std::cout << "Number of elements: " << mySet.size() << std::endl;

8. 清空集合

你可以使用 clear 方法清空 std::unordered_set

ySet.clear();

示例代码

以下是一个完整的示例代码,展示了上述方法的使用:

#include <iostream>
#include <unordered_set>
 
int main() {
    std::unordered_set<int> mySet = {1, 2, 3, 4, 5};
 
    // 插入元素
    mySet.insert(6);
    auto result = mySet.insert(3); // 尝试插入已存在的元素
    if (!result.second) {
        std::cout << "Element 3 already exists!" << std::endl;
    }
 
    // 删除元素
    mySet.erase(2);
 std
    // 查找元素
    auto it = mySet.find(4);
    if (it != mySet.end()) {
        std::cout << "Element 4 found!" << std::endl;
    }
 
    // 遍历元素
    std::cout << "Elements in the set: ";
    for (int elem : mySet) {
        std::cout << elem << " ";
    }
    std::cout << std::endl;
 
    // 获取元素数量
    std::cout << "Number of elements: " << mySet.size() << std::endl;
 
    // 清空集合
    mySet.clear();
    std::cout << "Number of elements after clear: " << mySet.size() << std::endl;
 
    return 0;
}

通过这些方法,你可以有效地使用 std::unordered_set<int> 来存储和管理唯一整数集合。

三、set容器(唯一、排序)

在C++中,set容器是一种特殊的关联容器,它存储的元素是唯一的,且默认情况下会按照升序排列。以下是set容器的详细使用方法:

头文件:#include<set>

1.set容器的定义与初始化

  1. 定义:使用std::set定义set容器,例如std::set<int> mySet;表示定义一个存储整型元素的set容器。
  2. 初始化
    • 默认初始化:std::set<int> mySet;,此时容器为空。
    • 使用数组初始化:std::set<int> mySet(a, a+n);,其中a是数组名,n是数组元素个数。
    • 使用vector初始化:std::set<int> mySet(vec.begin(), vec.end());,其中vec是一个vector容器。
    • 使用另一个set初始化:std::set<int> mySet1(mySet2);,其中mySet2是另一个已存在的set容器。
    • 使用初始化列表:std::set<int> mySet = {1, 2, 3, 4, 5};

2.set容器的常用操作

  1. 插入元素
    • 使用insert函数:mySet.insert(value);,其中value是要插入的元素。如果插入成功,返回一个pair,其中first指向插入元素的位置,second为true;如果插入失败(元素已存在),second为false。
    • 批量插入:mySet.insert(vec.begin(), vec.end());,从vector中批量插入元素。
    • 使用emplace函数(C++11及以后):mySet.emplace(args...);,直接在容器内构造元素,比insert更高效。
    • 使用hint参数提高插入性能(仅适用于有序容器):mySet.insert(hint, value);,其中hint是一个指向set容器中某个位置的迭代器,用于提示插入位置,可以提高插入效率。
  1. 删除元素
    • 删除指定值的元素:mySet.erase(value);,其中value是要删除的元素值。如果删除成功,返回1;如果失败(元素不存在),返回0。
    • 删除指定迭代器位置的元素:mySet.erase(iterator position);,其中position是一个指向要删除元素的迭代器。
    • 删除指定迭代器范围的元素:mySet.erase(iterator first, iterator last);,其中firstlast是两个迭代器,划定了要删除元素的范围。
    • 清空容器:mySet.clear();,删除容器中的所有元素。
  1. 查找元素
    • 使用find函数:mySet.find(value);,其中value是要查找的元素值。如果找到元素,返回一个指向该元素的迭代器;如果没找到,返回mySet.end()
    • 使用lower_bound函数:mySet.lower_bound(value);,返回一个指向第一个不小于value的元素的迭代器。
    • 使用upper_bound函数:mySet.upper_bound(value);,返回一个指向第一个大于value的元素的迭代器。
    • 使用equal_range函数:mySet.equal_range(value);,返回一个pair,分别表示第一个不小于value的元素和第一个大于value的元素的迭代器。
  1. 遍历容器
    • 使用迭代器:for (std::set<int>::iterator it = mySet.begin(); it != mySet.end(); ++it) { std::cout << *it << " "; }
    • 使用范围for循环(C++11及以后):for (const auto& element : mySet) { std::cout << element << " "; }
    • 使用标准库提供的算法库进行遍历:std::for_each(mySet.begin(), mySet.end(), [](int element) { std::cout << element << " "; });
  1. 其他操作
    • 判断容器是否为空:mySet.empty();,如果容器为空,返回true;否则返回false。
    • 获取容器大小:mySet.size();,返回容器中元素的个数。
    • 交换两个容器的内容:std::swap(mySet1, mySet2);,其中mySet1mySet2是两个set容器。

3.注意事项

  1. set容器中的元素是唯一的,不允许重复。如果需要允许重复元素,可以使用multiset容器。
  2. set容器底层使用红黑树实现,插入、删除和查找操作的时间复杂度均为O(logn)。
  3. 在使用迭代器遍历set容器时,要注意迭代器的有效性。如果在遍历过程中修改了容器(如插入或删除元素),可能会导致迭代器失效。

四、multiset容器(重复、排序)

std::multiset 是 C++ 标准模板库(STL)中的一个关联容器,它存储可以重复的元素,并且这些元素会根据指定的比较准则自动排序。std::multiset 内部通常使用红黑树实现,因此插入、删除和查找操作的时间复杂度都是对数级别的。

头文件:#include<set>

定义

std::multiset 的定义通常包括指定存储元素的类型和可选的比较准则。如果不指定比较准则,则使用默认的 < 操作符。

#include <set>
#include <iostream>
 
int main() {
    // 定义一个存储 int 类型元素的 multiset,使用默认的 < 操作符进行排序
    std::multiset<int> myMultiset;
 
    // 定义一个存储 std::string 类型元素的 multiset,使用自定义的比较准则
    auto compare = [](const std::string& a, const std::string& b) {
        return a.length() < b.length(); // 按字符串长度排序
    };
    std::multiset<std::string, decltype(compare)> myStringMultiset(compare);
 
    return 0;
}

基本使用方法

1.插入元素

使用 insert 方法可以插入元素。如果元素已经存在,则 std::multiset 会存储多个相同的元素。

#include <set>
#include <iostream>
 
int main() {
    std::multiset<int> myMultiset;
 
    myMultiset.insert(5);
    myMultiset.insert(2);
    myMultiset.insert(8);
    myMultiset.insert(2); // 插入重复元素
 
    for (int num : myMultiset) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
 
    return 0;
}

输出:

2 2 5 8
2.删除元素

使用 erase 方法可以删除元素。可以通过值或迭代器来删除元素。

#include <set>
#include <iostream>
 
int main() {
    std::multiset<int> myMultiset = {1, 2, 2, 3, 4};
 
    // 通过值删除(只会删除一个匹配的元素)
    myMultiset.erase(2);
 
    // 通过迭代器删除
    auto it = myMultiset.find(3);
    if (it != myMultiset.end()) {
        myMultiset.erase(it);
    }
 
    for (int num : myMultiset) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
 
    return 0;
}

输出:

1 2 4
3.查找元素

使用 find 方法可以查找元素,返回指向找到元素的迭代器,如果未找到则返回 end() 迭代器。

#include <set>
#include <iostream>
 
int main() {
    std::multiset<int> myMultiset = {1, 2, 2, 3, 4};
 
    auto it = myMultiset.find(2);
    if (it != myMultiset.end()) {
        std::cout << "Found: " << *it << std::endl;
    } else {
        std::cout << "Not found" << std::endl;
    }
 
    return 0;
}

输出:

复制代码



Found: 2
4.遍历元素

可以使用范围 for 循环或迭代器来遍历 std::multiset 中的元素。

#include <set>
#include <iostream>
 
int main() {
    std::multiset<int> myMultiset = {1, 2, 2, 3, 4};
 
    // 使用范围 for 循环遍历
    for (int num : myMultiset) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
 
    // 使用迭代器遍历
    for (auto it = myMultiset.begin(); it != myMultiset.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
 
    return 0;
}

输出:

1 2 2 3 4 
1 2 2 3 4

注意事项

由于 std::multiset 是基于红黑树实现的,而不是像 std::vectorstd::deque 那样基于数组或动态数组实现的,因此你不能使用 arr[i] 的形式来访问 std::multiset<int> 中的元素。std::multiset 的元素是通过迭代器来访问的。

五、vector容器(动态数组)

vector 是 C++ 标准模板库(STL)中的一个序列容器,它提供了动态数组的功能。vector 可以存储相同类型的元素,并允许在运行时动态地增加或减少其大小。以下是 vector 容器的定义以及基本使用方法。

定义

vector 是一个模板类,定义在头文件 <vector> 中。其基本定义形式如下:

#include <vector>
 
template <class T, class Allocator = std::allocator<T>>
class vector;
  • T 是存储在 vector 中的元素的类型。
  • Allocator 是分配器类型,用于控制内存分配,默认使用 std::allocator<T>

基本使用方法

1. 引入头文件

首先,要使用 vector,需要包含头文件 <vector>

#include <vector>
2. 创建 vector 对象

你可以通过几种方式创建 vector 对象:

std::vector<int> vec1;           // 创建一个空的 vector
std::vector<int> vec2(5);        // 创建一个包含 5 个元素的 vector,每个元素初始化为 0
std::vector<int> vec3(5, 10);    // 创建一个包含 5 个元素的 vector,每个元素初始化为 10
std::vector<int> vec4(vec2);     // 创建一个 vec2 的副本


std::vector<int> arr;
arr.resize(10);  // 将vector的大小设置为10,所有元素初始化为int的默认值0
for (int i = 0; i < 10; i++) {
    arr[i] = i;
}
3. 添加元素

1.可以使用 push_back 方法在 vector 的末尾添加元素:

vec1.push_back(1);
vec1.push_back(2);
vec1.push_back(3);

2.使用insert()插入单个元素:

std::vector<int> vec = {1, 2, 3};
vec.insert(vec.begin() + 1, 4); // 在第二个位置插入4,vec变为{1, 4, 2, 3}

3.使用insert()插入多个元素:

std::vector<int> vec = {1, 2, 3};
std::vector<int> new_elements = {4, 5, 6};
vec.insert(vec.begin() + 1, new_elements.begin(), new_elements.end());
// 在第二个位置插入{4, 5, 6},vec变为{1, 4, 5, 6, 2, 3}

4.使用初始化列表插入多个元素:

std::vector<int> vec = {1, 2, 3};
vec.insert(vec.begin() + 1, {4, 5, 6}); // 在第二个位置插入{4, 5, 6},vec变为{1, 4, 5, 6, 2, 3}

insert 函数会返回指向插入的第一个元素的迭代器,这在你需要立即访问新插入的元素时非常有用。

4. 遍历 vector

可以使用范围 for 循环、普通 for 循环或迭代器来遍历 vector

// 使用范围 for 循环
for (int val : vec1) {
    std::cout << val << " ";
}
std::cout << std::endl;
 
// 使用普通 for 循环
for (size_t i = 0; i < vec1.size(); ++i) {
    std::cout << vec1[i] << " ";
}
std::cout << std::endl;
 
// 使用迭代器
for (std::vector<int>::iterator it = vec1.begin(); it != vec1.end(); ++it) {
    std::cout << *it << " ";
}
std::cout << std::endl;
5. 删除元素

可以使用 pop_back 方法删除 vector 的最后一个元素,或使用 erase 方法删除指定位置的元素或元素范围:

vec1.pop_back();                 // 删除最后一个元素
vec1.erase(vec1.begin());        // 删除第一个元素
vec1.erase(vec1.begin(), vec1.begin() + 2); // 删除前两个元素
6. 获取 vector 的大小

可以使用 size 方法获取 vector 中元素的数量:

size_t size = vec1.size();
7. 判断 vector 是否为空

可以使用 empty 方法判断 vector 是否为空:

bool isEmpty = vec1.empty();
8.访问元素

1.访问元素可以使用下标操作符 []at 方法

int firstElement = vec1[0];      // 使用下标操作符访问第一个元素
int secondElement = vec1.at(1);  // 使用 at 方法访问第二个元素(带边界检查)

2.back():

用于访问容器中的最后一个元素

注意事项:

  1. 非空容器back() 函数只能在非空容器上调用,否则会引发未定义行为(通常是崩溃)。
  2. 只读访问back() 返回的是一个对容器中最后一个元素的引用(T&,其中 T 是容器中元素的类型)。这意味着你可以通过返回的引用来读取或修改最后一个元素的值。
#include <iostream>
#include <vector>

int main() {
    std::vector<int> arr = {1, 2, 3, 4, 5};

    if (!arr.empty()) {
        int lastElement = arr.back();
        std::cout << "The last element is: " << lastElement << std::endl;
        
        // 修改最后一个元素
        arr.back() = 10;
        std::cout << "After modification, the last element is: " << arr.back() << std::endl;
    } else {
        std::cout << "The vector is empty!" << std::endl;
    }

    return 0;
}

3.front()

    • 功能:返回容器中第一个元素的引用。
    • 示例:int firstElement = arr.front();

4.at(size_type n)

    • 功能:返回容器中位置为n的元素的引用,位置从0开始计数。如果n大于等于容器的大小,则抛出std::out_of_range异常。
    • 示例:int elementAtPosition = arr.at(2); // 获取第三个元素

5.begin()

    • 功能:返回一个指向容器中第一个元素的迭代器。
    • 示例:auto it = arr.begin(); // it现在指向arr的第一个元素

6.end()

    • 功能:返回一个指向容器中最后一个元素之后位置的迭代器(这是一个“尾后迭代器”,不指向任何有效的元素)。
    • 示例:auto it = arr.end(); // it现在指向arr的尾后位置

7.rbegin()rend()

    • 功能:分别返回指向容器中最后一个元素(反向迭代起始位置)和第一个元素之前位置(反向迭代结束位置)的反向迭代器。
    • 示例:auto rit = arr.rbegin(); // rit现在反向指向arr的最后一个元素

8.data()

    • 功能:返回一个指向容器中第一个元素的指针(与&arr[0]等价,但更安全,因为当容器为空时不会越界)。
    • 示例:int* ptr = arr.data(); // ptr现在指向arr的第一个元素

9.size()

    • 功能:返回容器中元素的数量。
    • 示例:size_t numElements = arr.size();

示例代码

以下是一个完整的示例代码,展示了如何使用 vector

#include <iostream>
#include <vector>
 
int main() {
    std::vector<int> vec;
 
    // 添加元素
    vec.push_back(10);
    vec.push_back(20);
    vec.push_back(30);
 
    // 访问和打印元素
    for (int val : vec) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
 
    // 删除最后一个元素
    vec.pop_back();
 
    // 遍历并打印剩余元素
    for (size_t i = 0; i < vec.size(); ++i) {
        std::cout << vec[i] << " ";
    }
    std::cout << std::endl;
 
    // 检查是否为空
    if (vec.empty()) {
        std::cout << "The vector is empty." << std::endl;
    } else {
        std::cout << "The vector is not empty." << std::endl;
    }
 
    // 获取并打印 vector 的大小
    std::cout << "Vector size: " << vec.size() << std::endl;
 
    return 0;
}

六、unordered_map容器 (不排序,键唯一)

unordered_map 是 C++ 标准库中的一个关联式容器,它提供了一种通过键值对(key-value)来存储和访问元素的方式。

定义

unordered_map 是 C++11 引入的一个容器,它使用哈希表来实现。哈希表是一种数据结构,能够根据键(key)的哈希值快速定位到对应的值(value)。因此,unordered_map 提供了常数时间复杂度的元素插入、访问和删除操作(在平均情况下)。

作用

unordered_map 的主要作用是提供一种高效的存储和查找键值对的方式。与 map 容器不同,unordered_map 不会对元素进行排序,因此它在查找操作上更加高效。unordered_map 适用于需要快速访问和修改数据的场景,例如计数器、缓存等。

使用方法

  1. 包含头文件
    使用 unordered_map 之前,需要包含 <unordered_map> 头文件。
  2. 声明对象
    声明一个 unordered_map 对象时,需要指定键和值的类型。例如:
std::unordered_map<int, std::string> myUnorderedMap;

这里,键的类型是 int,值的类型是 std::string

  1. 插入元素
    可以使用 insert() 函数或下标操作符 [] 来插入键值对。例如:
myUnorderedMap.insert(std::make_pair(1, "one"));
myUnorderedMap[2] = "two";
  1. 访问元素
    使用下标操作符 [] 可以访问元素。如果键不存在,将会自动插入一个默认值(对于基本类型,默认值是未初始化的)。例如:
std::string value = myUnorderedMap[1]; // 访问键为1的元素的值
  1. 删除元素
    使用 erase() 函数可以删除元素。例如:
myUnorderedMap.erase(1); // 删除键为1的元素
  1. 查找元素
    使用 find() 函数可以查找特定键的元素。如果找到了,返回的迭代器指向该元素;否则,指向 unordered_map.end()。例如:
auto it = myUnorderedMap.find(2);
if (it != myUnorderedMap.end()) {
    // 找到了键为2的元素
}
  1. 遍历元素
    可以使用迭代器来遍历 unordered_map 中的元素。例如:
for (auto it = myUnorderedMap.begin(); it != myUnorderedMap.end(); ++it) {
    std::cout << "Key: " << it->first << ", Value: " << it->second << std::endl;
}

注意事项

  • unordered_map 中的元素是无序存储的,所以无法保证元素的顺序。
  • 键(key)应该是唯一的,且键的类型应该是可哈希的。
  • 在使用下标操作符 [] 访问元素时,如果键不存在,将会自动插入一个默认值,这可能会导致不期望的行为。因此,在访问之前最好先使用 find() 函数检查键是否存在。
  • unordered_map 提供了高效的查找操作,但在某些情况下(例如,哈希函数不够优秀或容器中的元素非常多),其性能可能会受到影响。

七、map容器(按键排序,键唯一)

在C++标准库中,map容器是一种关联容器(associative container),它存储的是键值对(key-value pairs)。每个键(key)在map中都是唯一的,并且与一个值(value)相关联。map内部通常使用红黑树(red-black tree)或类似的数据结构来实现,这保证了map中的元素始终按照键的排序顺序存储。

头文件:#include <map>

以下是map容器的各种使用方法:

1. 构造和赋值

  • 构造函数
    • 默认构造函数:创建一个空的map
    • 拷贝构造函数:通过另一个map对象来构造一个新的map对象。
    • 使用比较函数构造:可以自定义比较函数来改变map中元素的排序方式。
    • 区间构造:可以使用其他容器(如vector)或迭代器范围来构造map
  • 赋值操作
    • 拷贝赋值运算符:将一个map对象的内容赋值给另一个map对象。
    • 初始化列表赋值(C++11及以上):使用初始化列表来赋值。
    • 区间赋值:通过迭代器对插入元素来“赋值”或填充map
#include <iostream>
#include <map>
 
int main() {
    // 默认构造函数
    std::map<int, std::string> map1;
 
    // 拷贝构造函数
    std::map<int, std::string> map2(map1);
 
    // 使用初始化列表构造(C++11及以上)
    std::map<int, std::string> map3 = {{1, "one"}, {2, "two"}};
 
    // 赋值操作
    map1 = map3;
 
    // 使用初始化列表赋值(C++11及以上)
    map2 = {{3, "three"}, {4, "four"}};
 
    return 0;
}

2. 大小和交换

  • size():返回map中元素的数量,即键值对的总数。
  • empty():判断map是否为空,如果容器中没有元素,则返回true,否则返回false
  • swap():交换两个map容器的内容,交换操作高效执行,不会涉及元素的复制或移动。
#include <iostream>
#include <map>
 
int main() {
    std::map<int, std::string> map1 = {{1, "one"}, {2, "two"}};
    std::map<int, std::string> map2 = map1;
 
    // 大小
    std::cout << "Size of map1: " << map1.size() << std::endl;
 
    // 判断是否为空
    if (map1.empty()) {
        std::cout << "map1 is empty" << std::endl;
    }
 
    // 交换
    map1.swap(map2);
 
    // 输出交换后的内容
    for (const auto& pair : map1) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
 
    return 0;
}

3. 插入元素

  • insert():使用insert函数插入元素,可以插入单个元素,也可以插入一个元素范围。如果插入的元素键已经存在,则不会插入新元素。
  • 使用[]运算符:可以直接使用[]运算符来插入或访问元素。如果键不存在,则会创建一个新的键值对,并将其插入到map中。如果键已经存在,则会更新该键对应的值。
  • 使用emplace()
#include <iostream>
#include <map>
 
int main() {
    std::map<int, std::string> map;
 
    // 使用insert函数插入单个元素
    map.insert(std::make_pair(1, "one"));
    //std::make_pair(1, "one")创建了一个std::pair<int, std::string>对象,其中int类型的值为1,std::string类型的值为"one"。
    //insert函数会检查map中是否已经存在一个键为1的元素。如果不存在,它会将新的键值对插入到map中,并返回一个指向新插入元素的迭代器。如果键已经存在,insert函数将不会插入新元素,而是返回一个指向已存在元素的迭代器。
    //在这个特定的例子中,假设map是空的或者之前不包含键为1的元素,那么这行代码将成功地将键值对(1, "one")插入到map中。
    //---------------------------------------------------------------
    // 使用insert函数插入一个元素范围(这里不演示,因为通常map不这样用)
    //---------------------------------------------------------------
    // 使用[]运算符插入元素
    map[2] = "two";
    //---------------------------------------------------------------
    // 使用emplace函数插入(C++11及以上,更高效)
    map.emplace(3, "three");
    //---------------------------------------------------------------
    // 输出map内容
    for (const auto& pair : map) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
 
    return 0;
}

4. 删除元素

  • erase():使用erase函数删除元素,可以传入要删除元素的键作为参数,也可以使用迭代器来删除元素。
  • clear():删除map中的所有元素。
#include <iostream>
#include <map>
 
int main() {
    std::map<int, std::string> map = {{1, "one"}, {2, "two"}, {3, "three"}};
 
    // 使用erase函数删除单个元素
    map.erase(2);
 
    // 使用迭代器删除元素
    auto it = map.find(3);
    if (it != map.end()) {
        map.erase(it);
    }
 
    // 清空map
    map.clear();
 
    // 输出map是否为空
    if (map.empty()) {
        std::cout << "Map is empty" << std::endl;
    }
 
    return 0;
}

5. 查找和访问元素

  • find():使用find函数查找元素,如果找到了指定的键,则返回指向该元素的迭代器;否则返回指向map末尾的迭代器。
  • count():返回指定键出现的次数。由于map中的键是唯一的,所以返回值只能是0或1。
  • 使用[]运算符:可以直接使用[]运算符来访问元素,如果键不存在,则会创建一个新的键值对(key,0)(注意:这可能会导致未定义行为,如果只是想检查键是否存在,应该使用find函数)。
#include <iostream>
#include <map>
 
int main() {
    std::map<int, std::string> map = {{1, "one"}, {2, "two"}, {3, "three"}};
 
    // 使用find函数查找元素
    auto it = map.find(2);
    if (it != map.end()) {
        std::cout << "Found " << it->first << ": " << it->second << std::endl;
    } else {
        std::cout << "Not found" << std::endl;
    }
 
    // 使用count函数判断键是否存在
    if (map.count(3) > 0) {
        std::cout << "Key 3 exists" << std::endl;
    }
 
    // 使用[]运算符访问元素(注意:如果键不存在,会插入新元素)
    // 这里只是为了演示,通常不建议这样检查键是否存在
    if (map.find(4) == map.end()) {
        std::cout << "Key 4 does not exist, will not insert" << std::endl;
    } else {
        // 这行代码实际上会插入一个新元素,因为4不存在
        // std::cout << map[4] << std::endl; 
    }
 
    return 0;
}

6.倒序排序(仿函数、反向迭代器)

a.使用仿函数

#include<iostream>
#include<map>

class Mycompare
{
public:
	bool operator()(int a, int b)const
	{
		return a > b;
	}
};

int main()
{
	std::map<int,int,Mycompare>arr;  //创建map容器时,参数修改
	arr[1] = 1;
	arr[2] = 2;
	arr[3] = 3;
	for (auto it = arr.begin(); it != arr.end(); it++)
	{
		std::cout <<"key = "<<it->first << " " <<"value = "<< it->second << std::endl;
	}

	return 0;
}
key = 3 value = 3
key = 2 value = 2
key = 1 value = 1

b.使用rbegin()rend():返回反向迭代器,用于从后向前遍历map

#include <iostream>
#include <map>
#include <string>

int main() {
    // 创建一个map并插入一些数据
    std::map<int, std::string> myMap = {
        {1, "one"},
        {2, "two"},
        {3, "three"},
        {4, "four"}
    };

    // 使用反向迭代器倒序输出
    std::cout << "Map in reverse order:\n";
    for (auto it = myMap.rbegin(); it != myMap.rend(); ++it) {
        std::cout << "Key: " << it->first << ", Value: " << it->second << '\n';
    }

    return 0;
}

7.key为自定义类型

#include<iostream>
#include<map>

//定义一个person类
class person
{
public:
	person(int i,std::string n)
	{
		this->id = i;
		this->name = n;
	}
	int id;
	std::string name;
};

//使用仿函数,规定map容器按键的哪种方式进行排序
class mycompare
{
public:
	bool operator()(person a, person b)const
	{
		return a.name < b.name;   //规定按键的名字进行排序,并且为升序
	}
};

int main()
{
    //定义3个person对象
	person a(1, "a");
	person b(2, "b");
	person c(3, "c");
    //定义一个map容器
	std::map<person, int,mycompare>arr;  
    //存放3个键值队,其中key存放的是自定义类型的person
	arr[a] = 100;
	arr[b] = 101;
	arr[c] = 102;
    //输出键值队
	for (auto it = arr.begin(); it != arr.end(); it++)
	{
		std::cout<<"person.id: " << it->first.id << " person.name: " << it->first.name 
            << " arr.value: " << it->second << std::endl;
	}
	return 0;
}
person.id: 1 person.name: a arr.value: 100
person.id: 2 person.name: b arr.value: 101
person.id: 3 person.name: c arr.value: 102

8. 迭代器

  • begin():返回指向map第一个元素的迭代器。
  • end():返回指向map末尾元素的下一个位置的迭代器。
  • cbegin():返回指向map第一个元素的常量迭代器。
  • cend():返回指向map末尾元素的下一个位置的常量迭代器。
  • crbegin()crend():返回反向常量迭代器。

9. 其他操作

  • key_comp():返回用于比较键的函数对象。
  • value_comp():返回用于比较值的函数对象(基于键的比较)。
  • get_allocator():返回map的配置器(内存分配器)。
  • max_size():返回map可以容纳的最大元素个数。

示例代码

以下是一个简单的示例代码,展示了如何使用map容器:

#include <iostream>
#include <map>
 
int main() {
    // 创建一个map对象,并插入一些元素
    std::map<int, std::string> myMap;
    myMap.insert(std::make_pair(1, "one"));
    myMap[2] = "two"; // 使用[]运算符插入元素
    myMap.insert(std::map<int, std::string>::value_type(3, "three"));
 
    // 遍历并打印map中的元素
    for (auto it = myMap.begin(); it != myMap.end(); ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }
 
    // 查找元素
    auto it = myMap.find(2);
    if (it != myMap.end()) {
        std::cout << "Found " << it->first << ": " << it->second << std::endl;
    } else {
        std::cout << "Not found" << std::endl;
    }
 
    // 删除元素
    myMap.erase(1);
 
    // 打印删除后的map
    std::cout << "After deletion:" << std::endl;
    for (auto it = myMap.begin(); it != myMap.end(); ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }
 
    return 0;
}

八、string容器

在C++中,string容器是一个专门用于存储和操作字符串的类,它封装了与字符串相关的各种操作,为开发者提供了丰富的接口来处理字符串数据。以下是string容器的作用和使用方法的详细介绍:

string容器的作用

  1. 封装字符串操作:string容器提供了对字符串进行赋值、连接、比较、查找、替换、截取、插入和删除等操作的方法,避免了手动对字符串进行内存分配、释放以及各种字符串操作的复杂实现。
  2. 提高安全性:由于string容器已经实现了很多常用的操作,并且内置了边界检查和异常处理机制,因此可以更加安全地处理字符串,减少程序出错的可能性。
  3. 提高效率:string容器内部使用了高效的内存管理机制,可以自动调整字符串的内存空间,并且在处理大量字符串操作时,可以显著提高程序的运行效率。

1.包含头文件

在使用string容器之前,需要包含头文件<string>

#include <string>

2.创建string对象

std::string str1; // 默认构造函数,创建一个空字符串
std::string str2("hello"); // 带参数的构造函数,用字符串字面量初始化
std::string str3(str2); // 拷贝构造函数,用另一个string对象初始化

3.字符串赋值

可以使用赋值运算符=assign()方法来为string对象赋值。

std::string str4;
str4 = "world"; // 使用赋值运算符
str4.assign("C++"); // 使用assign()方法

4.字符串连接

可以使用加号+运算符、append()方法或push_back()方法将两个或多个字符串连接起来。


std::string str5 = "Hello, ";
std::string str6 = "world!";
str5 += str6; // 使用加号运算符
str5.append(" C++"); // 使用append()方法
str5.push_back('!'); // 使用push_back()方法添加单个字符,但通常不用于连接字符串

5.字符串比较

可以使用比较运算符(如==!=<><=>=)或compare()方法来比较两个字符串的大小。

//使用比较运算符
if (str5 == str6) {
    // 字符串相等
} else if (str5 < str6) {
    // str5小于str6
} else {
    // str5大于str6
}

// 或者使用compare()方法
int result = str5.compare(str6);
if (result == 0) {
    // 字符串相等
} else if (result < 0) {
    // str5小于str6
} else {
    // str5大于str6
}

6.字符串查找

可以使用find()方法查找子串的位置

std::string str7 = "Hello, world!";
size_t pos = str7.find("world");
if (pos != std::string::npos)
{
    str7.replace(pos, 5, "C++"); // 将"world"替换为"C++"
}

std::string::npos的意思

在C++的std::string类中,std::string::npos是一个特殊的静态成员常量,用于表示一个不可能的位置或未找到的位置。它是一个无符号整数类型(通常是std::size_t),并且被初始化为该类型所能表示的最大值。当你使用std::string的find、rfind、find_first_of、find_last_of、find_first_not_of或find_last_not_of等成员函数来搜索子字符串或字符时,如果搜索失败(即没有找到指定的子字符串或字符),这些函数将返回std::string::npos。

在C++标准库中,std::string类的find成员函数用于在字符串中搜索子字符串或字符。find有多个重载版本,允许你以不同的方式指定要搜索的内容以及搜索的起始位置。以下是std::string::find各重载的定义和使用方法的详细解释:

1. 查找单个字符
size_t find(char ch, size_t pos = 0) const noexcept;
  • ch:要查找的字符。
  • pos:开始搜索的位置索引,默认为0(即从字符串的开头开始搜索)。

返回值

  • 如果找到字符ch,则返回其第一次出现的索引。
  • 如果未找到,则返回std::string::npos

示例

std::string str = "hello world";
size_t pos = str.find('w');
if (pos != std::string::npos) {
    std::cout << "Character 'w' found at position: " << pos << std::endl;
} else {
    std::cout << "Character 'w' not found." << std::endl;
}
2. 查找C风格字符串(const char*

函数签名

size_t find(const char* s, size_t pos = 0) const noexcept;
  • s:要查找的C风格字符串。
  • pos:开始搜索的位置索引,默认为0。

返回值

  • 如果找到子字符串s,则返回其第一次出现的起始索引。
  • 如果未找到,则返回std::string::npos

示例

std::string str = "hello world";
size_t pos = str.find("world");
if (pos != std::string::npos) {
    std::cout << "Substring 'world' found at position: " << pos << std::endl;
} else {
    std::cout << "Substring 'world' not found." << std::endl;
}
#include<iostream>

int main()
{
    std::string s, t;
    std::cin >> s >> t;
    size_t count = 0;
    size_t pos = 0;
    while ((pos = s.find(t, pos)) != std::string::npos) {
        count++;  //计数
        pos ++;  //更新pos的位置
    }
    std::cout << count << std::endl;
    return 0;
}
//例:
//s="aaabcd"
//t="aa"
//count=2
3. 查找std::string对象

函数签名

size_t find(const std::string& str, size_t pos = 0) const noexcept;
  • str:要查找的std::string对象。
  • pos:开始搜索的位置索引,默认为0。

返回值

  • 如果找到子字符串str,则返回其第一次出现的起始索引。
  • 如果未找到,则返回std::string::npos

示例

std::string str = "hello world";
std::string searchStr = "lo ";
size_t pos = str.find(searchStr);
if (pos != std::string::npos) {
    std::cout << "Substring 'lo ' found at position: " << pos << std::endl;
} else {
    std::cout << "Substring 'lo ' not found." << std::endl;
}
4. 查找字符数组(不常用,但存在)

虽然不常见,但理论上std::string::find还可以接受一个字符数组(即char[])作为参数,其行为与接受const char*参数的重载类似。然而,由于字符数组在传递给函数时会退化为指针,因此这种用法通常与接受const char*的重载混淆。

注意:在实际编程中,直接使用const char*std::string作为参数更为常见和清晰。

总结

std::string::find函数是查找字符串中子字符串或字符的强大工具。它提供了灵活的重载版本,允许你指定要查找的内容以及搜索的起始位置。在使用时,请确保检查返回值是否为std::string::npos,以判断搜索是否成功。

7.字符串的替换

使用replace()方法替换子串。

std::string str7 = "Hello, world!";
size_t pos = str7.find("world");
if (pos != std::string::npos)
    //在C++的std::string类中,std::string::npos是一个特殊的静态成员常量,
    //用于表示一个不可能的位置或未找到的位置。它是一个无符号整数类型(通常是std::size_t),
    //并且被初始化为该类型所能表示的最大值。
    //当你使用std::string的find、rfind、find_first_of、find_last_of、find_first_not_of
    //或find_last_not_of等成员函数来搜索子字符串或字符时,
    //如果搜索失败(即没有找到指定的子字符串或字符),这些函数将返回std::string::npos。
{
    str7.replace(pos, 5, "C++"); // 将"world"替换为"C++"
}

(1)、replace函数的作用

replace函数能够修改原始字符串的内容,将字符串中指定位置或范围内的字符或子字符串替换为新的字符或子字符串。

(2)、replace函数的用法

replace函数有多个重载版本,常用的有以下几种:

a、替换指定位置的子字符串

string& replace (size_t pos, size_t len, const string& str);
  • pos:表示替换起始位置的索引。
  • len:表示要替换的字符数。
  • str:表示用于替换的字符串。

例如:

string str = "Hello, world!";
str.replace(7, 5, "C++"); // 将"world"替换为"C++"
cout << str; // 输出: Hello, C++!

b、替换匹配到的第一个子字符串

string& replace (const string& from, const string& to);
  • from:表示要被替换的子字符串。
  • to:表示用于替换的子字符串。

例如:

string str = "Hello, world! Hello, everyone!";
str.replace("world", "C++"); // 将第一个出现的"world"替换为"C++"
cout << str; // 输出: Hello, C++! Hello, everyone!

注意:此版本只会替换匹配到的第一个子字符串,如果需要替换所有匹配项,则需要使用其他方法(如循环或正则表达式等)。

8.字符串截取和插入

可以使用substr()方法截取子串,使用insert()方法插入子串。

substrstd::string 类中的一个成员函数,用于提取字符串的一个子串。以下是 substr 函数的使用方法及其参数解释:

函数原型

std::string substr(size_t pos = 0, size_t len = npos) const;

参数说明

  • pos:要提取的子串的起始位置(从0开始)。如果 pos 超出了字符串的长度,则 substr 会抛出一个 std::out_of_range 异常。
  • len:要提取的子串的长度。如果 len 超出了从 pos 开始到字符串末尾的长度,则 substr 会提取从 pos 开始到字符串末尾的所有字符。如果省略 len 参数,则默认值为 npos(表示提取从 pos 开始到字符串末尾的所有字符)。

返回值

返回一个新的 std::string 对象,该对象是从原字符串中提取的子串。

例如:

std::string str8 = "example@email.com";
size_t atPos = str8.find("@");
//size_t用于存储字符串中"@"字符的位置索引。 此处atP0s=7;

std::string userName = str8.substr(0, atPos); // 截取用户名部分
std::cout<<userName<<std::endl;

//截取位置0~atPos上的字符串给userName,自身字符串保持不变
str8.insert(atPos, "_new"); // 在"@"前插入"_new"
std::cout<<str8<<std::endl;

输出结果为:

example
example_new@email.com

9.获取字符串长度

可以使用size()length()方法来获取字符串的长度。

无法使用sizeof()来获得string字符串的长度

在C++中,sizeof()运算符用于确定对象或类型在内存中占用的字节数。然而,当你尝试使用sizeof()来获取std::string对象(即字符串)的长度时,它并不会返回字符串中字符的数量,而是返回std::string对象本身在内存中占用的总字节数。

这是因为std::string是一个类,它包含了一些额外的成员变量来存储字符串的长度、容量以及实际的字符数据。sizeof()运算符返回的是这个类对象所占用的总内存大小,包括这些成员变量和可能的其他内部数据结构。

要获取std::string中字符的数量(即字符串的长度),你应该使用std::string类的成员函数length()size()。这两个函数都返回字符串中字符的数量,不包括结尾的空字符(\0,尽管在std::string中实际上并不存储这个空字符)。

size_t len1 = str8.size();

size_t len2 = str8.length();

10.清空字符串

可以使用clear()方法来清空字符串。

str8.clear(); // 清空字符串

11.判断字符串是否为空

可以使用empty()方法来判断字符串是否为空。

if (str8.empty()) {
    // 字符串为空
}

九、list容器(双向链表)

list容器在C++标准模板库(STL)中是一个非常重要的序列式容器,

适用于需要频繁插入和删除元素的场景

其定义、作用和使用方法如下:

一、定义

list容器是一个双向链表,链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。list容器的每个节点包含三个部分:前驱元素指针域、数据域和后继元素指针域,这使得list容器在插入和删除元素时能够保持较高的效率。

二、作用

  1. 动态存储分配:list容器采用动态存储分配,能够根据需要自动调整存储空间,避免了内存浪费和溢出的问题。
  2. 高效插入和删除:在链表的任一位置进行元素的插入、删除操作都是快速的,因为只需要修改相关节点的指针即可,不需要移动大量元素。
  3. 双向遍历:由于list是一个双向链表,因此可以从前往后或从后往前遍历整个链表,提供了灵活的访问方式。

三、使用方法

头文件:#include <list>

  1. 构造函数

list容器提供了多种构造函数,用于创建空的list容器或根据指定范围或元素个数创建list容器。例如:

list<int> lst; // 创建一个空的list容器
list<int> lst1(10, 1); // 创建一个包含10个元素的list容器,每个元素的值都为1
list<int> lst2(lst1.begin(), lst1.end()); // 创建一个包含lst1容器中所有元素的list容器
  1. 元素插入

list容器提供了多种插入元素的方法,包括在容器开头、结尾或指定位置插入元素。例如:

lst.push_back(10); // 在容器末尾插入元素10
lst.push_front(20); // 在容器开头插入元素20
lst.insert(lst.begin(), 30); // 在容器开头插入元素30(使用迭代器)
lst.insert(lst.end(), 40); // 在容器末尾插入元素40(使用迭代器)
  1. 元素删除

list容器提供了多种删除元素的方法,包括删除容器开头、结尾或指定位置的元素,以及删除容器中所有与指定值匹配的元素。例如:

lst.pop_back(); // 删除容器末尾的元素
lst.pop_front(); // 删除容器开头的元素
lst.erase(lst.begin()); // 删除容器开头的元素(使用迭代器)
lst.remove(10); // 删除容器中所有值为10的元素
  1. 元素访问

虽然list容器不支持像数组那样的随机访问,但可以通过迭代器或成员函数来访问元素。例如:

list<int>::iterator it = lst.begin(); // 获取指向容器开头的迭代器
cout << *it; // 输出迭代器指向的元素值
lst.front(); // 返回容器的第一个元素
lst.back(); // 返回容器的最后一个元素
  1. 容器大小

可以使用size()成员函数来获取list容器中元素的个数。例如:

size_t num = lst.size(); // 获取容器中元素的个数
  1. 容器操作

list容器还提供了其他操作,如清空容器(clear())、反转容器(reverse())和对容器进行排序(sort())等。例如:

lst.clear(); // 清空容器中的所有元素
lst.reverse(); // 反转容器中的元素顺序
lst.sort(); // 对容器中的元素进行排序(默认为升序)

十、deque容器(双端队列)

在C++标准库中,deque(双端队列)是一种拥有动态大小的数组

deque是double-ended queue(双端队列)的缩写,其名称来源于此。deque是一个序列容器,可以在其两端进行快速的插入和删除操作。与vector容器采用连续的线性空间不同,deque容器存储数据的空间是由一段一段等长的连续空间构成,各段空间之间并不一定是连续的,可以位于内存的不同区域。deque使用一个中控器(如指针数组)来指向这些一段一段的空间。

  • deque的主要优势在于支持快速的前后元素插入和删除操作,这使得它在需要在两端进行修改的数据结构中非常有用,例如滑动窗口。
  • 然而,与vector相比,deque在中间插入或删除元素的操作速度较慢。这是因为deque的元素在内存中可能不连续存储,插入或删除元素可能需要移动多个元素。
1. 引入头文件

在使用deque之前,需要引入相应的头文件:

#include <deque>
2. 初始化deque

deque的初始化方式有多种,包括默认初始化、使用另一个deque初始化、指定大小和初始值等。以下是一些示例:

std::deque<int> deq1; // 默认初始化,一个空的deque
std::deque<int> deq2(deq1); // 使用deq1初始化deq2,deq2将包含deq1中的所有元素
std::deque<int> deq3 = deq1; // 等价于deq2(deq1)
std::deque<int> deq4(3, 5); // 创建一个包含3个元素的deque,每个元素的值都为5
std::deque<int> deq5{1, 2, 3, 4, 5}; // 使用列表初始化deque
3. 插入元素

deque支持在两端和任意位置插入元素。以下是一些常用的插入方法:

myDeque.push_front(1); // 在头部插入元素1
myDeque.push_back(2); // 在尾部插入元素2
myDeque.insert(myDeque.begin(), 3); // 在begin()位置插入元素3
myDeque.insert(myDeque.begin(), 2, 4); // 在begin()位置插入2个元素4
4. 删除元素

deque同样支持在两端和任意位置删除元素。以下是一些常用的删除方法:

myDeque.pop_front(); // 删除头部元素
myDeque.pop_back(); // 删除尾部元素
myDeque.erase(myDeque.begin()); // 删除begin()位置的元素
myDeque.erase(myDeque.begin(), myDeque.begin() + 2); // 删除begin()到begin()+2之间的元素
5. 访问元素

deque支持通过索引访问元素,也可以使用front()back()方法访问首尾元素。以下是一些示例:

int first = myDeque.front(); // 访问首个元素
int last = myDeque.back(); // 访问末尾元素
int second = myDeque[1]; // 通过索引访问第二个元素
6. 判断和遍历

deque提供了多种方法来判断其状态以及遍历其元素。以下是一些常用的方法:

bool isEmpty = myDeque.empty(); // 判断deque是否为空
int size = myDeque.size(); // 获取deque中元素的个数
myDeque.clear(); // 清空deque中的所有元素
 
// 使用迭代器遍历deque
for (auto it = myDeque.begin(); it != myDeque.end(); ++it) {
    std::cout << *it << " ";
}
std::cout << std::endl;

此外,deque还支持反向迭代器,允许从尾部向前遍历元素。

十一、mutimap容器(键重复、排序)

multimap容器是C++标准库中的一种关联式容器,它允许存储多个具有相同键的键值对。以下是对multimap容器的定义、作用和使用方法的详细介绍:

定义:multimap容器将key/value pair(键值/实值对)作为元素进行管理,并根据key的排序准则自动将元素排序。与map容器不同的是,multimap允许存在多个具有相同键的元素。

作用:multimap容器的主要作用是提供一种高效的方式来存储、查找和访问具有多个相同键的键值对。由于它允许键的重复,因此非常适合于需要存储多个具有相同键的元素的场景。

1.插入元素

使用insert函数插入键值对。由于multimap允许键的重复,因此可以插入多个具有相同键的元素。

multimap中没有push_back函数

#include <iostream>
#include <map>
#include <string>
 
int main() {
    std::multimap<int, std::string> myMap;
    myMap.insert(std::make_pair(1, "Blue"));
    myMap.insert(std::make_pair(2, "Red"));
    myMap.insert(std::make_pair(2, "Cyan")); // 插入具有相同键的元素

    //或者
    std::multimap<int, std::string> myMultimap = {
        {1, "one"},
        {2, "two"},
        {2, "TWO"}, // 注意键2的重复
        {3, "three"},
        {4, "four"}
    };
    return 0;
}

2.查找元素

    • 使用find函数查找具有特定键的元素。由于multimap可能包含多个具有相同键的元素,因此find函数将返回指向第一个匹配元素的迭代器。
    • 示例代码:
std::multimap<int, std::string>::iterator pos = myMap.find(2);
if (pos != myMap.end()) {
    std::cout << "找到键为2的元素: " << pos->second << std::endl;
}

3.删除元素

    • 使用erase函数删除元素。erase函数有多种重载形式,可以删除指定键值的元素、指定迭代器位置的元素或指定迭代器范围的元素。
    • 示例代码:
myMap.erase(2); // 删除所有键为2的元素

4.修改元素

    • 由于multimap中的键不是唯一的,因此不能直接通过键来修改元素。需要先找到要修改的元素,然后使用迭代器来修改其值。
    • 示例代码(假设要修改键为2的第一个元素的值):
std::multimap<int, std::string>::iterator pos = myMap.find(2);
if (pos != myMap.end()) {
    pos->second = "New Value"; // 修改值
}

5.遍历元素

    • 使用迭代器遍历multimap中的所有元素。由于multimap中的元素是按照键的顺序存储的,因此可以使用迭代器按顺序访问这些元素。
    • 示例代码:
for (std::multimap<int, std::string>::iterator it = myMap.begin(); it != myMap.end(); ++it) {
    std::cout << "键: " << it->first << ", 值: " << it->second << std::endl;
}

6.统计元素数量

    • 使用count函数统计具有特定键的元素数量。
    • 示例代码:
size_t num = myMap.count(2);
std::cout << "键为2的元素数量: " << num << std::endl;

7.获取元素范围

    • 使用equal_range函数获取与给定键匹配的元素范围。equal_range返回一个pair,其中first是指向第一个匹配元素的迭代器,second是指向最后一个匹配元素之后一个位置的迭代器。
    • 示例代码:
std::pair<std::multimap<int, std::string>::iterator,std::multimap<int, std::string>::iterator> range = myMap.equal_range(2);
//或 auto range = myMap.equal_range(2);
for (std::multimap<int, std::string>::iterator it = range.first; it != range.second; ++it)
    {
    std::cout << "键: " << it->first << ", 值: " << it->second << std::endl;
    }

相关函数

一、equal_range函数

equal_range() 函数是 C++ 标准模板库(STL)中 multimapmultiset 容器特有的成员函数。它返回一个包含两个迭代器的 pair,这两个迭代器定义了一个范围,该范围包含了容器中所有键(或值,对于 multiset 而言)等于给定参数的所有元素。

以下是 equal_range() 函数的使用方法和注意事项:

使用方法

  1. 包含头文件
    确保你的程序中包含了 <map> 头文件,因为 multimap 是定义在该头文件中的。
  2. 创建 multimap 对象
    声明并初始化一个 multimap 对象。multimap 允许存储具有相同键的多个值。
  3. 调用 equal_range() 函数
    使用 equal_range() 函数查找与给定键相等的所有元素的范围。该函数接受一个键作为参数,并返回一个 pair 对象,其中 first 成员是指向第一个等于该键的元素的迭代器,second 成员是指向最后一个等于该键的元素之后的位置的迭代器。
  4. 处理返回的范围
    使用返回的迭代器范围来访问或处理所有等于给定键的元素。

示例代码

#include <iostream>
#include <map>
#include <string>
 
int main() {
    // 创建一个 multimap 并插入一些数据
    std::multimap<int, std::string> myMultimap = {
        {1, "one"},
        {2, "two"},
        {2, "TWO"}, // 注意键2的重复
        {3, "three"},
        {4, "four"}
    };
 
    // 使用 equal_range 查找键为 2 的所有元素的范围
    auto range = myMultimap.equal_range(2);
 
    // 输出范围内的所有元素
    std::cout << "键为 2 的元素有:" << std::endl;
    for (auto it = range.first; it != range.second; ++it) {
        std::cout << "值: " << it->second << std::endl;
    }
 
    return 0;
}

输出

键为 2 的元素有:
值: two
值: TWO

注意事项

  • equal_range() 函数返回的迭代器范围是基于容器的内部比较对象(key_comp)来确定的。
  • 如果容器中没有与给定键相等的元素,则返回的迭代器范围长度为 0,即 range.firstrange.second 都指向在给定键之后第一个元素的位置。
  • 在使用 equal_range() 函数之前,不需要对容器进行排序,因为 multimapmultiset 本身就是有序的容器。
  • equal_range() 函数特别适用于需要处理具有相同键的多个元素的情况,例如,在需要统计某个键出现的次数或获取与某个键关联的所有值时。

二、count函数的使用

1、作用

在C++中,count函数的作用主要用于统计某个元素在容器中出现的次数。这个函数通常与标准库容器(如vectorlistset等)一起使用,但需要注意的是,对于不同的容器类型,count函数的行为可能有所不同。

以下是对count函数作用的详细解释:

  1. 统计元素出现次数
    • 对于允许重复元素的容器(如vectorlistmultimapmultiset等),count函数会返回指定元素在容器中出现的次数。
    • 例如,如果有一个vector<int>容器,其中包含了元素1、2、3、2、4、2,那么调用count(2)将会返回3,因为元素2在容器中出现了3次。
  1. 检查元素是否存在
    • 虽然count函数的主要作用是统计次数,但也可以间接用于检查元素是否存在于容器中。如果count函数返回的结果大于0,则说明容器中包含该元素;如果返回0,则说明容器中不包含该元素。
    • 然而,对于不允许重复元素的容器(如setmapunordered_setunordered_map等),由于每个元素都是唯一的,因此count函数在这些容器中只会返回0(元素不存在)或1(元素存在)。在这种情况下,使用find函数可能更为直观和高效,因为find函数会直接返回一个指向元素位置的迭代器(如果元素存在)或指向容器末尾的迭代器(如果元素不存在)。
  1. 适用于多种容器
    • count函数是C++标准库算法的一部分,因此它可以与多种容器类型一起使用。只要容器支持迭代器操作,就可以使用count函数来统计元素的出现次数。
  1. 性能考虑
    • 对于不同的容器类型,count函数的性能可能会有所不同。例如,在vectorlist等序列容器中,count函数需要遍历整个容器来查找元素,因此其时间复杂度通常为O(n)。而在setmap等关联容器中,由于元素是按照特定顺序存储的,因此count函数的性能可能会更好(尽管对于不允许重复元素的容器来说,count的返回值只能是0或1,所以性能上的优势并不明显)。然而,在unordered_setunordered_map等哈希表中,count函数的时间复杂度通常为平均O(1),因为哈希表提供了常数时间复杂度的查找操作。

2、使用方法:

在C++标准库中,count函数的使用依赖于容器的类型。虽然许多容器都提供了count成员函数(特别是关联容器),但也可以使用标准算法库中的std::count函数来统计序列容器中元素的出现次数。以下是这两种情况的详细使用方法:

(1)容器自带的count成员函数

对于关联容器(如std::setstd::mapstd::multisetstd::multimapstd::unordered_setstd::unordered_map),它们通常提供了一个count成员函数,用于返回指定键(或元素)在容器中出现的次数。

#include <iostream>
#include <set>
#include <unordered_set>
#include <map>
#include <unordered_map>
 
int main() {
    std::set<int> s = {1, 2, 3, 4, 4, 4}; // 注意:set实际上不允许重复,这里只是为了说明
    std::unordered_set<int> us = {1, 2, 2, 3}; // unordered_set不允许重复
    std::multiset<int> ms = {1, 2, 3, 4, 4, 4}; // multiset允许重复
 
    std::cout << "Set count of 4: " << s.count(4) << std::endl; // 输出1,因为set不允许重复
    std::cout << "Unordered set count of 2: " << us.count(2) << std::endl; // 输出1
    std::cout << "Multiset count of 4: " << ms.count(4) << std::endl; // 输出3
 
    // 对于map和unordered_map,count函数用于返回指定键的条目数(每个键最多一个条目,除非是多映射)
    std::map<int, std::string> m = {{1, "one"}, {2, "two"}, {3, "three"}};
    std::cout << "Map count of 2: " << m.count(2) << std::endl; // 输出1
    //对于multimap
    std::multimap<int, std::string> m = { {1, "one"}, {2, "two"}, {2, "two"} };
    std::cout << "Map count of 2: " << m.count(2) << std::endl; //输出2
 
    return 0;
}

请注意,上面的std::set示例只是为了说明count成员函数的存在;实际上,std::set不会存储重复元素,所以count对于std::setstd::unordered_set总是返回0或1。

(2)标准算法库中的std::count

对于序列容器(如std::vectorstd::liststd::deque等),可以使用标准算法库中的std::count函数来统计元素的出现次数。

#include <iostream>
#include <vector>
#include <algorithm> // 包含std::count
 
int main() {
    std::vector<int> v = {1, 2, 3, 4, 2, 2, 5};
 
    // 使用std::count统计元素2的出现次数
    int count_of_twos = std::count(v.begin(), v.end(), 2);
 
    std::cout << "Vector count of 2: " << count_of_twos << std::endl; // 输出3
 
    return 0;
}

在这个例子中,std::count函数接受三个参数:两个迭代器(v.begin()v.end())定义了要搜索的序列范围,以及要查找的值(2)。函数返回该值在指定范围内的出现次数。

更多推荐