Los punteros en C++ (o apuntadores) son quizá uno de los temas que más confusión causan al momento de
aprender a programar en C++, sin embargo verás que no es para tanto y que todo depende de dos elementos:
el signo & (ampersand) y el * (asterisco) que los explicaré en breve. Durante este artículo verás entonces
que no es para nada difícil hacer y usar punteros y que además son de gran ayuda al momento de necesitar
valores y estructuras dinámicas, por ejemplo, para crear un array dinámico, con dinámico me refiero a que
su tamaño puede ser establecido en tiempo de ejecución y lo mismo se puede hacer con las matrices (que en
realidad son un array multidimensional).
Muy bien para comenzar veamos un pequeño ejemplo y luego su correspondiente explicación, una excelente
forma de aprender. No te preocupes de entender todo con este primer ejemplo, pues durante el resto del
artículo explicaré cada componente, su sintaxis y el final, cómo aprovechar dichas funcionalidades para
nuestro beneficio junto con algún ejercicio.
Ejemplo de punteros
puntero = NULL;
Muy bien, ya hemos creado y usado nuestro primer puntero ¿Notaste el uso del asterisco y del ampersand?
espero que sí y además de eso hay otros detalles que debemos considerar, veamos:
El tipo de dato del apuntador debe coincidir con el de la variable cuya posición en memoria apuntan. En el
ejemplo vemos que tanto variable como apuntador son enteros.
Siempre que queremos usar el apuntador debemos anteponer el asterisco (*) para indicar que usaremos el
valor en la posición de memoria apuntada.
Un puntero o apuntador puede ser de cualquier tipo de dato, inclusive los podemos usar con tipos complejos.
Ya que sabemos algunos trucos y detalles sobre los apuntadores en C++, vamos a definir formalmente la
utilidad del operador & y del asterisco.
El ampersand es un operador de C++ y es comúnmente utilizado para los punteros. Este operador nos
permite obtener la dirección de memoria de una variable cualquiera y es justo esto (la dirección en memoria)
lo que utilizan los punteros para referenciar valores.
El asterisco es, por decirlo de alguna forma, el operador por excelencia de los punteros. SU utilidad radica
en que si el valor de dicho apuntador corresponde a una dirección de memoria, el asterisco nos permite
resolverla y acceder al valor almacenado allí. Viéndolo desde otro enfoque, un apuntador es únicamente una
dirección de memoria (un número) y el asterisco es el que hace la magia de obtener el valor referenciado por
dicha dirección.
Ejemplo de apuntadores
Ahora que hemos visto los ejemplos y tenemos claro el uso del ampersand y el asterisco podemos entonces
realizar algunos ejercicios interesantes.
Usualmente al enviar un parámetro a una función todo lo que se haga con dicho parámetro allí adentro NO
tiene efecto por fuera. Por ejemplo si a una función la se le envía una variable cuyo valor es diez y al interior
de la función le sumamos un cinco, después de la ejecución de la función el valor de la variable seguirá
siendo diez en vez de quince. Lo que pasó al interior de la función se quedó allí. Para solucionar esto, si
queremos que el valor cambie definitivamente, usamos punteros para pasar no el valor del parámetro sino
una referencia a éste (paso por referencia). Veamos:
#include "iostream"
#include "stdio.h"
return valor;
return *valor;
}
int main()
cout << "Antes de funcion " << numero << "\n"; //10
cout << "Despues de funcion " << numero << "\n"; //10
cout << "Antes de funcionPunteros " << numero << "\n"; //10
cout << "Despues de funcionPunteros " << numero << "\n"; //20 (10+10)
system("pause");
return 0;
Como podrás comprobar si ejecutas el código del ejercicio al llamar a "funcion" sólo enviamos el valor y
por ende éste no es modificado por fuera de la función, con "funcionPunteros" estamos manipulando la
posición en memoria del parámetro recibido (por eso usamos el *) y por ende al ejecutarla el valor de la
variable se modifica. De ese modo ya hicimos el primer ejercicio con punteros en C++ y ya comprendemos
el paso por referencia.
Array dinámico
Como mencioné al comienzo del artículo, por medio de apuntadores podemos crear arreglos o vectores
dinámicos, es decir, un array al cual se le define su tamaño o capacidad durante la ejecución del código y no
antes, lo cual nos permite definirle el tamaño deseado por el usuario.
Para este ejercicio retomaré el ejemplo del artículo de arreglos o vectores: Queremos crear un programa con
el cual podamos guardar los títulos y los autores de diferentes libros sin perder ninguno de ellos. El usuario
es el encargado de suministrar la información de cada libro. En esta ocasión ya sabemos usar punteros, así
que será también el usuario quien nos diga cuántos libros desea ingresar, ya no necesitamos suponer que
sólo ingresará 5 libros. Veamos:
#include "iostream"
#include "stdio.h"
#include "string"
int main()
string entrada;
titulos = new string[tamanio]; //Declaramos un arreglo del tamaño ingresado para los titulos
autores = new string[tamanio]; //Declaramos un arreglo del tamaño ingresado para los autores
cout << "Por favor ingrese la siguiente información de los Libros: \n";
getline(cin, titulos[i]);
getline(cin, autores[i]);
delete [] titulos;
delete [] autores;
titulos = NULL;
autores = NULL;
system("pause");
return 0;
Así entonces tuvimos dos punteros, uno para todos los autores y otro para todos los títulos. Haciendo uso de
ellos pudimos definir la cantidad de libros a ingresar por medio del usuario, es decir lo hicimos de manera
dinámica, en tiempo de ejecución.
Matrices dinámicas
Así como lo hicimos con los array, también podemos tener matrices dinámicas y definir su tamaño, número
de filas o número de columnas (o las dos) según sea necesario.
Para esto tomaré el mismo ejemplo de los libros, pero usando una matriz, en vez de dos vectores, tal y como
se solucionó en la sección de matrices veamos:
#include "iostream"
#include "stdio.h"
#include "string"
int main()
string entrada;
cout << "Por favor ingrese la siguiente información de los Libros: \n";
//Notar que cols pudo haber sido ingresada por el usuario también
getline(cin,titulo);
getline(cin,autor);
libros[i][0] = titulo;
libros[i][1] = autor;
delete [] libros;
system("pause");
return 0;
}
Este ejercicio es el perfecto para aclarar dudas o darse cuenta si realmente comprendes el concepto de
apuntadores y su aplicación para arrays dinámicos. Debido a que la cantidad de columnas es fija, no se lo
pedimos al usuario, simplemente lo declaramos con valor dos. Luego tenemos el puntero, pero no es un
puntero cualquiera, al ser una matriz, será un puntero que tendrá otros punteros adentro, por eso se usa doble
asterisco, luego se obtiene el tamaño del usuario (cantidad de libros) y al momento de inicializar la fila
estamos indicando que es un arreglo de punteros, por eso se usa el * en la línea 23. Luego al interior del
ciclo, cuando estamos llenando la matriz, debemos indicar que cada fila estará compuesta por un array de
punteros de tamaño dos (dos columnas) y así construimos nuestra matriz dinámica.
Debes notar también que la liberación de la memoria es un poco más trabajosa, pues debemos ir fila por fila
liberando la memoria de las columnas creadas y luego liberar la fila completa. Ahí podrás notar la diferencia
en eficiencia y uso de memoria al usar arreglos o usar matrices.
Muy bien, es todo en esta función. Espero haber sido muy claro y que los ejemplos y ejercicios te hayan
servido de mucho. En este momento ya debes saber qué es y cómo hacer un puntero en C++, para qué sirve
el ampersand y el asterisco cuando estamos hablando de apuntadores en C++ y cómo crear arrays dinámicos
y matrices usándolos. No te olvides de dejar un comentario.
CONCEPTOS DE PUNTEROS EN C
Un puntero es una variable que contiene como valor una dirección de memoria, que puede ser la dirección
de memoria de otra variable, por tanto, podemos decir que el puntero apunta a otra variable. La variable
apuntada puede ser de cualquier tipo de dato básico (char, int, float, double), derivado (puntero) o
estructurado (tabla, estructura, etc..).
Los punteros tiene una gran utilidad, pues permiten una programación eficaz a nivel de máquina cuando se
maneja la memoria del ordenador.
· En el caso de paso de variables a una función por dirección o referencia, es necesario emplear
parámetros formales como punteros.
Los punteros deben usarse con precaución, ya que pueden provocar fallos en el programa difíciles de
localizar, las causas más corrientes de error en el uso de punteros es:
· Asignación incorrecta de direcciones, por lo que se puede llegar a escribir en zonas de la memoria que
contengan código o datos del programa
· Operaciones incorrectas con los punteros, cuando no se tiene en cuenta el tipo de valor de las variables
apuntadas.
Tipo_básico_de_dato *Nombre_variable_puntero;
Ejemplo:
Int *apunt;
Ejemplo:
Int cant;
Cant = 15;
Para que una variable puntero apunte a otra variable es necesario asignar la dirección de dicha variable al
puntero. El tipo de dato del puntero debe coincidir con el tipo de dato de la variable apuntada, excepto en el
caso de un puntero genérico (con el tipo de dato void). La asignación de la dirección se puede realizar de dos
formas:
Pnum = &nume;
Para visualizar la dirección contenida en un puntero, se puede utilizar el formato %p con la función printf()
Después de tener asignada una dirección a un puntero, durante la ejecución del programa se le puede volver
a asignar la dirección de otra variable, apuntando por tanto a esa variable y abandonando el apuntamiento
anterior.
pnum =&contador;
INDIRECCIÓN
Se entiende por indirección la forma de referenciar el valor de una variable a través de un puntero que
apunta a dicha variable. Para realizar la indirección se utiliza el operador monario * que aplicado al nombre
del puntero correspondiente indica el valor de la variable cuya dirección está contenida en dicho puntero.
*Nombre_variable_puntero;
int a= 5; b;
ASIGNACIÓN DE PUNTEROS
Int a = 5, b, c;
B = *p1;
c = *p2;
ARITMÉTICA DE PUNTEROS
Punt =#
Pnum =punt + 2
/ * Si se se supone que la dirección contenida en punt es 4274, la dirección que contiene ‘pnum’ es 4274 + 2
* 4 = 4282 * /
Char 1 byte
Int 2 bytes
Double 8 bytes
Float *apunt;
Apunt++;
int tabula[10], d;
d = ptab1 – ptab2;
COMPARACIÓN DE PUNTEROS
Se utiliza normalmente para la comparación de punteros para conocer las posiciones relativas en memoria
que ocupan las variables apuntadas por punteros.
Si efectuamos la comparación p1 > p2 podemos saber qué variable ocupa mayor posición de memoria. Hay
que evitar la confusión de comparar valores.
En el lenguaje C los punteros y los arrays están estrechamente relacionados. Para facilitar su estudio vamos
a diferenciar los casos de arrays unidimensionales (vectores) cadenas de caracteres y arrays
multidimensionales.
El nombre del array (sin índice) es un puntero que contiene la dirección de memoria del primer elemento del
array. El nombre del array es una constante de tipo puntero y por tanto el compilador no permitirá que se
pueda cambiar en las instrucciones del programa la dirección contenida en el nombre del array.
Int tabula[20];
Se pueden utilizar variables puntero que contengan la dirección de un array, lo que posibilita la realización
de todas aquellas operaciones permitidas con punteros.
Ejemplo:
Ptab++
ptab = ptab + 4;
La referencia al valor de un elemento del array se puede hacer por medio de la indirección de un puntero.
Una vez definido un puntero, se puede indexar como se hace con el nombre de un array.
Int *ptab = tabula
Esta indexación de punteros sólo es válida cuando se utiliza para apuntar a arrays, siendo errónea en los
demás casos.
Int a, b;
También es posible utilizar el nombre de un array como un puntero para referenciar a los elementos del
array.
Int vector[40];
*(vector + 3) es equivalente [3]
La diferencia entre el uso del nombre del array como puntero y el uso de un puntero definido
específicamente para contener la dirección del array es que el primero es una constante y siempre debe
apuntar a la misma dirección de memoria (posición del primer elemento del array), mientras que el segundo
es una variable que puede cambiar su contenido o sea que puede contener la direcciónde cualquier elemento
del array, incluso se puede volver a utilizar la variable puntero para apuntar a otro array distinto, siempre
que sus elementos sean del mismo tipo que el definido para el puntero.
Punt = cant;
El uso de punteros, a pesar de ser más difícil presenta las siguientes ventajas:
· Mayor rapidez en la ejecucióndel programa
INDIRECCIÓN MÚLTIPLE
La indirección múltiple consiste en que un puntero contiene la dirección de otro puntero que a su vez apunta
a una variable. Su formato de definición es el siguiente:
Tipo_dato **Nombre_puntero;
Int cant;
**ppcan=57;
printf(“%d”,cant); Visualiza el 57
En realidad se está utilizando un formato de un array bidimensional de punteros, tal como se describia en el
apartado anterior para arrays de punteros a cadenas.
Char nom[35];
Gets(*ppnom);
Printf(“%s”,nom);
La indirección múltiple se puede extender a más niveles, sin embargo, no es conveniente pasar del nivel **
de puntero a puntero, pues se presentan dificultadoes de seguimiento en el programa.
Esta manera de utilizar la memoria la podemos repetir tantas veces como sea necesario durante la ejecución
de un programa, teniendo siempre la precaución de ir liberando aquellas porciones o parcelas de memoria
reservadas anteriormente y que ya no vamos a volver a utilizar.
Las funciones de la libreria que el ANSI C define para las operaciones de asignación dinámica de memoria
son las descritas a continuación:
Se encuentra en la libreria stdlib.h, devuelve un puntero al primer byte de la parcela o porción de memoria
reservada o un puntero NULL, en el caso de no haberse podido reservar el bloque de memoria solicitado.
Reserva un bloque de memoria para almacenar ‘num’ elementos de ‘tam’ bytes cada uno de ellos.
· num: indica el número de elementos con la misma estructura que ocuparán la parcela de memoria
reservada.
· Tam:indica el tamaño en bytes de cada uno de los elementos que van a ocupar la parcela de memoria
reservada.
La cantidad de memoria reservada, viene determinada por el resultado que se obtiene al multiplicar el
número de elementos a almacenar en el bloque por el tamaño en bytes de cada uno de esos elementos, es
decir num*tam
Ejemplo:
#include <stdio.h>
#include <stdlib.h>
int *pt;
if (!pt)
{
...
return pt;
Se encuentra en la libreria stdlib.h, devuelve un puntero al primer byte de la parcela o porción de memoria
reservada o un puntero NULL, en caso de no haberse podido reservar el bloque de memoria solicitado.
· tam: Indica el tamaño en bytes del bloque de memoria que se desea reservar. Es muy importante
comprobar que el puntero devuelto por malloc() no es un puntero nulo antes de hacer uso de él.
Void free(void *puntero);
Se encuentra en la libreria stdlib.h, no devuelve ningún valor. Libera la parcela de memoria apuntada por el
puntero y que previamente habia sido asignado mediante malloc() o calloc(), dando la posibilidad a que
dicho bloque de memoria se pueda volver a asignar posteriormente.
*Puntero: variable puntero que debe conteener la dirección de memoria del primer byte del bloque de
memoria que queremos liberar.
#include <stdio.h>
#include <stdlib.h>
main()
char *nombres[50];
int j;
exit (0);
gets(nombres[j]);
}
/* Se libera la memoria ocupada */
free ( nombres[j]);
éxito.
Cambia el tamaño del bloque de memoria apuntada por ‘ptr’ al nuevo tamaño indicado por ‘nuevotam’.
Los argumentos necesarios para la utilización de la función realloc() son:
· Nuevotam: Valor en bytes que indica el nuevo tamaño del bloque de memoria apuntado por ‘ptr’ y que
puede ser mayor o menor que el tamaño original.
#include <stdio.h>
#include <stdlib.h>
main()
char *pt;
int i;
pt = (char *) malloc (5);
if (!pt)
exit (1);
pt = (char *) saludo;
for (j = 0; j < 5; j + +)
printf(“%c”,*(pt+j));
exit(1);
strcat(pt, nombre);
printf(“\%s”, pt);
free(pt);