Una matriz se llama dinámica , cuyo tamaño puede cambiar durante la ejecución del programa. La capacidad de cambiar el tamaño distingue una matriz dinámica de una estática, cuyo tamaño se establece en el momento en que se compila el programa. Para cambiar el tamaño de una matriz dinámica , un lenguaje de programación que admita tales matrices debe proporcionar una función u operador integrado. Los arreglos dinámicos permiten un trabajo más flexible con los datos, ya que no permiten predecir los volúmenes de datos almacenados, sino ajustar el tamaño del arreglo de acuerdo con los volúmenes realmente requeridos.
Además, a veces las matrices dinámicas son matrices de longitud variable , cuyo tamaño no se fija durante la compilación, sino que se establece cuando se crea o inicializa la matriz durante la ejecución del programa. Se diferencian de las matrices dinámicas "reales" en que no proporcionan un cambio de tamaño automático con conservación del contenido, por lo que el programador debe implementar dicha función si es necesario.
Las matrices dinámicas se pueden admitir en el nivel de sintaxis del propio lenguaje de programación o en el nivel de las bibliotecas del sistema. La descripción de una matriz dinámica puede ser sintácticamente diferente de la descripción de una estática, pero puede ser la misma. En el segundo caso, por regla general, todos los arreglos en el lenguaje son potencialmente dinámicos, y queda a criterio del programador si usar esta propiedad en cada caso particular. Cuando son compatibles con matrices dinámicas a través de bibliotecas del sistema, generalmente se implementan como clases (en el sentido de programación orientada a objetos ) o tipos genéricos (las llamadas "plantillas" o "genéricos"); la declaración de matriz en este caso es una instanciación de una clase o una instanciación de un tipo genérico. Según el idioma, las matrices dinámicas solo pueden ser matrices unidimensionales o tener dos o más dimensiones.
La compatibilidad con matrices dinámicas implica la presencia obligatoria de una operación integrada para determinar el tamaño actual de una matriz. El tamaño inicial de una matriz dinámica es igual a cero o se establece durante la descripción o la inicialización. El cambio de tamaño adicional se realiza mediante una operación integrada especial (procedimiento). En algunos lenguajes (por ejemplo , JavaScript , Lua ), la expansión de la matriz se produce automáticamente cuando se intenta escribir en una celda inexistente.
Para que las matrices puedan cambiar de tamaño dinámicamente, la implementación debe encontrar un "medio dorado" entre varios requisitos en conflicto.
Dependiendo de la prioridad de ciertos requisitos, se selecciona el método de implementación que los cumple.
La implementación de arreglos de longitud variable difiere poco de la implementación de arreglos estáticos ordinarios. Una matriz de elementos de tipo T de longitud variable generalmente se almacena en un bloque de RAM contiguo de tamaño , donde N es el número de elementos especificado al describir (crear) la matriz. Es decir, la declaración de una matriz de longitud variable, de hecho, simplemente enmascara la asignación dinámica de memoria. La diferencia puede ser que (por ejemplo, como en C99 y versiones posteriores) se asigna memoria en la pila a una matriz de longitud variable, como otras variables automáticas, mientras que la forma típica de asignación de memoria dinámica (en la función C ) asigna memoria en la pila. montón _ Además, para una matriz de longitud variable, el compilador generalmente genera automáticamente un código de desasignación de memoria cuando la matriz declarada queda fuera del alcance. malloc
Lo más común para los típicos lenguajes compilados de procedimientos es implementar el cambio de tamaño de una matriz moviéndola en la memoria del montón.
Esta implementación es la más eficiente en términos de velocidad de acceso a celdas de matriz ya asignadas. Además, proporciona un tiempo de acceso constante a cualquier elemento, independientemente de su índice. Sin embargo, la sobrecarga adicional de las operaciones de cambio de tamaño puede ser significativa. El valor de estos costos depende de los parámetros del algoritmo de movimiento de matriz.
Existen varias estimaciones de los valores óptimos para los parámetros del algoritmo de movimiento, pero a partir de consideraciones generales, está claro que ninguno de los métodos para determinarlos garantiza la máxima eficiencia en todos los casos, y para cualquiera es posible elegir un algoritmo. para trabajar con una matriz, en la que el movimiento será ineficiente. Para compensar los efectos negativos, muchos lenguajes que admiten arreglos dinámicos tienen parámetros adicionales en los comandos de aumento/disminución del arreglo que le permiten controlar directamente la capacidad del arreglo. Si el programador sabe con certeza hasta qué tamaño aumentará/disminuirá la matriz como resultado de esta o aquella operación, y cómo se procesará la matriz en el futuro, puede especificar directamente la capacidad final requerida en el comando de cambio de tamaño, evitando así un gran número de movimientos sin sentido.
Hay muchos algoritmos para implementar arreglos dinámicos, además de los anteriores. Por lo tanto, es posible implementar otras diversas estructuras de datos con la ayuda de celdas de memoria dinámica conectadas por enlaces. La mayoría de estos métodos brindan una ventaja en algunas condiciones específicas, pero no brindan un tiempo de acceso constante o son incompatibles con matrices estáticas y, por lo tanto, no pueden funcionar con código de bajo nivel.
Las matrices dinámicas se utilizan para procesar conjuntos de datos homogéneos cuyo tamaño no se conoce exactamente en el momento de escribir el programa, pero que potencialmente pueden caber en la memoria disponible. Sin el uso de arreglos dinámicos, la solución de tales problemas se reduce a una de las siguientes estrategias:
La primera opción es aplicable solo cuando el tamaño del conjunto de datos cambia dentro de un rango pequeño y limitado (por ejemplo, en el procesamiento de textos, un límite de 1000 caracteres por línea es bastante aceptable). De lo contrario, la elección de una matriz pequeña limitará la funcionalidad del programa, y una grande (para que sea suficiente para cualquier dato de entrada) conducirá a un uso ineficiente de la memoria. El almacenamiento en búfer de procesamiento complica el algoritmo, y otras estructuras dinámicas en tareas de procesamiento de grandes secuencias de datos simples no se pueden comparar con una matriz en términos de eficiencia.
El uso de matrices dinámicas le permite asignar exactamente tanta memoria como sea realmente necesaria (inmediatamente, si la tarea le permite determinar la cantidad antes de cargar los datos, o durante la carga, expandiendo la matriz según sea necesario), cargue todos los datos en uno ordenarlos y procesarlos uniformemente. Sin embargo, esta estrategia también tiene desventajas:
En general, se puede ver que el soporte de arreglos dinámicos aumenta la conveniencia del desarrollo, pero no releva al programador de la necesidad de evaluar las necesidades de memoria del programa; también requiere comprender las características de una implementación específica de matrices dinámicas y hacer coincidir los algoritmos de procesamiento de datos con estas características.
Las matrices dinámicas son compatibles con Delphi , FreePascal , pero no con Turbo Pascal .
var byteArray : matriz de bytes ; // Array unidimensional multiArray : Array of Array of string ; // Matriz multidimensional ... empieza ... // Establece el tamaño de una matriz unidimensional en n elementos SetLength ( byteArray , n ) ; // El acceso a una matriz dinámica es similar al acceso a una normal. // La indexación siempre comienza desde cero, los índices siempre son números enteros. matriz de bytes [ 0 ] := 10 ; // Redimensionar a m elementos. EstablecerLongitud ( matriz de bytes , m ) ; ... // Establecer el tamaño de una matriz bidimensional en elementos X*Y SetLength ( multiArray , X , Y ) ; multiArray [ 7 , 35 ] := 'ru.wikipedia.org' ; ... final .No hay matrices dinámicas en el lenguaje C en sí, pero las funciones mallocde biblioteca estándar freele reallocpermiten implementar una matriz de tamaño variable:
int * mas = ( int * ) malloc ( tamaño de ( int ) * n ); // Crea una matriz de n elementos de tipo int ... mas = ( int * ) realloc ( mas , tamaño de ( int ) * m ); // Cambiar el tamaño de la matriz de n a m, manteniendo el contenido ... libre ( más ); // Liberando memoria después de usar la matrizLa desventaja de este enfoque es la necesidad de calcular el tamaño de la memoria asignada, aplicar una conversión de tipo explícita y monitorear cuidadosamente la vida útil de la matriz (como siempre ocurre con la memoria asignada dinámicamente en C).
Una matriz dinámica multidimensional se puede crear como una matriz de punteros a matrices:
int ** A = ( int ** ) malloc ( N * tamaño de ( int * )); para ( int yo = 0 ; yo < norte ; yo ++ ) { A [ i ] = ( int * ) malloc ( M * sizeof ( int )); }Sin embargo, el aumento de la dimensión complica significativamente los procedimientos para crear una matriz y liberar memoria una vez que se completa su uso. La tarea de cambiar el tamaño de una matriz a lo largo de una o más coordenadas se vuelve aún más complicada.
Algunos compiladores de C proporcionan una función de biblioteca no estándar void *alloca(size_t size)que facilita un poco el trabajo con matrices dinámicas. Esta función asigna memoria no en el montón, como malloc, sino en la pila, y esta memoria se libera automáticamente cuando se alcanza el operador return. Es decir, cuando esta función asigna una matriz dinámica, no es necesario eliminarla manualmente, pero dicha matriz no puede devolverse desde la función al punto de llamada.
Desde la versión del estándar C99 , se han introducido en el lenguaje arreglos de longitud variable. En la sintaxis habitual para declarar una matriz C, en lugar del tamaño de la matriz, no solo se puede indicar una constante, sino también una variable de tipo entero:
void func ( int arraySize ) { intmas [ tamaño de la matriz ] ; // Descripción de una matriz de longitud variable para ( int i = 0 ; i < arraySize ; ++ i ) { mas [ i ] = otraFunc ( i ); // Haciendo referencia a los elementos de la matriz } ... }Una matriz de longitud variable se puede establecer en cualquier tamaño deseado en el momento de la creación. La memoria para ello se asigna en la pila. Una matriz de longitud variable existe hasta que abandona el ámbito en el que se declaró, después de lo cual su memoria se libera automáticamente. Como en el caso anterior, una matriz de longitud variable no se puede devolver desde una función a la persona que llama.
La biblioteca estándar proporciona una clase de plantilla std::vector[1] que implementa la funcionalidad de una matriz dinámica:
// Declarar una matriz mas, que inicialmente contiene los números 1 - 5: std :: vector < int > mas = { 1 , 2 , 3 , 4 , 5 }; más _ reserva ( 100 ); // Reserve espacio de almacenamiento para al menos 100 artículos sin cambiar el tamaño real. El contenido sigue siendo el mismo. más _ cambiar el tamaño ( 50 ); // Establecer el tamaño explícito en exactamente 50 elementos. Los elementos faltantes obtendrán el valor "predeterminado", los elementos adicionales se eliminarán. mas [ yo ] = yo ; // Asignar i'th elemento el valor i mas . retroceder ( 100 ); // Agrega un elemento al final de la matriz int x = mas . atrás (); // Accede al último elemento, en este ejemplo x == 100 mas . pop_back (); // Elimina el último elemento std :: cout << mas . tamaño () << " " << mas . capacidad () << " \n " ; // Capacidad de visualización y tamaño real automático mas2 = mas ; // La variable mas2 contiene una copia completa de masstd::vectortiene muchos métodos y operadores, algunos de los cuales se muestran en el ejemplo anterior. Le permiten acceder por índice, cambiar el tamaño de la matriz en cualquier dirección, usarla como una pila, escribir un nuevo valor en una posición arbitraria de la matriz (con un cambio de los elementos restantes) y, en general, admiten la semántica. del tipo de valor : copiar, intercambiar, mover, transferir en una función y devolver, comparación de elementos con otro objeto del mismo tipo. No solo se gestiona el tamaño real, sino también la capacidad, lo que permite optimizar el proceso de asignación de memoria.
std::vectorimplementa el principio RAII : posee sus subobjetos, asigna y libera memoria y llama correctamente a los constructores/destructores.
El estándar C++ requiere una implementación para cumplir con las siguientes condiciones:
El trabajo de bajo nivel con memoria dinámica se puede implementar por medios heredados del lenguaje ancestral , pero para garantizar la seguridad de tipo y cumplir con los requisitos del modelo de objeto, se recomienda asignar memoria para matrices utilizando operadores específicos del lenguaje new []y delete []:
Entre otras cosas, esto le permite redefinir operadores para tipos definidos por el usuario new []y delete [], por lo tanto, implementar sus propios esquemas de asignación de memoria.
En C++ moderno, la administración manual de la memoria es una base esencial para implementar contenedores de plantillas, pero presenta dificultades significativas incluso para programadores experimentados y no se recomienda su uso en el código de la aplicación. [2] [3]
Estructuras de datos | |
---|---|
Liza | |
Árboles | |
cuenta | |
Otro |