6 STL【deque介绍】【deque创建/成员函数/迭代器/底层实现原理】【访问deque内元素】【deque元素添加/删除】

0 - 前言

参考:C++ STL deque容器(详解版)

1 - deque是什么

deque 是 double-ended queue 的缩写,又称双端队列容器。

deque 容器和 vecotr 容器有很多相似之处,比如:

  • deque 容器也擅长在序列尾部添加或删除元素(时间复杂度为O(1)),而不擅长在序列中间添加或删除元素。
  • deque 容器也可以根据需要修改自身的容量和大小。

和 vector 不同的是,deque 还擅长在序列头部添加或删除元素,所耗费的时间复杂度也为常数阶O(1)。并且更重要的一点是,deque 容器中存储元素并不能保证所有元素都存储到连续的内存空间中。当需要向序列两端频繁的添加或删除元素时,应首选 deque 容器。

deque 容器以模板类 deque(T 为存储元素的类型)的形式在 头文件中,并位于 std 命名空间中。因此,在使用该容器之前,代码中需要包含下面两行代码:

#include <deque>
using namespace std;

2 - 创建deque

//创建一个没有任何元素的空 deque 容器
deque<int> d;
//创建一个具有 n 个元素的 deque 容器,其中每个元素都采用对应类型的默认值,(默认都为 0)
deque<int> d(10);
//创建一个具有 n 个元素的 deque 容器,并为每个元素都指定初始值
deque<int> d(10, 5);
//在已有 deque 容器的情况下,可以通过拷贝该容器创建一个新的 deque 容器
deque<int> d1(5);
deque<int> d2(d1);//采用此方式,必须保证新旧容器存储的元素类型一致
//通过拷贝其他类型容器中指定区域内的元素(也可以是普通数组),可以创建一个新容器
int a[] = {
    
     1,2,3,4,5 };//拷贝普通数组,创建deque容器
deque<int>d(a, a + 5);

array<int, 5>arr{
    
     11,12,13,14,15 };//适用于所有类型的容器
deque<int>d(arr.begin()+2, arr.end());//拷贝arr容器中的{13,14,15}

3 - deuqe成员函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TI0my3Pw-1618389321116)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210414151515807.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3PtfvBMf-1618389321119)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210414151536168.png)]

和 vector 相比,额外增加了实现在容器头部添加和删除元素的成员函数,同时删除了 capacity()、reserve() 和 data() 成员函数。

deque 容器还有一个std::swap(x , y) 非成员函数(其中 x 和 y 是存储相同类型元素的 deque 容器),它和 swap() 成员函数的功能完全相同,仅使用语法上有差异。

4 - deque迭代器

cbegin()/cend() 成员函数和 begin()/end() 唯一不同的是,前者返回的是 const 类型的正向迭代器,这就意味着,由 cbegin() 和 cend() 成员函数返回的迭代器,可以用来遍历容器内的元素,也可以访问元素,但是不能对所存储的元素进行修改。

deque 模板类中还提供了 rbegin() 和 rend() 成员函数,它们分别表示指向最后一个元素和第一个元素前一个位置的随机访问迭代器,又常称为反向迭代器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FFQaDHrY-1618389321120)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210414161215403.png)]

需要注意的是,在使用反向迭代器进行 ++ 或 – 运算时,++ 指的是迭代器向左移动一位,-- 指的是迭代器向右移动一位,即这两个运算符的功能也“互换”了。

迭代器的功能是遍历容器,在遍历的同时可以访问(甚至修改)容器中的元素,但迭代器不能用来初始化空的 deque 容器。对于空的 deque 容器来说,可以通过 push_back()、push_front() 或者 resize() 成员函数实现向(空)deque 容器中添加元素。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    
    
    vector<int>values;
    auto first = values.begin();
    //*first = 1;非法操作
    return 0;
}

除此之外,当向 deque 容器添加元素时,deque 容器会申请更多的内存空间,同时其包含的所有元素可能会被复制或移动到新的内存地址(原来占用的内存会释放),这会导致之前创建的迭代器失效。

#include <iostream>
#include <deque>
using namespace std;
int main()
{
    
    
    deque<int>d;
    d.push_back(1);
    auto first = d.begin();
    cout << *first << endl;
    
    d.push_back(1);//添加元素,会导致 first 失效
    cout << *first << endl;
    return 0;
}

上面程序运行崩溃的原因在于,在创建 first 迭代器之后,deque 容器做了添加元素的操作,导致 first 失效

在对容器做添加元素的操作之后,如果仍需要使用之前以创建好的迭代器,为了保险起见,一定要重新生成。

5 - deuqe底层实现原理

参考:C++ STL deque容器底层实现原理(深度剖析)

6 - 访问deque内的元素

和 array、vector 容器一样,可以采用普通数组访问存储元素的方式,访问 deque 容器中的元素,比如:

deque<int>d{
    
     1,2,3,4 };
cout << d[1] << endl;

容器名[n]的这种方式,不仅可以访问容器中的元素,还可以对其进行修改。但需要注意的是,使用此方法需确保下标 n 的值不会超过容器中存储元素的个数,否则会发生越界访问的错误。

如果想有效地避免越界访问,可以使用 deque 模板类提供的 at() 成员函数,由于该函数会返回容器中指定位置处元素的引用形式,因此利用该函数的返回值,既可以访问指定位置处的元素,如果需要还可以对其进行修改。

deque<int>d{
    
     1,2,3,4 };
cout << d.at(1) << endl;

deque 容器还提供了 2 个成员函数,即 front() 和 back(),它们分别返回 vector 容器中第一个和最后一个元素的引用,通过利用它们的返回值,可以访问(甚至修改)容器中的首尾元素。

注意,和 vector 容器不同,deque 容器没有提供 data() 成员函数,同时 deque 容器在存储元素时,也无法保证其会将元素存储在连续的内存空间中,因此尝试使用指针去访问 deque 容器中指定位置处的元素,是非常危险的。

deque 容器迭代器常用来遍历容器中存储的各个元素。因为deque的迭代器属于最高级的随机访问迭代器,因此遍历deque有三种方式:

deque<int>d{
    
    1,2,3,4,5};
//遍历方式 ①
for(i = d.begin(); i != d.end(); ++i)
    cout << *i;

//遍历方式②:双向迭代器不支持用“<”进行比较
for(i = d.begin(); i < d.end(); ++i)
    cout << *i;

//遍历方式③:双向迭代器不支持用下标随机访问元素
for(int i = 0; i < d.size(); ++i)
    cout << d[i];

7 - deque添加/删除元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J8zrrKq3-1618389321124)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210414162637964.png)]

在实际应用中,常用 emplace()、emplace_front() 和 emplace_back() 分别代替 insert()、push_front() 和 push_back()

#include <deque>
#include <iostream>
using namespace std;
int main()
{
    
    
    deque<int>d;
    //调用push_back()向容器尾部添加数据。
    d.push_back(2); //{2}
    //调用pop_back()移除容器尾部的一个数据。
    d.pop_back(); //{}
    //调用push_front()向容器头部添加数据。
    d.push_front(2);//{2}
    //调用pop_front()移除容器头部的一个数据。
    d.pop_front();//{}
    //调用 emplace 系列函数,向容器中直接生成数据。
    d.emplace_back(2); //{2}
    d.emplace_front(3); //{3,2}
    //emplace() 需要 2 个参数,第一个为指定插入位置的迭代器,第二个是插入的值。
    d.emplace(d.begin() + 1, 4);//{3,4,2}
    for (auto i : d) {
    
    
        cout << i << " ";
    }
    //erase()可以接受一个迭代器表示要删除元素所在位置
    //也可以接受 2 个迭代器,表示要删除元素所在的区域。
    d.erase(d.begin());//{4,2}
    d.erase(d.begin(), d.end());//{},等同于 d.clear()
    return 0;
}

这里重点讲一下 insert() 函数的用法。insert() 函数的功能是在 deque 容器的指定位置插入一个或多个元素。该函数的语法格式有多种

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-axNajEAR-1618389321125)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210414163133671.png)]

#include <iostream>
#include <deque>
#include <array>
using namespace std;
int main()
{
    
    
    std::deque<int> d{
    
     1,2 };
    //第一种格式用法
    d.insert(d.begin() + 1, 3);//{1,3,2}
    //第二种格式用法
    d.insert(d.end(), 2, 5);//{1,3,2,5,5}
    //第三种格式用法
    std::array<int, 3>test{
    
     7,8,9 };
    d.insert(d.end(), test.begin(), test.end());//{1,3,2,5,5,7,8,9}
    //第四种格式用法
    d.insert(d.end(), {
    
     10,11 });//{1,3,2,5,5,7,8,9,10,11}
    for (int i = 0; i < d.size(); i++) {
    
    
        cout << d[i] << " ";
    }
    return 0;
}

end());//{1,3,2,5,5,7,8,9}
//第四种格式用法
d.insert(d.end(), { 10,11 });//{1,3,2,5,5,7,8,9,10,11}
for (int i = 0; i < d.size(); i++) {
cout << d[i] << " ";
}
return 0;
}


Guess you like

Origin blog.csdn.net/weixin_44484715/article/details/115702570