Captulo 1. Apuntadores
Observe que el operador de indireccin utiliza el mismo smbolo que el operador de multiplicacin. En
este caso el asterisco le indica al sistema que se define una variable apuntador. Ejemplos:
int *x; a es un apuntador de tipo entero.
char *y; c es un apuntador de tipo carcter.
double *p, *q; p y q son apuntadores de tipo real doble precisin.
* toma su operando como una direccin de memoria y retorna la informacin almacenada en ese
lugar.
100
apunt ---->
Un apuntador es una variable que solo puede contener un valor a la vez, por lo que solo puede apuntar
a un objeto al mismo tiempo. Por otro lado, una variable cualquiera puede ser apuntada (referenciada)
por varios apuntadores, ya que su direccin de memoria puede ser almacenada en distintas variables a
la vez. Al declarar un apuntador, se est especificando el tipo de variable al que va a apuntar. Por
ejemplo, no podr declararse un apuntador a objetos de tipo int y despus intentar utilizarlo para
apuntar a objetos de tipo float. Cuando se desee manejar un apuntador a cualquier tipo de objeto, se
puede declarar de tipo void, como en: void *multiusos;
Progra3.cpp
#include <iostream.h>
#include <conio.h>
#define NL
cout << "\n";
void main()
{
int varent="0" ;
float varflot="0.0" ;
void *apmulti="&varent; // apmulti APUNTA A varent
*(int *)apmulti="2" ; // ASIGNA 2 AL OBJETO NL; // APUNTADO POR apmulti
cout << varent ;
4
(int *)apmulti est forzando a que apmulti apunte a objetos de tipo int.
Esto mismo puede hacerse por medio de apuntadores, como se muestra en siguiente ejemplo.
Progra4.cpp
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
void main()
{
char *nombre = "COMERCIO" ;
clrscr();
gotoxy(30,12);
cout<< "!! HOLA, " ; puts(nombre); gotoxy(43,12); cout << " !!";
getch();
}
Progra5.cpp
#include <iostream.h>
#include <conio.h>
#include <string.h>
void main()
{
char *nombres[ ] = { "HUGO", "PACO", "LUIS" } ;
char invitado[11];
int bandera;
clrscr();
gotoxy(30,10);
cout << "CUAL ES SU NOMBRE ? " ; gotoxy(50,10); cin>> invitado ;
gotoxy(30,12);
for( int x = 0 ; x <3 ; x++ ) if(strcmp(invitado, nombres[x])="=" 0)
bandera="0;" if(bandera="=" 0) cout << "!! PASE, ESTIMADO "
<< invitado << " !!"; else cout << "!! FUERA DE AQUI, " << invitado << "
!!"; getch();
}
Al pasar un parmetro correspondiente a un arreglo, se pasa la direccin del primer elemento, por lo
que la funcin invocada puede modificar cualquier elemento del arreglo. El siguiente programa maneja
una funcin llamada nputs(), la cual recibe como parmetro un arreglo de caracteres.
Progra6.cpp
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#include <string.h>
void nputs(char *);
void main()
{
char cadena[81];
clrscr();
gotoxy(10,10);
cout << "ESCRIBA UNA CADENA: "; gets(cadena); gotoxy(10,12); nputs(cadena); getch(); } void
nputs(char cad[ ]) { int x="0;" while(cad[x]) { cout << cad[x] ; x++; } }
6
Progra7.cpp
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#include <string.h>
void nputs(char *);
void main()
{
char cadena[81];
clrscr();
gotoxy(10,10);
cout << "ESCRIBA UNA CADENA: ";
gets(cadena);
gotoxy(10,12);
nputs(cadena);
getch();
}
void nputs(char *cad)
{
while(*cad) cout << *cad++ ;
}
Progra8.cpp
#include <iostream.h>
#include <string.h>
#include <conio.h>
int cmpcad(char*, char*);
void compara(char*, char*, int(*)(char*, char*));
void main()
{
char cadx[80], cady[80];
clrscr();
gotoxy(10,5);
cout << "ESCRIBA UNA CADENA : " ; cin>> cadx;
gotoxy(10,7);
cout << "ESCRIBA OTRA CADENA : " ; cin>> cady;
gotoxy(10,9);
compara(cadx, cady, cmpcad);
gotoxy(1,24);
7
}
void compara(char *cad1, char *cad2,
int (*cmpcad)(char*, char*))
{
if(!(*cmpcad)(cad1,cad2))
cout << "LAS CADENAS SON IGUALES";
else cout << "LAS CADENAS SON DISTINTAS";
}
int cmpcad(char *x, char *y)
{
return(strcmp(x,y));
}
Expliquemos la expresin que puede ser un tanto desconocida del listado anterior, la expresin:
int(*cmpcad)(char*, char*) establece que cmpcad es un apuntador a una funcin, la cual devuelve un
valor de tipo entero.
Como se vio al principio de la unidad, un apuntador tambin es una variable. Su direccin puede ser
almacenada por otra variable apuntador, por lo que puede hablarse de un apuntador a un apuntador.
Esto puede extrapolarse para dos o ms variables, como se observa en el ejemplo siguiente de
apuntadores a apuntadores.
Progra9.cpp
#include <iostream.h>
#include <conio.h>
void main()
{
int x, *a, **b, ***c ; // 1
clrscr();
a = &x ; // 2
*a = 100 ; // 3
b = &a ; // 4
**b += *a ; // 5
c = &b ; // 6
***c += **b + *a ; // 7
cout << " *a=" << *a << " \n" ;
cout << " **b=" << **b << " \n" ;
cout << "***c=" << ***c << " \n" ;
getch();
}
Se declaran:
x como una variable de tipo entero. a como un apuntador a objetos de tipo entero. b como un apuntador
a un apuntador, el cual a su vez apuntar a objetos de tipo entero. Se dice que b es "el apuntador del
apuntador". c como un apuntador a un apuntador que apunta a otro apuntador, el cual a su vez apunta
a objetos de tipo entero. Se dice que c es "el apuntador del apuntador del apuntador".
La pila lucira as:
a = &x ; // 2
*a = 100 ; // 3
Al objeto apuntado por a se le asigna el valor 100. La pila lucira as:
b = &a ; // 4
Al apuntador b se le asigna la direccin del apuntador a. La pila lucira as:
**b += *a ; // 5
Al objeto apuntado por el apuntador apuntado por b se le suma el valor del objeto apuntado por a. La
pila lucira as:
9
c = &b ; // 6
Al apuntador c se le asigna la direccin del apuntador b. La pila lucira as:
***c += **b + *a ; // 7
Se asigna al objeto apuntado por el apuntador apuntado por el apuntador c, el valor del objeto apuntado
por el apuntador apuntado por el apuntador b ms el valor del objeto apuntado por el apuntador a. La
pila lucira as:
Por ejemplo :
En realidad, el lenguaje manejar al arreglo a travs de un apuntador llamado calif, el cual tiene
almacenado el valor 65494, que a su vez corresponde a la direccin de inicio del elemento calif[0].
Calif 65494
En el siguiente listado se presenta el manejo del arreglo calif[ ], a travs de la notacin de arreglos, y en
el listado subsiguiente llamado Progra11.cpp el manejo con la notacin de apuntadores.
Progra10.cpp
#include <iostream.h>
void main()
{
int calif[ ] = { 100,90,95,80,90};
Debido a que la ejecucin de los programas de ambos listados producen resultados iguales, se deduce
que:
calif[i] == *(calif+i)
Para entender esto que a simple vista no es obvio, revisaremos algunos conceptos:
1. El nombre del arreglo corresponde al de un apuntador que apunta al primer elemento del arreglo, por
lo que:
calif apunta a calif[0]
Visto grficamente:
2. Para hacer referencia a un elemento especfico del arreglo, se toma como base la direccin del
primer elemento y, con el subndice del elemento especfico, se calcula su direccin. Por ejemplo, para
referirse al segundo elemento del
el arreglo puede procederse as
as:
*(calif+1) // Notacin de apuntadores, donde la expresin calif+1sirve para calcular la direccin del
elemento que est una posicin ms all del elemento apuntado por calif.
Para referirse a calif[2] ( tercer elemento ), puede escribirse:
*(calif+2)
Lo que significa: "El objeto que se encuentra dos posiciones despus del objeto apuntado por calif". En
este caso, una posicin es el espacio requerido por cada uno de los elementos, de tal manera que, si
calif apunta a la direccin 65494, entonces calif+2 es la expresin que calcula la direccin 65498. La
figura muestra los elementos del arreglo calif[ ] con sus nombres en notacin de arreglos y en notacin
de apuntadores.
por lo que:
&calif[i] == calif+i
Esto es que, la direccin del i-simo elemento de un arreglo se calcula sumndole el subndice a la
direccin del primer elemento.
Como regla, podemos establecer que el subndice se refiere a la posicin del elemento en el arreglo. Es
por esto que al calcular la direccin por medio del subndice, ste debe multiplicarse por el nmero de
bytes que representan el tamao de cada elemento (dado por el tipo utilizado en la declaracin del
arreglo).
En el siguiente cdigo se realiza un programa que emplea los operadores (& y *).
Progra12.cpp
#include <iostream.h>
#include <conio.h>
int main()
{
clrscr();
int a; //a es un puntero
13
Para iniciar este captulo es importante que le demos un vistazo a la memoria de la computadora. Si
usted ya sabe cmo funciona la memoria de la computadora, se puede saltar esta seccin. Sin
embargo, si no est seguro, le sugiero que la lea, le ayudar a comprender mejor ciertos aspectos de la
programacin.
La computadora usa memoria de acceso aleatorio (RAM) para guardar informacin mientras est en
funcionamiento. La RAM se encuentra en circuitos integrados o chips en el interior de la computadora.
La RAM es voltil, lo que significa que es borrada y reemplazada con nueva informacin tan pronto
como se necesita. La volatilidad tambin significa que la RAM recuerda solamente mientras la
computadora est encendida, y pierde su informacin cuando se apaga la computadora.
Cada computadora tiene una determinada cantidad de RAM instalada. La cantidad de RAM en un
sistema se especifica por lo general en Megabytes (Mb) por ejemplo 256 Mb, 512 Mb, en ese orden de
ideas se dice un byte es la unidad de medida fundamental de la memoria de una computadora, de los
cuales se obtiene los Kilobytes, Megabytes, Gigabytes, siendo estos los ms usados. Un kilobytes de
memoria equivale a 1,024 bytes.
Para darse una idea de que tantos bytes se necesitan para guardar determinados tipos de datos lo
invito a que revise la siguiente tabla de espacios requeridos para guardar datos.
La RAM en la computadora est organizada en forma secuencial, un byte tras otro. Cada byte de
memoria tiene una direccin nica mediante la cual es identificado, una direccin que tambin lo
14
distingue de todos los otros bytes de la memoria. Las direcciones son asignadas a la memoria en orden,
comenzando en 0 y aumentando hasta llegar al lmite del sistema.
Para ampliar un poco ms la conceptualizacin a cerca de los tipos de datos se define todo el posible
rango de valores que una variable puede tomar al momento de ser ejecutada en el programa al igual
que en toda la vida til del propio programa.
Alguna vez nos hemos hecho la siguiente pregunta Para qu se usa la memoria RAM de la
computadora? Tiene varios usos, pero solamente uno, el almacenamiento de datos, le interesa al
programador. Los datos significan la informacin con la cual trabaja un programa. Ya sea que el
programa est trabajando con una lista de direcciones, monitoreando la bolsa de valores, manejando un
presupuesto o cualquier otra cosa, la informacin (nombres, precios de acciones, gastos) es guardada
en la RAM de la computadora mientras el programa est ejecutando.
15
Hasta el momento, la mayora de los programas los hemos realizado definiendo variables, sin
preocuparnos de que se realiza internamente en el computador, muchas veces en forma indiscriminada,
es decir sin una verdadera depuracin, pero existen ocasiones en que no sabemos cuanta memora
necesitaremos para ejecucin de determinado programa, por ejemplo si deseamos realizar un
procesador de textos, no sabemos cul va hacer la longitud del texto.
Por eso a veces es necesario poder reservar memoria segn se va necesitando. Adems de esta forma
nuestros programas aprovecharn mejor la memoria del computador en el que se ejecuten, usando
slo los recursos necesarios.
Realmente la utilidad de asignacin dinmica de memoria ser aplicada en gran medida en los
captulos relacionados con las estructuras lineales.
Dependiendo el uso que se le d a las variables por parte del programador, en una rutina o tarea
especfica se pueden identificar dos tipos de variables ellas son variables dinmicas y variables
estticas.
Las variables estticas como recordamos en los inicios de los fundamentos de programacin, son
aquellas que el programador les asigna memoria antes de la ejecucin del programa o de una funcin,
las variables estticas se llaman mediante el nombre de la misma, que ha sido declarado por el
programador.
Progra13.cpp
#include <stdio.h>
#include <iostream.h>
#include <stdlib.h>
#include <conio.h>
void main()
{
clrscr();
priNumero = 136;
segNumero = 369;
suma = priNumero + segNumero;
16
En el cdigo del programa se hace uso de tres variables de tipo entero que son variables estticas ellas
son: priNumero que se le asigna el valor 136, mientras que a la variable segNumero se le asigna el
valor 369, de igual manera a la variable suma calcula el resultado de sumar el valor de las dos
variables.
En el listado del progra14.cpp se declaran tres variables apuntador de tipo entero ellas son *a, **b y **c
el apuntador a almacena la direccin de la variable x, mientras que **b precedida de dos asteriscos
indica que es una variable que apunta a un apuntador y ***c es un apuntador a apuntador a apuntador.
17
En el lenguaje C existen entre otras las funciones Malloc() y Free() para la asignacin y liberacin de
memoria dinmicamente respectivamente.
Cuando se ejecuta un programa, el sistema operativo reserva una zona de memoria para el cdigo o
instrucciones del programa y otra para las variables que se usan durante la ejecucin. A menudo estas
zonas son la misma zona, es lo que se llama memoria local. Tambin hay otras zonas de memoria,
como la pila, que se usa, entre otras cosas, para intercambiar datos entre funciones. El resto, la
memoria que no se usa por ningn programa es lo que se conoce como "heap" o montn.
Cuando nuestro programa use memoria dinmica, normalmente usar memoria del montn, y no se
llama as porque sea de peor calidad, sino porque suele haber realmente un montn de memoria de
este tipo.
Ejemplo utilizar el operador sizeof en una funcin con el propsito de determinar el tamao en bytes de
un parmetro.
En el siguiente listado se evidencia la aplicacin y uso del operador sizeof en cada variable el cual
devuelve el nmero de bytes dependiendo del tipo de variable.
Progra15.cpp
#include <iostream.h>
#include <conio.h>
void main()
{
char c;
short s;
int i;
long l;
float f;
double d;
long double ld;
int arreglo[20], * pt = arreglo;
clrscr();
gotoxy(20,2);
cout<<"valores utilizando sizeof para cada una de la varibles \n\n";
cout<<" variable c = " <<sizeof c;
cout<<"\t tipo char = " <<sizeof (char);
18
El operador New: Realiza una labor parecida a la de la funcin malloc(), asignando un bloque de
memoria segn sea requerido.
El operador Delete: Libera un bloque de memoria asignada por New en tiempo de ejecucin, de
manera semejante a como lo hace la funcin free().
La sintaxis para el uso del operador new es:
Este operador hace una peticin al sistema operativo para que se le asigne un espacio de memoria, con
tamao de acuerdo al tipo de datos (recordemos la funcin sizeof), si este espacio est disponible, la
operacin regresa la direccin real que se otorga, en caso de no haber espacio regresa el valor de
NULL (0),
delete apuntador;
La ejecucin de este operador provoca que se libere espacio, dejando como valor indefinido, es decir el
sistema operativo lo considera como memoria disponible.
Hay una regla de oro cuando se usa memoria dinmica, toda la memoria que se reserve durante el
programa hay que liberarla antes de salir del programa. No seguir esta regla es una actitud muy
irresponsable, y en la mayor parte de los casos tiene consecuencias desastrosas. No os fiis de lo que
diga el compilador, de que estas variables se liberan solas al terminar el programa, no siempre es
verdad.
19
Veamos un ejemplo de utilizacin de los operadores de c++ utilizados para asignar y liberar memoria
dinmicamente.
En el listado se declaran las variables index de tipo entero y los apuntadores point1 y point2 ambos de
tipo entero.
Progra16.cpp
# include <iostream.h>
main()
{
int index, *point1, *point2;
point1 = &index;
*point1 = 77;
point2 = new int;
*point2 = 173;
cout <<"Los valores son " << index <<" " << *point1 << " "<< *point2 <<'\n';
point1 = new int;
point2 = point1;
*point1 = 999;
cout <<"Los valores son " << index <<" " << *point1 << " "<< *point2 <<'\n';
delete point1;
float *float_point1, *float_point2 = new float;
float_point1 = new float;
*float_point2 = 3.14159;
*float_point1 = 2.4 * (*float_point2);
delete float_point2;
delete float_point1;
char *c_point;
c_point = new char;
delete c_point;
c_point = new char [sizeof(int) + 133];
delete c_point;
}
point2 ilustra el uso del operador new. Este operador requiere un modificador que debe ser un
tipo. La parte new int significa que se crea un nuevo entero en la memoria, y devuelve la
localizacin del entero creado. Esta localizacin es asignada a point2. La siguiente lnea asigna
173 al entero al que apunta point2. Es importante distinguir entre point2, la localizacin del
20
entero, y *point2, el entero. El puntero point2 apunta ahora a una variable entera que se ha
reservado dinmicamente, y que puede utilizarse de igual forma que se haca en C. Como
ejemplo, se imprime el valor al que apunta.
A continuacin, se reserva memoria para una nueva variable, y point2 se refiere a la misma
variable reservada dinmicamente a la que apunta point1. En este caso, la referencia a la
variable a la que point2 apuntaba previamente se ha perdido, y nunca podr ser utilizada o su
memoria liberada. Slo cuando se vuelva al sistema operativo se liberar la memoria que
ocupaba. Por tanto, no debe utilizarse.
Ya que el puntero point1 en s no ha cambiado, apunta realmente al dato original. Este dato
podra referenciarse otra vez utilizando point1, pero no es una buena prctica de programacin,
ya que no hay garanta de lo que el sistema pueda hacer con el puntero o el dato. La localizacin
del dato queda libre para ser reservada en una llamada subsiguiente, y ser pronto reutilizada en
cualquier programa.
Ya que el operador delete est definido para no hacer nada si se le pasa un valor NULL, se
puede liberar la memoria ocupada por un dato al que apunta un puntero NULL, ya que realmente
no se est haciendo nada. El operador delete slo puede utilizarse para liberar memoria
reservada con el operador new. Si se usa delete con cualquier otro tipo de dato, la operacin no
est definida, y por tanto nada sucede.
Finalmente, ya que el operador new requiere un tipo para determinar el tamao de un bloque
dinmicamente reservado, se muestra cmo reservar un bloque de tamao arbitrario. Esto es
posible utilizando la construccin de las ltimas lneas del programa, donde un bloque de 37
caracteres de tamao (37 bytes) es reservado. Un bloque de 133 bytes mayor que el tamao de
un entero se reserva posteriormente. Por tanto, el operador new se puede utilizar con la misma
flexibilidad de la funcin malloc() de C.
Cuando los datos reservados dinmicamente son borrados con delete, todava quedan en
memoria. Si repetimos la instruccin cout inmediatamente despus de utilizar delete, veremos
que todava se conservan los valores. Si la repetimos de nuevo antes de dejar el programa,
cuando el espacio que ocupaban debe haber sido sobre escrito, veremos que ya no es as.
Incluso aunque el compilador nos d los nmeros correctos, no es una buena prctica pensar
que esos datos estn ah todava, porque en un programa dinmico largo la memoria se usar
continuadamente.
Las funciones estndar utilizadas en C para manejo dinmico de memoria, malloc(), calloc() y
free(), tambin se pueden utilizar en C++ de la misma forma que en C. Los operadores new y
delete no deben mezclarse con estas funciones, ya que los resultados pueden ser
impredecibles. Si se est partiendo de cdigo C, lo mejor es continuar utilizando las funciones en
las nuevas lneas de programa. Si no es as, se deben utilizar los nuevos operadores, ya que se
21
han construido como parte del lenguaje en s, ms que aadirse, y por tanto son ms
eficientes1.
Cuando se utiliza new para reservar memoria para un vector, el tamao del vector se sita entre
corchetes, siguiendo al tipo:
int *intvector;
intvector = new int [20];
y se libera:
delete [ ] intvector;
Progra17.cpp
#include <iostream.h>
void main()
{
int *apent; // Declara un apuntador a entero
apent = new int ; // Reserva un bloque de memoria dinmica
// de 2 bytes para manejarlo por medio de apent.
*apent = 10 ; // Asigna el valor 10 al objeto apuntado por apent.
cout << *apent ; // Despliega el contenido del objeto apuntado por apent.
delete apent ; // Libera el espacio de memoria manejado por apent.
}
En el listado llamado Progra 17.cpp, se supone que la reservacin ser exitosa porque existe espacio
suficiente en el montculo.
Pero quin asegura que el espacio requerido por new est disponible?. Para controlar esta situacin y
evitar un mensaje de error por parte del sistema en tiempo de ejecucin, en el listado siguiente se
propone una nueva versin del programa.
Progra18.cpp
#include <iostream.h>
#include <stdlib.h> // Para exit().
void main()
{
int *apent; // Declara un apuntador a entero
1
Fuente http://www.pablin.com.ar/computer/cursos/c1/allocate.html
22
}
*apent="10" ; // Asigna el valor 10 al objeto apuntado por apent.
cout << *apent ; // Despliega el contenido del objeto apuntado por apent.
delete apent ; // Libera el espacio de memoria manejado por apent.
}
En caso de que el montculo no disponga del espacio requerido, new retorna el valor NULL ( nulo ).
La funcin malloc () regresa una direccin, y su tipo de retorno es un apuntador a tipo void. Por qu
void?. Un apuntador a tipo void es compatible con todos los tipos de datos. Como la memoria asignada
por malloc () puede ser usada para guardar cualquiera de los tipos de datos de C, es adecuado el tipo
de retorno void.
Al igual que malloc(), free() es una funcin del lenguaje de programacin C, utilizado para liberar la
memoria asignada por malloc (). Al usar la funcin free () se debe tener en cuenta la regla de oro
explicada en el apartado del operador delete toda la memoria que se reserve durante el programa hay
que liberarla antes de salir del programa
Al usar las funciones malloc () y free () se debe incluir el archivo de encabezado STDLIB.H
Ejemplo 1
// asigna memoria para un arreglo de 50 enteros
Int *nmeros;
Nmeros = (int * ) malloc (50 * sizeof (int));
23
Ejemplo 2
// asigna memoria para un arreglo de 10 valores float
float *nmeros;
Nmeros = (float * ) malloc (10 * sizeof (float));
Progra19.cpp
#include <conio.h>
#include <stdlib.h>
#include <stdio.h>
void main()
{
clrscr();
int *p==NULL;
int nbytes=100;
p=(int *)malloc(nbytes);
if(p=NULL)
{
cout<<"Insuficiente espacio en memoria\n");
return -1;
}
cout<<"se han asignado %d bytes de memoria\n", nbytes);
free(p);
getch();
}
El resultado del programa muestra un mensaje en pantalla confirmando que se realiz la asignacin
dinmica de memoria con xito.
Ahora veamos otro ejemplo de aplicacin de la asignacin dinmica de memoria utilizando las
funciones malloc () y free (), con un ingrediente adicional el uso de una estructura llamada persona que
nos permita almacenar el registro de una persona, dos tipos de datos diferentes, un dato de tipo
carcter que almacena el nombre de la persona y otro dato de tipo entero para almacenar la edad.
Progra20.cpp
// Listado de libreras o archives de cabecera
#include<iostream.h>
#include<stdlib.h>
#include<conio.h>
int n, i;
p =(persona *)malloc(sizeof(persona));
AGUILAR, Luis (2000). Programacin en C++, Algoritmos, estructura de datos y Objetos . Espaa:
McGRAW-HILL.
DEYTEL Y DEYTEL (1999). Como programar C++(segunda Edicin). Mexico D.F. : Prentice Hall.
McGRAW-HILL.
FARREL, Joyce (2000). Introduccin a la programacin lgica y diseo. Mexico D.F : Thomson.
http://www.programacionfacil.com/estructura_de_datos/manejo_de_memoria
http://www.conclase.net/c/fuentes.php?tema=3
http://www.ilustrados.com/publicaciones/EpZVVEZpyEdFpAKxjH.php
2
http://www.fismat.umich.mx/mn1/manual/node10.html consultado en Junio 18 de 2009