Nota: este tema no es necesario saberlo para OMIJAL aunque seria bueno saberlo

Definición de Puntero

Los punteros son variables que contienen una dirección que hacen referencia a un valor. Una variable común contiene o almacena un valor, mientras que un puntero almacena la dirección que hace referencia a este valor.

Para tener un mejor entendimiento de un puntero tenemos que saber que es la memoria, a la que podemos considerar como una colección de casillas en las cuales se puede guardar información, cada casilla esta enumera o tiene una dirección.
Podríamos imaginarnos a la memoria como una gran biblioteca, la cual consta de varios estantes, supongamos que están divididos en casillas, en donde cada una puede contener a un libro. En este ejemplo un puntero sería uno de los registros que se encuentren en el catálogo de la biblioteca, en donde cada registro contiene información de la ubicación (dirección) del libro dentro de los estantes de la biblioteca. De esta manera es más fácil buscar directamente en el catálogo que estante por estante, su manejo es más cómodo; lo mismo ocurre con los punteros.
Una variable seria directamente la porción de estante o la casilla que contiene al libro.

Declaración de un Puntero

Un puntero puede ser de cualquier tipo y su declaración es de la siguiente forma:

<tipo> * nombrePuntero ;

Esto quiere decir que un puntero tiene que tener un tipo de dato, luego le sigue el operador de indirección (*) y luego un nombre cualquiera, el que le daremos al puntero.

Ejemplos de declaración de punteros son:
int *puntero ; // es un puntero a un tipo de dato entero
char *nombre ; //es un puntero a una cadena de caracteres
float *promedio; //es un puntero a un tipo de dato real

Inicialización

Para inicializar un puntero debemos de hacerlo de acuerdo al tipo de dato al que pertenece el puntero.

Ejemplo 10.1 (Puntero a un arreglo de enteros)

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:

#include <iostream.h>

void main()
{
int arreglo[] = {3, 2, 1};

int *puntero;
puntero = arreglo;
cout<<*puntero;
cout<<*(puntero+0);
cout<<puntero[0];
}

En el anterior ejemplo en la línea 7 cuando se asigna puntero el arreglo, lo que se guarda en realidad en el puntero es la dirección del primer elemento del arreglo, conociendo esta dirección podemos conocer los demás elementos.

Entrando más en detalle las tres siguientes sentencias son iguales:

cout<<*puntero;

cout<<*(puntero+0);

cout<<puntero[0];

Todas las anteriores sentencias muestran el primer elemento del arreglo al que apunta el puntero y es 3.

Para acceder a los demás elementos se pude hacer de la siguiente manera:

cout<<*(puntero+1);

cout<<puntero[1];

Las anteriores sentencias muestran el segundo elemento del puntero, y de esta manera se pueden mostrar todos los elementos siguientes colocando en vez de 1; 2, 3, 4.... o sino utilizando un ciclo.

Existe una forma más de recorrer a los elementos del arreglo:

cout<<puntero[0]

puntero++;

cout<<puntero[0];

En el primer cout se muestra el primer elemento del arreglo, es decir 3, luego se encuentra la instrucción puntero++; la cual no hace incrementar en 1 el valor del puntero sino mas bien hace incrementar en 1 la posición a la que apunta el puntero. Entonces ahora el puntero apunta al segundo elemento.

Entonces el segundo cout mostrará 2.

Ejemplo 10.2 (Puntero a una cadena)

1:
2:
3:
4:
5:
6:
7:

#include <iostream.h>

void main()
{
char *cadena = “Universidad Mayor de San Simón”;
cout<<cadena;
}

Dirección y Valor de un puntero

Como habíamos mencionado que un puntero contiene una dirección que señala a un valor, entonces como podríamos hacer para ver la dirección o para ver solamente el valor. Existen dos operadores que nos permiten realizar estas operaciones, para ver solamente la dirección de un puntero se utiliza el operador &. Para obtener el valor que apunta nuestra variable se debe utilizar el operador *. Por ejemplo:

Ejemplo 10.3

1:
2:
3:
4:
5:
6:
7:
8:

#include <iostream.h>
void main()
{
int aux = 4 ;
int *puntero = &aux ;
cout<<"La direccion de memoria es: "<<&puntero<<endl;
cout<<"El valor es: "<<*puntero;
}

 

Respuesta:
La dirección de memoria es: 0xfff2

El valor es: 4

En el ejemplo anterior en la línea 5 después de declarar un puntero se le asigna una dirección, para ser más específicos la dirección de la variable aux, esto se logra precediendo el operador de dirección & a la variable.
En la línea 6 se muestra primero la dirección del puntero y luego su valor.

Punteros Vs Arreglos

Es posible utilizar punteros para reemplazar a los arreglos normales, veremos como podemos utilizar un puntero para reemplazar un arreglo de enteros y una arreglo de caracteres (cadena).

Arreglos de enteros

Esta parte la explicaremos utilizando un ejemplo escrito primero con arreglos y luego con punteros.


Programa resuelto utilizando un arreglo de enteros

Ejemplo 10.4

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

#include <iostream.h>
void main()
{
int notas[4];
int i;
for(i=0 ; i<4; i++)
  cin>>notas[i];
for(i=0; i<4; i++)
  cout<<[i];
}

Programa resuelto utilizando un puntero a enteros

Ejemplo 10.5

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

#include <iostream.h>
void main()
{
int *notas;
int i;
for(i=0 ; i<4; i++)
  cin>>*(notas+i);
for(i=0; i<4; i++)
  cout<<*(notas+i);
}

Esencialmente la diferencia en los ejemplos 10.4 y 10.5 esta en inicialmente en la línea 4 donde se declara es arreglo con tamaño 4 en el ejemplo 10.4 y en el ejemplo 10.5 en la línea 4 se declara un puntero a un entero y no se especifica tamaño alguno.
Luego la forma de acceder a los datos en el ejemplo 10.4 en la línea 7 es por medio del subíndice y corchetes "notas[i]" en el caso del ejemplo 10.5 en la línea 7, para acceder al valor de una dato se lo hace de la siguiente manera: "*(notas+i)".
Nota.- Es posible reemplazar en el ejemplo 10.5 la manera de acceder a un dato de la forma: "*(notas+i)" a la  "notas[i]".

Arreglos de caracteres (cadenas)

Como habíamos visto en capítulos anteriores una cadena es un arreglo de caracteres, por lo tanto podemos utilizar punteros para representar cadenas. El siguiente ejemplo muestra como definir e inicializar un puntero a una cadena.

Ejemplo 10.6

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

#include <iostream.h>
void main()
{
    char *nombre;
    cout<<"Ingresa Tu Nombre Completo\n";
    cin>>nombre;
    cout<<"Tu nombre es:"<<nombre;
}

Porque es mejor usar un puntero a una cadena que solamente una cadena pues, porque con el puntero no nos limita el tamaño que vaya a tomar la cadena.

Ejemplo 10.7

En este ejemplo se hará lo mismo que en el ejemplo 9.6 utilizando un puntero a una cadena. Se escribirá una función que permita conocer la longitud de una cadena.

1:
2:
3:
4:
5:
6:
7:
int longitud(char *cadena)
{
   char *ptr = cadena;
            while( ptr[0] != '\0' ) //mientras no sea fin de cadena
                        ptr++;   
   return (int)(ptr-cadena);
}

En la función anterior en la línea 2 se declara un puntero adicional ptr el cuál nos servirá para realizar el cálculo. En la línea tres encontramos la siguiente expresión dentro de la estructura while: ptr[0]!=’\0’ Esta expresión quiere decir que cuando la posición inicial del puntero sea fin de cadena ‘\0’ entonces el ciclo termina. En la línea 4 se encuentra la instrucción ptr++; Lo que hace es que el puntero recorra un índice a la derecha, es decir apuntará al siguiente carácter de la cadena, de esta manera el ciclo recorrerá todas las posiciones de la cadena hasta encontrar el fin de cadena.

Bien ahora la función tiene que devolver la longitud de la cadena, en la líne 5 podemos ver lo siguiente: (ptr-cadena) esto quiere decir que se restará la última posición de la cadena menos la primera, dándonos la longitud de la cadena. Además en la línea 5 se muestra (int) que es un casting, es decir que convierte en entero a las instrucciones que precede en este caso a la instrucción (ptr-cadena).

¿Por qué NO trabajar con punteros?

El error más común es que cuando trabajamos con punteros vamos apuntando a diferentes valores de memoria, y cuando los dejamos de utilizar un error muy común es de no liberar el espacio de memoria a cual están apuntando lo que nos ocasionará una sobrecarga en la memoria cuando nuestros programas sean grandes.

También por medio de los punteros es que se pueden tener acceso a valores privados, y podemos modificar valores haciendo que nuestros programas caigan. Este hecho es muy común en los crackers de programas, porque se valen de los punteros para cambiar valores en los programas y utilizarlos según su conveniencia.

Una posibilidad que tenemos para reemplazar a los punteros es utilizar referencias.

¿Por qué trabajar con punteros?

Más de una vez hemos puesto en duda el uso de punteros, pues ya que trabajamos con direcciones de memoria nos pueden provocar errores más de una vez si no tenemos cuidado, además si tengo una variable y puedo obtener directamente su valor un puntero sería redundante. Bueno, de lo que tenemos que estar seguros es donde utilizar punteros, y estos son usados con mayor frecuencia en:


- Asignación de Memoria Dinámica
- Paso de variables por referencia en funciones
- Para acceder a los atributos de una clase y a sus funciones

Asignación Dinámica de Memoria

Asignar dinámicamente memoria significa que personalicemos el espacio que ocuparán nuestras variables en memoria. Cuando reservamos memoria, esta permanece allí hasta que nosotros la liberemos. Reservar o asignar memoria es una buena técnica para no desperdiciar memoria inútilmente, pues sólo reservaremos el espacio necesario, este detalle será muy significante en aplicaciones o programas grandes.

Para reservar memoria utilizamos el operador new seguido del tipo de dato del cual queremos reservar memoria.
Por ejemplo:

int *apuntador = new int ;

La anterior instrucción reservará para el puntero  apuntador 2 bytes que es el tamaño de un tipo de dato int

char *nombre;
nombre = new char[10];

La anterior instrucción reservar para el puntero a una cadena nombre 10 caracteres

Como habiamos mencionado cuando reservamos memoria permenece allí hasta que la liberemos, esta operación la realizamos con el operador delete.

delete apuntador;
delete nombre;

Ejemplo 10.8

Ingresar un nombre sin desperdicio de memoria

#include <iostream.h>
#include <string.h>

void main()
{
    char *nombre;
    char aux[30];
    cout<<"Ingrese Su Nombre\n";
    cin>>aux;
    nombre = new char[strlen(aux)+1];
    strcpy(nombre, aux);
    cout<<nombre<<endl;
    //Le doy otros usos a mi puntero nombre
    delete nombre; //finalmente libero el espacio de memoria
}