Aprendiendo C++ para Linux PDF
Aprendiendo C++ para Linux PDF
Aprendiendo C++ para Linux PDF
Horvath
SÁM S
sus conocimientos en la
vida real
Jesse L ib e rty
D a v id B. H o rv a th
Aprendiendo
TRADUCCION:
Alfonso Vidal Romero Elizondc
Ingeniero en Electrónica y Comui c
REVISIÓN TÉCNICA:
Hugo Jiménez Pérez
Matemático
Pearson
Educación
. ci, .C O LO M BIA -C O ST A R IC A -C H IL E
MÉXICO • A R G E N T I ^ W pERÜ . puERTO RICO • VEN EZU ELA
ESPAÑA * GUAI l.
’ «t u b w m
/ Patos de catalogación bibliográfica
EDICIÓN EN ESPAÑOL Ed it o r A s o c ia d o
Mtchucl Stephcns
EDITOR DE DIVISIÓN COMPUTACIÓN: ÓSCAR MADRIGAL MUÑI/.
SUPERVISOR DE TRADUCCIÓN: ANTONIO NI ÑEZ RAMOS E d it o r a de A d q u is ic io n e s
SUPERVISOR DE PRODUCCIÓN: RODRIGO ROMERO VILLALOBOS Caro] Ackcrm.in
APRENDIENDO C++ PARA LINUX EN 21 DÍAS E d it o r d e D e s a r r o l l o
Robyn T ilo m as
Versión en español de la obra titulada SAMS Teach Yourself C+ + fo r Linux in
21 Days, de Jesse Liberty y David B. Horvath, publicada originalmente en E d it o r a A d m in is t r a t iv a
inglés por SAMS, una división de Macmillan Computer Publishing, 201 W. Charlotte Clupp
103rd Street, Indianapolis, Indiana, 46290. EUA. E d it o r a de P r o y e c t o
Esta edición en español es la única autorizada. Chrisiina South
Semana 2 De un vistazo +.
Día 8 Apuntadores
~
9 Referencias 259
I0 Funciones avanzadas *v ™ \ 293
II Herencia \ ^ C> \ 3
12 Arreglos, cadenas tipo C y listas enlazadas
13 Polimorfismo \ 413
14 Clases y funciones especiales 455
S e m a n a 4 D e un v is ta z o 805
Día 22 El entorno de programación de Linux
23 Programación shell
24 Programación de sistem as **53
25 Comunicación entre procesos K73
26 Programación de la GUI **95
Se m an a 1 D e un vistazo 5
DIa 1 Comencemos 7
Qué es G N U .................................................................................................................... 7
Cómo obtener las herramientas GNU .......................................................................... 8
Una breve historia acerca de C++ ................................................................................ 9
Programas ................................................................................................................... 1^
Solución de problem as..................................................................................................II
Programación procedural, estructurada y orientada a objetos .......................... II
C++ y la programación orientada a objetos ..... 13
Cómo evolucionó C++ .............................................................................................. 1
¿Primero debo aprender C? ........................................................................................15
C++ y J a v a .....................................................................................................................13
El estándar ANSI .......................................................................................................... 13
Prepárese para programar ........................................................................................... 16
El entorno de desarrollo GNU/Linux ........................................................................17
Cómo compilar el código fuente ............................................................................... 18
Cómo crear un archivo ejecutable con el enlazador ................................................ 19
El ciclo de desarrollo ....................................................................................................19
" i Hola, mundo!", su primer programa de C++ ...................................................... 20
Uso del compilador g++ .............................................................................................23
Construcción del proyecto ¡Hola, mundo! ...........................................................23
Errores de compilación ...............................................................................................24
Resum en.........................................................................................................................25
Preguntas y respuestas ................................................................................................. 25
Taller ............................................................................................................................. 26
Cuestionario ............................................................................................................26
Ejercicios ................................................................................................................ 26
DIA 5 Funciones 99
Qué es una función ................................................................................................ 99
Valores de retomo, parámetros y argumentos ......................................................100
Declaración y definición de funciones ......... ..................................................... 101
Declaración de una función ............................................................................ 101
Uso de los prototipos de funciones ................................................................ 102
Definición de una función .............................................................................. 104
Ejecución de una función .....................................................................................105
Variables locales ...................................................................................................106
Variables globales .................................................................................................108
Variables globales: una advertencia...................................................................... 109
Más acerca de las variables locales ...................................................................... 110
Instrucciones de una función ................................................................................ 111
VIII A p r e n d ie n d o C + + p ara L in u x e n 21 D ia s
Se m an a 2 D e un vistazo 223
Cómo pasar parámetros por referencia para tener eficiencia ............................. 275
Paso de un apuntador const ..............................................................................27,S
Referencias como alternativa para los apuntadores ........................................281
Cuándo utilizar referencias y cuándo utilizar apuntadores ................................. 283
Cómo mezclar referencias y apuntadores .............................................................284
;No regrese una referencia a un objeto que esté fuera de alcance! .................. 285
Cómo regresar una referencia a un objeto en el heap ..........................................287
¿A quién pertenece el apuntador? ......................................................................... 289
Resumen.................................................................................................................. -90
Preguntas y respuestas ............................................................................................290
Taller ...................................................................................................................... -91
Cuestionario ......................................................................................................-91
Ejercicios ..........................................................................................................-91
D ía 11 Herencia 333
Qué es la herencia ...................................................................... ;í;
Herencia y derivación ....................................................... v *4
Cómo crear clases que representen animales ................. * *5
La sintaxis de la derivación ...................................................... .335
Comparación entre privado y protegido ...................................... .337
Constructores y destructores .............................................................. ..'*40
Paso de argumentos a los constructores base .............................. *42
Redefinición de funciones ............................................................................................... 4.40
Cómo ocultar el método de la clase base ..................................... *48
Cómo llamar al método base .................................................................................... *50
Métodos v irtuales............................................................................................................... *52
Cómo trabajan las funciones virtuales .................................................................... *56
No puede llegar allá desde aquí ................................................................................*57
Partición de d a to s ..............................................................................................................*57
Destructores virtuales .................................................................................................. *60
Constructores virtuales de copia ............................................................................ *00
El costo de los métodos virtuales ............................................................................ *6 *
R esum en............................................................................................................................ *64
Preguntas y respuestas ......................................................................................................*64
Taller ...................................................................................................................................365
Cuestionario ................................................................................................................. 365
Ejercicios ..................................................................................................................... 366
S e m an a 2 R e p a so 487
S e m an a 3 D e un v ista z o 499
DIa 19 Plantillas
Qué son las p la n tilla s ............................................................................................................ 661
Tipos parametrizados ...........................................................................................................662
Cómo crear una instancia a partir de una plantilla ............................................. 662
Definición de una plantilla ............................................................................................... 662
Uso del nombre .............................................................................................................664
Implementación de la plantilla ................................................................................... 665
Funciones de plantillas ...................................................................................................... 668
Plantillas y funciones am igas ...........................................................................................669
Clases y funciones am igas que no son de plantilla ............................................. 669
Clases y funciones am igas de plantilla general ....................................................673
Uso de elementos de plantilla .......................................................................................... 677
Funciones especializadas ............................................................................................ 68 ^
Miembros estáticos y plantillas .................................................................................687
La Biblioteca Estándar de Plantillas .............................................................................. 691
Contenedores .................................................................................................................692
Contenedores de se c u e n c ia ......................................................................................... 692
Contenedores asociativos ............................................................................................ ^01
C o n te n id o xvü
Pilas .............................................................................................................................705
Colas ...........................................................................................................................706
Clases de algoritmos ................................................................................................. 706
Algoritmos de secuencia no mulante ................................................................. 707
Algoritmos de secuencia mulante ......................................................................708
Resumen...................................................................................................................... 709
Preguntas y respuestas ...............................................................................................710
Taller ......................................................................................................................... 711
Cuestionario ......................................................................................................... 711
Ejercicios ............................................................................................................. 711
D ía 20 Excepciones y manejo de errores 713
Bugs y corrupción de código ..................................................................................714
Excepciones .........................................................................................................715
Unas palabras acerca de la corrupción del código ........................................ 715
Excepciones ............................................................................................................... 716
Cómo se utilizan las excepciones....................................................................... 716
Uso de los bloques try y catch ............................................................................. 721
Cómo atrapar excepciones ..................................................................................722
Más de una especificación catch ....................................................................... 722
Jerarquías de excepciones ..................................................................................725
Acceso a los datos de excepciones mediante la denominación
de objetos de excepciones ....................................................................................728
Uso de excepciones y plantillas............................................................................... 735
Cómo activar excepciones sin errores .....................................................................738
Cómo tratar con los errores y la depuración ..........................................................739
Uso de gdb o depurador GNU ........................................................................... 740
Uso de los puntos de interrupción .....................................................................742
Uso de los puntos de observación .....................................................................742
Examen y modificación del estado de la memoria .........................................742
Desensamble.........................................................................................................742
Resumen..................................................................................................................... 743
Preguntas y respuestas ............................................................................................ 743
Taller ..........................................................................................................................744
Cuestionario .........................................................................................................744
Ejercicios ............................................................................................................. 745
Se m an a 3 R e paso 791
Se m an a 4 De un vistazo 805
Resum en...................................................................................................................... 8 7 1
Preguntas y respuestas ............................................................................................... 871
Taller .......................................................................................................................... 871
ri n
Cuestionario .........................................................................................................87
Ejercicios ............................................................................................................. 87
S e m an a 4 R e p a s o 943
Ejercicios ...................:.................................................................................................. ..
índice 1067
C onte nid o XXV
Para preguntas relacionadas con este libro, puede contactarse con David en cpplinux-
@cobs.com, y su página Web es http://w w w .cobs.com /. ¡No envíe correo basura, por
favor!
XXVI A p r e n d ie n d o C + + p a ra L in u x e n 21 D ia s
Autores contribuyentes
Paul C e vo li es in g en iero de so ftw are y v iv e en B o s to n . NÍA. en d o n d e ha e je r c id o e s ta
profesión d u ran te los ú ltim o s 11 añ o s. S e e s p e c ia h /a en s o f t w a r e d e s i s t e m a , c o n t r o
ladores de d isp o sitiv o s y c o m p o n e n te s in te rn o s d e s is te m a s o p e r a tiv o s . P a u l tra b a ja
actualm ente en el g ru p o de so ftw are d e s iste m a d e u n a c o m p a ñ ía d e a p l i c a c i o n e s d e
Internet en el área m e tro p o litan a de B o sto n .
Dedicatoria
Este libro está dedicado a la memoria viviente de David Levine.
—Jesse
Me gustaría dedicar este libro a todos los buenos maestros que he tenido a través de los años. Aquellos
que me enseñaron que las preguntas no son estúpidas, aunque parezcan ser simples; que hay más por
aprender que lo que está escrito en el libro; que la mejor forma de aprender algo que requiera pensarse,
es haciéndolo —lo que algunas personas llaman juego, a menudo es aprendizaje. ¡Esas maestros ya
saben quiénes son!
También quiero dedicar este libro a mis estudiantes. Me han hecho algunas preguntas asombrosas. Pensé
que sabía computación y programación antes de que empezara a enseñar; al tener que contestar sus
preguntas, ¡me han obligado a aprender aún más sobre esos temas!
—David
Reconocimientos
Quisiera agradecer a todas las personas que hicieron posible esta edición y la anterior de
este libro. En primer lugar se encuentran Stacey, Robin y Rachel Liberty. También debo
agradecer a los editores de SAMS, incluyendo a Carol Ackerman, Tracy Dunkelberger.
Holly Allender, Sean Dixon, Chris Denny, Brad Jones, Robyn Thomas, Christina Smith y
Margaret Berson.
Muchos lectores contribuyeron con sugerencias y mejoras para este libro y les agradezco
a todos ellos. Por último, y de nuevo, agradezco a la señora Kalish, quien en 1965 en
señó a mi clase de sexto grado cómo hacer aritmética binaria, cuando ni ella ni nosotros
sabíamos por qué o para qué.
— Jesse
Quiero agradecer a todas esas excelentes personas de Sams (Macmillan Computer Publi-
shing) que ayudaron a que este libro pudiera realizarse. Carol Ackerman (Editora de
adquisiciones) vino a mí con la idea de escribir este libro, Christina Smith fue Editora
de proyectos y guió al grupo durante el proceso de edición, Margaret Berson realizó la
edición de la copia (revisión de gramática, sintaxis, claridad, etcétera), Robyn Thomas
fue editor de desarrollo (contenido y estructura), y Javad Abdollahi y Sean Coughlin
realizaron la mayor parte de la edición técnica, junto con Rory Bray. Sin ellos, este libro
no sería tan bueno como lo es ahora.
XXVIII A p r e n d ie n d o C + + p a ra L in u x e n 21 D ia s
Los autores contribuyentes, Hal, Paul y Jon. han sido de enorm e ayuda para cubrir algunos
de los temas más específicos de Linux que van más allá del lenguaje ( ++ m ism o Q uien
merece atención especial es Jon Parry-McC’ulloch. que se encargo del ca p itu lo sobre la
GUI casi de inmediato y tuvo que batallar con las diferencias de las zonas horarias (el
vive en Inglaterra). Jon es contribuyente en el grupo de d iscu sión por correo electró n ico
en Internet llamado Culto del padre Darwin (C uli o í Father D arw m ) (el cual d iscu te la
manifestación de las teorías de Darwin en las a ccio n es diarias y los errores de v an as per
sonas). N o voy a decirle cóm o encontrar este grupo porque tratamos de m antener la re
lación Señal a ruido alta (si está interesado, tendrá que encontrarlo usted m ism o ). Tuve la
oportunidad de conocer a Jon y a otro m iem bro del grupo cuando a sistí a una c la se en
Londres en 1999. Existe una gran diferencia entre hablar via correo e lectró n ico v hablar
en persona.
Como con cualquier proyecto grande, alguien tiene que hacer un sacrif icio . En mi ca so
la que sufre más debido al tiempo que se necesita invertir en un libro c o m o éste es mi
esposa Mary. Lo asombroso es que ella ni siquiera se queja (cuando m en os no m ucho).
Como parte de este compromiso con usted, el lector de Pearson Educación de M éxico
lo invita a dar su opinión. Por favor háganos saber si disfruta este libro, si tiene alguna
dificultad con la información y los ejemplos que se presentan, o si tiene alguna sugerencia
para la próxima edición.
Sin embargo, recuerde que el personal de Pearson Educación de México no puede actuar
como soporte técnico ni responder preguntas acerca de problemas relacionados con el
software o el hardware.
También puede ponerse en contacto con Pearson Educación de México a través de nues
tra página Web: http://w ww .pearsonedlatino.com
çj
Í[í:
i í;
li
i:
j.
/■
í ,¡
líÉ
Introducción
Este libro está diseñado para que usted aprenda a programar en C++ en el sistema opera
tivo Linux. En sólo 21 días conocerá los aspectos básicos, com o la administración de
E/S, los ciclos y arreglos, la programación orientada a objetos, las plantillas y la creación
de aplicaciones con C++. todo dentro de lecciones bien estructuradas y fáciles de enten
der. Para ilustrar los temas del día, en las lecciones se proporcionan listados de ejemplo,
ejemplos de la salida y un análisis del código. Los ejemplos de sintaxis están explicados
claramente para una útil referencia.
Para que se ponga al corriente con las características, herramientas y entorno específicos
para Linux, ¡se incluye una semana adicional! Puede aprender C++ sin esa semana, pero
sin duda le ayudará a mejorar sus habilidades específicas para Linux.
Para ayudarlo a desarrollar aún más sus habilidades, cada lección termina con un conjun
to de preguntas y respuestas, un cuestionario y ejercicios. Puede comprobar su progreso
revisando las respuestas a estas secciones, las cuales se proporcionan en el apéndice D.
“Respuestas a los cuestionarios y ejercicios”.
C o n ve n cio n e s
Este libro utiliza varios tipos de letra para ayudarle a d iferenciar entre el c ó d ig o de C ++
y el español normal. El código de C ++ está im preso en un tipo de letra e sp ecia l c o n o c id o
como monoespaciado. Los marcadores de p o sició n — palabras o caracteres tjue se u ti
lizan temporalmente para representar las palabras o caracteres reales que usted escribirá
en el código— están impresos en c u r s i v a s m o n o e s p a c i a d a s . L os térm inos n u ev o s o
importantes están impresos en c u r s iv a s .
Introducción 3 i.
En los listados que se incluyen en este libro, cada línea de código real está numerada. Si
en un listado ve una línea que no esté numerada, esto indica que la línea sin numeración
es en realidad una continuación de la anterior línea de código numerada (algunas líneas
de código son demasiado extensas para la anchura del libro). También verá un carácter de
continuación de línea, como éste En este caso, debe escribir las dos líneas com o una
sola; no las divida.
Los listados también vienen incluidos en el CD-ROM con nombres de archivo que
empiezan con l s t . seguido de un número de dos dígitos correspondiente a la lección, un
guión corto y luego un número de 2 dígitos correspondiente al listado. Por ejemplo, el
primer ejemplo del día 1 es l s t 0 i -01 .cxx
//;rí
iï
m
ií
jí
jí
./ji;!)
/;o
r
/
i;
/
i
S emana 1
De un vistazo
Para empezar con la primera semana del aprendizaje de la pro
gramación en C++. necesitará unas cuantas cosas: un com pi
lador. un editor y este libro. Si no cuenta con un compilador
de C++ y un editor, de todas formas puede utilizar este libro,
pero no obtendrá tanto provecho de el como lo haría si siguie
ra los ejercicios.
¡La mejor forma de aprender a programar es escribir progra
mas! Al término de cada día encontrará un taller que contiene
un cuestionario y algunos ejercicios. Asegúrese de contestar
todas las preguntas, y de evaluar su trabajo tan objetivamente
como le sea posible. Las lecciones posteriores amplían los
conocimientos que usted obtiene en las primeras lecciones,
por lo que debe asegurarse de entender el material completa-
mente antes de avanzar.
Objetivos
La primera semana cubre el material necesario para que usted
empiece con la programación en general, y específicam ente
6 Sem ana 1
D ía 1
Comencemos
¡Bienvenido a Aprendiendo C ++ para Linux en 21 Dias\ Hoy iniciará el
camino para convertirse en un hábil programador en C++. Aprenderá.
• Qué son los compiladores GNU y cómo se relacionan GNU y Linux
• Por qué C++ es el estándar predominante en el desarrollo de software
• Los pasos para desarrollar un programa de C++
• Los fundamentos sobre el uso de los compiladores GNU
• Cómo escribir, compilar y enlazar su primer programa funcional de C++
Qué es GNU
El acrónimo GNU significa “GNU No es UNIX”. Es el nombre de una serie
de paquetes de software útiles encontrados comúnmente en entornos UNIX
que son distribuidos por el proyecto GNU en el MIT. Por lo general, los paque
tes están disponibles sin costo en varios lugares en Internet (se cobran si usted
quiere una copia en medio físico, como en disco flexible, cinta o CD-ROM). El
desarrollo de los paquetes es un proceso cooperativo, y el trabajo es realizado
por muchos voluntarios. Este esfuerzo es conducido principalmente por Richard
M. Stallman (uno de los desarrolladores del editor EMACS).
Dia 1
Linux es un sistema operativo muy parecido a U NIX <mii in tim e n l.is m . u c . i s »ceiMr.ul.tN
y los derechos reservados de las versiones c o m e r c i a l e s ile l 'NIX » IVr«> m i i M s i e b a s i c a
mente en un kernel (el núcleo del sistem a operativo»
La mayoría de los comandos que se utilizan en Linux es en i c a l i d a d p a r t e de l p r o v e c t o
GNU patrocinado por la FSF (Fundación para el Softw are I.ihre» I l J S ' ; d e u n a di str i
bución típica de Linux es GNU. y sido el V í es verdaderam ente I inux
Como lo mencioné anteriormente, también puede obtener el compilador para otras plata
formas y sistemas operativos.
Si ya tiene Linux instalado, existe la posibilidad de que tenga también los compiladores
GNU (pero tal vez tenga una versión antigua).
El mejor lugar para buscarlos (además del CD-ROM) es http://w w w .gnu.org. Allí usted
puede descargar sin costo las herramientas y los compiladores GNU para varias platafor
mas. También puede obtener información relacionada con la compra del software de GNU
en CD-ROM. De cualquier forma, debería considerar enviarles una donación. Después de
todo, cuesta dinero mantener páginas Web e imprimir documentación. Yo ordené un juego
completo de los archivos binarios y las herramientas para todas las plataformas, para com
pararlas. (Yo no obtengo dinero cuando usted compra o hace una donación a la Fundación
para el Software Libre; simplemente creo en lo que están haciendo.)
Desarrollar código lleva tiempo. Y también lleva tiempo cortar, duplicar y entregar los
CD-ROMs. Como resultado, existen diferentes versiones para diferentes plataformas, y
diferentes versiones entre los medios de distribución. Puede descargar la versión más
reciente o comprar una versión un poco menos reciente.
El CD-ROM que viene en este libro incluye la versión 2.9.5 de los compiladores GNU
(la más reciente al momento de escribir este libro). La versión disponible en el CD-ROM
de la FSF es la 2.7.2. En general, entre más nuevo sea el compilador, es mejor.
Los ejemplos que vienen en este libro se pueden compilar con ambas versiones, a menos
que se indique lo contrario.
Las versiones más recientes del compilador son parte del eges (Sistema Experimental del
Compilador GNU). La gente de Cygnus hizo la mayor parte del trabajo para crear el nuevo
compilador. Cygnus es la empresa oficial de mantenimiento para los compiladores GNU
(bajo la supervisión de un comité de dirección). Cygnus Solutions fue adquirida por la em
presa Red Hat, que tiene su propia distribución del sistema operativo Linux. Para obtener
información de los compiladores GNU, visite el URL http : / /www. redhat . com.
Programas
La palabra programa se utiliza de dos formas: para describir in stru ccio n es in d iv id u a le s
(o código fuente) creadas por el programador, y para describir una pieza co m p leta de s o ft
ware ejecutable. Esta distinción puede ocasionar una enorm e con fu sión , por lo qu e tratare
mos de distinguir entre el código fuente por una parte, y el ejecutable por la otra.
Un programa se puede definir ya sea com o un conjunto de instrucciones creadas por un
programador, o como una pieza ejecutable de software.
C om e nce m os
i
El código fuente se puede convertir en programa ejecutable de dos formas: los intérpretes
convierten el código fuente en instrucciones para la computadora, y ésta ejecuta de inmedia
to esas instrucciones. De igual manera, los compiladores convierten el código fuente en un
programa, el cual se puede ejecutar posteriormente. Aunque es más fácil trabajar con los in
térpretes, la mayor parte de la programación seria se hace con compiladores, debido a
que el código compilado se ejecuta mucho más rápido. C++ es un lenguaje compilado.
Solución de problemas
Los problemas que los programadores deben resolver han estado cambiando. Hace 20
años se creaban programas para manejar grandes cantidades de información no procesa
da. Las personas que escribían el código y las personas que utilizaban el programa eran
todos profesionales de la computación. En la actualidad, la cantidad de personas que uti
lizan computadoras es mucho mayor, y la mayoría conoce muy poco acerca de la forma
en que trabajan las computadoras y los programas. Las computadoras son herramientas
utilizadas por gente que está más interesada en resolver sus problemas de negocios que
en batallar con ellas.
Irónicamente, los programas son cada vez más complejos para que sean más fáciles de usar
para esta nueva generación. Han pasado ya los días en los que los usuarios escribían coman
dos crípticos en indicadores esotéricos, sólo para ver un flujo de información no procesada.
Los programas de la actualidad utilizan “interfaces amigables para el usuario" complejas,
que involucran múltiples ventanas, menús, cuadros de diálogo y la inmensidad de metáforas
con las que nos hemos familiarizado. Los programas escritos para soportar este nuevo méto
do son mucho más complejos que aquellos que fueron escritos hace sólo 10 años.
Con el desarrollo de Web, las computadoras han entrado a una nueva era de penetración
de mercado; en la actualidad hay más personas que utilizan computadoras, y sus expecta
tivas son muy altas. Durante los últimos años, los programas se han vuelto más grandes
y más complejos, y se ha manifestado ya la necesidad de las técnicas de programación
orientada a objetos para manejar esta complejidad.
A medida que han cambiado los requerimientos de programación, también han evolucio
nado las técnicas y los lenguajes utilizados para escribir programas. Aunque la historia com
pleta es fascinante, este libro se enfoca en la transformación de la programación procedu-
ral a la programación orientada a objetos.
La suma de los salarios se puede dividir en los sig u ien tes pasos:
1. Obtener el registro de cada em pleado.
2. Obtener el salario.
3. Sumar el salario al total.
4. Obtener el registro del siguiente em pleado.
A su vez, la obtención del registro de cada em pleado se puede d ivid ir en lo s s ig u ie n te s
pasos:
1. Abrir el archivo de empleados.
2. Ir al registro correcto.
3. Leer la información del disco.
La programación estructurada sigue siendo un m étodo bastante e x ito s o para r e so lv e r
problemas complejos. Sin embargo, a finales de los 80 se hicieron m uy claras a lg u n a s
deficiencias de la programación estructurada.
En primer lugar, es un deseo natural pensar en la in fo rm a ció n (p or e je m p lo , lo s r e g is
tros de los empleados) y en lo que se puede hacer con la in form ación (ordenar, editar,
etcétera) como una sola idea. La programación procedural trabajaba en contra d e e sto , y
separaba las estructuras de datos de las funciones que m anipulaban e so s datos.
En segundo lugar, los programadores tenían que reinventar co n sta n tem en te n u ev a s s o lu
ciones para viejos problemas. Esto se conoce com o “reinventar la rueda” , q u e e s lo
opuesto a la reutilización. La idea de la reutilización es crear co m p o n en tes q u e ten g a n
propiedades conocidas, y luego poder adaptarlos a su program a a m edid a q u e lo s n e c e
site. Esto se inspira en el mundo del hardware: cuando un in gen iero n e c esita un transistor
nuevo, por lo general no inventa uno; busca de entre todos los transistores e x iste n te s uno
que funcione de la manera que necesita, o tal vez lo m odifica. N o e x istía una o p c ió n
parecida para un ingeniero de software.
Comencemos
Los programas antiguos obligaban al usuario a proceder paso por paso a través de una
serie de pantallas. Los programas modernos controlados por eventos presentan todas las
opciones al mismo tiempo y responden a las acciones de los usuarios.
Encapsulación
Cuando un ingeniero necesita agregar una resistencia al dispositivo que está creando, por
lo general no la construye partiendo desde cero. Busca entre todas las resistencias exis
tentes, examina las bandas coloreadas que indican las propiedades y escoge la que nece
sita. La resistencia es una “caja negra” en lo que concierne al ingeniero; no le importa
mucho cómo realiza su trabajo dicha caja mientras se ajuste a sus especificaciones: no
necesita buscar dentro de la caja para utilizarla en su diseño.
La propiedad de ser una unidad independiente se conoce como e n c a p su la c ió n . Con esta
propiedad podemos lograr el ocultamiento de los datos. El a c u ita m ie n to d e d a to s es una
característica altamente valorada con la que un usuario puede utilizar un objeto sin saber
o preocuparse por la forma en que éste trabaja internamente. A sí como usted puede utili
zar un refrigerador sin saber cómo funciona el compresor, también puede utilizar un objeto
bien diseñado sin conocer sus miembros de datos internos.
De la misma manera, cuando el ingeniero utiliza la resistencia, no necesita saber nada re
lacionado con el estado interno de ésta. Todas sus propiedades están encapsuladas dentro
del objeto resistencia; no están distribuidas por todo el circuito. No es necesario entender
cómo funciona la resistencia para utilizarla en forma efectiva. Sus datos están ocultos den
tro de su cubierta protectora.
14 D ía 1
C++ soporta las propiedades de la encapsulación por m ed io de la c rea ció n tic tip o s d e fi
nidos por el usuario, conocidos com o clases. En el dia ó. "C’la ses b a so ” . verá c o m o crear
clases. Después de crear una clase bien definida, ésta funciona c o m o una em u la d co m p leta
mente encapsulada (se utiliza com o una unidad com pleta). I I lu n c io n a m ic n to interno de
la clase debe estar oculto. Los usuarios de una clase bien definida no n ecesita n saher co m o
funciona la clase; sólo necesitan saber cóm o utilizarla.
Herencia y reutilización
Cuando los ingenieros de Acmé Motors quieren construir un nuevo auto, tienen d o s ojx'io-
nes: pueden empezar desde cero, o pueden modificar un m odelo existente. Tal vez su m o d e
lo Estrella sea casi perfecto, pero quieren agregarle un turbocargador > una tra n sm isió n
de seis velocidades. El ingeniero en jefe preferiría no tener que em pezar d e sd e cero , sino
decir: Construyamos otro Estrella, pero agreguém osle estas capacidades a d ic io n a les. L la
maremos Quasar al nuevo modelo”. Un Quasar es c o m o un E strella, pero m ejo ra d o con
nuevas características.
C++ soporta la herencia. Se puede declarar un nuevo tipo que sea una e x te n s ió n de un
tipo existente. Se dice que esta nueva subclase se deriva del tipo e x iste n te , y a lg u n a s v e
ces se conoce como tipo derivado. El Quasar se deriva del Estrella y por c o n s e c u e n c ia
hereda todas sus cualidades, pero se le pueden agregar m ás en c a so de ser n e c e sa r io .
La herencia y su aplicación en C++ se tratan en el día 11, “ H eren cia ”, y en el día 15.
“Herencia avanzada”.
Polimorfismo
El nuevo Quasar podría responder en forma distinta de un E strella al oprim ir el a c ele r a
dor. El Quasar podría utilizar la inyección de com bustible y un turbocargador, m ientras
que el Estrella simplemente alimentaría gasolina a su carburador. Sin em bargo, un usuario
no tiene que conocer estas diferencias. Sim plem ente puede “p isarle” y ocurrirá lo a d e
cuado, dependiendo del auto que conduzca.
Aunque es cierto que C++ es un superconjunto de C. y que casi cualquier programa legí
timo de C es un programa legítimo de C++. el salto de C a C++ es muy significativo. C++
se benefició de su relación con C durante muchos años debido a que los programadores de
C podían utilizar C++ con facilidad. Sin embargo, para obtener el beneficio com pleto
de C++. muchos programadores descubrieron que tenían que olvidarse de todo lo que ha
bían aprendido y tenían que aprender una nueva forma de visualizar y resolver los pro
blemas de programación.
C++ y Java
En la actualidad, C++ es el lenguaje predominante para el desarrollo de software comer
cial. Durante los últimos años Java ha desafiado ese dominio, pero el péndulo se balancea
de nuevo a favor de C++, y muchos de los programadores que lo dejaron para utilizar Java
están volviendo con C++. De cualquier forma, los dos lenguajes son tan similares que si
se aprende uno es como si se aprendiera el 90% del otro.
El estándar ANSI
El Comité de Estándares Acreditados, que opera bajo los procedimientos del ANSI
(Instituto Estadounidense de Estándares Nacionales), ha creado un estándar interna
cional para C++.
El Estándar C++ se conoce ahora como Estándar ISO (Organización Internacio
nal de Estándares), Estándar NCITS (Comité Estadounidense para Estándares de
Tecnología de la Información), Estándar X3 (el antiguo nombre de NCITS) y E s
tándar ANSI/ISO. Este libro seguirá refiriéndose al estándar ANSI debido a que
es el término más utilizado.
16 D ía 1
El estándar ANSI es un esfuerzo para asegurar que C+ + sea portable i|*>i e ie m p lo . garanti
zar que el código apegado al estándar ANSI que usted escriba para los c o m p i l a d o r e s (¡N I*
se compile sin errores en un compilador de cualquier otro fabricante, c o m o M ic io so tt > A d e
más, debido a que el código que se usa en este libro se ap ega al estándar A N S I , se d eb e
compilar sin errores en una Mac. en un equipo W indow s o en un cqui|x> A lph a
Lo primero que debe preguntarse cuando se prepare para diseñar cu a lq u ier p io g ra m a es.
“¿Cuál es el problema que estoy tratando de resolver?” Todo program a debe tener un obje
tivo claro y bien definido, y usted descubrirá que inclu so los p io g ra m a s m ás sim p le s de
este libro lo tienen.
La segunda pregunta que todo buen programador se hace es. ¿S e pu ed e lograi e sto sin
recurrir a la escritura de software personalizado?” R eutilizar un v iejo program a, usai plu
m ay papel (la manera antigua, original, manual y segura de hacei el trabajo) o com prar
software de algún establecimiento son por lo general una m ejor so lu c ió n para un proble
ma que escribir algo nuevo. El programador que pueda ofrecer estas alternativas nunca
sufrirá por la escasez de trabajo; buscar soluciones m enos costosas para los p io b le m a s de
hoy siempre generará nuevas oportunidades más adelante.
Dando por hecho que entiende el problema y que requiere escribir un p ro g ia m a n u ev o ,
ya está listo para empezar su diseño.
El proceso de entender completamente el problema (análisis) y de crear una so lu c ió n
(diseño) es el fundamento necesario para escribir una aplicación co m ercia l de prim era
clase. Aunque, lógicamente, estos pasos vienen antes de la co d ifica ció n (e s d e c ii, usted
C om encem os 17
Con GNU puede utilizar EMACS, vi o el editor de texto que prefiera. Si utiliza un compi
lador distinto, tendrá distintas opciones: su compilador puede tener su propio editor integra
do o puede utilizar un editor de texto comercial para producir archivos de texto. Lo que
cuenta es que, sin importar en dónde escriba su programa, éste debe tener capacidad para
guardar archivos de texto plano, sin comandos de procesamiento de palabras incrustados en
el texto. Algunos ejemplos de editores adecuados son el Bloc de notas (Notepad) de
Windows, el comando ed it de DOS, Brief, Epsilon, EMACS y vi. Muchos procesadores
comerciales de palabras, como WordPerfect, Word y otros más, incrustan caracteres espe
ciales pero también ofrecen un método para guardar archivos de texto plano, por lo que
debe asegurarse de que la manera en que guarda su archivo es la adecuada.
Los archivos que usted crea con su editor se llaman archivos de código fuente, y para
C++ por lo general se utiliza la extensión .cpp, .cp o .c. En este libro pusimos la exten
sión .cxx a todos los archivos de código fuente, debido a que los compiladores GNU
aceptan esto como código fuente de C++ en distintas plataformas. Si usted utiliza algo
diferente, revise qué extensiones necesita su compilador.
D ebe N O DEBE
D E B E utilizar un editor de texto plano N O D E B E utilizar u n p ro c e sa d o r d e p a
(como vi, EMACS o incluso el com ando labras q u e g u a r d e caracte res d e fo rm a to
edit de DOS) para crear su código fuente, especiales. Si va a utilizar u n p ro c e sa d o r
o utilizar el editor integrado que tenga d e palabras, g u a r d e el a rc h ivo c o m o
su compilador. texto ASC II.
D E B E guardar sus archivos con la exten
sión .cxx o .c++.
D E B E revisar el m anual del co m pilador
GNU para averiguar cómo co m pilar y
enlazar correctamente sus program as.
Para convertir su código fuente en programa, debe utilizar un com pilador. I .a m anera m ás
sencilla de invocar el compilador g++ es la siguiente:
9++ a rc h iv o .c + + -o a r c h i v o
En ambos casos debe reemplazar a r c h iv o con el nom bre que usted s e le c c io n e para su
programa. Se producirá un archivo ejecutable con el nom bre a r c h i v o o a r c h i v o , e x e . Si
omite la opción -o, se producirá un archivo llam ado a .o u t (L in u x ) o a . e x e (M S -D O S ),
respectivamente.
Si utiliza un compilador diferente, tendrá que revisar su d o cu m en ta ció n para determ inar
la forma de invocarlo y cómo indicarle en dónde encontrar su c ó d ig o fu en te (e sto varía
de un compilador a otro).
C om encem os 19
Los compiladores GNU invocan automáticamente al enlazador (conocido como Id) para
producir el archivo ejecutable.
El ciclo de desarrollo
Si todos los programas funcionaran la primera vez que se prueban, ese sería el ciclo com
pleto de desarrollo: escribir el programa, compilar el código fuente, enlazar el programa
y ejecutarlo. Desafortunadamente, casi cualquier programa, sin importar qué tan trivial sea,
puede tener y tendrá errores, o bugs. Algunos errores ocasionarán que el compilador falle,
otros, que el enlazador falle, y otros más aparecerán solamente cuando se ejecute el
programa.
Cualquiera que sea el tipo de error que se encuentre, debe arreglarlo, y para eso necesita
editar su código fuente, volver a compilar y enlazar, y luego volver a ejecutar el programa.
Este ciclo se representa en la figura 1.1, que muestra en un diagrama los pasos del ciclo
de desarrollo.
D ía 1
Fig u r a 1.1
Los pasos para el
desarrollo de un
programa de C+ +
Utilice el archivo del listado que viene en el CD-ROM; si opta por escribir usted mismo
este programa, asegúrese de escribirlo exactamente como se muestra. Ponga mucha aten
ción en la puntuación. El símbolo « de la línea 5 es el operador de inserción que, en los
teclados que tienen la distribución de teclas en español, se obtiene al oprimir la tecla <
que está a la izquierda de la “Z”. La línea 5 termina con un punto y coma (;). ¡No omita
este símbolo!
Para compilar y enlazar este programa con el compilador GNU en Linux, debe escribir lo
siguiente:
g++ lst01-01.cxx -o lst01-01
Si prefiere utilizar otro compilador, asegúrese de seguir correctamente las indicaciones.
La mayoría de los compiladores enlaza automáticamente, pero de todas formas revise su
documentación.
Día 1
Trate de ejecutar el programa l s t - 0 1 -01 ; debe aparecer en su pantalla lo sig u ien te:
¡Hola, mundo!
en lu ga r de las n u e v a s b ib lio te c a s e s t á n d a r
ll inelude <iostream>
Com encem os 23
Esto debe funcionar en todos los compiladores y tiene pocas desventajas. No obstante, s¡
usted prefiere utilizar las nuevas bibliotecas estándar, sólo necesita cambiar el código a
§ inelude <iostream>
y agregar la linea
using namespace std;
justo debajo de su lista de archivos de encabezado. El tema acerca del uso de namespace
se detalla en el día 17, "Espacios de nombres".
Ya sea que utilice o no archivos de encabezado estándar, el código que viene en este libro
se debe ejecutar sin necesidad de modificarlo. La principal diferencia entre las antiguas bi
bliotecas y la nueva biblioteca estándar es la biblioteca iostream (la cual se describe en el
día 16, "Flujos"). Ni siquiera estos cambios afectan el código del libro; los cambios son suti
les, complejos y están más allá del alcance de una introducción elemental.
Errores de compilación
Los errores en tiempo de com pilación pueden ocurrir por m u c h a s r a / o n e s |*»»i h> general
son el resultado de un error en la escritura o d e cualquier o l i o ihujuciio e r r o r u n i d ' crudo.
Los buenos compiladores (com o los de G N U ) no so lo le i n d i c a n q u e h i / o m a l . s i n o que
también le indican el lugar exacto del có d ig o en d o n d e c o m e t i ó el e r r o r , l o s m e io r e s
hasta le sugerirán un remedio!
1: U include <iostream.h>
2:
3: int main()
4: {
5: cout « "¡Hola, mundo!\n";
6: return 0;
Resumen
Al terminar este día debe tener una buena comprensión de la forma en que evolucionó el
lenguaje C++ y de los problemas para los que fue diseñado. Debe sentirse seguro de que
aprender C++ es la elección correcta para cualquiera que esté interesado en la programación
en estos tiempos. C++ proporciona las herramientas de la programación orientada a
objetos y el rendimiento de un lenguaje de bajo nivel, lo que hace que sea el lenguaje de
desarrollo preferido.
Hoy aprendió a escribir, compilar, enlazar y ejecutar su primer programa de C++. así
como el concepto del ciclo normal de desarrollo de un programa. También aprendió un
poco sobre los fundamentos de la programación orientada a objetos. Verá de nuevo estos
temas durante las siguientes tres semanas. En la semana adicional aprenderá temas avan
zados relacionados con la forma de trabajar con el conjunto de herramientas GNU y la
programación en Linux.
Preguntas y respuestas
P ¿Cuál es la diferencia entre un editor de texto y un procesador de palabras?
R Un editor de texto produce archivos que contienen texto plano. Un procesador de
palabras no requiere comandos para dar formato ni cualquier otro símbolo especial.
Los archivos de texto no tienen ajuste de línea automático, formato en negritas o
cursivas, etcétera.
P Si mi compilador tiene un editor integrado, ¿debo usarlo?
R Los compiladores GNU no tienen editores de texto integrados. Linux viene con vi
y EMACS. Casi todos los demás compiladores pueden compilar código producido
por cualquier editor de texto. Sin embargo, una de las ventajas de utilizar el editor de
texto integrado puede ser la capacidad para alternar rápidamente entre los pasos
de edición y de compilación del ciclo de desarrollo. Los compiladores sofisticados
incluyen un entorno de desarrollo completamente integrado, lo cual permite que el
programador tenga acceso a archivos de ayuda, que edite y com pile el código sin
tener que cambiar de herramienta y que resuelva errores de compilación y de enlace
sin tener que salir del entorno.
P ¿Puedo ignorar los mensajes de advertencia de mi com pilador?
R Muchos libros dicen que sí, pero mi consejo es que no. Desde el primer día, usted debe
formarse el hábito de tratar los mensajes de advertencias como errores. C++ utiliza el
compilador para advertirle cuando haga algo que tal vez no sea lo que usted pretende.
Preste atención a esas advertencias y haga lo necesario para que ya no aparezcan.
Las advertencias significan que el compilador puede crear un archivo ejecutable a
partir de su código fuente, ¡pero el compilador no cree que usted realmente quiera
hacer lo que escribió en el código!
26 O ía 1
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su co m p r e n sió n del m a
terial tratado, así com o ejercicios para que experim en te con lo que ha aprendido. Trate de
responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D. “R es
puestas a los cuestionarios y ejercicios”, y asegúrese de com p ren d er las resp u estas antes
de pasar al siguiente día.
Cuestionario
1. ¿Cuál es la diferencia entre un intérprete y un com p ilad or ?
2. ¿Cóm o com pila el cód igo fuente con su com pilador 7
3. ¿Para qué sirve el enlazador?
4. ¿Cuáles son los pasos del c iclo normal de desarrollo.7
Ejercicios
1. Vea el siguiente programa y, sin ejecutarlo, trate de determ inar lo que hace.
1: #include <iostream.h>
2: int main()
3: {
4: int x = 5;
5: int y = 7;
6: cout « "\n";
7: cout « x + y « " " « x * y;
8: cout « "\n";
9: return 0;
1 0 :}
2. Escriba el programa del ejercicio 1, y lu ego c o m p ílelo y e n lá c elo . ¿Q ué e s lo que
hace? ¿Hace lo que usted pensó?
C om e nce m os 27
4. Corrija el error del programa del ejercicio 3 y vuelva a compilar, enlazar y ejecutar
dicho programa. ¿Qué es lo que hace?
\
il
li
S em ana 1
D ía 2
Los componentes de un
programa de C++
Los programas de C++ constan de objetos, funciones, variables y otros compo
nentes. La mayor parte de este libro se dedica a explicar con detalle estos
componentes, pero para que usted pueda comprender cómo se integran todos
estos componentes en un programa, debe ver uno que funcione y este com ple
to. Hoy aprenderá lo siguiente:
• Los componentes de un programa de C++
• Cómo funcionan en conjunto esos componentes
• Qué es una función y qué hace
• Las opciones que se pueden utilizar en el compilador g++
Un programa sencillo
Hasta el sencillo programa “¡Hola, mundo!” del día l, “Comencemos”, tiene
muchas partes interesantes. En esta sección se examina este programa con más
detalle. Para su conveniencia, el listado 2.1 reproduce la versión original de
“¡Hola, mundo!”
L
30 D ía 2
l
1: linclude <iostream.h>
2:
3: int main()
4: {
5: cout « "¡Hola, mundo!\n*;
6: return 0;
7: >
S a l id a ¡Hola, mundo!
sistema operativo es una característica que en muchos sistemas operativos casi no tiene
importancia y se utiliza poco, pero en Linux (y en todos los sistemas UNIX) se utiliza
para conocer el estado con que finalizó un proceso o programa. El estándar de C++
requiere que main() sea declarada como se muestra.
Todas las funciones empiezan con una llave de apertura ({) y terminan con una llave de
cierre (}). Las llaves de la función main () se encuentran en las lineas 4 y 7. Todo lo que
está dentro de estas llaves se considera parte de la función.
El procesamiento principal de este programa está en la línea 5.
El objeto cout se utiliza para imprimir un mensaje en la pantalla. En el día 6, “Clases
base”, se trata el tema de los objetos en general, y en el día 16, “Flujos”, se ve con
detalle el tema relacionado con cout y su objeto relacionado cin . En C++, estos dos
objetos, c in y cout, se utilizan para manejar la entrada (por ejemplo, desde el teclado)
y la salida (por ejemplo, a la pantalla), respectivamente.
cout se utiliza de esta manera: se escribe la palabra cout, seguida del operador de
inserción(<<). Cualquier cosa que esté después del operador de inserción se escribe en
la pantalla. Si quiere escribir una cadena de caracteres, asegúrese de encerrarlos entre
comillas dobles (“), como se muestra en la línea 5.
Una cadena de texto es un conjunto de caracteres imprimibles.
Los dos últimos caracteres, \n, le indican a cout que debe insertar una nueva línea
después de las palabras “¡Hola, mundo!” Este código especial se explica con detalle al
hablar sobre cout en el día 17, “Espacios de nombres”.
Saludos a todos.
S a l id a Aqui hay un 5: 5
El manipulador endl escribe una nueva linea en la pantalla.
Aquí hay un número muy grande: 70000
Aquí está la suma de 8 y 5: 13
Aquí hay una fracción: 0.625
Y un número muy, muy grande: 4.9e+07
No olvide reemplazar Jesse Liberty con su nombre.
¡Jesse Liberty es un programador de C++!
Los com ponen te s de un p ro g ra m a de C + +
11: cout << 'Aquí está la suma de 8 y 5:\t’ << (8+5) << endl;
Comentarios
Al escribir un programa, siem pre es claro y e v id e n te l o qu e u n o trata d e hacer. ^ cs
gracioso que, un mes después, al volver a ver el programa, é ste pueda ser a lg o c o n t u s o c
incierto. En realidad no sé cóm o surge esta con fu sión en su program a, pero siem p re c s asi.
Para evitar cualquier confusión, y para ayudar a qu e otros en tien d an el c ó d ig o q u e usted
escribe, debe utilizar los com entarios. L os c o m e n ta r io s son tex to qu e el co m p ila d o r ig
nora, pero que puede informar al lector lo que usted está h a c ie n d o en algún punto e sp e
cífico del programa.
T ip o s d e c o m e n t a r io s
Los comentarios de C++ pueden ser de dos formas: el com entario co n d ob le barra diagonal
( / / ) , y el comentario con barra diagonal y asterisco ( / *)- El prim ero, que se c o n o c e c o m o
comentario estilo C++ (o comentario corto), le indica al com pilador que ignore todo lo que
esté después de las dos barras diagonales ( I I ) hasta el linal de la linea.
El com entario con barra diagonal y asterisco 1c ind ica al c o m p ila d o i que ign ore tod o lo
que esté después de la barra diagonal y el a sterisco ( r h hasta qu e en cu en tre una marca
de com entario con asterisco y barra diagonal (* /)• E stas m arcas se c o n o c e n c o m o c o
mentarios estilo C (o com entarios largos). T od os los /* d eb en tener un * / para cerrar el
comentario.
Como puede imaginar, los com entarios estilo C se utilizan tam bién en el len gu aje C,
pero los comentarios estilo C++ no son parte de la d e fin ic ió n o fic ia l de C.
Muchos programadores de C++ utilizan el com entario e stilo C + + la m ayor parte del
tiempo, y reservan los comentarios estilo C para apartar grandes b loq u es de un program a.
Puede incluir comentarios estilo C++ dentro de un bloque de co m en ta rio s e stilo C; tod o
lo que está entre las marcas de com entario estilo C, in clu y en d o lo s co m en ta rio s e stilo
C++, se ignora.
U so de c o m e n ta rio s
Como regla general, el programa debe tener com entarios al p rin cip io que indiquen lo que
hace. Asim ism o, cada función debe tener com entarios que ex p liq u en su fu n cio n a m ien to
y los valores que regresa. Estos com entarios se deben actualizar cada v e z que se hagan
cambios al programa. Cuando m enos se debe m antener un historial de cam b ios.
Los com pon entes de un p rog ram a de C++ 35
¡H o la , mundo!
¡E se c o m e n ta rio te rm in ó!
El caso es que los comentarios no deben decir q u é está pasando; deben decir p o r q u é
está pasando.
D ia 2
Funciones
A u n q u e m ain ( ) es u n a f u n c ió n , es a l g o in u s u a l. P a ra q u e u n a ( u n c ió n s e a ú til, se J e b e
llamar, o invocar, d u r a n te el c u r s o eleI p r o g r a m a n a i n ( ) es i n \ o c a d a poi el s i s t e m a
o p erativo.
1: ^include <iostream.h>
3: // función FuncionDeMuestra
4: // imprime un mensaje útil
5: void FuncionDeMuestra()
6: {
7: cout « "Estamos dentro de FuncionDeMuestra\n‘
8: }
9:
10: // función main - imprime un mensaje y luego
11 : // llama a FuncionDeMuestra, luego imprime
12: // un segundo mensaje.
13: int main()
14: {
15: cout « "Estamos dentro de main\n" ;
16: FuncionDeMuestra();
17: cout « "Estamos de regreso en main\n";
18: return 0;
19: >
i
Los c o m p o n e n te s de un p ro g ra m a de C + + 37 ¡
a
lugar donde fue llamada. En este caso, el program a regresa a la línea 17, en d o n d e
main() im prim e el último mensaje.
Uso de funciones
Las funciones regresan ya sea un valor o un tipo v o id (vacío), lo que significa q u e no
regresan ningún valor. Una función que realiza la sum a de dos enteros podría re g re s a r el
resultado de la sum a, por lo que se definiría para regresar un valor entero. U n a función
que sólo im prim e un mensaje no tiene nada que regresar, por lo que se d eclararía para
regresar v o id .
in t S u m a (in t a, in t b)
Un parám etro es una declaración del tipo de valor que se va a pasar; el v alo r real p a sa d o
p o r la función que hace la llam ada se conoce c o m o argum ento. M u c h o s p ro g ra m a d o re s
utilizan estos dos térm inos, parám etros y argum entos, c o m o sin ó n im o s. O tro s son c u id a
dosos en cuanto a su distinción técnica. En este libro se utilizan los dos té rm in o s in d is
tintam ente.
El cuerpo de una función consta de una llave de apertura, cero o m ás in stru c cio n es y u n a
llave de cierre. Las instrucciones son el trabajo que va a realizar la función. U n a fu n c ió n
puede regresar un valor por m edio de la instrucción r e t u r n . E sta instrucción ta m b ié n
hace que la función termine. Si no coloca una instrucción r e t u r n en su función, ésta
regresará autom áticam ente v o id (ningún valor) al final de la función. El v alo r r e g re s a d o
debe ser del tipo declarado en el encabezado de la función.
i Estoy en main()I
S a lida Escriba dos números: 3 5
Llamando a Suma()
En Suma(), se recibieron 3 y 5
De regreso en main().
c contiene el número 8
Saliendo...
La función Suma( ) se define en la línea 2. Tom a d os parám etros enteros y re
A nálisis
gresa un valor entero. El programa en sí em p ieza en las lín eas 9 y 1 I , en d o n d e
imprime un mensaje. El programa pide dos números al usuario (líneas 13 a 15). El usua
rio escribe los dos números, separados por un esp acio, y lu eg o oprim e Entrar . En la
línea 17, main( ) pasa los dos números escritos por el usuario c o m o argum entos para
la función Suma ().
Los com ponentes de un p ro g ra m a de C + + 39
o
info
Las dos secciones siguientes proporcionan información acerca de las opciones de línea
de comandos más comunes y algunos tips sobre el uso del compilador.
40 D ía 2
-w
Deshabilitar todos los mensajes de advertencia.__________________
Puede utilizar varias opciones en cada linea de com andos de co m p ila ció n . D eb e insertar
un espacio antes del com ien zo de cada opción (antes del sig n o de resta). N o d eb e dejar
espacios entre la opción y sus argumentos (com o entre -o y a r c h i v o , aunque no siem pre
es necesario).
Resumen
La dificultad para aprender un tema complejo, como la programación, está en que mucho
de lo que usted aprende depende de todo lo demás que hay por aprender. Este capítulo pre
sentó los componentes básicos de un programa sencillo de C++. También presentó el
ciclo de desarrollo y muchos términos nuevos importantes.
Preguntas y respuestas
P ¿Qué hace la directiva #include?
R Ésta es una directiva para el preprocesador, el cual se ejecuta cada vez que usted
llama al compilador. Esta directiva en especial ocasiona que se lea el archivo que
está después de la palabra inelude, como si se hubiera escrito en esa ubicación de
su código fuente.
P ¿Cuál es la diferencia entre los com entarios estilo // y los com entarios estilo
/* - * /?
R Los comentarios con doble barra diagonal (//) terminan al final de la línea. Los co
mentarios con barra diagonal y asterisco (/* ) terminan hasta donde se encuentre
una marca de cierre de comentario (*/). Recuerde, ni siquiera el fin de una función
termina un comentario con barra diagonal y asterisco; debe colocar la marca de
cierre de comentario, o se producirá un error en tiempo de compilación.
P ¿Cuál es la diferencia entre un buen comentario y un mal com entario?
R Un buen comentario le indica al lector por qué este código específico está haciendo
una tarea determinada, o le explica qué está por hacer una sección de código. Un
mal comentario vuelve a decir lo que está haciendo una línea específica de código.
Las líneas de código se deben escribir de forma que hablen por sí solas. Leer la
línea de código debería indicarle lo que éste hace sin necesitar un comentario.
42 D ía 2
Taller
El taller le proporciona un cuestionario para ayudarlo a a f i a n / a r su c o m p r e n s i ó n del ma
terial tratado, así com o ejercicios para que experimente c o n lo q u e ha a p r e n d i d o . Trate de
responder el cuestionario y los ejercicios antes de ver las r e s p u e s t a s e n el a p é n d i c e 1).
“Respuestas a los cuestionarios y ejercicios*’, y as e g ú r e s e d e c o m p r e n d e r las r e s p u e s ta s
antes de pasar al siguiente día.
Cuestionario
1. ¿Cuál es la diferencia entre el compilador y el p r e p r o c e s a d o r ?
2. ¿Por qué es especial la función m ain()?
3. ¿Cuáles son los dos tipos de comentarios, y en qué se diferencian?
4. ¿Se pueden anidar los comentarios?
5. ¿Pueden los comentarios ser de más de una línea?
Ejercicios
1. Escriba un programa que imprima en la pantalla el m ensaje “M e gusta C + + “ .
2. Escriba el programa más pequeño que se pueda compilar, enlazar y ejecutar.
3. C A Z A E R R O R E S: Escriba el siguiente programa y com p ílelo. ¿Por qué falla?
¿C óm o puede arreglarlo?
1 : #include <iostream.h>
2: int m a in()
3: {
4: cout « ¿Hay un error aquí?-;
5: return 0;
6: }
4. Encuentre el error del ejercicio 3 y vuelva a compilar, enlazar y ejecutar el programa.
S e m a n a 1
D ía
Variables y constantes
Los programas necesitan una manera de guardar la información que utilizan.
Las variables y constantes ofrecen varias maneras de representar y manipular
esa información.
Hoy aprenderá lo siguiente:
• Cómo declarar y definir variables y constantes
• Cómo asignar valores a las variables y manipular esos valores
- Cómo escribir en la pantalla el valor de una variable
R A M s ig n if ic a m e m o r ia d e a c c e s o a le a t o r i o . A l e j e c u t a r u n p r o g r a m a , é s t e se
c a r g a e n R A M d e s d e el a r c h iv o e n d is c o . T o d a s la s v a r i a b l e s se c r e a n t a m b i é n
e n R A M . C u a n d o lo s p r o g r a m a d o r e s h a b la n s o b r e la m e m o r ia , p o r lo g e n e r a l
se r e fie r e n a la R A M .
Las variables también se conocen com o va lo res-i (l-value), debido a que se pueden uti
lizar del lado izquierdo de un operador de asignación. El operador de asign ación es el
signo de igual (en la lección de hoy verá más acerca del operador de asign ación , en la
sección “Cómo asignar valores a sus variables”, y en el día 4, “E xpresiones e instruc
ciones”, en la sección “Expresiones”). Las variables tam bién se pueden utilizar del lado
derecho del operador de asignación.
Una variable de tipo char (utilizada para guardar caracteres) es, por lo general, de 1 byte
de longitud.
Un carácter es una sola letra, número o símbolo que ocupa un byte de memoria.
en su c o m Putadora
1: #include <iostream.h>
2:
3: int main()
4: {
5: cout « "El tamaño « sizeof(int)
k* « " bytes.\n" i
6: cout « "El tamaño « sizeof(short)
k* « " bytes.\n";
7: cout « "El tamaño « sizeof(long)
k» « " bytes.\n";
8: cout « "El tamaño « sizeof(char)
k» « " bytes.\n";
9: cout « "El tamaño
k» « " bytes.\n";
continúa
L
t 46 D ía 3
Listado 3 .1 continuación
La mayor parte del listado 3.1 debe parecerle bastante fam iliar. La nueva carac
A nálisis
terística es el uso de la función sizeof () en las lín eas 5 a 1 1. Esta fu nción
es proporcionada por su compilador, y le indica el tamaño del objeto que se pasa com o
parámetro. Por ejem plo, en la línea 5 la palabra reservada int se pasa a sizeof ( ) . Por
m edio de sizeof () yo pude determinar que en mi com putadora un entero tiene la m ism a
longitud que un entero largo, que es de 4 bytes.
Las variables de punto flotante tienen valores que se pueden expresar como decimales
(es decir, son números racionales). Las variables tipo carácter almacenan un solo byte y
se utilizan para guardar los 256 caracteres y símbolos de los conjuntos de caracteres
ASCII y ASCII extendido.
El conjunto de caracteres ASCII es el conjunto de caracteres estandarizado para ser utiliza
do en las computadoras. ASCII es el acrónimo en inglés de Código Estándar Estadouniden
se para el Intercambio de Información. Casi cualquier sistema operativo de computadora
soporta este conjunto de caracteres, aunque muchos también soportan otros conjuntos de
caracteres internacionales.
Los tipos de variables utilizados en los programas de C++ se describen en la tabla 3.1.
Esta tabla muestra el tipo de variable, cuánto espacio asume este libro que ocupa en me
moria, y qué tipos de valores se pueden guardar en estas variables. Los valores que se
pueden guardar se determinan según el tamaño de los tipos de variables, por lo que ne
cesita comparar la salida que obtenga al ejecutar el programa del listado 3.1 con lo que
viene en este libro.
Los t a m a ñ o s d e las v a r ia b le s p u e d e n se r d is t in t o s d e lo s q u e se m u e s t r a n e n la
ta b la 3.1, d e p e n d ie n d o d e l c o m p i la d o r y d e la c o m p u t a d o r a q u e e s te u t i liz a n
d o . Si a l e je c u ta r el p r o g r a m a d e l lis t a d o 3.1 e n s u c o m p u t a d o r a la s a li d a
m u e s t r a lo s m is m o s v a lo r e s q u e el lib ro , e n t o n c e s la t a b la 3.1 se a p lic a a su
c o m p ila d o r y a su e q u ip o . Si la s a lid a q u e o b t u v o e s d is t i n t a d e la q u e se
m u e s tra e n el lis t a d o 3.1. e n t o n c e s d e b e c o n s u lt a r la s p á g i n a s d e l m a n u a l p a r a
s a b e r lo s v a lo r e s q u e su s t ip o s d e v a r ia b le s p u e d e n a lm a c e n a r e n su s is t e m a .
O t r o lu g a r e n el q u e p u e d e b u s c a r e s el a r c h iv o d e e n c a b e z a d o 1 í m i t s . h.
/
A l d e f in ir o d e c la r a r u n a v a ria b le , se a s ig n a u n e s p a c i o e n m e m o r i a (se r e s e r
Nota v a ) p a r a e sa v a r ia b le . El valor d e la v a r ia b le se r á lo q u e e s t a b a e n e s e e s p a
c io d e m e m o r ia al m o m e n t o d e d e c la r a r la v a r ia b le . E n u n m o m e n t o v e r á
c ó m o a s ig n a r u n n u e v o v a lo r a e se e s p a c io d e m e m o r ia .
L a s e s t r u c t u r a s y las c lase s d e o b je t o s se c o m p o r t a n d e u n a f o r m a u n p o c o
d is t in t a a las v a r ia b le s n o rm a le s . A p r e n d e r á e sa d if e r e n c ia c u a n d o l l e g u e a
la le c c ió n d e e se d ía . P u e d e d e fin ir o d e c la r a r u n a c la s e d e o b j e t o , p e r o n o
p u e d e u t iliz a r m e m o r ia . El e s p a c io d e m e m o r ia se a s i g n a a l m o m e n t o d e
c r e a r el o b je t o .
Pruebe este experimento. Adivine lo que hacen estas piezas de código, basándose en las
primeras líneas:
Ejemplo 1
int main()
{
unsigned short x;
unsigned short y;
unsigned short z;
z = x • y;
return 0;
Ejemplo 2
int main()
{
unsigned short Ancho;
unsigned short Longitud;
unsigned short Area;
Area = Ancho * Longitud;
return 0;
}
Existen varias convenciones para nombrar variables, y aunque no importa mucho cuál
método utilice, es importante ser consistente en todo el programa.
50 D ía 3
Muchos programadores prefieren utilizar sólo letras en m inúscula para los nom bres de
sus variables. Si el nombre requiere dos palabras (por ejem plo, mi carro), se utili/nn dos
convenciones populares: m ic a r r o o miCarro. La última se co n o ce co m o n<>un to n d e
c a m e llo debido a que el uso de m ayúsculas se parece a la joroba de un cam ello.
Algunas personas sienten que es más fácil leer el carácter de guión bajo (mi c a r r o ), pero
otras prefieren evitarlo por que piensan que es más dif ícil escribirlo, fin este libro se uti
liza la notación de cam ello, en la que la primera letra de la segunda palabra. \ de todas
las subsecuentes, se escribe con mayúscula: miCarro. elZ o rr o C o lo rC a f e. etcétera.
M u c h o s p r o g r a m a d o r e s a v a n z a d o s e m p l e a n u n e s t il o d e n o t a c i ó n q u e se
c o n o c e c o m ú n m e n t e c o m o n o ta c ió n h ú n g a ra . El o b j e t i v o d e la n o t a c i ó n
h ú n g a r a es p o n e r u n p r e fijo (u n c o n j u n t o d e c a r a c t e r e s ) a las v a r i a b l e s q u e
d e s c r i b a s u tip o . Las v a r ia b le s d e t i p o e n t e r o p o d r í a n e m p e z a r c o n u n a letra
i m i n ú s c u la , y lo s e n t e r o s la r g o s p o d r í a n e m p e z a r c o n u n a I m i n ú s c u l a . P ara
las c o n s t a n t e s , v a r ia b l e s g lo b a le s , a p u n t a d o r e s , e tc é te ra , se u t i l i z a n o t r a s
n o t a c io n e s . La m a y o r p a rt e d e e s t o es m u c h o m á s i m p o r t a n t e e n la p r o g r a
m a c i ó n e n C, d e b i d o a q u e C + + s o p o r t a la c r e a c i ó n d e t i p o s d e f i n i d o s p o r el
u s u a r i o (v e a el d ía 6, "C la s e s b a s e ") , y a q u e t i e n e m u c h o s tip os.
P a la b ra s re se rv a d a s
A lgunas palabras están reservadas para C++, y no se pueden utilizar co m o nom bres de
variables. Otro nombre para las p a la b r a s r e s e r v a d a s es palabras clave. Estas palabras son
utilizadas por el com pilador para controlar el programa. A lgu n as palabras reservadas
son i f , w h ile , f o r y main. El manual del compilador debe proporcionar una lista com
pleta. pero por lo general es muy poco probable que cualquier nom bre razonable para
una variable sea una palabra reservada. En el apéndice B, “Palabras reservadas de C ++”,
hay una lista de palabras reservadas de C++.
D e b e N O DEBE
D E B E d e f i n i r u n a v a r i a b l e e s c r ib ie n d o el N O D E B E u tiliza r p a la b r a s re s e rv a d a s de
t i p o y l u e g o el n o m b r e d e la variable. C + + c o m o n o m b r e s d e v a r ia b le s .
D E B E u t iliz a r n o m b r e s d e v a ria b le s s i g n i N O D E B E u t i liz a r v a r i a b l e s sin s i g n o p a r a
ficativos. n ú m e r o s n e g a tiv o s.
D E B E r e c o r d a r q u e C + + es se n s ib le al u s o
d e m a y ú s c u l a s y m i n ú s c u la s .
D E B E e n t e n d e r el n ú m e r o d e b y te s q u e
o c u p a e n m e m o r i a c a d a t i p o d e variable,
así c o m o los v a l o r e s q u e se p u e d e n
g u a r d a r e n c a d a t i p o d e variable.
Variables y constantes 51
Como puede ver, miEdad y miPeso se declaran como variables de tipo entero sin signo.
La segunda línea declara tres variables de tipo entero largo individuales llamadas area,
ancho y longitud. El tipo (long) se asigna a todas las variables, por lo que no se pueden
mezclar tipos en una instrucción de definición.
lo n g es u n a v e rsió n a b re v ia d a d e lo n g i n t (e n te ro la rg o ), y s h o r t es u n a
Nota v e rsió n a b r e v ia d a d e s h o r t i n t (e n te ro corto).
ntPUOTECÁ RAOtftWL
PF MAESTROS
Día 3
i 52
Ancho:5
S a l id a Longitud: 10
Area: 50
Uso de typedef
Escribir unsigned short int muchas veces puede ser tedioso, repetitivo y. lo que es
peor, puede propiciar errores. C++ le permite crear un alias para esta frase mediante el
uso de la palabra reservada typedef. que significa definición de tipo.
crea el nuevo nombre USHORT que usted puede utilizar en cualquier parte en la que nece 3
site escribir unsigned short int. El listado 3.3 es una reproducción del listado 3.2.
sólo que se utiliza la definición de tipo USHORT en lugar de unsigned short int.
Entrada L is t a d o 3 . 3 U n a m u e stra de t y p e d e f
■|. jj *****************
2: // M uestra de l a p a la b ra reservada typedef
3: //inelude <io stre a m .h >
4:
5: ty p e d ef un sign ed s h o r t i n t USHORT; //typedef d e f i n i d o
6:
7: i n t m a in ()
8: {
9: USHORT Ancho = 5;
10: USHORT Lo n gitu d ;
11: L o n g itu d = 10;
12: USHORT Area = Ancho * Longitud;
13: cout << "A n ch o :" « Ancho « " \ n " ;
14: cout << "L o n g it u d : " « Longitud « endl;
15: cout << "A rea: " « Area « e n d l ;
16: r e t u rn 0;
17: }
Ancho:5
S alida L o n g itu d : 10
Area: 50
A nálisis En la línea 5, typedef define a USHORT como sinónimo de unsigned short int.
El programa es m u y parecido al del listado 3.2, y la salida es la misma.
D ía 3
LE I
1: #include <iostream.h>
2: int main()
3: {
4: unsigned short int numeroChico;
5: numeroChico = 65535;
6: cout « "número chico:" « numeroChico << endl;
7: numeroChico++;
8: cout « "número chico:" « numeroChico « endl;
9: numeroChico++;
10: cout « “número chico:" « numeroChico « endl;
11: return 0;
12: }
i
Variables y constantes
numero c h ic o :6 5 5 3 5
S alida numero c h i c o : 0
numero c h i c o : 1
número c h ic o :3 2 7 6 7
S a lid a número c h i c o :-32768
número c h i c o : -32767
56 Día 3
L a s c o m p u t a d o r a s n o c o n o c e n letras, p u n t u a c i ó n n i o r a c i o n e s . T o d o lo q u e
Nota e n t i e n d e n s o n n ú m e ro s . D e h e c h o , t o d o lo q u e s a b e n e s si h a y u n a c a n t i d a d
s u fic ie n t e d e e n e r g ía e n u n a u n ió n e s p e c ífic a d e c a b le s . Si e s a si, e s t o se r e
p r e s e n t a in t e r n a m e n t e c o m o 1; si n o , se r e p r e s e n t a c o m o 0. M e d i a n t e la
a g r u p a c i ó n d e c e ro s y u n o s, la c o m p u t a d o r a e s c a p a z d e g e n e r a r p a t r o n e s
q u e se p u e d a n in te r p re ta r c o m o n ú m e r o s , y é s t o s a s u v e z s e p u e d e n a s i g n a r
a le tr a s y s ig n o s d e p u n tu a c ió n .
S u p r o g r a m a t a m b ié n se a lm a c e n a e n m e m o r ia c o m o u n c o n j u n t o d e c e r o s y
u n o s ; la c o m p u t a d o r a s a b e c ó m o in t e r p r e t a r lo s a p r o p i a d a m e n t e .
En el cód igo ASCII, la letra “a” minúscula tiene el valor 9 7 . Todas las letras m ayúsculas
y m inúsculas, todos los números y signos de puntuación tienen valores a sig n a d o s entre
0 y 127. S e reservan 128 signos y sím bolos adicionales para el fabricante de c o m p u
tadoras, aunque el conjunto de caracteres extendido de IBM casi se ha co n v ertid o en un
estándar.
Linux está basado en ASCII; los sistemas operativos más antiguos (com o los
Nota de mainframes IB M y otros) utilizan el conjunto de caracteres EBCDIC
(Código Extendido de Caracteres Decimales Codificados en Binario para el
Intercambio de Información), el cual está relacionado con la form a en que se
perforan los agujeros de las tarjetas Hollerith (un m étodo para introducir
datos que espero usted nunca tenga que ver, excepto en un museo).
La relación valor/letra es arbitraria; no hay una razón específica para que la letra “a”
tenga asignado el valor 97. Mientras todos (su teclado, compilador y pantalla) estén de
acuerdo, no hay problema. No obstante, es importante tener en cuenta que existe una
gran diferencia entre el valor 5 y el carácter “5”. Este último tiene en realidad el valor
53, así como la letra “a" tiene el valor 97.
El listado 3.6 muestra que los caracteres están guardados como números en la memoria.
1: ^include <iostream.h>
2: int main()
3: {
4: for (int i = 32; i<128; i++)
5: cout « (char) i;
6: return 0;
7: }
!"#$%’()*+,,/0123456789:;<>?@ABCDEFGHIJKLMNOP
S a lida _QRSTUVWXYZ[\]*_'abcdefghij klmnopqrstuvwxyzf|}~
Este programa sencillo imprime los valores de los caracteres para los enteros del
A nálisis
32 al 127.
En el día 12, “Arreglos, cadenas tipo C y listas enlazadas”, se proporciona más informa
ción sobre la forma de combinar caracteres en arreglos y cadenas para formar cosas tales
como palabras.
D ía 3
L£!
Este ejemplo declara una variable char (caracterTab) y la m ic ia li/a con el valor de tipo
carácter \t, el cual se reconoce com o labulador. Los caracteres de im presión e sp ec ia le s se
utilizan para imprimir en la pantalla, en un archivo o en cu a k |u ier otro d is p o s itiv o de
salida.
Un carácter de escape cam bia el significado del carácter que lo sigu e. Por ejem p lo , nor
malmente el carácter n representa la letra n, pero cuando se pone d esp u és del carácter de
escape (\), representa una nueva línea.
Uso de constantes
Al igual que las variables, las constantes son lugares para guardar datos. A d iferen cia de
las variables, y com o el nombre lo indica, las constantes no cam bian. D eb e inicializar
una constante al crearla, y no puede asignar un nuevo valor después.
Constantes literales
C++ tiene dos tipos de constantes: literales y sim bólicas.
miEdad es una variable de tipo int; 39 es una constante literal. No puede asignar un
valor a 39. y no puede cambiar su valor. Es un valor-d (r-value) debido a que sólo puede
aparecer del lado derecho de una instrucción de asignación.
Constantes simbólicas
Una constante simbólica es una constante que está representada por un nombre, así como
se representa a una variable. Sin embargo, a diferencia de una variable, después de ini-
cializar una constante no se puede cambiar su valor.
Si su programa tiene una variable de tipo entero llamada e stu d ia n te s y otra llamada
c la s e s , usted puede calcular la cantidad de estudiantes, dado un número conocido de
clases, si sabe que cada clase consta de 15 estudiantes:
estudiantes = clases * 15;
El s ím b o lo * d e n o ta u n a m u ltiplicación.
En este ejemplo, 15 es una constante literal. Su código sería más fácil de leer y de man
tener si substituye una constante simbólica por este valor:
estudiantes = clases * estudiantesPorClase
Si después decidiera cambiar el número de estudiantes que hay en cada clase, podría ha
cerlo en donde está definida la constante estudiantesPorClase sin tener que hacer un
cambio en cada lugar en que utilizó ese valor.
Hay dos formas de declarar una constante simbólica de C++. La forma antigua, tradicio
nal y ya obsoleta es usando la directiva #define del preprocesador, y la otra utilizando
la palabra reservada const.
#define estudiantesPorClase 15
Observe que estudiantesPorClase no es de ningún tipo especifico (int, char, etcétera).
#def ine hace una simple substitución de texto. Cada vez que el preprocesador vea la
palabra estudiantesPorClase en el código, la substituirá por el número 15.
Debido a que el preprocesador se ejecuta antes que el compilador, éste nunca verá la
constante; verá el número 15.
60 D ía 3
Sólo porque esta manera de definir constantes es \ leja > o b so leta no sig n ifica que usted
no deba comprenderla; muchos programadores crecieron m ili/.n u lo esta d irectiva. y exis
ten muchas líneas de cód igo que la utilizan.
D ebe N O DEBE
DEBE vig ilar que los números no sobre NO DEBE u tiliz a r el té rm in o int. U tilice
pasen el tam año del tipo entero y se short y long para d e ja r claro el ta m a ñ o
establezcan en valores incorrectos. del entero q ue desea u tilizar.
DEBE dar a sus variables nombres signi NO DEBE u tiliz a r p a la b ra s reservadas
ficativos que reflejen su uso. com o no m bres de v ariab le s.
La sintaxis para constantes enumeradas es escribir la palabra reservada enum, segu ida por
el nombre del tipo, una llave de apertura, cada uno de los valores válid os separados por
comas y, finalmente, una llave de cierre y un punto y com a. He aquí un ejem plo:
enum COLOR { ROJO, AZUL, VERDE, BLANCO, NEGRO };
1. Hace que COLOR sea el nombre de una enumeración, es decir, un tipo nuevo.
Variables y constantes 61
2. Hace que ROJO sea una constante simbólica de valor 0. AZUL una constante simbó
lica de valor 1. VERDE una constante simbólica de valor 2. y así sucesivamente.
Cada constante enumerada tiene un valor entero. Si no lo especifica de otra manera, la
primera constante tendrá el valor 0. y a cada constante subsecuente se le irá asignando
un valor igual a la constante anterior más uno. No obstante, cualquiera de las constantes
puede ser iniciali/ada con un valor específico, y las que no sean inicializadas tendrán un
valor igual al de la constante anterior más uno. Por lo tanto, si escribe:
enum Color { ROJO=100, AZUL, VERDE=500, BLANCO, NEGRO=700 };
entonces ROJO tendrá el valor 100: AZUL el valor 101; VERDE el valor 500; BLANCO el valor
501; y NEGRO el valor 700.
Puede definir variables de tipo COLOR, pero sólo les puede asignar uno de los valores
enumerados (en este caso, ROJO. AZUL. VERDE, BLANCO o NEGRO, o puede ser también 100,
101, 500, 501 o 700). Puede asignar cualquier valor de color a su variable COLOR. De
hecho, puede asignar cualquier valor entero, incluso si no es un color válido, aunque un
buen compilador mostrará un mensaje de advertencia si trata de hacer eso. Es importante
tener en cuenta que las variables enumeradoras en realidad son de tipo unsigned in t , y
que las constantes enumeradas son iguales a las variables de tipo entero. Sin embargo, es
muy conveniente poder nombrar estos valores al trabajar con colores, días de la semana
o conjuntos similares de valores.
Usted no crea un nuevo tipo de datos con las variables enumeradas, sólo oculta los deta
lles de implementación. Como programador, usted utiliza palabras como ROJO o AZUL,
pero el compilador sustituye los números por esas palabras en forma transparente para
usted. Este proceso hace que la creación y la comprensión del programa sean mucho más
sencillas.
El listado 3.7 presenta un programa que utiliza un tipo enumerado.
1: tfinclude <iostream.h>
2: int main()
3: {
4: enum Dias { Domingo, Lunes, Martes,
5: Miércoles, Jueves, Viernes, Sabado };
6: int opcion;
7: cout « "Escriba un dia (0-6): ";
8: cin » opcion;
9: if (opcion == Domingo || opcion == Sabado)
10: cout « "\niYa se le agotaron los fines de semanal\n";
11: else
12: cout « "\nEstá bien, incluiré un dia de descanso.\n";
13: return 0;
14: }
Día 3
í 62
1: #include <iostream.h>
2: int main()
3: {
4: const int Domingo = 0;
5: const int Lunes = 1;
6: const int Martes = 2;
7: const int Miércoles = 3;
8: const int Jueves = 4;
9: const int Viernes = 5;
10: const int Sabado = 6;
11:
12: int opcion;
13: cout « "Escriba un dia (0-6): ";
14: cin » opcion;
15:
16: if (opcion == Domingo || opcion == Sabado)
17: cout « "\niYa se le agotaron los fines de semana!\n";
Variables y constantes
18: e lse
19: co ut << " \ n E s t á b ie n , i n c l u i r é un d i a de d e s c a n s o . \ n ' ;
20:
21: r e t u r n 0;
22: }
E s c r i b a un d i a ( 0 - 6 ) : 6
S alida ¡Ya se le a g o t a r o n l o s f i n e s de semana!
A nálisis La salida de este listado es idéntica a la que se m uestra en el listado 3.7. Aquí,
cada una de las constantes (Dom ingo. L u n e s, etc.) se definió en form a explícita, y
no existe el tipo enum erado D IA S . Las constantes enum eradas tienen la ventaja de d ocu
mentarse por sí mismas (la intención del tipo enumerado D I A S se identifica de inmediato).
3
R e su m e n
En la lección de hoy se habló sobre las variables y constantes numéricas y de tipo carác
ter utilizadas en C++ para guardar información durante la ejecución de su programa. Las
variables numéricas pueden ser de tipo entero (char. int. short int y long int ) o de
punto flotante (f loat y double). Las variables numéricas también pueden ser con signo
(signed) o sin signo (unsigned). Aunque todos los tipos pueden ser de varios tamaños en
distintas computadoras, el tipo especifica un tamaño exacto en cualquier computadora.
Debe declarar una variable antes de poder utilizarla, y luego debe guardar el tipo de datos
correcto en esa variable. Si coloca un número demasiado grande en una variable de tipo
entero, ésta no podrá almacenarlo completamente y producirá un resultado incorrecto.
En esta lección también se habló sobre las constantes literales y simbólicas, así como las
constantes enumeradas, y se mostraron dos maneras de declarar una constante simbólica:
usando #def in e y usando la palabra reservada const.
Preguntas y respuestas
I* Si una variable cíe tipo s h o r t i n t se puede quedar sin espacio y sobregirarse,
¿por qué no utilizar siem pre enteros largos?
R Tanto los enteros largos como los cortos se pueden quedar sin espacio y sobregirar
se, pero un entero largo lo hará con un número mucho más grande. Por ejemplo, una
variable de tipo unsigned short int se sobregirará después de 65,535, mientras
que una de tipo long int lo hará hasta llegar a 4,294,967,295. Sin embargo, en la
mayoría de los equipos, un entero largo ocupa hasta el doble de memoria cada vez
que usted declara uno (4 bytes en comparación con 2 bytes para el entero corto), y
un programa con 100 de esas variables consumirá 200 bytes adicionales de RAM.
Francamente, esto no llega a ser un problema en la actualidad, pues la mayoría de
[6 4 D ía 3
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del
material tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D.
“Respuestas a los cuestionarios y ejercicios", y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Cuál es la diferencia entre una variable de tipo entero y una de punto notante?
2. ¿Cuáles son las diferencias entre un entero corto sin signo y un entero largo?
3. ¿Cuáles son las ventajas de usar una constante simbólica en lugar de una constante
literal?
4. ¿Cuáles son las ventajas de usar la palabra reservada const en lugar de #def ine?
5. ¿Qué hace que el nombre de una variable sea bueno o malo?
6. Dado el siguiente enum, ¿cuál es el valor de AZUL?
enum COLOR { BLANCO, NEGRO = 100, ROJO, AZUL, VERDE = 300 };
7. ¿Cuáles de los siguientes nombres de variables son buenos, cuáles son malos y
cuáles no son válidos?
a. Edad
b. lex
c. R79J
d. IngresoTotal
e. Invalido
Ejercicios
1. ¿Cuál seria el tipo de variable correcto para guardar la siguiente información?
a. Su edad.
b. El área de su patio.
c. El número de estrellas de la galaxia.
d. La cantidad promedio de lluvia para el mes de enero.
2. Cree nombres buenos de variables para la información de la pregunta 1.
3. Declare una constante para pi como 3.14159.
4. Declare una variable de tipo float e iniciahcela usando su constante pi.
"SS
if'cjj
Vi!'!'’
))
r;
ì.
i
$
S em ana 1
D ía 4
Expresiones
e instrucciones
Básicamente, un programa es un conjunto de comandos ejecutados en secuencia.
El poder de un programa viene de su capacidad para ejecutar uno u otro con
junto de comandos, dependiendo de si una condición específica es verdadera o
falsa. Hoy verá lo siguiente:
• Qué son las instrucciones
• Qué son los bloques
• Qué son las expresiones
• Cómo ramificar el código con base en ciertas condiciones
• Qué es la verdad, y cómo actuar con base en ella
Instrucciones
En C++, una instrucción controla la secuencia de la ejecución, evalúa una
expresión, o no hace nada (la instrucción nu il). Todas las instrucciones de C++
terminan con punto y coma, incluso la instrucción n u il, la cual consta única-
[68 Día 4
Espacio en blanco
Por lo general, el espacio en blanco (que se crea por m e d i o d e f a b u l a d o r e s , e s p a c i o s y
caracteres de nueva línea) se ignora en las in s tr uc ci o n es I .a instrucción d e asignación
descrita anteriormente se podría escribir de la si g u ie n te m a n e r a :
x=a+b;
o
x =a
+ b ;
Aunque la última variación es perfectamente válida, tam bién es p erfectam en te confusa.
Puede utilizar el espacio en blanco para hacer que sus program as sean m ás le g ib le s y
fáciles de mantener, o lo puede usar para crear có d ig o horrendo e in d escifrab le. Aquí,
com o en todas las cosas, C ++ proporciona el poder; usted pone la sen satez.
Los caracteres de espacio en blanco (espacios, fabuladores y caracteres de n u eva línea)
no se pueden ver. Si estos caracteres se imprimen, se verá s ó lo lo blan co del papel.
D ebe
DEBE utilizar una llave de cierre siempre
que tenga una llave de apertura.
DEBE terminar sus instrucciones con
punto y coma.
DEBE utilizar el espacio en blanco con sen
satez para que su código sea más claro.
Las instrucciones compuestas son muy importantes, como verá al llegar a las secciones
que hablan sobre las instrucciones i f / e l s e . f o r . w h i l e y d o / w h i l e . Estas instrucciones
sólo pueden tener una instrucción después de ellas (como parte de los resultados ver
daderos o falsos de i f / e l s e o del cuerpo de los ciclos f o r . w h i l e y d o / w h i l e ) . Para
poder colocar más de una instrucción después de cualquiera de estas instrucciones, nece
sita utilizar un bloque (que cuenta como una sola instrucción) para que contenga tantas
instrucciones como sea necesario. Aprenderá más sobre esto en la sección titulada **La
instrucción i f ” de esta lección.
Expresiones
En C++. cualquier cosa que tenga un valor es una expresión. Se dice que una expresión
regresa un valor. Por lo tanto, la instrucción 3+2; regresa el valor 5, por lo que se consi
dera una expresión. Todas las expresiones son instrucciones.
Podría sorprenderse por la cantidad de piezas de código que califican como expresiones.
He aquí algunos ejemplos:
3. 2 // r egr e sa e l v a l o r 3.2
PI // constante de t i p o f l o a t que r e g r e s a e l v a l o r 3 . 1 4
La expresión compleja
x = a + b;
70 Día 4
no sólo suma a y b y asigna el resultado a x, sino que uunbicn regí es.» el \a lo r de esa
asignación (el valor de x). Por lo tanto, este resultado tam b ién es una expresión. Debido
a que es una expresión, puede estar en el lado derecho de un o p e ra d o r de asignación;
y = x = a + b;
1: //include <iostream.h>
2: int main()
3: {
4: int a=0, b=0, x=0, y=35;
5: cout « “a: " « a « “ b: " « b;
6: cout « “ x: " « x « " y: “ << y << endl ;
7: a = 9;
8: b = 7;
9: y = x = a+b;
10: cout « "a: " « a « " b: “ « b;
11 : cout « " x: " « x « " y: " << y << endl;
12: return 0;
13: }
i a : 0 b: 0 x: 0 y: 35
a: 9 b: 7 x: 16 y : 16
Operadores
Un o p e ra d o r es un sím bolo que hace que el compilador realice una acción. Los operado
res actúan sobre los operandos. y en C++ todos los operandos son expresiones. En C ++
existen varias categorías de operadores. Dos de estas categorías son las siguientes:
• Operadores de asignación
• Operadores matemáticos
Operador de asignación
El operador de asignación (=) hace que el operando de su lado izquierdo cam bie su valor
por el que se encuentra de su lado derecho. La siguiente expresión
x = a + b;
le asigna al operando x el valor del resultado de sumar a y b.
Un operando que puede estar de forma válida del lado izquierdo de un operador de asig
nación se con oce com o valor-i (lvalue). Lo que puede estar del lado derecho se conoce
co m o (sí, adivinó) valor-d (rvalue).
Las constantes son valores-d. N o pueden ser valores-i. Por lo tanto, puede escribir
x = 35; // correcto
Es importante que recuerde esto, así es que vamos a repetirlo: Un valor-i es un operando
que puede estar del lado izquierdo de una expresión. Un valor-d es un operando que pue
de estar del lado derecho de una expresión. Observe que todos los valores-i son valores-d,
pero no todos los valores-d son valores-i. Un ejemplo de un valor-d que no es valor-i es
una literal. U sted puede escribir x = 5;, pero no puede escribir 5 = x; (x puede ser un
valor-i o un valor-d, mientras que 5 sólo puede ser un valor-d).
Operadores matemáticos
L os cin co principales operadores matemáticos son: suma (+), resta (-), m ultiplicación
(*), d ivisión ( / ) y residuo (%).
La sum a y la resta funcionan com o suma y resta normales, aunque la resta con enteros
sin sign o puede producir resultados inesperados si se utilizan números negativos. Ayer
vio algo muy similar a esto, al hablar sobre los desbordamientos de \ a n u b l e s . 1:1 listado
4 .2 muestra lo que ocurre al restar un número grande sin signo a un numero pequeño
sin signo.
1: // L i s t a d o 4 . 2 ■ muestra l a r e s t a y
2 : // e l d e s b o r d a m i e n t o de e n t e r o s
3: tfinclude <iostream .h>
4:
5 : i n t ma m ()
6: {
7: unsigned in t diferencia;
8: unsigned i n t numeroGrande = 100;
9: unsigned i n t numeroChico = 50;
10: d i f e r e n c i a = numeroGrande - numeroChico;
11: c o u t << " L a d i f e r e n c i a es: n << d i f e r e n c i a ;
12: d i f e r e n c i a = numeroChico • numer oGr ande;
13: cout « " \ n A h o r a l a d i f e r e n c i a es: ° << d i f e r e n c i a <<endl;
14: r e t u r n 0;
15: }
La d i f e r e n c i a es: 50
Sa lid a A h o r a l a d i f e r e n c i a es: 4294967246
Encontrar el residuo puede ser muy útil. Por ejemplo, tal vez quiera imprimir una frase
cada 10 acciones. Cualquier número cuyo residuo sea 0 al hacer la operación módulo 10.
es un múltiplo exacto de 10. Por lo tanto, 1 % 10 es 1 , 2 % 10 es 2, y así sucesivamente.
Expresiones e instrucciones 73
P re g u n ta s fre c u e n te s
FAQ: Al dividir 5/3, obtengo 1. ¿Qué estoy haciendo mal?
R e sp u esta: Si divide un entero entre otro, obtendrá un entero como resultado. Por lo
tanto 5/3 será igual a 1.
Para obtener un valor decimal debe utilizar valores y variables de tipo f loat.
5.0 / 3.0 le dará un resultado decimal: 1.66667.
Si su método toma enteros como parámetros, necesita utilizar la especificación de tipo,
en este caso de tipo f loat.
Al hacer una especificación de tipo con una variable, obliga a que cambie su tipo. En
esencia, le está diciendo al compilador: "Sé lo que estoy haciendo". Y más vale que sea
así, porque el compilador le dirá: "Está bien, jefe, es su responsabilidad".
En este caso específico, usted necesita decirle al compilador: "Sé que piensas que éste es
un tipo in t, pero sé lo que estoy haciendo: en realidad es un tipo flo a t" .
Existen dos maneras de realizar la especificación de tipo: puede utilizar la antigua especi
ficación de tipo al estilo C, o puede utilizar el nuevo operador sta tic_ c a st aprobado por
ANSI. El listado 4.3 muestra la especificación de tipo para flo a t.
L is t a d o 4 . 3 continuación
20: int x = 5, y = 3;
21: intDiv(x,y);
22: floatDiv(x,y);
23: return 0;
24: }
z: 1
S a l id a c: 1.66667
Sin embargo, este método es terriblemente com plicado y desperdicia muchos pasos. En
C ++, usted puede colocar la misma variable en ambos lados del operador de asignación;
por consecuencia, lo anterior se convierte en
miEdad = miEdad + 2;
que es mucho mejor. En álgebra, esta expresión no tendría sentido, pero en C++ se lee
com o, sumar dos al valor de miEdad y asignar el resultado a miEdad".
Esto se puede simplificar más, pero tal vez sea un poco más difícil de entender:
miEdad += 2;
El operador aritmético de suma (+=) suma el valor que se encuentra a su derecha (valor-d)
con el valor que se encuentra a su izquierda (valor-i) y luego vuelve a asignar el resultado
al valor de la izquierda (valor-i). Este operador se pronuncia “más igual a". La instrucción
se leería com o “miEdad más igual a dos”. Si miEdad tuviera un valor inicial de 4, tendría
6 después de esta instrucción.
Expresiones e instrucciones 75
También existen otros operadores aritméticos: el de resta (-=), de división (/=). de multi
plicación (*=) y de módulo (%=).
Incremento y decremento
El valor más común para sumar (o restar) y luego volver a asignar a una variable es 1.
En C++, sumar 1 a un valor se conoce como incrementar, y restar 1 se conoce como
decrementar. Debido a que esta operación es muy común, la mayoría de las computado
ras tiene operadores integrados en el hardware para realizar estas acciones. C++ tiene
operadores especiales para realizar estas acciones.
Tal vez haya observado que el nombre de este lenguaje ("C + + ") es similar a
un operador de incremento aplicado a la variable C. Esto no es casualidad.
El lenguaje C++ fue desarrollado como una mejora del lenguaje C existente,
y no como un lenguaje completamente nuevo. Es por esto que el nom bre
imita esa filosofía.
Prefijo y posfijo
Tanto el operador de incremento (++) como el operador de decremento (— ) vienen en
dos variedades: prefijo y posfijo. La variedad de prefijo se escribe antes del nombre de la
variable (++miEdad); la variedad de posfijo se escribe después (miEdad++).
En una instrucción sencilla no importa mucho cuál variedad se utilice, pero en una instruc
ción compleja, en la que se incremente (o decremente) el valor de una variable y luego
se asigne el resultado a otra variable, sí es muy importante. El operador de prefijo se
evalúa antes de la asignación; el de posfijo se evalúa después.
76 D ía 4
La semántica del prefijo es la siguiente: Incrementar el \alnr > luego usarlo l a semántica
del posfijo es distinta: Usar el valor y luego incrementar el original
Esto puede ser confuso al principio, pero si x es una \a iia h le de tip o cuten* cuso valor
es 5, y usted escribe
int a = ++x;
esto le indica al compilador que incremente x (cam biando su valoi a fii y luego ionícese
valor y lo asigne a la variable a. Por lo tanto, ahora a vale 6 \ x tam bién vale 6.
Vo tengo: 39 años.
S alida U s t ed t i e n e : 39 años.
Pas a un a ñ o . . .
Ah or a tengo: 40 años.
U sted t i e n e : 40 años.
Pas a o t r o año
Ahor a tengo: 40 años.
U sted t i e n e : 41 años.
Imprimamos eso de nuevo.
Vo tengo: 41 años.
U s te d t i e n e : 41 años.
Hn las líneas 7 y 8 se declaran dos variables de tipo entero, y cada una se inicia-
A nálisis
lixa con el valor 39. Sus valores se imprimen en las líneas 9 y 10.
En la línea 1 1 mi Eda d se incrementa usando el operador de incremento de posfijo, y en
la línea 12 s u E d a d se incrementa utilizando el operador de prefijo. Los resultados se
imprimen en las líneas 14 y 15. y ambos son idénticos (40).
En la línea 17 mi Edad se incrementa como parte de la instrucción de impresión, usando el
operador de incremento de posfijo, debido a que es posfijo, el incremento ocurre después
de la impresión, y esto ocasiona que se vuelva a imprimir el valor de 40. En contraste, en
la línea 18 s u E d a d se incrementa utilizando el operador de incremento de prefijo. Por
consecuencia, se incrementa antes de que se imprima, y se despliega el valor 41.
finalmente, en las líneas 20 y 21 se imprimen otra vez los valores. Como ya se ha com
pletado la instrucción de incremento, el valor de mi Edad ahora es 41. así como el de
s uE dad .
Precedencia de operadores
¿Qué se realiza primero en la siguiente instrucción compleja, la suma o la multiplicación?,
x = 5 + 3 * 8;
Paréntesis anidados
P a ra e x p re sio n e s co m p lejas, tal vez sea n ecesario a n id a r p a ré n te s is , es d e c ir, co lo c a r
u n o s d e n tro d e otros. P o r ejem plo, p o d ría n ece sitar c a lc u la r el to tal d e s e g u n d o s y luego
c a lc u la r el n ú m e ro total de personas in v o lu crad as an tes d e m u ltip lic a r los se g u n d o s por
las p erso n as:
Este ejemplo es más largo y utiliza más variables temporales que el ejemplo anterior,
pero es mucho más sencillo comprenderlo. Agregue un comentario al principio para
explicar lo que hace este código y cambie el 60 por una constante simbólica. Entonces
tendrá código fácil de entender y de mantener.
D eb e
DEBE recordar que las expresiones tienen NO DEBE anidar muchos paréntesis ya
un valor. que la expresión se vuelve difícil de com
DEBE utilizar el operador de prefijo prender y de mantener.
(++variable) para incrementar o decre-
mentar la variable antes de usarla en la
expresión.
DEBE utilizar el operador de posfijo
(variable++) para incrementar o decre-
mentar la variable después de utilizarla.
DEBE utilizar paréntesis para cambiar el
orden de precedencia.
La naturaleza de la verdad
En versiones anteriores de C++, la verdad y la falsedad se representaban con enteros,
pero el nuevo estándar ANSI ha introducido un tipo nuevo: bool. Este tipo nuevo tiene
dos valores posibles: falso ( f a l s e ) o verdadero ( tr u e) .
Cada expresión puede ser evaluada para ver si es verdadera o falsa. Las expresiones que
se evalúan matemáticamente en cero regresarán el valor f a l s e ; todas las demás regresa
rán t r u e .
Operadores relaciónales
Este tipo de operadores se utiliza para determinar si dos n ú m e r o s son iguales o uno es
m ayor o menor que el otro. Cada expresión relaeional se evalúa c o m o verdadera (true)0
falsa ( f a ls e ) . Los operadores relaciónales se presentan en la tabla 4.1
El n u e v o e s t á n d a r A N S I h a i n t r o d u c id o el n u e v o t i p o b o o l. y a h o r a t o d o s los
o p e r a d o r e s r e la c ió n a le s r e g r e s a n u n v a lo r d e t i p o b o o l: t r u e o f a l s e En ver
s io n e s a n t e r io r e s d e C + + , e s to s o p e r a d o r e s r e g r e s a b a n 0 p a r a f a l s e o u n
v a lo r d is t in t o d e c e ro ( p o r lo g e n e r a l 1) p a r a t r u c .
Si la variable de tipo entero miEdad tiene el valor 39, y la variable de tipo entero suEdad
tiene el valor 40, usted puede determinar si son iguales mediante el uso del operador
relaeional “igual a” (==):
miEdad == suEdad; // ¿es igual el valor de miEdad al de suEdad?
Esta expresión tendría el valor 0, o f a ls e , debido a que las variables ikí son iguales. La
expresión
miEdad < suEdad; // ¿es miEdad menor que suEdad?
tendría un valor diferente de 0, o true.
M u c h o s p r o g r a m a d o r e s d e C + + n o v a t o s c o n f u n d e n el o p e r a d o r d e a s i g
Precaución n a c ió n (=) c o n el o p e r a d o r re la e io n a l ig u a l a ( i g u a l d a d ) (= = ). E sto p u e d e
c r e a r u n te r r ib le e rr o r e n su p r o g r a m a .
L os seis operadores relaciónales son: igual a (==), menor que (<), mayor que (>), menor
o igual a (< =), mayor o igual a (>=) y diferente de (!=). La tabla 4.1 muestra cada opera
dor relaeional, su uso y un pequeño ejemplo.
D ebe N O DEBE
D EBE recordar que los operadores rela NO DEBE confundir el operador de asig
ciónales regresan los valores true (ver nación (=) con el operador relacional
dadero) o fa ls e (falso). igual a (==). Éste es uno de los errores
más comunes en la programación con
C++; tenga cuidado con esto.
La instrucción i f
Por lo general, su programa fluye línea por línea en el orden en el que aparece en su
código fuente. La instrucción i f le permite probar una condición (por ejem plo, si
dos variables son iguales) y saltar hacia distintas partes del código, dependiendo del
resultado.
La expresión que está entre paréntesis puede ser cualquier expresión, pero por lo general
contiene una de las expresiones relaciónales. Si la expresión tiene el valor f a ls e . la ins
trucción 1 no se ejecutará. Si tiene el valor true, la instrucciónl se ejecutará. Considere
el siguiente ejemplo:
if ( numeroGrande > numeroChico)
numeroGrande = numeroChico;
Dado que un bloque de instrucciones encerradas entre llaves es equis alente a una sola
instrucción, el siguiente tipo de ramificación puede ser bastante grande > poderoso:
if (expresión)
{
instruccióm ;
instrucción2;
instrucción3;
}
Un ejem plo simple de este uso se vería así:
if (numeroGrande > numeroChico)
{
numeroGrande = numeroChico;
cout « "numeroGrande: " « numeroGrande << “\n“;
cout << "numeroChico: " « numeroChico << "\n";
}
Esta vez, si numeroGrande es mayor que numeroChico. no sólo se le asigna el valor de
numeroChico, sino que también se imprime un mensaje de información. H1 listado 4.5
muestra un ejemplo más detallado de la ramificación basada en los operadores rela
ciónales.
M u c h o s p r o g r a m a d o r e s d e C + ♦ n o v a t o s c o lo c a n s i n q u c r t ? r u n p u n t o y coma
d e s p u é s d e la in stru c c ió n i f ;
Estilos de sangría
El listado 4.5 muestra un estilo de sangría para las instrucciones i f . Sin embargo, no hay
nada más propenso a crear una guerra religiosa que preguntar a un grupo de progra
madores cuál es el mejor estilo para alinear las llaves. Aunque puede haber docenas de
variaciones, las tres siguientes parecen ser las favoritas:
En este libro se utiliza la segunda alternativa, ya que am bos autores en cu en tran que es
m ás fácil en ten d er dónde em piezan y term inan los bloques de in stru ccio n es si se alin ean
las llaves una con otra y con la condición que se está probando. De nuevo, n o im porta
m ucho cuál estilo elija, siem pre y cuando sea consistente. Para el com p ilad o r, esto ta m
poco es im portante.
e lse
A menudo su programa necesitará ejecutar ciertas instrucciones si la condición es t r u e
(verdadera), y otras si la condición es f a l s e (falsa). En el listado 4.5 se imprimía un
mensaje ( ¡ v a m o s , M e d i a s r o j a s ! ) si la primera prueba ( C a r r e r a s M e d i a s R o j a s >
C a r r e r a s Y a n q u i s ) se evaluaba como verdadera, y otro mensaje ( ¡ V a m o s , Y a n q u i s ! )
si se evaluaba como falsa.
Hl m étodo m ostrado hasta ahora (probar prim ero una condición y luego la otra) funciona
bien, pero es un poco incóm odo. La cláusula e l s e puede ayudar a tener un có d ig o m u
cho m ás legible:
if (expresión)
instrucción;
else
instrucción;
1: // L i s t a d o 4 . 6 - muestra e l uso de l a i n s t r u c c i ó n i f
2: // con l a c l á u s u l a e l s e
3: //inelude < i o s t r e a m . h >
4: i n t m a i n ()
5: {
6: i n t primerNumero, segundoNumero;
7: cout << " E s c r i b a un número grande: ";
8: c i n » primerNumero;
9: cout « " \ n E s c r i b a un número más pequeño:
10: c i n » segundoNumero;
11: i f (primerNumero >= segundoNumero)
12: cout « " \ n ¡ G r a c i a s ! \ n " ;
13: else
14: cout << "\nOh. ¡ E l segundo es más g r a n d e ! \ n " ;
15:
16: r e t u r n 0;
17: }
E s c r i b a un número grande: 10
S a l id a
E s c r i b a un número más pequeño: 12
Oh. ¡ E l segundo es más grande!
86 Día 4
La instrucción i f
La sintaxis para la instrucción i f es la siguiente:
Forma 1
i f (expresión)
in st ru cc ió n;
siguiente instrucción;
Si la expresión se evalúa como verdadera, se ejecuta la instrucción y el program a con
tin ú a con la siguiente instrucción. Si la expresión es falsa, se ign o ra la instrucción y el pro
gram a salta hasta la siguiente instrucción.
Recuerde que la instrucción puede ser sencilla con un punto y com a al fin al, o un bloque
de instrucciones encerradas entre llaves (aunque tam bién puede ser vacía, o n u il).
Forma 2
i f (expresión)
instrucciónl;
else
instru cc ión2 ;
siguiente instrucción;
Si la expresión se evalúa como verdadera, se ejecuta in s t r u c c ió n l; de no ser así, se ejecu
ta in s t r u c c c ió n 2 . Después de eso, el program a continúa con la sig u ie n te instrucción.
Ejem plo 1
i f (AlgunValor < 10)
cout « "AlgunValor es menor que 1 0 " ) ;
e ls e
cout « "iAlgunValor no es menor que 1 0 1 " ;
cout « " L i s t o . " « endl;
In stru c c io n e s i f a v a n z a d a s
Vale la pena mencionar que se puede utilizar cualquier instrucción en las cláusulas i f o
e l s e , incluso otra instrucción i f o e l s e . Por lo tanto, usted podría ver instrucciones if
com plejas de la siguiente forma:
if (exp resió nl)
{
Expresiones e instrucciones 87
if (expresión2)
instrucciónl 1;
else
{
if (expresión3)
instrucciónl 2;
else
instrucciónl 3;
>
}
else
instrucciónl 4;
Esta voluminosa instrucción if dice: “Si tanto expresión 1 como expresión2 son ver
daderas (true), ejecutar instrucciónl. Si expresiónl es verdadera, pero expresión2
no lo es, entonces si expresión3 es verdadera, ejecutar instrucción2. Si expresiónl
es verdadera pero expresión2 y expresión3 no lo son, ejecutar instrucción3. Final
mente, si expresiónl no es verdadera, ejecutar instrucción4'\ Como puede ver, ¡las
instrucciones if complejas pueden ser confusas!
L is t a d o 4 .7 c o n t in u a c ió n
25: else
26: cout << " ¡ E l primer número es m u l t i p l o Jel segund
27: }
28: else
29: cout << " ¡ E l primer numero no es m u l t i p l o del s e g u n d
30: }
31 : else
32: cout << "¡Hey! ¡E l segundo es mas g r a n d e ! n * ;
33: return 0;
34: }
E s cr ib a dos números.
S alid a Primero: 10
S eg un d o: 2
L lave s en instrucciones i f c o m p le ja s
Aunque es válido no escribir las llaves en instrucciones i f que tengan só lo una instruc
ción, y aunque sea válido anidar instrucciones i f com o la siguiente:
if ( x > V) // s i x es mayor que y
i f (x < z) // y s i x e s menor que z
x = y¡ // e n t o n c e s a s i g n a r e l v a l o r de y a x
El programador quería pedir un número menor que 10 o mayor que 100. com
A nálisis
probar el valor correcto y luego imprimir un mensaje de agradecimiento.
E n t r a d a L istado 4 .9 Una muestra del uso adecuado de las llaves en una instrucción if
Las llaves de las líneas 12 y 15 hacen que todo lo que esté dentro de ellas sea
A n á l is is
una sola instrucción, y ahora la cláusula e l s e de la línea 16 se incluye en la
instrucción i f de la línea 11, como se quería.
¡R e c u e r d e p r o g r a m a r a la d e fe n s iv a !
Expresiones e instrucciones
Operadores lógicos
A menudo querrá hacer más de una pregunta relaciona! a la vez. “¿Es verdad que x es
mayor que y, y es también verdad que y es mayor que e?“ Un programa podría necesitar
determinar que ambas condiciones sean verdaderas (o que alguna otra condición sea ver
dadera) para poder realizar una acción.
Imagine un sofisticado sistema de alarma que tenga esta lógica: “Si suena la alarma de la
puerta después de las 6 p.m., Y (AND) NO (NOT) es un día festivo, O (OR) si es un fin
de semana, entonces llamar a la policía“. Los tres operadores lógicos de C++ se utilizan
para hacer este tipo de evaluación. Estos operadores se muestran en la tabla 4.2.
Operador lógico a n d 4
Una expresión lógica AND evalúa dos expresiones, y si ambas expresiones son verdade
ras (true), la expresión lógica AND también es verdadera. Si es verdad que usted está ham
briento, y es verdad que tiene dinero, entonces es verdad que puede comprar el almuerzo.
Por lo tanto,
i f ( (x == 5) && (y == 5) )
sería verdadera si tanto x como y son iguales a 5, y sería falsa (f alse) si cualquiera de
las dos o las dos son diferentes de 5. Observe que ambos lados deben ser verdaderos para
que toda la expresión sea verdadera.
Observe que el operador lógico AND está compuesto por dos símbolos &. Un solo símbolo
& es un operador diferente, el cual se describe en el día 21, Qué sigue .
Operador lógico OR
Una expresión lógica OR evalúa dos expresiones. Si cualquiera de ellas es verdadera, en
tonces la expresión OR es verdadera. Si usted tiene dinero o una tarjeta de crédito, puede
pagar la cuenta. No necesita tener dinero y tarjeta de crédito a la vez; sólo necesita una
de estas cosas, aunque tener las dos también sería perfecto. Por lo tanto,
if ( ( x == 5) || (y == 5) )
sería verdadera si x o y son iguales a 5 , o si ambas son iguales a 5.
92 D ía 4
i f ( (x == 5) && (y == 5) )
comprobará si la primera expresión (x==5 ) es verdadera, y si esto falla (es decir, si x no
es igual a 5), el compilador NO evaluará si la segunda expresión (y == 5) es cierta o
falsa, debido a que AND requiere que ambas sean verdaderas.
Asimismo, si el compilador evalúa una expresión OR como la siguiente:
i f ( (x == 5) | | (y == 5) )
si la primera expresión es verdadera (x == 5 ), el compilador nunca evaluará la segunda
expresión (y == 5 ), debido a que en una expresión OR basta con que cualquiera de las
dos sea verdadera.
Esto es importante si hay efectos secundarios (por ejemplo, si hubiera utilizado los opera
dores de incremento o decremento en x o en y). Si no se evaluara la segunda expresión,
no habría incremento o decremento.
Precedencia relacional
Debido a que en C++ los operadores relaciónales y los operadores lógicos son expresiones,
cada uno de ellos regresa un valor: verdadero o falso. Al igual que todas las expresio
nes, tienen un orden de precedencia (vea el apéndice A) que determina cuáles relaciones
se evalúan primero. Este hecho es importante al determinar el valor de la expresión, por
ejemplo:
i f (x > 5 && y > 5 | | z > 5)
Tal vez el programador quería que esta expresión fuera verdadera (tru e ) si tanto x como
y eran mayores que 5, o si z era mayor que 5 . Por otra parte, tal vez el programador haya
Expresiones e instrucciones 93
querido que esta expresión fuera verdadera sólo si x era mayor que 5 y si también era
verdad que y era mayor que 5. o que z era mayor que 5.
Si x es igual a 3. y tanto y como z tienen el valor 10, la primera interpretación sería ver
dad (z es mayor que 5. por lo que se ignoran x y y), pero la segunda sería falsa (no es
verdad que x es mayor que 5 y por consecuencia no importa qué haya del lado derecho
del símbolo |&&] ya que ambos lados deben ser verdaderos).
Aunque la precedencia determina cuál relación se evalúa primero, los paréntesis pueden
cambiar el orden y hacer que la instrucción sea más clara:
if ( (x > 5) && (y > 5 || z > 5) )
Si se utilizan los mismos valores que en el ejemplo anterior, esta expresión es falsa.
Como no es verdad que x es mayor que 5, el lado izquierdo de la expresión AND falla, y
por consecuencia toda la expresión es falsa. Recuerde que una expresión AND requiere
que ambos lados sean verdaderos (algo no es “sabroso” y “bueno para usted” si no sabe
bien).
se puede leer de la siguiente manera: “Si x tiene un valor distinto de cero, asignarle el
valor 0”. Aquí estamos haciendo una poca de trampa; sería más claro si se escribe
i f (x != 0) / / s i x es d istin to de cero
x = 0;
Ambas instrucciones son válidas, pero la última es más clara. Es una buena práctica de
programación reservar el primer método para verdaderas pruebas de lógica, en lugar
de usarlo para probar si hay valores distintos de cero.
94 Día 4
D ebe NO DEBE
D E B E colocar paréntesis alrededor de sus NO D E B E u tilizar i f (x) com o sinónimo
pruebas lógicas para que sean más claras para i f (x !■ 0 ); este últim o es más
y para que la precedencia sea explícita. claro.
D E B E u tilizar llaves en instrucciones i f NO D EB E u tiliza r i f ( !x) como sinónimo
an id a d a s para que las cláusulas e lse para i f (x 0 ); este últim o es más
sean más claras y para evitar errores. claro.
E ntrada L is t a d o 4 . 1 0 U na m u e stra d e l o p e ra d o r c o n d ic io n a l
1: // L i s t a d o 4 . 1 0 - M u e s t r a e l us o d e l o p e r a d o r c o n d i c i o n a l
2: //
3: # in clu d e <iostream.h>
4: i n t main()
5: {
6: i n t x, y, z;
7: c o u t << " E s c r i b a do s n ú m e r o s . \ n " ;
8: c o u t << " P r i m e r o : ";
9: c i n >> x;
10: c o u t << " \ n S e g u n d o : ";
11: c i n » y;
12: cout « "\n";
Expresiones e instrucciones 95
13:
14: if (x > y)
15: z = x;
16: else
17: z = y;
18:
19: cout << "z: ° << z;
20: cout « °\n";
21 :
22: z = (x > y) ? x : y
23:
24: cout « “z: " « z;
25: cout « "\n°;
26: return 0;
27: }
Segundo: 8
z: 8
z: 8
Se crean tres variables de tipo entero: x, y y z. El usuario asigna un valor a las
A nálisis
dos primeras. La instrucción i f de la línea 14 hace una prueba para ver cuál es
más grande y asigna el valor más grande a z. Este valor se imprime en la línea 19.
El operador condicional de la línea 22 hace la misma prueba y asigna el valor más gran
de a z. Se lee así: “Si x es mayor que y, regresar el valor de x; de no ser así, regresar el
valor de y”. El valor regresado se asigna a z. Ese valor se imprime en la línea 24. Como
puede ver, la instrucción condicional es un equivalente más corto para la instrucción
i f . . .else.
Resumen
En esta lección se ha cubierto bastante material. Ha aprendido lo que son las instruccio
nes y las expresiones de C++, lo que hacen los operadores de C++ y cómo funcionan las
instrucciones i f de C++.
También ha visto que en cualquier parte donde pueda utilizar una instrucción sencilla,
también puede utilizar un bloque de instrucciones encerradas por un par de llaves ({y}).
Asimismo, ha aprendido que todas las expresiones se evalúan y producen un cierto valor,
y que ese valor se puede probar en una instrucción i f o mediante el operador condicio
nal. También ha visto cómo evaluar varias instrucciones por medio del operador lógico,
cómo comparar valores por medio de los operadores relaciónales y cómo asignar valores
por medio del operador de asignación.
96 Día 4
Preguntas y respuestas
P ¿Porqué utilizar paréntesis innecesarios si la precedencia delem una cuales opera
dores se evalúan primero?
R Aunque es cierto que el compilador conoce la precedencia > que un programador
puede consultar el orden de precedencia, un código fácil de comprenderes más
fácil de mantener.
P Si los operadores relaciónales siem pre regresan verd ad ero o falso, ¿porqué
cualquier valor distinto de cero se con sid era verd ad ero ?
R Los operadores relaciónales regresan verdadero o falso, pero toda expresión regre
sa un valor, y ese valor también se pueden evaluar en una instrucción íf . Me aquí
un ejemplo;
lf ( (x = a + b) == 3 5 )
Lsta es una instrucción perfectamente válida en ( ’+ + . Tiene un v alor aunque la
suma de a y b no sea igual a 35. Además, hay que observar que en cualquier caso
el valor de la suma de a y b se asigna a x.
I ¿Q ué efecto tienen en un program a los fab u lad ores, esp acios y caracteres de
nueva línea?
R Los tabúIadores, espacios y caracteres de nueva línea (con los que se crean los
espacios en blanco) no tienen efecto en el programa, aunque el uso sensato del espa
cio en blanco puede facilitar la legibilidad del programa.
I ¿Q ué son los núm eros negativos, verdaderos o falsos?
R Todos los valores distintos de cero, positivos o negativos, son verdaderos.
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del
material tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Qué es una expresión?
2. ¿Es x = 5 + 7 una expresión? ¿Cuál es su valor?
Expresiones e instrucciones 97
d. x = 0
e. x == 0 // suponga que x vale 0
Ejercicios
1. Escriba una instrucción i f sencilla que examine dos variables de tipo entero y que
cambie la más grande a la más pequeña, usando sólo una cláusula e ls e .
2. Examine el siguiente programa. Imagine que escribe tres números, y escriba la
salida que espera obtener.
1: #include <iostream.h>
2: int main()
3: {
4: int a, b, c;
5: cout « "Escriba tres números\n ;
6: cout « "a:
7: cin » a;
8: cout « "\nb: ";
9: cin » b;
10: cout « “\nc:
11 : cin » c;
12:
13: if (c = (a -b))
14: {
15: cout « "a: “;
16: cout « a;
17: cout « "menos b:
18: cout « b;
19: cout « "igual a c: "í
20: cout « c « endl;
21 }
22: else
23: cout « "a-b no es igual a c:
24: return 0;
25: }
Día 4
3. Escriba el programa del ejercicio 2; com pílelo, e n lá c e lo \ ejecú telo Escriba l°®
números 20. 10 y 50. ¿Obtuvo la salida esperada ’ . Por que no ’
4. Examine este programa y trate de adivinar la salida
1 : tfinclude <iostream.h>
2: int main()
3: {
4: i nt a = 1 , b = 1 , c¡
5: if (c = ( a b ))
6: cout << El valor de c es: * <<c;
7: return 0;
8: }
5. Escriba, compile, enlace y ejecute el program a del ejercicio 4 /.Cuál íue la sal¡JJ
¿Por qué?
Sem ana 1
D ía 5
Funciones
Aunque la programación orientada a objetos ha desviado la atención de las
funciones hacia los objetos, las funciones siguen siendo, sin lugar a dudas, un
com ponente central de cualquier programa. Hoy aprenderá lo siguiente.
Cada función tiene su propio nombre, y cuando se encuentra ese nombre, la ejecución
del programa se dirige hacia el cuerpo de esa función, listo se conoce c o m o lla m a r a la
función. Cuando la función termina, la ejecución continúa en la siguiente línea de la fun
ción que realizó la llamada. Este flujo se muestra en la figura 5.1.
Cuando un programa
llama a una función,
la ejecución continúa
dentro de la función
y luego regresa a la
línea que está después
de la llamada a la
función.
Las funciones bien diseñadas realizan una tarea específica y clara. Las tareas com plica
das se deben dividir entre varias funciones, y luego se puede llamar en orden a cada una
de ellas.
Las funciones vienen en dos variedades: definidas por el usuario e integradas. Las fun
ciones integradas son parte del paquete del compilador (las proporciona el fabricante
para que usted las utilice). Las funciones definidas p o r el usuario son las funciones que
usted mismo escribe.
Un parámetro describe el tipo del valor que se pasará hacia la función, así como el nom
bre de la variable utilizada en la función cuando ésta es llamada. Los valores reales que
usted pasa a la función se conocen como argumentos.
int elValorRegresado = miFuncion(5, 6.7);
Puede ver aquí que la variable de tipo entero elValorRegresado se inicializa con el valor
regresado por miFuncion, y que los valores 5 y 6.7 se pasan como argumentos. El tipo de
los argumentos debe concordar con los tipos de los parámetros declarados.
Figura 5.2
Componentes de un
prototipo de función. unsigned
short int EncontrarArea (int longitud, int ancho) ;
Este prototipo declara una función llamada A rea() que regresa un valor de tipo long y
que tiene dos parámetros, ambos de tipo entero. Aunque esto es válido, no es una buena
idea. Agregar los nombres de los parámetros ayuda a que el prototipo sea más claro. La
siguiente es la misma función con los nombres de los parámetros:
long Area(int longitud, int ancho);
Ahora es obvio lo que esta función hace y qué valores contendrán los parámetros.
El compilador no requiere los nombres de las variables y esencialmente los ignora en el
prototipo. Están para nosotros, los humanos.
Observe que todas las funciones tienen un tipo de valor de retomo. Si no se declara
explícitamente uno, el tipo de valor de retomo predeterminado es in t . Sin embargo, sus
programas serán más fáciles de entender si declara explícitamente el tipo de valor de
retomo para cada función, incluyendo el de m ain(). EL listado 5.1 muestra un programa
que incluye un prototipo de función para la función Area().
Funciones
Observe también que los nombres de los parámetros del prototipo son longitud y ancho,
pero los nombres de los parámetros en la definición son jardinLongitud y jardinAncho.
Como vio anteriormente, los nombres que vienen en el prototipo no se utilizan; están
como información para el programador. Es una buena práctica de programación hacer
que los nombres de los parámetros del prototipo concuerden con los nombres de los
parámetros de la implementación, pero esto no es un requerimiento.
10 4 Día 5
^ - llave de apertura
// instrucciones
\ palabra
reservada
\ valor de retorno
} - llave de cierre
Fundones
S in t a x is d e l p r o t o t ip o d e fu n ció n :
t ip o _ v á lo r _ r e t o r n o no m b rp ^ fu n ción E( [ t i p o [n o m b re P a ra m e tr o ] ] . . . ) ;
S in t a x is d e la definición, d é fu n ció n : .
t ip o _ v a ló r _ r e t o r n o nom bre_función ( [ t i p o n o m b r e P a r a m e t r o ] . . . )
'■ ' ' 1 ' ' 1 ‘ :/ í ! -‘ - ^
i n s t r u c c io n e s ; .
.} yV / , 7''
U n p r o t o t ip o d e fu n c ió n {e in d ic a a í c o m p ila d o r,e l t ip o d e l v a lo r d e r e t o r n o , e l n o m b r e
d e la fu n c ió n y la. lista d e parám e tros. N o se re q u ie re q u e las f u n c io n e s t e n g a n p a r á m e
tro s, y s í lo s tie n e n , n o sé req uiere q u e el p r o to t ip o m u e s tre s u s n o m b r e s , s ó l o s u s tip o s .
U n p r o t o t ip o sie m p re te rm in a con p u n to y c o m a (;).
s
Fundones 105
E je m p lo s d e p ro to tip o s d e funciones:
lo n g E n c o n tr a r A re a (lo n g lo n g itu d , lo n g ancho);
**// regresa un entero largo, tiene dos parámetros
void imprimeMensaje (int numeroMensaje);
* ► / / r e g r e s a v o id (e s d e c ir , no re gre sa nad a), t ie n e un parám etro
i n t O b te n e rO p c io n ();
*■ *7/ r e g re s a un e nte ro, no tie n e parám etros
F u n c io n M a la O ; / / re g re sa un e n te ro , no t ie n e
parám e tros
E je m p lo s d e d e fin ic ió n d e función:
lo n g E n c o n tr a r A re a (lo n g 1, lo n g a)
{
re tu rn 1 * a;
>
Variables locales
N o solamente puede pasar variables a la función, también puede declarar variables den-
tro e cuerpo de la función. Esto se hace utilizando variables locales. que se llaman así
porque existen sólo localmente dentro de la misma función. Al salir de la función, las
variables locales ya no están disponibles.
Las variables locales se definen de la misma forma que cualquier otra variable. Los
parámetros que se pasan a la función también se consideran variables locales y se pueden
usar com o si se hubieran definido dentro del cuerpo de la función. El listado 5.2 es un
ejemplo del uso de parámetros y variables definidas localmente dentro de una función.
1: #include <iostream.h>
2:
3: float Convertir(float);
4: int main()
5: {
6: float TempFar;
7: float TempCen;
8:
9: cout « "Escriba la temperatura en grados Fahrenheit: ";
10: cin » TempFar;
11: TempCen = Convertir(TempFar);
12: cout « "\nAquí está la temperatura en grados centígrados:
13: cout « TempCen « endl;
14: return 0;
15: }
16:
17: float Convertir(float TempFar)
18: {
19: float TempCen;
20:
21: TempCen = ((TempFar - 32) * 5) / 9;
22: return TempCen;
23: }
Variables globales
Las variables que se defínen fuera de cualquier función tienen alcance global y, por lo
tanto, están disponibles para cualquier función del programa, incluyendo main ().
Las variables locales que tengan el mismo nombre que las variables globales no cambian
a las variables globales. Sin embargo, una variable local que tenga el m ism o nombre que
una variable global oculta a la variable global. Si una función tiene una variable con el
m ism o nombre que una variable global, el nombre se referirá a la variable local, no a la
global, cuando se utilice dentro de la función. El listado 5.3 ilustra estos puntos.
1: #include <iostream.h>
2: void miFuncion(); // prototipo
3:
4: int x = 5, y = 7; // variables globales
5: int main()
6: {
7:
8: cout « "x desde la función main: " « x« "\n";
9: cout « "y desde la función main: " « y« "\n \n " ;
10: miFuncion();
11 : cout « "IYa salimos de miFuncionl\n\n" ;
12: cout « "x desde la función main: " « x « "\n";
13: cout « "y desde la función main: " « y « "\n " ;
14: return 0;
15:
16:
Funciones 109
17 v oi d m i F u n c i o n ()
18 {
19 i n t y = 10
20
21 cout << "x desde miFuncion: d « x « "\n";
22 cout << “y desde miFuncion: " « y « " \n\n";
23 }
x desde l a f u n c i ó n main: 5
y desde l a f u n c i ó n main: 7
x desde mi Funcion: 5
y desde mi Funcion: 10
¡Ya s a l i m o s de miFuncion!
x desde l a f u n c i ó n main: 5
y desde l a f u n c i ó n main: 7
Las variables globales son peligrosas porque son información compartida, y una función
puede cambiar una variable global de manera que el cambio sea invisible para otra fun
ción. Esto crea errores que son muy difíciles de encontrar.
En el día 14, “Clases y funciones especiales”, verá una poderosa alternativa que ofrece
C++ para las variables globales, pero que no está disponible en C.
29:
30: cout « D\nLa misma variable local x vale: ° « x;
31: }
32:
33: cout « "\nFuera del bloque, en miFunc, x vale: - « x « endl;
34: }
En main x vale: 5
En miFunc, la variable local x vale: 8
Aunque en C++ no existe un límite para el tamaño de una función, las funciones bien
diseñadas tienden a ser pequeñas. Muchos programadores aconsejan mantener las funcio
nes lo suficientemente cortas como para que quepan en una sola pantalla, con el fin de
que se pueda ver toda la función a la vez. Ésta es una regla empírica que a menudo que
brantan los programadores que son muy buenos, pero una función más pequeña es más
fácil de comprender y de mantener.
Cada función debe realizar una tarea individual y fácil de comprender. Si su función
empieza a crecer, busque lugares en donde pueda dividirla en tareas más pequeñas.
Cualquier expresión válida de C++ puede ser un argumento de función, incluyendo cons
tantes, expresiones matemáticas y lógicas y otras funciones que regresen un valor.
Ahora cada resultado intermedio puede ser examinado, y el orden de ejecución es explícito.
Funciones 113
Este programa inicializa dos variables en m ain () y luego las pasa a la función
A nálisis
intercam biar(), la cual parece intercambiarlas. Sin embargo, al examinarlas
otra vez en main(), ¡permanecen sin cambio!
Las variables se inicializan en la línea 9, y sus valores se despliegan en la línea 11. Se
llama a la función intercambiar!), y se pasan las variables.
La ejecución del programa cambia a la función intercambiar ( ) . en donde se imprimen
de nuevo los valores (línea 21). Éstos se encuentran en el m ismo orden en el que estaban
en main( ), como era de esperarse. En las líneas 23 a 25 se intercambian los valores, y
esta acción se confirma con la impresión de la línea 27. Evidentemente, al estar en la
función intercambiar!), los valores se intercambian.
La ejecución regresa entonces a la línea 13, de nuevo en main ( ) , en donde los valores ya
no están intercambiados.
Como puede ver, los valores pasados a la función intercambiar! ) se pasan por valor, lo
que significa que se hacen copias de los valores que son locales para intercambiar!).
Estas variables locales se intercambian en las líneas 23 a 25, pero al regresar a
main () permanecen sin cambio.
En el día 8, “Apuntadores”, y en el día 10, “Funciones avanzadas”, verá alternativas para
pasar variables que permitan que se cambien los valores en main () .
Estas son instrucciones return válidas, asumiendo que la misma función MiFuncion re
grese un valor. El valor de la segunda instrucción, return (x > 5), será falso (false) si
x no es mayor que 5, o será verdadero (true). Lo que se regresa es el valor de la
expresión, f alse o true, no el valor de x.
Al encontrarse la palabra reservada return, la expresión que le sigue se regresa como el
valor de la función. La ejecución del programa regresa inmediatamente a la función que
hizo la llamada, y no se ejecuta ninguna instrucción que esté después de return.
Es válido tener más de una instrucción return en una sola función. Pero una sola ins
trucción return sólo puede regresar un valor. El listado 5.6 ilustra la idea de tener varias
instrucciones return.
Fundones 115
Preguntas frecuentes
F Á Q : ¿ C u á l e s la d ife re n c ia e n tre injt- m a in () y v o id m a in ( ) ; c u á l d e b o u s a r ? H e
u tífíz a d b a m b a s y fu n c io n a n bien, a sí q u e ¿ p o r q u é n e c e s it o u t iliz a r i n t m a in ( )
{ r e t u r n 0 ;> ?
Parámetros predeterminados
Para cualquier parámetro que se declare en un prototipo y en una definición de función,
la función que hace la llamada debe pasar un valor. El valor pasado debe ser del tipo
declarado. Por lo tanto, si tiene una función declarada de la siguiente manera:
Funciones 117
long miFuncion(int);
la función debe, en efecto, tomar una variable entera. Si la definición de la función es
distinta, o si no se pasa un valor entero, se obtendrá un error de compilación.
La única excepción a esta regla es si el prototipo de la función declara un valor predeter
minado para el parámetro. Un valor predeterminado es un valor que se utiliza en caso de
no proporcionar uno. La declaración anterior se podría escribir de la siguiente manera:
long miFuncion (int x = 50);
Este prototipo dice: “miFuncion () regresa un valor entero largo y toma un parámetro
entero. Si no se proporciona un argumento, utilizar el valor predeterminado 50”. Debido
a que no se requieren los nombres de los parámetros en los prototipos de funciones, esta
declaración se hubiera podido escribir de la siguiente manera:
long miFuncion (int = 50);
La definición de la función no cambia al declarar un parámetro predeterminado. El enca
bezado de la definición para esta función sería
long miFuncion (int x)
Si la función que hace la llamada no incluye un argumento, el compilador daría a x el
valor predeterminado 50. El nombre del parámetro predeterminado del prototipo no
necesita ser el mismo que el del encabezado de la función; el valor predeterminado se
asigna por posición, no por nombre.
Los valores predeterminados se pueden asignar a cualquiera o a todos los parámetros de
la función. La única restricción es ésta: si alguno de los parámetros no tiene un valor
predeterminado, ningún parámetro anterior puede tener un valor predeterminado.
Si el prototipo de la función se ve así:
long miFunción (int Paraml, int Param2, int Param3);
podrá asignar un valor predeterminado a Param2 sólo si ha asignado un valor predetermi
nado a Param3. Puede asignar un valor predeterminado a Paraml sólo si asigna valores
predeterminados a Param2 y a Param3. El listado 5.7 muestra el uso de valores predeter
minados.
8: int main()
9: {
10 int longitud = 100;
11 int ancho = 50;
12 int altura = 2;
13 int volumen;
14
15 volumen = VolumenCaja(longitud, ancho, altura);
16 cout « "La primera vez el volumen es igual a: " << volumen << °\n°;
17
18 volumen = VolumenCaja(longitud, ancho);
19 cout « "La segunda vez el volumen es igual a: “ << volumen << "\n";
20
21 volumen = VolumenCaja(longitud);
22 cout « "La tercera vez el volumen es igual a: " << volumen << n\n";
23 return 0;
24 >
25
26 int VolumenCaja(int longitud, int ancho, int altura)
27 {
28
29 return (longitud * ancho * altura);
30 >
A nálisis
En la línea 6, el prototipo de VolumenCaja( ) especifica que esta función tom a
tres parámetros de tipo entero. Los últimos dos tienen valores predeterminados.
Esta función calcula el volumen de una caja con las dim ensiones que se le pasan al m o
mento de ser invocada. Si se escriben dos parámetros, el primero corresponderá a la lon
gitud y el segundo al ancho, mientras que la altura tendrá el valor predeterminado de 1.
Si se escribe un sólo parámetro, éste corresponderá a la longitud y se utilizará un ancho
de 25 y una altura de 1. No es posible pasar la altura sin pasar el ancho.
En las líneas 10 a 12 se inicializan las dimensiones longitud, altura y ancho, y se
pasan a la función VolumenCaja() de la línea 15. Se calculan los valores, y el resultado
se imprime en la línea 16.
La ejecución regresa a la línea 18, en donde se llama otra vez a VolumenCaj a ( ) , pero sin
dar un valor para la altura. Se utiliza el valor predeterminado, y se vuelven a calcular y a
imprimir las dimensiones.
La ejecución regresa a la línea 21, y esta vez no se pasan ni el ancho ni la altura. La
ejecución se ramifica por tercera ocasión a la línea 27. Se utilizan los valores predeterm i
nados. Se calcula el volumen y luego se imprime.
D eb e N O D EBE
D E B E re co rd a r q u e los p a rá m e tro s de N O D E B E tra ta r d e cre ar u n v a lo r p re d e
u n a fu n c ió n a c tú a n c o m o variab le s te rm in a d o p a ra el p rim e r p a rá m e t ro si
locales d e n tro d e la fu n ció n . n o existe u n v a lo r p re d e t e rm in a d o p a ra
el s e g u n d o . Esto ta m b ié n se ap lica al
s e g u n d o p a rá m e tro y a los d e m ás. N o d é
a u n p a rá m e tro u n v a lo r p re d e t e rm in a d o
si el p a rá m e tro q u e se e n c u e n tra a su
d erecha n o tie n e u n o.
N O D E B E o lv id a r q u e los a r g u m e n t o s q u e
se p a sa n p o r v a lo r n o p u e d e n a fe c ta r a
las va riab le s d e la f u n c ió n q u e h ace la
llam ada.
N O D E B E o lv id a r q u e los c a m b io s q u e se
hacen a u n a va ria b le g lo b a l d e u n a f u n
ción cam b ian a esa va ria b le p a ra t o d a s
las fu n cion e s.
Sobrecarga de funciones
C++ le permite crear más de una función con el mismo nombre. Esto se conoce como
so b re ca rg a d e fu n c io n e s . Las funciones deben ser diferentes en su lista de parámetros, es
decir, ésta debe tener un tipo distinto de parámetro, un número distinto de parámetros, o
ambos. He aquí un ejemplo:
i n t m i Fu nc io n ( i n t , i n t ) ;
i n t m i Fu nc io n ( l o n g , l o n g ) ;
i n t m i F u nc i o n ( l o n g ) ;
miFuncion () está sobrecargada con tres listas de parámetros. La primera y segunda versio
nes difieren en los tipos de los parámetros, y la tercera difiere en el número de parámetros.
Los tipos de valores de retomo pueden ser iguales o diferentes en funciones sobrecargadas.
Debe tener en cuenta que dos funciones que tengan el mismo nombre y la misma lista de
parámetros, pero diferentes tipos de valor de retorno, generarán un error de compilación.
miEntero: 6500
S a l id a miLargo: 65000
miFlotante: 6.5
miDoble: 6.5e+20
En Duplicar(int)
En Duplicar(long)
En Duplicar(float)
En Duplicar(double)
enteroDuplicado: 13000
largoDuplicado: 130000
flotanteDuplicado: 13
dobleDuplicado: 1.3e+21
122 Día 5
La función Duplicar^) se sobrecarga con int. long. float y double. Los pro
A nálisis
totipos están en las líneas 6 a 9, y las definiciones están en las líneas 4 1 a 63.
En el cuerpo del programa principal se declaran ocho variables locales. Hn las líneas 13 a
16 se inicializan cuatro de los valores, y en las líneas 28 a 31 se asignan a los litros cuatro
los resultados obtenidos al pasar los primeros cuatro a la función Duplicar (). Observe
que cuando se llama a la función Duplicar(), la función que hace la llamada no decide a
cuál llamar; sólo pasa un argumento, y se invoca a la función correcta.
El compilador examina los argumentos y elige a cuál de las cuatro funciones Duplicar ()
debe llamar. La salida revela que se llamó a cada una de las cuatro, com o era de esperarse.
Funciones en línea
Al definir una función, normalmente el compilador crea sólo un conjunto de instruccio
nes en la memoria. Al llamar a la función, la ejecución del programa se dirige hacia esas
instrucciones, y cuando la función termina, la ejecución regresa a la siguiente línea de la
función que hizo la llamada. Si llama 10 veces a la función, el programa salta al mismo
conjunto de instrucciones cada vez. Esto significa que sólo existe una copia de la función,
no 10.
Hay una disminución en el rendimiento al saltar hacia las funciones y regresar de ellas.
Resulta que algunas funciones son muy pequeñas (sólo una o dos líneas de código), y se
puede obtener algo de eficiencia si el programa puede evitar hacer estos saltos sólo para
ejecutar una o dos instrucciones. Cuando los programadores hablan de eficiencia, por lo
general se refieren a la velocidad; el programa se ejecuta más rápido si se puede evitar la
llamada a la función.
Si se declara una función con la palabra reservada in lin e , el com pilador no creará una
función real; copiará el código de esa función directamente en la función que hace la lla
mada. No se hace ningún salto; es como si usted hubiera escrito las instrucciones de la
función dentro de la función que hace la llamada.
Observe que las funciones en línea pueden tener serias desventajas. Si se llama 10 veces
a la función, cada una de esas 10 veces el código en línea se copia en las funciones que
hacen las llamadas. El pequeño aumento de velocidad que se puede lograr queda más
que sepultado por el aumento de tamaño del programa ejecutable. Por lo tanto, el aumento
Funciones 123
Recursion
Una función puede llamarse a sí misma. Esto se conoce com o recursion, y puede ser di
recta o in irecta. a recursión es directa cuando una función se llama a sí misma; es in-
írecta cuan o una unción llama a otra función, que a su vez llama a la primera función.
Algunos problemas se resuelven con más facilidad mediante la recursión, generalm ente
aquellos en los que se trabaja sobre los datos, y luego se trabaja de la m isma manera
sobre el resultado. Ambos tipos de recursión, directa e indirecta, vienen en dos varieda
des. las que finalmente terminan y producen una respuesta, y las que nunca terminan y
producen un error en tiempo de ejecución. Los programadores piensan que estas últimas
son algo graciosas (cuando le ocurre a otra persona).
Es importante tener en cuenta que cuando una función se llama a sí misma, se ejecuta una
nueva copia de esa función. Las variables locales de la segunda versión son independien
tes de las variables locales de la primera, y no se pueden afectar unas a otras directamente,
así com o las variables locales de main() no pueden afectar a las variables locales de
ninguna función que ésta llame, como se mostró en el listado 5.4.
Para tener un ejemplo de la solución de un problema por medio de la recursión, considere
la serie de Fibonacci:
1,1,2,3,5,8,13,21,34.
Funciones 125
Cada número después del segundo es la suma de los dos números anteriores. Un problema
podría ser determinar cuál es el duodécimo número de la serie.
Una manera de solucionar este problema es examinar cuidadosamente la serie. Los dos pri
meros números son l . Cada número subsecuente es la suma de los dos números anterio
res. Por lo tanto, el séptimo número es la suma de los números quinto y sexto. Viéndolo en
forma más general: el enésimo número es la suma de n-2 y n-1, siempre y cuando n > 2.
Las funciones recursivas necesitan una condición para detenerse. Algo debe ocurrir para
que el programa detenga la recursión, o ésta nunca terminará. En la serie de Fibonacci, n
< 3 es una condición de alto.
El algoritmo a utilizar es el siguiente:
1. Pedir al usuario una posición en la serie.
2. Llamar a la función f i b ( ) con esa posición, pasando el valor que escribió el
usuario.
3. La función f i b ( ) examina el argumento (n). Si n < 3, regresa 1; de no ser así, f i b ( )
se llama a sí misma (en forma recursiva) pasando el valor n-2, se llama a sí misma
otra vez pasando n -1, y regresa la suma.
Si llama a f i b (1), ésta regresa 1. Si llama a f i b( 2) , regresa 1. Si llama a f i b ( 3 ) , regresa
la suma de llamar a f i b (2) y f i b (1). Como f i b (2) regresa 1 y f i b ( 1 ) regresa 1, f i b ( 3 )
regresará 2.
Si se llama a fib(4), ésta regresa la suma de llamar a fib(3) y a fib(2). Ya establecimos
que fib(3) regresa 2 (al llamar a fib(2) y a fib(1)) y que fib(2) regresa 1, por lo que
fib(4) sumará estos números y regresará 3, que es el cuarto número de la serie.
Llevando a cabo un paso más, si se llama a fib(5), ésta regresará la suma de f ib(4) y
fib(3). Ya establecimos que fib(4) regresa 3 y que fib(3) regresa 2, por lo tanto, la
suma regresada será 5.
Este método no es la forma más eficiente para solucionar este problema (¡en f ib (20) la
función f ib () se llama 13,529 veces!), pero sí funciona. Tenga cuidado; si proporciona
un número demasiado grande, se acabará la memoria. Cada vez que se llama a f ib () se
reserva parte de la memoria. Al regresar se libera esa memoria. Con la recursión se sigue
reservando la memoria antes de liberarla, y este sistema puede agotar la memoria rápida
mente. El listado 5.10 implementa la función fib().
Al ejecutar el listado 5.10, utilice un número pequeño (menor que 15). Debido
a que aquí se utilizan la recursión y cout para cada llamada de la función,
ésta produce mucha salida y puede consumir mucha memoria.
126 Día 5
1: #include <iostream.h>
2:
3: int fib (int n);
4:
5: int main()
6: {
7:
8: int n, respuesta;
9: cout « “Escriba el número a encontrar: B;
10: cin » n;
11: cout « °\n\n";
12:
13: respuesta = fib(n);
14:
15: cout « respuesta « " es el número " « n << " en la serie de
»»Fibonacci\n";
16 return 0;
17 }
18 int fib (int n)
19 {
20 cout « "Procesando fib(” « n « ")... ";
21 if (n < 3)
22 {
23 cout « “iRegresa 1!\n";
24 return 1;
25 }
26 else
27 {
28 cout « "Llama a fib(" « n-2 « ") y a fib( « n -1 « ").\n";
29 return (fib(n-2) + fib(n-1));
30 }
31 }
Figura 5.4
Uso de la recursión.
^ no|j; v
En el ejemplo, n vale 6, por lo que se llama a f ib(6) desde main ( ) . La ejecu ción salta
hasta la función fib( ), y en la línea 21 se prueba n para ver si tiene un valor menor que
3. La prueba falla, por lo que fib(6) regresa la suma de los valores que regresan f ib (4)
y fib(5).
29: return(fib(n-2) + fib(n-1));
Esto significa que se hace una llamada a f ib(4) (debido a que n == 6, f ib ( n -2) es
igual que fib(4)) y se hace otra llamada a fib(5) ( f ib(n -1) ), y luego la función en la
que usted está (fib(6)) espera hasta que estas llamadas regresen un valor. Cuando esto
ocurre, esta función puede regresar el resultado de la suma de esos dos valores.
Como fib(5) pasa un argumento que no es menor que 3, se llamará a f ib () de nuevo,
esta vez con 4 y 3. A su vez, f ib(4) llamará a f ib(3) y a f ib(2).
La salida rastrea estas llamadas y los valores de retomo. C om pile, enlace y ejecute este
programa, escribiendo primero 1, luego 2, luego 3, y así hasta llegar a 6, y observe
cuidadosamente la salida.
Esta sería una buena oportunidad para empezar a experimentar con su depurador. Coloque
un punto de interrupción en la línea 20 y luego rastree internamente cada llamada a f ib (),
llevando un registro del valor de n al ir rastreando cada llamada recursiva a f ib( ) .
La recursión no se utiliza muy seguido en la programación en C++, pero puede ser una
herramienta poderosa y elegante para ciertas necesidades.
Niveles de abstracción
Uno de los principales obstáculos para los nuevos programadores es tratar con los varios
niveles de abstracción intelectual. Desde luego que las computadoras son sólo equipos
electrónicos. No saben nada acerca de ventanas y menús, ni de programas ni instruccio
nes, y ni siquiera saben nada sobre unos y ceros. Todo lo que está ocurriendo realmente
es que se está midiendo voltaje en varios lugares de un circuito integrado. Incluso esto es
una abstracción: la electricidad en sí es sólo un concepto intelectual que representa el
comportamiento de partículas subatómicas.
Pocos programadores se preocupan por saber que hay más allá de los valores en la RAM.
Después de todo, usted no necesita comprender la física de partículas para conducir un
auto, hacer pan tostado o pegarle a una pelota de béisbol, y no necesita comprender la
electrónica de una computadora para programarla.
Lo que si necesita es comprender cómo esta organizada la memoria. Sin tener una ima
gen mental razonablemente sólida de en dónde se encuentran sus variables cuando son
creadas y de cómo se pasan los valores entre las funciones, todo seguirá siendo un miste
rio indescifrable.
Partición de la RAM
Cuando usted inicia su programa, Linux configura varias áreas de memoria con base en
los requerimientos del programa compilado. Como programador de C++, a menudo se
preocupará por el espacio de nombres global, el heap, los registros, el espacio de código
y la pila.
130 Día 5
Las variables globales se encuentran en el espacio de nom bres global. 1lablarem os más
acerca del espacio de nombres global y del heap en los días que siguen, pero por ahora
nos enfocaremos en los registros, el espacio de código y la pila.
Los registros son un área especial de memoria construida dentro de la CPU (unidad cen
tral de procesamiento). Se encargan del mantenimiento interno. M ucho de lo que ocurre
en los registros está más allá del alcance de este libro, pero lo que nos interesa es el con
junto de registros responsables de apuntar, en cualquier m om ento dado, a la siguiente
línea de código. Llamamos a estos registros en conjunto el ap u n tad o r de instrucciones.
El trabajo del apuntador de instrucciones es m antener un registro de cuál línea de código
se debe ejecutar a continuación.
El código en sí se encuentra en el espacio de código, que es parte de la m em oria que se
reserva para guardar el archivo binario de las instrucciones que usted creó en su programa
(ese archivo binario es su programa ejecutable). Cada línea de código fuente se traduce en
una serie de instrucciones en lenguaje de máquina, y cada una de estas instrucciones se
encuentra en una dirección específica de memoria. El apuntador de instrucciones tiene la
dirección de la siguiente instrucción que se va a ejecutar. La figura 5.6 ilustra esta idea.
F i g u r a 5 .6 Espacio de código
E l ap u n ta d o r de
100 int x=5; Apuntador de
instrucciones.
101 int y=7; instrucciones
102 cout « x;
104 y=9;
105 return;
La p ila es un área especial de memoria asignada para que su program a guarde la infor
mación requerida por cada una de sus funciones. Se llam a pila porque es una lista en la
que el último elemento en entrar es el primero que sale, algo m uy parecido a un montón
de platos apilados en una cafetería, como se muestra en la figura 5.7.
F ig u r a 5 .7
Una pila.
Fundones 131
En esta pila lo que se agregue al último será lo primero que se pueda sacar. La mayoría
de las listas son como la fila de personas esperando a comprar boletos en un cine. El
primero de la fila es el primero que sale. Una pila es más parecida a un montón de mo
nedas apiladas. Si apila 10 monedas en una mesa y luego quita algunas, las últimas tres
que haya puesto serán las primeras tres que quite.
Cuando los datos se introducen en la pila, ésta crece; al sacar datos de la pila, ésta
decrece. No es posible sacar un plato de la pila sin sacar primero todos los platos que
estén sobre ese plato.
Una pila de platos es la analogía más común, sólo que está mal en cierta manera funda
mental. Una imagen mental más precisa es una serie de casillas alineadas de arriba hacia
abajo. La parte superior de la pila es cualquier casilla a la que el apuntador de la pila
(que es otro registro) esté apuntando en ese momento.
Cada una de las casillas tiene una dirección secuencial, y una de esas direcciones se
mantiene en el registro del apuntador de la pila. Todo lo que haya debajo de esa direc
ción mágica, conocida como la parte superior de la pila, se considera que está dentro de
la pila. Todo lo que esté encima de la parte superior de la pila se considera que está fuera
de la pila, y no es válido. La figura 5.8 ilustra esta idea.
102
Mi Edad 103
104
105
Cuando se colocan datos en la pila, se colocan en una casilla que esté encima del apunta
dor de la pila, y luego se mueve el apuntador de la pila hacia los nuevos datos. Cuando
se sacan datos de la pila, lo que realmente ocurre es que se cambia la dirección del apun
tador de la pila moviéndolo hacia abajo una posición. La figura 5.9 aclara esta regla.
132 D ía 5
102
MiEdad 103
104 fuera de la pila
105
SuEdad 106
107
108 <=3
109
110 } dentro de la pila
Además, puede crear archivos de proyecto (conocidos com o archivos m ake, en relación
con el comando que los utiliza: make). Estos archivos de proyecto controlan la com pi
lación y el enlace con base en reglas que usted crea. En la form a m ás sim ple, su progra
ma se volverá a compilar y enlazar si cualquiera de sus partes cam bia (código fuente
principal, archivos de encabezado y bibliotecas).
La siguiente sección le muestra cómo crear y utilizar bibliotecas sim ples de funciones; la
sección que sigue después de ésa presenta el com ando make de G N U (gmake).
El listado 5.11b contiene la función fib( ). Ésta se com pila en form a sep arad a del pro
gram a principal y se enlaza posteriormente.
1: #include <iostream.h>
2: tfinclude "Ist05-11.h"
3: // Listado 5.12 - Una muestra del uso de bibliotecas
4:
5: int main()
6: {
7:
8: int n, respuesta;
9: cout « "Escriba el número a encontrar:
10: cin » n;
11: cout « "\n\n";
12 :
13: respuesta = fib(n);
14:
15: cout « respuesta « " es el número " « n « " en la serie
*>de Fibonacci\n";
16: return 0;
17: >
Estas reglas se guardan en un archivo conocido com o m ake. fq u c tam b ién es el nombre
predeterm inado para el archivo), aunque se le puede d ar c u a lq u ie r nom bre.
El form ato general de un archivo make consiste en dos tipos d istin to s de líneas. La prime
ra es el destino/dependencia, la cual define el destino a crear ju n to con cu alq u ier archivo
del que dependa. Las otras líneas son los com andos requeridos para p ro d u cir ese destino.
El archivo make para los listados 5 .1la, 5.11 b y 5.12 se m uestra en el listado 5.13.
L a prim era línea muestra que lst05-11 .o (la biblioteca de fu n cio n es, nuestro destino)
depende de lst05-11 .cxx y de Ist05-I1 .h. La línea 2 es el c o m a n d o p ara producir
ese destino. La línea 3 es un comentario.
L a línea 4 muestra que lst05-12 (el archivo ejecutable, nu estro d estin o de este paso)
depende de Ist05-I2.cxx, lst05-11 .o y de lst05-1l .h . La línea 5 es el com ando para
producir ese destino.
C uando Ist05-11 .h cambie y usted trate de hacer el archivo ejecu tab le usando el si
guiente comando, creará todas las partes necesarias:
make -f lst05-13.mak lst05-12
g++ -c lst05-11.cxx
S a l id a gcc: -lgpp: linker input file unused since linking not done
gcc: -lstdcx: linker input file unused since linking not done
gcc: -lm: linker input file unused since linking not done
g++ lst05-12.cxx lst05-11.o -o lst05-12
El destino especificado es lst05-12. Éste necesita a Ist05-11 .o , y a lst05-
A n á l is is
11 . h. Ist05 -11.o necesita a lst05 -11 . h. Yo cam bié a lst05 -11 . h (agregué
un espacio adicional al final de una línea) antes de em itir el com ando make. Este coman
do es lo suficientemente inteligente como para determ inar si hay otras dependencias o
Funciones 137
no. Determinó que como lst0 5 -12 necesita a ls t 0 5 -11. o, y com o lst0 5 -11. h cam bió
desde la última vez que se creó lst0 5 -1 1 .o, era necesario volver a com pilar a ls t0 5 -
11. cxx antes que a lst0 5 -12. cxx.
El comando make es mucho más sofisticado de lo que muestra este sencillo ejem plo. Dé
un vistazo a las páginas del manual para obtener mayor información; make soporta un
lenguaje de programación propio.
Usted puede crear archivos de proyecto sencillos como éste para sus programas. A sí no
necesita recordar cuáles son las dependencias.
Funciones matemáticas
La biblioteca estándar de funciones matemáticas le permite realizar operaciones m ate
máticas comunes (y algunas no tan comunes). Esto hace que usted se preocupe por la
forma de utilizar los resultados en lugar de preocuparse por cómo calcular los resultados.
Por ejemplo, hay una función que calcula la raíz cuadrada de un número positivo. Como
programador, usted no necesita saber cómo codificar esto (otro programador ya lo ha
hecho). Sólo necesita saber qué hacer con los resultados (o por qué quiere obtener la raíz
cuadrada de un número).
Algunas de las funciones matemáticas que se utilizan con más frecuencia se m uestran en
la tabla 5.1.
138 D ía 5
Para todas estas funciones se debe incluir el archivo de encabezado m a th . h (para obtener
los prototipos funcionales).
Al igual que las funciones matemáticas, ¡estas funciones existen para facilitarle la vida!
En la tabla 5.2 se muestran algunas de las funciones de cadenas y de caracteres que se
utilizan con más frecuencia.
Debe incluir el archivo de encabezado s t r i ng. h para todas estas funciones (para obtener
los prototipos funcionales).
Funciones generales
Existen muchas otras funciones que son parte de la biblioteca estándar. Algunas de las fun
ciones más comunes que no son matemáticas ni de caracteres se muestran en la tabla 5.3.
Ta b l a 5.3 Otras funciones utilizadas con frecuencia
Fundón Uso
System (s1) Envía a S1 como comando al sistema operativo o al interprete de
comandos.
malloc (n) Asigna n bytes de memoria y regresa un apuntador a la dirección.
Regresa NULL en caso de fallar.
free (ptr) Libera la memoria asignada por m a llo c o r e a l l o c .
realloc (ptr, n) Asigna n bytes que aumentan o dism inuyen el espacio asignado pre
viamente (por m a llo c o r e a l l o c ) . Permite la im plem entaeión de
arreglos “dinámicos”.
abs (n) Regresa el valor absoluto entero de n: a b s ( -1 ) es 1. abs (0) es
0 y abs (1) es l .
asctime (t) Convierte el tiempo de formato interno t en un form ato imprimible.
bsearch () Realiza una búsqueda binaria en una estructura de datos ordenada.
qsort () Ordena una estructura de datos con el m étodo rápido (quick sort).
isalpha (c) Regresa t r u e si el carácter c es alfabético.
isdigit (c) Regresa tr u e si el carácter c es un dígito numérico.
islower (c) Regresa tr u e si el carácter c es alfabético y está en minúscula.
isupper (c) Regresa t r u e si el carácter c es alfabético y está en mayúscula.
Existen muchas, pero muchas otras funciones disponibles en la b ib lio teca estándar.
R evise las paginas del manual o el archivo in f o para o b ten er m ay o r in fo rm ació n acerca
de estas y otras funciones. Algunas de las funciones son m uy c o m p licad as (co m o
bsearch () y qsort ()) y requieren de mucha más explicación, la cual se p u ed e hallar en
la documentación.
Mucho más
Recuerde, la reutilización de código es uno de los aspectos básicos de C y de C ++. Es
tam bién una de las bases de la ingeniería de software. C ualquier có d ig o q u e pueda volver
a utilizar es código que no tiene que escribir como si fuera nuevo, ni le tiene q u e dar
m antenim iento. Puede ser mucho más resistente y se puede p ro b ar con m ás intensidad
que si fuera escrito por programadores individuales. Y si se requieren cam b io s, se pueden
hacer en un solo lugar (en la biblioteca), en lugar de tener que rastrear to d o el código.
Revise las bibliotecas estándar (de C y C++) antes de escribir sus propias funciones (tal vez
ya exista una que pueda resolver su problema). Tal vez necesite varias funciones de la bi
blioteca estándar para realizar la tarea que requiere, pero esto le ahorrará tiem po y esfuerzo.
Recuerdo un proyecto durante el cual salí de la ciudad para asistir a una conferencia. Había
un consultor joven en el proyecto. Era un buen codificador y entendía bien el lenguaje C,
pero no estaba muy familiarizado con la biblioteca. Necesitaba ordenar cierta inform ación
Funciones
dentro de su programa (no era mucha, por lo que el algoritmo de ordenam iento no era
importante). Debido a que muy pocas personas memorizan la manera de escribir rutinas
para ordenar datos, él tuvo que investigar a fondo en sus apuntes de la universidad, co
dificar, probar y ajustar el código. Esto le tomó varias horas durante un par de días. Yo
regresé de la conferencia, y al enterarme de lo que estaba batallando con la codificación,
le mostré la página de la función q s o rt () de la biblioteca estándar.
En una o dos horas en el mismo día, usando una función estándar (y una función especial
para determinar el ordenamiento de los datos), él habría podido ordenar sus datos, pero
le tomó más tiempo, tuvo que trabajar más duro, y creó más código que mantener. Utilicé
el género masculino en esta descripción porque la persona con la que trabajé era hombre.
Se puede cometer el mismo error sin importar cuál sea el género.
Resumen
En este capítulo se presentaron las funciones. Una función es, en efecto, un subprogram a
al que usted puede pasar parámetros y del que puede regresar un valor. Todo program a
de C++ empieza en la función main (), la que a su vez puede llamar a otras funciones.
Una función se declara con un prototipo de función, el cual describe el valor de retom o,
el nombre de -la función y los tipos de sus parámetros. Como una opción, puede declarar
una función en línea. Un prototipo de función también puede declarar variables con va
lores predeterminados para uno o más de sus parámetros.
La definición de la función debe concordar con el prototipo de la función en el tipo de
valor de retorno, nombre y la lista de parámetros. Los nombres de funciones se pueden
sobrecargar cambiando el número o el tipo de los parámetros; el compilador encuentra la
función correcta basándose en la lista de argumentos.
Las variables locales de las funciones, junto con los argumentos que se pasan a la función,
son locales para el bloque en el que se declaran. Los parámetros que se pasan por valor son
copias y no pueden afectar el valor de las variables de la función que hace la llamada.
Preguntas y respuestas
P ¿ P o r qué no h a ce r todas las variables globales?
R Durante un tiempo, ésta era exactamente la manera en que se programaba. Sin
embargo, a medida que los programas se fueron haciendo más complejos, se hizo
muy difícil encontrar errores en los programas porque cualquiera de las funciones
podía alterar los datos (los datos globales se pueden cambiar en cualquier parte del
programa). Años de experiencia han convencido a los programadores de que los
datos se deben mantener tan locales como sea posible, y el acceso para cam biar
esos datos se debe definir estrechamente.
142 D ía 5
Taller
El taller le proporciona un cuestionario para ayudarlo a afian zar su c o m p re n sió n del ma
terial tratado, así como ejercicios para que experim ente con lo q u e ha ap ren d id o . Trate de
responder el cuestionario y los ejercicios antes de ver las resp u estas en el ap én d ice D,
R espuestas a los cuestionarios y ejercicios”, y asegúrese de c o m p re n d er las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Cuales son las diferencias entre el prototipo de la función y ln d e fin ició n de la
función?
2. ¿Tienen que concordar los nombres de los parám etros en el p ro to tip o , la definición
y la llamada a la función?
3. ¿Cóm o se declara una función si no regresa un valor?
4. Si no declara un valor de retomo, ¿qué tipo de valor de re to m o se asu m e?
5. ¿Q ué es una variable local?
Funciones
6. ¿Qué es el alcance?
7. ¿Qué es la reclusión?
8. ¿Cuándo debe utilizar variables globales?
9. ¿Qué es la sobrecarga de funciones?
10. ¿Qué es el polimorfismo?
Ejercicios
1. Escriba el prototipo para una función llamada P e r i m e t r o ( ). la cual regresa un
valor de tipo u n s i g n e d l o n g i n t y acepta dos parámetros, ambos de tipo
u n sig n e d short in t.
2. Escriba la definición de la función Perimetro! ) como se describe en el ejercicio 1.
Los dos parámetros representan la longitud y el ancho de un rectángulo. Haga que
la función regrese el perímetro (dos veces la longitud más dos veces el ancho).
3. CAZA E R R O R E S : ¿Qué está mal en la función del siguiente código?
/ ¿in c lu d e < i o s t r e a m . h >
v o id m iF u n c (u n sig n e d sh o rt in t x);
i n t m a in ( )
{
u n s i g n e d s h o r t i n t x, y;
y = m iF u n c (in t);
c o u t << " x : " « x << " y: " « y << 11\ n " ;
}
i n t m iF u n c (u n s ig n e d s h o r t i n t x);
{
r e t u rn ( 4 * x ) ;
}
5. Escriba una función que acepte dos argumentos de tipo u n s i g n e d s h o r t i n t y
que regrese el resultado de la división del primero entre el segundo. No haga la
división si el segundo número es igual a cero, pero regrese el valor de -1.
| 144 Día 5 — - - ----- -*
6 . Escriba un programa que pida dos núm eros al u su a rio y q u e lla m e a la función que
escribió en el ejercicio 5. Imprima la respuesta o im p rim a un m e n sa je d e error si
obtiene -1.
7. Escriba un programa que pida un núm ero y una potencia. E scrib a una función
recursiva que eleve el número a esa potencia. Por ejem p lo , si el n ú m ero es 2 y la
potencia es 4, la función debe regresar 16.
Se m a n a 1
D ía §
Clases base
Las clases extienden las capacidades integradas de C++ para ayudarlo a re
presentar y resolver problemas complejos del mundo real. Hoy aprenderá lo
siguiente:
• Qué son las clases y los objetos
° Cómo definir una nueva clase y crear objetos de esa clase
• Qué son las funciones miembro y los datos miembro
• Qué son los constructores y cómo utilizarlos
Las variables miembro, también conocidas como datos miembro, son las variables de la
clase. Las variables miembro son parte de la clase, así como las ruedas y el motor son
parte del auto.
Por lo general, las funciones de la clase manipulan a las variables miembro. Estas fun
ciones se conocen como funciones miembro o métodos de la clase. Entre los métodos de
la clase Auto se podrían incluir Arrancar!) y Frenar!). Una clase llamada Gato podría
tener datos miembro que representen la edad y el peso; entre sus métodos se podrían incluir
Dormir!), Maullar!) y PerseguirRatones!).
Las funciones miembro, también conocidas como métodos, son las funciones de la
clase. Las funciones miembro son parte de una clase de la misma manera que las
variables miembro. Ellas determinan lo que la clase puede hacer.
Algunos autores llaman funciones miembro a todas las funciones incluidas en una clase
y reservan el término método para las funciones miembro públicas.
C++ es sensible al uso de mayúsculas, y todos los nom bres de las c lase s d eb en seguir el
m ism o patrón. Establezca un patrón de denom inación para clases, v a ria b les y funciones,
de esta forma, nunca tendrá que revisar la m anera de d e letrea r el n o m b re de una clase;
¿era Rectángulo, rectángulo o RECTANGULO? A alg u n o s p ro g ra m a d o re s les gusta colo
car un prefijo en cada nombre de clase, form ado por una letra e sp e c ífic a (p o r ejemplo,
cGato o cPersona), mientras que otros utilizan sólo m ay ú scu las o so lo m in ú scu las para
el nom bre. La convención utilizada en este libro es d e n o m in a r to d as las c lases con la
prim er letra en mayúscula, como en Gato y Persona.
D e la misma manera, muchos programadores em piezan todas las funciones con mayúscula
y todas las variables con minúscula. Las palabras por lo gen eral se sep aran con un guión
bajo, como en Perseguir_Raton, o poniendo en m ayúscula la p rim er letra de cada pala
bra, por ejemplo, PerseguirRaton o Dibu jarCirculo.
C om o mencionamos hace unos días al hablar sobre las v ariab les, lo im p o rtan te es que
usted escoja un estilo y lo mantenga en todos sus program as. C on el tiem p o , su estilo
evolucionará para incluir no sólo convenciones de d en o m in ació n , sin o tam b ién sangrías,
alineación de las llaves y estilo de com entarios.
Es m u y c o m ú n q u e las c o m p a ñ ía s d e d e s a r r o llo t e n g a n e s t á n d a r e s in te r n o s
p ara m u c h a s c u e stio n e s r e la c io n a d a s c o n lo s e s tilo s . E s t o a s e g u r a q u e lo s
d e sa rro lla d o re s p u e d a n leer f á c ilm e n t e el c ó d i g o d e o t r o s d e s a r r o lla d o r e s .
A u n c u a n d o n o h a y e stá n d a r e s fo r m a le s , lo s g r u p o s d e s a r r o lla n s u s m é t o d o s
preferidos.
Declaración de un objeto
U sted declara un objeto de su nuevo tipo de la m ism a form a en que d e cla ra una variable
de tipo entero:
unsigned int PesoNeto; // declaración de un entero sin signo
Gato Pelusa; // declaración de un Gato
Este código declara una variable llamada PesoNeto, cuyo tipo es entero sin signo. También
declara a Pelusa, que es un objeto cuya clase (o tipo) es Gato.
,áim
, 'VíW
Clases base 149
El com pilador marcaría esto com o error porque no puede a sig n a r 5 a la p arte de la edad
de un Gato. En lugar de eso, debe definir un objeto G ato y a sig n a r 5 a ese objeto, por
ejemplo:
Gato P e lu s a ; // i g u a l que i n t x;
P e lu s a . s u E d a d = 5; // i g u a l que x = 5;
Si no lo declara, su clase no lo te n d rá
Pruebe este experimento: camine hacia un niño de tres añ o s y m u éstrele un gato. Luego
diga: “Éste es Pelusa. Pelusa sabe un truco. Pelusa, lad ra” . El niñ o reirá v dirá: “ No.
tonto, los gatos no pueden ladrar” .
Si escribiera
Gato P e lu sa; // h a c e r un Gato lla m a d o P e l u s a
P e lu sa .L a d ra r() // d e c i r a P e l u s a que l a d r e
el compilador diría: “No, tonto, los gatos no pueden lad rar” . En realid ad , el compilador
GNU le dirá:
e jem plo .cxx: I n f u n c t i o n ' i n t m a i n ( ) ' :
e je m p lo .cxx:2 5 : no member f u n c t i o n ' G a t o : : L a d r a r () ' d e f i n e d
El compilador sabe que Pelusa no puede ladrar porque la clase G ato no tiene una función
L a d ra r(). El compilador ni siquiera dejaría que Pelusa m au llara si usted no definiera
una función M aullar ().
D ebe N O BEBE
D EBE utilizar la palabra reservada class NO D E B E co nfun d ir una declaración con
para declarar una clase. una definición. Una declaración de clase
D EBE utilizar el operador de punto (.) dice lo que es la clase. Una definición es
para tener acceso a los miembros y a las la im plem entación de los m étodos y fun
funciones de la clase. ciones que se encuentran en la declara
ción de clase. Una declaración de objeto
reserva m em oria para un objeto.
NO D EB E co nfun dir una clase con un
objeto.
NO D EB E asignar valores a una clase.
Asigne valores a los datos m iem bro de
un objeto.
Clases base 151
el compilador marca esto como error. En efecto, usted dijo al compilador: “Voy a acceder
a suEdad, suPeso y Maullar () sólo dentro de funciones miembro de la clase Gato” . Y
aquí accedió a la variable miembro suEdad del objeto Botas desde afuera de un m étodo
de la clase Gato. El hecho de que Botas sea un objeto de la clase Gato no significa que
usted pueda acceder a las partes de Botas que sean privadas.
Esto produce mucha confusión en los programadores novatos de C++. Casi puedo escu
charlo gritar: “ ¡Hey! Acabo de decir que Botas es un Gato. ¿Por qué Botas no puede acce
der a su propia edad?” La respuesta es que Botas sí puede, pero usted no. Botas, dentro de
sus propios métodos, puede acceder a todas sus partes, públicas y privadas. Que usted
haya creado una clase Gato no significa que pueda ver o cambiar las partes de ella que
sean privadas.
152 D ía 6
La manera de utilizar Gato para que pueda acceder a los d a lo s m iem b ro es la siguiente:
class Gato
{
public:
unsigned int suEdad;
unsigned int suPeso;
void Maullar();
Entrada L is t a d o 6.1 Cómo acceder a los miembros públicos de una clase simple
La función principal del programa empieza en la línea 14. Pelusa se declara en la línea
16 como una instancia de Gato (es decir, un objeto Gato). En la línea 17, la edad de
Pelusa se establece en 5. En las líneas 18 y 19 se utiliza la variable miembro suEdad
para imprimir un mensaje acerca de Pelusa.
Trate d e con ve rtir la línea 8 en com e ntario e intente v o lv e r a com pilar. Reci
Nata birá u n m e n saje d e error en las líneas 17 y 19 d e b id o a q u e su E d a d ya n o
te n d rá acceso público. El acceso pre d e te rm in ad o para las clases es el p riv a
do. El c o m p ila d o r G N U le indicará esto de la sigu ie n te m an e ra:
lst06-01 .cx x : In fu n c t io n 'i n t m a in ( ) ':
Ist06-01.cxx:17: member 'suEdad' is a private member of class 'Gato'
lst06-01.cxx:19: member 'suEdad' is a private member of class 'Gato'
Si una función que necesita saber la edad de un Gato accede directamente a la variable
miembro suEdad, sería necesario volver a escribir esa función si usted, como autor de la
clase Gato, decidiera cambiar la forma en que se guarda ese dato. Al hacer que la función
llame a ObtenerEdad (), su clase Gato puede fácilmente regresar el valor correcto sin impor
tar cómo llegue a la edad. La función que hace la llamada no necesita saber si usted la está
guardando como entero sin signo o como entero largo, o si la está calculando a medida que
la necesita.
154 D ía 6
A nálisis Esta clase tiene cinco métodos públicos. Las líneas 9 y 10 c o n tien en los métodos
de acceso para suEdad. En las líneas 12 y 13 están los m étodos de acceso para
suPeso. Estas funciones de acceso asignan valores a las variables m iem b ro y regresan
sus valores.
La p a la b ra re se rvad a c la s s
La sintaxis para la palabra reservada c la ss es la siguiente:
c la s s nombre_clase
{
// aquí van las palabras reservadas de control de acceso
// aquí se declaran las variables y los métodos de la clase
>;
La palabra reservada cla s s se utiliza para declarar nuevos tipos. Una clase es una colección
de datos miembro que son variables de diversos tipos, incluyendo a otras clases. La clase
también contiene funciones (o métodos) que se utilizan para manipular los datos de la
clase y para realizar otros servicios para la clase.
Usted crea objetos del nuevo tipo en forma muy parecida a la que declara cualquier va
riable. Declara el tipo (class) y luego el nombre de la variable (el objeto). Accede a los
miembros y a las funciones de la clase mediante el operador de punto (.).
Utiliza palabras reservadas de control de acceso para declarar secciones de la clase como
públicas o privadas. El control de acceso predeterminado es privado. Cada palabra reser
vada cambia el control de acceso desde ese punto hasta el final de la clase o hasta la
siguiente palabra reservada de control de acceso. Las declaraciones de clases terminan
con una llave de cierre y un punto y coma.
Ejemplo 1
class Gato;
{
public:
unsigned int suEdad; 6
unsigned int suPeso;
void Maullar();
>;
Gato Pelusa;
Pelusa.suEdad = 8;
Pelusa.suPeso = 18;
Pelusa.Maullar();
156 Día 6
Ejemplo 2
class Auto
{
public: // l os s i g u i e n t e s cinco son
públicos
void Encender();
void Acelerar();
void Frenar();
void AsignarAnio(int anio);
int ObtenerAnio();
private: // el r e s t o es p r i v a d o
int Anio;
char Modelo [255];
}; // fin de la d e c l a r a c i ó n de la c l a s e
Auto ViejoFiel; // h a c e r u na i n s t a n c i a d e un a u t o
int comprado; // una v a r i a b l e l o c a l d e t i p o int
ViejoFiel.AsignarAnio(84); // a s i g n a r 84 al a ñ o
comprado = ViejoFiel.ObtenerAnio(); // asignar 84 a c o m p r a d o
ViejoFiel.Encender(); // l l a ma r al m é t o d o p a r a e n c e n d e r
el auto
D ebe N O DEBE
DEBE declarar las variables miembro NO DEBE tratar de u tilizar variables
como privadas. miembro privadas desde afuera de la
DEBE utilizar métodos de acceso públicos. clase.
DEBE acceder a las variables miembro
privadas desde el interior de las fun
ciones miembro de la clase.
La definición de un método empieza con el nombre de la clase, seguido por dos signos de -
dos puntos (::), el nombre del método y sus parámetros. El listado 6.3 m uestra la decla
ración completa de una clase Gato simple y la implementación de sus m étodos de acceso
y un método general de la clase.
Clases base 157
1: // Muestra de la d e c la ra c ió n de una c la se y
2: // la d e f i n i c i ó n de l o s métodos de la clase,
3:
4: tfinclude < io stre a m .h > // para cout
5:
6: c l a s s Gato // empieza la d e c la ra c ió n de la c la s e
7: {
8: p u b lic : // empieza la se cció n p ú b lic a
9: i n t O b tenerEdad(); // método de acceso
10: vo id Asig n a rE d a d ( i n t edad); II método de acceso
11: vo id M a u l l a r (); // método general
12 : p r iv a t e : // empieza la se cció n privad a
13: i n t suEdad; // v a ria b le miembro
14: };
15:
16: // ObtenerEdad, método de acceso público
17: // re gre sa e l v a l o r de la propiedad suEdad
18: i n t G a to ::O b te n e rE d a d ()
19: {
20: retu rn suEdad;
21: }
22:
23: // d e f i n i c i ó n de A signarEdad, método
24: // de acceso p ú b lic o
25: // da un v a l o r a la propiedad suEdad
26: vo id G a to : : A s ig n a r E d a d ( i n t edad)
27: {
28: // dar a l a v a r i a b l e miembro suEdad el
29: // v a l o r pasado por e l parámetro edad
30: suEdad = edad;
31: }
32:
33: // d e f i n i c i ó n del método M au llar
34: // re g re sa : vo id
35: // parámetros: Ninguno
36: // acción: Imprime "miau" en la p antalla 6
37: void G a t o : : M a u l l a r ( )
38: {
39: cout « "M ia u . \ n " ;
40: }
41:
42: // cre a r un gato, a s i g n a r un v a lo r a su edad, hacer que
43: // maúlle, que nos diga su edad y que vuelva a maullar.
44: i n t main()
45: {
continua
158 Día 6
L is t a d o 6 .3 continuación
Miau.
S a l id a Pelusa es un gato que tiene 5 años de edad.
Miau.
Las líneas 6 a 14 contienen la declaración de la clase Gato. La línea 8 contiene la
A nálisis
palabra reservada public, que le indica al com pilador que lo que sigue a conti
nuación es un conjunto de miembros públicos. La línea 9 tiene la declaración del método
de acceso público llamado ObtenerEdad(). Este método proporciona acceso a la variable
miembro privada suEdad, la cual se declara en la línea 13. La línea 10 contiene el m é
todo de acceso público llamado AsignarEdad (). Este método tom a un entero com o argu
mento y asigna a suEdad el valor de ese argumento.
La línea 11 tiene la declaración del método Maullar () de la clase. Este m étodo no es una
función de acceso. Es un método general que imprime la palabra M iau en la pantalla.
En la línea 12 comienza la sección privada, que incluye sólo la declaración en la línea 13
de la variable miembro privada suEdad. La declaración de la clase term ina en la línea 14
con una llave de cierre y un punto y coma.
Las líneas 18 a 21 contienen la definición del método ObtenerEdad (). Este m étodo no
toma parámetros; regresa un entero. Observe que los métodos de clases incluyen el nom
bre de la clase seguido por dos signos de dos puntos (::) y el nom bre del m étodo (línea
18). Esta sintaxis le indica al compilador que el método ObtenerEdad () que está defi
niendo es el que declaró en la clase Gato. Con excepción de esta línea de encabezado, el
m étodo ObtenerEdad () se crea de la misma manera que cualquier otro m étodo.
El método ObtenerEdad () ocupa sólo una línea; regresa el valor de suEdad. O bserve que
la función main( ) no puede tener acceso a suEdad debido a que ésta es privada para la
clase Gato. La función main() tiene acceso al método público ObtenerEdad (). C om o este
método es una función miembro de la clase Gato, tiene acceso com pleto a la variable
suEdad. Este acceso permite que ObtenerEdad () regrese el valor de suEdad a main ( ) .
La línea 26 contiene la definición del método de acceso AsignarEdad (). E n la línea 30,
este método toma un parámetro entero y asigna a la variable suEdad el valor de ese
parámetro. Como es un miembro de la clase Gato, AsignarEdad () tiene acceso directo a
la variable miembro suEdad.
Clases base 159
Preguntas frecuentes
FAQ: ¿S e llam a constructor predeterm inado p o rq u e n o tie n e a r g u m e n t o s , o p o r q u e lo
p rop orcio n a el com pilado r si yo no declaro u n o ?
R espuesta: U n constructor que no to m a a rg u m e n to s se lla m a c o n s tr u c t o r p r e d e t e r m in a
do, ya sea q u e el com pilador lo cree por usted, o q u e lo cree u ste d m is m o . U s t e d re c ib e
un constructor predeterm inado de forma predeterminada.
En caso de que el constructor no tuviera parámetros (es decir, que fuera un constructor
predeterminado), omitiría los paréntesis y escribiría
Gato Pelusa;
Ésta es una excepción a la regla que declara que todas las funciones requieren paréntesis,
aunque no lleven parámetros. Ésta es la razón por la cual usted puede escribir
Gato Pelusa;
Esto se interpreta como una llamada al constructor predeterminado. No proporciona
parámetros, y omite los paréntesis.
Observe que no tiene que usar el constructor predeterminado proporcionado por el com
pilador. Siempre es libre de escribir su propio constructor predeterminado (es decir, un
constructor sin parámetros). Usted puede dar a su constructor predeterminado un cuerpo
de función en el que podría inicializar la clase.
Tenga en cuenta que, por cuestión de forma, si declara un constructor, debe declarar un
destructor, aunque su destructor no haga nada. Aunque es cierto que el destructor prede
terminado funcionaría correctamente, nunca está de más declarar su propio constructor.
Ayuda a que su código sea más claro.
El listado 6.4 vuelve a escribir la clase Gato para utilizar un constructor que inicialice el
objeto Gato, asignando a su edad el valor que usted proporcione, y muestra dónde se
llama al destructor.
L is t a d o 6 .4 continuación
Miau.
S alida Pelusa es un gato que ti ene 5 años de edad.
Miau.
Ahora Pelusa ti e ne 7 años de edad.
El listado 6.4 es similar al listado 6.3, excepto que la línea 9 agrega un construc
A nálisis
tor que toma un entero como parámetro. En la línea 10 se declara el destructor, el
cual no toma parámetros. Los destructores nunca llevan parámetros, y ni los construc
tores ni los destructores regresan valores, ni siquiera void.
Las líneas 19 a 22 muestran la implementación del constructor. Ésta es similar a la
implementación del método de acceso A s i g n a r E d a d ( ). No hay valor de retorno.
Las líneas 24 a 26 muestran la implementación del destructor -Gato. Esta función no
hace nada, pero usted debe incluir la definición de la función si lo especifica en la
declaración de la clase.
La línea 58 contiene la declaración de un objeto de la clase Gato, llamado P e l u s a . El
valor 5 se pasa al constructor de Pe lus a. No se necesita llamar a A s i g n a r E d a d ( ) porque
P e l u s a se creó con el valor 5 en su variable miembro suEdad, como se muestra en la
línea 61. En la línea 63 se asigna 7 a la variable suEdad de P e lu s a . La línea 65 imprime
el nuevo valor.
D ebe N O DEBE
DEBE utilizar constructores para inicia- NO DEBE dar a los constructores o
lizar sus objetos. destructores un va lor de retorno.
NO DEBE d ar p arám e tro s a los d e stru c
tores.
AsignarEdad() no puede ser const porque cambia el valor de la variable miembro suEdad.
Por otro lado, ObtenerEdad() puede y debe ser const porque no cambia la clase para nada.
ObtenerEdad() simplemente regresa el valor actual de la variable miembro suEdad. Por
lo tanto, la declaración de estos métodos se debe escribir de la siguiente manera:
void AsignarEdad(int edad);
int ObtenerEdad() const;
Si declara una función como const, y la implementación de esa función cambia el objeto
al cambiar el valor de cualquiera de sus miembros, el compilador marcará un error. Por
ejemplo, si escribiera ObtenerEdad() de tal forma que llevara la cuenta del número de
veces que se pregunta la edad a Gato, generaría un error de compilación. Esto se debe a
que estaría cambiando al objeto Gato por medio de este método.
1: II Es te l i s t a d o * * N 0 * * compilará
2: // Muestra l o s e r r o r e s de compilación
3:
4: # i nc lu d e <i os t re am .h > // para cout
5:
6: c l a s s Gato
7: {
8: public:
9: Gato(int e d a d l n i c i a l ) ;
10: - Ga t o ( ) ;
11: i n t ObtenerEdad() const; // función const de acceso
12: v oi d As ignar Edad ( i n t edad);
13: v oi d M a u l l a r ( );
14: private:
15: i n t suEdad;
16: };
17:
18: // c o n s t r u c t o r de Gato,
19: Gato::Gato(int edadlnicial)
20: {
21: suEdad = e d a d l n i c i a l ;
22: cout « " C o n s t r u c t o r de Gato\n";
23: }
24:
25: G at o : : - G a t o ( ) // des tr uctor , no r e a l i z a ninguna acción
26: {
27: cout « " D e s t r u c t o r deGato\n";
28: }
29: // ObtenerEdad, f unci ón const
30: // ¡pero estamos v i ol ando el uso de const!
31: i n t G at o : ¡ObtenerEdad() const
32: {
33: r et ur n (suEdad++); II v i o l a el uso de const!
34: }
35:
36: II d e f i n i c i ó n de AsignarEdad, método
37: i / de acceso pú bl ic o
38:
39: void G at o : ¡ A s i g na r E d a d( i n t edad)
40: {
41: // a s i g n a r a l a v a r i a b l e miembro suEdad el
continua
166 Día 6
L is t a d o 6 .5 continuación
A nálisis Así como está escrito, este programa no compilará. Por lo tanto, no hay salida.
La línea 11 declara ObtenerEdad() como función const de acceso (com o debe ser). Sin
embargo, en la línea 33, en el cuerpo de ObtenerEdad (), se incrementa la variable
miembro suEdad. Como este método está declarado como const, no debe cambiar el
valor de suEdad. Por lo tanto, se marca como error al compilar el programa.
En la línea 13, Maullar () no se declara como const. Aunque éste no es un error, es una
mala práctica de programación. En un mejor diseño se tomaría en consideración que este mé
todo no cambia las variables miembro de Gato. Por lo tanto, Maullar () debería ser const.
La línea 59 muestra la declaración de un objeto de la clase Gato llamado Pelusa. Ahora
Gato tiene un constructor, el cual toma un entero como parámetro. Esto significa que
usted debe pasarle un parámetro. Como no existe ningún parámetro en la línea 59, se
marcará como un error.
La línea 61 muestra una llamada a un método de clase, llamado L a d r a r (). Este método
nunca fue declarado. Por lo tanto, es ilegal.
La línea 62 muestra que se asigna el valor 7 a suEdad. Como suEdad es un dato miembro
privado, esto se marca como un error al compilar el programa.
¿ P o r q u é u t iliz a r el c o m p i l a d o r p a r a a t r a p a r e rr o r e s ?
A u n q u e sería m aravilloso escribir có d igo 1 0 0 % libre de errores, pocos p ro g ra m a d o re s
han sido capaces de hacerlo. Sin em bargo, m uchos program adores han d esarrollado un
sistema para ayu d ar a m inim izar los errores al atraparlos y solucionarlos con prontitud en
el proceso.
A u n q u e los errores de com pilación son exasperantes y son la ruina en la existencia de un
program ador, son m enos nocivos que la alternativa. Un lenguaje débilm ente tipificado le
permite violar sus contratos sin que el com pilador haga nada, pero su p ro gra m a fallará
en tiem po de ejecución (por ejemplo, cuando su jefe esté observando).
Los errores en tie m po de com pilación (es decir, errores encontrados al m o m e n to de la
com pilación) son m enos nocivos que los errores en tiem po de ejecución (es decir, los
errores qu e se encuentran al estar ejecutando el programa). Esto se debe a q u e los e rro
res en tie m po de com pilación se pueden encontrar con m ucha más confiabilidad. Es
posible ejecutar un p ro gra m a muchas veces sin pasar por cada posible ruta del código.
Por lo tanto, un error en tie m po de ejecución puede permanecer oculto por bastante
tiempo. Los errores en tie m po de com pilación se encuentran cada vez que se com pila. Por
lo tanto, son m ás fáciles de identificar y de solucionar. La meta de la p rogra m ación de
calidad es a se gu rar que el có d igo no te n ga errores en tiem po de ejecución. U na técnica
com probada para lograr esto es utilizar el com pilador para que atrape los errores lo m ás
pronto posible en el proceso de desarrollo.
L
| 168 Día 6
Los com piladores G N U dan por hech o q u e los arch ivo s q u e te r m in a n c o n .c son
Nota program as de C, y que los archivos d e p ro g ra m a s d e C + + te r m in a n c o n .cpp, .cxx
o .c++. Puede utilizar cualquier extensión, p e ro .cpp m in im iz a r á la c o n fu sió n .
También puede colocar la declaración en este archivo, pero ésa no es una buena práctica de
programación. La convención que adopta la mayoría de los programadores es colocar la
declaración en lo que se conoce como archivo de encabezado, generalmente con el mismo
nombre pero con extensión .h, .hp o .hpp. En este libro los nombres de los archivos de
encabezado terminan con .hpp, pero verifique qué terminación prefiere su compilador.
Por ejemplo, usted coloca la declaración de la clase Gato en un archivo llamado g a to . hpp,
y coloca la definición de los métodos de la clase en un archivo llamado g a t o . cxx. Luego
adjunta el archivo de encabezado al archivo fuente de C++ colocando el siguiente código
al principio de gato.cxx:
#include “gato.hpp"
Esto le indica al compilador que lea gato.hpp y que lo inserte en el archivo fuente, como
si usted lo hubiera escrito directamente. Nota: Algunos compiladores insisten en que el
uso de mayúsculas y minúsculas debe ser igual entre la instrucción #include y su sistema
de archivos. Si usted declara #include "gato. hpp" y guarda el archivo de encabezado
con el nombre GATO.hpp, posiblemente recibirá un error durante la compilación.
¿Por qué tomarse la molestia de separar el contenido de su archivo .hpp y su archivo
cxx si va a volver a insertar el contenido del archivo .hpp dentro del archivo .cxx? La
mayor parte del tiempo, los clientes de su clase no se preocupan por los detalles específi
cos de la implementación. Con sólo leer el archivo de encabezado obtienen todo lo que
necesitan saber, así que pueden ignorar los archivos de implementación. Además, usted
podría necesitar incluir el archivo .hpp en más de un archivo .cxx.
El uso de archivos inelude (de encabezado) es un ejemplo de una buena ingeniería de
software (es más sencillo reutilizar su código).
E n t r ad a L is t a d o 6 . 6 a D e c la r a c ió n d e la cla se G a to e n l s t 0 6 - 0 6 . h p p
1: // l s t 0 6 - 0 6 . h p p
2: //include < i os tr eam. h>
3: c l a s s Gato
c o n tin ú a
| 170
Día 6
L istado 6 .6 a continuación
5: public:
6: Gato (int edadlnicial);
7: -Gato();
8: int ObtenerEdad() const { return
9: void AsignarEdad (int edad) { suEdUEdad;* // ¡en linea!
10: void Maullar() const { cout « ..yd = edad;} // ien linea!
11: private: Mlau.\n n;J // ¡en linea!
12: int suEdad;
13: };
4: #include "lst06-06.hpp"
^ «n«e?^rese de incluir los archivos de
5: encabezado!
6:
7: Gato::Gato(int edadlnicial) //constructor
8:
9: suEdad = edadlnicial;
10 }
11
12 Gato::-Gato()
//destructor, no realiza ninguna acción
13 {
14 }
15
16 // Crear un gato, asignar un valor a su edad, hacer que maúlle,
17: // que nos diga su edad y que maúlle nuevamente,
18: int main()
19: {
20: Gato Pelusa(5);
21: Pelusa.Maullar();
22: cout « "Pelusa es un gato que tiene " ;
23: cout « Pelusa.ObtenerEdad() « " años de edad.\nM-
24: Pelusa.Maullar();
25: Pelusa.AsignarEdad(7);
26: cout « "Ahora Pelusa tiene " ;
27: cout « Pelusa.ObtenerEdad() « " años de edad.\n";
28: return 0;
29: }
Clases base 171
Miau.
S a l id a P e l u s a es un g a t o que t i e n e 5 años de edad.
Miau.
Ahora P e l u s a t i e n e 7 años de edad.
El código presentado en los listados 6.6a y 6.6b es similar al código del listado
A nálisis
6.4, excepto que tres de los métodos se escriben en línea en el archivo de decla
ración. y la declaración se lia separado en l s t 0 6 -0 6 .hpp.
En la línea 8 se declara ObtenerEdad(), y se proporciona su implementación en línea. Las
líneas 9 y 10 proporcionan más funciones en línea, pero la funcionalidad de estas funcio
nes permanece sin cambio a partir de las anteriores implementaciones “fuera de línea".
La línea 4 del listado 6.6b muestra //inelude " ls t 0 6 - 0 6 . hpp", lo que hace que se
incluya el listado de l s t 0 6 - 0 6 .hpp. Al incluir l s t 0 6 - 0 6 . hpp, ha indicado al precompi
lador que lea el archivo lst0 6 -0 6 .h p p y lo inserte en el archivo como si hubiera sido
escrito directamente ahí, empezando en la línea 5.
Esta técnica le permite colocar sus declaraciones en un archivo distinto al de su imple-
mentación, y aún así tener esa declaración disponible cuando el compilador la necesite.
Ésta es una técnica muy común en la programación en C++. Normalmente, las declara
ciones de clases se encuentran en un archivo .hpp que luego se incluye, mediante la
instrucción #in clu d e, en el archivo .cxx asociado.
Las líneas 18 a 29 repiten la función main() del listado 6.4. Esto muestra que convertir
los métodos en métodos en línea no cambia su rendimiento.
E n t r a d a L is t a d o 6 . 7 a D e c la r a c ió n d e u n a c la s e c o m p l e t a
1: // Inicio de lst06-07.hpp
2: #include <iostream.h>
3: class Punto // guarda las coordenadas (x, y)
4: {
5: //no hay constructor, usar el predeterminado
6: public:
7: void AsignarX(int x) { suX = x; }
8: void AsignarY(int y) { suY = y; }
9: int ObtenerX() const { return suX;}
10: int ObtenerYO const { return suY;}
11: private:
12: int suX;
13: int suY;
14: }; // fin de ladeclaración de la clase Punto
15:
16:
17: class Rectángulo
18: {
19: public:
20: Rectángulo (int superior, int izquierdo, int inferior, int derecho);
21: -Rectángulo () {}
22:
23: int ObtenerSuperior()const { return suSuperior; >
24: int Obtenerlzquierdo() const { return sulzquierdo; }
25: int ObtenerInferior() const { return sulnferior; }
26: int ObtenerDerecho() const { return suDerecho; }
27:
28: Punto ObtenerSupIzq() const { return suSupIzq; >
29: Punto ObtenerlnfIzq() const { return sulnflzq; }
30: Punto ObtenerSupDer() const { return suSupDer; }
31: Punto ObtenerlnfDer() const { return suInfDer; }
32:
33: void AsignarSupIzq(Punto Ubicación) {suSupIzq = Ubicación;}
34: void AsignarlnfIzq(Punto Ubicación) {sulnflzq = Ubicación;}
35: void AsignarSupDer(Punto Ubicación) {suSupDer = Ubicación;}
36: void AsignarlnfDer(Punto Ubicación) {suInfDer = Ubicación;}
37:
38: void AsignarSuperior(int superior) { suSuperior = superior; }
39: void Asignarlzquierdo (int izquierdo) { sulzquierdo = izquierdo; }
40: void Asignarlnferior (int inferior) { sulnferior = inferior; }
41: void AsignarDerecho (int derecho) { suDerecho = derecho; }
42:
43: int ObtenerArea() const;
44:
45: private:
46: Punto suSupIzq;
47: Punto suSupDer;
48: Punto sulnflzq;
Clases base 173
49: Punto su I n f D e r ;
50: int suSuperior;
51 : int su I z q ui er do
52: int sulnferior;
53: int suDerecho;
54: };
En t r a d a L is t a d o 6 .7 b l s t 0 6 - 0 7 . cxx
1: // I n i c i o de l s t 0 6 - 0 7 . c x x
2: # i n c l u d e " l s t 0 6 - 0 7 . hpp"
3: R e c t á n g u l o : : R e c t á n g u l o ( i n t s u p e r i o r , i n t i zqui er do, i n t i n f e r i o r ,
i n t derecho)
4: {
5: suSuperior = superior;
6: sulzquierdo = izquierdo;
7: sulnferior = inferior;
8: suDerecho = derecho;
9:
10 : s u S u p I z q .A s i g n a r X ( i z q u i e r d o ) ;
11: s u S u p I z q .A s i g n a r Y ( s u p e r i o r ) ;
12:
13: suSupDer.AsignarX(derecho);
14: suSupDer.AsignarY(superior);
15:
16: sulnfIzq.AsignarX(izquierdo);
17: sulnfIzq.AsignarY(inferior);
18:
19: suInfDer.AsignarX(derecho);
20: sulnfDer.AsignarY(inferior);
21: }
22:
23:
24: // c a l c u l a r área del r ect ángulo encontrando l o s lados,
25: // e s t a b l e c e r ancho y a l t u r a y luego m u l t i p l i c a r
26: i n t R e c t á n g u l o : : Obtener Ar ea() const
27: {
28: i n t Ancho = s uD er echo- s ulz qui er do;
29: int A ltu ra = suSuperior - sulnferior;
30: r et u rn (Ancho * A l t u r a ) ;
31: }
32:
33: int main()
34: {
35: // inicializar una variable Rectángulo local
36: Rectángulo MiRectangulo (100, 20, 50, 80);
37:
38: i n t area = M i R e c t a n g u l o .ObtenerArea();
39:
continua
174 Día 6
L istado 6 .7 b continuación
Área: 3000
S a lid a Coordenada X superior izquierda: 20
A nálisis En las líneas 3 a 14 del listado 6.7a se declara la clase Punto, la cual se utiliza
para guardar las coordenadas (x, y) específicas en una gráfica. A sí com o está
escrito, este programa no utiliza mucho la clase Punto. Sin embargo, otros métodos de
dibujo requieren del uso de Punto.
En las líneas 12 y 13, dentro de la declaración de la clase Punto, se declaran dos varia
bles miembro (suX y suY). Estas variables guardarán los valores de las coordenadas. Al
incrementarse la coordenada x, usted se mueve hacia la derecha en la gráfica. Al incre
mentarse la coordenada y, se mueve hacia arriba en la gráfica. Otras gráficas utilizan
diferentes sistemas. Por ejemplo, algunos programas que utilizan ventanas incrementan
la coordenada y a medida que se avanza hacia abajo en la ventana.
La clase Punto utiliza funciones de acceso en línea para obtener y asignar un valor a las
coordenadas X y Y declaradas en las líneas 7 a 10. Los objetos Punto utilizarán el cons
tructor y el destructor predeterminados. Por lo tanto, usted debe establecer sus coorde
nadas en forma explícita.
En la línea 17 empieza la declaración de la clase Rectángulo. Un Rectángulo consta de
cuatro puntos que representan sus esquinas (no los confunda con los cuatro parámetros
que representan los lados).
El constructor para Rectángulo (línea 20) toma cuatro enteros como parámetros, llamados
superior, izquierdo, inferior y derecho. Los cuatro parámetros para el constructor se co
pian en cuatro variables miembro (listado 6.7b), y luego se establecen los cuatro objetos
Punto.
Además de las funciones de acceso usuales, Rectángulo tiene una función llamada
ObtenerArea() declarada en la línea 43. En lugar de guardar el área com o variable, la
función ObtenerArea() calcula el área en las líneas 28 a 30 del listado 6.7b. Para hacer
esto, calcula el ancho y la altura del rectángulo, y luego multiplica estos dos valores.
Para obtener la coordenada x de la esquina superior izquierda del rectángulo, necesita
acceder al punto suSupIzq y pedir a ese punto el valor de suX. Como ObtenerSupIzq()
es un método de Rectángulo, puede acceder directamente a los datos privados de
Rectángulo, incluyendo suSupIzq. Como suSupIzq es un Punto y el valor suX de Punto
es privado, ObtenerSupIzq() no puede tener acceso directo a este dato. En vez de eso,
debe utilizar el método de acceso público llamado ObtenerX() para obtener ese valor.
Clases base 175
j
La línea 33 del lisiado 6.7b es el com ienzo del cuerpo del program a en sí. H asta la línea
36, no se ha asignado m em oria, y no ha pasado realmente nada. Lo único que usted hizo
fue decir al com pilador cóm o hacer un punto y cóm o hacer un rectángulo, en caso de que
se necesite uno.
En la línea 36 se define un Rectángulo pasando valores para Superior. Izquierdo,
Inferior y Derecho.
En la línea 38 se crea una variable local de tipo entero llam ada area. Esta variable g u ar
da el área del Rectángulo creado anteriormente. Area se inicializa con el valor regresado
por la función ObtenerArea() del objeto MiRectangulo.
Un cliente de Rectángulo podría crear un objeto de la clase Rectángulo y obtener su
área sin siquiera ver la im plem entación de ObtenerArea().
Ist06-07. hpp se m uestra en el listado 6.7a. Con sólo m irar el archivo de encabezado,
el cual contiene la declaración de la clase Rectángulo, el program ador sabe que
ObtenerArea() regresa un entero. La forma en que ObtenerArea() realiza su trabajo no
es de im portancia para el usuario de objetos de la clase Rectángulo. De hecho, el autor
de la clase Rectángulo podría cam biar el método ObtenerArea() sin afectar los pro g ra
mas que utilizan esta clase.
Preguntas frecuentes
FAQ : ¿ C u á l e s la d ife re n c ia e n tre d e clarar y d e fin ir?
R e sp u e sta : U n a d e c la ra c ió n p resen ta el n o m b re d e a lg o y las acciones q u e ese a lg o
p u e d e realizar. U n a d e fin ic ió n , a d e m á s d e presentarnos el nom bre, n o s dice q u é h ace y
c ó m o lo hace; es la im p le m e n ta c ió n d e lo q u e se presenta en la d eclaración.
Con unas cuantas excepciones, todas las definiciones son también declaraciones. Las
excepciones más im portantes son la declaración de una función global (un prototipo) y
la declaración de una clase (por lo general en un archivo de encabezado).
Uso de estructuras
Un pariente muy cercano de la palabra reservada class es la palabra reservada struct,
que se utiliza para declarar una estructura. En C++, una estructura es lo m ism o que una
clase, con la excepción de que sus miembros son públicos de m anera predeterm inada.
Puede declarar una estructura exactam ente igual que como declara una clase, y puede
darle los mism os datos m iem bro y funciones. De hecho, si sigue la buena práctica de pro
gramación de siem pre declarar explícitamente las secciones públicas y privadas de su
clase, no existirá ninguna diferencia.
176 Día 6
Resumen
Hoy aprendió cómo crear nuevos tipos de datos llamados clases. Aprendió cóm o definir
variables de estos nuevos tipos, las cuales se conocen como objetos.
Una clase tiene datos miembro, que son variables de diversos tipos, incluyendo a otras
clases. Una clase también incluye funciones miembro (también conocidas com o métodos).
Estas funciones miembro se utilizan para manipular los datos miembro y para realizar
otros servicios.
Los miembros de las clases, tanto datos como funciones, pueden ser públicos o pri
vados. Los miembros públicos son accesibles para cualquier parte del programa. Los
miembros privados son accesibles sólo para las funciones miembro de la clase.
Es una buena práctica de programación aislar la interfaz, o declaración, de la clase en un
archivo de encabezado. Por lo general, esto se hace en un archivo con extensión .hpp. La
implementación de los métodos de la clase se escribe en un archivo con extensión cxx.
Los constructores de clases inicializan objetos. Los destructores de clases destruyen obje
tos y se utilizan normalmente para liberar memoria asignada por los métodos de la clase.
Clases base 177
Preguntas y respuestas
P ¿Q ué tan g ra n d e es el o bjeto de una clase?
R Hl tamaño en memoria del objeto de una clase se determina por medio de la suma
de los tamaños de sus variables miembro. Los métodos de las clases no ocupan
espacio como parle de la memoria reservada para el objeto.
Algunos compiladores alinean variables en memoria de tal torma que las variables
de 2 bytes en realidad consumen un poco más de 2 bytes. Revise el manual de su
compilador para estar seguro, pero en este momento no necesita preocuparse por
estos detalles.
P Si d eclaro u n a clase Gato con un m iem bro privado llam ado suEdad, y luego
defino dos o b jeto s Gato, P e l u s a y S i l v e s t r e , ¿puede S i l v e s t r e acc ed er a la
v aria b le m iem b ro suE dad de P e l u s a ?
R Si. Los datos privados están disponibles para las funciones miembro de una clase,
e instancias diferentes de una clase pueden acceder a los datos entre sí. En otras
palabras, si P e l u s a y S i l v e s t r e son instancias de Gato, las funciones miembro de
P e l u s a pueden acceder a los datos de P e l u s a y también a los datos de S i l v e s t r e .
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su com prensión del ma
terial tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D
“Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Qué es el operador de punto, y para qué se utiliza?
2. ¿Cuál de las dos acciones reserva memoria, la declaración o la creación?
3. ¿Qué es la declaración de una clase, su interfaz o su implementación?
4. ¿Cuál es la diferencia entre datos miembro públicos y privados?
5. ¿Se pueden establecer métodos privados?
6. ¿Se pueden establecer datos miembro públicos?
7. Si declara dos objetos Gato, ¿pueden éstos tener distintos v alo res en sus datos
m iem bro su Edad?
8. ¿Terminan con un punto y coma las declaraciones de clases? ¿Y las definiciones de
los métodos de clases?
9. ¿Cuál sería el encabezado para un método de la clase Gato, llamado Maullar, que
no toma parámetros y regresa void?
10. ¿Qué función se llama para inicializar una clase?
Ejercicios
1. Escriba el código que declare una clase llamada Empleado con estos datos miem
bro: edad, aniosDeServicio y salario.
2. Vuelva a escribir la clase Empleado para hacer los datos miembro privados, y pro
porcione métodos de acceso públicos para obtener y asignar un valor para cada uno
de los datos miembro.
Clases base 179
3. Escriba un programa con la clase Empleado que cree dos empleados: que asigne
un valor a los datos miembro edad. a n i o s D e S e r v i c i o y s a l a r i o , y que imprima
sus valores.
4. Como continuación del ejercicio 3. proporcione un método de la clase Empleado
que reporte cuántos miles de pesos gana el empleado, redondeados al múltiplo de
1000 más cercano.
5. Cambie la clase Empleado de forma que pueda inicializar edad. a n i o s D e S e r v i c i o
y s a l a r i o al crear al empleado.
6. CAZA ERRORES: ¿Qué está mal en la siguiente declaración?
c l a s s Cuadrado
{
public:
i n t Lado;
}
7. CAZA ERRORES: ¿Por qué no es muy útil la siguiente declaración de clase ?
c l a s s Gato
{
i n t ObtenerEdad( ) c o n s t ;
private:
i n t suEdad;
};
8. CAZA ERRORES: ¿Cuáles son los tres errores que encontrará el compilador en
este código?
c l a s s TV
{
public:
void A s i g n a r E s t a c i o n ( i n t es tación) ;
i n t O b t e n e r E s t a c i o n () const;
private:
int suEstacion;
};
i n t main()
{
TV miTV;
m i TV .s uE st ac i on = 9;
TV.A s i g n a r E s t a c i ó n (10);
TV m i 0 t r a T V ( 2 ) ;
r et ur n 0 ;
}
Se m a n a 1
D ía 7
Más flujo de programa
Los programas realizan la mayor parte de su trabajo mediante la ramificación y
los ciclos. En el día 4, “Expresiones e instrucciones”, aprendió cómo ramificar
su programa por medio de la instrucción i f . Hoy aprenderá lo siguiente:
En C++, una etiqueta es sólo un nombre seguido de un signo de dos punto (:). La etiqueta
se coloca a la izquierda de una instrucción válida de C++ o en su propia línea, y un salto se
logra al escribir goto seguido del nombre de la etiqueta. El listado 7 .1 muestra esto.
contador: 1
Salida contador: 2
contador: 3
contador: 4
contador: 5
Completo. Contador: 5.
La instrucción goto
Para utilizar la instrucción goto, se escribe la palabra reservada goto se gu id a de un n o m b re
de etiqueta. Esto p rod u ce un salto incondicional hacia la etiqueta.
Ejemplo:
i f ( v a lo r > 10) go to e n d ; if (v a lo r < 10)
goto en djcou t « " Î E 1 v a lo r es 1 0 1 ";
end: cout « " l i s t o " ;
Ciclos while
Un ciclo w hile ocasiona que su programa repita una secuencia de instrucciones siempre
y cuando la condición de inicio permanezca verdadera (true). En el ejemplo de goto del
listado 7.1. contador se incrementaba hasta llegar a 5. El listado 7.2 muestra el mismo
programa reescrito para aprovechar las ventajas que ofrece un ciclo while.
184 Día 7
1: // L i s t a d o 7.2
2 : // Uso de c i c l o s con while
3:
4: tfinclude <iostream.h>
5:
6: i n t main()
7: {
8 : i n t contador = 0; // i n i c i a l i z a r la condición
9:
10 w hi l e( co n ta d or < 5) // evaluar que l a c o n d i c i ó n aun sea verdadera
11 {
12 contador++; // cuerpo del c i c l o
13 cout « "contador: " « contador << " \ n " ;
14 }
15
16 cout « "Completo. Contador: " « contador << " . \ n " ;
17 r et ur n 0;
18 }
contador: 1
S a lid a contador: 2
contador: 3
contador: 4
contador: 5
Completo. Contador: 5.
Este programa sencillo muestra los fundamentos del ciclo while. Se evalúa una
A n á l is is
condición, y si es verdadera, se ejecuta el cuerpo del ciclo while. En este caso, la
condición que se evalúa en la línea 10 es si el contenido de la variable contador es menor
que 5. Si la condición es verdadera, se ejecuta el cuerpo del ciclo; en la línea 12 se incre
menta contador, y en la línea 13 se imprime su valor. Cuando falle la instrucción condi
cional de la línea 10 (cuando contador ya no sea menor que 5), no se ejecutará más el
cuerpo del ciclo while (líneas 11 a 14). La ejecución del programa se irá hasta la línea 15.
La instrucción while
La sintaxis para la instrucción w hile es la siguiente:
while ( condición )
in st rucci ón ;
Entrada L is t a d o 7 .3 C ic lo s w h i l e c o m p le jo s
1: // L i s t a d o 7.3
2; // I n s t r u c c i o n e s w hi l e complejas
3:
4: tfinclude <i os t re am .h >
5:
6: i n t main()
7: {
8: unsi gn ed s h o r t chi co ;
9: unsigned long grande;
10: co ns t u n si gn ed s h o r t MAXCHIC0=65535;
11 :
12: cout << " E s c r i b a un número chico: ";
13: c i n >> c hi c o ;
14: cout << " E s c r i b a un número grande: ";
15: c in >> grande;
16:
17: cout << " c h i c o : " << chi co «
18:
19: // para cada i t e r a c i ó n , evaluar t r e s condi ciones
20: w hi l e ( c h i c o < grande && grande > 0 && chico < MAXCHICO)
21: {
22: íf ( c h i c o % 5000 == 0) // e s c r i b i r un punto cada 5 mi l l i n e a s
23: cout << " .";
24:
25: c hi co++;
26:
27: grande-=2;
28: }
i• o n m u n i
186 Día 7
L is t a d o 7.3 continuación
29:
30: cout « “\nChico: " « chico « “ Grande: “ << grande << endl;
31: return 0;
32: }
El listado 7.4 m uestra el uso de estas instrucciones. Esta vez el ju eg o se ha vuelto más
com plicado. Se invita al usuario a que escriba un núm ero chico y un núm ero grande,
un valor de salto y un núm ero de destino. El número chico se increm entará en uno, y
el número grande se decrem entará en 2. Cada vez que el núm ero chico sea un m últiplo
de salto, no se realizará el decrem ento. El juego termina si ch ic o se hace m ayor que
grande. Si el núm ero grande llega al destino en forma exacta, se im prim irá una instruc
ción y el ju ego term inará.
El objetivo del usuario es colocar un número de destino para el núm ero grande que haga
que se detenga el juego.
En t r a d a L is t a d o 7 .4 break y continue
1: // Listado 7.4
2: // Muestra de break y continue
3:
4: ^include <iostream.h>
5:
6: int main()
7: {
8: unsigned short chico;
9: unsigned long grande;
10 unsigned long salto;
11 unsigned long destino;
12 const unsigned short MAXCHIC0=65535;
13
14 cout « “Escriba un número chico: ";
15 cin » chico;
16 cout << "Escriba un número grande: ";
17 cin » grande;
18 cout « "Escriba un valor de salto: ";
19 cin » salto;
20 cout « "Escriba un número de destino: ";
21 cin » destino;
22
23 cout « "\n";
24
25 // establece las 3 condiciones para detener el ciclo
26 while (chico < grande && grande > 0 && chico < MAXCHICO)
27
28 {
29
30 chico++;
31
32 if (chico % salto O) // ¿saltar el decremento?
33 {
34 cout « "salto en « chico « endl;
continúa
1 88 Dí a 7
35: continué;
36: }
37 :
38: íf (grande == d e s t i n o ) // ¿ g r a n d e concuerda e xa cta me nte con destino?
39: {
40: c o u t << "¡D estino alcanzado! ;
41: br ea K ;
42: }
43 :
44: grande-2;
45: } //fin del cic lo w hile
46:
47: c o u t << "\nChico: " << chico << " Grande: " << g r a n d e << endl;
48 : r e t u r n 0;
49: }
Escriba un numero c h i c o : 2
Escriba un n ú m e r o g r a n d e : 20
Escriba un v a l o r de s a l t o : 4
Escriba un n ú m e r o de d e s t i n o : 6
salto en 4
salto en 8
C hico: 10 G r a n d e : 8
E n e s t e j u e g o , el u s u a r i o p e r d i ó ; c h i c o s e h i z o m a y o r q u e g r a n d e antes de
a l e a n z a r el n ú m e r o s e i s .
En l a l í n e a 2 6 s e p r u e b a n l a s c o n d i c i o n e s d e l c i c l o w h i l e . Si c h i c o a ú n es m eno r que
g r a n d e , g r a n d e e s m a y o r q u e 0. y c h i c o n o h a s o b r e p a s a d o el v a l o r d e MAXCHICO,
e n t o n c e s s e e n t r a al c u e r p o d e l c i c l o w h i l e .
Nota T a n to c o n t i n u é c o m o b r e a k se d e b e n u t iliz a r c o n c u i d a d o . S o n lo s c o m a n d o s
m á s p e lig r o s o s d e s p u é s d e g o to , casi p o r la s m i s m a s r a z o n e s . L o s p r o g r a m a s
q u e c a m b ia n r e p e n t in a m e n t e d e d ir e c c ió n s o n m á s d i f í c i l e s d e c o m p r e n d e r ,
y el u s o in d is c r im in a d o d e c o n t in u é y b r e a k p u e d e o c a s i o n a r q u e h a s t a u n
ciclo w h ile p e q u e ñ o se a ile g ib le .
La instrucción continue
c o n tin u e ; o c a s io n a q u e lo s c ic lo s w h i le o f o r e m p ie c e n d e n u e v o a l p r i n c i p i o d e l c i c lo .
V ea el lista d o 7.4 p a r a u n e je m p lo d e l u s o d e c o n t in u e .
La instrucción break
break; o c a s io n a la t e r m in a c ió n in m e d ia t a d e lo s c ic lo s w h i l e o f o r . L a e j e c u c i ó n s a l t a
hasta la s ig u ie n t e in s tru c c ió n d e s p u é s d e la lla v e d e c ie rre .
Ejem plo
while (condición)
{
if (condición2)
break;
// instrucciones;
}
1: // Listado 7.5
2: I I Muestra de un ciclo while (true)
3:
4: #include <iostream.h>
i o n im u a
Día 7
ii 190
L is t a d o 7 . 5 continuación
5
6 int m a i n ()
7 {
8 int contador = 0;
9
10 while (true)
11 {
12 contador ++;
13 if (contador > 10)
14 break;
15 }
16 cout << "Contador: " << contador << "\n";
17 return 0;
18
Contador: 11
S a l id a
En la linea 10 hay un c iclo w h ile con una con d ición que nunca podrá ser falsa.
A n á l is is
El c ic lo increm enta la variable c o n t a d o r en la línea 12, y luego en la línea 13
prueba si c o n t a d o r pasa de 10. Si no pasa, el ciclo w h ile hace una iteración. Si contador
e s m ayor qu e 10, la instrucción b re a k de la línea 14 termina el ciclo w hile, y la ejecu
c ió n del program a se va hasta la línea 16, en donde se imprimen los resultados.
E ste program a fu n cion a, pero no es m uy elegan te. Éste es un buen ejemplo del uso de la
herram ien ta incorrecta para el trabajo. Se puede lograr lo m ism o colocando la prueba del
v a lo r de c o n t a d o r en donde pertenece (en la con d ición w h ile).
L o s c ic lo s e t e r n o s c o m o w h i l e ( t r u e ) p u e d e n o c a s i o n a r q u e su c o m p u tado ra
Precaución s e p a r a li c e si n u n c a se ll e g a a la c o n d i c i ó n d e s a lid a . U t ilic e e sto s ciclos con
p r e c a u c ió n y p r u é b e lo s e x h a u s t iv a m e n t e .
D ebe N O DEBE
D E B E utilizar ciclos v/hile para iterar c u a n N O D E B E utilizar la instrucción g o to .
do una condición sea verdadera.
D E B E tener cui dado al usar instrucciones
continué y break.
D E B E asegurarse q u e su ciclo te n ga
fin.
1 II L i s t a d o 7 . 6
2 II M ue s t r a cómo se s a l t a e l c u e r p o d e l
3 // c i c l o w h i l e cuando l a c o n d i c i ó n es falsa
4
5 #include <iostream.h>
6
7 i n t main()
8 {
9 int contador;
10:
•i .
11 : cout « "¿Cuántos holas?: J
12: c i n >> c o n t a d o r ;
13: w h i l e ( c o n t a d o r > 0)
14: {
15: cout << " ¡ H o l a ! \ n " ;
16: c o n ta d or - -;
17: }
18: cout << " C o n t a d o r v a l e : ' << c o n t a d o r <<
19: r e t u r n 0;
20: }
¿Cuántos h o l a s ? : 2
S alida ¡Hola!
iHola!
Contador v a l e : 0
¿Qué pasa si usted quiere asegurar que H o l a se imprima por lo menos una vez? El ciclo
w h ile no puede lo erar esto debido a que la condición se prueba antes de que se realice
cualquier im presión. Puede lor/ar esto con una instrucción í f pisto antes de entrar al
ciclo w h ile , co m o en el siguiente ejem plo:
í f (contador < 1) / / fo rza r un v a lo r mínimo
contador = 1 ;
pero e so es lo que los program adores llaman un paiche , una solución nada elegante.
E xiste una mejor solución: el c iclo do. . .w h ile , el cual se describe en la siguiente sección.
Ciclos d o . . .while
El c ic lo d o . . .w h ile ejecuta el cuerpo del c iclo antes de que se evalúe su condición y
asegura que el cuerpo siem pre se ejecute por lo m enos una v e /. El listado 7.7 vuelve a
escribir el listado 7 .6 , esta ve/, utilizando un c iclo do. . . whi l e.
1 // L i s t a d o 7.7
2 // M u e s t r a del ciclo do...w hile
3
4 #include <iostream.h>
5
6 int m a i n ()
7 {
8: mt contador;
9:
10 cout << "¿Cuántos holas?:
11 cin >> contador;
12 do
13 {
14 c o u t << " H o l a \ n ";
15 contador - - ;
16 } while (contador >0 );
17 cout << "Contador vale: " << contador << endl;
18 return 0;
19 }
.
M ás f lu jo de p ro g ra m a 193
¿Cuántos h o la s?: 2
S alid a H o la
H o la
Contador va le : 0
Las instrucciones c o n tin u e y b reak funcionan en el ciclo do. . .w h ile exactam ente igual
que en el ciclo w h ile . La única diferencia entre un ciclo w h ile y un ciclo do. . .w h ile
es el m om ento en el que se prueba la condición.
La in s t r u c c ió n do. . .w h ile
La sintaxis para la instrucción do. . .w hile es la siguiente:
do
in stru c c ió n
w h i le (c o n d ic ió n );
Ejem plo 2
// im p r im ir a l f a b e t o en m inúscu las,
ch a r ch = ' a ';
do
{
cout << ch << ' 1 ;
ch++;
} w h ile ( ch <= ' z ' );
194 Día 7
D ebe N O DEBE
D E B E u tiliz a r d o . . . w h ile c u a n d o q u ie ra
a s e g u r a r q u e el ciclo se e je cu te p o r lo
m e n o s u n a vez.
D E B E u t iliz a r ciclo s w h ile c u a n d o q u ie r a
s a lt a r el c iclo si la c o n d ic ió n es falsa.
D E B E p r o b a r t o d o s lo s cic lo s p a ra a s e g u
ra rs e d e q u e h a g a n lo q u e u s te d e sp e ra .
C iclo s for
Al p ro g ram a r con ciclo s w h ile , a m en u d o se en co n trará estab lecien d o una condición de
inicio, e v alu a n d o si la c o n d ició n es v erd ad era, e in crem en tan d o o cam biando una varia
ble cada ve/, que p ase p o r el ciclo . El listado 7.<X m u estra esto.
E ntrada L is t a d o 7 .8 w h ile e x a m in a d o n u e v a m e n te
1 // L i s t a d o 7 . 8
2 // U s o de c i c l o s con w h ile
3
4 tfin c lu d e < io stre a m .h >
5
6 in t m a i n ()
7 {
8 in t contador = 0;
9
10: w h ile (contador < 5)
11: {
12: contador++;
13: c o u t << " ¡ H a c i e n d o un c i c l o !
14: } ..........
15: c o u t << " \ n C o n t a d o r : " << c o n t a d o r << . \n ,
16: r e t u r n 0;
17: } ____________________________
¡H a c ie n d o un c i c l o ! ¡H a c ie n d o un c i c l o !
¡H a c ie n d o un c i c l o !
S a l id a ¡H a c ie n d o un c i c l o ! ¡H a c ie n d o un c i c l o !
Contador: 5.
La a sig n a c ió n se e sta b le c e en la línea 8: c o n ta d o r se in ic ia li/a en 0. En la línea
l() se p ru e b a c o n ta d o r p ara ver si es m e n o r q u e 5. En la linea l 2 se incrementa
c o n ta d o r . En la lín ea 13 se im p rim e un m en saje sen cillo , pero podem os imaginar que se
p o d ría n re a liz a r tra b a jo s m ás im p o rtan tes p ara cad a in crem en to del contador. Finalmente,
en la línea 15 se im p rim e el v alo r de c o n ta d o r cu an d o ha term in ad o el ciclo.
M ás f lu jo de p ro g ra m a 195
Un ciclo f o r com bina tres pasos en una instrucción. Los tres pasos son inicialización.
prueba e increm ento. Una instrucción f o r consiste en la palabra reservada f o r seguida
de un par de paréntesis. D entro de los paréntesis se encuentran tres instrucciones sep a
radas con punto y coma.
La prim era instrucción es la inicialización. Cualquier instrucción válida de C++ se puede
colocar aquí, pero generalm ente esto se utiliza para crear e inicializar una variable de co n
leo. La instrucción 2 es la condición, y cualquier expresión válida de C++ se puede utilizar
aquí. Esto juega el m ismo papel que la condición en el ciclo w hile. La instrucción 3 es la
acción. Por lo general se incrementa o decrementa un valor, aunque se puede colocar aquí
cualquier instrucción válida de C++. Observe que las instrucciones l y 3 pueden ser cual
quier instrucción válida de C++. pero la instrucción 2 debe ser una expresión (una instruc
ción de C++ que regrese un valor). El listado 7.9 muestra el uso de un ciclo fo r .
1: II L i s t a d o 7 . 9
2: // u s o de c i c l o s con f o r
3:
4: t f in c lu d e < i o s t r e a m . h >
5:
6: in t main ( )
7: {
8: in t contador;
9:
10: f o r ( c o n t a d o r = 0; c o n ta d o r < 5; c o nta d o r++ )
11: c o u t << " ¡ H a c ie n d o un c i c l o !
12: c o u t << " \ n C o n t a d o r : " « co n ta d o r « ",\n ";
13: r e t u r n 0;
14: }
¡H a c ie n d o un c i c l o ! ¡Haciendo un c i c l o ! ¡H aciend o un c i c l o !
S alida ¡H a c ie n d o un c i c l o ! ¡Haciendo un c i c l o !
C o n t a d o r : 5.
La in s t r u c c ió n fo r
La sintaxis para la instrucción for es la siguiente:
fo r (in ic ia liza c ió n ; condición; acción )
in stru c c ió n ;
| 196 Día 7
------------------------------------------------------------------------------- -1
i
J'rjf
vez que se repite el ciclo. Si la prueba de la condición es v erdadera, se ejecu ta el cuerpo v,1$
del ciclo fo r y luego se ejecuta la instrucción acción (por lo general se increm enta el con- |
tador).
Ejemplo 1 z1;,
/ / imprimir Hola diez veces
fo r (in t i =0 ; i< 10 ; i++) jl
cout « "i Hola i í;'
Ejemplo 2
fo r (in t í = 0 ; i < 10 ; i++)
i 7
cout << “iHolai" << endl;
cout « "el valor de i es: 0 << i , « endl;
>
D espués de pasar cada vez por el cuerpo del ciclo, ocu rre el p aso 4 y el cic lo repite los
pasos 2 y 3 .
E n tra d a L istado 7 .1 0 M u e s t r a d e v a r ia s i n s t r u c c io n e s e n e l c i c lo f o r
1: / / l i s t a d o 7.10
2: / / muestra de v a ria s in stru c c io n e s en
3: / / c ic lo s fo r
M ás f l u j o de p r o g r a m a 197
4:
5: //inelude <iostream.h>
6:
7: in t mam( )
8: {
9: fo r (in t i=0, j=0; i<3; i++, j++)
10: cout << "i: u « i « " j ; ■
11 : return 0;
12: }
i: 0 j: 0
S a lid a i: 1 j: 1
i: 2 j: 2
En la linea 9 se micializan dos variables.
A nálisis
L is t a d o 7 .H 1 In s tru c c io n e s n u la s en ciclos f o r
1: // L i s t a d o 7.11
2 : // C i c l o s f o r con in s t r u c c i o n e s nulas
3:
4: # in c lu d e < iostream .h>
5:
6 : i n t main()
7: {
8: i n t contador = 0;
9:
10 for( ; contador < 5; )
11 {
12 c o n ta d o r+ + ;
con tinú o
198 Día 7
L is t a d o 7 .1 1 c o n t in u a c i ó n
En t r a d a L is t a d o 7.12 E je m p lo d e u n a in s t r u c c ió n d e c ic lo f o r c o n e n c a b e z a d o v a c ío
22: }
23: re t u r n 0;
24: }
¿Cuántos h o l a s ? : 3
S a l id a
¡H ola!
¡Hola!
i H o la !
Ciclos fo r vacíos
Debido a que se pueden hacer muchas cosas en el encabezado de una instrucción fo r, al
gunas veces no será necesario que el cuerpo haga algo. En este caso, asegúrese de colocar
una instrucción nula (;) como cuerpo del ciclo. El punto y coma puede estar en la misma
línea que el encabezado, pero esto se puede confundir y se puede pasar por alto. El lista
do 7.13 muestra cómo utilizar un cuerpo nulo en un ciclo for.
1: / / L is ta d o 7.13
2: //Muestra de la in s t r u c c i ó n nula
3: // como cuerpo de un c i c l o fo r
4:
5: # in c lu d e <iostream .h>
6: i n t main()
7: {
8: f o r ( i n t i = 0; i<5; cout « "i: " « i++ « endl)
9: ;
10: retu rn 0;
11: }
i: 0
S a l id a i: 1
i: 2
i: 3
i: 4
20 0 Día 7
N o queda nada por hacer en el cuerpo del ciclo for. po r lo que se u tiliz a la instrucción
nula (;). O bserve que éste no es un ciclo for bien diseñ ad o : la in stru c ció n a c c ió n está
haciendo dem asiado. Esto quedaría m ejor si se escrib iera de la sig u ie n te m anera:
8: for (int i = 0; i<5; i++)
9: cout « "i: 0 « i « endl;
A unque am bos hacen lo mismo, este ejem plo es m ás fácil de co m p ren d er.
Ciclos anidados
Los ciclos pueden estar anidados, un ciclo dentro del cu erp o de o tro . El ciclo interno se
ejecutará por completo en cada ejecución del ciclo externo. C on b ase en la experiencia,
p arece que el ciclo f o r es el tipo de ciclo que se anida co n m ás frecu en c ia. C ualquiera
de los tres ciclos se puede anidar, pero el ciclo w h ile y el ciclo d o . . .w h ile son sim ple
m ente m enos comunes. El listado 7.14 m uestra la escritu ra de m arcas en u na m atriz
usando ciclos f o r anidados.
¿C u á n ta s f i l a s ? 4
¿C u á n ta s colum nas? 12
¿C u á l c a r á c t e r ? x
xxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx
E l com pilador GNU emite una advertencia con este código; tam b ién p u ed e obligarlo a
ser estricto y que muestre un error. La advertencia se ve así:
example.cxx: In function 'int main()':
example.cxx:10: warning: ñame lookup of 'i1 changed for new ANSI 'for' scoping
example.cxx:5: warning: using obsolete binding at i'
Si esto com pila sin problemas, su com pilador (que no sea de G N U ) n o so p o rta aún este
aspecto del estándar ANSI.
Si su com pilador reclam a que i aún no se defíne (en la línea i=7), en to n ces sí soporta el
nuevo estándar. Puede escribir código que com pile en cu alq u ier c o m p ila d o r si cam bia
esto po r lo siguiente:
#include <iostream.h>
int main()
{
int i; //declarar fuera del ciclo for
i = 7; // ahora esto está dentro del alcance para todos los compiladores
return 0;
}
Más f lu jo de p ro g ra m a 203
1: // L i s t a d o 7.15
2: // M uestra de l a s o lu c ió n del enésimo
3: // número de F ib o n a c c i por medio de la it e ra c ió n
4:
5: tfinclude < iostream .h>
6:
7: in t f ib ( i n t p o sició n );
8:
9: i n t main()
10 {
11 in t re sp u e sta , p o sic ió n ;
12
13 cout « "¿ C u á l p o s ic i ó n ? : ";
14 c in >> p o s ic ió n ;
15 cout « " \ n " ;
16 re s p u e s ta = f i b ( p o s i c i o n ) ;
17 cout « re sp u e sta « " es e l número ";
18 cout « p o s ic i ó n « " de la s e r ie de F ib o n a c c i.\ n " ;
19 re t u rn 0;
20 }
21
22 in t f ib ( in t n)
23 {
24 i n t menosDos=1, menosUno=1, respuesta=2;
25
26 if (n < 3)
27 retu rn 1 ;
28 f o r (n -= 3; n; n --)
29 {
30 menosDos = menosUno;
con tinú a
|2 0 4 Día 7
L is t a d o 7 . 1 5 c o n t in u a c i ó n
¿Cuál posición? 4
S a l id a 3 es e l número 4 en la s e r ie de F ib o n acci.
1, 1, 2,
Más f lu j o de p ro g ra m a 205
y pensar: “Faltan dos”. Luego tendría que sumar 2 + 1 y escribir 3. y pensar: “Falta
encontrar uno". Por último, tendría que escribir 3 + 2 y la respuesta sería 5. En efecto,
desvía su atención un número hacia la derecha cada vez que pasa por el ciclo, y decre-
menta el número que queda por encontrar.
Observe la condición que se prueba en la línea 28 (n). Este es un modismo de C++. y
equivale a n ! = 0 . Este ciclo fo r se basa en el hecho de que cuando n llegue a 0 será
f a ls e . debido a que 0 tiene el valor false en C++. El encabezado del ciclo f o r se
hubiera podido escribir de la siguiente manera:
for (n-=3; n !=0; n--)
lo que hubiera sido un poco más claro. Sin embargo, este modismo es tan común en C++
que no tiene mucho sentido evitarlo.
Compile, enlace y ejecute este programa, junto con la solución por medio de la recursión
que se ofrece en el día 3. Trate de encontrar la posición 25 y compare el tiempo que
tarda cada programa. La recursión es elegante, pero cada llamada a la función incremen
ta la pila, y como se llama muchas veces, su rendimiento es notablemente menor que el
de la iteración y puede provocar una sobrecarga. Las microcomputadoras tienden a estar
optimizadas para las operaciones aritméticas, por lo que la solución por medio de itera
ciones debería ser mucho más rápida.
Tenga cuidado con el tamaño del número que escriba, fib crece rápidamente, y los
enteros largos se desbordarán después de ciertos valores.
Instrucciones switch
En el día 4 vio cómo escribir instrucciones i f e if / e ls e . Estas instrucciones se pueden
volver algo confusas cuando se generan instrucciones i f complejas, por lo que C++
ofrece una alternativa. A diferencia de la instrucción if , que evalúa un solo valor, las
instrucciones sw itch le permiten minificar la ejecución del programa con base en
cualquiera de varios valores. La forma general de la instrucción switch es
sw itch (e x p re sió n )
{
case v a lo r U n o : in s t r u c c i ó n ;
break;
case v a l o r D o s : i n s t r u c c i ó n ;
o t r a _ in s t r u c c i o n ;
break;
pueda convertir sin ambigüedad en) un valor entero. Sin em b arg o , o b serv e que la evalua
ción es sólo para igualdad; tal vez no se puedan utilizar aq u í los o p erad o res relaciónales,
ni las operaciones booleanas.
Si uno de los valores de la cláusula case concuerda con la ex p resió n , la ejecución salta
hacia esas instrucciones y continúa hasta el fin del bloque s w itc h , a m enos que se
encuentre una instrucción break. Si nada concuerda, la ejecu ció n se ram ifica hacia la
cláusula predeterminada (def a u lt) opcional. Si no existe un v alo r p red eterm in ad o ni uno
que concuerde, la ejecución sale de la instrucción s w itc h y ésta term ina.
E s c r i b a un número entre 1 y 5: 3
S a l id a | ¡ E x c e le n t e !
¡M a g istra l!
¡In c re íb le !
La instrucción sw itc h
La sintaxis para la instrucción sw itch es la siguiente:
sw itc h (e x p re sió n )
{
case va lo rU n o : in s t r u c c ió n ;
case v a lo rD o s : in s t r u c c ió n ;
case v a lo rN : in s t r u c c ió n ;
d e fa u lt: in s t r u c c ió n ;
>
La instrucción sw itch permite la ramificación con base en múltiples valores de expresión.
Se evalúa la expresión, y si concuerda con alguno de los valores de las cláusulas case, la
ejecución salta hasta esa línea. La ejecución continúa hasta el final de la instrucción
sw itch , o hasta q u e encuentra una instrucción break.
Si la expresión no concuerda con ninguna de las cláusulas case, y si existe una cláusula pre
determ inada (default), la ejecución se dirige hacia ella; de no ser así, la instrucción sw itch
termina.
|208 Día 7
Ejemplo 1
switch (opcion)
{
case 0:
coût « "îCerol“ « endl;
break; .
case 1 :
coût « "îUnol" « endl;
break;
case 2:
coût « 01Dos 1" « endl;
default:
coût « "IPredeterminadol0 << endl;
}
Ejemplo 2
switch (opcion) ,
{
case 0:
case’ 1 î
case 2: ,
coût <jf. "IMener que 31" ;
break;
case 3:
c o û t « "liguai a 31";
break;
defaùlt: /
coût'<< “IMayor que 31";
>
Un ciclo e te rn o es un ciclo que no tiene una condición de salida. Para poder salir del
ciclo, se debe utilizar una instrucción break.
1: / / L is t a d o 7.17
2: //Uso de un c i c l o eterno para manejar
3: //la i n t e r a c c i ó n con e l usu ario
4: //include <io stre a m .h >
5:
6: // p r o t o t i p o s
7: i n t menu() ;
8: v o id HacerTareaUno();
9: v o id HacerTareaMuchos( i n t ) ;
10
11 i n t main()
12 {
13
14 bool s a l i r = f a l s e ;
15 fo r (;;)
16 {
17 i n t opcion = menu();
18 sw itc h (o p c io n )
19 {
20 case (1):
21 HacerTareaUno();
22 break;
23 case (2):
24 HacerTareaMuchos(2);
25 break;
26 case (3):
27 HacerTareaMuchos(3);
28 break;
29 case (4):
30 continué; // ¡redundante!
31 b re a k ;
32 case (5):
33 salir= tru e ;
34 break;
35 d e f a u lt:
36 cout « "¡Sele ccione otra v e z !\ n ";
37 break;
38 } // f i n de switch
39
40 if (sa lir)
41 break;
42 } // f i n de c ic l o eterno
43 re t u rn 0 ;
continúa
210 Día 7
L is t a d o 7 .1 7 c o n t in u a c i ó n
44 } // fin de main()
45
46 int menu()
47 {
48 int opcion;
49
50 cout « " ***** Menú ****\n\n°;
51 cout « "(1) Opción uno.\n";
52 cout « "(2) Opción dos.\n";
53 cout « "(3) Opción tres.\n";
54 cout « "(4) Volver a desplegar menú.Nn" ;
55 cout « "(5) Salir.\n\n";
56 cout « ": “;
57 cin » opcion;
58 return opcion;
59 }
60
61 void HacerTareallno( )
62 {
63 cout « "iTarea Unol\n";
64 }
65
66 void HacerTareaMuchos(int cual)
67 {
68 if (cual == 2)
69 cout « "iTarea Dos!\n";
70 else
71 cout « “iTarea Tresl\n";
72 }
S a l id a
(1) Opción uno.
(2) Opción dos.
(3) Opción tres.
(4) Volver a desplegar menú.
(5) Salir.
: 1
¡Tarea Unol
**** Menú ****
(1) Opción uno.
(2) Opción dos.
(3) Opción tres.
(4) Volver a desplegar menú.
(5) Salir.
: 3
Más f lu j o de p ro g ra m a 211
¡Tarea Tres!
* * ** Menú * * * *
1) Opción uno.
2) Opción d o s .
3) Opción t r e s .
4) V o lv e r a d esp lega r menú
5) S a l i r .
: 5
Este programa reúne varios conceptos vistos en este día y en días anteriores.
A n á l is is
También muestra un uso común de la instrucción switch.
El ciclo e t e r n o empieza en la línea 15. Se hace una llamada a la función m enu( ), la cual
imprime el menú en la pantalla y regresa la selección del usuario. La instrucción s w it c h .
que empieza en la línea 18 y termina en la línea 38, actúa en base a la opción que el
usuario elija.
Si el usuario escribe 1, la ejecución salta a la cláusula case (1): de la línea 20. La
línea 21 cambia la ejecución a la función HacerTareaüno, la cual imprime un mensaje y
regresa. A su regreso, la ejecución continúa en la línea 22, en donde la instrucción b re a k
termina la instrucción s w it c h , y la ejecución se va hasta la línea 39. En la línea 40 se
evalúa la variable s a l i r . Si es verdadera (true), se ejecutará la instrucción de la línea 41
y terminará el ciclo f o r ( ; ; ) ; pero si es falsa (f alse), la ejecución continúa en la línea
15, en el principio del ciclo.
Observe que la instrucción c o n t i n u é de la línea 30 es redundante. Si se omitiera esta
instrucción y se llegara a la instrucción break en la ejecución del programa, la instruc
ción s w i t c h terminaría, s a l i r sería falsa, el ciclo volvería a iterar y se volvería a
imprimir el menú. Sin embargo, la instrucción c o n t in u é pasa por alto la prueba de
sa lir.
D ebe N O DEBE
Resumen
Existen formas diferentes para hacer que un programa de C++ realice un ciclo. El ciclo
w hile comprueba una condición, y si es verdadera, ejecuta las instrucciones que se
encuentran en el cuerpo del ciclo. Los ciclos d o . . .w h ile ejecutan el cuerpo del ciclo y
luego prueban la condición. Los ciclos f or inicializan un valor y luego prueban una
condición. Si la condición es verdadera, se ejecuta el cuerpo del ciclo. Cada vez que se
pase por el ciclo, se ejecuta la instrucción final del encabezado del ciclo f or. se evalúa
otra vez la expresión y se repite todo el proceso.
Por lo general, el uso de la instrucción goto se evita, debido a que produce un salto in
condicional a una ubicación aparentemente arbitraria en el código, lo cual provoca que el
código sea difícil de entender y de mantener. La instrucción co n tin u é ocasiona que los
ciclos while, d o . . .w hile, y for empiecen de nuevo, y la instrucción break ocasiona que
las instrucciones while, d o . . .w hile, for, y switch terminen.
Preguntas y respuestas
P ¿Cómo puedo elegir entre i f /e ls e y switch?
R Si se utilizan dos o más cláusulas e lse , y todas evalúan el mismo valor, debe con
siderar el uso de una instrucción switch.
P ¿Cómo puedo elegir entre while y do. . .w hile?
R Si el cuerpo del ciclo se debe ejecutar por lo menos una vez, utilice un ciclo
d o . . .while; en caso contrario, trate de usar el ciclo w h ile.
P ¿Cómo puedo elegir entre while y for?
R Si está inicializando una variable de conteo, y la evalúa y la incrementa cada vez
que pasa por el ciclo, considere el uso del ciclo for. Si su variable ya está iniciali-
zada y no se incrementa en cada ciclo, tal vez el ciclo w h ile sea la mejor opción.
P ¿Cómo puedo elegir entre recursión e iteración?
R Algunos problemas piden a gritos la recursión, pero la mayoría de los problemas
también se puede resolver mediante la iteración. Mantenga la recursión bajo su
manga, tal vez le sea útil algún día..
P ¿Qué es mejor, usar while ( t r u e ) o for ( ; ; ) ?
R No existe ninguna diferencia considerable.
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del
material tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
Más f lu jo de p ro g ra m a 213
Cuestionario
1. ¿Cómo puede inicial izar más de una variable en un ciclo for?
2. ¿Por qué se evita el uso de la instrucción goto?
3. ¿Es posible escribir un ciclo f or que tenga un cuerpo que nunca se ejecute?
4. ¿Es posible anidar ciclos while dentro de ciclos f or?
5. ¿Es posible crear un ciclo que nunca termine? Dé un ejemplo.
6. ¿Qué pasa si crea un ciclo que nunca termine?
Ejercicios
1. ¿Cuál es el valor de x cuando el siguiente ciclo f or finaliza su ejecución?
fo r (int x = 0 ; x < 1 0 0 ; x++)
2. Escriba un ciclo fo r anidado que imprima ceros en un patrón de 10 x 10.
3. Escriba una instrucción fo r que cuente del 100 al 200 de dos en dos.
4. Escriba un ciclo w hile que cuente del 100 al 200 de dos en dos.
5. Escriba un ciclo d o . . .w hile que cuente del 100 al 200 de dos en dos.
6. C A ZA ERRO RES: ¿Qué está mal en el siguiente código?
in t contador = 0 ;
w h ile (co ntado r < 10 )
{
cout << "con tad or: " « contador;
}
7. C A ZA ERRO RES: ¿Qué está mal en el siguiente código?
fo r (int contador = 0 ; contador < 10; contador++);
cout « contador « " ";
v
214 Día 7
switch (elNumero)
{
case 0:
hacerCero();
case 1 : // pasar a la siguiente cláusula case
case 2: // pasar a la siguiente cláusula case
case 3: // pasar a la siguiente cláusula case
case 4: // pasar a la siguiente cláusula case
case 5:
hacerUnoHastaCinco();
break;
default:
hacerPredeterminado();
break;
Sem a n a 1
Repaso
Acaba de terminar la primera de tres semanas de aprendizaje
de C++ para Linux. Debe estar orgulloso. Pero antes de que
se relaje demasiado, hay más por hacer.
Dé un vistazo al listado Rl . l y piense en todo lo que ha
aprendido durante esta primera semana. El código de C++ de
este listado utiliza la mayoría de las técnicas que se cubrieron
en esta semana.
L is t a d o R 1 .1 c o n t in u a c i ó n
27
28 // Implementaciones de los métodos de la clase
29 void Rectángulo: :AsignaTamanio(int nuevaAnchura, int nuevaAltura)
30 {
31 suAnchura = nuevaAnchura;
32 suAltura = nuevaAltura;
33 }
34
35
36 Rectángulo::Rectángulo(int anchura, int altura)
37 {
38 suAnchura = anchura;
39 suAltura = altura;
40 >
41
42 Rectángulo::-Rectángulo() {}
43
44 int HacerMenu();
45 void HacerDibujaRect(Rectángulo);
46 void HacerDibujaArea(Rectangulo);
47 void HacerDibujaPerim(Rectangulo);
48
49 int main ()
50 {
51 // inicializar un rectángulo con 30,5
52 Rectángulo elRect(30,5);
53
54 int opcion = DibujaRect;
55 int fSalir = false;
56
57 while (IfSalir)
58 {
59 opcion = HacerMenu();
60 if (opcion < DibujaRect || opcion > Salir)
61 {
62 cout « "\nOpción inválida, por favor intente de nuevo.\n\n";
63 continué;
64 >
65 switch (opcion)
66 {
67 case DibujaRect:
68 HacerDibujaRect(elRect);
69 break;
70 case ObtenArea:
71 HacerDibujaArea(elRect);
72 break;
73 case ObtenPerim:
74 HacerDibujaPerim(elRect);
Repaso 217
75: break ;
76: case CambiaDimensiones:
77: int nuevaLongitud, nuevaAnchura;
78: cout « "\nNueva anchura: ";
79: cin » nuevaAnchura;
80: cout « "Nueva altura:
81 : cin » nuevaLongitud;
82: elRect.AsignaTamanio(nuevaAnchura, nuevaLongitud);
83: HacerDibujaRect(elRect);
84: break;
85: case Salir:
86 : fSalir = true;
87: cout « "\nSaliendo...\n\n°;
88: break;
89: default:
90: cout « "¡Error en opción!\n";
91 : fSalir = true;
92: break;
93: } // fin de switch
94: > // fin de while
95: return 0;
96: } // fin de main
97:
98: int HacerMenu()
99: {
100 int opcion;
101 cout « "\n\n *** Menú *** \n";
102 cout « "(1) Dibujar rectángulo^1';
103 cout « "(2) Área\n”;
104 cout « “(3) Perimetro\n";
105 cout « "(4) Cambiar tamaño\n";
106 cout « "(5) Salir\n";
107
108 cin » opcion;
109 return opcion;
110 }
111
112 void HacerDibujaRect(Rectángulo elRect)
113 {
114 int altura = elRect.ObtenAlturaO;
115 int anchura = elRect .ObtenAnchuraO ;
116
117 for (int i = 0; i<altura; i++)
118 {
119 for (int j = 0; j< anchura; j++)
120 cout « "*";
121 cout « "\n";
122 }
123
124
continúa
21 8 Sem ana 1
125:
126: void HacerDibujaArea(Rectangulo elRect)
127: {
128: cout « "Àrea: “ « elRect.ObtenArea( ) « endl;
129: >
130:
131: void HacerDibujaPeriin(Rectangulo elRect)
132: {
133: cout « "Perimetro: " « elRect.ObtenPerim() « endl;
134: }*
5
4
3
2
1
Nueva anchura: 10
Nueva altura: 8
**********
**********
**********
**********
**********
**********
**********
Saliendo...
El programa mostrado en el listado Rl.l utiliza la mayoría de las habilidades
A nálisis
que aprendió en esta semana. No sólo debe ser capaz de escribir, compilar,
enlazar y ejecutar este programa, sino también de entender lo que hace y cómo funciona,
con base en el trabajo que realizó esta semana.
Las prim eras seis líneas establecen los nuevos tipos y las definiciones que serán utiliza
das en todo el programa.
220 Sem ana 1
Las líneas 6 a 25 declaran la clase Rectángulo. Hay m étodos p ú b lico s de acc eso para
obtener y asignar la anchura y la altura del rectángulo, así co m o p ara c a lc u la r el área y el
perím etro. Las líneas 29 a 40 contienen las definiciones de las fu n cio n es de la clase que
no fueron declaradas en línea.
Los prototipos de las funciones para las funciones que no son m iem b ro s de la clase se
encuentran en las líneas 44 a 47, y el program a em pieza en la línea 49. La e sen cia de
este program a es generar un rectángulo y luego im prim ir un m enú en el q u e se ofrecen
cinco opciones: dibujar el rectángulo, determ inar su área, d e te rm in a r su p erím etro ,
cam biar de tamaño el rectángulo o salir.
Se establece un indicador en la línea 55, y cuando ese in d ic a d o r tien e a s ig n a d o el valor
false, el ciclo del menú continúa. El indicador sólo ten d rá el v a lo r true si el usuario
elige la opción Salir del menú.
C ada una de las otras opciones, con la excepción de CambiaDimensiones, llam a a una fun
ción. Esto hace que la instrucción switch sea más limpia. CambiaDimensiones no puede
llam ar a una función porque debe cambiar las dim ensiones del rectán g u lo . Si se pasara el
rectángulo (por valor) a una función tal como HacerCambiarDimensiones ( ) , las dim en
siones se cam biarían en la copia local del rectángulo en HacerCambiarDimensiones(),
y no en el rectángulo en main( ). En el día 8, “A p u n tad o res,” y en el d ía 1 0 , “ Funciones
avanzadas,” veremos cómo vencer esta restricción, pero p o r ah o ra el c a m b io se hace en
la función main().
O bserve cómo el uso de una enumeración hace que el e n u n ciad o switch sea m ás lim pio
y fácil de comprender. Si la instrucción switch dep en d iera de las o p c io n e s num éricas
(1 -5 ) del usuario, tendríamos que referim os constantem ente a la d e sc rip c ió n del m enú
para ver cuál opción era cuál.
En la línea 60 se com prueba la elección del usuario para a seg u rar q u e esté d en tro del
rango. Si no es así, se imprime un mensaje de error y se v u elve a im p rim ir el m enú.
O bserve que la instrucción sw itch incluye una condición p re d e te rm in a d a “ im p o sib le” .
Esto ayuda en la depuración. Si el program a está funcionando, esta in stru c c ió n nunca
podrá ejecutarse.
Repaso de la semana
¡Felicidades! ¡Acaba de com pletar la prim era sem ana! A hora p u ed e c re a r y com prender
program as sofisticados en C++. Desde luego que hay m ucho m ás p o r hacer, y la siguien
te sem ana inicia con uno de los conceptos más difíciles en C ++: los ap u n tad o res. N o se
dé por vencido ahora— está a punto de profundizar en el sig n ific a d o y el u so de la pro
gram ación orientada a objetos, las funciones virtuales y m uchas de las características
avanzadas de este poderoso lenguaje.
Repaso
Incluso aprendió cóm o utilizar el compilador. ¡Ésta es una habilidad que utilizará en
cada program a! Y después de que aprenda a utilizar un com pilador, no tendrá dificul
tades con los dem ás (debido a que es el mismo proceso).
í¡
I
S em ana 2 8
De un vistazo 9
Ha terminado su primera semana en el proceso de aprendizaje
de la programación en C++. Para estos momentos debe estar
familiarizado con la escritura de programas, con el uso de su
compilador y con los conceptos relacionados con los objetos,
las clases y el flujo de programa. 10
Objetivos
La semana 2 empieza con los apuntadores. Éste es un tema
tradicionalmente difícil para los nuevos programadores de
C++, pero su explicación será completa y clara, por lo que no 11
debe considerarse como un obstáculo invencible. El día 8,
“Apuntadores”, habla sobre los apuntadores, y el día 9,
“Referencias”, enseña las referencias, que son un pariente
cercano de los apuntadores. En el día 10, Funciones avan
zadas”, veremos cómo sobrecargar funciones, y en el día 11,
“Herencia”, hablaremos sobre la herencia, un concepto funda
12
mental en la programación orientada a objetos. En el día 12,
“Arreglos, cadenas tipo C y listas enlazadas’ , aprenderá a tra
bajar con los arreglos, las cadenas y las colecciones. El día
13, “Polimorfismo”, extiende las lecciones del día 11 para
hablar sobre el polimorfismo, y el día 14, Clases y (unciones 13
especiales”, finaliza la semana con una explicación sobre las
funciones estáticas y amigas.
• / l i Li Il ¡J
Í t.ow-r: , 7. V. .
Sem an a 2
Apuntadores
Una de las herramientas más poderosas disponibles para un programador de
C++ es la capacidad de manipular directamente la memoria mediante el uso
de apuntadores. Hoy aprenderá lo siguiente:
• Qué son los apuntadores
• Cómo declarar y utilizar apuntadores
• Qué es el heap y cómo manipular memoria
Los apuntadores le plantean dos retos especiales si está aprendiendo a programar
en C++. Pueden ser algo confusos, y no se aprecia de inmediato por qué se
necesitan. Esta lección explica paso a paso cómo funcionan los apuntadores.
Usted comprenderá completamente por qué se necesitan los apuntadores, a
medida que lea el resto del libro.
¿Qué es un apuntador?
Un a p u n t a d o r es una variable que guarda una dirección de memoria.
Para comprender lo que son los apuntadores, debe saber un poco acerca de la
memoria de la computadora. La memoria se divide en ubicaciones de memoria
numeradas en forma secuencial. Cada variable se encuentra en una ubicación
L _______________________
22 6 Día 8
única en m em oria, conocida com o su dirección. La figura 8.1 m uestra una representación
esquem ática del almacenamiento de una variable de tipo entero largo sin signo (unsigned
lo n g i n t ) llam ada laEdad.
Fig 8.1
ura Memoria
Una representación
la E d a d
esquemática de
la E d a d .
10110101 11110110
01110110 11101110
100 101 102 103 104 105 106 107 108 109 110 111 112 113
Variable de t i p o s ho r t s i n signo: 5
S alida Direcció n de v a r i a b l e de ti po short: 0 xbff ff a16
Variable de t i p o long s i n signo: 65535
Direcció n de v a r i a b l e de t i po long: 0 xbff ff a10
Variable de t i p o long con signo: - 65535
Direcció n de v a r i a b l e de ti po long con signo: 0xbffffa0c
(La salida que usted obtenga puede ser distinta, dependiendo de la configuración de su
sistema: algunos compiladores GNU mostrarán todas las direcciones como el valor 1
(uno). Tal v e/ necesite utilizar la función p r i n t f () con el especificado!' de formato %p
en caso de que esto le ocurra.)
Se declaran y se inicializan tres variables: una de tipo u n s i g n e d s h o r t en la línea
A nálisis
8, una de tipo u n s i g n e d l o n g en la línea 9 y otra de tipo l o n g en la línea 10. Sus
valores y direcciones se imprimen en las líneas 12 a 25 mediante el uso del operador de
dirección (&).
El valor de s h o r t V a r es 5, como se esperaba, y su dirección es Oxbffffaló. Esta compli
cada dirección es específica de la computadora, del compilador y del sistema operativo,
y puede cambiar ligeramente cada vez que el programa se ejecute. Los resultados que
usted obtenga serán diferentes. Sin embargo, lo que no cambia es que la diferencia en las
dos primeras direcciones es de 2 bytes, si su computadora utiliza enteros cortos de 2
bytes. La diferencia entre la segunda y la tercera es de 4 bytes, si su computadora utiliza
enteros largos de 4 bytes. La figura 8.2 muestra cómo se guardarían en memoria las va
riables de este programa.
lo n g V a r
F ig u r a 8.2
sh o rtV a r svar
Ejemplo del alm ace
namiento de variables. — i— i— r l i l i 1 1 1 1 1 1 1 1 1 1
Usted no necesita conocer el valor numérico real de la dirección de cada variable. Lo que
debe importarle es que cada una tiene una dirección y que se reserva la cantidad conecta de
memoria. Al declarar el tipo de la variable, usted le indica al compilador cuánta memoria
debe asignar para sus variables; el compilador asigna automáticamente una dirección para la
variable. Por ejemplo, un entero largo por lo general tiene un tamaño de 4 bytes, lo que sig
nifica tiue la variable tiene la dirección del primer bytc tic los 4 hvtes reservados en memoria
Día 8
No hay relación entre el tamaño de un tipo de datos y el tamaño del apuntador utilizado
para apuntar a ese tipo de datos. Que un entero largo y un apuntador a ese entero largo sean
de 4 bytes, es sólo una coincidencia. Usted no debe hacer suposiciones en relación con el
tamaño de un apuntador, y no debería tratar de guardar una dirección en algo que no sea un
apuntador (no trate de guardar una dirección en una variable de tipo un sig n ed long int).
miEdad: 5
S a l id a *apEdad: 5
*apEdad = 7
*apEdad: 7
miEdad: 7
miEdad = 9
miEdad: 9
*apEdad: 9
A nálisis Este programa declara dos variables: una de tipo unsigned short, miEdad, y un
apuntador a un unsigned short, apEdad. En la línea 11 se asigna el valor 5 a
miEdad; esto se verifica por medio de la impresión en la línea 12.
232 Día 8
77 1 miEdad: 5 suEdad: 10
bALiDA 0xbffffa16 SsuEdad: 0xbffffa14
apEdad: 0xbffffa16
*a pEd ad: 5
mi Edad: 5 suEdad: 10
&miEdad: 0xbffffa16 SsuEdad: 0 xbff ff a14
apEdad: 0xbffffa14
* apEdad: 10
&apEdad: 0xbffffal 0
D ebe N O DEBE
DEBE utilizar el operador de indirección
(*) para tener acceso a los datos guardados
en la dirección que guarda un apuntador.
DEBE inicializar todos los apuntadores ya
sea con una dirección válida o con NULL.
DEBE recordar la diferencia entre la direc
ción que guarda un apuntador y el valor
que se guarda en esa dirección.
234 Día 8
Uso de apuntadores
P ara d e c la ra r u n ap untad or, escríba el t ip o d e la v a r ia b le u o b je t o c u y a d ir e c c ió n se v a a
g u a r d a r en el apu n tad or, se g u id o d el o p e r a d o r d e in d ire c c ió n (* ) y d e l n o m b r e d e l a p u n
ta d o r. P o r ejem plo,
La pila y el heap
En el día 5, “Funciones”, en la sección “Cómo trabajan las funciones: un vistazo a su
interior”, se mencionan cinco áreas de memoria:
• Espacio de nombres global
• Heap
A p u n ta d o re s 235
• R eg is t ros
• Espacio de código 8
• Pila
Las variables locales se encuentran en la pila, junto con los parámetros de {unciones. El
código se encuentra, desde luego, en el espacio de código, y las variables globales se en
cuentran en el espacio de nombres global. Los registros se utilizan para el mantenimiento
interno de las funciones, como llevar el registro de la parte superior de la pila y del apun
tador de instrucciones. Casi toda la memoria restante se pasa al heap.
El problema con las variables locales es que no duran: cuando alguna función se ejecuta,
los datos y las variables se colocan en la pila, y cuando la función termina, las variables
locales desaparecen. Las variables globales solucionan ese problema, pero la desventaja
es que ofrecen un acceso sin restricciones en todo el programa, lo que conduce a la
creación de código difícil de entender y de mantener. Al colocar los datos en el heap se
solucionan ambos problemas.
Imagine que el heap es una sección masiva de memoria en la que miles de casillas
numeradas en forma secuencial permanecen en espera de sus datos. Lo malo es que no
puede etiquetar estas casillas, como se puede hacer con la pila. Tiene que pedir la direc
ción de la casilla que va a reservar y luego guardar esa dirección en un apuntador.
Una forma de visualizar esto es con una analogía: Un amigo le proporciona el número 800
de la compañía Acmé de pedidos por correspondencia. Usted va a su casa y programa su
teléfono con ese número, lueszo tira el pedazo de papel que tiene anotado el número tele
fónico. Si oprime el botón, un teléfono timbra en alguna parte, y contesta el servicio de
pedidos por correspondencia de la compañía Acmé. Este servicio representa los datos en
el heap. Usted no sabe donde está, pero sabe cómo llegar a él. Accede a él usando su
dirección (en este caso, el número telefónico). No tiene que conocer ese número, sólo
tiene que colocarlo en un apuntador (el botón). El apuntador le da el acceso a sus datos
sin molestarlo con los detalles.
La pila se limpia automáticamente cuando una función termina. Todas las variables loca
les quedan fuera de alcance, y se eliminan de la pila. El heap se limpia hasta que termina
el programa, y es responsabilidad de usted liberar cualquiei memoiia que haya leseiva
do, cuando ya no la utilice. Como se habrá dado cuenta, tiene que reservar la memoria
antes de poder utilizar variables en el heap (apuntadores). Aunque muchos programas y
compiladores le permitirán utilizar código que no reseive espacio explícitamente, dicho
código puede generar errores muy difíciles de depurar.
La ventaja de usar el heap es que la memoria que usted reserve estará disponible hasta
que la libere explícitamente. Si reserva memoria en el heap mientras se encuentra en una
función, la memoria todavía estará disponible cuando la función termine.
236
Día 8
new
se utiliza la palabra reservada new para asignar memoria en el heap. Después de
new debe ir el tipo del objeto que quiere asignar, para que el compilador sepa cuánta
memoria se requiere. Por lo tanto, new unsigned short int asigna 2 bytes en el heap,
y new long asigna 4.
d e le te
Al terminar de utilizar su área de memoria, debe llamar a delete para que actúe sobre el
apuntador, delete libera la memoria reservada. Recuerde que el apuntador mismo (a dife
rencia de la memoria a la que apunta) es una variable local. Cuando la función en la que
está declarado termina, ese apuntador queda fuera de alcance y se pierde. La memoria
asignada con new no se libera automáticamente. Esa memoria ya no está disponible porque
no hay manera de referenciarla (no tenemos idea de dónde está); esta situación se conoce
como fuga de memoria. Se llama así porque esa memoria sólo se puede recuperar hasta
que termina el programa. Es como si la memoria se hubiera fugado de su computadora.
Apuntadores 237
1: // Listado 8.4
2: // Asignación y eliminación de un apuntador
q ■•
O
4: //include <iostream.h>
5: int main()
6: {
7: int variableLocal = 5;
8: int * apLocal= &variableLocal;
9: int * apHeap = new int;
10
11 *apHeap = 7;
12 cout << "variableLocal: " « variableLocal
13 cout « " *apLocal: " « *apLocal « "\n";
14 cout « "*apHeap: " « *apHeap « "\n";
15 delete apHeap;
16 apHeap = new int;
17 *apHeap = 9;
18 cout « "*apHeap: " « *apHeap « “\n";
19 delete apHeap;
20 return 0;
21 >
238 Día 8
variableLocal: 5
S a lida *apLocal: 5
*apHeap: 7
*apHeap: 9
Fugas de memoria •-
Otra forma de crear inadvertidamente una fuga de memoria es reasignar su apuntador antes © . x'
de eliminar la memoria a la cual está apuntando. Considere este fragmento de código: 4Yi,-
"■ ■■ ir:.y.
1: unsigned short int * apApuntador = new unsigned short int;
2: ‘apApuntador = 72; V ■.'¿V
3: apApuntador = new unsigned short int;
4: ‘apApuntador = 84;
Objetos en el heap
Aún hay muchas cosas que aprender acerca del uso y la creación de objetos. En las
siguientes secciones verá estos temas:
• Creación de objetos en el heap
• Eliminación de objetos
• Acceso a los datos miembro
• Datos miembro en el heap
240 D ía 8
Eliminación de objetos
Cuando llama a delete para que actúe sobre un apuntador a un objeto en heap, el
destructor de ese objeto se llama antes de que se libere la memoria. Esto le da a su clase
una oportunidad para limpiarse, igual que como se hace para los objetos que se destruyen
en la pila. El listado 8.5 muestra la creación y la eliminación de objetos en el heap.
1: II Listado 8.5
2: // Creación de objetos en el heap
3:
4: #include <iostream.h>
5:
6: class GatoSimple
7: {
8: public:
9: GatoSimple();
10: -GatoSimple();
11: private:
12: int suEdad;
13: };
14:
15: GatoSimple::GatoSimple()
16: {
17: cout « "Se llamó al
18: suEdad = 1;
19: }
20:
21: GatoSimple::-GatoSimple()
22: {
23: cout « "Se llamó al
24: }
25:
26: int main()
27: {
Apuntadores 241
GatoSimple Pelusa...
Se llamó al constructor.
GatoSimple ‘apFelix = new GatoSimple...
Se llamó al constructor,
delete apFelix...
Se llamó al destructor,
saliendo, observe cómo se va Pelusa...
Se llamó al destructor.
Las líneas 6 a 13 declaran la clase simplificada GatoSimple. La línea 9 declara
A nálisis
el constructor de GatoSimple, y las líneas 15 a 19 contienen su definición. La
línea 10 declara el destructor de GatoSimple, y las líneas 21 a 24 contienen su definición.
En la línea 29 se crea en la pila el objeto Pelusa, lo que ocasiona que se llame al cons
tructor. En la línea 31 se crea en el heap el GatoSimple al que apunta apFelix; se llama
otra vez al constructor. En la línea 33 se llama a delete para que actúe sobre apFelix, y
se llama al destructor. Al terminar la función, Pelusa queda fuera de alcance, y se llama
al destructor.
L istado 8.6 A c c e s o a lo s d a t o s m i e m b r o d e lo s o b j e t o s q u e se
E n t r a d a e n c u e n t r a n e n el h e a p
P e l u sa t i e n e 2 años de edad
S a lid a P e l u s a t i e n e 5 años de edad
P r e g u n ta s frecuentes
FAQ: Si declaro un objeto en la pila que tiene variables miembro en el heap, ¿qué hay en
la pila y qué hay en el heap? Por ejemplo:
^include <iostream.h>
class GatoSimple
{
public:
GatoSimple();
-GatoSimple();
int ObtenerEdad() const
{ return *suEdad; }
// otros métodos
private:
int * suEdad;
int * suPeso;
>;
GatoSimple::GatoSimple()
{
suEdad = new int( 2 );
suPeso = new int( 5 );
}
GatoSimple: : -GatoSimple()
{
delete suEdad;
delete suPeso;
Apuntadores 245
SÜ
int main()
{
GatoSimple Pelusa;
cout « "Pelusa tiene " «
Pelusa.ObtenerEdad() « * años de'edad\ñ*;
Pelusa.AsignarEdad(5);
cout « "Pelusa tiene " « . ^
Pelusa.ObtenerEdad() « " años de edádVn";
return 0;
}
R e sp u e sta: Lo q u e h ay en la pila es la variable local Pelusa. Ésa variable tie n e d o s apuhb
tadores, ca d a u n o d e los cuales o c u p a a lg o d e espacio en la pila,y g u a rd a la dirección d e
un e n te ro a s ig n a d o en el heap. Por lo tanto, en el ejem plo hay 8 bytes en la pila (se d a
p o r h ech o q u e so n a p u n ta d o re s d e 4 bytes) y 8 bytes en el heap.
Ahora esto sería bastante raro en un programa real, a menos que existiera un buen motivo
para que el objeto Gato guardara sus miembros por referencia. En este caso no hay un
buen motivo, pero en otros casos esto tendría mucho sentido.
Esto hace que surja la siguiente pregunta: ¿Qué está tratando de lograr? Debe entender
también que debe empezar con el diseño. Si lo que diseñó es un objeto que se refiere a
otro objeto, pero el segundo objeto tal vez empiece a existir antes que el primero, y con
tinúe después de que el primero haya desaparecido, entonces el primer objeto debe
contener al segundo por referencia.
Por ejemplo, el primer objeto podría ser una ventana y el segundo podría ser un docu
mento. La ventana necesita acceso al documento, pero no controla el tiempo de vida de
éste. Por lo tanto, la ventana necesita guardar el documento por referencia.
En C++, esto se implementa mediante el uso de apuntadores o referencias. Las referencias
se tratan en el día 9, “Referencias”.
El ap u n tado r t h is
Toda función miembro de una clase tiene un parámetro oculto: el apuntador this. this
apunta al propio objeto. Por lo tanto, en cada llamada a ObtenerEdad () o a AsignarEdad(),
el apuntador this para el objeto se incluye como un parámetro oculto.
Es posible usar el apuntador th is en forma explícita, como lo muestra el listado 8.8.
1: // Listado 8.8
2: // Uso del apuntador this
3:
4: #include <iostream.h>
5:
6: class Rectángulo
7: {
8: public:
9: Rectangulo();
10 -Rectángulo();
11 void AsignarLongitud(int longitud)
12 { this->suLongitud = longitud; }
13 int ObtenerLongitud() const
14 { return this->suLongitud; }
15 void AsignarAncho(int ancho)
16 { suAncho = ancho; }
17 int ObtenerAncho() const
18 { return suAncho; >
19 private:
20 int suLongitud;
21 int suAncho;
22 };
23
24 Rectángulo::Rectangulo()
25 {
26 suAncho = 5;
27 suLongitud = 10;
28 >
29
30 Rectángulo::-Rectángulo()
31 {>
32
33 int main()
34 {
35 Rectángulo elRect;
36
37 cout « "elRect tiene " « elRect.ObtenerLongitud();
38 cout « " pies de largo.\n";
39 cout « "elRect tiene " « elRect.ObtenerAncho();
40 cout « " pies de ancho.\n";
41 elRect.AsignarLongitud(20);
42 elRect.AsignarAncho(10);
43 cout « "elRect tiene " « elRect .ObtenerLongitud();
44 cout « " pies de largo.\n";
A p u n ta d o re s 247
el Re ct ti e ne 10 p i e s de largo.
S a l id a el Re ct ti e ne 5 pi es de ancho.
elRect ti e ne 20 pi es de largo.
elRect tiene 10 pi es de ancho.
Los a p u n ta d o re s p e rd id o s se c o n o c e n c o m ú n m e n t e c o m o a p u n t a d o r e s
Nota d e sc o n tro la d o s o a m b u la n te s. E sto es s ó lo u n a d if e r e n c ia e n la t e rm in o lo g ía .
E n t r a d a L istado 8.9 C r e a c ió n d e u n a p u n t a d o r p e r d i d o
1: // Listado 8.9
2: // Muestra de un apuntador perdido
3:
4: #include <iostream.h>
5:
6: typedef unsigned short int USHORT;
7:
8: int main()
9: {
10: USHORT * aplnt = new USHORT;
11: *aplnt = 10;
12: cout « "*aplnt: ” « *aplnt « endl;
13: delete aplnt;
14:
15: long * apLong = new long;
16: *apLong = 90000;
17: cout « "*apLong: " « *apLong « endl;
18:
19: *aplnt = 20; // icaramba, éste fue eliminado!
20:
21: cout « "*aplnt: " « *aplnt << endl;
22: cout « "*apLong: " « *apLong « endl;
23: delete apLong;
24: return 0;
25: }
*aplnt: 10
S a lid a *apLong: 90000
*aplnt: 20
*apLong: 65556
5. Se imprimió el valor en apLong, con lo cual los hytes regresaron a su orden correc
to de 00 01 00 14, lo que se tradujo al valor 65556.
D ebe N O D EBE
DEBE utilizar new para crear objetos en NO DEBE olvidarse de balancear todas
el heap. las instrucciones new con su instrucción
DEBE utilizar delete para destruir objetos delete correspondiente.
en heap y para liberar la memoria que NO DEBE olvidarse de asignar NULL a
ocupaban. todos los apuntadores sobre los que haya
DEBE verificar el valor regresado por new. utilizado delete.
Apuntadores const
En los apuntadores, puede utilizar la palabra reservada c o n st antes del tipo, después del
tipo, o en ambos lugares. Por ejemplo, las siguientes declaraciones son válidas:
co ns t i n t * apUno;
i n t * c o n s t apDos;
c o n s t i n t * const apTres;
El truco para usar esto sin problemas es mirar a la derecha de la palabra reservada const
para saber qué se está declarando como constante. Si el tipo se encuentra a la derecha de
la palabra reservada, es el valor el que se declara como constante. Si es la variable la que
se encuentra a la derecha, entonces es la propia variable de apuntador la que se declara
como constante.
co ns t i n t * ap 1 ; // el v a l o r i n t a l que se apunta es constante
i n t * c o n s t ap2; // ap2 es constante, no puede apuntar a ninguna o t r a cosa
1: // L i s t a d o 8.10
2 : // Uso de apuntadores con métodos const
3:
4: //include <i ostream.h>
5:
6: c l a s s Rectángulo
7: {
8 : public:
9: R e c t á n g u l o ();
10: - R e c t a n g u l o ( );
11 : v oi d A s i g n a r L o n g i t u d ( i n t longitud)
12: { s uLo ng it ud = l ongi t ud ; }
13: i n t Ob te ne r Lo n gi t ud () const
14: { r e t u r n suLongitud; }
15: v o i d A s i g n a r A n c h o ( i n t ancho)
16: { suAncho = ancho; }
17: i n t ObtenerAncho() const
18: { r et ur n suAncho; }
19: private:
20: i n t suLongitud;
21 : i n t suAncho;
22: };
23:
24: Rectángulo::Rectangulo()
25: {
26: suAncho = 5;
27: s uL o ng i tu d = 10;
28: }
29:
30: R e c t á n g u l o : : ~R ect an gul o( )
31 : {}
252 Día 8
L is t a d o 8 . 1 0 c o n t in u a c ió n
Otros compiladores sólo detendrán la compilación del código. Verifique la acción que
realiza su compilador en estos casos.
Apuntadores th is const
Cuando declara un objeto para que sea const, está en efecto declarando que el apuntador
t h i s es un apuntador a un objeto const. Un apuntador th is que sea const se puede uti
lizar solamente con funciones miembro const.
Los objetos constantes y los apuntadores constantes se describirán otra vez mañana,
cuando hablemos sobre las referencias a objetos constantes.
D ebe ‘i |
DEBE proteger los objetos pasados por
referencia con const si no se deben
modificar.
DEBE pasar por referencia los objetos
que se puedan modificar.
DEBE realizar el paso por valor cuando los
objetos pequeños no se deban cambiar.
Aritmética de apuntadores
Los apuntadores se pueden restar unos de otros. Una técnica poderosa es hacer que dos
apuntadores apunten a diferentes elementos de un arreglo, y tomar su diferencia para ver
cuántos elementos separan a los dos miembros. Esto puede ser útil al analizar sintáctica
mente arreglos de caracteres, como se muestra en el listado 8.11.
1: // L i s t a d o 8.11 Muestra el a n á l i s i s s i n t á c t i c o de
2: // p a l a b r a s en una cadena de caracteres
<•( tu t intuí
254 Día 8
L is t a d o 8.11 continuación
3:
4: #include <iostream.h>
5: #include <ctype.h>
6: ffinclude <string.h>
7:
8: bool ObtenerPalabra(char* cadena,
9: char* palabra,
10 int & desplazamientoDePalabra);
11
12 // programa controlador
13 int main()
14 {
15 const int tamBufer = 255;
16 char buffer[ tamBufer + 1 ]; // guardar toda la cadena
17 char palabra! tamBufer + 1 ]; // guardar una palabra
18 int desplazamientoDePalabra = 0; // empezar al principio
19
20 cout « "Escriba una cadena: ";
21 cin.getline(buffer, tamBufer);
22 while (ObtenerPalabra(buffer,
23 palabra,
24 desplazamientoDePalabra))
25 {
26 cout « "Obtuve esta palabra: " << palabra << endl;
27 }
28 return 0;
29 }
30
31 // función para analizar sintácticamente
32 // palabras de una cadena,
33 bool ObtenerPalabra(char* cadena,
34 char* palabra,
35 int & desplazamientoDePalabra)
36 {
37 // ¿es fin de cadena?
38 if (1cadena! desplazamientoDePalabra ])
39 return false;
40 char * api, * ap2;
41
42 // apuntar a la siguiente palabra
43 api = ap2 = cadena + desplazamientoDePalabra;
44
45 // saltarse los primeros espacios en blanco
46 for (int i = 0;
47 i < (int)strlen(apl) && !isalnum(aplI 0 ]);
48 i++)
49 apl++;
50
51 // ver si se tiene una palabra
52 if (Iisalnum(ap1í 0 ]))
53 return false;
54
55 // api ahora apunta al inicio de la siguiente palabra
56 // hacer que ap2 apunte ahi también
A p u n ta d o re s 255
57: ap2 = a p i ;
58:
59: // hacer que ap2 apunte a l f i n a l de la palabra 8
60: w h i l e ( i salnum(ap2[ 0 ]))
61 : ap2 + + ;
62:
63: // ap2 ahora se encuentra a l f i n a l de la palabra
64: // api es tá a l p r i n c i p i o de la palabra
65: // l a l o n g i t u d de l a palabra es la d i fe r e n c i a
66: i n t len = i n t (ap2 • api);
67:
68: // c o p i a r l a palabra en el búfer
69: s t r n c p y (pa la br a, ap1, len);
70:
71: // hacer que termine con el carácter nulo
72: p al abr a[ len ] = ' \ 0 1;
73:
74: // ahora encontr ar el p r i n c i p i o de la s ig u i e n t e palabra
75: f o r ( i n t i = i n t ( a p 2 - cadena);
76: i < ( i n t ) str len( cadena) && !isalnum(ap2[ 0 ]);
77: i++)
78: a p2+ +;
79: desplazamientoDePalabra = int(ap2 - cadena);
80: r et u rn tr ue;
81: }
A nálisis En la línea 20 se pide al usuario que escriba una cadena. Ésta se proporciona a
O b t e n e r P a l a b r a () en la línea 22, junto con un búfer en el que se va a guardar la
primera palabra y una variable entera llamada d es pl az ami en toD ePa la br a, la cual se ini-
c i al iza con cero en la línea 18. A medida que Ob tene rPa labr a () regresa palabras, éstas
se imprimen hasta que O b t e n e r P a l a b r a () regrese el valor f a l s e .
Cada llamada a O b t e n e r P a l a b r a () provoca un salto a la línea 33. En la línea 38 verifi
camos si el valor de c a d e n a [ d e s p l a z a m i e n t o D e P a l a b r a ] es igual a cero. Esto será
verdadero ( t r u e ) si el código llega al final de la cadena, y en este momento O b t en e r -
P a l a b r a () regresará el valor f a l s e .
Las lineas 46 a 49 avanzan por la cadena, llevando a api hasta el primer carácter alfanu
mèrico. Las líneas 52 y 53 aseguran que se encuentre un carácter alfanumèrico: en easo
contrario, se regresa el valor fa ls e .
Ahora api apunta al principio de la siguiente palabra, y la línea 57 hace que ap2 apunte
a la misma posición.
Entonces, las líneas 60 y 61 ocasionan que ap2 avance por la palabra, deteniéndose en el
primer carácter que no sea alfanumèrico (si existen caracteres con acentos, diéresis o enes
se tomarán como fin de palabra). Ahora ap2 apunta al final de la palabra, y api apunta al
principio de la misma palabra. Al restar api de ap2 en línea 66 y al convertir el resultado
en un valor entero, el código puede establecer la longitud de la palabra. Luego copia esa
palabra en el búfer llamado palabra, pasando a api como el punto de inicio, y pasando
como longitud la diferencia que se acaba de establecer.
En la línea 72 el código agrega un carácter nulo para marcar el final de la palabra. Luego
se incrementa ap2 para que apunte al principio de la siguiente palabra, y se coloca el des
plazamiento de esa palabra en la referencia a la variable entera llamada desplazamiento-
DePalabra. Finalmente, se regresa el valor true para indicar que se ha encontrado una
palabra.
Resumen
Los apuntadores proporcionan un medio poderoso para tener acceso a los datos mediante
la indirección. Cada variable tiene una dirección, misma que se puede obtener mediante
el uso del operador de dirección (&). La dirección se puede guardar en un apuntador.
Los apuntadores se declaran escribiendo el tipo de objeto al que van a apuntar, seguido
del operador de indirección (*) y del nombre del apuntador. Los apuntadores se deben
inicializar para que apunten a un objeto o a NULL.
Puede tener acceso al valor que se encuentra en la dirección guardada en un apuntador
mediante el uso del operador de indirección (*). Puede declarar apuntadores const, los
cuales no se pueden reasignar para que apunten a otros objetos, y apuntadores a objetos
const, los cuales no se pueden utilizar para cambiar los objetos a los que apuntan.
Para crear nuevos objetos en el heap, se utiliza la palabra reservada new y la dirección
que regresa se asigna a un apuntador. Esta memoria se libera llamando a la palabra reser
vada delete para que actúe sobre el apuntador, delete libera la memoria, pero no des
truye el apuntador. Por lo tanto, usted debe asignarle NULL al apuntador después de que
se haya liberado su memoria.
A p u n tad o res 257
Preguntas y respuestas
P ¿Por qué son tan importantes los apuntadores? 8
R Hoy vio cómo se utilizan los apuntadores para guardar la dirección de los objetos
que se encuentran en el heap. y cómo se utilizan para pasar argumentos por referen
cia. Además, en el día 13. “Polimorfismo”, verá cómo se utilizan los apuntadores
en el poliformismo de clases.
P ¿Por qué debo preocuparme por declarar algo en el heap?
R Los objetos que se encuentran en el heap persisten después de que la función ter
mina. Además, la capacidad de guardar objetos en el heap le permite decidir en
tiempo de ejecución cuántos objetos necesita, en lugar de tener que declarar esto
por adelantado. Esto se verá con más detalle mañana.
P ¿Por qué debo declarar un objeto como const si limita lo que puedo hacer
con él?
R Como programador, querrá que el compilador le ayude a encontrar errores. Un
grave error difícil de encontrar es una función que cambia un objeto en formas que
no son obvias para la función que hace la llamada. Al declarar un objeto como
co n st se previenen tales cambios.
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del
material tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes pasar al siguiente día.
Cuestionario
1. ¿Qué operador se utiliza para determinar la dirección de una variable?
2. ¿Qué operador se utiliza para encontrar el valor guardado en una dirección que se
guarda en un apuntador?
3. ¿Qué es un apuntador?
4. ¿Cuál es la diferencia entre la dirección que se guarda en un apuntador y el valor
que se encuentra en esa dirección?
5. ¿Cuál es la diferencia entre el operador de indirección y el operador de dirección?
6. ¿Cuál es la diferencia entre c o n s t i n t * apuntUno e i n t * c o n s t a p u n t D o s ?
258 Día 8
Ejercicios
1. ¿Qué hacen estas declaraciones?
a. int * apuno;
b. int varDos;
c. int * apTres = &varDos;
2. Si tiene una variable de tipo entero corto sin signo llamada suEdad, ¿cómo decla
raría un apuntador para que manipule a la variable suEdad?
3. Asigne el valor 50 a la variable suEdad usando el apuntador que declaró en el
ejercicio 2.
4. Escriba un pequeño programa que declare un entero y un apuntador a ese entero.
Asígnele al apuntador la dirección del entero. Utilice el apuntador para asignarle
un valor a la variable de tipo entero.
5. CAZA ERRORES: ¿Qué está mal en este código?
#include <iostream.h>
int main()
{
int * aplnt;
*aplnt = 9;
cout « "El valor en aplnt: " « *aplnt;
return 0;
>
6. CAZA ERRORES: ¿Qué está mal en este código?
int main()
{
int UnaVariable = 5;
cout « "UnaVariable: " « UnaVariable « "\n";
int * apVar = &UnaVariable;
apVar = 9;
cout « "UnaVariable: " « *apVar « "\n";
return 0;
}
S em an a 2
[Referencias
Ayer aprendió cómo utilizar apuntadores para manipular objetos en el heap y
cómo hacer referencia a esos objetos en forma indirecta. Las referencias, el tema
de la lección de hoy. le proporcionan casi todo el poder de los apuntadores, pero
con una sintaxis mucho más sencilla. Hoy aprenderá lo siguiente:
° Qué son las referencias
• Cuál es la diferencia entre referencias y apuntadores
• Cómo crear referencias y utilizarlas
• Cuáles son las limitaciones de las referencias
• Cómo pasar valores y objetos por referencia hacia y desde las
funciones
Puede crear una referencia escribiendo el tipo del objeto de destino, seguido del operador
de referencia (&) y del nombre de la referencia. Las referencias pueden utilizar cualquier
nombre válido para una variable, pero muchos programadores prefieren colocar una “r”
antes del nombre de la referencia. Por lo tanto, si tiene una variable de tipo entero llama
da unEntero, puede crear una referencia a esa variable escribiendo lo siguiente:
int & rllnaRef = unEntero;
Esto se lee como rllnaRef y es una referencia a un entero que se iniciali/a como refe
rencia a la variable unEntero. El listado 9.1 muestra cóm o se crean y se utilizan las
referencias.
O b s e rv e q u e el o p e ra d o r d e re fe re n c ia (&) es el m is m o s ím b o l o q u e se utiliza
p a ra el o p e r a d o r d e dirección. S in e m b a r g o , s o n d is t in t o s o p e r a d o r e s , a u n
q u e e stá n re la cio n a d o s.
L i s t a d o 9.1 C r e a c ió n y u s o d e r e f e r e n c ia s
1: //Listado 9.1
2: // Muestra del uso de referencias
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int intUno;
9: int & rUnaRef = intUno;
10
11 intUno = 5;
12 cout « "intUno: " « intUno « endl;
13 cout << "rUnaRef: " « rUnaRef « endl;
14
15 rUnaRef = 7;
16 cout « "intUno: " « intUno « endl;
17 cout « "rUnaRef: " « rUnaRef « endl;
18 return 0;
19 }
intUno: 5
S a l id a rllnaRef : 5
intUno: 7
rllnaRef: 7
R e fe re n cia s 261
i n t U no : 5
S a l id a rUnaRef: 5
&intUno: 0 x b f f f f a 1 4
&rUnaRef: 0 x b f f f f a 1 4
Tenga cuidado de diferenciar entre el símbolo &de la línea 9 del listado 9.2, el cual de
clara una referencia a un entero llamado r U n a Re f , y los símbolos & de las líneas I5 y 16,
los cuales regresan las direcciones de la variable de tipo entero llamada i n t U n o y de la
referencia r U na Re f .
Por lo general, al utilizar una referencia, no se utiliza el operador de dirección. Simple
mente se utiliza la referencia de la misma forma en que se utilizaría la variable destino.
Esto se muestra en la línea 13.
1: / / L i s t a d o 9.3
2 : / / R e a s i g n a c i o n de una r e f e r e n c i a
3:
4: //include < i o s t r e a m . h >
5:
6: i n t m a i n ()
7: {
8: i n t intUno; 9
9: i n t & rllnaRef = intUno;
10
11 i ntUno = 5;
12 cout << "' i n t U n o : \ t " << intUno << endl;
13 cout << "1r U n a R e f : \ t " << rUnaRef << endl ;
14 cout << 1S i n t U n o : \ t " << &intUno << endl;
15 cout << 1'& r UnaR ef : \t " « &rUnaRef « endl;
16
17 i n t i n t D o s = 8;
18 rUnaRef =: i n t D o s ; // ¡no es lo que usted pi ens a
19 cout << 1' \ n i n t U n o : \t " << intUno << endl ;
20 cout << 11i n t D o s : \ t " « i nt Do s « endl;
21 cout << '' rUnaRef : \ t " << rUnaRef << endl ;
22 cout << 1'& i n t U n o : \ t " << &intUno << endl ;
23 cout << 1'& i n t D o s : \ t " << S in t Do s << endl ;
24 cout << '' S r U n a R e f : \t " << SrUnaRef << endl;
25 r e t u r n 0;
26
intUno: 5
rUnaRef: 5
SintUno: 0xbffffa14
SrUnaRef: 0xbff f fa14
intUno: 8
intDos: 8
rUnaRef: 8
SintUno: 0xbff f fa14
SintDos: 0 x b f f f fa0c
SrUnaRef: 0xbff f fa14
Una vez más, en las líneas 8 y 9 se declaran una variable de tipo entero y una
referencia a un entero. En la línea l l se asigna el valor 5 a la variable, y los
valores y sus direcciones se imprimen en las líneas 12 a 15.
En la línea 17 se crea una nueva variable llamada in t Do s , y se inicializa con el valor 8. En
la línea I8, el programador trata de reasignar a rllnaRef a fin de que sea un alias para la
264 D ía 9
variable i n t D o s , pero esto no es lo que ocurre. Lo que ocurre realmente es que rUnaRef
sigue actuando como alias para i n t u n o , por lo que esta asignación es equivalente a:
i ntUno = i n t D o s ;
D ebe N O D EBE
DEBE utilizar referencias para crear un NO DEBE tratar de reasignar una referencia.
alias para un objeto. NO DEBE confundir el operador de direc
DEBE inicializar todas las referencias. ción con el operador de referencia.
¿Q u é se puede referemciar?
Cualquier objeto se puede referenciar, incluyendo los objetos definidos por el usuario.
Observe que crea una referencia a un objeto, pero no a una clase o a un tipo. No debe
escribir lo siguiente:
in t & rlntRef = int; // i n c o r r e c t o
Las referencias a objetos se utilizan igual que el objeto mismo. Los datos miembro y los
métodos se acceden mediante el operador común de acceso a los miembros de una clase
(.), y al igual que los tipos integrados, la referencia actúa como alias para el objeto. El
listado 9.4 muestra esto.
1: II L i s t a d o 9.4
2: // R e f e r e n c i a s a o b j e t o s de una c l a s e
3
4 tfinclude ^ i o s t r e a m .h>
Referencias 265
5:
6: class GatoSimple
7: {
8: public:
9: GatoSimple (int edad, int peso);
10: -GatoSimple() {}
11: int ObtenerEdad()
12: { return suEdad; }
13: int ObtenerPeso()
14: { return suPeso; }
15: private:
16: int suEdad;
17: int suPeso;
18: };
19:
20: GatoSimple::GatoSimple(int edad, int peso)
21 : {
22: suEdad = edad;
23: suPeso = peso;
24: }
25:
26: int main()
27: {
28: GatoSimple Pelusa(5, 8);
29: GatoSimple & rGato = Pelusa;
30:
31: cout « "Pelusa tiene: ";
32: cout « Pelusa.ObtenerEdad() « " años de edad. \n";
33: cout « "Y Pelusa pesa: ";
34: cout « rGato.ObtenerPeso() « " libras. \n";
35: return 0;
36: }
A
En la línea 28, Pelusa se declara como un objeto de la clase GatoSimple. En la
n á l is is
línea 29 se declara una referencia a GatoSimple llamada rGato, y se inicializa
com o referencia a Pelusa. En las líneas 32 y 34 se realiza una llamada a los métodos
de acceso de GatoSimple utilizando primero el objeto Pelusa y luego la referencia a
Pelusa. O bserve que el acceso es idéntico. De nuevo, la referencia es un alias para el
objeto actual.
Referencias
U n a refe ren cia se d e clara e scrib ie n d o él tipo, se g u id o del o p e rad o r de referencia (&) y
del n o m b ré d e la referencia'. Las referencias sé debén irticializár al m o m e n to d e su
creación. ; ;
Ejemplo 1:
i n t suEdad;
i n t & rEdad = suEdad;
Ejemplo 2:
GATO s i l v e s t r e ;
GATO & r Gat oRef = s i l v e s t r e ;
__________________________________________________________________________________
Uso de apuntadores n u lo s
y referencias nulas
Cuando no se inicializan los apuntadores, o cuando se elim inan, se les dehe asignar NULL.
Esto no se aplica a las referencias. De hecho, una referencia no puede ser nula, y un
programa que tenga una referencia a un objeto nulo se considera no válido. Cuando
un programa no es válido, puede ocurrir cualquier cosa. Puede parecer que funciona, o
puede escribir datos raros (e incorrectos) en los archivos de su disco. Ambas son posibles
consecuencias de un programa no válido.
La mayoría de los compiladores soporta un objeto nulo sin m uchos problemas, y el pro
grama deja de funcionar sólo si se trata de utilizar el objeto. Sin embargo, aprovecharse
de esto no es una buena idea. Si transporta su programa a otro equipo o compilador, se
podrían desarrollar errores misteriosos si tiene objetos nulos.
E n t r a d a L is t a d o 9 .6 C ó m o s im u la r el p a s o p o r r e f e r e n c ia u s a n d o a p u n t a d o r e s
Comprensión
y prototipos de funciones
El listado 9.6 muestra a la función intercambiar () utilizando apuntadores, y el listado
9.7 la muestra utilizando referencias. Usar la función que toma referencias es más senci
llo. y el código es más fácil de leer, pero ¿cómo sabe la función que hace la llamada si
los valores se pasan por referencia o por valor? Como cliente (o usuario) de intercam-
biar( ). el programador debe asegurarse de que intercambiare) cambie los parámetros.
Éste es otro uso del prototipo de la función. Al examinar los parámetros declarados en el
prototipo, el cual se encuentra por lo general en un archivo de encabezado junto con los
demás prototipos, el programador sabe que los valores que se pasan a intercambiar ()
se pasan por referencia, y por lo tanto se intercambiarán apropiadamente.
Si intercambiar () hubiera sido una función miembro de una clase, la declaración de la
clase, que también se encuentra disponible en un archivo de encabezado, hubiera propor
cionado esta información.
En C++, los clientes de las clases y funciones dependen del archivo de encabezado para
que les indique todo lo necesario; este archivo actúa como la interfaz para la clase o fun
ción. La implementación real está oculta para el cliente. Esto permite que el programador
se enfoque en el problema en cuestión y utilice la clase o función sin preocuparse por la
forma en que funciona.
Cuando el coronel John Roebling diseñó el puente de Brooklyn, se preocupó mucho pol
la forma en que se vertió el cemento y por la forma en que se fabricó el cable para el
puente. Estaba íntimamente involucrado en los procesos mecánicos y químicos requeri
dos para crear sus materiales. No obstante, en la actualidad los ingenieros aprovechan su
tiempo en forma más eficiente al utilizar materiales bien conocidos, sin importar la
torma en que sus labricantes los hayan producido.
El objetivo de C++ es permitir que los programadores dependan de clases y funciones bien
conocidas sin importar su funcionamiento intemo. Estos “componentes'* se pueden ensam
blar para producir un programa, lo que es muy parecido a la forma en que se ensamblan
cables, tuberías, abrazaderas y otros componentes para construir edificios y puentes.
272 Día 9
15
16 e r r o r = F a c t o r ( numero, &alcuadrado, &alcubo);
17 íf (!error)
18 {
19 cout << "numero: " << numero << " \ n " ;
20 cout << " a l cuadrado: “ << alcuadrado « ”\ n" ;
21 cout << " a l cubo: " << alcubo « " \ n " ;
22 }
23 else
24
25
cout << " ¡ S e encontró un e r r o r ! ! \ n " ;
r e t u r n 0;
9
26
27
28 s ho r t F a c t o r ( i n t n, int * apAlCuadrado, int * apAlCubo)
29 {
30 s h o r t V a l o r = 0;
31
32 if (n > 20)
33 Valor = 1 ;
34 else
35 {
36 ‘ apAlCuadrado = n * n;
37 ‘ apAlCubo = n * n * n;
38 V a l o r = 0;
39 }
40 r e t u r n V al or ;
41
E s c r i b a un número (0 20) : 3
S alida número: 3
al cuadrado: 9
a l cubo: 27
En la línea l(), numero, a l c u a d r a d o y a l c u bo se definen como valores de tipo
A nálisis
i n t . A numero se le asigna un valor basado en la entrada que proporcione el
usuario. Este valor y las direcciones de a l c u a d r a d o y a l c u bo se pasan a la función
F a c t o r ().
F a c t o r ()
examina el primer parámetro, el cual se pasa por medio de V a l o r . Si es mayor
que 20 (el valor máximo que esta función puede manejar), asigna a V a l o r un 1 como va
lor de error. Observe que el valor de retorno de F a c t o r () se reserva ya sea para este
valor de error o para el valor 0, lo cual indica que todo salió bien, y observe que la fun
ción regresa este valor en la línea 40.
Los valores que realmente se necesitan, el cuadrado y el cubo de numero, no se regresan
por medio del mecanismo ríe retorno, sino cambiando las variables originales (cuyas di
recciones están contenidas en los apuntadores que se pasaron a la función).
274 Día 9
31
32 CODIGO_ERR F a c t o r (USHORT n, USHORT & rAlCuadrado, USHORT & rAlCubo)
33 {
34 i f (n > 20)
35 r e t u r n ERROR; // codigo simple de e r r o r
36 else
37 {
38 rAlCuadrado = n * n;
39 rAlCubo = n * n * n;
40
41
r e t ur n EXITO; 9
}
42 }
E s c r i b a un número (0 20): 3
S alida número: 3
a l cuadrado: 9
a l cubo: 27
Cuando se destruye el objeto temporal, lo que ocurre cuando una función termina, se lla
ma al destructor del objeto. Si un objeto se regresa de la función por valor, se debe crear
y destruir también una copia de ese objeto.
Con objetos grandes, estas llamadas al constructor y al destructor pueden disminuir la
velocidad y aumentar el uso de la memoria. Para ilustrar esta idea, el listado 9.10 crea
un objeto simplificado creado por el usuario: GatoSimple. Un objeto real podría ser más
grande y costoso, pero esto es suficiente para mostrar con qué frecuencia se llama al
constructor y al destructor de copia.
El listado 9.10 crea el objeto GatoSimple y luego llama dos funciones. La primera
función recibe al objeto GatoSimple por valor y luego lo regresa por valor. La segunda
recibe un apuntador al objeto, en lugar del objeto en sí, y regresa un apuntador al objeto.
L is t a d o 9 .1 0 P a s o d e o b j e t o s p o r r e f e r e n c ia m e d i a n t e el u s o
E n t r a d a de ap u n tad o re s
1: //Listado 9.10
2: // Paso de apuntadores a objetos
3:
4: #include <iostream.h>
5:
6: class GatoSimple
7: {
8: public:
9: GatoSimple (); //constructor
10: GatoSimple (GatoSimple &) ; // constructor de copia
11: -GatoSimple(); // destructor
12: };
13:
14: GatoSimple::GatoSimple()
15: {
16: cout << "Constructor de GatoSimple...\n";
17: }
18:
19: GatoSimple:¡GatoSimple(GatoSimple &)
20: {
21: cout << "Constructor de copia de GatoSimple...\n" ;
22: >
23:
24: GatoSimple:¡-GatoSimple()
25: {
26: cout << "Destructor de GatoSimple...\n" ;
27: }
28:
29: GatoSimple Funcionllno(GatoSimple elGato);
30: GatoSimple * FuncionDos(GatoSimple * elGato);
31 :
32: int main()
33: {
Referencias 277
1: Crear un gato...
S alida 2: Constructor de GatoSimple...
3: Llamando a Funcionllno...
4: Constructor de copia de GatoSimple
5: Funcionllno. Regresando...
6: Constructor de copia de GatoSimple
7: Destructor de GatoSimple...
8: Destructor de GatoSimple...
9: Llamando a FuncionDos...
10: FuncionDos. Regresando...
11 : Destructor de GatoSimple...
Finalm ente, el program a term ina y Pelusa q u ed a fu era de alcan ce, lo que produce una
últim a llam ada al destructor y se im prim e la línea 11 de la salida.
El efecto ocasionado con esto es que la llam ada a FuncionUno(), d eb id o a que pasó el
objeto gato por valor, produjo dos llam adas al co n stru cto r de co p ia y dos al destructor,
m ientras que la llam ada a FuncionDos () no p ro d u jo n inguna.
Pasar por valor es com o dar a un m useo una fo to g rafía de su o b ra m aestra en lugar de
darle la verdadera. Si los vándalos la rayan, no se dañ a la o rig in al. P asar por referencia
es com o enviar su dirección particular al m useo y h acer q u e v en g an invitados y vean la
obra verdadera.
La solución es pasar un apuntador a un objeto GatoSimple co n stan te. H acer esto evita
que se llam e a cualquier m étodo que no sea co nstante en GatoSimple. y por consecuen
cia evita que el objeto sea cam biado. El listado 9.11 m uestra esta idea.
R e fe re n c ia s 279
i
280 Día 9
L istado 9 . 1 1 c o n t in u a c ió n
Crear un gato...
Constructor de GatoSimple...
Pelusa tiene 1 años de edad
Pelusa tiene 5 años de edad
Llamando a FuncionDos...
FuncionDos. Regresando...
Ahora Pelusa tiene 5 años de edad
Pelusa tiene 5 años de edad
Destructor de GatoSimple...
1: / / Li s t a d o 9.12
2: // Paso de r e f e r e n c i a s a objetos
3:
4: # i n c l u d e < i os tr eam. h>
5:
6: c l a s s GatoSimple
7: {
8: public:
9: G a t o S i m p l e () ;
10: GatoSimple(GatoSimple &);
11: - G a t o S i m p l e ();
12: i n t ObtenerEdad() const
13: { r et ur n suEdad; }
14: v oi d A s i g n a r E d a d ( i n t edad)
15: { suEdad = edad; }
16: private:
17: i n t suEdad;
18: };
19:
20: G a t oS i m pl e : : Gat oS imple( )
21 : {
22: cout << "C ons tr u cto r de GatoSimple. . . \ n ";
23: suEdad = 1;
24: }
25:
onunua
L istado 9 .1 2 c o n t in u a c ió n
Crear un gato...
S a lid a Constructor de GatoSimple...
Pelusa tiene 1 años de edad
Pelusa tiene 5 años de edad
Llamando a FuncionDos...
FuncionDos. Regresando...
Ahora Pelusa tiene 5 años de edad
Pelusa tiene 5 años de edad
Destructor de GatoSimple...
R e fe re n cia s 283
R eferencias const
P o r lo g e n e ra l, los p r o g r a m a d o r e s de C + + n o hacen diferencias e n tre " r e f e r e n c i a c o n s
ta n t e a u n o b j e t o G a t o S i m p l e " y "referen cia a u n objeto G a t o S im p le c o n s t a n t e " . Las
re fe re n c ia s n o se p u e d e n r e a s i g n a r para referirse a o tro objeto, p o r lo q u e s ie m p r e s o n
c o n st a n t e s . Si se a plica la p a la b ra reservada const a un a referencia, es p a ra h a c e r q u e el
o b j e t o al q u e se refiere sea constante.
D ebe NO DEBE
D E B E p a s a r p a r á m e t r o s p o r referencia N O D E B E u t il iz a r a p u n t a d o r e s si puede
s i e m p r e q u e sea posible. u t iliz a r re f e re n c ia s .
D E B E r e g r e s a r p a r á m e t r o s p o r referencia N O D E B E r e g r e s a r u n a r e f e re n c ia a un
s i e m p r e q u e sea posible. o b j e t o local.
D E B E utiliza r c o n s t para p r o t e g e r las r e f e
ren cia s y los a p u n t a d o r e s s ie m p re q u e sea
posib le.
Esta declaración indica que U n a F u n c i o n toma tres parámetros. El primero es una referen
cia a un objeto llamado P e r s o n a , el segundo es un apuntador a un objeto llamado Casa, y
el tercero es un entero. La función regresa un apuntador a un objeto GATO.
La cuestión de dónde colocar el operador de referencia (&) o el de indirección (*) al
declarar las variables causa gran controversia. Puede escribir válidamente cualquiera de
las siguientes instrucciones:
1: GAT0& rP e lu sa ;
2: GATO & r P e l u s a ;
3: GATO & rP e lu sa ;
I
Referencias 285
En trada L is ta d o 9.13 R e g r e s o d e u n a re fe re n c ia a u n o b j e t o in e x is t e n t e
L istado 9 .1 3 c o n t in u a c ió n
3:
4: tfinclude <iostream.h>
5:
6: class GatoSimple
7: {
8: public:
9: GatoSimple(int edad, int peso);
10: -GatoSimple() {}
11: int ObtenerEdad()
12: { return suEdad; }
13: int ObtenerPeso()
14: { return suPeso; }
15: private:
16: int suEdad;
17: int suPeso;
18: };
19:
20: GatoSimple:‘
.GatoSimple(int edad, int peso)
21: {
22: suEdad = edad;
23: suPeso = peso;
24: }
25:
26: GatoSimple & LaFuncion();
27:
28: int main()
29: {
30: GatoSimple & rGato = LaFuncion();
31: int edad = rGato.ObtenerEdad();
32: cout « "¡rGato tiene " « edad « " años de edad!\n";
33: return 0;
34: }
35:
36: GatoSimple & LaFuncion()
37: {
38: GatoSimple Pelusa(5, 9);
39: return Pelusa;
40: }
L ista do 9 . 1 4 c o n t in u a c ió n
apPelusa: 0x8049b88
Salida ¡rGato tiene 5 años de edad!
&rGato: 0x8049b88
El c ó d ig o m o s t r a d o e n el lista d o 9 .1 4 se c o m p ila , se e n la z a y p a re c e f u n
Precaución c io n a r p e rfe c ta m e n te . P e ro es u n a b o m b a d e t ie m p o e s p e r a n d o a c tiv a rse ya
q u e h a y u n a f u g a d e m e m o ria ocu lta .
Para probar que la referencia declarada en main() está haciendo referencia al objeto de
L a F u n c i o n ( ) colocado en el heap. se aplica el operador de dirección a rGato. Cierta
mente muestra la dirección del objeto al que está haciendo referencia, y esto concuerda
con la dirección del objeto que se encuentra en el heap.
Hasta ahora lodo está bien. Pero, ¿cuánta memoria se liberará? No se puede usar d elete
sobre la referencia. Una solución inteligente es crear otro apuntador e inicializarlo con la
dirección obtenida de rGato. Esto sí elimina la memoria y tapa la fuga de memoria. Sin
embargo, hay un pequeño problema: ¿A quién hace referencia rGato después de la línea
36? Como se dijo anteriormente, una referencia siempre debe ser un alias para un objeto
actual: si hace referencia a un objeto nulo (como lo hace ahora), el programa no es válido.
Si está escribiendo una función que necesita reservar memoria \ lucen pasarla de regreso
a la función que hizo la llamada, considere cambiar su interfaz. Haga que la función que
hace la llamada asigne la memoria y luego pásela por reicrcncia a su (unción, Esto deja
fuera toda la administración de memoria de su programa y la pasa a la función que está
preparada para eliminarla.
D e b e N O DEBE
D E B E pasar p a rá m e tro s p o r v a lo r c u a n d o N O D E B E p a sa r p o r re fe re n cia si el ele
sea necesario. m e n to al q u e se h a ce re fere n cia puede
D E B E re gre sar p arám e tro s p o r valor q u e d a r fu e r a d e alcance.
cu an d o sea necesario. N O D E B E u tiliz a r re fe re n cia s a objetos
nulos.
Resumen
Hoy aprendió lo que son las referencias y cómo se comparan con los apuntadores. Tam
bién vio que debe inicializar las referencias para referirse a un objeto existente, y que no
las puede asignar a ninguna otra cosa. Cualquier acción realizada sobre una referencia en
realidad afecta al objeto destino de la referencia. La prueba de esto es que si se toma la
dirección de una referencia, se regresa la dirección del destino.
Vio que puede ser más eficiente pasar objetos por referencia que pasarlos por valor. Pasar
por referencia también permite que la función llamada cambie el valor de los argumentos
de la función que hace la llamada.
También vio que los argumentos de las funciones y los valores regresados de las funcio
nes se pueden pasar por referencia, y que esto se puede implementar con apuntadores o
con referencias.
Vio cómo utilizar apuntadores a objetos constantes y referencias constantes para pasar
con seguridad valores entre funciones, al tiempo que logra la eficiencia que se obtiene al
pasar por referencia.
Preguntas y respuestas
l* ¿Por qué tener referencias si los apuntadores pueden hacer todo lo que hacen
las referencias?
P Las referencias son más fáciles de usar y de entender. La indirección está oculta, y
no hay necesidad de desreferenciar repetidamente la variable.
j
R e fe re n c ia s 291
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del
material tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D.
“Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
C u e s t io n a r io
L ¿Cuál es la diferencia entre una referencia y un apuntador?
2. ¿Cuándo debe utilizar un apuntador en vez de una referencia?
3. ¿Qué regresa new si no hay memoria suficiente para crear el nuevo objeto?
4. ¿Qué es una referencia constante?
5. ¿Cuál es la diferencia entre pasar por referencia y pasar una referencia?
E je rc ic io s
L Escriba un programa que declare un int, una referencia a un in t. y un apuntador
a un in t. Use el apuntador y la referencia para manipular el valor contenido en
el in t.
2. Escriba un programa que declare un apuntador constante a un entero constante.
Inicial ice el apuntador a una variable entera, varUno. Asigne el valor 6 a varUno.
Use el apuntador para asignar el valor 7 a varUno. Cree otra variable de tipo entero
que se llame v a r D o s . Reasigne el apuntador a varDos. No compile todavía este
ejercicio.
3. Ahora compile el programa del ejercicio 2. ¿Qué es lo que produce errores .’ ¿Qué
es lo que produce advertencias?
i
292 Día 9
10
Funciones avanzadas
En el día 5. “Funciones", conoció los fundamentos del trabajo con funciones.
Ahora que sabe cómo funcionan los apuntadores y las referencias, puede hacer
más cosas con las funciones. Hoy aprenderá lo siguiente:
• Cómo sobrecargar funciones miembro
• Cómo sobrecargar operadores
• Cómo escribir funciones para soportar clases con variables asignadas
en forma dinámica
Di bu i a r F i g u r a ( ) :
S a lid a ******************************
******************************
******************************
****************************** 10
******************************
DibujarFigura(40, 2):
****************************************
****************************************
El listado l(). I representa una versión simplificada del proyecto del repaso de la
semana l . La prueba de valores no válidos se ha omitido para ahorrar espacio,
al igual que algunas de las funciones de acceso. El programa principal ha sido reducido a
un simple programa controlador, en lugar de un menú.
Sin embargo, la parte importante del código se encuentra en las líneas 14 y 15. en
donde se sobrecarga la función miembro D i b u j a r F i g u r a (). La implementaeión de
estos métodos sobrecargados de la clase se encuentra en las líneas 30 a 47. Observe
que la versión de D i b u j a r F i g u r a ( ) que no toma parámetros simplemente llama a la
versión que toma dos parámetros, y le pasa los valores miembro actuales. Trate de no
duplicar código en dos funciones. Si no puede, tratar de mantenerlas sincronizadas
cuando haga cambios a una u otra será muy difícil, y estará propenso a cometer
errores.
El programa controlador de las líneas 50 a 59 crea un objeto rectángulo y luego llama a
D i b u j a r F i g u r a ( ). primero sin pasar parámetros, y luego pasando dos enteros.
El compilador decide cuál método llamar con base en el número y tipo de parámetros
proporcionados. Podríamos imaginarnos una tercera función sobrecargada llamada
D i b u j a r F i g u r a ( ) que tome una dimensión y una enumeración, ya sea para el ancho o la
altura, esto a elección del usuario.
Día 10
L296
41: // u s a r v a l o r e s de l o s parámetros
42: imprimeAncho = ancho;
43: i mp r i m eA l t u r a = a l t u r a ;
44: }
45: for ( i n t i = 0; i < i mpr im eAl t ur a; i++)
46: {
47: for (int j = 0; j < imprimeAncho; j++)
48: {
49: cout <<
50: }
51 : cout << " \ n" ;
52: }
53: }
54:
55: // Programa c o n t r o l a d o r para mostrar l a s funci ones sobr ecar gadas
56: i n t ma i n( )
57: {
58: // i n i c i a l i z a r un r e ct á n g ul o con 30,5
59: R e c tá n gu lo e l R e c t ( 3 0 , 5);
60: cout << " D i b u j a r F i g u r a ( 0 , 0, t r u e ) . . . \ n " ;
61: e l R e c t .D i b u j a r F i g u r a (0, 0, tr ue) ;
62: cout << " D i b u j a r F i g u r a ( 4 0 , 2 ) . . . \ n " ;
63: e l R e c t .D i b u j a r F i g u r a (40, 2);
64: return 0;
65: }
D i b u j a r F i g u r a (0, 0, t r u e ) . . .
******************************
******************************
******************************
******************************
******************************
D i b u j a r F i g u r a (40, 2 ) . . .
****************************************
****************************************
¿Cómo elegir entre usar la sobrecarga de funciones o los valores predeterminados? Una
buena regla empírica es utilizar la sobrecarga de funciones bajo las siguientes condiciones:
• Que no exista un valor predeterminado razonable.
• Cuando necesite distintos algoritmos.
• Siempre que necesite parámetros de distintos tipos en las funciones o métodos
(diferentes tipos de datos o clases para diferentes llamadas).
Constructores predeterminados
Como se dijo en el día 6, “Clases base”, si no declara explícitamente un constructor para
una clase, se crea un constructor predeterminado que no lleva parámetros y no hace
nada. No obstante, usted puede hacer su propio constructor predeterminado que no lleve
parámetros pero que “configure” su objeto como lo necesite.
El constructor que le ofrece el compilador se conoce como el constructor “predeter
minado”, pero por convención así se llama también cualquier constructor que no lleve
parámetros. Esto puede ser un poco confuso, pero por lo general es más claro viéndolo
desde el contexto que cada uno quiera dar a entender.
Tome en cuenta que si codifica algún constructor, el compilador no creará el constructor
predeterminado. Así es que si quiere un constructor que no lleve parámetros v ya tiene
creado algún otro constructor, ¡deberá codificar su propio constructor predeterminado!
F u n c io n e s a v a n za d a s 299
Sobrecarga de constructores
El objetivo de un constructor es establecer el objeto: por ejemplo, el objetivo del cons
tructor de un R e c t á n g u l o es crear un rectángulo. Antes de que el constructor se ejecute
no existe ningún rectángulo, sólo un área de memoria. Al terminar el constructor, se tiene
un objeto rectángulo completo \ listo para utilizarlo.
Los constructores, al igual que todas las funciones miembro, se pueden sobrecargar. La
capacidad para sobrecargar constructores es muy poderosa y flexible.
Por ejemplo, podría tener un objeto rectángulo que tenga dos constructores: El primero
recibe una longitud y una anchura y crea un rectángulo de ese tamaño. El segundo
no recibe valores y crea un rectángulo de un tamaño predeterminado. El listado 10.3
implementa esta idea.
Ancho de Rectl: 5
S a l id a Longitud de Rectl: 10
Escriba un ancho: 20
A nálisis La clase Rectángulo se declara en las líneas 6 a 19. Se declaran dos constructo
res: el “constructor predeterminado” (en la línea 9) y el constructor que toma dos
variables de tipo entero como parámetros (línea 10).
En la línea 35 se crea un rectángulo utilizando el constructor predeterminado, y se
imprimen sus valores en las líneas 37 y 39. En las líneas 41 a 45 se pide al usuario un
ancho y una longitud, y en la línea 47 se llama al constructor que lleva dos parámetros.
Finalmente, el ancho y la longitud de este rectángulo se imprimen en las líneas 49 y 51.
Al igual que con cualquier función sobrecargada, el compilador elige el constructor
correcto con base en el número y el tipo de los parámetros.
Inicialización de objetos
Con la excepción de las líneas 21 y 22 del listado 10.2. hasta ahora las variables miembro
de los objetos se establecían en el cuerpo del constructor. Sin em bargo, los constructores
se invocan en dos etapas: la etapa de inicialización y la del cuerpo.
F u n c io n e s a v a n z a d a s 301
Casi todas las variables se pueden establecer en cualquier etapa, ya sea en la etapa de
inicialización o asignándoles un valor en el cuerpo del constructor. Es más limpio y e n
ciente inicializar variables miembro en la etapa de inicialización. El siguiente ejemplo
muestra cómo inicializar variables miembro:
GAT0(): // nombre del constructor y l i s t a
suEdad(5), // de i n i c i a l i z a c i ó n de parámetros
suPeso(8)
{ } // cuerpo del constructor
1: Rectángulo::Rectangulo():
2: suAncho(5),
3: suLongitud(10)
4: {}
5:
6: Rectángulo: : Rectángulo(int ancho, int longitud):
7: suAncho(ancho),
8: suLongitud(longitud)
9: O
10:
No hay salida.
Algunas variables se deben inicializar y no se les puede asignar un valor, como a las
referencias y constantes. Es común tener otras asignaciones o instrucciones de acción
en el cuerpo del constructor: sin embargo, es mejor utilizar la inicialización tanto como
sea posible.
| 302 Día 10
Esto conducirá a un desastre cuando cualquiera de los dos o b jeto s GATO queden fuera de
alcance. C om o se m encionó en el día 8 , “ A p untadores", el trab ajo del destructor es lim
piar esta m em oria. Si el destructor del GATO original libera esta m em oria y el nuevo GATO
aún sigue apuntando a esa m em oria, se crea un ap u n tad o r perdido, y el program a estará
en peligro mortal. La figura 10.2 ilustra este problem a.
Funciones avanzadas 303
Figura 10.1
Uso det constructor tic
copia predeterminado.
Figura 10.2
Creación de un
apuntador perdido.
En t r a d a L is t a d o 1 0 .5 Constructores de copia
1: // Listado 10.5
2: // Constructores de copia
3:
4: #include <iostream.h>
5:
6: class GATO
7: {
8: public:
9: GAT0(); // constructor predeterminado
10: GATO (const GATO &); // constructor de copia
11: —GATO(); // destructor
12: int 0btenerEdad() const
13: { return *suEdad; }
14: int ObtenerPeso() const
15: { return *suPeso; }
16: void AsignarEdad(int edad)
17: { *suEdad = edad; }
18:
19: private:
20: int * suEdad;
21: int * suPeso;
22: };
o > n tin tu í
L is t a d o 10.5 c o n t in u a c ió n
23:
24: GATO::GATO()
25: {
26: suEdad = new int;
27: suPeso = new int;
28: *suEdad = 5;
29: *suPeso = 9;
30: }
31:
32: GATO::GATO (const GATO & rhs)
33: {
34: suEdad= new int;
35: suPeso= new int;
36: *suEdad = rhs.ObtenerEdad(); // acceso público
37: *suPeso = *(rhs.suPeso); // acceso privado
38: }
39:
40: GATO::-GATO()
41: {
42: delete suEdad;
43: suEdad = NULL;
44: delete suPeso;
45: suPeso = NULL;
46: }
47:
48: int main()
49: {
50: GATO pelusa;
51: cout « "edad de pelusa: ";
52: cout « pelusa.ObtenerEdad() « endl;
53: cout « "Establecer la edad de pelusa en 6...\n";
54: pelusa.AsignarEdad(6);
55: cout « "Crear a silvestre a partir de pelusa\n";
56: GATO silvestre(pelusa);
57: cout « "edad de pelusa: ";
58: cout « pelusa.ObtenerEdad() << endl;
59: cout « "edad de silvestre: ";
60: cout « silvestre.ObtenerEdad() << endl;
61: cout « "establecer edad de pelusa en 7...\n" ;
62: pelusa.AsignarEdad(7);
63: cout « "edad de pelusa: ";
64: cout « pelusa.ObtenerEdad() « endl;
65: cout « "edad de silvestre: ";
66: cout « silvestre.ObtenerEdad() << endl;
67: return 0;
68: }
edad de pelusa: 5
S a lida Establecer la edad de pelusa en 6...
Crear a silvestre a partir de pelusa
edad de pelusa: 6
edad de silvestre: 6
establecer edad de pelusa en 7...
edad de pelusa: 7
edad de silvestre: 6
F u n c io n e s a v a n za d a s 305
Fi g u r a 10.3 HeaP
I lu s tr a c ió n d e u n a 5
c a p ia p r o fu n d a .
/
GATO v ie jo GATO n u e v o
suEdad — suEdad
306 Día 10
Sobrecarga de operadores
C++ tiene varios tipos de datos integrados, incluyendo in t , double. char. etc. Cada uno
de estos tipos tiene una variedad de operadores integrados, como el de suma ( +) y el de
multiplicación (*). C++ también le permite agregar estos operadores a sus propias clases.
Para explorar completamente la sobrecarga de operadores, el listado 10.6 crea una nueva
clase llamada Contador. Se utilizará un objeto Contador para contar (¡sorpresa!) en los
ciclos y en otras aplicaciones en donde se deba incrementar, decrementar o rastrear de
alguna otra forma un número.
19: C o n t a d o r : : C o n t a d o r () :
20: s u V a l (0)
21: { }
22:
23 : i n t mam ( )
24: {
25: Contador i;
26: cout << " E l v a l o r de i es ";
27: cout << i .O b te ne r Su V al () « endl;
28: r e t u r n 0;
29: }
E l v a l o r de i es 0
Así como se ve. es una ciase bastante inútil. Se define en las líneas 6 a l 7. Su
única variable miembro es un int. El constructor predeterminado, el cual se de 10
clara en la línea 9 y cuya ¡mplementación se encuentra en la línea 19. inicializa esa única
variable miembro. suVal. con cero.
A diferencia de un in t común definido por el compilador, el objeto Contador no se
puede incrementar, decrementar. sumar, asignarle un valor ni se puede manipular de
ninguna otra forma. Esta es una característica, no un problema. A cambio de esto, ¡hace
que su valor sea más difícil de imprimir! La impresión es más difícil debido a que no
existe un formato definido por el compilador, por lo que usted tiene que crearlo.
C ó m o e s c r ib ir u n a f u n c ió n d e in c r e m e n t o
La sobrecarga de operadores restaura la mayor parte de la funcionalidad que se ha quita
do de esta clase. Por ejemplo, existen dos formas de agregar la capacidad de incrementar
un objeto C on t a d o r . La primera es escribir un método de incremento, como se muestra
en el listado 10.7.
Listado 1 0 .7 C ó m o a g r e g a r un o p e r a d o r d e in c re m e n to
1: / / L i s t a d o 10.7
2: // La c l a s e Contador
3:
4:
5: # i n c l u d e < i os tr eam. h>
6:
7: c l a s s Contador
8: {
9: public:
10: C o n t a d o r ( );
11 : -C ontador(){}
12 : i n t O b tene rS uVa l( )c ons t
13: { r et u rn suVal; }
14: void A s i g n a r S u V a l ( i n t x)
t 'iitm iia
308 Día 10
L is t a d o 1 0 .7 c o n t in u a ció n
15: {suVal = x; }
16: void Incremento!)
17: {++suVal; }
18: prívate:
19: int suVal;
20: };
21:
22: Contador::Contador():
23: suVal(0)
24: {}
25:
26: int main()
27: {
28: Contador i;
29: cout « "El valor de i es
30: cout « i.ObtenerSuVal() « endl;
31: i.Incremento!);
32: cout « "El valor de i es
33: cout « i.ObtenerSuVal!) « endl;
34: return 0;
35: }
El valor de i es 0
S alida El valor de i es 1
A nálisis
El listado 10.7 agrega la función Incremento, definida en las líneas 16 y 17.
Aunque esto funciona, es incómodo de usar. El programa pide a gritos la capa
cidad de agregar el operador ++ y, por supuesto, se puede hacer.
1 // Listado 10.8
2 // La clase Contador
3
4 tfinclude <iostream.h>
5
F u n c io n e s a v a n z a d a s 309
6: c l a s s C on t a d o r
7: {
8: pub Lie :
9: C on ta d or();
10 -Contador(){}
11 i n t O b t e n e r S u V a l () const
12 { r e t u r n s uV al }
13 void A s i g n a r S u V a l ( int x
14 {s uV al - x; }
15 void Incremento()
16 { ++suVal; }
17 void operator++ (
18 { ++suVal; }
19 pr i v a t e :
20 i n t s uV al ;
21 };
22
23 Co n t a d or : : C o n t a d o r ( ) :
10
24 s u V a l (0)
25 {}
26
27 int main O
28 {
29 C on t a d or i;
30 coût << " E l v a l o r de i es "
31 coût << i .O b t e n e r S u V a l ( ) « endl;
32 i . Inc r emento ();
33 coût << " E l v a l o r de i es 1
34 coût << i .O b t e n e r S u V a l ( ) « endl ;
35 ++i ;
36 coût << " E l v a l o r de i es 1
37 coût << i .O b t e n e r S u V a l ( ) « endl ;
38 retur'n 0 ;
39 }
E l v a l o r de i es 0
S alida E l v a l o r de i es 1
E l v a l o r de i es 2
Este código trata de crear un nuevo Contador llam ado a. y luego asignarle el valor de i
después de incrementar i. El constructor de copia integrado se encargará de la asignación,
pero el operador de incremento actual no regresa un objeto Contador. Regresa void. Usted
no puede asignar un objeto void a un objeto Contador. ( ¡No puede crear algo de la nada!)
37: Contador i ;
38: cout << " E l v a l o r de i es “ ;
39: cout << i .O b t e n e r S u V a l () << endl;
40: i . I nc r e m e n t o () ;
41: cout << " E l v a l o r de i es ";
42: cout << i .O b te n e r S u V a l () << endl;
43: + + i;
44: cout << " E l v a l o r de i es
45: cout << i . O b t e n e r S u V a l ( ) << endl;
46: Contador a = + + i;
47: cout << " E l v a l o r de a: " « a.Obt ener S uV al ();
48: cout << " y de i : " << i .ObtenerSuVal() « endl;
49: r e t u r n 0;
50: }
El valor de i es 0
El valor de i es 1
El valor de i es 2
El valor de a: 3 y de i : 3
I: II L i s t a d o 10.10
2: // o per a to r ++ r eg re s a un objeto temporal s i n nombre
3:
4: ¿/include <i os t re am .h >
5:
6: c l a s s Contador
7: {
8: public:
9: C o n t a d o r ( );
10: Contador(int v a l ) ;
II : -Contador( ){}
12: i n t O b te n e r S u V a l ( ) const
13: { r e t ur n s uVa l; }
14: vo i d A s i g n a r S u V a l ( i n t x)
15: { suVal = x; }
16: v o i d I ncremento()
17: { ++suVal; }
ir <nm iini
Día 10
L is t a d o 1 0 . 1 0 co n t in u a ció n
El valor de i es 0
Salida El valor de i es 1
El valor de i es 2
El valor de a: 3 y de i: 3
Esto es más elegante, pero surge una pregunta: “¿Por qué crear un objeto temporal?"
Recuerde que cada objeto temporal debe ser construido y posteriormente destruido (cada
uno es una operación costosa en potencia). Además, el objeto i ya existe y ya tiene el va
lor correcto, así que. ¿por qué no regresarlo ? Podemos resolver este problema utilizando
el apuntador th is.
1: // L i s t a d o 10.11
2: // Cómo r e g r e s a r e l apuntador t h i s desreferenciado
3:
4: ^ i n c l u d e < i os tr eam. h>
5:
6: c l a s s Contador
7: {
8: public:
9: C o n t a d o r ( );
10: ~Contador(){}
11: i n t O b te n e r S u V a l ( )const
12: { r e t u r n s uVa l; }
13: v oi d A s i g n a r S u V a l ( i n t x)
14: { s u V a l = x; }
15: v o i d I ncremento()
16: { ++ suVal; }
17: co ns t Contador & operator++ ();
18: private:
19: i n t s uVa l;
20: };
21 :
22: C o n t a d o r : : C o n t a d o r ( ):
23: s u V a l (0)
24: {};
25:
26: co ns t Contador & C on t a d or : : oper ator ++()
27: {
28: + +s uV al ;
29: r e t ur n * t h i s ;
30: }
31 :
i ’iirmitti
314 Día 10
Listado 1 0 .1 1 continuación
El valor de i es 0
Salida El valor de i es 1
El valor de i es 2
El valor de a: 3 y de i: 3
La implementación de operator++, líneas 26 a 30, ha sido cambiada para desre-
ferenciar el apuntador this y regresar el objeto actual. Esto hace que se asigne
un objeto Contador al objeto a. Como se dijo anteriormente, si el objeto Contador asig
nara memoria, sería importante evadir al constructor de copia. En este caso, el constructor
de copia predeterminado funciona perfectamente.
Observe que el valor regresado es una referencia a Contador, lo que evita la creación de
un objeto temporal adicional. Es una referencia const porque el valor no debe ser cam
biado por la función que usa este Contador (es decir, no se debe asignar esta referencia a
otro objeto Contador).
Para recordar, prefijo significa “incrementar y luego utilizar”, pero posfijo dice “utilizar
y luego incrementar".
Por lo tanto, aunque el operador de prefijo puede simplemente incrementar el valor y
luego regresar el objeto mismo, el de posfijo debe regresar el valor que existía antes de
ser incrementado. Para hacer esto, debemos crear un objeto temporal que guarde el valor
original, incrementar el valor del objeto original, y luego regresar el objeto temporal.
Repasemos eso otra vez. Considere la siguiente línea de código:
a = x++;
Si x fuera 5. después de esta instrucción a es 5. pero x es 6. Por lo tanto, regresamos el
valor de x y lo asignamos a a. y luego incrementamos el valor de x. Si x es un objeto, su
operador de incremento de postfijo debe guardar el valor original (5) en un objeto tem
poral. incrementar el valor de x a 6. y luego regresar ese valor temporal para asignar su
valor a a.
Observe que debido a que estamos regresando el valor temporal, debemos regresarlo por
valor y no por referencia, ya que el valor temporal quedará fuera de alcance tan pronto
como termine la función.
El listado 10.1 2 muestra el uso de ambos operadores, prefijo y posfijo.
1: / / L i s t a d o 10.12
2: // Op er ad or es de p r e f i j o y p o s f i j o
3:
4: #include <iostream.h>
5:
6: c l a s s Contador
7: {
8: public:
9: C o n t a d o r () ;
1 0: - C o n t a d o r () {}
11: i n t O b t e n e r S u V a l ( ) const
12: { return suVal; }
13: v o i d A s i g n a r S u V a l ( i n t x)
14: { s u V a l = x; }
15: c o n s t Contador & o per a to r++ ( ) ; // p r e f i j o
16: c o n s t Contador o per a to r ++ ( i n t ) ; // p o s f i j o
17: private:
18: i n t s uV a l;
19: };
20:
21: C o n t a d o r : : C o n t a d o r ():
22: s u V a l (0)
23: {}
24:
co n tin u a
316 Día 10
L istado 1 0 .1 2 continuación
El valor de i es 0
S a l id a El valor de i es 1
El valor de i es 2
El valor de a: 3 y de i: 3
El valor de a: 3 y de i: 4
Una vez más, podría empezar escribiendo una función llamada Sumar ( ). que tomaría un
C o n t a d o r como su argumento, sumaría los valores y luego regresaría un C o n t a d o r con el
resultado. El listado 10.13 ejemplifica este método.
Entrada L is t a d o 1 0 . 1 3 La f u nc i ó n Sumar( )
1: // L i s t a d o 10.13
2: // Función Sumar
3:
4: # i n c l u d e < i os tr eam. h>
5:
6: c l a s s Contador
7: {
8: public:
9: C o n t a d o r ();
10: Contador(int v a l o r l n i c i a l ) ;
11 : -Contador(){}
12: i n t O b t en e rS u Va l ( ) const
13: { r et ur n suVal; }
14: v oi d A s i g n a r S u V a l ( i n t x)
15: { suVal = x; }
16: Contador Suniar(const Contador &);
i'< >n i n u n i
Listado 1 0 .1 3 continuación
17: prívate:
18: int suVal;
19: };
20:
21: Contador::Contador(int valorlnicial):
22: suVal(valorlnicial)
23: {}
24:
25: Contador::Contador():
26: suVal(0)
27: {}
28:
29: Contador Contador::Sumar(const Contador & rhs)
30: {
31: return Contador(suVal + rhs.ObtenerSuVal());
32: }
33:
34: int main()
35: {
36: Contador varllno(2), varDos(4), varTres;
37:
38: varTres = varUno.Sumar(varDos);
39: cout « "varUno: " « varUno.ObtenerSuVal( ) « endl;
40: cout « "varDos: " << varDos .ObtenerSuVal () << endl;
41: cout << "varTres: " « varTres.ObtenerSuVal() << endl;
42: return 0;
43: }
varUno: 2
Salida varDos: 4
varTres: 6
A nálisis
La función Sumar () se declara en la línea 16. Toma una referencia constante a
Contador, que es el número a sumar al objeto actual. Regresa un objeto Contador,
que es el resultado que se va a asignar en el lado izquierdo de la instrucción de asignación,
como se muestra en la línea 38. Es decir, varUno es el objeto, varDos es el parámetro
para la función Sumar(), y el resultado se asigna a varTres.
Para poder crear varTres sin tener que inicializarla con un valor, se requiere un cons
tructor predeterminado. El constructor predeterminado inicializa suVal con 0, como se
muestra en las líneas 25 a 27. Como varUno y varDos necesitan inicializarse con un
valor distinto de cero, se creó otro constructor, como se muestra en las líneas 21 a 23.
Otra solución para este problema es proporcionar el valor predeterminado 0 al construc
tor declarado en la línea 10.
F u n c io n e s a v a n za d a s 319
C ó m o s o b re c a rg a r a operator+
La función S um ar( ) se m uestra en las líneas 29 a 32. Funciona, pero su uso es m uy s o
fisticado. Sobrecargar el operador + ofrecería un uso más natural de la clase C o n ta d o r.
El listado I0.14 ejem plifica esto.
Entrada L is t a d o 1 0 . 1 4 operator+
1: // L i s t a d o 10.14
2: / / S o b r e ca r g ar e l operador de suma (+)
3:
4: //include < i o s t r e a m . h>
5:
6: c l a s s Contador
7: {
8: public: 10
9: C o n t a d o r ( );
10: C o n t a d o r ( i n t v a l o r l n i c i a l );
11 : ~Contador(){}
12: int ObtenerSuVal()const
13: { r e t u r n s uVa l; }
14: v oi d A s i g n a r S u V a l ( i n t x)
15: { s uV a l = x; }
16: Contador o per at or + ( const Contador &);
17: private:
18: i n t s uVa l;
19: };
20:
21: C o n t a d o r : :C o n t a d o r ( i n t v a l o r l n i c i a l ) :
22: suVal(valorlnicial)
23: {}
24:
25: Contador::Contador() :
26: s u V a l (0)
27: {}
28:
29: Contador C o n t a d o r : : operator+ (const Contador & rhs)
30: {
31: r e t u r n C o n t a d o r ( s uVal + rhs .Obten er Su Val( ));
32: }
33:
34: i n t main()
35: {
36: Contador v ar Un o( 2 ), var Dos (4) , varTres;
37:
38: v a r T r e s = varUno + varDos;
39: cout << "varUno: " << varUno.ObtenerSuVal()<< endl;
40: cout << " var Dos : " « v a r D o s .ObtenerSuVal() « endl;
41: cout << " v a r T r e s : " « v a r T r e s . ObtenerSuVal () « endl;
42: r e t u r n 0;
43: }
| 320 Día 10
varUno: 2
S alida varDos: 4
varTres: 6
En la línea I6 se declara a o p e r a to r + . y se d efine en las líneas 29 a 32. Compare
A n á l is is
estas líneas con la declaración y la d efinición de la función S u m a r ( ) del listado
anterior: son casi idénticas. Sin em bargo. la sintaxis de su uso es bastante diferente. Es
más natural decir esto:
varTres = varUno + varDos;
que decir:
varTres = varUno.Sumar(varDos);
No es un gran cam bio, pero es suficiente para que el pro g ram a sea más lácil de usar y de
entender.
D ebe NO DEBE
D E B E utilizar la sobrecarga de operadores N O D E B E crear operadores co n tra in tu i
cu an d o esto ayud e a que el program a sea tivos.
más claro.
D E B E regresar un objeto de la misma clase
para la cual sobrecarga los operadores .
Aquí se crea gatollno y se in ic ia li/a con suEdad igual a 5 y suPeso igual a 7. Después se
crea gatoDos y se le asignan los valores 3 y 4.
D espués, los valores contenidos en gatoUno se asig n an a gatoDos. Aquí surgen dos cues
tiones: ¿Q ué pasa si suEdad es un apuntador, y qué pasa con l o s v alores originales con
tenidos en gatoDos?
C uando exam inam os el co n stru cto r de copia, vio el m an ejo de v ariables miembro que
guardan sus valores en el heap. Las m ism as cu estio n es surgen aquí, (.orno vio en las fi
guras 10.1 y 1 0 .2 .
Los program adores de C ++ distinguen entre una co p ia su p cilicial. o de datos miembro,
por una parte, y una copia profunda por la otra. U na co p ia superficial sólo copia los
m iem bros, y am bos objetos term inan ap u n tan d o a la m ism a área de m em oria. Una copia
profunda asigna la m em oria necesaria. Esto se ilustra en la ligura 10.3.
Sin em bargo, hay un detalle adicional relacio n ad o con el o p erad o r de asignación. El ob
jeto gatoDos ya existe y tiene m em oria asignada. Esa m em o ria se debe elim inar si no se
desea una fuga de m em oria. Pero, ¿qué ocurre si se asig n a gatoDos a sí mism o?
gatoDos = gatoDos;
Es muy poco probable que alguien quiera h acer esto in ten cio n alm en te, pero el programa
debe ser capaz de m anejarlo. Lo que es m ás im p o rtan te, es po sib le que esto ocurra acci
dentalm ente si las referencias y los apuntadores d esreferen c iad o s ocultan el hecho de que
la asignación es al objeto mismo.
Si no m anejara cuidadosam ente este problem a, gatoDos elim in aría su asignación de me
m oria. Luego, cuando estuviera listo para co p iar en la m em o ria del lado derecho de la
asignación, tendría un problem a m uy grande. La m em o ria ya no estaría ahí.
Para protegerse contra esto, su operador de asig n ació n deb e v erificar si lo que está de
su lado derecho es el objeto m ism o. El o p erad o r hace esto ex am in an d o el apuntador
t h i s . El listado 10.15 m uestra una clase con un o p e ra d o r d e asig n ació n.
Entrada L ist a d o 1 0 . 1 5 U n o p e r a d o r d e a s ig n a c ió n
1: // Listado 10.15
2: // Constructores de copia
3:
4: ^include <iostream.h>
5:
6: class GATO
7: {
8: public:
9: GAT0(); // constructor predeterminado
10: // ¡constructor y destructor de copia suprimidos!
11: int ObtenerEdad () const
Funciones avanzadas 323
edad de pelusa: 5
Estableciendo edad de pelusa en 6...
edad de bigotes: 5
copiando pelusa a bigotes...
edad de bigotes: 6
324 Día 10
Operadores de conversión
¿Q ué pasa c u a n d o trata de a s ig n a r una v a riable de un tipo de d a lo s integrado, como int
o unsigned short, a un o bjeto de una clase d e fin id a p o r el u s u a rio ? El listado 10.16
vuelve a utilizar la clase Contador e intenta a s ig n a r u n a v a ria b le de tipo int a un objeto
Contador.
Entrada L is t a d o 1 0 . 1 6 In t e n t o d e a s i g n a r u n i n t a u n C o n t a d o r
1: // Listado 10.16
2: // ¡Este código no compilará!
3:
4: #include <iostream.h>
5:
6: class Contador
7: {
8: public:
9: Contador();
10: -Contador(){}
11: int 0btenerSuVal()const
12: { return suVal; }
13: void AsignarSuVal(int x)
14: { suVal = x; }
15: private:
16: int suVal;
17: };
18:
19: Contador::Contador() :
20: suVal(0)
21 : {}
22:
Funciones avanzadas 325
23 : int m a i n ()
24: {
25: int elShort = 5;
26: Contador elCtr = elShort;
27: cout << "elCtr:
28: cout << elCtr.ObtenerSuVal() << endl;
29: return 0;
30: }
Entrada L is t a d o 1 0 . 1 7 C o n v e r s ió n d e i n t a C o n ta d o r
1: / / Listado 10.17
2: // Un constructor como operador de conversión
3:
4: #include <iostream.h>
5:
6: class Contador
7: {
8: public:
9: Contador();
10: Contador(int val);
11 : -Contador(){}
12: int ObtenerSuVal()const
13: { return suVal; }
14: void AsignarSuVal(int x)
15: { suVal = x; }
16: private:
17: int suVal;
18: };
19:
20: Contador::Contador() :
iiu itm iu i
326 Día 10
Listado 1 0 . 1 7 continuación
21: suVal(0)
22: {}
23:
24: Contador::Contador( int val):
25: suVal(val)
26: {}
27:
28: int main()
29: {
30: int elShort = 5;
31: Contador elCtr = elShort;
32: cout « "elCtr: " ;
33: cout « elCtr.ObtenerSuVal() << endl;
34: return 0;
35: }
S a l id a elCtr: 5
Esto es c o m o decir: int x = 5;, lo cual crea una v a ria b le d e tip o e n te ro llam ada x y
luego la inicializa con el valor 5. En este caso, c r e a m o s un o b je to C o n t a d o r llamado
elCtr y lo inicializam os con la variable de tipo int lla m a d a elShort.
P a s o J : A s i g n a r f ueShort a elCtr. lo q u e e q u iv a le a
"elCtr = fueShort";
En este paso. f u e S h o r t (el o bjeto tem poral creado cuando se ejecutó el co n stru c to r) se
su b stitu y e p o r lo q u e e sta b a del lado derecho del operador de asignación. Es decir, a h o ra
que el c o m p ila d o r ha c re a d o una copia temporal por usted, inicializa elCtr con ese v a lo r
tem poral.
se c o n v ie rte en
a .operator=(b ) ;
En t r a d a L is t a d o 1 0 . 1 8 C o n v e r s ió n d e C o n t a d o r a u n s ig n e d sh o rt()
// Listado 10.18
// Uso del operador de conversión
#include <iostream.h>
icin tin m i
|328 Día 10
L is t a d o 10.18 c o n t in u a c ió n
6: class Contador
7: {
8: public:
9: Contador();
10: Contador(int val);
11: -Contador(){}
12: int ObtenerSuVal()const
13: { return suVal; }
14: void AsignarSuVal(int x)
15: { suVal = x; }
16: operator unsigned short();
17: private:
18: int suVal;
19: };
20:
21: Contador::Contador():
22: suVal(0)
23: {}
24:
25: Contador::Contador(int val):
26: suVal(val)
27: {>
28:
29: Contador::operator unsigned short ()
30: {
31: return (int (suVal));
32: >
33:
34: int main()
35: {
36: Contador ctr(5);
37: int elShort = ctr;
38:
39: cout « "elShort: " « elShort « endl;
40: return 0;
41: }
S a l id a elShort: 5
Resumen
Hoy aprendió c ó m o sobrecargar funciones miembro de sus clases. T am bién a p re n d ió
c ó m o prop o rc io n ar valores predeterm inados para las funciones y c ó m o d e c id ir c u á n d o
utilizar valores pred e term in a d o s y cuándo sobrecargar.
Sobrecargar los constructores de una clase le permite crear clases flexibles q u e se p u eden
crear a partir de otras clases. La inicialización de objetos ocurre en la e ta p a de inicializa-
ción del co n stru c to r y es m ás eficiente que asignar valores en el cu e rp o del constructor.
El c o m p ila d o r pro p o rc io n a el constructor de copia y el operador de asignación, si usted
no crea los suyos, pero éstos realizan una copia de los datos m ie m b ro de la clase. En
clases en las que los datos m ie m b ro incluyan apuntadores a objetos, variables o 1u n
ciones a lm acenadas en el heap, estos m étodos se deben pasar por alto y usted d e b e rá
asignar la m em o ria para el objeto destino.
Casi todos los ope ra d o re s de C ++ se pueden sobrecargar, aunque debe ten e r c u id a d o de
no crear ope ra d o re s cuyo uso sea contraintuitivo o ambiguo. No puede c a m b ia r la aridad
de los operadores, ni puede inventar nuevos operadores.
El ap u n ta d o r t h i s hace referencia al objeto actual y es un parám etro invisible para todas
las funciones m iem bro. A m e n u d o los operadores sobrecargados regresan el a p u n ta d o r
t h i s desreferenciado.
Los operadores de con v e rsió n le perm iten crear clases que se pueden utilizar en e x p re
siones que esperan un tipo distinto de objeto. Con ellos se rom pe la regla que establece
que todas las 1unciones regresan un valor explícito; al igual que los constructores y los
destructores, no tienen tipo de valor de retorno.
Preguntas y respuestas
P ¿P or qué utilizar valores predeterm inados si se puede sobrecargar u n a función?
R Es m ás sencillo m an te n er una función que mantener dos, y es m ás fácil e n te n d e r
una función con parám etros predeterm inados que estudiar los cuerpos de dos (un
ciones. A dem ás, actualizar una de las funciones sin actualizar la otra es una c ausa
com ún de errores.
P Dados los problem as con funciones sobrecargadas, ¿p o r qué no m ejo r u tiliz a r
siem pre valores predeterm inados?
R Las funciones sobrecargadas ofrecen capacidades que no están disponibles c o n las
variables predeterm inadas; una de ellas es la capacidad de cam biar la lista de p a rá
metros entre diversos tipos, en lugar de omitir algunos de ellos.
P Al escrib ir un co n stru cto r de clase, ¿cómo decide qué debe colocar en la in i
cialización y qué debe colocar en el cuerpo del constructor?
R Una regla em pírica sencilla es hacer todo lo que sea posible en la fase de inicializa
ción. es decir, inicializar ahí todas las variables miembro. A lgunas cosas, c o m o los
cálculos y las instrucciones de impresión, deben ir en el cuerpo riel constructor.
330 Día 10
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del ma
terial tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. Al sobrecargar funciones miembro de una clase, ¿de qué manera deben diferir?
2. ¿Cual es la diferencia entre una declaración y una definición?
3. ¿Cuándo se llama al constructor de copia?
4. ¿Cuándo se llama al destructor?
5. ¿Qué diferencia hay entre el constructor de copia y el operador de asignación (=)?
6. ¿Qué es el apuntador th is?
7. ¿Cómo puede diferenciar entre la sobrecarga de los operadores de incremento de
prefijo y los de posfijo?
8. ¿Puede sobrecargar el operator+ para el tipo de datos short int?
9. ¿Es válido en C++ sobrecargar el operator++ para que decremente un valor de su
clase?
10. ¿Qué valor de retomo deben tener los operadores de conversión en sus decla
raciones?
Funciones avanzadas 331
Ejercicios
1. Escriba la declaración de una clase llamada CirculoSencillo (únicam ente la
declaración) con una variable miembro: suRadio. Incluya un constructor p rede
terminado. un destructor y m étodos de acceso para la variable suRadio.
2. Usando la clase que creó en el ejercicio I. escriba la implementación del construc
tor predeterminado, e inicialice suRadio con el valor 5.
3. Usando la m ism a clase, agregue un segundo constructor que tome un valor como
parámetro y asigne ese valor a suRadio.
4. Cree un operador de incremento de prefijo y uno de posfijo para su clase
CirculoSencillo. que incrementen suRadio.
5. Cambie la clase CirculoSencillo para que guarde el dato miembro suRadio
en el heap. y corrija los métodos existentes. 10
6. Proporcione un constructor de copia para CirculoSencillo.
7. Proporcione un operador de asignación para CirculoSencillo.
8. Escriba un programa que cree dos objetos CirculoSencillo. Utilice el constructor
predeterminado en uno y cree una instancia con el otro que tenga el valor 9. Llame
al operador de increm ento para que actúe sobre cada uno y luego imprim a sus
valores. Por último, asigne el segundo al primero e imprima sus valores.
9. C A Z A E R R O R E S : ¿Qué está mal en esta implementación del operador de asig
nación?
CUADRADO CUADRADO::operator=(const CUADRADO & rhs)
{
suLado = new int;
*suLado = rhs.ObtenerLado();
return *this;
}
l(). C A Z A E R R O R E S : ¿Qué está mal en esta implementación del operadoi de
suma?
MuyCorto MuyCorto::operator+ (const MuyCorto & rhs)
{
suVal += rhs.ObtenerSuVal () ;
return *this;
}
■!■
..L ííi
S em an a 2
D ía 11
Herencia
Un aspecto fundamental de la inteligencia humana es buscar, reconocer y crear
relaciones entre conceptos. Creamos jerarquías, matrices, redes y otias interrela
ciones para explicar y entender la forma en que interactúan las cosas. El lenguaje
C++ intenta capturar esto en las jerarquías de herencia. Hoy aprenderá lo siguiente:
• Qué es la herencia
• Cóm o derivar una clase de otra
• Qué es el acceso protegido y cómo utilizarlo
• Qué son las funciones virtuales
Qué es la herencia
¿Qué es un perro? Cuando ve a su mascota, ¿qué es lo que ve? Yo veo cuati o
patas al servicio de un hocico. Un biólogo ve una red de Oiganos que inteiactúan
entre sí. un tísico ve átomos y fuerzas trabajando, y un taxónomo ve un represen
tante de la especie Canís domesticas.
334 Día 11
Esta última valoración es la que nos interesa en este m om ento. Un p erro es un tipo de
canino, un canino es un tipo de mamífero, y así sucesiv am en te. Los ta x ó n o m o s dividen
el mundo de cosas vivientes en reino, filo, clase, orden, fam ilia, g én ero y especie.
Esta jerarquía establece una relación es un. Un m iem bro de la esp ecie H o m o sa p ien s
es un tipo de primate. Esta relación se ve en todas partes: U na v ag o n eta es un tipo de
auto, el cual es un tipo de vehículo. Una nieve es un tipo de p o stre, el cual es un tipo
de comida.
Cuando decimos que algo es un tipo de otro algo, dam os a en ten d er que es una especiali-
zación de eso. Por ejemplo, un auto es un tipo especial de vehículo.
Cualquier objeto que sea del tipo es un tiene algunas características de la descripción de
orden mayor. Un auto es un vehículo. Un camión tam bién lo es. Un auto no es un ca
mión. Pero ambos tienen ciertas características similares que com parten con la descripción
común de vehículo. Algunas de estas similitudes heredadas son las llan tas, un chasis, un
tipo de motor, etc.
Herencia y derivación
El concepto perro hereda (es decir, obtiene autom áticam ente) todas las características de
un mamífero. Ya que es un mamífero, sabemos que se m ueve y que respira aire. P or natu
raleza, todos los mamíferos se mueven y respiran aire. El co n cep to de un perro añade la
idea de ladrar, mover su cola, comerse mis revisiones de esta lecció n ju s to c u an d o ya
había terminado, ladrar cuando estoy tratando de dormir, etc.
Podemos dividir a los perros en perros de trabajo, de deporte y T erriers (co m o el Terrier
escoces); y podemos dividir a los perros de deporte en R etrievers (co b rad o res), Spaniels
(como el Cocker Spaniel), y así sucesivamente. Por últim o, cad a uno de ésto s se puede
especializar aún más; por ejemplo, los Retrievers se pueden su b d iv id ir en L ab radores y
Goldens (dorados).
Un Golden es un tipo de Retriever, el cual es un tipo de perro de deporte, y po r lo tanto
también un tipo de mamífero, que es un tipo de animal y, por ende un tipo de cosa viviente.
Esta jerarquía se representa en la figura 11.1 usando el U M L (L enguaje de M odelado Uni
ficado). Las flechas apuntan desde los tipos más especializados a los tipos m ás generales.
El lenguaje C++ intenta representar estas relaciones perm itiendo que usted defina clases
que se deriven de otras clases. La derivación es una form a de expresar la relación es un.
Usted deriva una nueva clase llamada Perro de la clase Mamif ero. N o tiene que declarar
explícitamente que los perros se mueven porque han heredado eso de la clase Mamif ero.
Se dice que una clase que agrega nueva funcionalidad a una clase ex isten te se deriva de
esa clase original. Se dice que la clase original es la clase base de la nueva clase.
Si la clase Perro se deriva de la clase Mamífero, entonces Mamífero es una clase base
de Perro. Las clases derivadas son superconjuntos de sus clases base. A sí co m o un perro
agrega ciertas características a la idea proyectada por un m am ífero, la clase Perro agrega
ciertos métodos o datos a la clase Mamífero.
Herencia 335
F ig u r a 11.1
Jerarquía de animales.
Por lo general, una clase base tiene más de una clase derivada. Como los perros, gatos y
caballos son tipos de mamíferos, sus clases se derivan de la clase Mamífero.
La sintaxis de la derivación
Al declarar una clase, puede indicar de qué clase se deriva escribiendo el símbolo de dos
puntos (:) después del nombre de la clase, seguido del tipo de derivación (público u otro)
y de la clase de la que se deriva. A continuación se muestra un ejemplo:
class Perro : pubiic Mamífero
|336 Día 11
Hablaremos sobre el tipo de derivación más adelante en esta lecció n . P o r ah o ra, utilice
siempre el tipo público. Debió declarar con an terioridad la clase de la q u e d eriv a , o se
generará un error de compilación. El listado 11. 1 m uestra có m o d e c la ra r la clase Perro
que se deriva de la clase Mamífero.
La clase Perro hereda de Mamífero, como se indica en la línea 26. Cada objeto Perro
tendrá tres variables miembro: suEdad, suPeso y suRaza. Observe que la declaración de
clase para Perro no incluye las variables miembro suEdad y suPeso. Los objetos Perro
heredan estas variables de la clase Mamífero, junto con todos los métodos de Mamífero,
excepto el operador de copia, los constructores y el destructor.
Recuerde que las variables miembro también se conocen como datos miembro.
C u an do se crea una instancia de la clase, los datos miembro de esa instancia
se conocen com o propiedades. En el ejemplo anterior, si creamos un objeto
de la clase Perro, sus propiedades son los valores alm acenados en suPeso,
suEdad y suRaza. A lgunos autores utilizan indistintamente los térm inos datos
miembro, variables miembro y propiedades.
Lo que queremos es una designación que diga: “H acer eslos datos v isibles para esta clase
y para las clases que se deriven de esta clase” . Esta desig n ació n es p ro teg id a. Los datos y
funciones miembro protegidos son completamente visibles para las clases derivadas, pero
en cuanto a lo demás son privados.
En total, existen tres especificadores de acceso: público, protegido y privado. Si una función
tiene un objeto de su clase, puede tener acceso a todos los datos y funciones m iem bro públi
cos. Las funciones miembro, a su vez, pueden tener acceso a to d o s los d ato s y funciones
miembro privados de su propia clase, y todos los datos y fu n cio n es m iem b ro protegidos
de cualquier clase de la que se deriven.
Por lo tanto, la función Perro: :MoverCola() puede tener acceso al d ato pro tegido suRaza
y a los datos protegidos de la clase Mamif ero.
Incluso si hay otras clases interpuestas entre Mamif ero y Perro (por ejem plo. Animales -
Domésticos), la clase Perro aún podrá tener acceso a los m iem b ro s p ro teg id o s de
Mamif ero, asumiendo que estas otras clases utilicen h e re n c ia p ú b lic a . L a h eren c ia
privada se explica en el día 15, “Herencia avanzada” .
El listado 11.2 muestra cómo crear objetos de la clase P e rro y ten er acceso a los datos y
funciones de esa clase.
En t r a d a L istado 11.2 U so d e u n o b j e t o d e r i v a d o
¡Sonido de mamifero!
S a l id a
Moviendo la cola...
fido tiene 2 años de edad
En las líneas 7 a 30 se declara la clase Mamifero (todas sus funciones son en línea
A n á l is is
para ahorrar espacio aquí). En las líneas 32 a 50 se declara la clase Perro com o
clase derivada de Mamifero. Así. debido a estas declaraciones, todos los miembros de la clase
Perro tienen una edad, un peso y una raza.
En la línea 54 se declara un Perro llamado fido, fido hereda todos los atributos de un
Mamifero. así c o m o lodos los atributos de un Perro. Por lo tanto, fido sabe cóm o
MoverCola( ). pero tam bién sabe cómo Hablar() v Dormir().
340 Día 11
Constructores y destructores
Los objetos Perro son objetos Mamif ero. Ésta es la esencia de la relación <\v un. Al crear a
f ido, se llama primero a su constructor base, con lo que se crea un Mamif ero . Luego se llama
al constructor de Perro, lo que completa la construcción del objeto P e rro . C om o no le dimos
parámetros a Perro, en cada caso se llamó al constructor predeterm inado, f id o existe hasta
que está completamente construido, lo que significa que se deben construir sus partes corres
pondientes a Mamif ero y a Perro. Por lo tanto, es necesario llam ar a am bos constructores.
Al destruir a f ido, primero se llamará al destructor de P erro y luego al destructor de la parte
correspondiente a Mamif ero de f ido. Cada destructor tiene oportunidad de lim piar su propia
parte de f ido. ¡Recuerde siempre limpiar todo lo que haga su P erro ! El listado 11.3 muestra
esto.
33
34 public:
35 // Constructores
36 Perro();
37 -Perro();
38 // Métodos de acceso
39 RAZA ObtenerRaza() const
40 { return suRaza; }
41 void AsignarRaza(RAZA raza)
42 { suRaza = raza; }
43 // Otros métodos
44 void MoverCola() const
45 { cout « "Moviendo la cola...\n"; }
46 void PedirAlimento() const
47 { cout << "Pidiendo alimento...\n"; }
48 private:
49 RAZA suRaza;
50 };
51
52 Mamifero::Mamifero():
53 suEdad(1),
54 suPeso(5)
55 {
56 cout « "Constructor de Mamifero...\n";
57 }
58
59 Mamifero::-Mamifero()
60 {
61 cout « "Destructor de Mamifero...\n";
62 }
63
64 Perro::Perro():
65 suRaza(GOLDEN)
66 {
67 cout << "Constructor de Perro...\n";
68 }
69
70 Perro::-Perro()
71 {
72 cout << "Destructor de Perro...\n";
73 }
74
75 int main()
76 {
77 Perro fido;
78 fido.Hablar();
79 fido.MoverCola();
80 cout « "fido tiene ";
81 cout << fido.ObtenerEdad() « " años de edad\n";
82 return 0;
83 }
[342 Día 11
Constructor de Mamífero...
S a l id a Constructor de Perro...
¡Sonido de mamífero 1
Moviendo la cola...
fido tiene 1 años de edad
Destructor de Perro...
Destructor de Mamífero...
El listado 11.3 es igual que el listado 11.2, con la excepción de que los constructo
A n á l is is
res y destructores ahora imprimen en la pantalla cuando son llam ados. Prim ero se
llama al constructor de Mamífero, y luego al de Perro. En ese m om ento, el Perro existe en
su totalidad, y sus métodos pueden ser llamados. Cuando f id o q u ed a fuera de alcance, se
hace una llamada al destructor de Perro, seguida de una llam ada al destru cto r de Mamífero.
L istado 11.4 c o n t in u a c ió n
75:
76: Perro::Perro():
77: Mamifero(),
78: suRaza(GOLDEN)
79: {
80: cout « "Constructor de Perro...\n";
81: }
82:
83: Perro::Perro(int edad):
84: Mamífero(edad),
85: suñaza(GOLDEN)
86: {
87: cout « "Constructor de Perro(int)...\n";
88: }
89:
90: Perro:¡Perro(int edad, int peso):
91: Mamífero(edad),
92: SURaza(GOLDEN)
93: {
94: suPeso = peso;
95: cout « "Constructor de Perro(int, int)...\n";
96: >
97:
98: Perro::Perro(int edad, int peso, RAZA raza):
99: Mamífero(edad),
100: suRaza(raza)
101 : {
102: suPeso = peso;
103: cout « "Constructor de Perro (int, int, RAZA)...\n";
104:}
105:
106: Perro::Perro(int edad, RAZA raza):
107: Mamífero(edad),
108: suRaza(raza)
109: {
110: cout « "Constructor de Perro(int, R A Z A ) . . . \ n " ;
111: }
112:
113: Perro:¡-Perro()
114: {
115: cout « "Destructorde Perro...\n"-
116: } ’
117:
118: int main()
119: {
120: Perro fido;
121: Perro rover(5);
122: Perro buster(6, 8);
123: Perro yorkie (3, GOLDEN);
124: Perro dobbie (4, 20,DOBERMAN);
125: fido.Hablar();
126: rover.MoverCola();
Herencia 345
1 Constructor de Mamifero...
S a l id a
Constructor de Perro...
2
3 Constructor de Mamifero(int ) ...
4 Constructor de Perro ( int ) ...
5 Constructor de Mamifero ( int ) ...
6 Constructor de Perro(int, int)...
7 Constructor de Mamifero(int).. .
8 Constructor de Perro ( int, RAZA)...
9 Constructor de Mamifero(int)...
10 Constructor de Perro(int, int, RAZA)
11 ¡Sonido de mamifero !
12 Moviendo la cola...
13 yorkie tiene 3 años de edad
14 dobbie pesa 20 libras
15 Destructor de Perro...
16 Destructor de Mamifero...
17 Destructor de Perro...
18 Destructor de Mamifero...
19 Destructor de Perro...
20 Destructor de Mamifero...
21 Destructor de Perro...
22 Destructor de Mamifero...
23 Destructor de Perro...
24 Destructor de Mamifero...
El constructor base se llamaría de cualquier forma, pero hacerlo en el program a hace que
sus intenciones sean explícitas.
La implementación para el constructor de P erro , que tom a un entero, se encuentra en las
líneas 83 a 88. En su fase de inicialización (líneas 84 y 85), P e rro inicializa su clase base,
pasa el parámetro y luego inicializa su raza.
Hay otro constructor de Perro en las líneas 90 a 96. Éste toma dos parám etros. Una vez más
inicializa su clase base llamando al constructor apropiado, pero esta vez tam bién asigna un
peso a la variable suPeso de la clase base. Observe que en la fase de inicialización no puede
hacer una asignación a la variable de la clase base. Como Mamif ero no tiene un constructor
que tome este parámetro, usted debe hacer esto dentro del cuerpo del constructor de Perro.
Analice los constructores restantes para asegurarse de que le gusta la form a en que funcio
nan. Tome nota de las variables miembro que se inicializan y de las que deben esperar
hasta el cuerpo del constructor.
Se ha numerado la salida para que cada línea pueda tener una referencia en este análisis.
Las dos primeras líneas de salida representan la instanciación de f id o , usando el constructor
predeterminado.
Las líneas 3 y 4 representan la creación de rover. Las líneas 5 y 6 representan a buster.
Observe que el constructor de Mamifero que se llamó es el constructor que tom a un entero,
pero el constructor de Perro es el constructor que toma dos enteros.
Después de crear todos los objetos, se utilizan y luego quedan fuera de alcance. A medida
que se destruye cada objeto, primero se llama al destructor de Perro y luego al de Mamif ero,
siendo en total cinco de cada uno.
Redefinición de funciones
Un objeto Perrro tiene acceso a todos los métodos (funciones m iem b ro ) de la clase
Mamif ero, así como a cualquier método que la declaración de la clase Perro pueda agregar
(por ejemplo, MoverCola()). Ese objeto también puede redefinir una función de la clase base.
Esto significa que la implementación de la función de la clase base cam bia en una clase deri
vada. Cuando se crea un objeto de la clase derivada, se llama a la función apropiada.
Cuando una clase derivada crea una función con el mismo tipo de valor de retorno y de
firma que una función miembro de la clase base, pero con una nueva im plem entación, se
dice que está redefiniendo ese método.
Al redefinir una función, debe concordar con el tipo de retorno y de firm a de la función de
la clase base. La firma es el prototipo de la función sin el tipo de valor de retorno: es decir,
el nombre de la función, la lista de parámetros y la palabra reservada c o n s t, si se utiliza.
El listado 11.5 muestra lo que ocurre cuando la clase Perro redefine el m étodo Hablar ()
de Mamífero. Para ahorrar espacio, se han omitido las funciones de acceso de estas clases.
Herencia 347
L is t a d o 1 1 . 5 R e d e f in ic ió n d e u n m é to d o d e la clase b a se e n u n a clase
Entrada d e r iv a d a
Constructor de Mamífero...
Salida Constructor de Mamífero...
Constructor de Perro...
iSonido de mamífero!
¡Guau!
Destructor de Perro...
Destructor de mamífero...
Destructor de mamífero...
En la línea 39, la clase Perro redefine el método Hablar( ) , lo que ocasiona que
A nálisis
los objetos Perro “digan” ¡Guau! cuando se llama al m étodo Hablar(). En la
línea 47 se crea un objeto de la clase Mamífero llamado animalGrande, lo que produce
la primera línea de salida cuando se hace la llamada al constructor de Mamífero. En la
línea 48 se crea un objeto de la clase Perro llamado fido, lo que produce las siguientes
dos líneas de salida, en donde se llama al constructor de Mamífero y luego al de Perro.
En la línea 50, el objeto animalGrande llama a su método Hablar (); luego, en la línea 51,
el objeto fido llama a su método Hablar (). La salida refleja que se llamó a los métodos
correctos. Por último, los dos objetos quedan fuera de alcance y se llama a los destructores.
6: {
7: public:
8: void Mover() const
9: { cout << "Mamífero se mueve un paso\n"; }
10: void Mover(int distancia) const
11: {
Todos los métodos y datos adicionales se han omitido en estas clases. En las líneas
A n á l is is
8 y l(), la clase Mamifero declara los métodos Mover () sobrecargados. En la línea 25.
Perro redefine la versión del método Mover () que no lleva parámetros. Estos métodos se
invocan en las líneas 34 a 36, y la salida refleja esto a medida que se ejecuta el programa.
Entrada L istado 11.7 Cómo llamar al método base desde el m é to d o red efin ido
1: //Listado 11.7 Cómo llamar al método base desde el método redefinido.
2:
3: //include <iostream.h>
4:
5: class Mamifero
6: {
7: public:
8: void Mover() const
9: { cout « "Mamifero se mueve un paso\n"; }
10: void Mover(int distancia)const
11: {
12: cout « "Mamifero se mueve " « distancia;
13: cout « 11 pasos.\n";
14: }
15: protected:
Herencia 351
El programador quería invocar a Mover (int) en el objeto Perro, pero tuvo un problema.
Perro redefine el método Mover (), pero no lo sobrecarga y no proporciona una versión que
tome un int como parámetro. Esto se resuelve por medio de la llamada explícita al método
Mover (int) de la clase base, el cual se encuentra en la línea 38.
D ebe N O DEBE
Métodos virtuales
Esta lección recalca el hecho de que el objeto Perro es un objeto Mamif ero. Hasta ahora
eso sólo ha significado que el objeto Perro ha heredado los atributos (datos) y capacidades
(métodos) de su clase base. Sin embargo, en C++ la relación es un va más allá de eso.
C++ extiende su polimorfismo para permitir que apuntadores a clases base se asignen a
objetos de las clases derivadas. Por lo tanto, puede escribir
Mamífero * apMamifero = new Perro;
Esto crea un nuevo objeto Perro en el heap y regresa un apuntador a ese objeto, el cual se
asigna a un apuntador a Mamífero. Esto está bien porque un perro es un m am ífero.
Ésta es la esencia del polimorfismo. Por ejem plo, p o d ría crear m u c h o s tip o s de
Nota ventanas, incluyendo cuadros de d iálo go , v e n ta n a s d e s p la z a b le s y cu a d ro s
de lista, y darle a cada una de ellas un m étodo virtual lla m a d o d i b u j a r (). A l
crear un apuntador a una ventana, y asignarle a ese a p u n t a d o r c u a d ro s de
diálogo y otros tipos derivados, puede llam ar a d i b u j a r () sin p re o cu p a rse por
el tipo en tiempo de ejecución del objeto al que se ap unta. Se llam ará a la
función d i b u j a r ( ) apropiada.
Entonces puede usar este apuntador para invocar a cualquier método de Mamífero. Lo que
quiere es que esos métodos que están redefinidos en Perro () llamen a la función correcta.
Las funciones virtuales le permiten hacer esto. El listado 11.8 m uestra la form a en que
esto funciona, y lo que ocurre con métodos que no son virtuales.
18
19
20 class Perro : public Mamifero
21 {
22 public:
23 Perro()
24 { cout « "Constructor de Perro...\n"; }
25 virtual -Perro()
26 { cout << "Destructor de Perro...\n"; }
27 void MoverCola()
28 { cout << "Moviendo la cola...\n"; }
29 void Hablar()const
30 { cout << "iGuau!\n"; }
31 void Mover()const
32 { cout << "Perro se mueve 5 pasos...\n”; }
33
34
35 int main()
36 {
37
38 Mamifero * apPerro = new Perro;
39
40 apPerro->Mover();
41 apPerro->Hablar();
42 return 0;
43
Constructor de Mamifero...
S alida Constructor de Perro...
Mamifero se mueve un paso
¡Guau!
54 switch (opcion)
55 {
56 case 1:
57 aptr = new Perro;
58 break;
59 case 2 :
60 a pt r = new Gato;
61 break;
62 case 3:
63 aptr = new Caballo;
64 break;
65 case 4:
66 aptr = new Cerdo;
67 break;
68 default:
69 a pt r = new Mamífero;
70 break;
71 }
72 elArreglo[ i ] = aptr;
73 }
74 f or ( i = 0; i < 5; i++)
75 e l A r r e g l o [ i ] - > H a b l a r ( );
76 return 0;
77
11
( 1) perro (2) gato o : i c aballo (4)cerdo: 1
S alida ( 1) perro (2) gato o : l c aballo (4)cerdo: 2
( 1) perro (2) gato o : I c aballo (4)cerdo: 3
( 1) perro (2) gato o ; i c aballo (4)cerdo: 4
( 1) perro ( 2 ) gato o : i c aballo (4)cerdo: 5
¡Guau!
¡Miau!
¡Yihii!
iOink!
¡Mamífero habla!
Este programa simplificado que proporciona sólo la funcionalidad más básica para
A nálisis
cada clase, muestra un ejemplo de las funciones virtuales en su forma más pura. Se
declaran cuatro clases: Perro. Gato, C a b a l l o y Cerdo, todas derivadas de Mamífero.
En la línea 10 se declara como virtual la función H a b l a r () de Mamífero. En las líneas 19.
26, 33 y 40, las cuatro clases derivadas redefinen la implementación de H ab l a r ().
Se pide al usuario que elija los objetos a crear, y los apuntadores se agregan al arreglo
en las líneas 50 a 73.
Preguntas frecuentes
FAQ: Si marco un método miembro como virtual en la clase base, ¿necesito marcarlo tam
bién como virtual en clases derivadas?
Respuesta: No. Cuando un método es virtual, si lo redefine en clases derivadas, sigue sien
do virtual. Es una buena idea (aunque no es obligatorio) seguir marcándolo como virtual;
esto hace que el código sea más fácil de entender.
F ig u r a 1 1 .2
El objeto Perro des- Parte Mamífero
p u é s d e se r creado.
Objeto Perro
Cuando se crea una función virtual dentro de un objeto, el objeto debe estar al pendiente
de esa función. Muchos compiladores crean una ta b la d e f u n c i o n e s v i r t u a l e s , conocida
como tabla v. Se mantiene una de éstas para cada tipo, y cada objeto de ese tipo mantiene
un apuntador de tabla virtual (conocido como aptrv o apuntador v), el cual apunta a esa
tabla.
Aunque las implementaciones varían, todos los compiladores deben realizar lo mismo,
por lo que no estamos del todo mal con esta descripción.
Cada aptrv de cada objeto apunta a la tabla v que, a su vez, tiene un apuntador a cada una
de las funciones virtuales. (Nota: Hablaremos detalladamente de los apuntadores a funciones
en el día 14, “Clases y funciones especiales”.) Cuando se crea la parte Mamif ero del objeto
Perro, el aptrv se inicializa para apuntar a la parte correcta de la tabla v, como se muestra
en la figura 11.3.
Cuando se llama al constructor de Perro y se agrega la parte Perro de este objeto, el aptrv
se ajusta para apuntar a las redefiniciones (si existe alguna) de la función virtual del objeto
Perro (vea la figura 11.4).
H e re n c ia 357
Figura 11.3
La tahlci-v de un
Mamif ero. i M over
¿ H a b la r
Figura 11.4
L a t a b la - r d e u n
Perro. $ M a m ífe r o : M o v e r()
á> P e r r o : H a b l a r ( )
Partición de datos
La partición de datos es lo que ocurre cuando el compilador selecciona la función virtual
que es parte de una clase base en lugar de una función que se encuentre en una clase
derivada. Por lo general, necesitaría ejecutar una función con el mismo nombre en una
clase derivada. A m enudo, este proceso se conoce con el término nutria de la fu n c ió n
virtual .
358 Día 11
Hay que tener en cuenta que la magia de la función virtual opera sólo en apuntadores y
referencias. Si se pasa un objeto por valor, no se podrá invocar a las funciones virtuales.
El listado 11.10 ejemplifica este problema.
Se pide al usuario que elija un Perro o un Gato, y con base en la opción que elija, se crea
un apuntador al tipo correcto en las líneas 48 a 53.
| 360 Día 11
En la primera línea de la salida, el usuario elige Perro. El objeto Perro se crea en el heap en
la línea 49. Luego se pasa como apuntador, como referencia y por valor a las tres funciones.
El apuntador y la referencia invocan a las funciones virtuales, y se invoca a la función miem
bro Perro->Hablar(). Esto se muestra en las dos primeras líneas de salida después de la
elección del usuario.
Sin embargo, el apuntador desreferenciado se pasa por valor. La función espera recibir un
objeto Mamifero, por lo que el compilador parte el objeto Perro dejando sólo la parte
Mamifero. En ese momento, se hace una llamada al método Hablar () de Mamif ero, como
se refleja en la tercera línea de salida después de la elección del usuario.
Este experimento se repite para el objeto Gato, con resultados similares.
Destructores virtuales
Es válido y común pasar un apuntador a un objeto derivado cuando se espera un apunta
dor a un objeto base. ¿Qué pasa cuando ese apuntador a un objeto derivado se elimina?
Si el destructor es virtual, como debe ser, ocurre lo correcto (se llama al destructor de la
clase derivada). Debido a que el destructor de la clase derivada invocará automáticamen
te al destructor de la clase base, todo el objeto se destruirá de manera apropiada.
La regla empírica es esta: si alguna de las funciones de su clase es virtual, el destructor
también debe ser virtual.
L is ta d o 1 1 . 1 1 continuación
67: {
68 : cout « "Constructor de copia de Gato...\n";
69: >
70:
71 : int main()
72: {
73: Mamífero * elArreglo[ NumTiposAnimales ];
74: Mamífero * aptr;
75: int Opción, i;
76:
77: for (i = 0; i < NumTiposAnimales; i++)
78: {
79: cout « "(1)perro (2)gato (3)Mamifero:
80: cin » opcion;
81 : switch (opcion)
82: {
83: case PERRO:
84: aptr = new Perro;
85: break;
86 : case GATO:
87: aptr = new Gato;
88: break;
89: default:
90: aptr = new Mamífero;
91 : break;
92: >
93: elArreglo[ i 1 = aptr;
94: }
95: Mamífero * OtroArreglo[ NumTiposAnimales ];
96: for (i = 0 ; i < NumTiposAnimales; i++)
97: {
98: elArreglo[ i ]->Hablar();
99: OtroArreglo[ i ] = elArreglo[ i ]->Clonar();
100: }
101 : for (i = 0; i < NumTiposAnimales; i++)
102 : OtroArreglo[ i ]->Hablar();
103: return 0;
104 : }
14: C o n s t r u c t o r de copia de G at o. ..
15: ¡Mamifero habla!
16: C on s t r u c t o r de copia de Mamifero
17: ¡Guau!
18: iMiau!
19: ¡Mamifero habla!
El listado 11.11 es muy similar a los dos listados anteriores, con la excepción de
A n á lisis
que se lia agregado un nuevo método virtual a la clase Mamifero: C l o n a r ( ). Este
método regresa un apuntador a un nuevo objeto Mamifero mediante una llamada al construc
tor de copia, pasándose a sí mismo ( * t h i s ) como referencia const.
Tanto P e r r o como Gato redefinen el método C l o n a r (). inicializando sus datos y pasando
copias de sí mismos a sus propios constructores de copia. Como C l o n a r () es virtual, esto
creará en efecto un constructor virtual de copia, como se muestra en la línea 90.
Se pide al usuario que elija entre perros, gatos o mamíferos, los cuales se crean en las líneas
77 a 94. En la línea 95 se guarda en un arreglo un apuntador para cada opción.
A medida que el programa itera sobre el arreglo, se llama a los métodos H a b l a r ( ) y
C l o n a r () de cada objeto, uno por uno. en las líneas 98 y 99. El resultado de la llamada
a C l o n a r () es un apuntador a una copia del objeto, la cual se guarda a continuación en un
segundo arreglo en la línea 99.
En la línea 1 de la salida se pide al usuario un valor y responde con 1 , con el que elige crear
un perro. Se invoca a los constructores de Mamifero y de Perro. Esto se repite para Gato
y para Mamíf er o en las líneas 4 y 7 de la salida.
La línea 9 de la salida representa la llamada a Hablar () del primer objeto, el Perro. Se llama
al método virtual H a b l a r () y se invoca a la versión correcta de Hablar (). Luego se llama a
la función C l o n a r () y, como también es virtual, se invoca al método C l o n a r () de Perro, lo
que ocasiona que se llame a los constructores de copia de Mamifero y de Perro.
En las líneas 12 a 14 se repite lo mismo para Gato, y luego para Mamifero en las líneas 15
y 16. Por último, se itera el nuevo arreglo, y se invoca al método Hablar () de cada uno
de los nuevos objetos.
D ebe N O DEBE
D E B E utilizar métodos virtuales si espera N O D E B E marcar el constructor como
hacer derivaciones de una clase. virtual.
D E B E utilizar un destructor virtual si
cualquier método es virtual.
Resumen
Hoy aprendió la forma en que las clases derivadas heredan de las clases base. En esta lección
hablamos sobre la herencia pública y las funciones virtuales. Las clases heredan todos los
datos y funciones públicas y protegidas de sus clases base.
El acceso protegido es público para las clases derivadas y privado para todos los demás
objetos. Las clases derivadas no pueden tener acceso a los datos o funciones privadas de
sus clases base.
Los constructores se pueden inicializar antes del cuerpo del constructor. En ese momento
se invocan los constructores base y se pueden pasar parámetros a la clase base.
Las funciones de la clase base se pueden redefinir en la clase derivada. Si las funciones
de la clase base son virtuales, y si se accede al objeto por m edio de un apuntador o por
referencia, se invocarán las funciones de la clase derivada, con base en el tipo en tiempo
de ejecución del objeto al que se apunta.
Los métodos de la clase base se pueden invocar nombrando explícitam ente a la función
con el prefijo del nombre de la clase base y dos símbolos de dos puntos (::). Por ejemplo,
si P e r r o hereda de Mamíf ero, se puede llamar al método c a m i n a r ( ) de M a m í f e r o con
Mam ífero::caminar().
En clases que tengan métodos virtuales, el destructor casi siempre debe ser virtual. Un des
tructor virtual asegura que la parte derivada del objeto se liberará cuando se utilice d e le te en
el apuntador. Los constructores no pueden ser virtuales. Los constructores virtuales de copia
se pueden crear al hacer una función miembro virtual que llame til constructor de copia.
Preguntas y respuestas
P ¿Se p asan los m iem b ro s y fu n cio n es h e re d a d o s a la s g e n e r a c io n e s s u b se c u e n te s?
Si P erro se d eriv a d e M amífero, y M am ífero se d e r iv a d e A n im al, ¿ h e r e d a P erro
las funciones y los d a to s d e A n im al.
R Sí. Al continuar la derivación, las clases derivadas heredan la suma de todas las
funciones y datos que se encuentren en todas sus clases base.
H e re n c ia 365
P Si, en el ejemplo anterior, Mamif ero redefine una función de Animal, ¿cuál
obtiene Perro, la función original o la redefinida?
R Si Perro hereda de Mamif ero. recibe la función en el estado en que la tenga Mamífero:
la función redefinida.
P ¿Puede una clase derivada hacer que la función pública de una clase base sea
privada?
R Sí. la clase derivada puede redefinir el método y hacerlo privado. Y seguirá siendo
privado para cualquier derivación subsecuente.
P ¿Por qué no hacer que todas las funciones de una clase sean virtuales?
R Ocurre una sobrecarga con la primera función virtual al crear una tabla v. Después de
eso. la sobrecarga es trivial. Muchos programadores de C++ sienten que si una función
es virtual, todas las demás deben serlo. Otros programadores están en desacuerdo, pues
sienten que siempre debe haber un motivo para lo que se va a hacer.
P Si una función (UnaFunc( )) es virtual en una clase base y también está sobrecar
gada, de forma que tome ya sea uno o dos enteros, y la clase derivada redefine
la que toma un entero, ¿qué se llama cuando un apuntador a un objeto
derivado llama a la que toma dos enteros?
R La redefinición de la función que toma un int oculta toda la función de la clase base,
11
por lo que se obtendrá un error de compilación que dirá que esa 1unción necesita sólo
un int.
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del material
tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate de res
ponder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
C u e stio n a rio
1. ¿Qué es una tabla v?
2. ¿Qué es un destructor virtual?
3. ¿Cómo se puede mostrar la declaración de un constructor virtual?
4. ¿C ómo se puede crear un constructor virtual de copia?
3. ¿C omo se invoca a una función miembro de la clase base desde una clase derivada
en la que se haya redefinido esa función?
6. ¿C ómo se invoca a una función miembro de la clase base desde una clase derivada
en la que no se haya redefinido esa función?
366 Día 11
7. Si una clase base declara una función como virtual, y una clase derivada no utiliza
el término virtual cuando redefina esa clase, ¿seguirá siendo virtual cuando la
herede una clase de tercera generación?
8. ¿Para qué se utiliza la palabra reservada protected?
Ejercicios
1. Muestre la declaración de una función virtual que tome un parámetro entero y regrese
void.
2. Muestre la declaración de una clase llamada Cuadrado, la cual se deriva de
Rectángulo, que a su vez se deriva de Figura.
3. Si, en el ejercicio 2, Figura no toma parámetros, Rectángulo toma dos (lon gitu d
y ancho), pero Cuadrado toma sólo un parámetro (longitud), muestre la inicialización
del constructor para Cuadrado.
4. Escriba un constructor virtual de copia para la clase Cuadrado (del ejercicio 3).
5. CAZA ERRORES: ¿Qué está mal en este segmento de código?
void UnaFuncion (Figura);
Figura * apRect = new Rectángulo;
UnaFuncion(*apRect);
D ía 12
Arreglos, cadenas tipo C
y listas enlazadas
En lecciones anteriores se declaraba un int, char, u otro objeto individual. A
menudo es necesario declarar una colección de objetos, por ejemplo, 20 varia
bles de tipo int o un conjunto de objetos de la clase GATO. Hoy aprenderá lo
siguiente:
• Qué son los arreglos y cómo declararlos
• Qué son las cadenas y cómo utilizar arreglos de caracteres para crear cadenas
• La relación existente entre arreglos y apuntadores
• Cómo utilizar aritmética de apuntadores con arreglos
Qué es un arreglo
Un arreglo es una colección de ubicaciones para guardai datos, cada una ele las
cuales guarda el mismo tipo de datos. Cada ubicación de almacenamiento es un
elemento del arreglo.
Un arreglo se declara escribiendo el tipo, seguido del nombre del arreglo y del
subíndice. El subíndice es el número de elementos del arreglo, y va entre
corchetes ([]). Otra palabra para subíndice es clesphizximii’ino. ya que icpiesenta la dis
tancia desde el principio del arreglo. Por e je m p lo :
long ArregloLong [25];
Este ejemplo declara un arreglo de 25 enteros largos llamado ArregloLong. Cuando el
compilador ve esta declaración, reserva suficiente memoria para guardar los 25 elementos.
Debido a que cada entero largo ocupa 4 bytes, esta declaiación separa 100 bytes conti
guos de memoria, como se muestra en la figura 12.1.
Fig u r a 1 2 .1
Declaración de un
arreglo.
100 bytes
9:
10: f or ( i = 0 ; i < 5; i++ ) // 0-4
11: {
12: cout << " V a l o r para miArreglo[" « i « ”] :
13: c in >>mi Arreglo[ i ];
14: }
15: f or ( i = 0; i < 5; i++ )
16: cout << i << " : " << miArreglo[i] « "\ n" ;
17: return 0;
18: }
No ejecute este programa; ¡puede hacer que un sistema q u e no sea Linux deje
Precaución de funcionar!
Entrada L ista d o 12.2 Cómo escribir más allá del final de un arreglo
1: //Listado 12.2 Muestra qué pasa cuando se escribe
2: // más allá del fin de un arreglo
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: // centinelas
9: long centinelaUnof 3 ];
10: long ArregloDestino[ 25 ]; // arreglo que se va a llenar
11: long centinelaDos[ 3 ];
12: int i;
13:
14: for (i = 0; i < 3; i++ )
15: centinelaUno[ i l = centinelaDos[ i l - 0;
A r r e g l o s , c a d e n a s t i p o C y listas e n l a z a d a s 37 1
16 f or ( i = 0 ; i < 25; i ++ )
17 A r r e g i o D e s t i n o [ i ] = 0;
18 coût << "Prueba 1 : \n" ; // probar valores actuales (deben
19 coût << " A r r e g i o D e s t i n o ] 0]: " << ArregioDestino] 0 ] << ”
20 coût << " A r r e g i o D e s t i n o ] 24 : " << ArregioDestino 24 ] « \n\n" ;
21 f o r ( i = 0; i < 3; i++ )
22 {
23 coût << "centinelallno] " « i « "]:
24 coût << centinelaUno] i ] « "\n";
25 coût « " c e n t i n e l a D o s ] u « i « ;
26 coût << centinelaDos] i 1 « " \ n " ;
27 }
28 coût << " \ nA si gna nd o. . . " ;
29 f o r ( i = 0; i <= 25; i++ )
30 A r r e g i o D e s t i n o ] i ] = 20;
31 coût << " \nPrueba 2: \ n " ;
32 coût << " A r r e g i o D e s t i n o ] 0] " « ArregioDestino] 0 ] << "
33 coût « " A r r e g i o D e s t i n o ] 24 : " « ArregioDestino 24 ] « " \ n " ;
34 coût << " A r r e g i o D e s t i n o ] 25 : " « ArregioDestino 25 ] « " \ n \ n
35 f o r ( i = 0; i < 3; i++ )
36 {
37 coût << "centi nelaUno] " « i « "]:
38 coût << centinelaUno] i ] « " \ n " ;
39 coût << " c e n t i ne la D os ] " « i « "];
40 coût << centinelaDos] i ] « " \ n " ;
41 }
42 return 0;
43 }
Prueba 1 :
S alida ArregloDestino[0]: 0 12
ArregloDestino[24]: 0
A si g n a n d o ...
Prueba 2:
A r r e g l o D e s t i n o [ 0 ] : 20
A r r e g l o D e s t i n o [ 24]: 20
A r r e g l o D e s t i n o [ 2 5 ] : 20
centinelaUno[ 1 ]: 0
centinelaDos[1]: 0
centinelallno[2]: 0
centinelaDos[2]: 0
Las líneas 9 y 11 declaran dos arreglos de tres enteros que actúan com o centinelas
A nálisis
alrededor de ArregloDestino. Estos arreglos centinela se inicializan con 0. Si se
escribe en memoria que se encuentre más allá del final de ArregloDestino. es muy proba
ble que se cambien los centinelas. Algunos compiladores cuentan hacia ahajo en la memo
ria; otros cuentan hacia arriba. Debido a esto, los centinelas se colocan en ambos lados de
ArregloDestino.
Las líneas 21 a 27 confirman los valores de los centinelas en la prueba 1. En las líneas 29 y
30 se inicializan todos los miembros de ArregloDestino con el valor 20, pero el contador
cuenta hasta el desplazamiento 25 de ArregloDestino, el cual no existe.
Las líneas 32 a 34 imprimen los valores de ArregloDestino en la prueba 2. Observe que
ArregloDestino[25] imprime felizmente un 20. Sin embargo, cuando se imprimen
centinelaUno y centinelaDos, centinelaUno[0] revela que su valor ha cambiado. Esto se
debe a que la memoria que se encuentra a 25 elementos de distancia de ArregloDestino[01
es la misma memoria en la que está centinelal)no[0]. Cuando se accedió al
ArregloDestino[25] no existente, a lo que en realidad se accedió fue a CentinelaUno[0l-
Este terrible error puede ser muy difícil de encontrar, debido a que el valor de
centinelaUno[0] se cambió en una parte del código que no estaba escribiendo en centi
nelaUno.
Este código utiliza “números mágicos” como el 3 para el tamaño de los arreglos centinela
y 25 para el tamaño de ArregloDestino. Es más seguro utilizar constantes para que se
puedan cambiar todos estos valores en un solo lugar.
Tenga en cuenta que como algunos compiladores usan la memoria en forma distinta a otros,
sus resultados pueden variar.
barda. / /V / / ■ 'V 7 / /
Im 2m 3m 4m 5m 6m 7m 8m 9*^ lO rr»
Inicialización de arreglos
Puede inicializar un arreglo sencillo de tipos de datos integrados, como enteros y carac
teres, al declarar el arreglo. Después del nombre del arreglo, se coloca el signo de igual
(=) y una lista de valores separados por comas y encerrados entre llaves. Por ejemplo.
int ArregloEntero[5] = { 10, 20, 30, 40, 50 };
genera un error de compilación, debido a que declaró un arreglo con cin co elem entóse
inicializc3 seis valores. Sin embargo, es válido escribir
i n t A r r e g l o E n t e r o [5] = { 1 0 , 2 0 };
Aunque los miembros del arreglo que no se inicialicen no tienen valores garantizados,
los agregados se inicializarán con 0. Por lo tanto, si no inicial iza un elem en to de un
arreglo, su valor se establecerá en 0.
D ebe N O D EBE
DEBE permitir qu e el c o m p ila d o r e sta b le z N O D E B E e s c r ib ir m á s a llá d e l fin a l del
ca el tam año de los arre g lo s inicializados. a r r e g lo .
DEBE dar n om b re s sign ificativos a los a rre
glos, com o lo haría con cu alq u ie r variable.
DEBE recordar q u e el p rim e r m ie m b ro
del arreglo se e n cu e n tra e n el d e s p la z a
m iento 0.
D eclaració n d e a r re g lo s
Los arreglos pueden tener cualquier nombre de variable válido, pero no pueden tener el
mismo nombre que otra variable o arreglo dentro de su m ism o alcance. Por lo tanto, usted
no puede tener un arreglo llamado m isG atos[5] y una variable llamada niisGatos al mismo
tiempo.
Puede dimensional- el tamaño del arreglo con un c o n s t o con una enum eración. El lista
do 12.3 muestra esto.
1: // Listado 1 2 . 3
2; // Cómo dimensionar a r r e g l o s con c o n s ta n te s y enumeraciones
3;
4: #include <iostream.h>
5:
6; int main()
7: {
8: enum SemanaDias { Dom, Lun, Mar, Míe, J u e ,
9; Vie, Sab, DiasDeLaSemana };
10;
11: int ArregloSemana[ DiasDeLaSemana ] = { 1 0 , 20, 30, 40, 50, 60, 70 };
12:
13: cout « "El valor de Jueves e s : " << ArregloSemana [ J u e ) << endl;
14: return 0;
15: }
A r r e g l o s , c a d e n a s t i p o C y lis ta s e n l a z a d a s 375
Arreglos
Para declarar un arreglo, escriba el tipo del objeto guardado, seguido del nombre del arre
glo y un subíndice que indica el número de objetos que se van a guardar en el arreglo.
Ejemplo 1
int MiArregloEntero[90];
Ejemplo 2
lo ng * Ar r eg loDeApuntador es ALongs f100];
Para tener acceso a los miembros del arreglo, utilice el operador de subíndice.
Ejemplo 1
i n t elNovenoEntero = M i A r r eg l oD e En t e r os [ 8] ;
Ejemplo 2
l o n g * apLong = ArregloDeApuntadoresALongs[8];
Los arreglos empiezan a contar desde cero. Un arreglo de n elementos se numera desde 0
hasta n -1 .
A rre g lo s de o b je to s
Cualquier objeto, ya sea integrado o definido por el usuario, se puede guardar en un
arreglo. Cuando declara el arreglo, le indica al compilador el tipo de objeto a guardar y
el número de objetos a los que debe asignar espacio. El compilador sabe cuánto espacio
se necesita para cada objeto, con base en la declaración de la clase. La clase debe tener
un constructor predeterminado que no lleve argumentos, para que se puedan crear los
objetos cuando se defina el arreglo.
El acceso a los datos miembro de un arreglo de objetos es un proceso de dos pasos. Se
identifica el miembro del arreglo utilizando el operador de índice ([ ]), y luego se agre
ga el operador de punto (.) para tener acceso a la variable miembro específica. El listado
l 2.4 muestra cómo se crea un arreglo de cinco objetos de tipo GATO.
376 Día 12
Gato #1: 1
Salida Gato #2: 3
Gato #3: 5
Gato #4: 7
Gato #5: 9
A nálisis
Las líneas 5 a 20 declaran la clase GATO. Esta clase debe tener un constructor
predeterminado para que se puedan crear objetos de tipo GATO en un arreglo.
Recuerde que si crea cualquier otro constructor, el compilador no proporcionará el
constructor predeterminado.
El primer ciclo for (líneas 27 y 28) establece la edad de cada uno de los cinco objetos
de tipo GATO del arreglo. El segundo ciclo for (líneas 29 a 33) accede a cada miembro del
arreglo y llama al método ObtenerEdad ().
Arreglos, cadenas tip o C y listas enlazadas 377
Para llamar al método ObtenerEdad( ) de cada GATO individual, se accede al elemento del
arreglo, es decir Carnada! i ] , seguido del operador de punto (.) y de la función miembro.
Imagine que tiene una clase llamada CUADRO. La declaración de un arreglo llamado Tablero
que la represente sería
CUADRO Arreglo!8][8];
l
También podría representar los mismos datos con un arreglo de una dimensión de 64 i
Esto no representa tan fielmente al objeto real como el arreglo de dos dimensiones. Cuando
el juego empieza, el rey está ubicado en la cuarta posición de la primera fila. Si se cuenta
desde cero en el arreglo, esta posición corresponde a
A r re gl o! ©][3];
L
378 Día 12
los tres primeros elementos van en elArreglo[0]: los siguientes tres en elArreglo[ 1 ]: y
así sucesivamente.
Este arreglo se inicializa escribiendo lo siguiente:
int elArreglo[5] [3] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
Para que todo sea más claro, las inicializaciones se pueden agrupar con llaves. Por ejemplo:
int elArreglo[5][3] = { {1,2,3},
{4,5,6},
{7,8,9},
{ 1 0 , 1 1 , 12 } ,
{13,14,15} };
El compilador ignora las llaves de adentro, lo que facilita entender la manera en que se
distribuyen los números.
Cada valor debe estar separado por una coma, sin importar las llaves. Todo el conjunto de
inicialización debe estar encerrado entre llaves, y debe terminar con punto y coma.
El listado 12.5 crea un arreglo de dos dimensiones. La primera dim ensión es el conjunto
de números del 0 al 4. La segunda dimensión consiste en el doble de cada valor que se
encuentra en la primera dimensión.
U n A r r e g l o [ 0 ] [0] : 0
S alida UnArreglo[0 ][ 1 ]: 0
UnArreglo[1 ][0 ]: 1
UnArreglo[1][1]: 2
UnArreglo[2][0] : 2
U n A r r e g l o [2] [ 1 ] : 4
UnArreglo[3][0] : 3
U n A r r e g l o ! 3 1[ 1 ] : 6
U n A r r e g l o ! 4 ] [ 0 ]: 4
U n A r r e g l o ! 4 ] ! 1]: 8
F ig u r a 12.4
Un arreglo de 5 x 2.
Los valores se inicializan en pares, aunque también se pueden calcular. Las líneas 10 y l l
crean un ciclo f o r anidado. El ciclo f o r externo avanza porcada elemento de la primera
dimensión. Para cada elemento de esa dimensión, el ciclo f o r interno avanza por cada
elemento de la segunda dimensión. Esto concuerda con la salida. U n A r r e g l o [ 0 ] [ 1 ] sigue
a U n A r r e g l o ! 0 ] [ 0 ] . La primera dimensión se incrementa sólo después que se incrementa
en l la segunda dimensión. Luego la segunda dimensión vuelve a empezar.
Arreglos de apuntadores
Es posible declarar cada objeto en el heap y luego guardar sólo un apuntador al objeto del
arreglo. Esto reduce considerablemente la cantidad de memoria utilizada de la pila. El lista
do 12.6 vuelve a utilizar el arreglo del listado 12.4, pero guarda todos los objetos en el heap.
Como indicación de la mayor cantidad de memoria que esto permite, el arreglo se expande
de 5 a 500, y el nombre se cambia de Camada a Familia.
Gato #1 : 1
S alida Gato #2: 3
Gato #3: 5
En el ciclo inicial (líneas 28 a 33), se crean 500 nuevos objetos GATO en el heap, y se
establece la edad de cada uno al doble del índice mas uno. Por lo tanto, al primer GATO
se le asigna un 1. al segundo GATO un 3. al tercer GATO un 5. y así sucesivamente. Por
último. se agrega el apuntador al arreglo.
Debido a que el arreglo ha sido declarado para guardar apuntadores, el apuntador (en lugar
del valor desreferenciado contenido en él) se agrega al arreglo.
El segundo ciclo (líneas 34 a 38) imprime cada uno de los valores. El apuntador se
accede por medio del índice. F a m i l i a ! i ] . Esa dirección se utiliza entonces para tener
acceso al método ObtenerEdad ().
En este ejem plo, el arreglo F a m i l i a y todos sus apuntadores se guardan en la pila, pero los
500 objetos de tipo GATO que se crean se guardan en el heap.
La ventaja de usar Familia de esta manera es que puede utilizar aritmética de apuntadores
para tener acceso a cada miembro de F am ilia. Por ejem plo, puede escribir
GATO * Familia = new GATO[ 500 ] ;
GATO * apGato = Familia; //apGato apunta a Familia[0]
apGato->AsignarEdad(10 ); // asignar un 10 a Familia[0]
apGato++; // avanzar a Familia!1]
apGato-> AsignarEdad(20 ); // asignar un 20 a Familia! 1]
Esto declara un nuevo arreglo de 500 objetos de tipo GATO y un apuntador que apunta al
inicio del arreglo. A.1 utilizar ese apuntador, se llama a la función AsignarEdad( ), con
el valor 10, del primer GATO . Luego se incrementa el apuntador para que apunte al siguiente
GATO, y se llama al método AsignarEdad () del segundo GATO.
Las diferencias entre estas tres líneas de código afectan considerablemente la forma en
que funcionan estos arreglos . Lo que es aún más sorprendente es que Fam iliaTres es
una variante de FamiliaUno, pero es muy diferente de Fam iliaD os.
Esto trae a la mente la espinosa cuestión de la manera en que se relacionan los apuntadores
con los arreglos. En el tercer caso, FamiliaTres es un apuntador a un arreglo. Es decir,
la dirección contenida en FamiliaTres es la dirección del primer elem ento del arreglo.
Este es el mismo caso para FamiliaUno, con la diferencia de que FamiliaUno se crea en
la pila y FamiliaTres se crea en el heap.
1: // L i s t a d o 12.7 - Un a r r e g l o en el heap
2:
3: //inelude <iostream.h>
4:
5: c l a s s GATO
6: {
7: publ ic:
8: GATO()
9: { suEdad = 1; suPeso = 5; }
10: -GAT0();
11: i n t ObtenerEdad() const
12: { return suEdad; }
13: i n t ObtenerPeso() const
14: { return suPeso; }
15: void A s i g n a r E d a d ( i n t edad )
16: { suEdad = edad; }
17: p ri v at e :
18: int suEdad;
19: int suPeso;
20: };
21 :
22: GATO::-GATO()
23: {
24: // cout << " ¡Se llamó al d e s t r u c t o r ! \ n " ;
25: }
26:
27: i n t main()
28: {
29: GATO * Fa mi lia = new GAT0[ 500 ];
30: i n t i;
31 :
32: f o r ( i = 0 ; i < 500; i++ )
33: {
34: F a m i l i a [ 1 ] .AsignarEdad(2 * i + 1 );
35: }
36: f o r ( i = 0 ; i < 500 ; i++ )
c o m m in i
L istado 1 2 . 7 continuación
37: {
38: cout « "Gato #" « i + 1 « “:
39: cout « Familia! i ].ObtenerEdad() « endl;
40: }
41: delete U Familia;
42: return 0;
43: >
Gato #1: 1
S alida Gato #2: 3
Gato #3: 5
A nálisis
La línea 29 declara el arreglo Familia, el cual guarda 500 objetos tipo GATO. Todo
el arreglo se crea en el heap con la llamada a new GATO [ 500 ].
Para ver eso, cambie el tamaño del arreglo de 500 a 10 en las líneas 29, 32 y 36. Luego
modifique la instrucción cout de la línea 24 para que se pueda ejecutar y deje de ser comen
tario. Cuando se llegue a la línea 41 y se destruya el arreglo, se llamará al destructor de
cada objeto GATO.
Cuando cree un elemento en el heap por medio de new, siempre debe eliminar ese elemento
y liberar su memoria con delete. De la misma manera, cuando cree un arreglo por medio
de new <clase>[tamaño], debe eliminar ese arreglo y liberar toda su memoria con
delete[ 1- Los corchetes le indican al compilador que se va a eliminar un arreglo.
Si omite los corchetes, sólo se eliminará el primer objeto del arreglo. Puede probar esto
usted mismo si quita los corchetes de la línea 41. Si modificara la línea 24 para que el
destructor imprimiera, entonces vería sólo un objeto GATO destruido. ¡Felicidades! Acaba
de crear una fuga de memoria.
Arreglos, cadenas t ip o C y listas enlazadas 385
D ebe NO DEBE
DEBE recordar q u e un a rre glo de n elem en NO DEBE escribir o leer m ás allá del final
tos se n u m e ra d e sd e cero hasta n - 1. de un arreglo.
En C++, una cadena es un arreglo de valores de tipo char que termina con un carácter
nulo. Puede declarar e inicializar una cadena de la misma forma en que lo haría con
cualquier otro arreglo. Por ejemplo:
char Saludo[I =
{ ' H' , ' o ' , ' 1‘, 'a', V , ' 'ni', ' u' , ‘n 1, 1d 1, ' o ' , ' \0 ‘ };
El último carácter, ‘ \ 0 1, es el carácter nulo que muchas funciones de C++ reconocen como
terminador para una cadena. Aunque este método de carácter por carácter funciona, es difí
cil de escribir y al utilizarlo se pueden cometer errores. C++ le permite utilizar una forma
abreviada para escribir la anterior línea de código. Ésta es
char Saludo!] = "¡Hola, mundo!";
La cadena ¡Hola, mundo! es de 14 bytes. (¡) ocupa 1 byte, Hola ocupa 4 bytes, la coma
1 byte, el espacio 1, mundo 5, (!) ocupa 1 y el carácter nulo 1.
También puede crear arreglos de caracteres que no estén inicializados. Al igual que con
todos los arreglos, es importante asegurarse de no sobrepasar el espacio disponible en el
búfer.
A n á l isis
En la linea 4 se incluye el archivo de encabezado s t r in g -----l m c u iu iiv u cumicuc
los prototipos de las funciones para cadenas. Las líneas 8, 9 y 10 asignan tres
arreglos de caracteres; el primero se inicializa con “¡Así, siempre así he de verte!”, y
los otros dos están vacíos.
En la línea 13, la función strcpy () toma dos arreglos de caracteres (el de destino seguido
del de origen). Si el origen fuera más largo que el destino, strcpy () sobreescribiría más
allá del final del búfer.
Para protegerse contra esto, la biblioteca estándar también incluye a strncpy () como se
muestra en la línea 15. Esta variación toma un número máximo de caracteres a copiar,
strncpy () copia hasta el primer carácter nulo del número máximo de caracteres especi
ficados en el búfer de destino. Si este máximo es menor que la longitud de la cadena,
no se incluye un terminador nulo en la cadena de destino. Así que usted tiene que poner
uno, como se muestra en la línea 16.
strlen( ) toma un arreglo de caracteres como argumento y regresa la longitud del arreglo
hasta, pero sin incluir, el terminador nulo. El valor que regresa tendrá como límite el tamaño
del arreglo que contiene la cadena, menos uno.
En la línea 25, la función strcat () toma dos arreglos de caracteres (el de destino seguido
del de origen). El arreglo de origen será concatenado al final de la cadena que se guarda
en el arreglo de destino. Si el tamaño de las cadenas que se van a concatenar excede el
tamaño de la cadena de destino, strcat () sobreescribiría más allá del final del búfer
(para prevenir esto, se puede usar strncat ()).
Desde luego que hay disponibles muchas otras funciones para cadenas.
Arreglos, cadenas tipo C y listas enlazadas 389
Esto no funciona debido a que la variable Cadena2 fue asignada como un arreglo, y no se
permite cam biar la constante de la dirección que marca el principio del arreglo.
La línea 35 es igual que la línea 25. La salida de la línea 36 será distinta de la de la línea
26, ya que Cadenal es diferente y Cadena3 retiene su contenido en la línea 25.
La versión final de Cadena3 no es muy legible debido a que no existen separadores
entre las cadenas concatenadas en ella. Una buena técnica es utilizar strcat () en otro
carácter en la cadena de destino (como un espacio en blanco) de la siguiente forma:
strcpy(result_cadena, cadena_original);
strcat(result_cadena, " ");
strcat(result_cadena, cadena_siguiente);
Aparecerá un espacio entre el contenido de cadena_original y cadena_siguiente en
la variable result_cadena. La función strcat () requiere una cadena, lo que explica por
qué en la primera llamada el espacio se encierra entre comillas dobles (para convertirlo
en una cadena de longitud uno).
Puede utilizar apuntadores a cadenas con cualquiera de las funciones para cadenas.
Clases de cadenas
La mayoría de los com piladores de C++ viene con una biblioteca de clases que incluye
un gran conjunto de clases para manipulación de datos. Un componente estándar de una
biblioteca de clases es la clase S trin g .
C++ heredó de C la cadena con terminación nula y la biblioteca de funciones que incluye
strepy (), pero estas funciones no están integradas en una estructura orientada a objetos.
Una clase String proporciona un conjunto encapsulado de datos, funciones para m anipu
lar esos datos, así como funciones de acceso para que los mismos datos estén ocultos para
los clientes de la clase String.
Si su compilador no le proporciona una clase String (y tal vez aunque lo haga), quizás
usted quiera escribir su propia clase. GNU proporciona una clase String. El resto de esta
lección hablará sobre el diseño y la implementación parcial de clases String.
Como mínimo, una clase String debe sobrepasar las limitaciones básicas de los arreglos
de caracteres. Como todos los arreglos, los arreglos de caracteres son estáticos. Usted define
su tamaño. Siempre ocupan el mismo espacio en memoria, incluso si no se necesita todo.
Es desastroso escribir más allá del final del arreglo.
Una buena clase S trin g asigna sólo la memoria que necesite, y siempre es la suficiente para
guardar lo que reciba. Si no puede asignar suficiente memoria, debe fallar con elegancia.
El listado 12.12 proporciona una primera aproximación de una clase String.
392 Día 12
51:
52: // Convierte un arreglo de caracteres en una Cadena
53: String::String(const char * const cString )
54: {
55: itsLen = strlen(cString );
56: itsString = new char[ itsLen + 1 ];
57:
58: for (unsigned short i = 0; i < itsLen; i++ )
59: itsString[ i ] = cStringf i ];
60: itsString[ itsLen ] = '\0';
61: }
62:
63: // constructor de copia
64: String::String(const String & rhs )
65: {
66: itsLen = rhs.GetLen();
67: itsString = new char[ itsLen + 1 1 ;
68:
69: for (unsigned short i = 0; i < itsLen; i++ )
70: itsString[ i ] = rhs[ i ];
71: itsString[ itsLen ] = ‘\0*;
72: }
73:
74: // destructor, libera la memoria asignada
75: String:¡-String ()
76: {
77: delete [] itsString;
78: itsLen = 0;
79: }
80:
81: // operador igual a, libera la memoria existente
82: // luego copia la cadena y el tamaño
83: String & String::operator= (const String & rhs )
84: {
85: if (this == &rhs )
86: return *this;
87: delete [] itsString;
88: itsLen = rhs.GetLen();
89: itsString = new char[ itsLen + 1 ];
90: for (unsigned short i = 0; i < itsLen; i++ )
91: itsString[ i ] = rhs[ i ];
92: itsString[ itsLen ] = *\0■;
93: return *this;
94: }
95:
96: //operador de desplazamiento no constante, ¡regresa
97: // referencia a carácter para que se pueda
98: // cambiar!
99: char & String::operator[] (unsigned short offset )
100 : {
L is t a d o 12 .1 2 c o n t in u a c ió n
103 else
104 return itsString[ offset ];
105 >
106
107 // operador de desplazamiento constante para utilizar
108 // en objetos const (¡vea el constructor de copia!)
109 char String::operator[] (unsigned short offset ) const
110 {
111 if (offset > itsLen )
112 return itsString[ itsLen - 1 ];
113 else
114 return itsString[ offset ];
115 }
116
117 // crea una cadena nueva al agregar la cadena
118 // actual a rhs
119 String String::operator+ (const String & rhs )
120 {
121 unsigned short totalLen = itsLen + rhs.GetLen();
122 String temp(totalLen );
123 unsigned short i;
124
125 for (i = 0; i < itsLen; i++ )
126 temp[ i ] = itsString[ i ];
127 for (unsigned short j = 0; j < rhs.GetLen(); j++, i++ )
128 tempi i ] = rhs[ j ];
129 tempi totalLen ] = '\0 ';
130 return temp;
131
132
133 // cambia cadena actual, no regresa nada
134 void String::operator+= (const String & rhs )
135
136 unsigned short rhsLen = rhs.GetLen();
137 unsigned short totalLen = itsLen + rhsLen;
138 String temp(totalLen );
139 unsigned short i;
140
141 for (i = 0 ; i < itsLen; i++ )
142 tempi i ] = itsString[ i ];
143 for (unsigned short j = 0 ; j < rhs .GetLen (); j++, i++ )
144 tempi i ] = rhs[ i - itsLen ];
145 tempi totalLen ] = •\0 ■;
146 *this = temp;
147
148
149 int main()
150 {
151 String s1("Prueba inicial" );
152 cout « "Sl:\t" « si .GetString() « endl;
153
Arreglos, cadenas tipo C y listas enlazadas 395
Un solo
Figura 12.5 enlaco
Listas enlazadas.
Árboles
O O
Delegación de responsabilidad
Una premisa fundamental de la programación orientada a objetos es que cada objeto hace
algo muy bien y delega a otros objetos cualquier cosa que no sea su misión central.
Un automóvil es un ejemplo perfecto de esta idea sobre el hardware: el trabajo del motól
es producir la energía. La distribución de esa energía no es el trabajo del motor; eso es
responsabilidad de la transmisión. Girar no es el trabajo del motor ni de la transmisión;
eso se delega a la dirección.
|400 Día 12
Una máquina bien diseñada tiene muchas piezas pequeñas y bien definidas, que hacen su
propio trabajo y funcionan entre sí para lograr un mayor beneficio. Un programa bien dise
ñado es muy parecido: cada clase se dedica a su propio tejido, pero en conjunto crean una
estupenda colcha de punto.
!
n
8:
9:
10:
// Clase de datos que se va a colocar en la lista enlazada
// Cualquier clase de esta lista enlazada debe soportar dos métodos:
// Mostrar (despliega el valor) y Comparar (regresa la posición relativa)
* 11: class Datos
12: {
13: public:
14: Datos(int val ) : miValor(val ){}
15: -Datos(){}
16: int Comparar(const Datos & );
17: void Mostrar()
18: ícout « miValor « endl; }
19: private:
20: int miValor;
21: };
22:
J
A rre g lo s, cadenas t ip o C y listas enlazadas 401
L istado 12.13 c o n t in u a c ió n
69
70
71 // Todo lo que hace el constructor es inicializar
72 Nodolnterno::NodoInterno(Datos * losDatos, Nodo * siguiente ):
73 misDatos(losDatos ),
74 miSiguiente(siguiente )
75 {>
76
77 //la parte principal de la lista
78 // Cuando se coloca un nuevo objeto en la lista
79 // éste se pasa al nodo que averigua
80 //en dónde debe ir y lo inserta en la lista
81 Nodo * Nodolnterno::Insertar(Datos * losDatos )
82 {
83 // ¿es el nuevo objeto más grande o más pequeño que yo?
84 int resultado = misDatos->Comparar(*losDatos );
85 switch(resultado )
86 {
87 // por convención si es igual que yo debe ir primero
88 case kEsIgual:
89 // avanzar al siguiente case sin hacer nada
90 case kEsMasGrande:
91 // los datos nuevos van antes de mí
92 {
93 Nodolnterno * nodoDatos = new Nodolnterno
94
(losDatos, this );
95 return nodoDatos;
96 }
97 // es mayor que yo así que lo paso al siguiente
98 // nodo para que se encargue de él.
99 case kEsMasChico:
100 miSiguiente = miSiguiente->Insertar(losDatos );
101 return this;
102 >
103 return this; // apaciguar al compilador
104
105
106
107 // El nodo cola sólo es un centinela
108 class NodoCola : public Nodo
109 {
110 public:
111 NodoCola(){}
112 -NodoCola(){}
Arreglos, cadenas tipo C y listas enlazadas 403
continúa
Listado 12.13 c o n t in u a c ió n
158: public:
159: ListaEnlazada();
160: ~ListaEnlazada()
161 : { delete miCabeza; >
162: void Insertar(Datos * losDatos );
163: void MostrarTodo()
164: { miCabeza->Mostrar(); >
165: private:
166: NodoCabeza * miCabeza;
167: >;
168:
169: I I Al nacer, se crea el nodo cabeza
170 I I Éste crea el nodo cola
171 // Así que una lista vacía apunta a la cabeza que
172 // apunta a la cola y no tiene nada en medio
173 ListaEnlazada::ListaEnlazada()
174 {
175 miCabeza = new NodoCabeza;
176 }
177
178 I I Delegar, delegar, delegar
179 void ListaEnlazada::lnsertar(Datos * apDatos )
180
181 miCabeza->lnsertar(apDatos );
182 >
183
184 // programa controlador de prueba
185 int main()
186 {
187 Datos * apDatos;
188 int val;
189 ListaEnlazada le;
190
191 // pedir al usuario que produzca algunos valores
192 I I colocarlos en la lista
193 for (;;)
194 {
195 cout « "¿Cuál valor? (0 para detener): ";
196 cin » val;
197 if (!val )
198 break;
199 apDatos = new Datos(val );
200 le.Insertar(apDatos );
201 >
Arreglos, cadenas tipo C y listas enlazadas 405
Lo primero que hay que observar es la constante enumerada que proporciona tres
A nálisis
valores constantes: kEsMasChico, kEsMasGrande y kEsIgual. Cada objeto que se
pueda guardar en esta lista enlazada debe soportar un método Comparar (). Estas constantes
serán el valor del resultado regresado por el método Comparar!).
Para fines ilustrativos, la clase Datos se crea en las líneas 11 a 21, y el método Comparar!)
se implementa en las líneas 25 a 33. Un objeto Datos guarda un valor y puede compararse
a sí mismo con otros objetos Datos. También soporta un método Mostrar!) para desplegar
su valor (el del objeto Datos).
La manera más sencilla de entender el funcionamiento de la lista enlazada es ejemplificar
paso a paso el uso de una. En la línea 185 se define un programa controlador; en la línea
187 se declara un apuntador a un objeto Datos, y en la línea 189 se declara una lista
enlazada local.
Cuando se crea la lista enlazada, se llama al constructor en la línea 173. El único trabajo
que se hace en el constructor es asignar un objeto NodoCabeza y asignar la dirección de
ese objeto al apuntador que se guarda en la lista enlazada de la línea 166.
Esta asignación de un NodoCabeza invoca al constructor NodoCabeza que se muestra en las
líneas 142 a 145. Éste a su vez asigna un NodoCola y asigna su dirección al apuntador
miSiguiente del nodo cabeza. La creación del NodoCola llama al constructor NodoCola
que se muestra en la línea 1 1 1 , el cual está en línea y no hace nada.
Por lo tanto, con el simple hecho de asignar una lista enlazada en la pila, se crea la lista,
se crean un nodo cabeza y un nodo cola, y se establece su relación, como se muestra en
la figura 12 .6 .
406 Día 12
La línea 193 empieza un ciclo infinito. Se pedirán valores al usuario para agregarlos a la
lista enlazada. El usuario puede agregar tantos valores com o desee, y debe escribir un 0
cuando haya terminado. El código de la línea 197 evalúa el valor escrito; si es igual a 0, se
sale del ciclo.
Si el valor no es 0, se crea un nuevo objeto Datos en la línea 199, y se inserta en la lista
en la línea 200. Para fines ilustrativos, suponga que el usuario escribe el valor 15. Esto
invoca al método Insertar en la línea 179.
La lista enlazada delega inmediatamente la responsabilidad de insertar el objeto a su nodo
cabeza. Esto invoca al método I n s e r ta r en la línea 149. El nodo cabeza pasa de inmedia
to la responsabilidad a cualquier nodo al que apunte m iS ig u ie n te . En este caso (cuando
se agrega el primer elemento a la lista), está apuntando al nodo cola (recuerde, cuando el
nodo cabeza nació, creó un enlace a un nodo cola). Esto, p o r co n secu en cia, invoca al
método I n s e r ta r en la línea 119.
NodoCola:: Insertar () sabe que el objeto que ha recibido debe ser insertado inmedia
tamente antes que él, es decir, el nuevo objeto estará en la lista justo antes del nodo cola.
Por lo tanto, crea un nuevo objeto Nodolnterno en la línea 121, pasando los datos y un
apuntador a él mismo. Esto invoca al constructor para el objeto Nodolnterno, que se
muestra en la línea 72.
El constructor de Nodolnterno no hace nada más que inicializar su apuntador a Datos con
la dirección del objeto Datos que recibió y a su apuntador miSiguiente con la dirección
del nodo que recibió. En este caso, el nodo al que apuntará es el nodo cola (recuerde, el
nodo cola pasó su propio apuntador this).
Ahora que se ha creado Nodolnterno, se asigna la dirección de ese nodo interno al
apuntador nodoDatos en la línea 121, y esa dirección se regresa a su vez del método
NodoCola: :lnsertar(). Esto nos regresa a NodoCabeza: :lnsertar(), en donde se
asigna la dirección de Nodolnterno al apuntador miSiguiente de NodoCabeza (en la
línea 151). Por último, se regresa la dirección de NodoCabeza a la lista enlazada donde,
en la línea 181, se descarta (no se hace nada con ella porque la lista enlazada ya conoce
la dirección del nodo cabeza).
¿Por qué tom arse la molestia de regresar la dirección si no se u tiliza? Insertar se
declara en la clase base Nodo. El valor de retorno se necesita para las otras implementa-
ciones. Si se cambia el valor de retorno de NodoCabeza: : Insertar ( ) , se generará un
Arreglos, cadenas tipo C y listas enlazadas 407
error de compilación; es más sencillo regresar el NodoCabeza y dejar que la lista enlaza
da descarte su dirección.
¿Qué fue lo que pasó? Los datos se insertaron en la lista. La lista los pasó a la cabeza. La
cabeza, ciegamente, pasó los datos al nodo al que estaba apuntando en ese momento. En
este caso (cuando se insertó el primer elemento en la lista), la cabeza estaba apuntando a
la cola. La cola creó inmediatamente un nuevo nodo interno, e inicializó el nuevo nodo para
que apuntara a ella. Luego la cola regresó la dirección del nuevo nodo a la cabeza, la cual
reasignó su apuntador miSiguiente para que apuntara al nuevo nodo. ¡Listo! Los datos
están en la lista en el lugar adecuado, como se muestra en la figura 12.7.
Lista enlazada
Figura 1 2 . 7
La lista enlazada Nodo cabeza Nodo cola
después de haber
insertado el primer
nodo. miCabeza
Después de insertar el primer nodo, el control del programa continúa en la línea 195. Una
vez más se evalúa el valor. Para fines ilustrativos, suponga que se escribe el valor 3. Esto
ocasiona que se cree un nuevo objeto Datos en la línea 199 y que se inserte a la lista en la
línea 200 .
Una vez más, en la línea 181, la lista pasa los datos a su NodoCabeza. A su vez, el método
NodoCabeza:: Insertar () pasa el nuevo valor al nodo al que miSiguiente esté apuntan
do en ese momento. Como sabe, ahora está apuntando al nodo que contiene el objeto Datos
cuyo valores 15. Esto invoca al método Nodolnterno:: Insertar () en la línea 81.
En la línea 84, Nodolnterno utiliza su apuntador misDatos para indicar a su objeto Datos
(el que tiene el valor 15) que llame a su método Comparar (), y le pasa el nuevo objeto
Datos (cuyo valor es 3). Esto invoca al método Comparar() definido en la línea 25.
Los dos valores se comparan y, como miValor será 15 y losOtrosDatos.miValor sera 3,
el valor regresado será kEsMasGrande. Esto ocasionará que el flujo del programa salte a la
línea 93.
Se crea un nuevo Nodolnterno para el nuevo objeto Datos. El nuevo nodo apuntará al
objeto Nodolnterno actual, y la dirección del nuevo Nodolnterno se regresa del método
Nodolnterno: :Insertar () al NodoCabeza. Por lo tanto, el nuevo nodo, cuyo objeto
tiene un valor menor que el del objeto del nodo actual, se inserta en la lista, y la lista
ahora se ve como la figura 12 .8.
En la tercera invocación del ciclo, el cliente agrega el valor 8. Éste es m ayor que 3 pero
menor que 15, y por lo tanto se debe insertar entre los dos nodos existentes. El progreso
será como en el ejemplo anterior, excepto que cuando el nodo cuyo objeto tiene el valor
3 haga la comparación, en lugar de regresar kEsMasGrande, regresará kEsMasChico (lo
cual indica que el objeto cuyo valor es 3 es más chico que el objeto nuevo, cuyo valor
es 8).
¿Qué ha aprendido?
Dorothy dijo: “Si alguna vez vuelvo a tratar de seguir los sentim ientos de mi corazón, no
iré más allá de mi propio jardín". Aunque es cierto que no hay ningún lugar com o el hogar,
también es cierto que esto no es nada parecido a la programación procedural. En este tipo
de program ación, una función controladora examinaría los datos e invocaría a las funciones
correspondientes.
En la program ación orientada a objetos, a cada objeto individual se le otorga un conjunto
ajustado y bien definido de responsabilidades. La lista enlazada es responsable de m antener
el nodo cabeza. El nodo cabeza pasa inmediatamente los datos nuevos al nodo al que esté
apuntando en ese m om ento, sin importar qué nodo sea.
El nodo cola crea un nodo nuevo y lo inserta siempre que reciba datos. Sólo sabe una cosa:
si llegó a mí, se inserta antes de mí.
Los nodos internos son un poco más complicados; piden a su objeto existente que se
compare con el nuevo objeto. Dependiendo del resultado, lo insertan o sólo lo pasan al
siguiente nodo.
O bserve que Nodolnterno no tiene idea de cómo hacer la com paración; eso se deja al
objeto en sí. Todo lo que Nodolnterno sabe es pedir a los objetos que se com paren entre
sí y que esperen una de tres posibles respuestas. Dada una respuesta, hace la inserción;
de no ser así, sólo pasa el objeto nuevo al siguiente nodo, sin saber ni importarle en dónde
acabará.
Así que, ¿cuál objeto está a cargo? En un programa orientado a objetos bien diseñado, no
hay ni un objeto a cargo. Cada objeto hace su propio trabajo, y el efecto obtenido es una
m áquina que funciona perfectamente.
Resumen
Hoy aprendió cómo crear arreglos en C++. Un arreglo es una colección de tamaño fijo de
objetos del mismo tipo.
Los arreglos no hacen comprobación de sus límites. Por lo tanto es válido, aunque desas
troso, leer o escribir más allá del final del arreglo. Los arreglos em piezan a contar desde
0. Un error común es escribir al desplazamiento n de un arreglo con n elementos.
Los arreglos pueden ser unidimensionales o multidimensionales. En cualquier caso, los ele
mentos del arreglo se pueden inicializar, siempre y cuando el arreglo contenga ya sea tipos
integrados, como in t, u objetos de una clase que tenga un constructor predeterminado.
Los arreglos y su contenido pueden estar en el hep o en la pila. Si elim ina un arreglo en el
heap, recuerde utilizar los corchetes ([]) en la llamada a d e le te .
Los nombres de arreglos son apuntadores constantes al prim er elem ento del arreglo. Los
apuntadores y arreglos utilizan aritmética de apuntadores para encontrar el siguiente ele
mento de un arreglo.
Puede crear listas enlazadas para manejar colecciones con tam años que no conozca en
tiempo de compilación. A partir de las listas enlazadas, puede crear cualquier número de
estructuras de datos más complejas.
Las cadenas son arreglos de caracteres, o chars. C++ proporciona características especiales
para manejar cadenas de valores tipo char, incluyendo la capacidad de inicializarlas con
cadenas encerradas entre comillas.
Preguntas y respuestas
P ¿Q ué pasa si escribo en el elem ento 25 de u n a rre g lo de 24 m iem bros?
R Escribirá en otra área de memoria, con efectos potencialmente desastrosos en su
programa.
A rreglos, cadenas t ip o C y listas enlazadas 411
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su com prensión del
material tratado, así com o ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cu estio n ario y los ejercicios antes de ver las respuestas en el apéndice
D, “Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Cuáles son el prim ero y últim o elem entos en UnArreglo[25]?
2. ¿Cómo se declara un arreglo multidimensional?
3. Inicialice los m iem bros del arreglo de la pregunta 2.
4. ¿Cuántos elem entos hay en el arreglo UnArreglo[ 10] [5] [20]?
5. ¿Cuál es el núm ero m áxim o de elementos que se pueden agregar a una lista enlazada?
6. ¿Puede utilizar notación de subíndice en una lista enlazada?
7. ¿Cuál es el últim o carácter de la cadena “Brad es una buena persona”?
|412 Día 12
Ejercicios
1. Declare un arreglo de dos dimensiones que represente un tablero del juego tic-tac-toe.
2. Escriba el código que inicialice con 0 todos los elem entos del arreglo que creó en el
ejercicio 1 .
3. Escriba la declaración de una clase Nodo que guarde enteros.
4. CA ZA E R R O R E S : ¿Qué está mal en este fragm ento de código?
unsigned short UnArreglo[5][41;
-for (int i = 0 ; i<4; i++)
, for (int j = 0 ; j<5; j++)
' UnArreglo[i][j1 = i+j;
D ía 13
Polimorfismo
Ayer ap ren d ió có m o e sc rib ir funciones virtuales en clases derivadas. Éste es el
bloque de co n stru c ció n fundam ental del polimorfismo: la capacidad de enlazar
objetos e sp e cífico s de clase s derivadas con apuntadores a clases base en tiem po
de ejecu ció n . H oy ap ren d erá lo siguiente:
(1)Caballo (2)Pegaso: 1
S alida (1)Caballo (2)Pegaso: 2
(1)Caballo (2)Pegaso: 1
(1)Caballo (2)Pegaso: 2
(1)Caballo (2)Pegaso: 1
Estos ejem plos se han sim p lific a d o h asta lo m á s e se n cial p a ra ejem plificar los
puntos bajo consideración. Los constructores, d e stru cto re s virtu ales y dem ás se
han elim inado para m antener sencillo el c ó d igo .
Filtración ascendente
Colocar la función requerida en los niveles superiores de la je ra rq u ía de clases es una
solución común para este problema y el resultado es que m uchas funciones “se filtran”
dentro de la clase base. La clase base se encuentra entonces en grave peligro de convertirse
en un espacio de nombres global para todas las funciones que podrían ser utilizadas por
alguna de las clases derivadas. Esto puede m inar seriam ente la tipificación de clases de
C++, y puede crear una clase base grande y difícil.
En general, usted necesita filtrar la funcionalidad com partida que se define en las capas
superiores de la jerarquía, sin migrar la interfaz de cada clase. Esto significa que si dos
clases comparten una clase base común (por ejemplo, Caballo y Ave com parten Animal)
y tienen una función en común (por ejemplo, tanto las aves com o los caballos comen), usted
necesitaría llevar esa funcionalidad hacia la clase base y crear una función virtual.
No obstante, lo que necesita evitar es filtrar una interfaz definida en capas superiores
(digamos, el método Volar ()), para que pueda llam ar a ese m étodo sólo en algunas
clases derivadas. Si usted tiene que realizar este tipo de filtros, entonces su interfaz no
pertenece a esa capa en la jerarquía de clases.
Conversión descendente
Una alternativa para el método anterior, que aún se encuentra dentro de la herencia simple,
es mantener el método Volar () dentro de Pegaso y sólo llamarlo si el apuntador está real
mente apuntando a un objeto Pegaso. Para hacer esto, necesitará poder preguntar a su
apuntador a qué tipo apunta en realidad. Esto se conoce com o Identificación de Tipo en
Tiempo de Ejecución ( RTTI). RTTI se convirtió hasta hace poco en un componente oficial
de C++.
Si su compilador no soporta RTTI, puede imitarlo colocando un método que regrese un tipo
enum erado en cada una de las clases. Entonces puede probar ese tipo en tiem po de
ejecución y llamar a Volar () si regresa Pegaso.
Las primeras versiones de los compiladores GNU (2.7.2 y anteriores) no soportan RTTI
en todas las plataformas. La versión que se incluye en el CD-ROM (2.9.5) sí lo soporta
(de manera predeterminada).
Polimorfismo 417
Evite el uso de RTTI en sus programas. Esto puede ser el indicador de u n mal
diseño. En vez de eso, considere el uso de las funciones virtuales, plantillas o
herencia múltiple.
Para llam ar a V o l a r ( ), debe convertir el apuntador, y debe indicarle que el objeto al que
está apuntando es un objeto Pegaso, no un C ab allo . Esto se conoce com o conversión
descendente, ya que se está convirtiendo el objeto C a b a llo en un tipo más derivado.
Ahora, C++ soporta oficialm ente, aunque tal vez en forma renuente, la conversión
descendente por m edio del nuevo operador dynamic_cast. Este operador funciona así:
Si tiene un apuntador a una clase base, como Caballo, y lo asigna a un apuntador a una
clase derivada, com o Pegaso, puede utilizar el apuntador a C a b a llo de varias form as.
Si luego necesita tener acceso al objeto Pegaso, puede crear un apuntador a Pe gaso y
utilizar el operador dy n a m ic_ca st para hacer la conversión.
22:
23: const int NumeroCaballos = 5;
24: int main()
25: {
26: Caballo* Rancho[NumeroCaballos];
27: Caballo* apCaballo;
28: int opcion,i;
29: for (i=0; i<NumeroCaballos; i++)
30: {
31: cout « ”(1)Caballo (2)Pegaso: ";
32: cin » Opción;
33: if (opcion == 2)
34: apCaballo = new Pegaso;
35: else
36: apCaballo = new Caballo;
37: Rancho[i] = apCaballo;
38: }
39: cout « n\n";
40: /or (i=0; KNumeroCaballos; i++)
41: {
42: Pegaso *apPeg = dynamic_cast< Pegaso *> (Rancho[i]);
43: if (apPeg)
44: apPeg->Volar();
45: else
46: cout « "Sólo es un caballo\n";
47:
48: delete Rancho[i];
49: }
50: return 0;
51: >
(1)Caballo (2)Pegaso: 1
S a l id a (1)Caballo (2)Pegaso: 2
(1¡Caballo (2)Pegaso: 1
(1¡Caballo (2¡Pegaso: 2
(1¡Caballo (2¡Pegaso: 1
Sólo es un caballo
¡Puedo volar! ¡Puedo volar! ¡Puedo volar!
Sólo es un caballo
¡Puedo volar! ¡Puedo volar! ¡Puedo volar!
Sólo es un caballo
Polimorfismo 419
Preguntas frecuentes
FAQ: Al compilar, o b t e n g o u n error de mi com pilador g + + de G N U (versión 2.7.2 o anterior):
lst1 3 -0 2 .c x x : In fu n c tio n 'i n t m a in ( ) ':
ls t 1 3 -0 2 .c x x :4 2 : cannot take typeid of object when - f r t t i i s not s p e c if ie d
l s t 13 - 0 2 .c x x :42: i n v a l i d type argument
ls t 1 3 -0 2 .c x x :4 2 : f a i l e d to b u ild type descripto r node of 'P e g a so ',
maybe ty p e in f o .h not included
La necesidad de convertir el objeto Pegaso es una advertencia de que algo puede estar mal
en su diseño. Este program a, en efecto, mina el polimorfismo de funciones virtuales, ya
que depende de la conversión del objeto al tipo indicado en tiempo de ejecución.
D ebe N O DEBE
DEBE m o v e r la fu n c io n a lid a d hacia las N O DEBE realizar la c o n versió n descendente
capas superiores de la jerarquía de clases. d e a p u n t a d o r e s entre o b j e to s b ase y
DEBE evitar el c a m b io del tipo d e u n o b j e o bjeto s derivados.
t o en t ie m p o d e ejecución (utilice m é t o d o s N O DEBE m o v e r la in te rfa z hacia las capas
virtuales, plantillas y herencia múltiple). sup e rio re s d e la j e ra rq u ía d e clases.
Herencia múltiple
Es posible derivar una nueva clase de más de una clase base. Esto se conoce com o herencia
m últiple. Para derivar de más clases aparte de la clase base, se separa cada clase base con
comas en la designación de la clase. El listado 13.3 m uestra cóm o declarar una clase Pegaso
para que se derive tanto de C a b a llo com o de Ave. El p ro g ra m a luego a g re g a objetos
Pegaso en am bos tipos de listas.
26: p riva te :
27: in t suPeso;
28: };
29:
30: c l a s s P e g a s o : p u b l i c C a b a l l o , p u b l i c Ave
31 : {
32: p u b lic :
33: v o id G o rje a r() co n st { R e lin c h a r (); }
34: P e g a s o ( ) { c o u t < < " C o n s t r u c t o r de P e g a s o . . . }
35: ~ P e g a s o ( ) { c o u t << " D e s t r u c t o r de P e g a s o . . . }
36: };
37:
38: c o n st i n t N u m e ro M a g ico = 2;
39: in t m a in ()
40: {
41 : C a b a l l o * R a n c h o [ N u m e r o M a g i c o ]; I
42: A v e * P a j a r e r a [ N u m e r o M a g i c o ];
|l
43: C a b a llo * a p C a b a llo ; ¿1
•
44: Ave * apAve; if
45: in t o p c io n ,i; (
46: f o r ( i= 0 ; i< N u m e ro M a gico ; i+ + )
47: {
48: c o u t << " \ n ( 1 ) C a b a l l o ( 2 ) P e g a s o : 1
49: c i n >> o p c i o n ; j
50: i f ( o p c i o n == 2 )
51 : a p C a b a l l o = new P e g a s o ;
52: e lse
53: a p C a b a l l o = new C a b a l l o ;
54: R a n ch o [ i ] = a p C a b a llo ;
55: } 1
56: f o r ( i= 0 ; i< N u m e ro M a g ic o ; i+ + )
57: {
58: cout « " \ n ( 1 ) A v e ( 2 ) P e g a s o : ";
59: c i n >> o p c i o n ; [>
60: i f ( o p c i o n = = 2) [I
61 : a p A v e = new P e g a s o ; 1
62: e lse !•
63: a p A v e = new A v e; fl
64: Paj a r e r a [ i ] = a p A ve ;
65:
66: 1 3
67: c o u t << " \ n " ;
68: f o r ( i = 0 ; i< N u m e ro M a g ic o ; i+ + )
69: {
70: c o u t << " \ n R a n c h o [ " « i « "]: " ;
71 : R a n c h o [i]-> R e lin c h a r() ; .i
72: dele te R a n c h o [ i] ;
73: }
74:
75: for (i= 0 ; i< N u m e ro M a gico ; i++)
76: {
77: c o u t << " \ n P a j a r e r a [ " << i << "]: " ;
78: Pa j a r e r a [ i ] - > G o r j e a r ( ) ;
79: Pa j a r e r a [ i ] - > V o l a r ( );
c o n tin u a
A
L is t a d o 1 3 . 3 c o n t in u a c ió n
80: delete P a j a r e r a [ i ] ;
81: }
82: return 0;
83: }
Rancho[0]: ¡ Y i h i i ! . . . De structor de C a b a l l o . . .
Rancho[1]: ¡ Y i h i i ! . . . Destructor de Pe gaso ... D e s t r u c t o r de A ve ...
Destructor de C aballo ...
Pajarera[0]: G r i i i . . . ¡Puedo v o la r! ¡Puedo v o l a r ! ¡Puedo v o la r !
Destructor de A ve ...
Pa ja re ra[1 ]: ¡ Y i h i i ! . . . ¡Puedo v o la r! ¡Puedo v o la r ! ¡Puedo v o la r !
Destructor de Pegaso... Destructor de A v e ... D e s t r u c t o r de C a b a llo . . .
Las llamadas a los métodos virtuales tanto en los apuntadores a Ave com o en los apunta
dores a C a b a llo hacen lo correcto para los objetos Pegaso. Por ejem plo, en la línea 78 los
métodos del arreglo P a ja re ra se utilizan para llamar a G o r j e a r () en los objetos a los que
apuntan. La clase Ave declara este método com o virtual, por lo que se llam a a la función
apropiada para cada objeto.
Polimorfismo 423
Observe que cada ve/, que se crea un objeto Pegaso, la salida refleja que también se crean
tanto la parte Ave como la parte Caballo de dicho objeto. Cuando se destruye un objeto
Pegaso, también se destruyen la parte Ave y la parte Caballo, gracias a que los destructores
se hicieron virtuales.
D e c la ra c ió n d e la h e r e n c ia m ú lt ip le
Un objeto que se herede de m ás de una clase se declara enlistando las clases base después del
signo de dos pun tos (:) qu e va después del nom bre de la clase. Las clases base se separan por
medio de comas.
Ejemplo 1
class Pegaso : public Caballo, public Ave
Ejemplo 2
class Schnoodle : public Schnauzer, public Poodle
Fig u r a 13.1
Objetos con herencia
múltiple.
Surgen varias cuestiones con los objetos que tienen clases base múltiples. Por ejemplo,
¿qué pasa si dos clases base que por casualidad tienen el mismo nombre, tienen funciones
o datos virtuales? ¿Cómo se inicializan los constructores de clases base múltiples? ¿Qué
ocurre si ambas clases base múltiples se derivan de la misma clase? Las siguientes
secciones responderán estas preguntas y explicarán la forma en que se puede utilizar la
herencia múltiple.
|424 Día 13
1: // Listado 13.4
2: // Cómo llamar varios constructores
3: #include <iostream.h>
4: typedef int CUARTAS;
ri 5: enum COLOR { Rojo, Verde, Azul, Amarillo, Blanco, Negro, Cafe } ;
£
O•
P.
il 7: class Caballo
a. 8: {
n 9: public:
i! Caballo(COLOR color, CUARTAS altura);
li 10:
1! 11: virtual -Caballo() { cout « "Destructor de Caballo...\n"; }
II 12: virtual void Relinchar()const { cout « "¡Yihii!... "; }
h 13: virtual CUARTAS ObtenerAltura() const { return suAltura; }
ti 14: virtual COLOR ObtenerColor() const { return suColor; }
tt 15: private:
i; 16: CUARTAS suAltura;
II 17: COLOR suColor;
1! 18: };
19:
8 ; 20: Caballo:-.Caballo (COLOR color, CUARTAS altura):
ii
21: suColor(color),suAltura(altura)
i 22: {
ti 23: cout « "Constructor de Caballo...\n";
í¡' 24: >
l i 25:
26: class Ave
1 1
* 27: {
3 1 28: public:
¡1 29: Ave(COLOR color, bool emigra);
t 30: virtual ~Ave() {cout « "Destructor de Ave...\n"; }
31 : virtual void Gorjear()const { cout « "Griii... "; }
32: virtual void Volar()const
33: {
34: cout « "IPuedo volar! ¡Puedo volar! ¡Puedo volar! ";
35: }
36: virtual COLOR ObtenerColor()const { return suColor; }
37: virtual bool ObtenerMigracion() const { return suMigracion; }
38:
39: private:
40: COLOR suColor;
41 : bool suMigracion;
42: };
43:
A
Polimorfismo 425
C o n st ru c t o r de C a b a l l o . . .
S a l id a
C o n st ru c t o r de A v e ...
C o n st ru c t o r de P e g a so ...
¡Puedo v o la r! ¡Puedo v o l a r ! ¡Puedo v o l a r ! ¡ Y i h i i ! . . .
Su Pegaso mide 5 c u a r t a s de a l t u r a y s í em igra.
Un t o t a l de 10 perso na s creen que s í e x i s t e .
D e st ru c t o r de P e g a so ...
D e st ru c t o r de A v e ...
D e st ru c t o r de C a b a l l o . . .
R eso lu ció n de a m b ig ü e d a d
En el listado 13.4, tan to la clase C a b a l l o c o m o la c la s e A v e tie n e n un m éto d o llamado
O b t e n e r C o l o r ( ) . Tal vez necesite pedir al o b jeto P e g a s o q u e re g re se su color, pero hay
un pro b lem a: La clase P e g a s o h ered a tan to de A v e c o m o de C a b a l l o . A m b o s tienen un
color, y sus m étodos para obtener ese color tienen los m ism os n om bres y las m ism as firmas.
Esto crea una am bigüedad para el com pilador, q u e u sted d e b e resolver.
Si sim plem ente escribe
COLOR c o lo r A c t u a l = apPeg-> 0 b t e n e r C o lo r ();
Polimorfismo 427
Puede resolver la a m b ig ü e d ad con una llam ada explícita al m étodo que quiere invocar:
COLOR c o l o r A c t u a l = a p P e g -> C a b a l l o : : Obtene rCo lo r();
Siempre que necesite resolver de qué clase heredar una función m iem bro o datos m iem bro,
puede identificar p len am en te la llam ada anteponiendo el nom bre de la clase a los datos
o a la función de la c la se base.
Observe que si P e g a s o fuera a red e fin ir esta función, el problem a se debe reso lv e r en el
método de Pegaso:
v i r t u a l COLOR O b t e n e r C o lo r ( ) c o n st { return Caballo::O b ten erC o lo r() ; }
Esto oculta el problem a de los clientes de la clase Pegaso y encapsula dentro de P e gaso el
conocim iento de cuál va a ser la clase base de la que quiere heredar su color. El cliente
aún tiene la libertad de forzar esta cuestión escribiendo
COLOR c o lo r A c t u a l = apPeg - >A ve : : ObtenerColor () ;
Fig u r a 13.2
C lases b a se c o m u n e s.
Como puede ver en la figura 13.2, existen dos objetos de la clase base. Cuando se llama
a una función o a un dato miembro en la clase base com partida, existe otra ambigüedad.
Por ejemplo, si Animal declara a suEdad com o variable m iem bro y a ObtenerEdad()
como un método, y usted llam a a apPeg ->ObtenerEdad (), ¿quiso llam ar a la función
ObtenerEdadO que heredó de Animal por medio de Caballo, o por medio de Ave? Debe
resolver también esta ambigüedad, como se muestra en el listado 13.5.
1: I I Listado 13.5
2: // Clases base comunes
3: #include <iostream.h>
4:
5: typedef int CUARTAS;
6: enum COLOR { Rojo, Verde, Azul, Amarillo, Blanco, Negro, Cafe } ;
7:
8: class Animal // base común para caballo y ave
9: r
10: public:
11 : Animal(int);
12: virtual -Animal() { cout « "Destructor de Animal...\n"; }
13: virtual int ObtenerEdadO const { return suEdad; }
14: virtual void AsignarEdad(int edad) { suEdad = edad; }
15: private:
16: int suEdad;
17: };
18:
19: Animal::Animal(int edad):
20 : suEdad(edad)
21 : {
22: cout « "Constructor de Animal...\n" ;
23:
24:
25: class Caballo : public Animal
26: {
27: public:
28: Caballo(COLOR color, CUARTAS altura, int edad);
29: virtual -Caballo() { cout « "Destructor de Caballo...\n"; }
30: virtual void Relinchar()const { cout « "JYihiil... >
31 : virtual CUARTAS ObtenerAltura() const { return suAltura; }
32: virtual COLOR ObtenerColor() const { return suColor; }
33: protected:
34: CUARTAS suAltura;
35: COLOR suColor;
36: };
37:
38: Caballo::Caballo(COLOR color, CUARTAS altura, int edad):
39: Animal(edad),
Polimorfismo
40 suColor(color),suAltura(altura)
41 {
42 cout << "Constructor de Caballo...\nn;
43 }
44
45 class Ave : public Animal
46 {
47 public:
48 Ave(COLOR color, bool migra, int edad);
49 virtual -Ave{) {cout « "Destructor de Ave...\n"; }
50 virtual void Gorjear( )const { cout « “Griii... }
51 virtual void Volar()const
52 { cout << "¡Puedo volar! ¡Puedo volar! iPuedo volar! "; }
53 virtual COLOR ObtenerColor()const { return suColor; }
54 virtual bool ObtenerMigracion() const { return suMigracion; >
55 protected:
56 COLOR suColor;
57 bool suMigracion;
58 };
59
60 Ave::Ave(COLOR color, bool emigra, int edad):
61 Animal(edad),
62 suColor(color), suMigracion(emigra)
63 {
64 cout « "Constructor de Ave...\n";
65
66
67 class Pegaso : public Caballo, public Ave
68 {
69 public:
70 void Gorjear()const { Relinchar(); }
71 Pegaso(COLOR, CUARTAS, bool, long, int);
72 virtual -Pegaso() {cout « "Destructor de Pegaso...\n";}
73 virtual long ObtenerNumeroCreyentes() const
74 { return suNumeroCreyentes; }
75 virtual COLOR ObtenerColor()const { return Caballo::suColor; }
76 virtual int ObtenerEdad() const { return Caballo::ObtenerEdad(); }
77 private:
78 long suNumeroCreyentes;
79 };
80
81 Pegaso::Pegaso(
82 COLOR aColor,
83 CUARTAS altura,
84 bool emigra,
85 long NumCreyen,
86 int edad):
87 Caballo(aColor, altura,edad),
88 Ave(aColor, emigra,edad),
89 suNumeroCreyentes(NumCreyen)
90 {
continúa
L is t a d o 1 3 . 5 c o n t in u a c ió n
Constructor de Animal...
Salida Constructor de C ab allo ...
Constructor de Animal...
Constructor de A ve ...
Constructor de Pegaso...
Este Pegaso tiene 2 años de edad.
Destructor de Pegaso...
Destructor de Ave...
Destructor de Animal...
Destructor de C aballo...
Destructor de Animal...
En la línea 45 se declara la clase Ave derivada de Animal. Su c o n stru c to r tam bién toma
una edad y la utiliza para inicializar la clase base. Animal. Tam bién hereda ObtenerEdad ()
sin redefinirla.
Pegaso hereda tanto de Ave como de Animal, por lo que ahora tiene dos clases Animal en
su cadena hereditaria. Si se fuera a llam ar a ObtenerEdad () en un o b jeto Pegaso, habría
que resolver la ambigüedad, o identificar completam ente el m étodo que se quiere si Pegaso
no redefinió el método.
La cadena ascendente se hace por dos razones: ya sea para resolver la ambigüedad sobre
cuál clase base llamar, como en este caso, o para hacer algo y luego dejar que la función
de la clase base haga algo más. A veces, tal vez quiera trabajar y luego hacer una cadena
ascendente, o hacer la cadena y luego hacer el trabajo cuando regrese la función de la clase
base.
El constructor de Pegaso toma cinco parámetros: el color de la criatura, su altura (se mide
en cuartas), si emigra o no. cuántas personas creen que existe, y su edad. En la línea 87.
el constructor inicializa la parte Caballo del objeto Pegaso con el color, la altura y la edad.
En la línea 88 inicializa la parte Ave con el color, si emigra o no. y la edad. Por último, en
la línea 89 inicializa la variable suNumeroCreyentes.
En la línea 87. la llamada al constructor de Caballo invoca a la implementación que se
muestra en la línea 38. El constructor de Caballo utiliza el parámetro edad para inicializar
la parte Animal de la parte Caballo del objeto Pegaso. Luego inicializa las dos variables
miembro de Caballo: suColor y suAltura.
En la línea 88, la llamada al constructor de Ave invoca la implementación que se muestra
en la línea 60. Aquí también se utiliza el parámetro edad para inicializar la parte Animal
de Ave.
Observe que el parámetro aColor de Pegaso se utiliza para inicializar las propiedades tanto
de Ave como de Caballo. Observe también que edad se utiliza para inicializar la variable
suEdad de la clase base Animal de Caballo y de la clase base Animal de Ave.
Herencia virtual
En el listado 13.5, la clase Pegaso hizo un esfuerzo considerable para resolver la ambigüe
dad acerca de cuál de sus clases base Animal quería invocar. La mayoría de las veces, la
decisión sobre cuál clase base utilizar es arbitraria (después de todo, la clase Caballo y
la clase Ave tienen la misma clase base.
Usted puede decirle a C++ que no quiere dos copias de la clase base compartida, como se
muestra en la figura 13.2, sino que mejor quisiera tener una sola clase base compartida,
como se muestra en la figura 13.3.
Puede lograr esto haciendo que Animal sea una clase base virtual tanto de Caballo como de
Ave. La clase Animal no cambia en nada. Las clases Caballo y Ave cambian sólo en el uso
del término virtual en sus declaraciones. Sin embargo, Pegaso cambia considerablemente.
Por lo general, el constructor de una clase inicializa sólo sus propias variables y su clase
base. No obstante, las clases base heredadas en forma virtual son una excepción. Las
inicializa su clase más derivada. Por lo tanto, a Animal no la inicializa Caballo ni Ave. sino
Pegaso. Caballo y Ave tienen que inicializar a Animal en sus constructores, pero estas
inicializaciones serán ignoradas cuando se cree un objeto Pegaso.
432 Día 13
Figura 13.3
Una herencia de dia
mante.
1: // Listado 13.6
2: // Herencia virtual
3: tfinclude <iostream.h>
4:
5: typedef int CUARTAS;
6: enum COLOR { Rojo, Verde, Azul, Amarillo, Blanco, Negro, Cafe } ;
7:
8: class Animal // base común para caballo y ave
9: {
10: public:
11: Animal(int);
12: virtual ~Animal(){ cout « "Destructor de Animal. ..\n"; }
13: virtual int ObtenerEdad() const { return suEdad; >
14: virtual void AsignarEdad(int edad) { suEdad = edad; }
15: private:
16: int suEdad;
17: };
18:
19: Animal::Animal(int edad):
20: suEdad(edad)
2 1: {
22: cout « "Constructor de Animal...\n■;
23: }
24:
25: class Caballo : virtual public Animal
26: {
Polimorfismo
27 p u b lic :
28 Caballo(COLOR c o lo r, CUARTAS altura, in t edad);
29 v i r t u a l - C a b a llo ( ) { cout << "Destructor de C a b a llo . . . \ n "; }
30 v i r t u a l void R e l in c h a r ( ) const { cout « " ¡ Y i h i i ! . . . "; }
31 v i r t u a l CUARTAS O btene rAltu ra () const { return suAltura; }
32 v i r t u a l COLOR ObtenerColor() const { return suColor; }
33 p ro te c t e d :
34 CUARTAS s u A ltu ra ;
35 COLOR suColor;
36 };
37
38 Caballo::Caballo(COLOR c o lo r, CUARTAS altura, in t edad):
39 Anim al(edad),
40 s u C o l o r (c o l o r ) ,s u A lt u ra (a lt u ra )
41 {
42 cout << "C o n s tru c to r de C a b a llo . . . \ n " ;
43
44
45 c l a s s Ave : v i r t u a l p u b lic Animal
46 {
47 p u b lic :
48 A ve(COLOR c o lo r, bool emigra, in t edad);
49 v i r t u a l ~Ave() {cout « "D estructor de A ve ...\n "; }
50 v i r t u a l void G o rje a r( ) const { cout << " G r i i i . . . "; }
51 v i r t u a l void V o la r()c o n s t
52 { cout << "¡Puedo vo lar! ¡Puedo volar! ¡Puedo volar! "; }
53 v i r t u a l COLOR ObtenerColor( ) const { return suColor; }
54 v i r t u a l bool ObtenerMigracion() const { return suMigracion; }
55 p ro te c t e d :
56 COLOR suColor;
57 bool suMigracion;
58 };
59
60 Ave::Ave(COLOR c o lo r, bool emigra, int edad):
61 A n im a l(ed ad),
62 s u C o l o r ( c o l o r ) , suMigracion(emigra)
63 {
64 cout << "C o n stru cto r de A v e ...\n ";
65 }
66
67 c l a s s Pegaso : p u b lic Caballo, public Ave
68 {
69 p u b lic :
70 void G orjea r()co nst { Relinchar(); }
71 Pegaso(COLOR, CUARTAS, bool, long, in t);
72 v i r t u a l -Pegaso() {cout << "Destructor de Pegaso.. . \ n ”;}
73 v i r t u a l long ObtenerNumeroCreyentes () const
74 { return suNumeroCreyentes; }
75 v i r t u a l COLOR ObtenerColor( ) const { return C a b a llo : : s u C o lo r; }
76 p rivate :
77 long suNumeroCreyentes;
continúa
43 4 Día 13
78: };
79:
80: Pegaso:¡Pegaso(
81: COLOR aColor,
82: CUARTAS altura,
83: bool emigra,
84: long NumCreyen,
85: int edad):
86: Caballo(aColor, altura,edad),
87: Ave(aColor, emigra,edad),
88: Animal(edad*2),
89: suNumeroCreyentes(NumCreyen)
90: {
91: cout « "Constructor de Pegaso...\n";
92: >
93:
94: int main()
95: {
96: Pegaso *apPeg = new Pegaso(Rojo, 5, true, 10, 2);
97: int edad = apPeg->ObtenerEdad();
98: cout « "Este Pegaso tiene " « edad « " años de edad.\n";
99: delete apPeg;
100: return 0;
10 1: }
Constructor de Animal...
S a l id a Constructor de Caballo...
Constructor de Ave...
Constructor de Pegaso...
Este Pegaso tiene 4 años de edad.
Destructor de Pegaso...
Destructor de Ave...
Destructor de Caballo...
Destructor de Animal...
A nálisis En la línea 25, Caballo declara que tiene herencia virtual de Animal, y en la línea
45 Ave hace la misma declaración. Observe que los constructores tanto de Ave como
de Animal aún inicializan el objeto Animal.
Pegaso hereda tanto de Ave como de Animal, y siendo el objeto más derivado de Animal,
también inicializa a Animal. Sin embargo, la inicialización de Pegaso es la que se llama,
y las llamadas al constructor de Animal de Ave y de Caballo se ignoran. Puede ver esto
debido a que se pasa el valor 2, y Caballo y Ave se lo pasan a Animal, pero Pegaso lo
duplica. EL resultado, 4, se refleja en la impresión de la línea 98 y com o se m uestra en
la salida.
Polimorfismo 435
D e c la r a c ió n d e c la s e s p a r a h e re n c ia virtual
Para a s e g u r a r q u e las clases d e r iv a d a s t e n g a n sólo un a instancia d e clases base c o m u n e s ,
declare las clases i n t e r m e d ia s d e f o r m a q u e h e rede n virtualm en te de la clase base.
E je m p lo 1
c l a s s C a b a llo : v i r t u a l p u b lic Animal
c l a s s Ave : v i r t u a l p u b lic Animal
c l a s s Pegaso : p u b l ic C a b a llo , p u b lic Ave
Ejem plo 2
c l a s s Schnauzer : v i r t u a l p u b lic Perro
c l a s s Poodle : v i r t u a l p u b lic Perro
c l a s s Schnoodle : p u b l i c Schnauzer, p u b lic Poodle
Estos son p u n to s v álid o s, y usted debe estar en contra de im plem entar program as
com plejos si no es necesario. A lgunos depuradores tienen m uchas dificultades con la
herencia m últiple, y algunos diseños se hacen complejos al utilizar herencia m últiple
cuando no se necesita.
D ebe N O DEBE
13
D E B E utilizar he re n cia m últip le c u a n d o una N O D E B E utilizar herencia m últip le c u a n d o
clase n u e v a necesite f u n c i o n e s y característi baste con la herencia simple.
cas d e m á s d e u n a clase base.
D E B E utilizar la he ren cia virtual c u a n d o las
clases m á s d e r iv a d a s d e b a n t e n e r só lo una
instancia d e la clase base com partida .
D E B E inicializar la clase b a s e c o m p a rt id a
d esde la clase m á s d e r iv a d a c u a n d o utilice
clases ba se virtuales.
Mezclas y clases de capacidad
Una forma de llegar a un término medio entre herencia múltiple y herencia simple es uti
lizar lo que se conoce como mezclas. Por ejemplo, podría hacer que su clase Caballo se
derive de Animal y de Desplegable. Desplegable sólo agregaría unos cuantos métodos
para desplegar cualquier objeto en pantalla.
Una mezcla, o clase de capacidad, es una clase que agrega funcionalidad sin agregar
muchas o ninguna propiedad.
Las clases de capacidad se mezclan en una clase derivada de la mism a form a en que se
podría mezclar cualquier otra clase: declarando a la clase derivada para que herede de las
clases de capacidad en forma pública. La única diferencia entre una clase de capacidad y
cualquier otra clase es que la primera tiene pocas o ninguna propiedad. Esta es una dis
tinción arbitraria, y es sólo una manera abreviada de indicar que a veces todo lo que se
quiere hacer es mezclar algunas capacidades adicionales sin com plicar la clase derivada.
Esto hará que para algunos depuradores sea más sencillo trabajar con m ezclas que con
objetos con herencia múltiple más complejos. Además, existe una m enor probabilidad de
ambigüedad al tener acceso a las propiedades en la otra clase base principal.
Por ejemplo, si Caballo se derivara de Animal y de Desplegable, Desplegable no
tendría propiedades. Animal no tendría cambio, por lo que todas las propiedades de
Caballo se derivarían de Animal, pero sus funciones se derivarían tanto de Animal
como de Desplegable.
El término mezcla viene de una tienda de helados de Somerville, M assachusetts, en donde
se mezclaban dulces y pasteles en los sabores básicos de la nieve. Esto pareció una buena
metáfora para algunos de los programadores que hacían programas orientados a objetos,
quienes solían veranear ahí, especialmente cuando trabajaban con el lenguaje de progra
mación orientada a objetos llamado SCOOPS (en inglés, este térm ino se utiliza para la
cuchara con la que se sirve la nieve).
l'01111111111
L is ta d o 13.7 continuación
( l ) C i r c u l o (2)Rectangulo (3)Cuadrado ( 0 ) S a l ir : 3
x x x x x
x x x x x
x x x x x
x x x x x
x x x x x
Es preocupante que un cliente trate de instanciar un objeto Figura, y lo mejor se” ^seS que
eso imposible. La clase Figura existe sólo para proporcionar una interfaz para las c
se derivan de ella; como tal, es un tipo de datos abstracto, o ADT (Abstract Data YP
Un tipo de datos abstracto representa un concepto (como el de figura) en vez de un ^
(como un círculo). En C++, un ADT siempre es la clase base para otras clases, y
válido crear una instancia de un ADT.
1: class Figura
2: {
3: public:
4: Figuran {}
5: ~Figura(){}
6: virtual long ObtenerArea() = 0; // error si
7: virtual long ObtenerPerim()= 0; // la clase i
8: virtual void Dibujar() = 0;
9: private:
10:
Polimorfismo 441
( 1 (C ir c u lo (2)Rectangulo (3)Cuadrado ( 0 ) S a l i r : 2
x x x x x x
x x x x x x
x x x x x x
X X X X X X
(1 ( C ir c u lo (2)Rectangulo (3)Cuadrado ( 0 ) S a l i r : 3
x x x x x
x x x x x
x x x x x
x x x x x
x x x x x
Com o puede ver. el funcionamiento del programa no sufre cam bios. La única
A n á l is is
diferencia es que ahora sería imposible crear un objeto de la clase F ig u ra .
T ip o s de d a to s abstractos
Para d ec la ra r u n a clase c o m o tip o de d ato s abstractos se incluyen u n a o m á s f u n c i o n e s
virtuales puras en la declaración de la clase. Un fu nción virtual pura se declara escribiendo
= 0 despu és de la declaración de la función.
He a q u í un ejemplo:
cla ss F ig u ra
{
v i r t u a l vo id D ib u j a r ( ) = 0; // v i r t u a l pura
};
442 Día 13
t <'iilimid
|444 Dia 13
lin las líneas 5 a 14 se declara el tipo de datos abstracto Figura, y sus tres métodos
A n á l is is
de acceso se declaran como virtuales puros. Hay que tener en cuenta que esto no
es necesario. Si cualquiera fuera declarado como virtual puro, la clase hubiera sido un ADT
Los métodos O b te n e rA re a () y ObtenerPerim () no se implementan. pero D i b u j a r ( ) sí.
C i r c u l o y Rectángulo redefinen a D ib u j a r ( ). y ambos hacen una cadena ascendente para
el método base, aprovechando la funcionalidad compartida de la clase base.
Tal vez al examinarlos decida que cada Mamif ero se reproduzca de la misma forma, por lo
que la función Mamif ero: : R eproducir () no será pura, pero dejará a Comer(), Dormir ()
y Mover () como funciones virtuales puras.
De Mamif ero derivará a Perro, y Perro deberá redefinir e implementar las tres funciones
virtuales puras restantes, para que pueda crear objetos de tipo Perro.
Lo que está usted diciendo como diseñador de la clase, es que ningún Animal o ningún
Mamif ero puede ser distanciado, pero que todos los Mamiferos pueden heredar el método
R e p r o d u c i r () proporcionado sin redefinirlo.
El listado 13.10 muestra esta técnica con una implementación simplificada de estas clases.
1: II Listado 13.10
2: // Derivación de ADTs de otros ADTs
3: tfinclude <iostream.h>
4:
5: enum COLOR { Rojo, Verde, Azul, Amarillo, Blanco, Negro, Cafe } ;
6:
7: c la s s Animal II base común para Mamifero y Pez
8: {
9: public:
10: Animal(int) ;
11: v i r t u a l ~Animal() { cout « "Destructor de Animal.. . \ n " ; }
12: v i r t u a l in t ObtenerEdad() const { return suEdad; }
13: v i r t u a l void AsignarEdad(int edad) { suEdad = edad; }
14: v i r t u a l void Dormir() const = 0;
c o n t in u a
Listado 13.10 continuación
114:
115: apAnimal->Hablar();
116: apAnimal*>Comer();
117: apAnimal->Reproducir();
118: apAnimal->Mover();
119: apAnimal->Dormir();
120: delete apAnimal;
121: cout « "\n";
122 : }
123: return 0;
124: } _______
A nálisis En las líneas 7 a 21 se declara el tipo de datos abstracto Animal. Animal tiene
métodos de acceso virtuales no puros para su Edad, los cuales son compartidos por
todos los objetos de tipo Animal. Animal tiene cinco funciones virtuales puras, Dormir (),
Comer(), Reproducir(), Mover() y Hablar().
Mamífero se deriva de Animal, se declara en las líneas 29 a 37 y no agrega datos. No
obstante, redefine la función Reproducir (), proporcionando una forma común de reproduc
ción para todos los mamíferos. Pez debe redefinir a Reproducir () ya que Pez se deriva
directamente de Animal y no puede aprovechar la reproducción de los m am íferos (¡y eso
es bueno!).
Las clases derivadas de Mamífero ya no tienen que redefínir la función Reproducir(), pero
pueden hacerlo si quieren, como lo hace Perro en la línea 83. Pez, Caballo y Perro
rede finen las funciones virtuales puras restantes, para que se puedan crear instancias de
objetos de su tipo.
En el cuerpo del programa se utiliza un apuntador a Animal para apuntar a los diversos
objetos derivados en tumo. Se invocan los métodos virtuales, y con base en el enlace en
tiempo de ejecución del apuntador, se llama al método correcto en la clase derivada.
Se generaría un error en tiempo de compilación si se tratara de instanciar un Animal o un
Mamífero, ya que ambos son tipos de datos abstractos.
Polimorfismo 449
D ebe N O DEBE
D E B E utilizar tipos de d ato s abstractos N O D E B E tratar de instandar un objeto de
para p ro p o rcio n ar un a fun cion alid ad un tipo de datos abstracto.
com ún para varias clases relacionadas.
D E B E redefinir todas las funciones virtuales
puras.
D E B E hacer qu e cualquier función que se
deba redefinir sea virtual pura.
El patrón observador
Una tendencia muy popular en C++ es la creación y diseminación de patrones de diseño.
Éstas son soluciones bien documentadas para problemas comunes encontrados por los
programadores de C++. Como ejemplo, el patrón observador resuelve un problema común
de herencia.
Imagine que desarrolla una clase cronómetro que sabe cómo contar los segundos transcurri
dos. Una clase así podría tener una variable miembro llamada susSegundos, que sería un
entero, y tendría métodos para asignar valores a la variable miembro susSegundos, obtener
valores de ella, e incrementarla.
Ahora suponga que su programa quiere que se le informe cada vez que se incremente
la variable miembro susSegundos del cronómetro. Una solución obvia sería colocar un
método de notificación en el cronómetro. Sin embargo, la notificación no es una parte
intrínseca de la medición del tiempo, y el código complejo necesario para registrar esas
clases que necesitan que se les informe cada vez que el reloj se incremente, realmente
no pertenece a la clase cronómetro.
Lo que es más importante, después de descubrir la lógica de registrar aquellas clases que
estén interesadas en estos cambios y luego notificarlos, le gustaría aislar esto dentro de una
clase propia y poder volver a utilizarlo con otras clases que quieran ser “observadas” de
esta manera.
| 450 Día 13
Por lo tanto, una mejor solución es crear una clase observadora. Haga de este observador
un tipo de datos abstracto con una función virtual pura llamada Actualizar().
Ahora cree un segundo tipo de datos abstracto llamado Sujeto. Sujeto mantiene un arreglo
de objetos Observador y también proporciona dos métodos: registrar() (el cual agrega
observadores a su lista) y Notificar (), el cual se llama cuando hay algo que reportar.
Las clases que quieren ser notificadas de los cam bios en su cronóm etro heredan de
Observador. El cronómetro mismo hereda de Sujeto. La clase Observador se registra a
sí misma con la clase Sujeto. La clase Sujeto llama a Notificar cada vez que cambia
(en este caso, cuando el cronómetro se actualiza).
Por último, hay que tener en cuenta que no todos los clientes de cronóm etro quieren ser
observables, por lo que crearemos una nueva clase llamada Cronomet roObservado, la cual
hereda tanto de cronómetro como de Suj eto. Esto da a Cronomet roObservado las carac
terísticas de cronómetro, así como su capacidad para ser observado.
Una interfaz es muy similar a un tipo de datos abstracto en cuanto a que define un conjunto
de funciones que sólo se pueden implementar en una clase derivada. Sin embargo, con las
interfaces no se deriva directamente de la interfaz; se deriva de otra clase y se implementa
la interfaz, algo muy parecido a la herencia múltiple. Por consecuencia, este matrimonio
entre un tipo de datos abstracto y la herencia múltiple proporciona algo semejante a una
clase de capacidades, pero sin la complejidad o sobrecarga producida por la herencia
múltiple. Además, ya que las interfaces no necesitan tener ni im plem entaciones ni datos
miembro, se elimina la necesidad de herencia virtual.
Que esto sea un error o una característica depende de la forma en que se vea. De cualquier
manera, si comprende lo que son la herencia múltiple y los tipos de datos abstractos de C++.
estará en una excelente posición para poder utilizar algunas de las características más
avanzadas de Java, en caso de que decida aprender también ese lenguaje.
El patrón observador y la forma en que se implementa tanto en Java com o en C++ se tratan
detalladamente en el artículo de Robert Martin, titulado “C++ y Java: Una comparación
crítica”, que aparece en la edición de enero de 1997 de C + + Report.
Polimorfismo 451
R e su m e n
Hoy aprendió cómo vencer algunas de las limitaciones de la herencia simple. Conoció el
peligro de la filtración ascendente de interfaces y los riesgos de la conversión descendente
en la jerarquía de clases. También aprendió cómo utilizar la herencia múltiple, los problemas
que puede crear v cómo resolverlos por medio de la herencia virtual.
También aprendió lo que son los tipos de datos abstractos y cómo crear clases abstractas
por medio de funciones virtuales puras. Conoció la forma de implementar funciones
virtuales puras y por qué y cuándo hacerlo. Por último, vio cómo implementar el patrón
observador usando herencia múltiple y tipos de datos abstractos.
P re gu n ta s y re sp u e sta s
P ¿Q ué significa f iltr a r la fu n cio n alid ad de m a n era ascendente?
R Esto se refiere a la idea de llevar la funcionalidad compartida hacia una clase base
común. Si dos o más clases comparten una función, es recomendable encontrar una
clase base común en la que se pueda guardar esa función.
P ¿E s b ueno u tiliz a r sie m p re la filtración ascendente?
R Sí, si filtra la funcionalidad compartida en las capas superiores de la jerarquía de
clases. No. si sólo está moviendo la interfaz. Es decir, si sólo algunas clases
derivadas pueden utilizar el método, sería un error moverlo hacia una clase base
común. Si lo hace, tendrá que cambiar el tipo del objeto en tiempo de ejecución
antes de decidir si puede invocar a la función.
P ¿ P o r q u é es m a lo c a m b ia r el tipo de un objeto en tiem po de ejecución?
R Con programas grandes, las instrucciones switch se vuelven enormes y difíciles de
mantener. El objeto de las funciones virtuales es dejar que la tabla virtual determine
el tipo del objeto en tiempo de ejecución, en lugar de que lo haga el programador.
P ¿ P o r q u é es m a la la conversión?
R La conversión no es mala si se hace en una manera que sea segura para el tipo de
datos o la clase. Si se llama a una función que sabe que el objeto debe ser de un tipo
específico, hacer la conversión a ese tipo está bien. La conversión puede minar la
poderosa comprobación de tipos de C++, y eso es lo que se quiere evitar. Si está
cambiando el tipo del objeto en tiempo de ejecución y luego convirtiendo un apun
tador. eso puede ser una señal de advertencia de que algo esta mal en su diseño.
P ¿P or qué no hacer virtuales todas las funciones?
R Las funciones virtuales son soportadas por una tabla de funciones virtuales, lo que
provoca una sobrecarga en tiempo de ejecución, tanto en el tamaño del programa
como en su rendimiento. El aptrv, o apuntador a función virtual, es un detalle de
implementación de las funciones virtuales. Cada objeto de una clase que tenga fun
ciones virtuales tiene un aptrv, el cual apunta a la tabla de funciones virtuales para
esa clase. Si tiene clases muy pequeñas de las que no espera derivar otras, tal vez
no quiera hacer ninguna función virtual.
P ¿Cuándo se debe hacer virtual el d estru cto r?
R En cualquier momento que usted crea que la clase va a derivar a otras clases, y vaya
a utilizar un apuntador a la clase base para tener acceso a un objeto de la subclase.
Como regla general, si ha hecho virtual cualquiera de las funciones de su clase, asegú
rese de que el destructor también sea virtual.
P ¿Por qué tomarse la molestia de crea r u n tipo de d a to s a b s tra c to ? , ¿p o r qué
no sólo hacerlo no abstracto y evitar c re a r o b jeto s de ese tip o ?
R El propósito de muchas de las convenciones de C++ es ayudar al compilador a
encontrar errores, para poder evitar errores en tiem po de ejecución en el código
que proporcione a sus clientes. Hacer una clase abstracta (es decir, proporcionar
funciones virtuales puras) ocasiona que el com pilador m arque com o un error
cualquier objeto creado de ese tipo abstracto.
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del mate
rial tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios”, y asegúrese de com prender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Qué es una conversión descendente?
2. ¿Qué es el aptrv?
3. Si un rectángulo “redondo” tiene bordes rectos y esquinas redondeadas, y su clase
RectRedondo hereda tanto de Rectángulo como de Circulo, y éstos a su vez
heredan de Figura, ¿cuántas Figuras se crearán cuando cree un RectRedondo?
4. Si Caballo y Ave heredan de Animal usando herencia virtual pública, ¿inicializan
sus constructores el constructor de Animal? Si Pegaso hereda tanto de Caballo
como de Ave, ¿cómo inicializa el constructor de Animal?
Polimorfismo 453
Ejercicios
1. M uestre la declaración de una clase llamada AvionJet, que herede de Cohete y de
Avión.
2. M uestre la declaración de una clase llamada 777, que herede de la clase AvionJet
descrita en el ejercicio 1.
3. Escriba un program a que derive a Auto y a Camión de la clase Vehiculo. C onvierta
a Vehiculo en un A D T que tenga dos funciones virtuales puras. H aga que Auto y
Camión no sean ADTs.
4. M odifique el p ro g ram a del ejercicio 3 para que Auto sea un ADT, y derive de
Auto a AutoDeportivo, Vagoneta y Sedan. En la clase Auto, proporcione una
im plem entación para una de las funciones virtuales puras de Vehiculo y hágala
no pura.
Il *■
I» 1
Il I
I
I
I
II
II I
I
!
I
n
i
10
Id’
la
Sem ana 2
D ía 14
Clases y funciones
especiales
C++ ofrece varias maneras de limitar el alcance y el impacto de variables y
apuntadores. Hasta ahora ha visto cómo crear variables globales, variables
locales de funciones, apuntadores a variables y variables miembro de clases.
Hoy aprenderá lo siguiente:
• Qué son los datos miembro estáticos (variables) y las funciones miembro
estáticas
• Cómo utilizar variables miembro estáticas y funciones miembro estáticas
• Cómo crear y manipular apuntadores a funciones y apuntadores a funcio
nes miembro
• Cómo trabajar con arreglos de apuntadores a función
Sin embargo, a veces necesitará mantener el registro de una reserva de datos. Por ejem
plo, tal vez quiera saber cuántos objetos de una clase específica se han creado en su progra
ma, y cuantos existen todavía. Las variables miembro estáticas se comparten entre todas las
instancias de una clase. Son un pacto entre los datos globales, que están disponibles para
todos los componentes de su programa, y los datos miembro, que por lo general están
disponibles sólo para cada objeto.
Puede pensar que un miembro estático pertenece a la clase en lugar de al objeto. Los datos
miembro regulares son uno por objeto, pero los miembros estáticos son uno por clase. El
listado 14.1 declara un objeto Gato con un dato miembro estático, llamado CuantosGatos.
Esta variable mantiene el registro de cuántos objetos Gato se han creado. Esto se hace
incrementando la variable estática CuantosGatos con cada construcción y decrementándola
con cada destrucción.
¡Quedan 5 gatos!
S alida Se va a eliminar el que tiene 0 años de edad
i Quedan 4 gatos!
Se va a eliminar el que tiene 1 años de edad
¡Quedan 3 gatos!
Se va a eliminar el que tiene 2 años de edad
¡Quedan 2 gatos!
Se va a eliminar el que tiene 3 años de edad
¡ Quedan 1 gatos!
Se va a eliminar el que tiene 4 años de edad
En las líneas 5 a 17 se declara la clase simplificada Gato. En la línea 12 se declara
A nálisis
una variable miembro estática de tipo int llamada CuantosGatos.
La declaración de CuantosGatos no define un entero; no se reserva espacio de almacena
miento. A diferencia de las variables miembro que no son estáticas, no se reserva espacio
de almacenamiento al instanciar un objeto Gato, debido a que la variable miembro
CuantosGatos no se encuentra en el objeto. Por lo tanto, la variable se define y se ini-
cializa en la línea 19.
Es un error común olvidar definir las variables miembro estáticas de las clases. ¡No permita
que esto le pase a usted! Desde luego que si le pasa, el compilador GNU emitirá una varie
dad de mensajes de error como los que se muestran a continuación:
1s t 1 4 - 0 1 . cxx: In method 'Gato::Gato(int) ' :
l s t 1 4 - 0 1 . cxx : 8: 'CuantosGatos' undeclared ( fi r s t use this function)
l s t 1 4 - 0 1 . cxx:8: (Each undeclared identifier is reported only once
l s t 1 4 - 0 1 . c x x :8: for each function i t appears in.)
I s t 14 - 0 1. c x x : In method ‘ Gato: : -Gato( ) 1 :
1st 1 4 - 0 1 .cxx:9: 'CuantosGatos' undeclared ( fi r s t use this function)
l s t l 4 -0 1 .cxx: At top level:
l s t 1 4 - 0 1 . c x x : 19: 'in t Gato: : CuantosGatos1 is not a static member of 'c la s s Gato'
1 st 14 -0 1 . cxx : In function int main()‘ :
1st 1 4 - 0 1 . cxx: 31 : 'CuantosGatos' is not a member of type 'Gato'
Algunos compiladores no detectan el problema, y el enlazador emite un mensaje como:
undefined symbol Gato: : CuantosGatos
No necesita hacer esto para suEdad debido a que no es una variable miembro estática y se
define cada vez que usted crea un objeto Gato, lo cual hace aquí en la línea 26.
|458 Día 14
24: {
25: const int MaxGatos = 5; int i;
26: Gato *CasaGatos[MaxGatos];
27: for (i = 0; i<MaxGatos; i++)
28: {
29: CasaGatos[i] = new Gato(i);
30: FuncionTelepatica();
31: }
32:
33: for (i = 0; i<MaxGatos; i++)
34: {
35: delete CasaGatos[i];
36: FuncionTelepatica();
37: }
38: return 0;
39: }
40:
41: void FuncionTelepatica()
42: {
43: cout « "¡Hay ";
44: cout « Gato::CuantosGatos « " gatos vivos!\n";
45: }
El listado 14.2 es muy parecido al listado 14.1, excepto por la adición de una nueva
A nálisis
función, llamada FuncionTelepatica (). Esta función no crea un objeto Gato, ni
toma un objeto Gato como parámetro, pero puede tener acceso a la variable miembro
CuantosGatos. De nuevo, vale la pena recalcar que esta variable miembro no se encuentra
en ningún objeto específico; se encuentra en la clase como un todo y, de ser pública,
cualquier función que se encuentre en el programa puede tener acceso a ella.
La alternativa de hacer esta variable pública es hacerla privada. Si lo hace, puede tener
acceso a ella a través de una función miembro, pero entonces debe tener disponible un
objeto de esa clase. El listado 14.3 muestra este método. La alternativa, funciones miembro 14
estáticas, se discute inmediatamente después del análisis del listado 14.3.
460 Día 14
¡Quedan 5 gatos!
S alida Se va a eliminar el que tiene 2 años de edad
¡Quedan 4 gatos!
Se va a eliminar el que tiene 3 años de edad
¡Quedan 3 gatos!
Se va a eliminar el que tiene 4 años de edad
J
Clases y funciones especiales 461
¡Quedan 2 gatos!
Se va a eliminar el que tiene 5 años de edad
¡Quedan 1 gatos!
Se va a eliminar el que tiene 6 años de edad
D ebe N O DEBE
D E B E utilizar variables m iem bro estáticas NO D E B E utilizar variables m iem bro estáti
para com partir datos entre todas las instan cas para guardar datos para un objeto. Los
cias de una clase. datos miembro estáticos se comparten
D E B E hacer que las variables miembro entre todos los objetos de su clase.
estáticas sean protegidas o privadas si
quiere restringir su acceso.
Entrada L is t a d o 1 4 . 4 F u n c io n e s m ie m b ro estáticas
continua
462 Día 14
L istado 1 4 .4 continuación
A nálisis La variable miembro estática CuantosGatos se declara para tener acceso privado,
en la línea 15 de la declaración de Gato. La función de acceso, ObtenerCuantos(),
se declara como pública y estática en la línea 12.
Clases y fundones especiales 463
Como ObtenerCuantos () es pública, cualquier función puede tener acceso a ella, y como
es estática, no hay necesidad de tener un objeto de tipo Gato para llamarla. Por lo tanto,
en la línea 42 la función FuncionTelepatica() puede tener acceso al método de acceso
estático, aunque no tenga acceso a un objeto Gato. Claro que podría haber llamado a
ObtenerCuantos () desde los objetos Gato disponibles en main(), de igual forma que con
cualquier otro método de acceso.
Apuntadores a funciones
Así como un nombre de arreglo es un apuntador constante al primer elemento del arreglo,
el nombre de una función es un apuntador constante a la función. Es posible declarar una
variable de apuntador que apunte a una función y que invoque a la función mediante ese
apuntador. Esto puede ser muy útil; le permite crear programas que deciden cuáles funciones
invocar con base en las acciones del usuario.
La única parte difícil sobre los apuntadores a funciones es entender el tipo de objeto al que
se está apuntando. Un apuntador a int apunta a una variable entera, y un apuntador a una
función debe apuntar a una función del tipo de valor de retomo y firma apropiados.
En la siguiente declaración
long (* funcPtr) (int);
funcPtr se declara como apuntador (observe el * que está antes del nombre) que apunta
a una función que toma un parámetro entero y regresa un tipo long. Los paréntesis alrede
dor de * funcPtr son necesarios, ya que los paréntesis alrededor de int vinculan de forma
más estrecha, es decir, tienen una mayor precedencia que el operador de indirección (*).
Sin los primeros paréntesis, esto declararía una función que toma un entero y regresa un
apuntador a un tipo long. (Recuerde que los espacios no importan aquí.)
Examine estas dos declaraciones:
long * Función (int);
long (* funcPtr) (int);
La primera, Función (), es una función que toma un entero y regresa un apuntador a una
variable de tipo long. La segunda, funcPtr, es un apuntador a una función que toma un
entero y regresa una variable de tipo long.
La declaración de un apuntador a una función siempre incluirá el tipo de valor de retomo
y los paréntesis indicando el tipo de los parámetros, en caso de haberlos. El listado 14.5
muestra la declaración y el uso de los apuntadores a funciones.
Apuntador a función
Un apuntador a una fundón se invoca de la misma manera que las funciones a las que apunta*
excepto que se utiliza el nombre del apuntador a la función, en lugar del nombre de la función.
Clases y fundones especiales 467
Para asignar un apuntador a una función específica, se asigna al nombre de la función sin
los paréntesis. El nombre de la función es un apuntador constante a la función en sí.
Utilice el apuntador a una función de la misma forma que utilizaría el nombre de la fun
ción. El apuntador a una función debe concordar con el valor de retorno y la firma de la
función a la cual esté asignado.
He aquí un ejemplo:
long (*apFuncllno) (int, int);
long UnaFuncion (int, int);
apFuncUno = UnaFuncion;
apFuncUno(5,7);
L istado 1 4 .6 continuación
21: {
22: case 1:
23: ImprimirValores(valUno, valDos);
24: ObtenerValores(valUno, valDos);
25: ImprimirValores(valUno, valDos);
26: break;
27:
28: case 2:
29: ImprimirValores(valUno, valDos);
30: Cuadrado(valUno,valDos);
31: ImprimirValores(valUno, valDos);
32: break;
33:
34: case 3:
35: ImprimirValores(valUno, valDos);
36: Cubo(valUno, valDos);
37: ImprimirValores(valUno, valDos);
38: break;
39:
40: case 4:
41: ImprimirValores(valUno, valDos);
42: Intercambiar(valUno, valDos);
43: ImprimirValores(valUno, valDos);
44: break;
45:
46: default :
47: fSalir = true;
48: break;
49: >
50:
51: if (fSalir)
52: break;
53: }
54: return 0;
55: }
56:
57: void ImprimirValores(int x, int y)
58: {
59: cout « "x: " « x « " y: " « y << endl
60: }
61:
62: void Cuadrado (int & rX, int & rY)
63: {
64: rX *= rX;
65: rY *= rY;
66: >
67:
68: void Cubo (int & rX, int & rY)
Clases y fundones especiales 469
69: {
70: int tmp;
71:
72: tmp = rX;
73: rX *= rX;
74: rX = rX * tmp;
75:
76: tmp = rY;
77: rY *= rY;
78: rY = rY * tmp;
79: }
80:
81: void Intercambiar(int & rX, int & rY)
82: {
83: int temp;
84: temp = rX;
85: rX = rY;
86: rY = temp;
87: >
88:
89: void ObtenerValores (int & rValUno, int & rValDos)
90: {
91: cout « "Nuevo valor para valUno: ";
92: cin » rValUno;
93: cout « "Nuevo valor para valDos: ";
94: cin » rValDos;
95: }
Fue tentador colocar ImprimirValores () al inicio del ciclo while y de nuevo al final, en
lugar de colocarlo en cada instrucción case. Sin embargo, esto habría llamado a Imprimir-
Valores () incluso para el caso de salida, y esto no era parte de la especificación.
Dejando a un lado el aumento de tamaño del código y las llamadas repetidas para hacer
lo mismo, la claridad en general está algo reducida. Sin embargo, éste es un caso artificial,
creado para mostrar cómo funcionan los apuntadores a funciones. En condiciones reales,
las ventajas son aún más claras: los apuntadores a funciones pueden eliminar código
duplicado, clarificar su programa y permitirle crear tablas de funciones a llamar con base
en las condiciones en tiempo de ejecución.
Invocación abreviada
No necesita desreferenciar el apuntador a función, aunque puede hacerlo. Por lo tanto, si
apFunc es un apuntador a una función que toma un parámetro entero y regresa una variable
de tipo long, y asigna apFunc a una función relacionada, puede invocar esa función ya sea con
apFunc(x);
o con
(*apFunc)(x);
Las dos formas son idénticas. La primera es sólo una versión abreviada de la segunda.
12: {
13: int valllno=1, valDos=2;
14: int opcion, i;
15: const int MaxArreglo = 5;
16: void (*apFuncArreglo[MaxArreglo])(int&, int&);
17:
18: for (i=0;i<MaxArreglo;i++)
19: {
20: cout « “(1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar:
L istado 1 4 .7 continuación
63: rY = rY * tmp;
64: }
65:
66: void Intercambiar(int & rX, int & rY)
67: {
68: int temp;
69: temp = rX;
70: rX = rY;
71: rY = temp;
72: }
73:
74: void ObtenerValores (int & rValUno, int & rValDos)
75: {
76: cout « "Nuevo valor para valUno:
77: cin » rValUno;
78: cout « "Nuevo valor para valDos:
79: cin » rValDos;
80: }
Por ejemplo, el listado 14.5 se podría mejorar pasando el apuntador a función elegido a otra
función (fuera de main()), que imprima los valores, invoque a la función y luego imprima
otra vez los valores. El listado 14.8 muestra esta variación.
L istado 1 4 .8 continuación
x: 64 y: 729
(0)Salir (1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 4
x: 64 y: 729
x: 729 y:64
(0)Salir (i)Cambiar valores (2)Cuadrado (3)Cubo (4)Intercambiar: 0
Este tipo de declaración se utiliza con poca frecuencia y probablemente tenga que consultar
este libro cada vez que la necesite, pero salvará su programa en esas raras ocasiones en que
requiera esta construcción.
1 5: int opcion;
16: bool fSalir = false;
17:
18: VAF apFunc;
19:
20: While (fSalir == false)
21: {
22: cout « "(0)Salir (1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar:
23 cin » opcion;
24 switch (opcion)
25 {
26 case 1:apFunc = ObtenerValores; break;
27 case 2:apFunc = Cuadrado; break;
28 case 3:apFunc = Cubo; break;
29 case 4:apFunc = Intercambiar; break;
30 default:fSalir = true; break;
31 >
32 if (fSalir == true)
33 break;
34 ImprimirValores (apFunc, valUno, valDos);
35 }
36 return 0;
37 }
38
39 void ImprimirValores(VAF apFunc,int& x, int& y)
40 {
41 cout « "x: " « x << '" y: " « y « endl;
42 apFunc(x ,y );
43 cout << "x: " « X « 1■ y: " << y « endl;
44 }
45
46 void Cuadrado (int & rX, int & rY)
47 {
48 rX *= rX;
49 rY *= rY;
50
51
52 void Cubo (int & rX, int & rY)
53 {
54 int tmp;
55
56 tmp = rX;
57 rX *= rX;
58 rX = rX * tmp;
59
60 tmp = rY;
61 rY *= rY;
62: rY = rY * tmp;
63:
Clases y fundones especiales 477
64:
65: void Intercambiar(int & rX, int & rY)
66: {
67: int temp;
68: temp = rX;
69: rX = rY;
70: rY = temp;
71 : }
72:
73: void ObtenerValores (int & rValUno, int ■
74: {
75: cout << "Nuevo valor para vallino:
76: cin >> rValUno;
77: cout << "Nuevo valor para valDos:
78: cin >> rValDos;
79: }
Después que se define el tipo VAF, todos los usos subsecuentes para declarar a apFunc y a
ImprimirValores () son más limpios. Como puede ver, la salida es idéntica.
Para crear un apuntador a una función miembro, se utiliza la misma sintaxis que para un
apuntador a una función, pero se incluye el nombre de la clase y el operador de resolución
de ámbito Así que, si apFunc apunta a una función miembro de la clase Figura, la
cual toma dos parámetros enteros y regresa void, la declaración para apFunc es la siguiente:
void (Figura::*apFunc) (int, int);
Los apuntadores a funciones miembro se utilizan de la misma forma que los apuntadores
a funciones, excepto que se requiere de un objeto de la clase correcta para poder invocarlos.
El listado 14.10 muestra el uso de apuntadores a funciones miembro.
{
50: cout « “(0)Salir (l)perro (2)gato (3)caballo
51 : cin >> Animal;
52: switch (Animal)
53: {
54: case 1: aptr = new Perro; break;
en
n
(0) (3)caballo:
Salir (l)perro (2)gato 1
S alida (1) Hablar (2)Mover: 1
¡Guau!
(0) (3)caballo:
Salir (1)perro (2)gato 2
(1) Hablar (2)Mover: 1
¡Miau!
(0) (3)caballo:
Salir (l)perro (2)gato 3
(1)Hablar (2)Mover: 2
Galopando...
(0)Salir (l)perro (2)gato (3)caballo: 0
480 Día 14
En las líneas 5 a 14 se declara el tipo de datos abstracto Mamífero con dos métodos
A nálisis
virtuales puros, Hablar() y Mover(). Mamífero se divide en las subclases Perro,
Gato y Caballo, cada una de las cuales redefine a Hablar() y a Mover ().
El programa controlador de main() pide al usuario que elija el tipo de animal que se va a
crear, y luego se crea una subclase de Animal en el heap y se asigna a aptr en las líneas
54 a 56.
Luego se pide al usuario el método a invocar, y ese método se asigna al apuntador apFunc
en las líneas 66 o 67. La versión 2.7.2 de g++ compila esto sin problemas; la versión 2.9.5
emite las siguientes advertencias (debido a que se están pasando direcciones):
./Ist14-10.cxx: In function 'int main()‘:
./Ist14-10.cxx:66: warning: assuming & on 'Mamífero::Hablar() const’
./Ist14-10.cxx:67: warning: assuming & on 'Mamífero::Mover() const’
En la línea 70, el objeto creado invoca al método elegido mediante el apuntador aptr
para tener acceso al objeto y mediante apFunc para tener acceso a la función.
Finalmente, en la línea 71 se llama a delete en el apuntador aptr para regresar al heap la
memoria reservada para el objeto. Observe que no hay razón para utilizar delete sobre
apFunc ya que éste es un apuntador al código, no un a un objeto en el heap. De hecho, si
se intenta hacer esto se generará un error en tiempo de compilación.
15:
16:
17: typedef vo id (P e r r o : : *AFM)( ) const ;
18: in t main()
19: {
20 : const in t MaxFuncs = 7;
2 1: AFM PerroFuncionesfMaxFuncs] =
22 : { P e rro :¡H a b la r,
23: Perro:¡M over,
24: Perro:¡Comer,
25: Pe rro::G ru ñ ir,
26: P e r r o : : Gimotear,
27: P e r r o : : D a rV u e lta ,
28: Perro:¡HacerMuerto };
29:
30: P e rro * apPerro =NULL;
31: i n t Método;
32: bool f S a l i r = f a ls e ;
33:
34: w hile ( ¡ f S a l i r )
35: {
36: cout « " ( 0 ) S a l i r (1)Hablar (2)Mover (3)Comer (4)G runir";
37: cout « " (5)Gimotear (6)Dar vuelta (7)Hacerse el muerto: ";
38: c in >> Método;
39: i f (Método == 0)
40: {
41: f S a l i r = true;
42: }
43: else
44: {
45: apPerro = new Perro;
46: (apPerro->*PerroFunciones[Metodo-1 ]) ();
47: delete apPerro;
48: }
49: }
50: return 0;
51 : }
En las líneas 5 a 15 se crea la clase Perro, la cual tiene siete funciones miembro,
A n á l is is
y todas comparten el mismo tipo de valor de retorno y la misma firma. En la línea
17, una instrucción typedef declara a AFM como apuntador a una función miembro de Perro
que no lleva parámetros y no regresa valores, y que es const. es decir, la firma de las siete
funciones miembro de Perro.
En las líneas 21 a 28 se declara el arreglo P e r r o F u n c i o n e s para guardar esas siete funciones
miembro, y se inicializa con las direcciones de dichas funciones. Igual que en el listado
14.10, la versión 2.7.2 de g++ no tuvo problemas con este código; la versión 2.9.5 produjo
los siguientes mensajes:
. / I s t 1 4 - 1 1 .cxx: In f u n ctio n 'i n t m a in ( ) ':
. / Is t 1 4 - 1 1 , c x x : 2 8 : warning: assuming & on P e r r o : : H a b l a r () c o n s t '
. / I s t 1 4 - 1 1 .cxx:28: warning: assuming & on P e r r o : : M o v e r () c o n s t '
. / I s t 1 4 - 1 1 .cxx:28: warning: assuming & on P e r r o : : C om er() c o n s t '
. / I s t 1 4 - 1 1 .cxx:28: warning: assuming & on P e r r o : : G r u n i r () c o n s t '
. / I s t 1 4 - 1 1 .cxx:28: warning: assuming & on P e r r o : : G i m o t e a r () c o n s t '
. / Is t 1 4 -1 1 .c x x :2 8 : warning: assum ing & on P e r r o : : D a r V u e l t a () c o n s t '
. / I s t 1 4 - 1 1 .cxx:28: warning: assum ing & on P e r r o : : H a c e r M u e r t o () c o n s t '
En las líneas 36 y 37 se pide al usuario que elija un m étodo. A m enos que elija S a lir, se
crea un nuevo Perro en el heap y luego se invoca el m étodo correcto en el arreglo de la
línea 46. Ésta es otra buena línea para mostrar a los program adores brillantes de C++ de
su compañía; pregúnteles qué es lo que hace:
(a p P e rro -> *P e rro F u n cio n e s[M e to d o -1 ]) () ;
¿Qué cree usted que pasaría si se escribiera un valor fuera de rango (como -1 u 8)7 Como
C++ no tiene forma de verificar los límites de los arreglos, no obtendría un error de
compilación o un error en tiempo de ejecución. Podría obtener resultados completamente
inesperados si su programa intenta utilizar la dirección alm acenada en esa memoria como
una función. Como no estableció un valor en esa m em oria (com o se hizo en las líneas 21
a 28), no tiene idea de lo que hay ahí. Tal vez haya un valor num érico que será tratado como
si fuera la dirección de una función, con resultados im predecibles.
Una vez más, esto es un poco esotérico, pero cuando necesite una tabla construida a par
tir de funciones miembro, esto puede ayudar a que su program a sea más legible.
D ebe N O DEBE
DEBE invocar apuntadores a funciones NO DEBE utilizar apuntadores a funciones
miembro en un objeto específico de una m iem bro cuando se puedan utilizar solu
clase. ciones más sencillas.
D E B E utilizar typedef para que las decla
raciones de apuntador a función miembro
sean más legibles.
C la s e s y f u n c i o n e s e s p e c ia le s 483
Resumen
Hoy aprendió cómo crear variables miembro estáticas en su clase. Cada clase, en vez de
cada objeto, tiene una instancia de la variable miembro estática. Es posible tener acceso
a esta variable sin un objeto del tipo de la clase si se identifica completamente el nombre
(asumiendo que haya declarado el miembro estático con acceso público).
Las variables miembro estáticas se pueden utilizar como contadores a través de las instan
cias de la clase. Como no son parte del objeto, la declaración de variables miembro
estáticas no asigna memoria, y éstas se deben definir e inicializar fuera de la declaración
de la clase.
Las funciones miembro estáticas son parte de la clase de la misma manera que lo son las
variables miembro estáticas. Puede tener acceso a ellas sin un objeto específico de la clase
y puede utilizar para tener acceso a los datos miembro estáticos. Las funciones miembro
estáticas no se pueden utilizar para tener acceso a datos miembro que no sean estáticos,
ya que no tienen un apuntador th is .
Como las funciones miembro estáticas no tienen un apuntador t h i s , tampoco se pueden
hacer const. La palabra reservada c o n s t en una función miembro indica que el apuntador
t h i s es c o nst.
Preguntas y respuestas
P ¿ P o r qué u tiliz a r datos estáticos si se pueden utilizar datos globales?
R Los datos estáticos tienen alcance sólo dentro de la clase. De esta forma, los datos
estáticos están disponibles sólo mediante un miembro de la clase, mediante una llama
da explícita que utilice el nombre de clase si éste es público, o mediante el uso de
una función miembro estática. Sin embargo, los datos estáticos están tipificados con
el tipo de la clase, y el acceso restringido y la fuerte tipificación hacen que los datos
estáticos sean más seguros que los datos globales.
14
P ¿P o r qué u tiliz ar funciones m iem bro estáticas si se pueden utilizar funciones
globales?
R Las funciones miembro estáticas tienen alcance sólo dentro de la clase y sólo se
pueden llamar mediante el uso de un objeto de la clase o mediante una especificación
explícita completa (por ejemplo, NonibreClase: : NombreFuncion ()).
|484 Día 14
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su com prensión del
material tratado, así como ejercicios para que experim ente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios” , y asegúrese de com prender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Pueden las variables miembro estáticas ser privadas?
2 . M uestre la declaración de una variable m iem bro estática.
3. M uestre la declaración de una función estática.
4. Muestre la declaración de un apuntador a una función que regrese un long y que
tome un parámetro entero.
5. M odifique el apuntador de la pregunta 4 para que sea un apuntador a una función
miembro de la clase Auto.
6 . M uestre la declaración de un arreglo de 10 apuntadores com o los de la pregunta 5.
Ejercicios
1. Escríba un programa corto que declare una clase con una variable m iem bro y una
variable miembro estática. Haga que el constructor inicialice la variable miembro e
incremente la variable miembro estática. H aga que el destructor decremente la varia
ble miembro estática.
2. Usando el programa del ejercicio 1, escriba un program a controlador corto que cree
tres objetos y luego despliegue sus variables m iem bro y la variable miembro estática.
Luego destruya cada objeto y m uestre el efecto en la variable m iem bro estática.
3. M odifique el programa del ejercicio 2 para u tilizar una función m iem bro estática
que permita el acceso a la variable m iem bro estática. H aga que la variable miembro
estática sea privada.
C la s e s y f u n d o n e s e s p e c ia le s 485
4. Escriba un apuntador a una función miembro para que tenga acceso a los datos miem
bro que no sean estáticos del programa del ejercicio 3, y utilice ese apuntador para
imprimir el valor de esos datos.
5. Agregue dos variables miembro más a la clase de las preguntas anteriores. Agregue
métodos de acceso que obtengan el valor de estos valores y proporcionen a todas
las funciones miembro los mismos valores de retorno y firmas. Utilice el apunta
dor a una función miembro para tener acceso a estas funciones.
14
n*•
!- b
* i¡
l¡
J,
f
i ■
1
1
1
li
Î!
X •i
* ii
Î 'i
Ìj
3 :¡
4i
■
1
Î
q
f*
» >¡
V ii
% •l
1i :i
■ i
$
1i
S e m a n a 2
Repaso
El programa de repaso de la semana 2 reúne muchas de las
habilidades que usted adquirió durante las últimas dos sema
nas y produce un programa poderoso.
Esta demostración de listas enlazadas utiliza funciones virtua
les, funciones virtuales puras, redefinición de funciones, poli
morfismo, herencia pública, sobrecarga de funciones, ciclos
infinitos, apuntadores, referencias y más. Observe que éste es
un tipo distinto de lista enlazada del que se muestra en el día
12, “Arreglos, cadenas tipo C y listas enlazadas” ; en C++ hay
muchas formas de lograr lo mismo.
El objetivo de este programa es crear una lista enlazada. Los
nodos de la lista están diseñados para guardar piezas, como
podría usarse en una fábrica. Aunque ésta no es la forma final
del programa, hace una buena demostración de una estructura
de datos bastante avanzada. El listado R2.1 tiene 289 líneas.
Trate de analizar el código por su cuenta antes de leer el
análisis que se encuentra después de la salida.
1: //
**************************************************
2 : / /
3: // T í t u l o : R e v is i ó n de la semana 2
4: //
5: // A r c h iv o : Semana2
6: / /
7: // D e s c r ip c i ó n : P ro p o rc io n a r un programa de
‘» d e m o s tra c ió n de l i s t a s enlazadas
8 : / /
9: // C l a s e s : Pieza -guarda números de
»»pieza y po ten cia lm en te c u a lq u ie r otra
10: // inform ación re la c io n a d a
•»con l a s p ie z a s
continua
488 Sem ana 2
11: //
12 : // NodoPieza - actúa como nodo en una ListaPiezas
13: //
14: // ListaPiezas - provee los mecanismos para una lista
^►enlazada
15 //
16 //
17 Ij * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
18
19 #include <iostream.h>
20
21
22
23 II **************** p¿eza ************
24 // Clase base abstracta de piezas
25 class Pieza
26 {
27 public:
28 Pieza() : suNumeroPieza(1) {}
29 Pieza(int NumeroPieza) : suNumeroPieza(NumeroPieza) {}
30 virtual ~Pieza() {};
31 int ObtenerNumeroPieza() const { return suNumeroPieza; >
32 virtual void Desplegar() const = 0; // debe redefinirse
33 private:
34 int suNumeroPieza;
35 };
36
37 // implementación de la función virtual pura para que
38 // las clases derivadas puedan encadenarse
39 void Pieza::Desplegar() const
40 {
41 cout « "\nNúmero de pieza: " « suNumeroPieza << endl;
42 }
43
44 I I **************** pi0Za ¿g Auto ************
45 class PiezaAuto : public Pieza
46 {
47 public:
48 PiezaAuto() : suAnioModelo(94 ) {}
49 PiezaAuto(int anio, int numeroPieza);
50 virtual void Desplegar() const
51 {
52 Pieza::Desplegar(); cout « "Año del modelo:
53 cout « suAnioModelo « endl;
54 >
55 private:
Repaso 489
continúa
L istado R2.1 C O N T IN U A C IÓ N
104
105 NodoPieza NodoPieza()
106 {
107 delete suPieza;
108 suPieza = 0 ;
109 delete suSiguiente;
110
suSiguiente = 0 ;
111
112
113 // Regresa NULL si no hay siguiente NodoPieza
114 NodoPieza * NodoPieza::ObtenerSiguiente( ) cons*
115
116 return suSiguiente;
117
118
119
120
Pieza * NodoPieza::ObtenerPieza() const
121
if (suPieza)
122
123 return suPieza;
else
124
125 return NULL; //error
126
127 // **************** ************
128 Lista de Piezas
class ListaPiezas
129
130 public:
131
ListaPiezas();
132 -ListaPiezas();
133 // Inecesita constructor de copia y operador igual a!
134 Pieza * Encontrar(int & posición, int NumeroPieza) const;
135 int ObtenerCuenta() const { return suCuenta; }
136 Pieza * ObtenerPrimero() const;
137 static ListaPiezas & ObtenerListaPiezasGlobal()
138
139 return ListaPiezasGlobal;
140
141 void Insertar(Pieza *);
142 void Iterar(void (Pieza::*f)() const) const;
143 Pieza * operatori](int) const;
144 private:
145 NodoPieza * apCabeza;
146 int suCuenta;
147 static ListaPiezas ListaPiezasGlobal;
148
149
150 ListaPiezas ListaPiezas:¡ListaPiezasGlobal;
151
Repaso 491 [
L
492 Semana 2
L istado R2.1 c o n t in u a c ió n
200 >
201
202 void ListaPiezas::Iterar(void (Pieza::*func) () const) const
203 {
204 if (¡apCabeza)
205 return;
206 NodoPieza * apNodo = apCabeza;
207 do
208 (apNodo->ObtenerPieza()->*func)();
209 while (apNodo = apNodo*>ObtenerSiguiente());
210 }
211
212 void ListaPiezas::Insertar(Pieza * apPieza)
213 {
214 NodoPieza * apNodo = new NodoPieza(apPieza) ;
215 NodoPieza * apActual = apCabeza;
216 NodoPieza * apSiguiente = NULL;
217 int Nuevo = apPieza->ObtenerNumeroPieza();
218 int Siguiente = 0;
219
220 suCuenta++;
221 if (¡apCabeza)
222 {
223 apCabeza = apNodo;
224 return;
225 }
's
I’*I 226 //si éste es más pequeño que el nodo cabeza,
227 //se convierte en el nuevo nodo cabeza
228 if (apCabeza->ObtenerPieza() ->ObtenerNumeroPieza() > Nuevo)
y 229 {
230 apNodo->AsignarSiguiente(apCabeza);
*t 231 apCabeza = apNodo;
*ti 232
♦ti return;
i*«
t
233
234
}
for (;;)
»1 ii 235 {
236 // si no hay siguiente, agregar éste
237 if (lapActual->ObtenerSiguiente())
238 {
239 apActual->AsignarSiguiente(apNodo);
240 return;
241 }
242 // si va después de éste y antes del siguiente
243 // entonces insertarlo aquí, de no ser así, obtener el siguiente
J
Repaso 493
A nálisis El listado R 2.1 proporciona la implementación de una lista enlazada para obje-
tos Pieza. Una lista enlazada es una estructura de datos dinámica; es decir, es
como un arreglo pero se ajusta su tamaño a medida que se agregan o eliminan objetos.
Esta lista enlazada específica está diseñada para guardar objetos de la clase Pieza, mien
tras que Pieza es un tipo de datos abstracto que sirve como clase base para cualquier
objeto que tenga un número de pieza. En este ejemplo, Pieza se ha dividido en las sub
clases PiezaAuto y PiezaAeroPlano.
La clase Pieza, que se declara en las líneas 25 a 35, consiste en un número de pieza y
algunos métodos de acceso. Probablemente esta clase podría desarrollarse para guardar
más información importante sobre las piezas, como los componentes que se utilizan,
cuántos hay en existencia, etc. Pieza es un tipo de datos abstracto, reforzado por la fun
ción virtual pura Desplegar ().
La clase NodoPieza sirve como interfaz entre la clase Pieza y la clase ListaPiezas.
Contiene un apuntador a una pieza y un apuntador al siguiente nodo de la lista. Sus úni
cos métodos son obtener y asignar el siguiente nodo en la lista y regresar el objeto Pieza
al que apunta.
La inteligencia de la lista se encuentra, apropiadamente, en la clase ListaPiezas, cuya
declaración se encuentra en las líneas 128 a 148. ListaPiezas mantiene un apuntador al
primer elemento de la lista (apCabeza) y lo utiliza para tener acceso a los demás métodos
al avanzar por la lista. Avanzar por la lista significa pedir a cada nodo de la lista el si
guiente nodo, hasta llegar a un nodo cuyo siguiente apuntador sea NULL.
Esta es sólo una implementación parcial; una lista completamente desarrollada propor
cionaría un mayor acceso al primer y último nodos, o proporcionaría un objeto de
iteración, el cual permite que los clientes avancen fácilmente por la lista.
ListaPiezas proporciona sin duda una variedad de métodos interesantes, los cuales se
enlistan en orden alfabético. Esto es a menudo una buena idea, ya que facilita la búsque
da de las funciones.
El método Encontrar() toma como argumentos un NumeroPieza y un número entero
(int NumeroPieza). Si encuentra la pieza correspondiente a NumeroPieza, regresa un
apuntador a esa Pieza y asigna al entero la posición de esa pieza dentro de la lista. Si no
encuentra a NumeroPieza, regresa NULL, y la posición no tiene valor significativo.
El método ObtenerCuenta() regresa el número de elementos que hay en la lista.
ListaPiezas mantiene este número como una variable miembro llamada suCuenta,
aunque podría, desde luego, calcular este número al avanzar por la lista.
Por su parte, el método ObtenerPrimero() regresa un apuntador a la primera Pieza de
la lista, o regresa NULL si la lista está vacía.
ObtenerListaPiezasGlobal() regresa una referencia a la variable miembro estática
ListaPiezasGlobal, la cual es una instancia estática de esta clase; todo programa que
tiene una ListaPiezas también tiene una ListaPiezasGlobal, aunque, desde luego, tiene
la libertad de crear otras ListaPiezas también. Una implementación completa de esta
idea modificaría el constructor de Pieza para asegurar que cada pieza se cree en
ListaPiezasGlobal.
Insertar() toma un apuntador a un objeto Pieza, crea un NodoPieza para este objeto, y
agrega la Pieza a la lista, ordenada por NumeroPieza.
Iterar () toma un apuntador a una función miembro de Pieza, la cual no toma paráme
tros, regresa void, y es const. Llama a esa función por cada objeto Pieza de la lista. En
el programa de muestra se usa en Desplegar(), la cual es una función virtual, por lo que
se llamará el método Desplegar () apropiado con base en el tipo del objeto Pieza llam a
do en tiempo de ejecución.
496 Semana 2
El programa controlador se encuentra en las líneas 256 a 289. En la línea 258 se declara
una referencia a ListaPiezas y se inicializa con ListaPiezasGlobal. Observe que
ListaPiezasGlobal se inicializa en la línea 150. Esto es necesario debido a que la
declaración de una variable miembro estática no la define; la definición debe hacerse
afuera de la declaración de la clase.
En las líneas 264 a 285 se pide varias veces al usuario que elija entre escribir una pieza
de auto o una pieza de avión. Dependiendo de la opción, se pide el valor apropiado, y se
crea la pieza apropiada. Después de esto, la pieza se inserta en la lista en la línea 284.
La implementación del método Insertar () de ListaPiezas se encuentra en las líneas
212 a 254. Cuando se escribe el primer número de pieza, 2837, se crea un objeto
PiezaAuto con ese número de pieza y con 90 como año del modelo y se pasa a
ListaPiezas::Insertar!).
En la línea 214 se crea un nuevo NodoPieza con esa pieza, y la variable Nuevo se inicia-
liza con el número de pieza. La variable miembro suCuenta de ListaPiezas se incre
menta en la línea 220 .
En la línea 221, la prueba de si apCabeza es NULL resulta verdadera (tr u é ) . Como éste es
el primer nodo, es verdadero que el apuntador apCabeza de ListaPiezas tiene el valor
cero. Por lo tanto, en la línea 223 se asigna a apCabeza para que apunte al nuevo nodo, y
esta función regresa.
Se pide al usuario que escriba una segunda pieza, y esta vez se escribe una pieza de
Aeroplano con el número de pieza 378 y el número de motor 4938. Una vez más se
llama a ListaPiezas::Insertar!), y una vez más apNodo se inicializa con el nuevo
nodo. La variable miembro estática suCuenta se incrementa a 2, y se evalúa apCabeza.
Como la última vez se había asignado el primer nodo a apCabeza, ya no tiene el valor
null ,por lo que falla la prueba.
En la línea 228 se compara el número de pieza que guarda apCabeza, 2837, contra el
número de pieza actual, 378. Ya que el nuevo es menor que el que guarda apCabeza,
el nuevo debe convertirse en el nuevo apuntador a la cabeza, y la prueba de la línea 228
resulta verdadera.
Repaso 497
En la línea 230 el nuevo nodo se asigna para apuntar al nodo al que apunta actualmente
apCabeza. Observe que esto no hace que el nuevo nodo apunte a apCabeza, ¡sino al nodo
al que apCabeza estaba apuntando! En la línea 231, apCabeza se asigna para apuntar al
nuevo nodo.
La tercera vez que se pasa por el ciclo, el usuario escribe el número de pieza 4499 para
un Auto con 94 como año del modelo. El contador se incrementa y esta vez el número no
es menor que el número al que apunta apCabeza, por lo que se entra al ciclo fo r que
empieza en la línea 234.
El valor al que apunta apCabeza es 378. El valor al que apunta el segundo nodo es
2837. El valor actual es 4499. El apuntador apActual apunta al mismo nodo que
apCabeza, y por lo tanto la variable Siguiente tiene un valor diferente de cero;
apActual apunta al segundo nodo, por lo que la prueba de la línea 237 falla.
El apuntador apA ctual se asigna para apuntar al siguiente nodo, y se repite el ciclo. Esta
vez la prueba de la línea 237 tiene éxito. No hay un siguiente elemento, por lo que se
indica al nodo actual que apunte al nuevo nodo en la línea 239, y termina la inserción.
La cuarta vez que se pasa por el ciclo, se escribe el número de pieza 3000. Se procede de
la misma forma que en la iteración anterior, pero esta vez el nodo actual está apuntando
a 2837, y el siguiente nodo tiene 4499, la prueba de la línea 246 resulta true, y el nuevo
nodo se inserta en su posición.
Cuando el usuario finalmente oprime 0, la prueba de la línea 268 se evalúa como true y
se rompe el ciclo while (1). En la línea 286 se asigna la función Desplegar () al apunta
dor a función miembro llamado apFunc. En un programa real, esto se asignaría en forma
dinámica, con base en el método que elija el usuario.
El apuntador a la función miembro se pasa al método Iterar() de ListaPiezas. En la
línea 204 el método lterar() se asegura que la lista no esté vacía. Luego, en las lineas
207 a 209 se llama a cada Pieza de la lista por medio del apuntador a la función
miembro. Esto llama al método Desplegar() adecuado para Pieza, como se muestra en
la salida.
i •'■iiy.ijjii j
»
0
!l
Semiamá 3
De yo vistazo
Acaba de terminar la segunda semana de aprendizaje de C++.
Para estos momentos debe estar familiarizado con algunos de
ios aspectos más avanzados de la programación orientada a
objetos, incluyendo la encapsulación y el polimorfismo.
Objetivos
Esta última semana regular empieza con una discusión sobre
la herencia avanzada. En el día 16. “Flujos”, conocerá con
detalle los flujos, y en el día 17, “Espacios de nombres”,
aprenderá cómo trabajar con esta excitante adición al estándar
de C++. El día 18, “Análisis y diseño orientados a objetos",
es una partida: en lugar de enfocarse en la sintaxis del lengua
je, tomará un día de descanso para conocer el análisis y el
diseño orientados a objetos. En el día 19, “Plantillas", se pre
sentan las plantillas, y en el día 20, “Excepciones y manejo
de errores”, se explica lo que son las excepciones. El día 21,
“Qué sigue”, el último día regular de este libro, trata sobre
algunos temas variados que no se cubren en niguna otra parte,
y luego hay una explicación sobre los siguientes pasos a
seguir para convertirse en un gurú de C++.
;> Allí
. - ■
' ' .
í>-■’
S emana 3
D ía 15
Herencia avanzada
Hasta ahora, ha trabajado con herencias simple y múltiple para crear relaciones
del tipo es un. Hoy aprenderá lo siguiente:
• Qué es la contención y cómo modelarla
• Qué es la delegación y cómo modelarla
• Cómo implementar una clase con base en otra
• Cómo utilizar la herencia privada
Contención
Como ha visto en ejemplos anteriores, es posible que los datos miembro de
una clase incluyan objetos de otra clase. Los programadores de C++ dicen
que la clase externa contiene a la clase interna. Por lo tanto, una clase llamada
Empleado podría contener objetos tales como cadenas (para el nombre del
empleado) y enteros (para el salario del empleado, y así sucesivamente).
El listado 1 5 . 1 d escrib e una clase Cadena incom pleta, pero útil. Este listado no
produce n inguna salida; en lugar de eso, se utilizará con listados posteriores.
502 Día 15
49 suCadena( i ] = '\0';
50 suLongitud = longitud;
51 // cout << ”\tConstructor de Cadena(int)\n";
52 // ConstructorCuenta++;
53
54
55 // Convierte un arreglo de caracteres en una Cadena
56 Cadena::Cadena(const char * const cCadena)
57 {
58 suLongitud = strlen(cCadena);
59 suCadena = new char[ suLongitud+1 ];
60 for (int i = 0; i < suLongitud; i++)
61 suCadena[ i ] = cCadena( i ];
62 suCadena[ suLongitud ] = '\0';
63 // cout « "\tConstructor de Cadena(char *) constructor\n°;
64 // ConstructorCuenta++;
65
66
67 // constructor de copia
68 Cadena::Cadena (const Cadena & rhs)
69 {
70 suLongitud = rhs.ObtenerLongitud();
71 suCadena = new char[ suLongitud+1 ];
72 for (int i = 0; i < suLongitud; i++)
73 suCadena[ i ] = rhs[ i ];
74 suCadena[ suLongitud ] = *\0';
75 // cout « "\tConstructor de Cadena(Cadena &)\n";
76 // ConstructorCuenta++;
77
78
79 // destructor, libera la memoria asignada
80 Cadena::-Cadena ()
81 {
82 delete [] suCadena;
83 suLongitud = 0;
84 // cout « "\tDestructor de Cadena\n";
85 }
86
87 // operador igual a, libera la memoria existente
88 // y luego copia la cadena y el tamaño
89 Cadena& Cadena::operator=(const Cadena & rhs)
90 {
91 if (this == &rhs)
92 return *this;
93 delete [] suCadena;
94 suLongitud = rhs.ObtenerLongitudO;
95 suCadena = new char[ suLongitud+1 ];
96 for (int i = 0; i < suLongitud; i++)
97 suCadena[ i ] = rhs[ i ];
continúa
504 Día 15
L is t a d o 15.1 continuación
147:
148: for (i = 0; i < suLongitud; i++)
149: tempi i ] = suCadena[ i ];
150: for (j =0; j < rhs.ObtenerLongitud(); j++, i++)
151 : tempi i ] = rhsl i • suLongitud ];
152: tempi longitudTotal ] = '\0';
153: ‘this = temp;
154:
155:
156: // int Cadena ::ConstructorCuenta = 0;
S alida Ninguna.
El listado 15.1 proporciona una clase Cadena que es muy similar a la que se utili
A nálisis
za en el listado 12.12 del día 12, “Arreglos, cadenas tipo C y listas enlazadas“. La
diferencia considerable aquí es que los constructores y unas cuantas funciones del listado
12 .12 tienen instrucciones de impresión para mostrar su uso, las cuales se dejan como
comentarios en el listado 15.1. Estas funciones se utilizarán en ejemplos posteriores.
En la línea 25 se declara la variable miembro estática ConstructorCuenta, y se inicializa
en la línea 156. Esta variable se incrementa en cada constructor de cadena. Todo esto
está actualmente como comentarios, los cuales se utilizarán en un listado posterior.
El listado 15.2 describe una clase Empleado que contiene tres objetos de tipo cadena.
L is t a d o 1 5 .2 continuación
73: in t main()
74: {
75:
76:
Empleado E d i e ( " J a n e " , "Doe", "1461 Shore Parkway", 20000);
E d i e . A s i g n a r S a l a r i o (50000);
15
77: Cadena c A p e l l i d o ( " L e v i n e " );
78: E d i e .A sig n a rA p e 11ido(cApe11i d o ) ;
79: E d i e .AsignarPrim erNombre( nEdythe");
80: cout << “Nombre:
81: cout << Edie.ObtenerPrimerNombre() .ObtenerCadena();
82: cout < < “ “ << Ed ie.O bten erApe llido () .ObtenerCadena();
83: cout << " . \ n D ir e c c i ó n :
84: cout << E d ie .O b te n e rD ire c c io n {) . ObtenerCadena( );
85: cout << ”. \ n S a l a r i o : " ;
86: cout << E d i e .O b t e n e r s a la r io ();
87: cout << endl;
88: return 0;
89: }
El listado 15.2 muestra la clase Empleado, la cual contiene tres objetos de tipo
A nálisis
cadena: suPrimerNombre, s u A p e l l i d o y suD ireccion.
En la línea 75 se crea un objeto Empleado, y se pasan cuatro valores para inicializarlo. En
la línea 76 se llama a la función de acceso A s i g n a r S a l a r i o ( ) de Empleado, con el valor
constante 50000. Hay que tener en cuenta que en un programa real, esto sería un valor di
námico (establecido en tiempo de ejecución) o una constante.
En la linca 77 se crea una cadena y se inicializa con una cadena constante de C++. En la
línea 78, este objeto de tipo cadena se utiliza como argumento para A s i g n a r A p e l l i d o ().
En la línea 79 se llama a la función AsignarPrimerNombre () de Empleado con otra cadena
constante. Sin embargo, si pone mucha atención, observará que Empleado no tiene una
508 Día 15
El costo de la contención
Es importante observar que el usuario de la clase Empleado paga el precio por cada uno
de esos objetos de tipo cadena cada vez que se construye uno o que se crea una copia de
Empleado.
Si se quitan las marcas de comentarios de las instrucciones cout del listado 15.1, líneas
38, 51,63, 75, 84 y 99, se revela la frecuencia con que se llaman. El listado 15.3 modi
fica el programa controlador agregando instrucciones cout para indicar en qué parte del
programa se crean los objetos.
Herencia avanzada 509
Para com pilar este listado, quite las marcas de com entario de las líneas 38, 51,
Nota 63, 75, 84 y 99 del listado 15.1.
«as
L istado 1 5 . 3 continuación
42: suSalario(O)
43: {>
44:
45: Empleado:¡Empleado(char * primerNombre, char * apellido,
46: char * dirección, long salario):
47: suPrimerNombre(primerNombre),
48: suApellido(apellido),
49: suDireccion(dirección),
50: suSalario(salario)
51: {}
52:
53: Empleado:¡Empleado(const Empleado & rhs):
54: suPrimerNombre(rhs.ObtenerPrimerNombre ()),
55: suApellido(rhs.ObtenerApellido()),
56: suDireccion(rhs.ObtenerDireccion()),
57: suSalario(rhs.ObtenerSalario())
58: {}
59:
60: Empleado:¡-Empleado() {}
61:
62: Empleado & Empleado::operator= (const Empleado & rhs)
63: {
64: if (this == &rhs)
65: return *this;
66: suPrimerNombre = rhs.ObtenerPrimerNombre();
67: suApellido = rhs.ObtenerApellido();
68: suDireccion = rhs.ObtenerDireccion();
69: suSalario = rhs.ObtenerSalario();
70: return *this;
71: }
72:
73: int main()
74: {
75: cout « "Creando a Edie...\n";
76: Empleado Edie("Jane", "Doe", "1461 Shore Parkway", 20000);
77: Edie.AsignarSalario(20000);
78: cout « "Llamando a AsignarPrimerNombre con char *...\n";
79: Edie.AsignarPrimerNombre("Edythe");
80: cout « "Creando cadena cApellido temporal...\n";
81: Cadena cApellido("Levine");
82: Edie.AsignarApellido(cApellido);
83: cout « "Nombre: ";
84: cout « Edie.ObtenerPrimerNombre() .ObtenerCadena();
85: cout « " " « Edie.ObtenerApellido() ,ObtenerCadena();
86: cout « "\nDirección:
87: cout « Edie.ObtenerDireccion().ObtenerCadena();
88: cout « "\nSalario: 11 ;
89: cout « Edie.ObtenerSalario();
90: cout « endl;
91: return 0;
92: }
H e r e n c ia a v a n z a d a 511
1: Creando a E d i e . . .
2: C onstructor de Cadena(char *) constructor
3: C onstructor de Cadena{char *) constructor
4: C onstructor de Cadena{char *) constructor
5: Llamando a AsictnarPrímerNómbre con char * . . .
6: C on structor de Cadena(char *) constructor
7: Operador = de Cadena
8 : D e stru cto r de Cadena
9: Creando cadena cApellido temporal...
10 C onstructor de Cadena(char *} constructor
11 Operador = de Cadena
12 Nombre: Edythe Levine
13 D ire cció n : 1461 Shore Parkway
14 S a l a r i o ; 20000
15 D estructor de Cadena
16 D e stru ctor de Cadena
17 D e stru ctor de Cadena
18 D e stru ctor de Cadena
El listado 15.3 utiliza las mismas declaraciones de clases que los listados 15.1 y
15.2. Sin embargo, se les han quitado las marcas de comentario a las instrucciones
cout. La salida del listado 15.3 se ha numerado para que el análisis sea más claro.
En la línea 75 de! listado 15.3 se imprime el enunciado Creando a Edie. . como se
ve en la línea 1 de la salida. En la línea 76 se crea un objeto Empleado llamado Edie que
tiene cuatro parámetros. La salida refleja que el constructor para Cadena se llama tres
veces, como era de esperarse.
La línea 78 imprime un enunciado informativo, y en la línea 79 se encuentra la instruc
ción E d i e .A s ig n a r P r im e r N o m b r e ( "E d y t h e " ). Esta instrucción ocasiona que se cree una
cadena temporal a partir de la cadena de caracteres "Edythe", como se refleja en las
líneas 6 y 8 de la salida. Observe que la cadena temporal se destruye inmediatamente
después de utilizarla en la instrucción de asignación.
En la línea 81 se crea un objeto de tipo Cadena en el cuerpo del programa. Aquí el pro
gramador está haciendo en forma explícita lo que el compilador hizo en forma implícita
en la instrucción anterior. Esta vez se ve el constructor en la línea 10 de la salida, pero
no se ve el destructor. Este objeto se destruirá hasta que quede fuera de alcance al final
de la función.
Al ejecutarse la instrucción retu rn 0 de la línea 91, se destruyen las cadenas del objeto
Empleado al quedar éste fuera de alcance, y la cadena cApellido que se creó en la línea
81 también se destruye por quedar fuera de alcance.
El análisis del listado 15.1 muestra que ConstructorCuenta se incrementa cada vez que
se llama a un constructor de cadena. El programa controlador del listado 15.4 llama a las
funciones de impresión pasando el objeto Empleado, primero por referencia y luego por
valor. ConstructorCuenta mantiene la cuenta de cuántos objetos tipo cadena se crean
cuando el empleado se pasa como parámetro.
Para compilar este listado, deje las líneas a las qu e q u itó las marcas de co
Nota mentario en el listado 15.1 para ejecutar el listado 15.3, y tam bién quite las
marcas de com entario a las líneas 25, 39, 52, 64, 76 y 156 del listado 15.1.
34 Cadena suDireccion;
35 long suSalario;
36
37
38 Empleado::Empleado():
39 suPrimerNombre(""),
40 suApellido(““),
41 suDireccion("“),
42 suSalario(0)
43
44
45 Empleado::Empleado(char * primerNombre, char * apellido,
46 char * dirección, long salario):
47 suPrimerNombre(primerNombre),
48 suApellido(apellido),
49 suDireccion(direccion),
50 suSalario(salario)
51
52
53 Empleado::Empleado(const Empleado & rhs):
54 suPrimerNombre(rhs.ObtenerPrimerNombre()),
55 suApellido(rhs.ObtenerApellido()),
56 suDireccion(rhs.ObtenerDireccion()),
57 suSalario(rhs.ObtenerSalario())
58
59
60 Empleado::-Empleado() {}
61
62 Empleado & Empleado::operator= (const Empleado & rhs)
63 {
64 if (this == &rhs)
65 return *this;
66 suPrimerNombre = rhs.ObtenerPrimerNombre();
67 suApellido = rhs.ObtenerApellidoO;
68 suDireccion = rhs.ObtenerDireccion();
69 suSalario = rhs.ObtenerSalario();
70 return *this;
71
72
73 void FuncImpr(Empleado);
74
75 void rFuncImpr(const Empleado&);
76
77 int main()
78 {
79 Empleado Edie("Jane", "Doe", "1461 Shore Parkway", 20000);
80 Edie.AsignarSalario(20000);
81 Edie.AsignarPrimerNombre("Edythe");
82 Cadena cApellido("Levine");
83 Edie.AsignarApellido(cApellido);
continúa
514 Día 15
L istado 15.4 c o n t in u a c ió n
I m p l e m e n t a c i ó n c o n b a s e e n la
h e r e n c ia / c o n t e n c ió n e n c o m p a r a c ió n
c o n la d e l e g a c i ó n
Algunas veces una clase necesita algunos de los atributos de otra clase. Por ejemplo, su
ponga que necesita crear una clase llamada CatalogoPiezas. La especificación que dio
define a CatalogoPiezas como una colección de piezas; cada pieza tiene un número de
pieza único. CatalogoPiezas no permite entradas duplicadas y permite el acceso mediante
el número de pieza.
El listado del repaso de la semana 2 proporciona una clase llamada ListaPiezas. Ya está
comprobado y bien entendido el funcionamiento de ListaPiezas, por lo que puede apo
yarse en esa tecnología al crear su CatalogoPiezas, en lugar de crear esta clase desde
cero. La reutilización es una de las maneras más productivas de programar, es decir,
puede basarse en lo que ya tiene.
Podría crear una nueva clase CatalogoPiezas y hacer que contenga a ListaPiezas. Cata
logoPiezas podría delegar el manejo de la lista enlazada a la clase contenida ListaPiezas.
Una alternativa sería hacer que CatalogoPiezas se derivara de ListaPiezas y que, por
consiguiente, heredara las propiedades de ListaPiezas. No obstante, si recuerda que
la herencia pública proporciona una relación del tipo es un, debería preguntarse si
CatalogoPiezas es realmente un tipo de ListaPiezas.
Una manera de responder a la pregunta de si CatalogoPiezas es una ListaPiezas sería
asumir que ListaPiezas es la base y CatalogoPiezas es la clase derivada, y luego hacer
estas otras preguntas:
1. ¿Hay algo en la clase base que no deba estar en la clase derivada? Por ejemplo,
¿tiene la clase base ListaPiezas funciones que sean inapropiadas para la clase
CatalogoPiezas? De ser así, probablemente no sea conveniente la herencia
pública.
2. ¿Podría la clase que usted está creando tener más de una clase base? Por ejemplo,
¿podría CatalogoPiezas necesitar dos clases ListaPiezas en cada objeto? De ser
así, sería muy conveniente utilizar la contención.
3. ¿Necesita heredar de la clase base para poder aprovechar las funciones virtuales
o los miembros de acceso protegido? De ser así, debe utilizar herencia, pública o
privada.
Con base en las respuestas a estas preguntas, debe elegir ya sea entre herencia pública
(la relación de tipo es un), herencia privada (lo que se explica más adelante en este día) o
contención.
• Contención: La manera de declarar una clase como miembro de otra clase que es
contenida por esa clase.
• Delegación: Uso de los atributos de una clase contenida para lograr funciones que
no están disponibles de otra forma para la clase contenedora.
• Implementación con base en: Construir una clase con base en las capacidades de
otra, sin utilizar herencia pública.
Delegación
¿Por qué no derivar CatalogoPiezas de ListaPiezas? CatalogoPiezas no es una
ListaPiezas porque los objetos ListaPiezas son colecciones ordenadas, y cada miem
bro de la colección se puede repetir. CatalogoPiezas tiene entradas únicas que no están
ordenadas. El quinto miembro de CatalogoPiezas no es el número de pieza 5.
Evidentemente, hubiera sido posible heredar públicamente de ListaPiezas y luego rede
finir Insertar() y los operadores de desplazamiento ([ ]) para hacer lo correcto, pero
entonces hubiera cambiado la esencia de la clase ListaPiezas. En vez de esto, puede
crear una clase CatalogoPiezas que no tenga operador de desplazamiento, que no per
mita duplicados y que defina a ope rato r+ para combinar dos conjuntos.
La primera forma de lograr esto es con la contención. CatalogoPiezas delegará el manejo
de la lista a una ListaPiezas contenida. El listado 15.5 ejemplifica este método.
Herencia avanzada
47 suAnioModelo(anio),
48 Pieza(numeroPieza)
49
50
51
52 I I **************** pi9za de Aeroplano ************
53 class PiezaAeroPlano : public Pieza
54 {
55 public:
56 PiezaAeroPlano() : suNumeroMotor(l){};
57 PiezaAeroPlano(int NumeroMotor, int NumeroPieza);
58 virtual void Desplegar() const
59 {
60 Pieza::Desplegar();
61 cout « “Motor número:
62 cout « suNumeroMotor « endl;
63 }
64 private:
65 int suNumeroMotor;
66 };
67
68 PiezaAeroPlano: ¡PiezaAeroPlano (int NumeroMotor, int NumeroPieza):
69 suNumeroMotor(NumeroMotor),
70 Pieza(NumeroPieza)
71
72
73 I I **************** Nodo Pieza ************
74 class NodoPieza
75 {
76 public:
77 NodoPieza(Pieza *);
78 -NodoPieza();
79 void AsignarSiguiente(NodoPieza * nodo)
80 { suSiguiente = nodo; }
81 NodoPieza * ObtenerSiguiente() const;
82 Pieza * ObtenerPieza() const;
83 private:
84 Pieza * suPieza;
85 NodoPieza * suSiguiente;
86
87
88 // Implementaciones de NodoPieza...
89 NodoPieza::NodoPieza(Pieza * apPieza):
90 suPieza(apPieza),
91 suSiguiente(0)
92
93
94 NodoPieza::-NodoPieza()
Herencia avanzada
95: {
96: delete suPieza;
97: suPieza = NULL;
98: delete suSiguiente;
99: suSiguiente = NULL;
100
101
102 // Regresa NULL si no hay NodoPieza siguiente
103 NodoPieza * NodoPieza::ObtenerSiguiente() const
104 {
105 return suSiguiente;
106 }
107
108 Pieza * NodoPieza::ObtenerPieza() const
109 {
110 if (suPieza)
111 return suPieza;
112 else
113 return NULL; //error
114 }
115
116 I I **************** de Piezas ************
117 class ListaPiezas
118 {
119 public:
120 ListaPiezas();
121 -ListaPiezas();
122 // ¡necesita constructor de copia y operador igual a!
123 void Iterar(void (Pieza::*f) () const) const;
124 Pieza * Encontrar(int & posición, int NumeroPieza) const;
125 Pieza * ObtenerPrimero() const;
126 void Insertar(Pieza *);
127 Pieza * operator[](int) const;
128 int ObtenerCuenta() const
129 { return suCuenta; }
130 static ListaPiezas& ObtenerListaPiezasGlobalO
131 { return ListaPiezasGlobal; }
132 private:
133 NodoPieza * apCabeza;
134 int suCuenta;
135 static ListaPiezas ListaPiezasGlobal;
136 >;
137
138 ListaPiezas ListaPiezas:¡ListaPiezasGlobal;
139
140 ListaPiezas::ListaPiezas():
141 apCabeza(0),
142 suCuenta(0)
143 {}
continúa
L istado 15.5 continuación
144:
145: ListaPiezas::-ListaPiezas()
146: {
147: delete apCabeza;
148: } *
149:
150: Pieza* ListaPiezas::ObtenerPrimero() const
151: {
152: if (apCabeza)
153: return apCabeza->ObtenerPieza();
154: else
155: return NULL; // atrapar error aqui
156: }
157:
158: Pieza * ListaPiezas: :operator[ ](int desplazamiento) const
159: {
160: NodoPieza * apNodo = apCabeza;
161:
162: if (!apCabeza)
163: return NULL; // atrapar error aqui
164: if (desplazamiento > suCuenta)
165: return NULL; // error
166: for (int i = 0; i < desplazamiento; i++)
167: apNodo = apNodo->ObtenerSiguiente();
168: return apNodo->ObtenerPieza();
169: }
170:
171: Pieza* ListaPiezas::Encontrar(int & posición, int NumeroPieza) const
172: {
173: NodoPieza * apNodo = NULL;
174:
175: for (apNodo = apCabeza, posición = 0;
176: apNodo!=NULL;
177: apNodo = apNodo->ObtenerSiguiente(), posicion++)
178: {
179: if (apNodo->ObtenerPieza()->ObtenerNumeroPieza() == NumeroPieza)
180: break;
181: }
182: if (apNodo == NULL)
183: return NULL;
184: else
185: return apNodo->ObtenerPieza();
186: >
187:
Herencia avanzada 521
continúa
524 Día 15
319: else
320: {
321: cout « “¿Número de motor?:
322: cin » valor;
323: apPieza = new PiezaAeroPlano(valor, NumeroPieza);
324: }
325: cp.Insertar(apPieza);
326: }
327: cp.MostrarTodo();
328: return 0;
329: }
A nálisis El listado 15.5 reproduce las clases Pieza, NodoPieza y ListaPiezas del repaso
de la semana 2.
En las líneas 243 a 254 se declara una nueva clase llamada CatalogoPiezas. Catalogo-
Piezas tiene una clase ListaPiezas como dato miembro, a la que delega el manejo
Herencia avanzada 525
de la lista. Otra forma de decir esto es que CatalogoPiezas se implementa con base en
ListaPiezas.
Observe que los clientes de CatalogoPiezas no tienen acceso directo a ListaPiezas. La
interfaz es por medio de CatalogoPiezas, y como tal, el comportamiento de ListaPiezas
cambia dramáticamente. Por ejemplo, el método CatalogoPiezas::Insertar() no per
mite entradas duplicadas en ListaPiezas.
La implementación de CatalogoPiezas:: Insertar() empieza en la línea 256. A la Pieza
que se pasa como parámetro se le pide el valor de la variable miembro suNumeroPieza.
Este valor se proporciona para el método Encontrar() de ListaPiezas, y si no se encuen
tra ese valor, se inserta el número de pieza; si se encuentra el valor, se imprime un mensaje
informativo de error.
H e r e n c ia p r i v a d a
Si CatalogoPiezas necesitara tener acceso a los miembros protegidos de ListaPiezas
(en este caso no existe ninguno), o necesitara redefinir cualquiera de los métodos de
ListaPiezas, CatalogoPiezas estaría obligada a heredar de ListaPiezas.
Pero como los objetos de la clase CatalogoPiezas no son objetos de la clase ListaPiezas,
y como usted no quiere exponer todo el conjunto de funcionalidad de ListaPiezas a los
clientes de CatalogoPiezas, necesita usar herencia privada.
Lo primero que hay que saber acerca de la herencia privada es que todas las variables y
funciones miembro de la clase base se tratan como si fueran declaradas como privadas,
sin importar su nivel de acceso actual en la clase base. Por lo tanto, para cualquier función
que no sea miembro de CatalogoPiezas, son inaccesibles todas las funciones heredades
de ListaPiezas. Esto es crucial: la herencia privada no involucra la interfaz heredada, sólo
la implementación.
526 Día 15
36 {
37 Pieza::Desplegar();
38 cout « "Año del modelo:
39 cout « suAnioModelo « endl;
40 }
41 private:
42 int suAnioModelo;
43
44
45 PiezaAuto: :PiezaAuto(int anio, int numeroPieza):
46 suAnioModelo(anio),
47 Pieza(numeroPieza)
48 {}
49
50 I j **************** PÍ023 cIq AeroPlano ************
51 class PiezaAeroPlano : public Pieza
52
53 public:
54 PiezaAeroPlano() : suNumeroMotor(1) {};
55 PiezaAeroPlano(int NumeroMotor, int NumeroPieza);
56 virtual void Desplegar() const
57 {
58 Pieza::Desplegar();
59 cout « “Motor número: ";
60 cout « suNumeroMotor « endl;
61 }
62 private:
63 int suNumeroMotor;
64
65
66 PiezaAeroPlano::PiezaAeroPlano (int NumeroMotor, int NumeroPieza):
67 suNumeroMotor(NumeroMotor),
68 Pieza(NumeroPieza)
69 {}
70
71 I I **************** [\iodo Pieza ************
72 class NodoPieza
73 {
74 public:
75 NodoPieza(Pieza *);
76 -NodoPieza();
77 void AsignarSiguiente(NodoPieza * nodo)
78 { suSiguiente = nodo; }
79 NodoPieza * ObtenerSiguiente() const;
80 Pieza * ObtenerPieza() const;
81 private:
82 Pieza * suPieza;
83 NodoPieza * suSiguiente;
continúa
L istado 15.6 continuación
84:
85:
86: // Implementaciones de NodoPieza...
87: NodoPieza::NodoPieza(Pieza * apPieza):
88: suPieza(apPieza),
89: suSiguiente(0)
90: {>
91:
92: NodoPieza::-NodoPieza()
93: {
94: delete suPieza; '
95: suPieza = NULL;
96: delete suSiguiente;
97: suSiguiente = NULL;
98: >
99:
100 // Regresa NULL si no hay NodoPieza siguiente
101 NodoPieza * NodoPieza::ObtenerSiguiente() const
102 {
103 return suSiguiente;
104 }
105
106 Pieza * NodoPieza::ObtenerPieza() const
107 {
108 if (suPieza)
109 return suPieza;
110 else
111 return NULL; //error
112 }
113
114 // **************** Lista de Piezas ************
115 class ListaPiezas
116 {
117 public:
118 ListaPiezas();
119 -ListaPiezas();
120 // ¡necesita constructor de copia y operador igual a!
121 void Iterar(void (Pieza::*f)() const) const;
122 Pieza * Encontrar(int & posición, int NumeroPieza) const;
123 Pieza * ObtenerPrimero() const;
124 void Insertar(Pieza *);
125 Pieza * operator!](int) const;
126 int ObtenerCuenta() const
127 { return suCuenta; >
128 static ListaPiezas& ObtenerListaPiezasGlobal()
129 { return ListaPiezasGlobal; }
130 private:
131 NodoPieza * apCabeza;
H e re n c ia a v a n z a d a 529
132 in t suCuenta;
133 s t a t i c L is t a P ie z a s L is t a P ie z a s G lo b a l;
134 1 5
135
136 L i s t a P ie z a s L i s t a P i e z a s : :ListaPiezasG lobal;
137
138 L i s t a P i e z a s : :L i s t a P i e z a s ( ) :
139 apCabeza(0) ,
140 suCuenta(0)
141
142
143 L ista P ie z a s::-L ista P ie z a s()
144 {
145 delete apCabeza;
146 }
147
148 Pieza* L i s t a P ie z a s : :ObtenerPrimero() const
149 {
150 if (apCabeza)
151 return apCabeza->ObtenerPieza();
152 else
153 return NULL; // atrapar error aqui
154 }
155
156 Pieza * L i s t a P i e z a s : : o p e r a t o r [ ] (in t desplazamiento) const
157 {
158 NodoPieza * apNodo = apCabeza;
159
160 if (¡apCabeza)
161 return NULL; // atrapar error aqui
162 i f (desplazamiento > suCuenta)
163 return NULL; // error
164 f o r ( i n t i = 0; i < desplazamiento; i++)
165 apNodo = apNodo->ObtenerSiguiente();
166 return apNodo->ObtenerPieza();
167
168
169 Pieza * L i s t a P i e z a s : :E n con tra r(in t & posición, int NumeroPieza) const
170 {
171 NodoPieza * apNodo = NULL;
172
173 f o r (apNodo = apCabeza, posición = 0;
174 apNodo!=NULL;
175 apNodo = apNodo->ObtenerSiguiente(), posicion++)
176 {
177 if (apNodo->ObtenerPieza() ->ObtenerNumeroPieza() == NumeroPieza)
178 b re a k ;
179
continua
L ista d o 15.6 continuación
277: {
278: int desplazamiento;
279:
280: Encontrar(desplazamiento.NumeroPieza) ;
281 : return desplazamiento;
282: }
283:
284: Pieza * CatalogoPiezas: ¡Obtener(int NumeroPieza)
285: {
286: int desplazamiento;
287:
288: return (Encontrar(desplazamiento, NumeroPieza));
289: }
290:
291 : int main()
292: {
293: CatalogoPiezas cp;
294: Pieza * apPieza = NULL;
295: int NumeroPieza;
296: int valor;
297: int opcion;
298:
I 299: while (1)
l 300: {
I 301 : cout « "(0)Salir (l)Auto (2)Avión: ";
I. 302: cin » opcion;
h
¡) 303: if (¡opcion)
ll 304: break;
I! 305:
¡» cout « "¿Nuevo NumeroPieza?: ";
n 306: cin » NumeroPieza;
ii 307: if (opcion == 1)
t»
n 308: {
ti 309: cout « "¿Año del modelo?: ";
!) » 310: cin » valor;
ü ' 311 : apPieza = new PiezaAuto(valor, NumeroPieza);
!'i 312: }
¡i 313: else
314: {
315: cout « "¿Número de motor?: ";
316: cin » valor;
317: apPieza = new PiezaAeroPlano(valor, NumeroPieza
318: }
319: cp.Insertar(apPieza);
320: }
321 : cp.MostrarTodoO ;
322: return 0;
323: }
Herencia avanzada 533
A nálisis El listado 15.6 muestra una interfaz cambiada para CatalogoPiezas y el progra
ma controlador modificado. Las interfaces para las otras clases permanecen sin
cambio, quedando igual que en el listado 15.5.
En la línea 240 del listado 15.6, CatalogoPiezas se declara para que se derive en forma
privada de ListaPiezas. La interfaz para CatalogoPiezas no cambia y queda igual que
en el listado 15.5, aunque, por supuesto, ya no necesita un objeto de tipo ListaPiezas
como dato miembro.
La función MostrarTodo () de CatalogoPiezas llama a Iterar() de ListaPiezas
con el apuntador apropiado a la función miembro de la clase Pieza. MostrarTodo()
actúa como interfaz pública para Iterar(), proporcionando la información correcta,
pero evitando que las clases cliente llamen a Iterar() en forma directa. Aunque
ListaPiezas podría permitir que se pasaran otras funciones a lterar(), CatalogoPiezas
no lo permitiría.
534 D í a 15
En resum en, cuando los métodos de CatalogoPiezas necesitan llamar a los métodos
de ListaPiezas, pueden hacerlo en forma directa. La única excepción es cuando
CatalogoPiezas haya redefinido el método y se necesite la versión de ListaPiezas, en
cuyo caso se debe identificar completamente el nombre de la función.
La herencia privada permite que CatalogoPiezas herede lo que puede utilizar, pero aún
proporciona un acceso controlado al método Insertar() (de ListaPiezas) y a otros
métodos a los que las clases cliente no deben tener acceso directo.
D ebe N O DEBE
DEBE utilizar herencia pública cuando los NO DEBE utilizar herencia privada cuando
objetos de la clase derivada sean del tipo necesite utilizar más de una instancia de la
de la clase base. clase base. Debe usar la contención. Por
DEBE utilizar la contención cuando quiera ejemplo, si CatalogoPiezas necesitara dos
delegar funcionalidad a otra clase, pero instancias de L ista P ie z a s, no tendría que
no necesite acceso a sus miembros pro usar herencia privada.
tegidos. NO DEBE usar herencia pública cuando los
DEBE utilizar herencia privada cuando clientes de la clase derivada no deban tener
necesite implementar una clase con base acceso a los miembros de la clase base.
en otra, y necesite acceso a los miembros
protegidos de la clase base.
Clases amigas
Algunas veces se crean varias clases, como un conjunto. Por ejem plo, NodoPieza
y L i s t a P i e z a s estaban estrechamente acopladas, y hubiera sido conveniente que
L i s t a P i e z a s leyera de manera directa el apuntador a una Pieza de N o d o P ie z a llamado
s u P ie z a .
Después de que NodoPieza declara a ListaPiezas como amiga, todos los datos y funcio
nes miembro de NodoPieza son públicos, en lo que a ListaPiezas respecta.
Es importante observar que la amistad no se puede transferir. El hecho de que usted sea
mi amigo y que Juan sea su amigo, no significa que Juan sea mi amigo. La amistad tam
poco se hereda. De nuevo, el hecho de que usted sea mi amigo y yo comparta mis secre
tos con usted, no significa que esté dispuesto a compartir mis secretos con sus hijos.
Por último, la amistad no es conmutativa. Asignar la Clase Uno como amiga de la Clase
Dos, no hace que la Clase Dos sea amiga de la Clase Uno. Tal vez usted quiera decirme
sus secretos, pero eso no significa que yo quiera decirle los míos.
El listado 15.7 muestra un ejemplo de la amistad modificando el ejemplo del listado 15.6,
convirtiendo a ListaPiezas en amiga de NodoPieza. Observe que esto no hace que
NodoPieza sea amiga de ListaPiezas.
29:
20 * j j ****************Pieza ctG AlJ*tO ****** ***** ★
31: class PiezaAuto :public Pieza
32: {
33: public:
34: PiezaAuto() : suAnioModelo(94) {}
35: PiezaAuto(int anio, int numeroPieza);
36: virtual void Desplegar() const
37: {
38: Pieza::Desplegar();
39: cout « "Año del modelo: ";
40: cout « suAnioModelo « endl;
41: }
42: private:
43: int suAnioModelo;
44: };
45:
46: PiezaAuto:¡PiezaAuto(int anio, int numeroPieza):
47: suAnioModelo(anio),
48: Pieza(numeroPieza)
49: {}
50:
51: // **************** pieza de Aeroplano ************
52: class PiezaAeroPlano : public Pieza
53: {
54: public:
55: PiezaAeroPlano() : suNumeroMotor(1) {};
56: PiezaAeroPlano(int NumeroMotor, int NumeroPieza);
57: virtual void Desplegar() const
58: {
59: Pieza::Desplegar();
60: cout « "Motor número: ";
61: cout « suNumeroMotor « endl;
62: }
63: private:
64: int suNumeroMotor;
65: };
66:
67: PiezaAeroPlano:¡PiezaAeroPlano(int NumeroMotor, int NumeroPieza):
68 : suNumeroMotor(NumeroMotor),
69: Pieza(NumeroPieza)
70: {}
71:
721 ¡ i **************** Nodo dG Pieza ************
73: class NodoPieza
74: {
Herencia avanzada 537
75: public:
76: friend class ListaPiezas;
77: NodoPieza (Pieza *);
78: -NodoPieza();
79: void AsignarSiguiente(NodoPieza * nodo)
80: { suSiguiente = nodo; }
81: NodoPieza * ObtenerSiguiente() const;
82: Pieza * ObtenerPieza() const;
83: private:
84: Pieza * suPieza;
85: NodoPieza * suSiguiente;
86:
87:
88: NodoPieza::NodoPieza(Pieza * apPieza):
89: suPieza(apPieza),
90: suSiguiente(0)
91: {}
92:
93: NodoPieza::-NodoPieza()
94: {
95: delete suPieza;
96: suPieza = NULL;
97: delete suSiguiente;
98: suSiguiente = NULL;
99: }
100:
101: // Regresa NULL si no hay NodoPieza siguiente
102: NodoPieza * NodoPieza::ObtenerSiguiente() const
103: {
104: return suSiguiente;
105: }
106:
107: Pieza * NodoPieza::ObtenerPieza() const
108: {
109: if (suPieza)
110: return suPieza;
111: else
112: return NULL; //error
113:
114:
115: jj **************** Lista de Piezas ************
116: class ListaPiezas
117: {
118: public:
119: ListaPiezas();
120: -ListaPiezas();
continúa
538 Día 15
145:
t) , {
b
t| ; 146: delete apCabeza;
11 147: }
0: 148:
n 149: Pieza* ListaPiezas::ObtenerPrimero() const
u
ti 150: {
H
h l 151: if (apCabeza)
w. * 152:
1]4kP
return apCabeza->suPieza;
153: else
Ü1
!¡ ► 154: return NULL; // atrapar error aquí
¡i f 155: >
156:
157: Pieza * ListaPiezas::operator! ](int desplazamiento) const
158: {
159: NodoPieza * apNodo = apCabeza;
160:
161: if (¡apCabeza)
162: return NULL; // atrapar error aquí
163: if (desplazamiento > suCuenta)
164: return NULL; // error
165: for (int i = 0; i < desplazamiento; i++)
166: apNodo = apNodo->suSiguiente;
Herencia avanzada
continúa
540 Día 15
J
Herencia avanzada 541
259 else
260 {
261 cout << numeroPieza « “ fue la
262 switch (desplazamiento)
263 {
264 case 0: cout « “primera
265 break;
266 case 1: cout « "segunda
267 break;
268 case 2: cout « "tercera
269 break;
270 default: cout « desplazamiento+1 « "a 0;
271 }
272 cout « "entrada. ¡Rechazada!\n";
273 }
274 }
275
276 int CatalogoPiezas::Existe(int NumeroPieza)
277 {
278 int desplazamiento;
279
280 Encontrar(desplazamiento, NumeroPieza);
281 return desplazamiento;
282
283
284 Pieza * CatalogoPiezas: :Obtener(int NumeroPieza)
285 {
286 int desplazamiento;
287
288 return (Encontrar(desplazamiento, NumeroPieza));
289
290
291
292 int main()
293 {
294 CatalogoPiezas cp;
295 Pieza * apPieza = NULL;
296 int NumeroPieza;
297 int valor;
298 int opcion;
299
300 while (1)
301 {
302 cout « "(0)Salir (l)Auto (2)Avión: ";
303 cin » opcion;
304 if (¡opcion)
continúa
L istado 15.7 continuación
305: break;
306: coût « “¿Nuevo NumeroPieza?:
307: cin » NumeroPieza;
308: if (opcion == 1)
309: {
310: cout « “¿Año del modelo?:
311 : cin » valor;
312: apPieza = new PiezaAuto(valor, NumeroPieza);
313: }
314: else
315: {
316: cout « "¿Número de motor?:
317: cin » valor;
318: apPieza = new PiezaAeroPlano(valor, NumeroPieza);
319: }
320: cp.Insertar(apPieza);
321 : }
322: cp.MostrarTodo();
323: return 0;
324: }
325:
En este listado se coloca la declaración friend en la sección pública, pero esto no es nece
sario; se puede colocar en cualquier parte de la declaración de la clase sin cambiar el sig
nificado de la instrucción. Debido a esta instrucción, todos los datos y funciones miembro
privados están disponibles para cualquier función miembro de la clase ListaPiezas.
En la línea 149, la implementación de la función miembro ObtenerPrimero() refleja este
cambio. En lugar de regresar apCabeza->ObtenerPieza, esta función ahora puede regre
sar el que de otra manera sería dato miembro privado escribiendo apCabeza->suPieza.
De manera similar, la función Insertar() ahora puede escribir apNodo->suSiguiente
= apCabeza, en lugar de escribir apNodo->AsignarSiguiente(apCabeza).
Es cierto que estos son cambios triviales, y no existe un buen motivo para hacer que
ListaPiezas sea amiga de NodoPieza, pero esto sí sirve para mostrar la forma en que
funciona la palabra reservada f riend.
Las declaraciones de clases f riend se deben usar con extrema precaución. Si dos clases
están entrelazadas de manera que es difícil separarlas, y una debe tener acceso a los datos
de la otra con frecuencia, puede existir un buen motivo para usar esta declaración. Pero
úsela con moderación; por lo general es igual de sencillo utilizar los métodos de acceso
público, y esto le permite cambiar una clase sin tener que volver a compilar la otra.
Clase amiga
Para declarar una clase como amiga de otra, se.coloca la palabra friend en la clase que
va a otorgar los derechos de acceso. Es decir, yo puedo declararlo a usted como mi amigo,
pero usted no puede declararse a usted mismo cómo mi amigo.
He aquí un ejemplo:
class NodoPieza
{
public:
// declara a ListaPiezas como amiga de NodoPieza
friend class ListaPiezas;
>;
F u n c io n e s a m ig a s
Algunas veces necesitará otorgar este nivel de acceso no a toda la clase, sino sólo a una
o dos funciones de esa clase. Puede hacer esto declarando a las funciones miembro de
la otra clase como amigas, en lugar de declarar a toda la clase como amiga. De hecho,
puede declarar a cualquier función, sea o no una función miembro de otra clase, como
una función amiga.
F u n c io n e s a m ig a s y s o b r e c a r g a d e o p e r a d o r e s
El listado 15.1 proporciona una clase Cadena que redefine a operator+. También propor
ciona un constructor que toma un apuntador a un carácter constante, para que se puedan
crear objetos de tipo cadena a partir de cadenas estilo C. Esto le permite crear una cade
na y agregarle una cadena estilo C.
Lo que no puede hacer es crear una cadena estilo C (una cadena de caracteres) y concate
narle un objeto de tipo cadena, como se muestra en el siguiente ejemplo:
char cCadenal] = {"¡Hola"};
Cadena sCadena(", mundo");
Cadena sCadenaDos = cCadena + sCadena; //¡error!
Las cadenas estilo C no tienen un operator+ sobrecargado. Como se dijo en el día 10,
“Funciones avanzadas”, al decir cCadena + sCadena; lo que realmente está llamando es
a cCadena.operator+(sCadena). Como no puede llamar a o p e r a to r + ( ) en una cadena
estilo C, esto produce un error en tiempo de compilación.
Puede solucionar este problema declarando una función f riend en Cadena, lo cual sobre
carga al operator+ pero toma dos objetos de tipo cadena. El constructor apropiado con
vertirá a la cadena estilo C en un objeto de tipo cadena, y luego se llamará a operator+
usando los dos objetos de tipo cadena. El listado 15.8 muestra el uso de un operador
friend.
5:
6:
7: // Clase cadena rudimentaria
8: class Cadena
9: {
10 public :
11 // constructores
12 Cadena();
13 Cadena(const char ‘const);
14 Cadena(const Cadena &);
15 -Cadena();
16 // operadores sobrecargados
17 char & operatori](int desplazamiento);
18 char operatori](int desplazamiento) const;
19 Cadena operator+(const Cadena &);
20 friend Cadena operator+(const Cadena &, const Cadena &);
21 void operator+=(const Cadena &);
22 Cadena & operator=(const Cadena &);
23 // Métodos generales de acceso
24 int ObtenerLongitud()const
25 { return suLongitud; }
26 const char * ObtenerCadena() const
27 { return suCadena; }
28 private :
29 Cadena (int); // constructor privado
30 char * suCadena;
31 unsigned short suLongitud;
32 };
33
34 // constructor predeterminado, crea una cadena de 0 bytes
35 Cadena::Cadena()
36 {
37 suCadena = new char[ 1 ];
38 suCadena! 0 ] = '\0 ';
39 suLongitud = 0;
40 // cout << "\tConstructor de cadena predeterminado^";
41 // ConstructorCuenta++;
42 }
43
44 // constructor privado (auxiliar), lo utilizan sólo
45 // los métodos de la clase para crear una nueva cadena del
46 // tamaño requerido. Se llena con caracteres nulos.
47 Cadena::Cadena(int longitud)
48 {
49 suCadena = new char[ longitud + 1 ];
50
51 for (int i = 0; i <= longitud; i++)
52 suCadena! i ] = '\0 ';
53 suLongitud = longitud;
54 // cout « 11\tConstructor de Cadena(int)\n";
55 // ConstructorCuenta++;
56
continúa
546 Día 15
L is t a d o 1 5 . 8 continuación
57:
58: // Convierte un arreglo de caracteres en una Cadena
59: Cadena::Cadena(const char * const cCadena)
60: {
61: suLongitud = strlen(cCadena);
62: suCadena = new char[ suLongitud + 1 ];
63: for (int i = 0; i < suLongitud; i++)
64: suCadena[ i ] = cCadena[ i ];
65: suCadena[ suLongitud ] = *\0';
66: // cout « "\tConstructor de Cadena(char *)\n";
67: // ConstructorCuenta++;
68: }
69:
70: // constructor de copia
71: Cadena:¡Cadena (const Cadena &rhs)
72: {
73: suLongitud = rhs.ObtenerLongitud();
74: suCadena = new char[ suLongitud + 1 ];
75: for (int i = 0; i < suLongitud; i++)
76: suCadena[ i ] = rhs[ i ];
77: suCadena[ suLongitud ] = '\0';
78: // cout « “\tConstructor de Cadena(Cadena&)\n";
79: // ConstructorCuenta++;
80: >
81:
82: // destructor, libera lamemoria asignada
83: Cadena:¡-Cadena ()
84: {
85: delete [] suCadena;
86: suLongitud = 0;
87: // cout « “UDestructor de Cadena\n";
88: >
89:
90: // operador igual a, libera la memoria existente
91: // luego copia la cadena y el tamaño
92: Cadena& Cadena::operator=(const Cadena & rhs)
93: {
94: if (this == &rhs)
95: return *this;
96: delete [] suCadena;
97: suLongitud = rhs.ObtenerLongitud();
98: suCadena = new chart suLongitud + 1 ];
99: for (int i = 0; i < suLongitud; i++)
100: suCadena[ i ] = rhs[ i ];
101: suCadenaf suLongitud ] = '\0';
102: return *this;
103: // cout « "NtOperador = de Cadena\n";
104: }
105:
Herencia avanzada 547
Observe que este operator+ no es una función miembro de ésta ni de ninguna otra clase.
Se declara dentro de la declaración de la clase Cadena sólo para que se pueda hacer
amiga, pero como se declara, no se necesita otro prototipo de función.
La implementación de este operator+ se encuentra en las líneas 145 a 158. Observe que
es similar al operator+ anterior, excepto que toma dos cadenas y tiene acceso a ellas a
través de sus métodos de acceso público.
El program a controlador muestra el uso de esta función en la línea 176, en donde
o p e r a t o r + ahora se llama para actuar sobre ¡una cadena estilo C!
H e re n c ia a v a n z a d a 549
F un c i o n e s a m i g a s
Para declarar una función como amiga se usa la palabra reservada f rie n d y luego la 15
especificación completa de la función. Declarar una función como amiga no le da a la fun
ción f rie n d acceso a su apuntador t h is , pero sí proporciona un acceso completo a todos
los datos y funciones miembro protegidos y privados.
He aquí un ejemplo:
c l a s s NodoPieza
{ II ...
II hacer que otra función miembro de la clase sea una amiga
f r i e n d void L i s t a P i e z a s : : In se rta r(P ie z a *);
// hacer que una función global sea amiga
f r i e n d in t UnaFuncion();
// . . .
};
Para lograr esto, debe redefinir a o p e r a t o r « ( ). El día 16, “Flujos”, presenta los detalles
del trabajo con io stre a m ; por ahora, el listado 15.9 muestra cómo se puede sobrecargar
o p erato r< < por m edio de una función frie n d .
L is t a d o 1 5 . 9 Sobre carg a de o p e r a t o r « ()
3: tfinclude <iostream.h>
4: tfinclude < s t r in g . h >
5:
6:
7: c l a s s Cadena
8: {
9: p u b lic :
continúa
550 Día 15
L is t a d o 1 5 . 9 continuación
10 // constructores
11 Cadena();
12 Cadena(const char * const);
13 Cadenajconst Cadena &);
14 -Cadena();
15 // operadores sobrecargados
16 char & operatori](int desplazamiento);
17 char operatori](int desplazamiento) const;
18 Cadena operator+(const Cadena &);
19 void operator+=(const Cadena &);
20 Cadena & operator=(const Cadena &);
21 friend ostream & operator«
22 (ostream & elFlujo, Cadena & laCadena);
23 // Métodos generales de acceso
24 int ObtenerLongitud()const
25 { return suLongitud; }
26 const char * ObtenerCadena() const
27 { return suCadena; }
28 private:
29 Cadena (int); // constructor privado
30 char * suCadena;
31 unsigned short suLongitud;
32
33
34 // constructor predeterminado, crea una cadena de 0 bytes
35 Cadena::Cadena()
36 {
37 suCadena = new char[ 1 ];
38 suCadena[ 0 ] = '\0';
39 suLongitud = 0;
40 // cout « "\tConstructor de cadena predeterminado^'1;
41 // ConstructorCuenta++;
42 }
43
44 // constructor privado (auxiliar), lo utilizan sólo
45 // los métodos de la clase para crear una nueva cadena del
46 // tamaño requerido. Se llena con caracteres nulos.
47 Cadena::Cadena(int longitud)
48 {
49 suCadena = new char[ longitud + 1 ];
50 for (int i = 0; i <= longitud; i++)
51 suCadena[ i ] = '\0‘;
52 suLongitud = longitud;
53 // cout « "\tConstructor de Cadena(int)\n";
54 // ConstructorCuenta++;
55 }
56
57 // Convierte un arreglo de caracteres en una Cadena
Herencia avanzada
107: // cambiar!
108: char & Cadena::operator[](int desplazamiento)
109: {
110: if (desplazamiento > suLongitud)
111 : return suCadena[ suLongitud - 1 ];
112: else
113: return suCadena[ desplazamiento ];
114: }
115:
116: // operador de desplazamiento constante para utilizar
117: // en objetos const (vea el constructor de copia)
118: char Cadena::operator[](int desplazamiento) const
119: {
120: if (desplazamiento > suLongitud)
121 : return suCadena[ suLongitud - 1 ];
122: else
123: return suCadena[ desplazamiento j;
124: }
125:
126: I I crea una nueva cadena al agregar la cadena
127: l l actual a rhs
128: Cadena Cadena::operator+(const Cadena & rhs)
129: {
130: int longitudTotal = suLongitud + rhs.ObtenerLongitud();
131 : Cadena temp(longitudTotal);
132: int i, j;
133:
134: for (i = 0; i < suLongitud; i++)
135: temp[ i ] = suCadena[ i ];
136: for (j = 0; j < rhs.ObtenerLongitud(); j++, i++)
137: temp[ i ] = rhs[ j ];
138: temp[ longitudTotal ] = '\0’;
139: return temp;
140: }
141 :
142: // cambia la cadena actual, no regresa nada
143: void Cadena::operator+=(const Cadena & rhs)
144: {
145: unsigned short rhsLong = rhs.ObtenerLongitud();
146: unsigned short longitudTotal = suLongitud + rhsLong;
147: Cadena temp(longitudTotal);
148: int i, j;
149:
150: for (i = 0; i < suLongitud; i++)
151 : tempi i ] = suCadena[ i ];
152: for (j = 0, i = 0; j < rhs.ObtenerLongitud(); j++, i++!
153: tempi i ] = rhs[ i - suLongitud ];
154: tempi longitudTotal ] = '\0‘;
155: *this = temp;
156: }
157:
158: // int Cadena::ConstructorCuenta =
Herencia avanzada 553
R e su m e n
Hoy vio cómo delegar la funcionalidad a un objeto de una clase contenida. También vio
cómo implementar una clase con base en otra mediante el uso de la contención o de la
herencia privada. La contención está restringida en cuanto a que la nueva clase no tiene
acceso a los miembros protegidos de la clase contenida, y no puede redefinir a las fun
ciones miembro de la clase contenida. La contención es más simple de usar que la heren
cia privada, y se debe utilizar siempre que sea posible.
También vio cómo declarar funciones y clases amigas. Usando una función amiga,
aprendió a sobrecargar el operador « , para permitir que sus clases utilicen cout de la
misma manera que lo hacen las clases integradas.
Recuerde que la herencia pública expresa una relación de tipo es un, la contención
expresa una relación de tipo tiene un, y la herencia privada expresa implementado con
base en. La relación se delega a se puede expresar ya sea por medio de la contención o
de la herencia privada, aunque es más común la contención.
P re g u n ta s y re sp u e sta s
P ¿Por qué es tan importante distinguir entre relaciones de tipo es u n , tiene un,
e im plem en tado con base en?
R El objetivo de C++ es implementar programas orientados a objetos bien diseñados.
Mantener estas relaciones como debe ser asegura que su diseño corresponda a la
realidad de lo que está modelando. Además, un diseño bien entendido tendrá una
mayor probabilidad de reflejarse en un código bien diseñado.
P ¿Por qué se prefiere la contención a la herencia privada?
R El reto en la programación moderna es hacer frente a la complejidad. Entre más
se puedan utilizar los objetos como si fueran cajas negras, habrá menos detalles
de los cuales preocuparse y se podrá manejar una mayor complejidad. Las clases
contenidas ocultan sus detalles; la herencia privada expone los detalles de imple-
mentación.
P ¿Por qué no hacer que todas las clases sean amigas de todas las clases que
utilizan?
R Hacer que una clase sea amiga de otra expone los detalles de implementación y
reduce la encapsulación. Lo ideal es mantener ocultos tantos detalles de cada clase
como sea posible.
P Si una función está sobrecargada, ¿es necesario declarar cada forma de la
función para que sea amiga?
R Sí, si sobrecarga una función y la declara como amiga de otra clase, debe declarar
f riend por cada forma en que quiera otorgar el acceso.
T a lle r
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del
material tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Cómo se establece una relación de tipo es un?
2. ¿Cómo se establece una relación de tipo tiene un?
3. ¿Cuál es la diferencia entre contención y delegación?
4. ¿Cuál es la diferencia entre delegación e implementación con base en?
5. ¿Qué es una función f riend?
H e re n c ia a v a n z a d a 555
6. ¿ Q u é es lina clase f r i e n d ?
7. Si P e r ro es am igo de Muchacho. ¿Muchacho es amigo de P e rro ?
15
8. Si P e r ro es am igo de Muchacho y T e r r i e r se deriva de Perro, ¿ T e r r i e r es am igo
de Muchacho?
9. Si P e r ro es a m ig o de Muchacho y Muchacho es amigo de Casa. ¿ P e r r o es am igo de
Casa?
10. ¿D ónde debe aparecer la declaración de una función f rie n d ?
Ejercicios
1. M uestre la declaración de una clase llamada Animal, que contenga un dato m iem
bro que sea un objeto de tipo cadena.
2. M uestre la declaración de una clase llamada A rre g lo Lim ita d o , que sea un arreglo.
3. M uestre la declaración de una clase llamada Conjunto, que se declare con base en
un arreglo.
4. M odifique el listado 15.1 para proporcionar a la clase Cadena un operador de in
serción (<<).
5. C A Z A E R R O R E S : ¿Qué está mal en este programa?
1: tfinclude <iostream.h>
2:
3: c l a s s Animal;
4:
5: void asigna rV alo r(A n im a l & , in t);
6:
7:
8: c l a s s Animal
9: {
10: p u b lic :
11: in t ObtenerPeso()const { return suPeso; }
12: in t ObtenerEdad() const { return suEdad; }
13: p riv a te :
14: in t suPeso;
15: i n t suEdad;
16: };
17:
18: void asigna rV alo r(An im a l &elAnimal, int elPeso)
19: {
20: f rie n d c la s s Animal;
21: elAnimal.suPeso = elPeso;
22: }
23:
24: in t main()
25: {
26: Animal peppy;
27: a sign a rV alo r(p e p p y ,5);
28: }
556 Día 15
P ía 16
Flojos
Hasta ahora ha utilizado c o u t para escribir en la pantalla y c in para leer desde
el teclado, sin co m p ren d er com pletam ente cómo funcionan. Hoy aprenderá lo
siguiente:
• Qué son los flujos y cóm o se utilizan
• C óm o m anejar la entrada y la salida por medio de flujos
• C óm o escribir en archivos y leerlos por medio de flujos
Una biblioteca es una colección de archivos objeto (.o en Linux, .obj en una PC)
que se pueden enlazar con su programa para proporcionar una funcionali
dad adicional. Ésta es la forma más básica de la reutilización de código y se
ha estado utilizando desde que los primeros programadores manejaban las
tarjetas perforadas para interpretar los Os y 1s.
E n c a p s u la c ió n
Las clases io stre a m ven el conjunto de datos que va desde su program a hasta la pantalla
com o un flujo de datos, un byte detrás de otro. Si el destino del flujo es un archivo o la
pantalla, el origen por lo general es alguna parte de su program a. Si el flujo se invierte,
los datos pueden venir desde el teclado o un archivo en disco y “verterse” en sus varia
bles de datos.
Uno de los objetivos principales de los flujos es encapsular los problem as relacionados
con el envío y recepción de los datos desde y hacia el disco o la pantalla. Después de crear
un flujo, su programa trabaja con el flujo y éste se encarga de los detalles. La figura I6.l
ilustra esta idea elemental.
Figura 16.1
Encapsulación por
medio de flujos.
Disco Disco
A lm a c e n a m ie n to en b ú fe r
Escribir en el disco (y en la pantalla, aunque en menor extensión) es muy “costoso”. Lleva
mucho tiempo (relativamente hablando) escribir información en el disco o leer información
del disco, y la ejecución del program a por lo general se bloquea debido a las lecturas y
escrituras de disco. Para solucionar este problem a, los flujos proporcionan el “almace
nam iento en búfer” . La información se escribe en el flujo, pero no se escribe inmediata
mente en el disco. En vez de esto, el búfer del flujo se va llenando, y cuando está lleno,
escribe todo en el disco de una sola vez.
Flujos 559
Imagine un tanque que se llena de agua por medio de una válvula que está en la parte supe
rior del tanque, y el nivel de agua sube pero no sale agua por la válvula que se encuentra
en la parte inferior del tanque. La figura 16.2 ilustra esto.
Figura 16.2
Llenado del búfer.
«a©
Cuando el agua (datos) llega hasta arriba, la válvula inferior se abre y el agua sale rápi
damente. La figura 16.3 ilustra esto.
Figura 16.3
Vaciado del búfer.
Cuando el búfer queda vacío, la válvula inferior se cierra, la válvula superior se abre y fluye
más agua hacia el tanque búfer, como se muestra en la figura 16.4.
|560 Día 16
Figura 16.4
Rellenado del búfer.
De vez en cuando se necesita sacar el agua del tanque incluso antes de que se llene por
completo. Esto se conoce como “limpiar el búfer” . La figura 16.5 ilustra esto.
Figura 16.5
Limpieza del búfer.
b ú fe re s
Como es de esperarse, C++ se basa en el método orientado a objetos para implementar
los flujos y los búferes.
• La clase streambuf maneja el búfer, y sus métodos (funciones miembro) propor
cionan la capacidad para llenar, vaciar, limpiar y manejar de cualquier otra forma
el búfer.
F lu jo s 561
• La clase io s es la clase base para las clases de flujos de entrada y salida. La clase
i o s tiene un objeto strea m b uf com o variable miembro.
• Las clases i s t r e a m y o stream se derivan de la clase i o s y especializan el com por
tam iento de los flujos de entrada y salida, respectivamente.
• La clase io s t r e a m se deriva tanto de istream como de ostream y proporciona
m étodos de entrada y salida para escribir en la pantalla.
• Las clases f s tr e a m proporcionan entrada y salida desde archivos.
1 6
Redireccióm
Cada uno de los dispositivos estándar, entrada, salida y error, se pueden redireccionar a
otros dispositivos. Por lo general, el error estándar se redirecciona a un archivo, y la entrada
y salida estándar se pueden canalizar hacia archivos usando comandos del sistema operativo
La redirección se refiere a enviar la salida (o entrada) hacia un lugar distinto al predeterm i
nado. Los operadores de redirección para Linux (así como para otras versiones de UNIX
y DOS) son (<) redirigir entrada y (>) redirigir salida.
La canalización se refiere al redireccionam iento de la salida de un program a com o la
entrada de otro.
562 Día 16
E n t r a d a p o r m e d io d e cin
EL objeto global llamado cin es responsable de la entrada y está disponible para su pro
grama al incluir a iostream. h. En ejemplos anteriores utilizó el operador de inserción
sobrecargado ( « ) para manipular datos en las variables de su programa. ¿Cómo fun
ciona esto? La sintaxis, como tal vez recuerde, es la siguiente:
int unaVariable;
cout « "Escriba un número:
cin » unaVariable;
Hablaremos sobre el objeto global cout más adelante en este día; por ahora, enfoquémonos
en la tercera línea, cin » unaVariable;. ¿Puede adivinar lo que hace cin?
Evidentemente, debe ser un objeto global ya que no lo definió en su propio código. De
la experiencia anterior con los operadores, sabe que c in ha sobrecargado el operador
de extracción ( » ) y que el efecto es escribir en la variable local llamada unaVariable
cualquier información que cin tenga en su búfer.
Lo que tal vez no sea inmediatamente obvio es que cin ha sobrecargado el operador de
extracción para una gran variedad de parámetros, entre los cuales se encuentran int&,
short&, long&, double&, float&, char&, char*, etc. Al escribir cin » unaVariable;,
se valora el tipo de unaVariable. En el ejemplo anterior unaVariable es de tipo entero,
por lo que se llama a la siguiente función:
istr‘.am & operator» (int &)
Observe que como el parámetro se pasa por referencia, el operador de extracción puede
actuar en la variable original. El listado 16.1 muestra el uso de cin.
Flujos 563
int : 2
Sa l i d a Long : 70000
Double: 987654321
Float: 3.33
Unsigned: 25
Int: 2
Long: 70000
Double: 9.87654e+08
Float: 3.33
Unsigned: 25
En las líneas 8 a 12 se declaran variables de varios tipos. En las líneas 14 a 23 se
A nálisis
pide al usuario que escriba valores para estas variables, y los resultados se imprimen
(por medio de cout) en las líneas 25 a 29.
La salida refleja que las variables se colocaron en los “tipos” correctos de variables, y el
programa funciona como se espera.
564 Día 16
C adenas
cin también puede manejar apuntadores a caracteres (char*) com o argumentos; por lo
tanto, usted puede crear un búfer de caracteres y utilizar cin para llenarlo. Por ejemplo,
puede escribir lo siguiente:
char SuNombre[ 50 1
cout « "Escriba su nombre: ";
cin » SuNombre;
Si escribe “Jesse”, la variable SuNombre se llenará con los caracteres J, e, s, s, e, \0. El
último carácter es un carácter nulo; cin termina automáticamente la cadena con un carác
ter nulo, y usted debe tener suficiente espacio en el búfer para toda la cadena más el
carácter nulo. Este carácter significa “fin de cadena” para las funciones de la biblioteca
estándar, las cuales se explican en el día 21, “Qué sigue” .
i
El carácter nulo ('\0‘) es distinto del apuntador n u l l .Pueden contener el
mismo valor en memoria (ceros binarios), pero sirven para distintos
propósitos. Debido a eso, se deben tratar de distinta manera. No asigne el
valor •\0•a un apuntador. No termine una cadena con la constante null .
j
F lu jo s 565
Para entender por qué esto funciona así. examine el listado 16.3. el cual m uestra la en tra
da para varios cam pos.
L istado 16.3 c o n t in u a c ió n
int : 2
Salida Long: 30303
Double: 393939397834
Float: 3.33
Palabra: Hola
Unsigned: 85
Int: 2
Long: 30303
Double: 3.93939e+11
Float: 3.33
Palabra: Hola
Unsigned:: 85
Int: 3
Long: 304938
Double: 3.93847e+08
Float: 6.66
Palabra: adiós
Unsigned : 4294967294
Una vez más se crean varias variables, esta vez incluyendo un arreglo de tipo
A nálisis
char. Se pide al usuario la entrada, y la salida se imprime fielmente.
En la línea 34 se pide al usuario toda la entrada a la vez, y luego cada “palabra” de
entrada se asigna a la variable apropiada. Para facilitar este tipo de asignación múltiple, cin
debe considerar cada palabra de la entrada como la entrada com pleta para cada variable.
Si cin considerara toda la entrada como parte de la entrada de una variable, este tipo de
entrada concatenada sería imposible.
Flujos 567
Observe en la línea 34 que el último objeto requerido fue un entero sin signo, pero el usua
rio escribió - 2 . Como cin piensa que está escribiendo a un entero sin signo, el patrón de bits
de -2 se evaluó como entero sin signo, y en la línea 43 cout despliega el valor 4294967294.
El valor 4294967294 sin signo tiene el patrón de bits exacto del valor -2 con signo.
Más adelante en esta lección verá cómo escribir una cadena completa en un búfer, incluyen
do varias palabras. Por ahora, surge la pregunta, “¿cómo maneja el operador de extracción
este truco de concatenación?"
<0®
operator» regresa una referencia a un objeto istream
El valor de retorno de c in es una referencia a un objeto istream. Como cin es en sí un
objeto istream, el valor de retomo de una operación de extracción puede ser la entrada a
la siguiente extracción.
int VarUno, varDos, varTres;
cout « "Escriba tres números: “
cin >> VarUno >> varDos >> varTres;
Al escribir cin » VarUno >> varDos » varTres;, se evalúa la primera extracción (cin
» VarUno). El valor de retomo de esto es otro objeto istream, y el operador de extracción
de ese objeto recibe la variable varDos. Es como si hubiera escrito lo siguiente:
((cin » VarUno) >> varDos) » varTres;
Verá esta técnica otra vez, más adelante cuando hablemos sobre cout.
O tra s f u n c io n e s m ie m b r o d e c in
Además de sobrecargar a o p e r a to r » , cin tiene otras funciones miembro. Éstas se utilizan
cuando se requiere de un control más fino sobre la entrada.
ch: o
ch: 1
ch : a
ch:
mundo
ch : m
ch : u
ch : n
ch : d
ch : o
ch :
(c t r l-d )
¡Listo!
Flujos 569
A
En la línea 6 se declara una variable local de tipo carácter. El ciclo while le asigna
n á l is is
a ch la entrada recibida de c in .g e t (), y si no es EOF, se imprime la cadena. Sin
embargo, esta salida se envía a un búfer hasta que se lee un fin de línea. Al encontrarse con
EOF (al oprimir Ctrl+D en Linux, o Ctrl+Z en un equipo DOS), el ciclo termina.
Hay que tener en cuenta que no todas las implementaciones de istream soportan esta
versión de get (), aunque ahora es parte del estándar ANSI/ISO. Desde luego que los
compiladores GNU si la soportan.
En tra d a L is t a d o 1 6 .5 U s o d e g e t ( ) c o n p a rá m e tr o s
D eb e NO DEBE
i
F lu jo s 571
El usuario escribe "A hora es tiem po". Como el usuario termina la frase con un carácter
de nueva línea, esa frase se coloca en cadenallno. seguida de un term inador nulo.
En la línea 12 se pide al usuario otra cadena, y esta vez se utiliza el operador de extracción.
Com o el operad o r de extracción tom a todo hasta el primer espacio en blanco, la cadena
Para, ju n to con un carácter nulo, se guarda en la segunda cadena que, desde luego, no es
lo que se esperaba.
L is t a d o 1 6 . 7 Uso de getline ()
1: // L is t a d o 16.7 - Uso de g e t lin e ( )
2: ^ in clu d e <iostream .h>
3:
4: in t main()
5: {
6: char cadenaUno[ 256 ];
7: char cadenaDos[ 256 ];
8: char cadenaTres[ 256 ];
9:
10: cout « " E s c r i b a la cadena uno:
11 : c in .ge tlin e (ca d e n a U n o , 256);
12: cout << "cadenaUno: " << cadenaUno « endl;
13: cout << " E s c r i b a la cadena dos:
14: c in >> cadenaDos;
15: cout « "cadenaDos: " « cadenaDos « endl;
16: cout << " E s c r ib a la cadena tre s: ";
17: c i n .g e t l i n e (cadenaTres, 256);
18: cout << "cadenaTres: " << cadenaTres « endl;
19: return 0;
20: }
572 Día 16
c i n . i g n o r e ( ) p a r a l i m p i e z a d e la e n t r a d a
Algunas veces necesita ignorar los caracteres restantes en una línea hasta que llegue ya
sea al fin de la línea (E O L ) o al fin del archivo (EO F). La función m iem bro ig n o r e ()
sirve para este propósito, ig n o r e () toma dos parámetros: el número m áxim o de carac
teres a ignorar y el carácter de term inación. Si escribe ig n o r e ( 8 0 , 1\ n ') . se descartarán
máximo SO caracteres hasta eneontrar un carácter de nueva línea. Luego este carácter se
descarta y term ina la instrucción ig n o r e (). El listado 16.8 muestra el uso de ig n o r e ( ).
1 6
Ahora in te n te de nuevo...
E s c rib a la cadena uno: Habia una vez
Cadena uno: Habia una vez
E s c rib a la cadena dos: una princesa
Cadena dos: una princesa
574 D í a 16
En las líneas 6 y 7 se crean dos arreglos de caracteres. En la línea 9 se pide al usuario que
escriba algo, y escribe “Había una vez” y oprim e "E ntrar”. En la linca l() se utiliza get()
para leer esta cadena, get () llena cadenaUno y term ina con el carácter de nueva línea,
pero deja este carácter en el bíifer de entrada.
En la línea 12 otra vez se pide al usuario que escriba algo, pero la función g e t l i n e ( ) de
la línea 13 lee el carácter de nueva línea que ya estaba en el búfer y term ina inmediata
m ente, antes de que el usuario pueda escribir algo.
En la línea 16 se vuelve a pedir al usuario que escriba algo, y éste escribe la misma línea
de entrada que la prim era vez. Sin em bargo, en la línea 16. esta vez se utiliza ig n o re ()
para “com erse” el carácter de nueva línea. Por lo tanto, cuando se llega a la llamada a
g e t l i n e ( ) en la línea 21. el búfer está vacío y el usuario puede escribir la siguiente línea
del cuento.
peek() y putback()
El objeto de entrada c i n tiene dos m étodos adicionales que pueden ser bastante útiles:
p e e k ( ), que se fija en el siguiente carácter pero no lo extrae, y p u t b a c k ( ). que inserta un
carácter en el flujo de entrada. El listado 16.9 m uestra cóm o se podrían utilizar estos dos
m étodos.
E ntrada L is t a d o 1 6 . 9 Uso d e p e e k ( ) y d e p u t b a c k ( )
El program a se ejecuta en un ciclo mientras esté recibiendo caracteres distintos del fin de
archivo (Ctrl+D en Linux. Clrl+Z o Ctrl+D en otros sistemas operativos). (Recuerde que
c i n . g e t ( ) regresa un 0 para el fin de archivo). Si el carácter actual es un signo de adm ira
ción. se descarta y el signo de dólar se regresa al búfer de entrada: se leerá la próxim a vez.
Si el elem ento actual no es signo de admiración, se imprime. Entonces se "observa" el
siguiente carácter, y si resulta ser un signo de numeral, se quita.
Este ejemplo no es la m anera más eficiente de hacer estas cosas (y no encontrará un signo
de numeral si es el prim er carácter), pero ayuda a mostrar la forma en que estos m étodos
1 6
trabajan. Son relativamente complicados, así que no se preocupe demasiado pensando cuán
do podría utilizarlos realmente. Déjelos en su bolsa de trucos; alguna vez le serán útiles.
Limpieza de la salida
A nteriorm ente vio que si se utiliza en dl se limpiará el búfer de salida, en dl llam a a la
función m iem bro f l u s h ( ) de cout. la cual escribe todos los datos que está guardando en
el búfer. Puede llam ar al m étodo f l u s h ( ) directamente o escribir lo siguiente:
cout « f lu s h
Esto puede ser conveniente cuando necesite cerciorarse de que se vacíe el búfer de salida
y que su contenido se escriba en la pantalla.
Funciones relacionadas
Así como el operador de extracción se puede suplir con g e t ( ) y g e t l i n e ( ), el operador
de inserción se puede suplir con put () y w r i t e ( ).
La función put () se utiliza para escribir un solo carácter en el dispositivo de salida. Como
p u t ( ) regresa una referencia a ostream y como cout es un objeto ostream, puede concate
nar put () de la m ism a m anera que como lo hace con el operador de inserción. El listado
16.10 ilustra esta idea.
576 Día 16
Hola
Salida
La línea 6 se evalúa de esta forma: c o u t . put ( ' H ' ) escribe la letra H en la pantalla
A nálisis
y regresa el objeto cout. Esto hace que quede lo siguiente:
cout.put(’o1).put(11’).put(1a1).put('\n');
Se escribe la letra o, dejando a c o u t . put ( ’ 1 ’ ). Este proceso se repite, se escribe cada letra
y se escribe el objeto cout que se regresa hasta el carácter final ( 1\ n '), y la función termina.
La función w r i t e ( ) funciona igual que el operador de inserción ( « ) , excepto que toma
un parámetro que indica a la función el número máximo de caracteres a escribir. El listado
16.11 muestra su uso.
M a n i p u l a d o r e s , indicadores e instrucciones
p a r a d a r f o r m a t o
El flujo de salida mantiene varios indicadores de estado que determinan cuál base (decimal
o hexadecimal) utilizar, el ancho de los campos, y qué carácter utilizar para llenar los
campos. Un indicador de estado es un byte a cuyos bits individuales se les asigna un
significado especial. Hablaremos sobre esta forma de manipular bits en el día 21, “Qué
sigue”. Cada uno de los indicadores de ostream se puede establecer por medio de funciones
miembro y manipuladores.
Uso de co u t.w id th ()
El ancho predeterminado de la salida será sólo el espacio suficiente para imprimir el
número, carácter o cadena en el büfer de salida. Puede cambiar este ancho utilizando
w id th (). Como w id th ( ) es una función miembro o método, se debe invocar con un
objeto cout. Esta función sólo cambia el ancho del siguiente campo de salida y luego
regresa inmediatamente al valor predeterminado. El listado 16.12 muestra su uso.
578 D í a 16
La primera salida, que se encuentra en las líneas 6 a 8. im prim e el número 123 dentro
A nálisis
de un campo cuyo ancho se establece en 25 en la línea 7. Esto se refleja en la primera
línea de la salida.
La segunda línea de la salida imprime prim ero el valor 123 en el m ism o cam po cuyo ancho
se establece en 25, y luego im prim e el valor 456. O b se rv e que 456 se im prim e en un
cam po cuyo ancho se restablece a sólo el suficiente; com o se dijo, el efecto de width()
dura sólo hasta la siguiente salida.
La salida final refleja que establecer un ancho m enor que la salida es lo m ism o que estable
cer un ancho que es lo suficientem ente grande para que se im prim a el valor completo.
Por ejem plo, puede establecer si se van o no a mostrar ceros a la derecha (para que 20.00
no se trunque a 20). Para activar los ceros a la derecha, llame a setf (ios: :showpoint).
Las constantes enum eradas tienen alcance fuera de la clase iostream (ios) y por conse
cuencia se llam an con su identificación completa del tipo ios: :nombreindicador, com o
ios::showpoint.
Puede h acer que el signo de más (+) aparezca antes de los núm eros positivos utilizando
ios: :showpos. Puede cam biar la alineación de la salida usando ios: :left, ios: :right
o ios::internal.
Por último, puede establecer la base de los números a desplegar usando io s : :dec (decimal),
i o s : :o c t (octal-base ocho), o i o s : :hex (hexadecimal-base dieciséis). Estos indicadores
tam bién se pueden concatenar en el operador de inserción. El listado 16.14 m uestra estas
580 D í a 16
configuraciones. A manera de bono, el listado 16.14 tam bién presenta al manipulador setw.
el cual establece el ancho, pero también se puede concatenar con el operador de inserción.
El numero es 185
S a l id a I El número es b9
El número es 0xb9
El número es 0xb9
El número es 0xb9
El número es 0xb9
El número es 0xb9
En la línea 7. la constante entera llamada numero se iniciali/a con el valor 185. Esto
A nálisis
se despliega en la línea 9.
F lu jo s 581
El valor se despliega de nuevo en la línea 11. pero esta vez se concatena el m anipulador
hex, lo que ocasiona que el valor se despliegue en forma hexadecimal como b9. (El valor b
en forma hexadecim al representa al número 11. Once por 16 es igual a 176; sume el 9 para
obtener un total de 185.)
En la línea 26 se vuelve a establecer el ancho en 10. pero esta vez la alineación es interna.
Por lo tanto, el 0 x se im prim e alineado hacia la izquierda, pero el valor b9 se im prim e
alineado hacia la derecha.
p r i n t f () no proporciona seguridad en los tipos, por lo que es fácil indicarle sin darse
cuenta que despliegue un entero como si fuera un carácter, y viceversa. Además, p r i n t f ()
no soporta las clases, por lo que no es posible enseñarle cómo imprimir los datos de su
clase; debe pasar los m iem bros de la clase a p r i n t f () uno por uno.
Por otro lado, p r i n t f () facilita el form ateo ya que los caracteres de form ato se pueden
colocar directam ente en la instrucción p r i n t f (). Debido a que p r i n t f () tiene sus usos y
debido a que m uchos program adores aún la utilizan bastante, en esta sección se repasará
brevem ente su uso.
Cada uno de los especificadores de conversión también puede proporcionar una instruc
ción para el ancho y una instrucción para la precisión, expresado como tipo float, en el
que los dígitos a la izquierda del decimal se utilizan para el ancho total, y los dígitos a
la derecha del decimal proporcionan la precisión para los números de punto flotante. Por
ejemplo, %5d es el especificador para un entero de 5 dígitos de ancho, y % 1 5.5f es el especi-
ficador para un valor de tipo f l o a t de 15 dígitos de ancho, de los cuales se dedican los
cinco dígitos finales para los decimales. El listado 16.15 muestra varios usos de p rin tf ().
En t r a d a L istado 16.15 Im p r e s ió n p o r m e d io d e p r i n t f ( )
¡Hola, mundo!
S a l id a ¡Hola de nuevo!
5
F lu jo s 583
P re g u n ta s fre c u e n te s
F A Q : ¿ P u e d e r e s u m ir la f o r m a e n q u e se m a n ip u la la s a lid a ?
R e s p u e s t a (c o n a g r a d e c im ie n t o e sp e c ia l a R o b e rt Francis): En C ++ , p a ra d a r f o r m a t o
a la s a lid a se u tiliz a u n a c o m b in a c ió n d e caracteres especiales, m a n ip u la d o re s d e sa lid a e
in d ic a d o re s .
L os s ig u ie n t e s c a ra c te re s e sp e c ia le s se in clu ye n e n u n a ca d e n a de sa lid a q u e se e n v ía p o r
m e d io d e l o p e r a d o r d e in se rció n :
\n— C a rá c te r d e n u e v a lín e a
\r— R e t o r n o d e c a rro
\t— T a b u la d o r
\\— B a rra d ia g o n a l in ve rsa
\ddd ( n ú m e r o o c ta l)— ca rá cte r A S C II
\a— A l a r m a ( s o n a r c a m p a n a )
E je m p lo :
k.
| 584 Día 16
Timbra la campana, im prim e un m ensaje de error y avanza al sigu ie nte tabulador. Los manipu
ladores se utilizan con el op e rad o r cout. Los m anipulad ore s qu e to m a n a rg u m e n to s requieren
que se incluya iom anip.h en el arch ivo del p ro gram a .
La siguiente es una lista de m a n ip u la d o re s q u e no requieren d e im an ip.h :
f lu s h — Lim pia el búfe r de salida
endl— Inserta un carácter d e nueva línea y lim pia el b ú fe r de salida
o c t— Establece la base de la salida en octal
dec— Establece la base de la salida en decim al
hex— Establece la base d e la salida en hexadecim al
La sigu ie nte es un a lista de m an ip u la d o re s qu e sí req uiere n d e im an ip .h :
s e tb a s e (b a s e )— Establece la base de la salida (0 = decim al, 8 = octal, 10 = decimal, 16 =
hexadecim al)
setw (a n ch o )— Establece el a n c h o m ín im o del c a m p o
setf ill (c h )— Llena con el carácter especificado p o r ch c u a n d o se d e fin e el ancho
setprecision (p)— Establece la precisión para n ú m e ro s de p u n to flo ta n te
setiosflags (f)— Establece un o o m ás indicadores d e ios
resetiosflags (f) — Restablece un o o m ás ind icad ores de ios
Ejemplo:
cout « setw(12) « setfill('#') « hex « x « endl;
Establece el ancho del cam po en 12, establece el carácter de lle n a d o en especifica una
salida hexadecimal, im prim e el valor de ’ x \ coloca un carácter de n u eva línea en el búfer y
limpia el búfer. Todos los m anipuladores, excepto flu s h , endl, y setw, perm anecen vigentes
hasta que se cambien o hasta que termina el program a, setw regresa a su valor predeterminado
después del cout actual.
Los siguientes indicadores de ios se pueden utilizar con los m a n ip u la d o re s setiosflags y
resetiosflags:
io s : : l e f t — Justifica la salida a la izquierda en el a n ch o e specificado
i o s : : r i g h t — Justifica la salida a la derech a en el a n c h o e sp e c ific a d o
i o s : : in te r n a l— El sign o se justifica a la izquierda y el valo r se justifica a la derecha
io s : :dec— Salida decimal
i o s : :o c t— Salida octal
io s : :hex— Salida hexadecim al
i o s :: showbase— A g re g a u n Ox a los nú m ero s he xadecim ales y u n 0 a los nú m eros ocíales
i o s : -.showpoint— A g r e g a ceros a la derecha c o m o lo re q u ie ra la p re cisió n
io s : :uppercase— Los nú m ero s hexadecim ales y e n n o ta c ió n científica se m uestran en
m ayúsculas
ios :: showpos— M ostrar el signo + para núm eros positivos
ios: :scientific— M ostrar núm eros de pun to flo tan te en n o tación científica
ios: :fixed— M ostrar núm eros de punto flotan te en no tación decim al
Puede obtener inform ación adicional en el archivo ios.h y en la d o c u m e n tac ió n de la bi
blioteca GNU.
Flujos 585
E n t r a d a y salida d e archivos
Los flujos proporcionan una manera uniforme de manejar los datos que provienen del tecla
do o del disco duro y los datos que van a la pantalla o al disco duro. En cualquier caso,
puede utilizar los operadores de inserción y de extracción o las demás funciones y m anipu
ladores asociados. Para abrir y cerrar archivos se crean objetos ifstream y ofstream (para
entrada y salida, respectivam ente) como se describe en las siguientes secciones. Debido
a que los objetos ifs tre a m son similares a los objetos ofsteam, el material cubierto es lim i
tado. Sólo aplique las técnicas para ofstream.
U so de ofstre am
Los objetos específicos utilizados para leer desde los archivos o escribir en ellos se llaman
objetos ofstream. Éstos se derivan de los objetos iostream que ya ha utilizado.
Para em pezar a escribir en un archivo, primero debe crear un objeto ofstream y luego
asociar ese objeto con un archivo específico del disco. Para utilizar objetos ofstream,
debe asegurarse de incluir fstream.h en su programa.
Estados de condición
Los objetos iostream mantienen indicadores que informan sobre el estado de la salida y
de la entrada. Puede revisar cada uno de estos indicadores por medio de las funciones
booleanas eof (), bad(), fail() y good(). La función eof () regresa true si el objeto
iostream se ha encontrado con EOF, fin del archivo. La función bad() regresa true si
intenta realizar una operación no válida. La función fail() regresa true siempre que bad ()
sea true o que falle una operación. Por último, la función good() regresa true siempre
que las otras tres funciones sean todas false.
Una función importante para flujos de archivos que necesitará de inmediato es close().
Cada objeto de flujo de archivos que cree abrirá un archivo ya sea para lectura o para
escritura (o ambas). Es importante utilizar c lo s e ( ) en el archivo después de terminar de
leer o escribir; esto asegura que el archivo no se dañe y que los datos que escribió se copien
en el disco.
Después de que los objetos stream se asocian con archivos, se pueden utilizar igual que
cualquier otro objeto stream. El listado 16.16 muestra esto.
O bserve que app es ab rev iatu ra de append (agregar): a te es abreviatura de at en d (al fin al),
y tr u n c es a b rev iatu ra de trú n ca le (truncar). El listado 16.17 m uestra el uso de app a b rie n
do de n u ev o el a rc h iv o del lista d o 16.16 y agregándole contenido.
13: {
14: cout << "Contenido actual del a r c h i v o : \ n " ;
15: char ch;
16: while ( f i n . g e t ( c h ) )
17: cout << ch;
18: cout << " \ n * * * F i n del contenido del a r c h i v o . * * * \ n " ;
19: }
20: f i n . c l o s e ( );
21 : cout << "\nAbriendo " << nombreArchivo;
22: cout « " en nodo a g r e g a r . . . \ n " ;
23: ofstream fout(nombreArchivo, i o s : : a p p ) ;
24: if (!fout)
25: {
26: cout << "No se puede a b r i r " << nom breArchivo;
27: cout << " para a g re g a r.\n ";
28: r e t u r n (1);
29: }
30: cout « "\n E s c rib a el texto para e l a rc h iv o :
31 : c i n .ig n o r e (1, ' \ n ') ;
32: c in . g e t lin e ( b u f e r , 255 );
33: fout « bufer << "\ n ";
34: f o u t .c i ó s e ();
35: fin.open(nom breArchivo); // ¡r e a s ig n a r objeto f i n e x iste n te !
36: i f (¡fin )
37: {
38: cout << "No se puede a b r i r " << nom breArchivo;
39: cout << 11 para l e c t u r a . \ n " ;
40: r e t u r n ( 1 );
41 : }
42: cout << "\nHe aqui e l contenido del a r c h i v o : \ n " ;
43: char ch;
44: w h ile ( f i n . g e t ( c h ) )
45: cout << ch;
46: cout << " \ n * * * F i n del contenido del a r c h i v o . * * * \ n " ;
47: f i n . c ió s e ();
48: return 0;
49: }
De nuevo se pide al usuario que escriba el nombre del archivo. Esta vez se crea un
A n á l is is
objeto stream de archivo de lectura en la línea 11. Esa apertura se prueba en la línea
12. y si el archivo ya existe, se imprim e su contenido en las líneas 14 a 18. Observe que
i f ( f i n ) es sinónim o de i f ( f in . good ()) .
Luego se cierra el archivo de entrada y se vuelve a abrir, esta vez en modo agregar, en la
línea 23. Después de esta apertura (y de cada apertura), el archivo se prueba para asegurar
que se haya abierto adecuadam ente. Observe que i f ( !fo u t) es lo mismo que probar i f
(f o u t . f a i l () ). Luego se pide al usuario que escriba un texto, y el archivo se cierra de
nuevo en la línea 34.
Por último, com o en el listado 16.16. el archivo se vuelve a abrir en modo de lectura; sin
embargo, esta vez no es necesario volver a declarar fin . Sólo se le vuelve a asignar el
mismo nombre de archivo. De nuevo se prueba la apertura en la línea 36, y si todo está bien,
se imprime en la pantalla el contenido del archivo, y se cierra el archivo por última vez.
N O DEBE
Los archivos binarios pueden guardar no sólo enteros y cadenas, sino también estructuras
completas de datos. Puede escribir todos los datos a la vez usando el método write() de
f stream. Uno de los usos más comunes de un archivo binario es guardar todos los datos
miembro de un objeto.
Si utiliza w rite (), puede recuperar los datos por medio de read (). Sin embargo, cada
una de estas funciones espera un apuntador a un carácter, por lo que debe convertir la
dirección de su objeto en apuntador a un carácter.
El segundo argumento para estas funciones es el número de caracteres a escribir, el cual
se puede determinar mediante sizeof (). Observe que lo que está escribiendo son los datos,
no los métodos. Lo que recupera son sólo datos. El listado 16.18 muestra cómo escribir
en un archivo el contenido de una clase.
argumento 1: Aprenda
argumento 2: C++
argumento 3: en
argumento 4: 21
argumento 5: d ía s
En las líneas 7 y 8 se imprime cada uno de los argumentos de la línea de comandos, pasan
do las cadenas con terminación nula a cout mediante la indexación del arreglo de cadenas.
En el listado 16.20 se m uestra un uso más común para los argum entos de la línea de
comandos. El código del listado 16.18 se modificó para tomar el nombre de archivo com o
argum ento de la línea de comandos.
L is ta d o 1 6 . 2 0 c o n t in u a c ió n
11 : -Animal(){}
12: int ObtenerPeso() const
13: { return suPeso; >
14: void AsignarPeso(int peso)
15: { suPeso = peso; }
16: long ObtenerDiasVivo() const
17: { return suNumeroDiasVivo; }
18: void AsignarDiasVivo(long dias)
19: { suNumeroDiasVivo = dias; }
20: private:
21 : int suPeso;
22: long suNumeroDiasVivo;
23:
24:
25: int main(int argc, char *argv[]) // regresa 1 en caso de error
26: {
27: if (argc != 2)
28: {
29: coût « "Uso: “ « argv[ 0 ];
30: coût « " <nombrearchivo>" « endl;
31 : return(1);
32: }
33: ofstream fout(argv[ 1 ], ios::binary);
34: if (!fout)
35: {
36: coût « "No se puede abrir" « argv[l];
37: coût « " para escritura.\n";
38: return(l);
39: }
40:
41 : Animal Oso(50, 100);
42: fout.write((char*) &0so, sizeof Oso);
43: fout.close();
44: ifstream fin(argv[ 1 ], ios ::binary);
45: if (!fin)
46: {
47: coût « "No se puede abrir" « argv[ 1 ];
48: coût « " para lectura.\n";
49: return(1);
50: >
51 :
52: Animal Oso Do s (1, 1);
53: coût « "OsoDos peso: "« OsoDos.ObtenerPeso() « endl;
54: coût « "OsoDos días: "« OsoDos.ObtenerDiasVivo( ) « endl;
55: fin.read((char*) &OsoDos, sizeof OsoDos);
56: coût « "OsoDos peso: "« OsoDos.ObtenerPeso( ) « endl;
57: coût « "OsoDos días: "« OsoDos.ObtenerDiasVivo() « endl;
58: fin.close();
59: return 0;
60:
Flujos 595
OSODOS peso: 1
Sa l i d a OsoDos dias : 1
OsoDos peso: 50
OsoDos días : 100
La declaración de la clase Animal es la misma que la del listado 16.18. Sin embargo,
A nálisis
esta vez, en lugar de pedir al usuario el nombre del archivo, se utilizan argumentos
de la línea de comandos. En la línea 25 se declara a main () para tomar dos parámetros: la
cuenta de los argumentos de la línea de comandos y un apuntador al arreglo de cadenas
de argumentos de la línea de comandos.
En las líneas 27 a 32 el programa se asegura de recibir el número esperado de argumentos
(exactamente dos). Si el usuario no proporciona un solo nombre de archivo, se imprime
un mensaje de error:
Uso lst16-20 <nombrearchivo>
Entonces el programa termina. Observe que al usar argv[ 0 ] en lugar de codificar direc
tamente el nombre de un programa, puede compilar este programa para que tenga cualquier
nombre, y esta instrucción de uso funcionará automáticamente.
En la línea 33, el programa intenta abrir el nombre de archivo proporcionado para salida
binaria. No hay razón para copiar el nombre de archivo en un búfer local temporal. Se
puede utilizar directamente al tener acceso a argv[ 1 ].
Esta técnica se repite en la línea 44 en donde el mismo archivo se vuelve a abrir para lec
tura, y se utiliza en las instrucciones de condición de error cuando los archivos no se pueden
abrir, en las líneas 36 y 47.
R e s u m e n
Hoy se presentaron los flujos, y se describieron los objetos globales cout y cin. El objetivo
de los objetos is tre a m y ostream es encapsular el trabajo de escribir en los controla
dores de dispositivos y usar búferes para la entrada y la salida.
En cualquier programa se crean cuatro objetos stream estándar: cout, cin, c e rr y clog.
Cada uno de éstos se puede “redireccionar” en muchos sistemas operativos.
El objeto c in de is tre a m se puede utilizar para entrada, y su uso más común es con el
operador de extracción ( » ) sobrecargado. El objeto cout de ostream se utiliza para la
salida, y su uso más común es con el operador de inserción ( « ) sobrecargado.
Cada uno de estos objetos tienen una variedad de métodos, o funciones miembro, como
get () y put (). Debido a que las formas comunes de cada uno de estos métodos regresan
una referencia a un objeto stream, es fácil concatenar cada uno de estos operadores y
funciones.
El estado de los objetos stream se puede cambiar mediante el uso de manipuladores.
Estos pueden establecer las características de formato y despliegue y varios atributos más
de los objetos stream.
596 Día 16
La E/S de archivos se puede lograr mediante el uso de clases f stream. las cuales se
derivan de las clases stream. Además de soportar los operadores normales de inserción y
de extracción, estos objetos también soportan read() y write( ) para guardar y recuperar
objetos binarios grandes.
P r e g u n t a s y r e s p u e s t a s
P ¿Cómo se sabe cuándo utilizar los operadores de inserción y de extracción, y
cuándo utilizar los otros métodos de las clases stream?
R En general, es más fácil utilizar los operadores de inserción y de extracción, y se
prefieren cuando su comportamiento es lo que se necesita. En aquellas circunstancias
inusuales en las que estos operadores no puedan hacer el trabajo (como leer en una
cadena de palabras), se pueden utilizar los otros métodos.
P ¿Cuál es la diferencia entre cerr y clog?
R cerr no utiliza búfer. Todo lo que se escribe en cerr se escribe inmediatamente
hacia la salida estándar (por lo regular, la pantalla). Esto está bien para errores que
se escriben en pantalla, pero puede tener un costo demasiado alto en cuanto a rendi
miento al escribir archivos de registro en el disco, clog usa búfer para su salida, y
por ende puede ser más eficiente.
P ¿Por qué se crearon los flujos si printf () funciona bien?
R printf () no soporta el poderoso sistema de tipos de C++, y no soporta clases
definidas por el usuario.
P ¿Cuándo se utilizaría putback ()?
R Cuando se utilice una operación de lectura para determinar si un carácter es válido, y
otra operación de lectura diferente (tal vez un objeto diferente) necesite que el
carácter esté en el búfer. Esto se utiliza más comúnmente cuando se analiza sintácti
camente un archivo; por ejemplo, el compilador de C++ podría utilizar putback().
P ¿Cuándo se utilizaría ignore () ?
R Un uso común es después de utilizar get (). Como get () deja el carácter de termi
nación en el búfer, es común llamar a ignore (1, ' \n ' ); inmediatamente después
de una llamada a g e t ( ). Esto se utiliza con frecuencia en el análisis sintáctico.
P Mis amigos utilizan printf () en sus programas de C++. ¿Puedo hacerlo yo?
R Claro. Ganará algo de conveniencia, pero sacrificará la seguridad de los tipos.
F lu jo s 597
Taller
El taller le proporeiona un cuestionario para ayudarlo a afianzar su com prensión del
material tratado, así com o ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D.
“R espuestas a los cuestionarios y ejercicios", y asegúrese de com prender las respuestas
antes de pasar al siguiente día.
1 6
Cuestionario
1. ¿Q ué es el operador de inserción, y qué hace?
2. ¿Q ué es el operador de extracción, y qué hace?
3. ¿C uáles son las tres form as de utilizar c i n . g e t ( ), y cuáles son sus diferencias?
4. ¿Cuál es la diferencia entre c in .r e a d ( ) y c i n . g e t l i n e ( )?
5. ¿Cuál es el ancho predeterm inado para enviar como salida un entero largo m ediante
el op erador de inserción?
6. ¿Cuál es el valor de retorno del operador de inserción?
7. ¿Q ué parám etro lleva el constructor para un objeto o fstream ?
8. ¿Q ué hace el argum ento i o s : :a te ?
Ejercicios
1. Escriba un program a que escriba en los cuatro objetos io stre a m estándar: c in .
c o u t. c e r r y c lo g .
2. Escriba un program a que pida al usuario que escriba su nombre com pleto y luego
lo despliegue en pantalla.
3. M odifique el listado 16.9 para que haga lo mismo, pero sin utilizar p u tb a c k ( ) ni
i g n o r e ().
4. Escriba un program a que tome un nombre de archivo como parám etro y que abra
el arch iv o para lectura. Lea todos los caracteres del archivo y despliegue en la
pantalla sólo las letras y los signos de puntuación. (Ignore todos los caracteres no
im prim ibles.) Luego el program a deberá cerrar el archivo y terminar.
5. Escriba un program a que despliegue sus argumentos de la línea de com andos en
orden inverso, y que no despliegue el nombre del programa.
fliä
S emana 3
D ía 1 3 7
Espacios de nombres
Una nueva adición para el ANSI de C++ es el uso de espacios de nombres para
ayudar a que los program adores eviten conflictos de nombres al utilizar más de
una biblioteca. Hoy aprenderá lo siguiente:
• Cóm o se resuelven por m edio del nombre las funciones y las clases
• Cóm o crear un espacio de nombres
• Cóm o utilizar un espacio de nombres
• Cóm o utilizar el espacio de nombres estándar std
C ó m o se r e s u e l v e n p o r m e d i o del n o m b r e las
f u n c i o n e s y las c l a s e s
A medida que analiza sintácticamente el código fuente y construye una lista de nombres
de funciones y variables, el com pilador revisa si existen conflictos de nombres. Los
conflictos que no puede resolver el compilador, tal vez pueda resolverlos el enlazador.
El compilador no puede revisar conflictos de nombres entre unidades de traducción (por
ejemplo, archivos objeto); ése es el propósito del enlazador. Por lo tanto, el compilador ni
siquiera emitirá una advertencia.
Es muy común que el enlazador (Id en Linux) falle con el mensaje I d e n t i f i e r m ultiply
defined ( i d e n t i f i e r es algún tipo con nombre). Verá este mensaje si ha definido el mismo
nombre con el mismo alcance en diferentes unidades de traducción. Si redefine un nombre
dentro de un solo archivo que tenga el mismo alcance, obtendrá un error de compilación.
Al com pilar y enlazar el siguiente ejemplo, el enlazador producirá un mensaje de error:
// archivo primero.cxx
int valorEntero = 0 ;
int main( ) {
int valorEntero = 0 ;
// . . .
Espacios de nombres 601
return 0 ;
} ;
// archivo segundo.cxx
int valorEntero = 0 ;
// fin de segundo.cxx
El problema con los dos enteros globales definidos fuera de cualquier función es que
tienen el mismo nombre y visibilidad, y esto producirá un error de enlace.
602 Día 17
int enteroConAlcanceGlobal = 5 ;
void f( )
{
int enteroConAlcanceLocal = 10 ;
>
int main( )
{
int enteroConAlcanceLocal = 15 ;
{
int otroLocal = 20 ;
int enteroConAlcanceLocal = 30 ;
}
return 0 ;
}
La prim era definición int, enteroConAlcanceGlobal, tiene visibilidad dentro de las
funciones f () y main (). La siguiente definición se encuentra dentro de la función f () y
se llama enteroConAlcanceLocal. Esta variable tiene alcance local, lo que significa que
sólo es visible dentro del bloque que la define.
La función main() no puede tener acceso a la variable enteroConAlcanceLocal de la
función f (). Cuando la función termina, enteroConAlcanceLocal queda fuera de alcance.
La tercera definición, también llamada enteroConAlcanceLocal, se encuentra en la
función main (). Esta variable tiene alcance de bloque.
O bserve que la variable enteroConAlcanceLocal de main() no tiene conflicto con la
variable enteroConAlcanceLocal de f (). Las dos definiciones que siguen, otroLocal y
enteroConAlcanceLocal, tienen alcance de bloque. Tan pronto com o la ejecución del
programa llega a la llave de cierre, estas dos variables pierden su visibilidad.
Observe que esta variable enteroConAlcanceLocal está ocultando a la variable
enteroConAlcanceLocal definida justo antes de la llave de apertura (la segunda varia
ble enteroConAlcanceLocal definida en el programa). Cuando el programa pasa más
allá de la llave de cierre, la segunda variable enteroConAlcanceLocal definida recupera
su visibilidad. Ningún cambio realizado a la variable enteroConAlcanceLocal definida
dentro de las llaves afecta el contenido de la variable enteroConAlcanceLocal externa.
E s p a c io s d e n o m b re s 603
// archivo: segundo.cxx
extern const i n t j ;
#include <iostream>
in t main()
{
s td ::c o u t << "j vale " « j « std::endl ;
return 0 ;
}
Observe que este ejemplo llama a cout con la designación de espacio de nombres std ; esto
le permite hacer referencia a todos los objetos “estándar” de la biblioteca ANSI estándar.
Al crearlo, este ejem plo despliega lo siguiente:
j vale 10
604 D í a 17
}
El uso de s t a t i c para lim itar el alcance de las variables externas ya no se recomienda y
eventualm ente podría convertirse en algo ilegal. A hora se deben usar espacios de nom
bres en lugar de s t a t i c .
D ebe NO DEBE
namespace Ventana
{
void cambiarTamanio(int x, int y) ;
}
He aquí un ejemplo:
// archivo encabezado.h
namespace Ventana {
void mover(int x, int y) ;
// otras declaraciones ...
}
// archivo itnpl.cxx
void Ventana::mover(int x, int y)
{
// código para mover la ventana
}
2
3 #include < io s t re a m .h>
4
5
6 namespace Ventana
7 {
8 const in t MAX_X = 30 ;
9 const in t MAX_Y = 40 ;
10: c l a s s V id r i o
11 : {
12: p u b lic :
13: V i d r i o ();
14: - V i d r i o ();
15: void tam anioíint
16: void mover(int X
17: void mostrar()
18: p rivate :
19: s t a t i c in t cnt
20: in t x;
21 : in t y;
22: };
23: }
24:
25: in t V e n t a n a ::V id rio ::c n t = 0
26:
27: V e n t a n a : ¡ V i d r i o : : V i d r i o ( ):
continúa
| 608 Día 17
L i s t a d o 17.1 continuación
28: x(0),
29: y(0)
30: {}
31 :
32: Ventana:¡Vidrio::-Vidrio() {}
33:
34: void Ventana:¡Vidrio::tamanio(int x, int y)
35: {
36: if(x < Ventana::MAX_X && x > 0)
37: Vidrio::x = x;
38: if(y < Ventana: :MAX_Y && y > 0)
39: Vidrio::y = y;
40: }
41:
42: void Ventana::Vidrio::mover(int x, int y)
43: {
44: if(x < Ventana::MAX_X && x > 0)
45: Vidrio::x = x;
46: if(y < Ventana::MAX_Y && y > 0)
47: Vidrio::y = y;
48: }
49:
50: void Ventana:¡Vidrio::mostrar()
51: {
52: std::cout « " x " « Vidrio::x;
53: std::cout « " y " « Vidrio::y « std::endl;
54: }
55:
56: int main( )
57: {
58: Ventana::Vidrio vidrio;
59:
60: vidrio.mover(20, 20);
61: vidrio.mostrar( );
62: return 0 ;
63: }
S a l id a x 20 y 20
Observe que la clase Vidrio está anidada dentro del espacio de nombres Ventana.
A nálisis
Ésta es la razón por la que se tiene que identificar el nombre Vidrio con Ventana::.
La variable estática cnt, la cual se declara en Vidrio en la línea 19, se define como siem
pre. Observe que, en las líneas 34 a 40, MAX_X y MAX_Y están identificadas completamente
dentro de la función Vidrio::tamanio(). Esto se debe a que Vidrio está dentro del
alcance; de no ser así, el compilador emitiría un diagnóstico de error. Esto también es cierto
para la función Vidrio::mover ().
Si compila este código con la versión 2.7.2 de g++. recibirá el siguiente mensaje:
warning: namespaces are mostly broken in this version of g++
Espacios de nombres 609
La única solución es utilizar una versión más reciente que tenga soporte para los espa
cios de nombres.
Lo que también es interesante es la identificación de Vidrio: :x y de Vidrio: :y dentro de
ambas definiciones de funciones. ¿A qué se debe esto? Bueno, si usted hubiera escrito la
función Vidrio: :mover() de la siguiente manera, tendría un problema:
void Ventana::Vidrio::mover(int x, int y)
{
if(x < Ventana::MAX_X && x > 0)
x = x ;
if(y < Ventana::MAX_Y && y > 0)
y=y ;
Platform::mover(x, y) ;
}
¿Ya vio cuál es la cuestión ? Probablemente no obtendrá mucha información de su compi
lador; algunos no emiten ningún tipo de mensaje de diagnóstico.
La causa del problema son los argumentos de la función. Los argumentos x y y ocultan las
instancias privadas de las variables x y y declaradas dentro de la clase Vidrio. En efecto,
las instrucciones asignan x y y a sí mismas:
x = x ;
y=y ;
La directiva using
La directiva using expone efectivamente todos los nombres declarados en un espacio de
nombres para que estén en el alcance actual. Puede hacer referencia a los nombres sin
necesidad de identificarlos con su respectivo nombre de espacio de nombres. El siguiente
ejemplo muestra el uso de la directiva using:
namespace Ventana {
int valorl = 20 ;
int valor2 = 40 ;
}
Ventana::valorl = 1 0 ;
El alcance de la directiva using empieza en su declaración y llega hasta el final del alcance
actual. Observe que debe identificar a valorl para poder referendario. La variable valor2
|610 Día 17
Puede surgir una ambigüedad si se utiliza un nombre que esté definido tanto globalmente
como dentro de un espacio de nombres. La ambigüedad surge sólo si se utiliza el nombre,
y no cuando se introduce un espacio de nombres. Esto se demuestra en el siguiente frag
mento de código:
namespace Ventana {
int valorl = 20 ;
}
II. . ■
using namespace Ventana ;
int valorl = 10 ;
void f( )
{
valorl = 10 ;
}
La ambigüedad ocurre dentro de la función f (). La directiva lleva efectivamente a 17
Ventana: : v a l o r l al espacio de nombres global; como ya hay un valorl definido en
forma global, el uso de v a lo rl en f () es un error. Observe que si se quitara la línea de
código de f (), no existiría ningún error.
La d e c la ra c ió n u s in g
La declaración using es similar a la directiva using con la excepción de que la declaración
proporciona un nivel más fino de control. Dicho de manera más específica, la declara
ción using se utiliza para declarar un nombre específico (que pertenece a un espacio de
nombres) para que esté en el alcance actual. Así puede referirse al objeto especificado sólo
por su nombre. El siguiente ejemplo muestra el uso de la declaración using:
namespace Ventana {
int valorl = 20 ;
int valor2 = 40 ;
int valor3 = 60 ;
}
II. ■ ■
using Ventana::valor2 ; //llevar a valor2 hacia el alcance actual
Ventana:: valorl = 10 ; //se debe identificar a valorl
valor2 = 30 ;
Ventana: :valor3 = 10 ; // se debe identificar a valor3
La declaración using agrega el nombre especificado al alcance actual. La declaración no
afecta los otros nombres que están dentro del espacio de nombres. En el ejemplo anterior
se hace referencia a valor2 sin identificación, pero valorl y valor3 necesitan ser identi
ficados. La declaración using proporciona un mayor control sobre los nombres de los
espacios de nombres que se llevan hacia un alcance determinado. Esto contrasta con la
directiva que lleva hacia un alcance determinado a todos los nombres que se encuentran
dentro de un espacio de nombres.
612 Día 17
Después de llevar un nombre a un alcance, éste permanece visible hasta el fin de ese alcan
ce. Este comportamiento es igual que con cualquier otra declaración. Una declaración using
se puede utilizar en el espacio de nombres global o dentro de cualquier alcance local.
Sería un error introducir un nombre en un alcance local en el que se haya declarado un
nombre de un espacio de nombres. Lo opuesto también es un error. El siguiente ejemplo
muestra esto:
namespace Ventana {
int valorl = 20 ;
int valor2 = 40 ;
>
//. . .
void f()
{
int valor2 = 10 ;
using Ventana::valor2 ; // declaración múltiple
std::cout « valor2 « std::endl ;
}
int main( )
{
char c = ap(aptr) ;
}
614 Día 17
Cada uno de los nombres, valor y las funciones ap (), son distintos entre cada archivo.
Para hacer referencia a un nombre (espacio de nombre sin nombre) que esté dentro de una
unidad de traducción, se utiliza el nombre sin identificación. Este uso se demuestra en el
ejemplo anterior con la llamada a la función ap (). Esta sintaxis implica el uso de una
directiva using para los objetos a los que se hace referencia desde un espacio de nombres
sin nombre. Debido a esto, no se puede tener acceso a los miembros de un espacio de
nombres sin nombre en otra unidad de traducción. El comportamiento de un espacio
de nombres sin nombre es el mismo que el de un objeto s t a t ic que tenga enlace extemo.
Considere este ejemplo:
static int valor = 10 ;
Recuerde que el comité de estándares desaprueba este uso de la palabra reservada static.
Los espacios de nombres existen ahora para reemplazar código, como se mostró anterior
mente. Otra forma de ver a los espacios de nombres sin nombre es que son variables
globales con enlace interno.
Recuerde que la directiva using extrae todo del espacio de nombres con nombre. Es malo
emplear la directiva using al utilizar la biblioteca estándar. ¿Por qué? Porque esto anula
el propósito de utilizar un espacio de nombres; el espacio de nombres global se contaminará
con todos los nombres encontrados en el encabezado. Tome en cuenta que todos los archi
vos de encabezado utilizan la característica de los espacios de nombres, así que si incluye
múltiples archivos de encabezado y especifica la directiva using, todo lo que esté declarado
en los encabezados estará en el espacio de nombres global. Considere que la mayoría de
los ejemplos que vienen en este libro violan esta regla; esta acción no es un intento por
defender la violación de la regla, sólo se hace para asegurar la brevedad de los ejemplos.
Lo que usted debe utilizar es la declaración using, como en el siguiente ejemplo:
#include <iostream>
using std::cin ;
using std::cout ;
using std::endl ;
int main( )
{
int valor = 0 ;
cout << "Así que, ¿cuántos huevos dijo que quería?" << endl ;
Espacios de nombres 615
cin » valor ;
cout « valor « ” huevos estrelladosI" « endl ;
return(0) ;
>
A continuación se muestra un ejemplo de la ejecución del programa:
Así que, ¿cuántos huevos dijo que quería?
4
¡4 huevos estrellados!
Como alternativa, podría identificar completamente los nombres que utiliza, como en el
siguiente código de muestra:
#include <iostream>
int main( )
{
int valor = 0 ;
std::cout « "¿Cuántos huevos quería?" « std::endl ;
std::cin » valor ;
std::cout « valor « " huevos estrellados!" « std::endl ;
return(0) ;
}
La salida del programa se muestra a continuación:
¿Cuántos huevos quería?
4
¡4 huevos estrellados!
Esto podría ser apropiado para programas cortos, pero se puede volver algo incómodo
para cualquier cantidad considerable de código. ¡Imagine tener que escribir el prefijo
s td :: para todos los nombres que utilice que se encuentren en la biblioteca estándar!
Resumen
La creación de un espacio de nombres es muy similar a la declaración de clases. Hay un
par de diferencias que vale la pena mencionar. En primer lugar, después de la llave de cierre
de un espacio de nombres no se pone punto y coma. En segundo lugar, un espacio de
nombres es abierto, mientras que una clase es cerrada. Esto significa que puede seguir defi
niendo el espacio de nombres en otros archivos o en secciones separadas de un solo archivo.
Cualquier cosa que se pueda declarar se puede insertar en un espacio de nombres. Si
diseña clases para una biblioteca reutilizable, debe utilizar la característica de espacio de
nombres. Las funciones declaradas dentro de un espacio de nombres se deben definir fuera
del cuerpo de ese espacio de nombres. Esto provoca que la interfaz se separe de la imple-
mentación y también evita que el espacio de nombres se llene excesivamente.
Es posible anidar los espacios de nombres. Un espacio de nombres es una declaración;
este hecho le permite anidar los espacios de nombres. No olvide que debe identificar
completamente los nombres que estén anidados.
L
616 Día 17
La directiva using se utiliza para exponer en el alcance actual a todos los nombres que se
encuentran en un espacio de nombres. Esto contamina el espacio de nombres global con
todos los nombres que se encuentran en el espacio de nombres con nombre. Por lo general,
es una mala práctica utilizar la directiva using, especialmente con respecto a la biblioteca
estándar. Es mejor utilizar declaraciones using.
La declaración using se utiliza para exponer en el alcance actual a un espacio de nombres
específico. Esto le permite hacer referencia al objeto sólo con su nombre.
Un alias de un espacio de nombres es similar en naturaleza a un typedef. Un alias de un es
pacio de nombres le permite crear otro nombre para un espacio de nombres que ya tenga nom
bre. Esto puede ser bastante útil al utilizar un espacio de nombres que tenga un nombre largo.
Todos los archivos pueden contener un espacio de nombres sin nombre. Este, como su
nombre lo implica, es un espacio de nombres que no tiene nombre, y le permite utilizar
los nombres que están dentro del espacio de nombres, sin necesidad de identificación.
Hace que los nombres del espacio de nombres sean locales para la unidad de traduc
ción. Los espacios de nombres sin nombre son lo mismo que declarar una variable
global con la palabra reservada s ta tic .
La biblioteca estándar de C+ + está encerrada en un espacio de nombres llamado std. Evite
utilizar la directiva using al usar la biblioteca estándar; mejor utilice la declaración using.
Preguntas y respuestas
P ¿Tengo que utilizar espacios de nombres?
R No, puede escribir programas simples e ignorar por completo los espacios de nom
bres. Asegúrese de utilizar las viejas bibliotecas estándar (por ejemplo, #include
<string.h>) en lugar de las nuevas (por ejemplo #include <cstring>).
P ¿Cuáles son los dos tipos de instrucciones que hay con la palabra reservada
using? ¿Qué diferencias hay entre esos dos tipos de instrucciones?
R La palabra reservada using se puede utilizar para las directivas using y para las
declaraciones using. Una directiva using permite que se utilicen todos los nombres
que se encuentran en un espacio de nombres como si fueran nombres normales. Una
declaración using, por el contrario, permite que el programa utilice un nombre indi
vidual de un espacio de nombres sin tener que identificarlo con el identificador de
espacio de nombres.
P ¿Qué son los espacios de nombres sin nombre? ¿Para qué los necesitamos?
R Los espacios de nombres sin nombre son espacios de nombres que no tienen nombre.
Se utilizan para “envolver” una colección de declaraciones y protegerla contra posi
bles conflictos entre nombres. Los nombres que se encuentran en un espacio de
nombres sin nombre no se pueden utilizar fuera de la unidad de traducción en la que
está declarado el espacio de nombres.
Espacios de nombres 617
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del
material tratado, así como ejercicios para que experimente con lo que ha aprendido.
Trate de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice
D, “Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Puedo utilizar nombres definidos en un espacio de nombres sin utilizar la palabra
reservada using?
2. ¿Cuáles son las principales diferencias entre espacios de nombres normales y
espacios de nombres sin nombre?
3. ¿Cuál es el espacio de nombres estándar?
Ejercicios
1. CAZA ERRORES: ¿Qué está mal en este programa?
#include <iostream>
int main()
{
cout « "¡Hola, mundo!" « end;
return 0;
}
2. Mencione tres formas de solucionar el problema del ejercicio 1.
f
n
Semama 3
D ía 1 S
Análisis y diseño
orientados a objetos
Es fácil enfocarse en la sintaxis de C++ y olvidarse de cómo y por qué se utilizan
estas técnicas para construir programas. Hoy aprenderá lo siguiente:
• Cómo utilizar el análisis orientado a objetos para modelar el problema que
está tratando de resolver
• Cómo utilizar el diseño orientado a objetos para crear una solución con
tundente, extensiva y confiable
• Cómo utilizar el UML (Lenguaje de Modelado Unificado) para documentar
su análisis y su diseño
Hay. desde luego, simplificaciones considerables. El globo terráqueo de un niño nunca tiene
lluvia, inundaciones, terremotos, etcétera, pero puedo utilizar ese globo para predecir cuánto
tiempo me tomará volar desde mi hogar hasta Indianápolis en caso de que alguna vez
necesite ir a explicarle a la administración superior por qué mi manuscrito llegó tarde
(“verán, iba muy bien, pero luego me perdí en una metáfora y me tardé horas en salir”).
Un modelo que no es más simple que la cosa que se está modelando no sirve de mucho.
Steven Wright bromea acerca de esto: “Tengo un mapa en el que una pulgada equivale a
una pulgada. Vivo en E5".
El diseño de software orientado a objetos trata acerca de la construcción de buenos modelos.
Consta de dos componentes importantes: un lenguaje de modelado y un proceso.
Figura 18 .1
Generaliza ción/
especia Iiza d ón.
Puede ver en la figura que un geranio es un tipo especial de flor. Si usted y yo acordamos
dibujar nuestros diagramas de herencia (generalización/especialización) de esta manera,
nos entenderemos a la perfección. Con el tiempo, probablemente nos gustaría modelar
muchas relaciones complejas para desarrollar nuestro propio conjunto complicado de
convenciones y reglas para dibujar diagramas.
622 Día 18
Claro que necesitaremos explicar nuestras convenciones a las demás personas con las que
trabajemos, así como a cada nuevo empleado o colaborador. Tal vez interactuemos con otras
compañías que tengan sus propias convenciones, y necesitemos tiempo para negociar una
convención común y la forma de compensar los inevitables malos entendidos.
Sería más conveniente que todos en la industria acordaran usar un lenguaje común de
modelado. (De hecho, sería conveniente que todos en el mundo acordaran usar un lenguaje
hablado, pero hagamos una cosa a la vez.) El lenguaje unificado de desarrollo de software
es el UML (Lenguaje de Modelado Unificado). El trabajo de UML es contestar preguntas
como la siguiente: “¿Cómo se dibuja una relación de herencia?” El dibujo del geranio
mostrado en la figura 18.1 se dibujaría en UML como se muestra en la figura 18.2.
F ig u r a 18.2
Flor
D ib u jo U M L d e
e sp e c ia liza c ió n .
Geranio
En UML, las clases se dibujan como rectángulos, y la herencia se dibuja como una línea con
punta de flecha. Curiosamente, la flecha apunta de la clase más especializada a la clase más
general. La dirección de la flecha es contraintuitiva para muchas personas, pero esto no es
muy importante; cuando todos estamos de acuerdo, el sistema funciona perfectamente.
Los detalles de UML son bastante claros. Los diagramas no son difíciles de utilizar ni de
entender, y los explicaré a medida que vayamos avanzando en esta lección, en lugar de tra
tar de enseñar UML fuera del contexto. Aunque es posible escribir un libro completo que
trate sobre UML, la verdad es que el 90 % del tiempo sólo se utilizará un pequeño sub
conjunto de la notación de UML, y ese subconjunto se puede aprender fácilmente.
producto comercial de Rational Software, Inc. Los tres trabajan en Rational Software, en
donde se les conoce afectuosamente como los tres amigos.
Esta lección sigue el método Objectory , pero no tan al pie de la letra. No lo sigo así porque
no creo en la necesidad de apegarme estrictamente a la teoría académica (me interesa
mucho más el mercadeo del producto que apegarme a un método). Otros métodos tienen
algo que ofrecer, y tiendo a ser ecléctico; recojo piezas y pedazos a medida que avanzo y
las uno todas en un marco de trabajo funcional.
El proceso del diseño de software es iterativo. Esto significa que a medida que desarrolla
mos software, pasamos por todo el proceso en forma repetida esforzándonos por mejorar
la comprensión de los requerimientos. El diseño dirige la implementación. pero los detalles
descubiertos durante la implementación retroalimentan el diseño. Lo que es más impor
tante, no tratamos de desarrollar ningún proyecto de tamaño ajustable en una sola dirección
y con un solo orden; lo que hacemos es iterar sobre algunas piezas del proyecto, mejorando
constantemente nuestro diseño y refinando nuestra implementación.
El desarrollo iterativo se puede distinguir del desarrollo en cascada. En el desarrollo en
cascada, la salida de una etapa se convierte en la entrada de otra, y no hay regreso (vea la
figura 18.3). En un proceso de desarrollo en cascada, los requerimientos se expresan con
detalle, y los clientes autorizan (“Sí. esto es lo que yo quiero”); a continuación, los requeri 18
mientos se envían al diseñador, ya fijos. El diseñador crea el diseño (y qué maravilla para
contemplar) y lo envía al programador, quien implementa el diseño. El programador a su
vez entrega el código a una persona de control de calidad, quien prueba el código y luego
lo libera al cliente. Suena perfecto en teoría, pero es un desastre en la práctica.
624 Día 18
Esta lección sólo cubre los tres primeros pasos: conceptualización, análisis y
diseño. Los demás pasos están mucho más allá del alcance de esta lección
introductoria. Puede encontrar muchos libros que traten estos temas con
mucho detalle.
Análisis y diseño orientados a objetos 625
Controversias
Existen interminables controversias sobre lo que ocurre en cada paso del proceso de dise
ño iterativo, e incluso sobre el nombre que se dé a esos pasos. He aquí el secreto: los
pasos esenciales son los mismos en casi todos los procesos: encontrar lo que necesita
crear, diseñar una solución e implementar ese diseño.
Aunque los grupos de noticias y las listas de correo de tecnología de objetos se desarro
llan por separado, los fundamentos del análisis y el diseño orientados a objetos son bastante
claros. En esta lección se muestra un método práctico para el proceso, el cual puede ser la
base para que usted cree la arquitectura de su aplicación.
El objetivo de todo este trabajo es producir código que cumpla con los requerimientos
establecidos, que sea confiable y que se pueda extender y mantener. Lo que es más im
portante, el objetivo es producir código de alta calidad que esté a tiempo y dentro del
presupuesto establecido.
Conceptualización: la visión
Todo buen software empieza con una visión. Un individuo tiene una idea sobre un producto
y piensa que seria bueno crearlo. Raras veces los comités crean visiones apremiantes. La
primera fase del análisis y el diseño orientados a objetos es capturar esta visión en una sola
oración (o cuando mucho, en un párrafo corto). La visión se convierte en la guía funda
mental del desarrollo, y el equipo que se reúne para implementar dicha visión debe regresar
varias veces (y hacer las actualizaciones necesarias) a medida que avanza.
Incluso si la oración de la visión sale de un comité que se encuentre en el departamento de
comercialización, se debe designar a una persona para que sea el “visionario”. El trabajo
de esa persona es custodiar la luz sagrada. A medida que se progrese, los requerimientos
evolucionarán. Las demandas del tiempo programado para la producción y del tiempo en
el que el producto llegará al mercado pueden modificar lo que usted trata de lograr en la
primera iteración del programa. Pero el visionario debe vigilar la idea esencial, para ase
gurar que lo que se produzca refleje la visión original con alta fidelidad. Esta dedicación
implacable y este compromiso apasionado son los que conducen al buen término del
proyecto. Si pierde el sentido de la visión, su producto está perdido.
L
Algunas compañías confunden el enunciado de la visión con los requerimientos. Es ne
cesaria una visión sólida, pero no es suficiente. Para pasar al análisis, debe entenderla
forma en que se va a utilizar el producto y cómo debe funcionar. El objetivo de la fase de
análisis es articular y capturar estos requerimientos. El resultado de la fase de análisis es
la producción de un documento de requerimientos. La primera sección del documento de
requerimientos es el análisis de los casos de uso.
Casos de uso
La fuerza impulsora en el análisis, diseño e implementación son los casos de uso. Un caso
de uso es simplemente una descripción de alto nivel de la forma en que se va a utilizar el
producto. Los casos de uso no sólo dirigen el análisis, también dirigen el diseño, ayudan
a encontrar las clases y son muy importantes cuando se va a probar el producto.
Crear un conjunto resistente e integral de casos de uso puede ser la tarea individual más
importante del análisis. Aquí es en donde se depende más de los expertos de dominio;
estos expertos tienen la mayor parte de la información acerca de los requerimientos comer
ciales que se tratan de capturar.
Los casos de uso le dan muy poca importancia a la interfaz de usuario, y no le dan ninguna
importancia al funcionamiento interno del sistema que se está creando. Cualquier sistema
o persona que interactúa con el sistema se conoce como actor
Para resumir, a continuación se muestran algunas definiciones:
• Caso de uso: Una descripción de cómo se va a utilizar el software.
• Expertos de dominio: Personas con experiencia en el dominio (área) de negocios
para el que se está creando el producto.
• Actor: Cualquier persona o sistema que interactúa con el sistema que se está desa
rrollando.
Un caso de uso es una descripción de la interacción entre un actor y el sistema mismo.
Para los propósitos de análisis del caso de uso, el sistema se trata como una “caja negra .
Un actor “envía un mensaje” al sistema, y sucede algo: se regresa información, se cambia
el estado del sistema, la nave espacial cambia su dirección, o sucede cualquier otra cosa.
Cómo identificar a los actores
Es importante observar que no todos los actores son personas. Los sistemas que interac
túan con el sistema que se está creando también son actores. Por lo tanto, si fuéramos a
crear un cajero automático, tanto el cliente como el empleado del banco pueden ser
actores (así como otros sistemas con los que interactúe nuestro nuevo sistema, como un
sistema de rastreo de hipotecas o de préstamos a estudiantes). Las características esen
ciales de los actores son las siguientes:
• Son externos al sistema
• Interactúan con el sistema
Análisis y diseño orientados a objetos 627 |
Por lo general, empezar es la parte más difícil del análisis de los casos de uso. A menudo,
la mejor forma de avanzar es con una sesión de “lluvia de ideas . Simplemente escriba la
lista de personas y de sistemas que interactuarán con su nuevo sistema. Recuerde que cuan
do hablamos de personas, en realidad nos referimos a los papeles que juegan (el empleado
del banco, el gerente, el cliente, etcétera). Una persona puede jugar más de un papel.
Para el ejemplo del cajero automático que acabamos de mencionar, podemos esperar que
dicha lista incluya los siguientes papeles:
• El cliente
• El personal del banco
• Un sistema de soporte para la oficina
• La persona que llena con dinero el cajero automático
Al principio, no necesita ir más allá de la lista obvia. Generar hasta tres o cuatro actores
puede ser suficiente para que pueda empezar a generar casos de uso. Cada uno de estos
actores interactúa de distintas formas con el sistema. Debe capturar estas interacciones en
sus casos de uso.
11:í■'!¡n
Cómo determinar los primeros casos de uso
Debemos empezar con el papel del cliente. Podríamos hacer una lluvia de ideas para :-V
'.'V-ijífo
)',! í; '.'víSí/
encontrar los siguientes casos de uso para un cliente:
• El cliente revisa los balances de una cuenta
• El cliente deposita dinero en su cuenta
• El cliente retira dinero de su cuenta
• El cliente transfiere dinero entre cuentas
• El cliente abre una cuenta
• El cliente cierra una cuenta
¿Deberíamos distinguir entre “El cliente deposita dinero en su cuenta de cheques y El
cliente deposita dinero en su cuenta de ahorro”, o deberíamos combinar estas acciones
(como lo hicimos en la lista anterior) en “El cliente deposita dinero en su cuenta”? La
respuesta a esta pregunta depende de si esta distinción es significativa en el dominio.
Para determinar si estas acciones son uno o dos casos de uso, debe preguntarse si los
mecanismos son distintos (el cliente tiene que hacer algo muy diferente con estos depósitos)
y si los resultados son diferentes (el sistema contesta de forma distinta). La respuesta a
ambas preguntas para la cuestión relacionada con el depósito es “no”: en esencia, el
cliente deposita dinero de la misma forma en cualquier cuenta, y los resultados son casi
iguales; el cajero automático responde incrementando el balance en la cuenta apropiada.
628 Día 18
Dado que el actor y el sistema se comportan y responden más o menos de manera similar,
sin importar si el depósito se hace en la cuenta de cheques o en la de ahorro, estos dos
casos de uso son en realidad un solo caso de uso. Más adelante, cuando desarrollemos
escenarios de los casos de uso, podremos probar las dos variaciones para ver si habría
alguna diferencia.
Al ir pensando en cada actor, puede descubrir casos de uso adicionales al hacer estas
preguntas:
• ¿Por qué el actor está usando este sistema?
El cliente está usando el sistema para obtener efectivo. para hacer un depósito o
para revisar el balance de una cuenta.
• ¿Qué resultados quiere de cada petición el actor?
Agregar efectivo a una cuenta u obtener efectivo para hacer una compra.
• ¿Qué ocasionó que ahora el actor utilizara este sistema?
Tal vez el actor haya recibido un ingreso o esté pensando en hacer una compra.
• ¿Qué debe hacer el actor para utilizar el sistema?
Meter su tarjeta en el cajero automático.
¡Ahá! Necesitamos un caso de uso para que el cliente inicie una sesión en el sistema.
• ¿Qué información debe proporcionar al sistema el actor?
Escribir un número de identificación personal.
¡Ahá! Necesitamos casos de uso para obtener y modificar el número de identificación
personal.
• ¿Qué información espera el actor obtener del sistema?
Balances, entre otros.
A menudo puede encontrar casos de uso adicionales si se enfoca en los atributos de los
objetos que están en el dominio. El cliente tiene un nombre, un NIP (número de identifi
cación personal) y un número de cuenta; ¿tenemos casos de uso para manejar estos objetos?
Una cuenta tiene un número, un balance y un historial de transacciones; ¿hemos capturado
estos elementos en los casos de uso?
Después de explorar detalladamente los casos de uso para los clientes, el siguiente paso
para desarrollar la lista de casos de uso es desarrollar los casos de uso para cada uno de los
demás actores. La siguiente lista muestra un primer conjunto razonable de casos de uso
para el ejemplo del cajero automático:
• El cliente revisa los balances de la cuenta
• El cliente deposita dinero en su cuenta
• El cliente retira dinero de su cuenta
• El cliente transfiere dinero entre cuentas
A n á lisis y dise ñ o orie n ta d o s a objetos 629
Figura 18 .4
Captura de informa
ción con el programa
Rational Rose.
630 Día 18
Es importante tener en cuenta que los elementos que estamos describiendo aquí no son
objetos de diseño, sino objetos del dominio. Esta es la documentación de la forma en que
el mundo funciona, no la documentación de la forma en que nuestro sistema funcionará.
Podemos trazar en un diagrama la relación entre los objetos del dominio del ejemplo del
cajero automático por medio del UML, con las mismas convenciones para diagramas que
utilizaremos más adelante para describir las relaciones entre clases del dominio. Éste es
uno de los puntos fuertes del UML: podemos utilizar las mismas herramientas en cada etapa
del proyecto.
Por ejemplo, podemos capturar que las cuentas de cheques y las cuentas de ahorros son
especializaciones del concepto más general de cuenta de banco, usando las convenciones
del UML para clases y relaciones de generalización, como se muestra en la Figura 18.5.
Figura 1 8.5
Especialización.
En el diagrama de la figura 18.5, las cajas representan los diversos objetos del dominio,
y las líneas con punta de flecha indican la generalización. El UML especifica que estas
líneas se deben dirigir de la clase especializada a la clase “base” más general. Por
consiguiente, tanto Cuenta de cheques como Cuenta de ahorros apuntan hacia Cuenta
de banco, lo cual indica que cada una es una forma especializada de Cuenta de banco.
F i g u r a 18 .6
Contención.
Agregación
El diagrama de la figura 18.6 indica que la Cuenta de cheques tiene un Balance. Puede
combinar estos diagramas para mostrar un conjunto de relaciones bastante complejo (vea
la figura 18.7).
|632 Día 18
Figura 18.7
Relaciones de objetos.
El diagrama de la figura 18.7 establece que una Cuenta de cheques y una Cuenta de ahorros
son Cuentas de banco, y que todas las Cuentas de banco tienen un Balance y un Historial
de transacciones.
Asociación
La tercera relación que se captura comúnmente en el análisis del dominio es una asociación
simple. Una asociación indica que dos objetos se conocen entre sí y que interactúan de al
guna forma. Esta definición será mucho más precisa en la etapa de diseño. En la etapa de
análisis sólo indicamos que el Objeto A y el Objeto B interactúan, pero que ninguno contie
ne al otro y ninguno es una especialización del otro. Mostramos esta asociación en el UML
por medio de una línea recta simple entre los objetos, como se muestra en la figura 18.8.
Figura 18.8
Asociación.
Asociación
A nálisis y diseño orientados a objetos 633
El diagrama de la figura 18.8 indica que el Objeto A se asocia de alguna forma con el
Objeto B.
Cóm o establecer escenarios
Ahora que tenemos un conjunto preliminar de casos de uso y las herramientas con las que
formaremos un diagrama de la relación entre los objetos del dominio, estamos listos para
formalizar los casos de uso y darles más profundidad.
Cada caso de uso se puede dividir en una serie de escenarios. Un escenario es una descrip
ción de un conjunto específico de circunstancias que se distinguen de entre los diversos
elementos del contingente de casos de uso. Por ejemplo, el caso de uso “El cliente retira
dinero de su cuenta” podría tener los siguientes escenarios:
• El cliente solicita un retiro de S300 de la cuenta de cheques y el sistema coloca el
dinero en la bandeja de disposición de efectivo e imprime un recibo.
• El cliente solicita un retiro de $300 de su cuenta de cheques, pero su balance es de
$200. Se informa al cliente que no hay suficientes fondos en la cuenta de cheques
para completar el retiro.
• El cliente solicita un retiro de $300 de su cuenta de cheques, pero hoy ya ha retirado
$100 y el límite es de $300 por día. Se informa el problema al cliente, y éste opta por 18
retirar sólo $200.
• El cliente solicita un retiro de $300 de su cuenta de cheques, pero se acabó el papel
para imprimir los recibos. Se informa el problema al cliente, y éste elige proceder
sin obtener un recibo.
Y así por el estilo. Cada escenario explora una variación en el caso de uso original. A
menudo, estas variaciones son condiciones de excepciones (no hay suficiente dinero en
la cuenta, no hay suficiente dinero en el cajero, etcétera). Algunas veces, las variaciones
exploran matices de decisiones en el caso de uso en sí (por ejemplo, ¿quería el cliente
transferir dinero antes de hacer el retiro?).
No se pueden explorar todos los escenarios posibles. Se buscan los escenarios que pongan
a prueba los requerimientos del sistema o detalles de la interacción con el actor.
Figura 18.9
D iagram a d e un ra so
de uso.
Figura 18.10
El estereotipo
"usa".
La figura 18.10 indica que el caso de uso Retirar efectivo “usa” el caso de uso Iniciar
sesión, y por lo tanto implementa completamente a Iniciar sesión como parte de Retirar
efectivo.
Diagram as de interacción
Aunque el diagrama de un caso de uso en sí puede ser de valor limitado, puede asociar
diagramas con los casos de uso que mejoren en forma dramática la documentación y la
comprensión de las interacciones. Por ejemplo, sabemos que el escenario Retirar efectivo
representa las interacciones entre los siguientes objetos del dominio: el cliente, la cuenta
de cheques y la interfaz de usuario. Podemos documentar esta interacción con un diagra
ma de interacciones, como se muestra en la figura 18.11.
2: Mostrar opciones
« 5: Regresar autorización
a
a 6: Débito de $300
a
a
7: Entregar efectivo a
a
8: Solicitar recibo a
a
9: Imprimir recibo a
•
a
El diagrama de interacción de la figura 18.11 captura los detalles del escenario que tal
vez no sean evidentes al leer el texto. Los objetos que están interactuando son objetos
del dominio, y tanto el cajero automático como la interfaz de usuario son tratados como
un solo objeto, y sólo se pide la cuenta de banco específica para los detalles.
Este ejemplo algo simple del cajero automático muestra sólo un imaginario conjunto de
interacciones, pero descubrir todos los detalles específicos de estas interacciones puede
ser una herramienta poderosa para comprender tanto el dominio del problema como los
requerimientos de su nuevo sistema.
Análisis y diseño orientados a objetos 637
A n á lisis de la aplicación
Además de crear casos de uso, el documento de requerimientos capturará las suposiciones,
restricciones y requerimientos acerca del hardware y de los sistemas operativos. Los reque
rimientos de la aplicación son los prerrequisitos específicos de su cliente (esas cosas
que normalmente se determinan durante el diseño y la implementación, pero que su cliente
ha decidido por usted).
Los requerimientos de la aplicación con frecuencia se conducen por la necesidad de tener 18
una interfaz con los sistemas existentes (heredados). En este caso, comprender lo que hacen
los sistemas y cómo funcionan es un componente esencial de su análisis.
Lo ideal es que analice el problema, diseñe la solución y luego decida qué plataforma y
sistema operativo encajan mejor en el diseño. Ese escenario es tan ideal como raro. Muy
a menudo, el cliente tiene una inversión fija en un sistema operativo o en una plataforma
de hardware específicos. El plan de negocios del cliente depende de que su software se
ejecute en el sistema existente, y usted debe capturar estos requerimientos oportunamente
y realizar el diseño de acuerdo con ellos.
Debe contestar en la fase de análisis éstas y otras preguntas relacionadas, antes de que em
piece a diseñar su nuevo sistema. Además, será conveniente que trate de capturar las
restricciones y limitaciones implícitas en la interacción con los demás sistemas. ¿Disminui
rán la sensibilidad de su sistema? ¿Exigirán demasiado de su nuevo sistema, consumiendo
recursos y tiempo de cómputo?
Documentos de planeación
Después de entender lo que debe hacer su sistema y cómo debe comportarse, es hora de
empezar a crear un documento que establezca el tiempo y presupuesto para el proyecto. Por
lo general, el plazo de tiempo lo establece el cliente: ‘Tiene 18 meses para terminar esto”.
Lo ideal es que examine los requerimientos y calcule el tiempo que llevará diseñar e imple-
mentar la solución. Eso es lo ideal. La realidad práctica es que la mayoría de los sistemas
tiene un límite de tiempo y un límite de costo impuestos, y el verdadero truco es averiguar
cuánta de la funcionalidad requerida puede construir en el tiempo y con el costo asignados.
He aquí dos lincamientos que debe considerar cuando va a crear el presupuesto y plazo
de tiempo para un proyecto:
• Si se le da un rango, el número externo es probablemente optimista.
• Ley de Hofstadter: Siempre toma más tiempo de lo que usted espera, incluso si
toma en cuenta la ley de Hofstadter.
Teniendo en cuenta estas realidades, es imperativo que realice su trabajo por prioridades.
Sencillamente, no terminará a tiempo. Es importante que cuando se le agote el tiempo
lo que tenga funcione y sea adecuado para una primera liberación. Si está construyendo
un puente y se agota el tiempo, si no tuvo oportunidad de terminar el carril para las
bicicletas, eso está muy mal; pero de todas formas puede abrir el puente y empezar a cobrar
peaje. Si se le acaba el tiempo y apenas va a la mitad del río, eso no es muy bueno.
Una cosa esencial que debe saber acerca de los documentos de planeación es que están
mal. En esta etapa tan temprana del proceso, es casi imposible ofrecer una estimación
confiable de la duración del proyecto. Después de tener todos los requerimientos,
puede darse una buena idea de cuánto tiempo llevará hacer el diseño, una estimación justa
de cuánto tardará la implementación, y una estimación razonable del tiempo de prueba.
Luego debe dejar por lo menos de un 20 a un 25 por ciento de tiempo de reserva, mismo
que puede ir reduciendo a medida que avanza y aprende más.
VisuaüzacDones
La pieza final del documento de requerimientos es la visualización. Éste es un nombre
elegante para los diagramas, las imágenes, imágenes capturadas en pantalla, los prototipos
y cualquier otra representación visual creada para ayudarlo a idear y diseñar la interfaz
gráfica de usuario de su proyecto.
Para muchos proyectos grandes, puede desarrollar un prototipo completo para ayudarlo
(y ayudar a sus clientes) a comprender cómo se comportará el sistema. En algunos
equipos de trabajo, el prototipo se convierte en el documento de requerimientos viviente;
el sistema “real” se diseña para implementar la funcionalidad diseñada en el prototipo.
Artefactos
Al final de cada fase de análisis y diseño creará una serie de documentos o “artefactos”. La
tabla 18.1 muestra algunos de los artefactos de la fase de análisis. El cliente utiliza estos do
cumentos para asegurarse de que usted comprenda lo que él necesita; también los udlizan los
usuarios finales para retroalimentar y orientar el proyecto, y finalmente los utiliza el equipo
del proyecto para diseñar e implementar el código. Muchos de estos documentos también
proporcionan material de vital importancia tanto para su equipo de documentación como para
el equipo de control de calidad, para indicarles cómo debe comportai-.se el sistema.
18
Ta b l a 1 8 . 1 Artefactos creados durante la etapa de análisis de un proyecto
Artefacto Descripción
Visualizaciones del caso de uso Un documento que detalla los casos de uso. escenarios,
estereotipos, condiciones previas, condiciones posteriores y
visualizaciones.
Análisis del dominio Documento y diagramas que describen las relaciones entre los
objetos del dominio.
Diagramas de colaboración Diagramas de colaboración que describen las interacciones
de análisis entre los objetos del dominio del problema.
Diagramas de actividad de análisis Diagramas de actividad que describen las interacciones entre
los objetos del dominio del problema.
Análisis de sistemas Reporte y diagramas que describen los sistemas de bajo nivel
y de hardware en los que estará el proyecto.
Documento de análisis Reporte y diagramas que describen los requerimientos especí-
de la aplicación ficos del cliente para este proyecto.
Reporte de restricciones Reporte que describe las características y limitaciones del
operac ionales rendimiento impuestas por este cliente.
Documento de costo y planeación Reporte con diagramas de Gantt y Pert que indican el tiempo,
avance y costos programados.
| 640 Día 18
Diseño
El análisis se enfoca en comprender el dominio del problema, mientras que el diseño se
enfoca en crear la solución. El diseño es el proceso de transformar la comprensión de los
requerimientos en un modelo que se pueda implementar en software. El resultado de este
proceso es la producción de un documento de diseño.
El documento de diseño se divide en dos secciones: Diseño de clases y Mecanismos de
la arquitectura. A su vez, la sección Diseño de clases se divide en diseño estático (el cual
describe detalladamente las diversas clases y sus relaciones y características) y diseño
dinámico (el cual describe con detalle la forma en que interactúan las clases).
La sección Mecanismos de la arquitectura proporciona detalles acerca de cómo se va a
implementar la persistencia de los objetos, la concurrencia, un sistema de objetos distribui
do, etcétera. El resto de esta lección se enfoca en el aspecto del documento de diseño
relacionado con el diseño de las clases; otras lecciones del resto de este libro explican
cómo implementar varios mecanismos de arquitectura.
J
Análisis y diseño orientados a objetos 641
Figura 18.12
C lases preliminares.
Transformaciones
Lo que empezó a hacer en la sección anterior no era tanto extraer los sustantivos del esce
nario, sino empezar a transformar objetos del análisis del dominio en objetos del diseño.
Ese es un excelente primer paso. A menudo, muchos de los objetos del dominio tendrán
s u s titu to s en el diseño. Se llama sustituto a un objeto para distinguir entre el verdadero
recibo físico entregado por un cajero automático y el objeto de su diseño que es sólo una
abstracción intelectual implementada en el código.
Probablemente encontrará que la m a yo ría de los objetos del dominio tiene una representa
ción isomorfa en el diseño, es decir, existe una correspondencia exacta entre el objeto del
dominio y el objeto del diseño. Sin embargo, otras veces un solo objeto del dominio se
representa en el diseño por medio de toda una serie de objetos de diseño. Y a veces, una
serie de objetos del dominio se puede representar por medio de un solo objeto de diseño.
Observe en la figura 18.12 que ya hemos capturado el hecho de que CuentaDeCheques es
una especialización de Cuenta. No salimos a buscar la relación de generalización, pero
ésta era evidente, por lo que la capturamos. De la misma manera, gracias al análisis del
dominio supimos que CajeroAutomatico entrega tanto Efectivo como Recibos, por lo
que inmediatamente capturamos esa información en el diseño.
La relación entre Cliente y CuentaDeCheques es menos obvia. Sabemos que dicha relación
existe, pero los detalles no son obvios, por lo que preferimos esperar.
A n á lis is y d is e ñ o o r ie n t a d o s a o b je t o s 643
Estas clases de interfaces ofrecen la encapsulación del protocolo de la interfaz y por ende
protegen al có digo de cam bios en el otro sistema. Las clases de interfaces le perm iten
cam biar su propio diseño, o adecuar cambios en el diseño de otros sistemas sin tener que
descom poner el resto del código. Siempre y cuando los dos sistemas sigan soportando la
interfaz acordada, pueden moverse independientemente el uno del otro.
M a n ip u la ció n de d ato s
De la m ism a m anera, usted creará las clases para manipulación de datos. Si tiene que
transform ar datos de un form ato a otro (por ejemplo, de Fahrenheit a centígrados o de
pulgadas/yardas al sistem a m étrico), tal vez necesite encapsular estas m anipulaciones
en una clase de m anipulación de datos. Puede utilizar esta técnica al enviar mensajes con
datos en los formatos requeridos para otros sistemas o para transmitir por medio de Internet;
en resum en, cada vez que tenga que m anipular datos en un formato específico, debe
18
encapsular el protocolo en una clase de manipulación de datos.
V ista s
Cada “vista" o “reporte" que genere su sistema (o, si usted genera muchos reportes, cada
conjunto de reportes) es un candidato para una clase. Las reglas del reporte (tanto la form a
en que se recopila la inform ación como la forma en que se va a desplegar) se pueden
encapsular en form a productiva dentro de una clase de vistas.
D is p o sitiv o s
Si su sistem a interactúa con dispositivos (como impresoras, módems, escáneres, etc.) o
los manipula, los detalles específicos del protocolo del dispositivo deben estar encapsulados
en una clase. De nuevo, al crear clases para la interfaz del dispositivo, puede incluir nuevos
dispositivos con nuevos protocolos sin tener que descomponer el resto de su código; sólo
cree una nueva clase de interfaz que soporte la misma interfaz (o una interfaz derivada),
y ya está.
M odelo estático
Cuando ya tiene establecido su conjunto preliminar de clases, es momento de em pezar a
m odelar sus relaciones e interacciones. Con el fin de tener una mayor claridad, esta lección
explica prim ero el modelo estático y después el modelo dinámico. En el proceso de diseño
| 644 Día 18
real, puede moverse libremente entre los modelos estático y dinámico, proporcionar detalles
de ambos y, de hecho, agregar nuevas clases y delinearlas a medida que avanza.
El modelo estático se enfoca en tres áreas de interés: responsabilidades, atributos y rela
ciones. La más importante de éstas, y en la que se debe enfocar primero, es el conjunto
de responsabilidades para cada clase. El principio guía más importante es éste: ca da clase
d e b e s e r r e sp o n sa b le d e una co sa .
Esto no quiere decir que cada clase tenga sólo un método. Al contrario, muchas clases
pueden tener docenas de métodos. Pero todos estos métodos deben ser coherentes y
cohesivos; es decir, todos deben relacionarse entre sí y contribuir a la capacidad de la
clase para lograr cubrir una sola área de responsabilidad.
En un sistema bien diseñado, cada objeto es una instancia de una clase bien definida y bien
comprendida que es responsable de un área de interés. Por lo general, las clases delegan
las responsabilidades ajenas a otras clases relacionadas. Al crear clases que tengan sólo
un área de interés, se promueve la creación de código fácil de mantener.
Para tener una idea de cuáles deben ser las responsabilidades de sus clases, puede ser
benéfico que empiece su trabajo de diseño con el uso de tarjetas CRC.
Tarjetas CRC
CRC significa Clase, Responsabilidad y Colaboración. Una tarjeta CRC no es más que
una ficha de 4 x 6. Este sencillo dispositivo de baja tecnología le permite trabajar con
otras personas para entender cuáles son las principales responsabilidades de su conjunto
inicial de clases. Debe formar una pila de fichas de 4 x 6 en blanco y reunirse en una mesa
de conferencias para tener una serie de sesiones con tarjetas CRC.
La sesión CRC se empieza acomodando al grupo alrededor de una mesa para conferencias,
con una pequeña pila de fichas de 4 x 6 en blanco. En la parte superior de cada tarjeta
CRC escribirá el nombre de una clase individual. Dibuje una línea vertical en el centro
de la tarjeta y en la parte izquierda escriba R e sp o n sa b ilid a d e s , y en la derecha escriba
C o la b o r a c io n e s .
Empiece llenando tarjetas para las clases más importantes que haya identificado. Para cada
tarjeta, escriba una definición de una o dos oraciones en la parte posterior. También puede
capturar qué otra clase es especializada por esta clase si eso es obvio al momento de estar
trabajando con la tarjeta CRC. Sólo escriba S uperclase: abajo del nombre de la clase y
escriba el nombre de la clase de la que se deriva esta clase.
Suponga que tenemos cinco participantes en nuestra sesión CRC: Amy, la moderadora y
diseñadora orientada a objetos; Barry, el programador en jefe; Charlie, el cliente; Dorris,
la experta del dominio; y Ed, un programador.
Amy levanta una tarjeta CRC que representa a CuentaDeCheques y dice; “Le digo al cliente
cuánto dinero tiene disponible. Él me pide que le dé $300. Envío un mensaje al distribuidor
automático indicándole que dé $300 en efectivo". Barry levanta su tarjeta y dice: “Yo soy el
distribuidor automático. Libero $300 y envío a Amy un mensaje diciéndole que decremente
su balance en $300. ¿A quién le digo que ahora tengo $300 menos? ¿Tengo que mantener
un registro de eso?”. Charlie dice: “Creo que necesitamos un objeto que mantenga el regis
tro del efectivo que hay en la máquina”. Ed dice: “No, el distribuidor automático debe saber
cuánto efectivo tiene; eso es parte del distribuidor automático”. Amy no está de acuerdo y
dice: “No, alguien tiene que coordinar la distribución de efectivo. El distribuidor automático
necesita saber si hay efectivo disponible y si el cliente tiene suficiente en la cuenta, y tiene
que contar el dinero y saber cuándo cerrar la caja. Debe delegar en algún tipo de cuenta
interna la responsabilidad de mantener el registro del efectivo disponible. Cualquiera que
sepa sobre el efectivo disponible también puede notificar al sistema de apoyo de la oficina
cuando sea tiempo de volver a llenar con efectivo el cajero automático. De no ser así, se
está pidiendo demasiado al distribuidor automático”.
La discusión continúa. Al levantar tarjetas e interactuar entre todos, se descubren los
requerimientos y las oportunidades a delegar; cada clase toma vida, y se aclaran sus
responsabilidades. Cuando el grupo se atora en las cuestiones de diseño, el moderador
puede tomar una decisión y ayudar a que el grupo avance.
Limitaciones de las tarjetas CRC
Aunque las tarjetas CRC pueden ser una herramienta poderosa para empezar el diseño,
tienen limitaciones inherentes. El primer problema es que no tienen un escalamiento exitoso.
En un proyecto muy complejo, puede agobiarse con las tarjetas CRC; el simple hecho de
mantener un registro de todas ellas puede ser difícil.
Las tarjetas CRC tampoco capturan la interrelación entre clases. Aunque es cierto que
todas las colaboraciones se toman en cuenta, la naturaleza de la colaboración no se mo
dela bien. Al ver las tarjetas CRC no se puede saber si las clases se agregan unas a otras,
quién crea a quién, etc. Las tarjetas CRC tampoco capturan los atributos, por lo que es
difícil pasar de las tarjetas CRC al código. Lo que es más importante, las tarjetas CRC
son estáticas; aunque se pueden representar las interacciones entre clases, las tarjetas
CRC no capturan esta información por sí mismas.
En resumen, las tarjetas CRC son un buen inicio, pero necesita mover las clases hacia el
UML si va a crear un modelo robusto y completo de su diseño. Aunque la transición hacia
el UML no es muy difícil, es de un solo sentido. Después de mover las clases hacia los
diagramas del UML, ya no hay regreso; deja a un lado las tarjetas CRC y ya no regresa a
ellas. Simplemente es demasiado difícil mantener los dos modelos sincronizados entre sí.
A n á lis is y d is e ñ o o r ie n t a d o s a o b j e t o s 647
C ó m o t r a n s f o r m a r la s ta r je t a s C R C en U M L
Cada tarjeta C R C puede convertirse directamente en una clase m odelada con UM L. Las
resp o n sab ilid ad es se co n v ierten en m étodos de la clase, y los atributos que se tengan
capturados tam bién se agregan. La definición de la clase de la parte trasera de la tarjeta
se coloca en la docum entación de la clase. La figura 18.13 muestra la relación existente
entre la tarjeta CR C de CuentaDeCheques y la clase en UML creada a partir de esa tarjeta.
Clase: CuentaDeCheques
Superclase: Cuenta
Responsabilidades:
R egistrar el balance actual
A ceptar depósitos y transferencias
L lenar cheques
E ntregar efectivo
M antener el balance diario de retiros del cajero automático
Colaboraciones:
O tras cuentas 18
Sistem as de apoyo para las oficinas
D istribuidor autom ático de efectivo
F i g u r a 18.13 « A b s tra c ta »
Tarjeta CRC. Cuenta
-k
Cuenta de cheques
B a l a n c e : in t
D i a s R e t i r o C a j e r o A u t o m a t i c o : in t
O b t e n e r B a l a n c e Q : in t
D e p o s it o ( in t c a n t id a d ) ( ) : v o id
T r a n s f e r e n c ia D e n t r o ( in t c a n t id a d ) ( ) : b o o l
T r a n s f e r e n c i a F u e r a ( ) : in t
L le n a r C h e q u e s ( in t c a n t id a d ) ( ) : b o o l
648 Día 18
Figura 18.14
Herencia falsa.
Figura 18.15
Agregación.
El diagrama de la figura 18.15 indica que un auto tiene un volante, cuatro ruedas y de
dos a cinco puertas. Éste es un modelo más preciso de la relación entre un auto y sus
partes. Observe que el rombo del diagrama no está relleno; esto se debe a que estamos
modelando esta relación como una agregación, no como una composición. La composi
ción implica el control del tiempo de vida del objeto. Aunque el auto tien e llantas y una
puerta, las llantas y la puerta pueden existir antes de ser parte del auto y pueden seguir
existiendo cuando ya no sean parte del auto.
650 Día 18
La figura 18.16 modela la composición. Este modelo dice que el cuerpo no es sólo una
agregación de una cabeza, dos brazos y dos piernas, sino que estos objetos (cabeza, brazos
y piernas) se crean cuando el cuerpo se crea, y desaparecen cuando el cuerpo desaparece.
Es decir, no tienen existencia independiente; el cuerpo se compone de estas cosas y sus
tiempos de vida están entrelazados.
F ig u r a 18.16
Com posición.
Fig u r a 18.17
M o d e la d o d e s u b tip o s .
Fig u r a 18.18
M o d e la d o d e l
d isc rim in a d o r.
El diagram a de la figura 18.18 indica que las clases se pueden derivar de Auto con base
en la mezcla y la relación de los tres atributos discriminatorios. El tamaño del motor estipula
qué tan poderoso es el auto, y las características de rendimiento indican qué tan deportivo
es. Por lo tanto, puede tener una vagoneta deportiva potente, un sedán fam iliar de poca
potencia, y así por el estilo.
C ada atributo se puede im plem entar con un simple enumerador. Por ejemplo, el tipo de
chasis se podría im plem entar en el código con la siguiente instrucción:
enum T ip o C h a s is = { sedan, deportivo, minivan, vagoneta };
Sin em bargo, puede pasar que un simple valor sea insuficiente para modelar un discrim i-
nador específico. Por ejemplo, la característica de rendimiento puede ser bastante compleja.
En este caso se puede m odelar el discriminador como una clase, y la discrim inación se
puede encapsular en una instancia de ese tipo.
652 Día 18
Por ejemplo, el auto podría modelar las características de rendimiento en un tipo llamado
r e n d im ie n to , el cual contiene información acerca de cuándo cambia velocidades el motor
y qué tan rápido puede girar. El estereotipo de UML para una clase que encapsula aun
discriminador, y que se puede utilizar para crear in s ta n c ia s de una clase (Auto) que sean
lógicamente de tipos distintos (por ejemplo, AutoDeportivo comparado con AutoFamiliar)
es "tipo de poder". En este caso, la clase Rendimiento es un tipo de poder para auto. Al
distanciar a Auto, también se crea una instancia de un objeto Rendimiento, y se asocia a un
objeto Rendimiento dado con un Auto dado, como se muestra en la figura 18.19.
F ig u r a 1 8 . 1 9
Un discrim in ador
com o tipo de poder.
Los tipos de poder le permiten crear una variedad de tipos lógicos sin utilizar herencia.
Gracias a esto puede manejar un conjunto grande y complejo de tipos sin la explosión
combinatoria que podría encontrarse con la herencia.
Por lo general, el tipo de poder se im p le m e n to en C++ con apuntadores. En este caso, la
clase Auto contiene un apuntador a una instancia de la clase CaracteristicasRendimiento
(vea la figura 18.20). Dejo como ejercicio para el lector ambicioso convertir los discrimi-
nadores de chasis y de motor en tipos de poder.
F ig u r a 18.20
La relación entre un
objeto A u to y su tipo
de poder.
A n á lis is y d is e ñ o o r ie n t a d o s a o b je t o s 653
M o d e lo d in á m ic o
Adem ás de m odelar las relaciones entre las clases, es muy importante modelar la forma
en que interactíían. Por ejem plo, volviendo al escenario de las actividades bancarias, las
18
clases CuentaDeCheques, CajeroAutom atico y Recibo pueden interactuar con el C l i e n t e
para com pletar el caso de uso “Retiro de efectivo”. Regresemos a los diagramas de secuen
cia utilizados inicialm ente en el análisis, pero ahora desarrollemos los detalles con base
en los m étodos que desarrollam os en las clases, como se muestra en la figura 18.21.
Este sim ple diagram a de interacción muestra la interacción entre un número de clases de
diseño con el paso del tiempo. Sugiere que la clase Caj eroAutomatico delegue en la clase
CuentaDeCheques toda la responsabilidad de manejar el balance, mientras que C u e n ta
DeCheques se ap o y ará en la clase Caj eroAutomatico para m anejar el despliegue de
inform ación para el usuario.
Los diagram as de interacción pueden ser de dos tipos. El de la figura 18.21 se llama
d ia g ra m a d e secu en cia . Se proporciona otra vista de la misma información por medio
del d ia g ra m a d e colaboración. El diagrama de secuencia enfatiza la secuencia de even
tos con el paso del tiempo; el diagrama de colaboración enfatiza las interacciones entre
las clases. Puede generar un diagrama de colaboración directamente de un diagrama de
secuencia; las herram ientas com o el programa Rational Rose automatizan esta tarea con
el clic de un botón (vea la figura 18.22).
654 Día 18
Figura 18.21
D iagram a de
secuencia.
Fig u r a 18.22
D iagram a de
colaboración.
D i a g r a m a s d e transición d e e s t a d o
Para llegar a entender las interacciones entre los objetos, tenemos que entender los diversos
e s ta d o s posibles de cada objeto individual. Podemos modelar las transiciones entre los
diversos estados en un diagrama de estado (o diagrama de transición de estado). La figura
18.23 muestra los diversos estados de la clase CuentaCliente cuando el cliente entra al
sistema.
Análisis y diseño orientados a objetos 655 |
T
Sin inicio de sesión
Estado
Protección
>r
^ Sesión iniciada
Cada diagrama de estado empieza con un solo estado inicio y termina con cero o más ÍK
estados finales. Los estados individuales tienen nombre, y las transiciones se pueden
etiquetar, protección indica una condición que se debe satisfacer para que un objeto
pase de un estado a otro.
Superestados
El cliente puede cambiar de opinión en cualquier momento y decidir no entrar al sistema.
Puede hacerlo después de introducir su tarjeta para identificar su cuenta, o después de
escribir su contraseña. En cualquier caso, el sistema debe aceptar su petición de cancelar
y regresar al estado “Sin inicio de sesión” (ver la figura 18.24).
Como puede ver, en un diagrama más complicado el estado Cancelado se convertirá
rápidamente en una distracción. Esto es muy molesto ya que la cancelación es una condi
ción excepcional que no debe tener importancia en el diagrama. Puede simplificar este
diagrama utilizando un superestado, como se muestra en la figura 18.25.
656 Día 18
F ig u r a 1 8 . 2 4
El usuario pu ede
cancelan ? ”
F ig u r a 1 8 . 2 5
Superestado.
T"
Como resultado, las com pañías por lo general se apresuran o escatiman en la fase de análisis
para crear la apariencia de estar progresando y mantener a los program adores ocupados.
Desgraciadam ente, esto produce una “solución” que no resuelve el problema esencial. Este
hecho se descubre cuando el código está a punto de ser terminado. Debido a esto, los pro
gram adores tienen que regresar y arreglar las diferencias, y el resultado es que trabajan
apurados, hay errores en el software, los tiempos programados para la entrega del software
no se cum plen y se tienen soluciones incompletas.
Trate de resistir las presiones organizacionales. Trabaje para tener un análisis com pleto
antes de em pezar la codificación. De ser necesario, puede presentar información relacionada
con proyectos que fallaron por codificar antes de tiempo.
Claro que ésta no es una excusa para perm itir una “parálisis en el análisis” en la que el
análisis continúa eternam ente sin resultados ni progreso. Hay una línea delgada que estable 18
ce la distinción entre un análisis com pleto y uno que se va a paralizar. Por desgracia, la
experiencia es con lo único que se puede determinar la diferencia.
Existe el concepto “lo suficientem ente bueno”. Es muy difícil hacer algo verdaderam ente
"com pleto”. Hay una sentencia en esta industria que establece que el 80% de los resultados
ocupa un 20% del tiempo y el 20% restante ocupa el 80% del tiempo. En algún m om ento
debe tom ar la decisión de avanzar al siguiente paso, o empezará a sufrir lo que se conoce
como parálisis en el análisis (nunca llegar al producto final porque está tratando de obtener
el m odelo perfecto).
Por desgracia, el m ejor momento para determinar si el análisis y el diseño son “lo suficien
temente buenos” es una vez que termina el proyecto. Si el código que entrega cumple con
los requerimientos organizacionales, entonces fue lo suficientemente bueno. Si faltan piezas
o características, esto tiende a indicar que no invirtió tiempo ni esfuerzo suficientes. Si
hay características que nadie quiere o nadie sabe para qué son, eso indica que invirtió
dem asiado tiem po en el diseño.
Sólo m irando hacia atrás y aprendiendo de los resultados podrá saber si el esfuerzo
invertido en un diseño es lo suficientemente bueno.
¿Qué tan bueno o com pleto tiene que ser su análisis? ¿Qué tanto es lo suficientem ente
bueno? Sólo usted y su organización pueden decidir esto. Pero ¡piénselo! Hay demasiadas
organizaciones que no piensan sobre el proceso y continuamente entregan software que se
pasó del tiempo programado, del presupuesto o que no tiene las características necesarias.
658 Día 18
R e su m e n
Esta lección le proporcionó una introducción a las cuestiones relacionadas con el análisis
y el diseño orientados a objetos. La esencia de este método es analizar la forma en que se
utilizará su sistema (casos de uso) y cómo debe funcionar, y luego diseñar las clases y
modelar sus relaciones e interacciones.
En el pasado bosquejábamos una idea de ló que queríamos lograr y empezábamos a escribir
el código. El problema es que los proyectos complejos nunca se terminan; y en caso de
terminarse, son poco confiables y frágiles. Al dedicamos a comprender los requerimientos
y a modelar el diseño, aseguramos que el producto terminado esté correcto (es decir, que
cumpla con el diseño) y que sea robusto, confiable y extensible.
Las cuestiones relacionadas con la prueba y la distribución están más allá del alcance de
este libro, por lo que sólo queda mencionar que debe planear la prueba de sus unidades a
medida que implementa, y que utilizará el documento de requerimientos como la base de
su plan de prueba previo a la distribución.
P re g u n ta s y respuestas
P ¿En qué forma el análisis y el diseño orientados a objetos son fundamental
mente distintos de otros métodos?
R Antes del desarrollo de estas técnicas orientadas a objetos, los analistas y los pro
gramadores pensaban en los programas como grupos de funciones que actuaban
sobre los datos. La programación orientada a objetos se enfoca en los datos y la
funcionalidad integrados como unidades discretas que tienen tanto conocimiento
(datos) como capacidades (funciones). Por otro lado, los programas procedurales
se enfocan en las funciones y la forma en que actúan sobre los datos. Se ha dicho
que los programas escritos en Pascal y en C son colecciones de procedimientos, y
los programas de C++ son colecciones de clases.
P ¿Es la programación orientada a objetos la bala de plata que resolverá todos
los problemas de programación?
R No, nunca se planeó que fuera así. Sin embargo, para problemas grandes y complejos,
el análisis, el diseño y la programación orientados a objetos pueden proporcionar
herramientas al programador para que pueda manejar una enorme complejidad en
formas que anteriormente eran imposibles.
P ¿Es C++ el lenguaje orientado a objetos perfecto?
R C++ tiene varias ventajas y desventajas al compararlo con otros lenguajes alterna
tivos de programación orientada a objetos, pero tiene una ventaja inigualable sobre
todos los demás; es el lenguaje de programación orientada a objetos más popular
en todo el mundo. Francamente, la mayoría de los programadores no decide
Análisis y diseño orientados a objetos 659
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del ma
terial tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate de
responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios", y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Cuál es la diferencia entre programación orientada a objetos y programación
procedural?
2. ¿Cuáles son las fases del análisis y del diseño orientados a objetos?
3. ¿Cómo se relacionan los diagramas de secuencia y los diagramas de colaboración?
Ejercicios
1. Suponga que tiene que simular la intersección de la avenida Massachusetts con la
calle Vassar (dos caminos típicos de dos carriles, con semáforos y cruce de peatones).
El propósito de la simulación es determinar si la sincronización del semáforo permite
un flujo continuo de tráfico.
¿Qué tipos de objetos debe modelar en la simulación? ¿Cuáles serían las clases para
la simulación?
2. Suponga que la intersección del ejercicio 1 está en un suburbio de Boston, que sin
duda tiene las calles menos amigables de todo Estados Unidos. A cualquier hora hay
tres tipos de conductores en Boston:
Los locales, quienes siguen conduciendo por las intersecciones aunque el semáforo
esté en rojo; los turistas, que manejan lenta y cautelosamente (por lo general, en un
auto rentado); y los taxistas, que tienen una amplia variedad de patrones de manejo,
dependiendo de los tipos de pasajeros que lleven.
Además, Boston tiene dos tipos de peatones: los locales, que cruzan la calle cuando
les da la gana y raras veces utilizan las áreas para cruce de peatones; y los turistas,
quienes siempre utilizan las áreas para cruce de peatones y cruzan sólo cuando el
semáforo lo permite.
660 Día 18
Finalmente, Boston tiene ciclistas que nunca ponen atención a las señales de alto.
¿Cómo cambian el modelo estas consideraciones?
3. Diseñe un programador de grupos. Este software le permite programar juntas entre
individuos o grupos y reservar un número limitado de salones para conferencias.
Identifique los subsistemas principales.
4. Diseñe y muestre las interfaces para las clases del módulo de reservación de salones
del programa que se describe en el ejercicio 3.
Semana 3
D ía 1
P la n tilla s
Una nueva y poderosa herram ienta para los programadores de C++ son los
“tipos p a ra m etriz a d o s” o plantillas. Las plantillas son tan útiles que en la
definición del lenguaje C++ se ha adoptado la STL (Biblioteca Estándar de
Plantillas).
Para solucionar este problema, puede crear una clase base llamada Lista y de ahí derivar
las clases ListaPiezas y ListaGatos. Luego podría cortar y pegar la mayor parte de la
clase ListaPiezas en la nueva declaración de ListaGatos. La siguiente semana, cuando
quiera crear una lista de objetos llamados Auto, tendrá que crear una nueva clase, y de
nuevo usar los métodos cortar y pegar.
Sin necesidad de decirlo, ésta no es una solución satisfactoria. Con el tiempo, tendrá que
extender la clase Lista y sus clases derivadas. Asegurarse de que todos los cambios se
propaguen a todas las clases relacionadas sería una pesadilla.
Las plantillas resuelven este problema, y con la adopción del estándar ANSI son una parte
integral del lenguaje. Como todo lo que hay en C++, tienen seguridad de tipos y son muy
flexibles.
T ip o s param etrizados
Las plantillas le enseñan al compilador cómo crear una lista de cualquier tipo de objeto,
en lugar de crear un conjunto de listas de tipos específicos (una ListaPiezas es una lista
de piezas, una ListaGatos es una lista de gatos. En lo único que son diferentes es en el
tipo del objeto que hay en la lista). Con las plantillas, el tipo de objeto de la lista se
convierte en un parámetro para la definición de la clase.
Un componente común de casi todas las bibliotecas de C++ es una clase de arreglos. Como
vio con las clases Lista, es tedioso e ineficiente crear una clase de arreglos para enteros,
otra para dobles, y otra más para un arreglo de objetos Animal. Las plantillas le permiten
declarar una clase de arreglos parametrizada y luego especificar qué tipo de objeto guardará
cada instancia del arreglo. Hay que tener en cuenta que la STL (Biblioteca Estándar de
Plantillas) proporciona un conjunto estandarizado de clases c o n te n e d o r a s , incluyendo
arreglos, listas, etc. En esta lección verá lo que necesita para hacer sus propias plantillas y
para entender perfectamente su funcionamiento; sin embargo, en un programa comercial,
muy probablemente utilice las clases de la STL en lugar de las suyas.
4 pu b lic:
5 A r r e g l o ( );
6 // aquí va la declaración completa de la clase
7: };
La palabra reservada témplate se utiliza al principio de cualquier declaración y definición
de una clase de una plantilla. Los parámetros de la plantilla van después de la palabra
reservada témplate. Los parám etros son las cosas que cambiarán con cada instancia. Por
ejemplo, en la plantilla del arreglo mostrado anteriormente va a cambiar el tipo de los obje
tos guardados en el arreglo. Una instancia podría guardar un arreglo de enteros, y otra
podría guardar un arreglo de objetos Animal.
En este ejemplo se utiliza la palabra reservada c la ss seguida del identificador T. La palabra
reservada c la s s indica que este parámetro es un tipo. El identificador T se utiliza en el resto
de la definición de la plantilla para referirse al tipo parametrizado. Una instancia de esta cla
se substituirá a T con i n t en cualquier parte en que aparezca, y otra lo substituirá con Gato.
Para declarar una instancia i n t y una instancia Gato de la clase parametrizada A rreglo,
se escribiría lo siguiente:
A r r e g lo < in t > u n A r r e g lo In t ;
Arreglo<Gato> unArregloG ato;
1: #include <iostream.h>
2:
3: //Listado 19.1 La p l a n t i l l a de una clase de tipo arreglo
4:
5: const in t TamanioPredet = 10;
6:
7: // dec lara r la p l a n t i l l a y el parámetro
8: témplate < c la s s T >
9: // la clase que se va aparametrizar
10 : c l a s s A rreglo
11 : {
12 : public:
13: // constructores
continua
| 662 Día 19
Para solucionar este problema, puede crear una clase base llamada Lista y de ahí derivar
las clases ListaPiezas y ListaGatos. Luego podría cortar y pegar la mayor parte de la
clase ListaPiezas en la nueva declaración de ListaGatos. La siguiente semana, cuando
quiera crear una lista de objetos llamados Auto, tendrá que crear una nueva clase, y de
nuevo usar los métodos cortar y pegar.
Sin necesidad de decirlo, ésta no es una solución satisfactoria. Con el tiempo, tendrá que
extender la clase Lista y sus clases derivadas. Asegurarse de que todos los cambios se
propaguen a todas las clases relacionadas sería una pesadilla.
Las plantillas resuelven este problema, y con la adopción del estándar ANSI son una parte
integral del lenguaje. Como todo lo que hay en C++, tienen seguridad de tipos y son muy
flexibles.
T ip o s p aram etrizados
Las plantillas le enseñan al compilador cómo crear una lista de cualquier tipo de objeto,
en lugar de crear un conjunto de listas de tipos específicos (una ListaPiezas es una lista
de piezas, una ListaGatos es una lista de gatos. En lo único que son diferentes es en el
tipo del objeto que hay en la lista). Con las plantillas, el tipo de objeto de la lista se
convierte en un parámetro para la definición de la clase.
Un componente común de casi todas las bibliotecas de C++ es una clase de arreglos. Como
vio con las clases Lista, es tedioso e ineficiente crear una clase de arreglos para enteros,
otra para dobles, y otra más para un arreglo de objetos Animal. Las plantillas le permiten
declarar una clase de arreglos parametrizada y luego especificar qué tipo de objeto guardará
cada instancia del arreglo. Hay que tener en cuenta que la STL (Biblioteca Estándar de
Plantillas) proporciona un conjunto estandarizado de clases c o n te n e d o r a s , incluyendo
arreglos, listas, etc. En esta lección verá lo que necesita para hacer sus propias plantillas y
para entender perfectamente su funcionamiento; sin embargo, en un programa comercial,
muy probablemente utilice las clases de la STL en lugar de las suyas.
4: p u b lic :
5: A r r e g l o ();
6: // aquí va la declaración completa de la clase
7: };
La palabra reservada tém plate se utiliza al principio de cualquier declaración y definición
de una clase de una plantilla. Los parámetros de la plantilla van después de la palabra
reservada tém plate. Los parám etros son las cosas que cambiarán con cada instancia. Por
ejemplo, en la plantilla del arreglo mostrado anteriormente va a cambiar el tipo de los obje
tos guardados en el arreglo. Una instancia podría guardar un arreglo de enteros, y otra
podría guardar un arreglo de objetos Animal.
En este ejem plo se utiliza la palabra reservada c la s s seguida del identificador T. La palabra
reservada c l a s s indica que este parámetro es un tipo. El identificador T se utiliza en el resto
de la definición de la plantilla para referirse al tipo parametrizado. Una instancia de esta cla
se substituirá a T con i n t en cualquier parte en que aparezca, y otra lo substituirá con Gato.
Para declarar una instancia i n t y una instancia Gato de la clase parametrizada A rreg lo ,
se escribiría lo siguiente:
A r r e g lo < in t > u n A r r e g l o I n t ;
Arreglo<Gato> unArregloGato;
1: tfinclude <iostream.h>
2:
3: //Listado 19.1 La p l a n t i l l a de una clase de tipo arreglo
4:
5: const in t TamanioPredet = 10;
6:
7: // d e c la ra r la p l a n t i l l a y el parámetro
8: témplate < c l a s s T >
9: // la c la se que se va a parametrizar
10 c l a s s A rre g lo
11 {
12 p u b lic :
13 // c o nstru cto res
continúo
664 Día 19
L istado 19.1 c o n t in u a c ió n
La declaración en la prim era línea de este fragmento de código se requiere para identificar
el tipo ( c l a s s T). El nom bre de la plantilla es A rre g lo < T >, y el nombre de la función
es A r r e g l o ( i n t tam anio).
El resto de la función es igual a com o sería para una función que no sea de una plantilla.
Un m étodo com ún es hacer que la clase y sus funciones trabajen como una simple decla
ración antes de convertirla en una plantilla.
Implemmemtación de la plantilla
La im plem entación com pleta de la clase de la plantilla A rre g lo requiere que se implemen-
ten el constructor de copia, o p erato r= , y así sucesivamente. El listado 19.2 proporciona un
program a controlador sim ple para practicar con esta clase de plantilla.
Entrada g
L istado 19.2 c o n t in u a c ió n
21 int suPeso;
22 };
23
24 Animal::Animal(int peso):
25 suPeso(peso)
26 {}
27
28 Animal::Animal():
29 suPeso(0)
30 {}
31
32 // declarar la plantilla y el parámetro
33 témplate < class T >
34 // la clase que se va a parametrizar
35 class Arreglo
36 {
37 public:
38 // constructores
39 Arreglo(int suTamanio = TamanioPredet);
40 Arreglo(const Arreglo & rhs);
41 -Arreglo()
42 { delete [] apTipo; }
43 // operadores
44 Arreglo & operator=(const Arreglo &);
45 T & operator[](int desplazamiento)
46 { return apTipo[ desplazamiento ]; }
47 const T & operator[](int desplazamiento) const
48 { return apTipo[ desplazamiento ]; }
49 // métodos de acceso
50 int ObtenerTamanio() const
51 { return suTamanio; }
52 private:
53 T * apTipo;
54 int suTamanio;
55 };
56
57 // las implementaciones están a continuación...
58 // implementar el Constructor
59 témplate < class T >
60 Arreglo< T >::Arreglo(int tamanio):
61 suTamanio(tamanio)
62 {
63 apTipo = new T[ tamanio ];
64
65 for (int i = 0 ; i < tamanio; i++)
66 apTipo( i l = 0 ;
67 }
68
69 // constructor de copia
70 témplate < class T >
P la n t illa s 667
elArreglo[0]: 0 elZoologico[0] 0
elArreglo[1 ]: 2 elZoologico[1] 3
elArreglo[2]: 4 elZoologico[2] 6
elArreglo[3]: 6 elZoologico(3] 9
elArreglo[4]: 8 elZoologico[4] 12
elArreglo[5]: 10 elZoologico(5] 15
elArreglo[6]: 12 elZoologico[6] 18
elArreglo[7]: 14 elZoologico[7] 21
elArreglo[8]: 16 elZoologico[8] 24
elArreglo[9]: 18 elZoologico[9] 27
Las líneas 10 a 30 proporcionan una clase Animal simplificada, creada aquí para
que los objetos de un tipo definido por el usuario estén disponibles para agregar
al arreglo.
La línea 33 declara que lo que sigue a continuación es una plantilla y que el parámetro
para la plantilla es un tipo, designado como T. La clase Arreglo tiene dos constructores
como se muestra, el primero de los cuales toma un tamaño y utiliza como valor predeter
minado la constante de tipo entero llamada TamanioPredet.
Se declaran los operadores de asignación y de desplazamiento, el último de los cuales
declara dos variantes, una const y una que no es const. El único método de acceso pro
porcionado es ObtenerTamanio(), el cual regresa el tamaño del arreglo.
Ciertamente, uno podría imaginar una interfaz más completa y, para cualquier clase seria
de tipo Arreglo, lo que se ha proporcionado aquí sería inadecuado. Como mínimo se
requerirían los operadores para quitar elementos, expandir el arreglo, empacar el arreglo,
etc. Todo esto lo proporcionan las clases contenedoras de la STL, como se explica al final
de esta lección.
Los datos privados constan del tamaño del arreglo y de un apuntador al arreglo de objetos
que está actualmente en memoria.
Funciones de plantillas
Si quiere pasar un objeto tipo arreglo a una función, debe pasar una instancia específica
del arreglo, no una plantilla. Por lo tanto, si U n a F u n c i o n ( ) toma un arreglo de enteros
como parámetro, puede escribir
void UnaFuncion(Arreglo< int > & ) ; / / ok
pero no puede escribir
void UnaFuncion(Arreglo< T > &); // ¡error!
porque no hay forma de saber qué es un T. Tampoco puede escribir
void UnaFuncion(Arreglo &); // ¡error!
Para llegar al m étodo más general, debe declarar una función de plantilla,
témplate < c l a s s T >
void M iF u n c i o n P la n t i lla ( A r r e g lo < T > &); // ok
O bserve que esta función tom a dos arreglos: un arreglo parametrizado y un arreglo de
enteros. El prim ero puede ser un arreglo de cualesquier objetos, pero el segundo siem pre
será un arreglo de enteros.
Entrada L is t a d o 1 9 . 3 F u n c ió n a m ig a q u e n o es d e p la n tilla
L is t a d o 19.3 c o n t in u a c ió n
elZoologico[0]: 0
S a l id a elZoologico! 1]: 3
elZoologico!2]: 6
elZoologico[3]: 9
elZoologico[4]: 12
elZoologico[5]: 15
elZoologico[6]: 18
elZoologico[7]: 21
elZoologico[8]: 24
elZoologico[9]: 27
Usar ahora la función amiga para encontrar los miembros de Arreglo<int>
*** Intruso ***
i: 0
i: 2
i: 4
i: 6
i: 8
i: 10
i: 12
i: 14
i: 16
i: 18
Listo.
P la n t illa s 673
Ahora que operator<< es una función de plantilla, sólo necesita proporcionar una im-
plementación. El listado 19.4 muestra la plantilla Arreglo extendida para incluir esta
declaración, y proporciona la implementación para operator«.
19
Entrada L is t a d o 1 9 . 4 U s o del o p e ra d o r ostream
71 :
72: for ( in t i = 0; i < tamanio; i++)
73: apTipof i ] = 0;
74: }
75:
76: // c o n s t r u c t o r de copia
77: témplate < c l a s s T >
78: A rre g lo < T >: :A rre g lo (c o n s t Arreglo & rhs)
79: {
80: suTamanio = rhs.ObtenerTamanio();
81 : apTipo = new T[ suTamanio ];
82:
83: for ( in t i = 0; i < suTamanio; i++)
84: ap T ip o [ i ] = rhs [ i ];
85: }
86:
87: // operator=
88: témplate < c l a s s T >
89: A rre g lo < T > & Arreglo< T >: :operator=(const Arreglo & rhs)
90: {
91 : if ( t h i s == & rhs)
92: return * t h i s ;
93: delete [] apTipo;
94: suTamanio = rhs.ObtenerTamanio();
95: apTipo = new T[ suTamanio ];
96: f o r ( in t i = 0; i < suTamanio; i++)
97: apTipo[ i ] = rhs[ i ];
98: return * t h i s ;
99:
100
101 in t main()
102 {
103
104
// in d ic a d o r para el c ic lo
bool Detener = f a ls e ; 19
105 i n t desplazamiento, valor;
106 A rre g lo < in t > elArreglo;
107
108 w hile ( ! Detener)
109 {
110 cout << "E s c rib a un desplazamiento (0-9) ";
111 cout « "y un valor. (-1 para detener): " ;
112 cin » desplazamiento » valor;
113 i f (desplazamiento < 0)
114 break;
115 i f (desplazamiento > 9)
116 {
117 cout « " * * * u t i l i c e valores entre 0 y 9 . * * * \ n " ;
118 continué;
119 }
120 e lA rre g lo [ desplazamiento ] = valor;
121 >
122 cout << "\nHe aqui el arreglo completo:\ n " ;
123 cout << e lA rre g lo << endl;
124 return 0 ;
125 }
676 Día 19
Debe escribir -1 dos veces para que se pueda completar el programa. Esto se
Nota debe a que cin espera dos números enteros. Si sólo escribe -1 una vez, el
programa se quedará esperando la segunda entrada (sin proporcionar un
indicador de ningún tipo).
Así que, si utiliza ejemplos de otros libros y obtiene ese mensaje, necesitará
hacer el cambio sugerido en el código (como se muestra en la línea 50 del
listado 19.4).
[2] 20
[3] 30
[4] 40
[5] 50
[6] 60
[7] 70
[8] 80
[9] 90
a rre g lo A n im a l
[0] 0
[1] 100
[2] 200
[3] 300
[4] 400
[5] 500
[6] 600
[7] 700
[8] 800
[9] 900
A nálisis
La mayor parte de la implementación de la clase A r r e g l o se omite para ahorrar
espacio. La clase A n i m a l se declara en las líneas 9 a 25. Aunque ésta es una clase
simplificada, proporciona su propio operador de inserción ( « ) para permitir la impresión
de objetos A n i m a l . Lo que se hace es simplemente imprimir el peso actual de A n i m a l.
Observe que A n i m a l tiene un constructor predeterminado. Esto es necesario ya que cuando
se agrega un objeto a un arreglo, se utiliza el constructor predeterminado del objeto para
crearlo. Esto crea algunas dificultades, como veremos más adelante.
En la línea 106 se declara la función F u n c i o n L l e n a r l n t (). El prototipo indica que 19
esta función toma un arreglo de enteros. Observe que ésta no es una función de plan
tilla. F u n c i o n L l e n a r l n t () sólo espera un tipo de arreglo (un arreglo de enteros). De la
misma manera, en la línea 108 se declara la función F u n c i o n L l e n a r A n i m a l () para
tomar un A r r e g l o de objetos de tipo A nim al.
Las implementaciones para estas funciones son distintas, pues llenar un arreglo de enteros
no se hace de la misma forma que llenar un arreglo de objetos de tipo Animal.
Funciones especializadas
Si quita las marcas de comentario de las instrucciones cout de los constructores (líneas
37 y 43) y del destructor (línea 48) de Animal del listado 19.5, puede ver las construcciones
y destrucciones adicionales imprevistas de los objetos de tipo Animal.
Cuando se agrega un objeto a un arreglo, se llama al constructor predeterminado del objeto.
Sin embargo, el constructor de A r r e g l o asigna un 0 al valor de cada miembro del arreglo,
como se muestra en las líneas 91 y 92.
682 Día 19
36 {
37 cout « “animal(int) \n";
38 >
39
40 Animal:¡Animal():
41 suPeso(0)
42 {
43 cout « “animalO \n"¡
44 >
45
46 Animal::-Animal()
47 {
48 cout « “Se destruyó un animal...\n°;
49 }
50
51 // declarar la plantilla y el parámetro
52 témplate < class T >
53 I I la clase que se va a parametrizar
54 class Arreglo
55 {
56 public:
57 Arreglo(int suTamanio = TamanioPredet);
58 Arreglo(const Arreglo & rhs);
59 -Arreglo()
60 { delete [] apTipo; }
61 // operadores
62 Arreglo & operator=(const Arreglo &);
63 T & operator[](int desplazamiento)
64 { return apTipo[desplazamiento]; }
65 const T & operator[ ](int desplazamiento) const
66 { return apTipo[desplazamiento]; }
67 // métodos de acceso
68 int ObtenerTamanio() const
69 { return suTamanio; }
70 // función amiga
71 friend ostream & operator« <> (ostream &, const Arreglo< T > &);
72 private:
73 T *apTipo;
74 int suTamanio;
75 };
76
77 témplate < class T >
78 Arreglo< T >: :Arreglo(int tamanio = TamanioPredet):
79 suTamanio(tamanio)
80 {
81 apTipo = new T[ tamanio ];
82
83 for (int i = 0; i < tamanio; i++)
84 apTipo[ i ] = (T)0;
85 }
86
87 témplate < class T >
88 Arreglo< T > & Arreglo< T >: :operator=(const Arreglo & rhs)
89 {
90 if (this == &rhs)
continúa
684 Día 19
91 : return *this;
92: delete [] apTipo;
93: suTamanio = rhs.ObtenerTamanio();
94: apTipo = new T[ suTamanio ];
95: for (int i = 0; i < suTamanio; i++)
96: apTipo[ i ] = rhs[ i ];
97: return *this;
98:
99:
100 template < class T >
101 Arreglo< T >::Arreglo(const Arreglo & rhs)
102 {
103 suTamanio = rhs.ObtenerTamanio();
104 apTipo = new T[ suTamanio ];
105
106 for (int i = 0; i < suTamanio; i++)
107 apTipo[ i ] = rhs[ i ];
108 }
109
110
111 témplate < class T >
112 ostream & operator« (ostream & salida, const Arreglo< T > & elArreglo)
113 {
114 for (int i = 0; i < elArreglo.ObtenerTamanio(); i++)
115 salida « "[" « i « "] " « elArreglo[ i ] « endl;
116 return salida;
117
118
119 Arreglo< Animal >::Arreglo(int TamanioArregloAnimal):
120 suTamanio(TamanioArregloAnimal)
121
122 apTipo = new Animal[ TamanioArregloAnimal ];
123
124
125 void FuncionLlenarint(Arreglo< int > & elArreglo);
126
127 void FuncionLlenarAnimal(Arreglo< Animal > & elArreglo);
128
129 int main()
130 {
131 Arreglo< int > arreglolnt;
132 Arreglo< Animal > arregloAnimal;
133
134 FuncionLlenarint(arreglolnt);
135 FuncionLlenarAnimal(arregloAnimal);
136 cout « "arreglolnt...\n" « arreglolnt;
137 cout « "\narregloAnimal...\n" « arregloAnimal « endl;
138 return 0;
139
140
141 void FuncionLlenarint(Arreglo< int > & elArreglo)
142
143 bool Detener = false;
P la n t illa s 685
1 animal(
S alida 2 a nim al(
3 animal(
4 E scrib a un desplazamiento (0-9) y un valor. ( -1 para dete n e r): 0 0
5 E scrib a un desplazamiento (0-9) y un valor. ( -1 para dete n e r): 1 1
6 E scrib a un desplazamiento (0-9) y un valor. ( -1 para detene r): 2 2
7 E scrib a un desplazamiento (0-9) y un valor. ( -1 para dete n e r): 3 3
8 E scrib a un desplazamiento (0-9) y un valor. ( -1 para detene r): -1
te» -1
9: anim al(int)
10: Se destruyó un animal...
11 : anim al(int)
12: Se destruyo un animal...
13: a n im a l(in t)
14: Se destruyó un animal...
15: a r r e g l o l n t ...
16: [0] 0
17: [1] 1
686 Día 19
18 [2] 2
19
20 arregloAnimal...
21 [0] 0
22 [1] 10
23 [2] 20
24
25 Se destruyó un animal...
26 Se destruyó un animal...
27 Se destruyó un animal...
28 « < Segunda ejecución » >
29 animal(int)
30 Se destruyó un animal...
31 animal(int)
32 Se destruyó un animal...
33 animal(int)
34 Se destruyó un animal...
35 Escriba un desplazamiento (0-9) y un valor (- 1 para detener): 0 0
36 Escriba un desplazamiento (0-9) y un valor (-1 para detener): 1 1
37 Escriba un desplazamiento (0-9) y un valor (-1 para detener): 2 2
38 Escriba un desplazamiento (0-9) y un valor (-1 para detener): 3 3
39 animal(int)
40 Se destruyó un animal...
41 animal(int)
42 Se destruyó un animal...
43 animal(int)
44 Se destruyó un animal...
45 arreglolnt...
46 [0] 0
47 [1] 1
48 [2] 2
49
50 arregloAnimal...
51 [0] 0
52 [1] 10
53 [2] 20
54
55 Se destruyó un animal...
56 Se destruyó un animal...
57 Se destruyó un animal...
El listado 19.6 reproduce ambas clases en su totalidad para que pueda ver la
A nálisis
creación y destrucción de objetos Animal temporales. El valor de TamanioPredet
se ha reducido a 3 para simplificar la salida.
Cada uno de los constructores y destructores de Animal imprime un enunciado, líneas 37,
43 y 48, que indica el momento en que se le llama.
En las líneas 77 a 85 se declara el comportamiento de plantilla de un constructor de
Arreglo. En las líneas 119 a 123 se muestra el constructor especializado para un Arreglo
de objetos de tipo Animal. Observe que en este constructor especial se permite que el
constructor predeterminado establezca el valor inicial para cada Animal, y no se hace
ninguna asignación explícita.
P la n t illa s 687
La primera vez que se ejecuta este programa se muestra el primer conjunto de la salida. Las
líneas 1 a 3 de la salida muestran los tres constructores predeterminados llamados al crear el
arreglo. El usuario escribe cuatro números, y éstos se introducen en el arreglo de enteros.
La ejecución salta hasta F u n c io n L le n a rA n im a l( ). Aquí se crea un objeto Animal temporal
en el heap, línea 169, y su valor se utiliza para modificar el objeto Animal del arreglo de
la línea 170. En la línea 171 se destruye el Animal temporal. Esto se repite para cada
miembro del arreglo, y se refleja en las líneas 9 a 14 de la salida.
Al final del programa se destruyen los arreglos, y cuando se llama a sus destructores,
también se destruyen todos sus objetos. Esto se refleja en las líneas 25 a 27 de la salida.
Para el segundo conjunto de salida (líneas 29 a 57), se colocan marcas de comentarios en la
implementación especial del constructor del arreglo de animales, en las líneas 119 a 123 del
programa. Cuando se vuelve a ejecutar el programa, se ejecuta el constructor de plantilla
(mostrado en las líneas 77 a 85) cuando se construye el arreglo de objetos de tipo Animal.
Esto ocasiona que se llamen objetos Animal temporales para cada miembro del arreglo
(líneas 83 y 84), y esto se refleja en las líneas 29 a 34 de la salida.
Para todo lo demás, la salida de las dos ejecuciones es idéntica, como se podría esperar.
16 // métodos de acceso
17 int ObtenerPeso() const
18 { return suPeso; >
19 void AsignarPeso(int elPeso)
20 { suPeso = elPeso; }
21 // operadores amigos
22 friend ostream & operator« (ostream &, const Animal &);
23 private:
24 int suPeso;
25 };
26
27 // operador de extracción para imprimir animales
28 ostream & operator« (ostream & elFlujo, const Animal & elAnimal)
29 {
30 elFlujo « elAnimal.ObtenerPeso();
31 return elFlujo;
32 }
33
34 Animal:¡Animal(int peso):
35 suPeso(peso)
36 {
37 //cout « "animal(int) \n";
38
39
40 Animal::Animal():
41 suPeso(0)
42 {
43 //cout « "animal() \n";
44
45
46 Animal::-Animal()
47 {
48 //cout « "Se destruyó un animal...\n";
49
50
51 // declarar la plantilla y el parámetro
52 template < class T >
53 I I la clase que se va a parametrizar
54 class Arreglo
55 {
56 public:
57 // constructores
58 Arreglo(int suTamanio = TamanioPredet);
59 Arreglo(const Arreglo & rhs);
60 -Arreglo()
61 { delete [] apTipo; suNumeroArreglos— ; }
62 // operadores
63 Arreglo & operator=(const Arreglo &);
64 T & operator!](int desplazamiento)
65 { return apTipo[ desplazamiento ]; }
66 const T & operator[](int desplazamiento) const
P la n tilla s 689
continúa
690 Día 19
S a l i d a I 0 arreglos de enteros
0 arreglos de animales
1 arreglos de enteros
1 arreglos de animales
2 arreglos de enteros
1 arreglos de animales
1 arreglos de enteros
1 arreglos de animales
P la n tilla s 691
Es mejor utilizar la versión 2.9.5 para todos los ejemplos restantes de esta
lección. Es el compilador más reciente y tiene características más avanzadas.
D ebe No DEBE 19
DEBE utilizar m iem bros estáticos con las
plantillas cuand o sea necesario.
DEBE especializar el comportamiento de
la plantilla redefiniendo por tipo las fun
ciones de plantilla.
DEBE utilizar los parámetros para funcio
nes de plantilla para que sus instancias
tengan seguridad en los tipos.
E l objetivo de la S T L es ofrecerle una alternativa para que no tenga que volver a inventar
estos requerimientos comunes. L a S T L está probada y depurada, ofrece un alto rendimiento
y es gratis. L o que es más importante, la S T L es reutilizable: después de que comprenda
có m o utilizar un contenedor de la S T L , podrá utilizarlo en todos sus programas sin necesi
dad de reinventarlo.
Contenedores
Un contenedor es un objeto que guarda otros objetos. L a biblioteca estándar de C + + pro
porciona una serie de clases contenedoras que son herram ientas poderosas que ayudan a
los desarrolladores de C + + a m anejar tareas com u n es de program ación. Dos de los tipos
de
de clases contenedoras de la S T L son la de secuencia y la asociativa. L o s contenedores
secuencia están diseñados para proporcionar un acceso secuencial y aleatorio a sus miem
bros, o elementos. Lo s contenedores asociativos están optim izados para tener acceso a sus
elem entos mediante valores clave. Igual que otros com ponentes de la biblioteca estándar
de C + + , la S T L es portable entre varios sistem as operativos. Todas las clases contenedo
ras de la S T L están definidas en el esp acio de nom bres s td .
Contenedores de secuencia
L o s contenedores de secuencia de la S T L proporcionan un acceso secuencial eficiente para
una lista de objetos. L a biblioteca están dar de C + + p ro p o rcio n a tres contenedores de
secuencia: v e c t o r , l i s t y deque.
El contenedor vector
Por lo regular, los arreglos se utilizan para guardar y tener acceso a una variedad de elemen
tos. L o s elementos de un arreglo son del m ism o tipo y se tiene acceso a ellos mediante un
índice. L a S T L proporciona una clase contenedora llam ada v e c t o r que se comporta igual
que un arreglo, pero es más poderosa y segura de utilizar que el arreglo estándar de C++.
Como se mencionó anteriormente, los vectores pueden crecer de manera automática cuando
se les agregan más elementos de los que pueden manejar. Por ejemplo, suponga que una
clase de su escuela se ha vuelto tan popular que el número de estudiantes pasa de 50.
(Bueno, tal vez no ocurra en esta clase de matemáticas, pero quién sabe, a veces ocurren
cosas raras.) Cuando se agregue a ClaseMatematicas el estudiante número 51, Sally, el
compilador expandirá el espacio de almacenamiento para darle alojamiento.
Puede agregar un elemento a un vector de varias formas; una de ellas es con push_back():
ClaseMatematicas.push_back(Sally);
Esta función miembro agrega el nuevo objeto Estudiante al final del vector Clase
Matematicas. Ahora ClaseMatematicas tiene 51 elementos, y Sally se coloca en
ClaseMatematicas[50].
Para que esta función trabaje, la clase Estudiante debe definir un constructor de copia.
De no ser así, la función push_back() no podrá crear una copia del objeto Sally.
La STL no especifica el número máximo de elementos de un vector; los fabricantes de
compiladores son los que toman esta decisión. La clase vector proporciona una función
miembro que le indica cuál es este número mágico en su compilador; max_size(). Para
GNU, el número máximo de elementos depende del tamaño de cada elemento. Sin importar
cuál compilador esté utilizando, la mejor manera de determinar este número es con la
función miembro m ax_size().
El listado 19.8 muestra los miembros de la clase vector descritos hasta ahora. Verá que
en este listado se utiliza la clase strin g estándar para simplificar el manejo de las cade
nas. Para obtener más detalles acerca de la clase strin g , revise la documentación del
compilador GNU.
L is t a d o 19.8 c o n t in u a c ió n
C la s e V a c ia :
S alida max_size() = 536870911 s iz e () = 0 capacityO = 0 vacío
D ebe N O DEBE
D EBE definir un constructor predetermina
do para una clase si existe la posibilidad
de g u a rd a r sus instancias en un vector.
DEBE definir un constructor de copia para
dicha clase.
D EBE definir un op e rad or de asignación
sob re cargado para dicha clase.
S a l id a 2 4 6 8 10 12 14 16 18 20
En la línea 13 se define a lis t a ln t como una lista de enteros. Los primeros 10
A nálisis
números pares positivos se agregan a la lista usando la función push_back() en
las líneas 15 y 16.
En las líneas 17 a 19 accedemos a cada nodo de la lista por medio de un iterador constante.
Esto indica que no tenemos la intención de cambiar los nodos con este iterador. Si queremos
cambiar un nodo a un iterador, necesitamos utilizar un iterador que no sea const:
listalnt::iterator
La función miembro begin() regresa un iterador que apunta al primer nodo de la lista.
Como puede ver aquí, se puede utilizar el operador de incremento ++ para apuntar a un
iterador al siguiente nodo. La función miembro end() es un poco rara: regresa un iterador
que apunta a un nodo que está después del último nodo de una lista. Debido a esto, debe
asegurarse de que su iterador no llegue a end ().
P la n tilla s 701
El iterador es desreferenciado igual que un apuntador para regresar el nodo al que apunta,
como se muestra en la línea 19.
Aunque esta lección presenta el uso de los iteradores con la clase l i s t . la clase vector
también proporciona iteradores. Además de las funciones proporcionadas en la clase
vector, la clase l i s t también proporciona las funciones push_front() y pop_front()
que funcionan igual que push_back() y pop_back(). En lugar de agregar y quitar
elementos de la parte posterior de la lista, agregan y quitan elementos en la parte frontal
de la lista.
El c o n t e n e d o r d e q u e
Un contenedor deque es como un vector con dos extremos: hereda la eficiencia de la clase
contenedora vector en las operaciones secuenciales de lectura y escritura. Pero, además,
la clase contenedora deque proporciona operaciones optimizadas en primer y segundo
planos. Estas operaciones se implementan de manera similar a la clase contenedora l i s t .
en la que las asignaciones de memoria se aplican sólo para nuevos elementos. Esta carac
terística de la clase deque elimina la necesidad de reasignar todo el contenedor a una nueva
ubicación de memoria, como se hace con la clase vector. Por lo tanto, los contenedores
deque están idealmente adaptados para aplicaciones en las que las inserciones y las elimi
naciones ocurren en uno o en ambos extremos, y para las que es importante el acceso
secuencial de los elementos. Un ejemplo de dicha aplicación sería un simulador de
enganche de trenes, en el que los carros se pueden unir al tren en ambos extremos.
El c o n t e n e d o r m a p
Ya vio que un vector es como una versión mejorada de un arreglo. Tiene todas las carac
terísticas de un arreglo y algunas características adicionales. Desafortunadamente, el
vector también sufre de una de las debilidades más considerables de los arreglos: no se
puede tener acceso aleatorio de los elementos por medio de valores clave que no sean el
índice o el iterador. Por otra parte, los contenedores asociativos proporcionan un acceso
aleatorio rápido con base en valores clave.
Como ya se dijo, la biblioteca estándar de C++ proporciona cuatro contenedores asocia
tivos: map. multimap, set y multiset. En el listado 19.10 se utiliza el contenedor map para
implementar el ejemplo de la clase de la escuela que se muestra en el listado 19.8.
|702 Día 19
En tr a d a L is t a d o 1 9 .1 0 La clase c o n te n e d o ra m a p
continúa
704 Día 19
L is t a d o 1 9 .1 0 c o n t in u a c ió n
ClaseMatematicas:
S a lid a Bill: Bill tiene 17 años de edad
Harry: Harry tiene 18 años de edad
Peter: Peter tiene 16 años de edad
Sally: Sally tiene 15 años de edad
También puede utilizar las funciones push_back( ) o i n s e r t ( ) para agregar un par (clave,
valor) al contenedor map; para obtener más detalles acerca de esto, consulte la docu
mentación de su compilador GNU.
Después de que se han agregado todos los objetos de tipo Estudiante al contenedor map.
podemos tener acceso a cualquiera de ellos usando sus claves. En las líneas 102 y 104
utilizamos ClaseMatematicas[ " B i l l " ] para recuperar el registro de Bill.
L os d e m á s c o n t e n e d o r e s a s o c ia t iv o s
La clase contenedora multimap es una clase map sin la restricción de claves únicas. Dos
o más elementos pueden tener la misma clave.
La clase contenedora sel también es similar a la clase map. La única diferencia es que sus
elementos no son pares (clave, valor). Un elemento es sólo la clave.
Por último, la clase contenedora multiset es una clase set que permite claves duplicadas.
Pilas
La pila es una de las estructuras de datos utilizadas con más frecuencia en la programación
de computadoras. Sin embargo, la pila no se implementa como una clase contenedora
independiente. En vez de eso, se implementa como una envoltura de un contenedor. La
clase de plantilla stack se define en el archivo de encabezado <stack> que se encuentra en
el espacio de nombres std.
Una pila es un bloque asignado en forma continua que puede crecer o encoger en su parte
posterior. Sólo se puede tener acceso a los elementos de una pila, y sólo es posible elimi
narlos. desde la parte posterior. Ya ha visto características similares en los contenedores 1 9
de secuencia, especialmente en vector y deque. De hecho, para implementar una pila se
puede usar cualquier contenedor de secuencia que soporte las operaciones back(),
push_back() y pop_back(). Los demás métodos de contenedores no se requieren para la
pila y, por lo tanto, no se exponen aquí.
La clase de plantilla stack está diseñada para contener cualquier tipo de objetos. La única
restricción es que todos los elementos deben ser del mismo tipo.
Una pila es una estructura U F O ( Último en Entrar, Primero en Salir). Es como un elevador
atestado de gente: la primera persona que entra es empujada hacia la pared, y la última
persona está parada justo frente a la puerta. Cuando el elevador llega al piso destinado, la
última persona en entrar es la primera en salir. Si alguien quiere salir del elevador antes,
todos los que están entre esa persona y la puerta deben quitarse del paso, probablemente
saliendo del elevador y luego entrando otra vez.
Por convención, el extremo abierto de una pila se llama tope de la pila, y las operaciones
que se realizan en una pila se llaman push (empujar) y pop (sacar). La clase stack hereda
estos términos convencionales.
706 Día 19
Colas
Una cola es otra estructura de datos utilizada comúnmente en la programación de compu
tadoras. Los elementos se agregan a la cola en un extremo y se sacan por el otro. La
analogía clásica es ésta: una pila es como un montón de platos apilados en una mesa. Se
agrega a la pila colocando un plato encima (empujando la pila hacia abajo), y se quita
de la pila “sacando” el plato de la parte superior (el que se agregó más recientemente a
la pila).
Una cola es como una fila en el cine. Se entra a la cola por atrás, y se sale de la cola por
enfrente. Esto se conoce como estructura FIFO (Primero en Entrar, Primero en Salir)', una
pila es una estructura LIFO (Último en Entrar, Primero en Salir). Claro que, de vez en cuan
do, usted está antes de la última persona en una larga fila del supermercado, cuando alguien
abre una nueva caja y agarra a esa última persona de la línea, y convierte en una pila lo que
debería de ser una cola, lo cual provoca que usted rechine los dientes de frustración.
Al igual que stack, queue (que es el nombre de la clase de tipo cola) se implementa como
una clase de envoltura para un contenedor. El contenedor debe soportar las operaciones
fr o n t(), back(), push_back() y pop_front().
Clases de algoritmos
Un contenedor es un lugar útil para guardar una secuencia de elementos. Todos los contene
dores estándar definen operaciones que manipulan los contenedores y sus elementos. Sin
embargo, implementar todas estas operaciones en sus propias secuencias puede ser labo
rioso y propenso a errores. Debido a que la mayoría de estas operaciones probablemente
sean las mismas en casi todas las secuencias, un conjunto de algoritmos genéricos puede
reducir la necesidad de escribir sus propias operaciones para cada contenedor nuevo. La
biblioteca estándar proporciona aproximadamente 60 algoritmos estándar que realizan las
operaciones más básicas y más utilizadas de los contenedores.
Los algoritmos estándar se definen en <algorithm>, el cual se encuentra en el espacio de
nombres std.
Para comprender la forma en que funcionan los algoritmos, necesita conocer el concepto
de los objetos de funciones. Un objeto de función es una instancia de una clase que define
al operador () sobrecargado. Por lo tanto, se puede llamar como una función. El listado
19.11 muestra un objeto de función.
Plantillas 707
S a l id a 0 12 3 4
for_each()
Salida 0 3 6 9 12
Observe que todos los algoritmos estándar de C++ se definen en <algorithm>, por
A nálisis
lo que debemos incluir este archivo aquí. La mayor parte del programa debe ser fácil
de entender para usted. En la línea 26 se llama a la función, o algoritmo, for_each() para
que pase por todos los elementos del vector vlnt. Para cada elemento, el algoritmo invoca al
objeto de función Hacerlmpresion y pasa el elemento a Hacerlmpresion.operator(). Esto
ocasiona que el valor del elemento se imprima en la pantalla.
A lg o r itm o s d e s e c u e n c ia m u ta n te
Los algoritmos de secuencia mutante realizan operaciones que cambian los elementos de
una secuencia, incluyendo operaciones que llenan o reordenan colecciones. El listado 19.13
muestra el algoritmo f ill ().
6:
7: using namespace std;
8:
9:
10: template< class T >
11 : cla ss Imprimir
12: {
13: public:
14: void operator() (const T & t)
15: { cout « t « " }
16: };
17:
18: int main()
19: {
20: Imprimir< int > Hacerlmpresion;
21 : vector< int > vl nt (10);
22:
23: f i l l ( v l n t . b e g i n ( ), vlnt.begin() + 5, 1);
24: fi l l ( v l n t . b e g i n ( ) + 5, vlnt.end(), 2);
25: for_each(vlnt .begin(), vlnt.end(), Hacerlmpresion);
26: cout « "\n";
27: return 0;
28: }
Sa l i d a 1 1 1 1 1 2 2 2 2 2
El único contenido nuevo en este listado se encuentra en las líneas 23 y 24, en
A nálisis
donde se utiliza el algoritmo f i l l ( ). Este algoritmo llena los elementos de una
secuencia con un valor dado. En la línea 23 asigna el valor entero 1 a los primeros cinco
elementos de vlnt. En la línea 24 asigna el entero 2 a los últimos cinco elementos de vlnt. 1 9
Resumen
Hoy aprendió a crear y utilizar plantillas. Las plantillas son una comodidad integrada en
C++, utilizadas para crear tipos parametrizados (tipos que cambian su comportamiento
con base en los parámetros que reciben al momento de su creación). Son una manera de
reutilizar código en forma segura y efectiva.
La definición de la plantilla determina el tipo parametrizado. Cada instancia de la plantilla
es un objeto en sí, el cual se puede utilizar igual que cualquier otro objeto: como parámetro
para una función, como valor de retorno, etcétera.
Las clases de plantillas pueden declarar tres tipos de funciones amigas: que no sean de
plantilla, de plantilla general y de plantilla de tipo específico. Una plantilla puede declarar
datos miembro estáticos, en cuyo caso cada instancia de la plantilla tiene su propio conjunto
de datos estáticos.
710 Día 19
Preguntas y respuestas
P ¿Por qué utilizar plantillas cuando se tienen las macros?
R Las plantillas ofrecen seguridad de tipos y están integradas en el lenguaje.
P ¿Cuál es la diferencia entre el tipo parametrizado de una función de plantilla
y los parámetros para una función normal?
R Una función normal (que no sea de plantilla) toma parámetros sobre los cuales puede
realizar alguna acción. Una función de plantilla le permite parametrizar el tipo de
un parámetro específico para la función. Es decir, puede pasar un Arreglo de Tipo
a una función y luego determinar el Tipo mediante la instancia de la plantilla.
P ¿Cuándo se deben utilizar las plantillas y cuándo se debe utilizar la herencia?
R Utilice plantillas cuando todo el comportamiento, o casi todo el comportamiento,
permanezca sin cambio, excepto en relación con el tipo del elemento sobre el que
actúa su clase. Si tiene que copiar una clase y cambiar sólo el tipo de uno o más de
sus miembros, tal vez sea un buen momento para considerar el uso de una plantilla.
P ¿Cuándo se deben utilizar clases amigas de plantillas generales?
R Cuando cada instancia, sin importar el tipo, deba ser amiga para esta clase o función.
P ¿Cuándo se deben utilizar funciones o clases amigas de plantillas de tipo
específico?
R Cuando quiera establecer una relación exacta entre dos clases. Por ejemplo,
array<int> debería de concordar con iterator<int>, pero no con
iterator<Animal>.
P ¿Cuáles son los dos tipos de contenedores estándar?
R Los contenedores de secuencia y los contenedores asociativos. Los contenedores de
secuencia proporcionan un acceso secuencial y aleatorio optimizado para sus
elementos. Los contenedores asociativos proporcionan un acceso optimizado a
los elementos por medio de claves.
P ¿Qué atributos debe tener su clase para utilizarla con los contenedores estándar?
R La clase debe definir un constructor predeterminado, un constructor de copia y un
operador de asignación sobrecargado.
Plantillas 711
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del
material tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
C u e stio n a rio
1. ¿Cuál es la diferencia entre una plantilla y una macro?
2. ¿Cuál es la diferencia entre el parámetro de una plantilla y el parámetro de una
función?
3. ¿Cuál es la diferencia entre una clase amiga de plantilla de tipo específico y una
clase amiga de plantilla general?
4. ¿Es posible proporcionar un comportamiento especial para una instancia de una
plantilla, pero no para otras instancias?
5. ¿Cuántas variables estáticas se crean si se coloca un miembro estático en la defini
ción de una clase de plantilla?
6. ¿Qué son los iteradores de la biblioteca estándar de C++?
7. ¿ Q u é es un objeto de función?
Ejercicios
1. C ree u na plantilla con base en esta clase L is ta : 1 9
c la s s L ista
{
p r iv a te :
public:
L i s t a ( ) : cabeza(0),cola(0),laCuenta(0) {}
vi r tu al ~Lista();
void insertar(int valor);
void agregar(int valor);
int esta_presente(int valor) const;
int esta_vacia() const { return cabeza == 0; }
int cuenta() const { return laCuenta; }
p r iv a te :
class CeldaLista
{
public:
CeldaLista(int valor, CeldaLista *celda =):v a l ( v a l o r ) . s i
guiente (cel) {}
712 Día 19
int val;
CeldaLista ‘siguiente;
>;
CeldaLista ‘cabeza;
CeldaLista ‘cola;
int laCuenta;
};
2. Escriba la implementación para la versión (que no sea de plantilla) de la clase Lista.
3. Escriba la versión de plantilla de las implementaciones.
4. Declare tres objetos de tipo lista: una lista de objetos Cadena, una lista de objetos Gato
y una listas de valores de tipo in t.
5. CAZA ERRORES: ¿Qué está mal en el siguiente código? (Suponga que la plantilla
Lista está definida y que Gato es la clase que se definió anteriormente en el libro.)
Lista<Gato> Lista_Gato;
Gato Félix;
ListaGato.agregar(Felix);
cout « "Félix " «
(Lista_Gato.esta_presente(Félix)) ? "" : "no " « " está
presente\n";
E x c e p c i o n e s y m a n e jo
de e rro re s
El código que ha visto en este libro se ha creado para propósitos ilustrativos. No
trata sobre los errores para que usted no se distraiga de los temas principales que
se están presentando. Los programas del mundo real tienen que considerar las
condiciones de error.
Hoy aprenderá lo siguiente:
• Qué son las excepciones
• Cómo se utilizan las excepciones, y qué cuestiones surgen debido a ellas
• Cómo crear jerarquías de excepciones
• Cómo encajan las excepciones en un método general para manejo de errores
• Qué es un depurador
714 Día 20
E x c e p c io n e s
Los programadores utilizan compiladores poderosos y rocían su código con aserciones para
atrapar bugs de programación (las aserciones se explican en el día 21, “Qué sigue”). También
realizan revisiones de diseño y pruebas exhaustivas para encontrar errores lógicos.
Sin embargo, las excepciones son distintas. Usted no puede eliminar circunstancias excep
cionales; sólo puede estar preparado para ellas. A sus usuarios se les agotará la memoria
de vez en cuando, y la única pregunta es qué es lo que usted hará. Sus opciones están
limitadas a las siguientes:
• Hacer que el programa falle
• Informar al usuario y salir con elegancia
• Informar al usuario y permitir que trate de recuperarse y continuar
• Tomar una acción correctiva y continuar sin molestar al usuario
Aunque no es necesario, ni deseable, que cualquier programa que escriba se recupere
automática y silenciosamente de todas las circunstancias excepcionales, está claro que
debe hacer algo mejor que fallar.
El manejo de excepciones de C++ proporciona un método integrado con seguridad de tipos
para hacer frente a las condiciones predecibles pero inusuales que surgen al ejecutar un
programa.
U n a s p a l a b r a s a c e r c a d e la c o r r u p c i ó n d e l c ó d i g o
La corrupción del código es un fenómeno perfectamente probado en el que el software se
deteriora debido a la negligencia. Un programa bien escrito y completamente depurado
se echará a perder en la repisa de su cliente unas cuantas semanas después de su entrega.
Después de unos cuantos meses, su cliente se dará cuenta de que un moho verde ha cubierto
su lógica, y muchos de sus objetos han empezado a desprenderse.
Además de enviar su código fuente en contenedores sellados herméticamente, su única
protección es escribir sus programas de forma que cuando regrese a arreglar lo estropeado,
pueda identificar rápida y fácilmente en donde se encuentran los problemas.
La corrupción del código es algo así como una broma del programador, utili
zada para explicar cómo un código supuestamente libre de errores, de re
pente se vuelve inestable. Sin embargo, esto enseña una lección importante.
Los programas son muy complejos, y los bugs pueden permanecer escondi
dos por mucho tiempo antes de aparecer. Protéjase usted mismo escribiendo
código fácil de mantener.
Un término similar se ha aplicado a los libros impresos. Sin importar qué tan
cuidadosos sean el autor, los revisores técnicos, los editores y los correctores,
los errores aparecen. Pero parece que entre más se lee el libro (cuando éste
se encuentra ya en el mercado), los errores aparecen con más frecuencia.
¡Qué le parece!
716 Día 20
Esto significa que su código debe llevar comentarios, aun si no espera que nadie más lo
vaya a analizar. Seis meses después de entregar su código, lo leerá con los ojos de un
completo extraño, y se preguntará cómo pudo alguien escribir un código tan complicado
y retorcido, y esperar algo que no fuera un desastre.
Excepciones
En C++, una excepción es un objeto que se pasa desde el área del código en la que ocurre
un problema hasta la parte del código que se va a encargar del problema. El tipo de la
excepción determina cuál área de código se encargará del problema, y el contenido del
objeto enviado, si lo hay, se puede utilizar para retroalimentar al usuario.
La idea básica de las excepciones es bastante clara:
• La asignación de los recursos (por ejemplo, la asignación de memoria o el bloqueo
de un archivo) por lo general se hace a un nivel muy bajo en el programa.
• La lógica de lo que se debe hacer cuando falla una operación (no se puede asignar
la memoria o no se puede bloquear un archivo) se encuentra por lo general en un
nivel alto en el programa, con el código para interactuar con el usuario.
Las excepciones proporcionan un camino rápido que va del código que asigna los
recursos hasta el código que puede manejar la condición de error. Si existen niveles
intermedios de funciones, se les da una oportunidad para limpiar las asignaciones
de memoria, pero no se les pide que incluyan código cuyo único propósito sea pasar
más adelante la condición de error.
C ó m o s e u t iliz a n la s e x c e p c i o n e s
Los bloques try se crean para rodear áreas de código que puedan tener un problema. Por
ejemplo:
try
{
UnaFuncionPeligrosa();
>
Los bloques catch manejan las excepciones producidas en el bloque try. Por ejemplo:
try
{
UnaFuncionPeligrosa();
}
catch(NoHayMemoria)
{
// realizar algunas acciones
}
catch(FileNotFound)
{
é
Excepciones y manejo de errores 717
L is t a d o 2 0 .1 c o n t in u a c ió n
15 { delete [] apTipo;}
16 // operadores
17 Arreglo & operator=(const Arreglo &);
18 int & operatori](int desplazamiento);
19 const int & operatori](int desplazamiento) const;
20 // métodos de acceso
21 int ObtenersuTamanio() const
22 { return suTamanio; >
23 // función amiga
24 friend ostream & operator« (ostream &, const Arreglo &);
25 // definir la clase de excepción
26 class xLimite {};
27 private:
28 int *apTipo;
29 int suTamanio;
30
31
32
33 Arreglo::Arreglo(int tamanio):
34 suTamanio(tamanio)
35 {
36 apTipo = new int[ tamanio ];
37
38 for (int i = 0; i < tamanio; i++)
39 apTipo[ i ] = 0 ;
40
41
42
43 Arreglo & Arreglo::operator=(const Arreglo & rhs)
44
45 if (this == &rhs)
46 return *this;
47 delete [] apTipo;
48 suTamanio = rhs.ObtenersuTamanio();
49 apTipo = new int[ suTamanio ];
50 for (int i = 0 ; i < suTamanio; i++)
51 apTipo[ i ] = rhs[ i ];
52 return *this;
53
54
55 Arreglo::Arreglo(const Arreglo & rhs)
56
57 suTamanio = rhs.ObtenersuTamanio();
58 apTipo = new int[ suTamanio ];
59
60 for (int i = 0 ; i < suTamanio; i++)
61 apTipo[ i ] = rhs[ i ];
62
63
64 int & Arreglo::operator[](int desplazamiento)
65
66 int tamanio = ObtenersuTamanio();
67
Excepciones y m anejo de errores 719
A nálisis El listado 20.1 presenta una clase A rreglo algo simplificada, basada en la plantilla
desarrollada en el día 19, “Plantillas”.
En la línea 26 se declara una nueva clase llamada xLimite, dentro de la declaración de la
clase Arreglo extema.
Esta nueva clase no se distingue de ninguna forma como una clase de excepción. Es sólo
una clase como cualquier otra. Esta singular clase es muy simple; no tiene datos ni métodos.
No obstante, es una clase válida en todos los aspectos.
De hecho, es incorrecto decir que no tiene métodos, ya que el compilador le asigna automá
ticamente un constructor predeterminado, un constructor de copia y el operador de asigna
ción (operador igual a); así que en realidad tiene cuatro funciones, pero ningún dato.
Observe que declarar esta clase desde el interior de Arreglo sirve sólo para acoplar las dos
clases entre sí. Como se explicó en el día 15, “Herencia avanzada”, Arreglo no tiene acceso
especial a xLimite, ni xLimite tiene acceso preferencial a los miembros de Arreglo.
En las líneas 64 a 73 y 76 a 85 se modifican los operadores de desplazamiento para exami
nar el desplazamiento solicitado, y si está fuera de rango, para producir la clase xLimite
com o una excepción. Para distinguir entre esta llamada al constructor de xLimite y entre
el uso de una constante enumerada, se requieren los paréntesis. Hay que tener en cuenta
que algunos com piladores de M icrosoft requieren que se proporcione una instrucción
return para concordar con la declaración (en este caso, regresar una referencia a un entero);
aunque se produzca una excepción en la línea 70, el código nunca llegará a la línea 72.
Éste es un error del compilador, lo que prueba que ¡incluso Microsoft encuentra esto difícil
y confuso!
En la línea 98, la palabra reservada try empieza un bloque try que termina en la línea 106.
Dentro de ese bloque try, se agregan 101 enteros al arreglo que se declaró en la línea 96.
En la línea 107 se declara el bloque catch para atrapar las excepciones xLimite.
Excepciones y manejo de errores 721
En el programa controlador de las líneas 94 a 113 se crea un bloque try en el que se ini-
cializa cada miembro del arreglo. Cuando j (línea 100) se incrementa a 20, se accede al
miembro que se encuentra en el desplazamiento 20. Esto ocasiona que falle la prueba de
la línea 68, y operator[ ] produce una excepción xLimite en la línea 70.
El control del programa se va al bloque catch de la línea 107, y el bloque catch atrapa o
maneja la excepción en la misma línea, la cual imprime un mensaje de error. El flujo del
programa baja hasta el final del bloque catch en la línea 111.
Bloques t r y
Un bloque try es una serie de instrucciones que empiezan con la palabra reservada try,
seguida de una llave de apertura y terminan con lina llave de cierre.
r •t '■
He aquí un ejemplo:
try
{
Funciono;
};
Bloques c a tc h
Un bloque catch es una serie de instrucciones, cada una de las cuales empieza con la pa
labra reservada catch, seguida de un tipo de excepción entre paréntesis, una llave de
apertura y una llave de cierre.
He aquí un ejemplo:
try
{
Funciono;
}
catch (NoHayMemoria)
{
// realizar una acción
} . •' ■
Al tratar de determ inar las ubicaciones de los bloques tr y , busque en donde asigna
m em oria o utiliza recursos. También puede buscar en los errores fuera de los límites,
entradas ilegales, etc.
C ó m o a tr a p a r e x c e p c io n e s
El proceso de atrapar una excepción funciona así: cuando se produce una excepción, se
exam ina la pila de llamadas. Esta pila es la lista de llam adas a funciones que se crea
cuando una parte del programa invoca a otra función.
La pila de llamadas rastrea la ruta de ejecución. Si main() llama a la función
Animal: :ObtenerComidaFavorita(), y ésta llama a Animal: :BuscarPreferencias(),
que a su vez llama a fstream: :operator>>(), todas estas llamadas se encuentran en la
pila de llamadas. Una función recursiva podría estar en la pila de llamadas muchas veces.
La excepción se pasa de la pila de llamadas a cada bloque que la rodea. A medida que se
eliminan elementos de la pila, se invocan los destructores para los objetos locales que están
en ella, y se destruyen dichos objetos.
Después de cada bloque tr y hay una o más instrucciones catch . Si la excepción concuerda
con una de las instrucciones catch, se considera que se va a manejar al ejecutar esa instruc
ción. Si no concuerda con ninguna instrucción, continúa la eliminación de elementos de
la pila.
Si la excepción llega hasta el comienzo del program a (m ain ()) y aún no es atrapada, se
llama a un manejador integrado que termina el programa.
Es importante observar que la búsqueda de bloques que manejan la excepción se realiza en
un solo sentido. A medida que va progresando, la pila se reduce y los objetos que están
en ella se destruyen. No hay regreso: después de encargarse de la excepción, el programa
continúa en la instrucción catch que se encargó de la excepción.
Por ejemplo, en el listado 20.1 la ejecución continuará en la línea 109, y después en la 111.
Es decir, en la primera línea dentro de la instrucción ca tc h que se encargó de la excepción
x L im ite, y posteriormente al final del conjunto de bloques c atch . Recuerde que cuando
se produce una excepción, el flujo del programa continúa después del bloque catch, no
después del punto en el que se produjo la excepción.
M á s d e u n a e s p e c ific a c ió n c a tc h
Es posible que dos o más condiciones produzcan una excepción. En este caso, las instruc
ciones c a tc h se pueden alinear una después de otra, en forma muy parecida a las con
diciones de una instrucción switch. El equivalente para la instrucción predeterminada es la
instrucción “atrapar todo”, la cual se indica escribiendo catch ( . . . ) . El listado 20.2 muestra
condiciones de excepciones múltiples.
Excepciones y manejo de errores 723
En t r a d a L is t a d o 2 0 .2 Excepciones múltiples
1 : // Listado 20.2: Excepciones múltiples
2•
3: ^include <iostream.h>
L is t a d o 2 0 . 2 c o n t in u a c ió n
98: }
99: catch (Arreglo::xCero)
100 {
101 cout << "¡Pidió un arreglo";
102 cout « " de cero objetos!\n";
103 }
104 catch ( . . . )
105 {
106 cout << "¡Algo salió mal!\n";
107 }
108 cout << "Listo.\n";
109 return 0;
110 }
continúa
726 Día 20
L istado 2 0 .3 c o n t in u a c ió n
7:
8: class Arreglo
9: {
10 public:
11 // constructores
12 Arreglo(int suTamanio = TamanioPredet);
13 Arreglo(const Arreglo & rhs);
14 -Arreglo()
15 { delete [] apTipo; >
16 // operadores
17 Arreglo & operator=(const Arreglo &) ;
18 int & operatori](int desplazamiento);
19 const int & operatori](int desplazamiento) const;
20 // métodos de acceso
21 int ObtenersuTamanio() const
22 { return suTamanio; }
23 // función amiga
24 friend ostream & operator« (ostream &, const Arreglo &);
25 // definir las clases de excepciones
26 class xLimite {};
27 class xTamanio {>;
28 class xMuyGrande : public xTamanio {};
29 class xMuyChico : public xTamanio {};
30 class xCero : public xMuyChico {>;
31 class xNegativo : public xTamanio {};
32 private:
33 int *apTipo;
34 int suTamanio;
35 };
36
37 Arreglo:¡Arreglo(int tamanio):
38 suTamanio(tamanio)
39 {
40 if (tamanio == 0 )
41 throw xCero();
42 if (tamanio > 30000)
43 throw xMuyGrande();
44 if (tamanio < 0 )
45 throw xNegativo();
46 if (tamanio < 10 )
47 throw xMuyChico();
48 apTipo = new int[ tamanio ];
49 for (int i = 0 ; i < tamanio; i++)
50 apTipo[ i ] = 0;
51 }
52
53 int & Arreglo::operator[ ] (int desplazamiento)
54
Excepciones y m anejo de errores 727
L is t a d o 2 0 . 3 c o n t in u a c ió n
6:
7:
8: class Arreglo
9: {
10 public:
11 // constructores
12 Arreglo(int suTamanio = TamanioPredet) ;
13 Arreglo(const Arreglo & rhs);
14 -Arreglo()
15 { delete [] apTipo; >
16 // operadores
17 Arreglo & operator=(const Arreglo &);
18 int & operatori ](int desplazamiento);
19 const int & operatori](int desplazamiento) const;
20 // métodos de acceso
21 int ObtenersuTamanioO const
22 { return suTamanio; }
23 // función amiga
24 friend ostream & operator« (ostream &, const Arreglo &);
25 // definir las clases de excepciones
26 class xLimite {};
27 class xTamanio
28 {
29 public:
30 xTamanio(int tamanio) : suTamanio(tamanio) {>
31 -xTamanio(){}
32 int ObtenerTamanio()
33 { return suTamanio; >
34 private:
35 int suTamanio;
36 >;
37 class xMuyGrande : public xTamanio
38 {
39 public:
40 xMuyGrande (int tamanio) : xTamanio (tamanio) {}
41 };
42 class xMuyChico : public xTamanio
43 {
44 public:
45 xMuyChico (int tamanio) : xTamanio(tamanio) {}
46 >;
47 class xCero : public xMuyChico
48 {
49 public:
50 xCero(int tamanio) : xMuyChico(tamanio) {>
51 >;
52 class xNegativo : public xTamanio
53 {
54 public:
55 xNegativo(int tamanio) : xTamanio(tamanio) {}
56 };
57 private:
I730 Día 20
L istado 2 0 .4 c o n t in u a c ió n
Las instrucciones catch de las líneas 115 a 132 están m odificadas para nombrarla
excepción que atrapan, laExcepcion, y para utilizar este objeto para tener acceso a los
datos guardados en suTamanio.
Es un proceso tedioso y propenso a errores hacer que cada una de estas instrucciones catch
imprima individualmente el mensaje apropiado. Este trabajo pertenece al objeto, el cual sabe
qué tipo de objeto es y qué valor recibió. El listado 20.5 utiliza un método más orientado a ob
jetos para este problema, con funciones virtuales para que cada excepción “haga lo correcto”.
28 {
29 public:
30 xTamanio(int tamanio) : suTamanio(tamanio) {}
31 -xTamanio() {}
32 virtual int übtenerTamanio()
33 { return suTamanio; }
34 vi r tu a l void ImprimirError()
35 {
36 cout << "Error en tamaño. Se recibieron:
37 cout « suTamanio « endl;
38 }
39 protected:
40 int suTamanio;
41 };
42 class xMuyGrande : public xTamanio
43 {
44 public:
45 xMuyGrande(int tamanio) : xTamanio(tamanio) {}
46 vir tua l void ImprimirError()
47 {
48 cout « "¡Muy grande! Se recibieron:
49 cout << xTamanio:: suTamanio « endl;
50 >
51 };
52 class xMuyChico : public xTamanio
53 {
54 public:
55 xMuyChico(int tamanio) : xTamanio(tamanio) {}
56 vir tua l void ImprimirError()
57 {
58 cout << "¡Muy chico! Se recibieron: ";
59 cout << xTamanio: : suTamanio « endl;
60 }
61 };
62 cla ss xCero : public xMuyChico
63 {
64 public:
65 xCero(int tamanio) : xMuyChico(tamanio) {}
66 vir tua l void ImprimirError()
67 {
68 cout « "¡Cero!. Se recibieron: " ;
69 cout << xTamanio: : suTamanio « endl;
70 }
71 };
72 class xNegativo : public xTamanio
73 {
74 public:
75 xNegativo(int tamanio) : xTamanio(tamanio) {}
76 virtual void ImprimirError()
77 {
78 cout « "¡Negativo! Se recibieron: ";
continua
734 Día 20
L istado 2 0 .5 c o n t in u a c ió n
U s o d e e x c e p c i o n e s y plantillas
Al crear excepciones para trabajar con plantillas, tiene una opción: puede crear una excep
ción para cada instancia de la plantilla, o puede utilizar clases de excepciones declaradas
fuera de la declaración de la plantilla. El listado 20.6 muestra ambos métodos.
L
Entrada L is t a d o 2 0 .6 Uso de excepciones con plantillas
50: return * t h is ;
51 : delete [] apTipo;
52: suTamanio = rhs.ObtenersuTamanio();
53: apTipo = new T[ suTamanio ];
54: f o r ( in t i = 0; i < suTamanio; i++)
55: a pT ipo [ i ] = rhs[ i ];
56:
57:
58: tem plate < c la s s T >
59: A rre g lo < T > : :A rre glo(con st Arreglo< T > & rhs)
60: {
61 : suTamanio = rhs.ObtenersuTam anio();
62: apTipo = new T[ suTamanio ];
63: f o r ( in t i = 0; i < suTamanio; i++)
64: apTipof i ] = rhs[ i ];
65: }
66:
67: tém plate < c la s s T >
68: T & A rre g lo < T >: :o p e ra to r[] (in t desplazamiento)
69: {
70: in t tamanio = ObtenersuTamanio();
71 :
72: if (desplazam iento >= 0 && desplazamiento < ObtenersuTamanio())
73: return apTipof desplazamiento ];
74: throw x L im it e ();
75: return apTipo[ 0 ];
76:
77:
78: témplate < c la s s T >
79: const T & A rre g lo < T > : :operator[] (in t desplazamiento) const
80: {
81 : in t mitamanio = ObtenersuTamanio();
82:
83: if (desplazam iento >= 0 && desplazamiento < ObtenersuTamanio())
84: return apTipo[ desplazamiento ];
85: throw x L im it e ();
86:
87:
88: template < c la s s T > 2 0
89: ostream & o p e r a t o r « (ostream & salid a , const Arreglo<
**T > & e lA rre g lo )
90: {
91 : f o r ( in t i = 0; i < elArreglo.ObtenersuTamanio(); i++)
92: s a lid a « " [ " « i « "] " « e lA rre g lo [ i ] « endl;
93: re tu rn s a lid a ;
94: }
95:
96: in t main()
97: {
98: try
99: {
100: A rre g lo < in t > a rre glo ln t (9) ;
continúa
738 Día 20
L is t a d o 2 0 . 6 continuación
101 :
102: for (int j = 0; j < 100; j++)
103: {
104: arreglolntt j ] = j ;
105: cout << "arreglolnt [" << j << "] está bien...“ « endl;
106: }
107: }
108: catch (xLimite)
109: {
110: cout « "¡No se pudo procesar su entrada!\n";
111: }
112: catch (Arreglo< int >::xTamanio)
113: {
114: cout « "¡Tamaño incorrecto!\n";
115: }
116: cout « "Listo.\n";
117: return 0;
118: }
¡Tamaño incorrecto!
Salida Listo.
La primera excepción, xLimite, se declara fuera de la definición de la plantilla,
A nálisis
en la línea 8 . La segunda excepción, xTamanio, se declara desde el interior de la
definición de la plantilla, en la línea 29.
La excepción xLimite no está atada a la clase de plantilla, pero se puede utilizar de la
misma forma que cualquier otra clase. xTamanio está atada a la plantilla y debe ser llamada
con base en el Arreglo instanciado. Puede ver la diferencia en la sintaxis de las dos instruc
ciones catch. La línea 108 muestra catch (xLimite), pero la línea 112 muestra catch
(Arreglo< int >: ¡xTamanio). La segunda está atada a la instanciación de un Arreglo
de enteros.
de diálogos, el cual, a su vez, llama al código que procesa la solicitud, el cual llama al
código que decide cuál cuadro de diálogo utilizar, y éste llama al código que se encarga
de colocar el cuadro de diálogo, el cual, finalmente, llama al código que procesa la entrada
del usuario. Si el usuario oprime el botón Cancelar, el código debe regresar al prim er
método llamador en donde se manejó la solicitud original.
Una forma de solucionar este problema es colocar un bloque try alrededor de la llamada
original y atrapar DialogoCancelar como una excepción, la cual puede ser producida por
el manejador del botón Cancelar. Esto es seguro y efectivo, pero oprimir Cancelar es una
circunstancia de rutina, no una excepcional.
Con frecuencia, esto se convierte en algo así como un argumento religioso, pero una
forma razonable de decidir la cuestión es preguntar lo siguiente: ¿Usar de esta forma las
excepciones hace que el código sea más fácil o más difícil de entender? ¿Hay menos
riesgos de errores y fugas de memoria, o hay más? ¿Será más difícil o más fácil mantener
este código? Estas decisiones, como muchas otras, requerirán de un análisis de las conce
siones que haya que hacer; no existe una sola respuesta correcta que sea obvia.
Ta bla 2 0 .1 C o m a n d o s c o m u n e s d e gd b
Comando Propósito
p r i n t exp Imprimir exp: exp puede ser variable, nombre de función
o expresión compleja, como el comienzo de un arreglo
(nombre del arreglo) más un valor para mostrar un elemento
especificado. Esto le permite examinar la memoria.
q u it Salir de gdb. Si tiene un programa en ejecución, se le
pedirá que confirme.
run a r g l i s t a Ejecutar el programa desde el principio con la lista
opcional de argumentos de línea de comandos a r g l i s t a .
s e t v a r i a b l e = exp Asignar a la v a r i a b l e del código fuente la expresión exp.
Igual que con print, exp puede ser una variable, nombre de
función o expresión compleja. La variable sigue la sintaxis
del lenguaje fuente y reconoce cosas tales como notación
de arreglos. Esto le permite alterar el estado de la memoria.
se t Modificar las variables de entorno de gdb.
ste p Ejecutar el siguiente paso del programa (entrar en cualquier
función llamada).
u n d is p la y Cancelar el despliegue.
watch Crear un punto de observación.
w h a t is exp Desplegar el tipo de datos de exp.
El depurador GNU también acepta opciones de línea de comandos. Al ejecutar gdb, por
lo general se escribe un comando como el siguiente:
gdb suprograma
Tam bién puede utilizar gdb para depurar un programa que falle al ejecutarlo (cuando se
produzca un archivo core). En este caso, su comando se verá así:
gdb suprograma core
Debe tener en cuenta que el archivo core que utilizará para la depuración es el mismo que
20
se creó debido a la “caída” de su programa. Al depurar un archivo core, puede examinar
la memoria y la pila de llamadas, pero no puede ejecutar pasos porque el programa no está
en ejecución.
Para obtener mayor información acerca de las opciones de línea de comandos y los comandos
internos de gdb, debe revisar el manual (man gdb) o el archivo de información (info gdb).
Las siguientes secciones describen el significado de los términos punto de interrupción,
punto de observación, examen y modificación del estado de la memoria y desensamble.
Desensamble
Aunque leer el código puede ser todo lo que se requiere para encontrar un bug (error),
cuando todo lo demás falla, es posible que el depurador le muestre el código en lenguaje
E x c e p c io n e s y m a n e jo d e e rro re s 743
ensam blador generado para cada línea del código fuente. Puede exam inar el estado de los
registros, de la m em oria y de los indicadores, y por lo general ahondar en el funcionam iento
interno de su program a tanto como lo requiera.
A prenda a utilizar su depurador. Puede ser el arma más poderosa en su guerra santa contra
los bugs. Los bugs en tiem po de ejecución son los más duros de encontrar y de eliminar,
y un depurador poderoso puede hacer posible, si no es que fácil, encontrarlos casi todos.
Resumen
Hoy aprendió cóm o crear y utilizar las excepciones. Éstas son objetos que se crean cuan
do el código que se está ejecutando no puede manejar el error o cualquier otra condición
excepcional que haya surgido. Otras partes del programa, que se encuentran más abajo en
la pila de llam adas, im plem entan bloques catch que atrapan la excepción y realizan la
acción apropiada.
Las excepciones son objetos normales creados por el usuario, y como tales se pueden pasar
por valor o por referencia. Pueden contener datos y métodos, y el bloque c a tc h puede
utilizar esos datos para decidir cómo tratar la excepción.
Es posible crear m últiples bloques catch , pero después de que una excepción concuerda
con la firm a de un bloque ca tc h , se considera que ya fue manejada y no se proporciona a
los bloques c a tc h subsecuentes. Es importante ordenar apropiadamente los bloques c a tc h ,
de form a que los bloques c a tc h específicos tengan la primera oportunidad, y que los blo
ques c a tc h más generales se encarguen de los que no se manejan en forma específica.
Esta lección también examinó algunos de los fundamentos de los depuradores simbólicos, in
cluyendo el uso de puntos de observación, puntos de interrupción, etc. Estas herramientas
pueden ayudarle a centrarse en la parte de su programa que está ocasionando el error y le per
miten ver el valor de las variables a medida que cambian durante la ejecución del programa.
Preguntas y respuestas
P ¿P o r qué preocuparse por producir excepciones? ¿Por qué no m an ejar el 20
e rro r donde ocurre?
R A m enudo, el m ism o error se puede generar en distintas partes del código. Las
excepciones le perm iten centralizar el manejo de errores. Además, la parte del
código que genera el error tal vez no sea el mejor lugar para determinar la forma
de m anejar el error.
P ¿P o r qué g en era r un ob jeto? ¿Por qué no sólo pasar un código de erro r?
R Los objetos son más flexibles y poderosos que los códigos de error. Pueden transmitir
más información, y los mecanismos constructores/destructores se pueden utilizar para
la creación y remoción de recursos que se puedan necesitar para manejar apropiada
m ente la condición excepcional.
744 Día 20
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del mate
rial tratado, así como ejercicios para que experim ente con lo que ha aprendido. Trate de
responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Qué es una excepción?
2. ¿Qué es un bloque try ?
3. ¿Qué es una instrucción catch ?
4- ¿Qué información puede contener una excepción?
5. ¿Cuándo se crean los objetos de excepción?
6 . ¿Se deben pasar las excepciones por valor o por referencia ?
7. ¿Atrapará una instrucción catch una excepción derivada si está buscando la clase
base?
8 . Si se utilizan dos instrucciones catch , una para la clase base y una para la derivada,
¿cuál debe ir primero?
Excepciones y manejo de errores 745
9. ¿Qué significa c a t c h ( . . . ) ?
10. ¿Qué es un punto de interrupción?
Ejercicios
1. Cree un bloque tr y , una instrucción catch y una excepción simple.
2. M odifique la respuesta del ejercicio 1, coloque datos en la excepción junto con una
función de acceso, y utilícela en el bloque catch.
3. M odifique la clase del ejercicio 2 para que sea una jerarquía de excepciones.
Cambie el bloque catch para utilizar los objetos derivados y los objetos base.
4. M odifique el programa del ejercicio 3 para que tenga tres niveles de llamadas a
funciones.
5. CA ZA E R R O R E S : ¿Qué está mal en el siguiente código?
c la s s xNoHayMemoria
{
public:
xNoHayMemoria()
{
elMsje = new char[ 20 ];
strcpy(elMsje, "error en memoria");
>
-xNoHayMemoria!)
{
delete [] elMsje;
cout « "Memoria restablecida." « endl;
>
char * Mensaje!)
{
return elMsje;
}
private:
char * elMsje;
};
main()
{
try
{
char * var = new char;
if (var == 0)
{
xNoHayMemoria * apx = throw apx;
}
}
catch (xNoHayMemoria * laExcepcion)
{
cout << laExcepcion->Mensaje() «endl;
delete laExcepcion;
}
return 0;
746 Día 20
Este ejercicio muestra el bug planeado: Está asignando memoria para presentar el
mensaje de error, pero lo hace precisamente cuando no hay memoria disponible para
asignar (después de todo, ¡es lo que esta excepción m aneja!). Puede probar este
program a cam biando la línea i f (v a r == 0 ) a i f ( 1 ), la cual obligará a que
se produzca la excepción.
S emana 3
D ía 2 1
Qué sigue
¡Felicidades! Ya casi term ina una introducción intensiva de tres semanas a C++.
P ara esto s m om entos debe tener una comprensión sólida de C++, pero en la
p ro g ram ació n m oderna siem pre hay algo más que aprender. Este día cubre
algunos detalles faltantes y luego lo prepara para continuar el estudio.
La m ayor parte de lo que escribe en sus archivos de código fuente es C++. Su
com pilador interpreta esto y lo convierte en un programa. Sin embargo, antes
de que el com pilador se ejecute, se ejecuta el preprocesador, y esto proporciona
una oportunidad para la com pilación condicional. Hoy aprenderá lo siguiente:
El p r e p r o c e s a d o r y el c o m p i l a d o r
C ada vez que se ejecuta su com pilador, prim ero se tiene que ejecutar su preprocesador.
El preprocesador busca instrucciones de preprocesador, las cuales empiezan con un signo
de numeral (#). El efecto de cada una de estas instrucciones es un cambio en el texto del
código fuente. El resultado es un nuevo archivo de código fuente (un archivo temporal que
por lo general no se ve, pero puede indicarle al com pilador que lo guarde para que pueda
examinarlo, si lo desea).
El compilador no lee su código fuente original; lee la salida del preprocesador y compila
ese archivo. Ya ha visto el efecto de esto con la directiva tfinclude. Esta directiva le indica
al preprocesador que encuentre el archivo cuyo nom bre sigue después de la directiva
#include y que lo escriba en un archivo intermedio en esa ubicación. Es como si usted
hubiera escrito todo ese archivo dentro de su código fuente, y cuando el compilador ve
el código fuente, el archivo de encabezado está ahí.
L a directiva d e p r e p r o c e s a d o r # d e f i n e
El com ando #def ine define una sustitución de cadenas. Si escribe
#define GRANDE 512
esto le indica al precompilador que sustituya la cadena 512 en cualquier lugar donde vea
la cadena GRANDE. Ésta no es una cadena común de C++. Los caracteres 512 se sustituyen
en el cód igo fuente en cualquier lugar donde se vea el token GRANDE. Un token es una
cadena de caracteres que se pueden utilizar en cualquier lugar donde se pueda utilizar
una cadena o constante u otro conjunto de letras. Por lo tanto, si escribe
¿(define GRANDE 512
int miArreglo[GRANDE];
¿j
Uso de #def ine como alternativa para constantes
Una manera de utilizar //define es como sustituto para constantes. Sin embargo, ésta casi
nunca es una buena idea, ya que #def ine simplemente hace una sustitución de cadenas y
no comprueba los tipos. Como se explicó en la sección que trata sobre las constantes, exis
ten muchas ventajas al usar la palabra reservada const en lugar de #def ine.
#ifdef DEPURACION
cout « "Depuración está definida”;
tfendif
Cuando el precom pilador lee la directiva #ifdef, revisa una tabla que ha creado para ver
si ya definió DEPURACION. Si ya la definió, #ifdef se evalúa como true, y todo lo que se
encuentre antes de la siguiente directiva #else o #endif se escribe en el archivo inter
medio para compilarlo. Si se evalúa como false, nada de lo que haya entre #if def
d e p u r a c i ó n y #endif se escribirá en el archivo intermedio; será como si nunca hubiera
estado en el código fuente.
Observe que #if ndef es el inverso lógico de #ifdef.#if ndef se evalúa como true si la
cadena no ha sido definida hasta ese punto del archivo.
5: ^include <iostream.h>
cQ •
•
7: int main()
8: {
9: cout « 'Comprobando las definiciones de VersionDemo,
10 cout « "VERSION_NT y VERSION_WINDOWS...\n",
11
12 tfifdef VersionDemo
13 cout << 1VersionDemo definida.\n";
14 #else
15 cout « "VersionDemo no definida.\n";
16 #endif
17
18 tfifndef VERSION NT
19 cout « "iVERSION_NT no definida!\n";
20 #else
21 cout « "VERSION NT definida como: “ << VERSION_
22 #endif
23
24 #ifdef VERSION WINDOWS
25 cout << “IVERSION WINDOWS definida !\n";
26 #else
27 cout « 1VERSION WINDOWS no fue definida.\n“;
28 #endif
29
30 cout « "Listo.\n";
31 return 0;
32 >
Inclusión y g u a r d i a s d e inclusión
Usted creará proyectos con muchos archivos diferentes. Probablemente organizará sus direc
torios de forma que cada clase tenga su propio archivo de encabezado (por ejemplo, .hpp)
con la declaración de la clase y su propio archivo de implementación (por ejemplo,
.cxx) con el código fuente para los métodos de las clases.
Su función main () estará en su propio archivo .cxx, y todos los archivos .cxx se compilarán
en archivos .o (.obj en Windows y DOS), los cuales serán luego enlazados en un solo
programa por el enlazador.
Ya que sus programas utilizarán métodos de muchas clases, se incluirán muchos archivos
de encabezado en cada archivo. Además, los archivos de encabezado a menudo necesitan
incluir otros archivos de encabezados. Por ejemplo, el archivo de encabezado para la decla
ración de una clase derivada debe incluir el archivo de encabezado para su clase base.
Imagine que la clase Animal está declarada en el archivo ANIMAL.hpp. La clase Perro
(que se deriva de Animal) debe incluir el archivo ANIMAL, hpp en PERRO, hpp, o de lo con
trario Perro no se podrá derivar de Animal. El archivo de encabezado de Gato también
incluye a ANIMAL, hpp por la misma razón.
Si crea un método que utilice tanto un Gato como un Perro, correrá el peligro de incluir
dos veces a ANIMAL.hpp. Esto generará un error en tiempo de compilación, pues no es
válido declarar una clase (Animal) dos veces, aunque las declaraciones sean idénticas.
Puede resolver este problema con los guardias de inclusión. Al principio de su archivo
de encabezado de ANIMAL, escriba estas líneas:
#ifndef ANIMAL_HPP
#define ANIMAL_HPP
// todo el archivo va aqui
#endif
Esto dice: si no ha definido el término ANIMAL_HPP, defínalo ahora. Todo el contenido del
archivo va entre la directiva #def ine y la directiva #endif de cierre.
La primera vez que su programa incluya este archivo, leerá la primera línea y la prueba
se evaluará como tru e ; es decir, aún no ha definido a ANIMAL_HPP. Por lo tanto, el programa
define este término e incluye el archivo completo.
752 Día 21
La segunda vez que su programa incluya el archivo a n i m a l ,hpp, leerá la primera línea y
la prueba se evaluará como false; ya se ha definido a a n ima l .hpp. Por lo tanto, la ejecu
ción del programa salta hasta la siguiente directiva #else (en este caso no hay ninguna)
o hasta la siguiente #endif (al final del archivo). Por consecuencia, se salta todo el con
tenido del archivo, y así la clase no se declara dos veces.
El nombre actual del símbolo definido (ANIMAL_HPP) no es importante, aunque se acos
tumbra utilizar el nombre de archivo en mayúsculas, y cam biar el punto (.) por un guión
bajo. Sin embargo, esto es sólo una convención.
F u n c i o n e s d e m a c r o s
La directiva #define también se puede utilizar para crear funciones de macros. Una fun
ción de macro es un símbolo creado por medio de #def ine; toma un argumento en forma
muy parecida a una función común. El preprocesador sustituirá la cadena de sustitución
por cualquier argumento que se le dé. Por ejemplo, puede definir la macro DOBLE de la
siguiente manera:
#define DOBLE(x) ((x) * 2)
y luego escribir en su código
DOBLE(4)
Observe que en la definición de una función de macro, los paréntesis de apertura para la
lista de parám etros deben estar inm ediatam ente después del nombre de la macro, sin
espacios. El preprocesador no es tan considerado con los espacios en blanco como el
compilador.
Si escribiera
#define MAX (x,y) ((x) > (y) ? (X) : (y))
int x = 5, y = 7, z;
z = MAX(x,y);
el código intermedio sería
int x = 5, y = 7, z;
z = (x,y) ((x) > (y) ? (x) : (y))(x,y)
Se realizaría una simple sustitución de texto, en lugar de invocar a la macro tipo función.
Por lo tanto, el token MAX sería sustituido por (x ,y) ((x) > (y) ? (x) : (y)), y
después de esto seguiría (x, y), que estaba después de MAX.
No obstante, si quita el espacio entre MAX y (x,y), el código intermedio sería
int x = 5, y = 7, z;
z =7;
continúa
754 Día 21
L is t a d o 2 1 . 2 continuación
y: 125
z: 125
y: 1728
z: 82
lo que, a su vez, se evalúa como 1728. Sin embargo, TRIPLE (5+7) se evalúa como
5 + 7*5 + 7*5 + 7
Ya que la multiplicación tiene mayor precedencia que la adición, esto se convierte en
5 + (7 * 5) + (7 * 5) + 7
lo que se evalúa como
5 + (35) + (35) + 7
lo cual finalmente se evalúa como 82.
El segundo problem a es que las macros se expanden en línea cada vez que se utilizan.
Esto significa que si una macro se utiliza una docena de veces, la sustitución aparecerá
12 veces en el programa, en lugar de aparecer una vez, como ocurre con la llamada a una
función. Por otro lado, la mayoría de las veces son más rápidas que la llamada a una fun
ción ya que se evita la sobrecarga de una llamada a función.
Sin em bargo, el último problema es el más grande: las macros no tienen seguridad de
tipos. Aunque es conveniente que cualquier argumento se pueda utilizar con una macro,
esto m ina com pletam ente la fuerte tipificación de C++, por lo que es una maldición para
los program adores de C++. Desde luego, la manera correcta de resolver esto es con las
plantillas, com o vio en el día 19, “Plantillas”.
Funciones en línea
A m en u d o es posible declarar una función en línea en lugar de una m acro. Por ejem plo,
el listado 21.3 crea una función CUBO, la cual logra lo mismo que la macro CUBO del listado
21.2, p ero lo hace ofreciendo seguridad de tipos.
20: break ;
21 : cout « "Usted escribió: " « x
22: cout « ". Cuadrado!" « x « "
23: cout « Cuadrado(x);
24: cout« 1'. Cubo (" « x « "): ";
25: cout « Cubo(x) << "." « endl;
26: }
27: return 0;
28: }
A nálisis En las líneas 5 y 7 se definen dos funciones en línea: Cuadrado() y Cubo(). Cada
una se declara en línea, por lo que al igual que una función de macro, éstas se
expandirán en el lugar adecuado para cada llamada, y no ocurrirá una sobrecarga en las
llamadas a funciones.
Com o recordatorio, una función expandida en línea significa que el contenido de la fun
ción se colocará en el código en cualquier lugar en el que se haga la llamada a la función
(por ejemplo, en la línea 23). Como la llam ada a la función nunca se hace, no hay sobre
carga por colocar en la pila la dirección de retorno y los parámetros.
En la línea 23 se llama a la función Cuadrado, y en la línea 25 se llama a la función Cubo.
De nuevo, como éstas son funciones en línea, es exactam ente como si esta línea se hubiera
escrito así:
22: cout « Cuadrado(” « x « "):
23: cout « x * x;
24: cout << 11. Cubo (11 « x << "): ";
25: cout « x*x*x << endl;
M a n i p u l a c i ó n d e c a d e n a s
El preprocesador proporciona dos operadores especiales para manipular cadenas en las
m acros. El operador de cadena (#) sustituye cualquier cosa que le siga por una cadena
entre com illas. El operador de concatenación une dos cadenas en una.
Q u é s ig u e 757
O bserve que la cadena É s t a es una cadena se coloca entre comillas, como lo requiere
cout.
Concatenación
El operador de concatenación le permite unir más de un término para formar una nueva
palabra. La nueva palabra es en realidad un token que se puede usar como nombre de una
clase, nom bre de una variable, desplazamiento en un arreglo, o en cualquier lugar en el
que pueda aparecer una serie de letras.
Suponga por un m omento que tiene cinco funciones llamadas fUnolm prim ir, f ü o s lm p r i-
mir, f T r e s Im p r im ir , fC u a tro lm p rim ir y fCincolm prim ir. Entonces puede declarar
//define flM P R IM IR (x) f //# x #// Imprimir
y luego utilizarla con f IM PRIM IR (Dos) para generar fDoslm prim ir y con f IM PRIM IR (T re s)
para generar f T r e s Im p rim ir.
Al finalizar la sem ana 2 se desarrolló una clase llamada L is t a P ie z a s . Esta lista sólo
podía m anejar objetos de tipo L is t a . Suponga que esta lista funciona bien, y que le gus
taría crear listas de animales, autos, computadoras, etc.
Un método sería crear L istaA n im ale s, ListaAutos, ListaComputadoras, y así sucesiva
m ente, cortando y pegando el código en los lugares adecuados. Esto se convertiría rápi
dam ente en una pesadilla, pues cualquier cambio en una lista se debe escribir en todas
las demás.
Una alternativa es utilizar macros y el operador de concatenación. Por ejemplo, en la
m ayoría de los compiladores, podría definir
//define L ista d e (T ip o ) c la s s Tipo#//Lista \
{ \
p u b lic : \
Tipo////Lista() {} \
p riv a te : \
in t suLongitud; \
};
758 Día 21
Claro que la página no es lo suficientem ente amplia para mostrar toda la macro como
una línea. La mayoría de las ventanas de los editores tampoco son lo suficientemente
amplias, pero por lo menos se da una idea.
Este ejemplo es demasiado escaso, pero la idea sería colocar todos los métodos y datos
necesarios. Cuando estuviera listo para crear una ListaAnimales. escribiría
Listade(Animal)
M a c r o s p r e d e f i n i d a s
Cuando el precompilador ve una de estas macros, hace las sustituciones apropiadas. Para
— DATE— , se sustituye la fecha actual. P ara__ TIME__, se sustituye la hora actual.__ LINE_
y — PILE— se reem plazan con el núm ero de línea y el nom bre de archivo del código
fuente, respectivam ente. Debe tener en cuenta que esta sustitución se hace cuando se
precom pila el código fuente, no cuando se ejecuta el programa. Si pide al programa que
imprima — DATE__, no obtendrá la fecha actual, sino la fecha en la que se compiló el progra
ma. Estas macros definidas son muy útiles en la depuración.
A S S E R T ()
GNU y m uchos otros com piladores ofrecen una m acro llam ada ASSERT(). Esta macro
regresa tr u e si su parám etro se evalúa com o t r u e y realiza algún tipo de acción si se
evalúa como f a ls e . GNU y muchos otros com piladores abortarán el programa en caso
de que una macro ASSERT () falle; otros producirán una excepción (vea el día 20, “Excep
ciones y manejo de errores”).
Una característica poderosa de la macro ASSERT () es que el preprocesador la comprime
sin código alguno si DEPURAR no está definida. Es una gran ayuda durante el desarrollo, y
cuando el producto final está terminado, no se castiga el rendimiento ni se incrementa el
tamaño de la versión ejecutable del programa.
Qué sigue 759
Primera Assert:
S a lid a
Segunda Assert:
ERROR!1 Assert x 1=5 falló
en la línea 24
del archivo lst2l-04.cxx
Listo.
En la línea 3 se define el término DEPURAR. Por lo general, esto se haría desde la
A nálisis
línea de comandos (por medio del argumento de línea de comandos -D para g++)
en tiempo de compilación, para que pueda activar y desactivar esto según lo requiera. En
la línea 9 se define la macro ASSERT (). Por lo general, esto se haría en un archivo de enca
bezado, y ese archivo de encabezado (ASSERT. hpp) se incluiría en todos sus archivos de
implementación.
760 Día 21
En la línea 6 se prueba el térm ino DEPURAR. Si no está definido, ASSERT () se define para
no crear ningún código. Si DEPURAR está definido, se aplica la funcionalidad definida en
la línea 9.
La instrucción ASSERT () en sí es larga debido a que el preprocesador G N U no soporta la
división de líneas.
M uchos otros preprocesadores sí soportan la división de líneas, en donde ASSERT() se
divide entre siete líneas de código fuente en lo que al precom pilador concierne. Un
ejem plo de este tipo de código sería reem plazar las líneas 9 a 15 del listado 21.4
9: #define ASSERT(x) \
10: if (! (x)) \
11: { \
12: cout « "I iERROR!! Assert " « #x « " falló\n" \
13: cout « " en la línea " « _LINE__ « “\n"; \
14: cout << " del archivo “ « _FILE_ « "\n"; \
15: >
En la línea 10 se prueba el valor que se pasa com o parám etro; si se evalúa como falsa,
se invocan las instrucciones de las líneas 12 a 14, y se imprime un mensaje de error. Si el
valor que se pasa se evalúa como tr u e , no se realiza ninguna acción.
E sto es c rític o pues cu a n d o en víe su código a sus clientes, se quitarán instan cias de
ASSE R T (). N o puede depender de una macro ASSERT () para manejar un problem a en
tiem po de ejecución ya que ASSERT () no estará ahí.
Es un erro r com ún utilizar a ASSERT () para probar el valor de retorno de una asignación
de m em oria:
Anim al *apGato = new Gato;
ASSERT(apG ato) ; // mal uso de A sse rt
apG ato->UnaFuncion ();
Este es un clásico error de programación; cada vez que el programador ejecute el programa,
habrá suficiente m e m o ria disponible y ASSERT () nunca se disparará. D e sp ués de todo, el
p ro g ra m a d o r está ejecutando el program a con bastante R A M adicional para acelerar la
velo cid ad del com pilador, del depurador, etc. Luego, el program ador envía el ejecutable,
y el pobre usuario, que tiene m enos memoria, llega a esta parte del program a y la llam ada
a new falla, y regresa N U L L . S in embargo, la macro ASSE R T () ya no se encuentra en el
c ó d ig o y nada in d ica que el apuntador está apuntando a N U L L . Tan pronto co m o llegue
a la in stru cció n apG ato ->U n aFuncion (), el programa fallará.
Efectos secundarios
E s c o m ú n encontrar que un bug aparece sólo después de que se quitan las instancias de
ASSERT (). E sto casi siem pre se debe a que el programa depende sin querer de los efectos
secundarios de las cosas que se realizan en el código contenido en las instancias de ASSERT ()
y dem ás có d ig o de sólo depuración. Por ejemplo, si escribe
ASSERT (X = 5)
Su p o n ga que justo antes de esta macro ASSERT() llamó a una función que establecía el va lo r
de x en 0. C o n esta m acro ASSERT (), usted piensa que está probando si x es igual a 5; de
hecho, está a sign a n d o el valor 5 a x. L a prueba regresa tru e debido a que x = 5 no sólo
asigna el va lo r 5 a x, sino que también regresa el valor 5, y com o 5 no es igual a cero, se
evalúa c o m o tru e . 2 1
A l pasar la instrucción ASSE R T (), x realmente es igual a 5 (¡le acaba de asignar ese valor!).
S u program a f unciona a la perfección. Está listo para enviarlo al cliente, así que desactiva
la depuración. A h o ra desaparece la instrucción ASSERT(), y a x ya no se le asigna el va lo r
5 . C o m o x valía 0 antes de esto, sigue siendo 0 y su programa falla.
762 Día 21
Constantes de clases
La mayoría de las clases tiene algunas condiciones que siem pre deben ser verdaderas al
dejar de utilizar una función miembro de la clase. Estas constantes de clase son el sine
qua non (es decir, la condición indispensable) de su clase. Por ejemplo, puede ser cierto
que su objeto CIRCULO nunca debe tener un radio de cero, o que su objeto ANIMAL siempre
debe tener una edad mayor de cero y menor de 100.
Puede ser muy útil declarar un método Constantes!) que regrese true sólo si cada una
de estas condiciones sigue siendo true. Luego puede utilizar ASSERT(Constantes!)) al
inicio y al término de cada método de la clase. La excepción sería que sus Constantes!)
no esperen regresar true antes de que su constructor se ejecute o después de que su destruc
tor termine. El listado 21.5 muestra el uso del método Constantes!) en una clase trivial.
En t r a d a Listado 21.5 U so d e C o n s t a n t e s ! )
26 -Cadena();
27 char & operatori](int offset);
28 char operatori](int offset) const;
29 Cadena & operator=(const Cadena &);
30 int ObtenerLongitud()const
31 { return suLongitud; }
32 const char * ObtenerCadena() const
33 { return suCadena; }
34 bool Constantes!) const;
35 private:
36 // constructor privado
37- Cadena (int);
38 char * suCadena;
39 // unsigned short suLongitud;
40 int suLongitud;
41
42
43 // constructor predeterminado crea una cadena de 0 bytes
44 Cadena::Cadena()
45 {
46 suCadena = new char[ 1 ];
47 suCadena[ 0 ] = '\0';
48 suLongitud = 0 ;
49 ASSERT(Constantes ());
50 >
51
52 // constructor privado (auxiliar), lo utilizan sólo los
53 // métodos de la clase para crear una nueva cadena del
54 // tamaño requerido. Se llena con caracteres nulos.
55 Cadena::Cadena(int longitud)
56 {
57 suCadena = new char[ longitud + 1 ];
58
59 for (int i = 0; i <= longitud; i++)
60 suCadena! i ] = 1\0‘;
61 suLongitud = longitud;
62 ASSERT(Constantes());
63
64
65 // Convierte un arreglo de caracteres en una Cadena
66 Cadena::Cadena(const char * const cCadena)
67 {
68 suLongitud = strlen(cCadena);
69 suCadena = new char[ suLongitud + 1 ];
70
71 for (int i = 0; i < suLongitud; i++)
72 suCadena! i 1 = cCadena! i ];
73 suCadena! suLongitud ] = ‘\0';
74 ASSERT(Constantes());
75
76
77 // constructor de copia
continúa
L
L is t a d o 2 1 . 5 continuación
129:
130: // operador de desplazamiento constante
131 : char Cadena: :o p e ra to r[] (in t offset) const
132: {
133: ASSERT(C on stan tes());
134: char valRet;
135: i f (o ffs e t > suLongitud)
136: re tV a l = suCadena[ suLongitud - 1 ];
137: e lse
138: re tV a l = suCadena[ offset ];
139: ASSERT(C on sta ntes());
140: return retVal;
141 : }
142:
143: bool Cadena::Constantes() const
144: {
145: # ifd e f MOSTRAR_CONSTANTES
146: cout << "Constantes probadas";
147: #endif
148: return ((suLongitud && suCadena) || (¡suLongitud
¡suCadena));
149: }
150:
151 : c la s s Animal
152: {
153: p u b lic :
154: A n im a l() :suEdad(1) , suNombre("John Q. Animal")
155: { ASSERT(Constantes()); }
156: A n im a l(in t, const Cadena &);
157: -A n im a l() {}
158: in t ObtenerEdad()
159: { ASSERT(Constantes()); return suEdad; }
160: void AsignarEdad(int Edad)
161 :
162: ASSERT(Constantes());
163: suEdad = Edad;
164: ^ ASSERT(Constantes());
165:
166: Cadena& ObtenerNombre()
167: {
168: ASSERT(ConstantesO);
169: return suNombre;
170: }
171 : void AsignarNombre(const Cadena & nombre)
172: {
173: ASSERT(Constantes());
174: suNombre = nombre;
175: ^ ASSERT(ConstantesO);
176:
177: bool Constantes();
178: p r iv a t e :
179: in t suEdad;
180: Cadena suNombre;
continúa
766 Día 21
L is t a d o 2 1 . 5 continuación
181: };
182:
183: Animal::Animal(int edad, const Cadena & nombre):
184: suEdad(edad),
185: suNombre(nombre)
186: {
187: ASSERT(Constantes());
188: }
189:
190: bool Animal::Constantes()
191 : {
192: #ifdef MOSTRAR_CONSTANTES
193: cout « "Constantes probadas";
194: #endif
195: return (suEdad > 0 && suNombre.ObtenerLongitud());
196: >
197:
198: int main()
199: {
200: Animal sparky(5, "Sparky");
201 :
202: cout « "\n" « sparky.ObtenerNombre().ObtenerCadena();
203: cout « "tiene " « sparky.ObtenerEdad();
204: cout « "años de edad.\n";
205: sparky.AsignarEdad(8);
206: cout « "\n" « sparky.ObtenerNombre().ObtenerCadena();
207: cout « "tiene " « sparky.ObtenerEdad();
208: cout « "años de edad.\n";
209: return 0;
210: >
MOSTRAR_CONSTANTES definida
Salida
Constantes probadasConstantes probadasConstantes probadasConstantes
^probadasConstantes
^probadasConstantes probadasConstantes probadasConstantes
probadasConstantes
^probadasConstantes probadasConstantes probadasConstantes
probadasConstantes
»■►ProbadasConstantes probadasConstantes probadasConstantes
probadasConstantes
►►probadas
SparkyConstantes probadas tiene 5 años de edad.Constantes
probadasConstantes
►►probadasConstantes probadas
SparkyConstantes probadas tiene 8 años de edad.Constantes probadas
MOSTRAR_CONSTANTES indefinida
Sparky tiene 5 años de edad.
Sparky tiene 8 años de edad.
Qué sigue 767
Este patrón se repite para los otros constructores, y el destructor llama a Constantes ()
sólo antes de prepararse para destruir el objeto. Las funciones restantes de la clase llaman a
Constantes () antes de realizar cualquier acción y luego otra vez antes de terminar. Esto
afirma y valida un principio fundamental de C++: las funciones miembro que no sean
constructores ni destructores deben funcionar sobre objetos válidos y deben dejarlos en un
estado válido.
En la línea 177, la clase Animal declara su propio método Constantes(), el cual se im-
plementa en las líneas 190 a 196. Observe que las funciones en línea pueden llamar al
método Constantes () (líneas 155, 159, 162 y 164).
L is t a d o 2 1 . 6 continuación
20: IMPRIMIR(x);
21: for (int i = 0; i < x; i++)
22: {
23: IMPRIMIR(i);
24: }
25: IMPRIMIR(y);
26: IMPRIMIR(“Hola.");
27: int *apx = &x;
28: IMPRIMIR(apx);
29: IMPRIMIR(*apx);
30: return 0;
31: }
x: 5
Salida i: 0
i: 1
i: 2
i: 3
i: 4
y: 73898
"Hola. Hola.
apx: 0x7fffC64
*apx: 5
A nálisis
La macro que se encuentra en las líneas 6 a 9 proporciona la impresión del valor
actual del parámetro proporcionado. Observe que lo primero que se proporciona
a cout es la versión de cadena del parámetro; es decir, se pasa x y cout recibe "x".
A continuación, cout recibe la cadena entre comillas ": \ t ", la cual imprime el signo de
dos puntos (:) y luego un tabulador. Luego, cout recibe el valor del parámetro (x), y final
mente recibe endl, el cual escribe una nueva línea y vacía el búfer.
Observe que puede recibir un valor distinto de 0x7 f f f c64.
Niveles de depuración
En proyectos grandes y complejos, podría necesitar un mayor control que sólo activar y
desactivar el modo DEPURAR. Puede definir niveles de depuración y probar esos niveles
cuando decida qué macros utilizar y cuáles quitar.
Para definir un nivel, simplemente coloque un número después de la instrucción #define
DEPURAR. Aunque puede tener cualquier número de niveles, un sistema común es tener
cuatro: ALTO, MEDIO, BAJO y NINGUNO. El listado 21.7 muestra cómo se podría realizar esto,
usando las clases Cadena y Animal del listado 21.5.
Q u é s ig u e 769
L is t a d o 2 1 . 7 continuación
¡
193: Cadena suNombre;
194: >;
195:
196: Animal: ¡Animal(int edad, const Cadena & nombre):
197: suEdad(edad),
198: suNombre(nombre)
199: {
200: ASSERT(ConstantesO);
201: >
202:
203: bool Animal:¡Constantes()
204: {
205: IMPRIMIR (" (Constantes de Animal probadas)");
206: return (suEdad > 0 && suNombre.ObtenerLongitud());
207: }
208:
209: int main()
210: {
211: const int EDAD = 5;
212:
213: EVAL(EDAD);
214: Animal sparky (EDAD, "Sparky");
215: cout « "\n" « sparky.ObtenerNombre() .ObtenerCadena();
216: cout « " tiene "« sparky.ObtenerEdad();
217: cout « " años de edad.\n";
218: sparky.AsignarEdad(8);
219: cout « "\n" « sparky.ObtenerNombre().ObtenerCadena();
220: cout « " tiene "« sparky.ObtenerEdad();
221: cout « " años de edad.\n";
222: return 0;
223: >
EDAD: 5
S a l id a (Constantes de Cadena probadas)
(Constantes de Cadena probadas)
(Constantes de Cadena probadas)
(Constantes de Cadena probadas)
(Constantes de Cadena probadas)
(Constantes de Cadena probadas)
(Constantes de Cadena probadas)
(Constantes de Cadena probadas)
(Constantes de Cadena probadas)
(Constantes de Cadena probadas)
(Constantes de Cadena probadas)
(Constantes de Cadena probadas)
m
L
774 D ía 21
D ebe N O DEBE
D EB E utilizar M A Y Ú S C U L A S para sus n o m N O D EB E perm itir que sus macros tengan
bres de macros. Ésta es una convención efectos se cu nd arios. N o incremente las
m uy arraigada, y otros p rogram adores se variables ni asigne valores desde el inte
con fu n d irán si no lo hace así. rior de una macro.
D EB E encerrar entre paréntesis to do s los
a rg u m e n to s de las funciones de macros.
Qué sigue 775
Manipulación de bits
A menudo es necesario establecer indicadores en sus objetos para mantener el registro
del estado de su objeto. (¿Se encuentra en EstadoDeAlarma? ¿Se ha inicializado ya? ¿Va
o viene?)
Puede hacer esto con valores booleanos definidos por el usuario, pero cuando tiene muchos
indicadores, y cuando el espacio de almacenamiento es importante, es conveniente poder
utilizar los bits individuales como indicadores.
Cada byte tiene ocho bits, por lo que en un valor de tipo long de 4 bytes se pueden guardar
32 indicadores individuales. Se dice que un bit está “encendido” si su valor es 1 y apagado
si su valor es 0. Cuando enciende un bit, su valor se establece en 1, y cuando lo apaga se
establece en 0. Puede encender y apagar bits cambiando el valor del tipo long, pero eso
puede ser tedioso y confuso.
C++ proporciona operadores a nivel de bits que actúan sobre los bits individuales. Éstos
se parecen a los operadores lógicos, pero son distintos de ellos, por lo que muchos pro
gramadores novatos los confunden. Los operadores a nivel de bits se presentan en la
tabla 21.1.
1 OR
* OR exclusivo (XOR)
- De complemento
Operador AND
El operador AND (&) es un solo signo &, en contraste con el operador lógico AND, que
está formado por dos (&&). Cuando se utiliza la operación AND sobre dos bits, el resul
tado es l si ambos bits son l, y es 0 si uno de ellos o ambos son 0. Piense en esto de la
siguiente manera: el resultado es l si el bit 1 y el bit 2 están encendidos.
776 Día 21
Operador OR
El segundo operador a nivel de bits es OR ( | ). De nuevo, es una sola barra vertical, en
contraste con el operador lógico OR, que está formado por dos barras verticales (| |).
Cuando se utiliza la operación OR sobre dos bits, el resultado es l si cualquiera de los
dos bits está encendido, o si los dos están encendidos.
Operador OR exclusivo
El tercer operador a nivel de bits es el operador OR exclusivo O), o XOR. Cuando se
utiliza este operador sobre dos bits, el resultado es l si los dos bits son distintos.
El operador de complemento
El operador de complemento (-) apaga todos los bits de un número que estén encendidos
y enciende cada bit que esté apagado. Si el valor actual del número es 1010 0011, el com
plemento de ese número es 0101 1100 .
Hay unas cuantas cosas que observar. En primer lugar, como siempre, los bits se cuentan
de derecha a izquierda. En segundo lugar, el valor 128 se compone de puros ceros, excepto
el bit 8, que es el que se quiere encender. En tercer lugar, el número inicial 1010 0110 0010
0110 queda sin cambios después de la operación OR, excepto que se encendió el bit 8. En
caso de que el bit 8 ya hubiera estado encendido, habría permanecido encendido, que es lo
que se quería.
Para entender perfectamente esta solución, haga usted mismo la operación matemática.
Cada vez que ambos bits sean 1. escriba 1 en su respuesta. Si cualquiera de los dos bits
es 0, escriba 0 en la respuesta. Compare la respuesta con el número original. Debe ser la
misma, excepto que se apagó el bit 8.
D ebe N O DEBE
DEBE encender bits usando máscaras y el
operador O R a nivel de bits.
DEBE a p a g a r bits u san d o máscaras y el
operador A N D a nivel de bits.
DEBE invertir bits usando máscaras y el
operador O R exclusivo a nivel de bits.
Campos de bits
Bajo ciertas circunstancias, cada byte cuenta, y guardar 6 u 8 bytes en una clase puede ser
una gran diferencia. Si su clase o estructura tiene una serie de valores booleanos o variables
que puedan tener sólo un número muy pequeño de valores posibles, puede ahorrar algo
de espacio si utiliza campos de bits.
Utilice los tipos de datos estándar de C++, el tipo más pequeño que puede utilizar en su
clase es el tipo char, que es de l byte. Por lo general, terminará utilizando un tipo in t , el
cual es de 2 o, más comúnmente, de 4 bytes. Al usar campos de bits, puede guardar 8 valo 2 1
res binarios en un c h a r y 32 de esos valores en un long.
Los campos de bits funcionan de la siguiente manera: Se nombran y se acceden de la misma
manera que cualquier miembro de una clase. Su tipo siempre se declara como entero sin
signo ( u n s ig n e d in t). Después del nombre del campo de bits escriba un signo de dos
puntos (:) seguido de un número. El número es una instrucción para el compilador que
le indica cuántos bits debe asignar a esta variable. Si escribe l, el bit representará ya sea
|778 Día 21
47
48 estu diante Jim;
49
50 if (Jim .ObtenerEstado() == TiempoParcial)
51 cout << "Jim estudia tiempo p a rc ia l" « endl;
52 else
53 cout « "Jim estudia tiempo completo" « endl;
54 Jim .AsignarEstado(Tiem poParcial);
55 i f (Jim .ObtenerEstado())
56 cout << "Jim estudia tiempo p a rcia l" « endl;
57 else
58 cout << "Jim estudia tiempo completo" « endl;
59 cout « "Jim tiene el plan " ;
60
61 char Plan[ 80 ];
62 switch (Jim.ObtenerPlan())
63 {
64 case UnaComida :
65 s trc p y (P la n , "Una comida");
66 break;
67 case TodasLasComidas :
68 s trc p y (P la n , "Todas la s comidas");
69 brea k;
70 case FinesDeSemana :
71 strcpy(Plan,"Comidas en f in de semana");
72 break;
73 case SinComidas :
74 s t r c p y ( P la n , " S in comidas");
75 break;
76 d e fa u lt :
77 cout « "¡Algo s a l ió mal!\n";
78 b re a k ;
79 }
80 cout << Plan « " de alimentación." « endl;
81 return 0;
82 }
En las líneas 6 a 9 se definen varios tipos enumerados. Éstos sirven para definir
A n á l is is
los valores posibles para los campos de bits dentro de la clase estudiante.
En las líneas 12 a 31 se declara estudiante. Aunque es una clase trivial, es interesante ya __________ j
que toda la información está empacada en cinco bits. El primer bit representa el estado del
estudiante, tiempo completo o tiempo parcial. El segundo bit representa si está graduado 2 1
o no. El tercer bit representa si el estudiante vive en un dormitorio. Los últimos dos bits
representan los cuatro posibles planes de alimentación.
Los métodos de la clase se escriben de la misma manera que para cualquier otra clase y
no se afectan de ninguna manera por el hecho de que son campos de bits y no enteros o
tipos enumerados.
|780 Día 21
Lo más importante acerca del uso de campos de bits es que el cliente de la clase no necesita
preocuparse por la implementación del almacenamiento de los datos. Como los campos
de bits son privados, usted puede cambiarlos después y la interfaz no necesitará cambiar.
Estilo de codificación
Como se ha dicho en otras partes de este libro, es importante adoptar un estilo consistente
de codificación, aunque no importa mucho cuál estilo adopte. Un estilo consistente facilita
prever lo que quiso dar a entender en cierta parte del código, y le evita tener que esforzarse
en recordar si escribió la función con mayúscula al inicio la última vez que la invocó.
Los siguientes lincamientos sobre el estilo son arbitrarios; se basan en los lineamientos
utilizados en mis proyectos anteriores, y parecen funcionar bien. Usted puede hacer los
suyos, pero éstos le ayudarán a empezar.
Qué sigue 781 |
Como dijo Emerson, “La mala consistencia es el duende de las mentes pequeñas”, pero
tener algo de consistencia en su código es bueno. Haga su propio estilo de codificación,
pero luego trátelo como si se lo hubieran enviado los dioses de la programación.
Uso de sangrías
El tamaño de los tabuladores debe ser de tres o cuatro espacios. Asegúrese de que su editor
convierta cada tabulador en tres o cuatro espacios.
Llaves
La forma de alinear las llaves puede ser el tema más controversial entre los programadores
de C y de C++. He aquí los tips que yo sugiero:
• Las llaves relacionadas se deben alinear verticalmente.
• El conjunto principal de llaves de una definición o de una declaración debe ir en el
margen izquierdo. Las instrucciones que están en su interior deben tener sangría.
Todos los demás conjuntos de llaves deben estar alineados con sus primeras ins
trucciones.
• No debe aparecer código en la misma línea donde aparezca una llave. Por ejemplo:
if (condicion==true)
{
j = k;
UnaFuncion();
}
(H++;
Líneas largas
Mantenga las líneas en un ancho que se pueda mostrar en una sola pantalla. El código que
desaparece a la derecha de la pantalla puede pasar desapercibido fácilmente, y es muy
molesto desplazarse horizontalmente para ver todo el código. Cuando divida una línea, use
sangría en las siguientes líneas. Trate de dividir la línea en un lugar razonable, y trate de
dejar el operador que intervenga al final de la línea anterior (en lugar de al principio de la
siguiente línea) de forma que esté claro que la línea no es individual y que hay más a
continuación.
En C++, las funciones tienden a ser más cortas de lo que eran en C, pero aún se aplica el
antiguo y buen consejo. Trate de mantener sus funciones lo suficientemente cortas para que
se pueda imprimir toda la función en una página.
Instrucciones switch Sí
Utilice sangría en las instrucciones switch de la siguiente manera para conservar el espa
cio horizontal:
switch (variable)
{
case ValorUno:
782 Día 21
AccionUno();
break;
case ValorDos:
AccionDos();
break;
default:
ASSERT("mala acción“);
break;
>
• No declare más de una variable en la misma línea, a menos que esté muy rela
cionada con la o las otras variables de esa línea.
Q u é s ig u e 783
Nombres de identificadores
Los siguientes son lincamientos para trabajar con identificadores:
• Los nombres de identificadores deben ser lo suficientemente largos para ser
descriptivos.
• Evite las abreviaciones enigmáticas.
• Tómese el tiempo y la energía necesarios para aclarar las cosas.
• Yo no utilizo la notación húngara. C++ está fuertemente tipificado y no hay razón
para colocar el tipo en el nombre de la variable. Con los tipos definidos por el usuario
(clases), la notación húngara se vuelve ineficiente muy pronto. Las excepciones a
esto pueden ser utilizar un prefijo para los apuntadores (ap) y las referencias (r), así
como para las variables miembro de una clase (ints). Por otro lado, algunas personas
están a favor de la notación húngara.
• Los nombres cortos (i, p, x, y así sucesivamente) se deben utilizar sólo donde su
brevedad haga que el código sea más legible y donde su uso sea tan obvio que no
se necesite un nombre descriptivo.
• La longitud del nombre de una variable debe ser proporcional con su alcance.
• Asegúrese de que los identificadores se vean y suenen de manera distinta unos de
otros para minimizar la confusión.
• Por lo general, los nombres de funciones (o métodos) son verbos o frases com
puestas de verbo-sustantivo: Buscar(), Restablecer^ ), E n c o n t r a r P a r r a f o (),
M o s t r a r C u r s o r (). Asimismo, los nombres de variables son sustantivos abstractos,
posiblemente con un sustantivo adicional: cuenta, estado, velo cid ad V iento,
a lt u r a V e n t a n a . Las variables booleanas deben ser nombradas en forma apropiada:
v e n ta n a M in im iza d a , a rchivo EstaA b ierto.
• Empiece con unas cuantas letras minúsculas las constantes enumeradas, como
abreviación para el enum. Por ejemplo:
enum EstiloTexto
{
etSimple,
etNegrita,
etCursiva,
etSubrayado,
};
Comentarios
Los comentarios pueden facilitar la comprensión de un programa. Algunas veces no tra
bajará con un programa durante varios días, o incluso meses. Durante ese tiempo puede
olvidar lo que cierto código hace o por qué se ha incluido. Los problemas de comprensión
de código también pueden ocurrir cuando alguien más lee su código. Los comentarios
que se aplican en un estilo consistente y bien analizado pueden ser muy valiosos. Algunas
sugerencias en relación con los comentarios son:
• Siempre que sea posible, utilice comentarios estilo C++ ( / / ) en lugar de los de
estilo /* */.
• Los comentarios de nivel más alto son definitivamente más importantes que los
detalles del proceso. Agregue valor; no sólo vuelva a decir lo que hace el código.
n++; // n se incrementa en uno
• Este comentario no vale el tiempo que toma escribirlo. Concéntrese en la semántica
de las funciones y de los bloques de código. Diga lo que hace una función e indique
los efectos secundarios, tipos de parámetros y valores de retomo. Describa todas las
suposiciones que se hacen (o que no se hacen), como “supone que n no es negativo”
o “regresará -1 si x no es válida”. Dentro de la lógica compleja, utilice comentarios
que indiquen las condiciones que existen en ese punto del código.
Utilice enunciados completos en español con la puntuación y el uso de mayúsculas
apropiados. La escritura adicional vale la pena. No sea demasiado enigmático y no
abrevie. Lo que le parece demasiado claro al estar escribiendo el código, le será
sorprendentemente confuso en unos cuantos meses.
• Utilice líneas en blanco para ayudar al lector a comprender lo que está pasando.
Separe las instrucciones en grupos lógicos.
Acceso
La manera en que acceda a porciones de su programa también debe ser consistente. Algunas
sugerencias para el acceso son:
Q u é sig u e 785
Definiciones de clases
Trate de mantener las definiciones de los métodos en el mismo orden que las declaraciones.
Esto hace que las cosas sean más fáciles de encontrar.
Al definir una función, coloque el tipo de valor de retorno y los otros modificadores en una
línea anterior, para que el nombre de la clase y el nombre de la función empiecen en el
margen izquierdo. Esto facilita mucho la búsqueda de funciones.
Archivos de encabezado
Trate, lo más que pueda, de no incluir archivos en archivos de encabezado. El mínimo
ideal es el archivo de encabezado para la clase de la que se deriva. Otros archivos in elud e
obligatorios son los de los objetos que son miembros de la clase que se está declarando.
Las clases a las que simplemente se apunta o se hace una referencia sólo necesitan referen
cias posteriores de la forma.
No omita un archivo in e lu d e en un encabezado sólo porque suponga que cualquier
archivo .cxx que lo incluya también tendrá el inelude necesario.
2 1
ASSERT()
Utilice la macro a s s e r t () libremente. Ayuda a encontrar errores, pero también ayuda mu
cho a que el lector entienda con claridad cuáles son las suposiciones. También ayuda a
enfocar los pensamientos del escritor en lo que es y lo que no es válido.
786 Día 21
const
Utilice const en donde sea apropiado: para parámetros, variables y métodos. A menudo
existe la necesidad tanto de un método const como de uno que no sea const; no utilice
esto como excusa para omitir uno. Sea muy cuidadoso al convertir explícitamente de const
a no const y viceversa (algunas veces, ésta es la única forma de hacer algo), pero asegúrese
de que tenga sentido, e incluya un comentario.
Revistas
También puede reforzar sus habilidades suscribiéndose a una buena revista sobre pro
gramación en C++. Algunas de las mejores revistas en relación con este tema son: C++
Report de SIGS Publications y C/C+ + Users Journal de Miller Freeman. Cada número
tiene artículos útiles. Guárdelos; lo que no le preocupa hoy puede ser de vital importan
cia mañana.
Q u é s ig u e 787
üVüamtémgase en contacto
Si tiene comentarios, sugerencias o ideas sobre este u otros libros, puede ponerse en contac
to con los autores. Puede comunicarse con Jesse Liberty en la dirección jliberty@ liber-
ty a sso c ia te s . com (página Web http: / /w w w . lib ertyassociates. com). Puede ponerse
en contacto con David Horvath en la dirección cpplinux@cobs. com (página Web
h ttp : / / w w w . cobs.com/-dhorvath).
D ebe N O DEBE
DEBE consultar otros libros. Hay mucho ¡NO DEBE sólo leer código! La mejor
que ap re n de r y un solo libro no puede forma de aprender C++ es escribiendo
enseñarle to d o lo que necesita saber. programas de C++.
Su próximo paso
El próximo paso que debe realizar, después de descansar un poco, es aprender más sobre
los sangrientos detalles de C++ y de Linux. ¡La semana adicional de este libro lo guiará!
Resumen
Hoy aprendió más detalles sobre la forma de trabajar con el preprocesador. Cada vez que
ejecuta el compilador, primero se ejecuta el preprocesador y traduce sus directivas de
preprocesador, como # d e fin e e #ifdef.
El preprocesador realiza la sustitución del texto, aunque con el uso de las macros esto puede
ser un poco complejo. Al usar tfifdef, #e lse e # ifn d e f puede realizar la compilación
condicional, compilando instrucciones específicas bajo un conjunto de condiciones y
otro conjunto de instrucciones bajo otras condiciones. Esto puede ayudarle a escribir
programas para más de una plataforma y por lo regular se utiliza para incluir condicional
mente información de depuración.
Las funciones de macros proporcionan una sustitución compleja de texto basada en argu
mentos pasados a la macro en tiempo de compilación. Es importante colocar paréntesis
alrededor de todos los argumentos de la macro para asegurar que se haga la sustitución
adecuada.
En C++. las funciones de macros, y el preprocesador en general, son menos importantes
de lo que eran en C. C++ proporciona una variedad de características del lenguaje, como
variables const y plantillas, que ofrecen alternativas superiores al uso del preprocesador.
788 Día 21
También aprendió cómo encender y probar bits individuales y cómo asignar un número
limitado de bits a los miembros de una clase.
Por último, se trataron cuestiones relacionadas con el estilo de C++, y se proporcionaron
recursos para su posterior estudio.
Preguntas y respuestas
P Si C++ ofrece mejores alternativas que el preprocesador, ¿por qué esta opción
aún está disponible?
R En primer lugar, C++ tiene compatibilidad con C, por lo que todas las partes
importantes de C deben ser soportadas en C++. En segundo lugar, algunos usos
del preprocesador se siguen utilizando con frecuencia en C++, como los guardias de
inclusión, por ejemplo.
P ¿Por qué utilizar funciones de macros si se puede usar una función normal?
R Las funciones de macros se expanden en línea y se utilizan como sustituto parala
escritura repetitiva de los mismos comandos con pequeñas variaciones. Sin embar
go, las plantillas ofrecen una mejor alternativa.
P ¿Cómo se sabe cuándo usar una macro y cuándo usar una función en línea?
R Por lo general, esto no es muy importante; utilice la que sea más simple. Sin embargo,
las macros ofrecen la sustitución de caracteres, de cadenas y la concatenación. Ningu
na de estas tres está disponible en las funciones.
P ¿Cómo se puede usar el preprocesador para imprimir valores interinos durante
la depuración?
R La mejor alternativa es utilizar instrucciones watch dentro de un depurador. Para
obtener información sobre las instrucciones watch, consulte el manual de g++ y gdb
y el archivo de información (o la documentación que venga con su compilador o
depurador, si utiliza algo distinto de las herramientas GNU).
P ¿Cómo se puede decidir cuándo utilizar una macro ASSERT() y cuándo producir
una excepción?
R Si la situación que está probando puede ser verdadera sin que haya cometido un error
de programación, use una excepción. Si la única razón de que esta situación sea
verdadera es un bug en su programa, utilice una macro ASSERT ().
P ¿Cuándo se deben utilizar estructuras de bits, en lugar de simplemente usar
enteros?
R Cuando el tamaño del objeto sea muy importante. Si está trabajando con memoria
limitada o con software de comunicaciones, tal vez descubra que los ahorros ofrecidos
por estas estructuras son esenciales para el éxito de su producto.
.'■j
Q u é s ig u e 789
R He aquí algunos libros que he escrito para proporcionar un curso de estudio, aunque
hay muchos otros libros de gran valor. C++ Unleashed, Beginning Object-Oriented
A nalysis and D esign y Career Change C+ + son buenos puntos para comenzar.
P ¿ E s to d o ?
R ¡Sí! Ya ha aprendido C++, pero...¡no, todavía no termina! Hace 10 años era posible
que una persona aprendiera todo lo que había que saber acerca de las computadoras
y la programación, o por lo menos que confiara en estar cerca de ello. Hoy eso queda
fuera de cuestionamiento. Simplemente no se puede ponerse al corriente, porque
mientras usted tratar de hacerlo, la industria va cambiando. Pero asegúrese de seguir
leyendo, y manténgase en contacto con los recursos (revistas y servicios en línea)
que lo mantendrán al corriente de los cambios más recientes.
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del mate
rial tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate de
responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1 • ¿Qué es un guardia de inclusión?
2. ¿Cómo le indica al compilador que imprima el contenido del archivo intermedio,
para que muestre los efectos del preprocesador?
3. ¿Cuál es la diferencia entre //define depurar 0 y //undef depurar?
790 D ía 21
Ejercicios
1. Escriba las instrucciones de guardias de inclusión para el archivo de encabezado
STRING.H.
2. Escriba una macro ASSERT () que imprima tanto luí mensaje de error como el archivo
y el número de línea si el nivel de depuración es 2, que imprima un mensaje (sin
archivo ni número de línea) si el nivel es 1, y que no haga nada si el nivel es 0.
3. Escriba una macro llamada DImprimir que evalúe si DEPURAR está definida y, de ser
así, que imprima el valor que se pasa como parámetro.
4. Escriba un programa que sume dos números sin utilizar el operador de suma (*).
Pista: ¡Use los operadores a nivel de bits!
S ebvdaw a 3 8
[Repaso
El p ro g ra m a d e re p aso de la sem ana 3, que se m uestra en el
listado R 3 .1. reúne m uchas de las técnicas avanzadas que apren
d ió d u ra n te las ú ltim as tres sem anas de trabajo duro. El repaso
de la se m an a 3 p roporciona una lista enlazada basada en plan
tillas c o n m anejo de excepciones. Exam ine este program a con
d eta lle ; si lo en tien d e sin problem as, entonces usted es un pro
g ra m a d o r d e C-t--K
1 2
1: //
****************************************************
2:
3:
//
// T i t l e : Repaso de la semana 3
13
4: //
5: // F i l e : Semana3
6: //
7: // D e sc ripción : Proporcionar un programa de
'■ »muestra de una l i s t a
8: // enlazada basada en p l a n t i l l a s con manejo de
“ ■ excepciones 1 4
9: //
10: // C lase s: PIEZA - guarda números de piezas y
“ potencialmente otra
11 : // información sobre la s piezas. Ésta será la
“ c la s e de
continúa
1792 Semana 3
L is t a d o R 3 . 1 continuación
62: {
63: cout « "\nNúmero de pieza: "
64: « suNumeroObjeto « endl;
65: }
66:
67: // este operator« será llamado para todos los objetos Pieza.
68: //No necesita ser amigo ni tener acceso a los datos privados
69: // Llama a Mostrar(), el cual utiliza el polimorfismo requerido
70: // Nos gustaria poder redefinirlo con base en el tipo real
71: // de la Pieza, pero C++ no soporta la contravarianza
72: ostream & operator« (ostream & elFlujo, Pieza & laPieza)
73: {
74: // ¡contravarianza virtual!
75: laPieza.Mostrar();
76: return elFlujo;
77: }
78:
yg • j j **************** Pj_g23 de AutO ************
80: class PiezaAuto : public Pieza
81: {
82: public:
83: PiezaAuto() : SuAnioModelo(94) {}
84: PiezaAuto(int anio, int numeroPieza);
85: int ObtenerAnioModelo() const
86: { return SuAnioModelo; }
87: virtual void Mostrar() const;
88: private:
89: int SuAnioModelo;
90: };
91:
92: PiezaAuto::PiezaAuto (int anio, int numeroPieza):
93: SuAnioModelo(anio),
94: Pieza(numeroPieza)
95: {>
96:
97: void PiezaAuto::Mostrar() const
98: {
99: Pieza::Mostrar();
100: cout « "Año del modelo: "
101: « SuAnioModelo « endl;
102: }
103:
104: // **************** pieza de Aeroplano ************
105: class PiezaAeroPlano : public Pieza
106: {
107: public:
108: PiezaAeroPlano() : suNumeroMotor(1) {};
109: PiezaAeroPlano(int NumeroMotor,
110: int NumeroPieza);
111: virtual void Mostrar() const;
112: int ObtenerNumeroMotor()const
continúa
794 Semana 3
L is t a d o R 3 .1 continuación
164: Nodo<T>::-Nodo()
165: {
166: delete suObjeto;
167: suObjeto = 0;
168: delete suSiguiente;
169: suSiguiente = 0;
170: }
171:
172: // Regresa NULL si no hay Nodo siguiente
173: template < class T >
174: Nodo< T > * Nodo< T >: :ObtenerSiguiente() const
175: {
176: return suSiguiente;
177: }
178:
179: template < class T >
180: T * Nodo< T >: :ObtenerObjeto() const
181: {
182: if (suObjeto)
183: return suObjeto;
184: else
185: throw NodoNulo();
186: }
187:
188: // **************** Lista ************
189: // Plantilla de lista genérica
190: // Funciona con cualquier objeto numerado
191: // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
192: template < class T >
193: class Lista
194: {
195: public:
196: Lista();
197: ~Lista();
198: T * Buscar(int & posición,
199: int NumeroObjeto) const;
200: T * ObtenerPrimero() const;
201: void Insertar(T *);
202: T * operator!] (int) const;
203: int ObtenerCuenta() const
204: { return suCuenta; }
205: private:
206: Nodo< T > * apCabeza;
207: int suCuenta;
208: };
209:
210: // Implementaciones para las Listas...
211: template < class T >
212: Lista< T >::Lista():
213: apCabeza(0),
214: suCuenta(0)
215: {>
216:
continúa
L
796 Semana 3
L is t a d o R 3 . 1 co n t in u a c ió n
268 {
269 Nodo< T > * apNodo = new Nodo< T >(apObjeto);
270 Nodo< T > * apActual = apCabeza;
271 Nodo< T > * apSiguiente = NULL;
272
273 int Nuevo = apObjeto->ObtenerNumeroObjeto();
274 int Siguiente = 0;
275 suCuenta++;
276
277 if (¡apCabeza)
278 {
279 apCabeza = apNodo;
280 return;
281
282
283 //si éste es más pequeño que el nodo cabeza
284 // entonces se convierte en el nuevo nodo cabeza
285 if (apCabeza->suObjeto->ObtenerNumeroObjeto() > Nuevo)
286 {
287 apNodo->suSiguiente = apCabeza;
288 apCabeza = apNodo;
289 return;
290 }
291
292 for (; ;)
293 {
294 // si no hay siguiente, agregar éste
295 if (¡apActual->suSiguiente)
296 {
297 apActual->suSiguiente = apNodo;
298 return;
299 }
300
301 I I si va después de éste y antes del siguiente
302 // entonces insertarlo aquí, de no ser así
303 // obtener el siguiente
304 apSiguiente = apActual->suSiguiente;
305 Siguiente = apSiguiente->suObjeto->
306 ObtenerNumeroObjeto();
307 if (Siguiente > Nuevo)
308 {
309 apActual->suSiguiente = apNodo;
310 apNodo->suSiguiente = apSiguiente;
311 return;
312 }
313 apActual = apSiguiente;
314 }
315 >
316
317
318 int main()
319 {
320 Lista< Pieza > laPieza;
continúa
798 Semana 3
L is t a d o R 3 . 1 continuación
370 laPieza.Insertar(apPieza);
371 >
372 catch (NodoNulo)
373 {
374 cout « "¡La lista está dividida,"
375 « " y el nodo es nulo!" « endl;
376 return 1;
377 }
378 catch (ListaVacia)
379 {
380 cout « "¡La lista está vacía!” « endl;
381 return 1;
382 }
383 }
384 try
385 {
386 for (int i = 0; i < laPieza.ObtenerCuenta(); i++)
387 cout « *(laPieza[ i ]);
388 }
389 catch (NodoNulo)
390 {
391 cout « "¡La lista está dividida,”
392 « " y el nodo es nulo!" « endl;
393 return 1;
394 }
395 catch (ListaVacia)
396 {
397 cout « "¡La lista está vacía!” « endl;
398 return 1;
399 }
400 catch (ErrorLimites)
401 {
402 cout « "¡Trató de leer más allá "
403 « "del final de la lista!" « endl;
404 return 1;
405 }
406 return 0;
407 }
Las funciones virtuales no funcionarán aquí ya que operator« no es una función miembro
de Pieza. No puede hacer que operator« sea una función miembro de Pieza debido a
que debe invocar a
cout « laPieza
y eso significa que la llamada en realidad sería a cout.operator«(Pieza &), ¡y cout no
tiene una versión de operator« que tome una referencia a Pieza!
Para sobrepasar esta limitación, el programa de la semana 3 utiliza sólo un operator«,
que toma una referencia a una Pieza. Éste a su vez llama a Mostrar(), que es una función
miembro virtual, y por consecuencia se llama a la versión adecuada.
En las líneas 139 a 153, la clase Nodo se define como una plantilla. Esta clase sirve para lo
mismo que la clase Nodo del programa de repaso de la semana 2, pero esta versión de Nodo
no está atada a un objeto Pieza. De hecho, puede ser el nodo para cualquier tipo de objeto.
Observe que si trata de obtener el objeto de Nodo, pero no hay objeto, esto se considera
como una excepción, y la excepción se produce en la línea 185.
En las líneas 192 a 208 se define una plantilla de la clase genérica Lista. Esta clase Lista
puede guardar nodos de cualquier objeto que tenga número de identificación único, y los
mantiene ordenados en forma ascendente. Cada una de las funciones de la lista revisa si hay
circunstancias excepcionales y produce las excepciones apropiadas, según se requiera.
En la línea 320, el programa controlador crea una lista de objetos Pieza donde se almacena
rán los dos tipos distintos que existen: PiezaAuto y PiezaAeroPlano. Posteriormente, se
imprimirán los valores de cada objeto de la lista usando el mecanismo de flujo estándar.
P re gu n ta s frecuentes
FAQ: En el comentario que está arriba de la linea 72 del listado R3.1, mencionó que C++
no soporta la contravarianza. ¿Qué es la contravarianza?
Respuesta: La contravarianza es la habilidad de asignar un apuntador de una clase base a
un apuntador de una clase derivada.
void Hacerlo(Gato * c)
{
cout « "¡Pasaron un gato!\n" << endl;
c->Hablar();
>
void Hacerlo(Perro * d)
{
cout « "¡Pasaron un Perro!\n" « endl;
d ->Hablar();
}
Repaso 803
Lo que puede hacer, desde luego, es utilizar una función virtual, lo que resuelve parcial
mente el problema. El listado R3.3 muestra cómo se hace esto.
#include<iostream.h>
class Animal
{
public:
virtual void Hablar() { cout « "Animal habla\n"; }
};
class Perro : public Animal
{
public:
void Hablar() { cout « “Perro habla\n”; }
},*
int main()
{
Animal * apA = new Perro;
Hacerlo(apA);
return 0;
}
void Hacerlo(Animal * c)
{
cout « "Pasaron algún tipo de animal \n" « endl;
c->Hablar();
}
m
Se m a n a 4 2 2
De un vistazo 23
Ya term inó la tercera y última semana regular del aprendizaje
de C++ para Linux. Para estos momentos debe estar familia
rizado con los aspectos avanzados de la programación orientada
a objetos.
24
Objetivos
Por fin ha llegado a la semana adicional, que trata los temas
más específicos y avanzados sobre el desarrollo de programas
de C++ en Linux. En el día 22, ‘‘El entorno de programación de
Linux”, aprenderá acerca de algunas de las herramientas de pro 25
gram ación avanzadas en Linux, y en el día 23, “Programación
shell”, aprenderá cómo utilizar los intérpretes de comandos en
Linux y cóm o crear secuencias sencillas de comandos de shell.
El día 24, “Programación de sistemas”, se enfoca en la interfaz
del sistem a operativo y las funciones y objetos de las bibliote
cas de C++ que puede utilizar. En el día 25, “Comunicación
26
entre procesos”, aprenderá a utilizar las funciones del sistema
que perm iten la comunicación entre distintos procesos. Por
últim o, en el día 26, “Programación de la GUI”, aprenderá
cóm o crear aplicaciones para aprovechar las herramientas de la
GUI (interfaz gráfica de usuario) disponibles en Linux.
i
I
I
ï:i;
I-
?
¡:
S emana 4
El entorno de
programación de Linux
H asta estos días, ha estado aprendiendo a utilizar el lenguaje de programación
C++. P ara estos m om entos ya conoce la sintaxis del lenguaje y la forma de
diseñar y escribir program as orientados a objetos. La lección de hoy trata sobre
el entorno para escribir programas que proporciona Linux. ¿Cuáles son las herra
m ientas disponibles? ¿Cómo se crea, se compila y se depura un programa, y cómo
se rastrea su historial a medida que se va modificando?
F ilo s o f ía e h is t o r ia
En sus primeros días, Linux era popular casi exclusivamente entre los desarrolladores (en
especial, personas que querían estudiar o escribir piezas del kernel, o personas que querían
crear herramientas y otras utilerías). Si va a desarrollar software, lo primero que necesita
son herramientas de desarrollo. Una vez que tiene un editor, necesita un compilador. Como
resultado, las primeras cosas que se llevaron a Linux fueron compiladores, ensambladores
y enlazadores.
Un compilador es algo difícil de escribir, y usted realmente no querría crear uno desde
cero si no tuviera que hacerlo. Por lo tanto, era normal que los desarrolladores de Linux
buscaran un compilador gratuito, que tuviera el código fuente disponible para poder por
tarlo. La elección obvia (tal vez la única elección) en ese entonces era el compilador GNU
de C. Las herramientas GNU se adherían a una filosofía similar de código abierto, y estaban
ampliamente disponibles, además de contar con una alta calidad comprobada.
A diferencia de muchos sistemas operativos comerciales de la actualidad, las herramientas
disponibles para Linux no están limitadas a las que proporciona el fabricante o a las que
se pueden comprar. Linux es software de código abierto, y las herramientas de código
abierto disponibles en Internet funcionan generalmente con Linux.
Si hay una herramienta de código abierto que no haya sido incluida en su distribución, lo
más probable es que ya pueda descargar de Internet una versión creada para Linux. Por otro
lado, casi todas las distribuciones de Linux vienen con un conjunto muy completo de
herramientas GNU para desarrollo de programas.
P O S IX
En los 80, el IEEE (Instituto de Ingenieros Eléctricos y Electrónicos) empezó a desarrollar
un entorno de programación estándar para promover la portabilidad de aplicaciones entre
distintos entornos UNIX. El nombre que se dio a este esfuerzo y al estándar resultante
fue Interfaz Portable de Sistema Operativo”, y se conoce comúnmente como POSIX.
Este estándar no regula la forma en que debe comportarse un sistema operativo, pero sí
define la API (interfaz de programación de aplicaciones) que el sistema operativo debe
proporcionar para el escritor de programas de aplicaciones. Esto puede parecer un poco
confuso. Basta decir que Linux se apega al estándar POSIX. En otras palabras, propor
ciona un conjunto estándar de llamadas al sistema y servicios como los definidos por
el estándar POSIX.
El s is t e m a X W in d o w s
Si ha utilizado computadoras Macintosh o computadoras que ejecuten cualquier versión
de Microsoft Windows, entonces está familiarizado con lo que se conoce como GUI
(interfaz gráfica de usuario). En estos sistemas, la GUI es una parte integral del sistema
operativo. Esto no es así con Linux. En Linux, la GUI se encuentra en el nivel superior
El entorno de programación de llnux 809
del software de sistema de ventanas, que es, en esencia, un accesorio para el sistema opera
tivo. Con este método hay muchas ventajas en cuanto a rendimiento, confiabilidad y flexibi
lidad. El nombre “sistema X Windows” se aplica libremente a todos los componentes
de este software de ventanas, incluyendo con frecuencia a la propia GUI. El desarrollo de
“X”, como se le conoce comúnmente, empezó en el MIT a mediados de los 80. El sistema
X Windows proporcionado con Linux viene del proyecto XFree86.
U s o d e l o s e d it o r e s d e L in u x
Si va a escribir código, evidentemente necesita alguna forma de introducirlo a un archivo.
Uno de los primeros resultados del proyecto GNU fue el editor emacs, y los editores
estuvieron entre los primeros programas que se portaron a Linux.
F ig u r a 22.1
Un archivo This file describes how t h e I H I T p r o c e s s s h o u l d s e t up
editado en vi. t h e s y s t e m in a c e r t a i n run-level.
» System initialization.
s i :: s y s i n i t : / e t c / r c . d / r c . s y s i n i t '
10:0:wait:/etc/rc.d/rc 0
ll:l:wait:/etc/rc.d/rc i
|J12:2:wait:/etc/rc.d#rc 2 .
® 13:3¡wait:/etc/rc.d/rc 3
g " i n i t t a b " [readonly] line 1 of 57 — 1 % — col 1 ______________________ ____________
C o n c e p to s d e vi
vi es un "editor de m odo” basado en texto, y viene tres m odos: com m and, insert y ex. El
m odo p red eterm in ad o es el m odo de c o m a n d o s. E sto sig n ific a que lo que escribe son
com andos para el editor en lugar de entrada para el archivo en el que está trabajando.
La m ayoría de los com andos de vi es de una sola letra, algunos tienen un modificador de
alcance. La m ayoría puede estar precedida de un núm ero, que es el “factor de repetición”
(el cual ocasiona que el com ando subsecuente se repita ese núm ero de veces).
En m odo insertar, las pulsaciones de teclas se capturan y se introducen en el archivo que
se está editando. En este m odo, la palabra IN S E R T se despliega en la parte inferior de la
pantalla.
El vi original era en realidad una interfaz basada en pantalla para el editor de línea ex, y vim
em ula este com portam iento. El tercer m odo disponible en vim es el modo ex. En este modo
aparece un indicador en la parte inferior de la pantalla. Puede escribir cualquier coman
do ex en este indicador. Los com andos ex son útiles para alternar entre archivos sin salir del
El e n t o r n o d e p r o g r a m a c ió n d e lin u x 811
editor, y ofrecen la completación del nombre de archivo para facilitar esto (es decir, usted
escribe las prim eras letras del nombre de un archivo y después presiona la tecla “Entrar",
y el nombre del archivo se completa automáticamente). Casi todas las otras tareas que puede
realizar con un comando ex, también puede realizarlas desde el modo de comandos vi.
Para reg resar al m odo de comandos desde cualquier otro modo, oprima “Esc”. Si ya se
encuentra en m odo de comandos, esta tecla no tiene efecto, así que si alguna vez no está
seguro de en cuál m odo se encuentra, sólo oprima “Esc” un par de veces para regresar al
m odo de com andos.
Para tener acceso a la ayuda en línea con vi, como se muestra en la figura 22.2, escriba
:h e lp desde el m odo de comandos.
U so d e vi
Ahora pruebe una sesión de ejemplo con vi. Para esto, creará un programa sencillo en C++
llam ado h o l a . cxx.
1. Escriba vi hola.cxx para empezar a editar el archivo mievo.
2. E scriba i para entrar al modo insertar.
3. Escriba el siguiente texto:
#incl u d e <stream.h>
int main(int argc, char * argvf])
{
cout « "¡Hola, mundo!" « endl;
return 0 ;
}
812 Día 22
emacs de GNU
Las guerras religiosas abundan. Muchos usuarios de Linux juran que vi es demasiado
antiguo y que el único editor que vale la pena usar es emacs. Usted mismo tiene que decidir
cuál editor es más fácil de utilizar. Ciertamente, emacs tiene más características, y hay
varios libros importantes que tratan sobre este editor, emacs es demasiado complejo como
para tratarlo detalladamente aquí, por lo que esta sección cubre sólo sus aspectos básicos.
Cómo iniciar emacs de GNU
emacs se puede ejecutar dentro de una ventana de terminal o en su propia ventana en X
Windows. Si está ejecutando X, el comando emacs &abrirá una nueva ventana con el
editor. Para ejecutarlo dentro de la ventana actual, utilice el comando emacs -nw.
Si inicia emacs sin un nombre de archivo de destino, éste creará un búfer “scratch” por
usted y le mostrará una breve pantalla de ayuda, como se muestra en la figura 22.3.
Si está ejecutando la versión X de emacs, puede utilizar el ratón para navegar por los
menús desplegables que se encuentran en la parte superior de la pantalla. A medida que
vaya adquiriendo más experiencia, descubrirá que hay teclas de método abreviado para
todos estos comandos desplegables.
La tabla 22.1 muestra las notaciones convencionales de emacs.
Fig u r a 22.3
Buffer- Files Tools Edit Search Kile Help
Ventana de bienvenida loose to GNU Eaacs, one component of a Linus-based GNU eysten.
de emacs de GNU. The menu bar and scroll bar are sufficient for basic editing with the nouse.
U se fu l F i l e s menu item s:
E s it E m a c s (or type C o ntro l-s followed by C o ntro l-c)
ver Sess ion irecoverf i l e s you were e d itin g before a crash
Ta b la 2 2 . 1 N o t a c i ó n c o n v e n c i o n a l de emacs
Com ando n o t a c ió n de em acs
Entrar RET
Retroceso DEL
Escape ESC
Control C-
Meta (Alt) M-
Espacio SPC
Tabulador TAB
Para ir directam ente al tutorial en línea, oprima C-h t (oprima Control y h al mismo tiempo,
luego libere am bas teclas y oprima t).
El sistem a i n f o (descrito más adelante) proporciona ayuda en línea para todo el software
de G N U . Para ver la información, oprima C-h i.
Hay otros tipos de ayuda disponibles en emacs. Puede oprimir C-h ? para averiguar qué
tipo de ayuda existe. Por ejemplo, si desea ayuda acerca de una combinación específica
de teclas, siga estos pasos:
1. C-h (m uestra las opciones de ayuda)
2. c (selecciona la opción “describe key briefly” [breve descripción de teclas])
3. C-.v C-c (m uestra las acciones que realiza este comando)
| 814 Día 22
M uchas personas sólo utilizan emacs. Puede utilizarlo no sólo para editar archivos, sino
tam bién para ejecutar comandos y para compilar, depurar y ejecutar programas. Si tiene
errores en tiem po de compilación, emacs puede saltar automáticamente hasta la línea que
contenga el error. Sin duda, vale la pena invertir tiempo para investigar más sobre emacs.
ctags y etags
Al escribir un programa grande en C++, cabe la posibilidad de que necesite dividir el código
fuente en varios archivos. Cada archivo definirá los métodos para una o varias clases.
Después, al depurar el archivo, puede ser difícil navegar por todos esos archivos fuente. Tal
vez esté editando el archivo A y haya un método invocado que esté definido en alguna otra
parte, tal vez en el archivo B. Los programas ctags y etags crean archivos índice o "tag”,
que vi y em acs pueden utilizar para ayudarlo a navegar por sus archivos fuente.
ctag s es el program a m ás antiguo, y genera marcas para vi de manera predeterm inada.
Puede indicarle que genere marcas para emacs. etags genera marcas para emacs de manera
predeterm inada, pero también puede indicarle que genere marcas para vi.
E je m p lo d e c t a g s co n vi
Inicie una sesión de edición con el comando v i holaP rincipal.cxx. Ahora, usando las
teclas h, j. k y 1, ponga el cursor en la palabra Saludar. Oprima C- f , y verá que el editor
abre automáticamente el archivo que contiene la definición de esa función (Saludar.cxx)
y coloca el cursor al inicio de la función.
Hay una funcionalidad similar en eniacs. Utilice la ayuda en línea para ver cómo se utiliza.
L e n g u a je s
Linux proporciona todos los lenguajes disponibles en sistemas UNIX tradicionales, y algo
más. Muchos lenguajes no tradicionales están disponibles en Internet. La mayoría de las
distribuciones vienen con C y C++, y a menudo con una implementación de Java. Los len
guajes de secuencias de comandos como perl, sed y awk también son parte de la mayoría
de las distribuciones.
gcc y g++
El compilador C de GNU se llama gcc y puede compilar C, C++ y Objective-C. El com
pilador de C se apega al estándar ANSI, por lo que debe ser sencillo portar de ANSI a Linux
casi cualquier programa de C. Si está familiarizado con el compilador de C de cualquier
otro sistema UNIX, descubrirá que gcc es bastante similar. Debido a que gcc es gratuito
y de alta calidad, muchos sitios comerciales lo utilizan como su compilador de C preferido.
Cómo compilar con gcc
El compilador GNU se invoca con el comando gcc. De manera predeterminada, este
comando preprocesará, compilará y enlazará un programa de C. Existen muchas opciones
para gcc, y entre ellas existen controles que le permiten ejecutar cualquier fase específica
de la secuencia preproceso/compilación/enlace.
El siguiente ejemplo sencillo tira un dado n veces, y luego imprime el número de veces
que sale cada una de sus caras.
El listado 22.1 muestra el programa principal llamado para el juego dado.
11 : int i;
12: int ilter;
13: int Dado[ 6 l; 2 2
14:
15: if (argc < 2)
16: {
17: p r i n t f ("Uso: °ós n\n", argv[
18: return 1 ;
19: }
20: ilter = atoi(argv[ 1 ]);
21 : memset(Dado, 0, sizeof (Dado));
22: for(i = 0; i < ilter; i++)
23: {
24: Dado[ tirarDado() - 1 ]++;
25: }
26: p r i n t f ("%d tiradas\n", ilter);
27: p r i n t f ("\tCara\tTiradas\n");
28: for(i = 0; i < 6; i++)
29: {
30: p r i n t f (" \t%d :\t%d\n", i + 1
31 : }
32: }
tfinclude <stdlib.h>
int tirarDado(void)
{
return((rand() % 6) + 1);
Puede ver que -o indica el nombre del archivo de salida, gcc es lo suficientem ente
inteligente para ver que los archivos que terminan con .c son archivos fuente de C. y los
com pila com o tales. Si no especifica un nombre de archivo de salida, el programa de salida
predeterm inado se llamará a .o u t.
E stam os tratando de dem ostrar un concepto ligeramente más complejo, así que hagam os
esto otra vez, sólo que esta vez compilaremos los módulos por separado:
gcc -c lst2 2 -0 1 .c x x
gcc -c l s t 2 2 -0 2 .cxx
gcc -o dado l s t 2 2 -0 1 .o l s t 2 2 -0 2 .o
818 D ía 2 2
C óm o c o m p ila r c o n g + +
El com ando gcc es en realidad una "in terfaz" del com pilador. Al analizar los archivos que
se le proporcionan, sabe si se requiere un en lace o una com pilación. También sabe si el
archivo íuente es de C o de C++. Si está c o m p ila n d o ('+ + . puede invocaren forma alter
nativa el com pilador g++ directam ente con el co m an d o g ++ nom brearchivo.
A unque gcc puede com pilar program as de ('+ + . no hace autom áticam ente todos los enlaces
requeridos con las bibliotecas de clases. N ecesita usar g++ para esto. Como resultado, por
lo general es más sencillo com pilar y en la z a r program as de C’++ con g++.
Por e jem p lo , para cre a r el p ro g ra m a h o la que c re ó a n te rio rm e n te con vi, utilizaría los
siguientes com andos de g++:
g++ -c h o l a P r i n c i p a l . c xx
g++ -c S a l u dar.cxx
g++ -o hola h o l a P r i n c i p a l .o S a l u d a r . o
Al igual que con gcc, puede lograr lo m ism o con la sim ple invocación:
g++ -o hola h o l a P r i n c i p a l . c x x S a l u d a r . c x x
ELF
Cuando se com pila un program a, se genera un archivo objeto, y cuando se enlaza el progra
m a, se ciea un archivo binario ejecutable. El en la z a d o r debe entender el formato de los
ai chivos objeto, y com o el sistem a o p erativo debe cargar y ejecutar el programa ejecu
table. tam bién debe entender ese form ato.
Ya vio que el archivo ejecutable predeterm inado se llam a a .o u t. Hasta hace poco, el forma
to de los archivos objeto y de los archivos e jecu tab les se conocía com o formato a.out.
Este form ato es bastante antiguo y tiene varios defectos. El form ato más moderno utilizado
poi la m ayoría de los sistem as U N IX y Linux se co n o ce com o ELF (formato ejecutable
y de enlace). ELF es m ucho m ás versátil que a .o u t , y se presta muy bien para crear biblio
tecas com partidas.
P uede saber cuál es el form ato de un archivo p o r m edio del com ando f i l e :
file dado /u s r / b i n / a r c h i v o l s t 2 2 -0 2. o
Bibliotecas compartidas
A menudo, varios programas necesitan hacer las mismas cosas, como E/S por ejemplo.
Hace mucho tiempo se desarrolló el concepto de biblioteca para adaptar esto. Las funciones
comunes se pueden colocar en un archivo, y luego, cada vez que se crea un programa, éste
extrae de la biblioteca las funciones que necesita.
En su momento, esto fue un avance, pero tenía varias desventajas. Los ejecutables se hacen
más grandes ya que cada uno de ellos incrusta código copiado de las bibliotecas. Si se
encuentra un error en la biblioteca o se agrega una característica, el ejecutable no hace uso
de esto a menos que se vuelva a crear.
La solución para este problema es la biblioteca compartida (o dinámica). El mecanismo de
funcionamiento de las bibliotecas compartidas está más allá del alcance de esta lección. Sólo
veremos cómo crearlas y utilizarlas.
Regresemos al programa para tirar dados que vimos antes. Este programa tiene dos archivos
fuente. Compilamos ambos archivos y los enlazamos para crear un ejecutable.
Parece que hay un mercado para los programas para tirar dados, y creemos que podemos
usar la función tirarDado () en una variedad de productos que creará nuestra nueva compa
ñía. Tiene sentido colocar la función en una biblioteca para que todos nuestros programas
puedan utilizarla.
Primero necesitamos crear la biblioteca compartida. Compile el módulo con el siguiente
comando:
gcc -fPIC -c lst22-02.cxx
Ahora conviértalo en una biblioteca compartida llamada bibtirar.so. 1.0:
gcc -shared -Wl,-soname.libtirar.so.l -o libtirar.so.1.0 lst22-02.o
Por último, cree un enlace para libtirar. so, para que el programa en ejecución no necesite
mantener un registro de la información de versión en el nombre de la biblioteca compartida:
ln -s libtirar.so.1.0 libtirar.so.1
ln -s libtirar.so.1 libtirar.so
Ahora que tenemos la biblioteca, debemos volver a crear el programa principal para que
se enlace con esa biblioteca en tiempo de ejecución, en lugar de incorporar el código dentro
del ejecutable:
gcc -o dado lst22-0l.cxx -L. -ltirar
La opción - L. le indica al compilador que busque bibliotecas en el directorio actual, y la
opción -ltirar le indica que busque una biblioteca llamada libtirar.so.
Al ejecutar el programa, el sistema operativo cargará dinámicamente la biblioteca correc
ta, pero tiene que saber en dónde buscarla. Si la biblioteca no se encuentra en un lugar
estándar (/usr/lib), puede asignar una variable de entorno para que le indique en dónde
localizar bibliotecas adicionales:
L
| 820 Día 22
C o n s t r u c c ió n o c r e a c ió n
Con seguridad, usted no querrá escribir todos estos comandos de gee cuando cree un
programa. Con los programas pequeños como los que se muestran en esta lección, esto
es sólo un poco molesto. Con programas más grandes en los que se involucren varios
archivos y tal vez varias bibliotecas, esto se vuelve casi imposible.
Linux viene con la utilería make de GNU. make lee de un archivo conocido como make
toda la información que necesita para crear su programa. Esta utilería es tan importante
y popular que se ha especificado como estándar de POSIX. La versión GNU de make se
apega al estándar POSIX.
make
make de GNU busca automáticamente un archivo make llamado GNUmakef ile. Si no lo
encuentra, busca makef ile, y si tampoco lo encuentra busca Makef ile. Éstos son los nom
bres predeterminados. Puede nombrar a su archivo make como usted quiera e indicar
explícitamente a make que lo utilice. Un archivo make es un archivo de texto ordinario
con una sintaxis muy específica que la utilería make puede entender.
make tiene una gran variedad de reglas integradas. Por ejemplo, sabe que los archivos que
terminan con .c son archivos fuente de C, y sabe cómo compilarlos para convertirlos en
archivos objeto (.o). Usted puede redefinir cualquiera de estas reglas si gusta. En el caso
más simple, todo lo que necesita especificar en su archivo make es el nombre que va a
tener su archivo ejecutable, así como los archivos .o que se necesitan para crearlo.
He aquí un archivo make sencillo que crea el programa para tirar dados:
dado: lst22-0i.o lst2 2 -0 2 .o
$(CC) -o $@ lst22-01.o lst22-02.0
Los archivos que se incluyen en el CD-ROM tienen extensión .cxx, por lo que podría recibir
el siguiente mensaje de error del comando make:
make: ***No hay ninguna regla para construir el objetivo 'lst22-0l.o' necesario para
'dado'. Alto.
Para resolver este inconveniente, puede hacer dos cosas. La primera es cambiar la exten
sión de los archivos involucrados de .cxx a .c; esta solución no es recomendable, porque
se buscará código de C y no de C++, lo cual puede generar más errores de compilación.
El e n t o r n o d e p r o g r a m a c ió n d e lin u x 821
La segunda opción es agregar las líneas para crear los objetivos lst22-01.o y lst22-02.o
com o se m uestra a continuación.
dado: Ist 2 2 -0 1 .O lst 2 2 -0 2 .o
S(CC) -o $@ l s t 2 2 -01.0 lst22-02.o
l s t 2 2 - 0 1 .o: l s t 2 2 -01.cxx
$(CC) -c l s t 2 2 -01.cxx
l s t 2 2 -0 2 . o : lst22 -0 2 . cxx
S(CC) -c l s t 2 2 -02.cxx
O bviam ente, si ya creó los archivos .o, no recibirá ningún mensaje de error.
A hora puede crear el program a con un solo comando:
make
cc -c l s t 2 2 -01.cxx
cc -c l s t 2 2 -02.cxx
cc -o dado lst 2 2 -0 1 .o lst22-02.o
Nota: la línea del archivo make que empieza con “dado” se conoce como destino. Define
las “d ep en d en cias" del program a. La siguiente línea es la regla de construcción, make
requiere que el prim er carácter de esa línea sea un tabulador, y no espacios. Si hay espacios
en lugar de un tabulador, el error generado por make es el siguiente:
make
all: dado
dado: $(0BJS)
$(CC) $ (CFLAGS) -o $@ $(0BJS)
l s t 2 2 -0 1 . o : lst22-01.cxx
$(CC) -c l s t 2 2 -01.cxx
lst 2 2 -0 2 .o : l s t 2 2 -0 2 .cxx
$(CC) -c l s t 2 2 -02.cxx
clean:
- $ ( R M ) dado *.o
Este archivo m ake define una regla llamada clean, que se utiliza para elim inar todos los
archivos previam ente compilados (incluso el ejecutable) y empezar con un directorio lim
pio. Esta regla tam bién se utiliza para eliminar los archivos objeto temporales después de
instalar el program a ejecutable y las bibliotecas (si se han definido).
822 D ía 2 2
El lisiado 22.3 m uestra cóm o se vería el archivo m ake si querem os usar bibliotecas com
partidas.
O p c io n e s d e lín e a d e c o m a n d o s d e m ake
make tiene varias opciones útiles de línea de com andos.
Si quiere especificar un archivo m ake alternativo en lugar de uno de los predeterminados
que se m encionaron antes, invoque a m a k e de la siguiente m anera:
make -f n o m b r e a r c h i v o
E sto le indica a m a k e que analice el m a k e f i l e y que reporte qué com andos emitirá para
c re a r el program a, m a k e no ejecutará ningún com ando.
El entorno de programación de linux 823 |
Nota: Cuando usted programa en C++, tiene una extensión de archivo distinta de .c (.cpp,
.cxx, .cc, etcétera), así que deberá especificar las reglas para construir los archivos .o.
Otra característica útil en make es el uso de variables. Observe que definimos una variable
llamada CFLAGS. make pasa automáticamente esta variable a gcc cuando compila su progra
ma. Tal vez quiera cambiar el valor de esta variable una vez sin cambiar el archivo make.
Puede especificar un nuevo valor en la línea de comandos que redefina el valor en el archivo:
make CFLAGS="-g -0"
D e p u r a c ió n
Todo buen entorno de desarrollo tiene que proporcionar algo de capacidad de depuración, y
Linux incluye el depurador GNU llamado gdb. Éste es un excelente depurador de código
fuente con una interfaz de línea de comandos. Su distribución también debe incluir a xxgdb,
una versión del mismo depurador con una interfaz gráfica que se ejecuta en X Windows.
Si no tiene un ejecutable para xxgdb, realmente vale la pena conseguirlo.
gdb
gdb le permite analizar un programa paso a paso, establecer puntos de interrupción y
examinar y modificar variables por su nombre. Puede utilizarlo tanto con programas de
C como de C++.
Para preparar un programa para depurarlo, sólo necesita agregar la opción -g a gcc cuando
cree el programa. Puede hacer esto en la línea de comandos de gcc (si está creando el pro
grama de esta manera). Si está utilizando make, puede colocar esta opción en el archivo
make. De manera alternativa, si está utilizando make, puede hacer esto como se describió
anteriormente, redefiniendo el valor de CFLAGS en la línea de comandos de make:
make CFLAGS=-g
Cuando se haya creado su programa, inicie gdb con el comando
% gdb dado
GNU gdb 5.0
S a l id a Copyright 2000 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you
"»are welcome to change it and/or distribute copies of it under certain
^conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for
^»details.
This GDB was configured as "i386-redhat-linux"...
gdb tiene muchos comandos disponibles. Para verlos, escriba help.
(gdb) help
824 D ía 2 2
El com ando h e l p le m uestra por sí m ism o las clases de com andos que proporciona gdb.
Para encontrar más acerca de los com andos dentro de una clase particular, utilice otra vez
help, seguido del nom bre de la clase de com andos:
(gdb) help breakpoints
awatch — bet a w a t c h p o i n t f or an e x p r e s s i o n
break — Set break p oi nt at s p e c i f i e d line or function
catch — Set c a tc h p o i n t s to ca tc h e vents
cl e a r — Clear breakp o in t at s p e c i f i e d line or function
comma n d s - Set c om ma n d s to be e x e c u t e d w he n a breakpoint is hit
condition — Spe c if y b r e a k p o i n t n u m b e r N to break only if COND is true
d elete — Delete some b r e a k p o i n t s or a u t o - d i s p l a y expressions
di s a b l e -- Disable some b r e a k p o i n t s
e nable — Enable some b re a k p o i n t s
hbreak — Set a hardwa r e a s s i s t e d breakpoint
ignore — Set igno re - co un t of b r e a k p o i n t n u m be r N to COUNT
rbreak — Set a breakp o in t f or all f u n c t i o n s matchi n g REGEXP
rwatch — Set a read w a t c h p o i n t for an e x p r e ss io n
t break — Set a t em po r ar y b r e a k po in t
t catch — Set t em po r ar y c a t c h p o i n t s to cat c h events
th b r e a k -- Set a t em po r ar y h a r d w a r e a s s i s t e d breakpoint
tx b r e a k -- Set t emporary b r e a k p o i n t at p ro c e d u r e exit
w a t c h — Set a w a t c h p o i n t f or an e x p r e s s i o n
x break — Set breakpoint at p r o c e d u r e exit
xxgdb
xxgdb es una interfaz gráfica creada con base en gdb. Asegúrese de que su variable de
entorno DISPLAY esté definida, y escriba:
xxgdb nombre_programa
La figura 22.4 muestra la interfaz gráfica de xxgdb.
F ig u r a 2 2 . 4
lUiiftiatatUaitui 6
xxgdb. ■Includa<ctdto.h>
tntdoPoIMvoidl;
Mint Ininrgr..rjw«nrgvH)
iinnt
t I ls
itar:
Int Dlet6J;
ifp
<ri
an
rt
gf
el'
<Us
2ag1e:
{Xs n\n",ergvtOl);
>exitll);
liter=atoKergvllJI;
■onset!Dio.o.sizeoffDiel>S
print«"»Header:/t»ac^lAiork/Ji*/Dlce/RCS/dlceHain.c,y1.11333212/2008:37:58haiExp*\n*>;
fori1•0:1<liter;!■•>(
>DloIdoRollO-11»;
Readyforexecution
|run11cont11next||step||finish||break||tbreskllaelecel|i4>IIdown(Iprint.||print*||display|lindisplay||shoudisplay||arg*|
|looalo11otoch|foditIjoooroh||interrupt||filolistaitx+pta||yos||noHqult|
XX
G C
NpDg
UySdhomo
bh4.17u0
.l.
t1
h1ft
u BlStOhLULTiCn
Lu
YxNO
soI
tupMd
pn oKa
rMTY.
ti —
Co rig t 1938 Fro . S o ft u nr. F t
eo Gn
N. GIe
no.
1G
wDB 1n
*sft
reech
sa
on
ftw
*aro,clo/voer redlbyrithu Up in
ee
sra
ol
fPIu
tbw
li
dcar
Lic
ce
en
rs
te
a,
inendnd
yi
ot
uio
on
rs
e.
To
ylco
po "shoo
wcop on
yi g*ittm oseetd hesc
tonb ditt*iocno. co
L
H a y tres seccio n es en la pantalla de xxgdb. L a secció n superior muestra el código fuente
p ara el m ód u lo que se está e jecu tan d o en ese m om en to. L a secció n que está en medio
contiene botones que son m étodos ab reviad o s para varios com andos de xxgdb. La sección
in ferior es una ventana de co m an d os. En esta ventana se muestra la salida del programa
y de gdb. Si el program a o gdb requieren de entrada por medio del teclado, mueva el ratón
h acia esta ventana y escriba.
Puede d esp legar el valo r de una v a ria b le o ex p re sió n resaltándola con el ratón y luego
oprim iendo el botón D isp la y (M ostrar). En este ca so se abrirá otra sección que mostrará
la actualización continua del valo r de la variable que h aya seleccionado.
(gdb) b reakl6 20
Breakpoint 1 at 0x8048658: file lst22-01.cxx, line 20.
(gdb) run 5 2 2
Start i n g program: /root/office52/user/work/dia22/dado 5
Control de versiones
Los program as nunca son tan simples como se piensa que serán al principio. Cualquier
buen program a va más allá de su propósito original. Con el tiempo hay cambios, se agre
gan cosas, se solucionan errores y se hacen mejoras.
Los com entarios son una excelente manera de mantener información relacionada con los
cam bios, pero para cualquier trabajo serio se necesita alguna forma de control de versiones.
S uponga que cam bia el programa lst 2 2 - 0 2 . cxx, agregándole varias características. Un
año después, su cliente más importante le llama y le dice que no quiere todas esas caracte
rísticas; que quiere la versión original del año pasado, en la que se había arreglado un error.
Linux viene con RC S (Sistema de Control de Revisiones). RCS es una colección de com an
dos que le perm iten rastrear cambios realizados en archivos, recuperar cualquier versión
anterior y com parar las versiones actuales con las más antiguas.
828 Día 22
RCS
Los principales comandos de la suite RCS se muestran en la tabla 22.3.
RCS mantiene en un depósito el historial de las revisiones de los archivos. Por lo general,
ese depósito es un directorio llamado RCS, que se encuentra en su directorio actual.
En el siguiente ejemplo, iniciamos el historial de RCS de un archivo en nuestro proyecto
de tirar dados:
% mkdir RCS
% ci Makefile
RCS/Makefile,v <-- Makefile
S a l id a enter description, terminated with single '.' or end of file:
NOTE: This is NOT the log message!
» Makefile del programa para tirar dados
» .
initial revisión: 1.1
done
Ahora hemos registrado el Makefile en el depósito de RCS, y RCS ha creado un archivo en el
directorio RCS llamado Makefile, v. A medida que modifiquemos el Makefile y verifiquemos
las versiones más recientes, RCS llevará el registro de esos cambios en su copia Makefile,v.
Registre todos los archivos necesarios para crear el programa de los dados, con los
comandos c i Ist22-01.cxx y c i lst22-02.cxx.
Piense en RCS como si fuera una biblioteca que guarda sus archivos. Puede sacar copias
de sólo lectura con el comando co nombrearchivo. Cuando quiera modificar un archivo,
puede sacar una copia en la que se pueda escribir (bloqueada) con co -1. Puede sacar
El e n t o r n o d e p r o g r a m a c ió n d e lin u x 829
cualquier cantidad de copias de sólo lectura (desbloqueadas) a la vez. Sólo puede sacar
una copia bloqueada a la vez.
Hay varias palabras reservadas de identificación que puede colocar en su archivo y que
son reconocidas por RCS. Estas palabras reservadas empiezan y terminan con $. Yo podría
m odificar nuestro program a Ist22-01 .cxx como se muestra en el listado 22.4:
En t r a d a L is t a d o 2 2 . 4 P r o g r a m a l s t 2 2 - 0 1 .cxx modificado
C uando saco el archivo desbloqueado (sólo lectura), RCS reemplaza la palabra reservada
$ H e a d e r $ con inform ación acerca del nombre y la versión del archivo. Cuando lo saco
bloqueado. RCS no reemplaza el encabezado. Ahora saco una copia desbloqueada, creo el
program a y lo ejecuto:
% dado 10
830 D ía 2 2
D o c u m e n t a c i ó n
H ay bastante docum entación disponible en L inux, y la m ayor parte de ella se incluye con
las distribuciones. Usted tiene acceso a man, i n f o y a los H O W TO s y FAQs de Linux.
C om o la docum entación puede ser extensa, m uchas de las distribuciones dejan la insta
lación del conjunto com pleto com o algo opcional.
P á g i n a s del m a n u a l
Al igual que cualquier sistem a operativo sim ilar a U N IX , Linux incluye el comando man y
las páginas del m anual para todos los com andos.
A lgunas veces encontrará un com ando con una página en más de una sección del manual.
Por lo general, al utilizar man. éste despliega la prim era entrada que encuentra. Si utiliza
man con la opción -a, se m ostrará la página de m anual para el com ando seleccionado en
todas las secciones del m anual que tengan una.
El com ando xman proporciona una interfaz gráfica para el com ando man básico. Escriba
xman -notopbox -bothshown
Puede hacer clic con el botón izquierdo del ratón en el panel Sections (Secciones) para
seleccionar otra sección del m anual. H aga clic con el botón izquierdo del ratón en
cualquier com ando del panel superior para ver la p ágina de m anual correspondiente en el
panel inferior. H aga clic con el botón izquierdo en el botón O ptions (Opciones) y selec
cione la últim a entrada ( q u it) para salir de xman. La figura 22.5 m uestra la interfaz gráfi
ca para la página de m anual.
El e ntorno de programación de linux 831
Figura 2 2 .5
xman.
inf o
La documentación en línea en forma de páginas de manual ha estado en UNIX casi desde su
comienzo. Una contribución más reciente es el sistema inf o. Navegar con inf o le parecerá
natural si está familiarizado con emacs. De no ser así. puede ser un poco difícil de aprender.
Puede invocar a inf o desde la línea de comandos con el comando info. Si ya se encuentra
en emacs, oprima “Esc” y luego escriba -x info. La figura 22.6 muestra a info.
El comando in f o está organizado en nodos, cada uno de los cuales representa un tema
principal. A medida que lee la información en in f o. puede seguir v ínculos a otros temas
o a más información. Hay varios comandos de un solo carácter en inf o. La mejor manera
de aprender a utilizarlo es acceder al tutorial con el comando h (desde la ventana de direc
torio info inicial). Use el comando mpara seguir un elemento del menú. Escriba m gcc para
seguir el vínculo hacia la información sobre gcc. También puede mover el cursor hacia un
elemento del menú y oprimir “Entrar”. En cualquier momento puede escribir d para regresar
al directorio info principal.
Para salir de info, escriba q. Si entró a info por medio de emacs, use la secuencia de salida
normal de emacs: C-c C-h.
H O W TO s y FAQs
Muchos documentos de Linux son contribuciones de usuarios que averiguaron cómo hacer
ciertas cosas y que escribieron sus experiencias. Estas experiencias escritas se conocen
como HOWTOs. También hay un largo historial en Linux de varios grupos y organizaciones
de usuarios que proporcionan ayuda en línea a los usuarios. A menudo, las preguntas y sus
respuestas correspondientes se recolectan en documentos conocidos como FAQs (Preguntas
frecuentes). La mayoría de las distribuciones de Linux incluyen tanto los HOWTOs como
las Preguntas frecuentes. Por lo general, se pueden encontrar en el directorio /usr/doc/-
HOWTO y /usr/doc/FAQ. Su instalación puede colocarlos en cualquier otro lado.
Las Preguntas frecuentes y los HOWTOs más recientes están disponibles en línea en el
sitio Web del Proyecto de documentación de Linux: http: / /www. linuxdoc.org. También
hay un sitio llamado h ttp ://lu c a s.lin u x .o r g .m x , en el que se puede encontrar mucha
información relacionada con Linux en español.
Resumen
La lección de hoy trató sobre el entorno de programación y depuración proporcionado por
Linux. Se incluyeron introducciones rápidas para:
• Editores (vi y emacs)
• Compiladores (gcc y g++)
• Creación de ejecutables con make
• Bibliotecas y enlaces
• Depuración con gdb
• Control de versiones con RCS
• Documentación en línea, incluyendo man e in fo
Éste es un entorno rico en características, equivalente al disponible en la mayoría de
las estaciones de trabajo profesionales. Ofrece todas las herramientas básicas necesarias
para el desarrollo de programas.
El entorno de programación de linux 833
Preguntas y respuestas
P Compilé y construí mi programa de C++ con gcc. Compila bien, pero tengo
muchos errores de enlace.
R gcc enlaza de manera predeterminada con las bibliotecas estándar de C, no con las
bibliotecas requeridas por C++. Puede especificarlas en forma explícita; sin embargo,
es más sencillo utilizar g++ para la fase de enlace del programa que esté creando.
P He creado un programa usando bibliotecas compartidas, y he creado las biblio
tecas. Ahora, cuando ejecuto el programa, obtengo un error que dice: “cannot
open shared object file: No such file or directory”.
R No le ha indicado al cargador en dónde encontrar las bibliotecas. La variable de
entorno LD_LIBRARY_PATH es una lista de rutas separadas por el signo de dos puntos
(:) que indican los directorios en los que se pueden encontrar las bibliotecas com
partidas.
P Cuando escribo make, se produce el mensaje “make: nothing to be done for ...”.
R make intenta buscar todos los objetos y sus archivos fuente correspondientes. Examina
las etiquetas de tiempo en estos archivos. Si el objeto es más reciente que el archivo
fuente, make concluye que no es necesario volver a compilar ese objeto. Si su destino
existe y todos los objetos componentes son más recientes que sus archivos fuente,
make cree que no hay nada por hacer.
P Cuando ejecuto gdb, no puedo ver mis archivos fuente. Cuando ejecuto xxgdb,
la sección superior no muestra ningún archivo fuente, y la sección inferior
muestra el mensaje “No default source file yet”.
R Para que el compilador preserve los símbolos y los archivos fuente para que el
depurador pueda leerlos, debe compilar y enlazar todos los módulos de su programa
con la opción -g.
P He creado un directorio RCS y registrado todos mis archivos fuente y archivos
de encabezado. Ahora han desaparecido.
R Cuando registra con el comando ci, RCS actualiza un archivo de historial en el
directorio RCS. Es probable que sus archivos no hayan desaparecido; simplemente
aún no ha sacado las versiones en las que va a trabajar. Use el comando co para sacar
copias bloqueadas o desbloqueadas.
834 Día 22
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del
material tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios“, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Qué es POSIX?
2. ¿Qué es X Windows?
3. ¿Cuáles son los dos principales editores de texto disponibles en Linux?
4. Cite una de las principales distinciones entre vi y emacs de GNU
5. Cite una de las ventajas de las bibliotecas compartidas en comparación con las biblio
tecas estáticas, y cite una de las ventajas de las bibliotecas estáticas en comparación
con las bibliotecas compartidas.
6. ¿Qué utilería se usa para compilar y crear programas? ¿Cuál es su archivo de entrada
predeterminado?
Ejercicios
1. Cree una función adicional para el programa de los dados que se mostró en la lección
de hoy. La función debe tomar como entrada un apuntador al arreglo Dado. Para cada
cara del dado, esta función debe imprimir el porcentaje de veces que salió esa cara.
La función debe estar en un archivo separado de main() y de tirarDado().
2. Modifique el archivo make para enlazar la nueva función.
3. Analice el programa paso a paso con gdb.
S e m a n a 4
Programación shell
Aunque ha estado estudiando principalmente el lenguaje C++, también ha apren
dido acerca de los aspectos específicos y las implicaciones de este lenguaje en
el entorno Linux. Continuando con este estilo, la lección de hoy trata sobre los
intérpretes de comandos de Linux y su programación, conocida como progra
mación shell.
Aunque el intérprete de comandos es independiente del lenguaje de programación
que se utilice, Linux permite crear programas utilizando las características propias
del intérprete. Como se habrá imaginado, estos programas son interpretados y redu
cen el desempeño del sistema; pero la facilidad con que se pueden crear y mantener
supera por mucho las expectativas de desarrollo. Por ello, es importante que entien
da como trabaja el intérprete de comandos y cómo utilizarlo para poder lograr algo
útil con el menor esfuerzo. En el nivel más básico, debe interactuar con el intérpre
te para lograr cualquier cosa; si comprende el mecanismo básico, descubrirá que es
una herramienta poderosa que puede ayudarle en su trabajo.
• Qué es el shell
• Qué shells están disponibles
• Principios de los shells (redirección y procesamiento en segundo plano)
• Construcción de comandos del shell (sustitución y creación de alias)
• Variables de entorno
• Secuencias de comandos de los shells
Qué es un shell
Muchas personas piensan que el indicador que se ve al estar sentado frente a una compu
tadora es el sistema operativo, o que lo identifica. Éste no es el caso.
El sistema operativo es el software que dirige a la computadora, habla con el hardware,
carga y ejecuta programas, etc. El sistema operativo es algo que, básicamente, nunca se ve.
Cuando se ve el indicador de la computadora y se escriben comandos para ejecutar, con
lo que estamos tratando es con el shell. En el pasado, el shell se conocía como intérprete
de línea de comandos. De hecho, en el mundo de DOS, este software se conocía común
mente como CLI, y se llamaba COMMAND.COM.
Una característica interesante de UNIX y sus derivados, incluyendo a Linux, es que los
shells son completamente independientes. El usuario tiene la libertad de elegir uno de
vanos shells disponibles. Como verá más adelante en esta lección, el usuario puede elegir
interactuar con un shell pero escribir secuencias de comandos en otro.
Redirección de E/S
Cuando se ejecutan programas en Linux, se abren automáticamente tres archivos de E/S
para ellos. Estos archivos son la entrada estándar, la salida estándar y el error estándar.
Tal vez le parezca confuso, pero todos los sistemas UNIX están basados en el manejo de
archivos. Si usted quiere enviar información a través de un módem, debe enviarlos men
sajes a su archivo asociado que comúnmente es el archivo /dev/modem.
De manera predeterminada, la entrada estándar está conectada al teclado, y la salida y el
error estándar se conectan a la pantalla. Como el shell inicia el programa que usted espe
cifique, puede reasignar estas asignaciones predeterminadas en un proceso conocido como
redirección de la entrada, redirección de la salida, o de manera más genérica, redirección
de E/S, antes de que el programa inicie.
Suponga que quiere crear una lista de archivos en el directorio /usr/inelude que incluya
otros archivos. Una forma de hacer esto es la siguiente:
grep -1 "#include" /usr/include/*.h > Listalnclude
El signo “* ’ es un carácter “comodín”. Hablaremos con mayor detalle sobre los comodines
más adelante en esta lección. Por ahora, sólo asuma que grep comprobará todos los archi
vos del directorio /usr/include cuyos nombres terminen con “.h”. Imprimirá el nombre
de cada archivo en el que encuentre la cadena #include.
El > es el carácter de redirección de salida. Ocasiona que el shell redireccione la salida
del comando grep a un archivo llamado Listalnclude. El mismo comando grep no está
consciente de la redirección; éste es trabajo del intérprete de comandos. Los nombres que
están en el archivo Listalnclude se verán así:
/usr/inelude/FlexLexer.h
/usr/include/Fnlib.h
/usr/inelude/Fnlib_types.h
/usr/include/Imlib.h
/usr/include/Imlib_private.h
/usr/include/Imlib_types.h
/usr/include/QwCluster.h
/usr/include/QwPublicList.h
/usr/include/QwSpriteField.h
El comando sed opera sobre los datos de la entrada estándar. La entrada estándar se redirige
por medio del carácter “<” de redirección de entrada. En lugar de leer del teclado, sed leerá
del archivo Listalnclude. Como con la redirección de la salida, el comando sed no sabe
que su entrada estándar ha sido redirigida (a menos que verifique esto explícitamente). La
salida del comando sed se redirige al archivo Listalncludes.
Programación shell 839
Observe que el carácter “>” no redirige la salida de error estándar, sólo la salida estándar.
Utilice >& para redirigir el error estándar de la siguiente manera:
sed 's#A/usr/include/#+ #' < Listalnclude >& ErrorSed > SalidaSed
El siguiente comando redirige tanto el error estándar como la salida estándar al archivo
llamado Salida:
sed ’s#A/usr/include/#+ #' < Listalnclude >& Salida
Esto es algo confuso y se utiliza muy poco. Es más común que se redirija sólo la salida
estándar, y que se permita que el error estándar vaya a la pantalla.
Tuberías
Una forma relacionada de redirección se conoce como tubería.
Tal vez quiera una lista de archivos que contengan tfinclude, y quiera que la lista esté
ordenada.
Puede usar la redirección de la siguiente manera:
grep -1 “#include" /usr/include/*.h > Listalnclude
sort Listalnclude > ListaOrdenada
Evidentemente, debe haber una mejor manera que utilizar dos comandos y un archivo
temporal. A continuación se muestra algo mejor:
grep -1 "tfinclude” /usr/include/*.h ¡ sort > ListaOrdenada
Variables
Con frecuencia, un programa necesita cierta información específica para poder funcionar
correctamente. Por ejemplo, los editores de pantalla vi y emacs necesitan saber qué tipo
de terminal se está utilizando.
Esta información se podría proporcionar como opción de línea de comandos; sin embargo,
sería innecesariamente tedioso requerir que usted agregara esta información cada vez que
iniciara el editor. Además, hay más información que no cambia de una invocación a otra,
además del tipo de terminal que el editor requiere.
Los shells se encargan de este problema con las variables de entorno. Una variable de
entorno es simplemente un par nombre/valor. El shell mantiene una lista de estas variables
y las hace disponibles para cualquier programa.
En realidad, existen dos tipos de variables: las variables normales de shell y las variables
de entorno. La distinción entre estos dos tipos es delicada. Las variables de entorno también
se conocen como variables “globales”, mientras que las variables de shell también se cono
cen como variables “locales”. Las variables globales se pasan al inicio de un nuevo coman
do o shell. Para establecer una variable de shell en bash, utilice esta sintaxis:
N0MBRE= valor
• PS1— El indicador desplegado por el shell para indicar al usuario que está listo para
recibir entrada.
• HOME— El directorio personal del usuario.
Este comando crea una lista de todos los archivos cuyos nombres terminen con “.c”. Ordena
esta lista y coloca la salida en un archivo llamado ListaOrdenada. El shell imprime “[1]
142” y regresa inmediatamente, listo para recibir otro comando. La salida indica que se está
ejecutando una tarea en segundo plano, y que el PID (identificador del proceso) es 142.
Puede ejecutar más de un proceso en segundo plano. En este caso, el proceso es el número 1
en la cola de procesamiento en segundo plano.
Si ejecuta un programa desde la línea de comandos y no utiliza el carácter &, el shell espera
que el proceso termine antes de pedirle un nuevo comando. Se dice que este proceso se está
ejecutando en primer plano.
Si ejecuta un proceso en segundo plano, y luego decide que mejor quiere esperar a que
termine, lo puede “traer al primer plano” con el comando fg.
Si está ejecutando un proceso en primer plano y quiere suspenderlo sin eliminarlo per
manentemente, oprima “Ctrl+Z” (la tecla Control y “Z” al mismo tiempo). Ahora el proceso
está suspendido. Puede hacer que el proceso siga ejecutándose en primer plano con f g, o
en segundo plano con bg.
842 Día 23
Por últim o, si quiere elim inar perm anentem ente el proceso, puede utilizar el comando k ill.
P uede e lim in a rlo usando su núm ero de P II) ( k i l l 142). o por su lugar en la cola de
procesam iento en segundo plano ( k i l l ).
Para averiguar qué procesos se están ejecutando en segundo plano, utilice el comando jobs.
La lig u ra 23.1 m uestra el control de p ro ceso s. Id p rim er com ando 'Tind" se ejecuta en
seg u n d o plano, y el com ando “jo b s ” indica que se está e jecu tan d o ahí. El siguiente co
m ando “h n d " se ejecuta en prim er plano y luego se suspende con ”C'trl+Z". De nuevo, el
com ando "jobs” m uestra esto. A continuación, el com ando "lin d ” suspendido se reanuda en
segundo plano, y esto se verifica con el co m an d o "jo b s” . El com ando "fg" se utiliza para
que el prim er proceso que se ejecu ta en segundo plano pase al prim er plano, y luego se
suspende con “C trl+ Z ” .
Figura 23.1
Procesamiento en
segundo plano, sus
pensión y control
de procesos.
C
C h
l]ajIlO
s
CI2v
C Je>
C
C2h]alOs
Completación de comandos
bash incluye m uchos m étodos abreviados para reducir la cantidad de escritura que usted
necesite realizar. Esto se logra m ediante la com pletación y la sustitución de comandos. La
siguiente sección trata sobre la sustitución y el m ecanism o de comodines.
La c o m p le ta c ió n d e c o m a n d o s se refiere a la habilidad de bash para “adivinar” el comando
o nom bre de archivo que usted está escribiendo. Escriba algunos de los primeros caracteres
de un com ando y, en lugar de escribir el nom bre com pleto del com ando, oprima “Tab”. Si
bash pu ed e id en tifica r ese com ando en form a ú n ica, lo c o m p letará por usted. En caso
contrario, sonará un bip. O prim a “Tab” una segunda vez y se mostrarán los comandos más
aproxim ados a lo que usted está escribiendo.
Programación shell 843
Sustitución de comandos
El shell incluye otros mecanismos para ahorrarse la escritura. Estos piecanismos incluyen
cadenas de sustitución. Se permiten varios tipos de sustituciones.
La “a” y la “z” se combinan en el siguiente orden con las cadenas que aparecen entre
llaves: primero con “b”, luego con “c”, luego con “de” y al último con “fgh”.
Puede especificar rangos de caracteres con corchetes:
bash# ls a[b-h]z
abz acz adz aez afz agz ahz
bash#
Este ejemplo ejecutará el comando find para localizar todos los archivos make que estén
en el árbol de directorio /usr/src. La lista de archivos se presentará en la línea de coman
dos a ls, el cual mostrará las entradas en el directorio de estos archivos.
Para invocar cualquier comando anterior, escriba un signo de admiración y el número del
comando. Para repetir el comando cd /tmp de la secuencia anterior, escriba 12.
Puede repetir el último comando con 1!, los dos últimos comandos con ! -2, y así
sucesivamente.
También puede editar una línea de comandos anterior antes de repetirla. Suponga que
escribió el comando ls -1 /TMp. Para corregir este comando y repetirlo, podría escribir
''TMp/stmp'\ bash da por hecho que usted quiere editar el comando anterior y procede con
la sustitución, de ser posible. También hay soporte para la edición estilo emacs. Oprima
“Ctrl+P” y se mostrará el comando anterior. Utilice las teclas de dirección o “Ctrl+B” (para
moverse un carácter hacia atrás) y “Ctrl+F” (para moverse un carácter hacia adelante) y
corrija la línea de comandos. Oprima “Entrar” para ejecutar el comando editado.
Variables
Ya hemos hablado sobre las variables de los shells. Cuando se está ejecutando una secuen
cia de comandos, ya están definidas algunas variables útiles:
$$ El identificador del proceso que está ejecutando el programa de shell
$0 El nombre de la secuencia de comandos
• $1 hasta $9— Los primeros nueve argumentos de línea de comandos que se pasan a
la secuencia de comandos
• — El número de parámetros de línea de comandos que se pasan a la secuencia de
comandos
Programación shell 847
Estructuras de control
bash soporta instrucciones de control que son parecidas a muchas de las que hay en los
lenguajes C y C++, aunque la sintaxis es diferente. Entre estas instrucciones se incluyen
i f -th en -else, for, case y while.
La secuencia de comandos del listado 23.1 proporciona una simple muestra de las princi
pales instrucciones de control disponibles en una secuencia de comandos de bash.
L is t a d o 2 3 .1 c o n t in u a c ió n
bash# touch a b
S alida bash# is -F
a b lst23-01.sh*
bash# ./Ist23-01.sh a b el zorro café rápido salta
./Ist23-01.sh : Parámetro 2 : b
./Ist23-01.sh : Parámetro 1 : a
ejemplo de case
case a
case b
default case el
default case zorro
default case café
default case rápido
default case salta
ejemplo de while
1
2
3
4
5
6
7
ejemplo de until
argumento 1
Programación shell 849
argumento 2
argumento 3
argumento 4
argumento 5
argumento 6
argumento 7
bash#
Estos archivos de inicio se utilizan para establecer la ruta, el indicador y otras variables
de entorno que el usuario necesite establecer al momento de iniciar cada sesión. Es común
ejecutar comandos y aplicaciones desde estos archivos. Por ejemplo, si el usuario quiere
que se abra el cliente de correo electrónico cada vez que inicia una sesión, sólo debe
agregar el nombre del programa al final del archivo . b a sh p ro f ile o .profile. Otro
ejemplo común es eliminar todos los archivos core de su directorio de trabajo cada que ter
mina una sesión. Para ello sólo escriba el comando
find $H0ME -ñame core -type f -exec rm -rf {} \;
al final del archivo .bash_logout o .logout. Una desventaja es que los comandos
que agregue a . bash_prof ile se ejecutarán cada vez que abra una terminal (aunque ya
esté en sesión), así que no abuse de estas características.
Resumen
Esta lección ha sido una descripción de los shells en general, y de bash en particular. La
lección cubrió los siguientes aspectos:
• Redirección
• Procesamiento en segundo plano
• Historial
Edición de línea de comandos, comodines y sustitución
También hablamos brevemente sobre las secuencias de comandos de los shells.
El shell es un programa que permite que el usuario interactúe con el sistema operativo.
Proporciona características que facilitan esta interacción (como el historial, la completación
de comandos y el control de procesos), así como muchas de las características de un
lenguaje de programación (incluyendo variables, sustitución de variables e instrucciones
de control). Existen varios shells disponibles para Linux, aunque bash es tal vez el que
más se utiliza. El manual en línea proporciona una excelente referencia para la sintaxis y
las características del shell.
Preguntas y respuestas
P Escribo un comando común de Linux pero recibo el mensaje de error
“command not found”.
R Éste es un problema común. Tal vez haya escrito mal el comando, o tal vez no exista.
Lo más probable es que haya un error en su ruta de acceso, bash (además de otros
shells) sólo ejecutará un archivo si puede encontrarlo en la ruta de ejecución. Esta
ruta se establece por medio de la variable de entorno p a t h . Puede determinar cuál
Programación shell 851
es esta ruta con el comando echo ${PATH}. Con los comandos man o f ind puede
encontrar el directorio en el que se encuentra el comando faltante. Si ese directorio
no se encuentra en la lista de la ruta, agréguelo con export PATH="${PATH> :nuevo_
directorio".
P He creado una secuencia de comandos de shell, establecí su permiso de ejecución
y escribí su nombre. Recibo el error “command not found”.
R Compruebe que el se encuentre en su variable de entorno PATH. El significa
“el directorio actual".
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del mate
rial tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate
de responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice
D, “Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respues
tas antes de pasar al siguiente día.
Cuestionario
1. ¿Qué es un shell?
2. ¿Cuál es la sintaxis general de una línea de comandos de shell?
3. ¿Cuáles son los tres archivos de E/S disponibles para los programas?
4. ¿Cuáles son las 3 formas de redirección de E/S, y qué caracteres se utilizan para
representarlos en la línea de comandos?
5. ¿Qué son las variables de entorno, “locales” o “globales”? ¿Y las variables de shell?
6. ¿Cuál variable de entorno de bash establece la ruta de búsqueda de comandos?
¿Cuál establece el indicador de comandos?
7. Nombre 2 caracteres de sustitución (comodines) de la línea de comandos.
8. ¿Qué necesita haber en un archivo de secuencia de comandos de shell para que el
shell sepa a cuál intérprete debe enviar la secuencia de comandos?
Ejercicios
1. Escriba una secuencia de comandos de bash para imprimir todos los argumentos de
línea de comandos, además del número total de argumentos que reciba.
S em a n a 4
Programación de sistemas
La lección de hoy trata sobre la programación del sistema Linux usando el len
guaje de programación C++. La programación del sistema Linux es el proceso
de escribir programas usando las llamadas al sistema proporcionadas por el sistema
operativo Linux. Las llamadas al sistema son la interfaz de programación primaria
que interactúa con los diversos componentes del sistema operativo. La progra
mación de sistemas es un tema muy amplio. La lección de hoy se enfoca en el
desarrollo de programas usando procesos y subprocesos.
Procesos
Un proceso es un programa en ejecución. Un programa es una imagen binaria en
disco o en cualquier otro medio de almacenamiento masivo, por ejemplo, ps. ps
es una utilería de Linux que despliega el estado del proceso. Cuando se ejecuta un
programa, se lee del área de almacenamiento en el dispositivo periférico, que es
en donde reside, se envía a la memoria y luego se ejecuta.
Usando ps como ejemplo, cuando un usuario escribe ps en el indicador de coman
dos del sistema, el programa ps se lee del dispositivo de almacenamiento masivo,
se envía a la memoria y se ejecuta.
Cada proceso de Linux consta de varios segmentos de programa, recursos del
sistema e información del estado de ejecución. Los segmentos son la pila, el heap,
los datos y el texto. La organización de los segmentos que se encuentran en la
1854 Día 24
Fig u r a 24.1
Diagrama de un pila
proceso de Linux.
heap
datos
texto
En Linux, cuando se crea un proceso nuevo, los segmentos de datos del proceso padre no
se copian por medio de f ork( ). Inicialmente, los permisos de acceso a los segmentos del
proceso padre se establecen como de sólo lectura, y los comparten los procesos padre e hijo.
Cuando un proceso hijo accede al segmento del proceso padre, ese segmento se copia en
el entorno del proceso hijo. El proceso de retrasar la copia de los segmentos del proceso
padre hasta que el proceso hijo acceda a ellos se conoce como Copiar en la escritura (Copy
on Write). Copiar en la escritura optimiza la llamada a f ork () minimizando la cantidad
de información copiada del entorno del proceso padre durante dicha llamada.
Cuando se hace una llamada a f or k( ), ésta regresa dos veces. Cuando termina la llama
da a f ork () del proceso padre, ésta regresa el ID del proceso hijo, y cuando termina
la llamada a f o r k( ) del proceso hijo, regresa un 0. Tanto en el padre como en el hijo, la
ejecución del programa continúa justo después de que f o r k( ) termina. El proceso es un
identificador único en el sistema; a cada proceso se le asigna un ID de proceso.
Programación de sistemas 855
El listado 24.1 muestra un ejemplo de una interfaz que crea un proceso y puede determinar
si es el proceso padre o el hijo.
En los listados de este capítulo se utiliza el nombre del archivo como apare
ce en el C D -RO M que viene en el libro. Por convención, en la programación
de sistemas se utilizan nombres específicos para bibliotecas, archivos de encabe
zado y funciones, y aquí se respeta esa convención. Muchos listados tienen un
nom bre entre paréntesis al final de la primera línea; usted puede guardar ese
listado con dicho nombre y después hacer las llamadas a estos archivos con la
instrucción #include "archivo.h" que aparece en los listados subsecuentes.
C om o ejemplo, vea la línea 1 del listado 24.2; aparece el nombre processl.h
entre paréntesis. Guarde ese listado con este nombre y después cambíe la
línea 3 del listado 24.3 para que aparezca la instrucción #¡nclude
"p roce ssl.h ", en lugar de #¡ndude "Ist24-02.h". 2 4
Cuando se crea un proceso hijo, nunca existe garantía sobre cuál proceso, padre o hijo,
se ejecutará primero. En el ejemplo anterior, el proceso padre espera, por medio de la
llamada a s l e e p ( ), y deja que el proceso hijo se ejecute primero. Este método es insufi
ciente para aplicaciones reales. Hay una condición en la que no es posible determinar cuál
proceso se ejecutará a continuación, la cual se conoce como condición de carrera.
Un proceso utiliza la llamada de sistema conocida como exi t () para terminar. exit()
toma un parámetro, el código de estado, y regresa ese valor al proceso padre.
Control de procesos
En Linux se utilizan varias llamadas de sistema para el control de procesos. Esta sección se
enfoca en las rutinas básicas para el control de procesos.
exec()
La familia e xec( ) de llamadas de sistema reemplaza la imagen actual del proceso, o pro
grama, con la imagen especificada en el argumento para e xec ( ). Recuerde desde la defini
ción de un proceso que éste es un programa en ejecución. f o r k( ) crea un proceso nuevo
que es una copia del proceso actual, proporcionando efectivamente dos instancias en eje
cución del mismo programa. e x e c ( ) le proporciona la capacidad para ejecutar un progra
ma diferente en un proceso que ya se encuentre en ejecución.
Un proceso hijo utiliza la familia e x e c ( ) de funciones para ejecutar otro programa. Una
vez que se crea el proceso hijo, éste se cubre a sí mismo con otro programa y luego con
tinúa su ejecución.
w a i t ()
La llamada de sistema conocida como wait () ocasiona que el proceso padre se suspenda
a sí mismo hasta que termine el proceso hijo o hasta que reciba una señal. Un uso típico
de wait () sería que el padre llamara a wait () inmediatamente después de una llamada a
f ork () para esperar a que se complete el proceso hijo.
En el listado 24.2 se extiende la clase Process definida en el listado 24.1 con dos nuevas
funciones miembro que implementan a exec () y a wait ( ) .
Programación de sistemas 857
Listado 24.2 Clase Process con funciones miembro adicionales para control
Entrada de procesos
1: // Listado 24.2 Clase Process (processi.h)
2:
3: //include <wait.h>
4: //include <unistd.h>
5:
6: //ifndef C_PR0CESS1_H
7: //define C_PR0CESS1_H
8:
9: class Process
10: {
11 : public :
12: Processo;
13: -Pro ce sso;
14: void Create();
15: int WaitForChild() ;
16: int RunProgram(char * program); 2 4
17: bool isParent()
18: { return (pid != 0); }
19: bool isChild()
20: { return (pid == 0); }
21 : private : ■1- - ' ?
22: Process & operator= (const Process &) ; // no permitir la copia
23: int pid;
24: int wait_status;
25: int exec_status;
26: };
27:
28: #endif
Las funciones miembro agregadas permiten que un proceso padre espere a un pro
A nálisis
ceso hijo mediante la llamada a la función miembro Process: :WaitForChild () ,
en la línea 15. El proceso hijo puede cubrirse a sí mismo con la imagen de otro programa
usando la función miembro Process: :RunProgram() que se muestra en el listado 24.3.
L is t a d o 2 4 .3 c o n t in u a c ió n
14: p . RunProgram(program);
15: return 0;
16: }
A nálisis En el listado 24.3. la línea l() crea un proceso hijo. La línea l I determina si el pro
ceso que se encuentra actualmente en ejecución es el padre o el hijo. Si el proceso
es el padre, espera; de no ser así. se ejecuta el comando ls y el programa termina.
p t r a c e ()
p tr a c e ( ) es una llamada de sistema que permite que un proceso padre controle a un pro
ceso hijo que esté en ejecución, leyendo o escribiendo en la memoria del proceso hijo,
p tra c e () se utiliza principalmente para depurar un proceso hijo. La interfaz principal primi
tiva de p tr a c e ( ) son las señales.
Una señal es una interrupción de software. Por lo general, las señales se usan para terminar
un proceso, detener la ejecución del proceso o invocar alguna otra acción.
Existen dos tipos de señales, síncronas y asincronas. Las señales síncronas se producen por
la ejecución de una instrucción que ocasiona un problema, por ejemplo, desreferenciar un
apuntador no inicializado. Una señal asincrona se entrega a un proceso sin importarla
instrucción que esté en ejecución en ese momento.
gdb es un ejemplo de un proceso que usa p tra c e () para controlar un proceso hijo para su
depuración. En este caso, gdb es el proceso padre, y el programa que se está depurando es
el proceso hijo. El proceso hijo se ejecuta hasta que ocurra una señal. Cuando esto pasa, el
control se regresa a gdb. Éste bloquea al hijo por medio de la llamada de sistema wait().
Cuando gdb retoma el control, realiza la operación en el hijo por medio de ptrace().
El uso de p trace () ha sido sustituido casi totalmente por el sistema de archivos /proc.
D ebe RD® B S D E
D EB E ejecutar los procesos en tiem po real N O DEBE olvidar que necesita comprender
com o superusuario. m uy bien las estructuras de datos del kernel
al leer acerca del sistema de archivos /proc.
Programación de sistemas 861
Subprocesos
Un subproceso se define como una secuencia de instrucciones ejecutadas dentro del contex
to de un proceso. Los subprocesos permiten que un proceso realice múltiples operaciones en
paralelo. Esto se logra cuando una aplicación se diseña para utilizar subprocesos múltiples.
Los subprocesos reducen la sobrecarga al compartir segmentos fundamentales de un proce
so. Recuerde que el entorno de un proceso consiste en la pila, los datos y el código.
La biblioteca de subprocesos POSIX, conocida como LinuxThreads, es una implementa-
ción del paquete de subprocesos POSIX 1003.1c para Linux. POSIX 1003.1c es una API
para programación de subprocesos múltiples estandarizada por el IEEE como parte de los
estándares POSIX. La mayoría de los fabricantes de UNIX ha patrocinado el estándar
POSIX 1003. le. Los subprocesos POSIX se conocen comúnmente como pthreads. En la
lección de hoy utilizaremos los términos pthreads y LinuxThreads como uno solo. Linux
Threads se ejecuta en cualquier sistema Linux que cuente con el núcleo 2.0.0 o más recien
te, y con la biblioteca glibe 2.
S u b p ro ce sam ie n to sim p le
El subprocesam iento sim ple se define como un solo contexto de ejecución. Cuando un
proceso se crea con f or k( ). tiene un solo contexto de ejecución.
S u b p ro ce sam ie n to m ú ltip le
El subprocesamiento m últiple se define como contextos múltiples de ejecución. El subpro
cesamiento múltiple separa un proceso en muchos contextos de ejecución de subproce
sos, por consecuencia permite que se distribuya la ejecución del proceso entre muchos
subprocesos.
La reentrando es una característica de la programación de subprocesos múltiples en donde
varios contextos de ejecución pueden tener acceso a los datos o recursos compartidos, y
se garantiza que se mantiene la integridad de los recursos compartidos. Cuando varios
subprocesos comparten recursos, estos recursos deben ser protegidos mediante primitivas
de sincronización de subprocesos. La sincronización se describe en la siguiente sección.
Entrada L is t a d o 2 4 . 4 O b je to T h re a d
18 pthread a t t r t a ttr;
19 vo id * fn;
20 vo id * fn args;
21 bool i n i t ;
22 };
23
24 //endif
El código del listado 24.4 presenta un objeto Thread que encapsula llamadas de
A n á l is is
LinuxThreads básicas para crear y destruir un subproceso. El constructor de la línea
1 I tom a com o argum entos un apuntador a una función y un argumento que se va a pasar
a la función. Un Thread se crea mediante la llamada a la función miembro T hread: : -
C re a te . C re a te llama a p th read _ create usando los parámetros que se pasan al constructor
de Thread. La función miembro Thread: ¡Destroy llama a pthread_exit para terminar el
subproceso. El listado 24.5 muestra un programa de ejemplo que utiliza la clase Thread.
L is t a d o 2 4 . 5 Ejemplo de productor/consumidor
¿Puede ver el desastre potencial en este ejemplo? Tanto read_thread como write_thread
tienen acceso a los datos globales en cualquier momento. Éste es un problema potencial,
ya que la función read_thread podría empezar a ejecutarse y quedar suspendida, la función
write_thread podría empezar a ejecutarse, m odificar los datos globales y luego quedar
suspendida, y entonces read_thread continuaría su ejecución con un valor distinto para
los datos globales. Éste es un ejemplo de código que NO es reentrante.
Administración
Los conceptos de administración de subprocesos son exactamente los mismos que los con
ceptos de administración de procesos. De hecho, en Linux se utiliza el mismo programa
dor de tareas para los subprocesos y los procesos. Para obtener más detalles, vea la sec
ción “Algoritmos de administración de procesos” que se trató anteriormente en esta lección.
Sincronización
La sincronización de subprocesos es una técnica para garantizar la integridad de los
recursos compartidos entre subprocesos. LinuxThreads proporciona las siguientes primiti
vas de sincronización: los mutex, los semáforos, las variables de condición y la unión.
Los mutex
Un m utex es una prim itiva de sincronización que se utiliza para garantizar el acceso
exclusivo mutuo a un recurso, por lo general a datos.
Un mutex se encuentra en uno de dos estados: bloqueado o desbloqueado. Sólo un subpro
ceso puede bloquear un mutex en un momento dado. Si otro subproceso intenta adquirir
un mutex que ya esté ocupado por otro subproceso, el bloqueo falla.
El paradigm a bloquear/desbloquear se puede im plem entar mediante primitivas de sincro
nización múltiples; por lo tanto, definiremos una clase base virtual para esta interfaz. El
listado 24.6 define una clase que representa los bloqueos síncronos.
P r o g r a m a c ió n d e sistem as 865
La clase base virtual SyncLock define la interfaz para una primitiva de sincroniza
A n á l is is
ción que bloquea un recurso para acceso exclusivo mutuo y luego desbloquea ese
recurso, perm itiendo que otro subproceso obtenga el bloqueo.
L is t a d o 2 4 .7 continuación
Esta clase mutex tiene cuatro funciones miembro principales. Mutex: :Create(),
A n á l is is
Mutex::Destroy(), Mutex::Acquire() y Mutex::Release(). Después de que se
crea y se inicializa Mutex, un subproceso la adquiere para obtener acceso exclusivo a un
recurso y libera la Mutex al terminar.
Usando el listado 24.8, volvamos a analizar el ejem plo productor/consumidor del listado
24.5 de la sección anterior, usando los mutex.
E n trada L is t a d o 2 4 .8 E je m p lo d e p r o d u c t o r / c o n s u m i d o r u s a n d o a M u te x
22: {
23: Mutex * apMutex = static_cast< Mutex * >(param);
24:
25: w h i l e (1)
26: {
27: a p M u t e x - > A c q u i r e ();
28: cout << "escribir: " « data++ « endl;
29: a p M u t e x - > R e l e a s e ();
30: }
31: }
32:
33: int m a i n ( i n t argc, char** argv)
34: {
35: Mut ex lock;
36: Thread threadl ((v o i d * )&write_thread, &lock);
37: Thread thread2( (void*)&read_thread, &lock);
38: 2 4
39: l o c k . C r e a t e ( );
40: t h r e a d l . C r e a t e ( );
41: t h r e a d 2 .C re a t e ();
42: for (int i = 0; i < 100000; i++)
43: ;
44: l o c k .D e s t r o y ();
45: t h r e a d l .D e s t r o y ();
46: t h r e a d 2 . D e s t r o y ( );
47: return 0;
48: }
El lista d o 24.8 m uestra una vez más el uso de los dos subprocesos r e a d _ t h r e a d
A n á l is is
y w r i t e _ t h r e a d . Sin em bargo, los subprocesos reciben un parám etro, un m utex
q u e p erm ite q u e ca d a subproceso tenga acceso en forma segura a los datos globales. A hora,
ta n to r e a d _ t h r e a d co m o w r i t e _ t h r e a d intentan adquirir el mutex com partido por m edio
d e la fu n c ió n m iem b ro M utex: : A c q u i r e( ). Cuando se completa el acceso de los subpro
c e s o s , el m u tex se libera usando la función miembro Mutex: :Release().
S e m á fo ro s
U n sem á fo ro es un contador para un recurso compartido entre subprocesos. Los sem áforos
son co n tad o res a los que se debe tener acceso en forma atómica. La palabra atómica signifi
c a q u e c u a n d o un su b proceso está modificando el valor del semáforo, otro subproceso no
p u e d e m o d ific a r sim ultáneam ente ese valor.
868 Día 24
binarios y los
C o n ce p tu alm e n te , existen dos tipos b á sic o s de se m á fo ro s: los semáforos
sem áforos de conteo. Un sem áforo binario nunca tom a valores distintos de cero o de uno,
y es ló g icam e n te igual que un m u tex. U n se m á fo ro de co n teo puede tomar valores no
n egativo s arbitrarios.
S e podría utilizar un sem áforo binario para im plem en tar un objeto SyncLock definido en
la secció n anterior. L a interfaz del sem áfo ro se define en el archivo de encabezado sema
p h o r e , h. A continuación se muestra un breve listado de la interfaz.
extern int sem_init (sem_t *__sem, int __pshared, unsigned int __value)
_THROW;
Variables de condición
U na variable de condición es una prim itiva de sincronización que permite que un subproce
so espere a que ocurra algún evento mientras permite que otro subproceso notifique a este
su bproceso cuando ocurra una condición.
La clase virtual pura Event definida en el listado 24.10 contiene una interfaz para
A n á l is is
un objeto que. o espera un evento, o lo señala.
La unión
La unión es una primitiva de sincronización que ocasiona que un subproceso espere que
otro subproceso se complete. La unión no sería considerada como primitiva de sincroni
zación, ya que su propósito es esperar que otro subproceso termine y no que se ejecute
simultáneamente.
Punto muerto
Cuando está programando en un entorno de subprocesamiento múltiple, varios subprocesos
compiten por los recursos limitados. Si un subproceso pide un recurso que no está dispo
nible, ese subproceso entra en un estado de espera.
Un subproceso podría estar esperando en Corma indefinida, ya que otro subproceso en
espera está ocupando dicho recurso; esto se conoce como punto muerto. El punto muerto
se define como un subproceso bloqueado indefinidamente que compite con otro subproceso
por el mismo conjunto limitado de recursos.
Deben existir cuatro condiciones para caracterizar una condición de punto muerto;
1. Exclusión mutua — Por lo menos un bloqueo no es compartible.
2. O cupar y esperar — Un subproceso está ocupando un recurso y esperando por un
recurso que otro subproceso está ocupando.
3. N o preferencia — Un recurso ocupado sólo puede ser liberado por el subproceso
que lo posee.
4. E spera circular —Debe existir un conjunto de subprocesos en espera, {t0, t1,
t2, ... t (n)} en donde t0 está esperando un bloqueo ocupado por t1, t1 está
esperando un bloqueo ocupado por t2, ... t (n -1) y t (n) está esperando un
bloqueo ocupado por t0.
El punto m uerto recursivo es una condición en la cual un subproceso intenta adquirir un
recurso que ya posee.
El pu n to m uerto mutuo ocurre cuando dos subprocesos adquieren bloqueos separados y
están bloqueados, cada uno esperando que el otro libere el bloqueo. Esto también se
define como abrazo m ortal.
P r o g r a m a c ió n d e sistem as 871
D eb e NO D EBE
DEBE proteger los datos compartidos en NO DEBE olvidar liberar un mutex después
tre varios subprocesos para que su código de que ha sido adquirido.
sea reentrante.
DEBE inicializar adecuadamente las pri
mitivas de sincronización de LinuxThreads.
Resumen
La biblioteca L in u x T h re a d s no implemento completamente el estándar POSIX 1003.1c,
pero está casi com pleto. La principal característica que no se apega a este estándar es el
2 4
En la prim era mitad de la lección de hoy cubrimos los procesos y las técnicas de control
de procesos, así com o algunas clases básicas de C++ que utilizan las llamadas de sistema
para procesos descritas.
Preguntas y respuestas
P ¿ C ó m o se p o d ría ca m b ia r la adquisición de un mutex p ara evitar un punto
m u e r to ?
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del m ate
rial tratado, así com o ejercicios para que experimente con lo que ha aprendido. Trate de
resp o n d e r el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“R espuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
872 Día 24
Cuestionario
1. Enliste y defina los estados de un proceso.
2. Describa la diferencia entre la directiva de programación F1FO y la directiva de
programación RR.
3. ¿Cuál es la diferencia entre un semáforo binario y un semáforo de conteo?
4. Enliste y defina los cuatro requisitos para que se produzca un punto muerto.
Ejercicios
1. Usando un semáforo de conteo. ¿cómo podría resolver la "condición de carrera” al
iniciar subprocesos?
2. Implemente el ejemplo reentrante del listado 24.8 usando el objeto variable de
condición CondVar.
S emana 4
Comunicación
entre procesos
La co m u n icació n entre procesos (IPC) es el medio por el cual dos procesos se
co m u n ican entre sí. La lección de hoy trata sobre algunos de los métodos dispo
nibles para que los procesos se comuniquen entre sí.
L inux ofrece num erosos métodos para que los procesos de un mismo sistema se
com uniquen entre sí. Los métodos incluyen, pero no están limitados a, tuberías,
tuberías con nom bre y la comunicación entre procesos de System V. Linux también
prop o rcio n a otros m étodos para que los procesos que se encuentran en sistemas
separados se comuniquen entre sí, como los sockets, por ejemplo. Esta lección se
enfoca en la com unicación entre procesos de un mismo sistema.
Antecedentes
A n tes de hab lar sobre el tem a de hoy, empezaremos con la definición de una
interfaz que se utilizará para todas las clases de hoy. La clase Object definida en
el listado 25.1 im plem enta una interfaz común para la creación y destrucción de
objetos.
874 Día 25
Para poder implementar una interfaz común para la creación y destrucción de objetos.se
define una interfaz virtual pura llamada Object. Todos los objetos IPC definidos en la
lección de hoy se derivan de la clase Object.
Cada uno de nuestros objetos hereda esta interfaz para su creación y su destrucción. Esta
interfaz se define en o b ject.h en el código de muestra del listado 25.1.
Además, todos nuestros métodos de IPC utilizan estructuras de datos, recursos y maneja-
dores del kernel. Como tales, no es aplicable copiar estos objetos. Por ejemplo, si un
usuario copia una tubería, se cierra una instancia del objeto tubería, y por consecuencia el
manejador de la otra instancia de la tubería ya no es válido. Por esta razón, todos los objetos
de la lección de hoy definen el constructor de copia como una función miembro privada.
Además, dicho constructor no se implementa. AI no implementarlo, otras funciones miem
bro no pueden llamarlo sin querer; cualquier llamada al constructor de copia producirá un
error de enlace.
Tuberías
Una tubería es un método simple que permite que un proceso agregue su entrada estándar
a la salida estándar de otro proceso. Una tubería es un canal de comunicación semidúplex
entre un proceso padre y un proceso hijo.
Linux proporciona la llamada de sistema conocida como pipe (). Esta llamada regresa dos
descriptores de archivos como un arreglo de descriptores de archivos; el descriptor de
archivo 0 es el extremo de lectura de la tubería, y el descriptor de archivo 1 es el extremo
de escritura. El prototipo de la función pipe() se muestra a continuación:
int pipe(int fd[2]);
Comunicación entre procesos 875
El uso típico de una tubería es cuando un proceso padre crea una tubería y llama a f ork ()
para crear un proceso hijo. Como un proceso hijo hereda todos los descriptores de archivo
de los procesos padres, tanto el padre como el hijo ahora contienen descriptores de archi
vos que se referencian uno a otro. Para completar la configuración de una tubería semidú-
plex para que el hijo pueda escribir al padre, el proceso hijo cierra fd[0], el descriptor
de archivo de lectura, y el padre cierra fd[ 1], el descriptor de archivo de escritura. Lo que
queda es el descriptor de archivo de escritura para el hijo y el descriptor de archivo de
lectura para el padre. Esta secuencia de eventos crea una tubería semidúplex para que el
hijo escriba al padre. La figura 25.1 muestra un diagrama de una tubería entre dos procesos.
tubería
El listado 2 5 .2 define la clase Pipe. Esta clase encapsula las llamadas de sistema para
tuberías.
L is t a d o 2 5 . 2 continuación
4: #define C_PIPE_H
5:
6: //include <unistd.h>
7: //include "lst25-0l.h" // //include "object.h"
8:
9: class Pipe : public Object
10: {
11: public:
12: Pipe();
13: -Pipe();
14: int Create();
15: int Destroy();
16: void SetToRead();
17: void SetToWrite();
18: int ReadFromPipe(char *);
19: int WriteToPipe(char *);
20: private:
21: //no permitir la copia
22: Pipe & operator=(const Pipe &);
23: bool init_;
24: bool read_;
25: int pipe_[ 2 ];
26: };
27:
28: #endif
La clase Pipe definida en el listado 25.2 implementa las rutinas descritas en esta
A nálisis
lección relacionadas con las tuberías. El padre y el hijo deciden cuál proceso lee
y cuál proceso escribe, y cada proceso llama a la función miembro SetToRead() o a
SetToWrite(). Después de que cada proceso inicializa su tubería para leer o escribir, llama
a la función miembro ReadFromPipe () o a WriteToPipe() para transferir datos.
Las tuberías están limitadas a los procesos padre/hijo.
Debe crear y configurar la tubería semidúplex utilizando ambos procesos. Si uno de los
dos procesos no está listo, es decir, si un proceso escribe a una tubería que el otro proce
so no ha abierto para lectura, o lee de una tubería que el otro proceso no ha abierto para
escritura, se regresa la señal SIGPIPE.
Las tuberías semidúplex se implementan por medio de los descriptores de archivo del
proceso. Como los descriptores de archivo son recursos del proceso, se regresan al sistema
cuando el proceso termina.
C o m u n ic a c ió n e n tre p ro ce s o s 877
D ebe NO DEBE
DEBE utilizar una tubería para procesos NO DEBE escribir en una tubería a menos
que compartan la relación padre hijo. que ambos procesos hayan configurado
sus respectivos extremos de la tubería.
popen y p e ló s e
De m anera alternativa, Linux proporciona otra manera de crear una tubería semidúplex,
m ediante popen ().
F ILE * popen (char * comando, char * acceso);
La llam ad a de sistem a conocida como popen () crea una tubería, realiza una llam ada a
f o rk ( ) , y luego llam a a exec () para ejecutar el comando solicitado. El acceso de lectura
o de escritura a la tubería creada por popen () se determina mediante el argumento acceso.
El valor de retorno de popen () es un flujo iostream y se debe cerrar con p e ló se ().
Para c e rrar el flujo creado por popen (), el usuario debe llamar a pelóse (). Esta llam ada
de sistem a cierra la tubería y regresa los recursos del proceso al sistema.
2 5
F IL E * p o p e n (ls, "r");
p c lo se (ls);
O tra diferencia entre tuberías y tuberías con nombre es que como una tubería con nombre
es parte del sistem a de archivos, se debe abrir y cerrar como un archivo.
El p rocedim iento para utilizar una tubería con nombre es similar al uso de la llamada de
sistem a p ip e ( ). Un proceso abre la tubería con nombre para escribir y otro proceso la abre
para leer. U na vez que la tubería con nombre se abre utilizando ambos procesos, éstos se
com unican entre sí mediante la tubería con nombre. Puede crear una tubería con nombre
de v arias m aneras. Puede utilizar los comandos de shell mknoci y m kfifo. A dem ás, un
proceso puede utilizar la llamada de sistema mknod().
i n t mknod(char t r a y e c t o r i a , mode_t modo, dev_t d isp ositivo );
|878 Día 25
Una vez que se crea la tubería, los datos se pasan entre los dos procesos usando las llamadas
de sistema estándar de lectura y escritura: f o p e n ( ). f c l o s e ( ), f r e a d ( ) y fwrite().
El listado 25.3 muestra nuestra implementación de un objeto que representa a una tubería
con nombre.
L is t a d o 25.3 D e f i n i c i ó n d e la c la s e N a m e d P ip e
J
C o m u n ic a c ió n e n tre p ro ce s o s 879
Al term inar de utilizar el objeto NamedPipe, debe llamar a Cióse () para que cierre el
m anejador de la tubería, y a la función D e s t r o y ( ) para limpiar las estructuras de datos
del kernel relacionadas con esta conexión.
D ebe NO DEBE
DEBE utilizar una tubería con nombre NO DEBE escribir en una tubería a menos
para comunicarse entre dos procesos que que ambos procesos hayan completado
no estén relacionados. la inicialización.
Creación de claves
Cada uno de los métodos de IPC mencionados utiliza una clave para identificarse a sí mismo.
Antes de profundizar en los detalles de IPC de System V, hablaremos brevemente sobre las
claves y desarrollaremos una clase para manejar las claves para los métodos de IPC.
• Un servidor puede crear una nueva clave especificando IPC_PRIVATE como una clave
en la llam ada de sistema apropiada, ya sea msgget (), semget ( ) o smget (). Después
de crear la clave, el servidor debe escribirla en una ubicación en la que el cliente la
recuperará, por ejem plo un archivo, msgget (), semget () y smget () son rutinas de
IPC de System V que crean instancias de los métodos de IPC.
880 Día 25
Entrada L is t a d o 2 5 . 4 D e f in ic ió n d e la c la s e Key
El primer paso para obtener una clave es crear un objeto clave y llamar a la función
A nálisis
miembro Create (). Esta función está sobrecargada, así que puede pasarle una clave
predefinida o crear una clave pasándole un nombre de archivo y un identificador. Después
de erar la clave, puede obtener su valor llamando a la función miembro G et().
Comunicación entre procesos 881
Ahora que hemos quitado la memoria compartida con el identificador 78593, volveremos a
ejecutar el comando ipcs para verificar que se haya quitado el método de memoria com
partida. A continuación se presentan los resultados de volver a ejecutar el comando ipcs.
Colas de mensajes
Las colas de mensajes son un método para que los procesos se envíen mensajes (datos)
entre sí. Una cola de mensajes es una lista enlazada de mensajes mantenida por el kemel.
Los mensajes se agregan y se quitan de una cola FIFO por medio de los procesos. Cada
mensaje consta de un identificador de mensaje, los datos de mensaje y el número de bytes
de los datos del mensaje. La figura 25.2 muestra una cola de mensajes.
Para cada cola de mensajes, el kemel mantiene la estructura msqid_ds definida en el archi
vo de encabezado estándar <linux/msg.h> del kemel. El listado 25.6 muestra la estruc
tura msqid_ds de Linux. Esta estructura mantiene apuntadores a los mensajes, el número
de mensajes en la cola y demás información relacionada con la cola de mensajes, como el
identificador de proceso del último proceso que envió un mensaje y del último proceso que
recibió un mensaje.
Comunicación entre procesos 883
F i g u r a 25.2
La cola de mensajes.
Los apuntadores a la cola actual de mensajes son los miembros *first (primero) y
A n á l is is
*last (último) de la estructura msqid_ds que se muestra en el listado 25.6.
Antes de que se pueda utilizar una cola de mensajes, primero se debe crear. La llamada al
sistem a msgget () crea una nueva cola de mensajes, msgget () regresa el identifícador de
la cola de mensajes. Este identifícador es un int, y se utiliza para las funciones de mensajes
restantes, msgget () regresa un valor msgqid entero, y este valor msgqid se pasa a las otras
funciones msg*.
/* Obtener cola de mensajes. */
extern int msgget P ((key_t _key, int msgflg));
884 D ía 25
El parám etro key es la clave que se describió en la sección anterior. El parámetro msgflg
consiste en los perm isos de acceso para la cola de m ensajes, a los que se les aplica una
operación OR a nivel de bits con los siguientes indicadores:
• IPC CREAT C rear la cola si no existe.
Por lo general, un program ador de IPC redefine el m ensaje para que se apegue a la defi
nición estándar de msgbuf en msh.h. Por ejem plo, un m ensaje se podría definir como se
m uestra en el listado 25.8.
Este m ensaje se define com o un encabezado de m ensaje, mtype. El tipo de mensaje debe
ser un entero no negativo, un encabezado y una carga útil que sean específicos para la
aplicación. El núcleo no m odifica los datos del m ensaje de ninguna manera. El encabezado
Comunicación entre procesos 885 |
se puede usar como indicador para denotar la forma en que se deben interpretar los datos
de la carga útil.
Los mensajes se leen de una cola de mensajes por medio de la llamada de sistema msgrcv ().
El parámetro msgtype le permite aplicar algo de granularidad al leer mensajes. Los valores
y las definiciones de msgtype se muestran a continuación.
/* Recibir mensaje de la cola de mensajes. */
extern int msgrcv P ((int msqid, void * msgp, size_t msgsz,
long int _msgtype, int _msgflg));
Los valores para msgflg se definen de la siguiente manera:
• Si msgtype es cero, se regresa el primer mensaje de la cola.
• Si msgtype es mayor que cero, se regresa el mensaje que tenga un tipo de mensaje
igual.
• Si msgtype es menor que cero, se regresa el primer mensaje que tenga el tipo más
bajo que sea menor o igual que el valor absoluto del mensaje.
Cuando una aplicación termina de utilizar la cola de mensajes, ésta se debe quitar del sistema.
La llamada de sistema msgctl() se utiliza para quitar del sistema una cola de mensajes.
/* Operación de control de cola de mensajes. */
extern int msgctl _P ((int _msqid, int _cmd, struct msqid_ds *_buf));
El listado 25.9 define un objeto Message. Este objeto encapsula todas las llamadas de sis
tema para mensajes que se definen en esta sección.
L is t a d o 2 5 .9 continuación
22: private:
23: //no permitir la copia
24: Message & operator=(const Message &);
25: typedef struct
26: {
27: long type;
28: char * payload;
29: } TheMessage;
30: bool init_;
31: Key key_;
32: int msgqid_;
33: };
34:
35: #endif
El objeto Message que se define en el listado 25.9 contiene las funciones miembro
A nálisis
estándar Create() y Destroy () definidas para todos nuestros objetos. Observe
que el constructor toma un objeto Key. Los procesos cliente y servidor crean un objeto
Key ya sea pasando el mismo valor de clave o utilizando el mismo nombre de archivo e
identificador; de esta manera crearán un objeto Message que haga una referencia a la misma
cola de mensajes. Una vez que se ha creado el objeto Message, los procesos cliente y servi
dor envían y reciben mensajes por medio de las funciones miembro Read() y Write().
El objeto Message encapsula las llamadas de sistema para mensajes que se describen en
esta sección. El código de ejemplo para esta sección contiene una muestra del objeto
Message; el programa msgtest utiliza el objeto Message para enviar y recibir mensajes.
Semáforos
Los semáforos son un método utilizado para sincronizar operaciones entre dos procesos. Los
semáforos no son un método para pasar datos entre procesos, como los mensajes, sino un
medio para que dos procesos sincronicen el acceso a algún recurso compartido.
Para que un proceso obtenga acceso a un recurso, evaluará el valor del semáforo. Si el valor
es mayor que cero, el proceso decrementa el valor en uno. Si el valor es cero, el proceso
se bloquea hasta que el valor sea mayor que uno. Cuando el proceso termine de acceder
al recurso, lo liberará incrementando el valor del semáforo en uno.
J
C o m u n ic a c ió n e n tre p ro ce s o s 887
O b s e rv e el valo r de nsems. Semget () puede crear varios semáforos en una sola llamada.
Si se utiliza semget () para crear varios semáforos, los semáforos se enumeran em pezan
d o d e sd e 0.
888 Día 25
Después de crear un semáforo con semget (). se accede mediante el uso de la llamada de
sistema semop().
í* Operar sobre el semáforo. */
extern int semop __P ((int __semid, struct sembuf *__sops,
unsigned int __nsops));
semop() toma como argumento una estructura sembuf, la cual se define en el listado25.11.
Entrada L is t a d o 2 5 . 1 1 D e f in ic ió n d e la e s t r u c t u r a se m b u f d e L in u x
Los sem áforos tienen otra llamada de sistema, sem ctl(). sem ctl() se utiliza para quitar
un sem áforo del sistema.
/* Operación de co n tro l de semáforo. */
extern i n t semctl __P ( ( in t __semid, int __semnum, int __cmd, . . . ) ) ;
El listado 2 5 .13 define una clase semáforo que muestra los usos de las llamadas de sistema
para sem áforos definidas en esta sección.
al mismo semáforo. Una vez que se ha creado el semáforo, los procesos cliente y servi
dor tienen acceso al semáforo mediante las llamadas a las funciones miembro Acquire()
y Release().
El objeto Semaphore encapsula las llamadas de sistema para semáforos que se describen
en esta sección. El programa semtest que viene en el CD-ROM muestra el uso del objeto
Semaphore.
Memoria compartida
La memoria compartida es un método que permite que dos procesos compartan datos de
manera directa. Regresemos a lo discutido anteriormente sobre las colas de mensajes,
tuberías y tuberías con nombre. Cada uno de estos métodos de comunicación entre procesos
permite que varios procesos se pasen datos entre sí. Una cuestión a cerca del rendimiento
con estos métodos de comunicación entre procesos es que los datos que se copian entre los
procesos se copian del proceso de origen al kernel, y luego del kernel al proceso de des
tino. Estas copias son costosas.
La memoria compartida permite que varios procesos obtengan un apuntador a un área
de memoria que sea compartida entre los procesos. Tener un apuntador a un segmento de
memoria elimina las copias costosas hacia y desde el kernel.
La memoria compartida también tiene sus desventajas. Aunque cada uno de los proce
sos tiene acceso a un segmento de memoria, los accesos a esa memoria deben estar
sincronizados.
Por lo regular, la sincronización de procesos para la memoria compartida se implemen-
ta por medio de semáforos, como se describió en la sección anterior.
Antes de utilizar la memoria compartida, primero se debe crear; shmget () crea un segmento
de memoria compartida.
/* Obtener segmento de memoria compartida. */
extern int shmget _P ((key_t _key, int _size, int _shmflg));
Cada segmento de memoria compartida se mantiene en el kernel mediante una estructura
shmid_ds. shmid_ds se define en <kernel/shm. h>. El listado 25.14 muestra la estruc
tura shmid_ds de Linux.
Entrada L is t a d o 2 5 . 1 4 D e f in ic ió n d e la e s t r u c t u r a sh m id d s d e Lin u x
La llam ada de sistem a shm get() abre o crea un segmento de memoria compartida,
A n á l is is
pero no proporciona acceso a esa memoria. Para que un proceso pueda tener acceso
a la m em o ria com partida, se debe unir a ésta. La llamada de sistema sh m at() regresa un
ap u n tad o r al segm ento de memoria compartida:
/* U n ir segmento de memoria compartida. */
extern v o id *shmat __P ( ( in t __shmid, __const void *__shmaddr,
in t __shmflg));
C u ando un proceso term ina con la memoria compartida, debe separarse del segmento de
m em oria com partida. Esto se hace mediante una llamada a shmdt().
/* S e p a ra r segmento de memoria compartida. */
extern i n t shmdt __P ( ( __const void *__shmaddr)); 2 5
D espués de que todos los procesos se separan de la memoria compartida, el segmento de
m em oria co m p artid a debe ser eliminado. Esto se logra mediante una llamada de sistema
sh m c t1 ( ) .
/* Operación de c o n t ro l de memoria compartida. */
exte rn i n t shmctl __P ( ( i n t __shmid, int __cmd, struct shmid_ds * __buf));
El listado 25.15 define un objeto SharedMemory que encapsula todas las llamadas de sistema
para m em oria com partida definidas en esta sección.
1:
2: // L is t a d o 25.15 Clase para memoria compartida (smem.h)
3:
4: # i f n d e f C_SMEM_H
5: //define C_SMEM_H
6:
7: //include <sys/shm.h>
8: //include < s t r in g . h >
9: //include " l s t 2 5 -04 .h" // //include "key.h"
10 : //include "lst2 5 -0 l.h " // //include "object.h"
11 :
continúa
I 892 Día 25
L is t a d o 2 5 . 1 5 continuación
ipc
Linux también ofrece soporte para la llamada de sistema conocida como ipc( ). Ésta es
una llamada centralizada de sistema que se utiliza para implementar las llamadas de IPC
de System V en Linux. ipc( ) sólo se implementa en Linux y no es portable hacia otros
sistemas UNIX. Por medio de ipc( ), puede hacer cualquiera de las llamadas de sistema
C o m u n ic a c ió n e n tre p ro ce so s 893
Resumen
H oy h e m o s d e fin id o varios métodos disponibles para el programador de Linux en rela
ció n con la co m u n ic ac ió n entre procesos (IPC). Cada una de las secciones de esta lección
h a b la so b re el uso de los m étodos, y sobre los detalles relacionados con dicho uso.
En la p rim e ra m itad de esta lección hablamos sobre algunos de los primeros métodos
d isp o n ib le s en los program as UNIX para la comunicación entre procesos: tuberías y
tu b e ría s con nom bre.
2 5
Preguntas y respuestas
P ¿Cuáles son las ventajas de usar colas de mensajes en lugar de una tubería?
¿Y en lugar de una tubería con nombre?
R L as c o la s de m ensajes son un canal dúplex total. Las tuberías y las tuberías con
n o m b re son sem idúplex.
P ¿Por qué es importante sincronizar el acceso a la memoria compartida?
R C o m o los p ro ce so s se están ejecutando independientemente uno de otro, cada
u n o p o d ría escrib ir en la memoria compartida y corromper los datos leídos, o los
e sc rito s, por los otros procesos.
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del mate
rial tra ta d o , a sí co m o ejercicios para que experimente con lo que ha aprendido. Trate de
re s p o n d e r el c u e stio n a rio y los ejercicios antes de ver las respuestas en el apéndice D.
“ R e s p u e s ta s a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
a n te s d e p a sa r al siguiente día.
C uestionario
1. E nliste las tres rutinas utilizadas para crear métodos de comunicación entre procesos
de S y stem V.
894 Día 25
Ejercicios
1. Implemente un programa cliente/servidor en el que el cliente y el servidor compartan
datos usando la clase SharedMemory. y sincronice el acceso a la memoria compartida
usando la clase Semaphore.
2. Usando tuberías, prepare una comunicación dúplex total entre un proceso padre y un
proceso hijo.
3. Extienda la clase NamedPipe para que pueda abrir tuberías que no se bloqueen y una
función miembro Read() que no bloquee si no hay datos disponibles.
S e m a n a 4
D ía 2 6
Programación de la GUI
En las lecciones anteriores ha visto cómo se construye el lenguaje C++, su sintaxis
y tip o s de d a to s, y cóm o se pueden crear objetos con él que puedan interactuar
p a ra p ro d u c ir program as útiles que funcionen en el sistema operativo Linux.
En e sta lección llevará estos conceptos más allá, y verá cómo puede utilizar clases
y o b je to s p ara rep resen tar objetos reales en forma gráfica, de manera que pueda
in te ra c tu a r con e llo s y con su programa con un ratón u otro dispositivo gráfico
de e n tra d a .
H o y a p re n d e rá lo siguiente:
El escritorio de Linux
Hasta hace poco, una de las quejas más frecuentes con respecto a Linux como sistema
operativo (así como con respecto a otros sistemas UNIX) era que sólo tenía una interfaz
de línea de comandos en forma de un “s h e ir o de texto simple en la pantalla. Esto significa
que se interactúa con la computadora escribiendo com andos como texto y se obtiene la
salida de la misma manera: como texto. Ni siquiera es texto bonito: es texto monoespaciado
estilo máquina de escribir, grueso, atro/, y sin formato.
Esto está bien para los fanáticos de computadoras y los desarrolladores, y también está bien
para sistemas de servidores que se conectan a otras aplicaciones cliente más amigables para
el usuario, pero es un obstáculo masivo para llevar a Linux a donde pertenece: al escritorio,
literalmente. El usuario de computadora promedio quiere resultados, no quejas ni argumen
tos que parecen ser interminables en relación con entradas inadecuadas.
Las cosas empezaron a mejorar desde principios de los 80. cuando muchas aplicaciones
que aparecieron en el mercado estaban en el punto intermedio, entre la interfaz de línea
de comandos y la GUI (interfaz gráfica de usuario). Ésta era la interfaz no gráfica basada
en menús, que le permitía interactuar por medio de un ratón en lugar de tener que
escribir comandos con el teclado. Aún así, la interfaz seguía siendo fea e incómoda, pues
las imágenes y los cuadros se dibujaban por medio de los caracteres de línea del conjunto
extendido de caracteres ASCII. No era tan agradable como las GUIs de la actualidad.
Desde luego que, en la actualidad, la mayoría de los sistemas operativos importantes, como
varias versiones de Windows, el sistema operativo de Macintosh y OS/2 proporcionan una
verdadera GUI en donde los objetos con los que el usuario interactúa se dibujan en forma
limpia y precisa, píxel por píxel.
Por lo general, las aplicaciones utilizan los elementos de la GUI que vienen con el sistema
operativo y agregan sus propios elementos e ideas de GUI. A veces una GUI utiliza una
o más metáforas para los objetos familiares de la vida real: escritorios, ventanas, o la des
cripción física de un edificio.
Los elementos de una GUI incluyen ventanas, menús, botones, casillas de verificación,
controles deslizantes, medidores, barras de desplazamiento, iconos, iconos emotivos que
cambian su naturaleza en tiempo real a medida que nos desplazamos por el sistema de
archivos, asistentes, ratones y muchas otras cosas. Los dispositivos multimedia ahora for
man parte de la mayoría de las GUIs, y las interfaces de sonido, voz, vídeo en movimiento
y realidad virtual parecen convertirse en una parte estándar de la GUI para muchas
aplicaciones.
Programación de ia GUI 897
La interf az gráfica de usuario de un sistema, junto con sus dispositivos de entrada, algunas
veces se conoce com o “ look and feel”. Otra cosa que tiende a caracterizar la aplicación
GUI es que por lo general se controla por eventos, y no por procedimientos. Esto significa
que, por lo general, una aplicación GUI espera que usted le pida que haga algo, en lugar
de em pezar desde el principio y seguir un camino lógico hasta algún punto de terminación,
y luego terminar. Hay que tener en cuenta que un programa controlado por eventos también
puede ten er tareas que se controlen por procedimientos en su interior. Un ejemplo de esto
es un ID E (Entorno de Desarrollo Integrado) de GUI, el cual espera que usted le pida crear
el program a (un evento) y luego ejecutará a make por usted para realizar la creación (por
procedim iento).
Las G U Is más conocidas por la mayoría de las personas en estos sistemas operativos moder
nos y sus aplicaciones se originaron en el Laboratorio de Investigación de Xerox en Palo
A lto, a finales de los 70. La compañía Apple las utilizó en sus primeras computadoras
M acintosh; después, Microsoft utilizó muchas de las mismas ideas en su primera versión
del sistem a operativo Windows para las PCs compatibles con IBM. Pero esto no se aplica
para Linux: no tiene una GUI nativa, a diferencia de sistemas operativos como Windows
N T y A pple M acintosh, en donde la GUI es el sistema operativo, al menos en parte.
Lo que realm ente distingue a Linux de otros sistemas operativos modernos es que, aunque
se puede crear una GUI para él usando una tarjeta de vídeo, las capacidades del monitor
y softw are para controlar todo, la GUI realmente no forma parte del sistema operativo en
sí. En vez de esto, la GUI está por encima del sistema operativo y disfraza la mayor parte de
la com plejid ad de la línea de comandos del sistema operativo para el usuario. La mayoría
de los usuarios considera que esto es algo muy bueno.
A unque ésta puede parecer una diferencia trivial e incluso pedante entre éste y los sistemas
operativo s que no tienen funcionalidad GUI nativa, es una diferencia que provee a Linux
(y a usted) de un enorm e alcance en cuanto a flexibilidad.
L inux es nuevo, pero sus antecedentes, los variados tipos de UNIX, que son demasiados
com o para m encionarlos todos, han estado presentes por décadas, y en el pasado las per
sonas han intentado utilizar las capacidades para gráficos estándar de UNIX representadas
por el sistem a X W indows para facilitar la funcionalidad mediante la GUI. Pero la pro
gram ación de X es com pleja y difícil (vea el recuadro “El protocolo X y los desarrollos
más recientes"), e incluso kits de herramientas como Motif y su clon gratuito LessTif, que
están diseñados para ocultar la mayoría de los horrendos detalles de la programación de X,
tienen m uy poco éxito en el mejoramiento de un mal trabajo.
898 Día 26
R ecientem ente se han escrito varios niveles q u e e stán p o r encim a d e X lib para tratar de
sim plificar la program ación d e X, pero estos intentos, principalm ente M o tif y su clon gra
tuito, LessTif, sólo tienen éxito en parte (la A P I a ú n es m u y m ala) y, de cualquier forma, la
"lo o k an d fe e l" se está volviend o obsoleta.
M á s recientemente, hem os visto desarrollos com o G T K + + (una A P I de C que se encuentra
por encim a de GLib y GDK, que a su vez está un nivel arriba d e Xlib), w xW indow s (un nivel
d e C++ por encima de GTK++) y la biblioteca Q t de TrollTech (un nivel de C++ por encima de
X lib en U N IX y por encim a de la G D I en W in d o w s).
Toda esta división de niveles tal vez le p rovoque un d o lo r de cabeza, pero en realidad no
es m o tiv o de preocupación: hasta las bibliotecas de gráficos de C++ m ás conocidas, como
Programación de ia GUI 899
la M F C d e M ic r o s o ft y las clases C++ Builder de Borland son ppcp.rnás que niveles .de C++
p o r e n c im a d e la A P I d e C nativa de W indow s, y la GDI, .Evidentemente, GDK++,
w x W in d o w s , y en especial Qt, tienen una perspectiva que puede ser correcta. Las últimas
d o s b ib lio te c as proveen árboles de código fuente independiente dé lá plataforma; es decir,
u ste d e scrib e u n a vez, com pila eso para su plataforma usando'él kit de herramientas de
b ib lio te c a s nativas, y lo ejecuta. Esto es algo parecido a Java, sólo que la compilación se
re a liz a d e s p u é s d e enviar el có d igo y no cuando el programador lo construye.
Los e je m p lo s d e Q t y w x W in d o w s que se ven posteriorífiénte en ésta lección sé compilarán y
e je c u ta rá n t a n t o en p latafo rm as Linux como en Windows.
Pero, he aquí un dilema: tenemos un sistema operativo de primera clase gratuito y robusto,
que el usu ario prom edio no querrá utilizar debido a que su GUI se ve como lo que es:
aburrida, obsoleta y muy incómoda.
Para fortuna de Linux y de nosotros, los programadores que seremos convocados a escribir
las nuevas aplicaciones, hay dos proyectos nuevos que afianzan a Linux en el escritorio,
con sus intentos exitosos de convertir la GUI de Linux en una interfaz excelente y eminen
tem ente utilizable.
E stos dos proyectos son el proyecto GNOME (Entorno GNU de Modelo de Objetos de
R ed), y el K D E (Entorno de Escritorio K). Ambos proyectos son software gratuito y libe
rado bajo la GPL. KDE tiene uno o dos problemas de licencia con su biblioteca de gráficos
de so p o rte Q t de TrollTech, pero al parecer ya están resueltos; sin embargo, esto parece
disuadir a los aficionados empedernidos de GNU de adoptar a KDE como software genui-
nam ente gratuito.
Esta lección exam ina a GNOME y a KDE y crea algunas aplicaciones básicas para mostrar
qué tan sim ple puede ser escribir software controlado por GUI para estos entornos.
Qué es G N O M E
G N O M E es el Entorno GNU de Modelo de Objetos de Red, además de ser el escritorio GUI
del proyecto G NU.
Citando del anuncio original del grupo de noticias de Usenet comp.os.linux.announce, se
pretende que G N O M E sea
La gran diferencia es que. en contraste con los ejem plos antes mencionados, todos los
com ponentes de GNOME son gratuitos y están liberados ya sea bajo la GPL o la LGPL.
Y no sólo eso, además GNOME es extremadamente flexible en comparación con la mayoría
de los entornos de escritorio. Como un bono adicional para el usuario, se puede personalizar
fácilmente para adaptarse a las necesidades específicas.
GNO M E utiliza CORBA (A rquitectura Común de Agente de Solicitud de Objetos)
del Grupo de administración de objetos para permitir que los componentes de software
operen entre sí sin problemas, sin importar el lenguaje en el que se implementen ni el
equipo en el que se ejecuten. Además, los desarrolladores de GNOME están trabajando
duro para desarrollar un modelo de objetos llamado Bonobo. con base en CORBA, el cual
es similar a OLE2 (Vinculación e Incrustación de Objetos, versión 2) de Microsoft.
Bonobo permitirá a los programadores exportar e importar recursos como componentes
y, por ejemplo, permitirá que los usuarios utilicen en su entorno de desarrollo el editor
que prefieran, siempre y cuando éste sea soportado m ediante una interfaz de editor
CORBA estandarizada.
GNOME utiliza el kit de herramientas de Gimp (GTK++) como kit de herramientas de grá
ficos para todas las aplicaciones gráficas. GTK++ tiene muchas características excelentes y
surgió del desarrollo del GIMP (Programa GNU de Procesamiento de Imágenes), el cual
merece un libro por sí solo. Como GTK++ sostiene a GNOM E. ambos utilizan Imlib,
una biblioteca de imágenes para el sistema X Windows, el cual soporta varios formatos
de imágenes, desde XPM hasta PNG, y varios fondos de bits, desde color verdadero de
24 bits hasta blanco y negro de 1 bit, y todo es transparente para el programador.
Las aplicaciones GNOME son conscientes de la sesión: por decir, si usted apaga el pro
cesador de palabras de GNOME y luego lo vuelve a iniciar, éste abrirá el documento que
usted había abierto anteriormente, y colocará su cursor en el mismo lugar. Esto es posible
gracias al sistema Administración de Sesión de X, como se aplica en el Administrador
de Sesión de GNOME.
Qué es KDE
K D E solía ser el “Entorno Agradable de Desarrollo”, pero, afortunadamente, le han quitado
la palabra “ A gradable” .
K D E es un entorno de escritorio moderno transparente para la red para estaciones de trabajo
U N IX . S atisface admirablemente la necesidad de un escritorio fácil de usar para las esta
ciones de trabajo UNIX, similar a los entornos de escritorio que se encuentran en MacOS
y W indow s.
C on la llegada de KDE, ahora hay disponible un entorno de escritorio moderno y fácil de
u sar p ara U N IX que rivaliza con cualquier otro que haya en el mercado. Al igual que
G N O M E , K D E puede ser bastante difícil de instalar, pero también se incluye en liberacio
nes de Linux de varios fabricantes. Red Hat Linux 6.0 y posteriores también vienen con
K D E , y se pueden instalar como el escritorio predeterminado en lugar de GNOME. De
hecho, puede configurar su equipo para cambiar a una sesión GNOME o a una sesión KDE
cada vez que entre al sistema.
902 Día 26
Junto con Linux, KDE proporciona una plataforma completamente abierta, disponible sin
costo para cualquiera, incluyendo su código fuente para poder modificarla y así satisfacer
las necesidades de cada individuo.
Aunque en general esto es cierto, hay algunas cuestiones relacionadas con las bibliotecas
Qt que hacen la interfaz entre KDE con las profundidades de la interfaz Xlib. y para muchos
puritanos esto significa que KDE no es software gratuito. Evidentemente, no tiene que pagar
por usarlo, por modificarlo o por distribuirlo, pero no puede vender software que usted
escriba utilizando las bibliotecas de KDE; a menos que compre una licencia profesional de
TrollTech.
A pesar de las objeciones de los puritanos, y aunque siempre habrá espacio para mejorar, los
desarrolladores de KDE, un grupo no muy acoplado de programadores que están enlazados
por medio de Internet, han ideado una alternativa viable para algunas de las combinaciones
de sistemas operativos/escritorios más populares y comerciales que se puedan obtener.
Para el usuario, KDE ofrece, entre otras cosas
• Un sistema integrado de ayuda para un acceso conveniente y consistente a la ayuda
relacionada con el uso del escritorio KDE y sus aplicaciones.
• Look and feel consistente en todas las aplicaciones KDE.
• Menús y barras de herramientas estandarizados, enlaces de teclas, esquemas de
colores, etc.
• Internacionalización: KDE está disponible en más de 25 lenguajes.
• Una gran cantidad de aplicaciones KDE útiles.
En realidad, la look and feel predeterminada de KDE es asombrosamente parecida a la GUI
de Windows 95, algo que no creo que sea pura coincidencia.
W idgets
En t é r m in o s com pu tacio nales, un w idget es un elementó de una GUI (interfaz gráfica de
u su a rio ) q u e d e sp lie ga información o proporciona una manera especifica para.que un usua
rio in te ractú e con el sistema operativo y los programas de aplicaciones. Lps widgets incluyen
iconos, m e n ú s desplegables, botones, cuadros d e selección, indicadores de progreso, cuádros
d e verificación, barras de desplazamiento, ventanas; bordes/de ventanas (que nos permiten
c a m b ia r el t a m a ñ o d e la ventana), intérruptores, formularios y muchos otros dispositivos
p a ra d e s p le g a r inform ación y para pedir, aceptar y responder a ’la‘s acciones del usuario.
En p r o g r a m a c ió n , un w id g e t tam bién es el pequeño programa que se escribe para poder
d e sc rib ir la a p arie n c ia de un w id ge t específico, la forma en que se comporta, y cómo inte
ractú a c o n el usuario. La mayoría de los sistemas operativos incluyen un conjunto de w idgets
p r e d e f in id o s q u e el p ro g ra m a d o r puede incorporar en una aplicación, especificando la
f o r m a e n q u e d e b e comportarse. Puede crear nuevos widgets extendiendo los ya existentes,
o p u e d e e scribir sus propios w idgets partiendo desde cero.
904 Día 26
Este té rm in o se aplicó aparentem ente p o r prim era vez en sistem as operativos basados en
U N IX y en el sistema X W ind ow s. En O O P (p ro gram ació n o rien tada a objetos), cada tipo
d e w id g e t se define co m o un a clase (o un a su bclase bajo u n a clase w id g e t genérica y
am plia) y siempre se asocia con una ventana específica. Por ejemplo, en el kit de herramien
tas A IX Enhanced X-W indow , un w id g e t es el tip o de d ato s fundam ental.
En primer lugar, como GNOME utiliza GTK++ como su motor de gráficos, daremos un
breve vistazo a GTK++. Ésta es una biblioteca para crear interfaces gráficas de usuario.
Tiene licencia de la LGPL, por lo que puede desarrollar software abierto, software gratuito o
incluso software comercial con GTK++ sin tener que gastar nada en cuanto a licencias o
regalías.
Se llama Kit de herramientas de GIMP debido a que fue originalm ente escrito para
desarrollar el GIMP (Programa General de Manipulación de Imágenes, otra aplicación
excelente de software gratuito), pero ahora GTK++ se ha utilizado en un gran número
de proyectos de software, incluyendo el proyecto GNOME (Entorno GNU de Modelo de
Objetos de Red).
GTK++ está creado por encima de GDK (Kit de Dibujo de GIMP), el cual es básicamente
una envoltura alrededor de las funciones de bajo nivel para tener acceso a las funciones
de soporte para manejo de ventanas (Xlib, en el caso del sistema X Windows). GTK++ es
en esencia una API orientada a objetos. Aunque está escrita completamente en C, se
im plem enta usando la idea de clases y funciones callback (apuntadores a funciones).
Existe una unión de C++ de nivel delgado con GTK++, que se conoce como GTK—, la
cual proporciona una interfaz más parecida a C++ para GTK++. En la liberación actual,
GTK++ 1 .3, hay también una biblioteca llamada gnome— , la cual es una envoltura de C++
alrededor de la API C de GNOME. Pero gnome— parece ser inmadura en la actualidad,
o inexperta en el menor de los casos. Ciertamente, esto puede haber cambiado para cuando
usted lea esto.
Programación de la GUI 905
E n t r ad a L is t a d o 2 6 . 1 U n p r o g r a m a básico d e GNOME: b o t o n e s . c x x
54: FALSE,
55: 0);
56: g t k _ s ig n a l_ c o n n e c t (GTK_0BJECT(button),
57: "c lic k e d ",
58: GTK_SIGNAL_FUNC(Callback : : c lic k e d ) ,
59: "Button 1 \n ");
60:
61: b u tto n = gtk_button_new_with_label( "Button 2");
62: g t k _ b o x _ p a c k _ s t a r t (GTK_BOX(hbox),
63: bu tto n ,
64: FALSE,
65 : FALSE,
66: 0);
67: g t k _ s ig n a l_ c o n n e c t (GTK_OBJECT(button),
68: "c lic k e d ",
69: GTK_SIGNAL_FUNC(Callback: : c lic k e d ),
70: "B u t t o n 2 \ n " );
71 :
72 : gtk _vvidget_sho w _a ll (app) ;
73 : g t k main ();
74: r e t u r n 0;
75: }
En este caso, creamos un cuadro hori/onlal para alojar los botones que crearemos en breve
y lo agregaremos al widget de la aplicación en las lincas 4 1 y 42.
Esto significa que cuando hacemos clic en uno de los botones, la función callback
C a l l b a c k : : c l i c k e d () dirige y procesa el evento.
S a l id a
F i g u r a 2 6 .1
L a s a lid a d e l
p rogram a b o to n e s .
P r o g r a m a c ió n d e la GUI 909
C a d a G U I s o p o r t a d a t ie n e su p r o p ia biblio te ca (c o m o G T K + + , M o t if , o W i n
26
d o w s ) ; w x W i n d o w s e s el n o m b r e g e n é r i c o p a r a t o d a la s u i t e d e b i b l i o t e c a s .
L a q u e m á s n o s i n t e r e s a e s w x G T K , q u e e s la b i b l i o t e c a e s p e c i f i c a q u e e n
v u e l v e a G T K + + . S i n e m b a r g o , al i g u a l q u e la d o c u m e n t a c i ó n d e w x W i n d o w s ,
u t i l i z a r e m o s l o s t é r m i n o s w x W i n d o w s y w x G T K s in d i s t i n c i ó n , e x c e p t o c u a n d o
s e a i m p o r t a n t e dife re n ciarlas.
Sin embargo, wxWindows no es un traductor de una GUI a otra. Por ejemplo, no puede
tomar una aplicación Motif y generar una aplicación Window s. Para programar usando una
GUI n a tiv a , necesita aprender a usar una n u eva API. No obstante, la a\PI de wxWindows
ha sido elogiada por ser intuitiva y simple, y puede ser mucho más fácil aprendera utilizar
la que una API de GUI nativa como Motif o Windows, El mensaje es simple: si sabe utili
zar wxWindows (o Qt en esta cuestión), no necesita conocer las APIs de GUI nativas.
Este kit de herramientas no es único, hay varios a escoger, pero wxWindows es comple
ta m e n te gratuito bajo la GPL, está bien establecido, bien documentado, y tiene una
cobertura bastante amplia de funcionalidad de GUI.
El peso que arrastran GNOME y GTK++. gracias a Red Hat Labs y a otros, podría impulsar
a wxWindows a una posición bastante importante, como la única herramienta utilizable para
producir productos compatibles con GNOME. Windows, Motif, Mac, y tal vez versiones
para BeOS. Linux se está convirtiendo en una variante de UNIX cada vez más importante y
respetada, y esto producirá algunas aplicaciones wxGTK de corriente principal.
Ahora que sabe lo que es wxWindows, veamos cómo puede utilizarla.
E ntrada L ista d o 2 6 .2 El p r o g r a m a d e w x W i n d o w s G N O M E H e l l o W o r l d
21
22
23 class MyFrame: public wxFrame
24 {
25 public:
26 MyFrame(const wxString & title,
27 const wxPoint & pos,
28 const wxSize & size);
29
30
31 IMPLEMENT_APP(MyApp)
32
33 bool MyApp::0nlnit()
34 {
35 MyFrame * frame = new MyFrame("Hello World",
36 wxPoint(50, 50),
37 wxSize(200, 100));
38 frame->Show(TRUE);
39 SetTopWindow(frame);
40 return TRUE;
41 }
42
43 MyFrame::MyFrame(const wxString & title,
44 const wxPoint & pos,
45 const wxSize & size) :
46 wxFrame((wxFrame *)NULL, -1, title, pos, size)
47 {}
w x W i n d o w s debe definir una nueva clase der iva da de wxApp. Entonces podemos rede!inir
w xApp : : 0 n l n i t ( ) para i ni ciali/ar el p ro g r a m a c o m o lo h a c e m o s en la línea 20.
La siguiente línea de código, la línea 3 I. puede parecer un poco rara, pero se aclara loque
es al cons ider ar que tocios los p ro g ra ma s de ( ' + + deb e n tener una función main() como
el punt o de entrada del programa. La m ac r o IMPLMENT APP () i mple me nt a a main y crea
una instancia de un objeto aplicación, a d e m á s de e m p e / a r el ciclo principal de eventos
del programa.
Fig u r a 26.2
El prim er programa
GNOMEHelIo World.
P r o g r a m a c ió n d e la GUI 913
P ara hacerla m ás interesante, puede agregarle unas cuantas líneas y colocar algunos botones
que le p erm itan interactuar con ella. En wxWindows, en el marco de trabajo, por lo general
se c o lo c a n botones y otros widgets dentro de un objeto wxPanel; un wxPanel es en esencia
una vvxWiiulovv con un poco más de funcionalidad. Por lo general, estas ventanas residen
en c u a d ro s de d iálo g o , pero com o verá en el siguiente ejemplo, pueden aparecer casi en
c u a lq u ie r parte.
P rim e ro v e a m o s el listado fuente completo para el nuevo código del listado 26.3.
E n tr a d a L is t a d o 2 6 . 3 El p r o g r a m a d e w x W i n d o w s G N O M E H e l l o W o r l d c o n b o t o n e s
L is ta d o 2 6 .3 continuación
77:
78: void MyFrame: :OnQuit(wxCommandEvent & WXUNUSED(event))
79: {
80: Close(TRUE);
81: }
82:
83: void MyFrame::OnGreet (wxCommandEvent & WXUNUSED (event))
84: {
85: wxMessageBox("Ejemplo de Hello World con wxWindows",
86: "Hello World",
87: wxOK | wx IC0N_INFORMATION,
88: this);
89: }
Este código nuevo primero declara dos nuevos identificadores enumerados para identificar
eventos, y luego los agrega a las nuevas funciones miembro que vio declaradas anterior
m ente. En el código se utilizan las macros EVENT_TABLE () para asociarlos identificadores
de eventos con las funciones que manejarán los objetos de eventos creados. La tabla defini
da en las líneas 41 a 44 nos está indicando que la función miembro MyFrame: :0nQuit ()
m anejará los eventos que ocurran con el identificador ID_Quit, y que la función miembro
MyFrame: : OnGreet () manejará los eventos que tengan el identificador ID_Greet.
916 Día 26
A continuación observará que hemos llenado el cuerpo del constructor de MyFrame con
código para inicializar. posicionar y luego desplegar los botones que prometimos incluir
en la aplicación.
La prim era línea del constructor, línea 63 del lisiado, sim plem ente nos proporciona el
tamaño del área cliente del objeto MyFrame; un objeto wxSize representa las dimensiones
x (longitud) y y (altura) del área cliente. Utilizamos este objeto wxSize en la línea 64 para
crear un nuevo wxPanel que llene completamente el área cliente del objeto MyFrame.
Observe que el primer parámetro para el constructor de wxPanel. un apuntador al objeto
wxWindows padre del panel, es this, lo que significa que pertenece al objeto MyFrame. El
constructor predeterminado para la clase wxPanel toma varios parámetros que determinan
en dónde se coloca, cuál es su tamaño, etc. En la línea 64 le damos un identificador pre
determ inado de - l , establecemos su origen en la esquina superior izquierda del objeto
MyFrame (wxPoint (0,0)), y le damos las dimensiones establecidas en panelSize.
También usamos el tamaño del panel para calcular las posiciones de los botones. En este
sentido es código “ frívolo” ya que su función es m eram ente cosmética. Reducimos la
dimensión del objeto panelSize a sus valores componentes, los valores enteros h y wque
se encuentran en las líneas 65 y 66 .
Por último, creamos los botones que ha estado esperando pacientemente. Creamos dos de
ellos y hacemos que pertenezcan al panel que acabamos de crear. Si analiza los argumen
tos para el constructor que estamos pasando, es bastante evidente lo que está ocurriendo.
Por ejemplo, en la línea 69 se da al botón m_btnGreet el identilicador de evento ID_Greet.
Del código anterior, es evidente que este botón generará eventos ID_Greet cuando se
utilice.
También observará que establecimos el texto del botón en los argumentos del constructor.
Los argumentos restantes para el constructor que vemos especifican su posición (que se
determ ina por la altura y el ancho del panel) y su tam año. Observe que aquí tenemos
valores constantes fijos: ésta es, en general, una mala práctica de programación, pero la
utilizamos aquí para que el código sea más legible.
Creamos el boton m_btnQuit en forma muy similar.
La última sección del código implementa los dos manejadores de eventos, OnQuitO y
OnGreet(), en las líneas 78 a 89.
La única cuestión de interés aquí es la función w x M e s s a g e B o x (), que empieza en la línea
85, y que despliega un mensaje dentro de un cuadro que está en la pantalla.
P r o g r a m a c ió n d e la GUI 917
Al e je c u ta r el program a, deberá ver una ventana sencilla (vea la figura 26.3) con
S a l id a el títu lo “ H elio W orld” mostrado a lo largo de la parte superior de la ventana, y
d o s b o to n e s etiq u etad o s com o “Greet” y "Quit” centrados en el área cliente. Hacer clic
en el botón G reet m ostrará un cuadro de diálogo con un saludo impreso, y hacer clic en
Q u it h a rá q u e salga de la ventana.
Fig u r a 2 6 .3
El segundo program a
G N O M EH elloW orld.
con botones.
M a n e jo d e e v e n to s
H a b l a n d o e n g e n e r a l , el u s u a r i o t i e n e d o s m a n e r a s d e in te rac tu ar c o n u n p r o g r a m a d e
G U I : e l r a t ó n y e l t e c l a d o . P a r a a m b a s f o r m a s , u n a inte rfa z grá fic a d e u s u a r io t ie n e q u e
p r o p o r c i o n a r m é t o d o s q u e d e t e c t e n a c c io n e s y m é to d o s q u e h a g a n a lg o c o m o re acció n
a e s t a s a c c io n e s.
918 Día 26
Observe que las señales del contexto de estas discusiones sobre el desarrollo
Nota G U I en G N O M E y K DE no tienen nad a qu e ver con las señales tradicionales
de U N IX, co m o SIG H U P; só lo es una " d e s a fo r t u n a d a " coincidencia de
nom enclatura.
Para hacer que un botón realice una acción, configuramos un manejador de señal para que
capture estas señales y llame a la función apropiada. Esto se logra mediante el uso de una
función como la que se muestra a continuación:
gint gtk_signal_connect(GtkObject *object,
gchar *name,
GtkSignalFunc fuñe,
gpointer func_data);
El primer argumento es el widget que estará emitiendo la señal, y el segundo es el nombre
de la señal que se quiere capturar. El tercero es la función que se quiere llamar cuando
se atrape la señal, y el cuarto son los datos que se quieren pasar a esta función.
P r o g r a m a c ió n d e la GUI 919
En la práctica, eso se vería com o si el control de texto derivado sólo aceptara las letras de
la “ a " a la “z” y de la “ A ” a la “Z ”:
01: v o i d M y T e x t C t r l : : OnChar(wxKeyEvent & event)
02: {
03: if ( i s a l p h a ( event.KeyCode()))
04: {
05: / / E l c ó d ig o de te cla está dentro del rango válido. Llamamos a
06: // e v e n t . S k i p ( ) para que se pueda procesar el evento ya sea en
07: / / l a c l a s e wxWindows base o en el control nativo.
08: e v e n t . S k i p ( );
09: }
10: e ls e
11: {
12: // p u l s a c i ó n de te cla ile g a l. No llamamos a event.Skip() para que
|920 Día 26
E l cam b io de có d ig o que tenem os que hacer es m uy pequeño, así que no tenemos que
incluir todo el archivo fuente: todo lo que necesitamos es agregar algo de código adicional
(vea el listado 2 6 .4 ) en el constructor de MyFrame.
8: //include "wx/wxprec.h ”
9:
10 //ifdef BORLANDC
11 //pragma h d rsto p
12 //endif
13
14 //ifndef WX_PRECOMP
15 //include "wx/wx.h"
16 //endif
17
18
19 c l a s s MyApp: p u b lic wxApp
20 {
21 v i r t u a l bo o l O n I n i t ( ) ;
22 };
23
24 c l a s s MyFrame: p u b lic wxFrame
25 {
26 p u b lic :
27 M yFram e(const w xString & t i t l e ,
28 co n st wxPoint & pos,
29 const vvxSize & siz e );
30 v o id OnQuit(wxCommandEvent & event);
31 v o id OnGreet(wxCommandEvent & event);
32 DECLARE_EVENT_TABLE()
33 p riv a te :
34 wxPanel * m_panel;
35 wxButton * m_btnGreet;
36 wxButton * m_btnQuit;
37 }J
38
39 enum { ID _ Q u it = 1 , ID_Greet };
40
41 BEGIN_EVENT_TABLE(MyFrame, wxFrame)
42 EVT_BUTTON ( ID _ G re e t, MyFrame:: OnGreet)
43 EVT_BUTTON( ID_Quit, MyFrame: :0nQuit)
44 END_EVENT_TABLE ( )
45
46 IMPLEMENT_APP(MyApp)
47
48 b o o l MyApp: : 0 n l n i t ()
49 {
50 MyFrame *frame = new MyFrame("Hello World",
51 wxPoint(50, 50),
52 wxSize(200, 100));
53 frame->Show(TRUE) ;
54 SetTopWindow(frame) ;
55 r e t u r n TRUE;
56 }
57
58 MyFrame: :MyFrame(const wxString & title ,
59 const wxPoint& pos,
60 const wxSize& size) :
61 wxFrame( (wxFrame *)NULL, -1, t i t le , pos, size)
62
63 w x S iz e p a n e lS iz e = G etC lientSize();
64 i n t h = p a n e lS iz e .G e tH e ig h t O ;
65 i n t w = panelSize.G etW idth();
continúa
L is t a d o 2 6 .4 c o n t in u a c ió n
Figura 2 6 .4
El tercer program a
G N O M EH elloW orld.
con m enú y botones.
A
P r o g r a m a c ió n d e la GUI 925
En t r a d a L is t a d o 2 6 . 5 El p r o g r a m a K D E H e llo W o rld
E sta aplicación básica simplemente dibuja una ventana vacía estilo KDE, con
A n á l is is
“ H elio W orld" com o título.
C o m p ile esto s archivos con los siguientes comandos en el indicador del sistema:
g + + -c - I$KDEDIR/include/kde - ISQTDIR/include -fn o -rtti lst26-25.cxx
g++ - LS K D ED IR /l i b -lkdecore -lkdeui -lqt -o \
K D EH elloW orld l s t 2 6 - 0 5 . o
T am b ién pasam os los argum entos del programa, argv y argc, al constructor, y éste los 26
u tiliz a an tes de regresarlos, sin alteraciones, a main().
Llam ar a e x e c ( ) ocasiona que el program a entre al ciclo principal de eventos y que espe
re hasta que éste regrese un entero al sistem a operativo, señalando asi que la aplicación
ha term inado.
Lo principal aquí es que la aplicación entra a un "ciclo principal de eventos”. Esto significa
que el programa tiene que esperar las acciones del usuario \ luego reaccionar a ellas; loque
es más. con una aplicación KDE el program a tiene que esta r en el ciclo principal de
eventos para em pezar el m anejo de los mism os.
La figura 26.5 le m uestra cóm o debe lucir la aplicación KI)L1 IelloWorld al eje
S alida
cutarla. Tenga en cuenta que tal ve/ haya algunas diferencias de tipo cosmético,
pues tal vez usted haya elegido una "com posición " del escritorio o combinación decolo
res diferentes de los que yo tengo.
F i g u r a 26.5
I-7 prim er programa --W Helio World <3> □ X
KDEHelloWorlcl.
C r e a c ió n d e su p r o p ia c la se d e v e n t a n a d e K D E
El ejem plo que acaba de ver es probablemente el program a KDE más simple que se pueda
escribir; perm ítanos extenderlo un poco y derivar nuestra propia clase de ventana de la
clase KTMainWindow.
Ahora podemos cam biar los archivos fuente del proyecto para que contengan el código que
se m uestra en el listado 26.6. Tome en cuenta que no podem os utilizar en forma razonable
un solo archivo fuente, ya que tenemos que ejecutar el M OC (Com pilador de metaobjetos)
de Qt en el archivo de encabezado.
Programación de la GUI 927
Compile estos archivos con los siguientes comandos en el indicador del sistema:
$QTDIR/bin/moc lst26-06.hpp -o lst26-06.moc
g ++ -c - I$KDEDIR/include/kde -ISQTDIR/include -fno-rtti lst26-06.cxx
g ++ -LSKDEDIR/l i b -lkdecore -lkdeui -lqt -o KDEHelloWorld \
l s t 2 6 -0 6 . o
Verá exactamente para qué son estos cambios más adelante en la lección. Por ahora, basta
con saber que debe incluir la macro Q_OBJECT en todas sus clases derivadas de KDE. La
razón de esto es que el MOC (Compilador de metaobjetos) utiliza la macro Q_0BJECT
para crear un archivo .moc que. al ser incluido mediante la instrucción tfinclude, permite
la comunicación entre los widgets de KDE por medio del mecanismo de señales y
ranuras de Qt.
También observará que en las líneas 6 a 9 del listado 2 ó.6 b se implemenla el manejadorde
eventos para el objeto QCloseEvent. Los eventos y los manejadores de eventos son loque
la aplicación KDE utiliza para determinar cómo está interactuando el usuario. En el caso
anterior, QcloseEvent * ocasiona que la aplicación termine. Si quisiéramos, podríamos
agregar código para preguntar al usuario si ésta era la acción deseada, y así sucesivamente.
Vea el recuadro “Los eventos como objetos QEvent”.
La principal diferencia que verá al ejecutar la aplicación es que el título de la ventana ha
cambiado de “KDEHelloWorld” a “Helio World” ya que en este segundo ejemplo
especificamos el título para la aplicación en el constructor de KApplication.
Compile estos archivos con los siguientes comandos en el indicador del sistema:
$QTDIR/bin/moc lst26-07.hpp -o lst26-07.moc
g++ -c -I$KDEDIR/include/kde -ISQTDIR/include/ -fno-rtti lst26-07.cxx
g++ -L$KDEDIR/lib -lkdecore -lkdeui -lqt -o \
KDEHelloWorld lst26-07.o
Ahora puede ver que todo está empezando a movilizarse. Tenemos en el código
A nálisis
varios elementos nuevos que merecen una explicación.
Lo primero y más simple que debe observar es la llamada a KMsgBox::message() en la
línea 32 del listado 26.7b.
Esto coloca en la pantalla un simple cuadro de diálogo predefinido que da algo de informa
ción al usuario; en este caso no dice más que “¡Hola, mundo!”, pero lo puede configurar
para que muestre cualquier mensaje que desee. El primer parámetro de cadena, "Hola
mundo con KDE", establece el título para el cuadro, y el segundo "¡Hola, mundo 1”, es el
mensaje en sí.
Lo segundo y más importante que debe observar es la rara combinación de palabras reser
vadas public s lo ts : en el listado 26.7a. Ésta no es una declaración estándar de C++,
sino que forma parte del mecanismo de señales y ranuras de la biblioteca Qt; en esencia,
le indica al compilador de meta objetos que estas funciones pueden ser llamadas como
Programación de la GUI 931
Fig u r a 2 6 .6
El segundo program o
KD EH elloW orld. con
botones.
Las señales y ranuras son los elementos centrales del marco de trabajo de programación
de KDE y por lo tanto merecen una explicación más detallada.
Las señales y las ranuras pueden tomar cualquier número de argumentos de cualquier tipo.
Ofrecen una completa seguridad de tipos: ¡Al menos nos libramos de los vaciados del
kernel provocados por las callback!
Todas las clases que heredan de QObject o de una de sus subclases (por ejemplo, de
QWidget) pueden contener señales y ranuras. Los objetos emiten señales cuando cambian
su estado en una forma que podría ser interesante para el mundo exterior. Esto es todo lo
que el objeto hace para comunicarse. No sabe si hay algo que reciba la señal en el otro
extremo; además, ni siquiera le importa. Esta es una verdadera encapsulación de datos, y
asegura que se pueda utilizar el objeto como un componente de software.
Las ranuras pueden recibir señales, pero, por lo demás, son como cualquier (unción miem
bro normal. Una ranura no sabe ni le importa si tiene una o más señales conectadas; es
decir, el objeto no sabe nada acerca del mecanismo de comunicación y se puede utilizar
como un verdadero componente de software. Puede conectar tantas señales como desee
en una sola ranura, y puede conectar una señal a todas las ranuras que desee.
Un objeto emitirá una señal cuando su estado interno haya cambiado de alguna forma que
pueda ser relevante para el cliente o el propietario del objeto. Sólo la elase que define una
señal y sus subclases pueden emitir la señal.
Por ejemplo, un widget de cuadro de lista emite las señales highlighted() y activated().
Probablemente, la mayoría de los objetos sólo estarían interesados en activated(), pero
tal vez algunos querrían saber cuál elemento del cuadro de lista está resaltado actualmente.
Si la señal es relevante para dos objetos diferentes, puede conectarla en las ranuras de
ambos objetos.
Cuando un objeto emite una señal, las ranuras conectadas a éste se ejecutan igual que una
llamada normal a una función. El mecanismo de señales y ranuras es totalmente indepen
diente de cualquier ciclo de eventos de la GUI. La emisión terminará cuando todas las
ranuras hayan terminado; si varias ranuras están conectadas a una señal, éstas se ejecutarán
una después de la otra, en orden arbitrario, cuando se emita la señal.
El MOC genera automáticamente el código que emite señales y usted no debe implementar-
las por su cuenta en el archivo .cxx. Todas las señales tienen el tipo de valor de retomo
void . Usted, el programador de la aplicación, tiene el trabajo de implementar las ranuras
por su propia cuenta.
Como las ranuras son funciones miembro normales que se pueden invocar en formas
misteriosas de las que no necesitamos preocuparnos aquí, tienen derechos de acceso al
igual que cualquier otro miembro de la clase. No es sorprendente que el derecho de acceso
de una ranura determine quién puede conectarse a ella:
• Una sección public s lo ts : contiene ranuras a las que cualquiera puede conectar
señales. Usted crea objetos que no saben nada unos acerca de otros, conecta sus seña
les y ranuras, y pasa información entre ellos.
h.
Programación de la GUI 933
• Una sección protected slots: contiene ranuras a las que esta clase y sus subclases
pueden conectar señales. Esto se utiliza en las ranuras que son parte de la imple-
mentación de la clase, en lugar de su interfaz public externa.
• Una sección private slo ts: contiene ranuras a las que sólo los objetos instanciados
de esa misma clase pueden conectar señales. Esto se utiliza en clases conectadas en
forma muy estrecha, en donde ni siquiera se confía que las subclases hagan bien las
conexiones.
Desde luego que también puede definir las ranuras como virtuales. Esta característica es
muy útil.
Como ejemplo de una implementación típica de señales y ranuras, considere la siguiente
declaración mínima de una clase de C++:
01: class MiClase
02: {
03: public:
04: MiClase();
05: char Letter() const { return m_cVal; }
06: void SetValue(charcVal);
07: private:
08: char m_cVal;
09:};
Todas las clases que contienen señales, ranuras o ambas, también deben contener la macro
Q_OBJECT en su declaración.
934 Día 26
La línea 6 emite la señal LetterChanged () del objeto. Como puede ver, se emite una señal
usando la llamada a emit señal (argumentos) .
Si quisiéramos conectar dos instancias del objeto MiClase, podríamos escribir lo siguiente:
01: int main(int argc, char** argv)
02: {
03: MiClaseQt Alice, Bob;
04: connect(&Alice, SIGNAL(LetterChanged(char)), &Bob, SLOT(SetLetter(
•char)));
05: Bob.SetLetter('a*);
06: Alice.SetLetter('b');
07: Bob.Letter(); // ¿ qué seria esto ahora? 'b', desde luego
08: return 0;
09: }
Llamar a A lic e . SetLetter( ’b ') hará que Alice emita una señal, la cual será recibida por
Bob; es decir, invoca a Bob.SetLetter(' b' ). A su vez, Bob emitirá la misma señal, la cual
nadie recibe ni le presta atención ya que no le hemos conectado una ranura. Por lo tanto,
la señal emitida por Bob se desvanece para no ser vista nunca más.
Una analogía muy parecida en el mundo de las funciones callback sería un apuntador
NULL a una callback (lo que, por lo general, produciría un vaciado del kemel y un pro
gramador deprimido).
Observe que la función SetLetterQ establece el valor y emite la señal sólo si cVal
*= m_cVal. Esto previene ciclos infinitos en el programa donde, por ejemplo, Bob. •
LetterChanged() estaba conectada con Alice.SetLetter() .
Las señales y ranuras son eficientes en una forma razonable, pero son nominalmente más
lentas que las “verdaderas” funciones callback debido a la naturaleza flexible y dividida
en niveles de la llamada; sin embargo, la perdida es aceptablemente pequeña para todas
las aplicaciones, excepto las que requieran de un uso más crítico del tiempo.
Para implementar el mecanismo de señales y ranuras de Qt, tiene que compilar el código
fuente con el MOC (Compilador de metaobjetos), el cual forma parte de la biblioteca Qt.
El MOC analiza sintácticamente la declaración de la clase de un archivo de C++ y genera
código de C++ que inicializa el metaobjeto. Éste contiene los nombres de todos los
miembros de señales y ranuras, así como de apuntadores a estas funciones. El preproce-
sador se encarga en forma transparente de las palabras reservadas signal, slot y emit,
para que el compilador de C++ no vea algo que no pueda digerir.
Programación de la GUI 935 |
L is ta d o 2 6 .8 b c o n t in u a c ió n
14: SLOT(SlotGreet()));
15:
16: m_btnQuit = new QPushButton("Quit", this);
17: m_btnQuit->setGeometry(105, 30, 50, 20);
18: m_btnQuit->show();
19: connect(m_btnQuit,
20: SIGNAL(clicked()),
21 : this,
22: SLOT(SlotQuit()));
23:
24: m_MenuApp = new QPopupMenu();
25: m_MenuApp->insertItem(“&nGreet",
26: this,
27: SLOT(SlotGreet())) ;
28: m_MenuApp->insertItem("&Quit",
29: this,
30: SLOT(SlotQuit())) ;
31: m_Menu = new KMenuBar(this);
32: m_Menu->insertltem(“&Application", m_MenuApp);
33: }
34:
35: void KDEHelloWorld::closeEvent(QCloseEvent *)
36: {
37: kapp->quit();
38: }
39:
40: void KDEHelloWorld::SlotGreet()
41: {
42: KMsgBox::message(0,"Hello World con KDE","Hello World");
43: }
44:
45: void KDEHelloWorld::SlotQuit()
46: {
47: close();
48: }
49:
50: int main(int argc, char ** argv)
51: {
52: KApplication MyApp(argc, argv, "Hello World");
53: KDEHelloWorld * MyWindow = new KDEHelloWorld();
54:
55: MyWindow ->setGeometry(50, 100, 200, 100);
56: MyApp.setMainWidget(MyWindow);
57: MyWindow ->show();
58: return MyApp.exec();
59: }
60:
Compile estos archivos con los siguientes comandos en el indicador del sistema:
g++ -c -I$KDEDIR/include/kde/ -ISQTDIR/include/ -fno-rtti main.cxx
SQTDIR/bin/moc lst26-08.hpp -o lst26-08.moc
Programación de la GUI
L o p rim ero que debe observar son las variables miembro adicionales que repre
A nálisis
sen tan los objetos del menú que hemos agregado a la clase KDEHelloWorld en las
lín eas 21 y 22 del listado 26.8a.
L os n o m b res de las variables se explican por sí solos, al igual que el código de inicializa-
ció n en el c o n stru c to r KDEHelloWorld que abarca las líneas 24 a 32 del listado 26.8b.
Al ejecutar este programa, deberá ver algo parecido a la pantalla que se muestra en
S a l id a
la fis u ra 26.7.
Fig u r a 2 6 . 7
El tercer pro^ra/iui
KD EH elloW orld. con
- -wEiisjaniH ■ i x
menú v botones. Application
Greet Quit
Al momento de escribir esto, y debido a que KDevelop 1.3 utiliza KDE 2.0, me estoy
refiriendo al estado de las bibliotecas de KDE en relación con esa liberación. Las principales
bibliotecas de KDE que usted utilizará para crear sus propias aplicaciones KDE son
• La biblioteca Core de KDE, que contiene todas las clases que son elementos no
visibles y que proporcionan la funcionalidad que su aplicación puede utilizar.
• La biblioteca Ul de KDE, que contiene elementos de interfaz de usuario, como
barras de menús, barras de herramientas y demás cosas relacionadas.
• La biblioteca KFile, que contiene los cuadros de diálogo de selección de archivos.
Además, KDE ofrece las siguientes bibliotecas para soluciones específicas:
Programación de la GUI 939
Resumen
Finalmente Linux ha crecido. Los dos nuevos escritorios que ha visto hoy, GNOME y KDE,
así como sus bibliotecas y aplicaciones de soporte, le proporcionan una selección de GUIs
gratuitas, modernas, intuitivas y robustas para el sistema operativo Linux. También le
proporcionan un conjunto de poderosos marcos de trabajo de desarrollo, entornos y he
rramientas que eliminan el trabajo pesado de la escritura de aplicaciones de GUI en X y
le permiten concentrarse en lo que su aplicación debe estar haciendo, en lugar de involu
crarlo en una lucha que no puede ganar contra una API intratable.
Con las nuevas interfaces GUI para Linux, tiene una opción genuina, y eso sólo puede ser
bueno para todos, ya que se verá una notable mejora en la calidad y el precio (especialmente
porque los escritorios Linux son gratuitos) en todo el mercado de sistemas operativos.
Todos los paquetes y bibliotecas que he descrito en esta sección están siendo extendidos
y mejorados casi a diario por voluntarios, y están apareciendo nuevos casi con la misma
frecuencia.
Sus esfuerzos y habilidad han colocado firmemente a Linux donde pertenece; en el escritorio.
Preguntas y respuestas
P ¿Para qué necesitamos KDE y GNOME?
R La razón es que Linux, a diferencia de Windows y MacOS, no tiene una GUI nativa:
usted no puede interactuar con ella por medio de comandos textuales. La computadora
en sí tiene capacidad para gráficos (por lo general), pero Linux por sí solo no sabe
cómo manejar esta capacidad. GNOME y KDE le proporcionan un marco de trabajo
para que interactúe con la computadora en forma gráfica, y para que sus comandos
se dirijan en forma transparente al sistema operativo. En realidad, no necesitamos
ambos: son lo suficientemente distintos como para coexistir, y el tiempo dirá cuál de
los dos se hará más popular.
|940 Día 26
Taller
El taller le proporciona un cuestionario para ayudarlo a afianzar su comprensión del mate
rial tratado, así como ejercicios para que experimente con lo que ha aprendido. Trate de
responder el cuestionario y los ejercicios antes de ver las respuestas en el apéndice D,
“Respuestas a los cuestionarios y ejercicios”, y asegúrese de comprender las respuestas
antes de pasar al siguiente día.
Cuestionario
1. ¿Cuál es la diferencia entre programas controlados por eventos y programas controla
dos por procedimientos?
2. ¿Qué es un widget, en términos computacionales?
3. ¿Qué son las funciones callback, y por qué son propensas a errores?
4. ¿Qué es una ranura Qt, y cómo reacciona a las señales Qt?
5. Las señales y las ranuras ofrecen seguridad de tipos. ¿Qué significa esto y por qué
es algo bueno?
6 . ¿Cómo enlazaría el siguiente manejador de eventos de wxWindows a un evento
EVT_MENU desde un elemento de menú con el identificador de ID_MY_HELP?
Asuma que OnHelp () es miembro de la clase MyFrame, y que MyFrame se deriva de
wxFrame.
void OnHelp(wxCommandEvent & WXUNUSED(event));
7. ¿Qué hace la macro IMPLEMENT_APP()?
8 . ¿Qué hace la macro Q_OBJECT?
Ejercicios
1 . Usando el archivo fuente lst26 -01. cxx como base, extienda el programa para que
la clase callback muestre un cuadro de diálogo al hacer clic en los botones, en lugar
de escribir directamente en stdout.
2. Tomando como base el ejemplo final de wxWindows o el de KDE, extienda el código
para que despliegue un botón de exploración de archivos que le permita seleccionar
un archivo por medio de un cuadro de diálogo estándar de selección de archivos.
3. Extienda el código del ejercicio 2 de forma que, al hacer clic en OK y seleccionar
el archivo, el programa lo despliegue en un control de texto en la ventana principal.
4. Analice los resultados del ejercicio 1 y considere cómo podría envolver toda la
aplicación en una sola clase. Considere por qué podría necesitar implementar las
funciones callback como miembros estáticos privados de la clase, y exponer sólo
“envolturas” que las llamen cuando el usuario active eventos. Vea por ejemplo la
función do_message( ) del archivo lst26-01.cxx. Piense cómo podría extender el
concepto para crear una envoltura genérica para la aplicación, y utilizar funciones
virtuales para configurar la ventana principal y conectar señales.
r
S eíwiana 4
Repaso
¡Debe estar orgulloso de usted mismo! No sólo completó
los 21 días para aprender C++ para Linux, sino que también
completó los cinco días de la semana adicional. Esa semana
cubrió los siguientes temas:
• El entorno de programación de Linux
• Programación shell
• Programación de sistemas
• Comunicación entre procesos
• Programación de la GUI
Ciertamente, estas lecciones sólo le presentaron una introduc
ción a estos temas. Hay mucho más que usted debe aprender
acerca de estos temas y del lenguaje C++. La mejor forma
de aprender es practicando. Escriba programas, pruebe he
rramientas y trabaje con varias bibliotecas de funciones y
objetos.
¡Buena suerte!
¿
A p é n d ic e
Precedencia de operadores
Es importante comprender que los operadores tienen una precedencia, pero no es
esencial memori/ai la.
La p reced e n cia es el orden en el que un programa realiza las operaciones de una
íórmula matemática. Si un operador tiene precedencia sobre otro operador, se
evalúa primero.
Los operadores con mayor precedencia tienen un “lazo más estrecho” que los
operadores con menor precedencia; por ende, los operadores con mayor preceden
cia se evalúan primero. Entre menor sea el rango en la tabla A.l, será menor la
precedencia.
946 Apéndice A
A
A
V
V
8
II
D esig u a ld a d relaciona!
II
9 Igualdad, d esig u a ld a d == i =
10 A N D a n iv el de bits &
ll O R e x c lu s iv o a n iv e l d e b its (X O R ) *
12 O R a n iv el d e bits I
13 L ó g ic o A N D &&
14 L ó g ic o O R II
I5 C o n d ic io n a l ?:
16 O p eradores d e a sig n a ció n = *= /= %= += .= « =
>>=
&= |= *=
17 C om a
A p é n d ic e
Palabras reservadas
de C ++
El compilador utiliza las palabras reservadas (también llamadas palabras clave)
como parte del lenguaje de C++. No puede definir clases, variables o funciones
que tengan como nombre estas palabras reservadas. La lista es un poco arbitraria,
ya que contiene las palabras reservadas estándar, así como algunas de las palabras
reservadas que son específicas de g++. Algunas de las palabras reservadas no
están disponibles en todos los compiladores o en versiones anteriores del com
pilador g++. Puede haber ligeras variaciones.
948 Apéndice B
E n e l s i s t e m a n u m é r ic o c o n b a s e 1 0 ( d e c i m a l ) , s e u t i l i / a n lo s n ú m e r o s 0 . 1. 2 . 3 , 4, 5, 6,
7. 8 y 9 p a r a r e p r e s e n ta r a t o d o s l o s n ú m e r o s . ¿ C ó m o s e r e p r e s e n ta e l n ú m e r o d iez?
I m a g i n e m o s q u e h u b ié r a m o s p o d i d o d e s a r r o l l a r u n a e s t r a t e g i a p a r a u tiliz a r la letra A
p a r a r e p r e s e n ta r d ie z ; o h u b ié r a m o s p o d id o u sa r 1111111111 p a ra r e p r e s e n ta r e l co n ce p to . Los
r o m a n o s u tiliz a b a n X . E l s is t e m a a r á b ig o , q u e e s e l q u e u t il iz a m o s , u tiliz a la p o sició n junto
c o n l o s n ú m e r o s p a ra r e p r e s e n ta r l o s v a lo r e s . L a p r im e r a c o l u m n a ( la q u e s e encuentra a
la d e r e c h a ) s e u tiliz a p a ra la s “ u n id a d e s ” , y la s i g u i e n t e c o l u m n a ( h a c ia la izq u ierda) se
u t i l i z a p a r a la s “ d e c e n a s ” . P o r lo t a n t o , e l n ú m e r o q u i n c e s e r e p r e s e n ta c o m o 15 (se lee
“ u n o , c i n c o ” ); e s d e c ir , u n a d e c e n a ( 1 0 ) y c i n c o u n id a d e s ( 5) .
S u r g e n c ie r ta s r e g la s , d e la s q u e s e p u e d e n h a c e r a lg u n a s g e n e r a liz a c i o n e s :
1. L a b a s e 10 u t iliz a lo s d í g i t o s d e l 0 al 9 .
2. L a s c o l u m n a s s o n p o t e n c ia s d e d ie z : u n id a d e s , d e c e n a s , c e n t e n a s , y a s í su c e siv a
m e n te .
La tendencia natural es formar dos grupos: un grupo de diez asteriscos y otro de cinco.
Esto se representaría en decimal como 15 (una decena y cinco unidades). También puede
agrupar los asteriscos como
**** *******
** * *
Es decir, un grupo de ocho asteriscos y otro de siete. En base 8, esto se representaría como
17h. E s decir, un 8 y siete unidades.
343 49 7 1
1296 216 36 6 1
952 Apéndice C
No hay 1296s en 968, así que la columna 5 liene 0. Dividir 968 entre 216 da como resultado
4 con un residuo de 104. La columna 4 tiene 4. Dividir 104 entre 36 da como resultado 2
con un residuo de 32. La columna 3 tiene 2. Dividir 32 entre 6 da como resultado 5 con un
residuo de 2. Por lo tanto, la respuesta es 4252, .
5 4 3 2 1
64 6? 62 6' 6«
1296 216 36 6 1
0 4 2 5 2
Hay un método más sencillo para convertir de una base a otra (por ejemplo, de base 6 a
base 10). Puede multiplicar:
4 * 216 = 864
2 * 36 = 72
5*6 30
2*1 2
968
Números binarios
La base 2 es la extensión definitiva de este concepto. Sólo hay dos dígitos: 0 y 1. Las
columnas son:
Col: 8 7 6 5 4 3 2 1
Potencia: 27 26 25 24 23 22 2' 2°
Valor: 128 64 32 16 8 4 2 1
Para convertir el número 88 a base 2, se sigue el mismo procedimiento: no hay 128s, por
lo que la columna 8 es 0 .
Hay un 64 en 88 , por lo que la columna 7 es 1 y el residuo es 24. No hay 32s en 24, por lo
que la columna 6 es 0 .
Hay un 16 en 24, por lo que la columna 5 es 1. El residuo es 8 . Hay un 8 en 8, así que la
columna 4 es 1. No hay residuo, por lo que las columnas restantes son 0.
0 0 0 0 0
Números binarios, octales, hexadécimales y una tabla de valores ASCII 953
Para convertir de binario a decimal, sólo necesita seguir el método utilizado para volver a
comprobar nuestra conversión de decimal a binario, es decir, multiplicar el resultado binario
por el valor de las columnas respectivas.
Con 8 dígitos binarios se pueden representar hasta 256 valores distintos. ¿Porqué?Examine
las columnas: si los 8 bits están encendidos (1). el valor es 255. Si ninguno está encendido
(todos los bits están apagados o valen cero), el valor es 0. El rango 0-255 le proporciona
256 posibles estados.
Qué es un KB
Resulta que 2 10 (1.024) es aproximadamente igual a 10' ( 1 .0 0 0 ). Esta coincidencia era
demasiado buena como para pasarla por alto, por lo que los científicos de la computación
empezaron a referirse a 21" bytes como 1 KB o I kilobyte. basándose en el prefijo científico
de kilo para representar mil.
De la misma manera, 1024 * 1024 (1.048.576) se encuentra lo suficientemente cerca de un
millón para recibir la designación de 1 MB o de I megahyte. y 1,024 megabytes se conocen
como 1 gigabyte (giga implica mil millones).
Números binarios
Las computadoras utilizan combinaciones de Is y Os para codificar todo lo que hacen. Las
instrucciones en lenguaje de máquina se codifican como una serie de ls y Os y son interpre
tadas por los circuitos fundamentales. Los científicos de la computación pueden convertir
otra vez en números estos conjuntos arbitrarios de ls y Os. pero sería un error pensar que
estos números tienen un significado intrínseco.
Por ejemplo, el juego de chips Intel 80 x 86 interpreta la combinación de bits 1001 0101
como una instrucción. Evidentemente, este valor se puede convertir a decimal (149), pero
ese número no tiene significado por sí mismo.
Algunas veces los números son instrucciones, otras veces son valores, y algunas otras son
códigos. ASCII (Código Estándar Estadounidense para el Intercambio de Información) es
un importante conjunto de código estandarizado. En ASCII, cada letra y carácter de puntua
ción tiene una representación de 7 dígitos binarios. Por ejemplo, la letra “a” minúscula se
representa con 0 110 0 0 0 1 . Éste no es un número, aunque puede convertirlo en el número
97 (64 + 32 + 1 ). Por esta razón, se dice que en ASCII la letra “a” está representada por
el 97; pero lo cierto es que la representación binaria de 97, 01100001, es la codificación
de la letra “a”, y el valor decimal 97 es una conveniencia humana.
Números octales
Debido a que los números binarios son difíciles de leer, buscamos una manera más sencilla
de representar los mismos valores. Traducir de binario a base 10 implica una manipu
lación de números bastante compleja; es más sencillo traducir de octal a base 10 .
Para comprender esto, primero debe comprender la base 8 , conocida como octal. En base
8 hay ocho números: 0, 1,2, 3, 4, 5, 6 y 7. Las columnas en octal son:
Números binarios, octales, hexadécimales y una tabla de valores ASCII
Col: 5 4 3 2 1
Potencia: 8J 8* 82 81 80
Valor: 4096 512 64 8 1
Para convertir el número 88 a octal, se siguen los mismos procedimientos que con los núme
ros binarios: no hay 4096s ni 5 12s, por lo que las columnas 5 y 4 son 0, respectivamente.
Hay un 64 en el 88 , por lo que la columna 3 es 1 con 24 como residuo. Hay tres ochos
en 24, por lo que la columna 2 es 3 con un residuo de cero (por lo que la columna
restante es O).
0 0
Para convertir de octal a decimal sólo hay que seguir el método utilizado para comprobar
nuestra conversión de decimal a octal, es decir, multiplicar el resultado en octal por el valor
de las columnas respectivas.
Números hexadecimales
Como los números binarios son difíciles de leer, necesitamos una manera más sencilla de
representar los mismos valores. La traducción de binario a base 10 requiere una manipula
ción bastante compleja de números; pero resulta que traducir de base 2 a base 16 es muy
sencillo, pues existe un muy buen atajo.
Para comprender esto, primero debe comprender lo que es la base 16, conocida como
hexadecimal. En base 16 hay dieciséis números: 0, 1,2, 3,4, 5, 6, 7, 8, 9, A, B, C, D, E
y F. Los últimos seis son arbitrarios; se eligieron las letras de la A a la F ya que son fáciles
de representar en un teclado. Las columnas en hexadecimal son:
|956 Apéndice C
4 3 2 1
16? 162 16' 16'
4096 256 16 1
Para convertir de hexadecimal a decimal se puede multiplicar. Por ejemplo, el número F8C
representa:
F * 256 = 15 * 256 = 3840
8 * 16 = 128
C * 1 = 12 * 1 = 12
3980
Si se colocan los dos números hexadecimales se forma FC, que es el valor real de l i l i
1100. Este atajo siempre funciona. Puede tomar cualquier número binario de cualquier
longitud, y reducirlo en conjuntos de 4, convertir cada conjunto de 4 a hexadecimal y
colocar los números hexadecimales juntos para obtener el resultado en hexadecimal. He
aquí un número más grande:
1011 0001 1101 0111
Las columnas son 1, 2. 4. 8 , 16. 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384 y
32768.
1 X 1 = 1
1 X 2= 2
1 X 4 = 4
0 X 8 = 0
1 X 16 = 16
0 X 32 = 0
1 X 64 = 64
1 X 128 = 128
1 X 256 = 256
0 X 512 = 0
0 X 1024 = 0
0 X 2048 = 0
1 X 4096 = 4,096
1 X 8192 = 8,192
0 X 16384 = 0
1 X 32768 = 32,768
Total: 45,527
Para convertir esto a hexadecimal se requiere una tabla con los valores hexadecimales.
65536 4096 256 16 1
No hay 65,536s en 45,527, por lo que la primera columna es 4096. Hay once 4096s
(45,056), con un residuo de 471. Hay un 256 en 471 con un residuo de 215. Hay trece
16s (208) en 215 con un residuo de 7. Por lo tanto, el número hexadecimal es B1D7.
Comprobemos las matemáticas:
B (11) * 4096 = 45,056
1 * 256 = 256
D (13) * 16 = 208
7 * 1 = 7
Total 45,527
958 Apéndice C
La versión con atajo sería tomar el número binario original. 1011(MK)111010111, y dividirlo
en grupos de 4: 1011 0001 1101 0111. Cada uno de los cuatro se evalúa como número
hexadecimal:
10 11 =
1x1= 1
1 x 2 = 2
0x4= 0
1 x 8 = 8
Total 11
Hex: B
0001 =
1 x 1 = 1
0 x 2 = 0
0x4= 0
0 * 8 = 0
Total 1
Hex: 1
1101 =
1x1= 1
0x2= 0
1x4= 4
1x8= 8
Total 13
Hex = D
0 111 =
1x1= 1
1x2= 2
1x4= 4
0 x 8 = 0
Total 7
Hex: 7
El sistema hexadecimal es muy popular en los sistemas con múltiplos pares de 4 bits. En
la actualidad, la mayoría de los sistemas entra en esta categoría: 16, 32 y 64. Para cuando
este libro se imprima, probablemente habrá sistemas de 128 bits en el mercado.
ASCII
ASCII es la representación de caracteres más utilizada actualmente. La tabla C.l muestra
valores binarios, octales, decimales hexadécimales y sus equivalentes en ASCII.
N ú m e ro s b in a rio s , octales, hexadécimales y una ta b la de valores ASCII 959
continúa
960 Apéndice C
Ta b la C .1 c o n t in u a c ió n
00100110 46 26 38 &
00100111 47 27 39
00101000 50 28 40 (
00101001 51 29 41 )
00101010 52 2a 42
00101011 53 2b 43 +
00101100 54 2c 44
00101101 55 2d 45 -
00101110 56 2e 46
00101111 57 2f 47 /
00110000 60 30 48 0
00110001 61 31 49 1
00110010 62 32 50
00110011 63 33 51 3
00110100 64 34 52 4
00110101 65 35 53 5
00110110 66 36 54 6
00110111 67 37 55 7
00111000 70 38 56 8
00111001 71 39 57 9
00111010 72 3a 58
00111011 73 3b 59
00111100 74 3c 60 <
00 1 II101 75 3d 61 =
0 01I1110 76 3e 62 >
Números binarios, octales, hexadécimales y una tabla de valores ASCII 961
continúa
962 Apéndice C
Ta b la C .1 c o n t in u a c ió n
Los valores del 0 al I27 (decimal) son parle del estándar ASCII. Algunos sistemas soportan
lo que se conoce como “ASCII extendido” (128 a 255) para caracteres imprimibles especia
les. Todas las entradas ASCII de la tabla C.l que se muestran encerradas entre el signo
mayor que (<) y el signo menor que (>) (como <espacio>) son caracteres especiales o son
difíciles de representar al imprimirlos.
Existen otros conjuntos de caracteres además de ASCII: EBCDIC y Unicode son los más
comunes. EBCDIC (Código Extendido de Caracteres Decimales Codificados en Binario
para el Intercambio de Información) es el conjunto de caracteres utilizado en mainframes
de IBM. Unicode es un nuevo estándar que permite la representación de caracteres en la
mayoría de los lenguajes del mundo; ASCII es un subconjunto de Unicode.
El listado C. I muestra el programa que produjo la tabla C.l (exceptuando los caracteres
especiales encerrados entre los signos < y >, como <espacio>; tuve que editarlos manual
mente).
continúa
1964 Apéndice C
L is t a d o C . 1 co n tin u a ció n
32: }
33: resultados! 8 ) = '\0';
34:
35: return;
36: }
En este caso opté por tener un número de 8 posiciones (8 bits) con la posición de valor más
alto en primer lugar.
Este es un buen ejemplo de algunos de los trucos que C y C++ le permiten hacer. El ejemplo
del listado C.l es difícil de leer, pero la conversión de números en sí puede ser compleja.
A p é n d ic e
Respuestas a los
cuestionarios y ejercicios
Día 1
C u e s t io n a r io
1. ¿Cuál es la diferencia entre un intérprete y un compilador?
Los intérpretes leen el código fuente y traducen un programa, convirtien
do el código del programador, o instrucciones del programa, directamente
en acciones. Los compiladores convierten el código fuente en un progra
ma ejecutable que puede ejecutarse posteriormente.
2. ¿Cóm o compila el código fuente con su compilador?
Cada compilador es distinto. Asegúrese de revisar la documentación
incluida con su compilador. Con GNU se pueden utilizar los comandos
man (manual) o info para ver la documentación.
|966 Apéndice D
Ejercicios
1. Inicializa dos variables de tipo entero y luego imprime su suma y su producto.
2. Vea la documentación del compilador de GNU (o el día 2. "Los componentes de
un programa de C++”).
3. Debe colocar el símbolo # antes de la palabra inelude en la primera línea.
4. Este programa imprime las palabras "iHola, mundo! “ en la pantalla, seguidas de
un carácter de nueva línea (retorno de carro).
Día 2
Cuestionario
1. ¿Cuál es la diferencia entre el compilador y el preprocesador?
Cada vez que ejecuta su compilador, el preprocesador se ejecuta primero. Lee su
código fuente e incluye los archivos que usted pide, y realiza otras tareas de man
tenimiento. El preprocesador se describe detalladamente en el día 18, "Análisis y
diseño orientados a objetos”.
2. ¿Por qué es especial la función main () ?
main() se llama automáticamente cada vez que se ejecuta su programa.
3. ¿Cuáles son los dos tipos de comentarios, y en qué se diferencian?
Los comentarios estilo C++ empiezan con dos barras diagonales (//), y convierten
en comentario cualquier texto hasta llegar al final de la línea. Los comentarios estilo
C vienen en pares (/* */), y todo lo que se encuentre entre estos símbolos se con
vierte en comentario. Debe asegurarse de que haya pares relacionados.
4. ¿Se pueden anidar los comentarios?
Sí, los comentarios estilo C++ pueden anidarse dentro de comentarios estilo C. De
hecho, se pueden anidar comentarios estilo C dentro de comentarios estilo C++,
siempre y cuando recuerde que éstos terminan al final de la línea. La excepción es
que no se pueden anidar comentarios estilo C. Si usted crea un comentario estilo
/*--------- / * ---------- * / ------- */, el primer cierre de comentarios (*/) cierra todo el
comentario y obtendrá un error por los siguientes cierres de comentario. Esto se
debe a que el compilador no crea una pila para los delimitadores de comentarios.
Respuestas a los cuestionarios y ejercicios 9671
Ejercicios
1. Escriba un programa que imprima en la pantalla el mensaje “Me gusta C++”.
1 : tfinclude <iostream.h>
2:
3: int main()
4: {
5: cout « "Me gusta C++";
6: return 0;
7: }
2. Escriba el programa más pequeño que se pueda compilar, enlazar y ejecutar,
int main(){ return 0; >
3. CAZA ERRORES: Escriba este programa y compílelo. ¿Por qué falla? ¿Cómo
puede arreglarlo?
1 : tfinclude <iostream.h>
2: int main()
3: {
4: cout « ¿Hay un error aquí?";
5: return 0;
6: }
En la línea 4 falta un par de comillas al principio de la cadena.
4. Encuentre el error del ejercicio 3 y vuelva a compilar, enlazar y ejecutar el programa.
1: #include <iostream.h>
2: main()
3: {
4: cout « "¿Hay un error aquí?";
5: >
Día 3
C u e stio n ario
1. ¿Cuál es la diferencia entre una variable de tipo entero y una de punto flotante?
Las variables de tipo entero son números enteros; las variables de punto flotante son
decimales y tienen un punto decimal “flotante”. Los números de punto flotante se
representan por medio de una mantisa y un exponente. El exponente indicará el
lugar en el que se debe colocar el punto, de aquí el nombre de punto flotante.
968 Apéndice D
2. ¿Cuáles son las diferencias entre un entero corto sin signo y un entero largo?
La palabra reservada unsigned significa que el valor entero sólo guardará números
positivos. En la mayoría de las computadoras, los enteros cortos son de 2 bytes y
los enteros largos son de 4. Pero en general, una variable de tipo long tiene el
doble de capacidad que una variable de tipo short.
3. ¿Cuáles son las ventajas de usar una constante simbólica en lugar de una constante
literal?
Una constante simbólica se explica a sí misma; el nombre de la constante indica su
función. Además, las constantes simbólicas pueden redefinirse en una sola ubica
ción en el código fuente, en lugar de que el programador tenga que editar el código
en cualquier parte en que se utilice la literal.
4. ¿Cuáles son las ventajas de usar la palabra reservada const en lugar de #define?
Las variabes const están “tipificadas”; por lo tanto, el compilador puede comprobar
que no haya errores en la forma en que se utilizan. Además, sobreviven al prepro
cesador; por lo tanto, el nombre está disponible en el depurador.
5. ¿Qué hace que el nombre de una variable sea bueno o malo?
Un buen nombre de variable indica para lo que sirve la variable; un nombre malo
no da información. miEdad y PersonasEnElAutobus son buenos nombres de varia
bles, pero xjk y prndl probablemente son menos útiles.
6. Dado el siguiente enum, ¿cuál es el valor de Azul?
enum COLOR { BLANCO, NEGRO = 1 0 0 , ROJO, AZUL, VERDE = 300 };
AZUL = 102
7. ¿Cuáles de los siguientes nombres de variables son buenos, cuáles son malos y
cuáles no son válidos?
a. Edad
Bueno
b. !ex
No es válido
c. R79J
Ejercicios
1. Cuál sería el tipo de variable correcto para guardar la siguiente información?
Respuestas a los cuestionarios y ejercicios 969
a. Su edad.
Entero de tipo unsigned short
h. El área de su patio.
Entero de tipo unsigned long o flotante de tipo unsigned float
c. El número de estrellas en la galaxia,
u nsigned double
d. La cantidad promedio de lluvia para el mes de enero.
Entero de tipo unsigned short
2. Cree nombres buenos de variables para la información de la pregunta 1.
a. miEdad
b. a r e a Patio
c. EstrellasEnGalaxia
d. lluviaPromedio
3. Declare una constante para pi como 3.14159.
const float PI = 3.14159;
4. Declare una variable de tipo float e inicialícela usando su constante pi.
float miPi = Pi;
Día 4
C u e stio n ario
1. ¿Qué es una expresión?
Cualquier instrucción que regrese un valor.
2. ¿Es x = 5 + 7 una expresión? ¿Cuál es su valor?
Sí. 12
3. ¿Cuál es el valor de 201 / 4?
50
4. ¿Cuál es el valor de 201 % 4?
1
5. Si miEdad, a y b son variables de tipo int, ¿cuáles son sus valores después de
ejecutar las siguientes instrucciones?
miEdad = 39;
a = miEdad++;
b = ++miEdad;
Ejercicios
1. Escriba una instrucción i f sencilla que examine dos v, riabl :s de tipo entero y que
cambie la más grande a la más pequeña, usando sólo un¿ cláusula else.
if (x > y)
x = y;
else // y > x ¡¡ y == x
y = x;
2. Examine el siguiente programa. Imagine que escribe tres númt.1“ y escriba la sa
lida que espera obtener.
1: #include <iostream.h>
2: int main()
3: {
4: int a, b, c¡
5: cout « "Escriba tres números\n";
6: cout « "a:
7: cin » a;
8: cout « "\nb: ";
9: cin » b;
10: cout « "\nc: ";
11: cin » c;
12:
13: if (c = (a -b ))
14: {
15: cout « "a:
16: cout « a;
Respuestas a los cuestionarios y ejercicios 971
Día 5
C u e stio n ario
1. ¿Cuáles son las diferencias entre el prototipo de la función y la definición de la
función?
El prototipo de la función declara a la función; la definición contiene la implemen-
tación. El prototipo termina con punto y coma; la definición no lo necesita. La
declaración puede incluir la palabra reservada inline y valores predeterminados para
los parámetros; la definición no. La declaración no necesita incluir nombres para los
parámetros; la definición debe incluirlos.
2. ¿Tienen que concordar los nombres de los parámetros en el prototipo, la definición
y la llamada a la función?
No. Todos los parámetros se identifican por su posición, no por su nombre.
972 Apéndice D
Ejercicios
1. Escriba el prototipo para una función de nombre Perimetro( ) la cual, regresa un
valor de tipo unsigned long int y acepta dos parámetros, ambos de tipo unsigned
short int.
unsigned long int Perimetro(unsigned short int,unsigned short int);
2. Escriba la definición de la función Perim etro() como se describe en el ejercicio 1.
Los dos parámetros representan la longitud y el ancho de un rectángulo. Haga que
la función regrese el perímetro (dos veces la longitud más dos veces el ancho).
unsigned long int Perimetro(unsigned short int longitud,unsigned
short int ancho)
{
return 2*longitud + 2*ancho;
}
Respuestas a los cuestionarios y ejercicios 973
6. Escriba un programa que pida dos números al usuario y que llame a la función que
escribió en el ejercicio 5. Imprima la respuesta o imprima un mensaje de error si
obtiene -1.
#include <iostream.h>
short int Divisor(
unsigned short int valuno,
unsigned short int valdos);
int main()
{
short int uno, dos;
short int respuesta;
cout << “Escriba dos números.\n Número uno: “;
cin » uno;
cout « "Número dos: ";
cin » dos;
if ((uno + dos) == 0)
cout « "Respuesta: " << (-1);
else
{
respuesta = Divisor(uno, dos);
if (respuesta > -1)
cout « "Respuesta: " << respuesta;
else
cout « "¡Error, no se puede dividir entre cero!";
}
return 0;
7. Escriba un programa que pida un número y una potencia. Escriba una función
recursiva que eleve el número a esa potencia. Por ejemplo, si el número es 2 y la
potencia es 4, la función debe regresar 16.
tfinclude <iostream.h>
typedef unsigned short USHORT;
typedef unsigned long ULONG;
ULONG ObtenerPotencia(USHORT n, USHORT potencia);
int main()
{
USHORT numero, potencia;
ULONG respuesta;
cout « "Escriba un número: ";
cin » numero;
cout « "¿A qué potencia se va a elevar? ";
cin » potencia;
respuesta = ObtenerPotencia(numero,potencia);
cout « numero « " a la potencia " << power << "es " <<
respuesta « endl;
return 0;
}
ULONG ObtenerPotencia(USHORT n, USHORT potencia)
{
if(potencia == 1)
return n;
Respuestas a los cuestionarios y ejercicios 975
else
return (n * ObtenerPotencia(n,potencia-1));
}
Día 6
C u estio n ario
1. ¿Qué es el operador de punto, y para qué se utiliza?
El operador de punto es el punto (.). Se utiliza para tener acceso a los miembros
de la clase.
2. ¿Cuál de las dos acciones reserva memoria, la declaración o la creación?
Las declaraciones de variables reservan memoria. Las declaraciones de clases no
reservan memoria (tiene que crear el objeto). Cuando se crea un objeto, se reserva
la memoria necesaria para almacenar los datos miembros pero de ninguna forma se
reserva memoria para las funciones miembro.
3. ¿Qué es la declaración de una clase, su interfaz o su implementación?
La declaración de una clase es su interfaz; indica a los clientes de la clase cómo
interactuar con la misma. La implementación de la clase es el conjunto de funcio
nes miembro guardadas (por lo general, en un archivo CPP relacionado) que dan
funcionalidad a la clase.
4. ¿Cuál es la diferencia entre datos miembro públicos y privados?
Los datos miembro públicos pueden ser accedidos por clientes de la clase. Los datos
miembro privados pueden ser accedidos sólo por funciones miembro de la clase.
5. ¿Se pueden establecer métodos privados ?
Sí. Tanto los métodos como los datos miembro pueden ser privados.
6. ¿Se pueden establecer datos miembro o públicos?
Aunque los datos miembro pueden ser públicos, es una buena práctica de progra
mación hacerlos privados y proporcionar métodos de acceso públicos a los datos.
7. Si declara dos objetos Gato, ¿pueden éstos tener distintos valores en sus datos
miembro suEdad?
Sí. Cada objeto de una clase tiene sus propios datos miembro.
8. ¿Terminan con un punto y coma las declaraciones de clases? ¿Y las definiciones de
los métodos de clases?
Las declaraciones terminan con un punto y coma después de la llave de cierre; las
definiciones de funciones no.
9. ¿Cuál sería el encabezado para un método de la clase Gato, llamado Maullar, que
no toma parámetros y regresa void?
Sería uno como el siguiente:
void Gato::Maullar()
976 Apéndice D
Ejercicios
1. Escriba el código que declare una clase llamada Empleado con estos datos miembro:
edad, aniosDeServicio.y salario,
class Empleado
{
int edad;
int aniosDeServicio;
int salario;
};
2. Vuelva a escribir la clase Empleado para hacer los datos miembro privados, y pro
porcione métodos de acceso públicos para obtener y asignar un valor para cada uno
de los datos miembro,
class Empleado
{
public:
int ObtenerEdad() const;
void AsignarEdad(int edad);
int ObtenerAniosDeServicio()const;
void AsignarAniosDeServicio(int anios);
int ObtenerSalario()const;
void AsignarSalario(int salario);
private:
int edad;
int aniosDeServicio;
int salario;
};
3. Escriba un programa con la clase Empleado que cree dos Empleados; que asigne un
valor a los datos miembro edad, aniosDeServicio y salario, y que imprima sus
valores,
int main()
{
Empleado John;
Empleado Sally;
John.AsignarEdad(30);
John.AsignarAniosDeServicio(5);
John.AsignarSalario(50000);
Sally. AsignarEdad(32);
Sally•AsignarAniosDeServicio(8);
Sally.AsignarSalario(40000);
Día 7
Cuestionario
1. ¿Cómo podemos inicializar más de una variable en un ciclo f or?
Separando las inicializaciones con comas, como
for (x = 0, y = 10; x < 100 ; x++, y++)
2. ¿Por qué se evita el uso de la instrucción goto?
goto salta en cualquier dirección a cualquier línea arbitraria de código. Esto pro
duce código difícil de entender y por lo tanto difícil de mantener.
Respuestas a los cuestionarios y ejercicios 979
3. Es posible escribir un ciclo for que tenga un cuerpo que nunca se ejecute?
Sí. Si la condición es f alse después de la inicialización, el cuerpo del ciclo for
nunca se ejecutará. He aquí un ejemplo:
for (in t x = 100; x < 100; x++)
4. ¿Es posible anidar ciclos while dentro de ciclos for?
Sí. Cualquier ciclo puede anidarse dentro de cualquier otro ciclo.
5. ¿Es posible crear un ciclo que nunca termine? Dé un ejemplo.
Sí. A continuación se muestran ejemplos para un ciclo for y para un ciclo while:
f o r ( ;;)
{
// lEste ciclo for nunca termina!
}
while(1)
{
// íEste ciclo while nunca termina!
>
6. ¿Qué pasa si crea un ciclo que nunca termine?
El programa no se detendrá y pueden pasar muchas cosas. En el mejor de los casos,
ocupará mucho tiempo de procesador y reducirá el desempeño de la computadora
(en Linux se puede utilizar el comando kill -9 PID para terminar la ejecución del
programa). En el peor de los casos, se llenarán la pila, el heap y el segmento de
datos, habrá un desbordamiento y el sistema se congelará; si la versión del kemel
que utiliza no es muy estable o ejecuta el programa como superusuario, deberá
reiniciar el equipo.
Ejercicios
1. ¿Cuál es el valor de x cuando el siguiente ciclo for finaliza su ejecución?
for (in t x = 0; x < 100; x++)
100
2. Escriba un ciclo for anidado que imprima ceros en un patrón de 10 x 10.
for (in t i = 0; i< 10; i++)
{
for (in t j = 0 ; j< 10 ; j++)
cout << "0 ";
cout « "\n";
}
3. Escriba una instrución for que cuente del 100 al 200 de dos en dos.
for (in t x = 100 ; x<=200; x+=2)
4. Escriba un ciclo while que cuente dél 100 al 200 de dos en dos.
in t X = 100;
w hile (x <= 200)
x+= 2;
980 Apéndice D
5. Escriba un ciclo do. . .w hile que cuente del 100 al 200 de dos en dos.
int x = 100;
do
{
x+=2;
} while (x <= 200);
6. CAZA ERRORES: ¿Qué está mal en el siguiente código?
int contador = 0
while (contador < 10)
{
cout « "contador: “ << contador;
>
contador nunca se incrementa y el ciclo while nunca terminará.
7. CAZA ERRORES: ¿Qué está mal en el siguiente código?
for (int contador = 0; contador < 10; contador++);
cout « contador « "\n";
Hay un punto y coma después del ciclo, y el ciclo no hace nada. Tal vez éste sea el
objetivo del programador, pero si se supone que contador iba a im p rim ir cada
valor, no lo hará.
8. CAZA ERRORES: ¿Qué está mal en el siguiente código?
int contador = 100 ;
while (contador < 10)
cout << "contador ahora: " « contador;
contador--;
>
contador se inicializa con 100, pero la condición de prueba es que si es menor que
10, la prueba fallará y el cuerpo nunca se ejecutará. Si la línea 1 se cambia a int
contador = 5;, el ciclo no terminará hasta que haya contado más allá del int más
pequeño posible. Como in t es de tipo signed de manera predeterminada, esto no
funcionará como se esperaba.
9. CAZA ERRORES: ¿Qué está mal en el siguiente código?
cout « "Escriba un número entre 0 y 5: ";
cin >> elNumero;
switch (elNumero)
{
case 0:
hacerCero();
case 1 // pasar a la siguiente cláusula case
case 2 // pasar a la siguiente cláusula case
case 3 // pasar a la siguiente cláusula case
case 4 // pasar a la siguiente cláusula case
case 5
hacerl)noHastaCinco( );
break;
default :
Respuestas a los cuestionarios y ejercicios 981
hacerPredeteminado ();
break;
}
Case 0 probablemente necesita un una instrucción b r e a k . De no ser así, debería
documentarse con un comentario.
D ía 8
Cuestionario
1. ¿Qué operador se utiliza para determinar la dirección de una variable?
El operador de dirección (&) se utiliza para determinar la dirección de cualquier
variable.
2. ¿Qué operador se utiliza para encontrar el valor guardado en una dirección que se
guarda en un apuntador?
El operador de indirección (*) se utiliza para tener acceso al valor en una dirección
guardada en un apuntador.
3. ¿Qué es un apuntador?
Es una variable que guarda la dirección de otra variable.
4. ¿Cuál es la diferencia entre la dirección que se guarda en un apuntador y el valor
que se encuentra en esa dirección?
La dirección guardada en el apuntador es la dirección de otra variable. El valor
guardado en esa dirección es cualquier valor guardado en cualquier variable. El
operador de indirección (*) regresa el valor guardado en la dirección, que a su vez
se guarda en el apuntador.
5. ¿Cuál es la diferencia entre el operador de indirección y el operador de dirección?
El operador de indirección regresa el valor que se encuentra en la dirección guar
dada en un apuntador. El operador de dirección (&) regresa la dirección de memoria
de la variable.
6. ¿Cuál es la diferencia entre const int * apuntUno e int * const apuntDos?
co n st i n t * apuntUno declara que apuntUno es un apuntador a una constante de
tipo entero. El entero en sí no puede cambiarse por medio de este apuntador.
i n t * const apuntDos declara que apuntDos es un apuntador constante a un
entero. Después de inicializarse, este apuntador no puede ser reasignado.
Ejercicios
1. ¿Qué hacen estas declaraciones?
a. i n t * apuno;
b. i n t varDos;
c. i n t * apTres = &varDos;
A
982 Apéndice D
Día 9
C u e stio n ario
1. ¿Cuál es la diferencia entre una referencia y un apuntador?
Una referencia es un alias, y un apuntador es una variable que guarda una dirección.
Las referencias no pueden ser nulas y no pueden ser reasignadas.
2. ¿Cuándo dehe utilizar un apuntador en vez de una referencia?
Cuando se necesite reasignar a lo que se está apuntando, o cuando el apuntador
pueda ser nulo.
3. ¿Qué regresa new si no hay memoria suficiente para crear el nuevo objeto?
Un apuntador nulo (0).
4. ¿Qué es una referencia constante?
Es una forma abreviada de decir “una referencia a un objeto constante”.
5. ¿Cuál es la diferencia entre pasar por referencia y pasar una referencia?
Pasar por referencia significa no hacer una copia local. Puede lograrse al pasar una
referencia o al pasar un apuntador. Pasar una referencia es una forma del paso
por referencia, pero no es la única.
Ejercicios
1. Escriba un programa que declare un int, una referencia a un int, y un apuntador a
un int. Use el apuntador y la referencia para manipular el valor contenido en el int.
int main()
{
int varUno;
int & rVar = varUno;
int * apVar = &varUno;
rVar = 5;
*apVar = 7;
return 0;
}
2. Escriba un programa que declare un apuntador constante a un entero constante.
Inicialice el apuntador a una variable entera, varUno. Asigne un 6 a varUno. Use el
apuntador para asignar un 7 a varUno. Cree otra variable de tipo entero que se
llame varDos. Reasigne el apuntador a varDos. No compile todavía este ejercicio,
int main()
{
int varUno;
const int * const apVar = &varUno;
varUno = 6;
*apVar = 7;
984 Apéndice D
int varDos;
apVar = &varDos;
return 0;
}
3. Ahora compile el programa del ejercicio 2. ¿Qué es lo que produce errores? ¿Qué
es lo que produce advertencias?
No se puede asignar un valor a un objeto constante, y no se puede reasignar un
apuntador constante.
4. Escriba un programa que origine un apuntador perdido,
int main()
{
int * apVar;
*apVar = 9;
return 0;
}
5. Corrija el programa del ejercicio 4.
int main()
{
int varUno;
int * apVar = AvarUno;
*apVar = 9 ;
return 0;
}
6. Escriba un programa que origine una fuga de memoria.
#include <iostream.h>
int * FuncUno();
int main()
{
int * aplnt = FuncUno();
cout « "el valor de aplnt estando de regreso en main es:
" « *aplnt « endl;
return 0;
}
int * FuncUno()
{
int * aplnt = new int (5);
cout « "el valor de plnt en FuncUno es: " « *aplnt « endl;
return aplnt;
>
7. Corrija el programa del ejercicio 6.
tfinclude <iostream.h>
int FuncUno();
int main()
Respuestas a los cuestionarios y ejercicios 985
{
int ellnt = FuncUnoO;
cout << "el valor de aplnt estando de regreso en main es:
“ << ellnt « endl;
return 0;
>
int Funcllno()
{
int * aplnt = new int (5);
int temp;
cout << "el valor de aplnt en FuncUno es: ü « *aplnt « endl;
temp = *aplnt;
delete aplnt;
return temp;
>
8. ¿Qué está mal en este programa?
1: //inelude <iostream.h>
2:
3: class GATO
4: {
5: public:
6: GATO (int edad) {suEdad = edad; }
7: -GATO(){}
8: int ObtenerEdad() const {return suEdad;}
9: private:
10: int suEdad;
11: };
12:
13: GATO & CrearGato(intedad);
14: int main()
15: {
16: int edad = 7;
17: GATO Silvestre = CrearGato(edad);
18: cout « "Silvestre tiene " « Silvestre.ObtenerEdad()
19: « " años de edad\n";
20 return 0;
21: }
22:
23: GATO & CrearGato(intedad)
24: {
25: GATO * apGato = new GATO(edad);
26: return *apGato;
27: }
4:
5: public:
6: GATO(int edad) { suEdad = edad; }
7: -GATOOO
8: int ObtenerEdad() const { return suEdad;}
9: private:
10 int suEdad;
11
12
13 GATO * CrearGato(int edad);
14 int main()
15 {
16 int edad = 7;
17 GATO * Silvestre = CrearGato(edad);
18 cout << “Silvestre tiene " << Silvestre >ObtenerEdad()
19 « " años de edad\n";
20 delete Silvestre;
21 return 0;
22 }
23
24 GATO * CrearGato(int edad)
25 {
26 return new GATO(edad);
27 }
Día 10
Cuestionario
1. Al sobrecargar funciones miembro de una clase, ¿de qué manera deben diferir?
Las funciones miembro sobrecargadas son funciones en una clase que comparten
un nombre pero difieren en el número o en el tipo de sus parámetros.
2. ¿Cuál es la diferencia entre una declaración y una definición?
Cuando se habla de funciones y clases, la declaración nos muestra su prototipo o
interfaz, y la definición establece la implementación. Cuando se habla de variables
y tipos, la definición especifica las características particulares de cada tipo (y por
consecuencia de cada variable creada). Las definiciones de los tipos integrados son
intrínsecas a la plataforma y al compilador. Por otra parte, la declaración de una
variable reserva memoria para su uso posterior.
3. ¿Cuándo se llama al constructor de copia?
Siempre que se crea una copia temporal de un objeto. Esto ocurre cada vez que un
objeto se pasa por valor.
4. ¿Cuándo se llama al destructor?
El destructor se llama cada vez que se destruye un objeto, ya sea porque queda
fuera de alcance o porque se llama a d elete para que actúe sobre un apuntador que
está apuntando a ese objeto.
Respuestas a los cuestionarios y ejercicios 987
Ejercicio
1. Escriba la declaración de una clase llamada CirculoSencillo (únicamente la
declaración) con una variable miembro: suRadio. Incluya un constructor predeter
minado, un destructor y métodos de acceso para la variable suRadio.
class CirculoSencillo
{
public:
CirculoSencillo();
-CirculoSencillo();
void AsignarRadio(int);
int ObtenerRadio();
private:
int suRadio;
>;
2. Usando la clase que creó en el ejercicio 1, escriba la implementación del construc
tor predeterminado, e inicilice suRadio con el valor 5.
CirculoSencillo: :CirculoSencillo():
suRadio(5)
{}
988 Apéndice D
3. Usando la misma clase, agregue un segundo constructor que tome un valor como
parámetro y asigne ese valor a suRadio.
CirculoSencillo::CirculoSencillo(int radio):
suRadio(radio)
{}
4. Cree un operador de incremento de prefijo y uno de posfijo para su clase
CirculoSencillo, que incremente suRadio.
const CirculoSencillo & CirculoSencillo::operator++()
{
++(suRadio);
return *this;
}
// Operator ++(int) posfijo.
// Obtener y luego incrementar
const CirculoSencillo CirculoSencillo::operator++ (int)
{
// declarar CirculoSencillo como local e inicializar al valor de *this
CirculoSencillo temp(*this);
++(suRadio);
return temp;
CirculoSencillo::CirculoSencillo()
suRadio = new int(5);
CirculoSencillo::CirculoSencillo(int radio)
suRadio = new int(radio);
CirculoSencillo::-CirculoSencillo()
{
Respuestas a los cuestionarios y ejercicios 989
clelete suRadio;
}
c o n s t C i r c u l o S e n c i l l o S C irc u lo S e n c illo : :operator++()
{
+ + (*su R a d io );
return *t h is ;
}
// O p e r a t o r + + ( i n t ) p o s f ij o .
// O b tener y luego incrementar
c o n s t C i r c u l o S e n c i l l o C irc u lo S e n c illo : :operator++ (int)
{
// d e c l a r a r C i r c u l o S e n c i l l o como local e i n ic ia l iz a r al valor de *t h is
C i r c u l o S e n c i l l o te m p(*this);
+ + (*su R a d io );
r e t u r n temp;
}
6. Proporcione un constructor de copia para CirculoSencillo.
C i r c u l o S e n c i l l o : :C irc u lo S e n c illo ( c o n st CirculoSencillo & rhs)
{
i n t v a l = rhs.O btenerR adio();
s u R a d io = new i n t ( v a l ) ;
}
7. Proporcione un operador de asignación para C ircu lo S e n cillo .
C i r c u l o S e n c i l l o S C ir c u lo S e n c illo : :operator=(const CirculoSencillo & rhs)
{
if ( t h i s == &rhs)
r e t u rn * t h i s ;
d e l e t e suRadio;
s u R a d io = new in t;
* s u R a d io = rhs.ObtenerRadio();
return *th is ;
}
8. Escriba un programa que cree dos objetos C irc u lo S e n c illo . Utilice el constructor
predeterminado en uno y cree una instancia con el otro que tenga el valor 9. Llame
al operador de incremento para que actúe sobre cada uno y luego imprima sus
valores. Por último, asigne el segundo al primero e imprima sus valores.
//inelude < iostream .h>
c l a s s C irc u lo S e n c illo
{
p u b lic :
// constructores
C i r c u l o S e n c i l l o ();
C irc u lo S e n c illo (in t);
C i r c u l o S e n c i l l o ( c o n s t CirculoSencillo &);
990 Apéndice D
-CirculoSencillo() {}
// funciones de acceso
void AsignarRadio(int);
int ObtenerRadio()const;
// operadores
const CirculoSencillo& operator++();
const CirculoSencillo operator++(i n t );
CirculoSencillo& operator=(const CirculoSencillo &);
private:
int *suRadio;
};
CirculoSencillo::CirculoSencillo()
{suRadio = new int(5);}
CirculoSencillo::CirculoSencillo(int radio)
{suRadio = new int(radio);}
CirculoSencillo::-CirculoSencillo()
delete suRadio;
++(* suRadio);
return temp;
}
in t C i r c u lo S e n c i ll o : :ObtenerRadio() const
{
return *suRadio;
}
int main( )
{
C ir cu lo Se nc il lo CirculoUno, CirculoDos(9);
CirculoUno++;
++CirculoDos;
cout <<"CirculoUno: “ « CirculoUno.ObtenerRadio() « endl;
cout <<“CirculoDos: " « CirculoDos.ObtenerRadio() « endl;
CirculoUno = CirculoDos;
cout << "CirculoUno: " « CirculoUno.ObtenerRadio() « endl;
cout << "CirculoDos: " « CirculoDos.ObtenerRadio() « endl;
return 0;
}
9. C AZA ERRORES: ¿Qué está mal en esta implementación del operador de asig
nación?
CUADRADO CUADRADO : : operator=(const CUADRADO & rhs)
{
suLado = new int;
*suLado = rhs.ObtenerLado();
return *this;
}
Debe verificar si rhs es igual a this, o la llamada a a = a hará que su programa
falle.
10. CAZA ERRORES: ¿Qué está mal en esta implementación del operador de suma?
MuyCorto MuyCorto: : operator+ (const MuyCortoS rhs)
suVal += rhs.ObtenerSuVal();
return * th i s ;
}
Este o p e r a t o r + está cambiando el valor de uno de los operandos, en lugar de crear
un nuevo objeto MuyCorto con la suma. La manera correcta de hacer esto es la
siguiente:
MuyCorto MuyCorto: : operator+ (const MuyCorto& rhs)
Día 11
Cuestionario
1 . ¿Q u é es una tabla-v?
U n a tabla-v, o tabla de funciones virtuales, es una form a común de que los compi
ladores manejen las funciones virtuales en C + + . L a tabla mantiene una lista de las
direcciones de todas las fun cion es virtuales y, dependiendo del tipo del objeto al
que se apunte en tiempo de e jecu ció n , in vo ca a la función apropiada.
2. ¿Q u é es un destructor virtual?
Ejercicios
l. M uestre la declaración de una función virtual que tome un parámetro entero y
regrese v o id .
4. Escriba un constructor de copia virtual para la clase Cuadrado (del ejercicio 3).
c la s s Cuadrado
{
public:
// ...
};
5. C AZA ERRORES: ¿.Qué está mal en este segmento de código?
void UnaFuncion (Figura);
Figura * apRect = new Rectángulo;
UnaFuncion( *apRect);
Día 12
Cuestionario
1. ¿C u áles son el primero y último elem entos en UnArreglo[25]?
UnArreglo[0], UnArregloJ24)
2. ¿C ó m o se declara un arreglo m ultidim ensional?
Se escribe un conjunto de subíndices para cada dim ensión. Por ejemplo,
U n A r r e g lo [2 ] [ 3 ] [ 2 ] es un arreglo de tres dim ensiones. L a primera dimensión
tiene dos elementos, la segunda tiene tres, y la tercera tiene dos.
L a puede utilizar sólo si escribe su propia clase para que contenga la lista enlazada
y se sobrecargue el operador de subíndice.
V. ¿C u ál es el último carácter de la cadena “ Brad es una buena persona” ?
E l carácter nulo.
Ejercicios
1. D eclare un arreglo de dos dim ensiones que represente un tablero del juego
tic-tac-toe.
int Tablero[3][3];
2. Escrib a el código que inicialice con 0 todos los elem entos del arreglo que creó en
el ejercicio 1.
J
Respuestas a los cuestionarios y ejercicios 995
Día 13
C u e stio n a rio
1. ¿Qué es una conversión descendente?
Una conversión descendente (también conocida como “convertir hacia abajo”) es
una declaración que indica que un apuntador a una clase base debe tratarse como
un apuntador a una clase derivada.
2. ¿Qué es el aptrv?
El aptrv, o apuntador a función virtual, es un detalle de implementación de las fun
ciones virtuales. Cada objeto de una clase con funciones virtuales tiene un aptrv, el
cual apunta a la tabla de funciones virtuales para esa clase.
3. Si un rectángulo “redondo” tiene bordes rectos y esquinas redondeadas, y su clase
RectRedondo hereda tanto de Rectángulo como de Circulo, y éstos a su vez
heredan de Figura, ¿cuántas Figuras se crearán cuando cree un RectRedondo?
Si ninguna clase hereda usando la palabra reservada virtual, se crean dos
Figuras: una para Rectángulo y otra para Circulo. Si se utiliza la palabra reserva
da virtual para ambas clases, sólo se crea una Figura compartida.
996 Apéndice D
Ejercicios
1. Muestre la declaración de una clase llamada AvionJet, que herede de Cohete y de
Avión.
4. M odifique el programa del ejercicio 3 de forma que Auto sea un ADT, y derive de
Auto a AutoDeportivo, Vagoneta, y Sedan. En la clase Auto, proporcione una
implementación para una de las funciones virtuales puras de Vehículo y hágala
no pura.
class Vehículo
{
virtual void Mover() = 0;
virtual void Remolcar() = 0;
>;
class Auto : public Vehiculo
{
virtual void Mover();
};
class Camion : public Vehiculo
{
virtual void Mover();
virtual void Remolcar();
};
class AutoDeportivo : public Auto
{
virtual void Remolcar();
>;
class Sedan : public Auto
{
virtual void Remolcar();
};
Día 14
Cuestionario
1. ¿Pueden las variables miembro estáticas ser privadas?
Sí. Son variables miembro, y su acceso puede controlarse igual que cualquier otra. Si
son privadas, se puede tener acceso a ellas solamente mediante el uso de funciones
miembro o, lo que es más común, por medio de funciones miembro estáticas.
2. Muestre la declaración de una variable miembro estática,
s t a t ic in t suEstatica;
3. Muestre la declaración de una función estática,
static int UnaFuncion();
998 Apéndice D
4. M u e s t r e la d e c l a r a c ió n d e u n a p u n t a d o r a u n a f u n c i ó n q u e r e g r e s e un lo n g y que
t o m e u n p a r á m e tr o e n t e r o .
long (* Función)(int);
5. M o d i f i q u e e l a p u n ta d o r d e la p r e g u n t a 4 p a r a q u e s e a u n a p u n ta d o r a u n a función
Auto.
m ie m b r o d e la c l a s e
long (Auto::*Funcion)(int);
6. M u e s t r e la d e c l a r a c ió n d e u n a r r e g l o d e 1 0 a p u n t a d o r e s c o m o lo s d e la pregunta 5.
typedef long (Auto::'Función)(int);
Función elArreglof10);
O tr o s c o m p ila d o r e s ( q u e n o s e a n d e G N U ) ta l v e / , p r e f ie r a n lo s ig u ie n te :
Ejercicios
1. E s c r ib a u n p r o g r a m a c o r t o q u e d e c l a r e u n a c l a s e c o n u n a v a r ia b le m ie m b r o y una
v a r ia b le m ie m b r o e s t á t ic a . H a g a q u e e l c o n s t r u c t o r i n i c i a l i c e la v a r ia b le m iem bro e
in c r e m e n t e la v a r ia b le m ie m b r o e s t á t i c a . H a g a q u e e l d e s t r u c t o r d e c r e m e n te la
v a r ia b le m ie m b r o e s t á t ic a .
1: class miClase
2: {
3: Public:
4: miClase();
5: -miClase();
6: private:
7: int suMiembro;
8: static int suEstatica;
9: }!
10
11 miClase::miClase():
12 suMiembro(1 )
13 {
14 suEstatica++;
15 }
16
17 miClase::-miClase()
18 {
19 suEstatica— ;
20 }
21
22 int miClase::suEstatica = 0;
23
24 int main()
25 O
2. U s a n d o e l p r o g r a m a d e l e j e r c i c i o 1, e s c r ib a u n p r o g r a m a c o n t r o la d o r c o r to que
c r e e tr e s o b j e t o s y l u e g o d e s p l i e g u e s u s v a r i a b le s m ie m b r o y la v a r ia b le m iem bro
Respuestas a los cuestionarios y ejercicios 999
50: obj3.MostrarEstatica();
51: return 0;
52: }
3. Modifique el programa del ejercicio 2 para utilizar una función miembro estática
que permita el acceso a la variable miembro estática. Haga que la variable miembro
estática sea privada.
1: «include <iostream.h>
2:
3: class miClase
4: {
5: public:
6: miClase();
7: -miClase();
8: void MostrarMiembro();
9: static int ObtenerEstatica();
10 private:
11 int suMiembro;
12 static int suEstatica;
13 };
14
15 miClase::miClase():
16 suMiembro(1 )
17 {
18 suEstatica++;
19 }
20
21 miClase::-miClase()
22 {
23 suEstatica— ;
24 cout « "En destructor. suEstatica: " « suEstatica « endl;
25 }
26
27 void miClase::MostrarMiembro()
28 {
29 cout « "suMiembro: ” « suMiembro « endl;
30 }
31
32 int miClase:¡suEstatica = 0 ;
33
34 void miClase::ObtenerEstatica()
35 {
36 return suEstatica;
37 }
38
39 int main()
40 {
41 miClase obj1;
42 obj1.MostrarMiembro();
43 cout « "Estática: " « miClase::ObtenerEstatica() « endl;
44
Respuestas a los cuestionarios y ejercicios 1001
4. Escriba un apuntador a una función miembro para que tenga acceso a los datos
miembro que no sean estáticos del programa del ejercicio 3, y utilice ese apuntador
para imprimir el valor de esos datos.
1: «inelude <iostream.h>
2:
3: c l a s s miClase
4: {
5: public:
6: miClase();
7: -miClase();
8: void MostrarMiembro();
9: s t a t i c int ObtenerEstatica();
10: pnivate:
11: int suMiembro;
12: s t a t i c int suEstatica;
13: };
14:
15: miClase: :miClase():
16: suMiembro(1)
17: {
18: suEstatica++;
1 9: }
20:
21: miClase::-miClase()
22: {
2 3: s u E s t a t i c a —;
24: cout « "En destructor. suEstatica: “ « suEstatica « endl;
25: }
26:
27: void miClase: :MostrarMiembro()
28: {
29: cout << "suMiembro: " « suMiembro « endl;
30: }
31 :
32 : int miClase: : suEstatica = 0;
33 :
34: int miClase: :ObtenerEstatica()
35 : {
36: return suEstatica;
37 : }
38:
1002 Apéndice D
26:
27: miClase::-miClase()
28: {
29: s u E s t a t i c a —;
30: cout << "En destructor. suEstatica: ” « suEstatica « endl;
31 : }
32:
33: void miClase: :MostrarMiembro()
34: {
35: cout << "suMiembro: " « suUiembro « endl;
36: }
37:
38: void miClase: :MostrarSegunda()
39: {
40: cout << "suSegunda: “ « suSegunda « endl;
41: }
42:
43: void miClase: :MostrarTercera()
44: {
45: cout << "suTercera: " « suTercera « endl;
46: }
47: int miClase::suEstatica = 0;
48:
49: int miClase: :ObtenerEstatica()
50: {
51 : return suEstatica;
52: }
53:
54: int main()
55: {
56: void (miClase: : *AFM) ();
57:
58: miClase obj1;
59: AFM=miClase: :MostrarMiembro;
60: (o b j 1 . *AFM) ();
61: AFM=miClase: :MostrarSegunda;
62: (obj1 . *AFM)();
63: AFM=miClase: :MostrarTercera;
64: (o bj 1 . *AFM){);
65: cout « "Estatica: " « miClase: :ObtenerEstatica() « endl;
66:
67: miClase obj2;
68 : AFM=miClase: :MostrarMiembro;
69: (obj2 . *AFM) ();
70: AFM=miClase: :MostrarSegunda;
71: (obj2 . *AFM)();
72: AFM=miClase: :MostrarTercera;
73: (obj 2 . *AFM)();
74: cout « "Estatica: " « miClase: :ObtenerEstatica() « endl;
75:
76: miClase obj3;
1004 Apéndice D
77: AFM=miClase::MostrarUiembro¡
78: (obj3.*AFM)();
79: AFM=miClase::MostrarSegunda;
80: (obj3.*AFM)();
81: AFM=miClase::MostrarTercera;
82: (Obj3.*AFM)();
83: cout << “Estática: " << mídase: :ObtenerEstatica() « endl;
84: return 0;
85: }
Día 15
Cuestionario
1. ¿Cómo se establece una relación del tipo es ///#?
Mediante la herencia pública.
2. ¿Cómo se establece una relación del tipo tiene u n ?
Mediante la contención; es decir, una clase tiene un miembro que es un objeto de
otro tipo.
3. ¿Cuál es la diferencia entre contención y delegación?
La contención describe el concepto de que una clase tiene un dato miembro que es
un objeto de otro tipo. La delegación expresa el concepto de que una clase utiliza a
otra clase para realizar una tarea u objetivo. La delegación se logra por lo general
mediante la contención.
4. ¿Cuál es la diferencia entre delegación e implementación con base en?
La delegación expresa el concepto de que una clase utiliza a otra clase para realizar
una tarea u objetivo. Implementación con base en expresa el concepto de heredar la
implementación de otra clase.
5. ¿Qué es una función friend?
Una función friend (amiga) es una función declarada para tener acceso a los miem
bros privados y protegidos de su clase.
6. ¿Qué es una clase friend?
Una clase friend (amiga) es una clase declarada de forma que todas sus funciones
miembro sean funciones amigas de su clase.
7. S i P e rr o es am igo de Muchacho, ¿M uchacho es am igo de P e r r o ?
No, la amistad no es conmutativa.
8. Si Perro es amigo de Muchacho y Terrier se deriva de Perro, ¿Terrier es amigo
de Muchacho?
No, la amistad no se hereda.
9. S i P e rr o es am igo de Muchacho y Muchacho es am igo de C asa, ¿P e rro es amigo de
C asa?
Respuestas a los cuestionarios y ejercicios 1005
Ejercicios
1. Muestre la declaración de una clase llamada Animal, que contenga un dato miem
bro que sea un de objeto tipo cadena.
class Animal:
{
private:
String suNombre;
};
2. Muestre la declaración de una clase llamada ArregloLimitado, que sea un arreglo,
class arregloLimitado : public Arreglo
{
11. . .
>
3. Muestre la declaración de una clase llamada Conjunto, que se declare con base en
un arreglo.
class Conjunto : private Arreglo
{
11.. .
}
4. M odifique el listado 15.1 para proporcionar a la clase Cadena un operador de
inserción ( « ) .
1: #include <iostream.h>
2: #include <string.h>
3:
4: class Cadena
5: {
6: public:
7: // constructores
8: Cadena();
9: Cadena(const char *const);
19: Cadena(const Cadena &);
11: ~Cadena();
12:
13: // operadores sobrecargados
14: char & operator[](int desplazamiento);
15: char operator[](int desplazamiento) const;
16: Cadena operator+(const Cadena&);
17: void operator+=(const Cadena&);
18: Cadena & operator= (const Cadena &);
19: friend ostream& operator«
20: (ostream& _elFlujo,Cadena& laCadena);
21: friend istream& operator»
1006 Apéndice D
4:
5: void asignarValor(Ammal& , int);
6: void asignarValor(Animal& , int, int);
7:
8: class Animal
9: {
10 friend void asignarValor(Animal & , int); // ¡aqui está el cambio!
11 private:
12 int suPeso;
13 int suEdad;
14 >;
15
16 void asignarValor(Animal& elAnimal, int elPeso)
17 {
18 elAnimal.suPeso = elPeso;
19 >
20
21
22 void asignarValor(Animal& elAnimal, int elPeso, int laEdad)
23 {
24 elAnimal.suPeso = elPeso;
25 elAnimal.suEdad = laEdad;
26
27
28 int main()
29 {
30 Animal peppy;
31 asignarValor(peppy,5);
32 asignarValor(peppy,7,9);
33
La función asignarValor (Animal & , int) fue declarada como amiga, pero la fun
ción sobrecargada asignarValor( Animal & , int, int) no se declaró como amiga.
8. Corrija el ejercicio 7 para que se pueda compilar.
1: #include <iostream.h>
2:
3: class Animal;
4:
5: void asignarValor(Animal& , int);
6: void asignarValor(Animal& ,int,int); // ¡aqui está el cambio!
7:
8: class Animal
9: {
10: friend void asignarValor(Animal& ,int);
11: friend void asignarValor(Animal& ,int,int);
12: private:
13: int suPeso;
14: int suEdad;
Respuestas a los cuestionarlos y ejercicios 1009
15: >;
16:
17: void asignarValor(Animal& elAnimal, int elPeso)
18: {
19: elAnimal.suPeso = elPeso;
20: }
21 :
22:
23: void asignarValor(Animal& elAnimal, int elPeso, int laEdad)
24: {
25: elAnimal.suPeso = elPeso;
26: elAnimal. suEdad = laEdad;
27: }
28:
29: int main()
30: {
31 : Animal peppy;
32: asignarValor(peppy,5);
33: asignarValor(peppy,7,9);
34: return 0;
35: }
Día 16
Cuestionario
1. ¿Qué es el operador de inserción, y qué hace?
El operador de inserción ( « ) es un operador miembro del objeto ostream y se uti
liza para escribir en el dispositivo de salida.
2. ¿Qué es el operador de extracción, y qué hace?
El operador de extracción ( » ) es un operador miembro del objeto istream y se
utiliza para escribir en las variables de su programa.
3. ¿Cuáles son las tres formas de utilizar cin .get(), y cuáles son sus diferencias?
La primera forma de c in . get () es sin parámetros. Ésta regresa el valor del carácter
encontrado, y regresará EOF (fin de archivo) si se llega al fin del archivo.
La segunda forma de c in . get () toma una referencia a un carácter como su pará
metro; ese carácter se llena con el siguiente carácter en el flujo de entrada. El valor
de retorno es un objeto iostream.
La última forma de c in . get () toma tres parámetros. El primer parámetro es un
apuntador a un arreglo de caracteres, el segundo parámetro es el número máximo
de caracteres a leer más uno, y el tercer parámetro es el carácter de terminación.
El valor de retomo es el objeto iostream.
1010 Apéndice D
Ejercicios
1. Escriba un programa que escriba en los cuatro objetos iostream estándar: cin,
cout, cerry clog.
1: ^include <iostream.h>
2: int main()
3: {
4: int x;
5: cout « "Escriba un nùmero: >
6: cin » x;
7: cout << "Usted escribió: " << x << endl;
8: cerr « “Oh oh, iesto va a cerr!" << endl;
9: clog « "Oh oh, Iesto va a clog!" « endl;
10: return 0;
11 : }
2. Escriba un programa que pida al usuario que escriba su nombre completo y luego
lo despliegue en pantalla.
1: #include <iostream.h>
2: int main()
3: {
4: char nombre[8 0 ];
5: cout << "Escriba su nombre completo: ";
6: cin.getline(nombre,8 0 );
7: cout << "\nllsted escribió: " « nombre « endl;
8: return 0;
9: }
Respuestas a los cuestionarios y ejercicios 1011
3. Modifique el listado 16.9 para que haga lo mismo, pero sin utilizar putback( m
i g n o r e ( ).
1 // Listado
2 //inelude <iostream.h>
3
4 int main (
5 {
6 char ch;
7 coût << “escriba una frase:
8 v/hile (cin.get(ch) )
9 {
10 switch (ch)
11 {
12 case ' ! ' :
13 coût « '$' ;
14 break:
15 case
16 break:
17 default:
18 coût « ch;
19 break;
20 }
21 }
22 return 0;
23 }
4. Escriba un programa que tome un nombre de archivo como parámetro y abra el ar
chivo para lectura. Lea todos los caracteres del archivo y despliegue en la pantalla
sólo las letras y los signos de puntuación. (Ignore todos los caracteres no imprimi
bles.) Luego el programa deberá cerrar el archivo y terminar.
1 #include <fstream.h>
2 enum BOOL { FALSE, TRUE };
3
4 int main(int arge, char**argv) // regresa 1 en caso de error
5 {
6
7 if (arge != 2)
8 {
9 cout « "Uso: argv[0] <infile>\n";
10: return( 1 );
11: }
12:
13: // a b r i r el fl u j o de entrada
14: ifstream fin (argv[1 ] ,ios: :binary) ;
15: i f (¡fin)
16: {
17: cout « "No se pudo abrir " « argv[1 ] « " para lectura.\n";
18: re t u r n ( 1 );
19: }
1012 Apéndice D
20:
21: char ch;
22: while (fin.get(ch))
23: if ((ch > 32 && ch < 127) ¡¡ ch == '\rV ¡¡ ch == '\t')
24: cout << ch;
25: fin.close();
26: }
5. Escriba un programa que despliegue sus argumentos de la linea de comandos en
orden inverso, y que no despliegue el nombre del programa.
1: #include <fstream.h>
2:
3: int main(int arge, char**argv) // regresa 1 en caso de error
4: {
5: for (int ctr = argc-1; ctr ; ctr— )
6: cout « argv[ctr] << "
7: return 0;
8: }
Día 17
Cuestionario
1. ¿Puedo utilizar nombres definidos en un espacio de nombres sin utilizar la palabra
reservada using?
Sí, puede utilizar nombres definidos en un espacio de nombres si les antepone el
identificador del espacio de nombres.
2. ¿Cuáles son las principales diferencias entre espacios de nombres normales y espa
cios de nombres sin nombre?
Los compiladores agregan una directiva using implícita a un espacio de nombres
sin nombre. Por lo tanto, los nombres de un espacio de nombres sin nombre pueden
utilizarse sin necesidad de un identificador del espacio de nombres. Los espacios
de nombres normales no tienen directivas using implícitas. Para usar nombres en
espacios de nombres normales, debe aplicar ya sea las directivas using o las declara
ciones using, o identificar los nombres con el identificador del espacio de nombres.
Los nombres en un espacio de nombres normal pueden utilizarse fuera de la unidad
de traducción en la que está declarado el espacio de nombres. Los nombres en un
espacio de nombres sin nombre pueden utilizarse sólo dentro de la unidad de tra
ducción en la que está declarado el espacio de nombres.
3. ¿Cuál es el espacio de nombres estándar?
El espacio de nombres std está definido por la Biblioteca estándar de C++. Incluye
declaraciones de todos los nombres que se encuentran en la Biblioteca estándar.
Respuestas a los cuestionarios y ejercicios 1013 |
Ejercicios
1. C A ZA ERRORES: ¿Qué está mal en este programa?
«inelude <iostream>
in t main()
{
cout « "¡Hola, mundo!" « end;
return 0;
}
El archivo de encabezado iostream estándar de C++ declara a cout y a endl en el
espacio de nombres std. No pueden utilizarse fuera del espacio de nombres std
sin un identificador para el espacio de nombres.
2. M encione tres formas de solucionar el problema del ejercicio 1.
1. using namespace std;
2. using std: :cout;
using std: :endl;
3. std : : cout « "¡Hola, mundo!" « std: rendí;
Día 18
C u e stio n a rio
1. ¿Cuál es la diferencia entre programación orientada a objetos y programación pro-
cedural?
La programación procedural se enfoca en separar las funciones de los datos. La
programación orientada a objetos une los datos y la funcionalidad dentro de los
objetos y se enfoca en la interacción que debe existir entre distintos objetos.
2. ¿Cuáles son las fases del análisis y del diseño orientados a objetos?
La fase del análisis determina las necesidades de la organización y se enfoca en
comprender el dominio del problema. Es aquí donde se modelan las clases. La
etapa del diseño se enfoca en crear las soluciones. En términos generales, el diseño
es la transformación del concepto del problema en un modelo que pueda imple-
mentarse en software.
3. ¿Cómo se relacionan los diagramas de secuencia y los diagramas de colaboración?
Los diagramas de secuencia establecen la secuencia de cada objeto a través del
tiempo, mientras que los diagramas de colaboración establecen la interacción entre
las distintas clases. Se puede generar un diagrama de colaboración directamente de
un diagrama de secuencia.
1014 Apéndice D
Ejercicios
1. Suponga que tiene que simular la intersección de la avenida Massachusetts con la
calle Vassar (dos caminos típicos de dos carriles, con semáforos y cruce de peato
nes). El propósito de la simulación es determinar si la sincronización del semáforo
permite un flujo continuo de tráfico.
¿Qué tipos de objetos debe modelar en la simulación? ¿Cuáles serían las clases
para la simulación?
Autos, motocicletas, camionetas, bicicletas, vehículos de emergencias y peatones
utilizan la intersección. Además, existen señales de tráfico para vehículos y
peatones.
¿Se debería incluir el estado del asfalto en la simulación?
Efectivam en te, la calidad del asfalto tiene efe cto s importantes en el tráfico, pero
para un primer diseño, será m ás sen cillo si d ejam o s a un lado este aspecto.
2. Suponga que la intersección del ejercicio 1 está en un suburbio de Boston, que sin
duda tiene las calles menos amigables de todo Estados Unidos. A cualquier hora
hay tres tipos de conductores en Boston:
Los locales, quienes siguen conduciendo por las intersecciones aunque el semáforo
esté en rojo: los turistas, que manejan lenta y cautelosamente (por lo general, en un
auto rentado): y los taxistas, que tienen una amplia variedad de patrones de mane
jo. dependiendo de los tipos de pasajeros que lleven.
Además, Boston tiene dos tipos de peatones: los locales, que cruzan la calle cuan
do les da la gana y muy raras veces utilizan las áreas para cruce de peatones; y
los turistas, quienes siempre utilizan las áreas para cruce de peatones y cruzan sólo
cuando el semáforo lo permite.
Finalmente, Boston tiene ciclistas que nunca ponen atención a las señales de alto.
¿Cómo cambian el modelo estas consideraciones?
Un inicio razonable para ello será crear objetos derivados que modelen el refina
miento deseado por el problema:
c l a s s AutoLocal : public Auto...;
class AutoTurista : public Auto... ;
class Taxi : public Auto...;
class PeatonLocal : public
Pe at ón . . . ;
class PeatonTurista : public
Pe at ón . . . ;
class BicicletaLocal : public Bicicleta...;
Mediante el uso de métodos virtuales, cada clase puede modificar el comportamiento
genérico para alcanzar sus propias especificaciones. Por ejemplo, los conductores
de Boston reaccionan de manera diferente a los turistas cuando ven una luz roja,
aun así, heredarán el comportamiento genérico que se pueda aplicar.
3. D iseñe un programador de grupos. Este software le permite programar juntas entre
individuos o grupos y reservar un número limitado de salones para conferencias.
Identifique los subsistemas principales.
Se necesitarán dos programas discretos para este proyecto: el cliente, que ejecutarán
los usuarios, y el servidor, que se podría ejecutar en una máquina remota. Además,
la máquina cliente podrá tener un componente administrativo que permita al
administrador agregar nuevos usuarios y salones.
1016 Apéndice D
pr ot e ct e d :
ClaseCalendario calendario;
int capacidad;
ID_Salon id;
Cadena nombre;
Cadena direcciones; I I ¿donde está el salón?
Cadena descripción;
c la s s ClaseCalendario
{
public :
ClaseCalendario! ) ;
-ClaseCalendario! ) ;
void Agregar (const Conf erencia&); I I agregar una
co n fe re n c ia a l calendario
void Eliminar(const Conferencias);
Conferencia * Busqueda(Hora); // revisa si existe una
I I conferencia
// a la hora especificada
p rotected :
ListaOrdenadaDeConferencias conferencias;
}J
c la s s Conferencia
{
public:
Conferencia(ListaDePersonas &, Salón salón,
1018 Apéndice D
Día 19
Cuestionario
1. ¿Cuál es la diferencia entre una plantilla y una macro?
Las plantillas están incluidas en C++ y tienen seguridad de tipos. Las macros son
implementadas por el preprocesador y no tienen seguridad de tipos.
2. ¿Cuál es la diferencia entre el parámetro de una plantilla y el parámetro de una
función?
El parámetro de una plantilla crea una instancia de la plantilla para cada tipo. Si us
ted crea seis instancias de una plantilla, se crearán seis clases o funciones diferentes.
Los parámetros de una función cambian el comportamiento o datos de la función,
pero sólo una función es creada.
3. ¿Cuál es la diferencia entre una clase amiga de plantilla de tipo específico y una
clase amiga de plantilla general?
La clase amiga de plantilla general crea una función por cada tipo de la clase
parametrizada; la clase amiga de plantilla de tipo específico crea una instancia de
tipo específico por cada instancia de la clase parametrizada.
4. ¿Es posible proporcionar un comportamiento especial para una instancia de una
plantilla, pero no para otras instancias?
Sí, cree una función especializada para la instancia particular. Además de crear
Arreglo<t>::UnaFuncion (), también cree Arreglo<int>::UnaFuncion() para
cambiar el comportamiento de un arreglo de enteros.
5. ¿Cuántas variables estáticas se crean si se coloca un miembro estático en la defini
ción de una clase de plantilla?
Una por cada instancia de la clase.
Respuestas a los cuestionarios y ejercicios 1019
Ejercicios
1. Cree una plantilla basada en esta clase Lista:
c la s s Lista
{
private:
public:
L i s t a () : cabeza(0) ,cola(0) ,laCuenta(0) {}
v irtu al ~Lista() ;
void i n s e r t a r ( i n t valor);
void a g r e g a r (int valor);
int esta_presente(int valor) const;
int es ta_ va ci a( ) const { return cabeza == 0; }
in t cuenta() const { return laCuenta; }
private:
c l a s s CeldaLista
{
public:
CeldaLista(int valor, CeldaLista ‘ celda =):val(valor),
«»• siguiente(cel) {}
int va l;
CeldaLista ‘ siguiente;
};
Ce ld a Li st a ‘ cabeza;
Ce ld a Li st a ‘ cola;
int laCuenta;
private:
class CeldaLista
{
public:
CeldaLista(Tipo valor, CeldaLista ‘Celda = 0):val(valor),
^siguiente(cel) {}
Tipo val;
CeldaLista ‘siguiente;
};
CeldaLista ‘cabeza;
CeldaLista ‘cola;
int laCuenta;
if (head == 0 ) return 0 ;
if (head->val == value ¡¡ tail->val == value)
return 1;
Respuestas a los cuestionarios y ejercicios
L i s t C e l l *pt = head->next;
f o r (; pt ! = t a i l ; pt = pt->next)
i f (pt->val == value)
return 1;
return 0;
}
3. Escriba la versión de plantilla de las implementaciones.
template < c l a s s Type>
L i s t < T y p e > : : - L i s t ()
{
L i s t C e l l *pt = head;
v/hile (pt)
{
L i s t C e l l *tmp = pt;
pt = pt->next;
de le t e tmp;
}
head = t a i l = 0 ;
}
template < c la s s Type>
void List<Type>: : insert(Type value)
{
L i s t C e l l *pt = new ListCell(value, head);
a s s e r t (pt != 0 ) ;
head = p t ;
theCount++;
}
template <c la ss Type>
void List< Ty pe>: : append(Type value)
{
L i s t C e l l *pt = new ListCell(value);
i f (head == 0 )
head = pt;
else
t a i l - > n e x t = pt;
t a i l = pt;
theCount++;
}
template < c la s s Type>
1022 Apéndice D
if (lh.valué != rh.valué)
return 0;
SchoolClass GrowingClass;
GrowingClass.push_back(Harry);
GrowingClass. push_back (Sally);
GrowingClass.push_back (Bill);
GrowingClass. push_back(Peter);
ShowList (GrowingClass);
i != GrowingClass.end(); ++i )
i>AsignarEdad(i >0btenerEdad() ♦ 1);
ShowList(GrowingClass);
return 0;
}
//
// Muestra los elementos de la lista
//
template<class T, class A>
void ShowList(const list<T, A>& aList)
{
for (list<T, A>::const_iterator ci = aList.begin();
ci != aList.end(); ++ci)
cout « *ci << "\n";
#include <algorithm>
template<class T>
class Print
{
public:
void operator()(const T& t)
{
cout « t << "\n"•
}
};
Día 20
Cuestionario
1. ¿Qué es una excepción?
Una excepción es un objeto que se crea al invocar la palabra reservada throw. Se
utiliza para señalar una condición de excepción y se pasa al primer bloque catch de
la pila que maneje esa excepción.
2. ¿Qué es un bloque try?
Es un conjunto de instrucciones que pueden generar una excepción.
3. ¿Qué es una instrucción catch? L......
Una instrucción catch tiene la firma del tipo de excepción que maneja. Se utiliza
después de un bloque try y actúa como el receptor de excepciones generadas den
tro del bloque try.
4. ¿Qué información puede contener una excepción?
Una excepción es un objeto y puede contener cualquier información establecida
dentro de las clases creadas por el usuario.
5. ¿Cuándo se crean los objetos de excepción?
Los objetos de excepción se crean cuando se invoca la palabra reservada throw.
6. ¿Se deben pasar las excepciones por valor o por referencia?
En general, las excepciones deben ser pasadas por referencia. Si no pretende modi
ficar el contenido de una excepción, deberá pasar la excepción como una referencia
constante.
7. ¿Atrapará una instrucción catch una excepción derivada si está buscando la clase
base?
Sí, siempre que pase la excepción por referencia.
8. Si se utilizan dos instrucciones catch, una para la clase base y una para la derivada,
¿cuál debe ir primero?
Las instrucciones catch se examinan en el orden en que aparecen en el codigo fuente.
La primer instrucción catch que coincida con la firma de la excepción será utilizada.
9. ¿Qué sighifica catch ( . . . ) ?
ca tch ( . . . ) atrapará cualquier excepción de cualquier tipo.
10- ¿Qué es un punto de interrupción?
Un punto de interrupción es el lugar del código donde el depurador detendrá la eje
cución sin terminar el programa. En un punto de interrupción, puede verificar el
estado de las variables y modificar sus valores si es necesario.
1026 Apéndice D
Ejercicios
1. C re e un bloque t r y . una instrucción c a t c h . y una excep ción simple.
tfinclude <iostream.h>
class OutOfMemory {};
int main()
{
try
{
int *mylnt = new int;
if (mylnt == 0)
throw OutOfMemory();
}
catch (OutOfMemory)
{
cout « "Unable to allocate memory!\n";
}
return 0;
}
2. M o d ifiq u e la respuesta del e je rcicio 1, co lo q u e datos en la excepción junto con una
función de acceso, y utilícela en el bloque c a t c h .
^include <iostream.h>
^include <stdio.h>
^include <string.h>
class OutOfMemory
{
public:
OutOfMemory(char *);
char* ObtenerCadena() { return suCadena; }
private:
char* suCadena;
OutOfMemory::OutOfMemory(char * theType)
suCadena = new char[80];
char warningf] = "Out Of Memory! Can’t allocate room for: ";
strncpy(suCadena,warning,6 0 );
strncat(suCadena,theType,1 9 );
int main()
{
try
{
int *mylnt = new int;
if (mylnt == 0 )
Respuestas a los cuestionarios y ejercicios 10271
throw OutOfMemory("int°);
>
catch (OutOfMemory& theException)
{
cout << theException.ObtenerCadenaO;
}
return 0;
>
3. M odifique la clase del ejercicio 2 para que sea una jerarquía de excepciones.
Cambie el bloque catch para utilizar los objetos derivados y los objetos base.
I: #include <iostream.h>
2:
3: //el tipo de datos Exception es una clase abstracta
4: class Exception
5: {
6: public:
7: Exception(){}
8: virtual -Exception(){}
9: virtual void PrintError() = 0;
10: };
II :
12: // Clase derivada paramanejarproblemas de memoria.
13: // Vea que no sereserva memoria en esta clase.
14: class OutOfMemory : public Exception
15: {
16: public:
17: OutOfMemory(){}
18: -OutOfMemory!){}
19: virtual void PrintError();
20: private:
21 : >;
22:
23: void OutOfMemory::PrintError()
24: {
25: cout « "Out of Memory! l\n";
26: }
27:
28: // Clase derivada para manejar números erróneos
29: class RangeError : public Exception
30: {
31: public:
32: RangeError(unsigned long number){badNumber = number;}
33: -RangeError!){}
34: virtual void PrintError();
35: virtual unsigned long GetNumber() { return badNumber; }
36: virtual void SetNumber(unsigned long number) {badNumber =
** ?number;}
37: private:
38: unsigned long badNumber;
1028 Apéndice D
39: >;
40:
41 : void RangeError::PrintError()
42: {
43: cout << "Number out of range. You used “ << GetNumber() «
? ■!!\n ";
44 }
45
46 void MyFunction(); // prototipo de función
47
48 int main()
49 {
50 try
51 {
52 MyFunction();
53 }
54 // Sólo se requiere una instrucción catch, utilice funciones
55 // virtuales para llamar a la función correcta.
56 catch (Exception& theException)
57 {
58 theException.PrintError();
59 >
60 return 0;
61 }
62
63 void MyFunction()
64 {
65 unsigned int *mylnt = new unsigned int;
66 long testNumber;
67 if (mylnt == 0)
68 throw OutOfMemory();
69 cout « "Enter an int:
70 cin » testNumber;
71 // esta prueba se debe reemplazar por una serie de pruebas
72 // que indiquen una mala entrada
73 if (testNumber > 3768 ¡¡ testNumber < 0)
74 throw RangeError(testNumber);
75
76 *mylnt = testNumber;
77 cout << “Ok. mylnt: " << *mylnt;
78 delete mylnt;
79 }
Modifique el programa del ejercicio 3 para que tenga tres niveles de llamadas a
funciones.
1: #include <iostream.h>
(O00
virtual -Exception(){}
virtual void PrintError() = 0;
10:
11 :
12: // Clase derivada para manejar problemas de memoria.
13: // Vea que no se reserva memoria en esta clase,
14: class OutOfMemory : public Exception
15: {
16: public:
17: OutOfMemory(){}
18: -OutOfMemory() {}
19: virtual void PrintError();
20: private:
21 : };
22:
23: void OutOfMemory: :PrintError()
24: {
25: cout « -Out of Memory!!\n";
26: >
27:
28: // Clase derivada para manejar números erróneos
29: class RangeError : public Exception
30:
31 : public:
32: RangeError (unsigned long number){badNumber = number;}
33: -RangeError(){>
34: virtual void PrintError() ;
35: virtual unsigned long GetNumber() { return badNumber; }
36: virtual void SetNumber(unsigned long number) {badNumber =
ta» ?number;>
37: private :
38: unsigned long badNumber;
39: }I
40:
41 : void RangeError ::PrintError()
42: {
43: cout « "Number out of range. You used “ « GetNumber() «
ta» ?" !!\n°;
44
45
46 // prototipos de función
47 void MyFunction();
48 unsigned int * FunctionTwo();
49 void FunctionThree(unsigned int *);
50
51 int main()
52 {
53 try
54 {
55 MyFunction();
56 }
57 // Sólo se requiere una instrucción catch, utilice funciones
58 // virtuales para llamar a la función correcta.
1030 Apéndice D
prívate:
char * elMsje;
};
mam ( )
{
t ry
{
c h a r * v a r = new char;
i f ( v a r == 0)
{
xNoHayMemoria * apx = throw apx;
}
}
catch (xNoHayMemoria * laExcepcion)
{
cout << laExcepcion->Mensaje() «endl;
d e l e t e laExcepcion;
}
return 0 ;
}
Durante el manejo de la condición xNoHayMemoria, el constructor de xNoHayMemoria
crea un objeto string. Esta excepción se generará sólo cuando el sistema se quede
sin memoria; de esta forma la creación del objeto string fallará.
Es posible que al intentar crear la cadena se genere la misma excepción, lo que
provocará un ciclo infinito hasta que el sistema se congele. Si realmente se requiere
esta cadena, puede reservar el espacio en un búfer estático antes de iniciar la ejecu
ción del programa, y utilizarlo cuando se genere la excepción.
Día 2 1
C uestio n ario
1. ¿Qué es un guardia de inclusión?
Los guardias de inclusión se utilizan para evitar que un archivo de encabezado se
incluya más de una vez dentro de un programa.
2. ¿Cóm o le indica al compilador que imprima el contenido del archivo intermedio,
para que muestra los efectos del preprocesador?
Para el compilador de GNU, se debe utilizar la opción -E. Si utiliza un compilador
diferente, la respuesta a esta pregunta debe ser descubierta por usted mismo (revise
la documentación de su compilador).
3. ¿ C u á l es la diferencia entre #defi ne depurar 0 y #undef depurar?
# d e f i n e dep urar 0 establece que el término depurar será igual a 0 (cero). En cual
quier lugar donde aparezca la palabra depurar será sustituida por el carácter cero.
#undef dep urar elimina cualquier definición de depurar; cuando se encuentre esta
palabra en el archivo, se dejará intacta.
1032 Apéndice D
Ejercicios
1. Escriba las instrucciones de guardias de inclusión para el archivo de encabezado
STRING.H.
«ifndef STRING_H
«define STRING_H
«endif
2. Escriba una macro ASSERT () que imprima tanto un mensaje de error como el archivo
y el número de línea si el nivel de depuración es 2, que imprima un mensaje (sin
archivo ni número de línea) si el nivel es 1, y que no haga nada si el nivel es 0.
1: «include <iostream.h>
2:
3: «ifndef DEBUG
4: «define ASSERT(x)
5: «elif DEBUG == 1
6: «define ASSERT(x) \
7: if 0 (x) )\
8: { \
9: cout « "ERROR!! Assert « #x « " failed\n"; \
10: }
11 : «elif DEBUG == 2
12: «define ASSERT(x) \
13: if 0 (x)) \
14: { \
15: cout « "ERROR!! Assert " « «x « " failed\n"; \
16: cout « " on line " « LINE « "\n'j \
17: cout « » in file " « LINE « "\n1; \
18: } “ “
19: «endif
3. Escriba una macro llamada Dlmprimir que evalúe si DEPURAR está definida y, de
ser así, que imprima el valor que se pasa como parámetro.
«ifndef DEPURAR
Respuestas a los cuestionarios y ejercicios 1033
« d e f i n e Dlmprimir(CADENA)
« e 1s e
« d e f i n e DImprimir(CADENA) cout « «CADENA ;
«endif
4. Escriba un programa que sume dos números sin utilizar el operador de suma (+).
Pista: ¡Use los operadores a nivel de bits!
Si echa una mirada a la suma de dos bits, notará que la respuesta contiene dos bits
de longitud: el bit de resultado y el bit de acarreo. Así, sumar 1 más 1 en binario
tendrá com o resultado 0 con un acarreo de 1. Si sumamos 101 más 001, éstos serán
los resultados:
101 //5
001 //1
110 //6
Si se suman dos bits “encendidos” (cada uno con el valor 1), el resultado será 0 y
tendrá un acarreo de 1. Si se suman dos bits apagados, tanto el resultado como el
acarreo serán O. Si se suman un bit encendido y uno apagado, el resultado será 1
con un acarreo de 0. Aquí se muestra una tabla que resume estas reglas:
bitl bit2 ¡ acarreo resultado
---------------------+------------------------------
0 0 ¡ 0 0
0 1 ¡ 0 1
1 0 ¡ 0 1
1 1 ¡ 1 0
Examine la lógica del bit de acarreo. Si ambos bits (bit 1 y bit 2 ) son 0 o cual
quiera de ellos lo es, el bit de acarreo es 0. Sólo si ambos bits son 1 el bit de
acarreo valdrá 1. Éste es exactamente el mismo comportamiento que el operador
A N D (&).
u n s i g n e d i n t resultado, acarreo;
while (1)
{
r e s u l t a d o = bi t 1 A b i t 2 ;
a c ar r e o = bi t 1 & bit2;
if (acarreo == 0)
1034 Apéndice D
break;
#include <iostream.h>
Día 22
C u e stio n a rio
1. ¿Que os POSIX?
POS IX es el resultado de un intento por crear una interfaz de programación
estandarizada para sistemas operativos, iniciado en en los 80. La palabra POSIX se
deriva de “Intefaz Portable de Sistema Operativo”.
2. ¿Qué es X Windows?
X W indows es un sistema de ventanas desarrollado en el MIT a mediados de los
80. Es un estándar complejo construido sobre el modelo cliente/servidor.
3. ¿Cuáles son los dos principales editores de texto disponibles en Linux?
Los principales editores de texto que se incluyen con Linux son vim y emacs de
G N U . vim es una implementación de código abierto del editor vi. emacs de GNU
es una implementación de código abierto del editor emacs.
4. Cite una de las principales distinciones entre vi y emacs de GNU
vi y vim son editores de “modo” porque tienen distintos modos de operación. En
m odo de inserción, todos los caracteres escritos se insertan en el archivo editado.
En modo de comandos, los caracteres se interpretan como comandos del editor (no
todos los caracteres tienen un comando asociado), emacs de GNU es un editor “sin
m odo porque no tiene distintos modos de operación. En todo momento, los carac
teres que escribe pueden ser comandos para el editor o pueden ser texto para inser
tar en el archivo, esto depende de la secuencia de caracteres que escriba.
5. Cite una de las ventajas de las biblotecas compartidas en comparación con las
biblotecas estáticas, y cite una de las ventajas de las bibliotecas estáticas en com
paración con las bibliotecas compartidas.
Las bibliotecas compartidas producen programas ejecutables pequeños. Además,
permiten las actualizaciones y mejoras de un programa sin requerir la reconstruc
ción total de mismo. Las bibliotecas estáticas simplifican la instalación porque no
se necesita conocer la localización donde serán instaladas las distintas bibliotecas,
además de eliminar su búsqueda a la hora de cargar el programa para su ejecución.
6. ¿Qué utilería se usa para compilar y crear programas? ¿Cuál es su archivo de
entrada predeterminado?
La utilería make se utiliza para compilar y construir los programas. El archivo de
entrada predeterminado es GNUmakefile. Si no se encuentra, make buscará un ar
chivo llamado makef ile, y si tampoco se encuentra buscará uno llamado Makefile.
Ejercicios
1. Cree una función adicional para el programa de los dados que se mostró en la lec
ción de hoy. La función debe tomar como entrada un apuntador al arreglo Dado.
1036 Apéndice D
Para cada cara del dado, esta función debe imprimir el porcentaje de veces que
salió esa cara. La función debe estar en un archivo separado de main() y de
tirarDado().
«inelude <stdio.h>
void
doAveragefint * Die)
{
int i;
int TotRolls = 0;
double Pct;
double TotPct =0.0;
printf("\n\n");
for(i = -0; i < 6; i++) {
TotRolls += Die[i];
}
printf(”%d total rolls.\n", TotRolls);
for(i = 0; i < 6; i++) {
Pct = ((float)Die[i] / (float)TotRolls) * 100.0;
printf("\t%2d\t%5.2f%%\n", i, Pct);
TotPct += Pct;
>
printf(“\t\t=====\n\t\t%5.2f%%\n", TotPct);
CFLAGS = -0
OBJS = dice_ex.o doRoll_ex.o doAverage_ex.o
a ll: dice
Respuestas a los cuestionarios y ejercicios 1037
dice: $(0BJS)
$(CC) $ (CFLAGS) -O S§ $(0BJS)
clean:
- $(RM) dice *.o
Día 23
Cuestionario
1. ¿Qué es un shell?
Un shell es un programa que actúa como la interfaz de usuario para el sistema
operativo. Lee los comandos que el usuario escribe, los interpreta y finalmente se
los envía al kemel para su ejecución.
2. ¿Cuál es la sintaxis general de una línea de comandos de shell?
comando opciorn ... opcionN argumentol ... argumentoN
3. ¿Cuáles son los tres archivos de E/S disponibles para los programas?
Los tres archivos de E/S disponibles para un programa son la “entrada estándar”
(comúnmente el teclado), la “salida estándar” (comúnmente la pantalla) y el “error
estándar ’ (comúnmente la pantalla).
4. ¿Cuáles son las 3 formas de redirección de E/S, y qué caracteres se utilizan para
representarlos en la línea de comandos?
Las tres principales formas de redirección de E/S son: redireccionamiento de entra
da (<), redireccionamiento de salida (>) y las tuberías ( I).
5. ¿Qué son las variables de entorno, “locales” o “globales”? ¿Y las variabes de
shell?
Las variables de entorno son globales y se pasan a cualquier programa o intérprete
de comandos llamado por el intérprete actual. Las variables de shell son locales en
el intérprete actual.
6. ¿Cuál variable de entorno de bash establece la ruta de búsqueda de comandos?
¿Cuál establece el indicador de comandos?
En el intérprete bash, la variable PATH establece la ruta de búsqueda de comandos
y la variable P S 1 establece el indicador de comandos.
7. Nombre 2 caracteres de sustitución (comodines) de la línea de comandos.
El asterisco (*) es un comodín que sustituirá cualquier secuencia de 0 o más carac
teres. El carácter de interrogación (de cierre) sustituirá un solo carácter.
1038 Apéndice D
8. ¿Q u é necesita haber en un arch ivo de secuen cia de com andos de shell para que el
shell sepa a cuál intérprete debe en viar la secuencia de com andos?
L a prim era línea de cualquier arch ivo de secuencia de com andos de shell debe
incluir los caracteres tt\ seguidos de la trayectoria y el nombre del intérprete que
procesará el archivo. Por ejem plo, la línea l de un archivo de secuencia de coman
dos para bash debe ser #!/bin/sh.
Ejercicio
1. E scrib a una secuencia de com andos de bash para im prim ir todos los argumentos de
línea de com andos, adem ás del número total de argum entos que reciba.
#!/bin/bash
#
echo “$# argumentos"
for i
do
echo "argumento <$i>u
done
Día 24
Cuestionario
1. Enliste y defina los estados de un proceso.
* TASK_ r u n n in g El proceso está en espera de ser ejecutado.
* TASK_INTERRUPTIBLE El proceso está en ejecución y puede ser interrumpido.
* TASK_u n i n t e r r u p t i b l e El proceso está en ejecución y no puede ser interrumpido.
* TASK_Z0MBIE El proceso está detenido pero el sistem a considera que aún está
ejecutándose.
* TASK_STOPPED E l proceso está detenido, com únm ente por recibir una señal.
* TASK_SWAPPiNG E l sistema está intercam biando este proceso con algún otro.
2 . D escriba la diferencia entre la directiva de program ación F IF O y la directiva de
program ación R R .
F I F O (Primero en Entrar Prim ero en Salir) program a cada proceso ejecutable en el
orden en que se colocó en la cola de ejecución y ese orden nunca será cambiado.
Un proceso F IF O se ejecutará hasta que se bloqueé por cuestiones relacionadas con
la E /S o hasta que sea desplazado por un proceso de m ayor prioridad.
L a program ación de procesos R R ejecuta los procesos de tiempo real en tumo (es
decir, ejecuta el proceso que se encuentre al principio de la cola de procesos). La
diferencia entre un proceso F IF O y un R R es que este último se ejecutará por un
tiem po esp ecífico (cuanto) y entonces será expulsado y colocado al final de la cola
de procesos.
Respuestas a los cuestionarios y ejercicios 1039
Ejercicios
1. U sando un semáforo de conteo, ¿cómo podría resolver la “condición de carrera” al
iniciar subprocesos?
* Crear un semáforo y decrementar su valor al número de subprocesos que se están
iniciando; esto es, si existen tres subprocesos, se debe decrementar el semáforo tres
veces.
Obtener el semáforo al principio de cada subproceso.
* El subproceso será bloqueado hasta que todos los subprocesos se hayan iniciado.
2. Implemente el ejemplo reentrante del listado 24.8 usando el objeto variable de
condición CondVar.
tfinclude <iostream.h>
#include "tcreate.h"
tfinclude "mutex.h"
int data;
while (1)
{
1040 Apéndice D
apCond >Wait();
cout « 'leer: ■ << data << endl;
apCond->Signal();
>
>
void write_thread(void* param)
{
CondVar* apCond = static_cast<CondVar*>(param);
while(1)
{
apCond->Wait();
cout « "escribir: ” << data++ << endl;
apCond->Signal();
}
lock.Create();
threadl.Create();
thread2.Create()¡
for (int i = 0 ; i < 1 0 0000 ; i++)
»' I I La instrucción nula
lock.Destroy();
threadl.Destroy();
thread2.Destroy();
return 0;
}
Día 25
Cuestionario
1. Enliste las tres rutinas utilizadas para crear métodos de comunicación entre proce
sos de System V
msgget,semget y shmget
Respuestas a los cuestionarios y ejercicios 1041
Ejercicios
1. Im plem ente un programa cliente/servidor en el que el cliente y el servidor com
partan datos usando la clase SharedMemory, y sincronice el acceso a la memoria
compartida usando la clase Semaphore.
El primer listado es el cliente; el segundo listado es el servidor; el tercero es el
archivo make y el último es un archivo de encabezado,
tfinclude <lst25-13.h>
#include <lst25-15.h>
tfinclude "exl.h"
int main(int arge, char** argv)
{
char Buf fer[BUFFER_SIZE]; // crea un semáforo
Key* semkey = new Key();
semkey ->Create(SEM_KEY);
Semaphore* sem = new Semaphore(*semkey);
sem->Create(SEM_PERM);
delete sem;
delete semkey;
delete smem;
delete smkey;
return (0);
}
«include <lst25-l3.h>
«include <lst25-15.h>
«include "ex1.h”
int main(int argc, char** argv)
{
// crea un semáforo
Key* semkey = new Key();
semkey->Create(SEM_KEY);
Semaphore* sem = new Semaphore(*semkey);
sem->Create(SEM_PERM);
// crea memoria compartida
Key *smkey = new Key();
smkey->Create(SMEM_KEY);
SharedMemory *smem = new SharedMemory(*smkey);
smem->Create(SMEM_PERM, BUFFER_SIZE);
smem->Attach();
while (1 )
{
int i = 0 ;
sem->Acquire();
memset(smem, i, BUFFER_SIZE);
sem->Release();
}
// limpieza
sem->Destroy();
smem->Destroy();
delete sem;
delete semkey;
delete smem;
delete smkey;
return (0);
}
2. Usando tuberías, prepare una comunicación dúplex total entre un proceso padre y
un proceso hijo.
El primer listado es el programa principal; el segundo listado es el archivo make y
el último listado es la tubería (que es el mismo listado lst25-02.cxx de este día).
r p - > C r e a t e ( );
w p - > C r e a t e ( );
if (fo rk () > 0)
{
r p - > S e t T o R e a d ( );
rp -> R e a d F ro m P ip e (sta tic _ c a st< c h a r* > (b u f));
w p - > S e t T o W r i t e ( );
wp - > W r i t e T o P i p e ( s t a t i c _ c a s t < c h a r * > ( n i s g ) );
cout « buf « e n d l;
}
e lse
{
rp->SetToWrite();
r p - > W r i t e T o P i p e ( s t a t i c _ c a s t < c h a r * > ( m s g ) );
w p - > S e t T o R e a d ( );
wp - > R e a d F r o m P i p e ( s t a t i c _ c a s t < c h a r * > ( b u f ) ) ;
d e le te rp;
d e le te wp;
return ( 0 );
>
I N C L U D E S = - 1 .. / i n c - I . . /Key - I . . / P ip e s
CFLAGS=-c
1044 Apéndice D
0BJS=pipe.o
all: pipetest
main.o : main.cxx
gcc $(CFLAGS) -o main.o main.cxx $(INCLUDES)
»include <iostream>
»include "lst25-02.h“
Pipe::Pipe():
init_(false),
read_(false)
{}
Pipe::-Pipe()
{
if (init_)
Destroy();
}
int Pipe::Create()
{
if (pipe(pipe_) >= 0)
init_ = true;
int Pipe::Destroy()
if (read_)
close(pipe_[0 ]);
else
close(pipe_[1 ]);
void Pipe::SetToRead()
{
read_ = true;
close(pipe_[1 ]);
}
void Pipe::SetToWrite()
read_ = false;
close(pipe_[0 ]);
if (!read_)
return -1;
r e a d ( p i p e _ [ 0 ] , buf, s t r le n ( b u f ));
}
in t P i p e : : W r i t e T o P i p e ( c h a r * b u f)
{
if (read_)
return -1;
N a m e d P ip e * p i p e = new NamedPipe;
p i p e - > C r e a t e ( );
p i p e - > O p e n ( );
in t i = 0;
do
{
c o n s t i n t l e n = 32;
char b u f[le n ];
p ip e -> W r ite (& b u f [0], sta tic _ c a s t< in t> (strle n (b u f)));
p i p e - > C l o s e ( );
1046 Apéndice D
pipe->Destroy();
delete pipe;
}
#include <iostream>
^include °np.h°
pipe->Create();
pipe> 0 pen();
int i = 0 ;
do
{
const int len = 32;
char buf[len];
pipe->Close();
pipe->Destroy();
delete pipe;
>
^include <stdio.h>
^include <string.h>
^include <unistd.h>
^include <sys/types.h>
#include <sys/stat.h>
^include <fcntl.h>
^include "np.h"
NamedPipe::NamedPipe():
init_(false),
fp_(static_cast<int>(0 )),
nombre_(static_cast<char*>(0 ))
}
NamedPipe::-NamedPipe()
if (initj
k.
Respuestas a los cuestionarios y ejercicios 1047
Destroy();
>
int NamedPipe::Create()
{
char nombre[l = °NP’;
umask(0) ;
int NamedPipe::0pen()
{
// La clave para no bloquear la E/S es el indicador 0_N0NBL0CK
fp_ = open(nombre_, 0_RDWR ¡ 0_N0NBL0CK);
int NamedPipe::Close()
{
close(fp_);
fp_ = static_cast<int>(0);
int NamedPipe::Destroy()
{
if (fp_ != 0)
close(fp_);
delete []nombre_;
init_ = false;
>
int NamedPipe: :Read(char* buf, int len)
{
len = read(fp_, buf, len);
return len;
1048 Apéndice D
return len;
}
// Listado 25.3 La clase NamedPipe
«ifndef C_NP_H
«define C_NP_H
«include <sys/types.h>
«include <sys/stat.h>
«include <stdio.h>
«include “Ist25-01.h" // «include "object.h"
c l i e n t . o : c l i e n t . cxx
g c c S (C FL AG S) -o c l i e n t . o c l i e n t . cxx S(INCLUDES)
s e r v e r .o s e r v e r .cxx
gc c $ ( CFLAGS ) -o s e r v e r . o server.cxx S(INCLUDES)
c l i e n t : np.o c l i e n t . o
gc c -o c l i e n t c l i e n t . o S(OBJS) -lstdc++
s e r v e r : np.o s e rv e r.o
gc c •o s e r v e r s e r v e r . o $(OBJS) -lstdc++
Día 26
C u e stio n a rio
1. ¿C uál es la diferencia entre programas controlados por eventos y programas con
trolados por procedimientos?
Un programa controlado por procedimientos se ejecuta de principio a fin, siguiendo
las trayectorias de control que se establecen a partir de la entrada. Un programa
controlado por eventos espera en un ciclo diversos eventos extemos que lo afec
tarán y realizará funciones como respuesta a dichos eventos.
2. ¿Qué es un widget, en términos computacionales?
Un widget es un elemento de la GUI que muestra información u ofrece una manera
esp ecífica para que el usuario interactúe con el sistema operativo y con las diversas
aplicaciones.
3 .¿Qué es una función callback, y por qué son propensas a errores?
Una función callback es una llamada a función que se pasa como referencia a otra
función. Estas funciones se definen con el tipo (void) (* fuñe) (), por lo que el
com pilador no puede conocer si la función que se pasará como argumento es del
tipo adecuado. Aun si la declaración del tipo es segura, programadores sin escrúpu
los pueden utilizar la conversión de tipos para empeorar los efectos. Lo peor de
todo es que el apuntador puede ser nulo o inválido, y esto llevará a un terrible
“core dump".
4. ¿Qué es una ranura Qt, y cómo reacciona a la señales Qt?
Una ranura Qt es una función miembro normal, que se vincula a una señal utilizan
do la función connect (). Las ranuras que se conectan a señales de esta manera
serán invocadas cuando un objeto específico emita la señal correspondiente.
1050 Apéndice D
5. Las señales y las ranuras ofrecen seguridad de tipos. ¿Qué significa esto y porqué
es algo bueno?
Las ranuras y las señales siempre son del tipo correcto, lo que indica que una ranura
nunca recibe el tipo de señal equivocado v nunca tendrá los terribles “core dumps".
De hecho, las ranuras y las señales no están asociados estrechamente; cuando un
objeto emite una señal no se sabe si la señal ha sido atrapada por una ranura o se
ha ignorado completamente.
6. ¿Cómo enlazaría el siguiente manejador de eventos de \vxWindows a un evento
EVT_MENU desde un elemento de menú con el identificador de ID_MY_HELP?
Asuma que 0nHelp() es miembro de la clase MyFrame, y que MyFrame se deriva
de wxFrame.
void OnHelp(wxCommandEvent & WXUNUSED(event));
Con la siguiente tabla de eventos:
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(ID_MY_HELP, MyFrame::0nHelp)
END_EVENT_TABLE()
7. ¿Qué hace la macro IMPLEMENT_APP()?
Esta macro implementa la función main() en una aplicación de wxWindows y
oculta los detalles del ciclo de eventos principal al programador.
8. ¿Qué hace la macro Q_0BJECT ?
Esta macro la utiliza el compilador de metaobjetos (MOC) para convertir las sec
ciones signáis: y slo ts: de Qt, que se encuentran en las declaraciones de clase de
KDE, a la forma en que un compilador de C++ estándar pueda entender.
Ejercicios
l. Usando el archivo fuente Ist26-01 .cxx como base, extienda el programa para que
la clase callback muestre un cuadro de diálogo al hacer clic en los botones, en
lugar de escribir directamente en stdout.
Aquí verá que creamos una nueva función miembro estática llamada callback:: •
do_message() como nuestra función callback. En ella creamos un cuadro de diálo
go con la llamada a gnome_message_box_new() y la mostramos con la llamada a
gnome_dialog_run(). Después de esto, llamamos a do__message() desde la función
callback que conectamos con gtk_signal_connect () en main ().
// el archivo de encabezados de gnome principal
#include <gnome.h>
class callback {
public:
static void button_clicked(GtkWidget *button, gpointer data);
static gint delete_event(GtkWidget *widget,
**GdkEvent *event, gpointer data);
prívate:
Respuestas a los cuestionarios y ejercicios 1051
"OK",
NULL);
ret = gnome_dialog_run (GN0ME_DIAL0G (dialogue));
}
void
callback::button_clicked(GtkWidget ‘button, gpointer data)
do_message((char*)data);
// y otro botón.
button = gtk_button_new_with_label("Button 2");
gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (callback::button_clicked),
"Button 2\n");
return 0;
/*
* e x 2 6 - 0 1K D E . h
*/
m_btnQuit->show();
connect(mbtnQuit, SIGNAL(cllcked()) , this, SLOT(SlotBrowse()));
void KDEHelloWorld::SlotGreet()
{
KMsgBox::message(0,"KDEHelloWorld","Hello World!");
}
void KDEHelloWorld::SlotBrowse()
{
QCadena fileName = QFileDialog::getOpenFileName(
^QCadena::null, QCadena::null, this);
void KDEHelloWorld::SlotQuit()
c lo se ();
}
En el a r c h iv o ex26 •02wxWin. cxx, tenemos que agregar una variable miembro button
m btnBrowse en la declaración de la clase MyFrame. Inicializamos la va
a d ic io n a l.
riab le y c r e a m o s nuestro botón en el constructor de MyFrame (), después conectamos
el id e n tific a d o r del evento ID_Browse al manejador MyFrame: :0nBrowse() en la
d e c la r a c ió n de event table ().
Cuando haga clic en el botón, el manejador invoca
a wxFileSelector() p ara mostrar un dialógo estándar para examinar archivos.
/*
* ex26-02wxWin. cxx D
*/
//ifdef _GNUG__
// //pragma implementation
//endif
//ifdef _BORLANDC__
//pragma hdrstop
//endif
enum
{
ID_Quit = 1,
ID_Greet,
ID_Browse,
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(ID_Quit, MyFrame::0nQuit)
EVT_MENU(ID_Greet, MyFrame::OnGreet)
EVT_BUTTON(ID_Greet, MyFrame::OnGreet)
EVT_BUTTON(ID_Quit, MyFrame::OnQuit)
EVT_BUTTON(ID_Browse, MyFrame::OnBrowse)
END_EVENT_TABLE()
IMPLEMENT_APP(MyApp)
bool MyApp::0nlnit()
{
MyFrame ‘frame = new MyFrame("Hello World",
•wxPoint(50,50), wxSize(200,100));
frame->Show(TRUE);
SetTopWindow(frame);
return TRUE;
>
m_btnGreet
= new wxButton(m_panel, ID_Greet,
_T("Greet..."),
wxPoint((width/2)-
^•(buttonWidth/2+buttonWidth+buttonSpacing), 10), buttonSize);
m_btnQuit = new wxButton(m_panel, ID_Quit,
Respuestas a los cuestionarios y ejercicios 1057
_T("Quit"),
wxPoint ((width/2) -(buttonWidth/2),
"■»10), buttonSize);
»inelude "ex26KDE-03.moc"
»inelude <kapp.h>
»inelude <ktmainwindow.h>
Respuestas a los cuestionarios y ejercicios 10591
«include <kmsgbox.h>
void KDEHelloWorld::closeEvent(QCloseEvent *)
{
kapp->quit();
}
void KDEHelloWorld::SlotGreet()
{
KMsgBox::message(0, "KDEHelloWorld0."Hello Worldl0);
}
void KDEHelloWorld::SlotBrowse()
m_f ileName = QFileDialog: :getOpenFileName(QCadena: :null,
**QCadena::null, this);
if(1fileName.IsEmpty()) {
// Necesitamos leer el archivo.
QFile theFile(m_fileName);
1060 A p é n d ice D
void KDEHelloWorld::SlotQuit()
{
close();
}
MyApp.setMainWidget(MyWindow);
MyWindow->show();
return MyApp.exec();
«ífdef _GNUG__
// «pragma implementation
«end if
«lfdef _BORLANDC__
«pragma hdrstop
«endif
enum
{
ID_Quit = 1 ,
ID_Greet,
ID_Browse,
};
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU( ID_Quit, MyFrame: :OnQuit)
EVT_MENU(ID Greet, MyFrame::OnGreet)
1062 Apéndice D
EVT_BUTT0N(ID_Greet, MyFrame::0nGreet)
EVT_BUTT0N(IDQuit, HyFrame::0nQuit)
EVT_BUTT0N( IDBrowse, MyFrame: :0nBrowse)
END_EVENT_TABLE()
IMPLEMENT_APP(MyApp)
SetMenuBar(menuBar);
Puede ver que en el siguiente listado cream o s una nueva clase. TheAppClass. yen
ella declaram os los w id gets de Cítk que representan la aplicación y el cuadro que
contiene los botones co m o variables m iem bro.
Esto sign ifica que todo lo que necesitam os hacer en m a in ( ) es distanciar el objeto
aplicación, decirle que cree sus propios w id g e ts y que se ejecute.
/*
ex26-04.cxx • Envolvemos la aplicación como una clase. Puede expandirla
a cualquier grado de abstracción que quiera.
*/
#include <gnome.h>
class TheAppClass {
public:
TheAppClass(int arge, char* argv[]);
virtual gint CreateWidgets();
void Run();
private:
static void button_clicked(GtkWidget *button, gpointer data);
static gint delete_event(GtkWidget *widget, GdkEvent *event,
«•gpointer data);
static void do_message(const char*);
GtkWidget *app;
GtkWidget *hbox;
gint
TheAppClass::CreateWidgets()
{
GtkWidget *button=0;
GTK_SIGNAL_FUNC (TheAppClass::button_clicked),
’Button 2#);
return 0;
}
void
TheAppClass::do_message(const char * msg)
{
GtkWidget ‘dialogue;
int ret;
dialogue = gnome_message_box_new (
msg,
"OK",
NULL);
ret = gnome_dialog_run (GN0ME_DIAL0G (dialogue));
>
void
TheAppClass::button_clicked(GtkWidget ‘button, gpointer data)
do_message((char*)data);
}
void
TheAppClass::Run()
{
gtk_widget_show_all(app);
gtk_main ();
}
int
main(int argc, char *argv[])
{
TheAppClass MyApp(argc, argv);
MyApp.CreateWidgets();
MyApp.Run();
return 0;
}
■ i il uso de
colecciones ordenadas cuándo utilizar. U susccpob,lu ‘ v minúseulas.
(arreglos), 409 escribir. 4 I m¡«>úsc'u *,s ’
COLOR, variables de tipo, 61 legibilidad. 784 49
comandos listado. 55 lubhe- '• ° ’ 247
ifdef. 749 precauciones. <5 ,hr.
i índc f. 749 comillas niipihir
¡illas. 845-846 caracteres de escape. 58 bugs. 7 *** |Q-J9
argumentos. 837 insertar, operador de-
comodines. 838. 843 -..
cadena. 757 >’,hn- j;uc„tc. «2
completaceli de. 842-843 comodines, 838, 843 de arel.''— 111
Ctrl+Z. 841 11 I6 /
com parar clases con objetos. errores. _ • . gi7
editar. 845 148 ^ + C O m IHdor.'Hl6-8l7
ejecutar en segundo plano. compiladores, 10 - 1 1 , 1 8 -1 9 L.cc. eompi ^ rctcrencias a
841 748 programas c ^
emacs. 813-814 objetos mih’ • '
almacenamiento, expandir.
gdb común. 740 694 símbolos. 73g dor (-).
gdb. depurador. 823-824 arreglos. 368 mplemento. op».
in Ib. 137
compilar con símbolos. 739 r?6 . in< „redetermi*
iperm. 881 mportannentos I
conflictos de nombres. 600
ipes, 881 nidos. 587
constructores predetermina
kill. 841 dos. 298 .„p illad o ras
líneas de. 837 contar. 372 evolución. 11
argumentos (listado). descargar. 9 interpretar nl,,nert£ ¡ 398
592-595 editores integrados. 25 „ un solo enlace I * « * ’
concatenar. 837 enlazadores, 10 ucatenación. 8 37
desventajas. 896 errores. 24. 167.419. 600 cadenas. 33.
GCC. opciones del errores en tiempo de compi operador de 7. -
compilador. 40 K -eptualizacon, 6-4
lación. 167
variables, 8391ista de u-ordar (definiciones
espacios de nombres. 599
historial. 844 evaluar instrucciones AND unciones), 10 *
make. 135 (&&), 92 .dición de carrera, 856
modo ex (editor vi), 8 11 idicional, operador
formato intermedio, guardar
RCS. 827 748
secuencias de comandos 4'95 175
unciones de paro. 1 0
gcc. 8. 816-817
de shell. 846 GNU. 31, 808 dlictos de nombres, 3
shells, 836 GNU. g++. y, 23, 817 ,flietos. Vea espacios < c
com binar opciones. 40 ombres
arreglos, 4 1 1 i j u n t o s (arreglos), 403
página del manual. 39
operadores matemáticos con invocar, 18 ¿cíenles de la sesión, opil
operadores de asignación, macros, 758-761 aciones, 900
74-75 objetos nulos, 266 ist, apuntadores, 250-2
referencias y apuntadores. operadores, 7 1 declarar. 250-251
284 palabras reservadas, 50 listado. 279-280
comentarios, 3 4 -3 5 preprocesadores, 30 métodos, 25 1-252
/< (estilo C), 34. 4 1 RTTI, soporte, 419 pasar. 278-28 I
// (estilo C++). 34. 41 soporte de excepciones. ist, funciones miembro,
ayuda en la solución de 717 5 1-2 5 3
problem as. 7 16 soporte para plantillas, ist, instrucción, 60, 163,
control de versiones, 827 791
In d ic e 1077
_____
(lq4-69/
CPP- archivos, J7, 167. 751 clases
(1*1. tiempo, 859 'i.
\ ^eiK|l
7 . ,,L,**
' 8,5
de arreglos parametri/a
< KC, sesiones. 645 tías. 662 i-sil. 836 _
limitación. 647 excepciones. 725 ctags. 8 U-' . #41
responsabilidades. 646
( KC. tarjetas, 644-646
manipulación de datos.
644
( «
cuadro de ||S ^ vl^ 931
antropomorfismo. 646-647 para guardar un ob|eto. cuantos- voIúnun. 118
limitaciones. 647 398
traducir en UML. 64,S claves (N\stem Y IPC'i. ..... .
crear. \éa también asignar 879-880 orclu'"- |7‘‘8 ,, 9
alias. 845-846 Clone!). métodos. 360 r , Km.s. "
aplicaciones colas de mensajes. 883-885
KDE. 923-927 constructores de copia, 303
KDh. agregar bolones. contadores. 326
928-931 ejecutables. 40 D
KDE, agregar mentís. elementos. 69.3
934-936 1)roBn.ma ««sl"do)’
dados.c. Pro>-
Empleado, objetos. 5 1 I
KDcvelop. 937-938 espacios de nombres. 604 816
wxStudio. 922 lugas de memoria. 2 39 d a r form ato
wxWindows. kil de he archivos. 818
GNOME. widgets. 907
rramientas. 910-917 archivos niake.
KDE Window. clase.
wxWindows kit de he 926-927 cadenas. 5 8 1 58
rramientas. agregar caracteres especial«*
lis,as enlazadas. 405. 487
mentís a la clase 494-497 con p rinifO -:,fl1
Window. 920-92 I salida. 583-584
memoria compartida. 8 9 0
wxWindows. kit de he menús. 921 anchura. 5 7 7 -5 7 8
rramientas. comunica indicadores, 580-581
objetos. 873
ción de objetos, 918 objetos en el heap, 240-24 I datos
wxWindows kit de he operadores de conversión arreglos, 367
rramientas. procesa 325 compartidos. 110
miento de eventos. paquetes, 637 copiar. 146
919-920 procesos, 854-855 estáticos, iniciabzar,
apuntadores a funciones que programas estructuras
son miembros de clases, buen diseño, 16 colas. 706
477 Hola, mundo, 23 pilas. 705
apuntadores desconsolados. hacer referencia a. I4
puntos de observación, 7 4 1
247-248 Qt/KDE. aplicaciones GUI. heap. 235
archivos de proyecto. 134 924 m anipular, 146,231. '
archivos make, 820 referencias, 2 6 0 miembro. Vea datos
arreglos, 383-384 miembro
representaciones, 146
artefactos, 640 semáforos, 887 de estado, 483
bibliotecas com partidas. subprocesos, 862-864 ocultar, 15
819 tipos, 146 partición, 358-360
tipos de variable, 60 recuperar. 590
botones (aplicaciones
wxWindows). 916 tuberías reserva. 456
con nombre, 878 tipos. Vea tipos de datos
búferes de caracteres, 564
cadenas, 507 semidúplex, 877 validar, 62
casos (análisis del diseño). variables, 48
626 variables múltiples, 51
In d ic e 1079
.ir,,*.,raO-f‘,"CÍÓ"-293'
delele, instrucción. 236 -238 d e r h u r ADTs de otros \I) I s,
deletel J, instrucción, 384 445-448
deletel). operador, 693 desarrollo •rtn l.(, enlace»
delimitadores, 387 C R (se sio n e s. 645
entornos. 17 eccione.- ,.264
depuradores simbólicos. 739
de desi«n‘>- - 23o
depurar, 739 . 822-823. d esasignar m em oria.
Vea también solución de 2 36 -2 3 8 de mc»«or,;l-;% 26-227
deten” " 1'1 ; , , . 233
problemas descargar
archivos. 815 compiladores. 9
; : : ^ ^ ' n,“dorcs-
archivos core. 741 KDevclop. 903
arreglos. 369-370 describir 22*‘22'} -,->9 .3 3 0
ASSKRTO. macro. desplazam iento. 369 reeu|--ril,; ; (; 227
operadores- —
760-762 desplazam iento, operador
depuradores simbólicos. (| |),395 v e t i '" s . 1 (, 14
739 desplazarse a través de listas
ensambladores. 742 por medio de ¡teradores, 748
errores tipo poste de barda. 700
372 desplegar , . v 864-866
examinar memoria. 742 ceros a la derecha. 579 : : : : d o r o s .s 6 7
GNU. depurador. 740-741 información gráfica. 898
GNU. depurador gdb, Desplegar!). método, 494 b ^ d -o n d ic id .,
822-823 desreferencia, operador (*),
ayuda, 823-824 229-230 ,á.sos).860
cerrar. 824 desreferenciar apuntadores,
comandos. 824 234 2 3 % .
sesión de ejemplo. destino, direcciones (referen
826-827 cias), 261 ssss.;-*-“
»24.640 , .jón. 638
:;S:í:
goto, instrucciones. 183 asignar. 262-264
guardias de inclusión, listado. 2 6 1
751-752 regresar. 261 -262
herencia múltiple. 435 destino/dependencia, archivos 626 . -3«
anábsis de sistem as. 6.
imprimir valores interinos. make, 136
artefactos. 640
767-768 destructores, 159 , 1 6 1 , 16 3
asociación. 633
macros predefinidas. 758 herencia. 340, 342
cascuda, 623
mezclas, 436 iinplementar, 163
casos de uso 626
niveles, 768-774 limpiar memoria asignada. actores. 6 2 6 -6 2 8
puntos de interrupción. 244 análisis de Jonum o. fe ;
742 llamar (listado). 340-342 cajero autom ático. J
puntos de observación. predeterminados. 1 6 0 - 1 6 1 . pío. 629
742 163 creación de paquetes. 6.
reescrituras del sistema virtuales. 360, 452 diagram a. 636
determ inar
operativo. 370 escenarios. 6.34
ventajas, 744 direcciones de memoria. lincam ientos. 634
D E P U R A R , modo
226-227 m ecanism os, 628
niveles de depuración, tam años de variables,
768-774 45-46
diagram as, 6 53
valores interinos, 767-768
deque, contenedores, 70 1
de colaboración, 653
de secuencia. 657
derivación, 3 3 5 - 3 3 7
In d ic e 1081
establecer
proiitK ir I listado I. 1'' :n M'iuvion vic¡ enésim o numero
ba.se de números 57»)
rccujxrrar »latos de distad-
bits. 776
'*2s. o » »i 1 11 ( ) i P r i m e r o en 1 n l r a r .
indicadores. 57»)
sin errores. »S ’ 6) P rim e n « e n S a lir» , d ir e c tiv a
'friables (shcllsi. H4 n
s«»liuion de problem as. d e p riK iM is. KM.I
atados, 657
'72t> '31 *{lí>
tosircam. objetos. 57»)
ulen titu ar problem as tuberías ..m nom bre
supcrestados, 658
estereotipos, 653 js»ten» rales. ’ 1"* fili' i. im tix ln . 57K -571)
>*¡v nmes. ' 1s filtr a r el a c c e s o a la s c la s e s
estructuras
s o p u te »lei som pibnior ' 1 c o n te n id a s . 508
clascs. comparación, i 7S
declarar. 175-176
tr\.bbhjucs. '1o. 72 1 f i l t r a r f u n c io n e s c o m p a r t id a s .
%enlatas. 74 » 4 16. 45 1
^gerencias de uso. | 78
estructura de permisos d p ( \ i imite. vS filtro s d e c \ c u t o s . 9 2 8
881 e\ee( i. llamada de sistema. fin d e a r c b ix o i F ( ) f ». 573
estructuras dinámicas de 856 fin d e lin e a 41- <) 1 ». 5 7 3
datos, 494 expandidas, funciones en flu jo s , 557
etapa de iniciuli/.ución, ¡mu- linea, 756 bul e re s . ^rwi
ear constructores, 3 0 0 - 3 0 1 expandir almacena miento im p le m e n ta r.
etiquetas, 1 8 2 <compiladores », 6»)4 lim p ia r <> \ .»ciar. 7<>< •
etiquetas de acceso, 7 8 4 -7 8 5 expresiones, 70 »le v lase strcain. 5S-'
evaíuar, 95. Vea también e\ aluar. 7( ) abrir .írc lm o s para Iv'S.
operadores paréntesis anidados. 78-7») 5s 5 - \ s "
AND (&&», instrucciones i anni icat m stm ectones c o m p o rta m ie n to prede-
92 su neh. 207 tc n n im u lo . 58 7-58»)
expresiones. 70 su iteli, instrucción. 205 estallos d e coruiicion. 787
expresiones complejas. 70 vcrdad/talsedad. 7») c n c a p su la c io n . 558
¡f. instrucciones. 86 e x tra cció n , sím b o lo del fu n ció n p rintft ). c o m p a r a
ex, modo (editor vim), 8 1 1 o p e ra d o r de ( » ) , 5 6 2 ció n . 5 8 2 -5 8 3
examinar o b |d o s ile H/S e stá n d a r. 7b 1
caracteres. 570 c c ir. 561
direcciones de memoria. cin . 5 6 1-575
232-233 F c lo g . 56 1
memoria, 742 co u t. 5 6 1 . 5 7 5 -5 8 I
excepciones, 715-717 facilitad o re s, 6 4 5 rcilire cció n . 56 1-562
ASSERT0, macro, 760-761 F a c t o r t ), función flu sh ( ), m é to d o , 575
atrapar. 722 apuntadores. 273 for, cic lo s, 1 9 5 - 1 9 6
catch, bloques, 7 2 1 referencias. 274-275 a lc a n c e , 202
clases, 720-721 F ah ren lieit. co n v e rtir a ce n tí an id ar. 200-201
datos g ra d o s, 1 0 7 - 1 0 8 a v a n z a d o s, 196
leer, 728-732 falsed ad , 7 9 , 9 3 , 9 5 1 in ic ia liz a c ió n . 195-1 97
pasar por referencia, F A Q s (P re g u n ta s frecu en tes), in stru c c io n e s
732-735 831 m ú ltip le s ( lis ta d o ),
desventajas, 744 I1b(), fu n ción , 2 0 4 196-197
funciones virtuales, 732-735 F ib o n acci, serie de, 1 2 4 n u la s. 197-199
jerarquías, 725-728 rccursión, 12 6 -1 2 7 ,1 35 v a c ía s (lis ta d o ). 1 9 8 -1 9 9
manejadores, ordenar, 728 resolver por m edio de la lista d o . 195
múltiples, 722-725 iteración, 203-204 se c u e n c ia d e e je c u c ió n , 196
NoHayMemoria, 732 fib(). función, 204 v ac ío s. 198-2 0 0
plantillas, 735-738 precauciones, 205 w h ilc. c o m p a ra c ió n . 212
1086 ín d ic e
nivel de abstracción. 440 funciones miembro. Vea tam getí). método. 386, 56 7, 569
OblcncrAreat). 174 bién métodos; arreglos de caracteres.
Obtener!íilailt). 158 apuntadores a (listado). 570-571
Oblencrlistadoí i. 770 4 78 -4 7 9 con/sin parámetros.
ObtenerPcsot). 160 const palabra reservada. 568-569
OhtcncrSii[)l/i|( ). 174 251 parámetros de referencia de
parámetros de. 104. 112 gethnet ). 570 caracteres. 569
polimorfismo. 14. sobrecargar. 293-295 sobrecargar. 572
119-122 runcionl.lenarlntt ). función. getlinet), función. 5 7 0 -5 7 2
printl'í). 582-583 681 (¿etStrin gí). función. 3 9 7
prototipos. 101 -104. Pancioni noi ). función. 278 Gim p. Kit de herramientas
271-272 (G T K + + ), 900
nombres ile parámetros. globales, variables. 108-109 ,
103 130
tipos de valor ilc retorno, G limitaciones. 109-110. 141
102. 105 listado. 108-109
minificar. 105 g+ + , com piladores, 8, 23, ocultar. 108
ranuras. 932 8 17 G N O M E (Entorno G N U de
rccursión. 124-128 o p cio n e s, 40 Modelo de Objetos de Red),
redefinir. 346-348 página del manual. 39 899
regresar múltiples valores ( ¿ato. clase API, 903. 905
apuntadores. 272-274 datos miembro, acceder a. biblioteca. 905
referencias. 274-275 152 Bonobo. 900
rellenar. 335 declarar. 147. 169-170 CORBA. 900
resolución de nombres. funciones de acceso. 163 GDK++. 904-905. 907
600-604 implementar. 170-171 GTK++. 904
retorno, valores de. 100 listados. 169-171 instalar. 900
sintaxis, 31, 37 métodos listado básico de programa
sistemas operativos. 562 AsignarF-dadt ). 158 (botones.ee). 906-907
sizeofí), 46 de acceso. 154 programación. 904. 908
sobrecargar. I 19-1 20. implementar. 156-158 recursos en línea. 900
293-295. 348 Mau llarO. 154. 159 stio Web. 900
sqrtO. 272 ObtenerEdadO. 158 widgets. crear, 907
streatí). 388 ObtcnerPesoO. 169 wxWindows. kit de herra
strepyO, 387-388. 391 objeto, inieializar, 161-163 mientas. 909
stmcpyí). 388 (¿ATO, constructor, 302 Gnom eApp, m arco de trabajo
Sumaí), 38, 3 17-3 I 8 gee, com pilador, 8, 816-817 de aplicación, 908
tamaños, 112 gdl». com andos, 740 G N O M EH ello W orld,
tubería, 874 gdb, depurador, 822-823 program a
valores avuda. 823-824 agregar
regresar, 37. 114 cerrar. 824 botones. 913-91 7
void, 37 comandos, 824 mentís (listado). 92 I
virtuales, 365, 452, 801 sesión de ejemplo. 826-827 comunicación de objetos.
destructores, 452 xxgdb. 825 918
llamar múltiples (listado), G D I (Interfaz de Dispositivo listado (básico). 910
354-355 G ráfico), 898 (¿N U (G N U No es U N IX ), 7
puras, 440-445 G D K + + , 905 compiladores. 808
void, valores, I 14 generales, funciones, 139 com pilador g++. 39-40
VolumenCuboí). I 18 generalización, 631, 649 errores. 419
1086 índice
hola.cxx (el programa Hola. a método de la base por valor. 113. 267-268.
mundo). 21 desde método 512-515
ignore)). 573 redefimdo. 350-351 p cc k i) y putbackt). 574
implementación de galo en llaves que aclaran las Pipe, definición de clase.
gaio.hpp. 170-171 instrucciones else. 89 K75
implementaciones de plan llenar un arreglo. 387 prefijo y posfijo, operadores,
tilla. 682-686 manipular dalos mediante 76-77. 315-316
implementar apuntadores, 231 proceso con funciones
funciones virtuales map, clases de contene agregadas para control
puras. 442-444 dores. 702-704 de procesos. 856
métodos de clases, memoria, fugas. 287-289 Process. clase. 855
157-158 Message, objetos. 885 producir excepciones.
plantillas, arreglos. miembros estáticos 717-720
665-668 acceder con métodos. p u tt). 576
imprimir 460 ramificación con base en los
caracteres con base en acceder sin un objeto. operadores relaciónales,
los números, 57 458-459 82-83
con printfO, 582-583 msgbuf, estructura, 884 rect.cpp. 173
valores en modo msqid_ds. estructura. 883 rect.cxx. 174
DEPURAR, 767-768 múltiples excepciones. 722 recursión por medio de la
inicialización de variables Mutcx, clase. 865 serie de Fibonacci.
miembro, 301 NamcdPipe, definición de 126-127. 135
instrucciones de retomo clase, 878 redefinir un método de clase
múltiples, 115-116 niveles de depuración. base. 347-348
instrucciones if anidadas, 87 769-774 referencias
instrucciones múltiples en Object, interfaz. 874 a objetos no existentes.
ciclos for, 196-197 objetos de subprocesos, 286-287
intentar asignar un int a un 862-863 asignar a, 263
C ontador, 324-325 objetos derivados, 338-339 pasar a objetos, 264-265,
intercambiad), función rees objetos temporales, 311-312 281-283
crita con referencias, 270 ocultar métodos, 348-349 regresar un objeto temporal,
ipc_perm, estructura, 881 operator+ amigable, 310-311
jerarquías de clases y excep 544-548 repaso de la semana I,
ciones, 725-728 ostream, operador, 673-676 215-219
KDEHelloWorld, programa, paréntesis en macros, repaso de la semana 2 ,
924 753-754 487-494
con botones, 928 partición de datos al pasar repaso de la semana 3,
con clase Window por valor, 358-359 791-803
derivada, 926 pasar resta y desbordamiento de
con menús, 934 apuntadores a funciones, enteros, 72
Key, definición de clase, 880 473-475 Semaphore, definir, 889
límites de valores de los apuntadores const, sembuf, estructura, 888
enteros con signo, 55 279-280 semid_ds, definición de
límites de valores de los objetos de plantilla, estructura, 887
enteros sin signo, 54-55 677-681 semop, llamada de sistema,
listas enlazadas, 400-408 objetos por referencia, 888
llamar 276-277 setf, 580
a constructores múltiples, por referencia usando SharedMemory, clase, 891
424-426 apuntadores, 268-269 shmid_ds, estructura, 890
sobrecargar
el constnis ti >r. ?'*'> a d »
funciones mic-mhr.>
294-295 !¡a w . »1 V 'i
opcrutor+*. '<>*. tn*>
operatorio •. **lo ss »
solución del rn c sn n .>
número de l iK>n.u . i. II» n a r
203*204
Suma*). Iuik ion. ' I ' i s
switch. mstniss u»n. 7ur> ,'n • 1•»* ales % a i l a l d c s
1i 1ñ** *■*»''*
this. apuniailor. 2 O» 3 4 '
313-314
tipos de datos a b s t i . u t»»s list as d o h lt in r n t r i n l.i/.u l.is.
440-441 .tos, í»99
tomar la dirección de un.i listas e u la /a d a s . .'9N
referencia. 202 .t:b, i, . «•t -
typedef. palabra rcsei \ .ul.i, ..i..■.le e ,111,!i<■ '' J' /
53 <
. <>ii ip< 'lie r11r . l< ►* > ;< t
typedef usado con apunl.i .i»o un »¡<»en i.t. e »' >- I» »v^l» i >s . » i «»tes. 14
dores a funciones. <»nst.nite s enu m e i,i.iu . i■1
475-477 ».leal. •til*'. 1S ’ *‘M i •>
USOapropiado de II.i\ es i . »n »1»'N e n íen le enlazada-, |»»m» «>s. «»|»4'l .líl o l » ' • ^
instrucciones 11. 00 l i s l . u l * ». ll M) 4< I v 4< i s \M ' •’*
uso de ciclos con la p a l a b r a ni >d» >s. O í s \i >1
reservada goto. 1.S2 s entaias. 4 1 1 < >K J J '
. . , 'J J '
valores lite ra le s . 5 8 - 5 9 , 7 1 ¡ u e . e .S i • -•
regresar con apuntadores. lla m a d a s d e s is te m a Im il*. e n t e r o s . 4^. 6 . '
272-273 c \ e c . 850 |«»n^. t i p o d e ' a i i.»ld t .
regresar con reí ci encias. i pe t ). 892 l-\ a l u e s . 7 I
274-275 m s g re \ < i. 884
valores de parámetros p re m sgsm li i. 884
determinados. 117 - 11X pipe! ). 874
valores predeterminados. popen! ). 877 M
296-297 p trace. 858
se m o p (lista d o ). 888 I-, c o m a n d o , 8 I
variables y parámetros
shingct! ). 8 9 0 a cro s, 75 2 -7 r» 5
locales, 106
violaciones de la interfaz, w ait. 8 5 6 \SSI R I < i. 'ss 1
llam ar. Vea tam bién ejec u ta r ClKl.LM. ItK-ttic*. ' fl,)
165-166
d c p u t . t i t u n e i o n e s . o < »
while, ciclos. 184, 18 9 -19 1 apuntadores a m étodos. 478
C \ C C | K I C I K - S . 7 í > ( *
write(), 576-577 480
l i n u l a s i»»o e s. /6 I 6.
wxWindows, programa constructores, m últiples.
lis t a d o . '5 9
GNOMEHelloWorld, 910 4 2 4 -4 2 6
listas d elete . 236 cío I'in u . 752 ¡7 '
de parámetros, 100, 102 flu sh í), 575
lu u e .o n e s. c o m p a r a s io n
múltiples, 119 funciones, 100. I I I . 133
listado. 36 7 5 4 -7 5 5
plantillas, 662
reclusión. 124-1 28 m a n ip u la r c a d e n a s . » 6
desplazarse por medio de
métodos n o m b ra r. 774. 7 S 3
iteradores, 700
1094 Indice
A
In d ic e 1 10 5
T de acceso, 2 4 7 localizar, 7 2 2
miembro estáticas, 4 6 3 sintaxis. 7 2 1
t, shell (tesh), 8 3 6 listado, 2 4 6 -2 4 7 , 3 1 3 - 3 1 4 tu b ería (I), carácter, 9 1-9 2 ,
T A B , co m a n d o , 8 1 3 tiempo de com pilación , 26 839
ta b la s tilde (~), 1 5 9 , 7 7 6 tu b ería s, 5 6 1 , 8 7 4
de eventos, 9 2 0 tipo específico, funciones con nombre, 8 7 7 -8 7 9
v, 3 5 6 - 3 5 7 am igas, 7 1 0 redirección, 8 3 9
tabs tipo int, co n vertir en o bjeto sem idúplex, 8 7 6 -8 7 7
C on tad o r, 3 2 5 typ ed ef, in stru cción ,
caracteres de escape, 58
cód igo de escape, 3 3 tipos, 14 6 . Vea también ca te 4 7 5 -4 7 7
go rías typ ed ef, p a la b ra reservada,
ta g , a rc h iv o s, 8 1 5
crear, 1 4 5 - 1 4 6 53
v
X
X, clientes, 898
X, Organización, 898
X, Protocolo, 898
X Windows, Sistema, 809, 898
Xerox Palo Alto, Laboratorio
de Investigación, 897
XINU, 8
Xlib, 898
xLimite, excepción, 7 38
xxgdb, 825
□
PROGRAMAS EDUCATIVOS,S.A.DEC V
CALZ CHABACANO NO 65.
COL.ASTURIAS,DELGCUAUHTEMOC.
C P 06850,MÉXICO,O F
EMPRESACERTIFICADAPOR EL
INSTITUTOMEXICANODENORMALIZACION
YCERTIFICACIONA.C.BAJOLAS NORMAS
ISO-9002:1994/NMX-CC-004:1995
CON ELNO.DEREGISTRORSC-048
EISO-14001:1996/NMX-SAA-001:1998IMNC/
CON ELNO. DEREGISTRORSAA-003
am
□
Lea esto antes de abrir el softw are
Al abrir este paquete, m an ifiesta e sta r de a c u e rd o c o n lo s ig u ie n te :
En el C D -R O M se incluyen o tro s p ro g ra m a s co n p e rm is o e s p e c ia l o to r g a d o p o r su s
autores.
Este softw are se p ro p o rcio n a “co m o e s tá ” sin g a ra n tía d e n in g ú n tip o , ni e x p r e s a ni i m
plícita, incluyendo, pero no lim itad o a, las g a ra n tía s im p líc ita s d e c o m e r c ia liz a c ió n y
adecuación a un p ro p ó sito en p articu lar. N i el e d ito r ni su s v e n d e d o r e s o d is tr ib u id o r e s
asum en la resp o n sab ilid ad de c u a lq u ie r d a ñ o s u p u e s to o real q u e s e p r o d u z c a d e b id o al
uso de este program a. (A lg u n o s esta d o s no p e rm ite n la e x c lu s ió n d e g a r a n tía s im p líc ita s ,
por lo que esta ex clu sió n p o d ría no ap lic a rse en su c a s o .)
;
!
Instalación del CD-ROM
Instrucciones de instalación
para Windows 9 5 /9 8 /NT/ 2 0 0 0
1. Inserte el disco co m p acto en la u n id ad de C D -R O M .
2. En el escritorio de W indow s 95, h ag a d o b le clic en el ic o n o M i P C (M y C o m p u te r).
3 . H aga d o b le clic en el ic o n o q u e re p re s e n ta la u n id a d d e C D -R O M e n su
co m p u tad o ra.
4 . A bra el archivo R E A D M E .tx t p ara o b te n e r u n a d e sc rip c ió n d e lo s p ro d u c to s
proporcionados por terceros.
Instrucciones de instalación
para Linux y UNIX
Estas instrucciones de in stalació n asu m en q u e u sted e s tá fa m ilia riz a d o co n los c o m a n d o s
de U N IX y con la co n fig u ració n b ásica d e su e q u ip o . Ya q u e e x iste n v a ria s v e rs io n e s d e
U N IX , sólo se utilizan co m an d o s g en érico s. Si lleg a a te n e r p ro b le m a s c o n e s to s c o m a n
dos, por favor consulte la p ág in a m an ap ro p ia d a, o p ó n g a s e en c o n ta c to co n el a d m in is
trador de sistem as.
/m n t/c d ro m es s ó lo u n p u n t o d e m o n ta je , p e r o d e b e e x is tir c u a n d o se e m it a
el c o m a n d o m o u n t. T a m b ié n p u e d e u t iliz a r c u a lq u ie r d ir e c t o r io v a c ío c o m o
p u n t o d e m o n ta je , e n c a s o d e q u e n o q u ie r a u t iliz a r / m n t/c d r o m .
Abra el archivo readm e.txt p ara o b ten er u n a d escrip ció n d e los p ro d u cto s p ro p o rc io n a d o s
por terceros.
Precedencia y asociatividad de operadores
Nivel Descripción Operadores Orden de evaluación
1 Resolución de ámbito (binario, unarío) de izquierda a derecha
2 Llamadas a funciones, paréntesis, subíndice, O (1 de izquierda a derecha
selección de miembros, incremento . ->
y decremento de posfijo ♦+ —
3 sizeof. conversión implícita en C++. incremento ♦+ — de derecha a izquierda
y decremento de prefijo, más y menos unarios. •f
negación, complemento, conversión implícita en ! - ( cast )
C. sizeof (). de dirección, new de desreferencia,
new[J. delete. delete[ ] & *
4 Selección de miembros para apuntador • . >* de izquierda a derecha
5 Multiplicar, dividir, residuo * / % de izquierda a derecha
6 Suma, resta + de izquierda a derecha
7 Desplazamiento a nivel de bits << >> de izquierda a derecha
8 Desigualdad relacional de izquierda a derecha
A
V
V
A
ti
II
9 Igualdad, desigualdad == ! = de izquierda a derecha
10 AND a nivel de bits & de izquierda a derecha
11 OR exclusivo a nivel de bits - de izquierda a derecha
12 OR a nivel de bits 1 de izquierda a derecha
13 Lógico AND && de izquierda a derecha
14 Lógico OR II de izquierda a derecha
15 Condicional ?: de derecha a izquierda
16 Operadores de asignación = *= /= %= de derecha a izquierda
+= .= <<=
>>=
&=!='=
Los operadores que están en la parte superior de la tabla tienen mayor precedencia que los operadores de la parte
inferior. En expresiones que empiecen con argumentos en el conjunto de paréntesis más interno (en caso de haber),
los programas evalúan los operadores de mayor precedencia antes de evaluar los operadores de menor precedencia.
A falta de paréntesis de aclaración, los operadores del mismo nivel se evalúan de acuerdo con su orden de eva
luación, ya sea de izquierda a derecha o de derecha a izquierda.
Operadores q u e p u e d e n sobrecargarse
★ / + - % - & 1 - 1 ,
= < > <= >= ++ — << >> == 1=
&& *= /= %= &= += -= <<=
1 1 1 =
» = -> ->* (1 O new delete
Los operadores +, * y &pueden sobrecargarse para expresiones binarias y uñarías. Los operadores ., .*, :
y sizeof no pueden sobrecargarse. Además, =, (), [j y -> deben implementarse como funciones miembro no
estáticas.
Tipos d e datos d e C + +
Tipo T a m a ñ o Ra n g o
app
La siguiente operación de escritura agrega nueva información al final
del archivo.
a te *
B usca hasta el final del archivo al abrirse. La palabra “ate” significa
at end" (al final).
binary
Abre el archivo en m odo binario (no texto).
in
Abre el archivo para entrada (lectura).
nocreate
Si el archivo no existe, no se crea un nuevo archivo.
noreplace
Si el archivo ya existe, no se sobreescribe.
out
Abre el archivo para salida (escritura).
trunc
Abre y trunca un archivo existente. La nueva información que se escrih.’
en el archivo ....___________ ...„i
Palabras reservadas d e C y C + +
uto bool
break case
har class
const const cast
efault delete
do double
lse enum
explicit extern
for
friend goto
.nline int
long mutable
lew operator
private protected
-egister reinterpret_cast return short
íizeof static static cast struct
template this true
throw
typedef typeof typeid typename
unsigned using virtual void
while
j
3 v e s u s c o n o c i m i e n t o s
s i g u i e n t e nivel ©
Horvath, CCP. es consultor y profesor adjunto de medio tiempo en • Program ación de sistemas
ides locales, donde imparte clases sobre la programación en C. UNIX • Com unicación entre procesos
para bases de datos. Es autor de numerosos artículos para revistas y
•obre UNIX \ Linux, entre ellos UNIXfor í/ie M ainframet: Red Hat • Program ación de GUIs
nleashed y UNIX Unleashed, Second Edition. David participa con re
como orador en conferencias internacionales.
: Programación/Sistemas operativos
f+ para Linux
Nivel de usuario:
Visítenos en:
ww w.pearsonedlatino.com .m x