PUNTEROS GENÉRICOS
Un puntero genérico, o también conocido como puntero void, es un puntero con características especiales definido en C/C++.
La forma de declarar un puntero void se muestra a continuación:
void * ptr;
La característica principal de un puntero void es que puede apuntar a la dirección de cualquier tipo de dato, sin tener que hacer una conversión explícita del mismo. Los ejemplos que se muestran a continuación muestran esta propiedad.
#include <stdio.h>
#include <alloc.h>
int main (void)
{ void * ptrVoid;
int a = 78, arr[20];
float r = 283.91, * ptrFloat;
char nombre[30] = "Ana Roncal";
// Las siguientes instrucciones son válidas, no requieren de una conversión explícita del dato asignado (cast)
ptrVoid = &a; // recibe la dirección de la variable entera a
ptrVoid = arr; // apunta al primer elemento del arreglo de enteros arr
ptrVoid = &arr[5]; // recibe la dirección del quinto elemento del arreglo arr
ptrVoid = &r; // recibe la dirección de la variable de punto flotante r
ptrFloat = &malloc(sizeof(float));
* ptrFloat = 456.78;
ptrVoid = ptrFloat; // apunta a la misma dirección a la que apunta el puntero ptrFloat
ptrVoid = nombre; // recibe la dirección de cadena nombre
···
}
A pesar que esta propiedad permite una gran versatilidad a los punteros void, se debe tener mucho cuidado cuando se quiere trabajar con la variable referenciada por el puntero.
En primer lugar se debe tener en cuenta el concepto del puntero y de variable referenciada, cuando uno define un puntero a entero(int *ptrInt) la variable referenciada por el puntero es un entero (int), si se define un puntero de tipo float (float *ptrFloat) la variable referenciada por el puntero será de tipo float.
Cuando se define un puntero void (void *ptrVoid), la lógica nos puede llevar a pensar que la variable referenciadda por el puntero sería de tipo void, sin embargo eso es un error debido a que no existe en C/C++ el tipode dato void. No se puede escribir en un programa una sentencia como *ptrVoid = ..., la razón para esto es que como el puntero void puede apuntar a cualquier tipo de dato, el compilador no puede saber a que tipo de dato apuntrará cuando el programa se esté ejecutando.
La forma de solucionar este problema es indicándole al puntero a qué tipo de variable está apuntando, mediante una operación "cast", como se muestra a continuacón:
#include <stdio.h>
#include <alloc.h>
int main (void)
{ void * ptrVoid;
int a = 78;
int arr[5] = {55, 43, 71, 123, 44};
ptrVoid = &a; // recibe la dirección de la variable entera a
* (int *)ptrVoid = 345; // la variable referenciada por ptrVoid recibe el valor de entero 345
printf("Valor = %d\n",*(int *)ptrVoid); // se indica al compilador que interprete la variable referenciada por ptrVoid como un int
ptrVoid = arr; // apunta al primer elemento del arreglo de enteros arr
printf("Valor = %d\n",*(int *)ptrVoid); // imprime el valor de 55
El segundo problema que se presenta es cuando queremos aplicar el álgebra de punteros a un puntero void. Si arr es un arreglo de enteros y ptInt es un puntero a entero, si ejecutamos la orden ptInt = arr; estaremos haciendo que el puntero apunte al primer elemento del arreglo. Si luego hacemos ptInt++; o ptInt= ptInt + 2; haremos que el puntero apunte al segundo o tercer elemento del arreglo respectivamente.
Si por otro lado hacemos ptrVoid = arr;, esto es que el puntero void apunte al primer elemento del arreglo, se considerará un error si hacemos ptrVoid++; ó ptrVoid = ptvoid + 2;. Esto se debe a que, como se indicó en capítulos anteriores, cuando se le suma una cantidad a un puntero, el valor que se le suma no es cantidad misma, sino la cantidad multiplicada por el tamaño del tipo de datos al que apunta.
En ese sentido, al sumarle una cantidad a un puntero void, el compilador tratará de sumarle la contidad multuiplicada por el tamaño del dato al que apunta, y eso no se puede hacer porque como el puntero puede apuntar a cualquier tipo de dato, el compilador no podrá predecir el tamaño de la variable referenciada en el momento de traducir el programa. Este problem se puede soluciona aplicándole una operación "cast" al puntero antes de sumarle la cantidad.
Finalmente hay que indicar que los estándares actuales no permiten aplicar el operador ++ directamente a un puntero al que se le está aplicando una operación "cast".
A continuación se muestran unos ejemplos:
#include <stdio.h>
#include <alloc.h>
int main (void)
{ int arr[5] = {55, 43, 71, 123, 44};
void *ptrVoid;
ptrVoid = arr; // apunta al primer elemento del arreglo de enteros arr
printf("Valor = %d\n",*(int *)ptrVoid); // imprime el valor de 55
// ERRORES:
ptrVoid++;
ptrVoid += 3;
ptrVoid = prtVoid + 3;
//Soluciones:
ptrVoid = (int *)ptrVoid + 1; // Los compiladores modernos no permiten hacer ((int*)prtViod)++;
// Se le suma, a la dirección del puntero, el tamaño de un enteros
ptrVoid = (int *)ptrVoid + 3; // Los compiladores modernos no permiten hacer ((int*)prtViod)+=3;
// Se le suma, a la dirección del puntero, el tamaño de tres enteros.
Tampoco se puede emplear un puntero void como un arreglo, sin antes haberle aplicado una operación "cast".
// ERROR:ARREGLOS DE PUNTEROS GENÉRICOS
ptrVoid[3] = 111;
//Solución:
((int *)ptrVoid)[3] = 3;
Un arreglo de punteros void es una estructura de datos muy versatil, la razón de esto se debe a que como cada elemento del arreglo es un puntero void, podemos hacer que cada uno de ellos apunte a un tipo de dato diferente, por lo tanto habremos construído con ese arreglo una estructura de datos similar a un registro. La figura que se muestra a continuación ilustra este concepto.
A continuación mostramos un ejemplo que permite leer e imprimir un registro de datos de un alumno, similar al que se muestra en la figura anterior. En el registro se guardarán el código de un alumno, su nombre, su especialidad, la escala de pago asignada y el número de créditos aprobados.
/* Programa que emplea un arreglo estático de punteros void
para definir una ficha de datos
*/
#include<stdio.h>
#include<string.h>
void leerDatos(void *[ ]);
void imprimeDatos(void *[ ]);
void flushIn(void); //Limpia el buffer de entrada
int main (void)
{ void *ficha[5];
leerDatos(ficha);
imprimeDatos(ficha);
return 0;
}
void leerDatos(void *ficha[ ])
{ // Se definen punteros auxiliares para evitar operaciones "cast"
// excesivas y complejas
int *ptrInt;
double *ptrDoub;
char cad[500];
ptrInt = new int;
printf("Ingrese el código: "); scanf("%d", ptrInt);
ficha[0] = ptrInt; flushIn();
printf("Ingrese el nombre: "); gets(cad);
ficha[1] = new char [strlen(cad)+1];
strcpy( (char*)ficha[1], cad);
printf("Ingrese la especialidad: "); gets(cad);
ficha[2] = new char [strlen(cad)+1];
strcpy( (char*)ficha[2], cad);
ptrInt = new int;
printf("Ingrese la escala de pago: "); scanf("%d", ptrInt);
ficha[3] = ptrInt;
ptrDoub = new double;
printf("Ingrese los créditos aprobados: "); scanf("%lf", ptrDoub);
ficha[4] = ptrDoub;
}
void imprimeDatos(void *ficha[ ])
{ printf("\nCódigo: %ld\n", *(int*)(ficha[0]));
printf("Nombre: %-30s\n", (char*)(ficha[1]));
printf("Especialidad: %-30s\n", (char*)(ficha[2]));
printf("Escala de pago: %d\n", *(int*)(ficha[3]));
printf("Créditos aprobados: %3.1lf\n", *(double*)(ficha[4]));
}
void flushIn(void)
{ while (getchar()!='\n');
}
Este mismo ejemplo se puede desarrollar empleando un doble puntero void (void **), de este modo se gestará memoria dinámicamente para el arreglo. A continuación presentamos su desarrollo:
/* Programa que emplea un doble puntero void
para definir una ficha de datos
*/
#include<stdio.h>
#include<string.h>
void **leerDatos(void);
void imprimeDatos(void **);
void flushIn(void); //Limpia el buffer de entrada
int main (void)
{ void **ficha;
ficha = leerDatos( );
imprimeDatos(ficha);
return 0;
}
void **leerDatos(void)
{ // Se definen punteros auxiliares para evitar operaciones "cast"
// excesivas y complejas
void ** auxVoid;
int *ptrInt;
double *ptrDoub;
char cad[500];
auxVoid = new void *[5];
ptrInt = new int;
printf("Ingrese el código: "); scanf("%d", ptrInt);
auxVoid[0] = ptrInt; flushIn();
printf("Ingrese el nombre: "); gets(cad);
auxVoid[1] = new char [strlen(cad)+1];
strcpy( (char*)auxVoid[1], cad);
printf("Ingrese la especialidad: "); gets(cad);
auxVoid[2] = new char [strlen(cad)+1];
strcpy( (char*)auxVoid[1], cad);
ptrInt = new int;
printf("Ingrese la escala de pago: "); scanf("%d", ptrInt);
auxVoid[3] = ptrInt;
ptrDoub = new double;
printf("Ingrese los créditos aprobados: "); scanf("%lf", ptrDoub);
auxVoid[4] = ptrDoub;
return auxVoid;
}
// El resto es igual al anterior
Si a la estructura empleada en el ejemplo anterior (void **ficha), en lugar de definirla con cinco elementos la definimos con un elemento más, podríamos hacer que ese elemento (puntero void *) en vez de apuntar a otro dato de la ficha, que apunte a la dirección apuntada por otro puntero void** se podría generar una lista ligada, observe la figura siguiente:
El programa que se presenta a continuación muetra cómo se puede armar una lista ligada empleando punteros genéricos, observar que un puntero del tipo void ** no es un puntero genérico, un puntero void ** sólo puede apuntar a otro puntero de tipo void **.
// Programa que crea una lista ordenada de datos con punteros void.
// La lista almacena los registros de alumnos. Los campos están formados por:
// Código (Valor entero)
// Nombre (ApPat/ApMat/Nombre)
// Especialidad (Valor entero)
// Escala de pagos (Valor entero)
// Número de créditos aprobados
#include <stdio.h>
#include "Ficha.h"
#include "Lista.h"
int main(void) {
void **lista = NULL, **nodo;
while (1){
nodo = leeFicha();
if (nodo == NULL) break;
insertar(lista, nodo); //Lista ordenada por el nombre
}
mostrar(lista);
eliminar(lista);
return 0;
}
// Archivo de cabecera: Ficha.h
#ifndef _FICHA_H
#define _FICHA_H
void **leeFicha(void);
void imprimeFicha(void **);
void eliminaFicha(void **);
void flushIn(void);
#endif /* _FICHA_H */
// Módulo para manejar una ficha de datos
#include <stdio.h>
#include <string.h>
#include "Ficha.h"
void **leeFicha(void) {
int *ptrInt; double *ptrDoub; char auxCad[500], *cad;
void **ficha;
ptrInt = new int;
printf("Ingrese el código (Cero(0) = FIN): "); scanf("%d", ptrInt);
if (*ptrInt == 0) {
delete ptrInt;
return NULL;
}
ficha = new void*[6];
ficha[0] = ptrInt;
flushIn(); // Limpiamos el buffer de entrada
printf("Ingrese el nombre: (APat/AMat/Nomb)"); gets(auxCad);
cad = new char[strlen(auxCad) + 1];
strcpy(cad, auxCad);
ficha[1] = cad;
printf("Ingrese la especialidad: "); gets(auxCad);
cad = new char[strlen(auxCad) + 1];
strcpy(cad, auxCad);
ficha[2] = cad;
ptrInt = new int;
printf("Ingrese la escala de pago: "); scanf("%d", ptrInt);
ficha[3] = ptrInt;
ptrDoub = new double;
printf("Ingrese los créditos aprobados: "); scanf("%lf",ptrDoub);
ficha[4] = ptrDoub;
ficha[5] = NULL;
return ficha;
}
void imprimeFicha(void **ficha) {
int *codigo, *escala;
char *nombre, *especialid;
double *creditos;
codigo = (int *)(ficha[0]);
nombre = (char *)(ficha[1]);
especialid = (char *)(ficha[2]);
escala = (int *)(ficha[3]);
creditos = (double *)(ficha[4]);
printf("\nCodigo: %d\n", *codigo);
printf("Nombre: %-30s\n", nombre);
printf("Especialidad: %-30s\n", especialid);
printf("Escala de pago: %d\n", *escala);
printf("Creditos aprobados: %3.1lf\n", *creditos);
}
void eliminaFicha(void **ficha) {
int *codigo, *escala;
char *nombre, especialid;
double *creditos;
codigo = int *)(ficha[0]);
nombre = (char *)(ficha[1]);
especialid = (char *)(ficha[2]);
escala = int *)(ficha[3]);
creditos = (double *)(ficha[4]);
delete codigo;
delete nombre;
delete especialid;
delete escala;
delete creditos;
}
void flushIn(void) {
while(getchar( ) != '\n');
}
// Archivo de cabecera: Lista.h
#ifndef _LISTA_H
#define _LISTA_H
void insertar(void **&, void **);
void mostrar(void **);
void eliminar(void **&);
#endif /* _LISTA_H */
// Módulo de trabajo con la lista: Lista.cpp
#include <string.h>
#include "InOut.h"
void insertar(void **&lista, void **ficha)
{ void **p = lista, **ant = NULL;
while( p )
{ if( strcmp( (char*)p[1], (char*)ficha[1] ) > 0) break;
ant = p;
p = (void**)p[5]; // La operación "cast" se debe hacer por que
// p no es un puntero genérico (es void **).
}
ficha[5] = p; // Aquí no se requiere la operción "cast" por que
// ficha[5] es un puntero genérico (es void *).
if (ant != NULL) ant[5] = ficha;
else lista = ficha;
}
void mostrar(void **lista)
{ while (lista)
{ imprimir(lista);
lista = (void **)lista[5];
}
}
void eliminar(void **&lista){
void **sale;
while (lista){
sale = lista;
lista = (void **)lista[5];
eliminaFicha(sale);
delete sale;
}
}
![]() |
![]() ![]() |