STL面试题

一、讲讲STL的六大组件

1、容器:存放数据的各种数据结构,STL 容器是⼀种 class template

2、迭代器:为了访问容器中的元素,是一种泛型指针,迭代器是⼀种将 operator *, operator ->, operator++, operator-- 等指针相关操作予以重载的class template

3、算法:可以操作容器中的元素,如 sort、search、copy,STL 算法是⼀种 function template

4、适配器:容器适配器(stack、queue)、算法适配器(mem_fn)、迭代器适配器(插入迭代器)

5、函数对象(仿函数):仿函 数是⼀种重载了 operator() 的 class 或class template

6、空间适配器:负责空间的申请与释放,实现了动态空间配置、空间管理、空间释放的 class template

二、vector 及其底层原理

vector 是封装了动态大小数组的顺序容器,它能够存放各种类型的对象,可以简单的认为,vector是一个能够存放任意类型的动态数组

vector采用线性连续空间,以两个迭代器 startfinish 分别指向配置得来的连续空间中目前已被使用的空间 ,迭代器 end_of_storage 指向整块连续空间(含备用空间)的尾端。

vector在调⽤ push_back 插⼊新元素的时候,⾸先会检查是否有备⽤空间,如果有就直接在备⽤空间上构造元素,并调整迭代器 finish ,如果超过自身最大的容量,vector 则将自身的容量扩充为原来的两倍 (重新配置空间、元素移动、释放旧的空间)

内存扩充操作:如果原空间⼤⼩为 0 则分配 1 个元素,如果⼤于 0 则分配原空间两倍的新空间,然后把数据拷⻉过去

三、vector迭代器失效问题

迭代器底层就是一个指针,迭代器失效就是指迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃

造成迭代器失效的操作:resize、insert、erase、push_back 等会引起底层空间改变的操作

vector<int> vec = {1, 2, 3, 4, 5};
vector<int>::iterator it;

for(it = vec.begin(); it != vec.end(); it++){
    if(*it > 2){
        vec.erase(it);
    }

当前元素的iterator被删除后,其后的所有元素的迭代器都会失效,因为对其进行erase操作时,其后的每一个元素都会向前移一个位置,已经失效的迭代器不能进行++操作,所以程序中断了

解决:所以只要我们每次执行删除操作的时候,将下一个有效迭代器返回就可以顺利执行后续操作了

vector<int> vec = {1, 2, 3, 4, 5};
vector<int>::iterator it;

for(it = vec.begin(); it != vec.end(); ){
    if(*it > 2){
        it = vec.erase(it);
    }
    it++;

凡是涉及到扩容操作,都有可能引起迭代器失效,因为vector扩容是分配一个新的数组,然后全部元素移到新的数组中

vector<int> vec = { 1, 2, 3, 4, 5 };
    vector<int>::iterator it = vec.begin();

    while (it != vec.end()) {
        if (*it % 2 == 0) {
            it = vec.insert(it, 66);
            it++;
        }
        it++;
    }

在使用push_back对vector进行构造的时候,vector的容量capacity(与size有区别)会根据压入元素的数量进行内存的自动重新分配,这时候iterator会因为vector存储空间的变化而失效,要在每次

扩容后更新 begin 迭代器的位置

void test2() {
    
    vector<int> vec;
    cout << "容量" << vec.capacity() << "大小" << vec.size() << endl;

    vector<int>::iterator it;

    int i = 0;
    for (i = 0; i < 1000; i++) {
        vec.push_back(i);
        
        it = vec.begin();    //更新迭代器的位置
    }

    cout << "容量" << vec.capacity() << "大小" << vec.size() << endl;
}

四、push_back 和 emplace_back 的区别

push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。

 五、空间配置器

在 C++ ⾥,当我们调⽤ new 和 delete 进⾏对象的创建和销毁的时候,也同时会有内存配置操作和释放操作

为了精密分⼯,STL allocator 决定将这两个阶段操作区分开来。

- 对象构造由 ::construct() 负责;对象析构由 ::destroy() 负责。

- 内存配置由 alloc::allocate() 负责;内存释放由 alloc::deallocate() 负责;

1、构造与析构

在 STL ⾥⾯,construct() 函数接受⼀个指针 P 和⼀个初始值 value,该函数的⽤途就是将初 值设定到指针所指的空间上。

destroy() 函数有两个版本,第⼀个版本接受⼀个指针,准备将该指针所指之物析构掉,第⼆个版本接受 first 和 last 两个迭代器,将[first,last)范围内的所有对象析构掉

#include <new.h>  //使用placement new
template <class T1,class T2>
inline void construct(T1*p,const T2& value ) {
    new (p)T1( value);  // placement new; 调用T1::T1( value )
}

注:placement new 就是在用户指定的内存位置上构建新的对象,这个过程不需要额外分配内存

template <class T>
inline void destroy (T* pointer) {
    pointer->~T( );  //调用dtor ~T( )
}

2、内存的配置与释放

 

posted @ 2023-04-03 23:53  晚安地球人1  阅读(335)  评论(0)    收藏  举报