LISTAS Y TABLAS DE PUNTEROS.







Definición, declaración y acceso .
Es posible (q.v.) construir punteros de listas, tablas y matrices de todo tipo. No debe confundirse, sin embargo, un puntero de lista, tabla o matriz con una lista, tabla o matriz de punteros. La declaración de una matriz de punteros se hace siguiendo las reglas esperables:

Tipo_base * matriz_punteros[DIM_1][DIM_2]...[DIM_N];

en donde Tipo_base es cualquier tipo válido en C, y DIM_1 , DIM_2 ,..., DIM_N son constantes previamente definidas. Evidentemente, la expresión de acceso a un elemento de una matriz de punteros es de la forma

matriz_punteros[i][j]...[k]

y dado que ese elemento es un puntero, la expresión

*matriz_punteros[i][j]...[k]


denotará el objeto señalado por el puntero en cuestión. El valor inicial de los elementos de la matriz (las direcciones que contienen los elementos) es indeterminado (apuntan a cualquier parte). Para poder usarlos, será imprescindible darles valores correctos. Este valor puede ser, para cada elemento:



Aplicación a caracteres
El compilador de C reconoce las expresiones entre comillas dobles como constantes alfanuméricas. Estas cadenas se almacenan en una zona de memoria destinada automáticamente al efecto; el compilador interpreta que se trata de listas de caracteres, y por tanto las trata como punteros. Si se intenta asignar una constante alfanumérica a una variable alfanumérica (una lista de caracteres), se produce un error, porque el nombre de la cadena se interpreta como la dirección en que comienza. Otra cosa es asignar a un puntero de carácter una constante alfanumérica; el compilador se limita a almacenar en ese puntero la dirección de la zona de memoria que ha reservado previamente para la tal constante alfanumérica. Véase el oportuno Ejercicio . Explicar entonces por qué se admite lista[0] = cadena pero no cadena = lista[0] .

Existen dos aproximaciones a la hora de manejar listas de caracteres:
  1. La primera consiste en reservar anticipadamente tanto espacio como parezca necesario:
    char cadena[80];
    Este método es cómo pero plantea el problema habitual: en muchos caso se va a desperdiciar espacio, y no hay seguridad de que la cadena disponga de espacio suficiente para almacenar todos los caracteres necesarios en un determinado momento.
  2. La segunda consiste en crear inicialmente un puntero de carácter, para después reservar tanto espacio como sea necesario:
    char * cadena;
    cadena = malloc(ESPACIO_NECESARIO);
    Este segundo método requiere, desde luego, saber cuánto espacio se necesita, pero esto no es realmente un problema, al disponer de la función fgetln() , cuyo prototipo es
     char * fgetln(FILE *stream, size_t *num)
    y que proporciona todo lo necesario (un puntero del lugar en que comienza la cadena y su longitud a través de *num ). El método es más engorroso pero siempre funciona y siempre es seguro.
Por tanto, cuando se precisa manejar un texto, normalmente se va leyendo de disco línea por línea mediante una combinación de fgetln() y malloc() ; los punteros de las líneas se almacenan en una lista (posiblemente no lineal ) de punteros de char y quedan a disposición del programa. Listas de punteros de funciones. Según se ha indicado, los punteros pueden señalar distintos tipos de objetos. Uno de los posibles objetos señalables son precisamente las funciones. Esto va a resultar muy útil porque permitirá indicar a una función la dirección de otra función. Esto tiene aplicación, por ejemplo, en la función de biblioteca qsort(). Esta función, para su correcto funcionamiento requieren otra función que la relación de orden de los tipos de datos que se quieren ordenar. De este modo, qsort() permite ordenar enteros, reales, cadenas, o cualquier otro tipo de datos para el cual seamos capaces de definir una función de comparación. Un ejemplo sencillo de uso de qsort() es el que puede verse a continuación:
#include < stdlib.h >
#include < stdio.h >

long a[1000];

int comparar(const void *x, const void *y)
{
 if (*(int *)x < *(int *)y)
  return -1;
 else
  if (*(int *)x > *(int *)y)
   return 1;
  else
        return 0;
}

int main(int argc, char * argv)
{
 int i;

 for (i=0; i<1000 ; i++)
  a[i]=(long)rand();

 qsort(a, 1000, sizeof(int), comparar);

 for (i=0 ; i < 1000 ; i++)
  printf("%ld\n",a[i]);
}


En este programa se llena una lista de 1000 elementos int (llamada a) de valores aleatorios. Esta lista se pasa a la función qsort() , junto con el nombre (el puntero) de una función llamada comparar() , que admite como argumentos dos punteros de void y proporciona como resultado un int . Internamente, la función refunde los punteros de void a punteros de int y deshace la indirección; los dos números resultantes se comparan entre sí y se proporciona un resultado negativo, nulo positivo según el primer número sea menor, igual o mayor que el segundo. Esta convención es análoga, por cierto, a la empleada en la función de comparación de cadenas, strcmp() .

Ejercicio .- Construir un programa que haga uso de una matriz de punteros de caracteres. Estudiar la asignación con reserva automática de espacio, y la ubicación automática de variables alfanuméricas en memoria. Diferenciar las funciones de cadenas y las funciones de traslado de memoria.

 /*
 Este programa muestra la utilización de matrices de punteros
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DIM_1 5
/*
 Esto se admite y funciona!
*/
char * lista[DIM_1] =  {"uno", "dos", "tres", "cuatro", "cinco"};
char cadena[80];
/*
 La razón es que el compilador de C reserva una zona para
 variables alfanuméricas, y asigna a los elementos de lista
 la dirección de esas variables. El compilador se encarga de
 reservar  espacio  para las constantes consideradas.
 Esto es muy cómodo!
 RECORDATORIO
 Está prohibida la asignación de variables matriciales.
*/
int i;

int main(void)
{
 for(i=0;i<DIM_1;i++)
  printf("lista[%d] = %10s, direccion = %d\n",i,lista[i],lista[i]);
 lista[0] = "OOO";
 printf("\nUna nueva asignación de cadenas:\n\n");
 for(i=0;i<DIM_1;i++)
  printf("lista[%d] = %10s, direccion = %d\n",i,lista[i],lista[i]);
 /*
  Explicación: el compilador ha creado una nueva entrada
  en la tabla de constantes alfanuméricas, y hemos asignado
  a lista[0] la dirección de esa nueva entrada.
  El lenguaje "conoce" este mecanismo, y strcpy() funciona
  correctamente:
 */
 strcpy(lista[0],"aaaa");
 printf("\nTodo va bien:\n\n");
 for(i=0;i<DIM_1;i++)
  printf("lista[%d] = %10s, direccion = %d\n",i,lista[i],lista[i]);
 /*
  ¿Qué sucede si se emplea memcpy() en lugar de strcpy()?
 */
 memcpy(lista[2],"bbbbbbbb",strlen("bbbbbbbb"));
 printf("\nUn resultado inesperado:\n\n");
 for(i=0;i<DIM_1;i++)
  printf("lista[%d] = %10s, direccion = %d\n",i,lista[i],lista[i]);
 /*
  MORALEJA
  El lenguaje conoce las cadenas: en el tratamiento de cadenas,
  convienen utilizar funciones de cadenas (string.h) siempre que
  sea posible.
 */
 printf("\n\nTerminación normal del programa.\n\n");
 return 0;
}
/*
	RESULTADO
	lista[0] =        uno, direccion = 97159628
	lista[1] =        dos, direccion = 97159632
	lista[2] =       tres, direccion = 97159880
	lista[3] =     cuatro, direccion = 97159885
	lista[4] =      cinco, direccion = 97159892

	Una nueva asignación de cadenas:

	lista[0] =        OOO, direccion = 97159636
	lista[1] =        dos, direccion = 97159632
	lista[2] =       tres, direccion = 97159880
	lista[3] =     cuatro, direccion = 97159885
	lista[4] =      cinco, direccion = 97159892

	Todo va bien:

	lista[0] =       aaaa, direccion = 97159636
	lista[1] =        dos, direccion = 97159632
	lista[2] =       tres, direccion = 97159880
	lista[3] =     cuatro, direccion = 97159885
	lista[4] =      cinco, direccion = 97159892

	Un resultado inesperado:

	lista[0] =       aaaa, direccion = 97159636
	lista[1] =        dos, direccion = 97159632
	lista[2] = bbbbbbbbtro, direccion = 97159880
	lista[3] =     bbbtro, direccion = 97159885
	lista[4] =      cinco, direccion = 97159892


	Terminación normal del programa.

*/



Ejercicios propuestos



  1. Ejercicio 0706r01.- Construir una tabla de caracteres ( char * tabla[][20] o char (*tabla)[20] ), asignándole valores iniciales que sean cadenas de distintas longitudes. Mostrar en pantalla la tabla e indicar la cantidad de espacio desperdiciado en cada una. Permitir que el usuario modifique el contenido de las líneas. ¿Qué ocurre si el usuario aporta más caracteres que los esperados?

  2. Ejercicio 0706r02.- La función strcmp() sirve para determinar la relación de orden de dos cadenas. Se pide construir un programa que haga uso de alguna de las funciones de ordenación conocidas ( qsort() , heapsort() , mergesort() ) para ordenar una lista de cadenas introducidas a través del teclado.

  3. Ejercicio 0706r03.- Modificar el ejercicio anterior para que sea posible para emplear qsort() , heapsort() o mergesort() a voluntad del usuario. Emplear una lista de punteros de función.

  4. Ejercicio 0706r04.- Considérese una estructura de la forma siguiente:
    struct Ficha {
    	char nombre[80];
    	int edad;
    	float peso;
    };
    Se pide construir un programa dotado de una lista de estructuras de este tipo. El programa debe ser capaz de ordenar la lista de estructuras por cualquier campo, para luego mostrar todos los registros en pantalla, con formato encolumnado.

  5. Ejercicio 0706r05.- Considérese una tabla de punteros de estructura del tipo utilizado en los ejercicios anteriores. Se pide construir una función capaz de intercambiar dos columnas cualesquiera de la tabla.

  6. Ejercicio 0706r06.- Sea una lista de punteros de struct Ficha , según se ha definido en ejercicios anteriores. Se pide construir una pila estática basada en esa lista de punteros.

  7. Ejercicio 0706r07.- Declarar una lista de mensajes de error basada en una lista de punteros de char . Construir una función que permita mostrar el mensaje de error correspondiente a un cierto número de error, que actuará como índice dentro de la lista. Seguir el modelo de perror() .

  8. Ejercicio 0706r08.- Consultar la sintaxis de la función malloc() y crear dinámicamente las estructuras necesarias para utilizar una lista de punteros de estructura. ¿Hay alguna diferencia significativa respecto al uso de una tabla de estructuras estáticas?