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

1 std::deque 概述

std::deque(双端队列)是 C++ 标准库中的一个容器,它允许在序列的两端进行高效的插入和删除操作。std::deque 通常通过分段连续存储的方式来实现,这意味着它并不是将所有元素连续存储在一个连续的内存块中,而是将元素分散存储在多个连续的、固定大小的内存块中,这些内存块被链接起来以形成一个逻辑上的连续序列。

std::deque 的主要特点包括:

(1)高效的两端插入和删除: 由于 std::deque 内部使用了多个固定大小的内存块,因此在两端进行插入和删除操作时,只需要调整内存块的链接关系,而不需要移动大量的元素。这使得 std::deque 在两端进行操作的性能接近于O(1)。

(2)支持随机访问: std::deque 可以使用索引操作符[] 或 at() 函数直接访问元素。也可以通过迭代器或 front()、back() 成员函数来访问首尾元素。

(3)动态大小: std::deque 的大小是动态的,可以在运行时增长或缩小。

1.1 std::deque 的内部实现

std::deque 的内部实现通常涉及到一些复杂的数据结构和内存管理策略,以便提供在序列两端进行高效插入和删除操作的能力。注意:不同的 C++ 标准库实现(如 GCC 的 libstdc++、Clang 的 libc++ 或 Microsoft 的 STL)可能会有不同的实现细节。

如下是一个简化的 std::deque 内部实现的策略:

(1)分段连续存储:
std::deque 通常不是将所有元素存储在一个连续的内存块中,而是将元素分散存储在多个固定大小的内存块(称为 map 或 buffer )中。
每个内存块通常包含一定数量的元素,这个数量是一个实现定义的常量。
这些内存块通过指针或迭代器链接在一起,形成一个逻辑上的连续序列。

(2)控制结构:
std::deque 使用一个称为"控制结构"或"中间层"的组件来管理内存块。
控制结构通常包含指向第一个和最后一个内存块的指针,以及一个指向中间内存块的指针(如果有的话)。
控制结构还负责维护内存块的分配和释放,以及处理内存块之间的链接。

(3)内存分配:
当需要在 std::deque 的头部或尾部插入元素时,如果当前内存块已满,控制结构会分配一个新的内存块。
类似地,当从 std::deque 的头部或尾部删除元素时,如果某个内存块变得空闲,控制结构可能会释放它或将其用于其他目的。

(4)元素访问:
由于 std::deque 内部使用多个内存块,因此访问元素可能涉及多个内存访问。
通常,std::deque 提供随机访问迭代器,允许用户通过下标或迭代器直接访问任何位置的元素。
迭代器需要知道当前元素位于哪个内存块中,并计算偏移量以找到正确的元素。

(5)空间效率:
为了减少内存碎片和浪费,std::deque 可能会合并相邻的内存块,尤其是在内存块被释放后。
这可以通过移动元素来实现,将来自不同内存块的元素合并到一个内存块中。

(6)异常安全性:
std::deque 的实现通常是异常安全的,这意味着在抛出异常时,容器会保持其有效性。
这意味着,即使在插入或删除操作中发生异常,std::deque 也不会泄露内存或损坏其内部状态。

1.2 std::deque 的性能特点

std::deque(双端队列)的性能特点主要体现在以下几个方面:

(1)两端插入和删除的高效性: std::deque 支持在序列的两端进行高效的插入和删除操作。这是通过使用多个固定大小的内存块来实现的,这些内存块通过指针或迭代器链接在一起。当在序列的头部或尾部插入或删除元素时,只需要在相应的内存块上执行这些操作,因此这些操作的时间复杂度通常是常数时间。

(2)随机访问: std::deque 支持随机访问,这意味着可以通过下标操作符([])或迭代器直接访问序列中的任何元素。然而,需要注意的是,由于 std::deque 内部使用多个内存块,随机访问可能比连续内存容器(如 std::vector)慢一些。

(3)动态空间分配: 当 std::deque 的存储空间不足时,它会动态地分配新的内存块来存储元素。这允许 std::deque 在需要时增长,而无需预先分配大量内存。

(4)内存使用: 虽然std::deque在两端插入和删除元素时非常高效,但它通常比连续内存容器(如 std::vector)使用更多的内存,因为每个内存块可能只包含少量元素,并且可能存在未使用的内存块。

总体而言,std::deque 是一种适用于需要在两端进行高效插入和删除操作的场景的数据结构。然而,由于其内部使用多个内存块,随机访问和内存使用方面可能不如连续内存容器。

2 std::deque 的基本使用

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

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

#include <string>  
#include <deque>  

// 声明一个整数类型的链表  
std::deque<int> vals;

// 声明一个字符串类型的链表  
std::deque<std::string> strs;

// 声明一个自定义类型的链表  
struct MyStruct
{
	int id;
	std::string name;
};
std::deque<MyStruct> myStructs;

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

(1)默认初始化:
创建一个空的 std::deque 容器。

std::deque<int> vals; // 空的 deque

(2)使用初始化列表:
在声明时直接使用初始化列表来添加元素。

std::deque<int> vals = {1, 2, 3, 4, 5};

(3)使用迭代器或范围构造:
使用另一个容器的迭代器或范围来初始化 std::deque。

std::vector<int> vec = {1, 2, 3, 4, 5};
std::deque<int> vals(vec.begin(), vec.end());

(4)指定数量并填充默认值:
创建一个具有指定数量的元素,并使用默认构造函数生成这些元素。

std::deque<int> vals(10); // 创建一个包含 10 个默认构造的 int 的 deque

(5)指定数量和值填充:
创建一个具有指定数量的元素,并用给定的值填充它们。

std::deque<int> vals(5, 1); // 创建一个包含 5 个值为 1 的 int 的 deque

(6)拷贝构造:
使用另一个 std::deque 来初始化一个新的 std::deque。

std::deque<int> firstVals = {1, 2, 3};  
std::deque<int> secondVals(firstVals); // 拷贝构造

(7)移动构造:
使用另一个 std::deque 的移动语义来初始化一个新的 std::deque。

std::deque<int> firstVals = {1, 2, 3};  
std::deque<int> secondVals(std::move(firstVals)); // 移动构造

(8)运算符赋值:
使用 = 运算符将一个 std::deque 的内容赋给另一个 std::deque。

std::deque<int> firstVals = {1, 2, 3};  
std::deque<int> secondVals = firstVals; // 运算符赋值

2.2 std::deque 的大小与容量

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

  • empty(): 如果双端队列为空则返回 true。
  • size(): 返回双端队列中的元素数量。
  • max_size(): 返回双端队列可以容纳的最大元素数量。
  • resize(const size_type n): 调整双端队列的大小为n。
  • resize(const size_type n, const value_type& value): 调整双端队列的大小为 n,并用 value 填充新元素。

如下为样例代码:

#include <iostream>  
#include <deque>  

int main() 
{
	std::deque<int> myDeque;

	// 检查 deque 是否为空  
	if (myDeque.empty()) {
		std::cout << "Deque is empty." << std::endl;
	}

	// 添加元素到 deque  
	myDeque.push_back(1);
	myDeque.push_back(2);
	myDeque.push_front(0);

	// 获取 deque 的大小  
	std::cout << "Size of deque: " << myDeque.size() << std::endl;

	// 获取 deque 的最大可能大小  
	std::cout << "Max size of deque: " << myDeque.max_size() << std::endl;

	// 调整 deque 的大小  
	myDeque.resize(5); // 调整大小为 5,尾部的元素将被删除  
	std::cout << "After resizing to 5: ";
	for (const auto& element : myDeque) {
		std::cout << element << " ";
	}
	std::cout << std::endl;

	// 使用特定值填充并调整 deque 的大小  
	myDeque.resize(10, 99); // 调整大小为 10,并用 99 填充新元素  
	std::cout << "After resizing to 10 with value 99: ";
	for (const auto& element : myDeque) {
		std::cout << element << " ";
	}
	std::cout << std::endl;

	return 0;
}

上面代码的输出为:

Deque is empty.
Size of deque: 3
Max size of deque: 4611686018427387903
After resizing to 5: 0 1 2 0 0
After resizing to 10 with value 99: 0 1 2 0 0 99 99 99 99 99

在上面代码中,首先检查 myDeque 是否为空。然后向 myDeque 添加几个元素,并打印出其大小。接着打印出 myDeque 的最大可能大小(这通常是一个非常大的数字,取决于可用内存)。之后调整 myDeque 的大小到 5,尾部的元素被删除。最后再次调整 myDeque 的大小到 10,并用值 99 填充新添加的元素。

注意:std::deque 的 max_size() 方法通常返回的是一个非常大的数字,代表理论上的最大大小限制,这通常比实际可用的内存要大得多。在实际使用中,由于内存限制,可能无法接近这个理论上的最大值。

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

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

  • deque(): 默认构造函数,创建一个空的双端队列。
  • deque(const size_type n): 创建一个具有n个默认构造的元素的双端队列。
  • deque(const size_type n, const value_type& value): 创建一个具有 n 个元素,每个元素初始化为 value 的双端队列。
  • deque(const deque& other): 拷贝构造函数,创建一个与 other 相同内容的双端队列。
  • deque(deque&& other) noexcept: 移动构造函数,移动 other 中的元素到新的双端队列(C++11 新加入)。
  • deque(const_iterator first, const_iterator last): 用迭代器范围构造双端队列。
  • deque(InitializerList init): 使用初始化列表构造双端队列(C++11 新加入)。

析构函数

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

如下为样例代码:

#include <iostream>  
#include <deque>  
#include <initializer_list>  

int main() 
{
	// 使用默认构造函数创建一个空的双端队列  
	std::deque<int> emptyDeque;
	std::cout << "Empty deque size: " << emptyDeque.size() << std::endl;

	// 使用指定大小的构造函数创建一个具有5个默认构造的元素的双端队列  
	std::deque<int> dequeWithFiveInts(5);
	std::cout << "Deque with 5 ints: ";
	for (const auto& num : dequeWithFiveInts) {
		std::cout << num << " ";
	}
	std::cout << std::endl;

	// 使用大小和值构造函数创建一个具有5个元素,每个元素初始化为 12 的双端队列  
	std::deque<int> dequeWithValue(5, 12);
	std::cout << "Deque with 5 ints initialized to 12: ";
	for (const auto& num : dequeWithValue) {
		std::cout << num << " ";
	}
	std::cout << std::endl;

	// 使用拷贝构造函数创建一个与现有双端队列相同的双端队列  
	std::deque<int> copiedDeque(dequeWithValue);
	std::cout << "Copied deque: ";
	for (const auto& num : copiedDeque) {
		std::cout << num << " ";
	}
	std::cout << std::endl;

	// 使用移动构造函数创建一个新的双端队列,移动现有双端队列中的元素  
	std::deque<int> movedDeque(std::move(copiedDeque));
	std::cout << "Moved deque: ";
	for (const auto& num : movedDeque) {
		std::cout << num << " ";
	}
	std::cout << std::endl;

	// 使用迭代器范围构造双端队列  
	int arr[] = { 1, 2, 3, 4, 5 };
	std::deque<int> dequeFromArr(arr, arr + 5);
	std::cout << "Deque from array: ";
	for (const auto& num : dequeFromArr) {
		std::cout << num << " ";
	}
	std::cout << std::endl;

	// 使用初始化列表构造双端队列  
	std::deque<int> dequeFromList = { 6, 7, 8, 9, 10 };
	std::cout << "Deque from initializer list: ";
	for (const auto& num : dequeFromList) {
		std::cout << num << " ";
	}
	std::cout << std::endl;

	// 析构函数会在deque对象离开其作用域时自动调用  
	// 这里我们不需要显式地调用析构函数  

	return 0;
}

上面代码的输出为:

Empty deque size: 0
Deque with 5 ints: 0 0 0 0 0
Deque with 5 ints initialized to 12: 12 12 12 12 12
Copied deque: 12 12 12 12 12
Moved deque: 12 12 12 12 12
Deque from array: 1 2 3 4 5
Deque from initializer list: 6 7 8 9 10

上面代码展示了如何使用 std::deque 的不同构造函数来创建双端队列对象,并使用迭代器范围和初始化列表进行构造。每个双端队列在创建后都被打印出来以显示其内容。

注意:std::deque 的析构函数是自动调用的,当 deque 对象离开其作用域时,它会自动释放其分配的内存。上面代码没有显式调用析构函数,因为 C++ 的内存管理会自动处理这些事项。

3 std::deque 的元素操作

3.1 std::deque 元素的添加

std::deque 中与元素的添加相关的方法有如下几种:

  • void push_front(const value_type& value): 在双端队列的前端插入一个元素。
  • void push_back(const value_type& value): 在双端队列的尾端插入一个元素。
  • void emplace_front(const value_type& value): 在双端队列的前端插入一个元素,比 push_front 更高效(C++11 新加入)。
  • void emplace_back(const value_type& value): 在双端队列的尾端插入一个元素,比 push_back 更高效(C++11 新加入)。
  • iterator insert(const_iterator position, const value_type& value): 在指定位置插入一个元素。

如下为样例代码:

#include <iostream>  
#include <deque>  

int main() 
{
	// 创建一个空的双端队列  
	std::deque<int> myDeque;

	// 使用 push_front 在前端插入元素  
	myDeque.push_front(10);
	myDeque.push_front(20);

	// 使用 push_back 在尾端插入元素  
	myDeque.push_back(30);
	myDeque.push_back(40);

	// 使用 emplace_front 在前端就地构造并插入元素  
	myDeque.emplace_front(50);

	// 使用 emplace_back 在尾端就地构造并插入元素  
	myDeque.emplace_back(60);

	// 输出当前双端队列的内容  
	for (const auto& elem : myDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	// 使用 insert 在指定位置插入元素  
	// 假设我们想在第一个 20 之后插入一个元素 25  
	auto it = std::find(myDeque.begin(), myDeque.end(), 20);
	if (it != myDeque.end()) {
		++it; // 因为 find 返回的是第一个匹配的元素,我们需要插入在它之后  
		myDeque.insert(it, 25);
	}

	// 再次输出双端队列的内容以展示 insert 的效果  
	std::cout << "After inserting 25 after the first 20: ";
	for (const auto& elem : myDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	return 0;
}

上面代码的输出为:

50 20 10 30 40 60
After inserting 25 after the first 20: 50 20 25 10 30 40 60

在上面代码中,首先使用了 push_front 和 push_back 在双端队列的前端和尾端插入元素。接着,又使用 emplace_front 和 emplace_back 就地构造并插入元素,这通常比 push_front 和 push_back 更高效,因为它避免了额外的拷贝或移动操作。最后使用 insert 方法在指定位置插入一个元素(在第一个 20 之后的位置插入了 25)。

3.2 std::deque 元素的访问

std::deque 中与元素的访问相关的方法有如下几种:

  • []操作符: 随机访问。
  • at(size_type n): 随机访问,在索引无效时抛出 std::out_of_range 异常。
  • reference front(): 返回双端队列的前端元素的引用。
  • reference back(): 返回双端队列的尾端元素的引用。
  • const_reference front() const: 返回双端队列的前端元素的常量引用。
  • const_reference back() const: 返回双端队列的尾端元素的常量引用。
  • const_iterator begin() const: 返回指向双端队列第一个元素的迭代器。
  • const_iterator end() const: 返回指向双端队列尾端之后的迭代器。

如下为样例代码:

#include <iostream>  
#include <deque>  

int main() 
{
	// 创建一个包含一些元素的双端队列  
	std::deque<int> myDeque = { 10, 20, 30, 40, 50 };

	// 使用 [] 操作符访问元素  
	std::cout << "Element at index 2 (using []): " << myDeque[2] << std::endl;

	// 使用 at 方法访问元素,它在索引无效时抛出异常  
	try {
		std::cout << "Element at index 4 (using at): " << myDeque.at(4) << std::endl;
		std::cout << "Element at index 10 (using at): " << myDeque.at(10) << std::endl; // 这将抛出 std::out_of_range 异常  
	}
	catch (const std::out_of_range& e) {
		std::cout << "Caught exception: " << e.what() << std::endl;
	}

	// 使用 front 和 back 方法访问双端队列的前端和尾端元素  
	std::cout << "Front element: " << myDeque.front() << std::endl;
	std::cout << "Back element: " << myDeque.back() << std::endl;

	// 使用 const_reference 版本的 front 和 back 方法  
	const std::deque<int>& constDeque = myDeque;
	std::cout << "Front element (const): " << constDeque.front() << std::endl;
	std::cout << "Back element (const): " << constDeque.back() << std::endl;

	// 使用 const_iterator 的 begin 和 end 方法遍历双端队列  
	std::cout << "Deque elements (using const_iterator): ";
	for (const auto& elem : constDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	// 使用 begin 方法(非 const 版本)  
	std::deque<int>::iterator it = myDeque.begin();
	std::cout << "First element (using iterator): " << *it << std::endl;

	return 0;
}

上面代码的输出为:

Element at index 2 (using []): 30
Element at index 4 (using at): 50
Caught exception: invalid deque<T> subscript
Front element: 10
Back element: 50
Front element (const): 10
Back element (const): 50
Deque elements (using const_iterator): 10 20 30 40 50
First element (using iterator): 10

在上面代码中,展示了如何使用 std::deque 的各种访问方法来获取双端队列中的元素。代码的起始使用了 [] 操作符和 at 方法来随机访问元素,并处理了 at 方法可能抛出的 std::out_of_range 异常。之后展示了如何使用 front 和 back 方法来获取双端队列的前端和尾端元素,以及如何使用 const_reference 版本的这些方法。最后使用了 const_iterator 的 begin 和 end 方法来遍历双端队列的元素。

3.3 std::deque 元素的修改

std::deque 中与元素的修改相关的方法有如下几种:

  • []操作符: 随机访问并修改。
  • void assign(const_iterator first, const_iterator last): 替换双端队列中的元素,元素来自[first, last)范围内的迭代器。
  • void assign(size_type n, const value_type& value): 用 n 个值为 value 的元素替换双端队列中的元素。

如下为样例代码:

#include <iostream>  
#include <deque>  

int main() 
{
	// 创建一个包含一些元素的双端队列  
	std::deque<int> myDeque = { 10, 20, 30, 40, 50 };

	// 使用 [] 操作符随机访问并修改元素  
	myDeque[2] = 300; // 修改索引为2的元素  

	// 输出修改后的双端队列  
	std::cout << "After modifying element at index 2: ";
	for (const auto& elem : myDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	// 使用 assign 方法替换双端队列中的元素  
	// 创建一个新的迭代器范围  
	std::deque<int> newElements = { 60, 70, 80 };
	myDeque.assign(newElements.begin(), newElements.end());

	// 输出替换后的双端队列  
	std::cout << "After assigning new elements: ";
	for (const auto& elem : myDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	// 使用 assign 方法替换双端队列中的元素,使用固定数量和值  
	myDeque.assign(3, 90);

	// 输出再次替换后的双端队列  
	std::cout << "After assigning 3 elements with value 90: ";
	for (const auto& elem : myDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	return 0;
}

上面代码的输出为:

After modifying element at index 2: 10 20 300 40 50
After assigning new elements: 60 70 80
After assigning 3 elements with value 90: 90 90 90

在上面代码中,首先使用 [] 操作符来随机访问并修改双端队列中的一个元素。然后使用 assign 方法来替换双端队列中的元素:先用一个迭代器范围来替换元素,这个范围来自另一个 std::deque。然后使用 assign 方法来替换双端队列中的元素,这次指定了元素的数量和值。

3.4 std::deque 元素的删除

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

  • void pop_front(): 删除双端队列的前端元素。
  • void pop_back(): 删除双端队列的尾端元素。
  • iterator erase(const_iterator position): 删除指定位置的元素。
  • void clear(): 删除双端队列中的所有元素。

如下为样例代码:

#include <iostream>  
#include <deque>  

int main() 
{
	// 创建一个包含一些元素的双端队列  
	std::deque<int> myDeque = { 10, 20, 30, 40, 50 };

	// 输出原始双端队列  
	std::cout << "Original deque: ";
	for (const auto& elem : myDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	// 删除双端队列的前端元素  
	myDeque.pop_front();
	std::cout << "After pop_front(): ";
	for (const auto& elem : myDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	// 删除双端队列的尾端元素  
	myDeque.pop_back();
	std::cout << "After pop_back(): ";
	for (const auto& elem : myDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	// 删除指定位置的元素  
	auto it = myDeque.begin();
	std::advance(it, 2); // 移动到第三个元素  
	myDeque.erase(it);
	std::cout << "After erasing the third element: ";
	for (const auto& elem : myDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	// 删除双端队列中的所有元素  
	myDeque.clear();
	std::cout << "After clearing the deque: ";
	for (const auto& elem : myDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	return 0;
}

上面代码的输出为:

Original deque: 10 20 30 40 50
After pop_front(): 20 30 40 50
After pop_back(): 20 30 40
After erasing the third element: 20 30
After clearing the deque:

在上面代码中,首先创建了一个包含五个元素的双端队列。然后,逐步展示了如何使用 pop_front()、pop_back()、erase() 和 clear() 方法来删除元素。每次删除操作后,都遍历双端队列并输出其当前的内容,以便可以看到元素是如何被删除的。

3.5 std::deque 元素的遍历删除

在 std::deque 中遍历并删除元素需要小心处理,因为直接删除元素会导致迭代器失效。一种常见的方法是使用 std::deque 的 erase 方法结合 std::remove_if 算法来遍历并删除满足特定条件的元素。

如下为样例代码:

#include <iostream>  
#include <deque>  
#include <algorithm>  

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

	// 使用 std::remove_if 算法标记要删除的元素  
	auto it = std::remove_if(vals.begin(), vals.end(), [](int n) {
		return n % 2 == 0; // 假设要删除所有偶数  
		});

	// 使用 erase 方法删除标记过的元素  
	vals.erase(it, vals.end());

	// 输出结果  
	for (const auto& elem : vals) {
		std::cout << elem << ' ';
	}

	return 0;
}

上面代码的输出为:

1 3 5

在上面代码中,std::remove_if 将所有不被移除的元素移动到容器的前面,并返回一个指向第一个应该被移除的元素的迭代器。然后,erase 方法用于删除从该迭代器到 deque 实际结尾的所有元素。

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

如下为样例代码:

#include <iostream>  
#include <deque>  

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

	// 使用普通循环遍历 vector  
	auto it = vals.begin();
	while (it != vals.end()) {
		if (*it % 2 == 0) { // 假设要删除所有偶数  
			it = vals.erase(it); // 删除元素并更新迭代器  
		}
		else {
			++it; // 移动到下一个元素  
		}
	}

	// 输出结果  
	for (const auto& elem : vals) {
		std::cout << elem << ' ';
	}

	return 0;
}

上面代码的输出为:

1 3 5

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

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

4 std::deque 的迭代器

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

std::deque(双端队列)中与迭代器相关的方法有几种,主要包括:

  • begin(): 返回一个指向双端队列第一个元素的迭代器。
  • end(): 返回一个指向双端队列尾端之后位置的迭代器。它并不指向双端队列中的任何元素,而是用作一个哨兵值,表示序列的结束。
  • cbegin(): 返回一个指向双端队列第一个元素的常量迭代器。它不能被用来修改双端队列中的元素。
  • cend(): 返回一个指向双端队列尾端之后位置的常量迭代器。
  • rbegin(): 返回一个指向双端队列最后一个元素的逆向迭代器。
  • rend(): 返回一个指向双端队列首个元素之前位置的逆向迭代器。它是逆向序列的结束标记。
  • crbegin(): 返回一个指向双端队列最后一个元素的常量逆向迭代器。
  • crend(): 返回一个指向双端队列首个元素之前位置的常量逆向迭代器。

如下为样例代码:

#include <iostream>  
#include <deque>  

int main() 
{
	// 创建一个双端队列  
	std::deque<int> myDeque = { 1, 2, 3, 4, 5 };

	// 使用正向迭代器遍历双端队列  
	for (std::deque<int>::iterator it = myDeque.begin(); it != myDeque.end(); ++it) {
		std::cout << *it << " ";
	}
	std::cout << std::endl;

	// 使用常量迭代器遍历双端队列  
	for (const auto& elem : myDeque) {
		std::cout << elem << " ";
	}
	std::cout << std::endl;

	// 使用逆向迭代器遍历双端队列  
	for (std::deque<int>::reverse_iterator rit = myDeque.rbegin(); rit != myDeque.rend(); ++rit) {
		std::cout << *rit << " ";
	}
	std::cout << std::endl;

	return 0;
}

上面代码的输出为:

1 2 3 4 5
1 2 3 4 5
5 4 3 2 1

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

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

(1)迭代器的稳定性: std::deque 的迭代器是稳定的,这意味着只要容器不被修改,指向元素的迭代器就不会失效。然而,一旦对容器进行了修改(如插入或删除元素),指向被修改部分的迭代器就会失效。

(2)迭代器的失效: 当在 std::deque 的中间位置插入或删除元素时,指向被修改部分的迭代器、引用和指针都会失效。这意味着你不能依赖这些失效的迭代器来访问或修改容器中的元素。

(3)内存管理: std::deque 会自动管理其内存,包括元素的插入和删除。然而,迭代器本身并不管理内存。因此,当 std::deque 重新分配内存时,指向其元素的迭代器可能会失效。

(4)遍历与修改: 迭代器的主要用途是遍历容器并访问(甚至修改)其中的元素。然而,你不能使用迭代器来初始化一个空的 std::deque。

(5)尾部和头部操作: 在 std::deque 的尾部或头部插入或删除元素时,迭代器、引用和指针通常不会失效,除非它们正好指向被删除的元素。

(6)性能考虑: 虽然 std::deque 支持在两端快速插入和删除元素,但在中间位置进行这些操作可能会导致线性时间的复杂度。因此,在设计算法时,应尽量避免在中间位置进行频繁的插入和删除操作。

  • 31
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值