viernes, 8 de abril de 2011

Capitulo 7. Punteros

Fuente:
Programación en C++ / Principios y Aplicaciones (resumen)
Carlos Romero Shollande

1.   Introducción

Un puntero es una variable que tiene la dirección de memoria de otra variable que contiene un valor.

Gráfica 1. Dirección de memoria.

 


La dirección es la posición que ocupa en la memoria una variable, un arreglo, una cadena, una estructura, otro puntero, etc.
Normalmente las variables contienen valores específicos. En cambio, los punteros contienen direcciones de variables que contienen valores específicos.




Si una variable contiene la dirección de otra variable entonces se dice que: “la primera variable apunta a la segunda”.

Gráfica 2. Puntero de una variable a otra variable.




En este sentido, los nombres de variables hacen referencia directa a un valor y los punteros hacen referencia indirecta a un valor. La referencia a un valor a través de un puntero se llama indirección.






Gráfica 3. Referencia directa e indirecta a una variable.

cont hace referencia directa a una variable cuyo valor es 23



cont hace referencia indirecta a una variable cuyo valor es 23


Existen tres razones para usar punteros en la creación de programas:

-   Los punteros proporcionan los medios mediante los cuales las funciones modifican sus argumentos de llamada.
-   Se pueden utilizar para soportar las rutinas de asignación dinámica del C++.
-   Se puede sustituir por arreglos en muchas situaciones para incrementar la eficacia.

2. Variable Puntero

Sintaxis      : tipo *nombre_variable;
Propósito    : Declarar si una variable contiene una dirección de memoria. Donde tipo puede ser cualquier tipo base del C++, y éste define a que tipo de variable puede apuntar el puntero.
Ejemplo      : int *punt, k;

Esta sentencia declara que punt apunta a un valor u objeto de tipo entero. Cada variable que se declara como puntero debe ir precedida por un *.
Los punteros se deben inicializar a 0, a NULL o a una dirección, al declararlos o mediante una asignación, para evitar que apunten a áreas desconocidas de memoria.

3. Operadores Puntero

Existen dos operadores especiales de punteros:

&   : Operador de dirección, devuelve la dirección de memoria de su operando (variable a la que precede).

Gráfica 4. Aplicación del operador de dirección.


 punt = &cont;



Coloca la dirección de memoria 3500 de la variable cont en punt y la variable puntero punt está ubicada en la dirección 4000. Esta dirección es una posición interna del ordenador y no tiene que ver con el valor de cont.
Se lee que punt “recibe la dirección del valor” cont.

*   : Operador de indirección, es el complemento de &, devuelve el valor de la variable situada en la dirección que sigue (sinónimo de la variable hacia el que apunta su operando).

valt = *punt;

Si punt contiene la dirección de memoria de la variable cont, entonces coloca el valor de cont en valt. Ejemplo, si cont al inicio tenía el valor 23, entonces, después de esta asignación, valt tendrá el valor 23 ya que es el valor guardado en la posición 3500, que es la dirección de memoria que asignó a punt.
Recordar al * como “en la dirección”.
Se lee como que valt “recibe el valor de dirección” punt.
El proceso que consiste en reportar el valor de una variable precedida por un * se conoce como desreferenciación de un puntero.
Ejemplo:   cout<<*punt;
Esta sentencia reporta el valor de la variable cont, es decir 23.
También puede utilizarse un puntero desreferenciado del lado izquierdo de una sentencia de asignación.
Ejemplo:   *punt = 25;
Esta sentencia asigna el valor 25 a la variable cont.
El puntero desreferenciado también puede usarse para recibir un valor de entrada.
Ejemplo:   cin>>*punt;
Como el signo de multiplicación y el de “en la dirección” es el mismo, al escribir un programa, tener en cuenta que estos operadores no tienen relación.
& y * tienen una precedencia más alta que el resto de operadores aritméticos.
El tipo base del puntero determina el tipo de datos que el compilador asumirá que apunta el puntero.
Como punt es un puntero entero, C++ copia dos bytes en valt desde la dirección a la que apunta punt.

4. Expresiones con Punteros

a)        Se puede usar un puntero en la parte derecha  de una sentencia de asignación para asignar el valor del puntero a otro puntero. Ejemplo:

int t, *x, *y;
t = 450;  x = &t;  y = x;
cout<<“La dirección es : ”<< y;

Este fragmento de programa visualiza la dirección de t en hexadecimal, y no el valor de t.

b)        En aritmética convencional el resultado de 3000+2 es 3002. Pero en aritmética de punteros esto no es así, pues si a un puntero se le suma (+) o resta (-) un entero; dicho puntero no aumenta o disminuye en la misma cantidad que el entero, sino en el entero por el número de bytes de la variable hacia la que apunta. Número de bytes depende de tipo de dato de variable.
Ejemplo:   x += 7;
Si la variable x es de tipo entero; entonces, después de esta operación, x apuntará a la dirección 3014 (3000 + 7 * 2), ya que los enteros poseen dos bytes.
Si el ejemplo hubiera sido:   x – = 4;
Entonces x apunta a la dirección 2992 (3000 – 4 * 2).

c)        Se pueden incrementar (++) o disminuir (- -) punteros. Por ejemplo: sea y un puntero a un número flotante con la dirección actual 3500.
Después de la expresión:  y++;  la dirección de y será 3504 y no 3501.
Cada vez que se incrementa y, la computadora apuntará al siguiente flotante, que usa cuatro bytes.

Gráfica 5.  Variación de un puntero.










d)        Es posible asignar un apuntador a otro, si ambos son del mismo tipo. De otro modo, es necesario emplear un operador de conversión mediante cast para convertir el valor del apuntador del lado derecho de la asignación al tipo de apuntador a la izquierda de la asignación.

e)        Es posible comparar dos punteros en una relación:
           
if(g>h) cout<<“g apunta a dirección más alta que h”;

Se usan las comparaciones de punteros, generalmente, cuando dos o más punteros apuntan a un objeto común.

f)    No se pueden realizar otras operaciones sobre punteros; es decir, no se puede multiplicar o dividir punteros, no se puede sumar o restar dos punteros y no se puede sumar o restar los tipos float o double a punteros.

5. Punteros y Arreglos

Como la velocidad es una consideración frecuente en programación, el uso de punteros es más común que el método de indexación de arreglos, debido a que C++ tarda más en indexar un arreglo que lo que hace el operador *.
El método de los punteros requiere de las siguientes consideraciones:

a)        Un nombre de arreglo sin índice, es la dirección del comienzo del arreglo. Es decir, el nombre de un arreglo es un puntero al arreglo. Ejemplo:

char w[50], *x;
 x = w;

Aquí se pone en x la dirección de 1er. elemento de w.

b)    Debido a que los índices de arreglos comienzan en cero; para acceder a un determinado elemento de un arreglo; por ejemplo el cuarto elemento de w, se puede escribir:

w[3]    ó    *(w+3)

c)    En el caso del arreglo bidimensional g, cuyos contadores i y j se inicializan a partir de cero, el acceso puede especificarse como:

g[i][j]    ó     *(*(g + i) + j)

d)    Se puede indexar un puntero como si fuera un arreglo: int g[7] = {1, 2, 3, 4, 5, 6, 7};  int j, *x;

x = g;  for(j=0; j<7; j++) cout<<x[j];

Aquí se visualiza en la pantalla los números del 1 al 7.

e)    El C++ trata a las cadenas como punteros al primer carácter de cadena y no como cadena propiamente dicha

strlen(char *q)   {
int j = 0;
while(*q)  {   j++; q++;   }
return j;   }

Recordar que toda cadena termina en un nulo (o falso).

while(*q)

Es verdad hasta que se alcance el final de la cadena.

f)         Si se quiere acceder al arreglo aleatoriamente, es mejor la indexación, ya que es tan rápido como la evaluación por punteros y por que es más fácil programar.

g)    Se puede asignar la dirección de un elemento específico de un arreglo aplicando el & a un arreglo indexado:

q = &y[7];

Esta sentencia coloca la dirección del octavo elemento de y en q. Esto es útil en la localización de subcadenas.

char w[60],*y;   int t;
cout<<“Introducir una cadena: ”;  gets(w);
for(t=0; w[t] && w[t]==’’;t++);
y = &w[t];   cout<<y;

Esto imprime el resto de una cadena, que se introdujo por teclado, desde el punto en que la computadora encuentre el primer espacio o el final de la cadena.

h)    Recordar que el acceso a una cadena individual se especifica solamente con el índice izquierdo.

gets(y[i]);

Esta sentencia es funcionalmente equivalente a:

gets(&y[i][j]);

i)          Se pueden hacer arreglos de punteros. Ejemplo:

int *b[10];

Declara un arreglo de punteros int de tamaño 10.

j)     Para asignar una dirección de variable entera llamada z al quinto elemento del arreglo de punteros, se escribirá:

b[4] = &z;

y para encontrar el valor de z se escribirá:   *b[4]

6. Punteros a Punteros

Sintaxis     : tipo **nombre_variable
Propósito   : Declarar un puntero a un puntero, es decir efectuar un direccionamiento indirecto múltiple o una cadena de punteros.
Ejemplo    : float **pony;

Esta declaración le dice al compilador que pony es un puntero a un puntero tipo float, y que no es un puntero a un número en punto flotante.

Gráfica 6. Inderección simple e inderección múltiple.







Como se ve en la figura anterior, en el caso de un puntero normal, el valor del puntero es la dirección de la variable que contiene al valor deseado.
En el caso de un puntero a puntero, el primer puntero contiene la dirección del segundo puntero, que apunta a la variable que contiene el valor deseado.
Se puede llevar direccionamiento indirecto múltiple a cualquier extensión, pero en exceso es difícil de seguir y propensa a errores conceptuales (no confundir direccionamiento indirecto múltiple con listas enlazadas, que se usan en aplicaciones como las bases de datos).

7. Errores con Punteros

Los siguientes son algunos de errores al usar punteros:

-            Después de declarar un puntero, pero antes de asignarle un valor, contendrá un valor desconocido.
Si se trata de usar el puntero antes de asignarle un valor, es probable que cause un error extremadamente grave, no sólo para el puntero, sino para el sistema.
Se puede asignar a un puntero que apunta a ninguna parte el valor nulo, para significar que el puntero apunta a nada o está sin usar. Ejemplo:

for(k=0; x[k]; ++k) if(!strcmp(x[k], q)) break;

El bucle funcionará hasta que la rutina encuentre la cadena que busca o el puntero nulo, suponiendo que el último elemento del arreglo x está marcado con un nulo

-            El artificio anterior puede no ser tan seguro.
Si se usa un puntero nulo en el lado izquierdo de una sentencia de asignación, se correría todavía el riesgo de estropear el programa o el sistema operativo.
Por lo que se recomienda inicializar las cadenas:

char *g= “Buenos Días”;

Como se puede ver, el puntero g no es un arreglo.
El C++ crea lo que se llama tabla de cadenas, que se usan internamente para guardar las constantes de cadena.
Por tanto, esta sentencia de declaración pone la dirección de la cadena “Buenos Días”, almacenada en la tabla de cadenas, en el puntero g.
En el programa, g se puede usar como otra cadena.

-            Otro error es provocado por el mal entendimiento de la forma de usar un puntero. Ejemplo:

int x, *y;
x = 25;  y = x;
cout<<*y;

La llamada cout no imprimirá el valor de x, que es 25, sino algún valor desconocido.
La razón es que la asignación y = x; es errónea.
La sentencia asigna el valor 25 al puntero y, que se supone contiene una dirección y no un valor.
La sentencia correcta sería:

y = &z;

8. Uso de TypeDef

Sintaxis     : typedef nom_tipo_actual nuevo_nom_tipo;
Propósito   : Crear nuevos nombres de datos o sinónimos para tipos de datos anteriormente definidos, esto realmente no crea una nueva clase de datos sino que lo redefine, a fin de crear nombres de tipos más breves.
Ejemplo     : typedef float Real;

La sentencia del ejemplo anterior define el nuevo nombre de tipo Real como un sinónimo para el tipo float. Después se puede crear una variable de tipo punto flotante:
Real Numero

Ejemplo:

Ingresar 42 números enteros en un arreglo de orden 7 x 6 y realizar las siguientes operaciones:

-            Imprimir el arreglo
-            Encontrar el mayor elemento del arreglo
-            Indicar en que posición se encuentra el elemento mayor del arreglo
-        Si el elemento mayor está repetido, indicar cuantas veces lo está y la posición de cada elemento repetido.

Codificación:

#include <iostream.h>       // Procesos con arreglos
#include <conio.h>
#define lim 20
tipedef int entero;
main()  {
entero i, j, h, k, g, mayor, x[lim][lim];
clrscr();
cout<<"\nElementos de la matriz\n\n";
for(i=1;i<=7;i++)    {
for(j=1;j<=6;j++)    {
gotoxy(j*8,i+3); cin>>*(*(x + i) + j);    }
}
clrscr();  cout<<"Matriz Original\n\n";
for(i=1;i<=7;i++)    {
for(j=1;j<=6;j++)  cout<<*(*(x + i) + j)<<"    ";
cout<<endl;    }
mayor = *(*(x+1)+1);
for(i=1;i<=7;i++)    {
for(j=1;j<=6;j++)    {
if(*(*(x+i)+j)>mayor)    {
h = i;   k = j;   mayor = *(*(x+i)+j);
break;     }
}
}
cout<<"\nEl mayor elemento del arreglo es: "<<mayor;
cout<<"\n\nPosición en la que se encuentra\n\n";
cout<<"Fila   : "<<h<<endl;
cout<<"Columna: "<<k<<endl<<endl;
g=0;
cout<<"Posiciones en las que el mayor valor está repetido\n\n";
for(i=1;i<=7;i++)    {
for(j=1;j<=6;j++)    {
if(*(*(x+i)+j)==mayor)    {
g=g+1;
cout<<"["<<i<<"] ["<<j<<"]\n";     }
}
}
cout<<"\nEl mayor valor se repite "<<g<<" veces";
getch();
}

No hay comentarios:

Publicar un comentario