Sunteți pe pagina 1din 252

75.

42 - Taller de Programacin

TALLER DE PROGRAMACION I

Copyright (C) Gabriel Agustn Praino.


El presente material se encuentra registrado en el Registro Nacional de Propiedad Intelectual. Prohibida la reproduccin total y/o parcial sin autorizacin escrita, con excepcin de las expresamente autorizadas en esta licencia. Se autoriza el uso, distribucin y/o reproduccin del presente material, sin fines de lucro, como capacitacin personal, sujeto a las condiciones mencionadas a continuacin. Se autoriza el uso, distribucin y/o reproduccin del presente material en toda entidad de enseanza pblica y gratuita, sujeta a las condiciones mencionadas a continuacin. Condiciones de distribucin y reproduccin No podr cobrarse ningn importe ni servicio, bajo ningn concepto, por la distribucin y/o reproduccin de este material. Toda reproduccin de este material deber realizarse en forma completa y sin modificaciones. Toda reproduccin de este material deber mantener el presente copyright. Se prohibe expresamente todo uso, distribucin y/o reproduccin de este material con fines comerciales, sin autorizacin escrita.

75.42 - Taller de Programacin

PARTE I - El lenguaje de programacin C..........................................................................................7 Captulo I - Comenzando......................................................................................................................7 Introduccin......................................................................................................................................7 Estructura de un Programa C............................................................................................................7 Primer programa en C.......................................................................................................................9 Turbo C / Borland C para D.O.S..................................................................................................9 Borland C para Windows.............................................................................................................9 Microsoft C / Visual C para Windows.........................................................................................9 Agregando comentarios en C..........................................................................................................11 Tipos de Datos................................................................................................................................11 Definicin y alcance de las Variables.............................................................................................12 La funcin printf()..........................................................................................................................14 Algunos operadores........................................................................................................................16 Ejemplos de programas en C..........................................................................................................17 Resumen.........................................................................................................................................18 Captulo II - Funciones y prototipos...................................................................................................19 Definicines de constantes.............................................................................................................22 Declaraciones vs. definiciones.......................................................................................................24 Resumen.........................................................................................................................................27 Captulo III - Sentencias de control....................................................................................................28 Sentencias de control de programa (if)...........................................................................................28 Recursividad...................................................................................................................................30 Evaluacin de condiciones.............................................................................................................31 Sentencias de control de programa (switch-case)...........................................................................33 ifs anidados.....................................................................................................................................36 Sentencias de control de programa (while y do-while)..................................................................37 Sentencias de control de programa (for).........................................................................................38 Incluso pueden omitirse los tres parmetros de la sentencia for, escribiendo: for (;;). En este caso, este cdigo ser equivalente a: while (1)........................................................................................39 Sentencias break y continue............................................................................................................39 Sentencia goto.................................................................................................................................41 Operador ?......................................................................................................................................42 Resumen.........................................................................................................................................43 Captulo IV - Asignaciones y operadores...........................................................................................43 Operadores......................................................................................................................................44 Operadores y asignaciones..............................................................................................................46 Notacin entera...............................................................................................................................47 Captulo V - Definicin de tipos y conversiones................................................................................49 Estructuras......................................................................................................................................49 Enumeraciones................................................................................................................................50 Uniones...........................................................................................................................................51 Definicin de nuevos nombres para tipos de datos........................................................................53 Convirtiendo tipos de datos (cast)..................................................................................................53 Captulo VI - Vectores........................................................................................................................56 Pasaje de vectores a funciones........................................................................................................60 Ms sobre el pasaje de vectores a funciones..................................................................................61 Captulo VII - Matrices.......................................................................................................................65 Pasaje de matrices a funciones.......................................................................................................67 Captulo VIII - Punteros.....................................................................................................................70 Introduccin....................................................................................................................................70

75.42 - Taller de Programacin

Qu es un puntero?.......................................................................................................................70 Primer ejemplo con punteros..........................................................................................................73 Nota de sintaxis..........................................................................................................................74 Ms acerca del pasaje de variables a funciones..............................................................................76 Captulo IX - Memoria dinmica y punteros a vectores.....................................................................80 Pasaje de vectores a funciones........................................................................................................81 Aritmtica de punteros....................................................................................................................82 El modificador const y los punteros...............................................................................................85 Captulo X - Punteros y estructuras de datos......................................................................................88 Captulo XI - Archivos.......................................................................................................................90 Streams...........................................................................................................................................92 Entrada/salida standard...................................................................................................................93 Entrada/salida sin streams..............................................................................................................93 Captulo XII - Temas varios de C.......................................................................................................94 Variables static...............................................................................................................................94 Variables volatile............................................................................................................................94 Cmo recibir parmetros de la lnea de comandos?.................................................................95 Punteros a funciones.......................................................................................................................96 El preprocesador.............................................................................................................................97 Captulo XIII - Estructuras de datos.................................................................................................101 Listas enlazadas............................................................................................................................101 PARTE II - El lenguaje de programacin C++.................................................................................106 Captulo XIV - Introduccin a C++..................................................................................................106 Introduccin..................................................................................................................................106 C vs C++.......................................................................................................................................106 Captulo XV - Comenzando a programar en C++............................................................................108 Algunas variantes de C++.............................................................................................................108 Nuevos tipos de datos...............................................................................................................112 Captulo XVI - Programacin orientada a objetos...........................................................................113 PROGRAMACIN ORIENTADA A OBJETOS........................................................................113 Objetos......................................................................................................................................113 Polimorfismo............................................................................................................................113 Herencia....................................................................................................................................113 LAS CLASES...............................................................................................................................114 Creacin de la estructura Pila.......................................................................................................115 Captulo XVII - Parmetros por defecto y sobrecarga de funciones................................................125 PARMETROS POR DEFECTO...............................................................................................125 SOBRECARGA DE FUNCIONES..............................................................................................125 Captulo XVIII - Herencia................................................................................................................128 HERENCIA..................................................................................................................................128 Introduccin a la herencia mltiple..............................................................................................132 Distribucin en memoria de atributos, en clases herederas..........................................................133 Captulo XIX - Casting de objetos...................................................................................................134 Casting de objetos.........................................................................................................................134 Nota..........................................................................................................................................134 Tipos de casting entre objetos......................................................................................................135 Los constructores y la herencia.....................................................................................................137 Captulo XX - Sobrecarga de operadores y funciones friend...........................................................138 OPERADORES............................................................................................................................138 Tabla 2: Operadores sobrecargables.........................................................................................142 PARTE III - Programacin avanzada en C++..................................................................................147 Captulo XXI: Entrada/Salida en C++..............................................................................................147 3

75.42 - Taller de Programacin

Mtodos constantes......................................................................................................................148 Captulo XXII - Templates...............................................................................................................150 Clases template dentro de clases template....................................................................................156 EL PREPROCESADOR..............................................................................................................157 Captulo XXIII - Un poco ms sobre herencia - Herencia mltiple.................................................159 Captulo XXIV - Mtodos virtuales y funciones puras y abstractas.................................................163 LA HERENCIA VIRTUAL Y EL COMPILADOR....................................................................165 Captulo XXV - Utilizacin de memoria dinmica en clases - Constructor de copia......................167 La memoria dinmica y los objetos..............................................................................................167 Constructor de copia.....................................................................................................................170 Otro ejemplo.................................................................................................................................172 Objetos temporales.......................................................................................................................180 Resumen de puntos a tener en cuenta...........................................................................................182 Captulo XXVI - Memoria compartida entre objetos.......................................................................184 Captulo XXVII - Sobrecarga de operadores new y delete...............................................................186 Alternativa 1:............................................................................................................................188 Alternativa 2:............................................................................................................................189 Captulo XXVIII - Manejo de excepciones......................................................................................191 APNDICE I - Palabras reservadas de C.........................................................................................196 Palabras reservadas de C..............................................................................................................196 APNDICE II - Palabras reservadas de C++...................................................................................196 Palabras reservadas de C++..........................................................................................................196 Apndice III - Caracteres reservados................................................................................................198 Apndice IV - EL PREPROCESADOR...........................................................................................199 Macros predefinidas.....................................................................................................................199 Directivas del preprocesador........................................................................................................199 Apndice V - Mezclando cdigo C y C++.......................................................................................200 Apndice VI - Resumen de funciones especiales de C++................................................................202 PARTE IV WINDOWS API.........................................................................................................203 Captulo I - Introduccin a la programacin bajo Windows............................................................203 Introduccin..................................................................................................................................203 La filosofa de programacin Windows.......................................................................................203 Qu es Windows?.......................................................................................................................204 Muy bien, pero qu hay de nuevo?.............................................................................................204 Captulo II - Comenzando................................................................................................................209 Comenzando.................................................................................................................................209 Primer Ejemplo de programa Windows.......................................................................................211 Archivo EJEMPLO1.C.............................................................................................................211 Archivo RESOURCE.H...........................................................................................................213 Archivo EJEMPLO1.RC..........................................................................................................213 Analicemos el programa...........................................................................................................216 Analicemos ahora el cdigo C..................................................................................................217 Quin procesa los mensajes restantes?.......................................................................................221 Mltiples ventanas........................................................................................................................222 Ventanas modales.........................................................................................................................223 Un poco ms sobre ventanas........................................................................................................223 Ventanas STATIC:...................................................................................................................224 Ventanas BUTTON:.................................................................................................................224 Ventanas EDIT:........................................................................................................................224 Ventanas SCROLLBAR:..........................................................................................................225

75.42 - Taller de Programacin

Ventanas LISTBOX y COMBOBOX:......................................................................................225 Dibujando en pantalla...................................................................................................................226 Ejemplo2.c................................................................................................................................226 Ejemplo2.rc..............................................................................................................................227 resource.h..................................................................................................................................229 Redibujando la ventana............................................................................................................230 Captulo III - Creacin de una ventana.............................................................................................233 Paso 1: Registrar un modelo de ventana bsico.......................................................................233 Paso 2: Crear la ventana...........................................................................................................234 El ciclo de mensajes y threads......................................................................................................236 La necesidad de una nueva estructura de programacin...............................................................238 Captulo IV - Hacia un nuevo modelo de programacin Windows..................................................245 La necesidad de un nuevo modelo de programacin Windows....................................................245 Borland - OWL (Object Windows Library)..................................................................................245 Microsoft MFC.............................................................................................................................248 Paso 1: Creacin del proyecto:.................................................................................................248 Paso2: Agregar una variable String y mostrarla.......................................................................249 Paso3: Agregar un dialogo........................................................................................................251

75.42 - Taller de Programacin

75.42 - Taller de Programacin

PARTE I - El lenguaje de programacin C


Captulo I - Comenzando
Introduccin

El C es un lenguaje de programacin estructurado, tambin llamado funcional. Esto quiere decir que el mismo estar formado por un conjunto de variables y funciones, que se llamarn unas a otras.

A estas funciones y variables de un programa se las identifica mediante un nombre llamado identificador, que puede estar compuesto de caracteres alfanumricos (comenzando en una letra), y se distinguen las maysculas de las minsculas. Por ejemplo, las variables "Base", "BASE" y "base" sern diferentes. Tambin son vlidas variables como "registro_2", pero no "2_registro" ni "registro 2".

Si bien la longitud de los mismos es arbitraria, C define que tan slo los primeros 32 caracteres sern considerados.

De todas las funciones que componen un programa, existe una especial llamada main. Esta funcin ser llamada por el sistema operativo y ser por lo tanto el punto de entrada al programa (el lugar donde empieza la ejecucin). Cuando esta funcin termina, el programa se finaliza y se devuelve el control al sistema operativo.

Estructura de un Programa C

Un programa en C consta de las siguientes partes, si bien este orden no es estricto en absoluto:

Archivos a incluir

Declaraciones de tipos de datos, estructuras, constantes y macros

Definiciones de variables globales (no utilizar indiscriminadamente)

Prototipos de funciones

Funciones del programa

75.42 - Taller de Programacin

Cada una de estas partes se irn viendo ms adelante. Por ahora basta saber que estas funciones estarn formadas a su vez por llamadas a otras funciones o a si mismas, de sentencias de control y/o de asignaciones.

En C no hay distincin entre funciones y procedimientos como en otros lenguajes. De hecho, solamente existen funciones, si bien los procedimientos pueden ser fcilmente implementase mediante funciones. Las funciones en C pueden recibir un nmero arbitrario de parmetros (incluso variable) y pueden devolver un nico valor.

Toda funcin se define como sigue: Tipo_de_dato_devuelto nombre_funcion (lista_de_parametros) { Cuerpo de la funcin } Si la funcin no devuelve ningn dato, o no toma argumentos, debe utilizarse la palabra reservada void.

En C toda instruccin o sentencia debe terminarse con punto y coma (;), y el cuerpo de una funcin (proposicin) se encierra entre llaves.

75.42 - Taller de Programacin

Primer programa en C

Debido a la gran variedad de compiladores disponibles en el mercado, sera una tarea muy extensa explicar aqu la forma en que un programa puede ser compilado. Sin embargo, haremos algunos comentarios para los compiladores ms usados:

Turbo C / Borland C para D.O.S

Puede escribirse el programa en un nico archivo, y desde el men compilar el programa. Puede tambin crearse un proyecto (project) que incluya varios archivos.

Borland C para Windows

Puede escribirse el programa en un nico archivo y compilarlo desde el men. Se generar un programa que correr en una ventana Windows o DOS (dependiendo de la versin del compilador). Puede tambin crearse un proyecto (project) que incluya varios archivos.

Microsoft C / Visual C para Windows

Debe crearse un proyecto (WorkSpace), en el cual debe incluirse el archivo .c/.cpp a compilar.

Veamos nuestro primer programa en C

Ejemplo 1.1: Programa 'nada.c'

void main (void) { } Este es el programa ms sencillo que puede escribirse, y que no hace nada.

Simplemente se define una funcin de nombre main, que no toma ningn parmetro y no devuelve nada. Finalmente, ya que el cuerpo de la funcin es {}, la misma no hace nada.

El nombre de la funcin, main, no es casual. Todo programa debe tener una y tan slo una funcin main, que es donde comienza la ejecucin del programa. Es decir, al ejecutarse el programa, lo que hace el sistema operativo es llamar a la funcin main.

La funcin main() constituye el punto de entrada a un programa C.

75.42 - Taller de Programacin

Ahora bien, pasemos a algn programa que s haga algo:

Ejemplo 1.2: Programa 'hola.c' #include <stdio.h> void main (void) { printf ("Hola mundo\n"); } La salida de este programa es la siguiente: Hola mundo La primer lnea del programa, indica que debe incluirse un archivo, stdio.h, donde se define la entrada/salida standard de C, entre las cuales est la funcin printf (). En nombre del archivo est escrito entre smbolos <>, lo que indica que debe buscrselo en el directorio include. En algunos compiladores, este directorio se especifica mediante una instruccin SET include = ... en el sistema operativo, otros mediante una opcin en algn men desplegable, y otros leen la variable PATH del sistema operativo. Si el nombre del archivo se escribiese entre comillas (""), el archivo a incluir se buscara en primer lugar en el directorio actual.

La lnea que contiene la palabra printf() consiste en una llamada a la funcin printf, pasndole como parmetro el texto Hola mundo\n.

La funcin printf() (print with format) puede tomar un nmero cualquiera de parmetros, como se ver ms adelante.

El carcter \ (barra invertida) est reservado para imprimir caracteres especiales. El carcter % tambin est reservado, pero su uso se ver ms adelante. Los principales caracteres especiales son: \n \t \r \\ \0 salto de lnea tabulacin horizontal retorno de carro barra invertida byte 0

Veamos otro programa en C

Ejemplo 1.3: Ms acerca de la salida standard #include <stdio.h> void main { printf printf printf } (void) ("Hola mundo del C\nEste es mi "); ("primer programa en C.\n"); ("Ahora dejar una lnea en blanco\n\ny seguir escribiendo.\n");

10

75.42 - Taller de Programacin La salida de este programa es la siguiente:

Hola mundo del C Este es mi primer programa en C. Ahora dejar una lnea en blanco y seguir escribiendo Notar que pueden insertarse saltos de lnea (\n) en medio del texto, y no es necesario terminar una lnea en \n, como tampoco es obligatorio escribir una lnea en un nico printf(). Sin embargo, en muchos sistemas operativos el texto no ser impreso en pantalla hasta no encontrar un \n.

Agregando comentarios en C

Los comentarios en C se escriben entre smbolos /* y */. Los mismos pueden contener dentro, cualquier tipo de smbolo o carcter ascii, incluso saltos de lnea.

Agregumosle comentarios al programa anterior:

Ejemplo 1.4: Comentarios en un programa

/***********************\ * Programa de prueba * \***********************/ #include <stdio.h> /* Aqui comienza el programa */ void main (void) { /*********** Comienzo del programa ******/ printf ("Hola mundo\n"); /* Impresin en pantalla */ } Todo texto encerrado entre smbolos /* y */ ser interpretado como un comentario y no ser compilado

Tipos de Datos

C soporta los siguientes tipos de datos bsicos, definidos por el compilador. Notar que puede variar el significado de cada uno de ellos segn la plataforma sobre la que se est trabajando. Tipo de dato char unsigned char short int unsigned short int int Significado Caracter / Entero (-128 a 127) Caracter / Entero (0 a 255) Entero reducido (-127 a 128 -32768 a 32767) Entero reducido (0 a 255 0 a 65535) Entero con signo Longitud habitual (en bits) 8 8 8 16 8 16 16 32

11

75.42 - Taller de Programacin

unsigned int ( unsigned) long int unsigned long int ( unsigned long) long long long64 _int64 float double long double void

Entero sin signo Entero largo con signo Entero largo sin signo Entero largo. No forma parte del standard pero varios compiladores lo implementan. Punto flotante IEEE Punto flotante doble precisin Punto flotante doble precisin Sin valor

16 32 32 32 64 32 64 80 96 indeterminado

El tipo de dato puede variar de un sistema a otro. Por ejemplo, un entero (int), se compone de 16 bits en sistemas operativos de 16 bits, y de 32 bits en sistemas.

Si bien tantos tipos de datos pueden parecer difcil de recordar, basta saber que existen bsicamente dos tipos de datos: enteros y de punto flotante (reales), llamados int y float/double respectivamente, y que existen modificadores (prefijos) unsigned, long, etc, para controlar el uso de signo y su longitud. Existe tambin el prefijo signed, opuesto a unsigned, pero rara vez se lo usa ya que, por defecto las variables son con signo (puede especificarse al compilador que no sea as, pero por convencin nunca se lo hace)

Debe hacerse una aclaracin con el tipo de dato char:

El tipo de dato char debe ser interpretado como un nmero entero de 8 bits, no como un caracter, si bien en la prctica se lo suele usar para este fin. Incluso, como todo entero, el mismo puede ser definido con o sin signo. Ms an, los sistemas operativos avanzados utilizan el standardlenguaje UNICODE en lugar de ASCII, donde cada caracter se representa con un entero de 16 bits (short).

Definicin y alcance de las Variables

Las variables se definen escribiendo su nombre (identicador), que puede constar de hasta 32 caracteres alfanumricos, precedidos por el tipo de variable. Toda definicin de variable debe necesariamente terminar en punto y coma (;). Veamos un ejemplo:

Las variables tienen un alcance local al bloque donde fueron definidas.

Ejemplo 1.5: Definicin de variables void main (void) { /* Defino tres variables */ int a; unsigned int hola; float radio; }

12

75.42 - Taller de Programacin En este caso se estn definiendo tres variables. La primera llamada a, capaz de almacenar valores enteros con signo, la segunda llamada hola, capaz de almacenar enteros sin signo, y la tercera llamada radio permitir almacenar valores reales, tambin llamados de punto flotante.

Es posible definir varias variables en un nico rengln, separadas por coma.

Ejemplo 1.6: Definicin de variables void main (void) { /* Defino las variables a, b y c de tipo entero */ int a, b, c; } Las variables definidas en cualquier funcin o bloque de cdigo, es decir, en cualquier bloque delimitado por llaves {}, tiene un alcance local relativo a ese mismo bloque. Esto quiere decir que al salir del bloque donde fueron definidas dejarn de existir.

La nica excepcin son las variables definidas fuera de cualquier funcin, que tienen alcance global.

En cualquier parte de una funcin, al comienzo de un bloque delimitado por llaves, se pueden declarar variables, las cuales tendrn el alcance antes mencionado.

No puede definirse ninguna variable en un bloque, luego de ejecutarse alguna instruccin o sentencia.

Puede definirse varias variables del mismo tipo en una nica lnea, separando estas por comas. Ej: int a,b,c,d;

Es posible asignar un valor a una variable utilizando el operador igual (=).

Ejemplo 1.7: Asignacin de variables void main (void) { /* Defino tres variables */ int a, b; /* Defino dos variables de tipo entero, llamadas a y b*/ a = 5; b = a; /* Cargo el valor 5 en la variable a */ /* Copio el valor de a en b */

Tambin es posible asignar valores iniciales a las variables en el momento de crearlas. Veamos un ejemplo:

Ejemplo 1.8: Asignacin de valores iniciales

void main (void)

13

75.42 - Taller de Programacin

/* Defino tres variables */ /* Defino dos variables de tipo entero, llamadas a y b*/ int a = 5, b = 2; /* y una de tipo 'punto flotante' */ float f = 4.5;

La funcin printf()

La funcin printf() (print with format) permite la impresin en pantalla, en una forma muy versatil y poderosa, si bien un tanto compleja. Esta funcin est declarada en el archivo stdio.h, y recibe una cantidad aleatoria de parmetros, pero el primero especifica la forma y cantidad de parmetros que se reciben. Hay dos caracteres especiales cuyo uso est reservados. Ellos son \ (barra invertido) y % (porciento).

El carcter \ (barra invertida) est reservado para imprimir caracteres especiales. Los fundamentales son: \n \r \t \\ \" \0 salto de lnea retorno de carro tabulacin horizontal barra invertida comillas byte 0.

El carcter % est reservado para imprimir campos, y el siguiente o siguientes caracteres especifican el tipo de parmetro que se pasar y la forma en que debe imprimirselo. Las principales opciones son: %% %d %u %ld %lu %c %s %f smbolo % int unsigned long unsigned long char char* float

(strings) (se ver ms adelante)

En el primer parmetro de la funcin printf se define la forma en que se va a imprimir el texto en pantalla, escribiendo un smbolo % (porciento) donde deba imprimirse un valor pasado como parmetro. Estos parmetros se pasan a continuacin. Veamos algunos ejemplos:

Ejemplo 1.9: La funcin printf - Impresin de parmetros

#include <stdio.h> void main (void) { int a, b; a = 10; b = 5; printf ("El valor de a es %d y el valor de b es %d.\n", a, b); }

14

75.42 - Taller de Programacin Esto imprimir en pantalla: El valor de a es 10 y el valor de b es 5. Veamos otro ejemplo:

Ejemplo 1.10: La funcin printf - Impresin de parmetros (2)

/* Este programa es incorrecto */ #include <stdio.h> void main(void) { float a; a = 4.5; printf ("El valor de a es %d.\n", a); } El programa anterior es incorrecto, si bien ser compilado normalmente y no se reportar ningn error. Lo que ocurre es que la variable a es de tipo real, y en el primer parmetro de la funcin printf se dice que se pasar un parmetro entero. La forma correcta de llamar a la funcin printf en este caso sera:

printf ("El valor de a es %f.\n", a); Debe recordarse que el tipo de dato char almacena un entero de 8 bits, y por lo tanto es posible imprimir su valor numrico, al igual que todo entero. Esto se hace utilizando la opcin %d de la funcin printf. Tambin es posible imprimir el carcter ascii representado por un entero, ya sea este char, short int o int, con o sin signo. Veamos un ejemplo:

Ejemplo 1.11: Impresin de caracteres

void main(void) { char a; int b; a = A; b = 97; printf printf printf printf ("El ("El ("El ("El valor de valor de caracter caracter a es %d\n", a); b es %d\n", b); representado por a es %c\n", a); representado por b es %c\n", b);

Este programa producir la siguiente salida:

El El El El

valor de valor de caracter caracter

a es 65 b es 97 representado por a es A representado por b es a

15

75.42 - Taller de Programacin

La funcin printf() tiene muchas opciones y formatos de impresin que no se analizarn aqu. Se da simplemente el siguiente programa como ejemplo:

Ejemplo 1.12: Impresin con formato

void main(void) { int a; int b; float f; a = -32; b = 97; f = 10.0/3.0; printf ("El valor de b es: %05d\n", b); printf ("El valor de a es: %-6u\n", b); printf ("El valor de f es: %5.2f\n", f);

El primer printf() especifica que el primer parmetro ser de tipo entero (%05d), y deber imprimrselo utilizando 5 espacios (%05d) completando los restantes con 0 (%05d), con justificado derecho.

El segundo printf() indica que se imprimir un entero sin signo (%-6u), con formato izquierdo (%-6u), utilizando 6 espacios (%-6u).

El tercer printf() indica que se imprimir un nmero de punto flotante (%5.2f), utilizando 5 espacios (%5.2f), dos de los cuales se destinarn a la parte decimal (%5.2f).

La salida de este programa ser: El valor de b es: 00097 El valor de a es: -32 El valor de f es: 0.33 Algunos operadores

El lenguaje C define varios operadores, los cuales se vern ms adelante. Un operador es un smbolo que permite realizar operaciones matemticas, lgicas o binarias entre dos valores. Existen entre otros los operadores: + * / % suma resta multiplicacin divisin resto de una divisin (slo para nmeros enteros). Ejemplo 1.13: Los operadores #include <stdio.h>

16

75.42 - Taller de Programacin

void main (void) { float radio = 2; float pi = 3.14; printf ("La circunferencia de un crculo de radio %f es %f\n", radio, 2 * pi * radio);

La salida de este programa ser: La circunferencia de un crculo de radio 2 es 12.56 Notar cmo la llamada a la funcin printf() se escribi en dos renglones. Ya que el C no tiene en cuenta los saltos de lnea, tabulaciones o estilos de programacin, es posible escribir una sentencia en varios renglones, para que la misma resulte ms comprensible.

Ejemplos de programas en C

Ejemplo 1.14: Operaciones aritmticas

#include <stdio.h> void main (void) { int a, b,c; int prom; a = 1; b = 2; c = 3; prom = (a + b + c) / 3; printf ("El promedio de a, b y c es: %d\n", prom); prom = a/3 + b/3 + c/3; printf ("El promedio de a, b y c es: %d\n", prom);

El programa produce la siguiente salida:

El promedio de a, b y c es: 2 El promedio de a, b y c es: 1 El programa anterior calcula el promedio de los nmeros a, b y c. Notar que se obtuvieron dos resultados diferentes para un mismo clculo, realizado de formas diferentes pero matemticamente equivalentes.

En el primer caso se sumo los nmeros y luego los divido por 3.

(1+2+3)/3

17

75.42 - Taller de Programacin

6/3

En el segundo, si bien matemticamente la operacin es la misma, debe tenerse en cuenta que se est trabajando con nmeros enteros, y por lo tanto la parte decimal se truncar, en cada una de las operaciones.

La cuenta que el programa har es la siguiente:

1/3+2/3+3/3

0+0+1

Con lo que la variable prom quedar en 1.

Resumen

Un programa en C se compone principalmente de una serie de funciones. Estas funciones reciben parmetros por valor (no existe el pasaje de parmetros por referencia como en otros lenguajes), es decir, que las funciones reciben copias de los valores de las variables. Las funciones pueden o no devolver un valor.

Entre todas las funciones posibles, existe una llamada main(), que es la funcin por donde comienza la ejecucin del programa. Al terminar esta funcin, el programa termina.

Los compiladores C ya traen una serie de bibliotecas, que pueden ser incluidas en el programa mediante la directiva #include entre las cuales se encuentra la librera stdio, que define funciones de entrada/salida, como la funcin printf().

Las funciones se componen de sentencias y llamadas a otras funciones. Dentro de cada funcin pueden definirse variables, y su alcance, conocido como scope, est limitado a la funcin o bloque donde se defini la variable. Pueden definirse variables globales, y las mismas estarn disponibles en todo el programa.

Salvo las variables globales, ninguna variable es inicializada, y sus valores iniciales no estn definidos.

18

75.42 - Taller de Programacin

Captulo II - Funciones y prototipos


Hasta ahora hemos escrito todo nuestro programa en una nica funcin main. Est claro que en cuanto el programa comience a crecer, ser incomodo trabajar dentro de la misma funcin. El problema se complica an ms cuando deben realizarse operaciones largas en forma repetida. Por ejemplo, imaginemos tener que calcular la superficie de varias figuras en forma reiterada. Sera muy cmodo poder abstraerse de la frmula concreta en cada caso, al realizar el ciclo principal del programa.

Al llamar a una funcin con parmetros se crearn copias de los mismos, y estos sern pasados a la funcin. Esto quiere decir que toda funcin recibir una copia de la variable original, y por lo tanto la alteracin de este valor no modificar el valor original.

En C todas las variables se pasan por valor. No existe el pasaje de parmetros por referencia, si bien se lo puede implementar.

Veamos un ejemplo.

Ejemplo 2.1: Definicin de funciones

#include <stdio.h> float pi = 3.14; float SuperficieCirculo (float radio) { return 2 * pi * radio; } float VolumenCilindro (float radio, float altura) { float vol; vol = SuperficieCirculo (radio) * altura; return vol; } void main(void) { float radio = 3.0; float altura = 4.5; printf ("El volumen de un cilindro de radio %f y altura %f es %f", radio, altura, VolumenCilindro (radio, altura)); } Este programa imprimir el siguiente texto: El volumen de un cilindro de radio 3 y altura 4.5 es 42.39 Notar como se defini la variable global pi, y se le dio un valor inicial 3,14. Esta variable conservar este valor en tanto no se la modifique. Sera muy grave que por un error de programacin se modifique el valor de esta variable, ya que se produciran errores en todos los resultados. Para evitar que esto suceda, puede

19

75.42 - Taller de Programacin

utilizarse la palabra reservada const. Simplemente debe anteponerse esta palabra a la declaracin de la variable, y esta indicar que la misma deber conservar el valor durante todo el programa o la ejecucin del a funcin, e impedir que la misma sea modificada. Por ejemplo, en nuestro caso deberamos haber escrito: const float pi = 3.14; Se definieron tambin dos funciones, llamadas SuperficieCirculo y VolumenCilindro, cada una de las cuales puede toma uno y dos parmetros respectivamente de tipo real, y devuelve un valor de tipo real. La devolucin de un valor se hace utilizando la palabra reservada return. El valor que est a continuacin de esta palabra ser devuelto a la funcin original.

Ya que la funcin devuelve un valor, es posible copiar el mismo en una variable, como se hace al llamar a la funcin SuperficieCirculo desde VolumenCilindro, o pasarlo como parmetro a otra funcin como en el caso de la llamada a VolumenCilindro en la funcin printf.

Slo se puede llamar a una funcin que ha sido previamente declarada. Por ejemplo, en nuestro ejemplo anterior no sera posible escribir la funcin main antes de la funcin VolumenCilindro, ya que la misma no habra sido an declarada. De idntica forma, tampoco podra definirse la funcin VolumenCilindro antes de la funcin SuperficieCirculo.

Esto es, NO se puede llamar a una funcin que an no ha sido declarada, aunque esta se encuentre en forma inmediata a continuacin. Esto obligara a que todas las funciones deban ordenarse cuidadosamente, y nunca se podra llamar de una funcin a otra y de esta otra a la anterior.

Para solucionar este inconveniente, debe informrsele al compilador la existencia de una o ms funciones, antes de que estas sean escritas. Esto se conoce con el nombre de declaracin de funcin, y a esta declaracin de la suele llamar prototipo de la funcin. Veamos nuevamente nuestro ejemplo anterior, pero declarando las funciones.

Ejemplo 2.2: Prototipos de funciones

#include <stdio.h> const float pi = 3.14; /************** Declaro las funciones del programa ***************/ /* Prototipo de la funcin VolumenCilindro */ float VolumenCilindro (float radio, float altura); /* Prototipo de la funcin SuperficieCirculo */ float SuperficieCirculo (float radio); /************** Defino las funciones del programa ****************/ void main(void) { float radio = 3.0; float altura = 4.5; printf ("El volumen de un cilindro de radio %f y altura %f es %f", radio, altura, VolumenCilindro (radio, altura)); }

20

75.42 - Taller de Programacin

float VolumenCilindro (float radio, float altura) { float vol; vol = SuperficieCirculo (radio) * altura; return vol; } float SuperficieCirculo (float radio) { return 2 * pi * radio; }

21

75.42 - Taller de Programacin

Definicines de constantes

Existe una segunda forma de definir constantes (la que generalmente se utiliza), mediante macros o definiciones. Estas se definen utilizando la clusula #define, seguida del nombre y luego el valor, SIN PUNTO Y COMA al final. Por ejemplo, podra escribirse:

#define PI 3.1416 #define IVA 21 #define MAX_PATH 128 /* Mxima cantidad de caracteres en el nombre de un archivo */ En este caso, no se est definiendo variables, sino macros que sern reemplazadas por el preprocesador antes de compilar el cdigo. Veamos un ejemplo completo:

Ejemplo 2.3: Prototipos de funciones (2)

#include <stdio.h> #define PI 3.14 /* Prototipos de las funciones del programa */ float SuperficieCuadrado (float lado); float SuperficieRectangulo (float ancho, float alto); float SuperficieCirculo (float radio); float SuperficieElipse (float RadioMenor, float RadioMayor); /* Bloque principal del programa */ void main (void) { float lado; float superf; lado = 10.2; printf ("La superficie de un cuadrado de lado %f es %f\n", lado, SuperficieCuadrado (lado)); superf = SuperficieRectangulo (5,6); printf ("La superficie de un rectangulo de %f x %f es %f\n", 5, 6, superf); printf ("La superficie de un circulo de radio %f es %f\n", 8, SuperficieCirculo (8));

/* Funciones para calcular la superficie de las figuras */ float SuperficieCuadrado (float lado) { return SuperficieRectangulo (lado, lado); } float SuperficieRectangulo (float ancho, float alto) {

22

75.42 - Taller de Programacin return ancho * alto;

float SuperficieCirculo (float radio) { return SuperficieElipse (radio, radio); } float SuperficieElipse (float RadioMenor, float RadioMayor) { return PI * RadioMenor * RadioMayor; } Notar que en este ejemplo, la constante PI no se defini utilizando la palabra reservada const, sino #define. Esta es una directiva o directriz que indica al precompilador que reemplace todas las palabras PI por 3.14. Este es el caso ms simple de la definicin de una macro. Las macro constituyen una herramienta muy poderosa en C, y se las vern con ms detalle ms adelante. Slo se reemplazan palabras completas. Por ejemplo, en la palabra: ESPIA, PI no ser reemplazado.

Qu diferencia hay entre const y #define?

Rta: Si bien muchas veces puede resultar indistinto utilizar const o #define, su significado es muy diferente. const es un modificador que especifica que el valor de una variable permanecer constante, en tanto que #define define un texto que deber ser reemplazado por el precompilador antes de compilar el programa.

Veamos otro ejemplo. Escribamos ahora una funcin que intercambie el contenido de dos variables:

Ejemplo 2.4: Modificacin de parmetros de las funciones

#include <stdio.h> void Intercambiar (int a, int b) { int aux; aux = a; a = b; b = aux; } printf ("En Intercambiar: a=%u, b=%u\n", a, b);

void main(void) { int a, b; a = 4; b = 5; printf ("Originalmente: a=%u, b=%u\n", a, b); Intercambiar (a, b); printf ("De vuelta en main: a=%u, b=%u\n", a, b);

La salida de este programa es la siguiente:

23

75.42 - Taller de Programacin

Originalmente: a=4, b=5 En Intercambiar: a=5, b=4 De vuelta en main: a=4, b=5 Las variables a y b fueron efectivamente intercambiadas dentro de la funcin Intercambiar(), pero al volver a la funcin original las variables seguan teniendo el valor original. Qu ha sucedido?

Rta: como ya dijimos, en C slo existe pasaje de parmetros por valor, es decir que las funciones recibirn copias de los parmetros con que fueron llamadas. No hay forma en que una funcin pueda modificar los parmetros con que fue llamada.

Implica esto que es imposible escribir una funcin para intercambiar el contenido de dos variables?

Rta: No, no es as. Existe una forma de hacerlo, pero esto se ver ms adelante, en el tema punteros.

Declaraciones vs. definiciones

Hasta ahora hemos visto cmo escribir algunos programas bsicos, y hemos utilizado las palabras declaracin y definicin sin precisar qu significa cada una de ellas.

El lenguaje de programacin C fue desarrollado para escribir programas de gran envergadura, tambin denominados proyectos, en los cuales comnmente trabajarn muchas personas, y donde hablar de 50 mil o 100 mil lneas de cdigo es habitual. Es por eso ilgico pensar que todo el programa est en un nico archivo.

Para solucionar este inconveniente, los proyectos se subdividen en partes ms pequeas (cdigo fuente), que se compilan por separado para formar cdigo objeto o libreras, que luego se unirn (en ingls link) para formar un nico ejecutable. El segundo paso aqu mencionado (link) suele estar automatizado, con lo cual es transparente al programador, no as el primero (compilacin). Este proceso se muestra esquemticamente como sigue:

Debe sealarse que el proceso de compilacin es, por lo general, mucho ms costoso que el de link. Cuando un programa se subdivide en varios archivos, slo se compilarn aquellos archivos que hayan sufrido modificaciones desde la ltima compilacin. Esto hace que, la subdivisin del proyecto en varios archivos disminuye los tiempos de compilacin.

24

75.42 - Taller de Programacin Ya que el programa se subdividir en muchos archivos, es necesario informar a cada una de estas partes el contenido de las dems. Por ejemplo, si en un mdulo se llamar a la funcin Factorial(), es necesario informarle al mismo que tipos de parmetros recibe y devuelve, para poder llamarla.

De idntica forma, si tengo una variable global en uno de los mdulos, debo informarle al resto de los mdulos a cerca de su existencia y de su tipo.

Este concepto de informar al compilador la existencia de un tipo de variable o de funcin se conoce con el nombre de declaracin. En este caso se informa al compilador como debe ser utilizada la misma, pero este no reservar memoria para la variable ni sabr cmo ejecutar la funcin.

Por el contrario, al definirse una variable, el compilador reservar memoria para la misma o, en el caso de una funcin, se dar la forma precisa su contenido.

En el caso se funciones la declaracin se realiza poniendo el ttulo completo de la funcin, seguido de punto y coma. El nombre de los parmetros es opcional. Ej: unsigned Factorial (unsigned n); unsigned Factorial (unsigned); A esto se lo conoce tambin con el nombre de prototipos de funciones.

En el caso de las variables esto se hace anteponiendo la palabra reservada extern. Ej: extern int Salir; extern unsigned VarGlobal; Toda variable o funcin debe estar declarada o definida antes de que puede ser utilizada. Es por esto que generalmente todas las declaraciones se almacenan en archivos .h, en tanto que las definiciones se almacenan en archivos .c.

De esta forma, todo programa que incluya un archivo .h sabr de la existencia de variables globales y funciones en otros archivos.

Toda variable o funcin debe haber sido declarada antes de poder ser utilizada.

Las declaraciones a compartir entre varios archivos deben escribirse en archivos .h.

Si bien es posible, por regla general nunca debe incluirse un archivo .C dentro de otro archivo .C. S es posible que archivos .H incluyan a su vez otros archivos .H. Se ver este tema ms adelante.

25

75.42 - Taller de Programacin

Ejemplo 2.5: Declaracin vs. definicin

Archivo arch1.c

#include <stdio.h> /* Incluyo el archivo matem.h que contiene las declaraciones de la funcin VolumenCilindro. Uso comillas ya que el archivo matem.h no estar en el directorio include sino en el mismo directorio que este archivo. */ #include "matem.h" void main (void) { float radio = 3.0; /* Notar que escribo 3.0 en vez de 3, para que quede claro que se trata de un nmero real */ float altura = 4.5; printf ("El volumen de un cilindro de radio %f y altura %f es %f", radio, altura, VolumenCilindro (radio, altura)); } Archivo matem.h

#ifndef __MATEM_INCLUDED__ #define __MATEM_INCLUDED__ /* Declaro la variable pi */ extern float pi; /* Declaro las funciones VolumenCilindro y SuperficieCirculo mediante sus respectivos prototipos */ float VolumenCilindro (float radio, float altura); float SuperficieCirculo (float radio); #endif Archivo matem.c

/* Defino la variable pi */ float pi = 3.14; /* Defino las funciones VolumenCilindro y SuperficieCirculo dando el cdigo completo de las mismas */ float VolumenCilindro (float radio, float altura) { float vol; vol = SuperficieCirculo (radio) * altura; return vol; } float SuperficieCirculo (float radio) { return 2*pi*radio; }

26

75.42 - Taller de Programacin Notar en la segunda lnea del archivo matem.h la definicin de la constante __MATEM_INCLUDED__. Ya que un mismo archivo .h puede ser incldo desde varios otros archivos, puede ocurrir que un mismo archivo sea includo varias veces. Incluso, podra ocurrir que dos archivos .h se incluyan uno al otro, lo cual se conoce con el nombre de referencias cruzadas (cross references). Para evitar que en un mismo archivo .c se incluya un archivo .h varias veces, la tcnica utilizada consiste en preguntar si una constante ya fue definida, y en caso negativo, defirnirla e incluir el cdigo correspondiente.

Las palabras #ifndef, #define y #endif son directivas o directrices, y son interpretadas por el preprocesador, antes de compilar el cdigo. Volveremos sobre este tema ms adelante. Por ahora basta saber que todo archivo .h debe utilizar esta tcnica para evitar ser includo varias veces, y que linea #ifxxx se cierra siempre en una lnea #endif. El formato es siempre el siguiente:

Archivo .h

#ifndef CTE #define CTE ... #endif Resumen

Hemos visto que un proyecto grande puede ser subdividido en varios archivos, cada uno de los cuales ser compilado por separado. Para que en todos los archivos .C se conozcan las funciones existentes en otros archivos o mdulos del proyecto, los mismos deben incluir archivos .H, utilizando la directica #include. Estos archivos tendrn las declaraciones o prototipos de las funciones y variables existentes en otros mdulos.

27

75.42 - Taller de Programacin

Captulo III - Sentencias de control


Las sentencias de control de programas son la esencia de cualquier lenguaje de programacin, ya que gobiernan el flujo de ejecucin del programa.

Las sentencias de control de programa pueden agruparse en tres grupos. La primera est formada por las instrucciones condicionales if y switch. La segunda por las sentencias de control de bucles while, do-while y for. La tercera es la instruccin de ramificacin incondicional goto, que por ir en contra de la programacin estructurada, se recomienda no utilizar.

Sentencias de control de programa (if)

La sentencia if, ejecutar la sentencia o sentencias que se encuentren a continuacin si y slo si se cumple la condicin especificada. El formato es el siguiente: if (condicin) sentencia; else sentencia; Si hubiese que ejecutar varias sentencias debera encerrrselas entre llaves. if (condicin) { sentencia; ... sentencia; } else { sentencia; ... sentencia; } Un ejemplo de esto es el siguiente:

Ejemplo 3.1: Sentencias de control if

/* Funcin divisin */ int Division(int a, int b) { int r; /* Notar que la comparacin se realiza con el doble signo = (==) */ if (b==0) { printf ("Error\n");

28

75.42 - Taller de Programacin return 0; /* Es obligatorio devolver un valor */

r = a / b; /* En tanto que la asignacin se realiza tan slo con = */ } return r;

29

75.42 - Taller de Programacin

Recursividad

Ya que el C es un lenguaje de programacin funcional, nada impide llamar a una funcin dentro de s misma, incluso tantas veces como uno quiera (teniendo en cuenta limitaciones de memoria). A esto se lo conoce con el nombre de recursividad, y consiste en resolver un problema complejo, utilizando para ello la misma herramienta varias veces. Esto puede resultar muy til para resolver ciertos problemas, como por ejemplo el clculo del factorial de un nmero.

Ejemplo 3.2: Recursividad

#include <stdio.h> int factorial (int n) { if (n == 0) return 1; return n * factorial(n-1); } void main(void) { int n = 5; printf ("El factorial de %u es %u\n", n, factorial (n)); }

Qu hubiese sucedido si hubisemos llamado a la funcin con el nmero -3?

Pues bien, la funcin se hubiese seguido llamando a s misma indefinidamente con nmeros negativos (-4, -5,...), hasta llegar al mximo nmero negativo que puede representarse. Cuando a este nmero se le reste uno, se producir un error conocido como overflow, y el nmero resultante ser el mximo entero positivo que puede representarse. De esta forma se continuar hasta volver llegar al nmero 0.

No es importante comprender en este momento en forma detallada la forma en que esto ocurre, sino tan slo que esto constituye un error de programacin, y que ni el programa ni el compilador informarn nunca de este error, ya que el programa es sintcticamente correcto.

Lo que ocurre es que la funcin factorial est definida nicamente para nmeros positivos (ms el cero). Esto se conoce con el nombre de dominio de una funcin. Toda funcin debera validar que los parmetros recibidos son correctos. Ya que esta funcin slo puede recibir nmeros positivos, lo correcto es que reciba nmeros sin signo:

unsigned factorial (unsigned n) { if (n == 0) return 1; return n * factorial (n - 1); }

30

75.42 - Taller de Programacin De todas formas si quisisemos calcular el factorial de 1000, trabajando en un sistema operativo de 16 bits, basta notar que 1000*999 = 999000, que no es un nmero representable con 16 bits (el mximo es 216-1 = 65535). Nuevamente esto no producir ningn error, pero el resultado ser incorrecto. Concretamente la forma en que se har esto es multiplicando estos dos nmeros, pero se conservarn slo los 16 bits ms bajos del resultado.

En forma similar podemos calcular nmeros de la secuencia de fibonachi, que se define como:

Fib (0) = 1 Fib (1) = 1 Fib (x) = Fib (x-1) + Fib (x-2), para x>=2 Ejemplo 3.3: Ms sobre recursividad

#include <stdio.h> unsigned Fib(unsigned n) { if (n < 2) return 1; return Fib (n-1)+Fib(n-2); } void main(void) { unsigned n; n = 10; printf ("El nmero %u de Fibonachi es %u\n", n, Fib (n)); } Evaluacin de condiciones

Vimos en los programas anteriores que las comparaciones se efectan con == (dos smbolos =).

Los restantes smbolos de comparacin son: == = < > <= >= igual diferente menor mayor menor igual mayor igual

Si se debe efectuar una comparacin de varios parmetros, deben usarse && ||. && Y || O Implementemos una funcin para saber si un da es navidad.

Ejemplo 3.4: if-else

31

75.42 - Taller de Programacin

int Navidad (int dia, int mes) { if (dia == 25 && mes == 12) return 1; else return 0; } int Navidad (int dia, int mes) { if (dia == 25 && mes == 12) return 1; else return 0; } Notar que el else no es indispensable, ya que si la comparacin es verdadera, se saldr de la funcin. Es decir que podra escribirse:

int Navidad (int dia, int mes) { if (dia == 25 && mes == 12) return 1; return 0; } Esta funcin tambin podra escribirse: int Navidad (int dia, int mes) { if (dia != 25 || mes != 12) return 0; return 1; } Otro ejemplo sera implementar una divisin de nmeros enteros positivos: int Division (int a, int b) { if (a >= 0 && b > 0) return a / b; printf ("Error"); return 0; /* es obligatorio devolver un dato */ } Notar que el else no es indispensable, ya que si la comparacin es verdadera, se saldr de la funcin. Es decir que podra escribirse:

int Navidad (int dia, int mes) { if (dia == 25 && mes == 12) return 1; return 0; }

32

75.42 - Taller de Programacin Esta funcin tambin podra escribirse como sigue:

int Navidad (int dia, int mes) { if (dia != 25 || mes != 12) return 0; return 1; } Otro ejemplo sera implementar una divisin de nmeros enteros positivos:

int Division (int a, int b) { if (a >= 0 && b > 0) return a / b; printf ("Error"); return 0; /* es obligatorio devolver un dato */

En C toda condicin se evala por verdadero o por falso. Un valor igual a cero ser falso, y todo valor distinto de cero ser considerado verdadero. Veamos un ejemplo:

Ejemplo 3.5: Evaluacin de condiciones

void imprimir (int a) { if (a) printf ("a = %u", a); } La funcin imprimir imprimir el valor de a slo y slo si a es distinta de cero.

Sentencias de control de programa (switch-case)

La sentencia switch-case es un if mltiple, donde una variable se compara contra mltiples constantes:

switch (variable) { case constante_1: sentencia; ... break; case constante_2: sentencia; ... break; ... case constante_n: sentencia; ...

/* Ejecutar todas las sentencias hasta el break */

33

75.42 - Taller de Programacin

break; default: sentencia; ...

/* Ejecutar si no se entra en las anteriores */

esto sera equivalente a:

if (variable == constante_1) { } else if (variable == constante_2) { } ... else if (variable == constante_n) { } else { } Notar que cada case termina con una sentencia break; Este break es necesario para que no se contine ejecutando el case inferior. Si no se lo escribiese, la ejecucin continuara con el case inferior, y as sucesivamente hasta encontrar una sentencia break. La sentencia default: se ejecutar si ninguno de los case anteriores se cumple. Veamos un ejemplo:

int Cantidad_de_dias_del_mes (int mes, int anio) { int dias; switch (mes) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: dias = 31; break; case 2: if (anio %4 == 0) dias = 29; else dias = 28; break; case 4: case 6: case 9: case 11: dias = 30;

34

75.42 - Taller de Programacin break; default: printf ("El mes especificado es invalido.\n"); dias = 0; /* Es obligatorio devolver un dato */

} return dias;

Esta funcin podra escribirse tambin como:

int Cantidad_de_dias_del_mes (int mes, int anio) { int dias; if (mes == 1 || mes == 3 || mes == 7 || mes == 8 || mes == 10 || mes == 12) dias = 31; else if (mes == 2) { if (anio%4 == 0) dias = 29; else dias = 28; } else if (mes == 4 || mes == 6 || mes == 9 || mes == 11) dias = 30; else { printf ("El mes especificado es invalido.\n"); dias = 0; /* Es obligatorio devolver un dato */ } return dias;

Puede parecer ms prctico utilizar if-else, en vez de switch-case. Sin embargo, en muchos casos resulta mucho ms prctico e intuitivo esta ltima sentencia, por ejemplo cuando la aplicacin debe responder a una gran cantidad de mensajes, de formas diferentes. Es comn el uso por ejemplo al procesar mensajes de error. Por ejemplo:

switch (error) { case 1: printf ("Divisin por cero\n"); break; case 2: printf ("Overflow\n"); break; case 3: printf ("Parametros incorrectos\n"); break; case 4: printf ("Error fatal. Debe terminarse el programa inmediatamente.\n"); exit (1); /* exit() es una funcin definida en la librera stdlib.h, que interrumpe el programa inmediatamente y retorna el control al sistema operativo */ default: printf ("Error desconocido\n"); }

35

75.42 - Taller de Programacin

ifs anidados

Ejemplo 3.6: ifs anidados (1)

/* Devuelve 1 si el da indicado es Navidad, y 0 si no lo es */ int Navidad (int mes, int dia) { if (mes == 12) if (dia == 25) return 1; return 0; } Debe tenerse mucho cuidado, y por lo general conviene poner llaves cuando la situacin se vuelve confusa. Por ejemplo, la siguiente funcin es incorrecta:

int Navidad (int mes, int dia) { int ret; if (mes == 12) if (dia == 25) ret = 1; else ret = 0; } return ret;

ya que el else no corresponde al primer if, sino al segundo. En este caso, el segundo if debera haberse encerrado entre llaves.

Tambin es posible escribir sentencias if como sentencia de un else.

Ejemplo 3.7: if como sentencia de un else (1)

/* Devuelve 1 si se trata de un da feriado y 0 en caso contrario */ int Feriado (int dia, int mes) { int Ret; if (dia==25 && mes==5) Ret=1; else if (dia==9 && mes==7) Ret=1; else if (dia==12 && mes==10) Ret=1; else if (dia==25 && mes==12) Ret=1; else Ret=0;

36

75.42 - Taller de Programacin

return Ret;

Este cdigo es similar a un switch-case, pero por requerirse la evaluacin de dos valores no se lo puede implementar de esta forma. Gramaticalmente lo correcto sera escribir este cdigo debera escribiese como sigue:

int Feriado (int dia, int mes) { int Ret; if (dia==25 && mes==5) Ret=1; else if (dia==9 && mes==7) Ret=1; else if (dia==12 && mes==10) Ret=1; else if (dia==25 && mes==12) Ret=1; else Ret=0; } return Ret;

Sin embargo, lejos de resultar ms claro, este cdigo resulta ms confuso, razn por la cual esta forma de anidar if-else no se utiliza.

Sentencias de control de programa (while y do-while)

La sentencia while ejecuta la sentencia o sentencias que estn a continuacin, si y slo si se cumple la condicin especificada. Un ejemplo de esto es el siguiente:

while (condicin) sentencia; De idntica forma, puede utilizarse la dupla do-while si se quiere evaluar la condicin al final del ciclo:

do {

sentencia; ... sentencia; } while (condicin); Ambas sentencias funcionan en una forma similar. La nica diferencia consiste en que while evala la condicin al comenzar el ciclo, y por lo tanto el mismo puede no ejecutarse, y do-while al finalizar el mismo, y por lo tanto el mismo se ejecuta al menos una vez. Notar que do-while puede fcilmente implementase con while.

37

75.42 - Taller de Programacin

Reescribamos ahora la funcin factorial, pero en forma iterativa.

Ejemplo 3.8: while

unsigned Factorial (unsigned n) { int ret; ret = 1; while (n > 1) { ret = ret * n; n = n - 1; } return ret;

Sentencias de control de programa (for)

La sentencia for se usa para implementar ciclos controlados por un contador. La forma de esta sentencia es la siguiente:

for (sentencia_inicial; condicin_de_continuidad; sentencia_de_paso) sentencia_del_lazo; Un ejemplo de esto es:

Ejemplo 3.9: Ciclo for

for (a = 10; a < 20; a = a + 1) { printf ("a = %d\n",a); } Veamos otro ejemplo. Escribamos una funcin que imprima la tabla de multiplicar.

Ejemplo 3.10: Ciclos for anidados

void ImprimirTabla (void) { int i, j; for (i = 0; i <= 10; i= i + 1) for (j = 0; j < 10; j = j + 1) printf ("%d por %d es igual a %d\n", i, j, i * j);

38

75.42 - Taller de Programacin En realidad la sentencia for es mucho ms que una sentencia para implementar contadores. En realidad, la sentencia for como la siguiente: for (sentencia_inicial; condicion_de_continuidad; sentencia_de_paso) sentencia_del_lazo; puede traducirse como sigue: sentencia_inicial; while (condicin_de_continuidad) { sentencia_del_lazo; ... ... sentencia_de_paso; } Por lo cual, sera vlido escribir cosas como: for (a = 0; a < 10 && b > 30; a = a * 2) { } Pero por claridad, cuando se requiere hacer cosas as, generalmente se usa un ciclo while y no for, ya que resulta confuso para el programador.

Los tres parmetros de la sentencia for son opcionales y pueden omitirse. Incluso puede escribirse ms de una sentencia en cada uno de los parmetros, separadas por coma. int i, j; i = j = 0; for (; i < 10; ++i, j+=2) { ..................printf ("El producto de %d x %d es %d\n", i, j, i * j); } Incluso pueden omitirse los tres parmetros de la sentencia for, escribiendo: for (;;). En este caso, este cdigo ser equivalente a: while (1).

Sentencias break y continue

Las sentencias break y continue pueden utilzarce en cualquier ciclo de los anteriormente vistos: while, dowhile, for y su objetivo es modificar el normal flujo del ciclo. La primera de ellas (break) interrumpe el flujo del ciclo, y el programa continuar su ejecucin en la instruccin inmediatamente posterior. Veamos un ejemplo:

Ejemplo 3.11: break

#include <stdio.h> void main(void)

39

75.42 - Taller de Programacin

int i; for (i=0;i<10;i=i+1) { if (i == 4) break; printf ("El valor de i es %d\n", i); } printf ("Fin\n"

La salida del programa es la siguiente:

El valor El valor El valor El valor Fin

de de de de

i i i i

es es es es

0 1 2 3

Qu sentido tiene el uso de la sentencia break? En este programa no tiene mucho sentido, ya que simplemente se podra haber comparado i con 5 en vez de 10. Un uso muy comn de la sentencia break es interrumpir un ciclo cuando se produce un error en el programa.

La sentencia continue, salta ejecuta el siguiente ciclo del lazo, evaluando previamente la condicin de continuidad. Escribamos por ejemplo un programa que imprima los nmeros impares del 1 al 10.

Ejemplo 3.12: continue

#include <stdio.h> void main(void) { int i; for (i = 1; i < 10; i = i + 1) { if (i % 2 == 0) continue; printf ("El valor %d es impar\n", i); } printf ("Fin\n"); } La salida del programa es la siguiente:

El El El El El

valor valor valor valor valor

1 3 5 7 9

es es es es es

impar impar impar impar impar

40

75.42 - Taller de Programacin Nuevamente, este programa podra haberse escrito ms fcilmente incrementando i de a 2. continue se utiliza en condiciones en las cuales deben saltearse algunos casos particulares y no peridicos, como podra ser imprimir los nmeros no primos.

Sentencia goto

La sentencia de salto incondicional goto, permite realizar un salto en la ejecucin del programa. Esto es contrario a la programacin estructurada, y su uso puede llevar a cdigos difciles de leer. Por esta razn slo debe ser utilizada en casos muy limitados.

La sintaxis es la siguiente:

goto label; label: Veamos un ejemplo:

Ejemplo 3.13: goto

int FuncionCompleja () { for (...) { while (...) { for (...) { ... if (b == 0) goto error; r = a / b; ... } } } return r; error: printf ("Se ha producido un error\n"); return -1;

La sentencia goto puede resultar til para interrumpir funciones sumamente complejas, como la de arriba, en donde se deben interrumpir varios ciclos anidados al producirse un error. Resulta ms simple utilizar la sentencia goto que utilizar variables auxiliares para interrumpir todos los ciclos. Sin embargo, esto puede solucionarse reescribiendo las funciones correctamente. Por ejemplo, el programa anterior podra ser reescrito como sigue:

int Calculo () { for (...) { while(...) { for(...) { ...

41

75.42 - Taller de Programacin

} return r;

if (b == 0) return -1; r = a / b; ...

int FuncionCompleja() { int r = Calculo(); if (r < 0) printf ("Se ha producido un error\n"); return r;

La recomendacin de evitar el uso de la sentencia goto, no quiere decir que no existan casos en los que su uso simplifica un programa, pero es ms comn que programadores inexpertos la utilicen sin criterio resultando en programas complicados y difciles de mantener, razn por la cual se recomienda que slo sea utilizada por programadores con experiencia.

Operador ?

Existe una variante ms a la funcin if, que puede escribirse como una nica sentencia que devuelve un valor. Esta variante se escribe utilizando el operador ?, y es similar a if-else, salvo por el hecho de que puede retornar un valor. Veamos un ejemplo:

Ejemplo 3.14: Operador ?

if (a > b) r = a; else r = b; Este ejemplo puede ser reescrito como sigue:

r = (a > b) ? a : b; aunque generalmente se lo escribe en un nico rengln, como sigue:

r = (a > b) ? a : b;

42

75.42 - Taller de Programacin Si ya existe if-else, qu sentido tiene esta segunda variante? La respuesta es que esta segunda variante retorna un valor, y por ende permite escribir cdigos como:

max = a > b ? a : b;

printf ("el mximo es:%u\n", a > b ? a : b ); Resumen

Se han visto aqu tres tipos de sentencias de control; los saltos condicionales, los incondicionales y los ciclos o lazos.

Los santos condicionales, que se definen utilizando la palabra reservada if y switch-case, cuyas estructuras son: if (condicion) sentencia; switch (variable) { case valor: sentencia; ... default: sentencia; } En el caso se la sentencia switch, debe escribirse una sentencia break al finalizar cada case, para evitar que se ejecute el siguiente case. y

La sentencia puede ser reemplazada por una serie de sentencias entre llaves.

Los ciclos, pueden ser instrumentado utilizando las palabras reservadas while, do-while y for. En este caso la sintaxis es: while (condicin) sentencia; do { sentencia; } while (condicin); for (sentencia inicial; condicin; paso) sentencia; Existen dos sentencias, break y continue, que permiten alterar la secuencia de ejecucin de un ciclo.

Finalmente, aunque muy poco utilizada, existe la sentencia de salto incondicional, goto. La sintaxis es:

goto label; label:

Captulo IV - Asignaciones y operadores

43

75.42 - Taller de Programacin

Operadores

En se C definen los siguientes operadores, que pueden aplicarse a cualquier tipo de variable numrica, tanto enteros como reales: = * / + Asignacin Multiplicacin Divisin Suma Resta

Se definen tambin, nicamente para los nmeros enteros, los siguientes operadores % >> << & | ^ && || Mdulo o resto de divisin entera. Corrimiento de bits hacia la derecha Corrimiento de bits hacia la izquierda Y (and) binario (or) binario excluyente (xor) binaria Y lgica lgica

Operadores de comparacin: < > <= >= == = menor mayor menor o igual mayor o igual (el smbolo => es incorrecto) igual diferente

Como ya se dijo antes, las condiciones se evalan en C por verdaderas o falsas. qu ocurre entonces al evaluar una condicin? Esta devuelve un valor de verdad o falsedad. Por ejemplo:

{ }

if (a==3) printf ("a = 3");

La comparacin a==3 devolver 1 si a es igual a 3 y 0 en caso contrario. Es decir que este cdigo tambin podra haber sido escrito como sigue:

int tmp; tmp = (a == 3); if (tmp) printf ("a = 3\n");

Esta forma de trabajar del C resulta muy til, pero tambin algo confusa para quien recin comienza a programar. Veamos el siguiente ejemplo:

44

75.42 - Taller de Programacin

Ejemplo 4.1: Operadores y comparaciones

void Imprimir (int a, int b) { if (a && b) printf ("a = %d b = %d\n", a, b); } Imprimir el contenido de a y b si y slo si a y b son distintas de 0. Qu pasara si modificsemos la comparacin como sigue?

Ejemplo 4.2: Operadores y comparaciones

void Imprimir (int a, int b) { if (a & b) printf ("a = %d b = %d\n", a, b); } Pus bien se imprimiran a y b si y slo si a y b tienen algn bit en comn en 1, ya que lo que se est haciendo es un "y" bit a bit, y este es el valor que se pasa al if.

El uso de los operadores binarios es muy comn para indicar el estado de un proceso o de un registro, comnmente llamados flags o banderas. Por ejemplo, es comn que las funciones retornen un cdigo de error almacenado en un entero. Veamos un ejemplo:

Ejemplo 4.3: Flags

/* bit bit bit bit */

0: 0 = OK 1 = Error 1: 0 = Error del programa 1 = Error del Sistema operativo 2: 0 = Reintentar 1 = Cancelar 3: 0 = Continuar 1 = Interrumpir el programa ERROR 1 OS_ERROR RETRY 4 END_PROGRAM /* OK */ 2 /* El programa produjo un error */ /* Reintentar la operacin que produjo el error */ 8 /* Error del progra*/

#define #define #define #define

void ProcesarError (unsigned ErrCode) { if ((ErrCode & ERROR) == 0) return; /* Reporto el causante del error */ if (ErrCode & OS_ERROR) printf ("Error de sistema operativo\n"); else printf ("Error del programa\n");

45

75.42 - Taller de Programacin

/* Reporto si se va a reintentar la operacin o no */ if (ErrCode & RETRY) { printf ("Se reintentar la operacin\n"); return; } else printf ("La operacin no se intentar nuevamente\n"); /* Reporto si se va a reintentar la operacin o no */ if (ErrCode & END_PROGRAM) { printf ("Error fatal\n"); exit (1); /* Exit es una funcin que interrumpe el programa */ }

Esta funcin podra ser llamada por ejemplo como sigue:

ProcesarError (ERROR | RETRY); Operadores y asignaciones

Como ya se vi, una asignacin de variables se realiza utilizando el smbolo = (igual). Es decir que si yo quisiese incrementar el valor de una variable, debera efectuar una operacin como:

var = var + 1; Sin embargo, es muy comn que a una variable se la modifique simplemente sumndole, restndole, multiplicndola, dividindola, etc, por un valor. Por esta razn, C permite utilizar una notacin abreviada, que consiste en escribir la operacin a realizar antes del smbolo igual. Por ejemplo, la operacin anterior se escribira:

var += 1; En forma similar se pueden escribir:

var = var * 2; var = var % 3;

como var *= 2; como var %= 3;

An ms, en C es muy comn realizar operaciones donde a la variable sea incrementada o decrementada de a 1. Por esta razn, C provee una forma an ms simplificada de escribir esta operacin (slo vlida para enteros), y define a tal fin los operadores ++ y --. Estos operadores incrementan o decrementan el valor de una variable de tipo entero. Por ejemplo, en el caso anterior podra hacerse:

++var;

46

75.42 - Taller de Programacin var++; En forma similar tambin puede escribirse:

--var;

var--; para decrementar su valor en 1.

La nica diferencia entre poner el operador antes o despus es que, si el operador est antes, la variable se modifica en forma inmediata, en tanto que si el operador est al final, la variable se modifica luego de que se finalice la sentencia. Por ejemplo:

int var; var = 10; printf ("var = %d\n", ++a);

imprimir el valor 11, en tanto que

int var; var = 10; printf ("var = %d\n", a++);

imprimir el valor 10. En ambos casos, al finalizarse el llamado a printf(), el valor de la variable var ser 11.

Notacin entera

No siempre resulta prctico escribir nmeros en base decimal. Es por ello que el C permite escribir nmeros en otras bases diferentes de la decimal, estas son la base octal (8) y hexadecimal (16). Todo numero que comience con un cero, seguido de un dgito decimal se entender que est escrito en base octal, en tanto que todo nmero que comience con dgito 0 seguido del caracter x (equis) se interpretar como hexadecimal.

Ejemplo 4.4: Notaciones no decimales

void main (void) { int a, b, c; a = 10; b = 010; /* Cargo el valor 10 decimal */ /* Cargo el valor 10 octal (8 decimal) */

47

75.42 - Taller de Programacin

c = 0x10; }

/* Cargo el valor 10 hexadecimal (16 decimal) */

printf ("a=%d\nb=%d\nc=%d\n", a, b, c);

Este programa producir la siguiente salida:

a = 10 b = 8 c = 16

48

75.42 - Taller de Programacin

Captulo V - Definicin de tipos y conversiones


Estructuras

El C soporta varias formas de definir nuevos tipos de datos, que permiten agrupar informacin en varias formas. La ms conocida y utilizada es la estructura, que permite agrupar varias variables en un nico grupo.

Una estructura se define mediante la palabra reservada struct, encerrando entre llaves el tipo de dato

struct nombre_estructura { definicin de variables }; /* Notar el punto y coma al final */ Ejemplo:

struct Registro { int a; float b; unsigned int c; unsigned int d; }; /* Notar el punto y coma al final de la llave */ Esto permite agrupar un conjunto de variables en una nica estructura. Para crear una variable de este tipo, bastar con hacer referencia a esta estructura. Las variables o campos de esta estructura se referencian utilizando un punto.

Por ejemplo, podra utilizarse la estructura anterior en la siguiente forma:

void main (void) { struct Registro Reg; Reg.a Reg.b Reg.c Reg.d = = = = -10; 20.3; 15; Registro.c * 2;

Es posible cargar una estructura con un valor inicial. Por ejemplo, en el caso anterior podra escribirse:

void main (void) { struct Registro Reg = {-10, 20.3, 15, 15*2}; struct Registro Reg2; */ } Reg2 = Reg; /* Es posible copiar registros como cualquier otra variable

49

75.42 - Taller de Programacin

No existe ninguna funcin que imprima el contenido de un registro. Para ello debe definirse una funcin que lo haga.

Enumeraciones

El tipo de dato enumeracin consiste en un conjunto de constantes numricas, llamadas enumeradores ('enumerators' en ingls). Dichas constantes pueden tomar cualquier valor arbitrario (entero). Si no se lo especifica, se entiende cero para el primer valor, y uno ms que el anterior para los restantes. Por ejemplo:

enum DiasSemana { DOMINGO, LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO }; Para definir una variable de este tipo deber utilizarse:

enum DiasSemana Dia; /* En C++ est permitido omitir la palabra enum al definir una variable, en C no */ Dia = LUNES; En este caso, DOMINGO tendr el valor 0, LUNES el 1 y as sucesivamente.

Es posible asignar un valor especfico a cada elemento, incluso valores repetidos:

enum DiasSemana { DOMINGO = 10, LUNES, /* Se utilizar el valor del elemento de arriba + 1 */ MARTES, MIERCOLES = 15, JUEVES, VIERNES, SABADO = 10 /* Repito un valor ya utilizado */ }; En este caso, tanto SABADO como DOMINGO tendrn el valor 10, LUNES tendr el 11, MARTES el 12, MIERCOLES el 15, JUEVES el 16 y VIERNES 17.

Si bien una variable de tipo enum almacenar un entero no est permitido asignarle directamente valores de este tipo. Por ejemplo, el cdigo:

50

75.42 - Taller de Programacin Dia = 15; sera incorrecto, an cuando MIERCOLES tenga el valor 15.

S es posible realizar esta operacin utilizando algo llamado cast, que se ver ms adelante, en este mismo captulo. Por ahora simplemente diremos que es posible realizar esta operacin de la siguiente forma.

Dia = (enum DiasSemana) 15; Tambin es sintcticamente posible asignar un valor invlido, como en el siguiente ejemplo:

Dia = (enum DiasSemana) 4257; pero en este caso los resultados no estn definidos.

Uniones

Una unin es una agrupacin de variables bajo una nica denominacin, pero en la cual todas ellas comparten una misma zona de memoria. Segn como se la acceda, dicha zona de memoria ser utilizada para almacenar un tipo de dato diferente. Veamos un ejemplo:

Ejemplo 5.1: Uniones

union MiUnion{ char c; int n; float f; double d; }; void main(void) { MiUnion u; u.d = 10.0 / 3.0; printf printf printf printf ("u ("u ("u ("u como como como como double = %f\n", u.d); float = %f\n", u.f); int = %d\n", u.n); char = %d\n", u.c);

Este ejemplo es correcto. Sin embargo no debe leerse nunca una variable de una forma diferente a como fue escrita, por ejemplo, si u se escribe como int no debe leerse nunca como double. Las uniones son utilizadas principalmente para almacenar datos particulares de un tipo de dato ms general. En C++ este problema se resuelve de una forma diferente, con el concepto de herencia que se ver ms adelante, razn por la cual las uniones son rara vez utilizadas.

51

75.42 - Taller de Programacin

Para terminar con el tema, veamos un par de ejemplos, el los que se utilicen estructuras, enumeraciones y uniones:

Ejemplo 5.2: Estructuras complejas (1)

/* estructura para almacenar informacin sobre una figura, a dibujar en pantalla */ enum TipoFigura {CUADRADO, RECTANGULO,CIRCULO}; struct TPosicion { double x0, y0; }; struct TFigura{ enum TipoFigura; unsigned Color; TPosicion Pos; union Datos { double Lado; struct Lados { double Ancho; double Alto; } double Radio; }; }; Ejemplo 5.3: Estructuras complejas (2)

enum TipoMisil {TIERRATIERRA, TIERRAAIRE, AIRETIERRA, AIREAIRE}; enum TipoDureza {DEBIL, MEDIA, FUERTE} /* Estructura para almacenar los datos de un misil */ struct TMisil { double x,y,z; /* Posicin del misil */ double vx, vy, vz; /* Velocidad */ enum TipoMisil; union Blanco { struct Avion { double x,y,z; double vx,vy,vz; }; struct Edificio { double x,y; TipoDureza Dureza; } }; };

52

75.42 - Taller de Programacin Esta estructura permite almacenar la informacin de un misil. En este caso, el blanco u objetivo del misil nunca ser un avin y un edificio al mismo tiempo. Por ello se utiliza una unin para almacenar los datos del objetivo. Ambas estructuras utilizarn la misma rea de memoria, y el programador deber saber como tratarla.

Definicin de nuevos nombres para tipos de datos

Pueden definirse tipos de datos o nuevos nombres para los ya existentes usando la palabra reservada typedef. Esta se utiliza de la siguiente forma:

typedef definicin_del_tipo nuevo_nombre_del_tipo; Por ejemplo, si se quisiese llamar byte al tipo de dato unsigned char, debera escribir:

typedef unsigned char byte; Tambin podra utilizar typedef para abreviar el nombre de una estructura. Por ejemplo, en ejemplo de la seccin anterior, donde se defina el tipo de dato struct Registro, podra abreviar estas dos palabras en una sla escribiendo:

struct Registro { int a; float b; unsigned int c; unsigned int d; }; typedef struct Registro TRegistro; Estas lneas pueden escribirse en forma ms resumida como sigue:

typedef struct Registro { int a; float b; unsigned int c; unsigned int d; } TRegistro; con lo cual, para crear este tipo de dato podra escribir:

TRegistro Reg; Convirtiendo tipos de datos (cast)

En C est permitido realizar conversiones de un tipo de dato a otro, sin mayor inconveniente. Por ejemplo, podra escribirse:

Ejemplo 5.4: Conversin de tipos (cast) (1)

53

75.42 - Taller de Programacin

void main(void) { int a; unsigned int b; a = 10; b = a;

Qu valor tomar b? La respuesta es simple, 10. Pero que ocurrira en el caso siguiente?:

void main (void) { int a; unsigned int b; a = -1; b = a;

Para responder esta pregunta es necesario analizar la representacin de -1 en binario. Un valor entero -1 tiene la siguiente representacin binaria, en un sistema de 16 bits: 11111111 11111111. Si tratamos de leer este valor en decimal, considerndolo un nmero positivo obtendremos en valor 65535, es decir que b recibir este valor.

Para evitar problemas de este tipo, el compilador suele advertir con un warning cuando se realizan conversiones de datos no especificadas.

Pueden realizarse tambin operaciones entre nmeros enteros y de punto flotante. En este caso el compilador se encarga de la conversin. Por ejemplo:

float a; int b; a = 20 / 3; b = a;

En este caso a recibir en valor 6.66666, y b este valor, pero con la parte decimal truncada a 0, es decir, 6.

La mayora de los compiladores emiten mensajes de advertencia cuando se realizan operaciones entre datos de distinto tipo, y lo correcto es no hacer esto. Para realizar operaciones entre datos de distinto tipo, debe realizarse una conversin de tipo. Esto se hace encerrando entre parntesis el tipo de dato al que se quiere convertir. Esto se conoce con el nombre de cast, y la operacin de realizar esta conversin con el nombre de casting. Por ejemplo, en los casos anteriores se tendra:

int a; unsigned int b;

54

75.42 - Taller de Programacin float c; a = -1; b = (unsigned int) a; c = (float) a;

Ejemplo 5.5: Casting

typedef unsigned char byte; byte prom (byte a, byte b, byte c) { byte result; result = (byte)(((unsigned)a + (unsigned)b + (unsigned)c) / 3); } return retult;

La conversin de tipos puede realizarse an sin que se cambie el tipo de variable. Supongamos que se requiera calcular el promedio de 3 nmeros enteros de 8 bits, sin signo, llamados a, b y c.

Desde ya que la funcin promedio retornar un entero de 8 bits, ya que el resultado estar acotado a los valores recibidos. Vimos anteriormente que este clculo no lo podemos realizar en la forma: a/3+b/3+c/3, ya que al realizarse la divisin se trunca la parte decimal. Por ejemplo, el promedio de 2, 2, 2 sera 0.

La segunda alternativa consiste en realizar la suma primero, y luego la divisin (a+b+c)/3.

Pero qu ocurrira si la suma superase el nmero 255, que es el mximo entero representable con 8 bits?

Rta: El resultado de la suma se trucara quedndose nicamente con los 8 bits menos significativos, y el resultado sera incorrecto.

Cmo solucionar este problema? La respuesta consiste en realizar este clculo con nmeros de mayor precisin. Veamos el ejemplo:

typedef unsigned char byte; byte prom (byte a, byte b, byte c) { byte result; result = (byte) (((unsigned)a + (unsigned)b + (unsigned)c) / 3); } return retult;

Notar la forma en que se realiz la operacin. En primer lugar se convirti cada una de las tres variables a un entero de mayor precisin, luego se efectu la suma de las tres y la divisin por 3. Finalmente se convirti el resultado en un entero de 8 bits y se lo copi en la variable result.

55

75.42 - Taller de Programacin

Captulo VI - Vectores
Los vectores (arrays) en C se definen encerrando entre corchetes la cantidad de elementos que el mismo almacenar, a continuacin del nombre del mismo.

Por ejemplo, un vector de 10 elementos de tipo entero se definira como:

int vect[10]; y cada uno de estos 10 elementos se direccionar escribiendo

vect[i] con 0 <= i <= 9.

Todo vector de n elementos tendr un ndice que ir entre 0 y n-1.

Es importante destacar que el lenguaje C no realiza ningn tipo de verificacin del ndice con el cual se direcciona el vector. Si se utilizase un ndice que excediese el tamao del arreglo, pueden producirse las siguientes situaciones:

Se lee basura de la memoria.

Se sobreescriben otros datos del programa -> Esto conducir a que en otras partes de programa, cuando se requieran los datos sobreescritos, se trabaje con basura.

Se sobreescribe el programa. Si no se trabaja con un sistema operativo en modo protegido (ej: D.O.S.) los resultados son impredecibles. Generalmente, cuando se cuando se intente ejecutar la parte del programa que fue sobreescrita el programa producir un error y se "colgar" el programa y la computadora. En un sistema operativo que trabaje en modo protegido (Windows, UNIX), el mismo impedir que se efecte la modificacin del programa, y se producir algo conocido como interrupcin o excepcin, que se ver ms adelante. El resultado es la interrupcin del programa (salvo que se especifique lo contrario). En Windows se emitir un mensaje "el programa ha efectuado una operacin no vlida y se cerrar". En UNIX se produce un mensaje conocido como coredump.

Se intenta acceder (leer o escribir) memoria que no pertenece al programa. En un sistema operativo que no funcione en modo protegido, la operacin se llevar a cabo y los resultados son impredecibles. En un sistema operativo que funcione en modo protegido se cerrar la aplicacin (salvo que se especifique lo contrario) y se notificar al usuario. En Windows se imprimir un cartel "Fallo de proteccin general" ("General Protection Fault"). En UNIX el error es conocido como coredump.

56

75.42 - Taller de Programacin Es por esto que debe tenerse mucho cuidado al trabajar con vectores o, como se ver ms adelante, con punteros. Es deber del programador verificar todas las condiciones que pueden llevar a cometer este error, y el lenguaje C no ofrece absolutamente ningn tipo de proteccin ni validacin.

Veamos un par de ejemplos con vectores:

Ejemplo 6.1: Vectores

void main (void) { int vec[10]; /* Declaro un vector de 10 elementos de tipo entero */ int i; for (i=0;i<10;++i) vec[i] = i*2; /* Cargo el vector */ for (i=0;i<10;++i) printf ("La posicin %d del vector contiene el numero %d\n", i, vec[i]);

Este ejemplo imprimir la siguiente salida: La La La La La La La La La La posicin posicin posicin posicin posicin posicin posicin posicin posicin posicin 0 1 2 3 4 5 6 7 8 9 del del del del del del del del del del vector vector vector vector vector vector vector vector vector vector contiene contiene contiene contiene contiene contiene contiene contiene contiene contiene el el el el el el el el el el numero numero numero numero numero numero numero numero numero numero 0 2 4 6 8 10 12 14 16 18

Notar que ya que el vector tiene 10 elementos, el ndice va de 0 a 9.

Si se declaran vectores muy grandes se puede exceder el tamao de la pila, con lo cual la computadora se puede colgar. Por ejemplo, DOS no funcionar correctamente si se declaran cosas como:

long double Vect[50000]; En DOS y Windows 16 bits, el tamao de la pila es de algunos KBytes en tanto que en sistemas operativos de 32 bits (Windows 95/98/NT, o UNIX actuales) es de algunos MBytes. Algunos compiladores permiten verificar esto activando la opcin "Test stack overflow".

El C no define ningn tipo de dato para almacenar string, como en otros lenguajes. Por el contrario, los strings se almacenan en cadenas de caracteres o vectores (arrays) terminados en el byte 0, o lo que es lo mismo, el carcter \0. Por ejemplo, si quisiesemos almacenar la palabra HOLA necesitaremos 5 posiciones del vector. De esta forma, todo string se almacenar en un vector de caracteres, terminado en el byte 0.

57

75.42 - Taller de Programacin

Debe recordarse sin embargo que la palabra reservada char no debe entenderse como "caracter" sino como entero (con o sin signo) de 8 bits, si bien en la prctica se la usa para almacenar caracteres.

Puede utilizarse la opcin %s de la funcin printf para imprimir strings.

Ejemplo 6.2: Impresin de strings

#include <stdio.h> void main (void) { char Str[10]; Str[0] Str[1] Str[2] Str[3] Str[4] } = = = = = H; O; L; A; 0;

printf ("La cadena Str contiene %s\n", Str);

La salida de este programa sera:

La cadena Str contiene Hola Al igual que cualquier variable, el posible darle a un vector un valor inicial:

Ejemplo 6.3: Valores iniciales en los vectores

#include <stdio.h> void main (void) { char Str[10] = "HOLA"; } printf ("La cadena Str contiene %s\n", Str);

Sin embargo no es posible realizar copia de vectores enteros. Slo est permitido acceder a los elementos individuales de un vector. Es decir, no pueden efectuarse operaciones tales como: void main (void) { char Str[10] = "HOLA"; char Vec[10];

58

75.42 - Taller de Programacin

Vec = Str; }

/* Esto es incorrecto */

Escribamos entonces el cdigo necesario para copiar un vector de caracteres en otro.

Ejemplo 6.4: Copiando strings

/* Este programa es incorrecto */ #include <stdio.h> void main (void) { char Str[10] = "HOLA"; char Nuevo[10]; int i; /* Copio Str en Nuevo */ i = 0; while (Str[i] = 0) { /* Copio uno a uno los caracteres, hasta encontrar el 0 */ Nuevo[i] = Str[i]; ++i; } printf ("La cadena Nuevo contiene %s\n", Nuevo);

qu est mal en el programa anterior? Pues bien, toda cadena de caracteres debe terminar en el byte 0, sin embargo en el ejemplo anterio el 0 no sera copiado. Es un error pensar que una variable no inicializada contendr ceros. Esto slo es vlido para variables globales, salvo que se indique lo contrario. Veamos entonces nuestro ejemplo corregido.

Ejemplo 6.5: Copiando strings

#include <stdio.h> void main (void) { char Str[10] = "HOLA"; char Nuevo[10]; int i; /* Copio Str en Nuevo */ i = 0; do{ Nuevo[i] = Str[i]; ++i; }while (Str[i-1] = 0); /* Copio uno a uno los caracteres, hasta encontrar el 0 */

59

75.42 - Taller de Programacin

printf ("La cadena Nuevo contiene %s\n", Nuevo);

La salida de este programa ser: La cadena Nuevo contiene HOLA Pasaje de vectores a funciones

Nada impide pasar un vector a una funcin, como cualquier otro tipo de dato.

Veamos otro ejemplo de strings. Escribamos una funcin capaz de separar las palabras que componen una oracin

Ejemplo 6.6: Recorriendo un string

#include <stdio.h> void SepararPalabras (char Entrada[100]) { int Inicio; /* Sealar la posicin de comienzo de una palabra */ int Pos; /* Usar esta variable para ir recorriendo el vector */ Pos = Inicio = 0; /* Comienzo en el principio del vector */ do{ /* Si encuentro un caracter espacio punto o fin de texto, llegu al final de una palabra */ if (Entrada[Pos] == ' ' || Entrada[Pos] == '.' || Entrada[Pos] == 0) { /* Me fijo que la palabra no empiece y termine en la misma posicin, lo que ocurrira si el string estuviese vaco o si hubiesen varios espacios juntos */ if (Inicio != Pos) { /* Imprimo uno a uno los caracteres de la palabra */ while (Inicio < Pos) { printf ("%c", Entrada[Inicio]); ++Inicio; } /* Salto al siguiente rengln */ printf ("\n"); } /* Sealo el comienzo de la siguiente palabra */ Inicio = Pos+1; } ++Pos; }while (Entrada[Pos-1] != 0);

void main(void)

60

75.42 - Taller de Programacin {

char Texto[100] = "el gato esta arriba de la mesa."; printf ("Las palabras que componen la oracion '%s' son:\n", Texto); SepararPalabras (Texto);

El programa imprimir:

Las palabras que componen la oracion 'el gato esta arriba de la mesa' son: el gato esta arriba de la mesa En la funcin SepararPalabras, utilizo dos variables de tipo entero, llamadas Inicio y Pos. La primera de las variables contendr la posicin de comienzo de la palabra, en tanto que la segunda se utiliza para ir recorriendo la palabra, hasta encontrar el fin de la misma.

Notar que cuando se encuentra el final de una palabra, se la imprime carcter a carcter, y al final se agrega el salto de lnea. Este programa muestra claramente la forma en que se guardan las cadenas de caracteres. En C los string son simplemente vectores que contienen bytes, es decir, el cdigo ascii de cada letra, terminadas con el byte 0.

La funcin printf() provee la forma de imprimir strings, utilizando %s.

El lenguaje C provee una librera para manejo de string, llamada string.h. En ella se definen entre otras las siguientes funciones: strcmp (s1, s2); compara los string s1 con s2. Devuelve 0 si son iguales, o -1 o +1 si son diferentes (-1 si s1 est alfabticamente antes de s2, y 1 en caso contrario). strcpy (s1, s2); Copia en string s2 en s1. strcat (s1, s2); Copia (concatena) el string s2 al final de s1.

Ms sobre el pasaje de vectores a funciones

Veamos un ejemplo de un programa en C:

61

75.42 - Taller de Programacin

Ejemplo 6.7: Pasaje de vectores a funciones

#include <stdio.h> void Funcion (char s[10]) { s[0] = C; s[1] = h; s[2] = a; s[3] = u; s[4] = \0; } void main(void) { char Str[10] = "Hola"; printf ("%s\n", Str); Funcion (Str); printf ("%s\n", Str);

Este programa producir la siguiente salida:

Hola Chau En C todo el pasaje de parmetros a las funciones se realiza por valor, es decir, no existe el pasaje por referencia. Veamos ahora el programa:

Al comenzar el programa, defino una variable Str, local a la funcin main().

A continuacin llamo a la funcin Funcion(), que carga el texto "Chau" en la variable s, local a la funcin Funcion(), y que es una copia de la variable Str de la funcin main().

El siguiente printf() muestra que la variable Str contiene el texto Chau.

Pero si slo existe el pasaje por valor, cmo es posible que esta funcin haya modificado esta variable?

La respuesta a esta pregunta exige presentar un nuevo tema; los punteros, que se ver ms adelante. Sin embargo vamos a dar una respuesta rpida sin entrar en detalles, ya que el anlisis exhaustivo de este tema se ver en el captulo punteros.

La variable s no contiene en realidad un vector, sino la direccin de memoria donde se almacena dicho vector.

62

75.42 - Taller de Programacin Es por ello que cuando se llama a una funcin, no se pasa una copia del vector, sino una copia de la posicin de memoria donde se encuentra dicho vector, y por lo tanto cualquier modificacin que se realice sobre el vector se har sobre el vector original.

Vindolo desde otro punto de vista, se podra decir que el pasaje de vectores a funciones "se comporta en forma idntica" al pasaje de parmetros por referencia.

Ninguna funcin puede devolver un vector. La devolucin de vectores no est soportada por C, pero se puede hacer algo parecido, lo cual se ver en el tema punteros.

Existe una biblioteca de funciones standard para trabajar con strings, declarada en el archivo string.h. Veamos un ejemplo:

Ejemplo 6.8: funcin strcpy ()

#include <stdio.h> #include <string.h> void Funcion (char s[]) { strcpy (s, "Chau"); } void main(void) { char Str[10] = "Hola"; printf ("%s\n", Str); Funcion (Str); printf ("%s\n", Str);

Este programa producir la siguiente salida:

Hola Chau Notar que en la funcin Funcin, no se especific le tamao del vector s. Esto tiene sentido, ya que la funcin Funcion no recibe una copia de los elementos, sino tan slo de la direccin en memoria de los mismos. Tampoco se realizar ningn chequeo de validez de los ndices, razn por la cual esta funcin slo necesita saber que recibir como parmetro un vector, pero no le interesa la cantidad de elementos que el mismo contenga.

La funcin strcpy() copia un string en otro. Desde ya que el C no verifica que el string destino sea suficientemente grande. Es decir algo como:

char s[5]; strcpy (s, "Estoy tratando de cargar un string muy grande en un string muy chico.");

63

75.42 - Taller de Programacin

sera un error muy grave, que llevara a que el programa no funcione correctamente, pero ni el compilador ni el programa emitirn ningn mensaje de error ni de advertencia.

Otras funciones definidas en esta biblioteca son:

strcmp()

compara dos strings. Devuelve 0 si son iguales y -1 +1 si son diferentes (-1 si el primer string es alfabticamente anterior al segundo y +1 si no lo es).

strcat()

Concatena dos strings, es decir, copia el segundo string a continuacin del primero.

toupper ()

Devuelve el caracter en maysculas.

tolower ()

Devuelve el caracter en minsculas.

en stdio.h se definen tambin:

atoi()

Convierte un string en un entero.

atof()

Convierte un string en un nmero de punto flotante.

64

75.42 - Taller de Programacin

Captulo VII - Matrices


Las matrices son similares a los vectores. Pueden definirse matrices n-dimencionales, en forma similar a una matriz bidimensional, pudiendo las mismas contener cualquier tipo de datos. Por ejemplo, una matriz de enteros de 2x3 se define como: int Mat[2][3]; Al igual que con cualquier tipo de variable, es posible cargar valores iniciales en los vectores y matrices, encerrando cada vector entre llaves. Por ejemplo, podra darse valores iniciales a la matriz anterior en la forma que sigue: int Mat[2][3] = {{0, 1, 2}, {3, 4, 5}}; En este ejemplo, los ndices de la matriz irn de [0-1][0-2], pero como ya vimos, el lenguaje C no realiza ningn tipo de verificacin, por lo que nada nos impedira hacer referencia al elemento Mat[0][5]. En este caso esto no es un error, como podra suponerse, ya que este elemento no est fuera de la matriz sino que se tratar del elemento Mat[1][3]. Los elementos de una matriz se ordenan en forma contigua en memoria, uno detrs de otro, es decir que, en este ejemplo, el orden sera: [0][0] ; [0][1] ; [0][2] ; [1][0] ; [1][1] ; [1][2] Notar que en este ejemplo, el nmero almacenado en cada posicin de la matriz es el orden en el cual se almacenarn en la memoria.

Una matriz n-dimensional se definira como sigue:

int Mat[2][2][3] = { {{0, 1, 2}, {3, 4, 5}}, {{6, 7, 8}, {9, 0, 1}}}; Recordar que los saltos de lnea son opcionales, se utilizan slo para dar mayor claridad al texto.

Veamos ahora el ejemplo de un programa traductor. Nota: La funcin TraducirOracin() es una variacin de SepararPalabras(), vista en el captulo anterior, razn por la cual no se la analizar nuevamente.

Ejemplo 7.1: Matrices - programa traductor

#include <stdio.h> #include <string.h> void TraducirPalabra (char Palabra[]) { int i; char Diccionario [20][10] = { "el", "the", "gato", "cat", "perro", "dog", "esta", "is",

65

75.42 - Taller de Programacin

"arriba", "la", "de", "un", "mesa", "silla",

"on", "the", "", "a", "table", "chair"};

for (i = 0; i < 20;i += 2) { if (strcmp (Diccionario[i], Palabra) == 0) { printf (Diccionario[i+1]); printf (" "); return; } } printf ("%s? ", Palabra);

void TraducirOracion (char Entrada[]) { int Inicio; int Pos; int PosPalabra; char Palabra[10]; Pos = Inicio = 0; do { if (Entrada[Pos] == ' ' || Entrada[Pos] == '.' || Entrada[Pos] == 0) { if (Inicio != Pos) { PosPalabra = 0; while (Inicio < Pos) { Palabra [PosPalabra] = Entrada[Inicio]; ++PosPalabra; ++Inicio; } Palabra[PosPalabra] = 0; TraducirPalabra (Palabra); } Inicio = Pos+1; } ++Pos; }while (Entrada[Pos-1] != 0);

void main(void) { char Texto[100] = "el gato esta arriba de la mesa."; printf ("La traduccion de la oracion '%s' es:\n", Texto); TraducirOracion (Texto); printf("\n");

La salida del programa ser la siguiente:

66

75.42 - Taller de Programacin

La traduccion de la oracion 'el gato esta arriba de la mesa.' es: the cat is on the table Notar que en este ejemplo, en la funcin traducir se define una matriz Diccionario, que mediante el segundo ndice recorrer los distintos caracteres, y con el primero las palabras. Tambin podra pensrsela como un vector que en cada posicin contiene un string, (que no son otra cosa que vectores de caracteres). En las posiciones pares del primer ndice estar la palabra en castellano, y en las posiciones impares las palabras en ingls. La funcin strcmp() est definida en string.h, y sirve para comparar strings, es decir, cadenas de caracteres terminadas en 0. Esta funcin devuelve 0 si las palabras son iguales, y -1/+1 si son diferentes (-1 si la primer palabra es alfabticamente anterior a la segunda y +1 en caso contrario).

Podra haberse definido una matriz 3-dimensional en la forma siguiente:

void TraducirPalabra (char Palabra[10]) { int i; char Diccionario [10][2][10] = { {"el", "the"}, {"gato", "cat"}, {"perro", "dog"}, {"esta", "is"}, {"arriba", "on"}, {"la", "the"}, {"de", ""}, {"un", "a"}, {"mesa", "table"}, {"silla", "chair"}}; for (i = 0; i < 10; ++i) { if (strcmp (Diccionario[i][0], Palabra) == 0) { printf (Diccionario[i][1]); printf (" "); return; } } printf ("%s?", Palabra);

Pasaje de matrices a funciones

El pasaje de matrices a funciones es idntico al pasaje de vectores. Incluso en el ejemplo anterior podramos haber pasado el diccionario como parmetro:

Ejemplo 7.2: Matrices - programa traductor

#include <stdio.h> #include <string.h> void Traducir (char Palabra[10], char Diccionario [][2][10]) {

67

75.42 - Taller de Programacin

int i; for (i = 0; i < 10; ++i) { if (strcmp (Diccionario[i][0], Palabra) == 0) { printf (Diccionario[i][1]); printf (" "); return; } } printf ("%s? ", Palabra);

void TraducirOracion (char Entrada[], char Diccionario [][2][10]) { int Inicio; int Pos; int PosPalabra; char Palabra[10]; Pos = Inicio = 0; do{ if (Entrada[Pos] == ' ' || Entrada[Pos] == '.' || Entrada[Pos] == 0) { if (Inicio != Pos) { PosPalabra = 0; while (Inicio < Pos) { Palabra [PosPalabra] = Entrada[Inicio]; ++PosPalabra; ++Inicio; } Palabra[PosPalabra] = 0; TraducirPalabra (Palabra, Diccionario); } Inicio = Pos+1; } ++Pos; }while (Entrada[Pos-1] != 0);

void main(void) { char Texto[100] = "el gato esta arriba de la mesa."; char DicCastellanoIngles [10][2][10] = { {"el", "the"}, {"gato", "cat"}, {"perro", "dog"}, {"esta", "is"}, {"arriba", "on"}, {"la", "the"}, {"de", ""}, {"un", "a"}, {"mesa", "table"}, {"silla", "chair"}};

68

75.42 - Taller de Programacin printf ("La traduccion de la oracion '%s' es:\n", Texto); TraducirOracion (Texto, DicCastellanoIngles); printf("\n");

Notar que al especificar los parmetros que recibe la funcin, no se especifica el primer tamao pero s los restantes. En C es obligatorio especificar las dimenciones de cada uno de los vectores de un array ndimencional, con excepcin del primero.

69

75.42 - Taller de Programacin

Captulo VIII - Punteros


Introduccin

Un puntero es un tipo de variable como cualquier otra, cuyo contenido es una direccin de memoria. El concepto en s es muy simple, y su uso es igual a los dems tipos de variables del lenguaje C. Sin embargo el uso de punteros suele resultar algo complicado y confuso para quin recin comienza a programar en lenguaje C, y es un tipo de dato que no est presente en otros lenguajes de programacin y por lo tanto puede resultar nuevo.

Qu es un puntero?

Un puntero es simplemente una variable cuyo contenido es una direccin de memoria.

En la prctica, los punteros suelen usarse para direccionar un tipo de dato en particular. Por esta razn, y tambin para saber como direccionar el dato, en C se suele asociar al puntero el tipo de dato que se va a direccionar.

Un puntero se define simplemente anteponiendo un * (asterisco) al nombre de la variable, en su definicin (Ej: int *a;).

Es importante recordar que en C, ninguna variable es inicializada por defecto, salvo que se indique lo contrario. De la misma forma, un puntero, al ser creado, contendr una direccin de memoria cualquiera. Si se esta programando en una computadora sin modo protegido (D.O.S., por ejemplo), toda la memoria es direccionable, es decir que se puede leer/sobreescribir toda la memoria de la PC, incluyendo el sistema operativo. En modo protegido, direccionar una posicin de memoria invlida producir un mensaje de error, conocido en Windows como 'General Protection Fault'.

Veamos un par de ejemplos: int *a; a es una variable que almacenar una direccin de memoria, en tanto que *a ser el entero contenido de dicha direccin de memoria.

Por ejemplo, supongamos que a tenga el valor de la direccin de memoria 2032, y *a tenga el valor 50:

70

75.42 - Taller de Programacin

71

75.42 - Taller de Programacin

El operador &, antepuesto a una variable devuelve la direccin de dicha variable, y se lo llama operador de indireccin. En nuestro ejemplo tendremos: Variable puntero a entero a a Veamos otro ejemplo: Tipo de variable (int *) entero(int) puntero a un puntero a entero (int **) Tipo de contenido direccin de memoria entero direccin de memoria contenido 2032 50 1000

Ejemplo 8.1: Punteros

01 02 03 04 05 06 07 08 09 10 11 12 13 14

#include <stdio.h> void main(void) { /* Definicin de las variables */ int a; int *pa; a = 4; pa = &a; *pa = 2; } printf ("%d", a);

Este programa imprimir el valor 2

En este ejemplo, pa es un puntero a una variable de tipo entero, es decir, una variable que contendr una direccin de memoria en donde se almacenar un entero.

En la lnea 6 se define una variable a que guardar una variable de tipo entero, y a la cual le asigno el valor 4, en la lnea 9 del cdigo. &a ser la direccin de dicha variable. As, lo que estoy haciendo en la lnea 10 del ejemplo es cargar en pa la direccin de la variable a. Lo que se tiene en este momento es lo siguiente:

Cuando a continuacin hago referencia a *pa, me estoy refiriendo a la direccin apuntada por pa, que en este caso ser a.

72

75.42 - Taller de Programacin Puedo preguntar ahora que es &pa? Pues bien, es la direccin de memoria donde est almacenada la variable pa, que a su vez es la direccin de memoria de un entero, es decir, &pa es un puntero a un puntero a una variable de tipo entero.

En este caso podra escribirse:

int **ppa; ppa = &pa;

es decir que pueden definirse punteros a punteros a un tipo de variable, y as sucesivamente, aunque en la prctica slo se usan a lo sumo doble punteros.

Es comn utilizar un caracter p como primer letra del nombre de una variable de tipo puntero, para tener en claro que se trata de un puntero. De idntica forma, se suele usar pp al trabajar con doble punteros, y as sucesivamente.

Tendra sentido preguntarse por &&a?

Pues no, de ninguna manera. a es una variable que est almacenada en una posicin de memoria, conocida por el programa. Con &a puedo conocer dicha posicin, pero &&a no tiene ningn sentido.

Veamos un ejemplo de una funcin que intercambia el contenido de dos variables de tipo entero. Llamaremos a esta funcin swap.

Primer ejemplo con punteros

Ejemplo 8.2: Funcin swap (versin incorrecta)

/* Este ejemplo no funciona correctamente */ void swap (int a, int b); void main (void) { int a, b; } swap (a, b);

/* Funcin que intercambia los dos enteros */ void swap (int a, int b) { int c; c = a; a = b; b = c;

73

75.42 - Taller de Programacin

} Funcionar este ejemplo correctamente? Claramente no, ya que la funcion swap() recibir una copia de las variables a y b, las cuales locales a esta funcin, y que sern efectivamente intercambiadas. Pero las variables originales no sern modificadas.

Para poder intercambiar el contenido de estas dos variables en la funcin que la llam, deber pasar las direcciones de las mismas, de tal forma que la funcin pueda acceder directamente a las variables usadas por quien la llam.

Toda funcin recibe como parmetros copias de los datos especificados (pasaje por valor), nunca datos mismos (pasaje por referencia).

Ejemplo 8.3: Funcin swap (versin correcta

void swap (int *a, int *b); void main (void) { int a, b; } swap (&a, &b);

/* Funcin que intercambia los dos enteros */ void swap (int *a, int *b) { int c; c = *a; *a = *b; *b = c;

Nota de sintaxis

A veces se ve en algunas definiciones de punteros, el * del lado de la definicin del tipo de dato en vez del de la variable. Por ejemplo: int* a; en vez de int *a; Esto no es un error de programacin, pero si puede llevar a errores de comprensin, por ejemplo en el siguiente caso: int* a, b;

74

75.42 - Taller de Programacin En este caso, a ser un puntero, pero b no, ya que el compilador lo interpreta como: int *a, b; que es la notacin ms adecuada..

Por otro lado, cuando una funcin devuelve un puntero, esto puede escribirse como: int* funcion(); int *funcion(); En este caso, si bien ambas declaraciones son correctas, la segunda parecera ser un puntero a una funcin que devuelve un entero (un tema que se ver ms adelante), por lo que la sintaxis ms adecuada es la primera.

Veamos un par de ejemplos ms para comprender el tema de punteros.

Ejemplo 8.4: Punteros /* Este programa es incorrecto */ void main(void) { int *pa; } *pa = 5;

En la funcin main() defino una variable llamada pa. A continuacin cargo el valor 5 en la posicin "apuntada" por dicha variable.

El programa es sintcticamente correcto. Entonces qu est mal en este programa?

Rta: El valor de pa no fue nunca definido, o dicho de otra forma, la variable pa no fue inicializada. A este error se lo conoce como "puntero no inicializado". Todo puntero no inicializado tendr un valor impredecible, y por lo tanto el valor 5 se estar cargando en una posicin cualquiera de la memoria.

Escribamos ahora una funcin que devuelva el mximo y el mnimo de dos nmeros:

Ejemplo 8.5: Punteros void MinMax (int a, int b, int *min, int *max) { if (a < b) { *min = a; *max = b;

75

75.42 - Taller de Programacin

} else { *min = a; *max = b; }

void main(void) { int a, b, m, M; a = 5; b = 2; MinMax (a, b, &m, &M); } printf ("El mximo entre %d y %d es %d\n", a, b, M);

La salida de este programa ser: El mximo entre 5 y 2 es 5 Ms acerca del pasaje de variables a funciones

Veamos el siguiente ejemplo:

#include <stdio.h> #include <malloc.h> int DevolverEntero (void) { int n; printf ("Ingrese el numero:\n"); scanf ("%d", &n); } return n;

void CargarEntero (int *n) { printf ("Ingrese el numero:\n"); scanf ("%d", n); } void MostrarEntero (int n) { printf ("Numero = %d\n", n); } void main (void) { int i,j; printf ("Repaso de punteros\n");

76

75.42 - Taller de Programacin i = DevolverEntero (); CargarEntero (&j); MostrarEntero (i); MostrarEntero (j);

Notar las dos funciones que se definieron para cargar enteros. La primera de ellas (DevolverEntero()), ms simple de comprender, solicita al usuario el ingreso de un entero. La segunda de las funciones (CargarEntero()) recibe un puntero a la posicin de memoria donde debe cargar el entero ingresado por el usuario.

La funcin scanf() est definida en stdio.h, y su funcionamiento es similar y opuesto al de printf(). Esta solicita al usuario el ingreso de un parmetro por la entrada standard (generalmente teclado). Utilizar esta funcin con un nico parmetro. La principal diferencia entre prinf() y scanf() en la forma en que son usadas, es que la primera no requiere modificar los parmetros recibidos, y por lo tanto recibe copias de los mismos, en tanto que la segunda s debe hacerlo, por lo que recibe el puntero a los datos.

Finalmente la funcin MostrarEntero() es una funcin como cualquier otra. La misma recibe una copia del entero, y lo muestra en pantalla. Podra escribirse una versin similar que reciba un puntero al entero a mostrar.

Todo puntero debe estar inicializado?

Rta: Si bien no es obligatorio, todo puntero debe necesariamente ser inicializado para poder ser utilizado. En caso contrario se producir un error en tiempo de ejecucin al acceder a la posicin especificada. Pero esto no obliga a inicializar todos los punteros de un programa. Los punteros son utilizados con fines muy variados, y es comn que en muchos valores estos no contengan ningn valor vlido. Para dejar el claro que el puntero no contiene ningn valor til, se puede cargar en los mismos el valor 0, al que se llama habitualmente puntero nulo o NULL. Se supone que la direccin de memoria se reserva para indicar una posicin de memoria invlida. Ej: int *pa = NULL; /* Creo el puntero pa y le cargo el valor NULL, definido en stdio.h como 0 */ Veamos otro ejemplo: Escriba un programa que cargue el valor NULL en un puntero a entero, utilizando una funcin:

Ejemplo 8.6: Punteros a punteros (versin incorrecta)

/* Programa incorrecto */ #include <stdio.h> void Init (int *pa) { pa = NULL; } void main (void) { int *pa; Init (pa); }

77

75.42 - Taller de Programacin

Nuevamente, el programa es sintcticamente correcto. Qu est mal entonces? Al llamar a la funcin Init() se para como parmetro una copia de la variable pa. Esta copia se inicializa en NULL, pero el valor original no fue modificado.

Como puede verse el en dibujo, desde la funcin Init() simplemente no hay forma de modificar la variable pa de la funcin main(), ya que no se tiene acceso a ella. Para solucionar esto, debe modificarse tanto la funcin como la llamada a la misma.

Veamos ahora la versin corregida: #include <stdio.h> void Init (int **ppx) { *ppx = NULL; } void main (void) { int *a; Init (&a); }

78

75.42 - Taller de Programacin

Notar como en la funcin Init() debi utilizarse un puntero a puntero a entero. Pueden definirse de esta forma, punteros a punteros, indefinidamente. Sin embargo no hay ninguna razn til para definir ms de dos niveles de punteros.

79

75.42 - Taller de Programacin

Captulo IX - Memoria dinmica y punteros a vectores


Veamos otro ejemplo similar al del capitulo anterior, pero ahora con string:

Ejemplo 9.1: Punteros y vectores

#include <stdio.h> #include <stdlib.h> #define MAX_STRING 1000 void CargarString (char *s) { printf ("Ingrese el texto:\n"); scanf ("%s", s); } void MostrarString (char * s) { printf ("String=%s\n", s); } void main (void) { char *s1; s1 = (char *)malloc (MAX_STRING); CargarString (s1); MostrarString (s1); } free (s1);

s1 se define como un puntero a un caracter (recordemos que 1 caracter ocupa 1 byte en memoria). Sin embargo en la segunda lnea de la funcin main() reservaron 1000 bytes para dicha variable, es decir que estoy reservando memoria para 1000 caracteres. Esta informacin no se tiene en el puntero. Este simplemente apunta al primer carcter del arreglo (array) o vector, pero no conoce el tamao del mismo.

La funcin malloc() es la encargada de solicitar un bloque de memoria al sistema operativo. Esta funcin devuelve un puntero genrico (void *), que es un puntero que no apunta a ningn tipo de dato en particular o, dicho de otra forma, es una direccin de memoria en donde no se ha especificado qu tipo de dato hay. Para cargar este valor en la variable s1, que es un puntero a caracter (char *), debe utilizarse algo llamado cast o casting, esto es transformacin de un tipo de variable a otro (ya visto anteriormente), anteponiendo al valor devuelto por malloc() la conversin (char *).

La funcin free() sirve para devolver la memoria solicitada al sistema operativo. Al terminar todo programa, el sistema operativo automticamente libera toda la memoria que el mismo haya solicitado y que no haya sido liberada, razn por la cual el ltimo free no es necesario, pero es conveniente ponerlo por claridad, o por si en un futuro que quiere transformar el programa en una funcin de un programa mayor.

80

75.42 - Taller de Programacin Ambas funciones, malloc() y free(), estn definidas en stdlib.h.

Luego llamo a la funcin CargarString(), que recibe una copia del puntero a la cadena de caracteres, y llama a la funcin scanf() tambin pasndole una copia de dicho puntero.

Finalmente se llama a la funcin MostrarString(), a la cual se le pasa una copia del puntero a los datos.

Los vectores inicializados de esta forma (malloc - free) se los conoce como vectores o arrays dinmicos, tambin llamados memoria dinmica, en tanto que los vectores utilizados anteriormente, que se almacenan en la pila del sistema, se conocen como vectores o arrays estticos, tambin llamados memoria esttica.

Pasaje de vectores a funciones

Veamos otro ejemplo similar, del mismo programa. Es conveniente que se vaya comparndo el cdigo con el ejemplo anterior.

Ejemplo 9.2: Pasaje de vectores a funciones

#include <stdio.h> #include <stdlib.h> #define MAX_STRING 1000 void CargarString (char s[]) { printf ("Ingrese el texto:\n"); scanf ("%s", s); } void MostrarString (char s[]) { printf ("String=%s\n", s); } void main (void) { char s1[MAX_STRING]; CargarString (s1); MostrarString (s1); } El programa es idntico, salvo por el hecho de que ahora el string est definido de otra forma. Notar que en la funcin main se define a s1 como: char s1[MAX_STRING]; Esto hace que el vector est definido en forma "esttica", es decir al ejecutarse la funcin main() se reservan automticamente MAX_STRING caracteres para almacenar el dato, y los liberar automticamente al salir de la funcin.

81

75.42 - Taller de Programacin

Dnde se almacenarn los datos? Rta: en la pila del sistema. Si se trabaja con sistemas operativos de 16 bits (D.O.S. y Windows 3.1) la pila del sistema es muy escasa (algunos Kbytes), en tanto que en sistemas operativos de 32 bits (Windows 95, 98, NT, UNIX), la misma suele ser de algunos MegaBytes, lo cual es bastante pero no excesivo. Por esta razn conviene definir de esta forma slo variables pequeas.

Qu pasa cuando se llama a las funciones? No se ha definido que se pase un puntero. Se pasa una copia completa del vector?. La respuesta ya vista anteriormente es NO. Siempre que en C una funcin reciba un vector, recibir tan solo el puntero a los datos, es decir que es lo mismo definir una funcin como: MiFuncion (TipoDato Dato[]) o como MiFuncion (TipoDato *Dato) lo cual es ms claro, y por eso generalmente se utiliza esta notacin.

Aritmtica de punteros

Pero s es equivalente definir TipoDato Dato[] TipoDato *Dato, tambin debera ser equivalente definir Dato[0] *Dato. Y la respuesta es que efectivamente lo es.

Ms an, tambin es equivalente escribir Dato[n] *(Dato + n)

Esto est claro al trabajar con vectores de caracteres, como en el siguiente ejemplo:

Ejemplo 9.3: Aritmtica de punteros #include <stdio.h> #include <stdlib.h> void main (void) { char *string; string = (char*)malloc (1000); string[1] = 0; *(string+1) = 0; /* Cargo el la posicin 1 del vector el valor 0 */ /* Cargo el la posicin 1 del vector el valor 0 */

En este ejemplo, las dos ltimas lneas del programa son equivalentes.

Si string tuviese, por ejemplo, el valor 5200 en la posicin de memoria 5200 estar el primer elemento del vector. Pues bien, string+1 ser 5201, con lo cual al hablar de *(string+1) se estar hablando del contenido de la posicin 5201, o lo que es lo mismo, el segundo elemento (posicin 1) del vector.

82

75.42 - Taller de Programacin Qu hubiese pasado si se tena un vector de enteros? Vemoslo.

Ejemplo 9.4: Aritmtica de punteros

#include <stdio.h> #include <stdlib.h> void main (void) { int *string; string = (char*)malloc (1000 * sizeof (int)); string[1] = 0; *(string+1) = 0; /* Cargo el la posicin 1 del vector el valor 0 */ /* Cargo el la posicin 1 del vector el valor 0 */

Supondr en este anlisis que se trabaja en un sistema operativo de 32 bits, en donde el tamao del entero es de 32 bits (4 byte).

Notar que en este caso , en la funcin malloc() se reserv memoria para 1000 enteros, no 1000 bytes. La funcin sizeof() ser interpretada por el compilador, y ser reemplazada por el tamao del tipo de dato especificado.

Supongamos nuevamente que el valor devuelto por malloc() y copiado en string es 5200.

La lnea string[1] = 0; claramente copia el valor 0 en la posicin 1 del vector. Pero qu ocurre con la siguiente?

Si string tiene el valor 5200, sera lgico suponer que string+1 tendr el valor 5201, pero esto no es as. Por el contrario, tomar el valor 5204. qu ha sucedido? Al sumarle a un puntero un valor, no se suma dicha cantidad a la direccin de memoria, sin que se aumenta el puntero dicha cantidad de elementos.

De idntica forma, puede efectuarse diferencia (resta) de punteros, y el valor devuelto no ser la diferencia aritmtica de las direcciones sino la cantidad de elementos comprendidos entre los dos punteros.

Volvamos ahora al ejemplo anterior, en donde el string se defina como un puntero a caracter, y se reservaba memoria para el mismo. qu pasara si quisisemos que la misma funcin para cargar el dato fuese la que reservase memoria para el mismo?

Ejemplo 9.5: Punteros

#include <stdio.h> #include <stdlib.h> #include <malloc.h> #define MAX_STRING 1000

83

75.42 - Taller de Programacin

char * DevolverString (void) { char * s; s = (char *)malloc (MAX_STRING); if (!s) { printf ("Memoria insuficiente\n"); exit(1); } printf ("Ingrese el texto:\n"); scanf ("%s", s); } return s;

void CargarString (char **s) { *s = (char *)malloc (MAX_STRING); if (!*s) { printf ("Memoria insuficiente\n"); exit(1); } printf ("Ingrese el texto:\n"); scanf ("%s", *s);

void MostrarString (char * s) { printf ("%s\n", s); } void main (void) { char *s1, *s2; s1 = DevolverString (); CargarString (&s2); MostrarString (s1); MostrarString (s2); free (s1); free (s2);

Veamos que, al igual que en el ejemplo de los enteros, hay dos funciones para cargar el string. La primera de ellas, DevolverString() define un puntero a la cadena de caracteres char *s;

Luego reserva memoria y carga dicho puntero la direccin donde se reserv la memoria, y verifica que el resultado de esta operacin haya sido exitoso. Si no hubiese memoria disponible, la funcin malloc() retornar NULL (que es un #define con el valor 0).

84

75.42 - Taller de Programacin Finalmente se carga el dato y se devuelve una copia de dicho puntero.

La segunda funcin CargarString() es un tanto ms complicada. Ya que debe devolverse a la funcin principal un puntero, esta funcin deber saber la posicin de memoria donde debe almacenar dicho puntero, es decir, deber conocer la posicin de memoria de un puntero, o lo que es lo mismo, un puntero a un puntero a una cadena de caracteres. suena complicado? Es necesario practicar bastante con esto para comprenderlo correctamente.

Notar que *s es el dato que la funcin debe devolver, y s es la posicin de memoria donde est almacenada esta variable.

Finalmente reservo memoria para el dato, y almaceno la direccin en *s, es decir, en el puntero a la cadena de caracteres.

El modificador const y los punteros

a hemos visto la palabra reservada const, que permite garantizar que el valor de una variable se mantendr constante durante toda la ejecucin de la funcin. Por ejemplo:

void Funcion (const unsigned Valor) { unsigned a; a = Valor; a++; Valor++; /* Incorrecto, la variable Valor es constante y no puede ser modificada */ } Los punteros, son variables y por lo tanto tambin pueden ser especificados como constantes. Pero en este caso existen dos variables, que permiten especificar si el puntero o el valor especificado es constante. Veamos un ejemplo. En este caso el valor del puntero ser constante, pero no los datos apuntados.

void Funcion (char * const p) { char *s; *p = A; p[2] = A; *(p+5) = A; /* Correcto */ /* Correcto */ /* Correcto */

85

75.42 - Taller de Programacin

++p; ((char *)p) = NULL; s = p; ++s;

/* /* /* /*

Incorrecto, p es constante */ Incorrecto, p es constante */ Correcto */ Correcto */

Por otro lado, tambin es posible especificar que el valor apuntado por p es constante, que el lo que generalmente se requiere. En este caso se tendr:

void Funcion (const char *p) /* Tambin puede escribirse void Funcion (char const *p) */ { *p = A; /* p[5] = A; /* *(p+5) = A; /* *((char *)p+5) = A; /* ((char *)p)[5] = A; /* (char *)p++; /* }

Incorrecto */ Incorrecto */ Incorrecto */ Correcto, no recomendado */ Correcto, no recomendado */ Correcto, no recomendado */

Notar que en estos casos el puntero p no es modificable directamente pero s la posicin apuntada por p. Puede modificarse el valor de p mediante un cast (conversin de tipo), pero esto no es recomendable.

Tambin es posible especificar que la posicin apuntada por p no pueda ser modificada. Esto se hace simplemente cambiando la posicin de la palabra reservada const.

void Funcion (char * const { *p = A; ++p; p[5] = A; *(p+5) = A; ((char *)p)[0] = 'A'; *((char *)p+5) = A; }

p) /* /* /* /* /* /* Incorrecto */ Correcto */ Incorrecto */ Incorrecto */ Correcto, no recomendado */ Correcto, no recomendado */

Desde ya que es posible especificar las dos opciones, es decir un puntero que no pueda ser modificado, cuyo valor apuntado tampoco pueda serlo. En este caso se tendra: void Funcin (const char * const p) Tambin es posible que una funcin devuelva un puntero constante. Veamos un ejemplo: const char * Funcion (const char * p) { return p; } En este caso, podrn realizarse operaciones tales como:

86

75.42 - Taller de Programacin char c; c = Funcion("Hola") [0]; pero no

char c; Funcion("Hola") [0] = c; En ningn caso es obligatorio el uso de la palabra const, pero s es buena prctica utilizarla, y puede ayudar a detectar errores, as como a dar claridad al cdigo. Sin embargo su uso es un tanto tedioso, por lo que en la prctica muchas veces se la omite, o se la utiliza slo en el desarrollo de libreras.

87

75.42 - Taller de Programacin

Captulo X - Punteros y estructuras de datos


Punteros a estructuras

Definamos un registro para almacenar datos de personas:

typedef struct { char Nombre[19]; unsigned Edad; char Direccion[40]; char Telefono[10]; }TDato; Supongamos que queremos almacenar los datos de unas 1000 personas. Para ello debermos generar un vector con elementos de tipo TDato, con 1000 posiciones:

void main(void) { TDato Registros[1000]; } Sin embargo, como ya se dijo, los vectores definidos en forma esttica se almacenan en la pila del sistema, por lo cual no es bueno el uso indiscriminado de la misma. En un sistema operativo de 16 bits esto simplemente no sera posible, ya que excedera su capacidad. En un sistema de 32 bits, esto todava es posible, pero de ninguna manera recomendable. Utilicemos entonces memoria dinmica:

/* Este programa es incorrecto */ void main (void) { TDato *Registros; Registros = (TDato*) malloc (73 * 1000); printf ("Tamao del registro: %d\n", sizeof (TDato));

Nuevamente este programa es sintcticamente correcto. Ms an, si sumamos el tamao de los campos de la estructura TDato veremos que efectivamente suma 73, con lo cual 73*1000 debera ser la memoria requerida para almacenar el dato.

Sin embargo, si ejecutamos el programa veremos que el valor impreso (en la mayora de los compiladores) es 74. Qu ha sucedido?

Rta: Por razones internas de aquitectura de las computadoras, estas tienen problemas para acceder a enteros en posiciones de memoria no mltiplos del tamao del mismo. Esto es, un entero de 32 bits (4 bytes) debe estar en posiciones de memoria mltiplo de 4. Cuando esto no ocurre se dice que el dato est desalineado. En arquitecturas PC, acceder a un dato desalineado requiere el doble de tiempo. En arquitecturas RISC esto directamente no es posible, y si se lo hace, el programa se interrumpir.

88

75.42 - Taller de Programacin Para solucionar esto, el compilador dejar un espacio desperdiciado, a continuacin del campo Nombre, y almacenar el campo Edad en la vigsima posicin del registro, con lo cual el tamao del registro ser 74.

Por esta razn, nunca debe suponerse el tamao de una estructura, sino que debe utilizarse sizeof(). Corrijamos ahora nuestro programa:

void main (void) { TDato *Registros; } Registros = (TDato*) malloc (sizeof (TDato) * 1000);

Escribamos ahora una funcin que cargue datos en un registro del vector.

void CargarRegistro (TDato * Reg) { strcpy ( (*Reg).Nombre , "Juan"); (*Reg).Edad = 70; strcpy ( (*Reg).Direccion , "Santa Fe 2102"); strcpy ( (*Reg).Telefono , "344-1111"); } Notar la forma en que debieron escribirse cada uno de los campos del registro: (*Reg).Campo ya que el uso de parntesis suele resultar molesto, C permite escribir esto mismo como sigue: Reg->Campo con lo cual, esta ltima funcin quedara:

void CargarRegistro (TDato * Reg) { strcpy ( Reg->Nombre , "Juan"); Reg->Edad = 70; strcpy ( Reg->Direccion , "Santa Fe 2102"); strcpy ( Reg->Telefono , "344-1111"); } Agreguemos a nuestro ejemplo una funcin para imprimir un registro.

void MostrarRegistro (TDato Reg) { ... cdigo necesario para imprimir el registro... } Si bien este cdigo es correcto, constituye un ejemplo de mala programacin. En este caso, el registro no deber ser modificado, razn por la cual no hace falta pasar un puntero al registro, y puede pasarse el registro completo. Sin embargo, hacer esto requiere crear una copia del registro innecesariamente, desperdiciando as tiempo que podra ser aprovechado.

89

75.42 - Taller de Programacin

Este tiempo desperdiciado es pequeo, pero se torna crtico cuando la operacin se realiza en forma repetida. Para tener una idea de cmo esto puede llegar a afectar los resultados finales, un programa de encriptado de datos que implementa el tipo de dato entero grande (512 bits), llega a correr unas 10 veces ms rpido cuando los estos datos se pasan como punteros, en vez de por valor.

Preferentemente no pasar una estructura como parmetro a una funcin sino un puntero a la misma.

Captulo XI - Archivos
En stdio.h se define un conjunto standard muy extenso de funciones con las cuales acceder a archivos. Veremos aqu las funciones fundamentales. FILE *fopen (char *archivo, char *modo); Abre un stream a un archivo. Por ahora diremos que esto es equivalente a abrir un archivo, despus se ver la diferencia.

archivo es el nombre del archivo a abrir, con el path completo. Si no se indica el path, se usar el directorio actual.

El modo puede ser cualquiera de los siguientes: "r" Abre el archivo "w" Abre el archivo sobreescrito. "a" Abre el archivo escribir al final. "r+" Abre el archivo "w+" Abre el archivo ser sobreescrito. "a+" Abre un archivo para lectura. El archivo debe existir. nuevo para escritura. Si el archivo ya existe ser en modo append, es decir agregado. Se empezar a para lectura y escritura. El archivo debe existir. nuevo para lectura y escritura. Si el archivo ya existe para lectura y agregado.

Estos modos deben estar seguidos de la letra t (modo texto) o b (modo binario). El segundo de estos dos modos es ms eficiente, ya que se lee y escribe sin realizar ninguna conversin. En el primero, cada vez que se escriba un salto de lnea, ser grabado en el archivo en la forma que utilice el sistema operativo. En UNIX el salto de lnea se almacena simplemente como un salto de lnea. En DOS/Windows el mismo se almacena como dos bytes, un salto de lnea y un retorno de carro. El proceso inverso tiene lugar al leer el archivo.

El modo de apertura del archivo puede resumirse en la siguiente tabla, lo cual da un total de 12 modos: r

b + t

90

75.42 - Taller de Programacin En caso de poder abrir el archivo, se devuelve un puntero a la informacin requerida para acceder al mismo. En caso de error se devuelve NULL. int putc (int c, FILE *stream); Escribe un byte en el stream especificado. En caso de error devuelve EOF (255). En caso contrario el byte escrito. int getc (FILE *stream); Le un byte del archivo. En caso de error devuelve EOF (255). int fclose (FILE *stream); Cierra el archivo abierto. size_t fwrite (const void *buffer, size_t size, size_t count, FILE *stream); Escribe un dato cualquiera al stream. Recibe un puntero al dato a grabar, el tamao del mismo, la cantidad de elementos (en caso de tratarse de un vector) y el stream donde grabar los datos. size_t fread (void *buffer, size_t size, size_t count, FILE *stream); Lee un dato cualquiera al stream. Recibe un puntero al dato a leer, el tamao del mismo, la cantidad de elementos (en caso de tratarse de un vector) y el stream de donde leer los datos. int fprintf (FILE *stream, char *format,...); Similar a printf, pero en lugar de imprimirse en pantalla se imprime a un stream. int fseek (FILE *stream, long offset, int origin); Cambia la posicin dentro del stream. Recibe el stream a cambiar, la nueva posicin, y un parmetro (origin) que indica desde donde se la debe medir. Este parmetro puede ser: SEEK_SET: SEEK_END: SEEK_CUR: Desde el comienzo del archivo Desde el final Desde la posicin actual

Escribamos por ejemplo un programa que cree, escriba y lea un archivo cualquiera.

Ejemplo 11.1: Acceso a archivos

#include <stdio.h> void main (void) { FILE *fp; char c; /* Abro un archivo para escritura en modo texto */ fp = fopen ("prueba.txt", "wt"); /* Verifico que no se hayan producido errores */ if (fp == NULL)

91

75.42 - Taller de Programacin

} fprintf (fp, "Hola mundo\n"); /* Escribo un texto en el archivo */ fclose (fp); /* Cierro el archivo */ /* Abro nuevamente el archivo, pero ahora para lectura en modo texto, y verifico que no se hayan producido errores */ if ((fp = fopen ("prueba.txt", "wt")) == NULL) { printf ("Error al abrir nuevamente el archivo\n"); exit (1); } /* Leo un caracter. Si no se trata del fin de archivo lo imprimo */ while ((c = getc (fp)) = EOF) printf ("%c", getc(fp)); /* Cierro el archivo */ fclose (fp);

printf ("Error al crear el archivo\n"); exit (1);

Streams

Hasta ahora hemos procurado evitar la palabra archivo, y usado en lugar de ella la palabra stream. Pero qu es un stream y qu diferencia tiene con el acceso directo a un archivo? Para responder esta pregunta veamos el siguiente ejemplo: #include <stdio.h> #include <stdlib.h> void main(void) { FILE *fp; /* Abro un archivo para escritura en modo binario */ if ((fp = fopen ("prueba.txt", "wb")) == NULL) { printf ("Error al crear el archivo\n"); exit (1); } putc (10, fp); /* Escribo el byte 10 en el archivo */ fclose (fp); /* Cierro el archivo */

Si se verifica el tiempo requerido para ejecutar la llamada a putc() mediante programas destinados a tal fin, se ver que esta requiere algunos microsegundos. Sin embargo, el tiempo requerido por el disco rgido para acceder al archivo y grabar el byte es de varios milisegundos. qu ha sucedido? La respuesta es que el byte no ha sido copiado al archivo, sino a un buffer temporal y propio del sistema operativo. Esto es un stream.

El valor 10 no ha sido an escrito en el disco. El valor OK que devuelve el sistema operativo no significa que el mismo haya grabado el valor en el archivo sino tan slo:

92

75.42 - Taller de Programacin "he tomado conocimiento de que el programa requiere que se almacene este byte en una determinada posicin del archivo y he copiado este valor a un buffer propio sin que esto produzca ningn error. Posteriormente, cuando yo lo desee y si puedo hacerlo, escribir este valor en el archivo y no notificar a nadie el resultado de la operacin".

Esta forma de trabajar del sistema operativo hace que los programas funcionen en una forma muy rpida y eficiente, pero tiene sus riesgos, ya que si el funcionamiento de la computadora se interrumpe sbitamente los datos se perdern indefectiblemente.

Para evitar esto, existe la funcin fflush(). int fflush(FILE *fp); Esta funcin ordena al sistema operativo que los datos almacenados en el buffer del stream sean transferidos inmediatamente al archivo correspondiente.

Entrada/salida standard

Todo programa al comenzar recibe tres streams ya abiertos: stdin: Entrada standard, generalmente el teclado. stdout: Salida standard, generalmente pantalla. stderr: Salida standard de errores, generalmente pantalla. Por ejemplo, podra escribirse: fprintf (stdout, "Hola mundo\n"); y este texto sera impreso en pantalla.

nicamente bajo D.O.S. (no as bajo Windows ni UNIX ni Windows) se definen tambin: stdaux: Salida auxiliar. Rara vez se la utiliza. stdprn: Impresora. Entrada/salida sin streams

Existen tambin funciones para acceder directamente a archivos sin trabajar con streams. Estas son (entre otras): int int int int open (const char *filename, int oflag [, int pmode]); read (int handle, void *buffer, unsigned int count); write (int handle, void *buffer, unsigned int count); close (int handle);

Su uso es poco frecuente y muy similar a las anteriormente vistas para streams, razn por las cuales no se las analizar aqu.

93

75.42 - Taller de Programacin

Captulo XII - Temas varios de C


Variables static

En algunos casos puede ser til tener variables locales a una funcin, que conserven su valor an al salir de la misma. Un ejemplo de esto sera una variable contadora, que fuese local a una funcin Timer(), y que algn dispositivo externo incremente su valor. Otra razn puede ser una funcin que deba devolver un vector de datos, pero ya que esto no se puede hacer, se puede devolver un puntero a datos estticos de la funcin. Pero para ello es necesario que el vector no se destruya al salir de la misma.

En todos estos casos, puede utilizarse la palabra reservada static. Una variable de tipo static local a una funcin conservar su valor y no se destruir al salir de la funcin. La misma no ser duplicada si se implementa recursividad.

Una variable local static se comporta en forma idntica a una variable global, con la nica diferencia que slo ser conocida por la funcin donde se la defini.

Ejemplo 12.1: Variables static

int Timer(void) { /* El valor 0 se cargar en la funcin al comenzar el programa */ static int Contador = 0; Contador++; } return Contador;

Qu ocurre con las variables globales static?

Una variable global static no podr ser leda ni modificada por otros archivos del proyecto, es decir, pueden haber varias variables globales static con el mismo nombre en diferentes archivos del proyecto, sin que estas interfieran unas con otras.

Variables volatile

Otro modificador aplicable a las variables es la palabra reservada volatile. Una variable definida de esta forma no ser optimizada por el compilador, y su valor ser ledo de memoria cada vez que se lo requiera. Este modificador se utiliza cuando una variable puede ser modificada en forma externa. Este tipo de variables son frecuentes en sistemas que comparten el bus de datos con dispositivos externos. Imaginemos un programa que atienda comunicaciones por modem u otro dispositivo de entrada/salida:

94

75.42 - Taller de Programacin

Ejemplo 12.2: Variables volatile

void main(void) { volatile char *pLlamadaEntrante; */ /* Esta direccin de memoria 0x21000001 contiene el area de datos del modem pLlamadaEntrante = 0x21000001; while (!*pLlamadaEntrante); } AtenderLLamada();

En este caso, la variable pLlamadaEntrante no apuntar a un rea de memoria fsica, sino a un puerto de entrada/salida o un rea de registros de un dispositivo externo. El compilador necesita saber que dicha variable deber ser efectivamente leda y verificada cada vez que se la requiera, para evitar utilizar optimizaciones tales como cargar la variable en un registro y leer su valor del mismo. Las variables volatile son rara vez utilizadas en sistemas modernos ya que el sistema operativo se encarga de todas las operaciones de entrada/salida, y slo se las utiliza en el desarrollo de drivers.

Cmo recibir parmetros de la lnea de comandos?

La funcin main() ha sido hasta ahora declarada como: void main(void) Sin embargo, la misma puede recibir y devolver parmetros. En primer lugar la funcin main() puede devolver un entero al sistema operativo, que generalmente se conoce como ErrorLevel. Este valor puede ser utilizado por otros programas para saber si el mismo termin correctamente o no.

Pero la funcin main tambin puede recibir parmetros. Estos son conocidos como argc y argv, si bien podra elegirse otro nombre, y se definen como sigue: int main (int argc, char *argv[]) El primero contiene la cantidad de parmetros de la lnea de comandos, con que se llam al programa, valor que ser por lo menos 1.

El segundo es un puntero a una vector de punteros a caracter, que contienen los parmetros pasados en la lnea de comandos. En el primer parmetro de esta estructura es el nombre del programa con el path completo. Veamos un ejemplo:

Ejemplo 12.3: Lnea de comandos

#include <stdio.h> int main (int argc, char *argv[]) {

95

75.42 - Taller de Programacin

if (argc < 2) { printf ("No se especificaron parmetros.\n"); printf ("El programa debe llamarse como %s nombre_del_usuario\n", argv[0]); return 1; } printf ("Hola %s\n", argv[1]); return 0;

Si ejecutsemos este programa sin parmetros, la salida sera la siguiente: No se especificaron parmetros. El programa debe llamarse como C:\devel\ejemplos\params.exe nombre_del_usuario y devolvera el valor 1 al sistema operativo.

Si el programa se ejecutase con el parmetro Juan, el programa imprimira en pantalla Hola Juan y devolvera el valor 0 al sistema operativo.

Existe un tercer parmetro, pocas veces utilizado, llamado envp. Este parmetro contiene todas y cada una de las lneas de las variables de entorno del sistema. int main (int argc, char *argv[], char *envp[]); Microsoft ha desarrollado una nueva variante de la funcin main(), que es rara vez utilizada, pero permite compatibilidad con varios lenguajes.

int wmain (int argc, wchar_t *argv[], wchar_t *envp[]) wchar_t es un nuevo tipo de dato (wide character) que consiste en entero de 16 bits, utilizado para representar caracteres del cdigo extendido conocido como Unicode.

Punteros a funciones

Suele ser muy til y prctico utilizar punteros a funciones, para modificar o personalizar el funcionamiento de una funcin o una librera. Un puntero a una funcin es idntico a cualquier otro puntero, y se lo declara anteponiendo un asterisco al nombre de la funcin, encerrados ambos entre parntesis. Por ejemplo: int (*comparar) (char * a, char *b); Ser un puntero llamado comparar, cuyo contenido ser la direccin de memoria de una funcin que recibir dos punteros a carcter y devolver un entero.

Esta funcin puede ser llamada como cualquier otra funcin, pero desde ya que deber tener un valor vlido.

96

75.42 - Taller de Programacin Veamos un ejemplo: Supongamos que se tiene un vector de string, que se quiere ordenar.

Ejemplo 12.4: Punteros a funciones

#include <stdio.h> #include <stdlib.h> #include <string.h> #define TOTAL #define MAX_NAME 12 20

void Ordenar (char *Nombres[MAX_NAME], int Total, int (*CmpFunc) (char *, char *)) { } void ListarNombres (char *Nombres[MAX_NAME], int Total) { int i; for (i=0;i<Total;++i) fprintf ("%s\n", Nombres[i]); } void main(void) { char Nombres[TOTAL][MAX_NAME] = { "Juan", "pedro", "Mara", "Gabriel", "MARTA", "Pablo", "Carolina", "Esteban", "Laura", "Patricia", "josefina", "EZEQUIEL" }; printf ("Listado ordenado alfabticamente\n"); Ordenar (Nombres, TOTAL, strcmp); ListarNombres (Nombres, TOTAL); printf ("Listado ordenado alfabticamente, ignorando maysculas y minsculas\n"); Ordenar (Nombres, TOTAL, StrCaseCmp); ListarNombres (Nombres, TOTAL); printf ("Listado ordenado alfabticamente, mujeres primero, hombres despus\n"); Ordenar (Nombres, TOTAL, LadiesFirstCmp); ListarNombres (Nombres, TOTAL); printf ("Listado ordenado alfabtico en orden inverso\n"); Ordenar (Nombres, TOTAL, ); ListarNombres (Nombres, TOTAL);

El preprocesador

Una de las caractersticas interesantes del lenguaje C consiste en la potencia del preprocesador. Ya se mencion este tema en el captulo 2, pero ahora lo veremos con ms detalle. Todas las directivas o directrices del preprocesador comienzan con el prefijo #, y no llevan punto y coma final.

97

75.42 - Taller de Programacin

La directiva #include permite incluir un archivo dentro de otro. Si bien puede incluirse cualquier archivo dentro de otro, slo deben incluirse archivos header (.h, .hpp, .rh).

La directiva #define permite definir constantes y macros. Ya se vi la macro #define para definir constantes, pero tambin puede ser utilizada para definir macros, esto es, una constante que puede tomar parmetros. El formato es el siguiente: #define NOMBRE_MACRO(parmetros) Puede especificarse cualquier nmero de parmetros, pero siempre debe tratarse de un nmero fijo de parmetros. No se puede especificar una cantidad de parmetros variable, como en la funcin printf(). El nombre de la macro y el primer parntesis deben ser escritos sin ningn espacio en el medio, para ser interpretada como una constante.

Las directivas #ifdef, #ifndef permiten verificar la definicin de una constante.

La directiva #if (condicin) permite verificar condiciones, en forma similar a if.

Tanto la directiva #ifdef como #ifndef e #if (condicin) deben terminar en #else o #endif.

Existen tambin una serie de constantes predefinidas que resultan sumamente tiles: __FILE__ __LINE__ __TIME__ Nombre del archivo actual (cdigo fuente) Lnea del programa Un string con la fecha de compilacin del programa

Veamos un ejemplo:

Ejemplo 12.5: El preprocesador - definicin de macros

#define max(a,b) ((a)>(b) ? (a) : (b)) void main (void) { int a, b=50; a = max (a,10); } Esta macro permite obtener fcilmente el mximo entre dos nmeros. Si se utilizase una funcin en lugar de una macro, habra que escribir una funcin para todos los tipos de datos existentes (enteros, nmeros de punto flotante, etc). Debe tenerse en cuenta que las macros pueden llevar a confusiones si no se las utiliza correctamente. Por ejemplo:

int a, b = 50; a = max (b++, 10); No incrementar la variable b una vez sino dos veces, ya que en realidad el cdigo es:

98

75.42 - Taller de Programacin

a = ((b++) > (10) ? (b++) : (10)); Notar tambin la importancia de utilizar todos los parntesis. Si no se lo hiciese se podran producir errores. Por ejemplo, si se definiese: #define max(a,b) a > b ? a : b funcionar bien para: a = max (b, 10); pero no para: a = max (b, 10) / 2; Otro ejemplo de macros muy utilizadas son macros como la siguiente:

Ejemplo 12.6: Macro TRACE

#include <stdio.h> #define TRACE(Txt) {\ FILE *fp=fopen("salida.txt", "at");\ if (fp) { fprintf ("Archivo:%s Lnea:%5u Texto:%s\n", __FILE__,__LINE__,Txt); fclose(fp); }} void main (void) { TRACE ("Comenzando el programa"); ... TRACE ("Fin del programa"); } Esta macro grabar en un archivo (salida.txt) informacin especificada en el programa, junto con el nombre del archivo y la lnea del mismo. Estos cdigos son muy tiles en el momento de verificar el funcionamiento de un programa (debug). El carcter '\' sirve para continuar una lnea en el siguiente rengln.

El archivo salida.txt contendr, luego de la ejecucin del programa: Archivo:c:\devel\test1.c Lnea: Archivo:c:\devel\test1.c Lnea: 9 Texto:Comenzando el programa 11 Texto:Fin el programa

Macros de este tipo suelen ser ms complejas, guardando tambin la fecha y hora en que se produjo el evento, junto con otros datos que pueden ser tiles.

Las definiciones tambin son utilizadas para modificar un programa de tal forma que sea vlido para todas las plataformas. Una serie de macros utilizadas para lograr esto son las siguientes: /* Defino el entero de 16 y 32 bits, para todas las plataformas */ #if defined (_MSDOS) || defined (_WIN16) typedef int int16; typedef long int32;

99

75.42 - Taller de Programacin

#else typedef short int16; typedef int int32; #endif Existen algunas directrices para activar y desactivar las advertencias (warnings) del compilador, o para indicar que se compile el programa junto con una determinada librera, pero estas directrices no forman parte del standard y varan de un compilador a otro.

100

75.42 - Taller de Programacin

Captulo XIII - Estructuras de datos


Listas enlazadas

Otro uso muy comn de los punteros es la creacin de estructuras de datos complejas, tales como listas, rboles, grafos, etc. typedef struct StrLista { char Nombre[100]; struct StrLista *Siguiente; }TLista; Veamos un ejemplo; creemos una lista para almacenar cadenas de caracteres. Antes de comenzar, debe recordarse que pueden crearse muchsimos tipos de estructuras lista, todas ellas completamente diferentes unas a otras y adaptadas al uso que se le dar. La estructura ms genrica es la que se presenta a continuacin, donde la lista se arma como nodos, con dos punteros, uno de ellos el puntero al dato, y el segundo el puntero al siguiente elemento de la lista. Este tipo de estructura, si bien resulta ser muy verstil, tiene la desventaja de ser sumamente ineficiente en el uso y administracin de la memoria, velocidad de ejecucin y, dependiendo del uso que se le de, tambin en bsqueda/direccionamiento de datos.

Ejemplo 13.1: Listas enlazadas

#include #include #include #include

<stdio.h> <malloc.h> <stdlib.h> <string.h>

#define AL_PRINCIPIO 0 #define AL_FINAL 1 typedef struct str_nodo { char *pDato; str_nodo *pSiguiente; }TNodo, *TLista; void AgregarDato (TLista *Lista, char *Dato, int PosFlag) { TNodo *pNuevoNodo; /* Puntero al nuevo nodo */ /* reservo memoria para el nuevo nodo */

101

75.42 - Taller de Programacin

pNuevoNodo = (TNodo *)malloc (sizeof (TNodo)); if (!pNuevoNodo) { printf ("Memoria insuficiente\n"); exit(1); } /* reservo memoria para el dato */ pNuevoNodo->pDato = (char *)malloc (strlen (Dato)); if (!pNuevoNodo->pDato) { printf ("Memoria insuficiente\n"); exit(1); } /* Copiar el dato al nodo */ strcpy (pNuevoNodo->pDato, Dato); /* Insertar el dato en la lista */ if (PosFlag == AL_PRINCIPIO) { pNuevoNodo->pSiguiente = *Lista; *Lista = pNuevoNodo; } else { TNodo **ppLast; ppLast = Lista; /* El nuevo nodo no tiene sucesor */ pNuevoNodo->pSiguiente = NULL; /* Debo recorrer la lista hasta el final */ while (*ppLast!=NULL) ppLast = &((*ppLast)->pSiguiente); } *ppLast = pNuevoNodo;

void MostrarLista (TLista pNodo) { int i; i = 1; while (pNodo != NULL) { printf ("Dato %d = %s\n", i, pNodo->pDato); pNodo = pNodo->pSiguiente; ++i; }

void main (void) { TLista Lista; /* La lista esta inicialmente vaca */ Lista = NULL;

102

75.42 - Taller de Programacin

AgregarDato AgregarDato AgregarDato AgregarDato }

(&Lista, (&Lista, (&Lista, (&Lista,

"Maria", AL_PRINCIPIO); "Pedro", AL_FINAL); "Raul", AL_FINAL); "Juana", AL_PRINCIPIO);

MostrarLista (Lista);

Veamos el programa

El mismo comienza especificando los archivos a incluir; stdio.h (que define la entrada/salida standard), malloc.h (donde se definen las funciones malloc() y free()), stdlib (donde se definen funciones standard, tales como la funcion exit()), y string.h (donde se definen las funciones para manejo de strings o cadenas de caracteres).

Se definen luego dos constantes, que se usarn en el momento de insertar los datos, y luego la estructura que contendr los nodos de la lista. Uno de ellos ser el puntero al dato, y el otro el puntero al siguiente nodo de la lista.

typedef struct str_nodo { char *pDato; str_nodo *pSiguiente; }TNodo, *TLista; Estas lneas podran descomponerse para su mejor comprensin como sigue:

struct str_nodo { char *pDato; str_nodo *pSiguiente; }; typedef struct str_nodo TNodo; typedef struct str_nodo *TLista; Notar que se definen tres nombres relacionados con esta estructura:

struct str_nodo, TNodo y TLista.

Los dos primeros son directamente el nombre de esta estructura. El nombre str_nodo debe definirselo para poder utilizarlo dentro de la estructura. Luego, para no tener que utilizar siempre struct str_nodo, defino el tipo de dato TNodo, que ser equivalente a este. Finalmente defino un puntero a esta estructura con el nombre Lista. As, Lista ser equivalente a *TNodo, y una Lista ser un puntero al primer elemento de la lista. En muchos casos suele utilizar una estructura con una cabecera de la lista.

La primer funcin que se define es AgregarDato(). Esta funcin recibir un puntero a la lista, es decir, un puntero al puntero al primer nodo. Porqu no recibir simplemente una copia del puntero a la lista? Esto estara bien, si se quisiese insertar el dato al final, pero si debo insertar un nuevo nodo al comienzo, deber

103

75.42 - Taller de Programacin

modificar el valor del puntero al primer nodo, razn por la cual, deber conocer el valor de la posicin de memoria donde el mismo est almacenado.

En las siguientes lneas reservo memoria para el nuevo nodo, y verifico que la operacin haya sido exitosa:

pNuevoNodo = (TNodo *)malloc (sizeof (TNodo)); if (!pNuevoNodo) { printf ("Memoria insuficiente\n"); exit(1); } En forma similar reservo memoria para el dato, y copio luego el dato en la memoria que he reservado a tal fin.

El siguiente paso consiste en insertar el dato en la lista. Para esto, esta funcin presenta dos posibilidades. La primera, insertar el dato al comienzo. Para esto, ejecutan las siguientes lneas.

pNuevoNodo->pSiguiente = *Lista; *Lista = pNuevoNodo; La primer lnea modifica el puntero del nuevo nodo para que apunte a la lista, y el segundo el valor del puntero al primer nodo para que apunte al nuevo nodo. Debe estar claro que estas dos lneas no pueden ejecutarse en el orden inverso.

La segunda alternativa consiste en insertar el dato el final. Para ello debe recorrerse la lista hasta llegar el final de la misma. Notar que defino la variable ppNodo como un puntero al puntero al ltimo nodo de la lista, para poder as modificar su valor. As voy recorriendo la lista hasta llegar al final de la misma, y finalmente inserto el dato.

La funcin MostrarLista(), debe resultar ahora bastante ms fcil de entender, y se deja para el lector. Notar que la misma recibe una copia del puntero al primer elemento de la lista, y no un puntero a este puntero, como en el caso anterior, debido a que este puntero no ser modificado en la funcin.

104

75.42 - Taller de Programacin Sera un muy buen ejercicio para el lector, escribir en este momento una funcin para borrar datos de la lista.

Otro ejercicio muy til es la llamada lista doblemente encadenada. Esta es similar a la lista que acabamos de escribir, con excepcin de que cada nodo tiene, adems de un puntero al siguiente nodo de la lista, un puntero al nodo anterior, con lo cual pueden efectuarse recorridos en ambos sentidos.

Notar tambin que cada nodo tiene un tamao fijo, pues almacena simplemente dos punteros, (un puntero al dato y otro al nodo siguiente). Despreciando temas de alineamiento (ver ms adelante), cada puntero, suponiendo un ambiente de 32 bits, ocupar 4 bytes, por lo que cada nodo ocupar 8 bytes. Efectuar reservas de memoria (malloc) para tan solo 8 bytes es terriblemente ineficiente, puesto que cada nodo ocupar realmente ms de 8 bytes (dependiendo del sistema operativo) y provocar una importante fragmentacin de la memoria (quedarn bloques inutilizados). Pueden plantearse otras estructuras bastante ms tiles, que contengan al nodo y al dato en un mismo bloque de memoria. Se ver un ejemplo de esto hacia el final del curso C.

Otro punto importante es el siguiente. Supongamos que definimos una estructura como sigue: struct StrDato char c; int n;

{ }

Supongamos que trabajamos en un sistema operativo de 32 bits, donde un entero ocupa 4 bytes. Qu tamao ocupar esta estructura en memoria? Pus bien, sabemos que un carcter (char) ocupa 1 byte, y un entero 4 bytes, sera de esperar que esta estructura ocupe 5 bytes en memoria. Pero no es as.

El tamao de esta estructura en memoria depender del sistema operativo y del compilador u opciones del compilador que se est usando. Lo ms comn son 8 bytes. Lo que ocurre es que para muchos procesadores (casi todos los usados en este momento) resulta muy costoso acceder a un dato que se encuentra desalineado. Simplificando el tema, un entero est alineado cuando su posicin en memoria es mltiplo del tamao del mismo. Por ejemplo un entero de 32 bits (4 bytes) estar alineado si se encuentra en una posicin de memoria mltiplo de 4.

En la PC, acceder a un entero desalineado requiere el doble de tiempo. En mquinas RISC esta operacin directamente no se puede efectuar, y si se lo intenta el programa se interrumpir.

Esta funcin devuelve el string que se copi, razn por la cual podran efectuarse operaciones tales como:

char s[20]; printf ("Copio el string %s\n", strcpy (s, "Hola mundo")); lo cual imprimira: Copio el string Hola mundo

105

75.42 - Taller de Programacin

PARTE II - El lenguaje de programacin C++


Captulo XIV - Introduccin a C++
Introduccin

Con el objetivo de ampliar el lenguaje, Bell Laboratories aadi algunas extensiones al lenguaje C, y lo llam lenguaje "C con clases", nombre que en 1983 se cambi a C++, si bien en los aos siguientes y hasta 1989 se efectuaron cambios en el lenguaje, pero manteniendo la compatibilidad con el lenguaje C. Finalmente, durante la dcada del 90 se realizaron nuevas modificaciones, fundamentalmente se agregaron algunas caractersticas ms, llegndo as al standard ANSI C++ en 1998, que est basado fundamentalmente en el libro The Annotated C++ Reference Manual, de Margaret Ellis y Bjarne Stroustrup, y sobre el que se saban la mayora de los compiladores anteriores a esta fecha.

La mayora de las extensiones que C++ agrega al C estn relacionadas con la programacin orientada a objetos (OOP: Object Oriented Programming). El objetivo de la programacin orientada a objetos es permitir al programador trabajar con programas de gran envergadura, encapsulando el cdigo y la informacin en un diversas estructuras (o clases) organizadas segn una estructura jerrquica y abstrayndose de la implementacin concreta de cada una de las partes del programa. Estas son algunas de las ideas bsicas que se buscaron al desarrollar el lenguaje C++.

C vs C++

La razn por la cual se desarroll el lenguaje C++, es la presencia de algunos inconvenientes al desarrollar programas de gran envergadura, en cuanto a lo que se refiere al programador. Ellos son:

Gran dificultad para mantener o modificar un programa.

Gran cantidad de variables. Si se usan variables globales, estas crecen indefinidamente. Si se usan variables locales, se complica el llamado a las funciones por la gran cantidad de parmetros que hay que pasar.

Dificultad para crear nuevos tipos.

El programador debe recordar los nombres de demasiadas funciones.

En C++ se busca resolver estos problemas, manteniendo las ventajas y la potencia del lenguaje C, pero permitiendo la creacin de estructuras de datos que permitan trabajar como en un lenguaje de ms alto nivel. Todos estos puntos estn muy relacionados con la aparicin de una nueva filosofa de programacin, conocida como Programacin Orientada a Objetos, que no constituye una ruptura con el modelo de

106

75.42 - Taller de Programacin Programacin Estructurada (como lo fue este ltimo con la programacin imperativa) sino una extensin del mismo. Entre los puntos ms interesantes de este nuevo modelo se encuentran:

Las estructuras de datos pueden contener funciones.

Se crean niveles de seguridad y accesibilidad a los datos.

Se permite la redefinicin de operadores (+, -, *, etc)

Se crea el concepto de polimorfismo, esto es, que puedan existir funciones con un mismo nombre.

Se crea la herencia, para que puedan crearse varios tipos de datos basados en una estructura en comn.

Ya que C++ constituye una extensin del lenguaje C, todo programa en C puede compilarse como un programa de C++, si bien hay algunos conversiones entre punteros que en C se reportaban como warnings, y ahora se consideran errores. Ver: la seccin Algunas variante de C++ para ms detalle.

Tambin es posible mezclar cdigo C con cdigo C++. Sin embargo no slo se efectuaron variaciones en el lenguaje sino tambin en que se implementa el llamado a una funcin o la definicin de una variable a nivel compilador. Este hecho obliga a realizar un par de especificaciones adicionales en los cdigos C/C++.

107

75.42 - Taller de Programacin

Captulo XV - Comenzando a programar en C++


Algunas variantes de C++

Como regla general todo programa en C se graba en un archivo con extensin .c, y todo programa en C++ en uno con extensin .cpp. Esta es la forma en que un compilador distingue en cdigo C de uno en C++ en el momento de compilarlo, salvo que se indique lo contrario.

Para empezar, escribamos la clsica funcin en C para intercambiar el contenido de dos variables de tipo entero, y vayamos refrescando algunos conceptos:

Ejemplo 15.1: Funcin swap (versin incorrecta)

#include <stdio.h> /* Esta funcion no va a funcionar */ void swapint (int i1, int i2) { int aux; aux = i1; i1 = i2; i2 = aux;

void main(void) { int a,b; a = 10; b = 20; printf ("A=%d, B=%d\n", a, b); swapint (a, b); } printf ("A=%d, B=%d\n", a, b);

El se programa producir la siguiente salida:

A=10, B=20 A=10, B=20 Como puede verse, el contenido de las variables a y b no ha sido intercambiado. Qu ha sucedido? Recordando un poco de C, ser claro que, tal como est definida la funcin swapint(), la misma recibir una copia de los parmetros, y no los punteros. Estos sern intercambiados dentro de la funcin, pero no en la funcin que la llam. Para solucionar este inconveniente, la nica solucin es pasar los punteros a las variables:

108

75.42 - Taller de Programacin

Ejemplo 15.2: Funcin swap versin C

#include <stdio.h> /* Funcion para intercambiar variables */ void swapint (int *i1, int *i2) { int aux; /* Valor auxiliar */ aux = *i1; *i1 = *i2; *i2 = aux;

void main(void) { int a,b; a = 10; b = 20; printf ("A=%d, B=%d\n", a, b); swapint (&a, &b); /* Intercambiar el contenido de las variables */ } printf ("A=%d, B=%d\n", a, b);

Notar que para solucionar esto, debi modificarse tanto la llamada a la funcin como todo el uso de las variables en la funcin. Cada vez que se quiera trabajar con el contenido de la variable en la funcin, deber anteponerse en * (asterisco). Esto resulta bastante molesto, y puede llevar a confusin. Para solucionar esto, en C++ agrega una nueva forma de pasar los parmetros a las funciones (manteniendo las ya existentes, por supuesto, recordemos que todo programa C sigue siendo vlido en C++). Es el mtodo conocido como pasaje por referencia.

Escribamos ahora el mismo programa en C++:

Ejemplo 15.3: Funcin swap y el pasaje de parmetros por referencia

#include <stdio.h> void swapint (int &i1, int &i2) { int aux; aux = i1; i1 = i2; i2 = aux;

void main(void) { int a,b; a = 10; b = 20;

109

75.42 - Taller de Programacin

printf ("A=%d, B=%d\n", a, b); swapint (a, b); // Pasaje de parmetros por referencia, igual al pasaje por valor. } printf ("A=%d, B=%d\n", a, b);

Ntese la forma en que se defini la funcin swapint(), utilizando el smbolo & en vez de *. Esto sera similar a un pasaje por referencia en el lenguaje PASCAL. Tanto en el llamado a la funcin como dentro de la misma no es necesario hacer ninguna referencia a puntero. Pero C++ va ms all. Tambin es posible devolver una variable por referencia (ver prximo ejemplo). Esto hace que esta nueva variante del lenguaje no slo facilite la programacin, sino tambin permita resolver problemas de formas que antes no era posible.

Notar la presencia de comentarios con //. Este es un nuevo tipo de comentarios en C++. El comentario comienza con //, y termina con el fin de lnea.

Veamos ahora un ejemplo de devolucin de variables por referencia:

Ejemplo 15.4: Devolucin de parmetros por referencia

#include <stdio.h> int& funcion (int &x) { x++; return x; } void main(void) { int a,b,c; a = 10; b = funcion (a); funcion (c) = 30; } printf ("a=%d b=%d c=%d\n", a,b,c);

Este programa producir la siguiente salida a=10 b=11 c=30 Prestemos atencin a las dos llamadas a funcin. En el primer caso, la llamada a funcion(a) no crea una copia de a, sino que pasa la variable a por referencia. Luego la incrementa, y la devuelve. Pero nuevamente no devuelve una copia de la variable a, sino la variable misma. Es decir, la llamada a la funcin es equivalente a escribir: a++; b = a;

110

75.42 - Taller de Programacin Ahora bien, si la funcin funcion no devuelve una copia numrica de la variable sino la variable misma, es posible asignarle un valor. Este es el caso de la siguiente llamada a funcion(c). Esta funcin incrementa el valor de c, cualquiera que este sea, y luego carga 30 en esta variable. Este cdigo puede traducirse como: c++; c = 30; Ms adelante se ver que esto es escencial en el desarrollo de funciones operadores (operator).

Otra diferencia entre C y C++ es que asignaciones entre punteros de distinto tipo, tales como: void *pa; char *ps; ps = pa; que en C emitira tan slo un mensaje de advertencia (warning), en C++ ser considerado un error y el programa no ser compilado.

La forma correcta de hacer esto es:

void *pa; char *ps; ps = (char *)pa; Otra variante de C++ es que no es obligatorio escribir la palabra reservada void en el encabezado de una funcin que no recibe ningn parmetro. Por ejemplo, la funcin int Random (void); podra definirsela como: int Random (); Otra variante de C++, es que se permite la definicin de variables luego ejecutarse instrucciones del programa. Por ejemplo, el siguiente cdigo no sera vlido, ya que b y j no se declaran al comienzo de la funcin. Sin embargo, con el objetivo de flexibilizar el cdigo, esto s es un programa vlido en C++.

Ejemplo 15.5: Algunas variantes en C++

#include <stdio> void main (void) { int a; scanf ("%d", &a); int b = a; for (int j = 0; j < a; ++j) printf ("%d", j * a);

111

75.42 - Taller de Programacin

Ya que pueden definirse variables en cualquier punto del programa, tambin podra escribirse una lnea como la siguiente:

... unsigned largo = strlen (s); ... Este tipo de definiciones, donde la definicin de una variable est junto con la llamada a una funcin que le da su valor inicial recibe a veces el nombre de inicializacin dinmica.

Notar tambin la sentencia #include<stdio.h>. En la misma no se incluye el .h. En C++, el ".h" es opcional, si bien rara vez se lo omite, principalmente porque si bien forma parte del standard, muchos compiladores an no lo soportan.

Nuevos tipos de datos

Si bien no forma parte del standard ANSI C++, la mayora de los compiladores C++ incluye una nueva forma de declarar los enteros, especificando la cantidad de bits que estos utilizan. Se definen as los tipos de datos:

__int8 __int16 __int32 __int64

Entero Entero Entero Entero

de de de de

8 bits 16 bits 32 bits 64 bits

Puede anterponerse la palabra reservada unsigned para crear tipos enteros sin signo.

En 1998, se standariz el lenguaje de programacin C++, y se defini el tipo de dato booleano (bool), y las definiciones true y false. El nuevo tipo de dato slo puede tomar los valores true (1) o false (0). Cdigos como: bool bValor; bValor = 1; pueden emitir un mensaje de advertencia (warning) al ser compilados. El cdigo correcto es: bValor = true; Algunos compiladores anteriores a esta fecha, definen el tipo de dato BOOL utilizando typedef, como sigue:

typedef unsigned BOOL; #define TRUE 1 #define FALSE 0

112

75.42 - Taller de Programacin

Captulo XVI - Programacin orientada a objetos


PROGRAMACIN ORIENTADA A OBJETOS

La programacin orientada a objetos (conocida como POO u OOP) es una nueva concepcin en cuanto a la forma de tratar un problema. Fue desarrollada con el objetivo de adecuarse al creciente tamao y complejidad de los programas. La misma no se contrapone a la programacin estructurada (como lo haca esta con la programacin imperativa), sino que constituye una extensin de la misma. De esta forma, todos los programas y libreras escritas en C continan siendo vlidas en C++. Sin embargo la programacin orientada a objetos constituye una forma de pensar y concebir la programacin completamente nueva, como un conjunto de entidades abstractas independientes, razn por la cual no se podr aprovechar toda la potencia de la POO hasta no se haya asimilado esta nueva filosofa de programacin.

Las caractersticas fundamentales de la programacin orientada a objetos, comn a todos los lenguajes de programacin son:

Objetos

Un objeto es una entidad lgica que contiene datos y cdigo que permite manipularlo. Dicho de otra forma, es una estructura que contiene datos (variables), y funciones para acceder a los mismos, es decir que los datos no son accesibles directamente por ninguna otra parte del programa, sino mediante las funciones escritas a tal fin. Esto se conoce con el nombre de encapsulacin. La ventaja de esto es que permite al programador abstraerse de la forma en que los datos son almacenados o procesados, e incluso permite que las caractersticas de los mismos, la forma en que se los almacena, etc, puedan sean modificados sin tener que efectuar cambios en el resto del programa.

Como una forma de comenzar a cambiar la forma de pensar un programa, las variables y funciones de los objetos pasarn a llamarse atributos y mtodos de los mismos, respectivamente.

Polimorfismo

Bsicamente polimorfismo significa que un nombre (nombre de funcin) puede ser utilizado para diversos fines, dependiendo del contexto donde se lo utilice. Esto permite al programador no tener que memorizar una gran cantidad de nombres funciones. Un ejemplo de esto son las funciones atoi(), atol() y atof(), que convierten un texto ascii a entero corto, entero largo y punto flotante respectivamente.

Herencia

La herencia es un proceso mediante el cual un objeto puede adquirir las caractersticas de otro y otros, permitiendo as el almacenamiento de conocimiento o informacin con una estructura jerrquica. Por ejemplo, un len es un concepto abstracto o clase que forma parte de la clase ms grande felino, que a su vez forma parte de la clase mamifero y esta de la clase animal. Si tuvisemos que representar las caractersticas o definir el comportamiento de animales, sera muy til hacerlo con un esquema similar a este. Como veremos a lo largo de este libro, muchos problemas pueden plantearse de esta forma, lo cual facilita enormemente la programacin, y permite generar cdigos sumamente claros y ordenados.

113

75.42 - Taller de Programacin

LAS CLASES

Las clases (class) son la estructura fundamental de los objetos. Son similares a una estructura (struct) en C, slo que tambin pueden contener funciones. De hecho, en C++ las estructuras (struct) tambin pueden contener funciones, por lo que ambas son similares, con la salvedad de que por defecto, en las structs las variables y funciones son pblicas, en tanto que en las classes son privadas, y que no soportan algunas caractersticas de C++, como por ejemplo la herencia. Ya veremos qu quiere decir esto ms adelante.

La clase ms simple se define como sigue:

class nombre_de_la_clase { funciones y variables de la clase. }; Notar el punto y coma (;) al final de la declaracin de la clase. La omisin del mismo suele ser un error muy frecuente.

Las variables y funciones dentro de la clase se crean tal como se lo hara en un programa en C. Pero estas variables y funciones son privadas a la clase, esto es, slo pueden ser accedidas por funciones de la clase.

Como una forma de cambiar la filosofa de programacin, las variables dentro de una clase reciben en C++ el nombre de atributos de la clase, en tanto que las funciones pasarn a llamarse ahora mtodos de la clase.

Para que un atributo o mtodo de la clase pueda ser accedida desde afuera de la misma, se crearon tres niveles de seguridad o de proteccin de la informacin: private (privadas), protected (protegidas) y public (publicas).

Los atributos y mtodos de tipo private slo pueden ser accedidas por funciones de la clase.

Los atributos y mtodos de tipo protected slo pueden ser accedidas por la clase y por las clases que la hereden. Ya se ver el concepto de herencia ms adelante.

Los atributos y mtodos de tipo public pueden ser accedidas libremente. Las variables se definen en una clase.

Los atributos se crean como private, public o protected, definiendo poniendo este ttulo seguido de dos puntos (:) antes de la definicin de la variable.

La idea subyancente de la programacin orientada a objetos es que en ningn momento se acceda a los atributos de una clase, sino mediante mtodos (funciones) de la misma. Esto hace posible que si en un futuro se quisiese cambiar el tipo de variable o la forma en que estas se manipulan, slo haya que modificar el objeto, y que el resto del programa siga funcionando con el nuevo objeto, sin haber tenido que realizar

114

75.42 - Taller de Programacin ninguna modificacin. Incluso, debera ser posible recompilar el programa en un nuevo sistema operativo, totalmente diferente, y que el mismo funcione correctamente, simplemente cambiando las funciones de los objetos correspondientes. A continuacin veremos un primer ejemplo de programacin en C++.

Creacin de la estructura Pila.

Notar el nombre que recibe la clase, y que es el que habitualmente utiliza Borland: el nombre de toda clase coomienza con la letra T de Type (tipo), seguida por el nombre del tipo, con la primer letra en mayscula y el resto en minsculas. Microsoft utiliza un esquema similar, pero en vez de utilizar la letra T utiliza la letra C de Class.

Ejemplo 16.1: Objeto Pila (primer versin) #include <stdio.h> #include <malloc.h> #define PILA_MAX 100 class TPila { private: unsigned * Pila; unsigned Total; public: int Inicializar (void) { Total = 0; Pila = (unsigned *)malloc (PILA_MAX * sizeof (unsigned)); if (!Pila) { printf ("Memoria insuficiente"); return -1; } printf ("La pila fue inicializada.\n"); return 0; } void Destruir (void) { if (Pila) free (Pila); printf ("La pila fue destruida.\n"); } void Encolar (unsigned Word) { if (Total<PILA_MAX) { printf ("Encolando %u\n", Word); Pila[Total++]=Word; } else printf("Pila esta llena.\n"); } unsigned Desencolar (void) { if (Total>0) { printf ("Desencolando %u\n", Pila[--Total]); return Pila[Total];

115

75.42 - Taller de Programacin

};

} else { printf("Pila esta llena.\n"); return 0; }

void main (void) { /* Crear la pila */ TPila Pila; /* Inicializar la pila */ Pila.Inicializar (); /* Encolar un par de datos */ Pila.Encolar (10); Pila.Encolar (5); /* Desencolar los datos */ Pila.Desencolar (); Pila.Desencolar (); /* Destruir la pila */ Pila.Destruir();

El programa mostrar la siguiente salida:

La pila fue inicializada. Encolando 10 Encolando 5 Desencolando 5 Desencolando 10 La pila fue destruida. Este ejemplo crea una estructura pila, que permite almacenar datos de tipo entero. Notar como se definen las variables de la clase como privadas (private:), y las funciones que vn a ser utilizadas por el programa como pblicas (public:). En realidad, salvo que se indique lo contrario, las variables y funciones en una clase son privadas, por lo que el private: est de ms, pero de todas formas suele ponrselo por claridad.

En ejemplo puede verse como se crean los atributos (variables) como privadas, y se definen mtodos (funciones) para acceder a los datos.

En cuanto al programa, hay dos preguntas iniciales para hacer.

1 - No se podran definir los mtodos de la clase fuera de la definicin de la misma?

2 - Siempre que se cree la pila ser necesario inicializarla. No se puede hacer esto automticamente?

116

75.42 - Taller de Programacin Respuestas

1 - S. Declarar una funcin dentro de una clase puede ser muy incmodo y molesto, sobre todo cuando las mismas son demasiado largas. Los mtodos pueden definirse fuera de la clase, pero anteponiendo al nombre de la funcin el nombre de la clase, seguido de :: (doble dos puntos).

Hay una diferencia ms entre estas dos variantes y es que, por defecto, las funciones definidas dentro de la clase se expanden dentro del programa al ser compiladas, no as las funciones definidas fuera de la clase.

2 - S. Para ello debe definirse un mtodo que tenga el mismo nombre que la clase. Esta funcin se llama constructor, y no puede devolver datos.

Veamos nuestra versin mejorada de la pila:

Ejemplo 16.2: Objeto Pila - Constructores y Destructores

#include <stdio.h> #include <malloc.h> #define PILA_MAX 100 class TPila { private: unsigned * Pila; unsigned Total; public: TPila (); ~TPila (); void Encolar (unsigned Word); unsigned Desencolar (void); }; TPila::TPila() { Total = 0; Pila = (unsigned *)malloc (PILA_MAX * sizeof (unsigned)); if (!Pila) printf ("Memoria insuficiente.\n"); else printf ("Pila incializada.\n"); } TPila::~TPila () { if (Pila) free (Pila); printf ("La pila ha sido destruida.\n"); } void TPila::Encolar (unsigned Word) { if (!Pila) {

117

75.42 - Taller de Programacin

} if (Total<PILA_MAX) { printf ("Encolando %u\n", Word); Pila[Total++]=Word; } else printf("Pila esta llena.\n");

printf("No se pudo inicializar la pila.\n"); return;

unsigned TPila::Desencolar (void) { if (!Pila) { printf("No se pudo inicializar la pila.\n"); return 0; } if (Total>0) { printf ("Desencolando %u\n", Pila[--Total]); return Pila[Total]; } else { printf("Pila esta llena.\n"); return 0; }

void main (void) { /* Crear la pila */ TPila Pila; /* Encolar un par de datos */ Pila.Encolar (10); Pila.Encolar (5); /* Desencolar los datos */ Pila.Desencolar (); Pila.Desencolar ();

La salida de este programa es la siguiente:

La pila fue inicializada. Encolando 10 Encolando 5 Desencolando 5 Desencolando 10 La pila fue destruida. Notar la definicin de la funcin ~TPila(). Esta funcin se llama destructor de la clase, y consiste en una funcin con el mismo nombre que la clase precedida por el carcter ~, y no puede tomar ni devolver parmetros.

118

75.42 - Taller de Programacin Nunca debe llamarse a un constructor/destructor de una clase desde el programa. Esto puede hacerse, pero no operar como el programador esperara. Los constructores/destructores estn pensados para operar automticamente, no para ser llamados como un mtodo ms de la clase.

Cundo se llama a la funcin destructora de la clase?

Rta: cuando se la deja de utilizar, es decir, en el momento en que se sale de la funcin donde se la defini, siempre y cuando no se la est devolviendo en el return.

Pero porqu utilizar siempre el mismo tamao de pila? No se podra de alguna manera indicar la cantidad de objetos mximo que tendr la pila al crearla?

Rta: S. El constructor puede tomar parmetros, al igual que cualquier funcin.

Los constructores son mtodos especiales, pensados para ser llamados automticamente por el compilador. Puede llamarse a los mismos desde el programa, o desde mtodos de la clase, pero estos no operarn en la forma esperada. Por esta razn, nunca deben ser llamados desde el programa.

Veamos un ejemplo:

Ejemplo 16.3: Objeto Pila - Constructor parametrizado #include <stdio.h> #include <malloc.h> #define PILA_MAX 100 class TPila { private: unsigned * Pila; unsigned Total; unsigned Max; public: TPila (unsigned Maximo); ~TPila (); void Encolar (unsigned Word); unsigned Desencolar (void); }; TPila::TPila(unsigned Maximo) { Total = 0; Max = Maximo; Pila = (unsigned *)malloc (PILA_MAX * sizeof (unsigned)); if (!Pila) printf ("Memoria insuficiente.\n"); else printf ("Pila incializada.\n"); }

119

75.42 - Taller de Programacin

TPila::~TPila () { if (Pila) free (Pila); printf ("La pila ha sido destruida.\n"); } inline void TPila::Encolar (unsigned Word) { if (!Pila) { printf("No se pudo inicializar la pila.\n"); return; } if (Total<Max) { printf ("Encolando %u\n", Word); Pila[Total++]=Word; } else printf("Pila esta llena.\n"); } unsigned TPila::Desencolar (void) { if (!Pila) { printf("No se pudo inicializar la pila.\n"); return 0; } if (Total>0) { printf ("Desencolando %u\n", Pila[--Total]); return Pila[Total]; } else { printf("Pila esta llena.\n"); return 0; }

void main (void) { /* Crear la pila */ TPila Pila(100); /* Encolar un par de datos */ Pila.Encolar (10); Pila.Encolar (5); /* Desencolar los datos */ Pila.Desencolar (); Pila.Desencolar ();

120

75.42 - Taller de Programacin Veamos ahora otro ejemplo de esta misma pila:

Ejemplo 16.4: Objeto Pila - Parmetros por defecto - new y delete

#include <stdio.h> #include <malloc.h> #define PILA_MAX 100 /* IMPORTANTE: No pasar una variable de tipo TPila como parmetro a una funcin */ class TPila { private: unsigned * Pila; unsigned Total; unsigned Max; public: TPila (unsigned MaxPila = PILA_MAX); ~TPila (); void Encolar (unsigned Word); unsigned Desencolar (void); }; TPila::TPila(unsigned MaxPila) { Total = 0; Max = MaxPila; Pila = new unsigned[Max]; if (!Pila) printf ("Memoria insuficiente.\n"); else printf ("Pila incializada.\n");

TPila::~TPila () { if (Pila) delete []Pila; printf ("La pila ha sido destruida."); } void TPila::Encolar (unsigned Word) { if (!Pila) { printf("No se pudo inicializar la pila.\n"); return; } if (Total<Max) { printf ("Encolando %u\n", Word); Pila[Total++]=Word; } else printf("Pila esta llena.\n"); } unsigned TPila::Desencolar (void)

121

75.42 - Taller de Programacin

if (!Pila) { printf("No se pudo inicializar la pila.\n"); return 0; } if (Total>0) { printf ("Desencolando %u\n", Pila[--Total]); return Pila[Total]; } else { printf("Pila esta llena.\n"); return 0; }

void main (void) { /* Crear la pila */ TPila Pila; /* Encolar un par de datos */ Pila.Encolar (10); Pila.Encolar (5); /* Desencolar los datos */ Pila.Desencolar (); Pila.Desencolar ();

Notar como se reemplazo en el contructor, la lnea Pila = (unsigned *)malloc (sizeof (unsigned) * max ); por Pila = new unsigned[max]; y en el destructor, la lnea free (Pila); por delete []Pila; new y delete son palabras reservadas de C++, y cumple la misma funcin que malloc y free. En ambos casos, las dos lneas son similares, salvo por el hecho de que new y delete llaman automticamente al constructor/destructor de la clase, adems de poder sobrecargarse como cualquier operador. Puede seguir utilizndose malloc y free, sin embargo en C++ la convencin es utilizar nicamente new y delete, por ser ms simples y adaptarse ms a la filosofa de objetos. Nunca debe liberarse con delete un puntero alocado con malloc o liberar con free un puntero reservado con new.

122

75.42 - Taller de Programacin Notar que en la llamada a delete se escribieron dos corchetes []. Esto se debe a que en la memoria que se va a liberar, no se encuentra almacenado un nico dato, sino una determinada cantidad de los mismos, es decir, se trata de un vector. El [] indica que debe ejecutarse ms de un destructor (uno para cada uno de los elementos de vector). Dato = new TDato; es similar a:

Dato = malloc (sizeof (TDato)); Dato.TDato(); y Dato = new TDato[cantidad]; es similar a: Dato = malloc (sizeof (TDato)*cantidad); for (i = 0; i < cantidad; ++i) Dato[i].TDato(); y delete Dato; es similar a Dato.~TDato(); free (Dato); y delete []Dato; es similar a for (i = 0; i < cantidad; ++i) Dato[i].~TDato(); free (Dato); (Recordar que si se trata de un puntero a un vector de objetos, debe anteponerse [] al nombre del puntero, en la llamada a delete. (delete []Dato).

En la definicin de la funcin Encolar() se utiliz la palabra reservada inline: inline void TPila::Encolar (unsigned Word) Esta obliga al compilador a expandir el cdigo de la funcin dentro del cdigo, en vez de realizar la llamada a la funcin. Esto aumentar el tamao del cdigo, por lo cual slo debe ser utilizado para funciones pequeas o cuya eficiencia sea muy importante.

123

75.42 - Taller de Programacin

Notar la advertencia al comienzo de la funcin de que no se puede pasar una variable de tipo TPila como parmetro a una funcin (ni tampoco puede ser devuelto por una funcin). Es decir, cosas como:

/* Este programa est mal */ void Func (TPila PilaCopia) { ... } void main (void) { TPila Pila; Func (Pila) } constituyen un error de programacin. Lo que ocurre es que al llamar a la funcin Func() se crear una copia de la variable Pila (PilaCopia), y al salirse de la funcin Func() la misma ser destruida. Pero el destructor de PilaCopia liberar la memoria de la variable PilaCopia, entre ellos el puntero Pila de esta variable, que es el mismo que el de la variable Pila!!!, con lo cual al regresar a la funcin main, el puntero Pila.Pila ya habra sido liberado. Finalmente cuando al salir de la funcin main() se libere nuevamente la memoria se producir un error fatal.

Importante

Se ver como resolver este problema en la seccin UTILIZACIN DE MEMORIA DINMICA EN LAS CLASES. Como regla general, NUNCA UTILIZAR ALOCACIN/LIBERACIN DE MEMORIA (malloc-free / new-delete) DENTRO DE CONSTRUCTORES/DESTRUCTORES de objetos, hasta no haber ledo la seccin.

124

75.42 - Taller de Programacin

Captulo XVII - Parmetros por defecto y sobrecarga de funciones


PARMETROS POR DEFECTO

Otro punto a notar es la definicin del contructor de la clave: TPila (unsigned MaxPila = PILA_MAX); Qu significa que la variable est igualada a un valor?

Pues simple, que una funcin puede tomar valores por defecto. Esto hace posible no especificar valores al llamar a la funcin, en cuyo caso se utilizarn los valores por defecto, definidos en el encabezado de la funcin. A esto se llama parmetros por defecto y puede utilizarse en cualquier funcin.

La nica condicin que impone C++ es que las variables que tienen valores por defecto deben estar del lado derecho de la funcin, y que al llamar a la funcin, no puede especificarse ningn otro parmetro a partir del primero que no se especifique. Por ejemplo, puede tenerse algo como: funcion (int a, int b, int c=1, int d=2, int e=3); pero no algo como funcion (int a, int b, int c=1, int d=2, int e);

y no hay forma de especificar el parmetro e si no se especifican previamente c y d.

SOBRECARGA DE FUNCIONES

Una de las caractersticas muy tiles de C++ es la sobrecarga de funciones, esto es, definir varias funciones con el mismo nombre. Por ejemplo, en C, se tienen las funciones atoi(), atol(), y alof(), para convertir un texto en formato numrico. Pero ya que existen 3 tipos de datos nmericos, se deben definir tres funciones, segn el tipo de dato al que deba ser traducido el texto. Pero esto obliga al programador a memorizar tres funciones.

Para evitar este tipo de problemas, en C++ se pueden definir varias funciones con el mismo nombre, y el compilador decidir en el momento de compilar a qu funcin debe llamarse, segn la forma en que se la llame. A esto se conoce tambin con el nombre de polimorfismo.

Veamos el siguiente ejemplo de nuestra clase TPila:

Ejemplo 17.1: Objeto pila - sobrecarga de funciones y polimorfismo

#include <stdio.h> #include <malloc.h>

125

75.42 - Taller de Programacin

#include <iostream.h> #define PILA_MAX 100 class TPila { private: unsigned * Pila; unsigned Total; unsigned Max; public: TPila (unsigned MaxPila=PILA_MAX); ~TPila (); void Encolar (unsigned Dato); void Encolar (unsigned Dato1, unsigned Dato2); unsigned Desencolar (unsigned Cantidad=1); }; TPila::TPila(unsigned MaxPila) { Total = 0; Max = MaxPila; Pila = new unsigned[Max]; if (!Pila) printf ("Memoria insuficiente.\n"); else printf ("Pila incializada.\n"); } TPila::~TPila () { if (Pila) delete []Pila; printf ("La pila ha sido destruida."); } void TPila::Encolar (unsigned Dato) { if (!Pila) { printf("No se pudo inicializar la pila.\n"); return; } cout <<"Encolando "<<Dato<<"\n"; if (Total<PILA_MAX) Pila[Total++]=Dato; else printf("Pila esta llena.\n");

void TPila::Encolar (unsigned Dato1, unsigned Dato2) { Encolar (Dato1); Encolar (Dato2); } unsigned TPila::Desencolar (unsigned Cantidad) {

126

75.42 - Taller de Programacin if (!Pila) { printf("No se pudo inicializar la pila.\n"); return 0; } if (Cantidad==0) return 0; if (Total>0) { if (Cantidad>1) Desencolar (Cantidad-1); cout <<"Desencolando "<<Pila[--Total]<<"\n"; return Pila[Total];

} else { printf("Pila esta vacia.\n"); return 0; }

void main (void) { TPila Pila(10); Pila.Encolar (10); Pila.Encolar (20,30); Pila.Desencolar (); Pila.Desencolar (2);

Para empezar, en la tercer lnea del programa se incluye el archivo iostream.h. Este archivo define la entrada/salida standard en C++. Aqu se define, para empezar, la funcion cout. La forma de utilizar esta funcin es simple: cout << parametros separados por <<. Puede seguir utilizndose printf, pero cout est ms cercana a la filosofa de C++. Lo primero que se nota es que no es necesario especificar el tipo de dato que se va a imprimir. Ms adelante se ver como definir funciones de este tipo.

por ejemplo, para imprimir un nmero de punto flotante, que con printf se hara como: printf ("Valor = %f\n", valor); se escribira como: cout << "Valor = " << valor << "\n"; Notar como no es necesario especificar el tipo de dato, pero tampoco es posible especificar la forma en que este dato se debe imprimir, como se podra hacer con printf

Puede verse adems que existen dos funciones Encolar(), ambas con el mismo nombre, y que incluso, una llama a la otra. Esto permite que el programador no tenga que memorizar dos nombres, sin uno solo. A qu funcin se llama en cada caso? Rta: muy simple: los nombres de las funciones son los mismos, pero los parmetros que reciben no, por lo tanto, el tipo y la cantidad de parmetros que recibe la funcin especifica a qu funcin se llama. Dicho de otra forma, una funcin no se identifica por su nombre, como en C, sino tambin por los parmetros que recibe.

127

75.42 - Taller de Programacin

Notese tambin que la funcin desencolar est definida como: unsigned Desencolar (unsigned Cantidad=1); En este caso, el parmetro Cantidad especifica la cantidad de datos a desencolar. Ya que generalmente se desea desencolar un nico dato, puede no especificrselo, en cuyo caso, la variable tomar el valor por defecto, que se especific en la funcin. Recordar que pueden existir varios parmetros con valores por defecto, pero es condicin obligatoria que todos ellos se encuentren del lado derecho de la definicin de la funcin.

La sobrecarga de funciones puede conducir a algunos casos de ambigedad, conde el compilador no sepa a qu funcin debe llamarse, los cuales sern reportados indefectiblemente como errores, an cuando para el programador pueda ser indistinto. Por ejemplo, imaginemos un caso como el siguiente:

class TEjemplo { public: Funcion (int a); Funcion (int a, int b=1); } { ... class TEjemplo Ej; Ej.Funcion (1);

En este caso, no hay forma de saber a cual de las dos funciones Funcion se desea hacer referencia, y por lo tanto la lnea en negrita no puede ser compilada.

Existe tambin una funcin llamada cin, similar a scanf(). Por ejemplo, para leer el valor de un entero, lo que normalmente se hara como: int a; scanf ("%d", &a); ahora puede escribirse como cin >>a;

Captulo XVIII - Herencia


HERENCIA

Algo comn en programacin, es que se trabaje con tipos o estructuras de datos parecidas, o basadas unas en otras. Ver la seccin Programacin Orientada a Objetos para ms informacin. Por ejemplo, una estructura de datos muy simple es la lista. Las estructuras de datos conocidas como pila y cola no son ms que listas en donde se insertan elementos al comienzo y se los saca del comienzo/final o viceversa. Si se quisiese implementar estos tipos de datos, no tiene sentido escribir tres tipos de datos, que sern evidentemente muy similares. Para evitar esto, C++ permite definir clases que hereden a su vez otros tipos

128

75.42 - Taller de Programacin de datos. La clase ms simple recibe el nombre de clase base, y la clase heredera se denomina clase derivada.

Veamos un ejemplo: contruyamos el objeto TLista. Por simplicidad, la lista ser usada nicamente para almacenar enteros, con un par de funciones para insertar y quitar datos en cualquier posicin de la lista y una funcin que devuelva el total de datos en el la misma.

Ejemplo 18.1: Objeto Lista

#include #include #include #include

<stdio.h> <iostream.h> <malloc.h> <mem.h>

#define MAX 100 // Maxima cantidad de elementos que se almacenaran typedef unsigned TDato; // Tipo de dato que se almacenara en la lista/pila/cola /******************** Definicion del objeto TLista **************************/ class TLista { private: TDato Datos[MAX]; TDato Aux; unsigned Total; public: TLista (void) { Total = 0; } int Insertar (unsigned Pos, TDato Dato); TDato Sacar (int Pos); unsigned LeerTotal (void) { return Total; } }; int TLista::Insertar (unsigned Pos, TDato Dato) { // Verificar si queda espacio en la lista if (Total>=MAX) { cout<<"No se pueden almacenar mas datos.\n"; return -1; } // Verificar la posicion donde se quiere insertar el dato if (Pos>Total) { cout<<"Se quieren insertar datos en una posicion muy elevada.\n"; return -1; } // Mover los datos hacia posiciones mas altas en el array memmove (Datos+Pos+1, Datos+Pos, (Total-Pos)*sizeof (TDato)); // Almacenar el dato Datos[Pos] = Dato; // Incrementar la cantidad de datos en el array ++Total;

129

75.42 - Taller de Programacin

// Retornar OK; return 0;

TDato TLista::Sacar (int Pos) { TDato Ret; // Verificar que el dato a retornar exista if (Pos < 0 || Pos>=Total) { cout<<"No existe el dato en la lista.\n"; return Aux; } // Copiar el dato a retornar Ret = Datos[Pos]; // Mover los datos de posiciones mas altas a posiciones mas bajas dentro // del array. memmove (Datos+Pos, Datos+Pos+1, (Total-Pos-1)*sizeof (TDato)); // Decrementar la cantidad de datos en el array --Total; } return Ret;

Definamos ahora los objetos pila y cola:

Ejemplo 18.2: Objetos Pila y Cola - Herencia

/*************************** Definicion del objeto TPila ********************/ class TPila:public TLista { public: int Push (TDato Dato); TDato Pop (void); }; int TPila::Push (TDato Dato) { return Insertar (LeerTotal(), Dato); } TDato TPila::Pop (void) { return Sacar (LeerTotal()-1); } /*************************** Definicion del objeto TCola ********************/ class TCola:public TLista { public: int Encolar (TDato Dato);

130

75.42 - Taller de Programacin TDato LeerCola (void);

};

int TCola::Encolar (TDato Dato) { return Insertar (LeerTotal(), Dato); } TDato TCola::LeerCola (void) { return Sacar (0); } /*************************** Funcion main() *********************************/ void main (void) { TCola Cola; TPila Pila; cout << "Operaciones con la pila\n"; Pila.Push(10); Pila.Push(20); cout << Pila.Pop() << "\n"; cout << Pila.Pop() << "\n"; cout << "Operaciones con la cola\n"; Cola.Encolar(30); Cola.Encolar(40); cout << Cola.LeerCola() << "\n"; cout << Cola.LeerCola() << "\n";

Veamos ahora la definicin de los objetos Pila y Cola.

class TPila:public TLista { ... } La definicin de la clase es similar a las ya vistas, con la salvedad de public TLista. Esto lo que hace es indicar que la clase TPila debe heredar en forma pblica todos los atributos y mtodos de la clase TLista.

Al igual que los atributos y los mtodos, la forma en que los mismos son heredados por las clases herederas puede ser public, protected y private. El siguiente recuadro indica el tipo de atributo/mtodo que se tendr en una clase heredera.

Tipos de herencia Tipo de dato Herencia public Herencia protected Herencia private Private Protected Public no son accesibles no son accesibles protected public protected protected no son accesibles private private

En caso se no especificarse el tipo de herencia, se supone por defecto herencia de tipo private.

131

75.42 - Taller de Programacin

Por ejemplo, si una clase hereda los datos de su padre en forma private, los atributos y mtodos de tipo protected del padre pasarn a ser un atributos y mtodos de tipo private en la clase heredera.

La forma en que se heredan los atributos y los mtodos puede parecer un tanto confuso, pero con el uso se va aclarando.

Las variables que no se heredan siguen existiendo, pero no son accesibles desde la clase heredera. Veamos un ejemplo:

Ejemplo 18.3: Objeto Figura - Herencia

class TFigura { private: unsigned Color; public: SetColor (unsigned Col) { Color = Col; } GetColor (unsigned Col) { return Color; } }; class TCuadrado:public TFigura { private: unsigned Lado; public: SetLado (unsigned L) { Lado = L; } }; Si ahora cresemos una variable de tipo TCuadrado, esta tendra dos atributos: Color y Lado, pero slo Lado ser visible desde la misma y slo podr accederse al atributo Color mediante los mtodos SetColor y GetColor, que sern privados en la clase TCuadrado. Se ver ms a cerca de herencia en la seccin III Programacin avanzada en C++.

Introduccin a la herencia mltiple

Una misma clase puede costruirse como derivada de ms de una clase base. Las clases base de una clase derivada se especifican separadas por comas. Por ejemplo, para definir una clase D como derivada de tres clases base A, B y C debe escribirse: class D : public A, private B, private C { ... }; En este caso la clase D heredar en forma pbica la clase A y en forma privada las clases B y C. No hay lmite en el nmero de clases que pueden heredarse. La herencia mltiple puede llevar a casos ms complejos cuando una misma clase es heredada ms de una vez. Para ms informacin ver la seccin UN POCO MS SOBRE HERENCIA - HERENCIA MLTIPLE

132

75.42 - Taller de Programacin Distribucin en memoria de atributos, en clases herederas

Supongamos que se tenga un caso como el siguiente:

Ejemplo 18.4: Herencia mltiple

class A { int a; }; class B { int b; }; class C: public A, public B { int c; }; Si ahora se crease un objeto de tipo C, en la mayora de los casos, los compiladores almacenan la variable a, luego la b y finalmente la c, pero esto no est especificado en ningn lugar ni forma parte de la definicin del lenguaje ANSI C++, por lo que no tiene por qu ser as. Es decir que el orden de las variables en memoria en un obejeto de tipo C podra ser: a - b - c b - c - a o cualquier otro.

133

75.42 - Taller de Programacin

Captulo XIX - Casting de objetos


Casting de objetos

Volvamos al caso anterior en que se tienen tres tipos de objetos, uno de los cuales se contruye como derivado de los dos anteriores. Supongamos que ahora tengamos tres punteros que apunten a variables de estos tres tipos:

Ejemplo 19.1: Casting de objetos A *pa; B *pb; C *pc; Es posible en C++ obtener tan slo la parte B de un objeto C, simplemente asignando un puntero de tipo C a un puntero de tipo B, es decir, escribiendo: pb = pc; Desde ya que tiene sentido escribir pb = pc, pero no pc = pb;

Volviendo a la lnea pb=pc, pb apuntar ahora a la parte B del objeto apuntado por pc, y por lo tanto el valor de pb no tiene por qu ser el mismo que el de pc (y normalmente no lo ser), luego de efectuar la operacin. Por ejemplo: /* Este cdigo es incorrecto */ pb = pc; if (pb != NULL) Funcion (pb); As como est, la lnea en negrilla es incorrecta o al menos incompleta (si bien completamente compilable). pb tomar en valor de pc, ms el desplazamiento de la parte B dentro de C. Si pc fuese NULL, pb tomar el valor del offset o posicin de la parte B dentro de C, el cual no tiene por qu ser cero. Es comn utilizar la notacin pb = pc + delta (b), para describir esta situacin. La forma correcta de escribir la lnea (y la que generalmente se utiliza) es: pb = (pc == NULL) ? NULL : pc; lo cual es equivalente a: if (pc == NULL) pb = NULL; else pb = pc; Nota

134

75.42 - Taller de Programacin Esta situacin aqu descripta es un tanto confusa. Ciertos compiladores asignan a la variable de la clase base el valor NULL en vez de su offset dentro del objeto, cuando el puntero del lado derecho de la igualdad tiene el valor NULL, es decir, automticamente se ejecuta pb = (pc==NULL) ? NULL : pc; Tipos de casting entre objetos

Es posible para el programador efectuar distintos tipos de casting entre clases base y derivadas de un mismo objeto. Existen para esto las palabras reservadas: dynamic_cast, static_cast, que permiten especificar la operacin a realizar.

dynamic_cast realizar una verificacin en tiempo de ejecucin de que la operacin realizada es vlida, provocando una excepcin de no serlo, en tanto que static_cast realizar ca conversin sin verificar si la misma es o no segura. Desde ya que debe tratarse de clases base-derivada o viceversa, o se producir un error de compilacin. Veamos un ejemplo:

Ejemplo 19.2: dynamic_cast/static_cast

class B {...}; class D: public B {...}; { B *b = new B; D *d = new D; B *b2 = dynamic_cast <B*> d; B *b3 = static_cast <B*> d; validez // Conversin correcta. // Se verificar la validez de la operacin. // Conversin correcta. No se verificar la // de la operacin. Conversin correcta. Se verificar la validez de la operacin. Conversin correcta. No se verificar la de la operacin.

D *b2 = dynamic_cast <B*> b2; // // D *b3 = static_cast <B*> b2; // validez // D *b4 = dynamic_cast <B*> b; de ejecucin). D *b5 = static_cast <B*> b; validez de }

// Conversin incorrecta. // Se verificar la validez de la operacin // produciendoce una excepcin (error en tiempo // Conversin correcta. No se verificar la // la operacin.

Es posible realizar conversiones entre partes de objetos no directamente relacionadas, pero que formen parte de un objeto mayor. Esto se conoce con el nombre de cross-cast. Veamos un ejemplo:

Ejemplo 19.3: Cross-cast

class B {...};

135

75.42 - Taller de Programacin

class C {...}; class D: public B,C {...}; ... { D *d = new D; B *b = dynamic_cast <B*> d; C *c = dynamic_cast <C*> b; de D (cross cast) } // b apuntar a la parte B de D // Conversin correcta. c apuntar a la parte C

136

75.42 - Taller de Programacin

Los constructores y la herencia

Cabe preguntar qu ocurre con los constructores de una clase base, al crear una clase derivada. La respuesta es que siguen existiendo, y se llamarn automticamente al crear la clase derivada. Ya que al especificar un constructor de la clase derivada no se especifican parmetros para el constructor de la clase base, nicamente se llamar por defecto (de existir) el constructor que no reciba parmetros.

Como ya dijimos, nunca debe llamarse un constructor de una clase desde un mtodo de la misma, lo cual es vlido tambin para un constructor de la clase derivada. Veamos un ejemplo:

Ejemplo 19.4: Los constructores y la herencia

class A { public: A (void) {...} A (int x) {...} }; class B { public: B (void) {...} B (int x) {...} }; class C : private A, public B { public: C (int x) {...} }; Ahora bien, si creasemos un objeto de tipo C, se llamar automticamente los constructores A::A(void) y B::B(void), pero nunca a A::A(int x) y B::B(int x). Hay forma de hacer que se llame a estos dos ltimos constructores al crear una clase de tipo C? Rta: S, especificndolo junto con el constructor de la clase derivada, a continuacin del nombre del constructor de la misma, y antes de la declaracin. Por ejemplo:

class C : private A, public B { public: C (int x) : A (x) , B (x/2) {...} }; En este caso por ejemplo, al crearse el objeto B en la forma B b(10), se llamar al constructor de A en la forma A(10) y al constructor de la clase B como B(5).

137

75.42 - Taller de Programacin

Captulo XX - Sobrecarga de operadores y funciones friend


OPERADORES

Ya que el ejemplo de la Pila creci demasiado, empecemos un nuevo ejemplo desde cero, y vayamos refrescando algunos conceptos. Creemos la clase complejos.

Ejemplo 20.1: Objeto Complejo

#include <iostream.h> class TComplejo { private: int Real; int Imag; public: TComplejo (int Re=0, int Im=0); }; TComplejo::TComplejo (int Re, int Im) { Real = Re; Imag = Im; } void main(void) { TComplejo a, b(4,3); TComplejo c; } Notar que en el constructor de la clase se utilizan nombres distintos para los parmetros de la funcin, de los nombres de las variables a cargar, es decir, se define el contructor como: TComplejo::TComplejo (int Re, int Im); En vez de: TComplejo::TComplejo (int Real, int Imag); Esto es as para poder diferenciar las variables de la clase de los parmetros de la funcin. Existe una forma de hacer esto utilizando la palabra reservada this, pero esto se ver ms adelante.

Agreguemos ahora una funcin para sumar nmeros complejos.

Esta funcin deber tomar dos nmeros complejos, y devolver su suma. En principio podra pensarse que esto puede hacerse como sigue:

138

75.42 - Taller de Programacin

Ejemplo 20.2: Objeto Complejo - Funcin Sumar

#include <iostream.h> class TComplejo { private: int Real; int Imag; public: TComplejo (int Re=0, int Im=0); }; TComplejo Sumar(TComplejo C1, TComplejo C2) { TComplejo C3; C3.Real = C1.Real + C2.Real; C3.Imag = C1.Imag + C2.Imag; } return C3;

TComplejo::TComplejo (int Re, int Im) { Real = Re; Imag = Im; } void main(void) { TComplejo a, b(4,3); TComplejo c; } c = Sumar (a, b);

Pero hay un problema; la funcin: TComplejo Sumar (TComplejo C1, TComplejo C2); no pertenece a la clase Tcomplejo, y por lo tanto no puede acceder a los datos privados de la misma. Es decir que no podrn efectuarse las operaciones:

C3.Real = C1.Real + C2.Real; C3.Imag = C1.Imag + C2.Imag; Para solucionar esto, debe declararse a la funcin Sumar como amiga (friend) de la clase:

Ejemplo 20.3: Objeto Complejo - funciones friend class TComplejo { private: int Real; int Imag;

139

75.42 - Taller de Programacin

};

public: TComplejo (int Re=0, int Im=0); friend TComplejo Sumar(TComplejo C1, TComplejo C2);

Con lo cual ahora s se tiene un cdigo correcto.

Agreguemos ahora otra nueva funcin Sumar(), que sea llamada por un nmero complejo, tome como parmetro otro nmero complejo, y devuelva el resultado de la suma, es decir, sea llamada como:

TComplejo a, b, c; c = a.Sumar (b); // Quiero efectuar la operacin c = a + b Ejemplo 20.4: Objeto Complejo - Funcin Sumar (segunda variante)

#include <iostream.h> class TComplejo { private: int Real; int Imag; public: TComplejo (int Re=0, int Im=0); TComplejo Sumar (TComplejo C); friend TComplejo Sumar (TComplejo C1, TComplejo C2); }; TComplejo TComplejo::Sumar (TComplejo C) { TComplejo R; R.Real = Real + C.Real; R.Imag = Imag + C.Imag; } return R;

TComplejo Sumar (TComplejo C1, TComplejo C2) { TComplejo C3; C3.Real = C1.Real + C2.Real; C3.Imag = C1.Imag + C2.Imag; } return C3;

TComplejo::TComplejo (int Re, int Im) { Real = Re; Imag = Im; } void main (void)

140

75.42 - Taller de Programacin {

TComplejo a (1,3), b (4,3); TComplejo c,d; c = Sumar (a,b); d = a.Sumar (b);

Notar como ahora se tienen dos mtodos Sumar(), y el compilador sabe a cual llamar, segn la cantidad de parmetros que reciben. Estos dos mtodos hacen exactamente lo mismo, aunque son llamados en una forma diferente. De hecho, la segunda versin de Sumar(), aunque un tanto chocante en cuanto a la forma en que se la utiliza, tiene claras ventajas con respecto a la anterior, y por eso es fundamental prestar atencin y comprender el cdigo correctamente. La primera es que forma parte de la clase, y por lo tanto no requiere ser declarada como friend lo cual conviene evitar por ir en contra del encapsulamiento. La segunda, y la ms importante, es que puede ser declarado como un operador. Para esto se requiere usar la palabra reservada operator. Veamos nuevamente el ejemplo:

Ejemplo 20.5: Objeto Complejo - operador+

#include <iostream.h> class TComplejo { private: int Real; int Imag; public: TComplejo (int Re=0, int Im=0); friend TComplejo Sumar (TComplejo C1, TComplejo C2); TComplejo Sumar (TComplejo C); TComplejo operator+ (TComplejo C); }; TComplejo TComplejo::Sumar (TComplejo C) { TComplejo R; R.Real = Real + C.Real; R.Imag = Imag + C.Imag; } return R;

TComplejo TComplejo::operator+ (TComplejo C) { TComplejo R; R.Real = Real + C.Real; R.Imag = Imag + C.Imag; } return R;

TComplejo Sumar (TComplejo C1, TComplejo C2) { TComplejo C3; C3.Real = C1.Real + C2.Real;

141

75.42 - Taller de Programacin

C3.Imag = C1.Imag + C2.Imag; } return C3;

TComplejo::TComplejo (int Re, int Im) { Real = Re; Imag = Im; } void main (void) { TComplejo a (1, 3), b (4, 3); TComplejo c, d, e, f; c d e f = = = = Sumar (a, b); a.Sumar (b); a.operator+ (b); a + b;

Notar que la forma en que se defini el mtodo operator+ es idntica a como se defini la segunda versin de Sumar(), y por lo tanto debe ser llamada en la misma forma. Pero por ser la funcin un operador, es decir por estar definida con la palabra reservada operator, C++ permite omitir el texto .operator y el parntesis, con lo cual, solo queda el operador. C++ permite sobrecargar los siguientes operadores:

Tabla 2: Operadores sobrecargables + ! ^= <= () ~ != = &= >= [] *= , * < |= && new /= -> / > << || delete %= ->* % += >> ++ & >>= ^ -= <<= -| ==

Notar que new y delete son considerados operadores, y por lo tanto pueden ser sobrecargados.

Esto permite definir tipos de datos propios y operadores sobre ellos, y crear as un cdigo que se parecer mucho a un lenguaje de alto nivel. Por ejemplo, algo muy cmodo y comn es definir un tipo de dato TString, y poder ejecturar operaciones tales como:

TString S1, S2, S3; S1 = "Hola"; S2 = " mundo" S3 = S1 + S2; cout << S3; lo cual producira la salida: Hola mundo

142

75.42 - Taller de Programacin Volvamos a nuestro ejemplo de nmeros complejos. Agreguemos una funcin para imprimir nmeros complejos.

Ejemplo 20.6: Objeto complejo - sobrecarga de iostream #include <stdio.h> #include <iostream.h> class TComplejo { private: int Real; int Imag; public: TComplejo (int Re=0, int Im=0); friend ostream& operator<< (ostream &stream, TComplejo C); }; TComplejo::TComplejo (int Re, int Im) { Real = Re; Imag = Im; } ostream& operator<<(ostream &stream, TComplejo C) { stream << " (" << C.Real << " + i " << C.Imag << ") "; } return stream;

void main (void) { TComplejo a (1, 3); } cout <<"a =" << a << '\n';

Notar la forma en que se sobrecarg el operador operator<<, para poder definir la salida standard de la clase, no como miembro de la clase, sino como un operador global, "amigo" de la clase. Esto debe hacerse as porque el operador << deber anteponerse a la izquierda de la variable de tipo complejo a mostrar, y no a la derecha como el resto de los operadores aritmticos. Es decir, para mostrar la variable a no se escribe a<< sino <<a. El manejo de streams se ver con ms detalle en la seccin III - Programacin Avanzada en C++.

De la misma forma puede sobrecargarse el operador ++ como miembro de la clase para implementar operaciones como a++, pero para implementar el operador ++a deber sobrecargarse el operador ++ en forma global, como amigo a la clase.

Escribamos ahora el operador +=. Este operador debe sumar un complejo a otro, dejando el resultado en el que se encuentra a la izquierda. En principio parecera que el problema se resuelve escribiendo una funcin como sigue: void TComplejo::operator+= (TComplejo C) { Real += C.Real;

143

75.42 - Taller de Programacin

Imag += C.Imag;

Lo cual permitira efectuar operaciones tales como:

TComplejo C1, C2; C1 += C2; Esto no es incorrecto, pero s incompleto. Ya que en C/C++, al trabajar con tipos de datos convencionales, es posible escribir cosas como:

int a, b, c; c = a += b; Esto no es posible hacerlo con nuestra funcin void TComplejo::operator+= (TComplejo C) por la simple razn de que esta funcin no devuelve ningn parmetro, y por lo tanto, el siguiente operador (=), que esta definido como una copia de dato, no puede acceder al dato requerido. Pero esto puede solucionarse, de dos formas:

TComplejo TComplejo::operator+= (TComplejo C) { Real += C.Real; Imag += C.Imag; } return *this;

TComplejo& TComplejo::operator+= (TComplejo C) { Real += C.Real; Imag += C.Imag; } return *this;

Ambas funciones trabajan correctamente, y permiten efectuar operaciones tales como:

TComplejo C1, C2, C3; C3 = C1 += C2; Lo primero que se nota es la presencia de un puntero, this. (en castellano: este) Qu es este puntero? Dnde se lo defini? Este puntero se define automticamente en todas las clases de C++, y apunta a los datos de la clase. Este puntero se pasa automticamente a toda funcin de la clase, para que esta pueda acceder a los datos de la misma. As, toda clase tiene un puntero con el nombre this, que apunta a la estructura, es decir que, en nuestra funcion operator+= podramos haber escrito:

144

75.42 - Taller de Programacin Real += C.Real; Imag += C.Imag;

this->Real += C.Real; this->Imag += C.Imag; lo que es lo mismo:

(*this).Real += C.Real; (*this).Imag += C.Imag; Ahora bien, vayamos al return. En ambos casos se devuelve:

return *this; Pero en el primer caso, la funcin est definida como: TComplejo TComplejo::operator+= (TComplejo C) es decir que se devolver un dato de tipo TComplejo, que ser copiado el la pila del sistema, cuando la funcin retorne, en tanto que en el segunto caso, la funcin est definida como: TComplejo& TComplejo::operator+= (TComplejo C) por lo que solo se copiar a la pila un puntero al dato TComplejo actual, y por lo tanto resultar ms eficiente.

Un operador particular es el operador ++. Este (al igual que el operador --) puede ser prefijo tanto como postfijo. Pero el significado de estas dos variantes es diferente.

El operador ++ prefijo puede implementarse como sigue:

TComplejo& TComplejo::operator++(void) { printf ("Operador ++ (prefijo)\n"); Real++; return *this; } Pero el operador ++ postfijo, debe incrementar la variable, pero devolver el valor original de la variable. Por esto, no puede devolverse la variable original, sino que habr que crear una copia de la misma. Por esta razn, el operador prefijo resulta ser ms eficiente que el postfijo.

Para distinguir el operador prefijo del postfijo, este ltimo recibe un parmetro entero, cuyo valor no debe ser tenido en cuenta.

TComplejo TComplejo::operator++(int)

145

75.42 - Taller de Programacin

TComplejo c; c = *this; // Creo una copia de la variable Real++; // Incremento la variable original return c; // Retorno la copia

Notar que en este caso se devuelve una variable TComplejo por valor y no por referencia, como en el caso anterior. Esto se debe a que la variable c es local a la funcin operator++(int) razn por la cual no tiene sentido devolverla por referencia, ya que la misma se destruir al salir de la funcin.

En caso de no definirse una de estas funciones, la que est definida ser utilizada tanto como prefijo y postfijo. Esto puede conducir a errores, razn por la cual se recomienda nunca escribir uno slo de estos operadores.

Lo mismo ocurre con el operador --.

146

75.42 - Taller de Programacin

PARTE III - Programacin avanzada en C++


Captulo XXI: Entrada/Salida en C++
El lenguaje de programacin C tiene una de las interfaces de entrada/salida ms potentes y flexibles de todos los lenguajes de programacin. Siendo as, cabe la pregunta de porqu C++ define su propio sistema de entrada/salida. En realidad, C++, ms que definir un nuevo sistema de entrada/salida duplica la ya existente, adaptndola a los nuevos tipos de datos. Por ejemplo si tuviese algo como

struct MiTipoDeDato { char Calle[100]; int Altura; int CP; int Piso; }Dato; no hay forma en C, de adaptar printf() para este tipo de dato, es decir, no puedo hacer: printf ("%MiTipoDeDato", Dato); Sin embargo, s es posible en C++ sobrecargar el operador << de tal forma que pueda hacerse: cout<<Dato; El sistema de entrada/salida de C++, al igual que el de C, opera con streams. Ver streams en C para ms informacin. En C++ tiene cuatros streams predefinidos que se crean automticamente al ejecutar el programa. Estos son: cin, cout, cerr y clog. cin est asociado a la entrada standard, cout a la salida standard, y cerr y clog a la salida de errores. La diferencia entre cerr y clog es que clog se enva a la salida cuando el buffer de salida se llena, en tanto que cerr los datos enva la salida inmediatamente.

Estos streams estn definidos en el archivo iostream.h, donde se definen las clases streambuf, y en un nivel superior, heredando la clase anterior, la clase ios y luego las clases istream, ostream e iostream.

As, para adaptar la entrada/salida C++ para un nuevo tipo de dato, habr que sobrecargar los operadores << y >> de estos streams al nuevo tipo de dato. A esto se suele llamar insertadores y extractores.

Veamos un ejemplo: Recordemos clase TComplejo, y definamos la entrada/salida de C++.

Ejemplo 21.1: Objeto Complejo - entrada salida en C++

class TComplejo { private: int Real; int Imag; public:

147

75.42 - Taller de Programacin

};

TComplejo (int Re=0, int Im=0);

// Insertador para la clase TComplejo (imprime el dato en la salida standard) ostream &operator<<(ostream &stream, TComplejo C) { stream << ( << C.Real << + i * << C.Imag << ); return stream; } // Extractor para la clase TComplejo (carga el dato desde la entrada standard) istream &operator>>(istream &stream, TComplejo &C) { cout << "Ingrese en numero complejo Re, Im:" stream >>C.Real >> C.Imag; } return stream;

Por supuesto, para poder acceder a las variables Real y Imag de la clase TComplejo, ser necesario definir estos operadores como amigos (friend) de la clase TComplejo, es decir:

Ejemplo 21.2: Mtodos const

class TComplejo { private: int Real; int Imag; public: TComplejo (int Re=0, int Im=0); friend istream &operator>>(istream &stream, TComplejo &C) friend ostream &operator<<(ostream &stream, TComplejo C) }; Mtodos constantes

Es posible en C++ definir mtodos constantes, tambin llamados mtodos de slo lectura. Estos mtodos se caracterizan porque no pueden modificar ningn atributo de la clase, y se los declara utilizando la palabra reservada const, al final de la declaracin del mtodo. Por ejemplo:

class TComplejo { private: double Real; double Imag; public: TComplejo (double double Abs (void) { /* Este mtodo objeto pero double r;

Re=0, double Im=0); const puede leer los atributos del no modificarlos */

148

75.42 - Taller de Programacin r = Real*Real + Imag*Imag; return sqrt (r);

};

149

75.42 - Taller de Programacin

Captulo XXII - Templates


Volvamos ahora a nuestro ejemplo de la pila, que nos permite almacenar y luego recuperar enteros. Qu pasara si necesitsemos pilas para varios tipos de datos? Si estuvisemos trabajando en C, no habra otra posibilidad que crear una estructura pila para cada tipo de dato. Y lo que es peor, !!!modificar cada una de ellas, cada vez que se quisiese hacer un cambio en la forma de trabajar de la pila!!!. Pero afortunadamente, C++ define algo llamado template. Esta es una palabra reservada de C++.

Veamos un ejemplo.

Ejemplo 22.1: Clase Pila - templates

#include <stdio.h> #include <malloc.h> #include <iostream.h> #define PILA_MAX 100 template <class Tipo> class TPila { private: Tipo * Pila; unsigned Total; unsigned Max; public: TPila (unsigned MaxPila); ~TPila (); void Encolar (Tipo Dato); Tipo Desencolar (void); }; template <class Tipo> TPila<Tipo>::TPila(unsigned MaxPila) { Total = 0; Max = MaxPila; Pila = new Tipo[Max]; if (!Pila) printf ("Memoria insuficiente.\n"); else printf ("Pila incializada.\n"); } template <class Tipo> TPila<Tipo>::~TPila () { if (Pila) delete []Pila; printf ("La pila ha sido destruida.\n"); } template <class Tipo> void TPila<Tipo>::Encolar (Tipo Dato) { if (!Pila) { printf ("No se pudo inicializar la pila.\n");

150

75.42 - Taller de Programacin return;

if (Total<PILA_MAX) { cout <<"Encolando:"<<Dato<<'\n'; Pila[Total++]=Dato; } else printf("Pila esta llena.\n");

template <class Tipo> Tipo TPila<Tipo>::Desencolar (void) { if (!Pila) { printf("No se pudo inicializar la pila.\n"); return 0; } if (Total>0) { cout <<"Encolando:"<<Pila[--Total]<<'\n'; return Pila[Total]; } else { printf("Pila esta llena.\n"); return 0; }

void main (void) { TPila<char> Pila(10); Pila.Encolar ('a'); Pila.Encolar ('b'); Pila.Desencolar (); Pila.Desencolar ();

De esta forma estamos creando una clase TPila, que puede ser utilizada para encolar/desencolar cualquier tipo de dato, incluso estructuras y clases.

El tema template es ms extenso, y no es la idea de este curso profundizar ms en este tema. Para mayor informacin consultar bibliografa especializada.

Veamos otro ejemplo. Vamos a crear el tipo de dato TVector. Esto es algo muy til en programacin en C/C++, ya que es un error muy comn y difcil de encontrar el excederse en el tamao de un array. Me refiero por ejemplo a cosas del tipo:

char Str[100]; Str[100] = 0 ;

151

75.42 - Taller de Programacin

En este caso, el compilador no reportar ningn error, ni probablemente tampoco lo haga el programa al ejecutarse. Pero esto est sobreescribiendo datos en la memoria, y sin lugar a duda producir errores que se vern en otras partes del programa. Sera muy til poder crear un array que verificase el rango del ndice.

Veamos un ejemplo.

152

75.42 - Taller de Programacin

Ejemplo 22.2: Objeto Vector - templates

#include #include #include #include #include #include

<stdio.h> <stdlib.h> <iostream.h> <malloc.h> <string.h> <conio.h>

template <class Tipo, int Size> class TVector { private: Tipo Datos[Size]; Tipo Aux; public: inline Tipo& operator[](unsigned n); }; template <class Tipo, int Size> inline Tipo& TVector<Tipo,Size>::operator[] (unsigned n) { unsigned Max; Max = sizeof (Datos) / sizeof (Tipo); if (n >= Max) { /* Insertar un BreakPoint en esta posicion, para poder analizar porque se produjo el error */ cout << "Error: Se excede la capacidad del buffer.\n"; return Aux; } return Datos[n];

void main(void) { clrscr(); TVector <int,4>Vect; for (int i=0; i<5;++i) Vect[i] = 100+i; for (i=0; i<5;++i) cout << Vect[i]<<'\n';

Este programa produce la siguiente salida:

Error: Se excede la capacidad del buffer. 100 101 102 103 Error: Se excede la capacidad del buffer. 104

153

75.42 - Taller de Programacin

Veamos el cdigo fuente en detalle: template <class Tipo, int Size> class TVector El programa comienza definiendo una clase llamada TVector, que ser un template, es decir, que recibir parmetros en el momento de su creacin. En este caso se reciben dos datos, el primero, el tipo de dato especifico de la clase, el tipo de dato de los elementos del vector. El segundo, la cantidad de elementos mximos del vector.

Con esto, ya puedo crear vectores de cualquier tipo de dato. Por ejemplo, podra debera escribir:

char str[100]; int vect[200];

como TVector <char,100> str; como TVector <int,200> vect;

Cmo hago ahora para acceder a los datos del vector? Para empezar, podra definir un par de funciones:

template <class Tipo, int Size> void TVector<Tipo,Size>::CargarElemento (unsigned Posicion, Tdato dato); template <class Tipo, int Size> Tipo TVector<Tipo,Size>::LeerElemento (unsigned Posicion); Sin embargo, porqu no utilizar operadores para hacer esto? C++ permite sobrecargar el operador [] (corchete), y podramos hacerlo de las siguientes formas:

Tipo TVector::operator[](unsigned n) { return Datos[n]; }

Tipo& TVector::operator[](unsigned n) { return Datos[n]; } Nuevamente, la primer versin devolver el dato en cuestin, es decir que si definimos:

TVector <int, 10>V; int dato; podremos hacer cosas como:

dato = V[1]; pero NO: V[1] = dato;

154

75.42 - Taller de Programacin Esto se soluciona con la segunda versin de operator[], que devuelve un puntero al dato.

Ahora bien, ya que la clase fue creada como template<class Tipo, int Size>, ser necesario agregar esto a la definicin de la funcin, con lo cual: Tipo& TVector<Tipo,Size>::operator[](unsigned n) pasar a ser ahora: template <class Tipo, int Size> Tipo& TVector<Tipo,Size>::operator[](unsigned n) Una ltima correccin que puede hacerse a la funcin es agregar la palabra reservada inline. Esta hace que el compilador expanda en lnea el cdigo, en vez de llamar a la funcin, con lo cual el programa se vuelve ms rpido (pero tambin ms grande). Esto conviene hacerlo, ya que en caso contrario, cada acceso al vector requerir una llamada a una funcin. template <class Tipo, int Size> inline Tipo& TVector<Tipo,Size>::operator[] (unsigned n) Finalmente, y el objetivo de nuestro cdigo es verificar que el ndice que se pasa al operador est dentro del rango permitido. Esto lo podemos hacer de la siguiente forma:

template <class Tipo, int Size> inline Tipo& TVector<Tipo,Size>::operator[] (unsigned n) { unsigned Max; Max = sizeof (Datos) / sizeof (class Tipo); if (n >= Max) { /* Insertar un BreakPoint en esta posicion, para poder analizar porque se produjo el error */ cout << "Error: Se excede la capacidad del buffer.\n"; return Aux; } return Datos[n];

Qu es todo esto? Vamos por partes. La lnea: Max = sizeof (Datos) / sizeof (class Tipo); calcula la cantidad de elementos que pueden alojarse en el vector, dividiendo el tamao total del vector de datos sobre el tamao individual de cada dato. En el paso siguiente, verificamos que el ndice, n, est dentro del rango permitido. En caso contrario, indicamos el error. Notar la utilidad de poner un breakpoint (punto de ruptura) en la lnea que produce el error. Esto har que al verificar el programa, la ejecucin se interrumpa en dicha lnea, y se pueda verificar qu ocasion que se llegue a esta situacin.

Finalmente queda una pregunta. Porqu el return Aux? Lo que ocurre aqu es que no puedo efectuar la operacin: return Datos[n], ya que n est fuera de rango. Podra interrumpir la ejecucin del programa, exit (1);, pero en un programa importante, debe reportarse el error de una forma adecuada. Incluso puede ser que el programa tenga otras formas de solucionar el inconveniente, por ejemplo, si se tratase de un sistema de control, puede ser grave que sbitamente el programa se interrumpa. Pero necesariamente tengo que

155

75.42 - Taller de Programacin

devolver un dato, ya que la funcin as lo requiere. Podra devolver una posicin cualquiera del array, por ejemplo la cero, pero podran ocurrir cosas como: TVector <int, 4>Vect; Vect[5] = 10; En este caso la posicin cero del array sera sobreescrita, lo cual no es correcto. Por ello, la solucin ms adecuada es, o bien reservar un lugar adicional en el array, o por claridad, definir un dato auxiliar.

Clases template dentro de clases template

Pueden usarse las clases template como base para construir nuevas clases, o usar clases template como argumento de clases template? Desde ya que s, son clases como cualquier otra. Como ejemplo, a continuacin se usar la clase TVector como base para crear el tipo de dato TString. Luego, a continuacin se usarn estas dos clases para crear una lista de strings. Desde ya que esta estructura de clases no resultar ptima, pero eso no es importante en este momento.

Ejemplo 22.3: Objeto Vector - templates anidados

#include #include #include #include #include #include

<stdio.h> <stdlib.h> <iostream.h> <malloc.h> <string.h> <conio.h>

/**************************************************************************/ /*** C L A S E V E C T O R E S T A T I C O ***/ /**************************************************************************/ template <class Tipo, int Size> class TVector { private: Tipo Datos[Size]; Tipo Aux; public: inline Tipo& operator[](unsigned n); }; template <class Tipo, int Size> inline Tipo& TVector<Tipo,Size>::operator[] (unsigned n) { unsigned Max; Max = sizeof (Datos) / sizeof (Tipo); if (n >= Max) { /* Insertar un BreakPoint en esta posicion, para poder analizar porque se produjo el error */ cout << "Error: Se excede la capacidad del buffer.\n"; } return Aux;

156

75.42 - Taller de Programacin return Datos[n];

/**************************************************************************/ /*** CLASE TSTRING, DERIVADA DE TVECTOR ***/ /**************************************************************************/ class TString: public TVector <char, 100> { private: public: TString& operator=(char *s) { for (unsigned i=0;i<strlen(s)+1;++i) (*this)[i] = s[i]; return *this; } friend ostream& operator<< (ostream &stream, TString &s); }; ostream& operator<< (ostream &stream, TString &s) { stream << &(s[0]); return stream; } /**************************************************************************/ /*** F U N C I O N M A I N ***/ /**************************************************************************/ void main(void) { clrscr(); TString s; TVector <TString, 10> StrList; s = "hola"; StrList[1] = "HOLA "; StrList[2] = "MUNDO"; cout <<s <<"\n"; cout <<StrList[1]<<"\n"; cout <<StrList[2]<<"\n";

La salida de este programa es la siguiente:

hola HOLA MUNDO EL PREPROCESADOR

Finalmente un ltimo comentario con respecto al programa anterior. Efectivamente, cada vez que se quiera acceder a una posicin del array, se ejecutarn varias instrucciones, lo cual volver al programa bastante ineficiente. Para evitar esto hay una solucin, que es la utilizacin de definiciones. Por ejemplo podra escribirse:

Ejemplo 22.4: El preprocesador

157

75.42 - Taller de Programacin

template <class Tipo, int Size> inline Tipo& TVector<Tipo,Size>::operator[] (unsigned n) { #if defined (DEBUG_MODE) unsigned Max; Max = sizeof (Datos) / sizeof (class Tipo); if (n >= Max) { /* Insertar un BreakPoint en esta posicion, para poder analizar porque se produjo el error */ cout << "Error: Se excede la capacidad del buffer.\n"; return Aux; } #endif return Datos[n]; } Si al comienzo del programa se define DEBUG_MODE (#define DEBUG_MODE), se efectuaran todas las verificaciones, lo cual se dejar de hacer con slo eliminar esta definicin.

Otra macro til al trabajar con C++ es __cplusplus. Esta macro estar definida si se compila el programa utilizando un compilador C++ y es fundamental para compilar cdigo C junto con C++ (Ver apndice V). Ejemplo:

void main (void) { FILE *fp; #ifndef __cplusplus /* Mostrar el tipo de compilador utilizado */ printf ("El programa se compil en C\n"); #else cout <<"El programa fue compilado en C++ en "<< __DATE__ <<"\n"; #endif

158

75.42 - Taller de Programacin

Captulo XXIII - Un poco ms sobre herencia - Herencia mltiple


En las secciones anteriores vimos cmo poda definirse una clase como derivada de otra, y as sucesivamente. Esto permite crear estructura muy complejas de clases, que se heredan unas a otras. Incluso es posible hacer que una misma clase se construya heredando ms de una clase. Por ejemplo:

Ejemplo 23.1: Herencia mltiple

class TA { ... }; class TB { ... }; class TC : public TA, private TB { ... }; Pueden surgir situaciones complejas cuando una misma clase se hereda ms de una vez. Por ejemplo, imaginemos ahora dos situaciones como las siguientes:

Situacin 1: Quiero crear clases para describir los seres vivos, clasificndolos en aves, peces, anfibios, reptiles, mamferos. Para describir la Ballena, me puede interesar caracterizarla como un mamfero pero con caractersticas de pez.

Situacin 2: Quiero crear clases para describir caractersticas de algunos vehculos. Pero surge un vehculo anfibio capaz de desplazarse por agua y por tierra.

Estos casos se grafican esquemticamente a continuacin:

Los txtos marcados con asterisco (*) son los atributos de la clase. Veamos estos dos casos con ms detalle. En el primer ejemplo, al definir la clase Ballena, la clase Vertebrados ser incluida dos veces, una por Pez y otra por Mamfero. Lo mismo suceder en el segundo ejemplo con la clase Anfibio. Pero en el primer caso no quiero que se dupliquen los atributos VidaMedia y PesoMedio de la clase predecesora, en tanto que en el segundo caso s quiero que se dupliquen los atributos VelocMaxima y CargaMaxima, ya los mismos sern diferentes cuando el vehculo se est comportando como un vehculo acutico o como terrestre.

159

75.42 - Taller de Programacin

Es decir, se tienen las siguientes situaciones:

Cmo se resuelve esto? En caso de plantearse esta situacin C++ se asume por defecto el segundo caso. Si lo que se quiere es el primero, debe utilizarse herencia virtual.

Veamos como implementar estos dos casos:

Empecemos por el segundo, que es el ms simple:

Ejemplo 23.2: Herencia mltiple - Mltiple heredad de una clase

#include <stdio.h> #include <iostream.h> class TA { public: int a; }; class TB:public TA { }; class TC:public TA { }; class TD:public TB,public TC { }; void main (void) { TD d1; /* d1.a = 1; Sera es ambiguo, ya que no habra forma de saber a cual

160

75.42 - Taller de Programacin se los dos atributos a se quiere hacer referencia. */ d1.TB::a = 1; d1.TC::a = 2; } printf ("d1.TB::a = %d d1.TC::a = %d\n", d1.TB::a, d1.TC::a);

Si quiere que se entienda por defecto una de estas dos opciones, puede usarse la palabra reservada using:

Ejemplo 23.3: Herncia mltiple - operador using

void main (void) { TD d1; using TC::a; /* d1.a = 1; Sera es ambiguo, ya que no habra forma de saber a cual se los dos atributos a se quiere hacer referencia. */ d1.TB::a = 1; d1.a = 2; /* Por defecto se entiende d1.TC::a*/ } printf ("d1.TB::a = %d d1.TC::a = %d\n", d1.TB::a, d1.TC::a);

Este programa produce la salida

d1.TB::a = 1

d1.TC::a = 2

Veamos ahora como implementar el primer caso:

Ejemplo 23.4: Herencia mltiple - clases virtuales

#include <stdio.h> #include <iostream.h> class TA { public: int a; }; class TB:public virtual TA { }; class TC:public virtual TA { }; class TD:public TB,public TC {

161

75.42 - Taller de Programacin

}; void main(void) { TD d1; d1.TB::a = 1; d1.TC::a = 2; d1.a = 3; /* Ahora esta lnea no es ambigua */ } printf ("d1.TB::a = %d d1.TC::a = %d\n", d1.TB::a, d1.TC::a);

Este programa produce la salida

d1.TB::a = 3

d1.TC::a = 3

Notar la palabra reservada virtual en la definicin de las clases TB y TC, que indica que la clase TA debe ser heredada una nica vez. Notar tambin que no es posible duplicar tan solo parte de los atributos, es decir, no hay forma de declarar un atributo virtual. S se puede declarar mtodos virutales pero el significado de los mismos es diferente, ya que no tiene sentido hablar de duplicar un mismo cdigo.

Los mtodos virtuales se vern el la siguiente seccin.

162

75.42 - Taller de Programacin

Captulo XXIV - Mtodos virtuales y funciones puras y abstractas


Supongamos una clase para almacenamiento de datos. Esta clase tendra los mtodos tales como insertar, borrar, editar, grabar a disco y cargar de disco. Un programador podra utilizar esta clase base, para construir bases de datos ms sofisticadas, y podra querer reemplazar funciones de la clase base por otras mejoradas, o al menos agregarle otras funcionalidades. Por ejemplo, supongamos que quisiese reemplazar el mtodo de escritura a disco de la clase base. Desde el punto de vista del programa o de nuevas clases herederas, basta con definir una nueva funcin con el mismo nombre, por ejemplo:

Ejemplo 24.1: Clase Base

class TBase { ... public: Insertar (...); Editar (...); Borrar (...); GrabarADisco(...); CargarDeDisco(...); }; class TBaseMejorada:private TBase { public: GrabarADisco(...); }; En este caso, si se llamase al mtodo GrabarADisco() desde el programa principal o desde una clase derivada de TBaseMejorada, se llamara a la funcin definida en esta ltima. Sin embargo esta nueva funcin no ser llamada desde la clase TBase, la cual seguir llamando a su propio mtodo. Por ejemplo, si la clase TBase tuviese un mtodo AutoSave, que automticamente llamase a GrabarADisco(), se llamara al mtodo original.

Cmo puede hacerse para que siempre se llame al mtodo de la clase derivada, an desde la clase base? Rta: Definiendo el mtodo como virtual.

Las llamadas a un mtodo definido como virtual sern completamente reemplazadas por llamadas a los mtodos similares de clases derivadas, de existir estos. Todava puede llamarse a las funciones virtuales originales, si se especifica previamente el nombre de la clase seguida de :: (doble dos puntos). Veamos ahora el ejemplo completo.

Ejemplo 24.2: Clase Base - mtodos virtuales

class TBase { ... public: void AutoSave () { GrabarADisco(); } void Insertar ();

163

75.42 - Taller de Programacin

};

void Editar (); void Borrar (); virtual void GrabarADisco() { cout <<"Base grabada\n"; } void CargarDeDisco();

class TBaseMejorada:public TBase { public: void GrabarADisco(...) { cout << "Realmente desea grabar?\n"; if (SolicitarConfirmacin ()) return; TBase::GrabarADisco(); }; }; void main (void) { TBaseMejorada Base; } Base.AutoSave();

Este programa producira la siguiente salida:

Realmente desea grabar? Base grabada Ya que un mtodo virtual ser posiblemente redefinido en una clase derivada, tiene sentido pensar en que el mismo pueda ser declarado, pero no definido, es decir, que en la clase base no de especifique ningn cdigo para dicho mtodo, tan solo se declare su existencia para que pueda ser llamado por mtodos de la clase, dejando su declaracin a clases derivadas. Mtodos de este tipo se conocen como mtodos o funciones virtuales puras. Las clases que contengan una o ms funciones puras se conocen con el nombre de clases abstractas, y no podrn usarse para crear variables de este tipo, sino tan solo clases derivadas en donde se declaren estas funciones virtuales.

Los mtodos virtuales puros se declaran en forma idntica a cualquier mtodo virtual, pero igualados a cero. Por ejemplo:

Ejemplo 24.3: Clase Base: Mtodos puros y abstractos

class TBase { ... public: void AutoSave (); void Insertar (); void Editar (); void Borrar (); virtual void GrabarADisco() = 0;

164

75.42 - Taller de Programacin }; LA HERENCIA VIRTUAL Y EL COMPILADOR

Cmo se resuelve el problema de la herencia virtual desde el punto de vista del compilador? Para ello volvamos a analizar el ejemplo del captulo anterior.

Ejemplo 24.4: Herencia virtual

class TA { int a; }; class TB: virtual public TA{ { int b; }; class TC: virtual public TA{ { int c; }; class TD: public TB, public TC { int d; }; Ejemplo 24.5: Herencia virtual

class TA { int a; }; class TB: public TA{ { int b; }; class TC: public TA{ { int c; }; class TD: public TB, public TC { int d; };

En el segundo caso estar claro que una clase tipo TD se compondr de 5 variables de tipo entero, y por lo tanto ser de esperar que el tamao de la misma sea de 5*sizeof (int) bytes.

Si analizsemos el contenido la estructura TD en la memoria de la PC podramos encontrar algo como lo siguiente:

165

75.42 - Taller de Programacin

Qu ocurre con el primer caso. Ya que la clase TA se hereda en forma virtual, tanto en TB como en TC, una clase de tipo TD contendr 4 variables: a, b, c y d. Sera de esperar que el tamao de la clase sea de 4*sizeof (int) bytes, pero no es as. Dependiendo del compilador, pueden obtenerse valores tales como 6*sizeof (int). Qu ha ocurrido? Si el compilador simplemente armase la nueva clase TD con 4 variables sera imposible efectuar casting entre objetos (ver el captulo casting de objetos). Concretamente, no podran efectuarse operaciones tales como:

TA *A; TB *B; TC *C; TD *D = new TD; A = D; // Obtengo la posicin de la clase tipo TA dentro de la tipo TD B = D; // Obtengo la posicin de la clase tipo TB dentro de la tipo TD C = D; // Obtengo la posicin de la clase tipo TC dentro de la tipo TD Una alternativa a esto consiste en duplicar efectivamente las variables de la clase TA, como en el ejemplo anterior, y garantizar que estas dos variables tengan siempre el mismo valor, pero esto es imposible, ya que una de ellas podra ser modificada de una forma no prevista. La alternativa implementada por C++ es utilizar punteros. En todo caso en que se herede una clase en forma virtual, la misma ser accedida internamente mediante un puntero. Por ejemplo, podramos pensar en una situacin como la siguiente:

166

75.42 - Taller de Programacin

Captulo XXV - Utilizacin de memoria dinmica en clases - Constructor de copia


La memoria dinmica y los objetos

Veamos ahora otro ejemplo. Ya que estamos definiendo vectores, porqu no definir un vector que utilice memoria dinmica, es decir que reserve memoria al ser creado y la libere cuando ya no se lo requiera. C++ parece ideal para esto. Permite implementar un constructor, encargado de reservar memoria para l, y un destructor que automticamente lo destruya cuando se lo deja de utilizar. Pus bien, hagmoslo:

Ejemplo 25.1: Objeto String

#include #include #include #include #include

<stdio.h> <stdlib.h> <malloc.h> <string.h> <iostream.h>

class TString { private: char *Str; unsigned Size; public: TString (unsigned S); void operator=(char * Txt); ~TString (); }; TString::TString (unsigned S) { Size = S; Str = new char [Size]; if (!Str) { cout << "Memoria insuficiente. No se puede inicializar string.\n"; /* En un programa importante, no debera llamarse a exit(), sino retormar y reportar el error adecuadamente */ exit (1); } } cout << "String inicializado.\n";

TString::~TString () { if (!Str) { delete []Str; Str = NULL; } cout << "String destruido.\n"; }

167

75.42 - Taller de Programacin

void TString::operator=(char * Txt) { if (!Str) { cout << "Se quiere asignar datos a un string sin inicializar.\n"; return; } if (strlen (Txt) >= Size) { cout << "Cadena demasiado larga.\n"; return; } strcpy (Str,Txt); } void main(void) { clrscr(); TString s1(100); } s1="Hola mundo";

El programa produce la siguiente salida al ser ejecutado: String inicializado. String destruido. Lo cual es correcto. Agreguemos ahora una funcin que reciba un TString, es decir, que haga algo como lo siguiente:

void MiFuncion (TString S) { ... } void main(void) { clrscr(); TString s1 (100); s1 = "Hola mundo"; } MiFuncion (s1);

Aparentemente no debera haber ningn problema, no ha cambiado mucho. Pero al ejecutar el programa se obtiene la siguiente salida:

String inicializado. String destruido. String destruido.

168

75.42 - Taller de Programacin Se han destruido dos string, pero se ha creado uno slo!!! Esto ya indica la presencia de un error. Pero qu es lo que ha ocurrido? Para responder esta pregunta es necesario prestar atencin al cdigo del programa, y a la forma en que los contructores y destructores funcionan.

En la segunda lnea de la funcin main() se crea el dato s1. Al crear este dato se llama al contructor del mismo, que reserva memoria para el dato.

Un par de lneas ms abajo, se llama a la funcion MiFuncion(), a la cual se pasa una copia del dato s1. Este dato tendr el nombre S. En este punto se tiene la siguiente situacin:

Pero la variable S es efectivamente una variable de tipo TString, que pertenece a la funcin MiFuncion(). Y por lo tanto, al salir de la misma, se llamar al destructor de la variable. Este destructor destruir el dato *Str, y por lo tanto se tendr la siguiente situacin:

Finalmente, cuando se salga de la funcin main, se llamar al destructor de la variable s1, la cual a su vez LIBERAR NUEVAMENTE LA MEMORIA YA LIBERADA, lo cual puede provocar entre otras cosas que el sistema se "cuelgue" o se reporte un mensaje de error.

En el ejemplo que acabamos de ver, puede solucionarse este inconveniente haciendo algo como lo siguiente:

169

75.42 - Taller de Programacin

void MiFuncion (TString &S) { ... } o como lo siguiente:

void MiFuncion (TString *S) { ... } Esto funcionar correctamente. En el primer caso S no es una nueva variable sino la misma, pasada por referencia, y en el segundo S no es una variable de tipo TString sino un puntero. Sin embargo si modificamos la funcin para que devuelva una copia del dato como sigue:

TString MiFuncion (TString &S) { return S; } Volveramos a tener el mismo problema.

Constructor de copia

Retomemos el problema planteado en la seccin anterior. Estos problemas surgen porque estamos creando copias del objeto original, pero no duplicamos los datos dinmicos, los cuales siguen apuntando a la misma posicin. Para solucionar todos estos inconvenientes simplemente se debe crear un contructor de copia y un operador =.

Un constructor de copia es un constructor de la clase como cualquier otro, con la salvedad de que recibe como nico parmetro un objeto del mismo tipo, por referencia.

Por ejemplo, en este caso se tendra: TString::TString (TString &S2) { ... } Este constructor ser llamado automticamente cada vez que automticamente se duplique un objeto de clase.

Est prohibido definir un constructor como sigue: TString::TString (TString S2) ya que al llamarlo, habra que crear una copia del objeto, y para crear esta copia habra que llamar a este constructor, y as indefinidamente.

Pero todava falta un detalle, el constructor de copia no es suficiente, ya que este ser llamado cada vez que el compilador automticamente duplique la variable. Todava falta solucionar el problema cuando el

170

75.42 - Taller de Programacin programador explcitamente duplique la variable, es decir cuando se escriba Var1 = Var2. Si no se lo hiciese, se usara la asignacin por defecto, que es una copia literal del contenido.

Es bueno que todo programador principiante agrege a los constructores, destructores y operadores, cdigo suficiente para poder verificar el correcto funcionamiento de los mismos.

Veamos ahora como quedara este ejemplo:

Por simplicidad dejaremos el operador = para despus.

Ejemplo 25.2: Objeto String - Constructor de copia

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

#include #include #include #include #include #include

<stdio.h> <stdlib.h> <malloc.h> <iostream.h> <string.h> <conio.h>

class TString{ private: char * Str; public: TString (); TString (char *s); ~TString (); TString (TString &S2); }; TString::TString () { cout<<"Construyendo un string vacio\n"; Str = NULL; } TString::TString (char *s) { cout<<"Construyendo un string con el texto: "<<s<<"\n"; Str = new char[strlen(s)+1]; strcpy (Str,s); } TString::~TString () { if (Str) cout<<"Destruyendo el string con el texto: "<<Str<<"\n"; else cout<<"Destruyendo un string vacio\n"; if (Str) delete []Str; Str = NULL; } TString::TString (TString &S2)

171

75.42 - Taller de Programacin

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

if (S2.Str) cout<<"Duplicando el string con el texto: "<<S2.Str<<"\n"; else cout<<"Duplicando un string vacio\n"; if (S2.Str == NULL) Str = NULL; else { Str = new char[strlen(S2.Str)+1]; strcpy (Str, S2.Str); }

TString MiFunc (TString S2) { return S2; } void main(void) { clrscr(); TString S("Hola"); } MiFunc (S);

Este programa produce la siguiente salida:

Construyendo un string con el texto: Hola Duplicando el string con el texto: Hola Duplicando el string con el texto: Hola Destruyendo el string con el texto: Hola Destruyendo el string con el texto: Hola Destruyendo el string con el texto: Hola Veamos qu ocurre en detalle. En la lnea 66 se construye el primer string con el texto Hola. Luego en la lnea 68 se lo duplica, para llamar a la funcin MiFunc(). Esta variable ser la variable S2, que se define en la lnea 58. Un par de lneas ms abajo, en la lnea 60 se duplica nuevamente este string, el cual se almacena en una variable temporal, que es devuelta por la funcin. En la lnea 61, al salir de la funcin, se destruye la variable S2, que deja de tener significado, y se retorna de la funcin. La variable temporal devuelta se pierde por no ser copiada a ningn lugar, y en la lnea 66, inmediatamente despus del retorno de la funcin, se destruye. Finalmente, al salir de la funcin main() se destruye la ltima variable de tipo TString (S) que quedaba.

Otro ejemplo

Alentados por estos buenos resultados, agreguemos ahora los operadores += y +, para concatenar dos strings en el primero y en uno nuevo respectivamente.

Antes de hacerlo, agreguemos un par de funciones simples, una para vaciar el string: void TString::Clear() y otra para mostrar su contenido, sobrecargando el operador << como ya se ha visto. No nos olvidemos del operador = que haba quedado pendiente.

172

75.42 - Taller de Programacin Estas funciones sern como sigue:

Ejemplo 25.3: Objeto String - operadores varios

void TString::Clear () { if (Str) delete []Str; Str = NULL; } ostream& operator<<(ostream &stream, TString &S) { stream<<S.Str; return stream; } /* Cdigo incorrecto */ TString& TString::operator=(TString &S2) { if (S2.Str) cout<<"Duplicando (=) el string con el texto: "<<S2.Str<<"\n"; else cout<<"Duplicando (=) un string vacio\n"; if (S2.Str == NULL) Str = NULL; else { Clear(); Str = new char[strlen(S2.Str)+1]; strcpy (Str, S2.Str); } return *this;

El operador = parecera ser correcto. Incluso con un anlisis exhaustivo se ver que lo es, salvo para el caso en que se hiciese algo como: Var1 = Var1; ya que strcpy() no puede copiar un string sobre si mismo. La asignacin a = a parecera no tener sentido. Sin embargo es vlida y es muy comn que se produzca cuando se utilizan operadores en cascada. Se ver esto ms adelante.

El cgido correcto es el siguiente:

TString& TString::operator= (TString &S2) { if (this == &S2) return *this; if (S2.Str) cout<<"Duplicando (=) el string con el texto: "<<S2.Str<<"\n"; else cout<<"Duplicando (=) un string vacio\n"; if (S2.Str == NULL) Str = NULL; else

173

75.42 - Taller de Programacin

} return *this;

Clear(); Str = new char[strlen(S2.Str)+1]; strcpy (Str, S2.Str);

Muy bien, ahora creemos el operador +=. Este operador deber concatenar el variables de tipo TString. Es decir, deber ser similar a strcpy(). Este operador permitir efectuar operaciones como: S1 += S2; o como S1 += TString ("hola"); Podra crearse otro operator += que permita concatenar cadenas de caracteres (pero esto lo dejaremos de lado ya es trivial), tal como: S1 += "hola"; /* Codigo incorrecto */ TString& TString::operator+=(TString &S2) { /* Si el string a concatenar no esta vacio */ if (S2.Str != NULL) { if (Str == NULL) Str = strdup (S2.Str); else { Str = (char *)realloc (Str, strlen (Str) + strlen (S2.Str) + 1); strcat (Str, S2.Str); } } } return *this;

Notar el smbolo & en la indicacin del tipo a devolver (TString&) en la definicin de los operadores = y +=, que indica que debe devolverse la variable por referencia y no una copia de la misma. Si no lo hubiesemos puesto, el return hubiese creado una copia temporar de la variable *this, a la cual hubiese luego destruido, lo cual hubiese significado una prida de tiempo.

Al igual que antes, el operador += parecera ser correcto, salvo por el hecho de que no soporta operaciones tales como: Var1 += Var1; ya que strcat() no puede concatenar un string con si mismo. El cgido correcto es el siguiente:

TString& TString::operator+=(TString &S2) { /* Si el string a concatenar no esta vacio */ if (S2.Str != NULL) { if (Str == NULL)

174

75.42 - Taller de Programacin Str = strdup (S2.Str); else { Str = (char *)realloc (Str, strlen (Str) + strlen (S2.Str) + 1); memmove (Str+strlen(Str), S2.Str, strlen (S2.Str)+1); }

} }

return *this;

Finalmente agreguemos el operador +. Este operador deber crear un nuevo string que contenga los dos anteriores concatenados, y devolverlo en una variable de tipo TString. Qu ms simple y sencillo que escribir un operador como sigue:

/* Codigo incorrecto */ TString& TString::operator+(TString &S2) { TString S3(*this); S3 += S2; } return S3;

Sin embargo este cdigo no compilar, ya que la variable S3 dejar de existir inmediatamente al terminar la funcin, y por lo tanto no puede devolversela por referencia. Una alternativa es devolver una copia de la variable:

TString TString::operator+(TString &S2) es decir, sin el &. Pero esto har que se cree una copia de la variable al salir de la funcin, la cual deber ser luego destruida, y lo cual ser una prdida de tiempo innecesaria.

La solucin es utilizar una variable esttica, es decir, que no se destruya al salir de la funcin:

TString& TString::operator+(TString &S2) { static TString S3; cout<<"Sumando '"<<*this<<"' con '"<<S2<<"'\n"; S3 = *this; S3 += S2; } return S3;

Supongamos que ahora se tiene luego un cdigo como el siguiente:

void main (void) { clrscr (); TString S1 ("Hola"); TString S2 (" mundo");

175

75.42 - Taller de Programacin

TString S3; S3 = S1 + S2 + TString (" del C++"); } cout <<"El string final es: "<<S3<<"\n";

El listado completo del programa se muestra a continuacin:

Ejemplo 25.4: Objeto String - Versin final

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

#include #include #include #include #include #include

<stdio.h> <stdlib.h> <malloc.h> <iostream.h> <string.h> <conio.h>

class TString{ private: char * Str; public: void Clear (); TString (); TString (char *s); ~TString (); TString (TString &S2); TString& operator=(TString &S2); TString& operator+=(TString &S2); TString& operator+(TString &S2); friend ostream& operator<<(ostream &stream, TString &S); }; ostream& operator<<(ostream &stream, TString &S) { stream<<S.Str; return stream; } void TString::Clear () { if (Str) delete []Str; Str = NULL; } TString& TString::operator+=(TString &S2) { /* Si el string a concatenar no esta vacio */ if (S2.Str != NULL) { if (Str == NULL) Str = strdup (S2.Str); else {

176

75.42 - Taller de Programacin 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 Str = (char *)realloc (Str, strlen (Str) + strlen (S2.Str) + 1); memmove (Str+strlen (Str), S2.Str, strlen (S2.Str)+1);

} }

return *this;

TString::TString () { cout<<"Construyendo un string vacio\n"; Str = NULL; } TString::TString (char *s) { cout<<"Construyendo un string con el texto: "<<s<<"\n"; Str = new char[strlen(s)+1]; strcpy (Str,s); } TString::~TString () { if (Str) cout<<"Destruyendo el string con el texto: "<<Str<<"\n"; else cout<<"Destruyendo un string vacio\n"; if (Str) delete []Str; Str = NULL; } TString::TString (TString &S2) { if (S2.Str) cout<<"Duplicando el string con el texto: "<<S2.Str<<"\n"; else cout<<"Duplicando un string vacio\n"; if (S2.Str == NULL) Str = NULL; else { Str = new char[strlen(S2.Str)+1]; strcpy (Str, S2.Str); }

TString& TString::operator=(TString &S2) { if (this == &S2) return *this; if (S2.Str) cout<<"Duplicando (=) el string con el texto: "<<S2.Str<<"\n"; else cout<<"Duplicando (=) un string vacio\n"; if (S2.Str == NULL) Str = NULL; else

177

75.42 - Taller de Programacin

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

} return *this;

Clear(); Str = new char[strlen(S2.Str)+1]; strcpy (Str, S2.Str);

TString MiFunc (TString S) { return S; } /* Codigo incorrecto */ TString& TString::operator+(TString &S2) { static TString Stmp; cout<<"Sumando '"<<*this<<"' con '"<<S2<<"'\n"; Stmp = (*this); Stmp += S2; } return Stmp;

void main (void) { clrscr(); TString S1("Hola"); TString S2(" mundo"); TString S3; S3 = S1 + S2 + TString (" del C++"); } cout <<"El string final es: "<<S3<<"\n";

La salida completa de este programa es la siguiente: Construyendo un string con el texto: Hola Construyendo un string con el texto: mundo Construyendo un string vacio Construyendo un string con el texto: del C++ Construyendo un string vacio Sumando 'Hola' con ' mundo' Duplicando (=) el string con el texto: Hola Sumando 'Hola mundo' con ' del C++' Duplicando (=) el string con el texto: Hola mundo del C++ Destruyendo el string con el texto: del C++ El string final es: Hola mundo del C++ Destruyendo el string con el texto: Hola mundo del C++ Destruyendo el string con el texto: mundo Destruyendo el string con el texto: Hola Destruyendo el string con el texto: Hola mundo del C++ Analicemos el programa en detalle:

178

75.42 - Taller de Programacin En las lneas 137, 138 y 139 se crean los tres string iniciales, los dos primeros con los textos "Hola", " mundo", y el tercero inicialmente vaco.

La lnea 141 se evala de izquierda a derecha, pero lo primero que se hace es armarla en forma completa. Para ello, antes de comenzar a evaluarla se crea un string temporario con el texto " del C++".

Esta lnea 141 debe interpretarse como sigue: S3.operator= ( (S1.operator+ (S2.operator)).operator+ (TString (" del C++")) ); Lo cual puede resultar un tanto confuso inicialmente.

Se efecta la suma S1 + S2. Para hacerlo se llama al operador +, que comienza creando la variable Stmp, inicialmente vaco (lnea 124), lo cual tambin se reporta a la salida (lnea 126).

En la lnea 128 se copia en Stmp el texto "Hola", llamando al operador =, lo cual se reporta en la lnea 100.

En la lnea 129 se concatena a Stmp el texto " mundo", llamando al operador += definido en la lnea 37. Para simplificar el cdigo, no se puso ningn mensaje en esta funcin.

inalmente en la lnea 131 se retorna por referencia la variable esttica Stmp de tipo TString, y recin aqu ha terminado la primer suma.

Pero Stmp es una variable de tipo TString, por lo que ahora se tiene: S3 = Stmp + TString (" del C++"); Lo cual es equivalente a escribir: S3 = Stmp.operator+ (TString (" del C++")); Se ejecuta ahora el operador +, llamado por la variable Stmp. Es decir que ahora, dentro del operador +, el puntero this apuntar a la variable Stmp. Notar que en la lnea 128 se tiene: Stmp = (*this); y es por ello que es necesario que el operador = soporte cosas como a = a, si bien es redundante. En la lnea 129 se concatena ahora el texto " del C++", llamando al operador +=.

Finalmente se retorna del operador +, devolviendo Stmp por referencia, la cual se pasa como parmetro al operador =, que copia su contenido en la variable S3, lo cual se informa en la lnea 100. Lo nico que falta para terminar con la ejecucin de la lnea es destruir la variable TString temporaria, que contiene " del C++".

En la lnea 143 se muestra el string S3 resultante.

Finalmente, antes de finalizar la ejecucin del programa se destruyen las variables S1, S2, S3, y Stmp.

179

75.42 - Taller de Programacin

El nico inconveniente de este programa es la palabra static, que podra traer problemas en un sistema multitarea en el cual pudiesen ejecutarse dos operadores + simultaneamente, como podra ser UNIX (no es el caso de DOS ni de WINDOWS (en principio )). Para solucionar esto, simplemente basta que el operador + devuelva una copia del objeto, en vez del puntero al mismo, es decir:

122 123 124

TString& TString::operator+(TString &S2) { static TString Stmp;

desde ya que esto obligar a crear una copia del objeto, que luego habr que destruir, con lo cual el programa ser ms lento.

Objetos temporales

Finalmente queda un ltimo punto a considerar. Muchas funciones reciben parmetros de tipo char *, y sera bueno que nuestro tipo de dato (TString) tambin las soporte, es decir, que pueda llamar a estas funciones sin tener que crear una copia del dato. La solucin obvia para esto es definir una funcin que devuelva el puntero a la cadena de caracteres:

Una primer alternativa para hacer esto es la siguiente:

Ejemplo 25.5: Objetos temporales

class TString { private: char * Str; public: ... char * GetString (void) { return Str; } ... }; De esta forma podramos efectuar operaciones tales como:

char *Str; TString s; ... strcpy (Str, s.GetString()); Esta funcin GetString() puede definirse como un operador, para facilitar su uso:

Ejemplo 25.6: Sobrecarga de Casting

180

75.42 - Taller de Programacin class TString{ private: char * Str; public: ... operator char*() (void) { return Str; } ... }; De esta forma podramos efectuar operaciones tales como:

char *Str; TString s; ... strcpy (Str, s); Este cdigo es correcto. Sin embargo qu pasara si ejecutasemos un codigo como el siguiente:

Ejemplo 25.7: Problemas con sobrecarga

void MiFuncion (char * Str) { ... } { /* Este codigo es incorrecto */ TString s1,s2; s1 = "hola "; s2 = "mundo"; MiFuncion (s1 + s2);

Esto producira un error, por la siguiente razn: la llamada a MiFuncion (s1 + s2); es equivalente a llamar a: MiFuncion ( (s1 + s2).GetString() ); Analicemos en detalle este cdigo. La llamada a s1+s2 crea un objeto temporario de tipo TString: TString StringTemporario; StringTemporario = s1 + s2; A continuacin se llama a la funcin de casteo, GetString() de este objeto temporario: char * PunteroTemporario = StringTemporario; o lo que es lo mismo: char * PunteroTemporario = StringTemporario.operator char* ();

181

75.42 - Taller de Programacin

o lo que es lo mismo: char * PunteroTemporario = StringTemporario.GetString (); A continuacin SE DESTRUYE el StringTemporario, que ya "no se usa".

Finalmente se llama a MiFuncion(), con un puntero que qued apuntando a basura. Este es un error que puede producir la interrupcin del programa, pero si MiFuncion() es relativamente simple, en la prctica esto ocurre muy espordicamente, con lo cual este error puede ser muy difcil de encontrar, siendo un deber del programador tener mucho cuidado de no cometer este error.

La forma correcta de programar este cdigo es la siguiente:

TString s1, s2, s3; s1 = "hola "; s2 = "mundo"; s3 = s1 + s2 MiFuncion (s3);

Con el objetivo de solucionar este inconveniente, algunos compiladores (NO TODOS) mantienen las variables temporales hasta el final de la instruccin el la cual se los creo, es decir StringTemporario ser destruida inmediatamente despus de llamar a MiFuncion(), pero esto no es standard, y de ninguna manera solucionara errores como:

/* Este codigo es incorrecto */ TString s1, s2; char *Str; s1 = "hola "; s2 = "mundo"; Str = s1 + s2; MiFuncion (Str);

Como conclusin a esto simplemente debe tenerse en cuenta que no debe trabajarse con datos internos de variables temporales, ya que su destruccin est regida por el compilador.

Resumen de puntos a tener en cuenta

La creacin de objetos con memoria dinmica puede llevar a gran cantidad de confusiones y errores. Sin embargo el tema puede verse muy simplificado si se siguen los siguientes preceptos bsicos:

Crear un constructor de copia.

Sobrecargar el operador = (asignacin)

182

75.42 - Taller de Programacin Debe haber siempre un constructor, que inicialice todas las variables dinmicas.

Debe haber un destructor, que libere la memoria utilizada.

Nunca debe llamarse explicitamente un constructor de la clase. Estos deben llamarse automticamente por el compilador.

Todos los operadores que se definan que puedan recibir objetos del mismo tipo que la clase, deben poder soportar ser llamados por s mismo. Ej: a = a; a += a; a = a + a, etc.

Todas las funciones-operadores que retornen variables del tipo de la clase deben retornar las mismas por referencia, para evitar crear copias innecesarias del objeto.

Nunca obtener datos internos de variables temporales. Si no se tiene experiencia, limitar el uso de variables temporales a lo estrictamente necesario.

183

75.42 - Taller de Programacin

Captulo XXVI - Memoria compartida entre objetos


En la seccin anterior se vi todas las precauciones que deban tenerse al trabajar con constructores/destructores y memoria dinmica. Sin embargo, a veces se requiere ir ms all, compartiendo informacin entre variables del mismo tipo. Un ejemplo de esto es una base de datos, que debe ser accedida desde varios mdulos. Si la base est en disco, puede accederse a la misma haciendo algunos malabares y usando lockeo de registros, pero si la base est en memoria, no queda otra solucin ms que compartir un puntero a los datos. En ambos casos ser muy util poder compartir algunos datos ms, como por ejemplo la cantidad de registros.

Al momento de abrir una base por primera vez, deber crearse un conjunto de datos con los cuales manipular la informacin, los cuales debern ser compartidos por cada una de las objetos que manipulan la base. Ya que todos debern compartir estos datos, estos no podrn ser estticos a cada objeto, es decir, se tendr algo como:

class TBase{ TBaseData * Data; }; Estos datos debern ser inicializados en cada objeto, en el momento de llamar al contructor, y destruidos al llamar al destructor.

class TBase{ TBaseData * Data; TBase () { Data = ... } ~TBase () { delete (Data); };

Sin embargo, si los datos son compartidos por varias estructuras, no debern liberarse los datos, mientras alguna estructura los est utilizando. Concretamente lo que se hace, es definir un flag Referencias, que indica la cantidad de veces que se encuentra abierta una misma base, y sobrecargar los operadores de copia y de asignacin, para que corrijan este flag:

Ejemplo 26.1: Clase Base - Memoria compartida entre instancias de un objeto

class TBase{ TBaseData * Data; public: TBase () { /* Inicializo una nueva base */ Data = new ... Data->Referencias = 1; } ~TBase () { /* Abandono la base actual */

184

75.42 - Taller de Programacin Data->Referencias--; if (Data->Referencias == 0) delete (Data);

};

} TBase (TBase &NuevaBase) { /* Abandono la base actual */ Data->Referencias--; if (Data->Referencias == 0) delete (Data); /* Comienzo la nueva base */ Data = NuevaBase.Data; Data->Referencias++; } TBase& operator= (TBase &NuevaBase) { ... }

En este caso, la funcin operator=() ser similar al constructor TBase(TBase &), con la nica diferencia de que debe tenerse cuidado si se efectuan llamadas como Base = Base;

Esta forma de compartir informacin entre varias variables es muy poderosa y til, pero tambin peligrosa. Si cualquiera de estas funciones llega a fallar, el error puede ser muy difcil de localizar.

Si se trabaja con un programa multithread, debe realizarse lockeos antes de leer o modificar la variable Referencias, entre otras precauciones. Pero este tema queda fuera del alcance de este libro.

185

75.42 - Taller de Programacin

Captulo XXVII - Sobrecarga de operadores new y delete


Una lectura atenta de la seccin de operadores muestra que las palabras reservadas new y delete son consideradas operadores, y por lo tanto pueden ser sobrecargados. Sin embargo hay algunas restricciones:

Ambos operadores son estticos (an cuando no se lo especifique).

Puede existir un nico operador delete, pero puede existir ms de un operador new.

new debe recibir como primer parmetro un dato de tipo size_t y debe devolver un puntero a void.

delete debe recibir como primer parmetro un puntero de tipo void, y no puede devolver nada.

new se llama ANTES de reservar memoria para el dato, y por lo tanto no puede tener acceso a las variables de la clase (es por esto que es de tipo static).

Algunos ejemplos de esto son los siguientes:

Ejemplo 27.1: Sobrecarga de new y delete

class A { public: void * operator new (size_t size); void operator delete (void *dato); }; class B { public: void * operator new (size_t size); void * operator new (size_t size, int flags); void operator delete (void * dato, int flags); }; Puede sobrecargarse new y delete con propsitos tales como por ejemplo, en una lista dinmica, reservar con un nico malloc() memoria para el encabezado del elemento de la lista como para el dato. Veamos un ejemplo de esto:

Ejemplo 27.2: Sobrecarga de new y delete (2)

#include <stdio.h> #include <stdlib.h>

186

75.42 - Taller de Programacin #include #include #include #include <malloc.h> <iostream.h> <string.h> <conio.h>

class TNodo{ private: void *Dato; TNodo * Siguiente; public: void *operator new(size_t SizeNodo); void *operator new(size_t SizeNodo, size_t DatoSize); void operator delete (void *Dato) { free (Dato); } TNodo (void) { Dato = this+sizeof (TNodo); Siguiente = NULL; } }; // Este es el constructor que se llamar para reservar memoria para el nodo y el dato. void* TNodo::operator new(size_t SizeNodo, size_t DatoSize) { cout << "Reservando " << SizeNodo << "+" << DatoSize << " bytes\n"; return malloc (SizeNodo+DatoSize); } // Este es el constructor que se llamara normalmente al llamar a new TNodo; void* TNodo::operator new(size_t SizeNodo) { cout << "Reservando " << SizeNodo << " bytes\n"; return malloc (SizeNodo); } void main (void) { TNodo *Nodo; } Nodo = new (50) TNodo;

No suele ser una buena idea sobrecargar el operador new en objetos que sean exportados o estn disponible para el uso del programador, y si se lo hace, no debe modificarse la cantidad de memoria que reservan. El problema radica principalmente en su uso en listas y vectores. Estn disponibles varias implementaciones de listas y vectores que permiten almacenar cualquier tipo de objeto (templates), muchas de las cuales reservan memoria para el objeto sobrecargando el operador new o realizando operaciones tales como: malloc (sizeof (Objeto)).

Veamos un ejemplo que puede ayudar a refrescar algunos conceptos. Construyamos un objeto capaz de almacenar una lista de objetos de cualquier tipo. Desde ya, existen muchas tipos de implementaciones posibles, cada una de las cuales tendr sus ventajas y desventajas. Elegiremos como base para este ejemplo un vector:

Ejemplo 27.3: Template de vectores

187

75.42 - Taller de Programacin

template <class Type> class TVector { private: Type *Data; // Vector con los datos unsigned Total; // Nmero total de elementos almacenados public: /* Constructor de la clase */ TVector (void) { Data = NULL; Total = 0; } /* Destructor de la clase */ ~TVector (); /* Agregar un elemento a la clase */ void Add(Type &t); // Agregar un nuevo elemento al final del vector }; Este objeto permitir almacenar un vector de objetos del tipo Type.

Surge aqu un problema: Cmo hacemos para insertar un dato en esta lista?

Hay dos soluciones cada una de las cuales tiene sus ventajas y desventajas:

Alternativa 1:

/* Agregar un objeto a la lista (alternativa 1) */ template <class Type> void TVector<Type>::Add(Type &t) { Type *NewData; /* Reservo memoria para los nuevos datos */ NewData = new Type[Total+1]; /* Copio los datos existentes al nuevo vector */ if (Total) for (unsigned i = 0; i < Total; ++i) NewData[i] = Data[i]; /* Inserto el nuevo dato */ NewData[Total++] = t; /* Destruyo los datos originales y establezco el nuevo vector de datos */ delete []Data; Data = NewData;

El destructor correspondiente de la clase ser:

template <class Type> TVect<Type>::~TVect () { /* Destruir los objetos */ if (Data) delete []Data;

188

75.42 - Taller de Programacin } Esta alternativa es transparente y segura, independientemente del tipo de objeto que se est almacenando. Incluso no habra inconveniente en que el objeto original almacenado en la lista sobrecargase los operadores new y/o delete. La contrapartida es que cada vez que se inserte un elemento, se construir una copia de todo el vector y se llamarn a tantos constructores y destructores como elementos haya en la lista, con lo cual se vuelve bastante ineficiente.

Alternativa 2:

Si la memoria se reservase utilizando malloc(), podra modificarse el tamao del vector utilizando la funcin realloc(), sin que se deban construir copias y destruir los objetos ya almacenados.

/* Agregar un objeto a la lista (alternativa 2) Versin incorrecta */ template <class Type> void TVector<Type>::Add(Type &t) { Type *NewData; /* Reservo memoria para el nuevo dato */ NewData = (Type *)realloc (Data, sizeof (Type)*(Total+1)); /* Aumento el total */ ++Total;

Pero en este ejemplo no se llamar al constructor del nuevo elemento, y para que todo funcione bien, debo garantizar que esto suceda. Podra pensarse que simplemente podra llamarse al constructor del objeto en la forma: Data[Total].Type(); Pero esto no est permitido. La solucin consiste en sobrecargar el operador new, de forma tal que se llame al constructor, pero que este no reserve memoria. Ya que no hay forma de sobrecargar el constructor new de la clase original, deber sobrecargarse este operador en forma global.

static inline void* operator new (size_t, void *ptr) { return ptr; } La palabra reservada static se utiliz para que este operador slo est disponible en los archivos donde se utilice esta clase. La palabra reservada inline se utiliza para que el operador se expanda en lnea, en lugar se ser llamado como una funcin. Notar que este operador no reserva memoria, como sera de esperar.

Ahora bien escribamos la versin final del mtodo Add.

static inline void* operator new (size_t, void *ptr) { return ptr; } /* Agregar un objeto a la lista (alternativa 2) Versin incorrecta */ template <class Type> void TVector<Type>::Add(Type &t) {

189

75.42 - Taller de Programacin

Type *NewData; /* Reservo memoria para el nuevo dato */ NewData = (Type *)realloc (Data, sizeof (Type)*(Total+1)); /* Contruyo el objeto */ ::new ((void*)(Data+Total)) Type; /* Aumento el total */ ++Total;

El destructor correspondiente de la clase ser:

template <class Type> TVect<Type>::~TVect () { /* Destruir los objetos */ if (!Data) return; /* Llamar a los destructores para cada uno de los objetos insertados */ Type *p; for (p=Data;Total;--Total,++p) Data->~Type(); /* Liberar la memoria */ free (Data); } Esta alternativa es ms eficiente que la anterior, ya que no deben construirse y destruirse objetos innecesariamente. La contrapartida es que el objeto original no puede sobrecargar los operadores new y delete.

En las clases de vectores provistas por los compiladores de Borland C++(Compilador Borland C++ 5.00) se utiliza la primer alternativa. En las clases provistas por Microsoft Visual C++ (Compilador Microsoft Developer Studio - Visual C++ 4.0) se utiliza la segunda alternativa. Ambas libreras reservar memoria para varios elementos cada vez que esto es necesario, de tal forma que no se requiere efectuar estas operaciones cada vez que se inserta un nuevo elemento en el vector. Al trabajar se esta forma, en la alternativa 1 los objetos sern construidos por adelantado, al reservarse memoria para los mismos, en tanto que en la segunda alternativa sern construidos al ser insertados efectivamente en la lista.

190

75.42 - Taller de Programacin

Captulo XXVIII - Manejo de excepciones


El lenguaje C++ define una nueva y muy poderosa forma de manejar las excepciones, entendiendo por excepciones toda instruccin que no puede ser ejecutada por el procesador. Ejemplos de excepciones son divisiones por cero, overflow, underflow, accesos a memoria en zonas prohibidas, en algunos sistemas operativos acceso a datos desalineados en memoria, etc. Las excepciones son manejadas normalmente por el sistema operativo, quien en la mayora de los casos implementa la interrupcin inmediata del programa.

En C++ existen varias palabras reservadas utilizadas en el manejo de excepciones, entre las cuales se encuentran try y catch, y que permiten muy fcilmente implementar el manejo de excepciones.

Ya que la forma en que se producen y manejan las excepciones est muy relacionado con el sistema operativo, si bien ANSI C++ define la forma en que deben procesarse estas, pueden haber variantes. Por ejemplo, Microsoft define adems las palabras reservadas __try y __catch, con nuevas funcionalidades.

Nota: muchas de las palabras reservadas utilizadas en esta seccin estn redefinidas por Microsoft con un doble underscore; Ej: try, catch, except y leave est definidas como __try, __catch, __except y __leave.

La forma en que se implementa el manejo de excepciones es como sigue:

try{ cdigo } catch (argumento) { ... } El cdigo encerrado en try es cualquier cdigo en C++ cuyas excepciones se desean controlar y que puede incluir otros manejadores de excepciones.

La sentencia catch puede recibir un nico argumento, y define la forma en que la excepcin debe ser controlada. Pueden definirse ms de una sentencia catch. Por ejemplo:

Ejemplo 28.1: Manejo de excepciones

#include <stdlib.h> #include <stdio.h> #include <iostream.h> int Division (int a, int b) { return a / b; } int main(void) {

191

75.42 - Taller de Programacin

int r; cout <<"Comenzando\n"; try{ r = Division (10, 0); cout << "r=" << r << "\n"; } catch (char * str) { cout <<"Error:" << str << "\n"; } catch (int x) { cout <<"Error:" << x << "\n"; } catch (...) { cout << "Error desconocido\n"; } cout << "Fin del programa"; return 0;

La salida de este programa es:

Comenzando Error desconocido Fin del programa El programa comienza creando una variable entera llamada r. A continuacin se llama a la funcin Divisin, pero la divisin por cero generar automticamente una excepcin. Si no se hubiese definido el controlador de excepcines, esta sera controlada por el sistema operativo, y el programa sera interrumpido. Pero en este caso se verificar primero si alguno de los manejadores de excepciones definidos a continuacin del bloque try{}, puede manejar esta excepcin. La ltima de ellas catch(...) es un controlador genrico, por lo que se imprimir "Error desconocido" en pantalla. El programa continuar a continuacin desde el final del bloque try, con lo cual la lnea cout << "r=" << r << "\n"; no ser ejecutada.

Notar que en el programa se definen 3 sentencias catch, o sea 3 controladores de excepciones. Cul de ellos ser llamado? Rta: El primero que se pueda. Es decir que si se hubiese definido catch(...) antes que los otros dos, estos ltimos jams seran llamados.

Puede utilizarse la palabra reservada throw para forzar al generacin de una excepcin. Por ejemplo podramos haber escrito nuestra funcin Division como sigue:

Ejemplo 28.2: Generacin de excepciones

int Division (int a, int b) { if (b == 0) throw "Divisin por cero"; return a / b;

192

75.42 - Taller de Programacin } En cuyo caso la salida del programa hubiese sido:

Comenzando Error: Divisin por cero Fin del programa Es posible atrapar tambin otras exepciones, como por ejemplo accesos a zonas prohibidas. Por ejemplo, el siguiente programa no sera interrumpido por el sistema operativo, ni sera incorrecto:

Ejemplo 28.3: Manejo de excepciones (2)

#include <stdlib.h> #include <stdio.h> #include <iostream.h> void Funcion (int *p) { *p = 2; } int main(void) { int r; cout <<"Comenzando\n"; try{ Funcion(NULL); } catch (...) { cout << "Error desconocido\n"; } cout << "Fin del programa"; return 0;

La salida de este programa es:

Comenzando Error desconocido Fin del programa Qu sucedera si hubiesemos definido la funcin Funcin como sigue?

Ejemplo 28.4: Excepciones y variables locales

class T { public:

193

75.42 - Taller de Programacin

};

T() { ~T() {

cout <<"Construyendo T\n"; cout <<"Destruyendo T\n"; }

void Funcion (int *p) { T t; *p = 2; cout << "*p=" << *p;

Rta: La salida del programa hubiese sido:

Comenzando Construyendo T Destruyendo T Error desconocido Fin del programa Esto ilustra otra de las caractersticas que hacen la programacin en C++ muy poderosa: antes de llamarse a un controlador de excepciones se llama primero a los destructores de todas las variables locales. Esto permitira a un programa, por ejemplo, cerrar correctamente todos los archivos abiertos, y finalizar todas las tareas pendientes antes de reportar un error fatal o interrumpir el proceso.

Es posible tambin definir controladores para excepciones particulares. Para ello se utiliza la palabra reservada except. Veamos un ejemplo:

Ejemplo 28.5: Manejo de excepciones (except)

void main (void) { // En Microsoft debe usarse __try en vez de try try{ int a = 2; int b = 0; a = a / b; } // En Microsoft debe usarse __except en vez de except __except (GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) { /* GetExceptionCode() no forma parte del standard ANSI Las definiciones de excepciones, tales como EXCEPTION_INT_DIVIDE_BY_ZERO pueden variar de un sistema a otro. */ } cout << "Entero dividido cero\n";

Est prohibido utilizar una instruccin goto de afuera hacia adentro de un bloque try, y viceversa. Para salir inmediatamente de un bloque try puede utilizarse la palabra reservada leave.

194

75.42 - Taller de Programacin

Ejemplo 28.6: Manejo de excepciones (leave)

try{ int a, b; a = 2; b = 0; if (b == 0) leave; a = a / b; // Si b es igual a 0 esta instruccin jams ser ejecutada. } catch (...) { cout <<"Error desconocido"; // Este controlador tampoco ser llamado. } Existen varias funciones relacionadas con excepciones, tales como RaiseException(), pero no forman parte del standard y sus definiciones pueden variar mucho de un compilador/sistema operativo a otro. Para ms informacin consultar el manual del compilador.

195

75.42 - Taller de Programacin

APNDICE I - Palabras reservadas de C


Palabras reservadas de C auto asm break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while

APNDICE II - Palabras reservadas de C++


Palabras reservadas de C++ asm auto bad_cast bad_typeid break case catch char class const const_cast continue default delete do double dynamic_cast else enum except extern finally float for friend goto if inline int long namespace new operator private protected public register reinterpret_cas t return short signed sizeof static static_cast struct switch template this throw try type_info typedef typeid union unsigned using virtual void volatile while xalloc

196

75.42 - Taller de Programacin

197

75.42 - Taller de Programacin

Apndice III - Caracteres reservados


Carcter Carcter nulo Alert Backspace Tabulacin horizontal Nueva lnea Tabulacin vertical Formfeed Retorno de carro Comilla doble Comilla simple Signo de pregunta Backslash Nro octal Nro hexa Respresentacin NUL BEL BS HT NL VT FF CR " ' ? \ ooo hhh Valor dec. 0 7 8 9 10 11 12 13 34 39 63 92 --Valor hexa 0 7 8 9 A B C D 22 27 3F 5C --Secuencia escape \0 \a \b \t \n \v \f \r \" \' \? \\ \ooo \xhhh

198

75.42 - Taller de Programacin

Apndice IV - EL PREPROCESADOR
Macros predefinidas

__DATE__ __FILE__ __LINE__ #line __TIME__ __TIMESTAMP__

Fecha de compilacin (Formato: Mmm dd yyyy) Archivo actual, incluyendo path Lnea actual. Este valor puede ser alterado con la directiva String con la hora de compilacin (Formato: hh:mm:ss) Fecha y hora de la ltima modificacin de cdigo fuente.

Si bien no forma parte del standard, la mayora de los compiladores C++ definen tambin la macro __cplusplus.

Directivas del preprocesador

La directiva #if, junto con #else, #elif y #endif controlan la compilacin de una parte del programa. Si la expresin que sigue a #if resulta verdadera, la porcin de cdigo siguiente ser compilada. En caso contrario, ser ignorada. Existen otras directivas tales como: #elif #ifdef #ifndef #undef #define #import #line permite evaluar una nueva expresin. CTE es similar a #if defined(CTE) CTE es similar a #if !defined(CTE) permite anular una definicin de una constante. permite definir macros y constantes es utilizada para incluir informacin de una librera de tipos. Es utilizada al trabajar con componentes. permite modificar el nmero de lnea actual, para la macro __LINE__

es utilizada para incluir un archivo dentro de otro. Si se utilizan los smbolos <> se buscar el archivo en el #include o los directorios include. Si se utiliza "" se buscar el archivo en primer lugar en el directorio actual, y luego en los directorios include. #pragma permite modificar opciones de compilacin, tal como activar y desactivar warnings, e indicar al linker que utilice determinadas libreras.

199

75.42 - Taller de Programacin

Apndice V - Mezclando cdigo C y C++


Existen algunos inconvenientes al mezclar libreras escritas en C con otras escritas en C++. Si bien el C++ constituye una extensin del lenguaje C, existen algunas diferencias que deben tenerse en cuenta. Desde ya que no es posible utilizar clases u objetos dentro de cdigo en C, pero tampoco puede llamarse directamente a funciones en C desde cdigo en C++, ni vinceversa. El problema radica en que la forma en que se implementa la llamada a una funcin en C++ es diferente a la que se utiliza en C.

Si se dispone el cdigo fuente de las libreras, pueden transformrse los archivos en .c en archivos .cpp, obligando as al compilador a utilizar el standard de C++. Esto puede resultar un tanto tedioso, y slo es posible si se dispone del cdigo fuente de las libreras, lo cual no siempre es posible.

Existe una nica forma para solucionar este inconviente, y especificar a en todas las funciones que vayan a compartirse (tanto en libreras C como C++) que debe utilizarse el standard C para llamada a funciones. Esto se hace anteponiendo extern "C" a la declaracin de toda funcin, en los archivos .H, al compilar el cdigo en C++.

El preprocesador define una constante llamada __cplusplus que permite saber si se est compilando en lenguaje C o en C++. Para lograr fcilmente una compatibilidad total, basta agregar a todos los archivos .H a compartir entre cdigo C y C++ las siguientes lneas:

Ejemplo A.5.1: Mezclando cdigo C y C++

/* Comienzo del archivo .h */ #ifdef __cplusplus extern "C" { #endif ... Toda las declaraciones de funciones del archivo.h ... ... No hay ningn inconveniente en que estn tambin aqu las declaraciones de tipos de datos, variables, constantes, macros, etc ... #ifdef __cplusplus } #endif /* Fin del archivo .h */ De esta forma, ya sea que el archivo .h sea incluido en un archivo .c o .cpp, se definir la que las funciones debern llamarse utilizando el stantard C.

Es decir que para mezclar libreras y cdigo en C con C++ basta con agregar estas lneas al comienzo y al final de todo archivo .h.

200

75.42 - Taller de Programacin Ya que existe mucho cdigo escrito en C, es muy probable que en programas de gran envergadura se deban mezclar libreras en ambos lenguajes. Incluir cdigo C en programas en C++ es muy simple, como ya se vi. Pero utilizar libreras en C++ dentro de programas en C no lo es, y generalmente resulta muy tedioso y conduce a programas poco claros. La mejor forma de hacer esto es programar una capa de conversin de la librera C++ a una librera en C. Por ejemplo, si tuviesemos que adaptar nuestra clase cola para trabajar en un programa C, podramos hacer lo siguiente:

Ejemplo A.5.2: Mezclando cdigo C y C++ (2)

/* Cdigo en C */ { void *pCola; InicializarCola (&pCola); /* Inicializo la cola */ ColaPush (pCola, 20); /* Insertar el nmero 20 en la cola */ } /* Cdigo en C++: Adaptacin de la clase cola a un programa C */ void InitCola (void **pCola) { TCola *Cola; Cola = new TCola; // Creo la cola *pCola = (void *)Cola; // Devuelvo la cola } int ColaPush (void *pCola, int n) { TCola *Cola; Cola = (TCola *)pCola; // Convierto el puntero a void en un puntero a la clase return Cola.Push (n); // Inserto un elemento en la cola }

201

75.42 - Taller de Programacin

Apndice VI - Resumen de funciones especiales de C++


A continuacin se muestra una tabla que resume todas las funciones especiales que pueden existir en una clase, y su comportamiento. Funcin constructor de copia constructor por defecto otros constructores destructor conversin = (asignacin) operador= () [] -> new delete otros operadores otras funciones miembro otras funciones friend pueden heredarse no no no no si no si si si si si si si si no pueden ser virtuales no no no si si si si si si si no no si si no pueden retornar un valor no no no no no si si si si si void* void si si si funciones miembro o amigas (friend) miembro miembro miembro miembro miembro miembro cualquiera miembro miembro miembro miembro (static) miembro (static) cualquiera miembro friend generadas por defecto si si no si no si no no no no no no no no no

202

75.42 - Taller de Programacin

PARTE IV WINDOWS API


Captulo I - Introduccin a la programacin bajo Windows
Introduccin

El sistema operativo Windows plantea una nueva forma de interaccin de los programas con el usuario, donde el modelo de programacin utilizado en DOS resulta poco adecuado. En primer lugar, no habr un nico programa en ejecucin, como lo haba bajo DOS, sino un conjunto de programas, mejor llamados aplicaciones (desde el punto de vista del sistema operativo, procesos) los cuales debern interactuar entre s. Se suma a esto que toda aplicacin deber procesar (en tiempo real) una serie muy grande de eventos, tales como teclado, mouse, timers, etc. Esta situacin lleva al desarrollo de programas extensos, y plantea la necesidad de una programacin clara y ordenada. Es por ello que la Programacin Orientada a Objetos resulta ideal para desarrollar programas bajo Windows, si bien no es indispensable.

Esta es la razn por la cual varias empresas como Borland y Microsoft han desarrollado bibliotecas muy extensas de objetos, que crean niveles de abstraccin superiores, de tal forma que pocas veces se requiere descender hasta el nivel ms bajo e interactuar directamente con el API (Aplication Program Interface) de Windows, tema que desarrollaremos brevemente a continuacin.

Con el objetivo de facilitar la familiarizacin con el ambiente Windows, se darn en este captulo definiciones poco rigurosas, que sern redefinidas en captulos posteriores.

La filosofa de programacin Windows

Nos centraremos en la programacin 32 bits (Windows 95/98/2000/NT). Los sistemas operativos Windows de 16 bits (Windows 3.1 y anteriores), adems de ser muy poco utilizados en la actualidad, presentaban una serie de inconvenientes para la programacin, como el reducido tamao de la cola (stack), de tan slo algunos KBytes.

La programacin bajo el sistema operativo Windows, plantea una nueva forma de disear y concebir los programas. Este nuevo estilo de programacin, lejos de ser complejo como lo puede parecer en un comienzo, resulta muy prctico al momento de disear programas de gran envergadura. Pero un programa para Windows debe ser pensado en una forma completamente distinta a un programa bajo DOS, fundamentalmente porque el mismo no estar corriendo en forma independiente, sino en un sistema multitarea nativo, y deber interactuar tanto con otros programas como con el sistema operativo mismo. Este hecho da origen a un nuevo paradigma de programacin, conocido como Programacin Orientada a Eventos. Un evento podra ser, por ejemplo, la presin de una tecla, la llegada de datos a un dispositivo o un comando para que un ventana se redibuje. Este nuevo modelo de programacin puede ser aplicado tambin en otros ambientes, y no constituye una ruptura con los modelos de programacin estructurada ni orientada a objetos (como ocurra entre la primera y la programacin imperativa) sino una extensin de los mismos.

Un programa para Windows puede ser escrito tanto en C como en C++. Sin embargo, por las caractersticas del sistema, el lenguaje C++ resulta especialmente adecuado por su capacidad de trabajar con objetos y encapsulamiento del cdigo, que permiten manejar niveles superiores de abstraccin e independizarse de la implementacin concreta de los algoritmos.

203

75.42 - Taller de Programacin

De hecho, como ya se ver, Windows mismo ya provee un primer nivel de abstraccin, y varios "objetos" bsicos, con los que se puede trabajar a travs de handles (identificadores : enteros de 32 bits), recordemos que en C no existen objetos propiamente dichos. Pero para comprender la necesidad de este nuevo paradigma de programacin, veamos en qu difiere la programacin bajo Windows de la programacin habitual bajo DOS. Y para ello, antes es necesario saber qu es "Windows".

Qu es Windows?

Desde el punto de vista del programador, Windows (al igual que DOS) no es ms que un gran conjunto de funciones, que puede utilizar un programa. Es a este conjunto de estas funciones al que se conoce como API de Windows (como mencionamos antes, son las siglas de Application Program Interface y estn programadas en C). Esta concepcin todava sigue siendo muy limitada a lo que era el DOS. En el ambiente Windows es necesario ampliarla diciendo que es tambin un proceso o conjunto de procesos que pueden comunicarse con nuestra aplicacin o proceso.

Muy bien, pero qu hay de nuevo?

Para responder esta pregunta, veamos un ejemplo, que puede ayudar a entender la idea. Si alguien dijese que tiene un cdigo en C/C++, la primer pregunta que un programador, habituado a trabajar en DOS, probablemente hara es QU HACE?. En lugar de ello, un programador para WINDOWS podra preguntar A QU RESPONDE? QU IMPLEMENTA? Esto marca la principal diferencia de la programacin entre ambos ambientes. S bien un cdigo en particular puede estar pensado para "hacer algo", un programa (o aplicacin) bajo Windows no se concibe como un programa que "haga", sino como un conjunto de funciones, muchas veces agrupadas en objetos, que responden a eventos externos, generalmente mensajes, pudiendo estos generar nuevos eventos.

Un programa bajo DOS se compone de una serie de funciones, que incluyen llamadas al sistema operativo, del cual recibe respuestas. Un ejemplo de esto es: la llamada: "Abrir un archivo de disco", de la cual se recibe como respuesta un identificador con el cual acceder al mismo, o un cdigo de error.

Una aplicacin bajo Windows debe concebirse como un programa que puede enviar mensajes a Windows o a otras aplicaciones, de los cuales recibir respuestas (ya veremos ms adelante qu es esto), pero tambin puede recibir mensajes de Windows u otras aplicaciones, a los cuales debe responder. Siguen existiendo las llamadas convencionales al sistema operativo, en la forma que existan en DOS.

204

75.42 - Taller de Programacin

Es precisamente la interfaz (API) quien define a los miembros de la familia de sistemas operativos basados en Windows. Desde el punto de vista del programador, se trata de un conjunto de rutinas (funciones, estructuras, mensajes e interfases consistentes y uniformes para todas las plataformas de 32 bits basadas en Windows) que llevan a cabo servicios de bajo nivel ejecutados por el sistema operativo y las aplicaciones usan para interpretar, enviar y responder mensajes. Mediante su uso, se pueden desarrollar aplicaciones que corran con xito en todas esas plataformas siendo, adems, capaces de sacar ventajas de las caractersticas y capacidades nicas de cada una de ellas. Las distintas implementaciones de los programas dependen de las capacidades de las caractersticas subyacentes en cada una de las plataformas; siendo la diferencia mas notable que algunas funciones llevan a cabo sus tareas slo en las plataformas ms poderosas (por ejemplo, las funciones de seguridad slo responden completamente bajo los sistemas operativos Windows NT). Muchas de las diferencias son, en realidad, limitaciones del sistema, como las restricciones sobre la cantidad de items que una dada funcin puede manejar.

Queda ms claro ahora qu se debe entenderse cuando se dice que los sistemas operativos basados en Windows estn manejado por eventos. Cada vez que un evento ocurre, el sistema operativo enva mensajes a los procesos relevantes. Por lo tanto, un programa basado en Windows recibe mensajes, los interpreta y toma la accin apropiada.

En este contexto, un mensaje es entonces un paquete de datos usado para comunicar una informacin o proveer una respuesta. Los mensajes pueden pasarse entre el sistema operativo y una aplicacin, entre diferentes aplicaciones, entre hebras dentro de una aplicacin y entre ventanas dentro de una aplicacin.

Veamos otro ejemplo. Supongamos un programa que requiera realizar un lazo en forma repetida para realizar un clculo complejo, el cual requiere mucho tiempo para completarse. En un programa DOS, la ejecucin del programa seguira un esquema como el siguiente:

Bajo Windows, este esquema, (si bien puede utilizarse), hara que el programa deje de responder a mensajes externos mientras est realizando el clculo (hay formas de solucionar esto, volveremos sobre esto mas adelante). Un programa

205

75.42 - Taller de Programacin

bajo Windows debe en todo momento responder a eventos externos, y si el lazo requiere mucho tiempo para su ejecucin, deben tomarse medidas para la aplicacin contine respondiendo a mensajes externos. Hay bsicamente 3 formas de hacer esto, las cuales se ver ms adelante. La ms simple consiste en implementar el ciclo de tal forma que se cierre dentro del sistema operativo, y fuera de la funcin que lo est realizando.

Para ver la necesidad de cambiar la forma de programacin convencional a este nuevo esquema intentemos resolver el siguiente problema:

Se requiere disear un programa (para DOS) que imprima en pantalla los caracteres presionados. Al mismo tiempo, el programa debe emitir un 'beep' cada un segundo y debe controlarse constantemente el estado de una mquina. Si esta llega a fallar debe notificarse al operario inmediatamente. Finalmente si se presiona ESC el programa debe terminar.

El programa se podra concebir de muchas formas, pero una bastante clara consiste en separar la verificacin de los eventos (timer, teclado, verificacin de alarmas) con el procesamiento de los mismos. Podra escribirse una funcin diferente para procesar cada evento; pero si estas dos tareas (deteccin de eventos y respuesta a los mismos) fuesen desarrollados por dos grupos independientes, y ambas partes previeran un crecimiento importante, una forma clara de solucionar esto es el desarrollo de una nica funcin genrica de procesamiento de eventos. As, una forma de escribir el programa es la siguiente: /* Archivos a incluir */ #include #include #include #include #include #include /* Mensajes procesables */ #define COMENZANDO 1 #define FINALIZANDO 2 #define TECLA 3 #define TIMER 4 #define ALARMA 5 int ProcesarMensaje (int mensaje, int param) { switch (mensaje) { case COMENZANDO: clrscr(); printf ("Comenzando\n"); break;

206

75.42 - Taller de Programacin case FINALIZANDO: printf ("\nPrograma finalizado\n"); break; case TECLA: if (param == 27) return 1; putch (param); break; case TIMER: sound (440); delay(100); nosound (); break; case ALARMA: printf ("ALARMA %u\n", param); break;

} int VerificarMaquina(void) { /* Verificar el estado de la mquina y devolver cualquier error que se detecte */ return 0; /* Devuelvo OK */ } void main(void) { int salir = 0; time_t t1; int Alarma = 0; ProcesarMensaje (COMENZANDO, 0); do{ /* Verifico el estado del teclado */ if (kbhit()) salir = ProcesarMensaje (TECLA, getch()); /* Verifico si se debe emitir un timer */ if (time(NULL) != t1) { t1 = time (NULL); salir = ProcesarMensaje (TIMER, 0); } /* Verifico el estado de la maquina */ Alarma = VerificarMaquina(); if (Alarma) salir = ProcesarMensaje (ALARMA, Alarma); }while (!salir); ProcesarMensaje (FINALIZANDO, 0);

} return 0;

Esta nueva forma de programacin se la conoce con el nombre de Programacin Orientada a Eventos, y las funciones que procesan los mensajes (como ProcesarMensaje() en nuestro ejemplo) se denominan funciones callback. Queda claro ahora porqu la funcin ProcesarMensaje debe requerir un tiempo de procesamiento despreciable, o impedir procesar otros eventos en forma "simultnea".

En Windows, el ciclo de la funcin main encargado de verificar y generar los eventos est fundamentalmente a cargo del sistema operativo, quien notifica a nuestra aplicacin los eventos ocurridos mediante una cola de mensajes. La funcin ProcesarMensaje() debe procesar este mensaje y retornar el control a Windows inmediatamente.

207

75.42 - Taller de Programacin

En resumen, todo programa bajo Windows debe concebirse como una serie de componentes (funciones en C) que interactan entre s a travs de mensajes y respuestas.

208

75.42 - Taller de Programacin

Captulo II - Comenzando
Comenzando

Un programa bsico pensado para correr en sistemas operativos basados en Windows tiene generalmente tres elementos primarios:

una ventana

un "bombeador" de mensajes (en realidad, un lazo de mensajes)

un procesador de mensajes.

Pese a que una ventana es pensada comnmente en trminos de un despliegue visual, tambin puede estar definida como no visible; por ejemplo, si se est programando una aplicacin sin interfaz de usuario, generalmente se utiliza una ventana no visible para procesar mensajes; cada ventana tiene un identificador de ventana (hwnd) asociado con un procesador de mensajes que identifica los mensajes para esa ventana.

El "bombeador" de mensajes es un lazo simple que corre continuamente mientras corre la aplicacin recibiendo mensajes y despachndolos a un procesador de mensajes apropiado; cuando ocurren eventos que generan mensajes, el sistema operativo coloca los mensajes en una cola de mensajes.

En Windows es posible crear en un mismo programa varios puntos o hebras de ejecucin (threads) en un mismo proceso o aplicacin. Esta constituye una caracterstica muy poderosa que pocos sistemas operativos proveen. Al crear un programa con varias threads (multithread) deben realizarse algunas correcciones o aclaraciones a las definiciones de este captulo, las cuales se vern ms adelante.

Windows define una serie de elementos o pseudo-objetos (no confundir con objetos de C++, en C no existen objetos, y por lo tanto no hay forma de que Windows los provea), que pueden manipularse a travs de handles (identificadores), que en la prctica son, como mencionamos, enteros de 32 bits. Entre los ms comunes se encuentran: HINSTANCE HWND HICON HDC HMENU HBITMAP HPEN HBRUSH Instancia de un programa. A partir de ahora hablaremos de un proceso. Windows identifica cada proceso que est corriendo con un nmero entero. Ventana. (ya lo mencionamos mas arriba) Icono Contexto para dibujar en pantalla Men de una aplicacin Bitmap Lpiz Trama para pintar

209

75.42 - Taller de Programacin

De todos estos handles, el fundamental es HWND, utilizado para identificar una ventana. Una ventana puede recibir y transmitir mensajes de/hacia a otras ventanas o desde Windows, y este es el sistema de comunicacin principal y fundamental de Windows.

Todo mensaje se compone de 4 parmetros (actualmente todos ellos son enteros de 32 bits): hWnd uMsg wParam lParam Ventana destino Mensaje Word Parameter (primer parmetro auxiliar) Long Parameter (segundo parmetro auxiliar)

Por esta razn, todo programa capaz de recibir notificaciones de Windows generalmente crea al menos una ventana (pudiendo esta, como decamos, ser invisible).

Toda ventana se compone de una gran cantidad de parmetros que la definen, entre los cuales se encuentran la identificacin de la ventana padre (volveremos sobre esto mas adelante) y la funcin que controlar los mensajes de las ventanas. Debe hacerse una aclaracin en este punto. En Windows, una ventana es una entidad abstracta definida como tal, representable en pantalla mediante un rectngulo, capaz de procesar mensajes. En la prctica, casi todos los elementos constitutivos de una ventana (botones, casilleros de edicin, listados, iconos, bitmaps, animaciones, etc) son a su vez ventanas, e incluso hay ventanas que no se dibujan en pantalla. Por esta razn podra ampliarse la definicin de ventana diciendo que es, tambin, una entidad abstracta que implementa un "objeto" y todo el cdigo relacionado con l. Notar que la definicin de ventana difiere de la que habitualmente utiliza un usuario.

La razn de esto es muy simple. Sera una tarea sumamente compleja definir en cada ventana el cdigo necesario para procesar los mensajes provenientes de cada uno de los elementos y botones que esta tenga. Adems, todos los elementos del mismo tipo, (botones, listados, etc), se comportarn de la misma forma. Por esta razn es muy simple trabajar estos elementos como nuevas ventanas, que tendrn el cdigo necesario para dibujarse e interactuar con el ambiente Windows a travs de mensajes. La tarea del programador se limita en tal caso a agregar el cdigo necesario para integrar este elemento con el resto de la aplicacin, y de querer hacerlo, modificar el funcionamiento por defecto de este objeto o elemento.

Llega entonces el momento de definir correctamente una ventana:

Una ventana es una entidad abstracta, compuesta por varios parmetros, cuyas caractersticas fundamentales son capacidad para recibir y procesar mensajes y desplegarse en pantalla.

Finalmente, debe agregarse que, a diferencia de un programa en DOS, un programa para Windows se compone, adems del cdigo ejecutable, de recursos (resources), que pueden ser cosas muy diversas (ventanas de dialogo, bitmaps, iconos, tablas de textos, cursores, animaciones, tabla de aceleradores, versin del programa y cualquier tipo de dato que al programador le pueda resultar til incluir) pero los fundamentales son las ventanas de dialogo. Estos recursos deben incluirse con el cdigo fuente en el momento de compilar el programa, en un archivo de texto, llamado resource script, cuya extensin es .rc.

Todo programa Windows se compone, adems del cdigo fuente, de recursos (informacin de cualquier tipo), definida en un archivo denominado 'resource script', que debe ser compilado junto con el programa.

210

75.42 - Taller de Programacin Slo puede existir un nico archivo de recursos en un programa, pero este puede incluir a su vez referencias de otro. Ver documentacin del entorno para ms informacin.

Antes de continuar con definiciones, veamos un ejemplo de un programa Windows, para facilitar la familiarizacin con el entorno.

Primer Ejemplo de programa Windows

Este programa se compone de tres archivos:

ejemplo1.c: Cdigo fuente principal del programa

ejemplo1.rc: Recursos de la aplicacin

resource.h: Declaraciones de recursos

Los archivos ejemplo1.rc y ejemplo1.h se generan en forma visual, utilizando un editor de recursos, provisto junto con el compilador.

Al igual que al programar bajo DOS, al desarrollar un programa compuesto de varios archivos es necesario crear un proyecto. Tanto Borland C++ como Visual C++, permiten crear varios tipos de proyectos Windows. En este caso deberemos crear un proyecto vaci. Esta operacin depende del compilador utilizado, y se hace como sigue: Borland C++ 5.0 Debe seleccionarse la opcin "Nuevo proyecto", y agregar los archivos ejemplo1.c y ejemplo1.rc al proyecto.

Visual C++ Debe seleccionarse la opcin "File" - "New" - "Project Workspace" - "Application". Utilizando el men 4.0 "Insert" - "Insert Files Into Project" deben insertarse los archivos ejemplo1.c y ejemplo1.rc. Debe seleccionarse la opcin "File" - "New" - "Project" - "Win32 Application". Una vez creado el Visual C++ proyecto, presionando el botn derecho del mouse sobre carpeta "FileView-Source Files" y seleccionando 6.0 la opcin "Add files to project" deben insertarse los archivos ejemplo1.c y ejemplo1.rc. Los archivos .c, .cpp y .rc deben agregarse al proyecto, los .h no. Ciertos entornos de programacin, como Visual C++ 6.0 permiten incluir los .h en una seccin especial del proyecto, Header files, si bien esto slo se por claridad, pero no se tiene en cuenta al compilar.

Archivo EJEMPLO1.C 01 02 03 04 05 #include <windows.h> #include "resource.h" /* Instancia del programa */ HINSTANCE inst;

211

75.42 - Taller de Programacin

06 07 LONG CALLBACK FuncionPrincipal (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) 08 { 09 char Texto [1000]; 10 int Ret; 11 12 switch (wMsg) 13 { 14 case WM_INITDIALOG: 15 /* Este mensaje se enva a la ventana en el momento de crearla, 16 justo antes de mostrarla en pantalla */ 17 break; 18 case WM_COMMAND: 19 /* Este mensaje se enva a la ventana cada vez que se presiona un 20 botn (entre otras cosas) */ 21 switch (LOWORD(wParam)) 22 { 23 case IDC_SALIR: 24 PostQuitMessage (0); 25 break; 26 case IDC_AGREGAR: 27 SendMessage (GetDlgItem(hWnd, IDC_EDIT1), WM_GETTEXT, sizeof (Texto), (LPARAM)Texto); 28 SendDlgItemMessage (hWnd, IDC_LIST1, LB_ADDSTRING, 0, (LPARAM)Texto); 29 break; 30 } 31 break; 32 case WM_CLOSE: 33 /* Este mensaje se enva cuando el usuario presiona el botn [X] 34 para cerrar la ventana. Se debe devolver 0 si no 35 se permite cerrar, y 1 en caso afirmativo */ 36 Ret = MessageBox (hWnd, "Realmente desea salir?", "Saliendo...", 37 MB_YESNO|MB_ICONQUESTION); 38 if (Ret != IDYES) 39 return 0; 40 PostQuitMessage (0); 41 return 1; 42 break; 43 } 44 /* Una ventana debe devolver 0 si no procesa el mensaje */ 45 return 0; 46 } 47 48 49 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE prev_instance, LPSTR cmdline, int cmdshow) 50 { 51 MSG msg; 52 inst = hInstance; 53 54 /* Crear una ventana de dialogo (Ventana fundamental de la aplicacin) */

212

75.42 - Taller de Programacin 55 CreateDialog (inst, MAKEINTRESOURCE (IDD_DIALOG1), NULL, (DLGPROC) FuncionPrincipal); 56 57 while (GetMessage (&msg, NULL, 0, 0)) 58 { 59 TranslateMessage (&msg); 60 DispatchMessage (&msg); 61 } 62 63 return msg.wParam; 64 } Archivo RESOURCE.H //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by Ej01.rc // #define IDD_DIALOG1 101 #define IDC_EDIT1 1000 #define IDC_AGREGAR 1001 #define IDC_LIST1 1002 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE #define _APS_NEXT_COMMAND_VALUE #define _APS_NEXT_CONTROL_VALUE #define _APS_NEXT_SYMED_VALUE #endif #endif Archivo EJEMPLO1.RC

102 40001 1003 101

Este archivo contiene los recursos de la aplicacin, en esta caso, un cuadro de dilogo. Este archivo se genera en forma visual, utilizando un editor de recursos, provisto junto con el entorno de desarrollo o integrado al mismo. //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by ejemplo1.rc // #define IDD_DIALOG1 101 #define IDD_DIALOG2 102 #define IDC_EDIT1 1001 #define IDC_LIST1 1002 #define IDC_BUTTON1 1003 #define IDC_AGREGAR 1003 #define IDC_SALIR 1004 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 103 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1005 #define _APS_NEXT_SYMED_VALUE 101 #endif

213

75.42 - Taller de Programacin

#endif //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // Spanish (Mexican) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ESM) #ifdef _WIN32 LANGUAGE LANG_SPANISH, SUBLANG_SPANISH_MEXICAN #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_DIALOG1 DIALOGEX 0, 0, 263, 95 STYLE DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Ejemplo de un programa bajo Windows" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "Salir",IDC_SALIR,7,74,50,14 EDITTEXT IDC_EDIT1,7,7,110,14,ES_AUTOHSCROLL LISTBOX IDC_LIST1,133,7,123,82,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP,WS_EX_RIGHT PUSHBUTTON "-- Agregar ->",IDC_AGREGAR,7,33,60,14 END IDD_DIALOG2 DIALOG DISCARDABLE 0, 0, 186, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Dialog" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,129,7,50,14 PUSHBUTTON "Cancel",IDCANCEL,129,24,50,14 PUSHBUTTON "Button1",IDC_BUTTON1,17,40,50,14 EDITTEXT IDC_EDIT1,73,45,40,14,ES_AUTOHSCROLL END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_DIALOG1, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 256 TOPMARGIN, 7 BOTTOMMARGIN, 88

214

75.42 - Taller de Programacin END IDD_DIALOG2, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 179 TOPMARGIN, 7 BOTTOMMARGIN, 88 END END #endif // APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED #endif // Spanish (Mexican) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED

215

75.42 - Taller de Programacin

Analicemos el programa

En los archivos resource.h - ejemplo1.rc se crean utilizando un editor de recursos, que suele estar integrado con el entorno de desarrollo. En los mismos se definen los recursos del programa. Concretamente se define un dilogo como sigue:

Cada uno de los elementos de esta ventana es a su vez una ventana, y tiene asociado un ID, que permite identificarlos, tal como se muestra a continuacin:

Estos ID son enteros de 32 bits, y deben ser especificados al crear los elementos de la ventana, en el entorno. La mayora de los entornos o editores de recursos permiten hacer esto en un cuadro de propiedades del elemento, el cual suele abrirse presionando el botn derecho del mouse sobre el elemento.

216

75.42 - Taller de Programacin Analicemos ahora el cdigo C

En la lnea 1 del archivo ejemplo1.c se incluye el archivo windows.h, un nuevo header donde se definen todas las funciones, constantes y estructuras fundamentales de Windows. En la lnea 2 se incluye el archivo resource.h. Este archivo, generado automticamente al generar ejemplo1.rc, contiene las identificaciones de todos los recursos almacenados en este archivo.

La ejecucin de un programa Windows comienza su ejecucin en la funcin WinMain(). Esta funcin recibe 4 parmetros, y devuelve uno slo. Lnea 49.

hInstante: Contiene el nmero de proceso del programa. Todo proceso (programa) bajo Windows se identifica univocamente por un nmero (entero de 32 bits). Si un programa es ejecutado varias veces, cada "instancia" tendr un nmero distinto. Este nmero es muy usado en todo el programa, razn por la cual se lo suele almacenar como una variable global (ver lnea 52).

prev_instance: Definida por compatibilidad con Windows 16 bits. No tiene un valor til en Windows 32bits (95/98/2000/NT).

cmdline: Lnea de comandos con que se ejecut el programa. Idem DOS.

cmdshow: Forma en que de debe arrancar la aplicacin. Este parmetro indica si la aplicacin debe comenzar minimizada, maximizada, etc. Puede desprecirselo.

En la lnea 55 se crea la primer ventana del programa. En esta lnea se solicita la creacin de una ventana de dilogo, utilizando la funcin CreateDialog(), que recibe 4 parmetros:

1. El parmetro hInstance, ya visto.

2. La ventana de dilogo a crear. La macro MAKEINTRESOURCE() se utiliza por compatibilidad. Antiguamente se identificaba a cada recurso con un string (char *), y hoy se lo hace con un entero. Ya que por compatibilidad no se puede cambiar la definicin de la funcin, se utiliza esta macro que hace la conversin.

3. El padre de la ventana, en este caso NULL. Esto puede entenderse de dos formas, igualmente vlidas:

1. La ventana no tiene padre.

2. (Lo correcto) El padre de la ventana es el escritorio de Windows.

1. La funcin que procesar los mensajes.

217

75.42 - Taller de Programacin

El ciclo implementado entre las lneas 57 y 61 lo veremos ms adelante. Bsicamente es idntico en todos los programas, y simplemente se encarga de recibir mensajes y envirselos a la funcin correspondiente.

Notar las palabras reservadas CALLBACK y WINAPI en las lneas 7 y 49. Estas palabras simplemente modifican la forma en que el compilador implementa la llamada a la funcin, ya que las mismas no sern llamadas directamente por nuestro programa sino por Windows, y deben ser codificadas de una forma especial.

En la lnea 7 se define la funcin callback que procesar los mensajes de la ventana de dialogo creada, cuyo cdigo se implementa entre las lneas 8 y 46. Simplemente se define el cdigo para procesar 3 mensajes:

WM_INITDIALOG: Este mensaje es enviado al crearse la ventana, justo antes de que se la muestre en pantalla. En este punto deberan inicializarse todos los datos de la ventana, si los hubiese. Por ejemplo, limitar el tamao de los textos ingresables en los recuadros, cargar listados, etc.

WM_CLOSE: Este mensaje se transmite a la ventana cuando se presiona el botn [x] que se encuentra a la derecha, en la barra de ttulo de la ventana. La funcin MessageBox() crea y muestra una ventana muy sencilla, con el texto, ttulo y botones especificados. Su funcionamiento es muy simple. En la ayuda de la funcin se describen todos los parmetros posibles.

La funcin PostQuitMessage() finaliza el ciclo definido en las lneas 57 a 61, y con ello la ejecucin del programa.

WM_COMMAND: Agrupa una gran cantidad de eventos proveniente de ventanas hijas, particularmente todas las presiones de botones. El nmero de botn presionado se especifica en los 16 bits menos significativos de wParam. Por ello se define en la lnea 21: switch (LOWORD(wParam)) (que es equivalente a: switch (wParam&0xFFFF)) y que procesa los mensajes-comando emitidos por el botn Agregar (IDC_AGREGAR) y el botn Salir (IDC_SALIR).

Como vimos arriba, con el editor de recursos se asigna un ID o identificador a cada uno de los elementos constitutivos de la ventana. A su vez, al abrirse el dilogo en pantalla, Windows asignar un handle (que tambin es un entero de 32 bits).

F Una ventana tendr asociados dos nmeros enteros (de 32 bits). Un handle, asignado por Windows en tiempo de ejecucin al crear la ventana en pantalla, y un ID, asignado por el programador en la etapa de desarrollo.

Qu sentido tiene que una ventana est identificada por dos nmeros enteros? La respuesta es la siguiente: Windows, identifica cada ventana creada por su handle. Este permite identificar cada una de las ventanas del sistema, y son creados en tiempo de ejecucin. De hecho, si se ejecutasen dos instancias de la aplicacin, los mismos elementos en ambas instancias tendrn handles diferentes.

Entonces cmo puede el programador identificar un determinado elemento de la ventana de dilogo, por ejemplo, para enviarle un mensaje? La respuesta es, asignndole un ID o identificador, al momento de definirlo. Windows provee una funcin GetDlgItem(), que permite obtener el handle de un elemento, conocidos su ID y el handle de la ventana padre.

218

75.42 - Taller de Programacin De esta forma puede realizarse una llamada como:

HWND hWndList = GetDlgItem (hWnd, IDC_EDIT1);

para obtener el handle de la ventana de edicin de texto, y luego llamar a SendMessage() para enviarle un mensaje. Esta llamada podra escribirse tambin como sigue:

SendMessage(GetDlgItem(ventana,id),...,...,...)

Ya que esta llamada es muy comn, Windows define la funcin:

SendDlgItemMessage(ventana, id,...,...,...)

que hace esto mismo.

En la lnea 27 se lee el texto de la ventana de edicin de texto y en la lnea 28 se inserta este texto en el listado de texto. Los parmetros concretos que recibe cada mensaje no hace falta memorizarlos. Windows define ms de 5000 mensajes cantidad que aumenta da a da, cada uno de los cuales tiene sus propios parmetros. Pero afortunadamente no hace falta conocer todos los mensajes existentes. Simplemente basta saber que existen mensajes para prcticamente todo evento que se nos pueda ocurrir, pasando por movimientos de mouse, recepcin de mensajes de red o modem, apagado de Windows, y sus nombres son bastante representativos, por lo que no es una tarea muy difcil identificar el mensaje requerido. Todo compilador para Windows trae un ndice de los mensajes de Windows existentes.

Entre los tipos de mensajes ms utilizados se encuentran: LB_... LBN_... CB_... CBN_... EM_... EN_... STM_... STN_... SBM_... SBN_... WM_... WM_NOTIFY Mensajes enviables a un ListBox Notificacin proveniente de un ListBox Mensajes enviables a un ComboBox Notificacin proveniente de un ComboBox Mensajes enviables a un EditBox Notificacin proveniente de un EditBox Mensajes enviables a una ventana Static Notificacin proveniente de una ventana Static Mensajes enviables a una ventana Static Notificacin proveniente de una ventana Static Mensajes genricos de una ventana Notificaciones varias provenientes de una ventana

Hay tres categoras principales de mensajes:

Mensajes de Windows: incluye aquellos mensajes que comienzan con el prefijo WM_ excepto WM_COMMAND; estos mensajes tienen parmetros que son usados para identificar el mensaje y son manejados por Windows y las vistas.

219

75.42 - Taller de Programacin

Notificaciones de control: incluye los mensajes de notificacin WM_COMMAND desde controles y otras ventanas hijas a sus ventanas padres, por ejemplo: una ventana edit envia a su ventana padre un cdigo de notificacin de control cuando el usuario ha tomado una accion que puede alterar el texto en el control de edicin; el identificador de ventana para el mensaje responde al mensaje de notificacin de alguna manera apropiada, por ejemplo, retirando el texto en el control.

Mensajes de comandos: incluye los mensajes de notificacin WM_COMMAND desde los objetos de la interfaz de usuario: menes, botones de la barra de herramientas y teclas aceleradoras. El entorno de trabajo (framework) procesa estos comandos en forma diferente a otros mensajes ya que pueden ser manejados por una amplia variedad de objetos: documentos, vistas, la aplicacin misma y, por supuesto, el sistema operativo.

Entre los mensajes WM_... ms comunes se encuentran:

WM_INITDIALOG:

Enviado cuando se crea una ventana de dialogo, antes de mostrarla en pantalla.

WM_CREATE:

Enviado cuando se crea una ventana comn, antes de mostrarla en pantalla.

WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_MBUTTONDOWN

WM_LBUTTONUP, WM_RBUTTONUP, WM_MBUTTONUP

WM_RBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK

Presin de los botones del mouse (izquierdo, derecho y medio).

WM_CLOSE:

Se presiona el botn de cerrar la ventana.

WM_PAINT:

Enviado cuando la ventana debe ser redibujada.

220

75.42 - Taller de Programacin WM_COMMAND:

Mensajes provenientes de ventanas hijas, fundamentalmente presiones de botones o seleccin de comandos del men. En caso de presiones de un botn, En los 16 bits menos significativos de wParam se especifica el ID del botn presionado.

Quin procesa los mensajes restantes?

Claramente la aplicacin recibir muchos ms mensajes que no son procesados en este caso. Al crear ventanas de dilogo Windows se encargar de llamar (internamente) a una funcin de procesamiento genrico de mensajes, que proporciona el cdigo necesario para procesar todos los mensajes para los cuales la funcin callback de la aplicacin devuelva 0.

221

75.42 - Taller de Programacin

Mltiples ventanas

Qu pasara si duplicsemos la lnea 55? En este caso el cdigo de esta lnea quedara: int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE prev_instance, LPSTR cmdline, int cmdshow) { MSG msg; inst = hInstance; /* Crear dos ventanas de dialogo */ CreateDialog (inst, MAKEINTRESOURCE (IDD_DIALOG1), NULL, (DLGPROC) FuncionPrincipal); CreateDialog (inst, MAKEINTRESOURCE (IDD_DIALOG1), NULL, (DLGPROC) FuncionPrincipal); while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } } return msg.wParam;

Pues bien, en este caso se crearan dos ventanas de dilogo, y el usuario podra trabajar indistintamente con cualquiera de ellas, tal como se muestra abajo:

En este caso ambas ventanas compartirn la misma funcin de procesamiento de mensajes, FuncionPrincipal(). El parmetro hWnd identificar a la ventana a la cual se est enviando el mensaje. Como ya se vio, ambas ventanas tendrn los mismos elementos, pero los handles HWND tanto de las ventanas principales como de cada uno de sus elementos sern diferentes.

222

75.42 - Taller de Programacin Ventanas modales

Las lneas 55 a 63 de nuestro primer ejemplo podran haber sido reemplazadas en realidad por: return DialogBox (inst, MAKEINTRESOURCE (IDD_DIALOG1), NULL, (DLGPROC)FuncionPrincipal); Notar que al hacer esto se elimina el ciclo de mensajes del programa. Quiere esto no se utiliza un ciclo de mensajes? De ninguna manera. Simplemente, la funcin DialogBox ya implementa internamente un ciclo de procesamiento de mensajes como vimos arriba, slo que el mismo est implementado de una forma particular, que imposibilita al usuario seleccionar la ventana padre. Esto es, si dentro de una ventana se abre una otra ventana utilizando DialogBox, la ventana previa no podr ser seleccionada hasta que no se cierre la nueva ventana. A esto se conoce como ventana modal.

En nuestro primer ejemplo, ya que slo se abre una ventana, es indiferente trabajarla en forma modal y no modal.

Un poco ms sobre ventanas

Habiendo visto el funcionamiento general de un programa, podemos ahora analizar el funcionamiento de las ventanas con ms detalle.

Las funciones de manejo de las ventanas proveen a las aplicaciones el medio de crear e implementar la interfaz de usuario. Se usan funciones de manejo de ventanas para crear y usar ventanas para desplegar salidas, proveer la manera en que el usuario ingresar a las entradas y efectuar las otras tareas necesarias para mantener la interaccin con el usuario.

Las aplicaciones definen el comportamiento y apariencia generales de sus ventanas creando modelos de ventanas y los procedimientos de ventanas correspondientes. Los modelos, tipos o "clases" de ventanas (window classes) identifican las caractersticas por defecto, tales como, por ejemplo, la forma en que la ventana procesara un doble click del botn del mouse o si tiene o no un men para seleccin de items. Los procedimientos de ventanas (windows procedures) contienen el cdigo que define el comportamiento de las ventanas (esto es: cmo llevan a cabo las tareas requeridas) y procesan las entradas de usuario; estos procedimientos son funciones que se ocupan de todos los mensajes enviados y recibidos de y a todas las ventanas derivadas de ese modelo; todos los aspectos de la apariencia y comportamiento de esta ventanas dependen de las respuestas del procedimiento a los mensajes; por ejemplo, para computadoras con interfaz de usuario grfica, los iconos, menes y cajas de dialogo deben sus caractersticas a estas funciones.

Debido a que todas las ventanas comparten el rea de pantalla, las aplicaciones no reciben acceso a la pantalla entera. En lugar de eso, el sistema maneja todas las salidas, de forma tal que queden alineadas y, de ser necesario, recortadas para calzar dentro de la ventana correspondiente.

Las aplicaciones pueden dibujar ventanas en respuesta a requerimientos del sistema o mientras estn procesando mensajes de entrada. Cuando el tamao o porcin de una ventana cambia el sistema tpicamente enva un mensaje a la aplicacin requiriendo que esta dibuje cualquier parte de sus ventanas no expuesta previamente.

Windows informa a las aplicaciones de eventos en el sistema, tales como movimientos del mouse, presin sobre los botones el mouse, o presin sobre las teclas en mensajes de entrada utilizando mensajes, los cuales almacena en una cola para cada aplicacin, proveyendo automticamente estas colas. Las aplicaciones usan las funciones de mensajes para extraer mensajes de las cola y despacharlos a los procedimientos de ventanas apropiados para su procesamiento.

223

75.42 - Taller de Programacin

Ya que toda aplicacin con interfaz de usuario se compondr sin duda de una serie de elementos bsicos, tales como botones, cuadros de ingreso de texto, etc, cuya programacin (sumamente compleja) ser idntica en todos los casos, Windows ya ofrece una serie de ventanas bsicas, con su correspondiente cdigo necesario para procesar una serie muy grande de mensajes. Estas ventanas estn agrupadas en los siguientes tipos:

Ventanas STATIC:

textos, iconos, bitmaps, recuadros

Ventanas BUTTON:

Botones, en cualquiera de sus tipos: simple, check box, radio button

Ventanas EDIT:

Recuadros para la edicin de texto

224

75.42 - Taller de Programacin

Ventanas SCROLLBAR:

Barras de desplazamiento

Ventanas LISTBOX y COMBOBOX:

Ventanas para la seleccin de elementos

Existe un segundo grupo de ventanas bsicas, implementadas a partir de Windows 32 bits, que ayudan a darle a Windows la apariencia y comportamiento que lo caracterizan, conocidos como Common Controls y Common Dialogs. Estos son controles y ventanas de propsito general que representan listados con imgenes, rboles, animaciones, barras de progreso, y otros. Estos controles estn agrupados dentro de libreras .DLL (dinamic link library o librera de enlace dinmico) como COMCTL32.DLL, las cuales, por ser parte del sistema operativo, estn disponibles para todas las aplicaciones. Los controles son ventanas hijas que las aplicaciones usan juntamente con otras ventanas para realizar operaciones de entrada/salida.

Estas libreras se han ido extendiendo desde su aparicin original en Windows 95/NT, agregando nuevos elementos y nuevas capacidades a las ya existentes, y son actualizadas en Windows por algunos programas, tales como Internet

225

75.42 - Taller de Programacin

Explorer. De all que varios programas requieran para ejecutarse que se haya instalado previamente una determinada versin (o superior) de Internet Explorer.

Dibujando en pantalla

Ahora crearemos otro ejemplo con un tema interesante y muy aclarativo, el dibujar en pantalla. Nuestro programa dibujar una lnea en pantalla al presionarse el botn [OK].

El cdigo de nuestro programa ser ahora el siguiente:

Ejemplo2.c #include <windows.h> #include "resource.h" /* Instancia del programa */ HINSTANCE inst; LONG CALLBACK FuncionPrincipal (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { switch (wMsg) { case WM_INITDIALOG: /* Este mensaje se enva a la ventana en el momento de crearla, justo antes de mostrarla en pantalla */ break; case WM_COMMAND: /* Este mensaje se enva a la ventana cada vez que se presiona un botn (entre otras cosas) */ switch (LOWORD(wParam)) { case IDOK: { HDC hdc; hdc = GetDC (hWnd); MoveToEx (hdc, 0, 0, NULL); LineTo (hdc, 2000, 1000); ReleaseDC (hWnd, hdc); } break; } break; case WM_CLOSE: /* Este mensaje se enva cuando el usuario presiona el botn [X] para cerrar la ventana. */ PostQuitMessage (0); } /* Una ventana debe devolver 0 si no procesa el mensaje */ return 0; }

226

75.42 - Taller de Programacin int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE prev_instance, LPSTR cmdline, int cmdshow) { inst = hInstance; /* Crear una ventana de dialogo (Ventana fundamental de la aplicacin) */ DialogBox (inst, MAKEINTRESOURCE (IDD_DIALOG1), NULL, (DLGPROC) FuncionPrincipal); } return 0;

Ejemplo2.rc

Al igual que en el ejemplo anterior, tendremos un archivo de recursos, ejemplo2.rc, con su correspondiente resource.h. En estos archivos se define un dilogo como se muestra a continuacin:

El cdigo es el siguiente: //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // Spanish (Castilian) (unknown sub-lang: 0xB) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ESS) #ifdef _WIN32 LANGUAGE LANG_SPANISH, 0xB #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED /////////////////////////////////////////////////////////////////////////////

227

75.42 - Taller de Programacin

// // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED

///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 186, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Ejemplo02" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,129,7,50,14 END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_DIALOG1, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 179 TOPMARGIN, 7 BOTTOMMARGIN, 88 END END #endif // APSTUDIO_INVOKED #endif // Spanish (Castilian) (unknown sub-lang: 0xB) resources /////////////////////////////////////////////////////////////////////////////

228

75.42 - Taller de Programacin

#ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED resource.h //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by Ejemplo02.rc // #define IDD_DIALOG1 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE #define _APS_NEXT_COMMAND_VALUE #define _APS_NEXT_CONTROL_VALUE #define _APS_NEXT_SYMED_VALUE #endif #endif

102 40001 1000 101

El archivo de recursos se gener visualmente, como se vi en el ejemplo anterior, por lo cual no ser analizado nuevamente. No analizaremos todo el ejemplo nuevamente, sino que nos centraremos en las diferencias con el ejemplo anterior.

Como puede verse, al presionarse el botn [OK] se llamar al cdigo: case IDOK: { HDC hdc; hdc = GetDC (hWnd); MoveToEx (hdc, 0, 0, NULL); LineTo (hdc, 2000, 1000); ReleaseDC (hWnd, hdc); } break; Si el usuario prueba presionar el botn ver que se dibuja una lnea en la ventana tal como se muestra a continuacin:

229

75.42 - Taller de Programacin

En este cdigo de utiliza un nuevo handle, denominado HDC o Handle to Device Context. El mismo es un identificador o handle de un contexto de un dispositivo. Esta explicacin resulta sin duda bastante confusa, as que trataremos de explicarla ms claramente.

Windows controla todas las funciones de acceso a pantalla. Esto tiene, entre otros, dos propsitos bsicos:

1) El rea de dibujo est limitada a la ventana. Es decir que, el sistema de coordenadas tiene su origen en la posicin superior izquierda de la ventana, y no puede salirse de l. Si bien los parmetros recibidos son enteros de 32 bits, Windows posee una limitacin interna de tal forma que los nmeros recibidos no deben superar los 16 bits de presicin.

2) Windows controla el rea del dispositivo (en este caso la ventana) que est visible en pantalla y debe ser dibujada. Por ejemplo, si la ventana estuviese tapada por otra y se dibujase algo, este dibujo no se ver en pantalla y no tendr ningn efecto.

Como puede verse en la siguiente imagen, la lnea no se dibuja sobre la ventana superior.

Redibujando la ventana

Qu pasara si luego de presionar el botn [OK], la ventana queda oculta (total o parcialmente) y luego vuelve a ser visible en su totalidad?

230

75.42 - Taller de Programacin

La respuesta es que Windows no redibuja el rea que qued oculta, sino que esta es tarea de la aplicacin. Cuando un rea de la ventana vuelve a ser visible, Windows informa a la aplicacin que un rea de la ventana debe ser repintada, utilizando el mensaje WM_PAINT. Es posible para la aplicacin saber qu rea debe ser redibujada y actuar en consecuencia. Una solucin sencilla es repintar toda la ventana.

Modifiquemos entonces la funcin FuncionPrincipal() para que el programa funcione correctamente: LONG CALLBACK FuncionPrincipal (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { static BOOL bDibujarLinea; switch (wMsg) { case WM_INITDIALOG: /* Este mensaje se enva a la ventana en el momento de crearla, justo antes de mostrarla en pantalla */ bDibujarLinea = FALSE; break; case WM_PAINT: /* Windows enviar a la ventana un mensaje WM_PAINT cuando un rea de la misma deba ser redibujada */ if (bDibujarLinea) { HDC hdc; hdc = GetDC (hWnd); MoveToEx (hdc, 0, 0, NULL); LineTo (hdc, 2000, 1000); ReleaseDC (hWnd, hdc); } break; case WM_COMMAND: /* Este mensaje se enva a la ventana cada vez que se presiona un botn (entre otras cosas) */ switch (LOWORD(wParam)) { case IDOK: bDibujarLinea = TRUE; InvalidateRect (hWnd, NULL, FALSE); break; } break; case WM_CLOSE: /* Este mensaje se enva cuando el usuario presiona el botn [X] para cerrar la ventana. */

231

75.42 - Taller de Programacin

PostQuitMessage (0); } /* Una ventana debe devolver 0 si no procesa el mensaje */ return 0;

Esta es la forma correcta de programar el dibujo en pantalla. Todo el cdigo de dibujo en pantalla debe codificarse como una respuesta al mensaje WM_PAINT, de Windows. Esta es la nica forma de garantizar que la ventana ser redibujada si la misma queda oculta y luego vuelve a ser visible.

Al presionarse el botn [OK], simplemente "invalidamos la ventana", esto es, le informamos a Windows que la ventana tiene un estado invlido y que debe ser repintada, llamando a InvalidateRect(), tras lo cual Windows enviar un mensaje WM_PAINT, solicitando que la pantalla sea redibujada.

El primer parmetro de InvalidateRect() es el handle de la ventana que debe ser redibujada, el segundo es un puntero a un rectngulo (estrucuta RECT) que define el rea que debe ser redibujada. En este caso se utiliza NULL, que identifica la ventana entera. Antes de ver el significado del tercer parmetro, veamos la siguiente pregunta:

Cuando la ventana queda oculta, y luego vuelve a ser visible, quin repinta en fondo gris de la misma?. Algn programador inexperto podra pensar que esta es tarea de Windows, pero no es as. Cuando un rea de la pantalla vuelve a ser visible (luego de haber quedado oculta), Windows enva a la aplicacin un mensaje WM_ERASEBKGND. Al crear un cuadro de dilogo, cuando la funcin callback del mismo retorna 0 automticamente se llama a una funcin genrica que procesa el mensaje. En este caso, si la aplicacin no procesa el mensaje WM_ERASEBKGND lo har esta funcin genrica.

Ahora s, podemos ver qu es el tercer parmetro de la funcin InvalidateRect(). La documentacin de esta funcin especifica que este tercer parmetro indica si la ventana debe ser borrada antes de ser redibujada o no. Desde el punto de vista operativo, lo que se especifica con este parmetro es si debe enviarse un mensaje WM_ERASEBKGND para que se repinte todo el fondo (en este caso de gris), antes de enviarse un mensaje WM_PAINT para que se redibuje la lnea en la pantalla (en este caso un una lnea).

232

75.42 - Taller de Programacin

Captulo III - Creacin de una ventana


En el ejemplo del captulo anterior utilizamos un tipo muy particular de ventana, que es la ventana de dilogo, junto con botones, un cuadro de edicin y uno de listado, que son ventanas provistas por Windows. Sin embargo, pueden definirse nuevas ventanas, o incluso modificar las ya existentes. Para ello es necesario analizar la forma de crear una ventana.

La creacin de una ventana genrica se realiza en dos pasos:

1 - Debe registrarse en Windows un modelo bsico de ventana.

2 - Puede crearse una o ms ventanas como variantes de algn modelo ya registrado.

Una ventana se compone de una gran cantidad de atributos, los cuales se especifican en estos dos puntos. Entre los parmetros que se definen en la primera etapa se encuentra la funcin necesaria para procesar los mensajes. En el segundo punto se especifican fundamentalmente atributos variables referentes al dibujo de la ventana en pantalla, tales como posicin y tamao.

Veamos ahora como registrar y mostrar un modelo de ventana propio. En un programa "tradicional" para Windows, esto es, sin usar libreras prehechas, nosotros debemos procesar todos los mensajes en el procesador de mensajes asociado con una ventana mediante la "registracion de una clase ventana". Como mencionamos en el captulo anterior, esto se realiza en dos pasos. En primer lugar hay que registrar un modelo de ventana bsico, y luego se crea la ventana, a partir de un modelo bsico ya registrado.

Paso 1: Registrar un modelo de ventana bsico

Antes de crear una ventana en pantalla es necesario registrar un modelo bsico, con las caractersticas fundamentales de la misma. Todas las ventanas creadas a partir de este modelo (denominado clase de ventana, no confundir con clase de C++) tendrn todas las caractersticas bsicas, si bien pueden ser modificadas posteriormente. Uno de estos parmetros es la funcin callback, encargada de procesar los mensajes enviados a la ventana.

Para realizar esto es necesario cargar una estructura, WNDCLASSEX, con estos datos, y luego llamar a la funcin RegisterClass o su versin extendida RegisterClassEx. Esta funcin est definida como sigue:

ATOM RegisterClassEx (CONST WNDCLASSEX * lpwcx);

El valor retornado, ATOM, es un entero que identifica la clase creada. Generalmente su utilidad se reduce a verificar que la operacin haya sido exitosa. Esta funcin reporta error retornando 0.

lpwcx es un puntero a la siguiente estructura: typedef struct _WNDCLASSEX { UINT cbSize; // Debe tener el valor sizeof(WINDOWCLASSEX)

233

75.42 - Taller de Programacin

UINT style; // posibles WNDPROC lpfnWndProc; // int cbClsExtra; // estructura int cbWndExtra; // instancia de la ventana HANDLE hInstance; // HICON hIcon; // HCURSOR hCursor; // HBRUSH hbrBackground; // LPCTSTR lpszMenuName; // LPCTSTR lpszClassName;// HICON hIconSm; // } WNDCLASSEX; Paso 2: Crear la ventana

Estilo. Ver manual para lista de los tipos Funcin callback que procesar los mensajes Extra bytes a reservar a continuacin de esta Extra bytes a reservar a continuacin de la Instancia de la aplicacin Icono de la ventana Cursor de la ventana (por defecto) Fondo de la ventana Men de la ventana Nombre de la clase Icono pequeo (No utilizado en Windows NT)

La creacin de la ventana se realiza llamando a la funcin CreateWindow o su versin extendida CreateWindowEx. HWND CreateWindowEx( DWORD dwExStyle, // LPCTSTR lpClassName, // LPCTSTR lpWindowName, // siempre) el ttulo. DWORD dwStyle, // int x, // int y, // int nWidth, // int nHeight, // HWND hWndParent, // HMENU hMenu, // ventana en relacin al padre HINSTANCE hInstance, // LPVOID lpParam // manual de la funcin );

Estilo extendido de la ventana Puntero a un string con el nombre de la clase Texto de la ventana, generalmente (aunque no Estilo de la ventana Posicin horizontal de la ventana (en pixels) Posicin vertical de la ventana (en pixels) Ancho de la ventana (en pixels) Alto de la ventana (en pixels) Identificador del padre de la ventana Identificador del menu, o identificador de la Instancia actual del programa Puntero para pasar datos a la aplicacin. Ver

Esta funcin devuelve el identificador de la ventana creada, o NULL en caso de error.

Veamos el ejemplo completo: /*****************************************************************************\ *** A R C H I V O S A I N C L U I R *** \*****************************************************************************/ #include <windows.h> /* Nota: La mayor parte del cdigo referente a la verificacin de errores ha sido removido por claridad. */ /*****************************************************************************\ *** D E F I N I C I O N E S *** \*****************************************************************************/ #define OK 0 #define ERR 1 HINSTANCE inst;

234

75.42 - Taller de Programacin /*****************************************************************************\ *** P R O C E D I M I E N T O D E L A V E N T A N A *** \*****************************************************************************/ LONG CALLBACK MainProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { switch (wMsg) { case WM_CREATE: /* Aqu deben inicializarse las variables de la ventana */ break; case WM_CLOSE: /* Cerrar la ventana */ PostQuitMessage (0); break; default: /* Procesar todos los restantes mensajes */ return DefWindowProc (hWnd, wMsg, wParam, lParam); } return 0; } /*****************************************************************************\ *** C R E A C I O N D E L A V E N T A N A *** \*****************************************************************************/ int Init (void) { WNDCLASSEX wc; HWND Win; wc.cbSize = sizeof (wc); // Debe contener el tamao de la estructura wc.style = CS_DBLCLKS|CS_GLOBALCLASS; // Estilo de la ventana wc.lpfnWndProc = MainProc; // Funcion que procesar los mensajes wc.cbClsExtra = 0; // Bytes adicionales al final de esta estructura wc.cbWndExtra = 0; // Bytes adicionales en los datos de la ventana creada wc.hInstance = inst; wc.hIcon = NULL; // Icono de la ventana wc.hCursor = NULL; // Cursor que se usar en la ventana. wc.hbrBackground = COLOR_WINDOWFRAME; wc.lpszMenuName = NULL; wc.lpszClassName = "Clase Ventana"; // Nombre de este tipo de ventana wc.hIconSm = NULL; // Icono pequeo de la ventana /* Registrar la ventana */ if (RegisterClassEx (&wc) == 0) return ERR; /* Crear la ventana principal */ Win = CreateWindowEx ( 0, // Estilos extendidos de la ventana "Clase Ventana", // Nombrede la clase registrada "Ventana de ejemplo", // Ttulo de la ventana WS_CAPTION|WS_SIZEBOX|WS_SYSMENU|WS_VISIBLE, // Estilo de la ventana CW_USEDEFAULT, // Posicin x en pixels (usar posicin por defecto) CW_USEDEFAULT, // Posicin y en pixels (usar posicin por defecto) CW_USEDEFAULT, // Ancho en pixels (usar valor por defecto) CW_USEDEFAULT, // Alto en pixels (usar valor por defecto) NULL, // Padre de la ventana (escritorio de Windows) NULL, // Men de la ventana inst, // Instancia del programa NULL); // Informacin adicional if (!Win)

235

75.42 - Taller de Programacin

return ERR; } return OK;

/*****************************************************************************\ *** W I N M A I N *** \*****************************************************************************/ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { MSG msg; inst = hInstance; Init (); while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } return msg.wParam; } /*****************************************************************************\ *** F I N *** \*****************************************************************************/ El mensaje WM_PAINT es enviado por Windows cada vez que se deba redibujar la ventana (en su totalidad o una parte de ella). Para dibujar dentro de la ventana debe obtenerse el rea de contexto de la ventana (device-context), mediante la funcin GetDC(). El identificador devuelto (dc) nos garantizar que nuestros dibujos se harn dentro de la ventana especificada, y se dibujarn en ventanas que puedan estar superpuestas a la nuestra. El origen de coordenadas (0,0) es el pixel superior izquierdo de la ventana, y la posicin se mide en pixels. Cualquier posicin especificada fuera del rea de la ventana (tanto valores positivos como negativos) no sern dibujados. De esta forma no es incorrecto, por ejemplo, dibujar en zonas negativas de la ventana, simplemente no se dibujar nada. Esto puede ser muy til, ya que el sistema de coordenadas puede ser modificado, con lo cual podra trabajarse con una imagen muy grande, que pueda ser desplazada, ampliada o achicada con slo cambiar el sistema de coordenadas. Desafortunadamente, el sistema de dibujo de Windows trabaja nicamente con enteros (con signo) de 16 bits.

En nuestro cdigo redibujamos una lnea desde la posicin (0,0) hasta la posicin (1000,1000), y a continuacin imprimimos un nmero '1'. Es posible modificar todos los estilos de dibujo, como por ejemplo, utilizar diferentes estilos de lnea, color, grosor, fuente de texto, itlica, negrilla, tamao de letra, justificacin, etc.

Qu sucedera si hubisemos hecho nuestro dibujo en cualquier otro lugar del programa en vez de cmo respuesta a un mensaje WM_PAINT? Efectivamente se hubiese dibujado en pantalla, pero si por alguna razn la imagen de la ventana era tapada (por ejemplo por abrir otro programa), al volver a nuestra ventana el dibujo habra desaparecido.

Windows enviar un mensaje WM_PAINT cada vez que se deba redibujar la ventana. Esto puede resultar muy ineficiente, si el dibujo requiere tiempo para efectuarse. Para solucionar esto (en parte), es posible modificar este cdigo para conocer la parte de la ventana que requiere ser dibujada, o para que slo se redibuje aquella parte que fue modificada, pero lo dejaremos de lado por ahora.

El ciclo de mensajes y threads

236

75.42 - Taller de Programacin Hasta ahora, en nuestros ejemplos hemos definido un ciclo de mensajes, sin reparar mucho en qu hace o cmo trabaja. El mismo se transcribe ac por claridad: while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } Es comn que los programadores no profundicen en la forma en que se implementa el sistema de mensajera de Windows, y el resultado de ello son programas defectuosos. Algunos conceptos errneos muy frecuentes son los siguientes:

1) No existe ningn peligro en enviar un mensaje a una ventana utilizando SendMessage().

Rta: Si la ventana a la cual se enva el mensaje no procesa los mensajes, por ejemplo por estar "colgada" la aplicacin, esta funcin no retornar y por ende nuestra aplicacin tambin quedar colgada. Existe una funcin llamada SendMessageTimeout() que puede ser utilizada si no se sabe el estado de la ventana destino del mensaje.

2) Todos los mensajes son encolados

Rta: No todos los mensajes son encolados. Se explicar esto ms adelante.

3) Todo mensaje enviado utilizando PostMessage() a una ventana vlida siempre es encolado.

Rta: Ya que, para encolar un mensaje debe existir una cola de mensajes, la cual tiene un tamao finito, si la misma se llena el mensaje puede no ser encolado, en cuyo caso PostMessage() retornar un cdigo de error.

Antes de ver la forma en que opera el sistema de mensajera de Windows es necesario hacer unas aclaraciones:

Una aplicacin o proceso Windows se puede componer de varias hebras de ejecucin o threads, cada una de las cuales se ejecuta en forma independiente y tiene una cola propia. Inicialmente un proceso es creado con una nica thread, y este puede crear nuevas threads.

En Windows es posible definir parmetros de seguridad, que habilitan o bloquean ciertas operaciones. En Windows 95/98/Me, el sistema de seguridad es muy restringido, pero en NT el mismo es sumamente complejo. As, cada thread tomar parmetros de seguridad que determinarn el permiso para realizar operaciones, que generalmente sern iguales para todas las threads de un mismo proceso.

Habiendo hecho estas aclaraciones, volvamos al tema de mensajera.

Como ya se vi, al crear un proceso, Windows crea la thread inicial del mismo. Esta thread o hebra de ejecucin es la que comienza a ejecutarse en WinMain(). Una vez creada una o ms ventanas deben procesarse los mensajes destinados a las mismas. La funcin GetMessage() lee y retira el primer mensaje enviado a la thread. Notar que los mensajes enviados a todas las ventanas de la aplicacin son encolados en realidad en la cola de la thread que cre la ventana.

237

75.42 - Taller de Programacin

La funcin GetMessage() toma 4 parmetros:

El primero es una estructura MSG donde se almacenar el mensaje ledo y retirado de la cola.

El segundo identifica la ventana de la cual se desea leer el mensaje. Si se especifica NULL, se leern todos los mensajes. El campo hWnd de la estructura MSG contendr el handle de la a la cual se enva el mensaje. Es posible enviar un mensaje a una thread, en cuyo caso este campo tendr el valor NULL.

El tercer y cuarto parmetro permiten definir un rango de mensajes a leer. Si se especifica 0, 0 se leern todos los mensajes.

La funcin TranslateMessage() modifica el mensaje, de acuerdo a ciertas reglas y aceleradores. Por ejemplo, se traduce la tacla [TAB] en un cambio de la ventana seleccionada, y la presin de una tecla en un mensaje identificando al carcter ASCII correspondiente.

Finalmente, la funcin DispatchMessage() se encarga de llamar a la funcin callback correspondiente.

Cabe destacar que no todos los mensajes son encolados. Por ejemplo, cuando en el ejemplo del captulo 1 se llama a SendMessage para enviar un mensaje a un elemento de la ventana de dilogo, esta llamada se traduce directamente en una llamada a la funcin callback de elemento. Al llamar a SendMessage, si la ventana a la cual se enva el mensaje es creada por la misma tirad, el mensaje no es encolado sino que directamente se llama a la funcin. Por el contrario, si el mensaje fue enviado a una ventana de otra thread, el mensaje es encolado y procesado por el thread de la ventana destino, si es que lo hace. Esto garantiza que todos los mensajes enviados a una ventana sean procesados sean procesados por la ventana que cre la misma. Esto debe hacerse por varias razones, relacionadas principalmente con la seguridad del sistema. Por ejemplo, cuando el sistema operativo nos enva un mensaje como WM_MOUSEMOVE, el procesamiento del mismo debe hacerse con los parmetros de seguridad del usuario de la aplicacin, no con los del sistema operativo. En caso contrario, la aplicacin destino podra hacerse pasar por el sistema operativo y realizar cualquier operacin. En segundo lugar, si una segunda thread enviase mensajes imprevisibles, podra causar problemas en la aplicacin.

Los mensajes enviados llamando a PostMessage() siempre son encolados, salvo que la cola est llena o no exista. Los mensajes encolados por una thread para s misma tienen precedencia por sobre todos los mensajes enviados por otras threads.

Finalmente, debe aclararse que no necesariamente toda thread tiene una cola de mensajes. Esta cola se crea cuando la misma llama por primera vez a GetMessage(). Si no se llama a esta funcin, la cola no ser creada y por ende no habr forma de enviarle un mensaje a la misma.

Es posible definir los parmetros prioridades tanto para las threads como para los procesos. La prioridad con la cual se ejecutar una thread es un resultado de la prioridad del proceso correspondiente y la prioridad propia de la thread. Para ms informacin, ver documentacin de Microsoft.

La necesidad de una nueva estructura de programacin

238

75.42 - Taller de Programacin Veamos un nuevo ejemplo, an ms complejo. Supongamos que se quiere desarrollar una aplicacin que permita abrir mltiples ventanas. Un ejemplo de esto es el administrador de archivos de Windows:

Una ventana tan simple como esta, se compone en realidad de varias ventanas, unas dentro de otras. A continuacin se muestra la forma en que se construyen estas ventanas. Concretamente, la aplicacin se compone de una ventana principal, la cual contiene dos barras de tareas, una ventana "MDIClient", que contendr las ventanas abiertas por el usuario, y una ventana para dibujar una barra de estado. Estas ventanas a su vez pueden contener otras ms. De hecho, los botones suelen ser a su vez ventanas.

239

75.42 - Taller de Programacin

Se encuentra aqu un primer inconveniente. Vimos en el ejemplo anterior que la programacin Windows se basa fundamentalmente en funciones callback. Estas funciones son llamadas por Windows, y puede definirse variables dentro de ellas, pero al salir de la funcin las mismas se destruyen. Entonces: Dnde se definen las variables de las ventanas hija de un programa?

Hay tres respuestas posibles, igualmente inadecuadas.

La primera y ms simple es definir todas las variables fundamentales del programa como globales. Esto es completamente inadecuado por todas las razones ya vistas.

La segunda solucin consiste en declarar las variables dentro de cada funcin callback como estticas (static), de forma tal que no se destruyan. Esta alternativa, si bien es viable y de hecho se utiliza para programas pequeos, presenta muchos inconvenientes. Uno de ellos es la imposibilidad (o dificultad) de abrir simultneamente dos ventanas del mismo tipo, ya que se estar usando una nica funcin callback para dos o ms ventanas diferentes, y por lo tanto habr que escribir un cdigo para poder duplicar todas las variables para cada ventana.

La tercera, (y la que realmente se utiliza) es asociar a cada ventana una estructura con todas las variables de la misma. Toda ventana puede contener informacin adicional, definida por el usuario. Windows provee a tal fin la funcin: SetWindowLong() y GetWindowLong(), que entre sus opciones permiten asociar y leer un entero (datos del usuario) a la ventana (Ver opcin: GWL_USERDATA). Este entero se suele utilizar para almacenar un puntero a todas las variables del programa.

A continuacin se muestra un programa Windows bsico para crear una aplicacin de este tipo. La misma se compone de una ventana bsica, la cual contendr una ventana principal (comnmente llamada MDI), la cual a su vez contendr cuatro ventanas hijas. Estas ventanas hijas mostrarn en pantalla sus datos, los cuales sern simplemente un entero. Notar claramente como se crearon las variables de cada ventana. Notar tambin la forma en que se imprimen los datos en pantalla. Utilizando ya programacin orientada a objetos, se utiliza un mtodo de la clase para imprimir el contenido de la variable (a partir de ahora atributo) a.

Ya que las cuatro ventanas hijas debern compartir la misma funcin de procesamiento de mensajes, y esta funcin no puede tener acceso a ninguna variable externa ms que los cuatro parmetros que recibe (no quiero utilizar variables globales), Cmo puedo conocer los datos de la ventana correspondiente al momento de procesar los mensajes?

Rta: Toda ventana tiene la capacidad de almacenar un nmero adicional, que puede ser utilizado para almacenar un puntero a datos de la ventana. /*****************************************************************************\ *** A R C H I V O S A I N C L U I R *** \*****************************************************************************/ #include #include #include #include /* Nota: La mayor parte del cdigo referente a la verificiacin de errores ha sido removido por claridad. */ /*****************************************************************************\ *** D E F I N I C I O N E S *** \*****************************************************************************/ #define OK 0

240

75.42 - Taller de Programacin #define ERR 1 HINSTANCE inst; /*****************************************************************************\ *** P R O T O T I P O S *** \*****************************************************************************/ int RegistrarVentanaPrincipal (void); int RegistrarVentanaHija (void); HWND CrearVentanaHija (HWND Parent, int Numero); LONG CALLBACK MainProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam); LONG CALLBACK ChildProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam); int Init (void); /*****************************************************************************\ int RegistrarVentanaPrincipal (void) \*****************************************************************************/ int RegistrarVentanaPrincipal (void) { WNDCLASSEX wc; wc.cbSize = sizeof (wc); // Debe contener el tamao de la estructura wc.style = 0; // Estilo de la ventana wc.lpfnWndProc = MainProc; // Funcion que procesar los mensajes wc.cbClsExtra = 0; // Bytes adicionales al final de esta estructura wc.cbWndExtra = 0; // Bytes adicionales en los datos de la ventana creada wc.hInstance = inst; wc.hIcon = NULL; // Icono de la ventana wc.hCursor = LoadCursor (NULL, MAKEINTRESOURCE (IDC_ARROW));// Cursor que se usar en la ventana. wc.hbrBackground = (void *)COLOR_APPWORKSPACE; wc.lpszMenuName = NULL; wc.lpszClassName = "Clase Ventana"; // Nombre de este tipo de ventana wc.hIconSm = NULL; // Icono pequeo de la ventana /* Registrar la ventana */ if (RegisterClassEx (&wc) == 0) return ERR; return OK; } /*****************************************************************************\ int RegistrarVentanaHija (void) \*****************************************************************************/ int RegistrarVentanaHija (void) { WNDCLASSEX wc; wc.cbSize = sizeof (wc); // Debe contener el tamao de la estructura wc.style = 0; // Estilo de la ventana wc.lpfnWndProc = ChildProc; // Funcion que procesar los mensajes wc.cbClsExtra = 0; // Bytes adicionales al final de esta estructura wc.cbWndExtra = 0; // Bytes adicionales en los datos de la ventana creada wc.hInstance = inst; wc.hIcon = NULL; // Icono de la ventana wc.hCursor = LoadCursor (NULL, MAKEINTRESOURCE (IDC_ARROW)); // Cursor que se usar en la ventana. wc.hbrBackground = (void *)COLOR_WINDOWFRAME; wc.lpszMenuName = NULL; wc.lpszClassName = "Clase Vent Hija"; // Nombre de este tipo de ventana wc.hIconSm = NULL; // Icono pequeo de la ventana /* Registrar la ventana */ if (RegisterClassEx (&wc) == 0) return ERR;

241

75.42 - Taller de Programacin

return OK;

/*****************************************************************************\ int CrearVentanaHija (HWND Parent, int Numero) \*****************************************************************************/ HWND CrearVentanaHija (HWND Parent, int Numero) { HWND Win; /* Crear la ventana principal */ Win = CreateWindowEx ( WS_EX_WINDOWEDGE|WS_EX_CLIENTEDGE|WS_EX_MDICHILD, // Estilos extendidos de la ventana "Clase Vent Hija", // Nombrede la clase registrada "Ventana Hija", // Ttulo de la ventana WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|WS_BORDER|WS_DLGFRAME|WS_THICKFRAME| WS_TABSTOP|WS_GROUP, // Estilo de la ventana Numero*20, // Posicin x en pixels (usar posicin por defecto) Numero*20, // Posicin y en pixels (usar posicin por defecto) 150, // Ancho en pixels (usar valor por defecto) 100, // Alto en pixels (usar valor por defecto) Parent, // Padre de la ventana (escritorio de Windows) NULL, // Men de la ventana inst, // Instancia del programa NULL); // Informacin adicional return Win; } /*****************************************************************************\ * P R O C E D I M I E N T O D E L A V E N T A N A P R I N C I P A L * \*****************************************************************************/ LONG CALLBACK MainProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { /* Procesamiento de mensajes */ switch (wMsg) { case WM_CREATE: /* Aqu deben inicializarse las variables de la ventana */ break; case WM_CLOSE: /* Cerrar la ventana */ PostQuitMessage (0); break; default: /* Procesar todos los restantes mensajes */ return DefWindowProc (hWnd, wMsg, wParam, lParam); } return 0; } /*****************************************************************************\ *** P R O C E D I M I E N T O D E L A V E N T A N A H I J A *** \*****************************************************************************/ /* Datos de la ventana Hija */ class TDatosVentanaHija{ private: int a; public:

242

75.42 - Taller de Programacin TDatosVentanaHija (void) { a = rand(); } void EvPaint (HDC dc) { char Txt[20]; sprintf (Txt, "%u", a); TextOut (dc, 10, 10, Txt, strlen(Txt)); }

}; LONG CALLBACK ChildProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { /* Obtener el puntero a los datos de la aplicacin */ TDatosVentanaHija *p; if (wMsg == WM_CREATE) { p = new TDatosVentanaHija; SetWindowLong (hWnd, GWL_USERDATA, (int)p); } else p = (TDatosVentanaHija *)GetWindowLong (hWnd, GWL_USERDATA); /* Proceso de mensajes */ switch (wMsg) { case WM_DESTROY: /* Destruir los datos de la ventana */ delete p; break; case WM_PAINT: { HDC dc = GetDC (hWnd); p->EvPaint (dc); ReleaseDC (hWnd, dc); return DefWindowProc (hWnd, wMsg, wParam, lParam); } break; default: /* Procesar todos los restantes mensajes */ return DefWindowProc (hWnd, wMsg, wParam, lParam); } return 0; } /*****************************************************************************\ *** C R E A C I O N D E L A V E N T A N A *** \*****************************************************************************/ int Init (void) { HWND Win; HWND MdiWin; CLIENTCREATESTRUCT ClientData; /* Registrar la ventana principal */ RegistrarVentanaPrincipal (); /* Registrar la ventana Hija */ RegistrarVentanaHija (); /* Crear la ventana principal */ Win = CreateWindowEx ( WS_EX_CLIENTEDGE, // Estilos extendidos de la ventana "Clase Ventana", // Nombre de la clase registrada "Ventana de ejemplo",// Ttulo de la ventana WS_TILEDWINDOW|WS_CLIPCHILDREN|WS_VISIBLE, // Estilo de la ventana

243

75.42 - Taller de Programacin

defecto) defecto)

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL,

// Posicin x en pixels (usar posicin por // Posicin y en pixels (usar posicin por // Ancho en pixels (usar valor por defecto) // Alto en pixels (usar valor por defecto) // Padre de la ventana (escritorio de

Windows)

NULL, // Men de la ventana inst, // Instancia del programa NULL); // Informacin adicional /* Registrar la ventana MDI Cliente */ MdiWin = CreateWindowEx ( WS_EX_CLIENTEDGE, // Estilos extendidos de la ventana "MDIClient", // Nombre de la clase registrada "", // Ttulo de la ventana WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_GROUP|WS_TABSTOP, // Estilo de la ventana 0, // Posicin x en pixels (usar posicin por defecto) 0, // Posicin y en pixels (usar posicin por defecto) 600, // Ancho en pixels (usar valor por defecto) 400, // Alto en pixels (usar valor por defecto) Win, // Padre de la ventana (escritorio de Windows) NULL, // Men de la ventana inst, // Instancia del programa &ClientData); // Informacin adicional /* Crear cuatro ventanas hijas */ Win = CrearVentanaHija (MdiWin, 1); Win = CrearVentanaHija (MdiWin, 2); Win = CrearVentanaHija (MdiWin, 3); Win = CrearVentanaHija (MdiWin, 4); } return OK;

/*****************************************************************************\ *** W I N M A I N *** \*****************************************************************************/ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { MSG msg; inst = hInstance; Init (); while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } return msg.wParam; } /*****************************************************************************\ *** F I N *** \*****************************************************************************/

244

75.42 - Taller de Programacin

Captulo IV - Hacia un nuevo modelo de programacin Windows


La necesidad de un nuevo modelo de programacin Windows

El ejemplo del captulo anterior comienza a presentar algunos de los inconvenientes de la programacin Windows. La forma en que se crean, obtienen y destruyen las variables o atributos de una ventana resulta bastante confusa y complicada. Adems, tan slo para crear el entorno de la aplicacin debieron escribirse muchas lneas de cdigo, las cuales sern idnticas para todas las aplicaciones que trabajen con mltiples ventanas, adems de que nuestro ejemplo fue corto y muy limitado. Pero afortunadamente se cuenta con la Programacin Orientada a Objetos y una herramienta muy poderosa de C, que hasta ahora no se haba utilizado mucho: las macros. Utilizando estas dos herramientas, varias empresas tales como Borland y Microsoft han desarrollado bibliotecas muy grandes, que simplifican enormemente la programacin Windows. La biblioteca de Borland se conoce con el nombre de Object Windows Library (OWL) y la de Microsoft con el nombre de Microsoft Foundation Classes (MFC). Ambas son muy parecidas, y se basan en las mismas ideas.

Borland - OWL (Object Windows Library)

Para empezar, pocas veces se utilizan los parmetros de la funcin WinMain() (salvo hInstance, que suele almacenrselo como una varaible global). Por el contrario, s resultara muy cmodo tener los parmetros de la lnea de comandos separados en la forma que se tena con la funcin main(). Por esta razn estas bibliotecas ya definen la funcin WinMain(). En el caso de OWL esta llamar a la funcin OwlMain(), (que ser el nuevo punto de entrada al programa), que recibe los parmetros en la forma habitual que se tena en DOS.

En el caso de OWL, se define una clase TApplication, la cual se encargar de crear la ventana principal (en el mtodo InitMainWindow) y ejecutar el ciclo habitual de mensajes: while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } Nosotros podemos ahora definir una nueva clase, heredera de TApplication, y sobrecargar el mtodo InitMainWindow, para crear la ventana que nosotros querramos. Ya que este mtodo es virtual, no se llamar el mtodo original sino el nuevo. class TMiAplicacion:public TApplication { char *Parametro; public: TMiAplicacion (char *Param):TApplication() { Parametro = Param; } virtual void InitMainWindow() { TFrameWindow *Win = new TFrameWindow (NULL, "Ejemplo"); SetMainWindow (Win); } };

245

75.42 - Taller de Programacin

Notar que en nuestro mtodo InitMainWindow, se crea un nuevo objeto, de tipo TFrameWindow. Este objeto define una ventana bsica, y para hacer algo til nuestra aplicacin deber construir un nuevo objeto, como heredero de esta clase. Veamos como queda nuestro programa hasta ahora: #include<owl\applicat.h> #include<owl\framewin.h> class TMiAplicacion:public TApplication { private: char *Parametro; public: TMiAplicacion (char *Param):TApplication() { Parametro = Param; } virtual void InitMainWindow() { TFrameWindow *Win = new TFrameWindow (NULL, "Ejemplo"); SetMainWindow (Win); } }; int OwlMain(int argc, char *argv[]) { TMiAplicacion App(argv[1]); App.Run(); return 0; } Muy bien, pero para poder desarrollar una aplicacin interesante, no debemos utilizar un objeto de tipo TFrameWindow, sino uno nuevo derivado de este, con las modificaciones que nosotros querramos. Veamos como hacerlo: /******************************************************************************\ *** # I N C L U D E s *** \******************************************************************************/ #include<owl\applicat.h> #include<owl\framewin.h> /******************************************************************************\ *** V E N T A N A P R I N C I P A L *** \******************************************************************************/ class TMiVentana:public TFrameWindow { private: int x,y; public: TMiVentana (TWindow *Padre, char *Titulo):TFrameWindow(Padre, Titulo) { x = y = 0; } LRESULT BotonIzquierdo (WPARAM, LPARAM lParam) { x = LOWORD (lParam); y = HIWORD (lParam); Invalidate (); return 0; } void Paint(TDC& dc, bool erase, TRect& rect)

246

75.42 - Taller de Programacin {

}; DEFINE_RESPONSE_TABLE1 (TMiVentana, TFrameWindow) EV_MESSAGE (WM_LBUTTONDOWN, BotonIzquierdo), END_RESPONSE_TABLE; /******************************************************************************\ *** A P L I C A C I O N *** \******************************************************************************/ class TMiAplicacion:public TApplication { public: void InitMainWindow() { TMiVentana *Win = new TMiVentana (NULL, "Ejemplo"); SetMainWindow (Win); } }; /******************************************************************************\ *** O W L M A I N *** \******************************************************************************/ int OwlMain(int /*argc*/, char */*argv*/[]) { TMiAplicacion App; App.Run(); return 0; } Nuestra aplicacin se basa en una ventana que imprime "Hola mundo", en la posicin donde se presiona el botn izquierdo del mouse.

dc.TextOut (x, y, "Hola mundo"); } DECLARE_RESPONSE_TABLE (TMiVentana);

Notar la definicin de una "tabla de respuestas". Esta tabla es en realidad un conjunto de macros que se expanden para formar una funcin y una tabla, encargada de procesar los mensajes y llamar a la funcin correspondiente. Lo que hacen es construir las funciones callback, que se vio en los captulos anteriores, y llamar a la funcin correspondiente del objeto. Para ms informacin, ver el ejemplo del captulo anterior. La forma en que se define esta tabla de respuesta es muy simple:

En la clase debe incluirse la lnea: DECLARE_RESPONSE_TABLE (clase derivada); Fuera de la clase deben escribirse las lneas: DEFINE_RESPONSE_TABLE1 (Clase derivada, clase base) EV_MESSAGE (mensaje, Funcin a llamar), ... EV_COMMAND(comando, Funcin a llamar), ... END_RESPONSE_TABLE; En el caso de EV_MESSAGE, la funcin recibir dos enteros (WPARAM y LPARAM) y retornar un entero (LRESULT). En el caso de EV_COMMAND, la funcin no debe recibir ni retornar nada.

247

75.42 - Taller de Programacin

Importante: La declaracin de la tabla de respuesta es obligatoria para todo objeto derivado de TWindow o sus derivados, an cuando esta est vaca.

Microsoft MFC

Microsoft simplific enormemente la programacin bajo Windows, agregando a su compilador un constructor automtico de cdigo fuente (Wizard). Las clases desarrolladas son similares a las de Borland, pero adems se tiene una gran cantidad de herramientas para agregar y modificar cdigo automticamente. Veamos un ejemplo. Queremos desarrollar un programa capaz de mostrar en pantalla un texto ingresado por el usuario.

Paso 1: Creacin del proyecto:

1. Seleccionar en el men archivos la opcin new (nuevo), y luego seleccionar la opcin nuevo proyecto.
2. Seleccionar como tipo de proyecto la opcin: MFC AppWizard (exe), indicando un nombre de directorio donde crear el proyecto, y como nombre del mismo: ejemplo1

3. En el paso 1 debe seleccionarse la opcin Single document, es decir un nico documento.


4. Todos los pasos siguientes pueden dejarse con las opciones por defecto.

Automticamente se generar un proyecto con los siguientes archivos:

ejemplo1.cpp:

Contiene la definicin de las clases CEjemplo1App, derivada de CWinApp y una ventana de dialogo "About", derivada de CDialog. La primera almacenar "la aplicacin", es decir, es la base sobre la cual se construir todo el programa.

ejemplo1.rc

Contiene los recursos del programa. Particularmente contiene la definicin de la ventana CAbout, entre otras cosas.

ejemplo1Doc.cpp

Contienen la informacin del documento. En programas que pueden contener mltiples documentos, cada uno de ellos debe almacenar la informacin en objetos de este tipo.

248

75.42 - Taller de Programacin ejemplo1View.cpp

La vista del documento. Un documento puede contener varias vistas.

MainFrm.cpp

Contiene el cdigo referente al entorno del programa, tales como la barra de estado, etc.

ReadMe.txt

Datos de la aplicacin.

StdAfx.cpp

Contiene definiciones varias, y los archivos header a precompilar.

Este es un buen momento para compilar el programa. Pruebe el funcionamiento del programa e intente leer y entender el cdigo.

Paso2: Agregar una variable String y mostrarla.

1.

El entorno de desarrollo contiene una ventana con una etiqueta denominada ClassView, que muestra todas las clases con sus mtodos y atributos. Seleccinela, y seleccione dentro de ella la clase CEjeplo1Doc.

249

75.42 - Taller de Programacin

2. Presione sobre ella el botn derecho del mouse, y seleccione la opcin agregar variable. 3. Ingrese como tipo de variable: CString, como nombre m_Info y como acceso Pblico. Microsoft acostrumbra
llamar a las variables de una clase con el prefijo m_, para indicar que son miembro de la misma.

4.

Abra el mtodo CEjemplo1Doc, (el contructor de la clase)

5.

Reemplace el comentario // TODO por la lnea: m_Info = "Hola mundo";

6. Seleccione y abra la clase CEjemplo1View en la etiqueta ClassView.


7. Haga doble click en el mtodo OnDraw(). Este mtodo ser llamado cada vez que se deba dibujar el contenido de la ventana.

8.

Reemplace el comentario // TODO por la lnea: pDC->TextOut (10,10,pDoc->m_Info);

9.

Compile nuevamente el programa.

250

75.42 - Taller de Programacin

Paso3: Agregar un dialogo

1.

Seleccione la segunda etiqueta de este mismo men, donde se encuentran los recursos, y abra el men del programa.

2. Agregue una opcin en el men View, con el ID: ID_VIEW_TEXTO y el texto: Texto de la ventana. Puede
agregar un texto de ayuda si lo desea.

3. Agregue un dialogo al programa. Esto lo puede hacer seleccionando la opcin Insertar recurso del men
Insertar, y seleccionando all el recurso dilogo.

4. Inserte un recuadro de edicin de texto, pulse sobre l el botn derecho del mouse y seleccione la opcin
Propiedades. Modifique el ID del recuadro a IDC_TEXTO.

5. Seleccione las propiedades del dialogo, y especifique un ID: IDD_TEXTO, Caption: Ingrese el texto a
mostrar.

6. Haga click con el botn derecho del mouse sobre la ventana, y seleccione la opcin ClassWizard. 7. En la ventana Adding a Class seleccione la opcin: Crear nueva clase. 8. Ingrese CDlgTexto como nombre de la clase y presione ENTER. El entorno ha creado para usted una nueva
clase CDlgTexto, derivada de CDialog, en los archivos DlgTexto.cpp y DlgTexto.h. Puede ver que esta informacin ha ya sido agregada en la etiqueta ClassView.

9. Seleccione la etiqueta Member Variables, en la cual se muestran las variables de la clase. 10. Seleccione la opcin IDC_TEXTO, y presione el botn Add Variables. Ingrese como nombre de la variable:
m_Texto, y presione el botn OK. Cierre el dialogo MFC ClassWizard presionando OK.

11. Abra el archivo ejemplo1View.cpp, y agregue la:


ON_COMMAND(ID_VIEW_TEXTO,ModificarTexto) a continuacin de la lnea: BEGIN_MESSAGE_MAP(CEjemplo1View, CView).

12. Seleccione la clase CEjemplo1View, presione el botn derecho y seleccione la opcin Agregar funcin. 13. Ingrese void como tipo de funcin y ModificarTexto como nombre. 251

75.42 - Taller de Programacin

14. Dentro del cdigo de la funcin ModificarTexto recin creada ingrese:


15.CDlgTexto DlgTexto; // Creo el dilogo 16.CEjemplo1Doc* pDoc = GetDocument(); 17.DlgTexto.m_Texto = pDoc->m_Info; // Cargo el texto inicial del dilogo 18.if (DlgTexto.DoModal()==IDOK) // Muestro el dialogo 19.{ 20. pDoc->m_Info = DlgTexto.m_Texto; // Leo el texto ingresado 21. Invalidate (); // Redibujar la ventana } 22. Vaya al comienzo del archivo y agregue la siguiente lnea, debajo de los #include ya existentes: #include "dlgTexto.h" 23. Ingrese este mismo #include al final del archivo stdafx.h. Esto har que el archivo sea precompilado.

24. Ingrese la siguiente lnea al comienzo del archivo dlgTexto.h. #include "resource.h" 25. Compile el programa nuevamente.

Puede verse que en el men Ver existe la opcin ModificarTexto, que permite modificar el texto mostrado en pantalla.

252

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