Arquitectura y Uso de los Contenedores STL en C++

Fundamentos de Inicialización y Deducción de Tipos

La estandarización moderna de C++ introdujo mecanismos que mejoran la seguridad y la legibilidad del código al manejar estructuras de datos.

Inicialización Uniforme

A partir de C++11, las llaves permiten una construcción más estricta que prveiene conversiones implícitas peligrosas (narrowing).

int escala = 5; // Asignación tradicional
int limite{9};  // Inicialización con llaves

// Si se intenta: int limite{99999999999LL}; el compilador rechazará la conversión.

// Aplicado a contenedores complejos:
std::stack<std::pair<int, int>> pila;
pila.push({4, 8}); // Evita el uso explícito de std::make_pair

Deducción Automática de Tipos

La palabra clave auto delega la determinación del tipo al compilador, requiriendo obligatoriamente un inicializador.

auto contador = 100;   // Deduce int
auto inicial = 'K';    // Deduce char
// auto sin asignación inmediata genera error de compilación.

Referencias como Alternativa Segura a Punteros

Las referencias ofrecen alias directos a objetos existentes sin la sintaxis de desreferenciación manual.

void invertirValores(int& refA, int& refB) {
    int temp = refA;
    refA = refB;
    refB = temp;
}
// En C se usarían punteros explícitos, pero las referencias evitan operaciones de direccionamiento inseguras.

Interfaz Común y Mecanismo de Recorrido

Casi todos los contenedores comparten un conjunto básico de métodos de gestión:

  • clear(): Elimina todos los elementos.
  • size(): Devuelve la cantidad actual de elementos.
  • max_size(): Indica el límite teórico de almacenamiento (excepto en std::array).
  • empty(): Retorna verdadero si la colección está vacía.

Iteradores

Los iteradores actúan como punteros genéricos que abstraen la estructura de memoria subyacente.

std::vector<int> registro{1, 2, 3, 4};
std::vector<int>::iterator cursor;

// Métodos estándar de acceso
cursor = registro.begin();  // Primer elemento
auto final = registro.end();// Posición posterior al último

// Recorrido clásico
for (auto it = registro.begin(); it != registro.end(); ++it) {
    // *it accede al valor almacenado
}

// Recorrido inverso
for (auto rev = registro.rbegin(); rev != registro.rend(); ++rev) { }

// Sintaxis basada en rango (C++11)
for (int valor : registro) {
    // iteración directa sin gestión de punteros
}

Contenedores Secuenciales

Arreglo de Tamaño Fijo (std::array)

Requiere dimensión conocida en tiempo de compilación y permite el uso de iteradores, a diferencia de los arreglos C tradicionales.

#include <array>
std::array<int, 50> buffer;
buffer.fill(-1); // Asigna -1 a todas las posiciones

Vector Dinámico (std::vector)

Gestión automática de memoria con redimensionamiento implícito. Expone acceso aleatorio mediante operator[] y métodos de inserción/eliminación.

#include <vector>
std::vector<int> secuencia(5, 0); // 5 elementos inicializados en 0

secuencia.push_back(42);      // Agrega al final
secuencia.pop_back();         // Remueve el final
secuencia.at(2);              // Acceso con verificación de límites
secuencia.resize(10);         // Ajusta tamaño (rellena con 0 si crece)
secuencia.insert(secuencia.begin(), 99); // Inserta al inicio

Optimización de Memoria: El uso repetido de push_back puede desencadenar múltiples reasignaciones. reserve(n) preasigna capacidad, reduciendo copias internas. size() indica elementos usados, mientras capacity() muestra el espacio total asignado.

Cola de Doble Final (std::deque)

Similar a vector pero optimizada para inserciones/eliminaciones en ambos extremos. No implementa reserve() debido a su segmentación interna de memoria.

deque.push_front(10);
deque.pop_front();

Lista Enlazada (std::list)

Estructura nodal no contigua. Ideal para inserciones/eliminaciones frecuentes en el medio. Posee métodos nativos sort() y reverse(), ya que std::sort requiere acceso aleatorio.

std::list<int> cadena;
cadena.remove_if([](int n){ return n % 2 != 0; }); // Elimina impares

Adaptadores de Colección

Estos componentes envuelven contenedores subyacentes para restringir la interfaz a patrones específicos.

Pila (std::stack)

Opera bajo LIFO. Por defecto usa deque, pero puede forzar vector si no se requiere extracción por el frente.

std::stack<int, std::vector<int>> monton;
monton.push(5);
int cima = monton.top();
monton.pop();

Cola (std::queue)

Opera bajo FIFO. Requiere un contenedor con push_back y pop_front, por lo que vector no es válido.

std::queue<int> espera;
espera.push(1);
espera.pop();

Cola de Prioridad (std::priority_queue)

Implementa un montón binario. El elemento mayor (max-heap) o menor (min-heap) siempre está en la cima.

std::priority_queue<int> mayor; // Por defecto max-heap
std::priority_queue<int, std::vector<int>, std::greater<int>> menor;

Contenedores Asociativos

Organizan datos mediante árboles balanceados (generalmente RB-Trees), garantizando ordenamiento automático y búsqueda logarítmica.

Conjuntos (std::set / std::multiset)

set almacena claves únicas ordenadas. multiset permite duplicados.

  • find(val): Retorna iterador al valor o end().
  • lower_bound(val) / upper_bound(val): Búsqueda de rangos.
  • count(val): Devuelve cantidad de apariciones (0 o 1 en set).

Mapas (std::map / std::multimap)

Asocian claves con valores. Internamente, cada nodo es un std::pair<Key, Value>.

std::map<std::string, int> inventario;
inventario["espada"] = 1;
inventario.insert({"escudo", 2});

// Recorrido
for (const auto& par : inventario) {
    // par.first (clave), par.second (valor)
}

Manipulación de Bits (std::bitset)

Estructura compacta para operaciones a nivel de bit. No soporta valores negativos y el tamaño es fijo en compilación.

std::bitset<8> flags("11010010");
flags.set(3);       // Activa bit 3
flags.reset(3);     // Desactiva bit 3
flags.flip();       // Invierte todos
unsigned long val = flags.to_ulong(); // Conversión numérica
std::string bin = flags.to_string();  // Conversión textual

Estructuras Personalizadas y Sobrecarga de Operadores

Para utilizar tipos definidos por el usuario en algoritmos ordenados o contenedores asociativos, es necesario establecer criterios de comparación.

struct Punto {
    int coordenadaX;
    int coordenadaY;
    
    // Constructor con inicialización de miembros
    Punto(int x, int y) : coordenadaX(x), coordenadaY(y) {}
    
    // Sobrecarga para std::sort y contenedores ordenados
    bool operator<(const Punto& otro) const {
        if (coordenadaX != otro.coordenadaX)
            return coordenadaX < otro.coordenadaX;
        return coordenadaY < otro.coordenadaY;
    }
};

// Uso en algoritmo
std::vector<Punto> geometria = { {3,1}, {1,4}, {1,2} };
std::sort(geometria.begin(), geometria.end()); // Ordena por X, luego por Y

Mecánica de Iteradores en Profundidad

Los iteradores generalizan el concepto de puntero. Mientras un puntero aritmético asume contigüidad física (ptr++ salta al siguiente bloque de memoria), un iterador encapsula la lógica de navegación, permitiendo avanzar por estructuras dispersas como listas enlazadas sin exponer detalles de implementación.

La semántica estándar dicta que begin() apunta al primer elemento válido y end() marca un centinela justo después del último. La desreferenciación (*it) o el operador flecha (it->miembro) permite acceder a los datos almacenados, manteniendo un contrato uniforme independiente de si el contenedor subyacente es un arreglo lineal, un árbol o una red de nodos.

Etiquetas: C++11 stl-containers iteradores vector Map

Publicado el 8-15 15:22