4. Contenedor secuencial

¿Qué es un contenedor?

Los contenedores containerson plantillas de clase que se utilizan para almacenar datos, encapsulando varios métodos de organización de datos. Hay dos tipos de contenedores en STL, contenedores secuenciales y contenedores asociativos, como se muestra en la siguiente figura.

inserte la descripción de la imagen aquí


Los contenedores secuenciales incluyen principalmente vector、list y dequecontenedores, que se caracterizan porque la posición del elemento en el contenedor no tiene nada que ver con el valor del elemento, es decir, los elementos están desordenados en el contenedor, y cuando el elemento está insertado en el contenedor, la posición del elemento se encuentra en la posición especificada.
Los contenedores asociativos incluyen principalmente set, y , que se caracterizan porque los elementos del contenedor se ordenan automáticamente, y la posición donde se inserta el elemento no tiene nada que ver con la posición del elemento en el contenedor multiset. Este artículo primero presenta los detalles clave de implementación de varios contenedores secuenciales, y el principio de funcionamiento de los contenedores asociativos se presentará en el siguiente artículo.mapmultimap

list

listContenedor, también conocido como contenedor de lista doblemente enlazada, la capa inferior del contenedor se implementa en forma de lista doblemente enlazada, que se caracteriza porque los elementos que contiene pueden almacenarse listen un espacio de memoria disperso, en lugar de almacenarse en un espacio de memoria continuo.
¿Por qué hablar primero de contenedores listy no vectorde contenedores? La razón es que en el último artículo sobre iteradores, inicialmente implementamos un listiterador iterator, y STLpodemos aprender mejor el conocimiento del iterador comparándolo con la implementación de iterador.

lista de nodos

listLa capa inferior del contenedor se implementa en forma de lista doblemente enlazada, por lo que la estructura de datos de la capa inferior se list nodedefine de la siguiente manera:

// list node
template <class T>
struct __list_node
{
    
    
    typedef voide* void_pointer;
    void_pointer prev;
    void_pointer next;
    T data;
};

listar iterador exclusivo

A través del análisis del iterador en el artículo anterior, sabemos que el iterador es uno smart poiner, es decir, tiene la capacidad de realizar incrementos, decrementos, valores, acceso a miembros y otras operaciones correctas; para permitir un iterator traitstrabajo efectivo, el iterador debe definirse en un nested typedeftipo incrustado Defina cinco tipos comunes de información. Por lo tanto, el diseño del iterador se divide en las siguientes dos partes:

  • smart pointer
typedef __list_node<T>*                 link_type;
link_type node; 			// 迭代器内部指针, 指向list节点
// 实现递增、递减、取值和成员访问等运算符重载
  • cinco tipos de información
typedef T                               value_type;
typedef Ref                             reference;
typedef Ptr                             pointer;
typedef ptrdiff_t                       difference_type;
typedef bidirectional_iterator_tag      iterator_category;

listEl diseño del iterador es el siguiente:

// list node的专属迭代器
template <class T, class Ref, class Ptr>
struct __list_iterator
{
    
    
    typedef __list_iterator<T, T&, T*>      iterator;
    typedef __list_iterator<T, Ref, Ptr>    self;
    typedef __list_node<T>*                 link_type;
    typedef size_t                          size_type;

    typedef T                               value_type;
    typedef Ref                             reference;
    typedef Ptr                             pointer;
    typedef ptrdiff_t                       difference_type;
    typedef bidirectional_iterator_tag      iterator_category;

    link_type node; 

    // constructor
    __list_iterator(link_type x) : node(x) {
    
    }
    __list_iterator()   {
    
    }
    __list_iterator(const iterator& x) : node(x.node) {
    
    }

    bool operator== (const self& x) const {
    
    return node == x.node;}
    bool operator!= (const self& x) const {
    
    return node != x.node;}
    // 对迭代器取值(derefence), 取的是节点的数据值
    reference operator* () const {
    
    return (*node).data;}
    // 对迭代器的成员进行访问
    pointer operator-> () const {
    
    return &(operator*());}
    // 对迭代器累加1,  前进一步
    self& operator++ ()
    {
    
    
        node = (link_type)((*node).next);
        return *this;
    }
    self operator++(int)
    {
    
    
        self tmp = *this;	// 调用拷贝构造函数
        ++*this;
        return tmp;			// 调用拷贝构造函数
    }
    // 对迭代器递减1, 就是后退一个节点
    self& operator--()
    {
    
    
        node = (link_type)((*node).prev);
        return *this;
    }
    self operator--(int)
    {
    
    
        self tmp = *this;
        --*this;
        return tmp;
    }
};

En el iterador de lista, apunta al siguiente nodo cuando se incrementa, y apunta al nodo anterior cuando se decrementa, y obtiene el valor de los datos del nodo cuando obtiene el valor, como se muestra en la siguiente figura:

inserte la descripción de la imagen aquí


El proceso de trabajo de la operación de incremento posterior es el siguiente: primero, se llama al constructor de copia para registrar el valor original, luego se incrementa el iterador en uno y, finalmente, se devuelve el valor original del registro.

inserte la descripción de la imagen aquí

operator->El principio de funcionamiento del iterador se introdujo en el artículo anterior sobre iteradores. En pocas palabras, operator->cuando el valor de retorno de es un puntero, el operando derecho es un miembro del tipo de datos de ejecución del puntero.

inserte la descripción de la imagen aquí

Lista de gestión de memoria

De forma predeterminada, alloc se usa como el configurador de espacio en la lista y se define list_node_allocator en consecuencia para asignar memoria con el tamaño del nodo.

template <class T, class Alloc = alloc>
class list
{
    
    
protected:
    typedef __list_node<T> list_node;
    // 空间配置器, 每次配置一个节点大小
    typedef simple_alloc<list_node, Alloc> list_node_allocator;
	...
}

La implementación de configurar, liberar, construir y destruir un nodo es la siguiente:

// 给一个list节点分配内存, 返回一个list节点指针
link_type get_node() {
    
    return list_node_allocator::allocate();}
// 释放一个list节点内存
void put_node(link_type p) {
    
    list_node_allocator::deallocate(p);}
// 产生一个节点, 包括节点内存分配和调用构造函数
link_type create_node(const T& x)
{
    
    
    link_type p = get_node();
    construct(&p->data, x); // 全局函数, 构造对象
    return p;
}
// 销毁(析构并释放)一个节点
void destroy_node(link_type p)
{
    
    
    destroy(&p->data);  // 全局函数, 析构对象
    put_node(p);
}

La estructura de datos de la lista.

SGI listNo solo una lista doblemente enlazada, sino también una lista circular doblemente enlazada. Solo se necesita un puntero para representar completamente la lista enlazada completa, por lo que listsolo hay una variable de miembro de puntero en .

// 对list node数据进行处理
template <class T, class Alloc = alloc>
class list
{
    
    
protected:
    typedef __list_node<T> list_node;
    // 空间配置器, 每次配置一个节点大小
    typedef simple_alloc<list_node, Alloc> list_node_allocator;
public:
    typedef list_node*  link_type;

protected:
    link_type node;     // 只要一个指针, 便可表示整个环状双向链表
	...
}

En STL, el nodo variable de puntero de miembro apunta a un nodo en blanco (nodo centinela) para cumplir con el requisito de intervalo de "cerrar antes y abrir después". Entre ellos, el nodo en blanco se construirá llamando a la función empty_initialize en cada constructor. Tomando el constructor predeterminado como ejemplo, el código es el siguiente:

public:
    // default constructor
    list() {
    
    empty_initialize();}    // 产生一个空链表
protected:
    void empty_intialize()
    {
    
    
        node = get_node(); // 配置一个节点空间, 令成员node指向它
        node->next = node;  // 令node的头尾都指向自己,不设元素值
        node->prev = node;
    }

La estructura del nodo en blanco es la siguiente:

inserte la descripción de la imagen aquí

El diagrama de la lista es el siguiente:

inserte la descripción de la imagen aquí


Después de que el puntero del nodo de la variable miembro de la lista se apunte deliberadamente a un puntero en blanco, las siguientes funciones se pueden completar fácilmente:

iterator begin() {
    
     return (link_type)((*node).next); }
iteator end()    {
    
     return node; }

bool empty() const {
    
    return node->next == node;}
size_type size() const
{
    
    
    size_type result = 0;
    distance(begin(), end(), result);   // 全局函数
    return result;
}
// 取头节点的元素值
reference front() {
    
    return *begin();}
// 取为节点的元素值
reference back() {
    
    return *(--end());}

Hay tres funciones miembro muy importantes en la lista: insertar, borrar y transferir.Muchas funciones miembro se implementan llamando a estas tres funciones.

función de inserción

insertEs una función sobrecargada con varias formas, su función es insertar un nodo delante de la posición señalada por el iterador dado. Es importante tener en cuenta que la posición en la que se inserta el nuevo nodo está delante del nodo al que apunta el iterador dado. insertLa implementación del código es la siguiente:

// insert函数: 在迭代器position所指位置的前方插入一个节点, 内容为x
iterator insert(iteartor postion, const T& x)
{
    
    
    link_type tmp = create_node(x); 
    // 调整双向指针, 将tmp插入进去
    tmp->next = position.node;                      // 1
    tmp->prev = position.node->prev;                // 2
    (link_type(position.node->prev))->next = tmp;   // 3
    position.node->prev = tmp;                      // 4
    return tmp;     // iterator(tmp)
}

Inserte un nodo, el proceso de conversión del puntero se muestra en la figura a continuación, la figura a continuación se refiere al iterador de entrada end(), inserte un nodo, puede ver que el puntero anterior del nodo al que apunta end() apunta a esto nuevo nodo, también Es decir, el nodo insertado está delante del nodo al que apunta el iterador dado.

inserte la descripción de la imagen aquí

La siguiente función completa internamente la operación llamando a la función de inserción.

    // ************** 调用insert函数 ************** //
    // 插入一个节点, 作为头节点
    void push_front(const T& x) {
    
    insert(begin(), x);}
    // 插入一个节点, 作为尾节点
    void push_back(const T& x) {
    
    insert(end(), x);}
    // ****************************************** //

función de borrado

eraseLa función de la función es eliminar el nodo apuntado por el iterador dado.

// erase函数: 移除迭代器position所指位置的节点, 并返回下一个节点
iterator erase(iterator position)
{
    
    
    link_type next_node =  (link_type)(positon.node->next);     // 1
    link_type prev_node = (link_type)(position.node->prev);     // 2
    prev_node->next = next_node;                                // 3
    next_node->prev = prev_node;                                // 4
    destroy_node(position.node);                                // 5
    return iterator(next_node);
}

El flujo de la operación de eliminación listde un nodo se muestra en la siguiente figura:

inserte la descripción de la imagen aquí

la siguiente función erasese usa para completar la operación llamando a la función.

    // ************* 调用erase函数 ************* //
    // 移除节点
    void pop_front() {
    
    erase(begin());}
    // 移除尾节点
    void pop_back()
    {
    
    
        iterator tmp = end();
        erase(--tmp);
    }
    // 清除所有节点
    void clear()
    {
    
    
        link_type cur = (link_type) node->next;  // begin()
        while (cur != node) // begin() != end()
        {
    
    
            link_type tmp = cur;
            cur = (link_type) cur->next;
            destroy_node(tmp) ;  // 销毁一个节点
        }
        // 恢复node原始状态
        node->next = node;
        node->prev = node;
    }
    // 将数值为value的所有元素移除
    void remove(const T& value)
    {
    
    
        iterator first = begin();
        iterator last = end();
        while(first != last)
        {
    
    
            iterator next = first;
            ++next;
            if(*first == value) erase(first); // 找到就移除
            first = next;
        }
    }
    // 移除数值相同的连续元素
    // “连续并且相同的元素”才会被移除剩一个
    void unique()
    {
    
    
        iterator first = begin();
        iterator last = end();
        if(first == last) return;  // 空链表, 什么都不必做
        iterator next = first;
        while(++next != last)
        {
    
    
            if(*first == *next) // 如果在此区段中有相同的元素
                erase(next);
            else
                first = next;
            next = first;
        }
    }   
    // ************************************* //
};

función de transferencia

transferLa función es listuna operación de migración proporcionada internamente, y su función es migrar elementos de un rango continuo a una posición específica. transferEl código fuente es el siguiente:

    // 将[first, last)内所有元素移动到position之前
    void transfer(iterator position, iterator first, iterator last)
    {
    
    
        if(position != last)
        {
    
    
            (*(link_type((*last.node).prev))).next = position.node;  // 1
            (*(link_type((*first.node).prev))).next = last.node;     // 2
            (*(link_type((*position.node).prev))).next = first.node;     // 3
            link_type tmp = link_type((*position.node).prev);           // 4
            (*position.node).prev = (*last.node).prev;                  // 5
            (*last.node).prev = (*first.node).prev;                     // 6
            (*first.node).prev = tmp;                                   // 7
        }
    }

Contiene un total de 7 pasos, como se muestra en la siguiente figura:

inserte la descripción de la imagen aquí

transferla función no es stluna interfaz pública, sino spliceuna función. Las siguientes son splicevarias versiones de la función:

    // splice函数
    // 将另一个list结合在position所指位置之前
    void splice(iterator position, list& x)
    {
    
    
        if(!x.empty())
            transfer(position, x.begin(), x.end());
    }
    // 将i所指节点接合于position所指节点之前, position和i可以指向同一个list
    void splice(iterator position, list&, iterator i)
    {
    
    
        iterator j = i;
        ++j;
        if(position==i || position==j) return;
        transfer(position, i, j);
    }
    // 将[first, last)内所有元素结合于position所指节点之前
    // position和[first, last)可指向同一个list
    // 但是position不能位于[first, last)之内
    void splice(iterator position, list&, iterator first, iterator last)
    {
    
    
        if(first != last)
            transfer(position, first, last);
    }

Además, sortlas funciones mergey reversese completan llamando funciones internamente transfer, por lo que no entraré en detalles aquí.

vector

vectorEl arreglo de datos y el método de operación son arraymuy similares a . La única diferencia es que arrayes un espacio estático, y el tamaño del espacio no se puede cambiar una vez que se configura, mientras que el vector es un espacio dinámico. A medida que se agregan elementos, su espacio interno el espacio se expandirá automáticamente para acomodar nuevos elementos. , la siguiente figura muestra la diferencia entre ellos muy vívidamente.

inserte la descripción de la imagen aquí

elemento vectorial

vectortemplate class, el tipo de elemento almacenado en él se especifica al crear el objeto, como vector<int>, vector<string>etc., y vectorel tipo de elemento almacenado en el contenedor es into respectivamente string. vectorSolo los elementos del mismo tipo se pueden almacenar en un contenedor.

iterador de vectores

El iterador exclusivo del vector, de hecho, solo usa el puntero del tipo de datos correspondiente del elemento del vector , ¿por qué dices eso? Primero podemos escribir un iterador vectorial como el iterador de la lista, el código es el siguiente:

template <class T, class Ref, class Ptr>
struct __vector_iterator
{
    
    
    typedef __vector_iterator<T, T&, T*>      iterator;
    typedef __vector_iterator<T, Ref, Ptr>    self;
    typedef T*                              link_type;
    typedef size_t                          size_type;

    typedef T                               value_type;
    typedef Ref                             reference;
    typedef Ptr                             pointer;
    typedef ptrdiff_t                       difference_type;
    typedef random_access_iterator_tag      iterator_category;

    link_type elem_ptr;         // 需要一个指针, 指向vector容器

    // constructor
    __list_iterator(link_type x) : elem_ptr(x) {
    
    }
    __list_iterator()   {
    
    }
    __list_iterator(const iterator& x) : elem_ptr(x) {
    
    }

    bool operator== (const self& x) const {
    
    return elem_ptr == x.elem_ptr;}
    bool operator!= (const self& x) const {
    
    return elem_ptr != x.elem_ptr;}
    // 对迭代器取值(derefence), 取的是节点的数据值
    reference operator* () const {
    
    return *elem_ptr;}
    // 对迭代器的成员进行访问
    pointer operator-> () const {
    
    return elem_ptr;}
    // 对迭代器累加1,  前进一步
    self& operator++ ()
    {
    
    
        ++this;
        return *this;
    }
    self operator++(int)
    {
    
    
        self tmp = *this;
        ++*this;
        return tmp;
    }
    // 对迭代器递减1, 就是后退一个节点
    self& operator--()
    {
    
    
        --this;
        return *this;
    }
    self operator--(int)
    {
    
    
        self tmp = *this;
        --*this;
        return tmp;
    }
};

A través del aprendizaje previo, podemos saber que hay dos puntos clave en el diseño de iteradores:

  • El iterador es un puntero inteligente, que necesita completar operaciones como incremento, decremento, valor y acceso a miembros, pero al observar la implementación de los diversos operadores anteriores, se muestra que es solo una simple encapsulación del operador de puntero;
  • nested typedefEl iterador necesita definir cinco tipos de información común en forma de definición de tipo incrustada , pero del artículo anterior, sabemos iterator traitsque tanto los punteros ordinarios como los punteros constantes están parcialmente especializados. Por lo tanto, cuando se pasa un puntero, después de que iterator traitsse puede obtener automáticamente cinco información de tipo común;
    inserte la descripción de la imagen aquí


En resumen, en vectorel contenedor, utilizando vectorel puntero ordinario correspondiente al tipo de datos del elemento como iterador, se pueden cumplir todos los requisitos.

gestión de memoria vectorial

vectorPor defecto, se utiliza alloccomo un configurador de espacio, y se define otro en consecuencia data_allocator, para que pueda asignar espacio de N elementos a la vez:

template <class T, class Alloc = alloc>
class vector 
{
    
    
protected:
    typedef T                               value_type;
    typedef value_type*                     iterator;       // 使用普通指针即可满足迭代器的需求
    typedef simple_alloc<value_type, Alloc> data_allocator;
    ...
};

Por lo tanto, el espacio del elemento data_allocator::allocate(n)se puede configurar pasando .n

La estructura de datos del vector.

template <class T, class Alloc = alloc>
class vector 
{
    
    
public:
    typedef T                               value_type;
    typedef value_type*                     iterator;       // 使用普通指针即可满足迭代器的需求
    typedef value_type*                     pointer;
    typedef valure_type&                    reference;
    typedef size_t                          size_type;
    typedef ptrdiff_t                       difference_type;
protected:
    typedef simple_alloc<value_type, Alloc> data_allocator; // 空间配置器

    // 成员变量
    iterator start;             // 表示目前使用空间的头
    iterator finish;            // 表示目前使用空间的尾
    iterator end_of_storage;    //表示目前可用空间的尾
    ...
};

vectorSolo hay tres iteradores (punteros ordinarios) en la clase starty , finishrespectivamente, apuntan al rango utilizado en el espacio continuo configurado actualmente y end_of_storageapuntan al final del espacio continuo configurado.

inserte la descripción de la imagen aquí


A través de los tres iteradores anteriores, las funciones de los siguientes métodos se pueden completar fácilmente. Entre ellos, la capacidad se refiere al tamaño de la configuración del espacio continuo y el tamaño se refiere al tamaño del espacio continuo actual que se ha utilizado.

public:
    iterator begin() {
    
    return start;}
    iterator end()  {
    
    return finish;}
    size_type size() const {
    
    return size_type(end() - begin());}
    size_type capacity() const {
    
    return size_type(end_of_storage - begin());}
    bool empty() const {
    
    return begin() == end();}
    reference operator[](size_type n) {
    
    return *begin() + n;}

    reference front() {
    
    return *begin();}
    reference back() {
    
    return *(end() - 1);}
    ...

constructor de vectores

inserte la descripción de la imagen aquí

Como se muestra en la figura anterior, vectorse proporcionan 4 tipos de constructores, que fillse refieren al uso del mismo valor para inicializar n espacios de elementos, rangese refieren a la inicialización vectordel contenedor con datos dentro de un rango en el contenedor existente y copyse refieren al uso de la inicialización de contenedores existentes. contenedores nuevos vector. Los códigos de los últimos tres constructores son los siguientes, que se completan internamente mediante llamadas unintialized_fill_ny uninitialized_copyfunciones globales.

// 构造函数
vector(size_type n, const T& value) {
    
    fill_initialize(n, value);}
void fill_initialize(size_type n, const T& value)
{
    
    
    start = allocate_and_fill(n, value);
    finish = start + n;
    end_of_storage = finish;
}
iterator allocate_and_fill(size_type, n, const T& value)
{
    
    
    iterator result = data_allocator::allocate(n); // 配置n个元素空间
    unintialized_fill_n(result, n, x) ; // 全局函数
    return result;
}
vector (InputIterator first, InputIterator last,
             const allocator_type& alloc = allocator_type())
{
    
    
    finish = uninitialized_copy(first, last, start);        // 全局函数
}
vector(const vector<T, alloc>& x) 
{
    
    
    finish = uninitialized_copy(x.begin(), x.end(), start); // 全局函数
}

función de inserción

vectorEn el contenedor, insert_auxlos datos se insertan a través de la función de llamada subyacente y el tamaño aumenta automáticamente de forma dinámica. El método específico es configurar otro espacio del doble del tamaño del contenedor original, luego copiar el contenido original y luego comenzar a construir elementos después el contenido original y liberar el espacio original. El código específico y el diagrama de flujo son los siguientes:

void insert_aux(iterator position, const T& x)
{
    
    
    if(finish  != end_of_storage)   // 还存在备用空间
    {
    
    
        // 在备用空间起始处构造一个元素,并以vector最后一个元素值作为其初值
        construct(finish, *(finish - 1));
        ++finish;
        T x_copy = x;
        copy_backward(position, finish - 2, finish - 1);
        *position = x_copy;
    }   
    else    // 没有备用空间
    {
    
    
        const size_type old_size = size();
        const size_type len = old_size != 0 ? 2 * old_size : 1;
        iterator new_start = data_allocator::allocate(len);  // 实际配置空间
        iterator new_finish = new_start;
        try
        {
    
    
            // 将原vector的内容拷贝到新的vector
            new_finish = unintialized_copy(start, position, new_start);
            // 为新元素设定初值x
            construct(new_finish, x);
            ++new_finish;
            new_finish = unintialized_copy(position, finish, new_finish);
        }
        catch(...)
        {
    
    
            ...
        }
        // 析构并释放原vector
        destroy(begin(), end());
        deallocate();
        // 调整迭代器, 指向新的vector
        start = new_start;
        finish = new_finish;
        end_of_storage = new_start + len;
        
    }
}

inserte la descripción de la imagen aquí


inserte la descripción de la imagen aquí


Debido a la reconfiguración del espacio, todos los iteradores del vector original no son válidos y no se pueden obtener los datos correctos. A continuación se muestra un pequeño ejemplo.

// inserting into a vector
#include <iostream>
#include <vector>

int main ()
{
    
    
  std::vector<int> myvector (3,100);    // 分配3个元素空间, 调用unintialized_fill_n全局函数初始化元素内容为100
  std::vector<int>::iterator it;

  it = myvector.begin();                // 获取得到当前vector容器的start迭代器
  myvector.insert ( it , 200 );    // 想vector容器插入一个值为200的数据, 但是, 此时vector容器中已经没有可用空间,会重新分配两倍内存, 6个字节
  // it此时不再指向vector的begin
  std::cout << "myvector contains:";
  for (it=myvector.begin(); it<myvector.end(); it++)    // 需要重新获取
    std::cout << ' ' << *it;
  std::cout << '\n';

  return 0;
}

push_backCuando la función inserta un nuevo elemento hasta vectorel final, primero juzgará si hay espacio libre y, de ser así, construirá directamente el elemento y ajustará el iterador. Si no, insert_auxexpanda el espacio llamando a la función.

función de borrado

eraseLa función admite dos formas: eliminar (destruir) [first, last) todos los elementos y especificar un elemento en una determinada posición. A continuación, se explica principalmente cómo borrar [first, last)todos los elementos en el código y el flujo de trabajo, de la siguiente manera:

iterator erase(iterator first, iterator last)
{
    
    
    iterator it = copy(last, finish, first);//1
    destory(i, finish); // 2
	finish = finish - (last - first);	// 3
    return first;
}

inserte la descripción de la imagen aquí


cleareraseLa función borra vectortodos los elementos llamando directamente a la función anterior , el código es el siguiente:

void clear () {
    
     erase(begin(), end()); }

Cabe destacar que erasela función solo destruye los elementos, pero no libera el espacio de memoria correspondiente.

por lo tanto

A diferencia de la apertura unidireccional del vector, deque es un espacio lineal continuo con apertura bidireccional, y los elementos se pueden insertar y eliminar en ambos extremos de la cabeza y la cola, como se muestra en la figura.

inserte la descripción de la imagen aquí

Controlador central de Deque

dequeEl espacio se compone de un segmento de espacio continuo, y el espacio de memoria aumenta al agregar dinámicamente un nuevo segmento de espacio. Para gestionar esta sección de espacio continuo, se utiliza dequeun mapno- STLcontenedor mapcomo control maestro.
mapEs un pequeño espacio contiguo donde cada elemento es un puntero a otro espacio lineal contiguo (más grande) llamado búfer. El búfer es dequeel cuerpo principal del espacio de almacenamiento.

inserte la descripción de la imagen aquí


El código de definición de mapa en deque es el siguiente:

template <class T, class Alloc=alloc, size_t BufSize = 0>
class deque
{
    
    
public:
	typedef T value_type;
	typedef value_type* pointer;
...
protected:
	// 元素的指针的指针
	typedef pointer* map_pointer;
protected:
	map_pointer map;
	size_type map_size; 	// map内可容纳多少指针
};

SGI STLPermite especificar el tamaño del búfer, el valor predeterminado es 0 para usar un bytesbúfer 512. mapEl tipo es T**que el elemento es un puntero, y el referente es Tun espacio de tipo.

iterador deque

dequeSe utiliza el espacio continuo por partes, mientras que la superficie mantiene la "continuidad general", que se logra mediante operator--la suma de dos operaciones del iterador .operator++

  • nested typedefDefina cinco tipos de información comunes en forma de definición de tipo en línea
template <class T, class Ref, class Ptr, size_t BufSize>
struct __deque_iterator{
    
    	// 未继承std::iterator
  // 未继承std::iterator,所以自行撰写五个必要的迭代器相应类别
  typedef random_access_iterator_tag iterator_category;   // 1
  typedef _Tp value_type;                                 // 2
  typedef _Ptr pointer;                                   // 3
  typedef _Ref reference;                                 // 4
  typedef ptrdiff_t difference_type;                      // 5
...
};
  • La esencia de un iterador es uno smart pointer, porque necesita poder señalar dónde está la deducción continua segmentada, y también necesita juzgar si está en el borde del búfer donde se encuentra, el iterador dequecontiene tres tipos de objetos de puntero cur, first, last. También debe poder saltar con precisión entre el búfer anterior o el siguiente, por lo que se incluye el control map.
typedef T** map_pointer;
// 保持与容器的联结
T* cur;
T* first;
T* last;
map_pointer node;
...

dequeLa relación entre el controlador central, el búfer y el iterador es la siguiente:

inserte la descripción de la imagen aquí

entre ellos, el iterador también proporciona una función para determinar el tamaño del búfer buffer_size(), y el resultado devuelto se refiere a cuántos elementos se pueden almacenar en un búfer, internamente por llamando __deque_buf_size()al global La función está completa, el código es el siguiente:

static size_t _S_buffer_size() {
    
     return __deque_buf_size(sizeof(_Tp)); }
// 如果 n 不为 0,传回 n,表示 buffer size由使用者自定。
// 如果 n 为 0,表示 buffer size使用默认值,那么
//     如果sz(元素大小,sizeof(value_type))小于 512,传回 512/sz,
//     如果sz不小于 512,传回 1。
inline size_t __deque_buf_size(size_t __size) {
    
    
  return __size < 512 ? size_t(512 / __size) : size_t(1);
}

sobrecarga del operador

El iterador deque necesita saltar entre diferentes búferes, por lo que es necesario sobrecargar varias operaciones de puntero.

  • Saltar a un búfer, ajustar el puntero correspondiente al firstiteradorlast
void set_node (map_pointer new_node)
{
    
    
    node = new_node;
    first = *new_node;
    last = first + difference_type(buffer_size());
}
  • dereferencey acceso de miembros
reference operator*() const {
    
    return *cur; }
pointer operator->() const {
    
    return &(operator*()); }
  • operador menos
difference_type operator-(const self& x) const
{
    
    
    return difference_type(buffer_size()) * (node - x.node - 1) +
            (cur - first) + (x.last - x.cur);
}
  • Incremento y Decremento
self& operator++()
{
    
    
    ++cur;			// 切换至下一个元素
    if(cur == last )	// 如果已经到达缓冲区的尾端
    {
    
    
        set_node(node + 1);  // 就切换至下一节点
        cur = first;			// 第一个元素
    }
    return *this;
}
slef& operator--()
{
    
    
    if(cur == first) {
    
    	// 如果已经到达缓冲区的头端
        set_node(node - 1);
        cur = last;
    }
    --cur;
    return *this;
}
  • acceso aleatorio
self& operator+=(difference_type n){
    
    
    difference_type offset = n + (cur - first);
    if(offset >= 0 && offset < difference_type(buffer_size()))
        // 目标位置在同一缓冲区内
        cur += n;
	else {
    
    
        // 不在同一个缓冲区内
        difference_type node_offset = 
        offset > 0 ? offset / difference_type(buffer_size())
        : -difference_type((-offset - 1) / buffer_size()) - 1;
        // 切换至正确的节点
        set_node(node + node_offset);
        // 切换到正确的位置
        cur = first + (offset - node_offset * difference_type(buffer_size());
    }
    return *this;
}
self& operator-=(difference_type n) {
    
     return *this += -n; }

// 实现随机存取
reference operator[] (difference_type n) const {
    
    return *(*this + n);}

estructura de datos deque

Además de un puntero al mapa, los miembros del deque también necesitan mantener dos iteradores start y finish, que apuntan al primer elemento del primer búfer y al último elemento del último búfer respectivamente. Además, es necesario recordar el tamaño actual del mapa, cuando los nodos proporcionados por el mapa son insuficientes, se debe reconfigurar un mapa más grande.

template <class T, class Alloc=alloc, size_t BufSize = 0>
class deque
{
    
    
public:
	typedef T value_type;
	typedef value_type* pointer;
...
protected:
	// 元素的指针的指针
	typedef pointer* map_pointer;
protected:
	map_pointer map;
	size_type map_size; 	// map内可容纳多少指针
	iterator start ;
	iterator finish;
...
};

inserte la descripción de la imagen aquí


La implementación de las siguientes funciones se puede obtener fácilmente:

public:
	iterator begin() {
    
    return start;}
	iterator end() 	 {
    
    return finish;}
	reference operator[](size_type n){
    
     return start[difference_type(n)];}
	reference front() {
    
    return *start; }
	reference back() {
    
    
        iterator tmp = finish;
        --tmp;
        return *tmp;
    }
	size_type size() cosnt {
    
    return finish - start;}
	size_type max_size() const {
    
     return size_type(-1); }
	bool empty() const {
    
    return finish == start; }

Construcción de Deque y gestión de memoria.

Como se muestra en el siguiente código, establecer el tamaño del búfer en 8 significa que se pueden guardar 8 elementos, se reservan 20 elementos y el valor inicial de cada elemento es 9.

deque<int, alloc, 8> ideq(20, 9);

Una vez que los objetos anteriores dequese configuran con memoria y se inicializan, el diagrama esquemático general es el siguiente:

inserte la descripción de la imagen aquí


dequeDefinidos dos configuradores de espacios exclusivos:

protected:
	//专属之空间配置器, 每次配置一个元素大小
	typedef simple_alloc<value_type, Alloc> data_allocator;
	// 专属之空间配置器, 每次配置一个指针大小
	typedef simple_alloc<pointer, Alloc> map_allocator;

dequeLa definición de uno constructores la siguiente:

deque(int n, const value_type& value)
	: start (), finish (), map(0), map_size(0)
{
    
    
    fill_initialize(n, value);
}

La llamada interna fill_initialize()se encarga de generar y dequeordenar la estructura, y de establecer adecuadamente el valor inicial del elemento.

añadir elemento

Si continúa insertando tres nuevos elementos al final, en este momento, dado que el último búfer tiene 4 espacios de elementos libres, no provocará la reconfiguración del búfer, y la estructura se muestra en la siguiente figura:

for(int i=0; i<3; i++)
	ideq.push_back(i);

inserte la descripción de la imagen aquí


El contenido de la función push_back() es el siguiente:

public:
void push_back(const value_type& t)
{
    
    
    if(finish.cur != finish.last - 1)
    {
    
    
        // 最后缓冲区尚存在两个以上的元素备用空间
        construct(finish.cur, t); // 直接在备用空间构造元素
        ++finish.cur; // 调整最后缓冲区的使用状态
    }
    else	// 最后缓冲区只剩下一个元素备用空间
        push_back_aux(t);
}

Luego, agregue un nuevo elemento al final. En este momento, dado que el último búfer tiene solo un elemento de espacio libre, se llamará. Primero configure un búfer completamente nuevo, luego configure el contenido del nuevo elemento y luego cambie el estado de push_back_auxel iterador finish, la nueva estructura es la siguiente: De

inserte la descripción de la imagen aquí

manera similar, al llamar para push_front()insertar un elemento en el extremo frontal, primero juzgue si hay espacio libre en el primer búfer, si no, llame a la push_front_aux()función para configurar un nuevo búfer, establezca un nuevo elemento, modificar startel estado de iteración y nuevo La estructura se muestra en la siguiente figura:

inserte la descripción de la imagen aquí

Ajustar el mapa

En el deque, reserve_map_at_back() y reserve_map_at_front() juzgan cuándo es necesario ajustar el mapa, y reallocate_map() realiza la operación real.

// 如果 map 尾端的节点备用空间不足,符合条件就配置一个新的map(配置更大的,拷贝原来的,释放原来的)
void reserve_map_at_back (size_type nodes_to_add = 1) {
    
    
  if (nodes_to_add + 1 > map_size - (finish.node - map))
    reallocate_map(nodes_to_add, false);
}
// 如果 map 前端的节点备用空间不足,符合条件就配置一个新的map(配置更大的,拷贝原来的,释放原来的)
void reserve_map_at_front (size_type nodes_to_add = 1) {
    
    
  if (nodes_to_add > start.node - map)
    reallocate_map(nodes_to_add, true);
}

eliminar elemento

dequeEn , use pop_front()y pop_back()para eliminar elementos del principio o del final. Cuando no hay elementos en un búfer, el búfer debe liberarse, el código es el siguiente:

void pop_back()
{
    
    
    if(finish.cur != finish.first){
    
    
        --finish.cur;			// 调整指针,相当于删除了最后元素
        destroy(finish.cur)// 析构最后的元素
    }
    else
        pop_back_aux();		
}
void pop_back_aux() {
    
    
    deallocate_node(finish.first) ;  // 是否最后一个缓冲区
    finish.set_node(finish.node - 1); //调整迭代器finish的状态
    finish.cur = finish.last - 1; 
    destroy(finish.cur)	;        // 将该元素析构
}

clear()Función, utilizada para borrar todo el archivo deque. Cabe señalar que clear()después de la finalización, dequesolo hay un búfer para restaurar al estado original.

void clear()
{
    
    
    // 对头尾以外的每个缓冲区的元素都析构,且释放缓冲区的内存
    for(map_pointer node=start.node + 1; node < finish.node; ++node)
    {
    
    
        destroy(*node, *node + buffer_size();
        // 释放缓冲区内存
        data_allocator::deallocate(*node, buffer_size());
    }
    if(start.node != finish.node){
    
    	// 至少有头尾两个缓冲区
        destroy(start.cur, start.last);
        destroy(finish.first, finish.cur);
        // 释放尾端缓冲区
        data_allocator::deallocate(finish.first, buffer_size());
    }
    else
        destroy(start.cur, finish.cur);
    finish = start; // 调整状态
}

erase()Para mejorar la eficiencia, la función decidirá moverse según si la ubicación de eliminación es cercana starto finishreciente.

public:                         // erase
  iterator erase(iterator pos) {
    
    
    iterator next = pos;
    ++next;
    difference_type index = pos - start;
      // 删除的地方是中间偏前, 移动前面的元素
    if (index < (size() >> 1)) {
    
    
      copy_backward(start, pos, next);
      pop_front();
    }
      // 删除的地方是中间偏后, 移动后面的元素
    else {
    
    
      copy(next, finish, pos);
      pop_back();
    }
    return start + index;
  }
 // 范围删除, 实际也是调用上面的erase函数.
  iterator erase(iterator first, iterator last);

De manera similar, insert()la función también moverá diferentes contenidos de datos según la distancia entre la posición de inserción y el principio y el final, para mejorar la eficiencia.

Supongo que te gusta

Origin blog.csdn.net/hello_dear_you/article/details/128472201
Recomendado
Clasificación