jueves, 8 de mayo de 2008

INICIALIZACION INDIRECTA DE MATRICES



La iniciación indirecta de matrices iniciada en el apartado anterior. En concreto, detallamos aquí el caso en que la definición no puede hacerse en una sola sentencia, lo que ocurre con las matrices que deben ser almacenadas en el montón, y cuyas dimensiones no se conocen en el momento de la declaración.Hemos señalado que en estos casos, se define un puntero del tipo adecuado. Por ejemplo:char** a; // Ok. puede señalar a matriz de dos dimensiones!A continuación hay que reservar el espacio de almacenamiento, y finalmente, inicializar los miembros de forma individualizada. Para esto se requiere un poco de práctica y conocer ciertos trucos (cuyas claves se han apuntado antes) para no perderse en el proceso:· El identificador de una matriz se describe como un puntero. En particular, el identificador de matrices bidimensionales cuyo espacio de almacenamiento se localice en el montón, se describe como puntero-a-puntero (lo mismo para n dimensiones . · Los elementos se almacenan por filas . Esta frase encierra el secreto de crear y destruir matrices multidimensionales sin necesidad de "verlas", dado que las de más de tres dimensiones tienen una difícil representación mental. Como veremos en los ejemplos, el proceso para crear una matriz de n dimensiones (x1, x2, ... xn) empieza por crear la primera fila; una matriz de una dimensión con x1 elementos. §2 La creación de matrices de una dimensión ya se trató en el epígrafe dedicado al almacenamiento de matrices ( ; el razonamiento puede hacerse extensivo a matrices de n dimensiones. Por ejemplo, consideremos declarar matrices de tres dimensiones:void func() { int a1[2][4][3]; // L1: objeto de duración automática static int a2[2][4][3]; // L2: objeto persistente (estático)}En L1 tenemos un objeto a1 de duración automática alojado en la pila; en L2 tenemos un objeto estático a2 alojado en el segmento. Utilizaremos ahora el procedimiento de "C++ clásico" para crear una matriz ai de tres dimensiones en el montón. En primer lugar, recordaremos que desde ambas ópticas (puntero ↔ matriz), se tiene:ai: Puntero-a-puntero-a-puntero-a-int ↔ matriz de una fila de dos elementos (la llamaremos bi), cada uno de los cuales es una matriz. Traducido a código:int*** ai = (int***) calloc(2, sizeof(int**)); // I:Traducido a lenguaje coloquial: ai es una variable automática (4 bytes en la pila) de tipo puntero-a-puntero-a-puntero-a-int, señalando un espacio bi (8 bytes en el montón) capaz para albergar dos punteros-a-puntero-a-int.La situación se ha esquematizado en la figura 1.bi: Puntero-a-puntero-a-int ↔ matriz de una fila de cuatro elementos, cada uno de ellos es una matriz (la llamaremos ci) de una dimensión. Traducido a código:int** bi = (int**) calloc(4, sizeof(int*)); // II:Coloquialmente: bi es una variable automática (4 bytes en la pila) de tipo puntero-a-puntero-a-int, señalando un espacio ci de 16 bytes en el montón, que es capaz para albergar 4 punteros-a-int.ci: Puntero-a-int ↔ matriz de una fila de tres elementos, cada uno de los cuales: es un int. Traducido a código:int* ci = (int*) calloc(3, sizeof(int)); // III:En lenguaje coloquial: ci es una variable automática (4 bytes en la pila) de tipo puntero-a-int, señalando un espacio ci de 12 bytes en el montón, capaz para albergar 3 int.§2.1 Poniendo las sentencias anteriores en forma de una secuencia ejecutable, resultaría:int*** ai = (int***) calloc(2, sizeof(int**)); // L1:int i, j;for (i = 0; i < j =" 0;" 8 =" 24">EjemplosComo caso concreto, se muestra la definición indirecta de una matriz de dos dimensiones matriz[m][n] en el montón. Se ha previsto para tres filas y cinco columnas matriz[3][5]; sus elementos son del tipo long double, pero puede ser adaptada fácilmente para aceptar entradas de usuario en tiempo de ejecución, de forma que se acepten otros tamaños y/u otros tipos.Se presenta en dos versiones: la primera utiliza las funciones de librería clásicas de asignación y liberación de espacio (calloc y free). La segunda más moderna , utiliza las nuevas funciones de librería (new y delete). Finalmente, a título comparativo, se incluye una tercera versión que supone la definición directa utilizando el nuevo operador new[] para matrices .§3.1 Versión C clásicoEl proceso es análogo al esquematizado en la figura anterior aunque más sencillo (carece de la tercera fase), ya que la matriz a crear es de dos dimensiones. Consistirá en la creación de los siguientes objetos:· En el montón: o 3 matrices de 5 long double cada una, creadas en el bucle M4 (contienen los "datos" de la matriz). o 1 matriz 3 punteros (creada en M3). Cada uno de sus miembros señala al primer elemento de una de las matrices anteriores (la asignación se realiza en M5), por lo que son de tipo long double*. · En la pila: o Un puntero que señala al primer miembro de la matriz anterior, por lo que su tipo es puntero-a-puntero-a-long double (long double**). #include // Versión C clásico#include // para free & calloctypedef long double TIPO; // L3:typedef TIPO** OBJETO;unsigned int fil = 3, col = 5; // L5:void des_asigna(OBJETO); // L6: prototipoint main(void) { // =============== unsigned int i, j; OBJETO matriz; // M2: matriz = (OBJETO) calloc(fil, sizeof(TIPO*)); for (i = 0; i < i =" 0;" j =" 0;" i =" 0;" j =" 0;" i =" 0;">.En M5 se reserva memoria para contener col objetos tipo TIPO (long double). El proceso se realiza 3 veces, una para cada fila. Al final del bucle M4 se han reservado 3 espacios para 5 long double cada uno. Las direcciones de inicio de estos bloques se asignan a cada miembro de la matriz creada en M3.Podríamos considerar que M5 crea tres matrices de 5 elementos cada una. Este conjunto de 15 elementos constituiría la seudo-matriz m[3][5] cuyos miembros se inician con valores arbitrarios en el bucle M7.El bucle M11 es análogo al anterior (un bucle de 5 iteraciones anidado en otro de 3), y se encarga de mostrar los valores asignados.M16 invoca a la función encargada de rehusar el espacio previamente asignado en M3 y M5. Observe que el proceso se realiza en orden inverso al de asignación. El bucle F2 realiza la tarea inversa a la que se realizó en M4. F3 desasigna los espacios asignados en M5. A continuación F4 deshace la asignación realizada en M3.Finalmente el objeto matriz y el resto de objetos automáticos serán destruidos en M17 al salir de main.


MATRICES DE MATRICES

Las matrices de matrices (matrices multidimensionales), son simplemente matrices cuyos elementos son otras matrices. C++ no dispone de elementos incluidos en el propio lenguaje para manejar matrices ; mucho menos matrices multidimensionales. Sin embargo, la Librería Estándar proporciona potentes recursos para manejo de vectores y matrices. En concreto, existen una serie de clases predefinidas para este fin: vector, list, valarray, string, etc., dotadas de interfaces muy completas y cómodas para el programador.
El propio creador del C++ reconoce que el manejo de matrices multidimensionales "a pelo", tal como están implementadas en el lenguaje, son una importante fuente de errores y confusión, especialmente para el principiante. Aparte de una posible pesadilla para futuros mantenedores de tales programas, a no ser que estén cuidadosamente documentados. En consecuencia, aconseja utilizar en lo posible los recursos ofrecidos por la Librería Estándar.
No obstante lo anterior, incluso para manejar las Librerías, es imprescindible un mínimo conocimiento de las capacidades incluidas en el lenguaje: "cómo considera en realidad C++ las matrices multidimensionales"; aspecto este al que dedicamos el resto del epígrafe.
Declaración
Las matrices multidimensionales son matrices de matrices (matrices cuyos elementos son matrices):
int dias[2][12] = {
{31,28,31,30,31,30,31,31,30,31,30,31},
{31,29,31,30,31,30,31,31,30,31,30,31} };
char lista[5][4] = { "Cat", "Bar", "Cab", "Lab", "Tab" };
Sintaxis:
La forma de declararlas es mediante una serie de expresiones entre corchetes [
1]:
tipoX etiqueta [][]...[exp-cn]
Ejemplo:
int ai[2][3+1][6];
Comentario
El resultado de las expresiones representan el tamaño de cada submatriz, y deben reducirse a enteros positivos (unsigned int) distintos de cero. Cada submatriz es una "dimensión" de la matriz total. En el caso de matrices de dos dimensiones, y se denominan respectivamente filas y columnas. En el caso de más de dos dimensiones, la expresión se denomina sencillamente "dimensión-n".
El número total de elementos de una matriz de n dimensiones es el producto de los valores de todas las dimensiones. En el ejemplo anterior, la matriz ai tiene 2 x 4 x 6 = 48 elementos.
§4
Acceso mediante subíndices
Lo mismo que en el caso de matrices unidimensionales, existen dos formas para acceder los elementos de matrices multidimensionales: mediante subíndices y mediante punteros. En cuanto a la primera, la forma de designar un elemento es: a[f][c], el primer subíndice indica la fila y el segundo la columna.
Los elementos se almacenan por filas, de forma que el subíndice de la derecha (de columna) varía más rápidamente si los elementos se recorren en orden de almacenamiento. En realidad, una matriz bidimensional como dias, es para el compilador una matriz de una sola fila (dimensión) de elementos contiguos, en la que cada elemento es a su vez una matriz. En este caso una matriz de dos elementos, cada uno de los cuales es a su vez matriz de 12 elementos.
El razonamiento puede hacerse recursivo, de forma que una matriz de n dimensiones es una matriz de una sola fila, cada uno de cuyos x1 elementos es a su vez una matriz de una fila de x2 elementos, cada uno de los cuales...
Si aplicamos este razonamiento a una matriz m de enteros de tres dimensiones:
· int m[X][Y][Z]; Es la declaración de la matriz;
· m[a][b][c] Es un elemento de la matriz (en este caso un int)
· m Es el identificador de la matriz. Para algunos efectos puede ser considerado identificativo de la matriz como un todo. Para otros efectos puede ser considerado como un puntero que señala al elemento m[0][0][0]: m == &m[0][0][0]
· m[0][0][0] Es el primer elemento de la matriz
· m[X-1][Y-1][Z-1] Es el último elemento.
§5
Posición de un elemento
Al tratar de punteros y matrices de una dimensión , vimos que la posición Pi (contada en elementos respecto al comienzo) del elemento m[ i ] de una matriz de elementos tipoX es simplemente Pi = i.
En caso de una matriz bidimensional m[A][B], la posición Pa,b respecto del comienzo del elemento m[a][b] viene determinado por: Pa,b = ( a * B ) + b. Es decir: (fila x total de columnas) + columna.
En el caso del ejemplo anterior, el elemento dias[1][1] (número 29) está en la posición: P1,1 = (1 * 12)+ 1 == 13. Lo que significa que, para alcanzar el almacenamiento del número 29, desde la posición inicial (número 31), hay que desplazarse internamente en la memoria el espacio correspondiente a 13 int. (ver demostración .
Por su parte, el elemento lista[3][0] (carácter 'L'), está en la posición P3,0 = (3 * 4) + 0 == 12 desde el carácter inicial 'C'. Recuerde que cada submatriz de lista es una cadena de 4 elementos, el último es el terminador de cadena "\0" (no representado).
El razonamiento puede ser extendido a la posición Pa,b,c..z de cualquier elemento m[a][b][c]...[z] de una matriz multidimensional m[A][B][C]...[Z], siendo sus dimensiones A, B, C... Z:
Pa,b,c...z == a · (B·C·D·E·.. ·Z) + b · (C·D·E·...·Z) + d · (D·E·...·Z) + .... + z 5.a
Nota: el punto · (aunque apenas visible) representa aquí el símbolo de multiplicar. Los términos B·C·D·E...Z representan productos de las dimensiones de la matriz.
§5.1 Una observación importante es que la posición de un elemento no viene influenciada por el valor de la primera dimensión A (que no aparece en la fórmula), lo que tiene cierta trascendencia cuando se trata de pasar matrices como argumento de funciones .
Desde este punto de vista, una matriz m1 definida como:
int m1[2][4][3];
tiene 2 x 4 x 3 = 24 elementos tipo int, cada uno de los cuales ocupa 4 bytes. La matriz ocupa 24 x 4 = 96 bytes . Su almacenamiento se esquematiza en la figura 1 (en su interior se ha representado la posición del elemento m1[1][2][0]). La posición de cualquier elemento m1[a][b][c] viene determinada por la expresión:
Pa,b,c == a * (4*3) + b (3) + c == a * 12 + b * 3 + c
El elemento m1[0][0][0] está en la posición P0,0,0 == 0 * 12 + 0 * 3 + 0 == 0. El elemento m1 [1][2][0] en la posición P1,2,0 == 1 * 12 + 2 * 3 + 0 == 18, y el último elemento, m1[1][3][2] en la posición P1,3,2 == 1 * 12 + 3 * 3 + 2 == 23
§6
Acceso a elementos mediante punteros
El operador de elemento de matriz [ ] se define como:
[exp2] ↔ *((exp1) + (exp2))
donde exp1 es un puntero y exp2 es un entero, o exp1 es un entero y exp2 es un puntero. Según esta definición la expresión del elemento a de una matriz m, m[a] sería equivalente a la expresión *(m + a).
§6.1 Hemos señalado
que en una matriz bidimensional m[A][B], cada elemento m[a] de la primera (y en realidad única fila), es una matriz m1 de B elementos, en la que el elemento m1[b] sería equivalente a *(m1 + b).
Sustituyendo en esta última fórmula m1 por su equivalente, se tiene:
m1[b] ↔ m[a][b] ↔ *( *( m + a ) + b )
El razonamiento puede extenderse a matrices de cualquier número de dimensiones . De forma que en general, se tiene la siguiente serie de equivalencias:
Matriz de una dimensión: m[a] == *( m + a )
Matriz de 2 dimensiones: m[a][b] == *( *( m + a ) + b )
Matriz de 3 dimensiones: m[a][b][c] == *( *( *( m + a ) + b ) + c )
Matriz de 4 dimensiones: m[a][b][c][d] == *( *( *( *( m + a ) + b ) + c )+ d)
etc.
Ejemplo:
char m[][5] = {{'a','e','i','o','u'},{'A','E','I','O','U'}};cout << "Caracter " <<> Caracter Ocout << "Caracter " << *(*(m+1)+3); // -> Caracter O int dia[][5][2] = { {{ 0, 1},{ 10, 11},{ 20, 21},{ 30, 31},{ 40, 41}}, {{100,101},{110,111},{120,121},{130,131},{140,141}} };int a =0, b=2, c= 0;cout << "dia [0,2,0] = " << *(*(*(dia+a)+b)+c); // -> dia [0,2,0] = 20a= 0; b=3; c=1;cout << "dia [0,3,1] = " << *(*(*(dia+a)+b)+c); // -> dia [0,3,1] = 31a =1; b=2; c= 0;cout << "dia [1,2,0] = " << *(*(*(dia+a)+b)+c); // -> dia [1,2,0] = 120
§6.2 Si nos referimos al primer elemento de una matriz de una, dos, tres, etc. dimensiones, las expresiones anteriores se reducen a:
Matriz de una dimensión: m[0] == *( m + 0 ) == *m
Matriz de 2 dimensiones: m[0][0] == *( *( m + 0 ) + 0 ) == **m
Matriz de 3 dimensiones: m[0][0][0] == *( *( *( m + 0 ) + 0 ) + 0 ) == ***m
Matriz de 4 dimensiones: m[0][0][0][0] == *( *( *( *( m + 0 ) + 0 ) + 0 )+ 0) == ****m
etc.
§6.3 Si aplicamos el operador de referencia & a cada lado de las relaciones de equivalencia anteriores, y tenemos en cuenta que la dirección del primer elemento &m[0] es igual a la dirección de la matriz &m, y que los operadores &* se anulan entre sí:
Matriz de una dimensión: &m[0] == &m == m §6.3a
Matriz de 2 dimensiones: &m[0][0] == &m == *m
Matriz de 3 dimensiones: &m[0][0][0] == &m == **m
Matriz de 4 dimensiones: &m[0][0][0][0] == &m == ***m
etc.
La equivalencia 6.3a referida a matrices unidimensionales ya la habíamos visto anteriormente enunciada como: el identificador de una matriz es considerado un puntero a su primer elemento.
Ejemplo:
char m1[] = {'a','e','i','o','u'};cout << "Caracter " << *m1; // -> Caracter acout << "Caracter " <<> Caracter a char m2[][5] = {{'a','e','i','o','u'},{'A','E','I','O','U'}};cout << "Caracter " << **m2; // -> Caracter acout << "Caracter " <<> Caracter a
§6.4 La comprensión del significado de las relaciones §6.1 a §6.3, es de la mayor trascendencia para el manejo de matrices (especialmente multidimensionales). Pueden ser enunciadas de varias formas, aunque en el fondo todas son equivalentes. Por ejemplo, las relaciones §6.3 pueden ser expresadas diciendo: la dirección del primer elemento de una matriz puede obtenerse de la siguiente forma:
Matrices unidimensionales: mediante su identificador m.
Matrices bidimensionales: mediante la indirección del identificador *m.
Matrices tridimensionales: mediante la doble indirección del identificador **m.
Matrices n-dimensionales: mediante la n-1 indirección del identificador.
§6.5 Teniendo en cuenta que cualquiera que sean las dimensiones de una matriz, el tipo de la dirección a uno de sus elementos es tipoX* (puntero-a-tipoX), las equivalencias contenidas en los enunciados anteriores, pueden traducirse en afirmar que el tipo del identificador m de una matriz de objetos tipoX es:
Matrices unidimensionales: tipoX* (puntero-a-tipoX).
Matrices bidimensionales: tipoX** (puntero-a-puntero-a-tipoX).
Matrices tridimensionales: tipoX*** (puntero-a-puntero-a-puntero-a-tipoX).
etc.
Expresado en otras palabras:
Declaración
Descripción de m
Tipo de m
Descripción del tipo
tipoX m[a];
Matriz unidimensional de objetos tipoX
tipoX*
Puntero-a-tipoX
tipoX m[a][b];
Matriz bidimensional de objetos tipoX
tipoX**
Puntero-a-puntero-tipoX
tipoX m[a][b][c];
Matriz tridimensional de objetos tipoX
tipoX***
Puntero-a-puntero-a-puntero-tipoX
Etc.



Ver comentario más extenso respecto de estas afirmaciones:
[1] Las expresiones con comas, del tipo m[x, y, z], utilizadas en otros lenguajes para representar elementos de matrices, no están permitidas en C++.
[2] Nos referimos a matrices como un todo. En cambio si se dispone de instrumentos para manejo de "elementos" de matrices. Existen no obstante, algunas excepciones notables. Por ejemplo, algunos casos de inicialización, donde son posibles sentencias del tipo:
int m[5] = { 'A','E','I','O','U'};
Ver en página siguiente "Iniciación conjunta"

MATRICES DE PUNTEROS



Como cualquier otra variable, los punteros (del mismo tipo) pueden estar contenidos en matrices; las declaraciones tienen la forma:
int* iptr[10] // matriz de 10 punteros a enterochar* cptr[10]; // matriz de 10 punteros a caráctervoid (* fptr[10])(char); // matriz de 10 punteros a función...
Con estas declaraciones, iptr[i] es un puntero a entero, y cptr[j] un puntero a carácter, mientras que *iptr[i] es el int al que apunta el elemento i de la matriz. Del mismo modo, *cptr[j] es el char apuntado por el elemento j.
En el tercer caso, fptr es una matriz de 10 punteros a funciones que reciben un char y devuelven void. Es decir, funciones del tipo:
void funcion (char ch) { /* ... */ }
§1.1 No confundir una matriz de punteros con un puntero a matriz. Observe como los paréntesis cambian totalmente el significado de las sentencias :
int* pt1[10] // matriz de 10 punteros a enteroint (*pt2)[10] // puntero a matriz de 10 enterosint (* pt3[10])(); // matriz de 10 punteros a función
Nota: la función main (argc, agrv) presenta un primer ejemplo de ambos casos: "Puntero a matriz de punteros" y "Matriz de punteros". En concreto, el argumento argv es un puntero a una matriz de punteros.
§2 Inicialización
Es interesante considerar que una matriz de punteros puede definirse directamente en la propia declaración. Consideremos el siguiente caso, que define un matriz de cuatro punteros a matrices de caracteres:
char* est[] = {"Primavera", "Verano", "Otoño", "Invierno"};
El proceso sería como sigue: el compilador crea cuatro cadenas de caracteres de 10, 7, 6 y 9 caracteres respectivamente y las almacena en algún sitio (pueden estar contiguas o no). También crea un matriz de 4 punteros a carácter con nemónico est; sus elementos est[0] a est[3] (que si son contiguos) son las direcciones de los caracteres "P", "V", "O" e "I" respectivamente.
§3 El ejemplo que sigue define un matriz de dos punteros a función:
void f1(int); // L.1:void f2(int); // L.2:void (* apt[])(int) = {f1, f2}; // L.3:
Las líneas L.1 y L.2 no requieren comentario, se trata simplemente de los prototipos de dos funciones .
L.3 define apt como una matriz de punteros-a-función que reciben un int y devuelven void (exactamente del tipo de las funciones f1 y f1 declaradas en las líneas anteriores). Esta línea también inicia la matriz apt con dos valores.
Observe como los nombres de las funciones se han tomado como sinónimo de sus "direcciones", y que no ha sido necesario declarar el tamaño de la matriz, el compilador lo deduce (dos elementos) por el Rvalue de la propia inicialización.
§4 También es importante señalar que para que sus direcciones puedan almacenarse en una matriz, todas las funciones deben recibir los mismos argumentos y devolver el mismo valor, lo que nos deja que deben diferenciarse forzosamente en sus nombres (f1 y f2 en este caso), además del las diferencias que pueda haber en sus cuerpos.
5 Las matrices de punteros a funciones son especialmente interesantes. Este tipo de matrices representan tablas de funciones, con la que se puede manejar alternativas; en lugar de utilizar las clásicas sentencias del tipo if... then else , se ejecuta una u otra función en base a una variable. Esta forma de codificación se denomina "código dirigido por tablas.

§6 Tema relacionado
Matrices de estructuras .

COMENTARIOS ESPECIFICOS


La matriz m1 es de naturaleza estática, ya que es global al fichero . La especificación de tipo de almacenamiento está implícita en la propia situación de la declaración en L.2, fuera de cualquier función.
La matriz m2 es estática por la utilización explícita del especificador static en L.8; el compilador se encarga de reservar espacio adecuado en el segmento de datos. La matriz conserva sus valores entre posibles invocaciones sucesivas a func.
m3 es una alternativa a la anterior. Se declara un puntero-a-int de naturaleza estática; este puntero es almacenado en el segmento, por lo que conservará su valor entre llamadas sucesivas a la función. A su vez este puntero señala a un espacio en el montón; espacio que ha sido reservado mediante el operador new, así que los valores almacenados serán también persistentes.
Nota: el ejemplo es méramente didáctico y no sería operativo. Existe una diferencia adicional importante entre las opciones L.8/L.9. En L.8 el espacio es reservado una sola vez por el compilador, y es iniciado una sola vez por el módulo de inicio. En cambio, la opción L.9 reservaría un nuevo espacio con cada sucesiva invocación a la función.

La matriz m4 es de naturaleza automática, se creará en la pila y será iniciada y destruida con cada invocación a func.
Dejando aparte la declaración estática en sus dos versiones, implícita (L.2) o explícita (L.8), para crear una matriz en el montón existen dos procedimientos, según el tipo de funciones utilizadas. El que denominaremos "clásico", que utiliza las funciones "modernas" que utilizaría los operadores new / delete respectivamente. En ambos casos la referencia al elemento creado se realiza mediante un puntero.

Para crear la matriz int
m1[10] en el montón según el sistema moderno, se utilizaría la expresión:
int* m1;m1 = new int[10];
La consecuencia de estas sentencias es que se declara un puntero-a-int denominado m1; esta variable se crea en la pila (es automática), y a este Lvalue se le asigna el valor devuelto por el operador "new " que es la dirección de un espacio de memoria reservado en el montón.

El resultado sería la situación de memoria esquematizada en la figura 3, con la diferencia respecto al caso de las matrices estáticas anteriores (L.2 y L.8
), de que ahora existen dos objetos: un puntero en la pila, y un espacio anónimo en el montón (accesible mediante el puntero). El inconveniente es que hay que acordarse de desasignar la zona de memoria del montón cuando resulte innecesaria [2]. Esto se realizaría con la sentencia:
delete[] m1;

Procedimiento para crear la misma matriz según el sistema clásico:
int* m1 = (int*) calloc(10, sizeof(int));
También en este caso m1 se declara como puntero-a-int, y se le asigna el valor devuelto por la función calloc; el resultado sería idéntico al anterior. Observe que en este caso se necesita un modelado "casting" antes de la asignación a m1, porque el valor devuelto por calloc es un puntero-a-void, y no se puede asignar directamente a un puntero-a-int. La versión actualizada del modelado sería:
int* m1 = static_cast( calloc(10, sizeof(int)) );
§4.2.1 Como se comprueba a continuación, aunque se haya definido en términos de puntero-a-int, nada impide considerar a m1 como una auténtica matriz a todos los efectos (puede aplicársele la notación de subíndices):
#include int main() { // ============= int* m = new int[5]; for (int i = 0; i <5>No obstante lo anterior, considere los sorpresivos resultados del siguiente programa que crea tres matrices, m1, m2 y m3 utilizando los tres métodos señalados anteriormente:
#include using namespace std;void main() { // ============= int m1[5]; int* m2 = new int[5]; int* m3 = static_cast( calloc(5, sizeof(int)) ); cout << x =" 5;m2">
Inicio.
[1] Funciones de la Librería Estándar C++ que son en realidad herencia del C clásico.
[2] Cuando se reserva espacio en el montón mediante sentencias como new o calloc, junto con el propio espacio, el compilador incluye alguna información adicional, como el tamaño del área reservada. Por esta razón puede desasignar todo el espacio con una sola instrucción, delete o free, indicando solo el punto de comienzo del área a liberar.
Inicio

La matriz m1 es de naturaleza estática, ya que es global al fichero . La especificación de tipo de almacenamiento está implícita en la propia situación de la declaración en L.2, fuera de cualquier función.
La matriz m2 es estática por la utilización explícita del especificador static en L.8; el compilador se encarga de reservar espacio adecuado en el segmento de datos. La matriz conserva sus valores entre posibles invocaciones sucesivas a func.
m3 es una alternativa a la anterior. Se declara un puntero-a-int de naturaleza estática; este puntero es almacenado en el segmento, por lo que conservará su valor entre llamadas sucesivas a la función. A su vez este puntero señala a un espacio en el montón; espacio que ha sido reservado mediante el operador new, así que los valores almacenados serán también persistentes.
Nota: el ejemplo es méramente didáctico y no sería operativo. Existe una diferencia adicional importante entre las opciones L.8/L.9. En L.8 el espacio es reservado una sola vez por el compilador, y es iniciado una sola vez por el módulo de inicio. En cambio, la opción L.9 reservaría un nuevo espacio con cada sucesiva invocación a la función.
La matriz m4 es de naturaleza automática, se creará en la pila y será iniciada y destruida con cada invocación a func.
Dejando aparte la declaración estática en sus dos versiones, implícita (L.2) o explícita (L.8), para crear una matriz en el montón existen dos procedimientos, según el tipo de funciones utilizadas. El que denominaremos "clásico", que utiliza las funciones calloc / free [1] y el "moderno" que utilizaría los operadores new / delete respectivamente. En ambos casos la referencia al elemento creado se realiza mediante un puntero.
Para crear la matriz int m1[10] en el montón según el sistema moderno, se utilizaría la expresión:
int* m1;m1 = new int[10];
La consecuencia de estas sentencias es que se declara un puntero-a-int denominado m1; esta variable se crea en la pila (es automática), y a este Lvalue se le asigna el valor devuelto por el operador new que es la dirección de un espacio de memoria reservado en el montón.
El resultado sería la situación de memoria esquematizada en la figura 3, con la diferencia respecto al caso de las matrices estáticas anteriores (L.2 y L.8 ), de que ahora existen dos objetos: un puntero en la pila, y un espacio anónimo en el montón (accesible mediante el puntero). El inconveniente es que hay que acordarse de desasignar la zona de memoria del montón cuando resulte innecesaria [2]. Esto se realizaría con la sentencia:
delete[] m1;
Procedimiento para crear la misma matriz según el sistema clásico:
int* m1 = (int*) calloc(10, sizeof(int));
También en este caso m1 se declara como puntero-a-int, y se le asigna el valor devuelto por la función calloc; el resultado sería idéntico al anterior. Observe que en este caso se necesita un modelado ("casting"
4.9.9) antes de la asignación a m1, porque el valor devuelto por calloc es un puntero-a-void, y no se puede asignar directamente a un puntero-a-int. La versión actualizada del modelado sería:
int* m1 = static_cast( calloc(10, sizeof(int)) );
Como se comprueba a continuación, aunque se haya definido en términos de puntero-a-int, nada impide considerar a m1 como una auténtica matriz a todos los efectos (puede aplicársele la notación de subíndices):
#include int main() { // ============= int* m = new int[5]; for (int i = 0; i <5>
No obstante lo anterior, considere los sorpresivos resultados del siguiente programa que crea tres matrices, m1, m2 y m3 utilizando los tres métodos señalados anteriormente:
#include using namespace std;void main() { // ============= int m1[5]; int* m2 = new int[5]; int* m3 = static_cast( calloc(5, sizeof(int)) ); cout << x =" 5;m2">
Inicio.
[1] Funciones de la Librería Estándar C++ que son en realidad herencia del C clásico.
[2] Cuando se reserva espacio en el montón mediante sentencias como new o calloc, junto con el propio espacio, el compilador incluye alguna información adicional, como el tamaño del área reservada. Por esta razón puede desasignar todo el espacio con una sola instrucción, delete o free, indicando solo el punto de comienzo del área a liberar.

miércoles, 7 de mayo de 2008

DEFINICION DE MATRIZ

Desde el punto de vista del programa, una matriz (array ó vector) es una zona de almacenamiento contiguo, que contiene una serie de elementos del mismo tipo, los elementos de la matriz .
Desde el punto de vista lógico podemos considerarlas como un conjunto de elementos ordenados en fila. Así pues, en principio todas las matrices son de una dimensión, la dimensión principal, pero veremos que los elementos de esta fila pueden ser a su vez matrices (un proceso que puede ser recursivo), lo que nos permite hablar de la existencia de matrices multi-dimensionales, aunque las más fáciles de "ver" o imaginar son las de dos y tres dimensiones.

Aunque en C/C++ los conjuntos ordenados de elementos del mismo tipo se denomina matrices (arreglos), la idea aparece en otros lenguajes bajo distintos nombres. Por ejemplo, vector; lista ("list") o secuencia ("sequence"). En cualquier caso, no confundirlas (las matrices) con los conjuntos de pares nombre/valor, que existen en otros lenguajes bajo los nombres de diccionarios ("dictionarys"); tablas hash ("hash tables"); listas indexadas ("keyed lists") o matrices asociativas ("associative arrays"), pero que como tales, no existen en C++; aunque la Librería Estándar sí dispone de tales estructuras.

En C++ el compilador desconoce el tamaño de la matriz, de forma que el programador debe adoptar precauciones para no salir de sus límites, ya que el compilador permite referenciar elementos inexistentes, más allá del final de la matriz, con el consiguiente riesgo de error.
Puede afirmarse que las matrices son un recurso de programación simple y socorrido; en realidad pueden considerarse como las "estructuras" de datos más simples que cabe imaginar (todos los elementos del mismo tipo). Presentan la ventaja de que sus elementos son rápidamente accesibles, en especial si utiliza punteros en vez de subíndices, pero presentan una notable limitación: son de tamaño fijo; es preciso definir su tamaño desde el principio y no pueden ser fácilmente incrementadas o disminuidas sino mediante complejos procesos de copia.

Estas estructuras de datos son adecuadas para situaciones en las que el acceso a los datos se realice de forma aleatoria e impredecible. Por el contrario, si los elementos pueden estar ordenados y se va a utilizar acceso secuencial sería más adecuado utilizar una lista.

Las matrices C++ se consideran tipos complejos, y se alojan en zonas de memoria contiguas, aunque tendremos ocasión de ver que C++ permite definir unas seudo-matrices que en realidad no se almacenan de esta forma. Los programadores que hayan utilizado lenguajes con matrices dinámicas (incluso multi-dimensionales), sentirán una especial frustración con las limitaciones de las matrices C++. Sin embargo, el C++ Estándar ofrece en su Librería alternativas interesantes a las matrices; en especial las clases vector, string (adaptada al manejo de cadenas de caracteres), list, deque y valarray. Esta última especialmente optimizada para el manejo de matrices numéricas.

Antes de abordar cualquier proyecto medianamente importante en el que se requiera el uso intensivo de matrices, se aconseja vivamente evaluar las posibilidades que ofrecen al respecto las estructuras de la Librería Estándar, en especial si se trata de matrices cuyo tamaño deba cambiar, o no pueda ser conocido en tiempo de compilación.