¿Qué es un contenedor?
Los contenedores container
son 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.
Los contenedores secuenciales incluyen principalmente vector、lis
t y deque
contenedores, 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.map
multimap
list
list
Contenedor, 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 list
en un espacio de memoria disperso, en lugar de almacenarse en un espacio de memoria continuo.
¿Por qué hablar primero de contenedores list
y no vector
de contenedores? La razón es que en el último artículo sobre iteradores, inicialmente implementamos un list
iterador iterator
, y STL
podemos aprender mejor el conocimiento del iterador comparándolo con la implementación de iterador.
lista de nodos
list
La 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 node
define 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 traits
trabajo efectivo, el iterador debe definirse en un nested typedef
tipo 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;
list
El 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:
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.
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.
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 list
No 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 list
solo 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:
El diagrama de la lista es el siguiente:
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
insert
Es 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. insert
La 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.
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
erase
La 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 list
de un nodo se muestra en la siguiente figura:
la siguiente función erase
se 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
transfer
La función es list
una operación de migración proporcionada internamente, y su función es migrar elementos de un rango continuo a una posición específica. transfer
El 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:
transfer
la función no es stl
una interfaz pública, sino splice
una función. Las siguientes son splice
varias 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, sort
las funciones merge
y reverse
se completan llamando funciones internamente transfer
, por lo que no entraré en detalles aquí.
vector
vector
El arreglo de datos y el método de operación son array
muy similares a . La única diferencia es que array
es 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.
elemento vectorial
vector
Sí template class
, el tipo de elemento almacenado en él se especifica al crear el objeto, como vector<int>
, vector<string>
etc., y vector
el tipo de elemento almacenado en el contenedor es int
o respectivamente string
. vector
Solo 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 typedef
El iterador necesita definir cinco tipos de información común en forma de definición de tipo incrustada , pero del artículo anterior, sabemositerator traits
que tanto los punteros ordinarios como los punteros constantes están parcialmente especializados. Por lo tanto, cuando se pasa un puntero, después de queiterator traits
se puede obtener automáticamente cinco información de tipo común;
En resumen, en vector
el contenedor, utilizando vector
el puntero ordinario correspondiente al tipo de datos del elemento como iterador, se pueden cumplir todos los requisitos.
gestión de memoria vectorial
vector
Por defecto, se utiliza alloc
como 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; //表示目前可用空间的尾
...
};
vector
Solo hay tres iteradores (punteros ordinarios) en la clase start
y , finish
respectivamente, apuntan al rango utilizado en el espacio continuo configurado actualmente y end_of_storage
apuntan al final del espacio continuo configurado.
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
Como se muestra en la figura anterior, vector
se proporcionan 4 tipos de constructores, que fill
se refieren al uso del mismo valor para inicializar n espacios de elementos, range
se refieren a la inicialización vector
del contenedor con datos dentro de un rango en el contenedor existente y copy
se 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_n
y uninitialized_copy
funciones 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
vector
En el contenedor, insert_aux
los 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;
}
}
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_back
Cuando la función inserta un nuevo elemento hasta vector
el final, primero juzgará si hay espacio libre y, de ser así, construirá directamente el elemento y ajustará el iterador. Si no, insert_aux
expanda el espacio llamando a la función.
función de borrado
erase
La 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;
}
clear
erase
La función borra vector
todos los elementos llamando directamente a la función anterior , el código es el siguiente:
void clear () {
erase(begin(), end()); }
Cabe destacar que erase
la 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.
Controlador central de Deque
deque
El 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 deque
un map
no- STL
contenedor map
como control maestro. map
Es 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 deque
el cuerpo principal del espacio de almacenamiento.
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 STL
Permite especificar el tamaño del búfer, el valor predeterminado es 0 para usar un bytes
búfer 512. map
El tipo es T**
que el elemento es un puntero, y el referente es T
un espacio de tipo.
iterador deque
deque
Se 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 typedef
Defina 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 iteradordeque
contiene tres tipos de objetos de punterocur
,first
,last
. También debe poder saltar con precisión entre el búfer anterior o el siguiente, por lo que se incluye el controlmap
.
typedef T** map_pointer;
// 保持与容器的联结
T* cur;
T* first;
T* last;
map_pointer node;
...
deque
La relación entre el controlador central, el búfer y el iterador es la siguiente:
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
first
iteradorlast
void set_node (map_pointer new_node)
{
node = new_node;
first = *new_node;
last = first + difference_type(buffer_size());
}
dereference
y 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;
...
};
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 deque
se configuran con memoria y se inicializan, el diagrama esquemático general es el siguiente:
deque
Definidos dos configuradores de espacios exclusivos:
protected:
//专属之空间配置器, 每次配置一个元素大小
typedef simple_alloc<value_type, Alloc> data_allocator;
// 专属之空间配置器, 每次配置一个指针大小
typedef simple_alloc<pointer, Alloc> map_allocator;
deque
La definición de uno constructor
es 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 deque
ordenar 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);
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_aux
el iterador finish
, la nueva estructura es la siguiente: De
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 start
el estado de iteración y nuevo La estructura se muestra en la siguiente figura:
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
deque
En , 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, deque
solo 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 start
o finish
reciente.
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.