martes, 10 de enero de 2017

APUNTADORES

CAPÍTULO 7         APUNTADORES 







OPERADORES REFERENCIA-DESREFERENCIA
Un apuntador se define como una variable que contiene una dirección de memoria.
Para declarar un apuntador se hace uso del operador desreferencia o indirección (*),
no se debe confundir con la multiplicación.
Al declarar un apuntador debemos de hacerlo de la siguiente forma:
tipo *nombre;
int *MiApuntador;
Ya habíamos dicho anteriormente que el nombre de un arreglo apunta al primer
elemento de éste, es decir contiene la dirección en memoria del elemento 0.
Tomando en cuenta esto, podemos ver que si hacemos:
int arreglo[10];
int *apuntador;
apuntador = arreglo;
sucede que, pasamos la dirección del primer elemento del arreglo a la variable
“apuntador” que está determinada para almacenar una dirección de memoria.
75
0F40 0FF0 0FF1 … Direcciones
arreglo [0] [1] … Nombre o elemento
0FF0 548 9765 … Valor que contiene
0FA1 Dirección
apuntador Nombre
0FF0 Valor que contiene
Las variables también tienen una dirección en memoria, y para saber cual es
debemos utilizar el operador referencia o dirección (&).
int MiVariable=20;
int *MiApuntador;
MiApuntador = &MiVariable;
Así “MiApuntador” contendrá la dirección de “MiVariable”.
El uso de los apuntadores sirve para hacer uso de una variable con la cual no se
tiene contacto directo, por medio del operador desreferencia. Por ejemplo, podemos
asignar un nuevo valor a esta variable referenciada.
*MiApuntador=9;
por medio de la instrucción anterior se está asignando un valor de 9 a la variable que
apunta “MiApuntador”. Es decir que “MiVariable” ahora contiene un 9.
Un pequeño ejemplo nos mostrará lo dicho hasta el momento:
76
#include<iostream.h>
int main(){
    int variable=10;
    int *apuntador;
    cout<<"la variable contiene un "<<variable<<endl;
    apuntador=&variable;
    cout<<"variable esta en la direccion "
        <<apuntador<<endl;
    *apuntador=15;
    cout<<"la variable ahora tiene un "<<variable<<endl;
    cout<<"ahora tecle un numero"<<endl;
    cin>>*apuntador;
    cout<<"y ahora contiene un "<<*apuntador<<endl;
    cin.ignore();
    cin.get();
    return 0;
}
Como podemos ver podemos usar un apuntador para asignar cantidades (o lo que
sea) a la variable referenciada ya sea por instrucción o por medio del teclado.
En la última instrucción cout se imprime el valor de “lo que apunta ‘apuntador’ ”.
ARITMÉTICA DE OPERADORES
Quizá ya se dio cuenta, así como la multiplicación es el inverso de la división, en el
caso de los apuntadores sucede algo parecido, si se coloca una instrucción como la
que sigue:
cout<< “la variable contiene ” <<&*variable;
o
cout<< “la variable contiene ” <<*&variable;
77
tendrán el mismo resultado que si no se colocaran los operadores * y &.
Un arreglo se puede considerar un apuntador (por las razones anteriormente
mencionadas), y por lo tanto se puede aplicar la aritmética de operadores en ellos,
en realidad no es muy diferente de cómo lo hacíamos ya antes, pero ahora
utilizaremos el “ * ”.
Un array puede ser operado mediante incrementos y decrementos, por ejemplo:
MiArreglo [ 7 ];
También puede hacerse:
*(MiArreglo + 7);
Aunque no es nada recomendable utilizar este método para tratar los arreglos, nos
da la idea de que se pueden hacer operaciones de suma y resta sobre los
apuntadores. Si hacemos una instrucción como:
apuntador += 6;
la dirección de memoria almacenada cambia a 3 lugares adelante. Por consiguiente,
también se puede aplicar una resta.
Cuando encontramos una instrucción del tipo:
*puntero = una_variable+6;
se está haciendo una asignación a “lo que apunta ‘puntero’”. Se modificará el valor
de la variable referenciada por “puntero”. Igual pasa con asignaciones de otro tipo:
*puntero *=3;
78
que en este caso se hace una multiplicación de la variable referenciada por 3 y luego
se vuelve a asignar.
ARREGLOS DE APUNTADORES (ARREGLOS DE CADENAS)
Habíamos dejado pendiente un momento este tema, porque era mejor ver primero el
concepto de apuntadores, ahora que ya sabemos en que consiste, podemos declarar
e inicializar una cadena de caracteres de la siguiente forma:
char *cadena = “alguna_palabra”
Si quisiéramos declarar una cadena con un determinado número de elementos, es
necesario utilizar uno de los métodos ya conocidos.
Un arreglo de cadenas en realidad se trata de un arreglo de apuntadores porque una
cadena es un apuntador al primer carácter. Entonces, un arreglo de cadenas se vería
de la siguiente forma.
Alumnos
[0] [1] [2] [3] [4]
‘J’ ‘A’ ‘L’ ‘E’ ‘A’
‘o’ ‘l’ ‘u’ ‘n’ ‘l’
‘s’ ‘p’ ‘i’ ‘r’ ‘b’
‘e’ ‘h’ ‘s’ ‘i’ ‘e’
‘\0’ ‘o’ ‘\0’ ‘q’ ‘r’
‘n’ ‘u’ ‘t’
‘s’ ‘e’ ‘o’
‘e’ ‘\0’ ‘\0’
‘\0’
79
La declaración del arreglo “Alumnos” sería de la siguiente forma:
char *Alumnos[4] = {“Jose”, “Alphonse”, “Luis”, “Enrique”, \
“Alberto”};
Notemos que de esta forma no es necesario colocar el carácter ‘\0’ al final de cada
cadena, el compilador la coloca por sí sólo. También se pueden utilizar las otras
formas, que ya conocemos, de inicializar un arreglo de dos dimensiones y se puede
especificar el tamaño de las cadenas.
char MasAlumnos[4][10];
Veamos entonces que Alumnos[0] contiene la dirección en memoria que ocupa la
letra ‘J’.
Y para acceder a cada carácter del arreglo declarado de cualquier forma, se utilizan
los subíndices, por ejemplo, asignaremos con la siguiente instrucción un carácter
contenido en el arreglo a otra variable de tipo char:
caracter = Alumnos [1][5];
Así asignamos el carácter ‘n’ a la variable “caracter”
Un ejemplo nos ayudará a visualizar su utilización. El siguiente programa se trata de
un juego, o por lo menos la idea de cómo empezarlo. Consiste en teclear, lo más
rápidamente, las palabras que vallan apareciendo en la pantalla, se cuenta con un
determinado tiempo para hacerlo, si no se consigue dentro de ese intervalo
continuará a la siguiente palabra. El programa termina cuando presionan la tecla
“Esc”, pero bueno, aquí está el código.
80
#include<conio.h>
#include<stdlib.h>
#include<iostream.h>
#include<time.h>
int main(){
int cont=0,i=0,num_aleat=0;
char car;
    char *cadena[6]={"cinco","siete","texto","poema","radio",\
         "raton"};
while(car!=27){
         srand((unsigned)time(NULL));
         num_aleat=rand()%6;
         cout<<endl<<endl<<cadena[num_aleat]<<endl;
for(cont=0,i=0;(cont<100000)&&(cadena[num_aleat][i]!='\0');++cont){
     if(kbhit()){
     car=getch();
      if(car==cadena[num_aleat][i]){
                  cout<<car;
                  ++i;
              }
      fflush(stdin);
     }
     }
    }
cout<<endl<<"juego terminado"<<endl;
cin.get();
return 0;
}
El programa define primeramente una matriz de cadenas, para luego empezar el
juego mediante un ciclo while. Repetirá todas las instrucciones dentro del bloque
mientras no se oprima la tecla “Esc”. Se calcula un número aleatorio menor a 6 y se
almacena en una variable tipo int, la cual servirá para que se mande a pantalla,
mediante la instrucción cout, una cadena al azar, note que se le está enviando la
dirección del primer carácter de la cadena.
81
Después se usa un ciclo for, en el cual se inicializa la variable que contará el tiempo
(cont) y la variable de desplazamiento por los elementos de la tabla (i). Este ciclo se
detendrá cuando el contador de tiempo llegue a 100000 o cuando se llegue al
carácter ‘\0’ en la cadena.
Dentro de ese ciclo for se encuentra la parte más interesante del programa, usamos
una función (kbhit) que se encarga de detectar si se ha oprimido una tecla, esta
tecla no aparecerá en la pantalla pero sí se quedará dentro del flujo, si detecta que
se ha oprimido entonces, usando la función getch(), leerá y guardará en la variable
car aquel carácter que se quedó dentro del flujo, pero sin presentarlo en pantalla, si
el carácter leído corresponde con el carácter de la posición referida por “i”, entonces
si se presentará en pantalla y se continuará con el siguiente carácter (++i). Saliendo
del condicional se limpia el flujo de entrada (función fflush() definida en
stdlib.h) para evitar que se quede algún residuo o basura.
Las Funciones kbhit() y getch() no son parte de la librería estándar de C++,
estas librerías son propiedad de la compañía Borland® y que se distribuyen en sus
productos Borland/TurboC++. Sin embargo, debido a su amplio uso, están
disponibles también versiones para otros productos como lo son el Visual C++ de
Microsoft®, el Dev-C++ de Bloodshed, y por supuesto para el compilador GCC.
PASO DE ARGUMENTOS POR REFERENCIA
Anteriormente vimos como se hacia la modificación de un arreglo mediante el paso
por referencias simulado. En el caso de las variables de tipo int, float, etc.,
también es posible hacer uso de ellas mediante el paso por referencia.
82
El paso por referencia consiste en pasar la dirección en memoria que ocupa esa
variable para que la función que la recibe pueda hacer uso de ella, sin necesidad de
hacer una copia como ocurre con el paso por valor.
Para mandar la dirección es necesario utilizar el operador & dentro de la invocación a
la función. Y el prototipo y definición de la función debe especificar que será un
apuntador mediante el uso del *.
tipo_que_regresa nombre_funcion (tipo_apuntador *nombre, otro_tipo nombre, …);
Lo anterior es la forma de una definición que hace uso de apuntadores. La función
puede devolver o no un valor mediante una instrucción return(). Puede contener
varios parámetros apuntador o no apuntador.
Cuando se va a hacer uso de esa función, la invocación es de la siguiente forma:
nombre_funcion (&nombre, …);
En el caso de los arreglos recordemos que estos son la dirección, y no es necesario
poner el operador &.
nombre_funcion (arreglo[numero_cadena]);
La instrucción anterior, siendo “arreglo” un array de cadenas, envía la dirección del
primer carácter de la cadena numero_cadena.
El siguiente ejemplo mostrará estos aspectos.
83
#include<iostream.h>
void aMayusculas(char *cadena);
void cambiaNum(int *numero);
int main(){
    char palabra[10];
    int numero;
    cout<<"teclea una cadena "<<endl;
    cin.getline(palabra,10);
    aMayusculas(palabra);
    cout<<endl<<palabra<<endl;
    cout<<"ahora teclea un numero"<<endl;
    cin>>numero;
    cambiaNum(&numero);
    cout<<"este numero se cambio a: "<<numero;
    cin.ignore();
    cin.get();
    return 0;
}
void aMayusculas(char *cadena){
    for(int i=0;cadena[i]!='\0';++i)
             cadena[i]+=('A'­'a');
}
void cambiaNum(int *numero){
     *numero *= *numero;
}
El código no es difícil de entender, quizá la parte en la que cusa un poco de
confusión podría ser la función “cambiaNum”, simplemente lo que hace es multiplicar
“lo que apunta ‘numero’” por sí mismo, es decir lo eleva a cuadrado. Revise la tabla
de precedencia de operadores si se tienen más dudas sobre esto.
Gracias a las referencias de C++ es posible hacer una función cambiaNum() con
una sintaxis más sencilla y de una manera más amigable, utilizando el '&'.
void cambiaNum(int & numero){
numero *= numero;
}
84
de esta manera surtirá el mismo efecto que la que habiamos definido, cuando se
invoque sólo se hará:
cambiaNum(numero);
como si fuese una llamada normal.

No hay comentarios:

Publicar un comentario