Sunteți pe pagina 1din 130

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++ 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.- Directivas del preprocesador


El preprocesador es un programa que viene incluido en los compiladores de C++, y se encarga de ejecutar ciertas rdenes llamadas DIRECTIVAS DEL PREPROCESADOR. Estas directivas empiezan con el carcter # ( Numeral ). Al inicio de la compilacin, el compilador invoca al preprocesador, quien acta de acuerdo a la directiva correspondiente. Las directivas del preprocesador pueden clasificarse en tres grupos principales : 1.4.1.- Inclusin de Archivos. 1.4.2.- Reemplazamiento de Cadenas y Procesamiento de Macros. 1.4.3.- Compilacin Condicional.

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.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.- Identificadores
Los nombres que son utilizados para referenciar variables, funciones, etiquetas y dems objetos que se manejan con el Lenguaje C++, son referidos en forma genrica como identificadores. 1.6.1.- Identificadores Creados por el Usuario. 1.6.2.- Palabras Reservadas.

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

.7.1.- Funciones declaradas en stdio.h


En esta forma, se requiere la inclusin de la siguiente lnea al principio del programa fuente:
#include &ltstdio.h>

En el archivo stdio.h se encuentran definidos, entre otros, los prototipos de las funciones printf(), scanf(), gets() y puts() adems de las macros getc(x) y putc(). A continuacin analizaremos cada una de estas funciones y macros: 1.7.1.1.- La Funcin printf() 1.7.1.2.- La Funcin scanf() 1.7.1.3.- Las Funciones gets() y puts() 1.7.1.4.- Las Macros getc() y putc()

1.7.1.1.- La funcin printf()


Esta funcin es la mas utilizada en C para desplegar informacin en la pantalla, ms no en el lenguaje C++ como veremos ms adelante. El formato general para la funcin printf() es:
printf(<cadena_formato> , <elem> , <elem> , ... );

donde:
cadena_formato es una cadena de caracteres encerrada entre comillas dobles. Esta cadena es enviada a la pantalla ( la salida estndar ) despus de haber sustituidos los elementos listados despus de la cadena.

La sustitucin de los elementos se hace de acuerdo a los comandos de formato incluidos en cadena_formato, los cuales empiezan con un smbolo de porciento ( % ) . En la tabla 1.2 se muestran los comandos de formato ms usuales:

Comando Sirve para: %d entero %u entero sin signo %ld entero largo %p valor de apuntador %f nmero de punto flotante %e nmero de punto flotante en valor exponencial %c carcter %s cadena de caracteres %x entero en formato hexadecimal %o entero en formato octal Tabla 1.2.- Comandos de formato.

A fin de poder mostrar algunos ejemplos de aplicacin de la funcin printf(), se presenta aqu la tabla 1.3 que contiene los tipos de datos disponibles en el compilador de Borland C++ 3.1.

Tipo Tamao (bits) Precisin char 8 . unsigned char 8 . enum 16 . int 16 . short int 16 . unsigned int 16 .

Rango de valores: -128 a 127 0 a 255 -32,768 a 32,767 -32,768 a 32,767 -32,768 a 32,767 0 a 65,535

long unsigned long float double long double void . near pointer far pointer

32 32 32 64 80 16 16

. .

-2,147'483,648 a 2,147'483,647 0 a 4,294'967,295 7 dgitos 3.4E-38 a 3.4E+38 15 digitos 1.7E-308 a 1.7E+308 19 digitos 3.4E-4932 a 3.4E+3932 . . . Direcciones de memoria . Direcciones de memoria

Tabla 1.3.- Tipos de datos, tamaos y rangos en Borland C++ 3. Los listados 1.2 a 1.8 muestran ejemplos de utilizacin de formatos por medio de la funcin

// IMPRIME ENTEROS EN "DECIMAL" #include <stdio.h> int main() { int y=12345; printf("%13d\n",y); return 0;

Listado 1.2
// IMPRIME ENTEROS EN OCTAL #include <stdio.h> int main() { int y=12345; printf("y = %13d EN DECIMAL\n",y); printf("y = %13o EN OCTAL\n",y); return 0; }

Listado 1.3
// IMPRIME ENTEROS EN HEXADECIMAL #include <stdio.h> int main() { int y=12345; printf("y = %13d EN DECIMAL\n",y);

printf("y = %13x EN HEXADECIMAL\n",y); return 0;

Listado 1.4
// IMPRIME ENTEROS SIN SIGNO #include <stdio.h> int main() { int y= -12345; printf("y = %13d ENTERO CON SIGNO\n",y); printf("y = %13u ENTERO SIN SIGNO\n",y); return 0; }

Listado 1.5
// IMPRIME NUMEROS CON PUNTO FLOTANTE #include <stdio.h> int main() { float x=123.45; double y=678.90; printf("x = %12.2f\ny = %12.1f\n",x,y); return 0; }

Listado 1.6
// IMPRIME CARACTERES #include <stdio.h> int main() { char a='X', b='Y', c='Z' ; printf("%c%c%c\n",a,b,c); return 0; }

Listado 1.7
// IMPRIME CADENAS DE CARACTERES #include <stdio.h> int main() { /* Formato: %[-a.b]s donde: - JUSTIFICA A LA IZQUIERDA a NUMERO DE ESPACIOS DISPONIBLES PARA DESPLEGAR b NUMERO MAXIMO DE CARACTERES QUE PUEDEN DESPLEGARSE */

printf("\n%10s","DESPLEGADO DE CADENAS EN LENGUAJE C++"); printf("\n%-10s","DESPLEGADO DE CADENAS EN LENGUAJE C++"); printf("\n%25.10s","DESPLEGADO DE CADENAS EN LENGUAJE C++") printf("\n%-25.10s","DESPLEGADO DE CADENAS EN LENGUAJE C++"; printf("\n%.10s","DESPLEGADO DE CADENAS EN LENGUAJE C++"); return 0; }

Listado 1.8
Para desplegar el smbolo de porciento puede escribirse en cadena_formato : % .

Por ejemplo, el segmento de cdigo:

...........int utilidad;

...........utilidad = 30;

...........printf("La utilidad es del %2d %\n", utilidad);

desplegar :
La utilidad es del 30 %

1.7.1.2.- La funcin scanf()


Esta funcin toma la entrada, normalmente del teclado, y la almacena en variables previamente declaradas. El formato para la funcin scanf() es :
scanf( <cadena_formato> , <elem> , <elem> , ... ) ;

Los significados de <cadena_formato> y <elem> son los mismos que en el caso de la funcin printf() . La funcin scanf() utiliza la mayora de los comandos mostrados en la tabla 1.2 La funcin scanf() debe utilizarse con cautela cuando se van a introducir valores numricos y de cadenas de caracteres en una sola instruccin.

1.7.1.4.- Las macros getc() y putc()


La macro getc(c) toma un caracter desde el teclado y lo almacena en la variable c. Por su parte, putc() despliega un caracter en la pantalla. Estas macros estn definidas en el archivo stdio.h .

1.7.2.- Funciones declaradas en conio.h.


La consola es un concepto heredado de los ambientes multiusuario, y est formada por el teclado y el monitor. En las implementaciones de Borland C++ existe una serie de funciones que manejan la entrada/salida por la consola, entre otras:

clrscr(), que limpia la pantalla y posiciona el cursor en la esquina superior izquierda de


la ventana actual.

gotoxy(a,b), posiciona el cursor en la posicin establecida por la columna a y el


rengln b.

window(a,b,x,y), establece la ventana actual. Los valores a,b pertenecen a la columna y


el rengln de la esquina superior izquierda; los valores x,y corresponden a la columna y el rengln de la esquina inferior derecha de la nueva ventana.

getch(), toma un carcter desde el teclado y no lo despliega en la pantalla. getche(), toma un carcter desde el teclado y lo despliega en la pantalla.
Para poder utilizar las funciones relacionadas con la consola, es necesario incluir la linea: #include <conio.h> Donde conio.h es el archivo de cabecera donde se encuentran declarados los prototipos de las funciones que manejan la entrada/salida estndar por la consola.

1.7.3.- Objetos declarados en iostream.h


C++ provee una nueva forma para manejar la entrada/salida, proporcionando una sintaxis ms sencilla, elegante e intuitiva que la de stdio.h , adems de un mecanismo ms eficiente y flexible. La palabra iostream tiene su origen en las palabras: input output stream , por lo que puede traducirse como flujo de entrada/salida. El flujo de entrada/salida es utilizado para enviar datos desde una fuente ( productor ) a un destino ( consumidor ) . En esta forma se tiene una vasta jerarqua de clases para el manejo de entrada/salida para archivos y dispositivos. En el archivo iostream.h se encuentran definidos los objetos cin y cout.

El objeto cin utiliza al operador sobrecargado >> para tomar datos desde la entrada estndar( habitualmente el teclado ). El objeto cout utiliza al operador sobrecargado << para enviar datos hacia la salida estndar( normalmente la pantalla ). Veamos como se utilizan estos elementos en un pequeo ejemplo:
#include <iostream.h> int main() { char nombre[30]; cout << "CUAL ES TU NOMBRE ? " ; cin>> nombre ; cout << "\n" ; cout << "!! HOLA , " << nombre << " !!" ; return 0 ; }

Listado 1.9.- Ejemplo de utilizacin de cin y cout Tambin es posible especificar el ancho de un campo por medio del manipulador setw() , que se encuentra declarado en iomanip.h ; as como los manipuladores window(), clrscr() y gotoxy() declarados en constrea.h, como se muestra en el listado 1.10.
#include <iostream.h> #include <iomanip.h> #include <constrea.h> int main() { char nombre[10] ; int numero = 150 ; float sueldo ; constream ventana0, ventana1, ventana2 ; ventana0.window(1,1,80,25); ventana1.window(10,05,20,05); ventana2.window(10,07,40,07); ventana0.clrscr(); ventana1.clrscr(); cout << "NUMERO: " << setw(3) << numero; cout << " NOMBRE : " ; cin.getline(nombre,10); ventana2.clrscr(); cout << "\nSUELDO : $ " ; cin>> sueldo ; return 0; }

Listado 1.10.- Uso de manipuladores.

2.- Constantes, variables, operadores


En esta unidad se trata el manejo de los objetos donde se almacenan los datos, as como las operaciones que pueden realizarse con los valores almacenados. En este contexto, llamaremos objeto a una regin identificable de memoria que puede almacenar un valor fijo o variable. No debe confundirse el significado que se d aqu al vocablo objeto con el que tiene en la Programacin Orientada a Objetos.

2.1.- Constantes
Podemos definir a una constante como una localidad de memoria (objeto), la cual tiene un nombre y un tipo de dato asociados, adems de un valor que permanece fijo durante el tiempo de ejecucin. En la unidad 1 se estudi la manera de definir constantes por medio de la directiva #define . Aqu definiremos a las constantes utilizando el modificador const, usando del formato mostrado a continuacin:
const <tipo> <identificador> = <valor>

Ejemplo: const int iva = 10 ; Los valores para las constantes, permitidos por Borland C++, se clasifican en cuatro clases:

I).- Enteros, que pueden escribirse en formato:


I.a).- Decimal, con valores de 0 a 4,294'967,295. Ejemplo: const int smin = 15300 ;

I.b).- Octal, que deben empezar con un 0 (cero). Ejemplo: const int lim = 030 ; (decimal 27)

I.b).- Hexadecimal, que empiezan con 0X 0x (cero x) Ejemplo: const int mem = 0xFFFF ;

II).- Caracteres, compuestos de uno o ms caracteres encerrados entre comillas sencillas (apstrofes).
Ejemplo: const char inicial = 'A' ;

Para manejar los caracteres de control se utilizan las secuencias de escape que empiezan con el carcter de diagonal hacia atrs (\) seguida de un carcter. Si el carcter que sigue a la diagonal es alguno de los caracteres mostrados en la tabla 2.1, se obtendr el efecto explicado en ella; pero si es cualquiera de los dems caracteres del Cdigo ASCII, se visualizar el carcter tal cual. Secuencia Caracter Efecto \a BEL Sonido de la bocina \b BS Retroceso \f FF Alimentacin de forma \n LF Nueva lnea \r CR Retorno de carro \t HT Tabulacin horizontal \v VT Tabulacin vertical \\ \ Diagonal hacia atrs \' ' Comilla sencilla \" " Comilla doble \? ? Interrogacin \O O Cualquiera O=Nmero octal de hasta tres dgitos \x H Cualquiera H=Nmero hexadecimal Tabla 2.1.- Secuencias de escape. Borland C++ permite el uso de dos caracteres juntos como valores de caracteres.
Por ejemplo: 'AB' , '\n\t' , '\007\007'

III).- Punto flotante, cualquiera con punto decimal con formato exponencial.
Ejemplos: 3.135 , 0. , .0 , 34e3 , -3.4 , .075E12

IV).- Enumeracin, son identificadores definidos en declaraciones de tipo enum.


Ejemplo: enum frutas { pera, uva, manzana, fresa } ;

donde: pera, uva, manzana y fresa son constantes de enumeracin y tienen los siguientes valores:

pera uva manzana fresa

= = = =

0 1 2 3

Pueden asignarse valores explcitos como en:


enum frutas {pera, uva , manzana = 10 , fresa = uva+5} ;

y entonces :
pera uva manzana fresa = 0 = 1 = 10 = 6

2.2.- Variables
Una variable es un espacio de memoria que tiene un nombre y un tipo de dato asociados, adems de un valor que puede cambiarse durante el tiempo de ejecucin. En el Lenguaje C++ puede declararse una variable en cualquier lugar del programa, con la nica restriccin de que la declaracin preceda a la primera instruccin donde se utiliza tal variable. El formato para la declaracin de una variable o grupo de variables es el siguiente:
[, var2, ... ] ; Ejemplos: int algo, otro ; int suma = 0 , inicial = 1 ; float desc , acum = 0 ; char prim = 'A' , seg ;

2.3.- Ambito
El mbito es una zona de programa donde puede utilizarse un identificador para accesar su objeto . El mbito va a depender, bsicamente, del lugar del programa en que aparece la declaracin. Bajo este enfoque, se tienen las siguientes clases de mbito: Ambito de BLOQUE. En el Lenguaje C++, se generaliza el concepto de mbito local, a travs de la introduccin del concepto de bloque. Un bloque est formado por las instrucciones delimitadas por un par de llaves { }. Es posible anidar una serie de bloques, y pueden declararse variables dentro de cada bloque. Si, en diferentes bloques, existen variables con el mismo identificador, todas sern consideradas como variables diferentes. As que puede tenerse:
.............. .............. int x=5; { int x=10; { int x=20; { int x=30; printf("x=%d",x); } printf("%d",x); } printf("%d",x); } printf("%d",x); ............. .............

// x del bloque 1 // x del bloque 2 // x del bloque 3 // x del bloque 4 // x=30 // x=20 // x=10 // x=5

Debe tenerse cuidado al utilizar identificadores iguales para diferentes variables. Lo ms recomendable es no utilizar los mismos identificadores y no intentar manejar variables en un mbito diferente al que pertenecen. Ambito de FUNCION. En esta clase slo se encuentran los identificadores que se utilizan como etiquetas cuando se tienen instrucciones goto.

El identificador de una etiqueta debe ser nico dentro de una funcin, y no puede utilizarse la instruccin goto para saltar de una funcin a otra. Ambito de PROTOTIPO DE FUNCION. El mbito de los identificadores utilizados dentro de los parntesis en la declaracin de prototipo de una funcin, finaliza con la declaracin del prototipo. Ambito de ARCHIVO. Las variables declaradas fuera de cualquier bloque en un archivo son consideradas como variables globales, y el mbito de sus identificadores abarca todos los bloques contenidos en el archivo en cuestin; por lo que: una variable con ste mbito puede ser utilizada en las funciones del archivo que estn definidas despus del punto donde se declar dicha variable.

2.4.- Duracin
La duracin define el periodo de tiempo durante el cual los identificadores declarados tienen objetos asignados en memoria. Las variables, por ejemplo, tienen memoria asignada durante el tiempo de ejecucin.

2.4.1.- Duracin esttica


Los objetos con duracin esttica estn localizados en memoria durante todo el tiempo de ejecucin del programa. Las funciones, las variables con mbito de archivo y las variables con especificadores de clase de almacenamiento static extern, tienen duracin esttica. Los objetos con duracin esttica son inicializados a cero , en ausencia de un valor inicial explcito.

2.4.2.- Duracin local


Los objetos de duracin local, conocidos tambin como objetos automticos, son creados en la pila de la memoria RAM en los registros del microprocesador. La memoria asignada a stos objetos es liberada cuando finaliza la ejecucin del bloque donde fueron creados. Los objetos de duracin local deben utilizarse siempre en un mbito local de funcin. Cuando se utiliza el especificador de clase de almacenamiento register, se implica el uso del especificador de clase de almacenamiento auto.

2.4.3.- Duracin dinmica


Los objetos de duracin dinmica se crean y se destruyen, por invocaciones a funciones especficas, durante el tiempo de ejecucin de un programa. El almacenamiento de estos objetos se d en el rea de reserva de la memoria RAM llamado montculo.

2.5.- Enlace
En la creacin de un programa ejecutable, primero se lleva a cabo la compilacin de diversas unidades de traslacin, las cuales se componen del cdigo fuente junto con los archivos incluidos. Posteriormente, el archivo objeto (.obj) , se enlaza con libreras preexistentes para obtener el archivo ejecutable (.exe). El enlace es el proceso que permite a cada instancia de un identificador asociarse correctamente con un objeto o funcin particular. Todos los identificadores tienen uno de los siguientes atributos de enlace, ntimamente relacionados con su mbito:

. ENLACE EXTERNO, . ENLACE INTERNO, . SIN ENLACE.

Estos atributos se determinan a travs del emplazamiento y los formatos de las declaraciones, junto con el uso ( implcito explcito ) de los especificadores de clase de almacenamiento static extern. Cada instancia de un identificador con enlace externo representa al mismo objeto

funcin a travs de todo el conjunto de archivos y libreras que componen el programa. Cada instancia de un identificador con enlace interno representa al mismo objeto funcin solamente dentro de un archivo. Los identificadores sin enlace representan entidades nicas. A continuacin se presentan las: REGLAS PARA LOS ENLACES INTERNO Y EXTERNO: 1. Cualquier identificador de objeto archivo que tenga mbito de archivo tendr enlace interno si su declaracin contiene el especificador static. Para C++, si el mismo identificador aparece con ambos tipos de enlace en el mismo archivo, el identificador tendr enlace externo. 2. Si la declaracin de un identificador de un objeto funcin contiene el especificador extern , el identificador tendr el enlace correspondiente a una declaracin con mbito de archivo. 3. Si una funcin se declara sin un especificador de clase de almacenamiento, su enlace est determinado como si se hubiera utilizado el especificador extern. 4. Si un identificador de objeto con mbito de archivo se declara sin especificador de clase de almacenamiento, el identificador tendr enlace externo. Los siguientes identificadores no tienen atributo de enlace:

a).- Cualquier identificador declarado para ser algo diferente de un objeto funcin ( p.ej. un identificador typedef ). b).- Los parmetros de las funciones. c).- Los identificadores de mbito de bloque para objetos declarados sin el el especificador extern.

Para aclarar lo relacionado con el especificador extern, revisemos los listados 2.1 y 2.2.
// EXTERN1.CPP int edad; char nombre[31]; // DEFINICION de variables globales

Listado 2.1.- EXTERN1.CPP, que define las variables globales.


// EXTERN2.CPP #include <iostream.h> // DECLARACION de variables globales que: extern int edad; // se encuentran en otro archivo

extern char nombre[]; void main() { cout << "\nCUAL ES TU NOMBRE ? " ; cin>> nombre ; cout << "\nCUANTOS AOS TIENES, " << nombre << " ?" ; cin>> edad ; cout << "\n\n" ; cout << " TE FALTAN " << 100-edad ; cout << " PARA LLEGAR A LOS CIEN, " << nombre << "\n" ; }

Listado 2.1.- EXTERN2.CPP, que declara y utiliza variables globales definidas en otro archivo. En el archivo EXTERN1.CPP se definen las variables globales edad y nombre. Posteriormente se compila este archivo para obtener el archivo EXTERN1.OBJ. En el archivo EXTERN2.CPP se declaran las variable edad y nombre utilizando el modificador extern. Finalmente, se crea un proyecto al que puede llamarse EXTERN.PRJ, en el cual se incluyen los archivos EXTERN1.OBJ y EXTERN2.CPP; se compila el proyecto y se obtiene el archivo ejecutable EXTERN.EXE

2.6.1.- Operadores arimticos


Los operadores aritmticos se aplican sobre objetos con valores numricos, como se muestra en la tabla 2.2 . Sean: X = 20 , Y = 30 , A = 100.0 , B = 200.0 Operador Operacin Ejemplo Resultado + Adicin Z=X+Y Z=50 Sustraccin Z=Y-X Z=10 * Multiplicacin Z=X*Y Z=600 / Divisin Z=Y/X Z=1.5 % Mdulo Z=Y%X Z=10 ++ Incremento X++ X=21 Decremento XX=19 Tabla 2.2.- Operadores aritmticos.

2.6.2.- Operadores relacionales

Los operadores relacionales se usan para comparar los valores que resultan de reducir expresiones. Los resultados coincidirn con los valores de verdad: FALSO igual a CERO VERDADERO diferente de CERO Los operadores relacionales en C++ son :
> = <= == != Mayor que Mayor igual que Menor igual que Igual que Diferente que ( No igual que )

2.6.3.- Operadores lgicos


Los operadores lgicos se aplican sobre los enunciados que resultan de las operaciones relacionales, y el resultado siempre ser un valor de verdad. Los operadores lgicos son:
&& || ! Y O NO ( Conjuncin ) ( Disyuncin ) ( Negacin )

2.6.4.- Operadores entre bits


Con estos operadores se puede realizar la comprobacin, colocacin desplazamiento de los bits actuales de una variable de los tipos int y char. Los operadores entre bits son:
& | ^ ~ Y O O ( Conjuncin ) ( Disyuncin ) ( Disyuncin Exclusiva XOR )

( Complemento a uno NOT )

>>

( Desplazamiento a la DERECHA )

<< ( Desplazamiento a la IZQUIERDA )

A continuacin se presentan ejemplos de algunas operaciones entre bits: Ejemplo 2.6.1: Supongamos que se quiere cambiar el bit de paridad ( el de la extrema izquierda ) de uno a cero. Para esto puede utilizarse la operacin de conjuncin entre bits. Si tenemos la declaracin:
char indice = 81 ;

y, suponiendo que se quiere cambiar el bit de paridad de 1 a 0 , la instruccin en C++ se escribira : ........................indice & 127 ; La operacin realizada a mano es : .......................11010001 <------ indice operador .. & 01111111 <------ 127 en binario ......................01010001 <------ resultado En este caso se utiliz el nmero 127 porque es el nico nmero entero que puede escribirse en un octeto ( byte ) y que tiene los siete primeros bits con valor 1 . Ejemplo 2.6.2 Se requiere que en el nmero 112 tengan valor 1 los bits que correspondan con los del nmero 7 que tengan valor 1. En este caso, la operacin a utilizar es la disyuncin entre bits, quedando las instrucciones en la siguiente forma:
char masc ; masc = 112 | 7 ;

La operacin manual tomara la siguiente forma: ..................... 01110000 <------ 112 en binario ..operador> | 00000111 <------ 7 en binario .................... 01110111 <------ resultado

Ejemplo 2.6.3 La disyuncin exclusiva sirve para poner a uno los bits del primer operando cuyos

correspondientes bits en el segundo operando sean distintos, como se ve a continuacin: ..............................char x ; ..............................x = 125 ^ 120 ; Manualmente se tendra: ...................01111101 <------- 125 en binario operador | 01111000 <------- 120 en binario .................00000101 <------- resultado Los operadores de desplazamiento mueven todos los bits, de una variable entera de carcter, hacia la izquierda hacia la derecha. A medida que los bits son desplazados hacia un extremo, los lugares desocupados del estremo contrario van siendo ocupados por ceros. Los unos que salen por los extremos no se pueden recuperar(no hay rotacin) .

2.6.5.- Operadores de asignacin


El estudio de los operadores de asignacin requiere del conocimiento de los conceptos de valor izquierdo ( lvalue ) y de valor derecho ( rvalue ). Un valor izquierdo es una expresin que designa un objeto. Las expresiones utilizadas como valor izquierdo corresponden a objetos cuyos valores pueden cambiar durante la ejecucin de un programa. Generalmente, en la expresin correspondiente a un valor izquierdo aparece el identificador de una variable. Cuando el valor izquierdo se refiere a la direccin de una variable, la expresin puede constar de una combinacin de varios identificadores. Un valor derecho es una expresin formada por cualquier combinacin de objetos y operadores que pueda ser reducida a un valor. Los operadores de asignacin sirven para asignar un valor derecho a un valor izquierdo, y estn formados por la combinacin del operador de asignacin simple = con otro operador, como se muestra en la tabla 2.3 . Operador Significado Ejemplo: Equivale a: = Asignacin mltiple . += Suma asigna X+=Y X=X+Y -= Resta asigna X-=Y X=X-Y

*= /= %= <<= >>= &= ^= |=

Multiplicacin asigna X*=Y X=X*Y Divide asigna X/=Y X=X/Y Residuo asigna X%=Y X=X%Y Dezplaz. izq. asigna X<<Y X=X<<Y Dezplaz. der. asigna X>>Y X=X>>Y Conj. e/bits asigna X&=Y X=X&Y Disy. exclu. asigna X^=Y X=X^Y Disyuncin asigna X|=Y X=X|Y Tabla 2.3.- Operadores de asignacin.

Los operadores estudiados hasta aqu no son todos los que existen en el C++, sino los que a mi juicio son indispensables para iniciar el estudio del lenguaje. Si, posteriormente, es necesario utilizar algn operador no tratado en esta unidad, en ese momento se estudiarn las caractersticas particulares de tal operador.

3.- Instrucciones de control


En esta unidad estudiaremos las instrucciones que sirven para controlar el flujo de ejecucin de un programa en C++ . De acuerdo a las caractersticas de cada una, las clasificaremos en grupos de estructuras bsicas de: 3.1.- Instrucciones de secuencia. 3.2.- Instrucciones de seleccin. 3.3.- Instrucciones de iteracin. Las estructuras bsicas deben cumplir con la condicin bsica de la Programacin Estructurada de: slo una entrada, slo una salida.

3.1.- Secuencia
A este grupo pertenecen las instrucciones que estn formadas por una o varias expresiones simples colocadas una a continuacin de la otra. La sintaxis para las instrucciones estructurados en secuencia es la siguiente:
instruccion_1 ; instruccion_2 ; ......... instruccion_N ;

Su diagrama de flujo se presenta en la figura 3.1.

3.2.-Seleccin
A este grupo pertenecen aquellas instrucciones que sirven para que la ejecucin del programa tome una de varias opciones existentes en una ramificacin. En C++ se tienen las siguientes instruccins de seleccin: 3.2.1.- La instruccin if - else 3.2.2.- La instruccin switch

3.2.1.- La instruccin if - else


Esta instruccin permite elegir entre dos opciones de ejecucin, y su sintaxis es :
if( condicin ) bloque_1 [else] [bloque_2] donde: bloque_1 y bloque_2 pueden estar formados por uno ms instrucciones. else es opcional, y en caso de no existir, bloque_2 tampoco existir.

Al ejecutarse esta estructura, primero se evala la condicin. En caso de que, de esta evaluacin, resulte un valor de verdad verdadero, se ejecutarn las instrucciones que forman el bloque_1; en caso contrario (si el valor de verdad es falso), se ejecutarn las instrucciones del bloque_2. El diagrama de flujo para la estructura if-else se presenta en la figura 3.2.

Esta estructura puede anidarse para elgir entre un grupo de ms de dos opciones, tomando la siguiente forma:
if(condicin_1) bloque_1; else if(condicin_2) bloque_2; else if(condicin_3) bloque_3; ............ else bloque_N;

A continuacin se muestran algunos ejemplos de aplicacin de la instruccin if-else


#include <iostream.h> void main() { long ncontrol; cout << "NUMERO DE CONTROL: "; cin>> ncontrol; if(ncontrol<=0) cout << "NUMERO INVALIDO........."; else cout << "CORRECTO !!" << "\n\n\n";

Listado 3.1.- Uso de if-else


#include <iostream.h> #include <conio.h> void main() { int calif; clrscr(); cout << "CALIFICACION: "; cin>> calif; if(calif > 100) cout << "ERROR: CALIFICACION DEMASIADO ALTA ....."; else if(calif < 0) cout << "ERROR: CALIFICACION DEMASIADO BAJA ....."; else if( (calif>= 70) && (calif <=100)) cout << "CALIFICACION APROBATORIA."; else cout << "CALIFICACION REPROBATORIA."; }

Listado 3.2.- Uso de if-else-if

3.2.2.- La instruccin switch


Esta instruccin es til cuando se tiene que elegir entre ms de dos opciones, como es el caso de manejo de mens. Esta instruccin es preferible que el uso de anidamientos de varios if-else. Su sintaxis es:
switch(expresin_entera) { case Const_1 : Bloque_1 ; break ; case Const_2 : Bloque_2 ; break ; ............................. ............................. ............................. case Const_N : Bloque_N ; break ; default : Bloque_X ; }

A la entrada en una instruccin switch, primero se evala la expresin_entera. En caso de que el resultado coincida con Const_1, se ejecuta el Bloque_1 y break interrumpe la ejecucin del instruccin; en caso de que coincida con el valor de Const_2, se ejecuta el Bloque_2 , se interrumpe la ejecucin de la instruccin. Lo mismo pasa en caso de que el resultado coincida con cualquiera de los otros valores constantes. En caso de existir default:, y si el resultado no coincide con ninguno de los

valores constantes, se ejecutan las instrucciones contenidas en el Bloque_X. Esta estructura puede representarse con el diagrama mostrado en la figura 3.3.

Es recomendable el uso de la instruccin switch en el caso del manejo de un men de opciones como se observa en el listado 3.3.
#include <iostream.h> #include <conio.h> void main() { char opcion; clrscr(); gotoxy(30,5); cout << "MENU DE OPCIONES"; gotoxy(30,8); cout << "1.- CREACION"; gotoxy(30,10); cout << "2.- MODIFICACION"; gotoxy(30,12); cout << "3.- ELIMINACION"; gotoxy(30,14); cout << "0.- SALIDA"; gotoxy(30,18); cout << "SU OPCION ? "; opcion= getche(); cout << "\n\n"; switch(opcion) {

case '1': clrscr(); cout << "RUTINA DE CREACION\n"; break; case '2': clrscr(); cout << "RUTINA DE MODIFICACION\n"; break; case '3': clrscr(); cout << "RUTINA DE ELIMINACION\n"; break; case '0': clrscr(); cout << "SALIDA AL SISTEMA OPERATIVO\n";break; default:clrscr(); cout << "OPCION INVALIDA.....\n"; } }

Listado 3.3.- Uso de switch

3.3.1.- La instruccin while


Con esta instruccin se maneja una estructura en la que, de entrada, se evala una condicin. En caso de que el resultado de tal evaluacin sea un valor diferente de cero , se ejecuta un bloque de instrucciones, en el cual debe existir una instruccin que modifique la condicin, ya que de lo contrario ejecutar un ciclo infinito ( loop ). Si el resultado de la evaluacin es un valor igual a cero , el bloque de instrucciones no se ejecuta y finaliza la ejecucin de la instruccin. La sintaxis de la instruccin while es la siguiente:
while(condicin) bloque;

En la figura 3.4 se presenta el diagrama de la instruccin while.

El listado 3.4 muestra el uso de la instruccin while.


#include <iostream.h> #include <conio.h> #define FALSO 0 void main() { int valor=1; clrscr(); while(valor!=FALSO) { cout << "\nTeclee un valor entero ( 0="salir" ) : "; cin>> valor; } }

Listado 3.4.- Uso de la instruccin while

3.3.2.- La instruccin do - while


La instruccin do-while tiene un comportamiento similar a while, slo que en este caso primero se ejecuta el bloque de instrucciones y despus se evala la condicin. Con esto se asegura que el bloque se ejecutar al menos una vez. Esta es la sintaxis de la instruccin do-while :
do bloque; while(condicin);

La instruccin do-while puede representarse con el diagrama mostrado en la figura 3.5.

Como una aplicacin de la instruccin do-while , se presenta en el listado 3.5 una variante al problema resuelto en el listado 3.3 .

#include <iostream.h> #include <conio.h> void main() { char opcion; do{ // inicia ciclo 1 clrscr(); gotoxy(30,5); cout << "MENU DE OPCIONES"; gotoxy(30,8); cout << "1.- CREACION"; gotoxy(30,10); cout << "2.- MODIFICACION"; gotoxy(30,12); cout << "3.- ELIMINACION"; gotoxy(30,14); cout << "0.- SALIDA"; gotoxy(30,18); cout << "SU OPCION ? "; window(42,18,42,18); do{ // inicia ciclo 2 clrscr(); opcion = getche(); }while((opcion < '0')||(opcion>'3'));// fin ciclo 2 window(1,1,80,25); switch(opcion) { case '1': clrscr(); cout << "RUTINA DE CREACION\n"; break; case '2': clrscr(); cout << "RUTINA DE MODIFICACION\n"; break; case '3': clrscr(); cout << "RUTINA DE ELIMINACION\n"; break; case '0': clrscr(); cout << "SALIDA AL SISTEMA OPERATIVO\n"; break; } cout << "\n\nPULSE CUALQUIER TECLA PARA CONTINUAR.."; getch(); }while(opcion!="0" ); // fin ciclo 1

Listado 3.5.- Aplicacin de la instruccin do-while

3.3.3.- La instruccin for


Entre las instrucciones de iteracin, for es la ms verstil, ya que, entre otras caractersticas, permite la declaracin de variables dentro de su estructura. La sintaxis de la instruccin for es la siguiente:
for(inicializacin ; condicin ; control) bloque;

donde: inicializacin es un bloque de instrucciones que puede incluir la declaracin de las variables involucradas y la asignacin de valores iniciales. es una instruccin que puede evaluarse de tal forma que se obtenga como resultado un valor de verdad ( falso verdadero ). Mientras, se cumpla la condicin, se ejecutar el bloque de instrucciones. es un bloque de instrucciones separadas por comas y que controlan la variacin de los valores de las variables utilizadas.

condicin

control

Los bloques de inicializacin, condicin y control no son obligatorios, pero s lo son los tres punto y coma que los separan; de tal suerte que la forma mnima de una instruccin for quedara as:
for(;;) ; // ciclo infinito // no realiza tarea alguna

El diagrama para la instruccin for se muestra en la figura 3.6.

El listado 3.6 muestra el uso de la instruccin for.


#include <iostream.h> #include <conio.h> int main() { clrscr(); for(int x=1 ; x <= 25 ; x++) { for(int y="1" ; y <="80" ; y++) { gotoxy(y,x); if((x="=25)" && (y="=80))" getch(); cout << '.'; } } return 0; }

Listado 3.6.- Utilizacin de la instruccin for

4.- Diseo de funciones


Las funciones son el mdulo bsico para la construccin de programas en C++. Adems de la funcin main(), con frecuencia es necesario utilizar funciones adicionales que pueden ser accesadas a travs del enlace de libreras precompiladas a travs de su definicin en el archivo de cdigo fuente en archivos de cabecera. En esta unidad estudiaremos los procedimientos necesarios para el manejo de las funciones definidas en el cdigo fuente. En principio, debemos distinguir entre: declarar, definir e invocar una funcin, ya que la confusin de stos trminos es causa de frecuentes problemas. Desde los orgenes del Lenguaje C ha existido la distincin entre definir y declarar una funcin. Cuando se define una funcin se le est reservando espacio de almacenamiento en memoria; en cambio cuando se declara solo se est avisando que ms adelante se encuentra una funcin con ciertas caractersticas, pero no se le reserva espacio en memoria.

4.1.- Definicin de funciones


La definicin de una funcin implica reservarle espacio de almacenamiento en memoria, de acuerdo al tipo de dato a retornar. Es en la definicin donde se incluye el cuerpo de la funcin. El formato general de la definicin de una funcin es el siguiente:
tipo { } identificador( argumentos ) bloque;

donde: tipo es el tipo de dato que va a retornar la funcin, e identificador es el nombre de la funcin. La existencia de argumentos depender de la tarea que va a realizar la funcin, pero el par de parntesis es requisito indispensable.

En el ejemplo 4.1. se presenta una aplicacin de la definicin, la declaracin y la invocacion de funciones.

4.2.- Declaracin de funciones


Cuando se declara una funcin, se est avisando al compilador que ms adelante encontrar la definicion de tal funcin, y que por el momento, tome nota de las caractersticas de ella, como son: el tipo de dato que retorna, su nombre y los tipos de argumentos que va a recibir. Con esto, no habr ningn problema en invocar a la funcin en un bloque de programa ubicado antes del lugar donde se encuentra escrita su definicin. En el ejemplo 4.1, las lneas:
void saludo(); float calcula(float);

representan la declaracin de las funciones la declaracin de los prototipos de las funciones saludo() y calcula(). En la declaracin de la funcin saludo() se especifica que no va a retornar valor alguno, y que no recibir argumentos. En la declaracin de la funcin calcula() se especifica que va a retornar un valor de tipo float, y que va recibir un argumento de tipo float. Es importante observar que la declaracin de una funcin es parecida a la lnea de encabezado de su definicin , slo que en el caso de la declaracin se escribe un punto y coma al final. Tambin cabe hacer notar que en la declaracin no se requiere escribir identificadores para los argumentos, como se observa en el ejemplo 4.1, sino que basta con incluir los tipos de datos de los argumentos. Se pueden incluir identificadores de argumentos, slo que el mbito de tales identificadores estar restringido a la declaracin de la funcin correspondiente. Por lo tanto, la declaracin:
float calcula(float); podra haberse escrito as :

float calcula(float arg);

En este caso, el identificador arg no tiene uso alguno; por lo que es innecesaria su inclusin. Si el nmero de argumentos, o los tipos correspondientes no coinciden entre la

declaracin y la lnea de encabezado de la definicin de la funcin el compilador marcar un error.

4.3.- Invocacin a funciones


Una invocacin llamada a una funcin implica pasarle el control de la ejecucin del programa, as como los argumentos parmetros que requiere para realizar su tarea. En el listado 4.1 se tienen las lneas:
saludo(); //INVOCACION A LA FUNCION saludo() precio = calcula(costo); //INVOCACION A LA FUNCION calcula()

En la primera, se invoca a la funcin saludo() y no se le pasa ningn argumento. En la segunda, se invoca a la funcin calcula(), pasndosele como argumento una copia del valor que tiene la variable costo. El valor retornado por calcula() se asigna a la variable precio.
// ENCABEZADOS #include <iostream.h> #include <conio.h> // DECLARACION DE FUNCIONES void saludo(); float calcula(float); // DEFINICION DE LA FUNCION PRINCIPAL void main() { float costo, precio; clrscr(); cout << "COSTO : $ "; cin>> costo; saludo(); //INVOCACION A LA FUNCION saludo() precio = calcula(costo); //INVOCACION A LA FUNCION calcula() cout << "PRECIO : $ " << precio; } // DEFINICION DE LA FUNCION saludo() void saludo() { clrscr(); cout << "!! BIENVENIDO A LA VENTA ESPECIAL !!"; } // DEFINICION DE LA FUNCION calcula() float calcula(float x) { return( x * 1.6); }

Listado 4.1.- Diseo de funciones utilizando prototipos.

4.4.- El prototipo de una funcin es obligatorio?


Cuando encuentra la primera invocacin a una funcin, el compilador verifica si ya se realiz la declaracin la definicin de la funcin invocada. En caso de no existir ni declaracin ni definicin previa, enviar un mensaje de error diciendo que la funcin invocada debe tener un prototipo. En ese momento se detiene la compilacin y no se genera el archivo .OBJ correspondiente. Lo anterior puede sugerir que el prototipo de la funcin invocada es indispensable. La realidad es que se puede omitir el prototipo si escribimos la definicin de la funcin antes de su primera invocacin, como se muestra en el ejemplo 4.2.
// ENCABEZADOS #include <iostream.h> #include <conio.h> // DEFINICION DE LA FUNCION saludo() void saludo() { clrscr(); cout << "!! BIENVENIDO A LA VENTA ESPECIAL !!"; } // DEFINICION DE LA FUNCION calcula() float calcula(float x) { return( x * 1.6); } // DEFINICION DE LA FUNCION PRINCIPAL int main() { float costo, precio; clrscr(); cout << "COSTO : $ "; cin>> costo; saludo(); //INVOCACION A LA FUNCION saludo() precio = calcula(costo); //INVOCACION A LA FUNCION calcula() cout << "PRECIO : $ " << precio; return 0; }

Listado 4.2.- Diseo de funciones sin uso de prototipos. Aunque el prototipo no es obligatorio, es recomendable utilizarlo, ya que de esta manera se permite al compilador verificar que el nmero y tipos de argumentos utilizados en la invocacin (parmetros actuales) coinciden con los de la definicin (parmetros formales). La declaracin de prototipos tambin sirve para que el usuario de un programa

conozca la forma de utilizacin de la funcin, sin tener que proporcionarle el cdigo fuente. Por ejemplo, si tenemos un programa que maneja operaciones matemticas podemos distribuir a los usuarios el cdigo objeto que contenga las definiciones de las funciones y un archivo de cabecera que contenga los prototipos. Los archivos pudieran tomar la forma mostrada a continuacin:
ARITME.OBJ Archivo que contiene el cdigo objeto, donde se definen las funciones aritmticas.

//ARITME.HPP: CONTIENE LOS PROTOTIPOS DE LAS FUNCIONES double suma(double,double); double resta(double,double); ........................... ........................... //APLICA.CPP: PROGRAMA FUENTE ELABORADO POR // EL USUARIO #include <iostream.h> #include "ARITME.HPP" //Incluye el archivo que contiene //los prototipos de las funciones //aritmticas void main(void) { double result, a=13500.45, b=16763.87; result = suma(a,b); // Invocacin a la funcin suma() // definida en ARITME.OBJ cout << result; }

Este es el criterio que se sigue al utilizar los archivos de cabecera que contiene el paquete compilador. Por ejemplo, cuando se escribe la lnea:
#include <stdio.h>

se est incluyendo el archivo que contiene los prototipos de las funciones que se utilizan para el manejo de la entrada/salida estndar.

4.5.- Sobrecarga de funciones


En la mayora de los casos, los identificadores nombres de los objetos deben ser nicos, esto es, que dos objetos dentro del mismo mbito no deben tener el mismo nombre. Una de las excepciones la constituyen las funciones, que pueden compartir el mismo nombre entre varias de ellas, dando la impresin de que a una sola funcin se

le ha "sobrecargado de tareas", razn por la cual se les llama funciones sobrecargadas. Por ejemplo, supongamos que deseamos una funcin que sirva para sumar nmeros. Lo ideal sera contar con una sola funcin que realizara la suma de nmeros de diferentes tipos y que retornara un valor de acuerdo al tipo de resultado. El Lenguaje C++ permite resolver este problema por medio de la sobrecarga de funciones. A continuacin se muestran los prototipos para cada una de las funciones que comparten el nombre suma :
int float suma(int,int); suma(float,float); // Recibe enteros, devuelve entero

double suma(double,double);

Aqu surge la pregunta: cmo distingue el compilador entre cada una de las funciones suma() ?. Debido a que varias funciones sobrecargadas pueden usar el mismo identificador, C+ + utiliza el concepto de nombres ampliados para controlar cada funcin individualmente. El nombre ampliado de una funcin se construye tomando como base el nombre de la funcin y los tipos de los argumentos. El tipo de retorno de la funcin no se utiliza en la formacin del nombre ampliado. La composicin del nombre ampliado ocurre a nivel del compilador, no al nivel del enlazador. El enlazador resuelve fcilmente las referencias externas para sobrecargar las funciones debido a que stas tienen nombres ampliados nicos. Las reglas bsicas usadas en Borland C++ para los nombres ampliados son relativamente simples como se observa a continuacin : 1. Se utiliza primero el nombre de la clase, precedido por el carcter @. 2. Enseguida viene el nombre de la funcin, precedido por el carcter @. 3. Si la funcin no es parte de una clase, el nombre ampliado empieza con el smbolo @ seguido por el nombre de la funcin. Se respetan las reglas establecidas para la creacin de identificadores. 4. El nombre de la funcin est seguido por una secuencia $q ; las letras minsculas que le siguen designan cada tipo de argumento declarado. La tabla 4.1 muestra una lista de las letras que se utilizan para manejar los tipos predefinidos de C++ en la formacin de los nombres ampliados. Tipo de argumento Letras void v void* pv

unsigned char unsigned char* unsigned char& signed char signed char* signed char& int int* int& unsigned int unsigned int* unsigned int& long long* long& unsigned long unsigned long* unsigned long& float float* float& double double* double&

uc puc ruc zc pzc rzc i pi ri ui pui rui l pl rl ul pul rul f pf rf d pd rd

Tabla 4.1.- Letras para los tipos de argumentos en la creacin de nombres ampliados.

Utilizando la tabla 4.1, se pueden predecir fcilmente los nombres ampliados de las funciones, como se muestra a continuacin: Declaracin de funciones Nombre ampliado void imprim(void); @imprim$qv void imprim(int); @imprim$qi void imprim(int,int,int); @imprim$qiii int redim(long,unsigned int*); @redim$qlpui

void despliega(char*); int suma(int,int); float suma(float,float); double suma(double,double);

@despliega$qpzc @suma$qii @suma$qff @suma$qdd

4.6.- Paso de parmetros


Cuando se invoca a una funcin, en ocasiones es necesario enviarle algunos elementos indispensables para realizar su tarea. Estos elementos, enviados en la invocacin, se llaman parmetros actuales. Dependiendo de la naturaleza de los parmetros, clasificaremos el paso de ellos en: 4.6.1.- Paso Por Valor. 4.6.2.- Paso Por Referencia. 4.6.3.- Argumentos Predeterminados.

4.6.1.- Paso por valor


Se realiza un paso de parmetros por valor cuando se enva una copia de los valores de los parmetros actuales. En este caso, la funcin invocada no podr modificar los valores de las variables utilizadas como parmetros actuales, sino que trabajar con las copias que se le envan. Estas copias son recibidas en los parmetros formales, los cuales se comportan como variables locales pertenecientes a la funcin invocada. Al igual que las variables locales, una vez que termina la ejecucin de la funcin invocada, las variables pertenecientes a los parmetros formales se destruyen; y por lo tanto, se pierden los valores que envi el mdulo invocador. El listado 4.3 muestra el paso de parmetros por valor.
#include <iostream.h> #define NL cout << "\n" void precio(double); void main(void) { double costo; cout << "COSTO : $ "; cin>> costo; precio(costo); // SE INVOCA A precio() Y SE LE ENVIA UNA

// COPIA DEL VALOR DE costo NL; cout << costo; // EL VALOR DE costo SE CONSERVA DESPUES // DE LA INVOCACION A precio()

void precio(double recibido) { recibido="recibido" * 1.5; // SE MODIFICA EL VALOR DE LA // COPIA GURDADA EN recibido NL; cout << recibido; }

Listado 4.3.- Paso de parmetros por valor.

4.6.2.- Paso por referencia


A diferencia del paso por valor, el paso por referencia permite que la funcin invocada modifique el valor original de las variables usadas como argumentos. En C++, el paso de parmetros por referencia se realiza creando un alias del identificador de la variable que forma el parmetro actual, como se muestra en el listado 4.4 .
#include <iostream.h> #include <conio.h> void oferta(float &); void main(void) { float precio; clrscr(); gotoxy(20,12); cout << " CUAL ES EL PRECIO ?. N$ " ; cin>> precio; oferta(precio); gotoxy(20,14); cout << " DIJO USTED N$ " << precio << " ?"; gotoxy(20,16); cout << " ESO ES CON EL 20 % DE DESCUENTO"; gotoxy(1,25); } void oferta(float & bajo) { bajo *="0.8" ; gotoxy(20,24); cout << "PRECIO REBAJADO: N$ " << bajo; }

Listado 4.4.- Paso de parmetros por referencia.

En el listado 4.4, se puede decir que bajo es otro identificador asociado a la variable precio, por lo que la funcin oferta() realmente modifica el valor contenido en dicha variable. Segn lo anterior, podemos decir que es preferible utilizar una variable existente en lugar crear una nueva, y eliminar el paso de parmetros por valor. El paso de parmetros por valor puede sustituirse por un paso de parmetros por referencia donde se utilice el modificador const, como se muestra en el listado 4.5.
#include <iostream.h> #include <conio.h> void seudoferta(const float &); void main(void) { float precio; clrscr(); gotoxy(20,12); cout << " CUAL ES EL PRECIO ?. N$ " ; cin>> precio; seudoferta(precio); gotoxy(20,14); cout << " DIJO USTED N$ " << precio << " ?"; gotoxy(20,16); cout << " ESO DIJE.... NO HAY DESCUENTO"; gotoxy(1,25); } void seudoferta(const float & bajo) { float rebajado; rebajado="bajo" * 0.8 ; gotoxy(20,24); cout << "PRECIO REBAJADO: N$ " << rebajado; }

Listado 4.5.- Paso por valor utilizando referencias.

4.6.3.- Argumentos predeterminados


Cuando se declara el prototipo de una funcin, es posible asignar valores iniciales a los argumentos. A stos se les llama argumentos predeterminados, y tienen como objetivo simplificar el manejo de los valores de los parmetros actuales utilizados. Supngase el problema de calcular del precio de un artculo. En la solucin utilizaremos una funcin que recibe dos parmetros: el precio del artculo y el descuento correspondiente.

El valor del precio cambia para cada artculo, pero el valor del descuento puede considerarse como un pocentaje fijo del precio para la mayora de los artculos (por ejemplo en poca de ofertas). En el listado 4.6 se presenta un programa solucin para este caso.
////////////////////////////////////////////////////// // ARGPRED.CPP : EJEMPLO DEL MANEJO DE ARGUMENTOS // // CON VALORES PREDETERMINADOS // ////////////////////////////////////////////////////// #include <iostream.h> #include <conio.h> // DECLARACION DEL PROTOTIPO DE LA FUNCION desc(). // AL ARGUMENTO DE TIPO ENTERO porcentaje SE LE ASIGNA // UN VALOR INICIAL DE 20 Y AL iva UN VALOR INICIAL DE 10. double desc(double , int porcentaje = 20, int iva = 10); // DEFINICION DE LA FUNCION main() void main(void) { double precio, rebajado_1, rebajado_2, rebajado_3 ; clrscr(); gotoxy(20,10); cout << "PRECIO: N$ "; cin>> precio; rebajado_1 = desc(precio); // SE ACEPTA EL porcentaje Y EL // iva PREDETERMINADOS. gotoxy(40,10); cout << "PRECIO REBAJADO 1="N$" " << rebajado_1; gotoxy(20,12); cout << "PRECIO: N$ "; cin>> precio; rebajado_2 = desc(precio,40); // EL porcentaje CAMBIA A 40, // SE CONSERVA EL VALOR DEL iva. gotoxy(40,12); cout << "PRECIO REBAJADO 2="N$" " << rebajado_2; gotoxy(20,14); cout << "PRECIO: N$ "; cin>> precio; rebajado_3 = desc(precio,20,0); // SE CAMBIA EL VALOR // DE iva. AUNQUE SE CONSERVA EL VALOR // DE porcentaje, DEBE ESCRIBIRSE // DE NUEVO. gotoxy(40,14); cout << "PRECIO REBAJADO 3="N$" " << rebajado_3; } // DEFINICION DE LA FUNCION desc() double desc(double p, int d, int i) { return(p * (1.00 (d-i)/100.00)); }

4.7.- Funciones recursivas


Se dice que una funcin es recursiva cuando puede invocarse a s misma. En cada invocacin se crean las variables locales, por lo que es indispensable incluir una condicin de salida, ya que de no hacerlo se agotar la memoria disponible en la pila. Como un primer acercamiento al manejo de funciones recursivas en C++, se presenta el listado 4.7.
#include <iostream.h> #include <conio.h> #define NL cout << "\n" #define PULSE cout << "PULSE CUALQUIER TECLA PARA CONTINUAR." void recursiva(void); void main(void) { clrscr(); recursiva(); NL; PULSE; getch(); NL; } void recursiva(void) { int x; cout << "TECLEE UN NUMERO ENTERO... ( 0="SALIR)" "; cin>> x ; if(x) recursiva(); }

Listado 4.7.- Diseo de una funcin recursiva.

En el listado 4.7 podemos observar que, para que la funcin recursiva se invoque a si misma, es necesario que el valor almacenado en x sea diferente de 0. El clculo del factorial de un nmero es un problema clsico en la aplicacin de las funciones recursivas. En el listado 4.8 se presenta un programa en C++ que calcula el factorial de un nmero dado.
#include <iostream.h> #include <conio.h> long factorial(unsigned short int); void main() { unsigned short int num; long result; clrscr(); do { gotoxy(20,11);

cout << "El FACTORIAL del nmero: " ; clreol(); cin>> num ; } while((num <0) || (num> 19 )); result = factorial(num); gotoxy(20,13); cout << " es : " << result; } long factorial(unsigned short int n) { if(n="=0)" return 1; else return n*(factorial(n-1)) ; }

Listado 4.8.- Clculo del factorial utilizando funciones recursivas. En el listado 4.8, si el nmero dado por el usuario es 4, el proceso realizado por el programa se podra representar de la manera mostrada en la tabla 4.2. Numero de Valor de n invocacin 1 4 2 3 3 2 4 1 5 0 Resultado 4*(factorial(3)) 3*(factorial(2)) 2*(factorial(1)) 1*(factorial(0)) 1

Tabla 4.2.- Resultados de invocar a la funcin factorial() pasndole como parmetro el nmero 4 .

En cada invocacin, se crea en la pila una variable cuyo identificador es n y su valor cambiar como se muestra en la tabla 4.2. Como en las invocaciones 1,2,3 y 4 el valor de n es diferente de 0, la funcin vuelve a invocarse a s misma, quedando sin resolver el resultado. Cuando el valor de n es igual a 0 (invocacin 5), la funcin retorna el valor 1 la la invocacin 4, por lo que el resultado en sta se calcula as :
1 * (factorial(0)) = 1 * 1 = 1

La invocacin 4 retorna a la invocacin 3 el valor 1 y el resultado en la invocacin 4 es :


2 * (factorial(1)) = 2 * 1 = 2

A su vez, la invocacin 3 retorna a la invocacin 2 el valor 2. El resultado en la invocacin 2 es :

3 * (factorial(2)) = 3 * 2 = 6

Posteriormente, la invocacin 2 retorna el valor 6 a la invocacin 1. El resultado en la invocacin 1 es :


4 * (factorial(3)) = 4 * 6 = 24

Finalmente, la invocacin 1 retorna el valor 24 a la funcin invocadora main(), la cual asigna a la variable result el valor recibido ( 24 ).

5.- Arreglos.
Los arreglos consisten de un conjunto de elementos del mismo tipo almacenados en localidades contguas de memoria. Los elementos de un arreglo comparten el mismo nombre, pudindo distinguirse un elemento de otro a travs de un subndice. En esta unidad, describiremos la declaracin de los arreglos, el manejo de los elementos de un arreglo dado, as como lo relacionado con los arreglos que se utilizan para formar las cadenas de caracteres.

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: tipo es el tipo de los elementos que componen el arreglo identificador [ <expresin_constante> ] ;

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]; RESULTADO Declara un arreglo unidimensional llamado nombre compuesto de 31 elementos de tipo

int valor[20]; unsigned long abc[x]

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

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.

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.- Manejo de arreglos


En la seccin anterior se ha tratado lo relacionado con la declaracin de arreglos con elementos de diferentes tipos y con una o varias dimensiones. En esta seccin se tratar lo relativo al acceso a los elementos individuales de un arreglo, ya sea para asignarles valores especficos o para utilizar los valores almacenados.

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] = = = = = = 'A' 'R' 'R' 'E' 'G' 'L' ; ; ; ; ; ;

cadena[6] = 'O' ; cadena[7] = '\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> void main() { char nombre[31]; // Para gets() y puts() // Para clrscr() y gotoxy() // Para strupr() y strlen()

// 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 DESCRIPCION Copia una cadena de caracteres en otra.Se detiene cuando encuentra el stpcpy terminador nulo. strcat Aade una cadena de caracteres a otra. strchr Busca, en una cadena, un caracter dado. strcmp Compara dos cadenas. strcmpi Macro que compara dos cadenas sin distinguir entre maysculas y minsculas. strcpy Copia una cadena. Busca segmentos que no contienen un subconjunto de un conjunto especificado strcspn de caracteres. strdup Copia una cadena a una nueva localidad. _strerror Genera un mensaje de error definido por el programador. strerror Retorna el apuntador al mensaje asociado con el valor del error. stricmp Compara dos cadenas sin diferenciar entre maysculas y minsculas strlen Determina la longitud de una cadena. strlwr Convierte las maysculas de una cadena en minsculas. strncat Aade el contenido de una cadena al final de otra. strncmp Compara parte de una cadena con parte de otra. strncmpi Compara parte de una cadena con parte de otra, sin distinguir entre maysculas

y minsculas. strncpy 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 strnicmp y minsculas. strnset 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 strpbrk dado. strrchr Busca la ltima aparicin de un caracter en una cadena. strrev Invierte el orden de los caracteres de una cadena. strset Hace que los elementos de una cadena tengan un valor dado. Busca en una cadena el primer segmento que es un subconjunto de un conjunto strspn 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.

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.- Variables automticas, variables dinmicas y apuntadores


En esta unidad trabajaremos con los conceptos necesarios para el manejo de situaciones en que se requiere utilizar direcciones de memoria, ya sea que se trate de las direcciones correspondientes a las variables manejadas en la pila (variables automticas) o a las variables manejadas en el montculo (variables dinmicas). Para el manejo de esas direcciones de memoria se requiere el uso de apuntadores. Tambin revisaremos la estrecha relacin existente entre arreglos y apuntadores; y en la parte final retomaremos el tema del paso de parmetros para analizar la forma en que se pasa un parmetro cuando ste consiste de un arreglo o cuando consiste de una funcin.

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 |----------------|

int *apunt ; automatica = 100 ;

| 100 | |----------------| apunt = &automatica ; automatica, *apunt |----------------| apunt ---->| 100 | |----------------| automatica, *apunt |----------------| ---->| 200 | |----------------|

*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.

6.4.- Apuntadores y constantes


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: por lo que:
&calif[i] == calif+i 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.

6.8.- Paso de arreglos como parmetros


Un arreglo puede pasarse como parmetro a una funcin. Si tuviera que pasarse por valor un arreglo muy grande, sera un desperdicio de memoria. En el Lenguaje C++ el paso de arreglos se hace por referencia, ya que el nombre del arreglo corresponde a un apuntador al primer elemento del arreglo. 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 listado 6.11 contiene un programa que maneja una funcin llamada nputs() , la cual recibe como parmetro un arreglo de caracteres.
#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++; } }

Listado 6.11.- Manejo de nputs() con arreglos. En el listado 6.12 se muestra el manejo de la funcin nputs(), por medio de apuntadores.
#include #include #include #include <iostream.h> <conio.h> <stdio.h> <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++ ; }

Listado 6.12.- Manejo de nputs() con apuntadores.

6.9.- Paso de funciones como parmetros


Toda funcin tiene asociada una direccin de inicio de cdigo, la cual puede pasarse como parmetro en la invocacin a otra funcin, como se muestra en el listado 6.13.
#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); } 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)); }

Listado 6.13.- Paso de apuntadores como parmetros a funciones. En el listado 6.13, la expresin : int(*cmpcad)(char*, char*) establece que cmpcad es un apuntador a una funcin, la cual devuelve un valor de tipo entero .

6.10.- Apuntadores a apuntadores


Como se vi 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 listado 6.14.
#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(); }

Listado 6.14.- Ejemplo de apuntadores a apuntadores. A continuacin se analizan las lneas marcadas en el listado 6.14. int x, *a, **b, ***c; // 1 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 Se asigna, al apuntador a, la direccin de x. La pila lucira as:

*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:

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:

_ Gif animado _ Applet animado


El listado 6.15 muestra otro ejemplo de manejo de apuntadores a apuntadores. En l, la lnea de cabecera de la definicin de main() contiene argumentos entre los parntesis. Esto significa que, al invocar al programa ECO.EXE desde la lnea de comandos, se le pueden pasar argumentos. Estos argumentos sern tomados como cadenas de caracteres que sern manejadas por **argumentos. La variable entera contador contiene el nmero correspondiente a la cantidad de cadenas que se le pasan a main() en la invocacin.
// ECO.CPP : Maneja argumentos en la invocacin a main(), // de manera similar a como trabaja la orden // ECHO del sistema operativo #include <iostream.h> void main(int contador, char **argumentos) { cout << "\n"; for( int x="1" ; contador--> 0 ; x++) { int y = 0 ; do{ cout << argumentos[x][y++]; } while(argumentos[x][y]); cout << " "; } cout << "\n"; }

Listado 6.15.- Paso de argumentos a main(). Por ejemplo, si escribimos en la lnea de comandos: A:\> ECO HOLA AMIGOS !!!

en la pantalla aparecer: HOLA AMIGOS !!! En este caso: argumentos[0] = ECO 1 argumentos[1] = HOLA 2 argumentos[2] = AMIGOS 3 argumentos[3] = !!! 4 <---- Nmero de argumentos contador = 4 La lnea de cabecera de la funcin main() pudo escribirse de esta otra forma: void main(int contador, char *argumentos[]) ya que existe la siguiente equivalencia : Esto puede generalizarse, de manera que:
*cadena == cadena[] **cadena == *cadena[] ***cadena == **cadena[]

El uso de corchetes vacos slo es vlido cuando se realiza una asignacin cuando se escriben los argumentos en la lnea de cabecera de la definicin de una funcin. Slo el par de corchetes de la extrema derecha puede escribirse vaco, los dems debern contener un valor constante. La lnea de cabecera en la definicin de la funcin main() puede tomar tambin la siguiente forma:
void main(int contador, char **argumentos, char **ambiente)

donde: **ambiente se refiere a un conjunto de cadenas de caracteres correspondientes al ambiente del sistema operativo, como las asignadas a prompt, path , etc.

7.- Tipos definidos por el programador


Hasta aqu, se ha trabajado con los tipos de datos fundamentales (predefinidos por el lenguaje), y las variables han sido declaradas para almacenar un solo valor de un tipo especificado. Slo en el caso de arreglos se han declarado conjuntos de valores, pero todos ellos del mismo tipo.

En esta unidad, estudiaremos las formas existentes para definir nuevos identificadores para los tipos fundamentales, as como la manera en que se declaran las variables compuestas de elementos de diferentes tipos.

7.1.- El especificador typedef


La palabra reservada typedef representa un especificador de clase de almacenamiento, y se utiliza para definir nuevos especificadores o sinnimos para los tipos existentes. Por ejemplo :
typedef int entero ; define a entero como un sinnimo para el tipo int .

Una vez definido un sinnimo, se puede utilizar para definir nuevos especificadores, de manera que un tipo puede tener varios, como se muestra a continuacin.
typedef float flotante ; // Define a flotante como sinnimo de // float typedef flotante FLOAT ; // Define a FLOAT como sinnimo de // flotante typedef FLOAT FLOTANTE ; // Define a FLOTANTE como sinnimo de // FLOAT typedef float Float ; // Define a Float como sinnimo de float

En este caso, flotante, FLOAT, FLOTANTE y Float son nuevos identificadores o sinnimos de float . Otro ejemplo es :
typedef long double gigante ; // Define a gigante como sinnimo de // long double

Cabe hacer notar que typedef no crea nuevos tipos, sino que define nuevos nombres para los tipos existentes.

7.2.- Estructuras
En ocasiones es necesario contar con tipos de datos que estn compuestos por conjuntos de elementos de diferentes tipos entre s.

En C++, esto se logra por medio de estructuras, las cuales son tipos derivados definidos por el usuario que representan colecciones de elementos.

7.2.1.- Definicion de tipos de estructuras


La palabra struct se utiliza para definir un tipo compuesto. Por ejemplo, si se requiere definir un nuevo tipo llamado catalogo cuyos elementos sean:
clave precio se puede escribir: nmero entero , nmero de punto flotante

|------ Etiqueta | struct catalogo { int clave ; float precio ; } ;

En este caso, catalogo es un nuevo tipo que puede utilizarse para declarar una estructura como:
catalogo refac ;

7.2.2.- Acceso a los miembros de una estructura


Una vez declarada una estructura, pueden asignrseles valores iniciales a sus miembros, como en:
refac.clave = 1001 ;

refac.precio = 624.50 ;

Obsrvese que el operador punto sirve para representar la dependencia de cada uno de los miembros con su estructura; esto es: refac.clave se refiere al miembro clave de la estructura refac. El operador punto es necesario porque, al declararse varias estructuras del mismo tipo, habr que distinguir los correspondientes miembros de unas y otras. Por ejemplo, si escribimos:
catalogo refac, muebles ;

En este caso se declaran dos estructuras de tipo catalogo, cuyos identificadores son refac y muebles. Entonces podemos realizar las siguientes asignaciones:
refac.clave = 2001 ; refac.precio = 360.45 muebles.clave = 4001 ; muebles.precio = 2500.50 ;

En este caso, los miembros clave y precio de las estructuras refac y muebles son inconfundibles. El listado 7.1 muestra el cdigo fuente para este ejemplo.
#include <iostream.h> void main() { struct catalogo { } ; catalogo refac, muebles; refac.clave = 2001 ; refac.precio = 360.45 ; muebles.clave = 4001 ; muebles.precio = 2500.50; cout << refac.clave << " " << refac.precio << "\n"; cout << muebles.clave << " " << muebles.precio; }

int clave ; float precio ;

Listado 7.1.- Definicin y uso de un tipo struct. En el listado 7.1 se observa que primero se define el tipo catalogo y despus se utiliza para declarar las estructuras refac y muebles. El tipo catalogo est disponible para declarar otras estructuras, adems de refac y muebles; pero, si existe la seguridad de que eso no ser necesario, se puede cambiar el cdigo fuente a la forma presentada en el listado 7.2.
#include <iostream.h> void main() { struct { int clave ; float precio ;

} refac, muebles ; refac.clave = 2001 ; refac.precio = 350.45 ; muebles.clave = 4001 ; muebles.precio = 2500.50; cout << refac.clave << " " << refac.precio << "\n"; cout << muebles.clave << " " << muebles.precio; }

Listado 7.2.- Utilizacin de un tipo struct desechable. Como puede observarse en el listado 7.2, puede definirse un tipo struct sin etiqueta, escribiendo la primera lnea as :
struct {

y la declaracin de las estructuras a utilizar deber escribirse antes del punto y coma de la ltima lnea, como en:
} refac, muebles ;

Puede decirse que se ha definido un tipo "desechable", ya que slo lo utilizamos para declarar a las estructuras refac y muebles, adems de que no le asignamos una etiqueta.

7.2.3.- Arreglos de Estrucuras


Las estructuras, al igual que las dems variables, pueden agruparse en arreglos como se muestra en el listado 7.3.
#include <iostream.h> #include <string.h> // DEFINE UN TIPO DE ESTRUCTURA LLAMADO dicc struct dicc { char *ingl ; char *espa ; } ; // DECLARA UN ARREGLO DE ESTRUCTURAS LLAMADO tabla Y ASIGNA // VALORES INICIALES A LOS ELEMENTOS DEL ARREGLO. dicc tabla[] = { "absolute","absoluto", "append","anexar", "begin","iniciar,principiar,comenzar", "close","cerrar", "do","hacer",

};

"file","archivo", "while","mientras"

// DEFINE A NUMESTR, QUE CONTIENE EL NUMERO DE ESTRUCTURAS // QUE COMPONEN EL ARREGLO tabla #define NUMESTR (sizeof(tabla)/sizeof(dicc)) int busqbin(char* , dicc * , int) ; void main() { char pal[80]; int nret ; do{ cout << "\n\nPara finalizar, escriba FIN en : \n"; cout << "\n Palabra en ingls : "; cin>> pal; nret = busqbin(pal,tabla,NUMESTR); if(nret == -1) { if(strcmp(strupr(pal),"FIN")==0) cout << "\n\n!! HASTA LUEGO !!\n\n"; else cout << "\n\nPALABRA NO REGISTRADA\n\n"; } else cout << "\n" << pal << "=" << tabla[nret].espa ; }while(strcmp(strupr(pal)," FIN")!="0);" } // FUNCION QUE, DADA UNA PALABRA EN INGLES, REALIZA UNA // BUSQUEDA BINARIA EN EL ARREGLO tabla, RETORNANDO EL // NUMERO DE ESTRUCTURA DONDE SE ENCUENTRA LA palabra int busqbin(char *pal, dicc *t, int N) { int bajo = 0, alto = N-1, medio, cond; while(bajo <= alto) { medio="(bajo" + alto) / 2 ; if((cond = strcmp(pal, t[medio].ingl)) < 0) alto = medio 1 ; else if(cond> 0) bajo = medio + 1 ; else return(medio) ; } return(-1) ; }

Listado 7.3.- Manejo de un arreglo de estructuras

7.2.4.- Apuntadores a estructuras

Al declarar una estructura se le asocia un bloque de memoria en la pila en el montculo. La direccin inicial del bloque puede asignarse a un apuntador, por lo que la estructura puede manejarse a travs de ste. Un apuntador a una estructura se declara de la misma forma en que se declara cualquier apuntador. Por ejemplo, si usamos el tipo dicc definido anteriormente, podemos escribir la lnea:
dicc *apd ;

que declara a apd como un apuntador a objetos de tipo dicc. En este momento, se puede declarar una variable dinmica utilizando el apuntador apd, como se muestra a continuacin:
apd = new dicc;

El acceso a los elementos de la estructura apuntada por apd se logra a travs del "operador flecha" -> , que se forma con los smbolos:
- ( menos ) y > ( mayor que )

En el listado 7.4 se muestra el manejo de una lista simplemente ligada cuyos nodos estn compuestos por estructuras de tipo alumno.
#include <iostream.h> #include <conio.h> #include <stdio.h> #include <stlib.h> #include <string.h> #define IZQ 20 #define NOMEM "\nMEMORIA INSUFICIENTE....\n" #define VACIA "\nLISTA VACIA....\n" #define PULSE "\nPULSE CUALQUIER TECLA PARA CONTINUAR....\n" #define ELIM "\nNODO ELIMINADO....\n" #define NOEX "\nNOMBRE INEXISTENTE....\n" void inserta(); void elimina(); void despliega(); // Define a alumno como un tipo de estructura struct alumno { char nombre[31]; int calif;

struct alumno *sig; // Liga a cada nodo }; // con el siguiente. alumno *primero, *actual, *previo; // Declara apuntadores // a estructuras de char cad[31]; // tipo alumno. void main() { char opcion; primero = actual = previo = NULL ; // Controla apuntadores for(;;) // Ciclo infinito { clrscr(); gotoxy(IZQ,1); cout << "LISTA SIMPLEMENTE LIGADA"; gotoxy(IZQ,4); cout << "1.- INSERCION DE UN NODO"; gotoxy(IZQ,6); cout << "2.- ELIMINACION DE UN NODO"; gotoxy(IZQ,8);cout << "3.- DESPLEGADO DE LA LISTA"; gotoxy(IZQ,10); cout << "0.- F I N"; gotoxy(IZQ,14); cout << "Escriba el nmero de su opcin : "; do{ gotoxy(55,14); clreol(); opcion = getche(); } while((opcion < '0') || (opcion> '3')); clrscr(); switch(opcion) { case '1' : case '2' : case '3' : case '0' : } } }

inserta(); break; elimina(); break; despliega(); break; exit(0);

void inserta() { alumno *nuevo; // Declara apuntador a estructura nuevo = new alumno;// Crea nodo if(!nuevo) // Si nuevo = NULL ( No hay memoria) { cout << NOMEM << PULSE; getch(); return; } gotoxy(IZQ,2); cout << "INSERTANDO....";// Si hay memoria gotoxy(1,4); cout << "NOMBRE : "; gets(nuevo->nombre); // Lee campo nombre para nodo nuevo

gotoxy(wherex()+35,wherey()-1); cout << "\nCALIFICACION : "; cin>> nuevo->calif; // Lee campo calif para nodo nuevo if(!primero) // Lista vaca { primero = nuevo; // primero apunta a donde apunta nuevo primero->sig = NULL ; // Liga de primero apunta a NULL } else // Lista no vacia { nuevo->sig = primero; // Liga de nuevo apunta a donde // apunta primero primero = nuevo ;//primero apunta a donde apunta nuevo }

void elimina() { if(!primero) // Lista vaca { cout << VACIA << PULSE; getch(); return; } cout << "NOMBRE A ELIMINAR : "; gets(cad); actual="primero;" // actual apunta a donde apunta primero while(actual)//Mientras actual no apunte a NULL(fin lista) { // Si campo nombre de nodo apuntado por actual="cad" if(strcmp(strupr(cad),strupr(actual->nombre))==0) { if(actual==primero) // Si actual apunta a donde // apunta primero primero = actual->sig; // primero apunta a // donde apuntaliga de actual previo->sig = actual->sig; // Liga de previo apunta // a donde apunta liga // de actual delete actual; // Elimina nodo apuntado por actual cout << ELIM << PULSE; getch(); return; } else // nombre de nodo apuntado por actual !="cad" { previo = actual; // previo apunta a donde apunta // actual actual= actual->sig; // actual apunta a donde // apunta liga de actual } } cout << NOEX << PULSE; getch(); } void despliega() { actual = primero; // actual apunta a donde apunta primero if(!actual) // Si actual apunta a NULL ( lista vaca )

{ cout << VACIA << PULSE; getch(); return; } while(actual) // Mientras actual no apunte a NULL { cout << "\n" << actual->nombre;//Despliega nombre y cout << " " << actual->calif;//calif de actual actual = actual->sig; // Actual apunta a donde apunta // liga de actual } cout << "\n\n" << PULSE ; getch(); }

Listado 7.4.- Manejo de una lista simplemente ligada

7.3.- Uniones
Los tipos union comparten muchas de las caractersticas sintcticas y funcionales de los tipos struct, sin embargo existen algunas diferencias; la principal es que la union permite que solamente uno de sus miembros est activo a la vez, mientras que en la estructura todos los miembros estn activos al mismo tiempo. Otra diferencia es que el tamao de la union es el tamao del miembro ms grande, mientras que en la estructura su tamao es la suma de los tamaos de sus miembros. Las uniones son recomendables cuando se van a manejar variables que pueden compartir espacio en la memoria, debido a que sus valores van a requerirse en momentos diferentes. Por ejemplo, sean tres variables :
una de tipo int, otra de tipo float, y otra de tipo double.

Si no van a manejarse simultneamente, puede definirse un nuevo tipo union que las abarque, as:
union nums { int x ; // 2 bytes float y ; // 4 bytes double z ; // 8 bytes } ;

En este momento se cuenta con un nuevo tipo llamado nums, as que podemos utilizarlo para efectuar la siguiente declaracin:
nums varnums ; // Se declara la variable varnums // que ocupa 8 bytes de memoria.

Se pueden accesar los miembros de varnums a travs del operador punto, por ejemplo :
varnums.x = 10 ; // Se asigna el valor 10 al miembro // x de la union varnums.

En el listado 7.5 se muestra un programa completo para el manejo de este ejemplo.


#include <iostream.h> void main() { union nums{ int x ; float y ; double z ; }; nums varnums ; varnums.y = 200000.125 ; varnums.z = 3000000.3333 ; varnums.x = 10 ; // Despliega el valor 10 y dos valores inesperados. cout << varnums.x << " " cout << " " << varnums.z varnums.x = 10 ; varnums.z = 3000000.3333 varnums.y = 200000.125 ; 200000.125 y cout << varnums.x << " " cout << " " << varnums.z varnums.x = 10 ; varnums.y = 200000.125 ; varnums.z = 3000000.3333 3000000.3333 cout << varnums.x << " " "\n" ; } << varnums.y ; << "\n" ; ; // Despliega un valor inesperado, el valor // un valor inesperado. << varnums.y ; << "\n" ; ; // Despliega dos valores inesperados y << varnums.y ; cout << " " << varnums.z <<

Listado 7.5.- Acceso a los miembros de una union Los miembros de una union pueden ser de cualquier tipo, incluyendo estructuras, como se muestra en el listado 7.6.
#include <iostream.h> #include <dos.h> // Para int86() void main() { REGS regs; unsigned int tam ; int86(0x12, &regs, &regs) ; tam = regs.x.ax ; cout << "\nEl tamao de la memoria convencional es de " ; cout << tam << " KiloBytes\n" ;

Listado 7.6.- Uso de estructuras dentro de uniones. La union REGS est definida en el archivo dos.h de la siguiente forma :
struct WORDREGS { unsigned int ax, bx, cx, dx, si, di, flags ; }; struct BYTEREGS { unsigned char al, ah, bl, bh, cl, ch, dl, dh ; }; union REGS { WORDREGS x ; BYTEREGS h ; };

Puede declararse una union sin etiqueta, y en tal caso se le llama union annima. En el listado 7.7 se muestra un ejemplo.
#include void main() { union { // Ntese la ausencia de etiqueta int a ; char b ; }; // No se ha definido un tipo union, sino una // union annima.

a=1 ; b='X';

// Se accesan los miembros sin utilizar // el operador punto, ya que no existe // un identificador para la union.

// Despliega un valor inesperado y el valor X cout << a << " " << b << "\n"; b="X" ; a="1" ; // Despliega el valor 1 y un valor inesperado cout << a << " " << b << "\n"; }

Listado 7.7.- Manejo de uniones annimas.

8.- Programacin orientada a objetos


La Programacin Orientada a Objetos es un mtodo de programacin que intenta imitar la forma en que modelamos el mundo real y tiene como meta principal mejorar la eficiencia y productividad de los grupos de programadores. Muchos expertos afirman que la Programacin Orientada a Objetos ayuda a reducir la complejidad, especialmente en programas grandes ( de 10,000 lneas o ms ), adems de que permite a los programadores reutilizar el cdigo existente en lugar de reescribir mdulos que ya trabajan correctamente.

8.1.- Conceptos bsicos


Para enfrentarse a las complejidades del mundo real, el ser humano ha desarrollado la capacidad de generalizar, clasificar y generar abstracciones. As, tenemos un vocabulario donde muchos de los sustantivos representan clases de objetos. Los objetos de cada clase comparten ciertos atributos o rasgos de comportamiento. Por ejemplo, al tratarse algn tema relacionado con las aves no es necesario referirnos a alguna caracterstica de una ave en particular sino a los atributos que comparten todas ellas. Los lenguajes de Programacin Orientada a Objetos pueden dividirse en dos grupos: El primero formado por los llamados lenguajes "puros" (llamados as debido a que slo permiten programar con el paradigma de la Programacin Orientada a Objetos) dentro del cual se encuentran SmallTalk y Actor, entre otros. El segundo grupo lo forman los lenguajes que permiten, adems de la Programacin

Orientada a Objetos, la programacin procedimental, razn por la que se les llama "hbridos". A este grupo pertenece el lenguaje C++. Para el manejo de los lenguajes de Programacin Orientada a Objetos es necesario estudiar los conceptos de : ENCAPSULAMIENTO HERENCIA POLIMORFISMO En las siguientes seciones se estudia detalladamente cada una de ellas

8.2.- Encapsulamiento
El encapsulamiento consiste en poner juntos los datos y las funciones dentro de un objeto de tipo clase. El modelo procedimental puede representarse como en la figura 8.1.

Figura 8.1.- Modelo procedimental. donde se observa que los datos y el cdigo se manejan como partes separadas. El programa ( cdigo ) es alimentado con los datos para que produzca resultados. Por otra parte, el modelo orientado a objetos puede representarse como en la figura 8.2.

Figura 8.2.- Modelo de la Programacin Orientada a Objetos. donde DATOS y CODIGO se han unido para formar un OBJETO, el cual va a producir ciertos RESULTADOS, de acuerdo al MENSAJE que se le enve. Esto es, un objeto de ciertas caractersticas va a comportarse de acuerdo a su clase al recibir un mensaje especfico. Como se logra el ENCAPSULAMIENTO en el lenguaje C++ ? Podemos pensar que, as como se requieren tipos para definir las variables en un

enfoque procedimental, se requieren "tipos" o "moldes" para crear los objetos. En el lenguaje C++ esos moldes se crean por medio de las palabras reservadas : class, struct y union . La Geometra representa un campo idneo para ejemplificar el manejo de objetos. Las definiciones para cada una de las figuras se hacen a travs de enunciados que incluyen la expresin: " .. es el lugar geomtrico de todos los puntos que ...". Como puede verse, el concepto de punto es la base para la definicin de cualquier figura geomtrica. La representacin de las figuras geomtricas, en la pantalla de un monitor, se hace por medio de los pixeles, que son los elementos bsicos ( puntos iluminados ) utilizados para representar los grficos en la pantalla. Con base a estos conceptos, en la siguiente seccin vamos a crear una clase base llamada punto. Los objetos de tipo punto podrn situarse en cualquier lugar de la pantalla utilizando un par de coordenadas ( x,y ).

8.2.1.- Definicin de clases


La definicin de la clase punto en C++ puede tomar la siguiente forma :
class punto { int x,y ; // MIEMBROS DATO } ;

Aqu no se est haciendo uso del encapsulamiento, puesto que no se ha declarado ninguna funcin miembro dentro de la clase punto.

8.2.2.- Funciones miembro


En otros lenguajes orientados a objetos, a las funciones miembro se les conoce como mtodos . Para incluir una funcin miembro en la definicin de una clase, existen dos formas : 1.- Definir la funcin dentro de la definicin de la clase. Por ejemplo :
class punto

{ int x,y ; int dax() { return x ; } // FUNCION EN LINEA };

2.- Declarar la funcin dentro de la definicin de la clase y escribir la definicin de la funcin fuera de la definicin de la clase. Por ejemplo :
class punto { int x,y ; int dax() ; // DECLARACION DE LA FUNCION MIEMBRO }; int punto::dax() { return x ; } // DEFINICION DE LA FUNCION MIEMBRO

En la lnea de cabecera de la definicin de la funcin miembro dax() se utiliza el operador de resolucin de ambito :: . Este operador indica que la funcin dax() es una funcin miembro de la clase punto.

8.2.3.- Construtores
Los constructores y los destructores son dos tipos especiales de funciones miembro. Un constructor especifica la manera en que ser creado e inicializado un nuevo objeto de cierta clase. Los constructores en C++ pueden ser definidos por el usuario generados por el lenguaje. El compilador de C++ invoca automticamente al constructor apropiado cada vez que se defina un nuevo objeto. Esto puede ocurrir en una declaracin de datos, cuando se copia un objeto o a travs de la asignacin dinmica de memoria a un nuevo objeto por medio del operador new. Para aadir un constructor a la clase punto escribimos :
class punto { int x,y ; int dax() { return x ; } int day() { return y ; } punto(int nx, int ny) ; // DECLARACION DEL CONSTRUCTOR };

punto::punto(int nx, int ny) // DEFINICION DEL CONSTRUCTOR { x = nx ; y = ny ; }

8.2.4.- Destructores
Los destructores destruyen a los objetos creados, liberando la memoria asignada. Pueden ser invocados explcitamente por medio del operador delete. Siguiendo con el ejemplo de la clase punto :
class punto { int x,y ; int dax() { return x ; } int day() { return y ; } punto(int nx, int nx) ; ~punto() ; // DECLARACION DEL DESTRUCTOR }; punto::punto(int nx, int nx) { x = nx ; y = ny ; } punto::~punto() // DEFINICION DEL DESTRUCTOR { delete x ; delete y ; }

8.2.5.- Ambito de clase


El trmino mbito se refiere al rea dentro de un programa donde un identificador dado es accesible. Para controlar la manera en que son accesados los miembros de las clases, C++ ha introducido el concepto de mbito de clase . Todos los miembros de una clase estn en el mbito de esa clase, de manera que cualquier miembro de una clase puede referenciar a cualquier otro miembro de la misma clase. Las funciones miembro de una clase tienen acceso irrestricto a los miembros dato de esa misma clase. El acceso a los datos y funciones miembro de una clase exterior al mbito de la clase es controlado por el programador a travs de los especificadores de acceso.

8.2.5.1.- Especificadores de acceso


Para el control de acceso a los miembros de una clase, se cuenta con tres palabras clave : private: , protected: y public: . La palabra clave apropiada ( incluyendo los dos puntos ) debe colocarse antes de las declaraciones que van a afectarse.
private: Los miembros que siguen pueden ser accesados solamente por las funciones miembro declaradas dentro de la misma clase. funciones miembro declaradas dentro de la misma clase, y por funciones miembro de clases derivadas de esta clase.

protected: Los miembros que siguen pueden ser accesados por

public:

Los miembros que siguen pueden ser accesados desde cualquier lugar dentro del mismo mbito de la definicin de la clase.

Por predeterminacin, los miembros de las clases de tipo class son private , los de las clases de tipo struct son public. Por ejemplo, para hacer que las funciones miembro de la clase punto puedan ser accesadas desde cualquier lugar, escribimos :
class punto { int x, y ; // privadas POR PREDETERMINACION public: // LO QUE SIGUE ES pblico int dax() { return x; } int day() { return y; } punto ( int nx, int ny ) ; ~punto();

} punto::punto(int nx, int ny) { x = nx ; y = ny ; } punto::~punto() { delete x ; delete y ; }

8.2.5.2.- Acceso privilegiado

Una clase puede darle a otra clase o funcin acceso privilegiado a sus reas privadas. Tal acceso debe darse explcitamente, declarando la otra clase o funcin por medio del modificador friend (amigo). Los amigos son tratados como si fueran miembros de la clase y tienen acceso irrestricto a las reas privadas de los objetos. Por ejemplo :
class cosa { private: int dato ; public: friend void carga( cosa t , int x ) ; }; void carga ( cosa t , int x ) { t.dato = x ; }

La definicin de la clase cosa contiene el prototipo para la funcin carga y la disea como un amigo. La funcin carga no es un miembro de la clase cosa, sino que es una funcin independiente a la que se le ha dado acceso especial a los miembros privados de los objetos de tipo cosa. Debido a que es un amigo de la clase cosa, carga puede accesar directamente los datos miembro privados de su parmetro t. Una clase completa puede hacerse un amigo de otra, tal como en:
class perico ; class paloma { public: friend class perico ; }; class perico { //CUALQUIER COSA };

En el listado 8.1 se muestra un ejemplo de utilizacin de la clase punto. #include <iostream.h> class punto { ...........................int x,y ; ...........................public: punto(int nx, int ny) ...........................{

................................x = nx ; ............................... y = ny ; .......................... } ..................... .....int dax() ......................... .{ ............................... return x ; .......................... } .......................... int day() .......................... { .............................. return y ; ........................... } }; void main() { ...... int datox, datoy ; ...... cout << "COORDENADA EN X : " ; ...... cin>> datox ; ...... cout << "COORDENADA EN y : " ; ...... cin>> datoy ; ...... punto objeto(datox , datoy) ;//INVOCA AL CONSTRUCTOR punto ...... cout << "X=" << objeto.dax() ; ...... cout << '\n' ; ...... cout << " Y=" << objeto.day() ; ...... cout << '\n' ; } Listado 8.1.- Utilizacin de la clase punto.

8.3.- Herencia
En la Programacin Orientada a Objetos, la herencia es un concepto semejante al de la herencia gentica que se d en la naturaleza. En el reino animal, por ejemplo, podemos hablar de una clase base llamada aves, cuyos miembros poseen:
- plumas - alas - pico

Sin embargo, no todas las aves son iguales, por lo que se tiene que recurrir a las clases derivadas para distinguir unas aves de otras, como se muestra en la figura 8.3.

Figura 8.3.- Jerarqua de clases a partir de la clase aves. Cada una de las clases derivadas conserva las caractersticas de la clase base aves, pero posee algunas otras que la distingue de las dems clases. Hasta aqu, hemos hablado solamente de clases de aves no de las aves en s. No nos hemos referido a ninguna ave en particular, por lo que pudieramos decir que hemos estado hablando de los "tipos de aves". Una vez que se tienen los tipos, podemos llevar a cabo la construccin de aves. Cuando tales aves hayan cumplido su misin, podremos destruirlas. Esto ltimo puede parecer demasiado cruel en el mundo animal, pero en el caso de la Programacin Orientada a Objetos es lo ms prctico, pues resultara un gasto innecesario de memoria el conservar los objetos que ya no van a utilizarse . Las clases, al ser consideradas como tipos, pueden utilizarse para construir bloques de programa. Si un tipo no cumple con las especificaciones de un nuevo programa, se puede utilizar para crear otro nuevo. Este nuevo tipo se modifica para usarse en la elaboracin de nuevos objetos, y a su vez queda disponible para elaborar otros tipos, segn se necesite. Es en esta creacin de nuevos tipos donde se llega a la utilizacin de los modelos jerrquicos de clasificacin. En C++, la herencia se maneja por medio de la derivacin de clases.

8.3.1.- Derivacin de clases


La derivacin de clases en C++ se rige por la siguiente sintaxis :
class derivada : modificador_de_acceso { base

..... .....

modificador_de_acceso es opcional y puede ser public o private.

8.3.2.- Modificadores de acceso


Los modificadores de acceso se utilizan para cambiar la accesibilidad de los miembros heredados de acuerdo a lo que se observa en la tabla 8.1 : EN CLASE BASE MODIF. DE ACCESO EN CLASE DERIVADA private private inaccesible protected private private public private private private public inaccesible protected public protected public public public Tabla 8.1.- Modificadores de acceso para las clases derivadas. Cuando se utiliza la palabra class, el modificador de acceso est predefinido como private ; cuando se utiliza la palabra struct, est predefinido como public. Al crear nuevas clases que dependen de clases existentes, debe asegurarse de que entiende la relacin existente entre las clases base y derivada. Para esto es vital el entendimiento de los niveles de acceso conferidos por los especificadores private , protected y public . El C++ permite pasar los derechos de acceso de padres a hijos sin exponer los datos a quienes no son miembros ni amigos de la familia. El nivel de acceso de un miembro de una clase base, no es necesariamente el mismo cuando se maneja en el mbito de la clase base que cuando se maneja en el mbito de la clase derivada. El programador tiene el control sobre los niveles de acceso en todos los mbitos. Como puede observarse en la tabla 8.1, el especificador private convierte los miembros public y protected de la clase base a miembros private en la clase derivada. Los miembros con especificador private en la clase base, mantienen ese especificador en la clase derivada. Aunque en las clases definidas por medio de la palabra class el nivel de acceso esta

predeterminado como private, conviene explicitar los niveles de cada uno de los miembros de cada clase, para evitar confusiones. Por lo tanto, puede escribirse :
class ejemplo { private: int num ; float costo ; public: void valida( char * ) ; int precio( float ) ; };

Una clase derivada hereda todos los miembros de su clase base, pero puede utilizar solamente los miembros protegidos y pblicos de su clase base. Los miembros privados de la clase base no estn disponibles directamente a travs de los miembros de la clase derivada. Al usar la derivacin public, los miembros protegidos de la clase base permanecen protegidos en la clase derivada; as que no estarn disponibles desde el exterior, excepto para otras clases derivadas pblicamente y para los miembros y clases amigos.

8.3.3.- Manejo de clases en mdulos


Las clases pueden empacarse en mdulos para su utilizacin posterior en el desarrollo de programas. Una clase es inherentemente modular con sus miembros dato, funciones miembro y control de acceso. En el desarrollo de un programa, a veces tiene sentido poner las declaraciones, para cada clase o grupo relacionado de clases, por separado en un archivo de cabecera; y en otro las definiciones para las funciones miembro que no estn en lnea. En el listado 8.2 se presenta el archivo de cabecera PIXEL.H , donde, a partir de la clase base punto, se deriva otra clase llamada pixel que nos servir para visualizar u ocultar puntos en cualquier lugar de la pantalla.
/////////////////////////////////////////////////////////// // PIXEL.H : CONTIENE LAS CLASES : // // // // punto QUE MANEJA LA LECTURA Y COLOCACION // // DE UN PUNTO. // // // // pixel MANEJA LA VISIBILIDAD DE UN PUNTO EN //

// LA PANTALLA. // /////////////////////////////////////////////////////////// enum booleano { falso, verdadero } ; class punto { protected: int x , y ; public: //LAS SIGS.FUNCIONES PUEDEN ACCESARSE DESDE //EL EXTERIOR.

//PERMITE QUE LAS CLASES DERIVADAS HEREDEN //DATOS privados.

punto(int nx, int ny) ; int dax(); int day(); }; // CLASE DERIVADA DE punto. LA DERIVACION public SIGNIFICA // QUE x , y ESTARAN PROTEGIDAS DENTRO DE pixel. class pixel : public punto { protected: //LAS CLASES DERIVADAS DE pixel NECESITAN //ACCESO A ESTOS MIEMBROS DATO. booleano visible ; public: pixel(int nx, int ny); // CONSTRUCTOR void mostrar(); void ocultar(); boleano es_visible(); void mover_hacia(int nuevax, int nuevay); };

Listado 8.2.- Archivo PIXEL.H Ntese la utilizacin de protected en lugar de private para ciertos elementos, con lo que se hace posible la utilizacin del archivo PIXEL.H en aplicaciones que manejen clases derivadas de punto y pixel. Las definiciones para las funciones miembro de las clases punto y pixel se almacenan en el archivo PUNTO2.CPP del listado 8.3.
///////////////////////////////////////////////////////////// // PUNTO2.CPP : CONTIENE LA DEFINICION PARA LAS FUNCIONES // // MIEMBRO DE LAS CLASES punto y pixel // ///////////////////////////////////////////////////////////// #include "pixel.h" #include <graphics.h> // DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE punto

punto::punto(int nx , int ny) { x = nx ; y = ny ; }; int punto::dax(void) { return x ; }; int punto::day(void) { return y ; }; // DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE pixel pixel::pixel(int nx, int ny) : punto(nx,ny) { visible = falso ; }; void pixel::mostrar(void) { visible = verdadero ; putpixel(x,y, getcolor()); // USA EL COLOR PREDEFINIDO. // SUPONE QUE EN main() SE // INICIALIZAN LOS GRAFICOS. }; void pixel::ocultar(void) { visible = falso ; putpixel(x,y,getbkcolor()); // USA EL COLOR DEL FONDO. }; booleano pixel::es_visible(void) { return visible ; }; void pixel::mover_hacia(int nuevax, int nuevay) { ocultar(); // HACE INVISIBLE AL PUNTO ACTUAL x = nuevax ; // CAMBIA LAS COORDENADAS x,y . y = nuevay ; mostrar(); // MUESTRA EL PUNTO EN LA NUEVA LOCALIDAD. };

Listado 8.3.- Definicin de las clases punto y pixel. Observe que, para definir el constructor de la clase derivada, se hace referencia al constructor de la clase base como en :

pixel::pixel(int nx,intny) : punto(nx,ny)

Esto significa que, para construir un objeto de la clase pixel, primero se invoca al constructor de la clase base punto con los argumentos nx y ny creando e inicializando los miembros dato x,y. Posteriormente se invoca al constructor pixel, creando e inicializando el miembro dato visible. Note que la referencia al constructor de la clase base aparece en la definicin, no en la declaracin, del constructor de la clase derivada. Si no se ha definido un constructor para una clase x, el C++ generar un constructor predefinido de la forma :
x::x();

esto es, un constructor sin argumentos. El listado 8.4 contiene un programa que muestra la utilizacin de la clase pixel .
//////////////////////////////////////////////////////////// // PIXEL.CPP : APLICACION ELEMENTAL DE LA CLASE pixel // // // //////////////////////////////////////////////////////////// #include <graphics.h> #include <conio.h> #include "pixel.h" int main() { // INICIALIZACION DEL SISTEMA DE GRAFICOS int manej = DETECT, modo ; initgraph ( &manej, &modo, "c:\\borlandc\\bgi"); // ENCIENDE, CAMBIA Y APAGA PIXELES pixel unpixel(100,50) ; // DEFINE unpixel CON VALORES // INICIALES x=100, y=50 unpixel.mostrar(); // unpixel SE ENCIENDE getch() ; // ESPERA UNA PULSACION DE TECLA unpixel.mover_hacia(300,150); // MUEVE unpixel A 300,150 getch(); unpixel.ocultar(); // OCULTA A unpixel getch(); closegraph(); return 0 ; }

Listado 8.4.- Utilizacin de la clase pixel.

Como podr observarse, no se ha incluido el archivo PUNTO2.CPP, por lo que este programa deber compilarse utilizando la opcin PROJECT del Ambiente Integrado de Desarrollo ( IDE ) del C++ de Borland, creando un archivo de proyecto PIXEL.PRJ que incluya los archivos GRAPHICS.H, PUNTO2.CPP y PIXEL.CPP . Uno de los atractivos de las clases es que pueden utilizarse para construir otras clases que respondan a las necesidades de una nueva aplicacin. En el ejemplo que se muestra en el listado 8.5 se hace uso de la clase pixel para derivar una nueva clase llamada circulo, la cual contendr funciones miembro para mostrar, ocultar, mover, expander y contraer circulos.
///////////////////////////////////////////////////////////// // CIRCULO.CPP : MANEJO DE LA CLASE circulo DERIVADA DE LA // // CLASE pixel // ///////////////////////////////////////////////////////////// #include <graphics.h> #include <conio.h> #include "pixel.h" class circulo : pixel { int radio ; // DERIVADA PRIVADAMENTE DE pixel

};

public: circulo(int nx, int ny, int nradio); void mostrar(void); void ocultar(void); void expander(int cantexp); void contraer(int cantcont); void mover_hacia(int nvax, int nvay);

circulo::circulo(int nx,int ny,int nradio) : pixel(nx,ny) { radio = nradio; }; void circulo::mostrar(void) { visible = verdadero; circle(x,y,radio); // DIBUJA EL CIRCULO }; void circulo::ocultar(void) { unsigned int colortemp ; colortemp = getcolor(); setcolor(getbkcolor()); visible = falso ; circle(x,y,radio); }; setcolor(colortemp);

// PARA GUARDA EL COLOR ACTUAL // GUARDA EL COLORR ACTUAL // CAMBIA AL COLOR DEL FONDO // DIBUJA EL CIRCULO CON EL COLOR // DEL FONDO PARA BORRARLO // RESTABLECE EL COLOR ORIGINAL

void circulo::expander(int cantexp) { ocultar(); // BORRA EL CIRCULO ACTUAL radio += cantexp; // EXPANDE EL RADIO if( radio < 0 ) // EVITA RADIOS NEGATIVOS radio = 0 ; mostrar(); // DIBUJA EL NUEVO CIRCULO }; void circulo::contraer(int cantcontr) { expander(-cantcontr); }; void circulo::mover_hacia(int nvax, int nvay) { ocultar(); // BORRA EL CIRCULO ACTUAL x = nvax ; // ASIGNA NUEVAS COORDENADAS y = nvay ; mostrar(); // DIBUJA EL CIRCULO EN LA NUEVA POSICION }; void main() { int manej = DETECT , modo ; initgraph(&manej, &modo, "c:\\borlandc\\bgi"); circulo uncirculo(100,200,50); uncirculo.mostrar(); getch(); uncirculo.mover_hacia(200,250); getch(); uncirculo.expander(50); getch(); uncirculo.contraer(75); getch(); closegraph(); }

Listado 8.5.- Utilizacin de la clase crculo

8.3.4.- Herencia mltiple


Todos los lenguajes de Programacin Orientada a Objetos manejan la herencia en la forma que hemos visto hasta aqu, donde cada clase derivada tiene slo una clase base. Sin embargo, algunos de ellos permiten manejar lo que se llama herencia mltiple, en la cual se permite que una clase derivada pueda tener varias clases base. El mecanismo de herencia mltiple fu una de las principales caractersticas aadidas a la versin 2.0 de C++. Como un ejemplo del manejo de la herencia mltiple, consideremos el problema de

tener que desplegar texto dentro de un crculo. Al principio podemos estar tentados a aadir una cadena de caracteres como miembro dato a la clase circulo y despus agregarle cdigo a circulo::mostrar para que despliegue el circulo con texto adentro. Pero el texto y el crculo son dos cosas diferentes. Cuando se piensa en texto, se piensa en tamaos y tipos de caracteres y en otros atributos que nada tienen que ver con los crculos. Sin embargo, podemos derivar una nueva clase directamente de la clase circulo y de otra clase que permita darle capacidad de manejar texto. En la figura 8.4 se ilustra este enfoque, el cual se detalla en el programa MULTI.CPP del listado 8.6.

Figura 8.4.- Ejemplo de Herencia Mltiple.


//////////////////////////////////////////////// // MULTI.CPP : ILUSTRA LA HERENCIA MULTIPLE // //////////////////////////////////////////////// #include #include #include #include <graphics.h> <conio.h> <string.h> "pixel.h"

class circulo : public pixel { protected: int radio; public: circulo(int nx, int ny, int nradio) ; void mostrar(void); }; class mensaje : public punto // DESPLIEGA UN MENSAJE { char *mens ; int fuente ; int campo ; // MENSAJE A DESPLEGAR // FUENTE A UTILIZAR // TAMAO DEL CAMPO PARA EL TEXTO

};

public: mensaje( int mensx, int mensy, int mensf, int mensc, char *texto ); void mostrar(void); // MUESTRA EL MENSAJE

class multi : circulo, mensaje // HEREDA DE AMBAS { // CLASES. public: multi(int mcircx, int mcircy, int mcircr, int fuente, char *mens); void mostrar(void); // MUESTRA CIRCULO CON MENSAJE }; // DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE circulo circulo::circulo(int nx,int ny, int nradio) : pixel(nx,ny) { radio = nradio ; }; void circulo::mostrar(void) { visible = verdadero ; circle(x,y,radio) ; // DIBUJA EL CIRCULO }; // DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE mensaje mensaje::mensaje(int mensx, int mensy, int mensf, int mensc, char *texto ) : punto(mensx,mensy) { fuente = mensf ; campo = mensc ; mens = texto ; // APUNTA A MENSAJE }; void mensaje::mostrar(void) { int tam = campo / ( 8 * strlen(mens)) ; // 8 PIXELES POR

};

// CARACTER settextjustify(CENTER_TEXT, CENTER_TEXT);// CENTRA TEXTO settextstyle(fuente, HORIZ_DIR, tam) ; //AGRANDA SI tam >1 outtextxy(x,y, mens) ; // DESPLIEGA MENSAJE

// DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE multi multi::multi(int mcircx, int mcircy, int mcircr, int fuente, char *mens) : circulo(mcircx, mcircy, mcircr) , mensaje(mcircx, mcircy, fuente, 2*mcircr, mens) { }; void multi::mostrar(void) { circulo::mostrar(); mensaje::mostrar(); }; // DEFINICION DE LA FUNCION PRINCIPAL void main() { int manej = DETECT, modo ; initgraph(&manej, &modo, "c:\\borlandc\\bgi"); multi uno(250,100,25,SANS_SERIF_FONT,"Tu"); uno.mostrar(); multi dos(250,150,100,TRIPLEX_FONT,"Tierra"); dos.mostrar(); multi tres(250,250,225,GOTHIC_FONT,"Universo"); tres.mostrar(); getch(); closegraph(); }

Listado 8.6.- Ejemplo de herencia mltiple.

8.4.- Poliformismo
Las funciones miembro son las que determinan el comportamiento de un objeto. Esto conlleva la idea de que, si queremos que un objeto se comporte de manera diferente, primero habr que modificar la clase o "molde", agregndole la funcin miembro que determina el comportamiento deseado, para, posteriormente, crear un objeto de la clase modificada. En los ejemplos mostrados hasta aqu, todos los objetos tienen la habilidad necesaria para mostrarse en la pantalla. Sin embargo, cada tipo de objeto difiere de los dems en la forma de mostrarse.

Por ejemplo, un pixel puede mostrarse con slo indicarle las coordenadas x,y correspondientes a su posicin de la pantalla. En cambio, un crculo requiere de la medida del radio adems de las coordenadas x,y de su centro. Esto nos lleva a pensar que para otros objetos tales como: lineas, rectangulos, elipses, etc. , pueden derivarse nuevas clases tomando como base la clase pixel y en las cuales se conservan los miembros dato x,y adems de las funciones miembro dax() y day() definidos en la clase punto ; pero deben definirse nuevas funciones miembro mostrar(), as que, cada vez que se defina una nueva clase derivada de pixel, debe compilarse un nuevo proyecto ( archivo .PRJ ), donde se incluyan los archivos PUNTO2.CPP junto con el archivo correspondiente a la nueva figura ( por ejemplo LINEA.CPP ) para obtener el correspondiente archivo ejecutable ( por ejemplo LINEA.EXE ). Ya sea que hubieramos escrito las funciones mostrar() de todas las clases de figuras en un solo archivo fuente, o que las hubieramos manejado en varios archivos de un proyecto; habramos tenido que volver a compilar cada vez que una nueva clase hubiera sido aadida. Suponiendo que ya se tienen las funciones mostrar() para cada uno de los objetos involucrados, ahora surge la pregunta: A cul de las funcines mostrar() se est haciendo referencia ? Para responder a esto, con los mecanismos del C++ estudiados hasta antes de esta unidad, se pueden utilizar las siguientes formas:
1.- La que realiza la distincin por medio del nombre ampliado. Por ejemplo, mostrar(int,char) no es lo mismo que mostrar(int,float). 2.- La que utiliza el operador de resolucin de mbito, por medio del cual circulo::mostrar() se distingue de pixel::mostrar() y de ::mostrar(). 3.- La que distingue de acuerdo a la clase de objeto que se maneja. Por ejemplo: uncirculo.mostrar() invoca a circulo::mostrar(), mientras que unpixel.mostrar() invoca a pixel::mostrar().

Todas esas resoluciones de funcin se han hecho en tiempo de compilacin, por medio del mecanismo conocido como enlace temprano o esttico. La alternativa en C++ se d a travs del polimorfismo, que consiste en la capacidad que posee el cdigo para comportarse de diversas maneras. Cada forma de comportamiento depender de una situacin especfica, determinada en tiempo de ejecucin, por medio del enlace tardo. El enlace tardo o dinmico trabaja por medio de funciones miembro especiales llamadas funciones virtuales.

8.4.1.- Funciones virtuales


El concepto clave en las funciones virtuales es que las invocaciones a ellas se resuelven en tiempo de ejecucin (de ah el trmino enlace tardo). En nuestro ejemplo, esto significa que la invocacin a la funcin mostrar() adecuada va a realizarse hasta que se conozca el tipo de objeto involucrado. Consideremos la funcin miembro circulo::mover_hacia() definida en CIRCULO.CPP :
void circulo::mover_hacia(int nvax, int nvay) { ocultar(); x = nvax; y = nvay; mostrar(); }

Observamos esta definicin es parecida a la de pixel::mover_hacia() que se encuentra en PUNTO2.CPP :


void pixel::mover_hacia(int nvax, int nvay) { ocultar(); x = nvax; y = nvay; mostrar(); }

El valor de retorno, el nombre de la funcin, el nmero y tipo de argumentos, y hasta el cuerpo de la funcin, son idnticos. Si el C++ encuentra dos llamadas a funcin que tengan el mismo nombre de funcin pero diferentes nombres ampliados, el compilador resuelve las ambigedades. Pero nuestras dos funciones mover_hacia() , a simple vista, no ofrecen al compilador ninguna pista distintiva. Cmo va a saber el compilador a cul funcin llamar ?. Cuando se trata de funciones miembro ordinarias, el compilador determina la funcin objetivo a partir de la clase del objeto involucrado en la llamada. Entonces, porqu no dejar que circulo herede la funcin mover_hacia() desde pixel, tal como hereda las funciones miembro dax() y day(), desde la clase punto va la clase pixel ?. La razn para no hacerlo es que un pixel se mueve de manera diferente a como se mueve un crculo y las funciones ocultar() y mostrar() invocadas en circulo::mover_hacia() no son las mismas que se invocan en pixel::mover_hacia(). Heredar mover_hacia() desde pixel puede llevar a invocar a las funciones ocultar() y mostrar() errneas cuando se intente mover un crculo.

La solucin a este problema es declarar a las funciones ocultar() y mostrar() como funciones virtuales. Esto retardar el enlace para que se hagan las llamadas a las versiones correctas de esas funciones cuando se invoque a la funcin mover_hacia() ya sea para mover un pixel o para mover un crculo (o cualquier otra figura ).

8.4.1.1.- Declaracin de funciones virtuales


Para declarar una funcin como virtual, simplemente hay que anteponer la palabra virtual en la primera declaracin de la funcin miembro. Por ejemplo :
virtual void mostrar(); virtual void ocultar();

Es importante recordar que solamente las funciones miembro pueden ser declaradas como virtual , y que no deben redeclararse en las clases derivadas. Si redeclaramos a la funcin miembro mostrar() con los mismos parmetros y el mismo tipo de retorno, en una clase derivada, la nueva funcin mostrar() automticamente se vuelve virtual, ya sea que se utilice o no el calificador virtual. Se dice que la nueva funcin virtual mostrar() sobreescribe a la funcin mostrar() de la clase base. Tenemos la libertad de redeclarar a la funcin mostrar() con parmetros diferentes ( cambiando o no el tipo de retorno ), pero entonces el mecanismo virtual ser inoperante para esta versin de mostrar(). Al principio, deber evitarse la sobrecarga temeraria, ya que hay situaciones en que una funcin no virtual puede ocultar a una funcin virtual en su clase base. A manera de ejemplo, vamos a crear un mdulo ( Por medio de los archivos FIGURAS.H y FIGURAS.CPP mostrados ms adelante ) que define algunas clases de figuras y crea un mecanismo generalizado para arrastrarlas a travs de la pantalla. El principal objetivo, en el diseo del mdulo, es permitir a los usuarios del mismo extender las clases definidas en l. Al aadir una nueva funcin miembro a una jerarqua de clases existente, surge la siguiente reflexin : En qu lugar de la jerarqua debe ser colocada la nueva funcin miembro ? La funcin arrastrar() debe ser un miembro de la clase pixel, para que todos los objetos de clases derivadas de pixel puedan arrastrase a travs de la pantalla.

Una vez decidido el lugar donde va a colocarse la funcin arrastrar(), vamos a redisear su definicin. Vamos a re-escribir la funcin arrastar() para que las funciones que invoca, tales como: dax(), day(), mostrar(), mover_hacia() y ocultar() , referencien las versiones correspondientes al tipo de objeto que va a ser arrastrado. En la nueva definicin de la clase pixel, del archivo FIGURAS.H mostrado en el listado 8.7, se hacen virtuales a las funciones miembro mostrar() , ocultar() y arrastrar().
//////////////////////////////////////////////////////// // FIGURAS.H : CONTIENE LA DEFINICION DE LAS CLASES // // punto, pixel y circulo // // EN UN SISTEMA A COMERCIALIZAR, ESTA SERIA LA // // UNICA PARTE QUE DEBE DISTRIBUIRSE EN FORMA DE // // CODIGO FUENTE. // //////////////////////////////////////////////////////// enum booleano { falso, verdadero } ; class punto { protected: int x, y ; public: punto(int nx, int ny) { x = nx ; y = ny ; } int dax() { return x ; } int day() { return y ; } }; class pixel : public punto { protected: booleano visible ; public: pixel(int nx, int ny); virtual void mostrar(); virtual void ocultar(); virtual void arrastrar(int narrastre); booleano es_visible() { return visible ; } void mover_hacia(int nvax, int nvay); }; class circulo : public pixel { protected: int radio; public: circulo(int nx, int ny, int nradio); void mostrar(); void ocultar(); void expander(int cantexp); void contraer(int cantcont); };

// PROTOTIPO PARA LA FUNCION NO MIEMBRO leedelta() // DEFINIDA EN EL LISTADO 8.8 booleano leedelta(int& deltax, int& deltay);

Listado 8.7.- Archivo de cabecera para las clases punto,pixel y circulo. El archivo FIGURAS.CPP, mostrado en el listado 8.8, contiene las definiciones para las funciones miembro declaradas en el listado 8.7.
//////////////////////////////////////////////////////////// // FIGURAS.CPP : CONTIENE LAS DEFINICIONES PARA LAS // // FUNCIONES DE LAS CLASES DECLARADAS EN FIGURAS.H // // // // EN UN CONTEXTO COMERCIAL, ESTE ARCHIVO PUEDE DISTRI// // BUIRSE EN FORMA DE OBJETO ( .OBJ ). // //////////////////////////////////////////////////////////// #include <graphics.h> #include <conio.h> #include "figuras.h" // DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE pixel pixel::pixel(int nx, int ny) : punto(nx,ny) { visible = falso ; } void pixel::mostrar() { visible = verdadero; putpixel(x,y,setcolor()); } void pixel::ocultar() { visible = falso; putpixel(x,y, getbkcolor()); } void pixel::mover_hacia(int nvax, int nvay) { ocultar(); x = nvax ; y = nvay ; mostrar(); } void pixel::arrastrar(int narrastre) { int deltax, deltay ; int figx, figy ; mostrar() ; // DESPLIEGA LA FIGURA A SER ARRASTRADA figx = dax(); figy = day(); // ESTE ES EL CICLO DE ARRASTRE while(leedelta(deltax,deltay))

{ figx += (deltax * narrastre) ; figy += (deltay * narrastre) ; mover_hacia(figx, figy) ;

}; } // LA SIGUIENTE ES UNA FUNCION DE PROPOSITOS GENERALES QUE // UTILIZA LAS TECLAS DE FLECHAS PARA MOVER EL CURSOR. booleano leedelta(int& deltax, int& deltay) { char car; booleano salir; deltax = 0 ; deltay = 0 ; do{ car = getch() ; // LEE LA TECLA if(car == 13) // RETORNO DE CARRO return falso ; if(car == 0 ) // CODIGO EXTENDIDO { salir = verdadero ; // ASUME QUE ES UTILIZABLE car = getch() ; // TOMA EL RESTO DEL CODIGO switch(car) { case 72 : deltay = -1 ; break ; // ARRIBA case 80 : deltay = 1 ; break ; // ABAJO case 75 : deltax = -1 ; break ; // IZQ. case 77 : deltax = 1 ; break ; // DER. default : salir = falso ; // OTRA }; } } while( !salir ) ; return verdadero; } // DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE circulo circulo::circulo(int nx, int ny, int nradio) : pixel(nx, ny) { radio = nradio; } void circulo::mostrar() { visible = verdadero; circle(x,y,radio); } void circulo::ocultar() { unsigned int colortemp; colortemp = getcolor(); setcolor(getbkcolor()); visible = falso; circle(x,y, radio); setcolor(colortemp); }

void circulo::expander(int cantexp) { ocultar(); radio += cantexp; if(radio <0) radio="0;" mostrar(); } void circulo::contraer(int cantcont) { expander(-cantcont); }

Listado 8.8.- Definiciones de las funciones para las clases punto, pixel y circulo. Ahora vamos a probar las nuevas clases punto, pixel y circulo utilizndolas para derivar una clase llamada arco que se encuentra definida en el archivo POLIMORF.CPP del listado 8.9.
//////////////////////////////////////////////////////////// // POLIMORF.CPP : CONTIENE LA DEFINICION DE LA CLASE arco // // QUE SE DERIVA DE circulo. // // Para compilar, forme un proyecto con FIGURAS.OBJ y // // POLIMOFIRSMO // //////////////////////////////////////////////////////////// #include <graphics.h> #include <conio.h> #include "figuras.h" class arco : public circulo { int anginic, angfin ; public: arco(int nx, int ny, int nradio, int nanginic, int nangfin) : circulo(nx, ny, nradio) { anginic = nanginic; angfin = nangfin; } void mostrar() ; // ESTAS FUNCIONES SON VIRTUALES EN // pixel void ocultar() ; }; // DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE arco void arco::mostrar() { visible = verdadero ; arc( x, y, anginic, angfin, radio ) ; } void arco::ocultar() { int colortemp; colortemp = getcolor(); setcolor(getbkcolor()); visible = falso ; arc(x, y, anginic, angfin, radio);

setcolor(colortemp); } // DEFINICION DE LA FUNCION PRINCIPAL void main() { int manej =DETECT, modo ; initgraph( &manej, &modo, "c:\\borlandc\\bgi"); circulo uncirculo(151,82,50); arco unarco(151,82,25,0,190); unarco.arrastrar(10) ; // LA FUNCION arrastar() SE APLICA // A UNA FIGURA QUE NO CONOCE. unarco.ocultar(); uncirculo.arrastrar(10); closegraph(); getch(); }

Listado 8.9.- Definicin y uso de la clase arco.

8.5.- Objetos dinmicos


En los ejemplos mostrados anteriormente se utilizaron objetos cuyo espacio de almacenamiento se manej en la pila. En esta seccin trataremos con objetos dinmicos, cuyo espacio de almacenamiento en el montculo se asigna y se libera en tiempo de ejecucin por medio de los operadores new y delete. Por ejemplo, si existe una clase llamada punto, podemos escribir:
punto* ap ; ap = new punto(10,20);

En la primera lnea se est declarando un apuntador llamado ap, que servir para apuntar a objetos de clase punto . En la segunda lnea se gestiona un bloque de memoria lo suficientemente grande para almacenar un objeto de la clase punto, se invoca al constructor punto pasndole los argumentos 10,20 . La direccin de inicio del bloque gestionado se asigna al apuntador ap. Las dos lneas anteriores pueden sustituirse por:
punto* ap = new punto(10,20);

En el listado 8.10 se presenta un ejemplo del manejo de dos objetos dinmicos, donde, adems, se utiliza el polimorfismo.

En 8.10 puede observarse que se declara el apuntador ap para que apunte a objetos de clase numero y, sin embargo, ese mismo apuntador se utiliza para apuntar a objetos de clase division. Esto se debe a que, en C++, un apuntador declarado para apuntar a objetos de una clase base puede utilizarse para apuntar a objetos de una clase derivada. Esta regla no es vlida en el sentido inverso; esto es, un apuntador declarado para apuntar a objetos de una clase derivada no puede utilizarse para apuntar a objetos de la clase base, por lo que:
division* ap; ap = new numero(10);

produce un error.
/////////////////////////////////////////////////////// // OBJDIN.CPP : Muestra el uso de objetos dinmicos. // /////////////////////////////////////////////////////// #include <iostream.h> class numero { protected: int valor; public: numero(int numdado) { valor = numdado;} virtual int davalor(){return valor;} }; class division : public numero { protected: int divisor; public: division(int d, int n) : numero(d) {divisor=n;} virtual int davalor() {return valor/divisor;} }; void main() { numero* ap; ap = new numero(15); cout << ap->davalor() << '\n'; //Despliega 15 delete ap; ap = new division(15,2); cout << ap->davalor() << '\n'; // Despliega 7 }

Listado 8.10.- Ejemplo de utilizacin de objetos dinmicos.

9.- Archivos
En C++, el manejo de archivos se realiza a travs de los flujos de datos ( streams ). La elegancia y simplicidad de los flujos de datos est disponible tambin para los archivos creados por el usuario, tanto en modo texto como en modo binario. Hay varias clases en la libreria que tratan con archivos, pero las clases ms usadas son ifstream para lectura y ofstream para escritura de archivos. Las clases para los flujos de datos en archivos tienen funciones para abrir y cerrar archivos, leer y escribir lneas de texto, etc.

9.1.- Archivos de texto


Un archivo de texto es una secuencia de caracteres ASCII divididos en lneas, cuyo ltimo carcter es el de nueva lnea ( \n ). Debido a la sencillez en su manejo, la mayora de los programas utilizan archivos de texto. En el listado 9.1 se muestra un programa que abre un archivo de texto en el modo de lectura, checa la posibilidad de error en la apertura, y despliega el contenido del archivo.
////////////////////////////////////////////////// // LARCHTEX.CPP : Lee un archivo de texto // ////////////////////////////////////////////////// #include <iostream.h> #include <fstream.h> void main() { // Abre un archivo que supuestamente existe ifstream entrada("LARCHTEX.CPP"); // Verifica la existencia del archivo if(!entrada) { cout << "\nNo puede abrirse el archivo"; return; } // Despliega el contenido del archivo char maux[100];

while(entrada) { entrada.getline(maux,sizeof(maux)); cout << "\n" << maux; } }

Listado 9.1.- Lectura de un archivo de texto existente. En el listado 9.2 se muestra la manera en que un archivo de texto existente es copiado a un archivo existente o de nueva creacin.
///////////////////////////////////////////////////// // GARCHTEX.CPP : Graba un archivo de texto. // ///////////////////////////////////////////////////// #include <iostream.h> #include <fstream.h> void main() { // Abre un archivo en el modo de lectura ifstream entrada("GARCHTEX.CPP"); // Verifica la existencia del archivo a leer if(!entrada) { cout << "\nNo puede abrir el archivo GARCHTEX.CPP\n"; return; } // Abre otro archivo en modo de escritura ofstream salida("GARCHTEX.BAK"); // Verifica la posibilidad error al abrir el archivo // de escritura if(!salida) { cout << "\No puede abrir el archivo GARCHTEX.BAK\n"; return; } // Copia el archivo GARCHTEX.CPP en GARCHTEX.BAK int cnt="0;" char maux[80]; while(entrada && salida) { cnt++; entrada.getline(maux,sizeof(maux)); salida << maux << "\n"; } cout << "\n" << cnt << " lneas copiadas\n";

Listado 9.2.- Escritura en un archivo en modo texto.

9.2.- Archivos binarios


Los archivos binarios son diferentes a los archivos de texto principalmente porque los datos no estan organizado en lneas con terminadores de nueva lnea, y porque pueden contener cualquier valor de ocho bits. Sin embargo, los archivos binarios se abren en la misma forma que los archivos de texto, excepto por la bandera ios::binary que se pasa a la funcin de apertura, como se muestra en los listados 9.3 y 9.4.
///////////////////////////////////////////////////// // GARCHBIN.CPP : GRABA UN ARCHIVO EN MODO BINARIO // ///////////////////////////////////////////////////// #include <iostream.h> #include <fstream.h> struct empleado { int numero; char nombre[31]; float sueldo; }; void main() { ofstream salida("EMPRESA.DAT", ios::binary); if(!salida) { cout << "\nNo puede abrirse el archivo EMPRESA.DAT\n"; return; } empleado unempleado; do { cout << "Nmero ( 0="Fin" ) : "; cin>> unempleado.numero; cin.ignore(); if(!unempleado.numero) return; cout << "Nombre : "; cin.getline(unempleado.nombre,31); cout << "Sueldo : "; cin>> unempleado.sueldo; cin.ignore(); salida.write((char*) &unempleado,sizeof(unempleado)); }while(unempleado.numero != 0); }

Listado 9.3.- Escritura de un archivo en modo binario.

///////////////////////////////////////////////////// // LARCHBIN.CPP : Lee un archivo en modo binario // ///////////////////////////////////////////////////// #include <iostream.h> #include <fstream.h> #include <iomanip.h> struct empleado { int numero; char nombre[31]; float sueldo; }; void main() { ifstream entrada("EMPRESA.DAT", ios::binary); if(!entrada) { cout << "\nNo puede abrirse el archivo EMPRESA.DAT\n"; return; } empleado unemp; cout << setiosflags(ios::showpoint | ios::fixed) << setprecision(2) << '\n'; cout << setiosflags(ios::left) << setw(6) << "NUM." << setw(35) << "N O M B R E" << setw(10) << "SUELDO" << "\n="==========================" << setfill('.'); while(entrada) { if(entrada.read((char*) &unemp,sizeof(unemp))) { cout << setiosflags(ios::left) << setw(6) << unemp.numero << setw(35) << unemp.nombre << setiosflags(ios::right) << " $ " << setw(10) << unemp.sueldo << '\n'; } } }

Listado 9.4.- Lectura de un archivo en modo binario. Tambin es posible abrir un archivo de texto en modo binario, como se muestra en el listado 9.5.
//////////////////////////////////////////////////////////// // LARCHBI2.CPP : Lee un archivo de texto en modo binario // //////////////////////////////////////////////////////////// #include <iostream.h> #include <fstream.h> void main() { // Abre un archivo en modo binario

ifstream entrada("LARCHBI2.CPP", ios::binary); // Verifica la existencia del archivo if(!entrada) { cout << "\nNo puede abrirse el archivo"; return; } // Despliega el contenido del archivo char c; while(entrada) { entrada.get(c); cout << c ; } }

Listado 9.5.- Lectura de un archivo de texto en modo binario.

S-ar putea să vă placă și