Você está na página 1de 43

Estructura de un programa en C++

El formato general de un programa en C++ es el siguiente :


/ | | \

ENCABEZADO

<

Definicin de constantes y macros, declaracin de variables globales, inclusin de archivos.

void main(void) <---linea de cabecera de la funcin principal. { <-------------Inicio de bloque. ......... ......... ......... <--Cuerpo de la funcin principal. ......... } <-------------Fin de bloque.

En el listado 1.1 se muestra un programa que despliega en la pantalla del monitor el mensaje: El Lenguaje C++ es poderoso
// PRIMER.CPP // // Despliega un mensaje en la pantalla. #include <stdio.h> // Para utilizar la funcin printf()

void main(void) { printf("El lenguaje C++ es poderoso\n"); }

Listado 1.1.- Primer programa en C++

1.2.- Archivos de cabecera.


El listado 1.1 contiene la lnea :
#include <stdio.h>

En ella, stdio.h es el nombre de un archivo de cabecera en el cual se encuentran declarados los prototipos de las funciones y macros que se utilizan para la entrada/salida estndar. Existe un extenso grupo de archivos de cabecera, y cada uno de ellos sirve para manejar un grupo de funciones relacionadas. La extensin h de estos archivos es la inicial de la palabra header. Generalmente estos archivos se incluyen en las primeras lneas de los archivos que contienen el cdigo fuente de los programas en C++ . El compilador de C++ desplegar un mensaje de error cuando se trate de utilizar una funcin sin antes haber incluido el archivo de cabecera que contiene el prototipo de ella. Los archivos de cabecera son almacenados en el directorio INCLUDE cuando se instala el paquete del compilador.

1.3.- Comentarios.

En el C++ existen dos formas de manejar los comentarios: La primera sirve para escribir comentarios que abarcan varias lneas, y utiliza los caracteres /* para marcar el inicio de un comentario, y los caracteres */ para marcar el final, como se ve en los siguientes ejemplos:
/* Este es un pequeo comentario */ /* Este es otro comentario, pero a diferencia del anterior, utiliza varias lneas.

Observe que tambin puede incluir lneas en blanco (como la anterior y la siguiente).
*/

La segunda forma solo sirve para comentarios que abarcan una lnea o parte de ella, y utiliza los caracteres // al principio del comentario. El fin del comentario queda marcado por el fin de la lnea, como se muestra a continuacin:
// Este es un comentario de una lnea, // y este es otro. void main(void) // Este comentario ocupa parte de la lnea.

Los comentarios de una sola lnea pueden anidarse dentro de los comentarios de varias lneas, como en :
/* El principio del comentario de varias lneas. // Primer comentario de una sola lnea. // Segundo comentario de una sola lnea. */ Fin del comentario de varias lneas.

1.4.1.- Inclusin de archivos


Pueden incluirse los contenidos de otros archivos de texto en el archivo que contiene cdigo en C++ . Esto se logra por medio de la directiva #include en cualquiera de las tres formas siguientes : #include <nombre_archivo> /* nombre_archivo se buscar solamente en el directorio INCLUDE */ #include "nombre_archivo" /* nombre_archivo se buscar en el directorio de trabajo actual */ #include "d:\trayectoria\nombre_archivo" /* En este caso, nombre_archivo ser buscado en el directorio especificado en trayectoria */ Ejemplo: #include "C:\DATOSCPP\PANTALLA.C"

1.4.2.- Reemplazamiento de cadenas y procesamiento de macros

El reemplazamiento de cadenas consiste en sustituir (antes de efectuar la compilacin) cada una de las ocurrencias de cierta cadena (llamada constante simblica) por otra cadena equivalente. Esto de logra por medio de la directiva #define, como se muestra en los siguientes ejemplos:
#include <stdio.h> #define CADENA "El Lenguaje C++ es poderoso\n" void main(void) { printf(CADENA); }

En este caso, el preprocesador sustituir CADENA por El Lenguaje C++ es poderoso, de tal manera que el compilador se encontrar con la instruccin: printf("El Lenguaje C++ es poderoso\n");
#define VALMAX 1000

Aqu, se est definiendo la constante simblica VALMAX, cuyo valor ser el entero 1000.

La directiva #define tambin sirve para declarar macros, las cuales incluyen una ms instrucciones del Lenguaje C++ , como en:
#define CUBO(N) ((N)*(N)*(N))

Esta macro puede utilizarse en una instruccin de la siguiente forma:


r = CUBO(5);

El preprocesador sustituye lo anterior por:


r = ((5)*(5)*(5));

Las macros pueden tomar formas complejas y ocupar varias lneas de cdigo, como se muestra en el siguiente ejemplo:
#define REDOND(x) ((x)>=0?((x)+1/VALOR1)*(1-1/VALOR1): \ ((x)-1/VALOR1)*(1+1/VALOR1))

Obsrvese que al final de la primera lnea se escribi una diagonal invertida ( \ ), la cual sirve como enlace.

1.4.2.- Reemplazamiento de cadenas y procesamiento de macros


El reemplazamiento de cadenas consiste en sustituir (antes de efectuar la compilacin) cada una de las ocurrencias de cierta cadena (llamada constante simblica) por otra cadena equivalente. Esto de logra por medio de la directiva #define, como se muestra en los siguientes ejemplos:
#include <stdio.h> #define CADENA "El Lenguaje C++ es poderoso\n" void main(void) {

printf(CADENA); }

En este caso, el preprocesador sustituir CADENA por El Lenguaje C++ es poderoso, de tal manera que el compilador se encontrar con la instruccin: printf("El Lenguaje C++ es poderoso\n");
#define VALMAX 1000

Aqu, se est definiendo la constante simblica VALMAX, cuyo valor ser el entero 1000.

La directiva #define tambin sirve para declarar macros, las cuales incluyen una ms instrucciones del Lenguaje C++ , como en:
#define CUBO(N) ((N)*(N)*(N))

Esta macro puede utilizarse en una instruccin de la siguiente forma:


r = CUBO(5);

El preprocesador sustituye lo anterior por:


r = ((5)*(5)*(5));

Las macros pueden tomar formas complejas y ocupar varias lneas de cdigo, como se muestra en el siguiente ejemplo:
#define REDOND(x) ((x)>=0?((x)+1/VALOR1)*(1-1/VALOR1): \ ((x)-1/VALOR1)*(1+1/VALOR1))

Obsrvese que al final de la primera lnea se escribi una diagonal invertida ( \ ), la cual sirve como enlace.

1.4.3.- Compilacin condicional


Existe un grupo de directivas del preprocesador que permiten controlar cuales partes del cdigo fuente se van a compilar al cumplirse ciertas condiciones. A continuacin se muestra una lista de tales directivas; dejndose la ejemplificacin de su uso para unidades ms avanzadas de este trabajo.
#if #ifdef #if defined(algo) #ifndef #else #elif #endif

Adems de las directivas vistas hasta aqu, existen las siguientes:


#error #pragma inline #pragma warn #pragma saveregs

1.5.- La Funcin main()

Todo programa de C++ debe contener una funcin llamada main() (principal), la cual sirve de enlace con el sistema operativo que se est utilizando. Cuando se intente obtener un programa ejecutable(.EXE ) a partir de un archivo que no contenga una funcin main(), el compilador desplegar un mensaje de error. Al invocar a un programa escrito en C++, desde el indicador de presencia del sistema operativo, se le pueden pasar ciertos valores que se llaman argumentos, los cuales van a ser recibidos por la funcin principal. Esta, a su vez, va a regresar un valor de retorno al sistema operativo que la invoc. La devolucin del valor de retorno se hace por medio de la instruccin return, por ejemplo: return 0 ; La explicacin acerca de la utilizacin de argumentos en la funcin main() requiere del conocimiento de apuntadores, por lo que se tratar en la unidad 6.

1.6.1.- Identificadores creados por el usuario


El usuario debe cuidar que los identificadores creados por l cumplan las siguientes: Reglas generales para los identificadores: 1.- Pueden estar formados por: a.- Letras maysculas y/o minsculas. b.- El carcter de subrayado. c.- Los dgitos del 0 al 9. 2.- El primer carcter debe ser una letra o el carcter de subrayado. 3.- Pueden contener un nmero arbitrario de caracteres; pero Borland C++ considera como significativos los primeros 32 o los n primeros que decida el programador, donde: 1 <= n <="32" 4.- Se toma en cuenta la diferencia entre letras maysculas y minsculas, por lo que : SUMA, Suma y suma son identificadores diferentes. 5.- No puede utilizarse el mismo identificador para dos objetos que se encuentren dentro del mismo mbito.

1.6.2.- Palabras reservadas


Las palabras reservadas, como su nombre lo indica, son identificadores que tienen asignado un uso especfico y no podrn ser utilizadas como identificadores creados

por el usuario. La tabla 1.1 muestra las palabras reservadas de Borland C++
+ _asm asm auto break case _cdecl cdecl char class const continue _cs default delete do double @ @ @ @ @ @ + @ + _ds else enum _es _export extern _far far _fastcall float for friend goto _huge huge if inline @ @ @ @ + + @ @ + + + @ int _interrupt interrupt _loadds long _near near new operator _pascal pascal private protected public register return _saveregs @ _seg short signed sizeof _ss static struct switch template this typedef union unsigned virtual void volatile while

@ @ + @ +

+ +

Tabla 1.1.- Palabras reservadas de Borland C++. Las palabras marcadas con + en la tabla 1.1 son las palabras reservadas especficas de C++, las marcadas con @ son las palabras aadidas por Borland al C, y las palabras sin marca son las palabras reservadas del C estndar.

1.7.- Entrada/Salida
En el Lenguaje C++, la entrada/salida se maneja de dos formas. La primera consiste en la utilizacin de las funciones cuyos prototipos vienen declarados en el archivo stdio.h, y es la forma que tradicionalmente ha utilizado el Lenguaje C. La segunda es propia del C++ y consiste en el manejo de objetos y operadores cuyas definiciones se encuentran en el archivo iostream.h . A continuacin explicaremos cada una de estas dos formas. 1.7.1.- Funciones Declaradas en stdio.h 1.7.2.- Funciones Declaradas en conio.h 1.7.3.- Objetos Declarados en iostream.h

5.1.- Declaracin de arreglos


Los elementos de los arreglos son variables de algn tipo dado, que al compartir el mismo nombre pueden ser tratadas como una sola entidad. La sintaxis para la declaracin de un arreglo es la siguiente:
tipo donde: identificador [ <expresin_constante> ] ;

tipo

es el tipo de los elementos que componen el arreglo

identificador es el nombre del arreglo expresin_constante es una expresin que al reducirse debe dar como resultado un valor entero positivo.

En el ejemplo 5.1 se muestra la declaracin de arreglos.


DECLARACION char nombre[31]; int valor[20]; unsigned long abc[x] RESULTADO Declara un arreglo unidimensional llamado nombre compuesto de 31 elementos de tipo carcter. Declara un arreglo unidimensional llamado valor, compuesto por 20 elementos de tipo entero con signo. Declara un arreglo unidimensional llamado abc, compuesto por x elementos de tipo entero largo sin signo. En este caso x debe ser una constante entera. Declara un arreglo bidimensional llamado matriz, compuesto por 35 elementos de tipo entero. Declara un arreglo tridimensional llamado trid, compuesto por 120 elementos de tipo entero.

double matriz[5][7]; int trid[3][5][8];

Ejemplo 5.1.- Declaraciones de arreglos. Como se observa en el ejemplo 5.1, la declaracin de un arreglo multidimensional se distingue en que se agrega una pareja de corchetes para cada dimensin, por lo que la sintaxis, en este caso, toma la siguiente forma:
tipo identificador [ cte1 ][ cte2 ][ cte3 ] ... ; donde: cte1, cte2, etc. representan los subndices para cada dimensin.

El nmero y tamao de las dimensiones solo estar restringido por la disponibilidad de memoria RAM, por lo que se puede tener una declaracin como la siguiente:
double multidim [5][5][5][5][5][5] ;

El espacio de memoria ocupado por el arreglo multidim es el mismo que el ocupado por:
double unidim [15625

5.2.1.- Asignacin de valores a los elementos de un arreglo


Al declarar un arreglo dentro de una funcin, los valores almacenados en cada uno de los elementos son desconocidos (se dice que el arreglo "tiene basura"), lo cual causa que el programa correspondiente arroje resultados inesperados. Para evitar los valores desconocidos, se recomienda asignar valores iniciales a cada uno de los elementos de los arreglos, como se muestra a continuacin: Por ejemplo, supongamos la siguiente declaracin:

int vector[5];

En este caso, se declara un arreglo de 5 variables de tipo entero agrupadas con el nombre vector, las cuales pueden representarse con la figura 5.1.

Figura 5.1.- Representacin de un arreglo Como puede observarse en la figura 5.1, el primer subndice tiene valor cero y el ltimo tiene valor cuatro. Lo anterior se debe a que, en C++, el primer subndice siempre vale cero y el ltimo tiene un valor menor en uno que el valor de la dimensin del arreglo. Una vez declarado el arreglo, se pueden asignar valores a cada uno de sus elementos, como se muestra enseguida:
vector[0] vector[1] vector[2] vector[3] vector[4] = = = = = 100 101 102 103 104 ; ; ; ; ;

y el arreglo vector lucira como en la figura 5.2.

Figura 5.2.- El arreglo vector despus de asignarle valores. En este caso particular, pudimos haber asignado los valores por medio de un estatuto for de la siguiente forma:
for( int x = 0 ; x < 5 ; x++) vector[x] = x + 100 ;

Esta forma es la ms conveniente en caso de que la cantidad de elementos sea grande y que los valores a asignar sean iguales o las diferencia entre elementos consecutivos sea constante.

Todo lo escrito en este ejemplo es vlido para arreglos con elementos de cualquier tipo. Por ejemplo, si queremos desplegar en pantalla los caracteres del cdigo ASCII lo haramos por medio del siguiente grupo de instrucciones:
unsigned char caracter[256]; for( int x=0 ; x < 256 ; x++ ) printf("%c", caracter[x]) ;

Cuando se manejan arreglos de varias dimensiones, debe recordarse que el subndice de cada una de ellas inicia con un valor 0, como se observa en el listado 5.1.
#include <iostream.h> #include <conio.h> void main(void) { int matriz[3][4]; clrscr(); for(int x=0 ; x < 3 x++) { for(int y=0 y< 4 ; y++) { matriz[x][y]=x+y+1 ; gotoxy(y+1,x+1); cout matriz[x][y]; } } }

Listado 5.1.- Arreglo con dos dimensiones. El resultado de ejecutar el programa del listado 5.1 es el siguiente:
1234 2345 3456

El arreglo matriz puede representarse con la figura 5.3 .

Figura 5.3.- Representacin del arreglo matriz . Obsrvese que el primer subndice vara de 0 a 2 y el segundo vara de 0 a 3. En los prrafos anteriores, se ha mostrado la forma de asignar valores a cada uno de los elementos de un arreglo una vez que se ha escrito su declaracin. Aqu veremos la manera de asignarle valores iniciales a los elementos, en la misma instruccin que contiene la declaracin del arreglo. Por ejemplo, para declarar un arreglo de 10 enteros y al mismo tiempo asignarle a cada uno de sus elementos los valores 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, puede escribirse : int digitos[] = { 0,1,2,3,4,5,6,7,8,9 }; En este caso, aunque no se escribe el tamao de la dimensin del arreglo digitos, tiene el mismo efecto que escribir:
int digitos[10]; digitos[0] digitos[1] digitos[2] digitos[3] digitos[4] digitos[5] digitos[6] digitos[7] digitos[8] digitos[9] = = = = = = = = = = 0 1 2 3 4 5 6 7 8 9 ; ; ; ; ; ; ; ; ; ;

Como puede verse, la primera forma es mucho ms compacta, aunque , como en muchas de las instrucciones de C++, la brevedad del cdigo sacrifica la claridad.

5.3.- Cadenas de caracteres


En C++ no existe un tipo predefinido para el manejo de cadenas de caracteres como en otros lenguajes, sino que tienen que declararse como arreglos de caracteres. Lo que distingue a una cadena de caracteres, con respecto a un arreglo de caracteres cualquiera, es que la cadena de caracteres tiene como ltimo carcter al carcter nulo \0. Por ejemplo, si se declara el arreglo:
char cadena[8];

podemos asignar los siguientes valores a cada uno de sus elementos:


cadena[0] cadena[1] cadena[2] cadena[3] cadena[4] cadena[5] cadena[6] cadena[7] = = = = = = = = 'A' ; 'R' ; 'R' ; 'E' ; 'G' ; 'L' ; 'O' ; '\0';

Al contener el carcter nulo, el arreglo cadena ser reconocido por las funciones y objetos diseados para manipular cadenas de caracteres. Para manejar un arreglo de cadenas de caracteres se debe declarar como un arreglo bidimensional de elementos de tipo char, como puede observarse en el listado 5.2.
#include <iostream.h> #include <conio.h> void main() { unsigned short int calif[10]; char nombre[10][21]; // Se declara un arreglo bidimensional // para 10 nombres de 20 caracteres por // nombre mas un caracter para el nulo. clrscr(); for( int x=0 ; x < 10 ; x++) { gotoxy(10,x+1); cout << "NOMBRE [" << x << "] = " ; cin >> nombre[x]; gotoxy(45,x+1); cout << "CALIFICACION [" << x << "] = " ; cin >> calif[x]; } }

Listado 5.2.- Manejo de un arreglo de cadenas de caracteres. En el listado 5.2, se inicia con el subndice 0 para no desperdiciar el primer elemento

del arreglo. Adems, se debe recordar que el subndice del ltimo elemento del arreglo es igual al tamao de la dimensin menos 1.

5.4.- Asignacin de Valores Iniciales a Cadenas


Cuando se tiene un arreglo de cadenas de caracteres, se puede utilizar la forma compacta mostrada en la seccin anterior, slo que , en lugar de constantes numricas manejaramos constantes de cadena. Por Ejemplo:
char nombres[][5] = { "HUGO", "PACO", "LUIS" } ; es equivalente a: char nombres[3][5]; nombres[0] = "HUGO" ; nombres[1] = "PACO" ; nombres[2] = "LUIS" ;

Esto tambin puede manejarse a nivel de caracteres, as :


char nombres[][5] = { 'H','U','G','O','\0', 'P','A','C','O','\0', 'L','U','I','S','\0' };

o as:
char nombres[3][5]; nombres[0][0] nombres[0][1] nombres[0][2] nombres[0][3] nombres[0][4] nombres[1][0] nombres[1][1] nombres[1][2] nombres[1][3] nombres[1][4] nombres[2][0] nombres[2][1] nombres[2][2] nombres[2][3] nombres[2][4] = = = = = = = = = = = = = = = 'H' ; 'U' ; 'G' ; 'O' ; '\0' ; 'P' ; 'A' ; 'C' ; 'O' ; '\0' ; 'L' ; 'U' ; 'I' ; 'S' ; '\0' ;

En los listados 5.3 y 5.4 se muestran las dos primeras formas, observndose que se obtiene el mismo resultado.
#include <iostream.h> #include <conio.h> void main() { char nombres[][5] = { "HUGO","PACO","LUIS" }; int calif[] = { 100, 90, 100 }; clrscr(); for( int x=0 ; x < 3;x++) { gotoxy(35,x+10);

cout nombres[x]; gotoxy(40,x+10); cout << calif[x]; } }

Listado 5.3.- Manejo de arreglos de cadenas.


#include <iostream.h> #include <conio.h> void main() { char nombres[][5] = { 'H','U','G','O','\0', 'P','A','C','O','\0', 'L','U','I','S','\0' }; int calif[] = { 100, 90, 100 }; clrscr(); for( int x=0 ; x< 3;x++) { gotoxy(35,x+10); cout nombres[x]; gotoxy(40,x+10); cout << calif[x]; } }

Listado 5.4.- Manejo de arreglos bidimensionales de caracteres. En los listados 5.3 y 5.4, se muestra que no es necesario escribir el valor de la primera dimensin de los arreglos cuando, en su declaracin, se asignan valores constantes a los elementos. La ventaja que tiene la forma mostrada en 5.3 y 5.4 es que, al no especificar una de las dimensiones, la cantidad de cadenas a manejar puede variarse con slo agregarlas a la lista o eliminarlas de ella.

5.5.- Funciones para el manejo de cadenas


Como se estableci al principio de esta unidad, el lenguaje C++ no cuenta con algn tipo de dato especfico para el manejo de cadenas de caracteres, pero s cuenta con un grupo de funciones que se han acumulado durante la evolucin del Lenguaje C. Para leer una cadena de caracteres desde el teclado existe la funcin gets(), y para desplegar una cadena en pantalla se usa la funcin puts(). Los prototipos de ambas funciones se encuentran declarados en el archivo STDIO.H. Por ejemplo, el listado 5.5 muestra un programa que sirve para leer y desplegar cadenas de caracteres utilizando las funciones gets() y puts().
#include <stdio.h> #include <conio.h> #include <string.h> // Para gets() y puts() // Para clrscr() y gotoxy() // Para strupr() y strlen()

void main() { char nombre[31]; // Declara un arreglo de 31 caracteres char saludo1[] = " HOLA,"; //Constante de caracteres

char saludo2[] = " !!"; clrscr(); gotoxy(20,10); puts(" Cul es tu nombre ? "); //Despliega cadena de car. gotoxy(45,10); gets(nombre); // Lee cadena de caracteres strupr(nombre); // Convierte a maysculas gotoxy(20,12); puts(saludo1); gotoxy(30,12); puts(nombre); gotoxy(30+strlen(nombre),12); // Longitud de la cadena puts(saludo2); }

Listado 5.5.- Lectura y desplegado de cadenas de caracteres. Adems de las funciones gets() y puts(), existe otro grupo de funciones para el manejo de cadenas de caracteres, como strlen() y strupr() utilizadas en el programa del listado 5.5. Los prototipos de estas funciones se encuentran declarados en el archivo STRING.H En la tabla 5.1 se describen brevemente algunas de las funciones para el manejo de cadenas de caracteres en el C++ de Borland, cuyos prototipos se encuentran declarados en el archivo STRING.H . FUNCION stpcpy strcat strchr strcmp strcmpi strcpy strcspn strdup _strerror strerror stricmp strlen strlwr strncat strncmp strncmpi strncpy strnicmp strnset strpbrk strrchr DESCRIPCION Copia una cadena de caracteres en otra.Se detiene cuando encuentra el terminador nulo. Aade una cadena de caracteres a otra. Busca, en una cadena, un caracter dado. Compara dos cadenas. Macro que compara dos cadenas sin distinguir entre maysculas y minsculas. Copia una cadena. Busca segmentos que no contienen un subconjunto de un conjunto especificado de caracteres. Copia una cadena a una nueva localidad. Genera un mensaje de error definido por el programador. Retorna el apuntador al mensaje asociado con el valor del error. Compara dos cadenas sin diferenciar entre maysculas y minsculas Determina la longitud de una cadena. Convierte las maysculas de una cadena en minsculas. Aade el contenido de una cadena al final de otra. Compara parte de una cadena con parte de otra. Compara parte de una cadena con parte de otra, sin distinguir entre maysculas y minsculas. Copia un un nmero de bytes dados, desde una cadena hacia otra. Compara parte de una cadena con parte de otra, sin distinguir entre maysculas y minsculas. Hace que un grupo de elementos de una cadena tengan un valor dado. Busca la primera aparicin, en una cadena, de cualquier caracter de un conjunto dado. Busca la ltima aparicin de un caracter en una cadena.

Invierte el orden de los caracteres de una cadena. Hace que los elementos de una cadena tengan un valor dado. Busca en una cadena el primer segmento que es un subconjunto de un strspn conjunto de caracteres dado. strstr Busca en una cadena la aparicin de una subcadena dada. _strtime Convierte la hora actual a una cadena. strtod Convierte una cadena a un valor double long double. strtol Convierte una cadena a un valor long. strtoul Convierte una cadena a un valor unsigned long. strupr Convierte las minsculas de una cadena a maysculas. Tabla 5.1.- Funciones para el manejo de cadenas de caracteres.

strrev strset

5.6.- Control de acceso a los elementos de un arreglo


En C++, el acceso a los elementos de un arreglo tiene que ser controlado por el programador, ya que el lenguaje no restringe la posibilidad de accesar a posiciones de memoria que estn ms abajo de la ltima posicin reservada para el arreglo. Lo mismo sucede cuando se manejan cadenas, donde el programador tiene la responsabilidad de controlar el acceso a los caracteres de la cadena, tomando como lmite el terminador nulo. En el listado 5.6 se presenta un ejemplo de acceso a los 5 bytes colocados abajo del terminador nulo de una cadena dada por el usuario.
#include <stdio.h> #include <conio.h> #include <string.h> // Para gets() y puts() // Para clrscr() y gotoxy() // Para strlen()

void main() { char nombre[31]; clrscr(); gotoxy(10,1); puts(" Cul es tu nombre ? "); gotoxy(35,1); gets(nombre); clrscr(); gotoxy (10,1); puts("ELEMENTO CARACTER DIRECCION"); for( int x=0 ; (x <strlen(nombre)+5) && (x<23); x++) { gotoxy(10,x+2); printf("nombre[%2d] %c= %4d %9u", x,nombre[x],nombre[x],&nombre[x]); } }

Listado 5.6.- Colocacin de los elementos de una cadena en memoria RAM.

6.1.- Conceptos bsicos


Para efectos de nuestro estudio, llamaremos variables automticas a las creadas en la pila (stack) y variables dinmicas a las creadas en el montculo (heap). Un apuntador es una variable cuyo contenido es un valor entero sin signo( de 16 o 32 bits ), el cual representa una direccin de memoria. La direccin de memoria puede

corresponder a una variable creada en la pila o en el montculo. Los apuntadores son una de las herramientas ms poderosas con que cuenta el Lenguaje C++ . Desafortunadamente, muchos programadores han creado el mito de que el estudio de los apuntadores es muy complicado, lo cual ha desarrollado una fobia entre quienes se inician en el estudio del lenguaje. En las unidades anteriores se han utilizado variables automticas. Para el manejo de las variables dinmicas es indispensable el uso de apuntadores; mientras que para las variables automticas, los apuntadores son una alternativa. En las figuras 6.1, 6.2 y 6.3 se muestra el manejo de la pila y el montculo en los seis modelos de memoria disponibles en el C++ de Borland.

Figura 6.1.- Modelo TINY

Figura 6.2.- Modelos SMALL y MEDIUM

Figura 6.3.- Modelos COMPACT, LARGE y HUGE

Para cada programa, dependiendo del espacio requerido para el cdigo y para los datos, se puede utilizar un modelo de memoria especfico. El modelo predeterminado en el C++ de Borland es el modelo SMALL. A continuacin se presenta una breve descripcin de los modelos de memoria manejados por el C++ de Borland : Con el modelo de memoria TINY se utiliza un solo segmento (64 KB) para el cdigo, los datos y la pila. La utilizacin del modelo TINY es aconsejable nicamente cuando se quiere crear un archivo .COM En el modelo SMALL, el cdigo se almacena en un segmento y los datos en otro. Este modelo de memoria se recomienda para programas .EXE de tamao medio. Cuando se usa el modelo MEDIUM, los datos estn limitados a un segmento pero el cdigo puede utilizar varios (hasta 16 MB). El uso de este modelo se recomienda cuando se tiene un programa grande con pocos datos. El modelo COMPACT es lo contrario de MEDIUM, ya que limita el cdigo a un segmento mientras que los datos pueden ocupar varios (hasta 16 MB). Este modelo es recomendable cuando se tienen programas hasta de tamao medio con muchas variables variables muy grandes. En los modelos LARGE y HUGE, el cdigo y los datos pueden ocupar varios segmentos (hasta 16 MB). La diferencia entre estos modelos es que el HUGE puede manejar variables automticas que, en total, excedan los 64 KB. La tabla 6.1 muestra los modificadores predeterminados para los apuntadores a funciones y apuntadores a datos en los diferentes modelos de memoria. Modelo Apuntadores Apuntadores de a a Memoria Funciones Datos TINY near near SMALL near near MEDIUM far near COMPACT near far LARGE far far HUGE far far Tabla 6.1.- Modificadores predeterminados para apuntadores. NOTA: Cuando se compila un mdulo, el cdigo resultante no debe ser mayor que 64 KB, ya que debe caber en un segmento de cdigo. Esto debe cumplirse an cuando se est utilizando un modelo de memoria que utilice varios segmentos para el cdigo (MEDIUM, LARGE, HUGE). De manera similar, en el caso del modelo HUGE no debe haber datos automticos de ms de 64 KB en cada mdulo.

6.2.- Declaracin de apuntadores

Los apuntadores son variables automticas cuyos valores representan direcciones de memoria correspondientes a otras variables. La sintxis para la declaracin de un apuntador es la siguiente:
tipo Ejemplo: donde: * identificador ;

int *apint ; // Declaracin del apuntador apint // Se dice que : "apint va a apuntar a // variables de tipo int" apint * es el nombre del apuntador es el operador de indireccin

Obsrvese que el operador de indireccin utiliza el mismo smbolo que el operador de multiplicacin. En el ejemplo anterior, puede decirse que:
*apint se refiere al objeto apuntado por apint . apint es un apuntador a objetos de tipo int

Recuerde que: para referirse al objeto apuntado, se antepone el operador de indireccin al nombre del apuntador. Al declarar al apuntador, se le reserva espacio en la pila. Esto no implica que se est reservando espacio para el objeto apuntado. En la figura 6.4 se representa la asignacin de espacio en la pila para el apuntador apint.

Figura 6.4.- Apuntador en la pila.

6.3.- Variables automticas y apuntadores


Las variables automticas se crean en tiempo de compilacin y se destruyen al terminar la ejecucin del mdulo donde fueron declaradas. Aunque no es estrictamente necesario, se pueden manejar las variables automticas por medio de apuntadores, como se muestra en el listado 6.1.
#include <iostream.h> #include <conio.h> void main() { int automatica ; // Se declara la variable automatica. int *apunt ; // Se declara el apuntador apunt, que apun// tar a objetos de tipo int.

automatica = 100 ; // Se asigna el valor 100 a la variable // automatica. apunt = &automatica ; // Se asigna a apunt la direccin de // automatica apunt apunta a // automatica. clrscr(); cout << "VALOR = " << automatica << " \n"; // 100 *apunt = 200 ; // Se asigna el valor 200 al objeto apuntado por apunt. cout << "VALOR = " << automatica << " \n"; // 200 getch(); }

Listado 6.1.- Ejemplo de variables automticas y apuntadores. Las instrucciones del listado 6.1 se traducen en la siguiente secuencia, donde los apuntadores se representan con una flecha (para simular que "apuntan hacia" "sealan" un objeto) y los objetos apuntados se representan por un cuadro (para simular un recipiente).
INSTRUCCION int automatica ; REPRESENTACION GRAFICA automatica |----------------| | ? | |----------------| ----> ? automatica |----------------| | 100 | |----------------| automatica, *apunt |----------------| apunt ---->| 100 | |----------------| automatica, *apunt |----------------| ---->| 200 | |----------------|

int *apunt ; automatica = 100 ;

apunt = &automatica ;

*apunt = 200 ; apunt

El estado final de la zona de memoria correspondiente al objeto apuntado y al apuntador se representa en la figura 6.5.

Figura 6.5.- Visualizacin del objeto apuntado y del apuntador en la memoria RAM. Ntese que tanto el apuntador como el objeto apuntado se almacenan en la pila. Las direcciones de memoria FFF0 , FFF2 , FFF4 , son hipotticas. 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 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 ;

En el listado 6.2 se muestra un ejemplo de aplicacin de un apuntador de tipo void.


#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 // APUNTADO POR apmulti cout << varent ; apmulti = &varflot ; // apmulti APUNTA A varflot *(float *)apmulti = 1.1 ; // ASIGNA 1.1 AL OBJETO APUNTADO // POR apvoid cout << varflot ; NL; getch(); }

Listado 6.2.- Utilizacin de apuntadores de tipo void. Del listado 6.2, analicemos la instruccin:
*(int *)apmulti = 2 ;

en donde: apmulti es un apuntador de tipo void. (int *)apmulti est forzando a que apmulti apunte a objetos de tipo int. *(int *)apmulti se refiere a un objeto de tipo entero apuntado por apmulti.

En los ejemplos manejados hasta aqu se ha supuesto el modelo de memoria SMALL, por lo que los apuntadores son near por predeterminacin. El hecho de que sean near significa que solo disponen de dos bytes para almacenar una direccin de memoria. Al disponer de dos bytes, el nmero entero sin signo ms grande que pueden almacenar es 65535, el cual corresponde a la direccin ms alta de un segmento de memoria. Cuando se est utilizando el modelo de memoria SMALL y se quiere accesar una direccin de memoria de otro segmento, se debe utilizar el modificador far al declarar el apuntador correspondiente. Un apuntador far dispone de cuatro bytes; en los dos primeros bytes almacena la direccin del segmento y en los otros dos la direccin del desplazamiento. De esta forma se puede accesar una direccin de memoria que se encuentre en un segmento diferente al segmento de datos en uso. En el listado 6.3 se muestra el manejo de un apuntador far para rellenar la pantalla con el caracter tecleado. La ejecucin del programa termina cuando se pulsa la tecla .
#include <iostream.h> #include <conio.h> void main() { int far *aplej ; // Declara un apuntador far a enteros char c ; int ren, col ; clrscr(); cout << "Teclee caracteres ( Enter = Salida ) : " ; aplej=(int far *) 0xB8000000 ; while(( c=getche()) !='\r' ) for( ren=0 ; ren < 25 ; ren ++ ) for( col=0 ; col < 80 ; col++ ) *(aplej + ren*80 + col )= c | 0x0700 ; clrscr(); }

Listado 6.3.- Utilizacin de un apuntador far a enteros. A continuacin, se explican algunas lineas del listado 6.3. En:
int far *aplej ;

se declara un apuntador lejano a objetos de tipo entero. Observe que la sintaxis que rige la declaracin es:

tipo

far *identif ;

En la linea: aplej = (int far *) 0xB8000000 ; la parte (int far *) representa un forzamiento de tipo para que la constante hexadecimal sea manejada por un apuntador lejano. 0xB8000000 representa la direccn de memoria reservada para uso exclusivo en los modos de video CGA y EGA.

En este caso, B800 es la direccin del segmento, y 0000 es la direccin del desplazamiento. Por ltimo, en la linea:
*(aplej + ren*80 + col ) = c | 0x0700 ; la parte: aplej + ren*80 + col calcula la direccin de memoria correspondiente a cada caracter.

*(aplej + ren*col +80 ) representa al objeto apuntado (un caracter). mientras que: c | 0x0700

es una operacin OR a nivel de bits para asegurar que el caracter c tendr los atributos de caracter normal. Un apuntador o un objeto apuntado pueden declararse con el modificador const, y en tal caso no se les podr asignar un nuevo valor. Por ejemplo, en el listado 6.3 la constante B8000000 se asigna al apuntador aplej. El apuntador aplej fu declarado como una variable, por lo que sera vlido asignarle cualquier nuevo valor. Si deseamos que el valor B8000000 permanezca sin cambios en aplej, deberemos escribir la siguiente declaracin: int far *const aplej = (int far *) 0xB8000000L; con lo cual no podr asignarse un nuevo valor al apuntador aplej. El valor de una constante no puede cambiar, pero s puede utilizarse para realizar operaciones, como se observa en la siguiente fraccin de lnea del listado 6.3 : aplej + ren*80 + col

En este caso, se utiliza el valor de aplej para calcular la direccin de memoria donde se va a almacenar cada carcter.

6.5.- Variables dinmicas y apuntadores


Las variables dinmicas se manejan en el montculo, y deben su nombre al hecho de que pueden ser creadas y destruidas durante el tiempo de ejecucin de un mdulo. Para el manejo de variables dinmicas se hace indispensable la utilizacin de apuntadores, as como de funciones especiales para la asignacin y liberacin de la memoria correspondiente a dichas variables. Tanto en C como en C++ existen funciones tales como malloc() y free() para la asignacin y liberacin de memoria del montculo. Adems de estas funciones, el C++ cuenta con dos operadores interconstruidos:
new que realiza una labor parecida a la de la funcin malloc(), asignando un bloque de memoria.

delete que libera un bloque de memoria asignada en tiempo de ejecucin, de manera semejante a como lo hace free().

En el listado 6.4 se muestra un ejemplo de aplicacin de los operadores new y delete.


#include <iostream.h> void main() { int *apent;

// Declara un apuntador a entero Reserva un bloque de memoria dinmica de 2 bytes para manejarlo por medio de apent. Asigna el valor 10 al objeto apuntado por apent. Despliega el contenido del objeto apuntado por apent. Libera el espacio de memoria manejado por apent.

apent = new int ; // // // *apent = 10 ; // // cout << *apent ; // // delete apent ; // //

Listado 6.4.- Aplicacin de new y delete. En el programa del listado 6.4, 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 6.5 se propone una nueva versin del programa.
#include <iostream.h> #include <stdlib.h> // Para exit(). void main() { int *apent; // Declara un apuntador a entero

if((apent = new int)==NULL)// Intenta reservar un bloque de { // memoria dinmica de 2 bytes ra // manejarlo por medio de apent. cout << "NO hay espacio suficiente\n"; exit(1); // Finaliza la ejecucin del programa. } *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.

Listado 6.5.- Reservacin de memoria para una variable dinmica. Los operadores new y delete pertenecen al Lenguaje C++ , de tal manera que no se requiere incluir ningn archivo de cabecera para utilizarlos. Para crear un arreglo de 25 elementos de tipo double, en el montculo, puede escribirse:
double* dap ; dap = new double[25];

su forma equivalente:
double* dap = new double[25];

En este ejemplo, se est declarando a dap como un apuntador a variables dinmicas de tipo doble; al tiempo que se le asigna el valor retornado por new. El valor retornado por new es la direccin del inicio de un bloque de memoria del tamao requerido para almacenar 25 elementos de tipo double. En caso de que el montculo no disponga del espacio requerido, new retorna el valor NULL ( nulo ). Aunque, cuando se requiere memoria a travs de new se debe indicar el tamao exacto del bloque requerido, el tamao del bloque se puede dar en tiempo de ejecucin como se muestra en el listado 6.6.
#include <iostream.h> #include <conio.h> #include <stdlib.h> void main() { unsigned int n; clrscr(); gotoxy(20,1); cout << "NUMERO DE ELEMENTOS DEL ARREGLO: "; cin>> n; float* apf; if((apf=new float[n])==NULL) { cout << "NO HAY ESPACIO SUFICIENTE EN EL MONTICULO\N"; exit(1); } for(int x="0" ; x < n ; x++) {

gotoxy(20,x+3); *(apf+x)="x+100.25;" cout << *(apf+x) << "\n"; } delete apf; getch();

Listado 6.6.- Manejo de un arreglo en el montculo.

6.6.- Apuntadores y arreglos


Como se mencion en la unidad 5, el tema de arreglos est ntimamente ligado al de apuntadores; tanto que es posible intercambiarlos en la solucin de un problema. El nombre de un arreglo corresponde al de un apuntador que almacena un valor constante. Este valor constante es la direccin de memoria del primer elemento del arreglo. Por ejemplo :
int calif[]={100,90,95,80,90}; // // // // Declaracin e inicializacin de un arreglo de 5 enteros

se puede representar con la figura 6.6.

Figura 6.6.- El arreglo calif[ ] en la pila. 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]. La representacin del apuntador calif, en la zona de variables globales de la memoria RAM, es la siguiente :

En el listado 6.7 se presenta el manejo del arreglo calif[ ], a travs de la notacin de arreglos, y en el listado 6.8 el manejo con la notacin de apuntadores.
#include <iostream.h>

void main() { int calif[] = { 100,90,95,80,90}; for(int i=0 ; i <5 ; i++) //Notacin de arreglos. cout << "\n" << calif[i] ;

Listado 6.7.- Manejo de calif[] , notacin de arreglos.


#include <iostream.h> void main() { int calif[] = { 100,90,95,80,90}; for(int i=0 ; i <5 ; i++) // Notacin de apuntadores cout << "\n" << *(calif+i) ; }

Listado 6.8.- Manejo de calif[] , notacin de apuntadores. Como puede observarse, la nica diferencia entre los listados 6.7 y 6.8 es que el primero utiliza calif[i] y el segundo *(calif+i). 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 arreglo puede procederse as : calif[1] // Notacin de arreglos *(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 6.7 muestra los elementos del arreglo calif[] con sus nombres en notacin de arreglos y en notacin

de apuntadores.

Figura 6.7.- El arreglo calif[] en la pila. Notacin en arreglos y en apuntadores. De lo anterior, se infiere la regla:
calif[i] == *(calif+i)

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 otro ejemplo, supongamos la siguiente declaracin correspondiente a un arreglo de 10 elementos de tipo float.
float* sueldo[10];

Si la direccin del primer elemento es 65494, entonces:


&sueldo[5] es igual a : sueldo+5 = 65494 + ( 5 * 4 ) = 65514 ^ | Tamao de float ---------|

que corresponde a la direccin del elemento que se encuentra 5 posiciones ms adelante de aqul apuntado por sueldo. 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 ).

6.6.1.- Apuntadores y cadenas


Como se vi en la unidad 5, una cadena es un arreglo de caracteres cuyo ltimo elemento es el caracter nulo. Utilizando la nomenclatura de arreglos, podemos declarar:
char nombre[ ] = "PACO" ;

Esto mismo puede hacerse por medio de apuntadores, como se muestra en el listado

6.9.
#include <iostream.h> #include <conio.h> #include <stdio.h> void main() { char *nombre = "PACO" ; clrscr(); gotoxy(30,12); cout<< "!! HOLA, " ; puts(nombre); gotoxy(43,12); cout << " !!"; getch();

Listado 6.9.- Manejo de un arreglo de caracteres (cadena) por medio de apuntadores.

6.7.- Arreglos de apuntadores


Los apuntadores pueden manejarse en un arreglo, de tal forma que:
char nombres[ ][5] = { "HUGO", "PACO", "LUIS" } ;

es la declaracin de un arreglo de cadenas, con asignacin de valores iniciales. Su equivalente en notacin de apuntadores es: char *nombres[ ] = { "HUGO", "PACO", "LUIS" } ; en el que se declara un arreglo de apuntadores. El listado 6.10 muestra el programa completo para el manejo de este ejemplo.
#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(); }

Listado 6.10.- Manejo de un arreglo de apuntadores.

12 Tipos de objetos IV: Punteros 1


^ No, no salgas corriendo todava. Aunque vamos a empezar con un tema que suele asustar a los estudiantes de C++, no es algo tan terrible como se cuenta. Como se suele decir de los leones: no son tan fieros como los pintan.

El ataque de los punteros. Vamos a intentar explicar cmo funcionan los punteros de forma que no tengan el aspecto de magia negra ni un galimatas incomprensible. Pero no bastar con entender lo que se explica en este captulo. Es relativamente sencillo saber qu son y cmo funcionan los punteros. Para poder manejarlos es necesario tambin comprender los punteros, y eso significa saber qu pueden hacer y cmo lo hacen. Para comprender los punteros se necesita prctica, algunos necesitamos ms que otros, (y yo considero que no me vendra mal seguir practicando). Incluso cuando ya creas que los dominas, seguramente quedarn nuevos matices por conocer. Pero seguramente estoy exagerando. Si soy capaz de explicar correctamente los conceptos de este captulo, pronto te encontrars usando punteros en tus programas casi sin darte cuenta. Los punteros proporcionan la mayor parte de la potencia al C++, y marcan la principal diferencia con otros lenguajes de programacin. Una buena comprensin y un buen dominio de los punteros pondr en tus manos una herramienta de gran potencia. Un conocimiento mediocre o incompleto te impedir desarrollar programas eficaces. Por eso le dedicaremos mucha atencin y mucho espacio a los punteros. Es muy importante comprender bien cmo funcionan y cmo se usan. Creo que todos sabemos lo que es un puntero, fuera del mbito de la programacin. Usamos punteros para sealar cosas sobre las que queremos llamar la atencin, como marcar puntos en un mapa o detalles en una presentacin en pantalla. A menudo, usamos el dedo ndice para sealar direcciones o lugares sobre los que estamos hablando o explicando algo. Cuando un dedo no es suficiente, podemos usar punteros. Antiguamente

esos punteros eran una vara de madera, pero actualmente se usan punteros laser, aunque la idea es la misma. Un puntero tambin es el smbolo que representa la posicin del ratn en una pantalla grfica. Estos punteros tambin se usan para sealar objetos: enlaces, opciones de men, botones, etc. Un puntero sirve, pues, para apuntar a los objetos a los que nos estamos refiriendo. Pues en C++ un puntero es exactamente lo mismo. Probablemente habrs notado que a lo largo del curso nos hemos referido a variables, constantes, etc como objetos. Esto ha sido intencionado por el siguiente motivo: C++ est diseado para la programacin orientada a objetos (POO), y en ese paradigma, todas las entidades que podemos manejar son objetos. Los punteros en C++ sirven para sealar objetos, y tambin para manipularlos. Para entender qu es un puntero veremos primero cmo se almacenan los datos en un ordenador.

Memoria de un bit de ferrita. La memoria de un ordenador est compuesta por unidades bsicas llamadas bits. Cada bit slo puede tomar dos valores, normalmente denominados alto y bajo, 1 y 0. Pero trabajar con bits no es prctico, y por eso se agrupan. Cada grupo de 8 bits forma un byte u octeto. En realidad el microprocesador, y por lo tanto nuestro programa, slo puede manejar directamente bytes o grupos de dos o cuatro bytes. Para acceder a los bits hay que acceder antes a los bytes. Cada byte de la memoria de un ordenador tiene una direccin, llamada direccin de memoria. Los microprocesadores trabajan con una unidad bsica de informacin, a la que se denomina palabra (en ingls word). Dependiendo del tipo de microprocesador una palabra puede estar compuesta por uno, dos, cuatro, ocho o diecisis bytes. Hablaremos en estos casos de plataformas de 8, 16, 32, 64 128 bits. Se habla indistintamente de direcciones de memoria, aunque las palabras sean de distinta longitud. Cada direccin de memoria contiene siempre un byte. Lo que suceder cuando las palabras sean, por ejemplo, de 32 bits es que accederemos a posiciones de memoria que sern mltiplos de 4. Por otra parte, la mayor parte de los objetos que usamos en nuestros programas no caben en una direccin de memoria. La solucin utilizada para manejar objetos que ocupen ms de un byte es usar posiciones de memoria correlativas. De este modo, la direccin de un

objeto es la direccin de memoria de la primera posicin que contiene ese objeto. Dicho de otro modo, si para almacenar un objeto se precisan cuatro bytes, y la direccin de memoria de la primera posicin es n, el objeto ocupar las posiciones desde n a n+3, y la direccin del objeto ser, tambin, n. Todo esto sucede en el interior de la mquina, y nos importa relativamente poco. Pero podemos saber qu tipo de plataforma estamos usando averiguando el tamao del tipo int, y para ello hay que usar el operador sizeof, por ejemplo:
cout << "Plataforma de " << 8*sizeof(int) << " bits";

Ahora veamos cmo funcionan los punteros. Un puntero es un tipo especial de objeto que contiene, ni ms ni menos que, la direccin de memoria de un objeto. Por supuesto, almacenada a partir de esa direccin de memoria puede haber cualquier clase de objeto: un char, un int, un float, un array, una estructura, una funcin u otro puntero. Seremos nosotros los responsables de decidir ese contenido, al declarar el puntero. De hecho, podramos decir que existen tantos tipos diferentes de punteros como tipos de objetos puedan ser referenciados mediante punteros. Si tenemos esto en cuenta, los punteros que apunten a tipos de objetos distintos, sern tipos diferentes. Por ejemplo, no podemos asignar a un puntero a char el valor de un puntero a int. Intentemos ver con mayor claridad el funcionamiento de los punteros. Podemos considerar la memoria del ordenador como un gran array, de modo que podemos acceder a cada celda de memoria a travs de un ndice. Podemos considerar que la primera posicin del array es la 0 celda[0].

Indice <-> puntero Si usamos una variable para almacenar el ndice, por ejemplo, indice=0, entonces celda[0] == celda[indice]. Finalmente, si prescindimos de la notacin de los arrays, podemos ver que el ndice se comporta exactamente igual que un puntero. El puntero indice podra tener por ejemplo, el valor 3, en ese caso, el valor apuntado por

indice tendra el valor 'val3'. Las celdas de memoria tienen una existencia fsica, es decir son algo real y existirn siempre, independientemente del valor del puntero. Existirn incluso si no existe el puntero. De forma recproca, la existencia o no existencia de un puntero no implica la existencia o la inexistencia del objeto. De la misma forma que el hecho de no sealar a un rbol, no implica la inexistencia del rbol. Algo ms oscuro es si tenemos un puntero para rboles, que no est sealando a un rbol. Un puntero de ese tipo no tendra uso si estamos en medio del mar: tener ese puntero no crea rboles de forma automtica cuando sealemos con l. Es un puntero, no una varita mgica. :-D Del mismo modo, el valor de la direccin que contiene un puntero no implica que esa direccin sea vlida, en el sentido de que no tiene por qu contener la direccin de un objeto del tipo especificado por el puntero. Supongamos que tenemos un mapa en la pared, y supongamos tambin que existen diferentes tipos de punteros lser para sealar diferentes tipos de puntos en el mapa (ya s que esto suena raro, pero usemos la imaginacin). Creamos un puntero para sealar ciudades. Nada ms crearlo (o encenderlo), el puntero sealar a cualquier sitio, podra sealar incluso a un punto fuera del mapa. En general, daremos por sentado que una vez creado, el puntero no tiene por qu apuntar a una ciudad, y aunque apunte al mapa, podra estar sealando a un mar o a un ro. Con los punteros en C++ ocurre lo mismo. El valor inicial del puntero, cuando se declara, podra estar fuera del mapa, es decir, contener direcciones de memoria que no existen. Pero, incluso sealando a un punto de la memoria, es muy probable que no seale a un objeto del tipo adecuado. Debemos considerar esto como el caso ms probable, y no usar jams un puntero que no haya sido inicializado correctamente. Dentro del array de celdas de memoria existirn zonas que contendrn programas y datos, tanto del usuario como del propio sistema operativo o de otros programas, el sistema operativo se encarga de gestionar esa memoria, prohibiendo o protegiendo determinadas zonas. Pero el propio puntero, como objeto que es, tambin se almacenar en memoria, y por lo tanto, tambin tendr una direccin de memoria. Cuando declaramos un puntero estaremos reservando la memoria necesaria para almacenarlo, aunque, como pasa con el resto del los objetos, el contenido de esa memoria contendr basura. En principio, debemos asignar a un puntero, o bien la direccin de un objeto existente, o bien la de uno creado explcitamente durante la ejecucin del programa o un valor conocido que indique que no seala a ningn objeto, es decir el valor 0. El sistema operativo, cuanto ms avanzado es, mejor suele controlar la memoria. Ese control se traduce en impedir el acceso a determinadas direcciones reservadas por el sistema.

Declaracin de punteros
^ Los punteros se declaran igual que el resto de los objetos, pero precediendo el identificador con un asterisco (*).

Sintaxis:
<tipo> *<identificador>;

Ejemplos:
int *pEntero; char *pCaracter; struct stPunto *pPunto;

Los punteros slo pueden apuntar a objetos de un tipo determinado, en el ejemplo, pEntero slo puede apuntar a un objeto de tipo int. La forma:
<tipo>* <identificador>;

con el (*) junto al tipo, en lugar de junto al identificador del objeto, tambin est permitida. De hecho, tambin es legal la forma:
<tipo> * <identificador>;

Veamos algunos matices. Tomemos el primer ejemplo:


int *pEntero;

equivale a:
int* pEntero;

Otro detalle importante es que, aunque las tres formas de situar el asterisco en la declaracin son equivalentes, algunas de ellas pueden inducirnos a error, sobre todo si se declaran varios objetos en la misma lnea:
int* x, y;

En este caso, x es un puntero a int, pero y no es ms que un objeto de tipo int. Colocar el asterisco junto al tipo puede que indique ms claramente que estamos declarando un puntero, pero hay que tener en cuenta que slo afecta al primer objeto declarado, si quisiramos declarar ambos objetos como punteros a int tendremos que hacerlo de otro modo:
int* x, *y; // O: int *x, *y; // O: int* x; int* y;

Obtener punteros a objetos


^ Los punteros apuntan a objetos, por lo tanto, lo primero que tenemos que saber hacer con nuestros punteros es asignarles direcciones de memoria vlidas de objetos. Para averiguar la direccin de memoria de cualquier objeto usaremos el operador de direccin (&), que leeremos como "direccin de". Por supuesto, los tipos tienen que ser "compatibles", no podemos almacenar la direccin de un objeto de tipo char en un puntero de tipo int. Por ejemplo:
int A; int *pA;

pA = &A;

Segn este ejemplo, pA es un puntero a int que apunta a la direccin donde se almacena el valor del entero A.

Objeto apuntado por un puntero


^ La operacin contraria es obtener el objeto referenciado por un puntero, con el fin de manipularlo, ya sea modificando su valor u obteniendo el valor actual. Para manipular el objeto apuntado por un puntero usaremos el operador de indireccin, que es un asterisco (*). En C++ es muy habitual que el mismo smbolo se use para varias cosas diferentes, este es el caso del asterisco, que se usa como operador de multiplicacin, para la declaracin de punteros y, como vemos ahora, como operador de indireccin. Como operador de indireccin slo est permitido usarlo con punteros, y podemos leerlo como "objeto apuntado por". Por ejemplo:
int *pEntero; int x = 10; int y; pEntero = &y; *pEntero = x; // (1)

En (1) asignamos al objeto apuntado por pEntero en valor del objeto x. Como pEntero apunta al objeto y, esta sentencia equivale (segn la secuencia del programa), a asignar a y el valor de x.

Diferencia entre punteros y otros objetos


^ Debemos tener muy claro, en el ejemplo anterior, que pEntero es un objeto del tipo "puntero a int", pero que *pEntero NO es un objeto de tipo int, sino una expresin. Por qu decimos esto? Pues porque, como pasa con todos los objetos en C++, cuando se declaran slo se reserva espacio para almacenarlos, pero no se asigna ningn valor inicial, (recuerda que nuestro puntero para rboles no crea rbol cada vez que sealemos con l). El contenido del objeto permanecer sin cambios, de modo que el valor inicial del puntero ser aleatorio e indeterminado. Debemos suponer que contiene una direccin no vlida. Si pEntero apunta a un objeto de tipo int, *pEntero ser el contenido de ese objeto, pero no olvides que *pEntero es un operador aplicado a un objeto de tipo "puntero a int". Es decir, *pEntero es una expresin, no un objeto. Declarar un puntero no crear un objeto del tipo al que apunta. Por ejemplo: int *pEntero; no crea un objeto de tipo int en memoria. Lo que crea es un objeto que puede contener la direccin de memoria de un entero.

Podemos decir que existe fsicamente un objeto pEntero, y tambin que ese objeto puede (aunque esto no es siempre cierto) contener la direccin de un objeto de tipo int. Como todos los objetos, los punteros tambin contienen "basura" cuando son declarados. Es costumbre dar valores iniciales nulos a los punteros que no apuntan a ningn sitio concreto:
int *pEntero = 0; // Tambin podemos asignar el valor NULL char *pCaracter = 0;

NULL es una constante, que est definida como cero en varios ficheros de cabecera, como "cstdio" o "iostream", y normalmente vale 0L. Sin embargo, hay muchos textos que recomiendan usar el valor 0 para asignar a punteros nulos, al menos en C++.

Comentarios de los usuarios (8)


yemramirezca 2011-01-28 21:42:31 en cuanto a las declaraciones entiendo que int *punInt es un puntero a un int de la misma forma que: char *PunChar seria declarar un puntero a un char el que no me queda claro es : struct stPunto *pPunto; este es un puntero a un struct o un puntero a stPunto. como es la relacin en este caso????? Gracias!!! Salvador Pozo 2011-01-28 23:16:19 Yemramirezca: Todas las declaraciones de variables u objetos tienen la misma forma: <tipo> <identificador>; En el caso que indicas, est claro que el identificador es pPunto, por lo tanto, el resto es el tipo: struct stPunto. En C es obligatorio usar la palabra clave "struct" en las declaraciones de variables de un tipo que sea una estructura, pero en C++ no lo es, por lo tanto, en C++ estas dos declaraciones son equivalentes:
struct stPunto *pPunto; stPunto *pPunto;

Lo que lleva a errores con la declaracin de punteros es el asterisco. En estos ejemplos lo ponemos junto al identificador, pero podemos ponerlo entre ambos o junto al tipo. En cualquier caso, el compilador lo interpreta como parte del identificador, est el asterisco

donde est. El equvoco se produce al declarar varias variables en la misma lnea:


int* x, y;

Recuerda, el compilador interpreta que el asterisco es parte del identificador (para entendernos), de modo que x es un puntero a un int, pero y es un int, no un puntero. Por eso preferimos la otra notacin:
int *x, y;

Hasta pronto. Annimo 2011-02-18 23:12:14 Buenas, tengo una pregunta:


int LongitudCadena(char *cadena) { int i=0; while(*cadena++) i++; return i; }

Esta es mi funcion LongitudCadena, que funciona bien siempre que no haya espacios. Porque motivo si pongo la cadena "Hola que tal" me cuenta como si fuera "Hola"? Si pongo cadenas sin espacios me calcula la longitud perfectamente. Un saludo. PD: Grandisima esta web! Steven 2011-02-18 23:47:13 Hola Annimo, No veo que haya ningn problema con esta funcin acerca de si la cadena contiene espacios o no. Lo que s creo que est ocurriendo es que si lees una cadena desde el teclado con 'cin >>' en la que el usuario introduce espacios, la cadena slo contiene los primeros caracteres hasta el primer espacio. Por ejemplo,
char szCadena[40]; cin >> szCadena;

Si el usuario escribe: "hola a todos", la cadena 'szCadena' contendr: "hola". Esto es porque 'cin >>' se comporta de esta manera. En cuanto lea un espacio blanco: ' ' (espacio), '\t' (tabulador), '\r' (retorno), '\n' (nueva lnea), '\v' (tabulador vertical), y '\f' (salto de pgina). Si quieres leer y guardar espacios blancos, usa la funcin miembro 'getline()'. Esto es,
cin.getline( szCadena, 40 );

Leer hasta 39 caracteres, el carcter de "fin-de-fichero" (EOF), o hasta el carcter '\n' (por defecto), el cual no ser guardado en la cadena. Espero que esto te sirva.

Steven Mario 2011-02-19 01:16:16 Eso es exactamente lo que pasava Steve!! Muchisimas gracias. Por cierto, existe alguna funcion (hablando con propiedad seria un metodo?) de cin que lea la entrada de teclado y la almacene en un array sin indicarle el numero de caracteres? Es decir, algo como
cin.getline(cadena)

... pero que funcione! scanf? yo aprendi en la universidad a leer de teclado todo usando scanf (antes nunca habia usado cin o cout). Steven 2011-02-19 02:38:11 Hola Mario, En C++, solemos hablar de "funciones miembro", pero en otros lenguajes y ya casi en general se habla de "mtodos". Elijas cual elijas, te entendemos :) Para una cadena de caracteres como un array, no existe ninguna funcin miembro que no acepte la cantidad mxima de caracteres. De hecho, ni aconsejara usar una, si existiese. En el caso de ANSI C, s existe 'gets()', pero su uso no es recomendado, incluso por los propios diseadores de ANSI C. S existe uan forma de guardar todos los posibles caracteres, hasta el carcter de fin-delnea, en una cadena. Usando la funcin global 'getline()', podemos pasar una cadena de la clase 'string'; ambas entidades estn definidas en <string>. Por ejemplo,
string sCadena; getline( cin, sCadena );

La clase 'string' representa una cadena dinmica de caracteres. Esto significa que la cadena aumenta de tamao automticamente cuando es necesario. Tericamente, no tiene lmite de la cantidad de caracteres que puede almacenar. Espero que esto te sirva. Steven yemramirezca 2011-03-04 23:23:19 HOla!! Debo hacer un programa que lea una linea de caracteres y diga cuantas palabras repetidas hay.... me dijeron que usara punteros a cadena pero no me funciona... me podrian guiar por favor????

edwin 2011-03-30 20:46:27 1. Comente sobre cada instruccin del programa siguiente: #include <stdio.h> void main() { int i, j; i=90; j=180; pt=&j; *pt=55; pt=&j; *pt=66; } 2. Comente sobre cada instruccin del programa siguiente: #include <stdio.h> void main() { int a, *pt; a=7; pt=&a; cout << La direccion de a es: << &a; cout << El contenido de pt es: << pt; cout << El valor de a es: << a; cout << El valor de pt es: << *pt; cout << El valor de &*pt es: << &*pt; cout << El valor de *&pt es: << *&pt; } 3. Comente sobre cada instruccin del programa siguiente: #include <stdio.h>

void main() { int a, *pt; a=17; pt=&a; cout << *&pt; cout << &*pt; cout << &**&pt; cout << &*&*pt; cout << *&*&pt; cout << *&*&*&pt; cout << &*&*&*pt; } 4. Considere el programa siguiente: #include <sodio.h> void main() { int i; flota f; void *pt; /* modificacion del valor de i */ pt=&i; *((int *)pt)=12; /* modificacion del valor de f */ pt=&f; *((float *)pt)=165.33; cout<<El valor de i es <<i; cout<<El valor de f es <<f; }

a.Cmo se debe modificar el programa si se declara pt como un puntero sobre objetos de tipo int? b. Cmo se debe modificar el programa si se declara pt como un puntero sobre objetos de tipo float? c. Como se debe modificar el programa si se declara pt como un puntero sobre objetos de tipo char? 5. De acuerdo a la declaracin de las variables, de qu tipo son cada una? int* a,b; a. a puntero, b puntero b. a puntero, b entero c. a entero, b puntero d. a entero, b entero 6. Considerando las siguientes declaraciones y sentencias: int array[]={1,2,3,4,5,6}; int *puntero; puntero = array; puntero++; *puntero=*puntero+6; puntero=puntero+3; puntero=puntero-puntero[-2]; int x=puntero-array; a) Cul es el valor de x? b) Cul es el valor de array[1]? 7. Considerando la siguiente declaracin: struct A { struct { int x; int y; } campoB; } *estructuraA; Cmo se referenciara el campo x de la estructura A?

41 Punteros a miembros de clases o estructuras


^ C++ permite declarar punteros a miembros de clases, estructuras y uniones. Aunque en el caso de las clases, los miembros deben ser pblicos para que pueda accederse a ellos. La sintaxis para la declaracin de un puntero a un miembro es la siguiente:
<tipo> <clase|estructura|unin>::*<identificador>;

De este modo se declara un puntero "identificador" a un miembro de tipo "tipo" de la clase, estructura o unin especificada. Ejemplos:
struct int int int }; punto3D { x; y; z;

class registro { public: registro(); float v; float w;

};

int punto3D::*coordenada; // (1) float registro::*valor; // (2)

El primer ejemplo declara un puntero "coordenada" a un miembro de tipo int de la estructura "punto3D". El segundo declara un puntero "valor" a un miembro pblico de la clase "registro".

Asignacin de valores a punteros a miembro


^ Una vez declarado un puntero, debemos asignarle la direccin de un miembro del tipo adecuado de la clase, estructura o unin. Podremos asignarle la direccin de cualquiera de los miembros del tipo adecuado. La sintaxis es:
<identificador> = &<clase|estructura|unin>::<campo>;

En el ejemplo anterior, seran vlidas las siguientes asignaciones:


coordenada = &punto3D::x; coordenada = &punto3D::y; coordenada = &punto3D::z; valor = &registro::v; valor = &registro::w;

Operadores .* y ->*
^ Ahora bien, ya sabemos cmo declarar punteros a miembros, pero no cmo trabajar con ellos. C++ dispone de dos operadores para trabajar con punteros a miembros: .* y ->*. A lo

mejor los echabas de menos :-). Se trata de dos variantes del mismo operador, uno para objetos y otro para punteros:
<objeto>.*<puntero> <puntero_a_objeto>->*<puntero>

La primera forma se usa cuando tratamos de acceder a un miembro de un objeto. La segunda cuando lo hacemos a travs de un puntero a un objeto. Veamos un ejemplo completo:
#include <iostream> using namespace std; class clase { public: clase(int a, int b) : x(a), y(b) {} public: int x; int y;

};

int main() { clase uno(6,10); clase *dos = new clase(88,99); int clase::*puntero; puntero = &clase::x; cout << uno.*puntero << endl; cout << dos->*puntero << endl; puntero = &clase::y; cout << uno.*puntero << endl; cout << dos->*puntero << endl; delete dos; cin.get(); return 0;

La utilidad prctica no es probable que se presente frecuentemente, y casi nunca con clases, ya que no es corriente declarar miembros pblicos. Sin embargo nos ofrece algunas posibilidades interesantes a la hora de recorrer miembros concretos de arrays de estructuras, aplicando la misma funcin o expresin a cada uno. Tambin debemos recordar que es posible declarar punteros a funciones, y las funciones miembros de clases no son una excepcin. En ese caso s es corriente que existan funciones pblicas.
#include <iostream> using namespace std; class clase { public: clase(int a, int b) : x(a), y(b) {} int funcion(int a) { if(0 == a) return x; else return y; }

private: int x; int y; }; int main() { clase uno(6,10); clase *dos = new clase(88,99); int (clase::*pfun)(int); pfun = &clase::funcion; cout cout cout cout << << << << (uno.*pfun)(0) << endl; (uno.*pfun)(1) << endl; (dos->*pfun)(0) << endl; (dos->*pfun)(1) << endl;

delete dos; cin.get(); return 0;

Para ejecutar una funcin desde un puntero a miembro hay que usar los parntesis, ya que el operador de llamada a funcin "()" tiene mayor prioridad que los operadores ".*" y ">*".

Você também pode gostar