lunes, 14 de noviembre de 2016

Capítulo 83. Punteros en C


Diario de un programador. Día 185


Punteros en C


En esta oportunidad vamos a ver una de las características que hace que el lenguaje C sea tan poderoso y el porqué es elegido por sobre otros para desarrollar sistemas operativos, me refiero a los punteros, también conocidos como apuntadores. Los punteros tienen la capacidad de trabajar directamente en la memoria como veremos a continuación. Entender los punteros es algo complicado así que voy a ir lo más pausado posible y dar muchos ejemplos para que quede claro, es indispensable entenderlos bien ya que los capítulos siguientes incluirán el uso de punteros.

Ventajas del uso de punteros

- En el caso de paso de variables a una función por dirección o referencia, es necesario emplear parámetros formales como punteros
- Permiten realizar operaciones de asignación dinámica de memoria
- Permiten efectuar operaciones con estructuras de datos dinámicas
Todo lo mencionado anteriormente lo iremos viendo a medida que vamos avanzando.

Una primera aproximación

Hasta ahora sabemos que las variables contienen algún valor en específico, por ejemplo la siguiente variable llamada num1 contiene el valor de 7:

int num1 = 7;

Hasta aquí, todo bien. En cambio, un puntero lo que contiene es la dirección de memoria de alguna variable.
Entonces, podemos decir que un puntero es una variable que en vez de contener un valor, contiene una dirección de memoria. En este sentido, se entiende que una variable se refiere directamente a un valor y un apuntador se refiere indirectamente a un valor
Entonces, ¿si un puntero es una variable, como se declara?. Esto lo veremos a continuación.

¿Cómo se declara un puntero?

Para que el compilador entienda que lo que queremos declarar es un puntero y no una variable, hay que agregar el signo * antes del nombre que escogeremos para nuestro puntero. El signo * se conoce como operador de indirección.

Por ejemplo:

int *ptrNum1;

Por cierto, leí que los apuntadores deben ser inicializados cuando son declarados, para evitar algún resultado inesperado. Un apuntador se puede inicializar con valor 0, NULL o una dirección.
Estas inicializaciones quedarían de la siguiente manera:

int *prtNum1 =  0;
int *prtNum1 =  NULL;
int *prtNum1 =  &variable;

Las tres maneras de inicializar un puntero son válidas. Destaqué la tercera opción ya que de esa manera se indica una dirección. Al agregar el signo & indicamos que lo que se debe asignar es una dirección de memoria. ¿Recuerdas que la función scanf usa el operador & para asignar la variable a la memoria? El operador & se le conoce como el operador de dirección.
Recordar y aprender entonces que:

- El operador de indirección es el *
- El operador de dirección es el &

Ya que sabemos esto, veamos un pequeño ejemplo en código a continuación.


Implementando un puntero

El siguiente código muestra cómo asignar a un puntero la dirección de memoria de otra variable.

#include<stdio.h>

int main(void){
  int num1 = 7;
  int *ptrNum1 = &num1;
  printf("Valor de num1: %d\n", num1);
  printf("Valor del puntero prtNum1: %p\n", ptrNum1);
return 0;

}


En la línea 4 se crea una variable de tipo entero llamada num1, la cual tiene asignada el valor 7. En la línea 5 se crea una variable de tipo puntero llamada ptrNum1, la cual tiene asignada la dirección de la variable num1. Como se dijo antes, para asignar una dirección de memoria se utiliza el operador de dirección &.
La línea 7 imprime el valor de la variable num1, esta variable como ya dijimos contiene el valor 7. La línea 8 imprime el valor del puntero ptrNum1, como dijimos antes este puntero tiene asignado la dirección de memoria de la variable num1. Destaqué el modificador de formato que utiliza el puntero, el modificador %p se utiliza para mostrar el valor de los punteros, los cuales se muestran en formato hexadecimal. Si hubiese puesto un %d, mostraría su valor en formato decimal, pero por tratarse de direcciones de memoria, lo normal es mostrarlos en hexadecimal.
Al ejecutar este programa, me muestra el siguiente resultado


Muy bien, ahí tenemos una dirección de memoria, ¿pero cómo sabemos que esa dirección de memoria contiene el valor de la variable num1? Para este caso, utilizamos el operador de indirección *
Voy a agregar una línea más al programa anterior para averiguar el contenido de esa dirección de memoria.


#include<stdio.h>

int main(void){
  int num1 = 7;
  int *ptrNum1 = &num1;

  printf("Valor de num1: %d\n", num1);
  printf("Valor del puntero prtNum1: %p\n", ptrNum1);
  printf("Contenido del puntero prtNum1: %d\n", *ptrNum1);

return 0;
}



Vamos a ver otro ejemplo.
Vamos a crear un programa en el cual se tendrán dos variables. La primera variable tendrá un valor y la segunda variable será igualada a la primera. De esta forma:

int a = 7;
int b = a;

Lo siguiente que hará el programa será mostrar en pantalla el valor de ambas variables. Luego, se modificará el valor de la primera variable y se volverá a mostrar en pantalla el resultado. Como la segunda variable está igualada a la primera, lo normal sería pensar que su valor también será modificado. Veamos que sucede.

#include<stdio.h>

int main(void){
  int a = 7;
  int b = a;

  printf("valor de a: %d\n", a);
  printf("valor de b: %d\n\n", b);

  a = 10;
  printf("Nuevo valor de a: %d\n", a);
  printf("Nuevo valor de b: %d\n", b);

return 0;
}

Al ejecutar el programa, esto es lo que muestra:


El valor de la variable b no se modificó. Esto es porque la variable b, no es realmente una copia de la variable a. La variable b es una variable totalmente nueva que tiene su propia dirección de memoria y que se inició en un principio con el valor de la variable a. Es por esto que al modificar el valor de la variable a, la variable b se mantuvo sin cambios. Entonces ¿cómo hacemos para que la variable b actualice su valor a medida que la variable a se va modificando a lo largo del programa?, la respuesta es con el uso de punteros.

Vamos a modificar la variable b y la transformaremos en un puntero.


int main(void){
  int a = 7;
  int *b = &a;

  printf("valor de a: %d\n", a);
  printf("valor de b: %d\n\n", *b);

  a = 10;
  printf("Nuevo valor de a: %d\n", a);
  printf("Nuevo valor de b: %d\n", *b);

return 0;
}

Al ejecutar el programa con la modificación hecha, esto es lo que obtenemos.


Dejaremos este tutorial hasta aquí el día de hoy para que repasen bien este asunto antes de seguir ya que hay mucho más que aprender.
Buena suerte y saludos.

Gustavo J. Cerda Nilo
Septiembre 2016, Noviembre 2016



No hay comentarios:

Publicar un comentario

C++ El apuntador This

El apuntador This En C++, cada objeto tiene acceso a su propia dirección a través de un puntero o apuntador denominado This. Lo...