Documente Academic
Documente Profesional
Documente Cultură
/ | ENCABEZADO < | \
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()
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.
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))
Las macros pueden tomar formas complejas y ocupar varias lneas de cdigo, como se muestra en el siguiente ejemplo:
Obsrvese que al final de la primera lnea se escribi una diagonal invertida ( \ ), la cual sirve como enlace.
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.
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.
@ @ @ @ + + @ @ + + + @
@ @ + @ +
+ +
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
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()
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);
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 : % .
...........int utilidad;
...........utilidad = 30;
desplegar :
La utilidad es del 30 %
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.
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.
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; }
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.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
donde: pera, uva, manzana y fresa son constantes de enumeracin y tienen los siguientes valores:
= = = =
0 1 2 3
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.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:
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
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
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 )
>>
( Desplazamiento a la DERECHA )
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) .
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.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 ;
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
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 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"; } }
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
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
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.
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 :
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
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.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.
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&
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
// 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; }
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; }
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)); }
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
3 * (factorial(2)) = 3 * 2 = 6
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.
identificador es el nombre del arreglo expresin_constante es una expresin que al reducirse debe dar como resultado un valor entero positivo.
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
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 ; ; ; ; ;
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
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.
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.
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]; } }
#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.
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.
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]); }
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.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.
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.
Listado 6.1.- Ejemplo de variables automticas y apuntadores. Las instrucciones del listado 6.1 se traducen en la siguiente secuencia, donde los apuntadores se representan con una flecha (para simular que "apuntan hacia" "sealan" un objeto) y los objetos apuntados se representan por un cuadro (para simular un recipiente).
INSTRUCCION int automatica ; REPRESENTACION GRAFICA automatica |----------------| | ? | |----------------| ----> ? automatica |----------------|
| 100 | |----------------| apunt = &automatica ; automatica, *apunt |----------------| apunt ---->| 100 | |----------------| automatica, *apunt |----------------| ---->| 200 | |----------------|
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 ;
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.
*(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.
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().
// 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.
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
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(); }
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.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];
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 ).
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(); }
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.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++ ; }
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 .
// 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 = 100 ; // 3
**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 + *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:
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.
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.
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.
En este caso, catalogo es un nuevo tipo que puede utilizarse para declarar una estructura como:
catalogo refac ;
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; }
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.
};
"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) ; }
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' : } } }
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(); }
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.
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, ®s, ®s) ; 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"; }
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 ).
Aqu no se est haciendo uso del encapsulamiento, puesto que no se ha declarado ninguna funcin miembro dentro de la clase punto.
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 };
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 ; }
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();
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.
..... .....
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.
// LA PANTALLA. // /////////////////////////////////////////////////////////// enum booleano { falso, verdadero } ; class punto { protected: int x , y ; public: //LAS SIGS.FUNCIONES PUEDEN ACCESARSE DESDE //EL EXTERIOR.
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 :
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 ; }
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(); }
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.
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(); }
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.
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 ).
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))
}; } // 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(); }
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 }
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.
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";
///////////////////////////////////////////////////// // 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 ; } }