C , 9ª parte, Datos Derivados: Vectores, matrices y cadenas de caracteres.

Un array (también conocido como arreglo, vector o matriz) es un modo de manejar una gran cantidad de datos del mismo tipo bajo un mismo nombre o identificador. Por ejemplo, mediante la sentencia:

double a[10];

se reserva espacio para 10 variables de tipo double. Las 10 variables se llaman a y se accede a una u otra por medio de un subíndice, que es una expresión entera escrita a continuación del nombre entre corchetes […]. La forma general de la declaración de un vector es la siguiente:

tipo nombre[numero_elementos];

Los elementos se numeran desde 0 hasta (numero_elementos-1). El tamaño de un vector puede definirse con cualquier expresión constante entera. Para definir tamaños son particularmente útiles las constantes simbólicas. Como ya se ha dicho, para acceder a un elemento del vector basta incluir en una expresión su nombre seguido del subíndice entre corchetes. En C no se puede operar con todo un vector o toda una matriz como una única entidad, sino que hay que tratar sus elementos uno a uno por medio de bucles for o while. Los vectores (mejor dicho, los elementos de un vector) se utilizan en las expresiones de C como cualquier otra variable. Ejemplos de uso de vectores son los siguientes:


a[5] = 0.8;
a[9] = 30. * a[5];
a[0] = 3. * a[9] - a[5]/a[9];
a[3] = (a[0] + a[9])/a[3];

Una cadena de caracteres no es sino un vector de tipo char, con alguna particularidad que conviene resaltar. Las cadenas suelen contener texto (nombres, frases, etc.), y éste se almacena en la parte inicial de la cadena (a partir de la posición cero del vector). Para separar la parte que contiene texto de la parte no utilizada, se utiliza un carácter fin de texto que es el carácter nulo (”) según el código ASCII. Este carácter se introduce automáticamente al leer o inicializar las cadenas de caracteres, como en el siguiente ejemplo:


char ciudad[20] = "San Sebastián";

donde a los 13 caracteres del nombre de esta ciudad se añade un decimocuarto: el ”. El resto del espacio reservado –hasta la posición ciudad[19]– no se utiliza. De modo análogo, una cadena constante tal como “mar” ocupa 4 bytes (para las 3 letras y el ”).

Las matrices se declaran de forma análoga, con corchetes independientes para cada subíndice. La forma general de la declaración es:


tipo nombre[numero_filas][numero_columnas];

donde tanto las filas como las columnas se numeran también a partir de 0. La forma de acceder a los elementos de la matriz es utilizando su nombre, seguido de las expresiones enteras correspondientes a los dos subíndices, entre corchetes.

En C tanto los vectores como las matrices admiten los tipos de las variables escalares (char, int, long, float, double, etc.), y los modos de almacenamiento auto, extern y static, con las mismas características que las variables normales (escalares). No se admite el modo register. Los arrays static y extern se inicializan a cero por defecto. Los arrays auto pueden no inicializarse: depende del compilador concreto que se esté utilizando.

Las matrices en C se almacenan por filas, en posiciones consecutivas de memoria. En cierta forma, una matriz se puede ver como un vector de vectores-fila. Si una matriz tiene N filas (numeradas de 0 a N-1) y M columnas (numeradas de 0 a la M-1), el elemento (i, j) ocupa el lugar:

posición_elemento(0, 0) + i * M + j

A esta fórmula se le llama fórmula de direccionamiento de la matriz. En C pueden definirse arrays con tantos subíndices como se desee. Por ejemplo, la sentencia,


double a[3][5][7];

declara una hipermatriz con tres subíndices, que podría verse como un conjunto de 3 matrices de dimensión (5×7). En la fórmula de direccionamiento correspondiente, el último subíndice es el que varía más rápidamente.

Como se verá más adelante, los arrays presentan una especial relación con los punteros. Puesto que los elementos de un vector y una matriz están almacenados consecutivamente en la memoria, la aritmética de punteros descrita previamente presenta muchas ventajas. Por ejemplo, supóngase el código siguiente:


int vect[10], mat[3][5], *p;
p = &vect[0];
printf("%d\n", *(p+2)); // se imprimirá vect[2]
p = &mat[0][0];
printf("%d\n", *(p+2)); // se imprimirá mat[0][2]
printf("%d\n", *(p+4)); // se imprimirá mat[0][4]
printf("%d\n", *(p+5)); // se imprimirá mat[1][0]
printf("%d\n", *(p+12)); // se imprimirá mat[2][2]

RELACIÓN ENTRE VECTORES Y PUNTEROS

Existe una relación muy estrecha entre los vectores y los punteros. De hecho, el nombre de un vector es un puntero (un puntero constante, en el sentido de que no puede apuntar a otra variable distinta de aquélla a la que apunta) a la dirección de memoria que contiene el primer elemento del vector. Supónganse las siguientes declaraciones y sentencias:


double vect[10]; // vect es un puntero a vect[0]
double *p;
...
p = &vect[0]; // p = vect;
...

El identificador vect, es decir el nombre del vector, es un puntero al primer elemento del vector vect[ ]. Esto es lo mismo que decir que el valor de vect es &vect[0]. Existen más puntos de coincidencia entre los vectores y los punteros:

  • Puesto que el nombre de un vector es un puntero, obedecerá las leyes de la aritmética de punteros. Por tanto, si vect apunta a vect[0], (vect+1) apuntará a vect[1], y (vect+i) apuntará a vect[i].
  • Recíprocamente (y esto resulta quizás más sorprendente), a los punteros se les pueden poner subíndices, igual que a los vectores. Así pues, si p apunta a vect[0] se puede escribir:
    p[3]=p[2]*2.0; // equivalente a vect[3]=vect[2]*2.0;
  • Si se supone que p=vect, la relación entre punteros y vectores puede resumirse como se indica en las líneas siguientes:
    *p equivale a vect[0], a *vect y a p[0]
    *(p+1) equivale a vect[1], a *(vect+1) y a p[1]
    *(p+2) equivale a vect[2], a *(vect+2) y a p[2]

Como ejemplo de la relación entre vectores y punteros, se van a ver varias formas posibles para sumar los N elementos de un vector a[ ]. Supóngase la siguiente declaración y las siguientes sentencias:


int a[N], suma, i, *p;
for(i=0, suma=0; i<N; ++i) // forma 1
suma += a[i];
for(i=0, suma=0; i<N; ++i) // forma 2
suma += *(a+i);
for(p=a, i=0, suma=0; i<N; ++i) // forma 3
suma += p[i];
for(p=a, suma=0; p<&a[N]; ++p) // forma 4
suma += *p;

RELACIÓN ENTRE MATRICES Y PUNTEROS

En el caso de las matrices la relación con los punteros es un poco más complicada. Supóngase una declaración como la siguiente

int mat[5][3], **p, *q;

El nombre de la matriz (mat) es un puntero al primer elemento de un vector de punteros mat[ ] (por tanto, existe un vector de punteros que tiene también el mismo nombre que la matriz), cuyos elementos contienen las direcciones del primer elemento de cada fila de la matriz. El nombre mat es pues un puntero a puntero. El vector de punteros mat[ ] se crea automáticamente al crearse la matriz. Así pues, mat es igual a &mat[0]; y mat[0] es &mat[0][0]. Análogamente, mat[1] es &mat[1][0], mat[2] es &mat[2][0], etc. La dirección base sobre la que se direccionan todos los elementos de la matriz no es mat, sino &mat[0][0].

Recuérdese también que, por la relación entre vectores y punteros, (mat+i) apunta a mat[i]. Recuérdese que la fórmula de direccionamiento de una matriz de N filas y M columnas establece que la dirección del elemento (i, j) viene dada por:


dirección (i, j) = dirección (0, 0) + i*M + j
Teniendo esto en cuenta y haciendo **p = mat; se tendrán las siguientes formas de
acceder a los elementos de la matriz:
*p es el valor de mat[0] **p es mat[0][0]
*(p+1) es el valor de mat[1] **(p+1) es mat[1][0]
*(*(p+1)+1) es mat[1][1]

Por otra parte, si la matriz tiene M columnas y si se hace q = &mat[0][0] (dirección base de la matriz. Recuérdese que esto es diferente del caso anterior p = mat), el elemento mat[i][j] puede ser accedido de varias formas. Basta recordar que dicho elemento tiene por delante i filas completas, y j elementos de su fila:


*(q + M*i + j) // fórmula de direccionamiento
*(mat[i] + j) // primer elemento fila i desplazado j elementos
(*(mat + i))[j] // [j] equivale a sumar j a un puntero
*((*(mat + i)) + j)

Todas estas relaciones tienen una gran importancia, pues implican una correcta comprensión de los punteros y de las matrices. De todas formas, hay que indicar que las matrices no son del todo idénticas a los vectores de punteros: Si se define una matriz explícitamente por medio de vectores de punteros, las filas pueden tener diferente número deelementos, y no queda garantizado que estén contiguas en la memoria (aunque se puede hacer que sí lo sean). No sería pues posible en este caso utilizar la fórmula de direccionamiento y el acceder por columnas a los elementos de la matriz.

INICIALIZACIÓN DE VECTORES Y MATRICES

La inicialización de un array se puede hacer de varias maneras:

– Declarando el array como tal e inicializándolo luego mediante lectura o asignación por medio de un bucle for:


double vect[N];
...
for(i = 0; i < N; i++)
scanf(" %lf", &vect[i]);
...

– Inicializándolo en la misma declaración, en la forma:


double v[6] = {1., 2., 3., 3., 2., 1.};
float d[] = {1.2, 3.4, 5.1}; // d[3] está implícito
int f[100] = {0}; // todo se inicializa a 0
int h[10] = {1, 2, 3}; // restantes elementos a 0
int mat[3][2] = {{1, 2}, {3, 4}, {5, 6}};

donde es necesario poner un punto decimal tras cada cifra, para que ésta sea reconocida como un valor de tipo float o double.

Recuérdese que, al igual que las variables escalares correspondientes, los arrays con modo de almacenamiento external y static se inicializan a cero automáticamente en el momento de la declaración. Sin embargo, esto no está garantizado en los arrays auto, y el que se haga o no depende del compilador.

Estructuras

Una estructura es una forma de agrupar un conjunto de datos de distinto tipo bajo un mismo nombre o identificador. Por ejemplo, supóngase que se desea diseñar una estructura que guarde los datos correspondientes a un alumno de primero. Esta estructura, a la que se llamará alumno, deberá guardar el nombre, la dirección, el número de matrícula, el teléfono, y las notas en las 10 asignaturas. Cada uno de estos datos se denomina miembro de la estructura. El modelo o patrón de esta estructura puede crearse del siguiente modo:


struct alumno {
char nombre[31];
char direccion[21];
unsigned long no_matricula;
unsigned long telefono;
float notas[10];
};

El código anterior crea el tipo de dato alumno, pero aún no hay ninguna variable declarada con este nuevo tipo. Obsérvese la necesidad de incluir un carácter (;) después de cerrar las llaves. Para declarar dos variables de tipo alumno en C se debe utilizar la sentencia incluyendo las palabras struct y alumno (en C++ basta utilizar la palabra alumno):


struct alumno alumno1, alumno2; // esto es C
alumno alumno1, alumno2; // esto es C++

donde tanto alumno1 como alumno2 son una estructura, que podrá almacenar un nombre de hasta 30 caracteres, una dirección de hasta 20 caracteres, el número de matrícula, el número de teléfono y las notas de las 10 asignaturas. También podrían haberse definido alumno1 y alumno2 al mismo tiempo que se definía la estructura de tipo alumno. Para ello bastaría haber hecho:


struct alumno {
char nombre[31];
char direccion[21];
unsigned long no_matricula;
unsigned long telefono;
float notas[10];
} alumno1, alumno2;

Para acceder a los miembros de una estructura se utiliza el operador punto (.), precedido por el nombre de la estructura y seguido del nombre del miembro. Por ejemplo, para dar valor al telefono del alumno alumno1 el valor 903456, se escribirá:


alumno1.telefono = 903456;

y para guardar la dirección de este mismo alumno, se escribirá:


alumno1.direccion = "C/ Penny Lane 1,2-A";

El tipo de estructura creado se puede utilizar para definir más variables o estructuras de tipo alumno, así como vectores de estructuras de este tipo. Por ejemplo:


struct alumno nuevo_alumno, clase[300];

En este caso, nuevo_alumno es una estructura de tipo alumno, y clase[300] es un vector de estructuras con espacio para almacenar los datos de 300 alumnos. El número de matrícula del alumno 264 podrá ser accedido como clase[264].no_matricula.

Los miembros de las estructuras pueden ser variables de cualquier tipo, incluyendo vectores y matrices, e incluso otras estructuras previamente definidas. Las estructuras se diferencian de los arrays (vectores y matrices) en varios aspectos. Por una parte, los arrays contienen información múltiple pero homogénea, mientras que los miembros de las estructuras pueden ser de naturaleza muy diferente. Además, las estructuras permiten ciertas operaciones globales que no se pueden realizar con arrays. Por ejemplo, la sentencia siguiente:


clase[298] = nuevo_alumno;

hace que se copien todos los miembros de la estructura nuevo_alumno en los miembros correspondientes de la estructura clase[298]. Estas operaciones globales no son posibles con arrays.

Se pueden definir también punteros a estructuras:

alumno *pt;

= &nuevo_alumno;

Ahora, el puntero pt apunta a la estructura nuevo_alumno y esto permite una nueva forma de acceder a sus miembros utilizando el operador flecha (->), constituido por los signos (-) y (>). Así, para acceder al teléfono del alumno nuevo_alumno, se puede utilizar cualquiera de las siguientes sentencias:


pt->telefono;
(*pt).telefono;

donde el paréntesis es necesario por la mayor prioridad del operador (.) respecto a (*).

Las estructuras admiten los mismos modos auto, extern y static que los arrays y las variables escalares. Las reglas de inicialización a cero por defecto de los modos extern y static se mantienen. Por lo demás, una estructura puede inicializarse en el momento de la declaración de modo análogo a como se inicializan los vectores y matrices, por medio de valores encerrados entre llaves {}. Por ejemplo, una forma de declarar e inicializar a la vez la estructura alumno_nuevo podría ser la siguiente:


struct alumno {
char nombre[31];
char direccion[21];
unsigned long no_matricula;
unsigned long telefono;
float notas[10];
} alumno_nuevo = {"Mike Smith", "San Martín 87, 2º A", 62419, 421794};

donde, como no se proporciona valor para las notas, éstas se inicializan a cero.

Las estructuras constituyen uno de los aspectos más potentes del lenguaje C. En esta sección se ha tratado sólo de hacer una breve presentación de sus posibilidades. C++ generaliza este concepto incluyendo funciones miembro además de variables miembro, llamándolo clase, y convirtiéndolo en la base de la programación orientada a objetos.

Un Saludo.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s