Sie sind auf Seite 1von 185

Programacion en C++

Libro de JAMC

PDF generado usando el kit de herramientas de fuente abierta mwlib. Ver http://code.pediapress.com/ para mayor informacin. PDF generated at: Mon, 23 Jul 2012 14:29:43 UTC

Contenidos
Artculos
Lo ms bsico Programacin en C++ Iteraciones y decisiones Estructuras Funciones Streams Arrays y cadenas de texto Desarrollo Orientado a Objetos Objetos y Clases Sobrecarga de Operadores Herencia Funciones virtuales Punteros Estructuras II Plantillas Excepciones Librera Estndar de Plantillas Problemas Resueltos 1 11 12 20 30 38 49 59 60 71 80 113 129 142 162 169 175 178

Referencias
Fuentes y contribuyentes del artculo Fuentes de imagen, Licencias y contribuyentes 181 182

Licencias de artculos
Licencia 183

Lo ms bsico

Lo ms bsico
Introduccion Iteraciones y decisiones

Proceso de desarrollo de un programa


Si se desea escribir un programa en C++ se debe ejecutar como mnimo los siguientes pasos: 1. Escribir con un editor de texto plano un programa sintcticamente vlido o usar un entorno de desarrollo (IDE) apropiado para tal fin 2. Compilar el programa y asegurarse de que no han habido errores de compilacin 3. Ejecutar el programa y comprobar que no hay errores de ejecucin Este ltimo paso es el ms costoso, por que en programas grandes, averiguar si hay o no un fallo prcticamente puede ser una tarea totmica. Como ejemplo, si se desea escribir un archivo con el nombre hola.cpp y en l escribir un programa con emacs, por ejemplo, que es un programa de edicin de textos, se puede, en Linux, ejecutar el siguiente comando: $emacs hola.cpp & Para otros sistemas operativos u otros entornos de desarrollo, no necesariamente se sigue este paso. A continuacin se escribe el siguiente cdigo en C++: Ejemplo
<font size="11.67"> // Aqu generalmente se suele indicar qu se quiere con el programa a hacer // Programa que muestra 'Hola mundo' por pantalla y finaliza

// Aqu se sitan todas las bibliotecas que se vayan a usar con #include, que se ver posteriormente // Las bibliotecas del sistema son las siguientes #include <iostream>

// Funcin main // Recibe: void // Devuelve: int // Es la funcin principal, y la encargada de mostrar "Hola Mundo", sin comillas, por pantalla int main(void) { // Este tipo de lneas de cdigo que comienzan por '//' son comentarios // El compilador los omite, y sirven para ayudar a otros programadores o a uno mismo en caso de volver a revisar el cdigo // Es una prctica sana poner comentarios donde se necesiten, //aunque estas lneas tan slo sirven para ayudar al lector a visualizar el potencial de los comentarios.

Lo ms bsico
std::cout << "Hola Mundo" << std::endl; // Mostrar por std::cout el mensaje Hola Mundo y comienza una nueva lnea

return 0; // Y como en este programa no se hace ms, se termina devolviendo un 0, //que en este caso quiere decir que la salida se ha efectuado con xito. } </font>

Mediante simple inspeccin, el cdigo parece enorme, pero el compilador lo nico que leer para la creacin del programa es lo siguiente: Ejemplo <font size="11.67"> #include <iostream> int main(void){ std::cout << "Hola Mundo" << std::endl; return 0; } </font> Como se puede observar, este cdigo y el original no difieren en mucho salvo en los saltos de lnea y que los comentarios, de los que se detallan posteriormente, estn omitidos y tan slo ha quedado "el esqueleto" del cdigo legible para el compilador. Para el compilador, todo lo dems, sobra. O este otro, que es, en parte, como el lenguaje C, en su versin C99, es: Ejemplo <font size="11.67"> #include <stdio.h> #include <stdlib.h> int main(void) { //este texto solo lo puede leer el programador printf( "Hola Mundo\n" ); return EXIT_SUCCESS; // 'EXIT_SUCCESS' es una definicin que est dentro de 'stdlib.h' } </font> Nota: si se usa Windows, el cdigo es muy similar: Ejemplo <font size="11.67"> #include <iostream> int main(void) { std::cout << "Hola Mundo" << std::endl; std::cin.get(); // Esto se pone, a diferencia de en los otros cdigos, por que en windows la ventana se cerrar sbitamente. //con 'std::cin.get();' lo que se hace es esperar hasta que el usuario pulse enter. return 0; }

Lo ms bsico </font> Los pasos siguientes son para una compilacin en Linux o sistema operativo Unix, exceptuando el sistema operativo MacOSX. En Windows tampoco es aplicable. Con ctrl-x ctrl-s se guarda el archivo. Ahora para generar el ejecutable del programa se compila con g++ de la siguiente forma: $ g++ hola.cpp -o hola Para poder ver los resultados del programa en accin, se ejecuta el programa de la siguiente forma: $./hola Y a continuacin se debe mostrar algo como lo siguiente: Hola Mundo

Comentarios
Cuando se escriben programas es muy til agregar comentarios que ayuden a explicar lo que realiza un programa. En C++ se pueden utilizar tres tipos de comentarios: al estilo C, al estilo C++ y usando preprocesador. Los comentarios al estilo C se caracterizan por lo siguiente: comenzar el "bloque" de comentarios con /* y terminar dicho "bloque" de comentarios con */ Un comentario dentro del cdigo al estilo C puede ser como se muestra a continuacin: <font size="11.67"> /* Este es un comentario al estilo C */ </font> Los comentarios al estilo C pueden continuar por varias lneas de un mismo archivo como se muestra en el siguiente cdigo: <font size="11.67"> /* Este es un comentario al estilo C que puede ocupar varias lneas como se muestra a continuacin. */ </font> El siguiente bloque de comentarios en algunos compiladores puede provocar fallos, por lo que no se recomienda su uso: <font size="11.67"> /* Ahora el programador, por accidente, incluy un */ antes de tiempo, por lo que el programa tendr fallos. */ </font>

<--

Usando el estilo de cdigo de C++ slo pueden ocupar una lnea como en el siguiente cdigo: <font size="11.67"> // Este es un comentario al estilo C++

Lo ms bsico </font> Este es el tipo de comentarios ms utilizados en programas en los que se usa nicamente C++, por que generalmente los programadores resumen partes del cdigo que puedan provocar confusin o sean de difcil lectura para otros programadores. Una buena prctica de programacin es pensar que se programa a sabiendas de que otro programador, tal vez el lector mismo en un futuro, tenga que "desencriptar" qu quiso hacer ah y por qu. Otra posible forma de comentar cdigo es usando el preprocesador. Esto se detallar ms adelante en profundidad, por ahora la parte til del preprocesador que interesa es la siguiente: <font size="11.67"> #if 0 Comentarios sobre el programa /parte del programa. Pueden ocupar mltiples lneas. Ms complicado de visualizar que los comentarios C/C++ #endif </font> Este tipo de comentarios se usan rara vez. Generalmente son difciles de localizar, incluso para programadores experimentados que trabajan en papel, y son fciles de distinguir en casi cualquier IDE. Es recomendable indicar que se tratan de comentarios, o directamente no usarlos, salvo si son grandes cantidades de comentarios. Se ver ms adelante que tambin puede tener otros usos.

Utilizacin de la consola o terminal


En los ejemplos anteriores se utilizaron 'std::cout'. 'std::cout' es un "objeto" que permite escribir en la consola (la terminal en Linux/Unix/MacOSX), solo se puede utilizar gracias a que se ha incluido definiciones de su uso con la lnea de cdigo '#include <iostream>'. <font size="11.67"> std::cout << ALGO; </font> Donde ALGO puede ser lo que sea que 'std::cout' sea capaz de mostrar por consola. Ms adelante se trata ms sobre el en detalle y aclarando posibles dudas que ahora puedan surgir. Tambin se utiliz 'std::endl', esto permite que el texto se escriba en una nueva lnea. Un ejemplo ms completo sobre la escritura en consola es el siguiente. Ha de tenerse en cuenta que se han eliminado algunos comentarios superfluos del primer programa debido a que ahora se est tratando con ms detalle acerca del uso de imprimir texto en la consola: Ejemplo
<font size="11.67">

// Programa que muestra diversos textos por consola

// Las libreras del sistema usadas son las siguientes

#include <iostream>

// Funcin: main

// Recibe: void

Lo ms bsico
// Devuelve: int

// Es la funcin principal encargada de mostrar por consola diferentes

textos

int main(void)

// Ejemplo con una nica lnea, se muestra el uso de std::cout y

std::endl

std::cout << "Bienvenido. Soy un programa. Estoy en una linea de codigo." << std::endl;

// Ejemplo con una nica lnea de cdigo que se puede fraccionar

mediante el uso de '<<'

std::cout << "Ahora " << "estoy fraccionado en el codigo, pero en la consola me muestro como una unica frase." << std::endl;

// Uso de un cdigo largo, que cuesta leer para un programador, y

que se ejecutar sin problemas.

// *** No se recomienda hacer lneas de esta manera, esta forma de

programar no es apropiada ***

std::cout << "Un gran texto puede ocupar muchas lineas." << std::endl << "Pero eso no frena al programador a que todo se pueda poner en una unica linea de codigo y que" << std::endl << "el programa, al ejecutarse, lo situe como el programador quiso" << std::endl;

return 0; // Y se termina con xito.

</font>

Se reta a compilar este cdigo y a observar sus resultados. En este momento el lector est capacitado para escribir programas que impriman por pantalla el mensaje que se quiera. Atencin: Se hace hincapi en la posibilidad de que las palabras acentuadas no se puedan mostrar en la consola. Depende completamente del compilador el que se pueda ver lo siguiente: <font size="11.67"> std::cout << "programacin"; </font> Con algunos compiladores, ver 'programacin', pero con otros puede ver incluso 'programaci n'. Advertencia: cout puede ser utilizado sin tener std:: de forma previa porque se puede introducir una directiva, denominada 'using', que acomoda todos los cout. De otro modo habra un error de compilador. Este tema se trata en detalle ms adelante. <font size="11.67"> using namespace std; </font>

Lo ms bsico

Sintaxis
Sintaxis es la forma correcta en que se deben escribir las instrucciones para el computador en un lenguaje de programacin especifico. C++ hereda la sintaxis de C estndar, es decir, la mayora de programas escritos para el C estndar pueden ser compilados en C++.

El punto y coma
El punto y coma es uno de los simblos ms usados en C, C++; y se usa con el fin de indicar el final de una lnea de instruccin. El punto y coma es de uso obligatorio. ejemplo clrscr(); //Limpiar pantalla, funciona solo con la librera conio de Borland C++ x = a + b; El punto y coma se usa tambin para separar contadores, condicionales e incrementadores dentro de un sentencia for ejemplo for (i=0; i < 10; i++) cout << i;

Separador de bloque
Un bloque es un grupo de instrucciones contenidas entre los smbolos de llave izquierda '{' y llave derecha '}', su uso es obligatorio en la definicin de funciones, y opcionalmente pueden aparecer en cualquier otra parte del programa. ejemplos // aqu, las llaves son opcionales ya que dentro // del ciclo for hay solamente una instruccin. for (i=0; i < 10; i++) { cout << i; }

Espacios y tabuladores
Usar caracteres extras de espaciado o tabuladores ( caracteres tab ) es un mecanismo que nos permite ordenar de manera ms clara el cdigo del programa que estemos escribiendo, sin embargo, el uso de estos es opcional ya que el compilador ignora la presencia de los mismos. Por ejemplo, el segundo de los ejemplos anteriores se podra escribir como: for (int i=0; i < 10; i++) { cout << i * x; x++; } y el compilador no pondra ningn reparo.

Tipos primitivos
En un lenguaje de programacin es indispensable poder almacenar informacin, para esto en C++ estn disponibles los siguientes tipos que permiten almacenar informacin numrica de tipo entero o real: El modificador long El modificador long le indica al compilador que el tipo debe utilizar ms bits que los normalmente utilizados por ejemplo si tenemos en una maquina de 32 bits como un Pentium de Intel, normalmente de un int ocupara 32 bits, pero si al declararlo un entero le antecedemos long el entero ocupa 64 bits, el siguiente cdigo muestra como utilizar este modificador:

Lo ms bsico int corto; // Entero de 32 bits long int largo; // Entero de 64 bits El Modificador short Similar al anterior, pero indica que se deben utilizar menos bits. Por ejemplo, en una mquina de 32 bits, un short int ocupa 16 bits. El Modificador unsigned El modificador unsigned es utilizado nicamente con los enteros, su utilizacin permite utilizar en los enteros nicamente la parte positiva, int a; // Almacena valores entre -128 y 127 unsigned int a; // Almacena valores entre 0 y 255 El Modificador register Este modificador sobre una variable le indica al compilador que la variable debe almacenarse en un registro en el compilador, que para el caso de los IA32, es un registro real de la propia CPU, y por tanto el tiempo de acceso es ms rpido respecto a la memoria RAM. Hoy en da a penas se utiliza este modificador, ya que los compiladores son capaces de determinar de manera ptima la asignacin de registros a variables del programa. El Modificador volatile Al contrario que el modificador registrer, volatile obliga al compilador a forzar el cdigo resultante de manera que la variable modificada con este modificador, sea almacenada siempre en la memoria. El efecto que tiene es que cuando la variable se modifica con otro valor, dicha variable se almacena directamente en memoria y no queda localizado el valor slo en el registro de la CPU como pasaba en el caso de register o en condiciones normales. Un uso muy comn en el que se suele emplear este modificador, es para acceder a variables que estn siendo utilizadas por drivers o por perifricos, ya que si no declarsemos esta propiedad, podra darse el caso que la CPU usase el valor de la variable, por lo que la cach guarda el valor, y poco despus la CPU usase de nuevo dicha variable, pero como sta est en cache, la CPU coge el valor que existe en la cach, que puede ser bien distinta al real puesto que un posible perifrico puede haber modificado su valor. El Modificador static Dependiendo del entorno donde se declare la variable que la modifiquemos como static, puede significar dos cosas muy distintas 1. Si declaramos una variable static dentro del cuerpo de una funcin, lo que estamos indicndole al compilador es que dicha variable sea inicializada solo una vez (la primera vez que se llama a la funcin), y el resto de veces que se llame a la funcin, la variable contendr el ltimo valor asignado. Esta variable slo podr ser visible desde la funcin que declara dicha variable. Por ejemplo: void mifuncion(){ static int i=0; cout<<"En la entrada i vale "<<i<<endl; for(int j=0;j<10;j++) i++; cout<<"En la salida i vale "<<i<<endl; } 1. Si declaramos una variable static fuera del cuerpo de una funcin, lo que le estamos indicando al compilador, es que dicha variable es privada para el modulo donde se implementa el cdigo del contexto de la variable, es decir,

Lo ms bsico que otro fichero objeto binario, no podr tener acceso a dicha variable utilizando el modificador extern. Ejemplo: #import "prueba.h" //variable privada para prueba.cpp static int i=0; void mifuncion(){ cout<<"En la entrada i vale "<<i<<endl; for(int j=0;j<10;j++) i++; cout<<"En la salida i vale "<<i<<endl; } Tenga en cuenta que para este ltimo caso, usted podr acceder a la variable y desde cualquier funcin que defina dentro de prueba.cpp, pero no tendr acceso desde cualquier fichero objeto o fuente que no sea prueba.cpp

Espacio que ocupan la variables (en mquinas x86)


El espacio en bits que ocupan en la computadora una variable de este tipo se puede ver en la siguiente tabla:
Tipo char short int Nmero de Bits 8 16 32

long int 64 float double 32 64

Rango de los Tipos Primitivos El rango que puede almacenar los tipos primitivos en C++ es muy importante, para poder saber cual es el rango de valores que puede almacenar un tipo es necesario conocer el nmero de bits del tipo. El caso para enteros y para flotantes es distinto. Para enteros se debe saber si el tipo es con signo o sin signo. si es sin signo el rango de valores que puede almacenar es el siguiente:

Si el tipo es con signo el rango es el siguiente

Para ilustrar lo anterior supongamos que tenemos un entero de 16 bits sin signo, entonces el rango de valores que puede almacenar es el siguiente:

Para obtener el rango de un entero de 32 bits con signo se puede realizar el siguiente calculo:

El caso de los nmeros flotantes es distinto y depende en gran manera del compilador y el procesador que este utilizando. Sin embargo hoy en da la mayora de los compiladores y los procesadores utilizan en estndar de la IEEE para representacin en coma flotante. Para saber mas al respecto ver IEEE floating-point standard.

Lo ms bsico

Tipos enumerados (enum)


Los tipos enumerados son un mecanismo usado en C y C++ con el objetivo de agrupar de alguna manera constantes simblicas. Para definir tipos enumerados se usa la palabra reservada enum. Ejemplo 1 enum dias { domingo, lunes, martes, miercoles, jueves, viernes, sabado }; En el ejemplo anterior se define por medio de enum el tipo enumerado dias, en el mismo ejemplo se debe observar que dentro de la construccin se definen las constantes simblicas: domingo, lunes, ... sabado; y que a las mismas el compilador les asignar respectivamente y por defecto los valores: 0, 1, 2, 3, 4, 5, 6. Es decir, las constantes mencionadas pueden usarse dentro del programa y este sabr a que valor hace referencia cada una de las mismas. Por ejemplo, la instruccin: cout << domingo; desplegar 0 en la pantalla. El siguiente ejemplo muestra como usar las constantes enumeradas en un ciclo for. for (int d = domingo; d <= sabado; d++) cout << d; En el siguiente ejemplo se define por medio de enum el tipo enumerado colores, en el mismo se debe de observar la elinacin del comportamiento por defecto ya que a la primera constante (gris) se le asigna en forma especfica el valor de 7, de tal manera que la siguiente constante (grisoscuro) tendr un valor igual a 8 y la constante amarillo ser igual a 14. Ejemplo 2 enum colores { gris = 7, grisoscuro, amarillo = 14 };

Tipos definidos por el usuario


En muchas ocasiones descubriremos que los tipos primitivos no bastan para llevar a cabo ciertas tareas, debido a esto el lenguaje C, C++ da el soporte necesario para que el programador defina sus propios tipos. Para definir tipos se usa la palabra reservada typedef. Por ejemplo, si deseamos definir un tipo de dato llamado entero podemos usar la sintaxis: typedef int entero; Luego, podremos declarar variables, constantes y funciones del tipo entero. Por ejemplo, entero edad = 33; Un uso ms til y comn es el empleo de typedef para definir datos estructurados. Por ejemplo, supongamos que deseamos definir un tipo estructurado llamado persona y que contenga nombre, edad y sexo. Entonces podemos definir persona como: typedef struct persona { char nombre[32]; int edad; char sexo; }; Una vez que un tipo ya se ha definido, el mismo puede emplearse para declarar variables y constantes de este. Por ejemplo, con la instruccin: persona hombre;

Lo ms bsico se est declarando la variable hombre del tipo persona. Luego, para acceder a cada elemento de la variable hombre usaremos un mecanismo conocido como direccionamiento directo por medio del caracter de punto ( . ). Por ejemplo, la edad de hombre se debe indicar como: hombre.edad

10

Variables y constantes
Una variable, como su nombre lo indica, es un determinado objeto cuyo valor puede cambiar durante el proceso de una tarea especfica. Contrario a una variable, una constante es un determinado objeto cuyo valor no puede ser alterado durante el proceso de una tarea especfica. En C, C++ para declarar variables no existe una palabra especial, es decir, las variables se declarn escribiendo el tipo seguido de uno o ms identificadores o nombres de variables. Por otro lado, para declarar constantes existe la palabra reservada const, as como la directiva #define. A continuacin se muestran ejemplos de declaracin de variables y constantes.
Variables int a; float b; Constantes const int a = 100; Constantes #define a 100

const float b = 100; #define b 100

Notas: A diferencia de las constantes declaradas con la palabra const los smbolos definidos con #define no ocupan espacio en la memoria del cdigo ejecutable resultante. El tipo de la variable o constante puede ser cualquiera de los listados en Tipos primitivos, o bien de un tipo definido por el usuario. Las constantes son usadas a menudo con un doble propsito, el primero es con el fin de hacer ms legible el cdigo del programa, es decir, si se tiene (por ejemplo) la constante numerica 3.1416 y esta representa al nmero pi, entonces podemos hacer declaraciones tales como: #define pi 3.1416 En este caso podremos usar la palabra pi en cualquier parte del programa y el compilador se encargar de cambiar dicho simbolo por 3.1416. o bien,
const pi = 3.1416;

En este otro caso podremos usar la palabra pi en cualquier parte del programa y el compilador se encargar de cambiar dicho smbolo

por una referencia a la constante pi guardada en la memoria.


Introduccion Arriba Iteraciones y decisiones

Programacin en C++

11

Programacin en C++
Bienvenidos al Wikilibro de Programacin en C++

Si tienes algn comentario, crtica, elogio o sugerencia acerca del material incluido en este wikilibro puede usar la pgina de discusin, para ello puede hacer clic sobre las pestaas de arriba en donde aparece la palabra discusin. Si lo prefieres, puede colaborar en la edicin de este documento pues no necesita ningn permiso especial. As, si encuentras errores ortogrficos o te parece que alguna seccin est mal redactada, sintete libre de hacer responsablemente las correcciones necesarias.

Tabla de contenido
1. Introduccin 2. Captulo 1: Lo ms bsico 3. Captulo 2: Iteraciones y decisiones 4. Captulo 3: Estructuras I 5. Captulo 4: Funciones 6. Captulo 5: Streams ( entrada y salida de datos ) 7. Captulo 6: Arrays y cadenas de texto 8. Captulo 7: Desarrollo Orientado a Objetos 9. Captulo 8: Objetos y Clases 10. Captulo 9: Sobrecargado de operadores 11. Captulo 10: Herencia 12. Captulo 11: Funciones virtuales y Polimorfismo 13. Captulo 12: Punteros 14. Captulo 13: Estructuras II 15. Captulo 14: Plantillas 16. Captulo 15: Excepciones 17. Captulo 16: Biblioteca Estndar de Plantillas (STL) 18. Captulo 17: Analisis y Diseo orientado a Objetos 1. Introduccin al desarrollo de Software 2. Analisis del Problema 3. Diseo de la solucin (software) 4. Representacin del problema y la solucin (notacin) 19. Captulo 18: Problemas Resueltos

Programacin en C++

12

Enlaces externos
Tutorial de C++ [1] Tutoriales y descripciones de funciones [2] Uno de los mejores sitios sobre C++ [3] (en ingls) Posible estndar de creacin de cdigo [4] (en ingls)

Bibliografa
Cmo programar en C/C++, H.M. Deitel, P.J. Deitel, Ed. Prentice Hall Aprenda C++, Jeese Liberty, David Howard, Ed. Anaya
Se autoriza la copia, la distribucin y la modificacin de este documento bajo los trminos de la licencia de documentacin libre GNU, versin 1.2 o cualquier otra que posteriormente publique la Fundacin del Software Libre (Free Software Fundation); sin secciones invariantes (Unvariant Sections), textos de portada (Front-Cover Texts), ni textos de contraportada (Back-Cover Texts). Se incluye una copia en ingls de esta licencia en el artculo Text of the GNU Free Documentation License.

Referencias
[1] [2] [3] [4] http:/ / www. zator. com/ Cpp/ E_Ce. htm http:/ / c. conclase. net http:/ / www. cplusplus. com http:/ / www. possibility. com/ Cpp/ CppCodingStandard. html

Iteraciones y decisiones
Lo ms bsico Estructuras

Sentencias de decisin
DEFINICIN Las sentencias de decisin o tambin llamadas de CONTROL DE FLUJO son estructuras de control que realizan una pregunta la cual retorna verdadero o falso (evala una condicion) y selecciona la siguiente instruccin a ejecutar dependiendo la respuesta o resultado.

Estructura de control IF

Iteraciones y decisiones

13

En algn momento dentro de nuestros algoritmos, es preciso cambiar el flujo de ejecucin de las instrucciones, es decir, el orden en que las instrucciones son ejecutadas. Muchas de las veces tenemos que tomar una decisin en cuanto a que se debe ejecutar basndonos en una respuesta de verdadero o falso (condicion). La ejecucin de las instrucciones incluyendo una estructura de control como el condicional funcionan de esta manera: Las instrucciones comienzan a ejecutarse de forma secuencial (en orden) y cuando se llega a una estructura condicional, la cual esta asociada a una condicion, se decide que camino tomar dependiendo siempre del resultado de la condicion siendo esta falsa o verdadera. Cuando se termina de ejecutar este bloque de instrucciones se reanuda la ejecucin en la instruccin siguiente a la de la condicional.

Sentencia if
La instruccin if es, por excelencia, la ms utilizada para construir estructuras de control de flujo. SINTAXIS Primera Forma Ahora bin, la sintaxis utilizada en la programacin de C++ es la siguiente: if (condicion) { Set de instrucciones } siendo "condicion" el lugar donde se pondr la condicin que se tiene que cumplir para que sea verdadera la sentencia y as proceder a realizar el "set de instrucciones" o cdigo contenido dentro de la sentencia. Segunda Forma Ahora veremos la misma sintaxis pero ahora le aadiremos la parte "Falsa" de la sentencia: if (condicion) { Set de instrucciones } else { Set de instrucciones 2 }

//PARTE VERDADERA

//Parte FALSA

La forma mostrada anteriormente muestra la union de la parte "VERDADERA" con la nueva secuencia la cual es la parte "FALSA" de la sentencia de decision "IF" en la cual esta compuesta por el: else { Set de instrucciones 2 } //Parte FALSA

Iteraciones y decisiones la palabra "else" o "De lo contrario" indica al lenguaje que de lo contrario al no ser verdadera o no se cumpla la parte verdadera entonces realizara el "set de instrucciones 2". EJEMPLOS DE SENTENCIAS IF... Ejemplo 1: if(numero == 0) //La condicion indica que tiene que ser igual a Cero { cout<<"El Numero Ingresado es Igual a Cero"; } Ejemplo 2: if(numero > 0) // la condicion indica que tiene que ser mayor a Cero { cout<<"El Numero Ingresado es Mayor a Cero"; } Ejemplo 3: if(numero < 0) // la condicion indica que tiene que ser menor a Cero { cout<<"El Numero Ingresado es Menor a Cero"; } Ahora uniremos todos estos ejemplos para formar un solo programa mediante la utilizacin de la sentencia "Else" e introduciremos el hecho de que se puede escribir en este espacio una sentencia if ya que podemos ingresar cualquier tipo de cdigo dentro de la sentencia escrita despus de un Else. Ejemplo 4: if(numero == 0) //La condicion indica que tiene que ser igual a Cero { cout<<"El Numero Ingresado es Igual a Cero"; } else { if(numero > 0) // la condicion indica que tiene que ser mayor a Cero { cout<<"El Numero Ingresado es Mayor a Cero"; } else { if(numero < 0) // la condicion indica que tiene que ser menor a Cero { cout<<"El Numero Ingresado es Menor a Cero"; } } }

14

Iteraciones y decisiones

15

Sentencia switch
switch es otra de las instrucciones que permiten la construccin de estructuras de control. A diferencia de if, para controlar el flujo por medio de una sentencia switch se debe de combinar con el uso de las sentencias case y break. Notas: cualquier nmero de casos a evaluar por switch as como la sentencia default son opcionales. La sentencia switch es muy til en los casos de presentacin de menus. Sintaxis: switch (condicin) { case primer_caso: bloque de instrucciones 1 break; case segundo_caso: bloque de instrucciones 2 break; case caso_n: bloque de instrucciones n break; default: bloque de instrucciones por defecto } Ejemplo 1 switch (numero) { case 0: cout << "numero es cero"; } Ejemplo 2 switch (opcion) { case 0: cout << "Su opcion es cero"; break; case 1: cout << "Su opcion es uno"; break; case 2: cout << "Su opcion es dos"; } Ejemplo 3 switch (opcion) { case 1: cout << "Su opcion es 1"; break; case 2: cout << "Su opcion es 2"; break; case 3: cout << "Su opcion es 3"; break; default: cout << "Elija una opcion entre 1 y 3"; }

Iteraciones y decisiones

16

Operador condicional ternario ?:


En C, existe el operador condicional ( ?: ) el cual es conocido por su estructura como ternario. El comportamiento de dicho operador es el mismo que una estructura if - then - else del lenguaje BASIC (y de la funcin IIf de Visual Basic). El operador condicional ?: es til para evaluar situaciones tales como: Si se cumple tal condicin entonces haz esto, de lo contrario haz esto otro. Sintaxis: ( (condicion) ? proceso1 : proceso2 ) En donde, condicion es la expresin que se evalua, proceso1 es la tarea a realizar en el caso de que la evaluacin resulte verdadera, y proceso2 es la tarea a realizar en el caso de que la evaluacin resulte falsa. Ejemplo 1 int edad; cout << "Cual es tu edad: "; cin >> edad; cout << ( (edad < 18) ? "Eres joven aun" : "Ya tienes la mayora de edad" ); El ejemplo anterior podra escribirse de la siguiente manera: int edad; cout << "Cual es tu edad: "; cin >> edad; if (edad < 18) cout << "Eres joven aun"; else cout << "Ya tienes la mayora de edad"; Ejemplo 2 Vamos a suponer que deseamos escribir una funcin que opere sobre dos valores numricos y que la misma ha de regresar 1 (true) en caso de que el primer valor pasado sea igual al segundo valor; en caso contrario la funcin debe retornar 0 (false). int es_igual( int a, int b) { return ( (a == b) ? 1 : 0 ) }

Sentencias de iteracin
DEFINICIN Las Sentencias de Iteracin o Ciclos son estructuras de control que repiten la ejecucin de un grupo de instrucciones. Bsicamente, una sentencia de iteracin es una estructura de control condicional, ya que dentro de la misma se repite la ejecucin de una o ms instrucciones mientras que una a condicin especifica se cumpla. Muchas veces tenemos que repetir un nmero definido o indefinido de veces un grupo de instrucciones por lo que en estos casos utilizamos este tipo de sentencias. en C++ los ciclos o bucles se construyen por medio de las sentencias for, while y do - while. La sentencia for es til para los casos en donde se conoce de antemano el nmero de veces que una o ms sentencias han de repetirse. Por otro lado, la sentencia while es til en aquellos casos en donde no se conoce de antemano el nmero de veces que una o ms sentencias se tienen que repetir.

Iteraciones y decisiones

17

Sentencias For
for(contador; final; incremento) { Codigo a Repetir; } donde: 1. contador es una variable numrica 2. final es la condicin que se evalua para finalizar el ciclo (puede ser independiente del contador) 3. incremento es el valor que se suma o resta al contador Hay que tener en cuenta que el "for" evalua la condicin de finalizacin igual que el while, es decir, mientras esta se cumpla continuaran las repeticiones. Ejemplo 1: for(int i=1; i<=10; i++) { cout<<"Hola Mundo"; } Esto indica que el contador "i" inicia desde 1 y continuar iterando mientras i sea menor o igual a 10 ( en este caso llegar hasta 10) e "i++" realiza la sumatoria por unidad lo que hace que el for y el contador se sumen. repitiendo 10 veces "HOLA MUNDO" en pantalla. Ejemplo 2: for(int i=10; i>=0; i--) { cout<<"Hola Mundo"; } Este ejemplo hace lo mismo que el primero, salvo que el contador se inicializa a 10 en lugar de 1; y por ello cambia la condicin que se evalua as como que el contador se decrementa en lugar de ser incrementado. La condicin tambin puede ser independiente del contador: Ejemplo 3: int j = 20; for(int i=0; j>0; i++){ cout<<"Hola"<<i<<" - "<<j<<endl; j--; } En este ejemplo las iteraciones continuaran mientras j sea mayor que 0, sin tener en cuenta el valor que pueda tener i.

Iteraciones y decisiones

18

Sentencia while
while(condicion) { cdigo a Repetir } donde: 1. condicion es la expresin a evaluar Ejemplo 1: int contador = 0; while(contador<=10) { contador=contador+1; cout<<"Hola Mundo"; } El contador Indica que hasta que este llegue a el total de 10 entonces se detendr y ya no se realizar el cdigo contenido dentro de la sentencia while, de lo contrario mientras el "contador" sea menor a 10 entonces el cdigo contenido se ejecutar desplegando hasta 10 veces "Hola Mundo" en pantalla.

Sentencia do - while
La sentencia do es usada generalmente en cooperacin con while para garantizar que una o ms instrucciones se ejecuten al menos una vez. Por ejemplo, en la siguiente construccin no se ejecuta nada dentro del ciclo while, el hecho es que el contador inicialmente vale cero y la condicin para que se ejecute lo que est dentro del while es "mientras el contador sea mayor que diez". Es evidente que a la primera evaluacin hecha por while la condicin deja de cumplirse. int contador = 0; while(contador > 10) { contador ++; cout<<"Hola Mundo"; } Al modificar el segmento de cdigo anterior usando do tenemos: int contador = 0; do { contador ++; cout<<"Hola Mundo"; } while(contador > 10); Observe cmo en el caso de do la condicin es evaluada al final en lugar de al principio del bloque de instrucciones y, por lo tanto, el cdigo que le sigue al do se ejecuta al menos la primera vez.

Iteraciones y decisiones

19

Sentencias break y continue


En la seccin (Sentencia switch) vimos que la sentencia break es utilizada con el propsito de forzar un salto dentro del bloque switch hacia el final del mismo. En esta seccin volveremos a ver el uso de break, salvo que esta ocasin la usaremos junto con las sentecias for y la sentencia while. Adems, veremos el uso de la sentencia continue.

break
La sentencia break se usa para forzar un salto hacia el final de un ciclo controlado por for o por while. Ejemplo: En el siguiente fragmento de cdigo la sentencia break cierra el ciclo for cuando la variable ( i ) es igual a 5. La salida para el mismo ser: 01234 for (int i=0; i<10; i++) { if (i == 5) break; cout << i << " "; }

continue
La sentencia continue se usa para ignorar una iteracin dentro de un ciclo controlado por for o por while. Ejemplo: En el siguiente fragmento de cdigo la sentencia continue ignora la iteracin cuando la variable ( i ) es igual a 5. La salida para el mismo ser: 012346789 for (int i=0; i<10; i++) { if (i == 5) continue; cout << i << " "; } Uso de break y continue junto con while Los dos ejemplos anteriores se presentan en seguida, salvo que en lugar de for se hace uso de while. Nota: no deje de observar que la construccin del ciclo while para el caso de la sentencia continue es diferente, esto para garantizar que el ciclo no vaya a caer en una iteracin infinita. break int i = 0; while (i<10) { if (i == 5) break; cout << i << " "; i++; } continue int i = -1; while (i<10) { i++; if (i == 5) continue;

Iteraciones y decisiones cout << i << " "; }


Lo ms bsico Arriba Estructuras

20

Estructuras
Iteraciones y decisiones Funciones

Estructuras de datos
Las estructuras de datos se emplean con el objetivo principal de organizar los datos contenidos dentro de la memoria del ordenador. As, nuestra primera experiencia con estructuras comienza desde el momento mismo en que usamos en nuestros programas variables de tipos primitivos (char, short, int, float, etc). A la memoria del ordenador se le puede considerar como un gran bloque compuesto por una serie de BYTES dispuestos secuencialmente uno detrs de otro. por ejemplo, si un ordenador posee una memoria de 128MB (128 megas) entonces se le puede leer o escribir desde el BYTE 0 hasta el BYTE 128MB - 1 ( 0000000H .. 7FFFFFFH ). La idea de ver la memoria como un serie de bytes es buena, sin embargo no es suficiente ya que en la misma podemos guardar nmeros, cadenas de caracteres, funciones, objetos, etc. de tal manera que surge la necesidad de establecer los mecanismos adecuados para dar cuenta de la forma, tamao y objetivo de los datos almacenados. Segn el tipo de microprocesador, estos tienen la capacidad para manipular o direccionar estructuras compuestas por uno, dos, cuatro, etc, bytes; de donde se derivan los tipos que comunmente se conocen como: BYTE, WORD, DWORD, QWORD y TWORD. La estructura mnima de informacin manipulable en un sistema de memoria de ordenadores es el BIT el cual se agrupa normalmente en bloques de 8 para formar un BYTE. Cabe mencionar que los BITS no son direccionables directamente, sino a travez de compuertas AND, OR, NOT, XOR, mismas que en C,C++ se escriben como &, |, ~ y ^ conocidos como "Bitwise operators". En C,C++ existe una serie de estructuras bsicas o tipos primitivos, los cuales pueden ser usados por el programador para declarar variables, y tambin son el fundamento sobre el cual se crean estructuras complejas. El tamao de los tipos primitivos no es estndar ya que los mismos dependen de factores tales como: Tipo del microprocesador El compilador Sin embargo, en la actualidad, la mayoria de compiladores de C,C++ soportan los siguientes tipos con la longitud indicada:

Estructuras bsicas en C, C++

Estructuras

21

Tipos primitivos
Nombre comn Nombre C Longitud BYTE WORD DWORD DWORD DWORD QWORD TWORD char short int long float double 8 bits 16 bits 32 bits 32 bits 32 bits 64 bits

long double 80 bits

Nota: en el lenguaje C,C++ existe el operador sizeof(), con el cual se puede obtener el tamao (nmero de bytes) ocupados por un tipo especfico. Por ejemplo, sizeof(int) regresa el nmero de bytes ocupados por lo datos de tipo int.

variables
En C, C++ la sintaxis para declarar variables es: tipo id1 [, id2 ...] ; donde, tipo se refiere a uno de los tipos mostrados en la tabla anterior; id1 es el nombre con el cual se identificar a la variable. Observe que si se quiere declarar ms de una variable en una lnea de instruccin, las mismas deben separarse por medio de una coma. Ejemplos: char c; int i; float f; int x,y,z; declaradas a // // // // la La variable c La variable i La variable f Las variables vez es una variable es una variable es una variable x,y,z son todas char int float variables int, y

De acuerdo con la tabla anterior y segn las instrucciones anteriores, con la primera, o sea ( char c;), se le est indicando al ordenador que reserve en la memoria un espacio de tipo char (8 bits) y que el mismo ser identificado bajo el nombre de c. La segunda instruccin ( int i;) le indica al ordenador que reserve en la memoria un espacio de tipo int (16 bits) y que el mismo ser identificado bajo el nombre de i. Luego, la instruccin ( float f;) le indica al ordenador que reserve en la memoria un espacio de tipo float (32 bits) y que el mismo ser identificado bajo el nombre de f. Por ltimo, se le indica al compilador que reserve espacio para otras tres variables enteras identificadas como: x, y, z. As, se puede dar cuenta cmo los tipos primitivos sirven con el propsito de estructurar los datos dentro de la memoria y con la idea de referirnos a los mismos mediante nombres usamos identificadores de variables.

Estructuras

22

Matrices o Arreglos
Una Matriz (en ingls, array, tambin denominado arreglo) es una estructura usada para agrupar bajo un mismo nombre listas de datos de un mismo tipo.

El tipo de matriz puede ser cualquiera, sin embargo cada componente tiene que ser del mismo tipo. En C estndar solamente da soporte para matrices estticas, mientras que con C++ se pueden crear matrices dinmicas pudiendo usar la librera estndar de plantillas (STL).

Matrices estticas
Una matriz esttica es una estructura cuyo tamao es determinado en tiempo de compilacin, es decir, una vez establecido el tamao de la matriz sta no podr cambiarse durante el tiempo de ejecucin. En C, C++ para declarar un arreglo esttico de datos se emplea la sintaxis: tipo identificador[ [tamao] ] [ = { lista de inicializacin } ] ; donde, tipo se refiere al tipo de datos que contendr la matriz. El tipo puede ser cualquiera de los tipos estndar (char, int, float, etc.) o un tipo definido por el usuario. Es ms, el tipo de la matriz puede ser de una estructura creada con: struct, union y class. identificador se refiere al nombre que se le dar a la matriz. tamao es opcional e indica el nmero de elementos que contendr la matriz. Si una matriz se declara sin tamao, la misma no podr contener elemento alguno a menos que en la declaracin se emplee una lista de inicializacin. lista de inicializacin es opcional y se usa para establecer valores para cada uno de los componentes de la matriz. Si la matriz es declarada con un tamao especifco, el nmero de valores inicializados no podr ser mayor a dicho tamao. Ejemplos: int intA[5]; long longA[5] = { 1, 2, 3, 4, 5 }; char charA[] = { 'a', 'b', 'c' };

Acceso a los miembros de una matriz de datos:


En orden de acceder a los miembros de una matriz se debe indicar el nombre de la matriz seguido de dos corchetes, dentro de los cuales se debe especificar el ndice del elemento deseado. Se debe aclarar que los ndices son nmeros o expresiones enteras y que en C, C++ estos tienen un rango permitido de 0 a T-1 ( T = tamao de la matriz ). Ejemplos: dadas las matrices intA, charA, longA ( ejemplo anterior )
intA[0] = 100; 100. charA[3] = 'O'; 'O'. cout << longA[0]; // muestra por pantalla el elemento 0 de longA, que es longA[0]. // establece el valor del elemento 3 de charA a // establece el valor del elemento 0 de intA a

Estructuras

23

Matrices dinmicas
Una matriz dinmica es una estructura compleja y, ya que C estndar no da el soporte para operar con estos tipos de estructuras, le corresponde al programador crear los algoritmos necesarios para su implementacin. Crear lista dinmicas de datos en C estndar no es una tarea para programadores inexpertos, ya que para lograr tal objetivo se necesita tener conocimentos solidos acerca de los punteros y el comportamiento de los mismos. Los usuarios de C++ pueden auxiliarse de la librera estndar de plantillas, conocidas por sus siglas en ingles como STL.

Estructuras compuestas (struct, union, class)


Con base en los tipos bsicos mostrados arriba, se pueden crear nuevos tipos con estructuras compuestas por uno o ms de uno de los tipos mencionados. En C, C++ en orden de crear nuevas estructuras se emplean la palabras reservadas struct, union y class. struct: esta orden se emplea para agrupar variables de tipos iguales o diferentes en un solo registro, con la misma se pueden crear estructuras annimas, estructuras con nombre, y un tipo especial de estructura conocida como bit-fields ( banderas o campos de bits ). union: una union es similar a una struct, salvo que en una estructura creada con union los campos o variables comparten una direccin de almacenamiento comn. class: una clase es una estructura en donde se agrupan variables y funciones, la misma es usada en Programacin Orientada al Objeto. Las clases no son soportadas por el C estndar.
Nota: tanto las estructuras como las uniones y las clases pueden ser annimas, pero lo ms recomendable es darle a las mismas un nombre. Si una estructura, union o clase posee nombre, esta pueden ser empleadas para declarar variables de la misma y, lo ms importante, puede ser usada para el paso de parmetros a funciones.

Sintaxis general: struct struct [ <nombre tipo de estructura > ] { [ <tipo> <nombre-variable[, nombre-variable, ...]> ] ; [ <tipo> <nombre-variable[, nombre-variable, ...]> ] ; ... } [ <variables de estructura> ] ; Nota: recuerde que lo que se muestra entre corchetes es opcional. struct: Sintaxis ( variacin uno, estructura annima ) De acuerdo con la sintaxis general de la orden struct es posible crear estructuras de datos annimas. Solamente hay que tener en cuenta que en una declaracin annima se debe definir al menos una variable al final de la declaracin. Por ejemplo, con el siguiente fragmento de cdigo: struct { int a, b; } p1; se declara y define la variable estructurada p1, misma que se compone por los miembros a y b; ambos del tipo int. Ahora bien, la sintaxis mostrada arriba no es tan comn ni conveniente, ya que con la misma solamente se esta creando una variable estructurada pero no un nuevo tipo. Es decir, si desearamos tener otra variable que tuviera las mismas caracteristicas que posee la variable p1, necesitariamos escribir exactamente la misma instruccin, salvo que cambiando el nombre de la variable. Por ejemplo: struct { int a, b; } p2; Por supuesto, en una misma lnea de instruccin podemos definir ms de una variable. Ejemplo:

Estructuras struct { int a, b; } p1, p2; Entonces, para crear nuevos tipos con struct deberemos de modificar la sintaxis mostrada en los ejemplos anteriores. Sintaxis: struct ( variacin dos, estructura con nombre ) Observe que, la sintaxis para declarar estructuras con nombre es bastante parecida a la sintaxis para declarar estructuras annimas; salvo que una declaracin de estructura con nombre se debe especificar el nombre deseado para la misma. Adems, en una declaracin de estructura con nombre la o las variables definidas al final de la misma son opcionales. Ejemplos: struct pareja { int a, b; } p1; En el fragmento de cdigo anterior se declara la estructura identificada como pareja, misma que se compone de los miembros a y b, ambos de tipo int. En el mismo ejemplo, se define la variable p1; la cual es una variable estructurada de tipo pareja. Una vez que una estructura con nombre ha sido creada, la misma puede ser usada para declarar cualquier nmero de variables. Por ejemplo, en el siguiente fragmento de cdigo se crea la estructura tiempo compuesta por los miembros hora, minuto y segundo; todos del tipo int. En el mismo ejemplo, se declaran las variables t1 y t2. /* declaracin de estructura tiempo */ struct tiempo { int hora, minuto, segundo; }; /* declaracin de variables de tipo tiempo */ struct tiempo t1, t2; Nota: en C++ puede olviarse la palabra struct a la hora de declarar variables. As, en C++ la lnea de instrucin struct tiempo t1, t2; ( del ejemplo anterior) puede escibirse como: tiempo t1, t2;

24

Acceso a los miembros de una estructura


En orden de poder leer o escribir uno de los miembros de una variable estructurada se debe usar el operador de acceso ( . ), o sea, el nombre de la variable seguida por un punto seguido por el nombre del miembro o componente deseado de la estructura. Por ejemplo, para acceder a los miembros de la variable t1 (mostrada arriba) podemos hacerlo de la siguiente manera: t1.hora = 12; t1.minuto = 0; t1.segundo = 0; printf ("%i\n", t1.hora); cout << t1.minutos << endl;

Estructuras

25

Estructuras anidadas
Los miembros de una estructura pueden ser ellos mismos, otra estructura previamente identificada o bien una estructura annima. Por ejemplo, en el siguiente fragmento de cdigo se crean las estructuras pareja y pareja2. Observese cmo dentro de los miembros de pareja2 se declara el miembro X, mismo que es una estructura del tipo pareja. Luego, las variables declaradas a raiz de la estructura pareja2 poseern los miembros variables a y b heredados de pareja, y c. struct pareja { int a, b ; }; struct pareja2 { struct pareja X; int c; } P3; Ahora bien, para acceder a los miembros de una estructura dentro de otra estructura se emplea el mismo mecanismo de acceso (el punto). Por ejemplo, para desplegar el miembro a de la variable P3 declarada en el ejemplo anterior, lo haremos ms o menos as: printf( "%i\n", P3.X.a );

Herencia
El termino herencia se usa con gran frecuencia en Programacin Orientada al Objeto, y se le relaciona principalmente con las clases. Sin embargo, la herencia est presente siempre y cuando una estructura "struct", "union" o "class" posea a otra estructura. En ese sentido, en C++ se presentan dos tipos de herencia: herencia por agregacin o composicin. herencia por extensin. Por ejemplo, en la definicion de las estructuras pareja y pareja2 del ejemplo anterior, se dice que pareja2 hereda por composicin todos los miembros de pareja. Ahora, en el siguiente ejemplo se usa la sintaxis para que la estructura pareja2 herede por extensin los miembros de pareja: // solo C++ struct pareja { int a, b ; }; struct pareja2 : pareja { int c; } P3;

Estructura de campos de bits


Un campo de bit es un elemento de una estructura definido en terminos de bits. Usando un tipo especial de definicion de struct, se pueden declarar elementos de estructura con rangos de 1 a 16 de largo. (dependiendo de la arquitectura de la PC y del compilador, el rango para una estructura de campos de bits puede ser de 1 a 16, 1 a 32, 1 a 64). Antes de ver un ejemplo del uso de struct para crear estructuras de campos de bits consideremos el caso en donde se tiene una variable del tipo short (16 bits) y que para la misma se desea que los bits tengan significados especificos. digamos que el primer bit servir para controlar alguna condicin; los siguientes cuatro bits, o sea del segundo al quinto bit, controlarn otra condicin; el bit 6 tendr otra funcion; y el resto, o sea del septimo al decimosexto bit se emplearn para contralar otra condicin. De tal manera que si queremos, por ejemplo, saber si el primer bit de la variable tiene almacenado un 1 o un 0 podemos emplear la siguiente sintaxis: int int X = 123; r = X & 1;

la cosa parece sencilla, pero ahora consideremos el caso en el cual deseamos saber cual es el valor contenido por el grupo de bits ( segundo al quinto ), entonces nos daremos cuenta que no basta con una prueba mediante AND ( X & 1 ) sino que hay que realizar otros pasos.

Estructuras Precisamente, para problemas como el planteado arriba es que el lenguaje C,C++ da soporte a las estructuras de campos de bits. Por ejemplo, la estructura struct campo_de_bit { int bit_1 int bits_2_a_5 int bit_6 int bits_7_a_16 } bit_var;

26

: : : :

1; 4; 1; 10;

corresponde a la siguiente coleccin de campos bits:

El mecanismo de estructuras de campos de bits soportado por C,C++ es una herramienta til y poderosa, sobre todo en programacin de bajo nivel; ya que mediante el mismo es posible aislar y dar nombres a todos y cada uno de los bits de un dato y tambin crear en un mismo campo grupos de bits (como se muestra arriba). Ahora bien, no hay que olvidar que la estructura minima de informacin en un sistema de memoria de una PC es el bit, y que para aislar a cada uno de estos se puede emplear el operador AND ( &, en C ), pero mediante el mecanismo mencionado el compilador genera los algoritmos necesarios para llevar a cabo el aislamiento de los bits. Por ejemplo, para escribir y leer bit identificado como bit_1 de la variable bit_var del ejemplo anterior, podemos emplear las siguientes instrucciones: bit_var.bit_1 = 1; printf("%i\n", bit_var.bit_1 ); Nota: acerca de las estructuras de campos de bits hay que hacer la aclaracin que, aunque cada uno de los campos de la estructura pueden declarse como enteros con signo o enteros sin signo, la misma no tendr una longitud mayor a un entero largo.

union: Sintaxis general


union [ <nombre tipo de union > ] { <tipo> <lista de variables>; } [ <variables de union> ] ; De la misma manera que con la orden struct, con la orden union se pueden crear estructuras con nombre y estructuras sin nombre. El mecanismo de acceso a los miembros de una union es igual al mecanismo de acceso a los miembros de una struct. Los miembros de una union comparten un espacio de almacenamiento comn. En una union, el compilador reserva el espacio de almacenamiento para la misma de acuerdo con el el tipo de la variable de mayor tamao. Ejemplo: union annima union { short a; long b; } u1;

Estructuras En el ejemplo anterior se declara la variable u1, la cual es una estructura tipo union. El espacio de almacenamiento para la variable a es compartido por la variable b, en consecuencia, al escribir sobre cualquiera de estas dos variables se altera el contenido de ambas. Ejemplo: union con nombre union ux { short a; long b; } u1; En el ejemplo anterior se declara la variable u1, la cual es una estructura tipo union. El espacio de almacenamiento para la variable a es compartido por la variable b. Es decir, el compilador reservar espacio en la memoria para la variable de mayor tamao (que para ste caso es b ). Ahora bin, suponiendo que en su equipo el tipo long ocupa 32 bits y que el tipo short ocupa 16 bits, entonces la variable a ocupar solamente los 16 bits menos significativos, miemtras que la variable b ocupar todo el espacio, o sea los 32 bits; Observe que en la sintaxis se ha especificado el nombre ux, mismo que puede ser empleado para declarar cualquier nmero de variables de la union. Por ejemplo, a continuacin se declaran las variables u2 y u3 del tipo union ux creado en el ejemplo anterior. union ux u2, u3;

27

class: sintaxis
<classkey> <classname> [<:baselist>] { <member list> } [lista de variables] ; <classkey> es una de las palabras "class", "struct", o "union". <baselist> lista de clas(es) base de donde se deriva esta clase. <baselist> es opcional. <member list> declara los datos miembros y funciones miembros de la clase. [lista de variables] esta entrada es opcional y se usa para instanciar variables u objetos de esta clase.

Nota: Dentro de una clase, los datos son llamados "datos miembros" las funciones son llamadas "funciones miembros". El mecanismo para acceder a los miembros de una class es igual que aquel utilizado para acceder a los miembros de una struct Las clases son algo as como "super" estructuras capaces de agrupar no solo datos miembros sino tambin funciones miembros. En el lenguaje comn a los datos miembros de una clase se les conoce como atributos; mientras que a las funciones miembros de una clase se les llama mtodos. Normalmente, los mtodos se emplean para leer o escribir los atributos. Es decir, la norma general es no permitir el acceso directo a los atributos de una clase, con la idea de aumentar la seguridad de los datos. En seguida se mostrar el cdigo para crear la clase Pareja, misma que poseer los atributos a y b, y los mtodos setA(), setB(); getA(), getB(), y mostrar(); class Pareja { int a, b; public: void setA(int n) { a void setB(int n) { b int getA() { return int getB() { return void mostrar() { cout << "a = " }

= n; } = n; } a; } b; } << a << "; b = " << b << endl;

Estructuras } p1; Nota: por omisin, los miembros de una clase son privados, lo cual significa que los objetos instanciados de dicha clase no tendrn acceso a los mismos. As, en el ejemplo anterior se est creando la clase Pareja, y al mismo tiempo el objeto p1. Luego, para leer o escribir los atributos de p1 se debe hacer a traves de los mtodos definidos con tal objetivo. Por ejemplo, con el siguiente fragmento de cdigo se establecen respectivamente a 100 y a 200 los atributos a y b; y posteriormente se despliegan por medio del mtodo mostrar(). p1.setA(100); p1.setB(200); p1.mostrar();

28

struct vs. class


Esta seccin no pretende ensear que el uso de la palabra struct es lo mismo que usar la palabra class. Sin embargo, como ya se mencion en una seccin anterior a esta, las estructuras (struct) pueden extenderse de otras y heredar todos los miembros de la estructura base. Otro punto que se demostrar aqui es el hecho de que las estructuras en C++ (no en C estndar) pueden contener miembros funciones. Por supuesto, el compilador trata a una struct de una forma diferente que a una class. Entre algunas de las diferencias que se pueden mencionar respecto a las estructuras contra las clases son: Los miembros de una struct son pblicos por default, mientras que los miembros de una class son privados por default. Los parmetros-argumentos struct se pasan normalmente por copia, los parmetros-argumentos class se pasan normalmente por referencia. // Este programa ha sido probado en Dev-C++ y Borland C++ #include <iostream> using namespace std; // estructura tipo clase base struct Par { int a, b; // constructor base Par() { a = b = 0; } // destructor base ~Par() { cout << "hecho..." << endl; } void setA(int n) { a = n; } void setB(int n) { b = n; } void mostrar() { cout << "a = " << a << ", b = " << b << "; suma = " << a+b << endl; } };

Estructuras

29

// estructura tipo clase hija // ParHijo es una extensin de Par, y por lo tanto hereda los miembros de Par struct ParHijo : Par { // constructor del hijo ParHijo(int a, int b) { this->a = a; this->b = b; } };

// prueba void test00() { ParHijo p1(100, 200); // p1 es instancia de ParHijo p1.mostrar(); // se enva mensaje al mtodo mostrar() de p1 } // funcion principal int main() { test00(); cin.get(); return 0; }
Iteraciones y decisiones Arriba Funciones

Funciones

30

Funciones
Estructuras Streams

Funciones
Definiendo una funcin
Una funcin es un conjunto de lneas de cdigo que realizan una tarea especfica y puede retornar un valor. Las funciones pueden tomar parmetros que modifiquen su funcionamiento. Las funciones son utilizadas para descomponer grandes problemas en tareas simples y para implementar operaciones que son comnmente utilizadas durante un programa y de esta manera reducir la cantidad de cdigo. Cuando una funcin es invocada se le pasa el control a la misma, una vez que esta finaliz con su tarea el control es devuelto al punto desde el cual la funcin fue llamada. <tipo> [clase::] <nombre> ( [Parmetros] ) { cuerpo; } Ejemplo de una funcin Para comenzar, vamos a considerar el caso en el cual se desea crear la funcin cuadrado(), que deber devolver el cuadrado de un nmero real (de punto flotante), es decir, cuadrado() aceptar nmeros de punto flotante y regresar una respuesta como nmero flotante. Nota: aunque para la funcin que veremos el tipo de retorno coincide con el tipo de parmetro pasado, algunas veces las cosas pueden cambiar, es decir, no es obligatorio que una funcin reciba un parmetro de un tipo y que tenga que regresar una respuesta de dicho tipo. // regresar el cuadrado de un nmero double cuadrado(double n) { return n*n; }

Parmetros
Normalmente, las funciones operan sobre ciertos valores pasados a las mismas ya sea como constantes literales o como variables, aunque se pueden definir funciones que no reciban parmetros. Existen dos formas en C++ de pasar parmetros a una funcin; por referencia o por valor. El hecho es que si en una declaracin de funcin se declaran parmetros por referencia, a los mismos no se les podr pasar valores literales ya que las referencias apuntan a objetos (variables o funciones) residentes en la memoria; por otro lado, si un parmetro es declarado para ser pasado por valor, el mismo puede pasarse como una constante literal o como una variable. Los parmetros pasados por referencia pueden ser alterados por la funcin que los reciba, mientras que los parametros pasados por valor o copa no pueden ser alterados por la funcin que los recibe, es decir, la funcin puede manipular a su antojo al parmetro,

Funciones pero ningn cambio hecho sobre este se reflejar en el parmetro original. Parametros por valor La funcin cuadrado() (ver arriba) es un clsico ejemplo que muestra el paso de parmetros por valor, en ese sentido la funcin cuadrado() recibe una copia del parmetro n. En la misma funcin se puede observar que se realiza un calculo ( n*n ), sin embargo el parmetro original no sufrir cambio alguno, esto seguir siendo cierto an cuando dentro de la funcin hubiera una instruccin parecida a n = n * n; o n*=n;. Parametros por referencia Para mostrar un ejemplo del paso de parmetros por referencia, vamos a retomar el caso de la funcin cuadrado, salvo que en esta ocasin cambiaremos ligeramente la sintaxis para definir la misma. Veamos: // regresar el cuadrado de un nmero double cuadrado2(double &n) { n *= n; return n; } Al poner a prueba las funciones cuadrado() y cuadrado2() se podr verificar que la primera de estas no cambia el valor del parmetro original, mientras que la segunda s lo hace.

31

Llamar a una funcin


para llamar a la funcin cuadrado() vista anteriormente, podemos emplear: cout << cuadrado(25); cout << cuadrado(X); R = cuadrado(X); // guardar en R el cuadrado de X

Funciones void
Bajo ciertas circunstancias se desear escribir funciones que no regresen valor alguno (esto sera algo parecido a escribir procedures en Pascal) y para ello podemos declarar a la funcin como void. La palabra reservada void es utilizada para declarar funciones sin valor de retorno y tambin para indicar que una funcin especfica no requiere de parmetros. Por ejemplo, la funcin pausa() que se ver en seguida, no devolver valor alguno y la misma no requiere de parmetros. // esta funcin requiere de la librera iostream void pausa(void) { cout << "Por favor presione <Enter> HOLA..."; cin.get(); cin.ignore(255, '\n'); // rechazar caracteres introducidos antes de <Enter> } Notas: se debe de aclarar que el uso de la palabra void dentro de los parentesis es opcional al momento de declarar una funcin. As, la funcin pausa() podra haberse declarado como void pausa(), y la misma puede invocarse como: pausa();.

Funciones

32

Funciones anidadas
A diferencia de Pascal, el lenguaje C, C++ no permite anidar funciones, sin embargo, dentro de una funcon puede existir la llamada a una o ms funciones declaradas previamente.

Funciones de tipo puntero (*)


En muchas ocasiones se desea que ciertas funciones regresen una referencia o puntero hacia un tipo (sea este estructurado o no) especfico de dato en lugar de un valor especfico. En tales casos, la funcin se deber declarar como para que regrese un puntero. Por ejemplo, supongamos que deseamos crear una funcin para convertir un nmero entero en notacin decimal a una cadena de caracteres en forma de nmeros binarios, luego, la funcin mencionada podra escribirse para que reciba el nmero entero como parmetro y regrese un puntero a una cadena de caracteres conteniendo la conversin. Para ser ms puntuales, vamos a escribir un programa en donde se ver la funcin binstr(), y cuyo objetivo ser precisamente convertir nmeros decimales en cadenas binarias. Nota: observe que en la sintaxis para declarar funciones tipo puntero se debe de poner el smbolo * despues del tipo y antes del nombre de la funcin que se est declarando. Esto se puede ver en el programa, ya que la funcin binstr se declara como: char *binstr(unsigned int); #include <iostream> #include <string.h> using namespace std; // declaracin de prototipo char *binstr(unsigned int); // punto de prueba int main() { int n = 128; cout << "decimal = " << n << ", cin.get(); }

binario = " << binstr(n) << endl;

// definicin de funcin binstr() // nota: esta funcion requiere de la librera estndar string char *binstr(unsigned int n) { static char buffer[65]; int i = 0; strcpy(buffer, "0"); if (n > 0) { while (n > 0) { buffer[i] = ( n & 1 ) + '0'; i++; n >>= 1; }

Funciones buffer[i] = '\0'; strrev(buffer); } // fin (n > 0) return buffer; }

33

Variables estticas y automticas


Dentro de una funcin, las variables y/o constantes pueden ser declaradas como: auto (por omisin) o como static. Si una variable dentro de una funcin es declarada como esttica significa que la misma retendr su valor entre las llamadas a la funcin. Por otro lado, la variables automticas pierden su valor entre las llamadas. En el programa anterior puede verse que el arreglo de caracteres (buffer[65]) se ha declarado como esttico para garantizar que dicho buffer retenga los datos an despues de que la funcin termine. En el mismo ejemplo, si el buffer no se declara como esttico, el contenido del mismo podra ser destruido al salir de la funcin y los resultados obtenidos podran ser no deseados.

Parmetros constantes
Los parmetros usados por una funcin pueden declararse como constantes ( const ) al momento de la declaracin de la funcin. Un parmetro que ha sido declarado como constante significa que la funcin no podr cambiar el valor del mismo ( sin importar si dicho parmetro se recibe por valor o por referencia ). Ejemplo: int funcionX( const int n ); void printstr( const char *str );

Parmetros con valor por defecto


Los parmetros usados por una funcin pueden declararse con un valor por defecto. Un parmetro que ha sido declarado con valor por defecto es opcional a la hora de hacer la llamada a la funcin. Ejemplo: Dada la funcin: void saludo( char* mensaje = "Hola sudafrica 2010" ); la misma puede ser invocada como: saludo(); // sin parmetro saludo("Sea usted bienvenido a C++"); // con parmetro Para ver un ejemplo ms, vamos a considerar el caso de la funcin binstr() del programa funciones01. Ahora, vamos modificar dicha funcin, salvo que esta ocasin nos interesa que la misma sirva para convertir nmeros decimales en cadenas numricas y cuya base de conversin sea pasada como parmetro. Es decir, la funcin de la que estamos hablando podr convertir nmeros decimales a: binario, octal, decimal, hexadecimal, etc.; y la nica condicin ser que la base indicada est entre el 2 y el 36, inclusive. Nota: Ya que la funcin servir para convertir nmeros a cualquier representacin la nombraremos como numstr() en lugar de binstr(). Si la funcin es invocada sin el parmetro base regresar una cadena de digitos decimales. #include <iostream> #include <stdlib.h> using namespace std;

Funciones // declaracin de prototipo char *numstr(unsigned int, const int base = 10); // punto de prueba int main() { int n = 128; cout << "decimal = " << n << ", cout << "decimal = " << n << ", cin.get(); }

34

binario = " << numstr(n, 2) << endl; octal.. = " << numstr(n, 8) << endl;

// definicin de funcin numstr() // nota: esta funcion requiere de la librera stdlib.h char *numstr(unsigned int n, const int base) { static char buffer[65]; itoa(n, buffer, base); return buffer; }

Parmetros de tipo puntero


Anteriormente se mencion que en C++ los parmetros a una funcin pueden pasarse por valor o por referencia, al respecto, podemos agregar que los parmetros tambin pueden pasarse como punteros. El paso de parmetros de punteros es bastante parecido al paso de parmetros por referencia, salvo que el proceso de los datos dentro de la funcin es diferente. Por ejemplo, las funciones: void referencia( int &X ) { X = 100; } void puntero( int *X ) { *X = 100; } ambas reciben un puntero o referencia a un objeto de tipo entero, por lo tanto cualquiera de las funciones del ejemplo puede cambiar el valor de la variable entera apuntada por X, la diferencia radica en la forma en que cada una de las mismas lleva cabo la tarea. Si en la funcin puntero() en lugar de usar *X = 100; se usara X = 100; se le asignara 100 al puntero X, ms no al objeto apuntado por X, y esto podra ser la causa de que el programa se terminara de manera abrupta.

Parmetros estructurados
Al igual que cualquier otro tipo los parmetros de tipo estruturado pueden pasarse por valor o por referencia, sin embargo, podra ser que si una estructura es pasada por valor el compilador mostrara una advertencia ( warning ) indicando que se pasado por valor una estructura, puesto que el paso de estructuras por valor es permitido usted puede ignorar la advertencia, pero lo mejor es pasar estructuras por referencia. Si una estructura es pasada por valor y si esta es muy grande podria ser que se agotara la memoria en el segmento de pila ( Stack Segment ), aparte de que la llamada a la funcin sera ms lenta. Para ver un ejemplo, consideremos el caso del siguiente tipo estructurado: struct empleado { char nombre[32]; int edad;

Funciones char sexo; }; Ahora, pensemos que deseamos escribir una funcin para imprimir variables del tipo empleado. As, la funcin puede escribirse de las tres maneras siguientes: void ImprimeEmpleadoV( { cout << "Nombre: cout << "Edad: " cout << "Sexo: " } // Parametro empleado pasado por referencia void ImprimeEmpleadoR( empleado &e ) { cout << "Nombre: " << e.nombre << endl; cout << "Edad: " << e.edad << endl; cout << "Sexo: " << e.sexo << endl; } // Parametro empleado pasado como puntero void ImprimeEmpleadoP( empleado *e ) { cout << "Nombre: " << e->nombre << endl; cout << "Edad: " << e->edad << endl; cout << "Sexo: " << e->sexo << endl; } empleado e) " << e.nombre << endl; << e.edad << endl; << e.sexo << endl;

35

Funciones sobrecargadas
C++, a diferencia del C estndar, permite declarar funciones con el mismo nombre y a esto se conoce como sobrecarga de funciones. Las funciones sobrecargadas pueden coincidir en tipo, pero al menos uno de sus parmetros tiene que ser diferente. En todo caso, si usted trata de declarar funciones sobrecargadas que coincidan en tipo y nmero de parmetros el compilador no se lo permitir. Para poner un ejemplo vamos a considerar el caso de dos funciones cuyo nombre ser divide, ambas regresarn el cociente de dos nmeros, salvo que una de ellas operar sobre nmeros enteros y la otra lo har sobre nmeros reales ( de punto flotante ). Nota: cuando en los programas se hace una llamada a una funcin sobrecargada, el compilador determina a cual de las funciones invocar en base al tipo y nmero de parmetros pasados a la funcin. #include <iostream.h> #include <stdlib.h> using namespace std; // divide enteros int divide(int a, int b) {

Funciones cout << "divisin entera" << endl; return ( (b != 0) ? a/b : 0); } // divide reales double divide(double a, double b) { cout << "divisin real" << endl; return ( (b != 0) ? a/b : 0); } // punto de prueba int main() { cout << divide(10, 3) << endl; cout << divide(10.0, 3.0) << endl; cin.get(); }

36

Nmero variable de parmetros


En C,C++ se pueden crear funciones que operen sobre una lista variable de parmetros, es decir, en donde el nmero de parmetros es indeterminado. En esta seccin se mostrar un ejemplo de la manera en que podemos crear funciones para manejar tales asuntos, y para ello haremos uso de tres macros soportadas por C++: 1. va_list puntero de argumentos 2. va_start inicializar puntero de argumentos 3. va_end liberar puntero de argumentos La sintaxis que usaremos para declarar funciones con lista de parmetros variables es: 1) tipo nombrefuncion(...) 2) tipo nombrefuncion(int num, ...) donde: 1. 2. 3. 4. tipo es el tipo regresado por la funcin nombrefuncion es el nombre de la funcin int num es el nmero de parmetros que la funcin procesar ... esta notacin se emplea para indicar que el nmero de parmetros es variable

Nota: observe que la primera forma de declaracin es realmente variable el nmero de parmetros a procesar y en estos casos se debe establecer el mecanismo para determinar cuando se ha procesado el ltimo de los argumentos, en el segundo tipo de declaracin el nmero total de parmetros a procesar es igual al valor del parmetro num. En el siguiente programa, por ejemplo, se define una funcin ( printstr ) que despliega una lista variable de cadenas de caracteres. #include <iostream.h> #include <stdarg.h> // despliega una lista de cadenas, la ultima debe ser NULL void printstr(...) {

Funciones va_list ap; char *arg; va_start(ap, 0); while ( (arg = va_arg(ap, char*) ) != NULL) { cout << arg; } va_end(ap); } int main() { printstr("Hola, ", "Esta es\n", "una prueba\n", NULL); cin.get(); return 0; } En el programa que se listar en seguida, se define la funcin suma(), misma que operar sobre listas de nmeros enteros, la funcin devolver la suma de dichos nmeros. #include <iostream>//entrada y salida #include <stdarg.h> using namespace std; // Esta funcin opera sobre una lista variable de nmeros enteros int suma( int num, ... ) { int total = 0; va_list argptr; va_start( argptr, num ); while( num > 0 ) { total += va_arg( argptr, int ); num--; } va_end( argptr ); return( total ); } int main() { cout << suma(4, 100, 200, 300, 400) << endl; cin.get(); return 0; }
Estructuras Arriba Streams

37

Streams

38

Streams
Funciones Arrays y cadenas de texto

Streams, entrada y salida de datos


En este captulo abordaremos el tema de la manipulacin de datos a travs de los dispositivos de entrada y salida estndar por medio de ciertos componentes lgicos conocidos como: streams. A manera de definicin, un stream es una especie de canal a travs del cual fluyen los datos. Tcnicamente, un stream es el enlace lgico utilizado por el programador en C, C++ para leer o escribir datos desde y hacia los dispositivos estndar conectados a la PC. Normalmente, el dispositivo estndar para manipular entradas es el teclado y este, en C++, est asociado al objeto cin; el dispositivo estndar de salida est asociado (generalmente) con la pantalla o monitor de despliegue de la PC y, el mismo, en C++ se puede acceder por medio del objeto cout. El dispositivo estndar para mensajes de error es el cerr, y el mismo est asociado por defecto con la pantalla o monitor de despliegue. Otro dispositivo estndar es la impresora, pero este ltimo no es soportado por los objetos de la iostream. Los programadores que hayan tenido alguna experiencia al programar en C estndar, notarn que los tres objetos mencionados coinciden con los dispositivos: stdin, stdout y stderr. La tabla que se muestra en seguida puede servirnos de base.

Tabla I/O : 01, dispositivos estndar de I/O


C estandar C++ stdin stdout stderr --cin cout cerr clog

La iostream
La iostream es la librera estndar en C++ para poder tener acceso a los dispositivos estndar de entrada y/o salida. En sus programas, si usted desea hacer uso de los objetos cin, cout, cerr y clog tendr que incluir ( por medio de la directiva #include ) el uso de la librera iostream. En la iostream se encuentran definidas las clases ios ( misma que es la base para las clases que implementen operaciones de entrada y/o salida de datos ), istream ( para operaciones de entrada ) y ostream ( para operaciones de salida ). Aparte de las clases mencionadas, en la iostream se encuentra una lista de variables y constantes ( atributos ) que son accesibles por el usuario a travs del operador de mbito ( :: ).

Streams

39

Streams automticos
Si usted usa la directiva #include <iostream.h> o #include <iostream> en sus programas, automticamente la iostream pone a su disposicin los objetos cin, cout, clog y cerr en el mbito estndar (std), de tal manera que usted puede comenzar a enviar o recibir informacin a travs de los mismos sin siquiera preocuparse de su creacin. Asi, un sencillo ejemplo del uso de los objetos mencionados se muestra en seguida. // De nuevo con el hola mundo... #include <iostream.h> int main() { std::cout << "Hola mundo"; // imprimir mensaje (en la pantalla) std::cin.get(); // lectura ( entrada del teclado ) return 0; }

Operadores de direccionamiento
Los operadores de direccionamiento son los encargados de manipular el flujo de datos desde o hacia el dispositivo referenciado por un stream especfico. El operador de direccionamiento para salidas es una pareja de smbolos de "menor que" <<, y el operador de direccionamiento para entradas es una pareja de smbolos de "mayor que" >>. Los operadores de direccionamiento se colocan entre dos operandos, el primero es el Stream y el segundo es una variable o constante que proporciona o recibe los datos de la operacin. Por ejemplo, en el siguiente programa y en la instruccin cout << "Entre su nombre: "; la constante "Entre su nombre: " es la fuente o quien proporciona los datos para el objeto cout. Mientras que en la instruccin cin >> nombre la variable nombre es el destino o quien recibe los datos provenientes del objeto cin. // De nuevo con el hola mundo... #include <iostream.h> int main() { char nombre[80]; cout << "Entre su nombre: "; cin >> nombre; cout << "Hola," << nombre; cin.get(); return 0; } Observe que si en una misma lnea de comando se desea leer o escribir sobre varios campos a la vez, no es necesario nombrar ms de una vez al stream. Ejemplos: cout << "Hola," << nombre; cin >> A >> B >> C;

Streams

40

Banderas de I/O
En esta seccin abordaremos de manera ms directa el tema sobre el control de formato para los stream de C++. Especficamente, veremos las tres diferentes formas que existen en C++ para manipular las banderas relacionadas a los stream y que nos permitirn gobernar de una manera ms precisa la forma para representar datos de salida. En ese sentido, veremos que la primera de las forma que nos permitir el formateo sera a travs de las funciones flags(), setf() y unsetf(), la segunda y la tercera forma las encontraremos en ciertos manipuladores directos definidos en las libreras <iostream> y <iomanip>.

Banderas de formato:
C++ define algunas banderas de formato para entradas y salidas estndar, las cuales pueden ser manipuladas a travs de la funciones (mtodos) flags(), setf(), y unsetf(). Por ejemplo, cout.setf(ios::left); activa la justificacin a la izquierda para todas las salidas dirigidas hacia cout. A continuacin se muestra una tabla de referencia de las banderas de I/O.

Tabla I/O : 02, banderas de formato


Bandera boolalpha dec fixed hex left oct right scientific showbase showpoint showpos skipws unitbuf uppercase Descripcin Los valores booleanos pueden ser ledos/escritos usando las palabras "true" y "false" Los valores numricos se muestran en formato decimal Nmeros de punto flotante se despliegan en forma normal Los valores numricos se muestran en formato hexadecimal La salida es justificada por la izquierda Los valores numricos se muestran en formato octal La salida es justificada por la derecha Nmeros de punto flotante se despliegan en notacin cientfica Despliega la base de todos los valores numricos Despliega el punto decimal y extra ceros, an cuando no sean necesarios Despliega el smbolo de ms antes de valores positivos Descarta caracteres de espaciado (espacios, tabuladores, nuevas lneas) cuando se lee desde un stream Descarga el buffer despus de cualquier insercin Despliega la "e" en notaciones cientficas y la "x" en notaciones decimales como letras maysculas

Manipulando la lista de banderas de I/O de C++ (mostrada arriba) se pueden controlar los aspectos relacionados a la forma con la cual se desean presentar los datos en la salida. Por ejemplo, el programa que se muestra en seguida, activa la bandera boolalpha para mostrar los resultados de operaciones booleanas como "true" o "false" en lugar de "0" o "1" como es lo normal. // Programacin con C++ // programa Banderas01.cpp; // probado en Dev-Cpp Versin 4.9.9.2 #include <iostream>

Streams using namespace std; int main(int argc, char *argv[]) { cout <<"\n0 > 1 ? "<<"\t"<<(0>1)<< endl; cout <<"\t5 > 1 ? "<<"\t"<<(5>1)<< endl; cout.setf(ios::boolalpha); // activar bandera cout <<"\n0 > 1 ? "<<"\t"<<(0>1)<< endl; cout <<"\t5 > 1 ? "<<"\t"<<(5>1)<< endl; cin.get(); return 0; }

41

Manipuladores
Las banderas tambin pueden manipularse directamente usando los siguientes manipuladores. Seguramente usted ya estar familiarizado con el manipulador endl, el mismo puede darle una idea de cmo son usados los manipuladores. Por ejemplo, usted puede establecer la bandera de nmeros decimales usando el comando: cout << dec; La tabla que se muestra en seguida corresponde a los manipuladores definidos en <iostream>.

Tabla I/O : 03, manipuladores en <iostream>


Manipulador bollalpha dec endl ends fixed flush hex internal left nobollalpha noshowbase noshowpoint noshowpos noskipws nounitbuf nouppercase oct right Activa la bandera boolalpha Activa la bandera dec-imal Escribe caracter de cambio de lnea Escribe el caracter null Activa la bandera fixed (para nmeros reales) Descargar el stream Activa la bandera hex-adecimal Activa la bandera interna Activa la bandera left (izquierda) Desactiva la bandera boolalpha Desactiva la bandera showbase Desactiva la bandera showpoint Desactiva la bandera showpos Desactiva la bandera skipws Desactiva la bandera unitbuf Desactiva la bandera uppercase Activa la bandera oct-al Activa la bandera de justificar derecha Descripcin X X --------X ----X ------X ----X --Entrada X X X X X X X X X X X X X --X X X X Salida

Streams

42
Activa la bandera scientific Activa la bandera showbase Activa la bandera showpoint Activa la bandera showpos Activa la bandera skipws Activa la bandera unitbuf Activa la bandera uppercase Limpiar cualquier espacio al inicio --------X ----X X X X X --X X ---

scientific showbase showpoint showpos skipws unitbuf uppercase ws

El programa que se muestra en seguida es un ejemplo de como emplear manipuladores directos para formatear salidas al stream estndar de salida ( cout ). En el mismo, se emplea el manipulador boolalpha para que los resultados de la operaciones logicas sean textuales, o sea, true o false y los manipuladores dec, hex y oct. // Programacin con C++ // programa Banderas02.cpp; // probado en Dev-Cpp Versin 4.9.9.2 #include <iostream> using namespace std; int main(int argc, char *argv[]) { cout << boolalpha; cout << "0 > 1 ? " << '\t' << (0 > 1) << endl; cout << "5 > 1 ? " << '\t' << (5 > 1) << endl; cout << dec << 2048 << endl; cout << hex << 2048 << endl; cout << oct << 2048 << dec << endl; cin.get(); return 0; } Por ltimo, y para terminar esta seccin, hablaremos de los manipuladores definidos en la librera <iomanip>. Estos operan directamente igual que los que se viern anteriormente, salvo que son parametrizados, es decir, operan en lnea de salida mediante el operador <<, pero los mismos operan a manera de funciones, o sea con parmetros.

Streams

43

Tabla I/O : 04, manipuladores en <iomanip>


Manipulador resetioflags( long f ) setbase( int base ) setfill( int ch ) setioflags( long f ) setprecision( int p ) setw( int w ) Descripcin Desactiva las banderas especificadas por f Establece la bases numrica a base Establece caracter de relleno a ch Activa las banderas especificadas por f Establece el nmero de digitos de precisin a p Establece la longitud de campo a w X ----X ----Entrada X X X X X X Salida

Atendiendo a las tablas de banderas, as como a las tablas de manipuladores para las mismas mostradas arriba, usted puede (con practica y perceverancia) lograr salidas con muy buena presentacin hacia los dispositivos estndar. Por ejemplo, el programa que se mostrar a continuacin muestra una de las formas de emplear manipuladores para formatear nmeros de punto flotante, en el programa se despliega una lista de valores numricos y se establece el campo de salida a una longitud de 12 caracteres y dos decimales. // Programacin con C++ // programa Banderas03.cpp; // probado en Dev-Cpp Versin 4.9.9.2 #include <iostream> #include <iomanip> using namespace std; int main() { double data[] = { 347.25, 45.75, 124.50, 456.80, 1500.90 }; double total; int ancho = 12; cout.precision(2); cout.setf(ios::fixed); for (int c = 0; c < 5; c++) { cout << setw(ancho) << data[c] << endl; total += data[c]; } cout.fill('-'); cout << setw(ancho) << "" << endl; cout.fill(' '); cout << setw(ancho) << total << endl; cout << "Por favor oprime Enter..."; cin.get(); return 0; }

Streams

44

Streams para archivos o ficheros


Por definicin, un archivo es una coleccin de datos almacenados en algn lugar. En C++, el programador puede considerar que un archivo es un stream, de tal manera que los objetos vistos en la seccin anterior ( cin, cout, cerr y clog ) son una especie de archivo manipulados por streams. Este punto es de suma importancia, ya que todo lo que se ha mencionado acerca de los atributos, banderas y manipuladores; que son miembros de los objetos ( stream ) para entrada y salida estndar, son aplicables a los streams que usemos para trabajar con archivos en disco y/o cualquier otro dispositivo de almacenamiento. Hasta este momento, solamente se ha mencionado que los streams poseen banderas y que las mismas pueden alterarse a traves de los manipuladores, sin embargo, hace falta hablar acerca de los mtodos o funciones que son miembros de dichos streams. As, antes de ver un ejemplo para mostrar el uso de archivos en disco listaremos una tabla ms, o sea, la de los mtodos aplicables a los streams.

Tabla I/O : 05, mtodos para objetos istream, ostream y fstream


Funcin bad clear close eof fail fill flags flush gcount get getline good ignore open peek precision put putback rdstate read seekg seekp setf tellg tellp unsetf width true si ha ocurrido un error limpia las banderas de estado (status flags) cierra un stream true si se alcanz el fin de archivo true si ha ocurrido un error establecer manipulador de caracter de relleno accesa o manipula las banderas de formato de un stream vaciar el buffer de un stream nmero de caracteres leidos durante la ltima operacin de entrada lectura de caracteres lectura de una lnea de caracteres true si no ha ocurrido un error leer y descartar caracteres abrir un stream de entrada y/o salida verifica la siguiente entrada de caracter manipula la precisin del stream escritura de caracteres regresar caracteres al stream regresa la bandera de estado de stream lee datos de un stream hacia un buffer realiza acceso aleatorio sobre un stream de entrada realiza acceso aleatorio sobre un stream de salida cambiar las banderas de formato lee el puntero del stream de entrada lee el puntero del stream de salida limpiar las banderas de formato accesa y manipula la longitud minima del campo Descripcin

Streams

45
escritura datos desde un buffer hacia un stream

write

Abrir y cerrar archivo


A diferencia de los streams para dispositivos estndar, los cuales son creados y abiertos de manera automtica, para trabajar con archivos en discos se debe primeramente "abrir el archivo", y luego de haber terminado de leer o escribir datos en el mismo, se debe "cerrar el archivo". En C++, en orden de trabajar con archivos en disco, podemos emplear las clases fstream, ifstream, y ofstream. Si usted desea abrir un archivo especfico en modo de lectura y escritura use un objeto de la clase fstream, por el contrario, si desea abrir un archivo solo para lectura o solo para escritura use objetos de la clase ifstream y ofstream, respectivamente. La sintaxis para crear objetos de las tres clase mencionadas es: '''streams para lectura y escritura''' fstream(); fstream(const char*, int, int = filebuf::openprot); fstream(int); fstream(int _f, char*, int); '''streams solo para lectura''' ifstream(); ifstream(const char*, int, int = filebuf::openprot); ifstream(int); ifstream(int _f, char*, int);

'''streams solo para escritura''' ofstream(); ofstream(const char*, int, int = filebuf::openprot); ofstream(int); ofstream(int _f, char*, int); Observe que la sintaxis para crear objetos de las tres clases mostradas arriba es la misma. Es decir, 1. El primer mtodo constructor crea un objeto que no est (an) asociado a un archivo en disco, en estos casos, se tendr que usar el mtodo stream.open("nombre de archivo"); para establecer la conexin entre el stream y el archivo en disco. 2. El segundo mtodo constructor crea un objeto asociado a un archivo en disco, en estos casos, el archivo en el disco queda abierto y asociado al stream. En este, el parmetro char * apunta a una cadena de caracteres con el nombre del archivo. 3. El tecer mtodo crea un stream a raiz de un identificador de archivo. 4. El cuarto mtodo crea un stream a raiz de un identificador de archivo, un buffer y tamao de buffer especficos. Para nuestro primer ejemplo usaremos el segundo de los constructores mencionados. As, el programa que se muestra en seguida realiza las siguientes tareas: 1. Crea y escribe sobre el archivo de texto test.$$$ 2. Abre y lee los datos del archivo test.$$ 3. Cierra el archivo test.$$ Para el caso que se presenta se debe prestar atencin a los metodos: 1. bad(), para verificar el estado de error del stream

Streams 2. get(), para leer caracteres del stream 3. put(), para escribir caracteres en el stream 4. close(), para cerrar el archivo. // // // // Ejemplo de ifstream y ofstream En este programa se demuestra la forma de crear un archivo en disco por medio de objeto ofstream, y la manera abrir y leer un archivo por medio de un objeto ifstream.

46

#include <iostream> #include <fstream> #include <string.h>

using namespace std; char *filename = "test.$$$"; char *data = "Esta lnea de texto se guardar en el archivo test.$$$"; // crear un archivo en disco cuyo nombre es dado por filename int crearArchivo(char *filename) { ofstream fichero(filename); // crear o rescribir archivo // verificar la creacin del archivo if ( fichero.bad() ) { cout << "Error al tratar de abrir archivo"; cin.get(); return 1; } // escribir datos al archivo for (unsigned int t = 0; t < strlen(data); t++ ) fichero.put(data[t] ); fichero.close(); cout << "archivo creado exitosamente" << endl; return 0; } // abrir un archivo en disco cuyo nombre es dado por filename int leerArchivo(char *filename) { ifstream fichero(filename); // abrir archivo para lectura // verificar la apertura del archivo if ( fichero.bad() ) { cout << "Error al tratar de abrir archivo";

Streams cin.get(); return 1; } // lectura de datos while ( ! fichero.eof() ) cout << (char)fichero.get(); fichero.close(); cout << endl << "archivo leido exitosamente" << endl; return 0; } int main() { crearArchivo(filename); leerArchivo(filename); cout << endl << "Presione <Enter>..."; cin.get(); return 0; } Usando operadores de redireccin ( <<, >> ) sobre streams asociados con archivos en disco. No hay que olvidar que si un archivo es asociado a un stream las operaciones de entrada y/o salida para dicho archivo se hacen a travez del stream y, por lo tanto, se puede usar, no solo los operadores de redireccin, sino tambin todos los metodos, manipuladores y atributos que se ha mencionado hasta este momento. As, el objetivo del siguiente programa es demostrar como hacer uso del operador << sobre un archivo asociado a un stream de salida, y del operador >> sobre un archivo asociado a un stream de entrada. El ejemplo que se mostrar no es realmente til, sin embargo cumple con su cometido, es decir, el punto de interes del mismo est en las lneas de cdigo: while ( ! fin.eof() ) { fin >> temp; fout << temp ; } Encargadas de leer datos del archivo de entrada y de escribir los mismos en el archivo de salida. #include <iostream.h> #include <fstream.h> char *filename = "test.$$$"; // abre y lee datos de un archivo en disco cuyo nombre es dado por filename int leerArchivo(char *filename) { ifstream fichero(filename); // abrir archivo para lectura // verificar la apertura del archivo if ( fichero.bad() ) {

47

Streams cout << "Error al tratar de abrir archivo"; cin.get(); return 1; } // lectura de datos while ( ! fichero.eof() ) cout << (char)fichero.get(); fichero.close(); cout << endl << "archivo leido exitosamente" << endl; return 0; } // crea una copia del archivo test.$$$ hacia test.bak // Nota: ningn caracter de espaciado en el origen le es tranferido al destino int backup(char *filename) { char newname[80]; int i = 0; // copia el nombre del archivo original y remplaza la extensin por "BAK". while (filename[i] != '.' ) newname[i] = filename[i++]; newname[i] = '\0'; strcat(newname, ".BAK"); ifstream fin( filename ); // abrir archivo de entrada ofstream fout( newname, ios::trunc ); // abrir archivo de salida char temp; while ( ! fin.eof() ) { fin >> temp; fout << temp ; } fin.close(); fout.close(); return 0; } int main() { backup(filename); leerArchivo("test.bak"); cout << endl << "Presione <Enter>..."; cin.get(); return 0;

48

Streams }
Funciones Arriba Arrays y cadenas de texto

49

Arrays y cadenas de texto


Streams Desarrollo Orientado a Objetos

Arrays y cadenas de texto


Los arrays son usados extensamente por los programadores para contener listas de datos en la memoria, por ejemplo, los datos almacenados en un disco suelen leerse y ponerse dentro de un array con el objetivo de facilitar la manipulacin de dichos datos, ya que los datos en memoria pueden ser modificados, clasificados, marcados para su eliminacion, etc. para luego ser reescritos al disco. Otro ejemplo podra ser el de un men de opciones que se desplegarn dentro de una ventana para que el usuario pueda elegir una de stas, en tales casos y cuando las opciones son numerosas, solamente se ponen unas cuantas de ellas dentro de la ventana pero se le da al usuario la oportunidad de poder subir y bajar a su antojo para ver el resto de opciones que, aunque no se vean en la ventana, forman parte del men o array de opciones. Array: Un array es un conjunto de datos del mismo tipo ordenados en forman lneal uno despus de otro. Los componentes de un array se han de referenciar por medio del nombre del array y un ndice de desplazamiento para indicar el componente deseado.

Indices de un array
Los ndices son nmeros que se utilizan para identificar a cada uno de los componentes de un array. A modo de ejemplo, podemos pensar que los ndices son como los nmeros de habitaciones de un hotel, es decir, para poder dirijirnos a un hotel especfico es necesario saber el nombre del mismo, luego, si queremos llegar a una habitacin especfica de dicho hotel necesitaremos, adems del nombre del hotel, el nmero de habitacin deseado.

Dimensiones de un array
De acuerdo a la forma en que se construye o declara un array, ste puede ser clasificado como: unidimensional, bidimensional y multidimensional. Los arrays que se emplean con mucha ms frecuencia son los estructurados a manera de vector ( array unidimensional ) y los estructurados a manera de matriz ( array bidimensional ), as, aunque en C++ se pueden crear estructuras multidimensionales, en este captulo solo trataremos con vectores y matrices.

Array unidimensional
Una array uni-dimensional es aquel en donde los componentes son accesibles por medio de uno y solamente un ndice que apunte al componente requerido. Los arrays de este tipo son conocidos tambin con el nombre de vectores. Conceptualmente, podemos pensar en un array unidimensional como en una lista compuesta de lneas o filas en donde para referinos a una de ellas emplearemos un nmero para indicar la posicin de la misma dentro de la lista. Por ejemplo, consideremos el caso de la tabla o array VentaSemanal, la cual est pensada para registrar las ventas de cada uno de los das de la semana. De manera conceptual podemos ver el array como se muestra a continuacin:

Arrays y cadenas de texto Nota: en C++ los arrays estn basados en 0 ( cero ), es decir, el primer elemento de un array se indexa mediante el 0, y el ndice para el ltimo de los elementos es igual al nmero de componentes menos uno. array: VentaSemanal +------+ | dato | |------| | dato | |------| | dato | |------| | dato | |------| | dato | |------| | dato | |------| | dato | |------|

50

<-- componente 0, ( fila 0 ) <-- componente 1, ( fila 1 ) ... ... ... ... <-- componente 6, ( fila 6 )

Si en el array VentaSemanal queremos que el elemento 4 ( por ejemplo ) contenga el valor de 8987 lo podemos lograr con la instruccin: VentaSemanal[4] = 8987; y el estado del array sera: array: VentaSemanal +------+ | dato | |------| | dato | |------| | dato | |------| | dato | |------| | 8987 | <--- componente 4 |------| | dato | |------| | dato | |------|

Array bidimensional
Una array bi-dimensional es aquel en donde los componentes son accesibles por medio de una pareja de ndices que apunten a la fila y a la columna del componente requerido. Los arrays de este tipo son conocidos tambin con el nombre de matrices. Conceptualmente, podemos pensar en un array bidimensional como en una lista compuesta de filas y columnas, en donde para referirnos a una de ellas emplearemos un nmero para indicar la posicin de fila y otro nmero para indicar la posicin de la columna del componente deseado. Por ejemplo, consideremos el caso de

Arrays y cadenas de texto la tabla o array VentaSemanaQ, la cual est pensada para registrar las ventas de cada uno de los das de la semana por cuatro semanas, o sea, una tabla de 7 x 4 elementos. De manera conceptual podemos ver el array como se muestra a continuacin: array: VentaSemanaQ C O L U M N A S +--- componente ( 0, 0 ) | +------+------+------+------+ | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | +------+------+------+------+ | +---- componente ( 6, 3 )

51

F I L A S

Si en el array VentaSemanaQ queremos que el elemento de la fila 4, columna 3 ( por ejemplo ) contenga el valor de 5000 lo podemos lograr con la instruccin: VentaSemanaQ[4][3] = 5000; y el estado del array sera: array: VentaSemanaQ +--- componente ( 0, 0 ) | +------+------+------+------+ | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | 5000 | <-- componente ( 4, 3 ) |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato |

Arrays y cadenas de texto +------+------+------+------+ | +---- componente ( 6, 3 )

52

Declaracin de arrays en C, C++


En C, C++ para declarar un array se emplea la sintaxis: tipo identificador [tamao] donde, tipo se refiere al tipo de datos que contendr el array. El tipo puede ser cualquiera de los tipos estndar (char, int, float, etc.) o un tipo definido por el usuario. Es ms, el tipo del array puede ser de una estructura creada con: struct, union y class. identificador se refiere al nombre que le daremos al array. tamao es opcional e indica el nmero de elementos que contendr el array. Si un array se declara sin tamao, el mismo no podr contener elemento alguno a menos que en la declaracin se emplee una lista de inicializacin. lista de inicializacin es opcional y se usa para establecer valores para cada uno de los componentes del array. Si el array es declarado con un tamao especfico, el nmero de valores inicializados no podr ser mayor a dicho tamao. Ejemplos: int intA[5]; long longA[5] = { 1, 2, 3, 4, 5 }; char charA[3] = { 'a', 'b', 'c' }; = { lista de inicializacin } ;

Iteraciones dentro de un array (vector)


El termino Iterar se refiere al hecho de acceder (con el fin de leer o escribir) sobre cada uno de los componentes de un array. As, para poner un ejemplo reconsideremos el caso de la tabla VentaSemanal (vista en una seccin anterior), y que dicho sea de paso es un array de 7 elementos de tipo double. Luego, vamos a mostrar como ejemplo un programa completo en el cual se declara el array mencionado con valores inicializados, que sern mostrados en pantalla y al final la suma de estos. Observe que la variable i usada para iterar dentro del array va desde 0 hasta FILAS - 1 ( FILAS es el tamao del array ). Nota: por motivos de simplificacin el programa est escrito al estilo de C estndar. Sin embargo puede ser compilado y ejecutado en un compilador de C++. #include <stdio.h> #include <stdlib.h> #define FILAS 7 int main() { float ventas[FILAS] = { 123.50, 234.60, 345.45, 321.40, 345.00, 456.65, 0.0 }; float total = 0; int i;

Arrays y cadenas de texto

53

puts("Ventas de la semana"); puts("-------------------"); for (i=0; i<FILAS; i++) { total += ventas[i]; printf( "%8.2f\n", ventas[i] ); } puts("--------"); printf("%8.2f\n", total ); system("pause"); return 0; } Esta es la salida del programa: Ventas de la semana ------------------123.50 234.60 345.45 321.40 345.00 456.65 0.00 -------1826.60

Iteraciones dentro de un array (matriz)


Con el fin de leer o escribir sobre cada uno de los componentes de una matriz se deben crear dos ciclos de iteracin. As, para poner un ejemplo reconsideremos el caso de la tabla VentaSemanaQ (vista en una seccin anterior), y que dicho sea de paso es un array de 4 x 4 elementos de tipo double. Luego, vamos a mostrar como ejemplo un programa completo en el cual se declara el array mencionado con valores inicializados, que sern mostrados en pantalla y al final la suma de estos. Observe que en este caso se utilizan dos variables, una para iterar sobre las filas y otra para iterar sobre las columnas de la matriz. #include <stdio.h> #include <stdlib.h> //hola #define FILAS 7 #define COLS 4 int main() { float VentaSemanaQ[FILAS][COLS] = { 123.50, 234.60, 345.45, 321.40, 345.00, 456.65, 123.50, 234.60,

Arrays y cadenas de texto 345.45, 321.40, 345.00, 123.50, 234.60, 345.45, 345.00, 456.65, 123.50, 345.45, 321.40, 345.00, 0.0, 0.0, 0.0, 0.0 }; 456.65, 321.40, 234.60, 456.65,

54

float totales[COLS] = { 0.0, 0.0, 0.0, 0.0 }; float grantotal = 0; int f, c, t = 0 ; /* indices para filas, columnas y totales */ puts("Ventas de cuatro semanas"); puts("------------------------"); for (f=0; f<FILAS; f++) { for (c=0; c<COLS; c++) { totales[c] += VentaSemanaQ[f][c]; printf("%8.2f ", VentaSemanaQ[f][c] ); } puts(""); } puts("--------------------------------------"); for (t=0; t<COLS; t++) { printf("%8.2f ", totales[t] ); grantotal += totales[t]; } printf("\n\nGran total: %10.2f\n", grantotal); system("pause"); return 0; }

Salida del programa: Ventas de cuatro semanas -----------------------123.50 234.60 345.45 321.40 345.00 456.65 123.50 234.60 345.45 321.40 345.00 456.65 123.50 234.60 345.45 321.40 345.00 456.65 123.50 234.60 345.45 321.40 345.00 456.65 0.00 0.00 0.00 0.00 -------------------------------------1627.90 2025.30 1627.90 2025.30

Arrays y cadenas de texto

55

Gran total:

7306.40

Cadenas de caracteres
En C, C++ las cadenas de caracteres no son ms que arrays de caracteres, salvo que a este tipo de arrays el compilador les da un tratamiento especial. Usted puede manipular las cadenas de caracteres de la misma manera en que manipula cualquier otro tipo de array, sin embargo, es preferible hacer uso de una librera estndar especialmente escrita para manipulacion de cadenas de caracteres, me refiero a la librera <string.h> y que viene incluida con todo compilador de C, C++. Para comenzar y antes de ver algunas de las funciones de la mencionada librera, tenemos los siguientes ejemplos: 1. char 2. char nombre[] = "Oscar"; nombre2[] = { 'O', 's', 'c', 'a', 'r', '\0' };

En el ejemplo 1 se est declarando la variable nombre como una cadena de caracteres y cuyo contenido inicial es "Oscar". En el ejemplo 2 se est declarando la variable nombre2 como una cadena de caracteres y cuyo contenido inicial es { 'O', 's', 'c', 'a', 'r', '\0' };. En ambos casos el resultado es el mismo, es decir, al final se obtiene la misma cadena, pero usted debe poner atencin al hecho de que toda cadena de caracteres en C, C++ debe terminar con el caracter NULL, que normalmente es igual a cero y se puede escribir como '\0'. Ahora bien, cuando usted usa la sintaxis mostrada en el ejemplo 1 no tiene que preocuparse por agregar el caracter NULL, ya que esto lo hace el compilador automticamente.

La biblioteca string
Los compiladores de C, C++ dan soporte a la biblioteca de funciones <string.h>, a la que accede por medio de la directiva #include <string.h>. No veremos en detalle todas las funciones contenidas en dicha biblioteca, y nos limitaremos a mostrar algunos ejemplos de ciertas funciones importantes. strlen(): Obtener longitud de cadenas Sintaxis: size_t strlen(const char *s); Comentarios: La funcin strlen() devuelve la longitud de la cadena s. Ejemplo: char cout *nombre = "Oscar E. Palacios"; << strlen(nombre) << endl;

strcpy(): Copiar cadenas Sintaxis: char *stpcpy(char *dest, const char *src); Comentarios: stpcpy copia la cadena src hacia dest, la funcin termina hasta haber encontrado en src el caracter de terminacin null. Ejemplo: char *nombre = "Oscar E. Palacios"; char copia[80]; strcpy(copia, nombre); cout << copia << endl; strcat(): Concatenar cadenas

Arrays y cadenas de texto Sintaxis: char *strcat(char *dest, const char *src); Comentarios: strcat agrega la cadena src a dest, la funcin termina hasta haber encontrado en src el caracter de terminacin null. Ejemplo: char nombre[] = "Oscar E."; char copia[80] = " Palacios"; strcat(copia, nombre); cout << copia << endl; strlwr(): Convertir a minsculas. Sintaxis: char *strlwr(char *dest); Comentarios: strlwr convierte todos los caracteres alfabticos ( 'A' .. 'Z' ) en dest a sus correspondientes caracteres alfabticos ( 'a' .. 'z' ). Ejemplo: char nombre[] = "Oscar E. Palacios"; strlwr(nombre); cout << nombre << endl; strupr(): Convertir a maysculas. Sintaxis: char *strupr(char *dest); Comentarios: strupr convierte todos los caracteres alfabticos ( 'a' .. 'z' ) en dest a sus correspondientes caracteres alfabticos ( 'A' .. 'Z' ). strchr(): Buscar caracter ( hacia adelante ) Sintaxis: char *strchr(char *s, int c); Comentarios: strchr busca en s el caracter c. La busqueda se lleva a cabo desde el inicio hasta el final de s. Regreso: si la operacin es exitosa strchr regresa un puntero hacia la primera ocurrencia de c en s, en caso contrario strchr regresa null. Ejemplo: char char nombre[] *p; = "Oscar E. Palacios";

56

p = strchr(nombre, 'E'); if (p) { cout << "nombre contiene a E" << endl; cout << "indice = " << (p - nombre) << endl; } else cout << "E no est en nombre" << endl; strrchr(): Buscar caracter ( hacia atras ) Sintaxis: char *strrchr(char *s, int c); Comentarios: strchr busca en s el caracter c. La busqueda se lleva a cabo desde el final hasta el inicio de s.

Arrays y cadenas de texto Regreso: si la operacin es exitosa strchr regresa un puntero hacia la ltima ocurrencia de c en s, en caso contrario strchr regresa null. Ejemplo: char char nombre[] *p; = "Oscar E. Palacios";

57

p = strrchr(nombre, 'E'); if (p) { cout << "nombre contiene a E" << endl; cout << "indice = " << (p - nombre) << endl; } else cout << "E no est en nombre" << endl; strstr(): Buscar subcadena Sintaxis: char *strstr(const char *s1, char *s2); Comentarios: strstr busca en s1 la subcadena s2. La bsqueda se lleva a cabo desde el inicio hasta el final de s1. Regreso: si la operacin es exitosa strstr regresa un puntero hacia la primera ocurrencia de s2 en s1, en caso contrario strstr regresa null. Ejemplo: char char s[] *p; = "Un barco de tristeza";

p = strstr(s, "barco"); if (p) { cout << "barco est en s" << endl; cout << "indice = " << (p - s) << endl; } else cout << "barco no est en s" << endl;

Cadenas en C++
En la seccin anterior descubrimos algunas funciones para trabajar con cadenas de caracteres al estilo de C estndar, si bien no est de ms tener tal conocimiento, tambin es cierto que C++ es un lenguaje de programacn orientado a objetos, de tal manera que ciertos compiladores ( como el gcc, utilzado por Bloodshed Dev-C++ y otros tantos entornos de desarrolo ) dan soporte a la clase cstring, que no debe confundirse con la <string.h>. Nota: Bloodshed Dev-C++ es un IDE (Editor con Depurador Integrado) para programar en C++ en un ambiente grfico para Windows, distibuido gratuitamente bajo licencia GPL GNU y usted puede encontrarlo aqu: www.bloodshed.net [1]. Actualmente (febrero de 2008) se recomienda bajar la versin Dev-C++ 4.9.9.2. Nota:Como el Dev-c++ ya esta descontinuado, es recomendable usar su estencion Wx Dev-C++ que esta actualmente activa y es recomendable para muchos proyectos bajo el lenguaje C++, este programa tambien es licencia GPL, podras descargarlo desde=wxdsgn.sourceforge.net [2], y encontraras informacion de este aqui=es.wikipedia.org/wiki/WxDev-C%2B%2B [3]. Una de las ventajas que ofrece la clase cstring es que, a diferencia de las cadenas estndar, sta posee la capacidad de crecer o disminuir su tamao en tiempo de ejecucin. Adems, entre otras caracteristicas destacables, la clase

Arrays y cadenas de texto string soporta operaciones de asignacin tales como: =, +, +=, etc.; y de comparacin tales como: ==, <=, etc. Para tener una idea bsica sobre las cadenas en C++ veamos el siguiente programa: Nota: en el programa se debe de observar el uso del operador de asignacin +=, algo que no es posible hacer con las cadenas estndar. // Ejemplo: demostracin de la clase string // Compilado y ejecutado con exito en Bloodshed Dev-C++ #include <cstdlib> #include <iostream> using namespace std; int main(int argc, char *argv[]) { string s("Hola, "); s += "cmo estan ustedes..."; cout << s << endl; system("PAUSE"); return EXIT_SUCCESS; } Un estudio exhaustivo sobre la clase string requiere de un captulo completo, ya que la misma, segn el manual de referencia de C++, posee aproximadamente 33 mtodos y unos 7 constructores; adems de los atributos.

58

Arrays en C++
As como C++ da aternativas elegantes para la manipulacin de cadenas de caracteres, tambin da el soporte para la manipulacon de arrays dinmicos. Este tema ser ampliado en el captulo Libreria de Plantillas Estndar STL, sin embargo para tener una idea de lo que vendr mostraremos aqu un ejemplo sencillo en donde se usar la clase plantilla vector. // Ejemplo: demostracin de la clase vector // Compilado y ejecutado con exito en Bloodshed Dev-C++ #include <cstdlib> #include <iostream> #include <vector> using namespace std; int main(int argc, char *argv[]) { // creacin de un vector de enteros vector<int> v; // metiendo datos en el vector for (int x = 500; x<1000; x+=50) v.push_back(x); // desplegando los datos del vector

Arrays y cadenas de texto for (int x = 0; x < v.size(); x++) cout << "v[" << x << "] = " << v[x] << endl; system("PAUSE"); return EXIT_SUCCESS; }
Streams Arriba Desarrollo Orientado a Objetos

59

Referencias
[1] http:/ / www. bloodshed. net [2] http:/ / wxdsgn. sourceforge. net [3] http:/ / es. wikipedia. org/ wiki/ WxDev-C%2B%2B

Desarrollo Orientado a Objetos


Arrays y cadenas de texto Objetos y Clases

Desarrollo Orientado a Objetos


Antes de empezar a programar con objetos, es necesario entender ciertos conceptos bsicos. Terminologa El paradigma de Programacin Orientada a Objetos utiliza algunos trminos bsicos que se explicarn a continuacin. Objeto. Es una entidad que tiene atributos o propiedades y que presenta un comportamiento. Puede ser un objeto del mundo real, o una abstraccin al mundo informtico. Ejemplo, un automvil, una persona, un animal, etc. Clase. Los humanos percibimos el mundo como un entorno compuesto de cosas, objetos, les llamamos. Interactuamos con tantos, que lo natural es agruparlos de acuerdo a sus caractersticas ms relevantes o generales. En otras palabras, los clasificamos en clases. Todos los objetos que pertenecen a una clase, contiene todos los elementos comunes a todos los objeto y no particulariza en los elementos que distinguen a los objetos entre s; ejemplo, la clase automvil, comprende todos los automviles que circulan en este momento por las calles. Incluira elementos que asociamos a los automviles, como llantas, puertas, asientos, volante, etctera, pero no particularizara en cuestiones como color, nmero de registro o caractersticas individuales a cada auto. Propiedad. Una caracterstica asociada a una clase. Ejemplo, todos los autos tienen un color, o un registro aunque esta propiedad vare entre objetos. Mtodo. Posible accin que los objetos de una clase pueden realizar. En trminos informticos, un algoritmo que pueden ejecutar. Ejemplo: todos los autos pueden acelerar, frenar, etc.

Desarrollo Orientado a Objetos

60

Caractersticas de la programacin orientada a objetos


Encapsulamiento. Cuando conducimos un auto, no es necesario que nos enteremos de como hace el motor para girar las ruedas del vehculo. Esos pormenores quedan ocultos al usuario o encapsulados en la estructura del la clase auto. Cada objeto presenta una interfaz al usuario de mtodos y propiedades con las cuales se interacta con l. As se protege el objeto y todos los miembros de su clase, de alteraciones directas a sus mtodos y propiedades. Polimorfismo. Una vez que aprendemos a conducir un auto, podemos manejar prcticamente cualquier auto. Esto se debe a que la interfaz que presentan los objetos auto a los usuarios, es ms o menos la misma para cada auto. Todos tiene mtodos para acelerar, y si bien, posiblemente los pormenores de como acelera cada auto, sean sustancialmente diferentes; el usuario hace referencia a un mismo mtodo en todos los autos. En trminos informticos, objetos de clases diferentes; incluso en la misma clase; pueden tener mtodos homnimos, que hagan cosas equivalentes aunque los detalles del como, sean diferentes. Herencia. La clase auto es una clase muy general; se puede hacer un desglose ms pormenorizado, por ejemplo, si hacemos subclases de autos deportivos, utilitarios, compactos, manuales, automticos, de lujo, etc. Cada subclase toma de su clase base (o superclase, en la jerga informtica) las propiedades y mtodos ms generales y agrega las propias. As, como todos los autos, tienen color y registro, as que stas estaran contenidas en la superclase auto. Propiedades como Capacidad en toneladas, sistema estreo o cantidad de velocidades, estaran delegadas a las subclases ms particulares.
Arrays y cadenas de texto Arriba Objetos y Clases

Objetos y Clases
Desarrollo Orientado a Objetos Sobrecarga de Operadores

Clases y Objetos
Este capitulo introduce a las clases en C++. La clase es la fundacin de C++ para el soporte de la programacin orientada a objetos, y se encuentra en el ncleo de muchas de sus ms avanzadas caractersticas. La clase es la unidad bsica de C++ de la encapsulacin y esta provee el mecanismo por el cual los objetos son creados.

Fundamentos de Clases
Vamos a comenzar definiendo los trminos de clase y objeto. Una clase define un nuevo tipo de dato que especifica la forma de un objeto. Una clase incluye los datos y el cdigo que operarn sobre esos datos. Adems, una clase enlaza datos y cdigo. C++ usa una especificacin de una clase para construir objetos. Los objetos son instancias de una clase. Adems, una clase es esencialmente una serie de planes que especifican cmo construir un objeto. Es importante estar claro en algo: Una clase es una abstraccin lgica. No es hasta que un objeto de esa clase sea creado que la representacin fsica de la clase existe en la memoria. Cuando se define una clase, se declaran los datos que sta contiene y el cdigo que opera en esos datos. Aunque clases muy simples pueden contener slo cdigo o slo datos, la mayora de las clases en realidad contienen ambos. En conjunto con una clase, los datos se almacenan en las variables y el cdigo en las funciones. Colectivamente, las funciones y variables que constituyen una clase son llamados 'miembros' de la clase. Una variable declarada dentro de una clase es llamada 'variable miembro', y una funcin declarada en una clase es llamada 'funcin miembro'. En ocasiones el trmino 'variable de instancia' es usado en lugar de variable miembro.

Objetos y Clases Una clase es creada con la palabra clave class. La declaracin de una clase es similar sintcticamente a una estructura ( y tienen muchsimo que ver ). Aqu tenemos un ejemplo. La siguente clase define un tipo llamado CRender, el cual es usado para implementar operaciones de renderizado en este caso. // Esto define la clase CRender class CRender { char buffer[256]; public: void m_Renderizar(); }; Veamos ms de cerca esta declaracin de la clase. Todos los miembros de CRender son declarados dentro de la declaracin 'class'. La variables miembro de CRender es buffer. La funcin miembro es m_Renderizar. NOTA: Por defecto los miembros de una clase son privados. Una clase puede contener miembros privados as como pblicos. Por defecto, todos los elementos definidos en una clase son privados. Por ejemplo la variable buffer es privada. Esto significa que slo pueden acceder a ella otros miembros de la clase CRender, y no por alguna otra parte del programa. Esta es una forma de como la encapsulacin se logra, se puede controlar el acceso a ciertos elementos de datos manteniendolos privados. Aunque no hay ninguna en este ejemplo, se pueden definir funciones privadas, las cuales pueden ser llamadas solamente por otros miembros de la clase. Para hacer pblica una parte de la clase ( accesible a otras partes del programa ) se deben declarar con la palabra clave public. Todas las variables o funciones definidas despus de la declaracin pblica son accesibles por todas las demas funciones en el programa. En nuestra clase CRender, la funcin m_Renderizar() es pblica. Tipicamente, su programa acceder a los miembros privados de una clase a travs de sus funciones pblicas. Note que la palabra clave public es seguida con : . Mantenga en mente que un objeto forma una relacin entre cdigo y datos. Una funcin miembro tiene acceso a los elementos privados de su clase. Esto significa que m_Renderizar tiene acceso a buffer en nuestro ejemplo. Para aadir una funcin miembro a la clase, debe especificar su prototipo en la definicin de la misma. Una vez que se ha definido una clase, se puede crear un objeto de ese tipo usando el nombre de la clase. El nombre de la clase se convierte en un especificador del nuevo tipo. Por ejemplo la siguiente declaracin crea 2 objetos llamados render1 y render2 del tipo CRender. CRender render1, render2; Cuando un objeto de la clase es creado, este tendr su propia copia de las variables miembros que contiene la clase. Esto significa que render1 y render2 tendrn su propia y separado copia de buffer. Adems los datos asociados con render1 son distintos y separados de los datos asociados con render2. Recordemos: En c++, una clase es un nuevo tipo de dato que puede ser usado para crear objetos. Especificamente, una clase crea una consistencia lgica que define una relacin entre sus miembros. Cuando se declara una variable de una clase, se est creando un objeto. Un objeto tiene existencia fsica, y es una instancia especfica de una clase. ( Esto es, un objeto ocupa espacio de memoria, pero una definicin de tipo no ). Adems, cada objeto de una clase tiene su propia copia de los datos definidos dentro de esa clase. Dentro de la declaracin de CRender, el prototipo de una funcin es especificado. Ya que las funciones miembros son prototipadas dentro de la definicin de la clase, no necesitan ser prototipadas en otro lugar cualquiera. Para implementar una funcin que es un miembro de una clase, debe indicarle al compilador a cual clase pertenece la funcin calificando el nombre de la funcin con el nombre de la clase. Por ejemplo, esta es una manera de codificar la funcin m_Renderizar().

61

Objetos y Clases void CRender::m_Renderizar() { strcpy(buffer, "C++ en wikibooks"); return; }

62

Resolucin de mbito
El :: es llamado el operador de resolucin de mbito. Esencialmente le dice al compilador que esta versin de m_Renderizar pertenece a la clase CRender. O mirando diferente, :: declara que m_Renderizar se encuentra en el mbito de CRender. Varias clases diferentes pueden usar los mismos nombres de funcin. El compilador sabe cul funcin pertenece a cul clase y esto es posible por el operador de resolucin de mbito y el nombre de la clase.

Acceso a la funciones
Las funciones miembros de una clase slo pueden ser llamadas relativas a un objeto especfico. Para llamar a una funcin miembro desde alguna parte del programa que se encuentre fuera de la clase, se debe usar el nombre del objeto y el operador de direcionamiento '.' ( punto ). Por ejemplo, lo siguiente llama a m_Renderizar() en el objeto objeto1. CRender objeto1, objeto2; objeto1.m_Renderizar(); La invocacin de objeto1.m_Renderizar() causa a m_Renderizar() operar en los datos de la copia de objeto1. Mantenga en mente que objeto1 y objeto2 son 2 objetos separados. Esto significa, por ejemplo, que inicializar objeto1 no causa que objeto2 sea inicializado, La nica relacin que objeto1 tiene con objeto2 es que es un objeto del mismo tipo. Cuando una funcin miembro llama a otra funcin miembro de la misma clase, puede hacerlo directamente, sin usar un objeto y el operador '.' En este caso, el compilador ya conoce en cul objeto se est operando. Solamente cuando una funcin miembro es llamada por cdigo que se encuentra fuera de la clase es cuando debe utilizarse el nombre del objeto y el operador '.' Por la misma razn, una funcin miembro puede referirse directamente a una variable miembro, pero cdigo fuera de la clase debe referenciarse a la variable a travs de un objeto y el operador '.' El programa siguiente muestra aqu todas las piezas juntas y detalles perdidos, e ilustra la clase CRender.

Ejemplo
// Programa OPP01.CPP #include <iostream> using std::cout; using std::endl;

// Esto define la clase CRender class CRender { public: char buffer[256]; void m_Renderizar(const char *cadena);

Objetos y Clases
};

63

/* implementar m_Renderizar() para la c;*/ void CRender::m_Renderizar(const char *cadena){ strcpy(buffer, cadena);//copia la cadena return; }

int main (int argc, char **argv){ // crear 2 objetos CRender CRender render1, render2; render1.m_Renderizar("Inicializando el objeto render1"); render2.m_Renderizar("Inicializando el objeto render2"); cout << "buffer en render1: "; cout << render1.buffer << endl; cout << "buffer en render2: "; cout << render2.buffer << endl; return (0); } // tenemos acceso a buffer ya que es publico.

Este programa imprime: buffer en render1: Inicializando el objeto render1 buffer en render2: Inicializando el objeto render2

Miembros de una clase ( mtodos y atributos )


En el lenguaje coloquial de la programacin orientada al objeto es comn escuchar trminos tales como: mtodos, atributos, herencia, polimorfismo, etc. En esta seccin nos encargaremos de hablar de los dos primeros. Mtodos: En comparacin con la programacin tradicional, un mtodo es lo mismo que una funcin cualquiera, salvo que como los mtodos se declaran para pertenecer a una clase especfica, se dice que todos los mtodos de dicha clase son miembros de la misma. Por lo dems, la declaracin y definicin de los mtodos es exactamente igual que declarar y definir cualquier otra funcin. Atributos: En comparacin con la programacin tradicional, un atributo es lo mismo que una variable cualquiera, salvo que como los atributos se declaran para pertenecer a una clase especfica, se dice que todos los atributos de dicha clase son miembros de la misma. Por lo dems, la declaracin de los atributos es exactamente igual que declarar cualquier otra variable. Miembros: A partir de este momento usaremos la palabra miembro para referirnos al hecho de que un mtodo o un atributo pertenece a tal o cual clase.

Objetos y Clases Por Ejemplo, en el programa OOP01.CPP ( visto anteriormente ) la Clase CRender posee dos miembros, buffer que es un atributo; y m_Renderizar que es un mtodo. class CRender { public: char buffer[256]; // atributo void m_Renderizar(const char *cadena); // mtodo };

64

Visibilidad de los miembros de una clase


Por visibilidad se entiende al acto de acceder a los miembros de una clase. En este sentido, los miembros de una clase pueden ser: pblicos, privados y protegidos. Un miembro pblico significa que el acceso al mismo puede darse dentro del interior de la clase, dentro de una subclase, y desde un objeto instanciado de cualquiera de estas. Por ejemplo, los miembros de la clase CRender son accesibles dentro de la misma y podrn accederse desde cualquier otra clase que se derive de CRender, as como desde cualquier objeto instanciado de estas. Un miembro privado significa que el acceso al mismo puede darse solamente dentro del interior de la clase que lo posee. Normalmente, el programador creador de una clase declara a los atributos de la clase como privados y a los mtodos como pblicos, esto con la idea de que el usuario de la clase no pueda tener acceso a los atributos sino es a traves de los mtodos definidos para el caso. Un miembro protegido se comporta de manera parecida a un miembro privado, salvo que estos son accesibles dentro de la clase que lo posee y desde las clases derivadas, pero no desde los objetos instanciados a raiz de dichas clases. Nota: por defecto, los miembros de una clase son privados. En la clase Pareja que se ver en seguida, se declaran dos atributos y cuatro mtodos para la manipulacin de dichos atributos. Observe que los atributos son privados( por defecto ), mientras que los mtodos se declaran pblicos. class Pareja { // atributos double a, b; public: // mtodos double getA(); double getB(); void setA(double n); void setB(double n); }; // implementacin de los mtodos de la clase Pareja // double Pareja::getA() { return a; } double Pareja::getB() { return b; } void Pareja::setA(double n) { a = n; } void Pareja::setB(double n) { b = n; }

Objetos y Clases

65

Subclases
Una subclase es una clase que se deriva de otra. La clase que sirve de base suele conocerse como parent (padre), y a la subclase se le llama child (hija). En C++ cada clase que es creada se convierte en candidata para servir de base de donde se deriven otras. Por ejemplo, la clase Pareja es candidata para convertirse en la base para las subclases Suma, Resta, Multiplica, Divide, y otras posibles subclases en donde se utilice un par de valores numricos. Para poner un ejemplo, pensemos en que deseamos crear la clase Suma, misma que ser utilizada para obtener la suma de dos nmeros. Puesto que la clase Pareja posee dos atributos nmericos puede ser usada como base para la clase que estamos proyectando. As, el siguiente ejemplo se constituye en un caso de clases derivadas. Nota: Observe que la sintaxis para crear una subclase es: class hija : [public | private] padre { ... }; Donde padre es la clase base e hija es la subclase. class Suma : public Pareja { // atributos de Suma double resultado; public: // mtodos de Suma double calcular(); }; // implementacin de Suma // double Suma::calcular() { return getA() + getB(); } Probando las clases Pareja y Suma // programa OOP02.CPP // Este programa pone a prueba el uso de las clase Suma, // misma que es una subclase de la clase Pareja ( ambas definidas anteriormente). #include <iostream.h> int main() { Suma s; s.setA(80); s.setB(100); cout << s.getA() << " + " << s.getB() << " = " << s.calcular() << endl; cin.get(); return 0; }

Objetos y Clases

66

Herencia
La herencia es uno de los mecanismos ms tiles de la programacin orientada al objeto, ya que por medio de la misma se puede llevar a cabo la reutilizacin de cdigo. Es decir, puesto que toda clase definida se convierte en candidata para ser usada como base de donde se deriven otras, esto da como resultado que las clases derivadas hereden todos los miembros de la clase base. Por ejemplo, la clase Suma vista en la seccin anterior, hereda todos los miembros de la clase Pareja puesto que Suma es una extensin de Pareja. En ese sentido, podemos decir que existen dos tipos de herencia, por extensin y por agregacin o composicin. En el caso de las clases Pareja y Suma, se dice que Suma es una extensin de Pareja. Vista grficamente, la herencia por extensin se puede representar as:

Herencia por extensin

Herencia

Al tipo de diagrama mostrado arriba (Herencia por extensin) se le conoce como UML [1] y es utilizado para mostrar de forma grafica la relacin existente entre una clase hija con la clase padre. En el caso del ejemplo, se muestra que la clase Suma es una extensin de la clase Pareja y, en consecuencia, Suma posee a los miembros { a, b, getA(), getB(), setA(), setB() } heredados de la clase Pareja. Observe como la clase Suma posee otros dos miembros no heredados, { resultado, y calcular() }, y es precisamente a este tipo de situacin por lo que se dice que Suma es una extensin de Pareja, ya que Suma, adems de poseer a todos los miembros de Pareja, se extiende para poseer otros dos miembros.

Objetos y Clases

67

Agregacion o composicin
La composicin se da en los casos en donde una clase posee un objeto que es una instancia de otra clase. Por ejemplo, la clase Suma podra escribirse de la siguiente forma: class Suma { // atributo privado double resultado; public: // mtodo pblico double calcular(); // atributo pblico Pareja p; }; // implementacin del metodo calcular de la clase Suma. double Suma::calcular() { return p.getA() + p.getB(); } Luego, si usted presta atencin, notar que el miembro p de la clase Suma es un objeto o instancia de la clase Pareja, en consecuencia, la clase Suma puede acceder a los miembros de la clase Pareja a travs de la variable p. Tambin se debe observar que la implementacin del mtodo calcular() es diferente que el mismo de la clase Suma original. Si usted desea poner a prueba a la nueva clase Suma, compile y ejecute el siguiente programa.

// programa herencia_por_composicion.CPP #include <iostream> using namespace std; class Pareja { // atributos double a, b; public: // mtodos double getA(); double getB(); void setA(double n); void setB(double n); }; // implementacin de los mtodos de la clase Pareja double Pareja::getA() { return a; } double Pareja::getB() { return b; } void Pareja::setA(double n) { a = n; }

Objetos y Clases void Pareja::setB(double n) { b = n; }

68

class Suma { // atributo privado double resultado; public: // mtodo pblico double calcular(); // atributo pblico Pareja p; }; // implementacin del metodo calcular de la clase Suma. double Suma::calcular() { return p.getA() + p.getB(); } int main() { Suma s; s.p.setA(80); s.p.setB(100); cout << s.p.getA() << " + " << s.p.getB() << " = " << s.calcular() << endl; cin.get(); return 0; } ////SALIDA//// 80 + 100 = 180

Constructores
Un constructor es un mtodo que pertenece a una clase y el cual (en C++) debe tener el mismo nombre de la clase a la que pertenece. A diferencia de los otros mtodos de la clase, un constructor deber ser del tipo void, es decir, el mismo no regresar valor alguno. Una clase puede tener ms de un mtodo constructor. Cada clase debe tener al menos un constructor, tanto as que si el programador creador de una clase no define un mtodo constructor para la misma, el sistema, o sea el compilador, crear de manera automtica un constructor nulo. El objetivo principal del constructor es el de establecer las condiciones necesarias dentro de la memoria y crear una copia del objeto mismo dentro de la memoria. Los consturctores suelen usarse para la inicializacin de los atributos de los objetos instanciados. Por ejemplo, con las instrucciones: Suma s; s.setA(80); s.setB(100); del programa OOP02.CPP, se declara el objeto s de la clase Suma y luego se inicializan los atributos del objeto por medio de los mtodos setA() y setB(). En este caso, es necesario hacer uso de dichos mtodos debido al hecho de que no se ha definido un constructor para la clase Suma. Ahora bien, para evitar este tipo de situaciones podemos definir un mtodo constructor para Suma y que este se encargue de inicializar los atributos. Veamos.

Objetos y Clases Notas: ya que Suma es una extensin de Pareja, se ha definido el metodo constructor de Pareja y este a la vez es invocado por el constructor de Suma. Otro punto de interes es el hecho de la definicin de un constructor base para Pareja, ya que de acuerdo con las reglas de programacin en C++ toda clase en donde se defina un constructor parametrizado deber definir un constructor base, este regla se aplica en los casos en donde la clase proyectada servir de base para otras. // programa OOP04.CPP // Ejemplo: clases Pareja y Suma, ambas con constructor #include <iostream> using namespace std; //-----------------------class Pareja { // atributos double a, b; public: // constructor de base ( null ) Pareja() {} // constructror parametrizado Pareja(double x, double y) : a(x), b(y) {} // mtodos double getA(); double getB(); void setA(double n); void setB(double n); }; // implementacin de los mtodos de la clase Pareja // double Pareja::getA() { return a; } double Pareja::getB() { return b; } void Pareja::setA(double n) { a = n; } void Pareja::setB(double n) { b = n; } //-----------------------class Suma : public Pareja { // atributos de Suma double resultado; public: // constructor Suma(double a, double b) : Pareja(a, b) {}

69

Objetos y Clases

70

// mtodos de Suma double calcular(); }; // implementacin de Suma // double Suma::calcular() { return getA() + getB(); }

//-----------------------int main() { Suma s(80, 100); cout << s.getA() << " + " << s.getB() << " = " << s.calcular() << endl; cin.get(); return 0; }

Destructores
Un destructor es un mtodo que pertenece a una clase y el cual (en C++) debe tener el mismo nombre de la clase a la que pertenece. A diferencia de los otros mtodos de la clase, un destructor deber ser del tipo void, es decir, el mismo no regresar valor alguno. Para diferenciar a un mtodo destructor de un mtodo constructor, al nombre del destructor se le debe anteponer el caracter ~ (Alt + 126). El objetivo principal del destructor es el de retirar de la memoria al objeto, o sea, el destructor hace todo lo contrario que el constructor. Los destructores suelen usarse para liberar memoria que haya sido solicitada por el objeto a travez de las ordenes malloc(), new, etc. En tales casos se deber incluir dentro del mtodo destructor la orden free, delete, etc., segn sea el caso. // clase Pareja con constructor y destructor class Pareja { // atributos double a, b; public: // constructor de base ( nulo ) Pareja() {} // constructor parametrizado Pareja(double x, double y) : a(x), b(y) {} // destructor ~Pareja() {} // mtodos

Objetos y Clases double double void void };


Desarrollo Orientado a Objetos Arriba Sobrecarga de Operadores

71 getA(); getB(); setA(double n); setB(double n);

Referencias
[1] http:/ / es. wikipedia. org/ wiki/ Lenguaje_Unificado_de_Modelado

Sobrecarga de Operadores
Sobrecarga de operadores
La sobrecarga de operadores es uno de los mecanismos que nos permite ampliar las capacidades de los lenguajes de programacin orientados a objetos. En C++, la declaracin y definicin de una sobrecarga de operador es muy similar a la declaracin y definicin de una funcin cualquiera. El ejemplo ms sencillo de una sobrecarga de operadores nos lo da el lenguaje mismo, es decir, en una operacin aritmtica (por ejemplo, una suma ) el compilador determina el tipo de operacin requerida de acuerdo con el tipo de datos involucrados. Vamos a suponer que se tienen las variables: int A, B; double X, Y; int R; y las siguientes instrucciones: R = A + B; R = A + X; R = X + Y; Ahora bien: en el primer caso el compilador determina que el tipo de operacin requerida es una suma de enteros debido a que los dos miembros ( A y B ) de la suma son del tipo entero. en el segundo caso parece ser que las cosas no son tan claras, ya que en este los miembros involucrados en la suma son de tipos diferentes, sin embargo el compilador determinar el tipo de operacin requerida y depositar en el resultado ( R )el valor resultante redondeado. en el tercero y ltimo de los casos el compilador determina que el tipo de operacin requerida es una suma de reales debido a que los dos miembros ( X e Y ) de la suma son del tipo double. Tambin en este caso el resultado de la suma se redondea antes de ponerlo en R.

Mi primer sobrecarga
Para poner un ejemplo prctico de sobrecarga del operador de suma ( + ) vamos a considerar el caso de la clase pareja mostrada en seguida: class Pareja { public: double a, b; // constructor parametrizado Pareja(const double a,const double b)

Sobrecarga de Operadores { this->a = a; this->b = b; } }; int main() { Pareja A(50, 75 ); Pareja B(150, 175 ); Pareja C = A + B; return 0; } Si usted trata de compilar el programa anterior descubrir que el compilador se queja, es decir, el compilador no sabe que hacer en el caso de la instruccin Pareja C = A + B; aunque es evidente que se trata de la declaracion del objeto C y que el mismo se espera que sea igual a la suma de los objetos A y B. El error se debe al hecho de que el tipo Pareja es en realidad una clase y no un tipo primitivo y, en consecuencia, se debe de adiestrar al mismo compilador para que ste sepa de que manera hara la suma de dos objetos del tipo Pareja. As, para solucionar el error vamos a sobrecargar el operador + para trabajar la suma de dos de dichos objetos. Vamos: // Titulo..: programa sobrecarga01.cpp // Objetivo: demostracin de sobrecarga de operadores // Autor...: Oscar E. Palacios #include <iostream> using namespace std; class Pareja { public: double a, b; // constructor parametrizado Pareja(const double a,const double b) { this->a = a; this->b = b; } }; // Sobrecarga del operador + Pareja& operator +(const Pareja &p1,const Pareja &p2) { return *(new Pareja(p1.a + p2.a, p1.b + p2.b) ); } int main() {

72

Sobrecarga de Operadores Pareja A(50, 75 ); Pareja B(150, 175 ); Pareja C = A + B; cout << "A = " << A.a << ',' << A.b << "\n"; cout << "B = " << B.a << ',' << B.b << "\n"; cout << "C = " << C.a << ',' << C.b << "\n"; return 0; }

73

Sintaxis general
Tal y como usted lo habr notado, la declaracin de sobrecarga en el programa anterior es lo ms parecido a una funcin, es decir, la sintaxis general para sobrecargar uno operador cualquiera es: tipo operator + (lista de parmetros);

en donde, 1. tipo se refiesultado de la misma clase. Esto es lo ms recomendable ya que si usted recuerda el compilador regresa un resultado entero si es que (por eje

Sobrecarga permitida de operadores


En C++ no es posible sobrecargar todos los operadores, pero al menos la mayora de ellos s. Los operadores que no se pueden sobrecargar son: #, ##, ., :, .*, o ?.

Sobrecarga del operador << ( ostream )


Normalmente cuando se escribe una clase y se desea que el stream estndar de salida ( cout ) pueda mostrar una representacin de su valor se debe de sobrecargar el operador <<. Para mostrar un ejemplo retomemos el programa sobrecarga01.cpp ( visto arriba ), en el mismo se imprimen por medio de cout los miembros ( a y b ) de los objetos A, B y C. cout << "A = " << A.a << ',' << A.b << "\n"; cout << "B = " << B.a << ',' << B.b << "\n"; cout << "C = " << C.a << ',' << C.b << "\n"; Usted puede ver cmo para cada uno de los miembros de los objetos se debe de usar el operador de direccin ( . ), pues bien, nuestro objetivo es lograr que dado un objeto de la clase Pareja ste pueda ser desplegado por cout y para ello haremos la sobrecarga de operador << con el fin de que pueda operar con objetos de la clase mencionada. Veamos: // Titulo..: programa sobrecarga03.cpp // Objetivo: demostracin de sobrecarga de operadores // Autor...: Oscar E. Palacios #include <iostream> using namespace std; class Pareja { public:

Sobrecarga de Operadores double a, b; // constructor parametrizado Pareja(const double a,const double b) { this->a = a; this->b = b; } }; // Sobrecarga del operador + Pareja& operator +(const Pareja &p1,const Pareja &p2) { return *(new Pareja(p1.a + p2.a, p1.b + p2.b) ); } // Sobrecarga del operador << para la clase Pareja ostream& operator << (ostream &o,const Pareja &p) { o << "(" << p.a << ", " << p.b << ")"; return o; } int main() { Pareja A(50, 75 ); Pareja B(150, 175 ); Pareja C = A + B; cout << "A = " << A << "\n"; cout << "B = " << B << "\n"; cout << "C = " << C << "\n"; return 0; }

74

Sobrecarga del operador >> ( istream )


As como el operador << debe ser sobrecargado, lo mismo es cierto para el operador >> para poder ser usado con el stream estndar de entrada ( cin ). Retomaremos nuevamente a la clase Pareja para dar un ejemplo. // Titulo..: programa sobrecarga03.cpp // Objetivo: demostracin de sobrecarga de operadores // Autor...: Oscar E. Palacios #include <iostream> using namespace std; // inserte aqu la clase Pareja y los operadores sobrecargados vistos

Sobrecarga de Operadores // en el programa anterior. Se deber agregar un constructor en la clase Pareja como el siguiente: /*Pareja() { this->a=0; this->b=0; }*/ // Sobrecarga del operador >> para la clase Pareja istream& operator >> (istream &i, Pareja &p) { cout << "Introducir valores para ( a, b) : "; i >> p.a >> p.b; i.ignore(); return i; } int main() { Pareja Pareja cin >> Pareja

75

A(50, 75 ); B; B; C = A + B;

cout << "A = " << A << "\n"; cout << "B = " << B << "\n"; cout << "C = " << C << "\n"; return 0; }

Operadores amigos ( friend )


Un operador, amigo al igual que una funcin amiga, es aquel que an cuando no es miembro de una clase tiene todos los privilegios de acceso a los miembros de dicha clase. En la sobrecarga de los operadores +, << y >> para la clase Pareja de los programas anteriores se puede notar que dichos operadores no son parte de la clase Pareja, sino ms bien estos operan sobre objetos de dicha clase y es a travez de dichos objetos que pueden manipular a los miembros de la clase. Ahora bien, no hemos tenido ningn problema debido a que todos los miembros de Pareja han sido declarados como pblicos (public:), pero que sucede si un operador o una funcin que no sea parte de la clase trata de acceder a los miembros privados o protegidos de sta, en estos casos el compilador reportara el error indicando que tal o cal miembro es privado dentro de cierto contexto. Luego, si queremos que un operador o funcin que no es miembro de una clase pueda acceder a los miembros publicos, privados o protegidos deberemos declarar a dicho operador o funcin como amigo (friend) dentro de la clase especfica. Para mostrar un ejemplo modificaremos la clase Pareja, en donde sus atributos sern privados. // Titulo..: programa sobrecarga04.cpp // Objetivo: demostracin de sobrecarga de operadores // Autor...: Oscar E. Palacios

Sobrecarga de Operadores #include <iostream> using namespace std; class Pareja { private: double a, b; public: // constructor parametrizado Pareja(const double a,const double b) { this->a = a; this->b = b; } friend Pareja& operator +(const Pareja &p1,const Pareja &p2); friend ostream& operator << (ostream &o,const Pareja &p); }; // Sobrecarga del operador + para la clase Pareja Pareja& operator +(const Pareja &p1,const Pareja &p2) { return *(new Pareja(p1.a + p2.a, p1.b + p2.b) ); } // Sobrecarga del operador << para la clase Pareja ostream& operator << (ostream &o,const Pareja &p) { o << "(" << p.a << ", " << p.b << ")"; return o; } int main() { Pareja A(50, 75 ); Pareja B(150, 175 ); Pareja C = A + B; cout << "A = " << A << "\n"; cout << "B = " << B << "\n"; cout << "C = " << C << "\n"; return 0; }

76

Sobrecarga de Operadores

77

Sobrecarga de operadores dentro de una clase


Tal y como hemos dicho antes, los operadores que hemos sobrecargado para la clase Pareja ( de los ejemplos anteriores ) no son parte de la clase, pero en la mayora de las veces se ver que los operadores para una clase especfica se deben sobrecargar dentro de la misma clase, es decir, dichos operadores sern miembros de la clase. Antes de sobrecargar cualquier operador para una clase se deben tener en cuenta los siguientes factores: 1. Los operadores binarios se declaran con un solo parmetro, ya que el primer parmetro es pasado por el programa como this, es decir, un puntero al mismo objeto. 2. Los operadores unarios se declaran sin paramtros, ya que el nico parmetro es pasado por el programa como this.
Nota: Los operadores binarios son aquellos que poseen dos partes ( izquierda y derecha), por ejemplo, una operacin de suma requiere dos operandos ( o1 + o2 ). Los operadores unarios son aquellos que poseen solo una parte, por ejemplo, una operacin de incremento ( o1 ++ ).

Con el propsito de ver un ejemplo prctico vamos a retomar una vez ms la tan famosa clase Pareja, salvo que en esta ocasin vamos a sobrecargar dentro de la misma a los operadores binarios: + (suma), - (resta), * (multiplicacin) y / (divisin); el operador de asignacin (=); el operador de incremento (++) y el operador de comparacin (==). // Programa: sobrecarga05.cpp // Objetivo: mostrar sobrecarga de operadores // Autor...: Oscar Edmundo Palacios. #include <iostream> using namespace std; class Pareja { private: int a, b; public: // constructor base Pareja() : a(0), b(0) {} // constructor parametrizado Pareja(const int a,const int b) { this->a = a; this->b = b; } // constructor de copia Pareja(const Pareja&); // operadores miembros

Sobrecarga de Operadores Pareja& Pareja& Pareja& Pareja& Pareja& Pareja& bool operator operator operator operator operator operator operator + (const - (const * (const / (const = (const ++(); ==(const Pareja Pareja Pareja Pareja Pareja &p); &p); &p); &p); &p);

78

Pareja &p) const;

// operadores no miembros friend ostream& operator << (ostream &o,const Pareja &p); friend istream& operator >> (istream &o, Pareja &p); }; // implementacion de los operadores para //.................................... Pareja::Pareja(const Pareja &p) { *this=p; } //.................................... Pareja& Pareja::operator + (const Pareja { this->a += p.a; this->b += p.b; return *this; } //.................................... Pareja& Pareja::operator - (const Pareja { this->a -= p.a; this->b -= p.b; return *this; } //.................................... Pareja& Pareja::operator * (const Pareja { this->a *= p.a; this->b *= p.b; return *this; } //.................................... Pareja& Pareja::operator / (const Pareja { if (p.a != 0) this->a /= p.a; if (p.b != 0) this->b /= p.b; return *this; } la clase Pareja

&p)

&p)

&p)

&p)

Sobrecarga de Operadores //.................................... Pareja& Pareja::operator = (const Pareja &p) { if(this!=&p){ //Comprueba que no se est intentanod igualar un objeto a s mismo if (p.a != 0) this->a = p.a; if (p.b != 0) this->b = p.b; } return *this; } //.................................... Pareja& Pareja::operator ++ () { this->a ++; this->b ++; return *this; } //.................................... bool Pareja::operator == (const Pareja &p) const { return this->a == p.a && this->b == p.b; } // implemetacin de operadores no miembros ostream& operator << (ostream &o,const Pareja &p) { o << "(" << p.a << ", " << p.b << ")"; return o; } istream& operator >> (istream &i, Pareja &p) { cout << "Introducir valores para ( a, b) :"; i >> p.a >> p.b; i.ignore(); return i; }

79

// prueba para la clase Pareja int main() { Pareja A(50, 75); Pareja B(100, 15); Pareja C;

Sobrecarga de Operadores

80

cout << cout << cout << C = A * cout << cout << cout <<

"A = " << A << "\n"; "B = " << B << "\n"; "........................." << endl; B; "A = " << A << "\n"; "C = " << C << endl; "........................." << endl;

++C; cout << "C = " << C << endl; cout << "A == B " << ( (A==B) ? "Si": "No" ); cin.get(); return 0; }
Objetos y Clases Arriba Herencia

Herencia
Editores:
Sobrecarga de Operadores Funciones virtuales

Introduccin
La 'herencia' es una de las piedras angulares de la POO ya que sta permite la creacin de clasificaciones jerrquicas. Con la herencia, es posible crear una clase general que defina tratos comunes a una serie de elementos relacionados. Esta clase podra luego ser heredada por otras clases ms especficas, cada una agregando solo aquellas cosas que son nicas para la clase 'heredera'. En terminologa estndar C++, una clase que es heredada es referida como la clase 'base'. La clase que efecta la herencia es llamada la clase 'derivada'. Adems, una clase derivada puede ser usada como una clase base por otra clase derivada. De esta manera, una jerarqua multicapa de clases puede ser lograda.

INTRODUCIENDO LA HERENCIA.
C++ soporta herencia permitiendo a una clase incorporar otra clase dentro de su declaracin. Antes de discutir los detalles y la teora, se procede a comenzar por un ejemplo de herencia. La siguiente clase, llamada 'VehiculoRodante', define muy ampliamente a vehculos que viajan por la carretera. Este almacena el numero de ruedas que un vehculo tiene y el numero de pasajeros que puede llevar. Un regla sencilla para recordar esto es: "Una clase derivada hereda de una clase base" <font size="11.67"> // Definicin de una clase base para vehiculos class VehiculoRodante { public:

Herencia // CICLO DE VIDA /* En este lugar se sitan los constructores, los destructores, y/o los constructores copia */ // OPERADORES /* Aqu van los mtodos que se apliquen sobre operadores */ // OPERACIONES /* Aqu van los mtodos de esta clase que no sean ni de acceso ni de peticin o tratamiento */ // ACCESO /* Aqu van las funciones de acceso a los datos miembro o variables propias del objeto */ /* * Funcin 'set_ruedas' * Recibe: num como int * Devuelve: void * Asigna al dato miembro 'mRuedas' el valor 'num' */ void set_ruedas(int num) { this->mRuedas = num; } /* * Funcin 'get_ruedas' * Recibe: void * Devuelve: int * Devuelve el valor que hay dentro del dato miembro 'mRuedas' */ int get_ruedas(void) { return this->mRuedas; } /* * Funcin 'set_pasajeros' * Recibe: num como int * Devuelve: void * Asigna al dato miembro 'mPasajeros' el valor 'num' */ void set_pasajeros(int num) { this->mPasajeros = num; }

81

Herencia

82

/* * Funcin 'get_pasajeros' * Recibe: void * Devuelve: int * Devuelve el valor que hay dentro del dato miembro 'mPasajeros' */ int get_pasajeros(void) { return this->mPasajeros; } // PETICIONES/TRATAMIENTOS /* Aqu van las funciones del tipo "Is", que generalmente devuelven true/false */ private: /* Generalmente en 'private' se sitan los datos miembros */ int mRuedas; int mPasajeros; }; </font> Se puede usar esta amplia definicin de un vehculo rodante para ayudar a definir tipos especficos de vehculos. Por ejemplo, el fragmento de cdigo mostrado aqui podra usarse para ser heredado por una clase derivada llamada 'Camion'. <font size="11.67"> // Definicin de una clase 'Camion' derivada de la clase base 'VehiculoRodante'. class Camion : public VehiculoRodante { public: // CICLO DE VIDA /* En este lugar se sitan los constructores, los destructores, y/o los constructores copia */ // OPERADORES /* Aqu van los mtodos que se apliquen sobre operadores */ // OPERACIONES /* Aqu van los mtodos de esta clase que no sean ni de acceso ni de peticin o tratamiento */ // ACCESO /* Aqu van las funciones de acceso a los datos miembro o variables propias del objeto */

Herencia

83

/* * Funcin 'set_carga' * Recibe: size como int * Devuelve: void * Asigna al dato miembro 'mCarga' el valor 'size' */ void set_carga(int size) { this->mCarga = size; } /* * Funcin 'get_carga' * Recibe: void * Devuelve: int * Devuelve el valor que hay dentro del dato miembro 'mCarga' */ int get_carga(void) { return this->mCarga; }

/* * Funcin 'Mostrar' * Recibe: void * Devuelve: void * Muestra por pantalla las ruedas, pasajeros y la capacidad de carga del objeto 'Camion' */ void Mostrar(void); // PETICIONES/TRATAMIENTOS /* Aqu van las funciones del tipo "Is", que generalmente devuelven true/false */ private: /* Generalmente en 'private' se sitan los datos miembros */ int mCarga; }; </font> Como 'Camion' hereda de 'VehiculoRodante', 'Camion' incluye todo de 'vehiculo_rodante'. Entonces agrega 'carga' a la misma, en conjunto con las funciones miembros que trabajen sobre este dato. Ntese como 'VehiculoRodante' es heredado. La forma general para la herencia se muestra aqu:

Herencia <font size="11.67"> class ClaseDerivada : acceso ClaseBase { //cuerpo de la nueva clase } </font> Aqu, 'acceso' es opcional. Sin embargo, si se encuentra presente, debe ser 'public', 'private', o 'protected'. Se aprender ms sobre estas opciones ms tarde, por ahora para todas las clases heredadas se usar el acceso 'public'. Usar public significa que todos los miembros pblicos de la clase base sern tambin miembros pblicos de la clase derivada. Por tanto, en el ejemplo anterior, miembros de la clase 'Camion' tienen acceso a los miembros pblicos de 'VehiculoRodante', justo como si ellos hubieran sido declarados dentro de 'Camion'. Sin embargo, 'camion' no tiene acceso a los miembros privados de 'VehiculoRodante'. Por ejemplo, 'Camion' no tiene acceso a ruedas. He aqu un programa que usa herencia para crear dos subclases de 'VehiculoRodante'. Una es 'Camion' y la otra es 'Automovil'.
<font size="11.67"> // Programa que demuestra la herencia. // INCLUDES DE SISTEMA // #include <iostream> // INCLUDES DEL PROYECTO // // INCLUDES LOCALES // // DECLARACIONES // // Definicin de una clase base para vehiculos class VehiculoRodante { public: // CICLO DE VIDA /* En este lugar se sitan los constructores, los destructores, y/o los constructores copia */ // OPERADORES /* Aqu van los mtodos que se apliquen sobre operadores */ // OPERACIONES /* Aqu van los mtodos de esta clase que no sean ni de acceso ni de peticin o tratamiento */ /*

84

Herencia
* Funcin 'set_ruedas' * Recibe: num como int * Devuelve: void * Asigna al dato miembro 'mRuedas' el valor 'num' */ void set_ruedas(int num) { this->mRuedas = num; } /* * Funcin 'get_ruedas' * Recibe: void * Devuelve: int * Devuelve el valor que hay dentro del dato miembro 'mRuedas' */ int get_ruedas(void) { return this->mRuedas; } /* * Funcin 'set_pasajeros' * Recibe: num como int * Devuelve: void * Asigna al dato miembro 'mPasajeros' el valor 'num' */ void set_pasajeros(int num) { this->mPasajeros = num; } /* * Funcin 'get_pasajeros' * Recibe: void * Devuelve: int * Devuelve el valor que hay dentro del dato miembro 'mPasajeros' */ int get_pasajeros(void) { return this->mPasajeros; } // PETICIONES/TRATAMIENTOS /* Aqu van las funciones del tipo "Is", que generalmente devuelven true/false */

85

Herencia
private: /* Generalmente en 'private' se sitan los datos miembros */ int mRuedas; int mPasajeros; }; // Definicin de una clase 'Camion' derivada de la clase base 'VehiculoRodante'. class Camion : public VehiculoRodante { public: // CICLO DE VIDA /* En este lugar se sitan los constructores, los destructores, y/o los constructores copia */ // OPERADORES /* Aqu van los mtodos que se apliquen sobre operadores */ // OPERACIONES /* Aqu van los mtodos de esta clase que no sean ni de acceso ni de peticin o tratamiento */ // ACCESO /* Aqu van las funciones de acceso a los datos miembro o variables propias del objeto */ /* * Funcin 'set_carga' * Recibe: size como int * Devuelve: void * Asigna al dato miembro 'mCarga' el valor 'size' */ void set_carga(int size) { this->mCarga = size; } /* * Funcin 'get_carga' * Recibe: void * Devuelve: int * Devuelve el valor que hay dentro del dato miembro 'mCarga' */ int get_carga(void) { return this->mCarga;

86

Herencia
}

87

/* * Funcin 'Mostrar' * Recibe: void * Devuelve: void * Muestra por pantalla las ruedas, pasajeros y la capacidad de carga del objeto 'Camion' */ void Mostrar(void); // PETICIONES/TRATAMIENTOS /* Aqu van las funciones del tipo "Is", que generalmente devuelven true/false */ private: /* Generalmente en 'private' se sitan los datos miembros */ int mCarga; }; void Camion::Mostrar(void) { std::cout << "ruedas: " << this->get_ruedas() << std::endl; std::cout << "pasajeros: " << this->get_pasajeros() << std::endl; std::cout << "Capacidad de carga en pies cbicos: " << this->get_carga() << std::endl; } /* * Este enumerador sirve para definir diferentes tipos de automvil */ enum tipo {deportivo, berlina, turismo}; // Definicin de una clase 'Automovil' derivada de la clase base 'VehiculoRodante'. class Automovil : public VehiculoRodante { public: // CICLO DE VIDA /* En este lugar se sitan los constructores, los destructores, y/o los constructores copia */ // OPERADORES /* Aqu van los mtodos que se apliquen sobre operadores */ // OPERACIONES /* Aqu van los mtodos de esta clase que no sean ni de acceso ni de

Herencia
peticin o tratamiento */ // ACCESO /* Aqu van las funciones de acceso a los datos miembro o variables propias del objeto */ /* * Funcin 'set_tipo' * Recibe: t como tipo * Devuelve: void * Asigna al dato miembro 'mTipoDeAutomovil' el valor 't' */ void set_tipo(tipo t) { this->mTipoDeAutomovil = t; } /* * Funcin 'get_tipo' * Recibe: void * Devuelve: tipo * Devuelve el valor que hay dentro del dato miembro 'mTipoDeAutomovil' */ enum tipo get_tipo(void) { return this->mTipoDeAutomovil; }; /* * Funcin 'Mostrar' * Recibe: void * Devuelve: void * Muestra por pantalla las ruedas, pasajeros y la capacidad de carga del objeto 'Camion' */ void Mostrar(void); private: enum tipo mTipoDeAutomovil; }; void Automovil::Mostrar(void) { std::cout << "ruedas: " << this->get_ruedas() << std::endl; std::cout << "pasajeros: " << this->get_pasajeros() << std::endl; std::cout << "tipo: ";

88

Herencia
switch(this->get_tipo()) { case deportivo: std::cout << "deportivo"; break; case berlina: std::cout << "berlina"; break; case turismo: std::cout << "turismo"; } std::cout << std::endl; } /* * Funcin 'main' * Recibe: void * Devuelve: int * El cdigo es una posible implementacin para clarificar el uso efectivo de clases bases y clases derivadas. * Muestra por pantalla valores de la clase base y de las clases derivadas. */ int main(void) { Camion Camion1; Camion Camion2; Automovil Automovil1; Camion1.set_ruedas(18); Camion1.set_pasajeros(2); Camion1.set_carga(3200); Camion2.set_ruedas(6); Camion2.set_pasajeros(3); Camion2.set_carga(1200); Camion1.Mostrar(); std::cout << std::endl; Camion2.Mostrar(); std::cout << std::endl; Automovil1.set_ruedas(4); Automovil1.set_pasajeros(6); Automovil1.set_tipo(tipo::deportivo);

89

Herencia

90

Automovil1.Mostrar(); return 0; } </font>

La salida de este programa se muestra a continuacin: ruedas: 18 pasajeros: 2 Capacidad de carga en pies cbicos: 3200 ruedas: 6 pasajeros: 3 Capacidad de carga en pies cbicos: 1200 ruedas: 4 pasajeros: 6 tipo: deportivo Como este programa muestra, la mayor ventaja de la herencia es que permite crear una clase base que puede ser incorporada en clases ms especficas. De esta manera, cada clase derivada puede ser precisamente ajustada a las propias necesidades y aun siendo parte de la clasificacin general. Por otra parte, ntese que ambos, 'Camion' y 'Automovil', incluyen una funcin miembro llamada 'Mostrar()', la cual muestra informacin sobre cada objeto. Esto ilustra un aspecto del polimorfismo. Como cada funcin 'Mostrar()' esta enlazada con su propia clase, el compilador puede fcilmente indicar cul llamar para cualquier objeto dado. Ahora que se ha visto los procedimientos bsicos por los cules una clase hereda de otra, se examinar la herencia en detalle.

CONTROL DE ACCESO DE LA CLASE BASE.


Cuando una clase hereda de otra, los miembros de la clase base se convierten en miembros de la clase derivada. El estado de acceso de los miembros de la clase base dentro de la clase derivada es determinado por el especificador de acceso usado para heredar la clase base. El especificador de acceso de la clase base debe ser 'public', 'private' o 'protected'. Si el especificador de acceso no es usado, entonces se usara private por defecto si la clase derivada es una clase. Si la clase derivada es una 'struct' entonces 'public' es el acceso por defecto por la ausencia de un expecificador de acceso explcito. Examinemos las ramificaciones de usar accesos public o private. ( El especificador 'protected' se describe en la prxima seccin.) Cuando una clase base es heredada como 'public', todos los miembros pblicos de la clase base se convierten en miembros de la clase derivada. En todos los casos, los elementos privados de la clase base se mantienen de esa forma para esta clase, y no son accesibles por miembros de la clase derivada. Por ejemplo, en el siguiente programa, los miembros pblicos de 'base' se convierten en miembros publics de 'derivada'. Encima, son accesibles por otras partes del programa. #include <iostream> using namespace std; class base { int i, j;

Herencia

91

public: void set(int a, int b) { i = a; j = b; } void mostrar() { cout << i << " " << j << "\n"; } }; class derivada : public base { int k; public: derivada(int x) { k = x; } void mostrar_k() { cout << k << "\n"; } }; int main() { derivada obj(3); obj.set(1, 2); // accesar a miembro de base obj.mostrar(); // accesar a miembro de base obj.mostrar_k(); // usa miembro de la clase derivada return 0; } Como set() y mostrar() son heredadas como 'public', ellas pueden ser llamadas en un objeto del tipo 'derivada' desde main(). Como i y j son especificadas como 'private', ellas se mantienen privadas a base. El opuesto de herencia publica es herencia privada. Cuando la clase base es heredad como privada, entonces todos los miembros pblicos de la clase base se convierten en miembros privados de la clase derivada. Por ejemplo, el programa mostrado a continuacin no compilara, porque set() y mostrar() son ahora miembros privados de 'derivada', y por ende no pueden ser llamados desde main(). TIP:"Cuando una clase base es heredada como 'private' sus miembros pblicos se convierten en miembros privados de la clase derivada." // Este programa no compilara. #include <iostream> using namespace std; class base { int i, j; public: void set(int a, int b) { i = a; j = b; }

Herencia void mostrar() { cout << i << " " << j << "\n"; } }; // Miembros publicos de 'base' son privados en 'derivada' class derivada : private base { int k; public: derivada(int x) { k = x; } void mostrar_k() { cout << k << "\n"; } }; int main() { derivada obj(3); obj.set(1, 2); obj.mostrar(); return 0; } La clave a recordar es que cuando una clase base es heredada como 'private', los miembros pblicos de la clase base se convierten en miebros privados de la clase derivada. Esto significa que aun ellos son accesibles por miembros de la clase derivada, pero no pueden ser accedidos por otras partes de su programa. // Error, no se puede acceder a set() // Error, no se puede acceder a mostrar()

92

USANDO MIEMBROS PROTEGIDOS.


En adicin a public y private, un miembro de la clase puede ser declarado como protegido. Adems, una clase base puede ser heredada como protegida. Ambas de estas acciones son cumplidas por el especificador de acceso 'protected'. La palabra clave 'protected' esta incluida en C++ para proveer gran flexibilidad para el mecanismo de herencia. Cuando un miembro de una clase es declarado como 'protected', ese miembro no es accesible a otros, elementos no-miembros del programa. Con una importante excepcin, el acceso a un miembro protegido es lo mismo que el acceso a un miembro privado, este puede ser accedido solo por otros miembros de la clase de la cual es parte. La nica excepcin a esta regla es cuando un miembro protegido es heredado. En este caso, un miembro protegido difiere sustancialmente de uno privado. Como debe conocer, un miembro privado de una clase base no es accesible por cualquier otra parte de su programa, incluyendo cualquier clase derivada. Sin embargo, los miembros protegidos se comportan diferente. Cuando una clase base es heredada como publica, los miembros protegidos en la clase base se convierten en miembros protegidos de la clase derivada, y 'son' accesibles a la clase derivada. Adems, usando 'protected' usted puede crear miebros de clases que son privados para su clase, pero que aun asi pueden ser heredados y accedidos por una clase derivada. Considere este programa de ejemplo:

Herencia #include <iostream> using namespace std; class base { protected: int i, j;

93

// privados a base, pero accesibles a derivada.

public: void set(int a, int b) { i = a; j = b; } void mostrar() { cout << i << " " << j << "\n"; } }; class derivada : public base { int k; public: // derivada puede accesar en base a 'j' e 'i' void setk() { k = i * j; } void mostrark() { cout << k << "\n"; } }; int main() { derivada obj; obj.set(2, 3); // OK, conocido por derivada. obj.mostrar(); // OK, conocido por derivada. obj.setk(); obj.mostrark(); return 0; } Aqu, porque 'base' es heredada por 'derivada' como pblica, y porque j e i son declaradas como protegidas, la funcin setk() en 'derivada' puede acceder a ellas. Si j e i hubieran sido declaradas como privadas por 'base', entonces 'derivada' no tuviera acceso a ellas, y el programa no compilara. RECUERDE: El especificador 'protected' le permite crear un miembro de la clase que es accesible desde la jerarqua de la clase, pero de otra manera es privado. Cuando una clase derivada es usada como clase base para otra clase derivada, entonces cualquier miembro protegido de la clase base inicial que es heredado ( como public ) por la primera clase derivada puede ser heredado nuevamente, como miembro protegido, por una segunda clase derivada. Por ejemplo, el siguiente programa es correcto, y derivada2 tiene, de hecho, acceso a 'j' e 'i':

Herencia #include <iostream> using namespace std; class base { protected: int i, j; public: void set(int a, int b) { i = a; j = b; } void mostrar() { cout << i << " " << j << "\n"; } }; // j e i se heredan como 'protected' class derivada1 : public base { int k; public: void setk() { k = i*j; } // legal void mostrark() { cout << k << "\n"; } }; // j e i se heredan indirectamente a travs de derivada1 class derivada2 : public derivada1 { int m; public: void setm() { m = i-j; } // legal void mostrarm() { cout << m << "\n"; } }; int main() { derivada1 obj1; derivada2 obj2; obj1.set(2, 3); obj1.mostrar(); obj1.setk(); obj1.mostrark(); obj2.set(3, 4); obj2.mostrar(); obj2.setk();

94

Herencia obj2.setm(); obj2.mostrark(); obj2.mostrarm(); return 0; } Cuando una clase base es heredada como 'private', miembros protegidos de la clase base se convierten en miembros privados de la clase derivada. Adems, en el ejemplo anterior, si 'base' fuera heredada como 'private', entonces todos los miembros de 'base' se hubieran vuelto miembros privados de derivada1, significando que ellos no podran estar accesibles a derivada2. ( Sin embargo, j e i podran aun ser accesibles a derivada1.) Esta situacin es ilustrada por el siguiente programa, el cual es un error ( y no compilara ). Los comentarios describen cada error. // Este programa no compilara. #include <iostream> using namespace std; class base { protected: int i, j; public: void set(int a, int b) { i = a; j = b; } void mostrar() { cout << i << " " << j << "\n"; } }; // Ahora todos los elementos de base son privados en derivada1. class derivada1 : private base { int k; public: // Esto es legal porque j e i son privadas a derivada1 void setk() { k = i*j; } // OK void mostrark() { cout << k << "\n"; } }; // Acceso a j, i, set() y mostrar no heredado class derivada2 : public derivada1 { int m; public: // Ilegal porque j e i son privadas a derivada1 setm() { m = j-i; } // error void mostrarm() { cout << m << "\n"; } }; int main()

95

Herencia { derivada1 obj1; derivada2 obj2; obj1.set(1, 2); // Error, no se puede usar set() obj1.mostrar(); // Error, no se puede usar show() obj2.set(3, 4); // Error, no se puede usar set() obj2.mostrar(); // Error, no se puede usar show() return 0; } Incluso aunque 'base' es heredada como privada por derivada1, derivada2 aun tiene acceso a los elementos publicos y protegidos de 'base'. Sin embargo, no puede sobrepasar este privilegio a todo lo largo. Esta es la razon de las partes 'protected' del lenguaje C++. Este provee un medio de proteger ciertos miembros de ser modificados por funciones no-miembros, pero les permite ser heredadas. El especificador 'protected' puede ser usado con estructuras. Sin embargo no puede ser usado con una 'union' porque la union no puede heredar otra clase o ser heredad. ( Algunos compiladores aceptaran su uso en una declaracion en una union, pero como las uniones no pueden participar en la herencia, 'protected' es lo mismo que 'private' en este contexto.) El especificador 'protected' puede ocurrir en cualquier lugar en la declaracion de una clase, aunque tipicamente ocurre despues de ( por defecto ) que los miembros privados son declarados, y antes de los miebros publicos. Ademas, la mayor forma completa de la declaraciond de una clase es class nombre-clase { miembros privados protected: miembros protegidos public: miembros publicos }; Por supuesto, la categoria protected es opcional.

96

USANDO PROTECTED PARA LA HERENCIA DE UNA CLASE BASE.


Como adicion a especificar el estado protegido para los miembros de una clase, la palabra clave 'protected' tambien puede ser usada para heredar una clase base. Cuando una clase base es heredada como protected, todos los miembros publicos y protegidos de la clase base se convierten en miembros protegidos de la clase derivada. Aqui hay un ejemplo: // Demuestra la herencia de una clase base protegida #include <iostream> using namespace std;

Herencia class base { int i; protected: int j; public: int k; void seti(int a) { i = a; } int geti() { return i; } }; // heredar 'base' como protected. class derivada : protected base { public: void setj(int a) { j = void setk(int a) { k = int getj() { return j; int getk() { return k; }; int main() { derivada obj; /* La proxima linea es ilegal porque seti() es un miembro protegido de derivada, lo cual lo hace inaccesible fuera de derivada. */ // obj.seti(10); // cout << obj.geti(); // ilegal -- geti() es protected. // obj.k = 10; // tambien ilegal porque k es protected. // estas declaraciones son correctas obj.setk(10); cout << obj.getk() << " "; obj.setj(12); cout << obj.getj() << " "; return 0; } Como puede ver leyendo los comentarios en este programa, k,j, seti() y geti() en 'base' se convierten miembros 'protected' en 'derivada'. Esto significa que ellos no pueden ser accesados por codigo fuera de 'derivada'. Ademas, dentro de main(), referencias a estos miembros a traves de obj son ilegales.

97

a; }; // j es protected aqui. a; }; // k es tambien protected. } }

Herencia

98

REVISITANDO public, protected, y private


Ya que los permisos de acceso que son definidos por 'public', 'protected', y 'private' son fundamentales para la programacin en C++, volvamos a revisitar sus significados. Cuando una clase miembro es declarada como 'public', esta puede ser accedida por cualquier otra parte del programa. Cuando un miembro es declarado como 'private', este puede ser accedido solo por miembros de su clase. Adems, clases derivadas no tienen acceso a miembros privados de la clase base. Cuando un miembro es declarado como 'protected', este puede ser accedido solo por miembros de su clase, o por clases derivadas. Adems, protected permite que un miembro sea heredado, pero que se mantenga privado en la jerarqua de la clase. Cuando una clase base es heredada por el uso de 'public', sus miembros publicos se convierten en miebros publicos de la clase derivada, y sus miembros protected se convierten en miembros protected de la clase derivada. Cuando una clase base es heredada por el uso de 'protected', sus miembros publicos y protegidos se convierten en miembros protected de la clase derivada. Cuando una clase base es heredada por el uso de 'private', sus miembros publicos y protegidos se convierten en miebros private de la clase derivada. En todos los casos, los miembros privados de la clase base se mantienen privados a la clase base, y no son heredados.

HEREDANDO MULTIPLES CLASES BASE


Es posible para una clase derivada heredar dos o mas clases base. Por ejemplo, en este corto programa, derivada hereda de ambas clases base1 y base2: // Un ejemplo de multiples clases base #include <iostream> using namespace std; class base1 { protected: int x; public: void showx() { cout << x << "\n"; } }; class base2 { protected: int y; public: void showy() { cout << y << "\n"; } }; // Heredar multiples clases base. class derivada : public base1, public base2 { public: void set(int i, int j) { x= i; y = j; };

Herencia }; int main() { derivada obj; obj.set(10, 20); obj.showx(); obj.showy(); return 0; } Como este ejemplo ilustra, para causar que mas de una clase base sea heredad, debe usarse una lista separada por comas. Ademas, asegurese de usar un especificador de acceso para cada clase heredada. // proveida por derivada. // desde base1 // desde base2

99

CONSTRUCTORES, DESTRUCTORES, Y HERENCIA


Existen dos importantes preguntas que arriban relativas a los constructores y destructores cuando la herencia se encuentra implicada. Primero, donde son llamados los constructores y destructores de las clases base y clases derivadas? Segundo, como se pueden pasar los parametros al constructor de una clase base? Esta seccin responde estas preguntas. Examine este corto programa: #include <iostream> using namespace std; class base { public: base() { cout << "Construyendo base\n"; } ~base() { cout << "Destruyendo base\n"; } };

class derivada : public base { public: derivada() { cout << "Construyendo derivada\n"; } ~derivada() { cout << "Destruyendo derivada\n"; } }; int main() { derivada obj; // no hacer nada mas que construir y destruir obj return 0;

Herencia } Como el comentario en main() indica, este programa simplemente construye y destruye un objeto llamado obj, el cual es de la clase 'derivada'. Cuando se ejecuta, este programa muestra: Construyendo base Construyendo derivada Destruyendo derivada Destruyendo base Como puede ver, el constructor de 'base' es ejecutado, seguido por el constructor en 'derivada'. A continuacin ( ya que obj es inmediatamente destruido en este programa), el destructor en 'derivada' es llamado, seguido por el de 'base'. El resultado del experimento anterior puede ser generalizado como lo siguiente: Cuando un objeto de una clase derivada es creado, el constructor de la clase base es llamado primero, seguido por el constructor de la clase derivada. Cuando un objeto derivada es destruido, su destructor es llamado primero, seguido por el destructor de la clase base. Vindolo de otra manera, los constructores son ejecutados en el orden de su derivacin. Los destructores son ejecutado en orden inverso de su derivacin. Si lo piensa un poco, tiene sentido que las funciones constructor sean ejecutadas en el orden de su derivacin. Porque una clase base no tiene conocimiento de las clases derivadas, cualquier inicializacin que necesite realizar es separada de, y posiblemente un prerequisito a, cualquier inicializacin realidada por la clase derivada. Adems, esta debe ejecutarse primero. Asimismo, es bastante sensible que los destructores sean ejecutados en orden inverso a su derivacin. Ya que la clase base contiene una clase derivada, la destruccion de una clase base implica la destruccion de su clase derivada. Adems, el constructor derivado debe ser llamado antes de que el objeto sea completamente destruido. En el caso de una gran jerarqua de clases ( ej: cuando una clase derivada se convierta en la base clase de otra clase derivada ), la regla general se aplica: Los constructores son llamados en orden de su derivacion, los destructores son llamados en orden inverso. Por ejemplo, este programa #include <iostream> using namespace std; class base { public: base() { cout << "construyendo base\n"; } ~base() { cout << "Destruyendo base\n"; } }; class derivada1 : public base { public: derivada1() { cout << "Construyendo derivada1\n"; } ~derivada1() { cout << "destruyendo derivada1\n"; } };

100

class derivada2 : public derivada1 { public: derivada2() { cout << "Construyendo derivada2\n"; }

Herencia ~derivada2() { cout << "Destruyendo derivada2\n"; } }; int main() { derivada2 obj; // construir y destruir obj return 0; } Muestra la salida: construyendo base Construyendo derivada1 Construyendo derivada2 Destruyendo derivada2 destruyendo derivada1 Destruyendo base La misma regla general se aplica en situaciones en que se ven implicadas multiples clases base. Por ejemplo, este programa #include <iostream> using namespace std; class base1 { public: base1() { cout << "Construyendo base1\n"; } ~base1() { cout << "Desstruyendo base1\n"; } }; class base2 { public: base2() { cout << "Construyendo base2\n"; } ~base2() { cout << "Destruyendo base2\n"; } }; class derivada : public base1, public base2 { public: derivada() { cout << "Construyendo derivada\n"; } ~derivada() { cout << "Destruyendo derivada\n"; } }; int main()

101

Herencia { derivada obj; // construir y destruir obj return 0; } Produce la salida: Construyendo base1 Construyendo base2 Construyendo derivada Destruyendo derivada Destruyendo base2 Destruyendo base1 Como puede ver, los constructores son llamados en el orden de su derivacion, de izquierda a derecha, como se especifico en la lista de herencia en derivada. Los destructores son llamados en orden inverso, de derecha a izquierda. Esto significa que si base2 fuera especificada antes de base1 en la lista de derivada, como se muestra aqui: class derivada: public base2, public base1 { entonces la salida del programa anterior hubiera sido asi: Construyendo base2 Construyendo base1 Construyendo derivada Destruyendo derivada Destruyendo base1 Derstruyendo base2

102

PASANDO PARAMETROS A LOS CONSTRUCTORES DE LA CLASE BASE


Hasta ahora, ninguno de los ejemplos anteriores han incluido constructores que requieran argumentos. En casos donde solo el constructor de la clase derivada requiera uno o mas argumentos, simplemente use la sintaxis estandarizada de parametrizacion del constructor. Pero, como se le pasan argumentos a un constructor en una clase base? La respuesta es usar una forma expandida de la declaracion de la clase derivada, la cual pasa argumentos entre uno o mas constructores de la clase base. La forma general de esta declaracion expandida se muestra aqui: constructor-derivada(lista-argumentos) : base1(lista-argumentos), base2(lista-argumentos), baseN(lista-argumentos); { cuerpo del constructor derivado } Aqui, desde base1 hasta baseN son los nombres de las clases base heredadas por la clase derivada. Notese los dos puntos separando la declaracion del constructor de la clase derivada de las clases base, y que las clases base estan separadas cada una de la otra por comas, en el caso de multiples clases base. Considere este programa de ejemplo:

Herencia #include <iostream> using namespace std; class base { protected: int i; public: base(int x) { i = x; cout << "Construyendo base\n"; } ~base() { cout << "Destruyendo base\n"; } };

103

class derivada : public base { int j; public: // derivada usa x, y es pasado en consjunto a base derivada(int x, int y) : base(y) { j = x; cout << "Construyendo derivada\n"; } ~derivada() { cout << "Destruyendo derivada\n"; } void mostrar() { cout << i << " " << j << "\n"; } }; int main() { derivada obj(3, 4); obj.mostrar(); return 0; } Aqui, el constructor en derivada es declarado para que tome 2 parametros, x, y. Sin embargo, derivada() usa solo x, y es pasada a base(). En general, el constructor de la clase derivada debe declarar el/los parametro(s) que esta clase requiere, tamnbien cualquiera requerido por la clase base. Como el ejemplo anterior ilustra, cualquiera de los parametrosr requeridos por la clase base son pasados a esta en la lista de argumentos de la clase base, especificadas despues de los dos puntos. Aqui tenemos un programa de ejemplo que usa multiples clases base: #include <iostream> using namespace std; class base1 { protected: int i; // muestra 4 3

Herencia

104

public: base1(int x) { i = x; cout << "Construyendo base1\n"; } ~base1() { cout << "Destruyendo base1\n"; } }; class base2 { protected: int k; public: base2(int x) { k = x; cout << "construyendo base2\n"; } ~base2() { cout << "Destuyendo base2\n"; } }; class derivada : public base1, public base2 { int j; public: derivada(int x, int y, int z) : base1(y), base2(z) { j = x; cout << "construyendo derivada\n"; } ~derivada() { cout << "Destruyendo derivada\n"; } void mostrar() { cout << i << " " << j << " " << k << "\n"; } }; int main() { derivada obj(3, 4, 5); obj.mostrar(); return 0; } Es importante comprender que los argumentos al constructor de la clase base son pasado via argumentos al constructor de la clase derivada. Ademas, incluso si el constructor de una clase derivada no usa ningun argumento, este aun debe declarar uno o mas argumentos si la clase base toma uno o mas argumentos. En esta situacion, los argumentos pasado a la clase derivada son simplemente pasados hacia la clase base. Por ejemplo en el siguiente programa, el constructor en derivada no toma argumentos, pero base1() y base2() lo hacen: #include <iostream> using namespace std; class base1 { protected: // mostrar 3 4 5

Herencia

105

int i; public: base1(int x) { i=x; cout << "Construyendo base1\n"; } ~base1() { cout << "Destruyendo base1\n"; } }; class base2 { protected: int k; public: base2(int x) { k=x; cout << "construyendo base2\n"; } ~base2() { cout << "Destruyendo base2\n"; } }; class derivada : public base1, public base2 { public: /* el constructor en derivada no usa parametros, pero aun asi debe ser declarado para tomarlos y pasarselos a las clases base. */ derivada(int x, int y) : base1(x), base2(y) { cout << "Construyendo derivada\n"; } ~derivada() { cout << "Destruyendo derivada\n"; } void mostrar() { cout << i << "" << k << "\n"; } }; int main() { derivada obj(3, 4); obj.mostrar(); return 0; } El constructor de la clasde derivada es libre de usar cualquiera de todos los parameteos que se declara que toma, ya sea que uno o mas sean pasados a una clase base. Viendolo diferente, solo porque un argumento es pasado a la clase base no declara su uso en la clase derivada. Por ejemplo, este fragmento es perfectamente valido: class derivada : public base { int j; // mostrar 3 4

Herencia public: // derivada usa x, y, y tambien las pasa a base derivada(int x, int y) : base(x,y) { j = x*y; cout << "construyendo derivada\n"; } // ... Un punto final a tener en mente cuando se pase argumentos a los constructores de la clase base: Un argumento siendo pasado puede consistir de cualquier expresion valida en ese momento, incluyendo llamadas a funciones y variables. Esto es para sostener el hecho de que C++ permite la inicializacion dinamica.

106

GARANTIZANDO ACCESO
Cuando una clase base es heredada como private, todos los miembros de esa clase (public, protected o private) se convierten en miembros privates de la clase derivada. Sin embargo, en ciertas circunstancias, podra restaurar uno o mas miembros heredados a su especificacion de acceso original. Por ejemplo, quizas desee permitir a ciertos miembros publicos de la clase base tener un estado public en la clase derivada, incluso aunque la clase es heredada como private. Hay dos maneras de lograr esto. Primero, usted deberia usar una declaracion 'using' con la clase derivada. Este es el metodo recomendado por los estandares C++ para uso no nuevo codigo. Sin embargo, una discusion de 'using' se retrasa hasta finales de este libro donde los 'namespaces' son examinados. ( La primera razon para usar using es para dar soporte a namespaces. ) La segunda manera para ajustar el acceso a un miembro heredado es emplear una declaracion de acceso. Las declaraciones de acceso son aun soportadas por el estandar C++, pero han sido marcadas como deprecadas, lo que significa que ellas no deben ser usadas para nuevo codigo, Como aun son usadas en codigo existente, una discusion de las declaraciones de acceso es presentada aqui: Una declaracion de acceso toma esta forma general: clase-base::miembro; La declaracion de acceso es ubicada bajo los apropiados encabezados de acceso en la clase derivada. Notese que no se requiere ( o permite ) una declaracion de tipo en una declaracion de acceso. para ver como una declaracion de acceso funciona, comenzemos con este corto fragmento: class base { public: int j; // public en base }; // heredar base como private class derivada : private base { public: // aqui esta la declaracion de acceso base::j; // hacer j publica nuevamente. // ... }; Como base es heredada como private por derivada, la variable publica j es hecha una variable 'private' en derivada. Sin embargo, la inclusion de esta declaracion de acceso base::j;

Herencia Puede usar una declaracion de acceso para restaturar los derechos de acceso de miembros 'public' y 'protected', Sin embargo, no puede usar una declaracion de acceso para elevar o disminuir un estado de acceso de un miembro. Por ejemplo, un miembro declarado como private dentro de una clase base no puede ser hecho public por una clase derivada. ( Permitir esto destruirira la encapsulacion!) El siguiente programa ilustra el uso en declaraciones de acceso. #include <iostream> using namespace std; class base { int i; // private en base public: int j, k; void seti(int x) { i = x; } int geti() { return i; } }; // Heredar base como private class derivada : private base { public: /* las siguientes tres declaraciones sobreescriben la herencia de base como private y restablece j, seti() y geti() a publico acceso */ base::j; // hacer j public nuevamente - pero no k base::seti; // hacer seti() public base::geti; // hacer geti() public // base::i; // ilegal - no se puede elevar el acceso

107

int a; // public }; int main() { derivada obj; // obj.i = 10; obj.j = 20; //obj.k = 30; obj.a = 40; obj.seti(10); // ilegal porque i es private en derivada // legal porque j es hecho public en derivada // ilegal porque k es private en derivada // legal porque a es public en derivada

Herencia

108

cout << obj.geti() << " return 0; }

" << obj.j << " " << obj.a;

Notese como este programa usa declaraciones de acceso para restaurar j, seti(), y geti() a un estadi 'public'. Los comentarios describen otras varias restricciones de acceso. C++ provee la habilidad de ajustar acceso a los miembros heredados para acomodar aquellas situaciones especiales en las cuales la mayoria de una clase heredad esta pensada para ser private, pero unos cuantos miembros se hacen para mantener su estado public o protected. Es mejor usar esta caracteristica escasamente.

LEYENDO GRAFICOS DE HERENCIA EN c++


En ocasiones las jerarquias de clases en C++ son hechas graficamente para hacerlas mas facil de comprender. Sin embargo, debido a la forma en que estas son usualmente dibujadas por programadores de C++, los graficos de herencia son a veces incomprendidos por principiantes. Por ejemplo, considere una situacion en la cual la clase A es heredad por la clase B, la cual es heredada por la clase C. Usando la notacion estandar C++, esta situacion es dibujada como se muestra aqui: NOTA: "el simbolo ^ es la punta de la fecha, que indica direccion." A ^ | B ^ | C Como puede ver, las flechas apuntan hacia arriba, no hacia abajo. Mientras que muchas personas encuentran inicialmente la direccion de las flechas contra-intuitivasm este es el estilo que la mayoria de los programadores de C++ han adoptado. En graficos al estilo C++, las flechas apuntan a la clase base. Ademas, la flecha quiere decir "derivada desde", y no "derivando". Aqui tenemos otro ejemplo. Puede describir en palabras que significa esto? A ^ | ^ C ^ \ \ \ / E Este grafico declara que la clase E es derivada de C y D. ( Eso es, E tiene multiples clases base, llamadas C y D.) Ademas, C es derivada de A, y D derivada de B. / B ^ | ^ D ^ /

Herencia Mientras que la direccion de las flechas puede confundir a la primera, es mejor que se sienta familiar con este estilo de notacion grafica, ya que es comunmente usada en libros, revistas, y documentacion de compiladores.

109

CLASES BASE VIRTUALES


Un elemento de ambiguedad puede ser introducido en un programa C++ cuando multiples clases bases son heredadas. Considere este incorrecto programa: // Este programa contiene un error y no compilara. #include <iostream> using namespace std; class base { public: int i; }; // derivada 1 hereda base class derivada1 : public base { public: int j; }; // derivada2 hereda base class derivada2 : public base { public: int k; }; /* derivada3 hereda de ambas, derivada1 y derivada2 esto significa que hay 2 copias de base en derivada3! */ class derivada3 : public derivada1, public derivada2 { public: int sum; }; int main() { derivada3 obj; obj.i = 10; obj.j = 20; obj.k = 30; // esto es ambiguo; cual i???

Herencia

110

// i es ambiguo aqui, tambien obj.sum = obj.i + obj.j + obj.k; // tambien ambiguo, cual i? cout << obj.i << " "; cout << obj.j << " " << obj.k << " "; cout << obj.sum; return 0; } Como indican los comentarios en este programa, ambos derivada1 y derivada2 heredan base. Sin embargo, derivada3 hereda ambos, derivada1 y derivada2. Como resultado existen dos copias de base presenten en un objeto del tipo derivada3, asi pues una expresion como obj.i = 20; hacia cual i se esta haciendo referencia? La que se encuentra en derivada1 o derivada2? Como existen dos copias de base presentes en el objeto 'obj' entonces hays dos obj.i's. Como puede ver, la declaracion es inherentemente ambigua. Existen dos maneras de remediar el anterior programa. La primera es aplicar el operador de resolucion de ambito para manualmente seleccionar una 'i'. Por ejemplo, la siguiente version el programa compilara y se ejecutara como se esperaba: // Este programa usa el la resolucion de // ambito explicita para seleccionar 'i' #include <iostream> using namespace std; class base { public: int i; }; // derivada 1 hereda base class derivada1 : public base { public: int j; }; // derivada2 hereda base class derivada2 : public base { public: int k; };

Herencia

111

/* derivada3 hereda de ambas, derivada1 y derivada2 esto significa que hay 2 copias de base en derivada3! */ class derivada3 : public derivada1, public derivada2 { public: int sum; }; int main() { derivada3 obj; obj.derivada1::i = 10; // ambito resuelto, se usa la 'i' derivada1 obj.j = 20; obj.k = 30; // ambito resuelto obj.sum = obj.derivada1::i + obj.j + obj.k; // tambien resuelto aqui.. cout << obj.derivada1::i << " "; cout << obj.j << " " << obj.k << " "; cout << obj.sum; return 0; } Aplicando ::, el programa manualmente selecciona a derivada1 como version de base. Sin embargo, esta solucion levanta un detalle profundo: Que pasa si solo una copia de base es en realidad requerida? Existe alguna forma de prevenir que dos copias sean incluidas en derivada3? La respuesta, como probablemente ha adivinado se logra con 'clases base virtuales'. Cuando dos o mas objetos son derivados de una clase base comun, puede prevenir multiples copias de la clase base de estar presentes en un objeto derivado por esas clases, declarando la clase base como virtual cuando esta es heredada. para hacer esto, se precede el nombre de la clase base con la palabra virtual cuando esta es heredada. Para ilustrar este proceso, aqui esta otra version del programa de ejemplo. Esta vez, derivada3 contiene solo una copia de base. // Este programa usa clases base virtuales. #include <iostream> using namespace std; class base {

Herencia public: int i; }; // derivada1 hereda base como virtual class derivada1 : virtual public base { public: int j; }; // derivada2 hereda base como virtual class derivada2 : virtual public base { public: int k; }; /* derivada3 hereda ambas, derivada1 y derivada2. Esta vez, solo existe una copia de la clase base. */ class derivada3 : public derivada1, public derivada2 { public: int sum; }; int main() { derivada3 obj; obj.i = 10; obj.j = 20; obj.k = 30; // ahora no-ambiguo

112

// no-ambiguo obj.sum = obj.i + obj.j + obj.k; // no-ambiguo cout << obj.i << " "; cout << obj.j << " " << obj.k << " "; cout << obj.sum; return 0; }

Herencia Como puede ver la palabra clave 'virtual' precede el resto de la especificacion de la clase heredada. Ahora que ambas, derivada1 y derivada2 han heredado base como virtual, cualquier herencia multiple que las incluya causara que solo una copia de base se encuentre presente. Ademas, en derivada3 existe solo una copia de base, y obj.i = 10 es perfectamente valida y no-ambiguo. Otro punto a tener en mente: incluso cuando ambas, derivada1 y derivada2 espcifican base como virtual, base sigue presente en los dos tipos derivados. Por ejemplo, la siguiente secuencia es perfectamente valida: // Definir una clase del tipo derivada1 derivada1 miclase; miclase.i = 88; La diferencia entre una clase base normal y una virtual se hace evidente solo cuando un objeto hereda la base clase mas de una vez, si la clase base ha sido declarada como virtual, entonces solo una instancia de esta estara presente en el objeto heredado. De otra manera, multiples copias serian encontradas.
Sobrecarga de Operadores Introduccin Funciones virtuales

113

Funciones virtuales
Editores:
Herencia Punteros

Introduccin
Una de las tres mayores facetas de la programacion orientada a objetos es el polimorfismo. Aplicado a C++, el trmino polimorfismo usado para describir el proceso en el cual diferentes implementaciones de una funcion pueden ser accedidas a traves del mismo nombre. Por esta razon, el polimorfismo es en ocasiones caracterizado por la frase "Una interface, multiples metodos." Esto significa que cada miembro de una clase general de operaciones puede ser accedida del mismo modo, incluso cuando las acciones especificas con cada operacion puedan diferir. En C++, el polimorfismo es soportado en tiempo de ejecucion y en tiempo de compilacion. La sobrecarga de operadores y funciones son ejemplos de polimorfismo en tiempo de compilacion. Aunque la sobrecarga de operadores y funciones es muy poderosa, estos no pueden realizar todas las tareas requeridad por un lenguaje realmente orientado a objetos. Ademas, C++ tambien permite polimorfismo en tiempo de ejecucion a traves del uso de clases derivadas y funciones virtuales, y estos son los principales temas de este capitulo. Este capitulo comienza con una corta discusion de punteros a tipos derivados, ya que estos dan soporte al polimorfismo en tiempo de ejecucion.

PUNTEROS A TIPOS DERIVADOS.


La fundacion del polimorfismo en tiempo de ejecucion es el puntero de la clase base. Punteros a la clase base y clases derivadas estan relacionados en la manera en que otros tipos de puntero no lo estan. Como aprendio al principio del libro, un puntero de un tipo generalmente no puede apuntar a un objeto de otro tipo. Sin embargo, los punteros de clase base y objetos derivados son la excepcion a esta regla. En C++, un puntero de la clase base podria ser usado para apuntar a un objeto de cualquier clase derivada de esa base. Por ejemplo, asumiendo que usted tiene

Funciones virtuales una clase base llamada 'clase_B' y una clase llamada 'clase_D', la cual es derivada de 'clase_B'. En C++, cualquier puntero declarado como un puntero a 'clase_B' puede tambien ser un puntero a 'clase_D'. Entonces, dado clase_B *p; // puntero al objeto del tipo clase_B clase_B objB // objeto del tipo clase_B clase_D objD // objeto del tipo clase_D las siguientes dos declaraciones son perfectamente validas: p = &objB; // p apunta a un objeto del tipo clase_B p = &objD /* p apunta a un objeto del tipo clase_D el cual es un objeto derivado de clase_B. */ En este ejemplo, 'p' puede ser usado para acceder a todos los elementos de objD heredados de objB. Sin embargo elementos especificos a objD no pueden ser referenciados con 'p'. Para un ejemplo mas concreto, considere el siguiente programa, el cual define una clase llamada clase_B y una clase derivada llamada clase_D. Este programa usa una simple jerarquia para almacenar autores y titulos. // Usando punteros base en objetos de una clase derivada #include <iostream> #include <cstring> using namespace std; class clase_B { char autor[80]; public: void put_autor(char *s) { strcpy(autor, s); } void mostrar_autor() { cout << autor << "\n"; } }; class clase_D : public clase_B { char titulo[80]; public: void put_titulo(char *num) { strcpy(titulo, num); } void mostrar_titulo() { cout << "Titulo: "; cout << titulo << "\n"; } }; int main() { clase_B *p; clase_B objB; clase_D *dp; clase_D objD; p = &objB; // direccion la clase base

114

Funciones virtuales // Accesar clase_B via puntero p->put_autor("Tom Clancy"); // Accesar clase_D via puntero base p = &objD; p->put_autor("William Shakespeare"); // Mostrar cada autor a traves de su propio objeto. objB.mostrar_autor(); objD.mostrar_autor(); cout << "\n"; /* Como put_titulo() y mostrar_titulo() no son parte de la clase base, ellos no son accesibles a traves del puntero base 'p' y deben ser accedidas directamente, o, como se muestra aqui, a traves de un puntero al tipo derivado. */ *dp = objD; dp->put_titulo("La Tempestad"); p->mostrar_autor(); // los dos, 'p' o 'dp' pueden ser usados aqui. dp->mostrar_titulo(); return 0; } Este programa muestra lo siguiente: Tom Clancy William Shakespeare William Shakespeare Titulo: La Tempestad En este ejemplo, el puntero 'p' es definido como un puntero a clase_B. Sin embargo, este puede apuntar a un objeto de la clase derivada clase_D y puede ser usado para acceder aquellos elementos de la clase derivada que son heredados de la clase base. Pero recuerde, un puntero base no puede acceder aquellos elementos especificos de la clase derivada. De ahi el porque de que mostrar_titulo() es accesada con el puntero dp, el cual es un puntero a la clase derivada. Si se quiere acceder a los elementos definidos en una clase derivada usando un puntero de clase base, se debe hacer un casting hacia el puntero del tipo derivado. Por ejemplo, esta linea de codigo llamara apropiadamente a la funcion mostrar_titulo() en objD: ((clase_D *)p)->mostrar_titulo(); Los parentesis externos son necesarios para asociar el cast con 'p y no con el tipo de retorno de mostrar_titulo(). Aunque no hay nada tecnicamente erroneo con castear un puntero de esta manera, es probablemente mejor evitarlo, ya que este simplemente agrega confusion a sus codigo. ( En realidad, la mayoria de los programadores de C++

115

Funciones virtuales consideran esto como mala forma.) Otro punto a comprender es que, mientras un puntero base puede ser usado para apuntar a cualquier objeto derivado, no es posible hacerlo de manera inversa. Esto es, no puede acceder a un objeto de tipo base usando un puntero a una clase derivada. Un puntero es incrementado y decrementado relativamente a su tipo base. Por lo tanto, cuando un puntero de la clase base esta apuntado a un objeto derivado, incrementarlo o decrementarlo no hara que apunte al siguiente objeto de la clase derivada. En vez de eso, apuntara a ( lo que piense que es ) el proximo objeto de la clase base. Por lo tanto, deberia considerar invalido incrementar o decrementar un puntero de clase base cuando esta apuntando a un objeto derivado. El hecho de que un puntero a un tipo base pueda ser usado para apuntar a cualquier objeto derivado de la base es extremadamente importante, y fundamental para C++. Como aprendera muy pronto, esta flexibilidad es crucial para la manera en que C++ implementa su polimorfismo en tiempo de ejecucion.

116

REFERENCIAS A TIPOS DERIVADOS


Similar a la accion de punteros ya descritas, una referencia a la clase base puede ser usada para referirse a un objeto de un tipo derivado. La mas comun aplicacion de esto es encontrada en los parametros de la funciones. Una parametro de referencia de la clase base puede recibir objetos de la base clase asi como tambien otro tipo derivado de esa misma base.

FUNCIONES VIRTUALES
El polimorfismo en tiempo de ejecucion es logrado por una combinacion de dos caracteristicas: 'Herencia y funciones virtuales". Aprendio sobre la herencia en el capitulo precedente. Aqui, aprendera sobre funcion virtual. Una funcin virtual es una funcion que es declarada como 'virtual' en una clase base y es redefinida en una o mas clases derivadas. Ademas, cada clase derivada puede tener su propia version de la funcion virtual. Lo que hace interesantes a las funciones virtuales es que sucede cuando una es llamada a traves de un puntero de clase base ( o referencia ). En esta situacion, C++ determina a cual version de la funcion llamar basandose en el tipo de objeto apuntado por el puntero. Y, esta determinacion es hecha en 'tiempo de ejecucion'. Ademas, cuando diferentes objetos son apuntados, diferentes versiones de la funcion virtual son ejecutadas. En otras palabras es el tipo de objeto al que esta siendo apuntado ( no el tipo del puntero ) lo que determina cual version de la funcion virtual sera ejecutada. Ademas, si la clase base contiene una funcion virtual, y si dos o mas diferentes clases son derivadas de esa clase base, entonces cuando tipos diferentes de objetos estan siendo apuntados a traves de un puntero de clase base, diferentes versiones de la funcion virtual son ejecutadas. Lo mismo ocurre cuando se usa una refrencia a la clase base. Se declara una funcion como virtual dentro de la clase base precediendo su declaracion con la palabra clave virtual. Cuando una funcion virtual es redefinida por una clase derivada, la palabra clave 'virtual' no necesita ser repetida ( aunque no es un error hacerlo ). Una clase que incluya una funcion virtual es llamada una 'clase polimorfica'. Este termino tambien aplica a una clase que hereda una clase base conteniendo una funcion virtual. Examine este corto programa, el cual demuestra el uso de funciones virtuales:
// Un ejemplo corto que usa funciones virtuales. #include <iostream> using namespace std; class base { public:

Funciones virtuales
virtual void quien() {cout << "Base" << endl;} // especificar una clase virtual }; class primera_d : public base { public: // redefinir quien() }; class segunda_d : public base { public: // redefinir quien relativa a segunda_d void quien() {cout << "Segunda derivacion" << endl;} }; int main() { base obj_base; base *p; primera_d obj_primera; segunda_d obj_segundo; p = &obj_base; p->quien(); // acceder a quien() en base relativa a primera_d void quien() {cout << "Primera derivacion" << endl;}

117

p = &obj_primera; p->quien(); // acceder a quien() en primera_d

p = &obj_segunda; p->quien(); return 0; } // acceder a quien() en segunda_d

Este programa produce la siguiente salida: Base Primera derivacion Segunda derivacion Examinemos el programa en detalle para comprender como funciona: En 'base', la funcion 'quien()' es declarada como 'virtual'. Esto significa que la funcion puede ser redefinida en una clase derivada. Dentro de ambas 'primera_d' y 'segunda_d', 'quien()' es redefinida relativa a cada clase. Dentro de main(), cuatro variables son declaradas: 'obj_base', el cual es un objeto del tipo 'base'; 'p' el cual un un puntero a objetos del tipo 'base'; 'obj_primera' y 'obj_segunda', que son objetos de dos clases derivadas. A continuacion 'p' es asignada con la direccion de 'obj_base', y la funcion quien() es llamada. Como quien() es declarada como virtual, C++ determina en tiempo de ejecucion, cual version de quien() es referenciada por el tipo del objeto apuntado por 'p'.

Funciones virtuales En este caso, 'p' apunta a un objeto del tipo 'base', asi que es la version de 'quien()' declarada en 'base' la que es ejecutada. A continuacion, se le asigna a 'p' la direccion de 'obj_primera'. Recuerde que un puntero de la clase base puede referirse a un objeto de cualquier clase derivadas. Ahora, cuando quien() es llamada, C++ nuevamente comprueba para ver que tipo de objeto es apuntado por 'p' y basado en su tipo, determina cual version de quien() llamar. Como 'p' apunta a un objeto del tipo 'obj_primera', esa version de quien() es usada. De ese mismo modo, cuando 'p' es asignada con la direccion de 'obj_segunda', la version de quien() declarada en 'segunda_d' es ejecutada. RECUERDE: "Es determinado en tiempo de ejecucion cual version de una funcion virtual en realidad es llamada. Ademas, esta determinacion es basada solamente en el tipo del objeto que esta siendo apuntado por un puntero de clase base." Una funcion virtual puede ser llamada normalmente, con la sintaxis del operador estandar de 'objeto.' Esto quiere decir que en el ejemplo precedente, no seria incorrecto sintacticamente acceder a quien() usando esta declaracion: obj_primera.quien();

118

Sin embargo, llamar a una funcion virtual de esta manera ignora sus atributos polimorficos. Es solo cuando una funcion virtual es accesada por un puntero de clase base que el polimorfismo en tiempo de ejecucion es logrado. A primera vista, la redefinicion de una funcion virtual en una clase derivada parece ser una forma especial de sobrecarga de funcion. Sin embargo, este no es el caso. De hecho, los dos procesos son fundamentalmente diferentes. Primero, una funcion sobrecargada debe diferir en su tipo y/o numero de parametros, mientras que una funcion virtual redefinida debe tener exactamente el mismo tipo y numero de parametros. De hecho, los prototipos para una funcion virtual y sus redefiniciones debe ser exactamente los mismos. Si los prototipos difieren, entonces la funcion simplemente se considera sobrecargada, y su naturaleza virtual se pierde. Otra restriccion es que una funcion virtual debe ser un miembro, no una funcion 'friend', de la clase para la cual es definida. Sin embargo, una funcion virtual puede ser una funcion 'friend' de otra clase. Tambien, es permisible para funciones destructores ser virtuales, pero esto no es asi para los constructores. "Cuando una funcion virtual es redefinida en una clase derivada, se dice que es una funcion 'overriden' ( redefinida )" Por las restricciones y diferencias entre sobrecargar funciones normales y redefinir funciones virtuales, el termino 'overriding' es usado para describir la redefinicion de una funcion virtual.

LAS FUNCIONES VIRTUALES SON HEREDADAS


Una vez que una funcion es declarada como virtual, esta se mantiene virtual sin importar cuantas capas de clases derivadas esta debe perdurar. Por ejemplo, si 'segunda_d' es derivada de 'primera_d' en vez de 'base', como se muestra en el proximo ejemplo, entonces quien() es aun virtual y la version apropiada es aun correctamente seleccionada. // Derivar de primera_d, no de base. class segunda_d : public primera_d { public: void quien() { // definir 'quien()' relativa a 'segunda_d' cout << "Segunda derivacion\n";

Funciones virtuales } }; Cuando una clase derivada no redefine una funcion virtual, entonces la funcion, como se definicio en la clase base, es usada. Por ejemplo intente esta version del programa precedente en el cual 'segunda_d' no redefine 'quien()': #include <iostream> using namespace std; class base { public: virtual void quien() { cout << "Base\n"; } }; class primera_d : public base { public: void quien() { cout << "Primera derivacion\n"; } }; class segunda_d : public base { public: // quien() no definida }; int main() { base obj_base; base *p; primera_d obj_primera; segunda_d obj_segunda; p = &obj_base; p->quien(); // acceder a quien() en 'base' p = &obj_primera; p->quien(); // acceder a quien() en 'primera_d' p = &obj_segunda; p->quien(); /* acceder a quien() en 'base' porque segunda_d no la redefine */ return 0; }

119

Funciones virtuales El programa ahora muestra la salida siguiente: Base Primera derivacion Base Como confirma la salida, como 'quien()' no ha sido redefinida por 'segunda_d', entonces 'p' apunta a 'obj_segunda', es la version de 'quien()' en 'base' la que es ejecutada. Mantenga en mente que las caracteristicas heredadas de 'virtual' son jeraquicas. Por tanto,, si el ejemplo precendente es modificado para que 'segunda_d' sea derivada de 'primera_d' en vez de 'base', entonces cuando quien() es referenciada relativa a un objeto del tipo 'segunda_d', es la version de 'quien()' declarada dentro de primera_d' la que es llamada ya que es la clase mas cercana a 'segunda_d', no 'quien()' dentro de base. El siguiente programa demuestra esta jerarquia. #include <iostream> using namespace std; class base { public: virtual void quien() { cout << "Base\n"; } }; class primera_d : public base { public: void quien() { cout << "Primera derivacion\n"; } }; // segunda_d ahora hereda primera_d -- no base. class segunda_d : public primera_d { // quien() no es definida. }; int main() { base obj_base; base *p; primera_d obj_primera; segunda_d obj_segunda; p = &obj_base; p->quien(); // acceder a 'quien()' en 'base'. p = &obj_primera; p->quien(); // acceder a 'quien()' en 'primera'.

120

Funciones virtuales

121

p = &obj_segunda; p->quien(); /* acceder a 'quien()' en 'primera_d' porque 'segunda_d' no la redefine */ return 0; } Este programa produce la siguiente salida: Base Primera derivacion Primera derivacion Como puede ver, 'segunda_d' ahora usa la version 'quien()' de 'primera_d' porque esa version es la mas cercana en la cadena de herencia.

PORQUE FUNCIONES VIRTUALES


Como se declaraba en el inicio de este captulo, las funciones virtuales en combinacin con tipos derivados le permiten a C++ soportar polimorfismo en tiempo de ejecucin. El polimorfismo es esencial para la programacin orientada a objetos por una razn: Esta permite a una clase generalizada especificar aquellas funciones que sern comunes a todas las derivadas de esa clase, mientras que permite a una clase derivada definir la implementacin especfica de algunos o todas de esas funciones. A veces esta idea es expresada como: La clase base dicta la 'interface' general que cualquier objeto derivado de esa clase tendr, pero permite a la clase derivada definir el mtodo actual usado para implementar esa interface. De ah que la frase "una interface mltiples mtodos" sea usada a menudo para describir el polimorfismo. Parte del truco de aplicar el polimorfismo de una manera satisfactoria es comprender que la clase base y derivada forman una jerarqua, la cual se mueve de mayor a menor generalizacin (base a derivada). Diseada correctamente, la clase base provee todos los elementos que una clase derivada puede usar directamente. Tambin define cuales funciones la clase derivada debe implementar por su cuenta. Esto permite a la clase derivada la flexibilidad para definir sus propios mtodos, y aun mantener un interface consistente. Eso es, como la forma de la interface es definida por la clase base, cualquier clase derivada compartir esa interface comn. Adems, el uso de funciones virtuales hace posible para la clase base definir interfaces genricas que sern usada por todas las clases derivadas. En este punto, usted debe preguntarse a si mismo porque una consistente interface con mltiples implementaciones es importante. La respuesta, nuevamente, no lleva a la fuerza central manejadora detrs de la programacin orientada a objetos: Esta ayuda al programador a manejar programas de complejidad creciente. Por ejemplo, si usted desarrolla su programa correctamente, entonces usted sabr que todos los objetos que usted derive de una clase base son accedidos en la misma manera general, incluso si las acciones especficas varan de una clase derivada a la prxima. Esto significa que usted necesita solo recordar una interface, en vez de varias. Tambin, su clase derivada es libre de usar cualquiera o toda la funcionalidad provista por la clase base. No necesita reinventa esos elementos. Por tanto, la separacin de interface e implementacin permite la creacin de libreras de clases, las cuales pueden ser provistas por un tercero. Si estas libreras son implementadas correctamente, ellas proveern una interface comn que usted puede usar para derivar clases suyas propias que cumplan sus necesidades especificas. Por ejemplo, tanto como las Microsoft Fundation Classes ( MFC ) y la librera de clases .NET Framework Windows Forms soporta programacin en Windows. Usando estas clases, su programa puede heredar mucha de la funcionalidad requerida por un programa de Windows. Usted necesita agregar solo las caractersticas nicas a su aplicacin. Este es un mayor beneficio cuando se programa en sistemas complejos.

Funciones virtuales

122

UNA SIMPLE APLICACION DE LAS FUNCIONES VIRTUALES


Para tener una idea del poder del concepto "una interface, multiples metodos", examinemos el siguiente programa. Este crea una clase base llamada 'figura'. Esta clase almacena las dimensiones de varios objetos de 2-dimensiones y calcula sus areas. La funcion 'set_dim()' es una funcion miembro estandar porque esta operacion sera comun a las clases derivadas. Sin embargo, 'mostrar_area()' es declarada como virtual porque el metodo de computar el area de cada objeto puede variar. El programa usa 'figura' para derivar dos clases especificas llamadas 'rectangulo' y 'triangulo'. #include <iostream> using namespace std; class figura { protected: double x, y; public: void set_dim(double i, double j) { x = i; y = j; } virtual void mostrar_area() { cout << "No hay calculo de area definido "; cout << " para esta clase.\n"; } }; class triangulo : public figura { public: void mostrar_area() { cout << "Triangulo con alto "; cout << x << " y base " << y; cout << " tiene un area de "; cout << x * 0.5 * y << ".\n"; } }; class rectangulo : public figura { public: void mostrar_area() { cout << "Rectangulo con dimensiones "; cout << x << " x " << y; cout << " tiene un area de "; cout << x * y << ".\n"; }; };

Funciones virtuales int main() { figura *p;

123

// crear un puntero al tipo base.

triangulo t; // crear objetos de tipos derivados rectangulo r; p = &t; p->set_dim(10.0, 5.0); p->mostrar_area(); p = &r; p->set_dim(10.0, 5.0); p->mostrar_area(); return 0; } La salida es mostrada aqui: Triangulo con alto 10 y base 5 tiene un area de 25. Rectangulo con dimensiones 10 x 5 tiene un area de 50. En el programa, notese que ambas interfaces, 'rectangulo' y 'triangulo', son la misma, incluso ambas proveen sus propios metodos para computar el area de cada uno de sus objetos. Dada la declaracion para 'figura', es posible derivar una clase llamada 'circulo' que computara el area de un circulo, dado en radio? La respuesta es 'Si'. Todo lo que necesita hacer es crear un nuevo tipo derivado que calcule el area de un circulo. El poder de las funciones virtuales esta basado en el hecho de que usted puede facilmente derivar un nuevo tipo que mantendra un interface comun con otros objetos relaciones. Por ejemplo, esta es una manera de hacerlo: class circulo : public figura { public: void mostrar_area() { cout << "Circulo con radio "; cout << x; cout << " tiene un area de "; cout << 3.14 * x * x; } }; Antes de intentar usar 'circulo', vea de cerca la definicion de 'mostrar_area()'. Note que esta usa solo el valor de x, el cual se asume que almacena el radio. ( Recuerde, el area de un circulo es calculada usando la formula 'PI*R a la 2'.) Sin embargo, la funcion 'set_dim()' como se define en 'figura', asume que seran pasados dos valores, no solo uno. Como 'circulo' no requiere este segundo valor, que tipo de accion podemos tomar? Hay dos manera de resolver este problema. La primera y peor, usted simplemente llama a 'set_dim()' usando un valor falso como segundo parametro cuando use un objeto 'circulo'. Esto tiene la desventaja de ser chapucero, en conjunto requiriendo que recuerde una excepcion especial, la cual viola la filosofia "una interface, muchos metodos".

Funciones virtuales Una mejor manera de resolver este problema es pasarle al parametro 'y' dentro de 'set_dim()' un valor por defecto. Entonces, cuando se llame a 'set_dim()' para un circulo, necesita especificar solo el radio. Cuando llame a 'set_dim()' para un triangulo o un rectangulo, especificara ambos valores. El programa expandido, el cual usa este metodo, es mostrado aqui: #include <iostream> using namespace std; class figura { protected: double x, y; public: void set_dim(double i, double j=0) { x = i; y = j; } virtual void mostrar_area() { cout << "No hay calculo de area definido "; cout << " para esta clase.\n"; } }; class triangulo : public figura { public: void mostrar_area() { cout << "Triangulo con alto "; cout << x << " y base " << y; cout << " tiene un area de "; cout << x * 0.5 * y << ".\n"; } }; class rectangulo : public figura { public: void mostrar_area() { cout << "Rectangulo con dimensiones "; cout << x << " x " << y; cout << " tiene un area de "; cout << x * y << ".\n"; }; }; class circulo : public figura { public: void mostrar_area() {

124

Funciones virtuales cout cout cout cout } }; << << << << "Circulo con radio "; x; " tiene un area de "; 3.14 * x * x;

125

int main() { figura *p;

// crear un puntero al tipo base

triangulo t; // crear objetos de tipos derivada rectangulo r; circulo c; p = &t; p->set_dim(10.0, 5.0); p->mostrar_area(); p = &r; p->set_dim(10.0, 5.0); p->mostrar_area(); p = &c; p->set_dim(9.0); p->mostrar_area(); return 0; } Este programa produce la siguiente salida: Triangulo con alto 10 y base 5 tiene un area de 25. Rectangulo con dimensiones 10 x 5 tiene un area de 50. Circulo con radio 9 tiene un area de 254.34 TIP: Mientras que las funciones virtuales son sintacticamente faciles de comprender, su verdadero poder no puede ser demostrado en ejemplos cortos. En general, el polimorfismo logra su gran fuerza en sistemas largos y complejos. Si continua usando C++, las oportunidades de aplicar funciones virtuales se presentaran por si mismas.

FUNCIONES VIRTUALES PURAS Y CLASES ABSTRACTAS


Como se ha visto, cuando una funcion virtual que no es redefinida en una clase derivada es llamada por un objeto de esa clase derivada, la version de la funcion como se ha definido en la clase base es utilizada. Sin embargo, en muchas circunstancias, no habra una declaracion con significado en una funcion virtual dentro de la clase base. Por ejemplo, en la clase base 'figura' usada en el ejemplo anterior, la definicion de 'mostrar_area()' es simplemente un sustituto sintetico. No computara ni mostrara el area de ningun tipo de objeto. Como vera cuando cree sus propias librerias de clases no es poco comun para una funcion virtual tener una definicion sin significado en el contexto de

Funciones virtuales su clase base. Cuando esta situacion ocurre, hay dos manera en que puede manejarla. Una manera, como se muestra en el ejemplo, es simplemente hacer que la funcion reporte un mensaje de advertencia. Aunque esto puede ser util en ocasiones, no es el apropiado en muchas circunstancias. Por ejemplo, puede haber funciones virtuales que simplemente deben ser definidas por la clase derivada para que la clase derivada tenga algun significado. Considere la clase 'triangulo'. Esta no tendria significado si 'mostrar_area()' no se encuentra definida. En este caso, usted desea algun metodo para asegurarse de que una clase derivada, de hecho, defina todas las funciones necesarias. En C++, la solucion a este problema es la 'funcion virtual pura.' Una 'funcion virtual pura' es una funcin declarada en una clase base que no tiene definicion relativa a la base. Como resultado, cualquier tipo derivado debe definir su propia version -- esta simplemente no puede usar la version definida en la base. Para declarar una funcion virtual pura use esta forma general: virtual 'tipo' 'nombre_de_funcion'(lista_de_parametros) = 0; Aqui, 'tipo' es el tipo de retorno de la funcion y 'nombre_de_funcion' es el nombre de la funcion. Es el = 0 que designa la funcion virtual como pura. Por ejemplo, la siguiente version de 'figura', 'mostrar_area()' es una funcion virtual pura. class figura { double x, y; public: void set_dim(double i, double j=0) { x = i; y = j; } virtual void mostrar_area() = 0; // pura }; Declarando una funcion virtual como pura, se fuerza a cualquier clase derivada a definir su propia implementacin. Si una clase falla en hacerlo, el compilador reportara un error. Por ejemplo, intente compilar esta version modificando del programa de figuras, en el cual la definicin de mostrar_area() ha sido removida de la clase 'circulo': /* Este programa no compilara porque la clase circulo no redefinio mostrar_area() */ #include <iostream> using namespace std; class figura { protected: double x, y; public: void set_dim(double i, double j=0) { x = i; y = j; }

126

Funciones virtuales

127

virtual void mostrar_area() = 0; // pura }; class triangulo : public figura { public: void mostrar_area() { cout << "Triangulo con alto "; cout << x << " y base " << y; cout << " tiene un area de "; cout << x * 0.5 * y << ".\n"; } }; class rectangulo : public figura { public: void mostrar_area() { cout << "Rectangulo con dimensiones "; cout << x << " x " << y; cout << " tiene un area de "; cout << x * y << ".\n"; }; }; class circulo : public figura { // la no definicion de mostrar_area() causara un error };

int main() { figura *p;

// crear un puntero al tipo base

triangulo t; // crear objetos de tipos derivada rectangulo r; circulo c; // ilegal -- no puedo crearla! p = &t; p->set_dim(10.0, 5.0); p->mostrar_area(); p = &r; p->set_dim(10.0, 5.0); p->mostrar_area();

Funciones virtuales

128

return 0; } Si una clase tiene al menos una funcion virtual pura, entonces esa clase se dice que es 'abstracta'. Una clase abstracta tiene una caracteristica importante: No puede haber objetos de esa clase. En vez de eso, una clase abstracta debe ser usada solo como una base que otras clases heredaran. La razon por la cual una clase abstracta no puede ser usada para declarar un objeto es, por supuesto, que una o mas de sus funciones no tienen definicion. Sin embargo, incluso si la clase base es abstracta, la puede usar aun para declarar punteros o referencias, los cuales son necesarios para soportar el polimorfismo en tiempo de ejecucion.

ENLACE TEMPRANO VS TARDIO


Existen dos terminos que son comunmente usado cuando se discute sobre programacin orientada a objetos: "Enlace temprano y Enlace Tardio" ( del ingles "early binding and late binding" ). Relativo a C++, estos terminos se refieren a eventos que ocurren en tiempo de compilacion y eventos que ocurren en tiempo de ejecucion, respectivamente. Enlace temprano significa que una llamada a una funcion es resuelta en tiempo de compilacion. Esto es, toda la informacion necesaria para llamar a una funcion es conocida cuando el programa es compilado. Ejemplos de enlace temprano incluyen llamadas a funciones estandar, llamadas a funciones sobrecargadas y llamadas a funciones de operadores sobrecargados. La principal ventaja del enlace temprano es la eficiencia -- es rapido, y a menudo requiere menos memoria. Su desventaja es falta de flexibilidad. Enlace tardio significa que una llamada a la funcion es resuelta en tiempo de ejecucin. Mas precisamente a que la llamada a la funcion es determinada "al vuelo" mientras el programa se ejecuta. Enlace tardio es logrado en C++ a traves del uso de funciones virtuales y tipos derivados. La ventaja del enlace tardio es que permite gran flexibilidad. Puede ser usada para soportar una interface comn, mientras que se permite a varios objetos utilizar esa interface para definir sus propias implementaciones. Por tanto, puede ser usada para ayudar a crear librerias de clases, las cuales pueden ser reusadas y extendidas. Su desventaja, sin embargo, es una ligera perdida de velocidad de ejecucion.

POLIMORFISMO Y EL PURISTA
A traves de este libro, y en este capitulo especificamente, se ha hecho una distincion entre polimorfismo de tiempo de ejecucion y en tiempo de compilacion. Caracteristicas polimorficas en tiempo de compilacion son sobrecarga de operadores y funciones. El polimorfismo en tiempo de ejecucion es logrado con funciones virtuales. La definicion mas comun de polimorfismo es, "una interface, multiples metodos", y todas estas caracteristicas se ajustan a este significado. Sin embargo, aun existe controversia con el uso del termino "polimorfismo". Algunos puristas POO han insistido que el termino debe usarse para referise solo a eventos que ocurren en tiempo de ejecucin. Tambin, ellos podrian decir que solo las funciones virtuales soportan el polimorfismo. Parte de este punto de vista esta fundado en el hecho de que los primeros lenguajes de computacin polimrficos fueran intrpretes (en el que todos los eventos ocurrian en tiempo de ejecucin). La llegada de lenguajes polimrficos compilador expandi el concepto de polimorfismo. Sin embargo, aun algunos aseguran que el trmino polimorfismo debera referirse solo a eventos en tiempo de ejecucin. La mayora de los programadores de C++ no estan de acuerdo con este punto de vista y establecen que el trmino aplica en ambos casos a caractersticas en tiempo de compilacin y en tiempo de ejecucin. Sin embargo, no se sorprenda si algun dia, alguien va en contra de usted sobre el uso de este termino! Si su programa usa enlace tardio o temprano depende de lo que el programa este diseado para hacer. ( En realidad, la mayoria los programas grandes usan una combinacion de los dos.) Enlace tardio es una de las caractersticas mas

Funciones virtuales poderosas de c++. Sin embargo, el precio que usted paga por este poder es que su programa se ejecutara ligeramente mas lento. Por lo tanto, es mejor usar enlace tardio solo cuando este le agregue significado a la estructura y manejabilidad de su programa. ( En esencia, use -- pero no abuse -- el poder.) Mantenga en mente, sin embargo, que la perdida de desempeo causada por enlace tardio es muy ligera, asi pues cuando la situacin requiera enlace tardio, usted definitivamente deberia usarlo.
Herencia Introduccin Punteros

129

Punteros
Editores:
Funciones virtuales Estructuras II

Punteros
Los punteros permiten simular el paso por referencia, crear y manipular estructuras dinamicas de datos, tales como listas encadenadas, pilas, colas y rboles. Generalmente las variables contienen valores especificos. Los punteros son variables pero en vez de contener un valor especifico, contienen las direcciones de las variables a las que apuntan. Para obtener o modificar el valor de la variable a la que apuntan se utiliza el operador de indireccin. Los punteros, al ser variables deben ser declaradas como punteros antes de ser utilizadas.

Sintaxis
int *ptrID, ID; ID = 8; ptrID = &ID; // puntero a ID ptrID es un puntero a int, mientras que la variable ID es solo una variable del tipo int. Todo puntero debe ser precedido por un asterisco (*). Se puede declarar ms de un puntero en la misma sentencia. En el ejemplo que sigue se ve la declaracin de dos punteros a int. int *ptrY, *ptrX;

Operadores
Existen dos operadores a tener en cuenta cuando trabajamos con punteros. El operador de direccin (&) que devuelve la direccin de memoria de su operando y el operador de indireccin (*) que devuelve un alias para el objeto al cual apunta el operando del puntero. En el siguiente ejemplo vemos como se inicializa una variable X con el valor 15. Luego se crea un puntero a int y por ltimo el puntero pasa a apuntar a la variable X. Esto es, ptrX es un puntero a X. int X = 15; int *ptrX; ptrX = &X;

Punteros

130

Punteros y matrices
Las matrices son punteros constantes. Una matriz sin subindice es un puntero al primer elemento de la matriz. int X[15]; int *ptrX; ptrX = X; // ptrX recibe la direccin del primer elemento ( 0 ) de X As como tambin podra escribirse int X[15]; int *ptrX; ptrX = &X[0]; // ptrX es igual a la direccin del primer elemento de X Se pueden utilizar distintos elementos de la matriz teniendo en cuenta la sintaxis de punteros. int X[15], Y, *ptrX; ptrX = X; Y = *( ptrX + 7 ); En este caso puede verse que Y toma el valor del elemento 7 de la matriz X, siendo 7 el desplazamiento dentro de la matriz. El operador de indireccin queda fuera del parntesis porque tiene una prioridad superior a la del operador +. De no existir los parntesis, se sumaria 7 al elemento X[0]. Teniendo en cuenta que las matrices son punteros constantes, el nombre de la matriz puede tratarse como un puntero: Y = *( X + 7 );

Aritmtica de Punteros
Al usar punteros a matrices, hay que tener en cuenta que la aritmtica cambia sensiblemente. #include <iostream> using std::cout; using std::endl; void main() { int X[6] = { 1, 2, 3, 4, 5, 6 }; int *prtX; prtX = X; // incializo el valor del puntero. cout << prtX += cout << prtX -= cout << prtX++; cout << } endl << *prtX; 2; endl << *prtX; 2; endl << *prtX; endl << *prtX;

Punteros En el siguiente cdigo, primero se crea un puntero a un arreglo de 6 elementos y si inicializa el puntero prtX al primer elemento del arreglo X[0]. Si tenemos en cuenta que el siguiente ejemplo se ejecuta en una computadora con enteros de 4 bytes, el segundo elemento de la matriz tendr en memoria un desplazamiento de 4 bytes, el 2 de ocho y asi sucesivamente. La operacin prtX += 2; produce un desplazamiento llevndolo al 3 elemento dentro del arreglo. Debe entenderse que prtX ahora apunta a una direccin de memoria y la instruccin cambia esta direccin de memoria sumndole 2 multiplicado por el tamao del tipo de dato del arreglo que en este supuesto sera de 4. (dir = ( dir + 2 * 4 )), dando por resultado un desplazamiento de 8 bytes. Sera igual que ejecutar la operacin prtX = &X[2];. La operacin prtX -= 2 obedece a la misma lgica estableciendo el puntero al primer elemento del array X[0] y el operador ++ modifica el puntero desplazndolo 4 bytes y asignndole el segundo elemento de la matriz.

131

Matrices de punteros
Para realizar una estructura de datos dinmica, se puede utilizar una matriz donde sus elementos sean punteros. Suponiendo que queramos hacer un calendario y lo dividamos por semanas. Podramos utilizar una matriz con los das de la semana. const char *dias[7] = { "Domingo", "Lunes", "Martes", "Miercoles", "Jueves", "Viernes", "Sabado" } Cada da de la semana, no es un elemento de la matriz, sino que la expresin dias[7] crea una matriz de siete elementos como punteros a char.

Formas de pasar un valor por referencia


Un puntero no constante a un dato no constante #include <iostream> void sumoeldoble( int * ); // prototipo void main () { int X = 15; sumoeldoble( &X ); // Pasa la direccin de memoria de X . std::cout << X; } void sumoeldoble( int *ptrX ) { // Toma el valor de X mediante el operador de indireccin // La funcion no devuelve nada porque modifica el valor por referencia. *ptrX = *ptrX + ( *ptrX * 2 ); } Un puntero no constante a un dato constante #include <iostream> void imprimeChars( const char * ); // prototipo

Punteros void main () { char cFrase[] = "Hola Mundo"; imprimeChars( cFrase ); } void imprimeChars( const char *ptrStr ) { for ( ; *ptrStr != '\0'; ptrStr++ ) //Sin inicializacin std::cout << *ptrStr; } Un puntero constante a un dato no constante Un puntero es constante cuando apunta siempre a la misma direccin de memoria y si el dato no es constante entonces el valor puede ser modificado. void main () { int foo, bar; int * const ptrFoo = &foo; // Apuntador constante a un entero en la direccin de memoria de foo *ptrFoo = 53;

132

// Esto devuelve un error porque es un puntero constante. // No se puede alterar la direccin de memoria a la que apunta. ptrFoo = &bar; } Un puntero constante a un dato constante El puntero constante apunta siempre a la misma direccin de memoria y el valor al que apunta dicho puntero no puede ser modificado mediante el puntero. Este es el metodo en que se debe pasar matrices a funciones que solo leen la matriz y no la modifican. #include <iostream> using namespace std; int main () { int foo = 3, bar; const int * const ptrFoo = &foo; cout << foo; *ptrFoo = 53; //Error porque no puede alterarse el valor ptrFoo = &bar; // Error

Punteros foo = 23; cout << foo; }

133

Punteros a funciones
Teniendo en cuenta que el nombre de una funcin es en verdad la direccin de memoria donde comienza el cdigo, los punteros a funciones contienen la direccin de memoria de la funcin a la que apunta, y se pueden pasar y retornar entre funciones. #include <iostream> using namespace std; bool functionA( int, int, bool (*)( int ) ); //Prototipo bool functionB( int ); //Prototipo void main() { int x = 113, y = 226; if ( functionA( x, y, functionB ) ) cout << "\nEl resultado es verdadero"; else cout << "\nEl resultado es falso"; } bool functionA( int param1, int param2, bool (*verificar)( int ) ) { if ( ( (*verificar)( param1 ) ) && ( (*verificar)( param2 ) ) ) return true; } bool functionB( int param ) { if ( param > 100 ) return true; else return false; } En el ejemplo anterior podr ver que en la definicin de los prototipos, la funcin functionA recibe tres parmetros, siendo el tercer parmetro un puntero a la funcion functionB, practicamente copiando el prototipo sin el nombre. Cuando se ejecuta functionA, se le pasa como parametro el nombre de la funcin

Punteros

134

Ordenamiento burbuja
Ordenamiento burbuja utilizando punteros #include <iostream> #include <iomanip> using namespace std; void orden( int *, const int ); // prototipo void swap( int * const, int * const ); // prototipo int main() { const int nSize = 10; int a[ nSize ] = { 3, 9, 14, 27, 18, 154, 8, 6, 74, 33 }; cout << "\nElementos a ordenar\n"; for ( int j = 0; j < nSize; j++ ) cout << setw( 5 ) << a[ j ]; orden( a, nSize ); // ordena el arreglo

cout << "\nElementos ordenados\n"; for ( int j = 0; j < nSize; j++ ) cout << setw( 5 ) << a[ j ]; cout << endl; return 0; } void orden( int *matriz, const int nSize ) { for ( int pasada = 0; pasada < nSize - 1; pasada++ ) { for ( int k = 0; k < nSize - 1; k++ ) { if ( matriz[ k ] > matriz[ k + 1 ] ) { swap( &matriz[ k ], &matriz[ k + 1 ] ); } } } } // indica terminacin exitosa

Punteros void swap( int * const ptrElemento1, int * const ptrElemento2 ) { int mantiene = *ptrElemento1; *ptrElemento1 = *ptrElemento2; *ptrElemento2 = mantiene; }

135

Proyecto de colas para un banco


Por cada 2 clientes normales pasa uno VIP al Cajero El codigo es muy simple pero muy trabajado y depurado, esta relizado con if/while.... lo mas complicado son los apuntadores, pero nada del otro mundo, muestra el perfecto uso de funciones, tiene una clave: jahvi, de todas lo puedes leer en el codigo. Compila perfecto en DEV C++. Tiene una interfaces muy creativa, ideal estudian tes que se estan inciando en el desarrollo de software con programacion estructurada. recuerda copiarlo en un pagina en blanco en dev c++ desde aqui: (copialo completo y compilalo)
#include <stdio.h> #include <iostream.h> #include <conio.h> #include <stdlib.h> #include <string.h>

using namespace std;

typedef struct nodo { int cedula; struct nodo*siguiente; }tiponodo;

typedef tiponodo *pnodo; typedef tiponodo *cola;

cola cola_n=NULL; cola cola_vip=NULL; int solic;

void insertar (cola *cola_n) { cola aux, creado;

creado=(pnodo)malloc(sizeof(tiponodo));

cout<<" cout<<" cout<<" cout<<" cout<<"

|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"<<endl; ||||||||||||||||||||METROPOLI banco universal||||||||||||||||||||"<<endl; |||||||||||||||||||||||Le da la bienvenida|||||||||||||||||||||||"<<endl; |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"<<endl; Estimado cliente: Introduzca su numero de cedula"<<endl;

Punteros
cin>> cout<<" creado->cedula; Pronto sera atendido"<<endl;

136

if ((*cola_n)==NULL) { creado->siguiente=*cola_n; *cola_n=creado; } else { aux=*cola_n;

while(aux!=NULL) { if(aux->siguiente==NULL) { creado->siguiente=aux->siguiente; aux->siguiente=creado; aux=aux->siguiente; } aux=aux->siguiente; } } }

void mostrar_cola (cola cola_n) { cola aux; aux=cola_n; if(aux==NULL) { cout<<"No hay clientes en cola"<<endl; } else { while(aux!=NULL) { cout<<"Cedula: "<<aux->cedula<<endl; cout<<""<<endl; aux=aux->siguiente; cout<<"* Clientes ordenados por orden de llegada((((((SIMCOLAS))))))"<<endl; } } } //modificado hasta ac... falta de orden por parte del creador void insertar_vip (cola *cola_vip){

Punteros
cola aux_2, creado_vip; creado_vip=(pnodo)malloc(sizeof(tiponodo)); cout<<" cout<<" cout<<" cout<<" cout<<" cin>> cout<<" |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"<<endl;//62 ||||||||||||||||||||METROPOLI banco universal||||||||||||||||||||"<<endl; |||||||||||||||||||||||Le da la bienvenida|||||||||||||||||||||||"<<endl; |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"<<endl; Estimado cliente: Introduzca su numero de cedula"<<endl; creado_vip->cedula; Pronto sera atendido"<<endl;

137

if ((*cola_vip)==NULL){ creado_vip->siguiente=*cola_vip; *cola_vip=creado_vip; }else{ aux_2=*cola_vip; while(aux_2!=NULL){ if(aux_2->siguiente==NULL){ creado_vip->siguiente=aux_2->siguiente; aux_2->siguiente=creado_vip; aux_2=aux_2->siguiente; } aux_2=aux_2->siguiente; } }

void mostrar_cola_vip (cola cola_vip){ cola aux_2; aux_2=cola_vip; if(aux_2==NULL){ cout<<"No hay clientes V.I.P en cola"<<endl; }else{ while(aux_2!=NULL){ cout<<"Cedula: "<<aux_2->cedula<<endl; cout<<""<<endl; cout<<"* Clientes ordenados por orden de llegada((((((SIMCOLAS))))))"<<endl; aux_2=aux_2->siguiente; } } }

void pop (cola *cola_n){

if((*cola_n)==NULL){ cout<<"No hay clientes en cola ((((((SIMCOLAS))))))"<<endl; cout<<""<<endl;

Punteros
}

138

if((*cola_n)!=NULL){ cout<<"Cliente: "<<(*cola_n)->cedula; cout<<" es su turno"<<endl; *cola_n=(*cola_n)->siguiente; free(cola_n); system("PAUSE"); cout<<""<<endl; }

if((*cola_n)==NULL){ cout<<"No hay clientes en cola ((((((SIMCOLAS))))))"<<endl; cout<<""<<endl; }else{

cout<<"Cliente: "<<(*cola_n)->cedula; cout<<" es su turno"<<endl; *cola_n=(*cola_n)->siguiente; free(cola_n); system("PAUSE"); cout<<""<<endl; } }

void pop_vip(cola *cola_vip){

if(cola_vip!=NULL){ cout<<"Cliente: "<<(*cola_vip)->cedula; cout<<" es su turno"<<endl;

*cola_vip=(*cola_vip)->siguiente; free(cola_vip); }else{ cout<<"No hay usuarios V.I.P en cola ((((((SIMCOLAS))))))"<<endl; } }

void menu();

void Insertar(); void insertar_vip(); void mostrar_cola(); void mostrar_colavip(); void solicitar_numero();

Punteros
void salir();

139

struct perly{ char uxer[]; }data;

struct perly *p = &data;

int main(int argc, char *argv[]) {

system("color 0a");

cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" cout<<" ___| __ | * * * * | -+| _|_ _| |_ | _|[] []|_

* * * * * * * ___ | | | | |_| |_| |_| |_| * ____ |....| |....| |....| |....| * ____ |....| | ___|__ || -- - | || - -- | * * * * *

"<<endl; "<<endl; * "<<endl; "<<endl; "<<endl; "<<endl; * "<<endl; "<<endl; * "<<endl; "<<endl; "<<endl; "<<endl; "<<endl; "<<endl; "<<endl; "<<endl; "<<endl; "<<endl;

[_]||__ | [_]|

|__|_

______| |__ | |__[]| |__

|________ [_]| _____ | | [___] | [_]|

|# #| | |

_____

|....|__|| -- - | |* * *| |....|__|| -- -_|_|* * *| || ___|* *|* * *| | |* *|* *|* * *| | |* *|* *|* * *| |* *|* *|* * *| |* *|* *|* * *| ______________

|# #| | | |__

|__| |__|....| #|_______|....| |__|_______|__ |

| _____ | [_]| [___] | | |

__|_____________|__ _|___________________|_ |_______________________|

[_]| _____ | | [___] |

__|_______|_________|_______________________| _________________ "<<endl; |_______________________________________________________________|"<<endl; |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"<<endl;//62 ||||||||||||||||||||METROPOLI banco universal||||||||||||||||||||"<<endl; ||||||||||||Sistema Integral para el Manejo de COLAS|||||||||||||"<<endl; |||||||||||||||||||||||||||||SIMCOLAS||||||||||||||||||||||||||||"<<endl; |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"<<endl; |||||||||Creado por: Javier Rodriguez Caracas-Venezuela||||||||||"<<endl; |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"<<endl; Login: ";

char usuario[6] = "jahvi"; cin.getline(p->uxer,6);

if (strcmp(p->uxer,usuario) == 0) {

Punteros
cout<<"Bienvenid@ al sistema "<<endl; menu(); cout<<""<<endl; }else{ cout<<"Acceso Denegado. Consulte al Admisnistrador "<<endl; }

140

system("PAUSE"); return EXIT_SUCCESS; } void menu() { short a; do {

cout<<""<<endl; cout<< "0 - Agregar Cliente"<<endl; cout<< "1 - Agregar Cliente V.I.P"<<endl; cout<< "2 - Solicitar Numero"<<endl; cout<< "3 - Mostrar Cola"<<endl; cout<< "4 - Mostrar Cola V.I.P"<<endl; cout<< "5 - Salir del Sistema"<<endl; cout<<""<<endl;

fflush(stdout);

cout<<"Opcion #:"; cin>>a; cout<<""<<endl;

if (a == 5) exit(1);

switch(a){ case 0 : Insertar(); break;

case 1 : insertar_vip(); break;

case 2 : solicitar_numero(); break;

case 3 : mostrar_cola();

Punteros
break;

141

case 4 : mostrar_colavip();

break;

case 5 : salir(); break;

default : puts("Usted no ha seleccionado nada, porfavor seleccione algo\n"); break; }; } while (1); }

void Insertar(){ insertar(&cola_n); } void insertar_vip(){ insertar(&cola_vip); }

void mostrar_cola(){ mostrar_cola(cola_n);

void mostrar_colavip(){ mostrar_cola_vip(cola_vip); }

void solicitar_numero(){ pop(&cola_n);

if(cola_vip!=NULL){ pop_vip(&cola_vip); }else{ cout<<"No hay cliente V.I.P en cola ((((((SIMCOLAS))))))"<<endl; }

Punteros
void salir(){ }

142

Funciones virtuales Arriba Estructuras II

Estructuras II
Editores: Oscar E. Palacios [1]
Punteros Plantillas

Introduccin
Muchos autores comienzan por definir los conceptos de estructura de datos a raiz de estructuras conocidas como listas. En el mismo contexto, suele suceder que a dichas listas tambin se les conoce como secuencias y/o colecciones de datos. Hay que decir que dichos autores estn (en parte) en lo correcto, ya que una lista (de cualquier tipo) es una estructura ideada con el propsito de albergar datos agrupados bajo un mismo nombre. Al respecto, podemos pensar que las listas son como arreglos de datos, es decir, para hacer una introduccin al manejo y programacin de listas encadenadas podemos tomar como punto de partida a los arreglos estticos. Es as como en esta secccin se descubrir la forma de operacin de tres tipos comnes de listas conocidas como: PILAS, COLAS Y DOBLE COLA (STACK, QUEUE, DQUEUE). En programacin, el uso de listas es una prctica tan extendida que lenguajes tales como (por ejemplo) Java, Python y C++ soportan los mecanismos necesarios para trabajar con estructuras de: Vectores, Pilas, Colas, Listas, etc. En C++, los programadores que usen Dev-Cpp ( Bloodshed.software -Dev-C++ [2] ) pueden aprovechar las ventajas que ofrecen las STL (Standard Templates Libraries) dentro de la cual se pueden encontrar plantillas para la manipulacin de listas tales como: Vectores, Listas, Sets, Maps, etc. Por otro lado, los usuarios de Borland C++ ( Turbo C++ version 1.01 [3] ) pueden hacer uso de la CLASSLIB, misma que posee las librerias para los propsitos mencionados. Nota: En las siguientes secciones se presentarn seis programas, tres para simular listas basadas en arreglos estticos y tres para simular listas por medio de enlaces dinmicos (punteros). En cuanto al material incluido se debe hacer las siguientes declaraciones: 1. Puesto que el material es puramente didctico, cada programa se escribe en un mismo archivo. La idea es no perder de vista el objetivo. Los entendidos sabrn que normalmente se deben escribir archivos de cabecera, archivos de implementacion y archivos de prueba por separado. 2. Para cada una de las clases creadas en los programas se han elegido nombres en ingles. La idea es que gran parte de la documentacin e implementacin referente a listas est en dicho idioma, as, se le da al estudiante la idea bsica de como operar con las libreras soportadas por los compiladores Dev-Cpp, Borlan C++, y otros. 3. Igual, se debe observar que los mtodos de las clases tienen nombres en ingles, y que con el objetivo de establecer cierta estandarizacin todas las clases poseen los mismos mtodos, aunque cada una de ellas implementa los mismos a su manera.

Estructuras II

143

Pilas o Stacks
Una PILA es una estructuras en donde cada elemento es insertado y retirado del tope de la misma, y debido a esto el comportamiento de un una pila se conoce como LIFO (ltimo en entrar, primero en salir ). Un ejemplo de pila o stack se puede observar en el mismo procesador, es decir, cada vez que en los programas aparece una llamada a una funcin el microprocesador guarda el estado de ciertos registros en un segmento de memoria conocido como Stack Segment, mismos que sern recuperados al regreso de la funcin.

Pila en arreglo esttico


En el programa que se ver en seguida, se simula el comportamiento de una estructura de pila. Aunque en el mismo se usa un arreglo esttico de tamao fijo se debe mencionar que normalmente las implementaciones hechas por fabricantes y/o terceras personas se basan en listas dinmicas o enlazadas. Para la implementacin de la clase Stack se han elegido los mtodos: put(), get(), empty(), size(), poner un elemento en la pila retirar un elemento de la pila regresa 1 (TRUE) si la pila esta vacia nmero de elementos en la pila

El atributo SP de la clase Stack es el puntero de lectura/escritura, es decir, el SP indica la posicin dentro de la pila en donde la funcin put() insertar el siguiente dato, y la posicin dentro de la pila de donde la funcin get() leer el siguiente dato.

Cada vez que put() inserta un elemento el SP se decrementa. Cada vez que get() retira un elemento el SP se incrementa. En el siguente ejemplo se analiza lo que sucede con el SP (puntero de pila) cuando se guardan en la pila uno por uno los caracteres 'A', 'B', 'C' y 'D'. Observe que al principio el SP es igual al tamao de la pila. Llenando la pila. SP | +---+---+---+---+---+ | | | | | | +---+---+---+---+---+ SP | +---+---+---+---+---+ | | | | | A | +---+---+---+---+---+ ... SP | +---+---+---+---+---+ | | D | C | B | A | +---+---+---+---+---+

al principio (lista vacia)

push('A'); despus de haber agregado el primer elemento

despus de haber agregado cuatro elementos

Estructuras II Vaciando la pila. SP | +---+---+---+---+---+ | | D | C | B | A | +---+---+---+---+---+ ... SP | +---+---+---+---+---+ | | D | C | B | A | +---+---+---+---+---+

144

pop(); despus de haber retirado un elemento

despus de haber retirado todos los elementos

Nota: observe que al final la lista est vacia, y que dicho estado se debe a que el puntero de la pila. est al final de la pila y no al hecho de borrar fsicamente cada elemento

Ejemplo: Pila basada en un arreglo esttico #include <iostream> using namespace std; #define STACK_SIZE 256 /* capacidad mxima */ typedef char arreglo[STACK_SIZE]; class Stack { int int int arreglo sp; /* puntero de lectura/escritura */ items; /* nmero de elementos en lista */ itemsize; /* tamao del elemento */ pila; /* el arreglo */

public: // constructor Stack() { sp = STACK_SIZE-1; items = 0; itemsize = 1; } // destructor ~Stack() {}; /* regresa el nmero de elementos en lista */ int size() { return items; } /* regresa 1 si no hay elementos en la lista, o sea, si la lista

Estructuras II est vacia */ int empty() { return items == 0; } /* insertar elemento a la lista */ int put(char d) { if ( sp >= 0) { pila[sp] = d; sp --; items ++; } return d; } /* retirar elemento de la lista */ int get() { if ( ! empty() ) { sp ++; items --; } return pila[sp]; } }; // fin de clase Stack

145

// probando la pila. // Nota: obseve cmo los elementos se ingresan en orden desde la A hasta la Z, // y como los mismos se recupern en orden inverso. int main() { int d; Stack s; // s es un objeto (instancia) de la clase Stack // llenando la pila for (d='A'; d<='Z'; d++) s.put(d); cout << "Items =" << s.size() << endl; // vaciando la pila while ( s.size() ) cout << (char)s.get() << " "; cout << "\nPara terminar oprima <Enter>..."; cin.get(); return 0;

Estructuras II } }

146

Pila dinmica
En el siguiente programa se presenta una implementacin de una estructura dinmica tipo pila o stack. Es importante hacer notar que, a diferencia de una pila basada en un arreglo esttico, una pila enlazadada dinmicamente no posee de forma natural el mecanismo de acceso por ndices, en ese sentido, el programador puede crear los algoritmos necesarios para permitir tal comportamiento. En la clase que presentaremos en el ejemplo no se ha implementado el mecanismo de acceso por ndices, ya que la misma se presenta como una alternativa para la simulacin de una pila o stack. Uno de los puntos ms destacables en cuando al uso de listas enlazadas dinmicamente es el hecho de crear estructuras conocidas como nodos. Un nodo es una especie de eslavon ( similar al de una cadena de bicicleta ), es decir, cada nodo se enlaza con otro a travs de un puntero que apunta a una estructura del mismo tipo que el nodo. Por ejemplo, para crear una estructura de nodo para almacenar enteros y a la vez para apuntar a otro posible nodo podemos emplear la sintaxis: struct nodo { int data; nodo *siguiente; }; observe que con la declaracin anterior estamos creando el tipo estructurado nodo, mismo que posee a los miembros: data para guardar valores enteros, y siguiente para apuntar o enlazar a un supuesto siguiente nodo. Ya que las listas dinmicas inicialmente se encuentran vacias, y ms an, una lista dinmica no posee una direccin establecida en tiempo de compilacin ya que las direccin de memoria que ocupar cada uno de los elementos se establecer en tiempo de ejecucin, entonces cmo determinar la condicin de vacio ?. En nuestro ejemplo usaremos un contador ( ITEMS ) que dicho sea de paso, si ITEMS = 0, entonces la lista est vacia. ( la condicin de vacio tambin podra determinarse al verificar el SP, es decir, si el SP = NULL, significa que la lista no posee elementos ). Al hacer un anlisis previo de los eventos que acontecern en la pila y su puntero de lectura y escritura (SP, que en esta ocasin es una estructura tipo nodo), se tiene lo siguiente: 1) Al principio la lista est vacia, en ese caso el SP es igual a NULL y, en consecuencia, el puntero next tambin es NULL. SP = NULL +------+------+ | ???? | next |--> NULL +------+------+ 2) Despus de agregar el primer elemento la situacin se vera as: SP = asignado 1 +------+------+ | data | next |--> NULL +------+------+ 3) Despus de agregar otro elemento la situacin se vera as:

Estructuras II SP = asignado 2 1 +------+------+ +------+------+ | data | next |--> | data | next |--> NULL +------+------+ +------+------+ Ejemplo: Pila basada en un arreglo dinmico /*---------------------------------------------------------------+ + ejemplo de una pila ( STACK ) enlazada dinmicamente + + + + Autor: Oscar E. Palacios + + email: oscarpalacios1@yahoo.com.mx + + + + Manifiesto: + + Este programa puede distribuirse, copiarse y modificarse de + + forma libre. + +---------------------------------------------------------------*/ #include <iostream> //#include <conio.h> using namespace std; /* tipo de dato que contendr la lista */ typedef char DATA_TYPE; // declaracin de estructura nodo struct nodo { DATA_TYPE data; nodo *next; }; class StackDin { // atributos int ITEMS; /* nmero de elementos en la lista */ int ITEMSIZE; /* tamao de cada elemento */ nodo *SP; /* puntero de lectura/escritura */ public: // constructor StackDin() : SP(NULL), ITEMS(0), ITEMSIZE(sizeof(DATA_TYPE)) {} // destructor ~StackDin() {} /* agregar componente a la lista */ DATA_TYPE put(DATA_TYPE valor)

147

Estructuras II { nodo *temp; temp = new nodo; if (temp == NULL) return -1; temp->data = valor; temp->next = SP; SP = temp; ITEMS ++; return valor; } int empty() { return ITEMS == 0; }

148

/* retirar elemento de la lista */ DATA_TYPE get() { nodo *temp; DATA_TYPE d; if ( empty() ) return -1; d = SP->data; temp = SP->next; if (SP) delete SP; SP = temp; ITEMS --; return d; } }; // fin de la clase StackDin

/* punto de prueba para la clase StackDin */ int main() { //clrscr(); StackDin s; DATA_TYPE d; for (d='A'; d<='Z'; d++) s.put(d); while ( ! s.empty() ) cout << (DATA_TYPE)s.get() << " ";

Estructuras II

149

cout << "\nPara terminar presione <Enter>..."; cin.get(); return 0; }

Colas o Queues
Una cola sencilla es una estructura en donde cada elemento es insertado inmediatamente despus del ltimo elemento insertado; y donde los elementos se retiran siempre por el frente de la misma, debido a esto el comportamiento de un una cola se conoce como FIFO (primero en entrar, primero en salir). Un ejemplo a citar de cola es el comportamiento del buffer del teclado. Cuando en el teclado se oprime una tecla, el cdigo del caracter ingresado es trasladado y depositado en una ara de memoria intermedia conocida como "el buffer del teclado", para esto el microprocedador llama a una rutina especfica. Luego, para leer el caracter depositado en el buffer existe otra funcin, es decir, hay una rutina para excribir y otra para leer los caracteres del buffer cada una de las cuales posee un puntero; uno para saber en donde dentro del buffer se escribir el siguiente cdigo y otro para saber de donde dentro del buffer se leer el siguiente cdigo.

Cola en un arreglo esttico


En el programa que se ve en seguida, se simula el comportamiento de una estructura de cola simple. Aunque en el mismo se usa un arreglo esttico de tamaoo fijo se debe mencionar que normalmente las implementaciones hechas por fabricantes y/o terceras personas se basan en listas dinmicas o dinamicamente enlazadas. Para la implementacin de la clase Queue se han elegido los mtodos: put(), get(), empty(), size(), poner un elemento en la cola retirar un elemento de la cola regresa 1 (TRUE) si la cola est nmero de elementos en la cola

vacia

El atributo cabeza de la clase Queue es el puntero de lectura. El atributo cola de la clase Queue es el puntero de escritura. Es decir, la cola indica la posicin dentro de la lista en donde la funcin put() insertar el siguiente dato, y la cabeza indica la posicin dentro de la lista de donde la funcin get() leer el siguiente dato. Cada vez que put() inserta un elemento la cola se incrementa. Cada vez que get() retira un elemento la cabeza se incrementa. En el siguente ejemplo se analiza lo que sucede con la cola y la cabeza (punteros de escritura y de lectura de la Lista) cuando se guardan en la cola uno por uno los caracteres 'A', 'B', 'C' y 'D'. Observe que al principio: cola = cabeza = cero. Llenando la cola. cola | +---+---+---+---+---+ | | | | | |

al principio

Estructuras II +---+---+---+---+---+ | cabeza cola | +---+---+---+---+---+ | A | | | | | +---+---+---+---+---+ | cabeza ... cola | +---+---+---+---+---+ | A | B | C | D | | +---+---+---+---+---+ | cabeza Vaciando la cola. cabeza | +---+---+---+---+---+ | A | B | C | D | | +---+---+---+---+---+ cabeza | +---+---+---+---+---+ | A | B | C | D | | +---+---+---+---+---+ ... cabeza | +---+---+---+---+---+ | A | B | C | D | | +---+---+---+---+---+ | cola

150

put('A'); despus de haber agregado el primer elemento

despus de haber agregado cuatro elementos

antes de haber retirado elementos

get(); despus de haber retirado un elemento

al final despus de haber retirado todos los elementos

Observese que al final el cabeza apunta hacia el mismo elemento que la cola, es decir, la cola vuelve a estar vacia. Puesto que la cola que estamos proyectando reside en un arreglo esttico los componentes del arreglo an estn dentro de la misma, salvo que para su recuperacin se debera escribir otro mtodo. En una cola dinmica (como se demostrar ms adelante) los elementos retirados de la misma se eliminan de la memoria y podra no ser posible su recuperacin posterior.

Estructuras II Nota: En el programa que aparece en seguida, al tipo de lista implementado por la clase Queue se le conoce como "lista circular" debido al comportamiento de sus punteros. Es decir si los mtodos para escribir o leer detectan que el puntero correspondiente ha sobrepasado el tamao mximo de elementos permitidos dentro de la cola, ste es puesto a cero. Ejemplo: cola en un arreglo esttico /*---------------------------------------------------------------+ + ejemplo de una cola (QUEUE) basada en un arreglo esttico + + + + Autor: Oscar E. Palacios + + email: oscarpalacios1@yahoo.com.mx + + + + Manifiesto: + + Este programa puede distribuirse, copiarse y modificarse de + + forma libre. + +---------------------------------------------------------------*/ #include <iostream.h> #define MAX_SIZE 256 /* capacidad mxima */ typedef char almacen[MAX_SIZE]; class Queue { int cabeza; int cola; int ITEMS; int ITEMSIZE; almacen alma; /* /* /* /* /* puntero de lectura */ puntero de escritura */ nmero de elementos en la lista */ tamao de cada elemento */ el almacen */

151

public: // constructor Queue() { cabeza = 0; cola = 0; ITEMS = 0; ITEMSIZE = 1; } // destructor ~Queue() {} // regresa 1 (true) si la lista est vacia int empty() { return ITEMS == 0; } // insertar elemento a la lista int put(int d) { if ( ITEMS == MAX_SIZE) return -1;

Estructuras II if ( cola >= MAX_SIZE) { cola = 0; } alma[cola] = d; cola ++; ITEMS ++; return d; } // retirar elemento de la lista int get() { char d; if ( empty() ) return -1; if ( cabeza >= MAX_SIZE ) { cabeza = 0; } d = alma[cabeza]; cabeza ++; ITEMS --; return d; } // regresa el nmero de elementos en lista int size() { return ITEMS; } }; // fin de la clase Queue

152

// probando la cola int main() { int d; Queue q; for (d='A'; d<='Z'; d++) q.put(d); cout << "Items = " << q.size() << endl; while ( q.size() ) { cout << (char)q.get() << " "; } cout << "\nPara terminar oprima <Enter> ..."; cin .get(); return 0; } Ejemplo: cola en un arreglo dinmico /*---------------------------------------------------------------+ + ejemplo de una cola (QUEUE) basada en un arreglo dinmico +

Estructuras II + + + Autor: Oscar E. Palacios + + email: oscarpalacios1@yahoo.com.mx + + + + Manifiesto: + + Este programa puede distribuirse, copiarse y modificarse de + + forma libre. + +---------------------------------------------------------------*/ #include <iostream> using namespace std; typedef char DATA_TYPE; struct nodo { DATA_TYPE data; nodo *next; }; class QueueDin { // atributos int ITEMS, ITEMSIZE; nodo *cola, *cabeza; public: // constructor QueueDin() : cola(NULL), cabeza(NULL), ITEMS(0), ITEMSIZE(sizeof(DATA_TYPE)) {} // destructor ~QueueDin() {} /* agregar componente a la lista */ DATA_TYPE put(DATA_TYPE valor) { nodo *temp; temp = new nodo; if (temp == NULL) return -1; ITEMS ++; temp->data = valor; temp->next = NULL; if (cabeza == NULL) {

153

Estructuras II cabeza = temp; cola = temp; else cola->next = temp; cola = temp; } return valor; } // regresa 1 (true) si la lista est vacia int empty() { return ITEMS == 0; }

154

} {

/* retirar elemento de la lista */ DATA_TYPE get() { nodo *temp; DATA_TYPE d; if ( empty() ) return -1; d = cabeza->data; temp = cabeza->next; if (cabeza) delete cabeza; cabeza = temp; ITEMS --; return d; } }; // fin de la clase QueueDin /* punto de prueba */ int main() { QueueDin s; DATA_TYPE d; // llenando la cola for (d='A'; d<='Z'; d++) { s.put(d); cout << d << " "; } cout << endl; // vaciando la cola while ( ! s.empty() )

Estructuras II cout << (DATA_TYPE)s.get() << " "; cout << "\nPara terminar presione <Enter>..."; cin.get(); return 0; }

155

Colas de doble enlace


Una cola doble es una estructuras en donde cada elemento puede ser insertado y recuperado por la parte del frente (cabeza) o por la parte de atras (cola) de la lista. A diferencia de una cola sencilla, en donde solo se necesitan un mtodo para leer y otro para escribir componentes en la lista, en una doble cola debe haber dos mtodos para leer ( uno para leer por el frente y uno para leer por atras ) y dos mtodos para escribir ( uno para escribir por el frente y uno para escribir por atras ).

En el programa que se ver en seguida, se simula el comportamiento de una estructura de cola doble en base a un arreglo esttico. En dicho programa se declara e implementa la clase SDQueue con los siguientes mtodos: put_front(), put_back(), get_front(), get_back(), empty(), size(), poner un elemento en el frente de la cola poner un elemento en la parte tracera de la cola retirar un elemento de la parte frontal de la cola retirar un elemento de la parte tracera de la cola regresa 1 (TRUE) si la cola est vacia nmero de elementos en la cola

Nota: observe que para los mtodos put_front() y get_front() se hace uso de la funcin memmove(), esto es necesario debido al hecho de que put_front() tiene que mover una posicin hacia atras todos los elementos en la lista antes de insertar el componente indicado; por otro lado, la funcin get_front() tiene que mover una posicin hacia adelante a todos los elementos que le siguen al primer elemento. Ejemplo: doble cola en un arreglo esttico /*------------------------------------------------------------------+ + ejemplo de una cola doble (DQUEUE) basada en un arreglo est tico + + + + Autor: Oscar E. Palacios + + email: oscarpalacios1@yahoo.com.mx + + + + Manifiesto: + + Este programa puede distribuirse, copiarse y modificarse de + + forma libre. + +------------------------------------------------------------------*/ #include <iostream.h> #include <mem.h> // por memmove // using namespace std; #define MAX_SIZE 256 #define t_error -1; typedef int DATA_TYPE; // mximo nmero de elementos

Estructuras II typedef int almacen[MAX_SIZE]; class SDQueue { // atributos int itemsize; // tamao de cada elemento int items; // nmero de elementos int cola, cabeza; // punteros de lectura y escritura almacen alma; // el almacen o arreglo public: // constructor SDQueue() : cola(0), cabeza(0), items(0), itemsize(sizeof(DATA_TYPE)) {} // destructor ~SDQueue() {} int empty() { return items == 0; } int size() { return items; } /* agregar componente en la parte tracera de la lista */ DATA_TYPE put_back(DATA_TYPE valor) { if (items == MAX_SIZE) return t_error; alma[cola] = valor; items ++; cola ++; return valor; } /* agregar componente en la parte delantera de la lista */ DATA_TYPE put_front(DATA_TYPE valor) { if (items == MAX_SIZE) return t_error; memmove((void *)&alma[cabeza+1], (void*)&alma[cabeza], items*itemsize); alma[cabeza] = valor; items ++; cola ++; return valor; }

156

/* retirar elemento de la parte frontal de la lista */ DATA_TYPE get_front() {

Estructuras II DATA_TYPE d; if ( empty() ) return t_error; items --; cola --; d = alma[cabeza]; memmove((void*)&alma[cabeza], (void*)&alma[cabeza+1], items*itemsize); return d; } /* retirar elemento de la parte tracera de la lista */ DATA_TYPE get_back() { DATA_TYPE d; if ( empty() ) return t_error; items--; cola --; d = alma[cola]; return d; } }; // fin de la clase SDQueue /* punto de prueba */ int main() { SDQueue s; DATA_TYPE d; for (d='A'; d<='Z'; d++) s.put_back(d); while ( ! s.empty() ) cout << (char)s.get_front() << " "; cout << "\nPara terminar presione <Enter>..."; cin.get(); return 0; } Una cola doblemente encadenada es una estructuras en donde cada elemento puede ser insertado y recuperado por la parte del frente (cabeza) o por la parte de atras (cola) de la lista. A diferencia de una cola sencilla, en donde solo se necesita un puntero a un siguiente elemento, la estructura del nodo para una doble cola debe poseer un puntero a un posible siguiente elemento y un puntero a otro posible anterior elemento. Por ejemplo, para crear una estructura de nodo con doble enlace para coleccionar nmeros enteros podemos usar la sintaxis:

157

Estructuras II struct nodo { int data; nodo *next, *prev; }; Grficamente podemos imaginar la estructura anterior como: +------+------+------+ <--| prev | data | next |--> +------+------+------+ En el programa que se ver en seguida, se simula el comportamiento de una estructura de cola de doble enlace. Para la implementacin de la clase DDqueue en el programa se han elegido los mtodos: put_front(), put_back(), get_front(), get_back(), empty(), size(), poner un elemento en el frente de la cola poner un elemento en la parte tracera de la cola retirar un elemento de la parte frontal de la cola retirar un elemento de la parte tracera de la cola regresa 1 (TRUE) si la cola est vacia nmero de elementos en la cola

158

/*---------------------------------------------------------------+ + ejemplo de una cola doblemente enlazada (Dqueue) basada en un + + arreglo dinmico + + + + Autor: Oscar E. Palacios + + email: oscarpalacios1@yahoo.com.mx + + + + Manifiesto: + + Este programa puede distribuirse, copiarse y modificarse de + + forma libre. + +---------------------------------------------------------------*/ #include <iostream.h> #include <conio.h> // using namespace std; typedef char DATA_TYPE; struct nodo { DATA_TYPE data; nodo *next, *prev; }; class DDqueue { int itemsize, items; nodo *cola, *cabeza;

Estructuras II

159

public: // constructor DDqueue() : cola(NULL), cabeza(NULL), items(0), itemsize(sizeof(DATA_TYPE)) {} // destructor ~DDqueue() {}

/* agregar componente en la parte tracera de la lista */ DATA_TYPE put_back(DATA_TYPE valor) { nodo *temp; temp = new nodo; if (temp == NULL) return -1; temp->data = valor; items ++; if (cabeza == NULL ) { temp->next = NULL; temp->prev = NULL; cabeza = temp; cola = temp; } else { cola->next = temp; temp->prev = cola; cola = temp; cola->next = NULL; } return valor; } /* agregar componente en la parte frontal de la lista */ DATA_TYPE put_front(DATA_TYPE valor) { nodo *temp; temp = new nodo; if (temp == NULL) return -1; temp->data = valor;

Estructuras II items ++; if (cabeza == NULL ) { temp->next = NULL; temp->prev = NULL; cabeza = temp; cola = temp; } else { cabeza->prev = temp; temp->next = cabeza; cabeza = temp; cabeza->prev = NULL; } return valor; } // regresa true si la lista est vacia int empty() { return items == 0; }

160

/* retirar elemento de la parte frontal lista */ DATA_TYPE get_front() { nodo *temp; DATA_TYPE d; if ( empty() ) return -1; items --; d = cabeza->data; temp = cabeza->next; if (cabeza) delete cabeza; cabeza = temp; return d; } /* retirar elemento de la parte tracera de la lista */ DATA_TYPE get_back() { nodo *temp; DATA_TYPE d; if ( empty() ) return -1; items--; d = cola->data;

Estructuras II temp = cola->prev; if (cola) delete cola; cola = temp; return d; } }; // fin de la clase DDqueue /* punto de prueba */ int main() { clrscr(); DDqueue s; DATA_TYPE d; // insertando elementos en la parte tracera for (d='A'; d<='Z'; d++) s.put_back(d); // insertando en la parte delantera for (d=9; d>=0; d--)s.put_front(d+'0'); // vaciando la lista while ( ! s.empty() ) cout << (DATA_TYPE)s.get_front() << " "; cout << "\nPara terminar presione <Enter>..."; cin.get(); return 0; }
Punteros Arriba Plantillas

161

Referencias
[1] mailto:oscarpalacios1@yahoo. com. mx [2] http:/ / www. bloodshed. net/ devcpp. html [3] http:/ / dn. codegear. com/ article/ 21751

Plantillas

162

Plantillas
Editores: Oscar E. Palacios [1]
Estructuras II Excepciones

Plantillas
Introduccin
Con el objeto de explicar la razn de la necesidad de la existencia de las plantillas debemos reflexionar sobre tres paradgmas de programacin anteriores, estas son: programacin clsica o procedimental, programacin estructurada y programacin orientada al objeto POO. Programacin clsica En el tipo de programacin conocida como clsica existe una clara diferenciacin entre los datos y su manipulacin, es decir, entre los datos y el conjunto de algoritmos para manejarlos. Los datos son tipos muy simples y generalmente los algoritmos se agrupan en funciones orientadas de forma muy especfica a los datos que deben manejar. Por ejemplo, si se escribe una funcin ( sort ) para ordenar en forma ascendente o descendente los nmeros contenidos en un arreglo de nmeros enteros, dicha funcin puede aplicarse a cualquier arreglo de enteros ms no a arreglos de otro tipo. An as, la programacin clsica provee el soporte necesario para la reutilizacin de cdigo ya que el cdigo de la funcin se escribe solamente una vez y su reutilizacin se da por medio de un mecanismo conocido como llamada de funcin. Programacin estructurada En la medida en que los datos que haba de manipular se iban haciendo cada vez ms complejos se busco la forma de agruparlos de alguna manera bajo un mismo nombre, es as como surjen las estructuras de datos. Muchos autores se refieren a la programacin estructura como a la suma de funciones y/o procedimientos ms estructura de datos. Programacin Orientada al Objeto La Programacin Orientada al Objeto ( POO ) introduce nuevas facilidades y se extiende el concepto de dato, permitiendo que existan tipos ms complejos, es decir, si la programacin estructurada establece las bases para la manipulacin de funciones y datos estructurados, la POO establece las bases para manipular datos y funciones como un solo objeto. Esta nueva habilidad viene acompaada por ciertas mejoras adicionales: la posibilidad de ocultacin de determinados detalles internos irrelevantes para el usuario y la capacidad de herencia simple o mltiple. Notas: El ocultamiento de cdigo as como la herencia estn presentes ( en una forma simple ) en la programacin estructurada, y los mismos adquieren mucha ms relevancia en la POO. Por ejemplo, en la programacin estructurada si usted escribe una librera de funciones, al usuario de dicha librera solamente le informar de la existencia de tal o cual funcin, as como el objetivo y la forma correcta para comunicarse con estas, pero no es necesario que se le explique el funcionamiento interno de las mismas. Por otro lado, es bien conocido que lenguajes tales como Pascal y C, por ejemplo, dan el soporte para la creacin de datos estructurados ( Record en Pascal, y struct en C ), y que dichas estructuras pueden contener a otras estructuras previamente definidas. De tal manera vemos como a los usuarios de las funciones se les oculta el cdigo de las mismas, y que una estructura que contiene a otra hereda los miembros de la estructura contenida. Programacin genrica La programacin genrica est mucho ms centrada en los algoritmos que en los datos, y su postulado fundamental

Plantillas puede sintetizarse en una palabra: generalizacin. Significa que, en la medida de lo posible, los algoritmos deben ser parametrizados al mximo y expresados de la forma ms independiente posible de detalles concretos, permitiendo as que puedan servir para la mayor variedad posible de tipos y estructuras de datos. Con el objetivo de mostrar de una manera practica las diferencias entre los tres tipos de programacin mencionados arriba tomaremos como base el modelo de una vieja estructura de datos amiga de los programadores, me refiero a un array, tambin conocida por muchos como arreglo, tabla o lista. Para no entrar en polemicas de estndarizacin de nombrado en este captulo diremos que la estructura modelo con la que trabajaremos es un vector ( arreglo unidimensional ). Pues bien, dado un vector cualquiera se presenta la necesidad de crear un cierto nmero de funciones que sean las encargadas de la manipulacin de los datos dentro del vector. Para comenzar, podemos pensar en las funciones: 1. 2. 3. 4. 5. 6. 7. mostrar, para desplegar o imprimir los elementos del vector. ordenar, para ordenar los elementos del vector. buscar, para determinar la presencia o ausencia de un determinado elemento dentro del vector. insertar, para agregar componentes al vector. eliminar, para quitar componentes del vector. capacidad, para obtener la capacidad mxima del vector. cuenta, para obtener el nmero de elementos actuales contenidos por el vector.

163

Al hacer un anlisis muy detallado del problema planteado y al tratar de resolver el mismo mediante la programacin clsica, veremos que, si bien es cierto que se puede llegar a la solucin, para lograrlo se tendran que establecer una serie de medidas que nos permitiern controlar de alguna manera detalles tales como: el total de elementos soportados por el vector y el nmero de elementos actuales contenidos por el vector. Por ejemplo, con la instruccin: int vector[120]; se crea un arreglo con capacidad para 120 componentes de tipo entero . Ahora bien, el compilador sabe perfectamente que deber reservar un espacio de 120 enteros para la memoria del vector, pero no se garantiza que cualquier funcin trate de leer o escribir datos fuera de los limites del vector. Es decir, nuestro programa no podra saber el tamao del vector a menos que usaramos algn truco, por ejemplo usar el primer elemento del vector para contener el tamao del mismo. Otra forma de alcanzar una solucin, sera que a cada una de las funciones que tengan que ver con el vector se le pasar los parmetros: puntero de vector y nmero de elementos en el vector, de tal manera que, por ejemplo, la funcin mostrar podra declararse como: int mostrar(int *vector, int cuenta);

Un paso hacia adelante


Si continuamos en el mbito de la programacin clsica podemos dar un paso ms si es que nos valemos de tipos estructurados ms elaborados. Por ejemplo, podemos definir una estructura llamada vector la cual posea los miembros: capacidad, cuenta y data como se muestra en seguida: typedef int int int }; struct vector { capacidad; cuenta; *data;

De tal manera que podriamos escribir funciones que operen sobre un solo parmetro del tipo estructurado vector. Por ejemplo, la funcin mostrar podra declararse como: int mostrar(vector *v);

Plantillas En este punto tendramos que detenernos y pensar en lo siguiente: La estructura vector ( definida arriba con typedef ) es solamente un nuevo tipo de dato, es decir, podemos declarar tantas copias ( variables ) de la misma como sean necesarias, pero carece de un mtodo constructor adecuado. Por ejemplo, la declaracin: vector nombre_var; es vlida siempre y cuando que el tipo vector ya haya sido definido, pero la variable nombre_var contendr slo basura hasta que no se establezcan los valores adecuados para cada uno de sus miembros ( capacidad, cuenta y data ).

164

Una mejor solucin


Para el tipo de problemas como en el ejemplo vector y otros similares, surge la POO, misma que facilita en gran medida la solucin del mismo, pero an queda pendiente la resolucin a otro problema, es decir, hasta aqu hemos mencionado la estructura vector como un contenedor de nmeros enteros, pero un vector podra contener caracteres, nmeros de punto flotante y otros tipos estructurados; y los mismos algoritmos usados para ( mostrar, ordenar, buscar, etc. ) empleados en un vector de enteros, tendran su aplicacin sobre vectores de cualquier tipo. Es as como surge lo que se conoce como PLANTILLAS o lo que es lo mismo, la programacin genrica.

La clase vector desde la perspectiva de la POO


Con el objetivo de mostrar en la prctica los conceptos que hemos venido mencionando presentaremos un pequea implementacin de la clase vector. Se debe aclarar que la implementacin de la misma se har para vectores contenedores de datos tipo int solamente. Para simplificar el ejemplo, para la clase vector solamente se implementarn los mtodos mostrar(), ordenar() e insertar(), as como un mtodo constructor base y un mtodo destructor. #include <iostream> #include <cstdio> #include <ctime> using namespace std; #define VECTOR_DEFAULT_SIZE 128 class vector { // atributos int capacidad; int cuenta; int *data; public: // constructor base vector() { capacidad = VECTOR_DEFAULT_SIZE; cuenta = 0; data = new int[VECTOR_DEFAULT_SIZE]; }

Plantillas

165

// destructor ~vector() { delete[] data; } // despliega todos los elementos contenidos por el vector void mostrar() { for (int t = 0; t < cuenta; t++) cout << "elemento " << t << ", valor " << data[t] << endl; } // inserta un elemento al vector int insertar(int d) { if (cuenta == capacidad) return -1; data[cuenta] = d; cuenta ++; return cuenta; } // ordena en forma ascendente los elementos del vector void ordenar() { int i, j, temp; int fin = cuenta; i = 0; while (i < fin ) { for ( j = i ; j < fin-1; j++) if ( data[j] > data[j+1] ) { temp = data[j]; data[j] = data[j+1]; data[j+1] = temp; } fin --; } } };

#define MAX 10 int main() { vector v; srand( time(NULL) );

Plantillas for (int r = 0; r < MAX; r++) v.insertar( rand() % 100); cout << "\nvector v sin ordenar\n"; v.mostrar(); v.ordenar(); cout << "\nvector v ordenado\n"; v.mostrar(); getchar(); return 0; }

166

Una plantilla para la clase vector


Una vez que se llegado al entendimiento de la programacin estructurada as como de la programacin orientada al objeto, se puede observar que, si bien es cierto que ambas ofrecen soluciones a problemas fundamentales tambin es cierto que las soluciones se presentan como casos especializados. Esta ltima afirmacin la podemos ilustrar si nos fijamos en el caso del programa presentado anteriormente, en dicho programa se presenta la clase vector como un contenedor especial para nmeros enteros (int), ahora bien, si prestamos an aun ms atencin podemos llegar a la conclusin de que todos los algoritmos o funciones aplicadas en la clase vector pueden operar con cualquier otro tipo de datos y, por lo tanto, la nica diferencia sera el tipo de datos contenidos por el vector. De tal manera que aparece lo que se llama generalizacin o programacin genrica y esta, a su vez, nos permite la creacin de plantillas basadas en una lgica operatoria previamente concebida. Con el objetivo de mostrar un ejemplo prctico retomaremos la clase vector del programa de la seccin anterior y crearemos, a raiz del mismo, una plantilla. La plantilla resultante tendr la capacidad para crear vectores de cualquier tipo. #include <iostream> #include <cstdio> #include <ctime> using namespace std; #define VECTOR_DEFAULT_SIZE 128 template <class T> class vector { // atributos int capacidad; int cuenta; T *data; public: // constructor base vector() { capacidad = VECTOR_DEFAULT_SIZE; cuenta = 0;

Plantillas data = new T[VECTOR_DEFAULT_SIZE]; } // destructor ~vector() { delete[] data; } void mostrar(); int insertar(T d); void ordenar(); }; // implementacin del mtodo mostrar template <class T> void vector<T>::mostrar() { for (int t = 0; t < cuenta; t++) cout << "elemento " << t << ", valor " << data[t] << endl; } // implementacin del mtodo insertar template <class T> int vector<T>::insertar(T d) { if (cuenta == capacidad) return -1; data[cuenta] = d; cuenta ++; return cuenta; } // implementacin del mtodo ordenar template <class T> void vector<T>::ordenar() { T temp; int i, j, fin = cuenta; i = 0; while (i < fin ) { for ( j = i ; j < fin-1; j++) if ( data[j] > data[j+1] ) { temp = data[j]; data[j] = data[j+1]; data[j+1] = temp; } fin --; } }

167

Plantillas

168

#define TEST 10 int main() { // prueba de un vector de nmeros de punto flotante vector<double> v; srand( time(NULL) ); for (int r = 0; r < TEST; r++) v.insertar( (rand() % 10) * 0.5); cout << "\nvector v sin ordenar\n"; v.mostrar(); v.ordenar(); cout << "\nvector v ordenado\n"; v.mostrar(); // prueba de un vector de nmeros long int vector<long int> v2; srand( time(NULL) ); for (int r = 0; r < TEST; r++) v2.insertar( (rand() % 100) ); cout << "\nvector v2 sin ordenar\n"; v2.mostrar(); v2.ordenar(); cout << "\nvector v2 ordenado\n"; v2.mostrar(); getchar(); return 0; }
Estructuras II Arriba Excepciones

Referencias
[1] mailto:oscarpalacios1@yahoo. com-mx

Excepciones

169

Excepciones
Plantillas Librera Estndar de Plantillas

Excepciones. Motivacin histrica


Primeramente, antes de entrar en el tema de las excepciones en programacin, se ha de matizar en el concepto de qu son las excepciones, vistas desde un punto de vista fuera y dentro del mundo de la programacin. En el lenguaje humano, una excepcin es un elemento excluyente de una regla, y de forma convencional se ha extendido esta definicin. En el lenguaje mquina, una excepcin se trata, de forma general, de algo que no se espera que ocurra, pero que puede ocurrir, similar al tratamiento de errores, pero de los errores en tiempo de ejecucin. A veces estas excepciones, para una mquina, no son casos que no deberan estar contemplados, tal y como un programador se lo asigna, sino que pueden ser indicadores para comprobar que realmente todo est marchando bien o no. En los programas de ordenador hechos en C existi durante mucho tiempo la costumbre de usar el comando "goto" (tambin implementada en C++), pero ste se ha ido eliminando progresivamente de casi todos y cada uno de los cdigos y programas que han ido surgiendo. El significado de la funcin 'goto' no forma parte del libro actual, pero se pueden buscar referencias por internet donde se especifique con ms detalle qu es. Como una de las formas de control de errores ms usuales era con goto, se usaron otras variantes, como las aserciones de cdigo (assertions, en ingls) o, con la llegada de la programacin orientada a objetos, de los comandos try, catch y throw.

Conformacin de los bloques try y catch


Por norma general, los comandos try y catch conforman bloques de cdigo. Cada uno de estos bloques se recomienda, aunque sea de una nica lnea, envolverlos en llaves, como muestra el siguiente ejemplo: <font size="11.67"> // ...cdigo previo... try { // bloque de cdigo a comprobar } catch( tipo ) // Ha ocurrido un suceso en el try que se ha terminado //la ejecucin del bloque y catch recoge y analiza lo sucedido { // bloque de cdigo que analiza lo que ha lanzado el try } // ...cdigo posterior... </font> Generalmente entre el try y el catch no se suele insertar cdigo, pero se insta al lector a que lo intente con su compilador habitual y que compruebe si hay errores o no de compilacin.

Excepciones

170

Control de excepciones
Una excepcin es un error que puede ocurrir debido a una mala entrada por parte del usuario, un mal funcionamiento en el hardware, un argumento invlido para un clculo matemtico, etc. Para remediar esto, el programador debe estar atento y escribir los algoritmos necesarios para evitar a toda costa que un error de excepcin pueda hacer que el programa se interrumpa de manera inesperada. C++ soporta una forma ms directa y fcil de ver tanto para el programador como para los revisores del cdigo en el manejo de excepciones que su smil en el C estndar y esta consiste, tratndose del lenguaje C++, en el mecanismo try, throw y catch. La lgica del mecanismo mencionado consiste en: 1. Dentro de un bloque try se pretende evaluar una o ms expresiones y si dentro de dicho bloque se produce un algo que no se espera se lanza por medio de throw una excepcin, la misma que deber ser capturada por un catch especfico. 2. Puesto que desde un bloque try pueden ser lanzados diferentes tipos de errores de excepcin es que puede haber ms de un catch para capturar a cada uno de los mismos. 3. Si desde un try se lanza una excepcin y no existe el mecanismo catch para tratar dicha excepcin el programa se interumpir abruptamente despues de haber pasado por todos los catchs que se hayan definido y de no haber encontrado el adecuado. 4. Los tipos de excepciones lazados pueden ser de un tipo primitivo tal como: int, float, char, etc. aunque normalmente las exepciones son lanzadas por alguna clase escrita por el usuario o por una clase de las que vienen incluidas con el compilador. En el programa que se listar a continuacin muestra un ejemplo de como lanzar una excepcin de tipo int dentro del bloque try, y cmo capturar la excepcin por medio de catch. Ejemplo
<font size="11.67"> // Demostracin de los comandos try, throw y catch #include <iostream>

// Funcin: main // Recibe: void // Devuelve: int // En la funcin principal se tratarn los comandos try, throw y catch int main(void) { try // Se intenta hacer el siguiente cdigo { // Aqu puede ir ms cdigo... throw 125; //...aunque directamente en este caso se lanza una excepcin. } catch(int) // Se captura con un catch de enteros (podra usarse long o char, por ejemplo) { std::cout << "Ha surgido una excepcin de tipo entero" << std::endl; // y se muestra por pantalla }

std::cin.get(); // Y el programa finaliza.

Excepciones
return 0; } </font>

171

Excepciones genricas
Como ya se ha mencionado, los errores pueden deberse a una multitud de situaciones muchas veces inesperadas, por tal motivo, en C++ existe una forma de manejar excepciones desconocidas ( genricas ) y es buena idea que si se est escribiendo un controlador de excepciones incluya un catch para capturar excepciones inesperadas. Por ejemplo, en el siguiente progama se escribe un catch que tratar de capturar cualquier excepcin inesperada. // Demostracin: try, throw y catch #include <iostream> using namespace std; int main() { try { throw 125; } catch(...) { cout << "Ha ocurrido un error inesperado..." << endl; } cin.get(); return 0; }

Excepciones de clases
Si usted est usando una clase escrita por terceras personas o de las que se incluyen con el compilador y desea utilizar el mecanismo try, deber conocer el tipo de excepcin lanzado por dicha clase para as poder escribir el catch correspondiente para el tratamiento de dicho error. Por ejemplo, la funcin at() de la clase string ( que ser estudiada ms adelante ) lanza una excepcin cuando se trata de leer o escribir un componente fuera de rango. En tales casos usted puede proceder a capturar el error como se muestra en el siguiente programa. // Compilado y probado exitosamente con Dev-C++ // Demostracin: excepcin de la clase string #include <iostream> #include <string> using namespace std; int main() { string s = "Hola"; try {

Excepciones cout << s.at(100) << endl; } catch(exception& e) { cout << e.what() << endl; } cin.get(); return 0; } En el programa anterior el mtodo at de la clase string lanzar ( throw-up ) un error de excepcin, ya que la instruccin s.at(100) trata de acceder a un elemento fuera de los limites del objeto ( s ).

172

La clase exception
Tal como se mostr en el programa anterior, los errorres generados por las libreras estndar de C++ pueden ser capturados por un catch que tome un parmetro tipo exception. Realmente, exception es una clase base de donde usted puede derivar las suyas y sobrescribir los mtodos para el tratamiento de excepciones. La clase exception est incluida en la libreria <exception> y su estructura es la siguiente: class exception { public: exception() throw() { } virtual ~exception() throw(); virtual const char* what() const throw(); }; En muchos casos bastar con sobrescribir el mtodo what() en la clase derivada de exception, ya que dicho mtodo es el encargado de generar el mensaje que trata de explicar la naturaleza del error ocurrido. En el programa que veremos en seguida, se da un ejemplo sencillo de cmo crear una clase derivada de la clase exception con el objetivo de sobrescribir el mtodo what(). // Demostracin: sobrescritura del mtodo what() #include <iostream> #include <cstdlib> #include <exception> using namespace std; class div_cero : public exception { public: const char* what() const throw() { return "Error: divisin por cero..."; } }; int main(int argc, char *argv[]) {

Excepciones double N, D; cout << "Probando divisin" << endl; cout << "Ingrese el numerador :"; cin >> N; cin.clear(); cout << "Ingrese el denominador :"; cin >> D; cin.clear(); try { if (D == 0) throw div_cero(); cout << N << " / " << D << " = " << N/D << endl; } catch(exception& e) { cout << e.what() << endl; } system("PAUSE"); return 0; } Siguiendo la misma metodologa mostrada por el programa anterior, usted puede crear clases independientes para capturar errores de excepciones especficas. Por ejemplo, si se desea crear una serie de funciones matemticas se deben considerar los sucesos de errores tales como: Divisin por cero, Error de dominio, Error de rango, etc. As, el siguiente programa puede ser un buen ejemplo para que usted escriba sus propios controladores de mensajes de error. Observe cmo en el programa se crea la clase ErrorMat y dentro de la misma la funcin porque() la cual se encargar de desplegar el mensaje de error. Aunque ErrorMat solo ha sido pensada para tratar los posibles errores de rango y errores de dominio, la misma puede rescribirse para capturar todos los errores posibles que puedan resultar a raiz de operaciones matemticas. Nota: No deje de observar tambin, cmo la funcin independiente logaritmo() verifica si el parmetro pasado a la misma es 0 o menor que 0 y en tales circunstancias se lanzara (throw) un error de excepcin del tipo ErrorMat ya que el dominio para la funcin log() es el de los nmeros positivos y el logaritmo de cero no est definido en los nmeros reales. // Demostracin: try, throw y catch #include <iostream> #include <cmath> using namespace std; static const int EDOMINIO=100; static const int ERANGO=101; class ErrorMat { public:

173

Excepciones ErrorMat() : motivo(0) {}; ErrorMat(int m) : motivo(m) {}; const char* porque() const throw(); private: int motivo; }; const char* ErrorMat::porque() const throw() { switch (motivo) { case EDOMINIO: return "Error de Dominio ";break; case ERANGO: return "Error de Rango ";break; default: return "Error Desconocido"; //En rigor no debera ocurrir } } double logaritmo(const double n) { try { if (n < 0) throw(ErrorMat(EDOMINIO) ); if (n == 0) throw(ErrorMat(ERANGO) ); return log(n); } catch(ErrorMat& e) { cout << e.porque(); } return 0; } int main() { double r = 100; cout << "log(" << r << ") = " << logaritmo(r) << endl; cout << "log(-" << r << ") = " << logaritmo(-r) << endl; cin.get(); return 0; }
Plantillas Arriba Librera Estndar de Plantillas

174

Librera Estndar de Plantillas

175

Librera Estndar de Plantillas


Editores: Oscar E. Palacios [1]

Biblioteca estndar de plantillas


Vectores Colas de doble fin Listas Pilas (stack) Colas (queue) Colas con prioridad Sets Maps Bitsets Iteradores

La STL (Standard Template Library) de C++ es una coleccin genrica de plantillas de clases y algoritmos que permite a los programadores implementar fcilmente estructuras estndar de datos como colas (queues), listas (lists), y pilas (stacks). La STL de C++ provee a los programadores con lo constructores siguientes, agrupados en tres categorias: Secuencias (sequences) 1. C++ Vectors 2. C++ Lists 3. C++ Double-Ended Queues Adaptadores de contenedor (Container Adapters) 1. C++ Stacks 2. C++ Queues 3. C++ Priority Queues Contenedores asociativos (Associative Containers) 1. 2. 3. 4. 5. C++ Bitsets C++ Maps C++ Multimaps C++ Sets C++ Multisets

La idea detras de la STL de C++ es que la parte dificil en el uso de estructuras complejas de datos ya ha sido previamente completada. Por ejemplo, si un programador desea usar un stack de enteros, todo lo que tiene que hacer es escribir el cdigo: stack<int> myStack; Con un minimo de esfuerzo, l o ella puede usar la funcin push() para ingresar enteros al stack; y la funcin pop() para retirar enteros del stack. A travez de la magia de las plantillas de C++, se puede especificar cualquier tipo de dato, no slo enteros. La clase Stack de la STL provee la funcionalidad genrica de un stack, sin importar el tipo de dato en el stack.
Vectores Colas de doble fin Listas Pilas (stack) Colas (queue) Colas con prioridad Iteradores

Librera Estndar de Plantillas

176

Iteradores
El termino iterar significa (en palabras sencillas) el mtodo o forma que se usa para poder navegar sobre los elementos de una lista especfica. Luego, un iterador es como una especie de "puntero especial" que nos permite leer o escribir valores sobre cada uno de los elementos en una lista. Los iteradores pueden ser comparados con los ndices que se emplean para leer o escribir valores sobre los elementos de una lista. Igual a lo que sucede con los tipos de datos dentro de un arreglo primitivo en donde para leer o escribir valores sobre los elementos se tiene que prestar atencin al tipo de dato, un iterador tiene que ser del tipo de dato que posee la lista. En ese sentido, si tenemos por ejemplo un vector que posee datos de tipo entero (int) entonces el iterador tendr que ser de tipo entero; si por el contrario se tiene un vector cuyos datos son del tipo cadena (string) entonces el iterador tiene que ser del tipo cadena. Sintaxis La sintaxis general para la declaracin de un iterador es: NombrePlantilla<tipo>::iterator varid; Donde,
NombrePlantilla Es el nombre de una STL tipo ::iterator varid Es el tipo usado para la plantilla Es el mtodo para obtener el iterador Es el nombre de la variable

Por ejemplo, para obtener un iterador a un vector de tipo int podemos usar la instruccion: vector <int>::iterator el_iterador; A manera de ejemplo, vamos a mostrar un programa en el que se usar un iterador de tipo char. Nota: en el programa se hace uso de los mtodos begin para obtener un iterador hacia el inicio del vector, y end para obtener un iterador hacia el final del mismo. // Demostracion del uso de iteradores // probado en: Dev-C++ 4.9.9.2 #include <cstdlib> #include <iostream> #include <vector> using namespace std; int main(int argc, char *argv[]) { vector<char> v; for (int x = 'A'; x <= 'Z'; x++) v.push_back(x); // obtenemos un iterator del tipo char vector<char>::iterator it; // lectura y despliegue de datos cout << "\ndesplegando datos" << endl;

Librera Estndar de Plantillas for( it = v.begin(); it != v.end(); it++ ) cout << *it << endl; system("PAUSE"); return EXIT_SUCCESS; }

177

Iteradores reversos
La STL de C++ permite que los elementos de las listas creadas puedan ser navegados en orden "normal", es decir desde el primero hasta el ltimo de los elementos agregados a la lista, y para dichas tareas se usa un iterador normal. Tambin, los elementos pueden navegarse en orden "reverso" y en tales casos se usa un iterador en reverso ( reverse_iterator ). Un ejemplo de reverse_iterator se muestra en el siguiente programa. Notas: Se debe de observar que para obtener iteradores normales se usan (generalmente) los mtodos begin() y end(), mientras que que para obtener iteradores reversos se emplean los mtodos rbegin() y rend(). Los iteradores reversos se comportan de manera inversa. Por ejemplo, para obtener un iterador que vaya desde el final hasta el inicio de los elementos, se usa la funcin rbegin() la cual regresar un iterador para procesar la lista desde el ltimo hasta el primero de los elementos. En contraparte, la funcin begin() regresa un iterador para procesar la lista desde el primero hasta el ltimo de los elementos. Es decir, begin() regresa una referencia hacia el primer elemento, mientras que rbegin() regresa una referencia hacia el ltimo elemento. // Demostracion del uso de iteradores reversos // probado en: Dev-C++ 4.9.9.2 #include <cstdlib> #include <iostream> #include <list> using namespace std; int test() { list<char> v; for (int x = 'A'; x <= 'Z'; x++) v.push_back(x); cout << endl; cout << "orden original" << endl; // creamos un iterator normal list<char>::iterator i = v.begin(); while(i != v.end() ) { cout << *i++ << " "; } cout << endl; cout << "orden inverso" << endl;

Librera Estndar de Plantillas // creamos un iterator reverso list<char>::reverse_iterator ri = v.rbegin(); while(ri != v.rend() ) { cout << *ri++ << " "; } cout << endl; return 0; }

178

int main(int argc, char *argv[]) { test(); system("PAUSE"); return EXIT_SUCCESS; }

Referencias
[1] mailto:oscarpalacios1@yahoo. com

Problemas Resueltos
Problema 1
Dados dos puntos determinar si la recta es creciente, decreciente, vertical , horizontal.

Solucion: Marco terico: Sabemos que, dados dos puntos


contiene. La pendiente de esta recta es determinada por y entonces hay una recta que los . Podemos determinar si una recta es

creciente, decreciente, vertical. horizontal analizando su pendiente, estos son los casos: 1. Si , entonces la recta es creciente. 2. Si , entonces la recta es decreciente. 3. Si , entonces la recta es horizontal, es decir cuando 4. La recta sera vertical, si . Con esta informacin podemos entonces disear nuestro codigo.
#include <iostream>

using namespace std;

int main() { double a,b,c,d,m; cout<<"Digite dos puntos P=(a,b) y Q(c,d):"<<endl<<endl; cout<<"a : "; cin>>a; cout<<"b : "; cin>>b; cout<<"c : "; cin>>c; cout<<"d : "; cin>>d; cout<<endl;

Problemas Resueltos
if (b!=d) { m = (a-c)/(b-d); if (m>0) {cout<<"La recta determinada por estos dos puntos P=("<<a<<","<<b<<") y Q("<<c<<","<<d<<") es Creciente"<<endl;} else {cout<<"La recta determinada por estos dos puntos P=("<<a<<","<<b<<") y Q("<<c<<","<<d<<") es Decreciente"<<endl;} if (m==0) {cout<<"La recta determinada por estos dos puntos P=("<<a<<","<<b<<") y Q("<<c<<","<<d<<") es Horizontal"<<endl;} } else { if(a!=c) {cout<<"La recta determinada por estos dos puntos P=("<<a<<","<<b<<") y Q("<<c<<","<<d<<") es Vertical"<<endl;} else {cout <<"Lo sentimos!. Por un mismo punto pasan infinitas rectas..."<<endl;} } cin.get(); /*Recuerda que esta linea es por si usas Windows*/ cin.get(); return 0; }

179

Problema 2
Utilizar el mtodo burbuja de ordenamiento sin recurrir a punteros

Solucion:
// ALGORITMO DE ORDENAMIENTO BURBUJA // Creador por Joseph, 15 de mayo de 2010; licencia GNU // increiblecrean.blogspot.com #include <iostream> using namespace std; int main() { //DATOS //establecer array a ordenar int lista[6] = {1, 9, 3, 10, -6, 0}; //obtener tamao array int tamanyo = sizeof(lista)/sizeof(int); // Variables temporales int temp, a = 0; //MOSTRAR ARRAY ORIGINAL cout << "Este es el array original:" <<endl; for (int n = 0; n < tamanyo; n++)

Problemas Resueltos cout << lista[n] << ", "; cout << endl << endl;

180

//ORDENAMIENTO for (int limite = tamanyo - 1; limite >= 0;limite--) { while (a < limite) { /* Se puede cambiar el signo (> <) para ordenar de menor a mayor o viseversa */ if (lista[a] < lista[a + 1]) { temp = lista[a]; lista[a] = lista[a + 1]; lista[a + 1] = temp; } a++; } a = 0; } //MOSTRAR ARRAY MODIFICADO cout << "Este es el array ordenado:" <<endl; for (int n = 0; n < tamanyo; n++) cout << lista[n] << ", "; return 0; }

Fuentes y contribuyentes del artculo

181

Fuentes y contribuyentes del artculo


Lo ms bsico Fuente: http://es.wikibooks.org/w/index.php?oldid=189262 Contribuyentes: Cvmontuy, Davidcanar, Einsteinm2, El lobo, Jarisleif, Jcaraballo, Julian.caba, LadyInGrey, ManuelGR, MarcoAurelio, Martinaguilar, Oleinad, Pirenne, Ruly, Savh, Taichi, The Fantasy Club, 74 ediciones annimas Programacin en C++ Fuente: http://es.wikibooks.org/w/index.php?oldid=182428 Contribuyentes: Alejovivasp, Celeron, Davidcanar, El lobo, Fii.unmsm, Gambrinus, J.delanoy, Javier Carro, ManuelGR, Marioamendez, Martinaguilar, Muro de Aguas, Oleinad, Ruly, The Fantasy Club, Thorkemado, Wikimi-dhiann, 67 ediciones annimas Iteraciones y decisiones Fuente: http://es.wikibooks.org/w/index.php?oldid=181597 Contribuyentes: El lobo, Julian.caba, Marioamendez, Nagual95, Oleinad, Savh, 33 ediciones annimas Estructuras Fuente: http://es.wikibooks.org/w/index.php?oldid=184374 Contribuyentes: El lobo, Gabrielsnake, J.delanoy, Jordaker, Lemures, Oleinad, Ruy Pugliesi, Savh, UBravo, 71 ediciones annimas Funciones Fuente: http://es.wikibooks.org/w/index.php?oldid=186168 Contribuyentes: 67wkii, El lobo, Jhalvico, MarcoAurelio, Morza, Muro de Aguas, Nsmlinux, Oleinad, Rutrus, Savh, Zzzu, 55 ediciones annimas Streams Fuente: http://es.wikibooks.org/w/index.php?oldid=179487 Contribuyentes: ByDSA, Defender, Der Knstler, El lobo, Muro de Aguas, Oleinad, 28 ediciones annimas Arrays y cadenas de texto Fuente: http://es.wikibooks.org/w/index.php?oldid=177736 Contribuyentes: Defender, El lobo, MarcoAurelio, Oleinad, Ruy Pugliesi, Savh, Victor.spain, 38 ediciones annimas Desarrollo Orientado a Objetos Fuente: http://es.wikibooks.org/w/index.php?oldid=132163 Contribuyentes: El lobo, Jarisleif, Manolo.wiki, Oleinad, 3 ediciones annimas Objetos y Clases Fuente: http://es.wikibooks.org/w/index.php?oldid=189012 Contribuyentes: Albmont, Defender, El lobo, GermanX, Guanucoluis, Luckas Blade, Oleinad, Victor.spain, 39 ediciones annimas Sobrecarga de Operadores Fuente: http://es.wikibooks.org/w/index.php?oldid=184081 Contribuyentes: David0811, El lobo, Groucho Marx, 29 ediciones annimas Herencia Fuente: http://es.wikibooks.org/w/index.php?oldid=182388 Contribuyentes: Cvmontuy, El lobo, Morza, The Fantasy Club, 31 ediciones annimas Funciones virtuales Fuente: http://es.wikibooks.org/w/index.php?oldid=182198 Contribuyentes: 67wkii, El lobo, Morza, 37 ediciones annimas Punteros Fuente: http://es.wikibooks.org/w/index.php?oldid=188880 Contribuyentes: Ajraddatz, Albmont, El lobo, Jcaraballo, ManuelGR, MarcoAurelio, Martinaguilar, Muro de Aguas, Savh, XemDenots, 67 ediciones annimas Estructuras II Fuente: http://es.wikibooks.org/w/index.php?oldid=178029 Contribuyentes: El lobo, MarcoAurelio, Morza, 4 ediciones annimas Plantillas Fuente: http://es.wikibooks.org/w/index.php?oldid=179284 Contribuyentes: El lobo, Ranzel, 1 ediciones annimas Excepciones Fuente: http://es.wikibooks.org/w/index.php?oldid=182444 Contribuyentes: El lobo, Jarisleif, Ranzel, The Fantasy Club, 7 ediciones annimas Librera Estndar de Plantillas Fuente: http://es.wikibooks.org/w/index.php?oldid=156329 Contribuyentes: El lobo, ManuelGR, 2 ediciones annimas Problemas Resueltos Fuente: http://es.wikibooks.org/w/index.php?oldid=161502 Contribuyentes: Dark Bane, Einsteinm2, 2 ediciones annimas

Fuentes de imagen, Licencias y contribuyentes

182

Fuentes de imagen, Licencias y contribuyentes


Archivo:logoc++.png Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:Logoc++.png Licencia: GNU Free Documentation License Contribuyentes: El lobo Archivo:System-users.svg Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:System-users.svg Licencia: desconocido Contribuyentes: The people from the Tango! project Archivo:25%.svg Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:25%.svg Licencia: Public Domain Contribuyentes: Karl Wick Archivo:50%.svg Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:50%.svg Licencia: Public Domain Contribuyentes: Siebrand Archivo:100%.svg Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:100%.svg Licencia: Public Domain Contribuyentes: Siebrand Archivo:00%.svg Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:00%.svg Licencia: Public Domain Contribuyentes: Siebrand Imagen:Heckert GNU white.svg Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:Heckert_GNU_white.svg Licencia: Free Art License Contribuyentes: Aurelio A. Heckert <aurium@gmail.com> Imagen:desicion.png Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:Desicion.png Licencia: GNU Free Documentation License Contribuyentes: MyName (El lobo) Imagen:Crystal_Clear_app_kedit.png Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:Crystal_Clear_app_kedit.png Licencia: GNU Lesser General Public License Contribuyentes: Everaldo Coelho and YellowIcon Imagen:struct01.gif Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:Struct01.gif Licencia: Creative Commons Attribution-Share Alike Contribuyentes: El lobo Imagen:Funciones.png Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:Funciones.png Licencia: GNU Free Documentation License Contribuyentes: El lobo Imagen:Herencia01.png Fuente: http://es.wikibooks.org/w/index.php?title=Archivo:Herencia01.png Licencia: GNU Free Documentation License Contribuyentes: El lobo

Licencia

183

Licencia
Creative Commons Attribution-Share Alike 3.0 Unported //creativecommons.org/licenses/by-sa/3.0/

Das könnte Ihnen auch gefallen