Post #15
Si habéis llegado hasta aquí deberíais entender perfectamente que son los punteros, y cómo hacer que una funcion modifique el valor de una variable local gracias a ellos.
Ahora vamos a profundizar un poco más en los punteros. Los punteros pueden apuntar a una dirección de memoria, pero tienen la particularidad de que pueden también apuntar a la siguiente, y a la siguiente... Funcionando como un array. Supongamos que tenemos un puntero a entero (int). Normalmente los int ocupan 4 bytes. Entonces si está alojado en la dirección de memoria 122 (por poner un ejemplo) el int ocuparía los 4 siguientes bytes, hasta la posición 126. Esto no necesitamos decirselo, porque al hacer el puntero de tipo entero ya sabe cuántos bits tiene que leer. Pues lo que podemos hacer es después de ese entero poner otro, y decirle al puntero que avance (al decirle el tipo ya sabe cuántos bytes tiene que avanzar) y leemos el siguiente. Y así todo lo que queramos hasta que lleguemos a una zona de memoria prohibida. En realidad los arrays hacen esto, y siempre se cumple que los elementos de un array están en posiciones de memoria correlativas (es decir, si tenemos un array a[], en la memoria, a[1] estará justo después de a[0]). Además, un array apunta a la dirección de memoria del primer elemento, es decir, si tenemos el array miarray[], miarray sin corchetes es la dirección de memoria del primer elemento del array. Vamos a ver que esto es cierto con un ejemplo:
#include
int main() {
int array[5] = { 1, 2, 3, 4, 5 }, *puntero;
puntero = array;
//acabamos de hacer que puntero apunte también
//al primer elemento del array
printf("primer elemento del array: %d\ncontenido del puntero: %d",array[0],*puntero);
return 0;
}
Podéis compilarlo y ejecutarlo vosotros mismos para ver qué sale, intentad predecir primero lo que va a hacer el programa.
Después de ver esto, podríais pensar "si el nombre del array sin corchetes guarda una dirección de memoria, ¿entonces podemos usar el operador de desreferencia (*) con el nombre del array igual que hacemos con los punteros?". Pues sí, si se puede hacer, podéis probarlo vosotros mismos. Este es el mismo ejemplo de antes pero usando el * también con el array:
#include
int main() {
int array[5] = { 1, 2, 3, 4, 5 }, *puntero;
puntero = array;
printf("primer elemento del array: %d\ncontenido del puntero: %d",*array,*puntero);
return 0;
}
Según lo que hemos aprendido antes, desde el puntero también podemos acceder a la segunda posición del array, y a la tercera, y a la cuarta... ¿Cómo se hace? solo hay que sumarle al array tantos elementos como queramos avanzar. Al principio estamos en la posición [0] del array, pero si queremos ir a la [1] habrá que sumarle 1. Eso nos dará la dirección de memoria del segundo elemento, el que está en la posición [1]. Ahora queremos desreferenciar esa dirección de memoria para obtener su valor, pero para que no se confunda el compilador lo hacemos entre paréntesis. Vamos a ver otro ejemplo:
#include
int main() {
int array[5] = { 1, 2, 3, 4, 5 }, *puntero;
puntero = array;
printf("segundo elemento del array: %d\nahora lo mismo desde el puntero: %d",array[1],*(puntero+1));
return 0;
}
Esta forma de hacerlo puede resultar algo liosa, pero era necesario que supiérais que se puede hacer así. A mi en lugar de eso me gusta tratar a los punteros como arrays. Si después de un puntero ponemos corchetes, nos muestra el contenido de la dirección de memoria del array más lo que haya dentro de los corchetes. Para entenderlo mejor, hace lo mismo que un array. Vamos a verlo en otro ejemplo:
#include
int main() {
int array[5] = { 1, 2, 3, 4, 5 }, *puntero;
puntero = array;
printf("tercer elemento del array: %d\nahora lo mismo desde el puntero: %d",array[2],puntero[2]);
return 0;
}
A mi me parece más claro así, por lo que a partir de ahora yo usaré corchetes también con los punteros, pero vosotros podéis hacerlo de las dos formas.
Lo que vamos a ver ahora es un poquito más complicado, así que aseguraros de entender todos los ejemplos anteriores antes de seguir.
Hasta ahora siempre hemos usado punteros para que "apunten" a las direcciones de memoria de otras variables, pero también podemos nosotros reservar la memoria que queramos para ellos, y así podemos hacer arrays del tamaño que queramos (antes no podíamos definir el tamaño de un array con una variable, pero podemos hacerlo con punteros). Para hacer esto necesitamos unas funciones especiales que no están en , tenemos que incluir otra librería además de esa, . Las funcinoes que vamos a usar son: malloc() y calloc() (para reservar memoria), realloc() (para cambiar el tamaño de la memoria reservada) y free() (para liberar espacio). Para empezar vamos a reservar memoria para un solo elemento, pero antes vamos a ver de forma más detallada las funciones.
-
malloc() se encarga de reservar para nosotros la memoria que le pidamos (si intentamos acceder a memoria que no hemos reservado puede producirse un error). Devuelve un puntero a void que tenemos que transformar en el tipo de datos que usemos. Ese puntero "apunta" a la dirección de memoria tras la cual malloc ha reservado para nosotros tantos bytes como le hemos pedido, para que no se produzca ningún error al acceder ahí.
-
calloc() hace lo mismo que malloc pero nos rellena con ceros todos los bytes que reserva, es decir, nos garantiza que la memoria que nos ha reservado contendrá siempre 0 a no ser que nosotros cambiemos su contenido. Esta función recibe el número de elementos que queremos reservar y cuánta memoria ocupa CADA UNO (no en total).
-
realloc() sirve para cambiar cuánta memoria tenemos reservada. Veremos esta función con más detenimiento en el próximo post.
-
free() es muy importante, sirve para liberar la memoria que hemos reservado, recibe un puntero y hace que ya no tenga asignada memoria, con lo cual ya no se puede usar. Sirve o bien para borrar la memoria reservada en un puntero y reservar más o simplemente para no quedarse con memoria reservada al terminar el programa. Esto es importante porque si finalizamos un programa y teníamos memoria reservada es posible que la memoria siga estando reservada aunque ningún puntero apunte a ella ya. Lo que significa que eso es RAM que perdéis, hasta que reiniciéis el PC o la limpiéis con algún programa. Así que siempre, memoria que reserváis, memoria que liberáis luego. Que no se os olvide.Ahora vamos a ver cómo se usa malloc. Malloc viene de memory allocate que literalmente significa "asignar memoria". Hemos dicho que recibe el número de bytes que queremos reservar, pero ¿cómo sabemos cuantos bytes queremos reservar? Cada tipo de datos ocupa un número diferente de bytes, pero tranquilos, no tenéis que aprenderoslo de memoria. Para eso sirve sizeof(). Le pasamos una variable, un tipo de datos, una función o LO QUE SEA y nos dice cuantos bytes ocupa. Por ejemplo sizeof(int) nos devolverá lo que ocupa un int en bytes. Si queremos reservar 3 bytes pues ponemos sizeof(int)*3 (lo que ocupa un int * número de ints que queremos reservar). Además hemos dicho que devuelve un puntero a void, pero nosotros no queremos un puntero a void, sino uno a int (o a float o a lo que sea). Así que tenemos que transformarlo. Eso se hace simplemente poniendo antes el tipo de datos que queremos entre paréntesis. por ejemplo, al poner "(int *) malloc()" hemos transformado lo que devuelve malloc en un puntero a entero. Calloc funciona casi igual pero recibe el número de elementos a almacenar y cuánta memoria ocupa cada uno. Vamos a ver un ejemplo:
#include
#include //ademas de stdio tambien incluimos stdlib
int main() {
int *punt; //creamos el puntero
punt = (int *) malloc(sizeof(int)); //le asignamos tamaño suficiente para guardar un int
*punt = 10; //guardamos el valor 10 en la direccion que hemos reservado
printf("%d",*punt); //ahora mostramos su valor
free(punt); //y por ultimo liberamos espacio
return 0;
}
Acordaos de incluir stdlib para usar malloc y compañía. Con calloc sería igual pero en lugar de poner
punt = (int *) malloc(sizeof(int));
pondríamos:
punt = (int *) calloc(1,sizeof(int));
y así nos aseguramos de que hasta que no le asignemos un nuevo valor el contenido de punt será cero. Seguirá mostrando 10 en el prinf porque le hemos cambiado el valor, pero si no lo hiciéramos saldría 0. Ponemos 1 porque solo vamos a guardar un elemento en el array, y cada uno ocupa el tamaño de un int (sizeof(int)). Vamos a ver el mismo ejemplo de antes pero guardando dos números:
#include
#include //ademas de stdio tambien incluimos stdlib
int main() {
int *punt; //creamos el puntero
punt = (int *) malloc(sizeof(int)*2); //le asignamos tamaño suficiente para guardar dos ints
punt[0] = 10; //guardamos el valor 10 en la posición 0
punt[1] = 20; //guardamos el valor 20 en la posición 1
printf("%d %d",*punt,*(punt+1)); //ahora mostramos su valor, lo hago de la otra forma para que veais que da igual
free(punt); //y por ultimo liberamos espacio
return 0;
}
Ejericio 32 (M): Crea un programa que le pregunte al usuario cuántos números quiere guardar, reserve suficiente memoria para guardarlos todos y luego se los pida al usuario y los almacene. Por último los mostrará por pantalla en orden.
Ejericio 33 (M): Diseña un programa que pregunte al usuario un número N. El programa reservará memoria en dos punteros diferentes para almacenar N números en cada uno y luego le pedirá al usuario que los rellene con datos. Para terminar mostrará el producto de los números de ambos arrays en posición par y la suma de los números de ambos arrays en posición impar.