突破编程_C++_STL教程( map 的基础知识)

1 std::map 概述

std::map 是 C++ 标准模板库(STL)中的一个关联容器,它提供了一对一的数据处理能力,其中每个关键字(key)在 map 中只能出现一次,并且与一个值(value)相关联。这种键值对(key-value)的存储方式使得 map 成为一个单映射容器,非常适用于处理需要一对一映射关系的数据。

在 std::map 内部,元素总是按照其键(key)进行排序的。为了实现这种排序功能,map 内部自建了一颗红黑树(一种非严格意义上的平衡二叉树)。红黑树具有自动排序的功能,因此 map 内部的所有数据都是有序的。这种有序性使得在 map 中查找、删除和插入操作具有对数复杂度。

map 的另一个特点是,增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。对于迭代器来说,可以修改实值,但不能修改key。

此外,std::map 满足容器(Container)、具分配器容器(AllocatorAwareContainer)、关联容器(AssociativeContainer)和可逆容器(ReversibleContainer)的要求,这使得它在编程中能够提供灵活且高效的数据处理能力。

总的来说,std::map 是一个功能强大的关联容器,适用于需要处理一对一映射关系的数据,并且在内部实现了高效的排序和查找操作。

1.1 std::map 的内部实现

std::map 的内部实现主要基于红黑树(Red-Black Tree)这种数据结构。红黑树是一种自平衡的二叉搜索树,它通过对每个节点附加额外的颜色信息,并满足一系列性质来确保树的相对平衡,从而保证在树中的搜索、插入和删除操作的时间复杂度都是 O(log n)。

红黑树作为 std::map 核心数据结构的关键特性如下:

节点结构:

  • 每个节点通常包含键值(key)和可能的附加信息(如颜色标记,用于红黑树的平衡)。
  • 每个节点还有指向其左子节点和右子节点的指针。
  • 叶子节点(通常是空节点或哨兵节点)通常用于简化边界条件的处理。

颜色标记:

  • 在红黑树中,每个节点都有一个颜色属性,通常是红色或黑色。
  • 这些颜色用于维持树的平衡和满足红黑树的约束条件。

二叉搜索树特性:

  • 左子节点的键值小于其父节点的键值。
  • 右子节点的键值大于其父节点的键值。

红黑树的约束:

  • 每个节点或者是红色,或者是黑色。
  • 根节点是黑色。
  • 每个叶子节点(NIL节点)是黑色。
  • 如果一个节点是红色的,那么它的两个子节点都是黑色的(从每个叶子到根的所有路径上不能有两个连续的红色节点)。
  • 从任一节点到其每个叶子的所有简单路径都包含相同数量的黑色节点。

平衡调整:

  • 当插入或删除节点时,红黑树可能需要重新调整以维持其平衡和满足约束条件。
  • 这可能涉及到节点颜色的改变、节点的旋转(左旋或右旋)以及树结构的调整。

std::map 中的每个元素都以键值对(key-value)的形式存储,其中键是唯一的,用于在红黑树中进行排序和查找。在 std::map 中,键值对按照键的大小进行排序,这种排序方式称为严格的弱排序(strict weak ordering),即在判断 Key1 和 Key2 的大小时,使用"<"而不是"<="。

在 std::map 的底层实现中,每个节点(通常称为 _Rb_tree_node)都包含了一个键值对以及指向其子节点的指针(_M_left和_M_right)。此外,每个节点还有一个颜色属性(_M_color),用于红黑树的平衡维护。这些节点按照红黑树的规则进行链接,形成了一棵有序的树形结构。

当使用 std::map 的迭代器进行遍历时,实际上是遍历了这棵红黑树。通过迭代器的 _M_node 指针,可以访问到当前节点的键值对信息。由于红黑树的性质,这种遍历操作具有对数级别的复杂度,因此在处理大量数据时仍然能够保持较高的效率。

1.2 std::map 的性能特点

以下是关于 std::map 性能的一些关键点:

(1)对数复杂度操作: std::map 内部通过红黑树实现,因此搜索、删除和插入操作的平均时间复杂度为 O(log n),其中 n 是 map 中元素的数量。这意味着,无论 map 中有多少元素,这些操作的时间成本都会以对数级别增长,这在处理大量数据时仍能保持相对较高的效率。

(2)自动排序: std::map 中的元素会根据键(key)自动排序。这种排序功能是通过红黑树的特性实现的,无需程序员手动进行排序。

(3)迭代器稳定性: 在 std::map 中,增加和删除节点对迭代器的影响很小。除了被操作的节点,对其他节点几乎没有影响。这意味着,在遍历或操作 map 的过程中,即使有其他线程或操作对 map 进行修改,迭代器通常仍然有效,能够继续正确地访问元素。

(4)不允许修改键: 对于 std::map 的迭代器,可以修改实值(value),但不能修改键(key)。这是因为键是元素在 map 中排序和定位的基础,修改键可能会破坏 map 的结构和排序。

(5)内存使用: 由于 std::map 内部使用红黑树,这可能会导致其内存使用相对于其他数据结构(如哈希表)稍高。然而,红黑树的内存使用是可控的,并且提供了稳定的对数复杂度操作。

2 std::map 的基本使用

2.1 std::map 的声明与初始化

声明

首先,需要包含<map>头文件以使用 std::map:

cpp 复制代码
#include <map>  
#include <string>  

// 声明一个键与值都是整数类型的 map  
std::map<int,int> vals;

// 声明一个键是字符串类型,值是整数类型的 map  
std::map<std::string,int> strs;

// 声明一个键是自定义类型,值是整数类型的 map  
struct MyStruct
{
	bool operator<(const MyStruct& data) const
	{
		return id < data.id;
	}
	int id;
	std::string name;
};
std::map<MyStruct,int> myStructs;

初始化

可以使用多种方法来初始化 std::map。

(1)默认初始化:

创建一个空的 std::map 容器。

cpp 复制代码
std::map<int,int> vals; // 空的 map

(2)使用初始化列表:

在声明时直接使用初始化列表来添加元素,元素会按照排序顺序插入。

cpp 复制代码
std::map<int, int> vals = { {1,2}, {2,3}, {3,4}, {4,5}, {5,6} };
// vals 现在包含元素:{1,2}, {2,3}, {3,4}, {4,5}, {5,6}

(3)复制初始化:

使用另一个 std::map 来初始化新的 std::map。

cpp 复制代码
std::map<int, int> firstMap = { {1,2}, {2,3}, {3,4}, {4,5}, {5,6} };
std::map<int, int> secondMap(firstMap); // 复制初始化  
// firstMap 与 secondMap 现在都包含元素:{1,2}, {2,3}, {3,4}, {4,5}, {5,6}

(4)移动初始化:

使用另一个 std::map 的移动构造函数来初始化新的 std::map。

cpp 复制代码
std::map<int, int> firstMap = { {1,2}, {2,3}, {3,4}, {4,5}, {5,6} };
	std::map<int, int> secondMap(std::move(firstMap)); // 移动初始化  
// secondMap 现在包含元素:{1,2}, {2,3}, {3,4}, {4,5}, {5,6}
// firstMap 现在是一个空的 map

2.2 std::map 的大小与容量

std::map:中与大小与容量相关的方法有如下几种:

  • empty() const;: 检查 map 是否为空。
  • size() const;: 返回 map 中的元素数量。
  • max_size() const;: 返回 map 可能包含的最大元素数量。

如下为样例代码:

cpp 复制代码
#include <iostream>  
#include <map>  
#include <string>  

int main() 
{
	// 创建一个空的 std::map  
	std::map<std::string, int> myMap;

	// 检查 map 是否为空  
	if (myMap.empty()) {
		std::cout << "Map is empty.\n";
	}
	else {
		std::cout << "Map is not empty.\n";
	}

	// 插入一些元素  
	myMap["Apple"] = 1;
	myMap["Banana"] = 2;
	myMap["Cherry"] = 3;

	// 再次检查 map 是否为空  
	if (myMap.empty()) {
		std::cout << "Map is still empty.\n";
	}
	else {
		std::cout << "Map is now not empty.\n";
	}

	// 获取 map 的大小  
	std::cout << "Size of the map: " << myMap.size() << "\n";

	// 获取 map 的最大可能大小  
	std::cout << "Maximum possible size of the map: " << myMap.max_size() << "\n";

	// 遍历 map 并输出元素  
	for (const auto& pair : myMap) {
		std::cout << pair.first << ": " << pair.second << "\n";
	}

	// 清除 map 中的所有元素  
	myMap.clear();

	// 再次检查 map 是否为空  
	if (myMap.empty()) {
		std::cout << "Map is empty again.\n";
	}
	else {
		std::cout << "Map is not empty after clearing.\n";
	}

	return 0;
}

上面代码的输出为:

Map is empty.
Map is now not empty.
Size of the map: 3
Maximum possible size of the map: 230584300921369395
Apple: 1
Banana: 2
Cherry: 3
Map is empty again.

这个示例首先创建了一个空的 std::map,并使用 empty() 方法检查它是否为空。然后,插入了一些元素,并再次检查它是否为空。接着,使用 size() 方法来获取 map 中的元素数量,并将其输出。之后,使用 max_size() 方法来获取 map 可能包含的最大元素数量(这通常是一个非常大的值,代表了理论上可能存储的元素的最大数量,实际上很少会达到这个限制)。最后,遍历了 map 并输出了每个键值对,然后清除了 map 中的所有元素,并再次检查它是否为空。

2.3 std::map 的构造函数与析构函数

构造函数

std::map 有多个构造函数,允许以不同的方式创建 map 对象。下面是一些常用的构造函数:

  • std::map<K,V> s(): 默认构造函数,创建一个空的map。
  • std::map<K,V> s(const std::map<K,V>& other): 拷贝构造函数,创建一个与另一个 map 相同的 map。
  • std::map<K,V> s(std::map<K,V>&& other);: 移动构造函数,创建一个与 other 内容相同的 map,并将 other 置于有效但未定义的状态(C++11 新加入)。
  • std::map<K,V> s(std::initializer_list<K,V> init);: 使用初始化列表来创建 map(C++11 新加入)。

析构函数

  • ~map(): 析构函数,释放 map 中的所有元素。

如下为样例代码:

cpp 复制代码
#include <iostream>  
#include <map>  
#include <string>  

int main() 
{
	// 默认构造函数,创建一个空的map  
	std::map<std::string, int> mapDefault;
	std::cout << "mapDefault is empty: " << mapDefault.empty() << std::endl;

	// 插入元素到mapDefault  
	mapDefault["one"] = 1;
	mapDefault["two"] = 2;
	mapDefault["three"] = 3;

	// 拷贝构造函数,创建一个与mapDefault相同的map  
	std::map<std::string, int> mapCopy(mapDefault);
	std::cout << "mapCopy contains " << mapCopy.size() << " elements." << std::endl;

	// 移动构造函数,创建一个与mapCopy内容相同的map,并将mapCopy置于有效但未定义状态  
	std::map<std::string, int> mapMove(std::move(mapCopy));
	std::cout << "mapMove contains " << mapMove.size() << " elements." << std::endl;

	// mapCopy已经移动到了mapMove,所以现在是空的  
	std::cout << "mapCopy is now empty: " << mapCopy.size() << std::endl;

	// 使用初始化列表创建map  
	std::map<std::string, int> mapInitList = {
		{"four", 4},
		{"five", 5},
		{"six", 6}
	};
	std::cout << "mapInitList contains " << mapInitList.size() << " elements." << std::endl;

	// 析构函数会在map对象离开作用域时自动调用  
	// 在这个例子中,当main函数结束时,所有map对象都会被析构  

	// 注意:我们没有直接调用析构函数,它是隐式调用的。  

	return 0;
}

上面代码的输出为:

mapDefault is empty: 1
mapCopy contains 3 elements.
mapMove contains 3 elements.
mapCopy is now empty: 0
mapInitList contains 3 elements.

在这个示例中,分别展示了 std::map 的默认构造函数、拷贝构造函数、移动构造函数以及使用初始化列表创建 map 的方法。然后,通过检查 empty() 方法和size() 方法验证了 map 对象的状态。需要注意的是,当使用移动构造函数后,原来的 map 对象(在这个例子中是 mapCopy )将不再拥有其原有数据,并且处于有效但未定义的状态。

析构函数在这个示例中并没有显式调用,因为当 map 对象离开其作用域时,它们会自动被析构。在实际程序中,析构函数用于清理 map 对象所占用的资源,但通常不需要显式调用它,因为 C++ 会自动管理对象的生命周期。

3 std::map 的元素操作

3.1 std::map 元素的访问与修改

3.1.1 访问元素

使用下标运算符 operator[]:

这种方法不仅用于访问元素,如果键不存在于map中,它还会创建一个新的元素。

cpp 复制代码
std::map<std::string, int> myMap;  
myMap["apple"] = 10; // 插入或访问键为"apple"的元素  
int value = myMap["apple"]; // 访问键为"apple"的元素的值,返回10

如果尝试访问一个不存在的键,operator[]会创建一个新的元素,并将其值初始化为默认值(对于基本数据类型,如 int,默认值是 0)。

使用 find 方法:

如果只想检查键是否存在而不想创建新元素,可以使用 find 方法。

cpp 复制代码
std::map<std::string, int>::iterator it = myMap.find("apple");  
if (it != myMap.end()) {  
    int value = it->second; // 访问键为"apple"的元素的值  
} else {  
    std::cout << "Key not found!" << std::endl;  
}

3.1.2 修改元素

修改 std::map 中的元素非常简单,可以通过键直接访问并修改其对应的值。

cpp 复制代码
std::map<std::string, int> myMap;  
myMap["apple"] = 10; // 插入键为"apple"的元素,其值为10  
myMap["apple"] = 20; // 修改键为"apple"的元素的值为20

也可以使用迭代器来修改元素:

cpp 复制代码
std::map<std::string, int>::iterator it = myMap.find("apple");  
if (it != myMap.end()) {  
    it->second = 30; // 修改键为"apple"的元素的值为30  
}

注意事项:

  • 使用operator[]访问元素时,如果键不存在,它会创建一个新元素。如果只想检查元素是否存在而不希望改变 map 的内容,应该使用 find 方法。
  • find 方法返回一个迭代器,指向找到的元素,或者如果元素不存在,则返回 end() 迭代器。因此,在访问元素之前,应该检查返回的迭代器是否等于 end()。
  • 使用迭代器修改元素时,应该修改 iterator->second,因为 iterator->first 是键,而 iterator->second 是值。

3.2 std::map 元素的插入操作

std::map 中与元素插入操作相关的方法有如下几种:

  • insert(const value_type& val): 插入一个元素。
  • insert(value_type&& val): 移动插入一个元素(C++11 及以后)。
  • insert(const_iterator position, const value_type& val): 在指定位置前插入一个元素。
  • insert(const_iterator position, value_type&& val): 在指定位置前移动插入一个元素(C++11 及以后)。
  • insert(input_iterator first, input_iterator last): 插入一个元素范围。
  • operator[]: 通过键访问元素,如果元素不存在则创建它。

如下为样例代码:

cpp 复制代码
#include <iostream>  
#include <map>  
#include <string>  

int main() 
{
	// 创建一个空的std::map  
	std::map<std::string, int> myMap;

	// 使用insert方法插入单个元素(通过值)  
	myMap.insert(std::pair<std::string, int>("apple", 10));
	myMap.insert(std::make_pair("banana", 20));

	// 使用insert方法在指定位置前插入元素  
	auto it = myMap.find("banana");
	if (it != myMap.end()) {
		myMap.insert(it, std::pair<std::string, int>("blueberry", 25));
	}

	// 使用insert方法插入一个元素范围  
	std::map<std::string, int> tempMap = {
		{"elderberry", 50},
		{"fig", 60}
	};
	myMap.insert(tempMap.begin(), tempMap.end());

	// 使用operator[]访问元素,如果元素不存在则创建它  
	myMap["grape"] = 70;

	// 打印map中的所有元素  
	for (const auto& pair : myMap) {
		std::cout << pair.first << ": " << pair.second << std::endl;
	}

	return 0;
}

上面代码的输出为:

apple: 10
banana: 20
blueberry: 25
elderberry: 50
fig: 60
grape: 70

在上面代码中,需要注意的是,在实际编程中,可能需要处理插入操作可能引发的异常。例如当插入的键已经存在于 std::map 中时,使用 insert 方法不会替换现有元素,而是返回一个表示插入是否成功的 std::pair<iterator, bool>。如果希望替换现有元素的值,可以直接使用operator[]。

3.3 std::map 元素的删除操作

std::map 中与元素删除操作相关的方法有如下几种:

  • erase(const key_type& key): 删除指定键的元素。
  • erase(const_iterator position): 删除指定位置的元素。
  • erase(const_iterator first, const_iterator last): 删除一个元素范围。
  • clear(): 删除所有元素。

如下为样例代码:

cpp 复制代码
#include <iostream>  
#include <map>  
#include <string>  

int main() 
{
	// 创建一个std::map并插入一些元素  
	std::map<std::string, int> myMap = {
		{"apple", 10},
		{"banana", 20},
		{"cherry", 30},
		{"date", 40}
	};

	// 打印当前map的所有元素  
	std::cout << "Original map contents:\n";
	for (const auto& pair : myMap) {
		std::cout << pair.first << ": " << pair.second << std::endl;
	}

	// 删除指定键的元素  
	size_t numDeleted = myMap.erase("banana");
	if (numDeleted) {
		std::cout << "Element with key 'banana' deleted successfully.\n";
	}
	else {
		std::cout << "Element with key 'banana' not found.\n";
	}

	// 打印删除元素后的map  
	std::cout << "Map after deleting 'banana':\n";
	for (const auto& pair : myMap) {
		std::cout << pair.first << ": " << pair.second << std::endl;
	}

	// 删除指定位置的元素  
	auto it = myMap.find("cherry");
	if (it != myMap.end()) {
		myMap.erase(it);
		std::cout << "Element at position of key 'cherry' deleted successfully.\n";
	}
	else {
		std::cout << "Element with key 'cherry' not found.\n";
	}

	// 打印删除元素后的map  
	std::cout << "Map after deleting 'cherry':\n";
	for (const auto& pair : myMap) {
		std::cout << pair.first << ": " << pair.second << std::endl;
	}

	// 删除一个元素范围  
	it = myMap.begin();
	auto nextIt = std::next(it);
	myMap.erase(it, nextIt); // 删除第一个元素  

	// 打印删除元素范围后的map  
	std::cout << "Map after deleting a range of elements:\n";
	for (const auto& pair : myMap) {
		std::cout << pair.first << ": " << pair.second << std::endl;
	}

	// 删除所有元素  
	myMap.clear();

	// 打印清空后的map  
	std::cout << "Map after calling clear():\n";
	if (myMap.empty()) {
		std::cout << "Map is empty.\n";
	}
	else {
		for (const auto& pair : myMap) {
			std::cout << pair.first << ": " << pair.second << std::endl;
		}
	}

	return 0;
}

上面代码的输出为:

Original map contents:
apple: 10
banana: 20
cherry: 30
date: 40
Element with key 'banana' deleted successfully.
Map after deleting 'banana':
apple: 10
cherry: 30
date: 40
Element at position of key 'cherry' deleted successfully.
Map after deleting 'cherry':
apple: 10
date: 40
Map after deleting a range of elements:
date: 40
Map after calling clear():
Map is empty.

在上面代码中,需要注意的是,使用erase方法删除元素时,如果传入的迭代器或键不存在于std::map中,那么方法不会有任何效果(对于按键删除)或会导致未定义行为(对于按迭代器删除)。因此,在删除元素之前,最好先检查元素是否存在。对于按迭代器删除,通常需要先找到要删除的元素的迭代器,然后检查该迭代器是否不等于end()迭代器。

3.4 std::map 的查找操作

std::map 中与查找操作的方法有如下几种:

  • find(const key_type& k);: 查找键为 k 的元素,并返回指向它的迭代器,否则返回 end()。
  • count(const key_type& k);: 返回键为 k 的元素的数量(对于 map,此值始终为 0 或 1)。
  • lower_bound(const key_type& k);: 返回指向第一个不小于 k 的元素的迭代器。
  • upper_bound(const key_type& k);: 返回指向第一个大于 k 的元素的迭代器。
  • equal_range(const key_type& k);: 返回一个包含键为 k 的所有元素的迭代器对。

如下为样例代码:

cpp 复制代码
#include <iostream>  
#include <map>  
#include <string>  

int main() {
	// 创建一个std::map并插入一些元素  
	std::map<std::string, int> myMap = {
		{"apple", 10},
		{"banana", 20},
		{"cherry", 20},
		{"date", 30},
		{"elderberry", 40}
	};

	// 查找键为"banana"的元素  
	auto it = myMap.find("banana");
	if (it != myMap.end()) {
		std::cout << "Found element with key 'banana': " << it->second << std::endl;
	}
	else {
		std::cout << "Element with key 'banana' not found." << std::endl;
	}

	// 使用count方法检查键为"cherry"的元素是否存在  
	size_t count = myMap.count("cherry");
	std::cout << "Number of elements with key 'cherry': " << count << std::endl;

	// 使用lower_bound查找第一个不小于"date"的元素  
	auto lowerIt = myMap.lower_bound("date");
	if (lowerIt != myMap.end()) {
		std::cout << "First element not less than 'date': " << lowerIt->first << ": " << lowerIt->second << std::endl;
	}
	else {
		std::cout << "No element not less than 'date' found." << std::endl;
	}

	// 使用upper_bound查找第一个大于"elderberry"的元素  
	auto upperIt = myMap.upper_bound("elderberry");
	if (upperIt != myMap.end()) {
		std::cout << "First element greater than 'elderberry': " << upperIt->first << ": " << upperIt->second << std::endl;
	}
	else {
		std::cout << "No element greater than 'elderberry' found." << std::endl;
	}

	// 使用equal_range查找键为"cherry"的所有元素(在这个例子中只有一个)  
	auto range = myMap.equal_range("cherry");
	for (auto it = range.first; it != range.second; ++it) {
		std::cout << "Element in the range of 'cherry': " << it->first << ": " << it->second << std::endl;
	}

	return 0;
}

上面代码的输出为:

Found element with key 'banana': 20
Number of elements with key 'cherry': 1
First element not less than 'date': date: 30
No element greater than 'elderberry' found.
Element in the range of 'cherry': cherry: 20

这个示例中展示了如何使用std::map的查找方法:

  • 使用 find 方法查找一个键,并检查返回的迭代器是否等于 end(),以确定元素是否存在。
  • 使用 count 方法检查一个键在std::map中的出现次数(对于 std::map,它总是0或1)。
  • 使用 lower_bound 方法查找第一个不小于给定键的元素。
  • 使用 upper_bound 方法查找第一个大于给定键的元素。
  • 使用 equal_range 方法获取一个迭代器对,表示键的所有可能位置(对于 std::map,这通常是一个迭代器和一个指向下一个元素的迭代器)。

3.5 std::map 的遍历删除

如果需要在遍历过程中逐个删除元素,可以使用 std::map::erase 方法结合普通的循环,但每次删除元素后,都需要更新迭代器。以下是一个逐个删除特定元素的例子:

如下为样例代码:

cpp 复制代码
#include <iostream>  
#include <map>  

int main()
{
	std::map<int, int> vals = { {1,2}, {2,3}, {3,4}, {4,5}, {5,6} };

	auto it = vals.begin();
	while (it != vals.end()) {
		if ((it->second) % 2 == 0) {
			it = vals.erase(it); // erase 返回下一个有效元素的迭代器  
		}
		else {
			++it; // 继续到下一个元素  
		}
	}

	// 输出结果  
	for (const auto& elem : vals) {
		std::cout << elem.first << ":" << elem.second << std::endl;
	}

	return 0;
}

上面代码的输出为:

2:3
4:5

在上面代码中,使用一个循环来遍历 map,并在每次迭代中检查当前元素是否满足删除条件。如果满足条件,则使用 erase 方法删除该元素,并更新迭代器。如果不满足条件,则简单地递增迭代器以继续遍历。

注意:在删除元素后,迭代器 it 会被 erase 方法更新为指向被删除元素之后的位置,因此在下一次循环迭代中,it 仍然有效。

4 std::map 的迭代器

4.1 std::map 迭代器的基本使用

std::map 中与迭代器相关的方法有如下几种:

  • begin(): 返回一个指向容器中第一个元素的迭代器。
  • end(): 返回一个指向容器中最后一个元素之后位置的迭代器。
  • rbegin(): 返回一个指向容器中最后一个元素的反向迭代器。
  • rend(): 返回一个指向容器中第一个元素之前位置的反向迭代器。
  • cbegin(), cend(), crbegin(), crend(): 与上述类似,但返回的是常量迭代器或常量反向迭代器。

如下为样例代码:

cpp 复制代码
#include <iostream>  
#include <map>  
#include <string>  

int main() 
{
	// 创建一个std::map并插入一些元素  
	std::map<std::string, int> myMap = {
		{"apple", 10},
		{"banana", 20},
		{"cherry", 30},
		{"date", 40},
		{"elderberry", 50}
	};

	// 使用begin和end遍历map  
	for (std::map<std::string, int>::iterator it = myMap.begin(); it != myMap.end(); ++it) {
		std::cout << "Key: " << it->first << ", Value: " << it->second << std::endl;
	}

	// 使用cbegin和cend遍历map(常量迭代器)  
	for (std::map<std::string, int>::const_iterator cit = myMap.cbegin(); cit != myMap.cend(); ++cit) {
		std::cout << "Const Key: " << cit->first << ", Const Value: " << cit->second << std::endl;
	}

	// 使用rbegin和rend反向遍历map  
	for (std::map<std::string, int>::reverse_iterator rit = myMap.rbegin(); rit != myMap.rend(); ++rit) {
		std::cout << "Reverse Key: " << rit->first << ", Reverse Value: " << rit->second << std::endl;
	}

	// 使用crbegin和crend反向遍历map(常量反向迭代器)  
	for (std::map<std::string, int>::const_reverse_iterator crit = myMap.crbegin(); crit != myMap.crend(); ++crit) {
		std::cout << "Const Reverse Key: " << crit->first << ", Const Reverse Value: " << crit->second << std::endl;
	}

	// 使用auto和基于范围的for循环  
	std::cout << "Using range-based for loop:" << std::endl;
	for (const auto& pair : myMap) {
		std::cout << "Key: " << pair.first << ", Value: " << pair.second << std::endl;
	}

	return 0;
}

上面代码的输出为:

Key: apple, Value: 10
Key: banana, Value: 20
Key: cherry, Value: 30
Key: date, Value: 40
Key: elderberry, Value: 50
Const Key: apple, Const Value: 10
Const Key: banana, Const Value: 20
Const Key: cherry, Const Value: 30
Const Key: date, Const Value: 40
Const Key: elderberry, Const Value: 50
Reverse Key: elderberry, Reverse Value: 50
Reverse Key: date, Reverse Value: 40
Reverse Key: cherry, Reverse Value: 30
Reverse Key: banana, Reverse Value: 20
Reverse Key: apple, Reverse Value: 10
Const Reverse Key: elderberry, Const Reverse Value: 50
Const Reverse Key: date, Const Reverse Value: 40
Const Reverse Key: cherry, Const Reverse Value: 30
Const Reverse Key: banana, Const Reverse Value: 20
Const Reverse Key: apple, Const Reverse Value: 10
Using range-based for loop:
Key: apple, Value: 10
Key: banana, Value: 20
Key: cherry, Value: 30
Key: date, Value: 40
Key: elderberry, Value: 50

这个样例展示了如何使用不同类型的迭代器来遍历 std::map。begin() 和 end() 用于正向遍历,rbegin() 和 rend() 用于反向遍历。同时,cbegin(), cend(), crbegin(), 和 crend() 是常量版本的迭代器,它们用于保证在遍历过程中不会修改容器中的元素。最后还使用 auto 关键字和基于范围的 for 循环简化遍历代码。

4.2 std::map 迭代器使用的注意事项

在使用 std::map 的迭代器时,有几个重要的注意事项需要牢记:

(1)有效性: 一旦迭代器指向的元素被删除或移动,迭代器就失效了。在删除或修改 std::map 中的元素后,必须确保不再使用失效的迭代器。

(2)常量迭代器: std::map 提供了常量迭代器(const_iterator),这些迭代器不能用来修改元素的值。如果需要修改元素,应该使用非常量迭代器。

(3)遍历过程中删除元素: 在遍历 std::map 的过程中直接删除元素会导致迭代器失效。注意将迭代器重新赋值为下一个有效元素的迭代器(即 erase 的返回值)。

(4)end() 方法返回的迭代器: end() 方法返回的迭代器指向的是容器中的"尾后"位置,即最后一个元素之后的位置。这个迭代器不能被解引用。在遍历容器时,应该小心不要试图访问 end() 返回的迭代器。

(5)修改键值: std::map 中的元素是根据键值排序的。如果通过迭代器修改了元素的键值,这将会破坏容器的排序特性。通常情况下,不应该这样做。如果需要改变元素的键值,应该先删除原元素,然后插入新的元素。

相关推荐
一点媛艺3 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风3 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生4 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
pianmian14 小时前
python数据结构基础(7)
数据结构·算法
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程5 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
UestcXiye6 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
Chrikk6 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*6 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go