【C++语言】STL —— 六大组件

一、vector 中的常见方法介绍

  • reserve(20):vector中预留空间的,只给容器底层开辟执行大小的内存空间,并不会给容器进行添加新的元素。
  • resive(20) :容器扩容用的,不仅给容器底层开辟指定大小的内存空间,还会添加新的元素

二、容器的空间配置器的作用

       容器的空间配置器在STL容器中扮演着至关重要的角色,他负责为容器中的元素分配和释放内存空间。以下是空间配置器的一些作用和特点:

  1. 内存分配与释放:空间配置器通过 allocate()deallocate() 函数为容器分配所需内存,并在适当的时候释放内存。这样做可以将内存分配与对象构造分开,提高内存管理的灵活性和效率 。

  2. 对象构造与析构:空间配置器使用 construct()destroy() 函数来分别构造和析构对象。这种分离允许容器在不实际创建对象的情况下预先分配内存,从而提高性能 。

  3. 减少内存碎片:通过使用空间配置器,可以减少因频繁申请和释放小块内存而导致的内存碎片。STL中的一些实现采用内存池技术,预先分配一大块内存,然后从中分配小块内存给容器,有效避免了内存碎片的产生 。

  4. 提高运行效率:空间配置器通过减少系统调用的次数来提高程序的运行效率。通过预先分配大块内存并管理内存池,可以减少对操作系统内存分配器的依赖,从而减少系统调用,提高效率 。

  5. 支持自定义内存管理策略:STL允许用户通过自定义空间配置器来实现特定的内存管理策略,例如使用特定的内存池或者在内存分配时考虑线程安全等 。

  6. 容器与算法之间的桥梁:空间配置器通过迭代器与算法协作,使得算法能够通过迭代器访问容器中的元素,而不需要关心容器的底层实现细节 。

  7. 内存分配策略的抽象:空间配置器为容器提供了一个抽象的内存分配接口,这意味着容器不需要关心具体的内存分配细节,只需要通过配置器来请求所需的内存 。

  8. 内存对齐和空间利用率:空间配置器还考虑到内存对齐的问题,确保分配的内存满足特定类型对象的对齐要求,从而提高内存的使用效率 。

       总的来说,空间配置器是STL容器能够高效管理内存的关键组件,他通过提供灵活的内存分配和对象构造机制,帮助容器优化性能并减少资源浪费。

三、vector和deque之间的区别

vector特点:动态数组,内存是连续的,2倍的方式进行扩容(分清reserve(20) 和 resize(20))

扫描二维码关注公众号,回复: 17411653 查看本文章

deque特点:动态开辟的二维数组空间,第二维是固定长度的数组空间,扩容的时候(第一维的数组进行2倍扩容)

面经问题:deque底层内存是否是连续的?答案并不是。

vector和deque之间的区别??

  • 底层数据结构:
  • 前中后插入删除元素的时间复杂度:
  • 对于内存的使用效率:vector需要的内存空间必须是连续的,deque可以分块进行数据存储,不需要内存空间是连续的,可以利用碎片内存空间。
  • 向中间插入删除的时候,哪一个容器容易一些??vector要好一些,deque要差一些!!!由于deque的第二维内存空间不是连续的,所以在deque中间进行元素的insert或者erase,造成元素移动的时候比vector要慢。

四、vector和list之间的区别

底层数据结构:数组和双向循环链表

数组:增加删除 O(n) 查询 O(n) 随机访问 O(1)

链表:增加删除本身是O(1) 查询 O(n)

五、详解容器适配器(标准容器)

怎么理解这个适配器??

  • 适配器底层没有自己的数据结构,他是另外一个容器的封装,他的方法全部由底层依赖的容器进行实现的。大致代码如下:
template<typename T, typename Container=deque<T>> 
class Stack
{
public:
    void push(const T& val)
    {
        c.push_back(val);
    }
private:
    Container c;
};
  • 没有实现自己的迭代器

stack:push 入栈  pop  出栈  top  栈顶元素  empty 判断栈空  size 返回元素个数

stack => deque

queue:push 入队  pop  出队  front 查看队头元素  back 查看队尾元素 empty 判断队空  size 返回元素个数

queue => deque

为什么不依赖vector??

vector的初始效率太低了,没有deque好

对于queue来说,需要支持尾部插入和头部删除,O(1),如果queue依赖vector,其出队效率很低。

vector需要大片的连续的内存,而dequeue只需要分段的内存,当存储大量的数据时,小冉deque对于内存的利用更高。

priority_queue:push 入队  pop  出队  top  队顶元素  empty 判断队空  size 返回元素个数

priority_queue => vector 

为什么依赖vector??

底层默认把数据组成一个大根堆结构。

六、无序关联容器

常用的增删查方法:

增加:insert

遍历:iterator 

调用find成员方法

删除:erase(key)  erase(it)

七、有序关联容器

八、迭代器iterator

容器的迭代器

iterator:正向迭代器,从第一个元素访问到最后一个元素,可读可写

const_iterator:只能读,不能写

为什么const_iterator可以使用iterator??

class const_iterator
class iterator : public const_iterator

reverse_iterator:返回的是最后一个元素的反向迭代器表示

rend:返回的是首元素前驱位置的迭代器的表示

const_reverse_iterator:只能读,不能写

九、函数对象

       函数对象就是C语言的函数指针。把有operator()小括号进行重载的类,仿函数或者函数对象。

       通过函数指针调用函数,是没有办法内联的,效率很低,因为有函数调用开销。

       为了解决上述问题,C++提出了函数对象,是可以进行内联,通过函数对象调用 operator() ,可以省略函数的调用开销。因为函数对象是用来生成的,所以可以添加相关的成员变量,用来记录函数赌侠你给使用时的更多信息。

template<class T>
class mygreater
{
public:
    bool operator () (T a, T b)
    {
        return a > b;
    }
};

我们可以来看一看priority_queue底层的代码:

 

十、泛型算法和绑定器

10.1 绑定器

bind1st和bind2nd
今天,我们讲一讲bind1st和bind2nd。两者在C++11中均已不推荐使用,C++17中已经移除。

10.2 泛型算法 

  • 特点一:泛型算法的参数接收的都是迭代器
  • 特点二:泛型算法的参数还可以接收函数对象(C函数指针) 

10.2.1 sort 排序 和 unique 去重算法

       sort函数即从小到大进行排序,也可以自己传入一些函数进行排序。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main(){
    vector<int> v = {1, 5, 2, 1, 4, 3, 3, 5};
    sort(v.begin(), v.end());
    auto end_unique = unique(v.begin(), v.end());
    v.erase(end_unique, v.end());
    for(int i : v){
        cout << i << ' ';
    }
    return 0;
}

10.2.2 binary_search 二分查找

10.2.3 find_if

       该函数的主要功能是找到第一个小于 val 的数字,需要的是一元函数对象,但是C++中的greater和less都是二元函数对象,因此我们需要将二元函数进行绑定,生成一个一元函数对象。

10.2.4 for_each

       该函数的主要功能是可以遍历容器的所有元素,可以自行添加合适的函数对象对容器元素进行过滤。也是需要使用一元函数对象,我们可以使用lambda表达式来进行。

10.2.5 accumulate

这个函数是一种只读算法,用于累加序列中的元素,代码实例如下:

#include <iostream>
#include <vector>
#include <numeric>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4, 5};
    int sum = accumulate(v.begin(), v.end(), 0);
    cout << "Sum is " << sum << endl;
    return 0;
}

10.2.6 equal 

这个函数是一种只读算法,用于判断两个序列是否相等,代码实例如下:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v1 = {1, 2, 3, 4, 5};
    vector<int> v2 = {1, 2, 3, 4, 5};
    if(equal(v1.begin(), v1.end(), v2.begin()))
        cout << "The two vectors are equal." << endl;
    else
        cout << "The two vectors are not equal." << endl;
    return 0;
}

10.2.7 fill 和 fill_n 

这是一种修改容器的算法,用于填充容器的元素,代码实例如下:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v(5);
    fill(v.begin(), v.end(), 1); // fill all elements with 1
    for(int i : v)
        cout << i << " ";
    cout << endl;
    fill_n(v.begin(), 3, 2); // fill the first 3 elements with 2
    for(int i : v)
        cout << i << " ";
    return 0;
}

10.2.8 copy

这个是复制算法,将输入范围中的元素复制到目的序列中,会进行覆盖

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main(){
    vector<int> v1 = {1, 2, 3, 4, 5};
    vector<int> v2(5);
    copy(v1.begin(), v1.end(), v2.begin());
    for(int i : v2)
        cout << i << " ";
    return 0;
}