Universidad del Quindío Ingeniería Electrónica Programación II
Profesor: Hernán Felipe García Arias
PUNTEROS
En la programación en lenguaje C, el tema más controvertido son los punteros: pues su manejo es muy versátil comparado con otros lenguajes, característica esta que los convierten en "arma de doble filo", pues si se utilizan correctamente servirán para hacer programas más potentes, pero mal utilizados pueden hacer que el computador se "cuelgue" en el momento menos pensado.
Los punteros son variables cuyo contenido es una posición de memoria en donde se alberga un dato.
La naturaleza de dicho dato (el tipo de dato) determina el tipo de puntero. Así, existirán punteros a enteros, a caracteres, a flotantes, etc. Toda variable de un procedimiento debe estar en una posición de memoria específica, por eso lo más usual es que el contenido de un puntero sea la posición de memoria de una variable, cuando se tiene esta situación se dice que el puntero apunta a una variable dada. (Tambien se pueden presentar situaciones más complejas como un puntero que apunta a otro puntero, o un puntero que apunta a una función, en su momento sabremos como y para que se hacen).
Variables Puntero
Cuando una variable se va a utilizar como puntero se debe declarar como tal, y para hacer esto basta con anteponer el signo * al nombre de la variable. Como ya se comento se debe especificar el tipo de dato al que va a apuntar. La forma general de declarar un puntero será entonces:
Tipo *nombre;
Donde tipo es cualquiera de los tipos definidos del C o una estructura definida por nosotros y nombre es el nombre de la variable puntero. Técnicamente un puntero puede apuntar a cualquier cosa (variable o no), pero al declararle un tipo le estamos definiendo cuantos bytes mide la memoria a la que apunta y de acuerdo a esto las reglas para la aritmética de puntero, que veremos más adelante.
Veamos algunos ejemplos de declaración de variables de tipo puntero: char *nombre; /* puntero da carácter */
int *codigo; /* puntero a entero*/
Operadores de punteros
Para determinar la posición de memoria de una variable se utiliza el operador &, y para acceder al dato apuntado por un puntero se utiliza el operador *, ejemplo:
void main() {
int x; int *p; x=5;
p = &x; /* p apunta a x */
*p = 20; /* al dato apuntado por p asignarle 20, o lo que es lo mismo a x asignarle 20 */ printf("x = %d , &x = %p , p = %p, *p = %d",x,&x,p,*p);
}
La dirección de una variable, de un elemento de un array, de una estructura o de un elemento de una estructura se obtiene antecediendo & al nombre.
Dos excepciones:
1) La dirección de un array es el nombre del array.
2) La dirección de una función es el nombre de la función. Más adelante veremos ejemplos de estas situaciones.
Los punteros siempre deben apuntar al tipo de dato correcto. Por ejemplo, cuando se declara un puntero tipo int, el compilador asume que cualquier dirección que tenga asignada el puntero representa un dato tipo entero (sea cierto o no). Dependiendo del tipo de compilador, el siguiente programa se compilará con o sin mensajes de advertencia, pero en cualquier caso produce un resultado erróneo:
void main() {
float x=5.3,y=2.7; int *p;
p = &x; /* p que es puntero a entero esta apuntando a la dirección donde hay un flotante */ /* la siguiente sentencia no funciona como se esperaría, porqué? */
y = *p; }
Aritmética de punteros
cada uno de los elementos de un arreglo en cuyo caso se dice que el puntero recorre el arreglo. También se puede hacer que el puntero apunte al elemento que esta n posiciones hacia adelante o hacia atrás. Para todo esto se utiliza la aritmética de punteros.
De acuerdo a lo anterior, asumiendo que p es un puntero, las siguientes son sentencias válidas:
p++; p--; p +=10; p -=30;
*(p+3) = 8;/ * en la posición tercera después de p guarde el dato 8 */
En cuantas posiciones de memoria se incrementa p con la sentencia p++; depende del tipo de puntero que hayamos declarado a p. Igualmente para las otras sentencias.
El lenguaje C asume que se intenta acceder a un arreglo de elementos del tipo que se ha declarado el puntero, así entonces en la sentencia p++; sumara el número de bytes que mide el tipo de dato al que debe apuntar, para acceder al siguiente dato, veamos esto con un ejemplo:
/*en este ejemplo se recorren todos los elementos de un arreglo de enteros y todos los elementos de un arreglo de caracteres para mostrarlos en pantalla*/
#include <stdio.h> void main()
{
int datos[5] = {1,3,5,7,9}; char name[]="hola"; int x;
int *p1; char *p2;
p1 = datos; /* equivalente a p1 = &datos[0] */ p2 = name;
for(x=0;x<5;x++) { printf("%d\n",*p1);p1++;} for(x=0;x<5;x++) { printf("%c\n",*p2);p2++;} }
Contenido posición de memoria 1 2000 datos[0] p1=datos 0 2001
3 2002 datos[1] p1=datos+1
0 2003
5 2004 datos[2] p1=datos+2
0 2005
7 2006 datos[3] p1=datos+3
0 2007
9 2008 datos[4] p1=datos+4 0 2009
'h' 2010 name[0] p2=name 'o' 2011 name[1] p2=name+1 'l' 2012 name[2] p2=name+2 'a' 2013 name[3] p2=name+3 0 2014 name[4] p2=name+4
2015 ubicación de x 2016
2017 ubicación de y 2018
2019 ubicación de p1... Qué ocurriría si se cambian las líneas: p1 = datos;
p2 = name;
Por las líneas: p1 = name; p2 = datos;
Que aparecería en pantalla ?
En el programa anterior se muestra como hacer que un puntero apunte al inicio de un arreglo, simplemente se le asigna al puntero el arreglo (sin especificar elemento con un índice). Una vez se asigna a un puntero el nombre de un arreglo, el puntero se puede operar con notación de puntero o con notación de arreglo, es decir:
Si se declaró float arreglo[20]; float *p;
y se asignó: p = arreglo;
También es posible poner a apuntar a un puntero a una zona de datos que no tiene variable que la represente, esto se puede hacer en la inicialización. Así, al programa anterior se le pueden cambiar las primeras líneas:
Programa anterior Programa nuevo
void main() {
int datos[5] = {1,3,5,7,9}; char name[]="hola"; int x;
int *p1; char *p2;
p1 = datos; /* equivalente a p1 = &datos[0] */ p2 = name;
Void main() {
int *p1 = {1,3,5,7,9}; char *p2="hola"; int x;
Y el programa correrá de la misma forma, el único cambio es que ya no existen variables que representen estos datos después de haber incrementado los punteros, en caso de ser necesario para volver a apuntar al inicio de los datos pues habría que restar a cada puntero el número de veces que se incrementó.
Se pueden comparar punteros para ver como son las direcciones entre sí, o en términos de elementos de un arreglo, cuál de los dos punteros apunta al elemento con mayor índice. De acuerdo a este criterio que salida daría el siguiente programa ?:
#include <stdio.h> void main()
{
char name[]="hola";
char *p1,*p2; p1 = name+1; p2 = name+3;
Qué ocurrirá si se restan 2 punteros ?
El resultado es un número entero que representa la cantidad de elementos que hay entre uno y otro puntero. Escriba un programa para comprobarlo.
Si bien la resta de punteros tiene sentido, pues serviría para determinar la cantidad de elementos entre ellos, no tiene sentido sumar dos punteros y por este motivo no existe dicha operación en lenguaje C (mucho menos la división y la multiplicación).
Funciones y Punteros
Los punteros sirven para hacer más efectivo el uso de funciones, pues con estos es posible que una función altere los valores de las variables de otras funciones. Para hacer esto solo basta con declarar la función con parámetro(s) de tipo puntero, y al llamarla pasar como parámetro la(s) dirección(es) de la (s ) variable(s). Ya dentro de la función se accede al valor para leerlo o para alterarlo con el operador *.
Ejemplo:
/*programa para mostrar como se alteran los variables con funciones que tienen punteros como parámetros/
#include <stdio.h> #include <conio.h>
void ordena2(int* primer,int *segundo); void main()
{
int menor=7; int mayor=5;
printf("Antes. menor=%d y mayor = %d\n",menor,mayor);
ordena2(&menor,&mayor); /* primer = &menor segundo = &mayor */ getch();
printf("Despues. menor=%d y mayor = %d",menor,mayor); }
void ordena2(int* primer,int *segundo) {
int tempo;
if(*segundo<*primero)
Ahora debe resultar más claro porque la función scanf requiere que se pasen las direcciones de las variables.
OJO: Si al usar una función que requiere como parámetro una dirección, en vez de la dirección se le pasa como parámetro el valor de la variable, los resultados son impredecibles (pues la función vería la posición de memoria equivalente al valor de la variable). Por ejemplo, intente ejecutar el programa anterior cambiando la línea:
ordena2(&menor,&mayor); /* primer = &menor segundo = &mayor */ por :
ordena2(menor,mayor); /* primer = menor segundo = mayor */
En algunos compiladores, genera mensajes de advertencia o de error, pero lo mejor es tenerlo siempre presente. Así también es necesario tener en cuenta, cuales de las funciones del C tienen como parámetros punteros para no cometer el error mencionado (recuerde alguna vez cuando llamó a scanf sin el símbolo & en la variable).
En las funciones de C se habla entonces de pasar parámetros por valor, y pasar parámetros por puntero, (en C++ existe otro tipo que se llama paso por referencia). El paso de parámetros por puntero puede obedecer básicamente a tres motivos:
1. El tamaño de la variable (en bytes) que se pasa como parámetro es muy grande, por ejemplo una estructura con muchos miembros, situación que hace que el llamado a la función sea lento (pues es necesario copiar esa cantidad de bytes en la "pila" antes de cada llamado a la función).
2. En la función se desea acceder a elementos de arreglos (ya sea para leer o escribir en ellos), como es el caso del ejemplo anterior.
3. La función retorna un(os) valor(es) en una o varias de las direcciones que se pasan como parámetro.
En algunas funciones en C se utiliza el modificador const antes de la declaración de un parámetro tipo puntero, investigar qué significa.
PUNTEROS A ESTRUCTURAS
Como ya se ha dicho un puntero puede apuntar a una estructura, para declarar una variable puntero a una estructura se deberá proceder así:
struct mi_struc *ms;
Veamos un ejemplo: #include <stdio.h> #iclude <conio.h> struct mi_struct {
char nombre[40]; long telefono;
};
void pedir_datos(struct mi_struct *amigo) / * paso de parámetro por puntero */ void mostrar_datos(struct mi_struct amigo) /* paso de parámetro por valor*/ void main()
{
struct mi_struct mi_amigo; do{
clrscr();
printf("1. Leer datos \n2. Mostrar datos 3. Salir\n"); printf("opcion");
op = getch(); switch(op) {
case '1':
pedir_datos(&mi_amigo); break;
case '2':
mostrar_datos(mi_amigo); break;
}
}while (op!='3'); }
void pedir_datos(struct mi_struct *amigo) {
printf("Nombre ");
gets( (*amigo).nombre ); /* notación confusa */ printf("Teléfono ");
scanf("%ld", &( (*amigo).telefono) ) ; /* más confusa todavia */ }
void mostrar_datos(struct mi_struct amigo) {
printf("Nombre = %s Teléfono = %ld \n",amigo.nombre,amigo.telefono); }
operador que permite acceder al valor de un miembro de una estructura apuntada por un puntero, para esto se utiliza el símbolo -> (signo menos y signo menor). Entonces la función pedir_datos se puede reescribir:
void pedir_datos(struct mi_struct *amigo) {
printf("Nombre ");
gets( amigo->nombre ); /* = gets( (*amigo).nombre ); */ printf("Teléfono ");
scanf("%ld", &amigo->telefono ) ; /* =scanf("%ld", &( (*amigo).telefono) ) ; */ }
Si bien el operador & sobre una campo de una estructura (por ejemplo &amigo->telefono), logicamente deberia ser la direccion donde se encuentra dicho campo, en algunos compiladores no retorna la direccion efectiva (sobre todo con datos tipo flotante). Por este motivo se recomienda, leer el dato en una variable axiliar y luego asignarle su valor al campo. Ejemplo:
struct complejo {
float real; float ima; };
complejo x,*y; float aux; y=&x;
scanf(“%f”,&aux); y->real=aux // en vez de scanf(“%f”,&y->real);
CAST ENTRE PUNTEROS
Al igual que con las variables que no son puntero, el lenguaje C, permite hacer conversiones explícitas de tipos (en ingles CAST), para cuando se necesita operar con el valor de un dato en un tipo diferente al que tiene, por ejemplo : x= 7+5/2, da como resultado 9, pues 5/3 es una división entre enteros y por lo tanto el resultado es un entero, independientemente del tipo de la variable x. Para que el resultado sea 9.5 se necesita que la división de cómo resultado un flotante, y para lograrlo solo es suficiente con que uno de los dos operadores sea un flotante así: x=7+(float)5/2;.(que pasaría si se escribe x=7+(float)(5/2?). Entonces con punteros se puede hacer lo mismo y convertir un puntero de un tipo a otro. Por ejemplo si se tiene
char *p; float x;
x= *(float * )p;
Un ejemplo mas útil seria el caso en el cual se tiene una zona de memoria que se usa temporalmente para guardar datos de cualquier tipo. Por ejemplo:
char temp[100]; float *pf; int *pl;
Para que se distribuya la memoria temp, entre 20 flotantes y 10 enteros se escribiría el siguiente código:
pf=(float *)temp;
pl=(int*)(temp+80);//o tambien pl=(int*)(pf+20);
Punteros a NULL
Muchas veces se necesita indicar una condición especial con los punteros, por ejemplo decir que el puntero no apunta a ningún objeto, pero como codificar dicha condición ? La solución es ponerlo a apuntar a una dirección en particular y cada vez que un puntero sea igual a dicha dirección se asumirá que el puntero no apunta a ningún objeto (o la condición que se desee expresar). Para codificar esta condición en lenguaje C existe la dirección NULL (dirección 0).
Por ejemplo en la siguiente función se utiliza el valor NULL para indicar que no hay más elementos en la lista (que supuestamente se recorre con un puntero).
void mostrar_datos(struct mi_struct *amigo) {
if(amigo!=NULL) {
printf("Nombre = %s Teléfono = %ld \n",amigo->nombre,amigo->telefono); }else printf("No hay más elementos en la lista");
}
Punteros tipo void
En algunas situaciones un mismo puntero se quiere usar para apuntar a entero o a un flotante, y en otras situaciones es importante la dirección (mas no el dato contenido en ella). Para estos dos casos es muy útil declarar un puntero tipo void.
ejemplo una sola función mostrar en vez de mostrar_entero y mostrar flotante, de hecho la función printf trabaja así).
Un ejemplo del segundo caso sería el de la asignación dinámica de memoria, que es el tema del siguiente apartado.
Con los punteros tipo void no se pueden hacer directamente las operaciones de obtener el dato u operaciones aritméticas. En otras palabras: si p es un puntero a void, entonces para obtener el dato o hacer que apunte al siguiente (o cualquier relación aritmética con el puntero) es necesario convertirlo a un puntero de otro tipo de dato (obvio pues un dato tipo void mide 0). Para lo cual se utilizan los cast a punteros. Ejemplos:
Si p se ha declarado así: void* p;
no se puede escribir p++; o *p en vez de esto, las siguientes expresiones son válidas * (int*)p = 56; /* guardar el entero 56 en la posición apuntada por p*/
* (long*)p =10000L; /* guardar el número 100000L en la posición apuntada por p*/ (char*)p++; /* hacer que p apunte al siguiente carácter */
(float*)p++; /* hacer que p apunte al siguiente flotante */ ARREGLOS DE PUNTEROS
En algunos casos se desean construir “matrices” (arreglos bidimensionales) en los cuales el número de columnas no es igual para cada fila, en estas situaciones son útiles los arreglos de punteros.
Su declaración es igual a la declaración de arreglos de cualquier variable: TIPO* arr_ptr[TAM];
Ejemplo:
char* msjp[3]={ “Este es el primer mensaje” “Y este es el segundo”,
“y ya” }; // un arreglo de 3 punteros a caracteres ya inicializados la versión con arreglos bidimensionales de caracteres es:
char msj[3][26]= ]={ “Este es el primer mensaje” “Y este es el segundo”, “y ya” } ;
se puede determinar la cantidad total de bytes consumidos en cada caso:
En el primer caso, los bytes consumidos por los mensajes son la suma de sus tamaños es decir:
Además hay que agregar el tamaño de los punteros (asumiendo dos bytes por puntero) N_bytes=52+6=58;
En el segundo caso el tamaño es n_bytes=3*26=78;
Por lo tanto nos estamos ahorrando 19 bytes, imeginese ahora que el numero de mensajes fuera 100 y el mensaje mas largo fuera de 50 caracteres.
Lo anterior no se limita a caracteres:
int* int_ptr[100]; // un arreglo de 100 punteros a enteros float* f_ptr[128]; // arreglo de 128 punteros a flotantes
Y todo esto para ahorrarse un bytes ?, pues resulta que con los arreglos de puntero se puede optimizar los algoritmos que impliquen el intercambio elementos cuyo tamaño sea considerable, pues cuando se van intercambiar dos elementos es suficiente con intercambiar los punteros asociados.
struct Alumno {
int edad;
char nombre[50]; long código; };
Alumno alumno[20];
Alumno* a_ptr[20];// contendra las direcciones de los alumnos ordenados alfabeticamente Alumno* p=NULL;
int i,j,menor; for (i=0;i<20;i++)
a_ptr[i]=alumno+i; // se inicializa el arreglo de punteros p=alumno; //al primer elemento
for (i=0;i<20;i++) {
menor=i;
for(j=i+1;j<20;j++) {
}
p=a_ptr[menor]; // en el caso de usar arreglos tendrian que
a_ptr[menor]=a_ptr[i]; // intercambiarse las estrucuras completas (56 byte ) y a_ptr[i]=p; // aquí solo 2 bytes.
}
Punteros a punteros
Un puntero a puntero es una variable que contiene una dirección en la cual hay un dato que también es una dirección. Pero cuando se necesita saber la dirección donde hay una dirección ?
- Cuando se quiere utilizar un puntero para un array bidimensional.
- Cuando una función necesita alterar la dirección a la que apunta un puntero. - Cuando se tiene un arreglo de punteros que se asigna dinámicamente. Ejemplo del primer caso:
#include <stdio.h>
/* frases y autores son arrays bidimensionales que pueden ser recorridos por un puntero de punteros */
char frases[4][20] = {"el resto son detalles", "eureka","ser o no ser","pienso luego existo"}; char autores[4][10]= {"Einstein","Arquímedes","Shakespeare", "Descartes"};
/* p apunta a un arreglo que contiene las direcciones de inicio de cada frase y autor */ char * *p = { frases[0],autores[0],frases[1],autores[1],frases[2],autores[2]};
void main() {
clrscr();
printf("frases famosas\n");
for(i=0;i<6;i++,p++) /* con p++ paso a la siguiente dirección : frase o autor */
printf("%s\n",*p); /* el contenido de p es una direccíon: cual? el inicio de la frase o el inicio del
autor*/ }
Ejemplo del segundo caso: #include <stdio.h>
void main() {
float parciales[] = { 1.0, 3.3, 2.5, 4.0, 4.1}; float* mejor_p;
mejor_p = parciales;
printf("El promedio fue %f y la mejor nota fue %f", buscar_promedioymayor(&mejor_p,5),
*mejor_p); }
float buscar_promedioymayor(float** datos,int ndatos) {
int i,pm;
float prom,*p = *datos; /* el puntero flotante apuntado por datos */ float mejor = *p;
prom=0;pm=0;
for(i=1;i<ndatos;i++;p++) {
if(mejor<*p)
{ pm = i;mejor=*p; } prom+=*p;
}
*datos = (*datos)+i; return prom/ndatos; }
Para ver ejemplos del tercer caso se deben ver ejemplos de arrays dispersos (arrays de punteros dimensionados dinámicamente), tema que se verá posteriormente:
Reserva dinámica de memoria
En muchos casos no se conoce de antemano la cantidad de memoria que consumirán los datos manejados por un programa para un usuario en particular. Entonces es necesario recurrir a dos soluciones:
- Establecer arreglos con un tamaño máximo y cuando se usen llenar solo la parte necesaria.
- Utilizar la reserva dinámica de memoria para que el programa asigne la memoria exacta que necesita en un momento en particular.
Obviamente es mejor la segunda solución, pues con la primera el programa consumiría casi siempre más memoria de la que necesitaría.
Muchas estructuras de datos complejas como arboles y listas enlazadas se basan en la reserva dinámica de memoria.
Para hacer reserva dinámica de memoria en lenguaje C se cuenta con las funciones básicas: void* malloc(size_t nbytes); -> reserva nbytes de memoria
La función malloc retorna la dirección de inicio del bloque de nbytes que fue reservado. Como malloc retorna una dirección lo normal es que dicho valor sea asignado a un puntero. Cuando no hay espacio suficiente para localizar los nbytes de memoria malloc retorna la dirección NULL.
Veamos un ejemplo del uso de malloc y free: /* ejemplo de malloc*/
#include <stdio.h> #include <string.h> #include <alloc.h> #include <process.h> int main(void)
{
char *str;
/* reserva memoria para la cadena */ if ((str = (char *) malloc(10)) == NULL) {
printf("No hay memoria suficiente para reservar\n"); exit(1); /* termina el programa si no hay memoria */ }
/* copia "Hola" en la cadena */ strcpy(str, "Hola");
/* muestra la cadena */
printf("la cadena es %s\n", str); /* libera la memoria */
free(str); return 0; }
Como malloc retorna un puntero a tipo void, lo mejor es convertirlo antes de asignarlo al tipo de puntero en particular, para ello se utiliza el cast al puntero, en el ejemplo es (char*) en la expresión
str = (char*)malloc(10);
Para evitar estos problemas se recomienda siempre usar el puntero a NULL para expresar que un puntero no ha sido asignado o que ya se liberó y para liberar memoria siempre preguntar si el puntero vale null, algo así:
if(puntero!=NULL) {
free(puntero); puntero = NULL; }
También es necesario tener la costumbre de no operar con punteros que apunten al inicio de un bloque asignado dinámicamente, en caso de ser necesario utilizar un puntero auxiliar. Si no se tiene en cuenta esta recomendación se presentarían dos errores simultáneamente : 1. No se liberaría la memoria pues el puntero siempre debe apuntar al inicio
2. Se liberaría una zona de memoria distinta a la reservada.
Para comprobarlo, intente ejecutar el siguiente segmento de código: char *p;
p = (char*)malloc(10); p++;
. . free(p); `
En vez de esto se puede utilizar un segundo puntero, así: char *p, *aux;
p = (char*)malloc(10); aux = p;
Arreglos reservados dinámicamente
Muchas veces malloc se usa reservando memoria para manejar arreglos de un tipo de dato en particular (no necesariamente arreglo de caracteres como el ejemplo), en dicha situación es más importante para nosotros la cantidad de elementos a reservar que la cantidad de bytes que ocupan dichos datos, para estas situaciones se utiliza el operador sizeof que nos informa del tamaño de un tipo de dato. Veamos algunos ejemplos:
int *datos;
datos = (int*)malloc(5*sizeof(int)); /* reserva memoria para 5 enteros (10 bytes)*/
float* vector; int nvector; nvector = 15;
vector = (float*)malloc(nvector*sizeof(float)); /*reserva memoria para 15 flotantes */ struct complejo *corriente;
corriente = (complejo*)malloc(100*sizeof(complejo)); /* reserva memoria para 100 complejos*/
Genericamente se podría escribir:
puntero = (tipo de dato*)malloc(n_elementos*sizeof(tipo de dato)); Ejemplo de Puntero de Punteros
Diseñar una función a la cual se le pase un puntero a caracteres y un carácter; al retornar el puntero debe apuntar a la posición en donde se encuentra el carácter dentro de la cadena (en caso de no encontrarse en la cadena el puntero debe apuntar a NULL), por ejemplo si se tiene la siguiente situación:
char cadena[] = "Esta tarde gris"; char* p = cadena;
y se invoca a la función con los parámetros p y 't', después de ejecutada la función p queda apuntado a cadena+2.
El programa completo (función y ejemplo de uso), podría ser: #include <stdio.h>
void main() {
char cadena[]="Armenia ciudad milagro II"; char *p1 = cadena;
/* muestra la frase descompuesta en palabras*/ do{ p2=p1;
buscar_caracter(&p2,' ');
if(!p2) break; /* equivalente a if(p2==NULL) break; */ *p2=0;
printf("%s\n",p1); p1++;
}while(p!=NULL);
}
void buscar_carácter(char** p,char car) {
char* puntero = *p;
while((*puntero)&&( *puntero!=' ')) puntero ++; if(*puntero) *p = puntero;
else *p=NULL; }
La función realloc
Es utilizada generalmente cuando se desea cambiar el tamaño de una zona de memoria anteriormente reservada, por ejemplo si se están agotando las posiciones libres en un arreglo asignado dinámicamente es necesario reservar más memoria, copiar los datos de la localidad anterior a la nueva y por ultimo liberar la zona antigua, pues precisamente esto es la que hace la función realloc.
Prototipo:
void* realloc (void* p, int size);
La función trabaja de la siguiente forma:
Si la zona es mas grande que la inicial, busca la forma de reservar memoria que sea continua a la anterior, para evitarse trastear con los datos que allí se almacenaron, en caso de no lograr esto procederá a buscar en otra parte y cuando la encuentra copia los datos de la zona antigua, la libera y por ultimo retorna un puntero a la nueva zona. Si no puede encontrar memoria retorna NULL y no modifica la zona de memoria.
Si la zona solicitada es mas pequeña entonces la función solo libera el espacio sobrante de esta y retorna un puntero a la misma zona.
Al igual que la memoria asignada con malloc la memoria manipulada con realloc tambien debe ser liberada con la función free.
Ejemplo:
Suponga que se declaró un arreglo dinámico para 10 enteros y que en una variable se almacena el numero de posiciones que han sido usadas, de tal forma que si se sobrepasa ese limite se reserve mas memoria, en el arreglo se almacenaran solo valores positivos y luego se calcula su suma.
int j,suma; int TAM=10; int usadas=0; int* p=NULL; int* p2=NULL;
// se reserva memoria inicialmente para TAM enteros p=(int*) malloc(TAM*sizeof(int));
if (!p) return 1; // se comprueba la reserva de memoria
do {
scanf(“%f”,(p+usadas));
if (p[usadas]<0) // el ciclo se termina cuando se ingresa un numero negativo break;
usadas++;
if (usadas==TAM) {
p2=(int*)realloc(p,TAM+10); if (!p2)
{
printf (“Lo siento memoria insuficiente”); return 1;
p=p2; TAM+=10; }
}while (1); suma=0;
for (j=0;j<usadas;j++) suma+=p[j];
printf(“Las suma de los numeros es : %d”,suma); free(p);
Punteros a funciones
La zona de memoria a la que apuntan los punteros normalmente suele ser zona de datos (variables del programa), pero podría ser muy útil que dicha zona fuera memoria donde se albergan instrucciones de nuestro programa, así podríamos apuntar a diferentes zonas del programa para que se ejecutarán unas u otras dependiendo del valor de un puntero. Estos punteros en C se llaman punteros a funciones. Pero la zona a la que apunta no puede ser cualquiera sino que normalmente se asigna al punto de inicio de las instrucciones correspondientes a una función. La parte de entrada y salida de una función depende mucho de la forma en que esta es declarada (número y tipo de cada parámetro y tipo de retorno), razón por la cual al declarar un puntero a función debemos indicar el formato de la función a la que apunta e igual que en los punteros a variables se habla de punteros a funciones que retornan enteros, punteros a funciones que retornan flotantes, punteros a funciones que retornan void, etc.
La forma de declarar un puntero a función es :
tipo de retorno (*nombre)(tipo parámetro1,tipo parámetro 2,..); No olvidar los paréntesis en el nombre.
Para asignar la dirección de una función a un puntero se usa la notación simple: nombre_puntero = nombre_función; (no necesita el *)
Y para ejecutar la función apuntada por un puntero simplemente se escribe: nombre_puntero(par1,par2,..);
int suma(int x,int y) { return x+y;} int resta(int x,int y) {return x-y; } int (* operacion)(int,int);
main() {
int a,b,c; a = 10; b = 3;
operación = suma; // operación apunta a la función suma
c = operación(a,b); // se invoca la función a la que apunta operación printf("%d operado con %d es %d",c);
operación = resta; //? c = operación(a,b); //?
printf("%d operado con %d es %d",c); getch();
}
Veamos un ejemplo más interesante, como hacer un menú sin utilizar un switch para cada función. En vez de esto se crea un arreglo de punteros a funciones.
#include<stdio.h> #include<conio.h> void abrir(void); void cerrar(void); void nuevo(void); void salvar(void); void main(void) {
void (*p[])(void)={abrir,cerrar,nuevo,salvar}; //arreglo de punteros a funciones int opcion;
clrscr(); do{
if((opcion>=0)&&(opcion<=3)) p[opcion](); }while(opcion!=4);
}
void abrir(void) {
printf("\nAbrir");
//completar según el caso }
void cerrar(void) {
printf("\nCerrar"); //completar según el caso }
void nuevo(void) {
printf("\nnuevo");
//completar según el caso }
void salvar(void) {
printf("\nSalvar"); //completar según el caso }
qsort
void qsort(void *base, size_t nelem, size_t width, int (*fcmp)(const void *, const void *));//<stdlib.h>
Description
Sorts using the quicksort algorithm.
qsort is an implementation of the "median of three" variant of the quicksort algorithm. qsort sorts the entries in a table by repeatedly calling the user-defined comparison function pointed to by fcmp.
base points to the base (0th element) of the table to be sorted. nelem is the number of entries in the table.
width is the size of each entry in the table, in bytes.
fcmp accepts two arguments, elem1 and elem2, each a pointer to an entry in the table. The comparison function compares each of the pointed-to items (*elem1 and *elem2), and returns an integer based on the result of the comparison.
*elem1 < *elem2 fcmp returns an integer < 0 *elem1 == *elem2 fcmp returns 0
*elem1 > *elem2 fcmp returns an integer > 0
In the comparison, the less-than symbol (<) means the left element should appear before the right element in the final, sorted sequence. Similarly, the greater-than (>) symbol means the left element should appear after the right element in the final, sorted sequence.
Return Value None. #include <stdio.h> #include <stdlib.h> #include <string.h>
int sort_function( const void *a, const void *b); char list[5][4] = { "cat", "car", "cab", "cap", "can" }; int main(void)
{ int x;
qsort((void *)list, 5, sizeof(list[0]), sort_function); for (x = 0; x < 5; x++)
printf("%s\n", list[x]); return 0;
}