Aprendiendo C++ para Linux PDF

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 1152

Jesse Liberty y David B.

Horvath

SÁM S

las técnicas y conceptos


para dominar C++ para
Linux en sólo 21 días

sus conocimientos en la
vida real
Jesse L ib e rty
D a v id B. H o rv a th

Aprendiendo

ÏÏ++ para Linux

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

LIBERTY, JESSE Y B. HORVATH. DAVID


Aprendiendo C++ para Linux en 21 Días

PEARSON EDUCACION, México, 2(H)!

ISBN: 970-26-00 J2-X


Área: Computación

Formato: 18.5x23.5 cm P á g in a s: 1144

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

Authorized translation from the English language edition published by C o r r e c t o r a d e E s t il o


Macmillan Computer Publishing. Margarei Berson
Copyright © 2000 Ín d ic e
All rights reserved. No part of this book may be reproduced nr transmitted in I tic Scliroeder
any form or by any means, electronic or mechanical, including photocopying, C o rrecto ra de P r u ebas
recording or by any information storage retrieval system, without permission
Catolice Ilighlow er
from the Publisher.
R e v is o r e s T é c n ic o s
Primera Edición
Javad Ahdotlahi
D.R. © 2000 por Pearson Educación de Mexico, S.A. dc C.V. Sean C oughlin
Calle 4 No. 25-2do. Piso Rory Bray
Fracc. Industrial Alee Blanco
C o o r d in a d o r del E q u ip o
53370 Naucalpan de Juárez, Edo. de Mexico
Pama Ice Nelson
Cámara Nacional de la Industria Editorial Mexicana Registro No. 1031
D e s a r r o l l o de M e d io s
Reservados todos los derechos. Ni la totalidad ni parte de esta publicación Jason Haines
pueden reproducirse, registrarse o transmitirse, por un sistema de recuperación
D is e ñ o de I n t e r io r e s
de información, en ninguna forma ni por ningún medio, sea electrónico,
mecánico, fotoquímico, magnético o electroóptico, por fotocopia, grabación o Citiry Adair
cualquier otro, sin permiso previo por escrito del editor. D is e ñ o de Po r t a d a
El préstamo, alquiler o cualquier otra forma de cesión de uso de este ejem plar Aren Howell
requerirá también la autorización del editor o de sus representantes. R edactor
ISBN: 970-26-0012-X de la versión en español Eric Borgert
ISBN: 0-672-31895-4 de la versión en inglés Pr o d u c c ió n
Impreso en México, Printed in Mexico Branden Alien
Pearson Chcryl Lynch
I 2 3 4 5 6 7 8 9 0 - XX 03 02 10
Educación
Resumen de contenido
Introducción

Sem ana 1 De un vistazo


Día l Comencemos
d i
Los componentes de un programa
Variables y constantes
Expresiones e instrucciot
Funciones
Clases base w% %
Más flujo de programa
% \\ rr' % y \ A
Semana 1 Repaso ca ó <
)

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

Sem ana 2 Repaso 487

Sem ana 3 De un vistazo 499


Día 15 Herencia avanzada 501
16 Flujos 557
17 Espacios de nombres 599
18 Análisis y diseño orientados a objetos 619
19 Plantillas ‘ 661
20 Excepciones y manejo de errores 713
21 Qué sigue 747
Sem ana 3 R epaso 791

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

Sem an a 4 R e p aso 943


Apéndice A Precedencia de operadores 945
B Palabras reservadas de C++ 947
C Números binarios, ocíales, hexadecimales y una tabla
de valores ASCII 949
D Respuestas a los cuestionarios y ejercicios 965
índice 1067
Contenido
Introducción *

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 2 Los componentes de un programa de C++ 29


Un programa sencillo....................................................................................................29
Un vistazo breve a c o u t............................................................................................... 32
Comentarios ...................................................................................................................34
Tipos de comentarios ............................................................................................. 34
Uso de comentarios ................................................................................................34
Un consejo final sobre los comentarios ............................................................... 35
Funciones....................................................................................................................... 36
Uso de funciones .................................................................................................... 37
vi A prendiendo C ++ para Linux en 21 D ía s

Más acerca del compilador GNU ....................................................................................


Opciones del compilador GCC ................................................................................ 40
Tips sobre el compilador GNU ................................................................................ 4< >
Resumen........................................................................................................................... 4 1
Preguntas y respuestas ...................................................................................................... 4 1
Taller ................................................................................................................................. 42
Cuestionario .................................................................................................................42
Ejercicios .....................................................................................................................42
Día 3 Variables y constantes 43
Qué es una variable .......................................................................................................... 4 4
Cómo reservar memoria ..............................................................................................44
Cómo determinar el tamaño de los enteros y otros tipos de datos .................. 45
Uso de enteros con signo y sin s ig n o .......................................................................40
Tipos de variables fundamentales ............................................................................ 47
Definición de una variable...............................................................................................48
Sensibilidad al uso de mayúsculas ............................................................................ 40
Palabras reservadas..................................................................................................... 50
Cómo crear más de una variable a la vez ......................................................................51
Cómo asignar valores a las variables ............................................................................ 51
U sodetypedef .................................................................................................................55
Cuándo utilizar short y cuándo utilizar long ...............................................................54
Cómo sobregirar el valor de un entero s in signo ................................................. 54
Cómo sobregirar el valor de un entero con signo ................................................. 55
Uso de variables de tipo carácter.................................................................................... 56
Los caracteres como números ...................................................................................57
Caracteres de impresión especiales .......................................................................... 58
Uso de constantes .............................................................................................................58
Constantes literales......................................................................................................58
Constantes simbólicas ................................................................................................59
Uso de constantes enumeradas ....................................................................................... 60
Resumen.............................................................................................................................63
Preguntas y respuestas ...................................................................................................... 63
Taller ................................................................................................................................. 65
Cuestionario .................................................................................................................65
Ejercicios .....................................................................................................................65
Día 4 Expresiones e instrucciones 67
Instrucciones .....................................................................................................................67
Espacio en blanco........................................................................................................ 68
Bloques de instrucciones e instrucciones compuestas .......................................... 68
Expresiones ...................................................................................................................... 69
Operadores........................................................................................................................ 71
Contenido vi i

Operador de asignación ..................................................................................... 71


Operadores matemáticos ...................................................................................71
División de enteros y el operador de módulo ................................................. 72
Cómo combinar los operadores de asignación y matemáticos ............................. 74
Incremento y decrcmento ...................................................................................... 75
Prefijo y posfijo ................................................................................................ 75
Precedencia de operadores...................................................................................... 77
Paréntesis anidados ................................................................................................ 78
La naturaleza de la verdad...................................................................................... 79
Operadores relaciónales .................................................................................... 80
La instrucción if .................................................................................................... 81
Estilos de sangría .............................................................................................. 84
e l s e .................................................................................................................... 85
Instrucciones if avanzadas .............................................................................. 86
Llaves en instrucciones if complejas ..............................................................88
Operadores lógicos ................................................................................................91
Operador lógico AND ....................................................................................... 91
Operador lógico 0R ........................................................................................... 91
Operador lógico NOT ........................................................................................92
Evaluación de corto circuito ..................................................................................92
Precedencia relacional ............................................................................................ 92
Más sobre falso y verdadero ..................................................................................93
Operador condicional (temario) ............................................................................94
Resumen..................................................................................................................95
Preguntas y respuestas ............................................................................................96
Taller ...................................................................................................................... 96
Cuestionario ...................................................................................................... 96
Ejercicios .......................................................................................................... 97

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

Más acerca de los argumentos de funciones ........................ II'


Uso de funciones como parám etros para otras funciones I I2
Los parámetros son variables locales ....................................... II'
Más acerca de los valores de r e to m o ............................................ I1I
Parámetros predeterminados ............................................................. I b'
Sobrecarga de funciones .............................................................. II
Temas especiales sobre funciones .................................................... 122
Funciones en línea ......................................................................... 122
Recursión ......................................................................................... 12 1
Cómo trabajan las funciones: un v ista/o a su interior ......... I 2‘>
Niveles de abstracción .......................................................................... 1 2‘>
Partición de la RAM .....................................................................................................I 2‘>
La pila y las funciones ................................................................................................ I *2
Programas de archivos fuente múltiples (bibliotecas de funciones
creadas por el programador) ......................................................................................... I .V*
Cómo crear y utilizar bibliotecas de funciones con g++ ...................................I 34
Creación de archivos de proyecto (para make) ....................................................... 145
Funciones de bibliotecas estándar de C++ (lihg++ ) ................................................. 137
Funciones m atem áticas.................................................................................................137
Funciones de caracteres y de cadenas de caracteres............................................. I 38
Funciones generales ....................................................................................................I 30
Mucho más ..................................................................................................................... 140
R esu m en .................................................................................................................................14 I
Preguntas y respuestas ....................................................................................................... 141
Taller ...............................................................................................
Cuestionario ...................................................................................................................142
Ejercicios ....................................................................................................................... 143

DIa 6 Clases base 145


Creación de nuevos tipos ................................................................................................. 145
¿Por qué crear un nuevo tipo? ................................................................................. 146
Introducción a las clases y miembros .......................................................................... 146
Declaración de una clase ............................................................................................147
Unas palabras sobre las convenciones de denom inación .................................. 147
Declaración de un objeto .......................................................................................... 148
Comparación de clases y objetos ..............................................................................148
Cómo acceder a los miembros de las clases .............................................................. I49
Asignar a objetos, no a clases .................................................................................149
Si no lo declara, su clase no lo tendrá ...................................................................150
Definición del alcance público en com paración con la del privado .................... 15 1
Debe hacer que los datos miembro sean privados ............................................... 153
Distinción entre privacidad y seg u rid ad .................................................................. 155
C onte nid o ix

Implcmcntación de los métodos de una clase ........................................................ 156


Comprensión de los constructores y destructores ..................................................159
Constructores y destructores predeterminados...................................................160
Uso del constructor predeterminado ..................................................................160
Uso de funciones miembro const ............................................................................ 163
Distinción entre interfaz e implenientación............................................................. 164
Dónde colocar declaraciones de clases y definiciones de métodos ...................167
Aplicación de la implementación en línea ............................................................. 169
Uso de clases con otras clases como datos miembro ...........................................171
Uso de estructuras ..................................................................................................... 175
Por qué dos palabras reservadas hacen lo mismo ...........................................176
Resumen......................................................................................................................176
Preguntas y respuestas ..............................................................................................177
Taller ................................................................................................178
Cuestionario ......................................................................................................... *78
Ejercicios ............................................................................................................. 178
DIA 7 Más flujo de programa 181
Uso de los ciclos ...................................................................................................... 181
Las raíces del uso de ciclos: la instrucción goto ............................................. 181
Por qué se evita el uso de goto ........................................................................ 183
Ciclos w h ile ................................................................................................................... 18-*
Instrucciones while más complicadas ...............................................................185
Uso de continué y break en ciclos ...................................................................186
Ciclos while (trué) ............................................................................................ 189
Limitaciones del ciclo while ................................................................................19 1
Ciclos d o .. .while ....................................................................................................
194
Ciclos f or ................................................................................................................
Ciclos for avanzados .......................................................................................... 196
Ciclos fo r vacíos .................................................................................................1 "
Ciclos anidados.................................................................................................... 200
Los ciclos fo r y su alcance ............................................................................... 202
Resumen de los ciclos .............................................................................................. 203
Instrucciones switch ................................................................................................ 205
Uso de una instrucción switch con un m e n ú ....................................................208
Resumen ..................................................................................................................... 212
Preguntas y respuestas .............................................................................................. 212
Taller ......................................................................................................................... 212
Cuestionario .........................................................................................................213
Ejercicios ............................................................................................................ 213

Sem ana 1 Repaso 215


X A p re n d ie n d o C + + p ara Lin u x en 21 D ia s

Se m an a 2 D e un vistazo 223

Día 8 Apuntadores 225


¿Qué es un apuntador?................................................................................ 22*
Cómo guardar la dirección en un apuntador ........................... 22N
Elección de nombres de apuntadores .......................................................... 22l>
Uso del operador de indirección .................................................... ......... 22l>
Apuntadores, direcciones y variables .................................................................. 230
Manipulación de datos mediante el uso de apuntadores ............................ 2? I
Cómo examinar una dirección ................................................................................. 232
¿Porqué utilizar apuntadores?......................................................................................234
La pila y el heap ...............................................................................................................234
new ................................................................................................................................-
d e le te ............................................................................................................................ 236
Fugas de memoria ............................................................................................................230
Objetos en el heap ............................................................................................................ 230
Creación de objetos en el h e a p ................................................................................240
Eliminación de objetos .............................................................................................. 240
Acceso a los datos m iem b ro .....................................................................................24 *
Datos miembro en el heap ........................................................................................ 242
Apuntadores especiales y otras cuestiones ................................................................. 245
El apuntador th is .................................................................................................. 246
Apuntadores perdidos, descontrolados o am bulantes ........................................247
en
Apuntadores const ..................................................................................................... ..
Aritmética de apuntadores .................................................................................................... -
R esum en..................................................................................
_ 757
Preguntas y respuestas ..................................................................................................... —
Taller .................................................................................................................................. 257
757
Cuestionario ...................................................................................................................
Ejercicios ..................................................................................................................... ~~
ocq
DIa 9 Referencias
759
¿Qué es una referencia?................................................................................................... ..
} Uso del operador de dirección ( &) en referencias ................................................... 261
■ Las referencias no se pueden reasignar ................................................................. ^ “
* OAA
¿Qué se puede referenciar? .................................................................................... ........
Uso de apuntadores nulos y referencias n u la s ............................................................ 266
Paso de argumentos de funciones por referencia ..................................................... ^00
Cómo hacer que in tercam b iar () funcione con apuntadores ......................... 268
Cómo implementar a in te rc a m b ia re ) con referencias ..................................... 269
Comprensión de los encabezados y prototipos de funciones ................................ 271
Regreso de varios valores por medio de apuntadores .............................................. 272
Regreso de valores por referencia ..........................................................................274
Contenido XI

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

DIa 10 Funciones avanzadas 293


Sobrecarga de funciones miembro ....................................................................... 293
Uso de valores predeterminados ........................................................................... 296
Cómo elegir entre valores predeterminados yfunciones sobrecargadas............298
Constructores predeterminados ............................................................................. 298
Sobrecarga de constructores ..................................................................................299
Inicialización de objetos ........................................................................................300
Uso del constructor de copia ..................................................................................302
Sobrecarga de operadores ...................................................................................... 306
Cómo escribir una función de incremento .......................................................307
Sobrecarga del operador de prefijo ................................................................. 308
Cómo regresar tipos en funciones con operadores sobrecargados .............. 310
Cómo regresar objetos temporales sin nombre ..............................................311
Uso del apuntador th is ....................................................................................313
Sobrecarga del operador de posfijo ................................................................. 314
Cuáles son las diferencias entre prefijo yposfijo ......................................... 314
Uso del operador de suma ................................................................................317
Cómo sobrecargar a operator+ ....................................................................... 319
Cuestiones adicionales relacionadas con la sobrecarga de operadores ....... 320
Limitadores de la sobrecarga de operadores ................................................321
Qué se debe sobrecargar...................................................................................321
Uso del operador de asignación ....................................................................... 321
Operadores de conversión ......................................................................................324
Cómo crear sus propios operadores de conversión ....................................... 327
Resumen................................................................................................................. 329
Preguntas y respuestas ............................................................................................329
Taller ...................................................................................................................... 330
Cuestionario ...................................................................................................... 330
Ejercicios .......................................................................................................... 331
A p re n d ie n d o C + + p ara Lin u x en 21 D ía s

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

DIa 12 Arreglos, cadenas tipo C y listas enlazadas 367


Qué es un a rre g lo ............................................................................................................. 367
Cómo acceder a los elementos de un a r r e g lo ....................................................... 368
Cómo escribir mas allá del fin de un arreglo ....................................................... 369
Inicialización de arreglos ........................................................................................... 373
Declaración de arreglos .............................................................................................374
Arreglos de o b je to s.....................................................................................................375
Trabajo con arreglos multidim ensionales .............................................................377
Una palabra sobre los arreglos y la m e m o ria ....................................................... 379
Uso del heap para solucionar problem as relacionados con la m em oria ..............380
Arreglos de apuntadores ...........................................................................................380
Declaración de arreglos en el heap .........................................................................381
Uso de un apuntador a un arreglo en com paración con un arreglo
de apuntadores .......................................................................................................... 382
Uso de apuntadores con nombres de arreglos ..................................................... 382
Eliminación de arreglos en el heap ........................................................................ 384
Qué son los arreglos de tipo char ................................................................................. 385
Uso de funciones para cadenas ............................................................................... 387
Uso de cadenas y apuntadores ................................................................................. 389
Clases de cadenas ............................................................................................................ 391
C ontenido xiii

Listas enlazadas y otras estructuras.........................................................................398


Análisis de un caso de prueba de listas enlazadas ...............................................399
Delegación de responsabilidad...........................................................................399
Los componentes de una lista enlazada ........................................................... 400
¿Qué ha aprendido? .................................................................................................409
Uso de clases de arreglos en lugar de arreglos integrados ..................................409
Resumen................................................................................................................... 4W
Preguntas y respuestas .............................................................................................410
Taller ....................................................................................................................... 4,1
Cuestionario .......................................................................................................4 1i
Ejercicios ...........................................................................................................4 1-
DIa 13 Polim orfism o 413
Problemas con herencia simple .............................................................................. 413
Filtración ascendente ........................................................................................ 4 16
Conversión descendente .....................................................................................416
Cómo agregar objetos a dos listas ....................................................................4 19
Herencia múltiple..................................................................................................... 420
Las partes de un objeto con herencia m últiple.................................................423
Constructores en objetos con herencia múltiple ............................................ 424
Resolución de ambigüedad.................................................................................426
Herencia de una clase base compartida ............................................................427
Herencia virtual ................................................................................................. 4 3 1
Problemas con la herencia múltiple .................................................................. 435
Mezclas y clases de capacidad ...............................................................................436
Tipos df datos abstractos......................................................................................... 436
Funciones virtuales puras ...................................................................................440
Implementación de funciones virtuales puras ................................................ 4 4 1
Jerarquías de abstracción complejas ................................................................445
¿Qué tipos son abstractos? ................................................................................ 449
El patrón observador ...............................................................................................449
Unas palabras sobre la herencia múltiple, los tipos
de datos abstractos y Java .............................................................................. 450
Resumen................................................................................................................... 451
Preguntas y respuestas .............................................................................................451
Taller ........................................................................................................................452
Cuestionario ....................................................................................................... 452
Ejercicios ........................................................................................................... 453

□lA 14 Clases y fu n cio n es especiales 455


Datos miembro estáticos .........................................................................................455
Funciones miembro estáticas.................................................................................. 461
Apuntadores a funciones .........................................................................................463
Por qué utilizar apuntadores a funciones ......................................................... 467
Uso de arreglos de apuntadores a funciones ................................................... 470
XIV A p r e n d ie n d o C + + p a ra L in u x e n 21 D ia s

Paso de apuntadores a funciones hacia otras hincm ncs -l” 2


Uso de typedef con apuntadores a funciones ....... . 4 "'ñ
Apuntadores a funciones miembro ........................................
Arreglos de apuntadores a funciones m iem bro ............... is n
R esum en.............................................................................................................. *,s
Preguntas y respuestas ........................................................................ '
Taller ....................................................................................................
Cuestionario ..................................................................................... l
Ejercicios ........................................................................................................ 4,s4

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 15 Herencia avanzada 501


Contención ............................................................................................................................5<>l
Cómo tener acceso a miembros de una clase contenida ................................... 50S
Cómo filtrar el acceso a los miembros contenidos ............................................. 50X
El costo de la contención ............................................................................................ 50X
Cómo copiar por valor ................................................................................................5 11
Implementación con base en la herencia/eonlención en com paración
con la d eleg ació n ...............................................................................................................515
Delegación ..................................................................................................................... 516
o<
Herencia privada ................................................................................................................. ...
Clases a m ig a s........................................................................................................................■ '■ *4
Funciones a m ig a s.................................................................................................................*'44
Funciones amigas y sobrecarga de operadores ............................................................. *'44
Sobrecarga del operador de inserción ............................................................................. **4y
R esum en................................................................................................................................
_ 554
Preguntas y respuestas ...........................................................................................................
Taller ..................................................................................................................................... 554
_ . . 554
Cuestionario ......................................................................................................................
Ejercicios .......................................................................................................................
D ía 16 Flujos 557
Panorama general sobre los flujos ......................................................................................
Encapsulación ................................................................................................................ e c o
Almacenamiento en búfer ..........................................................................................
Flujos y búferes ..................................................................................................................
Objetos de E/S e stá n d a r..................................................................................................... 561
Redirección .................................................................................................................. 56 *
Entrada por medio de cin ................................................................................................ 562
C adenas........................................................................................................................... 564
Problemas con c a d e n a s................................................................................................ 564
o p e r a to r » regresa una referencia a un objeto is tre a m .....................................567
C onte nid o xv

Otras funciones miembro de cin ............................................................................. 567


Rnlrada de un solo carácter ............................................................................... 567
Entrada de cadenas desde el dispositivo de entrada estándar ....................... 570
cin. ignore () para limpieza de la entrada ........................................................573
peek() y putback() ............................................................................................ 574
Salida con cout ........................................................................................................ 575
Limpieza de la salida ..........................................................................................575
Funciones relacionadas ............................................................................................575
Manipuladores, indicadores e instrucciones para dar formato ........................... 577
Uso de cout .w idth() ..........................................................................................577
Cómo establecer los caracteres de llenado....................................................... 578
Cómo establecer indicadores de ío stre a m ........................................................579
Flujos en comparación con la función p rin tf () ................................................... 581
Entrada y salida de archivos ................................................................................... 585
Uso de of stream .................................................................................................. 585
Apertura de archivos para entrada y salida .....................................................585
Archivos binarios en comparación con archivos de te x to ....................................589
Procesamiento de la línea de comandos ................................................................ 592
Resumen.....................................................................................................................595
Preguntas y respuestas .............................................................................................. 596
Taller ......................................................................................................................... 597
Cuestionario .........................................................................................................597
Ejercicios .............................................................................................................597

DI/M7 Espacios de nombres 599


Comencemos con los espacios
de nombres .............................................................................................................599
Cómo se resuelven por medio del nombre las funciones y las clases ...............600
Creación de un espacio de nombres .......................................................................604
Declaración y definición de tip o s.......................................................................605
Cómo definir funciones fuera de un espacio de nombres ..............................605
Cómo agregar nuevos miembros ...................................................................... 606
Cómo anidar espacios de nom bres.....................................................................606
Uso de un espacio de nombres ............................................................................... 607
Presentación de la palabra reservada using ......................................................... 609
La directiva using .............................................................................................. 609
La declaración using ..........................................................................................611
Uso del alias de un espacio de nombres ................................................................ 613
Uso del espacio de nombres sin nombre ................................................................613
Uso del espacio de nombres estándar std .............................................................. 614
Resumen..................................................................................................................... 615
Preguntas y respuestas.............................................................................................. 616
Taller ..........................................................................................................................617
Cuestionario ......................................................................................................... 617
Ejercicios ............................................................................................................. 617
XVI A p r e n d ie n d o C + + p a ra L in u x e n 21 O ía s

D ía 18 Análisis y diseño orientados a objetos g19


¿Es C++ un lenguaje orientado a objetos*’
Qué son los modelos ..........................................................................
Diseño de software: el lenguaje de m o d e la d o j
Diseño de software: el proceso .......................................................... (->
Conceptualizacíón: la visión ............................................................... o
Análisis de los requerimientos .............................................................. 02
Casos de uso ........................................................................................... <Oó
Análisis de la aplicación ........................................................................ o
Análisis de los sistemas ......................................................................... ....<>37
Documentos de planeación ....................................................................... <>3X
Visual ¡/aciones ............................................................................................................... <>39
Artefactos ........................................................................................................................ 639
Diseño ....................................................................................................................................640
Qué son las c la s e s ...........................................................................................................M I)
Transformaciones ...........................................................................................................642
Modelo estático ............................................................................................................ 643
Modelo dinámico .......................................................................................................... 653
No se apresure a llegar al código ................................................................................... 657
R esu m en ................................................................................................................................... 65 X
Preguntas y respuestas ........................................................................................................ ^58
Taller ...................................................................................................................................... 959
Cuestionario .................................................................................................................... 959
Ejercicios ..............................................................................

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

D ía 21 Qué sigue 747


El preprocesador y el compilador ........................................................................... 748
Cómo ver el formato del archivo interm edio....................................................748
La directiva de preprocesador #define ...................................................................748
Uso de #def ine como alternativa para constantes........................................... 749
Uso de #def ine para probar el código ...............................................................749
Uso de la directiva de precompilador #else ....................................................749
XVIII A p r e n d ie n d o C + + p a ra L in u x e n 21 D ia s

Inclusión y guardias de inclusión "51


Funciones de macros "sj
¿Para qué son lodos esos paréntesis ' ................................ "5 '
Macros en com paración con funciones y plantillas "M
Funciones en línea ........................................................................ "55
Manipulación de c a d e n a s ..................................................................... 7 56
Uso de cadenas con la directiva ^define .......................... . "57
Concatenación ............................................................... 757
Macros p red efin id as........................................................................ 7 ss
ASSERT () 75X
Depuración con ASSERT () 760
ASSERT() en com paración con las excepciones 760
Efectos secundarios ......................................................................................... 761
Constantes de clases .................................................................................................... 762
Impresión de valores interinos ............................................................................... 767
Niveles de depuración ............................................................................................... 768
M anipulación de bits ............................................................................................................775
Operador AND ................................................................................................................ 775
Operador OR ...................................................................................................................776
O perador OR exclusivo ................................................................................................ 776
El operador de com plem ento .......................................................................................776
Cómo encender bits ....................................................................................................... 776
Cómo apagar bits ............................................................................................................ 776
Cómo invertir los bits ..................................................................................................... 777
Campos de bits ......................... 777
Estilo de codificación ..........................................................................................................780
Uso de sa n g ría s .................................................................................................................781
Llaves .................................................................................................................................781
Líneas largas ..................................................................................................................... 781
Instrucciones sw itch ..................................................................................................... 781
Texto del p ro g ra m a ..........................................................................................................782
Nombres de identificadores ......................................................................................... 783
Ortografía y uso de m ayúsculas en los nom bres ...................................................783
Comentarios ..................................................................................................................... 784
Acceso .............................................................................................................................. 784
Definiciones de clases .................................................................................................. 785
Archivos de encabezado ................................................................................................ 785
ASSERT() 785
const .................................................................................................................................. 786
Los siguientes pasos ............................................................................................................786
Dónde obtener ayuda y orientación ...........................................................................786
R ev istas..............................................................................................................................786
C onte nid o XIX

Manténgase en contacto ...................................................................................... 787


Su próximo p a so ................................................................................................... 787
Resumen...................................................................................................................... 787
Preguntas y respuestas ...............................................................................................788
Taller .......................................................................................................................... 789
Cuestionario ......................................................................................................... 789
Ejercicios ............................................................................................................. 790

Se m an a 3 R e paso 791

Se m an a 4 De un vistazo 805

D ía 22 El entorno de programación de Linux 807


Filosofía e historia .....................................................................................................808
POSIX ....................................................................................................................... 808
El sistema X W indows.............................................................................................. 808
• Uso de los editores de Linux ................................................................................... 809
ed. ex. vi y las variantes de vi ........................................................................... 809
emacs de GNU ....................................................................................................8 *-
ctags y etags ........................................................................................................ 81 ^
Lenguajes ................................................................................................................... 8 ,6
gee y g++ .............................................................................................................8 ^
Lenguajes de secuencias de comandos (perl, sed. awk) ................................818
E L F ....................................................................................................................... 818
Bibliotecas compartidas ......................................................................................... 819
Construcción o creación .......................................................................................... 820
make....................................................................................................................... 8^
Depuración.................................................................................................................8^3
................................................................................................................................823
xxgd b ............................................................................................ 8“3
Sesión de ejemplo de depuración con gdb ........................................................826
Control de versiones ................................................................................................ 822
R C S ....................................................................................................................... 828
Documentación .........................................................................................................830
Páginas del m anual.............................................................................................. 830
i n f o ........................................................................................................................831
HOWTOs y FAQs .............................................................................................. 832
Resumen......................................................................................................................832
Preguntas y respuestas ...............................................................................................833
Taller .......................................................................................................................... 834
Cuestionario ......................................................................................................... 834
Ejercicios ..............................................................................................................834
XX A p r e n d ie n d o C + + p a ra L in u x e n 21 D ia s

D ía 23 Programación shell 835


Qué es un s h e ll........................ s
Shclls disponibles en Linux s ;f'
Operación de los shclls y conceptos básicos tic sintaxis s
Características del shell s; *
Redirección de E/S ............................................................
Tuberías ............................................................................
V ariables............................................... S in
Variables utilizadas por el shell 840
Variables establecidas por el shell 84
Procesamiento en segundo plano, suspensión \ control ile procesos s il
Complctación de com andos ...................................................................... 842
Sustitución de com andos ...................................................................... 845
Sustitución mediante com odines ................................................. 843
Sustitución mediante cadenas ........................................................ *44
Sustitución mediante la salida de un com ando ........................ 844
Sustitución mediante variables ...................................................................... 844
Historial y edición de com andos ......................................................................................845
Creación de alias de com andos ......................................................................................... 845
Secuencias de com andos de los shclls ............................................................................. 846
Variables ............................................................................................................................. 846
Estructuras de control ......................................................................................................847
Archivo(s) de inicio de shell ...............................................................................................849
R esu m en ....................................................................................................................................850
Preguntas y respuestas .......................................................................................................... 850
Taller ........................................................................................................................................ 851
Cuestionario ......................................................................................................................851
Ejercicios ..........................................................................................................................851

D ía 24 Programación de sistem as 853


Procesos ................................................................................................................................... 853
Creación y term inación de procesos ......................................................................... 854
Control de procesos ....................................................................................................... ..
El sistema de archivos /p ro c .................................................................................... 858
Estado y prioridad del proceso .................................................................................... 859
Algoritmos de adm inistración de procesos ..............................................................860
Subprocesos .....................................................................................................................861
Subprocesamiento sim p le ........................................................................................ 862
Subprocesamiento múltiple ....................................................................................862
Creación y term inación de subprocesos ................................................................... 862
Administración ................................................................................................................864
Sincronización ................................................................................................................864
C onte nid o XXI

Resum en...................................................................................................................... 8 7 1
Preguntas y respuestas ............................................................................................... 871
Taller .......................................................................................................................... 871

ri n
Cuestionario .........................................................................................................87
Ejercicios ............................................................................................................. 87

DIa 25 Comunicación entre procesos 873


Antecedentes ............................................................................................................. 873
Tuberías ......................................................................................................................874
popen y p eló se.....................................................................................................877
Tuberías con nombre (FIFOs) ..................................................................................877
Comunicación entre procesos de System V .......................................................... 879
Creación de claves .............................................................................................. 879
• Estructura de permisos de IPC ...........................................................................881
Comandos ipes e iperm ......................................................................................881
Colas de mensajes .............................................................................................. 882
Semáforos ............................................................................................................. 886
Memoria compartida ..........................................................................................890
IPC .......................................................................................................................
Resumen..................................................................................................................... 893
Preguntas y respuestas .............................................................................................. 893
Taller ......................................................................................................................... 893
Cuestionario ........................................................................................................ 893
Ejercicios .............................................................................................................894

D ía 26 Programación de la GUI 895


El escritorio de Linux .............................................................................................. 896
Qué es GNOM E.........................................................................................................899
Cómo obtener GNOME y otros recursos de GNOME .................................. 900
Cómo obtener GTK++ y otros recursos de GTK++ .......................................901
Qué es KDE .............................................................................................................. 901
Cómo obtener KDE y otros recursos de KDE ................................................. 902
Programación con C++ en el escritorio de Linux ................................................. 903
Fundamentos de la programación en GNOME ......................................................904
Cómo envolver a GTK++ con wxWindows ................................................... 909
Creación de su primera aplicación de wxWindows: “¡Hola, mundo!” ........ 910
Cómo agregar botones a su propia clase de ventana de wxWindows ..........913
Cómo agregar un menú a su propia clase de ventana wxW indow s.............. 920
Creación de aplicaciones wxWindows más complejas: wxStudio ...............923
Cómo obtener wxWindows y otros recursos
de wxWindows ................................................................................................ 924
Fundamentos de la programación de KDE ............................................................ 924
Creación de su primera aplicación KDE: “Helio World” .............................. 924
Creación de su propia clase de ventana de KDE ............................................. 926
I XX i i A p r e n d ie n d o C + + p a ra L in u x e n 21 D ia s

Cómo agregar botones a su propia clase ile \c n ta n a tic KI>I *J2N


Interacción de objetos por m edio de señales \ ranuras o %i
Cómo agregar un menú a su propia clase de %entana de KDI *>*5
Creación de aplicaciones KDI! mas com plejas: K D eselo p o *7
R esu m en .............................................. o to
Preguntas y respuestas ............................................................. o to
Taller ........................................................... 04 l
Cuestionario ............................................. ‘>4 I
Ejercicios ..................................................................... 04 I

S e m an a 4 R e p a s o 943

Apéndice A Precedencia de operadores 945

Apéndice B Palabras reservadas de C++ 947

Apéndice C Núm eros binarios, octales, he xad e cim ale s


y una tabla de valores A S C II 949
Más allá de la base 10 .................................................................................................. 050
Un vistazo a las bases .......................................................................................................... 051
Números b in a rio s .................................................................................................................... 052
Por qué base 2 ................................................................................................................. 055
Bits, Bytes, y Nibbles .....................................................................................................055
Qué es un KB ...................................................................................................................054
Números binarios ............................................................................................................ 054
Números o c ta le s ..................................................................................................................... 054
Por qué octal .....................................................................................................................055
Números hexadecimales ..................................................................................................... 055
ASCII ....................................................................................................................................... 058

Apéndice D Respuestas a los cuestionarios y ejercicios 965


Día 1 ......................................................................................................................................... 965
Cuestionario .....................................................................................................................065
Ejercicios ......................................................................................................................... 066
Día 2 .........................................................................................................................................966
Cuestionario .....................................................................................................................966
Ejercicios .........................................................................................................................967
Día 3 ........................................................................................................................................ 967
Cuestionario .................................................................................................................... 967
Ejercicios .........................................................................................................................968
D»a 4 ........................................................................................................................................969
Cuestionario ....................................................................................................................969
Ejercicios ........................................................................................................................970
C o n te n id o xxiii

Día 5 ............................................................................................................................. 971


Cuestionario ..........................................................................................................971
Ejercicios .............................................................................................................. 972
Día 6 .............................................................................................................................975
Cuestionario ......................................................................................................... 975
Ejercicios ..............................................................................................................976
Día 7 ............................................................................................................................ 978
Cuestionario ......................................................................................................... 978
Ejercicios ............................................................................................................. 979
Día 8 ............................................................................................................................ 981
Cuestionario ......................................................................................................... 981
Ejercicios .............................................................................................................981
Día 9 ............................................................................................................................ 983
Cuestionario ......................................................................................................... 983
Ejercicios ............................................................................................................. 983
Día 1 0 .......................................................................................................................... 986
Cuestionario .........................................................................................................986
Ejercicio ............................................................................................................... 987
Día 11 ..........................................................................................................................992
Cuestionario .........................................................................................................992
Ejercicios ............................................................................................................. 992
Día 1 2 ..........................................................................................................................994
Cuestionario ......................................................................................................... 994
Ejercicios .............................................................................................................994
Día 1 3 ..........................................................................................................................995
Cuestionario .........................................................................................................995
Ejercicios ............................................................................................................. 996
Día 1 4 ......................................................................................................................... 997
Cuestionario .........................................................................................................997
Ejercicios .............................................................................................................998
Día 1 5 ........................................................................................................................ IOO4
Cuestionario ....................................................................................................... 1004
Ejercicios .......................................................................................................... 1005
Día 1 6 ........................................................................................................................ 1009
Cuestionario ....................................................................................................... 1009
Ejercicios ........................................................................................................... 1010
Día 1 7 ........................................................................................................................ 1012
Cuestionario ....................................................................................................... 1012
Ejercicios ............................................................................................................1013
Día 1 8 ........................................................................................................................ 1013
Cuestionario ....................................................................................................... 1013
Ejercicios ............................................................................................................1014
xxiv A p r e n d ie n d o C + + p a ra L in u x e n 21 D ia s

Día 1 9 ...................................................................... I '“ *


Cuestionario ..................................... H»I s
Ejercicios ................................................. H»l*>
Día 2 0 ..................................................................... ln - s
Cuestionario ...............................
Ejercicios ...................................
Día 21 ................................................. ln ;|
Cuestionario .................................. U >'|
Ejercicios .......................................... HM2
Día 2 2 .................................................................. |,n s
Cuestionario ................................................ MM5
Ejercicios ....................................................................................
Día 2 3 ........................................................................................................ ,(M 7
Cuestionario ............................................................................... HM7
Ejercicio ......................................................................................
Día 2 4 ............................................................................................................................. ,,MS
Cuestionario ......................................................................................................... lo^.s
c- - •
Ejercicios ..................................................................................... H)M
Día 2 5 ..................................................................................................................................... ,(M 0
Cuestionario ................................................................................................................. HMO
Ejercicios ...................................................................................................................... 1
Día 2 6 ....................................................................................................................................

Ejercicios ...................:.................................................................................................. ..

índice 1067
C onte nid o XXV

Acerca de los autores


Autores principales
Jesse Liberty es autor de C++ para principiantes, así como de varios libros más sobre
C++ y desarrollo de aplicaciones Web y programación orientada a objetos. Jesse es pre­
sidente de Liberty Associates. Inc., en donde ofrece programación personalizada, entre­
namiento. enseñanza y consultoría. Fue vicepresidente de transferencias electrónicas de
Citibank e Ingeniero de software distinguido en AT&T. También se desempeña como
editor de la serie de libros Programming From Scratch, de Que. Ofrece apoyo para sus
libros en http://www.LibertyAssociates.com.

D av id B. Horvath, CCP, es Consultor en jefe del área de Filadelfia, en Pensilvania.


Estados Unidos. Ha sido consultor por más de 14 años y también es profesor adjunto de
medio tiempo en universidades locales, en donde enseña temas que incluyen la progra­
mación en C/C++, UNIX y técnicas de bases de datos. Tiene una maestría en Dinámica
organizacional de la Universidad de Pensilvania (y sigue tomando clases). Ha ofrecido
seminarios y talleres a sociedades profesionales y empresas en todo el mundo.

David es autor de UNIX fo r the Mainframer (Prentice Hall/PTR), autor contribuyente de


UNIX Unleashed, segunda edición (con crédito en portada), Red Hat Linter, segunda edi­
ción, Using UNIX, segunda edición (Que), UNIX Unleashed, tercera edición, Leam Shell
Programming in 24 Hours, Red Hat Linter 6 Unleashed, Linter Unleashed, cuarta edición,
y ha escrito varios artículos en revistas.
Cuando no se encuentra escribiendo en el teclado de su computadora, puede encontrárse­
le trabajando en el jardín de su casa, tomando un baño de agua caliente en la bañera, o
involucrado en varias actividades que molestan a las personas. Tiene más de 12 años de
casado y tiene varios perros y gatos (y parece que la cantidad de ellos sigue en aumento).

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 .

Jo n a th a n P a rry -M c C u llo c h es c o n s u lto r té c n ic o p a ra u n a in s titu c ió n f in a n c ie ra i m p o r ­


tante en la ciu d ad de L o n d res, en d o n d e o fre c e su e x p e r ie n c ia en c r ip to g r a f ía p a ra s i s ­
tem as au to m atizad o s p ara c o m e rc io de in s tru m e n to s fin a n c ie ro s a p la /o . T a m b ié n e s c r ib e
aplicaciones G U I m u ltip la ta fo rm a en C + + . p rin c ip a lm e n te p o r d iv e rs ió n , y e s un f a n á tic o
de Linux d eclarado. T ien e un títu lo d e p rim e ra c la se en I n g e n ie ría e le c tr ó n ic a y e s tá
com placido de q u e no ten g a re le v a n c ia en re la c ió n c o n su tra b a jo .

Puede co n tactarse con J o n a th a n en jm @ a n tip o p e .o r g . si tie n e p r e g u n ta s s o b re la s p a rte s


en las que él co n trib u y ó p ara este libro, o s o b re c u a lq u ie r o tra c o s a q u e se le o c u rra . U n a
advertencia: él realm en te o d ia el c o rre o b a su ra , a s í q u e n o lo e n v íe .

Hal M o r o f f ha d iseñ ad o siste m a s in c ru sta d o s d u ra n te 2 0 a ñ o s. O r ig in a r io d e la C o s ta


Este, trabajó varios añ o s en E u ro p a y J a p ó n , y a h o ra es p r o g r a m a d o r d e tie m p o c o m p le to
en Silicon Valley, C alifo rn ia. E scrib e co n re g u la rid a d en u n a c o lu m n a d e L iu u .\
M a g a zin e, y lee la fu en te d e in fo rm a c ió n s ie m p re q u e p u e d e .
C o n te n id o xxvii

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

Definitivamente debo agradecer a mi coautor. Jesse I .ib e m . ( u.uul»» c '.m»l \ \«> em p e /a -


mos a discutir sobre eslc provéelo, pedí una muestra de un libro Je la s e iie .■\¡n en i!u ru lo
en 21 días, de Sams. Ella me envió una copia de Sttms Teat h h m r \t fr ( m 2 / f),j\ \
Después de revisar ese libro, com enté a fa r o l que este libro ser ía m ucho m a s s e n c i l l o si
ya existiera un libro de C++ similar al libro de C\ R esultó que va h a b í a u n o el de
Jesse. A sí que la creación de este libro fue m ucho m á s sen cilla g r a c i a s a Li informa», ion
de C++ que ya se había escrito y que se tom o c o m o b a s e D e s p u é s d e i o d o . hav ta n ta s
maneras de explicar un ciclo w h ile o el concep to de p o l i m o r f i s m o .

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

Y por último, aunque no menos importante, se encuentran las personas de D C A N et


(www.dca.net) que proporcionan alojam iento virtual de sitios W eb para mi página W eb,
manejan mi dominio, mantienen los servidores de mi correo, y tienen una gran reserva
de módem que significa llamadas locales para mí. Estas personas son m uy respon sivas:
la línea nunca estuvo ocupada, y ¡Andrew W hite es un A dm inistrador de sistem a s sin
igual! Ellos se encuentran en el área m etropolitana de F iladelfia, y he e n v ia d o a m uchas
organizaciones para que ocupen sus servicios. N o, no recibo ningún p ago por esta propa­
ganda (sin embargo, ellos me cuidan muy bien).
Estoy seguro que con tanto ajetreo pude haber olvid ado a alguien. ¡L es a segu ro que no
fue intencional!
— D a v id
C on te n id o XXIX

Pearson Educación de México


El personal de Pearson Educación de México está comprometido en presentarle lo mejor
en material de consulta sobre computación. Cada libro de Pearson Educación de M éxico
es el resultado de meses de trabajo de nuestro personal, que investiga y refina la infor­
mación que se ofrece.

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.

Si usted tiene alguna pregunta o comentario acerca de cualquier libro de Pearson


Educación de México, existen muchas formas de entrar en contacto con nosotros.
Responderemos a todos los lectores que podamos. Su nombre, dirección y número
telefónico jamás formarán parte de ninguna lista de correos ni serán usados para otro
fin, más que el de ayudamos a seguirle llevando los mejores libros posibles. Puede
escribirnos a la siguiente dirección:

Pearson Educación de México


Attn: Editorial División Computación
Calle Cuatro No. 25, 2o Piso,
Col. Fracc. Alce Blanco
Naucalpan de Juárez, Edo. de México
C.P. 53370.
Si lo prefiere, puede mandar un fax a Pearson Educación de México al (525) 5387-0811.

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

Quién debe leer este libro


Con este libro, usted no necesita tener experiencia previa en programación para aprender
C++. Con él empezará desde cero y aprenderá tanto el lenguaje como los conceptos rela­
cionados con la programación en C++. A medida que avance en este entorno tan gratifi­
cante, descubrirá que los numerosos ejemplos de sintaxis y análisis de código son una
guía excelente. No importa si es principiante o si tiene algo de experiencia en progra­
mación, la clara organización de este libro le facilitará y agilizará el aprendizaje de C++.
Este libro no le enseña cómo usar o instalar Linux, aunque se incluye una copia en el
CD-ROM. Existen muchos otros buenos libros para ese propósito (como las series Linux
Unleashed, de Sams).
C++ (al igual que su antecesor, el lenguaje C) es un lenguaje estándar, por lo que en esen­
cia el lenguaje es el mismo tanto en Linux como en otras plataformas. En otras palabras,
el lenguaje C++ que usted aprenda en este libro podrá aplicarlo en muchos sistemas y
compiladores diferentes.
Hay dos compiladores distintos disponibles en Linux — gee (Compilador C/C++ G NU) y
eges (Sistema Experimental del compilador GNU). El CD-ROM contiene eges, mientras
que las distribuciones de Linux anteriores (y la versión MS-DOS actual) utilizan gcc. Es
difícil distinguir uno del otro desde la línea de comandos. Sin embargo, eges tiene las
características más avanzadas y actuales de C++.
Hay versiones disponibles de los compiladores GNU para la mayoría de las plataformas
y sistemas operativos. Por esto, las habilidades que usted obtenga al leer este libro le
serán útiles no sólo para Linux.
Debe estar ejecutando Linux para utilizar el compilador incluido.
2 A p r e n d i e n d o C ++ p a r a L in u x e n 21 D ia s

C o n ve n cio n e s

Estos cuadros resaltan in fo rm ació n q u e le a y u d a a q u e su p ro g ra m a r io n e n


C++ sea más eficiente y efectiva
_____________________________________________________ .... ____________ I

Preguntas frecu en tes


FAQ: ¿Q u é hacen las FAQs?
Respuesta: Las preguntas frecuen tes p ro p o rc io n a n m a y o re s d e ta lle s a ce rca d e l u so d e l
lenguaje, y aclaran las áreas potenciales de co n fu sió n .

Las precauciones d irigen su ate n ció n h acia los p ro b le m a s o efecto s se cu n d a


Precaución rios que pueden ocurrir en situacio nes esp ecificas.

Estos cuadros proporcionan d efinicion es claras de té rm in o s ese n ciale s.

D ebe N O '.■ iDEBE


^ » ^ » ,1 «y i tv a
DEBE utilizar los cuadros "Debe/N o debe" N O D EBE p asar p o r a lto la ú til in fo rm a c ió n
para encontrar un resumen rápido de un q ue se o fre ce en estos cu ad ro s.
principio fundam ental de una lección.

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í

m



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

Una observación para


los program adores de C
Tal vez el material de los primeros cinco días le sea familiar.
Eche una ojeada a estos primeros días y haga los ejercicios
para asegurarse de contar con los conocim ientos necesa­
rios para avanzar al día 6, “Clases base”. Si conoce algo de
C pero no lo ha utilizado en Linux, será mejor que lea deteni­
damente la introducción a los compiladores GNU que se da
en las primeras dos lecciones.

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

con C++. En el día 1. “Comencemos'*. > en el d ía 2. “ I o s c o m í * m e n t e s d e un p r o g r a m a


de C++*\ se presentarán los c o n c e p to s b á s ic o s d e la p r o g r a m a c i ó n \ el 11ti i«> tic los p r o ­
gramas. En el día 3. "Variables y c o n s ta n t e s " , a p r e n d e r á s o b r e e s t o s e l e m e n t o s n t o m o
utilizar datos en sus programas. En el día 4. " E x p r e s i o n e s e in s t r u c c i o n e s " , a p r e n d e r á a
ramificar los programas con base en los d at o s p r o p o r c i o n a d o s s en las c o n d i c i o n e s e n c o n ­
tradas al ejecutar los programas. En el día 5. “ E t m c i o n c s ” . a p r e m í e l a lo q u e s o n las tnn-
ciones y có m o se utilizan, y en el día 6. " C l a s e s b as e" , a p r e n d e r a a c e r c a d e las c l a s e s \
los objetos. El día 7. “Más flujo de p r o g r a m a " , le e n s e n a r a m a s t o s a s s o b r e el I l u t o del
programa, y al finalizar la p rim era s e m a n a ya e s ta r á e s c r i b i e n d o v e r d a d e r o s p r o g r a m a s
orientados a objetos.
S em 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

Es v e rd ad , el a c r ó n im o G N U se r e fie r e a si m is m o , p o ro fu e r r o .id o di* o sa m a


ñ e ra a p r o p ó s ito . E ste t ip o d e d e s ig n a c ió n t ie n e su h is t o r ia e n lo s p r o d u c t o s
r e la c io n a d o s c o n U N IX , p e r o n o c u b ie r t o s p o r e s ta m a r c a r e g is t r a d a U n sis
te m a o p e r a t iv o no c o m e r c ia l s im ila r a U N IX fu e X IN U : " X IN U N o es U N I X ”

De manera que, cuando piensa que está em itiendo c o m a n d o s de L i n u x , e n r e a l i d a d e s t á


usando utilerías GNU. Las personas que trabajan en L i n u x n o v ie ro n u n a r a / o n p a r a d u ­
plicar el trabajo realizado por las personas de G N U . lista es parte d e la l i l o s o h a d e l ’N1X:
reutilizar, no recrear.
Si analiza detenidamente la documentación incluida con su sistem a L inux, d eb e e n c o n ­
trar alguna mención de GNU.
El proyecto GNU también incluye com piladores de C y C ++ (junto con m uchas otras
herramientas, lenguajes y utilerías). El com pilador de C se co n o ce c o m o g c c. y el c o m ­
pilador de C++ se conoce como g++ (en Linux) o gxx (en otros sistem a s o p e ia tiv o s ,
como DOS, que no permiten el uso del signo de suma en los nom bres cíe ai ch iv o s).
Lo agradable acerca de las herramientas G NU es que las hay para m uchas plataform as,
no sólo para Linux. Así pues, puede obtener un com pilador G N U para Linux, o para D O S ,
o para DEC (Compaq) Alpha, o para muchos otros sistem as.
Estó significa que puede aprender a utilizar un com pilador y usarlo en m u ch os lu g a ics.
También puede tener un proyecto personal de C++ en Linux en el que trabaje por su c u e n ­
ta cuando en realidad deba estar trabajando para su patrón, in clu sive si só lo tiene a c c e so
a un equipo basado en Microsoft Windows o DOS. Claro que, ¡usted nunca haría alg o así!

Cómo obtener las herramientas GNU


La manera más fácil de obtener el compilador G N U y otras herram ientas e s instalar la
copia de Linux que viene en el CD-ROM que se in clu ye en este libro. Para obtener deta­
lles sobre la instalación, tendrá que revisar las instrucciones que vienen en el C D -R O M , ya
que este libro se enfoca en C++, no en Linux. Pearson Educación publica tam bién algunos
buenos libros sobre Linux (no estoy haciendo publicidad para esta com pañía, ya que he
contribuido en algunos capítulos en varios de esos libros).
Com encem os 9

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.

Una breve historia acerca de C++


Los lenguajes computacionales han experimentado una impresionante evolución desde
que se construyeron las primeras computadoras electrónicas para ayudar en los cálculos
sobre trayectorias de artillería, durante la segunda guerra mundial. En un principio, los pro­
gramadores trabajaron con las instrucciones de computadora más primitivas; el lenguaje
de máquina. Estas instrucciones se representaban con largas cadenas de unos y ceros. En
poco tiempo se inventaron los ensambladores para asignar instrucciones de máquina a
instrucciones nemotécnicas manejables y entendibles para los humanos, como ADD y
MOV.
O ía 1

Con el tiempo surgieron los lenguajes de nivel m á s al io , c o m o B A S IC y ( *< >**( *:slo s


lenguajes permitieron que los programadores trabajaran co n a lg o q u e se aproM inah;1 a pa­
labras y oraciones, com o Let I = HH) (H acer que I = KH)). E stas m s i n i a i ' ,nt's sC
cían a lenguaje de máquina por m edio de los intérpretes \ c o m p ila d o r e s 1 n f
traduce un programa a medida que lo lee. co n v irtien d o las i n s t r u c c i o n e s de! p r o g r a m a ,
o código, directamente en accion es. Un c o m p i l a d o r traduce el c o d i g o a u n f o r m a t o
intermedio. Este paso se con oce co m o com pilar, y produ ce u n a r c h i v o o b j e t o . A c o n ­
tinuación, el compilador invoca a un c n ia z a d o r . el cual a su v e / c o n v i e r t e u n o o m a s
archivos objeto en un programa ejecutable.
Debido a que los intérpretes leen el có d ig o en el orden en el qu e e s t á e s c r i t o , y lo e je c u ­
tan al instante, los programadores pueden utilizarlos co n fa cilid a d . L o s c o m p ilu d o ie s
llevan a cabo los pasos adicionales de com pilar y enlazar el c ó d ig o , lo cual e s a lg o in ­
conveniente. Sin embargo, los com piladores producen un program a que e s muy v e lo z
cada vez que se ejecuta, debido a que la tarea de traducir el c ó d ig o fuente* a l e n g u a j e de
máquina, lo cual consume mucho tiem po, ya se ha realizado.
Otra ventaja de muchos lenguajes com pilados, co m o C+ + . es que se p u ed e d istrib u ir el
programa ejecutable a personas que no tengan el com pilador. C on un len g u a je interpreta­
do, es necesario tener el intérprete para ejecutar el programa.
Algunos lenguajes, com o Visual Basic, llaman al intérprete b ib lio teca en tie m p o d e e je ­
cución (runtime library). Java llama máquina virtual (V M ) a su intérprete en tie m p o de
ejecución. Si ejecuta código de JavaScript, el navegador W eb (c o m o Internet E x p lo rer o
Netscape) proporciona la VM.
Durante muchos años, el principal objetivo de los program adores de c o m p u ta d o r a s fue
escribir breves piezas de código que se ejecutaran rápidam ente. El program a n e c esita b a
ser pequeño, debido a que la memoria era costosa, y tam bién n ecesitab a ser v e lo z , ya que
el poder de procesamiento también era costoso. A m edida que las co m p u ta d o ra s se han
vuelto más pequeñas, económ icas y veloces, y a m edida que el c o sto de la m em o ria se
ha reducido, estas prioridades han cambiado. En la actualidad, el tiem p o de un progra­
mador cuesta más que la mayoría de las computadoras utilizadas en las em presas. U n c ó d i­
go bien escrito y fácil de mantener es im prescindible. “Fácil de m antener” s ig n ific a qu e a
medida que cambian los requerimientos de las em presas, se puede ex ten d er y m ejorar el
programa sin incurrir en costos demasiado altos.

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.

Programación procedural, estructurada


y orientada a objetos
Hasta hace poco, se pensaba que los programas eran una serie de procedimientos que
actuaban sobre los datos. Un procedimiento, o función, es un conjunto de instrucciones
específicas que se ejecutan una tras otra. Los datos estaban bastante separados de los
procedimientos, y el truco de la programación era llevar el registro de cuáles funciones
llamaban a cuáles otras funciones, y cuáles datos se cambiaban. Para que esta situación
potencialmente confusa tuviera sentido, se creó la programación estructurada.
12 D ía 1

La idea principal de la programación estructurada es tan s i m p l e c o m o la n i c a d e d i \ id ir >


conquistar. Se puede pensar que un programa d e c o m p u t a d o r a c o n s i s t e e n u n c o n i u n t o d e
tareas. Cualquier tarea que sea dem asiado com pleja c o m o para s e r d e s c r i t a , s i m p l e m e n t e
se divide en un conjunto de tareas más pequeñas, hasta q u e éstas s e a n lo suf i c i e n t e m e n t e
pequeñas e independientes com o para entenderlas c o n f a c i l i d a d .
Como ejemplo, calcular el salario prom edio de t o d o s los e m p i c a d o s d e u n a c o m p a ñ í a es
una tarea algo compleja. Sin embargo, se puede dividir e n las s i g u i e n t e s s u b t a r e . i s
1. Averiguar el ingreso que obtiene cada persona.
2. Contar cuántas personas hay en la nóm ina.
3. Sumar todos los salarios.
4. Dividir el total entre el número de personas que hay en la nóm ina.

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

En la actualidad, la forma en la que utilizamos las computadoras, con menús.


T é r m in o n u e v o
botones y ventanas, fomenta un método más interactivo y controlado por even­
tos para la programación de computadoras. C ontrolado p o r e v e n to s significa que al ocurrir
un evento (que el usuario haga clic en un botón o seleccione una opción de un menú) el pro­
grama debe responder. Los programas son cada vez más interactivos, y esto se ha converti­
do en algo importante a diseñar para ese tipo de funcionalidad.

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.

La programación o rien ta d a a o b je to s trata de responder a esas necesidades,


T é r m in o n u e v o
proporcionando técnicas para manejar la enorme complejidad. lograr la reu­
tilización de componentes de software y acoplar los datos con las tareas que manipulan
esos datos.
La esencia de la programación orientada a objetos es tratar a los datos y a los procedi­
mientos que actúan sobre esos datos como un solo objeto (una entidad independiente con
una identidad y ciertas características propias).

C++ y la program ación orientada a objetos


C++ soporta completamente la programación orientada a objetos, incluyendo los tres
pilares del desarrollo orientado a objetos: encapsulación. herencia y polimorfismo.

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.

C++ soporta la idea de que distintos objetos hacen “lo a d ecu a d o ” , m ed ia n te lo q u e se


conoce como polimorfismo defunciones y polim orfism o de c la se s. P oli s ig n ific a
muchos, y morfismo significa forma. Polim orfism o se refiere a que el m is m o n om b re
toma muchas formas; este tema se trata en el día 10, “F u n cio n es a v a n za d a s” , y en el
día 13, “Polimorfismo”.

Cómo evolucionó c++


A medida que el análisis, el diseño y la programación orientados a o b jeto s em p eza ro n a
tener popularidad, Bjame Stroustrup tomó el lenguaje m ás popular para el d esa rro llo de
software comercial, el lenguaje C, y lo extendió para proporcionar las ca ra cterística s
necesarias que facilitaran la programación orientada a objetos.
C om e nce m os
j

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.

¿Primero debo aprender C?


Esta pregunta surge inevitablemente: “Debido a que C++ es un superconjunto de C.
¿primero debería usted aprender C?” Stroustrup y la mayoría de los programadores de
C++ están de acuerdo en que no sólo es innecesario aprender C primero, sino que tam­
bién podría ser desventajoso.
En este libro no se da por hecho que usted tiene experiencia previa en programación.
Sin embargo, si usted es programador de C, los primeros cinco capítulos de este libro
son, en su mayor parte, un repaso, pero le indicarán también cómo utilizar los com pi­
ladores GNU. En el día 6 empezaremos con el verdadero trabajo del desarrollo de soft­
ware orientado a objetos.

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

Para la mayoría de los estudiantes d e C + + . el e s t á n d a r A N SI s e r a m \ i s i b l e I s t e e s t á n d a r


ha sido estable desde hace tiempo, y los p r i n c i p a l e s f a b r i c a n t e s lo m a l i c i a n N o s l i e m o s
asegurado de que todo el código que aparece en e s t a e d i c i ó n d e l l i b i o se a p e g u e al c s t a n -
dar ANSI.

Prepárese para programar


C++ requiere, tal vez con más exigencia que otros lenguajes, que el p r o g i a m a d o r d i s e ñ e el
programa antes de escribirlo. Los problemas triviales, c o m o los que se d i s c u t e n en los p r i ­
meros capítulos de este libro, no requieren de m ucho d i s e n o . Sin e m b a i g o . los p i o h l c m n s
complejos, como los que tienen que enfrentar a diario los p r o g r a m a d o r e s p o s e s i o n a l e s , si
requieren del diseño, y entre más com pleto sea, existe una m ayor p r o b a b i l i d a d d e que el
programa resuelva, a tiempo y dentro del presupuesto, los p r o b l e m a s q u e d e b e l e s o h e i .
Un buen diseño también ayuda a que un programa esté relativam ente e x e n to de e n o r e s v
sea fácil de mantener. Se ha estim ado que el 9 0 c/r del c o s t o del s o f t w a i c e s el c o s t o
combinado de la depuración y el m antenim iento. El que u n b u e n d i s e ñ o p u e d a r e d u c i r
esos costos puede tener un impacto considerable en el costo total del p i o y e c t o .

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

debe entender el problema y diseñar la solución antes de implementarla), es mejor que


aprenda la sintaxis fundamental y la semántica de C++ antes de aprender las técnicas de
análisis y diseño formales.

El entorno de desarrollo GNU/Linux


En este libro se da por hecho que usted utiliza los compiladores GNU en un entorno ba­
sado en texto, o una combinación similar. Es decir, su compilador debe tener un modo en
el que usted pueda escribir directamente en la pantalla sin preocuparse por un entorno
gráfico, como Windows o Macintosh. Este modo se conoce como modo de consola y
es estándar para los compiladores GNU. Si trabaja en un entorno diferente, tendrá que
buscar una opción, como consola o easy window, o revisar la documentación de su com ­
pilador. Si utiliza uno de los entomos gráficos en Linux, puede abrir una ventana de emula­
ción de terminal (como xterm) para que pueda trabajar en modo de sólo texto.

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.

Los c o m p ila d o re s G N U sí co n sid e ran im p o rta n te la e x te n sió n d e arch ivo.


D e b e utilizar .cxx o .c++.

M u c h o s o tro s c o m p ila d o re s d e C + + n o c o n sid e ra n im p o r ta n te la e x te n sió n .


Si n o especifica la extensión, m u c h o s u tiliz a rá n .cpp d e fo r m a p r e d e te rm in a ­
da. Sin e m b a rg o , te n g a c u id ad o ; a lg u n o s c o m p ila d o re s tr a ta n a lo s a rc h iv o s
.c c o m o c ó d ig o d e C y a los arch ivo s .cpp c o m o c ó d ig o d e C ++. N u e v a m e n te ,
revise su d o c u m e n tac ió n .
18 D ía 1

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.

Cómo compilar el código fuente


Aunque el código fuente de su archivo es algo críp tico, y aunque cu a lq u iera q u e no sepa
C++ tendrá problemas para entender su función, de todas form as es lo qu e lla m a m o s un
formato entendible para los humanos. Su archivo de c ó d ig o fuente no es un program a, y
no es posible ejecutarlo.

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

O si se encuentra en una plataforma que no soporte el sig n o de sum a ( + ) en un nom bre


de archivo, como M S-DOS, puede utilizar lo siguiente:
gxx archivo, cxx -o a r c h i v o . e x e

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

Después de compilar su código fuente, se produce un archivo objeto. Por lo general,


este archivo se nombra con la extensión .o (Linux) o .obj (M S-DOS). Sin embargo, éste
todavía no es un programa ejecutable. Para convertir esto en programa ejecutable, debe
ejecutar su enlazador.

Cómo crear un archivo ejecutable


con el enlazador
Por lo general, los programas de C++ se crean al enlazar uno o más archivos objeto con
una o más bibliotecas. Una biblioteca es una colección de archivos que se pueden enla­
zar y que se proporcionan con su compilador, se compran por separado, o usted puede
crearlos y compilarlos. Todos los compiladores de C++ vienen con una biblioteca de fun­
ciones (o procedimientos) y clases útiles que usted puede incluir en su programa. Una
función es un bloque de código que realiza un servicio, como agregar dos números o
imprimir en pantalla. Una clase es una colección de datos y funciones relacionadas; ha­
blaremos mucho sobre las clases, empezando en el día 5, “Funciones”.

Los pasos para crear un archivo ejecutable son los siguientes:


1. Crear un archivo de código fuente, con una extensión .c++ o .cxx.
2. Compilar el código fuente en un archivo con el formato de objeto.
3. Enlazar su archivo objeto con cualquier biblioteca necesaria para producir un pro­
grama ejecutable.

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

i Hola, mundo!", su primer programa de C++


Los libros tradicionales de programación empiezan enseñándole c ó m o escribir las palabras
iHola, mundo!” en la pantalla, o una variación de esa instrucción. Esta consagrada tradi­
ción se sigue también en este libro.
Escriba
p . ^1
el primer programa
1 o
directamente en <
V - 'V - ' L C ll I V s l l L V ^ U l l
.j U UU11U1 ) OVV1HV7
I J P . Q n i l P C r\r\ ^ « - . . * 1 • i
espues de escribirlo y estar seguro de que está correcto, guarde el archivo, c o m p ílelo , en­
ace o y ejecútelo. Debe imprimir las palabras “¡Hola, m undo!” en la pantalla. N o se preo­
cupe mucho poi la forma en que funciona; este ejemplo es sólo para que usted se fam iliarice
con el ciclo de desarrollo. Todos los aspectos de este programa se tratarán en lo s sigu ien tes
dos días.
C om encem os 21

T o d o s los listad os d e este libro v ie n e n en el C D - R O M p a ra fa c ilita rle las cosas.


Nota <0
T ie n e n n o m b re s d e arch ivo en el f o r m a t o l st DD NN .c xx. DO es el n ú m e r o d e l
d ía (01. 02, etcétera) y NN es el n ú m e ro d e l lista d o (01 p a ra el p rim e ro d e l día.
02 p a ra el se g u n d o , y así su cesivam en te ).
El primer listado del libro (listado 1.1) es el archivo l s t o i - oí .cxx.
N o to d o s los arch ivo s d e lista d o se c o m p ila rá n . C u a n d o é ste se a el caso, se
in d icará en el texto.

El s ig u ie n t e listad o , al ig u a l q u e el re sto d e lo s lis t a d o s d e e ste lib ro , c o n ­


Precaución tie n e n ú m e ro s d e lín e a a la iz q u ie rd a . E sto s n ú m e r o s se u tiliz a n s ó lo c o m o
re fe ren cia. N o d e b e e scrib irlos. P o r e je m p lo , e n la lín e a 1 d e l lis t a d o 1.1
d e b e rá escribir:
#include <iostream.h>.

Entrada L ist a d o 1.1 El program a "¡Hola, mundo!

1 : ^include <iostream .h>


2:
3: int main()
4: {
5: cout « "iHola, mundo!\n°¡
6: return 0;
7: }

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

Si hay errores, examine cuidadosam ente el c ó d i g o pura ver e n q u e d i f i e r e il cl l i s i a d o


1.1. Si ve un error en la línea 1. co m o ca n n o t f i n o f i l e í o s t r e a m . h , r e v í s e l a
documentación de su com pilador para ver las i n d i c a c i o n e s s o h r e la r u t a J e l o s a r c h i ­
vos de encabezado (que se encuentran e n el s u h d i r e e i o r i o i n e l u d e ) o l a s v a r i a b l e s d e
entorno.

Si recibe un error que indique que no hay prototipo para mam u le un c o m p d a d o i q u e no


sea GNU), agregue la línea in t m a in (). antes de la línea V I*n in d o s l o s p ro g ra m a s de
este libro necesitará agregar esta línea antes del e o m ie n /o de la tunen mi m a m I a m a y o ­
ría de los compiladores no requieren esta línea, pero alg u n o s s í

Si éste es el caso, su programa final debe lucir co m o el siguiente:


1: tfinclude < i o s t r e a m . h >
2: i n t main ( ) ; // l a m a y o r í a de l o s com piladores no n e c e s i t a n esta linea
3: in t main()
4: {
5: cout « " ¡ H o l a , m u n d o ! \ n ” ;
6: return 0 ;
7: }

Trate de ejecutar el programa l s t - 0 1 -01 ; debe aparecer en su pantalla lo sig u ien te:

¡Hola, mundo!

Si es así, ¡felicidades! Acaba de escribir, compilar y ejecutar su primer program a de C + + .


Tal vez no parezca mucho, pero casi todos los programadores p rofesion ales de C + + e m p ie ­
zan con este programa.

Uso de las bibliotecas estándar


Para asegurar que los lectores que utilicen com piladores a n tig u o s no te n g a n p ro b le m a s
con el código que viene en este libro, hemos u tilizad o los arch ivo s de e n c a b e z a d o a l e sti­
lo antiguo
tt inelude <iostre am .h >

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.

Uso del compilador g++


Todos los programas de este libro se probaron con el compilador g++ de GNU, versión
2.9.5; muchos de ellos también se probaron con la versión 2.7.2. En teoría, debido a que
es un código que se apega al estándar ANSI, todos los programas de este libro se deben
ejecutar sin problemas en cualquier compilador compatible con ANSI, de cualquier
fabricante.
En teoría, la teoría y la práctica son lo mismo. En la práctica, nunca es así.
Para que usted pueda poner manos a la obra, esta sección le presenta brevemente la forma
de editar, compilar, enlazar y ejecutar un programa por medio del compilador GNU. Si uti­
liza un compilador distinto, los detalles de cada paso tal vez sean algo diferentes. Incluso si
utiliza el compilador GNU versión 2.7.2 o 2.9.5, revise su documentación para averiguar
cómo proceder a partir de aquí.

Construcción del proyecto ¡Hola, mundo!


Para crear y probar el programa ¡Hola, mundo!, siga estos pasos:

1. Elija un editor y cree un archivo.


2. Escriba el código como se muestra en el listado 1.1 (o puede utilizar el editor para
abrir el archivo ls t 0 l -01. cxx que se incluye en el CD-ROM).
3. Guarde el archivo y salga del editor.
4. Escriba el comando para compilación (g++ lst0 1 -01 .cxx -o ls t0 1 -01 si utiliza
Linux; para otros sistemas, consulte su documentación).
5. Escriba el nombre del archivo ejecutable para ejecutar el programa.
(2 4
D ía 1

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!

Puede ver esto si coloca intencionalm enie un error en su p r o g r a m a Si el p r o g r a m a


¡Hola, mundo! se ejecuta sin problemas, edítelo ahora y quite la l l a v e de c i e r r e d e la línea
7. Ahora su programa debe lucir com o el del listado 1.2.

E ntrada L is t a d o 1.2 Muestra de un error de co m p ila ció n

1: U include <iostream.h>
2:
3: int main()
4: {
5: cout « "¡Hola, mundo!\n";
6: return 0;

Compile este programa y G NU le mostrará el sigu ien te e r r o r ( s e da p o r h e c h o q u e u tilizó


el listado lst01 -02.cxx):
./Ist01-02.cxx: In function 'int main()':
./lst01 *02.cxx:7: parse error at end of input

Otros compiladores le mostrarán un m ensaje de error c o m o el sig u ien te:


lst01-01 .cpp, line 7: Compound statement missing terminating; in
^function main().

o tal vez un error como el siguiente:


F:\Mcp\Tycpp21d\Testing\List0101 .cpp(8) : fatal error C1004:
unexpected end of file found
Error executing cl.exe.
}
Este error le indica el archivo y el número de línea en el que está el p ro b lem a , a d e m á s de
cuál es el problema (aunque confieso que es un p o co críp tico ). O b serv e q u e e l m e n s a je
de error apunta a la línea 7. El compilador notará que hace falta una llave de cierre al e n c o n ­
trar el fin de archivo; es en esa línea donde se reconocerá el error. A lgu n as v e c e s lo s errores
sólo indican vagamente la razón del problema. Si un co m p ila d o r pudiera id e n tific a r a la
perfección cualquier problema, él m ism o arreglaría el có d ig o .
C om e n ce m os 25

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

P ¿Q ué es tiem p o de co m p ila ció n ?


R El tiempo de com pilación es cuando usted ejecuta su com p ilad or, en contraste con
el tiem po de enlace (cuando ejecuta el en la /a d o r) o el ncni¡u> tir c j a tu /<*/» (cuando
ejecuta el programa). Éstos son térm inos u tilizad os por el program ador fiara identi­
ficar los tres tiem pos en los que generalm en te surgen los errores D e h u io a que el
compilador G NU invoca al enla/ad or si la co m p ila ció n tiene é x ito , tal vez no se dis­
tinga bien el tiempo de enlace (pero, créam e, esta ahí).

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

3. Escriba el siguiente programa y compílelo. ¿Qué error aparece?


1: inelude <iostream.h>
2: int main()
3: {
4: cout << ‘¡Hola, mundo!\n’¡
5: return 0;
6: }

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

Ent r a d a L is t a d o 2.1 A q u í se m uestran los c o m p o n e n te s de un program a de C«-*

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!

A nálisis En la línea 1 se incluye el a r c h i v o í o s t r e a m . h e n el a r c h i v o a c t u a l

He aquí la forma en que trabaja el preprocesador: el prim er carácter e s el sím b o lo # (de


numeral), el cual es una señal para el preprocesador. Otro nom bre para e ste Mimo es
gato. El preprocesador se ejecuta cada ve/, que inicia su com pilador. Ll preprocesador lee
el código fuente en busca de líneas que inicien con 0 y actúa sobre e sa s lin eas antes de
que se ejecute el compilador. En el día 21, "Qué sig u e , se habla d eta lla d a m en te sobre el
preprocesador.
in e lu d e es una instrucción del preprocesador que dice: "I.o que sig u e es un nom bre de
archivo. Encuentre ese archivo y coloque aquí su con ten id o . L os paréntesis angulares (< y
>) que rodean al nombre de archivo le indican al preprocesador que busque e ste archivo
en todas las ubicaciones usuales. Si su com pilador está co n fig u ra d o en form a correcta,
estos sím bolos ocasionan que el preprocesador bu sq ue el a rch iv o io s t r e a m .h en el
directorio que contiene todos los archivos .h para el co m p ila d o r (su b d irecto rio s con el
nombre inelude). El archivo io s tr e a m .h (flu jo de entrada-salid a) es u tiliz a d o por c o u t,
el cual ayuda a escribir en la pantalla. El efecto de la línea 1 es incluir el arch ivo
io strea m .h en este programa com o si usted lo hubiera escrito. El preprocesador se ejecu­
ta antes que el compilador cada vez que éste es invocado. El preprocesador con vierte
cualquier línea que em piece con el signo # en un com an d o e sp ec ia l, con lo cual prepara su
archivo de código para el compilador.
El programa en sí em pieza en la línea 3 con una función llam ada main ( ) . T odo programa
de C++ tiene una función main ( ) . Una Junción es un bloque de có d ig o que realiza una o
más acciones. Por lo general, las funciones son invocadas o llam adas por otras fu nciones,
pero main () es especial. Al iniciar su programa, el sistem a o p erativo llam a autom ática­
mente a main().
main() , al igual que todas las funciones, debe declarar el tipo de valor que va a regresar.
En el listado 2.1 (lst02-01.cxx), el tipo de valor de retorno para main () e s i n t , lo que
significa que, cuando termine, esta función regresará un entero al sistem a operativo. En
este caso, regresa el valor entero 0, com o se muestra en la línea 6. R egresar un valor al
Los com ponentes de un p ro g ra m a de C + + 31

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.

Los compiladores de GNU y algunos otros le permitirán declarar a main()


Nota para que regrese el valor void. Esto ya no es C++ legítimo, y usted no debe­
ría formarse malos hábitos. Haga que main() regrese int, y que simple­
mente regrese un 0 como su última línea. Esto le indicará que el programa
terminó sin problemas.

Algunos sistemas operativos (como Linux) le permiten probar el valor regre-


Nota sado por un programa. La convención es regresar 0 para indicar que el pro­
grama terminó en forma normal. Si utiliza el intérprete de comandos bash
o pdksh, la variable de entorno $? contiene este valor de retorno. Si el valor
regresado por el programa es diferente de 0, significa que hubo un proble­
ma. Utilice distintos valores enteros para catalogar el tipo de errores que
ocurren en la ejecución de un programa.

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

La función main () termina en la línea 7 con la llave de cierre.


32 D ía 2

Un vistazo breve a cout


En el día 16 verá la manera de u u li/a r el objeto co u t p.n.t im p m n u d a to s c*n la pantalla.
Por ahora, puede utilizar este objeto sin necesidad de en ten d ei «. o in p le ta m e n te o»m«i fun­
ciona. Para imprimir un v a lo r en la pantalla, csetib a la palab ia «.«»ut . si-punla «Icl o¡x*ra-
dor de inserción ( « ) . el cual se crea oprim ien do d os \e v e s A u n q u e en ic .i Ik I.uí son
dos caracteres. C++ los trata co m o si fueran tino
Coloque sus datos después del operador de inserción Id listad«» 2 2 m tie siia e«»mo se uti­
liza esto. Escriba el ejem plo exactam ente c o m o esta escrito , per«» s u s t n u s a el nom bre
“Jesse Liberty’* por el suyo.

Entrada L is t a d o 2 .2 Uso de cout

1 // Listado 2.2 uso de cout


2 #include <iostream.h>
3 int main()
4 {
5 cout « "Saludos a todos.\n”;
6 cout « "Aquí hay un 5: ° << 5 << *\n” ;
7 cout « "El manipulador endl escribe una nueva linea en la pantalla
8 cout <<
9 endl;
10 cout « "Aqui hay un número muy grande:\t" << 70000 << endl;
11 cout « “Aqui está la suma de 8 y 5:\t“ << 8+5 << endl;
12 cout « "Aqui hay una fracción:\t\t" << (float) 5/8 << endl;
13 cout « "Y un número muy, muy grande:\t” ;
14 cout « (double) 7000 * 7000 <<
15 endl;
16 cout « "No olvide reemplazar Jesse Liberty con su n o m b r e ...\n" ;
17 cout « "¡Jesse Liberty es un programador de C++!\n";
18 return 0;
19 }

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

A lg u n o s c o m p ila d o re s (d istin to s d e g + + ) e m ite n u n m e n sa je d e e rr o r q u e


in d ic a q u e se c o lo q u e la su m a e n tre p a ré n te s is a n te s d e p a s a r la a c o u t. En
e ste caso, la line a 11 q u e d a ría d e la s ig u ie n t e m a n e ra :

11: cout << 'Aquí está la suma de 8 y 5:\t’ << (8+5) << endl;

En la linea 2, la instrucción tfinclude <iostream .h> ocasiona que se agregue el


----------1 archivo iostream .h a su código fuente. Esto es necesario si usa cout y sus fun­
ciones relacionadas.
En la linea 5 está el uso más sencillo de cout: imprimir una cadena o serie de caracteres.
El símbolo \n es un carácter de formato especial, el cual le indica a cout que imprima
un carácter de nueva línea en la pantalla.
En la línea 6 se pasan tres valores a cout, y cada valor está separado por el operador de
inserción. El primer valor es la cadena "Aquí hay un 5: ". Observe el espacio que está
después de los dos puntos. Éste es parte de la cadena. A continuación, el valor 5 se pasa
al operador de inserción y al carácter de nueva línea (siempre encerrado entre comillas
dobles o sencillas). Esto ocasiona que la línea
Aquí hay un 5: 5
se imprima en la pantalla. Como no hay un carácter de nueva línea después de la primera
cadena, el siguiente valor se imprime inmediatamente después. Esto se conoce como
concatenación de los dos valores.
En la línea 7 se imprime un mensaje informativo, y luego se utiliza el manipulador endl.
El propósito de endl es escribir una nueva línea en la pantalla. (En el día 16 verá otros
usos para endl.)

e n d l sig n ific a fin de linea (e n d o f line), y el ú ltim o ca rá cte r es u n a le tra ele,


n o u n uno.

En la línea 10 se presenta un nuevo carácter de formato, \ t . Este carácter inserta un


carácter de tabulación y se utiliza en las líneas 10 a 13 para alinear la salida. La línea 10
muestra que no sólo se pueden imprimir enteros, sino también enteros largos. La línea
11 muestra que cout hará una suma simple. El valor 8 + 5 se pasa a cout, pero se impri­
me un 13.
En la línea 12, el valor 5/8 se inserta en cout. El término ( f lo a t ) le indica a cout que
este valor se debe evaluar como su equivalente en decimal, por lo que se imprime una
fracción en su representación decimal. En la línea 14 se da el valor 7000 * 7000 a cout.
D ía 2

y se utiliza el término (d o u b le) p a r a in d i c a r le a c o u t q u e recibirá u n n u m e r o d e puní*’


flotante con doble precisión. Todo e sto se v a a e x p l i c a r e n el ti la L " V a r i a b l e s > C° I,S
tantes”, al tratar el tema de los tipos de dalos
En la línea 17 usted puso su nombre, y la salida confirm a que. d e c u s a m e n t e , usted e s un
programador de C ++. ¡Y debe de ser verdad, ya qu e la com p u tad ora lo d ijo '

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

Es necesario nombrar las funciones de manera que no exista contusión en cuanto a lo


que hacen, y rediseñar y rescribir las piezas de código confusas para que sean evidentes. En
la mayoría de los casos, los comentarios son la excusa para que un programador flojo
mantenga su código confuso.
Esto no es para sugerir que no se deben utilizar los comentarios, sólo que no se debe
depender en exceso de ellos para clarificar el código contuso: en vez de eso. arregle el
código. En resumen, escriba bien su código y utilice comentarios para complementar la
2
comprensión.
El listado 2.3 muestra que el uso de los comentarios no afecta el procesamiento del pro­
grama o su salida.

Entrada L i s t a d o 2 .3 Uso de co m en tario s

1: //inelude < io s t r e a m . h >


2:
3: i n t m ain ()
4: {
5: /* é s t e es un com entario
6: y se e x t ie n d e h a s t a l a marca de c i e r r e de
7: c o m e n ta rio re p re se n ta d a por un a s t e r i s c o y una b a r r a d i a g o n a l *,
8: co ut << " ¡ H o l a , mundo!\n ;
9: // e s t e com en tario te rm in a a l f i n a l de l a l i n e a
10 co ut « " ¡ E s e com en tario t e r m i n ó ! \ n " ;
11
12 // l o s c o m e n ta rio s con do ble b a r r a d i a g o n a l pueden i r s o l o s en una l i n e a
13 /* i g u a l que l o s c o m e n ta rio s con b a r r a d i a g o n a l y a s t e r i s c o */
14 r e t u r n 0;
15 } __________________ ____________________________

¡H o la , mundo!
¡E se c o m e n ta rio te rm in ó!

El compilador ignora completamente los comentarios de las líneas 5 a 7, así


A nálisis
como los de las líneas 9. 12 y 13. El comentario de la línea 9 termina al finalizar
la línea, pero los comentarios de las líneas 5 y 13 necesitan una marca de cierre de
comentario.

Un consejo final sobre los com e n tario s


Los comentarios que indican lo que es obvio son innecesarios. De hecho, pueden ser contra­
producentes, ya que el código puede cambiar y tal vez el programador olvide actualizar el
comentario. Lo que es obvio para una persona puede ser confuso para otra, por lo que se
requiere de buen juicio al decidir si se van a incluir comentarios o no.

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.

Un p ro g ra m a se e je c u ta lín ea p o r línea en el o r d e n en q u e a p a r e c e en su c ó d i g o Itiente,


hasta que llega a u n a fu n c ió n . H n to n c e s el p r o g r a m a se r a m i l i c a p a ra e i e c u t a r la lime uní.
C u a n d o la función te rm in a , r e g r e s a el c o n tro l a la línea d e c o ilig o q u e se e n c u e n t r a i n m e ­
d ia tam e n te d e s p u é s de la lla m a d a a la fu n ció n .
U na b uena a n alo g ía para e sto es el p ro c e s o de afilar su lápiz. Si e sta h a c i e n d o un d ib u j o \
se ro m p e la p u n ta d e su lápiz., tien e q u e d e ja r de dibujar, ir a s a c a r p u n ta al lá p iz > lu e g o
reg resar a d ibujar. C u a n d o un p r o g r a m a n e c e sita qu e se re a lic e un sei s icio, p u e d e lla m a r
a un a fu n ció n p a r a q u e re a lic e el s e rv ic io y luego c o n t in u a r c u a n d o se t e r m i n a d e e j e c u ­
tar la función. El lista d o 2 .4 m u e s tr a e sta idea.

En t r a d a L is t a d o 2 . 4 Muestra de una llamada a una función

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

Estamos dentro de main


S a l id a Estamos dentro de FuncionDeMuestra
Estamos de regreso en main

L a función FuncionDeMuestra () se d efin e en las lín e a s 5 a 8. A l s e r lla m a d a ,


A nálisis
im p rim e un m e n saje en la pan talla y lu eg o reg resa.

i
Los c o m p o n e n te s de un p ro g ra m a de C + + 37 ¡

El p rogram a en sí em pieza en la línea 13. En la línea 15. main() im p rim e un m e n sa je


que dice que se encuentra en main(). D espués de im prim ir el m e n saje, en la línea 16 se
llam a a FuncionDeMuestra ( ). Esta llam ada ocasiona que se ejecu te n los c o m a n d o s de
FuncionDeMuestra(). En este caso, toda la función consiste en el c ó d ig o de la línea 7, el
cual im prim e otro mensaje. Al finalizar FuncionDeMuestra() (en la línea 8). reg resa al

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 .

Las funciones se com ponen de un encabezado y un cuerpo. El e n c a b e z a d o co n sta del


tipo de valor de retorno, el nom bre de la función y los p a rá m e tro s p ara esa función. L os
parám etros para una función perm iten que se pasen valores a esa función. P or lo tanto, si
la función fuera a sum ar dos números, los núm eros serían los parám etros para la función.
U n e n ca b ezad o de función típico se vería así:

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.

En el d ía 5, "F u n c io n e s ", se tr a ta c o n m á s d e t a lle el t e m a d e la s fu n c io n e s . Lo s


tip o s q u e p u e d e re g re sa r u n a fu n c ió n se tr a ta n c o n m á s d e t a lle e n el d ía 3. La
in fo r m a c ió n q u e se p r o p o r c io n a h o y es p a ra q u e u ste d o b t e n g a u n p a n o r a m a
g e n e ra l, p u e s en casi t o d o s su s p r o g r a m a s d e C + + u t iliz a rá las fu n c io n e s .
38 D ía 2

El listado 2.5 muestra una función que torna d o s p a r á m e t r o s e n t e r o s \ roeros.« u n \ a l o r


entero. Por ahora no se preocupe p o r la sin ta xi s m p o r los d e t a l l e s os | k *c i ! icon s o h r c la
forma de trabajar con valores enteros (por e j e m p l o , i n t xi. e s o se tr at a c o n d eta lle e n e l
día 3.

E n t r a d a | L is t a d o 2 .5 M uestra de una función se


1: tfinclude <iostream.h>
2: int Suma (int x, int y)
3: {
4:
5: cout « “En Sumaí), se recibieron *
6: return (x+y);
7: }
8:
9: int main()
10: {
11: cout « "¡Estoy en main()!\n';
12: int a, b, c;
13: cout « “Escriba dos números:
14: cin » a;
15: cin » b;
16: cout << “\nLlamando a Suma()\n“;
17: c=Suma(a,b);
18: cout « D\nDe regreso en main().\n”;
19: cout « “c contiene el número " << c
20: cout « "\nSaliendo...\n\n";
21: return 0;
22: }

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

El procesamiento se ramifica hacia la función Suma(), la cual empieza en la línea 2. Los


parámetros a y b se imprimen y luego se suman. El resultado se regresa en la línea 6 y se
asigna a la variable c. con lo cual termina la función; la función m ain() toma nuevamente
el control.
En las líneas 14 y 15 se utiliza el objeto cin para obtener un número para las variables a
y b, y se utiliza cout para escribir los valores en la pantalla. En los siguientes días verá
con más detalle las variables, así como otros aspectos de este programa. ■g»

Más acerca del compilador GNU


Siempre que tenga duda sobre la manera de hacer algo, debe revisar la documentación
de su compilador. El compilador g++ de GNU no es la excepción. Con el compilador
se incluyen un manual en línea (conocido como páginas de manual) y archivos de
información.
Puede tener acceso al manual en línea con sólo escribir lo siguiente,
man g++
O si se encuentra en MS-DOS
man gxx
Y puede revisar las páginas de manual más grande para gcc y g++ con el siguiente
comando:
man gcc
También puede revisar el archivo de información (in f o) combinado para gcc y g++ con
el siguiente comando:
info gcc
Si solicita información para g++, sólo obtendrá la página de manual (así que no des­
perdicie su tiempo).

info es la principal documentación en línea para Linux y las herramientas


GNU. Está basado en hipertexto y puede producir salida en varios formatos.
Puede obtener información rápida sobre esto con el siguiente comando:
man info

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

Opciones del compilador GCC


Las opciones del com pilador se esp ecifican por m e d io tic p a r á m e t r o s o a r g u m e n t o s de
línea de com andos. Las opciones del com pilador q u e se u t i l i / a n c o n m a s f r e c u e n c i a se
muestran en la tabla 2.1.

Ta b l a 2.1 Opciones del com pilador


O p c ió n S ig n ific a d o
(ninguna opción Compilar y cnla/ar el programa en la linea tic c o m a n d o s \
utilizada) producir un archivo ejecutable con nom bre predeterm inado
(a.out para Linux, a.exe para M S-D O S )
-c Compilar pero no enla/ar: crear archivos con extensión .o.
-Dmacro=valor Definir mac ro dentro del programa com o v a l o r .
-E Preprocesar, no compilar ni enlazar, la salida se envía a la pantalla.
-g Incluir información de depuración en el a rch iv o ejecutable.
- 1d ir Incluir d i r como directorio para buscar archivo de encabezado (en
donde el nombre está entre < y >).
-L d ir Incluir d i r como directorio para buscar archivos de biblioteca
(utilizados por el enlazador para resolver re fe recias externas);
varios directorios se separan con punto y com a ( ; ).
- lb ib li o t e c a Incluir b i b l i o t e c a al enlazar.
-0 Optimizar.
-o a rch ivo Guardar el archivo ejecutable com o a r c h i v o .

-Wall Habilitar advertencias para todo el código que se deba evitar.

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

Tips sobre el compilador GNU


Es una buena idea utilizar la opción -o al crear un archivo ejecutable para poder e sp ec i­
ficar el nombre de dicho archivo (de no ser así, la siguiente co m p ila ció n lo sobrescribirá
con el nombre predeterminado).
De manera predeterminada, cualquier m ensaje de error se dirige a la pantalla. Tal vez
quiera redirigir los mensajes a un archivo para poder revisarlos con calm a por m ed io de
un editor de texto. Esto es especialm ente útil cuando hay m uchos errores. Puede redirigir
la salida hacia un archivo de la siguiente manera.
Los com ponentes de un p ro g ra m a de C ++

g++ lst02-0i.cxx -o lst02-01 > lst02-01.lst


Desde luego que la selección del nombre de archivo es cuestión de usted. A mi me gusta
utilizar la extensión .lst.
A medida que construya bibliotecas de funciones (verá más sobre las funciones en el día
5), podrá compilarlas una vez y volver a utilizarlas. Si no cambia el código fuente, no hay
necesidad de volver a compilar. La opción -c crea el archivo objeto intermedio compila­
do y con formato. Cuando quiera utilizar el código de un archivo compilado (el archivo
objeto), sólo necesitará incluir el nombre de éste en la línea de comandos.
Como siempre, el tip más importante sobre cualquier compilador o herramienta de Linux
es revisar las páginas del manual (o archivo de información). Revise las opciones, prué­
belas y vea cómo se comportan. ¡Tal vez puedan ayudarle!

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

Qué es una variable


En C++, una v a ria b le es un lugar para guardar información. Una variable es
una ubicación en la memoria de su computadora en la que puede guardar un
valor, y desde la cual puede recuperar posteriormente ese valor.
Imagine que la memoria de su computadora es una serie de pequeñas casillas.
Hay muchas casillas alineadas unas con otras. Cada casilla (o ubicación de
memoria) está numerada en forma secuencial. Estos números se conocen como
direcciones de memoria. Una variable reseiva una o más casillas en las que
usted puede guardar un valor.
144 D ía 3

El nombre de la variable (por ejem plo, (invariable) es u n a d i q u e l a c o l o c a d a e n u n a de


estas casillas para que la pueda encontrar f á c ilm e n te sin n e c e s i d a d d e c o n o c e r su d i r e c ­
ción de memoria. La figura 3.1 es una r e p r e s e n ta c ió n e s q u e m á t i c a tic e s ta idea. C o m o
puede ver en la figura. miVariable em pieza en la d ire c c ió n d e m e m o r ia IOV D e p e n d ie n d o
de su tamaño, miVariable puede ocupar u n a o m á s d i r e c c io n e s d e m e m o r ia .

Fig u r a 3.1 miVarmblo


Una representación
esquemática de la
memoria.

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.

Cómo reservar memoria


Al definir una variable de C++, debe indicarle al com pilador de qué tipo es: entero,
carácter, etc. Esta información le indica al com pilador cuánto esp a cio debe reservar, así
como el tipo de valor que usted quiere guardar en la variable.
Cada casilla es de 1 byte de longitud. Si el tipo de variable que usted d esea crear es de 4
bytes de longitud, necesitará 4 bytes de memoria, o 4 casillas contiguas. El tipo de varia­
ble (por ejemplo, entero) le indica al com pilador cuánta m em oria (cuántas ca silla s) debe
reservar para la variable.
Debido a que las computadoras utilizan bits y bytes para representar valores, y debido a
que la memoria se mide en bytes, es importante que usted com prenda y se fam iliarice
con estos conceptos. Para ver un repaso com pleto sobre este tem a, lea el apéndice C,
“Números binarios, octales y hexadecim ales y una tabla de valores A S C II” .
Variables y constantes 45

Cómo determinar el tamaño de los enteros


y otros tipos de datos
En cualquier computadora, cada tipo de variable ocupa una sola cantidad invariable de
espacio. Es decir, un entero podrá ser de 2 bytes en un equipo y de 4 en otro, pero siem­
pre va a ser así en las dos computadoras.

Una variable de tipo char (utilizada para guardar caracteres) es, por lo general, de 1 byte
de longitud.

En la mayoría de las computadoras, un entero corto es de 2 bytes de longitud, un


entero largo es de 4 bytes (aunque los hay de 8 bytes), y un entero (sin las palabras
reservadas short o long) puede ser de 2 o 4 bytes. El tamaño de un entero se deter­
mina según la computadora (si es de 16, 32 o 64 bits) y el compilador utilizados.
En computadoras personales modernas de 32 bits (Pentium o posteriores) que utilizan
compiladores modernos (por ejemplo, gee versión 2 o posterior), los enteros son de 4
bytes. En este libro se da por hecho que un entero ocupa 4 bytes, aunque este valor
puede variar. El listado 3.1 le ayudará a determinar el tamaño exacto de estos tipos en
su computadora.

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

10: cout « “El tamaño de un doble es:\t* - < sizeof (double)


** « “ bytes.\n°;
11: cout « “El tamaño de un booleano es:\t* -*< sizeof (bool)
i» « " bytes.\n‘;
12:
13 return 0;
14: >

El tamaño de un entero es: 4 bytes


El tamaño de un entero corto es: 2 bytes
El tamaño de un entero largo es: 4 bytes
S a l id a El tamaño de un carácter es: 1 bytes
El tamaño de un punto flotante es: 4 bytes
El tamaño de un doble es: 8 bytes
El tamaño de un booleano es: 1 bytes

Tal vez el número de bytes presentados sea diferente en su computadora.

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.

Uso de enteros con signo y sin signo


Todos los tipos enteros vienen en dos variedades: con signo y sin sig n o . La idea aquí es
que algunas veces se necesitan los números negativos, y otras no. S e sobreentien de que
los enteros (cortos y largos) que no llevan la palabra “unsigned” (sin sig n o ) tienen signo.
Los enteros con signo pueden ser negativos o positivos. Los enteros sin sig n o siem pre
son positivos.
Debido a que usted dispone del mismo número de bytes para los enteros con signo y sin
signo, el número más grande que puede guardar en un entero sin signo es dos veces más
grande que el número positivo más grande que puede guardar en un entero con signo.
Un entero corto sin signo puede manejar números desde 0 hasta 6 5 ,5 3 5 . La mitad de los
números representados por un entero corto con signo son negativos, por lo que este tipo
de datos sólo puede representar números desde -32,768 hasta 3 2 ,7 6 7 . Si esto le parece
confuso, asegúrese de leer el apéndice C.
Variables y constantes 47

Tipos de variables fundamentales


Hay muchos otros tipos de variables integrados en C++, los cuales se pueden dividir
convenientemente en variables de tipo entero (de las que hemos hablado hasta ahora),
variables de punto flotante y variables de tipo carácter.

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.

Ta b l a 3.1 Tipos de variables


T ip o Tam año V a lo r e s
bool 1 byte Verdadero (True) o Falso (False)
unsigned short int 2 bytes 0 hasta 65,535
short int 2 bytes -32,768 hasta 32,767
unsigned long int 4 bytes 0 hasta 4,294,967,295
long int 4 bytes -2,147,483,648 hasta 2,147,483,647
int (16 bits) 2 bytes -32,768 hasta 32,767
int (32 bits) 4 bytes -2,147,483,648 hasta 2,147,483,647
unsigned int (16 bits) 2 bytes . 0 hasta 65,535
unsigned int (32 bits) 4 bytes 0 hasta 4,294,967,295
char 1 byte 256 valores de carácter, si tienen signo,
entonces de -128 hasta 127; si no tienen signo,
entonces de 0 hasta 255
float 4 bytes -1.2e-38 hasta 3.4e38
double 8 bytes -2.2e-308 hasta 1.8c308
48 Día 3

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.

Definición de una variable


Usted crea o define una variable declarando su tipo, seguido de uncí o más esp a cio s, del
nombre de la variable y un punto y coma. El nombre de la variable debe em pezar con
una letra o un guión bajo y puede contener casi cualquier com bin ación de letras, núm e­
ros y guiones bajos, pero no puede contener espacios. A lgunos nom bres de variables
válidos son x, J23qrsnf, mi_Edad, y miEdad. Los buenos nom bres de variables le indican
el uso de la variable; usar buenos nombres facilita la com prensión del flujo de un progra­
ma. La siguiente instrucción define una variable de tipo entero llam ada miEdad:
int miEdad;

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

Como práctica general de programación, evite nombres horribles, c o m o J2 3 q r s n f ,


y restrinja los nombres de variables de una sola letra (com o x o i) para las variables
que utilice en raras ocasiones. Trate de utilizar nombres expresivos, co m o miEdad o
Cuantos. Estos nombres serán más fáciles de comprender tres sem anas después cuando
se esté rascando la cabeza tratando de averiguar lo que quiso hacer al escribir esa línea
de código.
Variables y constantes

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

Si c o m p ila este p ro gram a , el c o m p ila d o r le a d ve rtirá q u e e sto s v a lo re s n o


Nota e stá n inicializados. M á s a d e la n te verá c ó m o so lu c io n a r este p ro b le m a .

Evidentemente, el propósito del segundo programa es más fácil de adivinar, y la incon­


veniencia de tener que escribir los nombres de variables más largos queda más que re­
compensada por la facilidad de dar mantenimiento a este programa.

Sensibilidad al uso de mayúsculas


C++ es sensible al uso de mayúsculas y minúsculas. En otras palabras, las letras ma­
yúsculas y minúsculas se consideran distintas. Una variable llamada e d a d es diferente
de E d a d , la cual a su vez es diferente de EDAD.

A lg u n o s c o m p ila d o re s le p e rm iten d e sactivar la se n sib ilid a d al u s o d e m a ­


y ú sc u la s y m inú sculas. Los c o m p ila d o re s G N U n o lo p e rm ite n , p o r lo q u e n o
d e b e tra ta r d e hacer esto.

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

Los n o m b re s d e las variab le s p u e d e n c o n te n e r p a la b r a s re se rvad as, sie m p re y


Nota c u a n d o el n o m b re n o co n ste só lo d e esa p a la b ra . r e t u r n _ v a lo r es u n n o m ­
bre d e v aria b le válid o, p e ro re tu rn n o lo es.

Cómo crear más de una variable a la vez


Puede crear más de una variable del mismo tipo en una sola instrucción escribiendo el
tipo y luego los nombres de las variables separados por comas. Por ejemplo:
unsigned int miEdad, miPeso; // dos variables enteras sin signo
long int area, ancho, longitud; // tres enteros largos

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.

Cómo asignar valores a las variables


Puede asignar un valor a una variable por medio del operador de asignación (=). Por
ejemplo, puede asignar un 5 a la variable Ancho escribiendo lo siguiente:
unsigned short Ancho;
Ancho = 5;

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

Puede combinar estos pasos e inicializar la variable Ancho al declararla escribiendo lo


siguiente:
unsigned short Ancho = 5;
La inicialización es muy similar a la asignación, y con variables de tipo entero la diferen­
cia es mínima. Más adelante, al tratar el tema de las constantes, verá que algunos valores
deben ser inicializados debido a que no pueden ser asignados. La principal diferencia es
que la inicialización ocurre en el momento en que se crea la variable.
Así como puede definir más de una variable a la vez, también puede inicializar más de
una variable al mismo tiempo. Por ejemplo:
// crear dos variables de tipo long e inicializarlas
long ancho = 5, longitud = 7;

ntPUOTECÁ RAOtftWL
PF MAESTROS
Día 3
i 52

Este ejemplo inicializa la variable tipo lo n g in t llamad.! a n c h o con el valor 5 y la varia­


ble tipo long in t llamada lo n g itu d con el valor 7. También se pueden m e /cla r defini­
ciones e inicializaciones:
int miEdad = 39, tuEdad, suEdad = 40;

Este ejemplo crea tres variables de tipo in t . e inicializa la primera y la tercera.


El listado 3.2 muestra un programa com pleto, listo para com pilarse, que calcu la el área
de un rectángulo y escribe la respuesta en la pantalla.

Entrada L is t a d o 3 . 2 Una muestra del uso de variables

1: // Muestra de las variables


2: tfinclude <iostream.h>
3:
4: int main()
5: {
6: unsigned short int Ancho = 5, Longitud;
7: Longitud = 10;
8:
9: // crear una variable de tipo unsigned short e inicializarla con el
10: // resultado de la multiplicación de Ancho por Longitud
11: unsigned short int Area = (Ancho * Longitud);
12:
13: cout « "Ancho:" « Ancho « "\n";
14: cout « "Longitud: " « Longitud « endl;
15: cout « "Area: " « Area « endl;
16: return 0;
17: >

Ancho:5
S a l id a Longitud: 10
Area: 50

En la línea 2, la directiva inelude solicita el uso de la bib lioteca iostream para


A nálisis
que cout pueda funcionar. El programa em pieza en la línea 4.
En la línea 6, Ancho se define com o entero corto sin signo, y se in icia liza con el valor 5.
También se define otro entero corto sin signo llamado Longitud, pero no se inicializa.
En la línea 7 se asigna el valor 10 a Longitud.
En la línea 11 se define un entero corto sin signo llam ado Area, y se in icia liza con el
valor obtenido de la multiplicación de Ancho por Longitud. En las lín eas 13 a 15 se
imprimen en pantalla los valores de las variables. O bserve que la palabra esp ecia l e n d l
crea una nueva línea.
Variables y constantes 53

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.

Hn efecto, está creando un sinónimo, y es importante distinguir esto de la creación de


un nuevo tipo de datos (lo que hará en el día 6) pues typedef no crea un nuevo tipo
de datos.
typedef se utiliza escribiendo la palabra reservada typedef. seguida del tipo existente,
el nuevo nombre y. por último, un punto y coma. Por ejemplo.
typedef u n sig n e d s h o rt in t USHORT;

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

Cuándo utilizar short y cuándo utilizar long


Una fuente de confusión para los programadores principiantes de ('+ + es c uando declarar
una variable com o tipo lon g y cuándo declararla c o m o tipo s h o r t . 1.a regla, al com pren­
derla, es bastante clara: si hay alguna probabilidad de que el valor que quiera colocar en
su variable sea demasiado grande para su tipo, en ton ces utilice un tipo m a s grande.
Como se muestra en la tabla 3.1. los enteros c o r t o s sin s i g n o t u n s i g n e d s h o r t i n t ) .
asumiendo que sean de 2 bytes, pueden contener un v al o r de h a s ta b 5 .5 3 b s o l a m e n t e . Los
enteros cortos con signo (s h o r t i n t ) dividen sus v a l o r e s e n t r e n ú m e r o s p o s i t i v o s y ne­
gativos, y por consecuencia su valor m áxim o equivale s ó l o a la mitad del v a l o r m áxim o
de los enteros cortos sin signo.
Aunque los enteros largos sin signo ( u n s i g n e d l o n g i n t ) pueden con ten er un número
extremadamente grande (4 ,2 9 4 ,9 6 7 ,2 9 5 ), aún así son núm eros finitos. Si n ecesita un
número más grande, tendrá que utilizar f lo a t o d o u b l e . pero en e se ca so perdería algo
de precisión. Los valores f lo a t y d o u b l e pueden contener núm eros extrem adam ente
grandes, pero sólo los primeros 7 o 19 dígitos son sig n ifica tiv o s en la m ayoría de las
computadoras. Esto significa que el número se redondea d esp u és de e so s d íg ito s.
Las variables más pequeñas ocupan m enos m emoria. En la actualidad, la m em oria es
económ ica y la vida es corta. Siéntase en libertad de utilizar i n t , el cual probablem ente
tendrá una longitud de 4 bytes en su equipo.

Cómo sobregirar el valor de un entero sin signo


El hecho de que los enteros largos sin signo tengan un lím ite para los valores que pueden
contener, raras veces es un problema, pero, ¿qué ocurre si se acaba el esp a cio ?
Cuando un entero sin signo llega a su valor m áxim o, se regresa a cero, c o m o lo hace el
odómetro de un auto. El listado 3.4 muestra lo que ocurre si trata de co lo ca r un valor
demasiado grande en un entero corto.

L is t a d o 3 . 4 Una muestra de lo que ocurre al colocar un v a lo r d e m a sia d o


E n t r a d a | grande en un entero corto sin signo

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

En la línea 4 se declara n u m e ro C h ic o como entero corto sin signo, que en mi


A nálisis
computadora es una variable de 2 bytes capaz de contener un valor entre 0 y 65.535
En la línea 5 se asigna el valor máximo a numeroChico, y se imprime en la línea 6.

En la línea 7 se incrementa numeroChico: es decir, se le agrega 1. El símbolo de incre­


mento es ++ (como en el nombre C++. que es un incremento de C). Por consecuencia, el
valor de numeroChico sería 65,536. Sin embargo, los enteros cortos sin signo no pueden
contener un número más grande que 65,535, por lo que el valor se regresa a 0, que es lo
que se imprime en la línea 8.
En la línea ó se incrementa numeroChico otra vez, y luego se imprime su nuevo valor. 1.
3
C óm o sobregirar el valor de un entero con sig n o
Un entero con signo difiere de un entero sin signo en que la mitad de los valores que se
pueden representar son negativos. En lugar de imaginar un odómetro de auto tradicional,
imagine uno que gira en aumento para los números positivos y disminuye para los nega­
tivos. Una milla a partir de cero puede ser 1 o -1. Al agotarse los números positivos,
llega a los números más negativos (el valor absoluto más grande) y luego regresa a 0.
El listado 3.5 muestra lo que ocurre al sumar 1 al número positivo máximo que puede
contener un entero corto.

L is t a d o 3 . 5 Una m uestra de lo que ocurre al co lo car un n ú m e ro d e m a siad o


Entrada g ra n d e en un entero con signo

1 ttin e lu d e < io s t re a m .h >


2 in t m a in ()
3 {
4 s h o r t i n t numeroChico;
5 numeroChico = 32767;
6 cout << "número c h i c o : " << numeroChico << en dl ;
7 numeroChico++;
8 cout << "número c h i c o : " << numeroChico « en dl ;
9 num eroC hico++;
10: cout « "número c h i c o : " << numeroChico << en dl ;
11 : r e t u r n 0;
12: }

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

En la línea 4 se declara numeroChico. per«» c s la v e / c o m o c u l e r o c o i t o c o n signo


A nálisis
(si no d ice en forma explícita que es sin s ig n o , se d a p o r h e c h o q u e e s con
signo). El programa funciona en forma muy p a r e c id a al a n te rio r, s o l o q u e la s a l i d a es
bastante diferente. Para comprender completam ente e s ta s al id a, d e b e l a m i h a i i / a r s e con
la forma en que se representan com o bits los n ú m e r o s c o n s i g n o e n u n e n t e r o d e 2 hytes.

N o obstante, lo importante es que al igual que un entero sin s i g n o , el e n t e r o c o n signo


se regresa de su valor positivo más alto a su valor m á s n e g a t i v o t e l valor a b s o l u t o más
alto).

Uso de variables de tipo carácter


Las variables de tipo carácter (tipo char) son generalm ente de I byte. tam año suficiente
para contener 256 valores (vea el apéndice C). Un char se puede interpretar c o m o un
número pequeño (desde -128 hasta 127 o, si no tiene signo, d esd e O hasta 2 5 5 ) o com o
un m iem bro del conjunto ASCII (Código Estándar E stadounidense para el Intercam bio
de Inform ación). El conjunto de caracteres ASCII y su eq u ivalen te ISO (O rg a n i/a ció n
Internacional de Estándares) son una manera de codificar todas las letras, nú m eros y
sign os de puntuación.

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.

Nota A S C II se p r o n u n c ia " A s k i " .


Variables y constantes

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

Los caracteres como números


Al colocar un carácter, por ejemplo una “a”, en una variable char. lo que se tiene real­
mente es un número entre -128 y 127. Algunos sistemas utilizan caracteres sin signo, que
son números entre 0 y 255. Sin embargo, el compilador sabe cómo traducir un carácter
(representado por una conidia sencilla y luego una letra, número o signo de puntuación,
seguido de otra comida sencilla) a uno de los valores ASCII, o viceversa.

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.

En trad a L is t a d o 3.6 Cómo imprimir caracteres con base en los números

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

Caracteres de impresión especiales


El compilador de C++ reconoce algunos caracteres esfxrciales para Jai tm matn. La tabla
3.2 muestra los más com unes. Puede colocarlos en su có d ig o escrib ien d o la barra diago­
nal inversa (conocida com o carácter de escape), seguida del carácter Por ejem p lo , para
colocar un carácter de tabulación en su cód igo, debe escribir una c o n id ia sen cilla , una
barra diagonal inversa, la letra t y. para terminar, una co m id a sen cilla
char caracterTab = \t';

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.

Ta b l a 3 .2 Los caracteres de escape


C a rá c te r S ig n ific a d o
\n Nueva línea
\t Tabulador
\b Retroceso
\” Doble comilla
V Comilla sencilla
Y? Signo de interrogación
\\ Barra diagonal inversa

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.

Una constante literal es un valor escrito directamente en el lugar de su program a donde


se necesite, por ejemplo:
int miEdad = 39;
Variables y constantes 59

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.

Definición de constantes con #define


Para definir una constante de la manera tradicional, usted escribiría lo siguiente:

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

Definición de constantes con const


Aunque ^define funciona, en C + + existe una nueva v mejor manera de del mu constantes:
const unsigned short in t estu d ia n tesP o rC la se 15;
Este ejem plo también declara una constante sim b ólica llam ada e s t u d m n t e s P o r C la s e .
pero esta vez dicha constante sí tiene tipo: u n sig n e d s h o r t in t liste m éto d o tiene
varias ventajas en cuanto a facilitar el m an tenim iento del c ó d ig o > prevenir errores.
La mayor diferencia es que esta constante tiene un tipo, y el co m p ila d o r pu ed e hacer
que se utilice de acuerdo con su tipo.

Las constantes no se pueden cam biar m ie n tras el p ro g ra m a se e n cu e n tre en


ejecución. Por ejem plo, si quiere cam biar est u d i ant e s P o r C lase, necesita
cam biar el código y volver a com pilar

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.

Uso de constantes enumeradas


Las constantes enumeradas le permiten crear nuevos tipos y luego definir variables de esos
tipos cuyos valores estén restringidos a un conjunto de valores p o sib les. Por ejem p lo,
puede declarar COLOR como una enumeración, y puede definir cin c o valores para COLOR:
ROJO, AZUL, VERDE, BLANCO y NEGRO.

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

Esta instrucción realiza dos tareas:

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.

Entrada L is t a d o 3 .7 Una muestra de las constantes enumeradas

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

Escriba un día (0-6): 6


Salida iYa se le agotaron los fines de semana!
En la línea 4 se define la constante enumerada D I A S con siete \ alores. C ada uno
A n á lisis
de éstos se evalúa como entero, contando en lorma ascendente a partir de 0; por
lo tanto, el valor de M a r t e s es 2.
Se pide al usuario que proporcione un valor entre 0 y 6. Id usuario no puede escribir la
palabra “Domingo” cuando se le pide un día; el programa no sabe com o traducir los ca­
racteres que forman la palabra Domingo a uno de los valores enumerados. N o obstante,
usted puede comparar el valor proporcionado por el usuario con una o m á s de las cons­
tantes enumeradas, como se muestra en la línea 9. Aquí el uso de constantes enumeradas
hace más explícita la intención de la comparación. Este m ism o efecto se hubiera podido
lograr mediante el uso de constantes de tipo entero, com o se muestra en el listado 3.8.
pero con un poco más de esfuerzo en la programación.

P ara e ste y t o d o s lo s p e q u e ñ o s p r o g r a m a s q u e v ie n e n e n e s te lib r o se h a


p a s a d o p o r a lt o t o d o el c ó d ig o q u e n o r m a lm e n t e se e s c r ib ir ía p a r a e n c a r ­
g a r s e d e lo q u e o c u rre c u a n d o el u s u a r io e sc r ib e d a t o s i n a p r o p ia d o s . P o r
e je m p lo , e ste p r o g r a m a n o se a s e g u r a , c o m o se h a r ía e n u n p r o g r a m a real,
d e q u e el u s u a rio e scriba u n n ú m e r o e n tr e 0 y 6. E ste d e t a lle se h a p a s a d o
p o r a lt o p a ra m a n te n e r e sto s p r o g r a m a s p e q u e ñ o s y s e n c illo s , y p a r a e n f o ­
c a rn o s e n la c u e stió n q u e se e stá e x p lic a n d o .
En p r o g r a m a s reales se d e b e inclu ir la v a lid a c ió n d e d a t o s . N u n c a s a b e lo q u e
u n u s u a rio (o a lg ú n cracker q u e tr a te d e e n t r a r e n f o r m a ilíc ita a s u s is t e m a )
h ará. El té r m in o g e n é r ic o p a ra e sto es p ro gra m a ció n a la defensiva d e b i d o a
q u e se d e fie n d e n la c o n siste n c ia y el c o m p o r t a m i e n t o a p r o p i a d o d e l c ó d i g o
y d e lo s d a to s.

En t r a d a L is t a d o 3 .8 El mismo programa, pero esta vez con constantes de tipo entero

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

las computadoras personales vienen co n m u c h o s m il es <si n o m i ll o ne s» d e hytes de


memoria.
P ¿Q u é pasa si asigno un núm ero con punto decim al » una variab le de tipo in t
en lugar de a una de tipo f lo a t ? C onsidere la sigu ien te línea de cód igo:
int unNumero = 5.4;
R Un buen com pilador mostrará un m ensaje de advertencia, pero la asig n a ció n es
completamente válida. El número que usted asigne sera truncado a un entero. Por
lo tanto, si asigna 5.4 a una variable de tipo entero, esa variable tendrá el valor 5.
Como puede ver, se perderá inform ación, y si trata en ton ces de asignar el valor de
esa variable de tipo in t a una de tipo f l o a t . esta última tendrá so lo un 5.
P ¿Por qué no utilizar constantes literales? ¿P or qué tom arse la m o lestia de usar
constantes sim bólicas?
R Si utiliza el v a lo ren muchos lugares en su programa, una constante sim b ó lica per­
mite que todos los valores cam bien con só lo cam biar la d efin ició n de la constante.
Las constantes sim bólicas también se describen a sí m ism as. Podría ser d ifícil
entender por qué se m ultiplica un número por 3 60. pero sería más fácil entender lo
que pasa si el número se m ultiplica por gradosDeUnCirculo.
P ¿Q ué ocu rre si a sig n o un n ú m ero n eg a tiv o a u n a variab le sin s ig n o ? C o n sid e r e
la sig u ie n te lín ea d e código:
unsigned int unNumeroPositivo = -1;
R Un buen com pilador mostrará una advertencia, pero la asig n a ció n es válida. El nú­
mero negativo se valorará com o patrón de bits y se asignará a la variable. El valor
de esa variable se interpretará entonces com o núm ero sin sign o. Por lo tanto, un -1»
cuyo patrón de bits puede ser 11111111 1111111 1 en algunas rep resen taciones bina­
rias (OxFFFF en hexadecim al), será valorado co m o el valor sin sig n o 6 5 ,5 3 5 . Si
esta inform ación es confusa para usted, vea el apéndice C.
P ¿Puedo trab ajar con C++ sin entender p atrones d e bits, a ritm ética b in aria y
núm eros hexadecim ales?
R Sí, pero no en forma tan efectiva com o lo haría si com prendiera e so s tem as. C ++
no hace un trabajo tan bueno com o otros lenguajes en cuanto a “p ro teg erlo ’ de lo
que está haciendo en realidad su computadora. A decir verdad, esto es un b en eficio
ya que le proporciona un tremendo poder, algo que otros lenguajes no pueden
hacer. Sin embargo, com o con cualquier herramienta de poder, para obten er el
m áxim o rendim iento de C++, debe comprender su fu ncionam iento. L os progra­
m adores que tratan de programar en C++ sin com prender los fu ndam en tos del sis­
tema binario, a menudo se confunden con sus resultados.
Variables y constantes 65

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

mente del punto y coma. Una de las instrucciones m . i s c o m u n e s e s l.i s i g u i e n t e instruc­


ción de asignación:
x = a + b;

A diferencia del álgebra, esta instrucción no signif ica q u e x e s igual a a • o E s t o se


lee: “asignar el valor de la suma de a y b a x". o “ as ig n a t a x el \ a l o i d e a • r>'\ Aún
cuando esta instrucción hace dos cosas, es una i n s tr u c c ió n \ poi l<* t a n t o s o l o n en e un
punto y com a. El operador de asignación asigna lo que e s t e d e su latió d e r e c h o a lo que
esté de su lado izquierdo.

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.

Bloques de instrucciones e instrucciones compuestas


En cualquier parte donde coloque una instrucción sen cilla , puede co lo ca r una instrucción
compuesta, conocida también com o bloque. Un bloque em p ieza con una lla v e de apertu­
ra ({) y termina con una llave de cierre (}). Aunque todas las in stru ccion es del bloque
deben terminar con punto y com a, el bloque en sí no term ina con punto y co m a , co m o se
muestra en el siguiente ejemplo:
{
temp = a;
a = b;
b = temp;
>
Este bloque de código actúa com o una instrucción, e intercam bia los valores de las varia­
bles a y b.
Expresiones e instrucciones 69

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

Segun do sPor Mi nut o // constante de t i p o i n t que r e g r e s a e l v a l o r 60

Asumiendo que P I es una constante igual a 3.14, y que S e g u n d o s P o r M i n u t o es una


constante igual a 60, las tres instrucciones son expresiones.

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;

Esta línea se evalúa en el siguiente orden:


Sumar a y b.
Asignar a x el resultado de la expresión a + b.
Asignar a y el resultado de la expresión de asignación x a * b

Si a, b, x y y son enteros, y si a tiene el valor 2 y b tiene el valor 5. entonces a x y a y


se les asignará el valor 7.

El listado 4.1 muestra la forma en que C++ evalúa expresiones complejas

Entrada L is t a d o 4.1 Evaluación de expresiones complejas

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

En la línea 4 se declaran e inicializan las cuatro variables. Sus valores se impri­


A nálisis
men en las líneas 5 y 6. En la línea 7 se asigna el valor 9 a la variable a. En la
línea 8 se asigna el valor 7 a b. En la línea 9 se suman los valores de a y b y el resultado
se asigna a x. Esta expresión (x = a + b ) tiene un valor (la suma de a + b ), y ese valor se
asigna a su vez a y.
E x p re sio n e s e in stru c c io n e s 7 1 f

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

pero no es válido que escriba


35 = x; // error, ¡no es un valor-i!

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.

Entrada L is t a d o 4 . 2 U na m uestra de la resta y el d e s b o r d a m ie n t o d e e n te ro s

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

El operador de resta se invoca en la línea 10. y el resultado se imprime en la


A nálisis
línea I I, com o podría esperarse. En la línea 12 se llama otra v e / al operador de
resta, pero esta vez se resta un número grande sin signo a un número pequeño sin signo.
El resultado sería negativo, pero com o se evalúa (y se imprime) com o numero sin
signo, el resultado es un desbordamiento, como se describió ayer. Este tema se describe
con detalle en el apéndice A, “Precedencia de operadores”.

División de enteros y el operador de módulo


La división de enteros es un poco distinta a la división com ún. De hecho, la división
de enteros es ig u a l q u e la división que usted aprendió en la primaria. Al dividir 21
entre 4 (21 / 4) se realiza una división de enteros, y el resultado es 5, con un residuo
de 1.

Para obtener el residuo, se hace la operación 21 módulo 4 (21 % 4) y el resultado es I.


El operador de módulo le indica el residuo producido por una división de enteros.

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

hasta 10 ‘ ( 10. cuyo residuo es 0. 11 % 10 es igual a 1, y este patrón continúa hasta el


siguiente múltiplo de 10. que es 20. Verá esta técnica al hablar sobre los ciclos en el día
7. “Más flujo de programa”.

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.

Entrada L is t a d o 4 .3 Especificación de tipo para f lo a t

1: //include < i o s t r e a m . h >


2:
3: v o i d i n t D i v ( i n t x, i n t y)
4: {
5: i n t z = x / y;
6: cout << " z : " << z << endl
7: }
8:
9: v o i d f l o a t D i v ( i n t x, i n t y)
10 {
11 flo a t a = (float)x; // e s t i l o a n t i g u o
12 flo a t b = static_cast<float // e s t i l o p r e f e r i d o
13 f l o a t c = a / b;
14
15 cout << "c: " << c « endl;
16 }
17
18 i n t m a i n ()
19 {
continua
74 O ía 4

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

En la línea 20 se declaran dos variables de lipo culero. En la linca 21 se pasan


A ná l i s i s
com o parámetros a intDiv, y en la línea 22 se pasan com o parámetros a
floatDiv. Este segundo método empieza en la línea 0. En las líneas 11 y 12. los enteros
se convierten a tipo float y se asignan a las variables de este tipo. En la línea 13. el
resultado de la división se asigna a un tercer tipo float, y se imprime en la línea 15.

Cómo combinar los operadores


de asignación y matemáticos
Es muy común querer sumar un valor a una variable y luego asignar el resultado a esa
m ism a variable. Si tiene una variable llamada miEdad y quiere sumar 2 a ese valor, puede
escribir lo siguiente:
int miEdad = 5;
int temp;
temp = miEdad + 2 ; // sumar 5 + 2 y colocar el resultado en temp
miEdad = temp; // colocar otra vez el resultado en miEdad

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.

El operador de incremento (++) incrementa en 1 el valor de una variable, y el operador


de decremento (— ) lo decrementa en 1. Por lo tanto, si tiene una variable llamada C y
quiere incrementarla, puede utilizar la siguiente instrucción:
C++; // Empezar con C e incrementarla.

Esta instrucción equivale a la siguiente, que es un poco más elaborada:


C = C + 1;

la cual, a su vez, equivale a la instrucción


C += 1;

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.

Si después de hacer esto, escribe


int b = x++;

ahora el compilador toma el valor contenido en x (6) y lo asigna a b. luego incrementa el


valor de x. Por lo tanto, ahora b vale 6, pero x vale 7. El listado 4.4. muestra el uso y las
im plicaciones de ambos tipos.

Entrada L is t a d o 4 .4 Una muestra de los operadores de prefijo y posfijo

1: // Listado 4.4 - muestra el uso de


2: // los operadores de incremento de
3: // prefijo y posfijo
4: #include <iostream.h>
5: int main()
6: {
7: int miEdad = 39; // inicializar dos variables de tipo entero
8: int suEdad = 39;
9: cout « "Yo tengo: " « miEdad « " años.\n";
10: cout « "Usted tiene: " « suEdad « " años \n";
11: miEdad++; // incremento mediante posfijo
12: ++suEdad; // incremento mediante prefijo
13: cout « "Pasa un año...\n";
14: cout « "Ahora tengo: " « miEdad << " años.\n";
15: cout « "Usted tiene: " « suEdad « " años\n";
16: cout « "Pasa otro año\n";
17: cout « "Ahora tengo: " « miEdad++ « " años.\n";
18: cout « "Usted tiene: " « ++suEdad « " años\n";
19: cout « "Imprimamos eso de nuevo.\n";
20: cout « "Yo tengo: " « miEdad « " años.\n";
21: cout « "Usted tiene: " « suEdad « " años\n";
22: return 0;
23 }
Expresiones e instrucciones 77

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;

Si se realiza primero la suma, el resultado es 8 * 8, o 64. Si se realiza primero la multi­


plicación, la respuesta es 5 + 24, o 29.
Todos los operadores tienen un valor de precedencia, y la lista completa se muestia en el
apéndice A. La multiplicación tiene una precedencia mayor que la suma; por lo tanto, el
valor de la expresión es 29.
Cuando dos operadores matemáticos tienen la misma precedencia, se procesan en orden
de izquierda a derecha. Por ejemplo, en la expresión
x = 5 + 3 + 8 * 9 + 6 * 4 ;
p rim e ro se e v alú a la m u ltiplicación, de i/q m e r d a a d e re c h a P o r lo ta n to . S * 1) = 72. y 6*4
= 24. A h o ra la ex p resió n q u ed aría de la sig u ie n te m a n e ra
x = 5 + 3 + 72 + 24;

Y la su m a , q u e se ev alú a de iz q u ierd a a d e re c h a , s e ria 5 + * = N. X + 72 = Sí); SO + 24


= 104.

T en g a cu id a d o con esto. A lgunos o p erad o res, c o m o los d e a s ig n a c ió n , ¡se e v a lú a n de


d e re c h a a izq u ierd a! De cu alq u ier m an era, ¿q u é p asa si la p r e c e d e n c ia n o s a tisfa c e sus
n e c e sid a d e s? C o n sid ere la sig u ien te ex p resió n :
TotalSegundos = NumMinutosParaPensar + NumMinutosParaEscnbir • 60

E n e s ta ex p re sió n no se q u iere m u ltip licar la v a ria b le N u m M i n u t o s P a r a E s c n b i r por


60 y lu e g o su m arla a N u m M in u to sP araP en sar. Se q u ie re s u m a r las d o s v a ria b le s para
o b te n e r el n ú m e ro total de m in u to s, y lu eg o m u ltip lic a r e s e n ú m e ro p o r 60 p a ra obtener
el to tal d e seg u n d o s.

E n este c a so se utilizan paréntesis p ara c a m b ia r el o rd e n d e p re c e d e n c ia . L o s elem entos


e n tre p arén tesis se evalúan con una m ay o r p re c e d e n c ia q u e c u a lq u ie ra d e los operadores
m a te m á tic o s. P o r lo tanto,

TotalSegundos = (NumMinutosParaPensar + NumMinutosParaEscnbir) * 60


lo g ra ría lo q u e se necesita.

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:

TotalSegundosPersona = ( ( (NumMinutosParaPensar + NumMinutosParaEscribir)


60 + NumSegundosParaEscuchar) * (PersonasEnLaOficina + PersonasDeVacaciones) )
E sta e x p re sió n co m p leja se lee de ad en tro h acia fu era. P rim e ro , NumMinutosParaPensar
se su m a a NumMinutosParaEscribir, y a q u e e sta s d o s v a ria b le s se e n c u e n tra n en los
p a ré n te s is d e m á s ad en tro . L u eg o e sta su m a se m u ltip lic a p o r 60 y el p ro d u c to se su­
m a a NumSegundosParaEscuchar. A c o n tin u a c ió n , PersonasEnLaOf icina se su m a a
PersonasDeVacaciones. F inalm ente, el n ú m ero total d e p e rs o n a s e n c o n tra d a s se m ulti­
p lic a p o r el n ú m e ro to tal d e segundos.

E ste e je m p lo trae a la m en te una cuestión re la c io n a d a im p o rta n te . P a ra u n a co m p u tad o ra


e s fácil c o m p re n d e r esta ex p resió n , pero p ara un h u m a n o es m u y d ifíc il le e rla , com pren­
d e rla o m o d ificarla. H e aq u í la m ism a ex p resió n e sc rita d e o tra m a n e ra , u s a n d o algunas
v a ria b le s te m p o ra le s de tipo entero:
Expresiones e instrucciones 79

TotalMinutos - NumMinutosParaPensar + NumMinutosParaEscnbir;


TotalSegundos = TotalMinutos * 60;
TotalPersonas = PersonasEnLaOfícina + PersonasDeVacaciones;
TotalSegundosPersona = TotalPersonas * TotalSegundos;

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 .

Anteriormente, muchos compiladores ofrecían un tipo bool, el cual se re­


presentaba internamente como in t y por lo general tenía un tam año de 4
bytes. Ahora los compiladores que se apegan al estándar ANSI por lo gene­
ral proporcionan un tipo bool de 1 byte.
Los compiladores GNU se apegan al estándar ANSI; su tipo bool ocupa 1
byte. Me aseguré de ello. Tal vez usted se pregunte cómo. Lo hice escribien­
do un programa rápido de muestra que utiliza la función size o f () como en
los ejemplos del día 3, "Variables y constantes".
80 D ía 4

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 .

C u a n d o se tr a b a ja c o n lo s in té r p r e t e s d e c o m a n d o s d e L in u x (csh, tesh, zsh,


b a s h , e n tr e o t r o s ) lo s v a lo r e s d e t r u e y f a l s e se in v ie r t e n . Si r e a liz a p r o ­
g r a m a c i ó n " s h e ll" , el v a lo r 0 c o r r e s p o n d e r á a t r u e y c u a lq u ie r o t r o v a lo r
c o r r e s p o n d e r á a f a l s e . R e v ise la d o c u m e n t a c i ó n d e s u s is t e m a p a r a p r o f u n ­
d iz a r e n e s to s d e ta lle s.

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.

T a b l a 4 .1 Los operadores relaciónales


N om bre O p era d o r E jem plo Valor
Igual a 100 == 50; fa ls e
50 == 50; tru e
Expresiones e instrucciones 81

N o m b re O p e ra d o r Ejem plo V alo r


No igual o != 100 != 50: true
diferente de 50 != 50: fa ls e
Mayor que > 1(H) > 50; true
50 > 50: fa ls e
Mayor o >- 1(H) >= 50: true
igual a 50 >= 50; true
Menor que < 100 < 50; fa ls e
50 < 50: fa ls e
Menor o <= !()()<= 50: fa ls e
igual a 50 <= 50; true

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 forma más simple de una instrucción i f es la siguiente:


if (expresión)
instrucciónl;

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;

Este código compara numeroGrande y numeroChico. Si numeroGrande es mayor, la


segunda línea le asigna el valor de numeroChico.
82 D ía 4

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.

L is t a d o 4 .5 Una muestra de la ramificación basada en los operadores


Entrada relaciónales

1: // Listado 4.5 • muestra el uso de la instrucción if


2: // con los operadores relaciónales
3 : tfinclude <iostream.h>
4 : int main()
5 : {
6: int CarrerasMediasRojas, CarrerasYanquis;
7: cout « "Escriba las carreras anotadas por los Medias rojas:
8: cin » CarrerasMediasRojas;
9:
10 cout « "\nEscriba las carreras anotadas por los Yanquis: ";
11 cin » CarrerasYanquis;
12
13 cout « "\n";
14
15 if (CarrerasMediasRojas > CarrerasYanquis)
16 cout « "IVamos, Medias rojas!\n";
17:
18 ; if (CarrerasMediasRojas < CarrerasYanquis)
19 : {
20: cout « "¡Vamos, Yanquis!\n";
21: cout « "¡Son dias felices en Nueva York!\n";
22: }
23 :
24 : if (CarrerasMediasRojas == CarrerasYanquis)
25 : {
Expresiones e instrucciones 83

26 cout << *¿Un empate? Nooo, no puede ser.\n";


27 cout << ’Escriba las carreras que anotaron realmente los Yanquis: ";
28 cin >> CarrerasYanquis;
29
30 íf (CarrerasMediasRojas > CarrerasYanquis)
31 cout « ’¡Lo sabia! ¡Vamos, Medias rojas!";
32
33 if (CarrerasYanquis > CarrerasMediasRojas)
34 cout « "¡Lo sabia! ¡Vamos, Yanquis!";
35
36 if (CarrerasYanquis == CarrerasMediasRojas)
37 cout « “¡Vaya! ¡Realmente fue un empate!";
38 }
39
40 cout « "\nGracias por decirmelo.\n";
41 return 0;
42

Escriba las carreras anotadas por los Medias rojas: 10


S a l id a
Escriba las carreras anotadas por los Yanquis: 10

¿Un empate? Nooo, no puede ser.


Escriba las carreras que anotaron realmente los Yanquis: 8
¡Lo sabíal ¡Vamos, Medias rojas!
Gracias por decírmelo.

Este programa pide al usuario la puntuación (número de carreras anotadas) de los


A nálisis
dos equipos de béisbol; las puntuaciones se guardan en variables de tipo entero.
Las variables se comparan en la instrucción i f de las líneas 15, 18 y 24.

Si una puntuación es mayor que la otra, se imprime un mensaje de información. Si las


puntuaciones son iguales, el programa entra al bloque de código que empieza en la línea
25 y termina en la línea 38. Se pide otra vez la segunda puntuación, y luego se comparan
nuevamente las puntuaciones.
Observe que si la puntuación inicial de los Yanquis fuera más alta que la de los Medias
Rojas, la instrucción if de la línea 15 tendría el valor false (falso), y no se invocaría la
línea 16. La prueba de la línea 18 tendría el valor true (verdadero), y se invocarían las
instrucciones de las líneas 20 y 21. Luego se probaría la instrucción if de la línea 24 y
tendría el valor f alse (si la línea 18 fuera true). Por lo tanto, el programa saltaría el
bloque completo, hasta llegar a la línea 39.
En este ejemplo, si se obtiene un resultado true en una instrucción if, esto no evita que
se prueben las otras instrucciones if.
D ía 4

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 ;

íf (AlgunValor < 10);


AlgunValor - 10;
Lo q u e se tra ta d e h a c e r a q u í e s p r o b a r si A i g u n v a i n ' - i*% m e n o r q u e 10. y de
ser así. e sta b le c e r su v a lo r e n 10. h a c i e n d o q u e 10 s e a el v a l o r m í n i m o para
A l g u n V a l o r . Si e je c u ta e sta p ie z a d e c o d i g o , d e s c u b r i r á q u e A i g u n v a l o r siem­
p re se e sta b le c e e n 10. ¿ P o r q u é ? L a i n s t r u c c i ó n i r t e r m i n a c o n el p u n to y
c o m a (el o p e r a d o r q u e n o h a c e n a d a ).

R e c u e rd e q u e la s a n g r ía n o tie n e n i n g ú n s i g n i f i c a d o p a r a el c o m p ila d o r. Esta


p ie z a d e c ó d i g o se h a b r ía p o d i d o e sc rib ir m a s a p r o p i a d a m e n t e c o m o

if (AlgunValor < 10) // probar


; // no hacer nada
AlgunValor = 10; // asignar
Q u it a r el p u n t o y c o m a h a r á q u e la lín e a f in a l f o r m e p a r t e d e la instrucción
i f , y a sí el c ó d i g o f u n c io n a r á c o m o es d e b id o .

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:

• Colocar la llave inicial después de la condición y alinear la llave final debajo de if


para cerrar el bloque de instrucciones:
if (expresión) {
instrucciones
}

• Alinear las llaves debajo de i f y utilizar sangrías en las instrucciones:


i f (expresión)
í
instrucciones
>
• Utilizar sangrías en las llaves e instrucciones:
if (expresión)
{
instrucciones
}
Expresiones e instrucciones 85

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;

El listado 4.6 m uestra el uso de la cláusula e l s e .

Entrada L is t a d o 4 .6 M uestra de la cláusula e ls e

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 se evalúa en la línea 11. Si la c o n d ició n es t r u e ( verdadera).


y7Tacrj| se ejecuta la instrucción de la línea 12; si la co n d ició n es f a l s o (falsa), se ejecuta
la instrucción de la línea 14. Si se quitara la cláusula e l so de la linea 1 \ la instrucción
de la línea 14 se ejecutaría sin importar si la instrucción i f fuera o no t r u e . Recuerde, la
instrucción í f termina después de la línea 12. Si e l s c no e stin ier;i ahí. la linea 14 sería
solam ente la siguiente línea del programa.
Recuerde que cada una o ambas instrucciones se pueden reem p lazar con un bloque de
código entre llaves.

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!

El listado 4.7 proporciona un ejemplo de una instrucción i f compleja.

Entrada L is t a d o 4.7 Una instrucción i f compleja

1: // Listado 4.7 - una instrucción if


2: // compleja
3: #include <iostream.h>
4: int main()
5: {
6: // Pedir dos números
7: // Asignar los números a primerNumero y segundoNumero
8: //Si primerNumero es mayor que segundoNumero,
9: // ver si primerNumero es un múltiplo de segundoNumero
10: //Si esto sucede, ver si son el mismo número
11:
12: int primerNumero, segundoNumero;
13: cout « "Escriba dos números.\nPrimero:
14: cin » primerNumero;
15: cout « "\nSegundo: “;
16: cin » segundoNumero;
17: cout « "\n\n";
18:
19: if (primerNumero >= segundoNumero)
20: {
21: if ((primerNumero % segundoNumero) == 0)
W / ¿es primerNumero múltiplo de segundoNumero?
22: {
23: if (primerNumero == segundoNumero)
24: cout « "¡Son iguales!\n";
continúa
88 D ia 4

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

¡ E l primer número es múlt ipl o del segundo!

Se piden dos números, uno a la v e /, y luego se comparan. La primera instruc­


A nálisis
ción i f , en la línea I9, comprueba que el primer núm ero sea m ayor o igual que
el segundo. Si no es así, se ejecuta la cláusula e l s e de la línea 3 I.

Si la primera instrucción i f es verdadera, se ejecuta el bloque de có d ig o que empieza en


la línea 20, y se prueba la segunda instrucción i f , que está en la línea 2 I . Esto comprue­
ba si la operación módulo del primer número con el segundo no produce residuo. De ser
así, el primer número es múltiplo del segundo o son iguales. La instrucción i f de la línea
23 comprueba la igualdad y despliega el mensaje apropiado según sea el caso.

Si la instrucción i f de la línea 21 falla, se ejecuta la cláusula e l s e de la línea 28.

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

cuando escriba instrucciones anidadas largas , puede haber mucha confusión.


Recuerde, los espacios en blanco y las sangrías son una conveniencia para el progra­
mador; no hacen ninguna diferencia para el compilador. Es fácil confundir la lógica y
asignai sin querer una cláusula e ls e a la instrucción i f equivocada. El listado 4.8 ilus­
tra este problema.
Expresiones e instrucciones 89

L is t a d o 4 . 8 Una m uestra de por q ué las llaves a y u d a n a a c la ra r cuál


Entrada in stru cció n e ls e va con cuál instrucción í f

1: // L i s t a d o 4 . 8 • muestra por qué l a s l l a v e s


2: // son i m p o r t a n t e s en i n s t r u c c i o n e s i f a ni d ad a s
3: «inelude <iostream.h>
4: int main()
5: {
6: i n t x;
7: cout << " E s c r i b a un número menor que 10 o mayor que 100:
8: c i n >> x;
9: cout << * \ n " ;
10:
11: if (x >= 10)
12: i f (x > 100)
13: cout << "Mayor que 100, ¡ g r a c i a s ! \ n ° ;
14: else // ¡no es l a i n s t r u c c i ó n e l s e que se q u i e r e !
15: cout << "Menor que 10, ¡ g r a c i a s ! \ n " ;
16:
17: r e t u r n 0;
18: }

E s c r i b a un número menor que 10 o mayor que 100: 20


S a l id a
Menor que 10, ¡gracias!

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.

Si la instrucción i f de la línea 11 es verdadera, se ejecuta la siguiente instrucción (línea


12). En este caso, la línea 12 se ejecuta cuando el número escrito es mayor que 10. La
línea 12 contiene también una instrucción i f . Esta instrucción i f es verdadera si el nú­
mero escrito es mayor que 100. Si el número es mayor que 100, se ejecuta la instrucción
de la línea 13.
Si el número escrito es menor que 10, la instrucción i f de la línea 11 es falsa. El control
del programa se va hasta la siguiente línea después déla instrucción i f , en este caso la
línea 16. Si escribe un número menor que 10, la salida es la siguiente:
E s c r i b a un número menor que 10 o mayor que 100: 9

El propósito de la cláusula e ls e de la línea 14 era incluirla en la instrucción i f de la


línea 11, para lo cual tiene la sangría adecuada. Desafortunadamente, la instrucción e ls e
se incluye, en realidad, en la instrucción i f de la línea 12, y por consecuencia el progra­
ma tiene un ligero error.
El error es ligero porque el compilador no se quejará. Éste es un programa de C++ válido,
pero no proporciona el resultado deseado. Más aún, la mayoría de las veces que el progia-
mador pruebe este programa, parecerá funcionar. Mientras se escriba un número mayor que
100, el programa parecerá funcionar bien. ¡Éste es un buen ejemplo de un error de lógica!
T o d a s las c lá u s u la s e l s e b u s c a r á n la in s t r u c c ió n i r i n m e d i a t a a n t e r io r p a ra
Precauciún in clu irse e n ella.

El listado 4.9 arregla el problema poniendo las llaves n e c e s a r i a s

E n t r a d a L istado 4 .9 Una muestra del uso adecuado de las llaves en una instrucción if

1: // Listado 4.9 • muestra el uso apropiado de las llaves


2: // en instrucciones if anidadas
3: ^include <iostream.h>
4: int main()
5: {
6: int x;
7: cout << “Escriba un número menor que 10 o mayor que 100: *;
8: cin » x;
9: cout « "\n";
10:
11 : if (x >= 10)
12 : {
13: if (x > 100)
14: cout « "Mayor que 100, Igracias1\n" ;
15: }
16: else // arreglado!
17: cout « "Menor que 10, igraciasi\n" ;
18: return 0;
19: }

S a l id a Escriba un número menor que 10 o mayor que 100: 20

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.

El usuario escribió 20, por lo que la instrucción i f de la línea 11 es verdadera (true);


sin embargo, la instrucción i f de la línea 13 es falsa ( f a l s e ) , por lo tanto no se imprime
nada. Sería mejor si el programador colocara otra cláusula e l s e después de la línea 14
para que se detectaran los errores y se imprimiera un mensaje.

L o s p r o g r a m a s m o s tr a d o s e n e ste lib r o e s t á n e s c r it o s p a r a d e m o s t r a r las


c u e s t io n e s e sp e c ífic as q u e se e s tá n d e s c r ib ie n d o . S e m a n t i e n e n s im p le s d e
m a n e r a in te n c io n a l; n o se h a c e n i n g ú n i n t e n t o p o r h a c e r el c ó d i g o " a p ru e ­
b a d e b a l a s " p a ra p r o t e g e r lo c o n tr a lo s e r r o r e s d e l u s u a r io . C o n u n c ó d ig o
d e c a lid a d p r o fe s io n a l, c a d a p o s ib le e r r o r d e l u s u a r i o se a n t ic ip a y se m a ­
n e ja d e m a n e r a e fectiva.

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

T a b l a 4 .2 Los operadores lógicos


O perador Símbolo Ejemplo
AND && expresiórd && expresión2
OR II expresiónl || expresión2
NOT ! !expresión

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

Observe que el operador lógico OR está c o m p u e s t o por J o s s í m b o l o s i l Un solí» símboloI


es un operador diferente, el cual se describe en el día 2 I

Operador lógico NOT


Una expresión lógica NOT es verdadera (tru e ) si la expresión que se prueba es falsa
( f a ls e ). De nuevo, si la expresión que se está probando e s f a l s a . , e l \ a l o r d e la prueba
es verdadero! Por lo tanto.
i f ( ! (x == 5) )
es verdadero sólo si x no es igual a 5 . Esto es lo mismo que es cr ib i r
i f (x != 5)

Evaluación de corto circuito


Cuando el compilador evalúa una expresión A N D c o m o la s i g u i e n t e :

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

P or lo g e n e r a l es u n a b u e n a idea utilizar p a ré n te sis a d ic io n a le s p a ra d e jar


m á s e n cla ro lo q u e se quiere agrupar. Recuerde, el o b je tiv o es escribir p r o ­
g r a m a s q u e fu n c io n e n y qu e sean fáciles d e leer y d e co m p re n d e r.

S ie m p re q u e te n g a d u d a s sobre el o rd e n d e las o p e ra c io n e s, utilice p a ré n te ­


sis. E sto o c a sio n a rá q u e el co m p ila d o r utilice u n o s c u a n t o s m ilis e g u n d o s m ás,
lo q u e p ro b a b le m e n te n o se n o tará al m o m e n t o d e la ejecución.

Más sobre falso y verdadero


En C++, un cero se evalúa como falso, y todos los demás valores se evalúan como ver­
daderos. Debido a que una expresión siempre tiene un valor, muchos programadores de
C++ sacan ventaja de esta característica en sus instrucciones i f . Una instrucción como
la siguiente:
i f (x) / / s i x es verdadero (d is tin to de cero)
x = 0;

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

Estas dos instrucciones también son equivalentes:


if (!x ) // si x es falso (cero)
if (x == 0) // si x es cero

Sin embargo, la segunda instrucción es un poco más fácil de comprender \ es más


explícita si usted está probando el valor matemático de x en v e / de su estado lógico.

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.

Operador condicional (ternario)


El operador condicional (?:) es el único operador ternario de C++; es decir, es el único
operador que toma tres términos.
El operador condicional toma tres expresiones y regresa un valor:
( e x p r e s i ó n -!) ? (expresión2) : (expresión3)

Esta línea se lee de la siguiente manera: “Si e x p r e s i ó n 1 es verdadera, regresar el valor


de e x p r e s i ó n 2 ; de no ser así, regresar el valor de e x p r e s i ó n 3 " . Por lo general, este
valor se asignará a una variable.

El listado 4 .1 0 muestra una instrucción i f escrita utilizando el operador condicional.

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

Escriba dos números.


S a lid a Primero: 5

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

Tamhién ha explorado la precedencia de los operad«»re**. > ha \ist<> la lorma en que se


pueden m ili/ai los paréntesis para cambiar la precedencia a luí de hacerla explícita y. por
consecuencia, más fácil de manejar.

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

3. ¿Cuál es el valor de 201 / 4?


4. ¿Cuál es el valor de 201 c/c 4?
5. Si miEdad. a y b son variables de tipo in t. ¿cuáles son sus valores después de eje­
cutar las siguientes instrucciones?
miEdad = 39;
a = miEdad++;
b = ++miEdad;
6. ¿Cuál es el valor de 8+2*3?
7. ¿Cuál es la diferencia entre x = 3 y x = 3?
8. ¿Qué son los siguientes valores, verdaderos o falsos?
a. 0
b. 1

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.

• Qué es una función y cuáles son sus componentes


• C óm o declarar y definir funciones
• C óm o pasar parámetros a las funciones
• Cóm o regresar un valor de una función
• C óm o crear y utilizar bibliotecas de funciones
• Qué son las bibliotecas estándar y cuál es su contenido

Qué es una función


U na función es, en efecto, un subprograma que puede actuar sobre los datos y
regresar un valor. Cada programa de C++ tiene por lo menos una función:
main ( ) . Cuando su programa inicia, el sistema operativo llama a main ( )d e
forma automática, main () podría llamar a otras funciones, algunas de las cuales
podrían también llamar a otras.
100 Día 5

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.

Figura 5.1 Programa

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.

Valores de retorno, parámetros


y argumentos
Las funciones pueden regresar un valor. Al llamar a una función, ésta puede hacer su tra­
bajo y luego regresar un valor como resultado de ese trabajo. Este valor se conoce como
valor de retorno, y usted debe declarar la función con el tipo de ese valor de retorno. Por
lo tanto, si usted escribe
int miFuncion();
está declarando que miFuncion regresará un valor de tipo entero.
También puede enviar valores hacia las funciones. La descripción de los valores enviados
se conoce como lista de parámetros.
int miFuncion(int algunEntero, float algunFlotante);
Esta declaración indica que miFuncion no sólo regresará un entero, sino que también
recibirá un valor de tipo int y uno de tipo float como parámetros.
Funciones 101

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.

Declaración y definición de funciones


El uso de funciones en su programa requiere que primero declare la función y que luego
la defina. La declaración le indica al compilador el nombre, el tipo de valor de retomo y
los parámetros de la función. La definición le indica al compilador cómo trabaja la fun­
ción. Ninguna función debe ser llamada desde otra función si no ha sido declarada. La
declaración de una función se conoce como prototipo.

Declaración de una función


Existen tres maneras de declarar una función:
• Escribir su prototipo en un archivo, y luego utilizar la directiva #include para
incluirla en su programa.
• Escribir el prototipo dentro del archivo en el que se utiliza su función.
• Definir la función antes de llamarla desde cualquier otra función. Al hacer esto, la
definición actúa como su propia declaración.
Aunque puede definir la función antes de usarla y, por ende, evitar la necesidad de crear
un prototipo de función, ésta no es una buena práctica de programación por tres razones.
En primer lugar, es mala idea hacer que las funciones aparezcan en un archivo en un
orden específico. Esto dificulta el mantenimiento del programa a medida que cambian
los requerimientos.
En segundo lugar, es posible que la función A() necesite llamar a la función B( ), pero la
función B( ) también puede necesitar llamar a la función A () bajo ciertas circunstancias.
No es posible definir la función A() antes de definir la función B(), ni tampoco definir la
función B() antes de definir la función A(), así que por lo menos una de ellas debe ser
declarada en cualquier caso.
En tercer lugar, los prototipos de funciones son una técnica de depuración buena y po­
derosa. Si su prototipo declara que su función recibe un conjunto específico de paráme­
tros o que regresa un tipo específico de valor, y luego su función no concuerda con el
prototipo, el compilador puede emitir un mensaje de error en lugar de esperar a que éste
aparezca cuando ejecute el programa.
102 Día 5

Uso de los prototipos de funciones


Muchas de las funciones integradas que utilice tendrán sus prototipos de función ya es­
critos en los archivos que incluya en su programa por medio de # i n e l u d e . Dehe incluir
el prototipo para las funciones que escriba usted mismo.
El prototipo de una función es una instrucción, lo que significa que termina con un punto
y coma. Consta del tipo de valor de retomo de la función y la firma. La firma de una
función es su nombre y su lista de parámetros.
La lista de parámetros es una lista de todos los parámetros y sus tipos, separados por
comas. La figura 5.2 muestra los componentes del prototipo de una función.

Figura 5.2
Componentes de un
prototipo de función. unsigned
short int EncontrarArea (int longitud, int ancho) ;

tipo de valor de retorno nombre parámetros punto y coma

El prototipo y la definición de la función deben concordar exactamente en el tipo de va­


lor de retomo y la firma. Si no concuerdan, obtendrá un error en tiempo de compilación.
N o obstante, debe tener en cuenta que el prototipo de la función no necesita contener los
nombres de los parámetros, sólo sus tipos. Un prototipo como el siguiente es perfecta­
mente válido:
long Area(int, int);

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

■ ■ || h _ h L is t a d o 5.1 La declaración de una función y la definición y el uso


de esa función

1: // Listado 5.1 - Muestra el uso de los prototipos de funciones


2: //
3: #include <iostream.h>
4: int Area(int longitud, int ancho); //prototipo de la función
5:
6: int main()
7: {
8: int longitudDeJardin;
9: int anchoDeüardin;
10: int areaDeJardin;
11 :
12: cout « "\n¿Cuál es el ancho de su jardin? ■;
13: cin » anchoDeüardin;
14: cout « "\n¿Cuál es la longitud de su jardín? ";
15: cin » longitudDeJardin;
16:
17: areaDeüardin= Area(longitudDeJardin,anchoDeüardin) ;
18:
19: cout « "\nSu jardín es de ■;
20: cout « areaDeJardin;
21: cout « " metros cuadrados\n\n";
22: return 0;
23: }
24:
25: int Area(int jardinLongitud, int jardinAncho)
26: {
27: return jardinLongitud * jardinAncho;
28: }

¿Cuál es el ancho de su jardín? 100


S a l id a
¿Cuál es la longitud de su jardín? 200
Su jardín es de 20000 metros cuadrados
El prototipo para la función Area() se encuentra en la línea 4. Compare el pro­
A n á l is is
totipo con la definición de la función de la línea 25. Observe que el nombre, el
tipo de valor de retomo y los tipos de los parámetros son los mismos. Si fueran diferen­
tes, se habría generado un error de compilación. De hecho, la única diferencia requerida
es que el prototipo de la función termine con un punto y coma y que no tenga cuerpo.

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

Los argumentos se pasan a la función en el orden en que están declarados y definidos


pero no se comparan los nombres. Si usted hubiera pasado anchoDeüardin seguido de
longitudDeJardin, la función Area( ) habría utilizado el valor de anchoDeüardin para
jardinLongitud y el valor de longitudDeJardin para jardinAncho. I£l cuerpo de la
función siempre está encerrado entre llaves, aunque conste sólo de una instrucción, conio
en este caso.

Definición de una función


La definición de una función consiste en el encabezado de la función y su cuerpo. El
encabezado es como el prototipo de la función, sólo que los parámetros deben tener
nombre, y no se utiliza punto y coma al final.

El cuerpo de la función es un conjunto de instrucciones encerradas entre llaves. La figura


5.3 muestra el encabezado y el cuerpo de una función.

Fig u ra 5.3 tipo de valor de retomo nombre parámetros


El encabezado y el in t A rea (int longitud, int ancho)
cuerpo de una función.

^ - llave de apertura

// instrucciones

return (longitud * ancho);

\ 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

U n a d e fin ic ió n d e fu n c ió n d e b e concordar con el tip o d e v a lo r d e r e t o m o y la lista d e


p a rá m e tro s co n su p ro to tip o . Se d ebe n proporcio nar n o m b re s p a ra to d o s lo s p a rá m e ­
tros, y el c u e rp o d e la d e fin ició n d e la fun ció n d e b e estar e n ce rra d o entre llaves. T o d a s
las instru ccio ne s q u e estén d e n tro del cuerpo d e la fu n ció n d e b e n te rm in a r co n p u n to y
com a, p e ro la fu n c ió n en si n o te rm ina con p un to y com a (;). Term ina con u n a llave d e
cierre.

Si la fu n c ió n regre sa un valor, éste debe term inar con la instrucción re tu rn , a u n q u e las


instrucciones r e tu r n p u e d e n aparecer en cualquier parte del cue rpo d e la fu n ció n .

T od a fu n c ió n tie n e un tip o d e valo r d e retorno. Si no se d e sig n a u n o en fo r m a explícita,


el tip o d e v a lo r d e re to rn o será in t. Asegúrese de d ar a to d a s las fu n cio n e s u n tip o d e
v a lo r d e re to rn o explícito. Si una fun ción no regresa un valor, su tip o d e v a lo r d e re to rn o
será v o id .

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

v o id Im p rim e M e n saje (in t cualM sg)


{
if (cualMsg == 0)
cout « "Hola.\n";
if (cualMsg == 1) '
cout « "Adiós.\n";
if (cualMsg > 1)
cou t « "E sto y c o n fu n d id o .A n ";
}

Ejecución de una función


Al llamar a una función, la ejecución empieza con la primera instrucción que va después
de la llave de apertura ({). La ramificación se puede lograr mediante el uso de la instruc­
ción i f (y las instrucciones relacionadas que verá en el día 7, “Más flujo de programa”).
Las funciones también pueden llamar a otras funciones e inclusive pueden llamarse a sí
mismas (vea la sección “Recursión”, que se muestra más adelante en este día).
106
D ía 5

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.

Entrada L is t a d o 5 .2 El uso de variables locales y parámetros

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

Escriba la temperatura en grados Fahrenheit: 212


S a l id a
Aqui está la temperatura en grados centígrados: 100
Escriba la temperatura en grados Fahrenheit: 32
Aqui está la temperatura en grados centígrados: 0
Escriba la temperatura en grados Fahrenheit: 85
Aquí está la temperatura en grados centígrados: 29.4444
Funciones 10 7 |

En las líneas 6 y 7 se declaran dos variables de tipo f lo a t, una para guardar la


A nálisis
temperatura en grados Fahrenheit y la otra para guardar la temperatura en grados
centígrados. En la línea 9 se pide al usuario que escriba la temperatura en grados
Fahrenheit, y ese valor se pasa a la función Convertir().
La ejecución salta hasta la primera línea de la función Convertir (), la línea 19. en donde
se declara una variable local, que se llama también TempCen. Observe que esta variable
local no es la misma que la variable TempCen de la línea 7. Esta variable existe sólo den­
tro de la función Convertir (). El valor que se pasa como parámetro, TempFar, también
es sólo una copia local de la variable que main() pasa a la función.
Esta función hubiera podido nombrar FarTemp al parámetro y CenTemp a la variable
local, y el programa funcionaría igual de bien. Puede escribir estos nombres y volver a
compilar el programa para ver su funcionamiento.
El valor que se obtiene al restar 32 al parámetro TempFar, multiplicar el resultado por 5 y
luego dividirlo entre 9 se asigna a la variable local TempCen. El valor resultante se regre­
sa entonces como el valor de retomo de la función, y se asigna a la variable TempCen,
línea 11, de la función main( ). El valor se imprime en la línea 13.
El programa se ejecuta tres veces. La primera vez se pasa el valor 212 para comprobar que
el punto de ebullición del agua en grados Fahrenheit (212) genere la respuesta correcta
en grados centígrados (100). La segunda prueba es el punto de congelación del agua. La
tercera prueba es un número aleatorio elegido para generar un resultado decimal.
Como ejercicio, escriba el programa otra vez con otros nombres de variables, como se
muestra a continuación:
1: #include <iostream.h> o-v5
2:
3: float Convertir(float); ■:ov-i..■
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 << "\nAqui está la temperatura an grados centígrados. ;
13: cout « TempCen « endl;
14: return 0;
15: >
16:
17: float Convertir(float GradFar)
18: {
19: float GradCen;
20:
108 Día 5

21: Gradeen = ((GradFar • 32) * 5) / 9;


22: return GradCen;
23: }

Los resultados son los mismos.


Se dice que una variable tiene un alcance, el cual determina cuánto tiem po estará dispo­
nible para el programa y en dónde se puede utilizar. Las variables declaradas dentro de
un bloque tienen su alcance limitado a ese bloque; se pueden acceder sólo dentro de ese
bloque y “dejan de existir” cuando ese bloque termina. Las variables globales tienen
alcance global y están disponibles en cualquier parte del programa.
Por lo general el alcance es obvio, pero existen algunas e x c e p c io n e s e n g a ñ o s a s . Verá más
sobre esto cuando hablemos de los ciclos f or en el día 7.
Nada de esto importa mucho si usted tiene cuidado de no volver a utilizar los mismos
nombres de variables dentro de cualquier otra función.

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.

Entrada L i s t a d o 5 .3 Muestra de variables globales y locales

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

Este programa sencillo ilustra unos cuantos puntos clave, y potencialmente


engañosos, sobre las variables locales y las globales. En la línea 4 se declaran
dos variables globales, x y y. La variable global x se inicializa con el valor 5. y la varia­
ble global y se inicializa con 7.
En las líneas 8 y 9, dentro de la función m a i n ( ), estos valores se imprimen en la pantalla.
Observe que la función m a i n ( ) no define ninguna de las dos variables; debido a que son
globales, ya están disponibles para m a i n ( ).
Cuando se llama a m i F u n c i o n () en la línea 10, la ejecución del programa pasa ada línea
18, se define una variable local llamada y, y se inicializa con el valor 10. En la línea 21, 5
m i F u n c i o n () imprime el valor de la variable x, y el valor de la variable global x se utiliza
como en main (). Sin embargo, cuando se utiliza el nombre de variable y, en la línea 22,
se utiliza la variable local y, que oculta a la variable global que tiene el mismo nombre.
La llamada a la función termina, y el control regresa a m a i n ( ), que vuelve a imprimir los
valores de las variables globales. Observe que la variable global y no fue afectada por el
valor asignado a la variable local y de m i F u n c i o n ().

Variables globales: una advertencia


En C++, las variables globales son válidas, pero casi nunca se utilizan. C++ se originó a
partir de C, y en C las variables globales son una herramienta peligrosa, pero necesaria.
Son necesarias porque hay veces que el programador necesita hacer que algunos datos
estén disponibles para muchas funciones, y no quiere pasar esa información como pará­
metro de función en función.
110 Día 5

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.

Más acerca de las variables locales


Se dice que las variables declaradas dentro de la función tienen alcance lo c a l. Esto sig­
nifica, como se dijo anteriormente, que son visibles y se pueden utilizar sólo dentro de la
función en la que se definen. De hecho, en C++ usted puede definir variables en cual­
quier parte de la función, no sólo al principio. El alcance de la variable es el bloque en el
que ésta se define. Por lo tanto, si define una variable dentro de un par de llaves que se
encuentren dentro de una función, la variable estará disponible sólo dentro de ese bloque.
El listado 5.4 ilustra esta idea.

Entrada L is t a d o 5.4 Variables con alcance de bloque


1: // Listado 5.4 - muestra de variables
2: // que tienen alcance de bloque
3:
4: #include <iostream.h>
5:
6: void miFunc();
7:
8: int main()
9: {
10 int x = 5;
11 cout « "\nEn main x vale: " « x;
12
13 miFunc();
14
15 cout « "\nDe regreso en main, x vale: " « x « endl;
16 return 0;
17 }
18
19 void miFunc()
20 {
21
22 int x = 8;
23 cout « “\nEn miFunc, la variable local x vale: 11 « x « endl;
24
25 {
26 cout « "\nEn el bloque de miFunc, x vale: " « x;
27
28 int x = 9;
Funciones 111

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

En el bloque de miFunc, x vale: 8


La misma variable local x vale: 9
Fuera del bloque, en miFunc, x vale: 8
De regreso en main, x vale: 5
Este programa empieza en la línea 10 con la inicialización de la variable local x
--------- 1 de main (). La impresión de la línea 11 verifica que x haya sido micializada con
el valor 5.
Se llama a miFunc(), y en la línea 22 se inicializa una variable local, también llamada x,
con el valor 8. Su valor se imprime en la línea 23.
En la línea 25 se inicia un bloque, y la variable x de la función se imprime otra vez en la
línea 26. En la línea 28 se crea una nueva variable, también llamada x, pero que es local
para el bloque, y se inicializa con el valor 9.
El valor de la nueva variable x se imprime en la línea 30. El bloque local termina en la
línea 31, y la variable creada en la línea 28 queda “fuera de alcance”, por lo que no se
puede ver ni utilizar.
Al imprimir x en la línea 33, se imprime la x que se declaró en la línea 22. Esta x no
resultó afectada por la x que se definió en la línea 28; su valor sigue siendo 8.
En la línea 34, miFunc () queda fuera de alcance, y su variable local x no se puede uti­
lizar. La ejecución regresa a la línea 15, y se imprime el valor de la variable local x que
se creó en la línea 10. Esta variable no resultó afectada por ninguna de las variables
definidas en miFunc ().
Sin necesidad de decirlo, ¡este programa hubiera sido mucho menos confuso si estas tres
variables tuvieran nombres distintos!

Instrucciones de una función


No existe casi ningún límite para la cantidad o tipos de instrucciones que se pueden co­
locar en el cuerpo de una función. Aunque no puede definir una función dentro de otra
función, sí puede llamar a una función, y desde luego que main() hace justo eso casi en
cualquier programa de C++. Las funciones pueden inclusive llamarse a sí mismas, lo
cual se trata en la sección que habla sobre la recursión.
Día 5

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.

Más acerca de los argumentos de funciones


Los argumentos de funciones no tienen que ser todos del mismo tipo. Es perfectamente
razonable escribir una función que tome un entero, dos enteros largos y un carácter como
sus argumentos.

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.

Uso de funciones como parámetros para otras funciones


Aunque es válido para una función tomar como parámetro una segunda función que
regrese un valor, esto puede ocasionar que el código sea difícil de leer y de depurar.

Como ejemplo, suponga que tiene las funciones doble () , t r i p l e ( ) , cuadrado () y


c u b o (), cada una de las cuales regresa un valor. Podría escribir
Respuesta = (doble(triple(cuadrado(cubo(miValor)))));
Esta instrucción toma una variable, miValor, y la pasa com o argumento para la función
cubo (), cuyo valor de retomo se pasa como argumento a la función cuadrado ( ) , cuyo
valor de retomo se pasa a su vez a triple (), y ese valor de retorno se pasa a doble ().
El valor de retomo de este número duplicado, triplicado, elevado al cuadrado y elevado
al cubo se pasa entonces a Respuesta.
Es difícil estar seguro de lo que hace este código (¿era triplicado el valor antes o después
de ser elevado al cuadrado?), y si la respuesta es equivocada, será difícil averiguar cuál
función falló.

Una alternativa es asignar cada paso a su propia variable intermedia:


unsigned long miValor = 2;
unsigned long alcubo = cubo(miValor); // alcubo = 8
unsigned long alcuadrado = cuadrado(alcubo); // alcuadrado = 64
unsigned long triplicado = triple(alcuadrado); // triplicado = 192
unsigned long Respuesta = doble(triplicado); // Respuesta = 384

Ahora cada resultado intermedio puede ser examinado, y el orden de ejecución es explícito.
Funciones 113

Los parámetros son variables locales


Los argumentos que se pasan a una función son locales para esa función. Los cambios
hechos a los argumentos no afectan los valores de la función que hace la llamada. Esto
se conoce como paso de parámetros por valor, lo que significa que se hace una copia
local de cada argumento de la función. Estas copias locales se tratan de la misma forma
que cualquier otra variable local. El listado 5.5 ilustra este punto.

E n t r a d a | L is t a d o 5 .5 Una muestra de parámetros pasados por valor

1: // Listado 5.5 - muestra de parámetros pasados por valor


O•
3: #include <iostream.h>
4.
5: void intercambiar(int x, int y);
c.
7: int main()
8: {
9: int x = 5, y = 10;
10:
11 : cout « "main(). Antes del intercambio, x: " « x « " y:
**•" « y « "\n ";
12: intercambiar^,y) ;
13: cout « "main(). Después del intercambio, x: " « x « " y:
ta»" « y « "\n";
14: return 0;
15: }
16:
17: void intercambiar (int x, int y)
18: {
19: int temp;
20:
21 : cout « "Intercambiar(). Antes del intercambio, x:: 11 « x « 11 y:
ta»" « y « "\n ";
22:
23: temp = x;
24: x = y;
25: y = temp;
26:
27: cout « "Intercambiar(). Después del intercambio, x: " « x « " y:
k»" « y « "\n ";
28:
29: }

main(). Antes del intercambio, x: 5 y: 10


Salida
Intercambiar()• Antes del intercambio, x: 5 y: 10
Intercambiar!)• Después del intercambio, x: 10 y: 5
main(). Después del intercambio: x: 5 y: 10
114 Día 5

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 () .

Más acerca de los valores de retorno


Las funciones regresan un valor o regresan void. void es una señal para el compilador de
que no se regresa ningún valor.
Para regresar un valor de una función, escriba la palabra clave return seguida del valor que
quiere regresar. El valor podría ser en sí una expresión que regrese un valor. Por ejemplo:
return 5;
return (x > 5);
return (MiFuncion());

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

Entrada L is t a d o 5 .6 Una muestra de varias instrucciones return

1: // Listado 5.6 - muestra varias instrucciones


2: // return
3:
4: #include <iostream.h>
5:
6: int Duplicador(int CantidadADuplicar);
7:
8: int main()
9: {
10 :
11: int resultado = 0;
12: int entrada;
13:
14: cout « "Escriba un número entre 0 y *0,000 para duplicarlo:
15: cin » entrada;
16:
17: cout « "\nAntes de llamar al duplicador... “;
18: cout « "\nentrada:" « entrada « " duplicada:
« resultado « "\n";
19:
20: resultado = Duplicador(entrada);
21:
22: cout « "\nRegresando del duplicador...\n";
23: cout « "\nentrada:" « entrada « " duplicada:
w" « resultado « "\n";
24:
25:
26: return 0;
27: }
28:
29: int Duplicador(int original)
30: {
31: if (original <=10000)
32: return original * 2;
33: else
34: return -1;
35: cout « "iNo puede llegar aquí!\n";
36: >

Escriba un número entre 0 y 10,000 para duplicarlo: 9000


S a l id a
Antes de llamar al duplicador...
entrada: 9000 duplicada: 0

Regresando del duplicador...

entrada: 9000 duplicada: 18000


116 D ía 5

Escriba un número entre 0 y 10,000 para duplicarlo: 11000


Antes de llamar al duplicador...
entrada: 11000 duplicada: 0
Regresando del duplicador...
entrada: 11000 duplicada: -1

En las líneas 14 y 15 se pide un número y se imprime en la línea 18. junto con el


A n á l is is
resultado de la variable local. En la línea 20 se llama a la función Duplicador(),
y el valor de entrada se pasa como parámetro. El resultado será asignado a la variable
local llamada resultado, y los valores se imprimirán de nuevo en la línea 23.
En la línea 31, dentro de la función Duplicador ( ) , el parámetro se prueba para ver si
es mayor que 10,000. Si no lo es, la función regresa el doble del número original. Si es
mayor que 10,000, la función regresa -1 como valor de error.
Nunca se llega a la instrucción de la línea 35 porque el valor sea o no mayor que 10,000,
la función regresa ya sea en la línea 32 o en la 34, antes de llegar a la línea 35. Un buen
compilador advertirá que esta instrucción no se puede compilar, ¡y un buen programador
la quitaría!

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

R e sp u e sta : A m b a s fu n c io n a rá n en los c o m p ila d o re s G N U y e n la m a y o r ía d e lo s d e m á s ,


p e ro s ó lo i n t m a in () se a p e g a a las n o rm a s A N S I, y p o r c o n s e c u e n c ia s ó l o i n t m a in ()
e stá g a r a n t iz a d a p a ra s e g u ir fu n c io n a n d o e n el fu tu r o .
La d ife re n c ia es q u e i n t m á in () re g re sa u n v a lo r a l s is t e m a o p e r a t iv o . P o r e j e m p lo , a l
t e r m in a r su p r o g r a m a , ese v a lo r p u e d e ser c a p t u r a d o p o r a r c h iv o s d e s e c u e n c ia s d e
co m an d o s.
N o s o tr o s n o u tiliz a m o s el v a lo r d e re to rn o .d e main e n n u e s tr o s e j e m p lo s (u s t e d p o d r ía
c o m p r o b a r lo en u n a d e sus secuencias d e c o m a n d o s d e sh e ll si lo d e s e a ), p e r o e l e s t á n d a r
A N S I ío 're q u ie re . ^ * •_ ' ' . '

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.

En t r a d a L is t a d o 5 .7 Una muestra de valores predeterminados de parámetros

1: // Listado 5.7 - muestra el uso


2: //de los valores predeterminados de parámetros
3:
4: #include <iostream.h>
5:
6: int VolumenCaja(int longitud, int ancho = 25, int altura = 1);
7:
continúo
L ista d o 5 .7 continuación

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 >

La primera vez el volumen es igual a: 10000


S a l id a La segunda vez el volumen es igual a: 5000
La tercera vez el volumen es igual a; 2500

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.

La sobrecarga de funciones también se conoce como p o lim o r fism o d e fu n c io n e s . P o li sig­


nifica muchos, y m o rfism o significa forma: una función polimorfa tiene muchas formas.
El polimorfismo de funciones se refiere a la capacidad de “sobrecargar” una función con
más de un significado. Al cambiar el número o el tipo de los parámetros, puede dar el
mismo nombre a dos o más funciones, y se llamará a la función adecuada mediante la
comparación de los parámetros utilizados. Esto le permite crear una función que pueda
sacar el promedio de enteros, flotantes y otros valores sin tener que crear nombres indi­
viduales para cada función, como P r o m e d i o E n t e r o s ( ) , P r o m e d i o F l o t a n t e s (), etc.
Suponga que escribe una función que d.mr
pasar un int, un long, un float o un d cu.a^ u*cr c n ln ,da tluc 1° dé. U ste d quiere
crear cuatro nombres de función: 0uble. Sin so b recarg a de I u n c io n e s, te n d ría que
int DuplicarEntero(int);
long DuplicarLargo(long);
float DuplicarFlotante(float)•
double DuplicarOoble(doubXe)¡*

Con la sobrecarga de funciones, haría la


siguiente declaración:
int Duplicar(int);
long Duplicar(long);
float Duplicar(float);
double Duplicar(double);

La sobrecarga es más fácil de leer v dp nt r ,,


función llamar; sólo necesita pasar una “ u? " ° ‘Íene qUC PreocuParse Por CUá!
apropiada. El listado 5.8 muestra el uso d i ’ y ** "ama au,om áticam ente a la func,ón
61 uso de la sobrecarga de funciones.

im a m a í ™ 18“ Una muestra del polimorfismo


1: // Listado 5.8 - muestra el
2: // polimorfismo de funciones
3:
4: tfinclude <iostream.h>
c•
9 •
6: int Duplicar(int);
7: long Duplicar(long);
8: float Duplicar(float);
9: double Duplicar(double):
10:
11 : int main()
12: {
13: int miEntero = 6500 ;
14: long miLargo = 65000;
15: float miFlotante = 6 .5 F;
16: double miDoble = 6.5e20;
17:
18: int enteroDuplicado;
19: long largoDuplicado;
20: float flotanteDupücado ;
21 : double dobleDupücado;
22:
23: coût « "miEntero: " « miEntero « "\n";
24: coût « "miLargo: " « miLargo « "\n";
25: coût « "miFlotante: " « miFlotante « "
26: coût « "miDoble: " « miDoble « u\n";
27:
Funciones 121

28: enteroDuplicado = Duplicar(miEntero) ;


29: largoDuplicado = Duplicar(miLargo) ;
30: flotanteDuplicado = Duplicar(miFlotante);
31: dobleDuplicado = Duplicar(miDoble) ;
32:
33: cout << “enteroDuplicado: ° « enteroDuplicado « “\n‘¡
34: cout « "largoDuplicado: 0 « largoDuplicado« "\n°;
35: cout « "flotanteDuplicado: 0 « flotanteDuplicado« “\n";
36: cout « “dobleDuplicado: " « dobleDuplicado« "\n";
37:
38: return 0;
39: }
40:
41: int Duplicar(int original)
42: {
43: cout « "En Duplicar(int)\n°;
44: return original * 2;
45: }
46:
47: long Duplicar(long original)
48: {
49: cout « "En Duplicar(long)\n";
50: return original * 2;
51: }
52:
53: float Duplicar(float original)
54: {
55: cout « "En Duplicar(float)\n";
56: return original * 2;
5 7: }
58:
59: double Duplicar(double original)
60: {
61: cout « "En Duplicar(double)\n" ;
62: return original * 2;
63: }

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.

Temas especiales sobre funciones


Debido a que las funciones son tan fundamentales para la programación, surgen unos
cuantos temas especiales que podrían ser de su interés cuando confronte problemas poco
usuales. Si las utiliza sabiamente, las funciones en línea pueden ayudarlo a sacar el máxi­
mo rendimiento. La recursión de funciones es una de esas partes m aravillosas y enig­
máticas de la programación, con la que, de vez en cuando, se puede resolver un proble­
ma que de otra manera sería muy difícil de solucionar.

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

de velocidad podría ser ilusorio. En primer lugar, los compiladores optimizadores de la


actualidad hacen un magnífico trabajo por sí solos, y declarar una función en línea casi
nunca produce un gran aumento de velocidad. Lo que es más importante, el aumento de
tamaño produce una disminución en el rendimiento.
¿Cuál es la regla empírica? Si tiene una función pequeña (una o dos instrucciones), es
una buena candidata para hacerla una función en línea. No obstante, cuando tenga duda,
no la use como función en línea. El listado 5.9 muestra el uso de una función en línea.

En t r a d a L is t a d o 5 .9 Muestra de una función en línea

1: // Listado 5.9 - muestra las funciones en linea


2:
3: #include <iostream.h>
4:
5: inline int Duplicar(int);
6:
7: int main()
8: {
9: int numero;
10 :
11: cout « "Escriba un número.con el que quiera trabajar:
12: cin » numero;
13: cout « "\n";
14:
15: numero = Duplicar(numero);
16: cout « "Número: " « numero « endl;
17:
18: numero = Duplicar(numero);
19: cout « "Número: " « numero « endl;
20:
21:
22: numero = Duplicar(numero);
23: cout « "Número: " « numero « endl;
24: return 0;
25: }
26:
27: int Duplicar(int numero)
28: {
29: return numero * 2;
30: }

Escriba un número con el que quiera trabajar: 20


S a l id a
Número: 40
Número: 80
Número: 160
124
Día 5

En la línea 5 se declara Duplicar n „__ __ .,


A nálisis
rámetro de tipo i n t y reerecnnH ° unc,tín cn *'n c a * ,a cual lo n ia un pa"
otro prototipo, excepto que se antepone ^ dcclarac,ón cs com o la de cua,cluier
retomo. P ° C ° pa abra reservada i n l i n e antes del valor de

Al compilarse esto, produce el m ism n


dl8 ° que si hubiera escrito lo siguiente:
numero = numero * 2;

en cualquier lugar en el que lo hubiera escrito


numero = Duplicar(numero);
Al momento en que se ejecuta el nm onmo i
i j _n arr,u:./r, . , , _ P ® ama, las instrucciones ya están en su lugar, compi­
ladas en el archivo ejecutable E sto J
t tpnrr nn r^r^rr *, 0ITa
costa de tener un programa más pranHí» un sabo en la ejecución del código, pero a

F|nd'Ca a * compilador que usted quiere que se p o n g a en linea a la


f u n r ió n
u n a lla m a H ^
171^ 13 o rb e n e *a libertad de ignorar la indicación y de hacer
una llamada real a la función.

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

En t r a d a L is t a d o 5 .1 0 Muestra de la recursión im plem entando la serie de Fibonacci

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 }

Escriba el número a encontrar: 6


S a l id a

Procesando fib(6).. Llama a fib(4) y a fib(5).


Procesando fib(4).. Llama a fib(2) y a fib(3).
Procesando fib(2).. iRegresa 1!
Procesando fib(3).. Llama a fib(1) y a fib(2).
Procesando fib(1).. •Regresa 11
Procesando fib(2).. iRegresa 1!
Procesando fib(5).. Llama a fib(3) y a fib(4).
Procesando fib(3).. Llama a fib(1) y a fib(2).
Procesando fib(1).. iRegresa 1!
Procesando fib(2).. iRegresa 1!
Procesando fib(4).. Llama a fib(2) y a fib(3).
Procesando fib(2).. iRegresa 1!
Procesando fib(3).. Llama a fib(1) y a fib(2).
Fundones 127

Procesando fib(l)... ¡Regresa 1!


Procesando fib(2)... ¡Regresa 11
8 es el número 6 en la serie de Fibonacci

A lg u n o s c o m p ila d o re s (que no son d e G N U ) tie n e n d ific u lta d e s al u tiliz a r


Nota o p e ra d o re s en u n a instrucción cout. Si recibe u n a a d v e rte n c ia en la líne a 28,
c o lo q u e p aré ntesis alre d e d o r de la op e ración d e resta p a ra q u e d ich a líne a
se ve a así:
28: cout « "Llama a fib("<< (n-2) « ") y a
fib(" « (n-1) « ").\n";

En la línea 9, el programa pide que se encuentre un número, y asigna ese número a n.


Luego llama a f ib () con n. En la línea 20, la ejecución se ramifica hacia la función
f i b ( ), que imprime su argumento.
En la línea 21 se prueba el argumento n para ver si es menor que 3; de ser así, f i b ( )
regresa el valor 1. De no ser así, regresa la suma de los valores regresados al llamar a
f i b ( ) con n-2 y n-1.
La función no puede regresar estos valores sino hasta que se resuelva cada llamada (a
f i b ( )). Por lo tanto, usted puede imaginar al programa dividiéndose en f i b () una y
otra vez, hasta llegar a una llamada a f i b ( ) que regrese un valor. Las únicas llamadas
que regresan un valor inmediatamente son las llamadas a f i b ( 2 ) y a f i b (1). Estos va­
lores de retomo se pasan entonces a las funciones que hicieron las llamadas y que están
en espera, las que a su vez suman el valor de retomo al suyo, y luego regresan ese valor.
Las figuras 5.4 y 5.5 muestran esta recursión de f i b ( ).

Figura 5.4
Uso de la recursión.

j rotum 1 ! j retum 1 j j return 1 j j retum 1 j j return \ j


| 128 D ía 5

Figura 5.5 fáX 6)______________ »•»KO)


Retomo de la recursión. ^ n f t a n W ♦ fü?S ^

^ no|j; v

/ rotum 1 / / rotum fibl ♦ fib2 / / 1 / / ro,urn 1 / / rotum Gb1 ♦ Í¡b2 /

__________ / L ___________ / ut>(y)/ / \ ! f f ___ /


1 / rotum^ 7
n b ( i ) V ^ rib(2)/ Z ------------------- / , lb

! retum 1 j ^ rotum 1 j J roturnT j j rolurn 1 * j j rotum 1 ^ j

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.

La recursión es una parte engañosa de la program ación avanzada. Se pre­


senta aquí porque puede ser útil para com prender los fu n d am en to s de su
funcionamiento, pero no se preocupe mucho si no en tien d e com pletam ente
todos los detalles.
Funciones 129

Cómo trabajan las funciones:


un vistazo a su interior
Al llamar a una función, el código se ramifica a la función llamada, se pasan los paráme­
tros y se ejecuta el cuerpo de la función. Al terminar de ejecutarse la función, se regresa
un valor (a menos que la función regrese void), y el control regresa a la función que hizo
la llamada.
¿Cómo se logra esto? ¿Cómo sabe el código hacia donde ramificarse? ¿En dónde se
guardan los valores cuando se pasan? ¿Qué ocurre con las variables que se declaran
en el cuerpo de la función? ¿Cómo se pasa el valor de retomo? ¿Cómo sabe el código en
dónde continuar?
La mayoría de los libros introductorios no trata de contestar estas preguntas, pero sin
entender esta información, le parecerá que la programación sigue siendo un misterio. El
tema requiere de una breve explicación sobre la memoria de la computadora.

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;

103 Fune (x,y);

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.

Figura 5.8 apuntador


El apuntador de la de la pila
100
pila.
fuera de la pila
laVariable 101

102
Mi Edad 103
104
105

SuEdad 106 dentro de la pila


107
108
109
110

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

Figura 5.9 apuntador


de la pila
Movimiento del apun­ 100
tador de la pila. laVariable 101
l 108 I

102
MiEdad 103
104 fuera de la pila

105
SuEdad 106
107
108 <=3
109
110 } dentro de la pila

La pila y las funciones


L o siguiente es lo que ocurre cuando un programa, que se ejecute en la m ayoría de los
sistem as, se ramifica hacia una función:
1. La dirección del apuntador de instrucciones se increm enta a la siguiente instruc­
ción después de la llamada a la función. Esa dirección se coloca a continuación en
la pila, y será la dirección de retom o cuando la función regrese.
2. Se hace espacio en la pila para el tipo de valor de retorno que usted declaró. Si en
un sistema con enteros de 2 bytes se declara com o i n t el tipo de v alor de retorno,
entonces se agregan otros dos bytes a la pila, pero no se co lo ca ningún valor en
estos bytes.
3. La dirección de la función llamada, que tam bién se en cu en tra en el área de código,
se carga en el apuntador de instrucciones, por lo que la siguiente instrucción que se
ejecute estará en la función llamada.
4. La parte superior actual de la pila se anota y se guarda en un a p u n tad o r especial
llam ado borde de la pila. En adelante, todo lo que se agregue a la pila hasta que la
función regrese, se considera “local” para la función.
5. Todos los argumentos para la función se colocan en la pila.
6. La instrucción que se encuentra ahora en la dirección del ap u n tad o r de instruccio­
nes se ejecuta, lo cual viene siendo la prim era instrucción de la función.
7. Las variables locales se introducen en la pila a m edida que se definen.
C uando una función está lista para regresar, ocurre lo siguiente:
1. El valor de retom o se coloca en el área de la pila reservada en el paso 2 de arriba.
2. A continuación, la pila se desplaza hacia abajo hasta llegar al ap u n tad o r del borde
de la pila, lo que efectivamente descarta todas las variables locales y los argum en­
tos para la función.
Funciones 133

3. El valor de retorno se saca de la pila y se asigna como el valor de la llamada de la


función en sí.
4. La dirección que se guardó en el paso 1 de la llamada se recupera y se coloca en el
apuntador de instrucciones. El programa continúa inmediatamente después de la
llamada a la función, con el valor de la función recuperado.
Algunos de los detalles de este proceso cambian de un compilador a otro, o de una com pu­
tadora a otra, pero las ideas esenciales son consistentes en todos los entornos. En general,
cuando se llama a una función, se colocan en la pila la dirección de retom o y los pará­
metros. Durante la vida de la función, las variables locales se agregan a la pila. Cuando
la función regresa, las variables locales se eliminan al vaciar la pila.
En los días siguientes verá otras ubicaciones en memoria que se utilizan para guardar
datos que deben continuar más allá de la vida de la función.

Programas de archivos fuente múltiples


(bibliotecas de funciones creadas por
el programador)
Uno de los puntos más fuertes de C y de C++ es la habilidad para reutilizar el código.
Ahora todos los lenguajes soportan esta habilidad. Sin embargo, para m uchos de ellos el
mecanismo es cortar y pegar (también conocido como clonación); cuando usted quiere
utilizar un código que ya existe, copia el código fuente en el programa nuevo y parte de
ahí. El problema con este método es la dificultad en el mantenimiento. C uando la regla
empresarial incluida en el código cambie (y todas lo harán alguna vez), todas las copias
del código tienen que cambiar. Pero cada versión es un poco diferente y se tiene que
revisar cuidadosamente antes de poder cambiarla.
C y C++ proporcionan un mejor mecanismo para la reutilización del código: bibliotecas
de funciones y de objetos. Éstas son colecciones de código ya compilado en form ato de
archivo objeto (.o o .o b j). Con una disciplina apropiada, hay solamente una versión que
todos los programadores utilizan. Sólo se cambia esa versión del código fuente, y cual­
quier programa que se vea afectado se vuelve a compilar o a enlazar.
Además del archivo objeto, usted debe crear un archivo de encabezado que contenga los
prototipos para esas funciones.
Linux también soporta un archivo de “biblioteca” en forma de archivo de paquetes. Un
archivo de paquetes contiene varios archivos objeto. El enlazador sabe cómo leer archi­
vos en formato objeto simple o cómo extraerlos de un archivo de paquetes. El com ando
a r se utiliza para manipular archivos de paquetes. Revise su manual o el archivo de
información para más detalles.
| 134 D ía 5

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

Cómo crear y utilizar bibliotecas de funciones con g++


Una biblioteca de funciones simple consta de tres partes: el código fuente de las funcio­
nes (las funciones en sí), los prototipos de las funciones de un archivo de encabezado y
las funciones compiladas.
Puede sacar la función del programa de la serie de Fibonacci que se m uestra en el listado
5.10. El listado 5.1 la contiene el archivo de encabezado y se debe g u ard ar com o ls t0 5 -
11 .h.

L is t a d o 5 .1 1 a Archivo de encabezado ( Is t 0 5 - 1 1. h) para mostrar


Entrada las bibliotecas de funciones

1: // archivo de encabezado lst05-11.h


2: ¿ifndef __LST05-11_H
3: ¿define _LST05-11_H
4: int fib (int n);
5: #endif

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.

Entrada L is t a d o 5 .1 1 b Biblioteca de funciones


1: ¿inelude <iostream.h>
2: ¿inelude "lst05-11.h"
3: // archivo fuente de biblioteca lst05-11.cxx
4: int fib (int n)
5: {
6: cout « “Procesando fib(” « n « ")... ";
7: if (n < 3)
8: {
9: cout « "¡Regresa 1!\n";
10: return 1;
11: }
12: else
13: {
14: cout « "Llama a fib(" « n-2 « “) y a fib(" « n-1 « ").\n";
15: return(fib(n-2) + fib(n-1));
16: }
17: }
Funciones 135

El listado 5.12 contiene el programa principal que llama a la función f i b ( ). Esto es lo


mismo que el programa del listado 5.10, excepto que la función se ha sacado y el pro­
totipo de la función se encuentra en un archivo de encabezado (en lugar de estar en el
mismo archivo que main ()).

L is t a d o 5 .1 2 Muestra de la recursion implementando la serie de


Entrada Fibonacci— Uso de bibliotecas

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

Hay dos maneras de compilar las funciones f ib () y main() juntas; la primera es al


mismo tiempo:
g++ lst05-12.cxx lst05-1l.cxx -o lst05-12
o puede utilizar dos comandos separados:
g++ -c lst05-11.cxx
g++ lst05-12.cxx lst05-11.o -o lst05-12
La ventaja del segundo método es que puede compilar ls t0 5 -11. cxx una vez en un ar­
chivo objeto y proporcionar el archivo objeto y el de encabezado a otros programadores.
Eso permite a los programadores utilizar su función sin poder cambiarla (porque no
tienen el código fuente).

Creación de archivos de proyecto (para make)


En su forma más simple, el comando make en Linux le permite preparar un proyecto y
dejar que ciertas reglas controlen lo que se compila y cuándo se compila. El código se
cambiará sólo cuando sea necesario (cuando algo cambie).
| 136 Día 5

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 is t a d o 5 .1 3 Archivo make que se utiliza en los listados 5.11a, 5.11b


Entrada y 5.12 (lst05*13.mak)

1: lst05-11.o: lst05-11.cxx lst05-n.h


2: g++ *c lst05-11.cxx
3: #
4: lst05-12: lst05-12.CXX lst05-11.o lst05-11.h
5: g++ lst05-12.cxx lst05-11.O -o lst05-12

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.

Las líneas 2 y 5 tie n e n u n c arácte r d e t a b u la c ió n a n t e s d e l c o m a n d o g + + .


Éste d e b e ser un carácter d e ta b u la c ió n . Si u tiliz a c u a lq u ie r o t r o c a rá c te r
a q u í (in clu ye n d o espacios), o b te n d r á el s ig u ie n t e m e n s a j e d e e r r o r d e l
c o m a n d o m ake:
suarchivo. mak:2: * * * missing separator. Stop.

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 de bibliotecas estándar


de C++ (libg++)
Todos los compiladores suscritos al estándar ANSI vienen con una serie de funciones para
que usted las utilice. Se dice que estas funciones son integradas ya que vienen con el
compilador; no las escribe usted. El compilador GNU no es la excepción. Existe una bi­
blioteca estándar de funciones para programas de C y un superconjunto de esa biblioteca
de funciones y objetos para los programadores de C++.
Aprenderá acerca de muchos de los objetos estándar más adelante. Siempre puede utilizar
el comando in f o para obtener mayor información:
info libg++
Puede obtener detalles acerca de las funciones estándar de C mediante el comando in fo .
info lib e
Las funciones estándar para C y C++ son muy importantes. Puede utilizarlas en lugar de
escribir sus propias funciones. Esto facilita la creación de los programas (menos cosas
por hacer) y simplifica enormemente su mantenimiento debido a que los fabricantes de
compiladores (Colaboradores del Proyecto GNU/Fundación para el Software Libre) se
preocupan por mantener esas funciones. Éstas se clasifican en las siguientes secciones,
matemáticas, de caracteres y funciones generales.

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

Ta b l a 5.1 Funciones matemáticas comunes


F u n c ió n U so
c e il(f) Redondea el número f al entero más pequeño que no sea menor que
f : c e i l ( 3 . 3 ) es 4 y c e i l (- 3 .3 ) es -3.
c o s(f) Calcula el coseno de f (donde f se da en radianes): e o s ( 0 . 0 ) es 1
y C 0 S (3 .1415926) es -1.
e x p (f) Función exponencial en la forma de e (: e x p ( 0 . 0 ) es 1 y e x p ( 1 . 0 )
es 2.718(e).
fa b s(f) Valor absoluto en punto flotante de f : f a b s ( - 1 . 0 ) es 1 .
f a b s ( 0 . 0 ) esO y f a b s ( - 1 . 0 ) es 1 .
flo o r(f) Redondea f al entero más grande que no sea m ayor que f :
f l o o r ( 3 .3 ) es 3 y f l o o r ( - 3 . 3 ) es -4; com párese con la
función c e i l ( ).
fmod ( f , g) Calcula el residuo de f / g como f l o a t .
lo g (f) Logaritmo natural de f usando com o base e: lo g (1 . 0 ) es 0.0 y
lo g (2 .7 1 8 ) es 1 .0 .
lo g lO (f) Logaritmo en base 10 de f : lo g 1 0 ( 0 .0 ) no está definido, lo g 1 0
( 1 . 0 ) es 0.0, lo g 10 ( 1 0 . 0 ) es 1 y lo g 1 0 ( 1 0 0 0 .0 ) es 3.
pow ( f , g) Eleva f a la g-ésima potencia (f s ): pow ( 2 . 0 , 3 . 0 ) es 8.0 y
pow( 3 .0 , 2 .0 ) es 9.0.
s in (f) Calcula el seno de f (donde f se da en radianes): s i n ( 0 . 0 ) es 0
y s in (3.1415926 / 2 .0 ) es 1.
sq rt (f) Raíz cuadrada de f: s q r t ( 4 .0 ) es 2 y s q r t ( 9 .0 ) es 3.
ta n (f) Calcula la tangente de f (donde f se da en radianes): t a n (0 . 0 )
es 0 y ta n (3 .1 4 1 5 9 2 6 / 4 .0 ) es 1.
a c o s , a s i n , a ta n Arco coseno, arco seno y arco tangente (funciones inversas del
coseno, seno y tangente).
a c o s h , a s in h , atan h Arco coseno hiperbólico, arco seno hiperbólico y arco tangente
hiperbólica (funciones inversas del coseno, seno y tangente
hiperbólicos).
c o s h , s i n h , ta n h Coseno hiperbólico, seno hiperbólico y tangente hiperbólica.

Para todas estas funciones se debe incluir el archivo de encabezado m a th . h (para obtener
los prototipos funcionales).

Funciones de caracteres y de cadenas de caracteres.


L a m anipulación de caracteres y cadenas es una tarea com ún en la m ay o ría de los pro­
gram as. Com o resultado, se ha construido una gran biblioteca de funciones a través de
los años, la cual se ha convertido en parte del estándar.
Funciones 139

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.

Ta b l a 5.2 Funciones comunes de cadenas y de caracteres


Función Uso
strcpy (s 1, s2) Copia s2 en s1 carácter por carácter hasta copiar el carácter nulo
de terminación.
st rncpy ( s 1, s2 , n ) Copia s2 en s1 carácter por carácter hasta copiar el carácter nulo
de terminación, o hasta que se copian n caracteres; s1 no termina
con carácter nulo si se terminó la copia por llegar a la cuenta de n
caracteres.
strcat (s1, s2) Agrega s2 al final de s1. (Concatena s1 y s2.)
strncat (s1, s2, n) Agrega, como máximo, n caracteres de s2 al final de s1 .
strcmp (s1, s2) Compara s1 con s2 carácter por carácter y regresa la diferencia.
Si las cadenas son iguales, se regresa 0; de no ser así, un valor
positivo o uno negativo muestra la diferencia entre las dos.
strncmp (s1, s2, n) Compara, como máximo, n caracteres de s1 con s2 carácter por
carácter y regresa la diferencia. Si las cadenas son iguales, se
regresa 0; de no ser así, un valor positivo o uno negativo muestra
la diferencia entre ambas.
strlen(s1) Regresa la longitud (en caracteres, sin contar el carácter nulo de
terminación) de s1.
strlwr (s1) Convierte a s 1 en minúsculas.
strtok (s1, s2) Divide s1 en tokens con base en los valores contenidos en s2.
Esto divide y analiza sintácticamente a s1 en piezas delimitadas
por los caracteres contenidos en s 2. La primera vez que se llama
a s trto k , se proporciona s2; cada vez subsecuente, utilice
NULL en lugar de s2.
strtod (si), strtol (s1) Convierte a s1 en un valor numérico entero o entero largo.
strupr (s1) Convierte a s 1 en mayúsculas.

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

P ¿Cuándo se debe utilizar la palabra reservada i n l i n e en un p rototip o de


función?
R Si la función es muy pequeña, no m ayor de una o dos lincas, y n o se llam ará desde
muchos lugares en su programa, se puede d eclarar co m o fu n c ió n en línea.
P ¿Por qué los cambios al valor de los argum entos de la función no se reflejan
en la función que hace la llamada?
R Los argumentos que se pasan a una función se pasan por valor. L sto sig n ifica que
el argumento de la función es en realidad una copia de la v a riab le o rig in al. Este
concepto se explica a profundidad en la sección “C ó m o trab ajan las funciones: un
vistazo a su interior”.
P Si se pasan los argumentos por valor, ¿qué hago si necesito reflejar los cambios
en la función que hace la llamada?
R En el día 8 hablaremos sobre los apuntadores. El uso de a p u n ta d o re s solucionará
este problema, además de proporcionar una form a para re so lv er la lim itació n de
regresar un solo valor de una función.
P ¿Qué pasa si tengo las siguientes dos funciones?:
int Area (int ancho, int longitud = 1); int Area (int tamanio);
¿Se sobrecargarían? Existe un número distinto de p arám etros, pero el
primero tiene un valor predeterminado.
R Las declaraciones sí compilarán, pero si invoca a la fu nción Area co n un parám e­
tro, recibirá un error en tiempo de com pilación: am b ig ü ed ad e n tre Area (int, int)
y Area(int).

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 " ;
}

v o id m iF u n c ( u n sig n e d short in t x);


{
return (4*x);
}
4. CAZA E R R O R E S : ¿Qué está mal en la función del siguiente código?
//include < io s t re a m .h > ;
i n t m iF u n c ( u n sig n e d s h o r t i n t x);
i n t m ain()
{
u n sig n e d s h o r t i n t x, y;
y = m iFu nc(x);
cout << "x : " « x « " y: " « y « "\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

Creación de nuevos tipos


Ya ha visto varios tipos de variables, incluyendo de enteros sin signo y de ca­
racteres. El tipo de una variable le da mucha información acerca de ella. Por
ejemplo, si declara A l t u r a y Ancho como tipo entero corto sin signo ( u n s ig n e d
s h o r t i n t ) , sabe que cada una puede guardar un número entre 0 y 65,535, si
el entero corto sin signo es de 2 bytes. Esto es lo que se quiere dar a entender
al decir que son enteros sin signo; tratar de guardar cualquier otra cosa en estas
variables produce un error. No puede guardar su nombre en un entero corto sin
signo, y ni siquiera debería tratar de hacerlo.
Con sólo declarar estas variables como enteros cortos sin signo, usted sabe que
es posible sumar A l t u r a y Ancho y asignar esc número a otra variable.
Día 6

El tipo de estas variables le indica lo siguiente:


• Su tamaño en memoria
• Qué información pueden guardar
• Qué acciones se pueden realizar sobre ellas
Visto en forma más general, un tipo es una categoría. Entre los tipos co n o cid o s del mun­
do real se encuentran auto, casa, persona, fruta y forma. En C + + , el p ro g ram ad o r puede
crear cualquier tipo que necesite, y cada uno de estos nuevos tipos puede ten er toda la
funcionalidad y poder de los tipos integrados.

¿Por qué crear un nuevo tipo?


Por lo general, los programas se escriben para resolver problem as del m un d o real, como
m antener el registro de los empleados o simular el funcionam iento de un sistem a de cale­
facción. Aunque puede resolver problemas com plejos utilizando p ro g ram as escritos sólo
con enteros y caracteres, le será mucho más fácil com batir p ro b lem as g ran d es y comple­
jo s si crea representaciones de los objetos de los que está hablando.
En otras palabras, simular el funcionamiento de un sistem a de calefacción es m ás fácil si
crea variables que representen cuartos, sensores térmicos, term ostatos y calentadores. Entre
más cercanas estén estas variables a la realidad, será más sencillo escrib ir el program a.

Introducción a las clases y miembros


Usted crea un nuevo tipo al declarar una clase. Una clase es sólo una colección de variables,
por lo general de tipos distintos, combinadas con un conjunto de funciones relacionadas.
Una forma de pensar en un auto es como si fuera una colección de ruedas, puertas, asientos,
ventanas etc. Otra forma es pensar en lo que un auto puede hacer: se puede m over, acelerar,
desacelerar, detener, estacionar, etc. Una clase le permite conjuntar, o reunir en un solo
paquete, todas estas partes y funciones en una sola entidad, lo que se conoce com o objeto.
U na ventaja de reunir un conjunto de características y funciones en una entidad, es la for­
m a en que se puede definir su interacción. En el ejem plo del auto ex iste u n a interacción
entre el carburador, el múltiple, las válvulas y los pistones. Sin em bargo, usted no necesi­
ta interactuar con ellos mientras maneja; simplemente oprim e el acelerador y el m o to r hará
el resto. Debido a que el funcionamiento intemo del m otor tiene poca im portancia para casi
todos los automovilistas, el motor se oculta bajo el cofre y uno se o lv id a de los detalles
técnicos. Este concepto corresponde a la encapsulación de datos y fu n c io n e s en una
clase. Encapsular en una clase todo lo que sepa acerca de un auto tien e v arias ventajas
para un programador. Todo está en un solo lugar, lo que facilita la referencia, el copiado
y la manipulación de los datos. Asimismo, los clientes de la clase (es decir, las partes del
program a que utilizan esa clase) pueden utilizar el objeto sin preo cu p arse p o r lo que hay
dentro de él o por la forma en que funciona.
U na clase se puede componer de cualquier combinación de los tipos de variables y también
de otros tipos de clases. Las variables que están en la clase se conocen co m o variables
m iem bro o datos miembro. Una clase llamada Auto podría tener variables m iem bro que
representen los asientos, el tipo de radio, las llantas, y así sucesivam ente.
Clases base 147

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.

Declaración de una clase


Para declarar una clase, utilice la palabra reservada c la ss seguida de una llave de apertura,
y luego ponga en una lista los datos miembro y métodos de esa clase. Termine la decla­
ración con una llave de cierre y un punto y coma. La declaración de una clase llamada
Gato sería así:
class Gato
{
unsigned int suEdad;
unsigned int suPeso;
void Maullar!);
};
Al declarar esta clase no se asigna memoria para un Gato. Sólo se le indica al compilador
cómo es un Gato, qué datos contiene (suEdad y suPeso), y qué puede hacer (Maullar! ))•
También se le indica al compilador qué tan grande es un Gato (es decir, cuánto espacio
debe reservar el compilador para cada Gato que se vaya a crear). En este ejemplo, si un
entero es de 4 bytes, el tamaño de un Gato sería de 8 bytes: suEdad es de 4 bytes y suPeso
ocupa otros 4 bytes. Maullar!) n0 ocuPa espacio porque no se reserva espacio de almace­
namiento para las funciones miembro (métodos).

Unas palabras sobre las convenciones


de denominación
Como programador, usted debe nombrar todas sus variables miembro, funciones miembro
y clases. Como aprendió en el día 3, “Variables y constantes”, los nombres deben ser fáciles
de entender y significativos. Gato, Rectángulo y Empleado son buenos nombres de clases.
Maullar!), PerseguirRatones!) y DetenerMotor() son buenos nombres de funciones
porque indican lo que éstas hacen. Muchos programadores denominan sus variables
miembro con el prefijo su, como en suEdad y suVelocidad. Esto ayuda a distinguir las
variables miembro de las que no son variables miembro.
148 D ía 6

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.

Comparación de clases y objetos


U sted no tiene como mascota la definición de un gato; tiene co m o m asco ta uno o más
gatos individuales. Usted establece la diferencia entre la idea de un g ato y el gato que se
encuentra justo ahora paseando por toda la sala. D e la m ism a m anera, C + + establece la
diferencia entre la clase Gato, que es la idea de un gato, y cada o b jeto individual del tipo
Gato. Por lo tanto, Pelusa es un objeto de tipo Gato, así com o PesoNeto es una variable
de tipo unsigned int.
Un objeto es una instancia individual de una clase. El térm ino técn ico para crear un obje­
to es instanciación.

,áim
, 'VíW
Clases base 149

A lg u n a s veces la te rm in o lo g ía se reutiliza y se utiliza en fo r m a e rró n e a . El


Nota s ig n ific a d o d e té rm in o s individuales, o q u é té rm in o se d e b e utilizar y en
d ó n d e , a m e n u d o son te m a de discusión y con ve n ción . C o m o re su ltad o , h o y
u n a p a la b ra es correcta, pero m a ñ a n a p u e d e ser incorrecta.

En este libro, la p a la b ra declarar se utiliza p ara ind icar q u e se está e sp e c ifi­


c a n d o o c re a n d o un ob je to o variable. En las clases p a sa a lg o similar, al d e s­
cribir los m ie m bro s (datos y funciones) en una clase d e objetos, se está d e c la ­
ra n d o esa clase.
A l tra b a ja r con variable s no rm ale s (com o int o float), n o existe d ife re n c ia
e n tre el p ro ce so d e describir y crear. Con las clases d e o b je to s sí h ay u n a
dife ren cia.

U ste d d escribe (d e fin e y declara) la clase de o b je to en u n a u b ic a c ió n d el p r o ­


g r a m a (p o r lo regular, en un archivo .hpp) y p o ste rio rm e n te crea u n o b je to
específico.
C o n las clases de objetos, cu a n d o a sig n a la m em oria, lo q u e h ace en re a lid a d
es crear un o b je to específico. A lg u n o s autores utilizan la p a la b ra especificar
p a ra este p ro p ó sito .

Cómo acceder a los miembros de las clases


Después de definir un objeto Gato (por ejemplo, Pelusa) se utiliza el operador de punto
( .) para tener acceso a los miembros de ese objeto. Por lo tanto, para asignar 50 a la va­
riable miembro suPeso del objeto Pelusa, se escribiría lo siguiente:
Pelusa.suPeso = 50;
De la misma forma, para llamar a la función Maullar(), se escribiría lo siguiente:
Pelusa.Maullar();
Cuando utiliza el método de una clase, llama al método. En este ejemplo, usted llama a
Maullar (), la cual se encuentra en Pelusa.

Asignar a objetos, no a clases


En C++ usted no asigna valores a los tipos; asigna valores a las variables. Por ejemplo,
nunca escribiría
int = 5; // incorrecto

El compilador marcaría esto como un error porque no puede asignar 5 a un entero. En


lugar de eso, debe definir una variable de tipo entero y asignar 5 a esa variable. Por
ejemplo,
int x; // declarar x para que sea int
x = 5; // asignar a x el valor 5
150 Día 6

Ésta es una manera corta de decir “A signar 5 a la v ariable \ . q u e es de tip o in t" . De la


m ism a manera, usted no escribiría
G a t o .s u E d a d = 5 ; // i n c o r r e c t o

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

Definición del alcance público


en comparación con la del privado
En la declaración de una clase se pueden utilizar otras palabras reservadas. Dos de las
más importantes son: public y private.
Todos los miembros de una clase (datos y métodos) son privados de manera predeterm i­
nada. Puede acceder a los miembros privados sólo dentro de métodos que se encuentren
en la clase misma. Puede acceder a los miembros públicos a través de cualquier objeto
que esté dentro del programa. Esta distinción es tan importante como confusa. Para hacer­
la un poco más clara, considere un ejemplo presentado anteriormente en este capítulo:
class Gato
{
unsigned int suEdad;
unsigned int suPeso;
void Maullar();
};
En esta declaración, suEdad, suPeso y Maullar() son privados debido a que todos los
miembros de una clase son privados de manera predeterminada. Esto significa que, a
menos que se especifique de otra forma, son privados.
No obstante, si escribe lo siguiente en la función m ain() (por ejemplo):
Gato Botas;
Botas.suEdad=5; // ¡error! ¡No puede acceder a datosprivados!

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();

Ahora suEdad, suPeso y Maullar () son públicos. Botas.suEdad=5 se co m p ila sin


problemas.
El listado 6.1 m uestra la declaración de una clase G ato co n variables m iem bro públicas.

Entrada L is t a d o 6.1 Cómo acceder a los miembros públicos de una clase simple

1: // Muestra de la declaración de una clase y


2: // de la declaración de un objeto de esa clase,
3:
4: inelude <iostream.h> // para cout
5:
6: class Gato // declarar el objeto clase
7: {
8: public: // los siguientes miembros son públicos
9: int suEdad;
10: int suPeso;
11: };
12:
13:
14: int main()
15: {
16: Gato Pelusa;
17: Pelusa.suEdad = 5; // asignar a la variable miembro
18: cout « "Pelusa es un gato que tiene ";
19: cout « Pelusa.suEdad « " años de edad.\n";
20: return 0;
21: }

S a l id a Pelusa es un gato que tiene 5 años de edad.

La línea 6 contiene la palabra reservada class. E sto le in d ica al com pilador


A nálisis
que lo que sigue es una declaración. El nombre de la nueva clase viene después de
la palabra reservada class. En este caso, es Gato.
El cuerpo de la declaración empieza en la línea 7 con la llave de apertura, y term ina en la
línea 11 con una llave de cierre y un punto y coma. La línea 8 contiene la palabra reser­
vada public, que indica que todo lo que sigue a continuación es público hasta llegar a la
palabra reservada private o al final de la declaración de la clase.
Clases base 153

Las líneas 9 y 10 contienen las declaraciones de los miembros de la clase, es decir, de


suEdad y suPeso.

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'

Debe hacer que los datos miembro sean privados


Como regla de diseño general, debe mantener privados los datos miembro de una clase.
Por lo tanto, debe crear funciones públicas, conocidas como métodos de acceso, para esta­
blecer y obtener los valores de las variables miembro privadas. Estos métodos de acceso
son las funciones miembro llamadas por otras partes del programa para establecer y obte­
ner valores de las variables miembro privadas.
Un método de acceso público es una función miembro de la clase que se utiliza para leer
el valor de una variable miembro privada de la clase, o para darle un valor.
¿Por qué batallar con este nivel adicional de acceso indirecto? Después de todo, es más
simple y sencillo utilizar los datos en lugar de trabajar mediante funciones de acceso.
Las funciones de acceso le permiten separar los detalles tanto de la forma en que se guardan
los datos como de la forma en que se utilizan. Esto le permite cambiar la forma en que se
guardan los datos sin tener que volver a escribir funciones que utilicen esos datos.

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

E sta técnica facilita el m antenim iento de los p ro g ram a s. Le p ro p o rc io n a u n a vid a más


larga a su código debido a que los cam bios en el d ise ñ o no h a c e n q u e su p ro g ra m a sea
obsoleto. Es una buena táctica de ingeniería de so ftw are y d e p ro g ra m a c ió n defensiva
d eb id o a que esconde los detalles del elem en to q u e h ace la lla m a d a .

El listado 6.2 m uestra la clase G ato m o d ificad a p ara in c lu ir d a lo s m ie m b ro p riv ad o s y


m étodos de acceso públicos. O bserve que éste no es un lista d o e je c u ta b le .

Entrada L is t a d o 6 .2 Una clase con métodos de acceso

1: // Declaración de la clase Gato


2: // Los datos miembro son privados, los métodos de acceso públicos
3: // se encargan de asignar y obtener los valores de los datos privados
4:
5: class Gato
6: {
7: public:
8: // elementos de acceso públicos
9: unsigned int ObtenerEdad();
10 void AsignarEdad(unsigned int Edad);
11
12 unsigned int ObtenerPeso();
13 void AsignarPeso(unsigned int Peso);
14
15 // funciones miembro públicas
16 void Maullar();
17
18 // datos miembro privados
19 private:
20 unsigned int suEdad;
21 unsigned int suPeso;
22
23

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 función miembro pública Maullar () se declara en la lín ea 16. Maullar () no es una


función de acceso. No asigna ni obtiene el valor de una variable m iem b ro ; realiza otro
servicio para la clase: imprimir la palabra Miau.
Las variables miembro se declaran en las líneas 20 y 21.
Para establecer la edad de Pelusa, se pasaría el valor al m étodo AsignarEdad ( ), com o en
el siguiente ejemplo:
Gato Pelusa;
Pelusa.AsignarEdad(5); // asignar la edad de Pelusa mediante el método de
acceso público
Clases base 155

Distinción entre privacidad y seguridad


La declaración de métodos o datos privados permite al compilador encontrar equivoca­
ciones en la programación antes de que se conviertan en errores. Cualquier programador
que valga lo que cobra puede encontrar una manera de evadir la privacidad, si lo desea.
Stroustrup, el inventor de C++. dijo: “Los mecanismos de control de acceso de C++ pro­
porcionan protección contra los accidentes, no contra los fraudes”. (ARM, 1990.)

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.

Smplementación de los métodos de una clase


Como ha visto, un método de acceso proporciona una interfaz p ú b lica p ara los datos
miembro privados de la clase. Cada método de acceso, asi com o cualquier otro m étodo de
la clase que declare, debe tener una implementación. A esto se le conoce com o d efin ició n
de métodos.

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

E n trada L is t a d o 6 .3 Im plem entación de los métodos de una clase sim ple

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

46: Gato Pelusa;


47: Pelusa.AsignarEdad(5);
48: Pelusa.Maullar();
49: cout « "Pelusa es un gato que tiene
50: cout « Pelusa.ObtenerEdad() « “ años de edad.\n";
51: Pelusa.Maullar();
52: return 0;
53: }

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

En la línea 37 empieza la definición, o implementación. del método M a u l l a r () de la clase


Gato. Este método es una función de una sola línea que imprime la palabra Miau en la
pantalla, seguida de una nueva línea. Recuerde que el carácter \n imprime una nueva
línea en la pantalla.
En la línea 44 empieza el cuerpo del programa con la conocida función m ain(). En este
caso, no toma argumentos. En la línea 46. m ain() declara un objeto del tipo Gato llamado
Pelusa. En la línea 47 se asigna el valor 5 a la variable miembro suEdad mediante el
método de acceso A signarEdad(). Observe que se llama al método mediante el uso del
nombre del objeto (Pelusa) seguido del operador miembro (.) y del nombre del método
(AsignarEdad ()). Cualquier otro método de una clase se puede llamar de la misma manera.
En la línea 48 llama a la función miembro M a u l l a r (), y en las líneas 49 y 50 se imprime
un mensaje usando el método de acceso O b t e n e r E d a d ( ). En la línea 51 se vuelve a llamar
a M a u l l a r ().

Comprensión de Sos constructores


y destructores
Existen dos maneras de declarar una variable de tipo entero. Puede declarar la variable y
asignarle un valor posteriormente en el programa, por ejemplo:
in t Peso; // d e c la r a r una v a r ia b le
// a q u í puede i r más código
Peso = 7 ; // a s i g n a r l e un v a lo r

O puede declarar el entero e inicializarlo inmediatamente. Por ejemplo:


in t Peso = 7; // d e c la r a r e i n i c i a l i z a r en 7

La inicialización combina la declaración de la variable con su asignación inicial. Nada le


impide cambiar ese valor más adelante. La inicialización asegura que su variable nunca
tenga un valor sin significado.
Las clases tienen una función miembro especial llamada constructor , el cual inicializa los
datos miembro de esa clase. El constructor puede tomar los parámetros que necesite,
pero no puede tener un valor de retorno, ni siquiera void. El constiuctor es un método de
clase que tiene el mismo nombre que ésta.
Siempre que declare un constructor, también debe declarar un destructor. Así como los
constructores crean e inicializan objetos de la clase, los destructores se encargan de limpiar
todo cuando usted ya no va a utilizar el objeto y liberan la memoria que haya asignado.
Un destructor siempre tiene el nombre de la clase, antecedido por una tilde (~). Los
destructores no toman argumentos y no tienen valor de retorno. Por lo tanto, la declara­
ción de Gato incluye lo siguiente:
-G a t o ();
160 Día 6

Puede tener múltiples constructores p o r m e d io d e la s o b r e c a r g a d e fu n c io n e s.


El com pilador selecciona la fu n ció n c o n stru c to ra q u e v a a u t iliz a r p o r m e d io
de los parám etros que usted p ro p o rcio n e en la lla m a d a o e n la c re a c ió n .
Pero debe tener m ucho cuid ad o al c o m b in a r m ú ltip le s c o n s t r u c t o r e s y a r g u ­
m entos predeterminados; el c o m p ila d o r ne ce sita te n e r la c a p a c id a d d e e le ­
g ir el constructor que usted quiere.

Constructores y destructores predeterminados


Si no declara un constructor o un destructor, el compilador crea uno por usted. Los cons­
tructores y destructores predeterminados no llevan argumentos y no hacen nada.

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.

Aunque suene desconcertante, el destructor predeterminado es el destructor proporcionado


por el compilador. Como ningún destructor toma parámetros, lo que distingue al destruc­
tor predeterminado es que no realiza ninguna acción (tiene un cuerpo de función vacío).

Uso del constructor predeterminado


¿De qué sirve un constructor que no hace nada? En parte, es cuestión de form a. Todos
los objetos deben ser construidos y destruidos, y estas funciones que no hacen nada se
llaman en el momento adecuado. Sin embargo, para declarar un objeto sin pasarle
parámetros, como:
Gato Silvestre; // Silvestre no tiene parámetros

debe tener un constructor de la siguiente manera:


Gato();

C uando se define un objeto de una clase, se llama al constructor. Si el co n stru c to r G ato ()


tuviera dos parámetros, usted podría definir a un objeto Gato escrib ien d o lo sig u ie n te:
Gato Pelusa (5, 7);

Si el constructor tuviera un parámetro, escribiría


Gato Pelusa (3);
Clases base 161

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.

Entrada L is t a d o 6.4 U so de constructores y destructores

1: // Muestra de la declaración de un constructor y


2: //un destructor para la clase Gato
3:
4: //include <iostream.h> II para cout
o: :;■í. 'K*
6: class Gato // empieza la declaración de la clase
"7 •
f . { S ii
8: public: // empieza la sección pública
9: Gato(int edadlnicial); // constructor
destructor ■v£V
10: -Gato(); if
11 : int ObtenerEdad(); II método de acceso
12: void AsignarEdad(int edad); II método de acceso
13: void Maullar();
14: private: II empieza la sección privada
15: int suEdad; 1/ variable miembro
16: };
17:
18: // constructor de Gato,
162 Día 6

L is t a d o 6 .4 continuación

19: Gato::Gato(int edadlnicial)


20: {
21: suEdad = edadlnicial;
22: }
23:
24: Gato: :-Gato() // destructor, no realiza ninguna acción
25: {
26: }
27:
28: // ObtenerEdad, método de acceso público
29: // regresa el valor de su miembro suEdad
30: int Gato::ObtenerEdad()
31: {
32: return suEdad;
33: }
34:
35: // Definición de AsignarEdad, método
36: //de acceso público
37:
38: void Gato::AsignarEdad(int edad)
39: {
40: // asignar a la variable miembro suEdad el
41: // valor pasado por el parámetro edad
42: suEdad = edad;
43: >
44:
45: // definición del método Maullar
46: // regresa: void
47: // parámetros: Ninguno
48: // acción: Imprime "miau" en la pantalla
49: void Gato:¡Maullar()
50: {
51: cout « "Miau.\n";
52: >
53:
54: // crear un gato, asignar un valor a su edad, hacer que
55: // maúlle, que nos diga su edad, y quevuelva amaullar.
56: int main()
57: {
58: Gato Pelusa(5);
59: Pelusa.Maullar});
60: cout « "Pelusa es un gato que tiene " ;
61: cout « Pelusa.ObtenerEdad() « " años de edad.\n";
62: Pelusa.Maullar();
63: Pelusa.AsignarEdad(7);
64: cout « "Ahora Pelusa tiene “ ;
65: cout « Pelusa.ObtenerEdad() « " años de edad.\n";
66: return 0;
67: >
Clases base 163

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.

Uso de funciones miembro const


Si declara un método de clase como co ns t, está prometiendo que el método no cambiará
el valor de ninguno de los miembros de la clase. Para declarar un método de una clase
como constante, coloque la palabra reservada c o n s t después de los paréntesis y antes del
punto y coma. La declaración de la función miembro constante llamada U n a F u n c i o n ( )
no toma argumentos y regresa void. Se ve así:
v oi d UnaFuncion() cons t;

Los métodos de acceso a menudo se declaran como funciones constantes mediante el


uso del modificador c o n s t . La clase Gato tiene dos métodos de acceso:
v oi d A s i g n a r E d a d ( i n t edad);
i n t Ob tene rEda d( );
164 Día 6

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.

Utilice con st siempre que sea posible. D eclare fu n c io n e s m ie m b r o c o m o c o n s t


cuando no deban cam biar el objeto. Esto pe rm ite q u e el c o m p ila d o r le a y u d e
a encontrar errores; es más rápido y m e n o s c o sto so q u e h a c e r lo u s te d m is m o .

Distinción entre interfaz e implementación


Como vio anteriormente, los clientes son la parte del programa que crean y utilizan obje­
tos de una clase. Imagine que la interfaz pública para su clase (la declaración de la clase)
es como un contrato con esos clientes. El contrato le indica la forma en que la clase debe
comportarse.
Por ejemplo, en la declaración de la clase Gato usted crea un contrato que establece que
la edad de cualquier clase Gato se puede inicializar en su constructor, se le puede asignar
un valor mediante el método de acceso AsignarEdad(), y se puede leer por m edio del
método de acceso ObtenerEdad(). También promete que cada clase Gato sabrá Maullar ().
Observe que no está diciendo nada en la interfaz pública acerca de la variable miembro
suEdad; ése es un detalle de implementación que no es parte del contrato. U sted propor­
cionará una edad (ObtenerEdad()) y asignará un valor a esa edad (AsignarEdad ( )), pero
el mecanismo (suEdad) es invisible.
Si hace de ObtenerEdad() una función const (como debe hacerlo), el contrato también
promete que este método no cambiará la clase Gato en la que lo llame.
C++ está fuertemente tipificado, lo que significa que el compilador se asegurará de que
cumpla estos contratos, emitiendo un error de compilación cuando los viole. El listado
6.5 muestra un programa que no compilará debido a las violaciones incurridas en estos
contratos.
Clases base 165

Pracauciún ¡El listado 6.5 no compila!

En t r a d a Listado 6 .5 U n a m u e s tra d e las v io la c io n e s a la in t e r fa z

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

42 // valor pasado por el parámetro edad


43 suEdad = edad;
44 }
45
46 // definición del método Maullar
47 // regresa: void
48 // parámetros: Ninguno
49 // acción: Imprime “miau" en la pantalla
50 void Gato::Miau()
51 {
52 cout « "Miau.\n°;
53 }
54
55 // muestra diversas violaciones a la
56 // interfaz, así como los errores de compilación resultantes
57 int main()
58 {
59 Gato Pelusa; //no concuerda con la declaración
60 Pelusa.Maullar();
61 Pelusa.Ladrar(); // No, tonto, los gatos no pueden ladrar.
62 Pelusa.suEdad = 7; // suEdad es privada
63 return 0;
64 }

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.

Si usted proporciona un constructor, el compilador no lo proporcionará. Por


lo tanto, si crea un constructor que tome un parámetro, entonces no tendrá
un constructor predeterminado, a menos que usted mismo escriba uno.
Clases base 167

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.

Dónde colocar declaraciones de clases


y definiciones de métodos 6
Cada función que usted declare para su clase debe tener una definición. La definición
también se conoce como la implementación del método. Al igual que otras funciones, la
definición del método de una clase tiene un encabezado y un cuerpo de función.
La definición se debe ubicar en un archivo que el compilador pueda encontrar. La mayo­
ría de los compiladores de C++ requiere que ese archivo tenga la extensión .c o .cpp. Los
compiladores GNU manejan las extensiones .c, .cpp, .cxx o ,c++. Los archivos de lista­
dos utilizan .cxx para que usted pueda utilizarlos en Linux o en un sistema de archivos
Microsoft Windows DOS/FAT. Si utiliza un compilador distinto, tendrá que determinar
cuál extensión prefiere. Tenga cuidado con la sensibilidad a mayúsculas y minúsculas en
Linux ( .CXX no es lo mismo que .cxx).

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

La declaración de una clase le indica al c o m p ila d o r lo q u e h a c e la clase, q u é


d ato s gu ard a y qué funciones tiene. La d e c la ra c ió n d e la cla se se c o n o c e
c o m o su interfaz porque le indica al u su a rio c ó m o in te r a c tu a r c o n la clase.
Por lo regular, la interfaz se g u a rd a en un a rc h ivo .hpp, el c u a l se c o n o c e
co m o archivo de encabezado.
La definición de la función le indica al c o m p ila d o r la f o r m a e n q u e é sta tr a ­
baja. La definición de la fun ció n se co n o ce c o m o la im p le m e n t a c ió n d e l
m é to d o de la clase, y se g u a rd a en un a rch ivo .cxx. Los d e t a lle s d e la im p le -
m entación de la clase conciernen só lo al a u to r d e la clase. Lo s c lie n te s d e la
clase (es decir, los com po ne nte s del p ro g r a m a q u e u tiliz a n la clase ) t a m p o c o
necesitan, ni les preocupa, saber c ó m o se im p le m e n ta n las fu n c io n e s.
Clases base 169

Aplicación de ¡a im plem entación en línea


Así como puede pedir al compilador que convierta a una función normal en función en
línea, también puede hacer que los métodos de una clase estén en línea. La palabra reser­
vada in lin e aparece antes del tipo de valor de retorno. Por ejemplo, la implementación
en línea de la función O btenerP eso() se vería así:
i n l i n e i n t G a t o : : O b t e ne r Pe s o()
{
return suPeso; // r e g r e s a r el dato miembro llamado s uP es o
}
También puede colocar la definición de una función dentro de la declaración de la clase,
lo que automáticamente hace que la función esté en línea, por ejemplo:
c l a s s Gato
{
public:
i n t O b t e ne r P es o ( ) { r e t ur n suPeso; } // en l i n e a
void A s i g n a r P e s o ( i n t peso);
};
Observe la sintaxis de la definición de ObtenerPeso (). El cuerpo de la función en línea
empieza inmediatamente después de la declaración del método de la clase; no se utiliza
punto y coma después de los paréntesis. Al igual que con cualquier función, la definición
empieza con una llave de apertura y termina con una llave de cierre. Como siem pre, el
espacio en blanco no importa; usted habría podido escribir la declaración de la siguiente
manera:
c l a s s Gato
{
public:
i n t Obtener Pes o() const
{
r et ur n suPeso;
} // en l i n e a
void A s i g n a r P e s o ( i n t peso);
6
};
Los listados 6.6a y 6.6b vuelven a crear la clase Gato, pero colocan la declaración en
l s t 0 6 - 0 6 .hpp y la implementación de las funciones en l s t 0 6 - 0 6 . cxx). El listado 6.6b
también convierte las funciones de acceso y la función M aullar () en funciones en línea.

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: };

En t r a d a L istado 6.6b Implementación de


ls t 0 6 -0 6 .c x x
1: // Muestra de las funciones en linea
2: // y la inclusión de archivos de encabezado

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.

Uso de clases con otras clases


como datos miembro
No es poco común formar una clase compleja mediante la declaración de clases más
simples y luego incluirlas en la declaración de la clase más complicada. Por ejemplo,
usted podría declarar una clase llamada rueda, una clase motor, una clase tiansmisión, 6
entre otras, y luego combinarlas en una clase llamada auto. Esto establece una ielación.
Un auto tiene un motor, ruedas y una transmisión.
Considere otro ejemplo. Un rectángulo se compone de líneas. Una línea se define como
dos puntos. Un punto se define como una coordenada x y una coordenada y. El listado
6.7b muestra la declaración completa de la clase Rectángulo, como podría aparecer en
r ec t .hpp (abreviación de r e c t á n g u l o . hpp). Debido a que un rectángulo se define como
cuatro líneas que conectan cuatro puntos, y cada punto se refiere a una coordenada en
una gráfica, primero se declara una clase llamada Punto para guardar las coordenadas
(x, y) de cada punto. El listado 6.7a muestra una declaración completa de ambas clases.
Día 6

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

40: cout « "Área: " « area « “\n";


41: cout « "Coordenada X superior izquierda:";
42: cout « MiRectangulo.ObtenerSupIzq().ObtenerX() << endl;
43: return 0;
44: }

Á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

Vuelva a escribir el listado 6.7a con estos cambios:


• En la línea 3, cambie class Punto por struct Punto.
• En la línea 17, cambie class R e c tá n gu lo por struct R e c t á n g u l o .
Ahora ejecute el programa de nuevo y compare la salida. No dehe haber ningún cambio.

Por qué dos palabras reservadas hacen lo mismo


Probablemente se esté preguntando porqué dos palabras reservadas hacen lo mismo. Este
es un accidente de la historia. Cuando se desarrollo C++. se creó como una extensión del
lenguaje C. C tiene estructuras, aunque las estructuras de C no tienen métodos de clases.
Bjarne Stroustrup. el creador de C++, hizo su creación a partir de las estructuras (struct).
pero cambió el nombre a clases (class) para representar la nueva funcionalidad expandida.

DEBE colocar su declaración de la clase


en un archivo .hpp y la implementación
de sus fu n cion e s m iem bro en un archivo
.cxx.
DEBE utilizar const siempre que pueda.
DEBE com prender las clases antes de
avan za r a otros temas.

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 .

P ¿ P o r q u é no d eb o h a c e r públicos todos los datos m iem bro?


R Hacer los datos miembro privados le permite al cliente de la clase utilizar los datos
sin preocuparse por la forma en que se guardan o se calculan. Poi ejemplo, si la
clase Gat o tiene un método llamado ObtenerEdad (), los clientes de la clase Gato
pueden preguntar la edad del gato sin saber ni preocuparse si el gato guarda su
edad en una variable miembro o si calcula su edad en el momento.
P Si u s a r u n a fu n ció n c o n st p a ra cam b iar la clase p roduce u n e r r o r de co m p ila­
ción, ¿ p o r q u é no o m itir la p a la b ra co n st p a ra estar seguro de e v ita r e rro re s ?
R Si su función miembro no debe cambiar la clase, usar la palabra reservada c o n s t
es una buena forma de hacer que el compilador lo ayude a encontrar errores típicos.
Por ejemplo, tal vez Ob te ne rE da d () no tenga motivo para cambiar la clase Gato,
pero su implementación podría tener la siguiente línea:
if ( Pelus a. suEdad = 100) cout « "¡Hey! Pelusa tiene 100 años de edad\n";

Declarar O b t e n e r E d a d () como c o n s t ocasiona que este código se matque como


error. Usted quiso comprobar si suEdad era igual a 100, pero en vez de eso asignó
sin querer 100 a suEdad. Debido a que esta asignación cambia la clase, y usted dijo
que este método no cambiaría la clase, el compilador puede encontrar el error.
Este tipo de error puede ser difícil de encontrar si sólo se examina el código. A
menudo, el ojo sólo ve lo que espera ver. Lo que es más importante, puede parecer
que el programa se ejecuta correctamente, pero ahora se le ha asignado un número
raro a suEdad. Esto ocasionará problemas tarde o temprano.
178 Día 6

P ¿Existe alguna razón para utilizar una estructura en un program a de C++?


R Muchos programadores de C++ reservan la palabra reservada s t r u c t para clases
que no tienen funciones. Esto es un recordatorio de las viejas estructuras de C, jas
cuales no podían tener funciones. Francamente, esto me parece confuso y una mala
práctica de programación. La estructura sin métodos de hoy podría necesitar métodos
en el futuro. Entonces usted estaría obligado a cambiar el tipo a clase, o a quebrantar
su regla y terminar con una estructura que tenga métodos. Por supuesto, C++ debe
tener compatibilidad con el código de C. Por lo general, el uso de s t r u c t se reserva
para programas de C que se compilan y se mantienen en compiladores de C++.

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:

• Qué son los ciclos y cómo se utilizan

• Cómo construir varios ciclos

• Una alternativa para las instrucciones i f /e ls e complejas

Uso de Sos ciclos


Muchos problemas de programación se solucionan al actual en toima lepetida
sobre los mismos datos. Dos maneras de hacer esto son la recursión (que se
explicó en el día 5, “Funciones”) y la iteración. Iteración significa hacer lo
mismo una y otra vez. El principal método de iteración es el ciclo.

Las raíces del uso de ciclos: la instrucción goto


En los días primitivos de la ciencia computacional, los programas eran pésimos,
torpes y cortos. Los ciclos consistían en una etiqueta, algunas instrucciones y
un salto.
182 Día 7

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.

Es una m ala práctica utilizar la instrucción goto. Ésta se in c lu ye a q u í s ó lo p a ra


Precaución cubrir todas las cuestiones relacionadas con los ciclos, y p o r r a z o n e s h istóricas.
¡Los buenos program adores evitan el uso de esta in stru c c ió n !

En t r a d a L is t a d o 7.1 Uso de ciclos con la palabra reservada goto


1: // Listado 7.1
2: // Uso de ciclos con goto
O
o••
4: #include <iostream.h>
c
O••
6: int main()
7: {
8: int contador = 0; // inicializar contador
9: ciclo: contador ++; // principio del ciclo
10: cout « "contador: " « contador « "\n";
11 : if (contador < 5) // evaluar el valor
12: goto ciclo; // saltar al principio del ciclo
13:
14: cout « "Completo. Contador: " « contador « ".\n";
15: return 0;
16: }

contador: 1
Salida contador: 2
contador: 3
contador: 4
contador: 5
Completo. Contador: 5.

En la línea 8 se inicializa contador en 0. En la línea 9, la etiqueta c i c l o marca


A n á l is is
el inicio del ciclo. Se incrementa contador y se imprime su nuevo valor. El
valor de contador se evalúa en la línea 11. Si es menor que 5, la instrucción i f es true
(verdadera) y se ejecuta la instrucción goto. Esto ocasiona que la ejecución del programa
regrese a la línea 9. El programa continuará en el ciclo hasta que contador sea igual a 5,
momento en el que “saldrá” del ciclo y se imprimirá la salida final.
Más flujo de programa 183

Por q u é se e v ita el uso de goto


La instrucción goto lia recibido muchas críticas negativas últimamente, y son bien mere­
cidas. Esta instrucción puede originar un salto hacia cualquier ubicación del código fuente,
hacia atrás o hacia adelante. Su uso indiscriminado produce programas enredados, malos
e imposibles de leer, lo cine se conoce como “código espagueti". Debido a esto, los maes­
tros de la ciencia computacional han pasado los últimos 20 años tratando de meter una
lección en la cabeza de sus estudiantes: “¡Nunca, pero nunca, utilicen goto!"
Uno de los mayores problemas de depuración con goto es que usted no sabe cómo llega
a esa etiqueta. Tiene que buscar instrucciones goto con esa etiqueta en todo el código y
luego tiene que descubrir cuál de ellas lo envió allá. ¡Esto es mucho trabajo!
Para evitar el uso de goto, se han introducido comandos para uso de ciclos más sofistica­
dos y estrechamente controlados: for. while y do. . .while. El uso de estos comandos
hace que los programas sean más fáciles de comprender, y por lo general se evita el uso
de goto, pero alguien podría argumentar que esto es un poco exagerado. Igual que cual­
quier herramienta en las manos apropiadas y utilizada cuidadosamente, goto puede ser
una instrucción útil, y el comité ANSI decidió mantenerla en el lenguaje porque tiene sus
usos legítimos. Pero como dicen: “Niños, no intenten esto en casa”.

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

El uso de goto es casi siempre un signo de un mal diseño. El mejor consejo es


evitar su uso. En nuestros casi 20 años com binados de program ación, la hem os
utilizado sólo unas cuantas veces.

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

Entrada L is t a d o 7 . 2 C iclo s w h ile

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 ;

c o n d ic ió n es cualquier expresión de C++, e in s t r u c c ió n es c u a lq u ie r in stru c c ió n o b lo q u e


d e instrucciones válido de C++. Cuando co n d ic ió n es ve rd ad e ra (1), se ejecuta
Mas flujo de programa 185

in s t ru c c ió n , y lu e g o se vue lve a evaluar condición. Esto con tin ú a hasta q u e c o n d ic ió n


sea falsa (0), m o m e n to en el qu e term ina el ciclo w hile y la ejecución con tin ú a en la si­
guiente línea d esp ués de in s t ru c c ió n .
Ejemplo
// co n ta r h a sta 10
in t x = 0;
w h ile (x < 1 0 )
cout << "X: " << x++;

In stru c cio n e s w h ile m á s com plicadas


La condición evaluada por un ciclo while puede ser tan compleja como cualquier expre­
sión válida de C++. listo incluye a las expresiones producidas mediante el uso de los
operadores lógicos cV.v (AND). | | (OR) y ! (NOT). Ll listado 7.3 muestra una instrucción
while un poco más complicada.

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

Escriba un número chico: 2


S a l id a Escriba un número grande: 100000
chico: 2 .......
Chico: 33335 Grande: 33334
Este programa es un juego. Escriba dos números, uno chico y uno grande. El
A n á l is is
número más chico contará en incrementos de uno, y el número más grande con­
tará en decrementos de dos. El objetivo del juego es adivinar cuándo se encontrarán los
dos números.
En las líneas 12 a 15 se escriben los números. En la línea 20 empieza un ciclo w h ile,
que continuará sólo mientras se cumplan tres condiciones:
1. ch ic o no debe ser mayor que grande.
2. grande no debe ser negativo ni cero.
3. ch ic o no debe sobrepasar el valor de un entero chico (MAXCHICO).
En la línea 22 se calcula el valor de chico en 5,000 por medio del operador de residuo.
Esto no cambia el valor de chico; sin embargo, sólo regresa el valor 0 cuando ch ico sea
un múltiplo exacto de 5,000. Cada vez que sea así, se imprime un punto (.) en pantalla
para mostrar el progreso, chico se incrementa en la línea 25, y grande se decrementa en
2 en la línea 27.
Cuando falle cualquiera de las tres condiciones del ciclo w hile, éste terminará, y la ejecu­
ción del programa continuará después de la llave de cierre del ciclo w h ile en la línea 29.

El o p e rad o r de residuo (% ) y las co n d icio n e s c o m p u e s ta s se d e s c rib e n e n el


día 4.

Uso de continué y break en ciclos


Algunas veces necesita regresar al principio de un ciclo w h ile antes de que se ejecute
todo el conjunto de instrucciones que está dentro del ciclo w h ile. La instrucción
continué salta hacia el principio del ciclo.
Otras veces, tal vez necesite salir del ciclo antes de que se cumplan las condiciones de
salida. La instrucción break sale inmediatamente del ciclo w h ile, y la ejecución del pro­
grama continúa después de la llave de cierre.
Más flujo de programa 187

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

L i s t a d o 7 .4 cont inu aci ón

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 .

Hn la lín ea 3 2 se realiza la operación de m ódulo del valor de c h ic o con el valor de


s a l t o . Si c h i c o es m ú ltip lo de s a l t o , se llega a la instrucción c o n tin u é y la ejecución
d el p rogram a salta hacia el principio del c ic lo en la línea 2 6 . Esto efectivamente elimina
la e v a lu a c ió n para el d e stin o y el decrem ento de g r a n d e .

En la lín ea 3 8 se com para d e s t i n o con el valor de g r a n d e . Si son iguales, el usuario ha


g a n a d o . E n to n c e s se im prim e un m ensaje y se llega a la instrucción b r e a k . E s t o ocasiona
una sa lid a in m ed ia ta del c ic lo w h i l e . y la ejecu ció n del programa continúa en la línea46.
Más flujo de program a 189

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

C idos w h ile (tru e)


La condición evaluada en un ciclo w h ile puede ser cualquier ex p resió n v á lid a de C + + .
Mientras que esa condición sea verdadera (tru e), el ciclo w h ile continuará. U ste d p u e d e
crear un ciclo que nunca termine usando el valor tr u e para la c o n d ic ió n q u e se va a
evaluar. El listado 7.5 muestra el conteo hasta 10 usando esta instrucción.

Entrada L is t a d o 7 . 5 Ciclos while

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

O tro nom bre para esto es “W hile eterno” o “c ic lo infinito” .

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 .

C + + le p ro p o rcio n a m u ch as m aneras de lograr la m ism a tarea. El verdadero truco es


e le g ir la herram ienta adecuada para un trabajo e sp ec ífic o .
Mas flu jo de p ro gram a 19 1

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.

Limitaciones del ciclo w h i l e


Es posible que el cuerpo de un ciclo while nunca se ejecute. La in stru cción w h i l e
prueba su condición antes de ejecutar cualquiera de sus instrucciones, y si la c o n d ic ió n
es falsa (false). se salta todo el cuerpo del ciclo while. El listado 7 .6 m uestra e sto .

Entrada Li s t a d o 7 . 6 Cóm o saltar el cuerpo del ciclo while

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

Si se ejecuta el programa por segunda vez:


¿ C u án to s h o l a s ? : 0
Contador v a l e : 0
192 Dia 7

En la linca I I se pide al u su ario un \al»>i ile m icio I s ii saloi de micio se guarda


en la variable entera c o n ta d o r . Id \a lo i de c o n t a d o r se e \a lu a en la línea 13 y
se deercm enta en el cuerpo del cielo w hi le . I.a primera \ e / que la ejecución del progra­
ma pasó por el cuerpo del ciclo, c o n ta d o r lem a el \a lo r > por consecuencia el cuerpo
del c ic lo w h ile se ejecuto dos \e e e s . Sin em barco, la secunda \ c / el usuario escribió
un 0. Id valor de co n ta d o r se probo en la línea I 3 > la condición fue lalsa: contador no
era m ayor que 0. Todo el cuerpo del c iclo whi lo se salto \ nunca se imprimió Hola.

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

L is t a d o 7 . 7 M u e s t r a del ciclo do. . . w h i 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

En la linca l() se pide al usuario un valor de inicio, el cual se guarda en la v aria­


A nálisis ble de tipo en tero c o n ta d o r. En el cielo do. . .w h ile se entra al cuerpo del ciclo
antes de ev alu a r la condición y. por lo tanto, se garantiza que el cuerpo del ciclo se eje­
cute por lo m enos una v e /. En la línea 14 se imprime el m ensaje, en la línea 15 se decre-
m enta el contador, y en la línea 16 se prueba la condición. Si la condición es verdadera
( tr u e ) . la ejecu ció n salta hasta el principio del ciclo en la línea 14: de no ser así. se va
hasta la línea I7.

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

Prim ero se ejecuta i n s t r u c c i ó n , y luego se evalúa condición. Si co ndició n es verdadera,


el ciclo se repite; de n o ser así, el ciclo termina. En to d o lo demás, las instrucciones y c o n d i­
ciones so n idénticas a las del ciclo while.
Ejem plo 1
// c o n t a r h a s t a 10
i n t x = 0;
do
cout « "X: " « x++;
w h i le (x < 1 0 )

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 .

Entrada L is t a d o 7.9 M u e s t r a d el ciclo f o 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 instrucción f o r de la línea 10 combina en una sola línea la inicialización de


A ná lisis
c o n ta d o r, la prueba para ver si es menor que 5, y su incremento. El cuerpo de la
instrucción f o r se encuentra en la línea 11. Claro que aquí tam bién se podría utilizar un
bloque.

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 ;

La instrucción i n i c i a l i z a c i ó n se utiliza para inicializar el estado de un contador, o para


prepararse para el ciclo, co n d ició n es cualquier expresión válida de C + + y se evalúa cada
1

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

Ciclos for avanzados


Las instrucciones f o r son poderosas y flexibles. Las tres in stru c cio n es independientes
( i n i c i a l i z a c i ó n , c o n d ició n y acció n ) se prestan a un n ú m e ro in c re íb le d e variaciones.
U n ciclo f o r funciona en la siguiente secuencia:

1. Realiza las operaciones de la inicialización


2 . Evalúa la condición
3. Si la condición es verdadera, ejecuta el cuerpo del ciclo f o r ; d e no ser así, el ciclo
term ina
4. Ejecuta la instrucción de acción

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 .

Inicialización e incrementos múltiples


Es com ún inicializar más de una variable para pro b ar u n a e x p resió n ló g ica com puesta y
para ejecutar más de una instrucción. La inicialización y la acció n se p u ed en reem plazar
po r varias instrucciones de C++, cada una separada p o r u n a co m a. El listad o 7 .1 0 mues­
tra la inicialización y el incremento de dos variables.

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

ción f or y se imprimen los valores. Finalmente, se ejecuta la tercera cláusula de la


instrucción f o r y se incrementan i y j .
Después de que termina la línea 10, se evalúa otra vez la condición, y si aún es verdadera,
se repiten las acciones (se incrementan de nuevo i y j) y se ejecuta otra vez el cuerpo
del ciclo. Esto continúa hasta que falle la prueba, en cuyo caso no se ejecuta la instruc­
ción acción (el cuerpo del ciclo, en este caso cout), y el control sale del ciclo.

instrucciones nulas en ciclos for


Cualquiera o todas las instrucciones del encabezado de un ciclo fo r pueden ser nulas.
Para lograr esto, utilice el punto y coma (;) para indicar el lugar en el que debería estar la
instrucción. Para crear un ciclo fo r que actúe exactamente como un ciclo w hile. omita
la primera y tercera instrucciones. El listado 7.11 ilustra esta idea.

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

13: cout « °!Haciendo un ciclo!


14: >
15: cout « °\nContador: 0 « contador « ”.\nH;
16: return 0;
17:

¡Haciendo un ciclo! ¡Haciendo un ciclo! ¡Haciendo un ciclo!


S a l id a ¡Haciendo un ciclo! ¡Haciendo un ciclo!
Contador: 5.
Observe que este código es idéntico al ciclo w h ile q u e se m u e stra en el listado
A nálisis
7.8. En la línea 8 se inicializa la variable c o n ta d o r. L a in stru c ció n f o r de la
línea 10 no inicializa ningún valor, pero incluye la condición c o n ta d o r < 5. N o hay
instrucción de incremento, por lo que este ciclo se co m p o rta ex a c ta m e n te com o si se
hubiera escrito de la siguiente manera:
while (contador < 5)

U na vez más, C++ le proporciona varias m aneras de lo g rar lo m ism o . N in g ú n progra­


m ador de C++ experimentado utilizaría un ciclo f o r de esta m a n era, p ero esto muestra
la flexibilidad de la instrucción f o r. De hecho, es posible, m ed ian te el uso de b reak y
c o n tin u é , crear un ciclo f o r que no tenga ninguna de las tres in stru ccio n es. El listado
7.12 m uestra cómo.

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

1: //Listado 7.12 muestra de una


2: //instrucción de ciclo for con encabezado vacio
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int contador=0; // inicialización
9: int max;
10
11 cout « "¿Cuántos holas?:
12 cin » max;
13 for (;;) //un ciclo for que no termina
14 {
15 if (contador < max) // prueba
16 {
17 cout « "¡Hola!\n";
18 contador++; // incremento
19 >
20 else
21 break;
Más f lu j o de p ro g ra m a 199

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 !

Ahora, el ciclo f o r ha sido llevado a su límite absoluto, i n i c i a l i z a c i ó n ,


A n á l is is
c o n d i c i ó n y a c c i ó n se han omitido en la instrucción f o r. La inicialización
se lleva a cabo en la línea 8. antes de que inicie el ciclo f o r. En la línea 15 se realiza la
prueba en una instrucción i f separada, y si la prueba tiene éxito, la acción, un incre­
mento a c o n t a d o r, se realiza en la línea 18. Si la prueba falla, la salida del ciclo ocurre
en la línea 2 1.
Aunque este programa en especial es un poco absurdo, algunas veces un ciclo f o r ( ; ; )
o un ciclo w h i l e ( tr u e ) es justo lo que queremos. Un ejemplo de un uso más razonable
de tales ciclos se presentará más adelante en este día, cuando hablemos sobre las instruc­
ciones s w it c h .

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.

En trada L ist a d o 7 . 1 3 M u e s t r a d e la in stru cció n n u la e n u n ciclo f o r

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

El ciclo f o r de la línea 8 incluye tres instrucciones: la in stru c ció n i n i c i a l i z a -


A nálisis
c ió n establece el contador i y lo inicializa en 0 . La in stru cció n c o n d ic ió n prueba
si i< 5, y la instrucción a cc ió n im prime el valor de i y lo in crem en ta.

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.

Entrada L is t a d o 7 .1 4 Muestra de ciclos for anidados


1: //Listado 7.14
2: //Muestra de ciclos for anidados
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int filas, columnas;
9: char elCaracter;
10:
11: cout « "¿Cuántas filas?
12: cin » filas;
13: cout « "¿Cuántas columnas? ";
14: cin » columnas;
15: cout « "¿Cuál carácter? ";
16: cin » elCaracter;
17: for (int i = 0; i<filas; i++)
18: {
19: for (int j = 0; j<columnas; j++)
20: cout « elCaracter;
21: cout « "\n";
22: >
23: return 0;
24: >
Más f lu j o de p r o g r a m a 201

¿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

Se pide al usuario el número de filas y columnas, así como un carácter a impri­


mir. El primer ciclo f o r , que se encuentra en la línea 17. inicializa un contador
(i) en 0, se comprueba la condición y luego se ejecuta el cuerpo del ciclo f o r externo.
En la línea 19 se establece la primera línea del cuerpo del ciclo fo r externo. Se inicializa
también un segundo contador (j ) en 0. se comprueba la condición, y se ejecuta el cuerpo
del ciclo fo r interno. En la línea 20 se imprime el carácter elegido, y el control regresa
al encabezado del ciclo fo r interno. Observe que el ciclo fo r interno consta de una sola
instrucción (la impresión del carácter). La variable j se incrementa y se prueba la condi­
ción (j < columnas), si es verdadera (true), se imprime el siguiente carácter. Esto con­
tinúa hasta que j sea igual al número de columnas.
Al fallar la prueba del ciclo fo r interno, en este caso después de imprimir 12 letras X. la
ejecución pasa a la línea 21 y se imprime una nueva línea. Ahora el ciclo fo r externo
regresa a su encabezado, en donde i se incrementa y se prueba su condición ( i < f il a s ) .
Si es verdadera, se ejecuta el cuerpo del ciclo.
En la segunda iteración del ciclo fo r externo, el ciclo fo r interno empieza de nuevo.
Por lo tanto, j se vuelve a inicializar en 0 , se prueba la condición y se ejecuta otra vez el
ciclo interno completo.
Lo importante aquí es que al utilizar un ciclo anidado, el ciclo interno se ejecuta por com­
pleto en cada iteración del ciclo externo. Por lo tanto, el carácter se imprime un número
de veces igual al valor de columnas para cada fila.

M u c h o s program adores de C++ utilizan las letras i y j com o contadores. Esta


tradición tiene sus raíces en la notación matemática para establecer índices.
Los científicos llevaron esa notación al lenguaje FORTRAN, en el qu e las le­
tras i, j, k, 1 , m y n eran automáticamente enteros (incluso cualquier variable
qu e em pezara con las letras i hasta la n) y los enteros eran los únicos con ta­
dores válidos.
O tros program ad ore s prefieren utilizar nom bres de contadores m ás descrip­
tivos, com o C o n tl y Cont2. Sin embargo, el uso de i y j en e ncabezados de
ciclos f o r no debe ocasionar mucha confusión.
202 Día 7

Los ciclos f or y su alcance


A nteriorm ente, las variables declaradas en el ciclo f o r tenían alcan ce en el bloque exter­
no. El nuevo estándar ANSI cambia el alcance de estas variables a sólo el bloque del
m ism o ciclo f o r ; sin embargo, no todos los com piladores so portan esle cam b io . Si usted
utiliza un com pilador que no sea de GNU, puede probarlo con el sig u ie n te código:
#include <iostream.h>
int main()
{
I I ¿i tiene alcance sólo en el ciclo for?
for (int i = 0; i<5; i++)
{
cout « "i: " « i « endl;
}
i = 7; // ino deberia estar dentro de su alcance 1
return 0;
>

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

for (i = 0; i<5; i++)


{
cout « “i: " « i « endl;
}

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

Resumen de los ciclos


En el día 5 aprendió cómo resolver el problema de la serie de Fibonacci por medio de la
recursión. Para repasar esto brevemente, la serie de Fibonacci empieza con 1. 1, 2, 3, y
todos los números subsecuentes son la suma de los dos anteriores:
1,1,2,3,5,8,13,21,34...
El enésimo número de Fibonacci es la suma de los números n-1 y n-2. El problema
resuelto en el día 5 fue encontrar el valor del enésimo número de Fibonacci. Esto se hizo
mediante la recursión. El listado 7.15 ofrece una solución por medio de la iteración.

L is t a d o 7 . 1 5 Solución del enésimo número de Fibonacci por medio


de la iteración

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

31: menosUno = resp u esta;


32: resp u esta = menosUno + menosDos;
33: }
34: re tu rn resp u esta;
35: }

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

Si se ejecuta el program a por segunda vez:


¿Cuál posición? 5
5 es e l número 5 de la s e rie de F ibonacci.

Si se ejecuta el program a otra vez:


¿Cuál p o sició n ? 20
6765 es e l número 20 de la s e r ie de F ibonacci.

Si se vuelve a ejecutar el programa:


¿Cuál p o sició n ? 70
885444751 es e l número 70 de la s e r ie de F ib o n acci.

El listado 7.15 soluciona la serie de Fibonacci po r m ed io de la iteración en lugar


A nálisis
de la recursión. Este método es más rápido y utiliza m enos m em o ria que la solu­
ción po r m edio de la recursión.

E n la línea 13 se pide al usuario la posición a verificar. Se hace u n a llam ad a a la función


f i b ( ), la cual evalúa la posición. Si la posición es m enor que 3, la fu n ció n regresa el
valo r i. Em pezando en la posición 3, la función itera usando el sig u ien te algoritm o:
1. E stablecer la posición inicial: llenar variable r e s p u e s ta con 2, menosDos con 1
y menosUno con 1. Decrem entar la posición en 3, ya que los d os prim eros números
se m anejan por la posición de inicio.
2. P ara cada número, contar en orden ascendente en la serie d e F ib o n acci. E sto se
hace así:
a. C olocar en menosDos el valor actual de menosUno.
b. C olocar en menosUno el valor actual de r e s p u e s ta .
c. Sum ar menosUno y menosDos y colocar la sum a en r e s p u e s ta .
d. D ecrem entar n.
3. C uando n llegue a 0, salir del ciclo y regresar el valor de respuesta.
É sta es exactam ente la m anera en que usted resolvería el p ro b lem a con lápiz y papel. Si
se le pidiera el quinto número de Fibonacci, tendría que escribir lo siguiente:

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;

case v a l o r N : in stru cció n ;


break;
d e f a u lt : in stru cció n ;
}
expresión es cualquier expresión válida de C++, y las instrucciones son cualquier
instrucción o bloque de instrucciones válidas de C++ que se evalúe como (o que se
206 Día 7

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.

Casi siem pre es una bu e n a idea te n e r u n ca so p r e d e t e r m in a d o e n las instruc­


ciones sw itch. Si no tiene necesidad d e u tiliz a r el c a so p r e d e te r m in a d o ,
úselo para p robar el caso su p u e sta m e n te im p o sib le , e im p r im a u n m e n saje
de error; esto pue d e ser de g ra n a y u d a en la d e p u ra c ió n .

Es im portante observar que si no hay una instrucción b re a k al final de una cláusula


c a s e , la ejecución pasará a la siguiente cláusula c a se . Esto es n ecesario algunas veces,
pero por lo general es un error. Si decide dejar que la ejecución p ase p o r la siguiente
cláusula case, asegúrese de poner un com entario que indique que no olv id ó colocar la
instrucción break.
El listado 7.16 muestra el uso de la instrucción sw itch .

Entrada L is t a d o 7 .1 6 Muestra de la instrucción switch


1: //Listado 7.16
2: // Muestra de la instrucción switch
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: unsigned short int numero;
9:
10: cout « "Escriba un número entre 1 y 5: ";
11: cin » numero;
12: switch (numero)
13: {
14: case 0: cout « "Demasiado pequeño, ilo siento!";
15: break;
16: case 5: cout « "iBuen trabajol\n"; // la ejecución pasa a la
^»siguiente cláusula case
17: case 4: cout « "iBuena elección 1\n"; // la ejecución pasa a la
^»siguiente cláusula case
18: case 3: cout « "iExcelente I\n"; // la ejecución pasa a la
^►siguiente cláusula case
19: case 2: cout « "¡Magistral!\n"; // la ejecución pasa a la
^►siguiente cláusula case
Más f lu j o de p ro g ra m a 207

20 case 1: cout << " ¡ I n c r e í b l e ! \ n ° ;


21 break;
22 d e f a u lt : cout << "¡Demasiado grand e!\n";
23 break;
24 }
25 cout « " \ n \ n " ;
26 re t u rn 0;
27 }

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 !

Si se ejecuta el programa por segunda vez:


E s c r i b a un número entre 1 y 5: 8
¡Demasiado grande!

Se pide un número al usuario. Ese número se le proporciona a la instrucción


A n á l is is
s w it c h . Si el número es 0, la cláusula case de la línea 14 concuerda y se
imprime el mensaje Dem asiado pequeño, ¡ l o sie n to !, y la instrucción break hace
que termine la instrucción s w it c h . Si el valor es 5, la ejecución pasa a la línea 16, en
la que se imprime un mensaje, y luego pasa a la línea 17, en la que se imprime otro
mensaje, y así sucesivamente, hasta llegar a la instrucción break de la línea 21.
El efecto neto de estas instrucciones es que para un número entre 1 y 5, se imprime esa
cantidad de mensajes. Si el valor del número no está entre 0 y 5, se da por hecho que es
demasiado grande, y se invoca a la cláusula predeterminada en la línea 22.

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

Uso de una instrucción switch con un menú


El listado 7.17 regresa al ciclo for(; ; ) que se describió anteriorm ente. E stos ciclos
tam bién se llam an ciclos eternos (forever), ya que se ejecutarán etern am en te si no se
encuentra una instrucción break. El ciclo eterno se utiliza p ara d esp leg a r un m enú,
solicitar una opción del usuario, actuar sobre esa opción y luego reg resar al m enú. Esto
continúa hasta que el usuario elige salir.

A algunos programadores les gusta escribir


#define SIEMPRE ;;
for (SIEMPRE)
{
// instrucciones...
}
Más f lu j o de p ro g ra m a 209

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.

Entrada L is t a d o 7 . 1 7 M u e s t r a de un ciclo eterno

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

D E B E utilizar instrucciones sw itch para NO D E B E olvidar poner una instrucción


evitar las instrucciones i f complejas. break al final de cada instrucción case, a
D E B E d o cu m e n tar cuidadosam ente todos menos que quiera que la ejecución pase
los casos intencionales en los que se pase a la siguiente instrucción case.
de una cláusula case a otra hasta llegar al
fin de la instrucción sw itch o encontrar
una instrucción break.
D E B E colocar un a cláusula case predeter­
m inada en las instrucciones sw itch, a u n ­
que sea sólo para detectar situaciones que
parezcan im posibles.
212 Día 7

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

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 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 « " ";

8. C A ZA ERRO RES: ¿Qué está mal en el siguiente código?


i n t contador = 100 ;
w h ile (contador < 10 )
{
cout << "contador ahora: " << contador;
contador - -;
}
9. C A ZA ERRO RES: ¿Qué está mal en el siguiente código?
cout << " E s c r i b a un número entre 0 y 5: ";
c in >> elNumero;

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.

Entrada L i s t a d o R1.1 Listado de repaso de la semana 1


1: tfinclude <iostream .h>
2: // A r c h iv o del l i s t a d o ls t r 1 - 0 1 . c x x
3: enum OPCION { DibujaRect = 1, ObtenArea,
4: ObtenPerim, CambiaDimensiones, S a l i r } ;
5: // D e c la ra c ió n de c la se Rectángulo
6: c l a s s Rectángulo
7: {
8: p u b lic :
9: // c o n s tru c to re s
10: R e c t a n g u lo (in t anchura, in t a lt u r a );
11: - R e c t á n g u lo ! );
12:
13: // fu n c io n e s de acceso
14: i n t O b te n A ltu ra () const { return s u A ltu ra ; }
15: i n t ObtenAnchura() const { return suAnchura; }
16: i n t ObtenArea() const { return su A lt u ra *
»♦ suAnchura; }
17: i n t ObtenPerim!) const { return 2 *s u A ltu ra +
»*2*suAnchura; }
18: vo id AsignaTam anio(int nuevaAnchura, in t
» ♦ n u e va A ltu ra );
19:
20: // Métodos misceláneos
21 :
22:
23: p r iv a t e :
24: i n t suAnchura;
25: i n t s u A ltu ra ;
26: };
continúa
|216 Sem ana 1

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

L istado R 1.1 continuación

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

*** Menú ***


Salida (1) Dibujar rectángulo
(2) Área
(3) Perímetro
(4) Cambiar tamaño
(5) Salir
1
******************************
******************************
******************************
******************************
******************************

*** Menú ***


(1) Dibujar rectángulo
(2) Área
(3) Perímetro
(4) Cambiar tamaño
(5) Salir
2
Área: 150

*** Menú ***


(1) Dibujar rectángulo
(2) Área
(3) Perímetro
(4) Cambiar tamaño
(5) Salir
3
Perímetro: 70

*** Menú ***


(1) Dibujar rectángulo
(2) Área
(3) Perímetro
(4) Cambiar tamaño
(5) Salir
Repaso 219

Nueva anchura: 10
Nueva altura: 8
**********
**********
**********
**********
**********
**********
**********

*** Menú ***


(1) Dibujar rectángulo
(2) Área
(3) Perímetro
(4) Cambiar tamaño
(5) Salir
2
Área: 80

*** Menú ***


(1) Dibujar rectángulo
(2) Área
(3) Perímetro
(4) Cambiar tamaño
(5) Salir
3
Perímetro: 36

*** Menú ***


(1) Dibujar rectángulo
(2) Área
(3) Perímetro
(4) Cambiar tamaño
(5) Salir
5

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

Tome un descanso, disfrute la gloria de su logro, y luego dé vuelta a la página para


em pezar con la sem ana 2 .
m
¡J]
fj
¡i

í¡

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

cada ubicación = 1 byte


u n s ig n e d lo n g i n t la E d a d = 4 b y t e s = 3 2 b i t s
el nombre de la variable laEdad apunta al 1er byte
la dirección de laEdad es 102

La numeración de la memoria varía entre computadoras, usando distintos esquemas


complejos. Por lo general, los programadores no necesitan conocer la dirección especí­
fica de alguna variable dada, ya que el compilador se encarga de los detalles. No obs­
tante, si usted quiere esta información, puede utilizar el operador de dirección (&), el
cual se muestra en el listado 8.1.

Entrada L is t a d o 8.1 Muestra del operador de dirección


1: // Listado 8.1 Muestra del operador de dirección,
2: // y de direcciones de variables locales
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: unsigned short shortVar = 5;
9: unsigned long longVar = 65535;
10 long sVar = -65535;
11
12 cout « "Variable de tipo short sin signo:\t";
13: cout « shortVar « "\n";
14: cout « "Dirección de variable de tipo short:\t";
15: cout « SshortVar « "\n";
16:
17: cout « "Variable de tipo long sin signo:\t";
18: cout « longVar « "\n";
19: cout « "Dirección de variable de tipo long:\t" ;
20: cout « &longVar « "\n";
21 :
22: cout « "Variable de tipo long con signo:\t";
23: cout « sVar « "\n";
A p u n ta d o re s 227

24: cout << " D i r e c c i ó n de v a r i a bl e de ti po long con s i g n o : \ t D ;


25: cout << &sVar << “\ n" ;
26:
27: r e t u r n 0;
28: }

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

0000 0000 65 535 •65 535


0000 0101 A
r >
T 0000 0000 1111 1111 1000 0000 1111 1111
5 0000 0000 1111 1111 0000 0000 1111 1111
1 I I I I I I I I I I 1 1 1 1 1 1 1
1
1113 mi ífol lloa ffeb Ho9 ff07 HoS Iíe3

1(1.1 (112 (ira Ileo IIoc llea Ile8 1(06 He-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).

Cómo guardar la dirección en un apuntador


Cada variable tiene una dirección. Aunque no conozca la dirección específica de una
variable dada, puede guardar esa dirección en un apuntador.
Por ejemplo, suponga que queTanViejo es una variable de tipo entero. Para declarar un
apuntador llamado apEdad para que guarde la dirección de esta variable, tendría que
escribir lo siguiente:
int * apEdad = NULL;
Esto declara apEdad como un apuntador a int. Es decir, apEdad se declara para guardar
la dirección de un int.
Observe que apEdad es una variable como cualquier otra. C uando se declara una variable
de tipo entero, se prepara para que guarde un entero. Cuando se declara una variable de
apuntador como apEdad, está preparada para guardar una dirección. apEdad es sólo un
tipo distinto de variable.
En este ejemplo, apEdad se inicializa con la dirección constante NULL. Un apuntador cuyo
valor sea NULL se conoce como a p u n t a d o r n u lo . Todos los apuntadores, al ser creados, de­
ben ser inicializados con algo. Si usted no sabe qué quiere asignar al apuntador, asígnele
NULL. Un apuntador que no está inicializado se conoce como apuntador perdido. Este
tipo de apuntadores es muy peligroso.

Practique la programación segura: ¡Inicialice sus apu n tad o res! A algunos


Nota programadores les gusta utilizar el valor 0 (cero) para in icializar sus apu n ta­
dores. Lo mejor es utilizar la constante NULL.

Si inicializa el apuntador con NULL, debe asignar específicam ente la d irección de


queTanV iejo a apEdad. El siguiente ejemplo m uestra cóm o h acer esto:
unsigned short int queTanViejo = 50; // crear una variable
unsigned short int * apEdad = NULL; // crear un apuntador
apEdad = &queTanViejo; // colocar la dirección de queTanViejo
en apEdad
La prim era línea crea una variable (queTanViejo, del tipo unsigned short int) y la
inicializa con el valor 50. La segunda línea declara apEdad com o un ap u n tador al tipo
unsigned short int y lo inicializa con NULL. Usted sabe que apEdad es un apuntador
por el asterisco (*) que va después del tipo de variable y antes del nom bre de la variable.
A p untadores 229

La tercera y última línea asigna la dirección de queTanViejo al apuntador apEdad. Puede


ver que se está asignando la dirección de queTanViej o gracias al operador de dirección
(&). Si este operador no se hubiera utilizado, se habría asignado el valor de queTanViejo
en lugar de su dirección. Ésta podría ser o no una dirección válida.
En este punto. apEdad tiene como valor la dirección de queTanViejo. queTanViejo. a su
vez, tiene el valor 50. Usted hubiera podido lograr esto con un solo paso, como se mues­
tra a continuación:
unsigned short int queTanViejo = 50; I I crear una variable
unsigned short int * apEdad = SqueTanViejo; I I crear un apuntador a
queTanViejo

apEdad es un apuntador que ahora contiene la dirección de la variable queTanViejo. Por


medio de apEdad puede determinar el valor de queTanViejo. que en este caso es 50. Acce­
der a queTanViejo mediante el uso del apuntador apEdad se conoce como indirección, ya
que usted está accediendo indirectamente a queTanViejo por medio de apEdad. Más adelan­
te en este día verá cómo utilizar la indirección para tener acceso al valor de una variable.
significa acceder ai valor de una variable cuya dirección está guardada en un
I n d ir e c c ió n
apuntador. El apuntador proporciona una manera indirecta de obtener el valor que se
guarda en esa dirección.

Elección de nombres de apuntadores


Los apuntadores pueden tener cualquier nombre que sea válido para otras variables. Muchos
programadores siguen la convención de nombrar a todos los apuntadores con una p o con
ap al principio (ap es una contracción de “apuntador”, mientras que p proviene de "pointer .
que en inglés significa apuntador), como en pEdad, pNumero. apEdad o apNumero. En este
libro utilizaremos la forma apNombreVariable para designar a los apuntadores.

Uso del operador de indirección


El operador de indirección (*) también se conoce como o p era d o r de d esreferen cia . Cuando
un apuntador es desreferenciado, se recupera el valor que se encuentra en la dirección
guardada por el apuntador.
Las variables normales proporcionan un acceso directo a sus propios valores. Si usted
crea una nueva variable de tipo unsigned short in t llamada suEdad y quiere asignarle
el valor de queTanViejo, debe escribir lo siguiente:
unsigned short int suEdad;
suEdad = queTanViejo;

Un apuntador proporciona un acceso indirecto al valor de la variable cuya dirección se


encuentra almacenada en otra variable. Para asignar el valor contenido en queTanViejo a
la nueva variable suEdad por medio del apuntador apEdad. debe escribir lo siguiente:
unsigned short int suEdad;
suEdad = *apEdad;
El operador de indirección (*) que está antes de la variable apEdad significa “el valor
guardado en”. Esta asignación dice: ‘‘tomar el valor guardado en la dirección de apEdad
y asignarlo a suEdad”.

El ope rad or de indirección (*) se utiliza d e d o s m a n e ra s d istin ta s con los ap u n ­


tadores: declaración y d esre fere ncia. C u a n d o se d e c la r a u n a p u n ta d o r, el
asterisco indica que es un apuntador, n o u n a v a ria b le n o rm a l. P or ejemplo,
unsigned short * apEdad = NULL; // crear un apuntador a un entero
corto sin signo
C u a n d o el a p u n ta d o r es d ere fere n ciad o, el o p e r a d o r d e in d ire c c ió n indica
qu e se debe acceder al valo r q u e se e n c u e n tra en la d ire c c ió n d e m e m o ria
g u a rd a d a en el apuntador, y n o a la d ire c c ió n en sí.
*apEdad = 5; // asignar 5 al valor que se encuentra en la dirección
a la que apunta apEdad
Observe tam bién que este m ism o carácter (*) se u tiliza c o m o o p e r a d o r de m ul­
tiplicación. El com pilador sabe a cuál o p e r a d o r llam ar, b a s á n d o s e en el contex­
to (se dice que estos lenguajes tie ne n u n a g r a m á t ic a se n sib le al contexto).

Apuntadores, direcciones y variables


Es importante distinguir entre un apuntador, la dirección que guarda el apuntador y el
valor que se encuentra en la dirección guardada por el apuntador. Esto es lo que provoca
la mayor parte de la confusión acerca de los apuntadores.
Considere el siguiente fragmento de código:
int laVariable = 5;
int * apApuntador = &laVariable ;
laVariable se declara como variable de tipo entero inicializada con el valor 5. apApuntador
se declara como apuntador a un entero, y se inicializa con la dirección de laVariable.
apApuntador es el apuntador. La dirección que guarda apApuntador es la dirección de
laVariable. El valor que se encuentra en la dirección guardada por apApuntador es 5. La
figura 8.3 muestra una representación esquemática de laVariable y apApuntador.

Fig ura 8.3 laVariable apApuntador


Una representación Nombre
esquemática de la de variable ,. ,,
memoria. I I I I I I I I
0000 0000 0000 0000 0000 0110
0000 0101 0000 0000 0000 0101
V.----
____ Y J
V -- J
5 101
______ I______ I______ I______ I___ ___ I______ I______ I______ I______ I______
100 101 102 103 104 105 106 107 108 109
Ubicación en memoria
Apuntadores 231

Manipulación de datos mediante el uso de apuntadores


Después de asignar la dirección de una variable a un apuntador, puede utilizar ese apun­
tador para tener acceso a los datos guardados en esa variable. El listado 8.2 muestra
cómo se asigna la dirección de una variable local a un apuntador, y cómo manipula el
apuntador los valores que se guardan en esa variable.

Entrada L is t a d o 8.2 M a n ip u la c ió n de datos m ediante el uso de a p u n ta d o re s

1: // Listado 8.2 Uso de apuntadores


2:
3: flinclude <iostream.h>
4:
5: typedef unsigned short int USHORT;
6: int main()
7: {
8: USHORT miEdad; // una variable
9: USHORT * apEdad = NULL; // un apuntador
10:
11 : miEdad = 5;
12: cout « "miEdad: "« miEdad « "\n";
13: // asignar dirección de miEdad a apEdad
14: apEdad = &miEdad;
15: cout « "*apEdad: " « *apEdad « 11\n\n" ;
16: cout « "*apEdad = 7\n";
17: // asigna el valor 7 a miEdad
18: *apEdad = 7;
19: cout « "*apEdad: " « *apEdad « "\n";
20: cout « "miEdad: " « miEdad « "\n\n";
21: cout « "miEdad = 9\n";
22: miEdad = 9;
23: cout « "miEdad: " « miEdad « "\n" ;
24: cout « "*apEdad: " « *apEdad « "\n";
25:
26: return 0;

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

En la línea 14 se asigna la dirección de miEdad a apEdad. En la linca 15. apEdad es desre-


ferenciado e impreso, lo que muestra que el valor que está en la dirección guardada por
apEdad es el 5 guardado en miEdad. En la línea 18 se asigna el valor 7 a la variable que
se encuentra en la dirección guardada por apEdad. Esto asigna un 7 a miEdad. lo que
queda confirmado por las impresiones de las líneas 19 y 20.
En la línea 22 se asigna el valor 9 a la variable miEdad. Este valor se obtiene directa­
mente en la línea 23 y se obtiene indirectamente (al desreferenciar a apEdad) en la línea 24.

Cómo examinar una dirección


Los apuntadores le permiten manipular direcciones sin que necesite saber su valor real.
Después de hoy, puede confiar ciegamente en que, cuando asigne la dirección de una va­
riable a un apuntador, éste realmente tendrá como valor la dirección de esa variable. Pero
sólo por esta vez, ¿por qué no comprobar para estar seguros? El listado 8.3 muestra esto.

E n t r a d a I L ist a d o 8.3 C ó m o averiguar lo que está guardado en un


1: // Listado 8.3 Qué se guarda en un apuntador.
p•
3: #include <iostream.h>
A•
H •
5: int main()
6: {
7: unsigned short int miEdad = 5, suEdad = 10;
8: unsigned short int * apEdad = &miEdad; // un apuntador
9:
10: coût « "miEdad:\t" « miEdad
11 : « "\tsuEdad:\t" « suEdad « "\n" ;
12: coût « "&miEdad:\t " « &miEdad
13: « "\t&suEdad: \t" « &suEdad «"\n";
14: coût « "apEdad:\t" « apEdad « "\n";
15: coût « "*apEdad:\t" « *apEdad « "\n";
16: // reasignar el apuntador
17: apEdad = &suEdad;
18: coût « "miEdad:\t" « miEdad
19: « "\tsuEdad:\t" « suEdad « "\n";
20: coût « "SmiEdad:\t " « &miEdad
21 : « "\t&suEdad: \t" « &suEdad « " \n";
22: coût « "apEdad:\t" « apEdad « "\n" ;
23: coût « "*apEdad:\t " « *apEdad « " \n" ;
24: coût « "&apEdad:\t " « &apEdad « "\n" ;
25: return 0 1
26: }
A p u n ta d o re s 233

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

(Tal vez la salida que usted obtenga sea distinta.)


En la línea 7, miEdad y suEdad se declaran como variables de tipo entero corto
A n á lisis sin signo ( u n s i g n e d short in t). En la línea 8, apEdad se declara como apunta­
dor a un entero corto sin signo, y se inicializa con la dirección de la variable miEdad.
Las líneas 10 a 13 imprimen los valores y las direcciones de miEdad y suEdad. La línea
14 imprime el contenido de apEdad. que es la dirección de miEdad. La línea 14 imprime
el resultado de dcsrefcrenciar a apEdad, lo que imprime el valor guardado en la dirección
a la que apunta apEdad (el valor guardado en miEdad, es decir. 5).
Ésta es la esencia de los apuntadores. La línea 14 muestra que apEdad guarda la dirección
de miEdad. y la línea 15 muestra cómo obtener el valor guardado en miEdad desreferen-
ciando al apuntador apEdad. Asegúrese de entender esto completamente antes de seguir
adelante. Estudie el código y examine la salida.
En la línea 17, apEdad se reasigna para apuntar a la dirección de suEdad. Los valores
y las direcciones se imprimen de nuevo. La salida muestra que ahora apEdad tiene la
dirección de la variable suEdad y que con esa desreferencia se obtiene el valor guardado
en suEdad.
La línea 2 4 imprime la dirección del mismo apuntador apEdad. Como cualquier otra va­
riable, tiene una dirección, y esa dirección se puede guardar en un apuntador. (En breve
discutiremos la asignación de la dirección de un apuntador a otro apuntador.)

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,

unsigned short int * apApuntador = NULL;


P ara in icializar o a s ig n a r un v a lo r a un a p u n tad o r, c o lo q u e el o p e r a d o r d e d ire c c ió n (&)
a n te s d el n o m b re d e la variab le cuya dirección se va a a sig n a r. P o r e je m p lo ,
unsigned short int laVariable = 5;
unsigned short int * apApuntador = & laVariable;
P a ra d e sre fe re n c ia r u n a p u n ta d o r, c o lo q u e el o p e r a d o r d e in d ir e c c ió n ( * ) a n t e s d e l n o m ­
b re d e l a p u n tad o r. Por ejem plo,

unsigned short int elValor = *apApuntador

¿Por qué utilizar apuntadores?


Hasta ahora ha visto paso a paso los detalles de la asignación de la dirección de una
variable a un apuntador. No obstante, en la práctica nunca haría esto. Después de todo,
¿para qué batallar con un apuntador cuando ya tiene una variable con acceso a ese valor?
La única razón para utilizar este tipo de manipulación por medio de apuntadores de una
variable automática es para mostrar la forma en que trabajan los apuntadores.
Ahora que está familiarizado con la sintaxis de los apuntadores, puede empezar a utili­
zarlos. Por lo general, los apuntadores se utilizan para tres cosas:
• Manejar datos en el heap
• Tener acceso a los datos miembro y a las funciones de las clases
• Pasar variables por referencia a las funciones
El resto de esta lección se enfoca en el manejo de datos en el heap y en el acceso de los
datos miembro y las funciones de las clases. Mañana aprenderá cóm o pasar variables por
referencia.

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

La ventaja de acceder a la memoria de esta manera, en lugar de utili/ar variables globa­


les, es que sólo las funciones que tengan acceso al apuntador tendrán acceso a los datos.
Esto proporciona una interfaz estrechamente controlada para esos datos, y elimina el
problema de que una función cambie esos datos en forma inesperada.
Para que esto funcione, usted debe ser capaz de crear un apuntador hacia un área en el
heap, y debe pasar ese apuntador entre las distintas funciones. Las siguientes secciones
describen cómo hacer esto.

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.

El valor de retomo de new es una dirección de memoria, la cual se debe asignar a un


apuntador. Para crear un tipo unsigned short en el heap, podría escribir lo siguiente:
unsigned short int * apApuntador;
apApuntador = new unsigned short int;
Puede, desde luego, al crear el apuntador, inicializarlo con lo siguiente:
unsigned short int * apApuntador = new unsigned short int;
En cualquier caso, ahora apApuntador apunta a un tipo unsigned short int en el heap.
Puede utilizar este apuntador como cualquier otro que apunte a una variable, y puede
asignar un valor en esa área de memoria si escribe lo siguiente:
*apApuntador = 72;

Esto significa: “Colocar 72 en el valor de apApuntador” o “Asignar el valor 72 al área


del heap a la que apunta apApuntador”.
Si new no puede crear memoria en el heap (la memoria es, después de todo, un recurso
limitado), se producirá una excepción (vea el día 20, “Excepciones y manejo de
errores”).

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

Para regresar la memoria al heap, se utiliza la palabra reservada d e le te . Por ejemplo,


delete apApuntador;

Al eliminar el apuntador, lo que realmente está haciendo es liberar la memoria cuya


dirección está guardada en el apuntador. Está diciendo: “Regresar al heap la memoria a
la que apunta este apuntador**. El apuntador todavía es un apuntador, y se puede asignar
a otra dirección. El listado 8.4 muestra la asignación de una variable al heap, el uso de
esa variable y su eliminación.

Al utilizar d e le te en un apuntador, se libera la memoria a la que éste a pu n ­


Precaución ta. ¡Si vuelve a utilizar d elete en ese apuntador, el programa dejará de fu n ­
cionar! Cuando elimine un apuntador, asígnele el valor NULL. U tilizar d e le te
en un apuntador nulo siempre es seguro. Por ejemplo,
Animal *apPerro = new Animal;
delete apPerro; //libera la memoria
apPerro = NULL; //asigna NULL al apuntador
n ...
delete apPerro; //inofensivo

L ist a d o 8.4 Asignación, uso y eliminación de

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

La línea 7 declara e inicializa una variable local. La linca 8 declara e inicializa


A nálisis
un apuntador con la dirección de la variable local. La línea 9 declara otro apun­
tador pero lo inicializa con el resultado obtenido al solicitar un nuevo in t. Esto asigna
espacio en heap para un in t. La línea 11 verifica que se haya asignado la memoria y que
el apuntador sea válido (no nulo) al utilizarlo. Si no se puede asignar memoria, el apun­
tador es nulo y se imprime un mensaje de error.
Para mantener las cosas simples, la verificación de errores real no se incluye en muchos
de los programas de muestra que vienen en este libro, pero usted debe incluir algún
tipo de verificación de errores en sus propios programas. Una mejor forma de comprobar
errores sería probar si un apuntador es nulo antes de inicializarlo con el valor. Podría
reemplazar la línea 11 del listado 8.4 con lo siguiente:
if (apHeap == NULL)
{
// encargarse aquí del error:
// reportar el problema. En la mayoría de los casos los siguientes pasos
// son realizar la limpieza (cerrar archivos, establecer estados, etc.)
/ / y salir del programa.
}
*apHeap = 7;

La linea 11 asigna el valor 7 a la memoria recién asignada. La línea 12 imprime el valor


de la variable local, y la línea 13 imprime el valor al que apunta apLocal. Como es de
esperarse, son iguales. La línea 14 imprime el valor al que apunta apHeap. Esto muestra
que el valor que se asigna en la línea 11 sí es accesible.
En la línea 15, la memoria que se asigna en la línea 9 se libera mediante una llamada a
delete. Esto libera la memoria y el apuntador queda desasociado de esa memoria. Ahora
apHeap está libre para apuntar a otra dirección de memoria. Se le vuelve a asignar una
dirección en las líneas 16 y 17, y la línea 18 imprime el resultado. La línea 19 libera esa
memoria.

Aunque la línea 19 es redundante (al terminar el programa se habría liberado esa


memoria), es una buena idea liberar esta memoria en forma explícita. Si el programa
cambia o se extiende, haberse hecho cargo de este paso será benéfico.
Apuntadores 239

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;

La línea 1 crea a apApuntador y le asigna la dirección de un área del heap. La línea 2


guarda el valor 72 en esa área de memoria. La línea 3 vuelve a asignar otra área de me­
moria a apApuntador. La línea 4 coloca el valor 84 en esa área. El área original (en la
que se guarda el valor 7 2 ) no está disponible, ya que el apuntador a esa área de memoria
ha sido reasignado. No existe forma de tener acceso a esa área original de memoria, ni
de liberarla antes de que el programa termine.
El código se debió escribir de la siguiente manera:
1: unsigned short int * apApuntador = new unsigned short int;
2: ‘apApuntador = 72;
3: delete apApuntador;
4: apApuntador = new unsigned short int;
5: ‘apApuntador = 84;

Ahora la memoria a la que apuntaba originalmente apApuntador se ha eliminado, es


decir, liberado, en la línea 3.

Por cad a vez q u e utilice new en su program a, debe haber un d e le te corres­


p o n d ie n te . Es im portan te llevar un registro de cuál área de m e m oria perte­
nece a cuál apuntador, y de asegurarse de que esa m em oria se libere al
dejar d e utilizarla.

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

Creación de objetos en el heap


Así como puede crear un apuntador a un entero. también puede crear un apuntador a
cualquier objeto. Si crea un objeto de la clase Gato, puede declarar un apuntador a ese
objeto Gato en el heap, de igual forma que com o crea uno en la pila. La sintaxis es la
misma que la de los enteros:
Gato *apGato = new Gato;

Esto llama al constructor predeterminado (el constructor que no lleva parámetros). El


constructor se llama siempre que se crea un objeto (en la pila o en el heap).

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.

E n tra d a L istado 8 .5 C r e a c ió n y e lim in a c ió n d e o b j e t o s e n e l h e a p

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

28: cout << "GatoSimple Pelusa...\n";


29: GatoSimple Pelusa;
30: cout « "GatoSimple ‘apFelix = new GatoSimple.
31 : GatoSimple * apFelix = new GatoSimple;
32: cout « “delete apFelix...\n";
33: delete apFelix;
34: cout « "saliendo, observe cómo se va Pelusa..
35: return 0;
36: }

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.

Acceso a los datos miembro


Usted tiene acceso a los datos miembro y a las funciones mediante el operador de punto
(.) para los objetos Gato creados en forma local. Para tener acceso al objeto gato en el
heap, debe desreferenciar el apuntador y llamar al operador de punto en el objeto al que
apunta el apuntador. Por lo tanto, para tener acceso a la función miembro ObtenerEdad,
debe escribir lo siguiente:
(‘apFelix).ObtenerEdad();
Los paréntesis se utilizan para asegurar que apFelix sea desreferenciado antes de tener
acceso a ObtenerEdad().
Como esto es un poco extraño, C++ proporciona un operador de método abreviado para
el acceso indirecto: el operador de flecha (->), que se crea escribiendo un guión corto (- )
seguido del signo de mayor que (>). C++ trata esto como un solo símbolo. El listado 8.6
muestra el acceso a las variables y funciones miembro de objetos creados en el heap.
| 242 Did 8

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

1: // Listado 8.6 Acceso a los datos miembro


2: // de objetos que se encuentran en el heap
3:
4: #include<iostream.h>
5:
6: class GatoSimple
7: {
8: public:
9: GatoSimple()
10: { suEdad = 2; }
11: -GatoSimple () {}
12: int ObtenerEdad() const
13: { return suEdad; }
14: void AsignarEdad(int edad)
15: { suEdad = edad; }
16: private:
17: int suEdad;
18: };
19:
20: int main()
21: {
22: GatoSimple * Pelusa = new GatoSimple;
23: cout « "Pelusa tiene " « Pelusa->ObtenerEdad();
24: cout « "años de edad\n";
25: Pelusa->AsignarEdad(5);
26: cout « "Pelusa tiene " « Pelusa->ObtenerEdad();
27: cout « " años de edad\n";
28: delete Pelusa;
29: return 0;
30: >

Pelusa tiene 2 años de edad


S a l id a
Pelusa tiene 5 años de edad
En la línea 22 se crea una instancia de un objeto GatoSimple en el heap. El
constructor predeterminado establece su edad en 2, y en la línea 23 se llama al
método ObtenerEdad(). Como éste es un apuntador, se utiliza el operador de flecha (->)
para tener acceso a los datos miembro y a las funciones. En la línea 25 se hace una lla­
mada al método AsignarEdad(), y se vuelve a acceder a ObtenerEdad() en la línea 26.

Datos miembro en el heap


Uno o más de los datos miembro de una clase pueden ser apuntadores a un objeto que se
encuentre en el heap. La memoria se puede asignar en el constructor de la clase o en uno
de sus métodos, y se puede eliminar en su destructor, como se muestra en el listado 8.7.
A p u n ta d o r e s 243

Entrada L is t a d o 8.7 Apuntadores como datos miembro


1 // L i s t a d o 8. 7 8
2 // Apu nt ado res como da to s miembro
3
4 //include < i o s t re am . h>
5
6 c l a s s GatoSimple
7 {
8 public :
9 GatoSimple();
10: - G a t o S i m p l e () ;
11 : i n t ObtenerEdad() const
12: { r e t u r n *suEdad; }
13: v o i d A s i g n a r E d a d ( i n t edad)
14: { *suEdad = edad; }
15: i n t O b t e n e r P e s o ( ) const
16: { r e t u r n *suPeso; }
17: v o i d A s i g n a r P e s o ( i n t peso)
18: { * s u P es o = peso; }
19: private
20: i n t * suEdad;
21 : i n t * suPeso;
22:
23:
24: G a t o S i m p l e : :Gat o Si mp le ()
25: {
26: suEdad = new i n t (2);
27: suPeso = new i n t (5);
28: }
29:
30: G a t o S i m p l e : : - G a t oS i m pl e ()
31 : {
32: d el e te suEdad;
33: d e l e t e suPeso;
34: }
35:
36: i n t ma i n()
37: {
38: GatoSimple * Pelusa = new
39: cout << " Pe l u s a ti ene " « Pelusa->ObtenerEdad();
40: cout « " años de edad\n";
41 Pelusa->AsignarEdad(5);
42: cout « " Pe l u s a ti ene " « Pelusa->0btenerEdad
43: cout « " años de edad\n";
44: d e l et e Pelusa;
45: r e t u r n 0;
46: }
244 Día 8

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

En las líneas 20 y 21 se declara la clase GatoSimple con dos variables miembro


A n á l is is
(ambas son apuntadores a enteros). El constructor (lincas 24 a 28) inicializa. con
los valores predeterminados, los apuntadores a la m em oria del heap.
El destructor (líneas 30 a 34) libera la memoria asignada. C om o éste es el destructor, no
tiene caso asignarles NULL a estos apuntadores, pues ya no serán accesibles. Este es uno
de los lugares seguros en los que se puede romper la regla que establece que a los apun­
tadores eliminados se les debe asignar NULL. aunque seguir la regla no hace daño.
La función que hace la llamada (en este caso. ma i n( )) no sabe que s u E d a d y s u P e s o son
apuntadores a la memoria del heap. m a i n ( ) continúa llam ando a O b t e n e r E d a d ( ) y a
A s i g n a r E d a d ( ) , y los detalles del manejo de memoria se ocultan en la implementación
de la clase (como debe ser).
Cuando se elimina a P e l u s a en la línea 44, se llama a su destructor. El destructor elimina
cada uno de sus apuntadores miembro. Si éstos, a su vez, apuntan a objetos de otras
clases definidas por el usuario, también se llama a sus destructores.

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


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

Apuntadores especiales y otras cuestiones


Aún quedan muchas cosas por aprender acerca del uso de apuntadores. Las siguientes
secciones tratarán sobre:
• El apuntador th is
• Apuntadores perdidos, descontrolados o ambulantes
• Apuntadores const
246 Día 8

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.

E n t r a d a L istado 8.8 U so del a p u n ta d o r t h i s

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

45: cout << ' elRect tiene “ « el Rect.ObtenerAncho();


46: cout << pies de ancho.\nu;
47: r et ur n 0; 8
48: }

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.

Las funciones de acceso A s i g n a r L o n g i t u d () y O b t en e rL on g it u d () utilizan explí­


A nálisis
citamente el apuntador t h i s para tener acceso a las variables miembro del objeto
R e c t á ng u l o. Las funciones de acceso AsignarAncho y ObtenerAncho no lo hacen. No exis­
te ninguna diferencia en su comportamiento, aunque la sintaxis es más fácil de entender.
Si eso fuera todo lo que hubiera en relación con el apuntador th is . no tendría mucho
caso que usted se tomara la molestia de utilizarlo. Sin embargo, hay que recordar que es
un apuntador; guarda la dirección de memoria de un objeto, y como tal, puede ser una
herramienta poderosa.
En el día 10, “Funciones avanzadas", verá un uso práctico del apuntador t h i s , cuando
hablemos de la sobrecarga de operadores. Por ahora, su meta es conocer este apuntador y
entender lo que es: un apuntador al objeto en sí.
No tiene que preocuparse por crear o eliminar el apuntador th is . El compilador se
encarga de eso.

Apuntadores perdidos, descontrolados o ambulantes


Los apuntadores perdidos producen errores desagradables y difíciles de encontrar. Un
a p u n ta d o r p e r d id o (también conocido como apuntador descontrolado o ambulante) se
crea si usted llama a d e l e t e para que actúe sobre un apuntador (lo que en consecuencia
libera la memoria a la que éste apunta), y no le asigna NULL a ese apuntador. Si trata
entonces de utilizar ese apuntador sin reasignarlo, el resultado será impredecible y con
suerte el programa sólo dejará de funcionar.
Es como si la compañía Acmé de pedidos por correspondencia se mudara, y usted aún opri­
miera el botón programado en su teléfono. Es posible que no ocurra nada terrible (es un
teléfono que suena en un almacén desierto). Tal vez el número haya sido reasignado a una
fábrica de municiones, su llamada detone un explosivo ¡y toda la ciudad vuele en pedazos!
Para resumir, tenga cuidado de no utilizar un apuntador después de haber llamado a d e l e t e
para que actúe sobre él. El apuntador aún apunta al área de memoria, pero el compilador
tiene la libertad de colocar otros datos ahí; si usa el apuntador, su programa puede dejar
de funcionar. O peor aún, su programa podría proceder como si nada y luego dejar de
funcionar varios minutos después. Esto se conoce como bomba de tiempo, y no es diver­
tido. Para estar seguro, después de usar d e l et e sobre un apuntador, asígnelo a NULL. Con
esto, el apuntador queda desarmado.
248 Día 8

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 .

El listado 8.9 muestra la creación de un apuntador perdido.

Este p ro g ra m a crea u n a p u n t a d o r p e r d id o . N O e je c u te e ste p r o g r a m a ; co n


Precaución suerte, s ó lo dejará d e fu n cio n a r.

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

(La salida que usted obtenga puede ser distinta.)


A p u n ta d o re s 249

La linca 10 declara a a p l n t como apuntador a USHORT. y el mismo a p l n t apunta


A nálisis
a la memoria recién asignada. La linea 11 coloca el valor 10 en esa memoria, y
la línea I2 imprime su valor. Después de imprimir el valor, se utiliza d e l e t e en el apun­
tador. Ahora a p l n t es un apuntador perdido, o descontrolado.
La línea 15 declara un nuevo apuntador. apLong. el cual apunta a la memoria asignada
por new. La línea 16 asigna el valor de 90000 a apLong. y la línea 17 imprime su valor.
La línea 19 asigna el valor 20 a la memoria a la que apunta a pl nt . pero a p l n t ya no
apunta a ningún lugar válido. La memoria a la que apunta a p l n t lúe liberada por la lla­
mada a d e le te , por lo que asignar un valor a esa memoria es un desastre evidente.
La línea 21 imprime el valor de aplnt. Como era de esperarse, es 20. La línea 22 imprime
el valor de apLong; este valor ha cambiado repentinamente a 65556. Surgen dos preguntas:
1. ¿Cómo pudo cambiar el valor de apLong, si ni siquiera se tocó a apLong ?
2. ¿En dónde se almacenó el valor 20 cuando se utilizó ap ln t en la línea 19?
Como podría suponer, éstas son preguntas relacionadas. Cuando se colocó un valor en
a p l n t en la línea 19. el compilador colocó felizmente el valor de 20 en la ubicación de
memoria a la que a p l n t apuntaba previamente. Sin embargo, como esa memoria tue
liberada en la línea 13, el compilador tenía la libertad de reasignarla. Cuando se creó
apLong en la línea 15, se le asignó la antigua ubicación de memoria de a p l n t . (En algu­
nas computadoras tal vez no pase esto, dependiendo del lugar de la memoria en donde se
guarden estas variables.) Cuando se asignó el valor 2 0 a la ubicación a la que apuntaba
a p l n t previamente, éste sobreescribió el valor al que apuntaba apLong. Esto se conoce
como “pisotear un apuntador”. Por lo general, éste es el desafortunado producto obtenido
al usar un apuntador perdido.
Éste es un error especialmente desagradable, ya que el valor que cambió no estaba aso­
ciado con el apuntador perdido. El cambio al valor en apLong fue un etecto secundario
del mal uso de a p ln t. En un programa grande, esto sería muy diiícil de rastrear.
Sólo por diversión, he aquí los detalles de cómo llegó el valor 65556 a esa dirección de
memoria:
1. a p ln t apuntaba a una ubicación específica de memoria, y se asignó el valor 10.
2. Se llamó a d e le te para que actuara sobre aplnt, lo cual le indicó al compilador
que podía colocar cualquier otra cosa en esa ubicación. Luego se asignó a apLong
la misma ubicación de memoria.
3. Se asignó el valor de 9 0 0 0 0 a *apLong. La computadora utilizada en este ejemplo
guardó el valor de 4 bytes de 90,000 (00 01 5F 90) con los bytes intercambiados.
Por lo tanto, dicho valor se guardó como 5F 90 00 01.
4. Se asignó a a p l n t el valor de 2 0 (o 00 14 en notación hexadecimal). Como a p l n t
aún apuntaba a la misma dirección, se sobreescribieron los dos primeros bytes de
apLong. con lo cual quedó como 00 14 00 01.
250 Día 8

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.

P reg u n tas frecu en tes


FAQ: ¿Cuál es la diferencia entre un apuntador nulo y un apuntador perdido?
Respuesta: Cuando elimina un apuntador, le dice al compilador que libere la memoria,
pero el apuntador en sí no deja de existir. Ahora es un apuntador perdido.
Si después de esto escribe miApuntador = NULL;, lo transforma de apuntador perdido en
apuntador nulo.
Por lo general, si utiliza delete sobre un apuntador y luego vuelve a utilizar delete, su
programa queda indefinido. Es decir, podría pasar cualquier cosa (con suerte, el progra­
ma sólo dejará de funcionar). Si elimina un apuntador nulo, no pasa nada; esto es seguro.
El uso de un apuntador perdido o de uno nulo (por ejemplo, escribir miApuntador = 5;)
es ilegal, y el programa podría dejar de funcionar. Si el apuntador es nulo, dejará de
funcionar, un beneficio más del apuntador nulo sobre el apuntador perdido. Los autores
prefieren que el programa deje de funcionar en forma predecible, ya que esto es más
fácil de depurar.

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;

apuno es un apuntador a un valor entero constante. El valor al que apunta no se puede


cambiar.
a p D o s es un apuntador constante a un entero. Se puede cam biar el entero, pero apDos no
puede apuntar a ninguna otra cosa.
a p T r e s es un apuntador constante a un entero constante. El valor al que apunta no se
puede cambiar, y a p T r e s no puede apuntar a ninguna otra cosa.
A p u n ta d o re s 251

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

Apuntadores const y funciones miembro const


En el día 6, “Clases base”, aprendió que puede aplicar la palabra reservada const a una
función miembro o método. Cuando una función se declara como const. el compilador
marca como error cualquier intento por cambiar los datos del objeto desde el interior de
esa función.
Si declara un apuntador a un objeto const. los únicos métodos que puede llamar con ese
apuntador son métodos const. El listado 8.10 muestra esto.

Entrada L is t a d o 8 . 1 0 Uso de apuntadores a objetos const

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

33: int main()


34: {
35: Rectángulo * apRect = new Rectángulo;
36: const Rectángulo * apConstRect = new Rectángulo;
37: Rectángulo * const apConstApunt = new Rectángulo;
38:
39: cout « "Ancho de apRect: “;
40: cout « apRect->ObtenerAncho() « " pies\n";
41: cout « "Ancho de apConstRect: ";
42: cout « apConstRect->ObtenerAncho() << " pies\n";
43: cout « "Ancho de apConstApunt: ";
44: cout « apConstApunt->ObtenerAncho() << " pies\n";
45:
46: apRect->AsignarAncho(10);
47: // apConstRect•>AsignarAncho(10);
48: apConstApunt->AsignarAncho(10);
49:
50: cout « "Ancho de apRect: ";
51 : cout « apRect->ObtenerAncho() « " pies\n";
52: cout « "Ancho de apConstRect: ";
53: cout « apConstRect->ObtenerAncho() « " pies\n";
54: cout « "Ancho de apConstApunt: ";
55: cout « apConstApunt->ObtenerAncho() « " pies\n";
56: return 0;
57: >

Ancho de apRect: 5 pies


S a lid a Ancho de apConstRect: 5 pies
Ancho de apConstApunt: 5 pies
Ancho de apRect: 10 pies
Ancho de apConstRect: 5 pies
Ancho de apConstApunt: 10 pies

A nálisis Las líneas 6 a 22 declaran la clase Rectángulo. La línea 17 declara el método


const llamado ObtenerAncho(). La línea 35 declara un apuntador a Rectángulo.
La línea 36 declara a apConstRect, que es un apuntador a un Rectángulo constante. La
línea 37 declara a apConstApunt, que es un apuntador constante a Rectángulo.
Las líneas 39 a 44 imprimen sus valores.
En la línea 46 se utiliza apRect para establecer en 10 el ancho del rectángulo. En la línea
47 se utilizaría apConstRect, pero se declaró como apuntador a un Rectángulo constan­
te. Por lo tanto, no puede llamar legalmente a una función miembro que no sea constante;
se convierte en comentario. En la línea 48, apConstApunt llama a AsignarAncho().
apConstApunt se declara como apuntador constante a un rectángulo. En otras palabras, el
apuntador es constante y no puede apuntar a ninguna otra cosa, pero el rectángulo no es
constante. Si elimina las barras diagonales de comentario en la línea 41, se ejecutará una
instrucción ilegal. En algunos compiladores GNU sólo recibirá una advertencia que le
indicará que puede estar modificando una variable o una función declarada como const.
El mensaje del compilador se verá de la siguiente manera:
A p u n ta d o r e s 253

ls t0 8 -10.c x x : in functio n int main()‘ :


l s t 0 8 •10.c x x :41 : warning: p a s s i n g const Rectángulo' as this' argument of void
R e c t á n g u l o : : A s i g n a r A n c h o ( i n t ) ' d i s c a r d s const 8
Esto lo indica quo sc descartará este método como const y se podrá utilizar como cualquier
otro método. En esto caso, la salida del programa será la siguiente:
Ancho de apRect: 5 pi es
Ancho de apConstRect: 5 pi es
Ancho de a p C o n st A pu n t: 5 pies
Ancho de apRect: 10 pi es
Ancho de apConstRect: 10 pies
Ancho de apConstApunt: 10 pi es

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.

L i s t a d o 8 .1 1 Ejem plo de cómo analizar sintácticam ente palabras de una


Entrada cadena de caracteres

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

E s c r i b a una cadena: Este l i s t a d o se obtuvo de la r e v i s t a C++ Report


S alida Obtuve e s t a palabra: Este
Obtuve e s t a palabra: listado
Obtuve e sta pa labr a: se
Obtuve e s t a pa labr a: obtuvo
Obtuve e sta pa labr a: de
Obtuve e sta palabra: la
Obtuve e s t a palabra: revista
Obtuve e s t a pa labr a: C
Obtuve e sta pa labr a: Report

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 .

En la línea 40 se declaran dos apuntadores a carácter, api y ap2, y en la línea 43 se hace


que apunten al desplazamiento de la cadena por medio de d e s p l a z a m i e n t o D e P a l a b r a .
Al principio, d e s p l a z a m i e n t o D e P a l a b r a es cero, por lo que dichos apuntadores apuntan al
principio ile la cadena.
256 Día 8

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.

Éste es un ejemplo clásico de código que se entiende mejor si se coloca en un depurador


y se analiza su ejecución paso a paso.

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

¿Qué es una referencia?


Una r e fe r e n c ia es un alias o sinónimo; cuando crea una ieferencia, la inicializa
con el nombre de otro objeto, que viene siendo el destino. A partir de ese
momento, la referencia actúa como un nombre alternativo para el destino, y
cualquier cosa que le haga a la referencia, en realidad se la hace al destino.
260 Día 9

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

■ ■ ■ ■ En la linca X se declara una variable local de tipo i n t llamada i n t u n o . En la Ií-


■■■■" nea 9 se declara una referencia a un entero llamada rUnaRef. y se inicializa para
hacer referencia a i n t U no . Si declara una referencia pero no la inicializa. obtendrá un
error en tiempo de compilación. Las referencias se deben inicializar.
En la linca I I se asigna el valor 5 a intuno. En las líneas I2 y 13 se im prim en los v a lo ­
res de in tU n o y rUnaRef. v son. desde luego, el mismo valor.

En la línea I 5 se asigna el valor 7 a rUnaRef. Como ésta es una referencia, es un alias


para i n t u n o , y por lo tanto valor 7 se asigna realmente a intuno, com o se m uestra en las
im presiones de las líneas I6 y 17.

Uso del operador de dirección (&)


en referencias
Si le pide su dirección a una referencia, ésta regresa la dirección de su destino. Ésa es la
naturaleza de las referencias: Son un alias para el destino. El listado 9.2 muestra esto.

L is t a d o 9 .2 Cómo tomar la dirección de una referencia


/ / L i s t a d o 9.2
2 // Muestra d e l uso de r ef e re nc i as
3
4: tí//include <iostream.h>
5
6 i n t main()
7: {
8: i n t intUno;
9: i n t & rUnaRef = intUno;
10
11 intUno = 5;
12 cout << "i ntUn o: " << intUno « endl;
13 cout << "rUnaRef: " << rUnaRef << endl;
14
15 cout << "&intUno: " << &intUno << endl;
16 cout << "&rUnaRef: " « &rUnaRef « endl;
17
18 r e t ur n 0;
19: }
262 D ía 9

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

La salida q u e u s t e d o b t e n g a p u e d e ser d i s t i n t a e n las d o s u l t i m a s lineas.

Una vez más, r U n aR e f se inicializa como referencia a i n t U n o . Usía vez se impri-


■■■■■ men las direcciones de las dos variables, y son idénticas. ('++ no le proporciona
la manera de tener acceso a la dirección de la referencia en sí. pues no es significativa,
como lo sería si estuviera utilizando un apuntador u otra variable. Las referencias se ini­
cial izan al crearse, y siempre actúan como sinónimo para su destino, incluso al aplicar el
operador de dirección.
Por ejemplo, si tiene una clase llamada P r e s i d e n t e , podría declarar una instancia deesa
clase, como se muestra a continuación:
P r e s i d e n t e Vi cen te_ Fo x_Quez ad a;

Entonces podría declarar una referencia a P r e s i d e n t e e inicializarla con este objeto:


P r e s i d e n t e &Vicente_Fox = V i c e n t e _ Fo x _Q u ez a d a ;

Sólo existe un P r e s i d e n t e ; ambos identificadores hacen referencia al mismo objeto


de la misma clase. Cualquier acción realizada sobre V i c e n t e _ F o x afectará también a
Vicente_Fox_Quezada.

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.

Las referencias no se pueden re asign ar


Aun los programadores de C++ experimentados que conocen la regla (que establece que
las referencias no se pueden reasignar y son siempre un alias para su destino) se pueden
confundir por lo que pasa si se trata de reasignar una referencia. Lo que parece ser una
reasignación resulta ser la asignación de un nuevo valor al destino. Ll listado 9.3 muestra
esto.
R e fe re n c ia s 263

Entrada L is t a d o 9 .3 Cóm o asignar una referencia

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 ;

Evidentemente, cuando se imprimen los valores de i n t U n o y r U n a R e f . son iguales que el


de i n t D o s (líneas 19 a 21 ). De hecho, cuando se imprimen las dilecciones en las líneas
22 a 24, puede ver que rUnaRef sigue haciendo referencia a i n t U n o \ no a 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

Debe inicializar a r l n t R e f como referencia a un entero específico, como se muestra a


continuación:
i n t queTanGrande = 200;
i n t & r l n t R e f = queTanGrande;

De la m ism a m anera, no debe inicializar una refe re n c ia a u n a c la s e GATO:


GATO & rGatoRef = GATO; // i n c o r r e c t o

Debe inicializar r G a t o R e f como referencia a un objeto específico de la clase GATO:


GATO p el us a;
GATO & rGatoRef = pel us a;

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.

Entrada L is t a d o 9 .4 Referencias a objetos

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

Pelusa tiene: 5 años de edad.


S a l id a Y Pelusa pesa: 8 libras.

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.

Paso de argum entos de fu n cio n e s


por referencia
En el día 5, “Funciones”, aprendió que las funciones tienen dos limitaciones: los argu­
mentos se pasan por valor, y la instrucción return sólo puede regresar un valor.
Si se pasan valores a una función por referencia se pueden resolver ambas limitaciones.
En C++, pasar por referencia se logra de dos formas: usando apuntadores y usando refe-
tencias. Observe la diferencia: Se pasa p o r referencia usando un apuntador, o se pasa por
referencia usando una referencia.
La sintaxis para usar un apuntador es distinta de la requerida para usar una referencia,
pero el electo neto es el mismo. En lugar de que se cree una copia dentro del alcance de
la función, en realidad se pasa el objeto original a la función (en realidad se pasa la di­
lección del objeto, es decir, su referencia).
En el día 5 aprendió que los parámetros de las funciones se pasan en la pila. Cuando a
una función se le pasa un valor por referencia (usando ya sea apuntadores o referencias),
se coloca en la pila la dirección del objeto, no todo el objeto.
R e fe re n c ia s 267

De hecho, en algunas computadoras la dirección se guarda en un registro y no se coloca


nada en la pila, l ’n cualquier caso, ahora el compilador sabe cómo tener acceso al objeto
original, y hace los cambios ahí. no en una copia, como sucede cuando se pasan los
parámetros por valor.
Si se pasa un objeto por referencia, se permite que la función cambie el objeto al que
hace referencia.
Recuerde que el listado 5.5 del día 5 demostró que una llamada a la función in terca m ­
biar ( ) no afectaba los valores de la función que hizo la llamada. Para su comodidad, el
listado 5.5 se reproduce aquí, en el listado 9.5.

L is t a d o 9 .5 Una m uestra de parámetros pasados por valor


1: // L i s t a d o 5 . 5 - muestra de parámetros pasados por v a l o r
2:
3: //inelude < i o s t r e a m . h >
4:
5: v o i d i n t e r c a m b i a r ( i n t x, i n t y) ;
6:
7: i n t m a i n ()
8: {
9: i n t x = 5, y = 10;
10:
11 : cout << " m a i n ( ) . A nt es d el i nter cambio, x: " << x << " y:
" << y « " \n" ;
12: intercam biar^,y) ;
13: cout << " m a i n ( ) . Después d e l intercambio, x: " « x << " y:
ta»" << y « " \n" ;
14: r e t u r n 0;
15: }
16:
17: void intercambiar ( i n t x, i n t y)
18: {
19: i n t temp;
20:
21 : cout << " I n t e r c a m b i a ^ ) . Antes del intercambio, x: " << x << " y:
te>" << y « " \ n " ;
22:
23: temp = x;
24: x = y;
25: y = temp;
26:
27: cout << " I n t e r c a m b i a r ( ) . Después d e l intercambio, x: " << x << y:
■=>" << y « " \n" ;
28:
29:
268 Día 9

main(). Antes del intercambio, x: 5 y: 10


S a l id a
Intercambiad). Antes del intercambio, x: 5 y: 10
Intercambiad). Después del intercambio, x: 10 y: 5
main(). Después del intercambio: x: 5 y: 10
Este programa inicializa dos variables en main( ) y luego las pasa a la función
A nálisis
intercambiar(), la cual aparentemente las intercambia. Sin embargo, al exami­
narlas otra vez en main(), ¡permanecen sin cambio!
El problema aquí es que x y y se están pasando a intercambiar () por valor. Es decir, se
hicieron copias locales en la función. Lo que necesita es pasar x y y por referencia.
En C++, este problema se puede resolver de dos maneras: puede hacer que los parámetros
de intercambiad ) sean apuntadores a los valores originales, o puede pasar referencias
a los valores originales.

C óm o hacer que intercam biar () fu n cio n e


con apuntadores
Al pasar un apuntador se pasa la dirección del objeto, y por consecuencia la función puede
manipular el valor que se encuentra en esa dirección. Para hacer que intercambiad ) cam­
bie el valor actual por medio de apuntadores, se debe declarar la función intercambiar!)
de manera que acepte dos apuntadores de tipo entero. Entonces, al desreferenciar los apun­
tadores, se cambiarán realmente los valores de x y y. El listado 9.6 muestra esto.

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

1: // Listado 9.6 Muestra el paso por


2: // referencia simulado usando apuntadores
3:
4: #include <iostream.h>
5:
6: void intercambiar(int * x, int * y);
7:
8: int main()
9: {
10 int x = 5, y = 10;
11
12: cout << "Main. Antes del intercambio, ";
13: cout « "x: " « x « " y: " « y « "\n";
14: intercambiad&x, &y);
15: cout « "Main. Después del intercambio,
16: cout « "x: " « x « " y: " « y << "\n";
17: return 0;
18: }
19:
20: void intercambiar (int * apx, int * apy)
21 : {
22: int temp;
23:
R e fe re n cia s 269

24 cout << " I n t e r c a m b i a r . Antes del intercambio,


25 cout << * a p x : " << *apx << “ *apy: “ « "apy << “ \ n D
26
27 temp = *apx;
28 *apx = *apy;
29 *apy = temp;
30
31 cout << " I n t e r c a m b i a r . Después del intercambio, "
32 cout << "* apx : " << *apx << " "apy: " << *apy « D\n"
33 } 9
Main. Antes del intercambio, x: 5 y: 10
Salida I n t e r c a m b i a r . Antes del intercambio, *apx: 5 *apy: 10
I n t e r c a m b i a r . Después del intercambio, *apx: 10 *apy: 5
Main. Después d el intercambio, x: 10 y: 5

A nálisis ¡Se logre')!

En la línea 6, el prototipo de i n t e r c a m b i a r () se cambia para indicar que sus dos


parámetros serán apuntadores a un tipo i n t en lugar de variables. Cuando se llama a
i n t e r c a m b i a r ( ) en la línea 14. se pasan las direcciones de x y de y como argumentos.

En la línea 22. en la función i n t e r c a m b i a r ( ). se declara una variable local llamada


temp. Esta variable no necesita ser apuntador; sólo guardará el valor de * a px (es decir, el
valor de x en la función que hace la llamada) durante la vida de la función. Al terminar
la función, temp ya no se necesitará.
En la línea 27 se asigna a temp el valor contenido en apx. En la línea 28. ese valor se
asigna al valor contenido en apy. En la línea 29. la variable guardada en temp (es decir,
el valor original contenido en apx) se coloca en apy.
El efecto ocasionado con esto es que los valores de la función que hizo la llamada, cuya
dirección se pasó a intercam biar(), se intercambiaron realmente.

Cóm o im plem em tar a in te rc a m b ia r^ ) con referen cias


El programa anterior funciona, pero la sintaxis de la función i n t e r c a m b i a r () es incómo­
da por dos motivos. En primer lugar, la necesidad repetida de desreferenciar los apuntado­
res dentro de la función i n t e r c a m b i a r () hace que ésta sea propensa a errores y difícil de
leer. En segundo lugar, la necesidad de pasar la dirección de las variables de la función
que hace la llamada hace que el funcionamiento interno de i n t e r c a m b i a r () sea dema­
siado transparente para sus usuarios.
Uno de los objetivos de C++ es prevenir que el usuario de una función se preocupe por
la forma en que ésta funciona. Al pasar los parámetros mediante apuntadores se coloca la
carga en la función que hace la llamada, en lugar de colocarla donde pertenece (en la
función que es llamada). El listado 9.7 vuelve a escribir la función i n t e r c a m b i a r ().
pero esta ve/ usando referencias.
270 Día 9

Listado 9.7 intercambiar() reescrita usando referencias

1: //Listado 9.7 Muestra del paso de parámetros por referenc:


2: / / ¡uso de referencias!
3:
4: //include <iostream.h>
5:
6: void intercambiar(int & x, int & y);
7:
8: int main()
9: {
10 int x = 5, y = 10;
11
12 cout « "Main. Antes del intercambio,
13 cout « "x: " « x << " y; " « y << " \n " ;
14 intercambiar^, y);
15 cout « "Main. Después del intercambio, ";
16 cout « "x: " « x « “ y: " « y « "\n";
17 return 0;
18 }
19
20 void intercambiar (int & rx, int & ry)
21 {
22 int temp;
23
24 cout « "Intercambiar. Antes del intercambio, ";
25 cout « " rx: " « rx « “ ry: " « ry << "\n";
26
27 temp = rx;
28 rx = ry;
29 ry = temp;
30
31 cout « "Intercambiar. Después del intercambio, ";
32 cout « " rx: " « rx « " ry: " « ry << "\n";
33 }

Main. Antes del intercambio, x: 5 y: 10


S a l id a Intercambiar. Antes del intercambio, rx: 5 ry: 10
Intercambiar. Después del intercambio, rx: 10 ry: 5
Main. Después del intercambio, x: 10 y: 5

Igual que en el ejemplo con apuntadores, en la línea 10 se declaran dos variables,


y sus valores se imprimen en la línea 13. En la línea 14 se hace una llamada a la
función intercambiar(), pero observe que se pasan x y y, no sus direcciones. La fun­
ción que hace la llamada simplemente pasa las variables.
Al llamar a intercambiar(), la ejecución del programa salta a la línea 20, en donde las
variables se identifican como referencias. Sus valores se imprimen en la línea 25, pero
observe que no se requieren operadores especiales. Éstos son alias para los valores origi­
nales y se pueden utilizar como tales.
Referencias 271

En las lincas 27 a 29 se intercambian los valores, y luego se imprimen en la línea 32.


La ejecución del programa salta de regreso a la función que hizo la llamada, y en la
línea ló, dentro de main( ), se imprimen los valores. Debido a que los parámetros para
intercam biar( ) se declaran como referencias, los valores de main( )se pasan por refe­
rencia. y por consecuencia se cambian también en main(). Otro método exitoso.
Las referencias proporcionan la conveniencia y facilidad del uso de las variables normales,
¡junto con el poder y la capacidad del paso por referencia que ofrecen los apuntadores!

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

A sí como un ingeniero examina la hoja de especificaciones de una tubería para determi­


nar su capacidad de soporte de flujo, el tamaño de las uniones, etc., un programador de
C h—h lee la interfaz de la función o de la clase para determinar los servicios que ésta pro­
porciona, los parámetros que necesita y los valores que regresa.
Esto también se conoce corno método de la caja tierra. Usted sabe cuáles son las entradas
de la caja negra, tiene una idea general de lo que hace, y obtiene las salidas esperadas.
Pero no tiene idea de cómo ocurrió esto porque no puede ver su funcionamiento intemo
(ni debe importarle, pues hay demasiadas cosas más por las que debe preocuparse).
Un ejemplo sencillo de esto es la función sqrt () que se encuentra en la biblioteca están­
dar. Usted sabe que toma un argumento de punto flotante y regresa la raíz cuadrada de ese
argumento. Conoce la interfaz (entrada y salida), conoce los detalles generales (calcula la
raíz cuadrada), pero no conoce los detalles internos de cóm o se realiza ese cálculo.

Regreso de varios valores por m edio


de apuntadores
Como se dijo anteriormente, las funciones sólo pueden regresar un valor. ¿Qué pasa si
necesita obtener dos valores de una función? Una forma de solucionar este problema es
pasar por referencia dos objetos a la función. Entonces la función puede llenar los obje­
tos con los valores correctos. Como el paso por referencia permite que una función cam­
bie los objetos originales, esto efectivamente permite que la función regrese dos piezas
de información. Este método no utiliza el valor de retorno de la función, el cual se puede
reservar entonces para reportar errores.
De nuevo, esto se puede hacer con referencias o con apuntadores. El listado 9.8 muestra
una función que regresa tres valores: dos como parámetros apuntadores y uno como el
valor de retorno de la función.

L is t a d o 9 .8 Regreso de valores con a p u n ta d o re s

1: //Listado 9.8 Regreso de varios valores de


2: // una función por medio de apuntadores
3:
4: #include <iostream.h>
5:
6: short Factor(int n, int * apAlCuadrado, int * apAlCubo);
7:
8: int main()
9: {
10 int numero, alcuadrado, alcubo;
11 short error;
12
13 cout « "Escriba un número (0 - 20):
14 cin >> numero;
R e fe re n cia s 273

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

En las líneas 36 y 37 se les asignan a las variables originales (relereneiadas mediante


apuntadores) sus valores de retorno. En la línea 38 se asigna un cero a Valor eomo valor
de éxito. En la línea 40 se regresa ese Valor.
Una mejora a este programa podría ser la siguiente declaración:
enum C0DIG0_ERR { EXITO, ERROR};

Así, en lugar de regresar 0 o 1, el program a podría reg resar EXITO o ERROR.

Regreso de valores por referencia


A unque el listado 9.8 funciona, se puede facilitar su legibilidad y m an tenim iento si se
utilizan referencias en lugar de apuntadores. El listado 9.9 m uestra el m ism o programa
reescrito para utilizar referencias e incorporar la en u m eració n llam ad a C0DIG0_ERR.

E n tra d a L ist a d o 9.9 L is t a d o 9 .8 m o d i f i c a d o , e s t a v e z p a r a u t i l i z a r r e f e r e n c ia s

1: //Listado 9.9 Regreso de varios valores de


2: // una función por medio de referencias
3:
4: #include <iostream.h>
5:
6: typedef unsigned short USHORT;
7: enum C0DIG0_ERR { EXITO, ERROR };
8:
9: C0DIG0_ERR Factor(USH0RT, USHORT &, USHORT &) ;
10:
11 : int main()
12: {
13: USHORT numero, alcuadrado, alcubo;
14: C0DIG0_ERR resultado;
15:
16: cout « “Escriba un número (0 - 20): “;
17: cin » numero;
18:
19: resultado = Factor(numero, alcuadrado, alcubo);
20:
21 : if (resultado == EXITO)
22: {
23: cout « "número: " « numero << "\n";
24: cout << "al cuadrado: " << alcuadrado << "\n";
25: cout « "al cubo: " « alcubo « "\n";
26: }
27: else
28: cout << "¡Se encontró un error!!\n";
29: return 0;
30: }
R e fe re n cia s 275

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

El listado 9.9 es idéntico al listado 9.8. con dos excepciones. La enumeración


A nálisis
CODIGO ERR hace que el reporte de errores sea más explícito en las líneas 35 y
40, así como el manejo de errores en la línea 21.
Sin embargo, el cambio más importante es que ahora F a c t o r ( ) se declara para tomar re­
ferencias a a l c u a d r a d o y a l c u b o en vez de apuntadores. Esto hace que la manipulación
de estos parámetros sea más sencilla y fácil de entender.

Cómo pasar parámetros por referencia


para tener eficiencia
Cada vez que le pasa un objeto por valor a una función, se crea una copia del objeto. Cada
vez que regresa un objeto por valor de una función, se crea otra copia.
En el día 5 aprendió que estos objetos se copian en la pila. Hacer esto toma tiempo y ocupa
memoria. Para objetos pequeños, como los tipos enteros integrados, esto es un costo trivial.
Sin embargo, con objetos más grandes creados por el usuario, el costo es mayor. El tama­
ño de un objeto creado por el usuario en la pila es la suma de cada una de sus variables
miembro. Estas, a su vez. pueden ser objetos creados por el usuario, y pasar toda esa
estructura masiva copiándola en la pila puede provocar una reducción bastante conside­
rable en el rendimiento y un consumo excesivo de memoria.
También hay otro costo. Con las clases que se crean, cada una de estas copias temporales
se crea cuando el compilador llama a un constructor especial: el constructor de copia.
Mañana aprenderá cómo funcionan los constructores de copia y cómo hacer sus propios
constructores, pero por ahora basta con saber que el constructor de copia se llama cada
vez que se coloca en la pila una copia temporal del objeto.
276 Día 9

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

34: cout << “Crear un gato...\n";


35: GatoSimple Pelusa;
36: cout << "Llamando a Funcionllno...\n°;
37: FuncionUno(Pelusa);
38: cout « “Llamando a FuncionDos...\n°;
39: FuncionDos(&Pelusa);
40: return 0;
41: }
42:
43: // Funcionllno, pasa por valor
44: GatoSimple FuncionUno(GatoSimple elGato)
45: {
46: cout << "Funcionllno. Regresando...\n°;
47: return elGato;
48: }
49:
50: // FuncionDos, pasa por referencia
51: GatoSimple * FuncionDos(GatoSimple * elGato)
52: {
53: cout « "FuncionDos. Regresando...\n";
54: return elGato;
55: }

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

Los n ú m e ro s d e línea m o stra d o s en la salida an te rio r n o se im p rim irá n . Se


Nota a g r e g a r o n c o m o a y u d a para el análisis.

En las líneas 6 a 12 se declara una clase GatoSimple muy simplificada. El cons­


A n á l is is
tructor, el constructor de copia y el destructor imprimen cada uno un mensaje
informativo para que podamos saber cuándo son llamados.
En la línea 34, main() imprime un mensaje que se ve en la línea 1 de la salida. En la
línea 35 se crea una instancia de un objeto de la clase GatoSimple. Esto ocasiona que
se llame al constructor, y la salida producida por el constructor se ve en la línea 2 de la
salida.
|278 Día 9

En la linea 36, m a i n ( ) in fo rm a que está llam an d o a F u n c i o n U n o . la cual crea la linea 3


de la salida D ebido a q u e al llam ar a F u n c i o n U n o l ) se le p asa el o b je to G atoS im ple por
valor, se crea u n a c o p ia de este o b je to en la pila c o m o un o b je to loeal para la función
llam ada. E sto o casio n a que se llam e al c o n stru c to r de co p ia, el cual crea la línea 4 de la
salida.
L a ejecución del pro g ram a salta h asta la linea 4 6 en la fu n ció n llam ada, la cual imprime
un m ensaje inform ativo, el de la linea 5 de la salid a. La fu n ció n re g ie sa entonces el obje*
to G atoS im ple por valor. E sto crea otra co p ia del o b je to , se llam a al co n stru cto r de copia
y se produce la línea 6 de la salida.
El valor de retomo de FuncionUno() no se asigna a ningún objeto, por lo que se descarta
el valor temporal creado para el valor de retorno, lo que ocasiona que se llame al des­
tructor, el cual produce la línea 7 de la salida. C o m o la FuncionU no() ha terminado, su
copia local queda fuera de alcance y se destruye, por lo que se llama al destructor y se
produce la línea 8 de la salida.
L a ejecución del program a regresa a main ( ) , y se llam a a FuncionDos ( ) , pero se pasa el
parám etro por referencia. No se produce copia, p o r lo q u e no hay salida. F uncionD os()
im prim e el m ensaje que aparece en la línea 10 de la s a lid a y lu e g o reg resa el objeto
GatoSimple, de nuevo por referencia, po r lo que no se p ro d u c e n in g u n a llam ada al
constructor ni al destructor.

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.

Paso de un apuntador const


A unque pasar un apuntador a FuncionDos () es m ás eficien te, es peligroso. N o se debe
perm itir que FuncionDos () cam bie el objeto GatoSimple q u e recib e, pero aún así se le
proporciona la dirección de GatoSimple. Esto ex p o n e seriam en te al objeto a ser cambia­
do y viola la protección que se ofrece al pasar p arám etro s p o r valor.

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

Entrada Listado 9.111 Paso de apuntadores const


1 / / L i s t a d o 9.11
2 // Paso de a pun tad or es const a objetos
3
4 //inelude < i o s t r e a m . h >
5
6 c l a s s Gat oSimple
7 {
8 public:
9 G a t o S i m p l e ();
10 Gat oS imple( Ga toS imple &);
11 - G a t o S i m p l e ();
12 i n t Obtener Edad() const
13 { r e t u r n suEdad; }
14 v o i 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 o S i m p l e : :G a t oS i m pl e ()
21 {
22 cout << "Constructor de GatoSimple. . . \n";
23 suEdad = 1 ;
24 }
25
26 G a t o S i m p l e : :GatoSimple(GatoSimple &)
27 {
28 cout << " C o n s t r u c t o r de copia de G a t o S i m p l e . . . \ n " ;
29 }
30
31 G a t o S i m p l e : :- G a t o S i m p l e ()
32 {
33 cout << "Destructor de GatoSimple. . . \n";
34 }
35
36 co ns t GatoSimple * c o n s t Funci on Do s( con st GatoSimple * const e l G at o );
37
38 i n t main()
39 {
40 cout « "C r ea r un g a t o . . . \ n " ;
41 GatoSimple Pelusa;
42 cout « " P e l u s a t i e n e " ;
43 cout « Pelus a. Obte ner E dad ();
44 cout « " años de edad\n";
45 i n t edad = 5;
46 P e l u s a .A s i g n a r E d a d ( e d a d ) ;
47 cout << " P e l u s a ti ene " ;
48 cout << Pelus a. Obte ner E dad () ;
49 cout << " años de edad\n";
co n tin u a

i
280 Día 9

L istado 9 . 1 1 c o n t in u a c ió n

50 cout << “Llamando a FuncionDos...\n";


51 FuncionDos(&Pelusa);
52 cout << “Pelusa tiene " ;
53 cout « Pelusa.ObtenerEdad();
54 cout « " años de edad\n";
55 return 0;
56 }
57
58 // FuncionDos, pasa un apuntador const
59 const GatoSimple * const FuncionDos(const GatoSimple * const elGato)
60 {
61 cout « "FuncionDos. Regresando...\n";
62 cout « "Ahora Pelusa tiene " << elGato->ObtenerEdad();
63 cout « " años de edad \n";
64 // elGato->AsignarEdad(8); iconst!
65 return elGato;
66 }

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

GatoSimple ha agregado dos funciones de acceso, ObtenerEdad( ) en la línea 12,


la cual es una función const, y AsignarEdad() en la línea 14, la cual no es una
función const. También ha agregado la variable miembro suEdad en la línea 17.
El constructor, el constructor de copia y el destructor aún se definen para imprimir sus
mensajes. Sin embargo, nunca se llama al constructor de copia, ya que el objeto se pasa
por referencia y no se crea ninguna copia. En la línea 41 se crea un objeto y se imprime
su edad predeterminada, empezando en la línea 42.
En la línea 46 se asigna un valor a Pelusa por medio de la función de acceso AsignarEdad,
y el resultado se imprime en la línea 47. En este programa no se utiliza FuncionUno,
pero sí se llama a FuncionDos (). Esta función ha cambiado ligeramente; el parámetro y
el valor de retorno ahora se declaran, en la línea 36, para recibir un apuntador constante
a un objeto constante y para regresar un apuntador constante a un objeto constante.
Como el parámetro y el valor de retomo se siguen pasando por referencia, no se crean
copias y no se llama al constructor de copia. Sin embargo, ahora el apuntador de Función-
Dos( ) es constante, y no puede llamar al método no constante llamado AsignarEdad(). Si
la llamada a AsignarEdad () de la línea 64 no se hubiera convertido en comentario, el com-
R e fe re n cia s 281

pilador enviaría un mensaje de advertencia y eliminaría la calidad de constante del objeto


GATO (con algunas versiones del compilador GNU. este programa no compilará).

Observe que el objeto creado en main( ) no es constante, y P e l u s a puede llamar a


A s i g n a r E d a d ( ). I.a dirección de este objeto no constante se pasa a F u n c i o n D o s (). pero
como la declaración de F u n c i o n D o s () declara al apuntador como apuntador constante a
un objeto constante, ¡el objeto se trata como si fuera constante!

Referencias como alternativa


para los apuntadores
El listado 9 .1I soluciona el problema de crear copias adicionales, y por consecuencia
evita las llamadas al constructor y al destructor de copia. Utiliza apuntadores constantes
a objetos constantes, y por lo tanto resuelve el problema de que la función cambie el
objeto. Sin embargo, todavía es algo incómodo debido a que los objetos que se pasan a
la función son apuntadores.
Debido a que usted sabe que el objeto nunca será nulo, sería más sencillo trabajar con la
función si se pasara una referencia, en lugar de un apuntador. El listado 9.12 muestra esto.

Entrada L is t a d o 9 . 1 2 Paso de referencias a objetos

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

26: GatoSimple::GatoSimple(GatoSimple &)


27: {
28: cout « "Constructor de copia de GatoSimple...\n";
29: }
30:
31: GatoSimple::-GatoSimple()
32: {
33: cout « “Destructor de GatoSimple...\n";
34: }
35:
36: const GatoSimple & FuncionDos(const GatoSimple & elGato);
37:
38: int main()
39: {
40: cout « "Crear un gato...\n";
41 : GatoSimple Pelusa;
42: cout « "Pelusa tiene " << Pelusa.ObtenerEdad();
43: cout « " años de edad\n";
44: int edad = 5;
45: Pelusa.AsignarEdad(edad);
46: cout « "Pelusa tiene " « Pelusa.ObtenerEdad();
47: cout « " años de edad\n";
48: cout « "Llamando a FuncionDos...\n" ;
49: FuncionDos(Pelusa);
50: cout « "Pelusa tiene " « Pelusa.ObtenerEdad();
51 : cout « " años de edad\n";
52: return 0;
53: }
54:
55: // FuncionDos, pasa una referencia a un objeto const
56: const GatoSimple & FuncionDos(const GatoSimple & elGato)
57: {
58: cout « "FuncionDos. Regresando...\n";
59: cout « "Ahora Pelusa tiene " « elGato.ObtenerEdad();
60: cout « " años de edad \n";
61 : // elGato.AsignarEdad(8); íconst!
62: return elGato;
63: >

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

La salida es idéntica a la producida por el listado 9.11. El único cambio signi­


A nálisis
ficativo es que ahora F u n c i o n D o s ( ) toma y regresa una referencia a un objeto
constante. De nuevo, trabajar con referencias es un poco más sencillo que trabajar con
apuntadores, y se logran los mismos ahorros y eficiencia, además de la seguridad propor­
cionada al usar c o n s t .

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.

Cuándo u tilizar referencias y cuándo


utilizar apuntado res
Los programadores de C-f+ prefieren más las referencias que los apuntadores. Las refe­
rencias son más limpias y fáciles de utilizar, y realizan mejor el trabajo de ocultar la
información, como vio en el ejemplo anterior.
Sin embargo, las referencias no se pueden reasignar. Si necesita apuntar primero a un
objeto y luego a otro, debe utilizar un apuntador. Las referencias no pueden ser nulas,
por lo que si existe alguna probabilidad de que el objeto en cuestión pueda ser nulo, no
debe utilizar una referencia. Debe usar un apuntador.
Un ejemplo de lo anterior es el operador new. Si new no puede asignar memoria en el
heap, regresa un apuntador nulo. Debido a que una referencia no puede ser nula, usted
sólo puede inicializar una referencia a este segmento de memoria hasta que compruebe
que no es nula. El siguiente ejemplo muestra cómo manejar esto:
i n t * a p ln t = new i n t ;
i f ( a pl nt != NULL)
int & rln t = *aplnt;

En este ejemplo se declara un apuntador a un entero llamado a p l n t . y se inicializa con la


memoria regresada por el operador new. Se prueba la dirección contenida en a p l n t , y si
no es nula, a p l n t es desreferenciado. El resultado de desreferenciar una variable de tipo
i n t es un objeto de tipo i n t , y r l n t se inicializa como referencia a ese objeto. Por lo
tanto, r l n t se convierte en un alias para el entero regresado por el operador new.
284 D ía 9

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.

C óm o m ezclar referencias y apuntadores


Es perfectamente válido declarar tanto apuntadores como referencias en la misma lista de
parámetros de la función, así como objetos pasados por valor, lie aquí un ejemplo:
GATO * U n a F u n c i o n ( P e r s o n a & e l P r o p i e t a r i o , C a s a * la C a sa , in t edad );

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 ;

El esp a cio e n b lan co se i g n o r a p o r c o m p le t o , p o r lo q u e p u e d e co lo c a r t a n ­


to s espacios, t a b u la d o r e s y n u e v a s líne as c o m o d e s e e e n c u a l q u i e r lu g a r en
el q u e vea u n espacio.
D e j a n d o a u n lado la libertad de c u e s t i o n e s r e l a c i o n a d a s c o n la expresión,
¿ c u á l es m e j o r ? H e a q u í a r g u m e n t o s p a ra las tres:
El a r g u m e n t o para el caso 1 es q u e r P e l u s a es u n a v a r ia b l e c u y o n o m b r e es
r P e l u s a y c u y o tip o se p u e d e c o n s id e r a r c o m o u n a " r e f e r e n c i a a u n objeto
d e la clase GATO". Por lo tanto, c o m o este a r g u m e n t o indica, el & d e b e ir con
el tipo.
El a r g u m e n t o co n trario es q u e la clase es GATO. El & es p a rte del "d e c la ra d o r",
el cual incluye el n o m b r e de la variable y el s í m b o l o &. L o q u e es m ás i m p o r ­
tante, te n e r el s ím b o lo & cerca de GATO p u e d e p r o v o c a r el s ig u i e n t e error:
GATO& rP e lu sa , rS lv e stre ;

I
Referencias 285

U n a n á lisis ca su a l d e esta linea le co n d u ciría a p e n sa r q u e t a n t o r P e lu s a


c o m o r S i l v e s t r e so n referencias a o bjeto s d e clase g a t o , p e r o e sta ría e q u i­
v o c a d o . Lo q u e e sto re a lm ente dice es q u e r P e lu s a es u n a re fe re n c ia a GATO,
y r S i l v e s t r e (a p e sa r d e su n o m b re ) n o es u n a referencia, s in o u n a sim p le
v a ria b le d e la clase GATO. Esto se d e b e reescribir d e la s ig u ie n t e m a n e ra :
GATO &rPelusa, rSilvestre;
La re sp u e sta a esta o b je ció n es q u e las d e c la ra cio n e s d e re fe re n c ia s y v a ­
ria b le s n u n c a se d e b e n c o m b in a r así. La respu esta co rre cta es la s ig u ie n te :
GATO& rPelusa;
GATO Silvestre;
F in a lm e n te , m u c h o s p ro g ra m a d o re s o p ta n p o r e v a d ir el a r g u m e n t o y u ti­
liz a n la p o s ic ió n m edia, la de co lo car el & en m e d io d e los dos, c o m o se
m u e stra e n el ca so 2.
D e s d e lu e g o q u e t o d o lo q u e se ha d ich o acerca del o p e r a d o r d e re fe re n c ia
(&) se a p lica d e ig u a l fo rm a al o p e ra d o r de indirección (*). Lo im p o r ta n te es
re c o n o c e r q u e p e rso n a s ra z o n a b le s d ifieren en sus p e rce p cio n e s acerca del
m é t o d o ú n ic o y v e rd a d e ro . Elija un estilo q u e fu n c io n e p a ra usted, y sea
c o n sis te n te d e n tro d e c u a lq u ie r p ro g ra m a ; la claridad es, y s ig u e sie n d o , el
o b je tivo .
M u c h o s p r o g r a m a d o r e s pre fie ren las sig u ie n te s c o n v e n c io n e s p a ra d e c la ra r
re fe re n c ia s y a p u n ta d o re s:
1. C o lo c a r el & y el * en m edio, con u n espacio en cada lado.
2. N u n c a d e cla ra r referencias, a p u n ta d o re s y variables ju n to s en la m ism a
línea.

¡No regrese una referencia a un objeto


que esté fuera de alcance!
Cuando los programadores de C++ aprenden a pasar parámetros por referencia, tienen
una tendencia a utilizarlos mucho. Sin embargo, es posible que los utilicen más de la
cuenta. Recuerde que una referencia siempre es un alias para algún otro objeto. Si pasa
una referencia hacia o desde una función, pregúntese: “¿Cuál es el objeto que estoy
pasando? y ¿Seguirá existiendo cada vez que lo utilice?”
El listado 9.13 muestra el peligro de regresar una referencia a un objeto que ya no exista.

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

1: // Listado 9.13 Regreso de una referencia


2: // a un objeto que ya no existe
c< »umita
| 286 Día 9

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

Salida irGato tiene -1073743376 años de edad!

Este p ro g ra m a n o se c o m p ila rá en los c o m p ila d o r e s G N U 2.7.2 y anteriores.


Precaución En las versiones 2.91.66 y 2.96 recibirá el m e n sa je d e e rro r sig u ie n te :
lst09-13.cxx: In function GatoSimple &LaFuncion () ':
lst09-13.cxx:38: warning: reference to local variable Pelusa' returned
Sin e m b a rgo , usted te n d rá un p r o g r a m a e je c u tab le q u e n o h a rá lo correcto.
A ú n más, en la versión 2.91.66 la sa lid a d el p r o g r a m a será:
¡rGato tiene 5 años de edad!
R e fe re n cia s 287

A p e sa r d e esto, este p r o g r a m a tiene f u g a s d e m e m o r i a y t e n d r á u n c o m p o r ­


t a m i e n t o im p redec ible. T a m p o c o se co m pilará en el c o m p i l a d o r d e B o r la n d ,
p e r o sí lo h a r á en los co m p ila d o re s de M icrosoft.
R e g re s a r u n a referencia a una variable local es una mala práctica d e codificación.

En las lincas 6 a 18 se declara la clase G a t o S i m p l e . En la línea 30 se inicializa una


A nálisis
referencia a un G a t o S i m p l e con los resultados obtenidos al llamar a L a F u n c i o n ( ).
la cual se declara en la línea 26 para regresar una referencia a un G a t o S i m p l e .
El cuerpo de L a F u n c i o n ( ) crea un objeto local de la clase GatoSimple e inicializa su
edad y peso. L uego regresa por referencia ese objeto local. Algunos com piladores lo
dejarán ejecutar el program a, pero los resultados serán impredecibles.

Cuando L a F u n c i o n ( ) regrese, el objeto local llamado P e l u s a será destruido (sin dolor,


se lo aseguro). La referencia regresada por esta función será un alias para un objeto
inexistente, y esto es algo malo.

Cóm o regresar una referencia


a un objeto en el heap
Podría estar tentado a solucionar el problema del listado 9.13 haciendo que L a F u n c i o n ( )
cree a P e l u s a en el heap. De esa forma, cuando usted regrese de L a F u n c i o n ( ), P e l u s a
aún existirá.
El problema de este método es: ¿Qué se hace con la memoria asignada para P e lu sa al
terminar de utilizarlo? El listado 9.14 ejemplifica este problema.

Entrada L is t a d o 9 .1 4 Fugas de memoria


1: // L i s t a d o 9 . 1 4
2: // S o l u c i ó n p a r a fugas de mem oria
3:
4: tfin clu d e <iostre a m .h >
5:
6: c l a s s GatoSimple
7: {
8: p u b lic :
9: G a t o S i m p l e ( i n t eda d , i n t p e s o ) ;
10: - G a t o S i m p l e () { }
11 : in t O b t e n e r E d a d ()
12: { re t u r n suEdad; }
13: in t O b t e n e r P e s o ()
14: { re t u r n suPeso; }
15: p riva te :
16: in t suEdad;
con tin u a
| 288 Día 9

L ista do 9 . 1 4 c o n t in u a c ió n

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 << "irGato tiene " << edad << " años de edad!\n";
33: cout « "&rGato: " « &rGato « endl;
34: // ¿cómo nos deshacemos de esa memoria?
35: GatoSimple * apGato =&rGato;
36: delete apGato;
37: // Hmmm?,¿a quién se refiereahora rGato??
38: return 0;
39: }
40:
41: GatoSimple & LaFuncion()
42: {
43: GatoSimple * apPelusa = new GatoSimple(5, 9);
44: cout « "apPelusa: " « apPelusa« endl;
45: return *apPelusa;
46: }

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 .

A nálisis LaFuncion () se ha cambiado de forma que ya no regrese una referencia a una


variable local. La memoria se reserva en el heap y se asigna a un apuntador en
la línea 43. Se imprime la dirección que guarda ese apuntador, y luego el apuntadores
desreferenciado y el objeto GatoSimple se regresa por referencia.
En la línea 30, el valor de retorno de LaFuncion () se asigna a una referencia a
GatoSimple, y ese objeto se utiliza para obtener la edad del gato, la cual se imprime en
la línea 32.
R e fe re n cia s 289

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.

Nunca está de m ás recalcar que un program a que tenga una referencia a un


objeto n u lo tal vez se pueda compilar, pero no es válido y su desem peño
será im predecible.

Existen tres soluciones para este problema. La primera es declarar un objeto G a t o S i m p l e


en la línea 3 0 y regresar por valor ese gato de L a F u n c i o n ( ). La segunda es declarar el
G a t o S i m p l e en el heap desde L a F u n c i o n ( ). pero haciendo que L a F u n c i o n ( ) regrese
un apuntador a esa memoria. Entonces la función que hace la llamada puede eliminar el
apuntador cuando deje de utilizarlo.
La tercera solución funcional, que es la correcta, es declarar el objeto en la función que
hace la llam ada y luego pasarlo por referencia a LaFuncion().

¿A quién pertenece el apuntador?


Cuando un programa asigna memoria en heap. se regresa un apuntador. Es imperativo
que mantenga un apuntador a esa memoria, ya que si pierde el apuntador, no puede libe­
rar la memoria y se convierte en una fuga de memoria.
Este bloque de memoria se pasa entre funciones, y alguien tiene que ser el "dueño" del
apuntador. Por lo general, el valor que se encuentra en el bloque se pasará por medio de
referencias, y la función que creó la memoria es la que la elimina. Pero ésta es una regla
general, no es rigurosa.
Es peligroso que una función cree memoria y que otra la libere. La imprecisión sobre
quién es dueño del apuntador puede conducir a uno de dos problemas: olvidar eliminar
un apuntador o eliminarlo dos veces. Cualquiera de las dos situaciones puede ocasionar
graves problemas en el programa. Es más seguro crear las funciones de forma que
liberen la memoria que reservan.
290 Día 9

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

l* ¿Por <juó tener apuntadores si las referencias son más sencillas?


R Las referencias no pueden ser nulas, y no se pueden reasignar. Los apuntadores
ofrecen mayor flexibilidad pero son un poco más difíciles de utilizar.
P ¿Para qué podría utilizar alguna vez el regreso por valor de una función?
R Si el objeto que va a regresar es local, debe regresarlo por valor o estará regresan­
do una referencia a un objeto inexistente.
P Dado el peligro existente al regresar por referencia, ¿por qué no regresar
siempre por valor?
R Se logra una mayor eficiencia al regresar por referencia. Se ahorra memoria y el
programa se ejecuta más rápido.

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

4. Escriba un programa que origine un apuntador perdido.


5. Corrija el programa del ejercicio 4.
6 . Escriba un programa que origine una fuga de m em oria.
7. Corrija el programa del ejercicio 6 .
8 . CAZA ERRORES: ¿Qué está mal en este program a?
1: #include <iostream.h>
2:
3: class GATO
4: {
5: public:
6: GATO(int edad) { suEdad = edad; }
7: -GATO(){}
8: int ObtenerEdad() const { return suEdad;}
9: prívate:
10 int suEdad;
11 };
12
13 GATO & CrearGato(int edad);
I! 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(int edad)
24 {
25 GATO * apGato = new GATO(edad);
26 return *apGato;
27 }
9. Corrija el programa del ejercicio 8 .
S em a n a 2

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

Sobrecarga de funciones miembro


En el día ó aprendió cómo implementar el polimorfismo de funciones, conocido
también como sobrecarga de funciones, escribiendo dos o más funciones con
el mismo nombre pero con distintos parámetros. Las funciones miembro (o
métodos) de una clase también se pueden sobrecargar, en forma muy parecida.
La clase R e c t á n g u l o que se muestra en el listado lO.l tiene dos funciones
( ). Una. que no lleva parámetros, dibuja el rectángulo con base
Di bu j a r F i g u r a
en los valores actuales del objeto. La otra toma dos valores, ancho y a l t u r a . \
dibuja el rectángulo con base en esos valores, ignorando los valores actuales
del objeto.
294 D ía 10

Entrada L is t a d o 1 0 .1 Sobrecarga de funciones m iem bro


1: // L i s t a d o 10.1 Sobrecarga de f u n c i o n e s
2: // miembro de una c l a se
3:
4: tfinclude < i o s t r e a m .h>
5:
6: // De c l a r a c i ó n de la c l a s e Rectá ng ul o
7: c l a s s Rectángulo
8 : {
9: public:
10: // c o n s t r u c t o r e s
11: R e c t a n g u l o ( i n t ancho, i n t a l t u r a ) ;
12: -Rectángulo(){}
13: // f unci ón D i b u j a r F i g u r a () s o b r e c a r g a d a de l a c l a s e
14: v oi d D i b u j a r F i g u r a () const;
15: void Dibuj a r F i g u r a ( i n t unAncho, i n t u n a A l t u r a ) c o n s t ;
16: private:
17: i n t suAncho;
18: i n t s uAl t ur a ;
19: };
20:
21: //Implenentación del c o n s t r u c t o r
22: R e c t á n g u l o : :R e c tá n gu lo ( i n t ancho, int a lt u r a )
23: {
24: suAncho = ancho;
25: s u Al t u r a = a l t u r a ;
26: }
27:
28: // Función D i b u j a r F i g u r a s ob reca rgad a - no toma v a l o r e s
29: // Di buja con base en l o s v a l o r e s miembro a c t u a l e s de l a c í a s
30: vo i d R e c t á n g u l o : :D i b u j a r F i g u r a () const
31: {
32: DibujarFigura(suAncho, suAltura);
33: }
34:
35: // Función D i b u j a r F i g u r a s obr ecar gada - toma dos v a l o r e s
36: // di buj a l a f i g u r a con base en l o s pa rámet ro s
37: void R e c t á n g u l o : : D i b u j a r F i g u r a ( i n t ancho, i n t a l t u r a ) c o n s t
38: {
39: f o r ( i n t i = 0; i < a l t u r a ; i+ + )
40: {
4 1: for (int j = 0; j < ancho; j++)
42: (
43: cout << " * " ;
44: }
45: cout << " \ n " ;
46: }
47: }
48:
F u n c io n e s a v a n z a d a s 295

49: // Programa c o n t r o l a d o r para mostrar l a s f u n ci o n es s ob r ec a rg ad a s


50 : i n t mam ( )
51: {
52: // í n i c i a l i z a r un r e ct á n g ul o con 30,5
53: R e c t á n g u l o e l R e c t ( 3 0 , 5);
54: cout << " D i b u j a r F i g u r a ( ): \ n “;
55: e l R e c t . D i b u j a r F i g u r a ();
56: cout << " \ n D i b u j a r F i g u r a ( 4 0 , 2): \n";
57: e l R e c t . D i b u j a r F i g u r a (40, 2);
58: r e t u r n 0;
59: }

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

Uso de valores predeterminados


Asi com o las funciones que no pertenecen a una clase pueden tener uno o más valores
predeterm inados, también las funciones m iem bro de una clase pueden tenerlos. Las mis­
mas reglas se aplican para declarar los valores p red eterm in ad o s, co m o se m uestra en el
listado 10 .2 .

Entrada L is ta d o 10.2 Uso de valores p re d e te rm in a d o s


1: //Listado 10.2 Valores predeterminados en funciones miembro
2:
3: ^include <iostream.h>
4:
5: // Declaración de la clase Rectángulo
6: class Rectángulo
7: {
8: public:
9: // constructores
10: Rectangulo(int ancho, int altura);
11: -Rectangulo(){}
12: void DibujarFigura(int unAncho, int unaAltura,
13: bool UsarValsActuales = false) const;
14: private:
15: int suAncho;
16: int suAltura;
17: };
18:
19: //Implementación del constructor
20: Rectángulo::Rectangulo(int ancho, int altura):
21 : suAncho(ancho), // inicializaciones
22: suAltura(altura)
23: {} // cuerpo vacio
24:
25:
26: // valores predeterminados usados para el tercer parámetro
27: void Rectángulo::DibujarFigura(int ancho, int altura,
28: bool UsarValActual) const
29: {
30: int imprimeAncho;
31 : int imprimeAltura;
32:
33: if (UsarValActual == true)
34: {
35: // usar los valores actuales de la clase
36: imprimeAncho = suAncho;
37: imprimeAltura = suAltura-
38: }
39: else
40: {
F u n c io n e s a v a n z a d a s 297

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

listado 10.2 reemplaza a la función sobrecargada Dibuj arFigura () con una


El

función sencilla con parámetros predeterminados. La función se declara en la


línea 12 para recibir tres parámetros. Los dos primeros, unAncho y unaAltura. son varia­
bles de tipo entero, y el tercero. UsarValsActuales. es un tipo bool que tiene false
como valor predeterminado.
La implementación para esta función un poco rara empieza en la línea 27. El tercer pa­
rámetro. U s a r V a l A c t u a l . es evaluado. Si es verdadero, se usarán las variables miembro
s uA n ch o y s u A l t u r a para asignar un valor a las variables locales impr imeAncho e
i m p r i m e A l t u r a . respectivamente.
298 Día 10

Si UsarValActual es falso, ya sea porque es su valor predeterminado o porque así lo


asignó el usuario, se utilizan los dos primeros parámetros para asignar un valor a
imprimeAncho e imprimeAltura.
Observe que si UsarValActual es verdadero, los valores de los otros dos parámetros se
ignoran por completo.
Las líneas 21 y 22, que inicializan variables de dalos miembro privados, se explican en
la sección “Inicialización de objetos" que se verá más adelante en este día.

Cómo elegir entre valores predeterminados


y funciones sobrecargadas
Los listados 10.1 y 10.2 logran lo mismo, pero las funciones sobrecargadas del listado 10.1
son más fáciles de entender y se utilizan con más naturalidad. Además, si se necesita
una tercera variación (tal vez el usuario quiera proporcionar el ancho o la altura, pero
no ambas) es fácil extender las funciones sobrecargadas. Sin embargo, el valor prede­
terminado pronto se volverá inusualmente complejo a medida que se agreguen nuevas
variaciones.

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

Entrada L is t a d o 1 0 .3 Sobrecarga del constructor


1: // L i s t a d o 10.3
2: // S obr ecar ga de c o n s t r u ct o re s
3:
4: //inelude <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 á n g u l o ( i n t ancho, i n t longi tud );
11 : -Rectángulo() {}
12: i n t ObtenerAncho() const
13: { r e t ur n suAncho; }
14: i n t Ob t en e r L o n g i t u d () const
15: { r e t ur n suLongitud; }
16: private:
17: i n t suAncho;
18: i n t suLo ng itu d;
19: >;
20:
21: R e c t á n g u l o : : R e c t á n g u l o ()
22: {
23: suAncho = 5;
24: s uLo ng itu d = 10;
25: }
26:
27: R e c t á n g u l o : : Rectángulo ( i nt ancho, int l ongi tud)
28: {
29: suAncho = ancho;
30: s uL o ng i tu d = l o n gi t u d;
31 : }
32:
i »n i m i n i
300 Día 10

L istado 10.3 continuación

33: int main()


34: {
35: Rectángulo Rectl ;
36: cout « "Ancho de Rectl:
37: cout « Rectl.ObtenerAncho() << endl;
38: cout « "Longitud de Rectl:
39: cout « Rectl.ObtenerLongitud() << endl;
40:
41: int unAncho, unaLongitud;
42: cout « "Escriba un ancho: ";
43: cin » unAncho;
44: cout « "\nEscriba una longitud: " ;
45: cin » unaLongitud;
46:
47: Rectángulo Rect2(unAncho, unaLongitud);
48: cout « "\nAncho de Rect2: “;
49: cout « Rect2.0btenerAncho() << endl;
50: cout « "Longitud de Rect2: ";
51: cout « Rect2.0btenerLongitud( ) « endl;
52: return 0;
53: }

Ancho de Rectl: 5
S a l id a Longitud de Rectl: 10
Escriba un ancho: 20

Escriba una longitud: 50


Ancho de Rect2: 20
Longitud de Rect2: 50

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

Después de los paréntesis de cierre de la lista de parámetros del constructor se escribe el


símbolo de dos puntos (:). Luego se escribe el nombre de la variable miembro y un par
de paréntesis. Dentro de los paréntesis se escribe la expresión que se va a utilizar para ini­
cializar esa variable miembro. Si existe más de una inicialización. se debe separar cada
una con una coma. El listado 10.4 muestra la definición de los constructores del listado
10.3. con la inicialización de las variables miembro en lugar de su asignación.
10

El archivo del listado 10.4 que viene en el C D -R O M no com pilará pues só lo es


Precaución
un a pieza de có d igo (segmento de código), no un program a.

L is t a d o 1 0 . 4 Un segm ento de código que muestra la inicializació n


Entrada de las variab les m iem bro

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

Uso del constructor de copia


A dem ás de proporcionar un co n stru cto r y un d estru cto r p red eterm in ad o s, el compilador
proporciona un constructor de co p ia p red eterm in ad o . Este co n stru c to r se llam a cada vez
que se crea la copia de un objeto.
C uando se pasa un objeto por valor, ya sea a una función o co m o el valor de retomo de
una función, se crea una co p ia tem p o ral de ese o b jeto . Si el o b je to es definido por el
usuario, se llam a al co n stru cto r de co p ia de la clase. Ayer, en los listados ó .5 y 9.6, vio
cóm o se crean las copias tem p o rales de las v ariab les co m u n es. Hoy veiá cóm o funciona
esto con los objetos.
Todos los con stru cto res de co p ia tom an co m o p arám etro una ic te ic n c iu a un objeto de la
m ism a clase. Es una b uena idea h acerla una re le ren cia co n stan te p o iq u e el constructor no
tendrá ciue alterar el o b jeto que se está pasando. Poi ejem p lo .

GATO(const GATO & elGato);


A quí, el constructor GATO tom a una referencia con stan te a un ob jeto GATO existente. El
objetivo del constructor de cop ia es crear una cop ia de e lG a to .
El constructor de copia predeterm inado sim plem ente co p ia cad a v aiiab le m iem bro del
objeto que se pasa com o parám etro a las variables m iem b ro del n uevo objeto. Esto se
conoce com o copia cié los datos m iem bro (o su p erficial), y au n q u e esto está bien para la
m ayoría de las variables m iem bro, no es adecuado para las v ariab les m iem bro que son
apuntadores a objetos que se encuentran en el heap.
U na copia superficial o de los datos m iem b ro co p ia en o tro o b je to los valores exactos
de las variables m iem bro de un objeto. Los a p u n tad o res de am b o s o b jeto s terminan
apuntando a la m ism a dirección de m em oria. U na co p ia p ro fu n d a co pia en memoria
recién asignada los valores asignados en el heap.
Si la clase GATO incluye una variable m iem bro llam ada suEdad que apunta a un entero en
el heap, el constructor de copia predeterm inado cop iará en la v ariable m iem bro suEdad
del nuevo GATO, la variable m iem bro suEdad del o bjeto GATO original. A hora los dos
objetos apuntarán a la m ism a dirección de m em oria, co m o se m uestra en la figura lO.l.

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.

La solución a este problema es crear su propio constructor de copia y asignar la memoria


según sus necesidades. Después de asignar la memoria, los viejos valores se pueden co­
piar en la nueva memoria. Esto se conoce como copia profunda. El listado 10.5 muestra
cómo hacer esto.

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

En las lincas 6 a 22 se declara la clase GATO. Observe que en la línea 9 se declara


A n á l is is
un constructor predeterminado, y en la línea 10 se declara un constructor de copia.
En las líneas 20 \ 21 s e declaran dos variables miembro, cada una como apuntador a un
entero. Por lo general, habría muy pocos motivos para que una clase guardara variables
miembro de tipo in t como apuntadores, pero se hizo esto para mostrar como se manejan
las variables miembro en el lieap.
El constructor predeterminado que está en las líneas 24 a 30 asigna espacio en el heap
para dos variables de tipo in t y luego les asigna valores.
El constructor de copia empieza en la línea 32. Observe que el parámetro es rhs. Es
común referirse al parámetro para un constructor de copia como rhs. que signitica "lado
derecho" (right-hand side). Al analizar las asignaciones de las líneas 36 y 37 se ve que el
objeto que se pasa como parámetro se encuentra al lado derecho del signo de igual. Esto
funciona así:
En las lineas 34 y 35 se asigna memoria en el heap. Luego, en las líneas .•>6 y a7 los valores
del GATO existente se asignan al valor que se encuentra en la nueva ubicación de memoria.
El parámetro r h s es un GATO que se pasa al constructor de copia como referencia constante.
Como un objeto de la clase GATO, r h s tiene todas las variables miembro de cualquier otro
objeto GATO.
Cualquier objeto GATO puede tener acceso a las variables miembro privadas de
cualquier otro objeto GATO; sin embargo, es una buena práctica de programación uti­
lizar métodos de acceso públicos siempre que sea posible. La función miembro
rhs . O b t e n e r E d a d ( ) regresa el valor guardado en la memoria a la que apunta la va­
riable miembro s u E d a d de rhs.
La figura 10.3 muestra un diagrama de lo que está ocurriendo aquí. Los valores a los que
apuntan las variables miembro del GATO existente se copian en la memoria asignada para
el nuevo GATO.

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

En la línea 50 se crea un GATO llamado pelusa. Se imprime la edad de pelusa y luego,


en la línea 54, se establece su edad en 6. En la línea 56 se crea un nuevo GATO llamado
silvestre, usando el constructor de copia y pasando a pelusa c o m o parámetro. Si
pelusa se hubiera pasado como parámetro a una función, el compilador hubiera hecho
la misma llamada al constructor de copia.
En las líneas 58 y 60 se imprimen las edades de ambos objetos GATO. Evidentemente,
silvestre tiene la edad de pelusa, es decir 6. no la edad predeterminada de 5. En la
línea 62 se establece la edad de pelusa en 7, y luego se imprimen otra ve/ las edades.
Esta vez la edad de pelusa es 7, pero la edad de silvestre aún es 6. lo que demuestra
que se guardan en áreas separadas de memoria.
Cuando los objetos GATO quedan fuera de alcance, sus destructores se invocan automática­
mente. La implementación del destructor GATO se muestra en las líneas 40 a 46. En ambos
apuntadores, suEdad y suPeso, se llama a delete, lo que regresa al heap la memoria
asignada. Además, por seguridad, los apuntadores se reasignan a NULL.

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.

E n t r a d a | L is t a d o 10.6 La clase Contador


1: // Listado 10.6
2: // La clase Contador
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:
F u n c io n e s a v a n za d a s 307

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.

Sobrecarga del operador de prefijo


Los operadores de prefijo se pueden sobrecargar al declarar funciones con la siguiente
forma:
tipoDeRetorno operator op (parámetros)
Aquí, op es el operador a sobrecargar. Por lo tanto, el operador ++ se puede sobrecargar
con la siguiente sintaxis:
void operator++ ()

El listado 10.8 muestra esta alternativa.

Entrada L is t a d o 1 0 .8 Sobrecarga del o p e ra d o r ++

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

En la línea 17 se sobrecarga el operador ++ y se utiliza en la línea 35. Esto es


A nálisis
lo más cercano a la sintaxis que se esperaría con el objeto Con ta dor . En este
punto podría considerar colocar las capacidades adicionales por las que se creó C o n t a d o r
en primer lugar, como detectar el momento en el que C o n t a d o r sobrepase su tamaño
máximo.

Sin embargo, hay un defecto considerable en la manera en que se escribió el operador de


incremento. Si quiere colocar el C o n t a d o r en el lado derecho de una asignación, esto
tallará. Por ejemplo:
Contador a ++1;
310 Día 10

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

Cómo regresar tipos en funciones


con operadores sobrecargados
E videntem ente, lo que usted necesita es regresar un objeto Contador para que pueda
asignarlo a otro objeto Contador. ¿Cuál objeto debe regresar? Un m étodo sería crear un
objeto tem poral y regresar ese objeto. El listado 10.9 ejem p lifica este método.

E ntrad a L is t a d o 10.9 Cómo reg resar un o b je to te m p o ra l


1: // Listado 10.9
2: // operator++ regresa un objeto temporal
3:
4: /¿include <iostream.h>
5:
6: class Contador
7: {
8: public:
9: Contador();
10: ~Contador(){}
11: int ObtenerSuVal()const
12: { return suVal; }
13: void AsignarSuVal(int x)
14: { suVal = x; }
15: void IncrementoO
16: { ++suVal; }
17: Contador operator++ ();
18: private:
19: int suVal;
20: };
21 :
22: Contador::Contador():
23: suVal(0)
24: {}
25:
26: Contador Contador::operator++()
27: {
28: ++suVal;
29: Contador temp;
30:
31: temp.AsignarSuVal(suVal);
32: return temp;
33: }
34:
35: int main()
36: {
F u n c io n e s a v a n z a d a s 311

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

En esta versión se ha declarado el operador ++ en la línea 17 para que regrese un


objeto de tipo C on t a d o r . En la línea 29 se crea una variable temporal llamada
temp, y su valor se asigna de forma que eoncuerde con el del objeto actual. Esa variable
temporal se regresa y se asigna inmediatamente a a en la línea 46.

Cóm o regresar objetos temporales sin nombre


En realidad, no hay necesidad de nombrar el objeto temporal creado en la línea 29. Si
C o n t a d o r tuviera un constructor que tomara un valor, usted podría simplemente regresar
el resultado de ese constructor como valor de retorno del operador de incremento. El lis­
tado 10.10 ejemplifica esta idea.

L is t a d o 1 0 . 1 0 Cóm o regresar un objeto tem poral sin nom bre

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

18: Contador operator++ ();


19: prívate:
20: int suVal;
21: };
22:
23: Contador::Contador():
24: suVal(0)
25: {}
26:
27: Contador::Contador(int val):
28: suVal(val)
29: {>
30:
31: Contador Contador::operator++()
32: {
33: ++suVal;
34: return Contador (suVal);
35: }
36:
37: int main()
38: {
39: Contador i;
40: cout « "El valor de i es
41 : cout « i.ObtenerSuVal() << endl;
42: i. Incremento();
43: cout « "El valor de i es
44: cout « i.ObtenerSuVal() « endl;
45: ++i;
46: cout « "El valor de i es
47: cout « i.ObtenerSuVal() « endl;
48: Contador a = ++i;
49: cout « "El valor de a: " << a.ObtenerSuVal();
50: cout « " y de i: " « i.ObtenerSuVal() << endl;
51 : return 0;
52: >

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

En la línea 10 se declara un nuevo constructor, el cual toma un in t como pará­


A nálisis
metro. La implementación se encuentra en las líneas 27 a 29. y en ésta se inicializa
a suV al con el valor que recibe.

Ahora la implementación de operator++ está simplificada. En la línea 33, suVal se in­


crementa. Luego, en la línea 34 se crea un objeto Contador temporal, se inicializa con
el valor contenido en suVal, y luego se regresa como resultado de operator+ +.
F u n d o n e s avanzadas 313

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.

Uso del apuntador t h is


El apuntador this. como se dijo en el día 9. “Referencias”, se puede pasar a la función
miembro o perator ++ al igual que a todas las demás funciones miembro. El apuntador
this apunta a i. y si es desreferenciado, regresará el objeto i. el cual ya tiene el valor
correcto en su variable miembro suVal. El listado 10.11 ilustra el regreso del apuntador
this desreferenciado. evitando así la creación de un objeto temporal innecesario.

Entrada L is t a d o 1 0 . 1 1 Regreso del apuntador t h 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

32: int main()


33: {
34: Contador i;
35: cout « "El valor de i es
36: cout « i.ObtenerSuVal() « endl;
37: i.Incremento();
38: cout « "El valor de i es
39: cout « i.ObtenerSuVal() << endl;
40: ++i;
41: cout « "El valor de i es
42: cout « i.ObtenerSuVal() « endl;
43: Contador a = ++i;
44: cout « "El valorde a: " « a.ObtenerSuVal() ;
45: cout « " y de i: " « i.ObtenerSuVal() << endl;
46: return 0;
47: }

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

Sobrecarga del operador de posfijo


Hasta ahora, ha sobrecargado el operador de prefijo. ¿Qué pasa si quiere sobrecargar el
operador de incremento de posfijo? El compilador tiene un problema aquí: ¿Cómo distin­
guir entre prefijo y posfijo? Por convención, para la declaración del operador se propor­
ciona una variable de tipo entero como parámetro. El valor del parámetro se ignora; sólo
es una indicación de que es el operador de posfijo. Esto se muestra en el listado 10.12.

Cuáles son las diferencias entre prefijo y posfijo


Antes de poder escribir el operador de posfijo, debe entender qué diferencia existe entre
éste y el operador de prefijo. En el día 4, “Expresiones e instrucciones , analizamos esto
con detalle (vea el listado 4.3).
F u n c io n e s a v a n z a d a s 315

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.

L is t a d o 10 .12 O p erad o res de 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

25: const Contador & Contador::operator ++()


26: {
27: ++suVal;
28: return *this;
29:
30:
31 : const Contador Contador::operator++(int x)
32: {
33: Contador temp(*this);
34: ++suVal;
35: return temp;
36:
37:
38: int main()
39: {
40: Contador i;
41 : cout « "El valor de i es
42: cout « i.ObtenerSuVal() « endl;
43: i++;
44: cout « "El valor de i es ";
45: cout « i.ObtenerSuVal() « endl;
46: ++i;
47: cout « “El valor de i es ";
48: cout « i.ObtenerSuVal() « endl;
49: Contador a = ++i;
50: cout « "El valor de a: " « a.ObtenerSuVal() ;
51 : cout « " y de i: " « i.ObtenerSuVal() << endl;
52: a = i++;
53: cout « "El valor de a: " << a.ObtenerSuVal( );
54: cout « " y de i: " « i.ObtenerSuVal() « endl;
55: return 0;
56: }

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

A nálisis El operador de posfijo se declara en la línea 16 y se implementa en las líneas 31


a 36. Observe que el prototipo del operador de prefijo (línea 15) no incluye el
indicador de entero (x), sino que se utiliza con su sintaxis normal. El operador de posfijo
sí utiliza ese indicador (x) para establecer que es el posfijo y no el prefijo. Sin embargo,
el valor del indicador (x) nunca se utiliza.
F u n c io n e s a v a n z a d a s 317

Sobrecarga de o peradores: operadores unarios


Un operador sobrecargado se declara de la misma forma que una función. Utilice la pala­
bra reservada operator, seguida del operador que se va a sobrecargar. Las funciones de
operadores unarios no llevan parámetros, con la excepción del incremento y decremento
de posfijo, que toman un entero como indicador.
Ejemplo 1
const Contador& Contador::operator++ ();
Ejemplo 2
Contador Contador::operator-- (int);

Uso del operador de suma


El operador de incremento es un operador unario. Funciona sólo con un objeto. El opera­
dor de suma ( + ) es un operador binario, en el que se involucran dos objetos. ¿Cómo se
implementa la sobrecarga del operador + para Con ta dor ?
El objetivo es poder declarar dos variables Contador y luego sumarlas, c o m o en el
siguiente ejemplo:
Contador varUno, varDos, varTres;
VarTres = VarUno + VarDos;

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.

Las técnicas utilizadas para sobrecargar el o p e ra d o r ++ se pueden aplicar a los


demás operadores unarios, como el o p e r a d o r -- .

Sobrecarga de operadores: operadores binarios


Los operadores binarios se crean igual que los operadores unarios, sólo que los binarios sí
llevan un parámetro. El parámetro es una referencia constante a un objeto del mismo tipo.
Ejemplo 1

Contador Contador::operator+ ( const Contador & rhs );


Ejemplo 2
Contador Contador::operator- ( const Contador & rhs );

Cuestiones adicionales relacionadas


con la sobrecarga de operadores
Los operadores sobrecargados pueden ser funciones m iem bro, com o se describe en esta
lección, o funciones no m iem bro. Estas últim as se describ irán en el día I4. “Clases y
funciones esp eciales”, cuando hablem os sobre las funciones am igas.
Los únicos operadores que deben ser m iem bros de la clase son los o peradores de asig­
nación ( = ). de subíndice de arreglosí [ ]). de llam ada a función (( ) ) y de flecha (->).
El op erad o r [ ] se verá en el día I I. “ H erencia” , al hablar sobre los arreglos.
Funciones avanzadas 321

Limitaciones de la sobrecarga de operadores


Los operadores de tipos de datos integrados (como i n t ) no se pueden sobrecargar. El
orden de precedencia no se puede cambiar, y la aridad del operador, es decir, si es unario
o binario, tam poco se puede cambiar. No puede crear nuevos operadores, por lo que no
puede declarar ** com o operador "de potencia” .
La aridad se refiere a la cantidad de términos que se utilizan en el operador. Algunos ope­
radores de C + + son unarios y sólo utilizan un término (miValor++). Otros son binarios
y utilizan dos térm inos (a + b). Sólo un operador es ternario y utiliza tres térm inos.
El operador ?: se denom ina comúnmente operador ternario porque es el único operador
ternario en C + + ( a > b ? x : y).

Qué se debe sobrecargar


La sobrecarga de operadores es uno de los aspectos de C++ de los que más abusan los pro­ 10
gramadores novatos. Es tentador crear nuevos e interesantes usos para algunos de los opera­
dores más confusos, pero esto produce invariablemente un código confuso y difícil de leer.
Claro que hacer que el operador + reste y el operador * sume podría ser divertido, pero
ningún program ador profesional haría eso. El mayor peligro recae en el bien intenciona­
do pero idiosincrásico uso de un operador (usar + para indicar que se deben concatenar
una serie de letras, o usar / para indicar que se debe dividir una cadena de caracteres).
Existe un buen m otivo para considerar estos usos, pero hay un mejor motivo para pro­
ceder con cautela. Recuerde, el objetivo de la sobrecarga de los operadores es aum entar
su uso y su com prensión.

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 .

Uso del operador de asignación


La cuarta y última función que proporciona el compilador, si usted no especifica una, es
el operador de asignación ( o p e r a t o r = ( )). Este operador se llama siempre que se asigna
algo a un objeto. Por ejemplo:
GATO gatoUno(5,7) ;
GATO gatoDos(3,4);
// ... aquí puede ir más código
gatoDos = gatoUno;
Día 10

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

12: { return *suEdad; }


13: int ObtenerPeso() const
14: { return *suPeso; }
15: void AsignarEdad(.int edad)
16: { *suEdad = edad; }
17: GATO & operator=(const GATO &) ;
18: prívate:
19: int * suEdad;
20: int * suPeso;
21: };
22:
23: GATO::GATO()
24: {
25: suEdad = new int;
26: suPeso = new int;
27: *suEdad = 5;
28:
29: }
*suPeso = 9; 10
30:
31: GATO & G A T O ::operator=(const GATO & rhs)
32: {
33: if (this == &rhs)
34: return *this;
35: *suEdad = rhs.ObtenerEdad();
36: *suPeso = rhs.ObtenerPeso();
37: return *this;
38: }
39:
40:
41 : int m a i n ()
42: {
43: GATO pelusa;
44:
45: cout << "edad de pelusa: ";
46: cout << pelusa.ObtenerEdad() « endl;
47: cout << "Estableciendo edad de pelusa en 6...\n";
48: pelusa.AsignarEdad(6);
49: GATO bigotes;
50: cout << "edad de bigotes:
51: cout << bigotes.ObtenerEdad() « endl;
52: cout << "copiando pelusa a bigotes...\n";
53: bigotes = pelusa;
54: cout << "edad de bigotes: ";
55: cout << bigotes.ObtenerEdad() « endl;
56: return 0;
57: }

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

E! listado 10.15 utili/a de n u e v o la clase G A T O y o m ite el c o n s tru c to r y el des­


A n á l is is
tructor de copia para aho rra r e sp acio . En la línea 17 se de c la ra el operador de
asignación, y se define en las líneas 31 a 3X.

En la línea 33 se prueba el o bjeto actual (el G A T O q u e va a ser a s ig n a d o ) para ver si es


igual al GATO al que se va a asignar. E sto se ha c e c o m p r o b a n d o si la dirección de rhs es
la m ism a que la dirección g u a rd a d a en el a p u n ta d o r t h i s .

Claro que el ope rad or relaciona! de igualdad ( = = ) t a m b ié n se pu ed e sobrecargar, lo que le


permite de termin ar por usted m i s m o lo q ue significa q ue sus ob jetos sean iguales.

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.

¡El listado 10.16 no com pilará!


Precaución

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

1st 10-16.c x x : In function 'int main ()’:


S a l id a lstl0-16.cxx:26: conversion from 'int' to non-scalar type Contador'
requested

La clase Contador declarada en las líneas 6 a 17 sólo tiene un constructor p rede­


A n á l is is
term inado. No declara un método específico para convertir un int en un objeto
Contador, por lo que la línea 26 produce un error de compilación. El com pilador no pue­ 10
de saber, a m enos que usted se lo indique, que si hay un int. debe asignar ese valor a la
variable m iem bro suVal.

La m ay o ría de los c o m piladores proporciona un mensaje más corto, algo así c o m o


“ U n a b le to c o n v e n int to C ontador" o "conversión from 'i n t' to non-scalar type
' C o n ta d o r' requested".

El listado 10.17 c o rrig e esto m ediante la creación de un operador de conversión: un


c o n s tru c to r q u e to m a un i n t y produce un objeto C o n ta d o r.

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

A n á l isis El cam bio im portante se e n c u en tra en la lín e a l(). e n d o n d e el constructor se


sobrecarga para to m a r un int, y en las líneas 24 a 26. en las q u e se implementa
el constructor. El efecto de este con stru c to r es c r e a r un C o n t a d o r a p artir de un int.

C on esto, el co m p ila d o r puede llam ar al c o n s tr u c to r q u e t o m a un i n t c o m o argumento.


H e aquí la form a de hacerlo:

P aso l: C rear un co n ta d o r llam ado elCtr.

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 aso 2: A sig n a r a elCtr el valor de elShort.

¡Pero elShort es de tipo int, no un c o n ta d o r ! P rim e ro te n e m o s q u e convertirlo en un


Contador. El com pilador tratará de hacer ciertas c o n v e rs io n e s p o r usted en forma automá­
tica. pero tiene que enseñarle cóm o. Para esto, d e b e c re a r un c o n s tr u c to r para Contador
que. poi ejem plo, tom e c o m o su único p a rám etro un int:
class Contador
{
Contador (int x );
// . .
};
Funciones avanzadas 327

Este c o n s tr u c to r cre a ob jeto s C o n t a dor a partir de valores de tipo int. H ace e s to m e d i a n ­


te la c re a c ió n de un c o n ta d o r tem poral sin nombre. Para fines ilustrativos, s u p o n g a q u e
el o bjeto tem p o ral C o n t a d o r que cre a m o s a partir del tipo int se llama fueShort.

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.

Para c o m p r e n d e r esto, debe e n tender que T O D A S las sobrecargas de operadores funcionan


de la m is m a form a: se d e c la ra un o p e ra d o r sobrecargado mediante el uso de la p alabra
rese rv ad a operator. C o n los operadores binarios (com o = o + ) la variable del lado d e r e ­ 10
c h o se c o n v ie rte en el parám etro. Esto lo realiza el constructor. Por lo tanto.
a = b;

se c o n v ie rte en
a .operator=(b ) ;

Sin e m b a rg o , ¿ q u é oc u rre si trata de invertir la asignación con lo siguiente?


1: Contador e lCtr(5);
2: int elShort = elCtr;
3: cout << "elShort : " « elShort « endl;

De n u evo, e sto g e n e ra rá un erro r de compilación. Aunque el com pilador a hora sabe


c ó m o c re a r un C o n t a d o r a partir de un int, no sabe cóm o invertir el proceso (a m e n o s
que usted le in d iq u e cóm o).

C óm o crear sus propios operadores de conversión


Para reso lv e r éste y otros p roblem as similares, C++ proporciona operadores de c o n v e r ­
sión que se pu ed en a g re g a r a una clase. Esto permite que la clase especifique c ó m o h a c e r
c o n v e rsio n e s im plícitas a tipos de datos integrados. El listado 10.18 m uestra esto. Sin
e m bargo, hay una obse rv a c ió n : los operadores de conversión no especifican un v a lo r de
retorno, au n q u e rea lm e n te sí regresan un valor convertido.

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

En la línea 16 se declara el operador de conversión. O bserve que no tiene valor


de retorno. La im plem entación de esta función se encuentra en las líneas 29 a 32.
La línea 31 regresa el valor de suVal convertido en un i n t .
A hora el com pilador sabe cóm o convertir variables de tipo int en objetos Contador y
viceversa, y se pueden asignar entre sí con libertad.
Funciones avanzadas

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

P ¿Puede una función sobrecargada tener un p ará m etro p red eterm in ad o ?


R Sí. No hay razón alguna para no combinar estas poderosas características. Una o
más de las funciones sobrecargadas pueden tener sus propios valores predetermi­
nados, siguiendo las reglas comunes para variables predeterminadas en cualquier
función.
P ¿Por qué algunas funciones miembro se defínen d en tro de la declaración de la
clase y otras no?
R Definir la implementación de una función miembro dentro de la declaración la
convierte en función en línea. Por lo general, esto se realiza sólo si la función es
extremadamente sencilla. Tome en cuenta que también puede convertir una función
miembro en función en línea si utiliza la palabra reservada inline, incluso si la
función se declara fuera de la declaración de la clase.

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.

Cómo crear clases que representen animales


Para facilitar la discusión de la derivación y la herencia, esta lección se enfocará en las
relaciones existentes entre una variedad de clases que representan animales. Imagine que le
han pedido que diseñe un juego para niños (una simulación de una granja).
A su tiempo desarrollará un conjunto completo de animales de granja, incluyendo caballos,
vacas, perros, gatos, ovejas, etc. Creará métodos para estas clases, de forma que puedan
actuar como los niños podrían esperar, pero por ahora puede rellenar cada método con una
simple instrucción de impresión.
Rellenar una función significa que usted escribe sólo lo suficiente para mostrar que la
función fue llamada, dejando los detalles para cuando tenga más tiempo. Siéntase libre
de extender el poco código proporcionado en esta lección para hacer que los animales actúen
en forma más realista.

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.

E ntrada L istado 11.1 H e re n c ia s im p le

1: //Listado 11.1 Herencia simple


2:
3: //include <iostream.h>
4:
5: enum RAZA { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
6:
7: class Mamífero
8: {
9: public:
10: //constructores
11: Mamifero();
12: -MamiferoO;
13: //métodos de acceso
14: int ObtenerEdad() const;
15: void AsignarEdad(int);
16: int ObtenerPeso() const;
17: void AsignarPeso();
18: //Otros métodos
19: void Hablar() const;
20: void Dormir() const;
21: protected:
22: int suEdad;
23: int suPeso;
24: };
25:
26: class Perro : public Mamifero
27: {
28: public:
29: // Constructores
30: Perro();
31: -Perro();
32: // Métodos de acceso
33: RAZA ObtenerRaza() const;
34: void AsignarRaza(RAZA);
35: // Otros métodos
36: void MoverCola();
37: void PedirAlimento();
38: protected:
39: RAZA suRaza;
40: };
Herencia 337

El programa anterior no tiene salida ya que es sólo un conjunto de declaraciones de


Salida
clases sin sus implementaciones. No es un programa completo. Sin embargo, hay
mucho que ver aquí.
En las lincas 7 ti 24 se declara la clase Mamífero. Observe que en este ejem plo
Análisis
Mamífero no se deriva de ninguna otra clase. En el mundo real, los mamíferos sí se
derivan de otra clase ( es decir, los mamíferos son tipos de animales). En un programa de
C++ sólo se puede representar una fracción de la información que se tiene acerca de un
objeto dado. La realidad es dem asiado compleja como para capturarla en su totalidad,
por lo que cada jerarquía de C'++ es una representación arbitraria de los datos disponibles.
El truco de un buen diseño es representar las áreas de importancia en una forma que se
asemeje a la realidad lo más fielmente posible.
La jerarquía tiene que empezar en algún lado: este programa empieza con la clase Mamífero.
Debido a esta decisión, algunas variables miembro que bien podrían pertenecer a una clase
base más alta, ahora se representan aquí. Es evidente que todos los animales tienen una edad
y un peso, por ejemplo, si Mamífero se deriva de Animal, es de esperarse que herede esos
atributos. C om o no es a s í . los atributos aparecen en la clase Mamífero.
Para mantener el programa sencillo y manejable, sólo se han colocado seis métodos en la
clase Mamífero (cuatro métodos de acceso, así como Hablar () y Dormir ()).

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.

Comparación entre privado y protegido


Tal vez se haya dado cuenta que se introdujo una nueva palabra reservada de acceso,
protected, en las líneas 21 y 38 del listado 11.1. Anteriormente, los datos de una clase
se habían declarado como privados. Sin embargo, los miembros privados no están dispo­
nibles para las clases derivadas. Podría hacer que suEdad y suPeso fueran públicas, pero
eso no es deseable. No queremos que otras clases tengan acceso directo a estos datos
miembro.
|338 Día 11

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

1: //Listado 11.2 Uso de un objeto derivado


2:
3: #include <iostream.h>
4:
5: enum RAZA { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
6:
7: class Mamifero
8: {
9: public:
10: // constructores
11: Mamifero() : suEdad(2), suPeso(5){}
12: - Mamifero(){)
13: //métodos de acceso
14: int ObtenerEdad() const
15: { return suEdad; )
16: void AsignarEdad(int edad)
17: { suEdad = edad; }
18: int ObtenerPeso() const
19: { return suPeso; }
20: void AsignarPeso(int peso)
21: { suPeso = peso; }
22: //Otros métodos
23: void Hablar()const
Herencia 339

24: { cout << "¡Sonido de mamifero! \ n " ; }


25: void Dormir()const
26: { cout << "shhh. Estoy durmiendo.\n"; }
27: protected:
28: int suEdad;
29: int suPeso;
30: };
31 :
32: class Perro : public Mamifero
33: {
34: public:
35: // Constructores
36: Perro() : suRaza(GOLDEN){}
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: 11
49: RAZA suRaza;
50: };
51 :
52: int main()
53: {
54: Perro fido;
55: fido.Hablar();
56: fido.MoverCola();
57: cout << "fidotiene
58: cout << fido.ObtenerEdad() << " años de edad\n";
59: return 0;
60: }

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

Entrada L istado 11.3 L la m a d a s a lo s c o n s t r u c t o r e s y d e s t r u c t o r e s

1: // Listado 11.3 Llamadas a los constructores y destructores.


2:
3: #include <iostream.h>
4:
5: enum RAZA { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
6:
7: class Mamifero
8: {
9: public:
10: // constructores
11: MamiferoO;
12: - Mamifero()i
13: //métodos de acceso
14: int ObtenerEdad() const
15: { return suEdad; }
16: void AsignarEdad(int edad)
17: { suEdad = edad; }
18: int ObtenerPeso() const
19: { return suPeso; >
20: void AsignarPeso(int peso)
21: { suPeso = peso; }
22: //Otros métodos
23: void Hablar() const
24: { cout « "ISonido de mamifero!\n"; }
25: void Dormir() const
26: { cout « "shhh. Estoy durmiendo.\n"; }
27: protected:
28: int suEdad;
29: int suPeso;
30: };
31:
32: class Perro : public Mamifero
Herencia

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.

Paso de argumentos a los constructores base


Es posible que usted necesite sobrecargar el constructor de Mamífero para que tenga una
edad específica, y sobrecargar el constructor de Perro para que tenga una raza. ¿Cóm o pasa
los parámetros de edad y peso al constructor adecuado de Mamífero? ¿Q u é pasa si los
objetos Perro quieren inicializar su peso, pero los objetos Mamífero no quieren hacerlo?
La inicialización de la clase base se puede realizar durante la in icializació n de la clase,
escribiendo el nombre de la clase base, seguido de los parám etros que espera esa clase base.
El listado 11.4 muestra esto.

Entrada L istado 11.4 Sobrecarga de constructores d e clases d e r iv a d a s

1: //Listado 11.4 Sobrecarga de constructores de clases derivadas


2:
3: #include <iostream.h>
4:
5: enum RAZA { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
6:
7: class Mamífero
8: {
9: public:
10: // constructores
11: MamiferoO;
12: Mamifero(int edad);
13: -MamiferoO;
14: //métodos de acceso
15: int ObtenerEdad() const
16: { return suEdad; }
17: void AsignarEdad(int edad)
18: {suEdad = edad; }
19: int ObtenerPeso() const
20: {return suPeso; }
21: void AsignarPeso(int peso)
22: {suPeso = peso; }
Herencia 343

23: //Otros métodos


24: void Hablar() const
25: { cout << "¡Sonido de mamífero!\n"; }
26: void Dormir() const
27: { cout << "shhh. Estoy durmiendo.\n°; }
28: protected:
29: int suEdad;
30: int suPeso;
31: };
32:
33: class Perro:public Mamífero
34: {
35: public:
36: // Constructores
37: Perro();
38: Perro(int edad);
39: Perro(int edad, int peso);
40: Perro(int edad, RAZA raza);
41: Perro(int edad, int peso, RAZA raza);
42: -Perro() ;
43: // Métodos de acceso
44: RAZA ObtenerRaza() const
45: { return suRaza; }
46:
47:
void AsignarRaza(RAZA raza)
{ suRaza = raza; }
11
48: // Otros métodos
49: void MoverCola() const fe
50: { cout << "Moviendo la cola...\n"; }
51: void PedirAlimento() const
52: { cout << "Pidiendo alimento...\n"; }
53: private:
54: RAZA suRaza;
55: };
56:
57: Mamifero::Mamífero():
58: suEdad( 1 ) ,
59: suPeso(5)
60: {
61: cout << "Constructor de Mamífero...\n";
62: }
63:
64: Mamífero::Mamifero(int edad):
65: suEdad(edad) ,
66: suPeso(5)
67: {
68: cout << "Constructor de Mamifero(int). ..\n";
69: }
70:
71: Mamifero::-Mamifero()
72: {
73: cout << "Destructor de Mamifero...\n";
74: }
continúa
|344 Día 11

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

127: cout << "yorkie tiene


128: cout << yorkie.ObtenerEdad() << años de edad\n";
129: cout << "dobbie pesa
130: cout << dobbie.ObtenerPeso() << libras\n";
131: return 0;
132: }

La salida se ha n u m e rad o a fin de que en el análisis se te n ga una referencia


para cada linea.

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

En el listado l l .4 se ha sobrecargado el constructor de Mamifero (línea 12) para que


A n á l is is
tome un entero, que es la edad del Mamifero. La implementación de las líneas 64
a 69 inicializa suEdad con el valor que se pasa al constructor, e inicializa suPeso con 5.

Perro ha sobrecargado cinco constructores en las líneas 37 a 41. El primero es el constructor


predeterminado. El segundo toma la edad, que es el mismo parámetro que toma el c o n stru c ­
tor de Mamifero. El tercer constructor toma la edad y el peso, el cuarto tom a la edad y la
raza, y el quinto tom a la edad, el peso y la raza.
Observe que en la línea 77. el constructor predeterminado de Perro llama al constructor
predeterminado de Mamifero. Aunque no es estrictamente necesario hacer esto, sirve c o m o
información que se haya intentado llamar al constructor base, el cual no tom a parámetros.
346 Día 11

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

1: //Listado 11.5 Redefinición de un método


2: // de la clase base en una clase derivada
3:
4: #include <iostream.h>
5:
6: enum RAZA { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
7:
8: class Mamifero
9: {
10: public:
11: I I constructores
12: Mamifero()
13: { cout << "Constructor de Mamifero...\n"; }
14: -Mamifero()
15: { cout << "Destructor de mamifero...\n"; }
16: //Otros métodos
17: void Hablar()const
18: { cout << "¡Sonido de mamifero!\n"; }
19: void Dormir()const
20: { cout << "shhh. Estoy durmiendo.\n"; }
21 : protected:
22: int suEdad;
23: int suPeso;
24: };
25:
26: class Perro : public Mamifero
27: {
28: public:
29: // Constructores
30: Perro()
31: { cout « "Constructor de Perro...\n"; }
32: ~Perro()
33: { cout << "Destructor de Perro...\n"; }
34: // Otros métodos
35: void MoverCola() const
36: { cout << "Moviendo la cola...\n"; }
37: void PedirAlimento() const
38: { cout << "Pidiendo alimento...\n"; }
39: void Hablar() const
40: { cout « "¡Guau!\n"; }
41: private:
42: RAZA suRaza;
43: };
44:
45: int main()
46: {
47: Mamifero animalGrande;
48: Perro fido;
49:
50: animalGrande.Hablar() ;
51: fido.Hablar() ;
52: return 0;
53: }
|348 Día 11

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.

Sobrecarga en comparación con redefinición


Estos términos son similares, y hacen cosas similares. A l so b re cargar un m é to d o , se crea más
de un método con el mismo nombre, pero con diferente firm a. A l re d e fin ir un m é todo, se
crea un m étodo en una clase derivada con el mism o nom bre qu e un m é to d o d e la clase
base, y con la misma firma.

Cómo ocultar el método de la clase base


En el listado anterior, el método Hablar() de la clase Perro oculta al método de la clase
base. Esto es lo que se quiere, pero se pueden tener resultados inesperados. Si Mamífero tie­
ne un método llamado Mover(), el cual está sobrecargado, y Perro redefine ese método, el
método de Perro ocultará todos los métodos de la clase Mamífero que tengan ese nombre.
Si Mamífero sobrecarga a Mover() con tres métodos (uno que no lleve parám etros, uno
que tome un entero y otro que tome un entero y una dirección) y Perro redefine sólo el
método Mover () que no lleva parámetros, no será fácil tener acceso a los otros dos méto­
dos usando un objeto Perro. El listado 11.6 ilustra este problema.

Entrada L istado 11.6 Ocultamiento de métodos


1: //Listado 11.6 Ocultamiento de métodos
2:
3: #include <iostream.h>
4:
5: class Mamífero
Herencia 349

6: {

7: public:
8: void Mover() const
9: { cout << "Mamífero se mueve un paso\n"; }
10: void Mover(int distancia) const
11: {

12: cout << "Mamífero se mueve


13: cout << distancia <<" pasos.\n";
14: }
15: protected:
16: int suEdad;
17: int suPeso;
18: };
19:
20: class Perro : public Mamifero
21: {
22: public:
23: // ¡Otros compiladores tal vez emitan una advertencia
24: // de que se está ocultando una función!
25: void Mover() const
26: { cout << "Perro se mueve 5 pasos.\n"; }
27: };
28:
29: int main()
30: {
31: Mamifero animalGrande;
32: Perro fido;
33:
34: animalGrande.Mover();
35: animalGrande.Mover(2);
36: fido.Mover();
37: // fido.Mover(10) ;
38: return 0;
39: }

Mamifero se mueve un paso


S a l id a Mamifero se mueve 2 pasos.
Perro se mueve 5 pasos.

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.

Sin embargo, la línea 37 se convirtió en comentario porque produce un error en tiempo


de compilación. Aunque la clase Perro podría haber llamado al método Mover ( i n t ) si
no hubiera redefinido la versión del Mover () que no lleva parámetros, ahora que lo ha
hecho debe redefinir ambos si quiere usarlos. De no ser así, ocultará el método que no
redefina. Esto es un recordatorio de la regla que establece que si usted proporciona un
constructor, el compilador no proporcionará un constructor predeterminado. Los compi­
ladores GNU producen el siguiente mensaje si no convierte la línea 37 en comentario:
350 Día 11

lst11-06.cxx: In function 'int main()':


lst11 -06.cxx:37: too many arguments for method void Perro: :Mover() const'
La regla es ésta: cuando redefine cualquier método sobrecargado, las dem ás sobrecargas
de ese método quedan ocultas. Si no quiere que se oculten, debe redefinirlas a todas.
Es un error común ocultar un método de la clase base cuando se trata de redefínirlo, al
olvidar incluir la palabra reservada const. const es parte de la Firma, y al om itirla cam­
bia la firma y, por consecuencia, se oculta el método en lugar de redefinirlo.

Redefinición en comparación con ocultamiento


En la siguiente sección se describen los m étodos virtuales. La re d e fin ició n d e u n m é to d o
virtual soporta el polimorfismo (si se oculta, se debilita el p o lim o rfism o ). V e rá m á s sobre
esto muy pronto;

Cómo llamar al método base


Si ha redefinido el método base, aún puede llamarlo especificando com pletam ente el nom­
bre del método. Esto se hace escribiendo el nombre base, seguido de dos símbolos de dos
puntos (::) y del nombre del método; por ejemplo: Mamif ero::Mover ().
Se podría modificar la línea 37 del el listado 11.6 para que pudiera compilar, escribiendo lo
siguiente:
37: fido.Mamifero::Mover(10);
Esto llama explícitamente al método de Mamif ero. El listado 11.7 ilustra detalladamente
esta idea.

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

16: int suEciad;


17: int suPeso;
18: };
19:
20: class Perro : public Mamífero
21: {
22: public:
23: void Mover()const;
24: };
25:
26: void Perro::Mover() const
27: {
28: cout << "En perro se mueve...\n";
29: Mamífero::Mover(3);
30: }
31 :
32: int main()
33: {
34: Mamífero animalGrande;
35: Perro fido;
36:
37: animalGrande.Mover(2);
38: fido.Mamífero::Mover(6);
39: return 0;
40: }

Mamífero se mueve 2 pasos.


S a l id a Mamífero se mueve 6 pasos.

En la línea 34 se crea un Mamífero llamado animalGrande, y en la línea 35 se crea


un Perro llamado fido. La llamada al método de la línea 37 invoca al método
Mover() de Mamífero, el cual toma un int como parámetro.

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

D E B E extender la funcionalidad de clases ya NO D EB E ocultar una función de la clase


probadas m ediante la derivación. base cambiando la firma de la función.
D E B E cam biar el com portam iento de
ciertas funciones de la clase derivada,
redefiniendo los m étodos de la clase base.
352 Día 11

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.

Entrada L istado 11.8 U so d e m é to d o s v irtu a le s

1: //Listado 11.8 Uso de métodos virtuales


2:
3: tfinclude <iostream.h>
4:
5: class Mamífero
6: {
7: public:
8: Mamífero() : suEdad(1)
9: { cout « 1'Constructor de Mamífero.. •\n "; }
10 : virtual -Mamífero()
11 : { cout « “Destructor de Mamífero.. ■\n"; }
12 : void Mover() const
13: { cout « "Mamífero se mueve un paso\n" ; }
14: virtual void HablarO const
15: { cout « "¡Mamífero habla!\n"; }
16: protected:
17: int suEdad;
Herencia 353

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!

En la línea 14 se proporciona un método virtual, Hablar(), a la clase Mamifero.


A nálisis
Con esto, el diseñador de esta clase indica que espera que esta clase se convierta
eventualmente en el tipo base de otra clase. Probablemente, la clase derivada necesitará
redefinir esta función.
En la línea 38 se crea un apuntador a Mamifero (apPerro), pero se le asigna la dirección de
un nuevo objeto Perro. Como un perro es un mamífero, ésta es una asignación válida. El
apuntador se utiliza entonces para llamar a la función Mover(). Como el compilador sabe
que apPerro sólo es un Mamifero, busca el método Mover() en el objeto Mamifero.
En la línea 41 , el apuntador llama al método Hablar (). Como Hablar () es virtual, se invoca
al método redefinido Hablar() de la clase Perro.
Esto es casi mágico. En lo que a la función respecta, tiene un apuntador a Mamifero, pero
aquí se llamó a un método de Perro. De hecho, si se tuviera un arreglo de apuntadores a
Mamifero, y cada uno apuntara a una subclase de Mamifero, se podrían llamar uno por uno.
y cada vez se llamaría a la función conecta. El listado 11.9 ilustra esta idea.
354 Día 11

E n trada L istado 1 1 . 9 Varias funciones virtuales llamadas una por una


1: //Listado 11.9 Varias funciones virtuales llamadas una por una
2:
3: #include <iostream.h>
4:
5: class Mamifero
6: {
7: public:
8: MamiferoO : suEdad(1) {}
9: virtual -MamiferoO {>
10: virtual void Hablar() const
11: { cout « “iMamifero habla!\n"; >
12: protected:
13: int suEdad;
14: };
15:
16: class Perro : public Mamifero
17: {
18: public:
19: void Hablar()const
20: { cout « “¡Guau!\nu; >
21: >;
22:
23: class Gato : public Mamifero
24: {
25: public:
26: void Hablar()const
27: { cout « "iMiau!\n"; }
28: };
29:
30: class Caballo : public Mamifero
31: {
32: public:
33: void Hablar()const
34: {cout « “¡Yihii!\n"; }
35: };
36:
37: class Cerdo: public Mamifero
38: {
39: public:
40: void Hablar()const
41: { cout « "iOink!\n"; }
42: };
43:
44: int main()
45: {
46: Mamifero * elArreglo[ 5 ];
47: Mamifero * aptr;
48: int opcion, i;
49:
50: for (i = 0; i < 5; i++)
51: {
52 cout « "(1)perro (2)gato (3)caballo (4 )cerdo:
53 cin » opcion;
H e re n c ia 355

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.

En t ie m p o de compilación, es imposible saber cuáles objetos se crearán, y por


e n d e cuáles m é t o d o s H a b l a r ( ) se invocarán. El ap u n ta d o r a p t r está lig a d o a
su o b je to en t ie m p o de compilación. Esto se conoce co m o vinculación d i n á ­
mica o vinculación en tiem po de ejecución, a diferencia de la vinculación
estática o vinculación en tiem po de compilación.
| 356 Día 11

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.

Cómo trabajan las funciones virtuales


Cuando se crea un objeto derivado, por ejemplo, un objeto Perro, primero se llama al
constructor de la clase base, y luego se llama al constructor de la clase derivada. La figura
11.2 muestra cómo se ve el objeto Perro después de ser creado. Observe que la parte
Mamif ero del objeto está junto a la memoria de la parte Perro.

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

Cuando se utiliza un a p u n ta d o r a un Mamífero, el a p t r v sigue apuntando a la función


correcta, d e p e n d ie n d o del tipo "real" del objeto. Por lo tanto, cuando se invoca a la 11
función H a b l a r ( ). se invoca a la función correcta.

No p ued e lle g a r a llá desde aquí


Si el objeto P e r r o tuviera un m étodo llamado M o v e r C o l a ( ) que no existiera en la clase
Mamífero, no podría utilizar el apuntador a Mamífero para tener acceso a ese m étodo (a
menos que lo convierta en apuntador a Perro). Como Mo ve r Co l a( ) no es una función vir­
tual, y como no se encuentra en un objeto Mamífero, usted no puede llegar a la función
sin un objeto P e r r o o sin un apuntador a Perro.
Aunque puede transform ar el apuntador a Mamífero en apuntador a Perro, por lo general
existen formas m ejores y más seguras de llamar al método M o v e r C o l a ( ). C++ desaprueba
las conversiones explícitas porque son propensas a errores. Este tema se explicará en detalle
cuando hablemos sobre la herencia múltiple en el día 13, " Polimorfismo", y también cuando
hablemos sobre las plantillas en el día 20. “Excepciones y manejo de errores".

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.

E n trad a L istado 1 1 . 1 0 Partición de datos al pasar parámetros por valor


1: //Listado 11.10 Partición de datos al pasar parámetros por valor
2:
3: ^include <iostream.h>
4:
5: class Mamifero
6: {
7: public:
8: Mamifero() : suEdad(1) {}
9: virtual -Mamifero() {>
10: virtual void Hablar() const
11: { cout « "¡Mamifero hablal\n"; >
12: protected:
13: int suEdad;
14: };
15:
16: class Perro : public Mamifero
17: {
18: public:
19: void Hablar()const
20: {cout « "¡Guau!\n"; }
21: };
22:
23: class Gato : public Mamifero
24: {
25: public:
26: void Hablar()const
27: { cout « "¡Miau!\n"; }
28: >;
29:
30: void ValorFuncion(Mamifero);
31: void AptrFuncion(Mamifero *);
32: void RefFunción(Mamifero &);
33:
34: int main()
35: {
36: Mamifero * aptr = NULL;
37: int opcion;
38: while (1)
39: {
40: bool fSalir = false;
41: cout « “(1)perro (2)gato (0)Salir: ";
42: cin » opcion;
43: switch (opcion)
44: {
45: case 0:
46: fSalir = true;
47: break;
48: case 1:
H e re n c ia 359

49: aptr = new Perro;


50: break;
51 : case 2:
52: aptr = new Gato;
53: break;
54: default:
55: aptr = new Mamífero;
56: break;
57: }
58: i f ( fS a lir )
59: break;
60: AptrFuncion(aptr);
61: RefFunción(* a p t r ) ;
62: ValorFuncion(*aptr);
63: }
64: return 0;
65: }
66:
67: void ValorFuncion(Mamífero MamiferoValor)
68: {
69: MamiferoValor.Hablar();
70: }
71 :
72: void AptrFuncion(Mamifero * apMamifero)
73: {
74: apMamifero->Hablar(); 11
75: }
76:
77: void Ref Función(Mamifero & rMamifero)
78: {
79: rMamifero.Hablar();
80: }

(1) perro ( 2) gato ( 0 ) S a l i r : 1


S alida ¡Guau!
¡Guau!
¡Mamífero habla!
(1) perro ( 2)gato ( 0 ) S a l i r : 2
¡ Miau!
¡ Miau!
¡Mamífero habla!
( 1 ) perro ( 2)gato ( 0 ) S a l i r : 0

En las líneas 5 a 28 se declaran versiones simplificadas de las clases Mamífero.


A nálisis
P e r r o y Gato. Se declaran tres funciones. A p t r F u n c i o n (), R e f F u n c i o n ( ) y
V a l o r F u n c i o n ( ). Toman un apuntador a Mamífero, una referencia a Mamífero y un
objeto Mamífero, respectivamente. Las tres funciones hacen lo mismo: llaman al método
H a b l a r ().

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.

Constructores virtuales de copia


Los constructores no pueden ser virtuales, así que, técnicamente, no existe un constructor
virtual de copia. Sin embargo, algunas veces su programa necesita con desesperación pasar
un apuntador a un objeto base como parámetro y tener una copia del objeto derivado correcto
que se crea. Una solución común para este problema es crear un método Clonar () en la clase
base y hacerlo virtual. El método Clonar () crea una copia del nuevo objeto de la clase actual
y regresa ese objeto.
Como cada clase derivada redefine el método Clonar(), se crea una copia de la clase
derivada. El listado 11.11 muestra cómo se utiliza esto.

L istado 11.11 Constructor virtual de copia


1: //Listado 11.11 Constructor virtual de copia
2:
3: #include <iostream.h>
4:
5: enum ANIMALES { MAMIFERO, PERRO, GATO};
6: const int NumTiposAnimales = 3;
7:
8: class Mamifero
9: {
10: public:
11: Mamifero() : suEdad(l)
H e re n c ia 361

12 { cout << " Co ns tr uc to r de Mamifero. . . \ n " ; }


13 v irt u a l -Mamífero))
14 { cout << " D es tr u ct o r de Mamifero.. . \ n " ; }
15 Mamifero(const Mamifero & rhs);
16 v i r t u a l void Hablar) ) const
17 { cout << "¡Mamifero ha bla! \ n" ; }
18 v i r t u a l Mamífero * Clonar))
19 { r et ur n new Mamifero( * t h i s ) ; }
20 int ObtenerEdad ( )const
21 { r et ur n suEdad; }
22 p ro t e c t e d :
23 i n t suEdad;
24 };
25
26 Mamí fer o: :Mamifero( const Mamifero & r h s ) : suEdad( r h s .ObtenerEdad())
27 {
28 cout << " C o ns tr u ct o r de copia de Mamifero. . . \ n " ;
29 }
30
31 c l a s s P e r r o : p u b l i c Mamifero
32 {
33 public:
34 P e r r o ()
35 { cout << "C ons tr u cto r de P e r r o .. .\ n "; }
36 v i r t u a l - P e r r o ()
37 { cout « "Des tr uct or de P e r r o . . . \ n " ; }
38 P e r r o (const Perro & rhs );
39 void H ab l a r ) ) c o n s t
40 { cout « " ¡Guau! \ n " ; }
41 v i r t u a l Mamifero * Clonar))
42 { r et ur n new P e r r o ( * t h i s ); }
43
44
45 P e r r o : : P e r r o ( c o n s t Perro & rhs):
46 Mamifero( r h s )
47 {
48 cout << " Co ns tr uc to r de copia de P er ro .. .\ n" ;
49
50
51 c l a s s G at o: p u b l i c Mamifero
52 {
53 public:
54 G at o ()
55 { cout « "Cons tr uctor de Gato. . . \n"; }
56 -Gato))
57 { cout « " Des tr uctor de Gato. . . \n"; }
58 Gatofconst Gato &);
59 void H a b l a r ) (const
60 { cout << " ¡ M i a u ! \ n " ; }
61 v i r t u a l Mamifero * Clonar))
62 { return new G a t o ( * t h i s ) ; }
63
64
65 Gato::Gato(const Gato & rhs ):
66 Mamifero( r h s )
i ( ‘i i i i n i i i i
362 Día 11

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

1: (1)perro (2)gato (3)Mamifero: 1


S a l id a 2: Constructor de Mamífero...
3: Constructor de Perro...
4: (1)perro (2)gato (3)Mamifero: 2
5: Constructor de Mamífero...
6: Constructor de Gato...
7: (1)perro (2)gato (3)Mamifero: 3
8: Constructor de Mamífero...
9: ¡Guau!
10: Constructor de copia de Mamífero
11 : Constructor de copia de Perro,» • •
12: ¡Miau!
13: Constructor de copia de Mamífero
H e re n c ia 363

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.

El costo de los m étodos virtuales


Debido a que los objetos con métodos virtuales deben mantener una tabla v, hay algo de
sobrecarga al tener métodos virtuales. Si tiene una clase muy pequeña de la que no espera
derivar otras clases, tal vez no exista un motivo para tener métodos virtuales.
Cuando declara a cualquier método como virtual, ya ha pagado la mayor parte del precio
de la tabla v (aunque cada entrada agrega una pequeña sobrecarga en la memoria). En ese
momento, es mejor que el destructor sea virtual, y debe dar por hecho que todos los demás
métodos también son virtuales. Examine cuidadosamente cualquier método que no sea
virtual, y asegúrese de entender por qué no es virtual.
364 D í a 11

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

6. CAZA ERRORES: ¿Qué está mal en este segmento de código ?


class Figura()
{
public:
Figura();
virtual -Figura();
virtual Figura(const Figura &);
};
S em ana 2

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

Cómo acceder a los elementos de un arreglo


Puede acceder a cada uno de los elementos del arreglo mediante una referencia al subíndice
o desplazamiento que se encuentra entre corchetes en el nombre del arreglo. Los elemen­
tos de los arreglos se cuentan desde cero. Por lo tanto, el primer elem ento del arreglo es
nombreArreglo[0]. En el ejemplo del ArregloLong, ArregloLong [0] es el primer elemen­
to, ArregloLong [ 1 ] el segundo, y así sucesivamente.
Esto puede ser un poco confuso. El arreglo UnArreglo[3] tiene tres elementos. Éstos son
UnArreglo[0], UnArreglo[l ] y UnArreglo[2] . Visto en forma más general, UnArreglo[nl
tiene n elementos numerados desde UnArreglo[0] hasta UnArreglof n - 1 ].
Por lo tanto, ArregloLong[25] se numera desde ArregloLong[0] hasta ArregloLong[24].
El listado 12.1 muestra cómo declarar un arreglo de cinco enteros y llenar cada uno con
un valor.

En t r a d a L ista d o 12.1 Uso de un arreglo de enteros

1: //Listado 12.1 - Arreglos


2:
3: #include <iostream.h>
4:
5: int main()
6: {
7: int miArreglo[ 5 ];
8: int i;
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 369

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

Valor para mi Arreglo[ 0] : 3


S alida Valor para miArreglo [ 1 L 6
Valor para m i A r r e g l o ! 2]: 9
Valor para m i A r r e g l o [3]: 12
Valor para miArreglo! 4] : 15
0: 3
1: 6
2: 9
3: 12
4: 15

La línea 7 declara un arreglo llamado mi Arreglo. el cual guarda cinco variables


enteras. La línea 10 establece un ciclo que cuenta desde 0 hasta 4. que es el con­
junto apropiado de subíndices o desplazamientos para un arreglo de cinco elementos. Se
pide al usuario un valor, y ese valor se guarda en el subíndice correcto del arreglo.
El primer valor se guarda en m i A r r e g l o [0], el segundo en m i A r r e g l o [ l ], y así sucesiva­
mente. El segundo ciclo for imprime en la pantalla cada valor.

Los a rre g lo s e m p ie z a n a co n ta r d esd e 0, n o d esd e 1. Ésta es la ca u sa d e m u c h o s


e rro re s e n p r o g ra m a s d e C + + escritos p o r novatos. Sie m p re q u e utilice u n a r r e ­
glo, re c u e rd e q u e u n a rre g lo con 10 e m p ieza a co n ta r d e sd e N o m b r e A r r e g lo [0 ]
h a st a N o m b r e A r r e g lo [9 ). N o m b re A rre g lo [ 10] n o se utiliza.

Cóm o escrib ir m ás allá del fin de un arreglo


Cuando escribe un valor en un elemento de un arreglo, el compilador calcula en dónde
se va a guardar el valor con base en el tamaño de cada elemento y del subíndice. Suponga
que pide que el valor se escriba en A r r e g l o L o n g [ 5 ] , que viene siendo el sexto elemento.
El compilador multiplica el desplazamiento (5) por el tamaño de cada elemento (en este
caso. 4). Luego mueve todos esos bytes (20) partiendo desde el principio del arreglo y
escribe el nuevo valor en esa ubicación.
Recuerde que subíndice y dcsplazomicnto son sinónimos.
370 Día 12

Si pide escribir en ArregloLong[50], el compilador ignora el hecho de que no existe dicho


elemento. Calcula qué tan lejos del primer elemento se debe escribir (200 bytes) y luego
escribe encima de lo que se encuentre en esa ubicación. Esto puede ser casi cualquier
información, y si escribe su nuevo valor ahí, podría tener resultados impredecibles. Si
tiene suerte, el programa se detendrá inmediatamente. Si no, eventualmente obtendrá resul­
tados extraños más adelante en el programa, y le será muy difícil descubrir qué salió mal.
En un sistema operativo robusto como Linux, un fallo en el programa no afectará al sis­
tema operativo en sí. En un ambiente que no sea robusto (com o M S-DO S), un programa
puede sobreescribir porciones del sistema operativo, lo que puede ocasionar que el equipo
se congele. Estos problemas son especialmente difíciles de depurar. En casos raros, un
programa así podría afectar a Linux, sobre todo si se ejecuta con privilegios de superusuario
(root).
El compilador es como un ciego midiendo la distancia entre casas. Empieza en la
primera casa, CallePrincipal[0]. Si usted le pide que vaya a la sexta casa de la calle
principal, el se dice a sí mismo: “Debo avanzar cinco casas más. Cada casa está a cuatro
pasos grandes de distancia de la otra. Debo avanzar 20 pasos más”. Si le pide que vaya a
CallePrincipalf 100] y la calle principal sólo tiene 25 casas, el medirá 400 pasos. Mucho
antes de llegar ahí, sin duda se pondrá enfrente de un autobús en marcha. A sí que tenga
cuidado hacia dónde lo envía.
El listado 12.2 muestra lo que ocurre cuando se escribe más allá del final de un arreglo.

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

cen ti nel aUn o[ 0]: 0


c e n t i n e l a D o s [0]: 0
c e n t i n el a Un o [ 1]: 0
c e n t i n e l a D o s [ 1]: 0
centinelaUno[2]: 0
centin elaDos[2]: 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

cen ti nel aUn o[ 0 ] : 20


centinelaDos[0]: 0
372 Día 12

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.

Errores tipo poste de barda


Debido a que es muy común escribir más allá del final de un arreglo, este error tiene su
propio nombre. Se conoce como error tipo poste ele buida. Esto se refiere al problema
producido al contar cuántos postes de barda se necesitan para una barda de 10 metros,
si se necesita un poste por cada metro. La mayoría de las personas contesta 10. pero en
realidad se necesitan 11. La figura 12.2 ilustra esto.
^ , conteo “menos
Este tipo de .. uno„ puede ser laia ruina
iu.. en la vida de cualquier
m
programador.
r *
0. . . „„,wt„mhrará
Sin embargo, con el tiempo se acostumnraia a la idea de que
M un arreglo
^ de 25 elementos
llega hasta el elemento 24, y que todo empieza desde 0.
Arreglos, cadenas tipo C y listas enlazadas 373

SS. Ss /N Ss. Ss Ss. Ss Ss. /N S\ Ss.


Fig u r a 12.2
Errores tip o p o s te d e 3 4 5 6 7 8 9 10 11
' • / / /, / / / 7 ,
'//» / . / /

barda. / /V / / ■ 'V 7 / /

Im 2m 3m 4m 5m 6m 7m 8m 9*^ lO rr»

Algunos programadores se refieren a NombreArreglo[0] como el elem ento


Nota cero. Tener este hábito es un gran error. Si NombreArreglo[0] es el elem en ­
to cero, ¿qué es NombreArreglo[ 1 ]? ¿El primero? Si es asi, al ver Nombre -
Arregloi24], ¿se daría cuenta de que no es el 24o elemento, sino el 25o? Es
mucho mejor decir que NombreArreglo[0] tiene un desplazamiento de cero y
es el primer elemento.
Ponga atención a eso: desplazamiento cero y primer elemento (distintos
números que describen la misma ubicación). Asegúrese de no intercambiar­
los m entalm ente.

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

declara a ArregloEntero como un arreglo de cinco enteros. Asigna a ArregloEntero[0]


el valor 10, a ArregloEntero [ 1 ] el valor 20, y así sucesivamente.
Si omite el tamaño del arreglo, se crea un arreglo suficientemente grande para guardar
los valores de su inicialización. Por lo tanto, si escribe
int ArregloEntero!] = { 10, 20, 30, 40, 50 };
creará el mismo arreglo que el del ejemplo anterior.
Si necesita conocer el tamaño del arreglo, puede pedir al compilador que lo calcule por
usted. Por ejemplo,
const USHORT LongitudArregloEntero;
LongitudArregloEntero = sizeof (ArregloEntero)/sizeof (ArregloEntero[0]);

le asigna a la variable constante de tipo USHORT, llamada LongitudArregloEntero, el


resultado obtenido al dividir el tamaño de todo el arreglo entre el tamaño de cada entrada
individual del arreglo. Ese cociente es el número de miembros del arreglo.
No puede inicial i/ar más elementos de los que haya declarado para el arreglo. Por lo tanto.
int ArregloEntero[5] = { 1 0 , 2 0 , 3 0 , 4 0 , 5 0 , 6 0 };

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.

Entrada L is ta d o 1 2 . 3 Uso d e c o n s t s y enums e n a r r e g l o s

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

S alida E l v a l o r de Jueves es: 50

La línea 8 crea lina enumeración llamada S emanaDias . Tiene ocho elementos.


A nálisis
Domingo es igual a 0. y DiasDeLaSemana es igual a 7.
La línea 13 utiliza la constante enumerada Jue como un desplazamiento dentro del arreglo.
Como J ue se evalúa en 4. se regresa el quinto elemento del arreglo. A r r e g l o S e m a n a [ 4] .
y se imprime en la línea 13.

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

E n t r a d a | L istado 12.4 Creación de un arreglo de objetos

1: // Listado 12.4 - Un arreglo de objetos


2:
3: #include <iostream.h>
4:
5: class GATO
6: {
7: public:
8: GAT0()
9: { suEdad = 1; suPeso = 5; >
10: -GAT0() {>
11: int ObtenerEdad() const
12: { return suEdad; }
13: int ObtenerPeso() const
14: { return suPeso; >
15: void AsignarEdad(int edad )
16: { suEdad = edad; }
17: private:
18: int suEdad;
19: int suPeso;
20: >;
21:
22: int main()
23: {
24: GATO Camada[ 5 ];
25: int i;
26:
27: for (i = 0; i < 5; i++ )
28: Camada[ i 1.AsignarEdad(2 * i + 1 );
29: for (i = 0; i < 5; i++ )
30: {
31: cout « "Gato #" « i + 1 « ": ";
32: cout « Carnada! i 1.ObtenerEdad () « endl;
33: }
34: return 0;
35: }

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.

Trabajo con arreglos multidimensionales


Es posible tener arreglos con más de una dimensión. Cada dimensión se representa como un
subíndice del arreglo. Por lo tanto, un arreglo de dos dimensiones tiene dos subíndices;
un arreglo de tres dimensiones tiene tres subíndices; y así sucesivamente. Los arreglos
pueden tener cualquier número de dimensiones, aunque es muy probable que la mayoría
de los arreglos que usted vaya a crear sea de una o dos dimensiones.
Un buen ejemplo de un arreglo de dos dimensiones es un tablero de ajedrez. Una dimensión
representa las ocho filas; la otra dimensión representa las ocho columnas. La figura 12.3
ilustra esta idea.
4
Fig u r a12.3
Un tablero de ajedrez
V un arreglo de dos
dimensiones.

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

cuadros. Por ejemplo:


CUADRO Tablero!64];

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

dando por hecho que el primer subíndice corresponde a f i l a y el segundo a columna. La


distribución de las posiciones para todo el tablero se muestra en la figura 12.3.

L
378 Día 12

Inicialización de arreglos multidimensionales


Los arreglos multidimensionales también se pueden iniciali/ar. Se asigna la lista de valores
para los elementos del arreglo en orden, y el último subíndice del arreglo cambia mientras
que los otros subíndices permanecen fijos. Por lo tanto, si tiene un arreglo
int elArreglo[5][3];

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.

En trada L is ta d o 1 2 . 5 Creación de un arreglo multidim ensional


1: // Listado 12.5 Muestra el uso de arreglos
2: // multidimensionales
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int UnArreglo[ 5 ][ 2 ] = { {0, 0}, {1, 2}, {2, 4}, {3, 6}, {4, 8} };
9:
10: for (int i = 0; i < 5; i++ )
11: for (int j =0; j <2 ; j++ )
12: {
13: cout « "UnArreglo[" « i « "][" « j « "]:
14: cout « UnArreglo( i ][ j ]<< endl;
15: }
16: return 0;
17: }
A rreg los, cadenas tip o C y listas enlazadas

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

La línea X declara un arreglo de dos dimensiones, UnArreglo. La primera dimensión


A nálisis
consta de cinco enteros; la segunda dimensión consta de dos enteros. Esto crea una
cuadrícula de 5 x 2. como se ve en la figura 12.4.

F ig u r a 12.4
Un arreglo de 5 x 2.

U n A rreg lo [5] [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.

Una p a la b ra sobre los arreglos y la m em oria


Cuando declara un arreglo, le está indicando al compilador exactamente cuántos objetos
espera guardar en él. El compilador reserva memoria para todos los objetos, incluso si no
los utiliza. Este no es un problema para arreglos en los que tenga una buena idea de cuántos
objetos necesitará. Por ejemplo, un tablero de ajedrez tiene 64 cuadros, y los gatos tienen
entre 1 y 10 gatitos. Sin embargo, cuando no tiene idea de cuántos objetos va a necesitar,
debe usar estructuras de datos más avanzadas.
Este libro trata arreglos de apuntadores, arreglos creados en el espacio libre de almacena­
miento (o heap). y otras colecciones más. Verá unas cuantas estructuras de datos avanzadas,
y puede aprender más en libros avanzados, como el libro C++. Cómo Program ar, de
Prentice Hall. Dos de las grandes ventajas de la programación son que siempre hav más
cosas que aprender, y siempre hay más libros de los que se puede aprender
| 380 Día 12

Uso del heap para solucionar problemas


relacionados con la memoria
Los arreglos de los que hemos hablado hasta ahora guardan todos sus elementos en la pila.
Por lo general, la memoria de la pila está severamente limitada, mientras que la memoria del
heap es mucho más grande. El tamaño de los arreglos es de naturaleza estática. Una vez que
declara un arreglo y compila el programa, no puede cambiar el tamaño de los arreglos duran­
te la ejecución. Pero existen formas de vencer esta limitación. El aprovechamiento del heap
le permite tener estructuras de datos que se comporten como si fueran arreglos dinámicos.

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.

En t r a d a L istado 12.6 C ó m o guardar un arreglo en el heap


1: // Listado 12.6 - Un arreglo de apuntadores a objetos
2:
3: #include <iostream.h>
4:
5: class GATO
6: {
7: public:
8: GAT0O
9: { suEdad = 1; suPeso =5; }
10: -GATO() {> // destructor
11: int ObtenerEdad() const
12: { return suEdad; }
13: int ObtenerPeso() const
14: { return suPeso; >
15: void AsignarEdad(int edad )
16: { suEdad = edad; >
17: private:
18: int suEdad;
19: int suPeso;
20: >;
21:
22: int main()
23: {
24: GATO * Familia! 500 J;
25: int i;
26: GATO * apGato;
27:
28: for (i = 0; i < 500; i++ )
29: {
A rre g lo s , cad en a s tip o Cy listas e n l a z a d a s 381

30: apGato = new GATO;


31: apGato->Asi gnarEdad(2 * i + 1 );
32: Familia! i ] = apGato;
33: }
34: f or ( i = 0 ; i < 500; i++ )
35: {
36: cout << "Gato tt" « i + 1 << " :
37: cout << Familia! i ] ->ObtenerEdad() << endl;
38: }
39: return 0;
40: }

Gato #1 : 1
S alida Gato #2: 3
Gato #3: 5

Gato #499: 997


Gato #500: 999

El o b jeto GATO declarad o en las líneas 5 a 20 es idéntico al objeto GATO d ec la rad o


A nálisis
en el listado I2.4. Sin em bargo, esta vez el arreglo declarado en la línea 24 se llam a
Fami lia, y se d ec la ra para g uardar 500 apuntadores a objetos GATO.

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.

Declaración de arreglos en el heap


Es posible colocar todo el arreglo en el heap. Esto se hace llamando a new y utilizando
el operador de subíndice. El resultado es un apuntador a un área del heap que guarda el
arreglo. Por ejemplo.
GATO * Fami lia = new GATO[ 500 ];

declara a F a m i l i a co m o ap untador al prim er elem ento de un arreglo de 500 o b jeto s de


tipo GATO. En otras p alabras. F a m i l i a apunta a (o tiene la dirección de) F a m i l i a ! 01-
38 2 Día 12

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.

Uso de un apuntador a un arreglo en comparación


con un arreglo de apuntadores
Examine las tres declaraciones siguientes:
1: GATO FamiliaUno! 500 ];
2: GATO * FamiliaDos[ 500 ];
3: GATO * FamiliaTres = new GAT0[ 500 ];
FamiliaUno es un arreglo de 500 objetos de tip o GATO. F a m ilia D o s es un arreglo de
500 apuntadores a objetos de tipo GATO. FamiliaTres e s un apuntador a un arreglo de 500
objetos de tipo 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.

Uso de apuntadores con nombres de arreglos


En C++, el nombre de un arreglo es un apuntador constante al primer elemento del arreglo.
Por lo tanto, en la declaración
GATO Familia! 50 ];

Familia es un apuntador a &Familia[ 0 ], que es la dirección del primer elemento del


arreglo Familia.
Es válido utilizar nombres de arreglos como apuntadores constantes, y viceversa. Por lo
tanto, Familia + 4 es una forma válida de tener acceso a los datos de Fam ilia! 4 ].
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 383

El compilador se encarga de toda la aritmética cuando se suman, incrementan y decremen-


tan apuntadores. La dirección que se accede al escribir F a m i l i a + 4 no está 4 bytes más
allá de la dirección de F a m i l i a , está 4 objetos más allá. Si cada objeto tiene 4 bytes de lar­
go, F a m i l i a + 4 está 16 bytes más allá del inicio del arreglo. Si cada objeto es un GATO
que tiene 4 variables miembro de tipo long de 4 bytes cada una, y dos variables miembro
de tipo short de 2 bytes cada una, esto quiere decir que cada GATO es de 20 bytes, y
F a m i l i a + 4 está SO bytes más allá del inicio del arreglo.

El listado 12.7 muestra la declaración y el uso de un arreglo en el heap.

Entrada Listado 12.7 Creación de un arreglo por medio de new

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

Gato #499: 997


Gato #500: 999

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

Eliminación de arreglos en el heap


¿Qué ocurre con la memoria asignada para estos objetos de tipo GATO cuando se destruye
el arreglo? ¿Existe una probabilidad de fuga de memoria? Al eliminar Familia se regresa
automáticamente toda la memoria reservada para el arreglo si se utiliza el operador delete [
1, recordando siempre incluir los corchetes. El compilador tiene suficiente inteligencia
para destruir cada objeto del arreglo y luego liberar la memoria que éste ocupaba en el
heap.

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.

DEBE utilizar in d e x a ció n d e a rre glos en NO DEBE con fund ir un arreglo d e a p u n ta


a p u n ta d o re s que a p u n te n a arreglos. dores con un a p u n ta d o r a un arreglo.

Qué son los arreglos de tipo char


Una cadena es una serie de caracteres. Las únicas cadenas que ha visto hasta ahora son
cadenas constantes sin nombre utilizadas en instrucciones cout. como
cout << "¡Hola, mundo!\n";

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!";

Debe tener en cuenta dos cosas acerca de esta sintaxis:


• En vez de tener caracteres entre comillas simples separados por comas y encerrados
entre llaves, tiene una cadena con doble comilla, sin comas y sin llaves.
• No necesita agregar el carácter nulo, ya que el compilador lo agrega por usted.

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.

El listado 12.8 m uestra el uso de un búfer sin inicializar.


En trada L istado 12.8 C ó m o lle n a r u n a r r e g lo

1: //Listado 12.8 búferes de arreglos de tipo char


2:
3: tfinclude <iostream.h>
4:
5: int main()
6: {
7: char bufer[ 80 ];
8:
9: cout « "Escriba la cadena:
10: cin » bufer;
11: cout « "Aqui está el búfer: " « bufer « endl;
12: return 0;
13: }

Escriba la cadena: ¡Hola, mundo!


Salida Aquí está el búfer: ¡Hola, mundo!
En la línea 7 se declara un búfer para guardar 80 caracteres. Este búfer es lo
A n á l is is
suficientemente grande para guardar una cadena de 79 caracteres y un carácter
nulo terminador.
En la línea 9 se pide al usuario que escriba una cadena, la cual se escribe en el búfer en
la línea 10. La sintaxis de cin es escribir un term inador nulo en el búfer después de
escribir la cadena.
Hay dos problemas con el programa del listado 12.8. En prim er lugar, si el usuario escribe
más de 79 caracteres, cin escribe más allá del final del búfer. En segundo lugar, si el
usuario escribe un espacio, cin piensa que es el final de la cadena y deja de escribir en el
búfer.
Para resolver estos problemas, debe llamar a un método especial en cin : get (). c i n . get ()
toma tres parámetros:
• El búfer que se va a llenar
• El número máximo de caracteres que se van a obtener
• El delimitador que termina la entrada
El delimitador predeterminado es newline. El listado 12.9 muestra su uso.

Entrada L istado 12.9 C ó m o lle n a r u n a r r e g lo

1 //Listado 12.9 uso de cin.get()


2
3 #include <iostream.h>
4
5 int main()
6 {
7 char bufer[ 80 ];
8
Arreglos, cadenas t ip o C y listas enlazadas 387

9: cout << "Escriba la cadena: “ ;


10: c i n . g e t (búfer, 79 ); // llegar hasta 79 o a newline
11 : cout << "Aqui está el búfer: " << búfer << endl;
12: return 0;
13: }

Escriba la cadena: ¡Hola, mundo!


S a l id a Aquí está el búfer: ¡Hola, mundo!

La linca 10 llama al método get () de cin. El búfer que se declara en la línea 7 se


A n á l is is
pasa com o el prim er argumento. El segundo argumento es el número m áxim o de
caracteres a obtener. En este caso, debe ser 79 para dejar un espacio para el term inador
nulo. No hay necesidad de proporcionar un carácter de terminación, ya que el valor pre­
determ inado de newline es suficiente.

Uso de funciones para cadenas


C++ hereda de C una biblioteca de funciones para manejo de cadenas. De todas esas
funciones, las más utilizadas son: strcpy (). strncpy (). strcat () y strlen ( ) . strcpy ()
copia en un búfer designado todo el contenido de una cadena; strncpy () copia el núm ero
especificado de caracteres de una cadena a otra; s t r le n () nos indica el tamaño de la cade­
na (que puede ser distinto del tam año del arreglo); y strcat () concatena dos cadenas.
El listado 12.10 m uestra el uso de estas funciones.

En t r a d a L is t a d o 1 2 . 1 0 Uso de funciones comunes para cadenas

1: // listado 1 2 . 1 0 - Uso de strcpy, strncpy, strlen y strcat


2: 12
3: tfinclude <iostream.h>
4: #include <string.h>
5:
6: int main()
7: {
8: char Cadenal[ ] = "¡Así, siempre así he de verte!";
9: char Cadena2[ 80 ] =
10: char Cadena3[ 80 ] =
11 :
12: cout << "Cadenal: " « Cadenal « endl;
13: strcpy(Cadena2, Cadenal);
14: cout << "Cadena2: " « Cadena2 « endl;
15: strncpy (Cadena3, Cadenal, 5); // no es toda la cadena
16: Cadena3[ 5 ] = ' \01 ; // necesita un terminador nulo
17: cout << "Cadena3 después de strncpy: ";
18: cout << Cadena3 << endl;
19: cout << "Cadenal mide " << strlen (Cadenal)
20 : << " bytes de largo, \n";
21 : cout « "Cadena2 mide " « strlen (Cadena2)
22 : << " bytes de largo, \n";
23: cout << "y Cadena3 mide " « strlen (Cadena3)
ce >iMinuti
L istado 1 2 . 1 0 continuación

24: « " bytes de largo" « endl;


25: strcat(Cadena3, Cadenal);
26: cout « "Cadena3 después de strcat: " << Cadena3 <
27: cout « "Cadenal aún mide " « strlen (Cadenal)
28: « " bytes de largo, \n" ;
29: cout « "Cadena2 aún mide " « strlen (Cadena2)
30: « 11 bytes de largo, \n" ;
31 : cout « "y Cadena3 ahora mide " « strlen (Cadena3
32: « " bytes de largo" « endl;
33: return %
34: }

Cadenal: ¡Así, siempre así he de verte!


S alida Cadena2: ¡Así, siempre así he de verte!
Cadena3 después de strncpy: ¡Así,
Cadenal mide 30 bytes de largo,
Cadena2 mide 30 bytes de largo,
y Cadena3 mide 5 bytes de largo
Cadena3 después de strcat: ¡Así,¡Así, siempre así he de verte!
Cadenal aún mide 30 bytes de largo,
Cadena2 aún mide 30 bytes de largo,
y Cadena3 ahora mide 35 bytes de largo

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

Uso de cadenas y apuntadores


Como aprendió hoy. cuando utiliza el nombre del arreglo solo (sin un subíndice encerra­
do entre corchetes), está haciendo referencia a la dirección del arreglo. Esta variable se
comporta como un apuntador. Es un apuntador especial (un apuntador constante). Es cons­
tante porque usted no puede cambiar la dirección a la que apunta. La razón de esto es la
siguiente: los arreglos no se mueven; si cambiara el apuntador, no podría hacer referencia
a la memoria asignada para ese arreglo.
Observe que, en el listado 12.10, el nombre del arreglo se utiliza como argumento en las
funciones para cadenas. También puede utilizar apuntadores a cadenas en lugar de arreglos.
La única diferencia es que no puede cambiar una cadena ahí mismo (no puede concatenarle
otra cadena, como lo hizo con el arreglo Cadena3). El estándar del lenguaje no prohíbe esto,
pero el compilador o el sistema operativo tal vez no lo permitan (tratan a las cadenas como
si fueran de sólo lectura). Aunque no se prohíbe hacer esto, no debe cambiar una cadena
así. Confíe en mí con respecto a esto (puede ocasionar todo tipo de efectos secundarios,
y no es portable).
El listado 12.11 muestra el uso de cadenas y apuntadores.

Entrada L istado 12.11 Uso de cadenas y apuntadores


1: // listado 12.11 - Cadenas y apuntadores
2:
3: tfinclude <iostream.h>
4: #include <string.h>
5:
6: int main()
7: {
8: char * Cadenal = "¡Así, siempre así he de verte!";
9: char Cadena2[ 80 ] =
10: char Cadena3[ 80 ] =
11:
12: cout « "Cadenal: " « Cadenal « endl;
13: strcpy(Cadena2, Cadenal );
14: cout << "Cadena2: " << Cadena2 « endl;
15: strncpy(Cadena3, Cadenal, 5 ); // no es toda la cadena
16: Cadena3[ 5 ] = '\0'; // necesita un terminador nulo
17: cout « "Cadena3 después de strncpy: ";
18: cout « Cadena3 « endl;
19: cout « "Cadenal mide " « strlen (Cadenal )
20: « " bytes de largo, \n";
21: cout « "Cadena2 mide " « strlen (Cadena2 )
22: « " bytes de largo, \n";
23: cout « "y Cadena3 mide " << strlen (Cadena3 )
24: « " bytes de largo" « endl;
25: strcat(Cadena3, Cadenal );
26: cout << "Cadena3 después de strcat: * « Cadena3 « endl;
27: cout << "Cadenal aún mide " « strlen (Cadenal )
continúo
L istado 12 .1 1 c o n t in u a c ió n

28: « " bytes de largo, \n";


29: cout « "Cadena2 aún mide " « strlen (Cadena2 )
30: « “ bytes de largo, \n";
31: cout « "y Cadena3 ahora mide " « strlen (Cadena3 )
32: « " bytes de largo" « endl;
33: Cadenal = “Alli estás hoy, junto a la tienda de Ayax...";
34: cout « "Cadenal: 0 « Cadenal « endl;
35: strcat(Cadena3, Cadenal );
36: cout « "Cadena3 después de strcat2: " « Cadena3 « endl;
37: cout « "Cadenal mide ahora 11 « strlen (Cadenal )
38: « " bytes de largo, \n";
39: cout « "Cadena2 aún mide " « strlen (Cadena2 )
40: « " bytes de largo, \n";
41: cout « "y Cadena3 mide ahora " « strlen (Cadena3 )
42: « " bytes de largo" « endl;
43: return 0;
44: }
Cadenal: ¡Así, siempre asi he de verte!
S a l id a Cadena2: iAsi, siempre asi he de verte!
Cadena3 después de strncpy: ¡Asi,
Cadenal mide 30 bytes de largo,
Cadena2 mide 30 bytes de largo,
y Cadena3 mide 5 bytes de largo
Cadena3 después de strcat: ¡Así,¡Asi, siempre así he de verte!
Cadenal aún mide 30 bytes de largo,
Cadena2 aún mide 30 bytes de largo,
y Cadena3 ahora mide 35 bytes de largo
Cadenal: Allí estás hoy, junto a la tienda de Ayax...
Cadena3 después de strcat2: ¡Así, ¡Así, siempre así he de verte!Allí
estás hoy, junto a la tienda de Ayax...
Cadenal mide ahora 44 bytes de largo,
Cadena2 aún mide 30 bytes de largo,
y Cadena3 mide ahora 79 bytes de largo

A nálisis En la línea 4 se incluye el archivo de encabezado s t r i n g . h. Este archivo contiene


los prototipos de las funciones para cadenas. La línea 8 asigna un apuntador a una
cadena de caracteres (“ ¡Así, siempre así he de verte!’’), y las líneas 9 y 10 asignan dos
arreglos de caracteres.
Hasta la línea 32, el listado 12.11 es exactamente igual al listado 12.10 (vea ese listado si
necesita una explicación de esas líneas).
La línea 33 cambia a Cadenal. No modifica el contenido original (“ ¡Así, siempre así he de
verte!”); ocasiona que Cadenal apunte a una cadena diferente en memoria (“ Allí estás hoy,
junto a la tienda de Ayax...”). Ésta es una distinción sutil, pero muy importante, ya que no
debe utilizar un apuntador a cadena (como Cadenal) como destino en strcpy (), strncpy ().
strcat () o en funciones similares.
Tampoco puede codificar algo como lo siguiente:
Cadena2 = "esto no compilará";
Arreglos, cadenas tipo C y listas enlazadas 391

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

Entrada L istado 12.12 U so d e u n a c la se String

1: //Listado 12.12 Uso de la clase String


2:
3: //include <iostream.h>
4: //include <string.h>
5:
6: // Clase String rudimentaria
7: class String
8: {
9: public:
10 // constructores
11 String();
12 String(const char * const );
13 String(const String & );
14 -String();
15 // operadores sobrecargados
16 char & operator!] (unsigned short offset );
17 char operator!] (unsigned short offset ) const;
18 String operator+ (const String & );
19 void operator+= (const String & );
20 String & operator (const String & );
21 // Métodos generales de acceso
22 unsigned short GetLen()const
23 { return itsLen; >
24 const char * GetString() const
25 { return itsString; >
26 private:
27 String (unsigned short ); // constructor privado
28 char * itsString;
29 unsigned short itsLen;
30 };
31
32 // constructor predeterminado crea una cadena de 0 bytes
33 String::String()
34 {
35 itsString = new char[ 1 ];
36 itsString! 0 ] = 1\0';
37 itsLen = 0;
38 }
39
40 // constructor privado (ayudante), sólo lo utilizan
41 // los métodos de la clase para crear una cadena nueva del
42 // tamaño requerido. Llena de caracteres nulos.
43 String:¡String(unsigned short len )
44 {
45 itsString = new char[ len + 1 ];
46
47 for (unsigned short i = 0 ; i <= len; i++ )
48 itsString! i ] = ’\0■;
49 itsLen = len;
50
Arreglos, cadenas tipo C y listas enlazadas 393

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

101: if (offset > itsLen )


102: return itsStringf itsLen • 1 );
|394 Dia 12

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

154 char * temp = "¡Hola, mundo!";


155 s1 = temp;
156 cout << ”Sl:\t" << si .GetString() « endl;
157
158 char tempDos[ 26 ];
159 strcpy(tempDos,"; íes grandioso estar aquí!“ );
160 si += tempDos;
161 cout « "tempDos:\t" « tempDos « endl;
162 cout « “Si:\t" « s1 .GetString() « endl;
163
164 cout << "Sl[3]:\t" « sl[ 3 ] « endl;
165 s1[ 3 ]= *x 1;
166 cout « "Sl:\t" « si .GetStringO « endl;
167
168 cout « "S1 [999] :\t" « si [ 999 ] « endl;
169
170 String s2(" Otra cadena" );
171 String s3;
172 S3 = s1 + s2;
173 cout « "S3:\t" « s3.GetString() « endl;
174
175 String s4;
176 s4 = "¿Por qué trabaja esta función?";
177 cout « "S4:\t" « s4.GetString() « endl;
178 return 0;
179
i
i
S1 : Prueba inicial
S alida S1 : ¡Hola, mundo!
tempDos: ; ¡es grandioso estar aqui!
S1 : ¡Hola, mundo!; ¡es grandioso estar aqui!
SI[31 : 1
S1 : ¡Hoxa, mundo!; ¡es grandioso estar aqui!
S1[999]: !
S3: ¡Hoxa, mundo!; ¡es grandioso estar aqui! Otra cadena
S4: ¿Por qué trabaja esta función?
Las líneas 7 a 30 son la declaración de una clase String simple. Las líneas 11 a i
A nálisis
13 contienen tres constructores: el constructor predeterminado, el constructor de
copia y un constructor que toma una cadena existente que termina con un carácter nulo
(estilo C).
La clase String sobrecarga los operadores de desplazamiento ([ ]), de suma (+) y más
igual a (+=). El operador de desplazamiento se sobrecarga dos veces: una vez como función
constante que regresa un char y otra vez como función no constante que regresa una
referencia a char.
La versión no constante se utiliza en instrucciones como la siguiente:
UnaCadena[ 3 ] = 'x’;
como se ve en la línea 165. Esto permite un acceso directo a cada uno de los caracteres
de la cadena. Se regresa una referencia al carácter para que la función que hace la lla­
mada pueda manipular dicha referencia.
La versión constante se utiliza cuando se accede a un objeto String constante, como
en la implementación del constructor de copia (línea 64). Observe que se accede a rhs[i],
aunque rhs esté declarado como const String &. No es legal tener acceso a este objeto
mediante el uso de un método no constante. Por lo tanto, el operador de desplazamiento
se debe sobrecargar con un método de acceso constante.
Si el objeto regresado fuera grande, tal vez sería mejor declarar el valor de retorno como
una referencia constante. Sin embargo, como un ch ar sólo mide 1 byte, no tendría caso
hacer eso.
En las líneas 33 a 38 se implementa el constructor predeterminado. Éste crea una cadena
de longitud 0. La convención de esta clase String es reportar su longitud sin contar el
terminador nulo. Esta cadena predeterminada contiene sólo un term inador nulo.
En las líneas 64 a 72 se implementa el constructor de copia. Éste establece la longitud de
la cadena nueva igual a la de la cadena existente (más 1 para el term inador nulo). Copia
en la cadena nueva cada carácter de la cadena existente, y agrega un term inador nulo a la
cadena nueva.
En las líneas 53 a 61 se implementa el constructor que tom a una cadena existente estilo
C. Este constructor es similar al constructor de copia. La longitud de la cadena existente
se establece mediante una llamada a la función s tr le n () de la biblioteca estándar String.
En la línea 27 se declara otro constructor, String (unsigned short), como función miem­
bro privada. La intención del diseñador de esta clase es que ninguna clase vaya a crear un
String de longitud arbitraria. Este constructor existe sólo para ayudar en la creación intema
de Strings según se requiera; por ejemplo, según lo requiera el operator+=, en la línea
134. Esto se describirá con detalle más adelante, cuando hablemos sobre operator+=.
El constructor String (unsigned short) llena cada elemento de su arreglo con NULL. Por
lo tanto, el ciclo f or evalúa i<=len en lugar de i<len.
El destructor, que se implementa en las líneas 75 a 79, elim ina la cadena de caracteres
mantenida por la clase. Asegúrese de incluir los corchetes en la llamada al operador d elete
para que se eliminen todos los miembros del arreglo, en lugar de que se elimine sólo el
primero.
El operador de asignación primero comprueba si el lado derecho de la asignación es igual
que el lado izquierdo. Si no lo es, se elimina la cadena actual, y se crea y se copia la
cadena nueva. Se regresa una referencia para facilitar asignaciones como la siguiente:
Stringl = String2 = String3;
Arreglos, cadenas tipo C y listas enlazadas 397

El operador de desplazamiento se sobrecarga dos veces. En ambas ocasiones se realiza un


chequeo rudimentario de los límites. Si el usuario intenta tener acceso a un carácter que
se encuentre en una ubicación más allá del final del arreglo, se regresa el último carácter
(es decir, len -1).
En las líneas 119 a 131 se implementa el operador de suma (+) como operador de concate­
nación. Es conveniente poder escribir
String3 = Stringl + String2;
y que String3 sea la concatenación de las otras dos cadenas. Para lograr esto, la función
del operador de suma (+) calcula la longitud combinada de las dos cadenas y crea una cade­
na temporal llamada temp. Esto invoca al constructor privado, el cual toma un entero y
crea una cadena llena de caracteres nulos. Estos caracteres se reemplazan a continuación
por el contenido de las dos cadenas. Primero se copia la cadena del lado izquierdo
(*this), y luego la del lado derecho (rhs).
El primer ciclo f o r avanza por la cadena del lado izquierdo y agrega cada carácter a la
cadena nueva. El segundo ciclo fo r avanza a través del lado derecho. Observe que i sigue
contando la ubicación para la nueva cadena, incluso cuando j cuenta en la cadena rhs.
El operador de suma regresa la cadena temp por valor, la cual se asigna a la cadena del lado
izquierdo de la asignación (String3). El operador += actúa sobre la cadena existente (es
decir, el lado izquierdo de la instrucción stringl += string2). Este operador funciona
igual que el operador de suma, excepto que el valor temp se asigna a la cadena actual
(*this = temp) en la línea 146.
La función main() (líneas de la 149 a la 179) actúa como un programa controlador de
prueba para esta clase. La línea 151 crea un objeto String mediante el uso del constructor
que toma como parámetro una cadena estilo C que termina con un carácter nulo. La línea
152 imprime su contenido mediante el uso de la función de acceso GetString(). La
línea 154 crea otra cadena estilo C. La línea 155 prueba el operador de asignación, y
la línea 156 imprime los resultados.
La línea 158 crea una tercera cadena estilo C llamada tempDos. La línea 159 invoca a
strcpy para llenar el búfer con los caracteres ; íes grandioso estar a q u í! La línea 160
invoca al operador += y concatena a tempDos con la cadena existente s1. La línea 162 impri­
me los resultados.

En la línea 164 se accede al cuarto carácter de s i, y también se imprime. En la línea 165


se le asigna un nuevo valor. Esto invoca al operador de desplazamiento ([ ]) que no es
constante. La línea 166 imprime el resultado, el cual muestra que, en efecto, se ha cambiado
el valor actual.
La línea 168 intenta tener acceso a un carácter que se encuentra más allá del final del
arreglo. Se regresa el último carácter del arreglo, como se tenía designado.
Las líneas 170 y 171 crean dos objetos String adicionales, y la línea 172 llama al operador
de suma. La línea 173 imprime los resultados.
La línea 175 crea un nuevo objeto String llamado s4. La línea 176 invoca al operador de
asignación. La línea 177 imprime los resultados. Usted podría estar pensando: “El operador
de asignación está definido para tomar una referencia constante a String en la línea 20,
pero aquí el programa pasa una cadena estilo C. ¿Por qué es válido esto?"
La respuesta es que el compilador espera un String, pero se le proporciona un arreglo de
caracteres. Por lo tanto, comprueba si puede crear un String a partir de lo que se le
proporciona. En la línea 12 se declaró un constructor que crea St rings a partir de arreglos
de caracteres. El compilador crea un String temporal a partir del arreglo de caracteres y
lo pasa al operador de asignación. Esto se conoce como conversión explícita, o promoción.
Si no se hubiera declarado (y definido) el constructor que toma com o parámetro un arreglo
de caracteres, la asignación habría generado un error de com pilación.

Listas enlazadas y otras estructuras


Los arreglos son muy parecidos a los artículos de Tupperware. Son excelentes contenedores,
pero tienen un tamaño fijo. Si usted escoge un contenedor dem asiado grande, desperdicia
espacio en su área de almacenamiento. Si escoge uno muy pequeño, su contenido se
derrama y se hace todo un desastre.
Una manera de solucionar este problema es con una lista enlazada. Ésta es una estructura
de datos que consiste en contenedores pequeños diseñados para ajustarse al tamaño y que
se enlazan entre sí según se necesite. El objetivo es escribir una clase que guarde un objeto
de sus datos (como un GATO o un Rectángulo) y que pueda apuntar al siguiente contenedor.
Usted crea un contenedor para cada objeto que necesite guardar, y encadena esos contene­
dores entre sí según lo necesite.
Los contenedores se llaman nodos. El primer nodo de la lista se llama cabeza, y el último
nodo se llama cola.
Las listas tienen tres formas básicas. En orden de la más simple a la más compleja, son
• Listas con un solo enlace
• Listas con doble enlace
• Árboles
En una lista con un solo enlace, cada nodo apunta hacia el siguiente, pero no hacia el
anterior. Para encontrar un nodo específico, se debe em pezar en el inicio de la lista y de
ahí avanzar de nodo en nodo, como en la búsqueda de un tesoro (“El siguiente nodo está
debajo del sofá”). Una lista con doble enlace le permite moverse hacia atrás y hacia ade­
lante en la lista. Un árbol es una estructura compleja creada a partir de nodos, cada uno
de los cuales puede apuntar hacia dos o más direcciones. La figura 12.5 muestra estas
tres estructuras fundamentales.
Arreglos, cadenas tipo C y listas enlazadas 399

Un solo
Figura 12.5 enlaco
Listas enlazadas.

Árboles

O O

Análisis de un caso de prueba de listas


enlazadas
En esta sección examinaremos detalladamente una lista enlazada como caso de estudio
sobre la forma en que se crean las estructuras complejas, y especialmente sobre la forma de
utilizar la herencia, el polifonnismo y el encapsulamiento para manejar proyectos grandes.

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.

Los componentes de una lista enlazada


La lista enlazada constará de nodos. La clase de nodos en sí será abstracta; utilizaremos
tres subtipos para lograr nuestro objetivo. Habrá un nodo cabeza cuyo trabajo será manejar
la cabeza de la lista, un nodo cola (¡adivine cuál será su trabajo!), y cero o más nodos
internos. Estos nodos se encargarán de que los datos actuales estén guardados en la lista.
Observe que los datos y la lista son bastante distintos. En teoría, usted puede guardar en
una lista cualquier tipo de datos. No son los datos los que están enlazados entre sí; es el
nodo el que guarda los datos.
El programa controlador no sabe nada acerca de los nodos; trabaja con la lista. Sin embargo,
la lista hace muy poco trabajo; simplemente lo delega a los nodos.
El listado 12.3 muestra el código de una lista enlazada; lo examinaremos con mucho detalle.

Entrada Listado 12.13 Lista e n la z a d a

1: I I Listado 12.13 Muestra un método orientado a objetos para


2: // listas enlazadas. La lista delega responsabilidad al nodo.
3:
4: #include <iostream.h>
5:
«H
6: enum { kEsMasChico, kEsMasGrande, kEsIgual};
7:

!
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

23: // Comparar se u t i l i z a para decidir a qué lugar de la l i s t a


24: // pertenece un objeto especifico.
25: int Datos:: Comparar(const Datos & losOtrosDatos )
26: {
27: i f (miValor < losOtrosDatos.miValor )
28: return kEsMasChico;
29: else i f (miValor > losOtrosDatos.miValor )
30: return kEsMasGrande;
31: else
32: return kEsIgual;
33: }
34:
35: // ADT que representa al objeto nodo de la lista
36: // Cada clase derivada debe redefinir a Insertar y a Mostrar
37: class Nodo
38: {
39: public:
40: Nodo(){}
41: vir tua l -Nodo(){}
42: vir tua l Nodo * Insertar(Datos * losDatos ) = 0;
43: vir tua l void Mostrar() = 0;
44: };
45:
46: // Éste es el nodo que guarda el objeto actual
47: // En este caso el objeto es de tipo Datos
48: // Veremos como generalizar más esto cuando
49: // hablemossobre las pl antillas
50: class Nodolnterno: public Nodo
51: {
52: public:
53: Nodolnterno(Datos *losDatos, Nodo * siguiente );
54: 'Nodolnterno()
55: { delete miSiguiente; delete misDatos; }
56: vir tual Nodo * Insertar(Datos * losDatos );
57: vir tual void Mostrar()
58: {
59: // ¡delegar!
60: misDatos->Mostrar();
61: miSiguiente->Mostrar();
62: }
63:
64: private:
65: // los datos en si
66: Datos * misDatos;
67: // apunta al siguiente nodo de la li s ta enlazada
68: Nodo * miSiguiente;
1402 Día 12

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

113 virtual Nodo * Insertar(Datos * losDatos );


114 virtual void Mostrar() {>
115 >;
116
117 // Si los datos llegan a mí, se deben insertar antes de mí
118 // ya que soy la cola y no hay NADA después de mi
119 Nodo * NodoCola::Insertar(Datos * losDatos )
120 {
121 Nodolnterno * nodoDatos = new NodoInterno(losDatos, this );
122 return nodoDatos;
123
124
125 // El nodo cabeza no tiene datos, sólo apunta
126 // al inicio de la lista
127 class NodoCabeza : public Nodo
128 {
129 public:
130 NodoCabeza();
131 -NodoCabeza()
132 { delete miSiguiente; >
133 virtual Nodo * Insertar(Datos * losDatos );
134 virtual void Mostrar()
135 { miSiguiente->Mostrar(); }
136 private: i
137 Nodo * miSiguiente;
138 };
139
140 // Tan pronto como se crea la cabeza
141 // se crea la cola
142 NodoCabeza::NodoCabeza()
143 {
144 miSiguiente = new NodoCola;
145 }
146
147 //No hay nada antes de la cabeza así que sólo
148 // se pasan los datos al siguiente nodo
149 Nodo * NodoCabeza::lnsertar(Datos * losDatos )
150 i
151 miSiguiente = miSiguiente->Insertar(losDatos );
152 return this;
153 >
154
155 // Yo obtengo todos los méritos y no hago nada del trabajo
156 class ListaEnlazada
157 {

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

202: // ahora avanzar por la lista y mostrar los datos


203: le.MostrarTodo!);
204: return 0; // ¡le queda fuera de alcance y se destruye!
205: }

¿Cuál valor? (0 para detener) 5


S alida ¿Cuál valor? (0 para detener) 8
¿Cuál valor? (0 para detener) 3
¿Cuál valor? (0 para detener) 9
¿Cuál valor? (0 para detener) 2
¿Cuál valor? (0 para detener) 10
¿Cuál valor? (0 para detener) 0
2
3
5
8
9
10

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

Figura 12.6 Lista enlazada


La lista enlazada des­
pués de su creación.

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.

Fig u r a 1 2 . 8 Lista enlazada

La lista enlazada d es­


p u és d e h aber insertado
e l segundo nodo.

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

Esto ocasionará que el método Nodolnterno::Insertar() se ram ifique hacia la línea


100. En lugar de crear un nuevo nodo e insertarlo, Nodolnterno sólo pasará los datos
nuevos al método Insertar del nodo al que esté apuntando en ese m om ento el apuntador
miSiguiente. En este caso, invocará a Insertar en el Nodolnterno cuyo objeto Datos es
tiene el valor 15.

Se hará otra vez la comparación y se creará un nuevo Nodolnterno. Este nuevo


Nodolnterno apuntará al Nodolnterno cuyo objeto Datos tiene el valor 15, y su direc­
ción se pasará de regreso al Nodolnterno cuyo objeto Datos tiene el valor 3, como se
muestra en la línea 100 .
El efecto obtenido es que el nuevo nodo se insertará en la lista en la ubicación correcta.
De ser posible, trate de seguir paso a paso la inserción de varios nodos en su depu­
rador. Debe ver a estos métodos invocarse entre sí y a los apuntadores ajustarse apro­
piadamente.
Arreglos, cadenas t ip o C y listas enlazadas 409

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

Uso de clases de arreglos en lugar


de arreglos integrados
Escribir su propia clase que manipule arreglos tiene muchas ventajas en comparación con
el uso de los arreglos integrados. Para empezar, puede prevenir el desbordam iento de
arreglos. Tam bién podría considerar crear su clase de arreglos con tam año dinám ico:
Al m om ento de la creación podría tener un solo miembro, y crecer según sea necesario
durante el funcionam iento del programa.
Tal vez tam bién quiera ordenar los miembros del arreglo. Podría considerar una cantidad
de variantes de arreglos poderosas. Entre las más populares están
• Colección ordenada: Cada miembro está colocado en orden.
• Conjunto: Ningún miembro aparece más de una vez.
• Diccionario: U tiliza pares relacionados en los que un valor actúa com o clave para
recuperar el otro valor.
410 Día 12

• Arreglo disperso: Se permiten índices con valores no secuenciales, y sólo aquellos


valores que se agregan al arreglo consum en m em oria. P or lo tanto, puede pedir
el valor de ArregloDisperso[5] o de ArregloDisperso[200], pero es posible que
la memoria esté asignada sólo para un número pequeño de entradas.
• Bolsa: Una colección desordenada a la que se agregan y quitan elem entos en orden
aleatorio.
Por medio de la sobrecarga del operador de índice ([ ]), puede convertir una lista enlazada
en una colección ordenada. Por medio de la eliminación de duplicados, puede convertir una
colección en un conjunto. Si cada objeto de la lista tiene un par de valores relacionados,
puede utilizar una lista enlazada para crear un diccionario o un arreglo disperso.

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

P ¿Q ué hay en un elem ento no inicializado de un arreglo?


R Lo que haya en la m em oria en cualquier momento dado. Los resultados de utilizar
este m iem bro sin asignarle un valor son impredecibles.
P ¿Puedo co m b in ar arreglos?
R Sí. Con arreglos sim ples puede utilizar apuntadores para combinarlos en un arreglo
nuevo y más grande. Con cadenas, puede utilizar algunas de las funciones integradas,
com o s t r c a t , para com binar cadenas.
P ¿Por qué debo c re a r u n a lista enlazada si un arreglo puede funcionar?
R Un arreglo debe tener un tamaño fijo, mientras que una lista enlazada puede cam biar
su tam año dinám icam ente en tiempo de ejecución.
P ¿P or qué u sa r arreglos integrados si puedo crear una clase de arreglos m ejo r?
R Los arreglos integrados son rápidos y fáciles de utilizar.
P ¿Debe una clase de cadenas utilizar un char * p ara g u ard ar el contenido de la
cadena?
R No. Puede utilizar el alm acenam iento de memoria que el diseñador considere más
conveniente.

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;

5. CAZA E R R O R E S : ¿Qué está mal en este fragm ento de código?


unsigned short UnArreglo[5 ][4 ];
for (int i = 0 ; i<=5 ; i++)
for (int j = 0 ; j<=4 ; j++)
UnArreglo[i][j] = 0 ;
S emana 2

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:

• Q ué es la heren cia m últiple y cóm o utilizarla


• Q ué es la heren cia virtual
• Q ué son los tipos de datos abstractos
• Q ué son las fu n cio n es virtuales puras

Problemas con herencia simple


Suponga que está trabajando con sus clases de animales por un tiem po, y divide
la jera rq u ía de clases en Aves y Mamíferos. La clase Ave incluye el m étodo (o
función m iem bro) Volar ( ) . La clase Mamífero se ha dividido en varios tipos de
Mamíferos, incluyendo Caballo. Esta clase incluye los métodos Relinchar() y
Galopar().

De repente, se da cu en ta de que necesita un objeto Pegaso: una cruza entre un


Caballo y un Ave. Un Pegaso puede Volar (), puede Relinchar () y puede
Galopar ( ) . C on la herencia sim ple, se encuentra en un gran aprieto.
Puede hacer que Pegaso sea un Ave, pero entonces no podrá Relinchar () ni
Galopar (). Puede hacerlo un Caballo, pero entonces no podrá Volar ( ) .
Su primera solución puede ser copiar el método Volar () en la clase Pegaso y derivar a
Pegaso de Caballo. Esto funciona bien, lo único malo es que tiene el método Volar ()
en dos lugares (Ave y Pegaso). Si cambia uno, debe recordar cam biar el otro. Claro que
un desarrollador que le dé mantenimiento a su código meses o años después, debe saber
también que tiene que arreglar ambos lugares.
Sin embargo, pronto tendrá un nuevo problema. Suponga que quiere crear una lista de
objetos Caballo y una lista de objetos Ave. Le gustaría poder agregar sus objetos Pegaso
a las dos listas, pero si Pegaso es un Caballo, no puede agregarlo a una lista de Aves.
Hay un par de soluciones potenciales. Puede cam biar el nom bre del m étodo Galopar ()
de Caballo a Mover(), y luego puede redefinir Mover () en su objeto Pegaso para que
haga el trabajo de Volar (). Podría entonces redefinir el m étodo Mover () de sus otros
caballos para que hagan el trabajo de Galopar (). Pegaso podría ser lo suficientemente
listo como para galopar distancias cortas y volar distancias más largas.
Pegaso::Mover(long distancia)
{
if (distancia > muyLejos)
volar(distancia);
else
galopar(distancia);
>
Esto es un poco limitante. Tal vez un día Pegaso quiera volar una distancia corta o galopar
una distancia larga. Otra solución podría ser definir el método Volar () en la clase Caballo,
como se muestra en el listado 13.1. El problema es que los caballos no pueden volar, por
lo que tendría que hacer que este método no hiciera nada a m enos que fuera un Pegaso.

L istado 13.1 Si los caballos pudieran volar...

1: // Listado 13.1. Si los caballos pudieran volar...


2: // Infiltrando a Volar() en Caballo
3:
4: #include <iostream.h>
5:
6: class Caballo
7: {
8: public:
9: void Galopar(){ cout « “Galopando...\n“; }
10 : virtual void Volar() { cout « “Los caballos no pueden volar.\n" ; }
11 : private:
12 : int suEdad;
13:
P o lim orfism o 415

15: class Pegaso : public Caballo


16: {
17: public:
18: vir tua l void Volar() { cout <<
»-■ "¡Puedo volar! ¡Puedo volar! ¡Puedo volar!\n"; }
19: };
20 :
21: const int NumeroCaballos = 5;
22: int main()
23: {
24: Caballo* Rancho[NumeroCaballos];
25: Caballo* apCaballo;
26: int opcion,i;
27: for (i=0; i<NumeroCaballos; i++)
28: {
29: cout « "(1)Caballo (2)Pegaso:
30: cin » opcion;
31: i f (opcion == 2)
32: apCaballo = new Pegaso;
33: else
34: apCaballo = new Caballo;
35: Rancho[i] = apCaballo;
36: }
37: cout « "\n";
38: for (i=0; i<NumeroCaballos; i++)
39: {
40: Rancho[i]->Volar();
41: delete Rancho[i];
42: }
43: return 0;
44: }

(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

Los caballos no pueden volar.


¡Puedo volar! ¡Puedo volar! ¡Puedo volar!
Los caballos no pueden volar.
¡Puedo volar! ¡Puedo volar! ¡Puedo volar!
Los caballos no pueden volar.

Este program a evidentem ente funciona, aunque con la desventaja de que la


A n á l is is
clase C aballo tiene un método Vol a r (). En la línea 10 se le proporciona a C aballo
el método V olar (). En una clase real, esta solución podría hacer que el compilador em itiera
un error, o que el programa fallara silenciosamente. En la línea 18, la clase Pegaso redefine
el método Vol ar () para “que haga lo correcto”, que se representa aquí con la im presión
de un feliz mensaje.
416 Día 13

El arreglo de apuntadores a Caballo de la línea 24 se utiliza para demostrar que se llama


al método V o la r() apropiado, con base en el enlace en tiem po de ejecución del objeto
C aballo o del objeto Pegaso.

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.

El apuntador principal será examinado en tiempo de ejecución. Si la conversión es correcta,


su nuevo apuntador a Pegaso estará bien definido. Si la conversión es incorrecta, quiere
decir que realmente no tenía un objeto Pegaso, y su nuevo apuntador será nulo. El listado
13.2 ejem plifica este punto.

Entrada L is t a d o 1 3 . 2 Conversión descendente

1: // Listad o 13.2 Uso de dynamic_cast.


2: // Uso de r t t i
O
O•.
4: #include <iostream.h>
5: enum TIPO { CABALLO, PEGASO };
b.
7: c l a s s Caballo
8: {
9: p u b lic :
10: v i r t u a l void G alopar(){ cout « "Galopando...'
11 :
12: p riva te :
13: in t suEdad;
14: };
15:
16: c la s s Pegaso : p u b lic Caballo
17: {
18: p u b lic :
19:
20: v i r t u a l void V o la r() { cout «
»"¡Puedo v o la r! ¡Puedo volar! ¡Puedo v o la r!\ n "; }
21 :
continua
418 Día 13

L istado 13.2 continuació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

Respuesta: Éste es u n o d e los m en sa jes de error más co n fuso s del c o m p ila d o r G N U .


D e s a fo rtu n a d a m e n te , la ve rsió n q u e usted utiliza tal vez no tiene soporte para RTTI, a ú n
cu a n d o su g ie re fo rm a s d e s o lu c io n a r esto.
Puede p ro b a r v o lv ie n d o a co m p ila r con la opción -frtti, pero si recibe la advertencia de q u e
no se reconoce la opción, en tonces su plataform a y versión específicas n o tienen so p o rte para
esta capacidad.
La versión 2.9.5 s o p o rta RTTI de m a n e ra predeterminada.

Esla solución tam bién funciona. V o l a r () se deja fuera de C ab allo , y no se llama


A n á l is is
en objetos C a b a llo . Sin embargo, al llamar a este método en objetos Pegaso, éstos
se deben convertir en form a explícita; los objetos C a b a llo no tienen el método V o l a r ().
por esta razón, el apuntador nos debe indicar que está apuntando a un objeto Pegaso antes
de usarlo.

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.

Cómo agregar objetos a dos listas


El otro problem a con estas soluciones es que com o declaró Pegaso com o un tipo de
Caballo, no puede agregar un objeto Pegaso a una lista de Aves. Ya pagó el precio de pasar
el método V o la r () a la clase C a b a llo o de realizar una conversión descendente del apunta­
dor. y aún así no obtiene toda la funcionalidad que necesita.
Una última solución con herencia simple se presenta a sí misma. Puede enviar a V o la r ().
R e lin c h a r ( ) y G a lo p a r () hacia una clase base común para Ave y Caballo: Animal. Ahora,
en lugar de tener una lista de Aves y una lista de Caballos, puede tener una lista unificada
de Animales. Esto funciona, pero filtra más funcionalidad dentro de la clase base.
C om o una alternativa, puede dejar los m étodos en donde están y rea liz a r la conversión
descendente de los objetos C a b a llo , Ave y Pegaso, ¡pero eso es peor!

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.

Entrada L is t a d o 1 3 . 3 Herencia múltiple

1: // Listad o 13.3. Herencia m últiple.


2: // Herencia m últiple
3:
4: ^include <iostream.h>
5:
6 : c l a s s Caballo
7: {
8: p u b lic :
9: C a b a llo () { cout « "C onstructor de C a b a l l o . . . }
10 v i r t u a l - C a b a llo () { cout « "D e stru c to r de C a b a l l o . . . }
11 v i r t u a l void Relin cha r() const { cout « " ¡ Y i h i i ! . . . }
12 p rivate :
13 in t suEdad;
14 };
15
16 c l a s s Ave
17 {
18 p u b lic :
19 Ave() { cout « "Constructor de Ave ... }
20 virtu a l ~Ave() { cout << "D estru cto r de A v e ...
21 virtu a l void Gorjear() const { cout << " G r i i i . -
22 virtu a l void V o l a r () const
23 {
24 cout « "¡Puedo volar! ¡Puedo v o la r! ¡Puedo v o l a r !
25 }
Polimorfismo 421

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

(1)Ca ba llo (2)Pegaso: 1


S a l id a C onstructor de C a b a llo ...
(1)Caballo (2)Pegaso: 2
Constructor de C a b a llo ... C on structor de A v e ... C o n s t r u c t o r de
Pegaso...
( 1 )Ave (2)Pegaso: 1
Constructor de Ave...
(1)Ave (2)Pegaso: 2
Constructor de C a b a llo ... C onstructor de A v e ... C o n s t r u c t o r de
Pegaso. . .

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

A nálisis En las líneas 6 a 14 se declara la clase C a b a llo . El c o n stru c to r y el d e s tru c to r


im prim en un mensaje, y el método R e l i n c h a r () im prim e la palabra (m ejo r dicho,
la voz onom atopéyica) ¡Y ih ii!
En las líneas 16 a 28 se declara la clase Ave. Además de su constructor y su destructor, esta
clase tiene dos métodos: G o rje a r() y V o la r(), los cuales im prim en m ensajes de identi­
ficación. En un program a real, éstos podrían, por ejem plo, activ ar la bocina o generar
im ágenes animadas.
Finalmente, en las líneas 30 a 36 se declara la clase Pegaso. Se deriva de C a b a l l o y de Ave.
La clase Pegaso redefine el método G o r j e a r () para llamar al m étodo R e l i n c h a r (), el cual
hereda de Caballo.
Se crean dos listas: una llamada Rancho con apuntadores a C a b a l l o en la línea 41. y una
llam ada P a ja re ra con apuntadores a Ave en la línea 42. En las líneas 4 6 a 55 se agregan
objetos C a b a llo y Pegaso a la lista Rancho. En las líneas 56 a 65 se agregan objetos Ave
y Pegaso a la lista Pajarera.

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

Las partes de un objeto con herencia múltiple


Cuando se crea el objeto Pegaso en memoria, ambas clases base forman parte del objeto
Pegaso, como se muestra en la figura 13.1.

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

Constructores en objetos con herencia múltiple


Si Pegaso se deriva tanto de Caballo como de Ave, y cada una de las clases base tiene
constructores que llevan parámetros, la clase Pegaso inicializa estos constructores uno
por uno. El listado 13.4 muestra cómo se hace esto.

E n t r a d a | L istado 13.4 C ó m o llamar varios constructores

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

44 A ve: : Ave(COLOR c o l o r , bool emigra):


45 s u C o l o r ( c o l o r ) , s u M i g r a c i o n ( em igra)
46 {
47 cout << " C o n s t r u c t o r de A v e . . . \ n " ;
48 }
49
50 c l a s s Pegaso : p u b l i c C ab allo , p u b lic Ave
51 {
52 p u b lic :
53 vo id Gor j e a r ( ) co n st { R e lin c h a r ( ) ; }
54 P e g a s o (COLOR, CUARTAS, b o o l,lo n g );
55 -P e g a so () {cout << "D e s t r u c t o r de P e g a so .. . \ n " ;}
56 v i r t u a l lo n g ObtenerNumeroCreyentes() const
57 {
58 re t u rn suNumeroCreyentes;
59 }
60
61 p rivate :
62 long suNum eroCreyentes;
63 };
64
65 Pegaso: : P e g a s o (
66 COLOR a C o lo r,
67 CUARTAS a lt u r a ,
68 bool emigra,
69 long NumCreyen):
70 C a b a llo ( a C o l o r , a l t u r a ) ,
71 Ave(aColor, em igra),
72 suNumeroCreyentes(NumCreyen)
73 {
74 cout << " C o n s t r u c t o r de P e g a so .. . \ n " ;
75 }
76
77 in t main()
78 {
79 Pegaso *apPeg = new Pegaso( R ojo, 5, true, 10);
80 a p P e g -> V o la r();
81 a p P e g - > R e lin c h a r ( );
82 cout << "\n Su Pegaso mide " << apPeg->ObtenerAltura();
83 cout << " c u a rta s de a ltu ra y ";
84 i f (apPeg->O bten erM igracio n())
85 cout << " s i em igra.";
86 else
87 cout << "no em igra.";
88 cout << "\nUn t o t a l de ' << apPeg->ObtenerNumeroCreyentes(
89 cout << " personas creen que s i e x is t e . \ n ";
90 delete apPeg;
91 return 0;
92 \
426 Día 13

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

En las lineas 7 a 18 se declara la clase C a b a llo . El c o n stru c to r lleva dos paráme­


A n á l is is
tros: uno es una e n u m eració n q u e se d e c la ra en la línea 5. y el o tro es un typedet
(definición de tipo) que se declara en la línea 4. La im p lem en tació n del constructoi en las
líneas 20 a 24 sim plem ente in icializa las v a ria b le s m ie m b ro e im p rim e un m ensaje.
En las líneas 26 a 42 se declara la clase Ave. y la im p le m e n ta c ió n de su constructoi se
encuentra en las líneas 44 a 48. De nuevo, la clase Ave lleva dos p aram etios. C uñosamente,
el co n stru cto r de C a b a llo to m a c o lo re s (p ara q u e u sted p u e d a d eteetai c a b allo s de dile-
rentes c o lo re s), y el c o n s tru c to r de Ave to m a el c o lo r d e las p lu m a s ( p a ia que los que
tienen las m ism as p lu m a s p u e d a n m a n te n e rs e ju n to s ) . E s to c o n d u c e a un problem a
cuando q u iere p reg u n ta r al P egaso cuál es su color, lo q u e v e ía en el sig u ie n te ejemplo.

La clase P e g a s o en sí se declara en las líneas 5 0 a 6 3 , y su c o n stiu c to i se encuentra en


las líneas 65 a 75. L a inicialización del objeto P e g a s o in clu y e tres insti ucciones. Piimero,
el c o n stru c to r de C a b a l l o se in icializa con c o lo r y a ltu ra . L u e g o , el c o n stiu c to i de Ave
se inicializa con co lo r y un valo r booleano. F in a lm e n te , se in ic ia liz a la v ariab le miembio
su N u m e ro C re y e n te s del objeto Pegaso. D espués de h a c e r to d o eso. se llam a al cuerpo del
co n stru cto r de Pegaso.
En la función m a in ( ) se crea un a p u n ta d o r a P e g a s o y se u tiliz a p a ra te n e r acceso a los
m étodos de los objetos base.

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

g + + producirá este e rro r de co m p ila c ió n :


request f o r method O b te n e rC o lo r i s ambiguous

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 () ;

Herencia de una clase base compartida


¿Qué pasa si tanto Ave co m o C a b a llo heredan de una clase base com ún, com o A nim al?
La figura 13.2 m uestra cóm o se ve esto.

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.

Entrada Listado 13.5 Clases base com unes

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

91: cout << "Constructor deP e g a so .. . \ n " ;


92: }
93:
94: in t main()
95: {
96: Pegaso *apPeg = new Pegaso(Rojo, 5, true, 10, 2);
97: in t edad = apPeg->ObtenerEdad();
98: cout « "Este Pegaso tiene " « edad << " años de e d a d .\ n ";
99: delete apPeg;
100 : return 0;
10 1 : }

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

Hay varias características interesantes en este listado. La clase A n im a l se declara


A n á l is is
en las líneas 8 a 17. Animal agrega una variable m iem bro llam ada suEdad y dos
métodos de acceso: ObtenerEdad () y AsignarEdad ().

En la línea 25 se declara la clase Caballo derivada de Animal. El co n stru cto r de Caballo


ahora tiene un tercer parámetro, edad, mismo que pasa a su clase base. Animal. Observe
cjue la clase Caballo no redefine a ObtenerEdad (); sim plem ente la hereda.

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.

E sto se resuelve en la línea 76 cuando el objeto Pegaso redefine a ObtenerEdad () para


que no haga nada más que una cadena ascendente, es decir, llam ar al m ism o m étodo de
una clase base.
P o lim o rfism o 431 |

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.

El listado 13.6 modifica el listado 13.5 para aprovechar la herencia virtual.

aws L istado 1 3 .6 Muestra del uso de la herencia virtual

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

L istado 13.6 continuación

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

Pegaso ya no tiene que reso lv e r la am bigüedad en la llam ada a O b t e n e r E d a d ( ). por lo


que tiene la libertad de heredar sim plem ente esta función de Animal. O bserve que P e g a s o
aún debe resolver la am bigüedad en la llamada a O b te n e r C o lo r () debido a que esta función
se encuentra en sus dos clases base y no en Animal.

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

Problemas con la herencia múltiple


A unque la h e re n c ia m ú ltip le ofrece varias ventajas sobre la herencia sim ple, m uchos
program adores de C++ se m uestran renuentes a usarla. Los problem as que citan son que
m uchos com piladores aún no la soportan, que dificulta la depuración, y que casi todo lo
que se puede h acer con la herencia m últiple se puede hacer sin ella.

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

Tipos de datos abstractos


Con frecuencia, usted creará una jerarquía de clases en conjunto. Por ejem plo, podría
crear una clase Figura y derivar de ésta las clases Rectángulo y Circulo. De Rectángulo,
podría derivar a Cuadrado como un caso especial de Rectángulo.

Cada una de las clases derivadas redefinirá el método Dibujar(), el m étodo


ObtenerArea(), y así sucesivamente. El listado 13.7 m uestra una im plem entación
simple de la clase Figura y de sus clases derivadas Circulo y Rectángulo.
Polimorfismo 437

Entrada L i s t a d o 13.7 C la se s d e F igu ra

1: //Listado 13.7. C la se s de Figura.


2:
3: //include <iostream.h>
4:
5:
6: c la s s Figura
7: {
8: public:
9: Figu ra (){}
10 : v irtu a l - F i g u r a n o
11: v i r t u a l long ObtenerArea() { return -1; }
12: v i r t u a l long ObtenerPerim() { return -1; }
13: v i r t u a l void D ib u ja r() {}
14: private:
15: };
16:
17: c l a s s C ir c u l o : p u b lic Figura
18: {
19: public:
20: C i r c u l o ( i n t r a d i o ) : suRadio( radio) {}
21: -C irc u lo (){}
22: long ObtenerArea() { return 3 * suRadio * suRadio; }
23: long ObtenerPerim () {return 6 * suRadio; }
24: void D i b u j a r ( );
25: private:
26: in t suRadio;
27: in t s u C ir c u n f e r e n c ia ;
28: };
29:
30: void C i r c u l o : : D i b u j a r ( )
31: {
32: cout << "¡A q u i va la rutina para dibujar un C i r c u l o ! \n";
33: }
34:
35:
36: c la s s Rectángulo : pu blic Figura
37: {
38: public:
39: R e cta n g u lo (in t longitud, int ancho):
40: s u L o n g itu d (lo n g i t u d ) , suAncho(ancho) {}
41: v i r t u a l -R e c ta n g u lo (){}
42: v i r t u a l long ObtenerArea() { return suLongitud * suAncho; }
43: v i r t u a l long ObtenerPerim(){return 2*suLongitud +2*suAncho; }
44: v i r t u a l in t ObtenerLongitud() { return suLongitud; }
45: v i r t u a l in t ObtenerAncho() { return suAncho; }
46: v i r t u a l void D ib u ja r();
47: private:
48: in t suAncho;

l'01111111111
L is ta d o 13.7 continuación

49: int suLongitud;


50:
51:
52: void Rectángulo::Dibujar()
53: {
54: for (int i = 0; i<suLongitud; i++)
55: {
56: for (int j = 0; j<suAncho; j++)
57: cout « “x
58:
59: cout « "\n";
60: }
61: }
62:
63: class Cuadrado : public Rectángulo
64: {
65: public:
66 : Cuadrado(int longitud);
67: Cuadrado(int longitud, int ancho);
68 : -Cuadrado(){>
69: long ObtenerPerim() {return 4 * ObtenerLongitud (); }
70: >;
71:
72: Cuadrado::Cuadrado(int longitud):
73: Rectángulo(longitud,longitud)
74: {>
75:
76: Cuadrado:¡Cuadrado(int longitud, int ancho):
77: Rectángulo(longitud,ancho)
78:
79:
80: if (ObtenerLongitud() l= ObtenerAncho())
81: cout « "Error, no es un Cuadrado... ¿un Rectangulo??\n";
82: }
83:
84: int main()
85: {
86 : int opcion;
87: bool fSalir = false;
88 : Figura * sp;
89:
90: while (i fSalir)
91 : {
92: cout « "(1)Circulo (2)Rectangulo (3)Cuadrado (0 )Salir: "
93: cin » opcion;
94:
95: switch (opcion)
96: {
Polimorfismo 439

97: case 0: f S a l i r = true;


98: b re a k ;
99: case 1 : sp = new C ir c u lo (5 );
100 b re a k ;
101 case 2: sp = new R ecta ng ulo (4,6 );
102 b re a k ;
103 case 3: sp = new Cuadrado(5);
104 b re a k ;
105 d e f a u lt: cout « "E scrib a un numero entre 0 y 3
• » « endl;
106 c o n t in u e ;
107 b re a k ;
108 }
109 i f ( f S a l i r ) break;
110 s p - > D i b u j a r ( );
111 delete sp;
112 cout « " \ n " ;
113 }
114 return 0;
115: }

( 1 ) C irc u lo ( 2 ) Rectángulo (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

( 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

( 1 ) C irc u lo (2)Rectangulo (3)Cuadrado ( 0 ) S a l ir : 0

En las líneas 6 a I5 se declara la clase Figu ra . Los métodos O b t e n e r A r e a ( ) y


ObtenerPerim () regresan un valor de error, y D i b u j a r () no realiza ninguna acción.
Después de todo, ¿qué significa dibujar una F i g u r a ? Sólo se pueden dibujar tipos de
figuras (círculos, rectángulos, etc.); Com o es una abstracción, no se puede d ib u jar una
Figura.

C i r c u l o se deriva de F i g u r a y redefine los tres métodos virtuales. O bserve que no hay


motivo para agregar la palabra “virtual", ya que es parte de su herencia. Pero no hay nada
malo en hacerlo, com o se m uestra en la clase Rectángulo en las líneas 42. 43, y 46. Es
una buena idea incluir el término virtual como recordatorio, o una forma de docum entación.

Cuadrado se deriva de R e c tá n g u lo , y tam bién redefine el m étodo O b te n e rP e rim ( ).


heredando el resto de los métodos definidos en Rectángulo.
440 Día 13

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.

Funciones virtuales puras


C++ soporta la creación de tipos de datos abstractos con funciones virtuales puras,
función virtual se convierte en pura inicializándola con cero, como en
virtual void Dibujar() = 0;
Cualquier clase que tenga una o más funciones virtuales puras es un ADT, y es ilegal
instanciar un objeto de una clase que sea ADT. Si se trata de hacer esto se producirá
error en tiempo de compilación. Al colocar una función virtual pura en su clase, les es
indicando dos cosas a los clientes de su clase:
• No crear un objeto de esta clase; hacer derivaciones de ella.
• Asegurarse de redefinir la función virtual pura.
Cualquier clase que se derive de un ADT hereda la función virtual pura como pura, por lo
que debe redefinir cada función virtual pura si quiere crear instancias de objetos. Por
ejemplo, si Rectángulo hereda de Figura, y Figura tiene tres funciones virtuales puras.
Rectángulo debe redefinir esas tres funciones; si no lo hace, será también un ADT. El
listado 13.8 vuelve a utilizar la clase Figura y la modifica para convertirla en un tipo de
datos abstracto. Para ahorrar espacio, aquí no se reproduce el resto del listado 13.7.
Reemplace la declaración de Figura del listado 13.7, líneas 7 a 16, con la declaración
de Figura del listado 13.8 y ejecute el programa de nuevo.

1 3 ¡ Ü ¡ Q Q L istado 13.8 Tipos de datos abstractos

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

( 1 (C ir c u lo ( 2 ) Rectángulo (3)Cuadrado ( 0 ) S a lir : 0

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

Implementadón de funciones virtuales puras


Por lo general, las funciones virtuales puras de una clase base abstracta no se im plem entan.
Como no se crean objetos de ese tipo, no hay razón para proporcionar im plem entaciones.
y el ADT trabaja simplemente como la definición de una interfaz para los objetos derivados
a partir de él.
Sin em bargo, es posible proporcionar una implementación para una función virtual pura.
La función puede entonces ser llamada por objetos que se deriven del ADT, tal vez para
proporcionar una funcionalidad común para todas las funciones redefinidas. El listado 13.9
reproduce el listado 13.7, esta vez con F ig u ra como un ADT y con una im plem entación
para la función virtual pura D i b u j a r (). La clase C i r c u l o redefine a D i b u j a r ! ). com o se
debe, pero luego hace una cadena ascendente para la función de la clase base para obtener
una funcionalidad adicional.
=5

442 Día 13

En este ejemplo, la funcionalidad adicional es simplemente un m ensaje adicional impreso,


pero podemos imaginar que la clase base proporciona un m ecanism o de dibujo com partido,
tal vez preparar una ventana que utilizarán todas las clases derivadas.

Entrada L is t a d o 1 3 . 9 Im p le m e n ta ció n de fu n c io n e s v irtu a le s p u r a s

1: //Implementación de funciones v irt u a le s puras


2:
3: #include <iostream.h>
4:
5: c la s s Figura
6: {
7: public:
8: F ig u ra (){}
9: v i r t u a l ~F ig u ra (){}
10: v ir t u a l long ObtenerArea() = 0;
11: v ir t u a l long ObtenerPerim()= 0;
12: v ir t u a l void Dibujar() = 0;
13: private:
14: };
15:
16: void F igu ra ::D ib u ja r()
17: {
18: cout « "¡Mecanismo abstracto de d ib u jo !\ n ";
19: }
20:
21 : c la s s Circulo : public Figura
22 : {
23: public:
24: C irc u lo (in t r a d io ) :suR adio (rad io ){}
25: v i r t u a l ~C ircu lo (){}
26: long ObtenerArea() { return 3 * suRadio * suRadio; }
27: long ObtenerPerim() { return 9 * suRadio; }
28: void D ib u ja r();
29: private:
30: in t suRadio;
31 : in t suCircunferencia;
32: };
33:
34: void C ir c u lo ::D ib u ja r ()
35: {
36: cout « "¡Aqui va una rutina para dibuja r un C i r c u l o ! \ n " ;
37: F ig u ra ::D ib u ja r();
38: }
39:
40:
41 : c la s s Rectángulo : public Figura
42 : {
43: public:
Polimorfismo

44 R e cta n g u lo (in t longitud, int ancho):


45 s u L o n g itu d ( l o n g i t u d ) , suAncho(ancho){}
46 v i r t u a l -Rectángulo) ) {}
47 long ObtenerArea() { return suLongitud * suAncho; }
48 long ObtenerPerim )) {return 2*suLongitud + 2*suAncho; }
49 v i r t u a l in t ObtenerLongitud() { return suLongitud; }
50 v i r t u a l in t ObtenerAncho() { return suAncho; }
51 void D i b u j a r ( ) ;
52 p rivate :
53 in t suAncho;
54 in t suLongitud;
55
56
57 void R ectángulo : : D i b u j a r ( )
58 {
59 fo r ( in t i = 0; i< s u L o n g itu d ; i++)
60 {
61 f o r ( in t j = 0; j<suAncho; j++)
62 cout << "x ";
63
64 cout << " \ n " ;
65 }
66 F i g u r a : : D i b u j a r ( );
67
68
69
70 c l a s s Cuadrado : p u b lic Rectángulo
71 {
72 p u b lic :
73 Cuadrado(int lo ngitud );
74 Cuadrado(int longitud, int ancho);
75 v i r t u a l -C uadrado)) { }
76 long ObtenerPerim() {return 4 * ObtenerLongitud( ) ; }
77
78
79 Cuadrado::Cuadrado( in t lo n g it u d ) :
80 Rectángulo ( l o n g i t u d ,longitud)
81 {}
82
83 Cuadrado::Cuadrado( int longitud, int ancho):
84 Rectángulo(longitud,ancho)
85
86 {
87 if (ObtenerLongitudf) != ObtenerAncho())
88 cout << "E rro r, no es un cuadrado... ¿un R e c t a n g u lo ??\ n ";
89 }
90
91 in t main()
92 {
93 in t opcion;
94 bool f S a l i r = fa lse ;

t <'iilimid
|444 Dia 13

Listado 13.9 continuación

95: Figura * sp;


96:
97: while (1)
98: {
99: cout « "(1)Circulo (2)Rectangulo (3)Cuadrado (0)Salir:
100 cin » opcion;
101
102 switch (opcion)
103 {
104 case 1: sp = new Circulo(5);
105 break;
106: case 2: sp = new Rectangulo(4,6);
107: break;
108: case 3: sp = new Cuadrado (5);
109: break;
110 : default: fSalir = true;
111: break;
112 : }
113: if (fSalir)
114: break;
115:
116: sp->Dibujar();
117: delete sp;
118: cout « "\n";
119: }
120 : return 0;
121 :

(1)Circulo (2)Rectangulo (3)Cuadrado (0)Salir: 2


E ntrada x x x x x x
x x x x x x
x x x x x x
x x x x x x
¡Mecanismo abstracto de dibujo!

(l)Circulo (2)Rectangulo (3)Cuadrado (0)Salir: 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
¡Mecanismo abstracto de dibujo!

(1JCirculo (2)Rectangulo (3)Cuadrado (0)Salir: 0


Polimorfismo 445
____ !

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.

Jerarquías de abstracción complejas


Algunas veces tendrá la necesidad de derivar ADTs de otros ADTs. Tal vez lo hará porque
necesitará cpie algunas de las funciones virtuales puras dejen de ser puras, y que otras
se queden como puras.
Si crea la clase Animal, puede hacer que Comer (), Dormir (). Mover () y R e p ro d u c ir ()
sean funciones virtuales puras. Tal vez quiera derivar a Mamif ero y a Pez de Animal.

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.

En t r a d a L is t a d o 1 3 . 1 0 D e riv a ció n de A D T s de otros A D T s

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

15: virtual void Comer() const = 0;


16: virtual void Reproducir() const = 0;
17: virtual void Mover() const = 0;
18 virtual void Hablar() const = 0;
19 private:
20 int suEdad;
21
22
23 Animal::Animal(int edad):
24 suEdad(edad)
25 {
26 cout « "Constructor de Animal...\n";
27 }
28
29 class Mamifero : public Animal
30 {
31 public:
32 Mamifero(int edad):Animal(edad)
33 { cout « "Constructor de Mamifero...\n";}
34 virtual ~Mamifero() { cout « "Destructor de Mamifero...\n";}
35 virtual void Reproducir() const
36 { cout « "Reproducción de Mamifero representada...\n"; }
37 }5
38
39 class Pez : public Animal
40 {
41 public:
42 Pez(int edad):Animal(edad)
43 { cout « "Constructor de Pez...\n";}
44 virtual ~Pez() {cout « "Destructor de Pez...\n"; }
45 virtual void Dormir() const { cout « "Pez roncando...\n"; }
46 virtual void Comer() const { cout « "Pez comiendo...\n"; >
47 virtual void Reproducir() const
48 { cout « "Pez poniendo huevos...\n"; }
49 virtual void Mover() const
50 { cout « "Pez nadando...\n"; }
51 virtual void Hablar() const { \
52 };
53
54 class Caballo : public Mamifero
55 {
56 public:
57 Caballo(int edad, COLOR color):
58 Mamifero(edad), suColor(color)
59 { cout « "Constructor de Caballo...\n"; }
60 virtual -Caballo() { cout « "Destructor de Caballo...\n"; }
61 virtual void Hablar()const { cout « 11¡Yihii!... \n"; >
62 virtual COLOR ObtenerSuColor() const { return suColor; }
Polimorfismo 447

63 v i r t u a l void Dormir() const


64 { cout << "Caballo roncando.. . \ n " ; }
65 v i r t u a l void Comer() const { cout « "Caballo comiendo.. . \ n " ; }
66 v i r t u a l void Mover() const { cout « "Caballo corriendo. . . \ n " ;}
67
68 protected:
69 COLOR suColor;
70 };
71
72 c la s s Perro : public Mamifero
73 {
74 p u b lic :
75 P e rro (in t edad, COLOR color):
76 Mamifero(edad), suColor(color)
77 { cout « "Constructor de P e rro ...\n"; }
78 v i r t u a l -Pe rro() { cout « "Destructor de P e rro ...\n"; }
79 v i r t u a l void Hablar ( )const { cout « "¡Guau!... \n"; }
80 v i r t u a l void Dormir() const { cout « "Perro roncando. . . \ n " ; }
81 v i r t u a l void Comer() const { cout « "Perro comiendo. . . \ n " ; }
82 v i r t u a l void Mover() const { cout « "Perro corriendo.. . \ n " ; }
83 v i r t u a l void Reproducir() const
84 { cout « "Perro reproduciéndose.. . \ n " ; }
85
86 protected:
87 COLOR suColor;
88 };
89
90 int main()
91 {
92 Animal *apAnimal=NULL;
93 in t opcion;
94 bool f S a l i r = false;
95
96 while ( 1 )
97 {
98 cout « " ( 1 )perro ( 2 )Caballo (3)Pez (0)Salir:
99 cin >> opcion;
100
101 switch (opcion)
102 {
103 case 1: apAnimal = new Perro(5,Cafe);
104 bpg 3 k *
105 case 2: apAnimal = new Caballo(4,Negro);
106 break;
107 case 3: apAnimal = new Pez (5);
108 break;
109 default: f S a l i r = true;
110 break;
111 }
112 if (fSa lir)
113 break;
,•<»111111111
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: } _______

(1)Perro (2)Caballo (3)Ave (0 )Salir: 1


S alida Constructor de Animal...
Constructor de Mamífero...
Constructor de Perro...
iGuau 1...
Perro comiendo...
Perro reproduciéndose__
Perro corriendo...
Perro roncando...
Destructor de Perro...
Destructor de Mamífero...
Destructor de Animal...
(1)Perro (2)Caballo (3)Ave (0)Salir: 0

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

¿Q u é tip o s son ab stracto s?


En algunos programas, la clase Animal es abstracta, en otros no lo es. ¿Qué es lo que
determina si una clase se hace abstracta o no?
La respuesta a esta pregunta no se decide por un factor intrínseco del mundo real, sino por
lo que tenga sentido en el programa. Si está escribiendo un programa que represente una
granja o un zoológico, tal vez quiera que Animal sea un tipo de datos abstracto, pero que
Perro sea una clase de la que pueda distanciar objetos.
Por otro lado, si está haciendo una perrera animada, tal vez quiera dejar a Perro como un
tipo de datos abstracto y sólo inslanciar tipos de perros: Retrievers, Terriers, y así sucesi­
vamente. El nivel de abstracción depende de la nitidez con la que necesite diferenciar sus
tipos.

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.

Unas palabras sobre la herencia múltiple, los tipos


de datos abstractos y Java
Muchos programadores de C++ saben que Java se basó, en gran parte, en C++, y aún así
los creadores de Java optaron por omitir la herencia múltiple. Su opinión fue que la herencia
múltiple presentaba complejidad que interfería con la facilidad de uso de Java. Ellos
sintieron que podían cubrir el 90% de la funcionalidad de la herencia m últiple utilizando
lo que se conoce como interfaces.

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

5. D eclare una clase llam ada v e h ic u lo y conviértala en un tipo de datos abstracto.


6 . Si una clase base es un A D T y tiene tres funciones virtuales puras, ¿cuántas de
estas funciones se deben redefinir en sus clases derivadas?

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

Datos miembro estáticos


Hasta ahora probablemente ha pensado que los datos de cada objeto son sólo
para ese objeto y no se comparten entre objetos de una clase. Por ejemplo, si tiene
cinco objetos Gato, cada uno tiene su propia edad, peso y otros datos. La edad de
uno no afecta la edad de otro.
456 Día 14

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.

L is t a d o 14.1 Datos miembro estáticos

1: //Listado 14.1 datos miembro estáticos


2:
3: //include <iostream.h>
4:
5: class Gato
6: {
7: public:
8: Gato(int edad) :suEdad(edad){CuantosGatos++; >
9: virtual ~Gato() { CuantosGatos— ; }
10: virtual int ObtenerEdad() { return suEdad; }
11 : virtual void AsignarEdad(int edad) { suEdad = edad; }
12: static int CuantosGatos;
13:
14: private:
15: int suEdad;
16:
17:
18:
19: int Gato:¡CuantosGatos = 0;
20 :
21 : int main()
22: {
23: const int MaxGatos = 5; int i;
24: Gato *CasaGatos[MaxGatos] ;
25: for (i = 0; i<MaxGatos; i++)
26: CasaGatos[i] = new Gato(i);
27:
28: for (i = 0; i<MaxGatos; i++)
29: {
30: cout « “¡Quedan ";
31 : cout « Gato::CuantosGatos;
32: cout « " gatos!\n";
33: cout « "Se va a eliminar el que tiene ";
Clases y fundones especiales 457

34: cout << CasaGatos[i]->ObtenerEdad() ;


35: cout << " años de edad\n";
36: delete CasaGatos[i];
37: CasaGatos[i] = 0;
38: }
39: return 0;
40: }

¡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

El constructor para Gato incrementa la variable miembro estática en la línea 8. El destructor


la decrementa en la línea 9. Así que, en cualquier momento dado, CuantosGatos tiene una
medición exacta de cuántos objetos Gato fueron creados y aún no son destruidos.
El programa controlador de las líneas 21 a 40 crea instancias de cinco Gatos y los coloca
en un arreglo. Esto llama a cinco constructores de Gato, y por consecuencia CuantosGatos
se incrementa cinco veces a partir de su valor inicial 0.
Luego, el programa avanza con un ciclo a través de cada una de las cinco posiciones del
arreglo e imprime el valor de CuantosGatos antes de eliminar el apuntador a Gato actual.
La impresión refleja que el valor de inicio es 5 (después de todo, se construyen 5), y que
cada vez que se ejecuta el ciclo queda un Gato menos.
Observe que CuantosGatos es una variable pública y main () tiene acceso directo a ella.
No existe motivo alguno para exponer esta variable miembro de esta manera. Es preferible
hacerla privada junto con las otras variables miembro y proporcionar un método público
de acceso, siempre y cuando se tenga acceso a los datos siempre a través de una instancia de
Gato. Por otro lado, si quiere tener acceso a estos datos en forma directa, sin tener necesaria­
mente un objeto Gato disponible, tiene dos opciones: mantener la variable pública, como se
muestra en el listado 14.2, o proporcionar una función miembro estática, como se explicará
más adelante en este día.

L istado 14.2 Acceso a los miembros estáticos sin un objeto

1: //Listado 14.2 datos miembro estáticos


2:
3: #include <iostream.h>
4:
5: class Gato
6: {
7: public:
8: Gato(int edad) :suEdad(edad){CuantosGatos++; >
9: virtual ~Gato() { CuantosGatos— ; }
10 virtual int ObtenerEdad() { return suEdad; >
11 : virtual void AsignarEdad(int edad) { suEdad = edad; }
12: static int CuantosGatos;
13:
14: private:
15: int suEdad;
16:
17: };
18:
19: int Gato:‘
.CuantosGatos = 0;
20:
21 : void FuncionTelepaticaO ;
22:
23: int main()
Clases y funciones especiales 459

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

¡Hay 1 gatos vivos!


S a l id a ¡Hay 2 gatos vivos!
¡Hay 3 gatos vivos!
¡Hay 4 gatos vivos!
¡Hay 5 gatos vivos!
¡Hay 4 gatos vivos!
¡Hay 3 gatos vivos!
¡Hay 2 gatos vivos!
¡Hay 1 gatos vivos!
¡Hay 0 gatos vivos!

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

L istado 14.3 Acceso a los m iem bros estáticos p o r m e d io de fu n cio n e s


Entrada m iem bro que no son estáticas

1: //Listado 14.3 datos miembro estáticos privados


2:
3: #include <iostream.h>
4:
5: class Gato
6: {
7: public:
8: Gato(int edad):suEdad(edad){CuantosGatos++; }
9: virtual -Gato() { CuantosGatos— ; >
10 virtual int ObtenerEdad() { return suEdad; >
11 virtual void AsignarEdad(int edad) { suEdad = edad; }
12 virtual int ObtenerCuantos() { return CuantosGatos; }
13
14
15 private:
16 int suEdad;
17 static int CuantosGatos;
18 >;
19
20 int Gato::CuantosGatos = 0;
21
22 int main()
23 {
24; const int MaxGatos = 5; int i;
25: Gato *CasaGatos[MaxGatos];
26; for (i = 0; i<MaxGatos; i++)
27: CasaGatos[i] = new Gato(i);
28:
29: for (i = 0; i<MaxGatos; i++)
30: {
31 : cout « "¡Quedan ";
32: cout « CasaGatos[i]->ObtenerCuantos();
33: cout « " gatos !\n";
34: cout « "Se va a eliminar el que tiene ";
35: cout « CasaGatos[i]->ObtenerEdad()+2;
36: cout « " años de edad\n";
37: delete CasaGatos[i] ;
38: CasaGatos[i] = 0;
39: }
40: return 0;
41 : }

¡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

A nálisisEn la línea 17 se declara la variable miembro estática CuantosGatos con acceso


privado. Ahora no puede tener acceso a esta variable desde funciones que no sean
miembro, como FuncionTelepatica del listado anterior.
Aún cuando CuantosGatos es estática, sigue dentro dél alcance de la clase. Cualquier fun­
ción de la clase, como ObtenerCuantos (), puede tener acceso a ella, así como las funciones
miembro pueden tener acceso a cualquier dato miembro. Sin embargo, para que una función
pueda llamar a ObtenerCuantos (), debe tener un objeto desde el que pueda llamarla.

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.

Funciones miembro estáticas


Las funciones miembro estáticas son como las variables miembro estáticas: no existen
en un objeto sino en el alcance de la clase. Por ende, se pueden llamar sin necesidad de
tener un objeto de esa clase, como se muestra en el listado 14.4.

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

1: //Listado 14.4 Funciones miembro estáticas


2:
3: tfinclude <iostream.h>
4:
5: class Gato
6: {
7: public:
8: Gato(int edad) :suEdad(edad){CuantosGatos++; }
9:
10:
virtual -Gato() { CuantosGatos— ; }
virtual int ObtenerEdad() { return suEdad; } 14
11: virtual void AsignarEdad(int edad) { suEdad = edad; }
12: static int ObtenerCuantos() { return CuantosGatos; }
13: private:
14: int suEdad;

continua
462 Día 14

L istado 1 4 .4 continuación

15: static int CuantosGatos;


16: >;
17:
18: int Gato::CuantosGatos = 0;
19:
20: void FuncionTelepatica();
21:
22: int main()
23: {
24: const int MaxGatos = 5;
25: Gato *CasaGatos[MaxGatos]; int i;
26: for (i = 0; i<MaxGatos; i++)
27: {
28: CasaGatos[i] = new Gato(i);
29: FuncionTelepatica();
30: }
31:
32: for (i = 0; i<MaxGatos; i++)
33: {
34: delete CasaGatos[i];
35: FuncionTelepatica();
36: }
37: return 0;
38: }
39:
40: void FuncionTelepatica()
41: {
42: cout « "¡Hay " « Gato: :ObtenerCuantos() « " gatos vivos! \n";
43: >

¡Hay 1 gatos vivos!


S a lid a ¡Hay 2 gatos vivos!
¡Hay 3 gatos vivos!
¡Hay 4 gatos vivos!
¡Hay 5 gatos vivos!
¡Hay 4 gatos vivos!
¡Hay 3 gatos vivos!
¡Hay 2 gatos vivos!
¡Hay 1 gatos vivos!
¡Hay 0 gatos vivos!

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.

Las funciones miembro estáticas no tienen un apuntador this. Por lo tanto, no


Nota se pueden declarar como const. Además, como lasvariables de datos miembro se
acceden en funciones miembro mediante el apuntador this, ¡las funciones
miembro estáticas no pueden tener acceso a ninguna variable miembro que
no sea estática!

Funciones miembro estáticas


Puede tener acceso a las funciones miembro estáticas llamándolas desde un objeto de la
clase, como lo hace con cualquier otra función miembro, o puede llamarlas sin un objeto si
identifica completamente el nombre de la clase y del método.
He aquí un ejemplo:
class Gato
{
public:
static int ObtenerCuantos () { return CuantosGatos; >
prívate:
static int CuantosGatos;
};
int Gato::CuantosGatos =0;
int main()
{
int cuantos;
Gato elGato; // definir un gato
cuantos = elGato.ObtenerCuantos(); I I acceso a través de un objeto
cuantos = Gato::ObtenerCuantos(); // acceso sin un objeto
>

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.

E ntrada L is t a d o 14.5 Apuntadores a funciones

1: // Listado 14.5 Uso de apuntadores a funciones


2:
3: #include <iostream.h>
4:
5: void Cuadrado (int&,int&);
6: void Cubo (int&, int&);
7: void Intercambiar (int&, int &);
8: void ObtenerValores(int&, int&);
9: void ImprimirValores(int, int);
10
11 int main()
12 {
13 void (* apFunc) (int &, int &);
14 bool fSalir = false;
15
16 int valUno=1, valDos=2;
Clases y funciones especiales 465J

17: int opcion;


18: while (fSalir == false)
19: {
20: cout << "(0)Salir (l)Cambiar Valores (2)Cuadrado
»*(3)Cubo (4)Intercambiar: ";
21 cin » opcion;
22 switch (opcion)
23 {
24 case 1: apFunc ObtenerValores; break;
25 case 2: apFunc Cuadrado; break;
26 case 3: apFunc Cubo; break;
27 case 4: apFunc Intercambiar; break;
28 default fSalir = true; break;
29 >
30
31 if (fSalir)
32 break;
33
34 ImprimirValores (vallino, valDos);
35 apFunc (vallino, valDos);
36 ImprimirValores (vallino, valDos);
37 }
38 return 0;
39 }
40
41 void ImprimirValores(int x, int y)
42 {
43 cout « "x: “ « x « " 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 >
64
65 void Intercambiar(int & rX, int & rY)
66 {
L istado 1 4 .5 continuación

67: int temp;


68: temp = rX;
69: rX = rY;
70: rY = temp;
71: >
72:
73: void ObtenerValores (int & rValUno, int & rValDos)
74: {
75: cout « “Nuevo valor para valUno: ";
76: cin » rValUno;
77: cout « "Nuevo valor para valDos: ";
78: cin » rValDos;
79: >

(0)Salir (1 )Cambiar Valores (2)Cuadrado (3)Cubo (4) Intercambiar: 1


Salida x: 1 y: 2
Nuevo valor para valUno: 2
Nuevo valor para valDos: 3
x: 2 y: 3
(0)Salir (1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 3
x: 2 y: 3
x: 8 y: 27
(0)Salir (l)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 2
x: 8 y: 27
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 (1 )Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 0
En las líneas 5 a 8 se declaran cuatro funciones, cada una con el mismo tipo de
A nálisis
valor de retomo y firma, que regresan void y toman como parámetros dos referen­
cias a enteros.
En la línea 13 se declara apFunc como un apuntador a una función que regresa void y toma
dos parámetros de referencia a enteros. apFunc puede apuntar a cualquiera de las funciones
anteriores. Se ofrece repetidamente al usuario la opción de cuál función invocar, y apFunc
se asigna de acuerdo con la respuesta. En las líneas 34 a 36 se imprime el valor actual de
los dos enteros, se invoca a la función asignada actualmente, y luego se vuelven a impri­
mir los valores.

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

Por qué utilizar apuntadores a funciones


Evidentemente, usted podría escribir el programa del listado 14.5 sin los apuntadores a
funciones, pero el uso de estos apuntadores hace que la intención y el uso del programa sean
explícitos: escoja una función de una lista, y luego invóquela.
El listado 14.6 utiliza los prototipos y las definiciones de funciones del listado 14.5, pero
el cuerpo del programa no utiliza un apuntador a una función. Analice las diferencias entre
estos dos listados.

L is ta d o 14.6 M o d ific a c ió n del listado 14.5, esta vez sin el a p u n ta d o r a u n a


En t r a d a fu n ció n

1: // Listado 14.6 Sin apuntadores a funciones


2:
3: #include <iostream.h>
4:
5: void Cuadrado (int&,int&);
6: void Cubo (int&, int&);
7: void Intercambiar (int&, int &);
8: void ObtenerValores(int&, int&);
9: void ImprimirValores(int, int);
10:
11: int main()
12: {
13: bool fSalir = false;
14: int valUno=1, valDos=2;
15: int opcion;
16: while (fSalir == false)
17: {
18: cout « "(0)Salir (l)Cambiar Valores (2)Cuadrado
**(3)Cubo (4)Intercambiar:
19: cin » opcion;
20: switch (opcion)
continúa
468 Día 14

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

(0)Salir (1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 1


S a l id a x: 1 y: 2
Nuevo valor para valUno: 2
Nuevo valor para valDos: 3
(0)Salir (1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 3
x: 2 y: 3
x: 8 y: 27
(0)Salir (1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 2
x: 8 y: 27
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 (1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 0

Se ha omitido la implementación de las funciones debido a que es idéntica a la que


A nálisis
se proporciona en el listado 14.5. Como puede ver, la salida no cambia, pero el
cuerpo del programa se ha expandido de 22 líneas a 46. Las llamadas a ImprimirValores ()
se deben repetir para cada caso.
1470 Día 14

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.

Uso de arreglos de apuntadores a funciones


Así como puede declarar un arreglo de apuntadores a enteros, también puede declarar un
arreglo de apuntadores a funciones que regresen un tipo de valor específico y que tengan
una firma específica. El listado 14.7 es una reproducción del listado 14.5, pero esta vez se
utiliza un arreglo para invocar todas las opciones al mismo tiempo.

Entrada L istado 14.7 Muestra del uso de un arre glo de a p u n ta d o re s a funciones

1: // Listado 14.7 Muestra del uso de un arreglo de


^apuntadores a funciones
2:
3: #include <iostream.h>
4.
5: void Cuadrado (int&,int&) 9
6: void Cubo (int&, int&);
7: void Intercambiar (int&, int &);
8: void ObtenerValores(int&, int&);
9: void ImprimirValores(int, int);
10:
11: int main()
Clases y fundones especiales 471

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:

21: cin » opcion;


22: switch (opcion)
23: {
24: case 1 :apFuncArreglo[i] = ObtenerValores; break;
25: case 2:apFuncArreglo[i] = Cuadrado; break;
26: case 3:apFuncArreglo[i] = Cubo; break;
27: case 4:apFuncArreglo[i] = Intercambiar; break;
28: default:apFuncArreglo[i] = 0;
29: >
30: }
31:
32: for (i=0;i<MaxArreglo; i++)
33: { I
34: if (apFuncArreglo[i] == 0) i
35: continué; i
36: apFuncArreglo[i] (vallino,valDos);
37: ImprimirValores(vallino, valDos);
38: }
39: return 0;
40: >
41:
42: void lmprimirValores(int x, int y)
43: {
44: cout « "x: " « x « " y: " « y « endl; i
45: !
I
46:
47: void Cuadrado (int & rX, int & rY)
48: I
{
49: rX *= rX;
50: rY *= rY;
51:
52:
53: void Cubo (int & rX, int & rY)
54: {
55: int tmp;
56:
57: tmp = rX;
58: rX *= rX;
59: rX = rX * tmp;
60:
61 : tmp = rY;
62: rY *= rY;
continúa
472 Día 14

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

(1)Cambian Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 1


S alida (1¡Cambiar Valores (2)Cuadrado (3)Cubo (4¡Intercambiar: 2
(1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 3
(1¡Cambiar Valores (2¡Cuadrado (3)Cubo (4¡Intercambiar: 4
(1¡Cambiar Valores (2¡Cuadrado (3)Cubo (4¡Intercambiar: 2
Nuevo valor para valUno: 2
Nuevo valor para valDos: 3
x: 2 y: 3
x: 4 y: 9
x: 64 y: 729
x: 729 y: 64
x: 531441 y:4096
Una vez más se ha omitido la implementación de las funciones para ahorrar espa­
A nálisis
cio, pero es la misma que la del listado 14.5. En la línea 16 se declara el arreglo
apFuncArreglo como un arreglo de cinco apuntadores a funciones que regresan void y
que toman dos referencias de tipo entero.
En las líneas 18 a 30 se pide al usuario que elija las funciones a invocar, y a cada miembro
del arreglo se le asigna la dirección de la función apropiada. En las líneas 32 a 38 se invoca
una por una cada función seleccionada por el usuario. El resultado se imprime después de
cada invocación.

Paso de apuntadores a funciones hacia otras


funciones
Los apuntadores a funciones (y los arreglos de apuntadores a funciones) se pueden pasar
a otras funciones, las cuales pueden realizar cierta acción y luego llamar a la función
apropiada por medio del apuntador.
Clases y funciones especiales 473

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 ista d o 14.8 Paso de apuntadores a funciones com o a rgu m e n to s de una


En tra d a función

1: // Listado 14.8 Sin apuntadores a funciones


2:
3: #include <iostream.h>
4:
5: void Cuadrado (int&,int&);
6: void Cubo (int&, int&);
7: void Intercambiar (int&, int &);
8: void ObtenerValores(int&, int&);
9: void lmprimirValores(void (*)(int&, int&),int&, int&);
10
11 int main()
12 {
13 int valUno=1, valDos=2;
14 int opcion;
15 bool fSalir = false;
16
17 void (*apFunc)(int&, int&);
18
19 while (fSalir == false)
20 {
21 cout « "(0)Salir (l)Cambiar Valores (2)Cuadrado
**(3)Cubo (4)Intercambiar:
22 cin » opcion;
23 switch (opcion)
24 {
25 case 1:apFunc ObtenerValores; break;
26 case 2:apFunc Cuadrado; break;
27 case 3:apFunc Cubo; break;
28 case 4:apFunc Intercambiar; break;
29 default¡fSalir = true; break;
30 >
31 if (fSalir == true)
32 break;
33 ImprimirValores (apFunc, valUno, valDos);
34
35
36 return 0;
37 }
38
39 void ImprimirValores(void *apFunc)(int&, int&),int& x, int& y)
40 {
41 cout << "x: " << x << y: •• « y « endl;
42 apFunc(x,y);
continúa
474 D ía 1 4

L istado 1 4 .8 continuación

43: cout « "x: " « x « " 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
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: }
64:
65: void Intercambiar(int & i
66: {
67: int temp;
68: temp = rX;
69: rX = rY;
70: rY = temp;
71: >
72:
73: void ObtenerValores (int
74: {
75: cout « "Nuevo valor para vallino: ";
76: cin » rValUno;
77: cout « "Nuevo valor para valDos:
78: cin » rValDos;
79:

(0)Salir (1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 1


S a l id a x: 1 y: 2
Nuevo valor para valUno: 2
Nuevo valor para valDos: 3
x: 2 y: 3
(0)Salir (l)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 3
x: 2 y: 3
x: 8 y: 27
(0)Salir (1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 2
x: 8 y: 27
Clases y funciones especiales 475 |

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

En la línea 17 se declara a apFunc como un apuntador a una función que regresa


A nálisis
void y toma dos parámetros, ambos referencias a enteros. En la línea 9 se declara a
imprimirValores como una función que toma tres parámetros. El primero es un apuntador
a una función que regresa void pero que toma como parámetros dos referencias a ente­
ros, y el segundo y tercer argumentos para ImprimirValores son referencias a enteros.
De nuevo se piden al usuario las funciones que se van a llamar, y luego se llama a
ImprimirValores en la línea 33.

Una buena manera de probar el nivel de conocimiento de un programador de C++ es


preguntarle lo que significa la siguiente declaración:
void ImprimirValores(void (*)(int&, int&),int&, int&);

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.

Uso de typ e d e f con ap u n tad o res a fu n cio n es


La construcción void (*) (int&, int&) es, en el mejor de los casos, incómoda. Puede
utilizar typedef para simplificar esto, al declarar un tipo (en este caso se llama VAF) como
apuntador a una función que regresa void y que toma como parámetros dos referencias a
enteros. El listado 14.9 reproduce el listado 14.8, pero utilizando esta instrucción typedef.

L is t a d o 1 4 . 9 Uso de typedef para hacer más legibles los apuntadores a


Entrada funciones
1: // Listado 14.9. Uso de typedef para hacer más legibles los apuntadores a
»•funciones
2:
3: ^include <iostream.h>
4:
5: void Cuadrado (int&,int&);
6: void Cubo (int&, int&);
7: void Intercambiar (int&, int&);
8: void ObtenerValores(int&, int&);
9: typedef void (*VAF) (int&, int&) ;
10: void ImprimirValores(VAF,int&, int&);
11 :
12: int main()
13: {
14: int valUno=1, valDos=2;
c o n tin u a
476 Día 14

L istado 14.9 co ntinuació 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: }

(0)Salir (1)Cambiar Valores (2)Cuadrado


x: 1 y: 2
Nuevo valor para vallino: 2
Nuevo valor para valDos: 3
x: 2 y: 3
(0)Salir (1)Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 3
x: 2 y: 3
x: 8 y: 27
(0)Salir (1 )Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 2
x: 8 y: 27
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 (1 )Cambiar Valores (2)Cuadrado (3)Cubo (4)Intercambiar: 0

E n la línea 9 se utiliza t y p e d e f para declarar a VAF como tipo “apuntador a función


A nálisis
que regresa void y que toma dos parámetros, ambos referencias a enteros”.
En la línea 10 se declara la función ImprimirValores () para tomar tres parámetros: un
VAF y dos referencias a enteros. En la línea 18 apFunc se declara ahora como tipo VAF.

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.

Apuntadores a fundones miembro 14


Hasta este punto, todos los apuntadores a funciones que se han creado han sido para 1un­
ciones generales que no pertenecen a una clase. También es posible crear apuntadores a
funciones que sean miembros de clases.
|478 Día 14

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.

L istado 14.10 Apuntadores a funciones miembro

1: //Listado 14.10 Apuntadores a funciones miembro que utilizan métodos


^»virtuales
2:
3: tfinclude <iostream.h>
4:
5: class Mamifero
6: {
7: public:
8: Mamifero():suEdad(l) { }
9: virtual -Mamifero() { }
10 virtual void Hablar() const = 0;
11 virtual void Mover() const = 0;
12 protected:
13 int suEdad;
14 >;
15
16: class Perro : public Mamifero
17: {
18: public:
19: void Hablar()const { cout « "¡Guau!\n"; }
20: void Mover() const { cout « “Caminando hacia el amo...\n"; }
21: };
22:
23:
24: class Gato : public Mamifero
25: {
26: public:
27: void Hablar()const { cout « "¡Miau!\n"; }
28: void Mover() const { cout « "caminando sigilosamente...\n"; }
29:
30:
31 :
32: class Caballo : public Mamifero
33: {
34: public:
35: void Hablar()const { cout « "¡Yihii!\n"; }
Clases y fundones espedales 479

36: void Mover() const { cout « "Galopando...-\n"; >


37: };
38:
39:
40: int main()
41 : {
42: void (Mamifero::*apFunc)() const =0;
43: Mamifero* aptr =NULL;
44: int Animal;
45: int Método;
46: bool fSalir = false;
47:
48: while (fSalir == false)
(O

{
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

case 2: aptr = new Gato; break;


e

56: case 3: aptr = new Caballo; break;


57: default: fSalir = true; break;
58: }
59: if (fSalir)
60: break;
61 :
62: cout « "(l)Hablar (2)Mover: ";
63: cin » Metodo;
64: switch (Metodo)
65: {
66: case 1: apFunc = Mamifero::Hablar; break;
67: default: apFunc = Mamifero::Mover; break;
68: }
69:
70: (aptr->*apFunc)();
71 : delete aptr;
72: }
73: return 0;
74: }

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

Arreglos de apuntadores a funciones miembro


Al igual que los apuntadores a funciones, los apuntadores a funciones miembro se pueden
guardar en un arreglo. El arreglo se puede inicializar con las direcciones de varias funciones
miembro, y éstas se pueden invocar por medio de desplazamientos en el arreglo. El listado
14.11 muestra esta técnica.

Entrada L is t a d o 14.11 Arreglo de apuntadores a fu n d o n e s m iem bro

1: //Listado 14.11 Arreglo de apuntadores a funciones miembro


2:
3: #include <iostream.h>
4:
5: class Perro
6: {
7: public:
8: void Hablar()const { cout « “¡Guau!\n"; }
9: void Mover() const { cout « "Caminando hacia el amo...\n"; }
10: void Comer() const { cout « "Devorando la comida...\n"; }
11: void Grunir() const { cout « "Grrrrr\n”; }
12: void Gimotear() const { cout « "Sonidos de gimoteos...\n"; }
13: void DarVuelta() const { cout « "Dando vuelta...\n”; }
14: void HacerMuerto() const { cout « "¿Es éste el final delpequeño
•»César?\n”; }
C la s e s y f u n c io n e s e s p e c ia le s 48 1

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

( 0 ) S a l i r (1)Hablar (2)Mover (3)Comer (4)Grunir (5)Gimotear (6)Dar


v u e lta (7)Hacerse e l muerto: 1
¡Guau!
( 0 ) S a l i r ( 1 )Hablar (2)Mover (3)Comer (4)Grunir (5)Gimotear (6)Dar
v u e lta (7)Hacerse el muerto: 4
G rrrrr
( 0 ) S a l i r (l)H a b la r (2)Mover (3)Comer (4)Grunir (5)Gimotear (6)Dar
v u e lta (7)Hacerse el muerto: 7 14
¿E s éste e l f i n a l del pequeño César?
( 0 ) S a l i r (l)H a b la r (2)Mover (3)Comer (4)Grunir (5)Gimotear (6)Dar
vuelta (7)Hacerse e l muerto: 0
482 D ía 1 4

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.

También aprendió cómo declarar y utilizar apuntadores a funciones y apuntadores a fun­


ciones miembro. Vio cómo crear arreglos de estos apuntadores y cómo pasarlos a las
funciones.
Los apuntadores a funciones y los apuntadores a funciones miembro se pueden utilizar para
crear tablas de funciones que se pueden seleccionar en tiempo de ejecución. Esto puede dar
flexibilidad a su programa, lo cual no se logra fácilmente sin estos apuntadores.

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

P ¿Es com ún utilizar m uchos ap u n tad o res a funciones y a p u n ta d o re s a funciones


m iem bro?
R No, éstos tienen sus usos especiales, pero no son construcciones comunes. Muchos
programas complejos y poderosos no tienen.

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¡

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.

En t r a d a L is t a d o R 2 . 1 Listado de repaso de la sem ana 2

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

L istado R2.1 CO N TIN U A CIÓ N

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

56: int suAnioModelo;


57: >¡
58:
59: PiezaAuto: :PiezaAuto(int anio, int numeroPieza) :
60: suAnioModelo(anio),
61 : Pieza(numeroPieza)
62: {}
63:
64:
65: // **************** Pieza de Aeroplano ************
66: class PiezaAeroPlano : public Pieza
67: {
68 : public:
69: PiezaAeroPlano() : suNumeroMotor(1) {};
70: PiezaAeroPlano (int NumeroMotor, int NumeroPieza);
71 : virtual void Desplegar() const
72: {
73: Pieza::Desplegar(); cout « "Motor número.:
74: cout « suNumeroMotor « endl;
75:
76: private:
77: int suNumeroMotor;
78:
79:
80: PiezaAeroPlano::PiezaAeroPlano (int NumeroMotor, int NumeroPieza):
81 : suNumeroMotor(NumeroMotor),
82: Pieza(NumeroPieza)
83: {>
84:
85: II **************** No(Jo de Pieza ************
86 : class NodoPieza
87: {
88: public:
89: NodoPieza(Pieza *);
90: -NodoPieza();
91 : void AsignarSiguiente(NodoPieza * nodo) { suSiguiente = nodo; }
92: NodoPieza * ObtenerSiguiente() const;
93: Pieza * ObtenerPieza() const;
94: private:
95: Pieza * suPieza;
96: NodoPieza * suSiguiente;
97: };
98:
99: // Implementaciones de NodoPieza...
100 : NodoPieza::NodoPieza(Pieza * apPieza):
101 : suPieza(apPieza),
102 : suSiguiente(0 )
103:

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 [

152: // Implementaciones para listas...


153:
154: ListaPiezas::ListaPiezas():
155: apCabeza(0),
156: suCuenta(0)
157: {>
158:
159: ListaPiezas::-ListaPiezas()
160: {
161: delete apCabeza;
162: >
163:
164: Pieza * ListaPiezas::ObtenerPrimero() const
165: {
166: if (apCabeza)
167: return apCabeza->ObtenerPieza();
168: else
169: return NULL; // atrapar error aquí
170: }
171:
172: Pieza * ListaPiezas::operator[](int desFase) const
173: {
174: NodoPieza* apNodo = apCabeza;
175:
176: if (!apCabeza)
177: return NULL; // atrapar error aquí
178: if (desFase > suCuenta)
179: return NULL; // error
180: for (int i = 0; i < desFase; i++)
181* apNodo = apNodo->ObtenerSiguiente();
182: return apNodo->ObtenerPieza();
183: }
184:
185: Pieza * ListaPiezas::Encontrar(int & posición, int NumeroPieza) const
186: {
187: NodoPieza * apNodo =NULL;
188:
189: for (apNodo = apCabeza, posición = 0;
190: apNodo != NULL;
191* apNodo = apNodo->ObtenerSiguiente(), posicion++)
192: {
193: if (apNodo ->ObtenerPieza () ->ObtenerNumeroPieza () NumeroPieza)
194: break;
195: >
196: if (apNodo == NULL)
197: return NULL;
198: else
199: return apNodo->ObtenerPieza();
continúa

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

244 apSiguiente = apActual*>ObtenerSiguiente();


245 Siguiente = apSiguiente->ObtenerPieza()->ObtenerNumeroPieza();
246 if (Siguiente > Nuevo)
247 {
248 apActual->AsignarSiguiente(apNodo);
249 apNodo->AsignarSiguiente(apSiguiente);
250 return;
251 }
252 apActual = apSiguiente;
253 }
254 }
255
256 int main()
257 {
258 ListaPiezas & lp = ListaPiezas: :ObtenerListaPiezasGlobal();
259 Pieza * apPieza = NULL;
260 int NumeroPieza;
261 int valor;
262 int opcion;
263
264 while (1)
265 {
266 cout « "(0)Salir (1)Auto (2)Avión: ";
267 cin » opcion;
268 if (!opcion)
269 break;
270 cout « "¿Nuevo NumeroPieza?: ";
271 cin >> NumeroPieza;
272 if (opcion == 1)
273 {
274 cout « "¿Año del modelo?: ";
275 cin » valor;
276 apPieza = new PiezaAuto(valor,NumeroPieza);
277 >
278 else
279 {
280 cout « "¿Número de motor?: ";
281 cin » valor;
282 apPieza = new PiezaAeroPlano(valor, NumeroPieza);
283 >
284 lp.Insertar(apPieza);
285 }
286 void (Pieza::*apFunc) ()const = &Pieza: ¡Desplegar;
287 lp.Iterar(apFunc);
288 return 0;
289 }
494 Semana 2

(0)Salir (1)Auto (2)Avión: 1


S a l id a
¿Nuevo NumeroPieza?: 2837
¿Año del modelo? 90
(0)Salir (1)Auto (2)Avión: 2
¿Nuevo NumeroPieza?: 378
¿Número de motor?: 4938
(0)Salir (1)Auto (2)Avión: 1
¿Nuevo NumeroPieza?: 4499
¿Año del modelo? 94
(0)Salir (1)Auto (2)Avión: 1
¿Nuevo NumeroPieza?: 3000
¿Año del modelo? 93
(0)Salir (1)Auto (2 )Avión: 0
Número de pieza: 378
Motor número.: 4938
Número de pieza: 2837
Año del modelo: 90
Número de pieza: 3000
Año del modelo: 93
Número de pieza: 4499
Año del modelo: 94

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 ().

Observe que Desplegar!) sí tiene implementación, en las líneas 39 a 42. El propósito


del diseñador es obligar a las clases derivadas a crear su propio método Desplegar!),
pero también se pueden encadenar con este método.
En las líneas 45 a 57 y 66 a 78 se declaran dos clases derivadas simples, llamadas
PiezaAuto y PiezaAeroPlano. Cada una proporciona un método Desplegar!)
redefinido, el cual hace efectivamente se encadena con el método Desplegar!) de la
clase base.
Repaso 495

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

Operator[ ] permite un acceso directo al objeto Pieza que se encuentra en el desplaza­


miento proporcionado. Se cuenta con una verificación de límites rudimentaria; si la lista
es NULL, o si el desplazamiento requerido es mayor que el tamaño de la lista, se regresa
NULL como una condición de error.
Observe que en un programa real, estos comentarios en las funciones se hubieran escrito
en la declaración de la clase.

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í

*. ' • ■ I '. ■' rt ■ r

.-'.11 ■- . ¿í J.1 a ; ; . •v:, v . ,, „<> nI>, ,•■■■ .’) •(

. - ■
' ' .
í>-■’
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

En t r a d a L is t a d o 15.1 La clase Cadena

1: // Listado 15.1: La clase Cadena - lstl5


2: // usada por los listados
3:
4: #include <iostream.h>
5: ^include <string .h>
6:
7:
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 & operator[](int desplazamiento);
18 char operator[](int desplazamiento) const;
19 Cadena operator+(const Cadena &);
20 void operator+=(const Cadena &);
21 Cadena & operator= (const Cadena &);
22 // Métodos generales de acceso
23 int ObtenerLongitud()const { return suLongitud; }
24 const char * ObtenerCadena() const { return suCadena; }
25 // static int ConstructorCuenta;
26 private:
27 Cadena (int); // constructor privado
28 char * suCadena;
29 unsigned short suLongitud;
30 };
31
32 // constructor predeterminado, crea una cadena de 0 bytes
33 Cadena::Cadena()
34 {
35 suCadena = new char[ 1 ];
36 suCadena[ 0 ] = *\0 •;
37 suLongitud = 0 ;
38 // cout « "\tConstructor de cadena predeterminado \n";
39 // ConstructorCuenta++;
40 >
41
42 // constructor privado (auxiliar), lo utilizan sólo
43 // los métodos de la clase para crear una nueva cadena del
44 // tamaño requerido. Se llena con caracteres nulos.
45 Cadena::Cadena(int longitud)
46 {
47 suCadena = new char[ longitud+1 ];
48 for (int i = 0; i <= longitud; i++)
Herencia avanzada 503

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

98: suCadena[ suLongitud ] = '\0';


99: // cout « "\tOperador = de Cadena\n";
100: return * this;
101 : }
102 :
103: //Operador de desplazamiento no constante, ¡regresa
104: // una referencia a un carácter para que se pueda
105: // cambiar!
106: char & Cadena::operator[](int desplazamiento)
107: {
108: if (desplazamiento> suLongitud)
109 : return suCadena[ suLongitud-1 ];
110: else
111: return suCadena[ desplazamiento ];
112: }
113:
114: // operador de desplazamiento constante para utilizar
115: // en objetos tipo const (ver constructor de copia)
116: char Cadena::operator[](intdesplazamiento) const
117: {
118: if (desplazamiento > suLongitud)
119: return suCadena[ suLongitud-1 ];
120: else
121: return suCadena[ desplazamiento ];
122 : }
123:
124: // crea una nueva cadena agregando la cadena
125: // actual a rhs
126: Cadena Cadena::operator+(const Cadena & rhs)
127: {
128: int longitudTotal = suLongitud + rhs.ObtenerLongitud();
129: Cadena temp(longitudTotal);
130: int i, j;
131:
132: for (i = 0; i < suLongitud; i++)
133: temp[ i ] = suCadena[ i ];
134: for (j = 0; j < rhs.ObtenerLongitud(); j++, i++)
135: temp[ i ] = rhs[ j ];
136: temp[ longitudTotal ]='\0';
137: return temp;
138: >
139:
140: // cambia la cadena actual, no regresa nada
141: void Cadena::operator+=(const Cadena & rhs)
142: {
143: unsigned short rhsLong = rhs.ObtenerLongitud();
144: unsigned short longitudTotal = suLongitud + rhsLong;
145: Cadena temp(longitudTotal);
146: int i, j;
Herencia avanzada 505

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.

Entrada L istado 15.2 La clase Empleado y el programa controlador


1: // Listado 15.1b - Programa que utiliza el listado 15.1
2: // con el archivo de encabezado lst15-0l.hpp
3:
4: #include "lst15-01.hpp"
5:
6:
7: class Empleado
8: {
9: public:
10: Empleado!);
11: Empleado(char *, char *, char *, long);
12: -Empleado!);
13: Empleado(const Empleado &);
14: Empleado & operator= (const Empleado &);
15: const Cadena & ObtenerPrimerNombre() const
16: { return suPrimerNombre; >
17: const Cadena & ObtenerApellido() const
18: { return suApellido; }
19: const Cadena & ObtenerDireccion() const
20: { return suDireccion; }
continúa
506 Día 15

L is t a d o 1 5 .2 continuación

21: long ObtenerSalario() const


22: { return suSalario; }
23: void AsignarPrimerNonibre(const Cadena & primNombre)
24: { suPrimerNombre = primNombre; }
25: void AsignarApellido(const Cadena & Apellido)
26: { suApellido = Apellido; }
27: void AsignarDireccion(const Cadena & dirección)
28: { suDireccion = dirección; >
29: void AsignarSalario(long salario)
30: { suSalario = salario; }
31: private:
32: Cadena suPrimerNombre;
33: Cadena suApellido;
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.ObtenerApellido();
68: suDireccion = rhs.ObtenerDireccion();
69: suSalario = rhs.ObtenerSalario();
70: return *this;
71: }
72:
H e r e n c ia a v a n z a d a 507

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

P u e d e co lo car el c ó d i g o del listado 15.1 en un archivo lla m a do Cadena.hpp.


Así, c a d a vez q u e necesite la clase Cadena puede incluir el lista d o 15.1 p o r
m e d i o d e t f i n c lu d e “C adena.npp", en lu g a r de la línea 4 # i n c l u d e “l s t i 5 -
01 . hpp" del lista d o 15.2.

Por conveniencia en este libro, incluí la implementación con la declaración de la clase.


En un programa real, usted guardaría la declaración de la clase en Cadena. hpp y la
implementación en C a d e n a . cpp. Luego compilaría Cadena. cpp para creare! programa
objeto C ad ena . o, que usaría como una biblioteca dinámica (también puede hacerlo con
un archivo make) y utilizaría la instrucción #in elud e Cadena. hpp en los programas que
utilicen esta biblioteca.
Nombre: Edythe Levine.
S alida D ire c c ió n : 1461 Shore Parkway.
S a l a r io : 50000

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

función AsignarPrimerNombre() que tome una cadena de caracteres como argumento;


AsignarPrimerNombre() requiere una referencia a una cadena constante.
El compilador resuelve esto debido a que sabe cómo crear una cadena a partir de una
cadena de caracteres constante. Sabe esto porque le dijo cómo hacerlo en la línea 13 del
listado 15.1.

Cómo tener acceso a miembros


de una clase contenida
Los objetos de la clase Empleado no tienen acceso especial a las variables miembro de la
clase Cadena. Si el objeto Edie de la clase Empleado tratara de tener acceso a la variable
miembro suLongitud de su propia variable miembro suPrimerNombre, se generaría un
error en tiempo de compilación. Sin embargo, esto no es un problema grave. Los métodos
de acceso proporcionan una interfaz para la clase Cadena, y la clase Empleado no necesi­
ta preocuparse por los detalles de implementación, de la misma forma que no necesita
preocuparse por la manera en que la variable entera suSalario guarda su información.

Cómo filtrar el acceso a los miembros contenidos


Observe que la clase Cadena proporciona la implementación para soportar el operator+.
El diseñador de la clase Empleado ha bloqueado el acceso al operator+ que se llama en
los objetos Empleado, declarando que todos los métodos de acceso de cadena, como
ObtenerPrimerNombre(), regresen una referencia constante. Debido a que operator+ no
es (y no puede ser) una función const (cambia al objeto en el que se llama), si trata de
escribir lo siguiente se generará un error en tiempo de compilación:
Cadena bufer = Edie.ObtenerPrimerNombre() + Edie.ObtenerApellido();
ObtenerPrimerNombre() regresa un objeto Cadena constante, y no se puede llamar a
operator* para actuar sobre un objeto constante.
Para solucionar esto, sobrecargue a ObtenerPrimerNombre() para que no sea const:
const Cadena & ObtenerPrimerNombre() const { return suPrimerNombre; }
Cadena & ObtenerPrimerNombre() { return suPrimerNombre; }
Observe que el valor de retomo ya no es const y que la propia función miembro ya no
es const. Cambiar el valor de retomo no es suficiente para sobrecargar el nombre de la
función; debe cambiar el estado constante de la propia función.

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

Entrada L ista d o 15.3 C o n s t r u c t o r e s d e la clase c o n te n id a

1: // Listado 15.3 - Otro ejemplo de laclaseEmpleado


2: // que utiliza la claseCadena (Istl5-01.hpp)
3:
4: ^include "Istl5-01.hpp"
5:
6:
7: class Empleado
8: {
9: public:
10: EmpleadoO;
11: Empleado(char *, char *, char *, long);
12: -EmpleadoO;
13: Empleado(const Empleado &);
14: Empleado & operator= (const Empleado &);
15: const Cadena & ObtenerPrimerNombre() const
16: {return suPrimerNombre; }
17: const Cadena & ObtenerApellido() const
18: { return suApellido; }
19: const Cadena & ObtenerDireccion() const
20: {return suDireccion; }
21: long ObtenerSalario() const
22: {return suSalario; }
23: void AsignarPrimerNombre(const Cadena & primNombre)
24: {suPrimerNombre = primNombre; }
25: void AsignarApellido(const Cadena & Apellido)
26: {suApellido = Apellido; }
27: void AsignarDireccion(const Cadena & dirección)
28: {suDireccion = dirección; }
29: void AsignarSalario(long salario)
30: {suSalario = salario; }
31: private:
32: Cadena suPrimerNombre;
33: Cadena suApellido;
34: Cadena suDireccion;
35: long suSalario;
36: };
37:
38: Empleado::Empleado():
39: suPrimerNombre(“"),
40: suApellido(""),
41: suDireccion(""),
continúo
510 Día 15

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.

Cómo copiar por valor


El listado 15.3 muestra cómo la creación de un objeto Empleado produjo tres llamadas al
constructor de cadena. El listado 15.4 vuelve a utilizar el mismo programa controlador.
Esta vez no se utilizan las instrucciones de impresión, pero se quitan las marcas de comen­
tario a la variable miembro estática de tipo cadena llamada ConstructorCuenta, y ésta
se utiliza.
512 Día 15

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.

Entrada L is t a d o 15.4 Paso de parám etros por valor

1: //Listado 15.4 - Este programa muestra el paso de parámetros


2: // por valor. Nuevamente se utiliza la clase Cadena
3:
4: #include "lst15-01.hpp"
5:
6:
7: class Empleado
8: {
9: public:
10: Empleado!);
11: Empleado(char *, char *, char *, long);
12: -EmpleadoO;
13: Empleado(const Empleado &);
14: Empleado & operator= (const Empleado &);
15: const Cadena & ObtenerPrimerNombre() const
16: { return suPrimerNombre; }
17: const Cadena & ObtenerApellido() const
18: { return suApellido; }
19: const Cadena & ObtenerDireccion() const
20: {return suDireccion; }
21: long ObtenerSalario() const
22: { return suSalario; }
23: void AsignarPrimerNombre(const Cadena & primNombre)
24: { suPrimerNombre = primNombre; }
25: void AsignarApellido(const Cadena & Apellido)
26: {suApellido = Apellido; >
27: void AsignarDireccion(const Cadena & dirección)
28: {suDireccion = dirección; }
29: void AsignarSalario(long salario)
30: { suSalario = salario; }
31: private:
32: Cadena suPrimerNombre;
33: Cadena suApellido;
Herencia avanzada

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

84: cout « "Cuenta de constructores: " ;


85: cout « Cadena::ConstructorCuenta « endl;
86: rFuncImpr(Edie);
87: cout « "Cuenta de constructores: ”;
88: cout « Cadena::ConstructorCuenta « endl;
89: FuncImpr(Edie);
90: cout « "Cuenta de constructores: ";
91 : cout « Cadena::ConstructorCuenta « endl;
92: return 0 ;
93: }
94:
95: void FuncImpr(Empleado Edie)
96: {
97: cout « "Nombre: ";
98: cout « Edie.ObtenerPrimerNombre( ).ObtenerCadena( );
99: cout « " " « Edie.ObtenerApellido( ).ObtenerCadena( );
100 cout « ".\nDirecciôn: ";
101 cout « Edie.ObtenerDireccion( ).ObtenerCadena( );
102 cout « ".\nSalario: 11 ;
103 cout « Edie.ObtenerSalario();
104 cout « endl;
105 >
106
107 void rFuncImpr(const Empleado & Edie)
108 {
109 cout « "Nombre: ";
110 cout « Edie.ObtenerPrimerNombre( ).ObtenerCadena( );
111 cout « " " « Edie.ObtenerApellido( ).ObtenerCadena( );
112 cout « "\nDirecciôn: ";
113 cout « Edie.ObtenerDireccion().ObtenerCadena( );
114 cout « "\nSalario: " ;
115 cout « Edie.ObtenerSalario();
116 cout « endl;
117 }

Constructor de Cadena(char *) constructor


S a l id a Constructor de Cadena(char *) constructor
Constructor de Cadena(char *) constructor
Constructor de Cadena(char *) constructor
Operador = de Cadena
Destructor de Cadena
Constructor de Cadena(char *) constructor
Operador = de Cadena
Cuenta de constructores: 5
Nombre: Edythe Levine
Dirección: 1461 Shore Parkway
Salario: 20000
Cuenta de constructores: 5
Constructor de Cadena(Cadena &)
Constructor de Cadena(Cadena &)
Herencia avanzada 515

Constructor de Cadena(Cadena &)


Nombre: Edythe Levine.
Dirección: 1461 Shore Parkway.
Salario: 20000
Destructor de Cadena
Destructor de Cadena
Destructor de Cadena
Cuenta de constructores: 8
Destructor de Cadena
Destructor de Cadena
Destructor de Cadena
Destructor de Cadena
La salida muestra que se crearon cinco objetos de tipo cadena, tres como parte de
A nálisis la creación de un objeto Empleado (línea 79), uno al asignar el nombre (línea 81)
y otro más al crear la cadena cApellido (línea82). Cuando el objeto Empleado se pasa a
rFunclmpr() por referencia, no se crean objetos Empleado adicionales, por lo que no se
crean objetos Cadena adicionales. (Éstos también se pasan por referencia.)
Cuando, en la línea 89, el objeto Empleado se pasa por valor a Funclmpr(), se crea una
copia del objeto Empleado, y se crean tres objetos más de tipo cadena (mediante llamadas
al constructor de copia).

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

L istado 15.5 Delegación a una ListaPiezas contenida


1: // Listado 15.5 • Ejemplo de la delegación de responsabilidades ■0
2: // a los miembros de una lista
3:
4: #include <iostream.h>
5:
6:
^• j j ★**★★*★*★***•*★** Piez3************
8: // Clase base abstracta de piezas
9: class Pieza
10: {
11: public:
12 : Pieza() : suNumeroPieza(1) {}
13: Pieza(int NumeroPieza):
14: suNumeroPieza(NumeroPieza) {}
15: virtual -Pieza() {}
16: int ObtenerNumeroPieza() const
17: { return suNumeroPieza; }
18: virtual void Desplegar() const = 0;
19: private:
20: int suNumeroPieza;
21: };
22:
23: // implementación de una función virtual pura para que
24: // las clases derivadas se puedan encadenar
25: void Pieza::Desplegar() const
26: {
27: cout << “\nNúmero de pieza: " « suNumeroPieza « endl;
28: }
29:
30 j ¡ j **************** pieza de Auto ************
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):
continúa
1 5 .5 CONTINUACIÓN

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

188 void ListaPiezas::Iterar(void (Pieza::*func) () const) const


189 {
190 if (lapCabeza) «OS
191 return;
192 NodoPieza * apNodo = apCabeza;
193 do
194 (apNodo->ObtenerPieza()->*func) ();
195 while (apNodo = apNodo->ObtenerSiguiente());
196 }
197
198 void ListaPiezas::Insertar(Pieza * apPieza)
199 {
200 NodoPieza * apNodo = new NodoPieza(apPieza);
201 NodoPieza * apActual = apCabeza;
202 NodoPieza * apSiguiente = NULL;
203 int Nuevo = apPieza->ObtenerNumeroPieza();
204 int Siguiente = 0;
205
206 suCuenta++;
207 if (lapCabeza)
208 {
209 apCabeza = apNodo;
210 return;
211 }
212 // si éste es más pequeño que el nodo cabeza
213 // entonces se convierte en el nuevo nodo cabeza
214 if (apCabeza->ObtenerPieza()->ObtenerNumeroPieza() > Nuevo)
215 {
216 apNodo->AsignarSiguiente(apCabeza);
217 apCabeza = apNodo;
218 return;
219 >
220 for (;;)
221 {
222 // si no hay siguiente, agregar éste
223 if (!apActual->ObtenerSiguiente())
224 {
225 apActual->AsignarSiguiente(apNodo);
226 return;
227 >
228 I I si va después de éste y antes del siguiente
229 // entonces insertarlo aqui, de no ser así
230 // obtener el siguiente
231 apSiguiente = apActual->ObtenerSiguiente();
continúa
522 Día 15

L istado 15.5 continuación

232: Siguiente = apSiguiente->ObtenerPieza()->ObtenerNumeroPieza();


233: if (Siguiente > Nuevo)
234: {
235: apActual->AsignarSiguiente(apNodo);
236: apNodo->AsignarSiguiente(apSiguiente);
237: return;
238: >
239: apActual = apSiguiente;
240: }
241: }
242:
243: class CatalogoPiezas
244: {
245: public:
246: void Insertar(Pieza *);
247: int Existe(int NumeroPieza);
248: Pieza * Obtener(int NumeroPieza);
249: operator+(const CatalogoPiezas &);
250: void MostrarTodo()
251 : { laListaPiezas.lterar(&Pieza:-.Desplegar); }
252: private:
253: ListaPiezas laListaPiezas;
254: };
255:
256: void CatalogoPiezas::Insertar(Pieza * nuevaPieza)
257: {
258: int numeroPieza = nuevaPieza->ObtenerNumeroPieza();
259: int desplazamiento;
260:
261: if (¡laListaPiezas.Encontrar(desplazamiento, numeroPieza))
262: laListaPiezas.Insertar(nuevaPieza);
263: else
264: {
265: cout « numeroPieza « “ fue la “;
266: switch (desplazamiento)
267: {
268: case 0: cout « "primera ";
269: break;
270: case 1: cout « "segunda ";
271: break;
272: case 2: cout « "tercera “;
273: break;
274: default: cout « desplazamiento+1 « "a ";
275: }
Herencia avanzada 523

276: cout « “entrada. ¡Rechazada!\n";


277: }
278: }
279:
280: int CatalogoPiezas::Existe(int NumeroPieza)
281: {
282: int desplazamiento;
283:
284: laListaPiezas. Encontrar (desplazamiento, NumeroPieza);
285: return desplazamiento;
286: }
287:
288: Pieza * CatalogoPiezas: :Obtener(int NumeroPieza)
289: {
290: int desplazamiento;
291:
292: Pieza * laPieza = laListaPiezas.Encontrar
»»(desplazamiento, NumeroPieza);
293: return laPieza;
294: >
295:
296:
297: int main()
298: {
299: CatalogoPiezas cp;
300: Pieza * apPieza= NULL;
301: int NumeroPieza;
302: int valor;
303: int opcion;
304:
305: while (1)
306: {
307: cout « "(0)Salir (1)Auto (2)Avión: ";
308: cin » opcion;
309: if (¡opcion)
310: break;
311: cout « “¿Nuevo NumeroPieza?: n;
312: cin » NumeroPieza;
313: if (opcion == 1)
314: {
315: cout « "¿Año del modelo?: ";
316: cin » valor;
317: apPieza = new PiezaAuto(valor, NumeroPieza);
318: }

continúa
524 Día 15

L istado 15.5 continuación

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

Salida (0)Salir (1)Auto (2)Avión: 1


¿Nuevo NumeroPieza?: 1234
¿Año del modelo?: 94
(0)Salir (1)Auto (2)Avión: 1
¿Nuevo NumeroPieza?: 4434
¿Año del modelo?: 93
(0)Salir (1)Auto (2)Avión: 1
¿Nuevo NumeroPieza?: 1234
¿Año del modelo?: 94
1234 fue la primera entrada. ¡Rechazada!
(0)Salir (1)Auto (2)Avión: 1
¿Nuevo NumeroPieza?: 2345
¿Año del modelo?: 93
(0)Salir (1)Auto (2)Avión: 0

Número de pieza: 1234


Año del modelo: 94

Número de pieza: 2345


Año del modelo: 93

Número de pieza: 4434


Año del modelo: 93

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.

Observe que CatalogoPiezas se encarga de la inserción al llamar a Insertar () sobre su


variable miembro laListaPiezas, que es un objeto de la clase ListaPiezas. La mecáni­
ca de la inserción en sí y el mantenimiento de la lista enlazada, junto con la búsqueda y
la recuperación de la lista enlazada, se mantienen en el miembro ListaPiezas contenido
en CatalogoPiezas. No hay razón para que CatalogoPiezas reproduzca este código;
puede aprovechar completamente la interfaz bien definida.
Ésta es la esencia de la reutilización dentro de C++: CatalogoPiezas puede reutilizar el
código de ListaPiezas, y el diseñador de CatalogoPiezas puede ignorar los detalles
de implementación de ListaPiezas. La interfaz para ListaPiezas (es decir, la decla­
ración de la clase) proporciona toda la información que necesita el diseñador de la clase
CatalogoPiezas.

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

Para los clientes de la clase CatalogoPiezas, la clase ListaPiezas es invisible. Ningún


componente de la interfaz está disponible para dichos clientes: no pueden llamar a nin­
guno de sus métodos. Sin embargo, pueden llamar a los métodos de CatalogoPiezas;
entonces los métodos de CatalogoPiezas pueden tener acceso a todo lo que haya en
ListaPiezas ya que CatalogoPiezas se deriva de ListaPiezas. Lo importante aquí es
que los objetos de CatalogoPiezas no son objetos de ListaPiezas, como hubiera sido
con la herencia pública. CatalogoPiezas se implementa con base en ListaPiezas, como
hubiera sido con la contención. La herencia privada es sólo una conveniencia.
El listado 15.6 muestra el uso de la herencia privada, para lo cual modifica la clase
CatalogoPiezas como derivada en forma privada de ListaPiezas.

Entrada L istado 15.6 H e re n cia p riv a d a

1: // Listado 15.6 - Muestra de la herencia privada


2:
3: #include <iostream.h>
4:
5:
6: I I **************** pigza ************
7: // Clase base abstracta de piezas
8: class Pieza
9: {
10 public:
11 Pieza() : suNumeroPieza(l) {>
12: Pieza(int NumeroPieza):
13: suNumeroPieza(NumeroPieza) {}
14: virtual -Pieza() {}
15: int ObtenerNumeroPiezaO const
16: { return suNumeroPieza; }
17: virtual void Desplegar() const =0;
18: private:
19: int suNumeroPieza;
20: };
21:
22: // implementación de la función virtual pura para que
23: // las clases derivadas se puedan encadenar
24: void Pieza::Desplegar() const
25: {
26: cout « "\nNúmero de pieza: " « suNumeroPieza « endl;
27: }
28:
29: // **************** pieza de Auto ************
30: class PiezaAuto : public Pieza
31: {
32: public:
33: PiezaAuto() : suAnioModelo(94) {}
34: PiezaAuto(int anio, int numeroPieza);
35: virtual void Desplegar() const
Herencia avanzada

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

180 if (apNodO == NULL)


181 return NULL;
182 else
183 return apNodo->ObtenerPieza();
184
185
186 void ListaPiezas::Iterar(void (Pieza::*func) () const) const
187 {
188 if (lapCabeza)
189 return;
190 NodoPieza * apNodo = apCabeza;
191 do
192 (apNodo->ObtenerPieza()->*func)();
193 while (apNodo = apNodo->ObtenerSiguiente());
194
195
196 void ListaPiezas::Insertar(Pieza * apPieza)
197 {
198 NodoPieza * apNodo = new NodoPieza(apPieza);
199 NodoPieza * apActual = apCabeza;
200 NodoPieza * apSiguiente = NULL;
201 int Nuevo = apPieza->ObtenerNumeroPieza();
202 int Siguiente = 0;
203
204 suCuenta++;
205 if (lapCabeza)
206 {
207 apCabeza = apNodo;
208 return;
209 >
210 //si éste es más pequeño que el nodo cabeza
211 // se convierte en el nuevo nodo cabeza
212 if (apCabeza->ObtenerPieza()->ObtenerNumeroPieza() > Nuevo)
213 {
214 apNodo->AsignarSiguiente(apCabeza);
215 apCabeza = apNodo;
216 return;
217 >
218 for (;;)
219 {
220 // si no hay siguiente, agregar éste
221 if (1apActual->ObtenerSiguiente())
222 {
223 apActual->AsignarSiguiente(apNodo);
224 return;
225 >
226 // si va después de éste y antes del siguiente
227 // entonces insertarlo aquí, de no ser así, obtener el siguiente
Herencia avanzada 531

228 apSiguiente = apActual->ObtenerSiguiente();


229 Siguiente = apSiguiente->ObtenerPieza()->ObtenerNumeroPieza();
230 if (Siguiente > Nuevo)
231 {
232 apActual->AsignarSiguiente(apNodo);
233 apNodo ->AsignarSiguiente(apSiguiente);
234 return;
235 }
236 apActual = apSiguiente;
237 }
238 >
239
240 class CatalogoPiezas : private ListaPiezas
241 {
242 public:
243 void Insertar(Pieza *);
244 int Existe(int NumeroPieza);
245 Pieza * Obtener(int NumeroPieza);
246 operator+(const CatalogoPiezas &);
247 void MostrarTodo()
248 { Iterar(&Pieza:¡Desplegar); >
249 private:
250
251
252 void CatalogoPiezas::lnsertar(Pieza * nuevaPieza)
253 {
254 int numeroPieza = nuevaPieza->ObtenerNumeroPieza();
255 int desplazamiento;
256
257 if (!Encontrar(desplazamiento, numeroPieza))
258 ListaPiezas::Insertar(nuevaPieza) ;
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 ";
271 }
272 cout « "entrada. ¡Rechazadal\n";
273 }
274 }
275
276 int CatalogoPiezas::Existe(int NumeroPieza)
continúa
532 Día 15

L istado 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)

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

(0)Salir (1)Auto (2)Avión: 1


S alida
¿Nuevo NumeroPieza?: 1234
¿Año del modelo?: 94
(0)Salir (1)Auto (2)Avión: 1
¿Nuevo NumeroPieza?: 4434
¿Año del modelo?: 93
(0)Salir (l)Auto (2)Avión: 1
¿Nuevo NumeroPieza?: 1234
¿Año del modelo?: 94
1234 fue la primera entrada. ¡Rechazada!
(0)Salir (1)Auto (2)Avión: 1
¿Nuevo NumeroPieza?: 2345
¿Año del modelo?: 93
(0)Salir (1)Auto (2)Avión: 0

Número de pieza: 1234


Año del modelo: 94

Número de pieza: 2345


Año del modelo: 93

Número de pieza: 4434


Año del modelo: 93

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

La función lnsertar() también ha cambiado. O bserve que en la línea 257 ahora se


llama directamente a Encontrar() debido a que se hereda de la clase base. La llamada
a Insertar() de la línea 258 debe estar identificada com pletam ente, o de lo contrario
term inaría realizando una recursión sobre el método Insertar () de CatalogoPiezas.

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 .

No sería conveniente hacer que s u P ie z a fuera público, ni siquiera protegido, ya que


éste es un detalle de implementación de NodoPieza y es m ejor mantenerlo privado. Sin
embargo, es conveniente exponerlo a L i s t a P i e z a s .
Si quiere exponer sus funciones o datos miembro privados a otra clase, debe declarar esa
clase como amiga. Esto extiende la interfaz de su clase para incluir a la clase amiga.
Herencia avanzada 535

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.

L istad o 15.7 Ejemplo de ciases amigas


1: // Listado 15.7 - Ejemplo de clases amigas
2:
3: #include <iostream.h>
4:
5:
6: II *****★********** pieza ************
7: // Clase base abstracta de piezas
8: class Pieza
9: {
10 public :
11 Pieza() : suNumeroPieza(1 ) {}
12 Pieza(int NumeroPieza):
13 suNumeroPieza(NumeroPieza) {}
14 virtual -Pieza() {}
15 int ObtenerNumeroPieza() const
16 { return suNumeroPieza; }
17 virtual void Desplegar() const = 0;
18 private:
19 int suNumeroPieza;
20 >;
21
22 // implementación de la función virtual pura para que
23 // las clases derivadas se puedan encadenar
24 void Pieza::Desplegar() const
25 {
26 cout « "\nNúmero de pieza:
27 cout « suNumeroPieza « endl;
28 }
continúa
536 Día 15

L istado 15.7 continuación

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

L istado 15.7 continuación

121 : // ¡necesita constructor de copia y operador igual a!


122: void Iterar(void (Pieza::*f)() const) const;
123: Pieza * Encontrar(int & posición, int NumeroPieza) const
124: Pieza * ObtenerPrimero() const;
125: void Insertar(Pieza *);
126: Pieza * operator[](int) const;
127: int ObtenerCuenta() const
128: { return suCuenta; }
129: static ListaPiezas & ObtenerListaPiezasGlobal()
130: { return ListaPiezasGlobal; }
131: private:
132: NodoPieza * apCabeza;
133: int suCuenta;
134: static ListaPiezas ListaPiezasGlobal;
135: >;
136:
137: ListaPiezas ListaPiezas:¡ListaPiezasGlobal;
138: // Implementaciones para listas...
139: ListaPiezas::ListaPiezas():
¡ 140: apCabeza(0),
141: suCuenta(0)
1 142: {>
1 143:
1 144: ListaPiezas::-ListaPiezas()
i*
1.

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

167 return apNodo->suPieza;


168 >
169
170 Pieza * ListaPiezas::Encontrar(int & posición, int NumeroPieza) const
171 {
172 NodoPieza * apNodo = NULL;
173
174 for (apNodo = apCabeza, posición = 0;
175 apNodo 1= NULL;
176 apNodo = apNodo->suSiguiente, posicion++)
177 {
178 if (apNodo->suPieza->ObtenerNumeroPieza() == NumeroPieza)
179 break;
180 }
181 if (apNodo == NULL)
182 return NULL;
183 else
184 return apNodo->suPieza;
185
186
187 void ListaPiezas::Iterar(void (Pieza::*func)() const) const
188 {
189 if (!apCabeza)
190 return;
191 NodoPieza* apNodo = apCabeza;
192 do
193 (apNodo->suPieza->*func)();
194 while (apNodo = apNodo->suSiguiente);
195
196
197 void ListaPiezas::Insertar(Pieza * apPieza)
198 {
199 NodoPieza * apNodo = new NodoPieza(apPieza);
200 NodoPieza * apActual = apCabeza;
201 NodoPieza * apSiguiente = NULL;
202 int Nuevo = apPieza->ObtenerNumeroPieza();
203 int Siguiente = 0;
204
205 suCuenta++;
206 if (!apCabeza)
207 {
208 apCabeza = apNodo;
209 return;
210 >
211 // si éste es más pequeño que el nodo cabeza
212 // se convierte en el nuevo nodo cabeza

continúa
540 Día 15

L istado 15.7 CONTINUACIÓN

213: if (apCabeza->suPieza->ObtenerNumeroPieza() > Nuevo)


214: {
215: apNodo->suSiguiente = apCabeza;
216: apCabeza = apNodo;
217: return;
218: >
219: for1 (;;)
220: {
221: // si no hay siguiente, agregar éste
222: if (!apActual->suSiguiente)
223: {
224: apActual->suSiguiente = apNodo;
225: return;
226: }
227: // si va después de éste y antes del siguiente
228: // entonces insertarlo aquí, de no ser así, obtener el siguiente
229: apSiguiente = apActual->suSiguiente;
230: Siguiente = apSiguiente->suPieza->ObtenerNumeroPieza();
231: if (Siguiente > Nuevo)
232: {
233: apActual->suSiguiente = apNodo;
i 234: apNodo->suSiguiente = apSiguiente;
i 235: return;
i 236:
l> }
237: apActual = apSiguiente;
b
u,
. 238: }
1 239: >
i), 240:
Ü! 241: class CatalogoPiezas : private ListaPiezas
242: {
243: public:
8!
IH 244: void lnsertar(Pieza *);
245: int Existe(int NumeroPieza);
SI 246:
i! Pieza * Obtener(int NumeroPieza);
247: operator+(const CatalogoPiezas &);
248: void MostrarTodo()
249: { Iterar(&Pieza:¡Desplegar); }
250: private:
251: };
252:
253: void CatalogoPiezas::lnsertar(Pieza * nuevaPieza)
254: {
255: int numeroPieza = nuevaPieza->ObtenerNumeroPieza();
256: int desplazamiento;
257: if (!Encontrar(desplazamiento, numeroPieza))
258: ListaPiezas::Insertar(nuevaPieza);

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:

(0)Salir (1)Auto (2)Avión: 1


¿Nuevo NumeroPieza?: 1234
¿Año del modelo?: 94
(0)Salir (1)Auto (2)Avión: 1
¿Nuevo NumeroPieza?: 4434
¿Año del modelo?: 93
(0)Salir (1)Auto (2)Avión: 1
¿Nuevo NumeroPieza?: 1234
¿Año del modelo?: 94
1234 fue la primera entrada. ¡Rechazada!
(0)Salir (1)Auto (2)Avión: 1
¿Nuevo NumeroPieza?: 2345
¿Año del modelo?: 93
(0)Salir (1)Auto (2)Avión: 0

Número de pieza: 1234


Año del modelo: 94

Número de pieza: 2345


Año del modelo: 93

Número de pieza: 4434


Año del modelo: 93
Herencia avanzada 543

A nálisis En la línea 76 se declara la clase ListaPiezas como amiga de la clase NodoPieza.

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.

A menudo escuchará a los programadores de C++ novatos quejarse de que


N ota las declaraciones friend "minan" la encapsulación, que es tan importante
para la programación orientada a objetos. Francamente, esto es mentira.
La declaración friend hace que la clase declarada como amiga sea parte de la
interfaz de la clase y no mina la encapsulación más que la derivación pública.

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.

Las cadenas estilo C son arreglos de caracteres con te rm in a d o r nulo, como


char myString [] = “¡Hola, mundo!".

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.

LISTADO 15.8 operator+ amigable


1: // Listado 15.8 - Operadores amigables
2:
3: #include <iostream.h>
4: #include <string.h>
Herencia avanzada

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

106: //operador de desplazamiento que no es constante, ¡regresa


107: // la referencia a un carácter para que se pueda
108: // cambiar!
109: char & Cadena: :operator[ ](int desplazamiento)
110: {
111: if (desplazamiento > suLongitud)
112: return suCadenaf suLongitud - 1 ];
113: else
114: return suCadena[ desplazamiento ];
115: }
116:
117: // operador de desplazamiento constante para utilizar
118: // en objetos const (vea el constructor de copia)
119: char Cadena: :operator[ ](int desplazamiento) const
120: {
121: if (desplazamiento > suLongitud)
122: return suCadena[ suLongitud • 1 ];
123: else
124: return suCadena[ desplazamiento 1;
125: }
126:
127: // crea una nueva cadena al agregar la cadena
128: // actual a rhs
129: Cadena Cadena::operator+(const Cadena & rhs)
130: {
131: int longitudTotal =suLongitud + rhs.ObtenerLongitud();
132: Cadena temp(longitudTotal);
133: int i, j;
134:
135: for (i = 0; i <suLongitud; i++)
136: temp[ i ] = suCadena[ i ];
137: for (j = 0, i = suLongitud; j<rhs.ObtenerLongitud(); j++, i++)
138: temp[ i ] = rhs[ j ];
139: temp[ longitudTotal ]='\0';
140: return temp;
141: }
142:
143: // crea una nueva cadena sumando
144: // una cadena a otra
145: Cadena operator+(const Cadena & lhs, const Cadena & rhs)
146: {
147: int longitudTotal = lhs.ObtenerLongitud() + rhs.ObtenerLongitud();
148: Cadena temp(longitudTotal);
149: int i, j;
150:
151: for (i = 0; i < lhs.ObtenerLongitud(); i++)
152: temp[ i ] = ihs( i ];
153: for (j = 0, i = lhs.ObtenerLongitud();
154: j < rhs.ObtenerLongitudO; j++, i++)
155: temp[ i ] = rhs( j ];
continúa
548 D í a 15

Listado 15.8 continuación

156 temp[ longitudTotal ] = ' \ 0 ' ;


157 return temp;
158 }
159
160 int main()
161 {
162 Cadena s 1 ( "Cadena Uno ");
163 Cadena s2("Cadena Dos " );
164 char *c1 = { 'C-Cadena Uno " } ;
165 Cadena s3;
166 Cadena s4;
167 Cadena s5;
168
169 cout « " s 1 : 1 << s1.ObtenerCadena( << endl ;
170 cout « " s 2 : 1 << s2.0btenerCadena( << endl ;
171 cout « " d : " << d << endl;
172 s3 = s1 + s2;
173 cout « "s3: " << s3.0btenerCadena( << endl ;
174 s4 = s1 + d ;
175 cout « " s 4 : 11 << s4.0btenerCadena( << endl ;
176 s5 = c1 + s2;
177 cout << " s 5 : " << s5.0btenerCadena( « endl ;
178 return 0;
179 }

s1: Cadena Uno


S a l id a s2: Cadena Dos
c1: C-Cadena Uno
s3: Cadena Uno Cadena Dos
s4: Cadena Uno C-Cadena Uno
s5: C-Cadena Uno Cadena Dos

La implementación de todos los métodos de cadena, exceptuando a ope rato r+,


A n á l is is
permanece sin cambio, quedando igual que en el listado 15.1. En la línea 20 se
sobrecarga un nuevo operator+ para tomar dos referencias constantes a una cadena y
para regresar una cadena, y esta función se declara com o amiga.

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();
// . . .
};

Sobrecarga del operador de inserción


Finalm ente está listo para dar a su clase Cadena la capacidad de utilizar cout de la
misma m anera que cualquier otro tipo. Hasta ahora, cuando quería im prim ir una cadena,
estaba obligado a escribir lo siguiente:
cout << laCadena.ObtenerCadena();

Lo que puede hacer en vez de esto es escribir lo siguiente:


cout << laCadena;

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 « ()

1: // L is t a d o 15.9 - Sobrecarga del operador «

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

58: Cadena::Cadena(const char * const cCadena)


59: {
60: suLongitud = strlen(cCadena);
61: suCadena = new char[ suLongitud + 1 ];
62: for (int i = 0; i < suLongitud; i++)
63: suCadena[ i ] = cCadena[ i ];
64: suCadena[ suLongitud ] = ’\0';
65: // cout << "\tConstructor de Cadena(char*)\n°;
66: // ConstructorCuenta++;
67: }
68:
69: // constructor de copia
70: Cadena::Cadena(const Cadena & rhs)
71: {
72: suLongitud = rhs.ObtenerLongitud();
73: suCadena = new char[ suLongitud + 1 ];
74: for (int i = 0; i < suLongitud; i++)
75: suCadena[ i ] = rhs( i ];
76: suCadenaf suLongitud ] = ’\0';
77: // cout « “\tConstructor de Cadena(Cadena&)\n°;
78: // ConstructorCuenta++;
79: }
80:
81: // destructor, libera la memoria asignada
82: Cadena::-Cadena()
83: {
84: delete [] suCadena;
85: suLongitud = 0;
86: // cout « “\tDestructor de Cadena\n";
87: }
88:
89: // operador igual a, libera la memoria existente
90: // luego copia la cadena y el tamaño
91: Cadena& Cadena::operator=(const Cadena & rhs)
92: {
93: if (this == &rhs)
94: return *this;
95: delete [] suCadena;
96: suLongitud = rhs.ObtenerLongitud();
97: suCadena = new char[ suLongitud + 1 ];
98: for (int i = 0; i < suLongitud; i++)
99: suCadena[ i ] = rhs[ i ];
100: suCadenaf suLongitud ] = '\01;
101: return *this;
102: // cout « "\tOperador = de Cadena\n";
103: }
104:
105: // operador de desplazamiento que no es constante, ¡regresa
106: // la referencia a un carácter para que se pueda
continúa
L is t a d o 1 5 . 9 continuación

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

159: ostream & operator«(ostream & elFlujo, Cadena & laCadena)


160: {
161: elFlujo « laCadena.suCadena;
162: return elFlujo;
163: }
164:
165: int main()
166: {
167: Cadena laCadena("¡Hola, mundo!");
168: cout « laCadena;
169: cout << endl;
170: return 0;
171: >

S alida ¡Hola, mundo!


En la línea 21 se declara a operator« como función friend que toma una re­
A nálisis
ferencia a ostream y una referencia a Cadena y luego regresa una referencia a
ostream. Observe que ésta no es una función miembro de Cadena. Regresa una referencia
a un objeto ostream para que usted pueda concatenar las llamadas a operator«, como
en el siguiente ejemplo:
cout « "miEdad: " « suEdad « " años.";
La implementación de esta función friend se encuentra en las líneas 159 a 163. Todo lo
que esto hace en realidad es ocultar los detalles de implementación relacionados con la
forma de proporcionar la cadena al ostream, y así es como debe ser. Verá más acerca de
la sobrecarga de este operador y de operator» en el día 16.

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

6 . Corrija el listado del ejercicio 5 para que se pueda compilar.


7. CAZA ERRO RES: ¿Qué está mal en este código?
1: #include <iostream.h>
2:
3: class Animal;
4:
5: void asignarValor(Animal & , int);
6: void asignarValor(Animal & ,int, int);
7:
8: class Animal
9: {
10: friend void asignarValor(Animal & , int);
11: private:
12: int suPeso;
13: int suEdad;
14: };
15:
16: void asignarValor(Animáis 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: }

8 . Corrija el ejercicio 7 para que se pueda compilar.


Semana 3

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

Panorama general sobre los flujos


C++, com o parte del lenguaje, no define cómo se escriben los datos en la pantalla o
en un archivo, ni cóm o se leen los datos en un programa. Sin embargo, éstas son,
evidentem ente, partes esenciales del trabajo con C++, y la biblioteca estándar de
C++ incluye la biblioteca io stream , que facilita la entrada y la salida (E/S).
La ventaja de tener la entrada y salida separadas del lenguaje y m anejarlas en
bibliotecas es que es m ás fácil hacer que el lenguaje sea “independiente de la
plataform a” . Es decir, puede escribir program as de C++ en una PC y volver a
com pilarlos y ejecutarlos en una estación de trabajo Sun. O puede com pilar por
m edio del com pilador GNU en la PC (por ejemplo, estando en su oficina) y
llevarse ese código a su casa para utilizarlo en Linux. El creador del
com pilador provee la biblioteca apropiada, y todo funciona. Al menos ésa es la
teoría general.
558 D í a 16

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

Objetos de E/S estándar


Cuando inicia un program a de C++ que incluye la clase iostream . se crean e inicializan
cuatro objetos:
° c in (se pronuncia "si-in” ) m aneja la entrada desde el teclado, que viene siendo la
entrada estándar.
• cout (se pronuncia “si-out” ) maneja la salida estándar, que viene siendo la salida a
la pantalla.
• c e r r (se pronuncia “si-err” ) maneja la salida que no se encuentra en el búfer al dis­
positivo de error estándar, la pantalla. Como esta salida no se encuentra en el búfer.
todo lo que se envíe a c e r r se escribe inmediatamente en el dispositivo de error
estándar, sin esperar que el búfer se llene o que se reciba un comando de limpieza.
• c lo g (se pronuncia com o “si-log” ) maneja los mensajes de error que se encuentran
en el búfer y que se envían com o salida al dispositivo de error estándar, la pantalla.
Es com ún que esto se “redireccione” a un archivo de registro, como se describe en
la siguiente sección.

La biblioteca de clases iostream se agrega automáticamente al programa


por medio del compilador. Todo lo que necesita hacer para utilizar estas fun­
ciones es colocar la instrucción inelude apropiada al principio del listado de
su programa.

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

Linux (y otras versiones de UNIX) proporciona capacidades de redirección avanzadas:


redirigir salida (>), redirigir entrada (<), y redirigir salida hacia la entrada de un segundo
programa (I). (La redirección en UNIX es muy particular, porque el sistema “ve” a todos
los dispositivos como archivos; así, puede redireccionar la salida hacia la pantalla o hacia
un archivo de texto, pero también la puede redireccionar hacia el módem, un disco duro
o la tarjeta de sonido.)
DOS proporciona comandos de redirección rudim entarios, com o redirigir salida (>) y
redirigir entrada (<) . También permite redirigir la salida de un programa hacia la entrada
de otro con el carácter (I). La idea general es la misma en DOS, Linux y UNIX: tomar la
salida dirigida a la pantalla y escribirla en un archivo, o canalizarla hacia otro programa.
De manera alternativa, la entrada de un programa se puede extraer de un archivo en lugar
del teclado.
La redirección es más una función del sistema operativo que de las bibliotecas iostream.
C++ sólo proporciona acceso a los cuatro dispositivos estándar; queda a elección del usuario
redireccionar los dispositivos a cualquier alternativa que sea necesaria.

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

En t r a d a L istado 16.1 e i n m a n e ja t ip o s d e d a t o s d is tin to s

1: //Listado 16.1 - cadenas de caracteres y ein


2:
3: //include <iostream.h>
4:
5:
6: int main()
7: {
8: int milnt;
9: long miLong;
10 double miDouble;
11 float miFloat;
12 unsigned int millnsigned;
13
14 cout « "int: n;
15 cin » milnt;
16 cout « "Long: “;
17 cin » miLong;
18 cout « "Double: “;
19 cin » miDouble;
20 cout « “Float: ";
21 cin » miFloat;
22 cout « "Unsigned: ";
23 cin » miUnsigned;
24
25 cout << "\n\nlnt:\t" « milnt « endl;
26 cout « "Long:\t" « miLong « endl;
27 cout « "Double:\t" « miDouble « endl;
28 cout « "Float:\t" « miFloat « endl;
29 cout « "Unsigned:\t" « miUnsigned « endl;
30 return 0;
31 }
32

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 .

Problemas con cadenas


Después de todo este éxito con cin, podría sorprenderse al tratar de escribir un nombre
completo en una cadena, cin cree que el espacio en blanco es un separador. Cuando ve
un espacio o un carácter de nueva línea, da por hecho que la entrada para el parámetro
está completa y, en el caso de las cadenas, agrega un carácter nulo justo ahí. El listado
16.2 muestra este problema.

Entrada Listado 16.2 M u e stra q u é p asa al tra ta r d e e scribir m á s d e u n a p a la b ra con cin

1: //Listado 16.2 - Cadenas de caracteres y cin


O•
3: #include <iostream.h>
4:
5:
6: int main()
7: {
8: char SuNombre[ 50 ];
9:
10: cout « "Su primer nombre: ";
11 : cin » SuNombre;
12: cout « "Aqui esté:; “ « SuNombre « endl ;
13: cout « "Su nombre completo: ";
14: cin » SuNombre;
15: cout « "Aqui esta;: " « SuNombre « endl;
16: return 0;
17: }

j
F lu jo s 565

Su primer nombre: Jesse


S a l id a Aquí e stá : Jesse
Su nombre completo: Jesse Liberty
Aqui e stá : Jesse

En la línea 8 se crea un arreglo de caracteres para guardar la entrada del usuario. En


A nálisis
la línea 10 se pide al usuario que escriba un nombre, y éste se guarda correctam ente,
como se muestra en la salida.
En la línea 13 se pide otra vez al usuario que escriba, esta vez un nombre com pleto, c in 1 6
lee la entrada y, cuando ve el espacio entre los nombres, coloca un carácter nulo después
de la prim era palabra y term ina la entrada. Esto no es exactamente lo que se espera que
haga el program a.

Para entender por qué esto funciona así. examine el listado 16.3. el cual m uestra la en tra­
da para varios cam pos.

Entrada L is t a d o 1 6 . 3 En trad a m últiple

1: // Lista do 16.3 - Cadenas de caracteres y cin


¿OL•.
3: //include <iostream .h>
4:
5:
6: in t m ain()
7: {
8: i n t m ilnt;
9: long miLong;
10: double miDouble;
11 : f l o a t miFloat;
12: unsigned i n t miUnsigned;
13: char m iPalabra[ 50 ];
14:
15: coût « " i n t :
16: c in >> m ilnt;
17: coût << "Long: 11;
18: c in >> miLong;
19: coût << "Double: ";
20: c in » miDouble;
21 : coût << "F lo a t : ";
22: c in >> miFloat;
23: coût « "P alabra: ";
24: c in >> miPalabra;
25: coût << "Unsigned: ";
26: c in » miUnsigned;
27:
28: coût « " \ n \ n l n t : \ t " « milnt « endl ;
29: coût << "L o n g : \ t " « miLong << endl;
30: coût << " D o u b l e n t " << miDouble << endl
31 : coût << " F l o a t : \ t " « miFloat « endl ;
continúa
566 Día 16

L istado 16.3 c o n t in u a c ió n

32: coût « "Palabra:\t" « miPalabra « endl;


33: coût << "Unsigned:\t" « miUnsigned « endl;
34: coût « "\n\nlnt, Long, Double, Float, Palabra Unsigned
35:
36: cin :» imilnt » miLong » miDouble;
37: cin » i miFloat » miPalabra » miUnsigned;
38: coût « "\n\nlnt:\t" « milnt << endl;
39: coût « "Long:\t“ « miLong « endl;
40: coût << "Double:\t" « miDouble « endl;
41 : coût « "Float:\t" « miFloat « endl;
42: coût « “Palabra:\t" « miPalabra « endl;
43: coût « "Unsigned:\t" « miUnsigned « endl;
44: return 0;
45: >
46:

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, Long, Double, Flc


adiós -2

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.

Entrada de un solo carácter


El operator» que toma una referencia a un carácter se puede utilizar para recibir un solo
carácter desde la entrada estándar. La función miembro get () también se puede utilizar
para recibir un solo carácter, y lo puede hacer de dos formas: get () se puede utilizar sin
parámetros, en cuyo caso se utiliza el valor de retomo, o se puede utilizar con una
referencia a un carácter.

Uso de get () sin parámetros


La primer forma de get () es sin parámetros. Ésta regresa el valor del carácter encontrado
y regresará EOF (fin de archivo) si se llega al final del archivo, get () sin parámetros no
se usa muy a menudo. No es posible concatenar diversas entradas con el método get ( ) ,
ya que el valor de retomo no es un objeto iostream. Por lo tanto, lo siguiente no funcionará:
cin.get() »miVarUno >> miVarDos; // ilegal
568 D í a 16

El valor de retorno de c i n . g e t ( ) >> miVarüno es un entero, no un objeto iostream.

En el listado 16.4 se muestra un uso com ún de g e t ( ) sin parám etros.

E ntrada L is t a d o 1 6 . 4 Uso de g e t ( ) sin p a rá m etro s

1: II Listado 16.4 - Uso de ge t() s in parámetros


2: #include <iostream.h>
3:
4: in t main()
5: {
6: char ch;
7: while ((ch = c in . g e t ( ) ) != EOF)
8: {
9: cout « "ch: 11 « ch « endl;
10: }
11: cout « "\n iL isto !\n " ;
12: return 0;
13: }

En m u c h a s p la ta fo rm a s (c o m o D O S), n e ce sita o p r im ir " E n t r a r " a n te s d e q u e se


m u e stre a lg u n o de los caracteres. A l g u n a s p la t a f o r m a s m u e s t r a n c a d a carácter
a m e d id a q u e se escribe.
Para salir d e este p ro g ra m a , d e b e e n v ia r u n a s e ñ a l d e fin d e a rc h iv o d e sd e el
teclado. En L in u x se utiliza C trl+D ; e n e q u ip o s D O S u tilic e C trl+Z .

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.

Uso de get () con una referencia a un carácter como parámetro


Cuando se pasa un carácter como parámetro a get (), ese carácter se llena con el siguiente
carácter del flujo de entrada. El valor de retomo es un objeto iostream, por lo que esta
forma de get () puede concatenarse, como se muestra en el listado 16.5.

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

1: // Listado 16.5 - Uso de get() con parámetros


2: #include <iostream.h>
3:
4: int main()
5: {
6: char a, b, c;
7:
8: cout « "Escriba tres letras: ";
9: cin.get(a).get(b).get(c);
10: cout « "a: " « a « "\nb: " « b « "\nc: " « c « endl;
11: return 0;
12: }

Escriba tres letras: uno


Sa l i d a a: u
b: n
c: o
En la línea 6 se crean tres variables de tipo carácter. En la línea 9, c in . get () se
A n á l is is
llama tres veces, concatenado. Primero se llama a cin. get (a). Esto coloca la
primera letra en a y regresa a cin para que al terminar se llame a c i n . get (b), lo que
coloca la siguiente letra en b. El resultado final de esto es que se llama a c i n . get (c)
y la tercera letra se coloca en c.
Como cin . get (a) se evalúa en cin, usted hubiera podido escribir esto:
cin. get(a) » b;
En esta forma, cin.get (a) se evalúa en cin, por lo que la segunda frase es cin » b;.
570 D í a 16

D eb e NO DEBE

DEBE utilizar el operador de extracción (» )


cuando necesite saltarse el espacio en blanco.
DEBE utilizar get() con un parámetro de
tipo carácter cuando necesite examinar cada
carácter, incluyendo el espacio en blanco.

Entrada de cadenas desde el dispositivo de entrada


estándar
El operador de extracción ( » ) se puede utilizar para llenar un arreglo de caracteres, al
igual que las funciones miembro g et () y g e t l i n e ( ).
La forma final de g e t( ) toma tres parámetros. El prim er parám etro es un apuntador a un
arreglo de caracteres, el segundo parámetro es el número m áxim o de caracteres a leer más
uno, y el tercer parámetro es el carácter de terminación. Éste es un buen ejem plo de sobre­
carga de funciones; dependiendo de los argum entos que se pasen en la llam ada de la
función, se elige la forma adecuada de la función.
I
I Si escribe 20 como el segundo parámetro, g e t ( ) leerá 19 y luego term inará la cadena con
i
i un carácter nulo, y guardará la cadena en el prim er parám etro. El tercer parámetro, el
I' carácter de terminación, tiene como carácter predeterm inado el carácter de nueva línea
h,
b, ( ' \ n '). Si se llega a un carácter de terminación antes de leer al número máximo de caracte­
tt.

R res, se escribe un carácter nulo y el carácter de term inación se deja en el búfer.


»■
R
n, El listado 16.6 muestra el uso de esta forma de g e t ().
Uv
Entrada L is t a d o 16 .6 Uso de g e t ( ) con un a r r e g l o d e c a r a c t e r e s

1: // Listado 16.6 - Uso de g e t () con un a rre g lo de


2: #include <iostream.h>
Q .•
O
4: in t main 0
5: {
6: char cadenaUno[ 256 1;
7: char cadenaDos[ 256 l;
8:
9: coût << "Escriba la cadena uno:
10: cin.get(cadenaUno, 256);
11 : coût « "cadenaUno: " « cadenaUno << endl;
12: coût « "Escriba la cadena dos:
13: cin >> cadenaDos;
14: coût « "CadenaDos: " « cadenaDos << endl;
15: return 0;
16: }

i
F lu jo s 571

E s c r i b a la cadena uno: Ahora es tiempo


S a l id a cadenallno: Ahora es tiempo
E s c r ib a la cadena dos: Para la bondad
CadenaDos: Para

En las lincas 6 y 7 se crean dos arreglos de caracteres. En la línea 9 se pide al usuario


A nálisis
que escriba una cadena, y en la línea 10 se llama a c i n . g e t ( ). El prim er parám etro
es el búfer a llenar, y el segundo es uno más que el número m áxim o que g e t ( ) puede
aceptar (la posición adicional se otorga al carácter nulo. [' \0 ' ]). El tercer parám etro pre­
determ inado es el carácter de nueva línea.

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.

Otra form a de solucionar este problem a es utilizar g e t l i n e ( ), como se m uestra en el


listado 16.7.

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

Escriba la cadena uno: uno dos tres


Salida cadenallno: uno dos tres
Escriba la cadena dos: cuatro cinco seis
cadenaDos: cuatro
Escriba la cadena tres: cadenaTres: cinco seis
Este ejemplo demanda un análisis cuidadoso; contiene algunas sorpresas potenciales.
A nálisis
En las líneas 6 a 8 se declaran tres arreglos de caracteres.
En la línea 10 se pide al usuario que escriba una cadena, la cual es leída por getline().
Al igual que get (), getline() toma un búfer y un núm ero m áxim o de caracteres. Sin
embargo, a diferencia de get(), getline() lee el carácter de nueva línea y lo descarta.
Con get () no se descarta el carácter de nueva línea. Se deja en el búfer de entrada.
En la línea 13 se vuelve a pedir al usuario que escriba, y esta vez se utiliza el operador de
extracción. El usuario escribe c u a tro cin co s e i s y la prim era palabra, cu a tro , se
coloca en cadenaDos. Luego se despliega la cadena E sc rib a cadena t r e s , y se llama
otra vez a g e t l i n e ( ). Como cinco s e is se encuentra todavía en el búfer de entrada, se
lee inm ediatamente hasta el carácter de nueva línea; term ina g e t l i n e ( ) y la cadena
contenida en cadenaTres se imprime en la línea 18.
El usuario no tiene oportunidad de escribir en cadenaTres porque la segunda llamada a
getline() se llena con la cadena que queda en el búfer de entrada después de la llamada
al operador de extracción en la línea 14. No hay forma de que el usuario pueda escribir la
cadena siguiente ya que cadenaDos termina al oprimir “Entrar”, pero esta tecla permanece
en el búfer de entrada y satisface inmediatamente la segunda llamada a getline().
El operador de extracción ( » ) lee hasta el primer espacio en blanco y coloca la palabra
en el arreglo de caracteres.
La función miembro get () está sobrecargada. En la primera versión no lleva parámetros
y regresa el valor del carácter que recibe. En la segunda versión toma una sola referencia
a un carácter y regresa el objeto istream por referencia.
En la tercera y última versión, get () toma un arreglo de caracteres, un número de caracteres
a obtener y un carácter de terminación (el cual tiene el carácter de nueva línea predeter­
minado). Esta versión de get () lee los caracteres que están en el arreglo hasta que llega
a uno menos que su número máximo de caracteres, o hasta que se encuentra con el carácter
de terminación, lo que ocurra primero. Si get () encuentra el carácter de terminación, deja
ese carácter en el búfer de entrada y deja de leer caracteres.
La función miembro getline() también toma tres parámetros: el búfer a llenar, uno más
que el número máximo de caracteres a obtener, y el carácter de terminación. getline()
funciona de la misma manera que get () con estos parámetros, excepto que getline() des­
carta el carácter de terminación.
F lu jo s 573

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

Entrada L is t a d o 1 6 . 8 Uso de ignore ()


1: // L is t a d o 16.8 - Uso de ignore()
2: //inelude <iostream .h>
3:
4: in t main()
5: {
6: char cadenaUno[ 255 ];
7: char cadenaDos[ 255 ];
8:
9: cout << " E s c r ib a la cadena uno:";
10: c i n .get(cadenaUno, 255);
11: cout << "Cadena uno: " << cadenaUno « endl;
12: cout << " E s c r ib a la cadena dos: ";
13: c in .g e t lin e (c a d e n a D o s , 255);
14: cout << "Cadena dos: " « cadenaDos « endl;
15: cout « "\n \nAh ora in tente de nuevo...\n";
16: cout << " E s c r ib a la cadena uno: " ;
17: cin.get(cadenaUno, 255);
18: cout << "Cadena uno: " << cadenaUno<< endl;
19: c in . ig n o r e ( 2 5 5 , ' \ n ' ) ;
20: cout << "E s c r ib a la cadena dos: ";
21: c in .g e t lin e (c a d e n a D o s , 255);
22: cout << "Cadena dos: " << cadenaDos« endl;
23: return 0;
24: }

E s c rib a la cadena uno: Habia una vez


S alida Cadena uno: Habia una vez
E s c rib a la cadena dos: Cadena dos:

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

1: // Listad o 16.9 - Uso de peek() y de putback()


2: ¿/include <iostream.h>
3:
4: in t main()
5: {
6: char eh;
7: cout << "E s c rib a una frase:
8: w hile (cin .ge t(ch ))
9: {
10: if (eh == ' ! ' )
11 : c in .p u t b a c k (' $ ' ) ;
12: else
13: cout « eh;
14: while (cin.peek() == ' # ' )
15: c in .ig n o re (1 , '//');
16: }
17: return 0;
18: }

E s c rib a unafrase: Ahora ! es//tiempo ! para/Zla ! diversión// !


S a l id a Ahora$estiempo$parala$diversión$

En la línea 6 se declara una variable de tipo carácter llam ada ch y en la línea 7 se


A nálisis
pide al usuario que escriba una frase. El propósito de este program a es convertir cual­
quier signo de admiración (!) en signo de dólar ($) y quitar cualquier signo de numeral (//).
F lu jo s 575

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.

p e e k ( ) y p u t b a c k ( ) se utilizan comúnmente para analizar sintácticamente


cadenas y otros datos, como cuando se escribe un compilador.

Salida con eout


Ya ha utilizado a c o u t ju n to con el operador de inserción (<<) sobrecargado para escribir
cadenas, enteros y otros datos numéricos en la pantalla. También es posible dar form ato a
los datos, alinear colum nas y escribir los datos numéricos en forma decimal y hexadecimal.
Esta sección le m uestra cóm o hacerlo.

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

Entrada L istado 16.10 Uso de put ( )

1: // Listado 16.10 - Uso de put{)


2: «include <iostream.h>
3:
4: int main()
5: {
6: cout.put(*H ').put('o').put('1').put('a').put( *\n');
7: return 0;
8: }

Hola
Salida

A lg u n o s com piladores (distintos de los de G N U ) tie n e n proble m as al imprimir


Nota utilizando este código. Si su com p ilado r no quiere im prim ir la palabra Hola, tal
vez quiera saltarse este listado.

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.

En t r a d a L istado 16.11 Uso de write ()


1: // Listado 16.11 - Uso de write()
2: «include <iostream.h>
3: «include <string.h>
4:
5: int main()
6: {
7: char Uno[] = "Uno, si por tierra";
8: int longitudCompleta = strlen(Uno);
9: int muyCorta = longitudCompleta - 6 ;
10: int muyLarga = longitudCompleta + 6 ;
11 :
12: cout.write(Uno, longitudCompleta) « "\n";
13: cout.write(Uno, muyCorta) « "\n";
Flujos 577

14: cout.write(Uno, muyLarga) « "\nB;


15: return 0;
16: }

Uno, si por tierra


S A L ID A Uno, si por
Uno, si por tierr#úy¿

La últim a línea de salida puede lucir diferente en su com putadora; en realidad


Nota sólo es basura.

En la linea 7 se crea la frase Uno. En la línea 8, la longitud de la frase se asigna al


A nálisis
entero longitudCompleta, a muyCorta se le asigna esa longitud menos seis, y a
muyLarga se le asigna longitudCompleta más seis.
En la línea 12 se imprime la frase completa usando w r ite ( ). La longitud se establece en
la longitud actual de la frase, y se imprime la frase correcta.
En la línea 13 se vuelve a imprimir la frase, pero es seis caracteres más corta que la frase
completa, y esto se refleja en la salida.
En la línea 14 se imprime de nuevo la frase, pero esta vez se indica a w r ite ( ) que escriba
seis caracteres adicionales. Después de escribir la frase, se escriben los siguientes seis
bytes de memoria contigua.

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

Entrada L istado 16.12 Ajuste del ancho de la salida


1: // Listad o 16.12 - Ajuste del ancho de la s a l i d a
2: tfinclude <iostream.h>
3:
4: i n t main 0
5: {
6: cout « " I n i c i o >";
7: c o u t .vvidth (25) ;
8: cout << 123 « "< F i n \ n " ;
9:
10: cout « " I n i c i o >";
11 : c o u t . width(25) ;
12: cout « 1 2 3 « "< S ig u ie n te >" ;
13: cout << 456 « "< F in \ n " ;
14:
15: cout « " I n i c i o >";
16: cout .w id th (4 );
17: cout « 123456 « "< Fin\n J
18: return 0;
19: }

In ic io > 123< Fin


In ic io > 123< S i g u i e n t e >456<
I n i c i o >123456< Fin

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.

Cómo establecer los caracteres de llenado


Por m edio de w i d t h ( ), co u t llena con espacios el cam p o vacío que se crea, como se
m uestra en el listado 16.12. Algunas veces puede ser necesario que llene el área con otros
caracteres, com o por ejem plo asteriscos. Para hacer esto, llam e a f i l l ( ) y pásele como
parám etro el carácter que quiere utilizar com o c a rá c te r de llen ad o . El listado 16.13
m uestra esto.
F lu jo s 579

Entrada L ista d o 16.13 Uso de f i l l ( )


1: // L is t a d o 16.13 - Uso de f i l l ( )
2: //inelude <iostream . h>
3:
4: i n t main()
5: {
6: cout << " I n i c i o > " J
7: co u t.w id th (2 5 );
1 6
8: cout << 123 << " < F i n \ n " ;
9:
10 cout << " I n i c i o > " 1
11 c o u t . w i d t h (25);
12 c o u t .f i l l ( 1* 1) ;
13 cout << 123 « " < F i n \ n " ;
14 re t u rn 0;
15 }

Inicio > 123< Fin


Salida Inicio >***************'*******^2 3 < Pin
Las líneas 6 a 8 repiten la funcionalidad del ejem plo anterior. Las líneas 10 a 14
A nálisis
repiten esto de nuevo, pero esta vez, en la línea 12 se establece un asterisco com o
carácter de llenado, com o se refleja en la salida.

Cómo establecer indicadores de iostream


Los objetos iostream llevan el registro de su estado por medio de indicadores. Puede
establecer estos indicadores llam ando a setf () y pasando cualquiera de las constantes
enum eradas predefinidas.
Se dice que los objetos tienen estado cuando uno o todos sus datos representan una condi­
ción que puede cam b iar durante el transcurso del programa.

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.

E n trada L is t a d o 1 6 .1 4 Uso de setf


1: // L i s t i n g 16.14 - Uso de s e t f ()
2: ¿¿include <iostream.h>
3: ¿¿include <iomanip.h>
4:
5: in t main()
6: {
7: const in t numero = 185;
8:
9: cout « " E l número es " << numero << endl;
10:
11: cout << " E l número es " << hex << numero << endl;
12:
13: c o u t .s e t f ( i o s : : showbase);
14: cout << " E l número es " << hex << numero << endl;
15:
16: cout << "E l número es " ;
17: co u t.w id th (1 0 );
18: cout << hex « numero << endl;
19:
20: cout « "E l número es " ;
21 : co u t.w id th ( 10 ) ;
22: c o u t . s e t f ( i o s : :l e f t ) ;
23: cout « hex « numero « endl;
24:
25: cout « "E l número es " ;
26: cout.w id th ( 10 );
27: c o u t.se tf (io s : -.internal) ;
28: cout « hex « numero « endl;
29:
30: cout « " E l número e s : " << setw(10) << hex << numero << endl;
31 : return 0;
32: }

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 13 se establece el indicador showbase. Esto ocasiona que se agregue el prefijo


Ox a todos los núm eros hexadeeim ales. como se refleja en la salida.
En la línea 17 se establece el ancho en 10. y el valor se desplaza hacia el lado derecho. En 1 6
la línea 21 el ancho se vuelve a establecer en 10. pero esta vez la alineación se establece
a la izquierda, y el núm ero se imprime alineado hacia la izquierda.

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.

Por últim o, en la línea 30 se utiliza el operador de concatenación s e t w ( ) para establecer


el ancho en 10. y se im prim e de nuevo el valor.

Flujos en comparación con la función p rin tf ()


La m ayoría de las im plem entaciones de C++ también proporcionan las bibliotecas de E/S
estándar de C. incluyendo la función p r i n t f (). Aunque p r i n t f () es, de cierta forma, más
fácil de utilizar que c o u t. es mucho menos deseable.

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.

Para utilizar p r i n t f (), asegúrese de incluir el archivo de encabezado s td io .h . En su forma


más simple, p r i n t f () tom a una cadena de formato como su primer parámetro y luego una
serie de valores com o sus parám etros restantes.
La cadena de lorm ato es una cadena encerrada entre comillas que contiene texto y especifi-
cadores de conversión. Todos los especificadores de conversión deben em pezar con el
sím bolo de porcentaje (%). Los especificadores de conversión comunes se presentan en la
tabla 16.1.
582 Día 16

T abla 16.1 L o s e s p e c ific a d o r e s d e c o n v e r s ió n c o m u n e s


Especificador Utilizado para
%s C adenas
%d Enteros
%1d Entero largo
%lf D o b le
%f Flotante

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

1: // Listado 16.15 - Uso de printf()


2: #include <stdio.h>
3:
4: int main()
5: {
6: printf("%s", "¡Hola, mundo!\n");
7:
8: char * frase = “¡Hola de nuevo!\n";
9: printf("%s", frase);
10:
11: int x = 5;
12: printf("%d\n",x);
13:
14: char * fraseDos = "He aquí algunos valores:
15: char * fraseTres = "y aquí están otros:
16: int y = 7, z = 35;
17: long longVar = 98456;
18: float floatVar = 8.8f;
19: printf("%s %d %d %s %ld %f\n",
^fraseDos, y, z, fraseTres, longVar, floatVar);
20:
21:
22: char * fraseCuatro = "Con formato: ";
23: printf("%s %5d %10d %10.5f\n", fraseCuatro, y, z, floatVar);
24: return 0;
25: }

¡Hola, mundo!
S a l id a ¡Hola de nuevo!
5
F lu jo s 583

He aquí a lgunos v a lo re s: 7 35 y aquí están o tros: 98456 8.800000


Con formato: 7 35 8.80000

La prim era instrucción p r i n t f (), que se encuentra en la línea 6. utiliza la form a


A n á l is is
estándar: el térm ino p r i n t f . seguido de una cadena encerrada entre com illas con
un especificado!' de conversión (en este caso %s), seguido de un valor a insertar en el
especificador de conversión.
El especificador %s indica que es una cadena, y el valor de la cadena es, en este caso, la
cadena encerrada entre com illas “ ¡Hola, mundo!\n”
La segunda instrucción p r i n t f () es igual que la primera, pero esta vez se utiliza un apunta­
dor a un tipo de datos ch ar, en lugar de colocar la cadena dentro de la instrucción p r i n t f ( ) .
La tercera instrucción p r i n t f (), que se encuentra en la línea 12, utiliza el especificador
de conversión para enteros, y como valor la variable de tipo entero x. La cuarta instrucción
p r i n t f ( ) , que se encuentra en la línea 19, es más compleja. Aquí se concatenan 6 valores.
Se proporciona cada uno de los especificadores de conversión, y luego se proporcionan
los valores, separados con comas.
Por último, en la línea 23 se utilizan especificaciones de formato para especificar el ancho
y la precisión. C om o puede ver, todo esto es un poco más sencillo que el uso de m anipu­
ladores.
Sin em bargo, com o se dijo anteriormente, la limitación aquí es que no hay com probación
de tipos y p r i n t f () no se puede declarar como función amiga o método de una clase.
A sí que, si quiere im prim ir todos los datos miembro de una clase, debe pasar de m anera
explícita cada m étodo de acceso a la instrucción p r i n t f ().

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 :

cout « "\aOcurrió un erro r\t"

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.

D e b id o a qu e fstre a m .h incluye a iostream.h, no necesita incluir io stre am en


Nota fo rm a explícita.

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.

Apertura de archivos para entrada y salida


Para abrir el archivo m ia rc h iv o . cpp con un objeto ofstream , se declara una instancia de
un objeto o fstre a m y se pasa el nombre del archivo como parámetro:
ofstream f o u t( "m iarchivo. cpp");
Para abrir este archivo como entrada se hace lo mismo, sólo que se utiliza un objeto:
ifstream f i n ( "m iarchivo. cpp");
Tenga en cuenta que fo u t y f i n son nombres que usted puede asignar; aquí se ha uti­
lizado f o u t para reflejar su similitud con cout, y se ha utilizado f in para reflejar su
similitud con c in .
| 586 Día 16

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.

E ntrada L istado 16.16 Apertura de archivos para lectura y escritura


1: // Listado 16.16 - Lectura y escritura de archivos
2: tfinclude <fstream.h>
3:
4: int main()
5: {
6: char nombreArchivo[ 80 ];
7: char bufer[ 255 ]; // para entrada del usuario
8:
9: cout « "Nombre de archivo: ";
10: cin » nombreArchivo;
11 : ofstream fout(nombreArchivo); // abrir para escritura
12: fout « "Esta linea se escribe directamente en el archivo...\n";
13: cout « “Escriba el texto para el archivo: ";
14: cin.ignore(1, '\n'); // elimina la nueva línea después del nombre
»»del archivo
15: cin.getline(bufer, 255); // obtener la entrada del usuario
16: fout « bufer « "\n"; // y escribirla en el archivo
17: fout.close(); I I cerrar el archivo, listo para volver a abrir
18:
19: ifstream fin(nombreArchivo); // volver a abrir para lectura
20: cout « "He aquí el contenido del archivo:\n";
21: char ch;
22: while (fin.get(ch))
23: cout « ch;
24:
25: cout « "\n*
***Fin del contenido del archivo.***\n";
26:
27: fin.close(); I I siempre reditúa ser ordenado
28: return 0;
29: >

Nombre de archivo: pruebal


S alida Escriba el texto para el archivo: ¡Este texto se escribirá en el archivo!
He aquí el contenido del archivo:
Esta línea se escribe directamente en el archivo...
¡Este texto se escribirá en el archivo!

***Fin del contenido del archivo.***


En la línea 6 se reserva un búfer para el nombre del archivo, y en la línea 7 se reserva
A nálisis otro búfer para la entrada del usuario. En la línea 9 se pide al usuario que escriba
F lu jo s 587

un n om bre d e archivo, y esta respuesta se escribe en el búfer llam ado nombreArchivo. E n la


lín ea 1 1 se c re a un o b je to o f s t r e a m llam ado fout, el cual se asocia con el n u ev o n o m b re
de arch iv o . E sto a b re el a rc h iv o : si el archivo ya existe, su co n ten id o se elim ina.
En la línea 12 se esc rib e u n a ca d en a de texto directam ente en el archivo. En la línea 13 se
pide al usu ario q u e esc rib a algo. El carácter de nueva línea que quedó al escribir el nom bre
del arch iv o se e lim in a en la línea 14. y la entrada del usuario se guarda en b u fe r en la lín ea
15. E sa e n tra d a se esc rib e en el archivo, ju n to con un carácter de nueva línea en la línea 16.
y lu eg o el a rc h iv o se c ie rra en la lín ea 17. 1 6
En la lín ea 19 se v u e lv e a a b rir el archivo, esta vez en m odo de lectura, y se lee su c o n ­
ten id o , un c a rá c te r a la v ez , en las líneas 22 y 23.

C ó m o c a m b ia r el c o m p o r ta m ie n to predeterm inado d e ofstream


al a b r i r u n a r c h i v o
El c o m p o rta m ie n to p re d e te rm in a d o al m om ento de abrir un archivo es cre a r el a rch iv o si
todavía no existe, y tru n carlo (es decir, elim inar todo su contenido) si ya existe. Si no q u iere
este c o m p o rta m ie n to p re d e te rm in a d o , p u ed e p ro p o rcio n ar ex p líc ita m e n te un se g u n d o
arg u m e n to al c o n s tru c to r d e su o b jeto ofstream.
E ntre los a rg u m e n to s v á lid o s se incluyen:
• i o s : : app— A grega al final de los archivos existentes en lugar de elim inar el contenido.
• i o s : : a t e — Lo lleva al final del archivo, pero puede escribir datos en cualquier lu g ar
del a rc h iv o .
• i o s : : t r u n o — El p re d e te rm in a d o . E lim ina los archivos existentes.
• i o s : : n o c r e a t e — Si el a rch iv o no existe, la apertura falla.
• i o s : : n o r e p l a c e — Si el a rch iv o ya existe, la apertura falla.

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.

Entreda L is t a d o 1 6 . 1 7 A g re g a c o n te n id o al final d e un archivo

1: / / L is ta d o 16.17 - Concatena texto a l f i n a l de un archivo


2: # in c lu d e <fstream .h>
3:
4: i n t main() // regre sa 1 en caso de error
5: {
6: char nombreArchivo[ 80 ];
7: char bu fer[ 255 ];
8:
9: cout << "Vuelva a e s c r i b i r el nombre del archivo:
10: c in » nombreArchivo;
11: if s t r e a m fin (n o m b re A rc h iv o );
12: i f ( f i n ) // ¿y a e x i s t e ?
continua
588 D í a 16

L ist ado 16.17 continuación

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

Vuelva a e s c r i b i r e l nombre del a rc h iv o : pruebal


S a l id a Contenido a c tu a l del a rc h iv o :
Esta lin e a se e s c r ib e directamente en e l a r c h i v o . . .
¡Este texto se e s c r i b i r á en e l a rc h iv o !

* * * F i n del contenido del a r c h i v o . * * *

Abriendo pruebal en modo a g r e g a r . ..

E s c rib a e l texto para e l a rc h iv o : Más te x to para e l a rc h iv o


He aquí e l contenido del a rc h iv o :
Esta lín e a se e sc rib e directamente en e l a r c h i v o . . .
F lu jo s 589

¡Este texto se e s c r i b i r á en el archivo!


Más texto para e l archivo

* * * F i n del contenido del a r c h iv o . * * *

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

D E B E probar cada apertura de un archivo N O D E B E tratar de cerrar o reasignar a cin


para cerciorarse de que fue exitosa. ni a cout.
D E B E reutilizar los objetos i f stream y
ofstream existentes.
D E B E cerrar todos los objetos fstream
cuando termine de utilizarlos.

Archivos binarios en comparación con


archivos de texto
Algunos sistemas operativos, como DOS. hacen distinción entre los archivos de texto y los
archivos binarios. Los archivos de texto guardan todo como texto (como tal vez haya adivi­
nado). por lo que los números grandes, como 54.325, se guardan como una cadena de núme­
ros (‘5 ', ‘4 '. *3', *2‘, ‘5'). Esto puede ser ineficiente, pero tiene la ventaja de que puede
leer y manipular el texto por medio de los comandos estándar de Linux, como cat, more, vi,
head. t a i l , grep, etc., y por medio de programas simples, como el comando type de DOS.
Para ayudar a que la biblioteca en tiempo de ejecución distinga entre archivos de texto y
binarios, C++ proporciona el indicador io s : :binary. Este indicador se ignora en muchos
sistemas debido a que todos los datos se guardan en formato binario. En algunos sistemas
algo m ojigatos, el indicador io s : :b in a ry no es válido y no compila, o lo que es peor, se
ignora silenciosam ente.
| 590 Día 16

En Linux, el sistema operativo ve los archivos como un flujo de bytes (es


Precauciún responsabilidad de los programadores dictar la estructura de ese flujo en sus
programas). En Linux, el compilador GNU maneja el indicador ios:binaryen
forma apropiada. En otros sistemas operativos, tal vez no sea así.

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.

En t r a d a L istado 16.18 Escritura de una clase en un archivo


1: // Listado 16.18 -
2: #include <fstream.h>
3:
4: class Animal
5: {
6: public:
7: Animal(int peso, long dias) :
8: suPeso(peso),
9: suNumeroDiasVivo(dias)
10: (}
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:
F lu jo s 591

25: int main() // regre sa 1 en caso de error


26: {
27: char nombreArchivo[ 80 ];
28:
29: cout << " E s c r ib a e l nombre del archivo:
30: c in >> nombreArchivo;
31 : ofstream f o u t ( nombreArchivo, i o s : : b in a ry ) ;
32: if (!fout)
33: {
34: cout << "No se puede a b r ir " << nombreArchivo;
35: cout << " para e s c r i t u r a . \ n " ;
1 6
36: r e t u r n (1 ) ;
37: }
38:
39: Animal Oso(50, 100);
40: f o u t . w r i t e ( ( c h a r *) &0so, siz e o f Oso);
41 : f o u t . c i ó s e ();
42: ifstre a m f i n (nombreArchivo, i o s : : b i n a r y ) ;
43: if (!f in )
44: {
45: cout << "No se puede a b r ir " « nombreArchivo;
46: cout << " para le c t u ra .\ n ";
47: r e t u r n (1 ) ;
48: }
49:
50: Animal OsoDos(1 , 1);
51 : cout « "OsoDos peso: " « OsoDos.ObtenerPeso() «
52: cout « "OsoDos dias: " « OsoDos.ObtenerDiasVivo(
53: f i n . read( (ch ar*) &0soDos, siz e o f OsoDos);
54: cout << "OsoDos peso: " « OsoDos.ObtenerPeso() «
55: cout « "OsoDos dias: " « OsoDos.ObtenerDiasVivo(
56: f i n .c i ó s e ();
57: return 0;
58: }

E s c rib a e l nombre del archivo: Animales


S a l id a OsoDos peso: 1
OsoDos d ia s : 1
OsoDos peso: 50
OsoDos d ia s : 100

En las líneas 4 a 23 se declara una clase Animal simplificada. En las líneas 31 a 37


A n á l is is
se crea un archivo y se abre para escritura en modo binario. En la línea 39 se crea
un animal cuyo peso es 50, y tiene 100 días de estar vivo, y en la línea 40 se escriben sus
datos en el archivo.

El archivo se cierra en la línea 41 y se vuelve a abrir para lectura en modo binario en la


línea 42. En la línea 50 se crea un segundo animal cuyo peso es 1, y tiene sólo un día de
estar vivo. En la línea 53 se leen los datos del archivo en el nuevo objeto animal, borrando
los datos existentes y reem plazándolos con los datos del archivo.
592 D í a 16

Procesamiento de ia línea de comandos


M uchos sistemas operativos, como DOS y UNIX, perm iten que el usuario pase parámetros
al programa cuando éste inicia. Estos parámetros se conocen com o opciones de la línea de
comandos, y por lo general se separan con espacios en la línea de com andos, por ejemplo:
UnPrograma Paraml Param2 Param3
Estos parámetros no se pasan directamente a m a in ( ). En v e / de eso, se pasan dos paráme­
tros a la función m a in ( ) de cualquier programa. El prim er parám etro es un entero que con­
tiene el número de argumentos de la línea de com andos. Tam bién se cuenta el nombre del
programa, así que lodos los programas tienen por lo m enos un parám etro. El ejemplo de la
línea de comandos que se mostró anteriormente tiene cuatro. (El nom bre UnPrograma más
los tres parámetros forman un total de cuatro argum entos de línea de comandos.)
El segundo parám etro que se pasa a m a in () es un arreg lo de apuntadores a cadenas de
caracteres. Ya que el nombre de un arreglo es un apuntador constante al prim er elemento
del arreglo, puede declarar este argum ento com o un apu n tad o r a un apuntador a un char.
un apuntador a un arreglo de valores de tipo char. o com o un arreglo de arreglos de valores
de tipo char.
Por lo general, el primer argumento se llama arg e (conleo de los argum entos), pero puede
ponerle el nombre que usted quiera. El segundo argum ento por lo general se llama argv
(vector de argum entos), pero, com o en el anterior, esto es sólo una convención.
Es com ún evaluar a arg e para asegurarse de haber recibido el núm ero esperado de argu­
m entos, y utilizar argv para tener acceso a los argum entos en sí. O bserve que a rg v [ 0 ] es
el nombre del programa, y a rg v [ 1 ] es el prim er parám etro para el program a, que se repre­
senta com o una cadena. Si su program a tom a dos núm eros com o argum entos, tendrá que
convertir estas cadenas en números. En el día 21 verá cóm o utilizar las conversiones de la
biblioteca estándar. El listado 16.19 muestra cóm o utilizar los argum entos de la línea de
com andos.

En tra d a L is t a d o 1 6 . 1 9 Uso d e los a r g u m e n to s d e la lín e a d e c o m a n d o s

1: // L is t a d o 16.19 - Uso de lo s argumentos de l a l í n e a de comandos


2: tfinclude <iostream .h>
3:
4: i n t m a in (in t arge, char **a rg v )
5: {
6: cout << "Se re c ib ie r o n " << arge << " argum entos. . . \ n " ;
7: f o r ( i n t i = 0; i < arge; i++)
8: cout << "argumento " << i << " << a r g v [ i ] << endl;
9: return 0;
10: }

l s t 16-19 Aprenda C++ en 21 d ía s


S a l id a Se re c ib ie r o n 6 argumentos...
argumento 0: ls t 1 6 - 1 9
F lu jo s 593

argumento 1: Aprenda
argumento 2: C++
argumento 3: en
argumento 4: 21
argumento 5: d ía s

E sto f u n c io n a m e jo r c u a n d o se ejecuta d e sd e u n a lín e a d e c o m a n d o s . Si e stá


u t iliz a n d o u n a in te rfa z G U I c o m o K D E (K D e sk to p En viron m e n t), te n d rá q u e a b r ir
1 6
u n a s e sió n d e te rm in a l. Si está u tiliza n d o u n sistem a o p e ra tiv o d istin to d e Linux,
tal v e z te n g a q u e ejecutar este c ó d ig o d esd e la línea de c o m a n d o s (es decir, d e s d e
u n a v e n t a n a D O S ) o c o n fig u ra r los p arám etros de línea de c o m a n d o s e n su c o m p i­
la d o r (p a ra ello, ve a la d o c u m e n ta c ió n de su co m p ila d o r).

La función m a in () declara dos argumentos: argc es un entero que contiene la cuenta


A n á l is is
de los argum entos de la línea de comandos, y argv es un apuntador al arreglo de
cadenas. Cada cadena del arreglo a la que apunta argv es un argumento de la línea de eo-
rnandos. O bserve que a r g v se hubiera podido declarar fácilmente como char * a r g v [ ] o
char a rg v [ ] [ ] . La form a en que declare a argv es cuestión de su estilo de program ación;
aún cuando este program a lo declaró como un apuntador a un apuntador, se utilizaron des­
plazam ientos de arreglos para tener acceso a las cadenas individuales.

En la línea 6 se utiliza a rg c para im prim ir el número de argum entos de la línea de


com andos: 6 en total, contando el nombre del programa.

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.

Entrada L is t a d o 1 6 . 2 0 Uso d e a rg u m e n to s d e la línea d e co m an d o s

1: // L is t a d o 16.20 - Ejemplo del manejo de los argumentos


2: tfinclude <fstream.h>
3:
4: c l a s s Animal
5: {
6: p u b lic :
7: A n im a l(m t peso, long d ia s):
8: suP e so ( p eso ),
9: suNumeroDiasVivo(dias)
10: {}
continua
594 Día 16

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

Comencemos con los espacios


de nombres
Los conflictos de nom bres han sido una fuente de irritación para los desarrolla­
dores de C y de C++. La estandarización ANSI ofrece una oportunidad de resolver
este problem a mediante el uso de espacios de nombres, pero tome en cuenta esto:
no todos los compiladores soportan esta característica. Los compiladores g++ ver­
siones 2.9.5 y posteriores sí soportan esta característica. El com pilador g++
versión 2.7.2 es uno de esos que no la soporta y emitirá el siguiente mensaje:
warning: namespaces are mostly broken in t h is versión of g++
600 Día 17

Un conflicto de nombres ocurre cuando se encuentra un nombre duplicado con el mismo


alcance en dos partes del programa. Esto ocurre con más frecuencia en paquetes de biblio­
tecas distintos. Por ejemplo, una biblioteca de clases contenedoras muy probablemente
declarará e implementará una clase llamada List. (Aprenderá más sobre las clases conte­
nedoras en el día 19, “Plantillas”.)
No es sorprendente encontrar también una clase List en una biblioteca de manejo de
ventanas. Suponga que necesita mantener una lista de ventanas para su aplicación, o que
está utilizando la clase List que se encuentra en la biblioteca de clases contenedoras. Al
declarar una instancia de la clase List de la biblioteca de ventanas para guardar sus ven­
tanas, descubre que las funciones miembro que quiere llamar no están disponibles. El com­
pilador ha igualado su declaración de List con la clase List contenedora de la biblioteca
estándar, pero la que realmente quiere es la clase List que se encuentra en la biblioteca de
ventanas específica del fabricante.
Los espacios de nombres se utilizan para particionar el espacio de nombres global y para
eliminar, o por lo menos reducir, los conflictos de nombres. Los espacios de nombres son
parecidos en cierta forma a las clases, y la sintaxis es muy similar.
Los elementos declarados dentro del espacio de nombres son propiedad del mismo. Todos
los elementos dentro de un espacio de nombres tienen visibilidad pública. Los espacios de
nombres se pueden anidar dentro de otros espacios de nombres. Las funciones se pueden
definir dentro o fuera del cuerpo del espacio de nombres. Si una función se define fuera del
cuerpo del espacio de nombres, debe ser identificada por el nombre del espacio de nombres.

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 enlazador GNU anuncia los siguientes mensajes de diagnóstico:


segundo.cc: múltiple definition of 'valorEntero'
primero.cc: primero defined here
Si estos nombres tuvieran un alcance distinto, ni el compilador ni el enlazador tendrían
problemas.
También es posible recibir una advertencia del compilador con respecto al ocultamiento
de identificado res. El com pilador debe advertir en primero, cxx que la variable
valorEntero de main() está ocultando la variable global que tiene el mismo nombre.
Para usar la variable valorEntero declarada fuera de main(), debe establecer en forma
explícita que desea utilizar la variable global. Considere este ejemplo, el cual asigna el
valor 10 a la variable valorEntero que está fuera de main(), y no a la variable
valorEntero declarada dentro de main():
// archivo primero.cxx
int valorEntero = 0 ;
int main( )
{
int valorEntero = 0 ;
::valorEntero = 10 ; //asignar a "valorEntero" global
// . . .
return 0 ;
} ;
// archivo segundo.cxx
int valorEntero = 0 ;
// fin de segundo.cxx

O b se rve el uso del o p e ra d o r de resolución de ám bito :: el cual indica q u e se


Nota hace referencia a v a lo rE n te ro com o global, no com o local.

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

El térm ino visibilidad se utiliza para d esign ar el alcance de un objeto definido, ya


sea una variable, una clase o una función. Por ejemplo, una variable declarada y
definida fuera de cualquier función tiene un alcance de archivo, o global. La visi­
bilidad de esta variable va desde el p un to de su definición hasta el fin del archivo.
Una variable que te n ga un alcance de bloque, o local, se encuentra dentro de una
estructura de bloque. Los ejemplos m ás com unes son las variables definidas dentro
de funciones. El siguiente ejem plo m uestra el alcance de las variables.

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

L o s n o m b r e s p u e d e n te n e r enlace interno y enlace externo. Estos d o s té r m in o s se


r e f ie re n al u s o o d is p o n ib ilid a d d e u n n o m b re e n tre m ú ltip le s u n id a d e s d e t r a ­
d u c c ió n o d e n t r o d e u n a sola u n id a d d e traducción. C u a lq u ie r n o m b re q u e te n g a
e n la c e in t e r n o p u e d e se r re ferid o só lo d e n tro d e la u n id a d d e tra d u c c ió n e n la
q u e e stá d e fin id o . P o r ejem plo, u n a variab le d e fin id a p a ra te n e r e n la ce in te rn o
p u e d e se r c o m p a r t id a p o r fu n c io n e s q u e estén d e n t ro d e la m ism a u n id a d d e
tra d u c c ió n . L os n o m b re s q u e te n g a n enlace e xte rn o e stá n d isp o n ib le s p a ra o tra s
u n id a d e s d e tra d u cc ió n . El sig u ie n te ejem p lo d e m u e stra el fu n c io n a m ie n t o d e los
e n la c e s in te rn o y e xte rn o.

// a rch ivo : primero.cxx


in t in tE x te rn o = 5 ;
1 7
in t main()
{
return 0 ;
}
// archivo: segundo.cxx
extern in t in tE x te rn o ;
in t unlntExterno = 10 ;
const in t j = 1 0 ;

La variable i n t E x t e r n o definida en p rim e ro . cxx tiene enlace externo. Aunque se define


en p r i m e r o . cxx, s e g u n d o . cxx también puede tener acceso a ella. Las dos “j ” de ambos
archivos son c o n s t y por lo tanto tienen enlace interno de manera predeterminada. Puede
evitar el c o n s t predeterm inado si proporciona una declaración explícita, como se m uestra a
continuación:
// archivo: primero.cxx
extern const in t j = 1 0 ;

// 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 com ité de estándares desaprueba el siguiente uso:


s t a t i c in t s t a t i c l n t = 10 ;
i n t main()
{
// . . .

}
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

D E B E u tiliz a r e sp a c io s d e n o m b r e s e n lu g a r N O D E B E a p lic a r la p a la b ra reservada sta tic


d e la p a la b ra re se rv a d a s t a t i c . a u n a v a ria b le d e fin id a c o n alcance de archivo.

Creación de un espacio de nombres


La sintaxis para la declaración de un espacio de nom bres es sim ilar a la sintaxis para una
declaración de un tipo s t r u c t o de una clase: primero, aplique la palabra reservada ñames -
pace seguida de un nom bre opcional para el espacio de nom bres, y luego una llave de
apertura. El espacio de nom bres se concluye con una llave de cierre, sin utilizar punto y
com a ai final.
He aquí un ejemplo:
namespace Ventana
{
void mover(int x, in t y) ;
}
El nom bre Ventana identifica en forma única al espacio de nom bres. Puede tener muchas
ocurrencias del nombre que identifica a un espacio de nom bres. Estas múltiples ocurrencias
pueden estar dentro de un solo archivo o entre múltiples unidades de traducción. El espa­
cio de nombre s td de la biblioteca estándar de C++ es un excelente ejem plo de esta carac­
terística. Esto tiene sentido ya que la biblioteca estándar es un agrupam iento lógico de
funcionalidad.
El principal concepto detrás de los espacios de nom bres es agrupar elem entos relacionados
en un área especificada (todos bajo un mismo nombre). El siguiente es un breve ejemplo de
un espacio de nom bres que abarca varios archivos de encabezado:
// encabezadol. h
namespace Ventana
{
vo id mover(int x, in t y) ;
}
// encabezado2. h
Espacios de nombres 605

namespace Ventana
{
void cambiarTamanio(int x, int y) ;
}

Declaración y definición de tipos


Puede declarar y definir tipos y funciones dentro de espacios de nombres. Claro que ésta
es una cuestión de diseño y de mantenimiento. Un buen diseño implica que debe separar
la interfaz de la implementación. Debe seguir este principio no sólo con las clases, sino
también con los espacios de nombres. El siguiente ejemplo muestra un espacio de nombres
desordenado y mal definido:
namespace Ventana {
// . . . otras declaraciones y definiciones de variables,
void mover(int x, int y) ; // declaraciones
void cambiarTamanio(int x, int y) ;
// . . . otras declaraciones y definiciones de variables.
void mover(int x, int y)
{
if(x < MAX_PANTALLA_X && x > 0)
if(y < MAX_PANTALLA_Y && y > 0)
plataforma.mover(x, y) ; // rutina específica
}
void cambiarTamanio(int x, int y)
{
if(x < MAX_TAMANI0_X && x > 0)
if(y < MAX_TAMANI0_Y && y > 0)
plataforma.cambiarTamanio(x, y) ; // rutina específica
}
// . . . continúan las definiciones
}
¡Puede ver qué tan rápido se llenan los espacios de nombres! El ejemplo anterior tiene apro­
ximadamente 20 líneas; imagínese si este espacio de nombres fuera cuatro veces más largo.

Cómo definir funciones fuera de un espacio


de nombres
Debe definir las funciones para los espacios de nombres fuera del cuerpo del espacio de
nombres. Esto muestra una clara separación de la declaración de la función y su definición,
y mantiene ordenado el espacio de nombres. Separar del espacio de nombres la defini­
ción de la función también le permite colocar dentro de un archivo de encabezado el espacio
de nombres y sus declaraciones encamadas; las definiciones se pueden colocar en un archi­
vo de implementación.
|606 Día 17

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
}

Cómo agregar nuevos miembros


Puede agregar nuevos miembros a un espacio de nombres sólo dentro de su cuerpo. No
puede definir nuevos miembros utilizando sintaxis de identificación completa. Lo más
que puede esperar de este estilo de definición es una queja del compilador. El siguiente
ejemplo demuestra este error:
namespace Ventana {
// muchas declaraciones
}
//.. .algo de código
int Ventana::nuevoEnteroEnEspacionombre ; // lo siento, no puedo hacer esto
La línea de código anterior no es válida. Su compilador (apegado al ANSI de C++) emitirá
un diagnóstico que refleje el error. Para corregir el error (o evitarlo por completo) coloque
la declaración dentro del cuerpo del espacio de nombres.
Todos los miembros que están dentro de un espacio de nombres son públicos. El siguiente
código no compilará:
namespace Ventana {
private:
void mover(int x, int y) ;
>

Cómo anidar espacios de nombres


Es posible anidar un espacio de nombres dentro de otro espacio de nombres. Se pueden
anidar debido a que la definición de un espacio de nombres también es una declaración.
Como con cualquier otro espacio de nombres, debe identificar cada elemento o función uti­
lizando el nombre de cada espacio de nombres que lo contiene. Por ejemplo, a continuación
se muestra la declaración de un espacio de nombres anidado dentro de otro espacio de
nombres:
namespace Ventana {
namespace Vidrio {
void tamanio(int x, int y) ;
}
}
E s p a c io s d e n o m b r e s 607

Para tener acceso a la función t a m a n i o ( ) fuera de Ventana, debe identificar a la función


con ambos nom bres de los espacios de nombres que la incluyen. El siguiente código m ues­
tra la identificación:
int main( )
{
Ventana: : V i d r i o : : tamanio(10, 20) ;
return 0 ;

Uso de un espacio de nombres


Ahora veamos un ejem plo del uso de un espacio de nombres y del operador de resolución
de ámbito. El código declara prim ero todos los tipos y funciones a utilizar dentro del espa­
cio de nom bres llamado Ventana. Después de definir todo lo requerido, el ejemplo define 1 7
todas las funciones m iem bro declaradas. Estas funciones miembro se definen fuera del
espacio de nom bres; los nom bres se identifican en forma explícita por medio del operador
de resolución de ámbito. El listado 17.1 muestra el uso de un espacio de nombres.

Entrada L is t a d o 17.1 U so de un espacio de nombres

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 ;

Presentación de la palabra reservada using


La palabra reservada using se utiliza tanto para la directiva using como para la declaración
using. La sintaxis de la palabra reservada using determina si el contexto es una directiva
o una declaración.

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 ;

using namespace Ventana ;


valor2 = 30 ;

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

no requiere de identificación ya que la directiva introduce en el alcance actual todos los


nombres que se encuentren en un espacio de nombres Ventana.
La directiva using se puede utilizar en cualquier nivel de alcance. Esto le permite uti­
lizar la directiva dentro del alcance de bloque; cuando ese bloque quede fuera de alcance,
también quedarán fuera de alcance todos los nombres que se encuentren dentro del espa­
cio de nombres. El siguiente ejemplo muestra este comportamiento:
namespace Ventana {
int valorl = 20 ;
int valor2 = 40 ;
}
//. . .
void f()
{
{
using namespace Ventana ;
valor2 = 30 ;
}
valor2 = 20 ; //¡error!
}
La última línea de código de f (), valor2 = 20 ; es un error, ya que valor2 no está
definido. El nombre es accesible en el bloque anterior debido a que la directiva lleva el
nombre dentro de ese bloque. Cuando ese bloque queda fuera de alcance, ocurre lo mismo
con los nombres que se encuentran en el espacio de nombres llamado Ventana.
Los nombres de variables declaradas dentro de un alcance local ocultan cualquier nombre
de un espacio de nombres que se introduzca en ese alcance. Este comportamiento es similar
a la forma en que una variable local oculta a una variable global. Incluso si usted introduce
un espacio de nombres después de una variable local, esa variable local ocultará el nombre
del espacio de nombres. El siguiente ejemplo muestra esto:
namespace Ventana {
int valorl = 20 ;
int valor2 = 40 ;
}
//. . .
void f()
{
int valor2 = 10 ;
using namespace Ventana ;
std::cout « valor2 « std:rendí ;
>
La salida de esta función es 10, no 40. Esta salida confirma el hecho de que el valor2 que
se encuentra en f () oculta al valor2 que se encuentra en el espacio de nombres llamado
Ventana. Si necesita utilizar un nombre dentro de un espacio de nombres, debe identificar
ese nombre con el nombre del espacio de nombres.
Espacios de nom bres 611

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 ;

La segunda línea de la función f () producirá un error de compilación debido a que el nom­


bre valor2 ya está definido. El mismo error ocurre si la declaración using se introduce
antes de la definición del valor2 local.
Cualquier nombre introducido en el alcance local con una declaración using oculta a cual­
quier nombre que esté fuera de ese alcance. El siguiente código demuestra este comporta­
miento:
namespace Ventana {
int valorl = 20 ;
int valor2 = 40 ;
}
int valor2 = 10 ;
//. . .
void f()
{
using Ventana::valor2 ;
std::cout « valor2 « std::endl
}
La declaración using de f () oculta al valor2 definido en el espacio de nombres global.
Como mencioné anteriormente, una declaración using le brinda un control más fino
sobre los nombres introducidos desde un espacio de nombres. Una directiva using lleva
a todos los nombres desde un espacio de nombres hasta el alcance actual. Es preferible uti­
lizar una declaración en lugar de una directiva, ya que una directiva anula el propósito
del mecanismo del espacio de nombres. Una declaración es más definitiva ya que se están
identificando explícitamente los nombres que se quieren introducir en un alcance. Una de­
claración using no contaminará el espacio de nombres global, como ocurre con una
directiva using (a menos que se declaren todos los nombres encontrados en el espacio
de nombres). El ocultamiento de nombres, la contaminación del espacio de nombres global
y la ambigüedad se reducen a un nivel más manejable por medio de la declaración using.
Espacios de nombres 613

Uso del alias de un espacio de nombres


El a lia s de un esp acio de nom bres está diseñado para dar a un espacio de nom bres un
nombre distinto del que tiene. Un alias proporciona un término abreviado para que usted
lo utilice para referirse a un espacio de nombres. Esto es cierto sobre todo cuando el nombre
de un espacio de nom bres es muy largo; crear un alias puede ayudar a no escribir mucho
y en form a repetitiva. A nalice este ejemplo:
namespace la_coinpania_de_software {
int valor ;
// . . .
}
la_compania_de_software: : valor = 10 ;

namespace LCS = la_compania_de_software ;


LCS:: valor = 20 ; 17
Una desventaja es que el alias que usted elija puede tener conflicto con un nombre existente.
De ser así, el com pilador se dará cuenta del conflicto y podrá resolverlo cam biando el
nombre del alias.

Uso del espado de nombres sin nombre


Un espacio de nombres sin nombre es simplemente eso: un espacio de nombres que no tiene
un nombre. Un uso com ún de los espacios sin nombre es proteger los datos globales de los
potenciales conflictos de nombre entre unidades de traducción. Cada unidad de traducción
tiene su propio y único espacio de nombres sin nombre. Puede hacer referencia a todos
los nom bres definidos dentro del espacio de nombres sin nombre (dentro de cada unidad
de traducción) sin necesidad de identificarlos explícitamente. Lo siguiente es un ejemplo de
dos espacios de nom bre sin nombre que se encuentran en dos archivos separados:
// archivo: uno.cxx
namespace {
int valor ;
char ap(char *ap) ;
//. ■ •
}
// archivo: dos.cxx
namespace {
int valor ;
char ap(char *ap) ;
I I . ■ •

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

Uso del espacio de nombres estándar std


El mejor ejemplo de los espacios de nombres se encuentra en la biblioteca estándar de C++.
La biblioteca estándar está completamente encerrada dentro del espacio de nombres lla­
mado std. Todas las funciones, clases, objetos y plantillas se declaran dentro del espacio
de nombres std.
Sin duda, verá código como el siguiente:
#include <iostream>
using namespace std ;

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

¿Es C++ un lenguaje orientado


a objetos?
C++ se creó como un puente entre la programación orientada a objetos y el
lenguaje C, el lenguaje de programación más popular en el mundo para desarrollo
de software comercial. El objetivo era proporcionar un diseño orientado a objetos
para una plataforma veloz de desarrollo de software comercial.
620 Día 18

C se desarrolló como intermediario entre los lenguajes de aplicaciones de negocios de


alto nivel, como COBOL, y el lenguaje ensamblador, que es de alto rendimiento pero
difícil de utilizar. C debía implementar la programación “estructurada", en la que los
problemas se “descomponían" en unidades más pequeñas de actividades repetitivas
llamadas procedimientos.
Los programas que estamos escribiendo en el comienzo de este nuevo siglo son mucho
más complejos que los que se escribieron en el principio de la última década. Los pro­
gramas creados en lenguajes procedurales tienden a ser difíciles de manejar, difíciles de
mantener e imposibles de extender. Las interfaces gráficas de usuario, Internet, la telefonía
digital y una gran variedad de nuevas tecnologías han incrementado en forma dramática
la complejidad de los proyectos de programación. A su vez, las expectativas del consumi­
dor en cuanto a la calidad de la interfaz de usuario se están elevando.
Ante esta complejidad creciente, los desarrolladores analizaron profundamente el estado
de la industria. Lo que descubrieron fue, en el mejor de los casos, desalentador. El software
se terminaba demasiado tarde, no estaba completo, era defectuoso, nada confiable y costoso.
A menudo los proyectos se pasaban del presupuesto y salían muy tarde al mercado. El
costo de mantener y construir estos proyectos era prohibitivo, y se desperdiciaba una
tremenda cantidad de dinero.
El desarrollo de software orientado a objetos ofrece una salida para este abismo. Los
lenguajes de programación orientada a objetos crean un sólido enlace entre las estructuras
de datos y los métodos que manipulan esos datos. Lo que es más importante, en la progra­
mación orientada a objetos ya no se piensa en las estructuras de datos y las funciones mani­
puladoras; en vez de esto, se piensa en los objetos (cosas).
El mundo está lleno de cosas: autos, perros, árboles, nubes, flores, etc. Cada cosa tiene
características (veloz, amigable, café, esponjoso, bonito). Casi todas las cosas tienen un
comportamiento (mover, ladrar, crecer, llover, marchitarse). No pensamos en los datos
acerca de un perro y cómo los podríamos manipular; pensamos en un perro como una
cosa de este mundo, qué apariencia tiene y qué hace.

Qué son los modelos


Si vamos a manejar la complejidad, debemos crear un modelo del universo. El objetivo
del modelo es crear una abstracción significativa del mundo real. Dicha abstracción debe
ser más simple que el mundo real, pero también debe reflejar al mundo real en forma
precisa, para que podamos utilizar el modelo para predecir el comportamiento de las
cosas del mundo real.
El globo terráqueo de un niño es un modelo clásico. El modelo no es la cosa en sí; nunca
podríamos confundir el globo terráqueo de un niño con el planeta Tierra, pero uno proyecta
al otro lo suficientemente bien como para que podamos aprender sobre la Tierra estudiando
el globo terráqueo.
A nálisis y diseño orien ta dos a objetos 621

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.

Diseño de software: el lenguaje


de modelado
El lenguaje de m odelado es el aspecto menos importante del análisis y diseño orientados a
objetos; por desgracia, tiende a recibir la mayor atención. Un lenguaje de modelado es sólo
una convención sobre la forma de dibujar un modelo en papel. Podemos decidir fácilmente
dibujar nuestras clases como triángulos y la relación de herencia como una línea punteada. 18
De ser así, podríamos modelar un geranio como se muestra en la figura 18.1.

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.

Diseño de software: el proceso


El proceso del análisis y el diseño orientados a objetos es mucho más complejo e importante
que el lenguaje de modelado. Así que, desde luego, es de lo que menos se oye hablar. Esto
se debe a que el debate sobre los lenguajes de modelado está más arraigado; como industria,
decidimos utilizar UML. El debate en cuanto al proceso continúa.
Un metodologista es alguien que desarrolla o estudia uno o más métodos. Por lo general,
los metodologistas desarrollan y publican sus propios métodos. Un método es un lenguaje
de modelado y un proceso. Tres de los principales metodologistas y sus métodos son Grady
Booch, quien desarrolló el método Booch; Ivar Jacobson, quien desarrolló la ingeniería de
software orientada a objetos; y James Rumbaugh, quien desarrolló la OMT (Tecnología
de Modelado de Objetos). Juntos, estos tres hombres han creado Objectory, un método y
A n á lisis y diseño o rie n ta d o s a o b jetos 623

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

En el diseño iterativo, el visionario idea un concepto, y luego empezamos a trabajar en él


para desarrollar los requerimientos. A medida que examinamos los detalles, la visión puede
crecer y evolucionar. Cuando llevamos un buen avance en los requerimientos, empezamos
el diseño, sabiendo perfectamente que las preguntas que surgen durante el diseño pueden
ocasionar modificaciones a los requerimientos. A medida que trabajamos sobre el diseño,
empezamos a crear prototipos y luego a implementar el producto. Las cuestiones que surgen
en el desarrollo retroalimentan el diseño e incluso pueden influenciar nuestra comprensión
de los requerimientos. Lo que es más importante, diseñamos e implementamos sólo piezas
del producto completo, iterando repetidamente en las fases de diseño e implementación.
Aunque los pasos del proceso se repiten en forma iterativa, es casi imposible describirlos
de manera tan cíclica. Por lo tanto, los describiré en secuencia: conceptualización, análisis,
diseño, implementación, prueba y distribución. No me malentienda, en realidad pasamos
por cada uno de estos pasos muchas veces durante el desarrollo de un solo producto. El
proceso de diseño iterativo sólo es difícil de presentar y de comprender si avanzamos por
cada paso en forma cíclica, por lo que los describo uno tras otro.
A continuación se muestran los pasos del proceso de diseño iterativo:
1. Conceptualización: cuando se desarrollan la visión y el propósito general del
proyecto.
2. Análisis: determinar las necesidades de la organización (entender lo que debe hacer
el software); aquí es donde se modelan las clases.
3. Diseño: crear el plano para solucionar el problema establecido.
4. Implementación: crear el sistema a partir del plano de diseño; aquí es donde se
desarrolla el código (por ejemplo, en C++).
5. Prueba: asegurarse de que el sistema haga lo que se supone que debe de hacer.
6. Distribución: proporcionar el sistema a los usuarios.
Estos pasos son muy sencillos. Todo lo que resta son detalles.

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.

Análisis de los requerimientos


La fase de conceptualización, en la que se articula la visión, es muy breve. Puede ser sólo
un destello de una idea, seguido del tiempo que lleva escribir lo que el visionario tiene
en mente. A menudo, como experto orientado a objetos, usted se unirá al proyecto después
de que la visión se haya articulado.

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

• El cliente abre una cuenta


• El cliente cierra una cuenta
• El cliente entra en su cuenta
° El cliente revisa las transacciones recientes
• El empleado del banco entra a un cuenta especial de administración
• El empleado del banco hace un ajuste a la cuenta de un cliente
• Un sistema de soporte para la oficina actualiza la cuenta de un usuario con base en
la actividad externa
• Los cambios en la cuenta de un usuario se reflejan en un sistema de apoyo para la
oficina
• El cajero automático avisa que no tiene suministro de efectivo
• El técnico del banco llena con efectivo el cajero automático

Cóm o crear el m odelo del dom inio


Después de un primer vistazo a sus casos de uso, puede empezar a desarrollar su documento
de requerimientos con un modelo detallado del dominio. El modelo del dom inio es un
documento en el que captura todo lo que sabe acerca del dominio (el campo comercial en
el que está trabajando). Como parte de su modelo de dominio, debe crear objetos que des­
criban a todos los objetos mencionados en sus casos de uso. Hasta ahora, el ejemplo del
cajero automático incluye estos objetos: cliente, personal del banco, sistemas de soporte
para las oficinas, cuanta de cheques, cuenta de ahorros, etc.
Para cada uno de estos objetos del dominio, necesitamos capturar datos esenciales, como el
nombre del objeto (por ejemplo, cliente, cuenta y asi sucesivamente), si el objeto es un actor,
el comportamiento y los atributos principales del objeto. Muchas herramientas de modelado
soportan la captura de esta información en descripciones de “clases”. La figura 18.4 muestra
cómo se captura esta información con el programa Rational Rose de Rational Software.

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.

De nuevo, es importante observar que las relaciones que estamos mostrando


en este momento son relaciones entre objetos del dominio. Más adelante,
tal vez decida tener un objeto CuentaCheques en su diseño, así como un
objeto CuentaBanco, e implementar esta relación por medio de la herencia;
pero éstas son decisiones en tiem po de diseño. En tiem po de análisis, todo
lo que estamos haciendo es documentar nuestra comprensión de estos obje­
tos del dominio.
A n á lisis y diseño o rie n ta d o s a o b jetos 631

El UML es un lenguaje de modelado muy completo, y puede capturar cualquier cantidad


de relaciones. Sin embargo, las principales relaciones que se capturan en el análisis son
la generalización (o especialización), la contención y la asociación.
G e n e ra liz a c ió n
A menudo la generalización se confunde con la “herencia”, pero existe una clara y con­
siderable distinción entre las dos. La generalización describe la relación; la herencia es la
implementación de la generalización por medio de la programación (es la forma en que
manifestamos la generalización en el código).
La generalización implica que el objeto derivado es un subtipo del objeto base. Por lo
tanto, una cuenta de cheques es una cuenta de banco. La relación es simétrica; la cuenta
de banco generaliza el comportamiento y los atributos comunes de las cuentas de cheques
y de ahorros.
Durante el análisis del dominio se busca capturar estas relaciones de la m ism a fo rm a en
que existen en el m undo real.
C o n te n c ió n
Por lo general, un objeto se compone de muchos objetos de otro tipo. Por ejemplo, un auto
está compuesto de un volante, llantas, puertas, radio, etc. Una cuenta de cheques se com­
pone de un balance, un historial de transacciones, el número de identificación del cliente, 18
etc. Decimos que la cuenta de cheques tiene estos elementos; los modelos de contención
tienen la relación tiene un. El UML muestra la relación de contención por medio del
dibujo de una línea con un rombo que va desde el objeto que contiene hasta el objeto con­
tenido. como se muestra en la figura 18.6.

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.

Cóm o establecer lineam ientos


Como parte de su metodología, debe crear lineamientos para documentar cada escenario.
Estos lineamientos se capturan en el documento de requerimientos. Por lo general, debe
asegurarse de que cada escenario incluya lo siguiente:
• Condiciones previas-—Qué debe ser cierto para que el escenario comience
• Activadores— Qué hace que el escenario comience
634 Día 18

• Qué acciones realizan los actores


• Qué resultados o cambios ocasiona el sistema
• Qué retroal imentación reciben los actores
• Si ocurren o no las actividades repetitivas, y qué ocasiona que concluyan
• Una descripción del flujo lógico del escenario
• Qué hace que el escenario termine
• Condiciones posteriores—Qué debe ser cierto cuando el escenario esté completo
Además, debe nombrar cada caso de uso y cada escenario. Por ejemplo, podría tener la
siguiente situación:
Caso de uso: El cliente retira efectivo.
Escenario: El retiro de efectivo de la cuenta de cheques se realiza con éxito.
Condiciones El cliente ya está dentro del sistema.
previas:
Activador: El cliente solicita un “retiro”.
Descripción: El cliente elige retirar efectivo de una cuenta de cheques. Hay
suficiente efectivo en la cuenta, suficiente papel para recibos en
el cajero automático y la red está funcionando. El cajero automá­
tico pide al cliente que indique la cantidad del retiro, y el cliente
pide $300, una cantidad válida para retirar en este momento. La
máquina entrega $300 e imprime un recibo, y el cliente toma el
dinero y el recibo.
Condiciones La cuenta del cliente tiene un débito de $300, y el cliente tiene
posteriores: $300 en efectivo.
Este caso de uso se puede mostrar con el diagrama tan increíblemente simple que se
muestra en la figura 18.9.
Aquí se captura poca información, exceptuando una abstracción de alto nivel de una
interacción entre un actor (el cliente) y el sistema. Este diagrama se vuelve un poco más
útil cuando se muestra la interacción entre los casos de uso. Digo que un poco más útil
debido a que sólo son posibles dos interacciones: "usa" y "extiende". El estereotipo "usa"
indica que un caso de uso es un superconjunto de otro. Por ejemplo, no es posible retirar
efectivo sin antes iniciar una sesión. Podemos mostrar esta relación con el diagrama que
se muestra en la figura 18.10.
Análisis y diseño orientados a objetos 635 |

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.

El caso de uso "extiende" tiene el propósito de indicar relaciones condicio­


nales y algo semejante a la herencia, pero existe tanta confusión en la co­
munidad modeladora de objetos en relación con la distinción entre "usa" y
"extiende" que muchos desarrolladores simplemente han hecho a un lado
a "extiende", pues sienten que su significado no está muy bien comprendido.
Yo utilizo "usa" cuando de otra forma tendría que copiar y pegar el caso
de uso completo, y utilizo "extiende" cuando sólo uso el caso bajo ciertas
condiciones definibles.
636 Día 18

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.

Figura 18.11 Interfaz de usuario- Cuenta


Cliente (cajero automático)
Diagrama de interac­ de cheques
ciones de UML.
1: Solicitar retiro

2: Mostrar opciones

! 3 : Indicar cantidad y cuenta

• 4: Revisar balances, estado, etc. •


i

« 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

Cóm o crear paquetes


Debido a que se generan muchos casos de uso para cualquier problema que sea conside­
rablemente complejo, el UML le permite agrupar los casos de uso en paquetes.
Un paquete es como un directorio o una carpeta; es una colección de objetos de modelado
(clases, actores, etcétera). Para manejar la complejidad de los casos de uso. puede crear
paquetes agregados por cualquier característica que tenga sentido para su problema. Por
ejemplo, puede agregar sus casos de uso por tipo de cuenta (todo lo que afecte a las cuentas
de cheques o a las de ahorros), por crédito o débito, por tipo de cliente, o por cualquier
característica que tenga sentido para usted. Lo que es más importante, un solo caso de uso
puede aparecer en distintos paquetes, lo que le permite una gran flexibilidad en el diseño.

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.

A n á lisis de los sistem as


Hay software escrito para operar individualmente, que interactúa sólo con el usuario final.
Sin embargo, a menudo necesitará tener una interfaz con un sistema operativo. El análisis
de los sistem as es el proceso de recolectar todos los detalles de los sistemas con los
cuales interactuará. ¿Su nuevo sistema será un servidor que proporcione servicios al
sistema existente, o será un cliente? ¿Podrá negociar una interfaz entre los sistemas, o
deberá adaptarse a un estándar existente? ¿El otro sistema será estable, o deberá estar
tirando continuamente a un blanco en movimiento?
638 Día 18

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.

La inclusión de "tiempo de reserva" en su documento de planeación no es


una excusa para evitar los documentos de planeación. Es sólo una adverten­
cia para no confiar demasiado en ellos antes de tiem po. A medida que el
proyecto avance, usted intensificará su comprensión de cómo funciona el sis­
tema, y sus estimaciones serán cada vez más precisas.
A nálisis y diseño orientados a objetos 639

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.

Qué son las clases


Como programador de C++, usted está acostumbrado a crear clases. La metodología del
diseño formal requiere que usted separe la clase de C++ de la clase de diseño, aunque
estarán íntimamente relacionadas. La clase de C++ que usted escribe en el código es la
implementación de la clase que diseñó. Éstas son isomorfas: cada clase de su diseño
corresponderá a una clase de su código, pero no las confunda. Ciertamente, es posible
implementar sus clases de diseño en otro lenguaje, y la sintaxis de las definiciones de la
clase podría cambiar.
Dicho esto, la mayor parte del tiempo hablamos sobre estas clases sin hacer distinción
entre ellas debido a que las diferencias son altamente abstractas. Cuando usted dice que en
su modelo la clase Gato tendrá un método Maullar (), debe entender que esto significa que
también colocará un método Maullar() en su clase de C++.
Las clases del modelo se capturan en diagramas de UML, y las clases de C++ se capturan
en código que se puede compilar. La distinción es significativa, aunque sutil.
De cualquier forma, el mayor obstáculo para muchos novatos es encontrar el conjunto
inicial de clases y comprender lo que conforma a una clase bien diseñada. Una técnica
simplista sugiere escribir los escenarios de los casos de uso y luego crear una clase para
cada sustantivo. Considere el siguiente escenario de un caso de uso:

J
Análisis y diseño orientados a objetos 641

El cliente elige retirar efectivo de su cuenta de cheques. Hay suficiente efectivo en la


cuenta, hay suficiente efectivo y recibos en el cajero automático, y la red está funcionan­
do. El cajero automático pide al cliente que indique una cantidad para el retiro, y el
cliente pide $300, una cantidad válida para retirar en este momento. La máquina entrega
$300 e imprime un recibo, y el cliente toma el dinero y el recibo.
De este escenario, podría obtener las siguientes clases:
• Cliente
• Efectivo
• Cheques
• Cuenta
• Recibos
• CajeroAutomatico
• Red
• Cantidad
• Retiro
• Maquina
• Dinero
Después podría agregar los sinónimos para crear esta lista, y luego crear clases para cada
uno de estos sustantivos:
• Cliente
• Efectivo (dinero, cantidad, retiro)
• Cheques
• Cuenta
• Recibos
• Cajero automático (máquina)
• Red
Ésta no es una mala forma de empezar, hasta donde se puede ver. Podría entonces describir
en un diagrama las relaciones obvias entre algunas de estas clases, como se muestra en la
figura 18.12.
642 Día 18

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

Otras transform aciones


Después de haber transform ado los objetos del dominio, puede empezar a buscar otros
objetos útiles en tiem po de diseño. Un buen lugar para empezar es con las interfaces. Cada
interfaz entre su nuevo sistem a y los sistemas existentes (heredados) debe estar encapsu­
lada en una clase de interfaz. Si va a interactuar con una base de datos de cualquier tipo,
tam bién es un buen candidato para una clase de interfaz.

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.

Cómo conducir una sesión CRC


Lo ideal es que a cada sesión CRC asista un grupo de tres a seis personas; más de seis
son difíciles de manejar. Debe tener un moderador, cuyo trabajo será evitar que la sesión
se salga de su objetivo, así como ayudar a que los participantes comprendan bien lo
que se está tratando. Por lo menos debe estar presente un arquitecto de software en
jefe, de preferencia alguien con experiencia considerable en el análisis y diseño orienta­
dos a objetos. Además, necesitará incluir por lo menos uno o dos “expertos de dominio”
que comprendan los requerimientos del sistema y que puedan dar asesoría especializada
en relación con la forma en que deben funcionar las cosas.
El ingrediente más esencial en una sesión CRC es la ausencia de gerentes. Ésta es una
sesión creativa y libre de preocupaciones que no se debe entorpecer por la necesidad de
impresionar a los jefes. El objetivo aquí es explorar, arriesgarse, separar las responsabili­
dades de las clases y comprender cómo podrían interactuar entre sí.
Análisis y diseño orientados a objetos 645

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.

Enfóquese en las responsabilidades


El objetivo de la sesión CRC es identificar las resp o n sa b ilid a d e s de cada clase. No dé
mucha importancia a los atributos, capture sólo los atributos más esenciales y obvios al ir
avanzando. El trabajo importante es identificar las responsabilidades. Si, al cumplir con una
responsabilidad, la clase debe delegar trabajo a otra clase, debe capturar esa información
bajo la sección de colaboraciones.
Vigile sus listas de responsabilidades a medida que vaya progresando. Si se le acaba el
espacio en la ficha de 4 x 6, tal vez tenga sentido que se pregunte si le está pidiendo dema­
siado a esta clase. Recuerde, cada clase debe ser responsable de un área general de trabajo,
y las diversas responsabilidades enlistadas deben ser cohesivas y coherentes, es decir, deben
trabajar en conjunto para lograr cumplir la responsabilidad general de la clase.
En este punto n o debe enfocarse en las relaciones, ni preocuparse por la interfaz de la clase
o por cuáles métodos serán públicos y cuáles privados. Sólo debe enfocarse en comprender
lo que hace cada clase.

Tarjetas CRC antropom orfas y dirigidas por el caso de uso


La característica clave de las tarjetas CRC es hacerlas a n tro p o m o rfa s , es decir, atribuir
calidades de tipo humano a cada clase. Esto funciona así: cuando ya tengan un conjunto
preliminar de clases, regresen a sus escenarios CRC. Dividan las tarjetas alrededor de la
mesa en forma arbitraria, y caminen juntos por el escenario. Por ejemplo, vayan al siguiente
escenario de actividades bancarias:
El cliente elige retirar efectivo de la cuenta de cheques. Hay suficiente efectivo en la
cuenta, hay suficiente efectivo y recibos en el cajero automático, y la red está funcio­
nando. El cajero automático pide al cliente que indique una cantidad para el retiro, y
el cliente pide $300, una cantidad válida para retirar en este momento. La máquina
entrega $300 e imprime un recibo, y el cliente toma el dinero y el recibo.
646 Día 18

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

Relaciones entre clases


Una vez que las clases estén en el UML, puede empezar a centrar su atención en las
relaciones entre las diversas clases. Las principales relaciones que debe modelar son
las siguientes:
• Generalización
• Asociación
• Agregación
• Composición
Estas son relaciones en el modelo del dominio que se llevan (traducen) a las clases de C++.
La relación de generalización se implementa en C++ mediante la herencia pública. Sin
embargo, desde la perspectiva del diseño, nos enfocamos menos en el mecanismo y más
en la semántica: qué es lo que implica esta relación.
Examinamos la relación de generalización en la fase de análisis, pero ahora centramos
nuestra atención no sólo en los objetos del dominio, sino también en los objetos de nuestro
diseño. Ahora nuestros esfuerzos se concentran en “factorizar” la funcionalidad común de
las clases relacionadas en clases base que puedan encapsular las responsabilidades
compartidas.
Cuando se “factoriza” la funcionalidad común, esa funcionalidad se saca de las clases
especializadas y se sube hacia la clase más general. Por lo tanto, si descubre que tanto
su cuenta de cheques como la de ahorros necesitan métodos para transferir dinero a
otras cuentas y de otras cuentas, puede subir el método Transf erirFondos() hacia la
clase base cuenta. Entre más factorice las clases derivadas, su diseño será más polimorfo.
Una de las capacidades disponibles en C++, que no está disponible en Java, es la heren­
cia m ú ltip le (aunque Java tiene una capacidad similar, aunque limitada, con sus interfaces
m ú ltip le s). La herencia múltiple permite que una clase herede de más de una clase base,
recibiendo los miembros y métodos de dos o más clases.
La experiencia ha demostrado que se debe usar la herencia múltiple con cautela ya que
puede complicar tanto el diseño como la implementación. Muchos problemas que se
solucionaban inicialmente con herencia múltiple ahora se solucionan por medio de la agre­
gación. Dicho esto, la herencia múltiple es una herramienta poderosa, y su diseño tal vez
requiera que una sola clase generalice el comportamiento de dos o más clases distintas.

Herencia múltiple en comparación con la contención


¿Es un objeto la suma de sus partes? ¿Tiene sentido modelar un objeto Auto como una
especialización de Volante, Puerta y Llanta, como se muestra en la figura 18.14?
Análisis y diseño orientados a objetos 649

Figura 18.14
Herencia falsa.

Es importante regresar a los fundamentos: la herencia pública siempre debe modelar la


generalización. La expresión común para la relación generalización-especialización es que
la herencia debe modelar relaciones de tipo e s un. Si quiere modelar la relación de tipo
tien e un (por ejemplo, un auto tiene un volante), debe hacerlo con la agregación, como se
muestra en la figura 18.15.

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.

Discriminadores y tipos de poder


¿Cómo podría diseñar las clases requeridas para reflejar las diversas líneas de modelos
de un fabricante típico de autos? Suponga que se le contrata para diseñar un sistema para
Acmé Motors, que actualmente fabrica cinco autos; el Plutón (un auto compacto lento con
motor pequeño); el Venus (un sedán de cuatro puertas con un motor de tamaño mediano);
el Marte (un auto deportivo con el motor más grande de la compañía, diseñado para un
rendimiento máximo); el Júpiter (una mini vagoneta con el mismo motor que el auto
deportivo, pero diseñada para cambiar velocidades a menos revoluciones por minuto y
utilizar su poder para mover su mayor peso); y la Tierra (una vagoneta con motor pequeño,
pero de gran potencia).
Podría empezar por crear subtipos del auto que reflejen los diversos modelos, y luego crear
instancias de cada modelo a medida que van saliendo por la línea de ensamblaje, como se
muestra en la figura 18.17.
¿Cómo se diferencian estos modelos? Como vio, se diferencian por el tamaño del motor,
el tipo de chasis y las características de rendimiento. Estas diversas características dis­
criminatorias se pueden mezclar y relacionar para crear varios modelos. Podemos modelar
esto en UML con el estereotipo cliscrim in a d o r , como se muestra en la figura 18.18.
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 651

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

C la s s Auto : p u b lic Vehículo


{
p u b lic :
A u to ( ) ;
- A u t o () ;
// o tro s métodos p ú b lic o s suprimidos
p rívate :
C a ra c te ris tic a s R e n d im ie n to * apRendimiento;
};
Com o observación final, los tipos de poder le permiten crear nuevos tipos (no sólo instan­
cias) en tiem po de ejecución. Debido a que cada tipo lógico se diferencia sólo por los
atributos del tipo de poder asociado, estos atributos pueden ser parámetros para el cons­
tructor del tipo de poder. Esto significa que usted puede, en tiempo de ejecución, crear
nuevos tipos de autos al instante. Es decir, al pasar diferentes tamaños de motor y revolu­
ciones para cam bio de velocidad al tipo de poder, puede crear efectivam ente nuevas
características de rendim iento. Al asignar esas características a varios autos, puede
engrandecer im presionantem ente el conjunto de tipos de autos en tiem po de ejecución.

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 |

Figura 18.23 Círculo sólido = inicio


Estado de la cuenta
del cliente. Inicio Transición de estado

T
Sin inicio de sesión
Estado

Protección

>r [Identificador de cuenta válido]


Obtener contraseña

>r

^ Sesión iniciada

Círculo doble = fin


T

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"

El diagrama de la figura 18.25 proporciona la misma información que la figura 18.24,


pero es mucho más limpio y fácil de leer. Desde el momento en que usted inicia la sesión
hasta que el sistema termina de iniciarla, puede cancelar el proceso. Si cancela, regresa al
estado “Sin inicio de sesión”.
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 657 |

No se apresure a llegar al código


Uno de los mayores problem as en las organizaciones de desarrollo de software es su prisa
por em pezar a codificar. Siem pre hay presiones de tiempo, y las porciones de codifica-
ción/prueba del proyecto tienden a ser la mayor parte. Y por lo general se asignan varias
personas a la tarea.

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

programar en C++ después de analizar exhaustivamente los lenguajes alternativos


de programación orientada a objetos; se van hacia donde está la acción, pero ahora
la acción está en C++. Hay buenos motivos para esto; C++ tiene mucho que ofrecer,
pero este libro existe debido a que C++ es el lenguaje de desarrollo preferido en
muchas empresas, y Linux es un área que está creciendo mucho.

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

Hoy aprenderá lo siguiente:

• Q ué son las plantillas y cóm o utilizarlas


• C óm o crear plantillas de clases
• C óm o crear plantillas de funciones
• Q ué es la B iblioteca E stándar de Plantillas y cómo utilizarla

Qué son las plantillas


Al final de la sem ana 2 vio cóm o crear un objeto llamado L i s t a P i e z a s y
cóm o utilizarlo para crear un C a t a lo g o P ie z a s . Si se quiere basar en el objeto
L i s t a P i e z a s para crear una lista de gatos, hay un problema: L i s t a P i e z a s sólo
conoce piezas.
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 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.

Cómo crear una instancia a partir de una plantilla


La in s ta n c ia c ió n es el acto de crear un tipo específico a partir de una plantilla. Las clases
individuales se llaman in sta n c ia s de la plantilla.
Las p la n tilla s p a r a m e tr iz a d o s le proporcionan la capacidad de crear una clase general y
pasar tipos como parámetros a esa clase para crear instancias específicas.

D efinición de una plantilla


Para declarar un objeto parametrizado Arreglo (una plantilla para un arreglo) se escribe
1: témplate <class T> // declarar la plantilla y el parámetro
2: class Arreglo / / l a clase que se va a parametrizar
3: {
P la n tilla s 663

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;

El objeto u n A r r e g l o I n t es del tipo arreglo de enteros; el objeto unArregloGato es del tipo


arreglo de gatos. A hora puede utilizar el tipo A r r e g lo < in t > en cualquier parte en que
norm alm ente utilizaría un tipo (como en el valor de retorno de una función, com o un
parám etro para una función, etcétera). El listado 19.1 muestra la declaración completa de
esta plantilla A r r e g l o simplificada.

¡El listado 19.1 no es un programa completo y no compilará!

L is t a d o 1 9 . 1 U n a p la n tilla p a ra u na c la s e lla m a d a A rre g lo

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.

Cómo crear una instancia a partir de una plantilla


La in s ta n c ia c ió n es el acto de crear un tipo específico a partir de una plantilla. Las clases
individuales se llaman in sta n c ia s de la plantilla.
Las p la n tilla s p a r a m e tr iz a d a s le proporcionan la capacidad de crear una clase general y
pasar tipos como parámetros a esa clase para crear instancias específicas.

D e fin ició n de una plantilla


Para declarar un objeto parametrizado Arreglo (una plantilla para un arreglo) se escribe
1: témplate <class T> // declarar la plantilla y el parámetro
2: class Arreglo //la clase que se va a parametrizar
3: {
P la n t illa s 663

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;

El objeto u n A r r e g l o I n t es del tipo arreglo de enteros; el objeto unArregloGato es del tipo


arreglo de gatos. A hora puede utilizar el tipo A r r e g l o < i n t > en cualquier parte en que
norm alm ente u tilizaría un tipo (com o en el valor de retorno de una función, com o un
parám etro para una función, etcétera). El listado 19.1 muestra la declaración com pleta de
esta plantilla A r r e g l o simplificada.

¡El listado 19.1 no es un programa completo y no compilará!

L is t a d o 19.1 U n a p la n tilla p a ra u n a c la s e lla m a d a A rre g lo

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

14: Arreglo(int suTamanio = TamanioPredet);


15: Arreglo(const Arreglo & rhs);
16: -Arreglo()
17: { delete [] apTipo; }
18: // operadores
19: Arreglo& operator=(const Arreglo &);
20: T & operatori](int desplazamiento)
21 : { return apTipo[ desplazamiento ]; }
22: // métodos de acceso
23: int obtenerTamanio()
24: { return suTamanio; >
25: private:
26: T * apTipo;
27: int suTamanio;
28: };

S a l id a No hay salida. Este programa está incompleto.


La definición de la plantilla empieza en la línea 8 con la palabra reservada
A nálisis
témplate seguida del parámetro. En este caso, el parámetro se identifica como
un tipo mediante la palabra reservada cla ss, y se utiliza el identificador T para representar
el tipo parametrizado.
Desde la línea 10 hasta el final de la plantilla en la línea 28, el resto de la declaración es
como cualquier otra declaración de clase. La única diferencia es que en lugar del tipo del
objeto se utiliza el identificador T. Por ejemplo, se esperaría que operator[ ] regresara una
referencia a un objeto del arreglo, y de hecho se declara para regresar una referencia a un T.
Cuando se declara una instancia de un arreglo de enteros, el ope rato r= que se proporciona
para ese arreglo regresará una referencia a un entero. Cuando se declare una instancia de un
arreglo de tipo Animal, el operator= proporcionado para el arreglo Animal regresará
una referencia a un Animal.

Uso del nombre


Dentro de la declaración de la clase, se puede utilizar la palabra Arreglo sin necesidad
de más identificación. En cualquier otra parte del programa se haría referencia a esta clase
como Arreglo<T>. Por ejemplo, si no escribe el constructor dentro de la declaración de
la clase, debe escribir lo siguiente:
témplate < class T >
Arreglo< T >::Arreglo(int tamanio):
suTamanio = tamanio
{
apTipo = new T[ tamanio ];
for (int i = 0; i < tamanio; i++)
apTipo[ i ] = 0;
>
P la n t illa s 665

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.

Los compiladores GNU 2.7.2 y posteriores soportan el uso de plantillas. Sin


embargo, algunos compiladores antiguos no soportan las plantillas. En la
actualidad, las plantillas forman parte del nuevo estándar ANSI de C++. Los
principales fabricantes de compiladores soportan plantillas en sus versiones
actuales. Si tiene un compilador muy antiguo, no podrá compilar y ejecutar
los ejercicios que vienen en esta lección. De todas formas sería bueno que
leyera toda la lección, y que viera este material cuando actualice su compi­
lador. Recuerde que el CD-ROM tiene la versión 2.9.5, la cual sí soporta el
uso de plantillas.

Entrada g

1: // L is t a d o 19.2: Implementación de la p l a n t il l a arreglo


2:
3: #include<iostream .h>
4:
5: const i n t TamanioPredet = 10;
6:
7:
8: // d e c la ra r una clase Animal simple para poder
9: // cre a r un a rre g lo de animales
10: c l a s s Animal
11: {
12: p u b lic :
13: A n im a l(in t );
14: A n im al();
15: -A n im a l() {}
16: in t ObtenerPeso() const
17: { return suPeso; }
18: void Desplegar() const
19: { cout « suPeso; }
20: p riv a te :
continúa
666 Día 19

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

71 Arre g lo < T >: :A r r e g l o (const Arreglo & rhs)


72 {
73 suTamanio = rhs.ObtenerTamanio();
74 apTipo = new T[ suTamanio ];
75
76 fo r ( in t i = 0; i < suTamanio; i++)
77 apTipof i ] = rhs( i ];
78 }
79
80 // operator=
81 template < c l a s s T >
82 Arre g lo < T > & Arreglo< T > : :operator=(const Arreglo
83 {
84 i f ( t h i s == &rhs)
85 return * t h i s ;
86 delete [] apTipo;
87 suTamanio = rhs.ObtenerTamanio();
88 apTipo = new T[ suTamanio ];
89 f o r ( i n t i = 0; i < suTamanio; i++)
90 apTipo[ i ] = rhs[ i ];
91 return * t h i s ;
92 }
93
94 // programa controlador
95 in t main()
96 {
97 // un a rre g lo de enteros
98 A rre g lo < in t > elArreglo;
99 // un a rre g lo de Animales
100: A rre g lo < Animal > elZoologico;
101 : Animal * apAnimal;
102: 19
103: // l l e n a r l o s arreglos
104: f o r ( in t i = 0; i < elArreglo.ObtenerTamanio()
105: {
106: e lA rre g lo [ i ] = i * 2;
107: apAnimal = new Animal(i * 3);
108: elZ o olog ico [ i ] = ‘ apAnimal;
109: delete apAnimal;
110: }
111 : // im primir el contenido de los arreglos
112: f o r (in t j = 0; j < elArreglo.ObtenerTamanio()
113: {
114: cout « "e lA rre g lo !" « j « " ] : \ t " ;
115: cout « elArreglo! j ] « " \ t \ t " ;
116: cout << "e lZ o o lo gico !" « j « " ] : \ t " ;
117: elZoologico! j ] . Desplegar!);
118: cout « endl;
119: }
120: return 0;
121 : }
668 Día 19

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!

porque no hay una clase Arreglo, sólo la plantilla y las instancias.


P la n t illa s 669

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

Aquí, la función M i F u n c i o n P l a n t i l l a () se declara como una función de plantilla mediante


la declaración de la línea superior. Observe que al igual que otras funciones, las funciones
de plantilla pueden tener cualquier nombre.
Las funciones de plantilla tam bién pueden tomar instancias de la plantilla adem ás de la
form a param etrizada. Lo siguiente es un ejemplo:
témplate < c l a s s T >
void M iO traF u n c ion (A rre g lo< T > &, Arreglo< int > &); // 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.

Plantillas y funciones amigas


Las clases de plantillas pueden declarar tres tipos de funciones amigas:
• C lases y funciones am igas que no sean de plantilla
• C lases y funciones am igas de plantilla general
• C lases y funciones am igas de plantilla de tipo específico

Clases y fundones amigas que no son de plantilla


Es posible declarar cualquier clase o función como amiga para la clase de plantilla. Cada
instancia de la clase tratará a la am iga en forma apropiada, como si la declaración de
am istad se hubiera hecho en esa instancia particular. El listado 19.3 agrega una función
am iga trivial, llam ada I n t r u s o (), a la definición de la plantilla de la clase A r r e g lo , y el
program a controlador invoca a I n t r u s o (). Como es una amiga, In t r u s o () puede tener
acceso a los datos privados del A rre g lo . Como ésta no es una función de plantilla, sólo
se puede llam ar en clases A r r e g l o de tipo int.

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

1: // L is t a d o 19.3 - Funciones amigas de tipo especifico en p l a n t i l l a s


2:
3: # in clu d e <iostream.h>
4:
5: const in t TamanioPredet = 10;
6:
7:
8: // d e c la r a r una clase Animal simple para poder
continúa
670 Día 19

L is t a d o 19.3 c o n t in u a c ió n

9: crear un arreglo de animales


10: class Animal
11: {
12: public:
13: Animal(int);
14: Animal();
15: -Animal() {}
16: int ObtenerPeso() const
17: { return suPeso; }
18: void Desplegar() const
19: { cout « suPeso; }
20: private:
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: template < 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: // función amiga
53: friend void Intruso(Arreglo< int >);
54: private:
55: T * apTipo;
56: int suTamanio;
57:
58:
59: // función amiga. No es una plantilla, isólo se puede usar
P la n tilla s 671

60: // con a r re g lo s de enteros! Se inmiscuye en lo s datos privados,


61 : void In t ru s o (A rre g lo < in t > elArreglo)
62: {
63: cout << " \ n * * * In truso * * * \ n " ;
64: f o r ( in t i = 0; i < elArreglo.suTamanio; i++)
65: cout << " i : " << elArreglo.apTipo[ i ] « endl;
66: cout « " \n ";
67: }
68:
69: // l a s implementaciones están a continuación...
70: // implementar e l Constructor
71 : témplate < c la s s T >
72: A rre g lo < T >: ¡A r r e g lo (in t tamanio):
73: suTamanio(tamanio)
74: {
75: apTipo = new T[ tamanio ];
76:
77: f o r ( in t i = 0; i < tamanio; i++)
78: a p T ip o [i] = 0;
79: }
80:
81 : // c o n s tru c to r de copia
82: témplate < c l a s s T >
83: A rre g lo < T > : : Arreglo(const Arreglo & rhs)
84: {
85: suTamanio = rhs.ObtenerTamanio();
86: apTipo = new T[ suTamanio ];
87:
88: f o r ( in t i = 0; i < suTamanio; i++)
89: apTipo[ i ] = rhs[ i ];
90: }
91 : 19
92: // operator=
93: témplate < c l a s s T >
94: A rre g lo < T > & Arreglo< T >: :operator=(const Arreglo & rhs)
95: {
96: if ( t h i s == &rhs)
97: return * t h is ;
98: delete [] apTipo;
99: suTamanio = rhs.ObtenerTamanio();
100 apTipo = new T[ suTamanio ];
101 f o r (in t i = 0; i < suTamanio; i++)
102 apTipo[ i ] = r h s [ i ];
103 return * t h i s ;
104
105
106 // programa controlador
107 in t main()
108 {
109 II un a rre g lo de enteros
110 Arreglo< in t > elArreglo;
111 // un a rre glo de animales
continúa
672 Día 19

L istado 19.3 continuación

112 Arreglo< Animal > elZoologico;


113 Animal *apAnimal;
114
115 // llenar los arreglos
116 for (int i = 0; i < elArreglo.ObtenerTamanio(); i++)
117 {
118 elArreglo[ i ] = i * 2;
119 apAnimal = new Animal(i * 3);
120 elZoologico[ i ] = ‘apAnimal;
121 }
122 for (int j = 0; j < elArreglo.ObtenerTamanio(); j++)
123 {
124 cout « “elZoologico[" « j « "]:\t";
125 elZoologico[ j ].Desplegar();
126 cout « endl;
127 }
128 cout « “Usar ahora la función amiga para
129 cout « "encontrar los miembros de Arreglo<int>";
130 Intruso(elArreglo);
131 cout « “\n\nListo.\n";
132 return 0;
133

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

La declaración de la plantilla Arreglo se ha extendido para incluir a la función


amiga I n t r u s o (). Esto declara que cada instancia de un arreglo de enteros
considerará a I n t r u s o ! ) como una función amiga; por lo tanto, I n t r u s o ! ) tendrá acceso
a los datos y funciones miembro privados de la instancia del arreglo.
En la línea 64 I n t r u s o ! ) tiene acceso directo a suTamanio, y en la línea 65 tiene acceso
directo a apTipo. Este uso trivial de estos miembros fue innecesario, ya que la clase
Arreglo proporciona métodos públicos de acceso para estos datos, pero sirve para demos­
trar cómo se pueden declarar las funciones amigas con las plantillas.

Clases y fu n cio n e s am igas de plantilla general


Sería útil agregar un operador de despliegue para la clase Arreglo. Una forma sería declarar
un operador de despliegue para cada posible tipo de Arreglo, pero esto debilitaría el
propósito en sí de hacer que Arreglo sea una plantilla.
Lo que se necesita es un operador de inserción que funcione para cualquier posible tipo
de Arreglo.
ostreamS operator<< (ostream &, Arreglo< T > &);
Para hacer que esto funcione, necesita declarar a ope rato r<< como una función de plantilla.
témplate < class T > ostream & operator« (ostream &, Arreglo< T > &)

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

1: // Listado 19.4: uso del operador ostream


2:
3: #include <iostream.h>
4:
5: const int TamanioPredet = 10;
6:
7:
8: cla ss Animal
9:
10: public:
11: Animal(int);
12: Animal();
13: -Animal!) {}
14: int ObtenerPeso() const
15: { return suPeso; }
16: void Desplegar() const
17: { cout « suPeso; }
18: private:
continúa
674 Día 19

Listado 19.4 continuación

19: int suPeso;


20:
21 :
22: Animal::Animal(int peso):
23: suPeso(peso)
24: {}
25:
26: Animal::Animal():
27: suPeso(0)
28: {}
29:
30: // declarar la plantilla y el parámetro
31 : témplate < class T >
32: I I la clase que se va a parametrizar
33: class Arreglo
34: {
35: public:
36: // constructores
37: Arreglo(int suTamanio = TamanioPredet);
38: Arreglo(const Arreglo & rhs);
39: -Arreglo()
40: { delete [] apTipo; }
41 : // operadores
42: Arreglo & operator=(const Arreglo &);
43: T & operator[](int desplazamiento)
44: { return apTipo[ desplazamiento ]; }
45: const T & operator[](int desplazamiento) const
46: { return apTipo[ desplazamiento ]; }
47: // métodos de acceso
48: int ObtenerTamanio() const
49: { return suTamanio; }
50: friend ostream & operator« <> (ostream &, Arreglo< T > &);
51 : private:
52: T * apTipo;
53: int suTamanio;
54: };
55:
56: témplate < class T >
57: ostream & operator« (ostream & salida, Arreglo< T > & elArreglo)
58: {
59: for (int i = 0; i < elArreglo.ObtenerTamanio(); i++)
60: salida « "[" << i « "] " << elArreglo[ i ] << endl;
^»return salida;
61 :
62: }
63:
64: I I las implementaciones están a continuación...
65: // implementar el Constructor
66: témplate < class T >
67: Arreglo< T >::Arreglo(int tamanio):
68: suTamanio(tamanio)
69: {
70: apTipo = new T[ tamanio ];
P la n t illa s 675

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

Escriba un desplazamiento (0-9) y un valor. (-1 para detener) 1 10


Salida Escriba un desplazamiento (0-9) y un valor. (-1 para detener) 2 20
Escriba un desplazamiento (0-9) y un valor. (-1 para detener) 3 30
Escriba un desplazamiento (0-9) y un valor. (-1 para detener) 4 40
Escriba un desplazamiento (0-9) y un valor. (-1 para detener) 5 50
Escriba un desplazamiento (0-9) y un valor. (-1 para detener) 6 60
Escriba un desplazamiento (0-9) y un valor. (-1 para detener) 7 70
Escriba un desplazamiento (0-9) y un valor. (-1 para detener) 8 80
Escriba un desplazamiento (0-9) y un valor. (-1 para detener) 9 90
Escriba un desplazamiento (0-9) y un valor. (-1 para detener) 10 10
***Utilice valores entre 0 y 9. *★4ir
Escriba un desplazamiento (0-9) y un valor. (-1 para detener) 1 -1

He aquí el arreglo completo:


[0] 0
[1] 10
[2] 20
[3] 30
[4] 40
[5] 50
[6] 60
[7] 70
[8] 80
[9] 90

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

En la línea 50 se declara la plantilla de la función operator«() como amiga de


A n á l is is
la clase de plantilla Arreglo. Como operator«() se implementa como función
de plantilla, cada instancia de este tipo de arreglo parametrizado tendrá automáticamente
una función operator«(). La implementación para este operador empieza en la línea 56.
Cada miembro de un arreglo se llama uno por uno. Esto funciona sólo si se define una
función operator«() para cada tipo de objeto guardado en el arreglo.

Muchos compiladores no requieren de los símbolos o mostrados en la línea


Nota 50 del listado 19.4; g++ sí. Si tiene la línea sin ellos (como se muestra en la si­
guiente versión de la línea 50, aceptada por la mayoría de los compiladores):
P la n t illa s 677

50: f rie n d ostream & o p e ra to r« (ostream &, Arreglo< T > &) ;


La versión 2.9.5 emitirá advertencias acerca de este código:
ls t 1 9 - 0 4 . c x x : 5 0 : warning: friend declaration 'c l a s s ostream &
■“»operator « ( c l a s s ostream &, cla ss Arreglo<T> &) '
1st 19 -0 4 .c x x :50: warning: declares a non-template function
ls t1 9 - 0 4 . c x x : 5 0 : warning: ( i f t h i s i s not what you intended,
«•make sure
lst1 9 -0 4 .cxx :5 0 : warning: the function template has already been
«•declared,
l s t 1 9 - 0 4 .c x x :50: warning: and add <> a fter the function name
«•here)
/tmp/ccm8T7bn.o: In function 'm ain':
/tmp/ccm8T7bn.o ( .text+0x101): undefined reference to
' o p e r a t o r « (o s t r e a m &, Arreglo<int> &)'
c o lle c t 2 : Id returned 1 e x it status

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

Uso de elementos de plantilla


Puede tratar a los elem entos de plantilla de la misma forma que a cualquier otro tipo. P uede
pasarlos com o parám etros, ya sea por referencia o por valor, y puede regresarlos com o los
v alo res d e re to rn o de las fu n cio n es, tam bién por valor o por referencia. El listad o 19.5
m u estra có m o p a sa r o b jeto s de plantilla.
19
Entrada L is t a d o 1 9 .5 Paso de un objeto de plantilla hacia funciones y desde ellas
1: // L is t a d o 19.5: Paso de objetos de p la n t illa
2:
3: # in clu d e <iostream.h>
4:
5: const i n t TamanioPredet = 10;
6:
7:
8: // Una c la s e t r i v i a l para agregar a los arreglos
9: c l a s s Animal
10: {
11 : p u b lic :
12 : // c o n stru c to re s
13: A n im a l( i n t );
14: A n im a l();
15: -A n im a l();
16 : // métodos de acceso
17: in t ObtenerPeso() const
18: { return suPeso; }
19: void A s ig n a rP e s o (in t elPeso) .,
continua
678 Día 19

Listado 19.5 continuación

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: //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 : Arreglo & operator=(const Arreglo &);
62: T & operator[](int desplazamiento)
63: { return apTipo[ desplazamiento ]; >
64: const T & operator!](int desplazamiento) const
65: { return apTipo[ desplazamiento ]; }
66: int ObtenerTamanio() const
67: { return suTamanio; }
68: // función amiga
69: friend ostream & operator« <> (ostream &, const Arreglo< T > &);
70: private:
71 : T *apTipo;
Plantillas 679

72: int suTamanio;


73: };
74:
75: témplate < class T >
76: ostream & operator« (ostream & salida, const Arreglo« T > & elArreglo)
77: {
78: for (int i = 0; i < elArreglo.ObtenerTamanio(); i++)
79: salida « « i « °] " « elArreglo[ i ] « endl;
80: return salida;
81: }
82:
83: // las implementaciones están a continuación...
84: // implementar el Constructor
85: témplate < class T >
86: Arreglo« T >::Arreglo(int tamanio):
87: suTamanio(tamanio)
88: {
89: apTipo = new T[ tamanio ];
90:
91: for (int i = 0; i < tamanio; i++)
92: apTipo[ i ] = 0 ;
93: >
94:
95: // constructor de copia
96: témplate < class T >
97: Arreglo« T >::Arreglo(const Arreglo & rhs)
98: {
99: suTamanio = rhs.ObtenerTamanio();
100: apTipo = new T[ suTamanio ];
101:
102: for (int i = 0; i < suTamanio; i++)
103: apTipo[ i ] = rhs[ i ];
104: >
105:
106: void FuncionLlenarlnt (Arreglo« int > & elArreglo);
107:
108: void FuncionLlenarAnimal(Arreglo« Animal > & elArreglo);
109:
110: int main()
111: {
112: Arreglo« int > arreglolnt;
113: Arreglo« Animal > arregloAnimal;
114:
115: FuncionLlenarlnt(arreglolnt);
116: FuncionLlenarAnimal (arregloAnimal);
117: cout « "arreglolnt...\n" « arreglolnt;
118: cout « "\narregloAnimal...\n" « arregloAnimal « endl;
119: return 0;
120: }
121:
122: void FuncionLlenarlnt (Arreglo« int > & elArreglo)
123: {
124: bool Detener = false;
continúa
680 Día 19

Listado 19.5 continuación

125 int desplazamiento, valor;


126
127 while (IDetener)
128 {
129 cout « “Escriba un desplazamiento (0-9)
130 cout « "y un valor. (-1 para detener): “ ;
131 cin » desplazamiento » valor;
132 if (desplazamiento < 0)
133 break;
134 if (desplazamiento > 9)
135 {
136 cout « “***Utilice valores entre 0 y 9.***\n”;
137 continué;
138 }
139 elArreglo[ desplazamiento ] = valor;
140 >
141
142
143
144 void FuncionLlenarAnimal(Arreglo< Animal > & elArreglo)
145 {
146 Animal * apAnimal;
147
148 for (int i = 0; i < elArreglo.ObtenerTamanio(); i++)
149 {
150 apAnimal = new Animal;
151 apAnimal->AsignarPeso(i * 100);
152 elArreglo[i] = *apAnimal;
153 // se colocó una copia en el arreglo
154 delete apAnimal;
155 }
156

Escriba un desplazamiento (0-9) y un valor. (-1 para detener): 1 10


S alida Escriba un desplazamiento (0-9) y un valor. para detener): 2 20
(-1
Escriba un desplazamiento (0-9) y un valor. (-1 para detener): 3 30
Escriba un desplazamiento (0-9) y un valor. (-1 para detener): 4 40
Escriba un desplazamiento (0-9) y un valor. (-1 para detener): 5 50
Escriba un desplazamiento (0-9) y un valor. (-1 para detener): 6 60
Escriba un desplazamiento (0-9) y un valor. (-1 para detener): 7 70
Escriba un desplazamiento (0-9) y un valor. (-1 para detener): 8 80
Escriba un desplazamiento (0-9) y un valor. (-1 para detener): 9 90
Escriba un desplazamiento (0-9) y un valor. (-1 para detener): 10I 10
***Utilice valores entre 0 y 9. ***r
Escriba un desplazamiento (0-9) y un valor. (-1 para detener): -1 •1
arreglolnt
[0] 0
[1] 10
P la n tilla s 681

[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

Al escribir unAnimal = (Animal) 0;, está llamando al operator= predeterminado para


Animal. Esto ocasiona que se cree un objeto Animal temporal usando el constructor, el cual
toma un entero (cero). Ese objeto temporal se utiliza como el lado derecho de operator=
y luego se destruye.
Esto es una desafortunada pérdida de tiempo, pues el objeto Animal ya estaba inicializado
de manera apropiada. Sin embargo, no puede quitar esta línea porque los enteros no se
inicializan automáticamente con un valor de 0. La solución es enseñar a la plantilla a no
utilizar este constructor para objetos de tipo Animal, sino utilizar un constructor especial
para Animal.
Puede proporcionar una implementación explícita para la clase Animal, como se indica
en el listado 19.6.

En t r a d a L is t a d o 1 9 .6 Especialización de las im plem entaciones de plantilla

1: // Listado 19.6: Especialización de implementaciones de plantilla


2:
3: #include <iostream.h>
4:
5: const int TamanioPredet = 3;
6:
7:
8: // Una clase trivial para agregar a los arreglos
9: class Animal
10: {
11: public:
12: // constructores
13: Animal(int);
14: Animal();
15: -Animal();
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)
Plantillas 683 |

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

L istado 19.5 continuación

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

144 i n t desplazamiento, valor;


145
146 v/hile (¡Detener)
147 {
148 cout « "E s c rib a un desplazamiento (0-9) y un valor. ";
149 cout « "(-1 para detener): " ;
150 c in » desplazamiento » valor;
151 i f (desplazamiento < 0)
152 break;
153 i f (desplazamiento > 9)
154 {
155 cout « " * * * U t i l i c e valores entre 0 y 9 . * * * \ n " ;
156 co n tin u é ;
157 }
158 e lA rre g lo [ desplazamiento ] = valor;
159 }
160 }
161
162
163 vo id Funcion!_lenarAnimal(Arreglo< Animal > & elArreglo)
164 {
165 Animal * apAnimal;
166
167 for ( in t i = 0; i < elArreglo.ObtenerTamanio(); i++)
168 {
169 apAnimal = new Anim al(i * 10);
170 e lA rre g lo [ i ] = *apAnimal;
171 delete apAnimal;
172 >
173
i

Se han agregado números de línea a la salida para facilitar el análisis. Los 19


núm eros de línea no aparecerán en la salida que usted obtenga.

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.

M iem b ro s estáticos y plantillas


Una plantilla puede declarar datos miembro estáticos. Cada instanciación de la plantilla
tiene entonces su propio conjunto de datos estáticos, uno por tipo de clase. Es decir, si
agrega un miembro estático a la clase A r r e g l o (por ejemplo, un contador que lleve la r
cuenta de cuántos arreglos se han creado), tendrá uno de esos miembros por cada tipo:
uno para los arreglos de objetos de tipo Animal y otro para todos los arreglos de enteros
19
El listado 19.7 agrega un miembro estático y una función estática a la clase A rre g lo .

Entrada L is t a d o 1 9 . 7 Uso de fundones y datos miembro estáticos con plantillas

1: // L is t a d o 19.7: Uso de funciones y datos miembro estáticos


2:
3: # in clu d e <iostream.h>
4:
5: const in t TamanioPredet = 3;
6:
7:
8: // Una c la s e t r i v i a l para agregar a lo s arreglos
9: c l a s s Animal
10: {
11 : p u b lic :
12 : // constru ctores
13: A n im a l(in t);
14: Anim al();
15: -An im a l();
continúa
| 688 Día 19

Listado 19.7 continuación

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

67: { return apTipof desplazamiento ]; }


68: // métodos de acceso
69: in t ObtenerTamanio() const
70: { return suTamanio; }
71: s t a t i c in t ObtenerNumeroArreglos()
72: { return suNumeroArreglos; }
73: // función amiga
74: f ri e n d ostream & o p e r a t o r « <> (ostream &, const Arreglo< T > &);
75: p riv a te :
76: T *apTipo;
77: in t suTamanio;
78: s t a t i c in t suNumeroArreglos;
79: };
80:
81 : témplate < c l a s s T >
82: i n t A rre g lo < T > : : suNumeroArreglos = 0;
83:
84: témplate < c la s s T >
85: A rre g lo < T >: :A rre g lo (in t tamanio = TamanioPredet):
86: suTamanio(tamanio)
87: {
88: apTipo = new T[ tamanio ];
89:
90: f o r ( in t i = 0 ; i < tamanio; i++)
91: apTipo[ i ] = (T)0;
92: suNumeroArreglos++;
93: }
94:
95: témplate < c la s s T >
96: A rre g lo < T> & Arreglo< T >: :operator=(const Arreglo & rhs)
97: {
98:
99:
i f (t h i s == &rhs)
return * t h i s ; 19
100: delete [] apTipo;
101 : suTamanio = rhs.ObtenerTamanio();
102: apTipo = new T[ suTamanio ];
103: f o r ( in t i = 0; i < suTamanio; i++)
104: apTipo[ i ] = r h s [ i ];
105: }
106:
107: témplate < c la s s T >
108: A rre g lo < T > : : Arreglo(const Arreglo & rhs)
109: {
110 : suTamanio = rhs .ObtenerTamanio();
111: apTipo = new T[ suTamanio ];
112:
113: f o r ( in t i = 0; i < suTamanio; i++)
114: apTipo[ i ] = rhs[ i ];
115: suNumeroArreglos++;
116: }
117:
118:

continúa
690 Día 19

Listado 19.7 continuación

119: témplate < class T >


120: ostream & operator« (ostream & salida, const Arreglo< T > & elArreglo)
121 : {
122: for (int i = 0; i < elArreglo.ObtenerTamanio(); i++)
123: salida « "[" « i « "] " « elArreglo[ i ] « endl;
124: return salida;
125: }
126:
127: int main()
128: {
129: cout « Arreglo« int >::ObtenerNumeroArreglos();
130: cout « " arreglos de enteros\n";
131: cout « Arreglo« Animal >::ObtenerNumeroArreglos();
132: cout « ■ arreglos de animales\n\n“;
133:
134: Arreglo« int > arreglolnt;
135: Arreglo« Animal > arregloAnimal;
136:
137: cout « arreglolnt.ObtenerNumeroArreglos() ;
138: cout « " arreglos de enteros\n";
139: cout « arregloAnimal.ObtenerNumeroArreglos();
140: cout « " arreglos de animales\n\n";
141 :
142: Arreglo« int > *apArregloInt = new Arreglo« int >;
143:
144: cout « Arreglo« int >::ObtenerNumeroArreglos();
145: cout « " arreglos de enteros\n";
146: cout « Arreglo« Animal >::ObtenerNumeroArreglos();
147: cout « " arreglos de animales\n\n";
148:
149: delete apArregloInt;
150:
151: cout « Arreglo« int >::ObtenerNumeroArreglos();
152: cout « " arreglos de enteros\n";
153: cout « Arreglo« Animal >::ObtenerNumeroArreglos();
154: cout « 11 arreglos de animales\n\n" ;
155: return 0;
156: }

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

La declaración de la clase Animal se ha omitido para ahorrar espacio. La clase


A rre g lo ha agregado la variable estática suNumeroArreglos en la línea 78, y como
este dato es privado, se agregó el método de acceso público estático ObtenerNumero -
A r r e g l o s () en la línea 71.

La inicialización de los datos estáticos se logra con una identificación completa de la


plantilla, como se muestra en las líneas 81 y 82. Cada uno de los constructores de A r r e g lo ,
así como el destructor, se modifican para llevar la cuenta de cuántos arreglos existen en
un momento dado.
El acceso a los miembros estáticos se logra de la misma forma que con los miembros
estáticos de cualquier clase: puede hacerlo con un objeto existente, como se muestra en
las líneas 137 y 139, o mediante el uso de la especificación completa de la clase, como se
muestra en las líneas 129 y 131. Observe que debe utilizar un tipo de arreglo específico al
acceder a los datos estáticos. Existe una variable para cada tipo.

La versión 2.7.2 no puede compilar este código— aparecen los siguientes


mensajes:
lst1 9 -0 7 .c x x :7 8 : sorry, not implemented: s ta tic data member
templates
ls t1 9 -0 7 .c x x :7 8 : end of f i l e read inside d efin ition

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.

La Biblioteca Estándar de Plantillas


Una nueva característica de C++ es la adopción de la STL (Biblioteca Estándar de Planti­
llas). Los principales fabricantes de compiladores ofrecen ahora la STL como parte de sus
compiladores. Las versiones más recientes del compilador GNU incluyen la STL. Ésta es
una biblioteca de clases contenedoras basadas en plantillas, incluyendo vectores, listas,
colas y pilas. La STL también incluye una variedad de algoritmos comunes, como el
ordenamiento y la búsqueda.
692 Día 19

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

Un vector es un contenedor optimizado para proporcionar un acceso rápido a sus elementos


mediante un índice. L a clase contenedoravector se define en el archivo de encabezado
<vector> que está en el espacio de nombres std (v ea el día 1 7 , “ E sp acio s de nombres” ,
para obtener más información acerca del uso de espacios de nombres). Un vector puede cre­
cer por sí mismo cuando sea necesario. Suponga que ha creado un vector para contener 10
elementos. D espués de agregarle esos 10 objetos, puede d ecir que el vector está lleno. Si
luego agrega otro objeto al vector, éste incrementa automáticamente su capacidad para poder
acom odar el undécimo objeto. He aquí la form a en que se define la clase vector:
témplate < class T, class A = allocator< T > > class vector
i
I I miembros de la clase
};
E l prim er argum ento(class T) es el tipo de los elem entos del vector. E l segundo argu­
mento (class A) es una clase asignadora. L o s asignadores son los administradores de
Plantillas 693 |

memoria responsables de la asignación y liberación de memoria para los elementos de cada


contenedor. El concepto y la implementación de los asignadores son temas avanzados que
están más allá del alcance de este libro.
De manera predeterminada, los elementos se crean mediante el operador new() y se liberan
mediante el operador delete (). Es decir, el constructor predeterminado de la clase T se
llama para crear un nuevo elemento. Esto proporciona otro argumento que sirve para
definir explícitamente un constructor predeterminado para sus propias clases. Si no define
uno explícitamente, no podrá utilizar el contenedor vector estándar para guardar un conjunto
de instancias de su clase.
Puede definir vectores que guarden enteros y valores de punto flotante de la siguiente
manera:
vector< int > vlnts; // vector que guarda elementos de tipo int
vector< float > vFloats; // vector que guarda elementos de tipo float
Por lo general, debe tener una idea de cuántos elementos contendrá un vector. Por ejemplo,
suponga que en su escuela el número máximo de estudiantes es 50. Para crear un vector
de estudiantes en una clase, el vector debe ser lo suficientemente grande como para contener
50 elementos. La clase vector estándar proporciona un constructor que acepta el número de
elementos como su parámetro. De esta manera, puede definir un vector de 50 estudiantes
como se muestra a continuación:
vector< Estudiante > ClaseMatematicas(50);
El compilador asignará suficiente espacio en memoria para 50 estudiantes; cada elemento
se crea utilizando el constructor predeterminado Estudiante: ¡Estudiante!).
Puede recuperar el número de elementos de un vector usando una función miembro llamada
s i z e (). En este ejemplo, ClaseMatematicas.size() regresará 50.
Otra función miembro, capacity (), le indica exactamente cuántos elementos puede alojar
un vector antes de que necesite incrementar su tamaño. Verá más sobre esto más adelante.
Se dice que un vector está vacío si no hay ningún elemento en él; es decir, el tamaño del
vector es cero. Para facilitar la prueba para ver si un vector está vacío, la clase vector pro­
porciona una función miembro llamada emptyO que se evalúa como true (verdadero) si
el vector está vacío.
Para asignar un objeto Estudiante llamado Harry a ClaseMatematicas, puede utilizar el
operador de subíndice [ ]:
ClaseMatematicas[5] = Harry;
El subíndice empieza en 0. Como pudo haberse dado cuenta, el operador de asignación
sobrecargado de la clase Estudiante se utiliza aquí para asignar a Harry al sexto ele­
mento de ClaseMatematicas. De la misma manera, para encontrar la edad de Harry,
puede tener acceso a su registro escribiendo lo siguiente:
ClaseMatematicas[5].ObtenerEdad();
694 Día 19

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.

E ntrada L istado 19.8 Creación d e un vector y acceso a los elem entos


1: // Listado 19.8: Vectores
2:
3: #include <iostream>
4: ^include <string>
5: #include <vector>
6:
7: using namespace std;
8:
9:
10: class Estudiante
11 : {
12: public:
13: Estudiante();
14: Estudiante(const string & nombre, const int edad);
15: Estudiante(const Estudiante & rhs);
16: -Estudiante();
P la n t illa s 695

17: vo id AsignarNombre(const s tr in g & nombre);


18: s t r i n g ObtenerNombre() const;
19: void AsignarEdad(const int edad);
20: in t ObtenerEdad() const;
21: Estudian te & operator=(const Estudiante & rhs);
22: p riv a t e :
23: s t r i n g suNombre;
24: in t suEdad;
25: };
26:
27: E s t u d i a n t e : :E s t u d ia n t e ():
28: suNombre( “Nuevo Estudiante"),
29: suEdad(16)
30: {}
31 :
32: E s t u d i a n t e : :Estudiante(const s trin g &nombre, const in t edad):
33: suNombre(nombre),
34: suEdad(edad)
35: {}
36:
37: E s t u d i a n t e :: Estudiante (const Estudiante & rhs):
38: suNombre( rhs.ObtenerNombreO),
39: suEdad( r h s .ObtenerEdad())
40: {}
41 :
42: E s t u d i a n t e : :- E s t u d ia n t e ()
43: {}
44:
45: vo id Estudian te: :AsignarNombre (const string &nombre)
46: {
47: suNombre = nombre;
48: }
49:
50: s t r i n g E s t u d i a n t e : :ObtenerNombre() const
51 : {
52: return suNombre;
53: }
54:
55: void Estudian te: :AsignarEdad(const int edad)
56: {
57: suEdad = edad;
58: }
59:
60: in t E s t u d i a n t e : :ObtenerEdad() const
61 : {
62: return suEdad;
63: }
64:
65: Estudiante & E s t u d ia n te ::operator=(const Estudiante & rhs)
66: {
67: suNombre = rhs.ObtenerNombre();
68: suEdad = rhs.ObtenerEdad();
continua
696 Día 19

L is t a d o 19.8 c o n t in u a c ió n

69: return *this;


70: }
71:
72: ostream & operator«(ostream & os, const Estudiante & rhs)
73: {
74: os « rhs.ObtenerNombre() « " tiene
75: os « rhs.ObtenerEdad() << " años de edad";
76: return os;
77: >
78:
79: template< class T >
80: // desplegar propiedades del vector
81: void MostrarVector(const vector< T > & v);
82:
83: typedef vector< Estudiante > ClaseEscuela;
84:
85: int main()
86: {
87: Estudiante Harry;
88: Estudiante Sally("Sally", 15);
89: Estudiante Bill(“Bill", 17);
90: Estudiante Peter(“Peter", 16);
91 :
92: ClaseEscuela ClaseVacia;
93: cout « "ClaseVacia:\n";
94: MostrarVector(ClaseVacia);
95:
96: ClaseEscuela ClaseCreciendo(3);
97: cout « “ClaseCreciendo(3):\n";
98: MostrarVector(ClaseCreciendo);
99:
100 ClaseCreciendo[ 0 ] = Harry;
101 ClaseCreciendo[ 1] = Sally;
102 ClaseCreciendo[ 2 ] = Bill;
103 cout « "ClaseCreciendo(3) después de asignar estudiantes:\n";
104 MostrarVector(ClaseCreciendo);
105
106 ClaseCreciendo.push_back(Peter);
107 cout « "ClaseCreciendo() después de agregar el
ta^4to estudiante:\n'
108: MostrarVector(ClaseCreciendo);
109:
110: ClaseCreciendo[ 0 ].AsignarNombre ("Harry");
111: ClaseCreciendo[ 0 ].AsignarEdad(l8);
112: cout « "ClaseCreciendo() después de Asignaran";
113: MostrarVector(ClaseCreciendo);
114: return 0;
115: }
116:
117: // Desplegar propiedades del vector
P la n tilla s 697

118 template< c la s s T >


119 void MostrarVector(const vector< T > & v)
120 {
121 cout << "tnax_size( ) = " « v.max_size( ) ;
122 cout « " \ t s i z e ( ) = " « v .s iz e () ;
123 cout << " \tc a p a c it y () = “ « v.capacity(
124 cout « " \ t " « (v . empty()? "vacío": "no vacio
125 cout << " \ n " ;
126 f or int i = 0; i < v .s iz e (); ++i)
127 cout « v[ i ] « "\n";
128 cout << endl;
129 }

C la s e V a c ia :
S alida max_size() = 536870911 s iz e () = 0 capacityO = 0 vacío

C lase C n e cie n d o(3 ):

m ax_size() = 53687091 1s iz e () = 3 capacityO = 3 novacío


Nuevo Estudiante tiene 16 años de edad
Nuevo Estudiante tiene 16 años de edad
Nuevo Estudiante tiene 16 años de edad

C laseCreciendo(3) después de asignar estudiantes:


max_size() = 536870911 s iz e () = 3 capacityO = 3 no vacío
Nuevo Estudiante tiene 16 años de edad
S a l l y tie n e 15 años de edad
B i l l tiene 17 años de edad

C la s e C re c ie n d o () después de agregar el 4to estudiante:


max_size() = 536870911 siz e () = 4 capacityO = 6 no vacío
19
Nuevo Estudiante tiene 16 años de edad
S a l l y tie n e 15 años de edad
B i l l tie n e 17 años de edad
Peter tiene 16 años de edad

C laseC reciendo() después de Asignar:


m ax_size() = 536870911 s iz e () = 4 capacityO = 6 no vacío
Harry tie n e 18 años de edad
S a l l y tiene 15 años de edad
B i l l tie n e 17 años de edad
Peter tie n e 16 años de edad

La clase E s t u d ia n t e se define en las líneas 10 a 25. Las implementaciones de sus


A nálisis
funciones miembro se encuentran en las líneas 27 a 70. Es simple y amigable para
el contenedor vector. Por las razones mencionadas anteriormente, definimos un constructor
predeterminado, un constructor de copia y un operador de asignación sobrecargado. Observe
que su variable miembro suNombre se define como una instancia de la clase s t r i n g de C++.
Como puede ver aquí, es mucho más sencillo trabajar con una cadena de C++ que con una
cadena char* estilo C.
698 Día 19

La función de plantilla MostrarVector() se declara en las líneas 79 y 81 y se define en


las líneas 118 a 129. Esta función muestra el uso de algunas de las funciones miembro del
vector: max_size (), size (), capacity () y empty (). Como puede ver en la salida, el
número máximo de objetos Estudiante que un vector puede alojar en g++ es 536,870,911
(y sólo 214,748,364 en Visual C++). Este número puede ser distinto para otros tipos de
elementos. Por ejemplo, un vector de enteros puede tener muchos elementos más. Si está
utilizando otros compiladores, tal vez tenga valores y número máximo de elementos
diferentes.
En las líneas 126 y 127 el programa pasa por cada elemento del vector y despliega su
valor utilizando el operador de inserción << sobrecargado, el cual se define en las líneas
72 a 77.
En las líneas 87 a 90 se crean cuatro estudiantes. En la línea 92 se define un vector vacío,
llamado apropiadamente ClaseVacia, por medio del constructor predeterminado de la
clase vector. Cuando se crea un vector de esta forma, el compilador no le asigna espacio.
Como puede ver en la salida producida por MostrarVector(ClaseVacia), su tamaño y
capacidad son cero.
En la línea 96 se define un vector de tres objetos de tipo Estudiante. Su tamaño y capa­
cidad es de tres, como era de esperarse. Los elementos de ClaseCreciendo se asignan con
los objetos Estudiante en las líneas 100 a 102 por medio del operador de subíndice l J.
En la línea 106 se agrega el cuarto estudiante, Peter, al vector. Esto incrementa el tamaño
del vector a cuatro. Es interesante ver que su capacidad ahora se establece en seis. Esto
significa que el compilador ha asignado suficiente espacio para guardar hasta seis objetos
de tipo Estudiante. Debido a que los vectores se deben asignar a un bloque continuo de
memoria, su expansión requiere de un conjunto de operaciones. Primero se asigna un nuevo
bloque de memoria lo suficientemente grande para los cuatro objetos de tipo Estudiante.
Luego se copian los tres elementos a esta nueva memoria asignada y el cuarto elemento
se agrega después del tercer elemento. Por último, se regresa a la memoria el bloque
original. Cuando se tiene un número de elementos grande en un vector, este proceso de
liberación y reasignación puede ser muy tardado. Por lo tanto, un compilador emplea una
estrategia de optimización para reducir la posibilidad de tener operaciones tan tardadas.
En este ejemplo, si agregamos uno o dos objetos más al vector, no hay necesidad de liberar
y reasignar memoria.
En las líneas 110 y 111 otra vez utilizamos el operador de subíndice [ ] para cambiar las
variables miembro para el primer objeto que se encuentra en ClaseCreciendo.
P la n t illa s 699

El co m p ilad o r G N U versión 2.7.2 no puede compilar este código. Utilice la


versión 2.9.5 para éste y todos los ejemplos que quedan.

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.

La clase contenedora vector tiene otras funciones miembro. La función f r o n t () regresa


una referencia al primer elemento de una lista. La función back() regresa una referencia
al último elemento. La función at () funciona igual que el operador de subíndice [ ]. Es
más segura porque comprueba si el subíndice que recibe se encuentra dentro del rango de
elementos disponibles. Si está fuera del rango, se produce una excepción o u t _ o f _ r a n g e .
(Las excepciones se tratan en el día 20, “Excepciones y manejo de errores”.)
La función i n s e r t () inserta uno o más nodos en una posición determinada de un vector.
Entonces, la función p o p _ b a c k ( ) quita el último elemento de un vector. Por último, la
función r e m o v e ( ) quita uno o más elementos de un vector. 19
El c o n t e n e d o r list
Una lista es un contenedor diseñado para optimizar la inserción y eliminación frecuentes
de elementos.
La clase contenedora l i s t de la STL se define en el archivo de encabezado < list> que
se encuentra en el espacio de nombres std. La clase l i s t se implementa como una lista
con doble enlace, en la que cada nodo tiene enlaces tanto con el nodo anterior como con
el siguiente nodo de la lista.
La clase l i s t tiene todas las funciones miembro que proporciona la clase vector. Como
vio en el repaso de la semana 2, puede desplazarse por una lista siguiendo los enlaces
proporcionados en cada nodo. Por lo general, los enlaces se implementan por medio de
apuntadores. La clase contenedora l i s t estándar utiliza un mecanismo llamado iterador
para este mismo propósito.
700 Día 19

Un iterador es una generalización de un apuntador. Puede desreferenciar un iterador para


recuperar el nodo al que apunta. El listado 19.9 muestra el uso de iteradores para tener
acceso a los nodos de una lista.

En t r a d a L is t a d o 19.9 C ó m o desplazarse por una lista usando un iterador


1: // Listado 19.9: Desplazamiento a través de una lista por medio de
**un iterador
¿;
3: #include <iostream.h>
4: #include <list.h>
O.
6: using namespace std;
7i •

8: typedef list< int > ListaEnteros;
y.
10:
11 : int main()
12: {
13: ListaEnteros listalnt;
14:
15: for (int i = 1; i <= 10; ++i)
16: listalnt.push_back(i * 2);
17: for (ListaEnteros::const iterator ci = listalnt.begin ();
18: ci != listalnt.end(); ++ci)
19: cout « *ci « " ";
20: cout « endl;
21 : return 0;
22: }

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.

C on ten ed o res asociativos


Aunque los contenedores de secuencia están diseñados para un acceso secuencial y alea­ 19
torio de elementos por medio del índice o de un iterador, los contenedores asociativos están
diseñados para un rápido acceso aleatorio de elementos por medio de claves. La biblioteca
estándar de C++ proporciona cuatro contenedores asociativos: map, multimap, set y
multiset.

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

1 // Listado 19.10: Clase contenedora map


2
3 #include <iostream>
4 #include <string>
5 ^include <map>
6
7 using namespace std;
8
9
10 class Estudiante
11 {
12 public:
13 Estudiante();
14 Estudiante(const string & nombre, const int edad);
15 Estudiante(const Estudiante & rhs);
16 -Estudiante();
17 void AsignarNombre(const string & nombre);
18 string ObtenerNombre() const;
19 void AsignarEdad(const int edad);
20 int ObtenerEdad() const;
21 Estudiante & operator=(const Estudiante & rhs);
22 private:
23 string suNombre;
24 int suEdad;
25 };
26
27 Estudiante::Estudiante():
28 suNombre("Nuevo Estudiante"),
29 suEdad(16)
30 {>
31
32 Estudiante::Estudiante(const string & nombre, const int edad):
33 suNombre(nombre),
34 suEdad(edad)
35 {}
36
37 Estudiante::Estudiante(const Estudiante & rhs):
38 suNombre(rhs.ObtenerNombre()),
39 suEdad(rhs.ObtenerEdad())
40 {}
41
42 Estudiante::-Estudiante()
43 {>
44
45 void Estudiante::AsignarNombre(const string & nombre)
46 {
47 suNombre = nombre;
48 }
49
50 string Estudiante::ObtenerNombre() const
51 {
Plantillas 703

52: return suNombre;


53: }
54:
55: void Estudiante: :AsignarEdad(const int edad)
56: {
57: suEdad = edad;
58: }
59:
60: int Estudiante::ObtenerEdad() const
61 : {
62: return suEdad;
63: }
64:
65: Estudiante & Estudiante: : operator=(const Estudiante & rhs)
66: {
67: suNombre = rhs.ObtenerNombre();
68: suEdad = rhs.ObtenerEdad();
69: return *this;
70: }
71 :
72: ostream & operator«(ostream & os, const Estudiante & rhs)
73: {
74: os « rhs.ObtenerNombre() « " tiene
75: os << rhs.ObtenerEdad() « " años de edad";
76: return os;
77: }
78:
79: template< class T, class A >
80: // desplegar propiedades del contenedor map
81 : void MostrarMapfconst map< T, A > & v);
82:
83: typedef map< string, Estudiante > ClaseEscuela; 1 9
84:
85: int main()
86: {
87: Estudiante Harry("Harry", 18);
88: Estudiante S a l l y ("Sally", 15);
89: Estudiante B i l l ( " B i l l " , 17);
90: Estudiante Peter("Peter", 16);
91 :
92: ClaseEscuela ClaseMatematicas;
93: ClaseMatematicasf Harry.ObtenerNombre() ] = Harry;
94: ClaseMatematicasf Sally.ObtenerNombre() ] = Sally;
95: ClaseMatematicasf Bill.ObtenerNombre() ] = B il l;
96: ClaseMatematicasf Peter.ObtenerNombref) ] = Peter;
97:
98: cout « "ClaseMatematicas:\n";
99: MostrarMap(ClaseMatematicas);
100
101 cout « "Sabemos que
102 cout « ClaseMatematicasf "Bill" ] . ObtenerNombre();

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

103: cout << " tiene ";


104: cout « ClaseMatematicas! "Bill" ].ObtenerEdad();
105: cout « " años de edad\n";
106: return 0;
107: }
108:
109: // Desplegar propiedades del contenedor map
110: template< class T, class A >
111 : void MostrarMap(const map< T, A > & v)
112: {
113: for (map< T, A >::const_iterator ci = v.begin();
114: ci != v.end(); ++ci)
115: cout « ci->first « ": " << ci->second « "\n";
116: cout « endl;
117: }

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

Sabemos que Bill tiene 17 años de edad


En la línea 5 incluimos el archivo de encabezado <map> ya que estaremos utilizan­

■ do la clase contenedora map estándar. En consecuencia, definimos la función de


plantilla MostrarMap para desplegar los elem entos de un contenedor map. En la línea 83
se define ClaseEscuela como un contenedor map de elem entos; cada uno consta del par
(clave, valor). El prim er elem ento en el par es la clave. En ClaseEscuela usamos los
nombres de los estudiantes como sus claves, que son de tipo string. La clave asociada a
cada uno de los elementos del contenedor map debe ser única; es decir, no puede haber dos
elem entos con la misma clave. El segundo elem ento del par es el objeto en sí, que en el
ejem plo es un objeto Estudiante. El tipo de datos en par se implementa en la STLcomo
un tipo struct de dos elementos: f irst y second. Puede utilizar estos elementos para
tener acceso a la clave y al valor de un nodo.
Puede saltarse la función main() y ver primero la función MostrarMap. Esta función utiliza
un iterador constante para tener acceso a un objeto de tipo map. En la línea 115, c i-> first
apunta a la clave, que es el nom bre de un estudiante. ci->second apunta al objeto
Estudiante.
En las líneas 87 a 90 se crean cuatro objetos de tipo Estudiante. En la línea 92,
ClaseMatematicas se define como una instancia de ClaseEscuela. En las líneas 93 a
96 agregamos los cuatro estudiantes a ClaseMatematicas usando la siguiente sintaxis:
objeto_map[valorclave] = valor_objeto;
Plantillas 705

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

La clase stack de la STL no es la misma que el mecanismo de pila utilizado


internamente por los compiladores y los sistemas operativos, en el que las
pilas pueden contener distintos tipos de objetos. Sin embargo, la funcionali­
dad fundamental es muy similar.

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

En t r a d a L ista d o 1 9 . 1 1 Un objeto de función


1: // Listado 1 9. 1 1 : Un objeto de función
2:
3: ¿/include <iostream.h>
4:
5: using namespace std;
6:
7:
8: template< class T >
9: class Imprimir
10: {
11 : public:
12: void operator^ ) (const T & t)
13: { cout « t « " "; }
14: };
15:
16: int main()
17: {
18: Imprimir< int > Hacerlmpresion;
19:
20: for (int i = 0; i < 5; ++i)
21 : Hacerlmpresion(i);
22: cout « endl;
23: return 0;
24: }

S a l id a 0 12 3 4

En las líneas 8 a I4 se define la clase de plantilla Imprimir. El operador ()


A n á lisis
sobrecargado de las líneas 12 y 13 toma un objeto y lo envía a la salida estándar.
En la línea 18. Hacerlmpresion se define como una instancia de la clase Imprimir. Así. 1 9
puede utilizar Hacerlmpresion igual que una función para imprimir cualquier valor entero,
com o se m uestra en la línea 21.

A lg o ritm o s de secuencia no m uíante


Los algoritm os de secuencia no imitante no cambian los elementos de una secuencia. Esto
incluye los operadores for_each(), find(), search(), count (), entre otros. El listado
19.12 m uestra cóm o utilizar un objeto de función y el algoritmo for_each para im prim ir
los elem entos de un vector:

En t r a d a L ista d o 1 9 . 1 2 Uso del algoritmo for_each ()

1: // Listado 19 .1 2: Uso de for_each


2:
3: //inelude <iostream>
4: //inelude <vector>
5: //inelude <algorithm>
6:
continúa
L istado 1 9 . 1 2 c o n t in u a c ió n

7: using namespace std;


8:
9:
10 template< class T >
11 class Imprimir
12 {
13 public:
14 void operatori)(const T & t)
15 { cout « t « “ "; >
16 };
17
18 int main()
19 {
20 Imprimir< int > Hacerlmpresion;
21 vector< int > vlnt(5);
22
23 for (int i = 0; i < 5; ++i)
24 vinti i ] = i * 3;
25 cout « "for_each()\n" ;
26 for_each(vInt.begin(), vlnt.endi), Hacerlmpresion);
27 cout « "\n" ;
28 return 0;
29 }

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 ().

L is t a d o 19.13 Un algoritmo de secuencia mutante


1: // Listado 19.13: Secuencia mutante
2:
3: #include <iostream>
4: #include <vector>
5: #include <algorithm>
Plantillas 709

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

Si necesita especializar el comportamiento para algunas funciones de plantilla con base


en el tipo actual, puede redefinir una función de plantilla con un tipo específico. Esto
también funciona para las funciones miembro.

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

PISTA (esto está difícil): ¿Qué hace a Gato diferente de int?


6. Declare el operator== amigo para Lista.
7. Implemente el operator== amigo para Lista.
8. ¿Tiene operator== el mismo problema que en el ejercicio 5?
9. Implemente una función de plantilla para intercambiar dos variables.
10. Implemente a ClaseEscuela del listado 19.8 como una lista. Utilice la función
push_back() para agregar cuatro estudiantes a la lista. Luego desplácese por la
lista resultante e incremente en uno la edad de cada estudiante.
11. Modifique el ejercicio 10 para utilizar un objeto de función para desplegar el regis­
tro de cada estudiante.
S em a n a 3

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

Bugs y corrupción de código


Todos los programas tienen bugs (errores). Entre más grande sea el programa tendrá más
bugs, y muchos de éstos irán en el software final y liberado. Que esto sea verdad no quiere
decir que esté bien, pues hacer programas robustos y libres de bugs es la principal prioridad
de cualquiera que considere seriamente la programación.
El principal problema en la industria del software es el código inestable y lleno de bugs. El
mayor gasto en muchos esfuerzos importantes de programación es probar y arreglar. La per­
sona que resuelva el problema de producir programas buenos, sólidos, a prueba de todo, a
un bajo costo y a tiempo, revolucionará la industria del software.
Hay diversos tipos de bugs que pueden ocasionar problemas en un programa. El primero
es una mala lógica: el programa hace lo que usted le pide, pero usted no ha pensado
apropiadamente todos los algoritmos. El segundo es de tipo sintáctico: utilizó el idioma,
la función o la estructura incorrectos. Éstos dos son los más comunes, y son los que la
mayoría de los programadores trata de evitar.
La investigación y la experiencia en el mundo real han mostrado que, sin lugar a dudas,
entre más tarde se encuentre un problema durante el proceso de desarrollo, más costará
arreglarlo. Los problemas menos costosos o los bugs más fáciles de arreglar son los que
usted evita crear. Los que siguen en menor costo son los que encuentra el compilador.
Los estándares de C++ hacen que los compiladores inviertan mucha energía para lograr
que aparezcan más bugs en tiempo de compilación.
Los bugs que se compilan pero que se detectan en la primera prueba (aquellos que ocasionan
fallas en todo momento) son menos difíciles de encontrar y arreglar que los que aparecen
sólo de vez en cuando.

Un problema mayor que los bugs lógicos o sintácticos es la fragilidad innecesaria: el


programa funciona perfectamente si el usuario escribe un número cuando se le pide, pero
falla si el usuario escribe letras. Otros programas fallan si se acaba la memoria, o si el
disquete no está dentro de la unidad, o si el módem pierde la conexión.
Para combatir este tipo de fragilidad, los programadores se esfuerzan por hacer sus progra­
mas a prueba de todo. Un programa a prueba de todo es aquel que puede manejar cualquier
cosa que surja en tiempo de ejecución, desde una entrada rara por parte del usuario hasta el
agotamiento de la memoria. Otro término para este proceso es programación a la defensiva,
que al igual que la conducción a la defensiva, significa estar preparado para lo inesperado.
Es importante distinguir entre los bugs, que aparecen debido a que el programador se
equivocó en la sintaxis; los errores lógicos, que surgen porque el programador entendió
mal el problema o la forma de resolverlo; y las excepciones, que surgen debido a problemas
inusuales pero predecibles, como cuando se agotan los recursos (memoria o espacio en
disco).
Excepciones y manejo de errores 715

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

// realizar otra acción


}
Los pasos básicos para utilizar las excepciones son los siguientes:
1. Identificar aquellas áreas del programa en las que se empieza una operación que
podría provocar una excepción, y colocarlas en bloques try.
2. Crear bloques catch para atrapar las excepciones en caso de que se produzcan,
limpiar la memoria asignada e informar al usuario según sea apropiado. El listado
20.1 muestra el uso de los bloques try y catch.
Las excepciones son objetos que se utilizan para transmitir información acerca de un
problema.
Un bloque try está entre llaves, y en él se puede producir una excepción.
Un bloque catch sigue inmediatamente después de un bloque try, y en él se manejan las
excepciones.
Cuando se produce (o surge) una excepción, el control se transfiere al bloque catch que
sigue inmediatamente después del bloque try actual.

Algunos compiladores muy antiguos, como el GNU 2.7.2, no soportan las


Nota excepciones. Sin embargo, éstas son parte del estándar ANSI de C++, y las edi­
ciones más recientes de compiladores de cualquier fabricante soportan com­
pletamente las excepciones nativas. Si usted tiene un compilador más antiguo,
no podrá compilar y ejecutar los ejercicios de esta lección. Sin embargo, sería
conveniente que leyera toda la lección, y que regresara a este material cuando
actualice su compilador.
Para estos ejemplos, utilice la versión de compilador que viene en el CD-ROM
(2.9.5).

Entrada L is t a d o 20.1 Cómo se produce una excepción


1: // listado 20.1: Cómo se produce una excepción
2:
3: #include <iostream.h>
4:
5: const int TamanioPred = 10;
6:
7:
8: class Arreglo
9: {
10: public:
11: // constructores
12: Arreglo(int suTamanio = TamanioPredet);
13: Arreglo(const Arreglo & rhs);
14: -Arreglo()
continúa
1718 Día 20

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

68 i f (desplazamiento >= 0 && desplazamiento < ObtenersuTamanio()


69 return apTipo[ desplazamiento ];
70 throw xLimite();
71 // apaciguar a MSC
72 return apTipo[ 0 ];
73
74
75
76 const int & Arreglo::operator[] (int desplazamiento) const
77 {
78 int mitamanio = ObtenersuTamanio();
79
80 i f (desplazamiento >= 0 && desplazamiento < ObtenersuTamanio())
81 return apTipo[ desplazamiento ];
82 throw xLimite();
83 // apaciguar a MSC
84 return apTipo[ 0 ];
85 }
86
87 ostream & operator« (ostream & salida, const Arreglo & elArreglo)
88 {
89 for (int i = 0; i < elArreglo.ObtenersuTamanio(); i++)
90 salida « "[" « i « "] 11 « elArreglof i ] « endl;
91 return salida;
92 }
93
94 int main()
95 {
96 Arreglo arreglolnt(20);
97
98 try
99 {
100: for (int j = 0; j < 100; j++)
101 : {
102: arreglolnt[ j ] = j;
103: cout « "arregloInt[" « j;
104: cout « "] está bien..." « endl;
105: }
106: }
107: catch (Arreglo::xLimite)
108: {
109: cout « "¡No se pudo procesar su entrada!\n";
110: }
111 : cout << "Listo.\n";
112: return 0;
1 1 3: }

ar reglolnt[0] está bien. . .


Salida arreglolnt[1] está bien...
arre glo lnt[2] está bien...
arre glo lnt[3] está bien...
arregloInt[4] está bien...
arreglolnt[5] está bien...
720 Día 20

arregloInt[6] está bien,


arreglolnt[7] está bien,
arreglolnt[8] está bien.
arregloInt[9] está bien,
arreglolnt!10J está bien
arreglolnt[11 ] está bien
arreglolnt[12] está bien
arreglolnt[13] está bien
arregloInt[14] está bien
arregloInt[15] está bien
arregloInt[l6] está bien
arreglolnt[17] está bien
arreglolnt[18] está bien
arreglolnt[19] está bien
INo se pudo procesar su entrada!
Listo.

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
} . •' ■

Uso de los bloques try y catch


No es fácil entender en dónde debe poner sus bloques try: no siempre es obvio qué
acciones pueden provocar una excepción. La siguiente pregunta es en dónde atrapar la
excepción. Tal vez quiera producir todas las excepciones de memoria en donde se asigna
la memoria, pero quiera atrapar las excepciones en un nivel alto en el programa en el que
trate con la interfaz de usuario.
Día 20

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>

5: const int TamanioPredet = 10;


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 & operator!](int desplazamiento);
19: const int & operator!](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 xMuyGrande {};
28: class xMuyChico{};
29: class xCero {};
30: class xNegativo {};
31 : private:
32: int *apTipo;
33: int suTamanio;
34:
35 • 'S-L*'■" '-V"--'*;1*
36: int & Arreglo:¡operatori](int desplazamiento)
37: {
38: int tamanio = ObtenersuTamanio() ; v'.w
39: t.■■■'■:
40: if (desplazamiento >= 0 && desplazamiento <
^►ObtenersuTamanioí))
41 : return apTipof desplazamiento ];
42: throw xLimite();
43: // apaciguar a MFC
44: return apTipo! 0 ];
45: }
46:
47:
48: const int & Arreglo:¡operatori](int desplazamiento) const
49: {
continúo
724 Día 20

L is t a d o 2 0 . 2 c o n t in u a c ió n

50: int mitamanio = ObtenersuTamanio();


51 :
52: if (desplazamiento >= 0 && desplazamiento <
^ObtenersuTamanio())
53 return apTipo[ desplazamiento ];
54 throw xLimite();
55 // apaciguar a MFC
56 return apTipo[ 0 ];
57
58
59 Arreglo::Arreglo(int tamanio):
60 suTamanio(tamanio)
61 {
62 if (tamanio == 0 )
63 throw xCero();
64 if (tamanio < 1 0 )
65 throw xMuyChico();
66 if (tamanio > 30000)
67 throw xMuyGrande();
68 if (tamanio < 0 )
69 throw xNegativo();
70 apTipo = new int[ tamanio ];
71 for (int i = 0 ; i < tamanio; i++)
72 apTipo[ i ] = 0 ;
73
74
75 int main()
76 {
77 try
78 {
79 Arreglo arreglolnt(0 );
80
81 for (int j = 0; j < 100; j++)
82 {
83 arreglolnt[ j ] = j;
84 cout « "arreglolnt[“ << j « "] está bien...\n";
85
86
87 catch (Arreglo::xLimite)
88 {
89 cout « "¡No se pudo procesar su entrada!\n";
90 }
91 catch (Arreglo::xMuyGrande)
92 {
93 cout << "Este arreglo es muy grande...\n";
94 >
95 catch (Arreglo:¡xMuyChico)
96 {
97 cout « "Este arreglo es muy chico...\n";
Excepciones y m anejo de errores 725

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 }

¡Pidió un arreglo de cero objetos!


S alida Listo.

En las líneas 27 a 30 se crean cuatro nuevas clases: xMuyGrande, xMuyChico, xCero


A nálisis
y xNegativo. El constructor, líneas 59 a 73, examina el tamaño que se le pasa. Si
es muy grande, muy pequeño, negativo o cero se produce una excepción.
El bloque try se cambia para incluir instrucciones catch para cada condición que no sea
un número negativo, la cual es atrapada por medio de la instrucción catch (. . .) (“atrapar
todo”), que se muestra en la línea 104.
Pruebe esto con varios valores para el tamaño del arreglo. Luego trate de colocar un -5. Tal
vez haya esperado que se llamara a xNegativo, pero el orden de las pruebas del constructor
previno esto: tamanio < 10 se evaluó antes de tamanio < 0. Para arreglar esto, inter­
cambie las líneas 64 y 65 con las líneas 68 y 69 y vuelva a compilar.

Jerarq u ías de excepciones


Las excepciones son clases, y como tales, se pueden hacer derivaciones de ellas. Puede ser
ventajoso crear una clase xTamanio, y derivar de ésta a xCero, xMuyChico, xMuyGrande
y xNegativo. Por ejemplo, algunas funciones podrían simplemente atrapar errores de tipo
2 0
xTamanio, y otras funciones podrían atrapar el tipo específico de error xTamanio. El listado
20.3 muestra esta idea.

Entrada L ist a d o 2 0 . 3 Jerarquías de clases y excepciones

1: // Listado 20.3: Jerarquias de clases y excepciones


2:
3: tfinclude <iostream.h>
4:
5: const int TamanioPredet = 10;
6:

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

55: int tamanio = ObtenersuTamanio();


56:
57: i f (desplazamiento >= 0 && desplazamiento < ObtenersuTamanio())
58: return apTipo[ desplazamiento ];
59: throw xLimite();
60: // apaciguar a MFC
61 : return apTipo[ 0 ];
62: }
63:
64: const int & Arreglo::operator[] (int desplazamiento) const
65: {
66: int mitamanio = ObtenersuTamanio();
67:
68: i f (desplazamiento >= 0 && desplazamiento < ObtenersuTamanio())
69: return apTipo[ desplazamiento ];
70: throw xLimite();
71 : // apaciguar a MFC
72: return apTipo[ 0 ];
73: }
74:
75: int main()
76: {
77: try
78: {
79: Arreglo arreglolnt(0);
80:
81 : for (int j = 0; j < 100; j++)
82: {
83: arregloInt[ j ] = j;
84: cout « "arreglolntf" « j « "] está bien...\n";
85: }
86: }
87: catch (Arreglo::xLimite)
88: {
89: cout « "¡No se pudo procesar su entrada!\n";
90: }
91 : catch (Arreglo: : xMuyGrande)
92: {
93: cout « "Este arreglo es muy grande.. . \n"; 2 0
94: }
95: catch (Arreglo: : xMuyChico)
96: {
97: cout << "Este arreglo es muy chico...\n";
98: }
99: catch (Arreglo::xCero)
100: {
101 : cout « "¡Pidió un arreglo";
102: cout << " de cero objetos!\n";
103: }
continúa
728 Día 20

L is t a d o 2 0 . 3 c o n t in u a c ió n

104: catch (...)


105: {
106: cout « "¡Algo salió mal!\n";
107: >
108: cout « “Listo.\n";
109: return 0;
110: >

Este arreglo es muy chico...


S alida Listo.
El cambio significativo se encuentra en las líneas 27 a 31, en donde se establece
A nálisis
la jerarquía de clases. Las clases xMuyGrande, xMuyChico y xNegativo se derivan
de xTamanio, y xCero se deriva de xMuyChico.
El A rre g lo se crea con un tamaño de cero, pero, ¿qué ocurrió? ¡Parece que se atrapó la
excepción equivocada! Sin embargo, examine cuidadosamente el bloque catch, y descubri­
rá que éste busca una excepción de tipo xMuyChico antes de buscar una excepción de tipo
xCero. Como se produce un objeto xCero, y éste es un objeto xMuyChico, el manejadorlo
atrapa como xMuyChico. Después de manejar la excepción, ya no se pasa a los otros mane­
jadores, por lo que nunca se llama al m anejador de xCero.
La solución para este problema es ordenar cuidadosamente los manejadores, de forma que
los manejadores más específicos vayan primero y los menos específicos después. En este
ejemplo específico, si se cambia la ubicación de los manejadores xCero y xMuyChico se
solucionará el problema.

Acceso a los datos de excepciones mediante


la denominación de objetos de excepciones
A menudo necesitará saber más que sólo el tipo de excepción que se produjo para poder
responder apropiadamente al error. Las clases de excepciones son iguales que cualquier
otra clase. Usted puede proporcionar datos, inicializar los datos en el constructor y leer
esos datos en cualquier momento. El listado 20.4 muestra cómo hacer esto.

Entrada| L ista d o 20.4 C ó m o sacar datos de un objeto de excepción


1: // Listado 20.4: Obtención de los datos de un objeto de excepción
2:
3: #include <iostream.h>
4:
5: const int TamanioPredet = 10;
Excepciones y manejo de errores

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

58: int *apTipo;


59: int suTamanio;
60:
61 :
62:
63: Arreglo::Arreglo(int tamanio):
64: suTamanio(tamanio)
65: {
66: if (tamanio == 0)
67: throw xCero(tamanio);
68: if (tamanio > 30000)
69: throw xMuyGrande(tamanio);
70: if (tamanio < 0)
71 : throw xNegativo(tamanio);
72: if (tamanio < 10)
73: throw xMuyChico(tamanio);
74: apTipo = new int[ tamanio ];
75: for (int i = 0; i < tamanio; i++)
76: apTipo[ i ] = 0 ;
77: }
78:
79: int & Arreglo::operator[ ] (int desplazamiento)
80: {
81; int tamanio = ObtenersuTamanio();
82:
83: if (desplazamiento >= 0 && desplazamiento < ObtenersuTamanioO)
84: return apTipof desplazamiento ];
85: throw xLimite();
86: return apTipo[0¡;
87:
88:
89: const int & Arreglo::operator(] (int desplazamiento) const
90:
91 : int tamanio = ObtenersuTamanioO;
92:
93: if (desplazamiento >= 0 && desplazamiento < ObtenersuTamanioO)
94: return apTipo[ desplazamiento ];
95: throw xLimite();
96: return apTipo[ 0 ];
97:
98:
99: int main()
100: {
101 : try
102: {
103: Arreglo arreglolnt(9);
104:
Excepciones y m anejo de errores 731

105 for (int j = 0; j < 100; j++)


106 {
107 arregloInt[ j ] = j;
108 cout « "arregloInt[“ « j « "] está bi en.. ." « endl;
109 }
110 }
111 catch (Arreglo::xLimite)
112 {
113 cout « "¡No se pudo procesar su entrada!\n";
114 }
1 15 catch (Arreglo::xCero laExcepcion)
116 {
1 17 cout << "¡Pidió un arreglo de cero objetos!" << endl;
118 cout << "Se recibieron ";
119 cout « laExcepcion.ObtenerTamanio() « endl;
120 }
121 catch (Arreglo::xMuyGrande laExcepcion)
122 {
123 cout << "Este Arreglo es muy grande..." « endl;
124 cout << "Se recibieron ";
125 cout « laExcepcion.ObtenerTamanio() « endl;
126 }
127 catch (Arreglo: : xMuyChico laExcepcion)
128 {
129 cout << "Este Arreglo es muy chico..." « endl;
130 cout << "Se recibieron ";
131 cout « laExcepcion.ObtenerTamanio() « endl;
132 }
133 catch ( . . . )
134 {
135 cout << "Algo salió mal,";
136 cout « " pero ¡no tengo idea de qué fue!\n";
137 }
138 cout « "Listo.\n";
139 return 0;
140

Este arreglo es muy chico... 2 0


S alida Se recibieron 9
Listo.
La declaración de xTamanio se ha modificado para incluir una variable miembro
A nálisis
llamada suTamanio (línea 35) y una función miembro llamada ObtenerTamanio ()
(línea 32). Además, se ha agregado un constructor que toma un entero e inicializa la variable
miembro, como se muestra en la línea 30.
Las clases derivadas declaran un constructor que no hace nada excepto inicializar la clase
base. No se declararon otras funciones, en parte para ahorrar espacio en el listado.
732 Día 20

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.

Tenga en mente que si está construyendo una excepción, es porque se ha


Nota producido una: algo ha salido mal, y su excepción debe tener cuidado de no
activar el mismo problema. Por lo tanto, si está creando una excepción
NoHayMemoria, lo más seguro será no asignar memoria en su constructor.

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

L is t a d o 2 0 .5 Paso de valores por referencia y uso de funciones virtuales


Entrada en las excepciones

1: // Listado 20.5: Paso de valores por referencia en las excepciones


2:
3: #include <iostream.h>
4:
5: const int TamanioPredet = 10;
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 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
Excepciones y m anejo de errores

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

79: cout « xTamanio::suTamanio << endl;


80: }
81 : };
82: private:
83: int * apTipo;
84: int suTamanio;
85: };
86:
87: Arreglo::Arreglo(int tamanio):
88: suTamanio(tamanio)
89: {
90: if (tamanio == 0 )
91 : throw xCero(tamanio);
92: if (tamanio > 30000)
93: throw xMuyGrande(tamanio);
94: if (tamanio < 0 )
95: throw xNegativo(tamanio);
96: if (tamanio < 10 )
97: throw xMuyChico(tamanio);
98: apTipo = new int[ tamanio ];
99: for (int i = 0 ; i < tamanio; i++)
100: apTipo[ i ] = 0 ;
101 :
102:
103: int & Arreglo::operator[] (int desplazamiento)
104:
105: int tamanio = ObtenersuTamanioO;
106:
107: if (desplazamiento >= 0 && desplazamiento < ObtenersuTamanioO)
108: return apTipo[ desplazamiento ];
109: throw xLimite();
110: return apTipo[0 ];
111 :
112:
113: const int & Arreglo::operator[] (int desplazamiento) const
114:
115: int tamanio = ObtenersuTamanio();
116:
117: if (desplazamiento >= 0 && desplazamiento < ObtenersuTamanioO)
118: return apTipo[ desplazamiento ];
119: throw xLimite();
120: return apTipof 0 ];
121 :
122:
123: int main()
124: {
125: try
126: {
Excepciones y manejo de errores 735

127 Arreglo arregloInt(9);


128
129 for (int j = 0; j < 100; j++)
130 {
131 arreglolntl j ] = j;
132 cout « “arregloInt[" « j « u] está bien...\n“;
133 }
134 }
135 catch (Arreglo: :xl_imite)
136 {
137 cout « "¡No se pudo procesar su entrada!\n°;
138 >
139 catch (Arreglo: rxTamanio & laExcepcion)
140 {
141 laExcepcion.ImprimirError();
142 }
143 catch (...)
144 {
145 cout « "¡Algo salió mal!\n";
146 }
147 cout « "Listo.\n";
148 return 0;
149

¡Muy chico! Se recibieron: 9


S a l id a Listo.
El listado 20.5 declara un método virtual en la clase xTamanio llamado Imprimir -
A nálisis
E rro r (), el cual imprime un mensaje de error y el tamaño actual de la clase. Este
método se redefíne en cada una de las clases derivadas.
En la línea 139, el objeto de excepción se declara como una referencia. Cuando se llama a
ImprimirError() con una referencia a un objeto, el polimorfismo ocasiona que se invoque
la versión correcta de ImprimirError(). El código es más limpio, más fácil de entender
y de mantener.

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

1: // Listado 20.6: Excepciones con plantillas


2:
3: flinclude <iostream.h>
4:
5: const int TamanioPredet = 10;
6:
7:
8: class xLimite {};
9:
10 témplate < class T >
11 class Arreglo
12 {
13 public:
14 // constructores
15 Arreglo(int suTamanio = TamanioPred) ;
16 Arreglo(const Arreglo & rhs);
17 -Arreglo()
18 { delete [] apTipo;}
19 // operadores
20: Arreglo & operator=(const Arreglo< T > &) ;
21 : T & operatori](int desplazamiento);
22: const T & operatori](int desplazamiento) const;
23: // métodos de acceso
24: int ObtenersuTamanio() const
25: { return suTamanio; }
26: // función amiga
27: friend ostream & operator<< <> (ostream &, const Arreglo< T > &);
28: // definir las clases de excepciones
29: class xTamanio {>;
30: private:
31 : int * apTipo;
32: int suTamanio;
33: };
34:
35: template < class T >
36: Arreglo< T >::Arreglo(int tamanio):
37: suTamanio(tamanio)
38: {
39: if (tamanio < 1 0 || tamanio > 30000)
40: throw xTamanio();
41 : apTipo = new T[ tamanio ];
42: for (int i = 0; i < tamanio; i++)
43: apTipol i ] = 0 ;
44:
45:
46: template < class T >
47: Arreglo< T > & Arreglo< T >::operator=(const Arreglo< T > & rhs)
48: {
49: if (this == &rhs)
E x c e p c io n e s y m a n e jo d e e rro re s 737

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.

C ó m o activar e x c e p c i o n e s sin erro re s


Cuando los programadores de C++ se reúnen después del trabajo para tomarse una cerveza
virtual en el bar del ciberespacio, la charla, por lo general, gira en tomo a si las excepciones
se deben utilizar para condiciones de rutina. Algunos afirman que, debido a su naturaleza,
las excepciones se deben reservar para aquellas circunstancias predecibles pero excep­
cionales (¡de ahí el nombre!) que un program ador debe anticipar, pero que no son parte
del procesam iento de rutina del código.
O tros recalcan que las excepciones ofrecen una form a poderosa y limpia de regresara
través de m uchos niveles de llamadas a funciones sin peligro de fugas de memoria. Un
ejem plo muy com ún es el siguiente: el usuario solicita una acción en un entorno GUI.
La parte del código que atrapa la solicitud debe llam ar a un método en un administrador
Excepciones y manejo de errores 739

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.

C ó m o tratar c o n los errores y la d e p u r a c i ó n


Casi todos los entornos de desarrollo modernos incluyen uno o más depuradores de alto
poder. La idea esencial acerca del uso de un depurador es la siguiente: usted ejecuta su
depurador, el cual carga el código fuente, y luego ejecuta su programa desde el depurador.
Esto le permite ver cada instrucción de su programa a medida que se va ejecutando, y
examinar sus variables a medida que van cambiando durante la vida de su programa.
Todos los compiladores le permitirán compilar con o sin símbolos. Compilar con símbolos
le indica al compilador que cree el mapeo necesario entre su código fuente y el programa
generado; el depurador utiliza esto para apuntar a la línea de código fuente que corresponde
a la siguiente acción del programa.
Los depuradores simbólicos facilitan esta tarea. El depurador GNU, gdb, es un depurador
simbólico que se ejecuta en modo de texto; algunos otros depuradores simbólicos soportan
un modo de pantalla completa con ventanas. Al cargar el depurador, éste leerá todo el
código fuente. Puede pasar por las llamadas a funciones sin entrar, o hacer que el depurador
entre a la función, línea por línea.
Con la mayoría de los depuradores puede alternar entre el código fuente y la salida para
ver los resultados de cada instrucción ejecutada. Algo que ofrece más poder es examinar el
estado actual de cada variable, examinar estructuras de datos complejas, examinar el valor
de los datos miembro dentro de las clases y ver los valores actuales en memoria de varios
apuntadores y de otras ubicaciones de memoria. Puede ejecutar varios tipos de control
dentro de un depurador que incluyan establecer puntos de interrupción, establecer puntos
de observación, examinar la memoria y analizar el código en lenguaje ensamblador.
740 Día 20

Uso de gdb o depurador GNU


El prim er paso para utilizar el depurador GNU (o casi cualquier otro depurador) es ase­
gurarse de que el compilador haya incluido el código requerido en el archivo ejecutable.
Con esta información, el depurador sabe dónde se guardan las variables, los nombres de
éstas y de las funciones, y cómo relacionar el código fuente y el código binario.
La prim era pregunta que podría venir a la mente sería: "¿Por qué no incluir siempre
el código especial de depuración en el archivo ejecutable?". La respuesta es simple; ese
código reduce la velocidad de ejecución del program a y ocasiona que ocupe memoria
adicional. Todos queremos que nuestros programas se ejecuten con rapidez y que utilicen
la menor cantidad posible de recursos del sistema. Así que, a menos que se necesite, no le
indicamos al compilador que incluya esta información.
Para indicarle al compilador GNU que incluya información de depuración en el código
binario, se utiliza la opción -g:
g++ -g suprogratna.cxx -o suprograma
De manera predeterminada, la información de depuración no se incluye.
Después de haber compilado todos sus módulos con la opción -g y de haberlos enlazado
en un archivo e je c u ta b le , está listo para utilizar gdb para depurar su programa.
La tabla 20.1 muestra algunos de los com andos comunes que se usan en gdb.

Ta bla 2 0 .1 C o m a n d o s c o m u n e s d e gd b

Com ando Propósito


break [ a r c h i v o : ] f u n c ió n Establecer un punto de interrupción al entrar a fu n c ió n en
a r c h i v o , a r c h i v o es opcional.
bt Forma abreviada de “ backtrace” ; mostrar la pila del programa.
c Continuar la ejecución del programa después del punto de
interrupción.
disassemble Mostrar el código binario como código en lenguaje ensam­
blador en vez de lenguaje fuente (es decir, C++).
display e x p Mostrar el valor de la variable e x p cada vez que el progra­
ma se detenga.
help Obtener ayuda en general o ayuda en categorías de coman­
dos, comandos específicos y opciones para comandos
específicos, help es casi el comando más útil de todos.
list [[a r c h i v o : ] l i n e a ] Muestra ± 5 líneas de código fuente, a r c h i v o y lin e a
especifican el código fuente a desplegar y son opcionales.
next Ejecutar el siguiente paso del programa sin entrar en las
funciones llamadas.
E x c e p c io n e s y m a n e jo d e e rro re s 741

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.

Dos tips rápidos para trabajar con gdb:


Debe establecer un punto de interrupción antes de emitir el comando run.
De no ser asi, el programa se ejecutará hasta completarse o fallar. Un buen
hábito es establecer un punto de interrupción dentro de main para que se
pueda realizar la inicialización de la biblioteca estándar en tiempo de ejecu­
ción, y luego introducir los otros puntos de interrupción, puntos de obser­
vación y despliegues.
742 Día 20

El o tro tip es acerca de u n m e n sa je de e rro r co n fu so :


pruebal.cxx:6: No such f i l e or d ir e c t o r y (ENOENT).
Si recibe este m ensaje, se d e b e a q u e n o se p u e d e e n c o n tra r el archivo
fu e n te (pruebal .cxx), o a q u e su a rc h iv o fu e n t e tie n e u n a extensión diferen­
te (pruebal .cpp) p e ro el c o m p ila d o r c o n fu n d ió las cosas. La solución para la
prim er situación es ejecutar el p ro g ra m a en el d ire cto rio a p ro p ia d o o utilizar
la o p ció n -d para especificar el d ire c to rio q u e co n tie n e los archivos fuente.
La se g u n d a es un p o c o rara: c o p ia r su a rc h iv o fu e n te (pruebal .cpp) en el
n o m b re q u e g d b está b u sc a n d o (p ru e b a l .cxx), y el p ro b le m a deberá desa­
parecer.

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.

Uso de los puntos de interrupción


Los puntos de interrupción son instrucciones que indican al depurador que cuando una
línea específica de código esté lista para ejecutarse, el programa debe detenerse. Esto le
permite ejecutar su programa sin impedimentos hasta llegar a la línea en cuestión. Los
puntos de interrupción le permiten analizar la condición actual de las variables justo antes
y después de una línea crítica de código.

Uso de los puntos de observación


Es posible pedir al depurador que le muestre el valor de una variable específica o que se
detenga cuando se lea o se escriba a una variable específica. Los puntos de observación
le permiten establecer estas condiciones, y algunas veces le permiten modificar el valor
de una variable mientras el programa está en ejecución.

Examen y modificación del estado de la memoria


A veces es importante ver los valores actuales que se guardan en memoria. Los depuradores
modernos pueden mostrar valores en el formato de la variable actual; es decir, cadenas que
se pueden mostrar como caracteres, enteros largos com o números y no como 4 bytes, y
así sucesivamente. El sofisticado depurador gdb puede incluso mostrar clases completas
y proporcionar el valor actual de todas las variables miembro, incluyendo el apuntador this.
Y lo que es igual de importante, gdb le permite cam biar los valores de las variables cuan­
do el programa se está ejecutando.

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

P ¿ P o r qué no u tilizar excepciones p a r a con d icio n es q u e no sean de error? ¿No


sería conveniente p o d er re g re s a r a las á re a s de código de m ás atrás, incluso
cu an d o existan condiciones q u e no sean excepcionales?
R Sí, algunos programadores de C++ utilizan excepciones para ese propósito. El peligro
es que las excepciones podrían crear fugas de memoria al eliminar los elementos de
la pila y algunos objetos se queden accidentalm ente en el heap. Con las técnicas
de programación cuidadosas y un buen com pilador, esto se puede evitar. De otra
manera, es cuestión de estética personal: algunos programadores sienten que, por sus
excepciones naturales, no se deben utilizar para condiciones de rutina.
P ¿Se tiene que a tr a p a r u n a excepción en el m ism o lu g a r en el que el bloque try
la creó?
R No, es posible atraparla en cualquier lugar de la pila de llamadas. Conforme se
eliminan los elementos de la pila, la excepción se pasa a la parte baja de la pila
hasta que es manejada.
P ¿P o r qué u tilizar u n d e p u ra d o r si se p u ed e u tiliz a r co u t con compilación
condicional ( # ifd e f debug)?
R El depurador proporciona un mecanismo mucho más poderoso para avanzar paso a
paso por su código y observar que los valores cam bien sin tener que atestar el
código con instrucciones de depuración.

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:

• Q ué es la com pilación condicional y cómo manejarla


• C óm o escribir m acros mediante el preprocesador
• C óm o utilizar el preprocesador para encontrar bugs
• C óm o m anipular bits individuales y utilizarlos como indicadores
• C uáles son los siguientes pasos para aprender a utilizar C++ en forma
efectiva
|748 Día 21

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

Cómo ver el formato del archivo intermedio


Casi todos los compiladores tienen un parám etro que se puede establecer ya sea en el
entorno de desarrollo integrado (IDE) o en la línea de com andos, el cual le indica al
com pilador que guarde el archivo intermedio.
Para el compilador GNU, esa opción es -E. Para otros compiladores, revise la documenta­
ción para establecer los parámetros adecuados si quiere exam inar este archivo.

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

El archivo intermedio producido por el precom pilador se verá así:


int miArreglo[512];

O bserve que la directiva #def in e ha desaparecido. Las instrucciones del precompilador


se quitan del archivo intermedio; no aparecen en el código fuente final.

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

Uso de #def ine para probar el código


Una segunda manera de utilizar #def ine es para declarar que se defíne una cadena de
caracteres específica. Por lo tanto, podría escribir
//define GRANDE
Después, puede probar si GRANDE ha sido definida y realizar las acciones apropiadas. Los
comandos del precompilador para probar si una cadena ha sido definida son #if def (si
está definida) e #ifndef (si no está definida). El comando #endif debe estar después de
ambos comandos antes de que el bloque termine (antes de la siguiente llave de cierre).
#if def se evalúa como true si la cadena que prueba ya ha sido definida. Por lo tanto,
puede escribir

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

Uso de la directiva de precompilador #else


Como podría imaginar, el término #else se puede insertar entre #ifdef o entre #if ndef
y el #endif de cierre. El listado 21.1 muestra cómo se utilizan estos términos.

Entrada Listado 21.1 Uso de #define

1: // Listado 21.1 - Uso de #define


2:
3: #define VersionDemo
4: //define VERSION NT 5
continúa
L is t a d o 2 1 . 1 continuación

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 >

Comprobando las definiciones de VersionDemo,


Salida *-VERSION_NT y VERSION_WINDOWS...
VersionDemo definida.
VERSION_NT definida como: 5
VERSION_WINDOWS no fue definida.
Listo.
En las líneas 3 y 4 se definen VersionDemo y VERSION_NT, y esta última se define
con la cadena 5. En la línea 12 se prueba la definición de VersionDemo, y como
VersionDemo sí está definida (aunque sin valor), la prueba es true y se imprime la cadena
de la línea 13.
En la línea 18 se prueba si VERSION_NT no está definida. C o m o sí está definida, esta prueba
falla y la ejecución del programa salta hacia la línea 21. Aquí, la cadena 5 sustituye a la
cadena VERSI0N_NT; el compilador ve esto como
cout « " VERSION_NT definida como: " << 5 << endl;
Qué sigue

Observe que la primera cadena VERSI0N_NT no se sustituye ya que se encuentra en una


cadena encerrada entre comillas. Sin embargo, la segunda cadena VERSI0N_NT sí se sustitu­
ye, y por consecuencia el compilador ve un 5 como si usted lo hubiera escrito ahí.
Por último, en la línea 24 el programa prueba la definición de VERSION_WINDOWS. Como
esta cadena no se definió, la prueba falla y se imprime el mensaje de la línea 27.

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.

Nunca está de m ás utilizar gu a rd ia s de inclusión. Con frecuencia le ahorrarán


Nota horas de tie m p o de depuració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)

Se quitará toda la cadena DOBLE( 4 ), ¡y se sustituirá el valor 8 ! Cuando el precompilador


vea el 4, lo sustituirá por ( (4) * 2), lo que se evaluará com o 4 * 2, o sea 8.
Una macro puede tener más de un parámetro, y cada parámetro se puede utilizar en form
repetida en el texto de reemplazo. Dos macros com unes son MAX y MIN:
#define MAX(x,y) ( ( x) > (y) ? (X) : (y))
#define MIN(x,y) ((x) < (y) ? (X) : (y))

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

y luego tratara de utilizar MAX de la siguiente manera:


Qué sigue 753

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;

¿Para qué son todos esos paréntesis?


Tal vez se esté preguntando por qué hay tantos paréntesis en muchas de las macros que
se han presentado hasta ahora. El preprocesador no requiere que se coloquen paréntesis
alrededor de los argumentos de la cadena de sustitución, pero los paréntesis le ayudan a
evitar efectos secundarios no deseados al pasar valores complicados a una macro. Por
ejemplo, si define MAX como
#define MAX(x,y) x > y ? x : y
y pasa los valores 5 y 7, la macro funciona como se espera. Pero si pasa una expresión
más complicada, obtendrá resultados inesperados, como se muestra en el listado 2 1 .2 .

Entrada Listado 2 1 . 2 U s o d e p aré ntesis en m acros

1: // Listado 21.2 Expansión de macros


2:
3: #include <iostream.h>
4: #define CUBO(a) ((a) * (a) * (a))
5: #define TRIPLE(a) a * a * a
6:
7:
8: int main()
9: {
10: long x = 5;
11: long y = CUBO(x);
12: long z = TRIPLE(x);
13:
14: cout « "y: 11 « y « endl;
15: cout « "z: “ « z « endl;
16:
17: long a = 5, b = 7;

continúa
754 Día 21

L is t a d o 2 1 . 2 continuación

18: y = CUBO (a + b);


19: z = TRlPLE(a + b);
20:
21 : cout « "y: “ « y « endl;
22: cout « “z: “ « z « endl;
23: return 0;
24: }

y: 125
z: 125
y: 1728
z: 82

En la línea 4 se define la macro CUBO, y se coloca el argumento x entre paréntesis


cada vez que se utiliza. En la línea 5 se define la macro TRIPLE, sin los paréntesis.
En el primer uso de estas macros, se da el valor 5 como parámetro, y ambas macros fun­
cionan bien. CUBO (5) se expande a ((5) * (5) * (5)), lo que se evalúa como 125, y
TRIPLE(5) se expande a 5 * 5 * 5, lo cual también se evalúa como 125.
En el segundo uso, en las líneas 17 a 19, el parámetro es 5 + 7. En este caso, CUB0(5+7)
se evalúa como
((5+7) * (5+7) * (5+7))
lo que se evalúa como
((12) * (12) * (12))

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.

Macros en comparación con funciones y plantillas


En C++-, las macros sufren de cuatro problemas. El primero es que pueden ser confusas
si se hacen grandes, ya que todas las macros se deben definir en una línea. En la mayoría
de los compiladores se puede extender esa línea por medio del carácter de barra diagonal
inversa (\), lo que se conoce como e m p a lm e d e lín e a s .
Q u é sig u e 755

El preprocesador distribuido con los compiladores GNU no maneja de manera apropiada


el em palm e de líneas, por lo que todas las macros deben permanecer en una línea. Las
macros grandes se vuelven rápidamente difíciles de manejar.

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.

El hecho de que se expanden en línea conduce al tercer problema: la macro no aparece


en el código fuente intermedio utilizado por los compiladores; por lo tanto, no está dis­
ponible en la mayoría de los depuradores. Esto dificulta la depuración de las macros.

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.

Entrada L i s t a d o 21.3 Uso de una función en línea en lugar de una macro


1: // Listado 21 . 3: Uso de una función en linea en lugar de una macro
2:
3: #include <iostream.h>
4:
5: inline unsigned long Cuadrado(unsigned long a)
6: { return a * a; }
7: inline unsigned long Cubo(unsigned long a)
8: { return a * a * a; }
9:
10:
11 : int main()
12: {
13: unsigned long x = 1 ;
14:
15: for (; ; )
16: {
17: cout « "Escriba un número (0 para s a lir ) :
18: cin >> x;
19: i f (x == 0)
continúa
L is t a d o 2 1 . 3 continuación

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

Escriba un número (0 para salir) : 1


S a l id a Usted escribió: 1. Cuadrado(1): 1 . Cubo(1): 1.
Escriba un número (0 para salir) : 2
Usted escribió: 2. Cuadrado(2): 4. Cubo(2): 8.
Escriba un número (0 para salir)::3
Usted escribió: 3. Cuadrado(3): 9. Cubo(3): 27
Escriba un número (0 para salir):: 4
Usted escribió: 4. Cuadrado(4): 16. Cubo(4): 64.
Escriba un número (0 para salir): 5
Usted escribió: 5. Cuadrado(5): 25. Cubo(5): 125.
Escriba un número (0 para salir): 6
Usted escribió: 6. Cuadrado(6): 36. Cubo(6): 216.
Escriba un número (0 para salir): 0

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

Uso de cadenas con la directiva #define


El operador de cadena coloca comillas alrededor de cualquier carácter que le siga, hasta
el siguiente espacio en blanco. Por ejemplo, si escribe
//define ESCRIBECADENA( x) cout « #x

y luego hace la siguiente llamada:


ESCRIBECADENA(Esta es una cadena);

El precom pilador la convertirá en


cout « "Ésta es una cadena";

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

Para GNU, se definiría de la siguiente manera:


#define Listade(Tipo) class Tipo##Lista { public: Tipo##Lista(){}
w-private: int suLongitud; };

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)

y esto se convertiría en la declaración de la clase ListaAnimales. Hay algunos problemas


con este método, los cuales se tratan en el día 19, “Plantillas”.

M a c r o s p r e d e f i n i d a s

Muchos compiladores predefinen una variedad de macros útiles, incluyendo__DATE_,


— TIME— , LINE y FILE .Cada uno de estos nombres está rodeado por dos carac­
teres de guión bajo para reducir la probabilidad de que los nombres tengan conflicto con
los que usted utilice en su programa.

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

En vez de depender de la macro ASSERT () proporcionada por el compilador, usted puede


escribir su propia macro ASSERT (). El listado 21.4 proporciona una macro ASSERT () sen­
cilla y muestra su uso.

Entrada Listado 21.4 U n a m a c ro A S S E R T O sencilla

1: // Listado 21.4 ASSERT


2:
3: #define DEPURAR
4: #include <iostream.h>
5:
6: #ifndef DEPURAR
7: ^define ASSERT(x)
8: #else
9: #define ASSERT(x) if (! (x)) { cout « "IIERRORI! Assert “j
^•cout « #x « " falló\n"; cout « “ en la línea “ « LINE « "\n";
**cout « " del archivo " « FILE « "\n"; }
10 //
11 //
12 //
13 //
14 //
15 //
16 #endif
17
18
19 int main()
20 {
21 int x = 5;
22
23 cout « "Primera Assert: \n";
24 ASSERT(x == 5);
25 cout « "\nSegunda Assert: \n";
26 ASSERT(x != 5);
27 cout « "\nListo.\n" ;
28 return 0;
29

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.

Depuración con ASSERT ()


Al escribir su programa, a menudo sabrá muy dentro de su ser que algo es verdadero: una
función tiene cierto valor, un apuntador es válido, y así sucesivamente. La naturaleza de
los bugs (errores) es que, bajo ciertas condiciones, lo que usted sabe que es cierto, podría
no serlo. Por ejemplo, sabe que un apuntador es válido, a pesar de que el programa falle.
ASSERT () puede ayudarlo a encontrar este tipo de bugs, pero sólo si lo utiliza con frecuencia
y libremente en su código. Cada vez que asigne o que pase un apuntador como parámetro o
como valor de retomo de una función, asegúrese de afirm ar ( ASSERT) que el apuntador
es válido. Cada vez que su código dependa de que se encuentre un valor específico en una
variable, afirme (ASSERT ()) que eso es cierto.
No hay ningún castigo por el uso frecuente de ASSERT (); se quita del código cuando se
quita la definición de la depuración. Tam bién proporciona una buena documentación
interna, recordando al lector lo que usted cree que es cierto en cualquier momento dado
en el flujo del código.

ASSERT () en comparación con las excepciones


Ayer vio cómo trabajar con las excepciones para manejar condiciones de error. Es importan­
te observar que ASSERT () no tiene el propósito de manejar condiciones de error en tiempo
de ejecución, como datos incorrectos, condiciones de agotamiento de memoria, incapacidad
para abrir un archivo, etc. ASSERT () fue creada sólo para atrapar errores de programación.
Es decir, si “se dispara” una macro ASSERT (), usted sabrá que tiene un bug en su código.
Q u é s ig u e 76 1

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

R ecibir N U L L de una asignación de memoria no es un error de program ación, aunque sí


es una situación excepcional. Su programa debe ser capaz de recuperarse de esta condición,
aunque sea sólo para producir una excepción. Recuerde: toda la instrucción ASSERT () desa­
parece cuando no se define DEPURAR. Las excepciones se trataron con detalle en el día 20.

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)

cuando lo que quiere es probar si x == 5, creará un bug especialmente terrible.

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

M otivado por la frustración, vuelve a activar la depuración, y ¡listo! El bug desaparece.


De nuevo, esto parece gracioso, pero no lo es, así que debe ser muy cuidadoso con los
efectos secundarios producidos en el código de depuración. Si ve un bug que aparece sólo
cuando la depuración está desactivada, analice cuidadosam ente su código de depuración
para buscar efectos secundarios desagradables.

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

1: // Listado 21.5: Uso de Constantes!)


2.
3: #include <iostream.h>
4: #include <string.h>
5: #define DEPURAR
6: #define M0STRAR_C0NSTANTES
7:
8: #ifndef DEPURAR
9: #define ASSERT!x)
10 : #else
11 : #define ASSERT(x) if ( (x)) { cout << "¡¡ERROR!! Assert "
**« #x « " falló\n"; cout « " en la línea " « _LINE__ « "\n"
^►cout « " del archivo " << _ FILE_ « "\n"; }
12 #endif
13
14 const int FALSE = 0;
15 const int TRUE = 1 ;
16 // typedef int bool;
17
18
19 class Cadena
20 {
21 public:
22 // constructores
23 Cadena!);
24 Cadena(const char * const);
25 Cadena(const Cadena &);
Qué sigue 763

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

78: Cadena::Cadena(const Cadena & rhs)


79: {
80 suLongitud = rhs.ObtenerLongitud( );
81 suCadena = new char[ suLongitud + 1 ];
82
83 for (int i = 0; i < suLongitud;i++)
84: suCadena[ i ] = rhs[ i ];
85: suCadena[ suLongitud ] = ’\0 ';
86: ASSERT(Constantes());
87:
88:
89: // destructor, libera la memoria asignada
90: Cadena: .--Cadena ()
91 : {
92: ASSERT(Constantes());
93: delete [J suCadena;
94: suLongitud = 0 ;
95: >
96:
97: // operador igual a, libera la memoria existente
98: // y luego copia la cadena y el tamaño
99: Cadena & Cadena::operator=(const Cadena & rhs)
100:
101 : ASSERT(Constantes());
102: if (this == &rhs)
103: return *this;
104: delete [] suCadena;
105: suLongitud = rhs.ObtenerLongitud();
106: suCadena = new char[ suLongitud + 1 ];
107: for (int i = 0; i < suLongitud; i++)
108: suCadena[ i ] = rhs[ i ];
109: suCadenaf suLongitud ] = ’\0 ';
110: ASSERT(Constantes());
111 : return *this;
112:
113:
114: //operador de desplazamiento no constante
115: ^char & Cadena::operatori](int offset)
116:
117: ASSERT(Constantes());
118: if (offset > suLongitud)
119:
120: ASSERT(Constantes());
121 : return suCadenaf suLongitud - 1 ];
122:
123: else
124: {
125: ASSERT(Constantes()) ;
126: return suCadena[ offset ];
127:
Q u é sig u e 765

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

En la línea 11 se define la macro ASSERT(). Si se define DEPURAR, esto provocará que


A nálisis
se escriba un mensaje de error cuando la macro ASSERT () se evalúe como f alse.
En la línea 34 se declara la función miembro Constantes () de la clase Cadena, y se de­
fine en las líneas 143 a 149. El constructor se declara en las líneas 44 a 50. En la línea
49, después de que el objeto está completamente construido, se llama a la función
Constantes () para confirmar que la construcción sea apropiada.

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

Impresión de valores interinos


Además de utilizar la macro ASSERT () para afirmar que algo es cierto, tal vez necesite
imprimir el valor actual de los apuntadores, variables y cadenas. Esto puede ser útil para
ayudarlo a comprobar sus suposiciones acerca del progreso de su programa, y para localizar
bugs en ciclos de tipo “se pasó por uno”. El listado 21.6 muestra esta idea.

En t r a d a L istado 21.6 Impresión de valor«

1: // Listado 21.6 - Impresión de


2:
3: #include <iostream.h>
4: #define DEPURAR
5:
6: #ifndef DEPURAR
7: #define IMPRIMIR(x)
8: #else
9: #define IMPRIMIR(x ) cout « #x « ":\t" « x « endl;
10: #endif
11:
12: // enum bool { FALSE, TRUE > ;
13:
14: Sí
15: int main()
16: {
17: int x = 5;
18: long y = 738981;
19:
continúa
768 Día 21

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 i s t a d o 21.7 Niveles de depuración

1: // L is t a d o 21.7: N ive le s de depuración


2:
3: //define ALTO 3
4: //define MEDIO 2
5: //define BAJO 1
6: //define NINGUNO 0
7: //define NIVELDEPURAR ALTO
8:
9: //include <iostream.h>
10 : //include < s t r in g .h >
11 :
12: //if NIVELDEPURAR < BAJO // debe ser medio o alto para d e f i n i r ASSERT()
13: //define ASSERT(x)
14: //else
15: //define ASSERT(x) i f (! (x)) { cout « "¡¡ERROR!! Assert "
«.<< # x << " f a l l ó \ n " ; cout « " en la linea " « __LINE__ « "\ n ";
=»cout << " del a rch ivo " « __FILE__ « 11\n"; }
16: //endif
17:
18: //if NIVELDEPURAR < MEDIO
19: //define EVAL(x)
20: //else
21: //define EVAL(x) cout « #x « " : \ t " « x « endl;
22: #endif
23:
24: # i f NIVELDEPURAR < ALTO
25: //define IMPRIMIR(x)
26: //else
27: //define IMPRIMIR(x) cout << x « endl;
28: #e ndif
29:
30:
31 : c l a s s Cadena
32: {
33: p u b lic :
34: // constru cto res
35: Cadena();
36: Cadena(const char * const);
37: Cadena(const Cadena &);
38: ~Cadena();
39: char & o p e ra to r!] (in t offset);
40: char o p e r a t o r ! ] (in t offaset) const;
41: Cadena & operator=(const Cadena &);
42: i n t ObtenerLongitud()const 2 1
43: { return suLongitud; }
44: const char * ObtenerCadena() const
45: { return suCadena; }
continúa
L is t a d o 2 1 . 7 co ntinuació n

46 bool Constantes() const;


47 prívate:
48 // constructor privado
49 Cadena (int);
50 char * suCadena;
51 unsigned short suLongitud;
52 };
53
54 // constructor predeterminado crea una cadena de 0 bytes
55 Cadena::Cadena()
56 {
57 suCadena = new char[ 1 ];
58 suCadena[ 0 ] = ■\0 *;
59 suLongitud = 0 ;
60 ASSERT(Constantes());
61 }
62
63 // constructor privado (auxiliar), lo utilizan sólo
64 // los métodos de la clase para crear una nueva cadena del
65 // tamaño requerido. Se llena de caracteres nulos.
66 Cadena::Cadena(int longitud)
67 {
68 suCadena = new char[ longitud + 1 ];
69
70 for (int i = 0 ; i <= longitud; i++)
71 suCadena[ i ] = •\0 ■;
72 suLongitud = longitud;
73 ASSERT(Constantes());
74 }
75
76 // Convierte un arreglo de caracteres en una Cadena
77 Cadena::Cadena(const char * const cCadena)
78 {
79 suLongitud = strlen(cCadena);
80 suCadena = new char[ suLongitud + 1 ];
81 for (int i = 0 ; i < suLongitud; i++)
82 suCadena[ i ] = cCadena[ i ];
83 suCadena[ suLongitud ] = '\0 ';
84 ASSERT(Constantes());
85 >
86
87 // constructor de copia
88 Cadena::Cadena(const Cadena & rhs)
89
90 suLongitud = rhs.ObtenerLongitud();
91 suCadena = new char[ suLongitud + 1 ];
92
93 for (int i = 0 ; i < suLongitud; i++)
94 suCadena[ i ] = rhs[ i J;
Q u é s ig u e 771

95: suCadena[ suLongitud ] = ' \ 0 ‘ ;


96: ASSERT(Constantes());
97:
98:
99: // d e s t ru c to r, lib e r a la memoria asignada
100 Cadena: : -Cadena ()
101 {
102 ASSERT (C onstantes());
103 delete [] suCadena;
104 suLongitud = 0;
105 }
106
107 // operador ig u a l a, lib e ra la memoria existente
108 // luego copia la cadena y el tamaño
109 Cadena & Cadena: : operator=(const Cadena & rhs)
110 {
111 ASSERT(C onstantes());
112 i f ( t h i s == &rhs)
113 return * t h i s ;
114 delete [] suCadena;
115 suLongitud = rhs.ObtenerLongitud();
116 suCadena = new charf suLongitud + 1 ];
117 f o r (in t i = 0; i < suLongitud; i++)
118 suCadenaf i ] = rhs[ i ];
119 suCadena[ suLongitud ] = ' \ 0 ' ;
120 ASSERT(Constantes());
121 return * t h i s ;
122 }
123
124 //operador de desplazamiento no constante
125 char & Cadena: : o p e ra to r[] (in t offset)
126 {
127 ASSERT(C onstantes());
128 i f (o ff s e t > suLongitud)
129 {
130 ASSERT(Constantes());
131 return suCadena[ suLongitud - 1 ];
132 }
133 else
134 {
135 ASSERT(Constantes());
136 return suCadena[ offset ];
137 }
138
139
140 / / operador de desplazamiento constante
141 char Cadena::operator[] (int offset) const
2 1
142 {
143 ASSERT(Constantes());
continúa
772 Día 21

L is t a d o 2 1 . 7 continuación

144 char retval;


145
146 if (offset > suLongitud)
147 retVal = suCadena[ suLongitud • 1 j;
148 else
149 retVal = suCadena[ offset ];
150 ASSERT(Constantes());
151 return retual;
152
153
154 bool Cadena::Constantes() const
155 {
156 IMPRIMIR("(Constantes de Cadena probadas)");
157 return ((bool) (suLongitud && suCadena) ||
158 (¡suLongitud && ¡suCadena));
159 }
160
161 class Animal
162 {
163 public:
164 Animal() : suEdad(1), suNombre("John Q. Animal")
165 { ASSERT(Constantes()); }
166 Animal(int, const Cadena &);
167 -Animal() {}
168 int ObtenerEdad()
169 {
170 ASSERT(Constantes());
171 return suEdad;
172 >
173 void AsignarEdad(int Edad)
174 {
175 ASSERT(Constantes());
176 suEdad = Edad;
177 ASSERT(Constantes());
178 }
179 Cadena& ObtenerNombre()
180
181 ASSERT(Constantes());
182 return suNombre;
183 }
184 void AsignarNombre(const Cadena & nombre)
185 {
186 ASSERT(Constantes());
187 suNombre = nombre;
188 ASSERT(Constantes());
189 >
190 bool Constantes();
191 private:
192 int suEdad;
Qué sigue 773

¡
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

(Constantes de Cadena probadas)


(Constantes de Cadena probadas)
(Constantes de Animal probadas)
(Constantes de Cadena probadas)
(Constantes de Animal probadas)

Sparky(C on stan tes de Animal probadas)


tie n e 5 años de edad.
(Constantes de Animal probadas)
(Constantes de Animal probadas)
(Constantes de Animal probadas)

Sparky(C on stan tes de Animal probadas)


tie n e 8 años de edad.
(Constantes de Cadena probadas)

Ejecutado de nuevo con DEPURAR = MEDIO


EDAD: 5
Sparky tie n e 5 años de edad.
Sparky tie n e 8 años de edad.

A n á l is is E n las líneas 13 y 15 se define la m a cro a s s e r t () para ser eliminada si NIVEL-


d e p u r a r es menor que BAJO (es decir, que NIVELDEPURAR sea NINGUNO). Si se per­
mite cualquier nivel de depuración, la m a cro ASSE RT () funcionará. E n la línea 19 se
declara EVAL () para ser eliminada si DEPURAR es m e n o r que MEDIO: si NIVELDEPURAR es
NINGUNO o BAJO. EVAL () se elimina.

P o r último, en las líneas 2 4 a 28 se declara la m a c ro I M P R I M I R para ser eliminada si


NIVELDEPURAR es menor que ALTO. IM P R IM IR se utiliza s ó lo c u a n d o NIVELDEPURAR sea
ALTO; puede eliminar esta macro a sign a n d o el va lo r MEDIO a NIVELDEPURAR y seguir uti­
lizando a E V A L () y a A S S E R T ().

IM P R IM IR se utiliza dentro de los métodos C o n s t a n t e s () para im prim ir un mensaje infor­


mativo. EVAL () se utiliza en la línea 213 para e v a lu a r el v a lo r actual de la constante
entera EDAD.

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.

El apéndice C, "Números binarios, octales, hexadécimales y una tabla de


valores ASCII", proporciona información adicional valiosa acerca de la
manipulación de números binarios, octales y hexadécimales.

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.

Ta b l a 21.1 Los operadores a nivel de bits


Símbolo Operador
& AND

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 .

Cómo encender bits


Cuando se quiere encender o apagar un bit específico, se utilizan operaciones de enmas­
caramiento. Si tiene un indicador de 4 bytes y quiere que el bit 8 sea true, es decir,
encenderlo, necesita poner el operador OR a nivel de bits entre el indicador y el valor
128. ¿Por qué? El valor 128 es 1000 0000 en binario; por lo tanto, el valor del octavo
bit es 128. Cualquiera que sea el valor de ese bit (encendido o apagado), si pone un
OR a nivel de bits entre él y el valor 128, encenderá ese bit y no afectará a ninguno de
los otros bits. Suponga que el valor actual de una variable de 16 bits es 1010 0110 0010
0110. Si se pone un OR a nivel de bits entre los 16 bits y el valor 128, el resultado sería:
1010 0110 0010 0110 // bit g está apagado
| 0000 0000 1000 0000 ¡i -|28

1010 0110 r0Í0"0ÍÍ0- "// el bit 8 está encendido

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.

Cómo apagar bits


Si quiere apagar el bit 8, puede poner un operador AND a nivel de bits con el bit y con
el complemento de 128. El complemento de 128 es el número que se obtiene al tomar el
patrón de bits de 128 (1000 0000), encender todos los bits que estén apagados, y apagar
todos los bits que estén encendidos (0111 l i l i ) . Cuando se pone el operador AND a ni­
vel de bits con estos números, el número original permanece sin cambio, excepto por el
octavo bit, el cual vale cero.
Q u é s ig u e 777

1010 0110 1010 0110 // el b it 8 está encendido


& 1111 1111 0111 1111 // -128

? 0 ? 0 0110 0010 0110 // e l b it 8 está apagado

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.

Cómo onvertór los bits


Por último, si desea invertir el valor del bit 8, sin importar el estado que tenga, ponga un
operador OR exclusivo a nivel de bits con el número 128. Por ejemplo:
1010 0110 1010 0110 // número
A 0000 0000 1000 0000 II 128
7010 0110 0010 0110 II b it invertido
- 0000 0000 1000 0000 II 128

70?0 01 10* 1010 0110 // va lo r o rig in a l

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

el valor 0 o 1. Si escribe 2, el bit puede representar 0, 1.2 o 3. es decir, un total de cuatro


valores. Un campo de tres bits puede representar ocho valores, y así sucesivamente. En
el apéndice C se da un repaso a los números binarios. El listado 21.8 muestra el uso de
los campos de bits.

Entrada Listado 21.8 Uso de campos de bits


1: // Listado 21.8: Uso de campos de bits
2:
3: tfinclude <iostream.h>
4: tfinclude <string.h>
5:
6: enum ESTADO { TiempoCompleto, TiempoParcial };
7: enum NIVELGRAD { NoGrad, Grad };
8: enum ALOJAMIENTO { Dorm, FueraDelCampus };
9: enum PLANALIMENT { UnaComida, TodasLasComidas, FinesDeSemana,
**SinComidas };
10:
11:
12 class estudiante
13 {
14 public:
15 estudiante():
16 miEstado(TiempoCompleto),
17 miNivelGrad(NoGrad),
18 miAlojamiento(Dorm),
19 miPlanAliment(SinComidas)
20: O
21 : -estudiante() {}
22: ESTADO ObtenerEstado();
23: void AsignarEstado(ESTADO);
24: unsigned ObtenerPlan()
25: { return miPlanAliment; }
26: private:
27: unsigned miEstado : 1 ;
28: unsigned miNivelGrad : 1 ;
29: unsigned miAlojamiento : 1 ;
30: unsigned miPlanAliment : 2 ;
31 :
32:
33: ESTADO estudiante::ObtenerEstado()
34:
35: if (miEstado)
36: return TiempoCompleto;
37: else
38: return TiempoParcial;
39:
40:
41 : void estudiante::AsignarEstado(ESTADO elEstado)
42:
43: miEstado = elEstado;
44:
45:
46: int main()
Q u é s ig u e 779

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 }

Jim e stu d ia tiempo pa rcia l


S alida Jim e stu d ia tiempo completo
Jim tie n e e l plan Sin comidas de alimentación.

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

La función miembro ObtenerEstado( ) lee el bit booleano y regresa un tipo enumerado,


pero esto no es necesario. Se hubiera podido escribir con la misma facilidad para que re­
gresara el valor del campo de bits directamente. El compilador hubiera hecho la traducción.
Para que usted mismo pruebe esto, reemplace la implementación de ObtenerEstado() con
este código:
ESTADO estudiante::ObtenerEstado()
{
return miEstado;
>
No debe ocurrir ningún cambio en el funcionamiento del programa. Es cuestión de claridad
al leer el código; el compilador no es específico.
Observe que el código de la línea 50 debe comprobar el estado y luego imprimir el mensaje
apropiado. Es tentador escribir esto:
cout << "Jim estudia " « Jim.ObtenerEstado() << endl;
Lo que imprimirá esto:
Jim estudia 0

El compilador no tiene manera de convertir la constante enumerada TiempoParcial en


texto significativo.
En la línea 62, el programa utiliza una instrucción switch para el plan de alimentación, y
para cada valor posible coloca un mensaje razonable en el búfer, que se imprime después
en la línea 80. Observe de nuevo que la instrucción switch se hubiera podido escribir de
la siguiente manera:
case 0: strcpy(Pian,"Una comida"); break;
case 1: strcpy(Plan,"Todas las comidas"); break;
case 2: strcpy(Pian,"Comidas en fin de semana"); break;
case 3: strcpy(Plan,"Sin comidas");break;

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

Texto del programa


Puede usar varias sugerencias para crear código fácil de leer. El código fácil de leer es fácil
de mantener.
• Utilice espacios en blanco para favorecer la legibilidad.
• Los objetos y los arreglos en realidad se refieren a una cosa. No utilice espacios
dentro de referencias a objetos ( ., ->, [ ] ) .
• Los operadores unarios se asocian con sus operandos. así que no coloque un espa­
cio entre ellos. Pero sí coloque un espacio a un lado del operando. Los operadores
unarios incluyen a * (para apuntadores), &(conversiones) y sizeof.
• Coloque espacios en ambos lados de los operadores binarios: +, =, *, /, %,» , « ,
<, >, ==, !=, &, |, &&, M, ?: , =, +=, etc.
• No prescinda de los espacios para indicar la precedencia (4+ 3*2).
• Coloque un espacio después de las comas y los signos de punto y coma, no antes.
• No ponga espacios en ninguno de los dos lados de los paréntesis.
• Separe las palabras reservadas, como if , con un espacio: if (a == b).
• El cuerpo de un comentario se debe separar de los símbolos / / con un espacio.
• Coloque el apuntador o indicador de referencia en seguida del nombre del tipo, y
no antes del nombre de la variable:
char* foo;
int& ellnt;
en vez de
char *foo;
int &ellnt;

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

Ortografía y uso de mayúsculas en los nombres


Al crear su propio estilo, no debe descuidar la ortografía y el uso de mayúsculas. Algunas
sugerencias para estas áreas son:
• Utilice sólo mayúsculas y guiones bajos para separar las palabras lógicas de los nom­
bres, como PLANTILLA_ARCHIV0_FUENTE. Sin embargo, tome en cuenta que este
tipo de nombres es raro en C++. Considere el uso de constantes y plantillas en la
mayoría de los casos.
• Escriba los nombres de macros sólo con mayúsculas.
• Todos los demás identificadores pueden ser una mezcla de minúsculas y mayúsculas 2 1
sin guión bajo, o un solo tipo de letra (mayúscula o minúscula, por lo general minús­
cula) con guiones bajos. Al utilizar mezcla de mayúsculas y minúsculas, empiece con
una letra mayúscula los nombres de funciones, métodos, clases, tipos definidos y
nombres de estructuras, y con letra minúscula los elementos, como datos miembro
o locales.
784 Día 21

• 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

• Utilice siempre etiquetas p u b lic:, prívate: y protected:: no se base en los valores


predeterminados.
• Enliste primero los miembros públicos, después los protegidos y luego los privados.
Ponga los dalos miembro en un grupo después de los métodos.
• Coloque primero el(los) constructor(es) en la sección apropiada, después del
destructor. Enliste los métodos sobrecargados con el mismo nombre adyacentes
uno con otro. Agrupe las funciones de acceso siempre que pueda.
• Considere la alfabetización de los nombres de los métodos dentro de cada grupo,
asi como la alfabetización de las variables miembro. Asegúrese de alfabetizar
los nombres de archivos en directivas #include.
• Utilice la palabra reservada virtual, aunque su uso sea opcional al redefinir: esto le
ayuda a recordar que es virtual, y además mantiene la consistencia en la declaración.

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.

T o d o s los archivos de encabezado deben utilizar guardias de inclusión.

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.

Los siguientes pasos


Acaba de pasar tres largas y duras semanas trabajando con C++, y ahora es un programador
de C++ competente, pero esto de ninguna manera significa que haya terminado. Hay
muchas más cosas que debe aprender, y puede obtener información valiosa de muchas
más fuentes al avanzar de programador novato de C++ a experto.
Las siguientes secciones recomiendan varias fuentes específicas de información, y estas
recomendaciones reflejan sólo mi experiencia y opinión personales. Sin embargo, hay
docenas de libros disponibles acerca de cada uno de estos temas, así que asegúrese de
obtener otras opiniones antes de empezar a comprar más libros.

Dónde obtener ayuda y orientación


Lo primero que querrá hacer como programador de C++ será entrar a una de las conferen­
cias sobre C++ en un servicio en línea. Estos grupos proporcionan un contacto inmediato
con cientos de miles de programadores de C++ que pueden contestar sus preguntas,
darle consejos y proporcionarle un portavoz para sus ideas.
Muchos programadores participan en los grupos de noticias sobre C++ en Internet
(comp.lang.c++ y comp.lang.c++.moderated), y se los recomiendo como excelentes
fuentes de información y de soporte.
También puede buscar grupos de usuarios locales. Muchas ciudades tienen grupos intere­
santes de C++ en donde puede conocer otros programadores e intercambiar ideas.

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

D EBE suscribirse a una buena revista de


C ++ y unirse a un buen grupo de usuarios
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

P ¿ P o r q u é g e n e ra n tan ta emoción las guerras de estilo?

R Los programadores se apegan mucho a sus hábitos. Si usted está acostumbrado a


utilizar el siguiente estilo de sangría:
i f (UnaCondicion){
// instrucciones
} // llave de cierre
es muy difícil dejar este hábito. Los nuevos estilos se ven mal y crean confusión.
Si se harta, pruebe entrando a un servicio en línea que sea popular y pregunte qué
estilo de sangría funciona mejor, qué editor es el mejor para C++, o qué producto es
el mejor procesador de palabras. Luego siéntese a observar cómo se generan 10 mil
mensajes, todos contradiciéndose entre sí.
P ¿ Q u é es lo m e jo r que h ay p a ra leer después de este libro?

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

4. ¿Qué hace el operador de complemento (~) a nivel de hits?


5. ¿Cuál es la diferencia entre C)K y OR exclusivo (Xt)Ri?
6. ¿Cuál es la diferencia entre &y &&?
7. ¿Cuál es la diferencia entre | y | |?

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

N e ce sita utilizar una versió n reciente de los


Precaución c o m p ila d o re s G N U (2.9.5 o posterior) p ara q u e
1 1
este e je m p lo fu ncione.
Si su c o m p ila d o r n o so p o rta el u so de plantillas,
o si n o so p o rta las instrucciones t r y y catch, n o
p o d rá c o m p ila r ni ejecutar este listado.

1 2

Entrada L is t a d o R3.1 Listad o de re p aso d e la se m a n a 3

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

12 // ejemplo que la lista guardará


13 // Observe el uso de operator<< para imprimir la
14 // información acerca de una pieza con base en su
15 // tipo en tiempo de ejecución.
16 //
17 // Nodo - actúa como un nodo de la lista
18 //
19 // Lista - lista basada en plantilla que proporciona los
20 // mecanismos para una lista enlazada
21 //
22 //
23 // Autor: Jesse Liberty (jl)
24 //
25 // Desarrollado en: Pentium 200 Pro. 128MB RAM MVC 5.0
26 //
27 // Destino: Independiente de la plataforma
28 //
29 //
30 //
31 / j **************************************************
32
33 tfinclude <iostream.h>
34
35 // clases de excepciones
36 class Excepción {};
37 class NoHayMemoria : public Excepcion{};
38 class NodoNulo : public Excepcion{};
39 class ListaVacia : public Excepción {};
40 class ErrorLimites : public Excepción {};
41
42 // **************** pj_02g ************
43 // Clase base abstracta de piezas
44 class Pieza
45 {
46 public:
47 Pieza() : suNumeroObjeto(1 ) {}
48 Pieza(int NumeroObjeto) :
49 suNumeroObjeto(NumeroObjeto) {}
50 virtual -Pieza(){};
51 int ObtenerNumeroObjeto() const
52 { return suNumeroObjeto; }
53 //se debe redefinir el siguiente método
54 virtual void Mostrar()const = 0;
55 private:
56 int suNumeroObjeto;
57 };
58
59 // implementación de función virtual pura para que
60 // las clases derivadas se puedan encadenar
61 void Pieza::Mostrar() const
Repaso 793

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

113: { return suNumeroMotor; }


114: private:
115: int suNumeroMotor;
116: };
117:
118: PiezaAeroPlano::PiezaAeroPlano(int NumeroMotor,
119: int NumeroPieza):
120: suNumeroMotor(NumeroMotor),
121 : Pieza(NumeroPieza)
122: {}
123:
124: void PiezaAeroPlano::Mostrar() const
125: {
126: Pieza::Mostrar();
127: cout << "Número de motor: "
128: << suNumeroMotor << endl;
129: }
130:
131 : // adelantar declaración de la clase Lista
132: témplate < class T >
133: class Lista;
134:
135: // **************** ^jQ£jQ ************
136: // Nodo genérico, se puede agregar a una lista
137: / j ************************************
138:
139: témplate < class T >
140: class Nodo
141 :
142: public:
143: friend class Lista< T >;
144: Nodo (T *);
145: -Nodo();
146: void AsignarSiguiente(Nodo * node)
147: { suSiguiente = node; }
148: Nodo * ObtenerSiguiente() const;
149: T * ObtenerObjeto() const;
150: private:
151 : T * suObjeto;
152: Nodo * suSiguiente;
153: }J
154:
155: // Implementaciones de Nodo...
156:
157: témplate < class T >
158: Nodo< T >::Nodo(T * apObjeto):
159: suObjeto(apObjeto),
160: suSiguiente(0)
161 : {}
162:
163: témplate < class T >
Repaso 795

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

217: template < class T >


218 Lista< T >::-Lista()
219 {
220 delete apCabeza;
221 }
222
223 template < class T >
224 T * Lista< T >::ObtenerPrimero() const
225 {
226 if (apCabeza)
227 return apCabeza->suObjeto;
228 else
229 throw ListaVacia();
230 }
231
232 template < class T >
233 T * Lista< T >::operator[) (int offset) const
234 {
235 Nodo< T > * apNodo = apCabeza;
236
237 if (!apCabeza)
238 throw ListaVacia();
239 if (offset > suCuenta)
240 throw ErrorLimites();
241 for (int i = 0; i < offset; i++)
242 apNodo = apNodo->suSiguiente;
243 return apNodo->suObjeto;
244 }
245
246 // Buscar un objeto dado en una lista con base en su numero unico (id)
247 template < class T >
248 T * Lista< T >::Buscar(int & posicion,
249 int NumeroObjeto) const
250
251 Nodo< T > * apNodo = NULL;
252 for (apNodo = apCabeza, posicion = 0;
253 apNodo!=NULL;
254 apNodo = apNodo->suSiguiente, posicion++)
255
256 if (apNodo->suObjeto->ObtenerNumeroObjeto() == NumeroObj eto)
257 break;
258 }
259 if (apNodo == NULL)
260 return NULL;
261 else
262 return apNodo->suObjeto;
263 }
264
265 // insertar si el numero del objeto es unico
266 template < class T >
267 void Lista< T >::Insertar(T * apObjeto)
Repaso 797

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

321 int opcion;


322 int NumeroObjeto;
323 int valor;
324 Pieza * apPieza;
325
326 while (1)
327 {
328 cout « "(0)Salir (1)Auto (2)Avión:
329 cin >> opcion;
330
331 if (¡opcion)
332 break;
333 cout « "¿Nuevo NumeroPieza?: ";
334 cin >> NumeroObjeto;
335
336 if (opcion == 1)
337 {
338 cout << "¿Año del modelo?: ";
339 cin >> valor;
340 try
341 {
342 apPieza = new PiezaAuto(valor,
343 NumeroObjeto);
344 }
345 catch (NoHayMemoria)
346 {
347 cout << "No hay suficiente memoria."
348 « " Saliendo..." « endl;
349 return 1;
350 >
351 }
352 else
353 {
354 cout « "Número de motor?: ";
355 cin » valor;
356 try
357 {
358 apPieza = new PiezaAeroPlano(valor,
359 NumeroObjeto);
360 >
361 catch (NoHayMemoria)
362 {
363 cout « "No hay suficiente memoria.
364 << " Saliendo..." << endl;
365 return 1;
366 }
367 >
368 try
369 {
Repaso 799

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 }

(0)Salír (1)Auto (2)Avión: 1


S a l id a
¿Nuevo NumeroPieza?: 2837
¿Año del modelo? 90

(0)Salir (1)Auto (2)Avión: 2


¿Nuevo NumeroPieza?: 378
¿Número de motor?: 4938
(0) Salir (1) Auto (2) Avión: 1
¿Nuevo NumeroPieza?: 4499
¿Año del modelo? 94
(0) Salir (1) Auto (2) Avión: 1
800 Semana 3

¿Nuevo NumeroPieza?: 3000


¿Año del modelo? 93

(0) Salir (1) Auto (2) Avión: 0

Número de pieza: 378


Número de motor: 4938

Número de pieza: 2837


Año del modelo: 90

Número de pieza: 3000


Año del modelo: 93

Número de pieza: 4499


Año del modelo: 94

A nálisis El listado R3.1 modifica el programa que se proporcionó en la semana 2, agregán­


dole plantillas, procesamiento ostream y manejo de excepciones. La salida es
idéntica.
En las líneas 36 a 40 se declaran varias clases de excepciones. En el manejo algo primitivo
de excepciones proporcionado por este programa no se requieren datos ni métodos de estas
excepciones; sirven como indicadores para las instrucciones catch. los cuales imprimen
una simple advertencia y luego hacen que el programa termine. Un programa más robusto
podría pasar estas excepciones por referencia y luego extraer el contexto o cualquier otra
información de los objetos de las excepciones en un intento por recuperarse del problema.
En la línea 44 se declara la clase base abstracta Pieza exactamente como se declaró en la
semana 2. El único cambio interesante aquí se encuentra en el miembro operator«() que
no es de la clase, el cual se declara en las líneas 72 a 77. Observe que éste no es miembro
ni amigo de Pieza; simplemente toma una referencia a Pieza como uno de sus argumentos.
Tal vez querría hacer que operator<< tomara un objeto PiezaAuto y un PiezaAeroPlano
esperando que se llamara al operator<< correcto, con base en el tipo de pieza que se
pasara, ya sea de auto o de aeroplano. Sin embargo, como el programa pasa un apuntador a
una pieza, y no un apuntador a una pieza de auto o de aeroplano, C++ tendría que llamar
a la función correcta con base en el tipo real de uno de los argumentos para la función.
Esto se conoce como contravarianza, y C++ no la soporta.
Existen sólo dos maneras de lograr el polimorfismo en C++: el polimorfismo de funciones y
las funciones virtuales. El polimorfismo de funciones no funcionará aquí, ya que en todo
caso se está relacionando la misma firma: la de tomar una referencia a una Pieza.
Repaso 801

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.

Si C++ soportara la contravarianza, podríamos redefinir la función con base en el tipo


real del objeto, en tiempo de ejecución. El listado R3.2 no compilará en C++, pero si
C++ soportara la contravarianza, sí se compilaría. ¡Este archivo no compilará!
802 Semana 3

A d ve rte n cia : ¡N o será p o sib le c o m p ila r este listado!


Precaución

L ist a d o R3.2 Una demostración de la contravarianza


#include<iostream.h>
class Animal
{
public:
virtual void Hablar() { cout << "Animal habla\n"; }
};
class Perro : public Animal
{
public:
void HablarO { cout << "Perro habla\n"; }
};

class Gato : public Animal


{
public :
void HablarO { cout « "Gato habla\n" ; }
};
void Hacerlo(Gato *);
void Hacerlo(Perro *);
int main()
{

Animal * apA = new Perro;


Hacerlo(apA);
return 0;
}

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.

L is t a d o R 3 . 3 U na m ustra de funciones virtuales

#include<iostream.h>

class Animal
{
public:
virtual void Hablar() { cout « "Animal habla\n"; }
};
class Perro : public Animal
{
public:
void Hablar() { cout « “Perro habla\n”; }
},*

class Gato : public Animal


{
public:
void Hablar() { cout « “Gato habla\n"; }
};
void Hacerlo(Animal *);

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?

Esta lección trata acerca de los siguientes conceptos básicos:


• Editores
• C om piladores
• C reación de archivos ejecutables con make
• B ibliotecas y enlace
• D epuración con gdb
• C ontrol de versiones con RCS
• D ocum entación
| 808 Día 22

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.

ed, ex, vi y las variantes de vi


ed y ex son de los primeros editores que se utilizan desde la línea de comandos, disponi­
bles en los sistemas UNIX. Por lo general, ed se utiliza como un filtro para modificar el
texto que pasa a través de él. ex es un editor de texto en línea útil pero primitivo. Uno de
los primeros editores de UNIX basados en pantallas es vi, el cual es básicamente una
interfaz basada en pantallas escrita con base en ex.
En la mayoría de los sistemas Linux está disponible la reimplementación de código abierto
de vi, llamada vim. Muchas personas consideran que vi (y también vim) es enigmático y
difícil de utilizar. Sin embargo, tiene muchas ventajas con respecto a otros editores:
• Es muy poderoso.
• Está disponible prácticamente en todos los sistemas UNIX y Linux.
• Necesita menos recursos del sistema que emacs, y por consecuencia se ejecuta
aunque el sistema no esté funcionando completamente.
• No es tan personalizare como emacs, lo que significa que todas las implementa-
ciones se comportan casi de la misma forma.
Puede utilizar otro editor para el uso diario, pero es conveniente que domine los fundamen­
tos de vi. Los sistemas UNIX siempre vienen con vi, y como vim es la implementación
de vi que se proporciona con Linux, por lo general está vinculado a vi para que pueda
invocarlo con el nombre vi. Para el resto de esta lección, utilizaremos los términos vi y vim
como si fueran uno solo. En realidad nos estamos refiriendo a vim.
Inicio de vi
Antes de ejecutar cualquiera de los editores de pantalla completa (incluyendo a vi), debe
tener configurada la variable de entorno TERM para que indique el tipo de su terminal. Esta
variable se configura normalmente a xterm cuando se inicia una ventana en el entorno
gráfico. Si no está ejecutando el entorno gráfico, la terminal predeterminada es Linux.
Puede ver la página del manual en línea para vi con el comando man vi. El comando man
vim le proporciona exactamente la misma página.
810 D ía 22

Tam bién hay bastante ayuda disponible dentro del editor.


Inicie el editor con el com ando v i nom brearchivo
vim cuenta con un m odo gráfico, con el cual no cuenta vi. Incluye soporte para ratón y
m enús desplegables. Puede invocar este m odo con gvim nom brearchivo o con vi -g
nom brearchivo. Observe que para que el m odo gráfico funcione, debió compilar el editor
con esa opción específicam ente habilitada. Su versión tal v e / no haya sido compilada de
esta m anera. La figura 2 2 .1 m uestra un archivo editado en vi.

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.

Miquel van Smoorenburg, <imiquel = @ d r i n t ; e l .nl . m u g n e t . o r g >


Modified f o r ’R H S L i n u x b y Marc E u i n g a n d D o n n i e Barnes

* Default runlevel. The runlevels used by R H S are:


0 - halt (Do M O T set i n i t d e f a u l t - t o this)
* 1 - Single user mode
* 2 - M u l t i u s e r , w i t h o u t N F S ( T h e s a m e a s 3, i f y o u d o n o t h a v e networking'
» 3 - Full m u l t i u s e r m o d e
» 4 - unused
* 5 - Xll
» b - r e boot (Do N O T s e t i n i t d e f a u l t t o this)
e
id:5:initdefault:

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

Figura 22.2 m r !ho>.yi:/«K im


Ghelp.txt* For Vim version 5.3. Last modification: 1998 Aug 23
Ayuda de vi.
V I M - main help file

M o v e around: U s e t h e c u r s o r keys, o r "h" t o g o left, 1


“j " t o g o down, "k" t o g o up, "1" t o g o r i g h t . j
C l o s e t h i s window: U s e ":q(Enter>".
G e t o u t o f Vim: U s e " : q a ! < E n t e r > " (careful, 3ll c h a n g e s a r e l o s t ! ) .
J u m p to a subject: F o s i t i o n t h e c u r s o r o n a t a g b e t w e e n Ibarsl a n d h i t C T R L - ] .
W i t h t h e mouse: ":set m o u s e = a " t o e n a b l e t h e m o u s e ( i n x t e r m o r G U I ) .
D o u b l e - c l i c k the left m o u s e b u t t o n o n a t a g b e t w e e n Ibarsl,
j u m p back: T y p e C T R L - T o r CTRL-0.
G e t s p e c i f i c help: It is p o s s i b l e t o g o d i r e c t l y t o w h a t e v e r y o u w a n t h e l p
on, by g i v i n g a n a r g u m e n t t o the " : h e l p " c o m m a n d | : h e l p | .
It is p o s s i b l e to f u r t h e r s p e c i f y t h e c o n t e x t :
UHAT PREPEND EXAMPLE
Normal mode commands ( n othing) .•help x
V i s u a l m o d e c ommands v_ :help v_u
Insert mode commands i_ ¡help i_<Esc>
command-line commands ¡help .-quit

» inittab T h i s file d e s c r i b e s h o w the I N I T p r o c e s s s h o u l d s e t u p


a t h e s y s t e m in a c e r t a i n r u n -level.
a
LI i n i t t a b [RO]
’h e l p . t x t " [r e a d o n l y J 1 1 S 5 lines, 5 5 7 9 0 c h a r a c t e r s

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

4. Oprima “Esc” para salir del modo insertar.


Ahora que se encuentra de nuevo en el modo de comandos, observe que las teclas
h, j. k y 1 le permiten mover el cursor hacia la izquierda, hacia abajo, hacia arriba y
hacia la derecha, respectivamente.
Puede volver a entrar al modo insertar en cualquier momento. La tecla “i” empieza
la inserción en el lugar donde se encuentra el cursor, y la tecla “a” empieza la inser­
ción después del cursor. Otras teclas que puede utilizar son: “A” para insertar al final
de la línea actual; “o” para insertar en una nueva línea después de la línea actual; y
“O” para insertar una nueva línea en la posición actual. Observe que cuando se
encuentra en modo insertar, se despliega la palabra IN S E R T en la parte inferior de la
pantalla. Para salir del modo insertar, oprima “Esc”.
5. Escriba ZZ para escribir el archivo y salir (también puede utilizar :wq).

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.

En la d o c u m e n ta c ió n d e em acs, es c o m ú n referirse a la secuencias de teclas


en n o ta c ió n a bre via da . La ta b la 22.1 d e scrib e la n o ta c ió n convencional de
em acs. La clave M e ta se p u e d e a s ig n a r d e m a n e ra distinta en varias compu­
ta doras. En a lg u n a s es u n a tecla e tiq u e ta d a c o m o " M e t a " . Si su computado­
ra n o tien e esa tecla (la m a y o ría d e las PCs típicas n o la tienen; en su lugar,
tie n e n la tecla "A lt ") , e n to n c e s en ve z d e esta tecla o p rim a y libere la tecla
"E s c ". Por ejem plo, si la d o c u m e n t a c ió n d e em acs dice " M - v " , entonces debe
o p rim ir y liberar "E s c ", y lu e g o o p rim ir " v " .
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 813

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

Important Help menu items:


acs Tutorial Learn-by-doing tutorial for using Emacs efficiently.
|(Non)Warranty GNU Enacs comes with ABSOLUTELY NO WARRANTY
pying Conditions Conditions for redistributing and changing Em3cs.
tting New Versions Hou to obtain the latest version of Emacs.

iru Emacs 2 0 .3 .1 (1386-redhat-linux-gnu, K to o lk it)


f Won Apr 19 1999 on porky.devel.redhat.com
C opyright (C) 1998 Free Software Foundation, In c .

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

Esto revelará que la secuencia C -x C -c es una abreviación del comando “save-buffers-


Kill-emacs”.
Conceptos sobre emacs de GNU
A diferencia de vi, emacs se conoce como un editor “sin modos”. Escribir en la ventana de
emacs ocasiona que se escriban caracteres en el búfer que se está editando. Los comandos
de emacs siempre se deben señalar (mediante “caracteres de escape”) con alguna secuen­
cia de teclas de control.
emacs tiene muchos comandos, la mayoría de los cuales tienen nombres descriptivos muy
largos. Muchos de estos comandos están “vinculados” con secuencias más cortas de teclas
de control. Utilice la secuencia C -h c para descubrir los vínculos para una tecla o secuen­
cia de control.
Una de las cosas buenas acerca de emacs es la gran cantidad de comandos disponibles. Una
de las cosas difíciles sobre este editor es aprender las secuencias de control a las que están
vinculadas estas teclas.
Puede pasar por alto la abreviación de comandos y utilizar el nombre largo del comando.
Oprima “Esc” y escriba x para desplegar el indicador M -x en la parte inferior de la ventana.
Ahora empiece a teclear un comando, por ejemplo, save - buf fer. Escriba sa y oprima la
barra espaciadora o el fabulador, y entonces emacs completará la palabra save-. Presione
otra vez la barra espaciadora y emacs le mostrará los posibles comandos completos. Des­
pués escriba la letra “b” y oprima de nuevo la barra espaciadora, y se completará la palabra
b u ffe rs . Ahora oprima “Entrar” y se ejecutará el comando.
En cualquier momento puede oprimir C -g para abortar cualquier comando parcial que haya
empezado a escribir.
Uso de emacs
Utilice los siguientes pasos para modificar el archivo que escribió anteriormente en vi.
1. Escriba emacs para iniciar una sesión de edición sin archivo.
2. Oprima C -x C - f para obtener un indicador que le permita elegir un archivo.
3. Escriba hola.cxx para abrir el archivo que creó anteriormente. Al estar escribiendo
el nombre, en cualquier momento puede presionar la barra espaciadora o el fabulador.
Si emacs puede completar sin ambigüedad el nombre de archivo, lo hará. En caso
contrario, vuelva a presionar la barra espaciadora o el fabulador, y entonces emacs
le mostrará una lista de los archivos existentes cuyos nombres inicien con la subca­
dena que escribió.
4. Oprima “Entrar” y se desplegará el archivo hola.cxx.
5. Utilice las teclas de dirección para desplazarse por el archivo. También puede utilizar
C -n para moverse una línea hacia abajo, C - p para moverse hacia arriba, C -f para
moverse un carácter hacia adelante, y C -b para moverse hacia atrás. Muévase a la “m”
de la palabra “mundo”.
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 815

6. Escriba de nuevo y observe que el texto se inserta en donde se encuentra el cursor.


7. O prim a C-.v C-.v para escribir en el archivo. 2 2
8. O prim a C-.v C-c para salir del editor.

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

Escriba el siguiente código en un archivo llamado ho laP rin cip al.cx x :


#incl ude <stream.h>
void Saludar(int i ) ;
int main(int argc, char * argv[])
{
for(int i = 0; i < 5; i++)
{
Saludar(i);
cout « endl;
}
}
Ahora escriba el siguiente código en un archivo llamado Saludar.cxx:
tfinclude <stream.h>
void Saludar(int i)
{
cout << "[" << i << "] ¡Hola, mundo!";
return 0;
}
Escriba el com ando ctags *.cxx

Ha creado el nuevo archivo tags.


816 Día 22

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.

H E S S L i s tad o 22.1 Programa principal para el juego "dado"_______________


// Listado 22.1 Programa principal del juego Dado
3: #include <stdio.h>
4: #include <stdlib.h>
5: #include <string.h>
6:
7: int tirarDado(void);
8:
9: int main(int argc, char * argv[])
10: {
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 817

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

La función tirarDado() se implementa en el archivo l s t 2 2 -02.cxx:

Entrada L is t a d o 2 2 . 2 La función tirarDadoi

// Listado 22.2 Implementación de la función tirarDado()

tfinclude <stdlib.h>

int tirarDado(void)
{
return((rand() % 6) + 1);

Podría utilizar un solo comando para crear este programa:


gcc -o dado lst2 2 -0 1 .cx x l s t 2 2 -0 2 .cxx

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

Lenguajes d e secuencias d e c o m a n d o s (perl, se d , a w k )


C om o entorno integral sim ilar a U N IX . Linux tam bién proporciona los lenguajes típicos de
secuencias de com andos, perl. sed y awk son tres lenguajes importantes que se proporcionan
de m anera predeterm inada con la m ayoría de las distribuciones de Linux. Hay otros lengua­
jes de secuencias de com andos (Tcl/Tk, Python, Expect, entre otros) disponibles para Linux,
p eio están m ás allá del alcance de esta lección.

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

1P3IOC3M dado: El F 3 2-bit LSB e x ec ut a bl e, Intel 80386, ...


I / usr I b i n l a r c h i v o : ELF 3 2-bit LSB e xe cutable, Intel 80386, ...
Ist22-02.o: ELF 32-bit LSB r el ocatable, Intel 80386, ...
El entorno de programación de linux 819J

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

setenv LD_LIBRARY_PATH /home/myname/mylibs (si utiliza csh o tcsh)


export LD_LIBRARY_PATH=/home/myname/mylibs (si utiliza sh o bash)
Por último, para ver qué bibliotecas usa un programa, utilice el comando ldd:
ldd dado
libtirar.so. 1 => /home/Dado/libtirar.s o .1 (0x40014000)
S a l id a libe.so.6 => /lib/libc.so.6 (0x400ib000)
/lib/ld-linux.so.2 => /lib/ld-linux.s o .2 (0x40000000)

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

makefile:2: *** missing separator. Stop.

He aquí un archivo m ake un poco más completo:


# Makef ile para crear el programa para tirar dados
CFLAGS = 0

OBJS = l s t 2 2 -01 .o l s t 2 2 -02.0

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.

Entrada L is t a d o 2 2 . 3 A r c h iv o m a k e c o n b ib lio te c a s c o m p a rtid a s

I: ti A r c h i v o make para cre a r el p r o g r a m a para ti ra r dados


2: # u sando b i b l iotecas c o m p a r t i d a s
3: C F L A G S = -0
4: O B J S = l s t 2 2 -01.0
5: LIBS = libtirar.so
6:
7: all: dado
8:
9: dado: S(OBJS) $(LIBS)
10: S(CC) $ ( C F L A G S ) -o $@ $(0BJS) -L. -ltirar
II :
12: l s t 2 2 -01.o : l s t 22-01.cxx
13: $(CC) -c l s t 2 2 - 0 1 .cxx
14:
15: l s t 2 2 -02.o : l s t 22-02.cxx
16: S(CC) -fPIC -c $<
17:
18: libtirar.so: lst22-02.o
19: -S(RM) libtirar*
20: S(CC) -shared - W 1 s o n a m e ,l i b t i r a r .s o .1 \
21: -o l i b t i r a r .s o .1.o $<
22: ln -s l i b t i r a r .s o .1.o l i b t i r a r .s o .1
23: ln -s l i b t i r a r .s o .1 l i b t i r a r . s o
24:
25: clean:
26: - $ ( R M ) dado *.o l ib ti r ar *

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

make es un p ro g ram a m uy so fisticad o . U na de las co sas que hace es comprenderlas


dependencias. P or ejem plo, sabe que los archivos .o se crean a partir de archivos .c. Su
p ro g ram a puede co n sistir en varios a rc h iv o s fuente .c. Si cam bia uno, no es necesario
volver a com pilarlos todos cada vez que vaya a crearlo. Sólo necesita volver a compilar
el a rc h iv o fu en te que haya cam b iad o , make c o m p re n d e e sto y com pila sólo aquellos
archivos que no estén actualizados. A lgunas veces será necesario que vea primero qué es
lo que make necesita para crear el program a. Puede hacer esto con el siguiente comando:
make -n

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

List of classes of commands:


S a l id a
al i a s e s — A l i as e s of o t h e r c om ma n d s
b r e a k p o i n t s — M ak i n g p r o g r a m stop at c e r t a i n points
data — Examining data
files — Spec i fy in g and e x a m i n i n g files
i n ternals — M a i n t e n a n c e c o m m a n d s
obscure — Obscure featur e s
running — Running the p r o g r a m
stack — E x amining the stack
status — Status i nquiries
support — Support f a c i l it ie s
t r a c e points — Tracing of p r o g r a m e x e c u t i o n w i t h o u t stopping the progran
u s e r-defined — U s e r - d e f i n e d c o m m a n d s

Type "help" follo w ed by a cl as s name for a list of commands in that


class.
Type "help" follo w ed by c o m m a n d name for full documentation.
Command name a b b r e v i a t i o n s are a l l o w e d if unambiguous.

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

M aking program stop at c e r t a i n points.


S a l id a
List of commands:

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

Type "help" follo w ed by c o m m a n d name for full documentation.


C o m m a n d name a b br e v i a t i o n s are a l l o w e d if unambiguous.

Por últim o, para salir de gdb, escriba q.


La tabla 22.2 m uestra algunos de los co m an d o s m ás útiles de gdb.
El entorno de programación de linux 825

Comandos útiles de gdb


Ta b l a 2 2 . 2
Comando Función
break [archivo:]funcion Establecer un punto de interrupción en la entrada a la función del
archivo llamado archivo.
run [listarg] Iniciar el programa y pasarle la [listarg], en caso de haber argumentos.
bt Desplegar la pila del programa.
print expr Evaluar la expresión e imprimir el resultado.
c Continuar la ejecución desde el punto actual.
next Ejecutar la siguiente línea del programa. Si la siguiente línea es una
llamada a una función, entonces esto hará que se llame a la función y
se detendrá en la siguiente línea después de la llamada.
step Ejecutar la siguiente línea del programa, y entrar a la función si esa
línea es una llamada a una función.
help Inombre] Mostrar la ayuda general, o ayuda sobre el tema especificado por
[nombre], si se proporciona uno.
q Salir de gdb.

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.

Sesión de ejemplo de depuración con gdb


He aquí una sesión de ejem plo de la e jecu ció n del p rogram a dado en gdb:
% gdb dado
GNU gdb 5.0
Salida 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
Aconditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for
Adetails.
This GDB was configured as "i386-redhat-linux"...
(gdb) list 1,25
1 // Listado 22.1 Programa principal del juego Dado
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 int tirarDado(void);
8
9 int main(int arge, char * argv[])
10
11 int i;
12 int ilter;
13 int Dado[ 6 ];
14
15 if (arge < 2)
16 {
17 printf("Uso: %s n\n", argv[ 0 ]);
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 }
El e n t o r n o d e p ro g ra m a c ió n d e lin u x 827

(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

Breakpoint 1, main (argc=2, argv=0xbffffa64) at Ist22-01.cxx:20


20 ilter = atoi(argv[ 1 ]);
(gdb) print ilter
$1 = -1073743352
(gdb) next
21 memset(Dado, 0, sizeof(Dado));
(gdb) print ilter
$2 = 5
(gdb) next
22 for(i = 0; i < ilter; i++)
(gdb) next
24 Dado[ tirarDado() - 1 ]++;
(gdb) print tirarDado()
$3 = 2
(gdb) next
25 }
(gdb) next
24 Dado[ tirarDado() - 1 ]++;
(gdb) cont
Continuing.
5 tiradas
Cara Tiradas
1 : 0
2 : 2
3 : 0
4 : 1
5 : 1
6 : 1
Program exited normally.
(gdb)

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.

Ta b l a 22.3 Comandos de RCS


C om ando D e sc rip c ió n
ci Insertar en el depósito una nueva revisión de un archivo
co Obtener la última versión de un archivo
id e n t Buscar identificadores de RCS en archivos
merge Crear una versión de un archivo que incorpore cambios de otras dos versiones
de ese archivo
r c s d if f Comparar dos versiones de un archivo
rlo g Ver el historial de un archivo

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.

D e sp u é s de re g istra r u n a rc h iv o e n RCS, ve rá q u e su a rch ivo original ha desa­


parecido. N o se asuste, n o lo perdió. RC S ha rastrea d o sus cam bios en su copia
y ha e lim in a d o su o rig in a l. A ú n p u e d e re visa r su a rch ivo con el com ando co.

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

1: // Listado 22.4 Muestra el uso de control de versiones


2:
3: #incl ude <stdio.h>
4: tfinclude <stdlib.h>
5: tfinclude <string.h>
6:
7: int t i r a r D a d o ( v o i d ) ;
8:
9: main (int argc, char * a r g v [])
10: {
11: int i ;
12: int ilter;
13: int D a d o [ 6 ];
14:
15: if(argc < 2)
16: {
17: printf("Uso: %s n\n", argv[0]);
18: return 1 ;
19: }
20: ilter = a t o i (a r g v [1]);
21: memset(Dado, 0, sizeof(Dado));
22: p r i n t f (" $ H e a d e r $ \ n ");
23: for(i = 0; i < ilter; i++)
24: {
25: Dado[ tirarDado() - 1 ]++;
26: }
27: print f ( " % d tiradas\n", ilter);
28: p r i n t f ("\tNúmero\tTiradas\n");
29: for(i = 0; i < 6; i++)
30: {
31: printf ("\t%d :\t%d\n", i + 1, Dado[i]);
32: }
33: }

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

SHeader: / t m p / R C S / l s t 2 2 -03. cxx,v 1.1 2 0 0 0 / 1 2 / 2 0 08:37:38 usr Exp $


S a l id a
10 tiradas
Número Ti ra d as
1 : 1
2 : 4
3 : 0
4 : 2
5 : 2
6 : 1

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.

Figura 22.6 Buffert Filn loo), Edil SearchHile InfoHelp


info.
Ile: dir Hcde: rop This Is the top of the INFOti— •
T his (the D irectory node) gives a m enu of man or topics.
Typing "q" exits, -?•' lists all Info commands, "d" returns here,
“h" gives a primer for first-tiners.
■'mEmacsoReturn»" visits the Emacs topic, etc.
In Emacs, you can click: mouse button - on a menu itemor cross i ie. once
to select it.
Menu
Tex ToInfo docu(tnyinfol
winfo: mentation system The RNII .Ho.-nin.onrat(on formar.
install-info: (texinfo) Invoicing install-info. Update info,-dir entries,
lox12dvi: ;terinfo)Format with texl2dvl. Print Texlnfc documen’.s.
toxindex: (texinfoiFormat with 1ex/1exindex. Sort Texinfo index files,
makoinfo: (texlnfo)nakelnfo Preferred. Translate Tesinfo sou.-c-.
MA iscellaneous
s: (as). The GHUassembler.
Autoconf: (autoconf). Create source cod odee configuration scripts,
scripts.
Bid: (bfd).
nfd: The Binary File Descriptor libiary.
Hinútilo: (blnutlls).
ninutils: Th
'-be O
CHU b in ary
ddump", "strip",utilities
"nm". "nlm "ar" , •cbloopy",
cb icopy".
•'ttrings”, anodon"ran
v", "cise”,
llb".
Filo util itios: (fileutils). -GIWf1le liti 1it les.
Finding Filan: (find). L.sling
that m sudcertnln
opei .tini on ill«-.
GIT: (git). OKUInteractive Taools
tch elicerla.
Gdb: fgdb). The GNUdebugger.
G
I.ddb-Intein.ils:
: (Id). (gdblnt I. ThT'-hG
.Hn
Uindiebiui»i~.
rgei’* Internals.
Into:
1832 Día 22

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.

Los té rm in o s shell e intérprete de com andos son equivalentes. Se


utiliza rá n indistintamente, a menos que surja alguna am bigüedad
entre un p ro gram a para el shell y el shell mismo.

Esta lección trata los siguientes conceptos básicos:


836 Día 23

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

Shells disponibles en Linux


Piense que el shell es un programa que está entre el usuario y el sistema operativo. Este
programa implementa el lenguaje que utiliza el usuario para controlar el sistema operativo.
El shell se inicia de manera automática al iniciar una sesión en el sistema.
El shell original disponible en los sistemas UNIX era el shell Bourne (”sh”). Después,
dos shells que se volvieron populares fueron el shell C (“csh”) y el shell Korn (“ksh”).
Cada uno tiene características únicas y muy útiles.
El shell Bourne fue reescrito, y la nueva versión se conoce como “Bourne again shell (Shell
Bourne nuevamente)”, o “bash”. El shell C fue reescrito y nombrado shell T (“tcsh”). Los
tres shells están disponibles en Linux: bash, tcsh y ksh. Probablemente, bash es el shell que
se utiliza con más frecuencia, y en muchas instalaciones es el shell predeterminado.

Operación de los shells y conceptos


básicos de sintaxis
El shell realiza varias funciones importantes.
Programación shell 837

En primer lugar, es el programa que le permite interactuar con el sistema operativo. La


“línea de comandos” es la entrada del usuario para el shell. El shell examina la línea de
comandos, determina si lo que se ha escrito es el nombre de un programa (un programa
binario compilado), y de ser así, envía ese programa al kemel o núcleo para su ejecución.
Si la línea de comandos contiene el nombre de un archivo de secuencia de comandos
para algún shell, entonces se llama al shell apropiado que se encarga de interpretar la
secuencia de comandos que tiene como entrada.
Todos los comandos de shell utilizan el siguiente formato general:
comando opcionl opcion2 opcion3 ... opcionN argumentol .. . argumentoM
Esta línea se conoce como línea de comandos. Una línea de comandos consiste en un
nombre de comando y una o más opciones (o argumentos). Por lo general, el espacio en
blanco de la línea de comandos se ignora. Los nombres de comandos y los argumentos
son generalmente sensibles al uso de mayúsculas y minúsculas. El comando se termina
oprimiendo la tecla “Entrar”. Puede continuar un comando en una nueva línea usando la
barra diagonal inversa (\):
nombre-de-comando-muy-largo opcion_larga_1 opcion_larga_2 \
opcion_larga_3 . . . opcionN
Puede concatenar más de un comando en una línea con un signo de punto y coma (;),
como se muestra a continuación:
comando_1 opcion argumento; comando_2 opcion argumento; comando_3 opcion argumento

Tal vez le parezca útil la siguiente secuencia:


olear; pwd; ls
Esto limpiará la ventana de terminal, y luego, en la ventana limpia, imprimirá
el nombre del directorio actual y una lista de todos los archivos que estén en
ese directorio.

Características del shell


Los shells tienen demasiadas características como para hablar detalladamente sobre ellas
aquí. Esta sección habla sobre los fundamentos de los shells en general. Estas caracterís­
ticas están presentes en casi todos los shells. La sintaxis exacta puede diferir de un shell
a otro, y por ende los ejemplos específicos se limitan a bash. Así pues, usted notará que
cada uno de los shells populares agrega alguna característica que tal vez no se encuentre
disponible en los demás.
Todos los shells tienen entradas completas y concisas en las páginas del manual. Además,
el archivo info para bash contiene aproximadamente 3,000 líneas. Para obtener información
más específica o avanzada, debe consultar esta documentación en línea.
|838 Día 23

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

Para reemplazar el /usr/include/ al principio de cada nombre de archivo, puede utilizar


el comando sed:
sed s# /usr/include/#+ #' < Listalnclude > Listalncludes

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

El comando sort Listalnclude > ListaOrdenada no está redireccionando la


entrada. Muchos comandos tienen como entrada los archivos que se escriben
en su lista de argumentos (no de opciones), y en caso de no haber argumen­
tos leen de la entrada estándar. Éste es el caso de sort y de varios comandos
más que se conocen como "filtros". Para el usuario será equivalente escribir
sort Listalnclude y sort < Listalnclude. Sin embargo, para el intérpre­
te, sort Listalnclude es un comando completo y lo envía al núcleo directa­
mente; por el contrario, sort < Listalnclude contiene un redireccionamien-
to "<" que el shell debe interpretar antes de enviar el comando al núcleo.

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

El carácter de tubería ( ¡) encadena dos comandos y conecta (redirecciona) la salida estándar


del primero a la entrada estándar del segundo. Una sola línea de comando puede contener
cualquier número de tuberías:
grep -1 "#include" /usr/include/*.h ¡ sort ¡ sed 's#Vusr/include/#+ #' >
SortedModifiedList
|840 Día 23

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

Para establecer una variable de entorno, la sintaxis es


export N0MBRE=valor

Si la variable incluye espacios en blanco o cualquier otro carácter especial, el valor se


puede encerrar entre comillas sencillas o dobles. A menudo es útil agregar valores a una
variable ya existente. Puede hacerlo de la siguiente manera:
export N0MBRE= "$N0MBRE nueva_cadena"
Esto agrega nueva_cadena” a la variable de entorno NOMBRE.

Variables utilizadas por el shell


Ciertas variables de entorno tienen un significado especial y son utilizadas por el shell. A
continuación se muestra una lista parcial de algunas de las variables más notables.
• DISPLAY— Esta variable la leen los programas X para saber en dónde desplegar su
salida. Por lo general se establece en “ :0 .0 ”, lo que significa que la salida se des­
plegará en la primera terminal gráfica (virtual) del equipo host.
• PATH — Una lista de nombres de directorios separados por el signo de dos puntos (:),
en la que el shell debe buscar los programas. Cuando usted escribe cualquier nombre
de comando, el shell buscará en estos directorios un programa con ese nombre.
• TERM — El tipo de terminal o de emulación de terminal. Los programas, como los
editores, deben saber el tipo de terminal para poder enviar los comandos apropia­
dos para manipular la pantalla y el cursor.
Programación shell 841 |

• PS1— El indicador desplegado por el shell para indicar al usuario que está listo para
recibir entrada.
• HOME— El directorio personal del usuario.

Variables establecidas por el shell


Ciertas variables son establecidas por el shell y pueden ser referenciadas por programas
iniciados desde el shell. A continuación se muestra una lista parcial de variables importantes
establecidas por el shell:
• SHELL—Contiene el nombre completo de la ruta del shell actual (por ejemplo,
/bin/bash).
• PWD— El directorio actual que haya sido establecido por el comando cd más
reciente.

Procesamiento en segundo plano,


suspensión y control de procesos
Por lo general, usted escribe el nombre de un comando en la línea de comandos y espera
a que ese comando termine. Suponga que el comando que ejecuta va a tardar mucho. En
lugar de esperar, podría abrir otra ventana xterm y continuar trabajando ahí. Sin embargo,
en vez de eso puede utilizar la naturaleza multitareas de Linux. Puede ejecutar el coman­
do en segundo plano con el carácter especial “&” de la siguiente manera:
bash# find . -ñame '*.c' -print ¡ sort > ListaOrdenada &
[1] 142
bash#

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

El mismo mecanismo funciona si está especificando un nombre de archivo como argumento


para un comando.
Por ejemplo, escriba mo, oprima ‘Tab”, y el shell deberá emitir un bip. Oprima “Tab” otra
vez y deberá aparecer una lista de comandos que empiecen con “mo”. Escriba r y oprima
“Tab” otra vez. El shell deberá completar el comando more. Ahora oprima la barra espa­
dadora, luego escriba /e tc /in y oprima ‘Tab”. El shell emitirá un bip. Oprima ‘Tab” de
nuevo y se mostrará una lista de archivos en /etc que empiecen con “in”. Escriba e y luego
oprima “Tab”. Se completará el nombre de archivo inetd.conf.

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.

Sustitución mediante comodines


En muchos juegos de cartas se puede utilizar un comodín para reemplazar cualquier otra
carta. De la misma manera, en el shell se puede utilizar un comodín para guardar un lugar
que el shell puede reemplazar con cualquier otro carácter o caracteres.
Existen dos caracteres comodines importantes: el asterisco (*), para representar cualquier
secuencia de cero o más caracteres de un nombre de archivo, y el signo de interrogación
(?), que representa cualquier carácter individual.
¿Qué significa esto? Tal vez usted quiera ejecutar el comando ls en el directorio actual
para ver una lista de todos los nombres de archivos que terminen con “.h”. Puede simple­
mente escribir ls *. h. El shell expandirá el carácter comodín a una lista de todos los
archivos que terminen con “.h” antes de invocar el comando ls. La lista completa de
nombres de archivo se pasará al comando.
Tal vez quiera ver todos los nombres de archivos que contengan exactamente tres caracteres
antes de “.h”. Para esto, puede escribir ls ???.h.
En bash, la sustitución de comodines es mucho más sofisticada que en DOS. bash no tiene
problemas para expandir “a??def*.xyz” a una lista de nombres de archivos que empiecen
con una “a” seguida por 2 caracteres cualesquiera, seguidos por “def’, por cero o más
caracteres y que terminen con “.xyz”.

Es importante recordar que los comodines (y cualquier otra sustitución


descrita en el resto de esta lección) se expanden antes de que se ejecute
el comando. Es decir, el shell los interpreta antes de enviarlos al núcleo.
|844 Día 23

Sustitución mediante cadenas


bash permite la sustitución de secuencias específicas de caracteres. Existen dos estilos de
sustitución.
Puede especificar una lista separada por comas de cadenas entre llaves, y cada uñase
utilizará en orden. Por ejemplo:
bash# ls a{b,c,de,fgh}z
abz acz adez afghz
bash#

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#

Los corchetes también pueden especificar caracteres específicos:


bash# ls a[bede]z
abz acz adz aez
bash#

Por último, puede mezclar comodines, llaves y corchetes.

Sustitución mediante la salida de un comando


Otra forma de sustitución es mediante la salida de un comando. La salida de un comando
se puede especificar como argumento para otro comando:
bash# ls -1 find /usr/src -ñame Makefile -print'

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.

Sustitución mediante variables


Una última forma útil de sustitución es mediante variables. El shell puede reemplazar una
variable con su valor en la línea de comandos.
Por ejemplo, suponga que su directorio personal es /home/nombreusuario:

bash# echo ${HOME}


/home/nombreusuario
bash#

El valor de la variable de entorno llamada “HOME” reemplaza la cadena ${H0ME} en el


argumento de la línea de comandos.
Programación shell 845

Historial y edición de comandos


bash mantiene una lista de los comandos que usted ha escrito en lo que se conoce como
una lista del historial. La longitud de la lista depende del entorno, pero por lo general no
es mayor de 500 comandos, bash preservará esta lista de sesión en sesión. Si escribe el
comando history, se mostrará la lista de comandos que ha escrito. Por ejemplo, considere
la siguiente secuencia de comandos:
bash# cd /
bash# cd /tmp
bash# cd
bash# cat .bashrc > /dev/null
bash# history
1 cd /
2 cd /tmp
3 cd
4 cat .bashrc > /dev/null
5 history

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.

Creación de alias de comandos


Probablemente utilice con frecuencia ciertos comandos o secuencias de comandos. Puede
crear sus propios métodos abreviados para estos comandos, lo que se conoce como alias.
El shell reemplaza un alias con su definición.
Por ejemplo, el comando ls le muestra los nombres de archivos. Con la opción de línea
de comandos -F, se indica también el tipo del archivo (“*” para ejecutables, “/” para
directorios, etcétera). Puede crear un alias para sustituir comandos de la siguiente manera:
bash# ls -F
a b* c* d e f / g/
bash# ls
a b c d e f g
bash# alias ls ="lS -F"
bash# ls
a b* c* d e f / g/
bash#
846 Día 23

El comando ls con y sin el argumento -F muestra sus resultados de manera diferente. Al


crear un alias para ls, y después cuando se escribe el comando Is, el shell sustituye el alias
automáticamente: l s -F.
El comando rm elimina un archivo. Con la opción -i, primero pedirá la confirmación de
la operación. Muchas personas encuentran útil sustituir el comando rm para evitar eliminar
accidentalmente un archivo: a lia s rm="rm - i ". Las comillas en este alias de comando
son importantes, ya que agrupan todo el lado que está a la Jerecha del signo de igualdad
como un solo argumento para el alias del comando. Sin las comillas, el alias del comando
trataría de interpretar los caracteres - i como una opción.

Secuencias de comandos de los shells


En un archivo se pueden colocar secuencias complejas de comandos de shell, para que se
puedan repetir en cualquier momento. Esto es muy parecido a la escritura de un programa,
sólo que no se necesita la compilación. El shell tiene muchas de las características de los
lenguajes estándar, incluyendo variables e instrucciones de control (de las que aún no hemos
hablado).
Casi todas las secuencias de comandos de shell empiezan con #! /bin/sh (o con el intér­
prete de comandos deseado). Los primeros dos caracteres indican al sistema que éste es
un archivo de secuencia de comandos, y /bin/sh inicia el shell bash. Esto se debe a que
/bin/sh es un vínculo al programa /bin/bash; si se desea utilizar otro intérprete, se debe
especificar en esta línea. Por ejemplo, los archivos de secuencias de comandos para tcsh
deberán tener como primera línea #! /bin/csh o #! /bin/tcsh y las secuencias de comandos
para perl incluirán la línea #! /usr/bin/perl . Después de iniciar el shell, éste recibe, una
por una, las líneas restantes del archivo.
Las secuencias de comandos de shell pueden ser programas simples de una línea, o com­
plejos con cientos de líneas. No son tan rápidos y eficientes como los programas de
lenguajes compilados.
Las secuencias de comandos de shell deben tener encendido su bit de permiso de “ejecu­
ción . Puede encender este bit con el comando: chmod a+x nombrearchivo.

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.

En t r a d a L istado 2 3 .1 Principales instrucciones de control de bash


1 #!/bin/bash
2
3 MAX=9
4
5 #
6 # Muestra del control 'if-endif'
7 if [ $# -gt $MAX ]
8 then
9 echo "$0 : $MAX o menos argumentos requeridos'
10 exit 1
11 f i
12
13 #
14 # imprimir los primeros 2 argumentos
15 echo "$0 : Parámetro 2 : $2“
16 echo "$0 : Parámetro 1 : $1"
17 echo ""
18
19
20 # Muestra del control 'for-done1
21 for i in $1 $2
22 do
23 Is -1 $i;
24 done
25 echo ""
26
27
28 # muestra del control 'case'
29 echo "ejemplo de case"
30 for i
31 do
32 case "$i" in
33 a) echo "case a";;
34 b) echo "case b";;
35 *) echo "default case $i";;
36 esac
37 done
38 echo ""
39
40 #
41 # muestra del control 'while-done'
continúa
848 Día 23

L is t a d o 2 3 .1 c o n t in u a c ió n

42: echo “ejemplo de while"


43: i=1;
44: while [ $i -le $# ]
45: do
46: echo $i;
47: i=$[$i+1];
48: done
49: echo ““
50:
51: #
52: # muestra del control 'until-done'
53: echo "ejemplo de until"
54: i=1;
55: until [ $i -gt $# ]
56: do
57: echo "argumento $i";
58: i=$[$i+1];
59: done
60: echo ""
61:
62: exit 0*
7
6
5
4
3
2
1

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

-rw-r — r — 1 hal hal 0 Jan 19 22:33 a


-rw-r — r - 1 hal hal 0 Jan 19 22:33 b

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#

La línea “MAX=9" asigna el valor 9 a la variable de shell llamada MAX. El


A nálisis
grupo de líneas “if-fi" que le sigue comprueba el número de argumentos de línea
de comandos. Si hay más de 9. entonces se imprime un mensaje y la secuencia de
comandos termina.
Las instrucciones “echo" muestran cómo se pueden referenciar los argumentos de línea de
comandos. El shell sustituye todos los argumentos de la línea de comandos (incluyendo
el nombre del comando) por las variables $0. $L $2 y así sucesivamente.
Las siguientes agrupaciones de comandos muestran las principales instrucciones de control
disponibles en el shell. Entre éstas se incluyen ‘for-do-done’ (similar a un ciclo ‘for de C),
‘case-esac' (similar a la instrucción ‘switch’ de C), ‘while-do-done' (similar al ciclo Avhile'
de C) y 'until-do-done' (similar al 'do-while' de C). Consulte las páginas del manual de bash
para más detalles acerca de la sintaxis y el uso de éstas y de otras instrucciones de control.
El comando 'touch a b' creará los archivos a y b vacíos, es decir, sólo creará la entrada en
el directorio pero no colocará nada dentro de ellos. El comando ‘ls -F’ muestra que hay 2
archivos (llamados a y b) en el directorio actual, además de la secuencia de comandos de
shell llamada ‘Ist23-01 .sh. Luego se invoca la secuencia de comandos con 7 argumentos.
Los primeros 2 argumentos son los nombres de los 2 archivos.

Archivo(s) de inicio de sheli


Un shell interactivo (a diferencia de un shell no interactivo, como el que se inicia para
una secuencia de comandos) es uno en el que se conecta la entrada estándar con el teclado
y la salida estándar con la pantalla. Si bash es el shell predeterminado para el usuario, al
momento del inicio de sesión se inicia un shell bash interactivo. Si se ejecuta una secuencia
de comandos para el shell bash, se inicia un shell no interactivo y recibe la secuencia de
comandos una línea a la vez.
Al momento del inicio de sesión, el shell busca una secuencia de comandos llamada
/ e t c / p r o f i l e . Si encuentra esa secuencia de comandos, la ejecuta. Si el archivo
. bash_prof i l e existe en el directorio personal del usuario, se ejecuta. Si no existe, se
ejecuta .p r o fi l e .
Al momento de cerrar la sesión, se ejecuta el archivo . bash_logout que se encuentra en
el directorio personal, si es que existe.
|850 Día 23

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

memoria se muestra en la figura 24.1. La información sobre el estado de ejecución del


proceso se conoce como contexto del proceso. L1 contexto del proceso consiste en el
espacio de direcciones, espacio de la pila, contenido de los registros, estructuras de datos
del núcleo y el estado del proceso (mismo que no debe confundir con el estado de
ejecución del proceso).
Cada proceso de Linux se ejecuta en su propio entorno, y cada proceso tiene su propia
memoria virtual y espacio de direcciones. Hn Linux no es posible afectar indirectamente
la ejecución de otro proceso.

Fig u r a 24.1
Diagrama de un pila

proceso de Linux.
heap

datos

texto

Creación y terminación de procesos


Un proceso nuevo se crea cuando un programa en ejecución utiliza la llamada de sistema
conocida como f ork (). f ork () crea un proceso nuevo y luego copia el entorno del proceso
padre en el proceso hijo recién creado.

El programa que llama a fork() se conoce como proceso padre. El proceso


recién creado se conoce como hijo.

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

Entrada L is t a d o 2 4 .1 Lista d o para la clase Process

1: // Listado 24.1 Clase Process (process.h)


2:
3: #ifndef C_PROCESS_H
4: /¿define C_PROCESS_H
5;
6: c la s s Process
7: {
8: public:
9: Process();
10: ~Process();
11: void Create();
12: bool isParent()
13: { return (pid != 0); }
14: bool isChild()
15: { return (pid == 0); }
16: private:
17: Process & operator= (const Process &); // no permitir la copia
18: int pid;
19: };
20:
21: #endif

La función miembro Process: :Create () de la línea 11 llama a fork() para crear


A nálisis
un nuevo proceso. Para determinar cuál proceso se está ejecutando, si el padre o el
hijo, se proporcionan dos funciones receptoras. Process: :isParent( ), en la línea 12, y
Process: : i s C h i l d ( ), en la línea 14. Debido a que un proceso tiene información del sis­
tema y datos del núcleo, esta clase necesita evitar explícitamente que un usuario copie la
clase. Esto se logra haciendo que el constructor de copia sea privado.
856 Día 24

En los programas de sistema es común utilizar recursos que pertenecen al


kernel. Como la copia de recursos del kernel puede producir efectos inde­
seables, todas las clases definen al constructor de copia como función miem­
bro privada sin implementación. Al hacer esto, garantiza que los usuarios de
la clase no puedan tener acceso al constructor de copia, y hace que las fun­
ciones miembro puedan compilar el código pero no enlazarlo.

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.

:^rr\ Listado 2 4 .3 Código de ejemplo usando funciones miembro para control


I[Íj?3ÍIjj^gyA\I de procesos
1: // Listado 24.3 Ejemplo de control de procesos
2:
3: //include "lst24-02.h" ////include "processi.h"
4:
5: int main(int argc, char** argv)
6: {
7: Process p;
8: char * program = "Is";
9:
10: p .Create() ;
11 : i f (p. isParent())
12: p.WaitForChildj) ;
13: else i f (p.isChild() )
continúa
858 Día 24

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.

El sistem a de arch ivo s /p ro c


Linux proporciona un sistema de archivos /proc, el cual permite a los usuarios ver infor­
mación sobre el sistema y los procesos individuales en ejecución, /proc no es un sistema
de archivos convencional, sino una estructura en la memoria mantenida por el kernel que
proporciona estadísticas del sistema. Un usuario o un proceso pueden obtener información
sobre el sistema y los procesos mediante la emisión de llamadas de lectura y escritura al
sistema de archivos /proc.
/proc se representa como un directorio de archivos jerárquico. El directorio /proc contiene
una entrada de archivo para cada proceso en ejecución, y cada proceso tiene su propio
subdirectorio nombrado por su ID de proceso. Además de los procesos individuales, /proc
contiene parámetros del sistema e información de los recursos. Un programa o comando
del sistema puede leer los directorios y archivos correspondientes a un proceso para ver
información del kernel.
Puede consultar a /proc en cualquier momento para obtener información acerca del sistema.
Programación de sistemas 859

El form ato de los datos presentados en el sistema de archivos /proc puede


cambiar para distintas versiones del kernel. Es muy recomendable que se
familiarice con las estructuras de datos del kernel antes de leer acerca del
sistema de archivos /proc.

Estado y p rio ridad del proceso


Antes de hablar sobre la programación de procesos, necesita tener una comprensión básica
del estado y de la prioridad de un proceso.
Durante el tiempo de vida de un proceso, éste pasa por muchas transiciones de estado. El
estado del proceso es el estado, o modo, del proceso en un tiempo determinado. Los estados
del proceso se pueden encontrar en el archivo de encabezado del kernel de Linux llamado 2 4
sched.h y se definen a continuación.
• T A S K _ r u n n i n g —El p ro c e so está esperando ser ejecutado.

• t a s k _ in t e r r u p t ib l e —La tarea está en ejecución y puede ser interrumpida.


• t a s k _ u n i n t e r r u p t i b l e —La tarea está en ejecución y no puede ser interrumpida.
• T A S K _ Z O M B l E—La tarea se ha detenido, pero el sistema aún considera que está en
ejecución.
• TASK_STO PPED —El proceso se ha detenido, por lo general, después de haber recibido
una señal.
• T A S K _ S W A P P IN G — El
núcleo está intercambiando información de la tarea entre la
memoria y la partición de intercambio.
La prioridad del proceso es un entero asignado a un proceso o subproceso en el que su valor
se encuentra en un rango definido por la directiva de programación de tareas. Cuando se
crea un proceso, se asigna un valor de prioridad estático. El valor de la prioridad del proceso
es la cantidad de tiempo (en “jiffies”) que el programador de tareas da a este proceso para
que se ejecute cuando se le permita hacerlo. Un “jiffy” es la duración de un pulso de reloj
en el sistema. La cantidad de tiempo en la que se permite que se ejecute un proceso se
conoce como cuanto.
Existen dos tipos de procesos en Linux, normales y de tiempo real. Los procesos normales
tienen una prioridad de 0. Un proceso de tiempo real tiene prioridades en el rango de 1-99.
Los procesos de tiempo real están programados para tener una prioridad mayor que cual­
quiera de los demás procesos del sistema. La prioridad de un proceso sólo puede alterarse
mediante el uso de llamadas de sistema, como nice(). Si existe un proceso en tiempo real
listo para ejecutarse, siempre se ejecutará primero. La llamada a nice( ) cambia la priori­
dad de un proceso, nice () toma como argumento un entero no negativo que se suma a la
prioridad actual del proceso.
860 Día 24

A lg o ritm o s de a d m in is tra c ió n de procesos


El recurso más preciado de un sistema operativo es el tiempo de la CPU. Para utilizar
eficientemente el tiempo de la CPU. los sistemas operativos utilizan un concepto conoci­
do como m ultiprocesam iento. Este concepto le da al usuario la impresión de que se están
ejecutando muchos procesos en forma simultánea. Id objetivo del multiprocesamientoes
tener un proceso en ejecución en todo momento para que la CPU siempre esté ocupada.
Esto garantiza que la CPU se esté utilizando de la manera más eficiente.
La implementación del multiprocesamiento de un sistema operativo consiste en dividir el
tiempo de la CPU entre muchos procesos. El p ro g ra m a d o r de tarcas (seheduler) es el pro­
ceso del kernel que determina cuál proceso se ejecutará a continuación. El programador
de tareas decide cuál proceso ejecutar, de todos los que se encuentran en el estado TASK_
RUNNING. El criterio, o las reglas, que determinan cuál proceso se va a ejecutar a continua­
ción se conoce como algoritm o de adm inistración.
El programador de tareas de Linux utiliza la prioridad del proceso y la directiva del proceso
para determinar cuál proceso se va a ejecutar a continuación.
Existen tres tipos de directivas de proceso: otros, PIFO (Primero en Entrar. Primero en
Salir), y RR (por petición). Un proceso normal tiene un tipo de directiva: otros. Los pro­
cesos de tiempo real tienen dos tipos de directivas: RR y PIFO.
La directiva otros se implementa como un algoritmo estándar de programación de tareas de
tiempo compartido. Ésa es una directiva en la que a cada proceso se le da un cuanto igual.
La directiva FIFO programa cada proceso ejecutable en el orden en que se encuentra en la
cola de ejecución, y ese orden nunca se cambia. Un proceso FIFO se ejecutará hasta que
se bloquee en la E/S o se intercambie por un proceso con mayor prioridad.
La programación RR ejecuta los procesos de tiempo real uno por uno. La diferencia entre
un proceso FIFO y un proceso RR es que éste se ejecutará durante un tiempo especificado
(cuanto) y luego se intercambiará y se colocará al final de la lista de prioridad.

Un usuario debe tener privilegios de su pe ru su ario o administrador (root)


para ejecutar procesos en tiem po real.

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.

Para compilar con pthreads, deber incluir el archivo de encabezado pthread,


#include <pthread.h>, y debe enlazar la biblioteca pthread. Por ejemplo:
g++ hola.cxx -o hola -lpthreads.

La API de subprocesos es la misma que para pthreads, pero la implementación es distinta


de la de otros sistemas operativos. Otros sistemas operativos de subprocesamiento múltiple,
como Windows 2000, definen un subproceso como algo separado de un proceso. Linux­
Threads define un subproceso como un contexto de ejecución.
En otros sistemas operativos, los subprocesos se definen como el contexto de la CPU, mien­
tras que el proceso posee la pila, los datos y el heap. Cuando se crea un nuevo proceso,
el subproceso se crea como parte del contexto del proceso. Esta implementación de los
subprocesos ofrece la ventaja de tener un cambio de contexto muy eficiente. Sin embargo,
esta definición de subproceso no encaja con el módulo de procesos de Linux. Por ejemplo,
si un proceso posee los recursos de la pila, los datos y el heap, ¿cómo se implementaría
la llamada de sistema fork() cuando se llame desde un subproceso? LinuxThreads tiene la
ventaja de un cambio rápido de contexto de un subproceso tradicional además de que
puede definir cuáles recursos del proceso se pueden compartir.
Los subprocesos se implementan mediante la llamada de sistema_clone.__clone ()
toma como argumento indicadores que especifican cuál sección se va a compartir entre el
proceso y el subproceso. Los recursos que se pueden compartir se definen en el archivo de
encabezado del kernel de Linux llamado schedbits.h, y se muestran a continuación:
• c l o n e _ v m —Compartir los datos y la pila
• CLONE FS —Compartir el sistema de archivos
862 Día 24

° CLONE_FILES—Compartir archivos abiertos


° CLONE_SIGHAND—Compartir señales
° CL0NE_PID—Compartir PID del padre (no esta completamente implementado)

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.

C reació n y te rm in a ció n de sub p ro ceso s


Un subproceso se crea por medio de la llamada de sistema pthread_create () y se destruye
usando la llamada de sistema pthread_exit (). Estas llamadas de sistema están encapsu­
ladas en la clase Thread, que se define en el listado 24.4.

Entrada L is t a d o 2 4 . 4 O b je to T h re a d

1: // Listado 24.4 Clase Thread (tcreate.h)


2:
3: #ifndef C_TCREATE_H
4: ^define C_TCREATE_H
5:
6: #include <pthread.h>
7:
8: c la ss Thread
9: (
10: public:
11: Thread(void * r, void * a); // ctor/dtor
12: v ir t u a l -Thread();
13: int Create(); // crea el subproceso
14: int Destroy(); // sale del subproceso
15: priv ate :
16: Thread & o p e r a t o r (const Thread &) ; // no permitir la copia
17: pthread_t thread;
P r o g r a m a c ió n d e sistem as

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

1: // L is t a d o 24.5 Ejemplo de productor/consumidor


2:
3: //include <iostream.h>
4: //include " l s t 2 4 - 0 4 . h " ////include "tcreate.h"
5:
6: i n t data = 0;
7:
8: v o id read_thread(void * param)
9: {
10 : w h ile (1)
11 : cout « " l e e r : " « data « endl;
12: }
13:
14: vo id w rit e _ t h re a d (void * param)
15: {
16: w h ile (1)
17: cout « " e s c r i b i r : " « data++ « endl;
18: }
19:
2 0 : i n t m a in (in t arge, char** argv)
21: {
22: Thread threadl ( (void*)&write_thread, NULL);
23: Thread thread2( ( void*)&read_thread, NULL);
24:
25: t h r e a d l .C re a t e ();
26: t h r e a d 2 .C re a t e ();
27: f o r ( in t i = 0; i < 10000; i++)
28: ;
29: t h r e a d l . D e s t r o y ( );
30: th r e a d 2 . D e s t r o y ( );
31: }
A n á l is is
El listado 24.5 demuestra el objeto Thread usando el clásico ejemplo productor/-
consumidor en acción. Se crean los objetos threadi y thread2 de la clase Thread.
threadl utiliza la función write_thread definida en las líneas 14 a 18; éste es el produc­
tor. thread2 utiliza la función read thread definida en las líneas 8 a 12; éste es el consumi­
dor. Los subprocesos empiezan su ejecución cuando se hace la llamada a las funciones
miembro Thread: :Create(). read_thread. el consumidor, lee de los datos de las variables
globales, y write_thread, el productor, escribe en los datos de las variables globales.

A l ig u a l q u e co n los p ro ce so s, n u n c a h a y g a r a n t ía d e cuál subproceso se


ejecutará p rim ero, th r e a d l o th re a d 2.

¿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

En trad a L is t a d o 2 4 .6 Clase para el bloqueo síncrono

1: // L is t a d o 24.6 Clase v ir t u a l pura SyncLock (synclock.h)


2:
3: # if n d e f C_SYNCLOCK_H
4: #d efine C_SYNCLOCK_H
5:
6: c l a s s SyncLock
7: {
8: p u b lic :
9: S y n c L o c k () {} ;
10: ~SyncLock() {};
11: v i r t u a l i n t Acq uire() = 0;
12: v i r t u a l i n t Release() = 0;
13: p r iv a t e :
14: SyncLock & operator= (const SyncLock &) ; // no permitir la copia
15: }; 2 4
16:
17: tfendif

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.

D ada la definición de SyncLock, el listado 24.7 muestra la implementación de un mutex


usando la interfaz SyncLock.

En trad a L is t a d o 2 4 . 7 Clase Mutex

1: // L is t a d o 24.7 Clase Mutex (mutex.h)


2:
3: # if n d e f C_MUTEX_H
4: ^ d e fin e C_MUTEX_H
5:
6: # in c lu d e <pthread.h>
7: tfinclude " l s t 2 4 - 0 6 . h " //^include "synclock.h
8:
9: c l a s s Mutex: p u b lic SyncLock
10: {
11: p u b lic :
12: M ute x(); // ctor/dtor
13: -M u t e x ();
14: i n t C r e a t e ( );
15: i n t D e s t r o y ( );
16: i n t A c q u ir e ( ) ; // obtener el bloqueo
continúa
866 Día 24

L is t a d o 2 4 .7 continuación

17: int ReleaseO; // liberar el bloqueo


18: private:
19: Mutex & operator= (const Mutex &); // no permitir la copia
20: bool init;
21: pthread_mutex_t mutex;
22: pthread_mutexattr_t attr;
23: >;
24:
25: #endif

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

1: // Listado 24.8 Ejemplo de productor/consumidor con Mutex


2:
3: //include <iostream.h>
4: //include "lst24-04.h" // //include "tcreate.h"
5: //include "lst24-07.h" // //include "mutex.h"
6:
7: int data;
8:
9: void read_thread(void * param)
10: {
11: Mutex * apMutex = static_cast< Mutex * >(param);
12:
13: while (1)
14: {
15: apMutex->Acquire();
16: cout « "leer: 11 « data « endl;
17: apMutex->Release();
18: }
19: }
20:
21: void write_thread(void * param)
P r o g r a m a c ió n d e sistem as 867

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().

É ste es un e je m p lo de código reentrante.

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;

extern int sem_wait(sem_t * __sem) __ THROW;

extern int sem_trywait(sem_t * __sem) __ THROW;

extern int sem_post(sem_t * __sem) __ THROW;

extern int sem_getvalue(sem_t * __sem, int * __sval) __ THROW;

extern int sem_destroy(sem_t * __sem) __ 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.

E l paradigm a esperar/notificar se puede im plem entar por m edio de primitivas de sincro­


nización de LinuxThreads; por ejem plo, en el listado 2 4 .9 declaramos una clase base virtual
para esta interfaz.

En trad a L is t a d o 2 4 . 9 Definición del objeto Event


1 // Listado 24.9 Clase vir (event.h)
2
3 #ifndef C_EVENT H
4 #define C_EVENT_H
5
6 class Event
7 {
8 public:
9 Event() {>;
1 0 : virtual -Evento {};
11 : virtual int Wait() = 0 ;
1 2 : virtual int Signal() = 0;
13: };
14:
15: #endif
P r o g r a m a c ió n d e sistem as 869

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.

Entrada L is t a d o 2 4 . 1 0 Objeto Variable de condición

1: // L is t a d o 24.10 Clase variable de condición (cond.h)


2:
3: # i f n d e f C_C0ND_H
4: # d e fin e C_C0ND_H
5:
6: //include <pthread.h>
7: //include " l s t 2 4 - 0 9 . h " //l//include “event.h"
8:
9: c l a s s CondVar : pu blic Event
10: {
11: p u b lic :
12: CondVar();
13: v i r t u a l -CondVar();
14: vo id C r e a t e ( );
15: vo id D e s t r o y ();
16: i n t W a i t ( );
17: i n t S i g n a l ();
18: p r iv a t e :
19: CondVar & operator= (const CondVar &); // no permitir la copia
20: pthread_cond_t cond;
21 : pthread_condattr_t attr;
22: pthread_mutex_t mutex;
23: pthread_mutexattr_t mattr;
24: };
25:
26: //endif

El listado 24.10 define el objeto CondVar usando la interfaz Event definida


A n á l is is
anteriorm ente. Un subproceso utilizaría la variable CondVar como primitiva de
sincronización en un esquema similar al de Mutex. Cuando el subproceso tuviera acceso
a los datos, llam aría a la función miembro CondVar: :W ait(). Al completar su actualiza­
ción. liberaría al objeto CondVar mediante una llamada a CondVar: : S i g n a l ( ).
| 870 Día 24

Es im p o rta n te q u e la v a ria b le d e c o n d ic ió n se inicialice co m o señalada, o se


te n d ría u n a c o n d ic ió n d e p u n t o m u e rto . Esta im p le m e n ta c ió n de CondVar
s ie m p r e inicializa la v a ria b le d e c o n d ic ió n c o m o seña lada.

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.

La seg u n d a m itad de la lección de hoy trató sobre la creación y la destrucción de los


subprocesos y sobre las primitivas de sincronización de subprocesos.

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 ?

R Al ad q u irir un m utex, utilice un valor de interrupción. Si no se puede adquirir el


m utex, la llam ada de interrupción permitirá que el proceso o subproceso continúe.
P ¿ C ó m o se p o d r ía e vitar un abrazo mortal?
R Al adquirir varios mutex, siempre adquiéralos en el mismo orden y libérelos en el
orden inverso al de la adquisición.

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.

Entrada L is t a d o 2 5 .1 Interfaz Object


1: // Listado 25.1 Clase Object (object.h)
2:
3: tfifndef C_0BJECT_H
4: tfdefine C_0BJECT_H
5:
6: class Object
7: {
8: public:
9: virtual int CreateO = 0;
10: virtual int DestroyO = 0;
11: };
12:
13: tfendif

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

Un descriptor de archivos es un entero utilizado para referirse a un archivo abierto por el


kemel. Cuando un proceso necesita leer o escribir en un archivo, el archivo se identifica
por el descriptor de archivo.

C o m o vio en el día 24, "Programación de sistemas", un proceso hijo hereda


los archivos abiertos de los procesos padres (es decir, se copian los m anejadores
de archivo al proceso hijo). Esto incluye los manejadores utilizados para las
tuberías.

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.

Figura 25.1 proceso proceso


Tubería entre dos
procesos.
kemel

tubería

Después de que se crea la tubería y se ha completado la configuración necesaria, la


comunicación se logra mediante el uso de llamadas estándar de lectura y de escritura.
De manera predeterminada, las tuberías utilizan bloqueo de E/S. Es decir, si una tubería
realiza una lectura y no hay datos que leer, la tubería se bloquea hasta que hayan datos
disponibles. Si un usuario no quiere bloquear, la tubería se puede abrir con el indicador 0
NO NBLO CK para deshabilitar el bloqueo de E/S.

El listado 2 5 .2 define la clase Pipe. Esta clase encapsula las llamadas de sistema para
tuberías.

Entrada L is t a d o 25.2 D e fin ic ió n d e la clase P ip e

1: // Listado 25.2 Clase Pipe (pipe.h)


2:
3: #ifndef C PIPE H
continúo
1876 Día 25

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

Tuberías con nombre (FIFOs)


U na tubería con nombre es un método que permite que procesos independientes se com uni­
quen. Las tuberías con nombre se conocen tradicionalmente en el mundo UNIX como
FIFO s (Prim ero en Entrar, Primero en Salir). Una tubería con nombre es similar a una
tubería; es una tubería semidúplex entre dos procesos. La diferencia entre una tubería y
una tubería con nom bre es que cuando los procesos terminan de usar la tubería con nom ­
bre, ésta perm anece en el sistema.

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

Po r lo ge nera l, el c o m a n d o d e shell mknod se reserva para el administrador


(root); sin e m b a rg o , c u a lq u ie r u s u a rio p u e d e crear u n a tubería con nombre
p o r m e d io d e mknod.

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

1: // Listado 25.3 Clase NamedPipe (npipe.h)


2:
3: ¿¿ifndef C_NP_H
4: #define C_NP_H
5:
6: ¿¿include <sys/types.h>
7: ¿¿include <sys/stat.h>
8: ¿¿include <stdio.h>
9: ¿¿include "lst25-01.h" // ¿¿include "object.h
10:
11: class NamedPipe : public Object
12: {
13: public:
14: NamedPipe();
15: -NamedPipe();
16: int Create();
17: int Create(char * name);
18: int Destroy();
19: int Open();
20: int Close();
21: int Read(char * buf, int len);
22: int Write(char * buf, int len);
23: private:
24: //no permitir la copia
25: NamedPipe & operator=(const NamedPipe &);
26: bool init_;
27: FILE * fp_;
28: char * name_;
29: };
30:
31: ¿¿endif

J
C o m u n ic a c ió n e n tre p ro ce s o s 879

La función C r e a t e ( ) se sobrecarga para tomar el nombre de una tubería con nom ­


A n á l is is
bre o para utilizar un valor predeterminado. Después de crear la tubería con
nom bre, se debe abrir. Después de abrir el objeto NamedPipe, se puede leer de él o escribir
en él usando las funciones miembro Read() y W rite().

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.

Comunicación entre procesos de System V


El sistem a LJNIX System V de AT&T presentó tres formas adicionales de comunicación
entre procesos (IPC ) de un mismo sistema: mensajes, semáforos y memoria compartida.
2 5
C ada uno de estos métodos de comunicación entre procesos (mensajes, semáforos y m em o­
ria com partida) se describe en esta sección, junto con una clase que demuestra su uso.

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.

El prim er paso para utilizar la comunicación entre procesos de System V es la creación de


una clave. Esta es un entero no negativo. Para que los procesos puedan tener acceso a un
m étodo de IPC, el m étodo debe utilizar una clave. La clave es del tipo key_t.

Los p ro ce so s que utilizan la comunicación entre procesos de System V deben acordar


la clave que van a usar. Existen varias formas de que los procesos acuerden el valor de la
clave. En esta sección llamaremos cliente y servidora nuestros procesos.

• 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

• El cliente y el servidor pueden acordar una clave y com partirla en un archivo de


encabezado común.
• El cliente y el servidor pueden acordar un nombre de ruta y un identificador y
realizar la llamada de sistema ftok() para generar una clave, ftok () es una lla­
mada de sistema que crea una clave con base en el nombre de archivo de entrada y
el identificador del proceso.
En el listado 25.4 desarrollamos una clase de C++ que maneja las claves de IPC de
System V. Cada uno de los objetos IPC debe tener un objeto clave para funcionar
adecuadamente.

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

1: // Listado 25.4 Clase Key (key.h)


2:
3: #ifndef C_KEY_H
4: //define C_KEY_H
5:
6: //include <sys/types.h>
7: /¿include <sys/ipc.h>
8: //include "lst25-01 .h" // //include "object.h
9:
10: class Key
11: {
12: public:
13: Key();
14: -Key();
15: int Create(char * name, char id);
16: int Create(key_t key);
17: void Destroy();
18: key_t Get(void);
19: private:
20: //no permitir la copia
21: Key & operator=(const Key &);
22: bool init_;
23: key_t key_;
24: };
25:
26: Z/endif

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

Estructura de permisos de IPC


Cada método para IPC contiene una estructura ipc_perm. Esta estructura contiene los
permisos y el propietario del objeto IPC. El listado 25.5 muestra la estructura ipc_perm
de Linux.

Entrada L is t a d o 2 5 . 5 E stru ctu ra ipc_perm de Linux

1: // Listado 24.5 Estructura de permisos de IPC


2:
3: struct ipc_perm
4: {
5: ___ kernel_key_t key;
6: ___ kernel_uid_t uid;
7: ___ kernel_gid_t gid;
8: ___ kernel_uid_t cuid;
9: ___ kernel_gid_t cgid;
10: __ kernel_mode_t mode;
11: unsigned short seq;
12: };

Cuando se crea un método de IPC, la estructura ipc_perm se llena automática­


A nálisis
mente. Durante la vida de un objeto IPC, el usuario puede modificar los campos
uid, gid y mode mediante las funciones msgctl(), semctl() o shmctl(). Para cambiar
estos campos, el usuario debió ser el creador del objeto IPC, o el superusuario. Cada una
de las llamadas al sistema de IPC que crean un método de IPC toma un argumento de
indicadores que define los permisos para ese objeto. Esos indicadores se utilizan para llenar
la estructura ipc_perm.

Comandos ipcs e ipcrm


Los métodos de IPC de System V se implementan en el kemel. Si un proceso termina
sin limpiar sus métodos de IPC, los métodos permanecerán en el kemel. Los comandos
ipcs e ipcrm se utilizan para mostrar el estado de los métodos de IPC y para limpiar
cualquier recurso de IPC restante.
El comando ipcs se utiliza para obtener el estado de todos los objetos IPC. A continuación
se muestra el resultado de usar el comando ipcs (dos segmentos de memoria compartida
en mi sistema, identificadores 78592 y 78593).

-----Segmentos memoria compartida-------


key shmid propietario perms bytes nattch estado
0x00bc614e 78592 paúl 666 1024 0
0x0000162e 78593 paúl 666 64 0

----- Matrices semáforo ------


882 Día 25

key semid propietario perms nsems estado

---- Colas de mensajes ------


key msqid propietario perms bytes utilizados mensajes
El comando ipcrm se utiliza para quitar del sistema un objeto IPC. Usando el ejemplo
de la sección anterior, utilizaremos el comando íprm para quitar uno de los segmentos de
memoria compartida. A continuación se muestra la remoción de la memoria compartida
con el identificador 78593. Para ello se utiliza el comando ipcrm. seguido del tipo del recur­
so, en este caso shm (shared memory), y por último, el o los identiPicadores a eliminar. Para
eliminar elementos de las secciones Matrices semáforo y Colas de mensajes utilice las
opciones sem y msg, respectivamente
bash# ipcrm shm 78593
resource deleted
bash#

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.

---- Segmentos memoria compartida


key shmid propietario perms bytes nattch estado
0x0000162e 78593 paúl 666 64 0
---- Matrices semáforo ------
key semid propietario perms nsems estado
---- Colas de mensajes ------
key msqid propietario perms bytes utilizados mensajes
Como puede ver, se quitó el segmento de memoria compartida. También se puede observar
que no hay colas de mensajes ni semáforos en el sistema.

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.

Cola de mensajes FIFO

Entrada L is t a d o 2 5 . 6 E stru ctu ra m sq id _ ds de Linux

1: // Listado 25.6 Una estructura msqid


2: // para cada cola en el sistema
3:
4: struct msqid_ds
5: i
6: struct ipc_perm msg_perm;
7: struct msg * msg_first; /* primer mensaje en la cola */
8: struct msg * msg_last; /* último mensaje en la cola */
9: ___ kernel_time_t msg_stime; /* última vez msgsnd */
10: ___ kernel_time_t msg_rtime; I* última vez msgrcv */
11 : ___ kernel_time_t msg_ctime; I* último cambio */
12: struct wait_queue * wwait;
13: struct wait_queue * rwait;
14: unsigned short msg_cbytes; /* número actual de bytes en la cola */
15: unsigned short msg_qnum; /* número de mensajes en la cola */
16: unsigned short msg_qbytes; /* número máximo de bytes en la cola */
17: ___ kernel_ipc_pid_t msg_lspid; /* pid del último msgsnd */
18: ___ kernel_ipc_pid_t msg_lrpid; /* último pid de recepción */
19: };

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.

° IPC EXCL Si se utiliza con IPC CREAT. fallar si la cola ya existe.


Los m ensajes se colocan en la cola de m ensajes por m edio de la llamada de sistema
m s g sn d ().
/* En via r mensaje a la cola de mensajes. */
extern in t msgsnd __P ( ( in t __msqid, v o id * msgp, s iz e _ t __msgsz,
in t __m s g f l g ) );

m s g s n d () tom a com o argum ento un tipo de e stru c tu ra msgbuf. msgbuf se delineen


< l i n u x / m s g . h>. El tipo msgbuf es una definición de m odelo para el mensaje actual que
se pasa a msgsnd (). El listado 25.7 m uestra la estructura msgbuf de Linux. El parámetro
size es el tam año del mensaje en bytes.

Entrada Listado 2 5 . 7 Estructura msgbuf de Linux


1: // L ista d o 25.7 Búfer de mensajes para
2: // l a s llamadas msgsnd y msgrcv
3:
4: s t r u c t msgbuf
5: {
6: long mtype; /* t ip o de mensaje */
7: char mtextf 1 ]; /* te xto del mensaje */
8: };

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.

Entrada Listado 2 5 .8 Estructura msgbuf ip c _ m e s s a g e de usu ario de muestra

1: // L is t a d o 25.8 E s t ru c tu ra de muestra para mensajes IPC


2:
3: typedef s t r u c t ipc_message
4: {
5: long mtype;
6: long header
7: char p a y lo a d [ 8 ];
8: } ipc_message;

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.

Entrada L is t a d o 2 5 . 9 Objeto Message


1: // Listado 25.9 Clase Message (message.h)
2:
3: #ifndef C_MESSAGE_H
4: #define C_MESSAGE_H
5:
6: #include <sys/types.h>
7: #include <sys/ipc.h>
8: #include <sys/msg.h>
9: #include "lst25-01.h" // #include “object.h"
10: #include “lst25-04.h" // #include “key.h"
11:
12: class Message : public Object
13: {
14: public:
15: Message(Key k);
16: -Message(void);
17: int Create(void);
18: int Create(int flags);
19: int Destroy(void);
20: virtual int Read(char * buf, int len, long type);
21: virtual int Write(char * buf, int len, long type);
continúa
18 86 Día 25

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

Un semáforo cuyos valores se limitan a cero y uno se conoce como semáforo


binario. Los semáforos de System V no están limitados a cero y uno; pueden
tener el valor de cualquier entero no negativo.

C o m o se d escrib ió en la sección anterior, el proceso de adquirir un semáforo requiere de


d o s p aso s; prim ero se prueba el valor, y luego se decrementa. Para que este método fun­
c io n e adecu ad am en te entre varios procesos, las operaciones de prueba y decremento
d e b e n se r atóm icas. Para que las operaciones de prueba y decremento sean atómicas, la
im p le m e n ta c ió n de estas operaciones se hace en el kernel.
El kernel m antiene una estructura semid_ds para cada uno de sus semáforos. semid_ds
se d e fin e en < li n u x / s e m . h>. El listado 25.10 muestra la estructura semid_ds de Linux.

Entrada L is t a d o 2 5 . 1 0 Definición de la estructura semid ds de Linux

1: // L i s t a d o 25.10 Una estructura de datos semid para


2: // cada conjunto de semáforos del sistema.
3:
4: s t r u c t semid_ds 2 5
5: {
6: s t r u c t ipc_perm sem_perm; /* permisos .. ver ipc.h */
7: ____ kernel_tim e_ t sem_otime; /* última vez semop */
8: ____ k e rnel_tim e_ t sem_ctime; /* último cambio */
9: s t r u c t sem * sem_base; /* apuntador al primer semáforo del arreglo */
10: s t r u c t sem_queue * sem_pending; /* operaciones pendientes de procesar */
11: s t r u c t sem_queue * * sem_pending_last; /* última operación pendiente */
12: s t r u c t sem_undo * undo; /* solicitudes para deshacer en este arreglo */
13: u n sig n e d s h o r t sem_nsems; /* núm. de semáforos del arreglo */
14: };

A n te s de u tilizar un semáforo, primero se debe crear. La llamada de sistem a


A nálisis
semget () crea un nuevo semáforo. semget( ) regresa un identifícador de semáforo
o - 1 si no se puede crear un semáforo. El identifícador de semáforo es un int. Este identi-
fic a d o r se utiliza para las funciones de semáforo restantes. El prototipo de la función
s e m g e t ( ) se m uestra a continuación:
/* Obtener semäforo. */
e x t e r n i n t semget __P ((key_t __key, int __nsems, int __semflg));

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

1: // Listado 25.11 Las llamadas de sistema semop


2: // toman un arreglo de éstos.
3:
4: struct sembuf
5: {
6: unsigned short sem_num; /* indice de semáforo del arreglo *1
7: short sem_op; /* operación del semáforo */
8: short sem_flg; /* indicadores de operación */
9: };

utiliza el elemento sem_num para denotar el semáforo al que se está tenien­


sembuf
A n á l is is
do aceso, sem_op para la operación, y sem_f lag para el valor de la operación. Las
operaciones válidas se definen en sem.h. semid se utiliza en caso de que esté tratando con
varios semáforos. Por medio de la estructura sembuf, puede realizar varias operaciones
en una sola llamada a semop ().
Por ejemplo, una función miembro que adquiere el semáforo se implementa de esta manera
usando las llamadas de sistema sembuf ( ) y semop (). El listado 25.12 muestra la implemen-
tación de la función miembro Acquire( ), que se lleva a cabo mendiante la llamada de sis­
tema semop ().

L is t a d o 2 5 . 1 2 F u n c ió n S e m a p h o re : : A c q u i r e ( ) q u e m u e s tra la llam ada de


Entrada s is t e m a semop ()

1 :// Listado 25.12 Muestra el uso de la llamada


2:// de sistema semop()
3:
4:int Semaphore::Acquire()
5: {
6:// probar el semáforo y decrementar
7 :static struct sembuf lock[ 1 ] = { { 0, 1, IPC_NOWAIT } };
8:
9:int len = sizeof(lock) / sizeof(struct sembuf);
10:int stat = semop(semid_, lock, len);
11:return stat;
C o m u n ic a c ió n e n tre p ro cesos 889

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.

E ntrada L ist a d o 2 5 . 1 3 Definición de la clase Semaphore

1: // L is t a d o 25-13 Clase Semaphore (semap.h)


2:
3: //if ndef C_SEMAP_H
4: //define C_SEMAP_H
5:
6: //include <sys/sem.h>
7: //include " l s t 2 5 - 0 4 . h " // //include "key.h"
8: //include " l s t 2 5 - 0 1 . h " // //include "object.h"
9:
10: c l a s s Semaphore : public Object
11 : {
12: p u b l ic :
13: Sem aphore(Key); 2 5
14: -Semaphore();
15: i n t C r e a t e ();
16: i n t C r e a t e ( in t f l a g s ) ;
17: i n t D e s t r o y ();
18: i n t Q ueryV alue();
19: i n t A c q u i r e ();
20: i n t R e le a s e ();
21: p r i v a t e :
22: // no p e r m it ir la copia
23: Semaphore & operator=(const Semaphore &);
24: b o o l i n i t _ ;
25: Key key_;
26: i n t semid_;
27:};
28:
29: //endif

El objeto Semaphore definido en el listado 25.13 contiene las funciones miembro


A nálisis
estándar C r e a t e ( ) y D e s t r o y ( ) definidas para todos nuestros objetos. Una vez más,
o b serv e que el constructor toma un objeto Key; los procesos cliente y servidor crean un
o b je to Key, ya sea pasando el mismo valor de clave, o utilizando el mismo nombre de
a rch iv o e identificado!-. De esta manera, crearán un objeto Semaphore que haga referencia
890 Día 25

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

1 // Listado 25.14 Definición de una estructura shmid_ds


2
3 struct shmid_ds
4 {
5 struct ipc_perm shm_perm; /* permisos de operación */
6 int shm_segsz; /* tamaño del segmento (en bytes) */
7 ____ kernel_time_t shm_ati.me; /* última unión */
8 ____ kernel_time_t shm_dtime; /* última separación */
C o m u n ic a c ió n e n tre p ro ce so s 891

9: ____ k ernel_tim e_t shm_ctime; /* último cambio */


10 : ___ k e rn e l_ ip c _ p id _ t shm_cpid; /* pid del creador */
11 : ___k e rn e l_ ip c _ p id _ t shm_lpid; /* pid del último operador */
12 : unsign ed sh o rt shm_nattch; /* núm. de uniones actuales */
13: unsign ed sh o rt shm_unused; /* compatibilidad */
14: v o id * shm_unused2; /* idem - usado por DIPC */
15: v o id * shm_unused3; /* no utilizado */
16: >;0

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.

Entrada L is t a d o 2 5 . 1 5 Definición de la clase SharedMemory

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

12: class SharedMemory : public Object


13: {
14: public:
15: SharedMemory(Key k);
16 : -SharedMemory();
17: int Create();
18: int Create(intflags, int size);
19: int Destroy();
20: char * Attach();
21 : void Detach();
22: int Read(char * buf, int len, int offset);
23: int Write(char * buf, int len, int offset);
24: private:
25: //no permitir la copia
26: SharedMemory &operator=(const SharedMemory &);
27: bool init_;
28: Key key_;
29: int shmid_;
30: char * shmaddr_;
31: };
32:
33: #endif

El objeto SharedMemory definido en el listado 25.15 contiene las funciones miem­


A nálisis
bro 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 SharedMemory que haga referencia al
mismo segmento de memoria compartida. Una vez que se ha creado el objeto de memo­
ria compartida, los procesos cliente y servidor pueden leer y escribir directamente en la
memoria compartida, usando el apuntador regresado por la llamada de sistema shmat().
Es importante observar que el acceso a la memoria compartida debe estar sincronizado
mediante el uso de un semáforo.

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

de 1PC de S ystem V m encionadas hoy. A continuación se muestra el prototipo de la fun­


ción i p c ( ):
in t ip c (u n s ig n e d in t ca ll, in t f i r s t , int second, int
t h i r d , vo id *p t r, long f i f t h ) ;

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.

En la s e g u n d a m itad de la lección hablamos sobre la comunicación entre procesos de


S y stem V. los m ensajes, sem áforos y la memoria compartida, y desarrollamos algunos
o b je to s de C + + para utilizar estos métodos de IPC.

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

2. ¿Qué señal se produce si los extremos de lectura y de escritura de una tubería no


están preparados?
3. ¿Por qué la memoria compartida es más rápida que los mensajes?
4. ¿Qué es un semáforo binario ?

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:

• Q u é es u n a G U I (interfaz gráfica de usuario), y por qué es tan importante


p a ra L inux
• C ó m o las G U Is representan objetos conocidos como widgets con los que
se p u e d e interactuar
• L a h isto ria de las G U Is en UNIX
• D o s n u e v a s G U Is o escritorios gratuitos para Linux
• C ó m o e sc rib ir aplicaciones gráficas para Linux usando los paquetes de
h e rra m ie n ta s de desarrollo que vienen con las distribuciones de la GUI
• C ó m o e sc rib ir aplicaciones multiplataforma simples para GUI en C++
u sa n d o el nuevo kit de herramientas wxWindows
896 Día 26

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

El protocolo X y los desarrollos más recientes


Probablem ente haya observado en esta lección que m e ncionam os con frecuencia el término
" X " en relación con los gráficos en U N IX.
La o rga n izació n llam ada X O rg, un consorcio sin fine s de lucro fo rm a d o por miembros de
to d o el m undo, desarrolló el protocolo a m e diado s de los 80 para responder a la necesidad
d e u n a interfaz gráfica de usuario transpare nte a las redes, principalm ente para el sistema
o p e rativo UN IX.
X proporciona el despliegue y el m anejo d e inform ación gráfica, en form a muy similar a la
G D I (interfaz gráfica de dispositivo) de M ic ro so ft W in d o w s y al A d m in istrad or de Presen­
tación (P re sen taro n M a n a ge r) de IB M .
La diferencia clave se encuentra en el d iseñ o del p ro to c o lo X en sí. M icroso ft W indows y
el A d m in istra d o r de Presentación de IB M sim p le m e n te d e sp lie g a n inform ación gráfica en
la PC en la que se están ejecutando, m ientras que el p rotoco lo X distribuye el procesamien­
to de las aplicaciones m ediante la especificación de un a relación cliente-servidor en el
nivel d e aplicación.
La parte del "q u é se va a hacer" de la aplicación se llam a cliente X y está lógica y, por lo
general, físicam ente separada de la parte "c ó m o h ace rlo", q u e viene siendo la pantalla, y
se llam a servidor X. Por lo general, los clientes X se ejecutan en un e quipo remoto que tiene
poder com putacional de sobra y se desplie ga en un servidor X. Ésta es una verdadera rela­
ción diente-servidor entre una aplicación y su pantalla, y tiene to d a la secuela de ventajas
de esta relación. (Note que el cliente y el servidor se distribuyen en form a inversa al estándar;
en realidad el servidor es el qu e ofrece el d e sp lie gu e y n o el p o d e r de cómputo.)
Para lograr esta separación, la aplicación (cliente X) se divorcia de la pantalla (servidor X),
y los dos se com unican m ediante un p ro to co lo asin cro n o b a sa d o en sockets que opera en
form a transparente a través de una red.
Adem ás, X proporciona un sistema com ún de m anejo de ventanas m ediante la especificación
de un nivel dependiente del dispositivo, así com o de u n o independiente. En esencia, el pro­
tocolo X oculta las peculiaridades del sistema operativo y del hardw are que lo soporta de las
aplicaciones que lo utilizan. En teoría, distintas configuraciones de hardw are presentarán una
interfaz com ún de software y aún así tendrán com ponentes internos bastante distintos.
Esto está m uy bien en teoría, pero la realidad es q u e la A P I (interfaz d e programación de
aplicaciones) Xlib, escrita en C, es d em asiad o com plicada, y la program ación de X es algo
así co m o un arte oculto.

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

“ ...un conjunto gratuito y completo de aplicaciones y herramientas de escritorio amiga­


bles para el usuario, similar a CDE y a KDE pero basado completamente en software
gratuito” .

Y, ev id en tem en te, GNOM E es todo lo que se podría esperar de un entorno moderno de


program ación. A este respecto, es casi igual a CDE (Entorno Común de Escritorio), Win32,
N extS tep y KDE.
900 Día 26

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.

GNOM E también ofrece soporte para los métodos de internacionalización y localización


de estándares Uniforum, permitiendo que se agregue soporte para nuevos lenguajes sin que
se necesite volver a compilar la aplicación.
En realidad, GNOME puede ser algo difícil de instalar debido a sus muchas dependencias
y algunas cosas más, pero los fabricantes de Linux ahora lo incluyen en la distribución
estándar. Por ejemplo, GNOME se incluyó a partir de la versión 5.0 de Red Hat Linux, y
lo puede establecer como su escritorio predeterminado al momento de la instalación.

Cómo obtener GNOME y otros recursos de GNOME


La página Web de GNOME se encuentra en h ttp : / / www.gnome.org/. Si está impaciente
por obtener el software, puede ir directo a la página de descargas de Linux en
Programación de la GUI 901

h t t p : / / www.gnome. o r g / s t a r t . Encontrará FAQs (Preguntas frecuentes) de GNOME


en h t t p : / / www.g n o m e.o rg /faq s/ y mucha más información útil en h ttp : //d ev elo p e r.
gnome. o r g /p r o je c ts /g d p , y en http://w w w .gnom e.org/resources/m ailing-lists.htm l
que tiene un catálogo muy amplio de listas de correo relacionadas con GNOME.
Si está interesado en desarrollar software GNOME o aplicaciones que utilicen GNOME,
es co n v en ien te que vea en http: //developer.gnome.org/, donde encontrará mucha
inform ación y recursos adicionales.
La liberación actual de GNOME es 1.2.

Cómo obtener GTK++ y otros recursos de GTK++


La página Web de GTK++ está en http://www.gtk.org/,y usted puede descargar la
liberación más reciente desde ftp://ftp.gtk.org/pub/gtk/v1.3/.La página Web de
G TK ++ tiene m uchos vínculos hacia otros sitios útiles, así como documentación esencial.
P uede o b te n e r G T K — , una biblioteca de envoltura de C++ gratuita relacionada con
G TK ++, en http: / /gtkmm. sourceforge.net/. La distribución también incluye a gnome— ,
una envoltura de C++ para las bibliotecas de GNOME. En enero del 2000, gnome— no
parecía tener documentación, y GTK— era algo inestable. Tal vez valga la pena que vea
estos proyectos otra vez.
Para G T K ++ 1.3.2 (versión actual) y posteriores, necesitará la libsigc++ 1.0.1 o posterior.
Puede obtenerla en http://libsigc.sourceforge.net/stable.html.

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

A u n q u e la instalación d e las d istrib u c io n e s e stá n d a r de G N O M E y KDE desde


Nota el C D es u n a m a ne ra fácil y te n ta d o ra de te n e rlo s listos y funcionando, una
desventaja es q u e tal vez a lg u n a s de las liberaciones m ás recientes de software
útil, co m o KD e velop, K O ffice y G N O M E Office, necesiten versiones más re­
cientes d e las bibliotecas centrales q u e las q u e está n disp o n ib le s en el CD.
Esto es inevitable, ya q u e las lib e ra cio n e s en C D se tie n e n que congelar en
a lg ú n punto.

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.

Cómo obtener KDE y otros recursos de KDE


La página Web de KDE se encuentra en http: //www. kde.org/, y puede descargarlo de
cualquiera de los sitios espejo enlistados en http: / /www.kde.org/mirrors.html/.Los
vínculos que vienen en esta página tratan sobre temas que van desde los archivos de lis­
tas de correo de KDE y la documentación de KDE, hasta las camisetas y juguetes de
peluche de KDE (y no estoy bromeando).
Programación de la GUI 903

Si está p en san d o en desarrollar aplicaciones KDE, necesitará la liberación gratuita


de las b ib lio tecas Qt de TrollTech; puede obtenerlas en su sitio Web que está en
h t t p : / /www. t r o l l .n o /. La versión más reciente de Qt es 2.2.3. Tome nota de que la ver­
sión gratuita es sólo para aplicaciones X. Si quiere compilar y enlazar su código en platafor­
m as W indow s, o si quiere vender sus programas, necesita comprar su paquete Profesional.
La versión actual de KDE es 2.0.1, pero está disponible la versión 2.1.0 beta.
Puede leer acerca de KDevelop y descargar este software visitando http://www.kdeve-
l o p . o rg /. La versión actual es 1.4, pero este número parece ir cambiando casi a diario.
Para cuan d o usted esté leyendo este libro, ya se habrá actualizado varias veces, por lo
que será conveniente que consulte la página Web para la última liberación.

Programación con C++ en el escritorio de Linux


P ara ayud ar a los programadores a utilizar los servicios ofrecidos por GNOME y KDE,
cada uno exporta una API a la que los programadores pueden escribir, para así asegurar
que exista com patibilidad y consistencia con otros programas escritos para el escritorio.
Las aplicaciones se conforman a un formato fijo, o marco de trabajo de aplicación; éste
es un térm ino conocido para alguien que haya programado con la MFC de Microsoft o
con C ++ B uilder de Borland.
En K D E , la biblioteca Qt proporciona el marco de trabajo y el mecanismo que utilizan
los objetos de Qt para comunicarse entre sí; en GNOME, la API de GNOME proporciona
servicios sim ilares. Llamaremos widgets a los objetos que están en la GUI de UNIX. Vea
el recuad ro “W idgets” .
V erem os un tercer marco de trabajo en la biblioteca wxGTK. Esta biblioteca es similar
al m arco de trabajo de Qt y, evidentemente, a los marcos de trabajo de Microsoft y de
B orland, en que im plementa un marco de trabajo Documento/Vista en el que el Documen­
to represen ta conceptualm ente a los objetos de datos, y la Vista representa conceptual­
m ente la vista que tiene el usuario de esos datos.

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.

Fundamentos de la programación en GNOME


GNOME es el escritorio de GNU y, a diferencia de KDE. está escrito completamente en C.
Se basa en la excelente biblioteca GTK++. que es en sí una biblioteca de envoltura de C
alrededor de GDK, que a su vez es una envoltura alrededor de las bibliotecas Xlib nativas
(vea el recuadro “El protocolo X y los desarrollos más recientes” ).
Aunque éste es un libro para enseñar a programar en C++, vale la pena dar un vistazo
breve a las APIs GTK++ y C de GNOME para ilustrar algunos conceptos de programación
útiles que se basan en las bibliotecas de envoltura de C++ más relevantes que se examinan
posteriormente en esta lección.

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

Si eslá decidido a usar C++, tiene tres opciones:


• En prim er lugar, siempre puede utilizar una de las bibliotecas de envoltura especiali­
zadas que envuelven a la API de C de GTK++ en clases de C++, en forma muy
p arecid a a com o lo hace GTK— . En la lección de hoy, esto es lo que realmente
hacem os, ya que se enlaza a la perfección con el entorno de programación KDE, y
esto facilita las comparaciones considerables.
• En segundo lugar, si no quiere confiar sus aplicaciones a gnome— o a otra biblio­
teca de envoltura de C++ por cualquier otra razón, puede utilizar solamente el sub­
conjunto de C incluido en C++ al hacer una interfaz con GTK++, y luego utilizar
la interfaz de C. Recuerde que C es un subconjunto válido de C++ y un programa
válido de C es un programa válido de C++, generalmente hablando.
• Por últim o, puede utilizar juntos a GTK++ y a C++ declarando todas las funciones
callback com o funciones estáticas en clases de C++, y llamando otra vez a GTK++
por m edio de su interfaz de C. El ejemplo botones que se muestra en el listado 26.1
hace esto.
Si elige esta tercera opción, puede incluir un apuntador al objeto que se va a manipular
(el apu n tad o r t h i s ) com o valor de los datos de la función callback.
E leg ir entre estas opciones es principalmente cuestión de preferencia, ya que en todas se
cu en ta con C++ y GTK++. En lo personal, la segunda solución me parece algo horrenda
y nada elegante, y prefiero utilizar una biblioteca de envoltura de C++, pero es cuestión
de gustos.
N inguna de estas opciones requiere del uso de un preprocesador especializado, así que, sin
im portar cuál sea su elección, puede utilizar C++ estándar con GTK++.
A hora veam os a G N O M E en sí, que se encuentra por encima de GTK++.
U n verd ad ero program a GNOME es una aplicación GUI de GTK++ que también utiliza
las bibliotecas de GNOM E. Estas bibliotecas hacen posible tener una look and feel similar
en todas las aplicaciones, y que las cosas simples sean simples de programar. Y no sólo
eso, tam bién agregan muchos widgets que no pertenecen propiamente a GTK++.
Para infortunio de los programadores novatos de C++, GNOME, al igual que su biblioteca
de gráficos de soporte, GTK++, tiene una API de C. Para estar seguros, la API está fuerte­
m ente orientada a objetos y utiliza estructuras opacas, funciones de acceso y otras cosas
más, pero sin duda es una API de C. Claro que se tienen las mismas tres opciones a elegir
tanto en la program ación de GNOME como en la programación de GTK++.
Vale la pena que analice un pequeño programa de GNOME (vea el listado 26.1) y que vea
cóm o en caja en el escritorio de GNOME. También verá cómo puede declarar una clase
sim ple con funciones miembro estáticas que actúan como callbacks. Desde luego que puede
hacer la clase tan com plicada y rica en funcionalidad como usted quiera.
906 D ia 26

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

1 : // L is t a d o 26.1 Un programa b á sico de GNOME: (botones.cxx


2:
3:
4: ¿¡include <gnome.h>
5:
6: c l a s s Callback
7: {
8: p u b lic :
9: s t a t i c void clicked(GtkW idget * button,
10 g p o in te r d a t a ) ;
11 s t a t i c g in t quit(GtkWidget * widget,
12 GdkEvent * event,
13 g p o in te r d a t a ) ;
14 };
15
16 void C a l l b a c k : :clicked(GtkWidget * button,
17 g p o in te r data)
18 {
19 char * s t r i n g = (char *)data;
20 g _ p rin t(strin g );
21 g _ p r i n t ( "Uso de Callback de C + + \n ");
22
23
24 g in t C a l l b a c k : :quit(GtkWidget * widget,
25 GdkEvent * event,
26 g p o in te r data)
27 {
28 g t k _ m a i n _ q u i t ();
29 return FALSE;
30 }
31
32 i n t m ain (int argc, char * a r g v [ ] )
33
34 GtkWidget * app;
35 GtkWidget * button;
36 GtkWidget * hbox;
37
38 // Inicializar GNOME, esto es muy similar a gtk_init
39 g n o m e _ i n i t ( " b o t o ne s - e j e m p l o - b a s i c o " , "0 .1 ", argc, argv);
40 app - g n o m e _ a p p _ n e w ( " b ot o n e s - e j e m p l o - b a s i c o " , "Buttons");
41 hbox = gtk_hbox_new(FALSE, 5);
42 gnome_app_set_c o nt e nt s( G N0 ME _ AP P (app), hbox);
43
44 // en lazar "quit_event con gtk_main_quit
45 g tk _sign al_ co nnec t (GTK_0BJECT (app),
46 "q u it _ e v e n t ",
47 GTK_SIGNAL FUNC ( C a l l b a c k : :q u i t ) ,
48 NULL); ~
49
50 button = gtk_ b u t to n_ n ew _w it h_ l ab el ( "B ut t on 1");
51 gtk_box_pack_start(GTK_B0X(hbox),
52 button,
53 FALSE,
P r o g r a m a c ió n d e la GUI 90 7

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

La p r im e ra parte del código, líneas 6 a 30, contiene la declaración de la clase


A n á l is is
C a l l b a c k y sus funciones eallback estáticas que creamos para responder a eventos
q u e la G U I de te c ta al hacer clic en los botones desplegados:
C o m o puede ver, las funciones son simples y directas. Callback: : c l i c k e d ( ) simplemente
im prim e texto en s t d o u t en la línea 21 (compare con cout en C++) y C allb ack : : q u i t ( )
sale del p ro g ra m a principal en la línea 28.
La p rim e ra llam ada que hacemos en m ain( ) es a gnom e_init( ) en la línea 39.
26
E sto es m uy sim ilar a una aplicación GTK++ pura, en la que llamaríamos a g t k _ i n i t ( );
de la m is m a m anera, utilizamos una llamada a gnome_app_new() en la línea 40 para que
nos d é una instancia de una nueva aplicación GNOME. En una aplicación GTK++ pura,
la lla m a d a correspondiente sería a gtk_window_new( ).
A u n q u e g n o m e _ a p p _ n e w ( ) regresa un objeto GtkWindow, después verificamos eso con un
GnomeApp m e d ia n te la macro GN0ME_APP. GnomeApp es el widget principal de cada apli­
ca ció n . Es la v e n ta n a principal de la aplicación que contiene el documento en el que se
está tra b a ja n d o , los mentís de aplicaciones, las barras de herramientas y barras de estado,
etc. T a m b ié n recuerda las posiciones acopladas de las barras de mentís y barras de herra­
m ientas, para que el usuario obtenga la ventana de la misma forma en que la aplicación
la h a b ía d e ja d o la última vez que se cerró.
C r e a r un w id g e t GnomeApp es tan sencillo como llamar a gnome_app_new( ) con el nom­
bre d e la a p lic a c ió n y el título de la ventana principal. Luego puede crear el contenido
de la v e n ta n a principal y agregarlo al widget GnomeApp mediante una llamada a
g n o m e _ a p p _ s e t _ c o n t e n t s () con el contenido como argumento.
908 D ía 26

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.

El marco de trabajo de aplicación GnomeApp es parte de la biblioteca libgnomeui. y es la


parte que en verdad hace que una aplicación (íN O M E sea lo que es. También hace que la
programación de aplicaciones G N O M E sea razonablemente simple y directa, y hace que
las aplicaciones sean amplias y consistentes en todo el escritorio. Si sólo utiliza GTK++
necesita hacer muchas cosas usted mismo, pero GnomeApp se encarga de la configuración
estándar de la GUI de G N O M E por usted, y aún asi permite que el usuario configure el
comportamiento y lo hace consistente para distintas aplicaciones.

Las líneas 45 a 70 enlazan el manejador de eventos C a llb a c k : : q u i t ( ) con su función


callback y luego crean dos botones y los enlazan al m anejador de eventos
C a llb a c k : : c li c k e d (). antes de agregarlos al cuadro horizontal.

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.

Por último, en las líneas 72 y 73 indicamos al widget de la aplicación principal que se


muestre a sí mismo y todo su contenido, y luego que entre al ciclo principal de eventos y
espere el clic del ratón.

Para crear este programa, puede utilizar el siguiente comando:


gee -g -Wall gnome-config - - c f l a g s gnome gnomeui LDFLAGS=' gnome-config \
- - l i b s gnome gnomeui' - 1 / u s r / lib / g lib / in e lu d e l s 126 - 0 1 .cxx -o botones

La figura 26.1 le muestra cómo debe lucir su programa botones.

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ómo envolver a GTK++ con wxWindows


En la se c c ió n a n te rio r vio cóm o utilizar C++ con las bibliotecas de GNOME para crear
a p I ic a c i o n e s G N O M E .

En 1992, un grupo de program adores de la Universidad Edinburgh de Escocia creó la ver­


sión 1.0 del kit de herram ientas wxWindows. Este kit de herramientas es un conjunto de
b ib lio te c a s de C ++ puras que facilitan el desarrollo para la GUI. Permite que las aplicacio­
nes de C + + se com p ilen y ejecuten en distintos tipos de computadoras, sin necesitar más
q u e u n o s c u a n to s cam bios en el código.

D u ra n te 1997 se o rig in ó un esfuerzo para producir un entorno de escritorio estándar en


L in u x , G N O M E . Su conjunto de widgets fue GTK++, creado por encima de X, y parecía
que las aplicaciones basadas en GTK++ se convertirían en el estándar en el universo Linux.
E sto c o n d u jo al eq u ip o de desarrollo de wxWindows a reafirmar su propósito y a liberar
w x W in d o w s 2.0.

En re a lid a d , w xW indow s no es un entorno de desarrollo para GNOME, ya que no utiliza


las b ib liotecas de G N O M E. Sin embargo, tal vez esta capacidad se incluya en futuras libera­
c io n e s y a q u e, en L inux, wxW indows está enfocada directamente a los entornos GTK++
y G N O M E . A ún así, un program a wxWindows se ejecutará sin problemas en KDE y en
G N O M E , y em pleará la look and feel (interfaz gráfica de usuario) del escritorio en el que se
e n c u e n tre ; pero, y esto puede ser importante para algunos usuarios, no tendrá acceso a las
fu n c io n e s de e scrito rio que proporcionan GNOME y KDE.

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.

A d e m á s de p ro v ee r una API común para funcionalidad de la GUI en un amplio rango de


p latafo rm as, w xW indow s proporciona la funcionalidad para tener acceso a algunas facilida­
des del sistem a operativo utilizadas comúnmente, como copiar o eliminar archivos, y muy
p ro n to in clu irá un conjunto de clases para proporcionar servicios criptográficos.
Por lo tanto, w xW indow s es un "marco de trabajo" en el sentido de que proporciona mucha
fu n c io n a lid a d in teg rad a, que puede ser utilizada o reemplazada por la aplicación según
sea necesario, ahorrando por consecuencia mucho esfuerzo de codificación. También sopor­
ta un c o n ju n to de estructuras básicas de datos, como cadenas, listas enlazadas y tablas de
hash.
910 Día 26

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.

Creación de su primera aplicación de wxWindows:


"¡Hola, mundo!"
El listado 26.2 muestra el código fuente para la aplicación más simple de wxWindows que
se pueda tener.

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

1: // Listado 26.2 La aplicación GNOMEHelloWorld


2:
3: #ifdef _ G N U G _
4: // #pragma implementation
5: #endif
6:
7: // Para los compiladores que soportan la precompilación,
8: #include "wx/wxprec.h”
9:
10: #ifdef BORLANDO
11: #pragma hdrstop
12: #endif
13:
14: #ifndef WX_PREC0MP
15: #include "wx/wx.h“
16: #endif
17:
18: class MyApp: public wxApp
19: {
20: virtual bool 0nlnit();
Programación de la GUI 911

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

Lo primero que hacemos es incluir los archivos de encabezado de wxWindows en


A nálisis
las líneas 8 y 15 del listado. Observará algunos pragmas e instrucciones #ifdef
condicionales: éstos son normales y ayudan a que el código sea verdaderamente portable.
En alguna parte del kit de herramientas wxWindows hay algunas otras macros que podemos
incluir para ayudar con distintos compiladores y otras cosas más, pero por claridad vamos a
asumir que sólo tenemos un entorno Linux. Por lo tanto, por el momento puede ignorar sin
peligro estas macros.
Desde luego que puede incluir los encabezado de manera individual, por ejemplo #include
"wx/window. h" o de manera global, que sena #include "wx/wx.h". Esto es especialmente
útil en plataformas que soportan encabezados precompilados, como los principales com­
piladores de la plataforma Windows.
El código en sí empieza en la línea 18 del listado, en donde declaramos nuestra propia clase
de aplicación, derivándola de la clase estándar wxApp. Prácticamente toda aplicación
912 D ía 26

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.

En las líneas 23 a 20 d e r iv a mo s de wxFrame la v en t a n a pr inci pa l; nombramos el marco


pa sá n do le una cadena ("Helio Wor ld" ) c o m o p a r á me tr o par a su constructor, como verá
post eri orme nt e en el código.

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.

La función mi embro wxApp: : 0 n l n i t ( ). que se i m pl eme nt a en las líneas 33 a 41, se ejecuta


al inicio e inicializa el prog rama al cr ear la ve nt ana principal y most rar pantallas instan­
táneas y otras cosas más. Aquí verá que se da un título al marco en la llamada a su construc­
tor en MyApp: : 0 n l n i t ( ) en la línea 35.

Para c o mp i la r el programa, utilice el s iguiente c om a n d o :


g++ l s t 2 6 - 0 2 . c x x wx-config —l i b s ' ' w x - c o n f i g —c f l a g s \
-o GNOMEHelloWorld

Al ej ecutar el programa, debe ver u n a ve nt ana sencilla con el titulo "Helio


S a l id a
World" despl egado a lo largo de la parte s up er ior (vea la tigura 26.2).

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

Cómo agregar botones a su propia clase de ventana


de wxWindows
La p rim e ra ap licació n , com o vio en el listado 26.2, es un programa GNOME completa­
m en te fu n cio n al, pero es extremadamente aburrido. No permite ninguna interacción por
p a rte del u su a rio que no sea finalizarla.

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

1: // L i s t a d o 2 6 .3 Otro ejemplo de GNOMEHelloWorld


2:
3: //if def __GNUG__
4: // //pragma implementation
5: //endif
6:
7: // P a ra co m p ilad o re s que soporten la precompilación,
8: //include "wx/wxprec. h"
9:
10: //if def BORLAN DC
11 : //pragma h d rst o p 26
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 bool O n ln it ();
22: };
23:
24: c l a s s MyFrame: p u b lic wxFrame
25: {
26: p u b l i c :
27: MyFrame ( co n st w xString & t it le ,
28: co n st wxPoint & pos,
continúa
914 Dia 26

L is ta d o 2 6 .3 continuación

29 const wxSize & size);


30 void OnQuit(wxCommandEvent & event);
31 void OnGreet(wxCommandEvent & event);
32 DECLARE_EVENT_TABLE()
33 private:
34 wxPanel * m_panel;
35 wxButton * mbtnGreet;
36 wxButton * m_btnQuit;
37
38
39 enum { ID_Quit = 1, ID_Greet };
40
41 BEGIN_EVENT_TABLE(MyFrame, wxFrame)
42 EVT_BUTTON(ID_Greet, MyFrame::OnGreet)
43 EVT_BUTTON(ID_Quit, MyFrame: ’
.OnQuit)
44 END_EVENT_TABLE()
45
46 IMPLEMENT_APP(MyApp)
47
48 bool MyApp::0nlnit()
49 {
50 MyFrame * frame = new MyFrame("Hello World",
51 wxPoint(50,50),
52 wxSize(200,100));
53 frame->Show(TRUE);
54 SetTopWindow(frame);
55 return TRUE;
56
57
58 MyFrame::MyFrame(const wxString & title,
59 const wxPoint & pos,
60 const wxSize & size) :
61 wxFrame((wxFrame *)NULL, -1, title, pos, size)
62
63 wxSize panelSize = GetClientSize();
64 m_panel = new wxPanel(this, -1 , wxPoint(0, 0), panelSize);
65 int h = panelSize.GetHeight();
66 int w = panelSize.GetWidth();
67
68 m_btnGreet = new wxButton(m_panel,
69 ID_Greet,_T("Greet"),
70 wxPoint(w/2-70, h/2-10),
71 wxSize(50, 20));
72 m_btnQuit = new wxButton(m_panel,
73 ID_Quit,_T("Quit"),
74 wxPoint(w/2+20, h/2-10),
75 wxSize(50, 20));
76
Programación de la GUI 915 |

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

P ro b ab lem en te, las primeras diferencias que observará se encuentran en la


A nálisis
declaración de la clase MyFrame. En las líneas 30 a 36 ahora hemos agregado
algunas funciones y variables miembro adicionales en la clase MyFrame.
Las lín eas 30 y 31 declaran dos funciones miembro públicas ordinarias, OnQuit() y
OnGreet (). Verá que ambas toman un apuntador wxCommandEvent como su único argumen­
to; los lectores astutos deducirán que estas funciones miembro deben responder de alguna
m anera a los eventos. Evidentemente, sus nombres son por sí solos una pista.
A continuación verá la macro DECLARE_EVENT_TABLE() en la línea 32. Lo que ésta hace es
in sertar có d ig o que actuará como marco de trabajo en la clase para permitimos asignar
eventos a las funciones que deban manejarlos. Verá más sobre esto en un momento.
P o r ú ltim o , el código adicional de esta declaración incluye tres variables miembro, una
wxPanel y dos wxButtons.
In m ediatam en te después de la declaración de clase modificada, verá más código nuevo
en las líneas 39 a 44.

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

P ara c o m p ila r el program a, utilice el siguiente comando:


g++ l s t 2 6 - 0 3 . cxx 'w x - c o n f ig — l i b s ’ 'wx-config — c f la g s ' \
-o GNOMEHelloWorld

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.

In te ra c c ió n de objetos por medio de eventos


A n te s de a g re g a r m ás funcionalidad al programa GNOMEHelloWorld. necesitamos ver
la fo rm a en que se com unican los distintos objetos de un programa wxWindows. Al igual
26
q u e la m ay o ría de las aplicaciones de GUI, los programas de wxWindows tienden a ser
c o n tro la d o s p o r eventos.

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

C u a n d o un usuario m ueve el ratón, op rim e un a tecla o hace clic en el botó n de un ratón,


d e scrib im o s esto c o m o un evento: es a lg o q u e ocu rre y b rin d a in fo rm ació n al sistema,
info rm ació n que debe ser identificada, d irigid a al obje to q u e está interesado en recibirla,
y lu e g o se debe realizar a lgu n a acción.
Por lo tanto, el sistema de ven tanas envía to d o s los eve ntos de interacción a la aplicación
G U I qu e tiene el e n foqu e en la pantalla.
Utilizam os la palabra enfoque para describir a la aplicación que se encuentra actualmente
en prim er plano, o la que to m ará la e n trada actual. Por lo general, en la mayoría de los
sistem as se cam bia la con figu ración para qu e la aplicación q u e te n g a el enfoque tenga
un borde o m arco de color distinto, con lo cual es m ás fácil saber cuál aplicación se está
utilizand o actualm ente.

Com o wxGTK está basado en la biblioteca GTK++, utiliza el mecanismo GTK++ de


soporte para manejar eventos: señales y callbacks.
GTK++ es un kit de herramientas controlado por eventos, lo que significa que esperará
en g tk _ m ain () hasta que ocurra un evento y se pase el control a la función apropiada.
Este paso de control se realiza utilizando el concepto de “señales”. Al ocurrir un evento,
com o el clic de un botón del ratón, el widget en el que se hizo clic “emitirá” la señal
apropiada. Así es como GTK++ hace la mayor parte de su trabajo útil. Hay señales que
todos los widgets heredan, como d e s tr o y ( ), y hay señales específicas de cada widget,
como to g g le d () en un interruptor.

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

La función especificada en el tercer argumento se llama “función callback". y por lo general


d e b e se r de la form a
v o id c a l l b a c k f une (GtkWidget *widget, gpointer callback_data);

d o n d e el p rim e r argum ento es un apuntador al widget que emitió la señal, y el segundo es


un a p u n ta d o r a los datos proporcionados como último argumento para la función
g t k _ s ig n a l_ c o n n e c t ( ). com o se mostró anteriormente.

w xW indow s extiende este concepto de señales y callbacks en su propio espacio de nombres


y u tiliz a ta b la s de eventos para asignar acciones a los eventos.

U ste d c o lo c a una tab la de eventos en un archivo de implementación para indicar a


w x W in d o w s có m o debe asignar eventos a funciones miembro. Estas funciones miembro
no son fu n cio n e s virtuales, pero todas son similares en forma: toman un solo argumento
d e riv a d o de w x E v en t, y tienen un tipo de valor de retorno void.

Tal vez o b se rv e que el sistem a de procesamiento de eventos de wxWindows implementa


alg o m uy p arecid o a los m étodos virtuales en C++ normal; es decir, puede alterar el com ­
p o rta m ie n to de una clase redefiniendo sus funciones de manejo de eventos.

En m u c h o s caso s esto funciona incluso para cambiar el comportamiento de los controles


n ativ o s. P or ejem plo, tam bién puede filtrar un número de eventos de pulsaciones de teclas
e n v ia d o s p o r el sistem a a un control de texto nativo si redefine a vvxTextCtrl y define un
m a n e ja d o r p ara eventos de teclas por medio de EVT_KEY_DOWN. Esto prevendría sin duda
q u e c u a lq u ie r evento relacionado con oprimir una tecla fuera enviado al control nativo (que
tal vez no sea lo que usted quiere). En este caso, la función manejadora de eventos llama
a S k i p () p a ra in d icar que debe continuar la búsqueda del manejador de eventos. 2 6
En resu m en , en lugar de llamar explícitamente a la versión de la clase base, como lo habría
h e c h o c o n las fu n cio n es virtuales de C++ (por ejemplo, vvxTextCtrl: :OnChar()), debe
lla m a r a S k i p ().

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

13: // el evento no se procese en ninguna otra parte.


14: wxBell();
15: }
16: }
Verá que la línea 3 comprueba si el cód igo de tecla estaba representando una tecla, y llama
a Skip() en la línea 8 para continuar la búsqueda de un manejador.
El orden normal de búsqueda en la tabla de eventos que real i/a ProcessEvent es el siguiente:
1. S i el objeto está deshabilitado, por lo gen eral con una llam ada a
wxEvtHandler: :SetEvtHandlerEnabled ( ) . la función salta hasta el paso (6).
2. S i el objeto es wxWindow, llam ar a ProcessEvent en form a recursiva sobre el
wxValidator de la ventana. S a lir de la función si esto regresa true.
3. L la m a r a SearchEventTable () para obtener un m an ejador de eventos para este
evento. Si esto falla, tratar en la clase base, y así sucesivam ente hasta que se agoten
las tablas o se encuentre una función apropiada, en cu y o caso la función termina.
4. A p licar la búsqueda descendente por toda la cadena de manejadores de eventos (por
lo general, la cadena tiene una longitud de uno). S a lir de la función si este paso tiene
éxito.

5. Si el objeto es wxWindow y el evento es wxCommandEvent, aplicar ProcessEvent en


form a recu rsiva al m anejador de even to s de la ventana padre. S a lir si este paso
regresa true.
6. Llam ar a ProcessEvent en el objeto wxApp.

Cómo agregar un menú a su propia clase de ventana


wxWindows
Po r últim o, agregarem o s un menú a la ap licació n sim p le que hem os desarrollado. No
agregarem os más funcionalidad, excepto proporcionar una segunda forma de invocar la
funcionalidad que ya tenemos.

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.

E ntrada L is t a d o 2 6 .4 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 lo W o r ld con un menú

// Listado 26.4 GNOMEHelloWorld


tfifdef _GNUG_
// ^pragma implementation
#endif
// Para compiladores que soporten la precompilación
P r o g r a m a c ió n d e la GUI 921

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

66 m _panel = new w x P a n e l ( t h i s , -1, w x P o in t(0 , 0), p a n e lS iz e );


67
68 m _btnG reet = new w x B u t t o n ( m p a n e l,
69 ID G re e t ,
70 _T ( ' G reet" ) ,
71 w x P o in t (w / 2 •70, h /2 10),
72 w x S iz e (5 0 ,2 0 ));
73 m _ b tn Q u it = new w x B u t t o n ( m _ p a n e l ,
74 ID Q u it ,
75 _ T ( " Q u i t ") ,
76 w x P o in t (w /2+20 , h/2 - 1 0 ),
77 w x S iz e (5 0, 20));
78 wxMenu * menuApp = new w xM e n u ;
79 menuApp -> A p p e n d (ID _ G re e t, "& G re e t...");
80 menuApp -> A p p e n d S e p a r a t o r () ;
81 menuApp ->Append( ID _ Q u it , "& Q u it");
82
83 wxMenuBar * m enuBar = new w xM enu Bar;
84 m enuBar->A ppe nd(m enu A pp, "S A p p lic a tio n ") ;
85
86 Se tM e n u B ar(m e n u B ar);
87 }
88
89 v o id M y F ra m e ::O n Q u it(w x C o m m a n d E ve n t & W X U N U S E D ( e v e n t ))
90 {
91 C l o s e ( T R U E );
92 }
93
94 v o id M yF ra m e : : O nG reet(w xCom m andEvent& W XUNUSED( e v e n t ) )
95 {
96 w x M e ssa g e B o x ( " E je m p lo de H e llo W o rld con w x W in d o w s",
97 "H e llo W o rld ",
98 wxOK | w x IC O N _ IN F O R M A T IO N ,
99 t h is ) ;
100: }
101 :

T e n e m o s e x a c ta m e n te el m is m o c ó d ig o p ara e m p e z a r, y luego simplemente


A nálisis
ag regam os al código lo necesario para crear el m enú y asociar los eventos del
m en ú con los m anejadores de eventos que ya tenem os.
En la línea 78 cream os un nuevo en c a b e z a d o de m en ú en la barra de menú principal, y
las líneas 79 a SI c o m p re n d e n el en c a b e z a d o del m enú y d eterm in a n lo que se verá al
s e lec cio n ar el encabezado de m enú y los e lem e n to s de m enú que se muestren.
L u e g o cream os en la línea 83 una barra de m enús, el objeto actual que se ve a lo largo de
la parte superior del área cliente, y agregam os el encabezado de menú y sus elementos en la
línea 84.
Por último, podem os configurar el menú principal de la aplicación para que sea la barra de
m enú que ac ab am o s de crear en la línea 86.
P r o g r a m a c ió n d e la GUI 923

P ara c o m p ila r el program a, utilice el siguiente comando:


g + + l s t 2 6 •04 . cxx w x-co n fig — l i b s ' 'wx-config — c f la g s ' \
-o GNOMEHelloWorld

Al e je c u ta r el program a deberá ver una ventana igual a la que se muestra en la


S a l id a
figura 26.3. pero ahora con un menú en la parte superior del área cliente de la ven­
tan a . c o m o se m uestra en la figura 26.4.

Figura 2 6 .4

El tercer program a
G N O M EH elloW orld.
con m enú y botones.

Creación de aplicaciones wxWindows más complejas:


wxStudio
H a sta a h o ra hem os creado una aplicación simple con un solo archivo fuente. Las aplica­
c io n e s m ás g ra n d e s que tengan varios archivos fuente que requieran un manejo más 2 6
e s tre c h o n ecesitarán obviam ente algún tipo de entorno de desarrollo formal.

El p ro y e c to w xW indow s ha sugerido el wxStudio. su propio IDE (Entorno de Desarrollo


In te g rad o ). El trab ajo con wxStudio se encuentra aún en sus primeras etapas, pero ya está
lib erad o y está disponible mediante descarga electrónica gracias a CVS (Sistema de Versio­
nes C o n c u rre n te s). Es muy interesante, cuando menos en plataformas Windows, que MS
D e v S tu d io funcione bien como IDE y como depurador para la biblioteca wxWindows; no
sé c ó m o se c o m p a ren otros IDEs.
C o m o w x S tu d io se encuentra en una etapa muy temprana, es difícil hacer un comentario
so b re e ste ID E, aparte de decir que el plan es para un IDE independiente de la plataforma
y c o m p le to con depuradores, editores de recursos, control de versiones y mucho, mucho
m ás. U no de los dogm as fundamentales del diseño es que se proporcionarán tantos módulos
fu n c io n a le s c o m o sea posible como complementos para permitir que el usuario configure
el ID E con el editor, com pilador y cualquier otra cosa de su elección.
924 Día 26

C ó m o obtener w x W in d o w s y otros recursos


de w x W in d o w s
La página Web de wxWindows está en h ttp : / /w w w . w x w i n d o w s .o r g /. wxWindows viene
en varias versiones, y proporciona el rango más amplio de plataformas soportadas que haya
visto para un paquete de este tipo. El que usted u tili/a con este libro es wxGTK, y la
página de descarga para este software se encuentra en
h ttp ://w w w .w xw indow s.org/dl_gtk. htm.
La versión más reciente de wxWindows es la 2 .2 .1 y es una actualización substancial de
la versión 1. Pienso que la versión 1 ahora se considera obsolescente, si no es que obsoleta.
Necesitará GTK++ 1.2 para wxGTK 2.2.1.

Existe también un conjunto de listas de correo de wxWindows en h t t p : / /www.wxwin-


d o w s.o rg /m a illst2 .h tm . Algunas de estas listas son de un volumen muy grande, así que
tenga cuidado con su bandeja de entrada.

Fundamentos de la programación de KDE


La biblioteca Qt es la biblioteca de gráficos que sostiene a todo el KDE. Es un kit de
herramientas de C++ desarrollado por la compañía TrollTech de Noruega, y ofrece elemen­
tos gráficos que se pueden utilizar para crear aplicaciones GUI para X.
Por añadidura, el kit de herramientas ofrece un conjunto completo de clases y métodos listos
para usarse, incluso para código de programación que no involucre gráficos, así como un
marco de trabajo sólido de interacción del usuario por medio de métodos virtuales y el
exclusivo mecanismo de señal y ranura de Qt, y una biblioteca de elementos de GUI
predefinidos, widgets de GUI que usted puede utilizar para crear los elementos visibles.
También ofrece cuadros de diálogo predefinidos que se utilizan comúnmente en aplicacio­
nes, como indicadores de progreso y cuadros de diálogo para archivos.
Por lo tanto, es muy conveniente que usted sepa cómo utilizar las clases de Qt, incluso si
sólo quiere programar aplicaciones KDE usando el marco de trabajo de aplicación KDE.
Vale la pena visitar el sitio de TrollTech que se encuentra en h ttp : / / www.tr o ll.n o / para
dar un vistazo a sus excelentes tutoriales y documentación.
Pero por ahora, para comprender los conceptos básicos de cómo crear aplicaciones GUI de
Qt/KDE, dé un vistazo a un programa KDE de muestra.

Creación de su primera aplicación KDE: "Helio World"


Para seguir con la tradición, daremos un vistazo (vea el listado 26.5) al programa Helio
World estilo KDE y explicaremos cómo funciona.

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

1: // L i s t a d o 26 .5 Ejemplo simple de KDEHelloWorld


2:
3: ffin clud e <kapp.h>
4: tfinclude <ktmainwindow.h>
5:
6: i n t m a in ( in t argc, char ** argv)
7: {
8: K A p p l i c a t i o n MyApp(argc, argv);
9: KTMainWindow ’ MyWindow = new KTMainWindow();
10:
11: MyWindow->setGeometry(50, 50, 200, 100);
12 : MyApp . setMainWidget (MyWindow);
13: MyWindow-> sh ow ();
14: r e t u r n MyApp.e x e c ();
15: }

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

En e s ta a p lic a c ió n , com o en todas las aplicaciones KDE, primero se debe instanciar un


o b jeto K A p p l i c a t i o n , en este caso representado por MyApp en la línea 8.

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().

D espués de esto, instanciamos un objeto KTMainWindow y lo llamamos MyWindow en la línea


9; c o m o el nom bre lo im plica, ésta será la ventana que eventualmente se verá desplegada
en la p a n ta lla . D am o s tam año y colocamos la ventana en la pantalla con la llamada a
s e t G e o m e t r y () en la línea 11. La movemos hacia las coordenadas (50, 50) y cambiamos
el ta m a ñ o a 200 x 100 píxeles (ancho x altura).

P o r ú ltim o , llam am o s a la función miembro setMainWidget () en MyApp, seguida del


m é to d o sho w ( ) en MyWindow, en las líneas 12 y 13, respectivamente. Esto indica a
K A p p l i c a t i o n que su vvidget principal u objeto de despliegue es la ventana que acabamos
de c re a r; al llam ar a s h o w ( ) indicamos a la ventana que se haga visible.
926 D ía 26

Para ejecutar el código tic K A p p l i c a t i o n \ para dibujar la \ entuna principal, llamamos a


la función m iem bro exec( ) en MyApp en la linea 14.

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

Entrada L istado 2 6 . 6 a El archivo de encabezado para el programa KDEHelloWorld

1 : // Listado 26.6a Definición de la clase KDEHelloWorld (lst26-06.hpp)


2:
3: //include <kapp.h>
4: //include <ktmainwindow.h>
5:
6: c l a s s KDEHelloWorld : public KTMainWindow
7: {
8: Q_OBJECT;
9: pu bl ic :
10: void closeEvent (QCloseEvent *);
1 1 : };

L istado 2 6 . 6 b El programa KDEHelloWorld con una clase de ventana


Entrada d e riv a d a

1 : // Listado 26.6b KDEHelloWorld (lst26-6.cxx)


2:
3: //include Mlst26-06.moc"
4: //include " l s t 2 6 -06.hpp"
5:
6: void KDEHelloWorld :: closeEvent (QCloseEvent *)
7: {
8: kapp->quit();
9: }
10 :
1 1 : in t main(int argc, char ** argv)
12: {
13: KApplication MyApp(argc, argv, "Helio World") ; 2 6
14: KDEHelloWorld * MyWindow = new KDEHelloWorld( ) ;
15 :
16: MyWindow ->setGeometry(50, 100, 200, 100);
17 : MyApp. setMainWidget (MyWindow) ;
18: MyWindow ->show();
19: return MyApp. exec() ;
20: }
21 :

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

Cómo puede ver en el código anterior, el cambio mas notable es la macro


A nálisis
extraña Q OBJECT en la línea 8 del listado 26.6a, en la declaración de clase de
KDEHelloWorld, y la directiva //inelude no muy conocida en la línea 3 del listado 26.6b.
1928 Día 26

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.

Los eventos como objetos QEvent


KApplication envía eventos a la ventana activa como objetos QEvent, y entonces los
mismos widgets tienen qué decidir qué hacer con ellos.
Un widget recibe el QEvent y llama a QWidget:: event (QEvent*), que luego decide
cuál evento ha sido detectado y cómo reaccionar; event () es por lo tanto el manejador
principal de eventos.
La función event () pasa el objeto QEvent a los filtros de eventos, los cuales determinan
qué ocurrió y qué hacer con el evento. Si ningún filtro es responsable del manejo de un
evento, se llama a los manejadores especializados de eventos.
En la documentación de Qt verá que todos los manejadores de eventos son funciones
virtuales que se declaran como protegidas; por lo tanto, puede redefinir los eventos que
necesite en sus propios widgets y especificar cómo tiene que reaccionar su widget.
QWidget también contiene algunos otros métodos virtuales que pueden ser útiles en sus
programas.

Cóm o agregar botones a su propia clase


de ventana de KDE
Hasta ahora, estas aplicaciones KDE han sido un poco simples, por no decir más; tal vez
tengamos una GUI, pero no la estamos aprovechando mucho.
En este tercer ejemplo expandiremos el código que tenemos y agregaremos un par de
botones a la aplicación. Al mismo tiempo, también introduciremos un concepto impor­
tante que sostiene todo el marco de trabajo de aplicación de Qt (y también de KDE): las
señales y ranuras.
Programación de la GUI 929

Primero modificaremos nuestra aplicación “Helio World” para utilizar el mecanismo de


señales y ranuras antes de profundizar más en su funcionamiento.
Puede ver el código fuente modificado y los archivos de encabezado en el listado 26.7.

Entrada L istado 2 6 . 7 a Los archivos de encabezado para el programa KDEHelloWorld


1 : // Listado 26.7a Declaración de la clase KDEHelloWorld
2:
3: //include <kapp.h>
4: //include <ktmainwindow.h>
5: //include <qpushbutton. h>
6:
7: c l a s s KDEHelloWorld : public KTMainWindow
8: {
9: Q_OBJ ECT
10: public:
11: KDEHelloWorld ();
12 : void closeEvent(QCloseEvent *);
1 3 : public s l o t s :
14: void S lo t G r ee t () ;
1 5: void Sl ot Q u it ();
16: p r i v a t e :
17: QPushButton * m_btnGreet;
18: QPushButton * m_btnQuit;
19: };
20:

Entrada L is t a d o 2 6 .7 b El programa KDEHelloWorld con botones


1 : // Li stado 26.7b Otro ejemplo de KDEHelloWorld
2: 26
3: //include " l s t 2 6 -07 .moc"
4: //include <kmsgbox.h>
5:
6: KDEHelloWorld :: KDEHelloWorld () : KTMainWindow()
7: {
8: m_btnGreet = new QPushButton ("Greet", this);
9: m_btnGreet->setGeometry(45, 30, 50, 20);
10: m_btnGreet->show();
11: connect(m_btnGreet,
12 : SIGNAL(clickedO),
13: t h is ,
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(clickedO),
21: this,
22: SLOT(SlotQuit()));
23: }
24:
continúa
L is t a d o 2 6 . 7 b continuación

25: void KDEHelloWorld::closeEvent(QCloseEvent *)


26: {
27: kapp->quit();
28: }
29:
30: void KDEHelloWorld::SlotGreet()
31: {
32: KMsgBox::message(0,"Hello World con KDE","Hello World");
33: }
34:
35: void KDEHelloWorld::SlotQuit()
36: {
37: close();
38: }
39:
40: int main(int argc, char ** argv)
41: {
42: KApplication MyApp(argc, argv, "Hello World");
43: KDEHelloWorld * MyWindow = new KDEHelloWorld!);
44:
45: MyWindow ->setGeometry(50, 100, 200, 100);
46: MyApp.setMainWidget(MyWindow);
47: MyWindow ->show();
48: return MyApp.exec();
49: >
50:

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

respuesta a las señales que se emitan. Si analiza la implementación del constructor de


KDEHelloWorlcl. KDEHelloWorld: : KDEHelloWorld (), verá que las llamadas a connect()
de las líneas II y 19 del listado 26.7b actúan para asociar la señal clicked() con los
m iembros s l o t G r e e t () y SlotQuit () de la clase KDEHelloWorld (vea la figura 26.6).
En esencia, lo que esto significa es que. al hacer clic en el widget m_btnGreet, se envía una
señal c l i c k e d ( ) y la biblioteca Qt invocará a la función miembro KDEHelloWorld: :Slot-
G r e e tt) por usted y desplegará un mensaje. De la misma manera, al hacer clic en el
widget m_btnQuit. se enviará una señal clicked( ) y la biblioteca Qt invocará a la fun­
ción miembro KDEHelloWorld: : SlotQuit () por usted y cerrará la ventana.

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.

In te ra c c ió n de objetos por medio de señales y ranuras


El m ecanism o de señales y ranuras ofrece una solución muy poderosa y útil al problema
de interacción de objetos, que por lo general se resuelve mediante el uso de funciones
callback en la mayoría de los kits de herramientas de X Windows.
Si las callbacks no se utilizan de manera correcta, pueden propiciar errores y con frecuencia
conduce al temible fallo de segmentación, por lo que requieren un estricto protocolo de
program ación, y algunas veces hacen que la creación de la interfaz de usuario sea muy
difícil. TrolITech ideó un nuevo sistema en el que los objetos pueden emitir señales que
se pueden conectar con métodos declarados como ranuras.
El m ecanism o de señales y ranuras es una característica central de Qt y es probable­
m ente el elem ento que más distingue al kit de herramientas Qt de los demás kits.
En la mayoría de los kits de herramientas para GUI los widgets tienen una callback para
cada acción que puedan activar. Esta callback es un apuntador a una función. En Qt. las
señales y las ranuras se han encargado del trabajo de estos apuntadores a funciones
propensos a errores.
|932 Día 26

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:};

Una pequeña clase Qt se podría declarar de la siguiente manera:


01: class MiClaseQt : public GObject
02: {
03: Q_OBJECT // observe que no hay punto y coma después de esta macro
04: public:
05: MiClaseQt();
06: char Letter() const { return m_cVal; }
07: public slots:
08: void SetLetter (char cVal);
09: signáis:
10: void LetterChanged(char cVal);
11: private:
12: char m_cVal;
13: };

Esta clase es esencialmente la misma, pero además de la funcionalidad básica de la clase


simple, también tiene soporte para la programación de componentes por medio de señales y
ranuras: esta clase puede decir al mundo que ha cambiado su estado emitiendo una señal,
LetterChanged (), y tiene una ranura a la que los otros objetos pueden enviar señales.
Hemos indicado a MOC que proporcione el código para hacer esto por medio de la macro
Q_OBJECT en la línea 3 y del especificador signáis en la línea 9.

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

He aquí una posible implementación de MiClaseQt: :SetLetter():


01: void MiClaseQt::SetLetter(char cVal)
02: {
03: if (cVal != m_cVal)
04: {
05: m_cVal = cVal;
06: emit LetterChanged(cVal);
07: >
08:}

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 |

Cómo agregar un menú a su propia clase


de ventana de KDE
Por último, daremos a nuestra aplicación KDE simple una última capa de respeto, agre­
gando un menú a la ventana.
Primero necesitamos modificar el código fuente para que se vea igual que el listado 26.8.

Entrada L is t a d o 2 6 . 8 a El archivo de encabezado para el programa KDEHelloWorld


1: // Listado 26.8a Declaración de la clase KDEHelloWorld
2:
3: #include <kapp.h>
4: #include <ktmainwindow.h>
5: #include <qpushbutton.h>
6: ^include <kmenubar.h>
7: ^include <qpopupmenu.h>
8:
9: class KDEHelloWorld : public KTMainWindow
10 : {
11: Q_OBJECT
12: public:
13: KDEHelloWorld();
14: void closeEvent(QCloseEvent *);
15: public slots:
16: void SlotGreet();
17: void SlotQuit();
18: private:
19: QPushButton * m_btnGreet;
20: QPushButton * m_btnQuit;
21: KMenuBar * m_Menu;
22: QPopupMenu * m_MenuApp;
23: >;

Entrada L is t a d o 2 6 . 8 b El programa KDEHelloWorld con botones y un menu

1: // Listado 26.8b KDEHelloWorld


2:
3: #include "lst26-08.moc"
4: #include <kmsgbox.h>
5:
6: KDEHelloWorld::KDEHelloWorld() : KTMainWindow()
7: {
8: m_btnGreet = new QPushButton("Greet", this);
9: m_btnGreet->setGeometry(45, 30, 50, 20);
10: m_btnGreet->show();
11: connect(m_btnGreet,
12: SIGNAL(clickedO),
13: this,
continúa
936 Día 26

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

g ++ -c • ISKDEDIR/include/kde -ISQTDIR/include -fno-rtti lst26-08.cxx


g++ - LSKDEDIR/l i b -lkdecore -Ikdeui -lqt -o KDEHelloWorld main.o \
l s t 2 6 -0 8 . o

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

C re a c ió n de aplicaciones KDE más complejas: KDevelop


L as a p lic ac io n e s de ejem plo que ha visto hasta ahora son simples y sirven sólo para ilustrar
los c o n c e p to s esenciales del marco de trabajo de aplicaciones KDE. Pero para aplicaciones
m ás c o m p lic a d a s , alternar entre muchos archivos fuente y navegar por las clases y otras
co sas m ás es m ucho trabajo; de cualquier forma, ningún escritorio GUI sería respetable sin
un ID E (E n to rn o de D esarrollo Integrado) de GUI, y KDE no es la excepción.
El ID E K D ev elo p proporciona muchas características que los desarrolladores necesitan.
A d e m á s , e n v u e lv e la funcionalidad de proyectos de terceros, como make y los com pi­
la d o re s G N U de C + + , y los convierte en una parte invisible e integrada del proceso de
d e s a rro llo . K D ev elo p ofrece las siguientes características:
• T o d as las herram ientas de desarrollo necesarias para la programación en C++, como
el c o m p ila d o r, el enlazador, automake y autoconf.
• K A p p W iz a rd , que genera aplicaciones de ejemplo completas.
• C la ssG e n e ra to r, que crea nuevas clases y las integra en el proyecto actual.
• A d m in istra c ió n de archivos para archivos fuente, archivos de encabezado, docu­
m en ta c ió n del proyecto, etc.
938 Día 26

• La creación de manuales de usuario escritos con SGML y la generación automática


de salida en HTML con la look and fcel de KDE.
• Documentación automática de API, basada en HTML para las referencias cruzadas
de las clases de su proyecto con las bibliotecas utilizadas.
• Soporte internacional para su aplicación, lo cual le permite agregar fácilmente sus
nuevos lenguajes a un proyecto.
• Creación WYSIWYG de interfaces de usuario con un editor de cuadros de diálogo
integrado.
• Administración de proyectos mediante CVS y la provisión de una interfaz fácil de
usar para las funciones más necesarias.
• Depuración de programas por medio de KDbg.
• Edición de iconos por medio de KiconEdit.
• La inclusión de cualquier otro programa que necesite para el desarrollo, agregándolo
al menú Herramientas de acuerdo con sus necesidades individuales.
KDevelop facilita el trabajo con todos los programas en un solo lugar y ahorra tiempo
al automatizar los procesos estándar de desarrollo, además de proporcionar un acceso
directo y transparente a toda la información que necesite para controlar su proyecto.
Los mecanismos de exploración integrados están diseñados para soportar solicitudes
de documentación que tengan los desarrolladores junto con su proyecto.
El visor de clases y el buscador de errores lo llevan a cualquier parte del código del
proyecto con sólo hacer clic con el ratón, sin necesidad de buscar archivos. Los árboles
de archivos le proporcionan un acceso directo a los archivos del proyecto, y el sistema de
ayuda integrado le ofrece un acceso excelente a la documentación en línea desde
cualquier parte del IDE.
En resumen, KDevelop es un IDE poderoso y completo en características. ¿Ya le men­
cioné que es gratuito?

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

• La biblioteca KHTMLW, que ofrece un widget completo para interpretar HTML


que se utiliza en varios programas, como KDEHélp, KFM y Kdevelop.
• La biblioteca KFM, que le permite utilizarel administrador de archivos de KD'ÍE
desde el interior de su aplicación.
• La biblioteca KAb, o KAddressBook. Proporciona acceso a la libreta de direccio­
nes, por ejemplo para aplicaciones de correo electrónico.
• La biblioteca KSpell, que ofrece widgets y funcionalidad para integrar el uso
de Ispell (el revisor ortográfico común) en aplicacionestales como, .editores; se
utiliza para la aplicación KEdit

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

P ¿Por qué no puedo simplemente utili/.ur CI)K (Kntorno Común de Escritorio)


o Motif como mi GUI?
R Puede hacerlo si lo desea. La belleza de Linux es que es gratuito, y si utiliza software
de GNU o de KDE, éste también es gratuito. Puede obtener versiones de CDEyde
Motif para Linux, pero son software propietario y tiene que pagar por ellos. No sólo
eso, sino que para muchas personas, las GUIs proporcionadas por CDE y Motif se
están empezando a ver un poco anticuadas.
P Si estoy escribiendo una aplicación GUI para GNOME, ¿ podré ejecutarla en
KDE, y viceversa?
R Tal vez. Si escribe aplicaciones para un escritorio, podrá ejecutarlas en el otro si y
sólo si tiene en su equipo las bibliotecas correctas de tiempo de ejecución. Por otra
parte, si funcionan tal vez no se comporten como usted espera y quiera que se com­
porten, ya que la información de sesión persistente de una aplicación GNOME será
ignorada por el escritorio KDE (y esto se aplica también en forma inversa). Loque
es más importante, en algunas instalaciones, las aplicaciones GNOME, al igual
que GIMP, perturban la combinación de colores de KDE cuando tienen el enfoque.
En mi equipo, cuando ejecuto Red Hat Linux 6.1, si ejecuto GIMP en KDE, toda
la pantalla se vuelve azul cuando GIMP tiene el enfoque. Si lo que quiere es una
aplicación genérica, siempre podrá utilizar wxWindows, que utiliza GTK++ pero no
utiliza GNOME.
P ¿Qué es el código multiplataforma? ¿Por qué podría ser importante?
R Es código que se escribe una sola vez y se compila usando el kit de herramientas
adecuado en el equipo de destino. La biblioteca Qt y wxWindows son “independien­
tes de la plataforma”, lo que significa que el código fuente para una aplicación
dada es el mismo, sin importar en qué plataforma se compila; obviamente, las biblio­
tecas de los kits de herramientas son considerablemente distintas, pero eso no debe
preocuparle. Ésta es el área en la que algunas personas piensan que KDE se derrum­
ba. Las bibliotecas Qt tienen una licencia gratuita sólo para Linux y otros sistemas
UNIX. Si quiere crear software para Windows usando las bibliotecas Qt, tiene que
comprar una licencia.
P Quiero utilizar C++, pero no quiero usar wxWindows ni GTK, ni cualquier
otra envoltura. ¿Puedo utilizar C++ directamente en las bibliotecas de
GNOME/GTK++?
R Sí. Aunque esto probablemente produzca código más revuelto, puede declarar sus
funciones callback como miembros estáticos de sus clases. Entonces puede imple-
mentar en cualquier forma que guste las clases que tienen los miembros callback
estáticos y hacer que hagan lo que usted quiera.
Programación de la GUI 941

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

Tabla A .1 Precedencia de operadores

Rango Nombre Operador


l R e so lu c ió n d e á m b ito (b in a rio , u n ario)
2 L lam ad as a fu n c io n e s, p a rén tesis, su b ín d ic e , s e le c c ió n O N
de m iem b ro, p o sfijo s d e in cr e m e n to y d e c r e m e n to . .>
++ . -
3 s i z e o f , c o n v e r sió n d e tip o s en C + + . p refijo s d e in crem en to ++ - -
y d ecrem en to , s ig n o s m ás y m e n o s u n a rio s. n e g a c ió n . +
co m p le m e n to , c o n v e r sió n e x p líc ita en C. s i z e o f ( ). ! - ( conversión
d ire cc ió n , d e sr efer en cia , new. new[ ]. d e l e t e . d e l e t e ( ) exp lícita)
& *
4 S e le c c ió n d e m iem b ro m ed ia n te ap u n tad or .>*
5 M u ltip lic a ció n , d iv id isió n . r e sid u o * / %
6 S u m a, resta + .
7 D esp la z a m ie n to a n iv el d e b its << >>

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

asm float static


auto for static_cast
bool friend struct
break goto switch
case if template
catch inline this
char int throw
class long true
const mutable try
const_cast namespace typedef
continue new typeof
default operator typeid
delete private typename
do protected union
double public unsigned
dynamic_cast register using
else reinterpretcast virtual
enum return void
explicit short volatile
extern signed while
false sizeof
A p é n d ic e

Números binarios, octales,


hexadecimales y una tabla
de valores ASCII
Aprendió los fundamentos de la aritmética hace ya tanto tiempo que es difícil
imaginar cómo serían las cosas si no tuviera esos conocimientos. Al ver el nú­
mero 145. inmediatamente ve “ciento cuarenta y cinco” sin necesidad de mucha
reflexión.
La comprensión de distintos sistemas de notación numérica requiere que reexa­
mine el número 145 y que lo vea no como un número, sino como un código
para (o la representación de) un número.
Empiece con cosas pequeñas: examine la relación que existe entre el número
tres y “3”. El símbolo 3 es un garabato en un pedazo de papel; el número tres
es un concepto. El símbolo se utiliza para representar el concepto de un
número.
Esta distinción se puede aclarar si tomamos en cuenta que tres, 3, III, III y ***
se pueden utilizar para representar el mismo concepto.
950 Apéndice C

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 .

3. S i la te r c e r a c o l u m n a e s 100, e l n ú m e r o m á s g r a n d e q u e p u e d e r e p r esen ta r con dos


c o lu m n a s e s 9 9 . D ic h o e n fo r m a m á s g e n e r a l, c o n n c o l u m n a s p u e d e representar
d e l 0 al ( 1 0«—1). P o r lo t a n to , c o n tr e s c o l u m n a s p u e d e r e p r e s e n ta r d e l 0 al ( 103— 1)
o d el 0 al 9 9 9 .

Más allá de la base 10


No es una coincidencia que utilicemos la base K); tenemos K) dedos. Sin embargo, podría­
mos imaginarnos una base diferente. Usando las reglas encontradas en la base 10,
podríamos describir la base 8 :
1. Los dígitos utilizados en la base 8 son del 0 al 7.
2. Las columnas son potencias de 8 : unos, ochos, sesenta y cuatros, y así sucesivamente.
3. Con n columnas, puede representar del 0 al 8"—1 .
Para distinguir los números escritos en cada base, se escribe la base como subíndice en
seguida del número. El número quince en base 10 se escribe como 15,0 y se lee como
“uno, cinco, base diez”.
Por lo tanto, para representar el número 1510 en base 8 , se escribe 17«. Esto se lee como
“uno, siete, base ocho”. Observe que también se puede leer como “quince octal” ya que
ése es el número que sigue representando.
¿Por qué 17? El 1 significa 8 y el 7 significa siete unidades. Un 8 más siete unidades es
igual a 15. Considere 15 asteriscos:
Números binarios, octales, hexadécimales y una tabla de valores ASCII 951

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.

Un vistazo a las bases


Puede representar el número quince en base 10 como 15, en base 9 como 16„ en base 8
com o 17„, en base 7 como 217. ¿Por qué 217? En base 7 no hay número 8. Para poder
representar el quince, necesita dos sietes y un 1 .
¿Cómo se generaliza el proceso? Para convertir un número base 10 a base 7, piense en las
columnas: en base 7 hay unos, sietes, cuarenta y nueves, trescientos cuarenta y tres, y así
sucesivamente. ¿Por qué estas columnas? Representan 7o, 71, 72, 73, y así sucesivamente.
Puede crear una tabla usted mismo:
4 3 2 1
73 T- 7' 70

343 49 7 1

La primera fila representa el número de la columna. La segunda fila representa la potencia


de 7. La tercera fila representa el valor decimal de cada número de esa fila.
He aquí el procedimiento para convertir un valor decimal a base 7: examine el número y
decida cuál columna utilizar primero. Por ejemplo, si el número es 200, sabe que la colum­
na 4 (343) es 0, y no tiene que preocuparse por ella.
Para averiguar cuántos cuarenta y nueves hay, divida 200 entre 49. La respuesta es 4, así
que coloque 4 en la columna 3 y examine el residuo: 4. No hay sietes en 4, por lo que debe
colocar un cero en la columna de los sietes. Hay cuatro unos en 4, así que coloque un 4 en
la columna de los unos. La respuesta es 4047.
Para convertir el número 968 a base 6 :
5 4 3 2 1
64 6* ó2 6i 60

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 probar esta respuesta, conviértala otra vez:


1 * 64 = 64
0 * 32 = 0
1 * 16 = 16
1 * 8 = 8
0 * 4 = 0
0 * 2 = 0
0 * 1 = 0
88

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.

Por qué base 2


El poder de la base 2 es que corresponde a la perfección a lo que una computadora necesita
representar. Las computadoras realmente no saben nada acerca de las letras, los números,
las instrucciones o los programas. En su interior sólo hay circuitos, y en una unión dada
hay mucha energía, o muy poca.
Para mantener clara la lógica, los ingenieros no tratan esto como una escala relativa
(poca energía, algo de energía, más energía, mucha energía o toneladas de energía), sino
como una escala binaria (“energía suficiente” o “energía insuficiente”). En lugar de decir
“suficiente” o “insuficiente”, lo simplifican como “sí” o “no”. Sí o no, o verdadero o falso,
se puede representar como 1 o 0. Por convención, 1 significa verdadero o sí, pero esto es
sólo una convención; podría, igual de fácil, significar falso o no.
Al dar este gran salto en intuición, el poder del sistema binario se vuelve claro: con unos
y ceros se puede representar la verdad fundamental de todo circuito (hay energía o no hay).
He aquí todo lo que una computadora sabe: “¿Es o No es?” Es = 1; No es = 0.

Bits, Bytes, y Nibbles


Cuando se toma la decisión de representar la verdad y la falsedad con ls y Os, los dígitos
binarios (o bits) se vuelven muy importantes. Debido a que las primeras computadoras
podían enviar 8 bits a la vez, era común empezar a escribir código usando números de 8
bits, conocidos como bvtes.

La mitad de un byte (4 bits) se conoce como nibble.


954 Apéndice C

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 probar esta respuesta, la convertimos en forma inversa:


1 * 64 = 64
3*8 = 24
0 * 1 = 0
88

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.

Por qué octal


El poder de los números octales (base 8) reside en que están en un nivel más alto que los
binarios. Cada dígito octal representa 3 dígitos binarios. Históricamente, los sistemas
computacionales originales en los que se desarrollaron UNIX y C eran equipos de 12 bits.
Se requerían 4 dígitos octales para describir una palabra de memoria.
Cada dígito octal equivale a 3 dígitos binarios. Es muy sencillo alinear los dígitos octales
y convertirlos, uno a la vez, en binarios. Y la conversión a la inversa también es fácil.

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

Traducir el número FC a binario se puede hacer traduciendo primero a base 10 y luego a


binario:
F * 1 6 = 1 5 * 16 = 240
C * 1 = 12 * 1 = 12
252

Convertir el número 252„, a binario requiere la tabla:


Col: 9 8 7 6 5 4 3 2 1
Potencia:: 28 27 26 25 24 23 22 21 20
Valor: 256 128 64 32 16 8 4 2 1
No hay 256s.
1 128 quedan 124
1 64 quedan 60
1 32 quedan 28
1 16 quedan 12
1 8 quedan 4
1 4 quedan 0
0
0
1 1 1 1 1 1 0 0

Por lo tanto, la respuesta en binario es 1 1 1 1 110 0 .


Ahora, resulta que si se trata este número binario como dos conjuntos de 4 dígitos, se puede
hacer una transformación mágica.
El conjunto de la derecha es 1100. En decimal esto es 12, o en hexadecimal es C.
El conjunto de la izquierda es l i l i , que en base 10 es 15, o en hexadecimal es F.
Por lo tanto, tenemos:
1 1 1 1 1100
F C
Números binarios, octales, hexadécimales y una tabla de valores ASCII 957

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

Total Hex: B1D7

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

T a b l a C .1 S i s t e m a s n u m é r i c o s y su s e qu ivalen tes en ASCII


B in ario Octal Hex Decimal ASCII
()()()()()()()() 0 0 0 <nul>
()()()()()()() I I l l <soh>
()()()()()() l() ~t 2 <stx>
()()()()()() I I 3 3 3 <etx>
()()()()() l 00 4 4 4 <eot>
00000 lo i 5 5 5 <enq>
0 0 0 0 0 I K) 6 6 6 <ack>
0 0 0 0 0 Ill 7 7 7 <bel>
0 0 0 0 l 000 I0 8 8 <bs>
0000I001 11 9 9 <tab>
OOOOIOIO l2 a K) <nuevalinea>
0000 l o i l 13 b 11 <vt>
0 0 0 0 l 100 14 c 12 <ff>
0000llo i 15 d 13 <cr>
0 0 0 0 1l K) 16 e 14 <so>
0000llll l7 f 15 <si>
0 0 0 l 0000 20 10 16 <dle>
0 0 0 l0 0 0 I 21 11 17 <dcl>
0 0 0 1 0 0 I0 22 12 18 <dc2>
OOOlOOlI 23 13 19 <dc3>
OOOIOIOO 24 14 20 <dc4>
OOOIOIOI 25 15 21 <nak>
OOOIOI K) 26 I6 22 <syn>
000l0 lll 27 17 23 <etb>
0 0 0 l l 000 30 18 24 <can>
0 0 0 lÎOOI 3l 19 25 <em>
0 0 0 1 ÎOK) 32 la 26 <sub>
000llo il 33 lb 27 <esc>
0 0 0 l l 100 34 le 28 <fs>
000l l l 0 l 35 Id 29 <gs>
000l l l l 0 36 le 30 <rs>

continúa
960 Apéndice C

Ta b la C .1 c o n t in u a c ió n

B in a rio O cta l H ex D e c im a l ASCII


0001 lili 37 If 31 <lls>
00100000 40 20 32 ccspacu»
00100001 41 21 33 !
00100010 42 i ■> 34
00100011 43 23 35 ff
00100100 44 24 36 S
00100101 45 25 37 *7t

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

Binario Octal Hex Decimal ASCII


0 0 1 1 111 I 77 3f 63 ?
01000000 100 40 64 @
01000001 101 41 65 A
01000010 102 42 66 B
01000011 103 43 67 C
01000100 104 44 68 D
01000101 105 45 69 E
01000110 106 46 70 F
01000111 107 47 71 G
01001000 110 48 72 H
01001001 111 49 73 I
01001010 112 4a 74 J
01001011 113 4b 75 K
01001100 114 4c 76 L
01001101 115 4d 77 M
01001110 116 4e 78 N
01001111 117 4f 79 0
01010000 120 50 80 P
01010001 121 51 81 Q
01010010 122 52 82 R
01010011 123 53 83 S
01010100 124 54 84 T
01010101 125 55 85 U
01010110 126 56 86 V
01010111 127 57 87 W
01011000 130 58 88 X
01011001 131 59 89 Y
01011010 132 5a 90 Z
01011011 133 5b 91 [
01011100 134 5c 92 \
01011101 135 5d 93 1
01011110 136 5e 94 A

continúa
962 Apéndice C

Ta b la C .1 c o n t in u a c ió n

B in a rio O cta l Hex D e c im a l ASCII


O lO lllll 137 51 95
O Il00000 140 60 96
OllOOOOl I4l 6l 97 a
OllOOOlO 142 62 98 b
0 l l 0 0 0 Il 143 63 99 c
OllOOlOO 144 64 loo d
OllOOlOl 145 65 IOI e
OllOOllO 146 66 102 I
OIlOOlll I47 67 103 y
OllOlOOO 150 68 104 h
0 l l 0 l00l I5l 69 105 i
OIIOIOIO 152 6a 106 j
OllOlOll 153 6b 107 k
OllOllOO 154 6c 108 I
OllOllOI 155 6d 109 m
OIlOllIO 156 6e 110 n
O llO llll 157 6f 111 o
omoooo 160 70 112 P
OIllOOOI I6l 71 113 q
omooio 162 72 Il4 r
0 1110 0 11 163 73 ll5 s
OllIOlOO 164 74 lió t
OlllOlOl 165 75 117 u
O lllO llO 166 76 ll 8 V
O l l l O l ll 167 77 ll 9 w
OllllOOO 170 78 120 X
OllllOOl I7l 79 I2l y
Olllioio 172 7a 122 z
O llllO U 173 7b 123 {
OlllllOO 174 7c 124 I
Ollllioi 175 7d 125 }
omino 176 7e 126 ~
O lllllll 177 7f 127 <del>
N ú m e ro s b in a rio s , octales, hexadécimales y una tabla de valores ASCII 963

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

Entrada L is t a d o C . 1 Programa creador de la tabla C.1

1: // l i s t a d o C.1 ( ls t x c - 0 1 .cxx) - Creación de la tabla C.1.


2: //include < i o s t r e a m .h>
3: //include <iom anip.h>
4: /¿include <math.h>
5:
6: v o id b in ( in t in , char resultados[] );
7:
8: in t m ain()
9: {
10: in t c ic lo ;
11: c h a r r e s u l t a d o b i n [ 9 ];
12:
13: f o r ( c i c l o = 0; c ic l o < 128; ciclo++ )
14: {
15: b i n ( c i c l o , resultadobin);
16: cout « resultad ob in « " \ t " « oct « ciclo « "\t" «
17: hex « c i c l o « " \ t " « dee « ciclo « " \ t M« (char) ciclo;
18: cout << endl;
19: }
20:
21 : r e t u r n 0;
22: }
23:
24: v o i d b i n ( i n t in , char resultados[] )
25: {
26: in t c ic lo ;
27: f o r ( c i c l o = 0; c i c l o < 8; ciclo++ )
28: {
29: r e s u l t a d o s [ c i c l o ] = in / ( int ) pow( 2, 7 ■ ciclo );
30: i n %= ( i n t ) pow( 2, 7 - ciclo );
31: r e s u l t a d o s f c i c l o ] += ' 0' ;

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

S alida La salida de este programa se muestra en la tabla C. I.

C++ me permite especificar el formato para la salida (oclal. hexadecimal y deci­


A n á l is is
mal). Pero no me permitirá especificar binario como un formato de salida. Así
que tuve que utilizar una función de conversión, cuyo prototipo se muestra en la línea 6,
y la función aparece en las líneas 24 a 36.
El programa principal es muy sencillo: un ciclo f or. Dentro de ese ciclo hay una llamada
a b in () y mi instrucción de salida. En la línea I7. para que cout muestre la copia final de
la variable c iclo como carácter, tuve que convertirla a carácter y no a entero.
La función b in () implementa la conversión de decimal a binario descrita en la sección
“Números binarios“ de este apéndice. Las líneas 29 y 30 utilizan la función pow() para
determinar el valor decimal de una potencia específica de 2. La función pow() se define en
math.h (línea 4) y regresa un tipo double; como se requiere la división y el módulo de
enteros, el resultado de pow() se convierte a tipo int.
El único truco verdadero de esta función está en la línea 3 l , en donde se convierten unos y
ceros enteros en caracteres imprimibles (sumando el valor de un cero ASCII). Un uno
ASCII es un número mayor (6 1 en comparación con 60) que el cero ASCII, por lo que esto
funciona correctamente.
La línea 33 agrega un terminador nulo para que el arreglo resultados se pueda tratar como
una cadena.

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

3. ¿Para qué sirve el enla/.ador?


El trabajo del enla/.ador es unir su código compilado con las bibliotecas proporcio­
nadas con su compilador GNU y de otras fuentes. Hl enla/.ador le permite crear su
programa en piezas y luego enlazar las piezas entre sí para formar un gran programa.
4. ¿ Cuáles son los pasos del ciclo normal de desarrollo?
Editar el código fuente, compilar, enlazar, probar y repetir.

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

5. ¿Pueden los comentarios ser de más de una línea?


Los comentarios estilo C sí. Si quiere extender los comentarios estilo C++ hasta
otra línea, debe colocar otro par de barras diagonales (//).

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

Válido, pero es una mala elección


d. IngresoTotal
Bueno
e. __Invalido
Válido, pero es una mala elección

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;

miEdad: 41, a: 39, b: 41


970 Apéndice D

6. ¿Cuál es el valor de 8+2*37


14
7. ¿Cuál es la diferencia entre x = 3 y x == 3?
La primera instrucción asigna 3 a x y regresa true. La segunda prueba sí x es igual
a 3; regresa true si el valor de x es igual a 3 y f a lse si no lo es.
8. ¿Qué son los siguientes valores, verdaderos o falsos?
a. 0
falso
b. 1
verdadero
c. -1
verdadero
d. x = 0
falso
e. x == 0 / / asuma que x vale 0
verdadero

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

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: }
3. Escriba el programa del ejercicio 2; compílelo, enlácelo y ejecútelo. Escríba los D
números 20, 10, y 50. ¿Obtuvo la salida que esperaba? ¿Por qué no?
Se escribe 20, 10, 50.
Se obtiene a: 20 b: 10 c: 10.
La línea 13 está asignando, no probando igualdad.
4. Examine este programa y trate de adivinar la salida:
1: #include <iostream.h>
2: int main()
3: {
4: int 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 programa del ejercicio 4. ¿Cuál fue la salida?
¿Por qué?
Como en la línea 5 se asigna el valor de a-b a c, el valor de la asignación es a (1)
menos b (1), o 0. Ya que 0 se evalúa como f alse, la instrucción i f falla y no se
imprime nada.

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

3. ¿Cómo se declara una función si no regresa un valor?


Declare la función de forma que regrese void.
4. Si no declara un valor de retomo, ¿qué tipo de valor de retomo se asume?
Cualquier función que no declare explícitamente un tipo de valor de retomo regre­
sa int.
5. ¿Qué es una variable local?
Una variable local es una variable que se pasa a o se declara dentro de un bloque,
que por lo general es una función. Está visible sólo dentro del bloque.
6. ¿Qué es el alcance?
El alcance se refiere a la visibilidad y el tiempo de vida de las variables locales y
globales. El alcance se establece generalmente con un conjunto de llaves.
7. ¿Qué es la recursión?
La recursión se refiere generalmente a la habilidad de una función para llamarse a
sí misma.
8. ¿Cuándo debe utilizar variables globales?
Las variables globales se utilizan generalmente cuando muchas funciones necesitan
tener acceso a los mismos datos. Éstas variables son muy raras en C++; cuando
sepa cómo utilizar apuntadores y pasar valor por referencia, casi nunca tendrá que
crear variables globales.
9. ¿Qué es la sobrecarga de funciones?
Es la habilidad de escribir más de una función con el mismo nombre, que se distin­
gan por el número o tipo de sus parámetros.
10. ¿Qué es polimorfismo?
Es la habilidad de tratar muchos objetos de tipos distintos pero relacionados, sin
importar sus diferencias. En C++, el polimorfismo se logra mediante el uso de
derivación de clases y funciones virtuales.

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

3. CAZA ERRORES: ¿Qué está mal en la función del siguiente código?


tfinclude <iostream.h>
void miFunc(unsigned short int x);
int main()
{
unsigned short int x, y;
y = miFunc(int);
c ou t « "x: “ « x « 0 y: " « y « ^n";
>
void miFunc(unsigned short int x)
{
return (4*x);
}
La función se declara para regresar void y no puede regresar un valor. Se debe
pasar x a miFunc en lugar de int.
4. CAZA ERRORES: ¿Qué está mal en la función del siguiente código?
#include <iostream.h>;
int miFunc(unsigned short int x);
int main()
{
unsigned short int x, y;
y = miFunc(x);
cout << "x: “ « x « " y: 0 « y « “\n";
}
int miFunc(unsigned short int x)
{
return (4*x);
>
Esta función estaría bien, pero hay un punto y coma al final del encabezado de de­
finición de la función. Además, se utiliza el valor x antes de ser asignado, esto
provocará resultados inesperados.
5. Escriba una función que acepte dos argumentos de tipo unsigned short int 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 0, pero regrese el valor -1.
short int Divisor(unsigned short int valUno, unsigned short int
valDos)
{
if (valDos == 0)
return -1;
else
return valUno / valDos;
}
974 Apéndice D

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

10. ¿Qué función se llama para iniciali/ar una clase?


El constructor se llama para iniciali/ar cada objeto (cada instancia) de una clase.

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

cout << "En la compañía AcmeCorp, John y Sally tienen el mismo


trabajo.\n";
Respuestas a los cuestionarios y ejercicios 977

cout « “John tiene “ « John.ObtenerEdad() « ° años de edad y ha


estado en";
cout << “la compañía durante 0 « John.ObtenerAniosDeServicio
<< ”años.\n";
cout << "John gana $“ « John.ObtenerSalario « “ pesos por año.\n\n°;
cout << "Sally, por otro lado, tiene “ « Sally.ObtenerEdad()
<< "años de edad y ha";
cout << "estado con la compañía 0 « Sally.ObtenerAniosDeServicio;
cout « n años. ¡Pero Sally sólo gana $° « Sally.ObtenerSalario();
cout « " pesos por año! Aquí hay algo injusto.";
return 0;
}
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
1()()() más cercano.
long int Empleado:ObtenerMilesRedondeados(int salariojconst
{
return (salario+500) / 1000;
>
5. Cambie la clase Empleado de forma que pueda inicializar edad, aniosDeServicio,
y salario al crear el empleado.
class Empleado
{
public:

Empleado (int edad, int aniosDeServicio, int salario);


int ObtenerEdad()const;
void AsignarEdad(int edad);
int ObtenerAniosDeServicio()const;
void AsignarAniosDeServicio(int anios);
int ObtenerSalario()const;
void AsignarSalario(int salario);
prívate:
int edad;
int aniosDeServicio;
int salario;
};
6. CAZA ERRORES: ¿Qué está mal en la siguiente declaración?
class Cuadrado
{
public:
int lado;
>
Las declaraciones de clases deben terminar con un punto y coma.
978 Apéndice D

7. CAZA ERRORES: ¿Por que no es muy útil la siguiente declaración de clase?


class Gato
{
int ObtenerEdad() const;
prívate:
int suEdad;
};
El método de acceso ObtenerEdad() es privado. Recuerde: todos los miembros de
una clase son privados, a menos que se indique lo contrario.
8. CAZA ERRORES: ¿Cuáles son los tres errores encontrará el compilador en este
código?
class TV
{
public:
void AsignarEstacion(int estación);
int ObtenerEstacion() const;
prívate:
int suEstacion;
};
int main()
{
TV miTV;
miTV.suEstacion = 9;
TV.AsignarEstacion(1 0 );
TV mi0traTv(2);
return 0;
>
No se puede tener acceso directamente a suEstacion. Es privado.
No se puede llamar a AsignarEstacion () en la clase. Puede llamar a
AsignarEstacion () sólo en objetos.
No se puede inicializar suEstacion con el valor 2 ya que no hay constructor rela­
cionado.

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

a. int * apuno; declara un apuntador a un entero.


b. in t varDos; declara una variable de tipo entero.
c. int * apTres = S v a r D o s ; declara un apuntador a un entero y lo inicializa
con la dirección de otra variable.
2. Si tiene una variable de tipo entero corto sin signo llamada suEdad. ¿cómo
declararía un apuntador para que manipule a la variable suEdad?
unsigned short * apEdad = &suEdad;
3. Asigne el valor 50 a la variable suEdad usando el apuntador que declaró en el
ejercicio 2.
*apEdad = 50;

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.
int elEntero;
int *apEntero = &elEntero;
*apEntero = 5;

5. ¿Qué está mal en este código?


tfinclude <iostream.h>
int main()
{
int * aplnt;
*aplnt = 9;
cout << "El valor en aplnt: " << *aplnt;
return 0;
}
aplnt debería haber sido inicializado. Lo que es más importante, como no se
inicializó y no se le asignó la dirección de ninguna memoria, apunta a un lugar
aleatorio en la memoria. Asignar un 9 a ese lugar aleatorio es un error peligroso.
6. CAZA ERRORES: ¿Qué está mal con este código?
int main()
{
int UnaVariable = 5 ;
cout « "UnaVariable: " « UnaVariable << "\n";
int * apVar = &UnaVariable;
apVar = 9;
cout « "UnaVariable: " « *apVar << "\n";
return 0;
}
Tal vaz el programador quiso asignar un 9 al valor contenido en el apuntador apVar.
Desafortunadamente, el 9 se asignó como valor de apVar debido a que se omitió el
operador de indirección (*). Esto provocará un desastre si *apVar se utiliza para
asignar un valor.
Respuestas a los cuestionarios y ejercicios 983 |

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

CrearGato regresa una referencia al objeto GATOcreado en el heap. No hay manera


de liberar esa memoria, y esto produce una fuga de memoria.
9. Corrija el programa del ejercicio 8.
1: #include <iostream.h>
2:
3: class GATO
986 Apéndice D

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

5. ¿Qué diferencia hay entre el constructor de copia y el operador de asignación (=)?


El operador de asignación actúa sobre un objeto existente; el constructor de copia
crea uno nuevo.
6. ¿Qué es el apuntador th is?
Es un parámetro oculto en cada función miembro que apunta al objeto en sí.
7. ¿Cómo puede diferenciar entre la sobrecarga de los operadores de incremento de
prefijo y los de posfijo?
El operador de prefijo no toma parámetros. El operador de posfijo toma un solo !
parámetro in t. el cual se utiliza como señal para el compilador que le indica que
ésta es la variante de posfijo.
8. ¿Puede sobrecargar el operator+ para el tipo de datos short int?
No, no es posible sobrecargar ningún operador para los tipos integrados.
9. ¿Es válido en C++ sobrecargar el operator++ para que decremente un valor de su
clase?
Es válido, pero no es buena idea. Los operadores deben sobrecargarse en una forma
que pueda ser entendida por cualquier persona que lea el código.
10. ¿Qué valor de retorno deben tener los operadores de conversión en sus decla­
raciones?
Ninguno. Al igual que los constructores y los destructores, no tienen valor de
retorno.

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;

5. Cambie la clase CirculoSencillo para que guarde el dato miembro suRadio en el


heap, y corrija los métodos existentes.
class CirculoSencillo
{
public:
CirculoSencillo!);
CirculoSencillo(int);
-CirculoSencillo();
void AsignarRadio(int);
int ObtenerRadio();
const CirculoSencillo & operator++();
const CirculoSencillo operator++(int);
private:
int * suRadio;
};

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(const CirculoSencillo & rhs)


{
int val = rhs.ObtenerRadio();
suRadio = new int(val);

CirculoSencillo::-CirculoSencillo()

delete suRadio;

CirculoSencillo& CirculoSencillo::operator=(const CirculoSencillo & rhs)


if (this == &rhs)
return *this;
‘suRadio = rhs.ObtenerRadio();
return *this;
}

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);
Respuestas a los cuestionarios y ejercicios 991

++(* 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)

return MuyCorto(suVal + rhs.ObtenerSuVal());


>
992 Apéndice D

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?

Un destructor de cualquier clase puede declararse co m o virtual. Cuando se elimina


el apuntador, el tipo del objeto es valo rad o en tiem po de ejecución y se invoca al
destructor derivado apropiado.
3. ¿C ó m o se puede mostrar la d eclaración de un constructor virtual?

N o hay constructores virtuales.


4. ¿C ó m o se puede crear un constructor virtual de co p ia ?

Creando un método virtual en la clase, que a su vez llame al eonstructor de copia.


5. ¿C ó m o se invoca a una función m iem bro de la clase base desde una clase derivada
en la que se haya redefinido esa fu n ción ?
ClaseBase::NombreDeFuncion();
6. ¿C ó m o se invoca a una función m iem bro de la clase base desde una clase derivada
en la que no se haya redefinido esa fu n ció n ?
NombreDeFuncion();
7. S i una clase base declara una función corno 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?

S í, la virtualidad es heredada y no puede desactivarse.

8. ¿P ara qué se utiliza la palabra reservada protected?


L o s m iem bros protected son accesib les para las funciones miembro de los objetos
derivados.

Ejercicios
l. M uestre la declaración de una función virtual que tome un parámetro entero y
regrese v o id .

virtual void UnaFuncion(int);


Respuestas a los cuestionarios y ejercicios 993

2. Muestre la declaración de una clase llamada Cuadrado, la cual se deriva de


Re ct á n g u lo , que a su vez se deriva de Figura.
c la s s Cuadrado : public Rectángulo
{};
3. Si, en el ejercicio 2, Figura no toma parámetros, Rectángulo toma dos (longitud y
ancho), pero Cuadrado toma sólo un parámetro (longitud), muestre la inicializa-
ción del constructor para Cuadrado.
Cuadrado: : Cuadrado(int longitud):
Rectangulo(longitud, longitud)!}

4. Escriba un constructor de copia virtual para la clase Cuadrado (del ejercicio 3).
c la s s Cuadrado
{
public:
// ...

v i r t u a l Cuadrado * clone() const { return new Cuadrado(*this); }


// ...

};
5. C AZA ERRORES: ¿.Qué está mal en este segmento de código?
void UnaFuncion (Figura);
Figura * apRect = new Rectángulo;
UnaFuncion( *apRect);

Tal vez nada. UnaFuncion espera un objeto Figura. Le ha pasado un Rectángulo


“rebanado” como una Figura. Siempre y cuando no necesite ninguna de las partes
de Re ct án g u lo , esto estará bien. Si necesita las partes de Rectángulo, necesita
modificar a UnaFuncion para que tome un apuntador o una referencia a una
Figura.
6. CAZA ERRORES: ¿Qué está mal en este segmento de código?
c l a s s Figura()
{
public:
Figurad ;
v irtu al -Figurad;
v i r t u a l Figura(const Figura &);
};
No se puede declarar un constructor de copia como virtual.
994 Apéndice D

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.

3. Inicialice los m iembros del arreglo de la pregunta 2.


UnArreglo[2 ][3][2 ] = { { {1,2 },{3,4},{5,6} } ,
{ , 8}, {9, 1 0 } , { 1 1 1 2 } } } ;
1

4. ¿Cuántos elementos hay en el arreglo UnArreglo[ 1 0 ][5] [20]?


10 x 5 x 2 0 = 1,0 0 0
5. ¿C u á l es el número m áxim o de elem entos que se pueden agregar a una lista
enlazada?

N o hay un m áxim o fijo. D epende de cuánta m em oria haya disponible.

6. ¿Puede utilizar notación de subíndice en una lista enlazada?

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.

int Tablero[3][3] = { {0,0,0},{0,0,0},{0,0,0} }


3. Escrib a la declaración de una clase Nodo que guarde enteros.
class Nodo
{
public:
Nodo ();
Nodo (int);
~Nodo();
void AsignarSiguiente(Nodo * node) { suSiguiente = node; }

J
Respuestas a los cuestionarios y ejercicios 995

Nodo * ObtenerSiguiente() const { return suSiguiente; }


int ObtenerValor() const { return suVal; >
void Insertar(Nodo *);
void Mostrar();
prívate:
int suVal;
Nodo * suSiguiente;
>;
4. C AZA ERRORES: ¿Qué está mal en este fragmento de código?
unsigned short UnArreglo{5 ][4 ];
for (int i = 0 ; i<4 ; i++)
for (int j = 0 ; j<5 ; j++)
UnArreglo[i] [j] = i+j;
El arreglo es de 5 elementos por 4 elementos, pero el código inicializa 4 x 5 .
5. C A ZA ERRORES: ¿Qué está mal en este fragmento de código?
unsigned short UnArreglo[5 ][4 ];
for (int i = 0 ; i<=5 ; i++)
for (int j = 0 ; j<=4 ; j++)
UnArreglo[i][j] = 0 ;
Quería escribir i<5, pero en vez de eso escribió i<=5.El código se ejecutará cuando
i == 5 y j == 4, pero no existe el elemento UnArreglo[5] [4].

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

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?
Tanto Caballo como Ave inicializan su clase base Animal en sus constructores.
Pegaso lo hace también, y cuando se crea un Pegaso, se ignoran las inicializacio-
nes de Caballo y Ave para Animal.
5. Declare una clase llamada Vehiculo y conviértala en un tipo de datos abstracto,
class Vehiculo
{
virtual void Mover() = 0;
}
6. Si una clase base es un ADT, y tiene tres funciones virtuales puras, ¿cuántas de
estas funciones se deben redefinir en sus clases derivadas?
Ninguna debe redefinirse, a menos que quiera hacer que la clase no sea abstracta,
en cuyo caso las tres deben redefinirse.

Ejercicios
1. Muestre la declaración de una clase llamada AvionJet, que herede de Cohete y de
Avión.

class AvionJet : public Cohete, public Avión


2. Muestre la declaración de una clase llamada 777, que herede de la clase AvionJet
descrita en el ejercicio 1.
class 777 : public AvionJet
3. Escriba un programa que derive a Auto y a Camión de la clase Vehiculo. Convierta
a Vehiculo en un ADT que tenga dos funciones virtuales puras. Haga que Auto y
Camión no sean ADTs.
class Vehiculo
{
virtual void Mover() = 0 ;
virtual void Remolcar() = 0 ;
};
class Auto : public Vehiculo
{
virtual void Mover();
virtual void Remolcar();
};
class Camión : public Vehiculo
{
virtual void Mover();
virtual void Remolcar();
Respuestas a los cuestionarios y ejercicios 997

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 :

long (Auto::*Funcion)(int) elArreglo [10);

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

estática. Luego destruya cada objeto y muestre el efecto en la variable miembro


estática.
1: «include <iostream.h>
2:
3: class miClase
4: {
5: public:
6: miClaseO;
7: -miClase();
8: void MostrarMiembro();
9: void MostrarEstatica();
10: private:
11: int suMiembro;
12: static int suEstatica;
13: };
14:
15: miClase::miClase():
16: suMiembro(l)
17: {
18: suEstatica++;
19: >
20:
21: miClase::-miClase ()
22: {
23: suEstatica— ;
24: cout « "En destructor. suEstatica: 0 « suEstatica « endl;
25: }
26:
27: void miClase: :MostrarMiembro()
28: {
29: cout « “suMiembro: " « suMiembro « endl;
30: >
31 :
32: void miClase::MostrarEstatica()
33: {
34: cout « "suEstatica: " « suEstatica « endl;
35: }
36: int miClase::suEstatica = 0;
37:
38: int main()
39: {
40: miClase obj1;
41 : obj1 .MostrarMiembro();
42: obj 1 .MostrarEstatica();
43:
44: miClase obj2;
45: obj2.MostrarMiembro() ;
46: obj2.MostrarEstatica();
47:
48: miClase obj3;
49: obj3.MostrarMiembro();
ilOOO Apéndice D

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

45: miClase obj2;


46: obj2.MostrarMiembro();
47: cout << "Estática: 0 « miClase: :ObtenerEstatica() « endl;
48:
49: miClase obj3;
50: obj3.MostrarMiembro();
51: cout << "Estática: " « miClase: :ObtenerEstatica() « endl;
52: return 0;
53: >

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

39: int main()


40: {
41 : void (miClase::*AFM) ();
42:
43: AFM=miClase::MostrarMiembro;
44:
45: miClase obj1 ;
46: (obj1.*AFM)();
47: cout << "Estática: << miClase: :ObtenerEstatica() <<
48:
49: miClase obj2;
50: (obj2.*AFM)();
51 : cout << “Estática: " << miClase: :ObtenerEstatica() <<
52:
53: miClase obj 3 ;
54: (obj 3.*AFM)();
55: cout << "Estática: " << miClase: :ObtenerEstatica() <<
56: return 0;
57: >
Agregue dos variables miembro más a la clase de las preguntas anteriores. Agregue
funciones de acceso que obtengan el valor de estos valores y proporcione a todas
las funciones miembro los m ismos valores de retorno y firmas. Utilice el apuntador
a una función miembro para tener acceso a estas funciones.
1 #include <iostream.h>
2
3 class miClase
4 {
5 public:
6 miClase();
7 -miClase();
8 void MostrarMiembro()
9 void MostrarSegunda()
10 void MostrarTercera(),
11 static int ObtenerEstatica();
12 private:
13 int suMiembro;
14 int suSegunda;
15 int suTercera;
16 static int suEstatica;
17 }I
18
19 miClase::miClase():
20 suMiembro(1),
21 suSegunda(2),
22 suTercera(3)
23 {
24 suEstatica++;
25 }
Respuestas a los cuestionarios y ejercicios 1003

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

No, la amistad no es transitiva.


10. ¿Dónde debe aparecer la declaración de una función friend?
En cualquier lugar dentro de la declaración de la clase. No importa si se coloca la
declaración dentro del área de acceso public:, protected: o private:.

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

22 (istreamS elFlujo,Cadenas laCadena);


23 // Métodos generales de acceso
24 int ObtenerLongitud()const { return suLongitud; }
25 const char * ObtenerCadena() const { return suCadena; }
26 // static int ConstructorCuenta;
27
28 private:
29 Cadena (int); // constructor privado
30 char • suCadena;
31 unsigned short suLongitud;
32
33
34
35 ostreamS operator<<(ostreamS elFlujo,Cadenas laCadena)
36 {
37 elFlujo << laCadena.ObtenerCadena();
38 return elFlujo;
39 }
40
41 istreamS operator>>(istreamS elFlujo,Cadenas laCadena)
42 {
43 elFlujo >> laCadena.ObtenerCadena();
44 return elFlujo;
45 }
46
47 int main()
48 {
49 Cadena laCadena("Hola, mundo.");
50 cout « laCadena;
51 return 0;
52 }
5. CAZA ERRORES: ¿Qué está malen este programa?
1: ^include <iostream.h>
2:
3: class Animal;
4:
5: void asignarValor(Animal S , int);
6;
7:
8: class Animal
9: {
10: public:
11 • i-ht ObtenerPeso()const{ return suPeso; }
12: iht ObtenerEdad()const { return suEdad; }
13: private:
14: int suPeso;
15: int suEdad;
16: };
17:
Respuestas a los cuestionarios y ejercicios 1007

18: void asignarValor(Animal & elAnimal, int elPeso)


19: {
20: friend cla ss Animal;
21: elAnimal. suPeso = elPeso;
22: }
23:
24 : int mam ( )
25: {
26: Animal peppy;
27: asignarValor(peppy,5);
28: }

No se puede colocar la declaración friend dentro de la función. Debe declarar la


función como amiga en la clase.
6. Corri ja el listado del ejercicio 5 para que pueda compilarse.
1: //include <iostream.h>
2:
3: c l a s s Animal;
4:
5: void asignarValor(Animal& , int);
6:
7:
8: c l a s s Animal
9: {
10: pu blic:
11: friend void asignarValor(Animal&, int);
12: int ObtenerPeso( )const { returnsuPeso; }
13: int ObtenerEdad() const { return suEdad; }
14: private:
15: int suPeso;
16 : int suEdad;
17: };
18:
19: void asignarValor(Animal& elAnimal, int elPeso)
20: {
21: elAnimal. suPeso = elPeso;
22: }
23:
24: int main()
25: {
26: Animal peppy;
27: asignarValor(peppy,5);
28: return 0 ;
29: }

7. C A ZA ERRORES: ¿Qué está mal en este código?


1: //include <iostream.h>
2
3 c l a s s Animal;
| 1008 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

4. ¿Cuál es la diferencia enlre cin.read( ) y c in .get 1 1ne ()?


cin.read() se utiliza para leer estructuras de datos binarios.
cin.getline( ) se utiliza para leer del hiíler de íostream.
5. ¿Cuál es el ancho predeterminado para enviar como salida un entero largo me­
diante el operador de inserción?
Lo suficientemente ancho para mostrar el número completo.
6. ¿Cuál es el valor de retorno del operador de inserción?
Una referencia a un objeto íostream.
7. ¿Qué parámetro lleva el constructor para un objeto of stream?
El nombre del archivo que se va a abrir.
8. ¿Qué hace el argumento io s: :ate?
ios: :ate lo lleva al final del archivo, pero puede escribir datos en cualquier parte
del archivo.

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.

El primer objeto es la intersección misma. Quizá el objeto intersección mantenga


una lista de autos que esperan las señales de tránsito en cada dirección, así como una
lista de peatones que esperan cruzar las avenidas; se necesitarán métodos para elegir
cuáles y cuántos autos y peatones cruzarán la intersección.
E xistirá sólo una intersección, así que con sid ere co m o asegurar que sólo un objeto
sea instanciado (sugerencia: piense en m étodos estáticos y acceso protegido).

Los vehículos y peatones son clientes de la intersección y comparten ciertas carac­


terísticas: pueden aparecer en cualquier momento, en cualquier cantidad y se
detienen en los semáforos a esperar el paso (aunque en diferente situación). Esto
sugiere una clase base común para peatones y vehículos.
Las clases pueden ser:
class Entidad; //un cliente de la intersección

//La base para todos los autos, bicicletas y vehículos de emergencia,


class Vehículo : Entidad ...;

I I La base para los peatones


class Peatón : Entidad...;

class Auto : public Vehículo...;


class Camioneta : public Vehículo...;
class Motocicleta : public Vehículo...;
class Bicicleta : public Vehículo...;
class VehiculoDeEmergencia : public Vehículo...;
Respuestas a los cuestionarios y ejercicios 1015

// Contiene l a s l i s t a s de autos y peatones que esperan el paso


c l a s s Intersecció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 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

Si decide implementar esta aplicación como un modelo cliente/servidor, el cliente


aceptará información de los usuarios y generará peticiones al servidor. El servidor
deberá atender las peticiones y regresar los resultados hacia el cliente. Con este
modelo, mucha gente podrá programar encuentros y conferencias al mismo tiempo.
En el lado del cliente existirán dos subsistemas principales además del módulo
administrativo: la interfaz de usuario y el sistema de comunicaciones. El servidor
consistirá en tres subsistemas principales: comunicaciones, programación de en­
cuentros y conferencias y una interrfaz para correo que puede anunciar a los usua­
rios cuando hayan ocurrido cambios en los programas (de conferencias).
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.
Un encuentro se define como un grupo de gente que ha reservado un salón durante
un lapso de tiempo. La persona que realiza la reservación podría querer un salón
o un lapso específicos; pero el programador de encuentros y conferencias siempre
debe indicar cuánto durará el último encuentro y quiénes deben asistir.
Probablemente los objetos incluirán a los usuarios del sistema así como los salones
de conferencias. No olvide incluir una clase para el calendario y quizá una clase
Conferencia que encapsule todo cuanto sabe acerca de un evento en particular.
Los prototipos para las clases podrían incluir:
class Calendario; // referencia a una clase posterior
class Conferencia; // referencia a una clase posterior
class Configuración
{
Public:
Configuración();
-Configuración();
Conferencia Programa(ListaDePersonas &, Delta Time
duración);
ouiiTerencia Programa(ListaDePersonas &, Delta Time
duración, Tiempo);
Conferencia Programa(ListaDePersonas &, Delta Time
duración, Salón);
ListaDePersonas & Personas(); // métodos de acceso
públicos
ListaDePersonas & Salones(); // métodos de acceso públicos
protected:
ListaDeSalones salones ;
ListaDePersonas personas;
};
typedef long ID_Salon;
class Salon
{
public:
Respuestas a los cuestionarios y ejercicios 1017

Salon(Cadena nombre, ID_Salon id, int capacidad,


Cadena di reccione s = " , Cadena descripción =
- S a l ó n ();
ClaseCalendario Calendario!);

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;

typedef long ID_Persona;


c l a s s Persona
{
p u b lic :
Persona (Cadena nombre, ID_Persona id);
- P e r s o n a l );
Clase Calendario Calendario!); I I punto de acceso para
I I agregar conferencias
p rotected :
ClaseCalendario calendario;
ID_Persona id;
Cadena nombre;

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

Bloquear (Hora, Duración, Cadena razón = ’"');


// r e s e r v a r tiempo para usted...

p rotected :
ListaOrdenadaDeConferencias conferencias;
}J
c la s s Conferencia
{
public:
Conferencia(ListaDePersonas &, Salón salón,
1018 Apéndice D

Hora cuando, Duración duración, Cadena proposito


=
-Conferencia();
protected:
ListaDePersonas personas;
Salón salón;
Hora cuando;
Duración duración;
Cadena proposito;
};

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

6. ¿Que son los iteradores de la biblioteca estándar de C++?


1.os iteradores son apuntadores generalizados. Un iterador puede incrementarse pa­
ra apuntar al siguiente nodo de una secuencia. También puede ser desreferenciado
para obtener el nodo al que apunta.
7. ¿Qué es un objeto de función?
Un objeto de función es una instancia de una clase donde se define la sobrecarga
del operador (). Puede ser usado como una función normal.

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;

Aquí hay una forma de implementar la plantilla:


témplate <c la ss Type>
c la s s Lista
{
public:
L i s t a ( ) :cabeza(0) ,cola(0) ,laCuenta(0) { }
virtual -L istai)i

void insertar(Tipo valor);


void agregar(Tipo valor);
1020 Apéndice D

int esta_presente(Txpo valor) const;


int esta_vacia() const { return cabeza == 0; }
int cuenta() const { return 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;

2. Escriba la implementación para la versión (que no sea de plantilla) de la clase


Lista .
void List::insert(int value)
{
ListCell ‘pt = new ListCell(value, head);
assert (pt != 0);

// esta linea se agrega para manejar la cola


if (head == 0) tail = pt;
head = pt;
theCount++;
>

void List::append(int value)


{
ListCell ‘pt = new ListCell(value);
if (head == 0 )
head = pt;
else
tail->next = pt;
tail = pt;
theCount++;
>

int List::is_present(int value) const

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

// e s t a li n e a se agrega para manejar la cola


i f (head == 0 ) t a i l = pt;

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

int List<Type>::ispresent(Type valué) const


{
if (head == 0) return 0;
if (head->val == valué ¡¡ tail>val == valué)
return 1;

ListCell *pt = head >next;


for (; pt != tail; pt = pt >next)
if (pt>val == valué)
return 1;
return 0;
}
4. Declare tres objetos de tipo lista: una lista de Cadena, una lista de objetos Gato y
una lista de valores de tipo int.
Lista<Cadena> listaDeCadenas;
Lista<Gato> listaDeGatos;
Lista<int> listaDeEnteros;
5. CAZA ERRORES: ¿Qué esta 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(Félix);
cout << "Félix " <<
(Lista_Gato.esta_presente(Felix)) ? "" : "no " « “ está presente\n°;
PISTA (esto está difícil): ¿Qué hace a Gato diferente de int?
Gato no tiene un operator== definido; todas las operaciones que comparan los
valores de los elementos de Lista, como esta_presente, resultarán en errores de
compilación. Para reducir la probabilidad de esto, ponga comentarios antes de la
definición de la plantilla que establezcan las operaciones a definir para todas las
clases que se utilicen en la plantilla; esto le permitirá compilar de manera segura.
6. Declare el operator== amigo para Lista.
friend int operator==(const Tipo& lhs, const Tipo& rhs);
7. Implemente el operator== amigo para Lista,
témplate <class Type>
int List<Type>::operator==(const Type& lhs, const Type& rhs)
// primero compara la longitud
if (lhs.theCount != rhs.theCount)
return 0; // longitudes diferentes
ListCell *lh = lhs.head;
ListCell *rh = rhs.head;
for(; lh != 0; ih = lh.next, rh = rh.next)
Respuestas a los cuestionarios y ejercicios 1023

if (lh.valué != rh.valué)
return 0;

return 1; //si no son diferentes, entonces son iguales


}
8. ¿Tiene operator== el mismo problema que en el ejercicio 5?
Sí. porque la comparación de los arreglos implica la comparación de los elementos;
el o p e r a to r ! = también debe estar definido para estos elementos.
9. Implemento una función de plantilla para intercambiar dos variables.
// plantilla swap:
/ / debe tener asignación y constructor de copia
// definidos para Type.
témplate <class Type>
void swap (Type& lhs, Type& rhs)
{
Type temp(lhs);
lhs = rhs;
rhs = temp;
>
10. Implemente a ClaseEscuela del listado 19.8 como una lista. Utilice la función
push_back() para agregar cuatro estudiantes a la lista. Luego desplácese por la
lista resultante e incremente en uno la edad de cada estudiante.
#include <list>

template<class T, class A>


void ShowList(const list<T, A>& aList); I I muestra los elementos de la lista

typedef list<Student> SchoolClass;


int main()
{
Student Harry("Harry", 18);
Student Sally("Sally", 15);
Student Bill(“Bill", 17);
Student Peter("Peter", 16);

SchoolClass GrowingClass;
GrowingClass.push_back(Harry);
GrowingClass. push_back (Sally);
GrowingClass.push_back (Bill);
GrowingClass. push_back(Peter);
ShowList (GrowingClass);

cout « "One year later;\n";

for (SchoolClass:-.iterator i = GrowingClass.begin();


1024 Apéndice D

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

cout << endl;


}
11. M odifique el ejercicio 10 para utilizar un objeto de función para desplegar el regis­
tro de cada estudiante.

#include <algorithm>

template<class T>
class Print
{
public:
void operator()(const T& t)
{
cout « t << "\n"•
}
};

template<class T, class A>


void ShowList(const list<T, A>& aList)
{
Print<Student> PrintStudent;

for_each(aList.begin(), aList.end(), PrintStudent);


cout « endl;
}
Respuestas a los cuestionarios y ejercicios 1025

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>

3: // El tipo de datos Exception es una clase abstracta


4: class Exception
5: {
6: public:
7: Exception(){}
Respuestas a los cuestionarios y ejercicios 1029

(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

59 catch (Exceptions theException)


60 {
61 theException.PnntError();
62 }
63 return 0;
64 }
65
66 unsigned int * FunctionTwo()
67 {
68 unsigned int *mylnt = new unsigned int;
69 if (mylnt == 0)
70 throw OutOfMemory() ;
71 return mylnt;
72 }
73
74 void MyFunction()
75 {
76 unsigned int *mylnt = FunctionTwo();
77
78 FunctionThree(mylnt);
79 cout << "Ok. mylnt: " << *mylnt;
80 delete mylnt;
81 }
82
83 void FunctionThree(unsigned int *ptr)
84 {
85 long testNumber;
86 cout « "Enter an int:
87 cin » testNumber;
88 // esta prueba se debe reemplazar por una serie de pruebas
89 // que indiquen una mala entrada,
90 if (testNumber > 3768 ¡¡ testNumber < 0)
91 throw RangeError(testNumber);
92 *ptr = testNumber;
93 }
5. CAZA ERRORES: ¿Qué está mal en el siguiente código?
class xNoHayMemoria
{
public:
xNoHayMemoria()
{
elMsg = new char[ 20 ];
strcpy(elMsje, "error en memoria");
-xNoHayMemoria()
{
delete [I elMsje;
cout << "Memoria restablecida." << endl;
}
char * Mensaje()
{
return elMsje;
}
Respuestas a los cuestionarios y ejercicios 1031

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

4. ¿Qué hace el operador de complemento (~) a nivel de bits?


Invierte el valor de cada bit contenido en un número.
5. ¿Cuál es la diferencia entre OR y OR exclusivo (xor)7
OR regresa true si alguno o ambos bits están encendidos; OR exclusivo regresa
true sólo si alguno de los bits está encendido, pero regresa false si ambos lo están.
6. ¿Cuál es la diferencia entre &y &&?
& es el operador AND a nivel de bits, mientras que && es el operador AND lógico.
7. ¿Cuál es la diferencia entre | y | |?
I es el operador OR a nivel de bits, mientras que I I es el operador OR lógico.

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 (&).

De la misma manera, el resultado de la suma es una operación XOR (A): si cualquie­


ra de los bits es 1 (pero no ambos), el resultado es 1; de otra forma, el resultado
es O.
Cuando tenga un acarreo, éste se sumará al bit más significativo (al bit que está a
la izquierda). Esto implica que se puede utilizar una recursión o una iteración,
« i n e l u d e < i o s t r ea m .h >

u n s i g n e d i n t suma(unsigned int bit 1, unsigned int bit2)

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;

bi11 = acarreo << 1;


bit2 = resultado;
}
return resultado;
}
int main()
{
unsigned long a, b;
for (;;)
{
cout << "Escriba dos números. (0 0 para salir): ";
cin >> a >> b;
if (!a && !b)
break;
cout <<a < < “ + " < < b << " = " << suma(a,b) << endl;
}
return 0;
}
C o m o u n a a lt e r n a tiv a , p u e d e r e s o lv e r e l p r o b le m a c o n r e c u r s ió n :

#include <iostream.h>

unsigned int suma(unsigned int bit1, unsigned int bit2)

unsigned int acarreo = bitl & bit2;


unsigned int resultado = bitl ' bit2;
if (acarreo)
return suma(resultado, acarreo << 1);
else
return resultado;
}
int main()
{
unsigned long a, b;
for (;;)
{
cout « "Escriba dos números. (0 0 para salir): ";
cin >> a >> b;
if (la && ¡b)
break;
cout « a « ■ + " « b « " = “ « suma(a,b) « endl;
}
return 0;
}
Respuestas a los cuestionarios y ejercicios 1035

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

2. Modifique el archivo make para enlazar la nueva función.


#
# Makefile para los ejercicios del capitulo 22
« sin bibliotecas compartidas.
#
# $Header$
# $Id$
ft
# Makefile para construir el programa del dado
ft sin bibliotecas compartidas.

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

3. A nalice el programa paso a paso con gdb.

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

3. ¿Cuál es la diferencia entre un semáforo binario y un semáforo de conteo?


Conceptualmente, existen dos tipos de semáforos: los binarios y los de conteo. Un
sem áforo binario sólo toma los valores cero y uno, y funciona como un mutex.
Un sem áforo de conteo puede tomar valores arbitrarios ya sea cero o cualquier
número positivo.
4. Enliste y defina los cuatro requisitos para que se produzca un punto muerto.
1. Exclusión mutua. Por lo menos un bloqueo no es compartible.
2. Ocupar y esperar. Un subproceso está ocupando un recurso y esperando un
recurso que está siendo ocupado por otro subproceso.
3. N o preferencia. Un recurso ocupado sólo puede ser liberado por el subproceso
que lo posee.
4. Espera circular. Debe existir un conjunto de subprocesos en espera, {t0, t1,
*2, ... 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á espérando un blo­
queo ocupado por t0.

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;

void read_thread(void* param)


{
CondVar* apCond = static_cast<CondVar*>(param);

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();
}

int main(int argc, char** argv)


{
CondVar lock;
Thread threadl((void*)&write_thread, &lock);
Thread thread2 ((void*)&read_thread, &lock);

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

2. ¿Que señal se produce si los extremos de lectura y de escritura de una tubería no


están preparados?
S IG P IP E

3. ¿Por qué la memoria compartida es más rápida que los mensajes?


La copia de los datos no se realiza desde el núcleo; realmente se realiza el acceso
desde/hacia la memoria compartida directamente.
4. ¿Qué es un semáforo binario?
Un sem áforo que sólo puede tener los valores cero o uno.

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

// 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)
{
sem->Acquire();
memepy(Buffer, smem, BUFFER_SIZE);
sem->Release();
>
// limpieza
sem->Destroy();
smem->Destroy();
1042 Apéndice D

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);
}

INCLUDES3 -I../inc -1../SharedMemory -1../Semaphores -I../Key


CFLAGS= -Wall
0BJS=../obj/key.o ../obj/semap.o ../obj/smem.o
all: client server
client: client.cxx
gcc -o client client.cxx $(0BJS) -lstdc++ $(INCLUDES)
server: server.cxx
gcc -o server server.cxx $(OBJS) -lstdc++ $(INCLUDES)
Respuestas a los cuestionarios y ejercicios 1043

« d e fin e SEMKEY 1122


« d e fin e SEMPERM 0666
« d e fin e SMEM_KEY 5678
« d e fin e SMEMPERM 0666
« d e fin e B U F F E R _ S IZ E 4096

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

« in c lu d e < io stre a m >


« in c lu d e " l s t 2 5 - 0 2 . h"

u s in g nam espace std;

in t m a in (in t argc, c h a r * * argv)


{
P ip e * r p = new P i p e ;
P ip e * wp = new P i p e ;

c h a r m sg[] = "¡H o la , m u n d o !\n a ;


c h a r b u f [1 2 8];

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)

pipetest: pipe.o main.o


gcc -o pipetest main.o $(OBJS) -lstdc++

»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 ]);

int Pipe ::ReadFromPipe(char* buf)


Respuestas a los cuestionarios y ejercicios 1045

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;

write (pipe_[ 1), buf, strlen(buf));


>
3. Extienda la clase N a m e d P i p e para que pueda abrir tuberías que no se bloqueen y
una función miembro R e a d () que no bloquee si no hay datos disponibles. Hacer
esto permite que el objeto N am edPipe detecte si no existen datos y entonces realice
otras acciones.
El primer listado es el cliente; el segundo es el servidor; el tercero es la implemen-
tación de la tubería con nombre; el cuarto es el archivo de encabezado y el último
es el archivo make.
# in c lu d e < io stre a m >
# in c lu d e "n p .h "

in t m a in (in t argc, char**argv)

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

sp rin tf(b u f, "% d", i++ );

p ip e -> W r ite (& b u f [0], sta tic _ c a s t< in t> (strle n (b u f)));

cout « "Client" « buf « endl;


} w h ile (i < 10);

p i p e - > C l o s e ( );
1046 Apéndice D

pipe->Destroy();

delete pipe;
}
#include <iostream>
^include °np.h°

int main(int argc, char**argv)


{
NamedPipe* pipe = nev/ NamedPipe;

pipe->Create();
pipe> 0 pen();

int i = 0 ;
do
{
const int len = 32;

char buf[len];

if (pipe->Read(buf, len) > 0 )


cout << "server" << buf << endl;

> while (i < 1 0 );

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 stat = mknod(noinbre, S_IFIF0 | 0666, 0); D


nombre_ = new char[strlen(nombre) ];
strcpy(nombre_, nombre);
init_ = true;

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: :Create(char* nombre)


{
mknod(nombre, S_IFIFO¡0666, 0);

nombre_ = new char[ strlen(nombre) ];


strcpy(nombre_, nombre);
init_ = true;

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

int NamedPipe::Write(char* buf, int len)


{
write(fp_, buf, len);

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"

class NamedPipe : public Object


{
public:
NamedPipe();
-NamedPipe();
int Create();
int Create(char * name);
int Destroy();
int Open();
int Close();
int Read(char * buf, int len);
int Write(char * buf, int len);
private:
//no permitir la copia
NamedPipe & operator=(const NamedPipe &);
bool init_;
FILE * fp_;
char * name_;
};
«endif
INCLUDES3•I../inc -I../Key
CFLAGS=-c
OBJS= ../obj/key.0 .,/obj/np.o
all: client server

np.o : np.cxx np.h


Respuestas a los cuestionarios y ejercicios

g c c $ ( C FL AG S) -o np.o np.cxx S(INCLUDES)


cp n p . o . . /obj

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

static void do_message(const char*);


};
void
callback ::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
callback::button_clicked(GtkWidget ‘button, gpointer data)

do_message((char*)data);

// Llamada cuando el usuario cierra la ventana,


gint
callback::delete_event(GtkWidget ‘widget, GdkEvent ‘event,
»»gpointer data)
{
// Indica el ciclo principal que va a salir.
g_print ("using C++ callback to quit\n");
gtk_main_quit();
// Devuelve FALSE para seguir cerrando la ventana,
return FALSE;
}
int
main(int argc, char *argv[])
{ •
GtkWidget *app;
GtkWidget ‘button;
GtkWidget *hbox;

// Inicializa GNOME, es muy similar a gth_int.


gnome_init ("buttons-basic-example", "0.1", argc, argv);

// Crea un widget de aplicación de Gnome que configura


una ventana básica para su aplicación,
app = gnome_app_new ("buttons-basic-example",
"Buttons");

/* asocia "delete_event", el evento que obtenemos cuando


1052 Apéndice D

el usuario cierra una ventana desde el manejador de ventanas


a gtk_main_quit, la función que causa la salida del ciclo
gtkjnain y por lo tanto de la aplicación*/
gtk_signal_connect (GTKOBJECT (app), "delete_event ’,
GTK_SIGNAL_FUNC (callback::delete_event),
NULL);

// Crea un cuadro horizontal para los botones


y lo agrega en el widget de aplicación,
hbox = gtk_hbox_new (FALSE,5);
gnome_app_set_contents (GNOMEAPP (app), hbox);

// Crea un botón y lo agrega al cuadro horizontal,


le asocia el clic del ratón al método button_clicked.
button = gtk_button_new_with_label("Button 1“);
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 1\n");

// 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");

// Muestra todo lo que está dentro del widget


de aplicación y al widget mismo.
gtk_widget_show_all(app);

// Entra al ciclo principal,


gtkjnain ();

return 0;

2. Tom ando com o base el ejem plo final d e w x W in d o w s o el de K D E , 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.

E n el listado e x 26 -0 2K D E .h verá que hem os ag reg ad o una variable miembro button


adicional, m _btnBrowse. L a in icializam os al crear un nuevo botón en e x 2 6 -0 2 -
K D E.cxx y utilizam os el m ecanism o de señal/ranura para conectarla al manejador
S l o t B r o w s e ( ) de la aplicación. Dentro del m anejador utilizam os la función miem­
bro estática Q f i l e D i a l o g : :g e tO p e n F ile N a m e () para abrir un diálogo estándar para
exam in ar archivos. V ea que no tenem os el arch ivo # in c lu d e "ex26-01KD E.m oc"
en nuestros listados. Esto es normal porque M O C crea el archivo .moc por nosotros
cuando procesa el archivo de encabezado.
Respuestas a los cuestionarios y ejercicios 1053

/*
* e x 2 6 - 0 1K D E . h
*/

// inc lude < k a p p . h >


/ / i n c l u d e <kt mai nwi ndow. h>
// inc lude < q p u s h b u t t o n . h>
// inc lude <kmenubar.h>
/ / inc l ude <qpopupmenu. h>

c l a s s KDE He 1 loWor I d : p u b l i c KTMainWindow


{
Q_0BJ ECT
pu blic:
KDEHel l oV Vor l d ( ) ;
v o i d c l o s e E v e n t (QCloseEvent * ) ;
public slo ts:
v o i d S l o t G r e e t () ;
void S l o t Q u i t ();
v o i d Sl ot Br ovvs e ( ) ;
private:
Q P u s h B u t t o n *m_btnGreet;
QPushButton *m_btnQuit;
Q P u s h B u t t o n *m_btnBrowse;
K Me nuB ar *m_Menu;
QPopupMenu *m_MenuApp;
}I
/*
* e x 2 6 - 0 1 K D E . cxx
*/

/ / inc lude " ex26KDE -01 .moc"


/ / inc lude < k a p p. h >
/ / i nc l ud e <ktmainwindow.h>
/ / i nc l ud e <kmsg bo x. h>

K D E H e l l o W o r l d : : K DEH el l oWor ld() : KTMainWindow()

m _ b tn G r ee t = new QPushButton( "Greet", this );


m _ b tn G r ee t ->setGeometry (45,30,50,20);
m _ b tn G r ee t - > s h o w ( );
c o n n e c t (m_btnGreet, S IGN AL ( cl ic k ed ()), this, SLOT(SlotGreet())) ;

m _ b t n Q u i t = new QPushButton ( " E x i t " , this );


m _ b t n Q u i t - >setGeometry( 105,30,50,20);
m_btnQuit->show() ;
connect (m_btnQuit, SIGNAL(clicked()), this, SL0T(SlotQuit ()));
m _ b t n Q u i t = new QPushButton( "Browse", t h is ) ;
m _ b t n Q u i t - >setGeometry( 155,30,50,20);
1054 Apéndice D

m_btnQuit->show();
connect(mbtnQuit, SIGNAL(cllcked()) , this, SLOT(SlotBrowse()));

m_MenuApp = new QPopupMenu() ;


m_MenuApp>insertItem("&Greet", this, SLOT(SlotGreet()));
m_MenuApp->insertItem(*&Quit’, this, SLOT(SlotQuit()));
m_MenuApp->insertItem("&Browse", this, SLOT(SlotBrowse()));
m_Menu = new KMenuBar(this);
m_Menu-»insertItem(“&Application", m MenuApp);
}
void KDEHelloWorld::closeEvent(QCloseEvent *)
{
kapp->quit();
}

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 ();
}

int main(int argc, char **argv)

^Application MyApp(argc, argv);


KTMainWindow *MyWindow = new KTMainWindow();
MyWindow->setGeometry(50,50,200,100 );
MyApp.setMainWidget(MyWindow);
MyWindow->show();
return MyApp.exec();
}

//Compile estos archivos con los comandos:


//moc ex26-01KDE.h -o ex26-0lKDE.moc
//g++ -c -ISKDEDIR/include -ISQTDIR -fno-rtti ex2 6 -01 KDE.cxx
//g++ -L$KDEDIR/lib -lkdecore -lkdeui -lqt -o ex26-0lKDE ex26-01KDE.o
Respuestas a los cuestionarios y ejercicios 1055 |

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

// Para compiladores que soporten precompilación, incluya “wx/wx.h".


//include "wx/wxprec.h"

//ifdef _BORLANDC__
//pragma hdrstop
//endif

// Para compiladores que soporten precompilación, incluya “wx/wx.h0.


//ifndef WX_PRECOMP
//include "wx/wx.h"
//endif

class MyApp: public wxApp


{
virtual bool 0nlnit();
}5

class MyFrame: public wxFrame


{
public:

MyFrame (const wxCadena& title, const wxPoint& pos,


'"••const wxSize& size);
void OnQuit(wxCommandEvent& event);
void OnGreet(wxCommandEvent& event);
void OnBrowse(wxCommandEvent& event);
DECLARE_EVENT_TABLE()
private:
wxPanel *m_panel;
wxButton *m_btnGreet;
wxButton *m_btnQuit;
wxButton *m_btnBrowse;
};
1056 A p é n d ice D

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

MyFrame::MyFrame(const wxCadena& title,


«•const wxPoint& pos, const wxSize& size)
: wxFrame((wxFrame *)NULL, -1, title, pos, size)
{

wxSize panelSize = GetClientSize();


const int buttonWidth=50;
const int buttonHeight=25;
const int buttonSpacing=l0;
const wxSize buttonSize(buttonWidth, buttonHeight);

int height = panelSize.GetHeight();


int width = panelSize.GetWidth();
m_panel = new wxPanel(this, -1, wxPoint(0, 0), panelSize);

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

mbtnBrowse = new wxButton(m_panel, ID_Browse,


_T(“Browse..."),
wxPoint((width/2)+
*•“(buttonWidth/2+buttonSpacing), 10), buttonSize);
wxMenu ‘menuApp = new wxMenu;

menuApp->Append(lD_Greet, °&Greet. ..");


menuApp->Append(ID_Browse, “&Browse...*);
menuApp->AppendSeparator();
menuApp ->Append(ID_Quit, "&Quit“);

wxMenuBar ‘menuBar = new wxMenuBar;


menuBar->Append(menuApp , °&Application");
SetMenuBar(menuBar);

void MyFrame: :0nQuit(wxCommandEvent& WXUNUSED(event))


Close(TRUE);

void MyFrame::OnBrowse(wxCommandEvent& WXUNUSED(event))

const wxCadena& file = ::wxFileSelector("Select a file");

void MyFrame: :0nGreet(wxCommandEvent& WXUNUSED(event))

wxMessageBox("This is a wxWindows Hello world sample",


"Greet: Hello World", wxOK ¡ wxICONJNFORMATION, this);

// compile estos archivos con el comando:


// gcc -g -Wall 'gnome-config --cflags gnome gnomeui \
// LDFLAGS='gnome-config \
// --libs gnome gnomeui' ex26 -02wxWin.cxx -o ex26-02wxWin
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.
1058 A pén dice D

E x te n d a m o s la d eclaració n de la cla se KDEHelloWorld en el listado ex26-03KDE.h


para in clu ir la variable m iem bro QCadena que con ten d rá el nombre del archivo que
seleccio n arem o s. T am bién ag re g a re m o s el control QtextView que mostrará el texto
en el área del cliente. C rearem o s la vista del texto en el constructor y lo posicionare-
m os de m anera arbitraria en la ven tan a p rin cip al.
A h o ra exten dam os la función de ranura S l o t B r o w s e ( ) para tomar el nombre de
arch ivo del d iálo go para e x am in ar a rc h iv o s, que d esp u és usarem os para inicializar
un objeto Q F ile . A b rim o s el arch iv o co n la fu n ción m iem bro o p e n () de Q File y
hacem os un cic lo de lectura, una línea a la v e / , hasta que h ayam o s leído el archivo
com pleto dentro de un o bjeto string tem poral. D e sp u é s de esto, estableceremos el
objeto string co m o el texto que será m ostrad o en el objeto texl-vievv. Es un enfoque
m uy sim plista trabajar únicam ente co n a rch iv o s de texto: si lee un archivo binario,
podría ver algunos resultados extrañ os.
/*
* ex26-03KDE.h
*/
»inelude <kapp.h>
»inelude <ktmainwindow.h>
»inelude <qpushbutton.h>
»inelude <kmenubar.h>
»inelude <qpopupmenu.h>

class KDEHelloWorld : public KTMainWindow


Q_0BJECT
public:
KDEHelloWorld();
void closeEvent(QCloseEvent *);
public slots:
void SlotGreet();
void SlotQuit();
void SlotBrowse();
private:
QPushButton *m_btnGreet;
QPushButton *m_btnQuit;
QPushButton *m_btnBrowse;
QTextView *m_textView;
QCadena *m_fileName;
KMenuBar *m_Menu;
QPopupMenu *m_MenuApp;
};
/*
* ex26-03KDE.cxx
*/

»inelude "ex26KDE-03.moc"
»inelude <kapp.h>
»inelude <ktmainwindow.h>
Respuestas a los cuestionarios y ejercicios 10591

«include <kmsgbox.h>

KDEHel loWorld::KDEHelloWorld() : KTMainWindow()


{
mbtnGreet = new QPushButton("Greet", this);
mbtnGreet •>setGeometry (45,30,50,20);
m b t n G r e e t ->show();
connect (mbtnGreet, SIGNAL(clicked()), this, SL0T(SlotGreet()));

m_btnQuit = new QPushButton("Exit", this);


mbtnQuit •>setGeometry( 1 0 5 ,3 0 ,5 0 ,2 0 );
m_btnQuit->show();
connect (mbtnQuit, SIGNAL (clicked()), this, SL0T(SlotQuit()));

m_btnQuit = new QPushButton("Browse", this);


m btnQuit >setGeometry(155,30,50,20);
m_btnQuit->show();
connect (m_btnQuit, SIGNAL (clicked ()), this, SL0T(SlotBrowse()));

m_textView = new QTextView(this);


m_textView->setMinimumSize(450, 250);
m_textView->show();

m_MenuApp = new QPopupMenu();


m_MenuApp->insertItem("&Greet°, this, SL0T(SlotGreet()));
m_MenuApp->insertltem(“&Quit", this, SL0T(SlotQuit()));
m_MenuApp->insertItem("&Browse", this, SLOT(SlotBrowse()));
m_Menu = new KMenuBar(this);
m_Menu ->insertltem( "»Application", m_MenuApp);

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

if (theFile.open(I0_Read0nly)) {// Archivo abierto exitosamente


QTextStream elFlujoí&theFile); ¡¡ use un "text Stream*
QCadena text;
int n = 1 ;
while (!elFlujo.eof()) { // Hasta el fin de archivo...
text += elFlujo.readLine();
*•// Linea de texto sin '\n
}
theFile.close();
m_textView.setText(text);
}
}
}

void KDEHelloWorld::SlotQuit()
{
close();
}

int main(int argc, char **argv)

KApplication MyApp(argc, argv);


KTMainWindow ‘MyWindow = new KTMainWindow();
MyWindow->setGeomet ry(100,100 f500 ,500 );

MyApp.setMainWidget(MyWindow);
MyWindow->show();
return MyApp.exec();

//Compile estos archivos con los comandos:


//moc ex26-03KDE.h -o ex26-03KDE.moc
//g++ -c -ISKDEDIR/inelude -ISQTDIR -fno-rtti ex26-03KDE.cxx
//g++ -L$KDEDIR/lib -lkdecore -lkdeui -lqt -o ex26-03KDE ex26-03KDE.o

L a siguiente es una versión m ás sim ple que la de K D E . A g r e g a m o s un control


wxTextCtrl a la cla se MyFrame d eclarada en ex2 6 -03 wxWin . c x x , asi com o
wxCadena para alm acen ar el nom bre del arch iv o . E s to es m u y parecido a lo que
h icim o s para el ejem p lo de K D E . In icializam o s el control de texto en el construc­
tor de MyFrame. En el m anejador de even to s, MyFrame: :OnBrowse(), abrir y
m ostrar el arch ivo es tan sim ple co m o obtener el nom bre de arch ivo e indicarle al
control de texto que cargu e los datos del arch iv o con la llam ada a LoadFile(), que
pertenece al objeto de control de texto.
/*
* ex26-03wxWin.cxx
*/
R e sp u e sta s a los cuestionarios y ejercicios 1061 |

«ífdef _GNUG__
// «pragma implementation
«end if

// Para compiladores que soporten precompilación, incluya "wx/wx.h"


«include “wx wxprec.h*

«lfdef _BORLANDC__
«pragma hdrstop
«endif

// Para compiladores que soporten precompilación, incluya "wx/wx.h"


«ífndef WXPRECOMP
«include "wx/wx.h"
«endif

class MyApp: public wxApp


{
virtual bool 0 nlnit();
};

class MyFrame: public wxFrame


{
public:

MyFrame(const wxCadena& title, const wxPoint& pos,


«-•const wxSize& size);
void OnQuit(wxCommandEvent& event);
void OnGreet (wxCommandEvent& event);
void OnBrowse(wxCommandEvent& event);
DECLARE_EVENT_TABLE()
private:
wxPanel *m_panel;
wxButton *m_btnGreet;
wxButton *m_btnQuit;
wxButton *m_btnBrowse;
wxCadena m_fileName;
wxTextCtrl *m_textViewer;
>;

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)

bool MyApp::On In it()


{
MyFrame 'frame = new MyFrame("Hello World“,
»»wxPoint (50,50), wxSize(500,500));
frame->Show(TRUE);
SetTopWindow(frame);
return TRUE;

MyFrame::MyFrame(const wxCadena& title, const wxPoint& pos,


‘»const wxSize& size)
: wxFrame((wxFrame *)NULL, -1, title, pos, size)
{
wxSize panelSize = GetClientSize();
const int buttonWidth=50;
const int buttonHeight=25;
const int buttonSpacing=l0;
const wxSize buttonSize(buttonWidth, buttonHeight);

int height = panelSize.GetHeight();


int width = panelSize.GetWidth();
m_panel = new wxPanel(this, -1, wxPoint(0, 0), panelSize);

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,
_T("Quit"),
wxPoint((width/2)-(buttonWidth/2),
*»10), buttonSize);
m btnBrowse = new wxButton(m_panel, ID_Browse,
_T("Browse..."),
wxPoint((width/2)+
^(buttonWidth/2 +buttonSpacing), 10), buttonSize);

m_textViewer = new wxTextCtrl(m_panel, ID_Browse,


_T(""),
wxPoint(10,40),
Respuestas a los cuestionarios y ejercicios 1063

»•■wxSize (width-20, height-100),


wxHSCROLL¡wxTE_MULTILINE¡wxTE_READ0NLY);

wxMenu ‘menuApp = new wxMenu;

menuApp ->Append (ID_Greet, “&Greet...");


menuApp ->Append(ID_Browse, °&Browse
menuApp ->AppendSeparator();
menuApp ->Append (ID_Quit, "&Quit");

wxMenuBar ‘menuBar = new wxMenuBar;


menuBar->Append(menuApp , °&Application°);

SetMenuBar(menuBar);

void MyFrame: :0nQuit (wxCommandEvent& WXUNUSED(event))


{
Close(TRUE);
>

void MyFrame::OnBrowse(wxCommandEvent& WXUNUSED(event))


{
m_fileName = ::wxFileSelector("Select a file");
if (!m_fileName.IsEmpty()) {
m_textViewer->LoadFile(m fileName);
}
>

void MyFrame::OnGreet (wxCommandEvent& WXUNUSED(event))


{
wxMessageBox("This is a wxWindows Hello world sample",
"Greet: Hello World", wxOK \ wxIC0N_INF0RMATI0N, this);

// compile este archivo con el comando:


// gcc -g -Wall 'gnome-config --cflags gnome gnomeui\
// LDFLAGS='gnome-config \
// --libs gnome gnomeui' ex26-03wxWin.cxx -o ex26-03wxWin
4. A nalice 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_m essage() en el archivo lst26-01 .cxx. Piense cómo podría extender
el concepto para crear una envoltura genérica para la aplicación, y utilizar fun­
ciones virtuales para configurar la ventana principal y conectar señales.
11064 Apéndice D

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.

C ream os los bolones m ism os en CreateWidgets () y los integramos dentro del


cuadro. Vea que la nueva clase en cap su la todos los w id gets que utilizamos, y que
las funciones m iem bro estáticas de la m ism a cla se m anejarán los eventos que se
generen cuando haga clic en cada uno de los botones.

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;

button = gtk_button_new_with_label("Button 1");


gtk_box_pack_start (GTK_B0X(hbox), button, FALSE, FALSE, 0);

gtk_signal_connect (GTK_0BJECT (button), “clicked",


GTK_SIGNAL_FUNC (TheAppClass::button_clicked),
"Button 1");

button = gtk_button_new_with_label("Button 2");


gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
gtk_signal_connect (GTK_0BJECT (button), "clicked",
Respuestas a los cuestionarios y ejercicios 1065

GTK_SIGNAL_FUNC (TheAppClass::button_clicked),
’Button 2#);
return 0;
}

TheAppClass::TheAppClass(int argc, char* argv[])


{

gnomeinit ("buttons-basic-example", "0.1°, argc, argv);


D
app = gnome_app_new ("buttons-basic-example",
»»‘Class-Wrapped Buttons’);

gtk_signal_connect (GTK_0BJECT (app), "delete_event",


GTK_SIGNAL_FUNC (TheAppClass::delete_event),
NULL);

hbox = gtk_hbox_new (FALSE,5);

gnome app set contents (GN0ME_APP (app), hbox);

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);
}

/ / Llamada cuando el usuario cierra la ventana,


gint
TheAppClass: :delete_event(GtkWidget ‘widget, GdkEvent *event,
»»gpointer data)
{
gtk_main_quit();
return FALSE;
}
1066 A péndice D

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

// compile este archivo con el comando:


// gcc -g -Wall gnome-config -cflags gnome gnomeui\
// LDFLAGS='gnome-config \
// --libs gnome gnomeui' ex26-04.cxx -o ex26-04
I n d ic e
++ (operador de prefijo),
Símbolos #include, directiva, 101-102,
748 308-310
{ } (llaves), 31 . (operador de punto), 155,
\b código de escape, 58 241
\n código de escape, 58
\ (barra diagonal inversa), 754 « (operador de redirecciona-
\n (código de nueva línea), 33
> (carácter de redirección miento), 21,31
= 0, notación, 441
de salida), 838 & (operador de referencia),
# (numeral), 30,748
\\ código de escape, 58 260-262,284-285
& (operador AND), 775
V código de escape, 58 >: (operador condicional), % (operador de residuo),
\” código de escape, 58 72-73
94-95
V> código de escape, 58 = (operador de asignación), :: (operador de resolución de
// comentarios, 34, 41 ámbito), 601
51, 68, 71,321-322,324
/< comentarios, 34, 41 confusión con operador - (operador de resta), 71-72
“ ” (comillas), colocar alre­ [ ] (operador de subíndice),
igual a (= =), 80
dedor de cadenas, 757 listado, 322-323 698
[ ] (corchetes), 384, 721 precedencia, 78 + (operador de suma),
#defíne, instrucción - (operador de decremento), 75 317-320
listado, 749-750 posfijo, 75-77 += (operador de suma
pruebas, 749 prefijo, 77 autoasignado), 74
sustituciones de cadenas, 748 [ ] (operador de desplaza­ != (operador diferente de), 80
sustituciones de constantes, miento), 395 == (operador igual a), 80
749 ++ (operador de incremento), && (operador lógico AND),
#else, comando del precom­ 75-77 91-92
pilador, 749-751 [ ] (operador de índice), 375 ! (operador lógico NOT), 92
#ifdef, comando del precom­ * (operador de indirección), il (operador lógico OR), 91-92
pilador, 749 229-230, 284-285 >= (operador mayor o igual
#ifndef, comando del precom­ « (operador de inserción), que), 81
pilador, 749 549, 553, 698 > (operador mayor que), 81
1068 ín d ic e

<= (o p erad o r m enor o igual acceder me iemento, operadores de.


que), 8 1 arreglos. <75 '07
< (o p erador m enor que). 32, atómicamente. 867 nuevos miembros a espacios
81 ayuda en linca en el cditoi de nombres. 606
I (o p erad o r OR), 776 \t. 8 11-812 operadores de despliegue,
A (o perador OR exclusivo), clases contenidas. 508 673
776 datos miembro. I 52 agregar al final de un archivo,
( ) (paréntesis), 78 heap. 24 1-242 587-589
agrupar. 93 privados. 1^1 I >4 alcance
anidación. 78-79 públicos. 152- 15' ciclos for. 202
macro. sintaxis. 753-754 datos miembro estáticos con Hielos de nombres. 600
/pro c, sistem a de archivos, métodos no estáticos. ile archivo. 602
858 459-461 definir. 602
; (punto y coma), 68 sin objetos. 458 -159 espacios de nombres. 609
alinear. 84. 78 1 direcciones tic memoria. global. 602
funciones. 104 232-233 limitar. 455. 604
instrucciones if anidadas. elementos. 694-697 ocultar nombres fuera de.
88-90 de arreglos. '68 612
\t código de escape, 58 referenciar objetos fuera de.
información del sistema.
\t (código de fabulador), 33 858
286
~ (tilde), 159 usar directiva. 6 10
miembros
I (tubería), carácter, 839 variables. 108-1II
dalos miembro en el
externas, 604
heap (listado). 242
visibilidad. 602
objetos. 149
algoritm os de secuencia,
estáticos. 691
A 708-709
objetos derivados. 338-339
alias. Vea también referencias,
páginas del manual de g+ + ,
a nivel de bits, operadores, 259
39
775 contundíts/sccuencias de
actores, 626-628 comandos. 845-846
AND (&). 775 A DI), 9
complemento (), 776 espacios de nombres, 613
A dm inistrador de p resen ta­ para frases, 53
OR (I), 776
ciones de IBM, 898 alinear llaves ({ }), 84, 781
OR exclusivo (A). 776
adm inistrar búferes, 560 am bigüedad, resolución de
ab razo m ortal, 870
ADTs (tipos de datos a b stra c ­ (herencia múltiple), 426-427
a b rir archivos, 585-587 tos), 436-440
abstracción (en program a­ am bulantes, apuntadores, 247
declarar. 441 comparados con apunta­
ción), 129
derivar de otros, 445-448 dores nulos. 250
niveles de, 129 funciones virtuales puras. crear. 248-249
programación orientada a 440 precauciones, 249
objetos, 620 Java, 450 amigas (plantillas), 669
abstractas, clases, 449 listados. 440-441.445-448 de tipo específico, 710
declarar, 441 producir errores en tiempo generales, 673-676. 710
derivación de otros ADTs, de compilación, 440 que no son de plantillas,
445-448
ventajas, 452 669. 673
funciones virtuales puras, agregar análisis, 624
440 botones en aplicaciones casos de uso. 626-633, 637
Java, 450 KDE. 928 actores, 626-628
listado de ejemplo. 440 dos variables Contador. cajero automático, ejem­
ventajas, 452 317 plo. 629
índice 1069

creación de paquetes. crear asignar, 237-238


637 KDE, 923-927 como datos miembro
del dominio. 6 3 3 KDE, agregar botones, heap, 242
diagrama. 6 36 928-931 listado, 243-244
escenarios. 6 34 KDE, agregar menús, const, 250-253
lincamientos. 6 34 934-936 declarar, 250-251
mecanismos. 628 KDevelop, 937-938 listado, 251-252,
modelos de dominio. wxSludio, 922 279-280
6 29 wxWindows, kit de he­ métodos, 251-252
resultados, 628 rramientas, 910-918 pasar, 278-281
U M L . diagrama de wx Windows, kit de he­ const this, 253
interacción. 6 3 7
rramientas, agregar constantes, 281
de aplicación. 6 38 menús a la clase declarar, 228,234,285
de requerimientos. 626
Window, 920-921 descontrolados/ambulantes,
de sistemas, 6 38
wx Windows, kit de he­ 247,250
precauciones. 6 59
rramientas, procesa­ crear, 248-249
analizar caden as sintáctica­
miento de eventos, precauciones, 249
mente (listado), 2 5 3 - 2 5 5
919-920 desreferenciar, 229-230,234
ancho (salida), 5 7 8
enfoque, 918 direcciones de memoria
AN D (& ), o p erad o r a nivel
GNO M E, programación, asignar, 228-229
de bits, 7 7 5
908 examinar, 232-233
AN D ( & & ) , operadores
G TK ++, 907 recuperar, 229-230
(lógicos), 9 1 - 9 2
manejo de eventos eliminar, 237-238,247
anidar
(wxGTK), 9 18-9 19 arreglos, 470-472
ciclos (for), 20 0 -20 1
marcos de trabajo, 903 asignar, 467
espacios de nombres,
por procedimientos, 897 declarar, 464-466
606
X, clientes, 898 desreferenciar, 470
if, instrucciones, 86-90
aptrv (apuntador a función listado, 464-468
paréntesis, 7 8 -7 9
virtual), 356 pasar, 472-475
A N SI (Instituto
apunta a, operador (->), 241 typedef, 475-477
Estadounidense de
apuntadores, 22 5-227,230 , ventajas, 467-470
Estándares Nacionales),
2 35 . Vea también referencias importancia de, 256
15-16
a cadenas, 391 inicializar, 228, 234-236
C + + , estándar, 1 5 - 1 6 instrucción, 130
comparados con apunta­
espacios de nombres, 599 listado, 389-390
dores nulos, 250
estándares, 15 manipulación de datos,
crear, 248-249
excepciones, 7 1 7 231-232
precauciones, 249
antropom orfism o (tarjetas manipulación, ventajas, 234
a funciones, 463-466
C R C ), 6 4 6 -6 4 7 memoria, propiedad del área
a funciones miembro (lista­
apagar bits, 7 7 6 - 7 7 7 de, 239
do), 478-479
apariencia de xxgd b , 8 2 5 acceso, 229 métodos, 477-480
A P ls (G N O M E ), 905 arreglos, 480-482
alcance, limitar, 455
aplicaciones. Vea también declarar, 478
almacenamiento, 232-233
program as aritmética, 253 invocar, 478,480
análisis, 6 3 8 arreglos de, 380-382 nombrar, 229
ciclos principales de en comparación con nulos, 228, 266
eventos, 9 2 5 apuntadores a arreglos, pasar por referencia, 268
conscientes de la sesión, 382 perdidos, 228
900 nombres de, 382-384 pisotear, 249
1070 ín d ic e

propiedad. 290 modificar (RCSi. 827-829 declarar. 367-368, eliminar


reasignar. 239 objeto. 19 del heap. 384
referencias primó i. 581 dispersos. 410
combinar, 284 rastrear cambios. 828 elementos. 367-369
como alternativas. 2 8 1 salida. 585-589 acceder. 375
comparación. 283-284. tag. 8 15 cero. 373
291 urge (contoo de argum entos), no mieializados, 374. 411
regresar valores múltiples, 592 numerar. 368
272-274 argum entos, 37, I 12. 837 ordenar. 409-410
RTT1 (Identificación de funciones. 101 errores. 369
Tipo en Tiempo de línea de comandos. 592-595 errores tipo poste de barda.
Ejecución). 4 1ó pasar. 104 372
tablas v. 356 a constructores base. escribir más allá del final
tamaños, 228 342-346 de. 369-372
tliis, 246, 313-314 apuntadores. 268 guardar
listado. 246-247. 313-314 por referencia. 266-268. en el heap. 380-381
métodos de acceso. 247 en la pila. 380
275-278
valores actuales, imprimir. inicializar. 373-374
por valor. 113-114. 142.
767-768 integrados, 409
267-268
ventajas, 234 llenar. 386-387
predeterminados. 116-118
árboles, 398 memoria. 379
señales y ranuras. 931
archivos muli¡dimensionales, 377
argv (vector de argum entos),
abrir para cntrada/salida, inicializar, 378-379
592
585-587 listado. 378-379
aridad, 321
agregar al final, 587-589 nombres (apuntadores),
aritmética de apuntadores, 253
alcance, 602-604 382. 384
arreglos objetos. 375-377
bibliotecas, 133 apuntadores. 380-382
binarios, 589-591 pasar objetos a funciones,
a funciones, 470, 472 668-669
core, depurar, 741 a funciones miembro
de encabezado, 134 plantillas
(listado), 480-48 declarar. 662-664
métodos, 271-272 a métodos, 480-^)82
de inicio, 849 implementar. 665-668
en comparación con tamaños, 374-375
de paquetes, manipular, 133 arreglos de apunta­ unidimensionales, 377
de proyecto, 134 dores, 382
de texto, 590-591 artefactos, 640
nombres de arreglos, ASCII, 56
comparación con 382-384 código de caracteres, 56
archivos binarios, bidimensionales, 377-379 conjuntos de caracteres, 47
589-591 char, 385-386 equivalentes numéricos
depurar, 741, 815 clases, 409-410 (tabla de), 958
descriptores, 875 combinar, 411 asignación (=), operador, 51,
determinar formato, 818 consts y enums (listado), 68, 71
ejecutables, 19 374-375 combinar con operadores
entrada, 585-589 contenedores vectoriales. matemáticos. 74-75
espacios de nombres, 614 692 confusión con operador
extensiones (compiladores crear (listado), 383-384 igual a (= =), 80
de GNU), 17 de bolsa, 410 contenedores vectoriales,
fuente, 17, 822 de diccionario, 409 693
kemel, 741 de enteros (listado), listado, 322-323
make, 136, 820-821 368-369 precedencia, 78
índice 10711

asignadores, 6 92 base (de números), establecer, bibliotecas, 558


asignar. Vea también cre a r 579 archivos de, 133
apuntadores. 2 3 7 - 2 3 8 base 2, números, 9 5 1-9 5 2 C++, de envoltura, 905
apuntadores a funciones. convertir compartidas, 818-819
467 a decimales, 951 definición, 19
direcciones a referencias. a números en base 10, espacios de nombres,
26 2-26 4 951 declarar, 604
direcciones de memoria a decimales a. 950 estándar, uso de directivas,
apuntadores. 2 2 8 -2 2 9 ventajas, 951 2 2 ,6 14
memoria, hcap. 2 3 6 base 7, números, 949 funciones, 4 1, 134, 137
valores a variables. 5 1 - 5 2 . base 8, números, 948-949, GNOME, 905
14 9 -15 0 9 5 2 -9 5 3 iostream, 557
variables a clases definidas base 10, números, 948 KDE, 938
por el usuario. 3 2 4 - 3 2 5 convertir a base 2, 950-951, libgnomeui, 908
AsignarEdadO , método, 2 4 2 954 múltiples, 599
A sign arP rim erN o m b re(), convertir a base 6, 949-950 POSIX, de subprocesos,
función, 5 0 7 convertir a base 7, 949 861
asociación, 6 3 3 convertir a base 8, 953 STL, 691-694, 698-701,
A S S E R T f), m acro, 7 5 8 - 7 6 1 , subíndice, 948 705
785 base 16, números, 953-956 TrollTech, de gráficos de
código fuente, 7 5 9 -7 6 0 base, clases, 3 3 4
Qt, 899
depurar funciones, 7 6 0 -76 2 especializadas, 631 wxGTK, 903,909
excepciones, 760 compartidas wxWindows, kit de herra­
limitaciones, 76 1 funciones, 4 16 mientas, 909-910
listado, 7 5 9 herencia, 427-431 binarios,
asterisco (*) comunes (listado), 428-430 archivos, en comparación
operador de indirección, métodos con archivos de texto,
2 2 9 -2 3 0 llamar, 350 -351
589-591
sintaxis, 2 8 4 -2 8 5 ocultar, 348-350 números. Vea también
A T & T U N IX System V, 8 79 redefinir, 347-348 números de base 2
atrapar excepciones, 7 2 2 base, constructores, 340
operadores, 320
autoasignados, operadores, 7 5 pasar argumentos a, 342-346
semáforos, 868
Ayuda, archivos (com pilador sobrecargar, 342-346
bits, 775,9 51
base, destructores, 340
g++), 3 9 apagar, 776-777
bash, 836
AYUDA.CPP, demostración cambiar, 777
comandos
de los comentarios (listado), 9 campos, 777-780
35 completación, 842
encender, 776
editar, 845
bloqueo de E/S, 875
lista de historial, 844
bloques (instrucciones), 81
establecer variables locales,
alcance, 602, 610
B 839
instrucciones de control, catch, 716
846, 848 múltiples especifica­
barra diagonal inversa, ca rá c­ ciones, 722-725
sustitución mediante
ter (\), 7 5 4 sintaxis, 721
comodines, 843
barra vertical (I), 9 1 -9 2 B A S IC , 10 try, 716, 721
barras diagonales (com enta­ Biblioteca de plantillas están­ excepciones sin errores,
rios), 3 4 , 4 1 dar. Vea S T L 739
barras diagonales inversas, sqrt(), función, 272 sintaxis, 721
dobles (\\), 5 8 usar directivas, 614 variables, 110 -111
1072 índice

Bonobo, 900 atrapar. 7 2 2 elegir la herramienta ade­


Booch, G rad y, método, 6 23 lunciones Nirtuales. 7 34 cuada. 190
Bool, tipo de datos, 79 múltiples. 722 especificar el formato
booleanos, valores, 7 7 5 plantillas. 7 3 5 -7 3 8 numérico de la salida, 962
botones sin errores. 7 3 8 -7 3 9 etiquetas. 182
agregar a la clase de ven­ lógicos. 7 1 4 expresiones. 69
tana de wxWindows. 9 1 3 mimmi/ar. 167 funciones. 269
agregar a la clase K D E producir fragilidad. 7 1 4 (ÍT K + + . 904
Window. 928-931 programación defensiva. historia de. 9-10, 14
botones.cc, listado, 906-907 7 14 implementar tipos de poder,
Bourne, shell (sh). Vea sh sintácticos, 7 1 4 655
Boum e, shell vuelto a nacer. solución de problemas limitar el alcance, 455
Vea bash comentarios en el códi­ manejo de excepciones, 715
break, instrucciones, 18 6 -18 9 go. 7 1 6 calch. bloques. 716
do...whilc, ciclo. 193 excepciones. 7 2 0 -7 2 1 ordenar. 728
listado. 18 7 -18 8 buscar direcciones de memo­ try. bloques, 716
precauciones, 189 ria, 2 2 6 -2 2 7 notación húngara, 783
switch, instrucciones, 207 bytes, 46, 9 51 operadores a nivel de bits,
búferes 77 5
administrar. 560 palabras reservadas. 50
caracteres, 564 polimorfismo, 14. 352, 800
copiar cadenas. 387-390 C programación orientada a
flujos, 558 -56 0 objetos, 1 3 ,6 1 9
implementar, 560-561 C , A P Is, 905 encapsulación, 14
limpiar o vaciar, 560 C -, comando, 8 1 3 herencia. 14
manipular, 560 C , compilador (gee), 8 referencias. 262
no inicializados, 38 5-38 6 C , extensión de nombre de relaciones es un, 334, 352
bugs o errores, 7 1 4 . Vea tam­ archivo, 16 7 software comercial. 15
bién depuración C , lenguaje, 14 switch. instrucciones, 205
apuntadores descontrolados, C , shell (csh). Vea csh tipos de variables, 47
24 7-24 9 C /C + + Users Jo u rn al, 786 tipos integrados, 306
arreglos, 369 C++ variables de corneo, 201
compilar, 7 1 4 aprender, 15 ventajas. 10
corrupción del código, 7 1 5 apunta a (->), operador, 241 verdad/falsedad. 79
depurar. 739 archivos de encabezado, C + + al descubierto, 379
archivos core, 741 271 C + + Report, 786
costo de solucionar, 7 1 4 bibliotecas (paquete cabeza (nodos), 398
depuradores simbólicos, wxWindows), 909 Cadena, clase, 3 9 1, 393-398,
739 bibliotecas de envoltura, 905 50 5
ensambladores, 742 cadenas, 385 acceso, 508
examinar memoria, 742 caja negra, método, 272 constructores
G N U , depurador, compilador (g++), 8 de copia, 396
740 -741 compilar programas, 8 1 7 predeterminados, 396
puntos de interrupción, constantes, 58, 28 3 declarar, 3 9 1, 393-398
74 2 contravarianza, 801 main(), función, 397
puntos de observación, conversión descendente, destructor. 396
74 2 4 17 listados. 39 2-395, 502-505
distinguir entre, 7 1 4 conversiones explícitas, 3 5 7 operadores, 397
excepciones, 7 16 desarrollo, 75, 620 sobrecargados, 395
índice 1073

usar instrucciones cout. ca ra cte re s, 45 categorías, 146. Vea también


549 A:. 84 I tipos
cadena con t e r n iin n d o r n u lo . A S C II, conjunto de. 4 7 C D E (E n to rn o C o m ú n de
391 buleres. 564 E sc rito rio ), 899
cadenas cadenas de texto. 31 C D - R O M (incluido en este
analizar sintácticamente comodines. 838 libro). 9
(listado). 2 5 3 - 2 5 5 de escape. 58 centígrados, convertir a
caracteres nulos. 5(»4 de llenado. 5 7 8 -5 7 9 Fahrenheit, 107-108
Char. arreglos. 3X5 E B C D IC . conjunto de ca­ centinelas (arreglos), 372
colocar entre comillas. 7 5 7 racteres. 57 cero, elemento, 373
concatenar. 33 . 7 5 7 -7 5 X especiales ceros a la derecha, desplegar,
contenedores vectoriales. cadenas de salida. 58 3 579
698 imprimir. 58 cerr, objetos, 561, 596
copiado examinar. 57 0 c e rra r el d e p u ra d o r gd b , 824
strcpyí). 3 8 7 -3 8 8 (unciones para. 139 C -h , com and o (em acs), 813
stm cpyt). 3XX manipulación. 138 char, arreglos, 385-386
crear. 50 7 nombres de variables. 48 char. variables, 4 5 ,5 6
dar formato. 58 I nulos. 38 5. 564 caracteres de escape. 58
de caracteres, análisis sin­ codificación de caracteres.
(numeral), símbolo. 30
táctico (listado). 2 5 3 - 2 5 5 I (tubería). 839 57
de texto. 3 1 - 3 3 valores. 56 tamaños, 56
estilo C . 54 4 ciclo de d esarrollo (p ro g ra ­
valor/letra, relación. 5 7
funciones comunes d ista­ m ación), 19-20
c a r g a r fun cio ne s, 122
dos), 38 7 -3 X 8 ciclo p rin cip a l de eventos, 925
c a sc a d a , d e sa rro llo , 623
funciones miembro. 5 7 0 ciclos, 181
case, v a lo re s (in stru ccio n e s
funciones para. 1 39 . 3X9 do...\vhile. 19 2-19 3
.switch), 206
listado, 3 8 9 -3 9 0 listados, 19 2-19 3
c a so s de uso, 626-633, 637
manipulación. 13 8 . 7 5 6 sintaxis. 193
actores. 626-628
output. caracteres espe­ cajero automático, ejemplo. eternos. Vea ciclos forever
ciales. 5 8 3 629 listado. 209-211
sustituciones, 74 8 , 8 4 3 menu(), función, 2 11
clientes. 627
tokens, 74 8 crear salir de, 209
uso de, 7 5 7 clases, 641 switch, instrucción,
valores actuales, imprimir, paquetes. 6 37 208-211
76 7 -7 6 8 diagrama, 636 while, 189-190
caja n e gra, m é to d o , 272 dominios externos. 190
cajero a u to m á tic o , e je m p lo , análisis. 6 33 Fibonacci, aplicación de la
629 modelos, 629 serie de, 203-204
calcular t a m a ñ o s d e a r r e g lo s , escenarios. 634 fib(), función, 204
373 ejemplo, 6 35 precauciones. 205
callbacks, c u e stio n e s, 931 lincamientos. 634 for, 19 5-19 6
cam biar mecanismos, 628 alcance. 202
comportamientos predeter­ resultados, 628 anidar, 200-201
minados del flujo. 5 8 7 U M L, diagrama de interac­ avanzados, 196
constantes, 5 9 -6 0 ción, 6 37 inicialización, 195
bits, 7 7 7 inicialización múltiple.
catch, bloques, 716
cam pos (b it), 7 7 7 -7 8 0 múltiples especificaciones, 19 6 - 197
cannot fin d file, m e n s a je s d e 7 2 2 -7 2 5 instrucciones nulas
error, 22 sintaxis, 72 1 19 7- 200
1074 ín d ic e

listado. 195-197 de otras clases. 171-175


secuencia de ejecución. abstractas. 449 pm ados. 151-152.
196 amigas. 534-535 337-338
vacíos. 198-200 declarar. 543 protegidos. 337-338
goto, palabra reservada, listado ile programa de públicos. 151-153
181-182 ejemplo. 535-542 de algoritmos. 706-707
limitaciones. 183 sugerencias de uso. 54 t operaciones de secuen­
recomendaciones. 183 Arreglo. 409-410. 720 cia mulantes. 708-709
sintaxis. 183 arreglos, plantillas. 663-664 operaciones de secuen­
listados asignadorcs. 692 cia no imitantes.
condiciones de inicio. base. 334. 348 707-708
194 compartidas. 427-4 3 | ile arreglos. 720
saltar cuerpo de. 191 múltiples. 424 parametrizadas. 662
sintaxis. 184-185 C+ + . intenciones. 27 i plantillas. 663-664
while. 189-190 C adena. 391-398. 501.505. usar en vez de arreglos
listas enlazadas. 408 553 integrados, 409
regresar al inicio de, 186 constructor de copia. 396 ile capacidad. 436
salir. 186 constructor predeterm i­ de figuras (listado). 437-439
uso de. con la palabra reser­ nado. 396 de flujos. 585
vada goto (listado). 182 declarar. 391-398 ile interfaz. 643-644
while. 183-186 destructor. 396 declarar. 146-147. 152, 155.
break. instrucciones, listado. 392-395
164-168. 543
186-189 operadores. 397
definidas por el usuario.
complejos (listado), sobrecargar. 395
324-325
185- 186 capacidad. 436
definir. 149, 785
continué, instrucción. compartir datos. 461
deque, contenedores, 701
186- 189 completas, declarar, 172-173
derivadas. 33-337, 436-439
ejecutar, 191-192 acceder, 508
constructores, 342-346
expresiones complejas. constructores. 509-5 I I
datos miembro, acceder,
185-186 copiar por valor, 511.
515 338-339
limitaciones, 191 declarar, 335, 337
listados, 184, 189-190. costos, 508, 5 11
delegación. 5 16-53 1 diseñar. 641-643
194 Empleado. 505. 508
cin implementar, 5 15-5 16
constantes, 762-767, enfoque del modelo estáti­
cadenas, terminar, 564 co, 644
770-773
espacio en blanco. 564 escribir
constructores, 159-160
tipos de datos, manejar, 563 en archivos, 590-591
predeterminados. 16 0 .
cin, objeto, 561-563 298 para guardar un objeto,
entrada Contador 398
cadenas, 564 declarar, 306-307 especializadas. 631
múltiple, 564-567 incremento, funciones, estructuras, comparación.
operador de extracción, 307-308 175
567 contenedoras, 662, 702-704 excepciones, 720-721, 725
métodos contenedores vectoriales, fstream, 561
get(), 567-571 692-694, 698-699 funcionalidad, 419
getlineQ, 571-572 contenidas, 501 funciones
ignoreí), 573-574 crear, 641,644 miembro, sobrecargar,
peekí), 574-575 datos miembro, 146-147 293
putbackí), 574-575 acceder, 149, 152 modificar, 414
In d ice 1075

Gato CRC. tarjetas. 647 crear fugas de memoria. 239


declarar. 147. 169-170 relaciones. 648 depurar. 739
implémentai. 170-171 públicas, 151-153 archivos core. 741
gdb. comandos. K2 * Punto. 174 ensambladores. 742
herencia Qt. 932 examinar memoria. 742
conversión descendente. Rectángulo. 173-175 GNU. depurador.
416-410. 451 resolución de nombres. 740-741
filtrar funciones com par­ 600-604 puntos de interrupción.
tidas. 4 16. 45 1 responsabilidades. 645 742
limitaciones. 4 I 5-4 16 seguridad. 155 puntos de observación.
interfaz. 643 streambuf. 560 742
ios. 561
subclases. 171-175 editores, S09
iostream. 561
sugerencias de uso. 543 elegir la herramienta ade­
istream. 561
superconjuntos. 334 cuada. 190
jerarquía. 337. 436
tipos de variables persona­ errores. 714
List. 600
lizados. 146 espacio en blanco. 68. 284
lista. 661
contenedores. 600-701 UML, 622 espacios de nombres. 614
listado de program a de class, instrucciones, 155-156 expresiones. 69
ejemplo. 535-542 elass, p a la b ra reservada, fuente, 11
Mamífero. 337 147, 663 fuente abierto. SOS
métodos. 157-158. 168 C lassCíenerator, 937 compilar. 18-19
constantes. 163-164 clientes, 164, 627 programas ejecutables.
de acceso públicos. X. 898
153-154 clog, objetos, 561, 596 reutilización. 12-14
definir. 150 clonación, código, 133, 137, funciones. 30
en línea. 169-171 140-141 guardias de inclusión. 752
implementar, 156-150 C lone(), m étodo, 360, 363 Hola, mundo, programa. 29
modelo de diseño está­ cn t, variable estática, 608 intérpretes. 10
tico. 645 C O B O L , 10 legibilidad. 68
sobrecargar, 293-295 código lincamientos en el estilo.
valores predeterm inados, agrupar. 93 780-781
296-298 alias, 53 listados. Vea listados
mezclas, 436 ambigüedad de nombres, macros, 752
nomenclatura, convenciones, 611 manejo de excepciones,
147-148 apuntadores, 253 716. 728
objetos apurar, 658 notación húngara, 783
comparar, 148 bugs o errores, 714 # (numeral), símbolo, 30
definir, 148, 155 clonación, 133. 137, rellenar funciones, 335
inicializar, 300-301 140-141 reutilizar, 133, 137, 140-141
refcrenciar, 264-265 solución de problemas, 716
comentarios, 34, 784
tamaños, 177 cuándo utilizar, 34 Código Extendido de
valores, 149-150 listado, 35 Caracteres Decimales
ostream, 561 Codificados en Binario para
precauciones, 35
PeiTo, 335-337 el Intercam bio de inform a­
comodines, 838
polimorfismo, 14 compilación ción, 57
privadas, 151-153 CO D IG O _ER R , enumeración,
compiladores, 9
Process, 855 errores, 24 275
programación orientada a símbolos, 739 cola (nodos), 398
objetos, diseño, 646 corrupción del, 715-716 colas, 706
1076 In d ice

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

const, m éto d o s, 163-164 predeterm inados. 160-163. program ación orientada a


const, p a la b ra ro s e rx a d a , 283 166 objetos, diseño. 622
funciones m icm h ro estad sobrecargar. 299-300 conversión descendente
cas. 463 clases deriv .idas. (h e ren cia), 416-419. 451
rcdellnir. 603 342-345 co m ersiones
ventajas. 64 listado. 299-300 base 2 a base 10. 951
constantes, 58. 762-767. variables m iem bro, im- base 8 a base 10. 953
770-773 ciali/ar. 301 base 10 a base 2. 950-95 1
cambiar. 6 0 C u n ta d o r, clase base 10 a base 6. 949
definir declarar. 306-307 base 10 a base 7. 949
const. 60 increm ento, funciones. base 10 a base 8. 953
¿'define. 50 307-308 base, núm eros. 950
enum eradas. 6< > 62 listado. 306-307 decim ales a binario. 954
dem ostración ( listado i. \ artablcs. 3 17 explícitas. 357
61 c o n ta d o re s , c re a r, 326 Fah re nhe it/cen t íg rados.
listas en lazadas. 405 co n te n c ió n . 501, 632 107-108
sintaxis. 60 clases contenidas C o n v ertir« ). función. 107-108
valores. 6 I acceder. 508 c o p ia r
literales. 58-50 constructores. 509-5 I 1 cadenas
r valúes. 7 I copiar por valor. 511. 515 a büferes. 390
simbólicas. 50. 64 costos. 508. 5 I I strcp y í). 3S7-388
sustituciones. 740 delegación. 516-53 1 stmcpyO. 388
C o n stan tes!), m é to d o en com paración con la datos. 146
(listado), 762-766 herencia multiple. 65 1 por valor. 511. -515
constructores herencia privada. 554 copias profundas, 302-306, 322
base, pasar argum entos a. implementar. 5 15-5 16 copias superficiales (de los
342-346 co n ten ed o res, 692 d ato s m iem bro). 302. 322
clases contenidas. 500-51 | asociativos. 692. 701. 705 C O R B A (A rq u ite ctu ra de
de copia. 302-306 de secuencias. 692 In term e d iario de Solicitudes
copias pro!undas. deque. 701 de O bjetos C om unes), 900
302-303. 305-306 envolturas. 705 corchetes (( J), 384, 721
copias superficiales (de lista. 699-701 costo
los datos m iem bro). mup. 701. 704-705 contención. 508
302 vectoriales. 692-694, memoria (métodos virtua­
crear, 303 698-699 les), 363
listado. 303-304 conten cout, instrucciones, 32-34
parám etros. 302 semáforos. 868 en comparación con p rin tfí).
predeterm inados, 302 variables. 201 581
virtuales. 360, 363 w hile, ciclos. 189 listado. 32
declarar, 159, 329 continue, instrucciones, 186, pasar valores a. 33
especializados. 686 188-189 quitar comentarios. 508
herencia. 340-342 do...while, ciclo. 193 cout, objeto, 31, 561, 575
implementar, 163 listado, 187-188 métodos
in¡cializar, 300 precauciones. 189 fillO. 578-579
invocar, 300 co n trav arian za, 801-803 fiushO. 575
llamar (listado). 340-342 control de versiones, 827 put(). 575-576
Múltiples, 160. 424-426 convenciones setf(). 580-581
°Peradores de conversión. para nombrar clases, width(). 577-578
325-328 147-148 \vrite(). 576-577
I 1078 ín d ic e

(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

definir. 457 C ontador. 3(t6-3H7 d efau lt. instrucción. 207


funciones, plantilias. d e n ta d a s . '3 5 -3 3 7 define, instrucción. 59
687-690 errores. 164-167 listado. 749-750
iniciali/ar. 6 9 1 Cíalo. 147. 169-170 pruebas. 749
listado. 456-457 Punto. 17-1 sustituciones de cadenas.
datos m iem bro. 14 6 R ectángulo. 175-175. 748
acceder. 149. 152 ;m sustituciones de constantes.
de clases. 17 1-175 subclases. 171. 173 749
estáticos. 455-458 co nstructores, 159 definir, tro también inicializar
acceder. 458-461 datos m iem bro estáticos. alcance (resolución de nom ­
declarar. 687. 69 1 687. 69 I bres». 602
definir. -157 elem entos dentro de e sp a ­ clases. 149. 785
listado. 456-457 cios de nom bres. 600 constantes
ventajas. 48 ^ espacios de nom bres. ¿define. 59
heap 6 0 4 -6 0 6 const. 60
acceder. 241-242 estructuras. I 75-1 76 constantes sim bólicas. 59
apuntadores. 242-244 Figura. 439 datos m iem bro estáticos.
privados. 15 1-152. 557- Mx t unciones. 101-103 457
acceder. 15 1 de plantillas. 669. 673 espacios de nom bres pobre­
ventajas. I 77 en linca. 122-124. mente. 605
protegidos. 557-538 755-756 funciones. 101. 104-105
Públicos. 15 1-152 S u m a!). 3 I 7-3 18 espacios de nombres. 607
seguridad. 155 virtuales. 735 fuera de espacios de
inicializar. 159 herencia múltiple. 423 nombres. 606
de dirección, o p e ra d o r (& ), herencia virtual. 435 macros. 752-753
226-227, 2 6 1 - 2 6 2 I.ist. 600 métodos, ubicaciones de
de paro, condiciones, 1 2 5 métodos archivos. 167-168
decimal (io s::d ec), 5 7 9 amigos. 549 objetos. I4S. 155
decimales, n ú m reros, 94 8 apuntadores a. 478 plantillas. 662-665
convertir a base 2. 950-95 I . const. 163 Semaphore. clase. 889
954 constructores. 329 SharedM cmory. clase. 892
convertir a base 6. 949-950 ubicaciones de archivos. sustituciones de cadenas.
convertir a base 7. 949 167-168 748
convertir a base 8, 953 valores predeterminados. sustituciones de constantes.
declarar, 149. Vea también 296-298 749
inicializar,* definir operadores sobrecargados. variables. 44. 49
apuntadores. 228. 234 327 de tipo CO LO R - 61
a funciones, 464 plantillas. 662-664 locales. 106
constantes. 250-25 1 precauciones, 285 múltiples, 5 1
arreglos, 367-368, 374-375 referencias. 260-261. nombres de, 48-49^
bidim ensionales, 379 265-266. 285 notación húngara. 50
en el heap, 381-382 tipos. 155 palabras reservadas. 50
memoria, 379 tipos de datos abstractos, susceptibilidad al uso de
objetos. 375-376 mayúsculas y minúscu­
441
c|ases, 146-147. 152. 155. variables. 285 las. 4 9 -50
168 variables miembro, 154 D E L , comando, 8 1 3
amigas, 543 decrementar, 7 5 delegación, 5 1 6 - 5 3 1
Cadena, 391 -398 decrcmento, operador (-), delegar a una lista enlazada
completas, 172-173 7 5 -7 7 contenida (listado), 5 1 7 - 5 2 4
1080 In d ice

.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

clases. 64 l d iK 'iim en tacio n t I.in tix i. e le m e n to s


crear. 642-64 4 829-831 acceder. 664-667
m anipulación «.le il.it«>> d o c u m e n to s arreglos. 4 6 ” . 375
644 .in c lu d i>s. 6-tt) crear. 694
contención. 642 diseño. 64 1 elem en to cero. 473
CRC. sesiones. 64 4-64 m odelos de dom in io . 62o liberar. 664
tliscrinmuulores. 65 2 -65o planea!. 638 Veo ta m h u n F I . F «Fo rm ato ejecu table
documentos. 64 I diseñar > de en lace). 8 1 8
documentos de planeacion. d o c u m e n to s d e p la n e a c ió n . elim in ar
638-646 638-639 apuntadores. 247. 244. 2N>
escenarios. 644 d o m in io creación de apuntadores
herencia m últiple en expertos. 626 d esc o n so la d o s. 24
comparación con la in tu id o s. 626. 640 precauciones. 2 4 /
contención. 65 I tibíelos. 637. 644 arreglos del heap. -'84
iterativo. 624-624 D O S . c o m a n d o s. 562 IPC. objetos. SS2
controversia. 625 d u p le x to ta l, tu b e ría s, m em oria com partida. «861
pasos. 624 876-877 tibíelos. 874
lenguajes para modelar. D u p lic a d o r! ). fu n c ió n . 1 16 objetos del heap. 240
621-622 d \ n a n n e c a st, o p e ra d o r, 417 restaurar m em oria en el
lincamientos. 644
heap. 248
lincamientos en el estilo
elim in ar trabajo s. «841
del código. 7 SO
else,
lluvia de ideas. 627 E com ando del precom pilador.
mciodologista. 624 746-751
modelo dinám ico. 657 K B C ’ D IC . conjunto de c a r a c ­ instrucciones. 86
modelo estático. 644 teres, 5 7 palabra re se ñ a d a . 85-86
precauciones. 658 ed ita r com andos, 8 4 5 em acs, «SI2
proceso de. 622 editores com andos. 8 1 .'-8 14
prototipos. 636 código. 806 iniciar. 8 12
tipos de poder. 652-656 de texto. 17-18 m odificar archivos. 817
transformaciones. 643-644 em acs. 8 I 2-8 14 tutoríal en línea. 8 13
visión. 625 com andos. 8 13 Fm pleado, clase. r>05-507
visualizaeión. 636 m odificar archivos. 815 en línea, funciones. 1 2 2 - 1 -
DISPLAY, v aria b le de tutoría! en línea. 813 7 5 5 -7 5 6
entorno, 840 seguros. I 7 desventajas. 122
dispositivos sin modo. 8 14 expandidas. 756
Programación orientada a eficiencia (program as), 12 2 listado. 755-756
objetos, diseño. 644 ejecutables, 11 en línea, implem entación.
redirigir, 561 crear. 19. 40 1 6 9 -17 1,3 3 0
división, 7 2 -7 3 predeterminados. 81 8 encabezados (funciones). 1<>4
do...whilo ciclos ejecutar. Vea también llaniai encapsulación, 13 , 1-16. 3r>8
break, instrucciones. 163 funciones. 105 endl, instrucción, 3 3
cic»0 s whilc. com paración, programas. 46 enfoque, 9 1 8
212 switch, instrucciones. 207 enlace
eontinuc. instrucciones. while, ciclos. 184. 186 dinám ico. 355
193 ejercicios. Vea ejercicios de nombres, 603
listado, 192-163 repaso en tiem po de ejecución. 3^5
sintaxis, 193 elegir la herramienta adecua­ externo. 603
Doble0, función, 12 4 da (C + + ), 190 interno. 603
1082 In d ic e

en lazad o res, 10 errores en pantallas. 561


espacios de nombres, 6(X) apuntadores descontrolados. incremento, funciones.
falla, 6ÍK) 249 307-308
e n la zar p ro g ra m a s, 8 18 cannot find file. 22 mas alia del final de arre­
en sam b lad o res, 7 4 2 C’ODIGO_ERR. enumera­ glos. 369
enteros long. 54 ción. 275 palabras, múltiples. 564-565
valores, límites de tamaño. corrupción del código. 715 prototipos. 101
54-55 de com pilación. 24. 167. espacio de código. 130
enteros, 45 419. 440. 600. 606 espacio en blanco, 284
arreglos, declarar. 368-369 declaraciones de clases. cin. 564
especificación de tipo. 73 164-167 ignorar, 570
long, 45. 54. 63 relercnciar objetos no exis­ espacios de nombres, 599,
operaciones de división. tentes. 286-287 6 0 7-60 9
72-73 errores tipo poste de bard a, agregar nuevos miembros.
short. 45. 54 372 606
signed. 46, 55-56 E/S, objetos, 5 6 1 alias. 613
unsigned. 46. 54-55, 145 cerr. 561 ambigüedad de nombres.
E n torn o C o m ú n de cin, 561-563 61 I
E scrito rio . Vea C D E cadenas. 564 anidar. 606
E n torn o de D esarrollo entrada múltiple. crear. 604
In tegrad o (ID E ), 9 3 7 564-567 declarar
En torn o G N U de M odelo de gei(). método, 567-571 elementos dentro de. 600
O bjetos de red. Vea gctlincí), método, tipos. 605
GNOME
571-572 definir
entornos o am bientes funciones fuera de espa­
ignoreí), método.
GNOME. 899 573- 574 cios de nombres, 606
KDE. 901 operador de extracción, funciones miembro. 607
variables de. 839-840 567 extender múltiples archivos
e n trad a de encabezado, 604
peek(), método, 574-575
archivos, 585-589 putbackO, método, globales. 613
ignorar. 573-574 574- 575 identificar nombres dentro
manejar clog, 561 de, 61 1
cadenas. 564, 570-572 cout, 561, 575 listado, 607
de un solo carácter, 567, fillí), método, 578-579 pobremente definidos, 605
569 flushí), método, 575 resolución de nombres.
extracciones, 562-563, put(), método, 575-576 600-604
567 setff), método, 580-581 sin nombre, 613-614
múltiple, 565-567 widthí), método, soporte del compilador, 599
objeto cin, 562 577-578 std, 614-615
peek(), método, 574-575 write(), método, 576-577 usar declaración, 611-613
putbackO, método, E S C , com ando, 8 1 3 usar directivas, 609-611,
574-575 escenarios, 6 3 3 614
enum , p a la b ra re se rva d a , 60 ejemplo, 635 variables locales, 610
e n viar m en sajes (procesos), lineamientos, 634 Ventana, 607
882 escribir especificación de tipo para
envolturas (contenedores), clases en archivos, 590-591 enteros, 7 3
705 en discos, 558 especificadores de acceso, 338
E O F (fin de a rch ivo ), 5 7 3 en dispositivos de salida, especificadores de conversión,
E O L (fin de línea), 5 7 3 575-576 58 1-58 2
I n d ic e 1083

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

extensiones de archivos. com piladores. 694


17 datos i arreglos i. 367 agregar a dos listas.
formato intermedio. 748 direcciones de m em oria en 4 19-420
soporte para plantillas. apuntadores. 228-229 clases. 335
791 ob|elos (arreg lo si. 175 com paración con la genera­
susceptibilidad al uso de guardias de inclusión. 7 5 1 - 7 5 2 lización. 63 1
m ayúsculas y m inúscu­ G l 'I s (in terfaces g rá fica s de comparación con plantillas.
las. 49 usuario). SOS 7IO
variables miembro elem entos. S96 constructores. 340-342
estáticas indefinidas. historia. 897 argumentos. 342-346
457 interactuar con program as. sobrecargar. 342-346
declarar función mainf). 3 1 91 S contención. 501
depuradores. 740-74 l Linux. S99 acceso. 508
documentación en línea. 39 nativas. 9 10 constructores. 509-511
editores de texto, 17 program ación copiar por valor. 511,
emacs. 812-814 wx W in d o u s. kit de 515
comandos, 8 13 herram ientas. 909 costos. 508. 5 11
modificar archivos, 8I5 señales. 91 S delegación. 5 16-53 1
tutorial en línea. 8I \\ idgets. 903-904 filtrar el acceso. 508
enla/ador. 6 0 1 implcmentar. 5 15-5 16
gdb. depurador. 822-823 conversión descendente,
ayuda, 823-824 416-419. 451
cerrar. 824 H derivación. 334-335, 364
comandos, 824 destructores. 340, 342
sesión de ejemplo, 826- h, extensión de n o m b re de funciones. 4 16. 45 I
827 arch ivo , 16 8 limitaciones. 4 13-416
make, utilería, 820-822 Hacer'TareaLInoO, función, 2 1 1 listado. 336-337
sitio Web, 9 h a rd w a re (C P U s ), 1 3 0 métodos virtuales. 352-357.
goto, instrucciones, 1 8 2 heap, 2 3 5 , 2 5 6 431-435
ciclos, 181-182 arreglos apuntadores v, 356
limitaciones, 183 declarar, 381-382 constructores de copia.
recomendaciones, 183 eliminar, 384 360,363
sintaxis, 183 guardar, 380-38 I costos en memoria, 363
grupos de noticias, 786 c re a r/e 1i m i n a r o bj e to s declarar. 435
G TK ++, 898 (listado), 240-241 destructores, 360
aplicaciones, 907 datos, 235 invocar múltiples.
funcionalidad, 918 datos miembro 353-355
GNOME, 904 acceder, 241-242 listado. 432-434
GTK— , 901 apuntadores, 242-244 partición. 358-360
LGPL, licencia, 904 memoria tablas v. 356-357
recursos en línea, 901 asignar, 236 mezclas, 436
wxGTK, biblioteca, 909 restaurar, 236-238 múltiple, 420-422, 650
G T K + + (K it de h erra m ie n ta s objetos clases base compartidas.
G im p), 900 crear, 240 427-43 1
g u a rd a r eliminar, 240-241 constructores, 424-426
apuntadores, 232-233 ventajas, 235-236 declarar, 423
arreglos H E L L O .C P P , d em o stració n Java, 450
en el heap, 380-381 de com ponentes de un limitaciones. 435
en la pila, 380 p ro g ra m a (listado), 30 listado, 420-422
ncJ K O 108 7

diodos virtuales. 4.'.' Imprimir ! rrur . fumunn. "”'5


°*>jcii)s. 42 ' ni. Indi , archi'irs. "85
re*°lución de
nu indr. instruí tnmc-s.. Vi. 41 .
amhiguct!.nl. -Of» 1.' Sf,|
Privada. 525-5 2 <» un n inriiUi. "5
Oslado de programa dr ifdef. i iiniiiudo ilfl |> m " in p i im murnlii. fum tmtrv. X»"- ''íl'
cJcmplo. 5 2 b -<' ' ; l.idnr. " 4 9 im n nirntii. operador * • .
diodos. 525 ifn d cf. to m a n d o di l p r e m in -
sugerencias tic uso. s '4 pil.idor. " 4 9
SÍrnPlc distado». 414-4 1<> ifslrv.un. objetos. 585-58"
UML, 622 ignorar i-sp.mn en blnum. 5"n mde|»cndientc-s dr la plata­
^ n u a l.a il. 435 ignorei i. función. 573-59f» forma. 55"
declarar. 4 15 igual, signo • ». 51. 71. mtlii ¿n|nn-s. ""5. S.V,
listado. 4 12 4 14 321-324 !¿- e s •. >. í • ■- ‘ ‘ s •-/
“ «»ramientas cciNI ».'> imitar. R1 II iIdeiilific¡h ion
Wst° r i a d e C + + ,‘M O. 1 4 - 1 5 de Iipo en Iieinpo de
^ s*0r^al» Usía de (coimimlosi. Ejecución i. 4 1<»
844
iniplenieutar. <»24 s S > ;

Hola, mundo, programa, 20 i'uteies. sf*<1 • nú !1''s'i.i/s. t: t».


código fuente. 2 1. 20- lo Slases. I 7<> I *I ^5 1
c°mp¡lar. 21-22 vtMlti'IU t<'O. ">I ^ I(y i n d i a - , o |K -rn c lo r i | | •. 3"7-*'
ejecutar, 2 2 tlu|(»s. Sf><) Sí, | índices. <»92
G N U, g++. 21 Iuik iones vuníales pinas. indirceeion. o|>crudor i-- '•
listado, 2 1 44 I 44 s 229-230. 2S4-2S5
Hollerith, tarjetas de, 5 7 m icic.m ibi.ni >. Iuik mil info, comando. 137
HOME , variable de am bien­ ap u n tailt'ies. 2<»8-2<»9 iniciali/acion, instrucción de.
te, 840 releí encías. 2b9 2 I 195
HOWTOs, 831 m étodos. I 5b I 59 in ic ia li/.a r , 159, 2 6 1 . I r « tn tn -
hp» extensión de nombre de eonst. I<>4 h iv n d e c l a r a r
archivo, 168 e o n stm elo rc s. I b 3 apuntadores. 228. 2 '4 - 'f’
hpp» extensión de n o m bre de de clases. 157-1 58. IOS anéelos. 5"' '/4. ' 8 *
archivo, 168 de p lantilla. b73 clases base. 34 2
e o n stm e lo rcs. 'OO
d estm eto re s. Ib3
ría los e s t a n c o s , 6 9 I
en linea. 169-171. 330
plantillas. 6 6 6 -6 6 8 . 6 8 2 -6 8 6 dalos m ie m b r o . I5b
I plantillas, arreglos. 6 6 5 -6 6 7 for. c i c l o s . I 05 - I 97
f u n c i o n e s v irt u a le s
subproccsos. 8b I
ID E (Entorno de D e sa rro llo tipos ilc poder. b55 con cero. 4 4 0
Integrado), 937 im p r im ir o b je t o s . 3 0 0 - 3 0 I
identifícadores cadenas de texto. 3 3 (¡a lo . I b I - 1b3
nombrar, 783 caracteres con base en los m étodos constructores.
ocultar, 601 números (listado). 5 7 159
I E E E (Instituto de In g e n ie ro s caracteres de impresión re fe re n c ia s . 261
Eléctricos y Electrónicos), especiales, 5 8 v a ria b le s . 5 1. 109
808 en pantalla. 3 1 -3 4 b ic a le s. 1 1 I
If, instrucciones, 81, 83, 105 prinllO. función. 5 8 1 - 5 8 3 ie m h ro , 301
anidar, 86, 88 valores en modo D E P U R A R in ic ia r
listado, 87 (listado). 7 b 7 -7 b 8 c u la c s . 8 12
llaves ({ }), 88-90 valores interinos, 7 b 7 - 7 6 8 vi. e d ito r, 8 0 9 -8 10
1084 ín d ic e

for_each(), algoritm o, 707-70,S listado, 387-388 Faetón i


fo rk (), función, 854 nombres de arreglos. apuntadores. 273
Form ato Ejecutable y de 389 referencias. 274-275
Enlace (ELF), 818 caracteres. I 39 Iib( i.204
form ato intermedio (compi­ cargar. 122 declarar. 549
lador), 748 comunes. 140 sobrecargar operadores,
FORTRAN, 201 consi. funciones miembro. 544-548
friend, palabra reservada, 549 163 FuneíonI .lenarlnU). 681
fstream , clases, 501 declarar. 163 l uncionUnol). 278
fuerte tipificación, 104 unplementar. 164 generales. I 39
fugas (memoria), 236, 287-289 ventajas. 164 ( ietSmng(). 397
dclete. instrucción. 236 control de procesos. 856 1lacerTareaUnoi). 211
rcasignación de apunta­ Convertir!). 107-108 herencia
dores. 239 cuerpo. 37 conversión descendente.
FUNC.CPP, demostración de de acceso. 153-154. 247 416-419. 451
una función simple (listado), de encabezados. 104 filtrar funciones compar­
38 de plantilla tidas. 416. 451
funciones, 19,30,37-39. Vea declarar. 669. 673 ImprimirF.rrorl). 735
también macros; métodos especializada, 68 I -682. incremento. 307-308
abstracción, 129 686-687 Insertan ). 534
amigas. 544, 548 nnplementación. 673 instrucciones. 111-112
amigas que no son de plan­ de plantilla generales. integradas. 100. 137
tillas, 669-672 673-676. 710 intercambian). 113. 267
apuntadores. 463-466, declarar. 101 apuntadores, 268-269
arreglos. 470, 472 listado. 103 referencias, 269-271
asignar, 467 ubicaciones de archivos, Intruso(). 669
declarar. 464-466 167-168 invocar. 36. 129. 133
desreferenciar, 470 definidas por el usuario. IPC. 879-880
listado, 464-468 100 listado. 38
pasar, 472-475 definir, 104. 111, 150. 168 llamar. 36, 100. 111
typedef, 475-477 concordar. 105 listado. 36
ventajas, 467-470 en orden, 101 pila, llamadas. 722
archivos de encabezado, fuera de espacios de macros, 752-755
271-272 nombres, 606 main(). 30, 99
argumentos, 101, 112 prototipos, 102 matemáticas. 137-138
pasar, 104 DibujarFiguraO, 293-295 Maullarí). 154, 158
pasar por referencia, DobleO, 124 menu(). 2 11
266-268, 275-278 Duplicador!), 116 miembro
pasar por valor. 113-114. ejecutar, 105 const. palabras reservada.
142, 267-268 en línea, 122-124, 755-756 251
predeterminados, desventajas, 122 getlineü, 570
116-118 expandidas. 756 miembro estáticas, 461-462.
arreglos, objetos, 668-669 listado, 755-756 687, 690-691
AsignarPrimerNombreí), espacios de nombres, 600, acceder, 463
507 606 listado, 461-462
ASSERTO, 785 especializadas, 681-682, llamar, 461-463
bibliotecas, 41, 134. 137 686-687 modificar, 414
C++. 269-271 especificadores de acceso, MostrarTodo(), 533
cadenas, 139 338 multiclase, 414
índice 1085

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

extensiones de archivos. compiladores. 694 herencia, 14, 3 3 3 -3 35


17 datos (arreglos). 367 agregar a dos listas.
formato intermedio. 748 direcciones de memoria en 419-420
soporte para plantillas. apuntadores. 228-229 c la s e s . 3 3 5
791 objetos (arreglos). 375 comparación con la gencra-
susceptibilidad al uso de guardias de inclusión, 751-752 li/ación. 631
mayúsculas y minúscu­ G l TIs (interfaces gráficas de comparación con plantillas,
las. 49 usuario), 808 710
variables miembro elementos. 896 constructores. 340-342
estáticas indefinidas. historia. 897 argumentos. 342-346
457 ¡nieractuar con programas. sobrecargar. 342-346
declarar función mainf). 31 918 contención. 501
depuradores, 7 4 0 -7 4 1 Linux. 899 acceso. 508
documentación en línea. 39 nativas. 910 constructores, 509-511
editores de texto, 17 programación copiar por valor. 511,
emacs, 8 1 2 - 8 1 4 wxWindows. kit de 515
comandos. 8 1 3 herramientas. 909 costos. 508. 511
modificar archivos, 8 15 señales. 9 18 delegación. 516-531
tutorial en línea, 81 widgets. 903-904 filtrar el acceso. 508
enlazador. 601 implemcntar. 515-516
gdb, depurador, 82 2-8 23 conversión descendente,
ayuda, 8 2 3-8 24 416-419. 451
cerrar, 824 H derivación. 334-335, 364
comandos, 824 destructores, 340, 342
sesión de ejemplo, 826- h, extensión de nombre de funciones. 416. 451
827 archivo, 168 limitaciones, 413-416
make, utilería, 820-822 HacerTareaUnoí), función, 2 1 1 listado. 336-337
sitio Web, 9 hardware (CPUs), 130 métodos virtuales, 352-357,
goto, instrucciones, 18 2 heap, 235, 256 431-435
ciclos, 1 8 1 - 1 8 2 arreglos apuntadores v, 356
limitaciones, 183 declarar, 3 8 1 -3 8 2 constructores de copia,
recomendaciones, 183 eliminar, 38 4 360. 363
sintaxis, 183 guardar, 38 0 -38 1 costos en memoria, 363
grupos de noticias, 786 crear/eliminar objetos declarar. 435
G T K + + , 898 (listado), 240 -24 1 destructores, 360
aplicaciones, 907 datos, 2 3 5 invocar múltiples,
funcionalidad, 9 18 datos miembro 353-355
G N O M E , 904 acceder, 2 4 1 -2 4 2 listado, 432-434
G T K — , 901 apuntadores, 2 4 2 -2 4 4 partición, 358-360
L G P L , licencia, 904 memoria tablas v, 356-357
recursos en línea, 901 asignar, 236 mezclas, 436
w x G T K , biblioteca, 909 restaurar, 2 3 6 -2 3 8 múltiple, 420-422, 650
G T K + + (K it de herram ientas objetos clases base compartidas,
G im p ), 900 crear, 240 427-431
gu ard ar eliminar, 24 0 -24 1 constructores, 424-426
apuntadores, 2 3 2 - 2 3 3 ventajas, 2 3 5 -2 3 6 declarar, 423
arreglos HELLO.CPP, demostración Java, 450
en el heap, 3 8 0 -38 1 de componentes de un limitaciones. 435
en la pila, 380 programa (listado), 30 listado, 420-422
Indice 1087

métodos virtuales. 422 else. palabra reservada. Im prim irErrorí). función. 7 35


objetos. 42 ' 85-86 inelude, archivos. 785
resolución de estilos de sangría. 84-85 inelude, instrucciones. 30. 4 1.
ambigüedad. 426-427 evaluar. 86 561
privada. 525-526 sintaxis. 86 incrementar, 75
lis t a d o ile p r o g r a m a de ifdef, comando del precompi­ incremento, funciones. 307-308
ejemplo, 526-533 lador, 749 incremento, operador (++), 7 5
métodos. 525 ifndef. comando del prccom- agregar. 307-308
sugerencias de uso. 534 pilador, 749 posfijo. 75-77
simple (listado). 4 14-4 15 ifstream, objetos, 585-587 prefijo. 75-77
UML. 622 ignorar espacio en blanco, 570 independientes de la plata­
virtual. 43 1.435 ignore«), función, 573-596 forma. 5 5 7
declarar. 435 igual, signo (= ). 5 1 , 7 1, indicadores, 7 7 5 , 836
listado. 432-434 32 1-32 4 de estado. 577-579
herramientas (G N U ), 9 imitar, R T T I (Identificación establecer. 579-581
historia de C ++, 9 -10 , 1 4 -15 de Tipo en Tiempo de manipulación de bits. 775
historial, lista de (comandos), Ejecución), 416 resetiosflags, manipula­
844 implementar, 624 dores. 584
Hola, mundo, program a, 20 büferes. 560-561 setiosfiags. manipuladores.
código fuente. 21. 29-30 clases. 170-171 584
compilar, 21-22 contención. 515-516 índice, operador ([ j), 3 7 5
ejecutar. 22 flujos. 560-561 índices, 692
GNU. g++. 23 funciones virtuales puras. indirección, operador (<),
listado, 21 441-445 229-230, 284-285
Hollerith, tarjetas de, 57 intercam biad). función info, comando, 13 7
HOM E , variable de am bien­ apuntadores. 268-269 inicialización, instrucción de,
te, 840 referencias. 269-271 195
HOWTOs, 831 métodos. 156-159 inicializar, 159 , 2 6 1. Vea tam­
hp, extensión de nombre de const, 164 bién declarar
archivo, 168 constructores. 163 apuntadores, 228. 234-236
hpp, extensión de nombre de de clases. 157-158. 168 arreglos, 373-374. 378-379
archivo, 168 de plantilla, 673 clases base. 342
destructores, 163 constructores, 300
en línea, 169-171,330 datos estáticos, 691
plantillas, 666-668, 682-686 datos miembro, 159
plantillas, arreglos, 665-667 for, ciclos, 195-197
subprocesos, 861 funciones virtuales
IDE (Entorno de Desarrollo tipos de poder, 655 con cero, 440
Integrado), 937 imprimir objetos, 300-301
identificadores cadenas de texto, 33 Gato, 161-163
nombrar, 783 caracteres con base en los métodos constructores,
ocultar, 601 números (listado), 57 159
IE E E (Instituto de Ingenieros caracteres de impresión referencias, 261
Eléctricos y Electrónicos), especiales, 58 variables, 51, 109
808 en pantalla, 31-34 locales, 111
If> instrucciones, 8 1 , 83, 105 printfO, función, 581-583 iembro, 301
anidar, 86, 88 valores en modo DEPURAR iniciar
listado, 87 (listado), 767-768 emacs, 812
llaves ({ }), 88-90 valores interinos, 767-768 vi. editor, 809-810
1088 ín d ice

inline, instru cción , 1 2 2 , 14 2 , for. 195-196 in tcrcn m h in rf), función, 1 13 ,


169 ciclos a\ an/ados. I 9 6 267
inserción, o p e rad o r ( « ) , 3 2 sintaxis. 195 apuntadores. 268-269
co n ten e d o res vectoriales. friend. 549 encabezados \ prototipos.
60S funciones. I 1 1 271
sobrecargar. 549. 553 funciones, prototipos de. recscrila con referencias
In s e r ta r )). función, 4 9 6 , 5 3 4 102 (listado). 270
in se rta r, m odo (ed itor vi), goto. 182 referencias. 269-271
810 ciclos. 18 1-182 interfaces
in sta lar lim itaciones. 183 Java. 450
G N O M E . 900 recom endaciones, i 83 program ación orientada a
K D E. 901 sintaxis. 183 ob|etos. diseño, 643
Linux. 8 if. 81-83. 105 Interfaz, de Dispositivo
instan cias (plantillas), 662 anidar. 86-90 G rá fico (G D I), 898
instrucciones, 6 7 else, palabra reservada. interfaz gráfica de usuario.
apuntadores. 130 85-86 Vea G U Is
bloques. 81 estilos de sangría. 84-85 intérpretes, 10
catch. 716. 721 sintaxis. 86 intérpretes en tiempo de
try. 716, 721 if com plejas. 86 ejecución, 10
break. 186-189 include. 30. 561 interprocesos, comunicación
do...w hile, ciclo, 193 iniciali/.ación. 195 entre. Vea II'C
listado. 187-188 inline. 122. 142. 169 In tru so )), función, 669
precauciones, 189 new. 236 invocar
catch. 716, 721-725 null, 67 apuntadores a métodos,
class. 147. 155-156, 663 operator. 317 478-480
const. 60. 163. 177. 786 paréntesis, 78-79 com piladores, 18
continue, 186-189 precedencia, 77 funciones. 36. 129, 133
do...w hile, ciclo. 193 protected. 337 m étodos
listado. 187-188 return, 37, 114-116 estáticos. 461 -463
precauciones, 189 struct, 175-176 de la base. 350-351
cout. 32-34 switch. 205-207. 45 1 ios, 5 8 7
de control (bash), 846-848 ciclos eternos. 208-21 1 ios, clase, 5 6 1
de retom o m ultiples (listado), listado, 206-207 ios::dec (decimal, base 10), 579
115-116 sintaxis. 205-208 io s::h e x (hexadecim al, base
default, 207 sugerencias de uso, 21 1 dieciséis), 5 7 9
define, 59 valores de case, 206 io s::o ct (octal, base ocho), 579
listado. 749-750 tem plate, 663 iostream , biblioteca, 5 5 7
pruebas. 749 try. 716, 721 iostream , clase, 5 6 1
sustitu cio n es de cadenas. typedef, 475-477 iostream , objetos, 579
748 verdad/falsedad, 93 IP C (com unicación entre
su stitu cio n es de constan­ watch, 788 procesos), 8 7 3
tes. 749 while elim inar objetos, 882
delete. 236-238 ciclos, 183-184 estructura de permisos. 881
delete! ). 384 lim itaciones, 191 funciones, 879-880
d o ...w h ile . 193 sintaxis, 184-185 lim piar recursos, 881
else. 749-75 1 int m ainí), 1 1 6 m em oria compartida. 890
endl, 33 interacción, d ia g ra m a ( U M L ) , crear, 890
en u n c ia d o s d e visión, 626 637 definir clase, 892
ex p resio n es, 70 in teractivos, shells, 8 4 9 elim inar, 891
In d ic e 1089

semáforos. KXb K li • kilohv tes i. 9 5 2 le n g u a je s


crear. XX7 K D 1 i l n tonio de K scrilorin
definir dase scm.il«t k . K i. S W , 9 2 3
889 \ f ’l
System V. crear das es. b ; b ! i . >tc, .*.x '> ; v
879-880 h.úmd.üU-x ‘«»7
ver estado de objetos, sx 1 m xt.O.ir ‘ >(»1
ípeí), llamada de sistem a, X92 K 1 >1 1 I c l l t ' W « ' ! Id pr« «er.irn.i I i s s I if. X9K
ípc_perm, estructura (listado i, w.'.j «j; * I ( .1 * 1 ., lice n cia . 9 0 4
881 k l V i el««p. o o is lib e ra r
iperm, comando, 881 („)!. b i b l i« >tci .i. 9t I x
e le m e n to s . *
Ipes, comando, 8X1 i c , in xi >x e n l i n c a . 9 1 1t
m e m o r ia 2 7 ;s
ISO (Organización I.IK O . e s tr u c tu r a s . 7 0 5
W i n d o w . v í a s e . 92(> 9 2
Internacional de lim itacio n es » v a ria b le s g lo b a ­
.»Ciega» b o t o n e s a.
Estándares), estándar, 15 les). 1 0 9 - 1 1 0
9 2 S -9 ' |
Istream, clase, 561 lim ita r a lc a n c e de v a r ia b le s v
a g i e g a t m e m i s a.
iteración (ciclos), 181 de a p u n ta d o r e s . 4 5 5
9 U - 9 3(x
alcance, 202 lim p ia r o v a c ia r
w x W m d o w s . Kit xle l l e n a
anidar, 200-201 bu lores. s(>< >
m ie n ta s . 9 0 9
do...whilc. 192-193 s a l i d a . 5*”5
K D L I k ilo W orld. p ro gram a
Fibonacci, aplicación ile la lin cam ien to s. 6 3 4
agiegat botones. 9 2 8 - 9 * 1
serie de, 203-205 lin cam ientos de estilo icin iig o ).
agregai memis. 9 * 4 - 9 * 6
for, 194-200 780
con clase Window derivada alineación de llaves, ‘' s i
goto, palabra reservada.
181-183 (listado). 9 2 6 com entarios. 7 S 4
rccursión, comparación. 2 12 lisiado. 9 2 4 ilclluiciones ilc clases. •V'
whilc, 183-192 K Develop, 9 3 7 etiquetas ilc acceso. . S.>
características. 9 3 7 incluilc. a r c h i v o s . 7 S 5
iteradores, 699-700
iteradores no constantes, 700 descargar. 903 legibilidad del co d ig o . 7 S _
iterativo, diseño, 623-624 KDH. bibliotecas. 9 3 8 lincas largas. 7 8 1
controversia, 625 Key, clase (listado), 880 ortografía. 7 8 3 - 7 8 4
pasos, 624 key, p arám etro, 88 4 sangría. 7 8 1 - 7 8 2
K F M , biblioteca, 9 3 8 uso de m ayú scu las. 7 S 3 - 7 N 4
K H T M L W , biblioteca, 9 3 8 L in u x . 8
kill, com ando, 8 4 1 /proc. sistem a de a rch iv o s.
J kilobytes (K B ), 9 5 2 858
K orn , shell (ksh), 8 36 acceder inform ación del s is ­
Jacobson, Ivar, 623 kprocess, contexto, 8 5 4 tema. 8 5 8
Java, 10,450 ksh, 8 36 apariencia del escritorio. 8 9 6
jerarquías K spcll, biblioteca, 9 3 8 ayuda en linca. 8 1 1 - 8 1 2
clases, 337, 436 K T M a in W indow , clase, 9 26 com andos, 8
excepciones, 725-728 control de p rocesos. 8 4 2 .
herencia, 333 8 5 6 .8 5 8
docum entación en linca
L en línea, 3 9 . 8 3 1
H O W TO s y FA Q s. 83 1
K 1-values. Vea valores editores de có d ig o
leer datos en excepciones, em acs. 8 1 2 - 8 1 5
Kapplication, constructor, 927 7 2 8 -7 3 2 vi, 8 0 9 - 8 1 0
KAppWizard, 937 legibilidad del código, 7 8 2 vim . 80 9
1090 ín d ice

G N O M E . 899 m tcracti\os. 849 a objetos const. 251-252


API. 905 program ar. 835-836 almacenamiento.
G D K ++. 904-907 rcdircccion de E/S. 232-233
G T K ++, 900 837-839 analizar cadenas sintácti­
instalar. 900 variables de entorno. camente. 253-255
program ación. 904. 908 839-840 com o ilaios miembro.
recursos en línea. 900 variables locales. 839 243-244
w idgets. crear. 907 shm id_ds. estructura. 890 archivos de encabezado.
G N U . com piladores. 808 wxW indows kit de herra­ 134
G U Is. 809. 896-899 mientas archivos makc. 136
interactuar con progra­ agregar mentís a la clase argum entos de la línea de
mas. 91 8 Window. 920-92 1 com andos. 592-593, 595
w idgets. 903-904 com unicación de o b je­ arreglos
historia. 808 tos. 918 apuntadores a funciones.
instalar. 8 crear aplicaciones. 470-472. 480-481
KDE. 923 91-917 clases de arreglos, plan­
crear aplicaciones. funcionalidad. 909-910 tillas, 663-664
9 2 8 -9 3 1 .9 3 4 -9 3 6 procesam iento de even­ consts y enums. 374-375
crear aplicaciones con tos. 919-920 creación de objetos, 376
K D evelop. 937-938 wxStudio. 922 crear por medio de new,
habilidades. 902 L in u x T h read s, 861 383-384
instalar, 901 List, clase, 600 escribir más allá del
KDEH elloW orld. pro­ lista, clases base, 661 final de. 370-371
gram a, 924-927 lista, contenedores, 6 9 9 -70 1 guardar en el heap.
Qt. biblioteca, 903 listados 380-381
recursos en línea, 903 define, com ando del pre­ llenar. 386
lenguajes, 816 compilador, 749-750 mu lt ¡dimensionales,
lenguajes de secuencias de + . operador, 319-320 378-379
com andos, 818 abrir archivos para lectura y arreglo de enteros. 368-369
llam adas de sistema, 856-858 escritura, 586 asignación, operador (=),
ipeO, 892 acceder 322-323
m sgrcvO , 884 datos m iem bro en el asignar, usar y eliminar
m sgsndí ), 884 heap, 242 apuntadores, 237-238
pipeO , 874 m iem bros públicos, 152 ASSERTO, macro, 759
p o p e n í), 877 ADTs, derivar de otros bash, instrucciones de
m sgbuf, estructura, 884 ADTs, 445-448 control, 847
m sqid_ds, estructura, 882 agregar al final de un archi­ bloques (variables), 110-111
m ultitareas, 841 vo. 587-589 botones.ee, programa
páginas del m anual, 830 agregar un operador de (GN OM E), 906-907
probar valores, 3 1 increm ento, 307-308 break y continué, 187-188
program ación, 903 ajustar ancho de salida, 578 Cadena, clase, 392-395,
program ación de sistem as, algoritm os de secuencia 502-505, 553
853-856, 859-861 imitantes, 708-709 cadenas, 389-390
p ro g ram ad o r de tareas, 860 apuntadores descontrolados, cam pos de bits, 778-779
puertos de E/S, 837 crear, 248 ciclos eternos, 209-21 i
shells apuntadores, 389-390 ciclos while complejos.
archivos de inicio, 849 a funciones, 464-468 185-186
estab lecer variables, 840 a funciones m iem bro, cin, m anejar tipos de datos,
funciones, 836 478-479 563
In d ic e 1091

clase anupa. óctuplo. 4 c m <"»íi .si i-l u s o 4 c *. - i r ; .t •¡ - v :v , •w »

535-542 H c s . *2 s 7{ *’1 2' '


clase de bloque»» siiKinno 4 c , p l . ) / . i r . i tra s es 4 c !.i^
865 iist.iN ¡ v i m e d i o 4 c 1 1 c : . t •. . . . ; . I N :*>*>

clases base. 42b 4 »o 4 o; cs. *oo


clases con m é t o d o s de .u *e 4 e s t i n o . 4 i r c « . «. i o n e s 'lis
so. 154 i i c t c r c i K í.i * 2 6 1 I f \ ( < PP 4 c ::t< ”'t r . u : :
clases de figuras. 4 '7-4 m ilc s tr iK lo tes, l o l lo v 4c una t u m r o n M in p ir ;s
Condición, o b i e t o s s a n a b l e s u n U2 l u n . H ' i i a i n i c - i q u e n> 4c
de clase. 869 4 c t c T t u in . t r t a m a ñ o s d e u p ’ s p l a n t ill a . fv i'f
condicional, operador. ‘»4 os 4 e v ai l a b i o s . 4 > ! un*, n m e s
constantes enumeradas. M d ir e c c ió n 4 c. o ¡x * r.u lo ic v b ib li* 'te c a s . I ; 4

Constantesi>. método. 226 227 declara*, io n e s ,

762-768 lio vv l u l o , c i c l o . 1 o 2 I ' o b j e t o s . "‘ti

constructor de copia virtual. o Iso . p a la b ia ic s c iv n d .i. bs p«>11m* ’r1tsrno. 12<• 1~ -


360-363 E m p ic a d o . c la s o v p r o g r a m a Junciones 4 e c.ulena'».
constructores. 1 6 1 - 1 6 2 c o n t i i >la4or. b( )S - S i ) ;s*' 'b s
( u n c i o n e s m i e m b r o e v t a t u .-.v
clase contenida. 509 5 I I en lin c a , lu n c io n o s . I 2 V
llamar. 340 342 ” 5 s -7 5 6 4 6 1-4 6 2
sobrecargar en c l a s e s enteros co nstantes. 6 2 lu n c io n e s v irtu a le s .

derivadas. 3 4 2 -3 4 5 c u tí.u la m ú ltip le . 5 6 5 -5 6 6 3 5 2 - 3 b 5.


t u n c i o n e s v «.latos m i e m b r o
constructores de copia. erro r de c o m p il a c ió n .
d e m o s tra c ió n . 24 e stá tic o s. 6 8 7 - 6 9 0
303-304
Contador, clase. 30 6 -30 7 g e t i ). f u n c i ó n
e s c rib ir
c o n a rre g lo s 4e c a ra c te
contravarian/a. 802-803 i m i s a l i a eleI I m a l d e u n
res. 5 7 0 - 5 7 I
convertir a rre g lo . 3 7 2
con p aram etro s. 5oo
de Contador a unsigned p a la b ra s m ú ltip le s .
sm p arám etros. 76 S
shortí). 327-328 5 6 4 -5 6 5
g c t l i n e i i. f u n c i ó n . 5 7 1 - 7 -
de int a Contador. una c la s e en un a rc h iv o .
g lo b a le s v lo c a le s , v a r ia b le s .
325-326 5 9 0 -5 9 1
espacios de nombres. 607 10 8 -10 9
de USHORT a Contador.
especificación de tipo GNOM EHelloW orld. pro
326
Cout, instrucción. 3 2 -3 3 (conversión) grama
descendente. 417-418 co n botones. 9 1 3
creación de vectores y acce­
so de elementos. 694-607 para un punto flotante. con meiuis agregados.
crear referencias. 260 73 921
crear/eliminar objetos del Evcnt. definición del objeto, guardados en el ( D-ROM .
heap, 240-241 868 21
dados.c. programa, 8 16 excepciones HELLO.CPP. dcm osuacion
datos miembro estáticos. con plantillas. 735 de los com ponentes de un
456-457 funciones virtuales en programa. 30
declaración de clase gato en excepciones. 732-735 HELP.CPP uso de com en­
gato.hpp, 16 9 -17 0 múltiples. 723-725 tarios. 35
declarar una clase completa, pasar por referencia en. herencia
172-173 732-735 múltiple. 420-422
delegar a una lista enlazada plantillas, 736-738 privada. 526-533
contenida, 5 1 7 - 5 2 4 recuperar datos de. sencilla. 4 14-4 15
demostración de una llamada 728-731 simple. 336-337
a una función, 36 lillO, método. 579 virtual, 432-434
1 0 92 índ ice

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

parám etros. 752 l i e s r e t e r c n c i . i r . 2 2 o 2 9). RAM. 44. 129-131


paréntesis (». 753-754 2 34 legistros. 130
plantillas, com paración, im ciali/ar. 228. 254 uso del compilador. 372
754-755 m anipulación de datos. \ .tii.ibles. 43. 54
predefinidas. 758 231-232 m em o ria com partida, 890
sintaxis. 752-753 nombrar. 229 crear. 890
ventajas. 788 nulos, 228 del mu clase. 892
m a in (), función, 3 0 -3 1, 99 perdidos, 228 eliminar. 891
niukc, com ando, 1 3 5 pisotear. 249 m en o r que, símbolo (<), 21,
m ake, utilería, 820-821 r e a s i g n a ! . 2 *9 32. 81
m akefíle, 13 6 , 8 20 -8 21 RTTI. 4 16 m ensajes
destino/dependcncia. 136 lilis. 246-247. 313-31 4 catch, instrucciones. 732
form atos. 136 ventajas. 234 creai colas de. 882-885
RCS. 828 arreglos. 379 enla/adores. 600
M am ífero, clase, 3 3 7 asignadorcs 692 imprimir
m an ejar tipos de datos, 56 3 asignar. 149. Vea también en pantalla. 3 I
m anejo de eventos, 9 18 -9 19 crear m ensajes de advertencia, 25
m anipuladores, 584 compartidos. 890 m enili ), función, 211
m anipular m entís
direcciones. 225. 230
apuntadores, 234 agregar a clase KDE
determinar. 226-227
archivos de paquete. 133 examinar. 232-233 Window. 934-936
búferes, 560 guardar en apuntadores. agregar a la clase Window
cadenas. 138 228-229 ile wx Windows, 920-921
caracteres. 138 M essage, objetos, 885-886
recuperar. 229-230
datos, 146. 231 eliminar, 250 M etaobjetos, com pilador
m antenimiento (program as), (MOCO, 927
espacio de código. I 30
1 0 1 , 13 7
examinar. 742 M eta, teclas, 812
m ap, clases de contendores, m etodologista, 623
lugas. 287-289
7 0 1-7 0 4 m étodos de acceso, 153-154
delele, instrucción. 236
m áquina virtual (VIVI), 10 m étodos de acceso públicos,
listado. 287-289
m atem áticas, funciones, 153-154
reasignación de apunta­
137 -138 m étodos. Vea también
dores. 239
M a u llarí), función, 15 4 , 158 heap, 235. 380 funciones
m ayo r o igual que (>=), amigos. 548-549
acceder, 241-242
operador, 81 apuntadores. 242-244 apuntadores. 477-480
m ayor que (>), operador, 81 asignación de memoria. arreglos, 480-482
m ecanism o de señales y 236 declarar. 478
ran u ras, 9 31 datos. 235 invocar, 478. 480
m ecanism os, 6 28 objetos, 240-241 archivos de encabezado.
m em oria, 130 objetos, eliminar, 240 271-272
apuntadores, 225-227 restaurar, 236-238 Asignarlidad( ), 242
a funciones, 463-477 ventajas, 235-236 clases, 157-158. 168
a m étodos, 477-482 limpiar la memoria asigna­ Clone( ). 360, 363
arreglos, 380 da. 244 Constantes! ), 762-767.
const, 250-253 métodos virtuales, 363 770-771
const this, 253 pila. 130-131, 235. 705 constructores. 159
declarar, 228, 234 limpiar, 235 de copia, 302-306
descontrol ados/am bulan- m eter datos en. 131-132 declarar, 329
tes, 247-250 sacar datos de. 131-132 implementar. 163
Indice 1095J

iniciali/nr. 3<Mi seto >. 580-581 m odificar


llamar a múltiples. sobrecargar. 293-295. 348 arclm os. 827-829
424-426 valores predeterminados. 329 funciones. 4 14
predeterminados. declarar. 296-298 modo, editores de, 8 1 0
16 0 -164. 29X sugeiencias de uso. 298 mordiscos, 9 5 1
sobrecargar. 269 3(H) \ irluales. 352-357 M ostrurTodo( i, función, 5 3 3
de acceso. 154 apuntadores-v. 356 Nlotif, 898
declarar constructores de copia. M OV, 9
ubicaciones de are lusos. 360. 363 m sgbuf, estru ctu ra (listado),
167-168 costos en memoria. 363 884
valores predeterminados. destructores. 360 m sgrcv<), llam ada de sistem a,
296-298 herencia múltiple. 422 884
destructores. 156 listado. 352-353 m sgsndt), llam ada de sistem a,
implementar. 165 llamar múltiples. 353-355 884
predeterminados. partición. 358-360 m sqid_ds, estructura. 8 8 2
160-163 tablas v. 356-357 múltiple entrada. 5 6 4 -5 6 6
en línea. 16 9 -17 1. 330 \\ ultht >. 577-578 múltiple inicialización (ciclos
estáticos. 4 6 1 -462 \\ n ie l). 575-577 for), 1 9 6 -19 7
acceder. 463 métodos virtuales, 3 5 2 -3 5 7 múltiples clases base
listado. 4 6 1 -462 aptrv. 356 constructores. 424-426
llamar. 4 6 1-4 6 3 constructores de copia. 360. objetos. 423
modelo de diseño estáti­ 363 resolución de ambigüedad.
co, 645 costos en memoria. 363 4 2 6 -4 2 7
ventajas. 483 destructores. 360 múltiples excepciones,
Tillo. 578-579 herencia múltiple. 422 7 2 2 -7 2 5
flushO, 575 listado. 3 5 2 -3 5 3 múltiples valores, 2 7 2 - 2 7 5
gctO, 386. 567-569 múltiples, llamar. 3 5 3 -3 5 5 multiplicación, operador, 2 30
arreglos de caracteres. partición. 358 -360 multiprocesamiento, 8 5 9 , 890
570-571 tablas v. 3 5 6 -3 5 7 multitareas, 8 4 1
parámetros de referencia mezclas (clases de capacida­ mutex, 864-866
de caracteres, 569 des), 436
sobrecargar, 5 7 2 miembros
getlineí), 5 7 1 - 5 7 2 clases. 151
herencia privada, 5 2 5 estáticos, acceder, 691 N
ignoreí), 5 7 3 -5 7 4 , 596 objetos, acceder, 149
implementar, 15 6 -15 9 minimizar errores, 167 n (código de nueva línea), 3 1
métodos de acceso públicos, mknod, comando de shell, 878 Nam edPipe, clase (listado),
15 3 -15 4 M O C (Compilador de 878
métodos de la base, llamar, metaobjetos), 927, 932 navegar
350-351 modelos emacs, 8 1 2
ObtenerAreaí ), 174 clases, 641 vi. editor. 8 1 1
ObtenerEdadí), 158, 242 de dominio, 629 N C I T S (Com ité
ObtenerSupIzqO, 174 dinámicos, 657 Estadounidense p ara
ocultar, 348-350 estáticos, 644 Estándares de Tecnología de
peek(), 5 74 -575 programación orientada a la Inform ación), estándar,
printfO, 582-583 objetos, 620 15
put(), 575-576 subtipos, 653 new, operador, 2 8 3
putback(), 5 7 4 -5 7 5 , 596 modelos dinámicos, 657 new(), operador, 6 9 3
redefinir, 346-348 modelos estáticos, 644 new, instrucciones, 2 3 6
1096 índice

niveles de depuración, notación de cam ello. 50


O
7 6 9 -7 7 4 notación h ú n g a ra , 50. 783
no hacer nada, operador, 84 notación n u m éric a, 947-948 obj, a rc lm o s, 19. 751
no inicializar nueva línea, c a ra c te re s t\n> . ( )bject, interfaz (listado), 874
arreglos de caracteres, 385 3 1 .3 3 ( )b jc c to r\. 623
búferes. 385-386 nueva línea, c a ra c te re s de ob jetos. 423. Vea también
elem entos de arreglos. 374. escape, 58 11e re n c ia : exce pciones
4! I nueva línea, delim itador. alias. 264
N odoln terno, objeto, 406-409 386-387 arreglos. 375-377
nodos, 39 8-40 0 nulas, instrucciones, 67. declarar. 375-376
N o H ayM em o ria, excepciones, 197-199 listado. 376
732 tudas, referencias, 266 comparados con las clases,
nombres M I. I., a p u n ta d o re s , 228, 148
am bigüedad. 6I I 266 contenedores. 632. 692
apuntadores. 229 eliminar. 250 cotit. 3 I
conflictos. 599-600 en comparación con apunta­ crear. 873
convenciones. 783 dores perdidos. 250 de F/S estándar. 561
de arreglos como apunta­ probar por si hav. 238 cerr. 5 6 1
dores. 382-384 nulo, carác te r, 385, 564 cm. 5 6 1-575
de clases. 14 7 -148 nulos, te rm in ad o res, 388 elog. 561
enlace. 603 num eral, sím bolo (//), 30, 748 cout. 561.575-581
espacios de. sin nombre. núm eros definir. 148. 155
613 base 10. 948 derivados. 338-339
¡dentificadores, 783 convertir a base 6. dominio. 643
macros. 774 949-950 eliminar. 873
ortografía. 783-784 convertir a base 7. 949 estados. 579. 657
plantillas, 664-665 base 2 (binarios). 951-952 fuera de alcance. 286
referencias, 260 convertir a. 950 funciones. 707
uso de m ayúsculas, ventajas. 95 I Galo, inicial izar. 161, 163
783-784 base 6 (hexadecim al), guardar miembros por refe­
variables. 48. 783 953-956 rencia. 245
claridad. 49 base 7. 949 heup. objetos del. 239
notación de camello, 50 base 8 (octal). 948-949 crear. 240
notación húngara, 50 952-953 eliminar, 240-241
palabras reservadas. 50 base, establecer, 579 herencia
precauciones. 48 contar (ciclos while). 189 conversión descendente.
susceptibilidad al uso de Fibonacci. serie de. 203-204 4 16 -4 19. 4 5 1
mayúsculas. 49-50 libO, función, 204 múltiple. 423
variables de comeo. 2 0 1 precauciones, 205 indicadores. 775
nom bres de archivo, exten­ interpretación por com pu­ inicializar. 159. 300-301
siones tadora. 56 iostream (estados). 579
c, l 67 valor/letra, relación, 57 Message. 885
CPP. 17. 167 números hexadecim ales, 579 , miembros, acceder. 149
CXX. 17-18 953-956 nulos, 266
h, 168 números ocíales (base 8), 57 9 , operador de asignación (=),
hp. 168 952-953 321
hpp. 168 base 10. 953 pasar
OBJ. 19 convertir números a partición de datos.
NOT, operadores, 92 ventajas, 953 358-360
Indice 1097

por valor. 302 objetos nulos, referencias a, operadores de despliegue,


referencias a. 2X2-28' 2X9 agregar. 6 73
plantillas, pasar. 677-6XI objetos temporales operadores integrados. 30 6
programación orientada a. crear. 3 13 operadores m atem áticos.
620 listado. 310-311 7 1-7 2
Qt. 903 nombrar. 3 12 com binar con operadores de
refercnciar. 264-265 regresar asignación. 74-75
en el heap. 287-289 funciones sobrecargadas. precedencia. 78. 944
listado. 264-265 310-313 residuo <cc ). 72-73
no existentes. 2X6-287 s in n o m b r e . 3 1 1 - 3 1 2 resta i - >. 71-72
relaciones. 63 I Obtener A reat), función. 174 operadores unarios, so b recar­
representaciones. 146 ObtenerEdadO , función, 158, gar, 3 1 7
señales. 9 3 1 242 operadores, 7 1 . \'ea también
subproceso. <863 O btenerEstadot), función. funciones
sustitutos. 643 779 <. 81
tamaños. 177 ObtenerPesoO, función, 169 <=. 81
temporales ( )btenerSupIzq(). función, >. 81
crear. 3 I 3 174 >=. 81
regresar. 3 10-3 11 ocultar a nivel de bits. 775
sin nombre. 3 I I -3 13 identificadores. 601 AND <&>. 775
valores, asignar. 149-1 50 métodos. 348-350 com plem ento <-). 6
visibilidad. 602 redefinir, comparación. 350 OR (I). 776
wxWindows, aplicaciones. variables (globales). 108 OR exclusivo(A). 776
918 ofstream, objetos, 585 apunta a (->). 241
objetos de E/S estándar, 56 1 abrir archivos para E/S. aridad. 32 1
585-587 asignación ( = ). 5 1 .6 8 . 1.
cerr, 561
cin, 561-563 argumentos. 587 321-324
cadenas, 564 comportamiento predetermi­ binarios, parám etros. 320
entrada múltiple. nado. 587-589 concatenación. 757-758
564-567 estados de condición. 585 condicional (>:). 94-95
gcl(). método. 567-57 1 O M T (Tecnología de conversión. 324-328
getlineí), método, Modelado de Objetos), 6 23 de despliegue. 673
571-572 opciones (línea de comandos), de dirección (&). 226 -2 2 /.
ignoreí). método, 592 261-262
573- 574 operaciones de secuencia no declarar sobrecargados. 327
operador de extracción. imitantes, 707-708 decrem ento (-). 75
567 operador de adición autoasig- posfijo. 75-77
pcckO, método, 574-575 nado (+=), 74 prefijo. 75-77
putbackO. método. operador de resolución de d elete t). 693
574- 575 ámbito (::), 601 direcciones, 226-227
elog, 561 operador igual a(==), 80 dynam ic_cast. 417
cout, 561. 575-58 I operadores aritméticos, 7 1 - 7 2 extracción ( » ) . 562
flushí), método. 575 combinar con operadores de increm ento (++). 75
put(), método, 575-576 asignación, 74-75 agregar, 307-308
write(). método, 576-577 precedencia. 78, 944 posfijo. 75. 77
objetos de función, 707 residuo (%). 72-73 prefijo. 75. 77
objetos derivados (listado), resta (-). 71 -72 indirección (<). 229-230.
338-339 operadores de conversión, 284-285
objetos no existentes, 286-287 3 2 4 -3 2 8 inserción ( « ) . 32. 549. 553
1098 Indice

integ rad o s. 306 m a n e ja d o rc s de e x c e p c io n e s p aram etri/ar


lógicos. 9 l 728 clases de arreglos. 662
AND (& & ). 9 1 ostream, clase. 5 6 1 plantillas. 662. 710
NOT (!). 92 ostream. operador, 6 7 3 -6 7 6 parám etros. 37
OR (II). 9 1-92 lunciones. 104. 112
m atem áticos. 7 1-72 gen ). método. 569
autoasignados. 75 k e\. 884
m ultiplicación. 230 P listas. 100-102. 119
residuo (c/c >. 72-73 ocultos. 246
resta (-). 7 I -72 páginas del m anual. 8 30 . 8 3 7 pasar. 592
nevv, 283 p alabras múltiples, 5 6 4 -5 6 5 pasar por valor. 113-114
n ew (). 693 p alab ras reservadas, 50, 176, predeterminados. 116-118
no hacer nada. 84 609 procesar en la línea de
osircam . 673-676 catch. 72 I com andos. 593. 595
posfijo. 3 14-3 16 class. I 47. I 55 I 56. 66 * rhs. 305
precedencia. 77-78. 92-93 const. 163. 177. 250-251. l 'sai Val Actual. 298
prefijo. 3 14-3 16 283. 603 variables locales. 106
punto (.). I55. 24 I delete. 236-238 parám etros de referencia de
redirección. 5 6 1 else. 85-86 caracteres (método gct()),
referencia (&). 260. enum. 60 56 9
284-285 for. 195-196 parám etros predeterminados
relaciónales. 80-83 friend. 549 (funciones), 1 1 6 - 1 1 8
sobrecargar. 306-307 goto parches, 19 2
cuestiones. 320 ciclos. 18 1-182 paréntesis (), 78
funciones amigas, limitaciones. 183 agrupar. 93
544-548 recom endaciones. I 83 anidación. 78-79
lim itaciones, 32 l sintaxis. 183 macro. sintaxis, 753-754
objetivos. 3 2 1 inline. 122. 142. 169 partición (métodos virtuales),
objetos tem porales, new. 236 3 5 8 -3 6 0
3 10 -3 13 operator, 3 17 p articio n ar la R A M , 12 9 -13 1
prefijo, operadores. private, 153 paso
308-310 protected, 337 apuntadores a funciones.
tipos de valor de retorno. public, 153. 158 472-475
310-313 RCS, 828 apuntadores const, 278-281
static_cast, 73 return, I 14-1 16 argum entos. 104
sum a ( + ). 31 7-320 static, 614 para constructores base.
unarios. 3 17 struct. 175-176 342-346
o p eran d os, 7 1 tem plate. 663 por referencia. 266-268.
operator, instruccion es, 3 1 7 try. 72 I 276-278
o p e rato r+ am igab le (listado), typedef, 53 por valor. 267-268
544, 546, 548 p alab ras reservadas de con­ m em oria entre funciones,
O R exclusivo (A), o p e rad o r a trol de acceso, 1 5 5 290
nivel de bits, 7 7 6 p an talla, im p rim ir en, 3 2 - 3 4 , objetos
O R , operadores 561 partición de datos.
a nivel de bits (I), 776 papeles, 6 2 7 358-360
lógico (II), 91 -92 paquetes, 6 3 7 por valor. 302
o rd en ar paquetes de bibliotecas, con­ objetos de arreglos a funcio­
elem entos de arreglos, flictos de nom bres, 600 nes. 668-669
409-410 paquetes, m an ipular, 1 3 3 objetos de plantilla. 677-681
In d ice 1099

parámetros. 11 3 - 1 14. 592 a rre g lo s p rcco m p ilad o rv s


por referencia c l a s e s , 6 6 1-6 fv4 . I >m.;'nl> >s
apuntadores <lis t a d o i. 268 im p le m e n l.ir. 6o5 ~vlet m e "4 s
excepciones. 7.12-7.15 >. la s e s . 7< )5 ffcN c "'4 9 - ~ * 1
objetos (listado). c o m p a ra c ió n con h c r c iu ia . ff ilv lc t ~4o
276-277 TIO -lin d e l ~4o
por valor. 1 1 1 . 267-268. datos m ie m b r o e stá tic o s. c u a r d ia s de iru lu s io n .
5 12 -5 15 6X7. 60 1
referencias d e fin ir, 6 6 2 -6 6 4 p r u e b a s . "'49
a objetos. 282-2X3 e x c e p c i o n e s . 7 1 5 - 7 1S s iis iitu c i« m es
a objetos (listado). fu n c io n e s . ad e n .is. 4s
28 1-28 3 c o n te n e d o re s v e c t o r ia le s . v i'lis ta n te s . 49
excepciones. 7 3 2 -7 3 5 698 p red efin idas, m a cro s. 7 x 8
PATH, variable de entorno, datos m ie m b r o e stá tic o s. prefijo , o p e rad o res. 7 5 . 7 /
840 6 8 7 -6 9 0 listad o . 76. s| 5 - ' I6
patrones de diseño, 449 -450 d e clara r. 66 9. 6 7 3 o p e r a d o r «,1c p o s f i j o , c o m
patrones de observación, e s p e c ia liz a d a s . 6X 1 -6 X 2 . p a ra c io n . ' I4- ' 16
449-450 6 X 6 -6 X 7 s o b re c a r g a r. IO S ' ÍO
peek(), método, 5 7 4 -5 7 5 implementacion, 6 7 3 prefijos (variables). 147
perdidos, apuntadores, 228. map contenedores. 704 preproeesadores. 30, 747-748
Vea también apuntadores implemeniar. 6 6 5-6 6 8 constantes de c la se s.
dcscontrolados 7 6 2 -7 6 7 .7 7 0 -7 7 1
mstanciamiento. 662
Perro, clase instancias. 662 fu n c io n e s en lin c a . / 5 5 - X6
constructores. 340 -34 2 macros. comparación. m a c r o s . 7 5 2 - 7 5 •>
constructores sobrecargados. 7 5 4 -7 5 5 A S S Í - R T t i. 7 5 8 - 7 6 1
345 nombrar. 6 64 -66 5 d e f i n i r . 7.*> 2-77.'
declarar. 3 3 5 -3 3 7 d e sv e n ta ja s . 7 5 4 - 7 7 7
objetos, pasar. 6 7 7 -6 8 I
destructores, 340-342 parametrizadas. 662. 7 1 0 p aram etros. 7 5 2
persistencia (variables p a r é n t e s i s < ). 7 5 . ' - 7 5 4
soporte del compilador. 665.
locales), 235 791 p r e d e fin id a s . 7 x 8
personas, 627 S T L , 6 9 1-6 9 4 . 6 9 8 -7 0 1 . s in ta x is . 7 5 2 - 7 5 3
pila, llamadas, 7 2 2 705 v e n ta ja s . 7 S S
pila (memoria), 2 3 5 , 380 polimorfismo (funciones), 14 , m a n ip u la c ió n de c a d e n a s.

datos, 1 3 1 - 1 3 2 12 0 -12 2 , 3 5 2 , 4 1 3 756


excepciones, 722 c o n c a t e n a c ió n . 7 5 7 - 7 7 8
funciones, 1 1 9 - 1 2 1
limpiar, 235 lograr en C + + , 800 uso de ca d e n a s. 7 x 7

memoria, 13 0 -13 1 minar. 4 1 9 n iv e le s de d e p u r a c ió n .

Pilas, 705 popenO, llam ada de sistema, 7 6 8 -7 6 9 . 7 7 3 -7 7 4

Pipe, clase, definir (listado), 877 valores interinos, imprimir.


875 posfijo, operadores, 7 5 - 7 7 7 6 7 -7 6 8
Pipeí), llamada de sistema, 874 P O S IX , bibliteca de subpro­ p r in tfi), función
pisotear apuntadores, 249 cesos, 861 a r c h iv o s de e n c a b e z a d o .

plantillas, 661-662 P O SIX , estándar, 808 581


agregar al ejemplo de la listado. 76, 3 1 5 - 3 1 6 dar formato con. 5 8 1
semana 2, 800 operador de prefijo, compa­ en comparación con cout.
amigas, 669 ración. 3 1 4 - 3 1 6 581
de tipo específico, 7 10 sobrecargar, 3 1 4 flujos, comparación. 5 8 2
generales, 673-676, 7 10 precedencia (operadores), limitaciones. 5 8 1 . 5 9 6
que no son de, 669, 673 77-78 , 9 2-93 prio rid ad (procesos), 8 5 9
1100 ín d ice

prívate, palabra reservada, 153 im plcm cnt.u. 861 de tareas


p rívate slots, sección, 9 3 2 listado. 862-863 algoritmo. 860
p ro b ar program ar. 864 subprocesos. 864
cadenas. 749 punto m uerto. 870 depurar archivos. 815
por si hay apuntadores sim ples. 862 diseño, 16
nulos. 238 subprocesam icnto muln entornos de desarrollo. 17
valores de retorno. 31 pie. 862 entornos de suhproce-
procedim ientos, 1 1 , 620 terminar. 856 samiento múltiple. 870
p rocesad ores de palabras, 25 tuberías. 874-875 estructurada. 11-12
p ro ce sa r (línea de comandos), con nom bre. 877-879 CíNOMli. 899. 904
592-593 sem idúplcx. 876 API. 905
procesos, 8 5 3 procesos de tiempo real, 8 59 aplicaciones. 908
acceder a recursos. 886 procesos hijos crear u idgets. 907
agregar (unciones para controlar, 858 CJDK++. 904-907
control de. 857 crear. 856 (¡TK++, 900
colas de m ensajes, 882-885 descriptores de archivos. instalar. 900
com unicación. 873 875 recursos en línea. 900
condición de carrera, 856 procesos norm ales, 859 GN U . utilería make,
control de, 856-858 Process, clase (listado), 8 5 5 820-821
crear. 854-855 producir excepciones (listado), opciones de la línea de
cuantos. 859 7 1 7 -7 2 0 comandos. 822
directivas. 860 program ación guardias de inclusión. 752
estado del proceso. 859 a la defensiva. 62. 714 KDE. 923
fork( ), función. 854 alias. 53 agregar botones a pro­
hijo. 856 alias de com andos. 845-846 gramas. 928-931
iterativo, 624 apurarse al código, 658 agregar mentís a progra­
listados archivos ejecutables, 19 mas. 934-936
funciones agregadas bash habilidades. 902
para control de proce­ eom plelación de com an­ instalar, 901
sos, 856 dos. 842 KDEHelloWorld, pro­
Process. clase, 855 sustitución de cadenas. grama. 924-927
subprocesos, 862-863 843 KDevelop, 937-938
m em oria com partida, 890 sustitución de salida de Qt. biblioteca, 903
crear. 890 com andos, 844 recursos en línea, 903
definir clase, 892 sustitución de variables, lenguajes de procedimientos.
elim inar, 891 844 620
mui ti procesam iento, 859 sustitución m ediante lincam ientos en el estilo del
prioridad, 859 com odines, 843 código, 780
program ación orientada a C++ en el escritorio de niveles de abstracción, 129
objetos, diseño, 622 Linux, 903 orientada a objetos, 13,
program ador de tareas, 860 ciclo de desarrollo, 19-20 660
sem áforos, 886 código fuente, com pilar, encapsulación, 13
crear, 887 18-19 herencia, 14
d efin ir clase, 889 com entarios, 784, 827 lenguajes, 660
sincronización, 886-889 controlada por eventos, 13, ocultación de datos, 13
subprocesos, 860 897 polimorfismo, 14
crear, 862-864 de sistem as, 853-856 ventajas, 660
directivas de sincroniza­ crear, 854 palabras reservadas, 50
ción. 864-870 procesos, 853 preparación, 16
ín d ice 1101

ramificación de programas. comentarios. 34-35. 716 plantillas. 735-736. 738


132-133 // (estilo C++). 34 sin errores. 738-739
recursos. 7X6 /< (estilo C), 34 fragilidad. 7 14
reentrancia, 862 cuándo utilizar. 34 fugas de m emoria. 239
shclls, 833-836 listado. 35 GNOM E. 908
instrucciones de control. precauciones. 35 GTK++. 907
846-848 compilar. 24. 817 GUI. elem entos. 896
secuencias de comandos. conflictos de nombres. 599 Hola, mundo. 20
846 conscientes de la sesión. código fuente. 21. 29-30
solución ríe problemas. 1 1 900 compilar. 21-22
subprocesos control de versiones. 827 ejecutar. 22
erear. 862-864 controlados por eventos. 13. listado. 2 1
897 imprimir mensajes en pan­
directivas de sin­
cronización. 864-870 corrupción del código. 715 talla. 31
listado. 862-863 crear fugas de memoria. 239 infractor. 370
definición. 10-11 interactuar con programas
programar. 864
punto muerto. 870 depurar. 739 de GUI. 918
archivos core. 74! llamar a función forkü. 8M
validación de dalos. 62
variables. 44 ASSERTl). macro. 760 lógica. 714
widgels. 903-904 ensambladores. 742 macros. 752
vvxWindows, aplicaciones. examinar memoria. 742 manejo de eventos
gdb. depurador. 823-827 iwxGTKl. 918-919
911-912
mantenimiento. 101, 1
botones, 913-917 GNU. depurador.
marcos de trabajo de aplica­
comunicaciones de obje­ 740-741
tos. 918 imprimir valores interi­ ciones. 903
nos. 767-768 nulas, referencias. 266
kit de herramientas. 910
niveles. 768-774 nulos, apuntadores. 2d0
menús (clase Window),
920-921 puntos de interrupción. parches, 192
por procedimientos. 897
procesamiento de ex en­ 742
precedencia de operadores.
tos. 919-920 puntos de observación.
742 943-944
vvxStudio. 922
preparar para situaciones
X. 897-898 desarrollar, 11, 19-20
excepcionales, 71 r>
programador de tareas, 860 diseñar, 16
probar valores de retorno.
programas a prueba de todo, eficiencia, 122
7 14 ejecutables. 11.19 31
procedimientos. 1 1
programas infractores, 370 ejecutar, 36
elegir la herramienta ade­ procesos. 853
programas. Vea también
cuada para programar, programación orientada a
aplicaciones
objetos (poo). 13. 620
a prueba de todo, 62. 714 190
ramificación, 132-133
ambigüedad de nombres, 6 1 1 enfoque. 918
referencias a objetos nulos.
apuntadores, 253 enlazar, 818
espacio en blanco, 284 289
apuntadores descontrolados,
estructura de. 12. 29-31 reinventar la rueda, 12
250
excepciones secuencias de com andos de
bibliotecas compartidas, 818
bugs o errores. 714 atrapar, 722 Shell. 846
funciones virtuales, 734 solución de problem as. 16
caja negra, método. 272
múltiples. 722 apuntadores descontro­
ciclos principales de eventos,
925 o rde nar manej adores, lados, 247-249
código fuente. 18 728 excepciones. 720-721
1102 índice

variables de contco. 2 0 1 push y p o p ,7 0 5 Rectángulo, clase


while. ciclo. 1X4 pulí ), función, 5 7 5 - 5 7 6 declarar. 1 7 3 - 1 7 5 . 300
X . clientes. 898 putbackO , función. 5 7 4 - 5 7 5 . (unciones
p ro p ied ad (ap u n tad o res), 596 constructores. 174
29 0 P W I), variable. 840 I)ilni|.irl;igura< ).
protected, p alab ra reservada, 29 3-29 5
337 <)btenerArea( ). 174
protected slots, sección, 9 3 2 ( )btenerSupl/q( ). 174
prototipos. 10 2 -10 4 , 624 Q recu p erar datos. 590
esc ribir. 101 en excepciones. 728 -732
nombres de parámetros. Qevent, objetos, 9 2 8 recursión
10 3 Qt kit de herram ientas de paro, condiciones. 125
parámetros. 2 7 1 - 2 7 2 clases, 9 3 2 directa. 124
tipos de valor de retomo. mecanismo de señales y l'ibonacci. serie de.
102. 105 ranuras. 9 3 | 1 2 6 -12 7 . 135
proyectos quitar m arcas de com entario Iunciones. 12 4 -12 8
artefactos, 640 de las instrucciones cout, indirecta. 124
crear archivos, 13 4 508 iteración, comparación. 2 12
documentos de planeación. recursos, 786
6 39 redéfinir
P S 1 , v a ria b le de entorno, const. palabra reservada,
840 R 603
pth read s, co m p ilar con, 861 métodos, 346 -348
p tr a c e í), llam ada de sistema, R A M (m em oria de acceso métodos de una clase base
858 aleatorio), 44 ( listado). 34 7-34 8
p u blic, p alab ra reservada, particionar. 1 2 9 - 1 3 1 métodos sobrecargados. 350
15 3 , 158 variables, 44 ocultar, comparación, 350
p u blic slots, sección, 9 3 2 ram ificación sobrecargar, comparación.
p u erto de en trada estándar, con base en los operadores 34 8
837 relaciónales (listado). redirección
p u erto de e rro r estándar, 8 3 7 8 2 -8 3 de entrada, comando (<).
pu erto de salida estándar, 8 3 7 funciones, 105 56 2
punto flotante, especificación programas, 1 3 2 - 1 3 3 de entrada, símbolo (<). 561
de tipo p a ra (listado), 7 3 ran u ras, 9 3 1 de E/S. 8 38 -8 39
punto flotante, variables de, ran u ras virtuales, 9 3 2 de salida (>). carácter, 838
47 rastrear cam bios en archivos, de salida, comando (>), 562
punto m uerto, 8 70 828 de salida, operador ( « ) , 31
punto m uerto m utuo, 8 70 Rational Rose, 6 30 de salida, símbolo (>), 561
punto m uerto recursivo, 870 Rational So ftw are, Inc, 6 2 3 redircccionam iento, operado­
pu n to y com a (,*), 68 R C S (Sistem a de control de res ( « ) , 2 1 , 3 1 , 56 1
Pu n to, clase, 1 7 4 revisiones), 8 2 7 -8 2 9 red irigir
punto, o p e rad o r de (.), 1 5 5 , archivos make, 8 28 dispositivos. 561
241 comandos. 8 2 7 flujos, 5 6 1 -5 6 2
pu n tos (o p e ra d o r de punto), palabras reservada, 828 tuberías, 839
155, 241 reasignar re d irig ir salida a, símbolo (I),
p untos de in terru p ción , apuntadores, 2 3 9 562
7 4 1-7 4 2 referencias, 26 2 reentran cia, 862
pu n tos de o bservació n , rect.cpp (código fuente), 1 7 3 referencia, operador de (&),
7 4 1-7 4 2 rect.cxx (código fuente), 1 7 4 260, 2 8 4 -2 8 5
In d ic e 1103

referencias, 259-261, 265 \ alores múltiples r e tu m , in s tru c c io n e s , 37.


apuntadores apun tadores. 2 7 2 - 2 7 4 114-116
combinar. 2X4 rctcrcncias. 2 7 4 - 2 7 5 re u tiliz u c ió n . 12
comparación. 283-2X4. relaciónales, o p e ra d o re s, de c in iig o . 14 . 1 3 3 . I ' ” .
291 80-81 14 0 -14 1

asignar a (listado). 263 precedencia. 92-93 rh s . p a r á m e tr o , 305


como alternativas para los ramificación. X2-X3 R R (p o r p e tic ió n ), d i r e c t o u
apuntadores. 2X1 relaciones d e p ro c eso s. 860
crear. 260-261, 265 c l a s e s (diseño, program a­ R T T I (Id e n tific a c ió n d e T ip o
declarar. 260. 285 ción orientada a objetos). en T ie m p o d e E je c u c ió n ).
destino, direcciones 648 416
asignar. 262-264 es un. 334. 352 im ita r. 4 16
relaciones es un, 334, 352 p re c a u c io n e s . 4 1 7
listado. 261
derivación, 334 soporte d el c o m p ila d o r . 4 1 9
regresar. 261 -262
errores herencia pública. 516 R u m b a u g h , J a m e s , 623
objetos no existentes. relaciones tiene un. Vea
286-287 contención
rcferenciar objetos en el re lle n a r funciones, 335
heap. 287-289 repasos S
iniciali/ar. 261 sem ana 1. 215-219
intercam biad). función, sem ana 2. 487-497 sa lid a . 575
sem ana 3. 791 -803 ancho, ajustar. 778
269-271
re p o rte s (sistem a), 644 archivos. 585-589
nombrar, 260
re p resen ta cio n es (o b je to s ), dar form ato. 583-584
nulas. 266
ancho. 577-578
objetos. 264 146
caracteres de llenado.
en el heap. 287 re q u erim ien to s
578-579
listado, 264-265 análisis. 626
indicadores. 580-581
no existentes, docum entos, 639
dispositivos de salida.
286-287 re serv ad a s, p a la b ra s (lista
escribir a, 575-576
nulos, 289 de), 945-946
resetiosflags, m an ip u la d o re s, lim piar o vaciar. 577
pasar
a objetos (listado), 584 s a lir de ciclos
break. instrucción. 186
281-283 residuo, o p e ra d o r de (% ),
ciclos eternos. 209
argumentos de funcio­ 72-73
resolver a m b ig ü ed a d (clases salto s, 182
nes, 266-267
base m últiples), 426 s a n g ría en el có d ig o , 78 1 -782
por referencia, 268,
resolver el enésim o n ú m ero s a n g ría , estilos, 84-85
275-278
de F ibonacci (listado), sec u en cias d e c o m a n d o s
reasignar, 262
instrucciones de control.
regresar valores múltiples. 203-204
resta, o p e ra d o r (-), 71-72 846-848
274-275
variables locales, 287 resta y d esb o rd am ie n to de shell. 846
enteros (listado), 72 sec u en cias m u ta n te s , a lg o r it­
regresar
objetos temporales (listado), re s ta u ra r m em o ria en el m os, 708-709
heap, 236-237 s e g u rid a d (clases), 155
310-311
valores, 114 resultados, 628 sem áfo ro s, 867, 886
con apuntadores (lista­ RET, com ando, 813 crear, 887
do), 272-273 reto rn o , valores de definir clase, 889
con referencias (listado), funciones, 100, 114-116 S e m a p h o re , c lase , 889
274-275 múltiples, 114-116 sem b u f, e s tr u c tu r a , 888
1104 ín d ice

se m ¡d _d s, estru ctu ra , 8S7 shmicl ds. estructura (listado). S iste m a de ( ‘ontrol de


sem op, llam ad a de sistema 890 R evisiones (RCS). Vea RCS
(listado), 888 short, enteros. 45 sistem as
señales, 8 5 8 . 9 1 8 short, tipo de variable, actores. 6 26
sesiones C R C , 6 4 5 54 . 63 a n á lis is . 638
s e tf(». m étodo, 5 7 9 -5 8 1 s ig n o d is p o s it iv os. 6 4 4
S e t i os fl a g s , m anipula do res, de adm iración <' i. 92 h e * ie d a d o s . 6 4 3
584 de intercalación (' i. opera \ is la s . 044
setw, m anipulador, 580 dor ( )R exclusivo. 77o sistem as operativos
sh, 8 3 6 más (+ i. carácter diseño \ análisis de progra­
S h a re d M e m o ry , clase, 891 operador de suma. mas. 638
S H E L L , varaible, 840 317-320 función de redirección. 562
shells, 8 36 prefijo, operador (+ + 1. sitio de TrollTecli, 923
alias de com andos, 845-846 308-310 sitios
archivos de inicio. 849 signos de interrogación ( gnus. 9
ha sh (caracteres de escape), 58 ( ; N ( >m e . 9 00
coinpletación de com an­ símbolo ¿v ONU. 9
dos. 842 operador dirección de. G I K + + . 901
editar com andos. 845 226-227. 775 KDH. 903
instrucciones de control. referencias. 261 -262 KDevelop. 903
846. 848 operador lógico AND (& & ). WxWindow s, kit de herra­
lista del historial de 91 mientas. 923
com andos. 844 referencia, operador. 260. sizeof'(), fu nción, 46
sustitución de cadenas. 284-285 s o b re c a rg a r
843 símbolos ( 'ailena. clase, 395
sustitución de salida de &. 841 constructores, 299-300
com andos, 844 com odines, 838. 843 clases derivadas.
sustitución de variables. compilar. 739 342-345
844 » (operador de extracción). listado. 299-300
sustitución mediante 562 extracción, operadores de.
com odines. 843 < (redirigir entrada). 561 562
com andos, 836 > (redirigir salida), 561 funciones, 119-120
control de procesos, 842 I (redirigir salida a entrada). miembro. 294-295
disponibles en Linux. 836 562 m étodos. 293-295
establecer variables, 840 I (tubería), 839 operador de inserción ( « ) ,
función. 836 sincronizar procesos, 8 86 -88 9 549. 553
interactivos. 849 síncronos, bloqueos (listado), operadores, 306-307
páginas del m anual. 837 865 binarios, 320
program ar. 835-836 sintácticos, errores, 7 1 4 cuestiones. 320
redirección de E/S, 837-839 sintaxis limitaciones, 32 1
secuencias de com andos, 846 catch, bloques, 721 objetivos. 321
variables. 846 definiciones, 169 objetos temporales,
de entorno. 839-840 errores, 7 14 310-313
locales. 839 for. instrucciones, 195 posfijo, 3 14
sh m g etí), llam ad a de sistema, switch, instrucciones, 205 prefijo, operadores,
890 try. bloques. 72 1 308-310

A
In d ic e 1 10 5

suma (+>. 3 1 7 - 3 2 0 p u n to s de in t e r r u p c ió n . static. p a la b r a reservad a. 604.


tipos de valor de retorno. 742 614
310-313 pu ntos de o b s e r v a c ió n . static casi, op erado r. 75
unarios. 3 1 7 742 std, e spacio de nom b re s.
redefinir. comparación. s e s ió n ile c | e m p l o i o n 614-615
348 gdb. 8 2 0 -8 2 7 stdio.h. a rc h iv o de e n ca b e ­
sobregirar d e s c o m p o s ic ió n d el c o d ic o . zado. 5 X 1
el valor de un entero con 715-716 S i l. i B ib lio te c a K s ta n d a r
signo. 55 errores de IM u n tillasi. 691-694.
el valor de un e n t e r o s i n en t ie m p o de c o m p i l a ­ 698-701. 705
signo, 54 c ió n . 16 7 J.1US de .dgoriimoN. *»
software ló g ic o s . 1 6 4 - 1 6 7 co n te n e d o r c la s e s . o o 2
casos de uso, 626 e x ce p cio n e s. 7 1 5 - 7 1 6 . m a p . co n te n e d .» re s. n|

código fuente abierto. 808 7 2 0 -7 2 1 streatt ). función. 388


comercial, 15 atrapar. 7 2 2 s tr c p v t). función. 387-388. 391
CO R BA. 900 catch. bloques. 7 2 1 - 7 2 5 streanibuf. clase. ^60
diseño, 6 2 2 -6 2 3 desventajas. 7 4 4 strlcni l. función. 388
GNU, 7 funciones virtuales. 7 3 4 stm cp v t >, función. 388
Rational Rose. 630 jerarquías. 7 2 5 - 7 2 8 Stro u stru p . líja m e . 14. 176
solución de problemas, 16, opciones. 7 1 5 Stru ct, p a la b r a reservada.
714 . Vea también depuración ordenar manejadores. 175-176
ambigüedad en propiedad 728 su, prefijo, 147
de apuntador. 290 plantillas. 7 3 5 - 7 3 8 subclases, 171-17:»
apuntadores descontrolados. producir. 7 1 7 - 7 2 0 sub ín dice, 367, 948
247, 249 sin errores, 7 3 8 - 7 3 9 o perado r. 698

A S S E R T 0 , macro, 761 soporte del compilador. su b pro ce san iie n to


bugs o errores 7 17 m ú ltip le . 8 6 2

arreglos, 369 try. bloques, 7 1 6 , 7 2 1 s im p le . 8 6 2

depuradores simbólicos, ventajas, 7 4 3 s u b p ro c e so s , 8 6 0


guardias de inclusión, 7 5 1 crear. 8 62-864
739
imprimir valores interinos. d i r e c t i v a s d e s . n eró n ./ació n .
distinguir entre, 7 1 4
767 8 6 4 -8 7 0
lógicos, 7 1 5
macros, 760 implementar. 8 6 1
comentarios en el código,
predefinidas, macros, 7 5 8 listado. 8 6 2 - 8 6 3
7 16
recursos para el progra­ P O S IX . biblioteca de. 8 6 1
costo, 7 1 4
mador, 7 8 6 program ar. 8 6 4
depurar, 739 , 8 2 2 -8 2 3
referencias a objetos nulos, punto muerto. 8 7 0
archivos core, 74 1
289 sim ples, 8 6 2
ayuda, 8 23-824
S O s, 8 36 m últiples. 8 6 2
cerrar, 824
comandos, 824 C P U , tiempo, 8 59 su m a
ensambladores, 7 4 2 G U Is, 896 operador (+ ). 3 1 7 - 3 2 0
subprocesos, 861 operador au toasign ad o ( + —).
examinar memoria,
742 widgets, 9 0 3-9 0 4 74
GNU, depurador, S P C , com ando, 8 1 3 S u m a ( ), fu n c ió n , 3 8
740-741 sq rt(), función, 2 7 2 declarar. 3 1 7 - 3 1 8

niveles, 774 Stallm an, R ich a rd M ., 7 listado. 3 1 7 - 3 1 8


| 1106 índice

su p e rcla se s, 6 4 6 tam años de poder. 6 5 2 -6 56


su p e rco n ju n to s, 3 3 4 arreglos. 3 7 3 - 3 7 5 ile v a l o r de retomo. 102.
su p e re stad o s, 6 5 8 enteros. 4 5 105
su sce p tib ilid ad al uso de m a­ variables. 44. 4 7 -4 8 declarar. 15 5
y ú sc u la s y m inúsculas, 14 8 cliar. 56 definición de. 53
nombres de variables. determinar. 4 5 -4 0 valores. 101
4 9 -5 0 uso de memoria. 54 variables. 14 5
notación húngara. 50 T A S K I N T E R R l 'P T I B E E , tipos de datos
su sp e n d e r tra b a jo s, 8 4 1 estado del proceso, 8 5 9 abstractos. 4 36 -4 4 0
su stitu ció n T A S K R U N N I N íi, estado del declarar. 441
cadenas. 8 4 3 proceso, 8 5 9 derivar de otros ADTs,
com odines, 8 4 3 T A S K S T O P P E I ) , estado del 4 4 5 -4 4 8
salida de comandos, 844 proceso, 8 5 9 funciones virtuales
variables. 8 4 4 puras. 44 0
T A S K S W A P P I N t ;, estado
sustitu ción de salid a de del proceso, 8 5 9 Java. 4 5 0
co m an d o s, 8 4 4 listado. 4 4 0 -4 4 1
T A SK U N IN T E R R U P T IB E E ,
sustitu tos, 6 4 3 ventajas. 4 5 2
estado del proceso, 8 5 9
sw itch, instrucciones, 2 0 5 -2 0 7 , T A S K Z O M B I E , estado del cin. 5 6 3
451 espacios de nombres, 605
proceso, 8 5 9
break, instrucciones, 20 7 tesh, 8 3 6 macros. 7 5 5
ciclos eternos, 2 0 8 -2 11 teclas o claves tokens, 7 4 8
listado. 2 0 6 -2 0 7 to m a r la dirección de una
Meta, 8 I 2
ram ificar mediante valores referen cia (listado), 262
System V IPC. 8 7 9 -8 8 0
múltiples de una expre­ tra b a jo s, estado, 8 4 1
tém plate, p a lab ra re se rv a d a ,
sión, 2 0 7 tran sfo rm acio n es (diseño),
663
sangría, 7 8 1 - 7 8 2
T E R M , variab le de am bien te, 6 4 3 -6 4 4
sintaxis, 2 0 5 -2 0 8 840 T re s A m ig o s, 6 2 3
sugerencias de uso, 2 1 1 T ro lIT ech , biblioteca de gráfi­
term in ar
valores de case, 20 6
línea, 3 3 cos de Q t, 899
SyncLock, clase base virtual, procesos, 8 56 T ro lIT ech , sitio W eb, 9 2 3
865
ternario, o p e rad o r (> :), 9 4 -9 5 try, bloques, 7 1 6 , 7 2 1
S y s te m V I P C , 8 7 9 -8 8 0
this, ap u n tad ores, 2 4 6 excepciones
const this, 2 5 3 atrapar, 7 2 2
funciones sin errores, 7 3 9

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

UML (Lenguaje de Modelado \ alo re s. 43


Unificado), 622 .tsie n .i! 3 v a n u b l e s . 43
clases. 622 .i v a i i . t N e s . "'I sj
clases derivadas. 334 I 40 1M)
clases especializadas. (d | 6o» >le.m< >s. . o
CRC. tárjelas. 648 «. ar.teleles. V >
diagrama de interacción. , entínela. ' ’7
637
ú 'iis i.m ic s c n u m c i.tila s M
estereotipo discriminado!. dec i c i n c n l . t i . S
653 c x p ic s io n e s . oo
herencia. 622 in c r e i ii c n t . ii . 7 5 de. i.tr.it. 7 7 s
programación orientada a in te rc a m b ia r. 27() des, .>ni:- >i.i,*>’s
objetos, diseño. 622 m í e n n o s , i m p r i m i r . 7(»7
discriminadores. 653 l i s t a s t ic p a r a m e t r o s . l o o d e s i e l e r e n , t.u - - J -
relaciones de clases. in a m i i.(u n c ió n . ó)
648 m ú ltip le s d ii i \ , i> m e s d e m e r n - .
transformar tarjetas C R C rem esar con apuntadores.
en, 648 272-274 I uik li'D C'. Ui ' *
unidades regresar con referencias. in u i.tl i z a r . 3 7 -V - ' l
autocontcnidas, 13 274-275 m a n ip u la n io n de d a lo s

de traducción. 6 14 parám etro (p re d e te rm in a d o ). 7 ' I 7 '7


unir, 870 I 17-1 IX m étodos. 4 4 n _
UNIX pasar. 113 n o in b i.tr. 2 3 o

AT&T UNIX System V. eficiencia. 275 n u lo s . 77N


879 listarlo. 5 12-5 14 p e í d u l i >s. 7 2 6

C, compiladores, 816 por referencia, 266-268. p iso te a r. 2 4 0

ELF, 818 276-278 p r o p ie d a d . 2 9 0

KDE, 901 por valor. 267-268 re a s ig n a r. 2 '9

shells, 836 predeterm i nados (1is ta d o ). R I T ! . 4 16


lilis. 2 4 6 -2 4 7 . 31 3 0 4
vim, editor, 809 296-297
XINU, 8 listado. 296-297 v e n t a ja s . 2 '4

usar m étodos. 296-298. 329 a sig n a r, c la s e s d e l n u d a s p o i

uso de funciones so b re­ e l u s u a r i o . 3 2 4 - ’•25


directiva, 6 0 9 -6 11,6 14
palabra reservada, 609 cargadas. 298 b lo q u e s . 110-111

UsarValActual, parámetro, probar (Linux). 3 I char. 4 5 . 5 6


298 regresar, 114 caracteres de e scap e .

Usenet, grupos de noticias, con apuntadores (listado). 58


786 272-273 c o d ific a c ió n d e c a r a c ­
uso apropiado de llaves con con referencias (listado). teres. 57
instrucciones ¡f (listado), 274-275 ta m a ñ o s. 56
90 mediante operador de ciclo s lor. a lc a n c e . 2 0 -
uso de mayúsculas (estilos posfijo. 3 15 cnt. 608
de código), 783-784 regresos fraccionarios, 73 c o n d ic ió n ( d ir e c t iv a s de

sí/no (1/0). 95 I s i n e r o n i / a c i o n >. « S 6 S - S 6 9


usuarios, 11
1108 ín d ice

crear. 60 shells. 846 \oid m ainO , función, 1 16


d atos m iem bro. I47 sustitución. 844 \o id . \alo re s (funciones), 37,
declarar. 285 tam años. 44-48 1 14
definir. 44. 48-49 tipos V o lu m en í 'u b o (), función, 118
de tipo C O L O R . 61 crear. 145-146
m últiples. 5 1 definición. 53
d e m o stra r el uso de. 52 lista de. 47
en tero s. 45 valores. 48. 51-52. 1 4 9 -150. w
long. 54 159
short. 54 u a i t l ), llam ada al sistema, 856
actuales, im prim ir.
signcd, 46. 55-56 w atcli, instrucciones, 788
767-768
unsigncd. 46. 54-55 \\ hile, ciclos, 1 8 3 - 1 8 6
variables de conleo. 201
entorno. 839-840 break. instrucción. 186-189
vectores, crear, 6 9 4 -6 9 7
estab le cer por m edio del ciclos eternos, 189-190
vectoriales, contenedores,
shell. 840 ciclos for, com paración, 212
6 9 2 -6 9 4 , 6 9 8 -6 9 9
ex tern as, lim itar alcance. condiciones de inicio, 194
Ventana, espacio de nom bres,
604 continué, instrucción.
6 0 7-60 8
g lobales. 108-109. 130 I 8 6 - 189
ventanas, 8 9 6 -8 9 8
lim itaciones. 109-110 ver
ilo ...u hile. 192-193
ocultar. 108 com paración. 2 I 2
inform ación gráfica. 898
inicial izar, 5 1. 109 sintaxis. 193
IRC, estado de objetos. 88 1
línea de com andos. 839 ejecutar, 191-192
trabajos actualm ente en e je ­
locales. 106-108, 839 ex p resio n es com plejas,
cución. 841
alcance. 110-111 185-186
verd ad , 79 , 9 3 , 9 5 1
espacios de nom bres. lim itaciones, 191
verdadero/falso, o p eracio n es,
610 listados, 184-186. 189-190
9 3 -9 4
inicializar, 1 1 1 reg resar al inicio de. 186
vi, editor, 809
p arám etros. 106-107 salir, 186
ayuda en línea. 811-812
persistencia, 235 sintaxis, 184-185
etiquetas Oags). 815
m iem bro vvidgets, 9 0 3-9 0 4
iniciar, 809-810
declarar, 154 crear (G N O M E ). 907
m odo de com andos, 8 10
inicializar, 301 cuadro de lista, 931
modo de inserción, 810
uso de m em oria, 54 \vidth(), m étodo, 5 7 7 -5 7 8
m odo ex, 8 1 1
nom bres, 44. 48 YVindow class (kit de herra­
navegar, 8 1 I
claridad, 49 m ientas de xw W indow s)
vim , editor, 809
notación de cam ello, 50 agregar m enús a, 920-921
violaciones de la in terfaz,
notación húngara, 50 KDE, 926-927
1 6 5 -1 6 6
palabras reservadas, 50 w r ite (), m étodo, 5 7 5 - 5 7 7
virtuales, con structores de
precauciones, 48 w xG TK
copia, 3 6 0 -3 6 3
susceptibilidad al uso de biblioteca, 903, 909
visibilidad, 602
m ayúsculas y m inúscu­ m anejo de eventos, 918-919
visión, 6 2 4 -6 2 5
las. 4 9-50 vvxStudio, 9 2 2
vistas (diseño, p ro g ra m a ció n
o cu ltas, 246 w x W in d o w s, kit de herra­
orientada a objetos), 6 4 4
prefijos, 147 m ientas, 909
Visual B asic, 10
p u n to flotante, 47 agregar botones. 913-917
visualización, 6 3 9
crear aplicaciones, 910-912
R A M . 44 V M (m áq u in a virtu al), 10
funcionalidad. 909-910
procesamiento de c u ’iitns.
919-920
recursos en línea. 92 '
señales. 919-920
Window. clase
agregar botones a. 9 | ^
agregar menos. 920-92 1
wxStudio, 922

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 :

N o puede co p iar ni red istrib u ir este C D -R O M en su to ta lid a d , 1.a c o p ia y r e d is tr ib u c ió n


de los program as in d iv id u ales de so ftw a re q u e v ie n e n en e s te C D - R O M e s tá n r e g u l a d a s
por los térm inos estab lecid o s p o r los re sp e c tiv o s p o s e e d o re s d e l d e r e c h o d e a u to r

El instalador y el có d ig o d el(d e los) a u to r(e s) e stá n p ro te g id o s p o r el e d ito r y cirio s»


autor(es) m ediante los d erec h o s de autor. L o s p ro g ra m a s in d iv id u a le s y d e m á s e le m e n to *
que vienen en el C D -R O M son p ro p ied a d re g is tra d a d e s u s r e s p e c tiv o s a u to r e s o p o s e e ­
dores del derech o de autor. A lg u n o s de los p ro g ra m a s in c lu id o s c o n e s te p r ix lu c to p u e d e n
estar regulados por la L icen cia p ú b lica g en era l d e G N U , la c u a l p e r m ite la r e d i s t r i b u c i ó n :
para obten er m ás in fo rm ació n , lea la lic e n c ia p a ra c a d a p ro d u c to .

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.

Inserte el disco com pacto en la u n id ad de C D -R O M .

Si tiene un adm inistrador de v o lú m en es, el m o n ta je d el C D -R O M s e rá a u to m á tic o . Si n o


tiene un adm inistrador de volúm enes, p u ed e m o n tar el C D -R O M e s c rib ie n d o lo sig u ie n te :

Mount -tiso9660 /dev/cdrom /mnt/cdrom

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

Plantillas d e clase contenedora estándar


Plantilla de clase Descripción
vector Secuencia lineal, similar a un arreglo de C++.
list Lista doblemente enlazada.
deque Cola con dos extremos.
set Arreglo asociativo de claves únicas. Un tipo especial de un conjunto capaz
de guardar valores binarios, se conoce como un conjunto de bits.
continúo
continuación
P l a n t ilia d e la c l a s e Descripción

multiset Arreglo asociativo de claves posiblem ente duplicadas.


map Arreglo asociativo de claves y valores únicos.
multimap A rreglo asociativo de claves y valores posiblem ente duplicados.
stack Estructura de datos de upo LIEO (Ú ltim o en Entrar. Prim ero en Salir).
queue Estructura de datos de tipo F1FO (Prim ero en Entrar. Prim ero en Salir).
p r i o n t y _ q u e u e Cola o vector ordenado por evento crítico.

Tipos d e datos d e C + +

Tipo T a m a ñ o Ra n g o

unsigned short int 2 bytes 0 a 65.535 ----------


short int 2 bytes -32.768 a 32,767
unsigned long int 4 bytes 0 a 4.294.967.295
long int 4 bytes -2.147,483.648 a 2,147.483.647
int 4 bytes -2.147.483.648 a 2.147.483.647
unsigned int 4 bytes 0 a 4.294.967.295
char 1 byte
256 valores de tipo carácter, con signo de form a predeterminada, -128 a 1 2 7
wchar_t 4 bytes 4.294,967,296 valores tipo de carácter
bool 1 byte Verdadero (truc) o falso (false)
float 4 bytes 1.2e-37 a 3.4c38
double 8 bytes 2.2e-307 a 1.8e308
long double 10 bytes 3.4c-4931 a l.lc + 4 9 3 2
Tenga en cuenta que estos tam años son típicos y pueden variar según la implementación.

Las c o n s t a n t e s o p e n m o d e d e la clase ios


Constante Estándar Efecto

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 ©

Un 21 días tendrá los conocimientos necesarios


Linux
rahajar en forma eficiente con C++ para Linux. Con
ida de este tutorial dominará los aspectos básicos para
es adentrarse en características y conceptos más avanzados.
mprenda los fundamentos de la programación en C++ para Linux
mine todas las características nuevas y avanzadas que ofrece C++ para • Programe específicamente para
Linux con GNU C++
enda a utilizaren forma efectiva las herramientas y características más • Descubra cómo escribir, compilar y
entes de C++ para Linux, mediante ejemplos prácticos y reales enlazar su primer programa com­
neche los consejos de una de las principales autoridades de la progra- pletam ente funcional en C++
ión en C++ para Linux en el ambiente empresarial
• Aprenda la program ación por pro­
ro está diseñado para adaptarse a su propio plan de aprendizaje. Puede
cedimientos y orientada a objetos
as lecciones paso a paso, capítulo por capítulo, o elegir sólo las leccio-
le interesen. desde sus bases
• Conozca el C++ estándar de
D -R O M in c lu y e : ANSI/ISO y la Biblioteca Estándar
de Plantillas
odo el código fuente que se usa en el libro
• Vaya más allá de los fundam entos
'tilerías de wxW indows creadas por otros fabricantes
de la program ación en C++ para
landrake 7.0 para Linux, que incluye un entorno com pleto
crear aplicaciones robustas y com­
e desarrollo con el compilador C/C++ de GNU, el depurador
pletas
GDB de GNU, la utilería make de GNU, GTK+, KDE, GNO M E
mucho más • Incluye una semana adicional con
temas como
•erty es autor de varios libros sobre C++, entre ellos C+ + pura Prin- • El entorno de program ación
Es presidente de Liberty Associates, Inc.. empresa que proporciona
de Linux
ion a domicilio sobre desarrollo de software orientado a objetos, con-
programación por contrato. • Programación de shells

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:

Principiante Intermedio Experto

Visítenos en:
ww w.pearsonedlatino.com .m x

También podría gustarte