1. Que son los apuntadores
Un
apuntador es una variable que almacena una dirección de memoria. Lo primero que
se debe hacer al trabajar con apuntadores es declararlos, la forma como se
declara un apuntador se muestra a continuación:
Donde1:
1
Las cosas que se encuentran entre corchetes son opcionales.
Tipo: Tipo de dato al cual se
desea apuntar, puede ser un tipo de dato simple (char, int, etc) o un tipo de
dato complejo como una estructura o una clase.
Modificadores del tipo: Puede
contener cualquier combinación de los modificadores de tipo const, volatile y
restrict.
Nombre: Nombre del apuntador.
Valor inicial: Valor inicial del apuntador.
Por ejemplo, supóngase que se
declaró un puntero a una variable la cual tiene el valor de 5 tal y como se
muestra a continuación:
int
theVariable = 5;
int *pPointer = &theVariable;
Como se puede apreciar el
valor almacenado en el apuntador es la dirección de memoria de la variable a la
cual está apuntando. Esto fue posible gracias al operador dirección (&).
Con base en la figura anterior se construyó la siguiente tabla para clarificar
su uso:
l Significado
|
Valor
|
||
theVariable
|
Contenido
de theVariable
|
5
|
|
&theVariable
|
Direccion
de theVariable
|
101
|
|
pPointer
|
Contenido
del apuntador pPointer
|
101
|
|
&pPointer
|
Direccion
del apuntador pPointer
|
106
|
|
Como se
puede notar de la tabla anterior el valor obtenido con el operador & es la
dirección en la cual se encuentra la variable en cuestión. Como una variable
puede ocupar más de 1 byte, el valor resultante es el byte asociado a la
dirección base de la variable.
Ahora
bien, con el apuntador es posible acceder a cualquier lugar de memoria y
modificar su valor. Para ello se tiene que referenciar y desreferenciar el
apuntador. Esto se describe a continuación:
2.
Referenciando apuntadores
Consiste en asociar el apuntador a una dirección
específica, para esto se suele usar el operador & para obtener la dirección
de la variable en cuestión. A continuación se muestra la forma como normalmente
se hace esto:
apuntador = &variable;
También es posible referenciar
un apuntador pasándole el valor que se tiene en otro apuntador. Note que no se
hizo uso del operador & en este caso:
apuntador1 = apuntador2;
Todo
apuntador debe inicializarse antes de usarse. Si esto no se hace, cuando
intente usarlo para hacer alguna operación en memoria el programa sacara un
error. Un puntero que no ha sido inicializado se conoce como Wild pointer.
En la siguiente figura se ilustra un poco mejor lo
anterior:
int
i,j;
int *p; //Apuntador a un
entero

Hasta el
momento solo se ha declarado el apuntador pero no se ha referenciado, en la
siguiente figura se muestra el efecto de referenciar el apuntador: p = &i;

Es
posible que varios punteros estén apuntando a un mismo lugar de memoria: int i;
int *p,*q,*r;
p = &i;
q = &i;
r = p;

3.
Deseferenciando un apuntador
Para
poder acceder al lugar de memoria que está siendo apuntado por el puntero y
realizar operaciones de lectura y escritura sobre este el puntero se debe
desreferenciar. Para ello se hace uso del operador desreferencia (*).
El valor del lugar de memoria apuntado se obtiene
de la siguiente manera:
variable = *apuntador;
Ahora si lo que se desea hacer
es escribir en el lugar de memoria apuntado se hace lo siguiente:
*apuntador = valor;
La siguiente figura muestra el
resultado de desreferenciar un apuntador:
*p
= 5;

Como se
puede notar de la figura anterior, es posible modificar el valor de i desde el
apuntador. Vale resaltar que todo apuntador antes de ser desreferenciado debió
haber sido previamente inicializado con una dirección valida.
4. Usos
de los apuntadores
Los
apuntadores se usan principalmente para 3 cosas:
Crear estructuras de
datos dinámicas.
Manejar parámetros
variables pasados a funciones.
Acceder
de a los diferentes elementos de arreglos o estructuras.
A
continuación se trata con más detalle cada una de estas aplicaciones.
4.1
Funciones y apuntadores
Existen dos maneras de hacer
llamados a funciones, por referencia y por valor. Cuando se realiza un llamado
por valor; se trabaja sobre una copia de la variable pasada como argumento y
por lo tanto la variable original (la que se pasó como argumento) no se
modifica. Por otro lado, cuando se realiza una llamada por referencia al estar
accediendo al lugar de memoria en el que se encuentra la variable pasada como
argumento es posible modificar el valor original de la variable pasada como
argumento.
El paso
de funciones por referencia es de extrema utilidad cuando los argumentos que se
están pasando a la función son pesados ya que esto evita que se tengan que
hacer copias de dichos argumentos que en el peor de los casos pueden ocasionar
que el programa colapse por llenar stack. También, mediante el uso de
apuntadores, es posible superar la restricción que se tiene en la cual una
función no puede retornar más de un elemento; así, por medio de referencias es
posible retornar un array por ejemplo.
Para indicar que una función será pasada por
referencia, se emplean apuntadores en la cabecera de la función, esto porque lo
que se pasa como argumento es la dirección de memoria. Por ejemplo:
tipo_retorno f(tipo_1
*pName_1,tipo_2 *pName_2,...,tipo_N *pName_N)
Para aterrizar un poco más lo
anterior, supongamos esta función:
void swap(int *px, int *py) {
int temp;
cout << "Swap. Before
swap, *px: " << *px <<
" *py: " << *py
<< endl;
temp = *px;
*px = *py;
*py = temp;
cout << "Swap. After
swap, *px: " << *px <<
"
*py: " << *py << endl;
}
Como se
pueden notar en la definición de la función anterior, en este caso ambos
argumentos son pasados por referencia.
Ahora en lo que respecta a la invocación si lo que
se pasa es como parámetro es una variable como tal se debe hacer uso del
operador & para obtener la dirección de dicha variable y así inicializar el
apuntador que funciona como argumento. Por otro lado si lo que se está pasando
es un apuntador a una variable, no es necesario usar el operador & ya que
el valor almacenado en este será una dirección de memoria. La siguiente tabla
ilustra esto:
Caso
|
Invocacion
|
Observaciones
|
Se está
pasando una variable a una función que se llama por referencia
|
int x =
5, y = 10;
swap(&x,&y);
|
Es
necesario usar el operador & para obtener la dirección de memoria de las
variables y así poder inicializar lo apuntadores que funcionan como
argumentos.
|
Se está
pasando apuntador a una función que se llama por referencia
|
int x = 5, y = 10;
int *px = &x, *py;
py =
&y;
swap(px,py);
|
Como lo
que se pasan son apuntadores previamente inicializados, estos ya tienen la
dirección de memoria de la variable que será pasada como argumento de la
función, por lo tanto no es necesario usar el operador &.
|
¿Qué sucede si lo que se pasa
como argumento es lo resaltado?
int x = 5, y = 10;
int *px = &x, *py;
py
= &y;
swap(&px,&py);
Codifique y compile el
siguiente código:
//
Demuestra el uso de funciones por referencia
#include
<sdtio.h>
void swap(int *x, int *y);
void swapv(int x, int y);
int main() {
int x = 5, y = 10;
printf("---------------------------------------------------\n");
printf("Llamada
por valor \n");
printf("Main.
Antes del swap; x: %d, y: %d \n");
swapv(x,y);
printf("Main. Despues del swap; x: %d, y: %d
\n");
printf("---------------------------------------------------\n");
printf("Llamada
por referencia \n");
printf("Main.
Antes del swap; x: %d, y: %d \n");
swapr(&x,&y);
printf("Main. Despues del swap; x: %d, y: %d
\n");
printf("---------------------------------------------------\n");
return 0;
}
void swapr(int *px, int *py) {
int temp;
printf("Swapr. Antes del
swap; *px: %d, *py: %d\n",*px,*py);
temp = *px;
*px = *py;
*py
= temp;
printf("Swapr.
Despues del swap; *px: %d, *py: %d\n",*px,*py);
}
void swapv(int x, int y) {
int
temp;
printf("Swapv.
Antes del swap; x: %d, y: %d\n",x,y);
temp
= x;
x
= y;
y
= temp;
printf("Swapv. Despues
del swap; x: %d, y: %d\n",x,y);
}
No hay comentarios.:
Publicar un comentario