</>waridocu

C++

std::array: tamaño fijo con seguridad de la STL

El contenedor de tamaño fijo que conserva la eficiencia del array de C y suma los métodos de la Standard Library.

cppstandard-librarystlarraycontenedores

De dónde venimos

Hasta aquí tienes los dos extremos:

Falta la opción del medio, y std::array es exactamente eso: el tamaño fijo del array de C, con los métodos y la seguridad de la Standard Library. Cuando sabes de antemano cuántos elementos vas a tener y esa cantidad no va a cambiar nunca, es la elección más precisa de las tres.

Qué es y cómo se declara

Un std::array es una secuencia de tamaño fijo, decidido al compilar, que envuelve al array de estilo C y le agrega los mismos métodos que ya usaste en el vector (.size(), .at(), .empty()). Vive en el header <array>:

El header que activa std::arraycpp
#include <array>

La declaración lleva dos datos entre ángulos, no uno:

Anatomía de la declaración: primero el tipo, después el tamañocpp
std::array<int, 3> numeros = {1, 2, 3};
//         ^^^  ^
//         tipo tamaño
Ejemplo: declarar un std::array de tamaño fijocpp
#include <iostream>
#include <array>

int main() {
    std::array<int, 3> numeros = {1, 2, 3};   // 3 enteros, ni uno más

    std::cout << "Cantidad de elementos: " << numeros.size() << "\n";
    std::cout << "El del medio: " << numeros[1] << "\n";

    numeros[1] = 20;                          // se modifica como un array normal
    std::cout << "El del medio ahora: " << numeros[1] << "\n";
    return 0;
}
bashbash
g++ -std=c++17 -Wall -o arreglo arreglo.cpp
./arreglo
texttext
Cantidad de elementos: 3
El del medio: 2
El del medio ahora: 20

Por qué el tamaño va dentro del tipo

Esta es la diferencia conceptual con std::vector, y explica todo lo demás. En un vector, el tamaño es un dato que el objeto guarda y que cambia mientras el programa corre. En un std::array, el tamaño es parte del tipo: std::array<int, 3> y std::array<int, 5> son tipos distintos, tan distintos entre sí como int y double.

De ahí salen tres consecuencias prácticas:

  1. El tamaño debe conocerse al compilar. Puede ser un número literal o una constante, pero nunca un valor leído por consola. std::array<int, n> v; con n pedido al usuario no compila — para ese caso está std::vector.
  2. No tiene .push_back(). No existe “agregar un elemento” a algo cuyo tamaño forma parte de su tipo. Los elementos están todos ahí desde el momento en que se declara.
  3. No usa memoria dinámica. Como su tamaño se conoce al compilar, sus elementos viven donde se declara la variable —normalmente en la pila, igual que un array de estilo C— y no hay ninguna reserva de memoria en el heap de por medio. Eso lo hace tan barato como un array crudo.
Nota

Esa sintaxis de <tipo, tamaño> es una plantilla (template) con dos parámetros: uno es un tipo y el otro un número. Es el mismo mecanismo que ya viste en std::vector<int>, solo que aquí se le pasa un dato más. Si la mezcla de <>, {} y [] en una sola línea todavía te marea, hay un repaso dedicado a cada símbolo en Repaso de sintaxis.

Conoce su propio tamaño

Aquí está la ganancia concreta frente al array de estilo C. Recuerda la sorpresa de Arrays y funciones: al pasar int datos[] a una función, el array se degrada a un puntero y la función queda ciega respecto al tamaño, así que hay que mandarlo aparte en un segundo parámetro.

Un std::array no se degrada: es un objeto que se pasa entero, con su tamaño incluido en el tipo.

Ejemplo: un std::array no pierde su tamaño al pasarlo a una funcióncpp
#include <iostream>
#include <array>

// No hace falta un segundo parámetro con la cantidad: viaja dentro del tipo
void mostrar(const std::array<int, 5>& datos) {
    std::cout << "Recibí " << datos.size() << " elementos: ";
    for (int valor : datos) {
        std::cout << valor << " ";
    }
    std::cout << "\n";
}

int main() {
    std::array<int, 5> numeros = {2, 4, 6, 8, 10};
    mostrar(numeros);
    return 0;
}
texttext
Recibí 5 elementos: 2 4 6 8 10 

Dentro de mostrar(), datos.size() vale 5 y el for basado en rango funciona sin más: dos cosas imposibles con un int datos[].

Importante

El precio de esa garantía es que la función solo acepta arrays de ese tamaño exacto: mostrar() no compilará si le pasas un std::array<int, 3>. No es un descuido, es el mismo compromiso de siempre — más rigidez a cambio de que el compilador detecte los errores en vez de descubrirlos en ejecución. Si necesitas una función que acepte cualquier cantidad de elementos, usa std::vector.

Tip

El parámetro va, otra vez, como referencia constante (const std::array<int, 5>&). Sin el &, el std::array se copiaría entero al llamar a la función, a diferencia del array de estilo C, que nunca se copia porque se degrada a puntero. Es un cambio de comportamiento fácil de pasar por alto al migrar código viejo.

Acceso seguro e inseguro a los elementos

Idéntico a lo que viste en vector, y esa es justamente la gracia: los métodos son los mismos en toda la Standard Library.

Ejemplo: [] no comprueba límites, .at() sícpp
#include <iostream>
#include <array>

int main() {
    std::array<int, 3> numeros = {10, 20, 30};

    std::cout << "Con []:    " << numeros[2]    << "\n";   // sin comprobación
    std::cout << "Con .at(): " << numeros.at(2) << "\n";   // con comprobación
    return 0;
}
texttext
Con []:    30
Con .at(): 30
Advertencia

numeros[5] en un std::array<int, 3> es comportamiento indefinido, exactamente igual que en un array crudo o en un vector: compila sin quejarse y hace lo que sea. .at(5) detiene el programa con un mensaje que dice qué índice pediste y cuántos elementos había. Ninguno de los tres contenedores de este grupo comprueba los límites con [] — la seguridad hay que pedirla escribiendo .at().

Ejemplo completo: los días de la semana

El caso típico de std::array: una colección cuyo tamaño es un hecho del mundo, no una decisión de programa. Los días de la semana son siete y no van a ser ocho mañana.

Ejemplo: los siete días de la semana en un std::arraycpp
#include <iostream>
#include <array>
#include <string>

int main() {
    const std::array<std::string, 7> dias = {
        "lunes", "martes", "miércoles", "jueves",
        "viernes", "sábado", "domingo"
    };

    std::cout << "La semana tiene " << dias.size() << " días:\n";

    for (const std::string& dia : dias) {
        std::cout << "- " << dia << "\n";
    }

    std::cout << "El tercer día es " << dias.at(2) << "\n";
    return 0;
}
bashbash
g++ -std=c++17 -Wall -o semana semana.cpp
./semana
texttext
La semana tiene 7 días:
- lunes
- martes
- miércoles
- jueves
- viernes
- sábado
- domingo
El tercer día es miércoles

Dos detalles del ejemplo:

  • El const de la primera línea dice que la colección entera no se va a modificar. Con un std::array de contenido fijo es lo habitual, y hace que el compilador rechace cualquier intento accidental de escribir en él.
  • dias.at(2) devuelve "miércoles", el tercer elemento: los índices siguen empezando en 0, igual que en todo lo demás.

Tabla resumen: array de C, std::array y std::vector

Array de C (int v[5])std::array<int, 5>std::vector<int>
TamañoFijo, decidido al compilarFijo, decidido al compilar (parte del tipo)Variable, cambia en ejecución con .push_back()
Conoce su tamañoSolo donde se declara; se pierde al pasarlo a una funciónSiempre, con .size()Siempre, con .size()
Dónde vive en memoriaDonde se declara (pila, si es una variable local)Donde se declara (pila, si es una variable local)Los elementos en el heap, pedidos y liberados solos
Cuándo usarloCuando una API de C o una librería te obligaCantidad fija y conocida al compilar: días de la semana, coordenadas de un punto, un tablero de 8×8Todo lo demás. Es la opción por defecto cuando no sabes cuántos elementos habrá
Tip

Los tres comparten la misma forma de indexar ([]), el mismo for basado en rango y —los dos de la Standard Library— los mismos métodos. Eso es deliberado: cambiar de uno a otro suele ser cuestión de tocar la línea de la declaración, no de reescribir la lógica que los usa.

Hasta aquí los contenedores básicos

Con std::vector y std::array ya tienes cubierto el caso general de “guardar muchos valores del mismo tipo”, que es la necesidad más frecuente del día a día. Los otros contenedores que aparecieron en el mapa de la Standard Librarystd::map para asociar claves con valores, std::set para valores únicos— resuelven necesidades más específicas y se apoyan en las mismas ideas que acabas de ver: un objeto que guarda otros objetos, conoce su tamaño y administra su propia memoria.

La documentación continúa con Dear ImGui, una librería externa para construir interfaces gráficas. Ahí volverás a encontrarte con casi todo lo de estas páginas: std::vector para las listas de elementos que se dibujan en pantalla, arrays de estilo C en las funciones que vienen de su API en C, y std::string con .c_str() para pasarle textos.