SlideShare una empresa de Scribd logo
95
Lo más leído
307
Lo más leído
312
Lo más leído
Visítenos en:
www.pearsonenespañol.com
ISBN 978-607-32-2739-1
Una introducción completa y autorizada del código activo DEITEL®
a C++,
la programación orientada a objetos (POO) y el diseño
orientado a objetos (DOO) con UML™
Bienvenido a la programación con C++, uno de los lenguajes de programación orientada a
objetos más populares.
Este libro utiliza las tecnologías de computación de vanguardia, y su base es el reconocido
método de código activo de Deitel, donde los conceptos se presentan en el contexto de progra-
mas funcionales completos, seguidos de ejecuciones de ejemplo, en lugar de hacerlo a través
de fragmentos separados de código.
A lo largo del texto encontrará recuadros con tips de programación, para prevenir errores, y de
rendimiento, así como buenas prácticas de programación, que resultan de gran utilidad.
Estándar C++11
Entre las características clave del nuevo estándar C++11 que se presenta en esta nueva
edición destacan las siguientes:
• Cumple con el nuevo estándar C++11. Con una extensa cobertura de las nue-
vas características de C++11.
• Apuntadores inteligentes. Los apuntadores inteligentes ayudan a evitar los
errores de administración de memoria dinámica al proveer una funcionalidad
adicional a la de los apuntadores integrados.
• Cobertura anticipada de los contenedores, iteradores y algoritmos de la
Biblioteca Estándar, mejorada con capacidades de C++11. La gran mayo-
ría de las necesidades de los programadores en cuanto a estructuras de datos
pueden satisfacerse mediante la reutilización de las capacidades de la Biblioteca
Estándar.
Este libro se adhiere al estándar de codificación segura en C++ de CERT.
Para mayor información visite:
www.pearsonenespañol.com/deitel
NOVENA EDICIÓN
NOVENA
EDICIÓN
ACCESO A LOS CAPÍTULOS ADICIONALES DEL LIBRO
Para acceder a los capítulos 14 a 17 (en español) y 18 a 23 (en inglés)
mencionados en el texto, visite el sitio web del libro:
www.pearsonenespañol.com/deitel
Utilice una moneda para descubrir el código de acceso.
(No use objetos filosos porque podría dañarlo).
Deitel / Cómo programar en C++
IMPORTANTE:
¡Este código de acceso tiene vigencia de 2 días!
Asegúrese que el código no aparezca dañado ya que sólo puede usarse
una vez y no será reemplazado en ningún caso.
novena
edición
CÓMO PROGRAMAR
Paul Deitel
Deitel & Associates, Inc.
Harvey Deitel
Deitel & Associates, Inc.
Traducción
Alfonso Vidal Romero Elizondo
Ingeniero en Sistemas Electrónicos
Tecnológico de Monterrey, Campus Monterrey
Revisión técnica
Sergio Fuenlabrada Velázquez
Edna Martha Miranda Chávez
José Luis López Goytia
Judith Sonck Ledezma
Mario Alberto Sesma Martínez
Mario Oviedo Galdeano
Oskar Armando Gómez Coronel
Academia de Computación
Unidad Profesional Interdisciplinaria de Ingeniería
y Ciencias Sociales y Administrativas (UPIICSA)
Instituto Politécnico Nacional, México
Sandra Luz Gómez Coronel
Academia de Electrónica
Unidad Profesional Interdisciplinaria de Ingeniería
y Tecnologías Avanzadas (UPIITA)
Instituto Politécnico Nacional, México
Authorized translation from the English language edition entitled C++ HOW TO PROGRAM, 9th
edition, by (HARVEY
& PAUL) DEITEL & ASSOCIATES; HARVEY DEITEL, published by Pearson Education, Inc., publishing as Pearson,
Copyright © 2014. All rights reserved.
ISBN 9780133378719
Traducción autorizada de la edición en idioma inglés titulada C++ HOW TO PROGRAM, 9ª edición, por (HARVEY &
PAUL) DEITEL & ASSOCIATES; HARVEY DEITEL, publicada por Pearson Education, Inc., publicada como Pearson,
Copyright © 2014. Todos los derechos reservados.
Esta edición en español es la única autorizada.
Edición en español
Dirección General: Philip de la Vega
Dirección Educación Superior: Santiago Gutiérrez
Editor Sponsor: Luis M. Cruz Castillo
luis.cruz@pearson.com
Editor de Desarrollo: Bernardino Gutiérrez Hernández
Supervisor de Producción: Enrique Trejo Hernández
Gerencia Editorial
Educación Superior: Marisa de Anta
Novena edición, 2014
D.R. © 2014 por Pearson Educación de México, S.A. de C.V.
Atlacomulco 500-5o. piso
Col. Industrial Atoto
53519, Naucalpan de Juárez, Estado de México
Cámara Nacional de la Industria Editorial Mexicana. Reg. núm. 1031.
Reservados todos los derechos. Ni la totalidad ni parte de esta publicación pueden reproducirse, registrarse o transmitirse,
por un sistema de recuperación de información, en ninguna forma ni por ningún medio, sea electrónico, mecánico, foto-
químico, magnético o electroóptico, por fotocopia, grabación o cualquier otro, sin permiso previo por escrito del editor.
El préstamo, alquiler o cualquier otra forma de cesión de uso de este ejemplar requerirá también la autorización del editor
o de sus representantes.
ISBN 978-607-32-2739-1
ISBN e-book 978-607-32-2740-7
ISBN e-chapter 978-607-32-2741-4
Impreso en México. Printed in Mexico.
1 2 3 4 5 6 7 8 9 0 - 17 16 15 14
'DWRVGHFDWDORJDFLyQELEOLRJUiILFD
3iJLQDV
3($5621('8$,Ð10p[LFR
,6%1
)RUPDWR×FP
yPRSURJUDPDUHQ
1RYHQDHGLFLyQ
DEITEL, PAUL y HARVEY DEITEL
www.pearsonenespañol.com ISBN 978-607-32-2739-1
En memoria de Dennis Ritchie,
creador del lenguaje de programación C,
uno de los lenguajes clave que inspiraron a C++.
Paul y Harvey Deitel
Marcas registradas
Deitel, el insecto con los dos pulgares hacia arriba y Dive Into son marcas registradas de Deitel  Associates, Inc.
Carnegie Mellon Software Engineering InstituteTM
es marca registrada de Carnegie Mellon University.
CERT®
está registado en la U.S. Patent and Trademark Office por Carnegie Mellon University.
Microsoft®
y Windows®
son marcas registradas de Microsoft Corporation en E.U.A. y otros países. Las capturas
de pantalla y los iconos son reproducidos con permiso de Microsoft Corporation. Este libro no es patrocinado ni
respaldado ni está afiliado a Microsoft Corporation.
UNIX es una marca registrada de The Open Group.
A lo largo de este libro se utilizan marcas. En lugar de poner un símbolo de marca registrada en cada ocurrencia de un
nombre de esa marca registrada, afirmamos que estamos usando los nombres únicamente de forma editorial y para
beneficio del propietario de la marca comercial, sin ninguna intención de infracción de derechos de la marca registrada.
Prefacio xvii
1 Introducción a las computadoras y a C++ 1
1.1 Introducción 2
1.2 Las computadoras e Internet en la industria y la investigación 3
1.3 Hardware y software 5
1.3.1 La Ley de Moore 6
1.3.2 Organización de la computadora 6
1.4 Jerarquía de datos 7
1.5 Lenguajes máquina, lenguajes ensambladores y lenguajes de alto nivel 9
1.6 C++ 10
1.7 Lenguajes de programación 11
1.8 Introducción a la tecnología de objetos 14
1.9 Entorno de desarrollo común en C++ 17
1.10 Prueba de una aplicación de C++ 19
1.11 Sistemas operativos 25
1.11.1 Windows: un sistema operativo propietario 25
1.11.2 Linux: un sistema operativo de código fuente abierto 26
1.11.3 OS X de Apple: iOS de Apple para dispositivos
iPhone®
, iPad®
y iPod Touch®
26
1.11.4 Android de Google 27
1.12 Internet y World Wide Web 27
1.13 Cierta terminología clave de desarrollo de software 29
1.14 C++11 y las bibliotecas Boost de código fuente abierto 31
1.15 Mantenerse actualizado con las tecnologías de la información 32
1.16 Recursos Web 33
2 Introducción a la programación
en C++, entrada/salida y operadores 38
2.1 Introducción 39
2.2 Su primer programa en C++: imprimir una línea de texto 39
2.3 Modificación de nuestro primer programa en C++ 43
2.4 Otro programa en C++: suma de enteros 44
2.5 Conceptos acerca de la memoria 48
Contenido
vi Contenido
2.6 Aritmética 49
2.7 Toma de decisiones: operadores de igualdad y relacionales 53
2.8 Conclusión 57
3 Introducción a las clases, objetos y cadenas 66
3.1 Introducción 67
3.2 Definición de una clase con una función miembro 67
3.3 Definición de una función miembro con un parámetro 70
3.4 Datos miembros, funciones establecer y obtener 74
3.5 Inicialización de objetos mediante constructores 79
3.6 Colocar una clase en un archivo separado para fines de reutilización 83
3.7 Separar la interfaz de la implementación 87
3.8 Validación de datos mediante funciones establecer 92
3.9 Conclusión 97
4 Instrucciones de control, parte I:
operadores de asignación, ++ y -- 104
4.1 Introducción 105
4.2 Algoritmos 105
4.3 Seudocódigo 106
4.4 Estructuras de control 107
4.5 Instrucción de selección if 110
4.6 Instrucción de selección doble if...else 112
4.7 Instrucción de repetición while 116
4.8 Cómo formular algoritmos: repetición controlada por un contador 118
4.9 Cómo formular algoritmos: repetición controlada por un centinela 124
4.10 Cómo formular algoritmos: instrucciones de control anidadas 134
4.11 Operadores de asignación 139
4.12 Operadores de incremento y decremento 140
4.13 Conclusión 143
5 Instrucciones de control, parte 2: operadores lógicos 157
5.1 Introducción 158
5.2 Fundamentos de la repetición controlada por un contador 158
5.3 Instrucción de repetición for 159
5.4 Ejemplos acerca del uso de la instrucción for 163
5.5 Instrucción de repetición do...while 168
5.6 Instrucción de selección múltiple switch 169
5.7 Instrucciones break y continue 178
5.8 Operadores lógicos 180
5.9 Confusión entre los operadores de igualdad (==) y de asignación (=) 185
5.10 Resumen de programación estructurada 186
5.11 Conclusión 191
Contenido vii
6 Funciones y una introducción a la recursividad 201
6.1 Introducción 202
6.2 Componentes de los programas en C++ 203
6.3 Funciones matemáticas de la biblioteca 204
6.4 Definiciones de funciones con varios parámetros 205
6.5 Prototipos de funciones y coerción de argumentos 210
6.6 Encabezados de la Biblioteca estándar de C++ 212
6.7 Caso de estudio: generación de números aleatorios 214
6.8 Caso de estudio: juego de probabilidad; introducción a enum 219
6.9 Números aleatorios de C++11 224
6.10 Clases y duración de almacenamiento 225
6.11 Reglas de alcance 228
6.12 La pila de llamadas a funciones y los registros de activación 231
6.13 Funciones con listas de parámetros vacías 235
6.14 Funciones en línea 236
6.15 Referencias y parámetros de referencias 237
6.16 Argumentos predeterminados 240
6.17 Operador de resolución de ámbito unario 242
6.18 Sobrecarga de funciones 243
6.19 Plantillas de funciones 246
6.20 Recursividad 248
6.21 Ejemplo sobre el uso de la recursividad: serie de Fibonacci 252
6.22 Comparación entre recursividad e iteración 255
6.23 Conclusión 258
7 Plantillas de clase array y vector;
cómo atrapar excepciones 278
7.1 Introducción 279
7.2 Arreglos 279
7.3 Declaración de arreglos 281
7.4 Ejemplos acerca del uso de los arreglos 281
7.4.1 Declaración de un arreglo y uso de un ciclo para inicializar
los elementos del arreglo 281
7.4.2 Inicialización de un arreglo en una declaración mediante
una lista inicializadora 282
7.4.3 Especificación del tamaño de un arreglo con una variable constante
y establecimiento de los elementos de un arreglo con cálculos 283
7.4.4 Suma de los elementos de un arreglo 286
7.4.5 Uso de gráficos de barra para mostrar los datos
de un arreglo en forma gráfica 286
7.4.6 Uso de los elementos de un arreglo como contadores 288
7.4.7 Uso de arreglos para sintetizar los resultados de una encuesta 289
7.4.8 Arreglos locales estáticos y arreglos locales automáticos 291
viii Contenido
7.5 Instrucción for basada en rango 293
7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo
para almacenar las calificaciones 295
7.7 Búsqueda y ordenamiento de datos en arreglos 302
7.8 Arreglos multidimensionales 304
7.9 Caso de estudio: la clase LibroCalificaciones que usa un
arreglo bidimensional 307
7.10 Introducción a la plantilla de clase vector de la Biblioteca estándar de C++ 314
7.11 Conclusión 320
8 Apuntadores 334
8.1 Introducción 335
8.2 Declaraciones e inicialización de variables apuntadores 335
8.3 Operadores de apuntadores 337
8.4 Paso por referencia mediante apuntadores 339
8.5 Arreglos integrados 344
8.6 Uso de const con apuntadores 346
8.6.1 Apuntador no constante a datos no constantes 347
8.6.2 Apuntador no constante a datos constantes 347
8.6.3 Apuntador constante a datos no constantes 348
8.6.4 Apuntador constante a datos constantes 349
8.7 Operador sizeof 350
8.8 Expresiones y aritmética de apuntadores 353
8.9 Relación entre apuntadores y arreglos integrados 355
8.10 Cadenas basadas en apuntador 358
8.11 Conclusión 361
9 Clases, un análisis más detallado:
lanzar excepciones 377
9.1 Introducción 378
9.2 Caso de estudio con la clase Tiempo 379
9.3 Alcance de las clases y acceso a los miembros de una clase 385
9.4 Funciones de acceso y funciones utilitarias 386
9.5 Caso de estudio de la clase Tiempo: constructores con argumentos
predeterminados 387
9.6 Destructores 393
9.7 Cuándo se hacen llamadas a los constructores y destructores 393
9.8 Caso de estudio con la clase Tiempo: una trampa sutil (devolver una
referencia o un apuntador a un miembro de datos private) 397
9.9 Asignación predeterminada a nivel de miembros 400
9.10 Objetos const y funciones miembro const 402
9.11 Composición: objetos como miembros de clases 404
9.12 Funciones friend y clases friend 410
Contenido ix
9.13 Uso del apuntador this 412
9.14 Miembros de clase static 418
9.15 Conclusión 423
10 Sobrecarga de operadores: la clase string 433
10.1 Introducción 434
10.2 Uso de los operadores sobrecargados de la clase string
de la Biblioteca estándar 435
10.3 Fundamentos de la sobrecarga de operadores 438
10.4 Sobrecarga de operadores binarios 439
10.5 Sobrecarga de los operadores binarios de inserción de flujo
y extracción de flujo 440
10.6 Sobrecarga de operadores unarios 444
10.7 Sobrecarga de los operadores unarios prefijo y postfijo ++ y -- 445
10.8 Caso de estudio: una clase Fecha 446
10.9 Administración dinámica de memoria 451
10.10 Caso de estudio: la clase Array 453
10.10.1 Uso de la clase Array 454
10.10.2 Definición de la clase Array 458
10.11 Comparación entre los operadores como funciones miembro
y no miembro 466
10.12 Conversión entre tipos 466
10.13 Constructores explicit y operadores de conversión 468
10.14 Sobrecarga del operador () de llamada a función 470
10.15 Conclusión 471
11 Programación orientada a objetos: herencia 482
11.1 Introducción 483
11.2 Clases base y clases derivadas 483
11.3 Relación entre las clases base y las clases derivadas 486
11.3.1 Creación y uso de una clase EmpleadoPorComision 486
11.3.2 Creación de una clase EmpleadoBaseMasComision
sin usar la herencia 491
11.3.3 Creación de una jerarquía de herencia
EmpleadoPorComision-EmpleadoBaseMasComision 497
11.3.4 La jerarquía de herencia EmpleadoPorComision-
EmpleadoBaseMasComision mediante el uso de datos protected 501
11.3.5 La jerarquía de herencia EmpleadoPorComision-
EmpleadoBaseMasComision mediante el uso de datos private 504
11.4 Constructores y destructores en clases derivadas 509
11.5 Herencia public, protected y private 511
11.6 Ingeniería de software mediante la herencia 512
11.7 Repaso 512
x Contenido
12 Programación orientada a objetos: polimorfismo 517
12.1 Introducción 518
12.2 Introducción al polimorfismo: videojuego polimórfico 519
12.3 Relaciones entre los objetos en una jerarquía de herencia 519
12.3.1 Invocación de funciones de la clase base desde objetos
de una clase derivada 520
12.3.2 Cómo orientar los apuntadores de una clase derivada
a objetos de la clase base 523
12.3.3 Llamadas a funciones miembro de una clase derivada a través
de apuntadores de la clase base 524
12.3.4 Funciones y destructores virtuales 526
12.4 Tipos de campos e instrucciones switch 533
12.5 Clases abstractas y funciones virtual puras 533
12.6 Caso de estudio: sistema de nómina mediante el uso de polimorfismo 535
12.6.1 Creación de la clase base abstracta Empleado 536
12.6.2 Creación de la clase derivada concreta EmpleadoAsalariado 540
12.6.3 Creación de la clase derivada concreta EmpleadoPorComision 542
12.6.4 Creación de la clase derivada concreta indirecta
EmpleadoBaseMasComision 544
12.6.5 Demostración del procesamiento polimórfico 546
12.7 (Opcional) Polimorfismo, funciones virtuales y vinculación
dinámica “detrás de las cámaras” 550
12.8 Caso de estudio: sistema de nómina mediante el uso de polimorfismo
e información de tipos en tiempo de ejecución con conversión descendente,
dynamic_cast, typeid y type_info 553
12.9 Conclusión 557
13 Entrada/salida de flujos: un análisis detallado 562
13.1 Introducción 563
13.2 Flujos 564
13.2.1 Comparación entre flujos clásicos y flujos estándar 564
13.2.2 Encabezados de la biblioteca iostream 565
13.2.3 Clases y objetos de entrada/salida de flujos 565
13.3 Salida de flujos 567
13.3.1 Salida de variables char * 568
13.3.2 Salida de caracteres mediante la función miembro put 568
13.4 Entrada de flujos 569
13.4.1 Funciones miembro get y getline 569
13.4.2 Funciones miembro peek, putback e ignore de istream 572
13.4.3 E/S con seguridad de tipos 572
13.5 E/S sin formato mediante el uso de read, write y gcount 572
13.6 Introducción a los manipuladores de flujos 573
13.6.1 Base de flujos integrales: dec, oct, hex y setbase 574
Contenido xi
13.6.2 Precisión de punto flotante (precision, setprecision) 574
13.6.3 Anchura de campos (width, setw) 576
13.6.4 Manipuladores de flujos de salida definidos por el usuario 577
13.7 Estados de formato de flujos y manipuladores de flujos 578
13.7.1 Ceros a la derecha y puntos decimales (showpoint) 579
13.7.2 Justificación (left, right e internal) 580
13.7.3 Relleno de caracteres (fill, setfill) 582
13.7.4 Base de flujos integrales (dec, oct, hex, showbase) 583
13.7.5 Números de punto flotante; notación científica
y fija (scientific, fixed) 584
13.7.6 Control de mayúsculas/minúsculas (uppercase) 585
13.7.7 Especificación de formato booleano (boolalpha) 585
13.7.8 Establecer y restablecer el estado de formato mediante
la función miembro flags 586
13.8 Estados de error de los flujos 587
13.9 Enlazar un flujo de salida a un flujo de entrada 590
13.10 Conclusión 590
14 Procesamiento de archivos 599
14.1 Introducción 600
14.2 Archivos y flujos 600
14.3 Creación de un archivo secuencial 601
14.4 Cómo leer datos de un archivo secuencial 605
14.5 Actualización de archivos secuenciales 611
14.6 Archivos de acceso aleatorio 611
14.7 Creación de un archivo de acceso aleatorio 612
14.8 Cómo escribir datos al azar a un archivo de acceso aleatorio 617
14.9 Cómo leer de un archivo de acceso aleatorio en forma secuencial 619
14.10 Caso de estudio: un programa para procesar transacciones 621
14.11 Serialización de objetos 628
14.12 Conclusión 628
15 Contenedores e iteradores de la biblioteca estándar 638
15.1 Introducción 639
15.2 Introducción a los contenedores 640
15.3 Introducción a los iteradores 644
15.4 Introducción a los algoritmos 649
15.5 Contenedores de secuencia 649
15.5.1 Contenedor de secuencia vector 650
15.5.2 Contenedor de secuencia list 658
15.5.3 Contenedor de secuencia deque 662
Los capítulos 15, 16 y 17
se encuentran, en español en el sitio web del libro
xii Contenido
15.6 Contenedores asociativos 664
15.6.1 Contenedor asociativo multiset 665
15.6.2 Contenedor asociativo set 668
15.6.3 Contenedor asociativo multimap 669
15.6.4 Contenedor asociativo map 671
15.7 Adaptadores de contenedores 673
15.7.1 Adaptador stack 673
15.7.2 Adaptador queue 675
15.7.3 Adaptador priority_queue 676
15.8 La clase bitset 677
15.9 Conclusión 679
16 Algoritmos de la biblioteca estándar 690
16.1 Introducción 691
16.2 Requisitos mínimos para los iteradores 691
16.3 Algoritmos 693
16.3.1 fill, fill_n, generate y generate_n 693
16.3.2 equal, mismatch y lexicographical_compare 695
16.3.3 remove, remove_if, remove_copy y remove_copy_if 697
16.3.4 replace, replace_if, replace_copy y replace_copy_if 700
16.3.5 Algoritmos matemáticos 702
16.3.6 Algoritmos básicos de búsqueda y ordenamiento 706
16.3.7 swap, iter_swap y swap_ranges 710
16.3.8 copy_backward, merge, unique y reverse 711
16.3.9 inplace_merge, unique_copy y reverse_copy 714
16.3.10 Operaciones establecer (set) 716
16.3.11 lower_bound, upper_bound y equal_range 719
16.3.12 Ordenamiento de montón (heapsort) 721
16.3.13 min, max, minmax y minmax_element 724
16.4 Objetos de funciones 726
16.5 Expresiones lambda 729
16.6 Resumen de algoritmos de la Biblioteca estándar 730
16.7 Conclusión 732
17 Manejo de excepciones: un análisis más detallado 740
17.1 Introducción 741
17.2 Ejemplo: manejo de un intento de dividir entre cero 741
17.3 Volver a lanzar una excepción 747
17.4 Limpieza de la pila 748
17.5 Cuándo utilizar el manejo de excepciones 750
17.6 Constructores, destructores y manejo de excepciones 751
17.7 Excepciones y herencia 752
17.8 Procesamiento de las fallas de new 752
Contenido xiii
17.9 La clase unique_ptr y la asignación dinámica de memoria 755
17.10 Jerarquía de excepciones de la biblioteca estándar 758
17.11 Conclusión 759
Los capítulos 18, 19, 20, 21, 22 y 23
se encuentran, en inglés en el sitio web del libro
18 Introduction to Custom Templates 765
18.1 Introduction 766
18.2 Class Templates 766
18.3 Function Template to Manipulate a Class-Template
Specialization Object 771
18.4 Nontype Parameters 773
18.5 Default Arguments for Template Type Parameters 773
18.6 Overloading Function Templates 774
18.7 Wrap-Up 774
19 Custom Templatized Data Structures 777
19.1 Introduction 778
19.2 Self-Referential Classes 779
19.3 Linked Lists 780
19.4 Stacks 794
19.5 Queues 799
19.6 Trees 803
19.7 Wrap-Up 811
20 Searching and Sorting 822
20.1 Introduction 823
20.2 Searching Algorithms 824
20.2.1 Linear Search 824
20.2.2 Binary Search 827
20.3 Sorting Algorithms 831
20.3.1 Insertion Sort 832
20.3.2 Selection Sort 834
20.3.3 Merge Sort (A Recursive Implementation) 837
20.4 Wrap-Up 843
21 Class string and String Stream Processing:
A Deeper Look 849
21.1 Introduction 850
21.2 string Assignment and Concatenation 851
xiv Contenido
21.3 Comparing strings 853
21.4 Substrings 856
21.5 Swapping strings 856
21.6 string Characteristics 857
21.7 Finding Substrings and Characters in a string 859
21.8 Replacing Characters in a string 861
21.9 Inserting Characters into a string 863
21.10 Conversion to Pointer-Based char * Strings 864
21.11 Iterators 865
21.12 String Stream Processing 867
21.13 C++11 Numeric Conversion Functions 870
21.14 Wrap-Up 871
22 Bits, Characters, C Strings and structs 879
22.1 Introduction 880
22.2 Structure Definitions 880
22.3 typedef 882
22.4 Example: Card Shuffling and Dealing Simulation 882
22.5 Bitwise Operators 885
22.6 Bit Fields 894
22.7 Character-Handling Library 897
22.8 C String-Manipulation Functions 903
22.9 C String-Conversion Functions 910
22.10 Search Functions of the C String-Handling Library 915
22.11 Memory Functions of the C String-Handling Library 919
22.12 Wrap-Up 923
23 Other Topics 938
23.1 Introduction 939
23.2 const_cast Operator 939
23.3 mutable Class Members 941
23.4 namespaces 943
23.5 Operator Keywords 946
23.6 Pointers to Class Members (.* and -*) 948
23.7 Multiple Inheritance 950
23.8 Multiple Inheritance and virtual Base Classes 955
23.9 Wrap-Up 959
Contenido xv
A Precedencia y asociatividad de operadores 965
B Conjunto de caracteres ASCII 967
C Tipos fundamentales 968
D Sistemas numéricos 970
D.1 Introducción 971
D.2 Abreviatura de los números binarios como números octales
y hexadecimales 974
D.3 Conversión de números octales y hexadecimales a binarios 975
D.4 Conversión de un número binario, octal o hexadecimal a decimal 975
D.5 Conversión de un número decimal a binario, octal o hexadecimal 976
D.6 Números binarios negativos: notación de complemento a dos 978
E Preprocesador 983
E.1 Introducción 984
E.2 La directiva de preprocesamiento #include 984
E.3 La directiva de preprocesamiento #define: constantes simbólicas 985
E.4 La directiva de preprocesamiento #define: macros 985
E.5 Compilación condicional 987
E.6 Las directivas de preprocesamiento #error y #pragma 988
E.7 Los operadores # y ## 989
E.8 Constantes simbólicas predefinidas 989
E.9 Aserciones 990
E.10 Conclusión 990
Índice 995
Cómo programar C++, 9na Edición - Paul Deitel.pdf
“El principal mérito del lenguaje es la claridad…”
—Galen
Bienvenido al lenguaje de programación C++ y a Cómo programar en C++, novena edición. Este libro,
que presenta las tecnologías de computación de vanguardia, es apropiado para secuencias de cursos in-
troductorios basados en las recomendaciones curriculares de dos organizaciones profesionales clave: la
ACM y el IEEE.
La base del libro es el reconocido método de código activo de Deitel: los conceptos se presentan en
el contexto de programas funcionales completos seguidos de ejecuciones de ejemplo, en lugar de
hacerlo a través de fragmentos separados de código. En la sección Antes de empezar (vea el sitio web
de este libro) encontrará información para configurar su computadora basada en Linux, Windows o
Apple OS X para ejecutar los ejemplos de código.Todo el código fuente está disponible en el sitio web
de este libro (si requiere los códigos en inglés, puede obtenerlos de: www.deitel.com/books/cpphtp9
y en www.pearsonhighered.com/deitel). Use el código fuente que le proporcionamos para ejecutar
cada programa a medida que lo vaya estudiando.
Creemos que este libro y sus materiales de apoyo le brindarán una introducción informativa,
desafiante y entretenida a C++. Si surge alguna duda o pregunta a medida que lea este libro, puede
comunicarse con nosotros en deitel@deitel.com; le responderemos a la brevedad. Síganos en Face-
book (www deitel.com/deitelfan) y Twitter (@deitel); también puede suscribirse al boletín de
correo electrónico Deitel®
Buzz Online (www.deitel.com/newsletter/subscribe.html).
Estándar C++ 11
El nuevo estándar C++11 que se publicó en 2011 nos motivó a escribir esta nueva edición de Cómo
programar en C++. A lo largo del libro, cada una de las nuevas características de C++11 se identifica con
el icono “11” como se muestra aquí en el margen. A continuación le presentamos algunas de las carac-
terísticas clave de C++11 en esta nueva edición:
• CumpleconelnuevoestándarC++11.ExtensacoberturadelasnuevascaracterísticasdeC++11
(figura 1).
• El código se probó de manera exhaustiva en tres compiladores de C++ populares a nivel indus-
trial. Probamos los ejemplos de código en GNU™ C++ 4.7, Microsoft®
Visual C++®
2012 y
Apple®
LLVM en Xcode®
4.5.
• Apuntadores inteligentes. Los apuntadores inteligentes nos ayudan a evitar errores de admi-
nistración de memoria dinámica al proveer una funcionalidad adicional a la de los apuntadores
integrados. En el capítulo 17 (en el sitio web) hablaremos sobre unique_ptr.
Prefacio
xviii Prefacio
Características de C++11 en Cómo programar en C++, novena edición
Algoritmo all_of
Algoritmo any_of
Contenedor array
auto para inferencia de tipos
Funciones begin/end
Funciones miembro de
contenedor cbegin/cend
Compilador fix for  en tipos
de plantillas
Algoritmo copy_if
Algoritmo copy_n
Funciones miembro de
contenedor crbegin/cend
decltype
Argumentos de tipo
predeterminado en plantillas
de funciones
Funciones miembro
predeterminadas (default)
Delegación de constructores
Funciones miembro eliminadas
( delete)
Operadores de conversión
explicit
Clases final
Funciones miembro final
Algoritmo find_if_not
Contenedor forward_list
Claves inmutables en
contenedores asociativos
Inicializadores en la clase
Heredar constructores de la clase
base
Funciones miembro de
contenedor insert devuelven
iteradores
Algoritmo is_heap
Algoritmo is_heap_until
Palabras clave nuevas en C++11
Expresiones lambda
Inicialización de listas de pares
clave-valor
Inicialización de listas de objetos
pair
Inicialización de listas de valores
de retorno
Lista inicializadora de un arreglo
asignado en forma dinámica
Lista inicializadora de un vector
Inicializadores de listas en
llamadas a constructores
Tipo long long inte
Algoritmos min y max con
parámetros initializer_list
Algoritmo minmax
Algoritmo minmax_element
Algoritmo move
Operadores de asignación de
movimiento
Algoritmo move_backward
Constructores de movimiento
noexcept
Generación de números
aleatorios no determinísticos
Algoritmo none_of
Funciones de conversión
numérica
nullptr
Palabra clave override
Instrucción for basada en rangos
Expresiones regulares
Referencias rvalue
Enumeraciones (enum) con
alcance
Apuntador inteligente shared_
ptr
Función miembro shrink_to_
fit vector/deque
Especificar el tipo de las
constantes de una
enumeración
Objetos static_assert para
nombres de archivo
Objetos string para nombres de
archivo
Función no miembro swap
Funciones for con tipos de
retorno al final
Plantilla variádica tuple
Apuntador inteligente unique_
ptr
long long int sin signo
Apuntador inteligente weak_ptr
Fig. 1  Una muestra de las características de C++11 en Cómo programar en C++, novena edición.
• Cobertura anticipada de los contenedores, iteradores y algoritmos de la Biblioteca Estándar,
mejorada con capacidades de C++11. Transferimos el tratamiento de los contenedores, itera-
dores y algoritmos de la Biblioteca de plantillas Estándar del capítulo 22, en la edición anterior,
a los capítulos 15 y 16, y lo mejoramos con las características adicionales de C++11. La gran
mayoría de las necesidades de los programadores en cuanto a estructuras de datos pueden sa-
tisfacerse mediante la reutilización de estas capacidades de la Biblioteca Estándar. En el capítu-
lo 19 (en inglés en el sitio web) le indicaremos cómo crear sus propias estructuras de datos
personalizadas.
• Generación de números aleatorios, simulación y juegos. Para ayudar a que los programas sean
más seguros, agregamos un tratamiento de las nuevas herramientas de generación de números
aleatorios no deterministas.
Programación orientada a objetos xix
Programación orientada a objetos
• Metodología de presentación de objetos en los primeros capítulos. El libro presenta los conceptos
básicos y la terminología de la tecnología de objetos en el capítulo 1. Usted desarrollará sus pri-
meras clases y objetos personalizados en el capítulo 3; de esta manera hacemos que usted “piense
acerca de los objetos” de inmediato y domine estos conceptos con más profundidad.1
• Objetos string de la Biblioteca Estándar de C++. C++ ofrece dos tipos de cadenas: los objetos
de la clase string (que comenzaremos a usar en el capítulo 3) y las cadenas de C. Reemplaza-
mos la mayoría de las ocurrencias de las cadenas de C con instancias de la clase string de C++
para que los programas sean más robustos y para eliminar muchos de los problemas de seguri-
dad de las cadenas de C. Seguiremos hablando sobre las cadenas de C más adelante en el libro
para preparar al lector a trabajar con el código heredado que encontrará en la industria. En
cuanto al desarrollo de nuevos programas, es preferible usar objetos string.
• Objetos array de la Biblioteca Estándar de C++. Nuestro principal tratamiento de los arreglos
usa ahora la plantilla de la clase array de la Biblioteca Estándar en vez de los arreglos integra-
dos estilo C, basados en apuntadores. De todas formas cubriremos los arreglos integrados ya
que siguen siendo útiles en C++ y para que el lector pueda leer el código heredado. C++ ofrece
tres tipos de arreglos: objetos array y vector (que comenzaremos a usar en el capítulo 7) y
arreglos estilo C basados en apuntadores, que veremos en el capítulo 8. Según sea apropiado,
usaremos la plantilla de clase array en vez de arreglos de C a lo largo del libro. En cuanto al
desarrollo de nuevos programas, es preferible usar objetos de la plantilla de clase array.
• Creación de clases valiosas. Un objetivo clave de este libro es preparar al lector para crear clases
valiosas. En el ejemplo práctico del capítulo 10, usted creará su propia clase Array personali-
zada (y luego en los ejercicios del capítulo 18, en inglés, la convertirá en una plantilla de clase).
Aquí es donde apreciará en verdad el concepto de las clases. El capítulo 10 comienza con una
prueba práctica de la plantilla de clase string, de modo que pueda ver un uso elegante de la
sobrecarga de operadores antes de implementar su propia clase personalizada con operadores
sobrecargados.
• Ejemplos prácticos en programación orientada a objetos. Ofrecemos ejemplos prácticos que
abarcan varias secciones y capítulos, además de cubrir el ciclo de vida de desarrollo de software.
Entre éstos se incluye la clase LibroCalificaciones en los capítulos 3 a 7, la clase Tiempo en
el capítulo 9 y la clase Empleado en los capítulos 11 y 12. El capítulo 12 contiene un diagrama
detallado y una explicación de cómo puede C++ implementar internamente el polimorfismo,
las funciones virtual y la vinculación dinámica.
• Ejemplo práctico opcional: uso de UML para desarrollar un diseño orientado a objetos y la
implementación en C++ de un ATM. El UML™
(Lenguaje unificado de modelado™
) es el
lenguaje gráfico estándar de la industria para los sistemas de modelado orientados a objetos.
Presentamos el UML en los primeros capítulos. Diseñamos e implementamos el software para
un cajero automático simple (ATM). Analizamos un documento de requerimientos típico que
1 En los cursos que requieren una metodología en la que se presenten los objetos en capítulos posteriores, le recomenda-
mos el libro C++ How to Program, Late Objects (versión en inglés), el cual empieza con los primeros seis capítulos sobre
losfundamentosdelaprogramación(incluyendodossobreinstruccionesdecontrol)ycontinúaconsietecapítulosque
introducen los conceptos de programación orientada a objetos en forma gradual.
xx Prefacio
especifica el sistema a construir. Determinamos las clases necesarias para implementar ese sis-
tema, los atributos que necesitan tener las clases, los comportamientos que deben exhibir y
especificamos la forma en que deben interactuar esas clases entre sí para cumplir con los reque-
rimientos del sistema. A partir del diseño producimos una implementación completa en C++.
A menudo los estudiantes reportan que el ejemplo práctico les ayuda a “enlazarlo todo” y
comprender en verdad la orientación a objetos.
• Manejo de excepciones. Integramos el manejo básico de excepciones en los primeros capítu-
los del libro. Los instructores pueden extraer con facilidad más material del capítulo 17, Mane-
jo de excepciones: un análisis detallado.
• Estructuras de datos basadas en plantillas personalizadas. Ofrecemos un tratamiento extenso
de varios capítulos sobre las estructuras de datos: consulte el módulo Estructuras de datos en
el gráfico de dependencia de los capítulos (figura 6).
• Tres paradigmas de programación. Hablamos sobre la programación estructurada, la programa-
ción orientada a objetos y la programación genérica.
Características pedagógicas
• Extensa cobertura de los fundamentos de C++. Incluimos un tratamiento conciso de dos capí-
tulos sobre las instrucciones de control y el desarrollo de algoritmos.
• El capítulo 2 ofrece una introducción simple a la programación en C++.
• Ejemplos: incluimos un amplio rango de programas de ejemplo seleccionados de temas como
ciencias computacionales, negocios, simulación, juegos y demás (figura 2).
Ejemplos
Ejemplo práctico de la clase Array
Clase Autor
Programa de cuenta bancaria
Programa para imprimir gráficos de barras
Clase EmpleadoBaseMasComision
Creación y recorrido de árboles binarios
Programa de prueba BusquedaBinaria
Barajar y repartir cartas
Clase DatosCliente
Clase EmpleadoPorComision
Comparación de objetos string
Proceso de compilación y vinculación
Cálculos del interés compuesto con for
Conversión de objetos string a cadenas
de C
Repetición controlada por contador
Simulación del juego de dados “craps”
Programa de consulta de crédito
Clase Fecha
Conversión de tipos descendente (downcasting)
e información de tipos en tiempo de ejecución
Clase Empleado
Constructor explicit
Función fibonacci
Algoritmos fill
Especializaciones de plantillas de funciones de la
plantilla de función imprimirArreglo
Algoritmos generate
Clase LibroCalificaciones
Inicialización de un arreglo en una declaración
Entrada a partir de un objeto istringstream
Solución de factorial iterativa
Fig. 2  Una muestra de los ejemplos del libro (parte 1 de 2).
Características pedagógicas xxi
Ejemplos
Expresiones lambda
Manipulación de listas enlazadas
Plantilla de clase map
Algoritmos matemáticos de la Biblioteca
Estándar
Plantilla de función maximum
Programa de ordenamiento por
combinación
Plantilla de clase multiset
new lanza bad_alloc durante una falla
Clase NumeroTelefonico
Programa de análisis de encuestas
Demostración del polimorfismo
Preincremento y postincremento
Clase de adaptador priority_queue
Clase de adaptador queue
Archivos de acceso aleatorio
Generación de números aleatorios
Función recursiva factorial
Tirar un dado 6,000,000 de veces
Clase EmpleadoAsalariado
Clase Vendedor
Algoritmos de búsqueda y ordenamiento de la
Biblioteca Estándar
Archivos secuenciales
Plantilla de clase set
Programa shared_ptr
Clase de adaptador stack
Clase Stack
Desenredo de pila (stack unwinding)
Programa de la clase string de la Biblioteca
Estándar
Manipulador de flujos showbase
Asignación y concatenación de objetos string
Función miembro substr de string
Suma de enteros con la instrucción for
Clase Tiempo
Objeto unique_ptr que administra la memoria
asignada en forma dinámica
Validar la entrada del usuario con expresiones
regulares
Plantilla de clase vector
• Audiencia. Los ejemplos son accesibles para estudiantes de ciencias computacionales, tecno-
logía de la información, ingeniería de software y de negocios en cursos de C++ para estudiantes
de nivel principiante a intermedio, aunque también puede ser de gran utilidad para los progra-
madores profesionales.
• Ejercicios de autoevaluación y respuestas. Se incluyen ejercicios extensos de autoevaluación
con sus respuestas para autoestudio.
• Ejercicios interesantes, entretenidos y desafiantes. Cada capítulo concluye con un amplio con-
junto de ejercicios: recordatorios simples de la terminología y los conceptos importantes, que
sirven para identificar los errores en ejemplos de código, escribir instrucciones individuales de
programas, escribir pequeñas porciones de clases de C++ y funciones tanto miembro como no
miembro, escribir programas completos e implementar proyectos grandes. La figura 3 contiene
una lista de muestra de los ejercicios del libro, incluyendo nuestros ejercicios Hacer la diferencia,
que animan al lector a usar las computadoras e Internet para investigar y resolver problemas so-
ciales importantes. Esperamos que usted los aborde con sus propios valores, políticas y creencias.
Ejercicios
Sistema de reservaciones de
una aerolínea
Ejercicios avanzados de
manipulación de cadenas
Ordenamiento de burbuja
Cree su propio compilador
Cree su propia computadora
Cálculo de los salarios
Clase abstracta de la huella de
carbono: polimorfismo
Fig. 3  Una muestra de los ejercicios del libro (parte 1 de 2).
Fig. 2  Una muestra de los ejemplos del libro (parte 2 de 2).
xxii Prefacio
Ejercicios
Barajar y repartir cartas
Instrucción asistida por
computadora
Instrucción asistida por
computadora: niveles de
dificultad
Instrucción asistida por
computadora: monitoreo del
desempeño de los estudiantes
Instrucción asistida por
computadora: reducción de
la fatiga de los estudiantes
Instrucción asistida por
computadora: variación de
los tipos de problemas
Cocinar con ingredientes más
saludables
Modificación del juego de
“craps”
Límites de crédito
Generador de crucigramas
Criptogramas
Leyes de De Morgan
Tiro de dados
Ocho reinas
Respuesta de emergencia
Implementar la privacidad con la
criptografía
Crecimiento de la base de usuarios
de Facebook
Serie de Fibonacci
Kilometraje de gasolina
Cuestionario de hechos de
advertencia global
Juego de adivinar el número
Juego del colgado
Registros médicos, Paseo del caballo
Quintillas
Recorrido de laberinto: generación
de laberintos al azar
Código Morse
Modificación al sistema de nóminas
Problema de Peter Minuit
Explorador de “phishing”
Latín cerdo
Programa bancario polimórfico
mediante el uso de la jerarquía
de cuentas
Triples de Pitágoras
Calculadora de salarios
Criba de Eratóstenes
Desencriptado simple
Encriptado simple
Lenguaje SMS
Explorador de “spam”
Corrector ortográfico
Calculadora de la frecuencia
cardiaca esperada
Alternativas para el plan fiscal:
el “impuesto justo”
Generador de palabras
de números telefónicos
Canción “Los doce días de
Navidad”
Simulación de la tortuga
y la liebre
Torres de Hanoi
Crecimiento de la población
mundial
• Ilustraciones y figuras. Se incluyen muchas tablas, dibujos lineales, diagramas de UML, pro-
gramas y resultados de los programas. La figura 4 incluye una muestra de los dibujos y diagra-
mas del libro.
Dibujos y diagramas
Dibujos y diagramas del texto principal
Jerarquía de datos
Proceso de compilación y
vinculación para programas con
varios archivos de código fuente
Orden en el que se evalúa un
polinomio de segundo grado
Diagramas de la clase
LibroCalificaciones
Diagrama de actividad de la
instrucción if de selección
simple
Diagrama de actividad de la
instrucción if...else de
selección doble
Diagrama de actividad de UML de
la instrucción de repetición while
Diagrama de actividad de UML de
la instrucción de repetición for
Diagrama de actividad de UML de
la instrucción de repetición
do...while
Diagrama de actividad de la
instrucción switch de selección
múltiple
Instrucciones de secuencia,
selección y repetición de una
sola entrada/una sola salida de
C++
Análisis de un programa de
paso por valor y paso por
referencia
Diagramas de la jerarquía de
herencia
Pila de llamadas a funciones y
registros de activación
Llamadas recursivas a la
función fibonacci
Diagramas de aritmética de
apuntadores
Jerarquía de herencia de
MiembroDeLaComunidad
Jerarquía de herencia de Figura
Fig. 3  Una muestra de los ejercicios del libro (parte 2 de 2).
Fig. 4  Una muestra de los dibujos y diagramas del libro (parte 1 de 2).
Programación segura en C++ xxiii
Dibujos y diagramas
Herencia public, protected y
private
Diagrama de clases de UML
de la jerarquía Empleado
Cómo funcionan las llamadas a
funciones virtual
Jerarquía de plantillas de E/S de
flujos
Dos objetos de clases
autorreferenciadas enlazados
entre sí
Representación gráfica de una
lista
Operación insertarAlFrente
representada en forma
gráfica
Operación insertarAlFinal
representada en forma
gráfica
Operación quitarDelFrente
representada en forma
gráfica
Operación quitarDelFinal
representada en forma
gráfica
Lista circular enlazada simple
Lista doblemente enlazada
Lista circular doblemente
enlazada
Representación gráfica de un
árbol binario
Dibujos y diagramas del ejemplo práctico del ATM
Diagrama de caso-uso para el
sistema del ATM desde la
perspectiva del Usuario
Diagrama de clases que muestra
una asociación entre clases
Diagrama de clases que muestra
relaciones de composición
Diagrama de clases para el modelo
del sistema del ATM
Clases con atributos
Diagrama de estado para el ATM
Diagrama de actividad para una
transacción SolicitudSaldo
Diagrama de actividad para una
transacción Retiro
Clases en el sistema del ATM con
atributos y operaciones
Diagrama de comunicaciones del
ATM ejecutando una solicitud
de saldo
Diagrama de comunicación para
ejecutar una solicitud de saldo
Diagrama de secuencia que
modela la ejecución de un
Retiro
Diagrama de caso-uso para una
versión modificada de nuestro
sistema ATM que también
permite a los usuarios transferir
dinero entre cuentas
Diagrama de clases que muestra
las relaciones de
composición de una clase
Auto
Diagrama de clases para el
modelo del sistema del
ATM que incluye la clase
Deposito
Diagrama de actividad para una
transacción Deposito
Diagrama de secuencia que
modela la ejecución de un
Deposito
Otras características
• Apuntadores. Ofrecemosunacoberturaextensadelasherramientasintegradasdeapuntadores
y la relación íntima entre los apuntadores integrados, las cadenas de C y los arreglos integrados.
• Presentación visual de la búsqueda y el ordenamiento con una explicación simple de Big O.
• Material adicional en línea. Se incluyen varios capítulos (unos en español y otros en inglés)
disponibles en formato PDF con capacidad de búsqueda en el sitio Web de este libro, consulte
con su proveedor de Pearson cómo acceder a ellos.
Programación segura en C++
Es difícil crear sistemas con solidez industrial que resistan los ataques de los virus, gusanos y otros tipos
de “malware”. En la actualidad dichos ataques pueden ser instantáneos y de alcance global debido a
Internet. Al integrar la seguridad en el software desde el comienzo del ciclo de desarrollo es posible re-
ducir la vulnerabilidad de manera considerable.
Fig. 4  Una muestra de los dibujos y diagramas del libro (parte 2 de 2).
xxiv Prefacio
El Centro de coordinación CERT®
(www.cert.org) se creó para analizar y responder a los ataques de
manera oportuna. CERT (el equipo de respuesta a emergencias cibernéticas) es una organización finan-
ciada por el gobierno de Estados Unidos, dentro del Carnegie Mellon University Software Engineering
Institute™
. CERT publica y promueve estándares seguros de codificación para varios lenguajes de pro-
gramación populares para ayudar a los desarrolladores de software a implementar sistemas con solidez
industrial que eviten las prácticas de programación que dejan los sistemas expuestos a ataques.
Agradecemos el apoyo de Robert C. Seacord, gerente de codificación segura en CERT y profesor
adjunto en la Carnegie Mellon University School of Computer Science. El señor Seacord fue revisor téc-
nico de nuestro libro Cómo programar en C, séptima edición, y revisó nuestros programas en C desde una
perspectiva de seguridad, y recomendó que nos adhiriéramos al Estándar de codificación segura en C de
CERT.
Con este libro hicimos lo mismo: nos adherimos al Estándar de codificación segura en C++ de CERT,
el cual puede encontrar en:
www.securecoding.cert.org
Nos llenó de orgullo descubrir que ya estábamos recomendando muchas de esas prácticas de codifica-
ción en nuestros libros. Actualizamos nuestro código y los análisis del mismo para cumplir con estas
prácticas, según lo apropiado para un libro de texto de nivel introductorio/intermedio. Si piensa crear
sistemas en C++ con solidez industrial, considere leer el libro Secure Coding in C and C++, Second Edi-
tion (de Robert Seacord, Addison-Wesley Professional).
Contenido en línea
Para acceder al sitio Web del libro, vaya a
www.pearsonenespañol.com/deitel
En el sitio web encontrará los siguientes capítulos en formato PDF con capacidad de búsqueda. Los tres
primeros se encuentran en español, y los restantes en idioma inglés:
• 15 Contenedores e iteradores de la biblioteca estándar
• 16 Algoritmos de la biblioteca estándar
• 17 Manejo de excepciones: un análisis más detallado
• 18 Introduction to Custom Templates
• 19 Custom Templatized Data Structures
• 20 Seearching and Sorting
• 21 Class string and String Stream Processing: A Deeper Look
• 22 Bits, Characters, C Strings and structs
• 23 Other Topics
El sitio web también incluye:
• Descripciones del ejercicio Build Your Own Compiler del capítulo 19.
• Prueba práctica del capítulo 1 para Mac OS X.
Métodos de enseñanza xxv
Gráfico de dependencias
El gráfico de la siguiente página (figura 6) muestra la dependencia entre los capítulos de este libro, el cual
puede ser de mucha ayuda para los profesores que desean planear sus programas de estudio con este
texto. El gráfico muestra la organización modular del libro.
Métodos de enseñanza
Esta edición contiene una extensa colección de ejemplos. Hacemos hincapié en la claridad de los pro-
gramas y nos concentramos en crear software bien diseñado.
Método de código activo. El libro está lleno de ejemplos de “código activo”: la mayoría de los nuevos
conceptos se presentan en aplicaciones de C++ funcionales completas, seguidos de una o más ejecuciones
que muestran las entradas y salidas de los programas. En algunos casos, en donde utilizamos un frag-
mento de código, para asegurar que esté correcto lo evaluamos en un programa funcional completo para
después copiarlo y pegarlo en el libro.
Coloreo de sintaxis. Para mejorar la legibilidad, resaltamos la sintaxis de todo el código de C++, en tonos
de gris, de manera similar a como lo hace la mayoría de los entornos de desarrollo integrados de C++ y
los editores de código. Nuestras convenciones de sintaxis son las siguientes:
los comentarios aparecen así
las palabras clave aparecen así
las constantes y los valores literales aparecen así
el resto del código aparece en este tipo
Resaltado de código. Colocamos rectángulos sombreados de color gris alrededor de los segmentos de
código clave en cada programa.
Uso de fuentes para dar énfasis. Colocamos la ocurrencia de definición de cada término clave en ne-
gritas para facilitar su referencia. Enfatizamos los componentes en pantalla en la fuente Helvetica en
negritas (porejemplo,elmenúArchivo)yeltextodelprogramadeC++enlafuenteLucida (porejemplo,
int x = 5;).
Objetivos. Las citas de apertura van seguidas de una lista de objetivos del capítulo.
Tips de programación. Incluimos tips de programación para ayudarle a enfocarse en los aspectos im-
portantes del desarrollo de programas. Estos tips y prácticas representan lo mejor que hemos podido
recabar a lo largo de siete décadas combinadas de experiencia en la programación y la enseñanza.
Buenas prácticas de programación
Las Buenas prácticas de programación llaman la atención hacia técnicas que le ayudarán
a producir programas más claros, comprensibles y fáciles de mantener.
Errores comunes de programación
Al poner atención en estos Errores comunes de programación se reduce la probabilidad de
que pueda cometerlos.
xxvi Prefacio
Fig. 6
Gráfico de
dependencia
de los capítulos
Introducción
Temas heredados de C
Flujos, archivos
y cadenas
Otros temas y
características de C++11
1 Introducción a las
computadoras y a C++
22 Bits, Characters,
C-Strings and struct
13 Entrada/salida de flujos:
un análisis más detalladoI
14
Procesamiento
de archivos
23 Other
Topics
21 Class
string and
String Stream
Processing: A
Deeper Look
[Nota: las flechas que apuntan
hacia un capítulo indican las
dependencias de ese capítulo].
Introducción a la
programación, clases
y objetos
Instrucciones de
control, métodos
y arreglos
Programación
orientada a objetos Estructuras de datos
2 Introducción a la programación
en C++, entrada/salida y operadores
4 Instrucciones de control, parte I:
operadores de asignación, ++ y --
9 Clases, un análisis más detallado:
lanzar excepciones 15 Contenedores e iteradores
de la biblioteca estándar
3 Introducción a las clases,
objetos y cadenas
5 Instrucciones de control,
parte 2: operadores lógicos
10 Sobrecarga de operadores:
la clase string 16Algoritmosdelabiblioteca
estándar
6 Funciones y una introducción
a la recursividad
11 POO: herencia
6.20-6.22 Recursividad
7Plantillasdeclasearray y
vector:cómoatraparexcepciones
12POO:polimorfismo
18IntrotoCustomTemplates
8 Apuntadores
17 Manejo de excepciones: un
análisis más detallado 19 Custom Templatized Data
Structures
20 Searching and Sorting
1. La mayoría del capítulo 13 puede leerse después del capítulo 7. Una pequeña parte requiere los capítulos 11 y 18.
Suplementos para el profesor xxvii
Tips para prevenir errores
Estos tips contienen sugerencias para exponer los errores y eliminarlos de sus programas;
muchos de ellos describen aspectos de C++ que evitan que los errores siquiera entren a los
programas.
Tips de rendimiento
Estos tips resaltan las oportunidades para hacer que sus programas se ejecuten más rápido,
o para minimizar la cantidad de memoria que ocupan.
Tips de portabilidad
Los Tips de portabilidad le ayudan a escribir código que pueda ejecutarse en varias plata-
formas.
Observaciones de Ingeniería de Software
LasObservacionesdeIngenieríadeSoftwareresaltanlosasuntosdearquitecturaydiseño,
lo cual afecta la construcción de los sistemas de software, especialmente los de gran escala.
Viñetas de resumen. Presentamos un resumen detallado del capítulo, estilo lista con viñetas, sección por
sección. Para facilitar su consulta, incluimos el número de página donde aparece la definición de los
términos clave del capítulo.
Índice. Incluimos un amplio índice en donde las definiciones de los términos clave se resaltan con un
número en negritas.
Cómo obtener el software utilizado en este libro
Escribimos los ejemplos de código en Cómo programar en C++, novena edición mediante el uso de las
siguientes herramientas de desarrollo de C++:
• El programa gratuito Visual Studio Express 2012 de Microsoft para Windows Desktop, que
incluye Visual C++ junto con otras herramientas de desarrollo de Microsoft. Se ejecuta en
Windows 7 y 8, y está disponible para su descarga en
www.microsoft.com/visualstudio/esn/downloads#
d-express-windows-desktop
• El programa gratuito GNU C++ de GNU (gcc.gnu.org/install/binaries.html), que ya
se encuentra instalado en la mayoría de los sistemas Linux y puede instalarse también en siste-
mas Mac OS X y Windows.
• El programa Xcode gratuito de Apple, que los usuarios de Mac OS X pueden descargar de la
App Store de Mac.
Suplementos para el profesor (todos en inglés)
Los siguientes suplementos están disponibles sólo para profesores calificados a través del Centro de recur-
sos para el profesor de Pearson (http://www.pearsonhighered.com/deitel/):
• Manual de soluciones: contiene soluciones para la mayoría de los ejercicios de final de capí-
tulo. Agregamos muchos ejercicios Making a Difference (Hacer la diferencia), la mayoría con
soluciones. El acceso está restringido para los profesores que lleven este libro como
texto en su curso. Los profesores pueden obtener acceso a través de los representantes de
Pearson. Si usted no es un miembro docente registrado, póngase en contacto con alguno
de nuestros representantes o contacte al editor. No se proporcionan soluciones a los ejercicios
xxviii Prefacio
de “proyectos”. Consulte nuestro Centro de recursos de proyectos de programación, en
donde encontrará muchos ejercicios y proyectos adicionales:
www.deitel.com/ProgrammingProjects
• TestItemFile(Archivodeprueba)depreguntasdeopciónmúltiple(aproximadamentedospor
cada sección del libro).
• Diapositivas de PowerPoint®
personalizables que contienen todo el código (en inglés) y las figu-
ras del texto (también en inglés), además de elementos en viñetas que sintetizan los puntos clave.
Agradecimientos
Queremos agradecer a Abbey Deitel y Barbara Deitel, de Deitel  Associates, Inc., por la gran cantidad
de horas que dedicaron a este proyecto. Abbey fue coautora del capítulo 1; además, junto con Barbara
investigaron de manera minuciosa las nuevas herramientas de C++11.
Somos afortunados al haber trabajado con el dedicado equipo de editores profesionales en Pearson.
Apreciamos la orientación, inteligencia y energía de Tracy Johnson, editor ejecutivo de ciencias compu-
tacionales. Carole Snyder hizo un extraordinario trabajo de reclutar a los revisores del libro y se hizo cargo
del proceso de revisión. Bob Engelhardt hizo un maravilloso trabajo para llevar el libro a su publicación.
Revisores
Queremos agradecer los esfuerzos de nuestros revisores. Este libro fue revisado por los miembros actua-
les y anteriores del comité de estándares de C++ que desarrolló C++11, académicos que imparten cursos
sobre C++ y expertos en la industria. Todos ellos proporcionaron incontables sugerencias para mejorar
la presentación. Cualquier error en el libro es por culpa nuestra.
Revisores de la novena edición: Dean Michael Berris (Google, Miembro del comité ISO C++),
Danny Kalev (experto en C++, analista de sistemas certificado y ex miembro del Comité de estándares
de C++), Linda M. Krause (Elmhurst College), James P. McNellis (Microsoft Corporation), Robert C.
Seacord (gerente de codificación segura en SEI/CERT, autor de Secure Coding in C and C++) y José
Antonio González Seco (Parlamento de Andalucía).
Revisores de ediciones anteriores: Virginia Bailey (Jackson State University),Thomas J. Borrelli (Ro-
chester Institute ofTechnology), Ed Brey (Kohler Co.), Chris Cox (Adobe Systems), Gregory Dai (eBay),
Peter J. DePasquale (The College of New Jersey), John Dibling (SpryWare), Susan Gauch (University of
Arkansas), Doug Gregor (Apple, Inc.), Jack Hagemeister (Washington State University), Williams M.
Higdon (University of Indiana), Anne B. Horton (Lockheed Martin),Terrell Hull (Logicalis Integration
Solutions), Ed James Beckham (Borland), Wing-Ning Li (University of Arkansas), Dean Mathias (Utah
State University), Robert A. McLain (Tidewater Community College), Robert Myers (Florida State Uni-
versity), Gavin Osborne (Saskatchewan Inst. of App. Sci. and Tech.), Amar Raheja (California State Po-
lytechnic University, Pomona), April Reagan (Microsoft), Raymond Stephenson (Microsoft), Dave
Topham (Ohlone College), Anthony Williams (autor y miembro del Comité de estándares de C++) y
Chad Willwerth (University Washington,Tacoma).
Apreciaremos con sinceridad sus comentarios, críticas, correcciones y sugerencias para mejorar el
texto. Dirija toda su correspondencia a:
deitel@deitel.com
Le responderemos a la brevedad posible. Disfrutamos mucho el escribir Cómo programar en C++, nove-
na edición. ¡Esperamos que usted disfrute el leerlo!
Paul Deitel
Harvey Deitel
Acerca de los autores xxix
Acerca de los autores
Paul J. Deitel, CEO y Director Técnico de Deitel  Associates, Inc., es egresado del MIT, en donde
estudió Tecnología de la Información. A través de Deitel  Associates, Inc., ha impartido cientos de
cursos de programación a empresas de la industria como: Cisco, IBM, Siemens, Sun Microsystems,
Dell, Fidelity, NASA (en el Centro Espacial Kennedy), National Severe Storm Laboratory, White
Sands Missile Range, Rogue Wave Software, Boeing, SunGard Higher Education, Nortel Networks,
Puma, iRobot, Invensys, entre muchos más. Él y su coautor, el Dr. Harvey M. Deitel, son autores de
los libros de texto, libros profesionales y videos sobre lenguajes de programación más vendidos en el
mundo.
Dr. Harvey M. Deitel, Presidente y Consejero de Estrategia de Deitel  Associates, Inc., tiene 50
años de experiencia en el campo de la computación. El Dr. Deitel tiene una licenciatura y una maestría
en ingeniería eléctrica por el MIT y un doctorado en matemáticas de la Boston University. Tiene mu-
chos años de experiencia como profesor universitario, la cual incluye un puesto vitalicio y el haber sido
presidente del departamento de Ciencias de la computación en el Boston College antes de fundar, con
su hijo Paul J. Deitel, Deitel  Associates, Inc. Los textos de los Deitel se han ganado el reconocimien-
tointernacionalyhansidotraducidosalchino,coreano,japonés,alemán,ruso,español,francés,polaco,
italiano, portugués, griego, urdú y turco. El Dr. Deitel ha impartido cientos de cursos de programación
a clientes corporativos, académicos, gubernamentales y militares.
Capacitación corporativa de Deitel  Associates, Inc.
Deitel  Associates, Inc., fundada por Paul Deitel y Harvey Deitel, es una empresa reconocida a nivel
mundial, dedicada al entrenamiento corporativo y la creación de contenido, especializada en lenguajes
de programación de computadora, tecnología de objetos, desarrollo de aplicaciones móviles y tecnolo-
gíadesoftwaredeInternetyWeb. Susclientesincluyenmuchasdelasempresasmásgrandes delmundo,
agencias gubernamentales, sectores del ejército e instituciones académicas. La empresa proporciona
cursos, en las instalaciones de sus clientes en todo el mundo, sobre la mayoría de los lenguajes y plata-
formas de programación, como C++, Visual C++®
, C, Java™
, Visual C#®
, Visual Basic®
, XML®
,
Python®
, tecnología de objetos, programación en Internet y Web, desarrollo de aplicaciones para An-
droid, desarrollo de aplicaciones de Objective-C y para iPhone, y una lista cada vez mayor de cursos
adicionales de programación y desarrollo de software.
A lo largo de su sociedad editorial de más de 36 años con Prentice Hall/Pearson, Deitel  Associa-
tes, Inc. ha publicado libros de texto de vanguardia sobre programación, libros profesionales y cursos en
video de LiveLessons. Puede contactarse con Deitel  Associates, Inc. y con los autores por medio del
correo electrónico:
deitel@deitel.com
Para conocer más sobre la Serie de Capacitación Corporativa Dive Into®
de Deitel, visite:
www.deitel.com/training
Para solicitar una propuesta de capacitación impartida en el sitio de su organización, en cualquier parte
del mundo, envíe un correo a deitel@deitel.com.
Quien desee comprar libros de Deitel y cursos en video de LiveLessons puede hacerlo a través de
www.deitel.com. Las empresas, el gobierno, las instituciones militares y académicas que deseen realizar
pedidos en masa deben hacerlo directamente con Pearson. Para obtener más información, visite
www.pearsonenespañol.com
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Introducción a las computadoras
y a C++ 1
El hombre sigue siendo
la computadora más
extraordinaria de todas.
—John F. Kennedy
Un buen diseño es un buen
negocio.
—Thomas J. Watson, fundador de IBM
Qué maravilloso es que nadie
necesite esperar un solo
momento para empezar a
mejorar el mundo.
—Anne Frank
O b j e t i v o s
En este capítulo aprenderá a:
n Familiarizarse con los
emocionantes y recientes
desarrollos en el campo de
las computadoras.
n Conocer los fundamentos del
hardware, software y redes
de computadoras.
n Emplear la jerarquía de datos.
n Reconocer distintos tipos de
lenguajes de programación.
n Familiarizarse con algunos
conceptos básicos de
tecnología de objetos.
n Apreciar algunos
fundamentos de Internet
y World Wide Web.
n Conocer un entorno
de desarrollo común de
programas en C++.
n Probar una aplicación en C++.
n Familiarizarse con algunas de
las recientes tecnologías clave
de software.
n Comprender cómo pueden
las computadoras ayudarlo
a hacer la diferencia.
2 Capítulo 1 Introducción a las computadoras y a C++
1.1Introducción
Bienvenido a C++: un poderoso lenguaje de programación de computadoras apropiado para las perso-
nas con orientación técnica con poca o ninguna experiencia de programación, y para los programadores
experimentados que desarrollan sistemas de información de tamaño considerable. Usted ya está fami-
liarizadoconlaspoderosastareasquerealizanlascomputadoras.Medianteestelibroaprenderáaescribir
instrucciones para ordenar a las computadoras que realicen esos tipos de tareas. El software (es decir,
las instrucciones que usted escribe) controla el hardware (es decir, las computadoras).
Aprenderá sobre la programación orientada a objetos: la metodología de programación clave de la
actualidad. En este texto creará muchos objetos de software que modelan las cosas del mundo real.
C++esunodeloslenguajesdedesarrollodesoftwaremáspopulares.Estelibroleproporcionaunaintro-
ducción a la programación en C++11: la versión más reciente estandarizada mediante la Organización In-
ternacional para la Estandarización (ISO) y la Comisión Electrotécnica Internacional (IEC).
En la actualidad hay en uso más de mil millones de computadoras de propósito general, además de
miles de millones de teléfonos celulares, teléfonos inteligentes (smartphones) y dispositivos portátiles
(como las computadoras tipo tableta). De acuerdo con un estudio realizado por eMarketer, el número
de usuarios móviles de Internet sobrepasará los 134 millones en 2014.1
Las ventas de teléfonos inte-
ligentes excedieron a las ventas de computadoras personales en 2011.2
Se espera que para 2015, las
ventas de las tabletas representen cerca del 20% de todas las ventas de computadoras personales.3
Se
espera que en 2014 el mercado de las aplicaciones de teléfonos inteligentes exceda los $40 mil millones.4
Este explosivo crecimiento está creando oportunidades importantes para la programación de aplicacia-
nes móviles.
1 www.circleid.com/posts/mobile_internet_users_to_reach_134_million_by_2013/.
2 www.mashable.com/2012/02/03/smartphone-sales-overtake-pcs.
3 www.forrester.com/ER/Press/Release/0,1769,1340,00.html.
4 Inc. diciembre de 2010/enero de 2011, páginas 116-123.
1.1 Introducción
1.2 Las computadoras e Internet en la
industria y la investigación
1.3 Hardware y software
1.3.1 La Ley de Moore
1.3.2 Organización de la computadora
1.4 Jerarquía de datos
1.5 Lenguajes máquina, lenguajes
ensambladores y lenguajes de alto nivel
1.6 C++
1.7 Lenguajes de programación
1.8 Introducción a la tecnología de objetos
1.9 Entorno de desarrollo común en C++
1.10 Prueba de una aplicación de C++
1.11 Sistemas operativos
1.11.1 Windows: un sistema operativo propietario
1.11.2 Linux: un sistema operativo de código
fuente abierto
1.11.3 OS X de Apple: iOS de Apple para
dispositivos iPhone®, iPad® y iPod Touch®
1.11.4 Android de Google
1.12 Internet y World Wide Web
1.13 Cierta terminología clave de desarrollo de
software
1.14 C++11 y las bibliotecas Boost de código
fuente abierto
1.15 Mantenerse actualizado con las
tecnologías de la información
1.16 Recursos Web
Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios | Hacer la diferencia |
Recursos para hacer la diferencia
1.2 Las computadoras e Internet en la industria y la investigación 3
1.2Las computadoras e Internet en la industria y la investigación
Éstos son tiempos emocionantes en el campo de la computación. Muchas de las empresas más influyen-
tes y exitosas de las últimas dos décadas son compañías de tecnología, como Apple, IBM, Hewlett
Packard, Dell, Intel, Motorola, Cisco, Microsoft, Google, Amazon, Facebook, Twitter, Groupon,
Foursquare, Yahoo!, eBay y muchas más. Estas empresas son importantes fuentes de empleo para las
personas que estudian ciencias computacionales, ingeniería computacional, sistemas de información o
disciplinas relacionadas. En 2013, Apple era la compañía más valiosa del mundo. La figura 1.1 provee
unos cuantos ejemplos de las formas en que las computadoras están mejorando las vidas de las personas
en la investigación, la industria y la sociedad.
Nombre Descripción
Registros de salud
electrónicos
Podrían incluir el historial médico de un paciente, prescripciones, vacunas, resultados
de laboratorio, alergias, información de seguros y demás. Al poner esa información a
disposición de los proveedores de servicios médicos a través de una red segura,
logramos mejorar el cuidado de los pacientes, se reduce la probabilidad de error y en
general, aumenta la eficiencia del sistema de servicios médicos.
Proyecto Genoma
Humano
El Proyecto Genoma Humano se fundó para identificar y analizar los más de 20 000
genes en el ADN humano. El proyecto utilizó programas de computadora para
analizar datos genéticos complejos, determinar las secuencias de los miles de millones
de pares base químicos que conforman el ADN humano y almacenar la información
en bases de datos disponibles a través de Internet para los investigadores en muchos
campos.
AMBER™ Alert El sistema de alerta AMBER (Niños norteamericanos extraviados: transmisión de
respuesta a emergencias) se utiliza para buscar niños secuestrados. Las autoridades
notifican a los funcionarios de transporte estatal y a las transmisoras de TV y radio,
quienes a su vez transmiten alertas en TV, radio, señales de tráfico computarizadas,
Internet y dispositivos inalámbricos. AMBER Alert se asoció hace poco con
Facebook, cuyos usuarios pueden “dar Like” a las páginas de AMBER Alert por
ubicación para recibir alertas en sus fuentes de noticias.
World Community
Grid
Personas de todo el mundo pueden donar su poder de procesamiento de cómputo que
no utilicen, mediante la instalación de un programa de software seguro gratuito
que permite a World Community Grid (www.worldcommunitygrid.org) aprovechar
la capacidad que no se utilice. Este poder de cómputo, al cual se accede a través de
Internet, se utiliza en lugar de costosas supercomputadoras para realizar proyectos
científicos de investigación que están haciendo la diferencia: ya sea proporcionar agua
potable a países del tercer mundo, combatir el cáncer, cultivar arroz más nutritivo para
regiones que combaten el hambre y otras cosas más.
Computación
en nube
La computación en nube nos permite usar software, hardware e información
almacenada en la “nube” (es decir, se accede desde computadoras remotas a través de
Internet y está disponible bajo demanda) en vez de tener que almacenarla en nuestra
computadora personal. Estos servicios, que le permiten aumentar o disminuir los
recursos para satisfacer sus necesidades en un momento dado, son por lo general más
efectivos en costos que comprar hardware costoso para asegurarse de tener el
suficiente almacenamiento y poder de procesamiento para satisfacer sus necesidades
en sus niveles máximos. Al usar los servicios de computación en nube traspasamos la
carga de tener que manejar estas aplicaciones de nuestra empresa al proveedor de
servicios, con lo cual ahorramos dinero.
Fig. 1.1  Unos cuantos usos para las computadoras (parte 1 de 3).
4 Capítulo 1 Introducción a las computadoras y a C++
Nombre Descripción
Imágenes para
diagnóstico médico
Las exploraciones por tomografía computarizada (CT) con rayos X, también
conocidas como CAT (tomografía axial computarizada), toman rayos X del cuerpo
desde cientos de ángulos distintos. Se utilizan computadoras para ajustar la
intensidad del rayo X, con lo cual se optimiza la exploración para cada tipo de tejido,
para después combinar toda la información y crear una imagen tridimensional (3D).
Los escáneres MRI usan una técnica conocida como imágenes de resonancia
magnética, también para producir imágenes internas de una manera no invasiva.
GPS Los dispositivos con Sistema de posicionamiento global (GPS) utilizan una red de
satélites para obtener información basada en la ubicación. Varios satélites envían
señales con etiquetas de tiempo al dispositivo GPS, el cual calcula la distancia hacia
cada satélite con base en la hora en que la señal salió del satélite y la hora en que se
recibió la señal. Esta información se utiliza para determinar la ubicación exacta del
dispositivo. Los dispositivos GPS pueden proveer indicaciones paso a paso y ayudarle
a localizar negocios cercanos (restaurantes, gasolineras, etc.) y puntos de interés. El
sistema GPS se utiliza en numerosos servicios de Internet basados en la ubicación,
como las aplicaciones de registro (check-in) en línea, para que usted pueda encon-
trar a sus amigos (por ejemplo, Foursquare y Facebook), en aplicaciones para hacer
ejercicio como RunKeeper, que rastrean el tiempo, la distancia y la velocidad
promedio de su rutina de trotar en exteriores, aplicaciones de citas que le ayudan a
buscar una pareja cercana y aplicaciones que actualizan en forma dinámica las
condiciones cambiantes del tráfico.
Robots Los robots se pueden utilizar para tareas diarias (por ejemplo, la aspiradora Roomba
de iRobot), de entretenimiento (como las mascotas robóticas), combate militar,
exploración espacial y en la profundidad del océano (como el trotamundos Curiosity
de la NASA), y otras más. RoboEarth (www.roboearth.org) es “una World Wide
Web para robots”. Permite a los robots aprender unos de otros mediante la
compartición de información, con lo cual mejoran sus habilidades para realizar
tareas, navegar, reconocer objetos y demás.
Correo electrónico,
mensajería
instantánea,
chat de video
y FTP
Los servidores basados en Internet soportan toda su mensajería en línea. Los mensajes
de correo electrónico pasan por un servidor de correo que también almacena esos
mensajes. Las aplicaciones de mensajería instantánea (IM) y chat de video, como AIM,
Skype, Yahoo! Messenger, GoogleTalk,Trillian, Microsoft Messenger y otras más, le
permiten comunicarse con otras personas en tiempo real, mediante el envío de
mensajes y video a través de los servidores. FTP (protocolo de transferencia de archivos)
le permite intercambiar archivos entre varias computadoras (por ejemplo, una
computadora cliente como su escritorio y un servidor de archivos) a través de Internet.
TV por Internet Los dispositivos de TV por Internet (como Apple TV, Google TV y TiVo) le
permiten acceder a una enorme cantidad de contenido bajo demanda, como juegos,
noticias, películas, programas de televisión, etcétera, además de que le ayudan a
asegurar que el contenido se transmita en flujo continuo hacia su TV sin problemas.
Servicios de
transmisión
de música por
flujo continuo
Los servicios de transmisión de música por flujo continuo (como Pandora, Spotify,
Last.fm y más) le permiten escuchar grandes catálogos de música a través de Web,
crear “estaciones de radio” personalizadas y descubrir nueva música con base en la
información que usted les retroalimente.
Fig. 1.1  Unos cuantos usos para las computadoras (parte 2 de 3).
1.3 Hardware y software 5
Nombre Descripción
Programación de
juegos
Los analistas esperan que los ingresos mundiales por juegos de video lleguen
a $91 mil millones en 2015 (www.vg247.com/2009/06/23/global-industry-
analysts-predicts-gaming-market-to-reach-91-billion-by-2015/). El desarrollo
de los juegos más sofisticados puede costar hasta $100 millones. Call of Duty: Black
Ops de Activision (uno de los juegos más vendidos de todos los tiempos) ¡obtuvo $360
millones en sólo un día (www.forbes.com/sites/insertcoin/2011/03/11/call-of-
duty-black-ops-now-the-best-selling-video-game-of-all-time/)! Los juegos
sociales en línea, que permiten a usuarios en todo el mundo competir entre sí a través
de Internet, están creciendo con rapidez. Zynga (creador de juegos en línea
populares, como Words with Friends, CityVille y otros) se fundó en 2007 y ya tiene
más de 300 millones de usuarios al mes. Para dar cabida al aumento en tráfico,
¡Zynga agregará cerca de 1000 servidores cada semana (techcrunch.
com/2010/09/22/zynga-moves-1-petabyte-of-data-daily-adds-1000-servers-a-
week/)!
Fig. 1.1  Unos cuantos usos para las computadoras (parte 3 de 3).
1.3Hardware y software
Las computadoras pueden realizar cálculos y tomar decisiones lógicas con una rapidez increíblemente
mayor que los humanos. Actualmente, muchas de las computadoras personales pueden realizar miles
de millones de cálculos en un segundo —más de lo que un humano podría realizar en toda su vida. ¡Las
supercomputadoras ya pueden realizar miles de billones de instrucciones por segundo! ¡La supercomputa-
dora Sequoia de IBM puede realizar más de 16 mil billones de cálculos por segundo (16.32 petaflops)!5
Dicho de otra forma, ¡la supercomputadora Sequoia de IBM puede realizar en un segundo alrededor de 1.5
millones de cálculos para cada uno de los habitantes del planeta! ¡Y estos “límites superiores” están aumen-
tando con rapidez!
Las computadoras procesan datos bajo el control de conjuntos de instrucciones conocidas como
programas de computadora. Estos programas guían a la computadora a través de conjuntos ordenados
de acciones especificadas por gente conocida como programadores de computadoras. A los programas
que se ejecutan en una computadora se les denomina software. En este libro aprenderá una metodolo-
gía de programación clave que mejora la productividad del programador, con lo cual se reducen los
costos de desarrollo del software: programación orientada a objetos.
Una computadora consiste en varios dispositivos conocidos como hardware (teclado, pantalla,
ratón, discos duros, memoria, unidades de DVD y unidades de procesamiento). Los costos de las
computadoras han disminuido en forma espectacular, debido a los rápidos desarrollos en las tecnolo-
gías de hardware y software. Las computadoras que ocupaban grandes habitaciones y que costaban
millones de dólares hace algunas décadas, ahora pueden colocarse en las superficies de chips de silicio
más pequeños que una uña, y con un costo de quizá unos cuantos dólares cada uno. Aunque suene
irónico, el silicio es uno de los materiales más abundantes en el planeta: es un ingrediente en la arena
común. La tecnología de los chips de silicio ha vuelto tan económica a la tecnología de la compu-
tación que en la actualidad las computadoras se han vuelto un producto básico.
5 www.top500.org/.
6 Capítulo 1 Introducción a las computadoras y a C++
1.3.1La Ley de Moore
Es probable que cada año espere pagar por lo menos un poco más por la mayoría de los productos y ser-
vicios. En el caso de los campos de las computadoras y las comunicaciones se ha dado lo opuesto, en es-
pecial con relación a los costos del hardware que da soporte a estas tecnologías. Los costos del hardware
han disminuido con rapidez durante varias décadas. Aproximadamente, cada uno o dos años, las capaci-
dades de las computadoras se duplican sin que el precio se incremente. Esta notable tendencia se conoce
en el ámbito común como la Ley de Moore, y debe su nombre a la persona que la identificó en la década
de 1960: Gordon Moore, cofundador de Intel —el principal fabricante de procesadores para las compu-
tadoras y los sistemas incrustados de la actualidad. La Ley de Moore y las observaciones relacionadas son
especialmente ciertas en cuanto a la cantidad de memoria que tienen las computadoras para los progra-
mas, la cantidad de almacenamiento secundario (como el almacenamiento en disco) que tienen para
guardar los programas y datos durante periodos extendidos, y las velocidades de sus procesadores —las
velocidades con que las computadoras ejecutan sus programas (es decir, realizan su trabajo). Se ha produ-
cido un crecimiento similar en el campo de las comunicaciones, en donde los costos se han desplomado
a medida que la enorme demanda por el ancho de banda de las comunicaciones (es decir, la capacidad de
transmisión de información) atrae una competencia intensa. No conocemos otros campos en los que la
tecnología mejore con tanta rapidez y los costos disminuyan de una manera tan drástica. Dicha mejora
fenomenal está fomentando sin duda la Revolución de la información.
1.3.2Organización de la computadora
Independientemente de las diferencias en su apariencia física, las computadoras pueden concebirse
como divididas en varias unidades lógicas o secciones (figura 1.2).
Unidad lógica Descripción
Unidad
de entrada
Esta sección “receptora” obtiene información (datos y programas de cómputo)
de los dispositivos de entrada y pone esta información a disposición de las otras
unidades para que pueda procesarse. La mayor parte de la información se
introduce en las computadoras a través de los teclados y ratones. Hay otras
formas de entrada: recibir comandos de voz, digitalizar imágenes y códigos de
barra, leer desde dispositivos de almacenamiento secundarios (como discos
duros, unidades de DVD, unidades de Blu-ray Disc™
y unidades flash USB),
recibir video de una cámara Web y hacer que su computadora reciba informa-
ción de Internet (como cuando transmite videos en flujo continuo desde
YouTube™
o descarga libros electrónicos de Amazon). Las formas más recientes
de entrada incluyen los datos de posición de un dispositivo GPS, la información de
movimiento y orientación de un acelerómetro en un Smartphone o controla-
dor de juegos (como Microsoft®
Kinect™
, Wii™
Remote y Move de Sony
PlayStation®
).
Unidad
de salida
Esta sección de “embarque” toma información que ya ha sido procesada por la
computadora y la coloca en varios dispositivos de salida, para que esté disponi-
ble fuera de la computadora. Hoy en día, la mayor parte de la información de
salida de las computadoras se despliega en pantallas, se imprime en papel (el
“movimiento ecológico” desaprueba esta opción), se reproduce como audio o
video en computadoras personales y reproductores de medios (como los
populares iPod de Apple) y pantallas gigantes en estadios deportivos, se transmite
a través de Internet o se usa para controlar otros dispositivos, como robots y
aparatos “inteligentes”.
Fig. 1.2  Unidades lógicas de una computadora (parte 1 de 2).
1.4 Jerarquía de datos 7
Unidad lógica Descripción
Unidad de memoria Esta sección de “almacén” de acceso rápido, pero con relativa baja capacidad,
retiene la información que se introduce a través de la unidad de entrada, para que
pueda estar disponible de manera inmediata para procesarla cuando sea
necesario. La unidad de memoria también retiene la información procesada hasta
que la unidad de salida pueda colocarla en los dispositivos de salida. La informa-
ción en la unidad de memoria es volátil: por lo general se pierde cuando se apaga
la computadora. Con frecuencia, a esta unidad de memoria se le llama memoria
o memoria primaria. Muchas memorias en computadoras de escritorio y tipo
notebook contienen comúnmente hasta 16 GB (esta medida significa gigabytes;
un gigabyte equivale aproximadamente mil millones de bytes).
Unidad aritmética y
lógica (ALU)
Esta sección de “manufactura” realiza cálculos como suma, resta, multiplicación
y división.También contiene los mecanismos de decisión que permiten a la
computadora hacer cosas como, por ejemplo, comparar dos elementos de
la unidad de memoria para determinar si son iguales o no. Por lo general, en los
sistemas actuales la ALU se implementa como parte de la siguiente unidad lógica,
la CPU.
Unidad central de
procesamiento
(CPU)
Ésta sección “administrativa” coordina y supervisa la operación de las demás
secciones. La CPU le indica a la unidad de entrada cuándo debe grabarse la
información dentro de la unidad de memoria, a la ALU cuándo debe utilizarse
la información de la unidad de memoria para los cálculos, y a la unidad de salida
cuándo enviar la información desde la unidad de memoria hasta ciertos
dispositivos de salida. Muchas de las computadoras actuales contienen múltiples
CPU y, por lo tanto, pueden realizar muchas operaciones de manera simultánea.
Un procesador multinúcleo implementa varios procesadores en un solo chip
de circuitos integrados; un procesador de doble núcleo (dual-core) tiene dos CPU
y un procesador de cuádruple núcleo (quad-core) tiene cuatro CPU. Las
computadoras de escritorio de la actualidad tienen procesadores que pueden
ejecutar miles de millones de instrucciones por segundo.
Unidad de
almacenamiento
secundario
Ésta es la sección de “almacén” de alta capacidad y de larga duración. Los
programas o datos que no utilizan las demás unidades con frecuencia se colocan
en dispositivos de almacenamiento secundario (por ejemplo, el disco duro) hasta
que se requieran de nuevo, lo cual puede llegar a ser horas, días, meses o incluso
años después. La información en los dispositivos de almacenamiento secundario
es persistente: se conserva aun y cuando se apaga la computadora. El tiempo para
acceder a la información en almacenamiento secundario es mucho mayor que
el necesario para acceder a la de la memoria principal, pero el costo por unidad
de memoria secundaria es mucho menor que el correspondiente a la unidad de
memoria principal. Las unidades de CD, DVD y Flash USB son ejemplos
de dispositivos de almacenamiento secundario, algunos de los cuales pueden
contener hasta 768 GB.Los discos duros típicos en las computadoras de
escritorio y portátiles pueden contener hasta 2 TB (TB se refiere a terabytes;
un terabyte equivale aproximadamente a un billón de bytes).
1.4Jerarquía de datos
Los elementos de datos que procesan las computadoras forman una jerarquía de datos que se vuelve
cada vez más grande y compleja en estructura, a medida que progresamos primero a bits, luego a carac-
teres, después a campos y así en lo sucesivo. La figura 1.3 ilustra una porción de la jerarquía de datos. La
figura 1.4 sintetiza los niveles de la jerarquía de datos.
Fig. 1.2  Unidades lógicas de una computadora (parte 2 de 2).
8 Capítulo 1 Introducción a las computadoras y a C++
Tom Azul
Sally Negro
Judy Verde Archivo
J u d y Campo
Byte (carácter J ASCII)
Registro
Iris Naranja
Randy Rojo
01001010
1 Bit
Judy Verde
Fig. 1.3  Jerarquía de datos.
Nivel Descripción
Bits El elemento de datos más pequeño en una computadora puede asumir el valor 0 o el valor 1.
A dicho elemento de datos se le denomina bit (abreviación de “dígito binario”: un dígito
que puede asumir uno de dos valores). Es notable que las impresionantes funciones que
realizan las computadoras sólo impliquen las manipulaciones más simples de 0s y 1s:
examinar el valor de un bit, establecer el valor de un bit e invertir el valor de un bit (de 1 a 0 o
de 0 a 1).
Caracteres Es tedioso para las personas trabajar con datos en el formato de bajo nivel de los bits. En
cambio, prefieren trabajar con dígitos decimales (0–9), letras (A–Z y a–z) y símbolos especiales
(por ejemplo, $, @, %, , *, (, ), –, +, “, :, ? y /). Los dígitos, letras y símbolos especiales se
conocen como caracteres. El conjunto de caracteres de la computadora es el conjunto de
todos los caracteres que se utilizan para escribir programas y representar elementos de datos.
Las computadoras sólo procesan 1s y 0s, por lo que cada carácter se representa como un patrón
de 1s y 0s. El conjunto de caracteres Unicode®
contiene caracteres para muchos de los
idiomas en el mundo. C++ soporta varios conjuntos de caracteres, incluyendo los caracteres
Unicode®
de 16 bits que están compuestos de dos bytes, cada uno de los cuales se compone a
su vez de ocho bits. En el apéndice B obtendrá más información sobre el conjunto de
caracteres ASCII (Código estándar estadounidense para el intercambio de información): el
popular subconjunto de Unicode que representa las letras mayúsculas y minúsculas, los dígitos
y algunos caracteres especiales comunes.
Campos Así como los caracteres están compuestos de bits, los campos están compuestos de caracteres o
bytes.Un campo es un grupo de caracteres o bytes que transmiten un significado. Por ejemplo,
un campo compuesto de letras mayúsculas y minúsculas se puede usar para representar el
nombre de una persona, y un campo compuesto de dígitos decimales podría representar la
edad de esa persona.
Fig. 1.4  Niveles de la jerarquía de datos (parte 1 de 2).
1.5 Lenguajes máquina, lenguajes ensambladores y lenguajes de alto nivel 9
Nivel Descripción
Registros Se pueden usar varios campos relacionados para componer un registro. Por ejemplo, en un
sistema de nómina, el registro de un empleado podría consistir en los siguientes campos (los
posibles tipos para estos campos se muestran entre paréntesis):
• Número de identificación del empleado (un número entero)
• Nombre (una cadena de caracteres)
• Dirección (una cadena de caracteres)
• Salario por horas (un número con punto decimal)
• Ingresos del año a la fecha (un número con punto decimal)
• Monto de impuestos retenidos (un número con punto decimal)
Así, un registro es un grupo de campos relacionados. En el ejemplo anterior, todos los campos
pertenecen al mismo empleado. Una compañía podría tener muchos empleados y un registro
de nómina para cada uno.
Archivos Un archivo es un grupo de registros relacionados. [Nota: dicho en forma más general, un
archivo contiene datos arbitrarios en formatos arbitrarios. En algunos sistemas operativos,
un archivo se ve tan sólo como una secuencia de bytes: cualquier organización de esos bytes,
como cuando se organizan los datos en registros, es una vista creada por el programador de la
aplicación]. Es muy común que una organización tenga muchos archivos, algunos de los
cuales pueden contener miles de millones, o incluso billones de caracteres de información.
Base de
datos
Una base de datos es una colección electrónica de datos organizada para facilitar su acceso y
manipulación. El modelo de base de datos más popular es la base de datos relacional, en la cual
los datos se almacenan en simples tablas. Una tabla incluye registros y campos. Por ejemplo, una
tabla de estudiantes podría incluir el primer nombre, apellido paterno, carrera, número de ID
de estudiante y promedio de calificaciones. Los datos para cada estudiante conforman un
registro y las piezas individuales de información en cada registro son los campos. Es posible
realizar búsquedas, ordenar y manipular los datos con base en su relación con varias tablas o
bases de datos. Por ejemplo, una universidad podría usar datos de la base de datos de
estudiantes en combinación con las bases de datos de los cursos, alojamiento en el campus,
planes de alimentación, etc.
1.5Lenguajes máquina, lenguajes ensambladores
y lenguajes de alto nivel
Los programadores escriben instrucciones en diversos lenguajes de programación, algunos de los cuales
los comprende directamente la computadora, mientras que otros requieren pasos intermedios de tra-
ducción.
Lenguajes máquina
Cualquier computadora puede entender de manera directa sólo su propio lenguaje máquina (también
conocido como código máquina), el cual se define según su diseño de hardware. Por lo general, los len-
guajes máquina consisten en cadenas de números (que finalmente se reducen a 1s y 0s). Dichos lengua-
jes son difíciles de comprender para los humanos.
Lenguajes ensambladores
La programación en lenguaje máquina era demasiado lenta y tediosa para la mayoría de los progra-
madores. Por lo tanto, empezaron a utilizar abreviaturas del inglés para representar las operaciones
Fig. 1.4  Niveles de la jerarquía de datos (parte 2 de 2).
10 Capítulo 1 Introducción a las computadoras y a C++
elementales. Estas abreviaturas formaron la base de los lenguajes ensambladores. Se desarrollaron
programas traductores conocidos como ensambladores para convertir los programas que se encontra-
ban en lenguaje ensamblador a lenguaje máquina. Aunque el código en lenguaje ensamblador es más
claro para los humanos, las computadoras no lo pueden entender sino hasta que se traduce en lengua-
je máquina.
Lenguajes de alto nivel
Para agilizar el proceso de programación se desarrollaron los lenguajes de alto nivel, en donde podían
escribirse instrucciones individuales para realizar tareas importantes. Los lenguajes de alto nivel,
como C++, Java, C# y Visual Basic nos permiten escribir instrucciones que son muy similares al inglés
y contienen expresiones matemáticas de uso común. Los programas traductores llamados compila-
dores convierten los programas que se encuentran en lenguaje de alto nivel a programas en lenguaje
máquina.
El proceso de compilación de un programa escrito en lenguaje de alto nivel a un lenguaje máqui-
na puede tardar un tiempo considerable en la computadora. Los programas intérpretes se desarrolla-
ron para ejecutar programas en lenguaje de alto nivel de manera directa (sin el retraso de la compila-
ción), aunque con más lentitud de la que se ejecutan los programas compilados. Los lenguajes de
secuencias de comandos, como los populares lenguajes JavaScript y PHP para Web, son procesados
por intérpretes.
Tip de rendimiento 1.1
Los intérpretes tienen una ventaja sobre los compiladores en las secuencias de comandos de
Internet. Un programa interpretado puede comenzar a ejecutarse tan pronto como se des-
carga en la máquina cliente, sin necesidad de compilarse antes de poder ejecutarse. Por otra
parte, las secuencias de comandos interpretadas generalmente se ejecutan con más lentitud
que el código compilado.
1.6C++
C++ evolucionó a partir de C, que fue desarrollado por Dennis Ritchie en los laboratorios Bell. C está
disponible para la mayoría de las computadoras y es independiente del hardware. Con un dise-
ño cuidadoso, es posible escribir programas en C que sean portables para la mayoría de las compu-
tadoras.
Por desgracia, el amplio uso de C con diversos tipos de computadoras (a las que algunas veces se
les denomina plataformas de hardware) produjo muchas variaciones. Era necesaria una versión estándar
de C. El Instituto nacional estadounidense de estándares (ANSI) cooperó con la Organización internacio-
nal para la estandarización (ISO) para estandarizar C a nivel mundial; el documento estándar colectivo se
publicó en 1990, y se conoce como ANSI/ISO 9899: 1990.
C11 es el estándar más reciente de ANSI para el lenguaje de programación C. Se desarrolló para que
C evolucionara y se mantuviera a la par con el hardware cada vez más poderoso y los requerimientos de
los usuarios, que cada vez son más exigentes. C11 también hace a C más consistente con C++. Para
obtener más información sobre C y C11, consulte nuestro libro C How to Program, séptima edición y
nuestro Centro de recursos de C (que se encuentra en www.deitel.com/C).
El lenguaje C++, que es una extensión de C, lo desarrolló Bjarne Stroustrup en 1979, en los labo-
ratorios Bell. Conocido en un principio como “C con clases”, se cambió su nombre a C++ a principios
de la década de 1980. C++ ofrece varias características que “pulen” al lenguaje C pero, lo más importan-
te es que proporciona las capacidades de una programación orientada a objetos.
El lector comenzará a desarrollar objetos y clases personalizadas reutilizables en el capítulo 3, Intro-
ducción a las clases, objetos y cadenas. El libro está orientado a objetos, en donde sea apropiado, desde
el principio y a lo largo del texto.
1.7 Lenguajes de programación 11
También proporcionamos un caso de estudio opcional sobre un cajero automático (ATM) en los
capítulos 25 y 26, el cual contiene una implementación completa en C++. El caso de estudio presenta
una introducción cuidadosamente pautada al diseño orientado a objetos mediante el UML: un lenguaje
de modelado gráfico estándar en la industria para desarrollar sistemas orientados a objetos. Lo guiamos
a través de una experiencia de diseño amigable enfocada hacia el principiante.
Biblioteca estándar de C++
Los programas en C++ consisten de piezas llamadas clases y funciones. Usted puede programar cada
pieza por su cuenta, pero la mayoría de los programadores de C++ aprovechan las extensas colecciones
de clases y funciones existentes en la Biblioteca estándar de C++. Por ende, en realidad hay dos partes
que debemos conocer en el “mundo” de C++. La primera es aprender acerca del lenguaje C++ en sí; la
segunda es aprender a utilizar las clases y funciones en la Biblioteca estándar de C++. A lo largo del libro,
hablaremos sobre muchas de estas clases y funciones. El libro de P. J. Plauger, titulado The Standard C
Library (Upper Saddle River, NJ: Prentice Hall PTR, 1992) es una lectura obligatoria para los progra-
madores que requieren una comprensión detallada de las funciones de la biblioteca de ANSI C que se
incluyen en C++. Hay muchas bibliotecas de clases de propósito especial que proporcionan los distri-
buidores de software independientes.
Observación de Ingeniería de Software 1.1
Utilice un método de “construcción en bloques” para crear programas. Evite reinventar la
rueda.Usepiezasexistentessiemprequeseaposible.Estaprácticadenominadareutilización
de software es el fundamento de la programación orientada a objetos.
Observación de Ingeniería de Software 1.2
Cuando programe en C++, generalmente utilizará los siguientes bloques de construcción:
clases y funciones de la Biblioteca estándar de C++, clases y funciones creadas por usted
mismo y sus colegas, y clases y funciones de varias bibliotecas populares desarrolladas por
terceros.
La ventaja de crear sus propias funciones y clases es que sabe exactamente cómo funcionan. Podrá
examinar el código de C++. La desventaja es el tiempo que consumen y el esfuerzo complejo que se
requiere para diseñar, desarrollar y dar mantenimiento a las nuevas funciones y clases que sean correc-
tas y operen con eficiencia.
Tip de rendimiento 1.2
Utilizar las funciones y clases de la Biblioteca estándar de C++ en vez de escribir sus propias
versiones puede mejorar el rendimiento de sus programas, ya que están escritas cuidadosa-
mente para funcionar de manera eficiente. Esta técnica también reduce el tiempo de desa-
rrollo de los programas.
Tip de portabilidad 1.1
Utilizar las funciones y clases de la Biblioteca estándar de C++ en vez de escribir sus propias
versiones mejora la portabilidad de sus programas, ya que estas funciones y clases se incluyen
en todas las implementaciones de C++.
1.7Lenguajes de programación
En esta sección proporcionaremos comentarios breves sobre varios lenguajes de programación popula-
res (figura 1.5).
12 Capítulo 1 Introducción a las computadoras y a C++
Lenguaje de
programación Descripción
Fortran Fortran (FORmula TRANslator, Traductor de fórmulas) fue
desarrollado por IBM Corporation a mediados de la década de 1950
para utilizarse en aplicaciones científicas y de ingeniería que requerían
cálculos matemáticos complejos. Aún se utiliza mucho y sus versiones
más recientes soportan la programación orientada a objetos.
COBOL COBOL (COmmon Business Oriented Language, Lenguaje común
orientado a negocios) fue desarrollado a finales de la década de 1950
por fabricantes de computadoras, el gobierno estadounidense y
usuarios de computadoras de la industria, con base en un lenguaje
desarrollado por Grace Hopper, un oficial de la Marina de Estados
Unidos y científico informático. COBOL aún se utiliza mucho en
aplicaciones comerciales que requieren de una manipulación precisa
y eficiente de grandes volúmenes de datos. Su versión más reciente
soporta la programación orientada a objetos.
Pascal La investigación en la década de 1960 dio como resultado la
programación estructurada: un método disciplinado para escribir
programas que sean más claros, fáciles de probar y depurar, y más
fáciles de modificar que los programas extensos producidos con
técnicas anteriores. Uno de los resultados más tangibles de esta
investigación fue el desarrollo de Pascal por el profesor Niklaus
Wirth en 1971. Se diseñó para la enseñanza de la programación
estructurada y fue popular en los cursos universitarios durante varias
décadas.
Ada Ada, un lenguaje basado en Pascal, se desarrolló bajo el patrocinio del
Departamento de Defensa (DOD) de Estados Unidos durante la
década de 1970 y a principios de la década de 1980. El DOD quería
un solo lenguaje que pudiera satisfacer la mayoría de sus necesidades.
El nombre de este lenguaje basado en Pascal es en honor de Lady Ada
Lovelace, hija del poeta Lord Byron. A ella se le atribuye el haber
escrito el primer programa para computadoras en el mundo, a
principios de la década de 1800 (para la Máquina Analítica, un
dispositivo de cómputo mecánico diseñado por Charles Babbage). Ada
también soporta la programación orientada a objetos.
Basic Basic se desarrolló en la década de 1960 en el Dartmouth College, para
que los principiantes se familiarizaran con las técnicas de
programación. Muchas de sus versiones más recientes son orientadas a
objetos.
C C fue implementado en 1972 por Dennis Ritchie en los laboratorios
Bell. En un principio se hizo muy popular como el lenguaje de desa-
rrollo del sistema operativo UNIX. En la actualidad, la mayoría del
código para los sistemas operativos de propósito general se escribe en
C o C++.
Objective-C Objective-C es un lenguaje orientado a objetos basado en C. Se
desarrolló a principios de la década de 1980 y después fue adquirido
por la empresa NeXT, que a su vez fue adquirida por Apple. Se ha
convertido en el lenguaje de programación clave para el sistema
operativo Mac OS X y todos los dispositivos operados por el iOS
(como los dispositivos iPod, iPhone e iPad).
Fig. 1.5  Algunos otros lenguajes de programación (parte 1 de 3).
1.7 Lenguajes de programación 13
Lenguaje de
programación Descripción
Java Sun Microsystems patrocinó en 1991 un proyecto de investigación
corporativo interno dirigido por James Gosling, que resultó en el
lenguaje de programación orientado a objetos basado en C++,
conocido como Java. Un objetivo clave de Java es poder escribir
programas que se ejecuten en una gran variedad de sistemas de
computadora y dispositivos para controlar computadoras. A esto se
le conoce algunas veces como “escribir una vez, ejecutar en donde
sea”. Java se utiliza para desarrollar aplicaciones empresariales a gran
escala, mejorar la funcionalidad de servidores Web (las computadoras
que proveen el contenido que vemos en nuestros navegadores Web),
proveer aplicaciones en dispositivos para el consumidor
(smartphones, tabletas, receptores digitales multimedia, aparatos,
automóviles y otros más) y para muchos otros propósitos. Java es
también el lenguaje clave para desarrollar aplicaciones Android para
smartphones y tabletas.
Visual Basic El lenguaje Visual Basic de Microsoft se introdujo a principios de la
década de 1990 para simplificar el desarrollo de aplicaciones para
Microsoft Windows. Sus versiones más recientes soportan la
programación orientada a objetos.
C# Los tres principales lenguajes de programación orientados a objetos de
Microsoft son Visual Basic (basado en el Basic original), Visual C++
(basado en C++) y C# (basado en C++ y Java; desarrollado para
integrar Internet y Web en las aplicaciones de computadora).
PHP PHP es un lenguaje orientado a objetos de “secuencias de comandos”
y “código fuente abierto” (vea la sección 1.11.2), el cual recibe
soporte por medio de una comunidad de usuarios y desarrolladores;
se utiliza en numerosos sitios Web, incluyendo Wikipedia y
Facebook. PHP es independiente de la plataforma —existen
implementaciones para todos los principales sistemas operativos
UNIX, Linux, Mac y Windows. PHP también soporta muchas bases
de datos, incluyendo MySQL.
Perl Perl (Lenguaje práctico para la extracción e informes), uno de los
lenguajes de secuencias de comandos orientados a objetos más
utilizados para la programación Web, fue desarrollado en 1987 por
Larry Wall. Cuenta con extensas herramientas de procesamiento de
texto y mucha flexibilidad.
Python Python, otro lenguaje de secuencias de comandos orientado a objetos,
se liberó al público en 1991. Fue desarrollado por Guido van Rossum
del Instituto Nacional de Investigación para las Matemáticas y
Ciencias Computacionales en Amsterdam (CWI); la mayor parte
de Python se basa en Modula-3 —un lenguaje de programación de
sistemas. Python es “extensible”: puede extenderse a través de clases
e interfaces de programación.
JavaScript JavaScript es el lenguaje de secuencias de comandos más utilizado en el
mundo. Su principal uso es para agregar capacidad de programación a
las páginas Web; por ejemplo, animaciones e interactividad con el
usuario. Se incluye en todos los principales navegadores Web.
Fig. 1.5  Algunos otros lenguajes de programación (parte 2 de 3).
14 Capítulo 1 Introducción a las computadoras y a C++
Lenguaje de
programación Descripción
Ruby on Rails Ruby fue creado a mediados de la década de 1990 por Yukihiro
Matsumoto; es un lenguaje de programación orientado a objetos de
código fuente abierto, con una sintaxis simple que es similar a Perl y
Python. Ruby onRails combina el lenguaje de secuencias de comandos
Ruby con el marco de trabajo de aplicaciones Web Rails, desarrollado
por 37Signals. Su libro, Getting Real (disponible sin costo en
gettingreal.37signals.com/toc.php), es una lectura obligatoria para
los desarrolladores Web. Muchos desarrolladores de Ruby onRails han
reportado ganancias de productividad superiores a las de otros
lenguajes, al utilizar aplicaciones Web que trabajan de manera
intensiva con bases de datos. Ruby on Rails se utilizó para crear
la interfaz de usuario de Twitter.
Scala Scala (www.scala-lang.org/node/273) abreviación en inglés de
“lenguaje escalable”, fue diseñado por Martin Odersky, un profesor en
la École Polytechnique Fédérale de Lausanne (EPFL) en Suiza. Se
lanzó al público en 2003; utiliza los paradigmas de orientación a
objetos y de programación funcional, y está diseñado para integrarse con
Java. Si programa en Scala, podrá reducir de manera considerable la
cantidad de código en sus aplicaciones. Twitter y Foursquare usan Scala.
Fig. 1.5  Algunos otros lenguajes de programación (parte 3 de 3).
1.8Introducción a la tecnología de objetos
Crear software en forma rápida, correcta y económica sigue siendo un objetivo difícil de alcanzar en una
época en que la demanda de software nuevo y más poderoso va en aumento.Los objetos, o dicho en
forma más precisa (como veremos en el capítulo 3), las clases de las que provienen los objetos, son
en esencia componentes de software reutilizables. Existen objetos de fecha, de hora, de audio, de video,
de automóviles, de personas, etc. Casi cualquier sustantivo se puede representar de manera razonable
como un objeto de software en términos de sus atributos (como el nombre, color y tamaño) y comporta-
mientos (por ejemplo, calcular, moverse y comunicarse). Los desarrolladores de software han descubier-
to que al usar una metodología de diseño e implementación orientada a objetos y modular, pueden crear
grupos de desarrollo de software más productivos de lo que era posible con las técnicas anteriores; por
lo general los programas orientados a objetos son más fáciles de comprender, corregir y modificar.
El automóvil como un objeto
Empecemos con una analogía simple. Suponga que desea conducir un automóvil y hacer que vaya más
rápido al oprimir el pedal del acelerador. ¿Qué debe ocurrir para que usted pueda hacer esto? Bueno,
antes de que pueda conducir un automóvil, alguien tiene que diseñarlo. Por lo general, un automóvil
empieza en forma de dibujos de ingeniería, similares a los planos de construcción que describen el diseño
de una casa. Estos dibujos de ingeniería incluyen el diseño del pedal del acelerador. El pedal oculta al
conductor los complejos mecanismos que se encargan de que el automóvil aumente su velocidad, de
igual forma que el pedal del freno oculta los mecanismos que disminuyen la velocidad del automóvil y
el volante oculta los mecanismos que hacen que el automóvil dé vuelta. Esto permite que las personas
con poco o nada de conocimiento acerca de cómo funcionan los motores, los frenos y los mecanismos
de la dirección puedan conducir un automóvil con facilidad.
1.8 Introducción a la tecnología de objetos 15
Antes de poder conducir un automóvil, éste debe construirse a partir de los dibujos de ingeniería que
lo describen. Un automóvil completo tendrá un pedal acelerador verdadero para hacer que aumente su
velocidad, pero aun así no es suficiente; el automóvil no acelerará por su propia cuenta (¡esperemos que así
sea!), así que el conductor debe oprimir el pedal del acelerador para aumentar la velocidad del automóvil.
Funciones miembro y clases
Ahora vamos a utilizar nuestro ejemplo del automóvil para introducir algunos conceptos clave de la
programación orientada a objetos. Para realizar una tarea en una aplicación se requiere una función
miembro. Esa función miembro aloja las instrucciones del programa que se encargan de realizar sus
tareas. Oculta al usuario estas tareas, de la misma forma que el pedal del acelerador de un automóvil
oculta al conductor los mecanismos para hacer que el automóvil vaya más rápido. En C++ creamos una
unidad de programa llamada clase para alojar el conjunto de funciones miembro que realizan las tareas
de esa clase. Por ejemplo, una clase que representa a una cuenta bancaria podría contener una función
miembro para depositar dinero en una cuenta, otra para retirar dinero de una cuenta y una tercera para
solicitar el saldo actual de la cuenta. Una clase es similar en concepto a los dibujos de ingeniería de un
automóvil, que contienen el diseño de un pedal acelerador, volante de dirección, etcétera.
Instanciamiento
Así como alguien tiene que construir un automóvil a partir de sus dibujos de ingeniería para que alguien
pueda conducirlo después, también es necesario crear un objeto de una clase para que un programa pue-
da realizar las tareas definidas por los métodos de esa clase. Al proceso de hacer esto se le denomina
instanciamiento. Entonces, un objeto viene siendo una instancia de su clase.
Reutilización
Así como los dibujos de ingeniería de un automóvil se pueden reutilizar muchas veces para construir
muchos automóviles, también es posible reutilizar una clase muchas veces para crear muchos objetos.
Al reutilizar las clases existentes para crear nuevas clases y programas, ahorramos tiempo y esfuerzo. La
reutilización también nos ayuda a crear sistemas más confiables y efectivos, debido a que con frecuen-
cia las clases y los componentes existentes pasan por un extenso proceso de prueba, depuración y opti-
mización del desempeño. De la misma manera en que la noción de piezas intercambiables fue crucial para
la Revolución Industrial, las clases reutilizables son cruciales para la revolución de software incitada
por la tecnología de objetos.
Mensajes y llamadas a funciones miembro
Cuando conduce un automóvil, al oprimir el pedal del acelerador envía un mensaje al automóvil para
que realice una tarea: aumentar la velocidad. De manera similar, es posible enviar mensajes a un objeto.
Cada mensaje se implementa como llamada a función miembro, para indicar a una función miembro
del objeto que realice su tarea. Por ejemplo, un programa podría llamar al método depositar de un obje-
to cuenta de banco específico para incrementar el saldo de esa cuenta.
Atributos y miembros de datos
Además de tener capacidades para realizar tareas, un automóvil también tiene atributos: color, número
de puertas, cantidad de gasolina en el tanque, velocidad actual y registro del total de kilómetros recorri-
dos (es decir, la lectura de su velocímetro). Al igual que sus capacidades, los atributos del automóvil se
representan como parte de su diseño en sus diagramas de ingeniería (que, por ejemplo, incluyen un
velocímetro y un indicador de combustible). Al conducir un automóvil real, estos atributos se llevan
junto con el automóvil. Cada automóvil mantiene sus propios atributos. Por ejemplo, cada uno sabe
cuánta gasolina hay en su tanque, pero no cuánta hay en los tanques de otros automóviles.
16 Capítulo 1 Introducción a las computadoras y a C++
De manera similar, un objeto tiene atributos que lleva consigo a medida que se utiliza en un progra-
ma. Estos atributos se especifican como parte de la clase del objeto. Por ejemplo, un objeto cuenta ban-
caria tiene un atributo saldo que representa la cantidad de dinero en la cuenta. Cada objeto cuenta banca-
ria conoce el saldo de la cuenta que representa, pero no los saldos de las otras cuentas en el banco. Los
atributos se especifican mediante los miembros de datos de la clase.
Encapsulamiento
Las clases encapsulan (envuelven) los atributos y las funciones miembro en objetos; los atributos y las
funciones miembro de un objeto están muy relacionados entre sí. Los objetos se pueden comunicar
entre sí, pero por lo general no se les permite saber cómo están implementados otros objetos; los detalles
de implementación están ocultos dentro de los mismos objetos. Este ocultamiento de información es,
como veremos, crucial para la buena ingeniería de software.
Herencia
Es posible crear una nueva clase de objetos con rapidez y de manera conveniente mediante la heren-
cia: la nueva clase absorbe las características de una clase existente, con la posibilidad de personalizar-
las y agregar características únicas propias. En nuestra analogía del automóvil, sin duda un objeto
de la clase “convertible” es un objeto de la clase más general llamada “automóvil” pero, de manera más
específica, el techo puede ponerse o quitarse.
Análisis y diseño orientado a objetos (A/DOO)
Pronto escribirá programas en C++. ¿Cómo creará el código (es decir, las instrucciones) para sus
programas? Tal vez, al igual que muchos programadores, sólo encenderá su computadora y empezará
a escribir. Quizás este método funcione para pequeños programas (como los que presentamos en los
primeros capítulos del libro), pero ¿qué tal si le pidieran crear un sistema de software para controlar
miles de cajeros automáticos para un banco importante? O ¿qué tal si le piden que trabaje con un
equipo de miles de desarrolladores de software para crear el nuevo sistema de control de tráfico aéreo
en Estados Unidos? Para proyectos tan grandes y complejos, no es conveniente tan sólo sentarse
y empezar a escribir programas.
Para crear las mejores soluciones, debe seguir un proceso de análisis detallado para determinar los
requerimientos de su proyecto (definir qué se supone que debe hacer el sistema) y desarrollar un diseño
que los satisfaga (decidir cómo debe hacerlo el sistema). Lo ideal sería pasar por este proceso y revisar el
diseño con cuidado (además de pedir a otros profesionales de software que revisen su diseño) antes de
escribir cualquier código. Si este proceso implica analizar y diseñar su sistema desde un punto de vista
orientado a objetos, se denomina proceso de análisis y diseño orientado a objetos (A/DOO). Los
lenguajes como C++ son orientados a objetos. La programación en un lenguaje de este tipo, conocida
como programación orientada a objetos (POO), le permite implementar un diseño orientado a ob-
jetos como un sistema funcional.
El UML (Lenguaje Unificado de Modelado)
Aunque existen muchos procesos de A/DOO distintos, hay un solo lenguaje gráfico para comunicar los
resultados de cualquier proceso de A/DOO que se utiliza en la mayoría de los casos. Este lenguaje, co-
nocido como Lenguaje Unificado de Modelado (UML), es en la actualidad el esquema gráfico más
utilizado para modelar sistemas orientados a objetos. Presentamos nuestros primeros diagramas de
UML en los capítulos 3 y 4; después los utilizamos en nuestro análisis más detallado de la programación
orientada a objetos en el capítulo 12. En nuestro caso de estudio opcional de ingeniería de software del
ATM en los capítulos 25 y 26 presentamos un subconjunto simple de las características del UML,
mientras lo guiamos por una experiencia de diseño orientada a objetos.
1.9 Entorno de desarrollo común en C++ 17
1.9Entorno de desarrollo común en C++
Por lo general, los sistemas de C++ consisten en tres partes: un entorno de desarrollo de programas, el
lenguaje y la Biblioteca estándar de C++. Comúnmente, los programas en C++ pasan a través de seis
fases: edición, preprocesamiento, compilación, enlace, carga y ejecución. A continuación explicaremos
un típico entorno de desarrollo de programas en C++.
Fase 1: Edición (Creación) de un programa
La fase 1 consiste en editar un archivo con un programa de edición, conocido comúnmente como editor
(figura1.6).UstedescribeunprogramaenC++(conocidoporlogeneralcomocódigofuente)utilizando
el editor, realiza las correcciones necesarias y guarda el programa en un dispositivo de almacenamiento
secundario, tal como su disco duro. A menudo, los nombres de archivos de código fuente en C++
terminan con las extensiones .cpp, .cxx, .cc o .C (observe que C está en mayúsculas), para indicar que
un archivo contiene código fuente en C++. Consulte la documentación para su compilador de C++ si
desea obtener más información acerca de las extensiones de nombres de archivos.
Disco
Editor
Fase 1:
El programador crea el
programa en el editor y
lo almacena en disco.
Fig. 1.6  Entorno de desarrollo común en C++: fase de edición.
Dos de los editores que se utilizan ampliamente en sistemas Linux son vi y emacs. Los paquetes de
software de C++ para Microsoft Windows tales como Microsoft Visual C++ (microsoft.com/express
eninglés,ywww.microsoft.com/visualstudio/esn#products/visual-studio-express-products en
español) tienen editores integrados en el entorno de programación.También puede utilizar un editor de
texto simple, como el Bloc de notas en Windows, para escribir su código de C++.
Para las organizaciones que desarrollan sistemas de información de un tamaño considerable, hay
entornos integrados de desarrollo (IDE) disponibles de muchos de los principales proveedores de
software. Los IDE ofrecen herramientas para apoyar el proceso de desarrollo de software, incluyendo
editores para escribir y editar programas, y depuradores para localizar errores lógicos: errores que pro-
vocan que los programas se ejecuten en forma incorrecta. Los IDE populares son: Microsoft®
Visual
Studio 2012 Express Edition, Dev C++, NetBeans, Eclipse, Xcode de Apple y CodeLite.
Fase 2: Preprocesamiento de un programa en C++
En la fase 2, se introduce el comando para compilar el programa (figura 1.7). En un sistema de C++,
unprogramapreprocesadorseejecutademaneraautomáticaantesdequeempiecelafasedetraducción
del compilador (por lo que a la fase 2 la llamamos preprocesamiento, y a la fase 3 la llamamos compila-
ción). El preprocesador de C++ obedece a comandos denominados directivas del preprocesador, las
cuales indican que deben realizarse ciertas manipulaciones en el programa, antes de compilarlo. Por lo
general, estas manipulaciones incluyen la compilación de otros archivos de texto y realizan varios reem-
plazos de texto. Las directivas más comunes del preprocesador se describen en los primeros capítulos; en
el apéndice E, Preprocesador, aparece una discusión detallada acerca de las características del preproce-
sador.
Disco
Preprocesador
Fase 2:
El programa preprocesador
procesa el código.
Fig. 1.7  Entorno de desarrollo común en C++: fase del preprocesador.
18 Capítulo 1 Introducción a las computadoras y a C++
Fase 3: Compilación de un programa de C++
En la fase 3, el compilador traduce el programa de C++ en código de lenguaje máquina (también cono-
cido como código objeto) (figura 1.8).
Disco
Compilador
Fase 3:
El compilador crea
el código objeto y
lo almacena en disco.
Fig. 1.8  Entorno de desarrollo común en C++: fase de compilación.
Fase 4: Enlace
Alafase4selellamaenlace.Porlogeneral,losprogramasenC++contienenreferenciasafuncionesydatos
definidos en otra parte, como en las bibliotecas estándar o en las bibliotecas privadas de grupos de
programadores que trabajan sobre un proyecto específico (figura 1.9). El código objeto producido por el
compilador de C++ comúnmente contiene “huecos”, debido a estas partes faltantes. Un enlazador
relaciona el código objeto con el código para las funciones faltantes, de manera que se produzca un
programa ejecutable (sin piezas faltantes). Si el programa se compila y se enlaza de manera correcta, se
produce una imagen ejecutable.
Disco
Enlazador
Fase 4:
El enlazador relaciona el código
objeto con las bibliotecas, crea
un archivo ejecutable y lo
almacena en disco.
Fig. 1.9  Entorno de desarrollo común en C++: fase de enlace.
Fase 5: Carga
A la fase 5 se le conoce como carga. Antes de poder ejecutar un programa, primero se debe colocar en
la memoria (figura 1.10). Esto se hace mediante el cargador, que toma la imagen ejecutable del disco y la
transfiere a la memoria.También se cargan los componentes adicionales de bibliotecas compartidas que
dan soporte al programa.
Disco
Cargador
Fase 5:
El cargador coloca el
programa en la memoria.
...
Memoria
principal
...
Fig. 1.10  Entorno de desarrollo común en C++: fase de carga.
1.10 Prueba de una aplicación de C++ 19
Fase 6: Ejecución
Por último, la computadora, bajo el control de su CPU, ejecuta el programa una instrucción a la vez
(figura 1.11). Algunas arquitecturas de cómputo modernas pueden ejecutar varias instrucciones en
paralelo.
CPU
Fase 6:
La CPU toma cada
instrucción y la ejecuta;
posiblemente almacena
nuevos valores de datos
a medida que se ejecuta
el programa.
...
Memoria
principal
...
Fig. 1.11  Entorno de desarrollo común en C++: fase de ejecución.
Problemas que pueden ocurrir en tiempo de ejecución
Esprobable que losprogramasnofuncionen la primera vez. Cada una de las fases anteriores puede fallar,
debido a diversos errores que describiremos en este texto. Por ejemplo, un programa en ejecución podría
intentar una división entre cero (una operación ilegal para la aritmética con números enteros en C++).
Esto haría que el programa de C++ mostrara un mensaje de error. Si esto ocurre, tendría que regresar a
la fase de edición, hacer las correcciones necesarias y proseguir con las fases restantes de nuevo, para
determinar que las correcciones hayan resuelto el(los) problema(s). [Nota: la mayoría de los programas
en C++ reciben y/o producen datos. Ciertas funciones de C++ toman su entrada de cin (el flujo están-
dar de entrada; se pronuncia “c-in”), que por lo general es el teclado, pero cin puede redirigirse a otro
dispositivo. A menudo los datos se envían a cout (el flujo estándar de salida), que por lo general es la
pantalla de la computadora, pero cout puede redirigirse a otro dispositivo. Cuando decimos que
un programa imprime un resultado, por lo general nos referimos a que el resultado se despliega en una
pantalla. Los datos pueden enviarse a otros dispositivos, como los discos y las impresoras.También hay
un flujo estándar de error, conocido como cerr. El flujo cerr (que por lo general se conecta a la pan-
talla) se utiliza para mostrar mensajes de error.
Error común de programación 1.1
Los errores, como la división entre cero, ocurren a medida que se ejecuta un programa, por
lo cual se les llama errores en tiempo de ejecución. Los errores fatales en tiempo de
ejecución hacen que los programas terminen inmediatamente, sin haber realizado correc-
tamente su trabajo. Los errores no fatales en tiempo de ejecución permiten a los programas
ejecutarse hasta terminar su trabajo, lo que a menudo produce resultados incorrectos.
1.10Prueba de una aplicación de C++
En esta sección, ejecutará su primera aplicación en C++ e interactuará con ella. Empezará ejecutando
un divertido juego de “adivinar el número”, el cual elije un número del 1 al 1000 y le pide que lo adivi-
ne. Si su elección es la correcta, el juego termina. Si no es correcta, la aplicación le indica si su elección
20 Capítulo 1 Introducción a las computadoras y a C++
es mayor o menor que el número correcto. No hay límite en cuanto al número de intentos que puede
realizar[Nota:sóloparaestapruebahemosmodificadoestaaplicacióndelejercicioquelepediremosque
cree en el capítulo 6, Funciones y una introducción a la recursividad. Por lo general, esta aplicación se-
lecciona al azar la respuesta correcta cuando usted ejecuta el programa. La aplicación modificada utiliza
la misma respuesta correcta cada vez que el programa se ejecuta (aunque esto podría variar según el
compilador), por lo que puede utilizar las mismas elecciones que utilizamos en esta sección, y verá los
mismos resultados a medida que interactúe con su primera aplicación en C++].
Demostraremos cómo ejecutar una aplicación de C++ mediante el Símbolo del sistema de
Windows y mediante un shell en Linux. La aplicación se ejecuta de manera similar en ambas pla-
taformas. Hay muchos entornos de desarrollo disponibles en los cuales los lectores pueden compi-
lar, generar y ejecutar aplicaciones de C++, como GNU C++, Microsoft Visual C++, Apple Xcode,
Dev C++, CodeLite, NetBeans, Eclipse, etc. Consulte con su instructor para obtener información
acerca de su entorno de desarrollo específico.
En los siguientes pasos, ejecutará la aplicación y escribirá varios números para adivinar el nú-
mero correcto. Los elementos y la funcionalidad que puede ver en esta aplicación son típicos de los
que aprenderá a programar en este libro. A lo largo del mismo, utilizamos distintos tipos de letra
para diferenciar entre las características que se pueden ver en la pantalla (como el Símbolo del siste-
ma) y los elementos que no están directamente relacionados con la pantalla. Enfatizamos las carac-
terísticas de la pantalla, como los títulos y los menús (como el menú Archivo menu) en un tipo de
letra Helvetica sans-serif en semi-negritas, y enfatizamos los nombres de archivo, el texto desplegado
por una aplicación y los valores que debe introducir en una aplicación (como AdivinarNumero o
500) en un tipo de letra Lucida sans-serif. Como tal vez ya se ha dado cuenta, la ocurrencia de
definición de cada término se establece en negritas. En las figuras en esta sección, señalamos las
partes importantes de la aplicación. Para aumentar la visibilidad de estas características, modifica-
mos el color de fondo de la ventana del Símbolo del sistema (sólo para la prueba en Windows). Para
modificar los colores del Símbolo del sistema en su sistema, abra una ventana Símbolo del sistema
seleccionando Inicio  Todos los programas  Accesorios  Símbolo del sistema, después haga clic con
el botón derecho del ratón en la barra de título y seleccione Propiedades. En el cuadro de diálogo
Propiedades de Símbolo del sistema que aparezca, haga clic en la ficha Colores y seleccione sus
colores de texto y fondo preferidos.
Ejecución de una aplicación de C++ desde el Símbolo del sistema de Windows
1. Revise su configuración. Es importante que lea la sección Antes de empezar en www.deitel.
com/books/cpphtp9/ para confirmar que haya copiado correctamente los ejemplos del libro en
su disco duro.
2. Localice la aplicación completa. Abra una ventana Símbolo del sistema window. Para cam-
biar al directorio de la aplicación completa AdivinarNumero, escriba cd C:ejemploscap01
AdivinarNumeroWindows y después oprima Intro (figura 1.12). El comando cd se utiliza para
cambiar de directorio.
Fig. 1.12  Abrir una ventana Símbolo del sistema y cambiar de directorio.
1.10 Prueba de una aplicación de C++ 21
3. Ejecute la aplicación AdivinarNumero. Ahora que se encuentra en el directorio que contiene la
aplicación AdivinarNumero escriba el comando AdivinarNumero (figura 1.13) y oprima Intro.
[Nota: AdivinarNumero.exe es el nombre real de la aplicación; sin embargo, Windows asume
la extensión .exe de manera predeterminada].
Fig. 1.13  Ejecución de la aplicación AdivinarNumero.
4. Escriba su primer intento. La aplicación muestra el mensaje Escriba su primer intento.
y después muestra un signo de interrogación (?) como un indicador en la siguiente línea (figu-
ra 1.13). En el indicador, escriba 200 (figura 1.14).
Fig. 1.14  Escriba su primer intento.
5. Intente de nuevo. La aplicación muestra Demasiado alto. Intente de nuevo. lo cual
significa que el valor que escribió es mayor que el número que eligió la aplicación como
la respuesta correcta. Por lo tanto, debe escribir un número menor como su siguiente in-
tento. En el indicador, escriba 100 (figura 1.15). La aplicación muestra de nuevo el men-
saje Demasiado alto. Intente de nuevo., ya que el valor que escribió sigue siendo
mayor que el número que eligió la aplicación como la respuesta correcta.
Fig. 1.15  Intente nuevamente y reciba retroalimentación.
6. Escriba más elecciones. Continúe el juego, escribiendo valores hasta que adivine el número
correcto. La aplicación mostrará el mensaje Excelente! Adivino el numero! (figura1.16).
22 Capítulo 1 Introducción a las computadoras y a C++
Fig. 1.16  Escriba más elecciones y adivine el número correcto.
7. Juegue de nuevo o salga de la aplicación. Una vez que adivine la respuesta correcta, la
aplicación le preguntará si desea jugar otra vez (figura 1.16). En el indicador Le gustaria
jugar de nuevo (s o n)? al escribir el carácter s la aplicación elegirá un nuevo número y
mostrará el mensaje Escriba su primer intento. seguido de un signo de interrogación
como indicador (figura 1.17), de manera que pueda escribir su primer intento en el nuevo
juego. Al escribir el carácter n la aplicación termina y el sistema nos regresa al directorio de
la aplicación en el Símbolo del sistema (figura 1.18). Cada vez que ejecute esta aplicación
desde el principio (es decir, desde el paso 3), elegirá los mismos números para que usted
adivine.
8. Cierre la ventana Símbolo del sistema.
Fig. 1.17  Juegue de nuevo.
Fig. 1.18  Salga del juego.
1.10 Prueba de una aplicación de C++ 23
Ejecución de una aplicación de C++ mediante GNU C++ con Linux
Para esta prueba, vamos a suponer que usted sabe cómo copiar los ejemplos en su directorio de inicio.
Consulte con su instructor si tiene dudas acerca de cómo copiar los archivos en su sistema Linux. Ade-
más, para las figuras en esta sección utilizamos texto resaltado en negritas para indicar la entrada del
usuariorequeridaencadapaso.Elindicadorenelshellennuestrosistemautilizaelcaráctertilde(~)para
representar el directorio de inicio, y cada indicador termina con el carácter de signo de dólar ($). El in-
dicador puede variar de un sistema Linux a otro.
1. Localice la aplicación completa. Desde un shell en Linux, cambie al directorio de la aplica-
ción AdivinarNumero completa (figura 1.19); para ello escriba
cd Ejemplos/cap01/AdivinarNumero/GNU_Linux
y oprima Intro. El comando cd se utiliza para cambiar de directorio.
~$ cd ejemplos/cap01/AdivinarNumero/GNU_Linux
~/ejemplos/cap01/AdivinarNumero/GNU_Linux$
Fig. 1.19  Cambie al directorio de la aplicación AdivinarNumero.
2. Compile la aplicación AdivinarNumero. Para ejecutar una aplicación en el compilador GNU
C++, primero debe compilarla; para ello escriba
g++ AdivinarNumero.cpp –o AdivinarNumero
como en la figura 1.20. Este comando compila la aplicación y produce un archivo ejecutable,
llamado AdivinarNumero.
~/ejemplos/cap01/AdivinarNumero/GNU_Linux$ g++ AdivinarNumero.cpp –o Adivinar-
Numero
~/ejemplos/cap01/AdivinarNumero/GNU_Linux$
Fig. 1.20  Compile la aplicación AdivinarNumero usando el comando g++.
3. Ejecute la aplicación AdivinarNumero. Para ejecutar el archivo AdivinarNumero, escriba
./AdivinarNumero en el siguiente indicador y luego oprima Intro (figura 1.21).
~/ejemplos/cap01/AdivinarNumero/GNU_Linux$ ./AdivinarNumero
Tengo un numero entre 1 y 1000.
Puede adivinar mi numero?
Escriba su primer intento.
?
Fig. 1.21  Ejecución de la aplicacióne AdivinarNumero.
4. Escribasuprimerintento.LaaplicaciónmuestraelmensajeEscriba su primer intento.,
y después muestra un signo de interrogación (?) como un indicador en la siguiente línea (fi-
gura 1.21). En el indicador, escriba 100 (figura 1.22). [Nota: ésta es la misma aplicación que
modificamos y probamos para Windows, pero los resultados podrían variar, dependiendo del
compilador que se utilice].
24 Capítulo 1 Introducción a las computadoras y a C++
5. Intente de nuevo. La aplicación muestra Demasiado alto. Intente de nuevo., lo cual
significa que el valor que escribió es mayor que el número que eligió la aplicación como
la respuesta correcta (figura 1.22). En el siguiente indicador, escriba 30 (figura 1.23). Esta
vez la aplicación muestra el mensaje Demasiado bajo. Intente de nuevo., ya que el
valor que escribió es menor que el número que la respuesta correcta.
6. Escriba más elecciones. Continúe el juego (figura 1.24), escribiendo valores hasta que adivi-
ne el número correcto. Cuando adivine la respuesta correcta, la aplicación mostrará el mensa-
je Excelente! Adivino el numero.
~/ejemplos/cap01/AdivinarNumero/GNU_Linux$ ./AdivinarNumero
Tengo un numero entre 1 y 1000.
Puede adivinar mi numero?
Escriba su primer intento.
? 100
Demasiado alto. Intente de nuevo.
?
Fig. 1.22  Escriba su elección inicial.
~/ejemplos/cap01/AdivinarNumero/GNU_Linux$ ./AdivinarNumero
Tengo un numero entre 1 y 1000.
Puede adivinar mi numero?
Escriba su primer intento.
? 100
Demasiado alto. Intente de nuevo.
? 30
Demasiado bajo. Intente de nuevo.
?
Fig. 1.23  Intente de nuevo y reciba retroalimentación.
Demasiado bajo. Intente de nuevo.
? 40
Demasiado bajo. Intente de nuevo.
? 60
Demasiado alto. Intente de nuevo.
? 50
Demasiado alto. Intente de nuevo.
? 45
Demasiado alto. Intente de nuevo.
? 41
Demasiado bajo. Intente de nuevo.
? 44
Demasiado alto. Intente de nuevo.
? 43
Demasiado alto. Intente de nuevo.
? 42
Excelente! Adivino el numero!
Le gustaria jugar de nuevo (s o n)?
Fig. 1.24  Escriba más elecciones y adivine el número correcto.
1.11 Sistemas operativos 25
7. Juegue de nuevo o salga de la aplicación. Una vez que adivine la respuesta correcta, la
aplicación le preguntará si desea jugar otra vez. En el indicador Le gustaria jugar de nuevo
(s o n)? al escribir el carácter s la aplicación elegirá un nuevo número y mostrará el mensa-
je “Escriba su primer intento.” seguido de un signo de interrogación como indicador (fi-
gura 1.25), de manera que pueda escribir su primera elección en el nuevo juego. Al escribir el
carácter n la aplicación termina y el sistema nos regresa al directorio de la aplicación en el shell
(figura 1.26). Cada vez que ejecute esta aplicación desde el principio (es decir, desde el paso 3),
elegirá los mismos números para que usted adivine.
Excelente! Adivino el numero!
Le gustaria jugar de nuevo (s o n)? s
Tengo un numero entre 1 y 1000.
Puede adivinar mi numero?
Escriba su primer intento.
?
Fig. 1.25  Juegue de nuevo.
Excelente! Adivino el numero!
Le gustaria jugar de nuevo (s o n)? n
~/ejemplos/cap01/AdivinarNumero/GNU_Linux$
Fig. 1.26  Salga del juego.
1.11Sistemas operativos
Los sistemas operativos son sistemas de software que se encargan de hacer más conveniente el uso de
las computadoras para los usuarios, desarrolladores de aplicaciones y administradores de sistemas. Pro-
veen servicios que permiten a cada aplicación ejecutarse en forma segura, eficiente y concurrente (es
decir, en paralelo) con otras aplicaciones. El software que contiene los componentes básicos del sistema
operativo se denomina kernel. Los sistemas operativos de escritorio populares son: Linux, Windows y
OS X (conocido anteriormente como Mac OS X); utilizamos estos tres en el desarrollo del libro. Los
sistemasoperativosmóvilespopularesqueseutilizanensmartphonesytabletasson:AndroiddeGoogle,
Apple iOS (para sus dispositivos iPhone, iPad e iPod Touch), BlackBerry OS y Windows Phone. Es
posible desarrollar aplicaciones en C++ para los siguientes sistemas operativos clave, incluyendo varios
de los más recientes sistemas operativos móviles.
1.11.1Windows: un sistema operativo propietario
A mediados de la década de 1980 Microsoft desarrolló el sistema operativo Windows, el cual consiste
en una interfaz gráfica de usuario creada sobre DOS: un sistema operativo de computadora personal
muy popular con el que los usuarios interactuaban al teclear comandos. Windows tomó prestados mu-
chos conceptos (como los iconos, menús y ventanas) desarrollados en un principio por Xerox PARC y
que se hicieron populares gracias a los primeros sistemas operativos Apple Macintosh. Windows 8 es el
26 Capítulo 1 Introducción a las computadoras y a C++
sistema operativo más reciente de Microsoft; sus características incluyen mejoras en la interfaz de usua-
rio, un arranque más veloz, un mayor grado de refinamiento en cuanto a las características de seguridad,
soporte para pantalla táctil y multitáctil, y otras cosas más.Windows es un sistema operativo propietario;
está bajo el control exclusivo de Microsoft. Windows es por mucho el sistema operativo de escritorio
más utilizado en el mundo.
1.11.2Linux: un sistema operativo de código fuente abierto
El sistema operativo Linux es tal vez el más grande éxito del movimiento de código fuente abierto. El
software de código fuente abierto se desvía del estilo de desarrollo de software propietario, el cual
predominó durante los primeros años del software. Con el desarrollo de código fuente abierto, indivi-
duos y compañías contribuyen sus esfuerzos para desarrollar, mantener y evolucionar el software a cam-
bio del derecho de usarlo para sus propios fines, por lo general sin costo. A menudo el código fuente
abierto es escudriñado por una audiencia mucho mayor que la del software propietario, de modo que
casi siempre los errores se eliminan con más rapidez. El código fuente abierto también fomenta una
mayor innovación. Las compañías de sistemas empresariales como IBM, Oracle y muchas otras, han
realizado inversiones considerables en el desarrollo del código fuente abierto de Linux.
Algunas organizaciones clave en la comunidad de código fuente abierto son: la fundación Eclipse
(el Entorno integrado de desarrollo Eclipse ayuda a los programadores a desarrollar software de manera
conveniente), la fundación Mozilla (creadores del navegador Web Firefox), la fundación de software
Apache (creadores del servidor Web Apache que se utiliza para desarrollar aplicaciones basadas en Web)
y SourceForge (quien proporciona las herramientas para administrar proyectos de código fuente abier-
to; cientos de miles de estos proyectos en desarrollo). Las rápidas mejoras en la computación y las comu-
nicaciones, la reducción en costos y el software de código fuente abierto han logrado que sea mucho más
fácil y económico crear un negocio basado en software en la actualidad de lo que era hace tan sólo una
década. Facebook es un gran ejemplo de ello; este sitio se inició desde un dormitorio universitario y se
creó con software de código fuente abierto.
El kernel de Linux es el núcleo del sistema operativo de código fuente abierto más popular y
lleno de funcionalidades, que se distribuye en forma gratuita. Es desarrollado por un equipo de vo-
luntarios organizados de manera informal; es popular en servidores, computadoras personales y sis-
temas incrustados. A diferencia de los sistemas operativos propietarios como Microsoft Windows y
Apple OS X, el código fuente de Linux (el código del programa) está disponible al público para que
lo examinen y modifiquen; además se puede descargar e instalar sin costo. Como resultado, los usua-
rios de Linux se benefician de una comunidad de desarrolladores que depuran y mejoran el kernel de
manera continua, y de la habilidad de poder personalizar el sistema operativo para cumplir necesida-
des específicas.
Son varias cuestiones —el poder de mercado de Microsoft, el pequeño número de aplicaciones
Linux amigables para los usuarios y la diversidad de distribuciones de Linux, tales como Red Hat Linux,
Ubuntu Linux y muchas más— las que han impedido que se popularice el uso de Linux en las compu-
tadoras de escritorio. Pero este sistema operativo se ha vuelto muy popular en servidores y sistemas in-
crustados, como los teléfonos inteligentes basados en Android de Google.
1.11.3OS X de Apple: iOS de Apple para dispositivos
iPhone®, iPad® y iPod Touch®
SteveJobsySteveWozniakfundaronAppleen1976,queseconvirtiórápidamenteenlíderenlacompu-
tación personal. En 1979, Jobs y varios empleados de Apple visitaron Xerox PARC (Centro de investi-
gación de Palo Alto) para aprender sobre la computadora de escritorio de Xerox que contaba con una
interfaz gráfica de usuario (GUI). Esa GUI sirvió como inspiración para la Apple Macintosh, que se
lanzó con muchas fanfarrias en un memorable anuncio del Súper Tazón de 1984.
1.12 Internet y World Wide Web 27
El lenguaje de programación Objective-C, creado por Brad Cox y Tom Love en Stepstone a prin-
cipios de la década de 1980, agregó capacidades de programación orientada a objetos (OOP) al lengua-
jedeprogramaciónC.Almomentodeescribirestelibro,Objective-Csecomparabaenpopularidadcon
C++.6
Steve Jobs dejó Apple en 1985 y fundó NeXT, Inc. En 1988, NeXT obtuvo la licencia para Ob-
jective-C de StepStone y desarrolló un compilador de Objective-C además de varias bibliotecas, lo cual
se usó como la plataforma para la interfaz de usuario el sistema operativo NEXTSTEP y para Interface
Builder (que se usaba para construir interfaces gráficas de usuario).
Jobs regresó a Apple en 1996, cuando esta empresa compró NeXT. El sistema operativo OS X
de Apple es descendiente de NeXTSTEP. El sistema operativo propietario de Apple, iOS, se deriva del
Apple OS X y se utiliza en los dispositivos iPhone, iPad y iPod Touch.
1.11.4Android de Google
Android —el sistema operativo para dispositivos móviles y smartphones, cuyo crecimiento ha sido el
más rápido hasta ahora— está basado en el kernel de Linux y en Java. Los programadores experimenta-
dos de Java no tienen problemas para entrar y participar con rapidez en el desarrollo de aplicaciones para
Android. Un beneficio de desarrollar este tipo de aplicaciones es el grado de apertura de la plataforma.
El sistema operativo es gratuito y de código fuente abierto.
El sistema operativo Android fue desarrollado por Android, Inc., compañía que adquirió Google
en 2005. En 2007 se formó la Alianza para los dispositivos móviles abiertos™ (OHA) —un consor-
cio de 34 compañías en un principio que creció a 84 para 2011—, para continuar con el desarrollo
de Android. Al mes de junio de 2012, ¡se activaban más de 900000 teléfonos inteligentes con An-
droid a diario!7
Ahora los smartphones Android se venden más que los iPhone en Estados Unidos.8
El sistema operativo Android se utiliza en varios smartphones (Motorola Droid, HTC One S, Sam-
sung Galaxy Nexus y muchos más), dispositivos lectores electrónicos (como el Kindle Fire y el
Nook™ de Barnes and Noble), computadoras tipo tableta (como Dell Streak y Samsung Galaxy
Tab), quioscos con pantallas táctiles dentro de las tiendas, automóviles, robots, reproductores multi-
media y demás.
1.12Internet y World Wide Web
Internet (una red global de computadoras) se hizo posible gracias a la convergencia de las tecnologías de la
computación y las comunicaciones. A finales de la década de 1960, ARPA (la Agencia de proyectos de
investigación avanzados) extendió los planos para conectar en red los sistemas de cómputo principales
de alrededor de una docena de universidades e instituciones de investigación patrocinadas por la ARPA.
La investigación académica estaba a punto de dar un gigantesco paso hacia delante. La ARPA procedió
a implementar la ARPANET, que eventualmente se convirtió en la Internet que conocemos. Pronto se
volvió claro que el primer beneficio clave de ARPANET era la capacidad de comunicarse con rapidez y
facilidad mediante correo electrónico. Esto es cierto incluso en Internet de la actualidad, ya que facilita
las comunicaciones de todo tipo entre los usuarios de Internet a nivel mundial.
Conmutación de paquetes
Uno de los principales objetivos de ARPANET fue permitir que múltiples usuarios enviaran y recibieran
información al mismo tiempo y a través de las mismas rutas de comunicaciones (por ejemplo, las líneas
6 www.tiobe.com/index.php/content/paperinfo/tpci/index.html.
7 mashable.com/2012/06/11/900000-android-devices/.
8 www.pcworld.com/article/196035/android_outsells_the_iphone_no_big_surprise.html.
28 Capítulo 1 Introducción a las computadoras y a C++
telefónicas). La red operaba mediante una técnica conocida como conmutación de paquetes, en don-
de los datos digitales se enviaban en pequeños grupos llamados paquetes. Los paquetes contenían in-
formación de dirección, control de errores y secuencia. La información de la dirección permitía enrutar los
paquetes hacia sus destinos. La información de secuencia ayudaba a volver a ensamblar los paquetes —ya
que debido a los complejos mecanismos de enrutamiento podrían llegar desordenados— en su orden
originalparapresentarlosalreceptor.Lospaquetesdedistintosemisoresseentremezclabanenlasmismas
líneas para usar con eficiencia el ancho de banda disponible. Esta técnica de conmutación de paquetes
redujo de manera considerable los costos de transmisión, en comparación con el costo de las líneas de
comunicaciones dedicadas.
La red se diseñó para operar sin un control centralizado. Si fallaba una parte de la red, el resto de las
porciones funcionales podrían seguir enrutando paquetes de los emisores a los receptores a través de
rutas alternativas para mejorar la confiabilidad.
TCP/IP
El protocolo (conjunto de reglas) para comunicarse a través de ARPANET se conoció como TCP:
Protocolo de control de transmisión.TCP aseguraba que los mensajes se enrutaran en forma correcta
del emisor hacia el receptor, y que llegaran intactos.
A medida que evolucionó Internet, organizaciones de todo el mundo estaban implementando sus
propias redes. Un desafío fue lograr que estas distintas redes se comunicaran. La ARPA lo logró con el
desarrollo de IP: el Protocolo de Internet, con lo que verdaderamente creó una red de redes, la arqui-
tectura actual de Internet. El conjunto combinado de protocolos se conoce ahora comúnmente como
TCP/IP.
World Wide Web, HTML, HTTP
WorldWideWeb nos permite localizar y ver documentos basados en multimedia sobre casi cualquier
tema en Internet. La Web es una creación relativamente reciente. En 1989, Tim Berners-Lee de
CERN (la Organización Europea para la Investigación Nuclear) empezó a desarrollar una tecnología
para compartir información a través de documentos de texto con hipervínculos. A esta invención,
Berners-Lee la llamó Lenguaje de marcado de hipertexto (HTML). También escribió protocolos
de comunicación para formar la espina dorsal de su nuevo sistema de información, al cual llamó
World Wide Web. En especial escribió el Protocolo de transferencia de hipertexto (HTTP): un
protocolo de comunicaciones utilizado para enviar información a través de Web. El URL (Localiza-
dor uniforme de recursos) especifica la dirección (ubicación) de la página Web que se visualiza en
la ventana del navegador. Cada página Web en Internet se asocia con un URL único. El Protocolo
seguro de transferencia de hipertexto (HTTPS) es el estándar para transferir datos cifrados enWeb.
Mosaic, Netscape, surgimiento de Web 2.0
El uso de Web explotó con la disponibilidad en 1993 del navegador Mosaic, el cual incluía una interfaz grá-
fica amigable para el usuario. Marc Andreessen, cuyo equipo en el Centro nacional de aplicaciones de super-
computación (NCSA) desarrolló Mosaic, fue el fundador de Netscape, la empresa que muchas personas
consideran fue la mecha que encendió la explosiva economía de Internet a finales de la década de 1990.
En 2003 hubo un cambio considerable en cuanto a la forma en que las personas y las empresas
usaban laWeb y desarrollaban aplicaciones basadas enWeb. Dale Dougherty de O’Reilly Media9
inven-
tó el término Web 2.0 en 2003 para describir esta tendencia. Por lo general, las compañíasWeb 2.0 usan
la Web como una plataforma para crear sitios colaborativos basados en comunidades (como sitios de
redes sociales, blogs y wikis).
9 T. O’Reilly, “What isWeb 2.0: Design Patterns and Business Models for the Next Generation of Software.” September
2005 http://www.oreillynet.com/pub/a/oreilly/tim/news/2005/09/30/what-is-web-20.html?page=1.
1.13 Cierta terminología clave de desarrollo de software 29
Algunas empresas con características de Web 2.0 son: Google (búsqueda Web), YouTube (compar-
tición de videos), Facebook (redes sociales), Twitter (microblogs), Groupon (comercio social), Fours-
quare (registro móvil), Salesforce (software de negocios que se ofrece en forma de servicios en línea “en
la nube”), Craigslist (en su mayoría, anuncios clasificados gratuitos), Flickr (compartición de fotogra-
fías), Skype (telefonía, videollamadas y conferencias por Internet) y Wikipedia (una enciclopedia gra-
tuita en línea).
Web 2.0 involucra a los usuarios; no sólo crean contenido con frecuencia, sino que ayudan a orga-
nizarlo, compartirlo, volver a mezclarlo, criticarlo, actualizarlo, etc. Web 2.0 es una conversación, en
donde todos tienen la oportunidad de hablar y compartir opiniones. Las empresas que entienden la
Web 2.0 saben que sus productos y servicios son conversaciones también.
Arquitectura de participación
La Web 2.0 abarca una arquitectura de participación: un diseño que fomenta la interacción del usua-
rio y las contribuciones comunitarias. Usted el usuario es el aspecto más importante de Web 2.0; de
hecho, es tan importante que en 2006 la “Persona del año” de la revista TIME fue “usted”.10
El artícu-
lo reconoció el fenómeno social de Web 2.0: el cambio de unos cuantos poderosos a muchos empoderados.
Ahora varios blogs populares compiten con los poderosos medios tradicionales y muchas empresas
Web 2.0 están basadas casi por completo en contenido generado por el usuario. Para sitios Web como
Facebook,Twitter,YouTube, eBay yWikipedia, los usuarios crean el contenido, mientras que las empresas
proporcionan las plataformas en las cuales se va a introducir, manipular y compartir la información.
1.13Cierta terminología clave de desarrollo de software
La figura 1.27 muestra una lista de palabras de moda que escuchará en la comunidad de desarrollo de
software. Creamos Centros de Recursos sobre la mayoría de estos temas, y hay muchos por venir.
Tecnología Descripción
Ajax Ajax es una de las tecnologías de software Premier de Web 2.0. Ajax ayuda a que
las aplicaciones basadas en Internet se ejecuten como aplicaciones de escritorio;
una tarea difícil, dado que dichas aplicaciones sufren de retrasos en la transmisión a
medida que los datos se intercambian entre su computadora y los servidores en
Internet.
Desarrollo ágil de
software
El desarrollo ágil de software es un conjunto de metodologías que tratan de
implementar software con más rapidez y menos recursos que las metodologías
anteriores. Visite los sitios de Agile Alliance (www.agilealliance.org) y Agile
Manifesto (www.agilemanifesto.org). También puede visitar el sitio en español
(www.agile-spain.com).
Refactorización La refactorización implica reformular programas para hacerlos más claros y fáciles
de mantener, al tiempo que se preserva su funcionalidad e integridad. Es muy
utilizado en las metodologías de desarrollo ágil. Muchos IDE contienen herra-
mientas de refactorización integradas para realizar la mayor parte del proceso de
refactorización de manera automática.
Fig. 1.27  Tecnologías de software (parte 1 de 2).
10 www.time.com/time/magazine/article/0,9171,1570810,00.html.
30 Capítulo 1 Introducción a las computadoras y a C++
Tecnología Descripción
Patrones de diseño Los patrones de diseño son arquitecturas probadas para construir software
orientado a objetos flexible y que pueda mantenerse. El campo de los patrones de
diseño trata de enumerar a los patrones recurrentes, y de alentar a los diseñadores
de software para que los reutilicen y puedan desarrollar un software de mejor
calidad con menos tiempo, dinero y esfuerzo.
LAMP LAMP es un acrónimo para el conjunto de tecnologías de código fuente abierto que
usan muchos desarrolladores en la creación de aplicaciones Web: se refiere a Linux,
Apache, MySQL y PHP (o Perl, o Python; otros dos lenguajes que se usan para
propósitos similares). MySQL es un sistema de administración de bases de datos de
código fuente abierto. PHP es el lenguaje de “secuencias de comandos” del lado
servidor de código fuente abierto más popular para el desarrollo de aplicaciones
basadas en Internet.
Software como un
servicio (SaaS)
Por lo general, el software siempre se ha visto como un producto; la mayoría del
software aún se ofrece de esta forma. Para ejecutar una aplicación, hay que comprar
el paquete a un distribuidor de software; por lo general un CD, DVD o descarga
Web. Después instalamos ese software en la computadora y la ejecutamos cuando sea
necesario. A medida que aparecen nuevas versiones, actualizamos el software, lo cual
genera con frecuencia un tiempo y un gasto considerables. Este proceso puede ser
incómodo para las organizaciones con decenas de miles de sistemas, a los que se debe
dar mantenimiento en una diversa selección de equipo de cómputo. En el Software
como un servicio (SaaS), el software se ejecuta en servidores ubicados en cualquier
parte de Internet. Cuando se actualizan esos servidores, los clientes en todo el mundo
ven las nuevas capacidades; no se requiere una instalación local. Podemos acceder al
servicio a través de un navegador. Los navegadores son bastante portables, por lo que
podemos ver las mismas aplicaciones en una amplia variedad de computadoras desde
cualquier parte del mundo. Salesforce.com, Google, Microsoft Office Live y
Windows Live ofrecen SaaS. Esta tecnología es una herramienta de la computación
en nube.
Plataforma como un
servicio (SaaS)
La Plataforma como un servicio (PaaS), otra herramienta de la computación
en nube, provee una plataforma de cómputo para desarrollar y ejecutar aplicacio-
nes como un servicio a través de Web, en vez de instalar las herramientas en su
computadora. Los proveedores de PaaS más importantes son: Google App Engine,
Amazon EC2, Bungee Labs, entre otros.
Kit de desarrollo de
software (SDK)
Los Kits de desarrollo de software (SDK) incluyen tanto las herramientas
como la documentación que utilizan los desarrolladores para programar
aplicaciones.
Fig. 1.27  Tecnologías de software (parte 2 de 2).
La figura 1.28 describe las categorías de liberación de versiones de los productos de software.
Versión Descripción
Alpha El software alfa es la primera versión de un producto de software cuyo desarrollo aún se
encuentra activo. Por lo general las versiones alfa tienen muchos errores, son incompletas e
inestables; además se liberan a un pequeño número de desarrolladores para que evalúen las
nuevas características, para obtener retroalimentación lo más pronto posible, etc.
Fig. 1.28  Terminología de liberación de versiones de productos de software (parte 1 de 2).
1.14 C++11 y las bibliotecas Boost de código fuente abierto 31
Versión Descripción
Beta Las versiones beta se liberan a un número mayor de desarrolladores en una etapa posterior
del proceso de desarrollo, una vez que se ha corregido la mayoría de los errores importantes
y las nuevas características están casi completas. El software beta es más estable, pero todavía
puede sufrir cambios.
Candidatos
para
liberación
(Release
Candidates)
En general, los candidatos para liberación tienen todas sus características completas, están
(supuestamente) libres de errores y listos para que la comunidad los utilice, con lo cual se
logra un entorno de prueba diverso —el software se utiliza en distintos sistemas, con
restricciones variables y para muchos fines diferentes. Cualquier error que aparezca
se corrige y, en un momento dado, el producto final se libera al público en general.
A menudo, las compañías de software distribuyen actualizaciones incrementales a través
de Internet.
Beta
permanente
El software que se desarrolla mediante este método por lo general no tiene números de
versión (por ejemplo, la búsqueda de Google o Gmail). Este software, que se aloja en la
nube (no se instala en su computadora), evoluciona de manera constante de modo que los
usuarios siempre dispongan de la versión más reciente.
1.14C++11 y las bibliotecas Boost de código fuente abierto
C++11 (anteriormente conocido como C++0x) es el estándar más reciente del lenguaje de programa-
ción C++. Las organizaciones ISO/IEC lo publicaron en 2011. Bjarne Stroustrup, el creador de C++,
expresó su visión para el futuro del lenguaje: las principales metas eran facilitar el aprendizaje de
C++, mejorar las herramientas para generar bibliotecas e incrementar la compatibilidad con el lenguaje
de programación C. El nuevo estándar extiende la Biblioteca estándar de C++ e incluye varias herra-
mientas y mejoras para incrementar el rendimiento y la seguridad. Los principales distribuidores de
compiladores de C++ ya implementaron muchas de las nuevas características de C++11 (figura 1.29).
A lo largo del libro hablaremos sobre varias características clave de C++11. Para obtener más informa-
ción, visite el sitio Web del Comité de Estándares de C++ en www.open-std.org/jtc1/sc22/wg21/ e
isocpp.org.PuedecomprarcopiasdelaespecificacióndellenguajeC++11(ISO/IEC14882:2011)en:
http://bit.ly/CPlusPlus11Standard
Compilador de C++
URL de las descripciones de las características
de C++11
Características de C++11
implementadas en
cada uno de los
principales
compiladores de C++.
wiki.apache.org/stdcxx/C%2B%2B0xCompilerSupport
Microsoft®
Visual C++ msdn.microsoft.com/en-us/library/hh567368.aspx
Coleccióndecompiladores
deGNU(g++)
gcc.gnu.org/projects/cxx0x.html
Fig. 1.28  Terminología de liberación de versiones de productos de software (parte 2 de 2).
Fig. 1.29  Compiladores de C++ que implementaron las principales porciones
de C++11 (parte 1 de 2).
32 Capítulo 1 Introducción a las computadoras y a C++
Compilador de C++
URL de las descripciones de las características
de C++11
Compilador Intel®
C++ software.intel.com/en-us/articles/c0x-featu-
res-supported-by-intel-c-compiler/
IBM®
XL C/C++ www.ibm.com/developerworks/mydeveloperworks/
blogs/5894415f-be62-4bc0-81c5-3956e82276f3/
entry/xlc_compiler_s_c_11_support50?lang=en
Clang clang.llvm.org/cxx_status.html
EDG ecpp www.edg.com/docs/edg_cpp.pdf
Bibliotecas Boost de C++
Las Bibliotecas Boost de C++ son bibliotecas gratuitas de código fuente abierto, creadas por miembros
delacomunidaddeC++.Sonrevisadasporexpertosyportablesentremuchoscompiladoresyplataformas.
El tamaño de Boost ha crecido hasta más de 100 bibliotecas, y se agregan más con regularidad. Hoy en día
haymilesdeprogramadoresenlacomunidaddecódigofuentedeBoost.Boostofrecealosprogramadores
de C++ bibliotecas útiles que funcionan bien con la Biblioteca estándar de C++ existente. Las bibliotecas
Boost las pueden utilizar los programadores de C++ que trabajan en una amplia variedad de plataformas,
con muchos compiladores distintos. Algunas de las nuevas características de la Biblioteca estándar de
C++11sederivarondelasbibliotecasdeBoostcorrespondientes.Nosotrosveremoslasgeneralidadessobre
las bibliotecas y proporcionaremos ejemplos de código para las bibliotecas de “expresiones regulares” y
“apuntadores inteligentes”, entre otras.
Las expresiones regulares se utilizan para relacionar patrones de caracteres específicos en el texto.
Pueden usarse para validar datos y asegurar que se encuentren en un formato específico, para reemplazar
partes de una cadena con otra, o para dividir una cadena.
Muchos errores comunes en el código de C y C++ están relacionados con los apuntadores, una
poderosa herramienta de programación que C++ absorbió de C. Como veremos, los apuntadores in-
teligentes nos ayudan a evitar errores asociados con los apuntadores tradicionales.
1.15Mantenerse actualizado con las tecnologías de la información
La figura 1.30 muestra una lista de las publicaciones técnicas y comerciales clave que le ayudarán a permane-
ceractualizadoconlatecnología,lasnoticiasylastendenciasmásrecientes.Tambiénencontraráunalistacada
vezmásgrandedeCentrosderecursosrelacionadosconInternetyWebenwww.deitel.com/resourcecen-
ters.html.
Publicación URL
ACM TechNews technews.acm.org/
ACM Transactions on
Accessible Computing
www.gccis.rit.edu/taccess/index.html
ACM Transactions on Internet
Technology
toit.acm.org/
Bloomberg BusinessWeek www.businessweek.com
Fig. 1.30  Publicaciones técnicas y comerciales (parte 1 de 2).
Fig. 1.29  Compiladores de C++ que implementaron las principales porciones
de C++11 (parte 2 de 2).
Publicación URL
CNET news.cnet.com
Communications of the ACM cacm.acm.org/
Computerworld www.computerworld.com
Engadget www.engadget.com
eWeek www.eweek.com
Fast Company www.fastcompany.com/
Fortune money.cnn.com/magazines/fortune/
IEEE Computer www.computer.org/portal/web/computer
IEEE Internet Computing www.computer.org/portal/web/internet/home
InfoWorld www.infoworld.com
Mashable mashable.com
PCWorld www.pcworld.com
SD Times www.sdtimes.com
Slashdot slashdot.org/
Smarter Technology www.smartertechnology.com
Technology Review technologyreview.com
Techcrunch techcrunch.com
Wired www.wired.com
1.16Recursos Web
Esta sección proporciona vínculos a nuestros Centros de recursos sobre C++ y temas relacionados que
le serán de utilidad a medida que aprenda C++. Los sitios blogs, artículos, documentos técnicos, com-
piladores,herramientasdedesarrollo,descargas,preguntasfrecuentes(FAQ),tutoriales,webcasts,wikis
y vínculos a recursos de programación de juegos en C++. Para actualizaciones sobre publicaciones Dei-
tel, Centros de recursos, cursos de capacitación, ofertas para socios y demás, síganos por Facebook®
en
www.facebook.com/deitelfan/,Twitter®
@deitel, Google+ en gplus.to/deitel y LinkedIn en bit.
ly/DeitelLinkedIn.
Sitios Web de Deitel  Associates
www.deitel.com/books/cpphtp9/
El sitio Web de Cómo programar en C++, 9/e de Deitel  Associates. Aquí encontrará vínculos a los ejemplos del
libro y otros recursos.
www.deitel.com/cplusplus/
www.deitel.com/visualcplusplus/
www.deitel.com/codesearchengines/
www.deitel.com/programmingprojects/
ReviseestosCentrosdeRecursosenbuscadecompiladores,descargasdecódigo,tutoriales,documentación,libros,
libros electrónicos, artículos, blogs, canales (feeds) RSS y demás, que le ayudarán a desarrollar aplicaciones de C++.
www.deitel.com
Revise este sitio en busca de actualizaciones, correcciones y recursos adicionales para todas las publicaciones Deitel.
www.deitel.com/newsletter/subscribe.html
Suscríbase al boletín de correo electrónico gratuito Deitel®
Buzz Online, para seguir el programa de publicaciones
de Deitel  Associates, incluyendo actualizaciones y fe de erratas para este libro.
1.16 Recursos Web 33
Fig. 1.30  Publicaciones técnicas y comerciales (parte 2 de 2).
34 Capítulo 1 Introducción a las computadoras y a C++
Ejercicios de autoevaluación
1.1 Complete las siguientes oraciones:
a) Las computadoras procesan los datos bajo el control de conjuntos de instrucciones llamadas
____________.
b) Las unidades lógicas clave de la computadora son __________, __________, __________,
__________, __________ y __________.
c) Los tres tipos de lenguajes descritos en este capítulo son __________, __________ y __________.
d) Los programas que traducen programas de lenguaje de alto nivel a lenguaje máquina se denominan
______ ____.
e) __________ es un sistema operativo para dispositivos móviles, basado en el kernel de Linux y en Java.
f) Porlogeneral,elsoftware__________tienetodassuscaracterísticascompletasy(supuestamente)está
libre de errores, listo para que la comunidad lo utilice.
g) El Wii Remote, así como muchos smartphones, usa un(a) __________ que permite al dispositivo
responder al movimiento.
1.2 Complete cada una de las siguientes oraciones relacionadas con el entorno de C++:
a) Por lo general, los programas de C++ se escriben en una computadora mediante el uso de un programa
______.
b) En un sistema de C++, un programa ______ se ejecuta antes de que empiece la fase de traducción del
compilador.
c) El programa _________ combina la salida del compilador con varias funciones de biblioteca para
producir una imagen ejecutable.
d) El programa _________ transfiere el programa ejecutable del disco a la memoria.
1.3 Complete cada una de las siguientes oraciones (basándose en la sección 1.8):
a) Los objetos tienen una propiedad que se conoce como __________; aunque éstos pueden saber cómo
comunicarse con los demás objetos a través de interfaces bien definidas, generalmente no se les permi-
te saber cómo están implementados los otros objetos.
b) Los programadores de C++ se concentran en crear __________, que contienen miembros de datos y las
funciones miembro que manipulan a esos miembros de datos y proporcionan servicios a los clientes.
c) Al proceso de analizar y diseñar un sistema desde un punto de vista orientado a objetos se le conoce
como _______.
d) Con la __________, se derivan nuevas clases de objetos absorbiendo las características de las clases
existentes, y después se agregan sus propias características.
e) ________ es un lenguaje gráfico, que permite a las personas que diseñan sistemas de software utilizar
una notación estándar en la industria para representarlos.
f) El tamaño, forma, color y peso de un objeto se consideran ________ de la clase de ese objeto.
Respuestas a los ejercicios de autoevaluación
1.1 a) programas. b) unidad de entrada, unidad de salida, unidad de memoria, unidad central de procesamien-
to, unidad aritmética y lógica, unidad de almacenamiento secundario. c) lenguajes máquina, lenguajes ensambla-
dores y lenguajes de alto nivel. d) compiladores. e) Android. f) Candidato para liberación. g) acelerómetro.
1.2 a) editor. b) preprocesador. c) enlazador. d) cargador.
1.3 a) ocultamiento de información. b) clases. c) análisis y diseño orientados a objetos (A/DOO). d) heren-
cia. e) El Lenguaje Unificado de Modelado (UML). f) atributos.
Ejercicios
1.4 Complete cada una de las siguientes oraciones:
a) La unidad lógica de la computadora que recibe información desde el exterior de la computadora para
que ésta la utilice es la __________.
b) El proceso de indicar a la computadora cómo resolver un problema se llama __________.
c) __________ es un tipo de lenguaje computacional que utiliza abreviaturas del inglés para las instruc-
ciones de lenguaje máquina.
d) __________ es una unidad lógica de la computadora envía información, que ya ha sido procesada por
la computadora, a varios dispositivos, de manera que la información pueda utilizarse fuera de la compu-
tadora.
e) ________ y ________ son unidades lógicas de la computadora que retienen información.
f) ________ es una unidad lógica de la computadora que realiza cálculos.
g) ________ es una unidad lógica de la computadora que toma decisiones lógicas.
h) Los lenguajes _________ son más convenientes para que el programador pueda escribir programas
rápida y fácilmente.
i) Al único lenguaje que una computadora puede entender directamente se le conoce como el _____
_____ de esa computadora.
j) __________ es una unidad lógica de la computadora que coordina las actividades de todas las demás
unidades lógicas.
1.5 Complete los siguientes enunciados:
a) ________ en un principio se hizo popular como el lenguaje de desarrollo del sistema operativo Unix.
b) El lenguaje de programación ________ fue desarrollado por Bjarne Stroustrup a principios de la dé-
cada de 1980 en los laboratorios Bell.
1.6 Complete los siguientes enunciados:
a) Por lo general los programas de C++ pasan por seis fases: __________, __________, __________,
__________, __________ y __________.
b) Un(a) __________ proporciona muchas herramientas para apoyar el proceso de desarrollo de soft-
ware, como editores para escribir y editar programas, depuradores para localizar errores lógicos en los
programas y muchas otras características.
1.7 Probablemente usted lleve en su muñeca uno de los tipos de objetos más comunes en el mundo: un
reloj. Hable acerca de cómo se aplica cada uno de los siguientes términos y conceptos a la noción de un reloj:
objeto, atributos, comportamientos, clase, herencia (considere, por ejemplo, un reloj con alarma), modelado,
mensajes, encapsulamiento, interfaz y ocultamiento de información.
Hacer la diferencia
Hemos incluido en este libro ejercicios Hacer la diferencia, en los que le pediremos que trabaje con problemas que
son de verdad importantes para individuos, comunidades, países y el mundo. Para obtener más información sobre
las organizaciones a nivel mundial que trabajan para hacer la diferencia, y para obtener ideas sobre proyectos de
programación relacionados, visite nuestro Centro de recursos para hacer la diferencia en www.deitel.com/makin-
gadifference.
1.8 (Prueba práctica: calculadora de impacto ambiental del carbono) Algunos científicos creen que las
emisiones de carbono, sobre todo las que se producen al quemar combustibles fósiles, contribuyen de manera
considerable al calentamiento global y que esto se puede combatir si las personas tomamos conciencia y limitamos
el uso de los combustibles basados en carbono. Varias organizaciones e individuos se preocupan cada vez más por el
“impacto ambiental debido al carbono”. Los sitios Web como Terra Pass
www.terrapass.com/carbon-footprint-calculator/
y Carbon Footprint
www.carbonfootprint.com/calculator.aspx
ofrecen calculadoras de impacto ambiental del carbono. Pruebe estas calculadoras para determinar el impacto que
provoca usted en el ambiente debido al carbono. Los ejercicios en capítulos posteriores le pedirán que programe
su propia calculadora de impacto ambiental del carbono. Como preparación, le sugerimos investigar las fórmulas
para calcular el impacto ambiental del carbono.
1.9 (Prueba práctica: calculadora del índice de masa corporal) Según las estimaciones recientes, dos terceras
partes de las personas que viven en Estados Unidos padecen de sobrepeso; la mitad de estas personas son obesas. Esto
provocaaumentosconsiderablesenelnúmerodepersonasconenfermedadescomoladiabetesylascardiopatías.Para
determinar si una persona tiene sobrepeso o padece de obesidad, puede usar una medida conocida como índice de
Hacer la diferencia 35
36 Capítulo 1 Introducción a las computadoras y a C++
masacorporal(IMC).ElDepartamentodesaludyservicioshumanosdeEstadosUnidosproporcionaunacalculado-
ra del IMC en www.nhlbisupport.com/bmi/. Úsela para calcular su propio IMC. Un ejercicio del capítulo 2 le pedirá
que programe su propia calculadora del IMC. Como preparación, le sugerimos investigar las fórmulas para calcular
el IMC.
1.10 (Atributos de los vehículos híbridos) En este capítulo aprendió sobre los fundamentos de las clases. Aho-
ra empezará a describir con detalle los aspectos de una clase conocida como “Vehículo híbrido”. Los vehículos
híbridos se están volviendo cada vez más populares, puesto que por lo general pueden ofrecer mucho más kilome-
traje que los vehículos operados sólo por gasolina. Navegue en Web y estudie las características de cuatro o cinco
de los automóviles híbridos populares en la actualidad; después haga una lista de todos los atributos relacionados
con sus características de híbridos que pueda encontrar. Por ejemplo, algunos de los atributos comunes son los
kilómetros por litro en ciudad y los kilómetros por litro en carretera. También puede hacer una lista de los atribu-
tos de las baterías (tipo, peso, etc.).
1.11 (Neutralidad de género) Muchas personas desean eliminar el sexismo de todas las formas de comuni-
cación. Usted ha recibido la tarea de crear un programa que pueda procesar un párrafo de texto y reemplazar
palabras que tengan un género específico con palabras neutrales en cuanto al género. Suponiendo que recibió
una lista de palabras con género específico y sus reemplazos con neutralidad de género (por ejemplo, reemplace
“esposa” por “cónyuge”, “hombre” por “persona”, “hija” por “descendiente”, y así en lo sucesivo), explique el
procedimiento que utilizaría para leer un párrafo de texto y realizar estos reemplazos en forma manual. ¿Cómo
podría su procedimiento generar un término extraño tal como “woperchild”, que aparece listado en el Diccio-
nario urbano (www.urbandictionary.com)? En el capítulo 4 aprenderá que un término más formal para “proce-
dimiento” es “algoritmo”, y que un algoritmo especifica los pasos a realizar, además del orden en el que se deben
llevar a cabo.
1.12 (Privacidad) Algunos servicios de correo electrónico en línea guardan toda la correspondencia electróni-
ca durante cierto periodo de tiempo. Suponga que un empleado disgustado de uno de estos servicios de correo
electrónico en línea publicara en Internet todas las correspondencias de correo electrónico de millones de personas,
incluyendo la suya. Analice las consecuencias.
1.13 (Responsabilidad ética y legal del programador) Como programador en la industria, tal vez llegue a
desarrollar software que podría afectar la salud de otras personas, o incluso sus vidas. Suponga que un error de
software en uno de sus programas provocara que un paciente de cáncer recibiera una dosis excesiva durante la te-
rapia de radiación y resultara gravemente lesionada o muriera. Analice las consecuencias.
1.14 (El “Flash Crash” de 2010) Un ejemplo de las consecuencias de nuestra excesiva dependencia con
respecto a las computadoras es el denominado “flash crash”, que ocurrió el 6 de mayo de 2010, cuando el mer-
cado de valores de Estados Unidos se derrumbó de manera precipitada en cuestión de minutos, al borrarse
billones de dólares de inversiones que se volvieron a recuperar pocos minutos después. Use Internet para inves-
tigar las causas de este derrumbe y analice las consecuencias que genera.
Recursos para hacer la diferencia
La Copa Imagine de Microsoft es una competencia global en la que los estudiantes usan la tecnología para intentar
resolver algunos de los problemas más difíciles del mundo, como la sostenibilidad ambiental, acabar con la ham-
bruna, la respuesta a emergencias, la alfabetización, combatir el HIV/SIDA y otros más. Visite www.imaginecup.
com/about para obtener más información sobre la competencia y para aprender sobre los proyectos desarrollados
por los anteriores ganadores.También encontrará varias ideas de proyectos enviadas por organizaciones de caridad
a nivel mundial.
Si desea obtener ideas adicionales para proyectos de programación que puedan hacer la diferencia, busque en
Web el tema “hacer la diferencia” y visite
www.un.org/millenniumgoals
El proyecto Milenio de Naciones Unidas busca soluciones para los principales problemas mundiales, como la
sostenibilidad ambiental, la igualdad de sexos, la salud infantil y materna, la educación universal y otros más.
www.ibm.com/smarterplanet/
El sitio Web Smarter Planet de IBM®
habla sobre cómo es que IBM utiliza la tecnología para resolver problemas
relacionados con los negocios, la computación en nube, la educación, la sostenibilidad y otros más.
Recursos para hacer la diferencia 37
www.gatesfoundation.org/Pages/home.aspx
La Fundación Bill y Melinda Gates ofrece becas a las organizaciones que trabajan para mitigar el hambre, la pobre-
zaylasenfermedadesenlospaísesendesarrollo.EnEstadosUnidos,lafundaciónseenfocaenmejorarlaeducación
pública, en especial para las personas con bajos recursos.
www.nethope.org/
NetHope es una colaboración de organizaciones humanitarias en todo el mundo, que trabajan para resolver los
problemas relacionados con la tecnología, como la conectividad y la respuesta a las emergencias, entre otros.
www.rainforestfoundation.org/home
La Fundación Rainforest trabaja para preservar los bosques tropicales y proteger los derechos de los indígenas que
consideran a estos bosques como su hogar. El sitio incluye una lista de cosas que usted puede hacer para ayudar.
www.undp.org/
El Programa de las Naciones Unidas para el Desarrollo (UNDP) busca soluciones a los desafíos globales, como la
prevención y recuperación de crisis, la energía y el ambiente, la gobernanza democrática y otros más.
www.unido.org
La Organización de las Naciones Unidas para el Desarrollo Industrial (UNIDO) busca reducir la pobreza, dar a
los países en desarrollo la oportunidad de participar en el comercio global y promover tanto la eficiencia de la
energía como la sostenibilidad.
www.usaid.gov/
USAID promueve la democracia global, la salud, el crecimiento económico, la prevención de conflictos y la ayuda
humanitaria, entre otras cosas.
www.toyota.com/ideas-for-good/
El sitio Web Ideas for Good deToyota describe varias tecnologías de esta empresa que están haciendo la diferencia;
entre éstas se incluyen su Sistema avanzado de asistencia de estacionamiento (Advanced Parking Guidance Sys-
tem), la tecnología Hybrid Synergy Drive®
, el Sistema de ventilación operado por energía solar (Solar Powered
Ventilation System), el modelo T.H.U.M.S. (Modelo humano total para la seguridad) y Touch Tracer Display.
Usted puede participar en el desafío de Ideas for Good; envíe un breve ensayo o un video que describa cómo se
pueden usar estas tecnologías para otros buenos propósitos.
Introducción a la programación
en C++, entrada/salida
y operadores
2
¿Qué hay en un nombre?
A eso a lo que llamamos rosa,
si le diéramos otro nombre
conservaría su misma fragancia
dulce.
—William Shakespeare
Los pensamientos elevados deben
tener un lenguaje elevado.
—Aristófanes
Una persona puede hacer una
diferencia y todas las personas
deberían intentarlo.
—John F. Kennedy
O b j e t i v o s
En este capítulo aprenderá a:
n Escribir programas de
cómputo simples en C++.
n Escribir instrucciones simples
de entrada y salida.
n Utilizar los tipos
fundamentales.
n Familiarizarse con los
conceptos básicos de la
memoria de la computadora.
n Utilizar los operadores
aritméticos.
n Reconocer precedencia de los
operadores aritméticos.
n Escribir instrucciones simples
para tomar decisiones.
2.2 Su primer programa en C++: imprimir una línea de texto 39
2.1Introducción
Ahora presentaremos la programación en C++, que facilita una metodología disciplinada para el diseño
deprogramas.LamayoríadelosprogramasenC++queestudiaráenestelibroprocesandatosymuestran
resultados. En este capítulo le presentaremos cinco ejemplos que señalan cómo sus programas pueden
mostrar mensajes y obtener datos del usuario para procesarlos. Los primeros tres ejemplos simplemente
muestran mensajes en la pantalla. El siguiente ejemplo obtiene dos números de un usuario, calcula su
suma y muestra el resultado. La discusión que acompaña a este ejemplo le muestra cómo realizar varios
cálculos aritméticos y guardar sus resultados para utilizarlos posteriormente. El quinto ejemplo demues-
tra la toma de decisiones, al mostrarle cómo comparar dos números y después mostrar mensajes con base
en los resultados de la comparación. Analizaremos cada programa, una línea a la vez, para ayudarle a
facilitar el proceso de aprender a programar en C++.
Compilar y ejecutar programas
En www.deitel.com/books/cpphtp9, publicamos videos que demuestran cómo compilar y ejecutar
programas en Microsoft Visual C++, GNU C++ y Xcode.
2.2Su primer programa en C++: imprimir una línea de texto
Considere un programa simple que imprime una línea de texto (figura 2.1). Este programa ilustra varias
características importantes del lenguaje C++. El texto en las líneas 1 a 11 es el código fuente (o código) del
programa. Los números de línea no son parte del código fuente.
1 // Fig. 2.1: fig02_01.cpp
2 // Programa para imprimir texto.
3 #include iostream // permite al programa imprimir datos en la pantalla
4
5 // la función main comienza la ejecución del programa
6 int main()
7 {
8 std::cout  “Bienvenido a C++!n”; // muestra un mensaje
9
10 rewrn 0; // indica que el programa terminó con éxito
11 } // fin de la función main
Bienvenido a C++!
Fig. 2.1  Programa para imprimir texto.
2.1 Introducción
2.2 Su primer programa en C++: imprimir una línea de
texto
2.3 Modificación de nuestro primer programa en C++
2.4 Otro programa en C++: suma de enteros
2.5 Conceptos acerca de la memoria
2.6 Aritmética
2.7 Toma de decisiones: operadores
de igualdad y relacionales
2.8 Conclusión
Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios
| Hacer la diferencia
40 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
Comentarios
Las líneas 1y 2
// Fig. 2.1: fig02_01.cpp
// Programa para imprimir texto.
comienzan con //, lo cual indica que el resto de cada línea es un comentario. Insertamos comentarios
para documentar nuestros programas y ayudar a que otras personas puedan leerlos y comprenderlos. Los
comentarios no hacen que la computadora realice ninguna acción cuando se ejecuta el programa; el
compilador de C++ los ignora y no genera ningún código objeto en lenguaje máquina. El comentario
Programa para imprimir texto describe el propósito del programa. A un comentario que empieza
con // se le llama comentario de una sola línea, ya que termina al final de la línea actual. [Nota:
también puede usar comentarios que contienen una o más líneas encerradas entre /* y */].
Buena práctica de programación 2.1
Todo programa debe comenzar con un comentario que describa su propósito.
La directiva de preprocesamiento #include
La línea 3
#include iostream // permite al programa imprimir datos en la pantalla
esunadirectivadelpreprocesador,lacualesunmensajeparaelpreprocesadordeC++(quepresentamos
en la sección 1.9). Las líneas que empiezan con # son procesadas por el preprocesador antes de que se
compile el programa. Esta línea indica al preprocesador que debe incluir en el programa el contenido
del encabezado de flujos de entrada/salida iostream. Este encabezado es un archivo que contiene
información que el compilador usa al compilar cualquier programa que muestre datos en la pantalla o
que reciba datos del teclado, mediante el uso de la entrada/salida de flujos al estilo C++. El programa de
la figura 2.1 muestra datos en la pantalla, como pronto veremos. En el capítulo 6 hablaremos con más
detalle sobre encabezados, y en el capítulo 13 explicaremos el contenido de iostream.
Error común de programación 2.1
Olvidar incluir el archivo de encabezado iostream en un programa que reciba datos
del teclado, o que envíe datos a la pantalla, hace que el compilador genere un mensaje de
error.
Líneas en blanco y espacio en blanco
La línea 4 es simplemente una línea en blanco. Los programadores usan líneas en blanco, caracteres de
espacio y caracteres de tabulación (es decir, “tabuladores”) para facilitar la lectura de los programas. En
conjunto, estos caracteres se conocen como espacio en blanco. Por lo general, el compilador ignora los
caracteres de espacio en blanco.
La función main
La línea 5
// la función main comienza la ejecución del programa
es otro comentario de una sola línea, el cual indica que la ejecución del programa empieza en la siguiente
línea.
La línea 6
int main()
2.2 Su primer programa en C++: imprimir una línea de texto 41
forma parte de todo programa en C++. Los paréntesis después de main indican que éste es un bloque de
construcción denominado function. Los programas en C++ comúnmente consisten en una o más
funciones y clases (como aprenderá en el capítulo 3). Exactamente una función en cada programa debe
ser main. La figura 2.1 contiene sólo una función. Los programas en C++ empiezan a ejecutarse en la
función main, aún si main no es la primera función definida en el programa. La palabra clave int a la
izquierda de main indica que “devuelve” un valor entero. Una palabra clave es una palabra en código
reservada para C++, para un uso específico. En la figura 4.3 encontrará la lista completa de palabras
clave de C++. Explicaremos lo que significa que una función “devuelva un valor” cuando le demostre-
mos cómo crear sus propias funciones en la sección 3.3. Por ahora, simplemente incluya la palabra clave
int a la izquierda de main en todos sus programas.
La llave izquierda, {, (línea 7) debe comenzar el cuerpo de toda función. Su correspondiente llave
derecha, }, (línea 11) debe terminar el cuerpo de cada función.
Una instrucción de salida
La línea 8
std::cout  “Bienvenido a C++!n”; // muestra un mensaje
indica a la computadora que debe realizar una acción; a saber, imprimir los caracteres contenidos entre
las comillas dobles. En conjunto, a las comillas y los caracteres entre ellas se les conoce como cadena
(string), cadena de caracteres o literal de cadena. En este libro nos referimos a los caracteres entre
comillas dobles simplemente como cadenas. El compilador no ignora los caracteres de espacio en blan-
co en las cadenas.
A la línea 8 completa, incluyendo std::cout, el operador , la cadena “Bienvenido a C++!n”
y el punto y coma (;), se le conoce como instrucción. La mayoría de las instrucciones en C++ termi-
nan con un punto y coma, lo que también se conoce como terminador de instrucciones (pronto
veremos algunas excepciones a esto). Las directivas del preprocesador (como #include) no terminan
con un punto y coma. Por lo general, en C++ las operaciones de entrada y salida se realizan mediante
flujos de caracteres. Por ende, cuando se ejecuta la instrucción anterior, envía el flujo de caracteres
Bienvenido a C++!n al objeto flujo estándar de salida (std::cout) el cual por lo general está “co-
nectado” a la pantalla.
Error común de programación 2.2
Omitir el punto y coma al final de una instrucción de C++ es un error de sintaxis. La
sintaxis de un lenguaje de programación especifica las reglas para crear programas apro-
piados en ese lenguaje. Un error de sintaxis ocurre cuando el compilador encuentra có-
digo que viola las reglas del lenguaje C++ (es decir, su sintaxis). Por lo general, el compi-
lador genera un mensaje de error para ayudarnos a localizar y corregir el código
incorrecto. A los errores de sintaxis también se les llama errores del compilador, errores
en tiempo de compilación o errores de compilación, ya que el compilador los detecta
durante la fase de compilación. No podrá ejecutar su programa sino hasta que corrija
todos los errores de sintaxis que contenga. Como veremos más adelante, algunos errores de
compilación no son errores de sintaxis.
Buena práctica de programación 2.2
Aplique sangría al cuerpo de cada función un nivel dentro de las llaves que delimitan el
cuerpo de la función. Esto hace que la estructura funcional de un programa resalte y hace
que sea más fácil de leer.
Buena práctica de programación 2.3
Establezca una convención para el tamaño de la sangría que prefiera y luego aplíquela de
manera uniforme. La tecla de tabulación puede usarse para crear sangrías, pero los topes
de tabulación pueden variar. Nosotros preferimos tres espacios por cada nivel de sangría.
42 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
El espacio de nombres std
La instrucción std:: antes de cout se requiere cuando utilizamos nombres que hemos traído al progra-
ma por la directiva del preprocesador #include iostream. La notación std::cout especifica que
estamos usando un nombre (en este caso cout) que pertenece al espacio de nombres std. Los nombres
cin (el flujo de entrada estándar) y cerr (el flujo de error estándar), que presentamos en el capítulo 1,
también pertenecen al espacio de nombres std. (Los espacios de nombres son una característica avan-
zada de C++ que puede consultar en el capítulo 23, Other Topics, que se encuentra en inglés en el sitio
web de este libro). Por ahora, simplemente debe recordar incluir std:: antes de cada mención de cout,
cin y cerr en un programa. Esto puede ser incómodo; en el siguiente ejemplo introduciremos las de-
claraciones using y la directiva using, la cual nos permitirá omitir std:: antes de cada uso de un nom-
bre en el espacio de nombres std.
El operador de inserción de flujo y las secuencias de escape
En el contexto de una instrucción de salida, el operador  se conoce como el operador de inserción
de flujo. Cuando este programa se ejecuta, el valor a la derecha del operador (el operando derecho) se
inserta en el flujo de salida. Observe que el operador apunta en la dirección hacia la que van los datos.
Por lo general los caracteres de la literal de cadena se imprimen exactamente como aparecen entre las
comillas dobles. Sin embargo, los caracteres n no se imprimen en la pantalla (figura 2.1). A la barra
diagonal inversa () se le llama carácter de escape, e indica que se va a imprimir en pantalla un carácter
“especial”. Cuando se encuentra una barra diagonal inversa en una cadena de caracteres, el siguiente
carácter se combina con la barra diagonal inversa para formar una secuencia de escape. La secuencia de
escape n representa una nueva línea. Hace que el cursor (es decir, el indicador de la posición actual en
la pantalla) se desplace al principio de la siguiente línea. Algunas secuencias de escape comunes se listan
en la figura 2.2.
Secuencia
de escape Descripción
n Nueva línea. Coloca el cursor al inicio de la siguiente línea.
t Tabulador horizontal. Desplaza el cursor hasta la siguiente posición de
tabulación.
r Retorno de carro. Coloca el cursor de la pantalla al inicio de la línea
actual; no avanza a la siguiente línea.
a Alerta. Suena la campana del sistema.
 Barra diagonal inversa. Se usa para imprimir un carácter de barra
diagonal inversa.
' Comilla sencilla. Se usa para imprimir un carácter de comilla sencilla.
 Doble comilla. Se usa para imprimir un carácter de doble comilla.
Fig. 2.2  Secuencias de escape.
La instrucción return
La línea 10
return 0; // indica que el programa terminó con éxito
es uno de varios medios que utilizaremos para salir de una función. Cuando se utiliza la instrucción
return al final de main, como se muestra aquí, el valor 0 indica que el programa ha terminado correcta-
mente. La llave derecha, }, (línea 11) indica el fin de la función main. De acuerdo con el estándar de C++,
2.3 Modificación de nuestro primer programa en C++ 43
si la ejecución del programa llega al final de main sin encontrar una instrucción return se asume que el
programa terminó con éxito: exactamente como cuando la última instrucción en main es una instruc-
ción return con el valor 0. Por esta razón, omitiremos la instrucción return al final de main en el resto
de los programas.
Una observación sobre los comentarios
Al escribir un nuevo programa o modificar uno existente, debe mantener actualizados sus comentarios
con el código del programa. A menudo será necesario realizar cambios en los programas existentes; por
ejemplo, para corregir errores (comúnmente conocidos como bugs) que evitan que un programa funcio-
ne de manera correcta, o para mejorar un programa. Al actualizar sus comentarios a medida que realiza
cambios en el código, ayuda a asegurar que los comentarios reflejen con precisión lo que hace el código.
Esto hará que sus programas sean más fáciles de comprender y modificar en el futuro.
2.3Modificación de nuestro primer programa en C++
Ahora le presentaremos dos ejemplos que modifican el programa de la figura 2.1 para imprimir texto en
una línea utilizando varias instrucciones, y para imprimir texto en varias líneas utilizando una sola ins-
trucción.
Cómo mostrar una sola línea de texto con varias instrucciones
Bienvenido a la programacion en C++! puede mostrarse en varias formas. Por ejemplo, la figura 2.3
realiza la inserción de flujos en varias instrucciones (líneas 8 y 9), y produce el mismo resultado que el
programa de la figura 2.1. [Nota: de aquí en adelante, utilizaremos un fondo color gris claro para resaltar
las características clave que se introduzcan en cada programa]. Cada inserción de flujo reanuda la im-
presión en donde se detuvo la anterior. La primera inserción de flujo (línea 8) imprime la palabra Bien-
venido seguida de un espacio, y puesto que esta cadena no terminó con n, la segunda inserción de
flujo (línea 9) empieza a imprimir en la misma línea, justo después del espacio.
1 // Fig. 2.3: fig02_03.cpp
2 // Imprimir una línea de texto con varias instrucciones.
3 #include iostream // permite que el programa envíe datos a la pantalla
4
5 // la función main comienza la ejecución del programa
6 int main()
7 {
8 std::cout  Bienvenido ;
9 std::cout  a C++!n;
10 } // fin de la función main
Bienvenido a C++!
Fig. 2.3  Impresión de una línea de texto con varias instrucciones.
Cómo mostrar varias líneas de texto con una sola instrucción
Una sola instrucción puede mostrar varias líneas mediante el uso de caracteres de nueva línea, como
en la línea 8 de la figura 2.4. Cada vez que se encuentra la secuencia de escape n (nueva línea) en el
flujo de salida, el cursor de la pantalla se coloca al inicio de la siguiente línea. Para obtener una línea
en blanco en sus resultados, coloque dos caracteres de nueva línea, uno después del otro, como en la
línea 8.
44 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
1 // Fig. 2.4: fig02_04.cpp
2 // Impresión de varias líneas de texto con una sola instrucción.
3 #include iostream // permite al programa imprimir datos en la pantalla
4
5 // la función main empieza la ejecución del programa
6 int main()
7 {
8 std::cout  BienvenidonannC++!n;
9 } // fin de la función main
Bienvenido
a
C++!
Fig. 2.4  Impresión de varias líneas de texto con una sola instrucción.
2.4Otro programa en C++: suma de enteros
Nuestro siguiente programa obtiene dos enteros escritos por el usuario mediante el teclado, calcula la
suma de esos valores e imprime el resultado mediante el uso de std::cout. En la figura 2.5 se muestra
el programa, junto con los datos de entrada y salida de ejemplo. En la ejecución de ejemplo resaltamos
la entrada del usuario en negritas. El programa empieza a ejecutarse con la función main (línea 6). La
llave izquierda (línea 7) marca el inicio del cuerpo de main y la correspondiente llave derecha (línea 22)
marca el fin.
1 // Fig. 2.5: fig02_05.cpp
2 // Programa que muestra la suma de dos enteros.
3 #include iostream // permite al programa realizar operaciones de entrada y salida
4
5 // la función main empieza la ejecución del programa
6 int main()
7 {
8 // declaraciones de variables
9 int numero1 = 0; // primer entero a sumar (se inicializa con 0)
10 int numero2 = 0; // segundo entero a sumar (se inicializa con 0)
11 int suma = 0; // suma de numero1 y numero2 (se inicializa con 0)
12
13 std::cout  Escriba el primer entero ; // pide los datos al usuario
14 std::cin  numero1; // lee el primer entero del usuario y lo coloca en numero1
15
16 std::cout  Escriba el segundo entero: ; // pide los datos al usuario
17 std::cin  numero2; // lee el segundo entero del usuario y lo coloca en numero2
18
19 suma = numero1 + numero2; // suma los números; almacena el resultado en suma
20
21 std::cout  La suma es   suma  std::endl; // muestra la suma; fin de línea
22 } // fin de la función main
Escriba el primer entero: 45
Escriba el segundo entero: 72
La suma es 117
Fig. 2.5  Programa que muestra la suma de dos enteros.
2.4 Otro programa en C++: suma de enteros 45
Declaraciones de variables
Las líneas 9 a 11
int numeror1 = 0; // primer entero a sumar (se inicializa con 0)
int numero2 = 0; // segundo entero a sumar (se inicializa con 0)
int suma = 0; // suma de numero1 y numero2 (se inicializa con 0)
son declaraciones. Los identificadores numero1, numero2 y suma son nombres de variables. Una
variable es una ubicación en la memoria de la computadora, en la que puede almacenarse un valor
para usarlo mediante un programa. Estas declaraciones especifican que las variables numero1, numero2
y suma son datos de tipo int, lo cual significa que estas variables contienen valores enteros; es decir,
números enteros tales como 7, –11, 0 y 31914. Las declaraciones también inicializan cada una de
estas variables con 0.
Tip para prevenir errores 2.1
Aunque no siempre es necesario inicializar cada variable de manera explícita, hacerlo
evitará muchos tipos de problemas.
Todas las variables se deben declarar con un nombre y un tipo de datos antes de poder usarlas en un
programa. Pueden declararse diversas variables del mismo tipo en una declaración, o en varias declara-
ciones.Podríamoshaberdeclaradolastresvariablesenunadeclaraciónmedianteunalistaseparadapor
comas, como se muestra a continuación:
int numeror1 = 0, numero2 = 0, suma = 0;
Esto reduce la legibilidad del programa y evita que podamos proporcionar comentarios que describan
el propósito de cada variable.
Buena práctica de programación 2.4
Declare sólo una variable en cada declaración y proporcione un comentario que explique
el propósito de la variable en el programa.
Tipos fundamentales
Pronto hablaremos sobre el tipo de datos double para especificar números reales, y sobre el tipo de datos
char para especificar datos tipo carácter. Los números reales son números con puntos decimales, como
3.4, 0.0 y –11.19. Una variable char puede guardar sólo una letra minúscula, una letra mayúscula, un
dígito o un carácter especial (como $ o *). Los tipos como int, double y char se conocen comúnmente
como tipos fundamentales. Los nombres de los tipos fundamentales consisten de una o más palabras
clave y, por lo tanto, deben aparecer todos en minúsculas. El apéndice C contiene la lista completa de
tipos fundamentales.
Identificadores
El nombre de una variable (como numero1) es cualquier identificador válido que no sea una palabra
clave.Unidentificadoresunaseriedecaracteresqueconsistenenletras,dígitosysímbolosdeguiónbajo
(_) que no empieza con un dígito. C++ es sensible a mayúsculas y minúsculas: las letras mayúsculas y
minúsculas son distintas, por lo que a1 y A1 se consideran identificadores distintos.
Tip de portabilidad 2.1
C++ permite identificadores de cualquier longitud, pero tal vez la implementación de C++
que usted utilice imponga algunas restricciones en cuanto a la longitud de los identificadores.
Use identificadores de 31 caracteres o menos, para asegurar la portabilidad.
46 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
Buena práctica de programación 2.5
Seleccionar nombres significativos para los identificadores ayuda a que un programa se
autodocumente: que una persona pueda comprender el programa con sólo leerlo, en lu-
gar de tener que consultar los comentarios o la documentación del programa.
Buena práctica de programación 2.6
Evite usar abreviaciones en los identificadores. Esto mejora la legibilidad del programa.
Buena práctica de programación 2.7
No use identificadores que empiecen con guiones bajos y dobles guiones bajos, ya que los
compiladores de C++ pueden, de manera interna, utilizar nombres como esos para sus
propios fines. Esto evitará que los nombres que usted elija se confundan con los nombres
que elijan los compiladores.
Colocación de las declaraciones de variables
Las declaraciones de las variables se pueden colocar casi en cualquier parte dentro de un programa, pero
deben aparecer antes de que sus correspondientes variables se utilicen en el programa. Por ejemplo, en el
programa de la figura 2.5, la declaración en la línea 9
int numero1 = 0; // primer entero a sumar (se inicializa con 0)
se podría haber colocado justo antes de la línea 14
std::cin  numero1; // lee el primer entero del usuario y lo coloca en
numero1
la declaración en la línea 10
int numero2 = 0; // segundo entero a sumar (se inicializa con 0)
se podría haber colocado justo antes de la línea 17
std::cin  numero2; // lee el segundo entero del usuario y lo coloca en
numero2
y la declaración en la línea 11
int suma = 0; // suma de numero1 y numero2 (se inicializa con 0)
se podría haber colocado justo antes de la línea 19
suma = numero1 + numero2; // suma los números; almacena el resultado en suma
Obtener el primer valor del usuario
La línea 13
std::cout  Escriba el primer entero: ; // pide los datos al usuario
imprime la cadena Escriba el primer entero: en la pantalla, seguida de un espacio. A este mensaje
se le conoce como indicador debido a que indica al usuario que debe realizar una acción específica. Nos
gusta pronunciar la instrucción anterior como “std::cout obtiene la cadena de caracteres Escriba el
primer entero: .” La línea 14
std::cin  numero1; // lee el primer entero del usuario y lo coloca en
numero1
utiliza el objeto flujo de entrada estándar cin (del espacio de nombres std) y el operador de extrac-
ción de flujo, , para obtener un valor del teclado. Al usar el operador de extracción de flujo con
2.4 Otro programa en C++: suma de enteros 47
std::cin se toma la entrada de caracteres del flujo de entrada estándar, que por lo general es el teclado.
Nos gusta pronunciar la instrucción anterior como “std::cin proporciona un valor a numero1”, o sim-
plemente como “std::cin proporciona numero1”.
Cuando la computadora ejecuta la instrucción anterior, espera a que el usuario introduzca un valor
para la variable numero1. El usuario responde escribiendo un entero (en forma de caracteres), y después
oprime la tecla Intro (a la que algunas veces se le conoce como tecla Return) para enviar los caracteres a
la computadora. Ésta a su vez convierte la representación de caracteres del número en un entero, y asig-
na (copia) este número (o valor) a la variable numero1. Cualquier referencia posterior a numero1 en este
programa utilizará este mismo valor.
Los objetos flujo std::cout y std::cin facilitan la interacción entre el usuario y la computadora.
Claro que los usuarios pueden introducir datos inválidos desde el teclado. Por ejemplo, cuando su
programa espera que el usuario introduzca un entero, éste podría introducir caracteres alfabéticos, sím-
bolos especiales (como # o @) o un número con un punto decimal (como 73.5), entre otros. En estos
primeros programas asumimos que el usuario introduce datos válidos. A medida que progrese por el li-
bro, aprenderá varias técnicas para lidiar con el amplio rango de posibles problemas de introducción
de datos.
Obtener el segundo valor del usuario
La línea 16
std::cout  Escriba el segundo entero: ; // pide los datos al usuario
imprime el mensaje Escriba el segundo entero: en la pantalla, pidiendo al usuario que realice una
acción. En la línea 17
std::cin  numero2; // lee el segundo entero del usuario y lo coloca en
numero2
se obtiene un valor para la variable numero2 de parte del usuario.
Calcular la suma de los valores introducidos por el usuario
La instrucción de asignación en la línea 19
suma = numero1 + numero2; // suma los números; almacena el resultado
en suma
calcula la suma de las variables numero1 y numero2, y asigna el resultado a la variable suma mediante el uso
del operador de asignación =. La instrucción se lee como “suma obtiene el valor de numero1 + numero2.”
Lamayoríadeloscálculosserealizaneninstruccionesdeasignación.Losoperadores= y+ seconocencomo
operadores binarios, ya que cada uno de ellos tiene dos operandos. En el caso del operador +, los dos
operandos son numero1 y numero2. En el caso del operador = anterior, los dos operandos son suma y el
valor de la expresión numero1 + numero2.
Buena práctica de programación 2.8
Coloque espacios en cualquier lado de un operador binario. Esto hace que el operador
resalte y mejora la legibilidad del programa.
Mostrar el resultado
La línea 21
std::cout  La suma es   suma  std::endl; // muestra la suma; fin de
línea
muestra a la cadena de caracteres La suma es, seguida del valor numérico de la variable suma seguido de
std::endl, a éste último se le conoce como manipulador de flujos. El nombre endl es una abreviación
48 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
de “fin de línea” (“end of line”, en inglés) y pertenece al espacio de nombres std. El manipulador de
flujos std::endl imprime una nueva línea y después “vacía el búfer de salida”. Esto simplemente signi-
fica que, en algunos sistemas en donde los datos de salida se acumulan en el equipo hasta que haya sufi-
cientes como para que “valga la pena” mostrarlos en pantalla, std::endl obliga a que todos los datos de
salida acumulados se muestren en ese momento. Esto puede ser importante cuando los datos de salida
piden al usuario una acción, como introducir datos.
La instrucción anterior imprime múltiples valores de distintos tipos. El operador de inserción de
flujo “sabe” cómo imprimir cada tipo de datos. Al uso de varios operadores de inserción de flujo () en
unasolainstrucciónseleconocecomooperacionesdeinsercióndeflujoencascada,concatenamien-
to o encadenamiento.
Los cálculos también se pueden realizar en instrucciones de salida. Podríamos haber combinado las
instrucciones en las líneas 19 y 21 en la instrucción
std::cout  La suma es   numero1 + numero2  std::endl;
con lo cual se elimina la necesidad de usar la variable suma.
Una poderosa característica de C++ es que los usuarios pueden crear sus propios tipos de datos
conocidos como clases (presentaremos esta herramienta en el capítulo 3 y la exploraremos con detalle
en el capítulo 9). Los usuarios pueden entonces “enseñar” a C++ cómo debe recibir y mostrar valores de
estos nuevos tipos de datos, usando los operadores  y  (a esto se le conoce como sobrecarga
de operadores; un tema que exploraremos en el capítulo 10).
2.5Conceptos acerca de la memoria
Los nombres de variables como numero1, numero2 y suma en realidad corresponden a las ubicaciones
en la memoria de la computadora. Cada variable tiene un nombre, un tipo, un tamaño y un valor.
En el programa de suma de la figura 2.5, cuando se ejecuta la instrucción
std::cin  numero1; // lee el primer entero del usuario y lo coloca en
numero1
en la línea 14, el entero que escribe el usuario se coloca en una ubicación de memoria a la que el compi-
lador haya asignado el nombre numero1. Suponga que el usuario introduce el número 45 como el valor
para numero1. La computadora colocará el 45 en la ubicación numero1, como se muestra en la figura 2.6.
Cuando se coloca un valor en una ubicación en memoria, ese valor sobrescribe al valor anterior en esa
ubicación; por ende, se dice que la acción de colocar un nuevo valor en una ubicación en memoria es
una operación destructiva.
45
numero1
Fig. 2.6  Ubicación de memoria que muestra el nombre y el valor de la variable numero1.
Regresando a nuestro programa de suma, suponga que el usuario introduce el valor 72 cuando se
ejecuta la instrucción
std::cin  numero2; // lee el segundo entero del usuario y lo coloca en
numero2
Este valor se coloca en la ubicación numero2, y la memoria aparece como se muestra en la figura 2.7. Las
ubicaciones de las variables no necesariamente están adyacentes en la memoria.
Una vez que el programa obtiene valores para numero1 y numero2, los suma y coloca el resultado de
esta suma en la variable suma. La instrucción
suma = numero1 + numeror2; // suma los números; almacena el resultado en suma
2.6 Aritmética 49
45
72
numero1
numero2
Fig. 2.7  Ubicaciones de memoria, después de almacenar valores en las variables para numero1
y numero2.
reemplaza el valor que estaba almacenado en suma. La suma calculada de numero1 y numero2 se coloca
en la variable suma sin importar qué valor haya tenido suma anteriormente; ese valor se pierde. Después
de calcular suma, la memoria aparece como se muestra en la figura 2.8. Los valores de numero1 y numero2
aparecen exactamente como estaban antes del cálculo. Se utilizaron estos valores pero no se destruyeron,
en el momento en el que la computadora realizó el cálculo. Por ende, cuando se lee un valor de una
ubicación de memoria, la operación es no destructiva.
45
72
117
numero1
numero2
suma
Fig. 2.8  Ubicaciones de memoria, después de calcular y almacenar la suma numero1
y numero2.
2.6Aritmética
La mayoría de los programas realizan cálculos aritméticos. Los operadores aritméticos de C++ se sin-
tetizan en la figura 2.9. Observe el uso de varios símbolos especiales que no se utilizan en álgebra. El
asterisco (*) indica la multiplicación y el signo de porcentaje (%) es el operador módulo que describire-
mos en breve. Los operadores aritméticos en la figura 2.9 son operadores binarios; es decir, funcionan
con dos operandos. Por ejemplo, la expresión numero1 + numero2 contiene el operador binario + y los
dos operandos numero1 y numero2.
La división de enteros (es decir, en donde tanto el numerador como el denominador son enteros)
produce un cociente entero; por ejemplo, la expresión 7 / 4 da como resultado 1 y la expresión 17 / 5 da
Operación en C++
Operador
aritmético de C++
Expresión
algebraica
Expresión en
C++
Suma + f + 7 f + 7
Resta - p – c p - c
Multiplicación * bm o b ×m b * m
División / x / y o x
y o x ÷ y x / y
Módulo % r mod s r % s
Fig. 2.9  Operadores aritméticos.
50 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
como resultado 3. Cualquier parte fraccionaria en una división de enteros se trunca (es decir, se descarta);
no ocurre un redondeo.
C++ proporciona el operador módulo, %, el cual produce el residuo después de la división entera. El
operador módulo sólo se puede utilizar con operandos enteros. La expresión x % y produce el residuo
después de que x se divide entre y. Por lo tanto, 7 % 4 produce 3 y 17 % 5 produce 2. En capítulos poste-
riores consideramos muchas aplicaciones interesantes del operador módulo, como determinar si un
número es múltiplo de otro (un caso especial de esta aplicación es determinar si un número es par o
impar).
Expresiones aritméticas en formato de línea recta
En la computadora, las expresiones aritméticas en C++ deben escribirse en formato de línea recta. Por
lo tanto, las expresiones como “a dividida entre b” deben escribirse como a / b, de manera que todas las
constantes, variables y operadores aparezcan en línea recta. La siguiente notación algebraica:
a
b
no es generalmente aceptable para los compiladores, aunque ciertos paquetes de software de propósito
especial soportan una notación más natural para las expresiones matemáticas complejas.
Paréntesis para agrupar subexpresiones
Los paréntesis se utilizan en las expresiones en C++ de la misma manera que en las expresiones algebrai-
cas. Por ejemplo, para multiplicar a por la cantidad b + c, escribimos a * ( b + c ).
Reglas de precedencia de operadores
C++ aplica los operadores en expresiones aritméticas en una secuencia precisa, determinada por las si-
guientes reglas de precedencia de operadores, que generalmente son las mismas que las que se utilizan
en álgebra:
1. Los operadores en las expresiones contenidas dentro de pares de paréntesis se evalúan primero.
Se dice que los paréntesis tienen el “nivel más alto de precedencia”. En casos de paréntesis
anidados o incrustados, como:
( a * ( b + c ) )
los operadores en el par más interno de paréntesis se aplican primero.
2. Las operaciones de multiplicación, división y módulo se aplican a continuación. Si una expre-
sión contiene varias de esas operaciones, los operadores se aplican de izquierda a derecha. Se
dice que los operadores de multiplicación, división y módulo tienen el mismo nivel de prece-
dencia.
3. Las operaciones de suma y resta se aplican al último. Si una expresión contiene varias de esas
operaciones, los operadores se aplican de izquierda a derecha. Los operadores de suma y resta
tienen el mismo nivel de precedencia.
Las reglas de precedencia de operadores definen el orden en el que C++ aplica los operadores. Cuan-
do decimos que ciertos operadores se aplican de izquierda a derecha, nos referimos a su asociatividad.
Por ejemplo, los operadores de suma (+) en la expresión
a + b + c
se asocian de izquierda a derecha, por lo que a + b se calcula primero, y después se agrega c a esa suma
para determinar el valor de toda la expresión. Más adelante veremos que algunos operadores se asocian
de derecha a izquierda. En la figura 2.10 se sintetizan estas reglas de precedencia de operadores. Expan-
diremos esta tabla a medida que introduzcamos operadores adicionales de C++. En el apéndice A se
incluye una tabla de precedencia completa.
2.6 Aritmética 51
Operador(es) Operación(es) Orden de evaluación (precedencia)
( ) Paréntesis Se evalúa primero. Si los paréntesis son anidados,
como en la expresión (a * ( b + c / d + e ) ), la
expresión en el par más interno se evalúa primero.
[Precaución: si tiene una expresión como (a + b) *
(c - d) en donde dos conjuntos de paréntesis no
están anidados, pero aparecen “en el mismo nivel”,
en C++ estándar no no especifica el orden en el
que se evaluarán estas subexpresiones entre
paréntesis].
*
/
%
Multiplicación
División
Módulo
Se evalúan en segundo lugar. Si hay varios
operadores de este tipo, se evalúan de izquierda a
derecha.
+
-
Suma
Resta
Se evalúan al último. Si hay varios operadores de
este tipo, se evalúan de izquierda a derecha.
Fig. 2.10  Precedencia de los operadores aritméticos.
Ejemplos de expresiones algebraicas y de C++
Ahora, consideremos varias expresiones en vista de las reglas de precedencia de operadores. Cada ejem-
plo lista una expresión algebraica y su equivalente en C++. El siguiente es un ejemplo de una media
(promedio) aritmética de cinco términos:
Álgebra: a + b + c + d + e
5
m =
C++: m = ( a + b + c + d + e ) / 5;
Los paréntesis son obligatorios, ya que la división tiene una mayor precedencia que la suma. La canti-
dad completa ( a + b + c + d + e ) va a dividirse entre 5. Si los paréntesis se omiten por error, obtenemos
a + b + c + d + e / 5, que se evalúa incorrectamente como:
a + b + c + d + e
5
El siguiente es un ejemplo de la ecuación de una línea recta:
Álgebra: y = mx + b
C++: y = m * x + b;
No se requieren paréntesis. El operador de multiplicación se aplica primero, ya que la multiplicación
tiene una mayor precedencia sobre la suma.
El siguiente ejemplo contiene las operaciones módulo (%), multiplicación, división, suma, resta y
de asignación:
z
6 1 2 4 3 5
= p * r % q + w / x - y;
z = pr%q + w/x – y
Álgebra:
C++:
52 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
Los números dentro de los círculos bajo la instrucción indican el orden en el que C++ aplica los opera-
dores. Las operaciones de multiplicación, residuo y división se evalúan primero en orden de izquierda a
derecha (es decir, se asocian de izquierda a derecha), ya que tienen mayor precedencia que la suma y la
resta. Las operaciones de suma y resta se aplican a continuación. Estas operaciones también se aplican
de izquierda a derecha. El operador de asignación se aplica al último, ya que su precedencia es menor que
la de cualquiera de los operadores aritméticos.
Evaluación de un polinomio de segundo grado
Para desarrollar una mejor comprensión de las reglas de precedencia de operadores, considere la evalua-
ción de un polinomio de segundo grado y = ax 2
+ bx + c:
Los números dentro de los círculos debajo de la instrucción indican el orden en el que C++ aplica los
operadores. En C++ no hay operador aritmético para la exponenciación, por lo que hemos representado a
x 2
como x * x. En el capítulo 5 hablaremos sobre la función pow (“power”o potencia) de la biblioteca
estándar que realiza la exponenciación.
Suponga que las variables a, b, c y x en el polinomio de segundo grado anterior se inicializan como
sigue: a = 2, b = 3, c = 7 y x = 5. La figura 2.11 muestra el orden en el que se aplican los operadores y el
valor final de la expresión.
(Multiplicación de más a la izquierda)
(Multiplicación de más a la izquierda)
(Multiplicación antes de la suma)
(Suma de más a la izquierda)
(Última suma)
(Última operación; colocar 72 en y)
Paso 1. y = 2 * 5 * 5 + 3 * 5 + 7;
2 * 5 es 10
Paso 2. y = 10 * 5 + 3 * 5 + 7;
10 * 5 es 50
Paso 3. y = 50 + 3 * 5 + 7;
3 * 5 es 15
Paso 4. y = 50 + 15 + 7;
50 + 15 es 65
Paso 5. y = 65 + 7;
65 + 7 es 72
Paso 6. y = 72
Fig. 2.11  Orden en el cual se evalúa un polinomio de segundo grado.
6 1 2 4 3 5
y = a * x * x + b * x + c;
2.7 Toma de decisiones: operadores de igualdad y relacionales 53
Paréntesis redundantes
Al igual que en álgebra, es aceptable colocar paréntesis innecesarios en una expresión para hacer que ésta
sea más clara. A dichos paréntesis innecesarios se les llama paréntesis redundantes. Por ejemplo, la
instrucción de asignación anterior podría colocarse entre paréntesis, de la siguiente manera:
y = ( a * x * x ) + ( b * x ) + c;
2.7Toma de decisiones: operadores de igualdad y relacionales
Ahora presentaremos una versión simple de la instrucción if de C++, la cual permite que un programa
tome una acción alternativa, con base en la verdad o falsedad de cierta condición. Si la condición es
verdadera, se ejecuta la instrucción que está en el cuerpo de la instrucción if. Si la condición es falsa, el
cuerpo no se ejecuta. En breve veremos un ejemplo.
Las condiciones en las instrucciones if pueden formarse utilizando los operadores de igualdad y los
operadores relacionales, que se sintetizan en la figura 2.12. Todos los operadores relacionales tienen el
mismo nivel de precedencia y se asocian de izquierda a derecha. Ambos operadores de igualdad tienen
el mismo nivel de precedencia, que es menor que la precedencia de los operadores relacionales, y se asocian
de izquierda a derecha.
Operador algebraico de
igualdad o relacional
Operador de igualdad
o relacional de C++
Ejemplo de
condición
en C++
Significado de la
condición
en C++
Operadores relacionales
  x  y x es mayor que y
  x  y x es menor que y
≥ = x = y x es mayor o igual que y
≤ = x = y x es menor o igual que y
Operadores de igualdad
= == x == y x es igual a y
≠ != x != y x no es igual a y
Fig. 2.12  Operadores de igualdad y relacionales.
Error común de programación 2.3
Invertir el orden del par de símbolos en cualquiera de los operadores !=, = y = (al escri-
birlos como =!, = y =, respectivamente) es comúnmente un error de sintaxis. En algunos
casos, escribir != como =! no será un error de sintaxis, sino casi con certeza será un error
lógico, el cual tiene un efecto en tiempo de ejecución. En el capítulo 5 comprenderá esto,
cuando aprenda acerca de los operadores lógicos. Un error lógico fatal hace que un progra-
ma falle y termine antes de tiempo. Un error lógico no fatal permite que un programa
continúe ejecutándose, pero por lo general produce resultados incorrectos.
Error común de programación 2.4
Confundir el operador de igualdad == con el operador de asignación = produce errores lógi-
cos. El operador de igualdad se debe leer como “es igual a” o “doble igual”, y el operador
de asignación se debe leer como “obtiene” u “obtiene el valor de”, o “se le asigna el valor de”.
Como veremos en la sección 5.9, confundir estos operadores no necesariamente puede provo-
car un error de sintaxis fácil de reconocer, pero puede producir errores lógicos sutiles.
54 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
Uso de la instrucción if
El siguiente ejemplo (figura 2.13) utiliza seis instrucciones if para comparar dos números introducidos
porelusuario.Sisesatisfacelacondiciónencualquieradeestasinstruccionesif,seejecutalainstrucción
de salida asociada con esa instrucción if.
1 // Fig. 2.13: fig02_13.cpp
2 // Comparación de enteros mediante instrucciones if, operadores
3 // relacionales y operadores de igualdad.
4 #include iostream // permite al programa realizar operaciones de entrada y
salida
5
6 using std::cout; // el programa usa cout
7 using std::cin; // el programa usa cin
8 using std::endl; // el programa usa endl
9
10 // la función main empieza la ejecución del programa
11 int main()
12 {
13 int numero1 = 0; // primer entero a comparar (se inicializa con 0)
14 int numero2 = 0; // segundo entero a comparar (se inicializa con 0)
15
16 cout  Escriba dos enteros a comparar: ; // pide los datos al usuario
17 cin  numero1  numero2; // lee dos enteros del usuario
18
19 if ( numero1 == numero2 )
20 cout  numero1  “ == “  numero2  endl;
21
22 if ( numero1 != numero2 )
23 cout  numero1  “ != “  numero2  endl;
24
25 if ( numero1  numero2 )
26 cout  numero1  “  “  numero2  endl;
27
28 if ( numero1  numero2 )
29 cout  numero1  “  “  numero2  endl;
30
31 if ( numero1 = numero2 )
32 cout  numero1  “ = “  numero2  endl;
33
34 if ( numero1 = numero2 )
35 cout  numero1  “ = “  numero2  endl;
36 } // fin de la función main
Escriba dos enteros a comparar: 3 7
3 != 7
3  7
3 = 7
Escriba dos enteros a comparar: 22 12
22 != 12
22  12
22 = 12
Fig. 2.13  Comparación de enteros mediante instrucciones if, operadores relacionales y operadores
de igualdad (parte 1 de 2).
2.7 Toma de decisiones: operadores de igualdad y relacionales 55
Escriba dos enteros a comparar: 7 7
7 == 7
7 = 7
7 = 7
Declaraciones using
Las líneas 6 a 8:
using std::cout; // el programa usa cout
using std::cin; // el programa usa cin
using std::endl; // el programa usa endl
son declaraciones using que eliminan la necesidad de repetir el prefijo std:: que usamos en programas
anteriores. Ahora podemos escribir cout en vez de std::cout, cin en vez de std::cin y endl en vez de
std::endl, respectivamente, en el resto del programa.
En lugar de las líneas 6 a 8, muchos programadores prefieren usar la directiva using:
using namespace std;
la cual permite que un programa utilice todos los nombres en cualquier encabezado estándar de C++
(como iostream) que un programa pudiera incluir. Desde este punto en adelante en el libro, usare-
mos la directiva anterior en nuestros programas.1
Declaraciones de variables y lectura de las entradas del usuario
Las líneas 13 y 14:
int numero1 = 0; // primer entero a comparar (se inicializa con 0)
int numero2 = 0; // segundo entero a comparar (se inicializa con 0)
declaran las variables utilizadas en el programa y las inicializan con 0.
El programa usa operaciones de extracción de flujos en cascada (línea 17) para recibir dos enteros
como entrada. Recuerde que podemos escribir cin (en vez de std::cin) debido a la línea 7. Primero se
lee un valor y se coloca en la variable number1, luego se lee un valor y se coloca en la variable number2.
Comparación de números
La instrucción if en las líneas 19 y 20:
if ( numero1 == numero2 )
cout  numero1   ==   numero2  endl;
compara los valores de las variables numero1 y numero2 para probar su igualdad. Si los valores son igua-
les, la instrucción en la línea 20 muestra una línea de texto que indica que los números son iguales. Si
las condiciones son true en una o más de las instrucciones if que empiezan en las líneas 22, 25, 28, 31
y 34, la correspondiente instrucción del cuerpo muestra una línea de texto apropiada.
Cada instrucción if en la figura 2.13 tiene una sola instrucción en su cuerpo y cada instrucción del
cuerpo tiene sangría. En el capítulo 4 le mostraremos cómo especificar instrucciones if con cuerpos con
múltiples instrucciones (encerrando las instrucciones del cuerpo en un par de llaves, { }, para crear lo
que se conoce como instrucción compuesta o bloque).
Fig. 2.13  Comparación de enteros mediante instrucciones if, operadores relacionales y operadores
de igualdad (parte 2 de 2).
1 En el capítulo 23, Other Topics (en inglés, que se encuentra en el sitio web), hablaremos sobre algunas directivas
using en sistemas de gran escala.
56 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
Buena práctica de programación 2.9
Aplique sangría a la(s) instrucción(es) en el cuerpo de una instrucción if para mejorar la
legibilidad.
Error común de programación 2.5
Colocar un punto y coma justo después del paréntesis derecho que va después de la condi-
ción en una instrucción if es, generalmente, un error lógico (aunque no es un error de
sintaxis). El punto y coma hace que el cuerpo de la instrucción if esté vacío, por lo que
esta instrucción no realiza ninguna acción, sin importar que la condición sea verdadera
o no. Peor aún, la instrucción del cuerpo original de la instrucción if ahora se convierte
en una instrucción en secuencia con la instrucción if y siempre se ejecuta, lo cual a me-
nudo ocasiona que el programa produzca resultados incorrectos.
Espacio en blanco
Observe el uso del espacio en blanco en la figura 2.13. Recuerde que, por lo general, el compilador ig-
nora los caracteres de espacio en blanco, como tabuladores, nuevas líneas y espacios. Por lo tanto, las
instrucciones pueden dividirse en varias líneas y espaciarse de acuerdo con las preferencias del progra-
mador. Es un error de sintaxis dividir identificadores, cadenas (como hola) y constantes (como el
número 1000) a través de varias líneas.
Buena práctica de programación 2.10
Una instrucción larga puede esparcirse en varias líneas. Si hay que dividir una sola
instrucción entre varias líneas, elija puntos que tengan sentido para hacer la división,
como después de una coma en una lista separada por comas, o después de un operación en
una expresión larga. Si una instrucción se divide entre dos o más líneas, aplique sangría
a todas las líneas subsecuentes y alinee a la izquierda el grupo de líneas con sangría.
Precedencia de operadores
La figura 2.14 muestra la precedencia y asociatividad de los operadores que se presentan en este capítu-
lo. Los operadores se muestran de arriba a abajo, en orden descendente de precedencia. Todos estos
operadores, con la excepción del operador de asignación, =, se asocian de izquierda a derecha. La suma
es asociativa a la izquierda, por lo que una expresión como x + y + z se evalúa como si se hubiera escrito
así: (x + y) + z. El operador de asignación = asocia de derecha a izquierda, por lo que una expresión tal
como x = y = 0 se evalúa como si se hubiera escrito así: x = (y = 0), donde, como pronto veremos, prime-
ro se asigna el 0 a y y luego se asigna el resultado de esa asignación (0) a x.
Operadores Asociatividad Tipo
() [vea la precaución
en la figura 2.10]
paréntesis para agrupar
* / % izquierda a derecha multiplicativa
+ - izquierda a derecha suma
  izquierda a derecha inserción/extracción de flujo
 =  = izquierda a derecha relacional
== != izquierda a derecha igualdad
= derecha a izquierda asignación
Fig. 2.14  Precedencia y asociatividad de los operadores descritos hasta ahora.
2.8 Conclusión 57
Buena práctica de programación 2.11
Consulte la tabla de precedencia y asociatividad de operadores (apéndice A) cuando escri-
ba expresiones que contengan muchos operadores. Confirme que los operadores en la ex-
presión se ejecuten en el orden que usted espera. Si no está seguro en cuanto al orden de
evaluación en una expresión compleja, divida la expresión en instrucciones más pequeñas
o use paréntesis para forzar el orden de evaluación de la misma forma como lo haría en
una expresión algebraica. Asegúrese de observar que ciertos operadores, como la asignación
(=), se asocien de derecha a izquierda en lugar de hacerlo de izquierda a derecha.
2.8Conclusión
En este capítulo aprendió muchas de las herramientas básicas importantes de C++, como mostrar datos
enlapantalla,recibirdatosdeltecladoydeclararvariablesdetiposfundamentales.Enespecial,aprendió
a utilizar el objeto flujo de salida cout y el objeto flujo de entrada cin para crear programas interactivos
simples. Le explicamos cómo se almacenan y recuperan las variables de memoria. También aprendió a
usar los operadores aritméticos para realizar cálculos. Hablamos sobre el orden en el que C++ aplica los
operadores (es decir, las reglas de precedencia de operadores), así como de la asociatividad de éstos.
Además aprendió cómo la instrucción if de C++ permite a un programa tomar decisiones. Por último
le presentamos los operadores de igualdad y relacionales, que se utilizan para formar condiciones en las
instrucciones if.
Las aplicaciones no orientadas a objetos que presentamos en este capítulo lo introdujeron a los
conceptos básicos de programación. Como verá en el capítulo 3, las aplicaciones de C++ por lo general
contienen sólo unas cuantas líneas de código en la función main: estas instrucciones normalmente crean
los objetos que realizan el trabajo de la aplicación y después los objetos “se hacen cargo a partir de aquí”.
En el capítulo 3 aprenderá a implementar sus propias clases y a usar objetos de esas clases en las aplica-
ciones.
Resumen
Sección 2.2 Su primer programa en C++: imprimir una línea de texto
• Los comentarios de una sola línea (pág. 40) comienzan con //. Insertamos comentarios para documentar los
programas y mejorar su legibilidad.
• Los comentarios no provocan que la computadora realice ninguna acción (pág. 41) cuando se ejecuta el progra-
ma: el compilador los ignora y no provocan que se genere ningún código objeto en lenguaje máquina.
• Una directiva de preprocesamiento (pág. 40) comienza con # y es un mensaje para el preprocesador de C++. Las
directivas de preprocesamiento se procesan antes de que se compile el programa.
• La línea #include iostream (pág. 40) indica al preprocesador de C++ que debe incluir el contenido del enca-
bezado del flujo de entrada/salida, que contiene la información necesaria para compilar programas que usen
std::cin (pág. 46) y std::cout (pág. 41) junto con los operadores de inserción de flujo (, pág. 42) y de ex-
tracción de flujo (, pág. 46).
• El espacio en blanco (es decir líneas en blanco, caracteres de espacio y caracteres de tabulación, pág. 40) hace
que los programas sean más fáciles de leer. El compilador ignora los caracteres de espacio en blanco que se en-
cuentran fuera de las literales de cadena.
• Los programas en C++ empiezan a ejecutarse en main (pág. 41), incluso aunque main no aparezca primero en el
programa.
• La palabra clave int a la izquierda de main indica que esta función “devuelve” un valor entero.
58 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
• El cuerpo (pág. 41) de toda función debe estar encerrado entre llaves ({ y }).
• Una cadena ( string) (pág. 41) entre comillas dobles se conoce algunas veces como cadena de caracteres, men-
saje o literal de cadena. El compilador no ignora los caracteres de espacio en blanco en las cadenas.
• La mayoría de las instrucciones de C++ (pág. 41) terminan con un punto y coma, también conocido como
terminador de instrucción (pronto veremos algunas excepciones a esto).
• En C++, las operaciones de entrada y salida se realizan mediante flujos (pág. 41) de caracteres.
• El objeto flujo de salida std::cout (que por lo general está conectado a la pantalla) se utiliza para mostrar
datos. Es posible mostrar múltiples elementos de datos mediante la concatenación de operadores de inserción
de flujo ().
• Elobjetoflujodeentradastd::cin (queporlogeneralestáconectadoalteclado)seutilizaparaintroducirdatos.
Es posible introducir múltiples elementos de datos mediante la concatenación de operadores de extracción de
flujo ().
• La notación std::cout especifica que utilizamos cout del “espacio de nombres” std.
• Cuando hay una barra diagonal (es decir, un carácter de escape) en una cadena de caracteres, el siguiente carác-
ter se combina con la barra diagonal para formar una secuencia de escape (pág. 42).
• La secuencia de escape de nueva línea n (pág. 42) mueve el cursor al principio de la siguiente línea en la pantalla.
• Un mensaje que indica al usuario que realice una acción específica se conoce como indicador (prompt)
(pág. 46).
• La palabra return de C++ (pág. 42) es uno de varios medios para salir de una función
Sección 2.4 Otro programa en C++: suma de enteros
• Hay que declarar todas las variables (pág. 45) en un programa en C++ antes de poder usarlas.
• El nombre de una variable es cualquier identificador válido (pág. 45) que no sea una palabra clave. Un identifi-
cadoresunaseriedecaracteresqueconsistenenletras,dígitosyguionesbajos(_).Losidentificadoresnopueden
comenzar con un dígito. Su nombre puede ser de cualquier longitud, aunque tal vez algunos sistemas o imple-
mentaciones de C++ impongan restricciones en cuanto al largo.
• C++ es sensible al uso de mayúsculas y minúsculas (pág. 45).
• La mayoría de los cálculos se realizan en instrucciones de asignación (pág. 47).
• Una variable es una ubicación en memoria (pág. 48) en donde se puede almacenar un valor para que lo utilice
un programa.
• Las variables de tipo int (pág. 45) contienen valores enteros; es decir, números integer como 7, –11, 0, 31914.
Sección 2.5 Conceptos acerca de la memoria
• Toda variable almacenada en la memoria de la computadora tiene un nombre, valor, tipo y tamaño.
• Cada vez que se coloca un nuevo valor en una ubicación de memoria, el proceso es destructivo (pág. 48); es
decir, el nuevo valor reemplaza al valor anterior en esa ubicación. El valor anterior se pierde.
• Cuando se lee un valor de la memoria, el proceso es no destructivo (pág. 49); es decir, se lee una copia del valor
y el original queda sin ser perturbado en la ubicación de memoria.
• El manipulador de flujos std::endl (pág. 47) imprime una nueva línea y luego “vacía el búfer de salida”.
Sección 2.6 Aritmética
• C++ evalúa las expresiones aritméticas (pág. 49) en una secuencia precisa determinada por las reglas de prece-
dencia de operadores (pág. 50) y la asociatividad (pág. 50).
• Pueden usarse paréntesis para agrupar expresiones.
• La división de enteros (pág. 49) produce un cociente entero. Cualquier parte fraccional en la división de enteros se
trunca.
• El operador módulo % (pág. 50) produce el residuo después de la división de enteros.
Ejercicios de autoevaluación 59
Sección 2.7 Toma de decisiones: operadores de igualdad y relacionales
• La instrucción if (pág. 53) permite a un programa tomar una acción alternativa con base en el cumplimiento
de una condición. El formato para una instrucción if es:
if ( condición )
instrucción;
Si la condición es verdadera, se ejecuta la instrucción en el cuerpo del if. Si la condición no se cumple (es falsa),
se brinca el cuerpo de la instrucción.
• Por lo general, las condiciones en las instrucciones if se forman mediante el uso de operadores de igualdad y
relacionales (pág. 53). El resultado de usar esos operadores siempre es un valor verdadero o falso.
• La siguiente declaración using (pág. 55):
using std::cout;
informa al compilador en dónde encontrar cout (namespace std) y elimina la necesidad de repetir std:: pre-
fijo. La siguiente directiva using (pág. 55):
using namespace std;
permitealprogramausarenelencabezadotodoslosnombresincluidosdecualquierbibliotecaestándardeC++.
Ejercicios de autoevaluación
2.1 Complete las siguientes oraciones:
a) Todo programa en C++ empieza su ejecución en la función __________.
b) Un(a) __________ empieza el cuerpo de toda función y un(a) __________ lo termina.
c) La mayoría de las instrucciones de C++ terminan con un(a) __________.
d) La secuencia de escape n representa el carácter __________, el cual hace que el cursor se posicione al
principio de la siguiente línea en la pantalla.
e) La instrucción __________ se utiliza para tomar decisiones.
2.2 Indique si cada una de las siguientes instrucciones es verdadera o falsa. Si es falsa, explique por qué. Asuma
que se usa la instrucción using std::cout.
a) Los comentarios hacen que la computadora imprima, en la pantalla, el texto que va después de los
caracteres / /, al ejecutarse el programa.
b) Cuando la secuencia de escape n, se imprime con cout y el operador de inserción de flujo causa que
el cursor se posicione al principio de la siguiente línea en la pantalla.
c) Todas las variables deben declararse antes de utilizarlas.
d) Todas las variables deben ser de un tipo al declararlas.
e) C++ considera que las variables numero y NuMeRo son idénticas.
f) Las declaraciones pueden aparecer casi en cualquier parte del cuerpo de una función de C++.
g) El operador módulo (%) se puede utilizar sólo con operandos enteros.
h) Los operadores aritméticos *, /, %, + y – tienen todos el mismo nivel de precedencia.
i) Un programa en C++ que imprime tres líneas de salida debe contener tres instrucciones en las que se
utilicen cout y el operador de inserción de flujo.
2.3 Escriba una sola instrucción en C++ para realizar cada una de las siguientes tareas (suponga que no se han
utilizado declaraciones using ni la directiva using):
a) Declarar las variables c, estaEsUnaVariable, q76354 y numero como de tipo int (en una instrucción).
b) Pedir al usuario que introduzca un entero.Termine su mensaje con un signo de dos puntos (:) seguido
de un espacio, y deje el cursor posicionado después del espacio.
c) Recibir un entero como entrada del usuario mediante el teclado y almacenarlo en la variable entera
edad.
d) Si la variable numero no es igual a 7, imprimir La variable numero no es igual a 7.
e) Imprimir en una línea el mensaje Este es un programa en C++.
f) Imprimir en dos líneas el mensaje Este es un programa en C++.Termine la primera línea con C++.
60 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
g) Imprimir el mensaje Este es un programa en C++ con cada palabra en una línea separada.
h) Imprimir el mensaje Este es un programa en C++. Separe una palabra de otra mediante un tabu-
lador.
2.4 Escriba una declaración (o comentario) para realizar cada una de las siguientes tareas (suponga que se han
utilizado declaraciones using para cin, cout y endl):
a) Indicar que un programa calculará el producto de tres enteros.
b) Declarar las variables x, y, z y resultado de tipo int (en instrucciones separadas) e inicializar cada una
con 0.
c) Pedir al usuario que escriba tres enteros.
d) Recibir tres enteros del teclado y almacenarlos en las variables x, y y z.
e) Calcular el producto de los tres enteros contenidos en las variables x, y y z, y asignar el resultado a la
variable resultado.
f) Imprimir El producto es  seguido del valor de la variable resultado.
g) Devolver un valor de main, para indicar que el programa terminó correctamente.
2.5 Utilizando las instrucciones que escribió en el ejercicio 2.4, escriba un programa completo que calcule e
imprima el producto de tres enteros. Agregue comentarios al código donde sea apropiado. [Nota: necesitará escri-
bir las declaraciones using o la directiva using que sean necesarias].
2.6 Identifique y corrija los errores en cada una de las siguientes instrucciones (suponga que se utiliza la ins-
trucción using std::cout;):
a) if ( c  7 );
cout  c es menor que 7n;
b) if ( c = 7 )
cout  c es igual o mayor que 7n;
Respuestas a los ejercicios de autoevaluación
2.1 a) main. b) llave izquierda ({), llave derecha (}). c) punto y coma. d) nueva línea. e) if.
2.2 a) Falso. Los comentarios no producen ninguna acción cuando el programa se ejecuta. Se utilizan para
documentar programas y mejorar su legibilidad.
b) Verdadero.
c) Verdadero.
d) Verdadero.
e) Falso. C++ es sensible a mayúsculas y minúsculas, por lo que estas variables son diferentes.
f) Verdadero.
g) Verdadero.
h) Falso. Los operadores *, / y % se encuentran en el mismo nivel de precedencia, y los operadores + y - se
encuentran en un nivel menor de precedencia.
i) Falso. Una instrucción con cout y varias secuencias de escape n puede imprimir varias líneas.
2.3 a) int c, estaEsUnaVariable, q76354, numero;
b) std::cout  Escriba un entero: ;
c) std::cin  edad;
d) if ( numero != 7 )
std::cout  La variable numero no es igual a 7n;
e) std::cout  Este es un programa en C++n;
f) std::cout  Este es unn programa en C++n;
g) std::cout  EstenesnunnprogramanennC++n;
h) std::cout  EstetestuntprogramatentC++n;
Ejercicios 61
2.4 a) // Calcula el producto de tres enteros
b) int x = 0;
int y = 0;
int z = 0;
int resultado = 0;
c) cout  Escriba tres enteros: ;
d) cin  x  y  z;
e) resultado = x * y * z;
f) cout  El producto es   resultado  endl;
g) return 0;
2.5 (Vea el siguiente programa).
1 // Calcula el producto de tres enteros
2 #include iostream // permite al programa realizar operaciones de entrada y salida
3 using namespace std; // el programa usa nombres del std namespace
4
5 // la función main empieza la ejecución del programa
6 int main()
7 {
8 int x = 0; // primer entero a multiplicar
9 int y = 0; // segundo entero a multiplicar
10 int z = 0; // tercer entero a multiplicar
11 int resultado = 0; // el producto de los tres enteros
12
13 cout  Escriba tres enteros: ; // pide los datos al usuario
14 cin  x  y  z; // lee tres enteros del usuario
15 resultado = x * y * z; // multiplica los tres enteros; almacena el resultado
16 cout  El producto es   resultado  endl; // imprime el resultado; fin de línea
17 } // fin de la función main
2.6 a) Error: punto y coma después del paréntesis derecho de la condición en la instrucción if.
Corrección: elimine el punto y coma después del paréntesis derecho. [Nota: el resultado de este error es
que la instrucción de salida se ejecuta sin importar que la condición en la instrucción if sea verdadera
o no]. El punto y coma después del paréntesis derecho es una instrucción nula (o vacía) que no hace
nada. Aprenderemos más sobre la instrucción nula en el capítulo 4.
b) Error: el operador relacional =.
Corrección: cambie = a = y tal vez quiera cambiar “igual o mayor que” a “mayor o igual que” también.
Ejercicios
2.7 Hable sobre el significado de cada uno de los siguientes objetos:
a) std::cin
b) std::cout
2.8 Complete las siguientes oraciones:
a) __________ se utilizan para documentar un programa y mejorar su legibilidad.
b) El objeto que se utiliza para imprimir información en la pantalla es ___________ .
c) Una instrucción de C++ que toma una decisión es __________.
d) La mayoría de los cálculos se realizan normalmente mediante instrucciones __________.
e) El objeto __________ recibe valores de entrada del teclado.
62 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
2.9 Escriba una sola instrucción o línea en C++ que realice cada una de las siguientes tareas:
a) Imprimir el mensaje Escriba dos números.
b) Asignar el producto de las variables b y c a la variable a.
c) Indicar que un programa va a realizar un cálculo de nómina (es decir, usar texto que ayude a documen-
tar un programa).
d) Recibir tres valores de entrada del teclado y colocarlos en las variables enteras a, b y c.
2.10 Indique cuál de los siguientes enunciados es true y cuál es falso. Si es falso, explique su respuesta.
a) Los operadores en C++ se evalúan de izquierda a derecha.
b) Los siguientes nombres de variables son todos válidos: _barra_inferior_, m928134, t5, j7, sus_
ventas, su_cuenta_total, a, b, c, z, z2.
c) La instrucción cout  a = 5;; es un ejemplo típico de una instrucción de asignación.
d) Una expresión aritmética válida en C++ sin paréntesis se evalúa de izquierda a derecha.
e) Los siguientes nombres de variables son todos inválidos: 3g, 87, 67h2, h22, 2h.
2.11 Complete cada una de las siguientes oraciones:
a) ¿Qué operaciones aritméticas se encuentran en el mismo nivel de precedencia que la multiplicación?
__________.
b) Cuando los paréntesis están anidados, ¿qué conjunto de paréntesis se evalúa primero en una expresión
aritmética? __________.
c) Una ubicación en la memoria de la computadora que puede contener distintos valores en distintos
momentos durante la ejecución de un programa se llama __________.
2.12 ¿Qué se imprime (si acaso) cuando se ejecuta cada una de las siguientes instrucciones de C++? Si no se
imprime nada, entonces responda “nada”. Suponga que x = 2 y y = 3.
a) cout  x;
b) cout  x + x;
c) cout  x=;
d) cout  x =   x;
e) cout  x + y   =   y + x;
f) z = x + y;
g) cin  x  y;
h) // cout  x + y =   x + y;
i) cout  n;
2.13 ¿Cuáles de las siguientes instrucciones de C++ contienen variables cuyos valores se reemplazan?
a) cin  b  c  d  e  f;
b) p = i + j + k + 7;
c) cout  variables cuyos valores se reemplazan;
d) cout  a = 5;
2.14 Dada la ecuación algebraica y = ax3
+ 7, ¿cuáles de las siguientes instrucciones (si acaso) en C++ son co-
rrectas para esta ecuación?
a) y = a * x * x * x + 7;
b) y = a * x * x * ( x + 7 );
c) y = ( a * x ) * x * ( x + 7 );
d) y = (a * x) * x * x + 7;
e) y = a * ( x * x * x ) + 7;
f) y = a * x * ( x * x + 7 );
2.15 (Orden de evaluación) Indique el orden de evaluación de los operadores en cada una de las siguientes
instrucciones en C++ y muestre el valor de x después de ejecutar cada una de ellas.
a) x = 7 + 3 * 6 / 2 - 1;
b) x = 2 % 2 + 2 * 2 - 2 / 2;
c) x = ( 3 * 9 * ( 3 + ( 9 * 3 / ( 3 ) ) ) );
Ejercicios 63
2.16 (Aritmética) Escriba un programa que pida al usuario que escriba dos números, obtenga esos dos números
del usuario e imprima la suma, producto, diferencia y cociente de los dos números.
2.17 (Impresión) Escriba un programa que imprima los números 1 a 4 en la misma línea con cada par de nú-
meros adyacentes separado por un espacio. Haga esto de varias formas:
a) Utilizando una instrucción con un operador de inserción de flujos.
b) Utilizando una instrucción con cuatro operadores de inserción de flujos.
c) Utilizando cuatro instrucciones.
2.18 (Comparación de enteros) Escriba un programa que pida al usuario que escriba dos enteros, obtenga los
números del usuario y luego imprima el número más grande, seguido de las palabras es más grande. Si los nú-
meros son iguales, imprima el mensaje Estos numeros son iguales.
2.19 (Aritmética, menor y mayor) Escriba un programa que reciba tres enteros del teclado e imprima la suma,
promedio, producto, menor y mayor de estos números. El diálogo de la pantalla debe aparecer de la siguiente
manera:
Introduzca tres enteros distintos: 13 27 14
La suma es 54
El promedio es 18
El producto es 4914
El menor es 13
El mayor es 27
2.20 (Diámetro, circunferencia y área de un círculo) Escriba un programa que lea el radio de un círculo como
un entero e imprima el diámetro del círculo, la circunferencia y el área. Use el valor constante 3.14159 para p.
Realice todos los cálculos en instrucciones de salida. [Nota: en este capítulo sólo hemos visto constantes y variables
tipo entero. En el capítulo 4 hablaremos sobre los números de punto flotante; es decir, valores que pueden tener
puntos decimales].
2.21 (Mostrar figuras con asteriscos) Escriba un programa que imprima un cuadro, un óvalo, una flecha y un
diamante como se indica a continuación:
********* *** * *
* * * * *** * *
* * * * ***** * *
* * * * * * *
* * * * * * *
* * * * * * *
* * * * * * *
* * * * * * *
********* *** * *
2.22 ¿Qué imprime el siguiente código?
cout  “*n**n***n****n*****”  endl;
2.23 (Enteros mayor y menor) Escriba un programa que lea cinco enteros, determine e imprima los enteros
mayor y menor en el grupo. Use sólo las técnicas de programación que aprendió en este capítulo.
2.24 (Impar o par) Escriba un programa que lea un entero, determine e imprima si es impar o par. [Sugerencia:
use el operador módulo. Un número par es un múltiplo de dos. Cualquier múltiplo de dos deja un residuo de cero
cuando se divide entre 2].
2.25 (Múltiplos) Escriba un programa que lea dos enteros, determine e imprima si el primero es múltiplo del
segundo. [Sugerencia: use el operador módulo].
64 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores
2.26 (Patrón de tablero de damas) Muestre el siguiente patrón de tablero de damas con ocho instrucciones de
salida, y después muestre el mismo patrón utilizando el menor número de instrucciones posible.
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
2.27 (Equivalente entero de un carácter) He aquí un adelanto. En este capítulo aprendió sobre los enteros y
el tipo int. C++ también puede representar letras mayúsculas, minúsculas y una considerable variedad de símbo-
los especiales. C++ utiliza enteros pequeños de manera interna para representar cada uno de los distintos caracte-
res. Al conjunto de caracteres que utiliza una computadora y las correspondientes representaciones enteras para
esos caracteres se le conoce como el conjunto de caracteres de esa computadora. Puede imprimir un carácter
encerrándolo entre comillas sencillas, como en el siguiente ejemplo:
cout  ‘A’; // imprimir una letra A mayúscula
Puede imprimir el equivalente entero de un carácter mediante el uso de using static_cast, como en el siguiente
ejemplo:
cout  static_cast int ( ‘A’ ); // imprime 'A' como un entero
A esto se le conoce como operación de conversión (en el capítulo 4 presentaremos de manera formal las conver-
siones). Cuando se ejecuta la instrucción anterior, imprime el valor 65 (en sistemas que utilizan el conjunto de
caracteres ASCII). Escriba un programa que imprima el equivalente entero de un carácter escrito en el teclado.
Almacene la entrada en una variable de tipo char. Pruebe su programa varias veces utilizando letras mayúsculas,
minúsculas, dígitos y caracteres especiales (como $).
2.28 (Dígitos de un entero) Escriba un programa que reciba como entrada un entero de cinco dígitos, que se-
pare ese número en sus dígitos individuales y los imprima, cada uno separado de los demás por tres espacios. [Su-
gerencia: use los operadores de división entera y módulo]. Por ejemplo, si el usuario escribe el número 42339, el
programa debe imprimir:
4 2 3 3 9
2.29 (Tabla) Utilice las técnicas de este capítulo para escribir un programa que calcule los cuadrados y cubos de
los enteros del 0 al 10. Use tabuladores para imprimir la siguiente tabla ordenada de valores:
entero cuadrado cubo
0 0 0
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
Hacer la diferencia 65
Hacer la diferencia
2.30 (Calculadora del índice de masa corporal) En el ejercicio 1.9 introdujimos la calculadora del índice de
masa corporal (BMI). Las fórmulas para calcular el BMI son
1.6
703
BMI
pesoEnLibras
alturaEnPulgadas alturaEnPulgadas
=
×
×
o
BMI
pesoEnKilogramos
alturaEnMetros alturaEnMetros
=
×
Cree una aplicación de calculadora del BMI que lea el peso del usuario en libras y la altura en pulgadas (o, si lo
prefiere, el peso del usuario en kilogramos y la altura en metros), para que luego calcule y muestre el índice de masa
corporal del usuario. La aplicación debe mostrar además la siguiente información del Departamento de Salud y
Servicios Humanos/Instituto Nacional de Salud para que el usuario pueda evaluar su BMI:
VALORES DE BMI
Bajo peso: menos de 18.5
Normal: entre 18.5 y 24.9
Sobrepeso: entre 25 y 29.9
Obeso: 30 o más
[Nota: en este capítulo aprendió a usar el tipo int para representar números enteros. Cuando se realizan los cálcu-
los del BMI con valores int se producen resultados en números enteros. En el capítulo 4 aprenderá a usar el tipo
double, para representar a los números con puntos decimales. Cuando se realizan los cálculos del BMI con valo-
res double, producen números con puntos decimales; a éstos se les conoce como números de punto flotante].
2.31 (Calculadora de ahorro por viajes compartidos en automóvil) Investigue varios sitios Web de viajes com-
partidos en automóvil. Cree una aplicación que calcule su costo diario al conducir su automóvil, de modo que
pueda estimar cuánto dinero puede ahorrar si comparte los viajes en automóvil, lo cual también tiene otras ven-
tajas, como la reducción de las emisiones de carbono y la reducción de la congestión de tráfico. La aplicación debe
recibir como entrada la siguiente información y mostrar el costo por día para el usuario por conducir al trabajo:
a) Total de kilómetros conducidos por día.
b) Costo por litro de gasolina.
c) Promedio de kilómetros por litro.
d) Cuotas de estacionamiento por día.
e) Peaje por día.
Introducción a las clases,
objetos y cadenas
3
Nada puede tener valor sin ser
un objeto de utilidad.
—Karl Marx
Sus sirvientes públicos le sirven
bien.
—Adlai E. Stevenson
Saber cómo responder a
alguien que habla,
Contestar a alguien que envía
un mensaje.
—Amenemopel
O b j e t i v o s
En este capítulo aprenderá a:
n Definir una clase y utilizarla
para crear un objeto.
n Implementar los
comportamientos de una
clase como funciones
miembro.
n Implementar los atributos de
una clase como datos
miembros.
n Llamar a una función
miembro de un objeto para
realizar una tarea.
n Diferenciar entre los datos
miembros de una clase y las
variables locales de una
función.
n Utilizar un constructor para
inicializar los datos de un
objeto al momento de crear
el objeto.
n Maquinar la interfaz de la
implementación de una clase
y fomentar la reutilización.
n Usar objetos de la clase
string.
3.2 Definición de una clase con una función miembro 67
3.1Introducción
En el capítulo 2 creó programas simples que mostraban mensajes al usuario, obtenían información del
usuario, realizaban cálculos y tomaban decisiones. En este capítulo empezará a escribir programas que
emplean los conceptos básicos de la programación orientada a objetos que presentamos en la sección 1.8.
Una característica común de todos los programas del capítulo 2 fue que todas las instrucciones que
ejecutaban tareas se encontraban en la función main. Por lo general, los programas que desarrollará en
este libro consistirán de la función main y una o más clases, cada una de las cuales puede contener datos
miembros y funciones miembro. Si usted se integra a un equipo de desarrollo en la industria, podría tra-
bajar en sistemas de software que contengan cientos, o incluso miles, de clases. En este capítulo desarro-
llaremos un marco de trabajo simple y bien maquinado para organizar los programas orientados a obje-
tos en C++.
Presentaremos una secuencia cuidadosamente pautada de programas funcionales completos para
demostrarle cómo crear y utilizar sus propias clases. Estos ejemplos empiezan nuestro caso de estudio
integrado acerca de cómo desarrollar una clase tipo libro de calificaciones, que los instructores pueden
utilizar para mantener las calificaciones de las pruebas de sus estudiantes. También presentaremos la
clase string de la biblioteca estándar de C++.
3.2Definición de una clase con una función miembro
Vamos a empezar con un ejemplo (figura 3.1) que consiste en la clase LibroCalificaciones (líneas 8 a
16) que, cuando se desarrolle en su totalidad en el capítulo 7, representará a un libro de calificaciones
que un instructor puede utilizar para mantener las calificaciones de los exámenes de sus estudiantes, y
una función main (líneas 19 a 23) que crea un objeto LibroCalificaciones. La función main utiliza
este objeto y su función miembro mostrarMensaje (líneas 12 a 15) para mostrar un mensaje en la pan-
talla y dar la bienvenida al instructor al programa del libro de calificaciones.
1 // Fig. 3.1: fig03_01.cpp
2 // Define la clase LibroCalificaciones con una función miembro llamada
mostrarMensaje
3 // crea un objeto LibroCalificaciones y llama a su función mostrarMensaje.
4 #include iostream
5 using namespace std;
3.1 Introducción
3.2 Definición de una clase con una función
miembro
3.3 Definición de una función miembro con un
parámetro
3.4 Datos miembros, funciones establecer y obtener
3.5 Inicialización de objetos mediante
constructores
3.6 Colocar una clase en un archivo
separado para fines de reutilización
3.7 Separar la interfaz de la
implementación
3.8 Validación de datos mediante
funciones establecer
3.9 Conclusión
Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios
| Hacer la diferencia
Fig. 3.1  Define la clase LibroCalificaciones con una función miembro llamada mostrarMensaje,
crea un objeto LibroCalificaciones y llama a su función mostrarMensaje (parte 1 de 2).
68 Capítulo 3 Introducción a las clases, objetos y cadenas
6
7 // Definición de la clase LibroCalificaciones
8 class LibroCalificaciones
9 {
10 public:
11 // función que muestra un mensaje de bienvenida para el usuario de
LibroCalificaciones
12 void mostrarMensaje() const
13 {
14 cout  Bienvenido al Libro de calificaciones!  endl;
15 } // fin de la función mostrarMensaje
16 }; // fin de la clase LibroCalificaciones
17
18 // la función main empieza la ejecución del programa
19 int main()
20 {
21 LibroCalificaciones miLibroCalificaciones; // crea un objeto
LibroCalificaciones llamado miLibroCalificaciones
22 miLibroCalificaciones.mostrarMensaje(); // llama a la función
mostrarMensaje del objeto
23 } // fin de main
Bienvenido al Libro de calificaciones!
La clase LibroCalificaciones
Antes de que la función main (líneas 19 a 23) pueda crear un objeto de la clase LibroCalificaciones,
debemos indicar al compilador qué funciones miembro y cuáles datos miembros pertenecen a la clase.
La definición de la clase LibroCalificaciones (líneas 8 a 16) contiene una función miembro llama-
da mostrarMensaje (líneas 12 a 15), la cual muestra un mensaje en la pantalla (línea 14). Necesitamos
crear un objeto de la clase LibroCalificaciones (línea 21) y llamar a su función miembro mostrar-
Mensaje (línea 22) para hacer que se ejecute la línea 14 y se muestre el mensaje de bienvenida. Pronto
explicaremos las líneas 21 y 22 con detalle.
La definición de la clase empieza en la línea 8 con la palabra clave class, seguida del nombre de la
claseLibroCalificaciones.Porconvención,elnombredeunaclasedefinidaporelusuarioempiezacon
una letra mayúscula, y por legibilidad, cada palabra subsiguiente en el nombre de la clase empieza
con una letra mayúscula. Este estilo de capitalización se conoce comúnmente como nomenclatura de
Pascal, debido a que esta convención era muy utilizada en el lenguaje de programación Pascal. Las oca-
sionales letras mayúsculas se asemejan a las jorobas de un camello. En términos más generales, el estilo de
capitalización conocido como nomenclatura de camello permite que la primera letra sea mayúscula o
minúscula (como miLibroCalificaciones en la línea 21).
El cuerpo de cada clase va encerrado entre un par de llaves izquierda y derecha ({ y }), como en las
líneas 9 y 16. La definición de la clase termina con un punto y coma (línea 16).
Error común de programación 3.1
Olvidar el punto y coma al final de la definición de una clase es un error de sintaxis.
Recuerde que la función main siempre se llama de manera automática cuando ejecutamos un programa.
Lamayoríadelasfuncionesnosellamandemaneraautomática.Comoprontoveremos,debemosllamar
a la función miembro mostrarMensaje de manera explícita para indicarle que debe realizar su tarea.
Fig. 3.1  Define la clase LibroCalificaciones con una función miembro llamada mostrarMensaje,
crea un objeto LibroCalificaciones y llama a su función mostrarMensaje (parte 2 de 2).
3.2 Definición de una clase con una función miembro 69
La línea 10 contiene la palabra clave public, que es un especificador de acceso. En las líneas 12 a
15 se define la función miembro mostrarMensaje. Esta función miembro aparece después del especifi-
cador de acceso public: para indicar que la función está “disponible para el público”; es decir, otras
funciones en el programa (como main) la pueden llamar, y también las funciones miembro de otras
clases (si las hay). Los especificadores de acceso siempre van seguidos de un signo de dos puntos (:). En
el resto del libro, cuando hagamos referencia al especificador de acceso public, omitiremos el punto y
coma como en esta oración. En la sección 3.4 presentaremos el especificador de acceso private. Más
adelante en el libro estudiaremos el especificador de acceso protected.
Cada función en un programa realiza una tarea y puede devolver un valor cuando complete su tarea;
por ejemplo, una función podría realizar un cálculo y después devolver el resultado del mismo. Al defi-
nir una función, debemos especificar un tipo de valor de retorno para indicar el tipo de valor que de-
vuelve la función cuando completa su tarea. En la línea 12, la palabra clave void a la izquierda del
nombre de la función mostrarMensaje es el tipo de valor de retorno de ésta. El tipo de valor de retorno
void indica que mostrarMensaje no devolverá datos a la función que la llamó (en este ejemplo, la línea
22 de main, como veremos en unos momentos) cuando complete su tarea. En la figura 3.5 veremos un
ejemplo de una función que sí devuelve un valor.
El nombre de la función miembro, mostrarMensaje, va después del tipo de valor de retorno (línea
12). Por convención, los nombres de nuestras funciones usan el estilo de nomenclatura de camello con
la primera letra en minúscula. Los paréntesis después del nombre de la función miembro indican que
ésta es una función. Un conjunto vacío de paréntesis, como se muestra en la línea 12, indica que esta
función miembro no requiere datos adicionales para realizar su tarea. En la sección 3.3 veremos un
ejemplo de una función miembro que sí requiere datos adicionales.
Declaramos la función miembro mostrarMensaje como const en la línea 12 debido a que, en el
proceso de mostrar el mensaje Bienvenido al libro de calificaciones! la función no modifica
(y no debería hacerlo) el objeto LibroCalificaciones sobre el cual se llama. Al declarar mostrar-
Mensaje como const indicamos al compilador que “esta función no debe modificar el objeto sobre el
cual se llama; y si lo hace, hay que generar un error de compilación”. Esto puede ayudar al programador
a localizar errores en caso de insertar por accidente código en mostrarMensaje que pudiera modificar el
objeto. La línea 12 se conoce comúnmente como encabezado de función.
El cuerpo de todas las funciones está delimitado por las llaves izquierda y derecha ({ y }), como en
las líneas 13 y 15. El cuerpo de la función contiene instrucciones que realizan la tarea de esa función. En
este caso, la función miembro mostrarMensaje contiene una instrucción (línea 14) que muestra el
mensaje Bienvenido al Libro de calificaciones!. Una vez que se ejecute esta instrucción, la
función habrá completado su tarea.
Prueba de la clase LibroCalificaciones
A continuación nos gustaría utilizar la clase LibroCalificaciones en un programa. Como aprendió en
el capítulo 2, la función main (líneas 19 a 23) empieza la ejecución de todos los programas.
En este programa queremos llamar a la función miembro mostrarMensaje de la clase LibroCali-
ficaciones para mostrar el mensaje de bienvenida. Por lo general, no podemos llamar a una función
miembro de una clase, sino hasta crear un objeto de esa clase. (Como veremos en la sección 9.14, las
funciones miembro static son una excepción). En la línea 21 se crea un objeto de la clase LibroCali-
ficaciones, llamado miLibroCalificaciones. El tipo de la variable es LibroCalificaciones: la cla-
se que definimos en las líneas 8 a 16. Cuando declaramos variables de tipo int, como en el capítulo 2,
el compilador sabe lo que es int: un tipo fundamental que está “integrado” a C++. Sin embargo, en la
línea 21 el compilador no sabe automáticamente qué tipo corresponde a LibroCalificaciones: es un
tipo definido por el usuario. Para indicar al compilador qué es LibroCalificaciones, incluimos la
definición de la clase (líneas 8 a 16). Si omitiéramos estas líneas, el compilador generaría un mensaje de
error. Cada nueva clase que creamos se convierte en un nuevo tipo que puede usarse para crear objetos.
Los programadores pueden definir nuevos tipos de clases según lo necesiten; ésta es una razón por la cual
C++ se conoce como un lenguaje de programación extensible.
70 Capítulo 3 Introducción a las clases, objetos y cadenas
En la línea 22 se hace una llamada a la función miembro mostrarMensaje, usando la variable mi-
LibroCalificaciones seguida del operador punto (.), el nombre de la función mostrarMensaje y un
conjunto vacío de paréntesis. Esta llamada hace que la función mostrarMensaje realice su tarea. Al
principio de la línea 22, “miLibroCalificaciones” indica que main debe usar el objeto LibroCalifi-
caciones que se creó en la línea 21. Los paréntesis vacíos en la línea 12 indican que la función miembro
mostrarMensaje no requiere datos adicionales para realizar su tarea, razón por la cual llamamos a esta
función con paréntesis vacíos en la línea 22 (en la sección 3.3 veremos cómo pasar datos a una función).
Cuando mostrarMensaje completa su tarea, el programa llega al final de main (línea 23) y termina.
Diagrama de clases de UML para la clase LibroCalificaciones
En la sección 1.8 vimos que UML es un lenguaje gráfico estandarizado, utilizado por los desarrolladores
de software para representar sistemas orientados a objetos. En UML, cada clase se modela en un diagra-
ma de clases de UML en forma de un rectángulo con tres compartimientos. La figura 3.2 presenta un
diagrama de clases para la clase LibroCalificaciones (figura 3.1). El compartimiento superior contiene
el nombre de la clase, centrado en forma horizontal y en negrita. El compartimiento de en medio contie-
ne los atributos de la clase, que en C++ corresponden a los datos miembros. En estos momentos el com-
partimiento está vacío, ya que la clase LibroCalificaciones no tiene atributos todavía (en la sección
3.4 presentaremos una versión de la clase LibroCalificaciones con un atributo). El compartimiento
inferior contiene las operaciones de la clase, que en C++ corresponden a las funciones miembro. Para
modelar las operaciones, UML lista el nombre de la operación seguido de un conjunto de paréntesis. La
clase LibroCalificaciones tiene una sola función miembro llamada mostrarMensaje, por lo que el
compartimiento inferior de la figura 3.2 lista una operación con este nombre. La función miembro
mostrarMensaje no requiere información adicional para realizar sus tareas, por lo que los paréntesis que
van después de mostrarMensaje en el diagrama de clases están vacíos, de igual forma que como apare-
cieron en el encabezado de la función miembro, en la línea 12 de la figura 3.1. El signo más (+) que va
antes del nombre de la operación indica que mostrarMensaje es una operación public en UML (es decir,
una función miembro public en C++).
LibroCalificaciones
+ mostrarMensaje( )
Fig. 3.2  Diagrama de clases de UML, el cual indica que la clase LibroCalificaciones tiene
una operación mostrarMensaje pública.
3.3Definición de una función miembro con un parámetro
En nuestra analogía del automóvil de la sección 1.8, hablamos sobre el hecho de que al oprimir el pedal
del acelerador se envía un mensaje al automóvil para que realice una tarea: hacer que vaya más rápido.
Pero ¿qué tan rápido debería acelerar el automóvil? Como sabe, entre más oprima el pedal, mayor será la
aceleración del automóvil. Por lo tanto, el mensaje para el automóvil en realidad incluye tanto la tarea a
realizarcomoinformaciónadicionalqueayudaalautomóvilarealizarsutarea.Alainformaciónadicional
se le conoce como parámetro; el valor del parámetro ayuda al automóvil a determinar qué tan rápido
debe acelerar. De manera similar, una función miembro puede requerir uno o más parámetros que re-
presentan la información adicional que necesita para realizar su tarea. La llamada a una función propor-
ciona valores llamados argumentos para cada uno de los parámetros de esa función. Por ejemplo, para
realizar un depósito en una cuenta bancaria, suponga que una función miembro llamada depositar de
una clase Cuenta especifica un parámetro que representa el monto a depositar. Cuando se hace una lla-
3.3 Definición de una función miembro con un parámetro 71
mada a la función miembro deposito, se copia al parámetro de la función miembro un valor como
argumento, que representa el monto a depositar. Después, la función miembro suma esa cantidad al
saldo de la cuenta.
Definición y prueba de la clase LibroCalificaciones
Nuestro siguiente ejemplo (figura 3.3) redefine la clase LibroCalificaciones (líneas 9 a 18) con una
función miembro mostrarMensaje (líneas 13 a 17) que muestra el nombre del curso como parte del
mensaje de bienvenida. La nueva versión de mostrarMensaje requiere un parámetro (nombreCurso en
la línea 13) que representa el nombre del curso a imprimir en pantalla.
1 // Fig. 3.3: fig03_03.cpp
2 // Define la clase LibroCalificaciones con una función miembro que recibe un
parámetro,
3 // crea un objeto LibroCalificaciones y llama a su función mostrarMensaje.
4 #include iostream
5 #include string // el programa usa la clase string estándar de C++
6 using namespace std;
7
8 // definición de la clase LibroCalificaciones
9 class LibroCalificaciones
10 {
11 public:
12 // función que muestra un mensaje de bienvenida para el usuario de
LibroCalificaciones
13 void mostrarMensaje( string nombreCurso ) const
14 {
15 cout  Bienvenido al libro de calificaciones paran  nombreCurso  !
16  endl;
17 } // fin de la función mostrarMensaje
18 }; // fin de la clase LibroCalificaciones
19
20 // la función main empieza la ejecución del programa
21 int main()
22 {
23 string nombreDelCurso; // cadena de caracteres que almacena el nombre del curso
24 LibroCalificaciones miLibroCalificaciones;
// crea un objeto LibroCalificaciones llamado mi LibroCalificaciones
25
26 // pide y recibe el nombre del curso como entrada
27 cout  Escriba el nombre del curso:  endl;
28 getline( cin, nombreDelCurso );
// lee el nombre de un curso con espacios en blanco
29 cout  endl; // imprime una línea en blanco
30
31 // llama a la función mostrarMensaje de miLibroCalificaciones
32 // y pasa nombreDelCurso como argumento
33 miLibroCalificaciones.mostrarMensaje( nombreDelCurso );
34 } // fin de main
Fig. 3.3  Define la clase LibroCalificaciones con una función miembro que recibe un parámetro,
crea un objeto LibroCalificaciones y llama a su función mostrarMensaje (parte 1 de 2).
72 Capítulo 3 Introducción a las clases, objetos y cadenas
Escriba el nombre del curso:
CS101 Introduccion a la programacion en C++
Bienvenido al libro de calificaciones para
CS101 Introduccion a la programacion en C++!
Antes de hablar sobre las nuevas características de la clase LibroCalificaciones, veamos cómo se
utiliza la nueva clase en main (líneas 21 a 34). En la línea 23 se crea una variable de tipo string llamada
nombreDelCurso, la cual se utilizará para almacenar el nombre del curso que escriba el usuario. Una
variable de tipo string representa a una cadena de caracteres tal como CS101 Introduccion a la
programacion en C++. En realidad, una cadena es un objeto de la clase string, de la Biblioteca están-
dar de C++. Esta clase se define en el encabezado string, y el nombre string (al igual que cout)
pertenece al espacio de nombres std. Para que las líneas 13 y 23 se puedan compilar, en la línea 5 se
incluye el encabezado string. La directiva using en la línea 6 nos permite escribir simplemente
string en la línea 23, en vez de std::string. Por ahora, puede considerar a las variables string como
las variables de otros tipos como int. En la sección 3.8 y en el capítulo 21 (en inglés, en el sitio web)
aprenderá acerca de las herramientas adicionales de string.
En la línea 24 se crea un objeto de la clase LibroCalificaciones, llamado miLibroCalificacio-
nes. En la línea 27 se pide al usuario que escriba el nombre de un curso. En la línea 28 se lee el nombre
del usuario y se asigna a la variable nombreDelCurso, usando la función de biblioteca getline para llevar
a cabo la entrada. Antes de explicar esta línea de código, veamos por qué no podemos simplemente es-
cribir
cin  nombreDelCurso;
para obtener el nombre del curso.
En la ejecución de ejemplo de nuestro programa, utilizamos el nombre “CS101 Introduccion a
la programacion en C++”, el cual contiene varias palabras separadas por espacios en blanco (recuerde que
resaltamos la entrada que suministra el usuario en negrita). Cuando se lee un objeto string con el ope-
rador de extracción de flujo, cin lee caracteres hasta que se llega al primer carácter de espacio en blanco.
Por ende, la instrucción anterior sólo leería “CS101”. El resto del nombre del curso tendría que leerse
mediante operaciones de entrada subsiguientes.
En este ejemplo, nos gustaría que el usuario escribiera el nombre completo del curso y que oprimie-
ra Intro para enviarlo al programa, y nos gustaría almacenar el nombre completo del curso en la variable
string llamada nombreDelCurso. La llamada a la función getline( cin, nombreDelCurso) en la línea
28 lee caracteres (incluyendo los caracteres de espacio que separan las palabras en la entrada) del objeto
flujo de entrada estándar cin (es decir, el teclado) hasta encontrar el carácter de nueva línea, coloca los
caracteresenlavariablestring llamadanombreDelCurso ydescartaelcarácterdenuevalínea.Aloprimir
Intro mientras se introducen los datos, se inserta una nueva línea en el flujo de entrada. Debe incluirse
el encabezado string en el programa para usar la función getline, que pertenece al espacio de nom-
bres std.
En la línea 33 se hace una llamada a la función miembro mostrarMensaje de miLibroCalifica-
ciones. La variable nombreDelCurso entre paréntesis es el argumento que se pasa a la función miembro
mostrarMensaje para que pueda realizar su tarea. El valor de la variable nombreDelCurso en main se
copia al parámetro nombreCurso de la función miembro mostrarMensaje en la línea 13. Al ejecutar este
programa, la función miembro mostrarMensaje imprime, como parte del mensaje de bienvenida, el
Fig. 3.3  Define la clase LibroCalificaciones con una función miembro que recibe un parámetro,
crea un objeto LibroCalificaciones y llama a su función mostrarMensaje (parte 2 de 2).
3.3 Definición de una función miembro con un parámetro 73
nombre del curso que usted escriba (en nuestra ejecución de ejemplo, CS101 Introduccion a la pro-
gramacion en C++).
Más sobre los argumentos y los parámetros
Para especificar en la definición de una función que ésta requiere datos para realizar su tarea, hay que
colocar información adicional en la lista de parámetros de la función, la cual se encuentra en los parén-
tesis que van después del nombre de la función. La lista de parámetros puede contener cualquier núme-
ro de parámetros, incluso ninguno (lo que se representa mediante los paréntesis vacíos, como en la línea
12 de la figura 3.1), para indicar que una función no requiere parámetros. La lista de parámetros de la
función miembro mostrarMensaje (figura 3.3, línea 13) declara que la función requiere un parámetro.
Cada parámetro debe especificar un tipo y un identificador. El tipo string y el identificador nombreCurso
indican que la función miembro mostrarMensaje requiere un objeto string para realizar su tarea.
El cuerpo de la función miembro utiliza el parámetro nombreDelCurso para acceder al valor que se pasa
a la función en la llamada (línea 33 en main). En las líneas 15 y 16 se muestra el valor del parámetro
nombreDelCurso como parte del mensaje de bienvenida. El nombre de la variable de parámetro (nombre-
Curso en la línea 13) puede ser igual o distinto al nombre de la variable de argumento (nombreDelCurso
en la línea 33); en el capítulo 6 aprenderá por qué.
Una función puede especificar múltiples parámetros; sólo hay que separar un parámetro de otro
mediante una coma. El número y el orden de los argumentos en la llamada a una función deben coincidir
conelnúmeroyordendelosparámetrosenlalistadeparámetrosdelencabezadodelafunciónmiembro
que se llamó. Además, los tipos de los argumentos en la llamada a la función deben ser consistentes con
los tipos de los parámetros correspondientes al encabezado de la función (como veremos en capítulos
posteriores, no siempre se requiere que el tipo de un argumento y el tipo de su correspondiente paráme-
tro sean idénticos, pero deben ser “consistentes”). En nuestro ejemplo, el único argumento string en la
llamada a la función (es decir, nombreDelCurso) coincide exactamente con el único parámetro string en
la definición de la función miembro (es decir, nombreCurso).
Diagrama de clases de UML actualizado para la clase LibroCalificaciones
El diagrama de clases de UML de la figura 3.4 modela la clase LibroCalificaciones de la figura 3.3.
Al igual que la clase LibroCalificaciones definida en la figura 3.1, esta clase LibroCalificaciones
contiene la función miembro public llamada mostrarMensaje. Sin embargo, esta versión de mostrar-
Mensaje tiene un parámetro. Para modelar un parámetro, UML lista el nombre del parámetro, seguido
de dos puntos y del tipo del parámetro entre paréntesis, después del nombre de la operación. UML tiene
sus propios tipos de datos similares a los de C ++. UML es independiente del lenguaje —se utiliza con mu-
chos lenguajes de programación distintos— por lo que su terminología no coincide exactamente con la
de C++. Por ejemplo, el tipo String de UML corresponde al tipo string de C++. La función miembro
mostrarMensaje delaclaseLibroCalificaciones (figura3.3,líneas13a17)tieneunparámetrostring
llamado nombreCurso, por lo que en la figura 3.4 se lista a nombreCurso : String entre los paréntesis que
van después del nombre de la operación mostrarMensaje. Esta versión de la clase LibroCalificaciones
aún no tiene datos miembros.
LibroCalificaciones
+ mostrarMensaje (nombreCurso : String )
Fig. 3.4  Diagrama de clases de UML, que indica que la clase LibroCalificaciones tiene
una operación pública llamada mostrarMensaje, con un parámetro llamado nombreCurso
de tipo String de UML.
74 Capítulo 3 Introducción a las clases, objetos y cadenas
3.4Datos miembros, funciones establecer y obtener
En el capítulo 2 declaramos todas las variables de un programa en su función main. Las variables que
se declaran en el cuerpo de la definición de una función se conocen como variables locales, y sólo se
pueden utilizar desde la línea de su declaración en la función, hasta la llave derecha de cierre (}) del
bloque en el que se declaran. Una variable local se debe declarar antes de poder utilizarla en una fun-
ción. No se puede acceder a una variable local fuera de la función en la que está declarada. Cuando una
función termina, se pierden los valores de sus variables locales (en el capítulo 6 veremos una excepción a
esto, cuando hablemos sobre las variables locales static).
Por lo general, una clase consiste en una o más funciones miembro que manipulan los atributos
pertenecientes a un objeto específico de la clase. Los atributos se representan como variables en la defi-
nición de una clase. Dichas variables se llaman datos miembros y se declaran dentro de la definición de
una clase, pero fuera de los cuerpos de las definiciones de las funciones miembro de la clase. Cada obje-
to de una clase mantiene sus propios atributos en memoria. Estos atributos existen durante toda la vida
del objeto. El ejemplo en esta sección demuestra una clase LibroCalificaciones, que contiene un dato
miembro llamado nombreCurso para representar el nombre del curso de un objeto LibroCalificacio-
nes específico. Si crea más de un objeto LibroCalificaciones, cada uno tendrá su propio miembro de
datos nombreCurso, y éstos pueden tener diferentes valores.
La clase LibroCalificaciones con un dato miembro, y funciones establecer y obtener
En nuestro siguiente ejemplo, la clase LibroCalificaciones (figura 3.5) mantiene el nombre del curso
como un dato miembro, para que pueda usarse o modificarse durante la ejecución de un programa. Esta
clase contiene las funciones miembro establecerNombreCurso, obtenerNombreCurso y mostrar-
Mensaje. La función miembro establecerNombreCurso almacena el nombre de un curso en un miem-
bro de datos de LibroCalificaciones. La función miembro obtenerNombreCurso obtiene el nombre
del curso de ese dato miembro. La función miembro mostrarMensaje, que en este caso no especifica
parámetros, sigue mostrando un mensaje de bienvenida que incluye el nombre del curso. Pero como
veremosmásadelante,lafunciónahoraobtieneelnombredelcursomedianteunallamadaaotrafunción
en la misma clase: obtenerNombreCurso.
1 // Fig. 3.5: fig03_05.cpp
2 // Define la clase LibroCalificaciones que contiene un miembro de datos
3 // nombreCurso y funciones miembro para establecer y obtener su valor;
4 // Crea y manipula un objeto LibroCalificaciones con estas funciones.
5 #include iostream
6 #include string // el programa usa la clase string estándar de C++
7 using namespace std;
8
9 // definición de la clase LibroCalificaciones
10 class LibroCalificaciones
11 {
12 public:
13 // función que establece el nombre del curso
14 void establecerNombreCurso( string nombre )
15 {
16 nombreCurso = nombre; // almacena el nombre del curso en el objeto
17 } // fin de la función establecerNombreCurso
Fig. 3.5  Definición y prueba de la clases LibroCalificaciones con un miembro de datos y funciones
establecer y obtener (parte 1 de 2).
3.4 Datos miembros, funciones establecer y obtener 75
18
19 // función que obtiene el nombre del curso
20 string obtenerNombreCurso() const
21 {
22 return nombreCurso; // devuelve el nombreCurso del objeto
23 } // fin de la función obtenerNombreCurso
24
25 // función que muestra un mensaje de bienvenida
26 void mostrarMensaje() const
27 {
28 // esta instrucción llama a obtenerNombreCurso para obtener el
29 // nombre del curso que representa este LibroCalificaciones
30 cout  Bienvenido al libro de calificaciones paran
 obtenerNombreCurso()  !
31  endl;
32 } // fin de la función mostrarMensaje
33 private:
34 string nombreCurso; // nombre del curso para este LibroCalificaciones
35 }; // fin de la clase LibroCalificaciones
36
37 // la función main empieza la ejecución del programa
38 int main()
39 {
40 string nombreDelCurso; // cadena de caracteres para almacenar el nombre del curso
41 LibroCalificaciones miLibroCalificaciones;
// crea un objeto LibroCalificaciones llamado miLibroCalificaciones
42
43 // muestra el valor inicial de nombreCurso
44 cout  El nombre inicial del curso es: 
 miLibroCalificaciones.obtenerNombreCurso()
45  endl;
46
47 // pide, recibe y establece el nombre del curso
48 cout  nEscriba el nombre del curso:  endl;
49 getline( cin, nombreDelCurso );
// lee el nombre de un curso con espacios en blanco
50 miLibroCalificaciones.establecerNombreCurso( nombreDelCurso )
// establece el nombre del curso
51
52 cout  endl; // imprime una línea en blanco
53 miLibroCalificaciones.mostrarMensaje();
// muestra un mensaje con el nuevo nombre del curso
54 } // fin de main
El nombre inicial del curso es:
Escriba el nombre del curso:
CS101 Introduccion a la programacion en C++
Bienvenido al libro de calificaciones parar
CS101 Introduccion a la programacion en C++!
Fig. 3.5  Definición y prueba de la clases LibroCalificaciones con un miembro de datos y funciones
establecer y obtener (parte 2 de 2).
76 Capítulo 3 Introducción a las clases, objetos y cadenas
Un instructor típico enseña varios cursos, cada uno con su propio nombre. En la línea 34 se declara
que nombreCurso es una variable de tipo string. Como la variable se declara en la definición de la clase
(líneas 10 a 35) pero fuera de los cuerpos de las definiciones de las funciones miembro de ésta (líneas 14
a 17, 20 a 23 y 26 a 32), la variable es un dato miembro. Cada instancia (es decir, objeto) de la clase
LibroCalificaciones contiene cada uno de los datos miembros de la clase; si hay dos objetos Libro-
Calificaciones, cada objeto tiene su propio nombreCurso (uno por cada objeto), como veremos en el
ejemplo de la figura 3.7. Un beneficio de hacer de nombreCurso un dato miembro es que todas las fun-
ciones miembro de la clase pueden manipular cualquier dato miembro que aparezca en la definición de
la clase (en este caso, nombreCurso).
Los especificadores de acceso public y private
La mayoría de las declaraciones de datos miembros aparecen después del especificador de accesos pri-
vate. Las variables o funciones declaradas después del especificador de acceso private (y antes del si-
guiente especificador de acceso, si hay uno) son accesibles sólo para las funciones miembro de la clase en
la que se declaran (o para las “amigas” de la clase, como veremos en el capítulo 9). Así, el miembro de
datos nombreCurso sólo puede utilizarse en las funciones miembro establecerNombreCurso, obtener-
NombreCurso y mostrarMensaje de la clase LibroCalificaciones (o en las “amigas” de la clase, si las
hay).
Tip para prevenir errores 3.1
Al hacer que los datos miembros de una clase sean private y las funciones miembro de la
clase sean public, se facilita la depuración debido a que los problemas con las manipulacio-
nes de datos se localizan, ya sea en las funciones miembro de la clase o en las amigas de ésta.
Error común de programación 3.2
Si una función, que no sea miembro de una clase específica (o amiga de esa clase), intenta
acceder a un miembro private de esa clase, se produce un error de compilación.
El acceso predeterminado para los datos miembros es private, de manera que todos los miembros
después del encabezado de la clase y antes del primer especificador de acceso son private. Los especifi-
cadores de acceso public y private pueden repetirse, pero esto es innecesario y puede ser confuso.
El proceso de declarar datos miembros con el modificador de acceso private se conoce como
ocultamiento de datos. Cuando un programa crea un objeto de la clase LibroCalificaciones, el dato
miembro nombreCurso se encapsula (oculta) en el objeto, y sólo está accesible para las funciones miem-
bro de la clase de ese objeto. En la clase LibroCalificaciones, las funciones miembro establecer-
NombreCurso y obtenerNombreCurso manipulan al dato miembro nombreCurso directamente.
Las funciones miembro establecerNombreCurso y obtenerNombreCurso
La función miembro obtenerNombreCurso (líneas 14 a 17) no devuelve datos cuando completa su tarea,
por lo que su tipo de valor de retorno es void. La función miembro recibe un parámetro nombre, el cual
representa el nombre del curso que recibirá como argumento (como veremos en la línea 50 de main). La
línea 16 asigna nombre al dato miembro nombreCurso, con lo cual se modifica el objeto; por esta razón no
declaramos a establecerNombreCurso como const. En este ejemplo, establecerNombreCurso no vali-
da el nombre del curso; es decir, la función no verifica que el nombre del curso se apegue a cierto formato
en especial, o que siga cualquier otra regla en relación con la apariencia que debe tener un nombre de
curso “válido”. Por ejemplo, suponga que una universidad puede imprimir certificados de los estudiantes
que contengan los nombres de cursos de sólo 25 caracteres o menos. En este caso, es conveniente que la
clase LibroCalificaciones asegure que su dato miembro nombreCurso nunca contendrá más de 25
caracteres. En la sección 3.8 hablaremos sobre la validación.
La función miembro obtenerNombreCurso (definida en las líneas 20 a 23) devuelve un valor de
nombreCurso de un objeto LibroCalificaciones específico, sin modificar el objeto; por esta razón
declaramos a obtenerNombreCurso como const. La función miembro tiene una lista de parámetros
3.4 Datos miembros, funciones establecer y obtener 77
vacía, por lo que no requiere información adicional para realizar su tarea. La función específica que de-
vuelve un objeto string. Cuando se hace una llamada a una función que especifica un tipo de valor
de retorno distinto de void, y ésta completa su tarea, la función usa una instrucción return (como en
la línea 22) para devolver un resultado a la función que la llamó. Por ejemplo, cuando usted va a un caje-
ro automático (ATM) y solicita el saldo de su cuenta, espera que el ATM le devuelva un valor que repre-
senta su saldo. De manera similar, cuando una instrucción llama a la función miembro obtener-
NombreCurso en un objeto LibroCalificaciones, la instrucción espera recibir el nombre del curso de
LibroCalificaciones (en este caso, un objeto string, como se especifica en el tipo de valor de retorno
de la función).
Si tiene una función llamada cuadrado que devuelve el cuadrado de su argumento, es de esperarse
que la instrucción
resultado = cuadrado( 2 );
devuelva 4 de la función cuadrado y asigne el valor 4 a la variable resultado. Si tiene una función lla-
mada maximo que devuelve el mayor de tres argumentos enteros, la siguiente instrucción
mayor = maximo( 27, 114, 51 );
devuelve 114 de la función maximo y asigna este valor a la variable mayor.
Las instrucciones en las líneas 16 y 22 utilizan la variable nombreCurso (línea 34), aun y cuando esta
variable no se declaró en ninguna de las funciones miembro. Podemos hacer esto ya que nombreCurso
es un miembro de datos de la clase y éstos son accesibles desde las funciones miembro de una clase.
La función miembro mostrarMensaje
La función miembro mostrarMensaje (líneas 26 a 32) no devuelve datos cuando completa su tarea,
por lo que su tipo de valor de retorno es void. Esta función no recibe parámetros, por lo que su lista
de parámetros está vacía. En las líneas 30 y 31 se imprime un mensaje de bienvenida, que incluye el
valor del dato miembro nombreCurso. La línea 30 llama a la función miembro obtenerNombreCurso
para obtener el valor de nombreCurso. La función miembro mostrarMensaje también podría acceder
al dato miembro nombreCurso directamente, así como las funciones miembro establecerNombre-
Curso y obtenerNombreCurso. En breve explicaremos por qué es preferible, desde la perspectiva
de ingeniería de software, llamar a la función miembro obtenerNombreCurso para obtener el valor de
nombreCurso.
Prueba de la clase LibroCalificaciones
La función main (líneas 38 a 54) crea un objeto de la clase LibroCalificaciones y utiliza cada una de
sus funciones miembro. En la línea 41 se crea un objeto LibroCalificaciones llamado miLibroCali-
ficaciones.Enlaslíneas44a45semuestraelnombreinicialdelcurso,llamandoalafunciónmiembro
obtenerNombreCurso del objeto. La primera línea de la salida no muestra un nombre de curso, ya que
al principio el dato miembro nombreCurso del objeto (es decir, un string) está vacío; de manera prede-
terminada, el valor inicial de un objeto string es lo que se denomina cadena vacía (una cadena que no
contiene caracteres). No aparece nada en la pantalla cuando se muestra una cadena vacía.
En la línea 48 se pide al usuario que escriba el nombre de un curso. La variable string local nom-
breDelCurso (declarada en la línea 40) se inicializa con el nombre del curso que escribió el usuario,
el cual se devuelve mediante la llamada a la función getline (línea 49). En la línea 50 se hace una lla-
mada a la función miembro establecerNombreCurso del objeto miLibroCalificaciones y se provee
nombreDelCurso como argumento para la función. Cuando se hace la llamada a la función, el valor del
argumento se copia al parámetro nombre (línea 14) de la función miembro establecerNombreCurso.
Después, el valor del parámetro se asigna al dato miembro nombreCurso (línea 16). En la línea 52 se
salta una línea en la salida; después en la línea 53 se hace una llamada a la función mostrarMensaje del
objeto miLibroCalificaciones para mostrar en pantalla el mensaje de bienvenida, que contiene
el nombre del curso.
78 Capítulo 3 Introducción a las clases, objetos y cadenas
Ingeniería de software mediante las funciones establecer y obtener
Los datos miembros private de una clase pueden manipularse sólo mediante las funciones miembro de
esa clase (y por los “amigos” de la clase, como veremos en el capítulo 9). Por lo tanto, un cliente de un
objeto (es decir, cualquier instrucción que llame a las funciones miembro del objeto desde su exterior)
llama a las funciones miembro public de la clase para solicitar los servicios de la clase para objetos espe-
cíficos de ésta. Esto explica por qué las instrucciones en la función main llaman a las funciones miembro
establecerNombreCurso, obtenerNombreCurso y mostrarMensaje en un objeto LibroCalificacio-
nes. A menudo, las clases proporcionan funciones miembro public para permitir a los clientes de la
clase establecer (es decir, asignar valores a) u obtener (es decir, obtener los valores de) datos miembros
private. Los nombres de estas funciones miembro no necesitan empezar con establecer u obtener,
pero esta convención de nomenclatura es común. En este ejemplo, la función miembro que establece el
dato miembro nombreCurso se llama establecerNombreCurso, y la función miembro que obtiene
el valor del miembro de datos nombreCurso se llama obtenerNombreCurso. Las funciones establecer se
conocen también como mutadores (porque mutan, o modifican, valores) y las funciones obtener
se conocen también como accesores (porque acceden a los valores).
Recuerde que al declarar datos miembros con el especificador de acceso private se cumple con la
ocultación de datos. Al proporcionar funciones establecer y obtener public, permitimos que los clientes
de una clase accedan a los datos ocultos, pero sólo en forma indirecta. El cliente sabe que está intentan-
do modificar u obtener los datos de un objeto, pero no sabe cómo el objeto lleva a cabo estas operaciones.
En algunos casos, una clase puede representar internamente una pieza de datos de cierta forma, pero
puede exponer esos datos a los clientes de una forma distinta. Por ejemplo, suponga que una clase Reloj
representa la hora del día como un miembro de datos private int llamado hora, que almacena el nú-
mero de segundos transcurridos desde media noche. Sin embargo, cuando un cliente llama a la función
miembro obtenerHora de un objeto Reloj, el objeto podría devolver la hora con horas, minutos y se-
gundos en un objeto string, en el formato HH:MM:SS. De manera similar, suponga que la clase Reloj
cuenta con una función establecer llamada establecerHora, que recibe un parámetro string en el
formato HH:MM:SS. Mediante el uso de las herramientas de la clase string (que puede ver en el capí-
tulo 21, el cual se encuentra en inglés en el sitio web), la función establecerHora podría convertir este
objeto string en un número de segundos, que la función almacena en su dato miembro private. La
función establecer también podría verificar que el valor que recibe represente una hora válida (por ejem-
plo, 12:30:45 es válida, pero 42:85:70 no). Las funciones establecer y obtener permiten que un
cliente interactúe con un objeto, pero los datos private del objeto permanecen encapsulados (ocultos)
de una manera segura dentro del mismo objeto.
Las funciones establecer y obtener de una clase también deben utilizarlas otras funciones miembro
dentro de la clase, para manipular los datos private de ésta, aunque estas funciones miembro pueden
acceder a los datos private directamente. En la figura 3.5, las funciones miembro establecerNombre-
Curso y obtenerNombreCurso son funciones miembro public, por lo que están accesibles para los
clientes de la clase, así como para la misma clase. La función miembro mostrarMensaje llama a la fun-
ción miembro obtenerNombreCurso para obtener el valor del dato miembro nombreCurso y mostrarlo
en pantalla, aun y cuando mostrarMensaje puede acceder directamente a nombreCurso; al acceder a un
dato miembro a través de su función obtener se crea una clase más robusta y mejor (es decir, una clase
que sea fácil de mantener y menos propensa a dejar de trabajar). Si decidimos cambiar el dato miembro
nombreCurso en cierta forma, la definición de mostrarMensaje no requerirá modificación; sólo los
cuerpos de las funciones establecer y obtener, que manipulan directamente al dato miembro, tendrán que
cambiar. Por ejemplo, suponga que decidimos representar el nombre del curso como dos datos miembro
separados:nombreCurso (porejemplo,CS101)ytituloCurso (porejemplo,Introduccion a la pro-
gramacion en C++). La función miembro mostrarMensaje puede aún emitir una sola llamada a la
función miembro obtenerNombreCurso para obtener el nombre completo del curso y mostrarlo como
parte del mensaje de bienvenida. En este caso, obtenerNombreCurso necesitaría crear y devolver un
objeto string que contenga el nombreCurso seguido del tituloCurso. La función miembro mostrar-
Mensaje seguiría mostrando el título completo del curso “CS101 Introduccion a la programacion
3.5 Inicialización de objetos mediante constructores 79
en C++”. Los beneficios de llamar a una función establecer desde otra función miembro de la clase se
volverán más claros cuando hablemos sobre la validación en la sección 3.8.
Buena práctica de programación 3.1
Trate siempre de localizar los efectos de las modificaciones a los datos miembros de una
clase, utilizando y manipulando los datos miembros a través de sus correspondientes
funciones obtener y establecer.
Observación de Ingeniería de Software 3.1
Escriba programas que sean comprensibles y fáciles de mantener. El cambio es la regla, en
vez de la excepción. Debemos anticipar que nuestro código será modificado en el futuro.
Diagrama de clases de UML para la clase LibroCalificaciones con un dato miembro,
y funciones establecer y obtener
La figura 3.6 contiene un diagrama de clases de UML actualizado para la versión de la clase Libro-
Calificaciones de la figura 3.5. Este diagrama modela el miembro de datos nombreCurso de la clase
LibroCalificaciones comounatributoenelcompartimientointermedio.UMLrepresentaalosdatos
miembros como atributos, listando el nombre del atributo, seguido de dos puntos y del tipo del atributo.
El tipo de UML del atributo nombreCurso es String, que corresponde al tipo string en C++. El
miembro de datos nombreCurso es private en C++, por lo que el diagrama de clases lista un signo menos
(-) en frente del nombre del atributo correspondiente. La clase LibroCalificaciones contiene tres
funciones miembro public, por lo que el diagrama de clases lista tres operaciones en el tercer compar-
timiento. La operación establecerNombreCurso tiene un parámetro String llamado nombre. UML
indica el tipo de valor de retorno de una operación colocando dos puntos y el tipo de valor de retorno
después de los paréntesis que le siguen al nombre de la operación. La función miembro obtener-
NombreCurso de la clase LibroCalificaciones tiene un tipo de valor de retorno string en C++, por lo
que el diagrama de clases muestra un tipo de valor de retorno String en UML. Las operaciones esta-
blecerNombreCurso y mostrarMensaje no devuelven valores (es decir, devuelven void en C++), por lo
que el diagrama de clases de UML no especifica un tipo de valor de retorno después de los paréntesis de
estas operaciones.
LibroCalificaciones
– nombreCurso : String
+ establecerNombreCurso ( nombre : String )
+ obtenerNombreCurso( ) : String
+ mostrarMensaje( )
Fig. 3.6  Diagrama de clases de UML para la clase LibroCalificaciones, con un atributo
privado nombreCurso y operaciones públicas establecerNombreCurso,
obtenerNombreCurso y mostrarMensaje.
3.5Inicialización de objetos mediante constructores
Como mencionamos en la sección 3.4, cuando se crea un objeto de la clase LibroCalificaciones (fi-
gura 3.5), su miembro de datos nombreCurso se inicializa con la cadena vacía de manera predetermina-
da. ¿Qué pasa si usted desea proporcionar el nombre de un curso a la hora de crear un objeto Libro-
Calificaciones? Cada clase que usted declare puede proporcionar uno o más constructores, los cuales
pueden utilizarse para inicializar un objeto de una clase al momento de crear ese objeto. Un constructor
es una función miembro especial que debe definirse con el mismo nombre que el de la clase, de manera
80 Capítulo 3 Introducción a las clases, objetos y cadenas
que el compilador pueda diferenciarlo de las demás funciones miembro de la clase. Una importante
diferencia entre los constructores y las otras funciones es que los primeros no pueden devolver valores, por
lo cual no pueden especificar un tipo de valor de retorno (ni siquiera void). Normalmente los construc-
tores son declarados public. En los primeros capítulos, por lo general nuestras clases tendrán un cons-
tructor; en capítulos posteriores veremos cómo crear clases con más de un constructor, mediante la
técnica de sobrecarga de funciones, que presentaremos en la sección 6.18.
C++ llama de manera automática a un constructor para cada objeto que se crea, lo cual ayuda a ase-
gurar que cada objeto se inicialice antes de utilizarlo en un programa. La llamada al constructor ocurre
cuando se crea el objeto. Si una clase no incluye constructores en forma explícita, el compilador propor-
ciona un constructor predeterminado sin parámetros. Por ejemplo, cuando en la línea 41 de la figura
3.5 se crea un objeto LibroCalificaciones, se hace una llamada al constructor predeterminado. Este
constructor proporcionado por el compilador crea un objeto LibroCalificaciones sin proporcionar
ningún valor inicial para los datos miembros de tipos fundamentales del objeto. Para los datos miembros
que son objetos de otras clases, el constructor predeterminado llama de manera implícita al constructor
predeterminado de cada dato miembro, para asegurar que ese dato miembro se inicialice en forma apro-
piada. Ésta es la razón por la cual el dato miembro string llamado nombreCurso (en la figura 3.5) se
inicializó con la cadena vacía; el constructor predeterminado para la clase string asigna al valor del ob-
jeto string a la cadena vacía.
En el ejemplo de la figura 3.7, especificamos el nombre de un curso para un objeto LibroCalifica-
ciones cuando se crea el objeto (línea 47). En este caso, el argumento CS101 Introduccion a la pro-
gramacion en C++ se pasa al constructor del objeto LibroCalificaciones (líneas 14 a 18) y se utiliza
para inicializar el nombreCurso. En la figura 3.7 se define una clase LibroCalificaciones modificada,
la cual contiene un constructor con un parámetro string que recibe el nombre inicial del curso.
1 // Fig. 3.7: fig03_07.cpp
2 // Creación de instancias de varios objetos de la clase LibroCalificaciones y uso
3 // de su constructor para especificar el nombre del curso
4 // cuando se crea cada objeto LibroCalificaciones.
5 #include iostream
6 #include string // el programa usa la clase string estándar de C++
7 using namespace std;
8
9 // definición de la clase LibroCalificaciones
10 class LibroCalificaciones
11 {
12 public:
13 // el constructor inicializa a nombreCurso con la cadena que se suministra
como argumento
14 explicit LibroCalificaciones( string nombre )
15 : nombreCurso ( nombre ) // inicializador de miembro para inicializar
nombreCurso
16 {
17 // cuerpo vacío
18 } // fin del constructor de LibroCalificaciones
19
20 // función para establecer el nombre del curso
21 void establecerNombreCurso( string nombre )
22 {
23 nombreCurso = nombre; // almacena el nombre del curso en el objeto
24 } // fin de la función establecerNombreCurso
Fig. 3.7  Creación de instancias de varios objetos de la clase LibroCalificaciones y uso de su
constructor para especificar el nombre del curso cuando se crea cada objeto LibroCalificaciones
(parte 1 de 2).
3.5 Inicialización de objetos mediante constructores 81
25
26 // función para obtener el nombre del curso
27 string obtenerNombreCurso() const
28 {
29 return nombreCurso; // devuelve el nombreCurso del objeto
30 } // fin de la función obtenerNombreCurso
31
32 // muestra un mensaje de bienvenida para el usuario de LibroCalificaciones
33 void mostrarMensaje() const
34 {
35 // llama a obtenerNombreCurso para obtener el nombreCurso
36 cout  Bienvenido al libro de calificaciones paran
 obtenerNombreCurso()
37  !  endl;
38 } // fin de la función mostrarMensaje
39 private:
40 string nombreCurso; // nombre del curso para este LibroCalificaciones
41 }; // fin de la clase LibroCalificaciones
42
43 // la función main empieza la ejecución del programa
44 int main()
45 {
46 // crea dos objetos LibroCalificaciones
47 LibroCalificaciones libroCalificaciones1( CS101 Introduccion a la
programacion en C++ );
48 LibroCalificaciones libroCalificaciones2( CS102 Estructuras de datos en
C++ );
49
50 // muestra el valor inicial de nombreCurso para cada LibroCalificaciones
51 cout  libroCalificaciones1 se creo para el curso: 
 libroCalificaciones1.obtenerNombreCurso()
52  nlibroCalificaciones2 se creo para el curso: 
 libroCalificaciones2.obtenerNombreCurso()
53  endl;
54 } // fin de main
libroCalificaciones1 se creo para el curso: CS101 Introduccion a la programacion en
C++ libroCalificaciones2 se creo para el curso: CS102 Estructuras de datos en C++
Definición de un constructor
En las líneas 14 a 18 de la figura 3.7 se define un constructor para la clase LibroCalificaciones. El
constructor tiene el mismo nombre que su clase, LibroCalificaciones. Un constructor especifica en su
lista de parámetros los datos que requiere para realizar su tarea. Al crear un nuevo objeto, el programador
coloca estos datos en los paréntesis que van después del nombre del objeto (como hicimos en las líneas 47
y 48). En la línea 14 se indica que el constructor de la clase LibroCalificaciones tiene un parámetro
string llamado nombre. Declaramos este constructor como explicit debido a que recibe un solo pará-
metro: esto es importante por razones sutiles que conocerá en la sección 10.13. Por ahora, sólo declara
todos los constructores de un solo parámetro como explicit. En la línea 14 no se especifica un tipo de
valor de retorno, ya que los constructores no pueden devolver valores (ni siquiera void). Además, los
constructores no pueden declararse como const (ya que al inicializar un objeto, éste se modifica).
Fig. 3.7  Creación de instancias de varios objetos de la clase LibroCalificaciones y uso de su
constructor para especificar el nombre del curso cuando se crea cada objeto LibroCalificaciones
(parte 2 de 2).
82 Capítulo 3 Introducción a las clases, objetos y cadenas
Elconstructorusaunalistadeinicializadoresdemiembros(línea15)parainicializareldatomiem-
bro nombreCurso con el valor del parámetro nombre del constructor. Los inicializadores de miembros
aparecen entre la lista de parámetros de un constructor y la llave izquierda que comienza el cuerpo del
constructor. La lista de inicializadores de miembros se separa de la lista de parámetros con un signo de dos
puntos (:). Un inicializador de miembro consiste en el nombre de la variable de un dato miembro seguido
de paréntesis que contienen el valor inicial del miembro. En este ejemplo, nombreCurso se inicializa con
el valor del parámetro nombre. Si una clase contiene más de un miembro de datos, el inicializador de cada
miembro de datos se separa del siguiente con una coma. La lista de inicializadores de miembros se ejecu-
ta antes de que se ejecute el cuerpo del constructor. Podemos realizar la inicialización en el cuerpo del
constructor, pero más adelante aprenderá en el libro que es más eficiente hacerlo con inicializadores
miembro; además, algunos tipos de datos miembros deben inicializarse de esta forma.
Observe que tanto el constructor (línea 14) como la función establecerNombreCurso (línea 21)
utilizan un parámetro llamado nombre. Puede usar los mismos nombres de parámetros en distintas fun-
ciones, ya que los parámetros son locales para cada función; no interfieren unos con otros.
Prueba de la clase LibroCalificaciones
En las líneas 44 a 54 de la figura 3.7 se define la función main que prueba la clase LibroCalificaciones
y demuestra cómo inicializar objetos LibroCalificaciones mediante el uso de un constructor. En la
línea 47, se crea y se inicializa un objeto LibroCalificaciones llamado libroCalificaciones1.
Cuando se ejecuta esta línea, se hace una llamada al constructor de LibroCalificaciones (líneas 14 a
18) con el argumento CS101 Introduccion a la programacion en C++ para inicializar el nombre
del curso de libroCalificaciones1. En la línea 48 se repite este proceso para el objeto LibroCalifi-
caciones llamado libroCalificaciones2, pero esta vez se pasa el argumento CS102 Estructuras de
datos en C++ para inicializar el nombre del curso de libroCalificaciones2. En las líneas 51 y 52 se
utiliza la función miembro obtenerNombreCurso de cada objeto para obtener los nombres de los cursos
y mostrar que, sin duda, se inicializaron al momento de crear los objetos. La salida confirma que cada
objeto LibroCalificaciones mantiene su propia copia del miembro de datos nombreCurso.
Formas de proporcionar un constructor predeterminado para una clase
Cualquier constructor que no recibe argumentos se llama constructor predeterminado. Una clase puede
recibir un constructor predeterminado en una de varias formas:
1. El compilador crea de manera implícita un constructor predeterminado en cada clase que no
tenga constructores definidos por el usuario. El constructor predeterminado no inicializa los
datos miembros de la clase, pero llama al constructor predeterminado para cada dato miembro
que sea un objeto de otra clase. Una variable sin inicializar contiene un valor indefinido (“ba-
sura”).
2. Usted como programador define en forma explícita un constructor que no recibe argumentos.
Dicho constructor llamará al constructor predeterminado para cada dato miembro que sea un
objeto de otra clase y realizará la inicialización adicional especificada por usted.
3. Si define constructores con argumentos, C++ no creará de manera implícita un constructor prede-
terminado para esa clase. Más tarde le mostraremos que C++11 le permite forzar al compilador
a crear el constructor predeterminado, incluso aunque no haya definido constructores no
predeterminados.
Para cada versión de la clase LibroCalificaciones en las figuras 3.1, 3.3 y 3.5, el compilador definió
de manera implícita un constructor predeterminado.
Tip para prevenir errores 3.2
A menos que no sea necesario inicializar los datos miembros de su clase (casi nunca), debe
proporcionar constructores para asegurar que los datos miembros de su clase se inicialicen
con valores significativos al momento de crear cada nuevo objeto de su clase.
3.6 Colocar una clase en un archivo separado para fines de reutilización 83
Observación de Ingeniería de Software 3.2
Los datos miembros se pueden inicializar en un constructor, o sus valores pueden establecer-
se más adelante, después de crear el objeto. Sin embargo, es una buena práctica de ingeniería
de software asegurarse que un objeto esté inicializado por completo antes de que el código
cliente invoque las funciones miembro de ese objeto. En general, no debemos depender del
código cliente para asegurar que un objeto se inicialice de manera apropiada.
Agregar el constructor al diagrama de clases de UML de la clase LibroCalificaciones
El diagrama de clases de UML de la figura 3.8 modela la clase LibroCalificaciones de la figura 3.7, la
cual tiene un constructor con un parámetro nombre de tipo string (representado por el tipo String en
UML). Al igual que las operaciones, el UML modela a los constructores en el tercer compartimiento de
una clase en un diagrama de clases. Para diferenciar a un constructor de las operaciones de la clase, UML
coloca la palabra “constructor” entre los signos « y » antes del nombre del constructor. Es costumbre
enlistar el constructor de la clase antes de todas las operaciones en el tercer compartimiento.
LibroCalificaciones
– nombreCurso : String
«constructor» + LibroCalificaciones( nombre : String )
+ establecerNombreCurso( nombre : String )
+ obtenerNombreCurso( ) : String
+ mostrarMensaje( )
Fig. 3.8  Diagrama de clases de UML, el cual indica que la clase LibroCalificaciones tiene
un constructor con un parámetro llamado nombre de tipo String de UML.
3.6Colocar una clase en un archivo separado para fines
de reutilización
Uno de los beneficios de crear definiciones de clases es que, cuando se empaquetan en forma apropia-
da, nuestras clases pueden ser reutilizadas por otros programadores. Por ejemplo, podemos reutilizar
el tipo string de la Biblioteca estándar de C++ en cualquier programa en C++ al incluir el encabeza-
do string (y, como veremos, al poder enlazarnos con el código objeto de la biblioteca).
LosprogramadoresquedeseenutilizarnuestraclaseLibroCalificaciones nopuedensimplemen-
te incluir el archivo de la figura 3.7 en otro programa. Como aprendió en el capítulo 2, la función main
empieza la ejecución de todo programa, y cada programa debe tener sólo y solamente una función main.
Si otros programadores incluyen el código de la figura 3.7, recibirán “equipaje” adicional (nuestra fun-
ción main) y sus programas tendrán entonces dos funciones main. Si intentamos compilar un programa
con dos funciones main se producirá un error. Por lo tanto, al colocar main en el mismo archivo con una
definición de clase, evitamos que esa clase pueda ser reutilizada por otros programas. En esta sección de-
mostraremos cómo hacer la clase LibroCalificaciones reutilizable, al separarla de la función main y
colocarla en otro archivo.
Encabezados
Cada uno de los ejemplos anteriores en el capítulo consiste de un solo archivo .cpp, al cual se le conoce
también como archivo de código fuente, y contiene la definición de la clase LibroCalificaciones y
una función main. Al construir un programa en C++ orientado a objetos, es costumbre definir el código
fuente reutilizable (como una clase) en un archivo que, por convención, tiene la extensión .h; a éste se
le conoce como encabezado. Los programas utilizan las directivas del preprocesador #include para
incluir encabezados y aprovechar los componentes de software reutilizables, como el tipo string que se
84 Capítulo 3 Introducción a las clases, objetos y cadenas
proporciona en la Biblioteca Estándar de C++, y los tipos definidos por el usuario como la clase Libro-
Calificaciones.
En nuestro siguiente ejemplo, separamos el código de la figura 3.7 en dos archivos: LibroCa-
lificaciones.h (figura 3.9) y fig03_10.cpp (figura 3.10). Cuando analice el encabezado de la figura
3.9, observe que sólo contiene la definición de la clase LibroCalificaciones (líneas 7 a 38) y los enca-
bezados de los que depende la clase. La función main que utiliza a la clase LibroCalificaciones se
define en el archivo de código fuente fig03_10.cpp (figura 3.10), en las líneas 8 a 18. Para ayudarlo a
prepararse para los programas más extensos que encontrará más adelante en este libro y en la industria,
a menudo utilizamos un archivo de código fuente separado que contiene la función main para probar
nuestras clases (a éste se le conoce como programa controlador). Pronto aprenderá cómo un archivo
de código fuente con main puede utilizar la definición de una clase que se encuentra en un encabezado
para crear objetos de esa clase.
1 // Fig. 3.9: LibroCalificaciones.h
2 // Definición de la clase LibroCalificaciones en un archivo separado de main.
3 #include iostream
4 #include string // la clase LibroCalificaciones utiliza la clase string
estándar de C++
5
6 // definición de la clase LibroCalificaciones
7 class LibroCalificaciones
8 {
9 public:
10 // el constructor inicializa nombreCurso con la cadena que se suministra como
argumento
11 explicit LibroCalificaciones( std::string nombre )
12 : nombreCurso( nombre ) // inicializador de miembro para inicializar
nombreCurso
13 {
14 // cuerpo vacío
15 } // fin del constructor de LibroCalificaciones
16
17 // función para establecer el nombre del curso
18 void establecerNombreCurso( std::string nombre )
19 {
20 nombreCurso = nombre; // almacena el nombre del curso en el objeto
21 } // fin de la función establecerNombreCurso
22
23 // función para obtener el nombre del curso
24 std::string obtenerNombreCurso() const
25 {
26 return nombreCurso; // devuelve el nombreCurso del objeto
27 } // fin de la función obtenerNombreCurso
28
29 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones
30 void mostrarMensaje() const
31 {
32 // llama a obtenerNombreCurso para obtener el nombreCurso
33 std::cout  Bienvenido al libro de calificaciones paran
 obtenerNombreCurso()
34  !  std::endl;
35 } // fin de la función mostrarMensaje
36 private:
37 std::string nombreCurso; // nombre del curso para este LibroCalificaciones
38 }; // fin de la clase LibroCalificaciones
Fig. 3.9  Definición de la clase LibroCalificaciones en un archivo separado de main.
3.6 Colocar una clase en un archivo separado para fines de reutilización 85
1 // Fig. 3.10: fig03_10.cpp
2 // Inclusión de la clase LibroCalificaciones del archivo LibroCalificaciones.h
para usarla en main.
3 #include iostream
4 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
5 using namespace std;
6
7 // la función main empieza la ejecución del programa
8 int main()
9 {
10 // crea dos objetos LibroCalificaciones
11 LibroCalificaciones libroCalificaciones1( CS101 Introduccion a la
programacion en C++ );
12 LibroCalificaciones libroCalificaciones2( CS102 Estructuras de datos en
C++ );
13
14 // muestra el valor inicial de nombreCurso para cada LibroCalificaciones
15 cout  libroCalificaciones1 creado para el curso: 
 libroCalificaciones1.obtenerNombreCurso()
16  nlibroCalificaciones2 creado para el curso:   libroCalificaciones2.
obtenerNombreCurso()
17  endl;
18 } // fin de main
libroCalificaciones1 creado para el curso: CS101 Introduccion a la programacion en
C++ libroCalificaciones2 creado para el curso: CS102 Estructuras de datos en C++
Fig. 3.10  Inclusión de la clase LibroCalificaciones del archivo LibroCalificaciones.h
para usarla en main.
Usar std:: con los componentes de la biblioteca estándar en los encabezados
En el encabezado (figura 3.9) utilizamos std:: al referirnos a un objeto string (líneas 11, 18, 24 y 37),
cout (línea 33) y endl (línea 34). Por razones sutiles que explicaremos en un capítulo posterior, los en-
cabezados nunca deben contener directivas using o declaraciones using (sección 2.7).
Incluir un archivo de encabezado que contiene una clase definida por el usuario
Un archivo de encabezado tal como LibroCalificaciones.h (figura 3.9) no puede usarse como un
programa completo, ya que no contiene una función main. Para probar la clase LibroCalificaciones
(definida en la figura 3.9), debe escribir un archivo de código fuente separado que contenga una función
main (como la figura 3.10), la cual debe instanciar y utilizar objetos de la clase.
El compilador no sabe qué es un LibroCalificaciones ya que es un tipo definido por el usuario.
De hecho, el compilador ni siquiera conoce las clases de la Biblioteca estándar de C++. Para ayudarlo a
comprender cómo usar una clase, debemos proporcionar en forma explícita al compilador la definición
de la clase; ésta es la razón por la que, para que un programa pueda usar un tipo string, debe incluir el
encabezadostring.Estopermitealcompiladordeterminarlacantidaddememoriaquedebereservar
paracadaobjetodelaclase,yasegurarqueunprogramallamealasfuncionesmiembrodelaclasestring
de una forma correcta.
ParacrearlosobjetosLibroCalificaciones llamadoslibroCalificaciones1 ylibroCalificacio-
nes2 en las líneas 11 y 12 de la figura 3.10, el compilador debe conocer el tamaño de un objeto LibroCali-
ficaciones. Aunque en concepto los objetos contienen datos miembros y funciones miembro, los objetos
de C++ sólo contienen datos. El compilador sólo crea una copia de las funciones miembro de la clase y
comparteesacopiaentretodoslosobjetosdelaclase.Desdeluegoquecadaobjetonecesitasuspropiosdatos
86 Capítulo 3 Introducción a las clases, objetos y cadenas
miembros, ya que su contenido puede variar de un objeto a otro (como dos objetos CuentaBanco distintos,
que tienen dos saldos distintos). Sin embargo, el código de la función miembro no se puede modificar, por
lo que puede compartirse entre todos los objetos de la clase. Por lo tanto, el tamaño de un objeto depende
de la cantidad de memoria requerida para almacenar los datos miembros de la clase. Al incluir a
LibroCalificaciones.h en la línea 4, proporcionamos acceso al compilador para que utilice la informa-
ción que necesita (figura 3.9, línea 37) para determinar el tamaño de un objeto LibroCalificaciones y
determinarsilosobjetosdelaclaseseutilizancorrectamente(enlaslíneas11a12y15a16delafigura3.10).
En la línea 4 se indica al preprocesador de C++ que reemplace la directiva con una copia del contenido
de LibroCalificaciones.h (es decir, la definición de la clase LibroCalificaciones) antes de compilar
el programa. Cuando se compila el archivo de código fuente fig03_10.cpp, ahora contiene la definición
de la clase LibroCalificaciones (debido a la instrucción #include), y el compilador es capaz de crear
objetos LibroCalificaciones y revisar que se hagan llamadas a sus funciones miembro en forma adecua-
da. Ahora que la definición de la clase está en un encabezado (sin una función main), podemos incluir ese
encabezado en cualquier programa que necesite reutilizar nuestra clase LibroCalificaciones.
Cómo se localizan los encabezados
Observe que el nombre del encabezado LibroCalificaciones.h en la línea 4 de la figura 3.10 se encie-
rra entre comillas ( ) en vez de usar los signos  y . Por lo general, los archivos de código fuente de un
programa y los encabezados definidos por el usuario se colocan en el mismo directorio. Cuando el prepro-
cesador encuentra el nombre de un encabezado entre comillas, intenta localizar el encabezado en el
mismo directorio que el archivo en el que aparece la directiva #include. Si el preprocesador no puede
encontrar el encabezado en ese directorio, lo busca en la(s) misma(s) ubicación(es) que los encabezados
de la Biblioteca Estándar de C++. Cuando el preprocesador encuentra el nombre de un encabezado entre
los signos  y  (como iostream), asume que el encabezado forma parte de la Biblioteca Estándar de
C++ y no busca en el directorio del programa que se está procesando.
Tip para prevenir errores 3.3
Para asegurar que el preprocesador pueda localizar los encabezados en forma correcta, en las
directivas del preprocesador #include se deben colocar los nombres de los encabezados definidos por
el usuario entre comillas (como LibroCalificaciones.h), y se deben colocar los nombres de los
archivos de encabezado de la Biblioteca Estándar de C++ entre los signos  y  (como iostream).
Cuestiones adicionales sobre Ingeniería de Software
AhoraquelaclaseLibroCalificaciones estádefinidaenunarchivodeencabezado,puedereutilizarse.Por
desgracia, al colocar la definición de una clase en un archivo de encabezado como en la figura 3.9, se sigue
revelandotodalaimplementacióndelaclasealosclientesdelamisma;LibroCalificaciones.h essimplemen-
teunarchivodetextoquecualquierapuedeabriryleer.LasabiduríadelaIngenieríadesoftwareconvencio-
nal nos dice que para usar un objeto de la clase, el código cliente necesita saber sólo qué funciones miembro
debellamar,quéargumentosdebeproporcionaracadafunciónmiembroyquétipodevalorderetornodebe
esperar de cada función miembro. El código cliente no necesita saber cómo se implementan esas funciones.
Si el código cliente sabe cómo se implementa una clase, el programador podría escribir código
cliente basado en los detalles de implementación de la clase. Lo ideal sería que, si cambia la implemen-
tación, los clientes de la clase no tengan que cambiar. Al ocultar los detalles de implementación de la clase,
facilitamos la tarea de cambiar la implementación de la clase al mismo tiempo que minimizamos (y con
suerte, eliminamos) los cambios al código cliente.
En la sección 3.7 le mostraremos cómo descomponer la clase LibroCalificaciones en dos archi-
vos, de manera que:
1. la clase sea reutilizable,
2. los clientes de la clase sepan qué funciones miembro proporciona la clase, cómo llamarlas y qué
tipo de valores de retorno esperar, y
3. los clientes no sepan cómo se implementan las funciones miembro de la clase.
3.7 Separar la interfaz de la implementación 87
3.7Separar la interfaz de la implementación
En la sección anterior le mostramos cómo fomentar la reutilización de software al separar la definición de
la clase del código cliente (por ejemplo, la función main) que utiliza esa clase. Ahora presentaremos otro
principio fundamental de la buena Ingeniería de software: separar la interfaz de la implementación.
La interfaz de una clase
Las interfaces definen y estandarizan las formas en las que las personas y los sistemas interactúan entre
sí. Por ejemplo, los controles de un radio sirven como una interfaz entre los usuarios del radio y sus
componentes internos. Los controles permiten a los usuarios realizar un conjunto limitado de opera-
ciones (como cambiar la estación, ajustar el volumen y elegir entre una estación en AM o una en FM).
Varias radios pueden implementar estas operaciones de manera distinta; algunos proporcionan boto-
nes, otros perillas y algunas incluso soportan comandos de voz. La interfaz especifica qué operaciones
permite realizar un radio a los usuarios, pero no especifica cómo se implementan estas operaciones en
su interior.
De manera similar, la interfaz de una clase describe qué servicios pueden usar los clientes de la
clase y cómo solicitar esos servicios, pero no cómo lleva a cabo la clase esos servicios. La interfaz public
de una clase consiste en las funciones miembro public de la clase (también conocidas como servicios
públicos). Por ejemplo, la interfaz de la clase LibroCalificaciones (figura 3.9) contiene un construc-
tor y las funciones miembro establecerNombreCurso, obtenerNombreCurso y mostrarMensaje. Los
clientes de LibroCalificaciones (por ejemplo, main en la figura 3.10) utilizan estas funciones para
solicitar los servicios de la clase. Como pronto veremos, podemos especificar la interfaz de una clase al
escribir una definición de clase que sólo enliste los nombres de las funciones miembro, los tipos de los
valores de retorno y los tipos de los parámetros.
Separar la interfaz de la implementación
En nuestros ejemplos anteriores, la definición de cada clase contenía las definiciones completas de las
funciones miembro public de la clase y las declaraciones de sus datos miembros private. Sin embargo,
una mejor ingeniería de software es definir las funciones miembro fuera de la definición de la clase, de
manera que sus detalles de implementación se puedan ocultar del código cliente. Esta práctica asegura
que los programadores no escriban código cliente que dependa de los detalles de implementación de la
clase.
El programa de las figuras 3.11 a 3.13 separa la interfaz de LibroCalificaciones de su implemen-
tación, para lo cual divide la definición de la clase de la figura 3.9 en dos archivos: el encabeza-
do LibroCalificaciones.h (figura 3.11) en el que se define la clase LibroCalificaciones, y el archivo
de código fuente LibroCalificaciones.cpp (figura 3.12) en el que se definen las funciones miembro de
LibroCalificaciones. Por convención, las definiciones de las funciones miembro se colocan en un
archivo de código fuente con el mismo nombre base (por ejemplo, LibroCalificaciones) que el enca-
bezado de la clase, pero con una extensión de archivo .cpp. El archivo de código fuente fig03_13.cpp
(figura 3.13) define la función main (el código cliente). El código y la salida de la figura 3.13 son idén-
ticos a los de la figura 3.10. En la figura 3.14 se muestra cómo se compila este programa de tres archivos,
desde las perspectivas del programador de la clase LibroCalificaciones y del programador del código
cliente; explicaremos esta figura con detalle.
LibroCalificaciones.h: definición de la interfaz de una clase mediante
prototipos de funciones
El archivo de encabezado LibroCalificaciones.h (figura 3.11) contiene otra versión de la definición
de la clase LibroCalificaciones (líneas 8 a 17). Esta versión es similar a la de la figura 3.9, pero
las definiciones de las funciones en la figura 3.9 se reemplazan aquí con prototipos de funciones (líneas
11 a 14) que describen la interfaz public de la clase sin revelar las implementaciones de sus funciones miem-
bro. Un prototipo de función es una declaración de una función que indica al compilador el nombre de
88 Capítulo 3 Introducción a las clases, objetos y cadenas
la función, su tipo de valor de retorno y los tipos de sus parámetros. Además, el encabezado sigue espe-
cificando el dato miembro private de la clase (línea 16) también. De nuevo, el compilador debe cono-
cer los datos miembros de la clase para determinar cuánta memoria debe reservar para cada objeto de la
misma. Al incluir el encabezado LibroCalificaciones.h en el código cliente (línea 5 de la figura 3.13),
el compilador obtiene la información que necesita para asegurar que el código cliente llame a las funcio-
nes miembro de la clase LibroCalificaciones en forma correcta.
1 // Fig. 3.11: LibroCalificaciones.h
2 // Definición de la clase LibroCalificaciones. Este archivo presenta la interfaz
3 // public de LibroCalificaciones sin revelar las implementaciones de sus funciones
4 // miembro, que están definidas en LibroCalificaciones.cpp.
5 #include string // la clase LibroCalificaciones utiliza la clase string
estándar de C++
6
7 // definición de la clase LibroCalificaciones
8 class LibroCalificaciones
9 {
10 public:
11 explicit LibroCalificaciones( std::string ); // constructor que inicializa a
nombreCurso
12 void establecerNombreCurso( std::string ); // establece el nombre del curso
13 std::obtenerNombreCurso() const; // obtiene el nombre del curso
14 void mostrarMensaje() const; // muestra un mensaje de bienvenida
15 private:
16 std::string nombreCurso; // nombre del curso para este LibroCalificaciones
17 }; // fin de la clase LibroCalificaciones
Fig. 3.11  Definición de la clase LibroCalificaciones que contiene prototipos de funciones
que especifican la interfaz de la clase.
El prototipo de función en la línea 11 (figura 3.11) indica que el constructor requiere un parámetro
string. Recuerde que los constructores no tienen tipos de valores de retorno, por lo que no aparece
ningún tipo de valor de retorno en el prototipo de la función. El prototipo de la función miembro
establecerNombreCurso indica que requiere un parámetro string y no devuelve un valor (es decir, su
tipo de valor de retorno es void). El prototipo de la función miembro obtenerNombreCurso indica
que la función no requiere parámetros y devuelve un string. Por último, el prototipo de la función
mostrarMensaje (línea 14) especifica que mostrarMensaje no requiere parámetros y no devuelve un
valor. Estos prototipos de funciones son iguales que las primeras líneas de las correspondientes defini-
ciones de funciones en la figura 3.9, sólo que los nombres de los parámetros (que son opcionales en los
prototipos) no se incluyen y cada prototipo de función debe terminar con un punto y coma.
Buena práctica de programación 3.2
Aunque los nombres de los parámetros en los prototipos de funciones son opcionales (el
compilador los ignora), muchos programadores utilizan estos nombres para fines de docu-
mentación.
LibroCalificaciones.cpp: definir las funciones miembro en un archivo
de código fuente separado
El archivo de código fuente LibroCalificaciones.cpp (figura 3.12) define las funciones miembro de
la clase LibroCalificaciones, que se declararon en las líneas 11 a 14 de la figura 3.11. Las definiciones
aparecen en las líneas 9 a 33 y son casi idénticas a las definiciones de las funciones miembro en las líneas
11 a 35 de la figura 3.9. Cabe mencionar que la palabra clave const debe aparecer tanto en los prototipos
3.7 Separar la interfaz de la implementación 89
de función (figura 3.11, líneas 13 y 14) y las definiciones de las funciones obtenerNombreCurso y mos-
trarMensaje (líneas 22 y 28).
1 // Fig. 3.12: LibroCalificaciones.cpp
2 // Definiciones de las funciones miembro de LibroCalificaciones. Este archivo
contiene
3 // implementaciones de las funciones miembro cuyo prototipo está en
LibroCalificaciones.h.
4 #include iostream
5 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
6 using namespace std;
7
8 // el constructor inicializa nombreCurso con el objeto string suministrado como
argumento
9 LibroCalificaciones::LibroCalificaciones( string nombre )
10 : nombreCurso( nombre ) // inicializador de miembro para inicializar nombreCurso
11 {
12 // cuerpo vacío
13 } // fin del constructor de LibroCalificaciones
14
15 // función para establecer el nombre del curso
16 void LibroCalificaciones::establecerNombreCurso( string nombre )
17 {
18 nombreCurso = nombre; // almacena el nombre del curso en el objeto
19 } // fin de la función establecerNombreCurso
20
21 // función para obtener el nombre del curso
22 string LibroCalificaciones::obtenerNombreCurso() const
23 {
24 return nombreCurso; // devuelve el nombreCurso del objeto
25 } // fin de la función obtenerNombreCurso
26
27 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones
28 void LibroCalificaciones::mostrarMensaje() const
29 {
30 // llama a obtenerNombreCurso para obtener el nombreCurso
31 cout  Bienvenido al libro de calificaciones paran  obtenerNombreCurso()
32  !  endl;
33 } // fin de la función mostrarMensaje
Fig. 3.12  Las definiciones de las funciones miembro de LibroCalificaciones representan
la implementación de la clase LibroCalificaciones.
Al nombre de cada función miembro (líneas 9, 16, 22 y 28) se le antepone el nombre de la clase y
el símbolo ::, que se conoce como el operador de resolución de ámbito. Esto “enlaza” a cada función
miembro con la definición (ahora separada) de la clase LibroCalificaciones (figura 3.11), la cual
declara las funciones miembro y los datos miembros. Sin “LibroCalificaciones::” antes de cada
nombre de función, el compilador no las reconocería como funciones miembro de la clase LibroCali-
ficaciones; las consideraría como funciones “libres” o “sueltas”, al igual que main. A éstas también se
les conoce como funciones globales. Dichas funciones no pueden acceder a los datos private de Libro-
Calificaciones ni llamar a las funciones miembro de la clase, sin especificar un objeto. Por lo tanto, el
compilador no podría compilar estas funciones. Por ejemplo, en las líneas 18 y 24 en la figura 3.12 que
acceden a la variable nombreCurso se producirían errores de compilación, ya que nombreCurso no está
declarada como una variable local en cada función; el compilador no sabría que nombreCurso ya está de-
clarada como dato miembro de la clase LibroCalificaciones.
90 Capítulo 3 Introducción a las clases, objetos y cadenas
Error común de programación 3.3
Al definir las funciones miembro de una clase fuera de la misma, si se omite el nombre de
la clase y el operador de resolución de ámbito (::) antes de los nombres de las funciones se
producen errores de compilación.
Para indicar que las funciones miembro en LibroCalificaciones.cpp forman parte de la clase
LibroCalificaciones, debemos primero incluir el archivo de encabezado LibroCalificaciones.h
(línea 5 de la figura 3.12), Esto nos permite acceder al nombre de la clase LibroCalificaciones en el
archivo LibroCalificaciones.cpp. Al compilar LibroCalificaciones.cpp, el compilador utiliza la
información en LibroCalificaciones.h para asegurar que
1. la primera línea de cada función miembro (líneas 9, 16, 22 y 28) coincida con su prototipo en
el archivo LibroCalificaciones.h; por ejemplo, el compilador asegura que obtenerNombre-
Curso no acepte parámetros y devuelva un valor string, y que
2. cada función miembro sepa acerca de los datos miembros y otras funciones miembro de la
clase; por ejemplo, en las líneas 18 y 24 se puede acceder a la variable nombreCurso, ya que está
declaradaenLibroCalificaciones.h comodatomiembrodelaclaseLibroCalificaciones,
y en la línea 31 se puede llamar a la función obtenerNombreCurso, ya que se declara como
función miembro de la clase en LibroCalificaciones.h (y debido a que la llamada se confor-
ma con el prototipo correspondiente).
Prueba de la clase LibroCalificaciones
En la figura 3.13 se realizan las mismas manipulaciones de objetos LibroCalificaciones que en la fi-
gura3.10.AlsepararlainterfazdeLibroCalificaciones delaimplementacióndesusfuncionesmiem-
bro, no se ve afectada la forma en que este código cliente utiliza la clase. Sólo afecta la forma en que se
compila y enlaza el programa, lo cual veremos en breve.
1 // Fig. 3.13: fig03_13.cpp
2 // Demostración de la clase LibroCalificaciones después de separar
3 // su interfaz de su implementación.
4 #include iostream
5 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones GradeBook
6 using namespace std;
7
8 // la función main empieza la ejecución del programa
9 int main()
10 {
11 // crea dos objetos LibroCalificaciones
12 LibroCalificaciones libroCalificaciones1( CS101 Introduccion a la
programacion en C++ );
13 LibroCalificaciones libroCalificaciones2( CS102 Estructuras de datos en C++ );
14
15 // muestra el valor inicial de nombreCurso para cada LibroCalificaciones
16 cout  libroCalificaciones1 creado para el curso: 
 libroCalificaciones1.obtenerNombreCurso()
17  nlibroCalificaciones2 creado para el curso: 
 libroCalificaciones2.obtenerNombreCurso()
18  endl;
19 } // fin de main
Fig. 3.13  Demostración de la clase LibroCalificaciones después de separar la interfaz
de su implementación (parte 1 de 2).
3.7 Separar la interfaz de la implementación 91
libroCalificaciones1 creado para el curso: CS101 Introduccion a la programacion en C++
libroCalificaciones2 creado para el curso: CS102 Estructuras de datos en C++
Al igual que en la figura 3.10, en la línea 5 de la figura 3.13 se incluye el archivo de encabezado
LibroCalificaciones.h, de manera que el compilador pueda asegurar que los objetos LibroCalifica-
ciones se creen y manipulen correctamente en el código cliente. Antes de ejecutar este programa, deben
compilarse los archivos de código fuente de las figuras 3.12 y 3.13, y después se deben enlazar; es decir,
las llamadas a las funciones miembro en el código cliente necesitan enlazarse con las implementaciones
de las funciones miembro de la clase; un trabajo que realiza el enlazador.
El proceso de compilación y enlace
El diagrama de la figura 3.14 muestra el proceso de compilación y enlace que produce una aplicación
LibroCalificaciones ejecutable, que los instructores pueden utilizar. Lo común es que un programa-
dor cree y compile la interfaz e implementación de una clase, y que otro programador implemente el
código cliente para utilizar esa clase. Así, el diagrama muestra lo que requieren tanto el programador de
la implementación de la clase, como el programador del código cliente. Las líneas punteadas en el dia-
grama muestran las piezas requeridas por el programador de la implementación de la clase, el progra-
mador del código cliente y el usuario de la aplicación LibroCalificaciones, respectivamente. [Nota:
la figura 3.14 no es un diagrama de UML].
El programador de la implementación de una clase, responsable de crear una clase LibroCalificacio-
nes reutilizable,creaelencabezadoLibroCalificaciones.h yelarchivodecódigofuenteLibroCalifica-
ciones.cpp que incluye (mediante #include) el encabezado, y después compila el archivo de código fuente
para crear el código objeto de LibroCalificaciones. Para ocultar los detalles de la implementación de las
funcionesmiembrodeLibroCalificaciones,elprogramadordelaimplementacióndelaclaseproporciona
al programador del código cliente el encabezado LibroCalificaciones.h (que especifica la interfaz y los
datos miembros de la clase) y el código objeto de LibroCalificaciones (que contiene las instrucciones en
lenguajemáquina que representan a lasfuncionesmiembrode LibroCalificaciones). El programador del
códigoclientenorecibeLibroCalificaciones.cpp,porloquedesconocecómoseimplementanlasfuncio-
nes miembro de LibroCalificaciones.
El programador del código cliente sólo necesita conocer la interfaz de LibroCalificaciones para
usar la clase, y debe tener la capacidad de enlazar su código objeto. Como la interfaz de la clase es parte de
su definición en el encabezado LibroCalificaciones.h, el programador del código cliente debe tener
acceso a este archivo e incluirlo (mediante #include) en el archivo de código fuente del cliente. Cuando
se compila el código cliente, el compilador usa la definición de la clase en LibroCalificaciones.h para
asegurar que la función main cree y manipule objetos de la clase LibroCalificaciones correctamente.
Para crear la aplicación LibroCalificaciones ejecutable, el último paso es enlazar
1. el código objeto para la función main (es decir, el código cliente),
2. el código objeto para las implementaciones de las funciones miembro de la clase LibroCali-
ficaciones y
3. el código objeto de la Biblioteca estándar de C++ para las clases de C++ (como string) que
utilicen tanto el programador de la implementación de la clase, como el programador del có-
digo cliente.
La salida del enlazador es la aplicación LibroCalificaciones ejecutable, que los instructores pueden
utilizar para administrar las calificaciones de sus estudiantes. Por lo general los compiladores y los IDE
invocan al enlazador por el programador después de compilar su código.
Fig. 3.13  Demostración de la clase LibroCalificaciones después de separar la interfaz
de su implementación (parte 2 de 2).
92 Capítulo 3 Introducción a las clases, objetos y cadenas
Usuario de la aplicación
LibroCalificaciones
Programador de la implementación
de la clase
Programador del
código cliente
aplicación ejecutable
LibroCalificaciones
definición/interfaz de la clase
LibroCalificaciones.h
función main
(código fuente cliente)
código objeto de la clase
LibroCalificaciones
código objeto de la
función main
compilador
compilador
enlazador
Archivo de implementación de
LibroCalificaciones.cpp
código objeto de la Biblioteca
estándar de C++
Fig. 3.14  Proceso de compilación y enlace que produce una aplicación ejecutable.
Para obtener más información acerca de cómo compilar programas con varios archivos de có-
digo fuente, consulte la documentación de su compilador. En nuestro Centro de recursos de C++ en
www.deitel.com/cplusplus/ proporcionamos vínculos a varios compiladores de C++.
3.8Validación de datos mediante funciones establecer
En la sección 3.4 presentamos funciones establecer para permitir a los clientes de una clase modificar el
valor de un dato miembro private. En la figura 3.5, la clase LibroCalificaciones define la función
miembro establecerNombreCurso tan sólo para asignar un valor recibido en su parámetro nombre al
dato miembro nombreCurso. Esta función miembro no asegura que el nombre del curso se adhiera a
algún formato específico, o que siga cualquier otra regla en relación con la apariencia que debe tener un
nombre de curso “válido”. Suponga que una universidad puede imprimir certificados de los estudiantes
que contengan nombres de cursos con 25 caracteres o menos. Si la universidad utiliza un sistema que
3.8 Validación de datos mediante funciones establecer 93
contenga objetos LibroCalificaciones para generar los certificados, tal vez sea conveniente que la
clase LibroCalificaciones asegure que su miembro de datos nombreCurso nunca contenga más de 25
caracteres. El programa de las figuras 3.15 a 3.17 mejora la función miembro establecerNombreCurso
de la clase LibroCalificaciones para realizar esta validación (lo que también se conoce como verifi-
cación de validez).
Definición de la clase LibroCalificaciones
La definición de la clase LibroCalificaciones (figura 3.15) y por ende, su interfaz es idéntica a la
de la figura 3.11. Como la interfaz permanece sin cambios, los clientes de esta clase no necesitan modi-
ficarse a la hora que se modifica la definición de la función establecerNombreCurso. Esto permite a los
clientes aprovechar la clase LibroCalificaciones mejorada, con sólo enlazar el código cliente con el
código objeto actualizado de LibroCalificaciones.
1 // Fig. 3.15: LibroCalificaciones.h
2 // Definición de la clase LibroCalificaciones presenta la interfaz public
3 // de la clase. Las definiciones de las funciones miembro aparecen en
LibroCalificaciones.cpp.
4 #include string // el programa usa la clase string estándar de C++
5
6 // Definición de la clase LibroCalificaciones
7 class LibroCalificaciones
8 {
9 public:
10 explicit LibroCalificaciones( std::string ); // constructor que inicializa
nombreCurso
11 void establecerNombreCurso( std::string ); // establece el nombre del curso
12 std::string obtenerNombreCurso() const; // obtiene el nombre del curso
13 void mostrarMensaje() const; // muestra un mensaje de bienvenida
14 private:
15 std::string nombreCurso; // nombre del curso para este LibroCalificaciones
16 }; // fin de la clase LibroCalificaciones
Fig. 3.15  La definición de la clase LibroCalificaciones presenta la interfaz public de la clase.
Validar el nombre del curso con la función miembro establecerNombreCurso
de LibroCalificaciones
Las modificaciones a la clase LibroCalificaciones están en las definiciones del constructor (figura
3.16, líneas 9 a 12) y en establecerNombreCurso (líneas 16 a 29). En vez de usar un inicializador de
miembro, el constructor ahora llama a establecerNombreCurso. En general, todos los datos miembros
deberían inicializarse con inicializadores de miembros. Sin embargo, algunas veces un constructor debe
también validar su(s) argumento(s); a menudo esto se maneja en el cuerpo del constructor (línea 11).
La llamada a establecerNombreCurso valida el argumento del constructor y establece el dato miembro
nombreCurso. En un principio, el valor de nombreCurso se establecerá en la cadena vacía antes de que se
ejecute el cuerpo del constructor, y luego establecerNombreCurso modificará el valor de nombreCurso.
En establecerNombreCurso, la instrucción if en las líneas 18 y 19 determina si el parámetro
nombre contiene un nombre de curso válido (es decir, un string de 25 caracteres o menos). Si el nom-
bre del curso es válido, en la línea 19 se almacena el nombre del curso en el miembro de datos nombre-
Curso. Observe la expresión nombre.size() en la línea 18. Ésta es una llamada a la función miembro,
justo igual que miLibroCalificaciones.mostrarMensaje(). La clase string de la biblioteca estándar
94 Capítulo 3 Introducción a las clases, objetos y cadenas
de C++ define a una función miembro size que devuelve el número de caracteres en un objeto string.
El parámetro nombre es un objeto string, por lo que la llamada a nombre.size() devuelve el número
de caracteres en nombre. Si este valor es menor o igual que 25, nombre es válido y se ejecuta la línea 19.
1 // Fig. 3.16: LibroCalificaciones.cpp
2 // Implementaciones de las definiciones de las funciones miembro de
LibroCalificaciones.
3 // La función establecerNombreCurso realiza la validación.
4 #include iostream
5 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
6 using namespace std;
7
8 // el constructor inicializa nombreCurso con la cadena que se suministra como
argumento
9 LibroCalificaciones::LibroCalificaciones( string nombre )
10 {
11 establecerNombreCurso( nombre ); // valida y almacena nombreCurso
12 } // fin del constructor de LibroCalificaciones
13
14 // función que establece el nombre del curso;
15 // asegura que el nombre del curso tenga como máximo 25 caracteres
16 void LibroCalificaciones::establecerNombreCurso( string nombre )
17 {
18 if ( nombre.size() = 25 ) // si nombre tiene 25 caracteres o menos
19 nombreCurso = nombre; // almacena el nombre del curso en el objeto
20
21 if ( nombre.size()  25 ) // si nombre tiene más de 25 caracteres
22 {
23 // establece nombreCurso a los primeros 25 caracteres del parámetro nombre
24 nombreCurso = nombre.substr( 0, 25 ); // empieza en 0, longitud de 25
25
26 cerr  El nombre   nombre   excede la longitud maxima (25).n
27 Se limito nombreCurso a los primeros 25 caracteres.n  endl;
28 } // fin de if
29 } // fin de la función establecerNombreCurso
30
31 // función para obtener el nombre del curso
32 string LibroCalificaciones::obtenerNombreCurso() const
33 {
34 return nombreCurso; // devuelve el nombreCurso del objeto
35 } // fin de la función obtenerNombreCurso
36
37 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones
38 void LibroCalificaciones::mostrarMensaje() const
39 {
40 // llama a obtenerNombreCurso para obtener el nombreCurso
41 cout  Bienvenido al libro de calificaciones paran  obtenerNombreCurso()
42  !  endl;
43 } // fin de la función mostrarMensaje
Fig. 3.16  Definiciones de las funciones miembro para la clase LibroCalificaciones, con una función
establecer que valida la longitud del dato miembro nombreCurso.
La instrucción if en las líneas 21 a 28 se encarga del caso en el que establecerNombreCurso recibe
un nombre de curso inválido (es decir, un nombre que tenga más de 25 caracteres). Aun si el parámetro
nombre es demasiado largo, de todas formas queremos que el objeto LibroCalificaciones quede en
un estado consistente; es decir, un estado en el que el dato miembro nombreCurso del objeto contenga
3.8 Validación de datos mediante funciones establecer 95
un valor válido (un string de 25 caracteres o menos). Por ende, truncamos (reducimos) el nombre del
curso especificado y asignamos los primeros 25 caracteres de nombre al dato miembro nombreCurso (por
desgracia, esto podría truncar el nombre del curso de una manera extraña). La clase string estándar
proporciona la función miembro substr (“substring”, o subcadena), la cual devuelve un nuevo objeto
string que se crea al copiar parte de un objeto string existente. La llamada en la línea 24 (es decir,
nombre.substr(0,25)) pasa dos enteros (0 y 25) a la función miembro substr de nombre. Estos argu-
mentos indican la porción de la cadena nombre que substr debe devolver. El primer argumento especi-
fica la posición inicial en el objeto string original desde el que se van a copiar los caracteres; en todas las
cadenas se considera que el primer carácter se encuentra en la posición 0. El segundo argumento espe-
cifica el número de caracteres a copiar. Por lo tanto, la llamada en la línea 24 devuelve una subcadena de
25 caracteres de nombre, empezando en la posición 0 (es decir, los primeros 25 caracteres en nombre).
Por ejemplo, si nombre contiene el valor CS101 Introduccion a la programacion en C++, substr
devuelve CS101 Introduccion a la p. Después de la llamada a substr, en la línea 24 se asigna la
subcadena devuelta por substr al dato miembro nombreCurso. De esta forma, establecerNombre-
Curso asegura que a nombreCurso siempre se le asigne una cadena que contenga 25 caracteres o menos.
Si la función miembro tiene que truncar el nombre del curso para hacerlo válido, en las líneas 26 y 27
se muestra un mensaje de advertencia mediante el uso de cerr, como se menciona en el capítulo 1.
La instrucción if en las líneas 21 a 28 contiene dos instrucciones en su cuerpo: una para establecer
el nombreCurso a los primeros 25 caracteres del parámetro nombre y una para imprimir un mensaje
complementario al usuario. Ambas instrucciones deben ejecutarse cuando nombre sea demasiado largo,
por lo que las colocaremos en un par de llaves, { }. En el capítulo 2 vimos que esto crea un bloque. En
el capítulo 4 aprenderá más acerca de cómo colocar varias instrucciones en el cuerpo de una instrucción
de control.
La instrucción en las líneas 26 y 27 también podría aparecer sin un operador de inserción de flujo
al principio de la segunda línea de la instrucción, como en
cerr  El nombre   nombre   excede la longitud maxima (25).n
Se limito nombreCurso a los primeros 25 caracteres.n  endl;
El compilador de C++ combina las literales de cadena adyacentes, aun si aparecen en líneas separadas de un
programa. Por ende, en la instrucción anterior, el compilador de C++ combinaría las literales de cadena
 excede la longitud maxima (25).n y Se limito nombreCurso a los primeros 25
caracteres.n en una sola literal de cadena que produzca una salida idéntica a la de las líneas 26 y 27
de la figura 3.16. Este comportamiento nos permite imprimir cadenas extensas, al descomponerlas en
varias líneas en nuestro programa, sin necesidad de incluir operaciones de inserción de flujo adicionales.
Prueba de la clase LibroCalificaciones
En la figura 3.17 se demuestra la versión modificada de la clase LibroCalificaciones (figuras 3.15 a
3.16) que incluye la validación. En la línea 12 se crea un objeto LibroCalificaciones llamado libro-
Calificaciones1. Recuerde que el constructor de LibroCalificaciones llama a establecerNombre-
Curso para inicializar el dato miembro nombreCurso. En versiones anteriores de la clase, el beneficio de
llamar a establecerNombreCurso en el constructor no era evidente. Sin embargo, ahora el constructor
aprovecha la validación que proporciona establecerNombreCurso. El constructor simplemente llama a
establecerNombreCurso, en vez de duplicar su código de validación. Cuando en la línea 12 de la figura
3.17 se pasa un nombre inicial de CS101 Introduccion a la programacion en C++ al constructor
de LibroCalificaciones, el constructor pasa este valor a establecerNombreCurso, en donde ocurre
la inicialización en sí. Como este nombre contiene más de 25 caracteres, se ejecuta el cuerpo de la segun-
da instrucción if, lo cual hace que nombreCurso se inicialice con el nombre truncado del curso de 25
caracteres de CS101 Introduccion a la p [la parte truncada se resalta con rojo (en su pantalla) en la
línea 12]. La salida en la figura 3.17 contiene el mensaje de advertencia que producen las líneas 26 y 27
96 Capítulo 3 Introducción a las clases, objetos y cadenas
de la figura 3.16 en la función miembro establecerNombreCurso. En la línea 13 se crea otro objeto
LibroCalificaciones llamado libroCalificaciones2; el nombre válido del curso que se pasa al
constructor es exactamente de 25 caracteres.
1 // Fig. 3.17: fig03_17.cpp
2 // Crea y manipula un objeto LibroCalificaciones; ilustra la validación.
3 #include iostream
4 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
5 using namespace std;
6
7 // la función main empieza la ejecución del programa
8 int main()
9 {
10 // crea dos objetos LibroCalificaciones;
11 // el nombre inicial del curso de libroCalificaciones1 es demasiado largo
12 LibroCalificaciones libroCalificaciones1( CS101 Introduccion a la
programacion en C++ );
13 LibroCalificaciones libroCalificaciones2( CS102 C++:Estruc de datos );
14
15 // muestra el nombreCurso de cada LibroCalificaciones
16 cout  el nombre inicial del curso de libroCalificaciones1 es: 
17  libroCalificaciones1.obtenerNombreCurso()
18  nel nombre inicial del curso de libroCalificaciones2 es: 
19  libroCalificaciones2.obtenerNombreCurso()  endl;
20
21 // modifica el nombreCurso de libroCalificaciones1 (con una cadena con
longitud válida)
22 libroCalificaciones1.establecerNombreCurso( CS101 Programacion en C++
23
24 // muestra el nombreCurso de cada LibroCalificaciones
25 cout  nel nombre del curso de libroCalificaciones1 es: 
26  libroCalificaciones1.obtenerNombreCurso()
27  nel nombre del curso de libroCalificaciones2 es: 
28  libroCalificaciones2.obtenerNombreCurso()  endl;
29 } // fin de main
El nombre CS101 Introduccion a la programacion en C++ excede la longitud maxima (25).
Se limito nombreCurso a los primeros 25 caracteres.
el nombre inicial del curso de libroCalificaciones1 es: CS101 Introduccion a la p
el nombre inicial del curso de libroCalificaciones2 es: CS102 C++:Estruc de datos
el nombre del curso de libroCalificaciones1 es: CS101 Programacion en C++
el nombre del curso de libroCalificaciones2 es: CS102 C++:Estruc de datos
Fig. 3.17  Creación y manipulación de un objeto LibroCalificaciones en el que el nombre del curso
está limitado a una longitud de 25 caracteres.
En las líneas 16 a 19 de la figura 3.17 se muestra el nombre del curso truncado para libroCalifica-
ciones1 (se resalta esto en color azul en la salida de su pantalla) y el nombre del curso para libroCali-
ficaciones2. En la línea 22 se hace una llamada a la función miembro establecerNombreCurso de
libroCalificaciones1 directamente, para modificar el nombre del curso en el objeto LibroCalifi-
caciones a un nombre más corto, que no necesite truncarse. Después, en las líneas 25 a 28 se imprimen
los nombres de los cursos para los objetos LibroCalificaciones de nuevo.
3.9 Conclusión 97
Observaciones adicionales acerca de las funciones establecer
Una función establecer public tal como establecerNombreCurso debe escudriñar cuidadosamente
cualquier intento por modificar el valor de un dato miembro (por ejemplo, nombreCurso) para asegurar
que el nuevo valor sea apropiado para ese elemento de datos. Por ejemplo, un intento por establecer el
día del mes en 37 debe rechazarse, un intento por establecer el peso de una persona en cero o en un valor
negativo debe rechazarse, un intento por establecer una calificación en un examen en 185 (cuando el
rango apropiado es de cero a 100) debe rechazarse, y así en lo sucesivo.
Observación de Ingeniería de Software 3.3
Hacer los datos miembros private y controlar el acceso, en especial el acceso de escritura,
a esos datos miembros a través de funciones miembro public, ayuda a asegurar la integri-
dad de los datos.
Tip para prevenir errores 3.4
Los beneficios de la integridad de datos no son automáticos sólo porque los datos miembros
se hacen private; debemos proporcionar una verificación de validez apropiada y reportar
los errores.
Una función establecer podría devolver un valor para indicar que se realizó un intento por asignar
datosinválidosalobjetodeunaclase.Unclientepodríaentoncesprobarelvalorderetornodelafunción
establecer para determinar si el intento del cliente por modificar el objeto fue exitoso, y para realizar la
acción apropiada en caso contrario. En capítulos posteriores haremos eso, después de que introduzca-
mos un poco más de tecnología de programación. En C++, se puede notificar a los clientes de objetos
sobre los problemas a través del mecanismo de manejo de excepciones, que veremos ligeramente en el ca-
pítulo 7 y presentaremos de manera detallada en el capítulo 17 (en el sitio web).
3.9Conclusión
En este capítulo aprendió a crear clases definidas por el usuario, y a crear y utilizar objetos de esas clases.
Declaramos datos miembros de una clase para mantener los datos para cada objeto de la misma. Tam-
bién definimos funciones miembro que operan con esos datos. Aprendió que las funciones miembro
que no modifican los datos de una clase deben declararse como const. Le mostramos cómo llamar a las
funciones miembro de un objeto para solicitar los servicios que éste proporciona, y cómo pasar datos a
esas funciones miembro como argumentos. Hablamos sobre la diferencia entre una variable local de una
función miembro y un dato miembro de una clase.También le mostramos cómo usar un constructor y
una lista inicializadora de miembros para asegurar que todos los objetos se inicialicen correctamente.
Aprendió que un constructor con un solo parámetro debe declararse como explicit, y que un cons-
tructor no puede declararse como const debido a que modifica el objeto que se va a inicializar. Le
demostramos cómo separar la interfaz de una clase de su implementación, para fomentar la buena in-
geniería de software. Aprendió que nunca se deben colocar las directivas using y las declaraciones using
enlosencabezados.Presentamosundiagramaquemuestralosarchivosquenecesitanlosprogramadores
de la implementación de la clase y los programadores del código cliente para compilar el código que
escriben. Demostramos cómo se pueden utilizar las funciones establecer para validar los datos de un
objeto y asegurar que los objetos se mantengan en un estado consistente. Además, se utilizaron diagra-
mas de clases de UML para modelar las clases y sus constructores, las funciones miembro y los datos
miembros. En el siguiente capítulo empezaremos nuestra introducción a las instrucciones de control,
las cuales especifican el orden en el que se realizan las acciones de una función.
98 Capítulo 3 Introducción a las clases, objetos y cadenas
Resumen
Sección 3.2 Definición de una clase con una función miembro
• La definición de una clase (pág. 68) contiene los datos miembro y las funciones miembro que definen los atri-
butos y comportamientos de esa clase, respectivamente.
• La definición de una clase empieza con la palabra clave class, seguida inmediatamente por el nombre de la
clase.
• Por convención, el nombre de una clase definida por el usuario (pág. 69) empieza con una letra mayúscula,
y por legibilidad, cada palabra subsiguiente en el nombre de la clase empieza con una letra mayúscula.
• El cuerpo de cada clase (pág. 68) va encerrado entre un par de llaves izquierda y derecha ({ y }), y termina con
un punto y coma.
• Las funciones miembro que aparecen después del especificador de acceso public (pág. 68) pueden ser llamadas
por otras funciones en un programa, y por las funciones miembro de otras clases.
• Los especificadores de acceso siempre van seguidos de un punto y coma (;).
• La palabra clave void (pág. 69) es un tipo de valor de retorno especial, el cual indica que una función realizará
una tarea, pero no devolverá datos a la función que la llamó cuando complete su tarea.
• Por convención, los nombres de las funciones (pág. 69) empiezan con la primera letra en minúscula, y todas las
palabras subsiguientes en el nombre empiezan con letra mayúscula.
• Un conjunto vacío de paréntesis después del nombre de una función indica que ésta no requiere datos adicio-
nales para realizar su tarea.
• Una función que no modifica, y no debe hacerlo, el objeto en el cual se llama debe declararse como const.
• Por lo general, no se puede llamar a una función miembro sino hasta que se crea un objeto de su clase.
• Cada nueva clase que creamos se convierte en un nuevo tipo en C++.
• En UML, cada clase se modela en un diagrama de clases (pág. 70) como un rectángulo con tres compartimien-
tos, que (de arriba hacia abajo) contienen el nombre de la clase, los atributos y las operaciones, respectivamente.
• UML modela las operaciones como el nombre de la operación, seguido de paréntesis. Un signo más (+) enfren-
te del nombre de la operación indica que ésta es una operación public (es decir, una función miembro public
en C++).
Sección 3.3 Definición de una función miembro con un parámetro
• Una función miembro puede requerir uno o más parámetros (pág. 70) para representar los datos adicionales
que necesita para realizar su tarea. La llamada a una función suministra un argumento (pág. 71) para cada uno
de los parámetros de esa función.
• Para llamar a una función miembro, se coloca después del nombre del objeto un operador punto (.), el nombre
de la función y un conjunto de paréntesis que contienen los argumentos de la misma.
• Una variable de la clase string (pág. 72) de la biblioteca estándar de C++ representa a una cadena de caracteres.
Esta clase se define en el encabezado string, y el nombre string pertenece al espacio de nombres std.
• La función getline (del encabezado string, pág. 72) lee caracteres de su primer argumento hasta encontrar
un carácter de nueva línea, y después coloca los caracteres (sin incluir la nueva línea) en la variable string que
se especifica como su segundo argumento. El carácter de nueva línea se descarta.
• Una lista de parámetros (pág. 73) puede contener cualquier número de parámetros, incluyendo ninguno (lo
cual se representa por paréntesis vacíos) para indicar que una función no requiere parámetros.
• El número de argumentos en la llamada a una función debe coincidir con el número de parámetros en la lista
de parámetros del encabezado de la función miembro a la que se llamó. Además, los tipos de los argumentos en
la llamada a la función deben ser consistentes con los tipos de los parámetros correspondientes en el encabezado
de la función.
• Para modelar un parámetro de una operación, UML lista el nombre del parámetro, seguido de dos puntos y del
tipo del parámetro entre los paréntesis que van después del nombre de la operación.
• UML tiene sus propios tipos de datos. No todos los tipos de datos de UML tienen los mismos nombres que los
tipos de C++ correspondientes. El tipo String de UML corresponde al tipo string de C++.
Resumen 99
Sección 3.4 Datos miembros, funciones establecer y obtener
• Las variables que se declaran en el cuerpo de una función son variables locales (pág. 74) y sólo pueden utilizarse
desde el punto en el que se declararon en la función, hasta la llave de cierre derecha (}) del bloque en el que se
declararon.
• Una variable local debe declararse antes de poder utilizarse en una función. Una variable local no puede utili-
zarse fuera de la función en la que está declarada.
• Por lo general, los datos miembros (pág. 74) son private (pág. 76). Las variables o funciones que se declaran
private son accesibles sólo para las funciones miembro de la clase en la que están declaradas, o para las funcio-
nes amigas de la clase.
• Cuando un programa crea (instancia) un objeto de una clase, sus datos miembros private se encapsulan (ocul-
tan, pág. 76) en el objeto y sólo las funciones miembro de la clase del objeto pueden utilizarlos (o las “amigas”
de la clase, como veremos en el capítulo 9).
• Cuando se hace una llamada a una función que especifica un tipo de valor de retorno distinto de void y ésta
completa su tarea, la función devuelve un resultado a la función que la llamó.
• De manera predeterminada, el valor inicial de un objeto string es la cadena vacía (pág. 77); es decir, una cade-
na que no contenga caracteres. No aparece nada en la pantalla cuando se muestra una cadena vacía.
• A menudo, una clase proporciona funciones miembro public para permitir que los clientes de la clase establez-
can u obtengan (pág. 78) datos miembros private. Los nombres de esas funciones miembro comúnmente
empiezan con establecer u obtener (set o get, en inglés).
• Las funciones establecer y obtener permiten a los clientes de una clase utilizar de manera indirecta los datos
ocultos. El cliente no sabe cómo realiza el objeto estas operaciones.
• Las funciones establecer y obtener de una clase deben ser utilizadas por otras funciones miembro de la clase para
manipular los datos private de esa clase. Si la representación de los datos de la clase cambia, las funciones
miembro que acceden a los datos sólo a través de las funciones establecer y obtener no requerirán modificación.
• Una función establecer pública debe escudriñar cuidadosamente cualquier intento por modificar el valor de un
dato miembro, para asegurar que el nuevo valor sea apropiado para ese elemento de datos.
• UML representa a los datos miembros como atributos, para lo cual enlista el nombre del atributo, seguido de
dos puntos y del tipo del atributo. En UML, se coloca un signo menos (-) antes de los atributos privados.
• Para indicar el tipo de valor de retorno de una operación en UML, se coloca un signo de dos puntos y el tipo de
valor de retorno después de los paréntesis que siguen del nombre de la operación.
• Los diagramas de clases de UML no especifican tipos de valores de retorno para las operaciones que no devuel-
ven valores.
Sección 3.5 Inicialización de objetos mediante constructores
• Cada clase debe proporcionar uno o más constructores (pág, 79) para inicializar un objeto de la clase cuando el
objeto se crea. Un constructor se debe definir con el mismo nombre que la clase.
• Una diferencia entre los constructores y las funciones es que los constructores no pueden devolver valores, por
lo que no pueden especificar un tipo de valor de retorno (ni siquiera void). Por lo general, los constructores se
declaran como public.
• C++ llama automáticamente a un constructor por cada objeto que se crea, lo cual ayuda a asegurar que todo
objeto se inicialice antes de usarlo en un programa.
• Un constructor sin parámetros es un constructor predeterminado (pág. 80). Si el programador no proporciona
un constructor, el compilador proporciona un constructor predeterminado.También podemos definir un cons-
tructor predeterminado de manera explícita. Si define un constructor para una clase, C++ no creará un construc-
tor predeterminado.
• Un constructor con un solo parámetro debe declararse como explicit.
• Un constructor usa una lista inicializadora de miembros para inicializar los datos miembros de una clase. Los
inicializadores de miembros aparecen entre la lista de parámetros y la llave izquierda que comienza el cuerpo del
constructor. La lista inicializadora de miembros se separa de la lista de parámetros con un signo de dos puntos (:).
Un inicializador de miembro consiste en el nombre de variable de un miembro de datos seguido de paréntesis
que contienen el valor inicial del miembro. Podemos realizar la inicialización en el cuerpo del constructor, pero
100 Capítulo 3 Introducción a las clases, objetos y cadenas
más adelante en el libro veremos que es más eficiente hacerlo con inicializadores de miembros; además algunos
tipos de datos miembros deben inicializarse de esta forma.
• UML modela a los constructores como operaciones en el tercer compartimiento de un diagrama de clases, con
la palabra “constructor” entre los signos « y » antes del nombre del constructor.
Sección 3.6 Colocar una clase en un archivo separado para fines de reutilización
• Cuando las definiciones de clases se empaquetan en forma apropiada, los programadores de todo el mundo
pueden reutilizarlas.
• Es costumbre definir una clase en un encabezado (pág. 83) que tenga una extensión de nombre de archivo .h.
Sección 3.7 Separar la interfaz de la implementación
• Si cambia la implementación de la clase, los clientes de ésta no tienen que cambiar.
• Las interfaces definen y estandarizan las formas en que deben interactuar las cosas como las personas y los siste-
mas.
• La interfaz public de una clase (pág. 87) describe las funciones miembro public que están disponibles para
los clientes de la clase. La interfaz describe qué servicios (pág. 87) pueden usar los clientes y cómo reutilizar esos
servicios, pero no especifica cómo es que la clase lleva a cabo los servicios.
• Al separar la interfaz de la implementación (pág. 87) se facilita la modificación de los programas. Los cambios en
la implementación de la clase no afectan al cliente, mientras que la interfaz de la clase permanezca sin cambios.
• Nunca se deben colocar directivas using ni declaraciones using en los encabezados.
• El prototipo de una función (pág. 87) contiene el nombre de una función, su tipo de valor de retorno y el nú-
mero, tipos y orden de los parámetros que la función espera recibir.
• Una vez que se define una clase y se declaran sus funciones miembro (a través de los prototipos de función), las
funciones miembro deben definirse en un archivo de código fuente separado.
• Para cada función miembro definida fuera de su correspondiente definición de clase, hay que anteponer al
nombre de la función el nombre de la clase y el operador de resolución de ámbito (::, pág. 89).
Sección 3.8 Validación de datos mediante funciones establecer
• La función miembro size de la clase string (pág. 93) devuelve el número de caracteres en un objeto string.
• La función miembro substr (pág. 95) de la clase string devuelve un nuevo objeto string que contiene una
copia de parte de un objeto string existente. El primer argumento especifica la posición inicial en el objeto
string original. El segundo argumento especifica el número de caracteres a copiar.
Ejercicios de autoevaluación
3.1 Complete las siguientes oraciones:
a) Cada declaración de clase contiene la palabra clave __________, seguida inmediatamente por el
nombre de la clase.
b) Por lo general, la definición de una clase se almacena en un archivo con la extensión de archivo
__________.
c) Cada parámetro en un encabezado de función debe especificar un(a) ___________________ y
un(a) __________________.
d) Cuando cada objeto de una clase mantiene su propia versión de un atributo, la variable que represen-
ta a este atributo se conoce también como __________.
e) La palabra clave public es un(a) __________.
f) El tipo de valor de retorno __________ indica que una función realizará una tarea, pero no devolverá
información cuando complete su tarea.
g) La función __________ de la biblioteca string lee caracteres hasta encontrar una nueva línea, y
después copia esos caracteres en el objeto string especificado.
h) Cuandosedefineunafunciónmiembrofueradeladefinicióndeunaclase,elencabezadodelafunción
debe incluir el nombre de la clase y el ________, seguido del nombre de la función para “enlazar” la
función miembro con la definición de la clase.
Ejercicios 101
i) El archivo de código fuente, y cualquier otro archivo que utilice una clase, pueden incluir el archivo
de encabezado de la clase mediante una directiva del preprocesador __________.
3.2 Conteste con verdadero o falso a cada una de las siguientes proposiciones; en caso de ser falso, explique
por qué.
a) Por convención, los nombres de las funciones empiezan con la primera letra en mayúscula y todas las
palabras subsiguientes en el nombre empiezan con la primera letra en mayúscula.
b) Los paréntesis vacíos que van después del nombre de una función en un prototipo de función indican
que ésta no requiere parámetros para realizar su tarea.
c) Los datos miembros o las funciones miembro que se declaran con el modificador de acceso private
son accesibles para las funciones miembro de la clase en la que se declaran.
d) Las variables que se declaran en el cuerpo de una función miembro específica se conocen como datos
miembros, y pueden utilizarse en todas las funciones miembro de la clase.
e) El cuerpo de toda función está delimitado por las llaves izquierda y derecha ({ y }).
f) Cualquier archivo de código fuente que contenga int main() puede usarse para ejecutar un programa.
g) Los tipos de los argumentos en la llamada a una función deben ser consistentes con los tipos de los
parámetros correspondientes en la lista de parámetros del prototipo de la función.
3.3 ¿Cuál es la diferencia entre una variable local y un dato miembro?
3.4 Explique el propósito de un parámetro de una función. ¿Cuál es la diferencia entre un parámetro y un
argumento?
Respuestas a los ejercicios de autoevaluación
3.1 a) class. b) .h. c) tipo, nombre. d) miembro de datos. e) especificador de acceso. f) void.
g) getline. h) operador de resolución de ámbito (::). i) #include.
3.2 a) Falso. Los nombres de las funciones empiezan con una primera letra en minúscula y todas las palabras
subsiguientes en el nombre empiezan con una letra en mayúscula. b) Verdadero. c) Verdadero. d) Falso.
Dichas variables son variables locales y sólo pueden usarse en la función miembro en la que están declaradas.
e) Verdadero. f) Verdadero. g) Verdadero.
3.3 Una variable local se declara en el cuerpo de una función, y sólo puede utilizarse desde el punto en el que
se declaró, hasta la llave de cierre del bloque correspondiente. Un miembro de datos se declara en una clase, pero
no en el cuerpo de alguna de las funciones miembro de la clase. Cada objeto de una clase tiene una copia separada
de los datos miembros de la clase. Los datos miembros están accesibles para todas las funciones miembro de la
clase.
3.4 Un parámetro representa la información adicional que requiere una función para realizar su tarea. Cada
parámetro requerido por una función está especificado en el encabezado de la función. Un argumento es el valor
que se suministra en la llamada a la función. Cuando se llama a la función, el valor del argumento se pasa al pará-
metro de la función, para que ésta pueda realizar su tarea.
Ejercicios
3.5 (Prototipos y definiciones de funciones) Explique la diferencia entre un prototipo de función y la definición
de una función.
3.6 (Constructor predeterminado) ¿Qué es un constructor predeterminado? ¿Cómo se inicializan los datos
miembros de un objeto, si una clase sólo tiene un constructor predeterminado definido en forma implícita?
3.7 (Datos miembros) Explique el propósito de un dato miembro.
3.8 (Encabezado y archivos de código fuente) ¿Qué es un encabezado? ¿Qué es un archivo de código fuente?
Hable sobre el propósito de cada uno.
3.9 (Uso de una clase sin una directiva using) Explique cómo puede usar un programa una clase string sin
insertar una directiva using.
102 Capítulo 3 Introducción a las clases, objetos y cadenas
3.10 (Funciones establecer y obtener) Explique por qué una clase podría proporcionar una función establecer
y una función obtener para un dato miembro.
3.11 (Modificación de la clase LibroCalificaciones) Modifique la clase LibroCalificaciones (figuras 3.11 a
3.12) de la siguiente manera:
a) Incluya un segundo miembro de datos string, que represente el nombre del instructor del curso.
b) Proporcione una función establecer para modificar el nombre del instructor, y una función obtener para
obtenerlo.
c) Modifique el constructor para especificar dos parámetros: uno para el nombre del curso y otro para el
nombre del instructor.
d) Modifique la función mostrarMensaje, de tal forma que primero imprima el mensaje de bienvenida y
el nombre del curso, y que después imprima Este curso es presentado por: , seguido del nombre
del instructor.
Use su clase modificada en un programa de prueba que demuestre las nuevas capacidades de la clase.
3.12 (Clase Cuenta) Cree una clase llamada Cuenta que podría ser utilizada por un banco para representar las
cuentas bancarias de sus clientes. Incluya un miembro de datos de tipo int para representar el saldo de la cuenta.
[Nota: en los siguientes capítulos, utilizaremos números que contienen puntos decimales (por ejemplo, 2.75), a los
cuales se les conoce como valores de punto flotante, para representar montos en dólares]. Proporcione un cons-
tructor que reciba un saldo inicial y lo utilice para inicializar el dato miembro. El constructor debe validar el saldo
inicial para asegurar que sea mayor o igual que 0. De no ser así, establezca el saldo en 0 y muestre un mensaje de
error,indicandoqueelsaldoinicialerainválido.Proporcionetresfuncionesmiembro.Lafunciónmiembroabonar
debe agregar un monto al saldo actual. La función miembro cargar deberá retirar dinero del objeto Cuenta y
asegurarse que el monto a cargar no exceda el saldo de Cuenta. Si lo hace, el saldo debe permanecer sin cambio y la
función debe imprimir un mensaje que indique El monto a cargar excede el saldo de la cuenta. La función
miembro obtenerSaldo debe devolver el saldo actual. Cree un programa que cree dos objetos Cuenta y evalúe las
funciones miembro de la clase Cuenta.
3.13 (Clase Factura) Cree una clase llamada Factura, que una ferretería podría utilizar para representar una
factura por un artículo vendido en la tienda. Una Factura debe incluir cuatro datos miembros: un número de
pieza(tipostring),ladescripcióndelapieza(tipostring),lacantidaddeartículosdeesetipoquesevanacomprar
(tipo int)yelprecioporartículo(tipo int).[Nota: enlossiguientescapítulos,utilizaremosnúmerosquecontienen
puntos decimales (por ejemplo, 2.75), a los cuales se les conoce como valores de punto flotante, para representar
montos en dólares]. Su clase debe tener un constructor que inicialice los cuatro datos miembros. Un constructor
que recibe múltiples argumentos se define mediante la siguiente forma:
nombreClase( nombreTipo1 nombreParámetro1, nombreTipo2 nombreParámetro2, …)
Proporcione una función establecer y una función obtener para cada miembro de datos. Además, proporcione una
función miembro llamada obtenerMontoFactura, que calcule el monto de la factura (es decir, que multiplique la
cantidad por el precio por artículo) y después devuelva ese monto como un valor int. Si la cantidad no es positiva,
debe establecerse en 0. Si el precio por artículo no es positivo, debe establecerse en 0. Escriba un programa de
prueba que demuestre las capacidades de la clase Factura.
3.14 (Clase Empleado) Cree una clase llamada Empleado, que incluya tres piezas de información como datos
miembros: un primer nombre (tipo string), un apellido paterno (tipo string) y un salario mensual (tipo int).
[Nota: en los siguientes capítulos, utilizaremos números que contienen puntos decimales (por ejemplo, 2.75), a
los cuales se les conoce como valores de punto flotante, para representar montos en dólares]. Su clase debe tener
un constructor que inicialice los tres datos miembros. Proporcione una función establecer y una función obtener
para cada dato miembro. Si el salario mensual no es positivo, establézcalo en 0. Escriba un programa de prueba
que demuestre las capacidades de la clase Empleado. Cree dos objetos Empleado y muestre el salario anual de cada
objeto. Después, proporcione a cada Empleado un aumento del 10% y muestre el salario anual de cada Empleado
otra vez.
3.15 (Clase Fecha) CreeunaclasellamadaFecha,queincluyatrespiezasdeinformacióncomodatosmiembros:
un mes (tipo int), un día (tipo int) y un año (tipo int). Su clase debe tener un constructor con tres parámetros,
los cuales debe utilizar para inicializar los tres datos miembros. Para los fines de este ejercicio, suponga que los va-
lores que se proporcionan para el año y el día son correctos, pero asegúrese que el valor del mes se encuentre en el
rango de 1 a 12; de no ser así, establezca el mes en 1. Proporcione una función establecer y una función obtener para
Hacer la diferencia 103
cada miembro de datos. Proporcione una función miembro mostrarFecha, que muestre el mes, día y año, separa-
dos por barras diagonales (/). Escriba un programa de prueba que demuestre las capacidades de la clase Fecha.
Hacer la diferencia
3.16 (Calculadora de la frecuencia cardiaca esperada) Mientras se ejercita, puede usar un monitor de frecuen-
cia cardiaca para ver que su corazón permanezca dentro de un rango seguro sugerido por sus entrenadores y doc-
tores. De acuerdo con la Asociación Estadounidense del Corazón (AHA) (www.americanheart.org/presenter.
jhtml?identifier=4736), la fórmula para calcular su frecuencia cardiaca máxima en pulsos por minuto es 220
menos su edad en años. Su frecuencia cardiaca esperada es un rango que está entre el 50 y el 85% de su frecuencia
cardiaca máxima. [Nota: estas fórmulas son estimaciones proporcionadas por la AHA. Las frecuencias cardiacas máxima
y esperada pueden variar con base en la salud, condición física y sexo del individuo. Siempre debe consultar un médico o
a un profesional de la salud antes de empezar o modificar un programa de ejercicios]. Cree una clase llamada Frecuen-
ciasCardiacas. Los atributos de la clase deben incluir el primer nombre de la persona, su apellido y fecha de naci-
miento (la cual debe consistir de atributos separados para el mes, día y año de nacimiento). Su clase debe tener un
constructor que reciba estos datos como parámetros. Para cada atributo debe proveer funciones establecer y obtener.
La clase también debe incluir una función obtenerEdad que calcule y devuelva la edad de la persona (en años), una
función obtenerFrecuenciaCardiacaMaxima que calcule y devuelva la frecuencia cardiaca máxima de esa persona,
y una función obtenerFrecuenciaCardiacaEsperada que calcule y devuelva la frecuencia cardiaca esperada de la
persona. Puesto que todavía no sabe cómo obtener la fecha actual de la computadora, la función obtenerEdad debe
pedir al usuario que introduzca el mes, día y año actual antes de calcular la edad de la persona. Escriba una aplica-
ción que pida la información de la persona, cree una instancia de un objeto de la clase FrecuenciasCardiacas e
imprima la información a partir de ese objeto (incluyendo el primer nombre de la persona, su apellido y fecha de
nacimiento), y que después calcule e imprima la edad de la persona en (años), frecuencia cardiaca máxima y rango
de frecuencia cardiaca esperada.
3.17 (Computarización de los registros médicos) Un problema relacionado con la salud que ha estado última-
mente en las noticias es la computarización de los registros médicos. Esta posibilidad se está tratando con mucho
cuidado,debidoalasdelicadascuestionesdeprivacidadyseguridad,entreotrascosas.[Trataremosesascuestiones
en ejercicios posteriores]. La computarización de los registros médicos puede facilitar a los pacientes el proceso
de compartir sus perfiles e historiales médicos con los diversos profesionales de la salud que consulten. Esto podría
mejorar la calidad del servicio médico, ayudar a evitar conflictos de fármacos y prescripciones erróneas, reducir
los costos y, en emergencias, podría ayudar a salvar vidas. En este ejercicio usted diseñará una clase “inicial” lla-
mada PerfilMedico para una persona. Los atributos de la clase deben incluir el primer nombre de la persona, su
apellido, sexo, fecha de nacimiento (que debe consistir de atributos separados para el día, mes y año de nacimien-
to), altura (en centímetros) y peso (en kilogramos). Su clase debe tener un constructor que reciba estos datos. Para
cada atributo, debe proveer las funciones establecer y obtener. La clase también debe incluir métodos que calculen
y devuelvan la edad del usuario en años, la frecuencia cardiaca máxima y el rango de frecuencia cardiaca esperada
(vea el ejercicio 3.16), además del índice de masa corporal (BMI; vea el ejercicio 2.30). Escriba una aplicación
que pida la información de la persona, cree una instancia de un objeto de la clase PerfilMedico para esa persona
e imprima la información de ese objeto (incluyendo el primer nombre de la persona, apellido, sexo, fecha de
nacimiento, altura y peso), y que después calcule e imprima la edad de esa persona en años, junto con el BMI, la
frecuencia cardiaca máxima y el rango de frecuencia cardiaca esperada.También debe mostrar la tabla de “valores
del BMI” del ejercicio 2.30. Use la misma técnica que en el ejercicio 3.16 para calcular la edad de la persona.
Instrucciones de control,
parte I: operadores
de asignación, ++ y ––
4
Desplacémonos un lugar.
—Lewis Carroll
La rueda se convirtió en un
círculo completo.
—William Shakespeare
Toda la evolución que conocemos
procede de lo vago a lo definido.
—Charles Sanders Peirce
O b j e t i v o s
En este capítulo aprenderá a:
n Comprender las técnicas
básicas para solucionar
problemas.
n Desarrollar algoritmos
mediante el proceso
de mejoramiento de arriba
a abajo, paso a paso.
n Utilizar las estructuras de
selección if e if...else
para elegir entre distintas
acciones alternativas.
n Utilizar la estructura de
repetición while para
ejecutar instrucciones de
manera repetitiva dentro
de un programa.
n Comprender la repetición
controlada por un contador
y la repetición controlada
por un centinela.
n Utilizar los operadores de
incremento, decremento
y asignación.
4.2 Algoritmos 105
4.1Introducción
Antes de escribir un programa que dé solución a un problema, es necesario tener una comprensión de-
tallada de todo el problema, además de una metodología cuidadosamente planeada para resolverlo. Al
escribir un programa, también debemos comprender los tipos de bloques de construcción disponibles,
y emplear las técnicas comprobadas para construirlos. En éste y en el capítulo 5, “Instrucciones de con-
trol, parte 2: operadores lógicos”, hablaremos sobre estas cuestiones cuando presentemos la teoría y los
principios de la programación estructurada. Los conceptos aquí presentados son imprescindibles para
crear clases efectivas y manipular objetos.
En este capítulo presentamos las instrucciones if, if...else y while de C++, tres de los bloques de
construcción que permiten a los programadores especificar la lógica requerida para que las funciones
miembro realicen sus tareas. Dedicamos una parte de este capítulo (y de los capítulos 5 a 7) para desa-
rrollar más la clase LibroCalificaciones. En especial, agregamos a dicha clase una función miembro
que utiliza instrucciones de control para calcular el promedio de un conjunto de calificaciones de estu-
diantes. Otro ejemplo demuestra formas adicionales de combinar instrucciones de control. Presenta-
mos los operadores de asignación, incremento y decremento de C++. Estos operadores adicionales
abrevian y simplifican muchas instrucciones de los programas.
4.2Algoritmos
Cualquier problema de computación puede resolverse ejecutando una serie de acciones en un orden
específico. Un procedimiento para resolver un problema en términos de
1. las acciones a ejecutar y
2. el orden en el que se ejecutan estas acciones
se conoce como un algoritmo. El siguiente ejemplo demuestra que es importante especificar de mane-
ra correcta el orden en el que se ejecutan las acciones.
Considere el “algoritmo para levantarse y arreglarse” que sigue un ejecutivo para levantarse de la
cama e ir a trabajar: (1) levantarse, (2) quitarse la pijama, (3) bañarse, (4) vestirse, (5) desayunar,
(6) transportarse al trabajo. Esta rutina logra que el ejecutivo llegue al trabajo bien preparado para tomar
decisiones críticas. Suponga que los mismos pasos se realizan en un orden ligeramente distinto: (1) le-
vantarse; (2) quitarse la pijama; (3) vestirse; (4) bañarse; (5) desayunar; (6) transportarse al trabajo. En
este caso, nuestro ejecutivo llegará al trabajo todo mojado. Al proceso de especificar el orden en el que
4.1 Introducción
4.2 Algoritmos
4.3 Seudocódigo
4.4 Estructuras de control
4.5 Instrucción de selección if
4.6 Instrucción de selección doble
if...else
4.7 Instrucción de repetición while
4.8 Cómo formular algoritmos:
repetición controlada por un contador
4.9 Cómo formular algoritmos:
repetición controlada por un centinela
4.10 Cómo formular algoritmos:
instrucciones de control anidadas
4.11 Operadores de asignación
4.12 Operadores de incremento y decremento
4.13 Conclusión
Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios
| Hacer la diferencia
106 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
se ejecutan las instrucciones (acciones) en un programa de computadora, se le llama control del pro-
grama. En este capítulo investigaremos el control de los programas mediante el uso de las instrucciones
de control de C++.
4.3Seudocódigo
El seudocódigo (o “imitación” de código) es un lenguaje artificial e informal que ayuda a los programa-
dores a desarrollar algoritmos sin tener que preocuparse por los detalles de la sintaxis del lenguaje C++.
El seudocódigo que presentaremos aquí es útil para desarrollar algoritmos que se convertirán en progra-
mas estructurados de C++. El seudocódigo es similar al lenguaje cotidiano; es conveniente y amigable
con el usuario, aunque no es realmente un lenguaje de programación de computadoras.
El seudocódigo no se ejecuta en las computadoras. En vez de ello, ayuda al programador a “organi-
zar” un programa antes de intentar escribirlo en un lenguaje de programación como C++.
El estilo de seudocódigo que presentaremos consiste solamente en caracteres, de manera que los
programadores puedan escribir el seudocódigo convenientemente, utilizando cualquier programa edi-
tor de texto. Un programa en seudocódigo preparado de manera cuidadosa puede convertirse fácilmen-
te en su correspondiente programa en C++. En muchos casos, esto requiere tan sólo reemplazar las
instrucciones en seudocódigo con sus instrucciones equivalentes en C++.
Por lo general, el seudocódigo describe sólo las instrucciones ejecutables, las cuales provocan que
ocurran acciones específicas después de que un programador convierte un programa de seudocódigo a
C++, y el programa se compila y ejecuta en una computadora. Las declaraciones (que no tienen inicia-
lizadores, o que no implican llamadas a un constructor) no son instrucciones ejecutables. Por ejemplo,
la declaración
int contador;
indica al compilador el tipo de la variable contador y lo instruye para que reserve espacio en memoria
para esa variable. Esta declaración no hace que ocurra ninguna acción (como una operación de entrada,
salida o un cálculo) cuando el programa se ejecuta. Por lo general no incluimos las declaraciones de va-
riables en nuestro seudocódigo. Algunos programadores optan por listar las variables y mencionar sus
propósitos al principio de sus programas en seudocódigo.
Veamos un ejemplo de seudocódigo que se puede escribir para ayudar a un programador a crear el
programa de suma de la figura 2.5. Este seudocódigo (figura 4.1) corresponde al algoritmo que recibe
como entrada dos enteros del usuario, los suma y muestra el resultado en pantalla. Aunque mostramos
aquí el listado completo en seudocódigo, más adelante le mostraremos cómo crear seudocódigo a partir
del enunciado de un problema.
Las líneas 1 y 2 corresponden a las instrucciones 13 y 14 de la figura 2.5 Observe que las instruccio-
nes en seudocódigo son simplemente instrucciones en lenguaje cotidiano que representan la tarea que
se debe realizar en C++. De igual forma, las líneas 4 y 5 corresponden a las instrucciones en las líneas 16
y 17, y las líneas 7 y 8 corresponden a las instrucciones en las líneas 19 y 21 de la figura 2.5.
1 Pedir al usuario que introduzca el primer entero
2 Recibir como entrada el primer entero
3
4 Pedir al usuario que introduzca el segundo entero
5 Recibir como entrada el segundo entero
6
7 Sumar el primer entero con el segundo entero, almacenar el resultado
8 Mostrar el resultado en pantalla
Fig. 4.1  Seudocódigo para el programa de suma de la figura 2.5.
4.4 Estructuras de control 107
4.4Estructuras de control
Por lo general, en un programa las instrucciones se ejecutan una después de otra, en el orden en que están
escritas. Este proceso se conoce como ejecución secuencial. Varias instrucciones en C++ que pronto
veremos, permiten al programador especificar que la siguiente instrucción a ejecutarse tal vez no sea la si-
guiente en la secuencia. Esto se conoce como transferencia de control.
Durante la década de 1960, se hizo evidente que el uso indiscriminado de las transferencias de
control era el origen de muchas de las dificultades que experimentaban los grupos de desarrollo de soft-
ware. A quien se señaló como culpable fue a la instrucción goto, la cual permite al programador espe-
cificar la transferencia de control a uno de los muchos posibles destinos dentro de un programa (crean-
do lo que se conoce comúnmente como “código espagueti”). La noción de la llamada programación
estructurada se hizo casi un sinónimo de la “eliminación del goto”.
Las investigaciones de Böhm y Jacopini1
demostraron que los programas podían escribirse sin
instrucciones goto. El reto de la época para los programadores fue cambiar sus estilos a una “progra-
mación sin goto”. No fue sino hasta la década de 1970 cuando los programadores tomaron en serio la
programación estructurada. Los resultados han sido impresionantes, ya que los grupos de desarrollo
de software han reportado reducciones en los tiempos de desarrollo, mayor incidencia de entregas de
sistemas a tiempo y más proyectos de software finalizados sin salirse del presupuesto. La clave para estos
logros es que los programas estructurados son más claros, más fáciles de depurar, probar y modificar, y
hay más probabilidad de que estén libres de errores desde el principio.
El trabajo de Böhm y Jacopini demostró que todos los programas podían escribirse en términos de
tres estructuras de control solamente: la de secuencia, la de selección y la de repetición. El término
“estructuras de control” proviene del campo de las ciencias computacionales. Cuando presentemos las
implementaciones en C++ de las estructuras de control, nos referiremos a ellas en la terminología del
documento del estándar de C++ como “instrucciones de control”.
Estructura de secuencia en C++
La estructura de secuencia está integrada en C++. A menos que se le indique lo contrario, la computado-
ra ejecuta las instrucciones en C++ una después de otra, en el orden en que estén escritas; es decir, en
secuencia. El diagrama de actividad en UML de la figura 4.2 ilustra una estructura de secuencia típica,
sumar 1 al contador
sumar calificación al total
Instrucción en C++:
total = total + calificacion;
Instrucción en C++:
contador = contador + 1;
Fig. 4.2  Diagrama de actividad de una estructura de secuencia.
1 Böhm, C. y G. Jacopini, “Flow Diagrams,Turing Machines, and Languages with OnlyTwo Formation Rules”, Com-
munications of the ACM, vol. 9, No. 5, mayo de 1996, páginas 366-371.
108 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
en la que se realizan dos cálculos en orden. C++ permite tantas acciones como deseemos en una estruc-
tura de secuencia. Como veremos pronto, en donde quiera que se coloque una sola acción, podrán colo-
carse varias acciones en secuencia.
En esta figura, las dos instrucciones implican sumar una calificación a una variable llamada total,
y sumar el valor 1 a una variable llamada contador. Dichas instrucciones podrían aparecer en un pro-
grama que obtenga el promedio de varias calificaciones de estudiantes. Para calcular un promedio, el
total de las calificaciones que se van a promediar se divide entre el número de calificaciones. Podría usarse
una variable contador para llevar la cuenta del número de valores a promediar. En el programa de la
sección 4.8 verá instrucciones similares.
Undiagramadeactividadmodelaelflujo de trabajo(tambiénconocidocomolaactividad)deuna
parte de un sistema de software. Dichos flujos de trabajo pueden incluir una porción de un algoritmo,
como la estructura de secuencia de la figura 4.2. Los diagramas de actividad están compuestos por sím-
bolos de propósito especial, como los símbolos de estado de acción (un rectángulo cuyo lado izquier-
do y derecho se reemplazan con arcos hacia fuera), rombos (diamantes) y pequeños círculos; estos
símbolos se conectan mediante flechas de transición, que representan el flujo de la actividad.
Los diagramas de actividad muestran claramente cómo operan las estructuras de control. Consi-
dere el diagrama de actividad para la estructura de secuencia de la figura 4.2. Este diagrama contiene
dos estados de acción que representan las acciones a realizar. Cada estado de acción contiene una
expresión de acción (por ejemplo, “sumar calificación a total” o “sumar 1 al contador”), que especifi-
ca una acción particular a realizar. Otras acciones podrían incluir cálculos u operaciones de entrada/
salida. Las flechas en el diagrama de actividad se llaman flechas de transición. Estas flechas representan
transiciones, las cuales indican el orden en el que ocurren las acciones representadas por los estados de
acción; el programa que implementa las actividades ilustradas por el diagrama de actividad de la figu-
ra 4.2 primero suma calificacion a total, y después suma 1 a contador.
El círculo relleno que se encuentra en la parte superior del diagrama de actividad representa el
estado inicial de la actividad: el inicio del flujo de trabajo antes de que el programa realice las activida-
des modeladas. El círculo sólido rodeado por una circunferencia que aparece en la parte inferior del
diagrama de actividad representa el estado final; es decir, el final del flujo de trabajo después de que
el programa realiza sus actividades.
La figura 4.2 también incluye rectángulos que tienen la esquina superior derecha doblada. En
UML, a estos rectángulos se les llama notas: comentarios con explicaciones que describen el propósito
de los símbolos en el diagrama.
La figura 4.2 utiliza las notas de UML para mostrar el código en C++ asociado con cada uno de los
estados de acción en el diagrama de actividad. Una línea punteada conecta cada nota con el elemento
que ésta describe. Por lo general, los diagramas de actividad no muestran el código de C++ que imple-
menta la actividad. En este libro utilizamos las notas con este propósito, para mostrar cómo se relaciona
el diagrama con el código en C++. Para obtener más información sobre UML, vea nuestro caso de estu-
dio opcional (pero ampliamente recomendado), que aparece en los capítulos 25 y 26, y visite nuestro
Centro de recursos sobre UML en www.deitel.com/UML/.
Instrucciones de selección en C++
C++ tiene tres tipos de instrucciones de selección (las cuales se describen en este capítulo y en el siguien-
te). La instrucción de selección if realiza (selecciona) una acción si una condición es verdadera, o evita
la acción si la condición es falsa. La instrucción de selección if...else realiza una acción si una condi-
ción es verdadera, o realiza una acción distinta si la condición es falsa. La instrucción de selección switch
(capítulo 5) realiza una de entre varias acciones distintas, dependiendo del valor de una expresión entera.
La instrucción de selección if es una instrucción de selección simple, ya que selecciona o ignora
una sola acción (o, como pronto veremos, un solo grupo de acciones). La instrucción if...else se cono-
ce como instrucción de selección doble, ya que selecciona entre dos acciones distintas (o grupos de
acciones). La instrucción switch es una estructura de selección múltiple, ya que selecciona entre
muchas acciones distintas (o grupos de acciones).
4.4 Estructuras de control 109
Instrucciones de repetición en C++
C++ cuenta con tres tipos de instrucciones de repetición (también llamadas instrucciones de ciclo
o ciclos) para ejecutar instrucciones en forma repetida, siempre y cuando una condición (llamada la
condición de continuación del ciclo) siga siendo verdadera. Éstas son las instrucciones while, do...
while y for (en el capítulo 5 presentaremos las instrucciones do...while y for, y en el capítulo 7 pre-
sentaremos una versión especializada de la instrucción for, que se utiliza con lo que se conoce como
arreglos y contenedores). Las instrucciones while y for realizan la acción (o grupo de acciones) en sus
cuerpos, cero o más veces; si la condición de continuación del ciclo es inicialmente falsa, no se ejecutará
la acción (o grupo de acciones). La instrucción do...while realiza la acción (o grupo de acciones) en su
cuerpo, por lo menos una vez.
Las palabras if, else, switch, while, do y for son palabras clave en C++. Las palabras clave no pue-
den usarse como identificadores, como los nombres de variables, y deben escribirse sólo en minúsculas.
En la figura 4.3 aparece una lista completa de las palabras clave en C++.
Palabras clave de C++
Palabras clave comunes para los lenguajes de programación C y C++
auto break case char const
continue default do double else
enum extern float for goto
if int long register return
short signed sizeof static struct
switch typedef union unsigned void
volatile while
Palabras clave sólo de C++
and and_eq asm bitand bitor
bool catch class compl const_cast
delete dynamic_cast explicit export false
friend inline mutable namespace new
not not_eq operator or or_eq
private protected public reinterpret_cast static_cast
template this throw true try
typeid typename using virtual wchar_t
xor xor_eq
Palabras clave de C++11
alignas alignof char16_t char32_t constexpr
decltype noexcept nullptr static_assert thread_local
Fig. 4.3  Palabras clave de C++.
110 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
Resumen de las instrucciones de control en C++
C++ sólo tiene tres tipos de estructuras de control, a las cuales nos referiremos de aquí en adelante como
instrucciones de control: la instrucción de secuencia, las instrucciones de selección (tres tipos: if,
if...else y switch) y las instrucciones de repetición (tres tipos: while, for y do...while). Cada
programa combina tantas de estas instrucciones de control como sea apropiado para el algoritmo que
implemente el programa. Podemos modelar cada una de las instrucciones de control como un diagrama
de actividad, con estados iniciales y finales que representan los puntos de entrada y salida de la instruc-
ción de control, respectivamente. Estas instrucciones de control de una sola entrada/una sola salida
facilitan la creación de programas; las instrucciones de control están unidas entre sí mediante la cone-
xión del punto de salida de una instrucción de control, al punto de entrada de la siguiente. Este proce-
dimiento es similar a la manera en que un niño apila los bloques de construcción, así que a esto le lla-
mamos apilamiento de instrucciones de control. En breve aprenderemos que sólo hay una manera
alternativa de conectar las instrucciones de control: el anidamiento de instrucciones de control, en el
cual una instrucción de control aparece dentro de otra.
Observación de Ingeniería de Software 4.1
Cualquier programa de C++ puede construirse a partir de sólo siete tipos distintos de
instrucciones de control (secuencia, if, if...else, switch, while, do...while y for),
combinadas en sólo dos formas (apilamiento de instrucciones de control y anidamiento de
instrucciones de control).
4.5Instrucción de selección if
Los programas utilizan instrucciones de selección para elegir entre los cursos alternativos de acción. Por
ejemplo, suponga que la calificación para aprobar un examen es de 60. La instrucción en seudocódigo
Si la calificación del estudiante es mayor o igual a 60
Imprimir “Aprobado”
determina si la condición “la calificación del estudiante es mayor o igual a 60” es verdadera (true) o
falsa (false). Si la condición es verdadera se imprime “Aprobado”, y se “ejecuta” en orden la siguiente
instrucción en seudocódigo (recuerde que el seudocódigo no es un verdadero lenguaje de programa-
ción). Si la condición es falsa se ignora la instrucción para imprimir, y se ejecuta en orden la siguiente
instrucción en seudocódigo. La sangría de la segunda línea es opcional, pero se recomienda ya que en-
fatiza la estructura inherente de los programas estructurados.
La instrucción anterior if en seudocódigo puede escribirse en C++ de la siguiente manera:
if ( calificacion = 60 )
cout  Aprobado;
El código en C++ corresponde en gran medida con el seudocódigo. Ésta es una de las propiedades que
hace del seudocódigo una herramienta de desarrollo de programas tan útil.
Es importante mencionar aquí que estamos suponiendo por casualidad que calificacion contie-
ne un valor válido: un entero en el rango de 0 a 100. A lo largo del libro presentaremos muchas técnicas
de validación importantes.
Tip para prevenir errores 4.1
En el código que se utiliza en la industria, siempre hay que validar todas las entradas.
4.5 Instrucción de selección if 111
La figura 4.4 muestra la instrucción if de selección simple. Esta figura contiene lo que quizá sea el
símbolo más importante en un diagrama de actividad: el rombo o símbolo de decisión, el cual indica
que se va a tomar una decisión. Un símbolo de decisión indica que el flujo de trabajo continuará a lo
largo de una ruta determinada por las condiciones de guardia asociadas de ese símbolo, que pueden ser
verdaderas o falsas. Cada flecha de transición que sale de un símbolo de decisión tiene una condición de
guardia especificada entre corchetes, por encima o a un lado de la flecha de transición. Si cierta condición
de guardia es verdadera, el flujo de trabajo entra al estado de acción al que apunta la flecha de transición.
En la figura 4.4, si la calificación es mayor o igual a 60, el programa imprime “Aprobado” en la pantalla,
y luego se dirige al estado final de esta actividad. Si la calificación es menor a 60, el programa se dirige
inmediatamente al estado final sin mostrar ningún mensaje.
imprimir Aprobado
[calificacion = 60]
[calificacion  60]
Fig. 4.4  Diagrama de actividad de la instrucción if de selección simple.
En el capítulo 2 aprendimos que las decisiones se pueden basar en condiciones que contengan
operadores de igualdad o relacionales. En realidad, en C++ una decisión se puede basar en cualquier
expresión; si la expresión se evalúa como cero, se considera como falsa; si la expresión se evalúa como un
número distinto de cero, se considera verdadera. C++ proporciona el tipo de datos bool para las variables
que sólo pueden contener los valores true y false; cada una de éstas es una palabra clave de C++.
Tip de portabilidad 4.1
Para tener compatibilidad con versiones anteriores de C, en las que se utilizan enteros para
los valores booleanos, el valor bool true también se puede representar mediante cualquier
valor distinto de cero (por lo general, los compiladores utilizan 1) y el valor bool false
también se puede representar como el valor cero.
La instrucción if es una instrucción de una sola entrada/una sola salida. Pronto veremos que los
diagramas de actividad para las instrucciones de control restantes también contienen estados iniciales,
flechas de transición, estados de acción que indican las acciones a realizar, símbolos de decisión (con sus
condiciones de guardia asociadas) que indican las decisiones a tomar, y estados finales.
Imagine siete cajones, en donde cada uno contiene diagramas de actividad de UML vacíos de uno
de los siete tipos de instrucciones de control. Su tarea es ensamblar un programa a partir de los diagramas de
actividad de tantas instrucciones de control de cada tipo como lo requiera el algoritmo, combinando
esas instrucciones de control en sólo dos formas posibles (apilando o anidando), y después llenando los
estados de acción y las decisiones con expresiones de acción y condiciones de guardia, en una manera
que sea apropiada para formar una implementación estructurada para el algoritmo. Seguiremos hablan-
do sobre la variedad de formas en que pueden escribirse las acciones y las decisiones.
112 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
4.6Instrucción de selección doble if…else
La instrucción if de selección simple realiza una acción indicada solamente cuando la condición es
verdadera (true); de no ser así, se evita dicha acción. La instrucción if...else de selección doble per-
mite al programador especificar una acción a realizar cuando la condición es verdadera, y otra distinta
cuando la condición es falsa (false). Por ejemplo, la instrucción en seudocódigo
Si la calificación del estudiante es mayor o igual a 60
Imprimir “Aprobado”
De lo contrario
Imprimir “Reprobado”
imprime “Aprobado” si la calificación del estudiante es mayor o igual a 60, e imprime “Reprobado” si la
calificación del estudiante es menor a 60. En cualquier caso, después de que ocurre la impresión se “eje-
cuta” la siguiente instrucción en seudocódigo en la secuencia.
La instrucción anterior if...else en seudocódigo puede escribirse en C++ como
if ( calificacion = 60 )
cout  Aprobado;
else
cout  Reprobado;
El cuerpo de la instrucción else también tiene sangría.
Buena práctica de programación 4.1
Si hay varios niveles de sangría, en cada nivel debe aplicarse la misma cantidad de espacio
adicional para promover la legibilidad y facilidad de mantenimiento.
La figura 4.5 muestra el flujo de control en la instrucción if...else.
imprimir Aprobado
imprimir Reprobado
[calificacion = 60]
[calificacion  60]
Fig. 4.5  Diagrama de actividad de la instrucción if...else de selección doble.
Operador condicional (?:)
C++ cuenta con el operador condicional (?:), que está estrechamente relacionado con la instrucción
if...else. Éste es el único operador ternario de C++: recibe tres operandos. En conjunto, los operan-
4.6 Instrucción de selección doble if...else 113
dos y el operador condicional forman una expresión condicional. El primer operando es una condi-
ción, el segundo es el valor de la expresión condicional si la condición es true, y el tercero es el valor de
toda la expresión condicional si la condición es false. Por ejemplo, la instrucción de salida
cout  ( calificacion = 60 ? Aprobado : Reprobado );
contiene una expresión condicional, calificacion = 60 ? Aprobado : Reprobado, que se evalúa
como la cadena Reprobado si la condición calificacion = 60 es true, pero se evalúa como la ca-
dena Reprobado si la condición es false. Por lo tanto, la instrucción con el operador condicional
realiza en esencia la misma función que la instrucción if...else anterior. Como veremos más adelan-
te, la precedencia del operador condicional es baja, por lo cual los paréntesis en la expresión anterior son
obligatorios.
Tip para prevenir errores 4.2
Para evitar problemas de precedencia (y por claridad), coloque las expresiones condicionales
(que aparezcan en expresiones más grandes) entre paréntesis.
Los valores en una expresión condicional también pueden ser acciones a ejecutar. Por ejemplo, la
siguiente expresión condicional también imprime Aprobado o Reprobado:
calificacion = 60 ? cout  Aprobado : cout  Reprobado;
La expresión condicional anterior se lee como: “si calificacion es mayor o igual que 60, entonces cout
 Aprobado; en caso contrario, cout  Reprobado”. Esto también puede compararse con la
instrucción if...else anterior. Las expresiones condicionales pueden aparecer en algunas partes de los
programas en las que no se pueden utilizar instrucciones if...else.
Instrucciones if…else anidadas
Las instrucciones if...else anidadas pueden evaluar varios casos, al colocar instrucciones de selec-
ción if...else dentro de otras instrucciones if...else. Por ejemplo, la siguiente instrucción
if...else en seudocódigo imprime A para las calificaciones de exámenes mayores o iguales a 90, B para
las que están en el rango de 80 a 89, C en el rango de 70 a 79, D si están en el rango de 60 a 69 y F para
todas las demás calificaciones:
Si la calificación del estudiante es mayor o igual a 90
Imprimir “A”
de lo contrario
Si la calificación del estudiante es mayor o igual a 80
Imprimir “B”
de lo contrario
Si la calificación del estudiante es mayor o igual a 70
Imprimir “C”
de lo contrario
Si la calificación del estudiante es mayor o igual a 60
Imprimir “D”
de lo contrario
Imprimir “F”
114 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
Este seudocódigo puede escribirse en C++ como
if ( calificacionEstudiante = 90 ) // 90 o más recibe una A
cout  A;
else
if ( calificacionEstudiante = 80 ) // 80 a 89 recibe una B
cout  B;
else
if ( calificacionEstudiante = 70 ) // 70 a 79 recibe C
cout  C;
else
if ( calificacionEstudiante = 60 ) // 60 a 69 recibe D
cout  D;
else // menos de 60 recibe F
cout  F;
Si calificacionEstudiante es mayor o igual a 90, las primeras cuatro condiciones serán true, pero
sólo se ejecutará la instrucción después de la primera prueba. Después, el programa evita la parte else
de la instrucción if...else más “externa”. La mayoría de los programadores escriben la instrucción
if...else anterior así:
if ( calificacionEstudiante = 90 ) // 90 o más recibe una A
cout  A;
else if ( calificacionEstudiante = 80 ) // 80 a 89 recibe una B
cout  B;
else if ( calificacionEstudiante = 70 ) // 70 a 79 recibe C
cout  C;
else if ( calificacionEstudiante = 60 ) // 60 a 69 recibe D
cout  D;
else // menos de 60 recibe F
cout  F;
Las dos formas son idénticas, excepto por el espaciado y la sangría, que el compilador ignora. La segun-
da forma es más popular, ya que evita usar mucha sangría hacia la derecha en el código lo cual puede
forzar a que las líneas se dividan.
Tip de rendimiento 4.1
Unainstrucciónif...else anidadapuedeejecutarseconmuchamásrapidezqueunaserie
de instrucciones if de selección simple, debido a la posibilidad de salir antes de tiempo, una
vez que se cumple una de las condiciones.
Tip de rendimiento 4.2
En una instrucción if...else anidada, debemos evaluar las condiciones que tengan más
probabilidades de ser true al principio de la instrucción anidada. Esto permite que la ins-
trucción if...else anidada se ejecute con más rapidez, al salir antes de lo esperado si se
evaluaran primero los casos que ocurren con menos frecuencia.
Problema del else suelto
El compilador de C++ siempre asocia un else con el if que le precede inmediatamente, a menos que se
le indique otra cosa mediante la colocación de llaves ({ y }). Este comportamiento puede ocasionar lo
que se conoce como el problema del else suelto. Por ejemplo,
if ( x  5 )
if ( y  5 )
cout  x e y son  5;
else
cout  x es = 5;
4.6 Instrucción de selección doble if...else 115
parece indicar que si x es mayor que 5, la instrucción if anidada determina si y es también mayor que 5.
De ser así, se produce como resultado la cadena x y y son  5. De lo contrario, parece ser que si x
no es mayor que 5, la instrucción else que es parte del if...else produce como resultado la cadena
x es = 5.
¡Cuidado! Esta instrucción if...else anidada no se ejecuta como parece ser. El compilador en
realidad interpreta la instrucción así:
if ( x  5 )
if ( y  5 )
cout  x y y son  5;
else
cout  x es = 5;
en donde el cuerpo del primer if es un if...else anidado. La instrucción if más externa evalúa si x
es mayor que 5. De ser así, la ejecución continúa evaluando si y es también mayor que 5. Si la segunda
condición es verdadera, se muestra la cadena apropiada (x y y son  5). No obstante, si la segun-
da condición es falsa se muestra la cadena x es = 5, aun y cuando sabemos que x es mayor que 5.
Para forzar a que la instrucción if...else anidada se ejecute como se tenía pensado originalmen-
te, podemos escribirla de la siguiente manera:
if ( x  5 )
{
if ( y  5 )
cout  x y y son  5;
}
else
cout  x es = 5;
Las llaves ({ }) indican al compilador que la segunda instrucción if se encuentra en el cuerpo del primer
if, y que el else está asociado con el primer if. Los ejercicios 4.23 a 4.24 investigan con más detalle el
problema del else suelto.
Bloques
La instrucción de selección if normalmente espera sólo una instrucción en su cuerpo. De manera similar,
las partes if y else de una instrucción if...else esperan sólo una instrucción en su cuerpo. Para incluir
variasinstruccionesenelcuerpodeunif oencualquierpartedeunif...else,encierrelasinstrucciones
entre llaves ({ y }). A un conjunto de instrucciones contenidas dentro de un par de llaves se le llama ins-
trucción compuesta o bloque. De aquí en adelante utilizaremos el término “bloque”.
Observación de Ingeniería de Software 4.2
Unbloquepuedecolocarseencualquierpartedeunprogramaendondepuedacolocarseuna
sola instrucción.
El siguiente ejemplo incluye un bloque en la parte else de una instrucción if...else:
if ( calificacionEstudiante = 60 )
cout  Aprobado.n;
else
{
cout  Reprobado.n;
cout  Debe tomar este curso otra vez.n;
}
116 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
En este caso, si calificacionEstudiante es menor que 60, el programa ejecuta ambas instrucciones en
el cuerpo del else e imprimes
Reprobado.
Debe tomar este curso otra vez.
Observe las llaves que rodean a las dos instrucciones en la cláusula else. Estas llaves son importantes.
Sin ellas, la instrucción
cout  “Debe tomar este curso otra vez.n”;
estaríafueradelcuerpodelaparteelse delainstrucciónif yseejecutaríasinimportarquelacalificación
fuera menor a 60. Éste es un error lógico.
Así como un bloque puede colocarse en cualquier parte en donde pueda colocarse una sola instruc-
ción individual, también es posible no tener instrucción alguna; a ésta se le conoce como instrucción
nula (o instrucción vacía). Para representar a la instrucción nula, se coloca un punto y coma (;) en
donde normalmente iría una instrucción.
Error común de programación 4.1
Colocar un punto y coma después de la condición en una instrucción if produce un error
lógico en las instrucciones if de selección simple, y un error de sintaxis en las instrucciones
if...else deseleccióndoble(cuandolapartedelif contieneunainstrucciónenelcuerpo).
4.7Instrucción de repetición while
Una instrucción de repetición especifica que un programa debe repetir una acción mientras cierta
condición sea verdadera. La instrucción en seudocódigo
Mientras existan más artículos en mi lista de compras
Comprar el siguiente artículo y quitarlo de mi lista
describe la repetición que ocurre durante una salida de compras. La condición “existan más artículos en
mi lista de compras” puede ser verdadera o falsa. Si es verdadera, entonces se realiza la acción “Comprar
el siguiente artículo y quitarlo de mi lista”. Esta acción se realizará en forma repetida mientras la condi-
ción sea verdadera. La instrucción contenida en la instrucción de repetición Mientras (while) constituye
el cuerpo de esta estructura, el cual puede ser una sola instrucción o un bloque. En algún momento, la
condición se hará falsa (cuando el último artículo de la lista de compras sea adquirido y eliminado de
la lista). En este punto la repetición terminará y se ejecutará la primera instrucción que esté después de la
instrucción de repetición.
Como ejemplo de la instrucción de repetición while en C++, considere un segmento de programa
diseñado para encontrar la primera potencia de 3 que sea mayor a 100. Suponga que la variable produc-
to de tipo entero se inicializa en 3. Cuando la siguiente instrucción while termine de ejecutarse, pro-
ducto contendrá el resultado:
int producto = 3;
while ( producto = 100 )
producto = 3 * producto;
Cuando esta instrucción while comienza a ejecutarse, el valor de producto es 3. Cada repetición de la
instrucción while multiplica a producto por 3, por lo que producto toma los valores de 9, 27, 81 y 243,
4.7 Instrucción de repetición while 117
sucesivamente. Cuando producto se vuelve 243, la condición de la instrucción while (producto =
100) se torna falsa. Esto termina la repetición, por lo que el valor final de producto es 243. En este
punto, la ejecución del programa continúa con la siguiente instrucción después de while.
Error común de programación 4.2
Un error lógico conocido como ciclo infinito, en el que la instrucción de repetición nunca
termina, ocurre si no se proporciona una acción en el cuerpo de una instrucción while que
ocasione que en algún momento la condición del while se torne falsa por lo general. Esto
puede hacer que un programa parezca “quedar colgado” o “congelarse” si el cuerpo del ciclo
no contiene instrucciones que interactúen con el usuario.
El diagrama de actividad de UML de la figura 4.6 muestra el flujo de control que corresponde a la
instrucción while anterior. Una vez más, los símbolos en el diagrama (aparte del estado inicial, las fle-
chas de transición, un estado final y tres notas) representan un estado de acción y una decisión. Este
diagrama también introduce el símbolo de fusión de UML, el cual une dos flujos de actividad en un
solo flujo de actividad. UML representa tanto al símbolo de fusión como al símbolo de decisión como
rombos. En este diagrama, el símbolo de fusión une las transiciones del estado inicial y del estado de
acción, de manera que ambas fluyan en la decisión que determina si el ciclo debe empezar a ejecutarse
(o seguir ejecutándose). Los símbolos de decisión y de fusión pueden diferenciarse por el número
de flechas de transición “entrantes” y “salientes”. Un símbolo de decisión tiene una flecha de transición
que apunta hacia el rombo y dos o más flechas que apuntan hacia afuera del rombo, para indicar las
posibles transiciones desde ese punto. Además, cada flecha que apunta hacia fuera de un símbolo
de decisión tiene una condición de guardia junto a ella. Un símbolo de fusión tiene dos o más flechas de
transición que apuntan hacia el rombo, y sólo una que apunta hacia fuera del rombo, para indicar múl-
tiples flujos de actividad que se fusionan para continuar la actividad. A diferencia del símbolo de deci-
sión, el de fusión no tiene su contraparte en el código de C++.
triplicar valor de producto
Instrucción correspondiente en C++:
producto = 3 * producto;
decisión
[producto = 100]
[producto  100]
fusión
Fig. 4.6  Diagrama de actividad de UML de la instrucción de repetición while.
El diagrama de la figura 4.6 muestra claramente la repetición de la instrucción while que vimos
antes en esta sección. La flecha de transición que emerge del estado de acción apunta a la fusión, desde
la cual regresa a la decisión que se evalúa en cada iteración del ciclo, hasta que la condición de guardia
producto  100 se vuelva verdadera. Entonces, la instrucción while termina (llega a su estado final) y
el control pasa a la siguiente instrucción en la secuencia del programa.
118 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
Tip de rendimiento 4.3
Una pequeña mejora en el rendimiento para el código que se ejecuta muchas veces en un
ciclo puede producir una mejora considerable en el rendimiento en general.
4.8Cómo formular algoritmos: repetición controlada
por un contador
Parailustrarlaformaenquelosprogramadoresdesarrollanlosalgoritmos,enestasecciónyenlasiguien-
te resolveremos dos variantes de un problema que promedia las calificaciones de una clase. Considere el
siguiente enunciado del problema:
A una clase de diez estudiantes se les aplicó un examen. Las calificaciones (de 0 a 100) para este examen
están disponibles para que usted las analice. Calcule y muestre el total de las calificaciones de todos los estu-
diantes y el promedio de la clase para este examen.
El promedio de la clase es igual a la suma de las calificaciones, dividida entre el número de estudiantes. El
algoritmo para resolver este problema en una computadora debe recibir como entrada cada una de las
calificaciones, calcular el promedio e imprimir el resultado.
Algoritmo de seudocódigo con repetición controlada por un contador
Emplearemos seudocódigo para listar las acciones a ejecutar y especificar el orden en el que deben ocurrir.
Usaremosunarepetición controlada por contadorparaintroducirlascalificaciones,unaporuna.Esta
técnica utiliza una variable llamada contador para controlar el número de veces que debe ejecutarse un
conjunto de instrucciones (a lo cual se le conoce también como el número de iteraciones del ciclo).
A la repetición controlada por contador se le llama comúnmente repetición definida, ya que el
número de repeticiones se conoce antes de que el ciclo empiece a ejecutarse. En este ejemplo, la repeti-
ción termina cuando el contador excede a 10. Esta sección presenta un algoritmo de seudocódigo
completamente desarrollado (figura 4.7), y una versión de la clase LibroCalificaciones (figuras 4.8 y
4.9) que implementa el algoritmo en una función miembro de C++. Después presentamos una aplica-
ción (figura 4.10) que demuestra el algoritmo en acción. En la sección 4.9 demostraremos cómo utilizar
el seudocódigo para desarrollar dicho algoritmo desde cero.
Observación de Ingeniería de Software 4.3
La parte más difícil para la resolución de un problema en una computadora es desarrollar
el algoritmo. El proceso de producir un programa funcional en C++ a partir de dicho
algoritmo es, por lo general, relativamente sencillo.
1 Asignar a total el valor de cero
2 Asignar al contador de calificaciones el valor de uno
3
4 Mientras que el contador de calificaciones sea menor o igual a diez
5 Pedir al usuario que introduzca la siguiente calificación
6 Obtener como entrada la siguiente calificación
7 Sumar la calificación al total
8 Sumar uno al contador de calificaciones
9
10 Asignar al promedio de la clase el total dividido entre diez
11 Imprimir el total de las calificaciones para todos los estudiantes en la clase
12 Imprimir el promedio de la clase
Fig. 4.7  Algoritmo en seudocódigo que utiliza la repetición controlada por contador para resolver
el problema del promedio de una clase.
4.8 Cómo formular algoritmos: repetición controlada por un contador 119
Observe las referencias en el algoritmo de seudocódigo de la figura 4.7 para un total y un contador.
Un total es una variable que se utiliza para acumular la suma de varios valores. Un contador es una
variable que se utiliza para contar; en este caso, el contador de calificaciones indica cuál de las 10 califi-
caciones está a punto de escribir el usuario. Por lo general, las variables que se utilizan para guardar to-
tales deben inicializarse en cero antes de utilizarse en un programa; de lo contrario, la suma incluiría el
valor anterior almacenado en la ubicación de memoria del total. Recuerde que en el capítulo 2 vimos
que todas las variables deben inicializarse.
Mejora a la validación de LibroCalificaciones
Consideremos una mejora que hicimos a nuestra clase LibroCalificaciones. En la figura 3.16, la
forma en que nuestra función miembro establecerNombreCurso validaría el nombre del curso sería
probar primero si la longitud del mismo es menor o igual a 25 caracteres, mediante el uso de una ins-
trucción if. Si esto fuera cierto, se establecería el nombre del curso. Después, a este código le seguiría
otra instrucción if que evaluaría si la longitud del nombre del curso es mayor que 25 caracteres (en cuyo
caso, el nombre del curso se reduciría). La condición de la segunda instrucción if es el opuesto exacto
de la condición de la primera instrucción if. Si una condición se evalúa como true, la otra se debe
evaluar como false. Dicha situación es ideal para una instrucción if...else, por lo que hemos modi-
ficado nuestro código, reemplazando las dos instrucciones if por una instrucción if...else, como se
muestra en las líneas 18 a 25 de la figura 4.9).
Implementación de la repetición controlada por contador en la clase LibroCalificaciones
La clase LibroCalificaciones (figuras 4.8 y 4.9) contiene un constructor (declarado en la línea 10
de la figura 4.8 y definido en las líneas 9 a 12 de la figura 4.9) que asigna un valor al miembro de datos
nombreCurso (declarado en la línea 16 de la figura 4.8) de la clase. En las líneas 16 a 26, 29 a 32 y 35 a
39 de la figura 4.9 se definen las funciones miembro establecerNombreCurso, obtenerNombreCurso y
mostrarMensaje, respectivamente. En las líneas 42 a 64 se define la función miembro determinarPro-
medioClase, la cual implementa el algoritmo para sacar el promedio de la clase, descrito por el seudo-
código de la figura 4.7.
1 // Fig. 4.8: LibroCalificaciones.h
2 // Definición de la clase LibroCalificaciones que determina el promedio de una
clase.
3 // Las funciones miembro se definen en LibroCalificaciones.cpp
4 #include string // el programa usa la clase string estándar de C++
5
6 // definición de la clase LibroCalificaciones
7 class LibroCalificaciones
8 {
9 public:
10 explicit LibroCalificaciones( std::string ); // inicializa el nombre del curso
11 void establecerNombreCurso( std::string ); // establece el nombre del curso
12 std::string obtenerNombreCurso() const; // obtener el nombre del curso
13 void mostrarMensaje() const; // muestra un mensaje de bienvenida
14 void determinarPromedioClase() const; // promedia las calificaciones escritas
por el usuario
15 private:
16 std::string nombreCurso; // nombre del curso para este LibroCalificaciones
17 }; // fin de la clase LibroCalificaciones
Fig. 4.8  Problema del promedio de una clase utilizando la repetición controlada por contador: encabezado
de LibroCalificaciones.
120 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
1 // Fig. 4.9: LibroCalificaciones.cpp
2 // Definiciones de funciones miembro para la clase LibroCalificaciones que resuelve
3 // el programa del promedio de la clase con repetición controlada por contador.
4 #include iostream
5 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
6 using namespace std;
7
8 // el constructor inicializa a nombreCurso con la cadena que se suministra como
argumento
9 LibroCalificaciones::LibroCalificaciones( string nombre )
10 {
11 establecerNombreCurso( nombre ); // valida y almacena nombreCurso
12 } // fin del constructor de LibroCalificaciones
13
14 // función para establecer el nombre del curso;
15 // asegura que el nombre del curso tenga cuando menos 25 caracteres
16 void LibroCalificaciones::establecerNombreCurso( string nombre )
17 {
18 if ( nombre.size() = 25 ) // si nombre tiene 25 caracteres o menos
19 nombreCurso = nombre; // almacena el nombre del curso en el objeto
20 else // si nombre es mayor de 25 caracteres
21 { // establece nombreCurso a los primeros 25 caracteres del parámetro nombre
22 nombreCurso = nombre.substr( 0, 25 ); // selecciona los primeros 25
caracteres
23 cerr  El nombre   nombre   excede la longitud maxima (25).n
24  Se limito nombreCurso a los primeros 25 caracteres.n  endl;
25 } // fin de if...else
26 } // fin de la función establecerNombreCurso
27
28 // función para obtener el nombre del curso
29 string LibroCalificaciones::obtenerNombreCurso() const
30 {
31 return nombreCurso;
32 } // fin de la función obtenerNombreCurso
33
34 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones
35 void LibroCalificaciones::mostrarMensaje() const
36 {
37 cout  Bienvenido al libro de calificaciones para n
 obtenerNombreCurso()  !n
38  endl;
39 } // fin de la función mostrarMensaje
40
41 // determina el promedio de la clase, con base en las 10 calificaciones escritas
por el usuario
42 void LibroCalificaciones::determinarPromedioClase() const
43 {
44 // fase de inicialización
45 int total = 0; // suma de las calificaciones introducidas por el usuario
46 unsigned int contadorCalif = 1; // número de la calificación a introducir a
continuación
47
48 // fase de procesamiento
49 while ( contadorCalif = 10 ) // itera 10 veces
50 {
51 cout  Escriba una calificacion: ; // pide la entrada
Fig. 4.9  Problema del promedio de una clase utilizando la repetición controlada por contador: archivo
de código fuente de LibroCalificaciones (parte 1 de 2).
4.8 Cómo formular algoritmos: repetición controlada por un contador 121
52 int calificacion = 0; // valor de la calificación introducida por el usuario
53 cin  calificacion; // recibe como entrada la siguiente calificación
54 total = total + calificacion; // suma la calificación al total
55 contadorCalif = contadorCalif + 1; // incrementa el contador por 1
56 } // fin de while
57
58 // fase de terminación
59 int promedio = total / 10; // está bien mezclar la declaración con el cálculo
60
61 // muestra el total y el promedio de las calificaciones
62 cout  nEl total de las 10 calificaciones es   total  endl;
63 cout  El promedio de la clase es   promedio  endl;
64 } // fin de la función determinarPromedioClase
Como la variable contadorCalif (figura 4.9, línea 46) se usa para contar de 1 a 10 en este programa
(todos son valores positivos), declaramos la variable como unsigned int, que puede almacenar sólo
valores no negativos (es decir, de 0 en adelante). Las variables locales total (figura 4.9, línea 45), cali-
ficacion (línea 52) y promedio (línea 59) son de tipo int. La variable calificacion almacena la en-
trada del usuario. Observe que las declaraciones anteriores aparecen en el cuerpo de la función miembro
determinarPromedioClase. Además, la variable calificacion se declara en el cuerpo de la instrucción
while debido a que se utiliza sólo en el ciclo; en general, hay que declarar las variables justo antes de que
se utilicen. Inicializamos calificacion con 0 (línea 52) como una buena práctica, incluso aunque se
introduzca de inmediato un nuevo valor para la calificación en la línea 53.
Buena práctica de programación 4.2
Declare cada variable en una línea separada con su propio comentario, para mejorar la
legibilidad.
En las versiones de la clase LibroCalificaciones de este capítulo, simplemente leemos y procesa-
mos un conjunto de calificaciones. El cálculo del promedio se realiza en la función miembro determi-
narPromedioClase, usando variables locales; no preservamos información acerca de las calificaciones de
los estudiantes en los miembros de datos de la clase. En el capítulo 7, modificaremos la clase Libro-
Calificaciones para mantener las calificaciones en memoria, utilizando un miembro de datos que
hace referencia a una estructura de datos conocida como arreglo. Esto permite que un objeto Libro-
Calificaciones realice varios cálculos sobre un conjunto de calificaciones, sin requerir que el usuario
escriba las calificaciones varias veces.
En las líneas 45 y 46 se inicializan total a 0 y contadorCalif a 1 antes de usarse en los cálculos. Por
lo general, las variables contador se inicializan con cero o uno, dependiendo de su uso en un algoritmo.
Una variable no inicializada contiene un valor “basura” (también conocido como valor indefinido):
el último valor almacenado en la ubicación de memoria reservada para esa variable.
Tip para prevenir errores 4.3
Inicialice siempre las variables al momento de declararlas. Esto le ayudará a evitar los
errores lógicos que ocurren al realizar cálculos con variables sin inicializar.
Tip para prevenir errores 4.4
En algunos casos, los compiladores emiten una advertencia si usted intenta usar el valor
de una variable sin inicializar. Siempre hay que obtener una compilación limpia, al
resolver todos los errores y advertencias.
Fig. 4.9  Problema del promedio de una clase utilizando la repetición controlada por contador: archivo
de código fuente de LibroCalificaciones (parte 2 de 2).
122 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
La línea 49 indica que la instrucción while debe continuar ejecutando el ciclo (lo que también
se conoce como iterar), siempre y cuando el valor de contadorCalif sea menor o igual a 10. Mientras
esta condición sea verdadera, la instrucción while ejecutará en forma repetida las instrucciones entre
las llaves que delimitan su cuerpo (líneas 49 a 56).
En la línea 51 se muestra el indicador Escriba una calificacion:. Esta línea corresponde a
la instrucción en seudocódigo “Pedir al usuario que introduzca la siguiente calificación”. En la línea 53 se lee la
calificación escrita por el usuario y se asigna a la variable calificacion. Esta línea corresponde a la instruc-
ción en seudocódigo “Obtener como entrada la siguiente calificación”. En la línea 54 se suma la nueva cali-
ficacion escrita por el usuario al total, y se asigna el resultado a total, que sustituye su valor anterior.
En la línea 55 se suma 1 a contadorCalif para indicar que el programa ha procesado la calificación
actual y está listo para recibir la siguiente calificación del usuario. Al incrementar a contadorCalif en
cada iteración, en un momento dado su valor excederá a 10. En ese momento, el ciclo while termina
debido a que su condición (línea 49) se vuelve falsa.
Cuando el ciclo termina, en la línea 59 se realiza el cálculo del promedio y se asigna su resultado a la
variable promedio. En la línea 62 se muestra el texto El total de las 10 calificaciones es , segui-
do del valor de la variable total. Después, en la línea 63 se muestra el texto El promedio de la clase
es , seguido del valor de la variable promedio. A continuación, la función miembro determinarProme-
dioClase devuelve el control a la función que hizo la llamada (es decir, a main en la figura 4.10).
Demostración de la clase LibroCalificaciones
La figura 4.10 contiene la función main de esta aplicación, la cual crea un objeto de la clase LibroCali-
ficaciones y demuestra sus capacidades. En la línea 9 de la figura 4.10 se crea un nuevo objeto Libro-
Calificaciones llamado miLibroCalificaciones. La cadena en la línea 9 se pasa al constructor de
LibroCalificaciones (líneas 9 a 12 de la figura 4.9). En la línea 11 de la figura 4.10 se hace una llama-
da a la función miembro mostrarMensaje de miLibroCalificaciones para mostrar un mensaje de
bienvenida al usuario. Después, en la línea 12 se hace una llamada a la función miembro determinar-
PromedioClase de miLibroCalificaciones para permitir que el usuario introduzca 10 calificaciones,
para las cuales la función miembro posteriormente calcula e imprime el promedio; la función miembro
ejecuta el algoritmo que se muestra en el seudocódigo de la figura 4.7.
1 // Fig. 4.10: fig04_10.cpp
2 // Crea un objeto LibroCalificaciones e invoca a su función
determinarPromedioClase.
3 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
4
5 int main()
6 {
7 // crea un objeto LibroCalificaciones llamado miLibroCalificaciones y
8 // pasa el nombre del curso al constructor
9 LibroCalificaciones miLibroCalificaciones( CS101 Programacion en C++ );
10
11 miLibroCalificaciones.mostrarMensaje(); // muestra el mensaje de bienvenida
12 miLibroCalificaciones.determinarPromedioClase();
// busca el promedio de 10 calificaciones
13 } // fin de main
Bienvenido al libro de calificaciones para
CS101 Programacion en C++
Fig. 4.10  Problema del promedio de una clase utilizando la repetición controlada por contador: creación
de un objeto de la clase LibroCalificaciones (figuras 4.8 y 4.9) e invocación de su función miembro
determinarPromedioClase (parte 1 de 2).
4.8 Cómo formular algoritmos: repetición controlada por un contador 123
Escriba una calificacion: 67
Escriba una calificacion: 78
Escriba una calificacion: 89
Escriba una calificacion: 67
Escriba una calificacion: 87
Escriba una calificacion: 98
Escriba una calificacion: 93
Escriba una calificacion: 85
Escriba una calificacion: 82
Escriba una calificacion: 100
El total de las 10 calificaciones es 846
El promedio de la clase es 84
Observaciones acerca de la división de enteros y el truncamiento
El cálculo del promedio realizado en respuesta a la llamada a la función en la línea 12 de la figura 4.10,
produce un resultado entero. La ejecución de ejemplo indica que la suma de los valores de las califica-
ciones es 846, que al dividirse entre 10, debe producir 84.6; un número con un punto decimal. Sin
embargo, el resultado del cálculo total / 10 (línea 59 de la figura 4.9) es el entero 84, ya que total y
10 son enteros. Al dividir dos enteros se produce una división entera: se trunca cualquier parte fraccio-
naria del cálculo (es decir, se descarta). En la siguiente sección veremos cómo obtener un resultado que
incluye un punto decimal a partir del cálculo del promedio.
Error común de programación 4.3
Asumir que la división entera redondea (en vez de truncar) puede producir resultados
erróneos. Por ejemplo, 7 ÷ 4 produce 1.75 en la aritmética convencional, pero trunca la
parte de punto flotante (.75) en la aritmética entera. Por lo tanto, el resultado es 1. De
manera similar, –7 ÷ 4 produce –1.
En la figura 4.9, si en la línea 59 se utilizará contadorCalif en vez de 10, el resultado para este
programa mostraría un valor incorrecto, 76. Esto ocurriría debido a que en la iteración final de la ins-
trucción while, contadorCalif se incrementó al valor 11 en la línea 55.
Error común de programación 4.4
El uso de una variable de control tipo contador de un ciclo en un cálculo después del ciclo
produce un error lógico, conocido como error de desplazamiento en 1. En un ciclo con-
trolado por contador que cuenta en uno cada vez que recorre el ciclo, éste termina cuando
el valor del contador es uno más que su último valor legítimo (es decir, 11 en el caso de
contar del 1 al 10).
Observaciones sobre el desbordamiento aritmético
En la figura 4.9, la línea 54
total = total + calificacion; // suma la calificación al total
sumó cada calificacion introducida por el usuario al total. Incluso esta instrucción simple tiene
un problema potencial: sumar los enteros podría producir un valor que sea demasiado grande como para
almacenarlo en una variable int. Esto se conoce como desbordamiento aritmético y provoca un
Fig. 4.10  Problema del promedio de una clase utilizando la repetición controlada por contador: creación
de un objeto de la clase LibroCalificaciones (figuras 4.8 y 4.9) e invocación de su función miembro
determinarPromedioClase (parte 2 de 2).
124 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
comportamiento indefinido, que puede producir resultados inesperados (en.wikipedia.org/wiki/
Integer_overflow#Secutiry-ramifications). El programa de suma de la figura 2.5 tiene el mismo
problema en la línea 19, que calcula la suma de dos valores int introducidos por el usuario:
suma = numero1 + numero2; // suma los números; almacena el resultado en suma
Los valores máximo y mínimo que pueden almacenarse en una variable int se representan median-
te las constantes INT_MAX e INT_MIN, respectivamente, que se definen en el encabezado climits. Hay
constantes similares para los otros tipos enteros y para los tipos de punto flotante. Puede ver los valores
de su plataforma para estas constantes si abre los encabezados climits y cfloat en un editor de
texto (puede buscar estos archivos en su sistema de archivos).
Se considera una buena práctica asegurarse de que, antes de realizar cálculos aritméticos como los
de la línea 54 de la figura 4.9 y la línea 19 de la figura 2.5, no se desborden. El código para esto se mues-
tra en el sitio Web de CERT www.securecoding.cert.org; sólo tiene que buscar el lineamiento
“INT32-CPP”. El código utiliza los operadores  (AND lógico) y || (OR lógico), que se introducen
en el capítulo 5. En el código de calidad industrial, hay que realizar revisiones como éstas para todos los
cálculos.
Un análisis más detallado del proceso de recibir los datos de entrada del usuario
Cada vez que un programa recibe la entrada del usuario, podrían ocurrir varios problemas. Por ejemplo,
en la línea 53 de la figura 4.9
cin  calificacion; // recibe como entrada la siguiente calificacion
asumimos que el usuario introducirá una calificación entera en el rango de 0 a 100. Sin embargo, la
persona que introduce una calificación podría introducir un entero menor a 0, un entero mayor a 100,
un entero fuera del rango de valores que pueden almacenarse en una variable int, un número que
contenga un punto decimal o un valor que contenga letras o símbolos especiales que ni siquiera sea un
entero.
Para asegurar que la entrada del usuario sea válida, los programas de calidad industrial deben probar
todos los casos erróneos posibles. A medida que avance por el libro, aprenderá diversas técnicas para li-
diar con el amplio rango de posibles problemas de entrada.
4.9Cómo formular algoritmos: repetición controlada
por un centinela
Generalicemos el problema para los promedios de una clase. Considere el siguiente problema:
Desarrollar un programa que calcule el promedio de una clase y que procese las calificaciones para
un número arbitrario de estudiantes cada vez que se ejecute.
En el ejemplo anterior, el enunciado del problema especificó el número de estudiantes, por lo que se
conocía el número de calificaciones (10) por adelantado. En este ejemplo no se indica cuántas califica-
ciones va a introducir el usuario durante la ejecución del programa. El programa debe procesar un nú-
mero arbitrario de calificaciones. ¿Cómo puede el programa determinar cuándo terminar de introducir
calificaciones? ¿Cómo sabrá cuándo calcular e imprimir el promedio de la clase?
Para resolver este problema podemos utilizar un valor especial denominado valor centinela (tam-
biénllamadovalordeseñal,valordepruebaovalordebandera)paraindicarel“findelaintroducción
de datos”. Después de escribir las calificaciones que desea, el usuario escribe el valor centinela para indi-
car que se introdujo la última calificación. A la repetición controlada por centinela a menudo se le llama
repetición indefinida, ya que el número de repeticiones no se conoce antes de que comience la ejecu-
ción del ciclo.
4.9 Cómo formular algoritmos: repetición controlada por un centinela 125
Debe elegirse un valor centinela de tal forma que no pueda confundirse con un valor de entrada
permitido. Por lo general, las calificaciones de un examen son enteros positivos, por lo que –1 es un
valor centinela aceptable para este problema. Por lo tanto, una ejecución del programa podría procesar
una cadena de entradas como 95, 96, 75, 74, 89 y –1. El programa entonces calcularía e imprimiría el
promedio de la clase para las calificaciones 95, 96, 75, 74 y 89. Como –1 es el valor centinela, no debe
entrar en el cálculo del promedio.
Desarrollo del algoritmo en seudocódigo con el método de mejoramiento de arriba a abajo, paso a
paso: la primera mejora (cima)
Vamos a desarrollar el programa para promediar clases con una técnica llamada mejoramiento de arri-
ba a abajo, paso a paso, la cual es esencial para el desarrollo de programas bien estructurados. Comen-
zamos con una representación en seudocódigo de la cima, una sola instrucción que transmite la función
del programa en general:
Determinar el promedio de la clase para el examen, para un número arbitrario de estudiantes
La cima es, en efecto, la representación completa de un programa. Desafortunadamente, la cima (como
en este caso) pocas veces transmite los detalles suficientes como para escribir un programa. Por lo tanto,
ahora comenzaremos el proceso de mejora. Dividiremos la cima en una serie de tareas más pequeñas y
las enlistaremos en el orden en el que se van a realizar. Esto arroja como resultado la siguiente primera
mejora:
Inicializar variables
Introducir, sumar y contar las calificaciones del examen
Calcular e imprimir el total de las calificaciones de todos los estudiantes y el promedio de la clase
Esta mejora utiliza sólo la estructura de secuencia; estos pasos se ejecutan en orden.
Observación de Ingeniería de Software 4.4
Cada mejora, así como la cima en sí, es una especificación completa del algoritmo; sólo
varía el nivel del detalle.
Observación de Ingeniería de Software 4.5
Muchos programas pueden dividirse lógicamente en tres fases: una fase de inicialización
en la que se inicializan las variables del programa; una fase de procesamiento en la que
se introducen los valores de los datos y se ajustan las variables del programa (como conta-
dores y totales) según sea necesario; y una fase de terminación, que calcula y produce los
resultados finales.
Cómo proceder a la segunda mejora
La anterior Observación de Ingeniería de Software es a menudo todo lo que usted necesita para la prime-
ra mejora en el proceso de arriba a abajo. En la segunda mejora, nos comprometemos a usar variables
específicas. En este ejemplo necesitamos el total actual de los números, una cuenta de cuántos números
se han procesado, una variable para recibir el valor de cada calificación, a medida que el usuario las vaya
introduciendo, y una variable para almacenar el promedio calculado. La instrucción en seudocódigo
Inicializar las variables
puede mejorarse como sigue:
Inicializar total en cero
Inicializar contador en cero
126 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
La instrucción en seudocódigo
Introducir, sumar y contar las calificaciones del examen
requiere una estructura de repetición (es decir, un ciclo) que introduzca cada calificación en forma su-
cesiva. No sabemos de antemano cuántas calificaciones van a procesarse, por lo que utilizaremos la re-
petición controlada por centinela. El usuario introduce las calificaciones una por una. Después de
introducir la última calificación, introduce el valor centinela. El programa evalúa el valor centinela
después de la introducción de cada calificación, y termina el ciclo cuando el usuario introduce el valor
centinela. Entonces, la segunda mejora de la instrucción anterior en seudocódigo sería
Pedir al usuario que introduzca la primera calificación
Recibir como entrada la primera calificación (puede ser el centinela)
While (mientras) el usuario no haya introducido aún el centinelal
Sumar esta calificación al total actual
Sumar uno al contador de calificaciones
Pedir al usuario que introduzca la siguiente calificación
Recibir como entrada la siguiente calificación (puede ser el centinela)
En seudocódigo no utilizamos llaves alrededor de las instrucciones que forman el cuerpo de la estructu-
ra While. Simplemente aplicamos sangría a las instrucciones bajo el Mientras para mostrar que pertene-
cenaestainstrucción.Denuevo,elseudocódigoessolamenteunaherramientainformalparadesarrollar
programas.
La instrucción en seudocódigo
Calcular e imprimir el total de las calificaciones de todos los estudiantes y el promedio de la clase
puede redefinirse de la siguiente manera:
Si el contador no es igual a cero
Asignar al promedio el total dividido entre el contador
Imprimir el total de las calificaciones para todos los estudiantes en la clase
Imprimir el promedio de la clase
de lo contrario
Imprimir “No se introdujeron calificaciones”
Evaluamos la posibilidad de una división entre cero; por lo general esto es un error lógico fatal que, si
no se detecta, haría que el programa fallara (a lo que a menudo se le conoce como “crashing”). La se-
gunda mejora completa del seudocódigo para el problema del promedio de una clase se muestra en la
figura 4.11.
Error común de programación 4.5
Un intento de dividir entre cero produce un comportamiento indefinido y generalmente
un error fatal en tiempo de ejecución.
1 Inicializar total en cero
2 Inicializar contador en cero
3
4 Pedir al usuario que introduzca la primera calificación
5 Recibir como entrada la primera calificación (puede ser el centinela)
Fig. 4.11  Algoritmo en seudocódigo del problema para promediar una clase, con una repetición
controlada por centinela (parte 1 de 2).
4.9 Cómo formular algoritmos: repetición controlada por un centinela 127
6
7 Mientras el usuario no haya introducido aún el centinela
8 Sumar esta calificación al total actual
9 Sumar uno al contador de calificaciones
10 Pedir al usuario que introduzca la siguiente calificación
11 Recibir como entrada la siguiente calificación (puede ser el centinela)
12
13 Si el contador no es igual a cero
14 Asignar al promedio el total dividido entre el contador
15 Imprimir el total de las calificaciones para todos los estudiantes de la clase
16 Imprimir el promedio de la clase
17 de lo contrario
18 Imprimir “No se introdujeron calificaciones”
Tip para prevenir errores 4.5
Al realizar una división entre una expresión cuyo valor pudiera ser cero, debemos evaluar explícitamente
esta posibilidad y manejarla de manera apropiada en el programa (como imprimir un mensaje de error),
en vez de permitir que ocurra el error fatal. Hablaremos más sobre cómo lidiar con estos tipos de errores
cuando veamos el manejo de excepciones (capítulos 7, 9 y 17 este último en el sitio web).
El seudocódigo en la figura 4.11 resuelve el problema más general para promediar una clase. Este algo-
ritmo sólo requirió dos niveles de mejoramiento. En ocasiones, se requieren más niveles de mejoramiento.
Observación de Ingeniería de Software 4.6
Termine el proceso de mejoramiento de arriba a abajo, paso a paso, cuando haya especificado el
algoritmo en seudocódigo con el detalle suficiente como para poder convertir el seudocódigo en C++.
Por lo general, la implementación del programa en C++ después de esto es mucho más sencilla.
Observación de Ingeniería de Software 4.7
Muchos programadores experimentados escriben programas sin utilizar herramientas de desarrollo de
programas como el seudocódigo. Estos programadores sienten que su meta final es resolver el problema en
una computadora y que usar herramientas de desarrollo de programas como el seudocódigo simplemen-
te retarda la producción de los resultados finales. Aunque este método pudiera funcionar para problemas
sencillos y conocidos, tiende a ocasionar graves errores y retrasos en proyectos grandes y complejos.
Implementación de la repetición controlada por centinela en la clase LibroCalificaciones
En las figuras 4.12 y 4.13 se muestra la clase LibroCalificaciones que contiene la función miembro
determinarPromedioClase, la cual implementa el algoritmo en seudocódigo de la figura 4.11 (esta clase
se demuestra en la figura 4.14). Aunque cada calificación introducida es un valor entero, existe la proba-
bilidad de que el cálculo del promedio produzca un número con un punto decimal; en otras palabras, un
número real o número de punto flotante (por ejemplo, 7.33, 0.0975 o 1000.12345). El tipo int no
puede representar un número de este tipo, por lo que esta clase debe usar otro tipo para hacerlo. C++
proporciona varios tipos de datos para almacenar números de punto flotante en la memoria, incluyendo
float y double. La principal diferencia entre estos tipos es que, en comparación con las variables float,
las variables double pueden almacenar comúnmente números con una mayor magnitud y un detalle más
fino (es decir, más dígitos a la derecha del punto decimal; a esto se le conoce también como la precisión
del número). Este programa introduce un operador especial llamado operador de conversión, para
forzar a que el cálculo del promedio produzca un resultado numérico de punto flotante.
Fig. 4.11  Algoritmo en seudocódigo del problema para promediar una clase, con una repetición
controlada por centinela (parte 2 de 2).
128 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
1 // Fig. 4.12: LibroCalificaciones.h
2 // Definición de la clase LibroCalificaciones que determina el promedio de una
clase.
3 // Las funciones miembro se definen en LibroCalificaciones.cpp
4 #include string // el programa usa la clase string estándar de C++
5
6 // definición de la clase LibroCalificaciones
7 class LibroCalificaciones
8 {
9 public:
10 explicit LibroCalificaciones( std::string ); // inicializa el nombre del curso
11 void establecerNombreCurso( std::string ); // establece el nombre del curso
12 std::string obtenerNombreCurso() const; // obtiene el nombre del curso
13 void mostrarMensaje() const; // muestra un mensaje de bienvenida
14 void determinarPromedioClase() const; // promedia las calificaciones
escritas por el usuarios
15 private:
16 std::string nombreCurso; // nombre del curso para este LibroCalificaciones
17 }; // fin de la clase LibroCalificaciones
Fig. 4.12  Problema del promedio de una clase utilizando la repetición controlada por centinela:
encabezado de LibroCalificaciones.
1 // Fig. 4.13: LibroCalificaciones.cpp
2 // Definiciones de funciones miembro para la clase LibroCalificaciones que resuelve
3 // el programa del promedio de la clase con repetición controlada por centinela.
4 #include iostream
5 #include iomanip // manipuladores de flujo parametrizados
6 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
7 using namespace std;
8
9 // el constructor inicializa a nombreCurso con la cadena que se suministra como
argumento
10 LibroCalificaciones::LibroCalificaciones( string nombre )
11 {
12 establecerNombreCurso( nombre ); // valida y almacena nombreCurso
13 } // fin del constructor de LibroCalificaciones
14
15 // función para establecer el nombre del curso;
16 // asegura que el nombre del curso tenga cuando mucho 25 caracteres
17 void LibroCalificaciones::establecerNombreCurso( string nombre )
18 {
19 if ( nombre.size() = 25 ) // si el nombre tiene 25 caracteres o menos
20 nombreCurso = nombre; // almacena el nombre del curso en el objeto
21 else // si el nombre es mayor de 25 caracteres
22 { // establece nombreCurso a los primeros 25 caracteres del parámetro nombre
23 nombreCurso = nombre.substr( 0, 25 ); // selecciona los primeros 25
caracteres
24 cerr  El nombre   nombre   excede la longitud maxima (25).n
25  Se limito nombreCurso a los primeros 25 caracteres.n  endl;
26 } // fin de if...else
27 } // fin de la función establecerNombreCurso
Fig. 4.13  Problema del promedio de una clase utilizando la repetición controlada por centinela: archivo
de código fuente de LibroCalificaciones (parte 1 de 3).
4.9 Cómo formular algoritmos: repetición controlada por un centinela 129
28
29 // función para obtener el nombre del curso
30 string LibroCalificaciones::obtenerNombreCurso() const
31 {
32 return nombreCurso;
33 } // fin de la función obtenerNombreCurso
34
35 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones
36 void LibroCalificaciones::mostrarMensaje() const
37 {
38 cout  Bienvenido al libro de calificaciones paran
 obtenerNombreCurso()  !n
39  endl;
40 } // fin de la función mostrarMensaje
41
42 // determina el promedio de la clase con base en las 10 calificaciones escritas
por el usuario
43 void LibroCalificaciones::determinarPromedioClase() const
44 {
45 // fase de inicialización
46 int total = 0; // suma de las calificaciones introducidas por el usuario
47 unsigned int contadorCalif = 0; // número de calificaciones introducidas
48
49 // fase de procesamiento
50 // pide la entrada y lee la calificación del usuario
51 cout  Escriba la calificacion o -1 para salir: ;
52 int calificacion = 0; // valor de la calificación
53 cin  calificacion; // recibe como entrada la calificacion o el valor
centinela
54
55 // itera hasta leer el valor centinela del usuario
56 while ( calificacion != -1 ) // mientras calificacion no sea -1
57 {
58 total = total + calificacion; // suma la calificacion al total
59 contadorCalif = contadorCalif + 1; // incrementa el contador
60
61 // pide la entrada y lee la siguiente calificación del usuario
62 cout  Escriba la calificacion o -1 para salir: ;
63 cin  calificacion; // recibe como entrada la calificacion o el valor
centinela
64 } // fin de while
65
66 // fase de terminación
67 if ( contadorCalif != 0 ) // si el usuario introdujo al menos una
calificacion...
68 {
69 // calcula el promedio de todas las calificaciones introducidas
70 double promedio = static_cast double ( total ) / contadorCalif;
71
72 // muestra el total y el promedio (con dos dígitos de precisión)
73 cout  nEl total de las   contadorCalif   calificaciones
introducidas es 
74  total  endl;
75 cout  setprecision( 2 )  fixed;
76 cout  El promedio de la clase es   promedio  endl;
77 } // fin de if
Fig. 4.13  Problema del promedio de una clase utilizando la repetición controlada por centinela: archivo
de código fuente de LibroCalificaciones (parte 2 de 3).
130 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
78 else // no se introdujeron calificaciones, por lo que imprime el mensaje
apropiado
79 cout  No se introdujeron calificaciones  endl;
80 } // fin de la función determinarPromedioClase
1 // Fig. 4.14: fig04_14.cpp
2 // Crea un objeto LibroCalificaciones e invoca a su función
determinarPromedioClase.
3 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
4
5 int main()
6 {
7 // crea el objeto LibroCalificaciones llamado miLibroCalificaciones y
8 // pasa el nombre del curso al constructor
9 LibroCalificaciones miLibroCalificaciones( CS101 Programacion en C++ );
10
11 miLibroCalificaciones.mostrarMensaje(); // muestra el mensaje de bienvenida
12 miLibroCalificaciones.determinarPromedioClase(); // busca el promedio de 10
calificaciones
13 } // fin de main
Bienvenido al libro de calificaciones para
CS101 Programacion en C++!
Escriba la calificacion o -1 para salir: 97
Escriba la calificacion o -1 para salir: 88
Escriba la calificacion o -1 para salir: 72
Escriba la calificacion o -1 para salir: -1
El total de las 3 calificaciones introducidas es 257
El promedio de la clase es 85.67
Fig. 4.14  Problema del promedio de una clase utilizando la repetición controlada por centinela: creación de
un objeto de la clase LibroCalificaciones e invocación de su función miembro determinarPromedioClase.
En este ejemplo vemos que las estructuras de control pueden apilarse una encima de otra; la ins-
trucción while (líneas 56 a 64 de la figura 4.13) va seguida por una instrucción if...else (líneas 67 a
79) en secuencia. La mayor parte del código en este programa es idéntico al código de la figura 4.9, por
lo que nos concentraremos en las nuevas características y conceptos.
Las líneas 46 y 47 inicializan las variables total y contadorCalif en 0, ya que no se han introdu-
cido calificaciones todavía. Recuerde que este programa utiliza la repetición controlada por centinela.
Para mantener un registro preciso del número de calificaciones introducidas, el programa incrementa
contadorCalif sólo cuando el usuario introduce un valor para la calificación que no sea el valor centi-
nela y el programa completa el procesamiento de la calificación. Declaramos e inicializamos las variables
calificacion (línea 52) y promedio (línea 70) en donde se utilizan. Cabe mencionar que la línea 70
declara la variable promedio como de tipo double. Recuerde que usamos una variable int en el ejemplo
anterior para almacenar el promedio de la clase. Al usar el tipo double en el ejemplo anterior podemos
almacenar el resultado del cálculo del promedio de la clase como un número de punto flotante. Por úl-
timo, observe que antes de ambas instrucciones (líneas 53 y 63) se coloca una instrucción de salida que
pide al usuario los datos de entrada.
Fig. 4.13  Problema del promedio de una clase utilizando la repetición controlada por centinela: archivo
de código fuente de LibroCalificaciones (parte 3 de 3).
4.9 Cómo formular algoritmos: repetición controlada por un centinela 131
Buena práctica de programación 4.3
Pida al usuario cada dato de entrada del teclado. El indicador debe indicar la forma de
la entrada y cualquier valor de entrada especial. En un ciclo controlado por centinela, los
indicadores que solicitan datos de entrada deben recordar explícitamente al usuario cuál
es el valor centinela.
Comparación entre la lógica del programa para la repetición controlada por centinela
y la repetición controlada por contador
Compare la lógica del programa para la repetición controlada por centinela con la repetición contro-
lada por contador en la figura 4.9. En la repetición controlada por contador, cada iteración de la ins-
trucción while (líneas 49 a 56 de la figura 4.9) lee un valor del usuario, para el número especificado de
iteraciones. En la repetición controlada por centinela, el programa lee el primer valor (líneas 51 a 53
de la figura 4.13) antes de llegar al while. Este valor determina si el flujo de control del programa debe
entrar al cuerpo del while. Si la condición es falsa, el usuario introdujo el valor centinela, por lo que el
cuerpo no se ejecuta (es decir, no se introdujeron calificaciones). Si, por otro lado, la condición es
verdadera, el cuerpo comienza a ejecutarse y el ciclo suma el valor de calificacion al total (línea
58) e incrementa contadorCalif (línea 59). Después, en las líneas 62 y 63 en el cuerpo del ciclo se pide
y recibe el siguiente valor del usuario. A continuación, el control del programa se acerca a la llave de-
recha de terminación (}) del cuerpo del while en la línea 64, por lo que la ejecución continúa con la
evaluación de la condición del while (línea 56). La condición utiliza el valor más reciente de califi-
cacion que acaba de introducir el usuario, para determinar si el cuerpo de la instrucción debe ejecu-
tarse otra vez. El valor de la variable calificacion siempre lo introduce el usuario inmediatamente
antes de que el programa evalúe la condición del while. Esto permite al programa determinar si el
valor que acaba de introducir el usuario es el valor centinela, antes de que el programa procese ese valor
(es decir, que lo sume al total e incremente contadorCalif). Si el valor introducido es el valor centi-
nela, el ciclo termina y el programa no suma el valor –1 al total.
Una vez que termina el ciclo, se ejecuta la instrucción if...else (líneas 67 a 79). La condición en
la línea 67 determina si se introdujeron calificaciones o no. Si no se introdujo ninguna, se ejecuta la
parte del else (líneas 78 y 89) de la instrucción if...else y muestra el mensaje “No se introdujeron
calificaciones”, y la función miembro devuelve el control a la función que la llamó.
Observe el bloque de la instrucción while en la figura 4.13. Sin las llaves, las últimas tres instruc-
ciones en el cuerpo del ciclo quedarían fuera de éste, ocasionando que la computadora interpretara el
código incorrectamente, como se muestra a continuación:
// itera hasta leer el valor centinela del usuario
while ( calificacion != -1 )
total = total + calificacion; // suma calificacion al total
contadorCalif = contadorCalif + 1; // incrementa el contador
// pide la entrada y lee la siguiente calificación del usuario
cout  Escriba calificacion o -1 para salir: ;
cin  calificacion;
El código anterior ocasionaría un ciclo infinito en el programa si el usuario no introduce el centinela –1
para la primera calificación (en línea 53).
Error común de programación 4.6
Omitir las llaves que delimitan a un bloque puede provocar errores lógicos, como ciclos
infinitos. Para prevenir este problema, algunos programadores encierran el cuerpo de todas
las instrucciones de control con llaves, aun si el cuerpo sólo contiene una instrucción.
132 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
Precisión de los números de punto flotante y requerimientos de memoria
Las variables de tipo float representan números de punto flotante con precisión simple y tienen
alrededor de siete dígitos significativos en la mayoría de los sistemas actuales. Las variables de tipo dou-
ble representan números de punto flotante con precisión doble. Estos requieren el doble de memo-
ria que las variables float y pueden proporcionar alrededor de 15 dígitos significativos en la mayoría de
los sistemas actuales; aproximadamente el doble de precisión que las variables float. La mayoría de los
programadores representan a los números de punto flotante con el tipo double. De hecho, C++ consi-
dera a todos los números de punto flotante que escribimos en el código fuente de un programa (como
7.33 y 0.0975) como valores double de manera predeterminada. Dichos valores en el código fuente se
conocen como constantes de punto flotante. En el apéndice C,Tipos fundamentales, podrá consultar
los rangos de valores para los números float y double.
En la aritmética convencional, los números de punto flotante surgen con frecuencia como resulta-
do de la división; cuando dividimos 10 entre 3, el resultado es 3.3333333..., donde la secuencia de
números 3 se repite en forma infinita. La computadora asigna sólo una cantidad fija de espacio para
guardar ese valor, por lo que evidentemente el valor de punto flotante guardado almacenado sólo puede
ser una aproximación.
Error común de programación 4.7
Usar números de punto flotante de una manera que suponga que se representan con pre-
cisión de manera exacta (por ejemplo, usándolos en las comparaciones de igualdad) puede
producir resultados imprecisos. Los números de punto flotante se representan sólo en forma
aproximada.
Aunque los números de punto flotante no siempre son 100 por ciento precisos, tienen numerosas
aplicaciones. Por ejemplo, cuando hablamos de una temperatura corporal “normal” de 36.7 grados
centígrados, no necesitamos tener una precisión con una gran cantidad de dígitos. Cuando leemos la
temperatura en un termómetro como 36.7, en realidad podría ser 36.6999473210643. Considerar a
este número simplemente como 36.7 está bien para la mayoría de las aplicaciones en las que se utilizan
temperaturas corporales. Debido a la naturaleza imprecisa de los números de punto flotante, se prefiere
el tipo double al tipo float, ya que las variables double pueden representar números de punto flotante
con más precisión. Por esta razón, usamos el tipo double en el resto del libro.
Conversión explícita e implícita entre los tipos fundamentales
La variable promedio se declara como de tipo double (línea 70 de la figura 4.13) para capturar el resul-
tado fraccionario de nuestro cálculo. Sin embargo, total y contadorCalif son variables enteras. Re-
cuerde que al dividir dos enteros se produce una división entera, en la cual cualquier parte fraccionaria
del cálculo se trunca. En la siguiente instrucción:
double promedio = total / contadorCalif;
primero se realiza el cálculo de la división, por lo que se pierde la parte fraccionaria del resultado antes de
asignarlo a promedio. Para realizar un cálculo de punto flotante con valores enteros, debemos crear va-
lores temporales de punto flotante. C++ cuenta con el operador static_cast para llevar a cabo esta
tarea. En la línea 70 se utiliza el operador de conversión de tipo static_castdouble(total) para
crear una copia de punto flotante temporal de su operando entre paréntesis: total. Utilizar un operador
de conversión de tipo de esta forma es un proceso que se denomina conversión explícita. El valor al-
macenado en total sigue siendo un entero.
El cálculo ahora consiste de un valor de punto flotante (la versión temporal double de total) divi-
dido entre el entero contadorCalif. El compilador sabe cómo evaluar sólo expresiones en las que los
tipos de datos de los operandos sean idénticos. Para asegurar que los operandos sean del mismo tipo, el
4.9 Cómo formular algoritmos: repetición controlada por un centinela 133
compilador realiza una operación llamada promoción (o conversión implícita) en los operandos se-
leccionados.Porejemplo,enunaexpresiónquecontengavaloresdelostiposdedatosint ydouble,C++
promueve los operandos int a valores double. En nuestro ejemplo, tratamos a total como double
(mediante el operador static_cast), por lo que el compilador promueve el valor de contadorCalif al
tipo double, con lo cual permite realizar el cálculo; el resultado de la división de punto flotante se asigna
a promedio. En el capítulo 6, Funciones y una introducción a la recursividad, hablaremos sobre todos
los tipos de datos fundamentales y su orden de promoción.
Los operadores de conversión de tipo están disponibles para usarse con cualquier tipo de datos,
además de los tipos de clases. El operador static_cast se forma colocando la palabra clave static_
cast entre los signos  y , alrededor del nombre de un tipo de datos. El operador static_cast es un
operador unario; un operador que toma sólo un operando. En el capítulo 2 estudiamos los operadores
aritméticos binarios. C++ también soporta las versiones unarias de los operadores de suma (+) y resta
(–), por lo que el programador puede escribir expresiones como –7 o +5. Los operadores de conversión
de tipo tienen mayor precedencia que los demás operadores unarios, como el + unario y el – unario. Esta
precedencia es un nivel mayor que la de los operadores de multiplicación *, / y %, y menor que la de
los paréntesis. En nuestras tablas de precedencia, indicamos el operador de conversión de tipos con la
notación static_casttipo().
Formato para los números de punto flotante
Aquí veremos brevemente las herramientas de formato en la figura 4.13, y las explicaremos con detalle
en el capítulo 13, Entrada/salida de flujos: un análisis detallado. La llamada a setprecision en la línea
75 (con un argumento de 2) indica que la variable double llamada promedio debe imprimirse con dos
dígitos de precisión a la derecha del punto decimal (por ejemplo, 92.37). A esta llamada se le conoce
como manipulador de flujo parametrizado (debido al 2 entre paréntesis). Los programas que utilizan
estas llamadas deben contener la directiva del preprocesador (línea 5):
#include iomanip
El manipulador endl es un manipulador de flujos no parametrizado (ya que no va seguido de un
valor o expresión entre paréntesis), por lo cual no requiere el encabezado iomanip. Si no se especifica
la precisión, los valores de punto flotante se imprimen generalmente con seis dígitos de precisión (es
decir, la precisión predeterminada en la mayoría de los sistemas actuales), aunque en un momento
veremos una excepción a esto.
El manipulador de flujo fixed (línea 75) indica que los valores de punto flotante deben imprimir-
se en lo que se denomina formato de punto fijo, en oposición a la notación científica. La notación
científica es una forma de mostrar un número como valor de punto flotante entre los valores de 1.0 y
10.0, multiplicado por una potencia de 10. Por ejemplo, el valor 3100.0 se mostraría en notación cien-
tífica como 3.1  103
. La notación científica es útil cuando se muestran valores muy grandes o muy
pequeños. En el capítulo 13 hablaremos sobre el formato mediante el uso de la notación científica. Por
otra parte, el formato de punto fijo se utiliza para forzar a que un número de punto flotante muestre un
número específico de dígitos. Al especificar el formato de punto fijo también forzamos a que se imprima
el punto decimal y los ceros a la derecha, aun si el valor es una cantidad entera, como 88.00. Sin la opción de
formato de punto fijo, dicho valor se imprime en C++ como 88, sin los ceros a la derecha ni el punto
decimal. Al utilizar los manipuladores de flujos fixed y setprecision en un programa, el valor impre-
so se redondea al número de posiciones decimales indicado por el valor que se pasa a setprecision (por
ejemplo, el valor 2 en la línea 75), aunque el valor en memoria permanece sin cambios. Por ejemplo, los
valores 87.946 y 67.543 se imprimen como 87.95 y 67.54, respectivamente.También es posible forzar
a que aparezca un punto decimal mediante el uso del manipulador de flujos showpoint. Si se especifica
showpoint sin fixed, entonces no se imprimirán ceros a la derecha. Al igual que endl, los manipulado-
res fixed y showpoint no usan parámetros y no requieren el encabezado iomanip. Ambos se encuen-
tran en el encabezado iostream.
134 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
En las líneas 75 y 76 de la figura 4.13 se imprime el promedio de la clase redondeado a la centésima
más cercana, y lo imprimimos con sólo dos dígitos a la derecha del punto decimal. El manipulador
de flujos parametrizado (línea 75) indica que el valor de la variable promedio se debe mostrar con dos
dígitos de precisión a la derecha del punto decimal; esto se indica mediante setprecision(2). Las tres
calificaciones introducidas durante la ejecución de ejemplo del programa de la figura 4.14 dan un total
de 257, que a su vez produce el promedio 85.666… y se imprime con redondeo como 85.67.
Una nota sobre los enteros sin signo
En la línea 46 de la figura 4.9, se declaró la variable contadorCalif como unsigned int debido a que
sólo puede asumir los valores de 1 a 11 (el 11 termina el ciclo), que son todos valores positivos. En ge-
neral, los contadores que deben almacenar sólo valores no negativos deberían declararse con tipos
unsigned. Las variables de tipos enteros unsigned pueden representar valores desde 0 hasta aproxima-
damente el doble del rango positivo de los tipos enteros con signo correspondientes. Puede determinar el
valor unsigned int máximo de su plataforma con la constante UINT_MAX de climits.
La figura 4.9 podría haber declarado también como unsigned int las variables calificacion,
total y promedio. Por lo general, las calificaciones son valores de 0 a 100, por lo que total y promedio
deberían ser cada uno mayor o igual a 0. Declaramos esas variables como int debido a que no podemos
controlar lo que el usuario introduzca en realidad; incluso podría introducir valores negativos. Peor aún,
el usuario podría introducir un valor que ni siquiera sea un número (más adelante en el libro le mostra-
remos cómo lidiar con dichas entradas erróneas).
Algunas veces los ciclos controlados por centinela usan valores inválidos de manera intencional para
terminar un ciclo. Por ejemplo, en la línea 56 de la figura 4.13 terminamos el ciclo cuando el usuario
introduce el valor centinela -1 (una calificación inválida), por lo que sería inapropiado declarar la varia-
ble calificacion como unsigned int. Como veremos más adelante, el indicador de fin de archivo
(EOF) (que presentaremos en el siguiente capítulo y que se utiliza a menudo para terminar ciclos contro-
lados por centinela) también se implementa de manera interna en el compilador como un número ne-
gativo.
4.10 Cómo formular algoritmos: instrucciones
de control anidadas
En el siguiente ejemplo formularemos una vez más un algoritmo utilizando seudocódigo y el mejora-
miento de arriba a abajo, paso a paso, y después escribiremos el correspondiente programa en C++.
Hemos visto que las instrucciones de control pueden apilarse una encima de otra (en secuencia). Aquí
examinaremos la otra forma en la que pueden conectarse las instrucciones de control, a saber, mediante
el anidamiento de una instrucción de control dentro de otra. Considere el siguiente enunciado de un
problema:
Una universidad ofrece un curso que prepara a los estudiantes para el examen estatal de certificación
del estado como corredores de bienes raíces. El año pasado, diez de los estudiantes que completaron
este curso tomaron el examen. La universidad desea saber qué tan bien se desempeñaron sus estu-
diantes en el examen. A usted se le ha pedido que escriba un programa para sintetizar los resultados.
Se le dio una lista de estos 10 estudiantes. Junto a cada nombre hay un 1 escrito, si el estudiante
aprobó el examen, o un 2 si lo reprobó.
Su programa debe analizar los resultados del examen de la siguiente manera:
1. Introducir cada resultado de la prueba (es decir, un 1 o un 2). Mostrar el mensaje “Escriba el resulta-
do” en la pantalla, cada vez que el programa solicite otro resultado de la prueba.
2. Contar el número de resultados de la prueba, de cada tipo.
3. Mostrar un resumen de los resultados de la prueba, indicando el número de estudiantes que aprobaron
y el número de estudiantes que reprobaron.
4. Si más de ocho estudiantes aprobaron el examen, imprimir el mensaje “Bono para el instructor”.
4.10 Cómo formular algoritmos: instrucciones de control anidadas 135
Después de leer cuidadosamente el enunciado del programa, hacemos las siguientes observa-
ciones:
1. El programa debe procesar los resultados de la prueba para 10 estudiantes. Puede usarse un
ciclo controlado por contador, ya que el número de resultados de la prueba se conoce de ante-
mano.
2. Cada resultado de la prueba es un número; ya sea 1 o 2. Cada vez que el programa lee un re-
sultado de la prueba, debe determinar si el número es 1 o 2. Para fines de simplificación, no-
sotros sólo evaluamos un 1 en nuestro algoritmo. Si el número no es 1, suponemos que es
un 2 (asegúrese de hacer el ejercicio 4.20, en donde se consideran las consecuencias de esta
suposición).
3. Se utilizan dos contadores para llevar el registro de los resultados del examen: uno para contar
el número de estudiantes que aprobaron el examen y uno para contar el número de estudiantes
que reprobaron el examen.
4. Una vez que el programa ha procesado todos los resultados, debe decidir si más de ocho estu-
diantes aprobaron el examen.
Veamos ahora el mejoramiento de arriba a abajo, paso a paso. Comencemos con la representación
del seudocódigo de la cima:
Analizar los resultados del examen y decidir si hay que pagar un bono o no
Una vez más, es importante enfatizar que la cima es una representación completa del programa, pero es
probable que se necesiten varias mejoras antes de que el seudocódigo pueda evolucionar de manera
natural en un programa en C++.
Nuestra primera mejora es
Inicializar variables
Introducir las 10 calificaciones del examen, y contar los aprobados y reprobados
Imprimir un resumen de los resultados del examen y decidir si debe pagarse un bono
Aquí también, aun y cuando tenemos una representación completa de todo el programa, es necesario
mejorarla. Ahora nos comprometemos con variables específicas. Se necesitan contadores para registrar
los aprobados y reprobados; utilizaremos un contador para controlar el proceso de los ciclos y necesita-
remos una variable para guardar la entrada del usuario.
La instrucción en seudocódigo
Inicializar variables
puede mejorarse de la siguiente manera:
Inicializar aprobados en cero
Inicializar reprobados en cero
Inicializar contador de estudiantes en uno
Observe que sólo se inicializan los contadores al principio del algoritmo.
La instrucción en seudocódigo
Introducir las 10 calificaciones del examen, y contar los aprobados y reprobados
136 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
requiere un ciclo en el que se introduzca sucesivamente el resultado de cada examen. Sabemos de ante-
mano que hay precisamente 10 resultados del examen, por lo que es apropiado utilizar un ciclo contro-
lado por contador. Dentro del ciclo (es decir, anidado dentro del ciclo), una instrucción if...else
determinará si cada resultado del examen es aprobado o reprobado, e incrementará el contador apropia-
do. Entonces, la mejora al seudocódigo anterior es
Mientras el contador de estudiantes sea menor o igual a 10
Pedir al usuario que introduzca el siguiente resultado del examen
Recibir como entrada el siguiente resultado del examen
Si el estudiante aprobó
Sumar uno a aprobados
De lo contrario
Sumar uno a reprobados
Sumar uno al contador de estudiantes
Nosotros utilizamos líneas en blanco para aislar la estructura de control Si...De lo contrario, lo cual me-
jora la legibilidad.
La instrucción en seudocódigo
Imprimir un resumen de los resultados de los exámenes y decidir si debe pagarse un bono
puede mejorarse de la siguiente manera:
Imprimir el número de aprobados
Imprimir el número de reprobados
Si más de ocho estudiantes aprobaron
Imprimir “Bono para el instructor”
La segunda mejora completa aparece en la figura 4.15. Se utilizan líneas en blanco para separar la estruc-
tura Mientras y mejorar la legibilidad del programa. Este seudocódigo está ahora lo suficientemente
mejorado para su conversión a C++.
1 Inicializar aprobados en cero
2 Inicializar reprobados en cero
3 Inicializar contador de estudiantes en uno
4
5 Mientras el contador de estudiantes sea menor o igual a 10
6 Pedir al usuario que introduzca el siguiente resultado del examen
7 Recibir como entrada el siguiente resultado del examen
8
9 Si el estudiante aprobó
10 Sumar uno a aprobados
11 De lo contrario
12 Sumar uno a reprobados
13
14 Sumar uno al contador de estudiantes
15
16 Imprimir el número de aprobados
17 Imprimir el número de reprobados
Fig. 4.15  El seudocódigo para el problema de los resultados del examen (parte 1 de 2).
4.10 Cómo formular algoritmos: instrucciones de control anidadas 137
18 If more than eight students passed
19 Si más de ocho estudiantes aprobaron
20 Imprimir “Bono para el instructor”
Análisis de conversión a la clase
El programa que implementa el algoritmo en seudocódigo se muestra en la figura 4.16. Este ejemplo no
contiene una clase; sólo contiene un archivo de código fuente en donde la función main realiza todo el
trabajo de la aplicación. En éste y en el capítulo 3, hemos visto ejemplos que consisten en una clase (in-
cluyendo los archivos de encabezado y código fuente para esta clase), así como otro archivo de código
fuente para probar la clase. Este archivo de código fuente contenía la función main, que creaba un objeto
de la clase y llamaba a sus funciones miembro. En ocasiones, cuando no tenga sentido tratar de crear una
clase reutilizable para demostrar un concepto, usaremos un ejemplo contenido totalmente dentro de la
función main de un sólo archivo de código fuente.
En las líneas 9 a 11 y 18 se declaran e inicializan las variables que se utilizan para procesar los resul-
tados del examen. Algunas veces los programas de iteración requieren la inicialización al principio de
cada repetición; dicha reinicialización se realiza mediante instrucciones de asignación en vez de decla-
raciones, o se pueden mover las declaraciones adentro de los cuerpos de los ciclos.
1 // Fig. 4.16: fig04_16.cpp
2 // Problema de los resultados del examen: instrucciones de control anidadas.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 // inicialización de las variables en las declaraciones
9 unsigned int aprobados = 0; // número de aprobados
10 unsigned int reprobados = 0; // número de reprobados
11 unsigned int contadorEstudiantes = 1; // contador de estudiantes
12
13 // procesa 10 estudiantes usando el ciclo controlado por contador
14 while ( contadorEstudiantes = 10 )
15 {
16 // pide datos de entrada y obtiene el valor del usuario
17 cout  Escriba el resultado (1 = aprobado, 2 = reprobado): ;
18 int resultado = 0; // resultado de un examen (1 = aprobado, 2 = reprobado)
19 cin  resultado; // recibe como entrada el resultado
20
21 // if...else anidado en la instrucción while
22 if ( resultado == 1 ) // si resultado es 1,
23 aprobados = aprobados + 1; // incrementa aprobados;
24 else // else resultado no es 1, por lo que
25 reprobados = reprobados + 1; // incrementa reprobados
26
27 // incrementa contadorEstudiantes para que el ciclo termine en cierto
momento
28 contadorEstudiantes = contadorEstudiantes + 1;
29 } // fin de while
Fig. 4.15  El seudocódigo para el problema de los resultados del examen (parte 2 de 2).
Fig. 4.16  Problema de los resultados de un examen: instrucciones de control anidadas (parte 1 de 2).
138 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
30
31 // fase de terminación; muestra el número de aprobados y reprobados
32 cout  Aprobados   aprobados  nReprobados   reprobados  endl;
33
34 // determina si aprobaron más de ocho estudiantes
35 if ( aprobados  8 )
36 cout  Bono para el instructor  endl;
37 } // fin de main
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 2
Escriba el resultado (1 = aprobado, 2 = reprobado): 2
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 2
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 2
Aprobados 6
Reprobados 4
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 2
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Escriba el resultado (1 = aprobado, 2 = reprobado): 1
Aprobados 9
Reprobados 1
Bono para el instructor
La instrucción while (líneas 14 a 29) itera 10 veces. Durante cada iteración, el ciclo recibe y proce-
sa un resultado del examen. La instrucción if...else (líneas 22 a 25) para procesar cada resultado se
anida en la instrucción while. Si resultado es 1, la instrucción if...else incrementa a aprobados; en
caso contrario, asume que resultado es 2 e incrementa reprobados. La línea 28 incrementa contador-
Estudiantes antes de que se evalúe otra vez la condición del ciclo, en la línea 15. Después de introducir
10 valores, el ciclo termina y en la línea 32 se muestra el número de aprobados y de reprobados. La
instrucción if de las líneas 35 a 36 determina si más de ocho estudiantes aprobaron el examen y, de ser
así, imprime el mensaje Bono para el instructor.
En la figura 4.16 se muestra la entrada y la salida de dos ejecuciones de ejemplo del programa. Al
final de la segunda ejecución de ejemplo, la condición en la línea 35 es verdadera: más de ocho estudian-
tes aprobaron el examen, por lo que el programa imprime un mensaje en pantalla indicando que el
instructor debe recibir un bono.
Fig. 4.16  Problema de los resultados de un examen: instrucciones de control anidadas (parte 2 de 2).
4.11 Operadores de asignación 139
Inicialización de listas en C++11
C++11 introduce una nueva sintaxis de inicialización de variables. La inicialización de listas (también
conocida como inicialización uniforme) nos permite usar una sintaxis para inicializar una variable de
cualquier tipo. Considere la línea 11 de la figura 4.16:
unsigned int contadorEstudiantes = 1;
En C++11 podemos escribir esto como
unsigned int contadorEstudiantes = { 1 };
o
unsigned int contadorEstudiantes{ 1 };
Las llaves ({ y }) representan el inicializador de listas. Para una variable de tipo fundamental, sólo se
coloca un valor en el inicializador de listas. Para un objeto, el inicializador de listas puede ser una lista
de valores separada por comas que se pasan al constructor del objeto. Por ejemplo, el ejercicio 3.14 le
pidió crear una clase Empleado que representara el primer nombre, apellido y salario de un empleado.
Suponiendo que la clase define un constructor que recibe objetos string para el primer nombre y el
apellido, y un double para el salario, podría inicializar el objeto Empleado de la siguiente forma:
Empleado empleado1{ Bob, Blue, 1234.56 };
Empleado empleado2 = { Sue, Green, 2143.65 };
Para las variables de tipos fundamentales, la sintaxis de inicialización de listas también evita lo que
se conoce como conversiones de reducción (narrowing), que podrían provocar pérdida de datos. Por
ejemplo, antes era posible escribir la siguiente instrucción:
int x = 12.7;
la cual intenta asignar el valor double 12.7 a la variable int x. Para convertir un valor double en int,
se trunca la parte de punto flotante (.7) y se produce una pérdida de información: una conversión de
reducción. El valor real asignado a x es 12. Muchos compiladores generan una advertencia para esta
instrucción, pero de todas formas permiten que se compile. Pero si utilizamos la inicialización de listas,
como en
int x = { 12.7 };
o
int x{ 12.7 };
se produce un error de compilación, el cual le ayuda a evitar un error lógico potencialmente sutil. Por
ejemplo, el compilador Xcode LLVM de Apple proporciona el error
Type ‘double’ cannot be narrowed to 'int' in initializer list
En capítulos posteriores hablaremos sobre características adicionales de los inicializadores de listas.
4.11 Operadores de asignación
C++ cuenta con varios operadores de asignación para abreviar las expresiones de asignación. Por ejem-
plo, la instrucción
c = c + 3;
puede abreviarse mediante el operador de asignación de suma, +=, de la siguiente manera:
c += 3;
140 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
este operador suma el valor de la expresión que está a la derecha del operador, al valor de la variable que
está a la izquierda del operador, y almacena el resultado en la variable que está a la izquierda del operador.
Cualquier instrucción de la forma
variable = variable operador expresión;
en donde la misma variable aparece en ambos lados del operador de asignación y operador es uno de los
operadores binarios +, -, *, / o % (u otros que veremos más adelante en el libro), puede escribirse de la
siguiente forma:
variable operador= expresión;
Por lo tanto, la expresión de asignación c += 3 suma 3 a c. La figura 4.17 muestra los operadores de
asignación aritméticos, algunas expresiones de ejemplo en las que se utilizan los operadores y las expli-
caciones de lo que estos operadores hacen.
Operador de
asignación
Expresión
de ejemplo Explicación Asigna
Suponer que: int c = 3, d = 5, e = 4, f = 6, g = 12;
+= c += 7 c = c + 7 10 a c
-= d -= 4 d = d - 4 1 a d
*= e *= 5 e = e * 5 20 a e
/= f /= 3 f = f / 3 2 a f
%= g %= 9 g = g % 9 3 a g
Fig. 4.17  Operadores de asignación aritméticos.
4.12 Operadores de incremento y decremento
Además de los operadores de asignación aritméticos, C++ proporciona dos operadores unarios para
sumar 1, o restar 1, al valor de una variable numérica. Estos operadores son el operador de incremento
unario, ++, y el operador de decremento unario, --, los cuales se sintetizan en la figura 4.18. Un pro-
grama puede incrementar en 1 el valor de una variable llamada c, utilizando el operador de incremento,
++, en lugar de usar la expresión c = c + 1 o c +=1. A un operador de incremento o decremento que se
coloca antes de una variable se le llama operador de preincremento o predecremento, respectivamen-
te.Aunoperadordeincrementoodecrementoquesecolocadespuésdeunavariableselellamaoperador
de postincremento o postdecremento, respectivamente.
Operador Llamado
Expresión
de ejemplo Explicación
++ preincremento ++a Incrementar a en 1, después utilizar el
nuevo valor de a en la expresión en que esta
variable reside.
++ postincremento a++ Usar el valor actual de a en la expresión en
la que esta variable reside, después
incrementar a en 1.
Fig. 4.18  Los operadores de incremento y decremento (parte 1 de 2).
4.12 Operadores de incremento y decremento 141
Operador Llamado
Expresión
de ejemplo Explicación
-- predecremento --b Decrementar b en 1, después utilizar el
nuevo valor de b en la expresión en que esta
variable reside.
-- postdecremento b-- Usar el valor actual de b en la expresión en
la que esta variable reside, después
decrementar b en 1.
Al proceso de utilizar el operador de preincremento (o postdecremento) para sumar (o restar) 1 a
una variable, se le conoce como preincrementar (o predecrementar) la variable. Al preincrementar
(o predecrementar) una variable, ésta se incrementa (o decrementa) en 1, y después el nuevo valor de
la variable se utiliza en la expresión en la que aparece. Al proceso de utilizar el operador de postincre-
mento (o postdecremento) para sumar (o restar) 1 a una variable, se le conoce como postincrementar
(o postdecrementar) la variable. Al postincrementar (o postdecrementar) una variable, el valor actual
de la variable se utiliza en la expresión en la que aparece y después el valor de la variable se incrementa
(o decrementa) en 1.
Buena práctica de programación 4.4
A diferencia de los operadores binarios, los operadores unarios de incremento y decremen-
to deben colocarse enseguida de sus operandos, sin espacios entre ellos.
En la figura 4.19 se demuestra la diferencia entre la versión de preincremento y la versión de
predecremento del operador de incremento ++. El operador de decremento (--) funciona de manera
similar.
1 // Fig. 4.19: fig04_19.cpp
2 // Preincremento y postincremento.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 // demuestra el postincremento
9 int c = 5; // asigna 5 a c
10 cout  c  endl; // imprime 5
11 cout  c++  endl; // imprime 5 y después postincrementa
12 cout  c  endl; // imprime 6
13
14 cout  endl; // salta una línea
15
16 // demuestra el preincremento
17 c = 5; // asigna 5 a c
18 cout  c  endl; // imprime 5
19 cout  ++c  endl; // preincrementa y después imprime 6
20 cout  c  endl; // imprime 6
21 } // fin de main
Fig. 4.18  Los operadores de incremento y decremento (parte 2 de 2).
Fig. 4.19  Preincrementar y postdecrementar (parte 1 de 2).
142 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
5
5
6
5
6
6
En la línea 9 se inicializa c con 5, y en la línea 10 se imprime el valor inicial de c. En la línea 11 se
imprime el valor de la expresión c++. Esta expresión postincrementa la variable c, por lo que se imprime
su valor original (5) y después se incrementa su valor. Así, en la línea 11 se imprime el valor inicial de c
(5) otra vez. En la línea 12 se imprime el nuevo valor de c (6) para mostrar que se incrementó el valor de
la variable en la línea 11.
En la línea 17 se restablece el valor de c a 5 y en la línea 18 se imprime ese valor. En la línea 19 se
imprime el valor de la expresión ++c. Esta expresión preincrementa a c, por lo que se incrementa su
valor y luego se imprime el nuevo valor (6). En la línea 20 se imprime el valor de c otra vez para mostrar
que sigue siendo 6 después de que se ejecuta la línea 19.
Los operadores de asignación aritméticos y los operadores de incremento y decremento pueden
utilizarse para simplificar las instrucciones de los programas. Las tres instrucciones de asignación de la
figura 4.16:
aprobados = aprobados + 1;
reprobados = reprobados + 1;
contadorEstudiantes = contadorEstudiantes + 1;
se pueden escribir en forma más concisa con operadores de asignación, de la siguiente manera:
aprobados += 1;
reprobados += 1;
contadorEstudiantes += 1;
con operadores de preincremento de la siguiente forma:
++aprobados;
++reprobados;
++contadorEstudiantes;
o con operadores de postincremento de la siguiente forma:
aprobados++;
reprobados++;
contadorEstudiantes++;
Al incrementar (++) o decrementar (--) una variable entera que se encuentre en una instrucción por
sí sola, las formas preincremento y postincremento tienen el mismo efecto lógico, y las formas predecre-
mento y postdecremento tienen el mismo efecto lógico. Solamente cuando una variable aparece en el
contextodeunaexpresiónmásgrandeescuandolosoperadorespreincrementoypostincrementotienen
distintos efectos (y lo mismo se aplica a los operadores de predecremento y postdecremento).
Error común de programación 4.8
Tratar de usar el operador de incremento o decremento en una expresión que no sea un
nombre de variable que pueda modificarse [por ejemplo, escribir ++(x + 1)] es un error
de sintaxis.
Fig. 4.19  Preincrementar y postdecrementar (parte 2 de 2).
4.13 Conclusión 143
En la figura 4.20 se muestra la precedencia y la asociatividad de los operadores que se han presen-
tado hasta este punto. Los operadores se muestran de arriba a abajo, en orden descendente de preceden-
cia. La segunda columna indica la asociatividad de los operadores en cada nivel de precedencia. Cabe
mencionar que el operador condicional (?:), los operadores unarios de preincremento (++), predecre-
mento (--), suma (+) y resta (-), y los operadores de asignación =, +=, -=, *=, /= y %= se asocian de dere-
cha a izquierda. Todos los demás operadores en la figura 4.20 se asocian de izquierda a derecha. La ter-
cera columna enlista los diversos grupos de operadores.
Operadores Asociatividad Tipo
:: () izquierda a derecha
[Vea la precaución en la figura 2.10 con
respecto al agrupamiento de paréntesis]
primario
++ -- static_casttype() izquierda a derecha postfijo
++ -- + - derecha a izquierda unario (prefijo)
* / % izquierda a derecha multiplicativo
+ - izquierda a derecha aditivo
  izquierda a derecha inserción/extracción
 =  = izquierda a derecha relacional
== != izquierda a derecha igualdad
?: derecha a izquierda condicional
= += -= *= /= %= derecha a izquierda asignación
Fig. 4.20  Precedencia de los operadores vistos hasta ahora en el libro.
4.13 Conclusión
Este capítulo presentó las técnicas básicas de solución de problemas que los programadores utilizan para
crear clases y desarrollar funciones miembro para estas clases. Demostramos cómo construir un algorit-
mo (es decir, una metodología para resolver un problema) en seudocódigo, y después cómo refinar el
algoritmo a través de diversas fases de desarrollo de seudocódigo, lo cual produce código en C++ que
puede ejecutarse como parte de una función. En este capítulo aprendió a utilizar el método de mejora-
miento de arriba a abajo, paso a paso, para planear las acciones específicas que debe realizar una función,
y el orden en el que debe realizar estas acciones.
Aprendióquesólohaytrestiposdeestructurasdecontrol(secuencia,selecciónyrepetición)necesarias
para desarrollar cualquier algoritmo. Demostramos dos de las instrucciones de selección de C++: la ins-
trucción de selección simple if y la instrucción de selección doble if...else. La instrucción if se utiliza
para ejecutar un conjunto de instrucciones basadas en una condición; si la condición es verdadera, se eje-
cutan las instrucciones; si no, se omiten. La instrucción de selección doble if...else se utiliza para ejecu-
tar un conjunto de instrucciones si se cumple una condición, y otro conjunto de instrucciones si la condi-
ción es falsa. Después vimos la instrucción de repetición while, en donde un conjunto de instrucciones se
ejecutan de manera repetida, mientras que una condición sea verdadera. Utilizamos el apilamiento de
instruccionesdecontrolparacalculareltotalyelpromediodeunconjuntodecalificacionesdeestudiantes,
mediante la repetición controlada por un contador y controlada por un centinela, y utilizamos el anida-
miento de instrucciones de control para analizar y tomar decisiones con base en un conjunto de resultados
deunexamen.Vimosunaintroducciónalosoperadoresdeasignación,quepuedenutilizarseparaabreviar
instrucciones. Presentamos los operadores de incremento y decremento, los cuales se pueden utilizar para
sumar o restar el valor 1 de una variable. En el siguiente capítulo continuaremos nuestra discusión acerca
de las instrucciones de control, en donde presentaremos las instrucciones for, do...while y switch.
144 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
Resumen
Sección 4.2 Algoritmos
• Un algoritmo (pág. 105) es un procedimiento para resolver un problema, en términos de las acciones a ejecutar
y el orden en el que se ejecutan.
• El proceso de especificar el orden en el que se ejecutan las instrucciones en un programa se denomina control
del programa (pág. 106).
Sección 4.3 Seudocódigo
• El seudocódigo (pág. 106) ayuda al programador a idear un programa antes de intentar escribirlo en un lengua-
je de programación.
Sección 4.4 Estructuras de control
• Un diagrama de actividad modela el flujo de trabajo (también conocido como la actividad; pág. 108) de un
sistema de software.
• Los diagramas de actividad (pág. 107) se componen de símbolos tales como los símbolos de estados de acción,
rombos y pequeños círculos, que se conectan mediante flechas de transición, las cuales representan el flujo de la
actividad.
• Al igual que el seudocódigo, los diagramas nos ayudan a desarrollar y representar algoritmos.
• Un estado de acción se representa como un rectángulo en el que sus lados izquierdo y derecho se sustituyen con
arcos que se curvean hacia fuera. La expresión de acción (pág. 108) aparece dentro del estado de acción.
• Las flechas en un diagrama de actividad representan las transiciones (pág. 108), que indican el orden en el que
ocurren las acciones representadas por los estados de acción.
• El círculo relleno en un diagrama de actividad representa el estado inicial (pág. 108): el comienzo del flujo de
trabajo antes de que el programa realice las acciones modeladas.
• El círculo sólido rodeado por una circunferencia, que aparece en la parte inferior del diagrama de actividad,
representa el estado final (pág. 108): el término del flujo de trabajo después de que el programa realiza sus ac-
ciones.
• Los rectángulos con las esquinas superiores derechas dobladas se llaman notas (pág. 108) en UML. Una línea
punteada (pág. 108) conecta a cada nota con el elemento que ésta describe.
• Existen tres tipos de estructuras de control (pág. 107): secuencia, selección y repetición.
• La estructura de secuencia está integrada en C++; de manera predeterminada, las instrucciones se ejecutan en
el orden en el que aparecen.
• Una estructura de selección elige uno de varios cursos alternativos de acción.
Sección 4.5 Instrucción de selección if
• La instrucción if de selección simple (pág. 110) ejecuta (selecciona) una acción si una condición es verdadera,
o la ignora si la condición es falsa.
• Un símbolo de decisión (pág. 111) en un diagrama de actividad indica que se debe tomar una decisión. El flujo
de trabajo sigue una ruta determinada por las condiciones de guardia asociadas. Cada flecha de transmisión que
sale de un símbolo de decisión tiene una condición de guardia. Si una condición de guardia es verdadera, el
flujo de trabajo entra al estado de acción al que apunta la flecha de transición.
Sección 4.6 Instrucción de selección doble if…else
• La instrucción if...else de selección doble (pág. 112) ejecuta (selecciona) una acción cuando la condición es
verdadera, y otra acción distinta cuando la condición es falsa.
• Para incluir varias instrucciones en el cuerpo del if (o en el cuerpo del else para una instrucción if...else),
encierre las instrucciones entre llaves ({ y }). A un conjunto de instrucciones contenidas dentro de un par de
llaves se le llama bloque (pág. 115). Un bloque puede colocarse en cualquier parte de un programa en donde se
pueda colocar una sola instrucción.
• Una instrucción nula (pág. 116), la cual implica que no puede realizarse ninguna acción, se indica mediante un
punto y coma (;).
Resumen 145
Sección 4.7 Instrucción de repetición while
• Una instrucción de repetición (pág. 116) repite una acción mientras cierta condición sea verdadera.
• En un símbolo de fusión de UML (pág. 117) hay dos o más flechas de transición que apuntan al rombo y
sólo una que sale de él, para indicar que se fusionan múltiples flujos de actividad para continuar con la
actividad.
Sección 4.8 Cómo formular algoritmos: repetición controlada por un contador
• La repetición controlada por un contador (pág. 118) se utiliza cuando se conoce el número de repeticiones
antes de que un ciclo empiece a ejecutarse; es decir, cuando hay una repetición definida.
• Una suma de enteros puede producir un valor que sea demasiado grande como para almacenarlo en una variable
int. A esto se le conoce como desbordamiento aritmético y provoca un comportamiento impredecible en tiem-
po de ejecución.
• Los valores máximo y mínimo que pueden almacenarse en una variable int se representan mediante las cons-
tantes INT_MAX e INT_MIN, respectivamente, del encabezado climits.
• Se considera una buena práctica asegurar que los cálculos aritméticos no se desborden antes de realizarlos. En
código de uso industrial, hay que realizar revisiones para todos los cálculos que puedan producir un desborda-
miento o subdesbordamiento.
Sección 4.9 Cómo formular algoritmos: repetición controlada por un centinela
• El proceso de mejoramiento de arriba a abajo, paso a paso (pág. 125) es un proceso para refinar el seudocódigo,
manteniendo una representación completa del programa durante cada mejoramiento.
• La repetición controlada por un centinela (pág. 126) se utiliza cuando no se conoce el número de repeticiones
antes de que un ciclo se empiece a ejecutar; es decir, cuando hay repetición indefinida.
• Un valor que contiene una parte fraccionaria se conoce como número de punto flotante y se representa de
manera aproximada mediante tipos de datos como float y double (pág. 127).
• El operador de conversión de tipos static_castdouble (pág. 132) se puede utilizar para crear una copia
temporal de punto flotante del operando.
• Los operadores unarios (pág. 133) sólo reciben un operando; los operadores binarios reciben dos.
• El manipulador de flujo parametrizado setprecision (pág. 133) indica el número de dígitos de precisión que
deben mostrarse a la derecha del punto decimal.
• El manipulador de flujo fixed (pág. 133) indica que los valores de punto flotante se deben imprimir en lo que
se denomina formato de punto fijo, en oposición a la notación científica.
• Engeneral,cualquiervariableenteraquedebaalmacenarsólovaloresnonegativosdebedeclararsecon unsigned
antes del tipo entero. Las variables de tipos unsigned pueden representar valores de 0 hasta aproximadamente
el doble del rango positivo del tipo entero con signo correspondiente.
• Puede determinar el valor unsigned int máximo de su plataforma con la constante UINT_MAX de climits.
Sección 4.10 Cómo formular algoritmos: instrucciones de control anidadas
• Una instrucción de control anidada (pág. 134) aparece en el cuerpo de otra instrucción de control.
• C++11 introduce la nueva inicialización de listas para inicializar variables en sus declaraciones, como en:
int contadorEstudiantes = { 1 };
o
int contadorEstudiantes{ 1 };
• Las llaves ({ y }) representan el inicializador de listas. Para una variable de tipo fundamental, sólo se coloca un
valor en el inicializador de listas. Para un objeto, el inicializador de listas puede ser una lista de valores separada
por comas, que se pasan al constructor del objeto.
• Para las variables de tipo fundamental, la sintaxis de inicialización de listas también previene lo que se conoce
como conversiones de reducción, que podrían provocar pérdidas de datos.
146 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
Sección 4.11 Operadores de asignación
• Los operadores aritméticos +=, -=, *=, /= y %= abrevian las expresiones de asignación (pág. 140).
Sección 4.12 Operadores de incremento y decremento
• Los operadores de incremento (++) y de decremento (--) (pág. 140) incrementan o decrementan una variable
en 1, respectivamente. Si el operador se coloca antes de la variable, ésta se incrementa o decrementa en 1 pri-
mero, y después su nuevo valor se utiliza en la expresión en la que aparece. Si el operador se coloca después de
la variable, ésta se utiliza primero en la expresión en la que aparece, y después su valor se incrementa o decre-
menta en 1.
Ejercicios de autoevaluación
4.1 Complete los siguientes enunciados:
a) Todoslosprogramaspuedenescribirseentérminosdetrestiposdeestructurasdecontrol:__________,
__________ y __________.
b) La instrucción de selección __________ se utiliza para ejecutar una acción cuando una condición es
verdadera, y otra acción cuando esa condición es falsa.
c) Al proceso de repetir un conjunto de instrucciones un número específico de veces se le llama repetición
__________.
d) Cuando no se sabe de antemano cuántas veces se repetirá un conjunto de instrucciones, se puede usar
un valor __________ para terminar la repetición.
4.2 Escriba cuatro instrucciones distintas en C++, en donde cada una sume 1 a la variable entera x.
4.3 Escriba instrucciones en C++ para realizar cada una de las siguientes tareas:
a) En una instrucción, asignar la suma del valor actual de x y y a z, y postincrementar el valor de x.
b) Determinar si el valor de la variable cuenta es mayor que 10. De ser así, imprimir Cuenta es mayor
que 10.
c) Predecrementar la variable x en 1, luego restarla a la variable total.
d) Calcular el residuo después de dividir q entre divisor, y asignar el resultado a q. Escriba esta instruc-
ción de dos maneras distintas.
4.4 Escriba instrucciones en C++ para realizar cada una de las siguientes tareas:
a) Declarar la variable suma como de tipo unsigned int e inicializarla con 0.
b) Declarar la variable x como de tipo unsigned int e inicializarla con 1.
c) Sumar la variable x a suma y asignar el resultado a la variable suma.
d) Imprimir la cadena La suma es: , seguida del valor de la variable suma.
4.5 Combine las instrucciones que escribió en el ejercicio 4.4 para formar un programa que calcule e imprima
la suma de los enteros del 1 al 10. Use una instrucción while para iterar a través de las instrucciones de cálculo e
incremento. El ciclo debe terminar cuando el valor de x se vuelva 11.
4.6 Determine el valor de cada una de estas variables unsigned int después de realizar el cálculo. Suponga que,
cuando se empieza a ejecutar cada una de las instrucciones, todas las variables tienen el valor 5 entero.
a) producto *= x++;
b) cociente /= ++x;
4.7 Escriba instrucciones individuales de C++ o porciones de instrucciones que realicen lo siguiente:
a) Recibir como entrada la variable unsigned int x con cin y .
b) Recibir como entrada la variable unsigned int y con cin y .
c) Declarar la variable unsigned int i e inicializarla con 1.
d) Declarar la variable entera potencia e inicializarla con 1.
e) Multiplicar la variable potencia por x y asignar el resultado a potencia.
f) Preincrementar la variable i en 1.
g) Determinar si i es menor o igual a y.
h) Imprimir la variable entera potencia con cout y .
4.8 Escriba un programa en C++ que utilice las instrucciones del ejercicio 4.7 para calcular x elevada a la y
potencia. El programa debe tener una instrucción de repetición while.
Respuestas a los ejercicios de autoevaluación 147
4.9 Identifique y corrija los errores en cada uno de los siguientes fragmentos de código:
a) while ( c = 5 )
{
product *= c;
++c;
b) cin  valor;
c) if ( genero == 1 )
cout  Mujer  endl;
else;
cout  Hombre  endl;
4.10 ¿Qué está mal en la siguiente instrucción de repetición while?
while ( z = 0 )
suma += z;
Respuestas a los ejercicios de autoevaluación
4.1 a) Secuencia, selección y repetición. b) if...else c) Controlada por contador o definida.
d) Centinela, de señal, de bandera o de prueba.
4.2 x = x + 1;
x += 1;
++x;
x++;
4.3 a) z = x++ + y;
b) if ( cuenta  10 )
cout  Cuenta es mayor que 10  endl;
c) total -= --x;
d) q %= divisor;
q = q % divisor;
4.4 a) unsigned int suma = 0;
b) unsigned int x = 1;
c) suma += x;
o
suma = suma + x;
d) cout  La suma es:   suma  endl;
4.5 Vea el siguiente código:
1 // Solución al ejercicio 4.5: ej04_05.cpp
2 // Calcula la suma de los enteros del 1 al 10.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 unsigned int suma = 0; // almacena la suma de los enteros del 1 al 10
9 unsigned int x = 1; // contador
10
11 while ( x = 10 ) // itera 10 veces
12 {
13 suma += x; // suma x a suma
14 ++x; // incrementa x
15 } // fin de while
16
17 cout  La suma es:   suma  endl;
18 } // fin de main
La suma es: 55
148 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
4.6 a) producto = 25, x = 6;
b) quotient = 0, x = 6;
4.7 a) cin  x;
b) cin  y;
c) unsigned int i = 1;
d) unsigned int potencia = 1;
e) potencia *= x;
o
potencia = potencia * x;
f) ++i;
g) if ( i = y )
h) cout  potencia  endl;
4.8 Vea el siguiente código:
1 // Solución al ejercicio 4.8: ej04_08.cpp
2 // Eleva x a la y potencia.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 unsigned int i = 1; // inicializa i para comenzar a contar desde 1
9 unsigned int potencia = 1; // inicializa potencia
10
11 cout  Escriba la base como un entero: ; // pide la base
12 unsigned int x; // base
13 cin  x; // recibe la base como entrada
14
15 cout  Escriba el exponente como un entero: ; // pide el exponente
16 unsigned int y; // exponente
17 cin  y; // recibe el exponente como entrada
18
19 // cuenta de 1 a y y multiplica potencia por x cada vez
20 while ( i = y )
21 {
22 potencia *= x;
23 ++i;
24 } // fin de while
25
26 cout  potencia  endl; // muestra el resultado
27 } // fin de main
Escriba la base como un entero: 2
Escriba el exponente como un entero: 3
8
4.9 a) Error: falta la llave derecha de cierre del cuerpo de la instrucción while.
Corrección: Agregar una llave derecha de cierre después de la instrucción ++c;.
b) Error: Se utiliza inserción de flujo, en vez de extracción de flujo.
Corrección: cambie  a .
c) Error: El punto y coma después de else produce un error lógico. La segunda instrucción de salida
siempre se ejecutará.
Corrección: Quitar el punto y coma después de else.
4.10 El valor de la variable z nunca se cambia en la instrucción while. Por lo tanto, si la condición de continua-
ción de ciclo ( z = 0 ) es en un principio verdadera, se crea un ciclo infinito. Para evitar que ocurra un ciclo infi-
nito, z debe decrementarse de manera que eventualmente se vuelva menor que 0.
Ejercicios 149
Ejercicios
4.11 (Corrija los errores de código) Identifique y corrija los errores en cada uno de los siguientes fragmentos
de código:
a) if ( edad = 65 );
cout  Edad es mayor o igual que 65  endl;
else
cout  Edad es menor que 65  endl;
b) if ( edad = 65 )
cout  Edad es mayor o igual que 65  endl;
else;
cout  Edad es menor que 65  endl;
c) unsigned int x = 1;
unsigned int total;
while ( x = 10 )
{
total += x;
++x;
}
d) While ( x = 100 )
total += x;
++x;
e) while ( y  0 )
{
cout  y  endl;
++y;
}
4.12 (¿Qué hace este programa?) ¿Qué es lo que imprime el siguiente programa?
1 // Ejercicio 4.12: ej04_12.cpp
2 // ¿Qué imprime este programa?
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 unsigned int y = 0; // declara e inicializa y
9 unsigned int x = 1; // declara e inicializa x
10 unsigned int total = 0; // declara e inicializa el total
11
12 while ( x = 10 ) // itera 10 veces
13 {
14 y = x * x; // realiza el cálculo
15 cout  y  endl; // imprime el resultado
16 total += y; // suma y al total
17 ++x; // incrementa el contador x
18 } // fin de while
19
20 cout  El total es   total  endl; // muestra el resultado
21 } // fin de main
Para los ejercicios 4.13 a 4.16, realice cada uno de los siguientes pasos:
a) Lea el enunciado del problema.
b) Formule el algoritmo utilizando seudocódigo y el proceso de mejoramiento de arriba a abajo, paso a
paso.
c) Escriba un programa en C++.
d) Pruebe, depure y ejecute el programa en C++.
150 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
4.13 (Kilometraje de gasolina) Los conductores se preocupan acerca del kilometraje de sus automóviles. Un
conductor ha llevado el registro de varios viajes, anotando los kilómetros conducidos y los litros usados en ca-
da viaje. Desarrolle un programa en C++ que utilice una instrucción while para recibir como entrada los kilóme-
tros conducidos y los litros usados por cada viaje, y que imprima el total de kilómetros por litro obtenidos en todos
los reabastecimientos hasta este punto.
Escriba los kilometros usados (-1 para salir): 287
Escriba los litros: 13
KPL en este reabastecimiento: 22.076923
Total KPL: 22.076923
Escriba los kilometros usados (-1 para salir): 200
Escriba los litros: 10
KPL en este reabastecimiento: 20.000000
Total KPL: 21.173913
Escriba los kilometros usados (-1 para salir): 120
Escriba los litros: 5
KPL en este reabastecimiento: 24.000000
Total KPL: 21.678571
Escriba los kilometros usados (-1 para salir): -1
4.14 (Límites de crédito) Desarrolle una aplicación en C++ que determine si alguno de los clientes de una
tienda de departamentos se ha excedido del límite de crédito en una cuenta. Para cada cliente se tienen los siguien-
tes datos:
a) Número de cuenta (un entero)
b) Saldo al inicio del mes
c) Total de todos los artículos cargados por el cliente en el mes
d) Total de todos los créditos aplicados a la cuenta del cliente en el mes
e) Límite de crédito permitido.
El programa debe usar una instrucción while para recibir como entrada cada uno de estos datos, debe calcu-
lar el nuevo saldo (= saldo inicial + cargos – créditos) y determinar si éste excede el límite de crédito del cliente. Para
los clientes cuyo límite de crédito se ha excedido, el programa debe mostrar el número de cuenta del cliente, su lí-
mite de crédito, el nuevo saldo y el mensaje “Se excedio el limite de su credito”.
Introduzca el numero de cuenta (o -1 para salir): 100
Introduzca el saldo inicial: 5394.78
Introduzca los cargos totales: 1000.00
Introduzca los creditos totales: 500.00
Introduzca el limite de credito: 5500.00
El nuevo saldo es 5894.78
Cuenta: 100
Limite de credito: 5500.00
Saldo: 5894.78
Se excedio el limite de su credito.
Introduzca el numero de cuenta (o -1 para salir): 200
Introduzca el saldo inicial: 1000.00
Introduzca los cargos totales: 123.45
Introduzca los creditos totales: 321.00
Introduzca el limite de credito: 1500.00
El nuevo saldo es 802.45
Introduzca el numero de cuenta (o -1 para salir): -1
4.15 (Calculadora de comisiones de ventas) Una empresa grande paga a sus vendedores mediante comisiones.
Los vendedores reciben $200 por semana, más el 9% de sus ventas brutas durante esa semana. Por ejemplo, un
vendedor que vende $5000 de productos químicos en una semana, recibe $200 más el 9% de $5000, o un total de
$650. Desarrolle un programa en C++ que utilice una instrucción while para recibir como entrada las ventas
Ejercicios 151
brutas de cada vendedor de la semana anterior, y que calcule y muestre los ingresos de ese vendedor. Procese las
cifras de un vendedor a la vez.
Introduzca las ventas en dolares (-1 para salir): 5000.00
El salario es: $650.00
Introduzca las ventas en dolares (-1 para salir): 6000.00
El salario es: $740.00
Introduzca las ventas en dolares (-1 para salir): 7000.00
El salario es: $830.00
Introduzca las ventas en dolares (-1 para salir): -1
4.16 (Calculadora de salario) Desarrolle un programa en C++ que utilice una instrucción while para determi-
nar el sueldo bruto para cada uno de varios empleados. La empresa paga la “cuota normal” en las primeras 40 horas
de trabajo de cada empleado, y paga “cuota y media” en todas las horas trabajadas que excedan de 40. Usted recibe
una lista de los empleados de la empresa, el número de horas que trabajó cada empleado la semana pasada y la ta-
rifa por horas de cada empleado. Su programa debe recibir como entrada esta información para cada empleado,
debe determinar y mostrar el sueldo bruto de cada empleado.
Introduzca las horas trabajadas (-1 para salir): 39
Introduzca la tarifa por horas del empleado ($00.00): 10.00
El salario es $390.00
Introduzca las horas trabajadas (-1 para salir): 40
Introduzca la tarifa por horas del empleado ($00.00): 10.00
El salario es $400.00
Introduzca las horas trabajadas (-1 para salir): 41
Introduzca la tarifa por horas del empleado ($00.00): 10.00
El salario es $415.00
Introduzca las horas trabajadas (-1 para salir): -1
4.17 (Encontrar el más grande) El proceso de encontrar el número más grande (es decir, el máximo de un gru-
po de números) se utiliza frecuentemente en aplicaciones de computadora. Por ejemplo, un programa para deter-
minar el ganador de un concurso de ventas recibe como entrada el número de unidades vendidas por cada vende-
dor. El vendedor que haya vendido más unidades es el que gana el concurso. Escriba un programa en seudocódigo
y después una aplicación en C++ que utilice una instrucción while para determinar e imprimir el mayor número
de una serie de 10 números introducidos por el usuario. Su programa debe utilizar tres variables, como se muestra
a continuación:
contador: Un contador para contar hasta 10 (es decir, para llevar el registro de cuántos
números se han introducido, y para detectar cuando se hayan procesado
los 10 números).
numero: El número actual que se introduce al programa.
mayor: El número más grande encontrado hasta ahora.
4.18 (Salida tabular) Escriba un programa en C++ que utilice una instrucción while y la secuencia de escape
de tabulación t para imprimir la siguiente tabla de valores:
N 10*N 100*N 1000*N
1 10 100 1000
2 20 200 2000
3 30 300 3000
4 40 400 4000
5 50 500 5000
152 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
4.19 (Encontrar los dos números más grandes) Utilizando una metodología similar a la del ejercicio 4.17, en-
cuentre los dos valores más grandes de los 10 que se introdujeron. [Nota: debe introducir cada número sólo una vez].
4.20 (Validar la entrada del usuario) El programa de resultados de un examen de la figura 4.16 asume que
cualquier valor introducido por el usuario que no sea un 1 debe ser un 2. Modifique la aplicación para validar sus
entradas. Para cualquier entrada, si el valor introducido es distinto de 1 o 2, debe seguir iterando hasta que el usua-
rio introduzca un valor correcto.
4.21 (¿Qué hace este programa?) ¿Qué es lo que imprime el siguiente programa?
1 // Ejercicio 4.21: ej04_21.cpp
2 // ¿Qué es lo que imprime este programa?
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 unsigned int cuenta = 1; // inicializa cuenta
9
10 while ( cuenta = 10 ) // itera 10 veces
11 {
12 // imprime una línea de texto
13 cout  ( cuenta % 2 ? **** : ++++++++ )  endl;
14 ++cuenta; // incrementa cuenta
15 } // fin de while
16 } // fin de main
4.22 (¿Qué hace este programa?) ¿Qué es lo que imprime el siguiente programa?
1 // Ejercicio 4.22: ej04_22.cpp
2 // ¿Qué es lo que imprime este programa?
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 unsigned int fila = 10; // inicializa fila
9
10 while ( fila = 1 ) // itera hasta que fila  1
11 {
12 unsigned int columna = 1; // establece columna a 1 cuando empieza la iteración
13
14 while ( columna = 10 ) // itera 10 vecess
15 {
16 cout  ( fila % 2 ?  :  ); // salida
17 ++columna; // incrementa columna
18 } // fin de while interior
19
20 --fila; // decrementa fila
21 cout  endl; // empieza nueva línea de salida
22 } // fin de while exterior
23 } // fin de main
4.23 (Problema del else suelto) Determine la salida de cada uno de los siguientes conjuntos de código, cuando
x es 11 y y es 9. El compilador ignora la sangría en un programa en C++. El compilador de C++ siempre asocia un
else con el if anterior, a menos que se le indique de otra forma mediante la colocación de llaves ({}). A primera
vista, el programador tal vez no esté seguro de cuál if corresponde a cuál else; esta situación se conoce como el
Ejercicios 153
“problema del else suelto”. Hemos eliminado la sangría del siguiente código para hacer el problema más retador.
[Sugerencia: aplique las convenciones de sangría que aprendió].
a) if ( x  10 )
if ( y  10 )
cout  *****  endl;
else
cout  #####  endl;
cout  $$$$$  endl;
b) if ( x  10 )
{
if ( y  10 )
cout  *****  endl;
}
else
{
cout  #####  endl;
cout  $$$$$  endl;
}
4.24 (Otro problema de else suelto) Modifique el siguiente código para producir la salida que se muestra.
Utilice las técnicas de sangría apropiadas. No debe hacer modificaciones en el código, sólo insertar llaves. El com-
pilador ignora la sangría en un programa en C++. Hemos eliminado la sangría en el código dado, para hacer el
problema más retador. [Nota: es posible que no se requieran modificaciones].
if ( y == 8 )
if ( x == 5 )
cout  @@@@@  endl;
else
cout  #####  endl;
cout  $$$$$  endl;
cout    endl;
a) Suponiendo que x = 5 y y = 8, se produce la siguiente salida:
@@@@@
$$$$$

b) Suponiendo que x = 5 y y = 8, se produce la siguiente salida:
@@@@@
c) Suponiendo que x = 5 y y = 8, se produce la siguiente salida:
@@@@@

d) Suponiendo que x = 5 y y = 7, se produce la siguiente salida. [Nota: las tres últimas instrucciones de
salida después del else forman parte de un bloque].
#####
$$$$$
154 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
4.25 (Cuadrado de asteriscos) Escriba un programa que pida al usuario que introduzca el tamaño del lado de
un cuadrado y que muestre un cuadrado hueco de ese tamaño, compuesto de asteriscos y espacios en blanco. Su
programa debe funcionar con cuadrados que tengan lados de todas las longitudes entre 1 y 20. Por ejemplo, si
su programa lee un tamaño de 5, debe imprimir
*****
* *
* *
* *
*****
4.26 (Palíndromos) Un palíndromo es un número o una frase de texto que se lee igual al derecho y al revés. Por
ejemplo, cada uno de los siguientes enteros de cinco dígitos es un palíndromo: 12321, 55555, 45554 y 11611.
Escriba una aplicación que lea un entero de cinco dígitos y determine si es un palíndromo. [Sugerencia: use los
operadores de división y módulo para separar el número en sus dígitos individuales].
4.27 (Imprimir el equivalente decimal de un número binario) Escriba un programa que reciba como entrada
un entero que contenga sólo ceros y unos (es decir, un entero “binario”), y que imprima su equivalente decimal.
Use los operadores módulo y división para elegir los dígitos del número “binario” uno a la vez, de derecha a izquier-
da. En forma parecida al sistema numérico decimal, en donde el dígito más a la derecha tiene un valor posicional
de 1 y el siguiente dígito a la izquierda tiene un valor posicional de 10, después 100, después 1000, etcétera, en el
sistema numérico binario, el dígito más a la derecha tiene un valor posicional de 1, el siguiente dígito a la izquierda
tiene un valor posicional de 2, luego 4, luego 8, etcétera. Así, el número decimal 234 se puede interpretar como
2 * 100 + 3 * 10 + 4 * 1. El equivalente decimal del número binario 1101 es 1 * 1 + 0 * 2 + 1 * 4 + 1 * 8, o 1 + 0 + 4 + 8,
o 13. [Nota: para aprender más acerca de los números binarios, consulte el apéndice D].
4.28 (Patróndeajedrezdeasteriscos) Escribaunprogramaquemuestreelsiguientepatróndetablerodedamas.
Su programa debe utilizar sólo tres instrucciones de salida, una para cada una de las siguientes formas:
cout  * ;
cout  ' ';
cout  endl;
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
* * * * * * * *
4.29 (Múltiplos de 2 con un ciclo infinito) Escriba un programa que imprima las potencias del entero 2; a saber,
2, 4, 8, 16, 32, 64, etcétera. Su ciclo while no debe terminar (es decir, debe crear un ciclo infinito). Para ello, sim-
plemente use la palabra clave true como la expresión para la instrucción while. ¿Qué ocurre cuando ejecuta este
programa?
4.30 (Calcular el diámetro, la circunferencia y el área de un círculo) Escriba un programa que lea el radio de
un círculo (como un valor double), calcule e imprima el diámetro, la circunferencia y el área. Use el valor 3.14159
para .
4.31 ¿Qué está mal con la siguiente instrucción? Proporcione la instrucción correcta para realizar lo que proba-
blemente el programador trataba de hacer.
cout  ++( x + y );
Hacer la diferencia 155
4.32 (Lados de un triángulo) Escriba un programa que lea tres valores double distintos de cero, y que determi-
ne e imprima si podrían representar los lados de un triángulo.
4.33 (Lados de un triángulo recto) Escriba un programa que lea tres enteros distintos de cero, y que determine
e imprima si podrían ser los lados de un triángulo recto.
4.34 (Factorial) El factorial de un entero n no negativo se escribe como n! (n factorial) y se define de la siguien-
te manera:
n! = n · (n – 1) · (n – 2) · … · 1 (para valores de n mayores o iguales a 1)
y
n! = 1 (para n = 0 o n = 1).
Por ejemplo, 5! = 5 · 4 · 3 · 2 · 1, que es 120. Use instrucciones while en cada uno de los siguientes casos:
a) Escriba un programa que lea un entero no negativo, que calcule e imprima su factorial.
b) Escriba un programa que estime el valor de la constante matemática e, utilizando la fórmula:
1.11
1
1
1!
1
2!
1
3!
e 
= + + + +
Pida al usuario la precisión deseada de e (es decir, el número de términos en la suma).
c) Escriba una aplicación que calcule el valor de ex
, utilizando la fórmula
1
1! 2! 3!
2 3
e
x x x
x

= + + + +
Pida al usuario la precisión deseada de e (es decir, el número de términos en la suma).
4.35 (Inicializadores de listas de C++) Escriba instrucciones que usen la inicialización de listas de C++ para
realizar cada una de las siguientes tareas:
a) Inicializar la variable unsigned int contadorEstudiantes con 0.
b) Inicializar la variable double saldoInicial con 1000.0
c) Inicializar un objeto de la clase Cuenta que proporcione un constructor que reciba un unsigned int,
dos string y un double para inicializar los miembros de datos numeroCuenta, primerNombre, apelli-
do y saldo.
Hacer la diferencia
4.36 (Implementar la privacidad con la criptografía) El crecimiento explosivo de las comunicaciones de Inter-
net y el almacenamiento de datos en computadoras conectadas a Internet, ha incrementado de manera considera-
ble los problemas de privacidad. El campo de la criptografía se dedica a la codificación de datos para dificultar
(y, mediante los esquemas más avanzados, tratar de imposibilitar) su lectura a los usuarios no autorizados. En este
ejercicio, usted investigará un esquema simple para cifrar y descifrar datos. Una compañía que desea enviar datos
porInternetlepidióqueescribieraunprogramaqueloscifre,demodoquesepuedantransmitirconmásseguridad.
Todos los datos se transmiten como enteros de cuatro dígitos. Su aplicación debe leer un entero de cuatro dígitos
introducido por el usuario, y cifrarlo de la siguiente manera: Reemplace cada dígito con el resultado de sumarle 7
y obtenga el residuo después de dividir el nuevo valor entre 10. Después intercambie el primer dígito con el terce-
ro, y el segundo dígito con el cuarto. Luego imprima el entero cifrado. Escriba una aplicación separada que reciba
como entrada el número entero de cuatro dígitos cifrado y lo descifre (invirtiendo el esquema de cifrado) para
formar el número original. [Proyecto de lectura opcional: investigue la “criptografía de clave pública” en general y el
esquemadeclavepúblicaespecíficoPGP(Privacidadbastantebuena).Talveztambiénquierainvestigarelesquema
RSA, que se utiliza mucho en las aplicaciones de nivel industrial].
4.37 (Crecimiento de la población mundial) La población mundial ha crecido de manera considerable a través
de los siglos. El crecimiento continuo podría, en un momento dado, desafiar los límites del aire respirable, el agua
potable, la tierra cultivable y otros recursos limitados. Hay evidencia de que el crecimiento se ha reducido en años
156 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y ––
recientes, y que la población mundial podría llegar a su valor máximo en algún momento de este siglo, para luego
empezar a disminuir.
Para este ejercicio, investigue en línea las cuestiones sobre el crecimiento de la población mundial. Asegúrese
de investigar varios puntos de vista. Obtenga estimaciones de la población mundial actual y su tasa de crecimiento
(el porcentaje por el cual es probable que aumente este año). Escriba un programa que calcule el crecimiento anual
de la población mundial durante los siguientes 75 años, utilizando la suposición simplificada de que la tasa de creci-
miento actual permanecerá constante. Imprima los resultados en una tabla. La primera columna debe mostrar el año,
desde el año 1 hasta el año 75. La segunda columna debe mostrar la población mundial anticipada al final de ese
año. La tercera columna deberá mostrar el aumento numérico en la población mundial que ocurriría ese año. Use
sus resultados para determinar el año en el que el tamaño de la población será del doble del actual, si fuera a persis-
tir la tasa de crecimiento de este año.
Instrucciones de control, parte 2:
operadores lógicos 5
¿Quién puede controlar
su destino?
—William Shakespeare
La llave usada siempre brilla.
—Benjamín Franklin
O b j e t i v o s
En este capítulo aprenderá a:
n Conocer los fundamentos
de la repetición controlada
por un contador.
n Usar las instrucciones de
repetición for y do...while
para ejecutar instrucciones
repetidas veces en un
programa.
n Implementar la selección
múltiple mediante el uso
de la instrucción de selección
switch.
n Usar break y continue para
alterar el flujo de control.
n Usar los operadores lógicos
para formar expresiones
condicionales complejas en
las instrucciones de control.
n Evitar las consecuencias de
confundir los operadores
de igualdad y de asignación.
158 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
5.1Introducción
En este capítulo, continuaremos nuestra presentación de la programación estructurada, presentando el
resto de las instrucciones de control en C++. Las instrucciones de control que estudiaremos aquí y las
que vimos en el capítulo 4 son útiles para crear y manipular objetos. Continuaremos con nuestro énfa-
sis anticipado sobre la programación orientada a objetos, que empezó con una discusión de los concep-
tos básicos en el capítulo 1, además de muchos ejemplos y ejercicios de código orientado a objetos en
los capítulos 3 y 4.
En este capítulo demostraremos las instrucciones for, do...while y switch. A través de una serie
de ejemplos cortos en los que utilizaremos las instrucciones while y for, exploraremos la repetición
controlada por contador. Expandiremos la clase LibroCalificaciones que utiliza una instrucción
switch para contar el número de calificaciones equivalentes de A, B, C, D y F, en un conjunto de cali-
ficaciones numéricas introducidas por el usuario. Presentaremos las instrucciones de control de progra-
ma break y continue. Hablaremos sobre los operadores lógicos, que nos permiten utilizar expresiones
condicionales más complejas. También examinaremos el error común de confundir los operadores de
igualdad (==) y desigualdad (=), y cómo evitarlo.
5.2Fundamentos de la repetición controlada por un contador
Esta sección utiliza la instrucción de repetición while, presentada en el capítulo 4, para formalizar los
elementos requeridos para llevar a cabo la repetición controlada por contador:
1. el nombre de una variable de control (o contador de ciclo)
2. el valor inicial de la variable de control
3. la condición de continuación de ciclo, que evalúa el valor final de la variable de control (es
decir, determina si el ciclo debe continuar o no)
4. el incremento (o decremento) con el que se modifica la variable de control cada vez que pasa
por el ciclo.
El programa simple de la figura 5.1 imprime los números del 1 al 10. La declaración en la línea 8
nombra a la variable de control (contador), la declara como unsigned int, reserva espacio para ella en
memoria y la establece a un valor inicial de 1. Las declaraciones que requieren inicialización son instruc-
ciones ejecutables. En C++, es más preciso llamar a una declaración de variable que también reserva
memoria a una definición. Como las definiciones también son declaraciones, utilizaremos el término
“declaración” excepto cuando la distinción sea importante
En la línea 13 se incrementa el contador del ciclo en 1 cada vez que se ejecuta el cuerpo del ciclo. La
condición de continuación de ciclo (línea 10) en la instrucción while determina si el valor de la variable
5.1 Introducción
5.2 Fundamentos de la repetición controlada
por un contador
5.3 Instrucción de repetición for
5.4 Ejemplos acerca del uso de la instrucción
for
5.5 Instrucción de repetición do...while
5.6 Instrucción de selección múltiple switch
5.7 Instrucciones break y continue
5.8 Operadores lógicos
5.9 Confusión entre los operadores de
igualdad (==) y de asignación (=)
5.10 Resumen de programación estructurada
5.11 Conclusión
Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios
| Hacer la diferencia
5.3 Instrucción de repetición for 159
1 // Fig. 5.1: fig05_01.cpp
2 // Repetición controlada por un contador.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 unsigned int contador = 1; // declara e inicializa la variable de control
9
10 while ( contador = 10 ) // condición de continuación de ciclo
11 {
12 cout  contador   ;
13 ++contador; // incrementa la variable de control en 1
14 } // fin de while
15
16 cout  endl; // imprime una nueva línea
17 } // fin de main
1 2 3 4 5 6 7 8 9 10
Fig. 5.1  Repetición controlada por contador.
de control es menor o igual que 10 (el valor final para el que la condición es true). El cuerpo de este
while se ejecuta, aún y cuando la variable de control sea 10. El ciclo termina cuando la variable de con-
trol es mayor a 10 (es decir, cuando contador se convierte en 11).
La figura 5.1 se puede hacer más concisa si se inicializa contador con 0 y se sustituye la instrucción
while con:
contador = 0;
while ( ++contador = 10 ) // condición de continuación de ciclo
cout  contador   ;
Este código ahorra una instrucción, ya que el incremento se realiza de manera directa en la condición
del while, antes de evaluarla. Además, el código elimina las llaves alrededor del cuerpo del while, ya que
éste ahora sólo contiene una instrucción. La codificación de tal forma condensada puede producir pro-
gramas que sean más difíciles de leer, depurar, modificar y mantener.
Tip para prevenir errores 5.1
Los valores de punto flotante son aproximados, por lo que controlar los ciclos de conteo con
variables de punto flotante puede producir valores de contador imprecisos y pruebas inco-
rrectas para la terminación. Controle los ciclos de conteo con valores enteros. Por separado,
++ y -- pueden usarse sólo con operandos enteros.
5.3Instrucción de repetición for
Además de while, C++ cuenta con la instrucción de repetición for, la cual especifica los detalles de la
repetición controlada por contador en una sola línea de código. Para ilustrar el poder del for, vamos a
modificar el programa de la figura 5.1. El resultado se muestra en la figura 5.2.
Cuando la instrucción for (líneas 10 y 11) se empieza a ejecutar, la variable de control contador se
declara e inicializa en 1. A continuación, el programa verifica la condición de continuación de ciclo
(línea 10 entre los signos de punto y coma) contador = 10. Como el valor inicial de contador es 1, la
condición se satisface y la instrucción del cuerpo (línea 11) imprime el valor de contador, que es 1.
160 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
1 // Fig. 5.2: fig05_02.cpp
2 // Repetición controlada por contador con la instrucción for.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 // el encabezado de la instrucción for incluye la inicialización
9 // la condición de continuación del ciclo y el incremento.
10 for ( unsigned int contador = 1; contador = 10; ++contador )
11 cout  contador   ;
12
13 cout  endl; // imprime una nueva línea
14 } // fin de main
1 2 3 4 5 6 7 8 9 10
Fig. 5.2  Repetición controlada por contador con la instrucción for.
Después, la expresión ++contador incrementa la variable de control contador y el ciclo empieza de
nuevo, con la prueba de continuación de ciclo. Ahora la variable de control es igual a 2, por lo que no se
excede del valor final y el programa ejecuta la instrucción del cuerpo otra vez. Este proceso continúa
hasta que el cuerpo del ciclo se haya ejecutado 10 veces y la variable de control contador se incremente
a 11, con lo cual falla la prueba de continuación de ciclo y termina la repetición. El programa continúa,
ejecutando la primera instrucción después de la instrucción for (en este caso, la instrucción de salida en
la línea 13).
Componentes del encabezado de la instrucción for
La figura 5.3 muestra un análisis más detallado del encabezado de la instrucción for (línea 10) de la fi-
gura 5.2. Observe que el encabezado de la instrucción for “se encarga de todo”: especifica cada uno de
los elementos necesarios para la repetición controlada por contador con una variable de control. Si hay
más de una instrucción en el cuerpo del for, se requieren llaves para encerrar el cuerpo del ciclo. Por lo
general, las instrucciones for se utilizan para la repetición controlada por contador y las instrucciones
while se usan para la repetición controlada por centinela.
Valor inicial de la
variable de control
Condición de
continuación de ciclo
Incremento de la
variable de control
Palabra clave
for
Nombre de
variable de control
Separador
de punto y
coma requerido
Separador de
punto y coma
requerido
Valor final de la
variable de control
para la cual la
condición es verdadera
for ( unsigned int contador = 1; contador = 10; ++contador )
Fig. 5.3  Componentes del encabezado de la instrucción for.
5.3 Instrucción de repetición for 161
Errores por desplazamiento en uno
Si usted especificara por error contador  10 como la condición de continuación de ciclo en la figura
5.2, el ciclo sólo se ejecutaría 9 veces. A este error lógico común se le conoce como error por desplaza-
miento en 1.
Error común de programación 5.1
Utilizar un operador relacional incorrecto o un valor final incorrecto de un contador de
ciclo en la condición de continuación de ciclo de una instrucción while o for puede pro-
ducir errores por desplazamiento en 1.
Buena práctica de programación 5.1
Utilizar el valor final en la condición de una instrucción while o for con el operador rela-
cional = nos ayuda a evitar los errores por desplazamiento en 1. Por ejemplo, para un ciclo
que imprime los valores del 1 al 10, la condición de continuación de ciclo debe ser contador
= 10, en vez de contador  10 (lo cual produce un error por desplazamiento en uno) o
contador  11 (que es correcto). Muchos programadores prefieren el llamado conteo con
base cero, en el cual para contar 10 veces, contador se inicializaría a cero y la prueba de
continuación de ciclo sería contador  10.
Formato general de una instrucción for
El formato general de la instrucción for es
for ( inicialización; condiciónDeContinuaciónDeCiclo; incremento )
instrucción
en donde la expresión inicialización inicializa la variable de control del ciclo, la condiciónDeContinua-
ciónDeCiclo determina si el ciclo debe seguir ejecutándose y el incremento aumenta el valor de la variable
de control. En la mayoría de los casos, la instrucción for se puede representar mediante una instrucción
while equivalente, como se muestra a continuación:
inicialización;
while ( condiciónDeContinuaciónDeCiclo )
{
instrucción
incremento;
}
Hay una excepción a esta regla, que veremos en la sección 5.7.
Si la expresión de inicialización declara la variable de control (es decir, si el tipo de la variable de
control se especifica antes del nombre de la variable), ésta puede utilizarse sólo en el cuerpo de esa ins-
trucción for; ya que sería desconocida fuera de la instrucción for. Este uso restringido del nombre de
la variable de control se conoce como el alcance de la variable. El alcance de una variable especifica en
dónde puede utilizarse en un programa. En el capítulo 6 veremos con detalle el concepto de alcance.
Listas de expresiones separadas por comas
Las expresiones inicialización e incremento pueden ser listas de expresiones separadas por comas. Las
comas, según el uso que se les da en estas expresiones, son operadores coma, los cuales garantizan que
las listas de expresiones se evalúen de izquierda a derecha. El operador coma tiene la menor precedencia
de todos los operadores de C++. El valor y tipo de una lista de expresiones separadas por comas es el valor y
tipo de la expresión que está más a la derecha. El operador coma se utiliza con frecuencia en las instruccio-
nes for. Su principal aplicación es permitir al programador utilizar varias expresiones de inicialización
162 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
y/o varias expresiones de incremento. Por ejemplo, puede haber distintas variables de control en una sola
instrucción for que deban inicializarse e incrementarse.
Buena práctica de programación 5.2
Coloque sólo expresiones que involucren a las variables de control en las secciones de inicia-
lización e incremento de una instrucción for.
Las expresiones en el encabezado de la instrucción for son opcionales
Las tres expresiones en el encabezado de la instrucción for son opcionales (pero los dos separadores de
punto y coma son obligatorios). Si se omite la condiciónDeContinuaciónDeCiclo, C++ asume que esta
condición es verdadera, con lo cual se crea un ciclo infinito. Podríamos omitir la expresión de inicializa-
ción si el programa inicializa la variable de control antes del ciclo. Podríamos omitir la expresión de in-
cremento si el programa calcula el incremento mediante instrucciones dentro del cuerpo del for, o si no
se necesita un incremento.
La expresión de incremento actúa como una instrucción independiente
La expresión de incremento en una instrucción for actúa como si fuera una instrucción independiente
al final del cuerpo de la instrucción for. Por lo tanto, para contadores enteros, las expresiones
contador = contador + 1
contadorr += 1
++contador
contador++
son todas equivalentes en la expresión de incremento (cuando no aparece ningún otro código ahí). La
variable que se incrementa aquí no aparece en una expresión más grande, por lo que los operadores de
preincremento y postdecremento tienen en realidad el mismo efecto.
Error común de programación 5.2
Al colocar un punto y coma justo a la derecha del paréntesis derecho del encabezado de un
for, el cuerpo de esa instrucción for se convierte en una instrucción vacía. Por lo general,
esto es un error lógico.
Instrucción for: notas y observaciones
Las expresiones de inicialización, condición de continuación de ciclo e incremento de una instrucción
for pueden contener expresiones aritméticas. Por ejemplo, si x = 2 y y = 10, y además, x y y no se mo-
difican en el cuerpo del ciclo, el siguiente encabezado de for:
for ( unsigned int j = x; j = 4 * x * y; j += y / x )
es equivalente a la instrucción
for ( unsigned int j = 2; j = 80; j += 5 )
El “incremento” de una instrucción for también puede ser negativo, en cuyo caso sería realmente
un decremento y el ciclo contaría en orden descendente (como se muestra en la sección 5.4).
Si la condición de continuación de ciclo es inicialmente falsa, el programa no ejecutará el cuerpo de
la instrucción for, sino que la ejecución continuará con la instrucción que siga inmediatamente después
del for.
Con frecuencia, la variable de control se imprime o utiliza en cálculos dentro del cuerpo de una
instrucción for, pero este uso no es obligatorio. Por lo general, la variable de control se utiliza para
controlar la repetición sin que se le mencione dentro del cuerpo de la instrucción for.
5.4 Ejemplos acerca del uso de la instrucción for 163
Tip para prevenir errores 5.2
Aunque el valor de la variable de control puede cambiarse en el cuerpo de una instrucción
for, evite hacerlo, ya que esta práctica puede llevarlo a cometer errores sutiles.
Diagrama de actividad de UML de la instrucción for
El diagrama de actividad de UML de la instrucción for es similar al de la instrucción while (figura 4.6).
La figura 5.4 muestra el diagrama de actividad de la instrucción for de la figura 5.2. El diagrama hace
evidente que la inicialización ocurre sólo una vez antes de evaluar la condición de continuación de ciclo
por primera vez, y que el incremento ocurre cada vez que se realiza una iteración, después de que se eje-
cuta la instrucción del cuerpo. Observe que (además de un estado inicial, flechas de transición, una
fusión, un estado final y varias notas) el diagrama sólo contiene estados de acción y una decisión.
Determina si el
ciclo debe
continuar
[contador  10]
[contador = 10]
unsigned int contador = 1
++contador
Muestra en pantalla el
valor del contador
Inicializa la variable
de control
Incrementa la variable
de control
cout  counter   ;
cout  contador   ;
Fig. 5.4  Diagrama de actividad de UML para la instrucción for de la figura 5.2.
5.4Ejemplos acerca del uso de la instrucción for
Los siguientes ejemplos muestran métodos para modificar la variable de control en una instrucción for.
En cada caso, escribimos el encabezado for apropiado. Observe el cambio en el operador relacional para
los ciclos que decrementan la variable de control.
a) Modificar la variable de control de 1 a 100 en incrementos de 1.
for ( unsigned int i = 1; i = 100; ++i )
b) Modificar la variable de control de 100 a 0 en decrementos de 1. Cabe mencionar que usamos
el tipo int para la variable de control en este encabezado de for. La condición no se vuelve
164 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
falsa sino hasta que la variable de control contenga -1, por lo que ésta debe ser capaz de alma-
cenar números tanto positivos como negativos.
for ( int i = 100; i = 0; --i )
c) Modificar la variable de control de 7 a 77 en incrementos de 7.
for ( unsigned int i = 7; i = 77; i += 7 )
d) Modificar la variable de control de 20 a 2 en incrementos de -2.
for ( unsigned int i = 20; i = 2; i -= 2 )
e) Modificar la variable de control con la siguiente secuencia de valores: 2, 5, 8, 11, 14, 17.
for ( unsigned int i = 2; i = 17; i += 3 )
f) Modificar la variable de control con la siguiente secuencia de valores: 99, 88, 77, 66, 55.
for ( unsigned int i = 99; i = 55; i -= 11 )
Error común de programación 5.3
No utilizar el operador relacional apropiado en la condición de continuación de un ciclo
que cuente en forma regresiva (como usar incorrectamente i = 1 en vez de i = 1 en un
ciclo que cuente en forma regresiva hasta llegar a 1) es generalmente un error lógico que
produce resultados incorrectos al momento de ejecutar el programa.
Error común de programación 5.4
No use operadores de igualdad (!= o ==) en una condición de continuación de ciclo si la
variable de control del ciclo se incrementa o decrementa por más de 1. Por ejemplo, consi-
dere el encabezado de la instrucción for ( unsigned int contador = 1; contador !=
10; contador += 2 ). La prueba de continuación de ciclo contador != 10; nunca se
volverá falsa (lo que produce un ciclo infinito) debido a que contador se incrementa por 2
después de cada iteración.
Aplicación: sumar los enteros pares del 2 al 20
El programa de la figura 5.5 utiliza una instrucción for para sumar los enteros pares del 2 al 20. Cada
iteración del ciclo (líneas 11 y 12) suma el valor de la variable de control numero a la variable total.
1 // Fig. 5.5: fig05_05.cpp
2 // Suma de enteros con la instrucción for.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 unsigned int total = 0; // inicializa el total
9
10 // obtiene el total de los enteros pares del 2 al 20
11 for ( unsigned int numero = 2; numero = 20; numero += 2 )
12 total += numero;
13
14 cout  La suma es   total  endl; // muestra los resultados
15 } // fin de main
Fig. 5.5  Suma de enteros con la instrucción for (parte 1 de 2).
5.4 Ejemplos acerca del uso de la instrucción for 165
La suma es 110
El cuerpo de la instrucción for de la figura 5.5 podría mezclarse con la porción del incremento del
encabezado for mediante el uso del operador coma, como se muestra a continuación:
for ( unsigned int numero = 2; // inicialización
numero = 20; // condición de continuación de ciclo
total += numero, numero += 2 ) // calcula el total e incrementa
; // cuerpo vacío
Buena práctica de programación 5.3
Aunque las instrucciones antes de un for y las instrucciones en el cuerpo de un for común-
mente se pueden fusionar en el encabezado del for, esto podría hacer que el programa fuera
más difícil de leer, mantener, modificar y depurar.
Aplicación: cálculo del interés compuesto
Considere el siguiente enunciado del problema:
Una persona invierte $1000.00 en una cuenta de ahorro que produce el 5 por ciento de interés.
Suponiendo que todo el interés se deposita en la cuenta, calcule e imprima el monto de dinero en la
cuenta al final de cada año, durante 10 años. Use la siguiente fórmula para determinar los montos:
c = p ( 1 + r )n
en donde
p es el monto que se invirtió originalmente (es decir, el monto principal)
t es la tasa de interés anual
n es el número de años y
c es la cantidad depositada al final del nésimo
año.
La instrucción for (figura 5.6, líneas 21 a 28) ejecuta el cálculo indicado por cada uno de los 10 años
que el dinero permanece en depósito, variando una variable de control de 1 a 10, en incrementos de 1.
C++ no incluye un operador de exponenciación, por lo que utilizamos la función pow de la biblioteca
estándar (línea 24). La función pow(x, y) calcula el valor de x elevado a la yésima
potencia. En este ejem-
plo, la expresión algebraica (1 + r)n
se escribe como pow(1.0 + tasa, anio), en donde la variable tasa
representa a r y la variable anio representa a n. La función pow recibe dos argumentos de tipo double y
devuelve un valor double.
1 // Fig. 5.6: fig05_06.cpp
2 // Cálculo del interés compuesto con for.
3 #include iostream
4 #include iomanip
5 #include cmath // biblioteca de matemáticas estándar
6 using namespace std;
7
8 int main()
9 {
Fig. 5.5  Suma de enteros con la instrucción for (parte 2 de 2).
Fig. 5.6  Cálculo del interés compuesto con for (parte 1 de 2).
166 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
10 double monto; // monto a depositar al final de cada año
11 double principal = 1000.0; // monto inicial antes del interés
12 double tasa = .05; // atasa de interés anual
13
14 // muestra los encabezados
15 cout  Anio  setw( 21 )  Monto en deposito  endl;
16
17 // establece el formato de número de punto flotante
18 cout  fixed  setprecision( 2 );
19
20 // calcula el monto en depósito para cada uno de los diez años
21 for ( unsigned int anio = 1; anio = 10; ++anio )
22 {
23 // calcula el nuevo monto para el año especificado
24 monto = principal * pow( 1.0 + tasa, anio );
25
26 // muestra el año y el monto
27 cout  setw( 4 )  anio  setw( 21 )  monto  endl;
28 } // fin de for
29 } // fin de main
Anio Monto en deposito
1 1050.00
2 1102.50
3 1157.63
4 1215.51
5 1276.28
6 1340.10
7 1407.10
8 1477.46
9 1551.33
10 1628.89
Este programa no se compilará si no se incluye el encabezado cmath (línea 5). La función pow
requiere dos argumentos double. La variable anio es un entero. El encabezado cmath incluye infor-
mación que indica al compilador cómo convertir el valor de anio en una representación double tempo-
ral antes de llamar a la función. Esta información se incluye en el prototipo de la función pow. En el
capítulo 6 veremos un resumen de las demás funciones matemáticas de la biblioteca.
Error común de programación 5.5
Olvidar incluir el encabezado apropiado al utilizar funciones de la biblioteca estándar
(por ejemplo, cmath en un programa que utilice funciones matemáticas de la biblioteca)
es un error de compilación.
Una advertencia en relación con el uso de los tipos float o double
para cantidades monetarias
En las líneas 10 a 12 se declaran las variables double llamadas monto, principal y tasa. Hicimos
esto para simplificar, ya que estamos tratando con partes fraccionarias de dólares, y necesitamos un
tipo que permita puntos decimales en sus valores. Por desgracia, esto puede ocasionar problemas.
He aquí una explicación simple de lo que puede salir mal al utilizar float o double para represen-
tar montos en dólares (asumiendo que se utiliza setprecision(2) para especificar dos dígitos de
Fig. 5.6  Cálculo del interés compuesto con for (parte 2 de 2).
5.4 Ejemplos acerca del uso de la instrucción for 167
precisión a la hora de imprimir): dos montos en dólares almacenados en el equipo podrían ser
14.234 (que se imprime como 14.23) y 18.673 (que se imprime como 18.67). Al sumar estos
montos, producen la suma interna 32.907, la cual se imprime como 32.91. Por ende, el resultado
podría aparecer como
14.23
+ 18.67
-------
32.91
pero ¡alguien que sumara los números individuales que aparecen impresos esperaría la suma de 32.90!
¡Ya ha sido advertido! En los ejercicios exploramos el uso de enteros para realizar cálculos monetarios.
[Nota: algunos distribuidores independientes venden bibliotecas de clases de C++ que realizan cálculos
monetarios precisos].
Uso de manipuladores de flujo para dar formato a los resultados numéricos
La instrucción de salida en la línea 18 antes del ciclo for, y la instrucción de salida en la línea 27 en el
ciclo for se combinan para imprimir los valores de las variables anio y monto, con el formato especifi-
cado por los manipuladores de flujo parametrizados setprecision y setw, y por el manipulador de
flujo no parametrizado fixed. El manipulador de flujo setw(4) especifica que el siguiente valor a im-
primir debe aparecer en una anchura de campo de 4; por ejemplo, cout imprime el valor con al menos
4 posiciones de caracteres. Si el valor a imprimir es menor que 4 caracteres de ancho, de manera prede-
terminada se imprime justificado a la derecha. Si el valor a imprimir es mayor que 4 caracteres, la an-
chura de campo se extiende a la derecha para dar cabida a todo el valor. Para indicar que los valores deben
imprimirse justificados a la izquierda, simplemente imprima el manipulador de flujo no parametri-
zado left (que se encuentra en el encabezado iostream). La justificación a la derecha se puede res-
taurar al imprimir el manipulador de flujo no parametrizado right.
El otro formato en las instrucciones de salida indica que la variable monto se imprime como un valor
de punto fijo con un punto decimal (especificado en la línea 18 con el manipulador de flujo fixed),
justificado a la derecha en un campo de 21 posiciones de caracteres (especificado en la línea 27 con
setw(21)) y dos dígitos de precisión a la derecha del punto decimal (especificado en la línea 18 con el
manipulador setprecision(2)). Aplicamos los manipuladores de flujo fixed y setprecision al flujo
de salida (es decir, cout) antes del ciclo for, ya que estas opciones de formato están en vigor hasta que se
modifican; a dichas opciones se les conoce como opciones pegajosas y no necesitan aplicarse durante
cada iteración del ciclo. Sin embargo, la anchura de campo especificada con setw sólo se aplica al siguien-
te valor que se imprime. En el capítulo 13, Entrada/salida de flujos: un análisis detallado, hablaremos
sobre las poderosas herramientas de formato de entrada/salida de C++.
El cálculo 1.0 + tasa, que aparece como argumento para la función pow, está contenido en el
cuerpo de la instrucción for. De hecho, este cálculo produce el mismo resultado durante cada iteración
del ciclo, por lo que repetirlo es un desperdicio; debería realizarse una vez antes del ciclo.
Asegúrese de probar nuestro problema de Peter Minuit en el ejercicio 5.29. Este problema demues-
tra las maravillas del interés compuesto.
Tip de rendimiento 5.1
Evite colocar expresiones cuyos valores no cambien dentro de los ciclos. Si lo hace, mu-
chos de los compiladores optimizadores sofisticados de la actualidad colocarán de mane-
ra automática dichas expresiones fuera de los ciclos en el código de lenguaje máquina
generado.
168 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
Tip de rendimiento 5.2
Muchos compiladores contienen características de optimización que mejoran el rendi-
miento del código que el programador escribe, sin embargo, es mejor escribir buen código
desde el principio.
5.5 Instrucción de repetición do...while
La instrucción de repetición do...while es similar a la instrucción while. En la instrucción while, la
evaluación de la condición de continuación de ciclo ocurre al principio del ciclo, antes de ejecutar su
cuerpo. La instrucción do...while evalúa la condición de continuación de ciclo después de ejecutar
el cuerpo del ciclo; por lo tanto, el cuerpo del ciclo siempre se ejecutará cuando menos una vez.
La figura 5.7 utiliza una instrucción do...while para imprimir los números del 1 al 10. Al entrar
a la instrucción do...while, en la línea 12 se imprime el valor de contador y en la línea 13 se incremen-
ta contador. Después el programa evalúa la prueba de continuación de ciclo al final del mismo (línea
14). Si la condición es verdadera, el ciclo continúa a partir de la primera instrucción del cuerpo en la
instrucción do...while (línea 12). Si la condición es falsa, el ciclo termina y el programa continúa con
la siguiente instrucción después del ciclo (línea 16).
1 // Fig. 5.7: fig05_07.cpp
2 // La instrucción de repetición do...while.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 unsigned int contador = 1; // inicializa contador
9
10 do
11 {
12 cout  contador   ; // muestra contador
13 ++counter; // incrementa contador
14 } while ( contador = 10 ); // fin de do...while
15
16 cout  endl; // imprime una nueva línea
17 } // fin de main
1 2 3 4 5 6 7 8 9 10
Fig. 5.7  La instrucción de repetición do...while.
Diagrama de actividad de UML de la instrucción do...while
La figura 5.8 contiene el diagrama de actividad de UML para la instrucción do...while, el cual hace
evidente que la condición de continuación de ciclo no se evalúa sino hasta después que el ciclo ejecuta
su cuerpo, por lo menos una vez. Compare este diagrama de actividad con el de la instrucción while
(figura 4.6).
Llaves en una instrucción do...while
No es necesario utilizar llaves en la instrucción do...while si sólo hay una instrucción en el cuerpo; sin
embargo, la mayoría de los programadores incluyen las llaves para evitar la confusión entre las instruc-
ciones while y do...while. Por ejemplo:
while ( condición )
5.6 Instrucción de selección múltiple switch 169
Determina si
debe continuar
el ciclo [contador  10]
[contador = 10]
++contador
Muestra el valor
del contador
Incrementa la
variable de control
cout  contador   ;
Fig. 5.8  Diagrama de actividad de UML de la instrucción de repetición do...while de la figura 5.7.
generalmente se utiliza como encabezado de una instrucción while. Una instrucción do...while sin
llaves, alrededor de un cuerpo con una sola instrucción, aparece así:
do
instrucción
while ( condición );
lo cual puede ser confuso. Podríamos malinterpretar la última línea [while( condición );] como una
instrucción while que contiene como cuerpo una instrucción vacía. Por ello, el do...while con
una instrucción se escribe a menudo de la siguiente manera, para evitar confusión:
do
{
instrucción
} while ( condición );
5.6 Instrucción de selección múltiple switch
C++ cuenta con la instrucción switch de selección múltiple para realizar muchas acciones distintas,
con base en los posibles valores de una variable o expresión. Cada acción se asocia con un valor de una
expresión integral constante (es decir, una combinación de constantes tipo carácter y constantes en-
teras que se evalúan como un valor entero constante).
La clase LibroCalificaciones con la instrucción switch para contar las calificaciones
A, B, C, D y F
Esta siguiente versión de la clase LibroCalificaciones pide al usuario que introduzca un conjunto de
calificacionesenformadeletrasydespuésmuestraunresumendelnúmerodeestudiantesquerecibieron
170 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
cada calificación. La clase utiliza una instrucción switch para determinar si cada calificación introduci-
da es el equivalente de A, B, C, D o F, y para incrementar el contador de la calificación apropiada. La
clase LibroCalificaciones está definida en la figura 5.9, y las definiciones de sus funciones miembro
aparecen en la figura 5.10. La figura 5.11 muestra entradas y salidas de ejemplo del programa main que
utiliza la clase LibroCalificaciones para procesar un conjunto de calificaciones.
Al igual que las versiones anteriores de la definición de la clase, esta definición de LibroCalificaciones
(figura5.9)contieneprototiposdefuncionesparalasfuncionesmiembroestablecerNombreCurso (línea11),
obtenerNombreCurso (línea12)ymostrarMensaje (línea13),asícomoelconstructordelaclase(línea10).La
definicióndelaclasetambiéndeclaraelmiembrodedatosprivate llamadonombreCurso (línea17).
Encabezado de la clase LibroCalificaciones
Ahora la clase LibroCalificaciones (figura 5.9) contiene cinco miembros de datos private adiciona-
les (líneas 18 a 22): variables contador para cada categoría de calificación (A, B, C, D y F). La clase
también contiene dos funciones miembro public adicionales: recibirCalificaciones y mostrar-
ReporteCalificaciones. La función miembro recibirCalificaciones (declarada en la línea 14) lee
un número arbitrario de calificaciones de letra del usuario mediante el uso de la repetición controlada
por centinela, y actualiza el contador de calificaciones apropiado para cada calificación introducida. La
función miembro mostrarReporteCalificaciones (declarada en la línea 15) imprime un reporte que
contiene el número de estudiantes que recibieron cada calificación de letra.
1 // Fig. 5.9: LibroCalificaciones.h
2 // Definición de la clase LibroCalificaciones que cuenta calificaciones de letras.
3 // Las funciones miembro se definen en LibroCalificaciones.cpp
4 #include string // el programa usa la clase string estándar de C++
5
6 // definición de la clase LibroCalificaciones
7 class LibroCalificaciones
8 {
9 public:
10 explicit LibroCalificaciones( std::string ); // inicializa el nombre del curso
11 void establecerNombreCurso( std::string ); // establece el nombre del curso
12 std::string obtenerNombreCurso() const; // obtiene el nombre del curso
13 void mostrarMensaje() const; // muestra un mensaje de bienvenida
14 void recibirCalificaciones(); // recibe un número arbitrario de
calificaciones del usuariouser
15 void mostrarReporteCalificaciones() const; // muestra un reporte con base en
la entrada del usuario
16 private:
17 std::string nombreCurso; // nombre del curso para este LibroCalificaciones
18 unsigned int aCuenta; // cuenta de calificaciones A
19 unsigned int bCuenta; // cuenta de calificaciones B
20 unsigned int cCuenta; // cuenta de calificaciones C
21 unsigned int dCuenta; // cuenta de calificaciones D
22 unsigned int fCuenta; // cuenta de calificaciones F
23 }; // fin de la clase LibroCalificaciones
Fig. 5.9  Definición de la clase LibroCalificaciones que cuenta calificaciones de letras.
Archivo de código fuente de la clase LibroCalificaciones
El archivo de código fuente LibroCalificaciones.cpp (figura 5.10) contiene las definiciones de las
funciones miembro para la clase LibroCalificaciones. Las líneas 11 a 15 en el constructor inicializan
los cinco contadores de calificaciones con 0; cuando se crea un objeto LibroCalificaciones por pri-
mera vez, no se han introducido aún calificaciones. Estos contadores se incrementan en la función
miembro recibirCalificaciones a medida que el usuario introduce las calificaciones. Las definicio-
5.6 Instrucción de selección múltiple switch 171
nes de las funciones miembro establecerNombreCurso, obtenerNombreCurso y mostrarMensaje son
idénticas a las de las versiones anteriores de la clase LibroCalificaciones.
1 // Fig. 5.10: LibroCalificaciones.cpp
2 // Definiciones de las funciones miembro para la clase LibroCalificaciones que
3 // utiliza una instrucción switch para contar calificaciones A, B, C, D y F.
4 #include iostream
5 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
6 using namespace std;
7
8 // el constructor inicializa nombreCurso con la cadena suministrada como
9 // argumento; inicializa los miembros de datos contadores a 0
10 LibroCalificaciones::LibroCalificaciones( string nombre )
11 : aCuenta( 0 ), // inicializa cuenta de calificaciones A con 0
12 bCuenta( 0 ), // inicializa cuenta de calificaciones B con 0
13 cCuenta( 0 ), // inicializa cuenta de calificaciones C con 0
14 dCuenta( 0 ), // inicializa cuenta de calificaciones D con 0
15 fCuenta( 0 ) // inicializa cuenta de calificaciones F con 0
16 {
17 establecerNombreCurso( nombre );
18 } // fin del constructor de LibroCalificaciones
19
20 // función para establecer el nombre del curso; limita el nombre a
25 caracteres o menos
21 void LibroCalificaciones::establecerNombreCurso( string nombre )
22 {
23 if ( nombre.size() = 25 ) // si nombre tiene 25 caracteres o menos
24 nombreCurso = nombre; // almacena el nombre del curso en el objeto
25 else // si el nombre es mayor que 25 caracteres
26 { // establece nombreCurso a los primeros 25 caracteres del parámetro nombre
27 nombreCurso = nombre.substr( 0, 25 ); // selecciona los primeros 25
caracteres
28 cerr  El nombre   nombre   excede la longitud maxima (25).n
29  Se limito nombreCurso a los primeros 25 caracteres.n  endl;
30 } // fin de if...else
31 } // fin de la función establecerNombreCurso
32
33 // función para obtener el nombre del curso
34 string LibroCalificaciones::obtenerNombreCurso() const
35 {
36 return nombreCurso;
37 } // fin de la función obtenerNombreCursoe
38
39 // muestra un mensaje de bienvenida para el usuario de LibroCalificaciones
40 void LibroCalificaciones::mostrarMensaje() const
41 {
42 // esta instrucción llama a obtenerNombreCurso para obtener el
43 // nombre del curso que representa este LibroCalificaciones
44 cout  Bienvenido al libro de calificaciones paran  obtenerNombreCurso()
 !n
45  endl;
46 } // fin de la función mostrarMensaje
47
48 // recibe un número arbitrario de calificaciones del usuario; actualiza el
contador de calificaciones
49 void LibroCalificaciones::recibirCalificaciones()
50 {
Fig. 5.10  Clase LibroCalificaciones que usa la instrucción switch para contar calificaciones de letras
(parte 1 de 3).
172 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
51 int calificacion; // calificacion introducida por el usuario
52
53 cout  Escriba las calificaciones de letra.  endl
54  Escriba el caracter EOF para terminar la entrada.  endl;
55
56 // itera hasta que el usuario escriba la secuencia de fin de archivo
57 while ( ( calificacion = cin.get() ) != EOF )
58 {
59 // determina cuál calificación se introdujo
60 switch ( calificacion ) // instrucción switch anidada en el while
61 {
62 case 'A': // calificacion fue A mayúscula
63 case 'a': // o a minúscula
64 ++aCuenta; // incrementa aCuenta
65 break; // es necesario salir del switch
66
67 case 'B': // calificacion fue B mayúscula
68 case 'b': // o b minúscula
69 ++bCuenta; // incrementa bCuenta
70 break; // sale del switch
71
72 case 'C': // calificacion fue C mayúscula
73 case 'c': // o c minúscula
74 ++cCuenta; // incrementa cCuenta
75 break; // sale del switch
76
77 case 'D': // calificacion fue D mayúscula
78 case 'd': // o d minúscula
79 ++dCuenta; // incrementa dCuenta
80 break; // sale del switch
81
82 case 'F': // calificacion fue F mayúscula
83 case 'f': // o f minúscula
84 ++fCuenta; // incrementa fCuenta
85 break; // sale del switch
86
87 case 'n': // ignora caracteres de nueva línea,
88 case 't': // tabuladores,
89 case ' ': // y espacios en la entrada
90 break; // sale del switch
91
92 default: // atrapa todos los demás caracteres
93 cout  Se introdujo una letra de calificacion incorrecta.
94   Escriba una nueva calificación.  endl;
95 break; // opcional; saldrá del switch de todas formas
96 } // fin de switch
97 } // fin de while
98 } // fin de la función recibirCalificaciones
99
100 // muestra un reporte con base en las calificaciones introducidas por el usuario
101 void LibroCalificaciones::mostrarReporteCalificaciones() const
102 {
Fig. 5.10  Clase LibroCalificaciones que usa la instrucción switch para contar calificaciones de letras
(parte 2 de 3).
5.6 Instrucción de selección múltiple switch 173
103 // imprime resumen de resultados
104 cout  nnNumero de estudiantes que recibieron cada calificacion de letra:
105  nA:   aCuenta // muestra el número de calificaciones A
106  nB:   bCuenta // muestra el número de calificaciones B
107  nC:   cCuenta // muestra el número de calificaciones C
108  nD:   dCuenta // muestra el número de calificaciones D
109  nF:   fCuenta // muestra el número de calificaciones F
110  endl;
111 } // fin de la función mostrarReporteCalificaciones
Lectura de los datos de entrada tipo carácter
El usuario introduce las calificaciones de letras para un curso en la función miembro recibirCalifi-
caciones (líneas 49 a 98). En el encabezado del while, en la línea 57, la asignación entre paréntesis
( calificacion = cin.get() ) se ejecuta primero. La función cin.get() lee un carácter del teclado
y lo almacena en la variable entera calificacion (declarada en la línea 51). Por lo general, los caracteres
se almacenan en las variables de tipo char; sin embargo, los caracteres se pueden almacenar en cualquier
tipo de datos entero, ya que se garantiza que los tipos short, int, long y long long son por lo menos
tan grandes como el tipo char. Por ende, podemos tratar a un carácter como entero o como carácter,
dependiendo de su uso. Por ejemplo, la instrucción
cout  El caracter (  'a'  ) tiene el valor 
 static_cast int  ( 'a' )  endl;
imprime el carácter a y su valor entero de la siguiente manera:
El caracter (a) tiene el valor 97
El entero 97 es la representación numérica del carácter en la computadora. En el apéndice B se muestran
los caracteres y sus equivalentes decimales del conjunto de caracteres ASCII (Código estándar esta-
dounidense para el intercambio de información).
En general, las instrucciones de asignación tienen el valor que se asigna a la variable en el lado iz-
quierdo del signo =. Por ende, el valor de la expresión de asignación calificacion = cin.get( ) es el
mismo que el valor devuelto por cin.get( ) y que se asigna a la variable calificacion.
El hecho de que las expresiones de asignación tengan valores puede ser útil para asignar el mismo
valor a distintas variables. Por ejemplo,
a = b = c = 0;
evalúa primero la asignación c = 0 (ya que el operador = asocia de derecha a izquierda). Después, a la
variable b se le asigna el valor de c = 0 (que es 0). Luego, a la variable a se le asigna el valor de b = (c = 0)
(que también es 0). En el programa, el valor de calificacion = cin.get() se compara con el valor de
EOF (un símbolo cuyo acrónimo significa “fin de archivo”). Utilizamos EOF (que por lo general tiene
el valor –1) como el valor centinela. Sin embargo, no debe escribir el valor –1, ni las letras EOF, como el
valor centinela. En vez de ello, debe escribir una combinación de teclas dependiente del sistema, que repre-
sente el “fin de archivo” para indicar que no hay más datos que introducir. EOF es una constante entera
simbólica que se incluye en el programa mediante el encabezado iostream1
. Si el valor asignado a
calificacion es igual a EOF, el ciclo while (líneas 57 a 97) termina. Hemos optado por representar los
caracteres introducidos en este programa como valores int, ya que EOF tiene el tipo int.
1 Para compilar este programa, algunos compiladores requieren el encabezado cstdio que define a EOF.
Fig. 5.10  Clase LibroCalificaciones que usa la instrucción switch para contar calificaciones de letras
(parte 3 de 3).
174 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
Introducción del indicador EOF
En los sistemas OS X/Linux/UNIX y muchos otros, el fin de archivo se introduce escribiendo la se-
cuencia
Ctrl d
en una línea por sí sola. Esta notación significa que hay que oprimir al mismo tiempo la tecla Ctrl y la
tecla d. En otros sistemas como Microsoft Windows, para introducir el fin de archivo se escribe
Ctrl z
[Nota: en algunos casos, hay que oprimir Intro después de escribir la secuencia de teclas anterior. Ade-
más, generalmente se muestran los caracteres ^Z en la pantalla para representar el fin de archivo, como
se muestra en la figura 5.11].
Tip de portabilidad 5.1
Las combinaciones de teclas para introducir el fin de archivo son dependientes del sistema.
Tip de portabilidad 5.2
La acción de evaluar la constante simbólica EOF en vez de -1 hace que los programas sean
más portables. El estándar de C, del cual C++ adopta la definición de EOF, establece que
EOF es un valor integral negativo, por lo que EOF podría tener distintos valores en diferen-
tes sistemas.
En este programa, el usuario introduce las calificaciones mediante el teclado. Cuando el usuario
oprime la tecla Intro (o Return), la función cin.get() lee los caracteres, uno a la vez. Si el carácter intro-
ducido no es el fin de archivo, el flujo de control entra a la instrucción switch (figura 5.10, líneas 60 a
96), la cual incrementa el contador de calificaciones de letras apropiado.
Detalles acerca de la instrucción switch
La instrucción switch consiste en una serie de etiquetas case y un caso default opcional. Estas eti-
quetas se utilizan en este ejemplo para determinar qué contador incrementar, con base en una califica-
ción. Cuando el flujo de control llega a la instrucción switch, el programa evalúa la expresión entre
paréntesis (es decir, calificacion) que está después de la palabra clave switch (línea 60). A ésta se
le conoce como expresión de control. La instrucción switch compara el valor de la expresión de
control con cada etiqueta case. Suponga que el usuario introduce la letra C como una calificación. El
programa compara C con cada case en la instrucción switch. Si ocurre una coincidencia (case 'C' : en
la línea 72), el programa ejecuta las instrucciones para esa etiqueta case. Para la letra C, en la línea 74
se incrementa cCuenta en 1. La instrucción break (línea 75) hace que el control del programa se rea-
nude en la primera instrucción después del switch; en este programa, el control se transfiere a la línea
97. Esta línea marca el final del cuerpo del ciclo while que recibe las calificaciones (líneas 57 a 97), por
lo que el control fluye hasta la condición del while (línea 57) para determinar si el ciclo debe continuar
ejecutándose.
Las etiquetas case en nuestra instrucción switch evalúan explícitamente las versiones en mi-
núscula y mayúscula de las letras A, B, C, D y F. Observe las etiquetas case en las líneas 62 y 63, que
evalúan los valores 'A' y 'a' (ambos representan la calificación A). Al listar las etiquetas case de esta
forma consecutiva, sin instrucciones entre ellas, pueden ejecutar el mismo conjunto de instrucciones;
cuando la expresión de control se evalúe como 'A' o 'a', se ejecutarán las instrucciones de las líneas 64
y 65. Cada etiqueta case puede tener varias instrucciones. La instrucción de selección switch no requiere
llaves alrededor de varias instrucciones en cada case.
5.6 Instrucción de selección múltiple switch 175
Sin instrucciones break, cada vez que ocurra una concordancia en la instrucción switch, se ejecu-
tarán las instrucciones para esa etiqueta case y todas las etiquetas case subsiguientes, hasta llegar a una
instrucción break o al final de la instrucción switch. Esta característica es perfecta para escribir un
programa conciso, que muestre la canción iterativa “Los Doce Días de Navidad” en el ejercicio 5.28.
Error común de programación 5.6
Olvidar una instrucción break cuando se necesita una en una instrucción switch es un
error lógico.
Error común de programación 5.7
Omitir el espacio entre la palabra case y el valor integral que se está evaluando en una
instrucción switch (como escribir case3: en vez de case 3:) es un error lógico. La instruc-
ción switch no ejecutará las acciones apropiadas cuando su expresión de control tenga un
valor de 3.
Proporcionar un caso default
Si no ocurre una coincidencia entre el valor de la expresión de control y una etiqueta case, se ejecuta el
caso default (líneas 92 a 95). Utilizamos el caso default en este ejemplo para procesar todos los valores
de la expresión de control que no sean calificaciones válidas o caracteres de nueva línea, tabuladores o
espacios. Si no ocurre una coincidencia, se ejecuta el caso default y las líneas 93-94 imprimen un men-
saje de error indicando que se introdujo una letra de calificación incorrecta. Si no ocurre una coinciden-
cia en una instrucción switch que no contenga un caso default, el control del programa continúa con
la primera instrucción después de la instrucción switch.
Tip para prevenir errores 5.3
Proporcione un caso default en las instrucciones switch. Los casos que no se evalúen en
forma explícita en una instrucción switch sin un caso default deben ignorarse. Al in-
cluir un caso default, nos enfocamos en la necesidad de procesar las condiciones excepcio-
nales. Existen situaciones en las que no se necesita un procesamiento default. Aunque las
cláusulas case y la cláusula del caso default en una instrucción switch pueden ocurrir
en cualquier orden, es una práctica común colocar la cláusula default al último.
Buena práctica de programación 5.4
El último case en una instrucción switch no requiere una instrucción break. Algunos
programadores incluyen este break por claridad y por simetría con otros casos.
Ignorar los caracteres de nueva línea, tabuladores y de espacio en blanco en la entrada
Las líneas 87 a 90 en la instrucción switch de la figura 5.10 hacen que el programa omita los caracteres
de nueva línea, tabuladores y espacios en blanco. Al leer los caracteres uno a la vez, pueden producirse
ciertos problemas. Para hacer que el programa lea los caracteres, debemos enviarlos a la computadora,
oprimiendo la tecla Intro en el teclado. Con esto se coloca un carácter de nueva línea en la entrada,
después del carácter que deseamos procesar. A menudo, este carácter de nueva línea se debe procesar de
una manera especial. Al incluir las cláusulas case anteriores en nuestra instrucción switch, evitamos
que se imprima el mensaje de error en el caso default cada vez que nos encontramos un carácter de
nueva línea, tabulador o espacio.
Prueba de la clase LibroCalificaciones
En la figura 5.11 se crea un objeto LibroCalificaciones (línea 8). En la línea 10 se invoca la función
miembromostrarMensaje delobjetoparaimprimirenpantallaunmensajedebienvenidaparaelusuario.
En la línea 11 se invoca la función miembro recibirCalificaciones del objeto para leer un conjunto
de calificaciones del usuario y llevar el registro de cuántos estudiantes recibieron cada calificación. La
176 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
ventana de resultados en la figura 5.11 muestra un mensaje de error en respuesta a la acción de introducir
una calificación inválida (es decir, E). En la línea 12 se invoca la función miembro mostrarReporteCali-
ficaciones de LibroCalificaciones (definida en las líneas 101 a 111 de la figura 5.10), la cual imprime
enpantallaunreporteconbaseenlascalificacionesintroducidas(comoenlosresultadosdelafigura5.11).
1 // Fig. 5.11: fig05_11.cpp
2 // Creación de un objeto LibroCalificaciones e invocación de sus funciones
miembro.
3 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
4
5 int main()
6 {
7 // crea un objeto LibroCalificaciones
8 LibroCalificaciones miLibroCalificaciones( CS101 Programacion en C++ );
9
10 miLibroCalificaciones.mostrarMensaje(); // muestra el mensaje de bienvenida
11 miLibroCalificaciones.recibirCalificaciones(); // lee las calificaciones
del usuario
12 miLibroCalificaciones.mostrarReporteCalificaciones();
// muestra el reporte con base en las calificaciones
13 } // fin de main
Bienvenido al libro de calificaciones para
CS101 Programacion en C++!
Escriba las calificaciones de letra.
Escriba el caracter EOF para terminar la entrada.
a
B
c
C
A
d
f
C
E
Se introdujo una letra de calificacion incorrecta. Escriba una nueva calificacion.
D
A
b
^Z
Numero de estudiantes que recibieron cada calificacion de letra:
A: 3
B: 2
C: 3
D: 2
F: 1
Fig. 5.11  Creación de un objeto LibroCalificaciones e invocación de sus funciones miembro.
Diagrama de actividad de UML de la instrucción switch
La figura 5.12 muestra el diagrama de actividad de UML para la instrucción de selección múltiple
switch general.Lamayoríadelasinstruccionesswitch utilizanunainstrucciónbreak encadacase para
5.6 Instrucción de selección múltiple switch 177
terminar la instrucción switch después de procesar el case. La figura 5.12 enfatiza esto al incluir ins-
trucciones break en el diagrama de actividad. Sin la instrucción break, después de procesar un case el
control no se transferiría a la primera instrucción después de la instrucción switch. En vez de ello, se
transferiría a las acciones del siguiente case.
...
Acción(es) del caso
default
Acción(es) del
case a
Acción(es) del
case z
Acción(es) del
case b
break
break
break
case b
case z
case a
[falso]
[verdadero]
[verdadero]
[verdadero]
[falso]
[falso]
Fig. 5.12  Diagrama de actividad de UML de la instrucción switch de selección múltiple
con instrucciones break.
El diagrama hace evidente que la instrucción break al final de un case hace que el control salga de
la instrucción switch de inmediato. De nuevo, observe que (además de un estado inicial, flechas de tran-
sición, un estado final y varias notas) el diagrama contiene estados de acción y decisiones. Además, el
diagrama utiliza símbolos de fusión para fusionar las transiciones de las instrucciones break hacia el es-
tado final.
Cuando utilice la instrucción switch, recuerde que cada case sólo se puede usar para evaluar una
expresión integral constante: cualquier combinación de constantes carácter y enteras que se evalúen
como un valor entero constante. Una constante de tipo carácter se representa como el carácter específico
entre comillas sencillas, como 'A'. Una constante entera es simplemente un valor entero. Además, cada
etiqueta case puede especificar sólo una expresión integral constante.
Error común de programación 5.8
Especificar una expresión integral no constante en la etiqueta case de una instrucción
switch es un error de sintaxis.
Error común de programación 5.9
Proporcionar etiquetas case idénticas en una instrucción switch es un error de compi-
lación.
178 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
En el capítulo 12 presentaremos una manera más elegante de implementar la lógica con switch.
Utilizaremos una técnica llamada polimorfismo para crear programas que sean con frecuencia más claros,
concisos, fáciles de mantener y de extender que los programas que utilizan la lógica de switch.
Observaciones sobre los tipos de datos
C++cuentacontiposdedatosdetamañosflexibles(veaelapéndiceC,Tiposfundamentales).Porejemplo,
distintas aplicaciones podrían requerir enteros de distintos tamaños. C++ proporciona varios tipos de
enteros. El rango de valores enteros para cada tipo de datos depende de la plataforma. Además de los
tipos int y char, C++ proporciona los tipos short (una abreviación de short int), long (una abrevia-
ción de long int) y long long (una abreviación de long long int). El rango mínimo de valores para
los enteros short es de -32767 a 32767. Para la amplia mayoría de los cálculos con enteros, basta con
usarenteroslong.Elrangomínimodevaloresparalosenteroslong esde-2147483647a2147483647.
En la mayoría de las computadoras, los valores int son equivalentes a short o long. El rango de valores
para un int es por lo menos el mismo que para los enteros short, y no más grande que para los enteros
long. El tipo de datos char puede utilizarse para representar cualquiera de los caracteres en el conjunto
de caracteres de la computadora. También se puede utilizar para representar enteros pequeños.
Inicializadores dentro de la clase en C++11
C++ nos permite proporcionar un valor predeterminado para un miembro de datos al declararlo en la
declaración de la clase. Por ejemplo, las líneas 19 a 23 de la figura 5.9 podrían haber inicializado los
miembros de datos aCuenta, bCuenta, cCuenta, dCuenta y fCuenta con 0, como se muestra a conti-
nuación:
unsigned int aCuenta = 0; // cuenta de calificaciones A
unsigned int bCuenta = 0; // cuenta de calificaciones B
unsigned int cCuenta = 0; // cuenta de calificaciones C
unsigned int dCuenta = 0; // cuenta de calificaciones D
unsigned int fCuenta = 0; // cuenta de calificaciones F
en vez de inicializarlos en el constructor de la clase (figura 5.10, líneas 10 a 18). En capítulos posterio-
res seguiremos hablando sobre los inicializadores dentro de la clase y le mostraremos cómo nos permi-
ten realizar ciertas inicializaciones de miembros de datos que no eran posibles en versiones anteriores
de C++.
5.7 Instrucciones break y continue
C++ proporciona también las instrucciones break y continue para alterar el flujo de control. La sección
anterior mostró cómo se puede utilizar break para terminar la ejecución de la instrucción switch. En
esta sección veremos cómo usar break en una instrucción de repetición.
Instrucción break
Cuando la instrucción break se ejecuta en una instrucción while, for, do...while, o switch, ocasiona
la salida inmediata de esa instrucción. La ejecución del programa continúa con la siguiente instrucción.
Los usos comunes de la instrucción break son para escapar anticipadamente de un ciclo, o para omitir
el resto de una instrucción switch. La figura 5.13 demuestra el uso de una instrucción break (línea 13)
para salir de una instrucción de repetición for.
Cuando la instrucción if determina que conteo es 5, se ejecuta la instrucción break. Esto termina
la instrucción for y el programa continúa a la línea 18 (inmediatamente después de la instrucción for), la
cual muestra un mensaje indicando el valor de la variable de control cuando terminó el ciclo. La ins-
trucción for ejecuta su cuerpo por completo sólo cuatro veces en vez de 10. La variable de control
5.7 Instrucciones break y continue 179
1 // Fig. 5.13: fig05_13.cpp
2 // instrucción break para salir de una instrucción for.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 unsigned int cuenta; // la variable de control también se usa después de que
termina el ciclo
9
10 for ( cuenta = 1; cuenta = 10; ++cuenta ) // itera 10 veces
11 {
12 if ( cuenta == 5 )
13 break; // termina el ciclo sólo si cuenta es 5
14
15 cout  cuenta   ;
16 } // fin de for
17
18 cout  nSalio del ciclo en cuenta =   cuenta  endl;
19 } // fin de main
1 2 3 4
Salio del ciclo en cuenta = 5
Fig. 5.13  Instrucción break para salir de una instrucción for.
cuenta se define fuera del encabezado de la instrucción for, por lo que podemos usar la variable de
control tanto en el cuerpo del ciclo como después de que el ciclo completa su ejecución.
Instrucción continue
Cuando la instrucción continue se ejecuta en una instrucción while, for o do...while, omite las ins-
trucciones restantes en el cuerpo de esa instrucción y continúa con la siguiente iteración del ciclo. En las
instrucciones while y do...while, la prueba de continuación de ciclo se evalúa justo después de que se
ejecuta la instrucción continue. En una instrucción for se ejecuta la expresión de incremento y después
el programa evalúa la prueba de continuación de ciclo.
La figura 5.14 utiliza la instrucción continue (línea 11) en una instrucción for para omitir la
instrucción de salida (línea 13) cuando la instrucción if anidada (líneas 10 y 11) determina que el
valor de cuenta es 5. Cuando se ejecuta la instrucción continue, el control del programa continúa con
el incremento de la variable de control en el encabezado de la instrucción for (línea 8) e itera cinco
veces más.
1 // Fig. 5.14: fig05_14.cpp
2 // instrucción continue para terminar una iteración de una instrucción for.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 for ( unsigned int cuenta = 1; cuenta = 10; ++cuenta ) // itera 10 veces
9 {
Fig. 5.14  Instrucción continue para terminar una iteración de una instrucción for (parte 1 de 2).
180 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
10 if ( cuenta == 5 ) // si cuenta es 5,
11 continue; // omite el código restante en el ciclo
12
13 cout  cuenta   ;
14 } // fin de for
15
16 cout  nSe uso continue para no imprimir el 5  endl;
17 } // fin de main
1 2 3 4 6 7 8 9 10
Se uso continue para omitir imprimir 5
En la sección 5.3 declaramos que la instrucción while puede utilizarse, en la mayoría de los casos,
para representar a la instrucción for. La única excepción ocurre cuando la expresión de incremento en
la instrucción while va después de la instrucción continue. En este caso, el incremento no se ejecuta
antes de que el programa evalúe la condición de continuación de ciclo, por lo que el while no se ejecu-
ta de la misma manera que el for.
Buena práctica de programación 5.5
Algunos programadores sienten que las instrucciones break y continue violan la progra-
mación estructurada. Como pronto veremos, pueden lograrse los mismos efectos de estas
instrucciones con las técnicas de programación estructurada, por lo que estos programadores
prefieren no utilizar instrucciones break o continue. La mayoría de los programado-
res consideran aceptable el uso de break en las instrucciones switch.
Observación de Ingeniería de Software 5.1
Existe una tensión entre lograr la ingeniería de software de calidad y lograr el software con
mejor desempeño. A menudo, una de estas metas se logra a expensas de la otra. Para todas
las situaciones excepto las que demanden el mayor rendimiento, aplique la siguiente regla
empírica: primero, asegúrese de que su código sea simple y correcto; después hágalo rápido
y pequeño, pero sólo si es necesario.
5.8 Operadores lógicos
Hasta ahora sólo hemos estudiado las condiciones simples, como contador = 10, total  1000 y
numero != valorCentinela. Expresamos esas condiciones en términos de los operadores relacionales
, , = y =, y los operadores de igualdad == y !=. Cada decisión evaluó precisamente una condición.
Para evaluar varias condiciones a la hora de tomar una decisión, realizamos estas pruebas en instruccio-
nes separadas, o en instrucciones if o if...else anidadas.
C++ proporciona operadores lógicos que se utilizan para formar condiciones más complejas, al
combinar condiciones simples. Los operadores lógicos son  (AND lógico), || (OR lógico) y ! (NOT
lógico, también se conoce como negación lógica).
Operador AND lógico ()
Suponga que deseamos asegurar en cierto punto de una aplicación que dos condiciones sean ambas
verdaderas, antes de elegir cierta ruta de ejecución. En este caso, podemos utilizar el operador  (AND
lógico) de la siguiente manera:
Fig. 5.14  Instrucción continue para terminar una iteración de una instrucción for (parte 2 de 2).
5.8 Operadores lógicos 181
if ( genero == FEMENINO  edad = 65 )
++mujeresMayores;
Esta instrucción if contiene dos condiciones simples. La condición genero == FEMENINO se utiliza aquí
para determinar si una persona es de género femenino. La condición edad = 65 determina si una per-
sona es un ciudadano mayor. La condición simple a la izquierda del operador  se evalúa primero. Si es
necesario, la condición simple a la derecha del operador  se evalúa a continuación. Como veremos en
breve, el lado derecho de una expresión AND lógica se evalúa sólo si el lado izquierdo es verdadero
(true). Después, la instrucción if considera la condición combinada
genero == FEMENINO  edad = 65
Esta condición es true si, y sólo si ambas condiciones simples son true. Por último, si esta condición
combinada es evidentemente true, la instrucción en el cuerpo de la instrucción if incrementa la cuen-
ta de mujeresMayores. Si alguna de esas condiciones simples es false (o ambas lo son), el programa
omite el incremento y procede a la instrucción que va después del if. La condición combinada anterior
puede hacerse más legible si se agregan paréntesis redundantes:
( genero == FEMENINO )  ( edad = 65 )
Error común de programación 5.10
Aunque 3  x  7 es una condición matemáticamente correcta, no se evalúa como podría
esperarse en C++. Use ( 3  x  x  7 ) para obtener la evaluación apropiada en
C++..
En la figura 5.15 se sintetiza el operador . Esta tabla muestra las cuatro combinaciones posibles
de valores false y true para expresión1 y expresión2. A dichas tablas se les conoce comúnmente como
tablas de verdad. C++ evalúa todas las expresiones que incluyen operadores relacionales, de igualdad
y/o lógicos como true o false.
expresión1 expresión2 expresión1  expresión2
false false false
false true false
true false false
true true true
Fig. 5.15  Tabla de verdad del operador  (AND lógico).
Operador OR lógico (||)
Ahora considere el operador || (OR lógico). Suponga que deseamos asegurar que de dos condiciones,
una de ellas o ambas serán true antes de elegir cierta ruta de ejecución. En este caso, utilizamos el ope-
rador ||, como en el siguiente segmento de un programa:
if ( ( promedioSemestre = 90 ) || ( examenFinal = 90 ) )
cout  La calificacion del estudiante es A  endl;
Esta instrucción también contiene dos condiciones simples. La condición simple promedioSemestre
= 90 se evalúa para determinar si el estudiante merece una “A” en el curso, debido a que tuvo un sóli-
do rendimiento a lo largo del semestre. La condición simple examenFinal = 90 se evalúa para deter-
182 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
minar si el estudiante merece una “A” en el curso debido a un desempeño sobresaliente en el examen
final. Después, la instrucción if considera la condición combinada
( promedioSemestre = 90 ) || ( examenFinal = 90 )
y otorga una “A” al estudiante si una o ambas de las condiciones simples son true. El mensaje “La ca-
lificación del estudiante es A” se imprime a menos que ambas condiciones simples sean false.
La figura 5.16 es una tabla de verdad para el operador OR condicional (||).
expresión1 expresión2 expresión1 || expresión2
false false false
false true true
true false true
true true true
Fig. 5.16  Tabla de verdad del operador || (OR lógico).
El operador  tiene mayor precedencia que el operador ||. Ambos operadores se asocian de iz-
quierda a derecha. Una expresión que contiene operadores  o || se evalúa sólo si se conoce verdad o
falsedad de la expresión. Por ende, la evaluación de la expresión
( genero == FEMENINO )  ( edad = 65 )
se detiene de inmediato si genero no es igual a FEMENINO (es decir, toda la expresión es false) y continúa
si genero es igual a FEMENINO (es decir, toda la expresión podría ser aún true si la condición edad = 65
es true). Esta característica de rendimiento para la evaluación de las expresiones con AND lógico y OR
lógico se conoce como evaluación en corto circuito.
Tip de rendimiento 5.3
En las expresiones que utilizan el operador , si las condiciones separadas son independien-
tes una de otra, haga que la condición que tenga más probabilidad de ser false sea la
condición de más a la izquierda. En expresiones que utilicen el operador ||, haga que
la condición que tenga más probabilidad de ser true sea la condición de más a la izquier-
da. Este uso de la evaluación en corto circuito puede reducir el tiempo de ejecución de un
programa.
Operador lógico de negación (!)
C++ cuenta con el operador ! (NOT lógico, también conocido como negación lógica) para que un
programador pueda “invertir” el significado de una condición. El operador lógico de negación unario
sólo tiene una condición como operando. Este operador se coloca antes de una condición cuando nos
interesa elegir una ruta de ejecución si la condición original (sin el operador lógico de negación) es
false, como en el siguiente segmento de un programa:
if ( !( calificacion == valorCentinela ) )
cout  La siguiente calificacion es   calificacion  endl;
Los paréntesis alrededor de la condición calificacion == valorCentinela son necesarios, ya que el
operador lógico de negación tiene mayor precedencia que el operador de igualdad.
5.8 Operadores lógicos 183
En la mayoría de los casos, puede evitar el operador ! mediante el uso de un operador relacional o
de igualdad apropiado. Por ejemplo, la instrucción if anterior también puede escribirse de la siguiente
manera:
if ( calificacion != valorCentinela )
cout  La siguiente calificación es   calificacion  endl;
Con frecuencia, esta flexibilidad puede ayudar a un programador a expresar una condición de una ma-
nera más “natural” o conveniente. La figura 5.17 es una tabla de verdad para el operador lógico de ne-
gación (!).
expresión !expresión
false true
true false
Fig. 5.17  Tabla de verdad del
operador ! (negación lógica).
Ejemplo de los operadores lógicos
La figura 5.18 demuestra el uso de los operadores lógicos; para ello produce sus tablas de verdad. Los
resultados muestran cada expresión que se evalúa y su resultado boolean. De manera predeterminada,
los valores bool true y false se muestran mediante cout y el operador de inserción de flujo como 1 y
0, respectivamente. Utilizamos el manipulador de flujo boolalpha (un manipulador pegajoso) en la
línea 9 para especificar que el valor de cada expresión bool se debe mostrar como la palabra “true” o
la palabra “false”. Por ejemplo, el resultado de la expresión false  false en la línea 10 es false, así
que la segunda línea de salida incluye la palabra “false”. Las líneas 9 a 13 producen la tabla de verdad
para el . Las líneas 16 a 20 producen la tabla de verdad para el ||. Las líneas 23 a 25 producen la tabla
de verdad para el !.
1 // Fig. 5.18: fig05_18.cpp
2 // Operadores lógicos.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 // crea la tabla de verdad para el operador  (AND lógico)
9 cout  boolalpha  AND logico ()
10  nfalse  false:   ( false  false )
11  nfalse  true:   ( false  true )
12  ntrue  false:   ( true  false )
13  ntrue  true:   ( true  true )  nn;
14
15 // crea la tabla de verdad para el operador || (OR lógico)
16 cout  OR logico (||)
17  nfalse || false:   ( false || false )
18  nfalse || true:   ( false || true )
19  ntrue || false:   ( true || false )
20  ntrue || true:   ( true || true )  nn;
Fig. 5.18  Operadores lógicos (parte 1 de 2).
184 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
21
22 // crea la tabla de verdad para el operador ! (negación lógica)
23 cout  NOT logico (!)
24  n!false:   ( !false )
25  n!true:   ( !true )  endl;
26 } // fin de main
AND logico ()
false  false: false
false  true: false
true  false: false
true  true: true
OR logico (||)
false || false: false
false || true: true
true || false: true
true || true: true
NOT logico (!)
!false: true
!true: false
Resumen de precedencia y asociatividad de los operadores
La figura 5.19 agrega los operadores lógicos y de coma a la tabla de precedencia y asociatividad de
los operadores. Los operadores se muestran de arriba hacia abajo, en orden descendente de prece-
dencia.
Operadores Asociatividad Tipo
:: () izquierda a derecha
[Vea la precaución en la figura
2.10 con respecto al
agrupamiento de paréntesis].
primario
++ -- static_cast type () izquierda a derecha postfijo
++ -- + - ! derecha a izquierda unario (prefijo)
* / % izquierda a derecha multiplicativo
+ - izquierda a derecha aditivo
  izquierda a derecha inserción/extracción
 =  = izquierda a derecha relacional
== != izquierda a derecha igualdad
 izquierda a derecha AND lógico
|| izquierda a derecha OR lógico
?: derecha a izquierda condicional
= += -= *= /= %= derecha a izquierda asignación
, izquierda a derecha coma
Fig. 5.19  Precedencia/asociatividad de los operadores.
Fig. 5.18  Operadores lógicos (parte 2 de 2).
5.9 Confusión entre los operadores de igualdad (==) y de asignación (=) 185
5.9 Confusión entre los operadores de igualdad (==)
y de asignación (=)
Hay un tipo de error que los programadores de C++, sin importar su experiencia, tienden a cometer con
tanta frecuencia que creemos requiere una sección separada. Ese error es el de intercambiar accidental-
mente los operadores == (igualdad) y = (asignación). Lo que hace a estos intercambios tan peligrosos es
el hecho de que por lo general no producen errores sintácticos; las instrucciones con estos errores tienden
a compilarse correctamente y el programa se ejecuta hasta completarse, generando a menudo resultados
incorrectos a través de errores lógicos en tiempo de ejecución. Algunos compiladores generan una adverten-
cia cuando se utiliza = en un contexto en el que normalmente se espera ==.
Hay dos aspectos de C++ que contribuyen a estos problemas. Uno de ellos establece que cualquier
expresión que produce un valor se puede utilizar en la porción correspondiente a la decisión de cual-
quier instrucción de control. Si el valor de la expresión es cero, se trata como false, y si el valor es distin-
to de cero, se trata como true. El segundo establece que las asignaciones producen un valor; a saber, el
valor asignado a la variable del lado izquierdo del operador de asignación. Por ejemplo, suponga que
tratamos de escribir
if ( codigoPago == 4 ) // bien
cout  Obtuvo un bono!  endl;
pero accidentalmente escribimos
if ( codigoPago = 4 ) // mal
cout  Obtuvo un bono!  endl;
La primera instrucción if otorga apropiadamente un bono a la persona cuyo codigoPago sea igual a 4.
La segunda instrucción if (la del error) evalúa la expresión de asignación en la condición if a la cons-
tante 4. Cualquier valor distinto de cero se interpreta como true, por lo que esta condición siempre se
evalúa como true, ¡y la persona siempre recibe un bono sin importar cuál sea el código de pago! Aún
peor, ¡el código de pago se ha modificado, cuando se supone que sólo debía examinarse!
Error común de programación 5.11
El uso del operador == para asignación y el uso del operador = para igualdad son errores
lógicos.
Tip para prevenir errores 5.4
Por lo general, los programadores escriben condiciones como x == 7 con el nombre de la
variable a la izquierda y la constante a la derecha. Al colocar la constante a la izquierda,
como en 7 == x, estará protegido por el compilador si accidentalmente sustituye el opera-
dor == con =. El compilador trata esto como un error de compilación, ya que no se puede
modificar el valor de una constante. Esto evitará la potencial devastación de un error
lógico en tiempo de ejecución.
lvalues y rvalues
Se dice que los nombres de las variables son lvalues (por “valores a la izquierda”), ya que pueden usarse
del lado izquierdo de un operador de asignación. Se dice que las constantes son rvalues (por “valores a
la derecha”), ya que sólo se pueden usar del lado derecho de un operador de asignación. Los lvalues tam-
bién se pueden usar como rvalues, pero no al revés.
186 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
Hay otra situación que es igual de incómoda. Suponga que desea asignar un valor a una variable con
una instrucción simple, como:
x = 1;
pero en vez de ello, escribe
x == 1;
Aquí podemos ver también que esto no es un error sintáctico. En vez de ello, el compilador simplemen-
te evalúa la expresión condicional. Si x es igual a 1, la condición es true y la expresión se evalúa con el
valor true. Si x no es igual a 1, la condición es false y la expresión se evalúa con el valor false. Sin
importar el valor de la expresión, no hay operador de asignación, por lo que el valor sólo se pierde. El
valor de x permanece sin alteraciones, lo que probablemente produzca un error lógico en tiempo de
ejecución. ¡Por desgracia, no tenemos un truco disponible para ayudarlo con este problema!
Tip para prevenir errores 5.5
Use su editor de texto para buscar todas las ocurrencias de = en su programa, y compruebe
que tenga el operador de asignación u operador lógico correcto en cada lugar.
5.10 Resumen de programación estructurada
Así como los arquitectos diseñan edificios, empleando la sabiduría colectiva de su profesión, de igual
forma los programadores deben diseñar programas. Nuestro campo es mucho más joven que la arqui-
tectura, y nuestra sabiduría colectiva es mucho más escasa. Hemos aprendido que la programación es-
tructurada produce programas que son más fáciles de entender, probar, depurar, modificar que los
programas no estructurados, e incluso probar que son correctos en sentido matemático.
La figura 5.20 utiliza diagramas de actividad para sintetizar las instrucciones de control de C++. Los
estados inicial y final indican el único punto de entrada y el único punto de salida de cada instrucción
de control. Si conectamos los símbolos individuales de un diagrama de actividad en forma arbitrara,
existe la posibilidad de que se produzcan programas no estructurados. Por lo tanto, la profesión de la
programación utiliza sólo un conjunto limitado de instrucciones de control que pueden combinarse
sólo de dos formas simples, para crear programas estructurados.
Por cuestión de simpleza, sólo se utilizan instrucciones de control de una sola entrada/una sola salida;
sólo hay una forma de entrar y una de salir de cada instrucción de control. Es sencillo conectar instruc-
ciones de control en secuencia para formar programas estructurados: el estado final de una instrucción de
control se conecta al estado inicial de la siguiente instrucción de control; es decir, las instrucciones
de control se colocan una después de la otra en un programa. A esto le llamamos apilamiento de ins-
trucciones de control. Las reglas para formar programas estructurados también permiten anidar las
instrucciones de control.
La figura 5.21 muestra las reglas para formar programas estructurados. Las reglas suponen que
pueden utilizarse estados de acción para indicar cualquier acción. Además, suponen que comenzamos
con el diagrama de actividad más sencillo (figura 5.22), que consiste solamente de un estado inicial, un
estado de acción, un estado final y flechas de transición.
Al aplicar las reglas de la figura 5.21, siempre se obtiene un diagrama de actividad con una agrada-
ble apariencia de bloque de construcción. Por ejemplo, si se aplica la regla 2 repetidamente al diagrama
de actividad más sencillo, se obtiene un diagrama de actividad que contiene muchos estados de acción
en secuencia (figura 5.23). La regla 2 genera una pila de estructuras de control, por lo que la llamaremos
regla de apilamiento. Las líneas punteadas verticales en la figura 5.23 no son parte de UML; las utili-
zamos para separar los cuatro diagramas de actividad que demuestran cómo se aplica la regla 2 de la fi-
gura 5.21.
5.10 Resumen de programación estructurada 187
break
break
[v]
[v]
[f]
[f]
instrucción if...else
(selección doble)
instrucción if...else
(selección doble)
instrucción if
(selección simple)
instrucción if
(selección simple)
[v]
[v]
[f]
[f]
[v]
[v]
[f]
[f]
break
break
[v]
[v]
break
break
[v]
[v]
[f]
[f]
[f]
[f]
instrucción switch con instrucciones break
(selección múltiple)
instrucción switch con instrucciones break
(selección múltiple)
Secuencia Selección
Repetición
procesamiento default
procesamiento default
inicialización
inicialización
incremento
incremento
...
...
...
...
[v]
[v]
[f]
[f]
instrucción for
instrucción for
[v]
[v]
[f]
[f]
instrucción while
instrucción while
[v]
[v]
[f]
[f]
instrucción do...while
instrucción do...while
cuerpo
cuerpo
Fig. 5.20  Instrucciones de secuencia, selección y repetición de una sola entrada/una sola
salida de C++.
188 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
Reglas para formar programas estructurados
1) Comenzar con el “diagrama de actividad más sencillo” (figura 5.22).
2) Cualquier estado de acción puede reemplazarse por dos estados de acción en secuencia.
3) Cualquier estado de acción puede reemplazarse por cualquier instrucción de control
(secuencia, if, if...else, switch, while, do...while o for).
4) Las reglas 2 y 3 pueden aplicarse tantas veces como se desee y en cualquier orden.
Fig. 5.21  Reglas para formar programas estructurados.
estado de acción
Fig. 5.22  El diagrama de actividad más sencillo.
estado de
acción
estado de
acción
estado de
acción
estado de
acción
estado de
acción
estado de
acción estado de
acción
estado de
acción
...
aplicar
regla 2
aplicar
regla 2
aplicar
regla 2
Fig. 5.23  El resultado de aplicar la regla 2 de la figura 5.21 repetidamente al diagrama
de actividad más sencillo.
La regla 3 se conoce como la regla de anidamiento. Al aplicarla repetidamente al diagrama de ac-
tividad más sencillo, se obtiene un diagrama de actividad con instrucciones de control perfectamente
anidadas. Por ejemplo, en la figura 5.24 el estado de acción en el diagrama de actividad más sencillo se
reemplaza con una instrucción de selección doble (if...else). Luego la regla 3 se aplica otra vez a los
5.10 Resumen de programación estructurada 189
estados de acción en la instrucción de selección doble, reemplazando cada uno de estos estados con una
instrucción de selección doble. Los símbolos punteados de estado de acción alrededor de cada una de
las instrucciones de selección doble representan el estado de acción que se reemplazó en el diagrama
de actividad anterior. [Nota: las flechas punteadas y los símbolos punteados de estado de acción que se
muestran en la figura 5.24 no son parte de UML. Aquí se utilizan como dispositivos pedagógicos para
ilustrar que cualquier estado de acción puede reemplazarse con una instrucción de control].
estado de
acción
estado de
acción
estado de
acción
estado de
acción
estado de
acción
estado de
acción
aplicar
regla 3
aplicar
regla 3
aplicar
regla 3
estado de
acción
Fig. 5.24  Aplicación de la regla 3 de la figura 5.21 varias veces al diagrama de actividad
más sencillo.
La regla 4 genera instrucciones más grandes, más implicadas y más profundamente anidadas. Los
diagramas que surgen debido a la aplicación de las reglas de la figura 5.21 constituyen el conjunto de
todos los posibles diagramas de actividad estructurados y, por lo tanto, el conjunto de todos los posibles
programas estructurados. La belleza de la metodología estructurada es que utilizamos solamente siete
instrucciones de control simples de una sola entrada/una sola salida, y las ensamblamos en una de sólo
dos formas simples.
190 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
Si se siguen las reglas de la figura 5.21, no podrá crearse un diagrama de actividad con una sintaxis
ilegal(comoeldelafigura5.25).Siustednoestásegurodequeciertodiagramaseaestructurado,aplique
las reglas de la figura 5.21 en orden inverso para tratar de reducir el diagrama al diagrama de actividad
más sencillo. Si puede reducirlo, entonces el diagrama original es estructurado; de lo contrario, no es
estructurado.
estado de acción
estado de acción
estado de acción estado de acción
Fig. 5.25  Diagrama de actividad con sintaxis ilegal.
La programación estructurada promueve la simpleza. Böhm y Jacopini nos han dado el resultado
de que sólo se necesitan tres formas de control:
• Secuencia
• Selección
• Repetición
La estructura de secuencia es trivial. Simplemente enliste las instrucciones a ejecutar en el orden en el
que deben ejecutarse.
La selección se implementa en una de tres formas:
• instrucción if (selección simple)
• instrucción if...else (selección doble)
• instrucción switch (selección múltiple)
Es sencillo demostrar que la instrucción if más simple es suficiente para proporcionar cualquier
forma de selección; todo lo que pueda hacerse con las instrucciones if...else y switch puede imple-
mentarse si se combinan instrucciones if (aunque tal vez no con tanta claridad y eficiencia).
La repetición se implementa en una de tres maneras:
• instrucción while
• instrucción do...while
• instrucción for
Es sencillo demostrar que la instrucción while es suficiente para proporcionar cualquier forma de repeti-
ción.Todo lo que puede hacerse con las instrucciones do...while y for, puede hacerse también con la
instrucción while (aunque tal vez no sea tan sencillo).
Si se combinan estos resultados, se demuestra que cualquier forma de control necesaria en un pro-
grama en C++ puede expresarse en términos de:
Resumen 191
• secuencia
• instrucción if (selección)
• instrucción while (repetición)
y que estas tres instrucciones de control pueden combinarse en sólo dos formas: apilamiento y anida-
miento. Evidentemente, la programación estructurada es la esencia de la simpleza.
5.11 Conclusión
Hemoscompletadonuestraintroducciónalasinstruccionesdecontrol,lascualesnospermitencontrolar
el flujo de la ejecución en los programas. El capítulo 4 trató acerca de las instrucciones de control if,
if...else y while. En este capítulo demostramos las instrucciones de control for, do...while y
switch. Mostramos que cualquier algoritmo puede desarrollarse mediante el uso de combinaciones de la
estructuradesecuencia,lostrestiposdeinstruccionesdeselección(if,if...else yswitch)ylostrestipos
de instrucciones de repetición (while, do...while y for). Hablamos acerca de cómo puede combinar
estos bloques de construcción para utilizar las técnicas, ya probadas, de construcción de programas y solu-
ción de problemas. Aprendió a usar las instrucciones break y continue para alterar el flujo de control de
una instrucción de repetición. En este capítulo también se introdujeron los operadores lógicos, que nos
permiten utilizar expresiones condicionales más complejas en las instrucciones de control. Por último,
examinamos los errores comunes al confundir los operadores de igualdad y asignación, y proporcionamos
sugerencias para evitar estos errores. En el capítulo 6 examinaremos las funciones con más detalle.
Resumen
Sección 5.2 Fundamentos de la repetición controlada por un contador
• En C++, es más preciso llamar definición a una declaración de variable que también reserva memoria
(pág. 158).
Sección 5.3 Instrucción de repetición for
• La instrucción de repetición for (pág. 159) maneja todos los detalles de la repetición controlada por con-
tador.
• El formato general de la instrucción for es
for ( inicialización; condiciónDeContinuacionDeCiclo; incremento )
instrucción
en donde la expresión inicialización inicializa la variable de control del ciclo, condiciónDeContinuaciónDe-
Ciclo determina si el ciclo debe continuar su ejecución, e incremento incrementa o decrementa la variable de
control.
• Por lo general, las instrucciones for se utilizan para la repetición controlada por contador y las instrucciones
while para la repetición controlada por centinela.
• El alcance de una variable (pág. 161) especifica en qué parte de un programa se puede utilizar.
• El operador coma (pág. 161) tiene la menor precedencia de todos los operadores de C++. El valor y tipo de
una lista de expresiones separadas por comas es el valor y tipo de la expresión que esté más a la derecha en la
lista.
• Las expresiones de inicialización, condición de continuación de ciclo e incremento de una instrucción for
pueden contener expresiones aritméticas. Además, el incremento de una instrucción for puede ser negativo.
• Si en un principio la condición de continuación de ciclo en un encabezado for es false, no se ejecuta el cuerpo
de la instrucción for. En vez de ello, la ejecución se reanuda en la instrucción después del for.
192 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
Sección 5.4 Ejemplos acerca del uso de la instrucción for
• La función pow(x, y) de la biblioteca estándar (pág. 165) calcula el valor de x elevado a la yésima
potencia.
La función pow recibe dos argumentos de tipo double y devuelve un valor double.
• El manipulador de flujo parametrizado setw (pág. 167) especifica la anchura de campo en la que debe aparecer el
siguiente valor a imprimir, justificado a la derecha de manera predeterminada. Si el valor a imprimir es mayor
que la anchura de campo, ésta se extiende para dar cabida al valor completo. El manipulador de flujo left (pág.
167) hace que un valor se justifique a la izquierda en un campo, y right (pág. 167) se puede usar para restaurar
la justificación a la derecha.
• Las opciones pegajosas (pág. 167) son las opciones de formato que permanecen en vigor hasta que se modi-
fican.
Sección 5.5 Instrucción de repetición do…while
• La instrucción de repetición do...while evalúa la condición de continuación de ciclo al final de éste, por lo que
el cuerpo del ciclo se ejecutará al menos una vez. El formato para la instrucción do...while es
do
{
instrucción
} while ( condición );
Sección 5.6 Instrucción de selección múltiple switch
• La instrucción switch de selección múltiple (pág. 169) realiza distintas acciones, con base en el valor de su ex-
presión de control.
• La función cin.get() lee un carácter del teclado. Por lo general, los caracteres se almacenan en variables de tipo
char (pág. 173). Un carácter se puede tratar ya sea como entero o como carácter.
• Una instrucción switch consiste de una serie de etiquetas case (pág. 174) y un caso default opcional
(pág. 174).
• La expresión entre paréntesis después de la palabra clave switch se llama expresión de control (pág. 174). La
instrucción switch compara el valor de la expresión de control con cada etiqueta case.
• Las etiquetas case consecutivas, sin instrucciones entre ellas, ejecutan el mismo conjunto de instrucciones.
• Cada etiqueta case sólo puede especificar una expresión integral constante.
• Cada etiqueta case puede tener varias instrucciones. La instrucción de selección switch difiere de otras instruc-
ciones de control, en cuanto a que no requiere llaves alrededor de varias instrucciones en cada case.
• C++ proporciona varios tipos de datos para representar enteros: int, char, short y long. El rango de valores
enteros para cada tipo depende de la plataforma.
• C++11 nos permite proporcionar un valor predeterminado para un miembro de datos al declararlo en la decla-
ración de la clase.
Sección 5.7 Instrucciones break y continue
• Cuando la instrucción break (pág. 178) se ejecuta en una de las instrucciones de repetición (for, while y
do...while), provoca la salida inmediata de esa instrucción.
• Cuando la instrucción continue (pág. 179) se ejecuta en una instrucción de repetición, omite el resto de las
instrucciones en el cuerpo del ciclo y continúa con la siguiente iteración del mismo. En una instrucción while
o do...while, la ejecución continúa con la siguiente evaluación de la condición. En una instrucción for, la
ejecución continúa con la expresión de incremento en el encabezado de la instrucción for.
Sección 5.8 Operadores lógicos
• Los operadores lógicos (pág. 180) nos permiten formar condiciones complejas mediante la combinación de
condiciones simples. Los operadores lógicos son  (AND lógico), || (OR lógico) y ! (negación lógica).
• El operador  (AND lógico, pág. 180) asegura que dos condiciones sean ambas true.
Ejercicios de autoevaluación 193
• El operador || (OR lógico, pág. 181) asegura que de dos condiciones, una o ambas sean true.
• Una expresión que contenga los operadores  o || se evalúa sólo hasta que se conoce si la expresión es verda-
dera o falsa. Esta característica de rendimiento para la evaluación de las expresiones AND lógico y OR lógico se
conoce como evaluación de corto circuito (pág. 182).
• El operador ! (NOT lógico, también conocido como negación lógica; pág. 182) permite a un programador
“invertir” el significado de una condición. El operador de negación lógico unario se coloca antes de una condi-
ción, para elegir una ruta de ejecución si la condición original (sin el operador de negación lógico) es false. En
la mayoría de los casos, podemos evitar el uso de la negación lógica si expresamos la condición con un operador
relacional o de igualdad apropiado.
• Cuando se utiliza en una condición, cualquier valor distinto de cero se convierte de manera implícita en true;
0 (cero) se convierte de manera implícita en false.
• De manera predeterminada, cout muestra a los valores bool true y false como 1 y 0, respectivamente. El ma-
nipulador de flujo boolalpha (pág. 183) especifica que el valor de cada expresión bool debe mostrarse, ya sea
como la palabra “true” o la palabra “false”.
Sección 5.9 Confusión entre los operadores de igualdad (==) y de asignación (=)
• Cualquier expresión que produzca un valor se puede utilizar en la porción de decisión de cualquier instruc-
ción de control. Si el valor de la expresión es cero, se trata como false, y si es distinto de cero, se trata como
true.
• Una asignación produce un valor; a saber, el valor asignado a la variable del lado izquierdo del operador de
asignación.
Sección 5.10 Resumen de programación estructurada
• Cualquier forma de control se puede expresar en términos de instrucciones de secuencia, selección y repetición,
y éstas pueden combinarse sólo en dos formas: apilamiento y anidamiento.
Ejercicios de autoevaluación
5.1 Conteste con verdadero o falso a cada una de las siguientes proposiciones; en caso de ser falso, explique
por qué.
a) El caso default es requerido en la instrucción de selección switch.
b) La instrucción break es requerida en el caso default de una instrucción de selección switch, para salir
del switch de manera apropiada.
c) La expresión ( x  y  a  b ) es true si la expresión x  y es true, o si la expresión a  b es
true.
d) Una expresión que contiene el operador || es true si uno o ambos de sus operandos son true.
5.2 Escriba una instrucción o un conjunto de instrucciones en C++, para realizar cada una de las siguientes
tareas:
a) Sumar los enteros impares entre 1 y 99 utilizando una instrucción for. Use las variables unsigned int
de nombre suma y cuenta.
b) Imprimir el valor 333.546372 en una anchura de campo de 15 caracteres, con precisiones de 1, 2
y 3. Imprimir cada número en la misma línea. Justificar a la izquierda cada número en su campo.
¿Cuáles son los tres valores que se imprimen?
c) Calcular el valor de 2.5 elevado a la potencia de 3, utilizando la función pow. Imprimir el resultado con
una precisión de 2 en una anchura de campo de 10 posiciones. ¿Qué se imprime?
d) Imprimir los enteros del 1 al 20, utilizando un ciclo while y la variable contador unsigned int x.
Imprimir solamente cinco enteros por línea. [Sugerencia: cuando x % 5 sea 0, imprima un carácter de
nueva línea; de lo contrario, imprima un carácter de tabulación].
e) Repita el ejercicio 5.2 (d), usando una instrucción for.
194 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
5.3 Encuentre los errores en cada uno de los siguientes segmentos de código, y explique cómo
corregirlos:
a) unsigned int x = 1;
while ( x = 10 );
++x;
}
b) for ( double y = 0.1; y != 1.0; y += .1 )
cout  y  endl;
c) switch ( n )
{
case 1:
cout  El numero es 1  endl;
case 2:
cout  El numero es 2  endl;
break;
default:
cout  El número no es 1 ni 2  endl;
break;
}
d) El siguiente código debe imprimir los valores 1 a 10:
unsigned int n = 1;
while ( n  10 )
cout  n++  endl;
Respuestas a los ejercicios de autoevaluación
5.1 a) Falso. El caso default es opcional. Sin embargo, se considera buena ingeniería de software proporcio-
nar siempre un caso default.
b) Falso. La instrucción break se utiliza para salir de la instrucción switch. La instrucción break no se
requiere cuando el caso default es el último.Tampoco se requerirá la instrucción break si tiene senti-
do hacer que el control se reanude en el siguiente caso.
c) Falso. Al utilizar el operador , ambas expresiones relacionales deben ser true para que toda la expre-
sión sea true.
d) Verdadero.
5.2 a) unsigned int suma = 0;
for ( unsigned int count = 1; count = 99; count += 2 )
suma += cuenta;
b) cout  fixed  left
 setprecision( 1 )  setw( 15 )  333.546372
 setprecision( 2 )  setw( 15 )  333.546372
 setprecision( 3 )  setw( 15 )  333.546372
 endl;
La salida es:
333.5 333.55 333.546
c) cout  fixed  setprecision( 2 )  setw( 10 )  pow( 2.5, 3 )  endl;
La salida es:
15.63
d) unsigned int x = 1;
while ( x = 20 )
{
Ejercicios 195
if ( x % 5 == 0 )
cout  x  endl;
else
cout  x  't';
++x;
}
e) for ( unsigned int x = 1; x = 20; ++x )
{
if ( x % 5 == 0 )
cout  x  endl;
else
cout  x  't';
}
5.3 a) Error: El punto y coma después del encabezado while provoca un ciclo infinito.
Corrección: Reemplazar el punto y coma por una llave izquierda ({), o eliminar tanto el punto y
coma (;) como la llave derecha (}).
b) Error: Utilizar un número de punto flotante para controlar una instrucción de repetición for.
Corrección: Utilice un unsigned int y realice el cálculo apropiado para poder obtener los valores
deseados.
for ( unsigned int y = 1; y != 10; ++y )
cout  ( static_cast double ( y ) / 10 )  endl;
c) Error: Falta una instrucción break en el primer case.
Corrección: Agregue una instrucción break al final del primer case. Esto no es un error, si el programa-
dor desea que la instrucción del case 2: se ejecute siempre que lo haga la instrucción del case 1:.
d) Error: Se está utilizando un operador relacional inadecuado en la condición de continuación de ciclo.
Corrección: Use = en vez de , o cambie 10 a 11.
Ejercicios
5.4 (Encuentre los errores en el código) Encuentre el (los) error(es), si los hay, en cada uno de los siguientes
fragmentos de código:
a) For ( unsigned int x = 100, x = 1, ++x )
cout  x  endl;
b) El siguiente código debe imprimirse sin importar que el entero valor sea par o impar:
switch ( valor % 2 )
{
case 0:
cout  Entero par  endl;
case 1:
cout  Entero impar  endl;
}
c) El siguiente código debe imprimir los enteros impares del 19 al 1:
for ( unsigned int x = 19; x = 1; x += 2 )
cout  x  endl;
d) El siguiente código debe imprimir los enteros pares del 2 al 100:
unsigned int counter = 2;
do
{
cout  contador  endl;
contador += 2;
} While ( contador  100 );
196 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
5.5 (Suma de enteros) Escriba un programa que utilice una instrucción for para sumar una secuencia de en-
teros. Suponga que el primer entero leído especifica el número de valores que quedan por introducir. Su programa
debe leer sólo un valor por cada instrucción de entrada. Una secuencia típica de entrada podría ser
5 100 200 300 400 500
en donde el 5 indica que se van a sumar los 5 valores subsiguientes.
5.6 (Promedio de enteros) Escriba un programa que utilice una instrucción for para calcular e imprimir el
promedio de varios enteros. Suponga que el último valor leído es el valor centinela 9999. Por ejemplo, la secuencia
10 8 11 7 9 9999 indica que el programa debe calcular el promedio de todos los valores antes del 9999.
5.7 (¿Qué hace el siguiente programa?) ¿Qué hace el siguiente programa?
1 // Ejercicio 5.7: ex05_07.cpp
2 // ¿Qué hace este programa?
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 unsigned int x; // declara x
9 unsigned int y; // declara y
10
11 // pide al usuario los datos de entrada
12 cout  Escriba dos enteros en el rango 1 a 20: ;
13 cin  x  y; // lee valores para x y y
14
15 for ( unsigned int i = 1; i = y; ++i ) // cuenta desde 1 hasta y
16 {
17 for ( unsigned int j = 1; j = x; ++j ) // cuenta desde 1 hasta x
18 cout  '@'; // imprime @
19
20 cout  endl; // empieza nueva línea
21 } // fin de for exterior
22 } // fin de main
5.8 (Encontrar el entero más chico) Escriba un programa que utilice una instrucción for para encontrar el
menor de varios enteros. Suponga que el primer valor leído especifica el número de valores restantes.
5.9 (Producto de enteros impares) Escriba un programa que utilice una instrucción for para calcular e impri-
mir el producto de los enteros impares del 1 al 15.
5.10 (Factoriales) La función factorial se utiliza frecuentemente en los problemas de probabilidad. Utilizando
la definición de factorial del ejercicio 4.34, escriba un programa que utilice una función for para evaluar los facto-
riales de los enteros del 1 al 5. Muestre los resultados en formato tabular. ¿Qué dificultad podría impedir que usted
calculara el factorial de 20?
5.11 (Interés compuesto) Modifique el programa de interés compuesto de la sección 5.4, repitiendo sus pasos
para las tasas de interés del 5, 6, 7, 8, 9 y 10%. Use una instrucción for para variar la tasa de interés.
5.12 (Patrones de dibujo con ciclos for anidados) Escriba un programa que utilice ciclos for para imprimir
los siguientes patrones por separado, uno debajo del otro. Use ciclos for para generar los patrones. Todos los aste-
riscos (*) deben imprimirse mediante una sola instrucción de la forma cout  '*'; (esto hace que los asteriscos
se impriman uno al lado del otro). [Sugerencia: los últimos dos patrones requieren que cada línea empiece con un
número apropiado de espacios en blanco. Crédito adicional: combine su código de los cuatro problemas separados
enunsoloprogramaqueimprimaloscuatropatrones,unoalladodelotro,haciendounusointeligentedelosciclos
for anidados].
Ejercicios 197
(a) (b) (c) (d)
* ********** ********** *
** ********* ********* **
*** ******** ******** ***
**** ******* ******* ****
***** ****** ****** *****
****** ***** ***** ******
******* **** **** *******
******** *** *** ********
********* ** ** *********
********** * * **********
5.13 (Gráfico de barras) Una aplicación interesante de las computadoras es dibujar gráficos convencionales y
de barra. Escriba un programa que lea cinco números (cada uno entre 1 y 30). Suponga que el usuario sólo intro-
duce valores válidos. Por cada número leído, su programa debe imprimir una línea que contenga ese número de
asteriscos adyacentes. Por ejemplo, si su programa lee el número 7, debe mostrar *******.
5.14 (Cálculo de las ventas totales) Un almacén de pedidos por correo vende cinco productos distintos, cuyos
precios de venta son los siguientes: producto 1, $2.98; producto 2, $4.50; producto 3, $9.98; producto 4, $4.49 y
producto 5, $6.87. Escriba un programa que lea una serie de pares de números, como se muestra a continuación:
a) número del producto
b) cantidad vendida
Su programa debe utilizar una instrucción switch para determinar el precio de venta de cada producto. Debe
calcular y mostrar el valor total de venta de todos los productos vendidos. Use un ciclo controlado por centinela
para determinar cuándo debe el programa dejar de iterar para mostrar los resultados finales.
5.15 (Modificación de LibroCalificaciones) Modifique el programa LibroCalificaciones de las figuras 5.9
a 5.11, de manera que calcule el promedio de puntos de calificaciones. Una calificación A vale 4 puntos, B vale
3 puntos, etcétera.
5.16 (Cálculo del interés compuesto) Modifique el programa de la figura 5.6, de manera que se utilicen sólo
enteros para calcular el interés compuesto. [Sugerencia: trate todas las cantidades monetarias como números de
centavos. Luego “divida” el resultado en su porción de dólares y su porción de centavos, utilizando las operaciones
de división y módulo. Inserte un punto].
5.17 (¿Qué se imprime?) Suponga que i = 1, j = 2, k = 3 y m = 2. ¿Qué es lo que imprime cada una de las
siguientes instrucciones?
a) cout  ( i == 1 )  endl;
b) cout  ( j == 3 )  endl;
c) cout  ( i = 1  j  4 )  endl;
d) cout  ( m = 99  k  m )  endl;
e) cout  ( j = i || k == m )  endl;
f) cout  ( k + m  j || 3 - j = k )  endl;
g) cout  ( !m )  endl;
h) cout  ( !( j - m ) )  endl;
i) cout  ( !( k  m ) )  endl;
5.18 (Tabla de sistemas numéricos) Escriba un programa que imprima una tabla de los equivalentes binario,
octal y hexadecimal de los números decimales en el rango de 1 a 256. Si no está familiarizado con estos sistemas
numéricos, lea el apéndice D. [Sugerencia: puede usar los manipuladores de flujo dec, oct y hex para mostrar los
enteros en los formatos decimal, octal y hexadecimal, respectivamente].
5.19 Calcule el valor de π a partir de la serie infinita

= 4–
4
3
+
4
5
+
4
7
+
4
9
–
4
11
+
π
Imprima una tabla que muestre el valor aproximado de π, después de cada uno de los primeros 1000 términos de
esta serie.
198 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
5.20 (Triples de Pitágoras) Un triángulo recto puede tener lados cuyas longitudes sean valores enteros. Un
conjunto de tres valores enteros para los lados de un triángulo recto se conoce como triple de Pitágoras. Estos tres
lados deben satisfacer la relación que establece que la suma de los cuadrados de dos lados es igual al cuadrado de la
hipotenusa. Encuentre todos los triples de Pitágoras para lado1, lado2, y la hipotenusa, que no sean mayores de
500. Use un ciclo for triplemente anidado para probar todas las posibilidades. Este método es un ejemplo de la
computacióndefuerza bruta.Encursosdecienciascomputacionalesmásavanzadosaprenderáqueexistenmuchos
problemas interesantes para los cuales no hay otra metodología algorítmica conocida, sólo el uso de la fuerza bruta.
5.21 (Cálculo de salarios) Una empresa paga a sus empleados como gerentes (quienes reciben un salario sema-
nal fijo), trabajadores por horas (que reciben un sueldo fijo por hora para las primeras 40 horas que trabajen y
“tiempo y medio”: 1.5 veces su sueldo por horas, para las horas extra trabajadas), empleados por comisión (que
reciben $250 más el 5.7 por ciento de sus ventas totales por semana), o trabajadores por piezas (que reciben una
cantidad fija de dinero por cada artículo que producen; cada trabajador por piezas en esta empresa trabaja sólo en
un tipo de artículo). Escriba un programa para calcular el sueldo semanal para cada empleado. No necesita saber
cuántos empleados hay de antemano. Cada tipo de empleado tiene su propio código de pago: los gerentes tienen
el código 1, los trabajadores por horas tienen el código 2, los trabajadores por comisión tienen el código 3 y los
trabajadores por piezas tienen el código 4. Use una instrucción switch para calcular el sueldo de cada empleado,
de acuerdo con el código de pago de cada uno. Dentro del switch, pida al usuario (es decir, el cajero de nóminas)
que introduzca los hechos apropiados que su programa necesita para calcular el sueldo de cada empleado, de acuer-
do con su código de pago.
5.22 (Leyes de De Morgan) En este capítulo hemos hablado sobre los operadores lógicos , || y !. Algunas
veces, las leyes de De Morgan pueden hacer que sea más conveniente para nosotros expresar una expresión lógica.
Estas leyes establecen que la expresión !( condición1  condición2 ) es lógicamente equivalente a la expresión
( !condición1 || !condición2 ). Además, la expresión !( condición1 || condición2 ) es lógicamente equivalente a la
expresión ( !condición1  !condición2 ). Use las leyes de De Morgan para escribir expresiones equivalentes para
cada una de las siguientes expresiones, luego escriba un programa que demuestre que, tanto la expresión original
como la nueva expresión, son equivalentes en cada caso:
a) !( x  5 )  !( y = 7 )
b) !( a == b ) || !( g != 5 )
c) !( ( x = 8 )  ( y  4 ) )
d) !( ( i  4 ) || ( j = 6 ) )
5.23 (Rombo de asteriscos) Escriba un programa que imprima la siguiente figura de rombo. Puede utilizar
instrucciones de salida que impriman un solo asterisco (*), un solo espacio en blanco o un solo carácter de nue-
va línea. Maximice el uso de la repetición (con instrucciones for anidadas) y minimice el número de instruccio-
nes de salida.
*
***
*****
*******
*********
*******
*****
***
*
5.24 (Modificación del rombo de asteriscos) Modifique el programa que escribió en el ejercicio 5.23, para que
lea un número impar en el rango de 1 a 19, de manera que especifique el número de filas en el rombo y después
muestre un rombo del tamaño apropiado.
5.25 (Eliminación de break y continue) Una crítica de las instrucciones break y continue es que ninguna es
estructurada. En realidad, estas instrucciones pueden reemplazarse en todo momento por instrucciones estruc-
turadas. Describa, en general, cómo eliminaría la instrucción break de un ciclo en un programa, para reempla-
Hacer la diferencia 199
zarlas con alguna de las instrucciones estructuradas equivalentes. [Sugerencia: la instrucción break se sale de un
ciclo desde el cuerpo de éste. La otra forma de salir es que falle la prueba de continuación de ciclo. Considere
utilizar en la prueba de continuación de ciclo una segunda prueba que indique una “salida anticipada debido a
una condición de ‘interrupción’”]. Use la técnica que desarrolló aquí para eliminar la instrucción break de la
aplicación de la figura 5.13.
5.26 (¿Qué hace este código?) ¿Qué hace el siguiente segmento de programa?
1 for ( unsigned int i = 1; i = 5; ++i )
2 {
3 for ( unsigned int j = 1; j = 3; ++j )
4 {
5 for ( unsigned int k = 1; k = 4; ++k )
6 cout  ‘*’;
7
8 cout  endl;
9 } // fin del for interior
10
11 cout  endl;
12 } // fin del for exterior
5.27 (Eliminación de la instrucción continue) Describa, en general, cómo eliminaría las instrucciones conti-
nue de un ciclo en un programa, para reemplazarlas con uno de sus equivalentes estructurados. Use la técnica que
desarrolló aquí para eliminar la instrucción continue del programa de la figura 5.14.
5.28 (Canción “Los Doce Días de Navidad”) Escriba un programa que utilice instrucciones de repetición y
switch para imprimir la canción “Los Doce Días de Navidad”. Una instrucción switch debe utilizarse para impri-
mir el día (es decir, “primer”, “segundo”, etcétera). Una instrucción switch separada debe utilizarse para imprimir
el resto de cada verso. Visite el sitio Web http://www.12days.com/library/carols/ para obtener la letra comple-
ta de la canción.
5.29 (Problema de Peter Minuit) La leyenda establece que, en 1626, Peter Minuit compró la isla de Man-
hattan por $24.00 en un trueque. ¿Hizo él una buena inversión? Para responder a esta pregunta, modifique el
programa de interés compuesto de la figura 5.6, para empezar con un monto principal de $24.00 y calcular
el monto de interés en depósito, si ese dinero se había mantenido en depósito hasta este año (por ejemplo, 387 años
hasta 2013). Coloque el ciclo for que realiza el cálculo del interés compuesto en un ciclo for exterior que varíe la
tasa de interés del 5 al 10%, para observar las maravillas del interés compuesto.
Hacer la diferencia
5.30 (Examen rápido sobre hechos del calentamiento global) La controversial cuestión del calentamiento
global obtuvo una gran publicidad gracias a la película An InconvenientTruth en la que aparece el anterior vicepre-
sidente Al Gore. El señor Gore y una red de científicos de Naciones Unidas (N.U.), el Panel Intergubernamental
sobre el Cambio Climático, compartieron el Premio Nobel a la Paz de 2007 en reconocimiento por “sus esfuerzos
al generar y diseminar un mayor conocimiento sobre el cambio climatológico provocado por el hombre”. Investi-
gue ambos lados de la cuestión del calentamiento global en línea (tal vez quiera buscar frases como “escépticos del
calentamiento global”). Cree un examen rápido de opción múltiple con cinco preguntas sobre el calentamiento
global; cada pregunta debe tener cuatro posibles respuestas (enumeradas del 1 al 4). Sea objetivo y trate de repre-
sentar con imparcialidad ambos lados de la cuestión. Después escriba una aplicación que administre el examen
rápido, calcule el número de respuestas correctas (de cero a cinco) y devuelva un mensaje al usuario. Si éste respon-
de de manera correcta a las cinco preguntas, imprima el mensaje «Excelente»; si responde a cuatro, imprima «Muy
bien»; si responde a tres o menos, imprima «Es tiempo de aprender más sobre el calentamiento global», e incluya
una lista de algunos de los sitios Web en donde encontró esos hechos.
200 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos
5.31 (Alternativas para el plan fiscal: el “impuesto justo”) Existen muchas propuestas para que los impuestos
sean más justos. Consulte la iniciativa FairTax (impuestos justos) de Estados Unidos en el sitio:
www.fairtax.org/site/PageServer?pagename=calculator
Investigue cómo funciona la iniciativa FairTax que se propone. Nuestra sugerencia es eliminar los impuestos sobre
los ingresos y otros impuestos más a favor de un 23% de impuestos sobre el consumo en todos los productos y
servicios que usted compre. Algunos opositores a FairTax cuestionan la cifra del 23% y dicen que, debido a la
forma en que se calculan los impuestos, sería más preciso decir que la tasa sea del 30%; revise esto con cuidado.
Escriba un programa que pida al usuario que introduzca sus gastos en diversas categorías de gastos disponibles (por
ejemplo, alojamiento, comida, ropa, transporte, educación, servicios médicos, vacaciones) y que después imprima
el impuesto FairTax estimado que esa persona pagaría.
5.32 (Crecimiento de la base de usuarios de Facebook) Al mes de enero de 2013, hay aproximadamente 2500
millones de personas en Internet. Facebook llegó a los mil millones de usuarios en octubre de 2012. En este ejerci-
cio, escribirá un programa para determinar cuándo llegará Facebook a 2500 millones de personas con tasas de
crecimiento mensuales del 2, 3, 4 o 5%. Use las técnicas que aprendió en la figura 5.6.
Funciones y una introducción
a la recursividad 6
La forma siempre va después
de la función.
—Louis Henri Sullivan
E pluribus unum.
(Uno compuesto de varios).
—Virgilio
¡Oh! recuerdos del ayer, tratar
de regresar el tiempo.
—William Shakespeare
Respóndeme en una palabra.
—William Shakespeare
Hay un punto en el cual los
métodos se devoran a sí mismos.
—Frantz Fanon
O b j e t i v o s
En este capítulo aprenderá a:
n Crear programas en forma
modular, a partir de
funciones.
n Utilizar las funciones
matemáticas comunes de la
biblioteca.
n Conocer los mecanismos
para pasar datos a funciones
y devolver resultados.
n Comprender cómo el
mecanismo de llamadas a/
regreso de funciones está
soportado por la pila de
llamadas a funciones y los
registros de activación.
n Usar la generación de números
aleatorios para implementar
aplicaciones de juegos.
n Comprender cómo la
visibilidad de los
identificadores está limitada
a regiones específicas de
programas.
n Escribir y usar funciones
recursivas.
202 Capítulo 6 Funciones y una introducción a la recursividad
6.1Introducción
La mayoría de los programas de computadora que resuelven problemas del mundo real son mucho más
grandes que los programas que se presentan en los primeros capítulos de este libro. La experiencia ha
demostrado que la mejor forma de desarrollar y mantener un programa extenso es construirlo a partir de
piezas (o componentes) simples y pequeñas. A esta técnica se le conoce como divide y vencerás.
Veremos las generalidades de una porción de las funciones matemáticas de la Biblioteca estándar de
C++. Después, el lector aprenderá a declarar una función con más de un parámetro.También presenta-
remos información adicional acerca de los prototipos de funciones y la forma en que el compilador los
utiliza para convertir el tipo de argumento en la llamada a una función, al tipo especificado en la lista de
parámetros de una función, si es necesario.
Luego, daremos un pequeño giro hacia las técnicas de simulación con la generación de números
aleatorios, y desarrollaremos una versión de un popular juego de dados de casino, el cual utiliza la ma-
yoría de las técnicas de programación que el lector ha aprendido hasta este punto.
Posteriormente, presentaremos las clases de almacenamiento y reglas de alcance de C++. Éstas de-
terminan el periodo durante el cual un objeto existe en la memoria, y en dónde se puede hacer referen-
cia a su identificador en un programa.También aprenderá cómo C++ es capaz de llevar el registro de cuál
función se ejecuta en un momento dado, cómo se mantienen los parámetros y otras variables locales de
las funciones en la memoria, y cómo sabe una función a dónde regresar, una vez que termina su ejecu-
ción. Hablaremos sobre dos temas que ayudan a mejorar el rendimiento de los programas: las funciones
en línea que pueden eliminar la sobrecarga de la llamada a una función, y parámetros de referencia que
pueden usarse para pasar elementos extensos de datos a las funciones con eficiencia.
Muchas de las aplicaciones que desarrollará tendrán más de una función con el mismo nombre.
Esta técnica, llamada sobrecarga de funciones, se utiliza para implementar funciones que realicen tareas
similares para los argumentos de distintos tipos, o posiblemente para distintos números de argumentos.
Consideremos las plantillas de funciones: un mecanismo para definir una familia de funciones sobre-
6.1 Introducción
6.2 Componentes de los programas en C++
6.3 Funciones matemáticas de la biblioteca
6.4 Definiciones de funciones con varios
parámetros
6.5 Prototipos de funciones y coerción de
argumentos
6.6 Encabezados de la Biblioteca estándar de
C++
6.7 Caso de estudio: generación de números
aleatorios
6.8 Caso de estudio: juego de probabilidad;
introducción a enum
6.9 Números aleatorios de C++11
6.10 Clases y duración de almacenamiento
6.11 Reglas de alcance
6.12 La pila de llamadas a funciones y los
registros de activación
6.13 Funciones con listas de parámetros vacías
6.14 Funciones en línea
6.15 Referencias y parámetros de referencias
6.16 Argumentos predeterminados
6.17 Operador de resolución de ámbito unario
6.18 Sobrecarga de funciones
6.19 Plantillas de funciones
6.20 Recursividad
6.21 Ejemplo sobre el uso de la recursividad:
serie de Fibonacci
6.22 Comparación entre recursividad e iteración
6.23 Conclusión
Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios
| Hacer la diferencia
6.2 Componentes de los programas en C++ 203
cargadas. Este capítulo concluye con una discusión de las funciones que se llaman a sí mismas, ya sea en
forma directa o indirecta (a través de otra función): un tema conocido como recursividad.
6.2Componentes de los programas en C++
Como hemos visto, por lo general los programas en C++ se escriben mediante la combinación de nuevas
funciones y clases que escribimos con funciones y clases “preempaquetadas”, disponibles en la Biblio-
teca estándar de C++, que proporciona una extensa colección de funciones para realizar cálculos mate-
máticos comunes, manipulaciones de cadenas, manipulaciones de caracteres, entrada/salida, compro-
bación de errores y muchas otras operaciones útiles.
Las funciones nos permiten modularizar un programa, al separar sus tareas en unidades autoconte-
nidas. Ya ha utilizado una combinación de funciones de biblioteca y sus propias funciones en todos los
programas que ha escrito. A las funciones que usted escribe se les conoce como funciones definidas por
el usuario. Las instrucciones en los cuerpos de las funciones se escriben sólo una vez, se pueden reutili-
zar tal vez desde varias ubicaciones en un programa y están ocultas de las demás funciones.
Hay varias razones para modularizar un programa con funciones:
• Una de ellas es la metodología divide y vencerás.
• Otra de ellas es la reutilización de software. Por ejemplo, en los programas anteriores no tuvi-
mos que definir cómo leer una línea de texto del teclado; C++ proporciona esta herramienta a
través de la función getline del archivo de encabezado string.
• Una tercera motivación es la de evitar repetir código.
• Además, al dividir un programa en funciones significativas, es más fácil depurarlo y darle man-
tenimiento.
Observación de Ingeniería de Software 6.1
Para promover la reutilización de software, toda función debe limitarse a realizar una sola
tarea bien definida, y el nombre de la función debe expresar esa tarea con eficiencia.
Como sabemos, una función se invoca mediante una llamada, y cuando la función a la que se llamó
completa su tarea, devuelve un resultado o simplemente devuelve el control a la función que la lla-
mó. Una analogía a esta estructura de un programa es la forma jerárquica de la administración (figura
6.1). Un jefe (similar a la función que hace la llamada) pide a un trabajador (similar a la función que se
llamó) que realice una tarea y reporte (devuelva) los resultados, después de completarla. La función jefe
no sabe cómo realiza la función trabajador sus tareas designadas. El trabajador también podría llamar a
otras funciones trabajador, sin que el jefe supiera. Este ocultamiento de los detalles de implementación
promueve la buena ingeniería de software. La figura 6.1 muestra cómo la función jefe se comunica con
jefe
trabajador2 trabajador3
trabajador1
trabajador5
trabajador4
Fig. 6.1  Relación jerárquica entre la función jefe y las funciones trabajador.
204 Capítulo 6 Funciones y una introducción a la recursividad
varias funciones trabajador. La función jefe divide las responsabilidades entre las diversas funciones
trabajador, y trabajador1 actúa como “función jefe” para trabajador4 y trabajador5.
6.3Funciones matemáticas de la biblioteca
Algunas veces, las funciones como main no son miembros de una clase. A éstas se les conoce como
funciones globales. Al igual que las funciones miembro de una clase, los prototipos para las funciones
globales se colocan en encabezados, de manera que las funciones globales se puedan reutilizar en cual-
quier programa que incluya el archivo de encabezado y pueda crear un enlace con el código objeto de la
función. Por ejemplo, recuerde que utilizamos la función pow del archivo de encabezado cmath para
elevar un valor a una potencia en la figura 5.6. Introduciremos aquí varias funciones del archivo de en-
cabezado cmath para presentar el concepto de las funciones globales que no pertenecen a una clase
específica.
El encabezado cmath proporciona una colección de funciones que nos permiten realizar cálculos
matemáticos comunes. Por ejemplo, puede calcular la raíz cuadrada de 900.0 con la siguiente llamada
a la función:
sqrt( 900.0 )
La expresión anterior se evalúa como 30.0. La función sqrt recibe un argumento de tipo double y
devuelve un resultado double. No hay necesidad de crear objetos antes de llamar a la función sqrt.
Además, todas las funciones en el encabezado cmath son globales; por lo tanto, para llamar a cada una
de ellas sólo hay que especificar el nombre de la función, seguido de paréntesis que contienen los argu-
mentosdelamisma.Sillamamosasqrt conunargumentonegativo,lafunciónestableceunavariableglobal
llamada errno con el valor constante EDOM. La variable errno y la constante EDOM se definen en el en-
cabezado cerrno. En la sección 6.10 hablaremos sobre las variables globales
Tip para prevenir errores 6.1
No llame a sqrt con un argumento negativo. Para el código de nivel industrial, revise
siempre que los argumentos que pasa a las funciones matemáticas sean válidos.
Los argumentos de una función pueden ser constantes, variables o expresiones más complejas.
Si c = 13.0, d = 3.0 y f = 4.0, entonces la instrucción:
cout  sqrt( c + d * f )  endl;
muestra en pantalla la raíz cuadrada de 13.0 + 3.0 * 4.0 = 25.0; a saber, 5.0. Algunas funciones
matemáticas de la biblioteca se sintetizan en la figura 6.2. En la figura, las variables x y y son de tipo
double.
Función Descripción Ejemplo
ceil( x ) redondea x al valor más
pequeño que no sea menor
que x
ceil( 9.2 ) es 10.0
ceil( -9.8 ) es -9.0
cos( x ) coseno trigonométrico de x
(x está en radianes)
cos( 0.0 ) es 1.0
Fig. 6.2  Funciones matemáticas de la biblioteca (parte 1 de 2).
6.4 Definiciones de funciones con varios parámetros 205
Función Descripción Ejemplo
exp( x ) función exponencial ex exp( 1.0 ) es 2.718282
exp( 2.0 ) es 7.389056
fabs( x ) valor absoluto de x fabs( 5.1 ) es 5.1
fabs( 0.0 ) es 0.0
fabs( -8.76 ) es 8.76
floor( x ) redondea x al entero más
grande, no mayor a x
floor( 9.2 ) es 9.0
floor( -9.8 ) es -10.0
fmod( x, y ) residuo de x/y como número
de punto flotante
fmod( 2.6, 1.2 ) es 0.2
log( x ) logaritmo natural de x
(base e)
log( 2.718282 ) es 1.0
log( 7.389056 ) es 2.0
log10( x ) logaritmo de x (base 10) log10( 10.0 ) es 1.0
log10( 100.0 ) es 2.0
pow( x, y ) x elevado a la potencia y (xy
) pow( 2, 7 ) es 128
pow( 9, .5 ) es 3
sin( x ) seno trigonométrico de x
(x en radianes)
sin( 0.0 ) es 0
sqrt( x ) raíz cuadrada de x (en donde x
es un valor no negativo)
sqrt( 9.0 ) es 3.0
tan( x ) tangente trigonométrica de x
(x en radianes)
tan( 0.0 ) es 0
6.4Definiciones de funciones con varios parámetros
Consideremos las funciones con varios parámetros. El programa en las figuras 6.3 a 6.5 modifica la
clase LibroCalificacones, al incluir una función definida por el usuario llamada maximo, la cual deter-
mina y devuelve la mayor de tres calificaciones int. Cuando la aplicación empieza su ejecución, la
función main (líneas 5 a 13 de la figura 6.5) crea un objeto de la clase LibroCalificaciones (línea 8) y
llama a la función miembro recibirCalificaciones del objeto (línea 11) para leer tres calificaciones
enteras del usuario. En el archivo de implementación de la clase LibroCalificaciones (figura 6.4), en
las líneas 52 y 53 de la función miembro recibirCalificaciones se pide al usuario que introduzca tres
valores enteros y los recibe de éste. En la línea 56 se hace una llamada a la función miembro maximo
(definida en las líneas 60 a 73). La función maximo determina el valor más grande, y después la instruc-
ción return (línea 72) devuelve el valor al punto en el cual la función recibirCalificaciones invocó
a maximo (línea 56). Entonces, la función miembro recibirCalificaciones almacena el valor de re-
torno de maximo en el miembro de datos calificacionMaxima. Después, este valor se imprime llaman-
do a la función mostrarReporteCalificaciones (línea 12 de la figura 6.5). [Nota: a esta función la
llamamos mostrarReporteCalificaciones, ya que versiones subsiguientes de la clase LibroCalifi-
caciones utilizarán esta función para mostrar un reporte completo de calificaciones, incluyendo las
calificaciones máxima y mínima]. En el capítulo 7 mejoraremos la clase LibroCalificaciones para
procesar conjuntos de calificaciones.
Fig. 6.2  Funciones matemáticas de la biblioteca (parte 2 de 2).
206 Capítulo 6 Funciones y una introducción a la recursividad
1 // Fig. 6.3: LibroCalificaciones.h
2 // Definición de la clase LibroCalificaciones que encuentra el máximo de tres
calificaciones.
3 // Las funciones miembro están definidas en LibroCalificaciones.cpp
4 #include string // el programa usa la clase string estándar de C++
5
6 // definición de la clase LibroCalificaciones
7 class LibroCalificaciones
8 {
9 public:
10 explicit LibroCalificaciones( std::string ); // inicializa el nombre del
curso
11 void establecerNombreCurso( std::string ); // establece el nombre del curso
12 std::string obtenerNombreCurso() const; //obtiene el nombre del curso
13 void mostrarMensaje() const; // muestra un mensaje de bienvenida
14 void recibirCalificaciones(); // recibe las tres calificaciones del usuario
15 void mostrarReporteCalificaciones() const; // muestra un reporte con base en
las calificaciones
16 int maximo( int, int, int ) const; // determina el máximo de 3 valores
17 private:
18 std::string nombreCurso; // nombre del curso para este LibroCalificaciones
19 int calificacionMaxima; // valor máximo de las tres calificaciones
20 }; // fin de la clase LibroCalificaciones
Fig. 6.3  Definición de la clase LibroCalificaciones que encuentra el máximo de tres calificaciones.
1 // Fig. 6.4: LibroCalificaciones.cpp
2 // Definiciones de las funciones miembro para la clase LibroCalificaciones
3 // que determina el máximo de tres calificaciones.
4 #include iostream
5 using namespace std;
6
7 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
8
9 // el constructor inicializa nombreCurso con la cadena suministrada como
10 // argumento; inicializa calificacionMaxima a 0
11 LibroCalificaciones::LibroCalificaciones( string nombre )
12 : calificacionMaxima( 0 ) // este valor se reemplazará por la calificación máxima
13 {
14 establecerNombreCurso( nombre ); // valida y almacena nombreCurso
15 } // fin del constructor LibroCalificaciones
16
17 // función para establecer el nombre del curso; limita nombre a 25 o menos
caracteres
18 void LibroCalificaciones::establecerNombreCurso( string nombre )
19 {
20 if ( nombre.size() = 25 ) // si nombre tiene 25 o menos caracteres
21 nombreCurso = nombre; // almacena el nombre del curso en el objeto
22 else // si nombre es mayor que 25 caracteres
23 { // establece nombreCurso a los primeros 25 caracteres del parámetro nombre
24 nombreCurso = nombre.substr( 0, 25 ); // selecciona los primeros 25 caracteres
25 cerr  El nombre   name   excede la longitud maxima (25).n
26  Se limito nombreCurso a los primeros 25 caracteres.n  endl;
27 } // fin de if...else
28 } // fin de la función establecerNombreCurso
Fig. 6.4  Definiciones de las funciones miembro para la clase LibroCalificaciones que determina
el máximo de tres calificaciones (parte 1 de 2).
6.4 Definiciones de funciones con varios parámetros 207
29
30 // función para obtener el nombre del curso
31 string LibroCalificaciones::obtenerNombreCurso() const
32 {
33 return nombreCurso;
34 } // fin de la función obtenerNombreCurso
35
36 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones
37 void LibroCalificaciones::mostrarMensaje() const
38 {
39 // esta instrucción llama a obtenerNombreCurso para obtener el
40 // name of the course this GradeBook represents
41 cout  Bienvenido al libro de calificaciones paran
 obtenerNombreCurso()  !n
42  endl;
43 } // fin de la función mostrarMensaje
44
45 // recibe tres calificaciones del usuario; determina el valor máximo
46 void LibroCalificaciones::recibirCalificaciones()
47 {
48 int calificacion1; // primera calificación introducida por el usuario
49 int calificacion2; // segunda calificación introducida por el usuario
50 int calificacion3; // tercera calificación introducida por el usuario
51
52 cout  Introduzca tres calificaciones enteras: ;
53 cin  calificacion1  calificacion2  calificacion3;
54
55 // almacena el valor máximo en el miembro calificacionMaxima
56 calificacionMaxima = maximo( calificacion1, calificacion2, calificacion3 );
57 } // fin de la función recibirCalificaciones
58
59 // devuelve el máximo de sus tres parámetros enteros
60 int LibroCalificaciones::maximo( int x, int y, int z ) const
61 {
62 int valorMaximo = x; // supone que x es el mayor para empezar
63
64 // determina si y es mayor que valorMaximo
65 if ( y  valorMaximo )
66 valorMaximo = y; // hace a y el nuevo valorMaximo
67
68 // determina si z es mayor que valorMaximo
69 if ( z  valorMaximo )
70 valorMaximo = z; // hace a z el nuevo valorMaximo
71
72 return valorMaximo;
73 } // fin de la función maximo
74
75 // muestra un reporte con base en las calificaciones introducidas por el usuario
76 void LibroCalificaciones::mostrarReporteCalificaciones() const
77 {
78 // imprime el máximo de las calificaciones introducidas
79 cout  Calificacion maxima introducida:   calificacionMaxima  endl;
80 } // fin de la función mostrarReporteCalificaciones
Fig. 6.4  Definiciones de las funciones miembro para la clase LibroCalificaciones que determina
el máximo de tres calificaciones (parte 2 de 2).
208 Capítulo 6 Funciones y una introducción a la recursividad
1 // Fig. 6.5: fig06_05.cpp
2 // Crea un objeto LibroCalificaciones, introducir calificaciones y mostrar
reporte.
3 #include LibroCalificaciones.h // incluye la definición de la clase
LibroCalificaciones
4
5 int main()
6 {
7 // crea un objeto LibroCalificaciones
8 LibroCalificaciones miLibroCalificaciones( CS101 Programacion en C++ );
9
10 miLibroCalificaciones.mostrarMensaje(); // muestra mensaje de bienvenida
11 miLibroCalificaciones.recibirCalificaciones(); // lee calificaciones del
usuario
12 miLibroCalificaciones.mostrarReporteCalificaciones();
// muestra reporte con base en las calificaciones
13 } // fin de main
Bienvenido al libro de calificaciones para
CS101 Programacion en C++!
Introduzca tres calificaciones enteras: 86 67 75
Calificacion maxima introducida: 86
Bienvenido al libro de calificaciones para
CS101 Programacion en C++!
Introduzca tres calificaciones enteras: 67 86 75
Calificacion maxima introducida: 86
Bienvenido al libro de calificaciones para
CS101 Programacion en C++!
Introduzca tres calificaciones enteras: 67 75 86
Calificacion maxima introducida: 86
Fig. 6.5  Crear objeto LibroCalificaciones, introducir calificaciones y mostrar reporte.
Observación de Ingeniería de Software 6.2
Las comas utilizadas en la línea 56 de la figura 6.4 para separar los argumentos de la fun-
ción maximo no son operadores coma, como vimos en la sección 5.3. El operador coma ga-
rantiza que sus operandos se evalúen de izquierda a derecha. Sin embargo, el estándar de
C++ no especifica el orden de evaluación de los argumentos de una función. Por ende, dis-
tintos compiladores pueden evaluar los argumentos de una función en distintos órdenes. El
estándardeC++garantizaquetodoslosargumentosenlallamadaaunafunciónseevalúen
antes de ejecutar la función que se va a llamar.
Tip de portabilidad 6.1
Algunas veces, cuando los argumentos de una función son expresiones, como las de las lla-
madas a otras funciones, el orden en el que el compilador evalúa los argumentos podría
afectar a los valores de uno o más de los argumentos. Si el orden de evaluación cambia entre
un compilador y otro, los valores de los argumentos que se pasan a la función podrían variar,
lo cual produciría errores lógicos sutiles.
6.4 Definiciones de funciones con varios parámetros 209
Tip para prevenir errores 6.2
Si tiene dudas acerca del orden de evaluación de los argumentos de una función, y de si el
orden afectaría a los valores que se pasan a la función, evalúe los argumentos en instruc-
ciones de asignación separadas antes de la llamada a la función, asigne el resultado de cada
expresión a una variable local y después pase esas variables como argumentos para la
función.
Prototipo de función para maximo
El prototipo de la función miembro maximo (figura 6.3, línea 16) indica que la función devuelve un
valor entero, que el nombre de la función es maximo y que requiere tres parámetros enteros para realizar
su tarea. La primera línea de la función maximo (figura 6.4, línea 60) concuerda con el prototipo de
función e indica que los nombres de los parámetros son x, y y z. Cuando se hace una llamada a maximo
(figura 6.4, línea 56), el parámetro x se inicializa con el valor del argumento calificacion1, el paráme-
tro y se inicializa con el valor del argumento calificacion2 y el parámetro z se inicializa con el valor
del argumento calificacion3. Debe haber un argumento en la llamada a la función para cada paráme-
tro (también conocido como parámetro formal) en la definición de la función.
Observe que varios parámetros se especifican en el prototipo de función y en el encabezado de la
función como una lista separada por comas. El compilador hace referencia al prototipo de la función
para comprobar que las llamadas a maximo contengan el número y tipos de argumentos correctos, y que
los tipos estén en el orden correcto. Además, el compilador usa el prototipo para asegurar que el valor
devuelto por la función se pueda utilizar de manera correcta en la expresión que llamó a la función (por
ejemplo, la llamada a una función que devuelve void no se puede utilizar como el lado derecho de una
instrucción de asignación). Cada argumento debe ser consistente con el tipo del parámetro correspon-
diente.Porejemplo,unparámetrodetipodouble puederecibirvalorescomo7.35,22o–0.03456,pero
no una cadena como «hola». Si los argumentos que se pasan a una función no concuerdan con los tipos
especificados en el prototipo de la función, el compilador trata de convertir los argumentos a esos ti-
pos. En la sección 6.5 hablaremos sobre esta conversión.
Error común de programación 6.1
Declarar los parámetros de funciones del mismo tipo como double x, y en vez de double x,
double y es un error de sintaxis; se requiere un tipo para cada parámetro en la lista de
parámetros.
Error común de programación 6.2
Si el prototipo de función, el encabezado y las llamadas a la función no concuerdan en el
número, tipo y orden de argumentos y parámetros, además del tipo de retorno, se produ-
cen errores de compilación. También pueden ocurrir errores en el enlazador y otros tipos
de errores, como veremos más adelante en el libro.
Observación de Ingeniería de Software 6.3
Unafunciónquetienemuchosparámetrospuedeestarrealizandomuchastareas.Considere
dividir la función en funciones más pequeñas que realicen las tareas por separado. Limite el
encabezado de la función a una línea, si es posible.
Lógica de la función máximo
Para determinar el valor máximo (líneas 60 a 73 de la figura 6.4), empezamos con la suposición de que
el parámetro x contiene el valor más grande, por lo que en la línea 62 de la función maximo se declara la
variable local valorMaximo y se inicializa con el valor del parámetro x. Desde luego, es posible que el
210 Capítulo 6 Funciones y una introducción a la recursividad
parámetro y o z contenga el valor más grande actual, por lo que debemos comparar cada uno de estos
valores con valorMaximo. La instrucción if en las líneas 65 y 66 determina si y es mayor que valor-
Maximo y, de ser así, asigna y a valorMaximo. La instrucción if en las líneas 69 y 70 determina si z es
mayor que valorMaximo y, de ser así, asigna z a valorMaximo. En este punto, el mayor de los tres valo-
res está en valorMaximo, por lo que la línea 72 devuelve ese valor a la llamada en la línea 56. Cuando el
control del programa regresa al punto en donde se llamó a maximo, los parámetros x, y y z de maximo no
están accesibles ya para el programa.
Devolver el control de una función a quien la llamó
Hay varias formas de devolver el control al punto en el que se invocó a una función. Si la función no
devuelve un resultado (es decir, si la función tiene un tipo de valor de retorno void), el control regresa
cuando el programa llega a la llave derecha de fin de la función, o mediante la ejecución de la instrucción
return;
Si la función devuelve un resultado, la instrucción
return expresion;
evalúa expresion y devuelve el valor de expresion a la función que hizo la llamada. Algunos compiladores
generan errores y otros generan advertencias si no se proporciona una instrucción return apropiada en
una función que se supone debe devolver un resultado.
6.5Prototipos de funciones y coerción de argumentos
Un prototipo de función (también conocido como declaración de función) indica al compilador el
nombre de una función, el tipo de datos devuelto por la función, el número de parámetros que espera
recibir, los tipos de esos parámetros y el orden en el que éstos se esperan.
Observación de Ingeniería de Software 6.4
Los prototipos de función son obligatorios, a menos que la función se defina antes de usarla.
Use directivas #include del preprocesador para obtener prototipos de función para las
funciones de la Biblioteca estándar de C++ de los encabezados para las bibliotecas apropia-
das (por ejemplo, el prototipo para sqrt está en encabezado cmath; una lista parcial de
los encabezados de la Biblioteca estándar de C++ aparece en la sección 6.6). Use también
#include para obtener encabezados que contengan prototipos de función escritos por usted,
o por otros programadores.
Error común de programación 6.3
Si se define una función antes de invocarla, entonces su definición también sirve como el
prototipo de la misma, por lo que no es necesario un prototipo separado. Si se invoca una
función antes de definirla, y no tiene un prototipo de función, se produce un error de com-
pilación.
Observación de Ingeniería de Software 6.5
Siempre debemos proporcionar prototipos de funciones, aun cuando es posible omitirlas
cuandosedefinenlasfunciones antesdeutilizarlas.Alproporcionarlosprototipos,evitamos
fijar el código al orden en el que se definen las funciones (lo cual puede cambiar fácilmente,
a medida que un programa evoluciona).
6.5 Prototipos de funciones y coerción de argumentos 211
Firmas de funciones
La porción de un prototipo de función que incluya el nombre de la función y los tipos de sus argumentos
se conoce como la firma de la función, o simplemente firma. La firma de la función no especifica el
tipo de valor de retorno de la función. Las funciones en el mismo alcance deben tener firmas únicas. El al-
cance de una función es la región del programa en la que la función se conoce y es accesible. En la sección
6.11 hablaremos más acerca del alcance.
En la figura 6.3, si el prototipo en la línea 16 se hubiera escrito como:
void maximo( int, int, int );
el compilador reportaría un error, ya que el tipo de valor de retorno void en el prototipo de la función
sería distinto del tipo de valor de retorno int en el encabezado de la función. De manera similar, dicho
prototipo haría que la instrucción
cout  maximo( 6, 7, 0 );
genere un error de compilación, ya que esa instrucción depende de maximo para devolver un valor que
se va a mostrar en pantalla.
Coerción de argumentos
Una característica importante de los prototipos de función es la coerción de argumentos; es decir,
obligar a que los argumentos tengan los tipos especificados por las declaraciones de los parámetros. Por
ejemplo, un programa puede llamar a una función con un argumento entero, aun cuando el prototipo
de función especifique un argumento double; la función de todas maneras trabajará correctamente.
Reglas de promoción de argumentos y conversiones implícitas1
Algunas veces, los valores de los argumentos que no corresponden precisamente a los tipos de los pará-
metros en el prototipo de función pueden ser convertidos por el compilador al tipo apropiado, antes de
que se haga una llamada a la función. Estas conversiones ocurren según lo especificado por las reglas
de promoción de C++. Las reglas de promoción indican las conversiones implícitas que el compila-
dor puede realizar entre los tipos fundamentales. Un int se puede convertir en double. Un double
también puede convertirse en un int, pero se trunca la parte fraccionaria del valor double. Tenga en
cuenta que las variables double pueden contener números de una magnitud mucho mayor que las va-
riables int, por lo que la pérdida de datos puede ser considerable. Los valores también pueden modifi-
carse al convertir tipos de enteros largos en tipos de enteros pequeños (por ejemplo, de long a short),
números con signo a números sin signo, o viceversa. Los enteros sin signo varían desde 0 a un valor
aproximado del doble del rango positivo del tipo con signo correspondiente.
Las reglas de promoción se aplican a expresiones que contienen valores de dos o más tipos de datos;
dichas expresiones se conocen también como expresiones de tipo mixto. El tipo de cada valor en una
expresión de tipo mixto se promueve al tipo “más alto” en la expresión (en realidad, se crea y se utiliza
una versión temporal de cada valor para la expresión; los valores originales permanecen sin cambios). La
promoción también ocurre cuando el tipo de un argumento de función no concuerda con el tipo de
parámetro especificado en la definición o prototipo de la función. La figura 6.6 lista los tipos de datos
aritméticos en orden del “tipo más alto” al “tipo más bajo”.
1 Las promociones y conversiones son temas complejos que se ven en la sección 4 y al principio de la sección 5 del
estándar de C++. Puede comprar una copia del estándar en bit.ly/CPlusPlus11Standard.
212 Capítulo 6 Funciones y una introducción a la recursividad
Tipos de datos
long double
double
float
unsigned long long int (sinónimo con unsigned long long)
long long int (sinónimo con long long)
unsigned long int (sinónimo con unsigned long)
long int (sinónimo con long)
unsigned int (sinónimo con unsigned)
int
unsigned short int (sinónimo con unsigned short)
short int (sinónimo con short)
unsigned char
char y signed char
bool
Fig. 6.6  Jerarquía de promociones para los tipos de datos
fundamentales.
Las conversiones pueden producir valores incorrectos
La conversión de valores a los tipos fundamentales más bajos puede producir valores incorrectos. Por lo
tanto, un valor se puede convertir en un tipo fundamental menor sólo si se asigna de manera explícita el
valor a una variable de tipo inferior (algunos compiladores generarán una advertencia en este caso), o
mediante el uso de un operador de conversión (vea la sección 4.9). Los valores de los argumentos de una
función se convierten a los tipos de los parámetros en un prototipo de función, como si se hubieran
asignado de manera directa a las variables de esos tipos. Si se hace una llamada a una función cuadrado,
que utiliza un parámetro entero, con un argumento de punto flotante, el argumento se convierte a int
(un tipo más bajo) y cuadrado podría devolver un valor incorrecto. Por ejemplo, cuadrado(4.5) de-
vuelve 16, no 20.25.
Error común de programación 6.4
Si los argumentos en la llamada a una función no concuerdan con el número y tipos de los
parámetros declarados en el prototipo de función correspondiente, se produce un error de
compilación.Tambiénesunerrorsielnúmerodeargumentosenlallamadaconcuerda,pero
los argumentos no se pueden convertir de manera implícita a los tipos esperados.
6.6Encabezados de la Biblioteca estándar de C++
LaBibliotecaestándardeC++estádivididaenmuchasporciones,cadaunaconsupropioencabezado.Los
encabezadoscontienenlosprototiposdefunciónparalasfuncionesrelacionadasqueformancadaporción
de la biblioteca. Los encabezados también contienen definiciones de varios tipos de clases y funciones, así
comolasconstantesquenecesitanesasfunciones.Unencabezado“instruye”alcompiladoracercadecómo
interconectarse con los componentes de la biblioteca y los componentes escritos por el usuario.
En la figura 6.7 se listan algunos encabezados comunes de la Biblioteca estándar de C++, la mayoría
de los cuales veremos más adelante en el libro.
6.6 Encabezados de la Biblioteca estándar de C++ 213
Encabezado de la
Biblioteca
estándar de C++ Explicación
iostream Contiene prototipos de función para las funciones de entrada y salida estándar de
C++, presentadas en el capítulo 2, y que se tratan con más detalle en el capítulo 13,
Entrada/salida de flujos: un análisis detallado.
iomanip Contiene prototipos de función para los manipuladores de flujo que dan formato a
flujos de datos. Este encabezado se utiliza primero en la sección 4.9 y se analiza con
más detalle en el capítulo 13, Entrada/salida de flujos: un análisis detallado.
cmath Contiene prototipos de función para las funciones de la biblioteca de matemáticas
(sección 6.3).
cstdlib Contiene prototipos de función para las conversiones de números a texto, de texto a
números, asignación de memoria, números aleatorios y varias otras funciones
utilitarias. En la sección 6.7 veremos partes de este encabezado; también en el
capítulo 11, Sobrecarga de operadores; la clase string; en el capítulo 17 (en el sitio
web), Manejo de excepciones: un análisis más detallado; en el capítulo 22 (en inglés,
en el sitio web), Bits, caracteres, cadenas tipo C y tipos struct; y en el apéndice F,
Temas sobre código heredado de C.
ctime Contiene prototipos de función y tipos para manipular la hora y la fecha. Este
encabezado se utiliza en la sección 6.7.
array,
vector, list,
forward_list,
deque, queue,
stack, map,
unordered_map,
unordered_set,
set, bitset
Estos encabezados contienen clases que implementan los contenedores de
la Biblioteca estándar de C++. Los contenedores almacenan datos durante la
ejecución de un programa. El encabezado vector se introduce por primera vez en
el capítulo 7, Plantillas de clase array y vector; cómo atrapar excepciones. En el
capítulo 15 (en el sitio web), Contenedores e iteradores de la Biblioteca estándar
hablaremos sobre todos estos encabezados.
cctype Contiene prototipos de función para las funciones que evalúan caracteres en base a
ciertas propiedades (por ejemplo, si el carácter es un dígito o un signo de puntua-
ción), y prototipos de funciones que se pueden utilizar para convertir letras
minúsculas a mayúsculas y viceversa. Hablaremos sobre estos temas en el capítulo 22
(en inglés, en el sitio web), Bits, caracteres, cadenas tipo C y tipos struct.
cstring Contiene prototipos de funciones para las funciones de procesamiento de cadenas
estilo C. Este encabezado se utiliza en el capítulo 10, Sobrecarga de operadores; la
clase string.
typeinfo Contiene clases para la identificación de tipos en tiempo de ejecución (determinar
los tipos de datos en tiempo de ejecución). Este archivo de encabezado se describe en
la sección 12.8.
exception,
stdexcept
Estosencabezadoscontienenclasesqueseutilizanparamanejarexcepciones(sedescriben
enelcapítulo17(enelsitioweb),Manejodeexcepciones:unanálisismásdetallado).
memory Contiene clases y funciones utilizadas por la Biblioteca estándar de C++ para asignar
memoria a los contenedores de la Biblioteca estándar de C++. Este encabezado se utiliza
en el capítulo 17 (en el sitio web), Manejo de excepciones: un análisis más detallado.
fstream Contiene prototipos de funciones para las funciones que realizan operaciones de
entrada desde archivos en disco, y operaciones de salida hacia archivos en disco (que
veremos en el capítulo 14, Procesamiento de archivos).
string Contiene la definición de la clase string de la Biblioteca estándar de C++ (que
veremos en el capítulo 21 [en inglés, en el sitio web], La clase String y el procesa-
miento de flujos de cadena).
Fig. 6.7  Encabezados de la Biblioteca estándar de C++ (parte 1 de 2).
214 Capítulo 6 Funciones y una introducción a la recursividad
Encabezado de la
Biblioteca
estándar de C++ Explicación
sstream Contiene prototipos de función para las funciones que realizan operaciones de
entrada a partir de cadenas en memoria, y operaciones de salida hacia cadenas en
memoria (que veremos en el capítulo 21(en inglés, en el sitio web). (La clase string
y el procesamiento de flujos de cadena).
functional Contiene las clases y funciones utilizadas por algoritmos de la Biblioteca estándar
de C++. Este encabezado se utiliza en el capítulo 15.
iterator Contiene clases para acceder a los datos en los contenedores de la Biblioteca estándar
de C++. Este encabezado se utiliza en el capítulo 15.
algorithm Contiene las funciones para manipular los datos en los contenedores de la Biblioteca
estándar de C++. Este encabezado se utiliza en el capítulo 15.
cassert Contiene macros para agregar diagnósticos que ayuden a depurar programas
(debug). Este encabezado se utiliza en el apéndice E, Preprocesador.
cfloat Contiene los límites del sistema en cuanto al tamaño de los números de punto
flotante.
climits Contiene los límites del sistema en cuanto al tamaño de los números enteros.
cstdio Contiene los prototipos de función para las funciones de la biblioteca de entrada/
salida estándar estilo C.
locale Contiene clases y funciones que se utilizan comúnmente en el procesamiento de
flujos, para procesar datos en la forma natural para distintos lenguajes (por ejemplo,
formatos monetarios, almacenamiento de cadenas, presentación de caracteres,
etcétera).
limits Contiene clases para definir los límites de los tipos de datos numéricos en cada
plataforma computacional.
utility Contiene clases y funciones utilizadas por muchos encabezados de la Biblioteca
estándar de C++.
6.7Caso de estudio: generación de números aleatorios
[Nota: las técnicas de generación de números aleatorios que veremos en esta sección y en la sección 6.8
se incluyen para los lectores que todavía no utilizan compiladores de C++11. En la sección 6.9 presen-
taremos las herramientas mejoradas para generar números aleatorios de C++11].
Ahora analizaremos de manera breve una parte divertida de un tipo popular de aplicaciones de la pro-
gramación: simulación y juegos. En ésta y en la siguiente sección desarrollaremos un programa de un
juego que incluye varias funciones.
El elemento de azar puede introducirse en las aplicaciones computacionales mediante el uso de la
función rand de la Biblioteca estándar de C++. Considere la siguiente instrucción:
i = rand();
La función rand genera un entero sin signo entre 0 y RAND_MAX (una constante simbólica definida en el
encabezado cstdlib. Para determinar el valor de RAND_MAX para su sistema, sólo tiene que mostrar la
constante. Si rand produce verdaderamente enteros al azar, cada número entre 0 y RAND_MAX tiene una
oportunidad (o probabilidad) igual de ser elegido cada vez que se llame a rand.
Fig. 6.7  Encabezados de la Biblioteca estándar de C++ (parte 2 de 2).
6.7 Caso de estudio: generación de números aleatorios 215
El rango de valores producidos directamente por la función rand es a menudo distinto de lo que
requiere una aplicación específica. Por ejemplo, un programa que simula el lanzamiento de una moneda
sólo requiere 0 para “águila” y 1 para “sol”. Un programa para simular el tiro de un dado de seis lados
requeriría enteros aleatorios en el rango de 1 a 6. Un programa que adivine en forma aleatoria el siguien-
te tipo de nave espacial (de cuatro posibilidades distintas) que volará a lo largo del horizonte en un vi-
deojuego requeriría números aleatorios en el rango de 1 a 4.
Tirar un dado de seis lados
Para demostrar la función rand, en la figura 6.8 se simulan 20 tiros de un dado de seis lados, y se mues-
tra el valor de cada tiro. El prototipo de la función rand se encuentra en cstdlib. Para producir va-
lores enteros en el rango de 0 a 5, usamos el operador módulo (%) con rand, como se muestra a conti-
nuación:
rand() % 6
A esto se le conoce como escalar. El número 6 se conoce como el factor de escala. Después desplaza-
mos el rango de números producidos sumando 1 a nuestro resultado anterior. En la figura 6.8 se con-
firma que los resultados están en el rango de 1 a 6. Si ejecuta este programa más de una vez, podrá
comprobarqueproducelosmismosvalores“aleatorios”cadavez.Enlafigura6.10lemostraremoscómo
corregir esto.
1 // Fig. 6.8: fig06_08.cpp
2 // Enteros desplazados y escalados, producidos por 1 + rand() % 6.
3 #include iostream
4 #include iomanip
5 #include cstdlib // contiene el prototipo de funcion para rand
6 using namespace std;
7
8 int main()
9 {
10 // itera 20 veces
11 for ( unsigned int contador = 1; contador = 20; ++contador )
12 {
13 // elije un número aleatorio de 1 a 6 y lo imprime
14 cout  setw( 10 )  ( 1 + rand() % 6 );
15
16 // si contador puede dividirse entre 5, empieza una nueva línea de salida
17 if ( contador % 5 == 0 )
18 cout  endl;
19 } // fin de for
20 } // fin de main
6 6 5 5 6
5 1 1 5 3
6 6 2 4 2
6 2 3 4 1
Fig. 6.8  Enteros desplazados y escalados, producidos por 1 + rand() % 6.
Tirar un dado de seis lados 6000000 veces
Para mostrar que los números que produce la función rand ocurren con una probabilidad aproximada-
mente igual, la figura 6.9 simula 6000000 de tiros de un dado. Cada entero en el rango de 1 a 6 debe
aparecer aproximadamente 1000000 veces. Esto se confirma mediante la salida del programa.
216 Capítulo 6 Funciones y una introducción a la recursividad
1 // Fig. 6.9: fig06_09.cpp
2 // Tiro de un dado de seis lados 6 000 000 veces.
3 #include iostream
4 #include iomanip
5 #include cstdlib // contiene el prototipo de la función rand
6 using namespace std;
7
8 int main()
9 {
10 unsigned int frecuencia1 = 0; // cuenta las veces que se tiró 1
11 unsigned int frecuencia2 = 0; // cuenta las veces que se tiró 2
12 unsigned int frecuencia3 = 0; // cuenta las veces que se tiró 3
13 unsigned int frecuencia4 = 0; // cuenta las veces que se tiró 4
14 unsigned int frecuencia5 = 0; // cuenta las veces que se tiró 5
15 unsigned int frecuencia6 = 0; // cuenta las veces que se tiró 6
16
17 // sintetiza los resultados de tirar un dado 6 000 000 veces
18 for ( unsigned int tiro = 1; tiro = 6000000; ++tiro )
19 {
20 unsigned int cara = 1 + rand() % 6; // número aleatorio del 1 al 6
21
22 // determina el valor del tiro de 1 a 6 e incrementa el contador apropiado
23 switch ( cara )
24 {
25 case 1:
26 ++frecuencia1; // incrementa el contador de 1
27 break;
28 case 2:
29 ++frecuencia2; // incrementa el contador de 2
30 break;
31 case 3:
32 ++frecuencia3; // incrementa el contador de 3
33 break;
34 case 4:
35 ++frecuencia4; // incrementa el contador de 4
36 break;
37 case 5:
38 ++frecuencia5; // incrementa el contador de 5
39 break;
40 case 6:
41 ++frecuencia6; // incrementa el contador de 6
42 break;
43 default: // valor inválido
44 cout  El programa nunca debio llegar aqui!;
45 } // fin de switch
46 } // fin de for
47
48 cout  Cara  setw( 13 )  Frecuencia  endl; // imprime encabezados
49 cout   1  setw( 13 )  frecuencia1
50  n 2  setw( 13 )  frecuencia2
51  n 3  setw( 13 )  frecuencia3
52  n 4  setw( 13 )  frecuencia4
Fig. 6.9  Tiro de un dado de seis lados 6000000 veces (parte 1 de 2).
6.7 Caso de estudio: generación de números aleatorios 217
53  n 5  setw( 13 )  frecuencia5
54  n 6  setw( 13 )  frecuencia6  endl;
55 } // fin de main
Cara Frecuencia
1 999702
2 1000823
3 999378
4 998898
5 1000777
6 1000422
Como se muestra en los resultados del programa, al escalar y desplazar los valores producidos por
rand, podemos simular el tiro de un dado de seis lados. El programa nunca debe llegar al caso default
(líneas 43 y 44) en la estructura switch, ya que la expresión de control del switch (cara) siempre
tiene valores en el rango de 1 a 6; sin embargo, proporcionamos el caso default como una cuestión
de buena práctica. Una vez que estudiemos los arreglos en el capítulo 7, le mostraremos cómo reem-
plazar toda la estructura switch de la figura 6.9 de una manera elegante, con una instrucción de una
sola línea.
Tip para prevenir errores 6.3
Hay que proporcionar un caso default en una instrucción switch para atrapar errores,
¡incluso si estamos absoluta y positivamente seguros de no tener errores!
Randomización del generador de números aleatorios
Al ejecutar el programa de la figura 6.8 otra vez, se produce lo siguiente:
6 6 5 5 6
5 1 1 5 3
6 6 2 4 2
6 2 3 4 1
El programa imprime exactamente la misma secuencia de valores que se muestra en la figura 6.8. ¿Cómo
pueden ser estos números aleatorios? Al depurar un programa de simulación, esta repetitividad es esencial
para demostrar que las correcciones al programa funcionan en forma apropiada.
En realidad, la función rand genera números seudoaleatorios. Si se llama repetidas veces a rand,
se produce una secuencia de números que parecen ser aleatorios. No obstante, la secuencia se repite a
sí misma cada vez que se ejecuta el programa. Una vez que un programa se ha depurado extensivamen-
te, puede condicionarse para producir una secuencia diferente de números aleatorios para cada ejecu-
ción. A esto se le conoce como randomización, y se logra mediante la función srand de la Biblioteca
estándar de C++. La función srand recibe un argumento entero unsigned y siembra la función rand
para que produzca una secuencia distinta de números aleatorios para cada ejecución del programa.
C++11 proporciona herramientas adicionales para números aleatorios que pueden producir números
aleatorios no determinísticos: un conjunto de números aleatorios que no pueden producirse. Dichos
generadores de números aleatorios se utilizan en simulaciones y escenarios de seguridad en donde la
predictibilidad es indeseable. La sección 6.9 introduce las herramientas de generación de números
aleatorios de C++11.
Fig. 6.9  Tiro de un dado de seis lados 6000000 veces (parte 2 de 2).
218 Capítulo 6 Funciones y una introducción a la recursividad
Buena práctica de programación 6.1
Asegúrese de que su programa siembre el generador de números aleatorios de manera distin-
ta (y sólo una vez) cada vez que se ejecute el programa; de lo contrario, un atacante podría
determinar con facilidad la secuencia de números seudoaleatorios que se producirían.
Sembrar el generador de números aleatorios con srand
En la figura 6.10 se demuestra el uso de la función srand. El programa utiliza el tipo de datos unsigned
int. Un valor int se representa cuando menos por dos bytes; por lo general, cuatro bytes en los sistemas
de 32 bits y puede ser de hasta ocho bytes en sistemas de 64 bits. Un int puede tener valores positivos y
negativos. Una variable de tipo unsigned int también se almacena en al menos dos bytes de memoria.
Un valor unsigned int de cuatro bytes puede tener sólo valores no negativos en el rango de 0 a
4294967295. La función srand recibe un valor unsigned int como argumento. El prototipo para la
función srand se encuentra en el encabezado cstdlib.
1 // Fig. 6.10: fig06_10.cpp
2 // Randomización del programa para tirar dados.
3 #include iostream
4 #include iomanip
5 #include cstdlib // contiene los prototipos para las funciones srand y rand
6 using namespace std;
7
8 int main()
9 {
10 unsigned int semilla = 0; // almacena la semilla introducida por el usuario
11
12 cout  Introduzca la semilla: ;
13 cin  semilla;
14 srand( semilla ); // siembra el generador de números aleatorios
15
16 // itera 10 veces
17 for ( unsigned int contador = 1; contador = 10; ++contador )
18 {
19 // elije un número aleatorio entre 1 y 6, y lo imprime
20 cout  setw( 10 )  ( 1 + rand() % 6 );
21
22 // si contador puede dividirse entre 5, empieza una nueva línea de salida
23 if ( contador % 5 == 0 )
24 cout  endl;
25 } // fin de for
26 } // fin de main
Introduzca la semilla: 67
6 1 4 6 2
1 6 1 6 4
Introduzca la semilla: 432
4 6 3 1 6
3 1 5 4 2
Fig. 6.10  Randomización del programa para tirar dados (parte 1 de 2).
6.8 Caso de estudio: juego de probabilidad; introducción a enum 219
Introduzca la semilla: 67
6 1 4 6 2
1 6 1 6 4
El programa produce una secuencia distinta de números aleatorios cada vez que se ejecuta, siempre
y cuando el usuario introduzca una semilla distinta. Utilizamos la misma semilla en la primera y la ter-
cera ventana de resultados, por lo que se muestra la misma serie de 10 números en cada uno de esos re-
sultados.
Sembrar el generador de números aleatorios con la hora actual
Para randomizar sin tener que introducir una semilla cada vez, podemos usar una instrucción como la
siguiente:
srand( static_castunsigned int( time( 0 ) ) );
Esto hace que la computadora lea su reloj para obtener el valor para la semilla. Por lo general, la función
time (con el argumento 0, como se escribe en la instrucción anterior) devuelve la hora actual como el
número de segundos transcurridos desde enero 1, 1970, a media noche en Tiempo del Meridiano de
Greenwich(GMT).Estevalor(queesdetipotime_t)seconvierteenununsigned int yseutilizacomo
semilla para el generador de números aleatorios; la palabra clave static_cast en la instrucción anterior
elimina una advertencia del compilador que se genera si pasamos un valor time_t a una función que
espera un valor unsigned int. El prototipo de la función time está en ctime.
Escalamiento y desplazamiento de números aleatorios
Anteriormente simulamos cómo tirar un dado de seis lados con la instrucción:
cara = 1 + rand() % 6;
la cual siempre asigna un entero (al azar) a la variable cara en el rango 1 ≤ cara ≤ 6. La amplitud de
este rango (es decir, el número de enteros consecutivos en el rango) es 6, y el número inicial en el rango
es 1. Si hacemos referencia a la instrucción anterior, podemos ver que la amplitud del rango se determi-
na en base al número que se utiliza para escalar rand con el operador módulo (es decir, 6), y el número
inicial del rango es igual al número (es decir, 1) que se agrega a la expresión rand % 6. Podemos gene-
ralizar este resultado de la siguiente manera:
numero = valorDesplazamiento + rand() % factorEscala;
en donde valorDesplazamiento es igual al primer número en el rango deseado de enteros consecutivos
y factorEscala es igual a la amplitud del rango deseado de enteros consecutivos.
6.8Caso de estudio: juego de probabilidad; introducción a enum
Uno de los juegos de azar más populares es el juego de dados conocido como “craps”, el cual se juega en
casinos y callejones por todo el mundo. Las reglas del juego son simples:
Un jugador tira dos dados. Cada dado tiene seis caras, las cuales contienen 1, 2, 3, 4, 5 y 6 puntos negros.
Unavezquelosdadosdejandemoverse,secalculalasumadelospuntosnegrosenlasdoscarassuperiores.
Si la suma es 7 u 11 en el primer tiro, el jugador gana. Si la suma es 2, 3 o 12 en el primer tiro (llamado
“craps”), el jugador pierde (es decir, la “casa” gana). Si la suma es 4, 5, 6, 8, 9 o 10 en el primer tiro, esta
sumaseconvierteenel“punto”deljugador.Paraganar,eljugadordebeseguirtirandolosdadoshastaque
salga otra vez “su punto”. El jugador pierde si tira un 7 antes de llegar a su punto.
Fig. 6.10  Randomización del programa para tirar dados (parte 2 de 2).
220 Capítulo 6 Funciones y una introducción a la recursividad
El programa de la figura 6.11 simula el juego de craps. En las reglas del juego, observe que el jugador
debe tirar dos dados en el primer tiro y en todos los tiros subsiguientes. Definimos la función tirar-
Dados (líneas 62 a 74) para tirar el dado, calcular e imprimir la suma. La función está definida sólo una
vez, pero se llama desde las líneas 20 y 44. La función no recibe argumentos y devuelve la suma de los
dos dados, por lo que se indican paréntesis vacíos y el tipo de valor de retorno unsigned int en el pro-
totipo de la función (línea 8) y en el encabezado de la misma (línea 62).
1 // Fig. 6.11: fig06_11.cpp
2 // Simulación del juego de craps.
3 #include iostream
4 #include cstdlib // contiene los prototipos para las funciones srand y rand
5 #include ctime // contiene el prototipo para la función time
6 using namespace std;
7
8 unsigned int rollDice(); // tira los dados, calcula y muestra la suma
9
10 int main()
11 {
12 // enumeración con constantes que representa el estado del juego
13
enum Estado { CONTINUAR, GANO, PERDIO };
// todas las letras mayúsculas en las constantes
14
15 // randomiza el generador de números aleatorios, usando la hora actual
16 srand( static_castunsigned int( time( 0 ) ) );
17
18 unsigned int miPunto = 0; // punto si no se gana o pierde en el primer tiro
19 Estado estadoJuego = CONTINUAR; // puede contener CONTINUAR, GANO o PERDIO
20 unsigned int sumaDeDados = tirarDados() ; // primer tiro del dado
21
22 // determina el estado del juego y el punto (si es necesario), con base en el
primer tiro
23 switch ( sumaDeDados )
24 {
25 case 7: // gana con 7 en el primer tiro
26 case 11: // gana con 11 en el primer tiro
27 estadoJuego = GANO;
28 break;
29 case 2: // pierde con 2 en el primer tiro
30 case 3: // pierde con 3 en el primer tiro
31 case 12: // pierde con 12 en el primer tiro
32 estadoJuego = PERDIO;
33 break;
34 default: // no gano ni perdio, por lo que recuerda el punto
35 estadoJuego = CONTINUAR; // el juego no ha terminado
36 miPunto = sumaDeDados; // recuerda el punto
37 cout  El punto es   miPunto  endl;
38 break; // opcional al final del switch
39 } // fin de switch
40
41 // mientras el juego no esté completo
42 while ( CONTINUAR == estadoJuego ) // no GANO ni PERDIO
43 {
13
Fig. 6.11  Simulación del juego de “craps” (parte 1 de 3).
6.8 Caso de estudio: juego de probabilidad; introducción a enum 221
44 sumaDeDados = tirarDados(); // tira los dados de nuevo
45
46 // determina el estado del juego
47 if ( sumaDeDados == miPunto ) // gana al hacer un punto
48 estadoJuego = GANO;
49 else
50 if ( sumaDeDados == 7 ) // pierde al tirar 7 antes del punto
51 estadoJuego = PERDIO;
52 } // fin de while
53
54 // muestra mensaje de que ganó o perdió
55 if ( GANO == estadoJuego )
56 cout  El jugador gana  endl;
57 else
58 cout  El jugador pierde  endl;
59 } // fin de main
60
61 // tira los dados, calcula la suma y muestra los resultados
62 unsigned int tirarDados()
63 {
64 // elige valores aleatorios para el dado
65 unsigned int dado1 = 1 + rand() % 6; // tiro del primer dado
66 unsigned int dado2 = 1 + rand() % 6; // tiro del segundo dado
67
68 unsigned int suma = dado1 + dado2; // calcula la suma de valores de los dados
69
70 // muestra los resultados de este tiro
71 cout  El jugador tiro   dado1   +   dado2
72   =   suma  endl;
73 return suma; // devuelve la suma de los dados
74 } // fin de la función tirarDados
El jugador tiro 2 + 5 = 7
El jugador gana
El jugador tiro 6 + 6 = 12
El jugador pierde
El jugador tiro 1 + 3 = 4
El punto es 4
El jugador tiro 4 + 6 = 10
El jugador tiro 2 + 4 = 6
El jugador tiro 6 + 4 = 10
El jugador tiro 2 + 3 = 5
El jugador tiro 2 + 4 = 6
El jugador tiro 1 + 1 = 2
El jugador tiro 4 + 4 = 8
El jugador tiro 4 + 3 = 7
El jugador pierde
Fig. 6.11  Simulación del juego de “craps” (parte 2 de 3).
222 Capítulo 6 Funciones y una introducción a la recursividad
El jugador tiro 3 + 3 = 6
El punto es 6
El jugador tiro 5 + 3 = 8
El jugador tiro 4 + 5 = 9
El jugador tiro 2 + 1 = 3
El jugador tiro 1 + 5 = 6
El jugador gana
El tipo de enum Estado
El jugador puede ganar o perder en el primer tiro, o en cualquier tiro subsiguiente. El programa utiliza
la variable estadoJuego para llevar la cuenta de esto. La variable estadoJuego se declara como del nue-
vo tipo Estado. En la línea 13 se declara un tipo definido por el usuario, llamado enumeración, que se
introduce mediante la palabra clave enum (por enumeración) y va seguida de un nombre de tipo (en este
caso, Estado), además de un conjunto de constantes enteras representadas por identificadores. Los va-
lores de estas constantes de enumeración empiezan en 0, a menos que se especifique lo contrario, y se
incrementan en 1. En la enumeración anterior, la constante CONTINUAR tiene el valor 0, GANO tiene el
valor 1 y PERDIO tiene el valor 2. Los identificadores en una enumeración deben ser únicos, pero las
constantes de enumeración separadas pueden tener el mismo valor entero.
Buena práctica de programación 6.2
La primera letra de un identificador que se utilice como un nombre de tipo definido por
el usuario debe ir en mayúscula.
Buena práctica de programación 6.3
Use sólo letras mayúsculas en los nombres de las constantes de enumeración. Esto hace que
resalten y le recuerdan que las constantes de enumeración no son variables.
Las variables del tipo Estado definido por el usuario pueden recibir sólo uno de los tres valores
declarados en la enumeración. Cuando se gana el juego, el programa establece la variable estadoJuego
a GANO (líneas 27 y 48). Cuando se pierde el juego, el programa establece la variable estadoJuego a
PERDIO (líneas 32 y 51). En caso contrario, el programa establece la variable estadoJuego a CONTINUAR
(línea 35) para indicar que el dado se debe tirar de nuevo.
Error común de programación 6.5
Asignar el equivalente entero de una constante de enumeración (en vez de la misma
constante de enumeración) a una variable del tipo de enumeración es un error de com-
pilación.
Otra enumeración popular es
enum Meses { ENE = 1, FEB, MAR, ABR, MAY, JUN, JUL, AGO,
SEP, OCT, NOV, DIC };
la cual crea el tipo Meses definido por el usuario, con constantes de enumeración que representan los
meses del año. El primer valor en la enumeración anterior se establece explícitamente en 1, por lo que el
resto de los valores se incrementan a partir de 1, lo cual produce los valores del 1 al 12. Cualquier cons-
tante de enumeración puede recibir un valor entero en la definición de la enumeración, y cada una de
las constantes de enumeración subsiguientes tiene un valor igual a 1 más que la constante anterior en la
lista, hasta la siguiente configuración explícita.
Fig. 6.11  Simulación del juego de “craps” (parte 3 de 3).
6.8 Caso de estudio: juego de probabilidad; introducción a enum 223
Tip para prevenir errores 6.4
Use valores únicos para las constantes de una enumeración, para ayudar a evitar los erro-
res lógicos difíciles de localizar.
Ganar o perder en el primer tiro
Después del primer tiro, si se gana o pierde el juego, el programa omite el cuerpo de la instrucción
while (líneas 42 a 52) debido a que estadoJuego no es igual a CONTINUAR. El programa pasa a la instruc-
ción if...else en las líneas 55 a 58, que imprime El jugador gana si estadoJuego es igual a GANO
y El jugador pierde si estadoJuego es igual a PERDIO.
Continuar los tiros
Después del primer tiro, si el juego no ha terminado, el programa guarda la suma en miPunto (línea 36).
La ejecución continúa con la instrucción while, ya que estadoJuego es igual a CONTINUAR. Durante
cada iteración del while, el programa llama a tirarDados para producir una nueva suma. Si suma con-
cuerda con miPunto, el programa establece estadoJuego en GANO (línea 48), la prueba del while falla,
la instrucción if...else imprime El jugador gana y termina la ejecución. Si suma es igual a 7,
el programa establece estadoJuego a PERDIO (línea 51), falla la prueba del while, la instrucción
if...else imprime El jugador pierde y termina la ejecución.
El programa de “craps” utiliza dos funciones (main y tirarDados) y las instrucciones switch,
while, if...else, if...else anidadas e if anidadas. En los ejercicios investigamos varias característi-
cas interesantes del juego de “craps”.
C++11: enum con alcance
En la figura 6.11 presentamos las enumeraciones (enum). Un problema con las enumeraciones (que
también se conocen como enumeraciones sin alcance) es que varias enumeraciones pueden contener los
mismos identificadores. Si se utilizan dichas enumeraciones en el mismo programa, se pueden producir
conflictos de nombres y errores lógicos. Para eliminar estos problemas, C++11 introduce lo que se co-
noce como enumeraciones con alcance, que se declaran con las palabras clave enum class (o con el
sinónimo enum struct). Por ejemplo, podemos definir la enumeración Estado de la figura 6.11 como:
enum class Estado { CONTINUAR, GANO, PERDIO };
Para hacer referencia a una constante enum con alcance, debemos calificar la constante con el nombre del
tipoenumconalcance(Estado)yeloperadorderesolucióndeámbito(::),comoenEstado::CONTINUAR.
Esto identifica explícitamente a CONTINUAR como una constante en el alcance de la enum class Estado.
De esta forma, si otra enum con alcance contiene el mismo identificador para una de sus constantes,
siempre queda claro qué versión de la constante se está utilizando.
Tip para prevenir errores 6.5
Use enum con alcance para evitar conflictos de nombres y errores lógicos potenciales debido
a las enumeraciones sin alcance que contienen los mismos identificadores.
C++11: especificar el tipo de las constantes de una enumeración
Las constantes en una enum se representan como enteros. De manera predeterminada, el tipo integral
subyacente de una enum sin alcance depende de los valores de sus constantes; se garantiza que el tipo será
lo bastante grande como para almacenar los valores constantes especificados. De manera predetermina-
da, el tipo integral subyacente de una enum con alcance es int. C++11 nos permite especificar el ti-
po integral subyacente de una enum siguiendo el nombre del tipo de enum con dos puntos (:) además del
tipo integral. Por ejemplo, podemos especificar que las constantes en la enum class Estado tengan
el tipo unsigned int, como en:
enum class Estado : unsigned int { CONTINUAR, GANO, PERDIO };
224 Capítulo 6 Funciones y una introducción a la recursividad
Error común de programación 6.6
Si el valor de la constante de una enum está fuera del rango que puede representarse por el
tipo subyacente de esa enum, se produce un error de compilación.
6.9Números aleatorios de C++11
De acuerdo con el CERT, la función rand no tiene “buenas propiedades estadísticas” y puede ser prede-
cible, lo que hace a los programas que usan rand menos seguros (Lineamiento de CERT MSC30-CPP).
Como mencionamos en la sección 6.7, C++11 cuenta con una nueva y más segura biblioteca de herra-
mientasdegeneracióndenúmerosaleatoriosquepuedenproducirnúmerosaleatoriosnodeterminísticos
para simulaciones y casos de seguridad, en donde no es conveniente la predictibilidad. Estas nuevas he-
rramientas se encuentran en el encabezado random de la Biblioteca estándar de C++.
La generación de números aleatorios es un tema matemático sofisticado, para el cual los matemáti-
cos han desarrollado muchos algoritmos de generación de números aleatorios con diferentes propieda-
des estadísticas. Por cuestión de flexibilidad, con base en cómo se utilizan los números aleatorios en los
programas, C++11 cuenta con muchas clases que representan diversos motores de generación de núme-
ros aleatorios y distribuciones. Un motor implementa un algoritmo de generación de números aleatorios
que produce números seudoaleatorios. Una distribución controla el rango de valores producidos por un
motor, los tipos de esos valores (por ejemplo, int, double, etc.) y las propiedades estadísticas de los
valores. En esta sección, usaremos el motor de generación de números aleatorios predeterminado
(default_random_engine) y la distribución uniform_int_distribution, que distribuye uniforme-
mente los enteros seudoaleatorios a través de un rango especificado de valores. El rango predeterminado
es de 0 al valor máximo de un int en su plataforma.
Tirar un dado de seis lados
La figura 6.12 usa default_random_engine y uniform_int_distribution para tirar un dado de seis
lados. La línea 14 crea un objeto default_random_engine llamado motor. El argumento de su construc-
tor siembra el motor de generación de números aleatorios con la hora actual. Si no pasamos un valor al
constructor, se utilizará la semilla predeterminada y el programa producirá la misma secuencia de núme-
ros cada vez que se ejecute. La línea 15 crea intAleatorio: un objeto uniform_int_distribution que
produce valores unsigned int (según lo especificado por unsigned int) en el rango de 1 a 6 (como lo
especifican los argumentos del constructor). La expresión intAleatorio(motor) (línea 21) devuelve un
valor unsigned int en el rango de 1 a 6.
1 // Fig. 6.12: fig06_12.cpp
2 // Uso de un motor de generación de números aleatorios y una distribución
3 // de C++11 para tirar un dado con seis lados.
4 #include iostream
5 #include iomanip
6
#include random // contiene herramientas de generación de números aleatorios
de C++11
7 #include ctime
8 using namespace std;
9
10 int main()
11 {
6
Fig. 6.12  Uso de un motor de generación de números aleatorios y una distribución de C++11 para tirar un
dado con seis lados (parte 1 de 2).
6.10 Clases y duración de almacenamiento 225
12 // usa el motor de generación de números aleatorios predeterminado para
13
// producir valores int seudoaleatorios del 1 al 6, distribuidos de manera
uniforme
14 default_random_engine motor( static_castunsigned int( time(0) ) );
15 uniform_int_distributionunsigned int intAleatorio( 1, 6 );
16
17 // itera 10 veces
18 for ( unsigned int contador = 1; contador = 10; ++contador )
19 {
20 // elije un número aleatorio del 1 al 6 y lo imprime
21 cout  setw( 10 )  intAleatorio( motor );
22
23 // si contador es divisible entre 5, comienza una nueva línea de salida
24 if ( contador % 5 == 0 )
25 cout  endl;
26 } // fin de for
27 } // fin de main
2 1 2 3 5
6 1 5 6 4
La notación unsigned int en la línea 15 indica que uniform_int_distribution es una plantilla
de clase. En este caso, puede especificarse cualquier tipo entero entre los signos  y . En el capítulo 18 (en
inglés, en el sitio web) veremos cómo crear plantillas de clases y en otros capítulos le mostraremos cómo
usar las plantillas de clases existentes de la Biblioteca estándar de C++. Por ahora, siéntase cómodo al usar
la plantilla de clase uniform_int_distribution, imitando la sintaxis que se muestra en el ejemplo.
6.10Clases y duración de almacenamiento
Los programas que hemos visto hasta ahora utilizan identificadores para nombres de variables y funcio-
nes. Los atributos de las variables incluyen su nombre, tipo, tamaño y valor. Cada identificador en un
programa tiene también otros atributos, incluyendo la duración del almacenamiento, el alcance y la
vinculación.
C++ proporciona cinco especificadores de clase de almacenamiento que determinan la dura-
ción del almacenamiento de una variable: register, extern, mutable, static y thread_local. En
esta sección hablaremos sobre los especificadores de clase de almacenamiento register, extern y
static. El especificador de clase de almacenamiento mutable se utiliza exclusivamente con las clases
y thread_local se utiliza en las aplicaciones multihilo (hablaremos sobre ellos en los capítulos 23 y 24
[en inglés, en el sitio web], respectivamente).
Duración del almacenamiento
La duración del almacenamiento de un identificador determina el periodo durante el cual éste existe en la
memoria. Algunos identificadores existen brevemente, algunos se crean y destruyen repetidas veces, y
otros existen durante toda la ejecución de un programa. Primero hablaremos sobre las clases de almace-
namiento static y automatic.
Alcance
El alcance de un identificador es la parte en la que se puede hacer referencia a éste en un programa. Se
puede hacer referencia a algunos identificadores a lo largo de un programa; otros identificadores sólo
se pueden referenciar desde ciertas partes limitadas de un programa. En la sección 6.11 hablaremos
sobre el alcance de los identificadores.
13
Fig. 6.12  Uso de un motor de generación de números aleatorios y una distribución de C++11 para tirar un
dado con seis lados (parte 2 de 2).
226 Capítulo 6 Funciones y una introducción a la recursividad
Enlace
El enlace de un identificador determina si se conoce sólo en el archivo fuente en el que se declara, o en
varios archivos fuente que se compilen y después se enlacen. El especificador de clase de almacenamiento de un
identificador ayuda a determinar la duración de su almacenamiento y su enlace.
Duración del almacenamiento
Los especificadores de clases de almacenamiento se pueden dividir en cuatro duraciones de almacena-
miento: automática, estática, dinámica y de hilo. Las duraciones de almacenamiento automática y está-
tica se describen a continuación. En el capítulo 10 aprenderá que puede solicitar memoria adicional en
su programa durante la ejecución del mismo; a esto se le conoce como asignación dinámica de memoria.
Las variables que se asignan en forma dinámica tienen duración de almacenamiento dinámica. En el ca-
pítulo 24 (en inglés, en el sitio web) hablaremos sobre la duración de almacenamiento de hilo.
Variables locales y duración de almacenamiento automática
Las variables con duración de almacenamiento automática incluyen:
• variables locales declaradas en funciones
• parámetros de funciones
• variables locales o parámetros de funciones declarados con register
Dichas variables se crean cuando la ejecución del programa entra al bloque en el que se definen, existen
mientras el bloque esté activo y se destruyen cuando el programa se sale del bloque. Una variable auto-
mática existe sólo en el par circundante más cercano de llaves dentro del cuerpo de la función en la cual
aparece la definición, o durante todo el cuerpo de la función en el caso de un parámetro de función. Las
variables locales son de duración de almacenamiento automática de manera predeterminada. En el resto
del libro, nos referiremos a las variables de duración de almacenamiento automática simplemente como
variables automáticas.
Tip de rendimiento 6.1
El almacenamiento automático es un medio de conservar la memoria, ya que las variables
de duración de almacenamiento automática existen en memoria sólo cuando se ejecuta el
bloque en el cual están definidas.
Observación de Ingeniería de Software 6.6
El almacenamiento automático es un ejemplo del principio del menor privilegio. En el
contexto de una aplicación, el principio establece que el código debe recibir sólo el nivel de
privilegio y acceso que requiere para realizar su tarea designada, pero no más. ¿Por qué
deberíamos tener variables almacenadas en memoria y accesibles cuando no se necesitan?
Buena práctica de programación 6.4
Declare las variables lo más cerca posible de donde se utilicen por primera vez.
Variables de registro
Los datos en la versión de lenguaje máquina de un programa se cargan generalmente en los registros,
para cálculos y otros tipos de procesamiento.
El compilador podría ignorar las declaraciones register. Por ejemplo, tal vez no haya un número
suficiente de registros disponibles. La siguiente definición sugiere que la variableunsigned int contador
se coloque en uno de los registros de la computadora; sin importar que el compilador haga esto o no,
contador se inicializa en 1:
6.10 Clases y duración de almacenamiento 227
register unsigned int contador = 1;
La palabra clave register se puede utilizar sólo con variables locales y parámetros de funciones.
Tip de rendimiento 6.2
El especificador de clase de almacenamiento register se puede colocar antes de la decla-
ración de una variable automática, para sugerir que el compilador debe mantener la va-
riable en uno de los registros de hardware de alta velocidad de la computadora, en vez de
hacerlo en memoria. Si las variables de uso intensivo, como los contadores o totales, se
mantienen en los registros de hardware, se elimina la sobrecarga de cargar de manera re-
petitiva las variables de memoria hacia los registros, y almacenar los resultados de vuelta
a la memoria.
Tip de rendimiento 6.3
Por lo general, es innecesario el uso de register. Los compiladores optimizadores de la
actualidad pueden reconocer las variables de uso frecuente y colocarlas en registros, sin
necesitad de una declaración register.
Duración de almacenamiento estática
Las palabras clave extern y static declaran identificadores para variables con duración de almacenamien-
to estática y para funciones. Las variables con duración de almacenamiento estática existen en memoria a
partirdelpuntoen elque elprograma empiezaaejecutarse,y dejan deexistircuandoterminaelprograma.
Dicha variable se inicializa una vez al encontrar su declaración. Para las funciones, el nombre de la función
existe cuando el programa empieza a ejecutarse. Aun cuando los nombres de las funciones y las variables
de duración estática existen desde el inicio de la ejecución del programa, esto no implica que estos identi-
ficadores se puedan utilizar a lo largo de todo el programa. La duración del almacenamiento y el alcance
(en dónde se puede usar un nombre) son cuestiones separadas, como veremos en la sección 6.11.
Identificadores con duración de almacenamiento estática
Hay dos tipos de identificadores con duración de almacenamiento estática; los identificadores externos
(como las variables globales) y las variables locales declaradas con el especificador de clase de almacena-
miento static. Para crear variables globales, se colocan declaraciones de variables fuera de cualquier
definición de clase o función. Las variables globales retienen sus valores a lo largo de la ejecución del
programa. Las variables y las funciones globales se pueden referenciar mediante cualquier función que
siga sus declaraciones o definiciones en el archivo fuente.
Observación de Ingeniería de Software 6.7
Al declarar una variable como global en vez de local, se permite la ocurrencia de efectos
secundarios inesperados cuando una función que no requiere acceso a la variable la
modifica en forma accidental o premeditada. Esto es otro ejemplo del principio del menor
privilegio. En general, con la excepción de los recursos verdaderamente globales como cin
y cout, debe evitarse el uso de variables globales, excepto en ciertas situaciones con reque-
rimientos de rendimiento únicos.
Observación de Ingeniería de Software 6.8
Las variables que se utilizan sólo en una función específica deben declararse como locales
en esa función, en vez de declararlas como variables globales.
Variables locales static
Las variables locales que se declaran como static siguen siendo conocidas sólo en la función en la que
se declaran, pero a diferencia de las variables automáticas, las variables locales static retienen sus valores
cuando la función regresa a la función que la llamó. La próxima vez que se hace una llamada a la función,
228 Capítulo 6 Funciones y una introducción a la recursividad
las variables locales static contienen los valores que tenían cuando la función se ejecutó por última vez.
La siguiente instrucción declara la variable local cuenta como static, y la inicializa en 1:
static unsigned int cuenta = 1;
Todas las variables numéricas con duración de almacenamiento estática se inicializan con cero de mane-
ra predeterminada, pero sin duda es una buena práctica inicializar todas las variables en forma explícita.
Los especificadores de clase de almacenamiento extern y static tienen un significado especial
cuando se aplican de manera explícita a los identificadores externos, como las variables globales y los
nombres de funciones globales. En el apéndice F, Temas sobre código heredado de C, hablaremos sobre
el uso de extern y static con identificadores externos y programas con varios archivos de código fuente.
6.11Reglas de alcance
La porción del programa en la que se puede utilizar un identificador se conoce como su alcance. Por
ejemplo, cuando declaramos una variable local en un bloque, sólo se puede referenciar en ese bloque y en
los bloques anidados dentro de ese mismo bloque. En esta sección hablaremos sobre alcance de bloque,
alcance de función, alcance de espacio de nombres global y alcance de prototipo de función. Más
adelante veremos otros dos tipos de alcance: alcance de clase (capítulo 9) y alcance de espacio de nom-
bres (capítulo 23, en inglés, en el sitio web).
Alcance de bloque
Los identificadores que se declaran dentro de un bloque tienen alcance de bloque, el cual empieza en la de-
claración del identificador y termina en la llave derecha de finalización (}) del bloque en el que se declara
el identificador. Las variables locales tienen alcance de bloque, al igual que los parámetros de las funciones.
Cualquier bloque puede contener declaraciones de variables. Cuando los bloques están anidados y
un identificador en un bloque exterior tiene el mismo nombre que un identificador en un bloque interior, el
identificador del bloque exterior se “oculta” hasta que termine el bloque interior. El bloque interior “ve”
el valor de su propio identificador local y no el valor del identificador del bloque circundante que tiene el
nombre idéntico. Las variables locales declaradas como static siguen teniendo alcance de bloque, aun
cuando existan desde el momento en que el programa empieza su ejecución. La duración del almacena-
miento no afecta al alcance de un identificador.
Error común de programación 6.7
El uso accidental del mismo nombre para un identificador en un bloque interior, que se
utiliza para un identificador en un bloque exterior, cuando de hecho deseamos que el
identificador en el bloque exterior esté activo durante la ejecución del bloque interior, es
comúnmente un error lógico.
Tip para prevenir errores 6.6
Evite los nombres de variables que ocultan nombres en alcances exteriores.
Alcance de función
Las etiquetas (identificadores seguidos por dos puntos, como inicio:, o una etiqueta case en una
instrucción switch) son los únicos identificadores con alcance de función. Las etiquetas se pueden utili-
zar en cualquier parte en la función en la que aparecen, pero no se pueden referenciar fuera del cuerpo
de la función.
6.11 Reglas de alcance 229
Alcance de espacio de nombres global
Un identificador que se declara fuera de cualquier función o clase tiene alcance de espacio de nombres
global. Dicho identificador se “conoce” en todas las funciones a partir del punto en el que se declara,
hasta llegar al final del archivo. Las variables globales, las definiciones de funciones y los prototipos de
funciones que se colocan fuera de una función tienen alcance de archivo.
Alcance de prototipo de función
Los únicos identificadores con alcance de prototipo de función son los que se utilizan en la lista de pará-
metros de un prototipo de función. Como se mencionó antes, los prototipos de función no requieren
nombres en la lista de parámetros; sólo los tipos. El compilador ignora los nombres que aparecen en la
lista de parámetros de un prototipo de función. Los identificadores que se utilizan en un prototipo de
función se pueden reutilizar en cualquier otra parte del programa sin ambigüedad.
Demostración del alcance
El programa de la figura 6.13 demuestra cuestiones sobre el alcance con las variables globales, variables
locales automáticas y variables locales static. En la línea 10 se declara e inicializa la variable global x en
1. Esta variable local está oculta en cualquier bloque (o función) que declare una variable llamada x. En
main, la línea 14 muestra el valor de la variable global x. En la línea 16 se declara una variable local x y
se inicializa en 5. En la línea 18 se imprime esta variable para mostrar que la x global está oculta en main.
A continuación, en las líneas 20 a 24 se define un nuevo bloque en main, en el cual otra variable local x
se inicializa con 7 (línea 21). En la línea 23 se imprime esta variable, para mostrar que oculta a x en el
bloque exterior de main, así como la x global. Cuando el bloque termina, la variable x que tiene el valor
7 se destruye automáticamente. A continuación, en la línea 26 se imprime la variable local x en el bloque
exterior de main, para mostrar que ya no está oculta.
1 // Fig. 6.13: fig06_13.cpp
2 // Ejemplo sobre el alcance.
3 #include iostream
4 using namespace std;
5
6 void usarLocal(); // prototipo de función
7 void usarLocalStatic(); // prototipo de función
8 void usarGlobal(); // prototipo de función
9
10 int x = 1; // variable global
11
12 int main()
13 {
14 cout  la x global en main es   x  endl;
15
16 int x = 5; // variable local para main
17
18 cout  la x local en el alcance exterior de main es   x  endl;
19
20 { // empieza nuevo alcance
21 int x = 7; // oculta la x en el alcance exterior y la x global
22
23 cout  la x local en el alcance interior de main es   x  endl;
24 } // termina nuevo alcance
Fig. 6.13  Ejemplo sobre el alcance (parte 1 de 3).
230 Capítulo 6 Funciones y una introducción a la recursividad
25
26 cout  la x local en el alcance exterior de main es   x  endl;
27
28 usarLocal(); // usarLocal tiene la x local
29 usarLocalStatic(); // usarLocalStatic tiene la x local estática
30 usarGlobal(); // usarGlobal usa la x global
31 usarLocal(); // usarLocal reinicializa su x local
32 usarLocalStatic(); // la x local estática retiene su valor anterior
33 usarGlobal(); // la x global también retiene su valor anterior
34
35 cout  nla x local en main es   x  endl;
36 } // fin de main
37
38 // usarLocal reinicializa la variable x local durante cada llamada
39 void usarLocal()
40 {
41 int x = 25; // se inicializa cada vez que se llama a usarLocal
42
43 cout  nla x local es   x   al entrar a usarLocal  endl;
44 ++x;
45 cout  la x local es   x   al salir de usarLocal  endl;
46 } // fin de la función usarLocal
47
48 // usarLocalStatic inicializa la variable x local estática sólo la
49 // primera vez que se llama a la función; el valor de x se guarda
50 // entre las llamadas a esta función
51 void usarLocalStatic()
52 {
53
static int x = 50; // se inicializa la primera vez que se llama a
usarLocalStatic
54
55 cout  nla x local estatica es   x   al entrar a usarLocalStatic
56  endl;
57 ++x;
58 cout  la x local estatica es   x   al salir de usarLocalStatic
59  endl;
60 } // fin de la función usarLocalStatic
61
62 // usarGlobal modifica la variable global x durante cada llamada
63 void usarGlobal()
64 {
65 cout  nla x global es   x   al entrar a usarGlobal  endl;
66 x *= 10;
67 cout  la x global es   x   al salir de usarGlobal  endl;
68 } // fin de la función usarGlobal
la x global en main es 1
la x local en el alcance exterior de main es 5
la x local en el alcance interior de main es 7
la x local en el alcance exterior de main es 5
la x local es 25 al entrar a usarLocal
la x local es 26 al salir de usarLocal
53
Fig. 6.13  Ejemplo sobre el alcance (parte 2 de 3).
6.12 La pila de llamadas a funciones y los registros de activación 231
la x local estatica es 50 al entrar a usarLocalStatic
la x local estatica es 51 al salir de usarLocalStatic
la x global es 1 al entrar a usarGlobal
la x global es 10 al salir de usarGlobal
la x local es 25 al entrar a usarLocal
la x local es 26 al salir de usarLocal
la x local estatica es 51 al entrar a usarLocalStatic
la x local estatica es 52 al salir de usarLocalStatic
la x global es 10 al entrar a usarGlobal
la x global es 100 al salir de usarGlobal
la x local en main es 5
Para demostrar otros alcances, el programa define tres funciones, cada una de las cuales no recibe
argumento y no devuelve nada. La función usarLocal (líneas 39 a 46) declara la variable automática x
(línea 41) y la inicializa en 25. Cuando el programa llama a usarLocal, la función imprime la variable,
laincrementaylavuelveaimprimirantesdequelafuncióndevuelvaelcontroldelprogramaalafunción
que la llamó. Cada vez que el programa llama a esta función, ésta vuelve a crear la variable automática x
y la vuelve a inicializar en 25.
La función usarLocalStatic (líneas 51 a 60) declara la variable static x y la inicializa con 50.
Las variables locales que se declaran como static retienen sus valores aun cuando estén fuera de alcan-
ce (es decir, la función en la que se declaran no se está ejecutando). Cuando el programa llama a
usarLocalStatic, la función imprime x, la incrementa y la vuelve a imprimir antes de que la función
devuelva el control del programa a la función que la llamó. En la siguiente llamada a esta función, la
variable local static x contiene el valor 51. La inicialización en la línea 53 ocurre sólo una vez: la prime-
ra vez que se llama a usarLocalStatic.
La función usarGlobal (líneas 63 a 68) no declara ninguna variable. Por lo tanto, cuando hace
referencia a la variable x, se utiliza la x global (línea 10, antes de main). Cuando el programa llama a
usarGlobal, la función imprime la variable global x, la multiplica por 10 y la imprime de nuevo, antes
de que la función devuelva el control del programa a la función que la llamó. La siguiente vez que el
programa llama a usarGlobal, se modifica el valor de la variable global, 10. Después de ejecutar las
funciones usarLocal, usarLocalStatic y usarGlobal dos veces cada una, el programa imprime la
variable local x en main, de nuevo para mostrar que ninguna de las llamadas a la función modificó el
valor de x en main, debido a que todas las funciones hicieron referencia a las variables en otros alcances.
6.12La pila de llamadas a funciones y los registros
de activación
Para comprender la forma en que C++ realiza las llamadas a las funciones, primero necesitamos consi-
derar una estructura de datos (es decir, colección de elementos de datos relacionados) conocida como
pila. Piense en una pila como la analogía a una pila de platos. Cuando se coloca un plato en la pila, por
lo general se coloca en la parte superior: lo que se conoce como meter el plato en la pila. De manera
similar, cuando se extrae un plato de la pila, siempre se extrae de la parte superior: lo que se conoce
como sacar el plato de la pila. Las pilas se denominan estructuras de datos “último en entrar, pri-
mero en salir” (UEPS); el último elemento que se mete (inserta) en la pila es el primero que se saca
(extrae) de ella.
Fig. 6.13  Ejemplo sobre el alcance (parte 3 de 3).
232 Capítulo 6 Funciones y una introducción a la recursividad
La pila de llamadas a funciones
Uno de los mecanismos más importantes que los estudiantes de ciencias computacionales deben com-
prender es la pila de llamadas a funciones (conocida algunas veces como la pila de ejecución del
programa). Esta estructura de datos (que trabaja en segundo plano) soporta el mecanismo de llamada
a/regreso de las funciones. También soporta la creación, mantenimiento y destrucción de las variables
automáticas de cada función a la que se llama. Como veremos en las figuras 6.15 a 6.17, este compor-
tamiento “último en entrar, primero en salir” (UEPS) es exactamente lo que hace una función cuando
regresa a la función que la llamó.
Marcos de pila
A medida que se hace la llamada a cada función, ésta puede a su vez, llamar a otras funciones, las cuales,
a su vez, pueden llamar a otras funciones; todo ello antes de que regrese alguna de las funciones. En
cierto momento, cada función debe regresar el control a la función que la llamó. Por ende, de alguna
manera debemos llevar el registro de las direcciones de retorno que requiere cada función para regresar el
control a la función que la llamó. La pila de llamadas a funciones es la estructura de datos perfecta para
manejar esta información. Cada vez que una función llama a otra función, se mete una entrada en la pila.
Esta entrada, conocida como marco de pila o registro de activación, contiene la dirección de retorno
que necesita la función a la que se llamó para poder regresar a la función que hizo la llamada. También
contiene cierta información adicional que veremos en breve. Si la función a la que se llamó regresa, en
vez de llamar a otra función antes de regresar, se saca el marco de pila para la llamada a la función, y el
control se transfiere a la dirección de retorno en el marco de la pila que se sacó.
La belleza de la pila de llamadas es que cada función a la que se ha llamado siempre encuentra la
información que requiere para regresar a la función que la llamó en la parte superior de la pila de llama-
das. Y, si una función hace una llamada a otra función, simplemente se mete a la pila de llamadas un
marco de pila para esa nueva llamada a una función. Por ende, la dirección de retorno requerida por la
función recién llamada para regresar a la función que la llamó se encuentra ahora en la parte superior de
la pila.
Variables automáticas y marcos de pila
Los marcos de pila tienen otra responsabilidad importante. La mayoría de las funciones tienen variables
automáticas: parámetros y cualquier variable local que declare la función. Las variables automáticas
necesitan existir mientras una función se está ejecutando. Necesitan permanecer activas si la función
hace llamadas a otras funciones. Pero cuando una función a la que se llamó regresa a la función que
la llamó, las variables automáticas de la función a la que se llamó necesitan “desaparecer”. El marco de
pila de la función a la que se llamó es un lugar perfecto para reservar la memoria para las variables auto-
máticas de la función a la que se llamó. Ese marco de pila existe mientras la función a la que se llamó esté
activa. Cuando esa función regresa (y ya no necesita sus variables automáticas locales) su marco de pila
se saca de la pila, y esas variables automáticas ya no son conocidas para el programa.
Desbordamiento de pila
Desde luego que la cantidad de memoria en una computadora es finita, por lo cual sólo se puede usar
cierta cantidad de memoria para almacenar registros de activación en la pila de llamadas a funciones. Si
ocurren más llamadas a funciones de las que se puedan guardar sus registros de activación en la pila de
llamadas a funciones, se produce un error conocido como desbordamiento de pila.
La pila de llamadas a funciones en acción
Ahora vamos a considerar cómo la pila de llamadas ofrece soporte para la operación de una función cua-
drado llamada por main (líneas 9 a 14 de la figura 6.14). Primero, el sistema operativo llama a main; esto
hace que se meta un registro de activación en la pila (lo cual se muestra en la figura 6.15). El registro de
activación indica a main cómo debe regresar al sistema operativo (es decir, transferir el control a la direc-
ción de retorno R1) y contiene el espacio para la variable automática de main (a, que se inicializa en 10).
6.12 La pila de llamadas a funciones y los registros de activación 233
1 // Fig. 6.14: fig06_14.cpp
2 // Función cuadrado utilizada para demostrar la pila
3 // de llamadas a funciones y los registros de activación.
4 #include iostream
5 using namespace std;
6
7 int cuadrado( int ); // prototipo para la función cuadrado
8
9 int main()
10 {
11 int a = 10; // valor para cuadrado (variable local automática en main)
12
13 cout  a   al cuadrado:   cuadrado( a )  endl; // muestra a al cuadrado
14 } // fin de main
15
16 // devuelve el cuadrado de un entero
17 int cuadrado( int x ) // x es una variable local
18 {
19 return x * x; // calcula el cuadrado y devuelve el resultado
20 } // fin de la función cuadrado
10 cuadrado: 100
Fig. 6.14  Función cuadrado utilizada para demostrar la pila de llamadas a funciones y los registros
de activación.
Llamada a función después del paso 1
Registro de activación
para la función main
Parte superior de la pila
Dirección de retorno: R1
Variables automáticas:
a 10
Líneas que representan al sistema
operativo ejecutando instrucciones
Clave
Paso 1: El sistema operativo invoca a main para ejecutar la aplicación.
Sistema operativo
{
int a = 10;
cout  a   al cuadrado: 
 cuadrado( a )  endl;
}
Dirección de retorno R1
int main()
Fig. 6.15  La pila de llamadas a funciones, después de que el sistema operativo invoca a main
para ejecutar el programa.
234 Capítulo 6 Funciones y una introducción a la recursividad
La función main (antes de regresar al sistema operativo) llama ahora a la función cuadrado en la
línea 13 de la figura 6.14. Esto hace que se meta un marco de pila para cuadrado (líneas 17 a 20) en
la pila de llamadas a funciones (figura 6.16). Este marco de pila contiene la dirección de retorno que
cuadrado necesita para regresar a main (es decir, R2) y la memoria para la variable automática de cua-
drado (es decir, x).
Dirección de retorno R2
Registro de activación
para la función cuadrado
Registro de activación
para la función main
Paso 2: main invoca a la función cuadrado para realizar el cálculo
Dirección de retorno: R1
Variables automáticas:
a 10
Dirección de retorno: R2
Variables automáticas:
x 10
Parte superior de la pila
{
int a = 10;
cout  a   al cuadrado: 
 cuadrado( a )  endl;
}
int main()
{
return x * x;
}
int cuadrado( int x )
Llamada a función después del paso 2
Fig. 6.16  La pila de llamadas a funciones, después de que la función cuadrado regresa a main.
Una vez que cuadrado calcula el cuadrado de su argumento, necesita regresar a main; y ya no nece-
sita la memoria para su variable automática, x. Por ende, el marco de pila de cuadrado se saca de la pila,
con lo cual se proporciona a cuadrado la dirección de retorno en main (es decir, R2) y se pierde la varia-
ble automática de cuadrado. La figura 6.17 muestra la pila de llamadas a funciones después de sacar el
registro de activación de cuadrado.
Ahora la función main muestra el resultado de llamar a cuadrado (figura 6.14, línea 13). Al llegar a
la llave derecha de cierre de main, su marco de pila se saca de la pila, proporciona a main la dirección que
requiere para regresar al sistema operativo (es decir, R1 en la figura 6.15) y, en este punto, la variable
automática de main (es decir, a) ya no existe.
Ahora hemos visto qué tan valiosa es la estructura de datos tipo pila para implementar un mecanis-
mo clave que ofrezca soporte para la ejecución de un programa. Las estructuras de datos tienen muchas
aplicaciones importantes en las ciencias computacionales. En el capítulo 15, Contenedores e iteradores
de la Biblioteca estándar y en el capítulo 19, CustomTemplatized Data Structures (en inglés, en el sitio
web), hablaremos sobre las pilas, colas, listas, árboles y otras estructuras de datos.
6.13 Funciones con listas de parámetros vacías 235
Pila de llamadas a funciones después del paso 3
Dirección de retorno R2
Registro de activación
para la función main
Paso 3: cuadrado devuelve su resultado a main
Dirección de retorno: R1
Variables automáticas:
a 10
Parte superior de la pila
{
int a = 10;
cout  a   al cuadrado: 
 cuadrado( a )  endl;
}
int main()
{
return x * x;
}
int cuadrado( int x )
Fig. 6.17  La pila de llamadas a funciones, después de que la función cuadrado regresa a main.
6.13 Funciones con listas de parámetros vacías
En C++, una lista de parámetros vacía se especifica escribiendo void o nada entre paréntesis. El pro-
totipo
void imprimir();
especifica que la función imprimir no recibe argumentos y no devuelve un valor. La figura 6.18 demues-
tra ambas formas de declarar y usar funciones con listas de parámetros vacías.
1 // Fig. 6.18: fig06_18.cpp
2 // Funciones que no reciben argumentos.
3 #include iostream
4 using namespace std;
5
6 void funcion1(); // función que no recibe argumentos
7 void funcion2( void ); // función que no recibe argumentos
8
9 int main()
10 {
11 funcion1(); // llama a funcion1 sin argumentos
12 funcion2(); // llama a funcion2 sin argumentos
13 } // fin de main
14
Fig. 6.18  Funciones que no reciben argumentos (parte 1 de 2).
236 Capítulo 6 Funciones y una introducción a la recursividad
15 // funcion1 usa una lista de parámetros vacía para especificar que
16 // la función no recibe argumentos
17 void funcion1()
18 {
19 cout  funcion1 no recibe argumentos  endl;
20 } // fin de funcion1
21
22 // funcion2 usa una lista de parámetros vacía para especificar que
23 // la función no recibe argumentos
24 void funcion2( void )
25 {
26 cout  funcion2 tampoco recibe argumentos  endl;
27 } // fin de funcion2
funcion1 no recibe argumentos
funcion2 tampoco recibe argumentos
6.14 Funciones en línea
Es bueno implementar un programa como un conjunto de funciones desde el punto de vista de la inge-
niería de software, pero las llamadas a funciones implican una sobrecarga en tiempo de ejecución. C++
cuenta con las funciones en línea para ayudar a reducir la sobrecarga de las llamadas a funciones. Al
colocar el calificador inline antes del tipo de valor de retorno de la función en su definición, se aconse-
ja al compilador para que genere una copia del código del cuerpo de la función en cada lugar en donde
se llame a la función (cuando sea apropiado), para evitar la llamada a una función. Con frecuencia, esto
hace que el tamaño del programa aumente. El compilador puede ignorar el calificador inline, y por lo
general lo hace para todas las funciones, excepto las más pequeñas. Por lo general las funciones inline
reutilizables se colocan en encabezados, de modo que puedan incluirse sus definiciones en cada archivo
de código fuente que las utilice.
Observación de Ingeniería de Software 6.9
Si cambia la definición de una función inline, deberá volver a compilar todos los clien-
tes de esa función.
Tip de rendimiento 6.4
Los compiladores pueden poner código en línea para el que no hayamos utilizado de ma-
nera explícita la palabra clave inline. Los compiladores optimizadores actuales son tan
sofisticados que es mejor dejarles las decisiones sobre poner o no código en línea.
La figura 6.19 utiliza la función inline llamada cubo (líneas 9 a 12) para calcular el volumen de un
cubo. La palabra clave const en la lista de parámetros de la función cubo (línea 9) indica al compila-
dor que la función no modifica la variable lado. Esto asegura que la función no modifique el valor de
lado cuando se realice el cálculo (la palabra clave const se describe con detalle en los capítulos 7 a 9).
Observación de Ingeniería de Software 6.10
El calificador const se debe utilizar para hacer valer el principio del menor privilegio. El
uso de este principio para diseñar software de manera apropiada puede reducir considera-
blemente el tiempo de depuración y los efectos secundarios inadecuados; además puede
facilitar la modificación y el mantenimiento de un programa.
Fig. 6.18  Funciones que no reciben argumentos (parte 2 de 2).
6.15 Referencias y parámetros de referencias 237
1 // Fig. 6.19: fig06_19.cpp
2 // Función inline que calcula el volumen de un cubo.
3 #include iostream
4 using namespace std;
5
6 // Definición de la función en línea cubo. La definición de la función aparece
7 // antes de llamar a la función, por lo que no se requiere un prototipo de función.
8 // La primera línea de la función actúa como el prototipo.
9 inline double cubo( const double lado )
10 {
11 return lado * lado * lado; // calcula el cubo
12 } // fin de la función cubo
13
14 int main()
15 {
16 double valorLado; // almacena el valor introducido por el usuario
17 cout  Escriba la longitud del lado de su cubo: ;
18 cin  valorLado; // lee el valor del usuario
19
20 // calcula el cubo de valorLado y muestra el resultado
21 cout  El volumen del cubo con un lado de 
22  valorLado   es   cubo( valorLado )  endl;
23 } // end main
Escriba la longitud del lado de su cubo: 3.5
El volumen del cubo con un lado de 3.5 es 42.875
Fig. 6.19  Función inline que calcula el volumen de un cubo.
6.15Referencias y parámetros de referencias
Dos formas de pasar argumentos a las funciones en muchos lenguajes de programación son el paso por
valor y el paso por referencia. Cuando se pasa un argumento por valor, se crea una copia del valor del
argumento y se pasa (en la pila de llamadas a funciones) a la función que se llamó. Las modificaciones a
la copia no afectan al valor de la variable original en la función que hizo la llamada. Esto evita los efectos
secundarios accidentales que tanto obstaculizan el desarrollo de sistemas de software correctos y confia-
bles. Hasta ahora, cada argumento en el libro se ha pasado por valor.
Tip de rendimiento 6.5
Una desventaja del paso por valor es que, si se va a pasar un elemento de datos extenso, el
proceso de copiar esos datos puede requerir una cantidad considerable de tiempo de ejecu-
ción y espacio en memoria.
Parámetros por referencia
En esta sección presentamos los parámetros por referencia: el primero de los dos medios que proporcio-
na C++ para realizar el paso por referencia. Mediante el paso por referencia, la función que hace la llama-
da proporciona a la función que llamó la habilidad de acceder directamente a los datos de la primera, y de
modificarlos.
Tip de rendimiento 6.6
El paso por referencia es bueno por cuestiones de rendimiento, ya que puede eliminar la
sobrecarga de copiar grandes cantidades de datos en el paso por valor.
238 Capítulo 6 Funciones y una introducción a la recursividad
Observación de Ingeniería de Software 6.11
El paso por referencia puede debilitar la seguridad, ya que la función a la que se llamó
puede corromper los datos de la función que hizo la llamada.
Más adelante veremos cómo lograr la ventaja de rendimiento que ofrece el paso por referencia, al
tiempo que se obtiene la ventaja de ingeniería de software de evitar que la corrupción de los datos de la
función que hizo la llamada.
Un parámetro por referencia es un alias para su correspondiente argumento en la llamada a una
función. Para indicar que un parámetro de función se pasa por referencia, simplemente hay que colocar
un signo  después del tipo del parámetro en el prototipo de la función; use la misma convención al
listar el tipo del parámetro en el encabezado de la función. Por ejemplo, la siguiente declaración en el
encabezado de una función:
int cuenta
si se lee de izquierda a derecha, significa que “cuenta es una referencia a un valor int”. En la llamada a
la función, simplemente hay que mencionar la variable por su nombre para pasarla por referencia. Des-
pués, al mencionar la variable por el nombre de su parámetro en el cuerpo de la función a la que se llamó,
en realidad se refiere a la variable original en la función que hizo la llamada, y esta variable original se
puede modificar directamente en la función a la que se llamó. Como siempre, el prototipo de función y
el encabezado deben concordar.
Paso de argumentos por valor y por referencia
En la figura 6.20 se compara el paso por valor y el paso por referencia con los parámetros por referencia.
Los “estilos” de los argumentos en las llamadas a la función cuadradoPorValor y la función cuadrado-
PorReferencia son idénticos; ambas variables sólo se mencionan por nombre en las llamadas a las
funciones. Sin comprobar los prototipos o las definiciones de las funciones, no es posible deducir sólo de las
llamadas si cada función puede modificar sus argumentos. Como los prototipos de función son obligato-
rios, el compilador no tiene problemas para resolver la ambigüedad.
Error común de programación 6.8
Como los parámetros por referencia se mencionan sólo por su nombre en el cuerpo de la fun-
ción a la que se llama, podríamos tratar de manera inadvertida los parámetros por referen-
cia como parámetros de paso por valor. Esto puede provocar efectos secundarios inesperados
si la función modifica las copias originales de las variables.
1 // Fig. 6.20: fig06_20.cpp
2 // Paso de argumentos por valor y por referencia.
3 #include iostream
4 using namespace std;
5
6 int cuadradoPorValor( int ); // prototipo de función (paso por valor)
7 void cuadradoPorReferencia( int  ); // prototipo de función (paso por referencia)
8
9 int main()
10 {
11 int x = 2; // valor para cuadrado usando cuadradoPorValor
12 int z = 4; // valor para cuadrado usando cuadradoPorReferencia
13
Fig. 6.20  Paso de argumentos por valor y por referencia (parte 1 de 2).
6.15 Referencias y parámetros de referencias 239
14 // demuestra cuadradoPorValor
15 cout  x =   x   antes de cuadradoPorValorn;
16 cout  Valor devuelto por cuadradoPorValor: 
17  cuadradoPorValor( x )  endl;
18 cout  x =   x   despues de cuadradoPorValorn  endl;
19
20 // demuestra cuadradoPorReferencia
21 cout  z =   z   antes de cuadradoPorReferencia  endl;
22 cuadradoPorReferencia( z );
23 cout  z =   z   despues de cuadradoPorReferencia  endl;
24 } // fin de main
25
26 // cuadradoPorValor multiplica el número por sí mismo, almacena el
27 // resultado en el número y devuelve el nuevo valor del número
28 int cuadradoPorValor( int numero )
29 {
30
return numero *= numero; // no se modificó el argumento de la función que hizo
la llamada
31 } // fin de la función cuadradoPorValor
32
33
// cuadradoPorReferencia multiplica a refNumero por sí solo y almacena el
resultado
34 // en la variable a la que refNumero hace referencia en la función main
35 void cuadradoPorReferencia( int refNumero )
36 {
37
refNumero *= refNumero; // se modificó el argumento de la función que hizo la
llamada
38 } // fin de la función cuadradoPorReferencia
x = 2 antes de cuadradoPorValor
Valor devuelto por cuadradoPorValor: 4
x = 2 despues de cuadradoPorValor
z = 4 antes de cuadradoPorReferencia
z = 16 despues de cuadradoPorReferencia
El capítulo 8 habla sobre los apuntadores; éstos proporcionan una forma alternativa del paso por
referencia, en la cual el estilo de la llamada indica claramente el paso por referencia (y el potencial de
modificar los argumentos de la función que hace la llamada).
Tip de rendimiento 6.7
Para pasar objetos extensos, use un parámetro por referencia constante para simular la
apariencia y seguridad del paso por valor, evitando así la sobrecarga de pasar una copia
del objeto extenso.
Para especificar que una referencia no debe modificar el argumento, coloque el calificador const
antes del especificador de tipo en la declaración del parámetro. Observe la colocación del signo  en la
lista de parámetros de la función cuadradoPorReferencia (línea 35, figura 6.20). Algunos programa-
dores de C++ prefieren escribir la forma equivalente int refNumero.
Referencias como alias dentro de una función
Las referencias también se pueden usar como alias para otras variables dentro de una función (aunque
por lo general se utilizan con las funciones, como se muestra en la figura 6.20). Por ejemplo, el código
Fig. 6.20  Paso de argumentos por valor y por referencia (parte 2 de 2).
37
33
30
240 Capítulo 6 Funciones y una introducción a la recursividad
int cuenta = 1; // declara la variable entera cuenta
int cRef = cuenta; // crea cRef como alias para cuenta
++cRef; // incrementa cuenta (usando su alias cRef)
incrementa la variable cuenta mediante el uso de su alias cRef. Las variables de referencia deben inicia-
lizarse en sus declaraciones y no se pueden reasignar como alias para otras variables. Una vez que se de-
clara una referencia como alias para otra variable, todas las operaciones que supuestamente se realizan
en el alias (es decir, la referencia) en realidad se realizan en la variable original. El alias es simplemente
otro nombre para la variable original. A menos que sea una referencia a una constante, un argumento
por referencia debe ser un lvalue (por ejemplo, el nombre de una variable), no una constante o expresión
que devuelva un rvalue (por ejemplo, el resultado de un cálculo).
Devolver una referencia de una función
Las funciones pueden devolver referencias, pero esto puede ser peligroso. Al devolver una referencia a
una variable declarada en la función que se llamó, a menos que esa variable se declare como static, la
referencia se refiere a una variable automática que se descarta cuando termina la función. El intento de
acceder a dicha variable produce un comportamiento indefinido. Las referencias a variables indefinidas se
llaman referencias sueltas.
Error común de programación 6.9
Devolver una referencia a una variable automática en una función a la que se ha llama-
do es un error lógico. Por lo general, los compiladores generan una advertencia cuando esto
ocurre. Para el código de nivel industrial, siempre hay que eliminar todas las adverten-
cias de compilación para poder producir código ejecutable.
6.16Argumentos predeterminados
Para un programa, es algo común el invocar una función repetidas veces con el mismo valor de argumen-
to para un parámetro específico. En tales casos, podemos especificar que dicho parámetro tiene un
argumento predeterminado;esdecir,quetieneunvalorpredeterminadoquedebepasaraeseparámetro.
Cuando un programa omite un argumento para un parámetro con un argumento predeterminado en la
llamada a una función, el compilador vuelve a escribir la llamada a la función e inserta el valor predeter-
minado de ese argumento.
Los argumentos predeterminados deben ser los argumentos de más a la derecha en la lista de pa-
rámetros de una función. Al llamar a una función con dos o más argumentos predeterminados, si
se omite un argumento que no sea el de más a la derecha en la lista de argumentos, entonces también deben
omitirse todos los argumentos que estén a la derecha de ese argumento. Los argumentos predetermina-
dos deben especificarse con la primera ocurrencia del nombre de la función; por lo general, en el proto-
tipo de la función. Si el prototipo se omite debido a que la definición de la función también actúa como
el prototipo, entonces los argumentos predeterminados se deben especificar en el encabezado de la
función. Los valores predeterminados pueden ser cualquier expresión, incluyendo constantes, variables
globales o llamadas a funciones. Los argumentos predeterminados también se pueden usar con funcio-
nes inline.
En la figura 6.21 se demuestra el uso de argumentos predeterminados para calcular el volumen de
una caja. El prototipo de función para volumenCaja (línea 7) especifica que los tres parámetros reciben
valores predeterminados de 1. Proporcionamos nombres de variables en el prototipo de función para
mejorar la legibilidad. Como siempre, los nombres de las variables no se requieren en los prototipos de
función.
La primera llamada a volumenCaja (línea 13) especifica que no hay argumentos, con lo cual se
utilizan los tres valores predeterminados de 1. En la segunda llamada (línea 17) sólo se pasa un argumen-
to longitud, con lo cual se utilizan los valores predeterminados de 1 para los argumentos anchura y
6.16 Argumentos predeterminados 241
1 // Fig. 6.21: fig06_21.cpp
2 // Uso de argumentos predeterminados.
3 #include iostream
4 using namespace std;
5
6 // prototipo de función que especifica argumentos predeterminados
7 unsigned int volumenCaja( unsigned int longitud = 1, unsigned int anchura = 1,
8 unsigned int altura = 1 );
9
10 int main()
11 {
12 // sin argumentos--usa valores predeterminados para todas las medidas
13 cout  El volumen predeterminado de la caja es:   volumenCaja();
14
15 // especifica la longitud; anchura y altura predeterminadas
16 cout  nnEl volumen de una caja con longitud 10,n
17  anchura 1 y altura 1 es:   volumenCaja( 10 );
18
19 // especifica longitud y anchura; altura predeterminada
20 cout  nnEl volumen de una caja con longitud 10,n
21  anchura 5 y altura 1 es:   volumenCaja( 10, 5 );
22
23 // especifica todos los argumentos
24 cout  nnEl volumen de una caja con longitud 10,n
25  anchura 5 y altura 2 es:   volumenCaja( 10, 5, 2 )
26  endl;
27 } // fin de main
28
29 // la función volumenCaja calcula el volumen de una caja
30 unsigned int volumenCaja( unsigned int longitud, unsigned int anchura,
31 unsigned int altura )
32 {
33 return longitud * anchura * altura;
34 } // fin de la función volumenCaja
El volumen predeterminado de la caja es: 1
El volumen de una caja con longitud 10,
anchura 1 y altura 1 es: 10
El volumen de una caja con longitud 10,
anchura 5 y altura 1 es: 50
El volumen de una caja con longitud 10,
anchura 5 y altura 2 es: 100
Fig. 6.21  Uso de argumentos predeterminados.
altura. La tercera llamada (línea 21) pasa argumentos sólo para longitud y anchura, con lo cual se
utiliza un valor predeterminado de 1 para el argumento altura. La última llamada (línea 25) pasa argu-
mentos para longitud, anchura y altura, con lo cual no utiliza valores predeterminados. Cualquier
argumento que se pasa a la función de manera explícita se asigna a los parámetros de la función, de iz-
quierda a derecha. Por lo tanto, cuando volumenCaja recibe un argumento, la función asigna el valor de
ese argumento a su parámetro longitud (es decir, el parámetro de más a la izquierda en la lista de pará-
metros). Cuando volumenCaja recibe dos argumentos, la función asigna los valores de esos argumentos
242 Capítulo 6 Funciones y una introducción a la recursividad
a sus parámetros longitud y anchura en ese orden. Por último, cuando volumenCaja recibe los tres
argumentos, la función asigna los valores de esos argumentos a sus parámetros longitud, anchura y
altura, respectivamente.
Buena práctica de programación 6.5
El uso de argumentos predeterminados puede simplificar la escritura de las llamadas a
funciones. Sin embargo, algunos programadores sienten que es más claro especificar de
manera explícita todos los argumentos.
6.17Operador de resolución de ámbito unario
Es posible declarar variables locales y globales con el mismo nombre. C++ proporciona el operador de
resolución de ámbito binario (::) para acceder a una variable global cuando una variable local con el
mismo nombre se encuentra dentro del alcance. El operador de resolución de ámbito unario no se puede
utilizar para acceder a una variable local con el mismo nombre en un bloque exterior. Se puede acceder a
una variable global directamente sin el operador de resolución de ámbito unario, si el nombre de la varia-
ble global no es el mismo que el de una variable local dentro del alcance.
En la figura 6.22 se demuestra el operador de resolución de ámbito unario con variables local y
global con el mismo nombre (líneas 6 y 10). Para enfatizar que las versiones local y global de la variable
numero son distintas, el programa declara una variable de tipo int y la otra de tipo double.
1 // Fig. 6.22: fig06_22.cpp
2 // Operador de resolución de ámbito unario.
3 #include iostream
4 using namespace std;
5
6 int numero = 7; // variable global llamada numero
7
8 int main()
9 {
10 double numero = 10.5; // variable local llamada numero
11
12 // muestra los valores de las variables local y global
13 cout  Valor local double de numero =   numero
14  nValor global int de numero =   ::numero  endl;
15 } // fin de main
Valor local double de numero = 10.5
Valor global int de numero = 7
Fig. 6.22  Operador de resolución de ámbito unario.
Buena práctica de programación 6.6
Si utiliza siempre el operador de resolución de ámbito unario (::) para hacer referencia a
las variables globales, establece claramente que está tratando de acceder a una variable
global, en vez de una variable no global.
Observación de Ingeniería de Software 6.12
Al utilizar siempre el operador de resolución de ámbito unario (::) para hacer referencia
a las variables globales, se facilita la modificación de los programas, al reducir el riesgo de
conflictos de nombres con variables no globales.
6.18 Sobrecarga de funciones 243
Tip para prevenir errores 6.7
Al utilizar siempre el operador de resolución de ámbito unario (::) para hacer referencia
a una variable global, se eliminan los posibles errores lógicos que podrían ocurrir si una
variable no global oculta a la variable global.
Tip para prevenir errores 6.8
Evite usar variables con el mismo nombre para distintos propósitos en un programa.
Aunque esto se permite en diversas circunstancias, puede producir errores.
6.18Sobrecarga de funciones
C++ permite definir varias funciones con el mismo nombre, siempre y cuando éstas tengan distintas
firmas. A esta capacidad se le conoce como sobrecarga de funciones. El compilador de C++ selecciona
la función apropiada al examinar el número, tipos y orden de los argumentos en la llamada. La sobre-
carga de funciones se utiliza para crear varias funciones con el mismo nombre que realicen tareas simila-
res, pero con distintos tipos de datos. Por ejemplo, muchas funciones en la biblioteca de matemáticas
están sobrecargadas para distintos tipos de datos numéricos; el estándar de C++ requiere versiones so-
brecargadas float, double y long double de las funciones matemáticas de la biblioteca que se describen
en la sección 6.3.
Buena práctica de programación 6.7
Al sobrecargar las funciones que realizan tareas estrechamente relacionadas, los programas
pueden ser más fáciles de leer y comprender.
Funciones cuadrado sobrecargadas
Lafigura6.23utilizafuncionescuadrado sobrecargadasparacalcularelcuadradodeunvalorint (líneas
7a 11) y el cuadrado de un valor double (líneas 14 a 18). En la línea 22 se invoca a la versión int de la
función cuadrado, para lo cual se le pasa el valor literal 7. C++ trata a los valores literales numéricos
enteros como de tipo int. De manera similar, en la línea 24 se invoca a la versión double de la función
cuadrado, para lo cual se le pasa el valor literal 7.5, que C++ trata como valor double. En cada caso, el
compilador elije la función apropiada que va a llamar, con base en el tipo del argumento. Las últimas
dos líneas en la ventana de salida confirman que se llamó a la función apropiada en cada caso.
1 // Fig. 6.23: fig06_23.cpp
2 // Funciones cuadrado sobrecargadas.
3 #include iostream
4 using namespace std;
5
6 // función cuadrado para valores int
7 int cuadrado( int x )
8 {
9 cout  el cuadrado del entero   x   es ;
10 return x * x;
11 } // fin de la función cuadrado con argumento int
12
Fig. 6.23  Funciones cuadrado sobrecargadas (parte 1 de 2).
244 Capítulo 6 Funciones y una introducción a la recursividad
13 // función cuadrado para valores double
14 double cuadrado( double y )
15 {
16 cout  el cuadrado del double   y   es ;
17 return y * y;
18 } // fin de la función cuadrado con argumento
19
20 int main()
21 {
22 cout  cuadrado( 7 ); // llama a la versión int
23 cout  endl;
24 cout  cuadrado( 7.5 ); // llama a la versión double
25 cout  endl;
26 } // fin de main
el cuadrado del entero 7 es 49
el cuadrado del double 7.5 es 56.25
Cómo diferencia el compilador las funciones sobrecargadas
Las funciones sobrecargadas se diferencian mediante sus firmas. Una firma es una combinación del
nombre de una función y los tipos de sus parámetros (en orden). El compilador codifica cada identifi-
cador de función con los tipos de sus parámetros (lo que algunas veces se conoce como manipulación
de nombres o decoración de nombres) para permitir el enlace seguro de tipos. Este tipo de enlace
aseguraquesellamealafunciónsobrecargadaapropiada,yquelostiposdelosargumentosseconformen
a los tipos de los parámetros.
La figura 6.24 se compiló con GNU C++. En vez de mostrar los resultados de la ejecución del
programa (como se haría normalmente), mostramos los nombres manipulados de la función que
GNU C++ produce en lenguaje ensamblador. Cada nombre manipulado (distinto de main) empieza
con dos guiones bajos (__) seguidos de la letra Z, un número y el nombre de la función. El número
después de Z especifica cuántos caracteres hay en el nombre de la función. Por ejemplo, la función
cuadrado tiene 8 caracteres en su nombre, por lo que su nombre manipulado recibe el prefijo __Z8.
Después, el nombre de la función va seguido de una codificación de su lista de parámetros. En la lista
de parámetros para la función nada2 (línea 25; vea la cuarta línea de salida), c representa a un valor
char, i representa a un valor int, Rf representa a un valor float  (es decir, una referencia a un va-
lor float) y Rd representa a un valor double  (es decir, una referencia a un valor double). En la lista
de parámetros para la función nada1, i representa a un valor int, f representa a un valor float, c
representa a un valor char y Ri representa a un valor int . Las dos funciones cuadrado se diferencian
en base a sus listas de parámetros; una especifica d para double y la otra especifica i para int. Los tipos
de valores de retorno de las funciones no se especifican en los nombres manipulados. Las funciones so-
brecargadas pueden tener distintos tipos de valores de retorno, pero si es así, también deben tener distintas
listas de parámetros. De nuevo, no se pueden tener dos funciones con la misma firma y distintos tipos de
valores de retorno. La manipulación de nombres de funciones es específica para cada compilador.
Además, la función main no está manipulada, ya que no puede sobrecargarse.
Error común de programación 6.10
Crear funciones sobrecargadas con las listas de parámetros idénticos y distintos tipos de
valores de retorno es un error de compilación.
Fig. 6.23  Funciones cuadrado sobrecargadas (parte 2 de 2).
6.18 Sobrecarga de funciones 245
1 // Fig. 6.24: fig06_24.cpp
2 // Manipulación de nombres para permitir la vinculación segura de tipos.
3
4 // función cuadrado para valores int
5 int cuadrado( int x )
6 {
7 return x * x;
8 } // fin de la función cuadrado
9
10 // función cuadrado para valores double
11 double cuadrado( double y )
12 {
13 return y * y;
14 } // fin de la función cuadrado
15
16 // función que obtiene argumentos de los tipos
17 // int, float, char e int 
18 void nada1( int a, float b, char c, int d )
19 {
20 // cuerpo vacío de la función
21 } // fin de la función nada1
22
23 // función que recibe argumentos de los tipos
24 // char, int, float  y double 
25 int nada2( char a, int b, float c, double d )
26 {
27 return 0;
28 } // fin de la función nada2
29
30 int main()
31 {
32 } // fin de main
__Z8cuadradoi
__Z8cuadradod
__Z5nada1ifcRi
__Z5nada2ciRfRd
main
Fig. 6.24  Manipulación de nombres para permitir la vinculación segura de tipos.
Elcompiladorutilizasólolaslistasdeparámetrosparadiferenciarentrelasfuncionessobrecargadas.
Dichas funciones no necesitan tener el mismo número de parámetros. Hay que tener cuidado al sobre-
cargar funciones con parámetros predeterminados, ya que esto puede provocar ambigüedades.
Error común de programación 6.11
Una función en la que se omiten sus argumentos predeterminados se podría invocar de
una manera idéntica a otra función sobrecargada; esto es un error de compilación. Por
ejemplo, al tener en un programa tanto una función que no reciba argumentos de mane-
ra explícita, como una función con el mismo nombre que contenga todos los argumentos
predeterminados, se produce un error de compilación cuando tratamos de usar el nombre
de esa función en una llamada en la que no se pasen argumentos. El compilador no sabe
cuál versión de la función elegir.
246 Capítulo 6 Funciones y una introducción a la recursividad
Operadores sobrecargados
En el capítulo 10 hablaremos acerca de cómo sobrecargar operadores, para definir la forma en que de-
ben operar con objetos de tipos de datos definidos por el usuario (de hecho, hemos estado utilizando
muchos operadores sobrecargados hasta este punto, incluyendo el operador de inserción de flujo  y el
operador de extracción de flujo , que se sobrecargan para todos los tipos fundamentales. En el capítu-
lo 10 hablaremos más acerca de cómo sobrecargar los operadores  y  para poder manejar objetos de
tipos definidos por el usuario).
6.19Plantillas de funciones
Por lo general, las funciones sobrecargadas se utilizan para realizar operaciones similares que involucren
distintos tipos de lógica de programa en distintos tipos de datos. Si la lógica del programa y las opera-
ciones son idénticas para cada tipo de datos, la sobrecarga se puede llevar a cabo de una forma más
compacta y conveniente, mediante el uso de plantillas de funciones. El programador escribe una sola
definición de plantilla de función. Dados los tipos de los argumentos que se proporcionan en las llama-
das a esta función, C++ genera de manera automática especializaciones de plantilla de función sepa-
radas para manejar cada tipo de llamada de manera apropiada. Por ende, al definir una sola plantilla de
función, en esencia se define toda una familia de funciones sobrecargadas.
La figura 6.25 define una plantilla de función maximo (líneas 3 a 17) que determina el mayor de tres
valores. Todas las definiciones de plantillas de función empiezan con la palabra clave template
(línea 3), seguidas de una lista de parámetros de plantilla para la plantilla de función encerrada entre
los paréntesis angulares ( y ). Antes de cada parámetro en la lista de parámetros (que a menudo se
conoce como un parámetro de tipo formal) se coloca la palabra clave typename o la palabra clave class
(que son sinónimos en este contexto). Los parámetros de tipo formal son receptáculos para los tipos
fundamentales, o los tipos definidos por el usuario. Estos receptáculos, en este caso T, se utilizan para
especificar los tipos de los parámetros de la función (línea 4), para especificar el tipo de valor de retorno
de la función (línea 4) y para declarar variables dentro del cuerpo de la definición de la función (línea 6).
Una plantilla de función se define de igual forma que cualquier otra función, pero utiliza los parámetros
de tipo formal como receptáculos para los tipos de datos actuales.
1 // Fig. 6.25: maximo.h
2 // Archivo de encabezado de la plantilla de la función maximo.
3 template  typename T  // o template class T 
4 T maximo( T valor1, T valor2, T valor3 )
5 {
6 T valorMaximo = valor1; // asume que valor1 es maximo
7
8 // determina si valor2 es mayor que valorMaximo
9 if ( valor2  valorMaximo )
10 valorMaximo = valor2;
11
12 // determina si valor3 es mayor que valorMaximo
13 if ( valor3  valorMaximo )
14 valorMaximo = valor3;
15
16 return valorMaximo;
17 } // fin de la plantilla de función maximo
Fig. 6.25  Archivo de encabezado de la plantilla de la función maximo.
6.19 Plantillas de funciones 247
La plantilla de función declara un solo parámetro de tipo formal T (línea 3) como receptáculo para
el tipo de datos a evaluar por la función maximo. El nombre de un parámetro de tipo debe ser único en
la lista de parámetros de la plantilla para una definición específica de ésta. Cuando el compilador detecta
una invocación a maximo en el código fuente del programa, el tipo de los datos que se pasan a maximo se
sustituye por T en toda la definición de la plantilla, y C++ crea una función completa para determinar el
máximo de tres valores del tipo de datos especificado; los tres deben tener el mismo tipo, ya que sólo
usamos un parámetro de tipo en este ejemplo. Después se compila la función recién creada; las plantillas
son un medio para generar código.
La figura 6.26 utiliza la plantilla de función maximo para determinar el mayor de tres valores int,
tresvaloresdouble ytresvaloreschar,respectivamente(líneas17,27y37).Secreanfuncionesseparadas
como resultado de las llamadas en las líneas 17, 27 y 37: esperar tres valores int, tres valores double y
tres valores char, respectivamente.
1 // Fig. 6.26: fig06_26.cpp
2 // Programa de prueba de la plantilla de función maximo.
3 #include iostream
4 #include maximo.h // incluye la definición de la plantilla de función maximo
5 using namespace std;
6
7 int main()
8 {
9 // demuestra la función maximo con valores int
10 int int1, int2, int3;
11
12 cout  Introduzca tres valores enteros: ;
13 cin  int1  int2  int3;
14
15 // invoca a la versión int de maximo
16 cout  El valor int de maximo es: 
17  maximo( int1, int2, int3 );
18
19 // demuestra la función maximo con valores double
20 double double1, double2, double3;
21
22 cout  nnIntroduzca tres valores double: ;
23 cin  double1  double2  double3;
24
25 // invoca a la versión double de maximo
26 cout  El valor double de maximo es: 
27  maximo( double1, double2, double3 );
28
29 // demuestra la función maximo con valores char
30 char char1, char2, char3;
31
32 cout  nnIntroduzca tres caracteres: ;
33 cin  char1  char2  char3;
34
35 // invoca a la versión char de maximo
36 cout  El valor char de maximo es: 
37  maximo( char1, char2, char3 )  endl;
38 } // fin de main
Fig. 6.26  Programa de prueba de la plantilla de función maximo (parte 1 de 2).
248 Capítulo 6 Funciones y una introducción a la recursividad
Introduzca tres valores enteros: 1 2 3
El valor int de maximo es: 3
Introduzca tres valores double: 3.3 2.2 1.1
El valor double de maximo es: 3.3
Introduzca tres caracteres: A C B
El valor char de maximo es: C
La especialización de plantilla de función que se crea para el tipo int reemplaza cada ocurrencia de
T con int, como se muestra a continuación:
int maximo( int valor1, int valor2, int valor3 )
{
int valorMaximo = valor1; // asume que valor1 es el máximo
// determina si valor2 es mayor que valorMaximo
if ( valor2  valorMaximo )
valorMaximo = valor2;
// determina si valor3 es mayor que valorMaximo
if ( valor3  valorMaximo )
valorMaximo = valor3;
return valorMaximo;
} // fin de la plantilla de función maximo
C++11: tipos de valores de retorno al final para las funciones
C++ introduce los tipos de valores de retorno al final para las funciones. Para especificar un tipo de
valor de retorno al final, se coloca la palabra clave auto antes del nombre de la función, luego se coloca
después de la lista de parámetros de la función el símbolo - y el tipo de valor de retorno. Por ejemplo,
para especificar un tipo de valor de retorno al final para la plantilla de función maximo (figura 6.25),
escribiríamos:
template  typename T 
auto maximo( T x, T y, T z ) - T
A medida que construya plantillas de función más complejas, habrá casos en los que sólo se permitan
tipos de valores de retorno al final. Dichas plantillas de función complejas están más allá del alcance de
este libro.
6.20Recursividad
Para algunos problemas, es conveniente hacer que las funciones se llamen a sí mismas. Una función re-
cursiva es una función que se llama a sí misma, ya sea en forma directa o indirecta (a través de otra
función). [Nota: el estándar de C++ indica que main no debe llamarse dentro de un programa ni en
forma recursiva. Su único propósito es servir de punto inicial para la ejecución del programa]. En esta
sección y en la siguiente presentaremos ejemplos simples de recursividad. La recursividad se discute
extensamente en los cursos de ciencias computacionales de nivel superior. En la figura 6.32 (al final de
la sección 6.22) se sintetizan los ejemplos y ejercicios de recursividad que se incluyen en el libro.
Conceptos de recursividad
Primero hablaremos sobre la recursividad en forma conceptual, y después analizaremos programas que
contienen funciones recursivas. Las metodologías recursivas de solución de problemas tienen varios ele-
mentos en común. Se hace una llamada a una función recursiva para resolver un problema. La función
Fig. 6.26  Programa de prueba de la plantilla de función maximo (parte 2 de 2).
6.20 Recursividad 249
en realidad sabe cómo resolver sólo el (los) caso(s) más simple(s), o caso(s) base. Si se hace la llamada a la
función con un caso base, ésta simplemente devuelve un resultado. Si se hace la llamada a la función con
un problema más complejo, la función comúnmente divide el problema en dos piezas conceptuales: una
pieza que sabe cómo resolver y otra pieza que no sabe cómo resolver. Para que la recursividad sea factible,
esta última pieza debe ser similar al problema original, pero una versión ligeramente más sencilla o simple
del mismo. Debido a que este nuevo problema se parece al problema original, la función llama a una
copiadesímisma para trabajar en elproblemamáspequeño;aestoseleconocecomollamada recursiva,
y también como paso recursivo. Por lo general, el paso recursivo incluye la palabra clave return, ya que
su resultado se combina con la parte del problema que la función supo cómo resolver, para formar un
resultado que se pasará de vuelta a la función original que hizo la llamada, que posiblemente sea main.
Error común de programación 6.12
Omitir el caso base o escribir el paso de recursividad en forma incorrecta, de modo que no
converja en el paso base, producirá un error de recursividad infinito y tal vez un desbor-
damiento de pila. Esto es análogo al problema de un ciclo infinito en una solución itera-
tiva (no recursiva).
El paso recursivo se ejecuta mientras siga “abierta” la llamada original a la función (es decir, que
no haya terminado su ejecución). Este paso puede producir muchas llamadas recursivas más, a medida
que la función divide cada nuevo subproblema, con el que se llama a la función, en dos piezas concep-
tuales. Para que la recursividad termine en un momento dado, cada vez que la función se llama a sí
misma con una versión más simple del problema original, esta secuencia de problemas cada vez más
pequeños debe converger en un caso base. En ese punto, la función reconoce el caso base y devuelve un
resultado a la copia anterior de la función; después se origina una secuencia de retornos, hasta que la
llamada a la función original devuelve el resultado final al método main. Todo esto suena bastante ex-
travagante, en comparación con el tipo de solución de problemas que hemos usado hasta este punto.
Como ejemplo de estos conceptos en la práctica, vamos a escribir un programa recursivo para realizar
un popular cálculo matemático.
Factorial
El factorial de un entero positivo n, que se escribe como n! (y se pronuncia como “factorial de n”), viene
siendo el producto
n · (n – 1) · (n – 2) · … · 1
en donde 1! es igual a 1 y 0! se define como 1. Por ejemplo, 5! es el producto 5 ⋅ 4 ⋅ 3 ⋅ 2 ⋅ 1, que es
igual a 120.
Factorial iterativo
El factorial de un numero entero más grande o igual a 0, puede calcularse de manera iterativa (sin recur-
sividad), usando una instrucción for de la siguiente manera:
factorial = 1;
for ( unsigned int contador = numero; contador = 1; --contador )
factorial *= contador;
Factorial recursivo
Podemos llegar a una definición recursiva de la función factorial, si observamos la siguiente relación
algebraica:
n! = n · (n – 1)!
250 Capítulo 6 Funciones y una introducción a la recursividad
Por ejemplo, 5! es sin duda igual a 5 *
4!, como se muestra en las siguientes ecuaciones:
5! = 5 · 4 · 3 · 2 · 1
5! = 5 · (4 · 3 · 2 · 1)
5! = 5 · (4!)
Evaluación de 5!
La evaluación de 5! procedería como se muestra en la figura 6.27, que ilustra cómo procede la sucesión
de llamadas recursivas hasta que 1! se evalúa como 1, lo cual termina la recursividad. La figura 6.27(b)
muestra los valores devueltos de cada llamada recursiva a la función que hizo la llamada, hasta que se
calcula y devuelve el valor final.
(a) Procesión de llamadas recursivas.
5 * 4!
4 * 3!
3 * 2!
2 * 1!
5!
1
(b) Valores devueltos de cada llamada recursiva.
Valor final = 120
se devuelve 5! = 5 * 24 = 120
se devuelve 4! = 4 * 6 = 24
se devuelve 3! = 3 * 2 = 6
se devuelve 2! = 2 * 1 = 2
se devuelve 1
5 * 4!
4 * 3!
3 * 2!
2 * 1!
5!
1
Fig. 6.27  Evaluación recursiva de 5!.
Uso de una función factorial recursiva para calcular los factoriales
La figura 6.28 utiliza la recursividad para calcular e imprimir los factoriales de los enteros del 0 al 10.
(En unos momentos explicaremos la elección del tipo de datos unsigned long). La función recursiva
factorial (líneas 18 a 24) determina primero si la condición de terminación numero = 1 (línea 20)
es verdadera. Si numero es menor o igual que 1, la función factorial devuelve 1 (línea 21), ya no es
necesaria más recursividad y la función termina. Si numero es mayor que 1, en la línea 23 se expresa
el problema como el producto de numero y una llamada recursiva a factorial en la que se evalúa el
factorial de numero – 1, que es un problema un poco más simple que el cálculo original, factorial
(numero).
Por qué elegimos el tipo unsigned long en este ejemplo
La función factorial se ha declarado para recibir un parámetro de tipo unsigned long y devolver un
resultado de tipo unsigned long. Ésta es una notación abreviada para unsigned long int. El estándar
de C++ requiere que una variable de tipo unsigned long int sea por lo menos tan grande como un valor
6.20 Recursividad 251
1 // Fig. 6.28: fig06_28.cpp
2 // Función recursiva factorial.
3 #include iostream
4 #include iomanip
5 using namespace std;
6
7 unsigned long factorial( unsigned long ); // prototipo de función
8
9 int main()
10 {
11 // calcula los factoriales del 0 al 10
12 for ( unsigned int contador = 0; contador = 10; ++contador )
13 cout  setw( 2 )  contador  ! =   factorial( contador )
14  endl;
15 } // fin de main
16
17 // definición recursiva de la función factorial
18 unsigned long factorial( unsigned long numero )
19 {
20 if ( numero = 1 ) // evalúa el caso base
21 return 1; // casos base: 0! = 1 y 1! = 1
22 else // paso recursivo
23 return numero * factorial( numero - 1 );
24 } // fin de la función factorial
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
Fig. 6.28  Función recursiva factorial.
int. Por lo general, un valor unsigned long int se almacena en por lo menos cuatro bytes (32 bits);
dicha variable puede contener un valor en el rango desde 0 hasta (por lo menos) 4294967295. (El tipo
de datos long int también se almacena por lo menos en cuatro bytes, y puede contener un valor en por
lo menos el rango de –2147483647 a 2147483647). Como se puede ver en la figura 6.28, los valores
de los factoriales aumentan su tamaño rápidamente. Elegimos el tipo de datos unsigned long de ma-
nera que el programa pueda calcular factoriales mayores que 7! en las computadoras con enteros peque-
ños (como los de dos bytes). Por desgracia, la función factorial produce valores extensos con tanta
rapidez que, incluso el tipo unsigned long no nos ayuda a calcular muchos valores de factorial antes de
que se exceda el valor de una variable unsigned long.
El tipo unsigned long long int de C++11
El nuevo tipo unsigned long long int de C++ (que puede abreviarse como unsigned long long) en
algunos sistemas nos permite almacenar valores en 8 bytes (64 bits), para guardar números tan grandes
como 18446744073709551615.
252 Capítulo 6 Funciones y una introducción a la recursividad
Representar números aún más grandes
Podrían usarse variables de tipo de datos double para calcular los factoriales de números más grandes.
Esto apunta a una debilidad en la mayoría de los lenguajes de programación, a saber, que los lenguajes no
se extienden fácilmente para manejar los requerimientos únicos de varias aplicaciones. Como veremos
cuando hablemos sobre la programación orientada a objetos con más detalle, C++ es un lenguaje exten-
sible que nos permite crear clases que puedan representar enteros arbitrariamente grandes, si lo deseamos.
Dichas clases ya están disponibles en bibliotecas de clases populares, y trabajaremos en nuestras propias
clases similares en los ejercicios 9.14 y 10.9.
6.21Ejemplo sobre el uso de la recursividad: serie de Fibonacci
La serie de Fibonacci,
0, 1, 1, 2, 3, 5, 8, 13, 21, …
empieza con 0 y 1, y tiene la propiedad de que cada número subsiguiente de Fibonacci es la suma de los
dos números Fibonacci anteriores.
Esta serie ocurre en la naturaleza y, en específico, describe una forma de espiral. La proporción de
números de Fibonacci sucesivos converge en un valor constante de 1.618…. Este número también
ocurre con frecuencia en la naturaleza, y se le ha denominado proporción dorada, o media dorada.
Los humanos tienden a descubrir que la media dorada es estéticamente placentera. A menudo, los ar-
quitectos diseñan ventanas, cuartos y edificios cuya longitud y anchura se encuentran en la proporción
de la media dorada. A menudo se diseñan tarjetas postales con una proporción de anchura/altura de
media dorada.
Definición recursiva de Fibonacci
La serie de Fibonacci se puede definir de manera recursiva como:
fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(n) = fibonacci(n – 1) + fibonacci(n – 2)
El programa de la figura 6.29 calcula el n-ésimo número de Fibonacci en forma recursiva, usando la
función fibonacci. Los números de Fibonacci tienden a aumentar con bastante rapidez, aunque son
más lentos que los factoriales. Por lo tanto, elegimos el tipo de datos unsigned long para el tipo del
parámetroyeltipodevalorderetornoenlafunciónfibonacci.Enlafigura6.29semuestralaejecución
del programa, que muestra los valores de Fibonacci para varios números.
La aplicación empieza con una instrucción for que calcula y muestra los valores de Fibonacci para
los enteros 0 a 10, y va seguida de tres llamadas para calcular los valores Fibonacci de los enteros 20, 30
y 35 (líneas 16 a 18). Las llamadas a la función fibonacci (líneas 13 y 16 a 18) de main no son llamadas
recursivas, pero las llamadas de la línea 27 de fibonacci son recursivas. Cada vez que el programa invo-
ca a fibonacci (líneas 22 a 28), la función evalúa de inmediato el caso base para determinar si numero
es igual a 0 o 1 (línea 24). Si esto es verdadero, en la línea 25 se devuelve numero. Lo interesante es que,
si numero es mayor que 1, el paso recursivo (línea 27) genera dos llamadas recursivas, cada una para un
problema ligeramente más pequeño que la llamada original a fibonacci.
1 // Fig. 6.29: fig06_29.cpp
2 // Función recursiva fibonacci.
3 #include iostream
Fig. 6.29  Función recursiva fibonacci (parte 1 de 2).
6.21 Ejemplo sobre el uso de la recursividad: serie de Fibonacci 253
4 using namespace std;
5
6 unsigned long fibonacci( unsigned long ); // prototipo de función
7
8 int main()
9 {
10 // calcula los valores de fibonacci del 0 al 10
11 for ( unsigned int contador = 0; contador = 10; ++contador )
12 cout  fibonacci(   contador   ) = 
13  fibonacci( contador )  endl;
14
15 // muestra valores de fibonacci mayores
16 cout  “nfibonacci( 20 ) = “  fibonacci( 20 )  endl;
17 cout  “fibonacci( 30 ) = “  fibonacci( 30 )  endl;
18 cout  “fibonacci( 35 ) = “  fibonacci( 35 )  endl;
19 } // fin de main
20
21 // método fibonacci recursivo
22 unsigned long fibonacci( unsigned long numero )
23 {
24 if ( ( 0 == numero ) || ( 1 == numero ) ) // casos base
25 return numero;
26 else // paso recursivo
27 return fibonacci( numero - 1 ) + fibonacci( numero - 2 );
28 } // fin de la función fibonacci
fibonacci( 0 ) = 0
fibonacci( 1 ) = 1
fibonacci( 2 ) = 1
fibonacci( 3 ) = 2
fibonacci( 4 ) = 3
fibonacci( 5 ) = 5
fibonacci( 6 ) = 8
fibonacci( 7 ) = 13
fibonacci( 8 ) = 21
fibonacci( 9 ) = 34
fibonacci( 10 ) = 55
fibonacci( 20 ) = 6765
fibonacci( 30 ) = 832040
fibonacci( 35 ) = 9227465
Evaluación de fibonacci(3)
La figura 6.30 muestra cómo la función fibonacci evaluaría fibonacci(3). Esta figura genera ciertas
preguntas interesantes, en cuanto al orden en el que los compiladores de C++ evalúan los operandos de
los operadores. Este orden es distinto del orden en el que se aplican los operadores a sus operandos; a
saber, el orden que dictan las reglas de la precedencia y asociatividad de los operadores. La figura 6.30
muestra que al evaluar fibonacci(3), se producen dos llamadas recursivas: fibonacci(2) y
fibonacci(1). ¿En qué orden se hacen estas llamadas?
Fig. 6.29  Función recursiva fibonacci (parte 2 de 2).
254 Capítulo 6 Funciones y una introducción a la recursividad
fibonacci( 3 )
fibonacci( 2 ) fibonacci( 1 )
return +
fibonacci( 1 ) fibonacci( 0 ) return 1
return 0
return 1
return +
Fig. 6.30  Conjunto de llamadas recursivas a la función fibonacci.
Orden de evaluación de los operandos
La mayoría de los programadores simplemente suponen que los operandos se evalúan de izquierda a
derecha. C++ no especifica el orden en el que se van a evaluar los operandos de la mayoría de los opera-
dores (incluyendo +). Por lo tanto, no debemos hacer suposiciones en cuanto al orden en el que se eva-
luarán estas llamadas. De hecho, se podría ejecutar fibonacci(2) primero y después fibonacci(1),
o se podrían ejecutar en el orden inverso: fibonacci(1) y luego fibonacci(2). En este programa y en
la mayoría de los otros programas, el resultado sería el mismo. Sin embargo, en algunos programas la
evaluación de un operando puede tener efectos secundarios (cambios en los valores de los datos) que
podrían afectar al resultado final de la expresión.
C++ especifica el orden de evaluación de los operandos sólo para cuatro operadores: , ||, el ope-
rador coma (,) y ?:. Los primeros tres son operadores binarios, y se garantiza que sus dos operandos se
evaluarán de izquierda a derecha. El último operador es el único operador ternario de C++. Su operando
de más a la izquierda siempre se evalúa primero; si se evalúa como verdadero, el operando intermedio se
evalúaacontinuaciónyseignoraelúltimooperando;sieloperandodemásalaizquierdaseevalúacomo
falso, el tercer operando se evalúa a continuación y se ignora el operando intermedio.
Tip de portabilidad 6.2
Los programas que dependen del orden de evaluación de los operandos de operadores dis-
tintos de , ||, ?: y el operador coma (,) pueden funcionar de manera distinta en siste-
mas con distintos compiladores y producir errores lógicos.
Error común de programación 6.13
La escritura de programas que dependan del orden de evaluación de los operandos de
operadores distintos de , ||, ?: y el operador coma (,) pueden producir errores lógicos.
Tip para prevenir errores 6.9
No dependa del orden en el que se evalúan los operandos. Para asegurar que los efectos
secundarios se apliquen en el orden correcto, divida las expresiones complejas en instruc-
ciones separadas.
6.22 Comparación entre recursividad e iteración 255
Error común de programación 6.14
Recuerde que los operadores  y || usan la evaluación de corto circuito. Colocar una
expresión con un efecto secundario del lado derecho de un operador  o || es un error
lógico si esa expresión siempre debe evaluarse.
Complejidad exponencial
Hay que tener cuidado con los programas recursivos, como el que usamos aquí para generar números
de Fibonacci. Cada nivel de recursividad en la función fibonacci tiene un efecto de duplicación sobre
el número de llamadas a funciones; es decir, el número de llamadas recursivas que se requieren para
calcular el n-ésimo número de Fibonacci se encuentra en el orden de 2n
. Esto se sale rápidamente de
control. Para calcular sólo el 20vo. número de Fibonacci se requeriría un orden de 220
, o cerca de un
millón de llamadas, para calcular el 30vo. número de Fibonacci se requeriría un orden de 230
, o aproxi-
madamente mil millones de llamadas, y así en lo sucesivo. Los científicos computacionales se refieren a
esto como complejidad exponencial. Los problemas de esta naturaleza pueden humillar incluso hasta
a las computadoras más poderosas del mundo. Las cuestiones relacionadas con la complejidad, tanto en
general como en particular, se discuten con detalle en un curso del plan de estudios de ciencias compu-
tacionales de nivel superior, al que generalmente se le llama “Algoritmos”.
Tip de rendimiento 6.8
Evite los programas recursivos al estilo Fibonacci que produzcan una “explosión” exponen-
cial de llamadas.
6.22Comparación entre recursividad e iteración
En las dos secciones anteriores, estudiamos dos funciones recursivas que también pueden implementar-
se mediante programas iterativos simples. En esta sección comparamos las dos metodologías, y habla-
mos acerca del por qué podríamos elegir una metodología sobre la otra en una situación específica.
• Tanto la iteración como la recursividad se basan en una instrucción de control: la iteración utili-
za una estructura de repetición; la recursividad utiliza una estructura de selección.
• Tanto la iteración como la recursividad implican la repetición: la iteración utiliza en forma ex-
plícita una estructura de repetición; la recursividad logra la repetición a través de llamadas repe-
tidas a una función.
• La iteración y la recursividad implican una prueba de terminación: la iteración termina cuando
fallalacondicióndecontinuacióndeciclo;larecursividadterminacuandosereconoceuncasobase.
• La iteración mediante la repetición controlada por un contador y la recursividad se acercan
gradualmente a la terminación: la iteración modifica un contador hasta que éste asuma un valor
que haga que falle la condición de continuación de ciclo; la recursividad produce versiones más
simples del problema original hasta que se llega al caso base.
• Tanto la iteración como la recursividad pueden ocurrir infinitamente: un ciclo infinito ocurre
con la iteración si la prueba de continuación de ciclo nunca se vuelve falsa; la recursividad infi-
nita ocurre si el paso recursivo no reduce el problema durante cada llamada recursiva, de forma
tal que llegue a converger en el caso base.
Implementación del factorial iterativo
Para ilustrar las diferencias entre la iteración y la recursividad, vamos a examinar una solución itera-
tiva para el problema del factorial (figura 6.31). Se utiliza una instrucción de repetición (líneas 23 y
24 de la figura 6.31) en vez de la instrucción de selección de la solución recursiva (líneas 20 a 23 de la
256 Capítulo 6 Funciones y una introducción a la recursividad
figura 6.28). Ambas soluciones usan una prueba de terminación. En la solución recursiva, en la línea
20 (figura 6.28) se evalúa el caso base. En la solución iterativa, en la línea 23 (figura 6.31) se evalúa la
condición de continuación de ciclo; si la prueba falla, el ciclo termina. Por último, en vez de producir
versiones cada vez más simples del problema original, la solución iterativa utiliza un contador que se
modifica hasta que la condición de continuación de ciclo se vuelve falsa.
1 // Fig. 6.31: fig06_31.cpp
2 // Función iterativa factorial.
3 #include iostream
4 #include iomanip
5 using namespace std;
6
7 unsigned long factorial( unsigned int ); // prototipo de función
8
9 int main()
10 {
11 // calcula los factoriales del 0 al 10
12 for ( unsigned int contador = 0; contador = 10; ++contador )
13 cout  setw( 2 )  contador  ! =   factorial( contador )
14  endl;
15 } // fin de main
16
17 // función factorial iterativa
18 unsigned long factorial( unsigned int numero )
19 {
20 unsigned long resultado = 1;
21
22 // cálculo iterativo del factorial
23 for ( unsigned int i = numero; i = 1; --i )
24 resultado *= i;
25
26 return resultado;
27 } // fin de la función factorial
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
Fig. 6.31  Función iterativa factorial.
Desventajas de la recursividad
La recursividad tiene muchas desventajas. Invoca al mecanismo en forma repetida, y en consecuencia se
produce una sobrecarga de las llamadas a la función. Esta repetición puede ser perjudicial, en términos
de tiempo del procesador y espacio de la memoria. Cada llamada recursiva crea otra copia de las variables
de la función; esto puede consumir una cantidad considerable de memoria. Como la iteración ocurre
6.22 Comparación entre recursividad e iteración 257
comúnmente dentro de una función, se evita la sobrecarga de las llamadas repetidas a la función y la
asignación adicional de memoria. Entonces, ¿por qué elegir la recursividad?
Observación de Ingeniería de Software 6.13
Cualquier problema que se pueda resolver mediante la recursividad, se puede resolver
también mediante la iteración (sin recursividad). Por lo general se prefiere un método
recursivo a uno iterativo cuando el primero refleja con más naturalidad el problema, y se
produce un programa más fácil de entender y de depurar. Otra razón por la que es prefe-
rible elegir una solución recursiva es que una iterativa podría no ser aparente.
Tip de rendimiento 6.9
Evite usar la recursividad en situaciones en las que se requiera un alto rendimiento. Las
llamadas recursivas requieren tiempo y consumen memoria adicional.
Error común de programación 6.15
Hacer que una función no recursiva se llame a sí misma por accidente, ya sea en forma
directa o indirecta (a través de otra función), es un error lógico.
Resumen de los ejemplos y ejercicios de recursividad en este libro
En la figura 6.32 se sintetizan los ejemplos de recursividad y los ejercicios que se incluyen en el libro.
Ubicación en el libro Ejemplos y ejercicios de recursividad
Capítulo 6
Sección 6.20, fig. 6.28 Función factorial
Sección 6.21, fig. 6.29 Función Fibonacci
Ejercicio 6.36 Exponenciación recursiva
Ejercicio 6.38 Torres de Hanoi
Ejercicio 6.40 Visualización de la recursividad
Ejercicio 6.41 Máximo común divisor
Ejercicios 6.44 y 6.45 Ejercicio “¿Qué hace este programa?”
Capítulo 7
Ejercicio 7.17 Ejercicio “¿Qué hace este programa?”
Ejercicio 7.20 Ejercicio “¿Qué hace este programa?”
Ejercicio 7.28 Determinar si una cadena es un palíndromo
Ejercicio 7.29 Ocho reinas
Ejercicio 7.30 Imprimir un arreglo
Ejercicio 7.31 Imprimir una cadena en forma inversa
Ejercicio 7.32 Valor mínimo en un arreglo
Ejercicio 7.33 Recorrido de laberinto
Ejercicio 7.34 Generación de laberintos al azar
Fig. 6.32  Resumen de los ejemplos y ejercicios de recursividad en el libro (parte 1 de 2).
258 Capítulo 6 Funciones y una introducción a la recursividad
Ubicación en el libro Ejemplos y ejercicios de recursividad
Capítulo 19 (en el sitio web)
Sección 19.6, figs. 19.20 y 19.22 Inserción de árboles binarios
Sección 19.6, figs. 19.20 y 19.22 Recorrido preorden de un árbol binario
Sección 19.6, figs. 19.20 y 19.22 Recorrido inorden de un árbol binario
Sección 19.6, figs. 19.20 y 19.22 Recorrido postorden de un árbol binario
Ejercicio 19.20 Imprimir una lista enlazada en forma inversa
Ejercicio 19.21 Buscar en una lista enlazada
Ejercicio 19.22 Eliminación de árboles binarios
Ejercicio 19.23 Búsqueda de árboles binarios
Ejercicio 19.24 Recorrido por orden de nivel de un árbol binario
Ejercicio 19.25 Árbol de impresión
Capítulo 20 (en el sitio web)
Sección 20.3.3, fig. 20.6 Ordenamiento por combinación
Ejercicio 20.8 Búsqueda lineal
Ejercicio 20.9 Búsqueda binaria
Ejercicio 20.10 Quicksort
6.23Conclusión
En este capítulo aprendió más acerca de las declaraciones de funciones, incluyendo los prototipos, las
firmas, los encabezados y los cuerpos de las funciones. Vimos las generalidades sobre las funciones
matemáticas de la biblioteca. Aprendió acerca de la coerción de argumentos, o la acción de forzar a que
los argumentos sean de los tipos apropiados que se especifiquen mediante las declaraciones de los pa-
rámetros de una función. Demostramos cómo usar las funciones rand y srand para generar conjuntos
de números aleatorios que se puedan utilizar para las simulaciones. Le mostramos cómo definir con-
juntos de constantes mediante enum. Aprendió también acerca del alcance de las variables, los especi-
ficadores de clase de almacenamiento y la duración del almacenamiento. Vimos dos formas distintas
de pasar argumentos a las funciones: paso por valor y paso por referencia. Para el paso por referencia,
las referencias se utilizan como un alias para una variable. Le mostramos cómo implementar funciones
en línea y funciones que reciban argumentos predeterminados. Aprendió que varias funciones en una
clase se pueden sobrecargar, usando el mismo nombre y distintas firmas. Dichas funciones se pueden
utilizar para realizar las mismas tareas (o similares), usando distintos tipos o números de parámetros.
Después, demostramos una manera más simple de sobrecargar funciones mediante las plantillas de
función, en donde una función se define una sola vez, pero se puede usar para varios tipos distintos.
Posteriormente le presentamos el concepto de la recursividad, en donde una función se llama a sí mis-
ma para resolver un problema.
En el capítulo 7, aprenderá a mantener listas y tablas de datos en arreglos y vectores (vector) orien-
tados a objetos. Veremos una implementación basada en arreglo más elegante de la aplicación para tirar
dados, y dos versiones mejoradas del caso de estudio LibroCalificaciones que estudiamos en los ca-
pítulos 3 a 6, en donde utilizaremos arreglos para almacenar las calificaciones introducidas.
Fig. 6.32  Resumen de los ejemplos y ejercicios de recursividad en el libro (parte 2 de 2).
Resumen 259
Resumen
Sección 6.1 Introducción
• La experiencia ha demostrado que la mejor forma de desarrollar y mantener un programa extenso es construir-
lo a partir de piezas simples y pequeñas. A esta técnica se le conoce como divide y vencerás (pág. 202).
Sección 6.2 Componentes de los programas en C++
• Por lo general, los programas en C++ se escriben mediante la combinación de nuevas funciones y clases que
escribimos con funciones “pre-empaquetadas”, y clases disponibles en la Biblioteca estándar de C++.
• Las funciones permiten al programador modularizar un programa, al separar sus tareas en unidades autocon-
tenidas.
• Las instrucciones en los cuerpos de las funciones se escriben sólo una vez, se pueden reutilizar tal vez desde varias
ubicaciones en un programa y además están ocultas de las demás funciones.
Sección 6.3 Funciones matemáticas de la biblioteca
• Algunas veces las funciones no son miembros de una clase. A dichas funciones se les conoce como funciones
globales (pág. 204).
• A menudo, los prototipos para las funciones globales se colocan en encabezados, de manera que las funciones
globales se puedan reutilizar en cualquier programa que incluya el encabezado y se pueda crear un enlace con el
código objeto de la función.
Sección 6.4 Definiciones de funciones con varios parámetros
• El compilador hace referencia al prototipo de función, para comprobar que las llamadas a una función tengan
el número y tipos de argumentos correctos, que los tipos de los argumentos estén en el orden correcto y que el
valor devuelto por la función se pueda utilizar de manera correcta en la expresión que llamó a la función.
• Si una función no devuelve un resultado, el control regresa cuando el programa llega a la llave derecha de fin de
la función, o mediante la ejecución de la instrucción
return;
Si una función devuelve un resultado, la instrucción
return expresión;
evalúa expresión y devuelve el valor de expresión a la función que hizo la llamada.
Sección 6.5 Prototipos de funciones y coerción de argumentos
• La porción de un prototipo de función que incluye el nombre de la función y los tipos de sus argumentos se
conoce como la firma de la función (pág. 211), o simplemente firma.
• Una característica importante de los prototipos de función es la coerción de argumentos (pág. 211); es decir,
obligar a que los argumentos tengan los tipos especificados por las declaraciones de los parámetros.
• El compilador puede convertir los argumentos a los tipos de parámetros según lo especificado por las reglas de
promoción de C++ (pág. 211). Las reglas de promoción indican las conversiones implícitas que el compilador
puede realizar entre tipos fundamentales.
Sección 6.6 Encabezados de la Biblioteca estándar de C++
• La Biblioteca estándar de C++ está dividida en muchas porciones, cada una con su propio encabezado. Los
encabezados también contienen definiciones de varios tipos de clases, funciones y constantes.
• Un encabezado “instruye” al compilador acerca de cómo interconectarse con los componentes de la biblioteca.
Sección 6.7 Caso de estudio: generación de números aleatorios
• Llamar a rand (pág. 214) en forma repetida produce una secuencia de números seudoaleatorios (pág. 217). La
secuencia se repite cada vez que se ejecute el programa.
260 Capítulo 6 Funciones y una introducción a la recursividad
• Para randomizar los números producidos por rand, se pasa un argumento unsigned integer (por lo general de
la función time; pág. 219) a la función srand (pág. 217), la cual siembra la función rand.
• Los números aleatorios en un rango se pueden generar de la siguiente manera:
numero = valorDesplazamiento+ rand() % factorEscala;
en donde valorDesplazamiento (pág. 219) especifica el primer número en el rango deseado de enteros consecu-
tivos y factorEscala (pág. 219) es igual a la anchura del rango deseado de enteros consecutivos.
Sección 6.8 Caso de estudio: juego de probabilidad: introducción a las enum
• Una enumeración, que se introduce mediante la palabra clave enum y va seguida de un nombre de tipo
(pág. 222), es un conjunto de constantes enteras con nombres (pág. 222) que empiezan en 0, a menos que se
especifique lo contrario, y se incrementan en 1.
• enum sinalcancepuedenproducirconflictosdenombresyerroreslógicos.Paraeliminarestosproblemas,C++11
introduce enum con alcance (pág. 223), que se declaran con las palabras clave enum class (o con el sinónimo
enum struct).
• Para hacer referencia a una constante enum con alcance, debemos calificar la constante con el nombre del tipo
de la enum con alcance y con el operador de resolución de ámbito (::). Si otra enum con alcance contiene el
mismo identificador que para una de sus constantes, siempre queda claro cuál versión de la constante se está
utilizando.
• Las constantes en una enum se representan como enteros.
• El tipo integral subyacente de una enum sin alcance depende de los valores de sus constantes; se garantiza que el
tipo será lo bastante grande como para poder almacenar los valores constantes especificados.
• El tipo integral subyacente de la enum con alcance es int, de manera predeterminada.
• C++11 nos permite especificar el tipo integral subyacente de una enum, colocando después del nombre del tipo
de enum dos puntos (:) y el tipo integral.
• Se produce un error de compilación si el valor de la constante de una enum está fuera del rango que puede repre-
sentarse por el tipo subyacente de la enum.
Sección 6.9 Números aleatorios de C++11
• De acuerdo con CERT, la función rand no tiene “buenas propiedades estadísticas” y puede ser predecible, lo
que hace a los programas que usan rand menos seguros.
• C++11 cuenta con una nueva biblioteca más segura de herramientas de números aleatorios, que puede producir
números aleatorios no determinísticos para simulaciones y casos de seguridad, en donde la predictibilidad es
indeseable.Estasnuevasherramientasseencuentranenelencabezadorandom delaBibliotecaestándardeC++.
• Para tener flexibilidad con base en la forma en que se utilizan los números aleatorios en los programas, C++11
provee muchas clases que representan varios motores de generación de números aleatorios y distribuciones. Un
motor implementa un algoritmo de generación de números aleatorios que produce números seudoaleatorios.
Una distribución controla el rango de valores producidos por un motor, los tipos de esos valores y las propieda-
des estadísticas de los valores.
• El tipo default_random_engine (pág. 224) representa el motor de generación de números aleatorios predeter-
minado.
• La distribución uniform_int_distribution (pág. 224) distribuye de manera uniforme los enteros seudoalea-
torios a través de un rango especificado de valores. El rango predeterminado es desde 0 hasta el valor máximo
de un int en su plataforma.
Sección 6.10 Clases y duración de almacenamiento
• La duración de almacenamiento de un identificador (pág. 225) determina el periodo durante el cual éste existe
en la memoria.
• El alcance de un identificador es la parte en la que se puede hacer referencia a éste en un programa.
• El enlace de un identificador (pág. 225) determina si se conoce sólo en el archivo fuente en el que se declara, o
en varios archivos fuente que se compilan y después se enlazan juntos.
Resumen 261
• Las variables con duración de almacenamiento automática incluyen las variables locales declaradas en las fun-
ciones, los parámetros de función y las variables locales o los parámetros de función declarados con register
(pág. 225). Dichas variables se crean cuando la ejecución del programa entra en el bloque en el que están defi-
nidas, existen mientras el bloque está activo y se destruyen cuando el programa sale del bloque.
• Las palabras clave extern (pág. 225) y static declaran identificadores para variables de la duración de almace-
namiento estática (pág. 225) y para funciones. Las variables de duración de almacenamiento estática existen a
partir del punto en el que el programa empieza a ejecutarse, y dejan de existir cuando termina el programa.
• El almacenamiento de una variable con duración de almacenamiento estático se asigna cuando el programa
empieza su ejecución. Dicha variable se inicializa una vez al encontrar su declaración. Para las funciones, el nom-
bre de la función existe cuando el programa empieza a ejecutarse, de igual forma que para las otras funciones.
• Los identificadores externos (como las variables globales) y las variables locales declaradas con el especificador
de clase de almacenamiento static tienen duración de almacenamiento estática (pág. 225).
• Las declaraciones de variables globales (pág. 227) se colocan fuera de cualquier definición de clase o función.
Las variables globales retienen sus valores a lo largo de la ejecución del programa. Las variables y las funciones
globales se pueden referenciar mediante cualquier función que siga sus declaraciones o definiciones.
• A diferencia de las variables automáticas, las variables locales static retienen sus valores cuando la función en
la que se declaran regresa a la que hizo la llamada.
Sección 6.11 Reglas de alcance
• Un identificador que se declara fuera de cualquier función o clase tiene alcance de espacio de nombres global
(pág. 228).
• Los identificadores que se declaran dentro de un bloque tienen alcance de bloque (pág. 228), el cual empieza en
la declaración del identificador y termina en la llave derecha de finalización (}) del bloque en el que se declara
el identificador.
• Las etiquetas son los únicos identificadores con alcance de función (pág. 228). Las etiquetas pueden usarse en
cualquier parte de la función en la que aparezcan, pero no pueden referenciarse fuera del cuerpo de la función.
• Un identificador que se declara fuera de una función o clase tiene alcance de espacio de nombres global. Dicho
identificador es “conocido” en todas las funciones, desde el punto en el que se declara hasta el final del archivo.
• Los identificadores en la lista de parámetros de un prototipo de función tienen alcance de prototipo de función
(pág. 228).
Sección 6.12 La pila de llamadas a funciones y los registros de activación
• Las pilas (pág. 231) se denominan estructuras de datos “último en entrar, primero en salir” (UEPS); el último
elemento que se mete (inserta; pág. 231) en la pila es el primero que se saca (extrae; pág. 231) de ella.
• La pila de llamadas a funciones (pág. 232) soporta el mecanismo de llamadas/regresos a funciones y la creación,
mantenimiento y destrucción de las variables automáticas de cada función a la que se llama.
• Cada vez que una función llama a otra, se mete un marco de pila o registro de activación (pág. 232) a la pila, el
cual contiene la dirección de retorno que necesita la función a la que se llamó para poder regresar a la función
que hizo la llamada, junto con las variables automáticas y parámetros de la llamada a la función.
• El marco de pila (pág. 232) existe mientras la función a la que se llamó esté activa. Cuando esa función regresa,
su marco de pila se saca de la pila y sus variables automáticas locales dejan de existir.
Sección 6.13 Funciones con listas de parámetros vacías
• En C++, una lista de parámetros vacía se especifica mediante void o nada entre paréntesis.
Sección 6.14 Funciones en línea
• C++ cuenta con las funciones en línea (pág. 236) para ayudar a reducir la sobrecarga de las llamadas a funciones;
en especial para las funciones pequeñas. Al colocar el calificador inline (pág. 236) antes del tipo de valor de
retorno de la función en su definición, se aconseja al compilador para que genere una copia del código de la
función en cada lugar en donde se llame a la función, para evitar la llamada a una función.
262 Capítulo 6 Funciones y una introducción a la recursividad
• Los compiladores pueden poner código en línea para el que no hayamos utilizado de manera explícita la palabra
clave inline. Los compiladores optimizadores modernos son tan sofisticados, que es mejor dejar a ellos las de-
cisiones sobre poner código en línea.
Sección 6.15 Referencias y parámetros por referencia
• Cuando se pasa un argumento por valor (pág. 237), se crea una copia del valor del argumento y se pasa a la
función que se llamó. Las modificaciones a la copia no afectan al valor de la variable original en la función que
hizo la llamada.
• Mediante el paso por referencia (pág. 237), la función que hace la llamada proporciona a la función que llamó
lahabilidaddeaccederdirectamentealosdatosdelaprimera,ydemodificaresosdatosencasodequelafunción
que se llamó así lo decida.
• Un parámetro por referencia (pág. 238) es un alias para su correspondiente argumento en la llamada a una
función.
• Para indicar que un parámetro de función se pasa por referencia, simplemente hay que colocar un signo “”
después del tipo del parámetro en el prototipo de la función y en su encabezado.
• Todas las operaciones que se realizan en una referencia en realidad se realizan en la variable original.
Sección 6.16 Argumentos predeterminados
• Cuando una función se invoca repetidas veces con el mismo argumento para un parámetro específico, podemos
especificar que dicho parámetro tiene un argumento predeterminado (pág. 240).
• Cuando un programa omite un argumento para un parámetro con un argumento predeterminado, el compila-
dor inserta el valor predeterminado del argumento para pasarlo a la llamada a la función.
• Los argumentos predeterminados deben ser los argumentos de más a la derecha en la lista de parámetros de una
función.
• Los argumentos predeterminados deben especificarse en el prototipo de la función.
Sección 6.17 Operador de resolución de ámbito unario
• C++ proporciona el operador de resolución de ámbito unario (::) (pág. 242) para acceder a una variable global
cuando una variable local con el mismo nombre se encuentra dentro del alcance.
Sección 6.18 Sobrecarga de funciones
• C++ permite definir varias funciones con el mismo nombre, siempre y cuando éstas tengan diferentes conjuntos
de parámetros. A esta capacidad se le conoce como sobrecarga de funciones (pág. 243).
• Cuando se hace una llamada a una función sobrecargada, el compilador de C++ selecciona la función apropia-
da al examinar el número, tipos y orden de los argumentos en la llamada.
• Las funciones sobrecargadas se diferencian mediante sus firmas.
• El compilador codifica cada identificador de función con los tipos de sus parámetros para permitir un enlace
seguro de tipos (pág. 244). Este tipo de enlace asegura que se llame a la función sobrecargada apropiada, y que
los tipos de los argumentos se conformen a los tipos de los parámetros.
Sección 6.19 Plantillas de funciones
• Por lo general, las funciones sobrecargadas realizan operaciones similares que involucren distintos tipos de ló-
gica de programa en distintos tipos de datos. Si la lógica del programa y las operaciones son idénticas para cada
tipo de datos, la sobrecarga se puede llevar a cabo de una forma más compacta y conveniente, mediante el uso
de plantillas de funciones (pág. 246).
• Dados los tipos de los argumentos que se proporcionan en las llamadas a una plantilla de función, C++ genera
de manera automática especializaciones de plantilla de función separadas (pág. 246) para manejar cada tipo de
llamada de manera apropiada.
• Todas las definiciones de plantillas de función empiezan con la palabra clave template (pág. 246) seguida de
una lista de parámetros de plantilla (pág. 246) para la plantilla de función encerrada entre los paréntesis angu-
lares ( y ).
Ejercicios de autoevaluación 263
• Los parámetros de tipo formal (pág. 246) son precedidos por la palabra clave typename (o class) y son recep-
táculos para los tipos fundamentales, o los tipos definidos por el usuario. Estos receptáculos se utilizan para
especificar los tipos de los parámetros de la función, para especificar el tipo de valor de retorno de la función y
para declarar variables dentro del cuerpo de la definición de la función.
• C++11 introduce los tipos de valores de retorno al final para las funciones. Para especificar este tipo de valor de
retorno al final, coloque la palabra clave auto antes del nombre de la función y coloque después de la lista
de parámetros de la función el signo - y el tipo de valor de retorno.
Sección 6.20 Recursividad
• Una función recursiva (pág. 248) es una función que se llama a sí misma, ya sea en forma directa o indirecta.
• Una función recursiva sabe cómo resolver sólo el (los) caso(s) más simple(s), o caso(s) base. Si se hace la llamada
a la función con un caso base (pág. 249), ésta simplemente devuelve un resultado.
• Si se hace la llamada a la función con un problema más complejo, la función comúnmente divide el problema
en dos piezas conceptuales: una que sabe cómo resolver y otra que no sabe cómo. Para que la recursividad sea
factible, esta última pieza debe ser similar al problema original, pero una versión ligeramente más sencilla o
simple del mismo.
• Para que la recursividad termine, la secuencia llamadas recursivas (pág. 249) debe converger en el caso base.
• El nuevo tipo unsigned long long int de C++11 (que puede abreviarse como unsigned long long) en algunos
sistemas nos permite almacenar valores en 8 bytes (64 bits), que pueden contener números tan grandes como
18446744073709551615.
Sección 6.21 Ejemplo sobre el uso de la recursividad: serie de Fibonacci
• La proporción de números de Fibonacci sucesivos converge en un valor constante de 1.618… Este número
también ocurre con frecuencia en la naturaleza, y se le ha denominado proporción dorada, o media dorada
(pág. 252).
Sección 6.22 Comparación entre recursividad e iteración
• La iteración (pág. 249) y la recursividad tienen muchas similitudes: ambas se basan en una instrucción de con-
trol, implican la repetición, implican una prueba de terminación, se acercan gradualmente a la terminación y
pueden ocurrir infinitamente.
• La recursividad invoca al mecanismo en forma repetida, y en consecuencia se produce una sobrecarga de las
llamadas a la función. Cada llamada recursiva (pág. 249) crea otra copia de las variables de la función; este
conjunto de copias puede consumir una cantidad considerable de espacio en memoria.
Ejercicios de autoevaluación
6.1 Complete las siguientes oraciones:
a) En C++, los componentes de un programa se llaman ________ y ________.
b) Una función se invoca con un(a) __________.
c) Aunavariablequeseconocesólodentrodelafunciónenlaqueestádeclarada,selellama__________.
d) La instrucción __________ en una función a la que se llamó pasa el valor de una expresión, de vuelta
a la función que hizo la llamada.
e) La palabra clave __________ se utiliza en un encabezado de función para indicar que una función no
devuelve ningún valor, o para indicar que esa función no contiene parámetros.
f) El ________ de un identificador es la porción del programa en la que puede usarse.
g) Las tres formas de regresar el control de una llamada a una función a la función que la llamó son
__________, __________ y __________.
h) Un __________ permite al compilador comprobar el número, tipos y orden de los argumentos que
se pasan a una función.
i) La función ________ se utiliza para producir números aleatorios.
j) La función _________ se utiliza para establecer la semilla de números aleatorios, para randomizar la
secuencia de números generada por la función rand.
264 Capítulo 6 Funciones y una introducción a la recursividad
k) El especificador de clase de almacenamiento ________ es una recomendación que se hace al compi-
lador para que almacene una variable en uno de los registros de la computadora.
l) Una variable que se declara fuera de cualquier bloque o función es una variable ________.
m) Para que una variable local en una función retenga su valor entre las llamadas a la función, debe decla-
rarse con el especificador de clase de almacenamiento ________.
n) Una función que se llama a sí misma, ya sea en forma directa o indirecta (a través de otra función), es
una función ________.
o) Por lo general, una función recursiva tiene dos componentes: uno que proporciona el medio para que
termine la recursividad, al evaluar un caso ______, y uno que expresa el problema como una llamada
recursiva para un problema más simple que el de la llamada original.
p) Es posible tener varias funciones con el mismo nombre, que operen con distintos tipos o números de
argumentos. A esto se le conoce como ________ de funciones.
q) El __________ permite acceder a una variable global con el mismo nombre que una variable en el
alcance actual.
r) El calificador ________ se usa para declarar variables de sólo lectura.
s) Una ________ de función permite definir una sola función para realizar una tarea en muchos tipos de
datos distintos.
6.2 Para el programa de la figura 6.33, indique el alcance (ya sea de función, de espacio de nombres global, de
bloque o de prototipo de función) de cada uno de los siguientes elementos:
a) La variable x en main.
b) La variable y en cubo.
c) La función cubo.
d) La función main.
e) El prototipo de función para cubo.
f) El identificador y en el prototipo de función para cubo.
1 // Ejercicio 6.2: ej06_02.cpp
2 #include iostream
3 using namespace std;
4
5 int cubo( int y ); // prototipo de función
6
7 int main()
8 {
9 int x = 0;
10
11 for ( x = 1; x = 10; x++ ) // itera 10 veces
12 cout  cubo( x )  endl; // calcula el cubo de x e imprime los resultados
13 } // fin de main
14
15 // definición de la función cubo
16 int cubo( int y )
17 {
18 return y * y * y;
19 } // fin de la función cubo
Fig. 6.33  Programa para el ejercicio 6.2.
6.3 Escriba un programa que pruebe si los ejemplos de las llamadas a las funciones matemáticas de la biblio-
teca que se muestran en la figura 6.2 realmente producen los resultados indicados.
6.4 Proporcione el encabezado para cada una de las siguientes funciones:
a) La función hipotenusa, que toma dos argumentos de punto flotante con doble precisión, llamados
lado1 y lado2, y que devuelve un resultado de punto flotante, con doble precisión.
b) La función menor, que toma tres enteros x, y y z, y devuelve un entero.
c) La función instrucciones, que no recibe argumentos y no devuelve ningún valor. [Nota: dichas fun-
ciones se utilizan comúnmente para mostrar instrucciones a un usuario].
Ejercicios de autoevaluación 265
d) La función intADouble, que recibe un argumento entero llamado numero y devuelve un resultado de
punto flotante, con doble precisión.
6.5 Proporcione el prototipo de función (sin nombres de parámetros) para cada una de las siguientes situa-
ciones:
a) La función descrita en el ejercicio 6.4(a).
b) La función descrita en el ejercicio 6.4(b).
c) La función descrita en el ejercicio 6.4(c).
d) La función descrita en el ejercicio 6.4(d).
6.6 Escriba una declaración para cada uno de los siguientes casos:
a) Una variable int llamada cuenta, que deba mantenerse en un registro. Inicialice cuenta en 0.
b) La variable de punto flotante con doble precisión llamada ultimoVal, que debe retener su valor entre
las llamadas a la función en la que está definida.
6.7 Encuentre el o los errores en cada uno de los siguientes segmentos de programas, y explique cómo se pue-
den corregir (vea también el ejercicio 6.47):
a) int g()
{
cout  Dentro de la funcion g  endl;
int h()
{
cout  Dentro de la función h  endl;
}
}
b) int suma( int x, int y )
{
int resultado = 0;
resultado = x + y;
}
c) int suma( int n )
{
if ( 0 == n )
return 0;
else
n + suma( n - 1 );
}
d) void f( double a );
{
float a;
cout  a  endl;
}
e) void producto()
{
int a = 0;
int b = 0;
int c = 0;
cout  Escribe tres enteros: ;
cin  a  b  c;
int resultado = a * b * c;
cout  El resultado es   resultado;
return resultado;
}
6.8 ¿Por qué un prototipo de función podría contener la declaración del tipo de un parámetro, como double ?
266 Capítulo 6 Funciones y una introducción a la recursividad
6.9 (Verdadero/Falso) Todos los argumentos a las llamadas a funciones en C++ se pasan por valor.
6.10 Escriba un programa completo que pida al usuario el radio de una esfera, calcule e imprima el volumen
de esa esfera. Use una función inline llamada volumenEsfera que devuelva el resultado de la siguiente expresión:
(4.0 / 3.0 * 3.14159 * pow(radio, 3)).
Respuestas a los ejercicios de autoevaluación
6.1 a) funciones, clases. b) llamada a una función. c) variable local. d) return. e) void. f) alcance.
g) return; return expresión; o encontrar la llave derecha de cierre de una función. h) prototipo de función.
i) rand. j) srand. k) register. l) global. m) static. n) recursiva. o) base. p) sobrecarga q) operador
de resolución de ámbito unario (::). r) const. s) plantilla.
6.2 a) alcance de bloque. b) alcance de bloque. c) alcance de espacio de nombres global. d) alcance de
espacio de nombres global. e) alcance de espacio de nombres global. f) alcance de prototipo de función.
6.3 Vea el siguiente programa:
1 // Ejercicio 6.3: ej06_03.cpp
2 // Prueba de las funciones matemáticas de la biblioteca.
3 #include iostream
4 #include iomanip
5 #include cmath
6 using namespace std;
7
8 int main()
9 {
10 cout  fixed  setprecision( 1 );
11
12 cout  sqrt(  9.0  ) =   sqrt( 9.0 );
13 cout  nexp(  1.0  ) =   setprecision( 6 )
14  exp( 1.0 )  nexp(  setprecision( 1 )  2.0
15  ) =   setprecision( 6 )  exp( 2.0 );
16 cout  nlog(  2.718282  ) =   setprecision( 1 )
17  log( 2.718282 )
18  nlog(  setprecision( 6 )  7.389056  ) = 
19  setprecision( 1 )  log( 7.389056 );
20 cout  nlog10(  10.0  ) =   log10( 10.0 )
21  nlog10(  100.0  ) =   log10( 100.0 ) ;
22 cout  nfabs(  5.1  ) =   fabs( 5.1 )
23  nfabs(  0.0  ) =   fabs( 0.0 )
24  nfabs(  -8.76  ) =   fabs( -8.76 );
25 cout  nceil(  9.2  ) =   ceil( 9.2 )
26  nceil(  -9.8  ) =   ceil( -9.8 );
27 cout  nfloor(  9.2  ) =   floor( 9.2 )
28  nfloor(  -9.8  ) =   floor( -9.8 );
29 cout  npow(  2.0  ,   7.0  ) = 
30  pow( 2.0, 7.0 )  npow(  9.0  , 
31  0.5  ) =   pow( 9.0, 0.5 );
32 cout  setprecision( 3 )  nfmod(
33  2.6  ,   1.2  ) = 
34  fmod( 2.6, 1.2 )  setprecision( 1 );
35 cout  nsin(  0.0  ) =   sin( 0.0 );
36 cout  ncos(  0.0  ) =   cos( 0.0 );
37 cout  ntan(  0.0  ) =   tan( 0.0 )  endl;
38 } // fin de main
Respuestas a los ejercicios de autoevaluación 267
sqrt(9.0) = 3.0
exp(1.0) = 2.718282
exp(2.0) = 7.389056
log(2.718282) = 1.0
log(7.389056) = 2.0
log10(10.0) = 1.0
log10(100.0) = 2.0
fabs(5.1) = 5.1
fabs(0.0) = 0.0
fabs(-8.8) = 8.8
ceil(9.2) = 10.0
ceil(-9.8) = -9.0
floor(9.2) = 9.0
floor(-9.8) = -10.0
pow(2.0, 7.0) = 128.0
pow(9.0, 0.5) = 3.0
fmod(2.600, 1.200) = 0.200
sin(0.0) = 0.0
cos(0.0) = 1.0
tan(0.0) = 0.0
6.4 a) double hipotenusa( double lado1, double lado2 )
b) int menor( int x, int y, int z )
c) void instrucciones()
d) double intADouble( int numero )
6.5 a) double hipotenusa( double, double );
b) int menor( int, int, int );
c) void instrucciones();
d) double intADouble( int );
6.6 a) register int cuenta = 0;
b) static double ultimoVal;
6.7 a) Error: la función h está definida en la función g.
Corrección: mueva la definición de h fuera de la definición de g.
b) Error: se supone que la función debe devolver un entero, pero no es así.
Corrección: coloque una instrucción return resultado; al final del cuerpo de la función, o elimine
la variable resultado y coloque la siguiente instrucción en la función:
return x + y;
c) Error: no se devuelve el resultado de n + suma( n – 1 ); suma devuelve un resultado incorrecto.
Corrección: vuelva a escribir la instrucción en la cláusula else de la siguiente manera:
return n + sum( n - 1 );
d) Errores: el punto y coma que va después del paréntesis derecho de la lista de parámetros y la redefini-
ción del parámetro a en la definición de la función.
Correcciones: elimine el punto y coma que va después del paréntesis derecho de la lista de parámetros,
y elimine la declaración float a;.
e) Error: la función devuelve un valor cuando no debe hacerlo.
Corrección: elimine la instrucción return o cambie el tipo de valor de retorno.
6.8 Esto crea un parámetro de referencia de tipo “referencia a double”, el cual permite que la función modifi-
que la variable original en la función que hace la llamada.
6.9 Falso. C++ permite el paso por referencia mediante el uso de parámetros por referencia (y apuntadores,
como veremos en el capítulo 8).
268 Capítulo 6 Funciones y una introducción a la recursividad
6.10 Vea el siguiente programa:
1 // Solución al ejercicio 6.10: Ej06_10.cpp
2 // Función en línea que calcula el volumen de una esfera.
3 #include iostream
4 #include cmath
5 using namespace std;
6
7 const double PI = 3.14159; // define la constante global PI
8
9 // calcula el volumen de una esfera
10 inline double volumenEsfera( const double radio )
11 {
12 return 4.0 / 3.0 * PI * pow( radio, 3 );
13 } // fin de la función en línea volumenEsfera
14
15 int main()
16 {
17 double valorRadio = 0;
18
19 // pide el radio al usuario
20 cout  Escriba la longitud del radio de su esfera: ;
21 cin  valorRadio; // recibe el radio
22
23 // usa valorRadio para calcular el volumen de la esfera y mostrar el resultado
24 cout  El volumen de la esfera con radio   valorRadio
25   es   volumenEsfera( valorRadio )  endl;
26 } // fin de main
Ejercicios
6.11 Muestre el valor de x después de ejecutar cada una de las siguientes instrucciones:
a) x = fabs( 7.5 )
b) x = floor( 7.5 )
c) x = fabs( 0.0 )
d) x = ceil( 0.0 )
e) x = fabs( -6.4 )
f) x = ceil( -6.4 )
g) x = ceil( -fabs( -8 + floor( -5.5 ) ) )
6.12 (Cargos de estacionamiento) Un estacionamiento cobra una cuota mínima de $2.00 por estacionarse
hasta tres horas. El estacionamiento cobra $0.50 adicionales por cada hora o fracción que se pase de tres horas. El
cargo máximo para cualquier periodo dado de 24 horas es de $10.00. Suponga que ningún automóvil se estaciona
durante más de 24 horas a la vez. Escriba un programa que calcule e imprima los cargos por estacionamiento para
cada uno de tres clientes que estacionaron su automóvil ayer en este estacionamiento. Debe introducir las horas de
estacionamientoparacadacliente.Elprogramadebeimprimirlosresultadosenunformatotabularordenado,debe
calcular e imprimir el total de los recibos de ayer. El programa debe utilizar la función calcularCargos para deter-
minar el cargo para cada cliente. Sus resultados deben aparecer en el siguiente formato:
Automóvil Horas Cargo
1 1.5 2.00
2 4.0 2.50
3 24.0 10.00
TOTAL 29.5 14.50
Ejercicios 269
6.13 (Redondeo de números) Una aplicación de la función floor es redondear un valor al siguiente entero. La
instrucción
y = floor( x + 0.5 );
redondea el número x al entero más cercano y asigna el resultado a y. Escriba un programa que lea varios números
y que utilice la instrucción anterior para redondear cada uno de los números a su entero más cercano. Para cada
número procesado, muestre tanto el número original como el redondeado.
6.14 (Redondeo de números) La función floor puede utilizarse para redondear un número hasta un lugar de-
cimal específico. La instrucción
y = floor( x * 10 + 0.5 ) / 10;
redondea x en la posición de las décimas (la primera posición a la derecha del punto decimal). La instrucción
y = floor( x * 100 + 0.5 ) / 100;
redondea x en la posición de las centésimas (la segunda posición a la derecha del punto decimal). Escriba un pro-
grama que defina cuatro funciones para redondear un número x en varias formas:
a) redondearAEntero( numero )
b) redondearADecimas( numero )
c) redondearACentesimas( numero )
d) redondearAMilesimas( numero )
Para cada valor leído, su programa debe imprimir el valor original, el número redondeado al entero más cer-
cano, el número redondeado a la décima más cercana, el número redondeado a la centésima más cercana y el nú-
mero redondeado a la milésima más cercana.
6.15 (Preguntas con respuestas cortas) Responda a cada una de las siguientes preguntas:
a) ¿Qué significa elegir números “al azar”?
b) ¿Por qué es la función rand útil para simular juegos al azar?
c) ¿Por qué se debe randomizar un programa mediante srand? ¿Bajo qué circunstancias es aconsejable no
randomizar?
d) ¿Por qué a menudo es necesario escalar o desplazar los valores producidos por rand?
e) ¿Por qué es la simulación computarizada de las situaciones reales una técnica útil?
6.16 (Números aleatorios) Escriba instrucciones que asignen enteros aleatorios a la variable n en los siguientes
rangos:
a) 1 ≤ n ≤ 2
b) 1 ≤ n ≤ 100
c) 0 ≤ n ≤ 9
d) 1000 ≤ n ≤ 1112
e) –1 ≤ n ≤ 1
f) –3 ≤ n ≤ 11
6.17 (Números aleatorios) Escriba una sola instrucción que imprima un número al azar de cada uno de los si-
guientes conjuntos:
a) 2, 4, 6, 8, 10.
b) 3, 5, 7, 9, 11.
c) 6, 10, 14, 18, 22.
6.18 (Exponenciación) Escriba una función llamada enteroPotencia( base, exponente ) que devuelva el valor de
baseexponente
Por ejemplo, enteroPotencia(3, 4) = 3 * 3 * 3 * 3. Suponga que exponente es un entero positivo distinto de cero
y que base es un entero. No utilice ninguna función matemática de la biblioteca.
270 Capítulo 6 Funciones y una introducción a la recursividad
6.19 (Cálculos de hipotenusa) Defina una función llamada hipotenusa que calcule la hipotenusa de un trián-
gulo recto, cuando se proporcionen las longitudes de los otros dos lados. La función debe recibir dos argumentos
double y devolver la hipotenusa como double. Use esta función en un programa para determinar la hipotenusa
para cada uno de los triángulos que se muestran a continuación.
Triángulo Lado 1 Lado 2
1 3.0 4.0
2 5.0 12.0
3 8.0 15.0
6.20 (Múltiples) Escriba una función llamada multiple que determine, para un par de enteros, si el segundo
entero es múltiplo del primero. La función debe tomar dos argumentos enteros y devolver true si el segundo es
múltiplo del primero, y false en caso contrario. Use esta función en un programa que reciba como entrada una
serie de pares de enteros.
6.21 (Números pares) Escriba un programa que reciba una serie de enteros y los pase, uno a la vez, a una función
llamada esPar, que utilice el operador módulo para determinar si un entero dado es par. La función debe tomar un
argumento entero y devolver true si el entero es par, y false en caso contrario.
6.22 (Cuadrado de asteriscos) Escriba una función que muestre en el margen izquierdo de la pantalla un cua-
drado relleno de asteriscos, cuyo lado se especifique en el parámetro entero lado. Por ejemplo, si lado es 4, la fun-
ción debe mostrar lo siguiente:
****
****
****
****
6.23 (Cuadrado de cualquier carácter) Modifique la función creada en el ejercicio 6.22 para formar el cuadra-
do de cualquier carácter que esté contenido en el parámetro tipo carácter caracterRelleno. Por ejemplo, si lado
es 5 y caracterRelleno es “#”, esta función debe imprimir lo siguiente:
#####
#####
#####
#####
#####
6.24 (Separar dígitos) Escriba segmentos de programas que realicen cada una de las siguientes tareas:
a) Calcular la parte entera del cociente, cuando el entero a se divide entre el entero b.
b) Calcular el residuo entero cuando el entero a se divide entre el entero b.
c) Utilizar las piezas de los programas desarrollados en las partes (a) y (b) para escribir una función que
reciba un entero entre 1 y 32767, y que lo imprima como una serie de dígitos, separando cada par de
dígitos por dos espacios. Por ejemplo, el entero 4562 debe imprimirse de la siguiente manera:
4 5 6 2
6.25 (Calcular el número de segundos) Escriba una función que reciba la hora en forma de tres argumentos
enteros (horas, minutos y segundos) y devuelva el número de segundos transcurridos desde la última vez que el
reloj “marcó las 12”. Use esta función para calcular el monto de tiempo en segundos entre dos horas, las cuales
deben estar dentro de un ciclo de 12 horas del reloj.
Ejercicios 271
6.26 (Temperaturas en Centígrados y Fahrenheit) Implemente las siguientes funciones enteras:
a) La función centigrados que devuelve la equivalencia en grados Centígrados de una temperatura en
grados Fahrenheit.
b) La función fahrenheit que devuelve la equivalencia en grados Fahrenheit de una temperatura en
grados Centígrados.
c) Utilice estas funciones para escribir un programa que imprima gráficos que muestren los equivalentes
en grados Fahrenheit de todas las temperaturas en grados Centígrados, desde 0 hasta 100, y los equi-
valentes en grados Centígrados de todas las temperaturas en grados Fahrenheit, desde 32 hasta 212.
Imprima los resultados en un formato tabular ordenado que minimice el número de líneas de salida,
al tiempo que permanezca legible.
6.27 (Encontrar el mínimo) Escriba un programa que reciba tres números de punto flotante de doble precisión,
y que los pase a una función que devuelva el número más pequeño.
6.28 (Números perfectos) Se dice que un número entero es un número perfecto si la suma de sus divisores, inclu-
yendo 1 (pero no el número en sí), es igual al número. Por ejemplo, 6 es un número perfecto ya que 6 = 1 + 2 + 3.
Escriba una función llamada esPerfecto que determine si el parámetro numero es un número perfecto. Use esta
función en un programa que determine e imprima todos los números perfectos entre 1 y 1000. Imprima los divi-
sores de cada número perfecto para confirmar que el número sea realmente perfecto. Ponga a prueba el poder de
su computadora, evaluando números mucho más grandes que 1000.
6.29 (Números primos) Se dice que un entero es primo si puede dividirse solamente por 1 y por sí mismo. Por
ejemplo, 2, 3, 5 y 7 son primos, pero 4, 6, 8 y 9 no.
a) Escriba una función que determine si un número es primo.
b) Useestafunciónenunprogramaquedetermineeimprimatodoslosnúmerosprimosentre2y10,000.
¿Cuántos de estos números hay que probar realmente para asegurarse de encontrar todos los números
primos?
c) Al principio podría pensarse que n/2 es el límite superior para evaluar si un número es primo, pero lo
máximo que se necesita es ir hasta la raíz cuadrada de n. ¿Por qué? Vuelva a escribir el programa y eje-
cútelo de ambas formas. Estime la mejora en el rendimiento.
6.30 (Dígitos inversos) Escriba una función que reciba un valor entero y devuelva el número con sus dígitos
invertidos. Por ejemplo, para el número 7631, la función debe regresar 1367.
6.31 (Máximo común divisor) El máximo común divisor (MCD) de dos enteros es el entero más grande que
puede dividir uniformemente a cada uno de los dos números. Escriba una función llamada mcd que devuelva el
máximo común divisor de dos enteros.
6.32 (Puntos de calidad para calificaciones numéricas) Escriba una función llamada puntosCalidad que reciba
como entrada el promedio de un estudiante y devuelva 4 si el promedio se encuentra entre 90 y 100, 3 si el prome-
dio se encuentra entre 80 y 89, 2 si el promedio se encuentra entre 70 y 79, 1 si el promedio se encuentra entre 60 y
69, y 0 si el promedio es menor de 60.
6.33 (Lanzar monedas) Escriba un programa que simule el lanzamiento de monedas. Cada vez que se lance la
moneda, el programa debe imprimir Cara o Cruz. Deje que el programa lance la moneda 100 veces y cuente el
númerodevecesqueaparezcacadaunodelosladosdelamoneda.Imprimalosresultados.Elprogramadebellamar
a una función separada llamada tirar, que no reciba argumentos y devuelva 0 en caso de cara y 1 en caso de cruz.
[Nota: si el programa simula en forma realista el lanzamiento de monedas, cada lado de la moneda debe aparecer
aproximadamente la mitad del tiempo`].
6.34 (Juego “Adivina el número”) Escriba un programa que juegue a “adivina el número” de la siguiente mane-
ra: su programa elige el número a adivinar, seleccionando un entero aleatorio en el rango de 1 a 1000. Después, el
programa muestra lo siguiente:
Tengo un numero entre 1 y 1000.
Puedes adivinar mi numero?
Por favor escribe tu primera respuesta.
272 Capítulo 6 Funciones y una introducción a la recursividad
El jugador escribe su primer intento. El programa responde con uno de los siguientes mensajes:
1. Excelente! Adivinaste el numero!
Te gustaria jugar de nuevo (s/n)?
2. Demasiado bajo. Intenta de nuevo.
3. Demasiado alto. Intenta de nuevo.
Si la respuesta del jugador es incorrecta, su programa deberá iterar hasta que el jugador adivine correctamente. Su
programa deberá seguir indicando al jugador los mensajes Demasiado alto o Demasiado bajo, para ayudar a que
el jugador “se acerque” a la respuesta correcta.
6.35 (Modificación del juego “Adivina el número”) Modifique el programa del ejercicio 6.34 para contar
el número de intentos que haga el jugador. Si el número es 10 o menos, imprima el mensaje “O ya sabia usted el
secreto, o tuvo suerte!”. Si el jugador adivina el número en 10 intentos, imprima el mensaje “Aja! Sabía
usted el secreto!”. Si el jugador hace más de 10 intentos, imprima el mensaje “Deberia haberlo hecho mejor!”.
¿Por qué no se deben requerir más de 10 intentos? Bueno, en cada “buen intento”, el jugador debe poder eliminar
la mitad de los números. Ahora muestre por qué cualquier número de 1 a 1000 puede adivinarse en 10 intentos o
menos.
6.36 (Exponenciación recursiva) Escriba una función recursiva llamada potencia( base, exponente ) que,
cuando sea llamada, devuelva
base exponente
Por ejemplo, potencia( 3, 4 ) = 3 * 3 * 3 * 3. Suponga que exponente es un entero mayor o igual que 1. Suge-
rencia: el paso recursivo debe utilizar la relación
base exponente
= base · base exponente — 1
y la condición de terminación ocurre cuando exponente es igual a 1, ya que
base 1
= base
6.37 (Serie de Fibonacci: solución iterativa) Escriba una versión no recursiva de la función fibonacci de la
figura 6.29.
6.38 (Torres de Hanoi) En este capítulo estudiamos funciones que pueden implementarse con facilidad, tanto
en forma recursiva como iterativa. En este ejercicio presentamos un problema cuya solución recursiva demuestra
la elegancia de la recursividad, y cuya solución iterativa tal vez no sea tan aparente.
Las Torres de Hanoi son uno de los problemas clásicos más famosos con los que todo científico computacio-
nal en ciernes tiene que lidiar. Cuenta la leyenda que en un templo del Lejano Oriente, los sacerdotes intentan
mover una pila de discos dorados, de una aguja de diamante a otra (figura 6.34). La pila inicial tiene 64 discos in-
sertados en una aguja y se ordenan de abajo hacia arriba, de mayor a menor tamaño. Los sacerdotes intentan mover
la pila de una aguja a otra, con las restricciones de que sólo se puede mover un disco a la vez, y en ningún momen-
to se puede colocar un disco más grande encima de uno más pequeño. Se cuenta con tres agujas, una de las cuales
se utiliza para almacenar discos temporalmente. Se supone que el mundo acabará cuando los sacerdotes completen
su tarea, por lo que hay pocos incentivos para que nosotros podamos facilitar sus esfuerzos.
Vamos a suponer que los sacerdotes intentan mover los discos de la aguja 1 a la aguja 3. Deseamos desarrollar
un algoritmo que imprima la secuencia precisa de transferencias de los discos de una aguja a otra.
Siatacamosesteproblemaconlosmétodosconvencionales,rápidamenteterminaríamos “atados”manejando
los discos sin esperanza. En vez de ello, si atacamos este problema teniendo en mente la recursividad, los pasos serán
mássimples.Laaccióndemover ndiscospuedeverse entérminosdemoversólo n–1 discos (deahí larecursividad)
de la siguiente forma:
a) Mover n – 1 discos de la aguja 1 a la aguja 2, usando la aguja 3 como un área de almacenamiento
temporal.
b) Mover el último disco (el más grande) de la aguja 1 a la aguja 3.
c) Mover n – 1 discos de la aguja 2 a la aguja 3, usando la aguja 1 como área de almacenamiento tem-
poral.
Ejercicios 273
aguja 1 aguja 2 aguja 3
Fig. 6.34  Las Torres de Hanoi para el caso con cuatro discos.
El proceso termina cuando la última tarea implica mover n = 1 disco (es decir, el caso base). Esta tarea se logra
con sólo mover el disco, sin necesidad de un área de almacenamiento temporal. Escriba un programa para resolver
el problema de las Torres de Hanoi. Use una función recursiva con cuatro parámetros:
a) El número de discos a mover.
b) La aguja en la que están insertados estos discos en un principio.
c) La aguja a la que se va a mover esta pila de discos.
d) La aguja que se va a utilizar como área de almacenamiento temporal.
Imprima las instrucciones precisas para mover los discos de la aguja inicial a la aguja de destino. Para mover
una pila de tres discos de la aguja 1 a la aguja 3, el programa muestra la siguiente serie de movimientos:
1 → 3 (Esto significa mover un disco de la aguja 1 a la aguja 3).
1 → 2
3 → 2
1 → 3
2 → 1
2 → 3
1 → 3
6.39 (Torres de Hanoi: versión iterativa) Cualquier programa que se pueda implementar en forma recursiva se
puede implementar en forma iterativa, aunque algunas veces con mayor dificultad y menor claridad. Pruebe escri-
bir una versión iterativa de lasTorres de Hanoi. Si tiene éxito, compare su versión iterativa con la versión recursiva
desarrollada en el ejercicio 6.38. Investigue las cuestiones relacionadas con el rendimiento, la claridad y su habili-
dad de demostrar que los programas estén correctos.
6.40 (Visualización de la recursividad) Es interesante observar la recursividad “en acción”. Modifique la fun-
ción factorial de la figura 6.28 para imprimir su variable local y su parámetro de llamada recursiva. Para cada lla-
mada recursiva, muestre los resultados en una línea separada y agregue un nivel de sangría. Haga su máximo esfuer-
zo por hacer que los resultados sean claros, interesantes y significativos. Su meta aquí es diseñar e implementar un
formato de salida que ayude a una persona a comprender mejor la recursividad. Tal vez desee agregar dichas capa-
cidades de visualización a otros ejemplos y ejercicios recursivos a lo largo de este libro.
6.41 (Máximo común divisor recursivo) El máximo común divisor de los enteros x y y es el entero más grande
que se puede dividir entre x y y de manera uniforme. Escriba una función recursiva llamada mcd, que devuelva el
máximo común divisor de x y y, definida mediante la recursividad, de la siguiente manera: si y es igual a 0, enton-
ces mcd(x,y) es x; en caso contrario, mcd(x,y) es mcd(y, x % y), en donde % es el operador módulo. [Nota: para
este algoritmo, x debe ser mayor que y].
274 Capítulo 6 Funciones y una introducción a la recursividad
6.42 (Distancia entre puntos) Escriba una función llamada distancia que calcule la distancia entre dos puntos
(x1, y1) y (x2, y2). Todos los números y valores de retorno deben ser de tipo double.
6.43 ¿Qué error tiene el siguiente programa?
1 // Ejercicio 6.43: ej06_43.cpp
2 // ¿Qué error tiene este programa?
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
8 int c = 0;
9
10 if ( ( c = cin.get() ) != EOF )
11 {
12 main();
13 cout  c;
14 } // fin de if
15 } // fin de main
6.44 ¿Qué hace el siguiente programa?
1 // Ejercicio 6.44: ej06_44.cpp
2 // ¿Qué hace este programa?
3 #include iostream
4 using namespace std;
5
6 int misterio( int, int ); // prototipo de función
7
8 int main()
9 {
10 int x = 0;
11 int y = 0;
12
13 cout  Escriba dos enteros: ;
14 cin  x  y;
15 cout  El resultado es   misterio( x, y )  endl;
16 } // fin de main
17
18 // el parámetro b debe ser un entero positivo para prevenir la recursividad infinita
19 int misterio( int a, int b )
20 {
21 if ( 1 == b ) // caso base
22 return a;
23 else // paso recursivo
24 return a + misterio( a, b - 1 );
25 } // fin de la función misterio
6.45 Una vez que determine qué es lo que hace el programa del ejercicio 6.44, modifíquelo para que funcione
de manera apropiada, después de eliminar la restricción de que el segundo argumento no debe ser negativo.
6.46 (Funciones matemáticas de la biblioteca) Escriba un programa que evalúe todas, si es posible, las funcio-
nes matemáticas de la biblioteca en la figura 6.2. Ejercite cada una de estas funciones, haciendo que su programa
imprima tablas de valores de retornos para una diversidad de valores de los argumentos.
6.47 (Encuentre el error) Encuentre el error en cada uno de los siguientes segmentos de programa, y explique
cómo corregirlo:
Ejercicios 275
a) float cubo( float ); // prototipo de función
cubo( float numero ) // definición de función
{
return numero * numero * numero;
}
b) int numeroAleatorio = srand();
c) float y = 123.45678;
int x;
x = y;
cout  static_cast float ( x )  endl;
d) double cuadrado( double number )
{
double numero = 0;
return numero * numero;
}
e) int suma( int n )
{
if ( 0 == n )
return 0;
else
return n + suma( n );
}
(Modificación del juego de Craps) Modifique el programa Craps de la figura 6.11 para permitir apuestas.
Empaquete como función la parte del programa que ejecuta un juego de craps. Inicialice la variable saldoBanco
con 1000 dólares. Pida al jugador que introduzca una apuesta. Use un ciclo while para comprobar que esa
apuesta sea menor o igual al saldoBanco y, si no lo es, haga que el usuario vuelva a introducir la apuesta hasta
que se introduzca un valor válido. Después de esto, comience un juego de craps. Si el jugador gana, agregue
la apuesta al saldoBanco e imprima el nuevo saldoBanco. Si el jugador pierde, reste la apuesta al saldoBanco,
imprima el nuevo saldoBanco, compruebe si saldoBanco se ha vuelto cero y, de ser así, imprima el mensaje
Lo siento. Se quedo sin fondos!. A medida que el juego progrese, imprima varios mensajes para crear algo
de “charla”, como Oh, se esta yendo a la quiebra, verdad?, o Oh, vamos, arriesguese!, o La hizo en
grande. Ahora es tiempo de cambiar sus fichas por efectivo!.
6.48 (Área de círculo) Escriba un programa en C++ que pida al usuario el radio de un círculo y después llame a
la función inline areaCirculo para calcular el área de ese círculo.
6.49 (Paso por valor y paso por referencia) Escriba un programa completo en C++ con las dos funciones alter-
nativas que se especifican a continuación, de las cuales cada una simplemente triplica la variable cuenta definida
en main. Después compare y contraste ambos métodos. Estas dos funciones son:
a) la función triplicarPorValor, que pasa una copia de cuenta por valor, triplica la copia y devuelve el
nuevo valor, y
b) la función triplicarPorReferencia, que pasa cuenta por referencia a través de un parámetro por
referencia y triplica el valor original de cuenta a través de su alias (es decir, el parámetro por refe-
rencia).
6.50 ¿Cuál es el propósito del operador de resolución de ámbito unario?
6.51 (Plantilla de función minimo) Escriba un programa que use una plantilla de función llamada minimo para
determinar el menor de dos argumentos. Pruebe el programa usando argumentos tipo entero, carácter y número de
punto flotante.
276 Capítulo 6 Funciones y una introducción a la recursividad
6.52 (Plantilla de función maximo) Escriba un programa que utilice una plantilla de función llamada maximo
para determinar el mayor de dos argumentos. Pruebe el programa usando argumentos tipo entero, carácter y nú-
mero de punto flotante.
6.53 (Encuentreelerror) Determinesilossiguientessegmentosdeprogramacontienenerrores.Paracadaerror,
explique cómo puede corregirse. [Nota: para un segmento de programa específico, es posible que no haya errores
presentes en el segmento].
a) template  class A 
int suma( int num1, int num2, int num3 )
{
return num1 + num2 + num3;
}
b) void imprimirResultados( int x, int y )
{
cout  La suma es   x + y  'n';
return x + y;
}
c) template  A 
A producto( A num1, A num2, A num3 )
{
return num1 * num2 * num3;
}
d) double cubo( int );
int cubo( int );
6.54 (Números aleatorios de C++11: juego de craps modificado) Modifique el programa de la figura 6.11 para
usar las nuevas herramientas de generación de números aleatorios que se muestran en la sección 6.9.
6.55 (Enumeración con alcance de C++11) Cree una enum con alcance llamada TipoCuenta que contenga las
constantes llamadas AHORROS, CHEQUES e INVERSION.
Hacer la diferencia
A medida que disminuyen los costos de las computadoras, aumenta la posibilidad de que cada estudiante, sin
importar su economía, tenga una y la utilice en la escuela. Esto crea excitantes posibilidades para mejorar la expe-
riencia educativa de todos los estudiantes a nivel mundial, según lo sugieren los siguientes cinco ejercicios. [Nota:
vea nuestras iniciativas, como el proyecto One Laptop Per Child (www.laptop.org). Investigue también acerca de
las laptops “verdes” o ecológicas y observe las características “ecológicas” clave de estos dispositivos. Investigue
también la Herramienta de evaluación ambiental de productos electrónicos (www.epeat.net), que le puede ayudar
a evaluar las características “ecológicas” de las computadoras de escritorio, notebooks y monitores para poder de-
cidir qué productos comprar].
6.56 (Instrucción asistida por computadora) El uso de las computadoras en la educación se conoce como ins-
trucción asistida por computadora (CAI). Escriba un programa que ayude a un estudiante de escuela primaria, para
que aprenda a multiplicar. Use la función rand para producir dos enteros positivos de un dígito. El programa debe
entonces mostrar una pregunta al usuario, como:
¿Cuánto es 6 por 7?
El estudiante entonces debe escribir la respuesta. Luego, el programa debe verificar la respuesta del estudiante. Si
es correcta, muestre el mensaje Muy bien! y haga otra pregunta de multiplicación. Si la respuesta es incorrecta,
muestre el mensaje No. Por favor intenta de nuevo. y deje que el estudiante intente la misma pregunta varias
veces, hasta que esté correcta. Debe utilizarse una función separada para generar cada pregunta nueva. Esta función
debe llamarse una vez cuando la aplicación empiece a ejecutarse, y cada vez que el usuario responda correctamente
a la pregunta.
Hacer la diferencia 277
6.57 (Instrucción asistida por computadora: reducción de la fatiga de los estudiantes) Un problema que se de-
sarrolla en los entornos CAI es la fatiga de los estudiantes. Este problema puede eliminarse si se varían las contes-
taciones de la computadora para mantener la atención del estudiante. Modifique el programa del ejercicio 6.57 de
manera que se muestren diversos comentarios para cada respuesta, de la siguiente manera:
Posibles contestaciones a una respuesta correcta:
Muy bien!
Excelente!
Buen trabajo!
Sigue asi!
Posibles contestaciones a una respuesta incorrecta:
No. Por favor intenta de nuevo.
Incorrecto. Intenta una vez mas.
No te rindas!
No. Sigue intentando.
Use la generación de números aleatorios para elegir un número entre 1 y 4 que se utilice para seleccionar una
de las cuatro contestaciones apropiadas a cada respuesta correcta o incorrecta. Use una instrucción switch para
emitir las contestaciones.
6.58 (Instrucción asistida por computadora: supervisión del rendimiento de los estudiantes) Los sistemas de
instrucción asistida por computadora más sofisticados supervisan el rendimiento del estudiante durante cierto
tiempo. La decisión de empezar un nuevo tema se basa a menudo en el éxito del estudiante con los temas anterio-
res. Modifique el programa del ejercicio 6.58 para contar el número de respuestas correctas e incorrectas introdu-
cidas por el estudiante. Una vez que el estudiante escriba 10 respuestas, su programa debe calcular el porcentaje de
respuestas correctas. Si éste es menor del 75%, imprima Por favor pida ayuda adicional a su profesor y
reinicie el programa, para que otro estudiante pueda probarlo. Si el porcentaje es del 75% o mayor, muestre el
mensaje Felicidades, esta listo para pasar al siguiente nivel! y luego reinicie el programa, para que
otro estudiante pueda probarlo.
6.59 (Instrucción asistida por computadora: niveles de dificultad) En los ejercicios 6.57 al 6.59 se desarrolló un
programa de instrucción asistida por computadora para enseñar a un estudiante de escuela primara cómo multi-
plicar. Modifique el programa para que permita al usuario introducir un nivel de dificultad. Un nivel de 1 significa
que el programa debe usar sólo números de un dígito en los problemas; un nivel 2 significa que el programa debe
utilizar números de dos dígitos máximo, y así en lo sucesivo.
6.60 (Instrucción asistida por computadora: variación de los tipos de problemas) Modifique el programa del
ejercicio 6.60 para permitir al usuario que elija el tipo de problemas aritméticos que desea estudiar. Una opción de
1 significa problemas de suma solamente, 2 significa problemas de resta, 3 significa problemas de multiplicación,
4 significa problemas de división y 5 significa una mezcla aleatoria de problemas de todos estos tipos.
Plantillas de clase array
y vector; cómo atrapar
excepciones
7
Ahora ve, escríbelo
ante ellos en una tabla,
y anótalo en un libro.
—Isaías 30:8
Comienza en el principio…
y continúa hasta que llegues
al final; después detente.
—Lewis Carroll
Ir más allá es tan malo
como no llegar.
—Confucio
O b j e t i v o s
En este capítulo aprenderá a:
n Utilizar la plantilla de clase
array de la Biblioteca
estándar de C++: una
colección de tamaño fijo
de elementos de datos
relacionados.
n Utilizar arreglos para
almacenar, ordenar y buscar
datos en listas y tablas de
valores.
n Declarar arreglos,
inicializarlos y hacer
referencia a elementos
individuales de los arreglos.
n Usar la instrucción for
basada en rango.
n Pasar arreglos a las funciones.
n Declarar y manipular arreglos
multidimensionales.
n Utilizar la plantilla de clase
vector de la Biblioteca
estándar de C++: una
colección de tamaño variable
de elementos de datos
relacionados.
7.2 Arreglos 279
7.1Introducción
En este capítulo presentamos el tema de las estructuras de datos: colecciones de elementos de datos re-
lacionados. Hablaremos sobre los arreglos, que son colecciones de tamaño fijo que consisten de ele-
mentos de datos del mismo tipo, y de los vectores, que son colecciones (también de elementos de datos
del mismo tipo) que pueden aumentar y reducir su tamaño en forma dinámica en tiempo de ejecución.
Tanto array como vector son plantillas de clase de la biblioteca estándar de C++. Para usarlas, hay que
incluir los encabezados array y vector, respectivamente.
Después de hablar acerca de cómo se declaran, se crean y se inicializan los arreglos, presentaremos
ejemplos que demuestran varias manipulaciones comunes de ellos. Le mostraremos cómo realizar bús-
quedas en los arreglos para encontrar elementos específicos, y cómo ordenar los arreglos para poner sus
datos en orden.
Mejoraremos la clase LibroCalificaciones mediante el uso de arreglos unidimensionales y bidi-
mensionales para mantener un conjunto de calificaciones en memoria y analizar las calificaciones de dis-
tintosexámenes.Introduciremoselmecanismodemanejodeexcepcionesylousaremosparaqueunprogra-
ma pueda seguir ejecutándose cuando intente acceder al elemento de un vector o array que no exista.
7.2Arreglos
Un arreglo es un grupo contiguo de localidades de memoria, todas ellas del mismo tipo. Para hacer refe-
rencia a una localidad o elemento específico en el arreglo, especificamos su nombre y el número de
posición del elemento en el arreglo.
La figura 7.1 muestra un arreglo de enteros llamado c, el cual contiene 12 elementos. Para hacer
referencia a cualquiera de estos elementos en un programa, se proporciona el nombre del arreglo segui-
do del número de posición del elemento específico entre corchetes ([]). Al número de posición se le
conoce más formalmente como el índice o subíndice (este número especifica el número de elementos
a partir del inicio del arreglo). El primer elemento tiene el subíndice 0 (cero) y se conoce algunas veces
como el elemento cero. Por ende, los elementos del array c son c[0] (se pronuncia como “c sub cero”),
7.1 Introducción
7.2 Arreglos
7.3 Declaración de arreglos
7.4 Ejemplos acerca del uso de los arreglos
7.4.1 Declaración de un arreglo y uso de un
ciclo para inicializar los elementos del
arreglo
7.4.2 Inicialización de un arreglo en una
declaración mediante una lista
inicializadora
7.4.3 Especificación del tamaño de un arreglo
con una variable constante y
establecimiento de los elementos de un
arreglo con cálculos
7.4.4 Suma de los elementos de un arreglo
7.4.5 Uso de gráficos de barra para mostrar los
datos de un arreglo en forma gráfica
7.4.6 Uso de los elementos de un arreglo como
contadores
7.4.7 Uso de arreglos para sintetizar los resultados
de una encuesta
7.4.8 Arreglos locales estáticos y arreglos locales
automáticos
7.5 Instrucción for basada en rango
7.6 Caso de estudio: la clase
LibroCalificaciones que usa un arreglo
para almacenar las calificaciones
7.7 Búsqueda y ordenamiento de datos en
arreglos
7.8 Arreglos multidimensionales
7.9 Caso de estudio: la clase
LibroCalificaciones que usa un arreglo
bidimensional
7.10 Introducción a la plantilla de clase vector
de la Biblioteca estándar de C++
7.11 Conclusión
Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios
| Ejercicios de recursividad | Hacer la diferencia
280 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
c[1], c[2] y así en lo sucesivo. El subíndice más alto en el arreglo c es 11, el cual es 1 menos que el nú-
mero de elementos en el arreglo (12). Los nombres de los arreglos siguen las mismas convenciones que
los demás nombres de variables.
0
-45
62
-3
1
6453
78
0
-89
1543
72
6
c[ 0 ]
El nombre del arreglo es c
Número de posición del
elemento dentro del arreglo c
c[ 7 ]
c[ 8 ]
c[ 9 ]
c[ 10 ]
c[ 11 ]
c[ 6 ]
c[ 5 ]
c[ 4 ]
c[ 3 ]
c[ 2 ]
c[ 1 ]
Valor
Nombre de un elemento
individual del arreglo
Fig. 7.1  Un arreglo con 12 elementos.
Un subíndice debe ser un entero o una expresión entera (usando cualquier tipo integral). Si un
programa utiliza una expresión como un subíndice, entonces el programa evalúa la expresión para de-
terminar el subíndice. Por ejemplo, si suponemos que la variable a es igual a 5 y que la variable b es igual
a 6, entonces la instrucción
c[ a + b ] += 2;
suma 2 al elemento c[11] del arreglo. El nombre del arreglo con subíndice es un lvalue: se puede utilizar en
el lado izquierdo de una asignación, de igual forma que los nombres de las variables que no son arreglos.
Examinaremoselarregloc delafigura7.1conmásdetalle.Elnombredelarreglocompletoesc.Cada
arreglo conoce su propio tamaño, que puede determinarse mediante una llamada a su función miembro
size, como en c.size(). La manera en que se hace referencia a los 12 elementos de este arreglo es de c[0]
a c[11]. El valor de c[0] es -45, el valor de c[7] es 62 y el valor de c[11] es 78. Para imprimir la suma de
los valores contenidos en los primeros tres elementos del arreglo c, escribiríamos lo siguiente:
cout  c[ 0 ] + c[ 1 ] + c[ 2 ]  endl;
Para dividir el valor de c[6] entre 2 y asignar el resultado a la variable x, escribiríamos lo siguiente:
x = c[ 6 ] / 2;
Error común de programación 7.1
Observe la diferencia entre el “séptimo elemento del arreglo” y el “elemento 7 del arreglo”.
Los subíndices de los arreglos empiezan en 0, por lo que el “séptimo elemento del arreglo”
tiene un subíndice de 6, mientras que el “elemento 7 del arreglo” tiene un subíndice de 7 y
es en realidad el octavo elemento del arreglo. Por desgracia, esta distinción genera con fre-
cuencia errores de desplazamiento en 1. Para evitar dichos errores, nos referimos explí-
citamente a los elementos específicos de un arreglo por medio del nombre del arreglo y el
número de subíndice (por ejemplo, c[6] o c[7]).
Los corchetes que se utilizan para encerrar el subíndice de un arreglo son en realidad un operador
que tiene la misma precedencia que los paréntesis. En la figura 7.2 se muestran la precedencia y la aso-
7.4 Ejemplos acerca del uso de los arreglos 281
ciatividad de los operadores introducidos hasta ahora. Los operadores se muestran de arriba hacia abajo,
en orden descendente de precedencia, con su asociatividad y su tipo.
Operadores Asociatividad Tipo
:: () izquierdaaderecha
[Vealaprecauciónenlafigura2.10
conrespectoalagrupamientodeparéntesis].
primario
() [] ++ -- static_casttipo(operando) izquierda a derecha postfijo
++ -- + - ! derecha a izquierda unario (prefijo)
* / % izquierda a derecha multiplicativo
+ - izquierda a derecha aditivo
  izquierda a derecha inserción/extracción
 =  = izquierda a derecha relacional
== != izquierda a derecha igualdad
 izquierda a derecha AND lógico
|| izquierda a derecha OR lógico
?: derecha a izquierda condicional
= += -= *= /= %= derecha a izquierda asignación
, izquierda a derecha coma
Fig. 7.2  Precedencia y asociatividad de los operadores introducidos hasta ahora.
7.3Declaración de arreglos
Los arreglos (objetos array) ocupan espacio en memoria. Para especificar el tipo de los elementos y el
número de elementos requerido por un arreglo, use una declaración de la forma:
array tipo, tamañoArreglo  nombreArreglo;
La notación tipo, tamañoArreglo indica que el array es una plantilla de clase. El compilador reserva la
cantidad apropiada de memoria con base en el tipo de los elementos y el tamañoArreglo. (Recuerde que
una declaración que reserva memoria se conoce en forma más apropiada como definición). El tamaño-
Arreglo debe ser un entero sin signo. Para indicar al compilador que debe reservar 12 elementos para
el arreglo c de enteros, use la siguiente declaración:
array int, 12  c; // c es un arreglo de 12 valores enteros
Se pueden declarar arreglos para contener valores de la mayoría de los tipos de datos. Por ejemplo, es
posible usar un arreglo de tipo string para almacenar cadenas de caracteres.
7.4Ejemplos acerca del uso de los arreglos
Los siguientes ejemplos demuestran cómo declarar, inicializar y manipular arreglos.
7.4.1Declaración de un arreglo y uso de un ciclo para inicializar
los elementos del arreglo
El programa de la figura 7.3 declara el arreglo n con cinco elementos enteros (línea 10). La línea 5 inclu-
ye el encabezado array, que contiene la definición de la plantilla de clase array. En las líneas 13 y 14
282 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
se utiliza una instrucción for para inicializar los elementos del arreglo con cero. Al igual que otras varia-
bles automáticas, los arreglos automáticos no se inicializan de manera implícita con cero, aunque los
arreglos static sí. La primera instrucción de salida (línea 16) muestra los encabezados de columna para
las columnas impresas en la instrucción for subsiguiente (líneas 19 y 20), la cual imprime el arreglo en
formato tabular. Recuerde que setw especifica la anchura de campo en la que sólo se va a imprimir el si-
guiente valor.
1 // Fig. 7.3: fig07_03.cpp
2 // Inicialización de los elementos de un arreglo con ceros, e impresión del arreglo.
3 #include iostream
4 #include iomanip
5 #include array
6 using namespace std;
7
8 int main()
9 {
10 array int, 5  n; // n es un arreglo de 5 valores int
11
12 // inicializa los elementos del arreglo n con 0
13 for ( size_t i = 0; i  n.size(); ++i )
14 n[ i ] = 0; // establece el elemento en la ubicación i a 0
15
16 cout  Elemento  setw( 13 )  Valor  endl;
17
18 // imprime el valor de cada elemento del arreglo
19 for ( size_t j = 0; j  n.size(); ++j )
20 cout  setw( 7 )  j  setw( 13 )  n[ j ]  endl;
21 } // fin de main
Elemento Valor
0 0
1 0
2 0
3 0
4 0
Fig. 7.3  Inicialización de los elementos de un arreglo con ceros, e impresión del arreglo.
En este programa, las variables de control i (línea 13) y j (línea 19) que especifican los subíndices
del arreglo se declaran como de tipo size_t. De acuerdo con el C++ estándar, size_t representa un tipo
integralsinsigno.Estetiposerecomiendaparacualquiervariablequerepresentaeltamañodeunarreglo
o los subíndices de éste. El tipo size_t está definido en el espacio nombre std y se encuentran en el
encabezado cstddef, que se incluye a través de muchos otros encabezados. Si intenta compilar un
programa que utilice el tipo size_t y recibe errores que indiquen que no está definido, sólo tiene que
incluir cstddef en su programa.
7.4.2Inicialización de un arreglo en una declaración mediante
una lista inicializadora
Los elementos de un arreglo también se pueden inicializar en la declaración del arreglo, para lo cual
colocamos después del nombre del arreglo un signo igual y entre llaves, una lista de inicializadores se-
parados por comas. El programa de la figura 7.4 utiliza una lista inicializadora para inicializar un
arreglo de enteros con cinco valores (línea 11) y lo imprime en formato tabular (líneas 13 a 17).
7.4 Ejemplos acerca del uso de los arreglos 283
1 // Fig. 7.4: fig07_04.cpp
2 // Inicialización de un arreglo en una declaración.
3 #include iostream
4 #include iomanip
5 #include array
6 using namespace std;
7
8 int main()
9 {
10 // usa la lista inicializadora para inicializar el arreglo n
11 array int, 5  n = { 32, 27, 64, 18, 95 };
12
13 cout  Elemento  setw( 13 )  Valor  endl;
14
15 // imprime el valor de cada elemento del arreglo
16 for ( size_t i = 0; i  n.size(); ++i )
17 cout  setw( 7 )  i  setw( 13 )  n[ i ]  endl;
18 } // fin de main
Elemento Valor
0 32
1 27
2 64
3 18
4 95
Fig. 7.4  Inicialización de un arreglo en una declaración.
Si hay menos inicializadores que elementos en el arreglo, el resto de los elementos del arreglo se
inicializan con cero. Por ejemplo, los elementos del arreglo n en la figura 7.3 podrían haberse inicializa-
do en ceros con la declaración
array int, 5  n = {}; // inicializa los elementos del arreglo n con 0
la declaración inicializa los elementos con cero, ya que hay menos inicializadores (ninguno en este caso)
que elementos en el arreglo. Esta técnica sólo se puede utilizar en la declaración del arreglo, mientras que
la técnica de inicialización que se muestra en la figura 7.3 se puede utilizar de manera repetida durante la
ejecución del programa, para “reinicializar” los elementos de un arreglo.
Si se especifican el tamaño del arreglo y una lista inicializadora en la declaración de un arreglo, el
número de inicializadores debe ser menor o igual que el tamaño del arreglo. La declaración del arreglo
array int, 5  n = { 32, 27, 64, 18, 95, 14 };
produce un error de compilación, ya que hay seis inicializadores y sólo cinco elementos en el arreglo.
7.4.3Especificación del tamaño de un arreglo con una variable constante
y establecimiento de los elementos de un arreglo con cálculos
En la figura 7.5 se establecen los cinco elementos de un arreglo s con los enteros pares 2, 4, 6, 8 y
10 (líneas 15 y 16), y se imprime el arreglo en formato tabular (líneas 18 a 22). Para generar estos núme-
ros (línea 16), se multiplica cada valor sucesivo del contador de ciclo por 2, y se le suma 2.
En la línea 11 se utiliza el calificador const para declarar lo que se conoce como una variable
constante llamada tamanioArreglo con el valor 5. Una variable constante que se utiliza para especificar
el tamaño de un arreglo debe inicializarse con una expresión constante cuando se declara y no puede
284 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
1 // Fig. 7.5: fig07_05.cpp
2 // Establece el arreglo s con los enteros pares del 2 al 10.
3 #include iostream
4 #include iomanip
5 #include array
6 using namespace std;
7
8 int main()
9 {
10 // la variable constante se puede usar para especificar el tamaño de los arreglos
11 const size_t tamanioArreglo = 5; // debe inicializarse en la declaración
12
13 array int, tamanioArreglo  s; // el arreglo s tiene 5 elementos
14
15 for ( size_t i = 0; i  s.size(); ++i ) // establece los valores
16 s[ i ] = 2 + 2 * i;
17
18 cout  Elemento  setw( 13 )  Valor  endl;
19
20 // imprime el contenido del arreglo s en formato tabular
21 for ( size_t j = 0; j  s.size(); ++j )
22 cout  setw( 7 )  j  setw( 13 )  s[ j ]  endl;
23 } // fin de main
Elemento Valor
0 2
1 4
2 6
3 8
4 10
Fig. 7.5  Establece el arreglo s con los enteros pares del 2 al 10.
modificarse de ahí en adelante (como se muestra en las figuras 7.6 y 7.7). Estas variables también se
conocen como constantes con nombre o variables de sólo lectura.
Error común de programación 7.2
Si no se inicializa una variable constante al momento de declararla, se produce un error de
compilación.
Error común de programación 7.3
Asignar un valor a una variable constante en una instrucción ejecutable es un error de
compilación.
1 // Fig. 7.6: fig07_06.cpp
2 // Uso de una variable constante inicializada en forma apropiada.
3 #include iostream
4 using namespace std;
5
6 int main()
7 {
Fig. 7.6  Uso de una variable constante inicializada en forma apropiada (parte 1 de 2).
7.4 Ejemplos acerca del uso de los arreglos 285
8 const int x = 7; // variable constante inicializada
9
10 cout  El valor de la variable constante x es:   x  endl;
11 } // fin de main
El valor de la variable constante x es: 7
1 // Fig. 7.7: fig07_07.cpp
2 // Una variable const debe inicializarse.
3
4 int main()
5 {
6 const int x; // Error: x debe inicializarse
7
8 x = 7; // Error: no se puede modificar una variable const
9 } // fin de main
Mensaje de error del compilador de Microsoft Visual C++:
error C2734: 'x' : const object must be initialized if not extern
error C3892: 'x' : you cannot assign to a variable that is const
Mensaje de error del compilador GNU C++:
fig07_07.cpp:6:14: error: uninitialized const 'x' [-fpermissive]
fig07_07.cpp:8:8: error: assignment of read-only variable 'x'
Mensaje de error del compilador LLVM:
Default initialization of an object of const type 'const int'
Fig. 7.7  Una variable const debe inicializarse.
En la figura 7.7, el error de compilación producido por Microsoft Visual C++ se refiere a la variable
int x como un “objeto const”. El estándar de C++ define a un “objeto” como una “región de almace-
namiento”. Al igual que los objetos de las clases, las variables de tipo fundamental también ocupan es-
pacio en memoria, por lo que se conocen comúnmente como “objetos”.
Las variables constantes se pueden colocar en cualquier parte en la que se espera una expresión
constante. En la figura 7.5, la variable constante tamanioArreglo especifica el tamaño del arreglo s en
la línea 13.
Buena práctica de programación 7.1
Definir el tamaño de un arreglo como una variable constante, en vez de una constante lite-
ral, hace a los programas más claros. Esta técnica elimina lo que se conoce como números
mágicos: valores numéricos que no se explican. Al usar una variable constante podemos
proporcionar un nombre para una constante literal; esto ayuda a explicar el propósito del
valor en el programa.
Fig. 7.6  Uso de una variable constante inicializada en forma apropiada (parte 2 de 2).
286 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
7.4.4Suma de los elementos de un arreglo
A menudo, los elementos de un arreglo representan una serie de valores para ser utilizados en un cálcu-
lo. Por ejemplo, si los elementos de un arreglo representan calificaciones de un examen, tal vez un pro-
fesor desea obtener el total de los elementos del arreglo y usar esa suma para calcular el promedio de la
clase para el examen.
El programa en la figura 7.8 suma los valores contenidos en el arreglo a de cuatro elementos enteros.
El programa declara, crea e inicializa el arreglo en la línea 10. La instrucción for (líneas 14 y 15) realiza
los cálculos. Los valores que se suministran como inicializadores para el arreglo a también se podrían
haber pedido, en el programa, al usuario mediante el teclado, o mediante un archivo en el disco (vea el
capítulo 14, Procesamiento de archivos). Por ejemplo, la instrucción for
for ( size_t j = 0; j  a.size(); ++j )
cin  a[ j ];
lee un valor a la vez del teclado y lo almacena en el elemento a[j].
1 // Fig. 7.8: fig07_08.cpp
2 // Cálculo de la suma de los elementos de un arreglo.
3 #include iostream
4 #include array
5 using namespace std;
6
7 int main()
8 {
9 const size_t tamanioArreglo = 4; // especifica el tamaño del arreglo
10 array int, tamanioArreglo  a = { 10, 20, 30, 40 };
11 int total = 0;
12
13 // suma el contenido del arreglo a
14 for ( size_t i = 0; i  a.size(); ++i )
15 total += a[ i ];
16
17 cout  Total de elementos del arreglo:   total  endl;
18 } // fin de main
Total de elementos del arreglo: 100
Fig. 7.8  Cálculo de la suma de los elementos de un arreglo.
7.4.5Uso de gráficos de barra para mostrar los datos de un arreglo
en forma gráfica
Muchos programas presentan datos a los usuarios en forma gráfica. Por ejemplo, los valores numéricos se
muestran comúnmente como barras en un gráfico de barras. En dicho gráfico, las barras más extensas
representan valores numéricos proporcionalmente más grandes. Una manera simple de mostrar datos
numéricos en forma gráfica es mediante un gráfico de barras que muestra cada valor numérico como una
barra de asteriscos (*).
A menudo, a los profesores les gusta examinar la distribución de calificaciones en un examen. Un
profesor podría graficar el número de calificaciones en cada una de varias categorías, para visualizar la
distribución de calificaciones. Suponga que las calificaciones fueron 87, 68, 94, 100, 83, 78, 85, 91, 76
y 87. Hubo una calificación de 100, dos calificaciones entre 90 y 99, cuatro entre 80 y 89, dos entre 70 y
79, una calificación entre 60 y 69, y ninguna menor a 60. Nuestro siguiente programa (figura 7.9) al-
macena estos datos en un arreglo de 11 elementos, cada uno de los cuales corresponde a una categoría
de calificaciones. Por ejemplo, n[0] indica el número de calificaciones en el rango de 0 a 9, n[7] indica
7.4 Ejemplos acerca del uso de los arreglos 287
1 // Fig. 7.9: fig07_09.cpp
2 // Programa para imprimir gráficos de barra.
3 #include iostream
4 #include iomanip
5 #include array
6 using namespace std;
7
8 int main()
9 {
10 const size_t tamanioArreglo = 11;
11 array unsigned int, tamanioArreglo  n =
12 { 0, 0, 0, 0, 0, 0, 1, 2, 4, 2, 1 };
13
14 cout  Distribucion de calificaciones:  endl;
15
16 // para cada elemento del arreglo n, imprime una barra del gráfico
17 for ( size_t i = 0; i  n.size(); ++i )
18 {
19 // imprime etiquetas de las barras (0-9:, ..., 90-99:, 100: )
20 if ( 0 == i )
21 cout   0-9: ;
22 else if ( 10 == i )
23 cout   100: ;
24 else
25 cout  i * 10  “-”  ( i * 10 ) + 9  : ;
26
27 // imprime barra de asteriscos
28 for ( unsigned int estrellas = 0; estrellas  n[ i ]; ++estrellas )
29 cout  '*';
30
31 cout  endl; // inicia una nueva línea de salida
32 } // fin de for externo
33 } // fin de main
Distribución de calificaciones:
0-9:
10-19:
20-29:
30-39:
40-49:
50-59:
60-69: *
70-79: **
80-89: ****
90-99: **
100: *
Fig. 7.9  Programa para imprimir gráficos de barra.
el número de calificaciones en el rango de 70 a 79 y n[10] indica el número de calificaciones de 100. Las
versiones de la clase LibroCalificaciones de las figuras 7.15 y 7.16, y de las figuras 7.22 y 7.23, con-
tienen código que calcula estas frecuencias de calificaciones, con base en un conjunto de calificaciones.
Por ahora crearemos el arreglo en forma manual, analizando el conjunto de calificaciones.
Elprograma lee losnúmerosdelarregloygraficalainformacióncomoungráficodebarras,mostran-
do cada rango de calificaciones seguido de una barra de asteriscos, los cuales indican el número de califi-
caciones en ese rango. Para etiquetar cada barra, en las líneas 20 a 25 se imprime un rango de calificacio-
nes (por ejemplo, 70-79: ) con base en el valor actual de la variable contador i. La instrucción for
288 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
anidada (líneas 28 y 29) imprime las barras. Observe la condición de continuación de ciclo en la línea
28 (estrellas  n[i]). Cada vez que el programa llega al for interior, el ciclo cuenta desde
0 hasta n[i], con lo cual usa un valor en el arreglo n para determinar el número de asteriscos a
mostrar. En este ejemplo, n[0] – n[5] contiene ceros, ya que ningún estudiante recibió una califica-
ción menor a 60. Por ende, el programa no muestra asteriscos enseguida de los primeros seis rangos
de calificaciones.
7.4.6Uso de los elementos de un arreglo como contadores
Algunas veces, los programas usan variables contadores para sintetizar datos, como los resultados de una
encuesta. En la figura 6.9, utilizamos contadores separados en nuestro programa para tirar dados, para
rastrear el número de ocurrencias de cada lado de un dado, a medida que el programa tiraba el dado
6000000veces.Enlafigura7.10semuestraunaversióndeesteprograma,enlaqueseutilizaunarreglo.
Esta versión también utiliza las nuevas herramientas de generación de números aleatorios de C++11 que
se introdujeron en la sección 6.9.
La figura 7.10 utiliza el arreglo frecuencia (línea 18) para contar las ocurrencias de cada lado del
dado. La instrucción individual en la línea 22 de este programa reemplaza a la instrucción switch en las
líneas 23 a 45 de la figura 6.9. En la línea 22 se utiliza un valor aleatorio para determinar cuál elemento
de frecuencia incrementar durante cada iteración del ciclo. El cálculo en la línea 22 produce un subín-
dice aleatorio de 1 a 6, por lo que el arreglo frecuencia debe ser lo suficientemente grande como para
almacenar seis contadores. Sin embargo, usamos un arreglo de siete elementos en el que ignoramos
1 // Fig. 7.10: fig07_10.cpp
2 // Programa para tirar dados que utiliza un arreglo en vez de una instrucción switch.
3 #include iostream
4 #include iomanip
5 #include array
6 #include random
7 #include ctime
8 using namespace std;
9
10 int main()
11 {
12 // usa el motor predeterminado de generación de números aleatorios para
13 // producir valores int seudoaleatorios distribuidos de manera uniforme de 1 a 6
14 default_random_engine motor( static_cast unsigned int ( time(0) ) );
15 uniform_int_distribution unsigned int  intAleatorio( 1, 6 );
16
17 const size_t tamanioArreglo = 7; // ignora el elemento cero
18 array unsigned int, tamanioArreglo  frecuencia = {}; // inicializa con ceros
19
20 // tira el dado 6 000 000 de veces; usa el valor del dado como índice de
frecuencia
21 for ( unsigned int tiro = 1; tiro = 6000000; ++tiro )
22 ++frecuencia[ intAleatorio( motor ) ];
23
24 cout  Cara  setw( 13 )  Frecuencia  endl;
25
26 // imprime el valor de cada elemento del arreglo
27 for ( size_t cara = 1; cara  frecuencia.size(); ++cara )
28 cout  setw( 4 )  cara  setw( 13 )  frecuencia[ cara ]
29  endl;
30 } // fin de main
Fig. 7.10  Programa para tirar dados que utiliza un arreglo en vez de una instrucción switch (parte 1 de 2).
7.4 Ejemplos acerca del uso de los arreglos 289
Cara Frecuencia
1 1000167
2 1000149
3 1000152
4 998748
5 999626
6 1001158
frecuencia[0]; es más lógico hacer que la cara del dado con el valor 1 incremente a frecuencia[1]
que a frecuencia[0]. Por ende, el valor de cada cara se utiliza como subíndice para el arreglo frecuen-
cia.También reemplazamos las líneas 49 a 54 de la figura 6.9, iterando a través del arreglo frecuencia
para imprimir los resultados (figura 7.10, líneas 27 a 29).
7.4.7Uso de arreglos para sintetizar los resultados de una encuesta
Nuestro siguiente ejemplo utiliza arreglos para sintetizar los resultados de los datos recolectados en una
encuesta. Considere el siguiente enunciado del problema:
Se pidió a veinte estudiantes que calificaran la calidad de la comida en la cafetería estudiantil, en una
escala del 1 al 5, en donde 1 significa “pésimo” y 5 significa “excelente”. Coloque las 20 respuestas en
un arreglo entero y determine la frecuencia de cada calificación.
Ésta es un tipo popular de aplicación de procesamiento de arreglos (figura 7.11). Deseamos resumir
el número de respuestas de cada tipo (es decir, del 1 al 5). El arreglo respuestas (líneas 15 a 16) es
un arreglo entero de 20 elementos, y contiene las respuestas de los estudiantes a la encuesta. El arreglo
respuestas se declara como const, ya que sus valores no cambian (y no deben hacerlo). Utilizamos un
arreglo de seis elementos llamado frecuencia (línea 19) para contar el número de ocurrencias de cada
respuesta. Cada elemento del arreglo se utiliza como un contador para una de las respuestas de la encues-
ta, y se inicializa con cero. Al igual que en la figura 7.10, ignoramos frecuencia[0].
1 // Fig. 7.11: fig07_11.cpp
2 // Programa para analizar encuestas.
3 #include iostream
4 #include iomanip
5 #include array
6 using namespace std;
7
8 int main()
9 {
10 // define los tamaños de los arreglos
11 const size_t tamanioRespuesta = 20; // tamaño del arreglo respuestas
12 const size_t tamanioFrecuencia = 6; // tamaño del arreglo frecuencia
13
14 // coloca las respuestas de la encuesta en el arreglo respuestas
15 const array unsigned int, tamanioRespuesta  respuestas =
16 { 1, 2, 5, 4, 3, 5, 2, 1, 3, 1, 4, 3, 3, 3, 2, 3, 3, 2, 2, 5 };
Fig. 7.10  Programa para tirar dados que utiliza un arreglo en vez de una instrucción switch (parte 2 de 2).
Fig. 7.11  Programa para analizar encuestas (parte 1 de 2).
290 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
17
18 // inicializa los contadores de frecuencia con 0
19 array unsigned int, tamanioFrecuencia  frecuencia = {};
20
21 // para cada respuesta, selecciona el elemento de respuestas y usa ese valor
22 // acomo subíndice de frecuencia para determinar el elemento a incrementar
23 for ( size_t respuesta = 0; respuesta  respuestas.size(); ++respuesta )
24 ++frecuencia[ respuestas[ respuesta ] ];
25
26 cout  Calificacion  setw( 17 )  Frecuencia  endl;
27
28 // imprime el valor de cada elemento del arreglo
29 for ( size_t calificacion = 1; calificacion  frecuencia.size(); ++calificacion )
30 cout  setw( 6 )  calificacion  setw( 17 )  frecuencia[ calificacion ]
31  endl;
32 } // fin de main
Calificacion Frecuencia
1 3
2 5
3 7
4 2
5 3
La primera instrucción for (líneas 23 y 24) recibe las respuestas, una a la vez, del arreglo respuestas
eincrementaunodeloscincocontadoresenelarreglofrecuencia (defrecuencia[1] afrecuencia[5]).
La instrucción clave en el ciclo es la línea 24, la cual incrementa el contador de frecuencia apropiado,
dependiendo del valor de respuestas[respuesta].
Consideraremos varias iteraciones del ciclo for. Cuando la variable de control respuesta es 0, el
valor de respuestas[respuesta] es el valor de respuestas[0] (es decir, 1 en la línea 16), por lo que
el programa interpreta a ++frecuencia[respuestas[respuesta]] como
++frecuencia[ 1 ]
con lo cual se incrementa el valor en el elemento 1 del arreglo. Para evaluar la expresión, empiece con el
valor en el conjunto más interno de corchetes (respuesta). Una vez que conozca el valor de respuesta
(que viene siendo el valor de la variable de control de ciclo en la línea 23), insértelo en la expresión y
evalúe el siguiente conjunto más externo de corchetes (respuestas[respuesta], que es un valor selec-
cionado del arreglo respuestas en las líneas 15 a 16). Después utilice el valor resultante como subíndi-
ce del arreglo frecuencia, para especificar cuál contador se va a incrementar.
Cuando respuesta es 1, respuestas[respuesta] es el valor de respuestas[1], que es 2, por lo
que el programa interpreta a ++frecuencia[respuestas[respuesta]] como
++frecuencia[ 2 ]
con lo cual se incrementa el elemento 2 del arreglo.
Cuando respuesta es 2, respuestas[respuesta] es el valor de respuestas[2], que es 5, por lo
que el programa interpreta a ++frecuencia[respuestas[respuesta]] como
++frecuencia[ 5 ]
Fig. 7.11  Programa para analizar encuestas (parte 2 de 2).
7.4 Ejemplos acerca del uso de los arreglos 291
con lo cual se incrementa el elemento 5 del arreglo, y así en lo sucesivo. Sin importar el número de res-
puestas procesadas en la encuesta, el programa sólo requiere un arreglo de seis elementos (en el cual se
ignora el elemento cero) para resumir los resultados, ya que todos los valores de las respuestas se encuen-
tran entre 1 y 5, y los valores de subíndice para un arreglo de seis elementos son del 0 al 5.
Comprobación de límites para los subíndices de un arreglo
Si los datos en el arreglo respuestas tuvieran valores inválidos, como 13, el programa trataría de sumar
1 a frecuencia[13], lo cual se encuentra fuera de los límites del arreglo. Cuando usamos el operador []
paraaccederalelementodeunarreglo,C++nocuentaconcomprobacióndelímitesautomáticadearreglospara
evitar que hagamos referencia a un elemento que no existe. Por lo tanto, un programa en ejecución puede
“salirse” de cualquier extremo de un arreglo sin advertencia. En la sección 7.10 demostraremos la fun-
ción at de la plantilla de clase vector, que realiza la comprobación de límites por el usuario. La planti-
lla de clase array también tiene una función at.
Es importante asegurar que cada subíndice que utilicemos para acceder al elemento de un arreglo
se encuentre dentro de los límites de ese arreglo; es decir, mayor o igual a 0 y menor que el número de
elementos del arreglo.
A la acción de permitir que los programas lean de, o escriban en, los elementos de un arreglo fuera
de los límites, se le conoce como falla de seguridad. Si se intentan leer elementos fuera de los límites de un
arreglo, el programa puede fallar o incluso tal vez parezca ejecutarse correctamente al utilizar datos inco-
rrectos.Siescribimosenunelementofueradeloslímites(loqueseconocecomodesbordamientodebúfer),
los datos del programa en memoria podrían corromperse, el programa podría fallar y dejar que algún
atacante explote el sistema y ejecute su propio código. Para obtener más información sobre desborda-
mientos de búfer, consulte en.wikipedia.org/wiki/Buffer_overflow (http://es.wikipedia.org/
wiki/Desbordamiento_de_b%C3%BAfer en español).
Error común de programación 7.4
Hacer referencia a un elemento fuera de los límites del arreglo es un error lógico en tiem-
po de ejecución. No es un error de sintaxis.
Tip para prevenir errores 7.1
Al iterar a través de un arreglo, el subíndice del arreglo debe ser mayor o igual a 0 y siempre
debe ser menor que el número total de elementos en el arreglo (uno menos que el tamaño del
arreglo). Asegúrese que la condición de terminación de ciclo evite acceder a los elementos fuera
de este rango. En los capítulos 15 y 16 aprenderá sobre los iteradores, que pueden ayudar a
evitar el acceso a los elementos fuera de los límites de un arreglo (o de otro contenedor).
7.4.8Arreglos locales estáticos y arreglos locales automáticos
En el capítulo 6 hablamos sobre el especificador de clase de almacenamiento static. Una variable local
static en la definición de una función existe durante todo el programa, pero sólo puede verse en el
cuerpo de la función.
Tip de rendimiento 7.1
Podemos aplicar static a la declaración de un arreglo local, de manera que el arreglo no
se cree e inicialice cada vez que el programa llame a la función, y no se destruya cada vez
que termine la función en el programa. Esto puede mejorar el rendimiento, en especial
cuando se utilizan arreglos extensos.
Un programa inicializa los arreglos locales static la primera vez que encuentra sus declaraciones.
Si el programador no inicializa un arreglo static de manera explícita, el compilador inicializa con cero
cada elemento de ese arreglo al momento de su creación. Recuerde que C++ no realiza dicha inicializa-
ción predeterminada para las variables automáticas.
292 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
La figura 7.12 demuestra la función inicArregloStatic (líneas 24 a 40) con un arreglo local
static (línea 27) y la función inicArregloAutomatico (líneas 43 a 59) con un arreglo local automáti-
co (línea 46).
1 // Fig. 7.12: fig07_12.cpp
2 // Inicialización de un arreglo static e inicialización de un arreglo automático.
3 #include iostream
4 #include array
5 using namespace std;
6
7 void inicArregloStatic(); // prototipo de función
8 void inicArregloAutomatico(); // prototipo de función
9 const size_t tamanioArreglo = 3;
10
11 int main()
12 {
13 cout  Primera llamada a cada funcion:n;
14 inicArregloStatic();
15 inicArregloAutomatico();
16
17 cout  nnSegunda llamada a cada funcion:n;
18 inicArregloStatic();
19 inicArregloAutomatico();
20 cout  endl;
21 } // fin de main
22
23 // función para demostrar un arreglo local static
24 void inicArregloStatic( void )
25 {
26 // inicializa los elementos con 0 la primera vez que se llama a la función
27 static array int, tamanioArreglo  arreglo1; // arreglo local static
28
29 cout  nValores al entrar en inicArregloStatic:n;
30
31 // imprime el contenido de arreglo1
32 for ( size_t i = 0; i  arreglo1.size(); ++i )
33 cout  arreglo1[  i  ] =   arreglo1[ i ]   ;
34
35 cout  nValores al salir de inicArregloStatic:n;
36
37 // modifica e imprime el contenido de arreglo1
38 for ( size_t j = 0; j  arreglo1.size(); ++j )
39 cout  arreglo1[  j  ] =   ( arreglo1[ j ] += 5 )   ;
40 } // fin de la función inicArregloStatic
41
42 // función para demostrar un arreglo local automático
43 void inicArregloAutomatico( void )
44 {
45 // inicializa los elementos cada vez que se llama a la función
46 array int, tamanioArreglo  arreglo2 = { 1, 2, 3 }; // arreglo local automático
47
48 cout  nnValores al entrar a inicArregloAutomatico:n;
Fig. 7.12  Inicialización de un arreglo static e inicialización de un arreglo automático (parte 1 de 2).
7.5 Instrucción for basada en rango 293
49
50 // imprime el contenido de arreglo2
51 for ( size_t i = 0; i  arreglo2.size(); ++i )
52 cout  arreglo2[  i  ] =   arreglo2[ i ]   ;
53
54 cout  nValores al salir de inicArregloAutomatico:n;
55
56 // modifica e imprime el contenido de arreglo2
57 for ( size_t j = 0; j  arreglo2.size(); ++j )
58 cout  arreglo2[  j  ] =   ( arreglo2[ j ] += 5 )   ;
59 } // fin de la función inicArregloAutomatico
Primera llamada a cada funcion:
Valores al entrar en inicArregloStatic:
arreglo1[0] = 0 arreglo1[1] = 0 arreglo1[2] = 0
Valores al salir de inicArregloStatic:
arreglo1[0] = 5 arreglo1[1] = 5 arreglo1[2] = 5
Valores al entrar a inicArregloStatic:
arreglo2[0] = 1 arreglo2[1] = 2 arreglo2[2] = 3
Valores al salir de inicArregloStatic:
arreglo2[0] = 6 arreglo2[1] = 7 arreglo2[2] = 8
Segunda llamada a cada funcion:
Valores al entrar en inicArregloStatic:
arreglo1[0] = 5 arreglo1[1] = 5 arreglo1[2] = 5
Valores al salir de inicArregloStatic:
arreglo1[0] = 10 arreglo1[1] = 10 arreglo1[2] = 10
Valores al entrar a inicArregloAutomatico:
arreglo2[0] = 1 arreglo2[1] = 2 arreglo2[2] = 3
Valores al salir de inicArregloAutomatico:
arreglo2[0] = 6 arreglo2[1] = 7 arreglo2[2] = 8
La función inicArregloStatic se llama dos veces (líneas 14 y 18). El compilador inicializa el
arreglo local static arreglo1 con cero la primera vez que se hace una llamada a la función. Ésta impri-
me el arreglo, suma 5 a cada elemento e imprime el arreglo de nuevo. La segunda vez que se llama a la
función, el arreglo static contiene los valores modificados que se almacenan durante la primera llama-
da a la función.
La función inicArregloAutomatico también se llama dos veces (líneas 15 y 19). Los elementos
del arreglo local automático arreglo2 se inicializan (línea 46) con los valores 1, 2 y 3. La función
imprime el arreglo, suma 5 a cada elemento e imprime el arreglo de nuevo. La segunda vez que se llama
a la función, los elementos del arreglo se reinicializan con 1, 2 y 3. El arreglo tiene una duración
de almacenamiento automática, por lo que se vuelve a crear y se reinicializa durante cada llamada a
inicArregloAutomatico.
7.5Instrucción for basada en rango
Como hemos visto, es común procesar todos los elementos de un arreglo. La nueva instrucción for
basada en rango de C++11 nos permite hacerlo sin usar un contador, con lo que evitamos la posibilidad
de “salirnos” del arreglo y eliminamos la necesidad de implementar nuestra propia comprobación de
límites.
Fig. 7.12  Inicialización de un arreglo static e inicialización de un arreglo automático (parte 2 de 2).
294 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
Tip para prevenir errores 7.2
Al procesar todos los elementos de un arreglo, si no necesita acceso al subíndice de los ele-
mentos del mismo, use la instrucción for basada en rango.
La sintaxis de la instrucción for basada en rango es:
for ( declaracionVariableRango : expresión )
instrucción
en donde declaracionVariableRango tiene un tipo y un identificador (como int elemento), y expresión
es el arreglo a través del cual se va a iterar. El tipo en la declaracionVariableRango debe ser consistente con
el tipo de los elementos del arreglo. El identificador representa los valores sucesivos de los elementos del
arregloeniteracionessucesivasdelciclo.Podemosusarlainstrucciónfor basadaenrangoconlamayoría
delasestructurasdedatospreconstruidasdelaBibliotecaestándardeC++(queseconocencomúnmente
como contenedores), incluyendo las clases array y vector.
Lafigura7.13usalainstrucciónfor basadaenrangoparamostrarelcontenidodeunarreglo(líneas
13 y 14; líneas 22 y 23) y para multiplicar cada uno de los valores de los elementos del arreglo por 2
(líneas 17 y 18).
1 // Fig. 7.13: fig07_13.cpp
2 // Uso de la instrucción for basada en rango para multiplicar los elementos de un
arreglo por 2.
3 #include iostream
4 #include array
5 using namespace std;
6
7 int main()
8 {
9 array int, 5  items = { 1, 2, 3, 4, 5 };
10
11 // muestra los items antes de modificarlos
12 cout  items antes de modificarlos: ;
13 for ( int item : items )
14 cout  item   ;
15
16 // multiplica los elementos de los ítems por 2
17 for ( int refItem : items )
18 refItem *= 2;
19
20 // muestra los ítems después de modificarlos
21 cout  nitems despues de modificarlos: ;
22 for ( int item : items )
23 cout  item   ;
24
25 cout  endl;
26 } // fin de main
items antes de modificarlos: 1 2 3 4 5
items después de modificarlos: 2 4 6 8 10
Fig. 7.13  Uso de la instrucción for basada en rango para multiplicar los elementos de un arreglo por 2.
Uso del for basado en rango para mostrar el contenido de un arreglo
La instrucción for basada en rango simplifica el código para iterar por un arreglo. La línea 13 puede
leerse como “para cada iteración, asignar el siguiente elemento de items a la variable item, después
7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para... 295
ejecutar la siguiente instrucción”. Así, para cada iteración, el identificador item representa un elemento
en items. Las líneas 13 y 14 son equivalentes a la siguiente repetición controlada por contador:
for ( int contador = 0; contador  items.size(); ++contador )
cout  items[ contador ]   ;
Uso de la instrucción for basada en rango para modificar el contenido de un arreglo
Las líneas 17 y 18 usan una instrucción for basada en rango para multiplicar cada elemento de items
por 2. En la línea 17, la declaracionVariableRango indica que refItem es una referencia int (). Recuerde
que una referencia es un alias para otra variable en memoria; en este caso, uno de los elementos del
arreglo. Usamos una referencia int debido a que items contiene valores int y queremos modificar
el valor de cada elemento; ya que refItem se declara como referencia, cualquier modificación realizada
en refItem cambia el valor del elemento correspondiente en el arreglo.
Uso del subíndice de un elemento
La instrucción for basada en rango puede usarse en vez de la instrucción for controlada por contador,
cada vez que el código que itera a través de un arreglo no requiera acceso al subíndice del elemento. Por
ejemplo, para obtener el total de los enteros en un arreglo (como en la figura 7.8) se requiere acceso sólo
a los valores de los elementos; sus subíndices son irrelevantes. No obstante, si un programa debe usar
subíndices por alguna razón que no sea iterar a través de un arreglo (por ejemplo, para imprimir un
número de subíndice enseguida del valor de cada elemento del arreglo, como en los primeros ejemplos
de este capítulo), debe usar la instrucción for controlada por contador.
7.6Caso de estudio: la clase LibroCalificaciones
que usa un arreglo para almacenar las calificaciones
En esta sección desarrollaremos aún más la clase LibroCalificaciones, que presentamos en el capítulo 3
y ampliamos en los capítulos 4 a 6. Recuerde que esta clase representa un libro de calificaciones utilizado
por un instructor para almacenar y analizar un conjunto de calificaciones de estudiantes. Las versiones
anteriores de esta clase procesan un conjunto de calificaciones introducidas por el usuario, pero no man-
tienen los valores de las calificaciones individuales en los miembros de datos de la clase. Por ende,
los cálculos repetidos requieren que el usuario vuelva a introducir las calificaciones. Una manera de resol-
ver este problema sería almacenar cada calificación introducida por el usuario en un miembro de datos
individual de la clase. Por ejemplo, podríamos crear los miembros de datos calificacion1, califica-
cion2, …, calificacion10 en la clase LibroCalificaciones para almacenar 10 calificaciones de estu-
diantes. No obstante, el código para totalizar las calificaciones y determinar el promedio de la clase sería
voluminoso. En esta sección resolvemos este problema, almacenando las calificaciones en un arreglo.
Almacenar las calificaciones de los estudiantes en un arreglo en la clase LibroCalificaciones
La figura 7.14 muestra la salida que sintetiza las 10 calificaciones que almacenamos en un objeto de la
siguienteversióndelaclaseLibroCalificaciones (figuras7.15y7.16)queutilizaunarreglodeenteros
para almacenar las calificaciones de 10 estudiantes en un solo examen. Esto elimina la necesidad de
introducir varias veces el mismo conjunto de calificaciones. El arreglo calificaciones se declara como
miembro de datos en la línea 28 de la figura 7.15; por lo tanto, cada objeto LibroCalificaciones
mantiene su propio conjunto de calificaciones.
Bienvenido al libro de calificaciones para
CS101 Introduccion a la programacion en C++!
Fig.7.14  SalidadelejemplodeLibroCalificaciones quealmacenalascalificacionesenunarreglo(parte1de2).
296 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
Las calificaciones son:
Estudiante 1: 87
Estudiante 2: 68
Estudiante 3: 94
Estudiante 4: 100
Estudiante 5: 83
Estudiante 6: 78
Estudiante 7: 85
Estudiante 8: 91
Estudiante 9: 76
Estudiante 10: 87
El promedio de la clase es 84.90
La calificacion mas baja es 68
La calificacion mas alta es 100
Distribucion de calificaciones:
0-9:
10-19:
20-29:
30-39:
40-49:
50-59:
60-69: *
70-79: **
80-89: ****
90-99: **
100: *
1 // Fig. 7.15: LibroCalificaciones.h
2 // Definición de la clase LibroCalificaciones que usa un arreglo para almacenar
calificaciones de una prueba.
3 // Las funciones miembro se definen en LibroCalificaciones.cpp
4 #include string
5 #include array
6
7 // definición de la clase LibroCalificaciones
8 class LibroCalificaciones
9 {
10 public:
11 // constante -- número de estudiantes que tomaron la prueba
12 static const size_t estudiantes = 10; // observe los datos públicos
13
14 // el constructor inicializa el nombre del curso y el arreglo de calificaciones
15 LibroCalificaciones( const std::string , const std::array int, estudiantes
  );
16
17 void establecerNombreCurso( const std::string  ); // establece el nombre del
curso
18 string obtenerNombreCurso() const; // obtiene el nombre del curso
19 void mostrarMensaje() const; // muestra un mensaje de bienvenida
20 void procesarCalificaciones() const; // realiza varias operaciones con los
datos de las calificaciones
21 int obtenerMinimo() const; // busca la calificación mínima para la prueba
22 int obtenerMaximo() const; // busca la calificación máxima para la prueba
Fig. 7.14  Salida del ejemplo de LibroCalificaciones que almacena las calificaciones en un arreglo
(parte 2 de 2).
Fig. 7.15  Definición de la clase LibroCalificaciones que usa un arreglo para almacenar calificaciones
de una prueba (parte 1 de 2).
7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para... 297
23 double obtenerPromedio() const; // determina la calificación promedio para la
prueba
24 void imprimirGraficoBarras() const; // imprime gráfico de barras de la
distribución de calificaciones
25 void imprimirCalificaciones() const; // imprime el contenido del arreglo
calificaciones
26 private:
27 std::string nombreCurso; // nombre del curso para este libro de calificaciones
28
std::array int, estudiantes  calificaciones; // arreglo de calificaciones
de estudiantes
29 }; // fin de la clase LibroCalificaciones
1 // Fig. 7.16: LibroCalificaciones.cpp
2 // Funciones miembro para la clase LibroCalificaciones
3 // que manipulan un arreglo de calificaciones.
4 #include iostream
5 #include iomanip
6 #include LibroCalificaciones.h // definición de la clase LibroCalificaciones
7 using namespace std;
8
9 // el constructor inicializa nombreCurso y el arreglo calificaciones
10 LibroCalificaciones::LibroCalificaciones( const string nombre,
11 const array int, estudiantes  arregloCalificaciones )
12 : nombreCurso( nombre ), calificaciones( arregloCalificaciones )
13 {
14 } // fin del constructor de LibroCalificaciones
15
16 // función para establecer el nombre del curso
17 void LibroCalificaciones::establecerNombreCurso( const string nombre )
18 {
19 nombreCurso = nombre; // almacena el nombre del curso
20 } // fin de la función establecerNombreCurso
21
22 // función para obtener el nombre del curso
23 string LibroCalificaciones::obtenerNombreCurso() const
24 {
25 return nombreCurso;
26 } // fin de la función obtenerNombreCurso
27
28 // muestra un mensaje de bienvenida para el usuario de LibroCalificaciones
29 void LibroCalificaciones::mostrarMensaje() const
30 {
31 // esta instrucción llama a obtenerNombreCurso para obtener el
32 // nombre del curso que representa este LibroCalificaciones
33 cout  Bienvenido al libro de calificaciones paran
 obtenerNombreCurso()  !
34  endl;
35 } // fin de la función mostrarMensaje
36
37 // realiza varias operaciones con los datos
38 void LibroCalificaciones::procesarCalificaciones() const
39 {
40 // imprime el arreglo calificaciones
41 imprimirCalificaciones();
42
Fig. 7.15  Definición de la clase LibroCalificaciones que usa un arreglo para almacenar calificaciones
de una prueba (parte 2 de 2).
28
Fig. 7.16  Funciones miembro de la clase LibroCalificaciones que manipulan un arreglo
de calificaciones (parte 1 de 3).
298 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
43 // llama a la función obtenerPromedio para calcular la calificacion promedio
44 cout  setprecision( 2 )  fixed;
45 cout  nEl promedio de la clase es   obtenerPromedio()  endl;
46
47 // llama a las funciones obtenerMinimo y obtenerMaximo
48 cout  La calificacion mas baja es   obtenerMinimo()
 nLa calificacion mas alta es 
49  obtenerMaximo()  endl;
50
51 // llama a la función imprimirGraficoBarras para imprimir el gráfico de
ditribución de calificaciones
52 imprimirGraficoBarras();
53 } // fin de la función procesarCalificaciones
54
55 // busca la calificación mínima
56 int LibroCalificaciones::obtenerMinimo() const
57 {
58 int calificacionInf = 100; // asume que la calificación más baja es 100
59
60 // itera a través del arreglo calificaciones
61 for ( int calificacion : calificaciones )
62 {
63 // si la calificación actual es menor que calificacionInf, la asigna a
calificacionInf
64 if ( calificacion  calificacionInf )
65 calificacionInf = calificacion; // nueva calificación más baja
66 } // fin de for
67
68 return calificacionInf; // devuelve la calificación más baja
69 } // fin de la función obtenerMinimo
70
71 // busca la calificación máxima
72 int LibroCalificaciones::obtenerMaximo() const
73 {
74 int calificacionSup = 0; // asume que la calificación más alta es 0
75
76 // itera a través del arreglo calificaciones
77 for ( int calificacion : calificaciones )
78 {
79 // si la calificación actual es mayor que calificacionSup, la asigna a
calificacionSup
80 if ( calificacion  calificacionSup )
81 calificacionSup = calificacion; // nueva calificación más alta
82 } // fin de for
83
84 return calificacionSup; // devuelve la calificación más alta
85 } // fin de la función obtenerMaximo
86
87 // determina la calificación promedio para la prueba
88 double LibroCalificaciones::obtenerPromedio() const
89 {
90 int total = 0; // inicializa el total
91
92 // suma las calificaciones en el arreglo
93 for ( int calificacion : calificaciones )
94 total += calificacion;
95
Fig. 7.16  Funciones miembro de la clase LibroCalificaciones que manipulan un arreglo
de calificaciones (parte 2 de 3).
7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para... 299
96 // devuelve el promedio de las calificaciones
97 return static_cast double ( total ) / calificaciones.size();
98 } // fin de la función obtenerPromedio
99
100 // imprime gráfico de barras que muestra la distribución de las calificaciones
101 void LibroCalificaciones::imprimirGraficoBarras() const
102 {
103 cout  nDistribucion de calificaciones:  endl;
104
105 // almacena la frecuencia de calificaciones en cada rango de 10 calificaciones
106 const size_t tamanioFrecuencia = 11;
107 array unsigned int, tamanioFrecuencia  frecuencia = {};
// inicializa elementos con 0
108
109 // para cada calificación, incrementa la frecuencia apropiada
110 for ( int calificacion : calificaciones )
111 ++frecuencia[ calificacion / 10 ];
112
113 // para cada frecuencia de calificación, imprime barra en el gráfico
114 for ( size_t cuenta = 0; cuenta  tamanioFrecuencia; ++cuenta )
115 {
116 // imprime etiquetas de las barras (0-9:, ..., 90-99:, 100: )
117 if ( 0 == cuenta )
118 cout   0-9: ;
119 else if ( 10 == cuenta )
120 cout   100: ;
121 else
122 cout  cuenta * 10  -  ( cuenta * 10 ) + 9  : ;
123
124 // imprime barra de asteriscos
125 for ( unsigned int estrellas = 0; estrellas  frecuencia[ cuenta ];
++estrellas )
126 cout  '*';
127
128 cout  endl; // empieza una nueva línea de salida
129 } // fin de for exterior
130 } // fin de la función imprimirGraficoBarras
131
132 // imprime el contenido del arreglo calificaciones
133 void LibroCalificaciones::imprimirCalificaciones() const
134 {
135 cout  nLas calificaciones son:nn;
136
137 // imprime la calificación de cada estudiante
138 for ( size_t estudiante = 0; estudiante  calificaciones.size(); ++estudiante )
139 cout  Estudiante   setw( 2 )  estudiante + 1  :   setw( 3 )
140  calificaciones[ estudiante ]  endl;
141 } // fin de la función imprimirCalificaciones
El tamaño del arreglo en la línea 28 de la figura 7.15 se especifica mediante el miembro de datos
public static const llamado estudiantes (declarado en la línea 12), el cual es public, de manera
que sea accesible para los clientes de la clase. Pronto veremos un ejemplo de un programa cliente que
utiliza esta constante. Al declarar a estudiantes con el calificador const, indicamos que este miembro
de datos es constante; su valor no se puede modificar después de inicializarlo. La palabra clave static
en esta declaración de variable indica que el miembro de datos es compartido por todos los objetos de la
Fig. 7.16  Funciones miembro de la clase LibroCalificaciones que manipulan un arreglo
de calificaciones (parte 3 de 3).
300 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
clase; así que, en esta implementación específica de la clase LibroCalificaciones, todos los objetos
LibroCalificaciones almacenan calificaciones para el mismo número de estudiantes. En la sección
3.4 vimos que cuando cada objeto de una clase mantiene su propia copia de un atributo, la variable que
representa a ese atributo se conoce como miembro de datos; cada objeto (instancia) de la clase tiene una
copia separada de la variable en memoria. Hay variables para las que cada objeto de una clase no tiene
una copia separada. Éste es el caso con los miembros de datos static, que también se conocen como
variables de clase. Cuando se crean los objetos de una clase que contiene miembros de datos static,
todos los objetos comparten una copia de los miembros de datos static de la clase. Se puede acceder a
un miembro de datos static dentro de la definición de la clase y de las definiciones de las funciones
miembro al igual que con cualquier otro miembro de datos. Como veremos más adelante, también se
puede acceder a un miembro de datos public static desde el exterior de la clase, aun cuando no existan
objetos de la misma; para ello se utiliza el nombre de la clase, seguido del operador de resolución de
ámbito (::) y el nombre del miembro de datos. En el capítulo 9 aprenderá más acerca de los miembros
de datos static.
Constructor
El constructor de la clase (declarado en la línea 15 de la figura 7.15 y definido en las líneas 10 a 14 de la
figura 7.16) tiene dos parámetros: el nombre del curso y una referencia a un arreglo de calificaciones.
Cuando un programa crea un objeto LibroCalificaciones (por ejemplo, en la línea 15 de la figura
7.17), el programa pasa un arreglo int existente al constructor, el cual copia los valores del arreglo en
el miembro de datos calificaciones (línea 12 de la figura 7.16). Los valores de las calificaciones en el
arreglo que se pasa podrían haberse recibido de un usuario, o de un archivo en disco (como veremos en
el capítulo 14, Procesamiento de archivos). En nuestro programa de prueba, simplemente inicializamos
un arreglo con un conjunto de valores de calificaciones (figura 7.17, líneas 11 y 12). Una vez que las
calificaciones se almacenan en el miembro de datos calificaciones de la clase LibroCalificaciones,
todas las funciones miembro de la clase pueden acceder al arreglo calificaciones según sea necesario,
para realizar varios cálculos. Cabe mencionar que el constructor recibe tanto el parámetro string como
el arreglo por referencia; esto es más eficiente que recibir copias del objeto string original y del arreglo
originales. El constructor no necesita modificar el objeto string ni el arreglo originales, por lo que
también declaramos cada parámetro como const, para asegurar que el constructor no modifique acci-
dentalmente los datos originales en la función que hizo la llamada. También modificamos la función
establecerNombreCurso para que reciba su argumento string por referencia.
La función miembro procesarCalificaciones
La función miembro procesarCalificaciones (declarada en la línea 20 de la figura 7.15 y definida
en las líneas 38 a 53 de la figura 7.16) contiene una serie de llamadas a funciones miembro que pro-
duce un reporte en el que se resumen las calificaciones. La línea 41 llama a la función miembro
imprimirCalificaciones para imprimir el contenido del arreglo calificaciones. Las líneas 138 a
140 en la función miembro imprimirCalificaciones utilizan una instrucción for para imprimir la
calificación de cada estudiante. Aunque los subíndices de los arreglos empiezan en 0, lo común es que
el profesor enumere a los estudiantes empezando desde 1. Por ende, las líneas 139 y 140 imprimen
estudiante + 1 como el número de estudiante para producir las etiquetas Estudiante 1: ,
Estudiante 2: , y así en lo sucesivo.
La función miembro obtenerPromedio
A continuación, la función miembro procesarCalificaciones llama a la función miembro obtener-
Promedio (línea 45) para obtener el promedio de las calificaciones. La función miembro obtenerPro-
medio (declarada en la línea 23 de la figura 7.15 y definida en las líneas 88 a 98 de la figura 7.16) totali-
za los valores en el arreglo calificaciones antes de calcular el promedio. El cálculo del promedio en la
línea 97 utiliza calificaciones.size() para determinar el número de calificaciones que se van a
promediar.
7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para... 301
Las funciones miembro obtenerMinimo y obtenerMaximo
Las líneas 48 y 49 en procesarCalificaciones llaman a las funciones miembro obtenerMinimo y
obtenerMaximo paradeterminarlascalificacionesmásbajaymásaltadecualquierestudianteenelexamen.
Vamos a examinar la forma en que la función miembroobtenerMinimo encuentra la calificación más baja.
Comolacalificaciónmásaltapermitidaes100,empezamosporsuponerque100eslacalificaciónmásbaja
(línea 58). Después comparamos cada uno de los elementos en el arreglo con la calificación más ba-
ja, buscando valores más pequeños. En las líneas 61 a 66 de la función miembro obtenerMinimo se itera a
través del arreglo, y en la línea 64 se compara cada calificación con calificacionInf. Si una calificación
esmenorquecalificacionInf,acalificacionInf seleasignaesacalificación.Cuandoseejecutalalínea
68,calificacionInf contienelacalificaciónmásbajaenelarreglo.LafunciónmiembroobtenerMaximo
(líneas 72 a 85) funciona de manera similar a la función miembro obtenerMinimo.
La función miembro imprimirGraficoBarras
Por último, la línea 52 en la función miembro procesarCalificaciones llama a la función miembro
imprimirGraficoBarras para imprimir un gráfico de distribución de los datos de las calificaciones,
usando una técnica similar a la de la figura 7.9. En ese ejemplo, calculamos en forma manual el número
de calificaciones en cada categoría (es decir, 0-9, 10-19, …, 90-99 y 100), para lo cual simplemente
analizamos un conjunto de calificaciones. En las líneas 110 y 111, de este ejemplo, se utiliza una técni-
ca similar a la de las figuras 7.10 y 7.11 para calcular la frecuencia de calificaciones en cada categoría. En
la línea 107 se declara y crea el arreglo frecuencia de 11 valores del tipo unsigned int para almacenar la
frecuencia de calificaciones en cada categoría. Para cada calificacion en el arreglo calificaciones,
en las líneas 110 y 111 se incrementa el elemento apropiado del arreglo frecuencia. Para determinar
qué elemento se debe incrementar, en la línea 111 se divide la calificacion actual entre 10, usando la
división entera. Por ejemplo, si calificacion es 85, en la línea 111 se incrementa frecuencia[8] para
actualizar la cuenta de calificaciones en el rango de 80 a 89. Después, en las líneas 114 a 129 se imprime
el gráfico de barras (vea la figura 7.17) con base en los valores en el arreglo frecuencia. Al igual que en
las líneas 28 y 29 de la figura 7.9, en las líneas 125 y 126 de la figura 7.16 se utiliza un valor en el arreglo
frecuencia para determinar el número de asteriscos a mostrar en cada barra.
Prueba de la clase LibroCalificaciones
El programa de la figura 7.17 crea un objeto de la clase LibroCalificaciones (figuras 7.15 y 7.16) me-
diante el uso del arreglo int calificaciones (que se declara y se inicializa en las líneas 11 y 12). Usamos
el operador de resolución de ámbito (::) en la expresión “LibroCalificaciones::estudiantes” (línea
11) para acceder a la constante static llamada estudiantes, de la clase LibroCalificaciones. Utiliza-
mos aquí esta constante para crear un arreglo que sea del mismo tamaño que el arreglo que se almacena
como miembro de datos en la clase LibroCalificaciones.En la línea 13 se declara un objeto string que
representa el nombre de un curso. En la línea 15 se pasa el nombre del curso y el arreglo de calificacio-
nes al constructor de LibroCalificaciones. En la línea 16 se imprime un mensaje de bienvenida, y en la
línea 17 se invoca la función miembro procesarCalificaciones del objeto LibroCalificaciones.
1 // Fig. 7.17: fig07_17.cpp
2 // Crea un objeto LibroCalificaciones usando un arreglo de calificaciones.
3 #include array
4 #include LibroCalificaciones.h // definición de la clase LibroCalificaciones
5 using namespace std;
6
7 // la función main empieza la ejecución del programa
8 int main()
9 {
Fig. 7.17  Crea un objeto LibroCalificaciones usando un arreglo de calificaciones, y después invoca
a la función miembro procesarCalificaciones para analizarlas (parte 1 de 2).
302 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
10 // arreglo de calificaciones de estudiantes
11 const array int, LibroCalificaciones::estudiantes  calificaciones =
12 { 87, 68, 94, 100, 83, 78, 85, 91, 76, 87 };
13 string nombreCurso = CS101 Introduccion a la programacion en C++;
14
15 LibroCalificaciones miLibroCalificaciones( nombreCurso, calificaciones );
16 miLibroCalificaciones.mostrarMensaje();
17 miLibroCalificaciones.procesarCalificaciones();
18 } // fin de main
7.7Búsqueda y ordenamiento de datos en arreglos
En esta sección vamos a usar la función sort de la Biblioteca estándar de C++ para ordenar los elemen-
tos de un arreglo en forma ascendente, y la función binary_search integrada para determinar si un
valor se encuentra o no en el arreglo.
Ordenamiento
El ordenamiento de los datos (colocarlos en orden ascendente o descendente) es una de las aplicaciones
de cómputo más importantes. Un banco ordena todos los cheques por número de cuenta, para poder
preparar los estados bancarios individuales al final de cada mes. Las compañías telefónicas ordenan sus
directorios por apellido; y cuando las entradas contienen el mismo apellido, las ordenan por nombre de
pila para facilitar la búsqueda de números telefónicos. Casi todas las empresas deben ordenar ciertos
datos y, en algunos casos, cantidades masivas de ellos. El ordenamiento de datos es un problema intri-
gante que ha atraído algunos de los esfuerzos de investigación más intensos en el campo de las ciencias
computacionales. En el capítulo 20 (en inglés en el sitio web) vamos a investigar e implementar algunos
esquemas de ordenamiento, analizaremos su rendimiento y presentaremos la notación Big O para ca-
racterizar lo duro que debe trabajar cada esquema para realizar su tarea.
Búsqueda
A menudo puede ser necesario determinar si un arreglo contiene un valor que concuerde con cierto
valor clave. Al proceso de buscar un elemento específico de un arreglo se le llama búsqueda. En el ca-
pítulo 20 (en inglés en el sitio web) vamos a investigar e implementar dos algoritmos de búsqueda: la
búsqueda lineal, que es simple pero lenta y se usa para buscar en un arreglo desordenado, y la búsqueda
binaria, que es más compleja pero mucho más rápida y se usa para buscar en un arreglo ordenado.
Demostración de las funciones sort y binary_search
La figura 7.18 comienza por crear un arreglo desordenado de objetos string (líneas 13 y 14) y muestra
el contenido del arreglo (líneas 17 a 19). A continuación, en la línea 21 se usa la función sort de la
Biblioteca estándar de C++ para ordenar los elementos del arreglo colores en forma ascendente. Los
argumentosdelafunciónsort especificanelrangodeelementosquedebenordenarse;enestecaso,todo
el arreglo. En capítulos posteriores hablaremos sobre los detalles completos de las funciones begin y end
de la plantilla de clase array. Como veremos más adelante, la función sort puede usarse para ordenar
los elementos de varios tipos diferentes de estructuras de datos. Las líneas 24 a 26 muestran el contenido
del arreglo ordenado.
Las líneas 29 y 34 demuestran el uso de binary_search para determinar si un valor está en el arre-
glo. La secuencia de valores debe ordenarse primero en forma ascendente; binary_search no verifica
esto por nosotros. Los primeros dos argumentos de la función representan el rango de elementos a
Fig. 7.17  Crea un objeto LibroCalificaciones usando un arreglo de calificaciones, y después invoca
a la función miembro procesarCalificaciones para analizarlas (parte 2 de 2).
7.7 Búsqueda y ordenamiento de datos en arreglos 303
buscar y el tercero es la clave de búsqueda: el valor a localizar en el arreglo. La función devuelve un valor
bool para indicar si se encontró el valor o no. En el capítulo 16 usaremos la función find estándar de
C++ para obtener la ubicación de la clave de búsqueda en un arreglo.
1 // Fig. 7.18: fig07_18.cpp
2 // Búsqueda y ordenamiento de arreglos.
3 #include iostream
4 #include iomanip
5 #include array
6 #include string
7 #include algorithm // contiene sort y binary_search
8 using namespace std;
9
10 int main()
11 {
12 const size_t tamanioArreglo = 7; // tamaño del arreglo colores
13 array string, tamanioArreglo  colores = { rojo, naranja, amarillo,
14 verde, azul, indigo, violeta };
15
16 // imprime el arreglo original
17 cout  Arreglo desordenado:n;
18 for ( string color : colores )
19 cout  color  “ “;
20
21 sort( colores.begin(), colores.end() ); // ordena el contenido de colores
22
23 // imprime el arreglo ordenado
24 cout  nArreglo ordenado:n;
25 for ( string elemento : colores )
26 cout  elemento   ;
27
28 // busca indigo en colores
29 bool encontro = binary_search( colores.begin(), colores.end(), indigo );
30 cout  nnindigo   ( encontro ? se : no se )
31   encuentra en colores  endl;
32
33 // busca cian en colores
34 encontro = binary_search( colores.begin(), colores.end(), cyan );
35 cout  cian   ( encontro ? se : no se )
36   encuentra en colores  endl;
37 } // fin de main
Arreglo desordenado:
rojo naranja amarillo verde azul indigo violeta
Arreglo ordenado:
amarillo azul indigo naranja rojo verde violeta
indigo se encuentra en colores
cian no se encuentra en colores
Fig. 7.18  Búsqueda y ordenamiento de arreglos.
304 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
7.8Arreglos multidimensionales
Es posible usar arreglos de dos dimensiones (subíndices) para representar tablas de valores, las cuales
consisten en información ordenada en filas y columnas. Para identificar un elemento específico de una
tabla, debemos especificar dos subíndices. Por convención, el primero identifica la fila del elemento y el
segundo su columna. Los arreglos que requieren dos subíndices para identificar un elemento específico
se llaman arreglos bidimensionales o arreglos 2-D. Los arreglos con dos o más dimensiones se cono-
cen como arreglos multidimensionales y pueden tener más de dos dimensiones. La figura 7.19 ilustra
un arreglo bidimensional a, que contiene tres filas y cuatro columnas (es decir, un arreglo de tres por
cuatro). En general, a un arreglo con m filas y n columnas se le llama arreglo de m por n.
Fila 0
Fila 1
Fila 2
Subíndice de columna
Subíndice de fila
Nombre del arreglo
a[ 0 ][ 0 ]
a[ 1 ][ 0 ]
a[ 2 ][ 0 ]
a[ 0 ][ 1 ]
a[ 1 ][ 1 ]
a[ 2 ][ 1 ]
a[ 0 ][ 2 ]
a[ 1 ][ 2 ]
a[ 2 ][ 2 ]
a[ 0 ][ 3 ]
Columna 0 Columna 1 Columna 2 Columna 3
a[ 1 ][ 3 ]
a[ 2 ][ 3 ]
Fig. 7.19  Arreglo bidimensional con tres filas y cuatro columnas.
Cada elemento en el arreglo a se identifica en la figura 7.19 mediante el nombre de un elemento de la
forma a[i][j], donde a es el nombre del arreglo, i y j son los subíndices que identifican en forma única
a cada elemento en el arreglo a. Observe que los nombres de los elementos en la fila 0 tienen todos un
primer subíndice de 0, y los nombres de los elementos en la columna 3 tienen un segundo subíndice de 3.
Error común de programación 7.5
Es un error hacer referencia al elemento a[x][y] de un arreglo bidimensional en forma
incorrecta como a[x, y]. En realidad, a[x, y] se trata como a[y], ya que C++ evalúa
la expresión x, y (que contiene un operador coma) simplemente como y (la última de las
expresiones separadas por coma).
La figura 7.20 demuestra cómo inicializar arreglos bidimensionales en las declaraciones. Las líneas
13 y 14 declaran cada una un arreglo de arreglos, con dos filas y tres columnas. Observe la declaración
de tipo array anidada. En cada array se especifica el tipo de sus elementos como:
array int, columnas 
para indicar que cada array contiene como elementos arreglos de tres elementos de valores int; la
constante columnas tiene el valor de 3.
1 // Fig. 7.20: fig07_20.cpp
2 // Inicialización de arreglos multidimensionales.
3 #include iostream
Fig. 7.20  Inicialización de arreglos multidimensionales (parte 1 de 2).
7.8 Arreglos multidimensionales 305
4 #include array
5 using namespace std;
6
7 const size_t filas = 2;
8 const size_t columnas = 3;
9 void imprimirAreglo( const array array int, columnas , filas  );
10
11 int main()
12 {
13 array array int, columnas , filas  arreglo1 = { 1, 2, 3, 4, 5, 6 };
14 array array int, columnas , filas  arreglo2 = { 1, 2, 3, 4, 5 };
15
16 cout  Los valores en el arreglo1 por fila son:  endl;
17 imprimirArreglo( arreglo1 );
18
19 cout  nLos valores en el arreglo2 por fila son:  endl;
20 imprimirArreglo( arreglo2 );
21 } // fin de main
22
23 // imprime arreglo con dos filas y tres columnas
24 void imprimirArreglo( const array array int, columnas , filas  a )
25 {
26 // itera a través de las filas del arreglo
27 for ( auto const fila : a )
28 {
29 // itera por las columnas de la fila actual
30 for ( auto const elemento : fila )
31 cout  elemento  ' ';
32
33 cout  endl; // inicia nueva línea de salida
34 } // fin de for externo
35 } // fin de la función imprimirArreglo
Los valores en el arreglo1 por fila son:
1 2 3
4 5 6
Los valores en el arreglo2 por fila son:
1 2 3
4 5 0
La declaración de arreglo1 (línea 13) proporciona seis inicializadores. El compilador inicializa los
elementos de la fila 0 seguidos de los elementos de la fila 1. Así, los primeros tres valores inicializan
los elementos de la fila 0 con 1, 2 y 3, y los últimos tres inicializan los elementos de la fila 1 con 4, 5 y 6.
La declaración de arreglo2 (línea 14) proporciona sólo cinco inicializadores. Éstos se asignan a la fila 0,
después a la fila 1. Cualquier elemento que no tenga un inicializador explícito se inicializa con cero, por
lo que arreglo2[1][2] es 0.
El programa llama a la función imprimirArreglo para imprimir cada uno de los elementos del
arreglo. Observe que el prototipo de la función (línea 9) y la definición (líneas 24 a 35) especifican que
la función recibe un arreglo de dos filas y tres columnas. El parámetro recibe el arreglo por referencia y
se declara como const, ya que la función no modifica los elementos del arreglo.
Instrucciones for anidadas basadas en el rango
Para procesar los elementos de un arreglo bidimensional, usamos un ciclo anidado en donde el ciclo exter-
no itera a través de las filas y el ciclo interno itera a través de las columnas de una fila dada. El ciclo anidado
Fig. 7.20  Inicialización de arreglos multidimensionales (parte 2 de 2).
306 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
de la función imprimirArreglo se implementa con instrucciones for basadas en el rango. Las líneas 27 y
30 introducen la palabra clave auto de C++, la cual indica al compilador que infiera (determine) el tipo de
datos de una variable con base en el valor inicializador de ésta. La variable de rango fila del ciclo inferior
se inicializa con un elemento del parámetro a. Si analizamos la declaración del arreglo, podemos ver que
contiene elementos del tipo
array int, columnas 
de modo que el compilador infiera que fila se refiere a un arreglo de tres elementos de valores int (de
nuevo, columnas es 3). La parte const  en la declaración de fila indica que la referencia no puede
usarse para modificar las filas y evita que cada fila se copie a la variable de rango. La variable de rango
elemento del ciclo interno se inicializa con un elemento del arreglo representado por fila, por lo que
el compilador infiere que elemento hace referencia a un int debido a que cada fila contiene tres valores
int. En un IDE, por lo general pasamos el ratón sobre una variable declarada con auto y el IDE mues-
tra el tipo inferido por la variable. La línea 31 muestra el valor de una fila y columna dadas.
Instrucciones for anidadas, controladas por contador
Podríamos haber implementado el ciclo anidado con una repetición controlada por contador de la si-
guiente manera:
for ( size_t fila = 0; fila  a.size(); ++fila )
{
for ( size_t columna = 0; columna  a[ fila ].size(); ++columna )
cout  a[ fila ][ columna ]  ' ';
cout  endl;
} // fin de for externo
Otras manipulaciones comunes de arreglos
Muchas manipulaciones comunes en los arreglos utilizan instrucciones de repetición for. Por ejemplo,
la siguiente instrucción for establece todos los elementos en la fila 2 del arreglo a de la figura 7.19 con
cero:
for ( size_t columna = 0; columna  4; ++columna )
a[ 2 ][ columna ] = 0;
La instrucción for sólo varía el segundo subíndice (es decir, el subíndice de la columna). La instrucción
for anterior es equivalente a las siguientes instrucciones de asignación:
a[ 2 ][ 0 ] = 0;
a[ 2 ][ 1 ] = 0;
a[ 2 ][ 2 ] = 0;
a[ 2 ][ 3 ] = 0;
La siguiente instrucción for controlada por contador anidada determina el total de todos los elementos
en el arreglo a de la figura 7.19:
total = 0;
for ( size_t fila = 0; fila  a.size(); ++fila )
for ( size_t columna = 0; columna  a[ fila ].size(); ++columna )
total += a[ fila ][ columna ];
7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional 307
La instrucción for calcula el total de los elementos del arreglo, una fila a la vez. La instrucción for exte-
rior empieza estableciendo fila (es decir, el subíndice de la fila) en 0, de manera que la instrucción for
interior pueda calcular el total de los elementos de la fila 0. Después, la instrucción for exterior incre-
menta fila a 1, de manera que se pueda obtener el total de los elementos de la fila 1. Luego, la instruc-
ción for exterior incrementa fila a 2, de manera que pueda obtenerse el total de los elementos de la
fila 2. Cuando termina la instrucción for anidada, total contiene la suma de todos los elementos del
arreglo. Este ciclo anidado puede implementarse con instrucciones for basadas en el rango, como:
total = 0;
for ( auto fila : a ) // por cada fila
for ( auto columna : fila ) // por cada columna en la fila
total += columna;
7.9Caso de estudio: la clase LibroCalificaciones
que usa un arreglo bidimensional
En la sección 7.6 presentamos la clase LibroCalificaciones (figuras 7.15 y 7.16), la cual utilizó un
arreglo unidimensional para almacenar las calificaciones de los estudiantes de un solo examen. En la
mayoría de los semestres, los estudiantes presentan varios exámenes. Es probable que los profesores
quieran analizar las calificaciones a lo largo de todo el semestre, tanto para un solo estudiante como para
la clase en general.
Cómo almacenar las calificaciones de los estudiantes en un arreglo bidimensional
en la clase LibroCalificaciones
La figura 7.21 muestra el resultado en donde se sintetizan las calificaciones de 10 estudiantes de tres
exámenes. Almacenamos las calificaciones como un arreglo bidimensional en un objeto de la siguiente
versión de la clase LibroCalificaciones de las figuras 7.22 y 7.23. Cada fila del arreglo representa las
calificaciones de un solo estudiante durante todo el curso, y cada columna representa todas las califica-
ciones que obtuvieron los estudiantes para un examen específico. Un programa cliente, como el de la
figura 7.24, pasa el arreglo como argumento para el constructor de LibroCalificaciones. Como hay
10 estudiantes y tres exámenes, usamos un arreglo de tres por diez para almacenar las calificaciones.
Bienvenido al libro de calificaciones para
CS101 Introduccion a la programacion en C++!
Las calificaciones son:
Prueba 1 Prueba 2 Prueba 3 Promedio
Estudiante 1 87 96 70 84.33
Estudiante 2 68 87 90 81.67
Estudiante 3 94 100 90 94.67
Estudiante 4 100 81 82 87.67
Estudiante 5 83 65 85 77.67
Estudiante 6 78 87 65 76.67
Estudiante 7 85 75 83 81.00
Estudiante 8 91 94 100 95.00
Estudiante 9 76 72 84 77.33
Estudiante 10 87 93 73 84.33
Fig. 7.21  Salida de LibroCalificaciones que usa dos arreglos bidimensionales (parte 1 de 2).
308 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
La calificacion mas baja en el libro de calificaciones es 65
La calificacion mas alta en el libro de calificaciones es 100
Distribucion general de calificaciones:
0-9:
10-19:
20-29:
30-39:
40-49:
50-59:
60-69: ***
70-79: ******
80-89: ***********
90-99: *******
100: ***
1 // Fig. 7.22: LibroCalificaciones.h
2 // Definición de la clase LibroCalificaciones que utiliza un
3 // arreglo bidimensional para almacenar calificaciones de una prueba.
4 // Las funciones miembro se definen en LibroCalificaciones.cpp
5 #include array
6 #include string
7
8 // definición de la clase LibroCalificaciones
9 class LibroCalificaciones
10 {
11 public:
12 // constantes
13 static const size_t estudiantes = 10; // número de estudiantes
14 static const size_t pruebas = 3; // número de pruebas
15
16 // el constructor inicializa el nombre del curso y el arreglo de calificaciones
17 LibroCalificaciones( const std::string ,
18 std::array std::array int, pruebas, estudiantes   );
19
20 void establecerNombreCurso( const std::string  ); // establece el nombre del
curso
21 std::string obtenerNombreCurso() const; // obtiene el nombre del curso
22 void mostrarMensaje() const; // muestra un mensaje de bienvenida
23 void procesarCalificaciones() const; // realiza varias operaciones en los
datos de las calificaciones
24 int obtenerMinimo() const; // encuentra el valor mínimo en el libro de
calificaciones
25 int obtenerMaximo() const; // encuentra el valor máximo en el libro de
calificaciones
26 double obtenerPromedio( const std::array int, pruebas   ) const;
27 void imprimirGraficoBarras() const; // imprime gráfico de barras de la
distribución de calificaciones
28 void imprimirCalificaciones() const; // imprime el contenido del arreglo
calificaciones
29 private:
30 std::string nombreCurso; // nombre del curso para este libro de calificaciones
31
std::array std::array int, pruebas , estudiantes  calificaciones;
// arreglo bidimensional de calificaciones
32 }; // fin de la clase LibroCalificaciones
Fig.7.22  Definición de la clase LibroCalificaciones que utiliza un arreglo bidimensional para
almacenar calificaciones de una prueba.
Fig. 7.21  Salida de LibroCalificaciones que usa dos arreglos bidimensionales (parte 2 de 2).
31
7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional 309
1 // Fig. 7.23: LibroCalificaciones.cpp
2 // Definiciones de las funciones miembro de LibroCalificaciones,
3 // que utiliza un arreglo bidimensional para almacenar calificaciones.
4 #include iostream
5 #include iomanip // manipuladores de flujo parametrizados
6 using namespace std;
7
8 // incluye la definición de la clase LibroCalificaciones de LibroCalificaciones.h
9 #include LibroCalificaciones.h // definición de la clase LibroCalificaciones
10
11 // constructor con dos argumentos que inicializa nombreCurso y el arreglo
calificaciones
12 LibroCalificaciones::LibroCalificaciones( const string nombre,
13 std::array std::array int, pruebas , estudiantes  arregloCalificaciones )
14 : nombreCurso( nombre ), calificaciones( arregloCalificaciones )
15 {
16 } // fin del constructor de LibroCalificaciones con dos argumentos
17
18 // función para establecer el nombre del curso
19 void LibroCalificaciones::establecerNombreCurso( const string nombre )
20 {
21 nombreCurso = nombre; // almacena el nombre del curso
22 } // fin de la función establecerNombreCurso
23
24 // función para obtener el nombre del curso
25 string LibroCalificaciones::obtenerNombreCurso() const
26 {
27 return nombreCurso;
28 } // fin de la función obtenerNombreCurso
29
30 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones
31 void LibroCalificaciones::mostrarMensaje() const
32 {
33 // esta instrucción llama a obtenerNombreCurso para obtener el
34 // nombre del curso que representa este LibroCalificaciones
35 cout  Bienvenido al libro de calificaciones paran
 obtenerNombreCurso()  !
36  endl;
37 } // fin de la función mostrarMensaje
38
39 // realiza varias operaciones con los datos
40 void LibroCalificaciones::procesarCalificaciones() const
41 {
42 // imprime el arreglo calificaciones
43 imprimirCalificaciones();
44
45 // llama a las funciones obtenerMinimo y obtenerMaximo
46 cout  nLa calificacion mas baja en el libro de calificaciones es 
 obtenerMinimo()
47  nLa calificacion mas alta en el libro de calificaciones es 
 obtenerMaximo()  endl;
48
49 // imprime gráfico de distribución de todas las calificaciones en todas las
pruebas
50 imprimirGraficoBarras();
51 } // fin de la función procesarCalificaciones
Fig. 7.23  Definiciones de las funciones miembro de LibroCalificaciones, que utiliza un arreglo
bidimensional para almacenar calificaciones (parte 1 de 4).
310 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
52
53 // encuentra la calificación más baja en todo el libro de calificaciones
54 int LibroCalificaciones::obtenerMinimo() const
55 {
56 int calificacionInf = 100; // asume que la calificación más baja es 100
57
58 // itera a través de las filas del arreglo calificaciones
59 for ( auto const estudiante : calificaciones )
60 {
61 // itera a través de las columnas de la fila actual
62 for ( auto const calificacion : estudiante )
63 {
64 // si la calificación actual es menor que calificacionInf, la asigna a
calificacionInf
65 if ( calificacion  calificacionInf )
66 calificacionInf = calificacion; // nueva calificación más baja
67 } // fin de for interior
68 } // fin de for exterior
69
70 return calificacionInf; // devuelve la calificación más baja
71 } // fin de la función obtenerMinimo
72
73 // busca la calificación más alta en todo el libro de calificaciones
74 int LibroCalificaciones::obtenerMaximo() const
75 {
76 int calificacionSup = 0; // asume que la calificación más alta es 0
77
78 // itera a través de las filas del arreglo calificaciones
79 for ( auto const estudiante : calificaciones )
80 {
81 // itera a través de las columnas de la fila actual
82 for ( auto const calificacion : estudiante )
83 {
84 // si la calificación actual es mayor que calificacionSup, la asigna a
calificacionSup
85 if ( calificacion  calificacionSup )
86 calificacionSup = calificacion; // nueva calificación más alta
87 } // fin de for interior
88 } // fin de for exterior
89
90 return calificacionSup; // devuelve la calificación más alta
91 } // fin de la función obtenerMaximo
92
93 // determina la calificación promedio para un conjunto específico de
calificaciones
94 double LibroCalificaciones::obtenerPromedio( const arrayint, pruebas
conjuntoDeCalificaciones ) const
95 {
96 int total = 0; // inicializa el total
97
98 // suma las calificaciones en el arreglo
99 for ( int calificacion : conjuntoDeCalificaciones )
100 total += calificacion;
101
Fig. 7.23  Definiciones de las funciones miembro de LibroCalificaciones, que utiliza un arreglo
bidimensional para almacenar calificaciones (parte 2 de 4).
7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional 311
102 // devuelve el promedio de las calificaciones
103 return static_cast double ( total ) / conjuntoDeCalificaciones.size();
104 } // fin de la función obtenerPromedio
105
106 // imprime gráfico de barras que muestra la distribución de las calificaciones
107 void LibroCalificaciones::imprimirGraficoBarras() const
108 {
109 cout  nDistribucion general de calificaciones:  endl;
110
111 // almacena la frecuencia de las calificaciones en cada rango
de 10 calificaciones
112 const size_t tamanioFrecuencia = 11;
113 array unsigned int, tamanioFrecuencia  frecuencia = {}; // inicializa con ceros
114
115 // para cada calificación, incrementa la frecuencia apropiada
116 for ( auto const estudiante : calificaciones )
117 for ( auto const prueba : estudiante )
118 ++frecuencia[ prueba / 10 ];
119
120 // para cada frecuencia de calificaciones, imprime la barra en el gráfico
121 for ( size_t cuenta = 0; cuenta  tamanioFrecuencia; ++cuenta )
122 {
123 // imprime las etiquetas de las barras (0-9:, ..., 90-99:, 100: )
124 if ( 0 == cuenta )
125 cout   0-9: ;
126 else if ( 10 == cuenta )
127 cout   100: ;
128 else
129 cout  cuenta * 10  -  ( cuenta * 10 ) + 9  : ;
130
131 // imprime barra de asteriscos
132 for ( unsigned int estrellas = 0; estrellas  frecuencia[ cuenta ];
estrellas++ )
133 cout  '*';
134
135 cout  endl; // empieza una nueva línea de salida
136 } // fin de for exterior
137 } // fin de la función imprimirGraficoBarras
138
139 // imprime el contenido del arreglo calificaciones
140 void LibroCalificaciones::imprimirCalificaciones() const
141 {
142 cout  nLas calificaciones son:nn;
143 cout   ; // alinea los encabezados de las columnas
144
145 // crea un encabezado de columna para cada una de las pruebas
146 for ( size_t prueba = 0; prueba  pruebas; ++prueba )
147 cout  Prueba   prueba + 1   ;
148
149 cout  Promedio  endl; // encabezado de la columna de promedio de
estudiantes
150
Fig. 7.23  Definiciones de las funciones miembro de LibroCalificaciones, que utiliza un arreglo
bidimensional para almacenar calificaciones (parte 3 de 4).
312 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
151 // crea filas/columnas de texto que representan el arreglo calificaciones
152 for ( size_t estudiante = 0; estudiante  calificaciones.size(); ++estudiante )
153 {
154 cout  Estudiante   setw( 2 )  estudiante + 1;
155
156 // imprime las calificaciones del estudiante
157 for ( size_t prueba = 0; prueba  calificaciones[ estudiante ].size();
++prueba )
158 cout  setw( 8 )  calificaciones[ estudiante ][ prueba ];
159
160 // llama a la función miembro obtenerPromedio para calcular el promedio del
161 // estudiante; pasa la fila de calificaciones como argumento
162 double promedio = obtenerPromedio( calificaciones[ estudiante ] );
163 cout  setw( 9 )  setprecision( 2 )  fixed  promedio  endl;
164 } // fin de for exterior
165 } // fin de la función imprimirCalificaciones
Generalidades de las funciones de la clase LibroCalificaciones
Cinco funciones miembro (declaradas en las líneas 24 a 28 de la figura 7.22) realizan manipulaciones de
arreglos para procesar las calificaciones. Cada una de estas funciones miembro es similar a su contraparte
en la versión anterior de la clase LibroCalificaciones con un arreglo unidimensional (figuras 7.15 y
7.16). La función miembro obtenerMinimo (definida en las líneas 54 a 71 de la figura 7.23) determina la
calificación más baja de cualquier estudiante durante el semestre. La función miembro obtenerMaximo
(definida en las líneas 74 a 91 de la figura 7.23) determina la calificación más alta de cualquier estudiante
durante el semestre. La función miembro obtenerPromedio (líneas 94 a 104 de la figura 7.23) determina
elpromedio semestralde un estudiante específico.La funciónmiembro imprimirGraficoBarras (líneas
107 a 137 de la figura 7.23) imprime un gráfico de barras de la distribución de todas las calificaciones
de los estudiantes durante el semestre. La función miembro imprimirCalificaciones (líneas 140 a
165 de la figura 7.23) imprime el arreglo bidimensional en formato tabular, junto con el promedio se-
mestral de cada estudiante.
Las funciones obtenerMinimo y obtenerMaximo
Cada una de las funciones miembro obtenerMinimo, obtenerMaximo, imprimirGraficoBarras e
imprimirCalificaciones iteranatravésdelarreglo calificaciones medianteelusodeinstrucciones
for anidadas basadas en rango, o de instrucciones for controladas por contador. Por ejemplo, consi-
dere la instrucción for anidada en la función miembro obtenerMinimo (líneas 59 a 68). La instrucción
for exterior itera por las filas que representan a cada estudiante, y la instrucción for interior itera a
través de las calificaciones de un estudiante específico. Cada calificación se compara con la variable
calificacionInf en el cuerpo de la instrucción for interior. Si una calificación es menor que cali-
ficacionInf, a calificacionInf se le asigna esa calificación. Esto se repite hasta que se hayan reco-
rrido todas las filas y columnas de calificaciones. Cuando se completa la ejecución de la instrucción
anidada, calificacionInf contiene la calificación más baja de todo el arreglo bidimensional. La
función miembro obtenerMaximo funciona de manera similar a la función miembro obtenerMinimo.
La función miembro imprimirGraficoBarras
La función miembro imprimirGraficoBarras en la figura 7.23 es casi idéntica a la de la figura 7.16. Sin
embargo,paraimprimirladistribucióndecalificacionesengeneraldurantetodounsemestre,lafunción
utiliza una instrucción for anidada (líneas 116 a 118) para incrementar los elementos del arreglo uni-
Fig. 7.23  Definiciones de las funciones miembro de LibroCalificaciones, que utiliza un arreglo
bidimensional para almacenar calificaciones (parte 4 de 4).
7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional 313
dimensional frecuencia, con base en todas las calificaciones en el arreglo bidimensional. El resto del
código en cada una de las dos funciones miembro imprimirGraficoBarras que muestran el gráfico es
idéntico.
La función imprimirCalificaciones
La función miembro imprimirCalificaciones (líneas 140 a 165) utiliza instrucciones for anidadas,
controladas por contador, para imprimir valores del arreglo calificaciones, además del promedio
semestral de cada estudiante. La salida en la figura 7.21 muestra el resultado, el cual se asemeja al forma-
to tabular del libro de calificaciones real de un profesor. Las líneas 146 y 147 imprimen los encabezados
de columna para cada prueba. Aquí utilizamos una instrucción for controlada por contador, para poder
identificar cada prueba con un número. De manera similar, la instrucción for en las líneas 152 a 164
imprime primero una etiqueta de fila mediante el uso de una variable contador para identificar a cada
estudiante (línea 154). Aunque los subíndices de los arreglos empiezan en 0, en las líneas 147 y 154 se
imprimen prueba + 1 y estudiante + 1 en forma respectiva, para producir números de prueba y estu-
diante que empiecen en 1 (vea la figura 7.21) La instrucción for interior en las líneas 157 y 158 utiliza la
variable contador estudiante de la instrucción for exterior para iterar a través de una fila específica del
arreglo calificaciones, e imprime la calificación de la prueba de cada estudiante. Por último, en
la línea 162 se obtiene el promedio semestral de cada estudiante, para lo cual se pasa la fila actual de
calificaciones (es decir, calificaciones[estudiante]) a la función miembro obtenerPromedio.
La función obtenerPromedio
La función miembro obtenerPromedio (líneas 94 a 104) toma como argumento un arreglo unidimensio-
nalderesultadosdelapruebaparaunestudianteespecífico.Cuandolalínea162llamaaobtenerPromedio,
el primer argumento es calificaciones[ estudiante ], el cual especifica que debe pasarse una fila espe-
cífica del arreglo bidimensional calificaciones a obtenerPromedio. Por ejemplo, con base en el arreglo
creado en la figura 7.24, el argumento calificaciones[1] representa los tres valores (un arreglo unidi-
mensional de calificaciones) almacenados en la fila 1 del arreglo bidimensional calificaciones. Los
elementos de un arreglo bidimensional son arreglos unidimensionales. La función miembro obtener-
Promedio calcula la suma de los elementos del arreglo, divide el total entre el número de resultados de la
prueba y devuelve el resultado de punto flotante como un valor double (línea 103).
Prueba de la clase LibroCalificaciones
El programa de la figura 7.24 crea un objeto de la clase LibroCalificaciones (figuras 7.22 y 7.23)
mediante el uso del arreglo bidimensional de valores int llamado arregloCalif (el cual se declara y se
inicializa en las líneas 11 a 21). En la línea 11 se accede a las constantes static llamadas estudiantes
ypruebas delaclase LibroCalificaciones paraindicareltamañodecadadimensióndelarreglo arre-
gloCalificaciones. En las líneas 23 y 24 se pasan el nombre de un curso y calificaciones al cons-
tructor de LibroCalificaciones. Después, en las líneas 25 y 26 se invocan las funciones miembro
mostrarMensaje y procesarCalificaciones de miLibroCalificaciones, para mostrar un mensaje
de bienvenida y obtener un informe que sintetice las calificaciones de los estudiantes para el semestre,
respectivamente.
1 // Fig. 7.24: fig07_24.cpp
2 // Crea un objeto LibroCalificaciones mediante el uso de un arreglo bidimensional
de calificaciones.
3 #include array
4 #include LibroCalificaciones.h // definición de la clase LibroCalificaciones
5 using namespace std;
6
Fig. 7.24  Crea un objeto LibroCalificaciones mediante el uso de un arreglo bidimensional de calificaciones,
y después invoca a la función miembro procesarCalificaciones para analizarlas (parte 1 de 2).
314 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
7 // la función main empieza la ejecución del programa
8 int main()
9 {
10 // arreglo bidimensional de calificaciones de estudiantes
11 array array int, LibroCalificaciones::pruebas ,
LibroCalificaciones::estudiantes  calificaciones =
12 { 87, 96, 70,
13 68, 87, 90,
14 94, 100, 90,
15 100, 81, 82,
16 83, 65, 85,
17 78, 87, 65,
18 85, 75, 83,
19 91, 94, 100,
20 76, 72, 84,
21 87, 93, 73 };
22
23 LibroCalificaciones miLibroCalificaciones(
24 CS101 Introduccion a la programacion en C++, calificaciones );
25 miLibroCalificaciones.mostrarMensaje();
26 miLibroCalificaciones.procesarCalificaciones();
27 } // fin de main
7.10Introducción a la plantilla de clase vector de la Biblioteca
estándar de C++
Ahora introduciremos la plantilla de clase vector de la Biblioteca estándar de C++, que es similar a la
plantilla de clase array, pero también soporta el ajuste de tamaño dinámico. Con la excepción de las
características que modifican a un vector, las demás características que se muestran en la figura 7.25
también funcionan en los arreglos. La plantilla de clase estándar vector está definida en el encabezado
vector (línea 5) y pertenece al espacio de nombres std. En el capítulo 15 hablaremos sobre la funcio-
nalidad completa de vector. Al final de esta sección demostraremos las herramientas de comprobación
de límites de la clase vector e introduciremos el mecanismo de manejo de excepciones de C++, que
puede usarse para detectar y manejar el índice fuera de límites de un objeto vector.
1 // Fig. 7.25: fig07_25.cpp
2 // Demostración de la plantilla de clase vector de la Biblioteca estándar de C++.
3 #include iostream
4 #include iomanip
5 #include vector
6 #include stdexcept
7 using namespace std;
8
9 void imprimirVector( const vector int   ); // muestra el vector
10 void recibirVector( vector int   ); // introduce los valores en el vector
11
12 int main()
13 {
Fig. 7.24  Crea un objeto LibroCalificaciones mediante el uso de un arreglo bidimensional de calificaciones,
y después invoca a la función miembro procesarCalificaciones para analizarlas (parte 2 de 2).
Fig. 7.25  Demostración de la plantilla de clase vector de la Biblioteca estándar de C++ (parte 1 de 4).
7.10 Introducción a la plantilla de clase vector de la Biblioteca estándar de C++ 315
14 vector int  enteros1( 7 ); // vector de 7 elementos int 
15 vector int  enteros2( 10 ); // vector de 10 elementos int 
16
17 // imprime el tamaño y el contenido de enteros1
18 cout  El tamanio del vector enteros1 es   enteros1.size()
19  nvector despues de la inicializacion:  endl;
20 imprimirVector( enteros1 );
21
22 // imprime el tamaño y el contenido de enteros2
23 cout  nEl tamanio del vector enteros2 es   enteros2.size()
24  nvector despues de la inicializacion:  endl;
25 imprimirVector( enteros2 );
26
27 // recibe e imprime enteros1 y enteros2
28 cout  nEscriba 17 enteros:  endl;
29 recibirVector( enteros1 );
30 recibirVector( enteros2 );
31
32 cout  nDespues de la entrada, los vectores contienen:n
33  enteros1:  endl;
34 imprimirVector( enteros1 );
35 cout  enteros2:  endl;
36 imprimirVector( enteros2 );
37
38 // usa el operador de desigualdad (!=) con objetos vector
39 cout  nEvaluacion: enteros1 != enteros2  endl;
40
41 if ( enteros1 != enteros2 )
42 cout  enteros1 y enteros2 no son iguales  endl;
43
44 // crea el vector enteros3 usando enteros1 como un
45 // inicializador; imprime el tamaño y el contenido
46 vector int  enteros3( enteros1 ); // constructor de copia
47
48 cout  nEl tamanio del vector enteros3 es   enteros3.size()
49  nvector despues de la inicializacion:  endl;
50 imprimirVector( enteros3 );
51
52 // usa el operador de asignacion (=) sobrecargado
53 cout  nAsignacion de enteros2 a enteros1:  endl;
54 enteros1 = enteros2; // asigna enteros2 a enteros1
55
56 cout  enteros1:  endl;
57 imprimirVector( enteros1 );
58 cout  enteros2:  endl;
59 imprimirVector( enteros2 );
60
61 // usa el operador de igualdad (==) con objetos vector
62 cout  nEvaluacion: enteros1 == enteros2  endl;
63
64 if ( enteros1 == enteros2 )
65 cout  enteros1 y enteros2 son iguales  endl;
66
Fig. 7.25  Demostración de la plantilla de clase vector de la Biblioteca estándar de C++ (parte 2 de 4).
316 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
67 // usa corchetes para asignar el valor en la ubicación 5 como un rvalue
68 cout  nenteros1[5] es   enteros1[ 5 ];
69
70 // usa corchetes para crear lvalue
71 cout  nnAsignacion de 1000 a enteros1[5]  endl;
72 enteros1[ 5 ] = 1000;
73 cout  enteros1:  endl;
74 imprimirVector( enteros1 );
75
76 // intenta usar subíndice fuera de rango
77 try
78 {
79 cout  nIntento de mostrar enteros1.at( 15 )  endl;
80 cout  enteros1.at( 15 )  endl; // ERROR: fuera de rango
81 } // fin de try
82 catch ( out_of_range ex )
83 {
84 cerr  Ocurrio una excepcion:   ex.what()  endl;
85 } // fin de catch
86
87 // cambiar el tamaño de un vector
88 cout  nEl tamanio actual de enteros3 es:   enteros3.size()  endl;
89 enteros3.push_back( 1000 ); // agrega 1000 al final del vector
90 cout  El tamanio nuevo de enteros3 es:   enteros3.size()  endl;
91 cout  Ahora enteros3 contiene: ;
92 imprimirVector( enteros3 );
93 } // fin de main
94
95 // imprime el contenido del vector
96 void imprimirVector( const vector int  arreglo )
97 {
98 for ( int elemento : elementos )
99 cout  elemento   ;
100
101 cout  endl;
102 } // fin de la función imprimirVector
103
104 // recibe el contenido del vector
105 void recibirVector( vector int  arreglo )
106 {
107 for ( int elemento : elementos )
108 cin  elemento;
109 } // fin de la función recibirVector
El tamanio del vector enteros1 es 7
vector despues de la inicializacion:
0 0 0 0 0 0 0
El tamanio del vector enteros2 es 10
vector despues de la inicializacion:
0 0 0 0 0 0 0 0 0 0
Fig. 7.25  Demostración de la plantilla de clase vector de la Biblioteca estándar de C++ (parte 3 de 4).
7.10 Introducción a la plantilla de clase vector de la Biblioteca estándar de C++ 317
Escriba 17 enteros:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Despues de la entrada, los vectores contienen:
enteros1:
1 2 3 4 5 6 7
enteros2:
8 9 10 11 12 13 14 15 16 17
Evaluacion: enteros1 != enteros2
enteros1 y enteros2 no son iguales
El tamanio del vector enteros3 es 7
vector despues de la inicializacion:
1 2 3 4 5 6 7
Asignacion de enteros2 a enteros1:
enteros1:
8 9 10 11 12 13 14 15 16 17
enteros2:
8 9 10 11 12 13 14 15 16 17
Evaluacion: enteros1 == enteros2
enteros1 y enteros2 son iguales
enteros1[5] es 13
Asignacion de 1000 a enteros1[5]
enteros1:
8 9 10 11 12 1000 14 15 16 17
Intento de mostrar enteros1.at( 15 )
An exception ocurred: invalid vectorT subscript
El tamanio actual de enteros3 es: 7
El tamanio nuevo de enteros3 es: 8
Ahora enteros3 contiene: 1 2 3 4 5 6 7 1000
Creación de objetos vector
En las líneas 14 y 15 se crean dos objetos vector que almacenan valores de tipo int: enteros1 contie-
nesieteelementos,yenteros2 contiene10elementos.Demanerapredeterminada,todosloselementos
de cada objeto vector se establecen en 0. Al igual que los arreglos, pueden definirse objetos vector para
almacenar la mayoría de los tipos de datos, al sustituir int en vector int  con el tipo de datos apro-
piado.
Función miembro size de vector; función imprimirVector
En la línea 18 se utiliza la función miembro size de vector para obtener el tamaño (es decir, el núme-
ro de elementos) de enteros1. En la línea 20 se pasa enteros1 a la función imprimirVector (líneas 96
a 102), la cual usa una instrucción for basada en rango para obtener el valor en cada elemento del
vector para imprimirlo. Al igual que con la plantilla de clase array, es posible hacer esto mediante un
ciclo controlado por contador y el operador de subíndice ([]).En las líneas 23 y 25 se realizan las mismas
tareas para enteros2.
Fig. 7.25  Demostración de la plantilla de clase vector de la Biblioteca estándar de C++ (parte 4 de 4).
318 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
Función recibirVector
En las líneas 29 y 30 se pasan los objetos enteros1 y enteros2 a la función recibirVector (líneas 105
a 109) para que el usuario introduzca los valores de los elementos de cada vector. La función utiliza una
instrucción for basada en rango, con una variable de rango que es una referencia a un int para formar
lvalues que se utilizan para almacenar los valores de entrada en cada elemento vector.
Comparación de desigualdad de objetos vector
En la línea 41 se demuestra que los objetos vector se pueden comparar entre sí mediante el operador
!=. Si el contenido de dos objetos vector no es igual, el operador devuelve true; en caso contrario
devuelve false.
Inicialización de un vector con el contenido de otro
La plantilla de clase vector de la Biblioteca estándar de C++ nos permite crear un nuevo objeto vector
que se inicializa con el contenido de un vector existente. En la línea 46 se crea un objeto vector llama-
do enteros3 y se inicializa con una copia de enteros1. Esto invoca al constructor de copia de vector para
realizar la operación de copia. En el capítulo 10 aprenderá con detalle acerca de los constructores de
copia. En las líneas 48 a 50 se imprime el tamaño y el contenido de enteros3 para demostrar que se
inicializó en forma correcta.
Asignar objetos vector y comparar si dos objetos vector son iguales
En la línea 54 se asigna enteros2 a enteros1, lo cual demuestra que el operador de asignación (=)
se puede usar con objetos vector. En las líneas 56 a 59 se imprime el contenido de ambos objetos
para mostrar que ahora contienen valores idénticos. Después, en la línea 64 se compara enteros1 con
enteros2 mediante el operador de igualdad (==) para determinar si el contenido de los dos objetos es el
mismo después de la asignación en la línea 54 (lo cual es cierto).
Uso del operador [] para acceder a los elementos del vector y modificarlos
En las líneas 68 y 70 se utilizan corchetes ([]) para obtener un elemento de vector y usarlo como rva-
lue y lvalue, respectivamente. En la sección 5.9 vimos que un rvalue no se puede modificar, pero un
lvalue sí. Como es el caso con los arreglos, C++ no realiza comprobación de límites cuando se accede a
elementos de un objeto vector mediante el uso de corchetes.1
Por lo tanto, el programador debe asegurar
que las operaciones en las que se utilizan los corchetes ([]) no traten accidentalmente de manipular
elementos fuera de los límites del vector. Sin embargo, la plantilla de clase estándar vector cuenta con
lacapacidaddecomprobarloslímitesensufunciónmiembroat (aligualquelaplantilladeclasearray),
que usamos en la línea 80 y describiremos en breve.
Manejo de excepciones: procesamiento de un subíndice fuera de rango
Unaexcepciónindicaunproblemaqueocurremientrasseejecutaunprograma.Elnombre“excepción”
sugiere que el problema ocurre con poca frecuencia; si la “regla” es que una instrucción se ejecuta nor-
malmente en forma correcta, entonces el problema representa la “excepción a la regla”. El manejo de
excepciones nos permite crear programas tolerantes a errores que puedan resolver (o manejar) excep-
ciones. En muchos casos, esto permite a un programa seguirse ejecutando como si no hubiera encontra-
do problemas. Por ejemplo, la figura 7.25 se ejecuta hasta completarse, aun cuando se trató de acceder
a un subíndice fuera de rango. Los problemas más severos podrían evitar que un programa continúe su
ejecución normal y tenga que notificar al usuario sobre el problema, para después terminar. Cuando una
1 Algunos compiladores tienen opciones para comprobación de límites y ayudar a evitar desbordamientos de búfer.
7.10 Introducción a la plantilla de clase vector de la Biblioteca estándar de C++ 319
función detecta un problema, como el subíndice inválido de un arreglo o un argumento inválido, lanza
una excepción; es decir, ocurre una excepción. Aquí presentamos brevemente el manejo de excepciones.
Hablaremos con detalle en el capítulo 17 (en el sitio web), Manejo de excepciones: un análisis más de-
tallado.
La instrucción try
Para manejar una excepción, coloque el código que pudiera lanzar una excepción en una instrucción
try (líneas 77 a 85). El bloque try (líneas 77 a 81) contiene el código que podría lanzar una excepción,
y el bloque catch (líneas 82 a 85) contiene el código que maneja la excepción, en caso de que ocurra.
Como veremos en el capítulo 17, puede tener varios bloques catch para manejar diferentes tipos de
excepciones que podrían lanzarse en el bloque try correspondiente. Si el código en el bloque try se
ejecuta con éxito, se ignoran las líneas 82 a 85. Las llaves que delimitan los cuerpos de los bloques try y
catch son obligatorias.
La función miembro at de vector cuenta con comprobación de límites y lanza una excepción si su
argumento es un subíndice inválido. De manera predeterminada, esto hace que un programa de C++
termine. Si el subíndice es válido, la función at devuelve el elemento en la ubicación especificada como
un lvalue modificable o un lvalue no modificable. Un lvalue no modificable es una expresión que iden-
tifica a un objeto en memoria (como un elemento en un vector), pero no puede usarse para modificar
ese objeto. Si se hace una llamada a at en un arreglo const o mediante una referencia declarada como
const, la función devuelve un lvalue no modificable.
Ejecución del bloque catch
Cuando el programa llama a la función miembro at de vector con el argumento 15 (línea 80), la fun-
ción intenta acceder al elemento en la ubicación 15, que se encuentra fuera de los límites del vector;
enteros1 tiene sólo 10 elementos en este punto. Puesto que la comprobación de límites se realiza en
tiempo de ejecución, la función miembro at de vector genera una excepción; específicamente, la línea
80 lanza una excepción out_of_range (del encabezado stdexcept) para notificar al programa sobre
este problema. En este punto, el bloque try termina de inmediato y el bloque catch comienza a ejecu-
tarse; si declaró variables en el bloque try, ahora están fuera de alcance y no pueden accederse en el
bloque catch.
Elbloquecatch declarauntipo(out_of_range)yunparámetrodeexcepción(ex)querecibecomo
referencia. El bloque catch puede manejar excepciones del tipo especificado. Dentro del bloque puede
usar el identificador del parámetro para interactuar con un objeto de excepción capturado.
Función miembro what del parámetro de excepción
Cuando las líneas 82 a 85 atrapan la excepción, el programa muestra un mensaje para indicar el proble-
ma que ocurrió. La línea 84 llama a la función miembro what del objeto excepción para obtener el
mensaje de error que está almacenado en este objeto y mostrarlo. Una vez que se muestra el mensaje en
este ejemplo, se considera que se manejó la excepción y el programa continúa con la siguiente instruc-
ción después de la llave de cierre del bloque catch. En este ejemplo, las líneas 88 a 92 se ejecutan a
continuación. Usaremos el manejo de excepciones de nuevo en los capítulos 9 a 12; el capítulo 17 pre-
senta un análisis más detallado sobre el manejo de excepciones.
Cambiar el tamaño de un vector
Una de las diferencias clave entre un vector y un array es que un vector puede crecer en forma diná-
mica para dar cabida a más elementos. Para demostrar esto, en la línea 88 se muestra el tamaño actual
de enteros3, en la línea 89 se llama a la función miembro push_back de vector para agregar un nuevo
elementoquecontiene1000alfinaldelvector yenlalínea90semuestraelnuevotamañodeenteros3.
Después en la línea 92 se muestra el nuevo contenido de enteros3.
320 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
C++11: Lista para inicializar un vector
Muchos de los ejemplos con array en este capítulo utilizan incializadores de listas para especificar los
valores iniciales de los elementos de un arreglo. C++11 también permite esto para los objetos vector (y
otras estructuras de datos de la Biblioteca estándar de C++). Al momento de escribir este libro, no había
soporte para los inicializadores de listas para objetos vector en Visual C++.
7.11Conclusión
Enestecapítuloempezónuestraintroducciónalasestructurasdedatos,explorandoelusodelasplantillas
de clase array y vector para almacenar datos y obtenerlos de listas y tablas de valores. Los ejemplos de
este capítulo demostraron cómo declarar un arreglo, inicializarlo y hacer referencia a los elementos indi-
viduales de un arreglo. Pasamos arreglos a las funciones por referencia y utilizamos el calificador const
para evitar que la función a la que se llamó modificara los elementos del arreglo, para hacer valer el prin-
cipio del menor privilegio. Aprendió a usar la nueva instrucción for basada en rango para manipular
todosloselementosdeunarreglo.Tambiénlemostramoscómousarlasfuncionessort ybinary_search
de la Biblioteca estándar de C++ para ordenar y buscar en un arreglo, respectivamente. Aprendió a decla-
rar y manipular arreglos multidimensionales de arreglos. Utilizamos instrucciones for anidadas, contro-
ladas por contador y basadas en rango, para iterar a través de todas las filas y columnas de un arreglo bi-
dimensional. Además le mostramos cómo usar auto para inferir el tipo de una variable con base en su
valor inicializador. Finalmente, demostramos las herramientas de la plantilla de clase vector de la Biblio-
tecaestándardeC++.Eneseejemplo,vimoscómoaccederaloselementosdeobjetosarray yvector con
la comprobación de límites y demostramos los conceptos básicos sobre manejo de excepciones. En capí-
tulos posteriores continuaremos nuestra cobertura de las estructuras de datos.
Ya le hemos presentado los conceptos básicos de las clases, los objetos, las instrucciones de control,
las funciones y los objetos array. En el capítulo 8 presentaremos una de las herramientas más poderosas
de C++: el apuntador. Los apuntadores llevan la cuenta de la ubicación en donde se almacenan los datos
ylasfuncionesenmemoria,locualnospermitemanipularesoselementosenformasinteresantes.Como
veremos más adelante, C++ también cuenta con un elemento del lenguaje conocido como arreglo (di-
ferente de la plantilla de clase array), que está muy relacionado con los apuntadores. En el código de
C++ contemporáneo, se considera una mejor práctica usar la plantilla de clase array de C++11 que los
arreglos tradicionales.
Resumen
Sección 7.1 Introducción
• Las estructuras de datos (pág. 279) son colecciones de elementos de datos relacionados. Los arreglos
(pág. 279) son estructuras de datos que consisten en elementos de datos relacionados del mismo tipo.
Los arreglos son entidades “estáticas”, en cuanto a que permanecen del mismo tamaño a lo largo de la ejecu-
ción del programa.
Sección 7.2 Arreglos
• Un arreglo es un grupo consecutivo de localidades de memoria que comparten el mismo tipo.
• Cadaarregloconocesupropiotamaño,elcualpuededeterminarsemedianteunallamadaasufunciónmiembro
size (pág. 280).
• Para hacer referencia a una ubicación específica o elemento en un arreglo, especificamos el nombre del arreglo
y el número de posición del elemento específico en el arreglo.
• Para hacer referencia a cualquiera de los elementos de un arreglo, un programa proporciona el nombre del
arreglo seguido del índice (pág. 279) del elemento específico entre corchetes ([]).
Resumen 321
• El primer elemento en cada arreglo tiene el índice cero (pág. 279), y algunas veces se le llama el elemento cero.
• Un índice debe ser un entero o una expresión entera (que utilice cualquier tipo integral).
• Los corchetes que se utilizan para encerrar el subíndice de un arreglo son un operador con la misma precedencia
que los paréntesis.
Sección 7.3 Declaración de arreglos
• Los arreglos ocupan espacio en memoria. El programador especifica el tipo de cada elemento y el número de
elementos requeridos de la siguiente manera:
array tipo, tamañoArreglo  nombreArreglo;
y el compilador reserva la cantidad de memoria apropiada.
• Los arreglos se pueden declarar de manera que contengan cualquier tipo de datos. Por ejemplo, un arreglo de
tipo char se puede utilizar para almacenar una cadena de caracteres.
Sección 7.4 Ejemplos acerca del uso de arreglos
• Los elementos de un arreglo se pueden inicializar en la declaración de arreglos, seguida del nombre del arreglo
con un signo de igual y una lista inicializadora (pág. 282): una lista separada por comas (encerrada entre llaves)
de inicializadores constantes (pág. 282).
• Al inicializar un arreglo con una lista inicializadora, si hay menos inicializadores que elementos en el arreglo, el
resto de los elementos se inicializa con cero. El número de inicializadores debe ser menor o igual que el tamaño
del arreglo.
• Una variable constante que se utiliza para especificar el tamaño de un arreglo debe inicializarse con una expre-
sión constante al momento de declararse, y no puede modificarse en lo sucesivo.
• C++ no cuenta con comprobación de límites de los arreglos (pág. 291). Hay que asegurar que todas las referen-
cias al arreglo permanezcan dentro de los límites del mismo.
• Una variable local static en la definición de una función existe durante el tiempo que se ejecute el programa,
pero sólo es visible en el cuerpo de la función.
• Un programa inicializa los arreglos locales static la primera vez que encuentra sus declaraciones. Si el progra-
mador no inicializa en forma explícita un arreglo static, el compilador inicializa con cero cada elemento de ese
arreglo a la hora de crearlo.
Sección 7.5 Instrucción for basada en rango
• La nueva instrucción for basada en rango de C++ (pág. 293) nos permite manipular todos los elementos de un
arreglo sin usar un contador, con lo cual se evita la posibilidad de “salirse” del arreglo y se elimina la necesidad
de que implementemos nuestra propia comprobación de límites.
• La sintaxis de una instrucción for basada en rango es:
for ( declaracionVariableRango : expresion )
instruccion
en donde declaracionVariableRango tiene un tipo y un identificador, y expresión es el arreglo a través del cual se
va a iterar. El tipo en la declaracionVariableRango debe ser consistente con el tipo de los elementos del arreglo.
El identificador representa los elementos sucesivos de un arreglo en iteraciones sucesivas del ciclo. Puede usar la
instrucción for basada en rango con la mayoría de las estructuras de datos preconstruidas de la Biblioteca es-
tándar de C++ (las que se conocen comúnmente como contenedores), incluyendo las clases array y vector.
• Podemos usar una instrucción for basada en rango para modificar cada elemento, convirtiendo a definicionVa-
riableRango en una referencia.
• La instrucción for basada en rango puede usarse en vez de la instrucción for controlada por contador, cada vez
que la iteración del código a través de un arreglo no requiera acceso al subíndice del elemento.
322 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
Sección 7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para almacenar
las calificaciones
• Las variables de clase (miembros de datos static, pág. 300) son compartidas por todos los objetos de la clase en
la que se declaran las variables.
• Se puede acceder a un miembro de datos static dentro de la definición de la clase y de las definiciones de las
funciones miembro, al igual que con cualquier otro miembro de datos.
• También se puede acceder a un miembro de datos public static desde el exterior de la clase, aún y cuando no
existan objetos de la misma; para ello se utiliza el nombre de la clase, seguido del operador de resolución de
ámbito binario (::) y el nombre del miembro de datos.
Sección 7.7 Búsqueda y ordenamiento de datos en arreglos
• Ordenar los datos (colocarlos en orden ascendente o descendente) es una de las aplicaciones de cómputo más
importantes.
• Al proceso de buscar un elemento específico de un arreglo se le denomina búsqueda.
• La funcion sort de la Biblioteca estándar de C++ ordena los elementos de un arreglo en forma ascendente. Los
argumentos de la función especifican el rango de elementos que deben ordenarse. Más adelante verá que la
función sort puede usarse en otros tipos de contenedores también.
• La función binary_search de la Biblioteca estándar de C++ determina si un valor está en un arreglo. La secuen-
cia de valores debe ordenarse en forma ascendente primero. Los primeros dos argumentos de la función repre-
sentan el rango de elementos a buscar y el tercero es la clave de búsqueda: el valor a localizar. La función devuel-
ve un bool para indicar si se encontró el valor o no.
Sección 7.8 Arreglos multidimensionales
• Los arreglos multidimensionales (pág. 304) de dos dimensiones se utilizan con frecuencia para representar ta-
blas de valores (pág. 304), las cuales consisten en información ordenada en filas y columnas.
• Los arreglos que requieren dos subíndices para identificar a un elemento específico se llaman arreglos bidimen-
sionales (pág. 304). Un arreglo con m filas y n columnas se llama arreglo de m por n (pág. 304).
Sección 7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional
• En una declaración de variable, puede usarse la palabra clave auto (pág. 306) en vez de un nombre de tipo, para
inferir el tipo de la variable con base en el valor inicializador de la misma.
Sección 7.10 Introducción a la plantilla de clase vector de la Biblioteca estándar de C++
• La plantilla de clase vector (pág. 314) de la Biblioteca estándar de C++ representa una alternativa más comple-
ta a los arreglos, ya que cuenta con muchas capacidades que no se proporcionan para los arreglos basados en
apuntador estilo C.
• De manera predeterminada, todos los elementos de un objeto vector entero se establecen en 0.
• Un vector se puede definir de manera que almacene cualquier tipo de datos, mediante el uso de una declaración
como la siguiente:
vector tipo  nombre( tamaño );
• La función miembro size (pág. 317) de la plantilla de clase vector devuelve el número de elementos en el
vector en el que se invoca.
• Para acceder al valor de un elemento de un vector (o para modificarlo), se utilizan corchetes ([]).
• Los objetos de la plantilla de clase estándar vector se pueden comparar de manera directa con los operadores de
igualdad (==) y desigualdad (!=). El operador de asignación (=) también se puede usar con objetos vector.
• Un lvalue no modificable es una expresión que identifica a un objeto en memoria (como un elemento en un
vector), pero no se puede utilizar para modificar ese objeto. Un lvalue modificable también identifica a
un objeto en memoria, pero se puede usar para modificar el objeto.
• Una excepción (pág. 318) indica un problema que ocurre mientras se ejecuta un programa. El nombre “excep-
ción” sugiere que el problema ocurre con poca frecuencia; si la “regla” es que una instrucción se ejecute normal-
mente en forma correcta, entonces el problema representa la “excepción a la regla”.
Ejercicios de autoevaluación 323
• El manejo de excepciones (pág. 318) nos permite crear programas tolerantes a errores (pág. 318) que pueden
resolver excepciones.
• Para manejar una excepción, coloque cualquier código que pueda lanzar una excepción (pág. 319) en una ins-
trucción try.
• El bloque try (pág. 319) contiene el código que podría lanzar una excepción, y el bloque catch (pág. 319)
contiene el código que maneja la excepción, en caso de que ocurra.
• Cuando termina un bloque try, las variables declaradas en ese bloque quedan fuera de alcance.
• Un bloque catch declara un tipo y un parámetro de excepción. Dentro del bloque catch es posible usar el
identificador del parámetro para interactuar con un objeto excepción atrapado.
• El método what de un objeto excepción (pág. 319) devuelve el mensaje de error de la excepción.
Ejercicios de autoevaluación
7.1 (Llene los espacios en blanco) Complete las siguientes oraciones:
a) Las listas y tablas de valores pueden guardarse en __________ o ________.
b) Los elementos de un arreglo están relacionados por el hecho de que tienen el mismo ________ y
________.
c) El número utilizado para referirse a un elemento específico de un arreglo se conoce como el
__________ de ese elemento.
d) Un(a) __________ debe usarse para declarar el tamaño de un arreglo, ya que elimina los números
mágicos.
e) Al proceso de colocar los elementos de un arreglo en orden se le conoce como ______ el arreglo.
f) Al proceso de determinar si un arreglo contiene un valor clave específico se le conoce como ________
el arreglo.
g) Un arreglo que usa dos subíndices se conoce como un arreglo __________.
7.2 (Verdadero o falso) Conteste con verdadero o falso a cada una de las siguientes proposiciones; en caso de ser
falso, explique por qué.
a) Un arreglo puede guardar muchos tipos distintos de valores.
b) El subíndice de un arreglo debe ser generalmente de tipo float.
c) Si hay menos inicializadores en una lista inicializadora que el número de elementos en el arreglo, el
resto de los elementos se inicializa con el último valor en la lista inicializadora.
d) Es un error si una lista inicializadora contiene más inicializadores que elementos en el arreglo.
7.3 (Escriba instrucciones de C++) Escriba una o más instrucciones que realicen las siguientes tareas para un
arreglo llamado fracciones:
a) Defina una variable constante entera llamada tamanioArreglo para representar el tamaño de un arre-
glo y que se inicialice con 10.
b) Declare un arreglo con tamanioArreglo elementos de tipo double, e inicialice los elementos con 0.
c) Nombre el cuarto elemento del arreglo.
d) Haga referencia al elemento 4 del arreglo.
e) Asigne el valor 1.667 al elemento 9 del arreglo.
f) Asigne el valor 3.333 al séptimo elemento del arreglo.
g) Imprima los elementos 6 y 9 del arreglo con dos dígitos de precisión a la derecha del punto decimal, y
muestre los resultados que aparecen realmente en la pantalla.
h) Imprima todos los elementos del arreglo usando una instrucción for. Defina la variable entera i como
variable de control para el ciclo. Muestre la salida.
i) Imprima todos los elementos del arreglo separados por espacios, usando una instrucción for basada
en rango.
7.4 (Preguntas sobre arreglos bidimensionales) Responda a las siguientes preguntas en relación con un arreglo
llamado tabla:
a) Declare el arreglo para que almacene valores int y cuente con tres filas y tres columnas. Suponga que
se ha declarado la variable tamanioArreglo con el valor de 3.
b) ¿Cuántos elementos contiene el arreglo?
324 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
c) Utilice una instrucción for controlada por contador para inicializar cada elemento del arreglo con la
suma de sus subíndices.
d) Escriba una instrucción for anidada que muestre los valores de cada elemento del arreglo tabla en
formato tabular, con 3 filas y 3 columnas. Cada fila y columna deben etiquetarse con el número de fila
o columna correspondiente. Suponga que el arreglo se inicializó con una lista inicializadora que con-
tiene los valores del 1 al 9 en orden. Muestre la salida.
7.5 (Encuentre el error) Encuentre y corrija el error en cada uno de los siguientes segmentos de programa:
a) #include iostream;
b) tamanioArreglo = 10; // tamanioArreglo se declaró como const
c) Suponga que array int, 10  b = {};
for ( size_t i = 0; i = b.size(); ++i )
b[ i ] = 1;
d) Suponga que a es un arreglo bidimensional de valores int con dos filas y dos columnas:
a[ 1, 1 ] = 5;
Respuestas a los ejercicios de autoevaluación
7.1 a) arreglos, objetos vector. b) nombre de arreglo, tipo. c) subíndice o índice. d) variable constante.
e) ordenamiento. f) búsqueda. g) bidimensional.
7.2 a) Falso. Un arreglo sólo puede guardar valores del mismo tipo.
b) Falso. El subíndice de un arreglo debe ser un entero o una expresión entera.
c) Falso. El resto de los elementos se inicializa con cero.
d) Verdadero.
7.3 a) const size_t tamanioArreglo = 10;
b) array double, tamanioArreglo  fracciones = { 0.0 };
c) fracciones[ 3 ]
d) fracciones[ 4 ]
e) fracciones[ 9 ] = 1.667;
f) fracciones[ 6 ] = 3.333;
g) cout  fixed  setprecision( 2 );
cout  fracciones[ 6 ]  ' '  fracciones[ 9 ]  endl;
Salida: 3.33 1.67
h) for ( size_t i = 0; i  fracciones.size(); ++i )
cout  fracciones[  i  ] =   fracciones[ i ]  endl;
Salida:
fracciones[ 0 ] = 0.0
fracciones[ 1 ] = 0.0
fracciones[ 2 ] = 0.0
fracciones[ 3 ] = 0.0
fracciones[ 4 ] = 0.0
fracciones[ 5 ] = 0.0
fracciones[ 6 ] = 3.333
fracciones[ 7 ] = 0.0
fracciones[ 8 ] = 0.0
fracciones[ 9 ] = 1.667
i) for ( double element : fracciones )
cout  element  ' ';
7.4 a) array array int, tamanioArreglo , tamanio Arreglo  tabla;
b) Nueve.
Ejercicios 325
c) for ( size_t fila = 0; fila  tamanioArreglo(); ++fila )
for ( size_t columna = 0; columna  tabla[ fila ].size(); ++columna )
tabla[ fila ][ columna ] = fila + columna;
d) cout   [0] [1] [2]  endl;
for ( size_t i = 0; i  tamanioArreglo; ++i ) {
cout  '['  i  ] ;
for ( size_t j = 0; j  tamanioArreglo; ++j )
cout  setw( 3 )  tabla[ i ][ j ]   ;
cout  endl;
}
Salida:
[0] [1] [2]
[0] 1 8 0
[1] 2 4 6
[2] 5 0 0
7.5 a) Error: punto y coma al final de la directiva del preprocesador #include.
Corrección: elimina el punto y coma.
b) Error: asignar un valor a una variable constante que utiliza una instrucción de asignación.
Corrección: inicializa la variable constante en una declaración const size_t tamanioArreglo.
c) Error: hacer referencia a un elemento del arreglo fuera de sus límites (b[10]).
Corrección: cambie la condición de continuación de ciclo para usar  en vez de =.
d) Error: el subíndice del arreglo se escribió en forma incorrecta.
Corrección: cambie la instrucción por a[ 1 ][ 1 ] = 5;.
Ejercicios
7.6 (Llene los espacios en blanco) Complete las siguientes oraciones:
a) Los nombres de los cuatro elementos del arreglo p son ______, ______, ______ y ______.
b) Al proceso de nombrar un arreglo, declarar su tipo y especificar el número de elementos se le conoce
como __________ el arreglo.
c) Por convención, el primer subíndice en un arreglo bidimensional identifica el(la) __________ de un
elemento y el segundo índice identifica el(la) __________ del elemento.
d) Un arreglo de m por n contiene __________ filas, __________ columnas y __________ elementos.
e) El nombre del elemento en la fila 3 y la columna 5 del arreglo d es __________.
7.7 (Verdadero o falso) Conteste con verdadero o falso a cada una de las siguientes proposiciones; en caso de ser
falso, explique por qué.
a) Para referirnos a una ubicación o elemento específico dentro de un arreglo, especificamos el nombre
del arreglo y el valor del elemento específico.
b) La definición de un arreglo reserva espacio para el mismo.
c) Para indicar que deben reservarse 100 ubicaciones para el arreglo entero p, el programador escribe la
declaración
p[ 100 ];
d) Hayqueusarunainstrucciónfor parainicializarconceroloselementosdeunarreglode15elementos.
e) Hay que usar instrucciones for anidadas para sumar el total de los elementos de un arreglo bidimen-
sional.
7.8 (Escriba instrucciones en C++) Escriba instrucciones en C++ que realicen cada una de las siguientes tareas:
a) Mostrar el valor del elemento 6 del arreglo de caracteres alfabeto.
b) Recibir un valor y colocarlo en el elemento 4 del arreglo de punto flotante unidimensional llamado
calificaciones.
c) Inicializar con 8 cada uno de los 5 elementos del arreglo entero unidimensional.
326 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
d) Sumar el total e imprimir los elementos del arreglo temperaturas de punto flotante con 100 ele-
mentos.
e) Copiar el arreglo a en la primera parte del arreglo b. Suponga que ambos arreglos contienen valores
double y que los arreglos a y b tienen 11 y 34 elementos, respectivamente.
f) Determinar e imprimir los valores menor y mayor contenidos en el arreglo w con 99 elementos de
punto flotante.
7.9 (Preguntas sobre arreglos bidimensionales) Considere un arreglo entero t de 2 por 3.
a) Escriba una declaración para t.
b) ¿Cuántas filas tiene t?
c) ¿Cuántas columnas tiene t?
d) ¿Cuántos elementos tiene t?
e) Escriba los nombres de todos los elementos en la fila 1 de t.
f) Escriba los nombres de todos los elementos en la columna 2 de t.
g) Escriba una instrucción que asigne cero al elemento de t en la primera fila y la segunda columna.
h) Escriba una serie de instrucciones que inicialice cada elemento de t con cero. No utilice un ciclo.
i) Escriba una instrucción for anidada y controlada por contador, que inicialice cada elemento de t con
cero.
j) Escriba una instrucción for anidada y basada en rango, que inicialice cada elemento de t con cero.
k) Escriba una instrucción que reciba como entrada los valores para los elementos de t desde el teclado.
l) Escriba una serie de instrucciones que determine e imprima el valor más pequeño en el arreglo t.
m) Escriba una instrucción que muestre los elementos en la fila 0 de t.
n) Escriba una instrucción que totalice los elementos de la columna 2 de t.
o) Escriba una serie de instrucciones para imprimir el contenido de t en formato tabular ordenado. En-
liste los subíndices de columna como encabezados a lo largo de la parte superior, y enliste los subíndi-
ces de fila a la izquierda de cada fila.
7.10 (Rangos de salarios de vendedores) Utilice un arreglo unidimensional para resolver el siguiente problema.
Una compañía paga a sus vendedores por comisión. Los vendedores reciben $200 por semana más el 9% de sus
ventastotalesdeesasemana.Porejemplo,unvendedorqueacumule$5000enventasenunasemana,recibirá$200
más el 9% de $5000, o un total de $650. Escriba un programa (utilizando un arreglo de contadores) que determi-
ne cuántos vendedores recibieron salarios en cada uno de los siguientes rangos (suponga que el salario de cada
vendedor se trunca a una cantidad entera):
a) $200–299
b) $300–399
c) $400–499
d) $500–599
e) $600–699
f) $700–799
g) $800–899
h) $900–999
i) $1000 en adelante
7.11 (Preguntas sobre arreglos unidimensionales) Escriba instrucciones individuales que realicen las siguientes
operaciones con arreglos unidimensionales:
a) Inicializar con cero los 10 elementos del arreglo cuentas de tipo entero.
b) Sumar uno a cada uno de los 15 elementos del arreglo bono de tipo entero.
c) Leer 12 valores para el arreglo de valores double llamado temperaturasMensuales mediante el te-
clado.
d) Imprimir los 5 valores del arreglo entero mejoresPuntuaciones en formato de columnas.
7.12 (Encontrar los errores) Encuentre el (los) error(es) en cada una de las siguientes instrucciones:
a) Asuma que a es un arreglo de tres valores int:
cout  a[ 1 ]     a[ 2 ]     a[ 3 ]  endl;
b) array double, 3  f = { 1.1, 10.01, 100.001, 1000.0001 };
Ejercicios 327
c) Asuma que d es un arreglo de valores double con dos filas y 10 columnas.
d[ 1, 9 ] = 2.345;
7.13 (Eliminacióndeduplicadoscon array)Useunarreglounidimensionalpararesolverelsiguienteproblema.
Recibir como entrada 20 números, cada uno de los cuales debe estar entre 10 y 100, inclusive. A medida que se lea
cada número, validarlo y almacenarlo en el arreglo, sólo si no es un duplicado de un número ya leído. Después de
leer todos los valores, mostrar sólo los valores únicos que el usuario introdujo. Prepárese para el “peor caso”, en el
que los 20 números son diferentes. Use el arreglo más pequeño que sea posible para resolver este problema.
7.14 (Eliminación de duplicados con vector) Reimplemente el ejercicio 7.13, usando ahora un vector. Co-
mience con un vector vacío y use su función push_back para agregar cada valor único al vector.
7.15 (Inicialización de arreglos bidimensionales) Etiquete los elementos del arreglo bidimensional ventas de 3
por 5, para indicar el orden en el que se establecen en cero, mediante el siguiente fragmento de programa:
for ( size_t fila = 0; fila  ventas.size(); ++fila )
for ( size_t columna = 0; columna  sales[ fila ].size(); ++columna )
ventas[ fila ][ columna ] = 0;
7.16 (Tirar dados) Escriba un programa para simular el tiro de dos dados. Después debe calcularse la suma
de los dos valores. [Nota: cada dado puede mostrar un valor entero del 1 al 6, por lo que la suma de los valores
variará del 2 al 12, siendo 7 la suma más frecuente, mientras que 2 y 12 serán las sumas menos frecuentes]. En la
figura 7.26 se muestran las 36 posibles combinaciones de los dos dados. Su programa debe tirar los dados 36000
veces. Utilice un arreglo unidimensional para registrar el número de veces que aparezca cada una de las posibles
sumas. Imprima los resultados en formato tabular. Determine además si los totales son razonables (es decir, hay
seis formas de tirar un 7, por lo que aproximadamente una sexta parte de los tiros deben ser 7).
2
1
3
4
5
6
3
2
4
5
6
7
4
3
5
6
7
8
5
4
6
7
8
9
6
5
7
8
9
10
7
6
8
9
10
11
8
7
9
10
11
12
3
2
1 6
5
4
Fig. 7.26  Los 36 posibles resultados de tirar dos dados.
7.17 (¿Qué hace este código) ¿Qué hace el siguiente programa?
1 // Ej. 7.17: ej07_17.cpp
2 // ¿Qué hace este programa?
3 #include iostream
4 #include array
5 using namespace std;
6
7 const size_t tamanioArreglo = 10;
8 int queEsEsto( const array int, tamanioArreglo  , size_t ); // prototipo
9
10 int main()
11 {
12 array int, tamanioArreglo  a = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
328 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
13
14 int resultado = queEsEsto( a, tamanioArreglo );
15
16 cout  El resultado es   resultado  endl;
17 } // fin de main
18
19 // ¿Qué hace esta función?
20 int queEsEsto( const array int, tamanioArreglo  b, size_t tamanio )
21 {
22 if ( tamanio == 1 ) // caso base
23 return b[ 0 ];
24 else // paso recursivo
25 return b[ tamanio - 1 ] + queEsEsto( b, tamanio - 1 );
26 } // fin de la función queEsEsto
7.18 (Modificación al juego de Craps) Modifique el programa de la figura 6.11 para ejecutar 1000 juegos de
craps. El programa debe mantener un registro de las estadísticas y responder a las siguientes preguntas:
a) ¿Cuántos juegos se ganan en el primer tiro, en el segundo tiro, …, en el vigésimo tiro y después de
éste?
b) ¿Cuántos juegos se pierden en el primer tiro, en el segundo tiro, …, en el vigésimo tiro y después de
éste?
c) ¿Cuáles son las probabilidades de ganar en craps? [Nota: con el tiempo descubrirá que craps es uno de
los juegos de casino más justos. ¿Qué cree usted que significa esto?]
d) ¿Cuál es la duración promedio de un juego de craps?
e) ¿Las probabilidades de ganar mejoran con la duración del juego?
7.19 (Conversión del ejemplo del vector de la sección 7.10 a un arreglo) Convierta el ejemplo de un vector de
la figura 7.26 para que use arreglos. Elimine las características que sean sólo para vectores.
7.20 (¿Qué hace este código) ¿Qué hace el siguiente programa?
1 // Ej. 7.20: ej07_20.cpp
2 // ¿Qué hace este programa?
3 #include iostream
4 #include array
5 using namespace std;
6
7 const size_t tamanioArreglo = 10;
8 void unaFuncion( const array int, tamanioArreglo  , size_t ); // prototipo
9
10 int main()
11 {
12 array int, tamanioArreglo  a = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
13
14 cout  Los valores en el arreglo son:  endl;
15 unaFuncion( a, 0 );
16 cout  endl;
17 } // fin de main
18
19 // ¿Qué hace esta función?
20 void unaFuncion( const array int, tamanioArreglo  b, size_t actual )
21 {
22 if ( actual  b.size() )
23 {
24 unaFuncion( b, actual + 1 );
25 cout  b[ actual ]   ;
26 } // fin de if
27 } // fin de la función unaFuncion
Ejercicios 329
7.21 (Resumen de ventas) Use un arreglo bidimensional para resolver el siguiente problema: una compañía
tiene cuatro vendedores (1 a 4) que venden cinco productos distintos (1 a 5). Una vez al día, cada vendedor pasa
una nota por cada tipo de producto vendido. Cada nota contiene lo siguiente:
a) El número del vendedor.
b) El número del producto.
c) El valor total en dólares de ese producto vendido en ese día.
Así, cada vendedor pasa entre 0 y 5 notas de venta por día. Suponga que está disponible la información sobre todas
las notas del mes pasado. Escriba un programa que lea toda esta información para las ventas del último mes (los
datos de un vendedor a la vez) y que resuma las ventas totales por vendedor, por producto.Todos los totales deben
guardarse en el arreglo bidimensional ventas. Después de procesar toda la información del mes pasado, muestre
los resultados en formato tabular, en donde cada columna represente a un vendedor específico y cada fila represen-
te a un producto. Saque el total de cada fila para obtener las ventas totales de cada producto durante el último mes.
Saque el total de cada columna para obtener las ventas totales por cada vendedor durante el último mes. Su impre-
sión tabular debe incluir estos totales cruzados a la derecha de las filas totalizadas, y en la parte inferior de las
columnas totalizadas.
7.22 (Paseo del caballo) Uno de los enigmas más interesantes para los entusiastas del ajedrez es el problema del
Paseo del caballo. La pregunta es: ¿Puede la pieza de ajedrez, conocida como caballo, moverse alrededor de un ta-
blero de ajedrez vacío y tocar cada una de las 64 posiciones una y sólo una vez? A continuación estudiaremos deta-
lladamente este intrigante problema.
El caballo realiza solamente movimientos en forma de L (dos espacios en una dirección y un espacio en una
dirección perpendicular). Por lo tanto, como se muestra en la figura 7.27, desde una posición cerca del centro de
un tablero de ajedrez vacío, el caballo puede hacer ocho movimientos distintos (numerados del 0 al 7).
0 5
4
3
2
1
C
2 1
3
4
0
7
5 6
1
2
0
3
4
5
6
7
6 7
Fig. 7.27  Los ocho posibles movimientos del caballo.
a) Dibuje un tablero de ajedrez de 8 por 8 en una hoja de papel, e intente realizar un Paseo del caballo en
forma manual. Ponga un 1 en la posición inicial, un 2 en la segunda posición, un 3 en la tercera, etc.
Antes de empezar el paseo, estime qué tan lejos podrá avanzar, recordando que un paseo completo
consta de 64 movimientos. ¿Qué tan lejos llegó? ¿Estuvo esto cerca de su estimación?
b) Ahora desarrollaremos un programa para mover el caballo alrededor de un tablero de ajedrez. El
tablero estará representado por un arreglo bidimensional llamado tablero, de ocho por ocho. Cada
posición se inicializará con cero. Describiremos cada uno de los ocho posibles movimientos en
términos de sus componentes horizontales y verticales. Por ejemplo, un movimiento de tipo 0,
como se muestra en la figura 7.27, consiste en mover dos posiciones horizontalmente a la derecha y
una posición verticalmente hacia arriba. Un movimiento de tipo 2 consiste en mover una posición
horizontalmente a la izquierda y dos posiciones verticalmente hacia arriba. Los movimientos hori-
zontal a la izquierda y vertical hacia arriba se indican con números negativos. Los ocho movimientos
330 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
pueden describirse mediante dos arreglos unidimensionales llamados horizontal y vertical, de la
siguiente manera:
horizontal[ 0 ] = 2 vertical[ 0 ] = -1
horizontal[ 1 ] = 1 vertical[ 1 ] = -2
horizontal[ 2 ] = -1 vertical[ 2 ] = -2
horizontal[ 3 ] = -2 vertical[ 3 ] = -1
horizontal[ 4 ] = -2 vertical[ 4 ] = 1
horizontal[ 5 ] = -1 vertical[ 5 ] = 2
horizontal[ 6 ] = 1 vertical[ 6 ] = 2
horizontal[ 7 ] = 2 vertical[ 7 ] = 1
Haga que las variables filaActual y columnaActual indiquen la fila y columna, respectivamente,
de la posición actual del caballo. Para hacer un movimiento de tipo numeroMovimiento, en donde
numeroMovimiento puede estar entre 0 y 7, su programa debe utilizar las instrucciones
filaActual += vertical[ numeroMovimiento ];
columnaActual += horizontal[ numeroMovimiento ];
Utilice un contador que varíe de 1 a 64. Registre la última cuenta en cada posición a la que se mueva
el caballo. Evalúe cada movimiento potencial para ver si el caballo ya visitó esa posición y, desde luego,
pruebe cada movimiento potencial para asegurarse que el caballo no se salga del tablero de ajedrez.
Ahora escriba un programa para desplazar el caballo por el tablero. Ejecute el programa. ¿Cuántos
movimientos hizo el caballo?
c) Después de intentar escribir y ejecutar un programa de Paseo del caballo, probablemente haya desa-
rrollado algunas ideas valiosas. Utilizaremos estas ideas para desarrollar una heurística (o estrategia)
para mover el caballo. La heurística no garantiza el éxito, pero una heurística cuidadosamente desarro-
llada mejora considerablemente la probabilidad de tener éxito. Probablemente usted ya observó que
las posiciones externas son más difíciles que las posiciones cercanas al centro del tablero. De hecho, las
posiciones más difíciles o inaccesibles son las cuatro esquinas.
La intuición sugiere que usted debe intentar mover primero el caballo a las posiciones más pro-
blemáticas y dejar pendientes aquellas a las que sea más fácil llegar, de manera que cuando el tablero
se congestione cerca del final del paseo, habrá una mayor probabilidad de éxito.
Podríamos desarrollar una “heurística de accesibilidad” clasificando cada una de las posiciones de
acuerdo a qué tan accesibles son y luego mover siempre el caballo (usando los movimientos en L del
caballo)alaposiciónmásinaccesible.Etiquetaremosunarreglobidimensionalllamadoaccesibilidad
con números que indiquen desde cuántas posiciones es accesible una posición determinada. En un ta-
blero de ajedrez en blanco, cada una de las 16 posiciones más cercanas al centro se clasifican con 8; cada
posición en la esquina se clasifica con 2; y las demás posiciones tienen números de accesibilidad 3, 4 o
6, de la siguiente manera:
2 3 4 4 4 4 3 2
3 4 6 6 6 6 4 3
4 6 8 8 8 8 6 4
4 6 8 8 8 8 6 4
4 6 8 8 8 8 6 4
4 6 8 8 8 8 6 4
3 4 6 6 6 6 4 3
2 3 4 4 4 4 3 2
Escriba una nueva versión del programa del Paseo del caballo, utilizando la heurística de acce-
sibilidad. El caballo deberá moverse siempre a la posición con el número de accesibilidad más bajo.
En caso de un empate, el caballo podrá moverse a cualquiera de las posiciones empatadas. Por lo
tanto, el paseo puede empezar en cualquiera de las cuatro esquinas. [Nota: al ir moviendo el caballo
alrededor del tablero, su aplicación deberá reducir los números de accesibilidad a medida que se va-
yan ocupando más posiciones. De esta manera y en cualquier momento dado durante el paseo, el
número de accesibilidad de cada una de las posiciones disponibles seguirá siendo igual al número
preciso de posiciones desde las que se puede llegar a esa posición]. Ejecute esta versión de su progra-
Ejercicios 331
ma. ¿Logró completar el paseo? Ahora modifique el programa para realizar 64 paseos, en donde cada
uno empiece desde una posición distinta en el tablero. ¿Cuántos paseos completos logró realizar?
d) Escriba una versión del programa del Paseo del caballo que, al encontrarse con un empate entre dos
o más posiciones, decida qué posición elegir buscando más adelante aquellas posiciones que se
puedan alcanzar desde las posiciones “empatadas”. Su aplicación debe mover el caballo a la posición
empatada para la cual el siguiente movimiento lo lleve a una posición con el número de accesibilidad
más bajo.
7.23 (Paseo del caballo: métodos de fuerza bruta) En el ejercicio 7.22, desarrollamos una solución al problema
del Paseo del caballo. El método utilizado, llamado “heurística de accesibilidad”, genera muchas soluciones y se
ejecuta con eficiencia.
A medida que se incremente de manera continua la potencia de las computadoras, seremos capaces de resolver
más problemas con menos potencia y algoritmos relativamente menos sofisticados. A éste le podemos llamar el
método de la “fuerza bruta” para resolver problemas.
a) Utilice la generación de números aleatorios para permitir que el caballo se desplace a lo largo del table-
ro (mediante sus movimientos legítimos en L) en forma aleatoria. Su programa debe ejecutar un paseo
e imprimir el tablero final. ¿Qué tan lejos llegó el caballo?
b) La mayoría de las veces, el programa anterior produce un paseo relativamente corto. Ahora modifique
su aplicación para intentar 1000 paseos. Use un arreglo unidimensional para llevar el registro del nú-
mero de paseos de cada longitud. Cuando su programa termine de intentar los 1000 paseos, deberá
imprimir esta información en un formato tabular ordenado. ¿Cuál fue el mejor resultado?
c) Es muy probable que el programa anterior le haya brindado algunos paseos “respetables”, pero no
completos.Ahoradejequesuaplicaciónseejecutehastaqueproduzcaunpaseocompleto.[Precaución:
esta versión del programa podría ejecutarse durante horas en una computadora poderosa]. Una vez
más, mantenga una tabla del número de paseos de cada longitud e imprímala cuando se encuentre el
primer paseo completo. ¿Cuántos paseos intentó su programa antes de producir uno completo?
¿Cuánto tiempo se tomó?
d) Compare la versión de la fuerza bruta del Paseo del caballo con la versión heurística de accesibilidad.
¿Cuál requirió un estudio más cuidadoso del problema? ¿Qué algoritmo fue más difícil de desarro-
llar? ¿Cuál requirió más poder de cómputo? ¿Podríamos tener la certeza (por adelantado) de obtener
un paseo completo mediante el método de la heurística de accesibilidad? ¿Podríamos tener la certe-
za (por adelantado) de obtener un paseo completo mediante el método de la fuerza bruta? Argumen-
te las ventajas y desventajas de solucionar el problema mediante la fuerza bruta en general.
7.24 (Ocho reinas) Otro enigma para los entusiastas del ajedrez es el problema de las Ocho reinas, el cual pre-
gunta lo siguiente: ¿es posible colocar ocho reinas en un tablero de ajedrez vacío, de tal manera que ninguna reina
“ataque” a cualquier otra (es decir, que no haya dos reinas en la misma fila, en la misma columna o a lo largo de la
misma diagonal)? Use la idea desarrollada en el ejercicio 7.22 para formular una heurística y resolver el problema
de las Ocho reinas. Ejecute su programa. [Sugerencia: es posible asignar un valor a cada una de las posiciones en el
tablero de ajedrez, para indicar cuántas posiciones de un tablero vacío se “eliminan” si una reina se coloca en esa
posición. A cada una de las esquinas se le asignaría el valor 22, como se demuestra en la figura 7.28. Una vez que
estos “números de eliminación” se coloquen en las 64 posiciones, una heurística apropiada podría ser la siguiente:
coloque la siguiente reina en la posición con el número de eliminación más pequeño. ¿Por qué esta estrategia es
intuitivamente atractiva?].
7.25 (Ocho reinas: métodos de fuerza bruta) En este ejercicio usted desarrollará varios métodos de fuerza bruta
para resolver el problema de las Ocho reinas que presentamos en el ejercicio 7.24.
a) Utilice la técnica de la fuerza bruta aleatoria desarrollada en el ejercicio 7.23, para resolver el problema
de las Ocho reinas.
b) Utilice una técnica exhaustiva (es decir, pruebe todas las combinaciones posibles de las ocho reinas en
el tablero).
c) ¿Por qué supone que el método de la fuerza bruta exhaustiva podría no ser apropiado para resolver el
problema del Paseo del caballo?
d) Compare y contraste el método de la fuerza bruta aleatoria con el de la fuerza bruta exhaustiva en ge-
neral.
332 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones
* *
*
*
*
*
* *
* *
* *
* *
* *
*
*
*
*
*
*
Fig. 7.28  Las 22 posiciones eliminadas al colocar una reina en la esquina superior izquierda.
7.26 (Paseo del caballo: prueba del paseo cerrado) En el Paseo del caballo se lleva a cabo un paseo completo cuan-
do el caballo hace 64 movimientos, en los que toca cada esquina del tablero una sola vez. Un paseo cerrado ocurre
cuando el movimiento 64 se encuentra a un movimiento de distancia de la posición en la que el caballo empezó el
paseo. Modifique el programa del Paseo del caballo que escribió en el ejercicio 7.22 para probar si el paseo ha sido
completo, y si se trató de un paseo cerrado.
7.27 (La criba de Eratóstenes) Un entero primo es cualquier entero divisible sólo por sí mismo y por el número 1.
La Criba de Eratóstenes es un método para encontrar números primos, el cual opera de la siguiente manera:
a) Cree un arreglo con todos los elementos inicializados en 1 (verdadero). Los elementos del arreglo con
subíndices primos permanecerán como 1. Cualquier otro elemento del arreglo eventualmente cam-
biará a cero. En este ejercicio, ignoraremos los elementos 0 y 1.
b) Empezandoconelsubíndice2delarreglo,cadavezqueseencuentreunelementodelarreglocuyovalorsea1,
itere a través del resto del arreglo y asigne cero a todo elemento cuyo subíndice sea múltiplo del subíndice
del elemento que tiene el valor 1. Para el subíndice 2 del arreglo, todos los elementos más allá del elemento 2
en el arreglo que tengan subíndices múltiplos de 2 (los índices 4, 6, 8, 10, etcétera) se establecerán en cero;
para el subíndice 3 del arreglo, todos los elementos más allá del elemento 3 en el arreglo que sean múltiplos
de3(losíndices6,9,12,15,etcétera)seestableceránencero;yasísucesivamente.
Cuando este proceso termine, los elementos del arreglo que aún sean uno indicarán que el subíndice es un número
primo. Estos subíndices pueden entonces imprimirse. Escriba un programa que utilice un arreglo de 1000 elemen-
tos para determinar e imprimir los números primos entre 2 y 999. Ignore el elemento 0 del arreglo.
Ejercicios de recursividad
7.28 (Palíndromos) Un palíndromo es una cadena que se escribe de la misma forma tanto al derecho como al
revés. Algunos ejemplos de palíndromos son “radar”, “reconocer” y (si se ignoran los espacios) “anita lava la tina”.
Escriba una función recursiva llamada probarPalindromo, que devuelva true si la cadena almacenada en el arreglo
es un palíndromo, y false en caso contrario. Cabe mencionar que, al igual que un arreglo, es posible usar el ope-
rador de corchetes ([]) para iterar a través de los caracteres en un objeto string.
7.29 (Ocho reinas) Modifique el programa de las Ocho reinas que creó en el ejercicio 7.24 para resolver el pro-
blema en forma recursiva.
7.30 (Imprimir un arreglo) Escriba una función recursiva llamada imprimirArreglo que reciba un arreglo, un
subíndiceinicialyunsubíndicefinalcomoargumentos,quenodevuelvanadayqueimprimaelarreglo.Lafunción
deberá dejar de procesar y deberá regresar cuando el subíndice inicial sea igual al subíndice final.
7.31 (Imprimir una cadena en forma inversa) Escriba una función recursiva llamada cadenaInversa, que reciba
un arreglo de caracteres que contenga una cadena y un subíndice inicial como argumentos, imprima la cadena en
forma inversa y no devuelva nada. La función deberá dejar de procesar y deberá regresar al encontrar la cadena nula
de terminación. Cabe mencionar que, al igual que un arreglo, es posible usar el operador de corchetes ([]) para iterar
a través de los caracteres en un objeto string.
Hacer la diferencia 333
7.32 (Buscar el valor mínimo en un arreglo) Escriba una función recursiva llamada minimoRecursivo que reci-
ba un arreglo de enteros, un subíndice inicial y un subíndice final como argumentos, y que devuelva el elemento
más pequeño del arreglo. La función deberá dejar de procesar y deberá regresar al encontrar la cadena nula de ter-
minación.
7.33 (Recorrido de laberinto) La cuadrícula de signos # y puntos (.) en la figura 7.29 es la representación de un
arreglo bidimensional integrado de un laberinto. En este arreglo bidimensional integrado, los signos # representan
las paredes del laberinto y los puntos representan posiciones en las posibles rutas por el laberinto. Sólo pueden
hacerse movimientos hacia una ubicación en el arreglo integrado que contenga un punto.
Hay un algoritmo simple para recorrer un laberinto que garantiza encontrar la salida (suponiendo que la
haya). Si no hay una salida, volverá a la misma ubicación inicial de nuevo. Coloque su mano derecha en la pared
a su derecha y comience a caminar hacia delante. Nunca quite su mano de la pared. Si el laberinto da vuelta a la
derecha, siga la pared a su derecha. Mientras no quite su mano de la pared, finalmente llegará a la salida del labe-
rinto. Puede haber una ruta más corta que la que usted eligió, pero se garantiza que saldrá del laberinto si sigue el
algoritmo.
# # # # # # # # # # # #
# . . . # . . . . . . #
. . # . # . # # # # . #
# # # . # . . . . # . #
# . . . . # # # . # . .
# # # # . # . # . # . #
# . . # . # . # . # . #
# # . # . # . # . # . #
# . . . . . . . . # . #
# # # # # # . # # # . #
# . . . . . . # . . . #
# # # # # # # # # # # #
Fig. 7.29  Representación de un laberinto en un arreglo integrado bidimensional.
Escriba la función recursiva recorrerLaberinto para caminar por el laberinto. La función debe recibir argu-
mentos que incluyan un arreglo integrado de 12 por 12 de valores char, que representan el laberinto y la ubicación
inicial del mismo. A medida que recorrerLaberinto intente localizar la salida del laberinto, debe colocar el carác-
ter X en cada posición en la ruta. La función debe mostrar el laberinto después de cada movimiento, para que el
usuario pueda observar a medida que se vaya resolviendo.
7.34 (Generación de laberintos al azar) Escriba una función llamada generadorLaberinto que produzca un
laberinto al azar. La función debe recibir como argumentos un arreglo bidimensional integrado de 12 por 12 de
valores char y apuntadoras a las variables int que representan la fila y la columna del punto de entrada del laberin-
to. Pruebe su función recorrerLaberinto del ejercicio 7.33, usando varios laberintos generados al azar.
Hacer la diferencia
7.35 (Votaciones)) InternetyWebpermitenquecadavezhayamáspersonasconectadasenred,unidasporunacausa,
expresen sus opiniones, etcétera. En 2012, los candidatos presidenciales usaron Internet para expresar sus mensajes y
recaudar dinero para sus campañas. En este ejercicio, usted escribirá un pequeño programa de votaciones que permite a
los usuarios calificar cinco asuntos de conciencia social, desde 1 (menos importante) hasta 10 (más importante). Elija
cinco causas (por ejemplo, asuntos políticos, asuntos sobre el entorno global). Use un arreglo string unidimensional
llamado temas para almacenar las causas. Para sintetizar las respuestas de la encuesta, use un arreglo bidimensional de
5filasy10columnasllamadorespuestas (detipoint),endondecadafilacorrespondaaunelementodelarreglotemas.
Cuandoseejecuteelprograma,debepediralusuarioquecalifiquecadaasunto.Hagaquesusamigosyfamiliaresrespon-
dan a la encuesta. Después haga que el programa muestre un resumen de los resultados, incluyendo:
a) Un informe tabular con los cinco temas del lado izquierdo y las 10 calificaciones a lo largo de la parte
superior, listando en cada columna el número de calificaciones recibidas para cada tema.
b) A la derecha de cada fila, muestre el promedio de las calificaciones para cada asunto específico.
c) ¿Qué asunto recibió la mayor puntuación total? Muestre tanto el asunto como la puntuación total.
d) ¿Qué asunto recibió la menor puntuación total? Muestre tanto el asunto como la puntuación total.
Apuntadores
8
Recibimos direcciones para
ocultar nuestro paradero.
—Saki (H. H. Munro)
Averigua la dirección mediante
la indirección.
—William Shakespeare
Muchas cosas, teniendo
referencia completa
A un consentimiento, pueden
trabajar en forma contraria
—William Shakespeare
¡Descubrirá que es una muy
buena práctica verificar siempre
sus referencias, señor!
—Dr. Routh
O b j e t i v o s
En este capítulo aprenderá a:
n Distinguir qué son los
apuntadores.
n Conocer las similitudes y
diferencias entre los
apuntadores y las referencias.
n Utilizar apuntadores para
pasar argumentos a las
funciones por referencia.
n Conocer las estrechas
relaciones entre los
apuntadores y los arreglos
integrados.
n Utilizar cadenas basadas
en apuntador.
n Utilizar los arreglos
integrados.
n Utilizar las herramientas de
C++11, incluyendo nullptr
y las funciones begin y end
de la Biblioteca estándar.
8.2 Declaraciones e inicialización de variables apuntadores 335
8.1Introducción
En este capítulo hablaremos sobre los apuntadores: una de las características más poderosas y desafiantes
de usar del lenguaje de programación C++. Nuestros objetivos aquí son ayudarle a determinar cuándo
es apropiado usar apuntadores y mostrarle cómo usarlos de manera correcta y responsable.
En el capítulo 6, vimos que se pueden utilizar referencias para realizar el paso por referencia. Los
apuntadores también permiten el paso por referencia, y se pueden utilizar para crear y manipular estruc-
turas dinámicas de datos que pueden crecer y reducirse, como las listas enlazadas, colas, pilas y árboles.
En este capítulo explicaremos los conceptos básicos sobre los apuntadores. En el capítulo 19 (en inglés
en el sitio web) presentaremos ejemplos de cómo crear y usar estructuras de datos dinámicas que se
implementan con apuntadores.
También le mostraremos la estrecha relación entre los arreglos integrados y los apuntadores. C++
heredó los arreglos integrados del lenguaje de programación C. Como vimos en el capítulo 7, las clases
array y vector de la Biblioteca estándar de C++ proporcionan implementaciones de los arreglos como
objetos completos; de hecho, tanto array como vector almacenan sus elementos en arreglos integra-
dos. En los proyectos nuevos de desarrollo de software, es mejor usar objetos array y vector en vez de los
arreglos integrados.
De manera similar, C++ en realidad ofrece dos tipos de cadenas: objetos de la clase string (que
hemos estado usando desde el capítulo 3) y cadenas basadas en apuntador, estilo C (cadenas de C). Este
capítulo introduce brevemente las cadenas C para que el lector amplíe su conocimiento sobre los apun-
tadores y los arreglos integrados. Las cadenas de C se utilizaban ampliamente en software antiguo de C
y C++. Hablaremos con detalle sobre ellas en el apéndice F. En los proyectos nuevos de desarrollo de soft-
ware, es mejor usar objetos de la clase string.
En el capítulo 12 examinaremos el uso de los apuntadores con los objetos de clases, en donde vere-
mos que lo que se denomina “procesamiento polimórfico” de la programación orientada a objetos se
lleva a cabo mediante apuntadores y referencias.
8.2Declaraciones e inicialización de variables apuntadores
Indirección
Las variables apuntadores contienen direcciones de memoria como sus valores. Por lo general, una varia-
ble contiene directamente un valor específico. Un apuntador contiene la dirección de memoria de una
variable que, a su vez, contiene un valor específico. En este sentido, el nombre de una variable hace re-
ferencia directa a un valor, y un apuntador hace referencia indirecta a un valor (figura 8.1). Al
8.1 Introducción
8.2 Declaraciones e inicialización de
variables apuntadores
8.3 Operadores de apuntadores
8.4 Paso por referencia mediante
apuntadores
8.5 Arreglos integrados
8.6 Uso de const con apuntadores
8.6.1 Apuntador no constante a datos no
constantes
8.6.2 Apuntador no constante a datos constantes
8.6.3 Apuntador constante a datos no constantes
8.6.4 Apuntador constante a datos constantes
8.7 Operador sizeof
8.8 Expresiones y aritmética de apuntadores
8.9 Relación entre apuntadores y arreglos
integrados
8.10 Cadenas basadas en apuntador
8.11 Conclusión
Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios
| Sección especial: construya su propia computadora
336 Capítulo 8 Apuntadores
proceso de hacer referencia a un valor a través de un apuntador se le conoce comúnmente como indi-
rección. Por lo general, los diagramas representan un apuntador en forma de una flecha que parte de la
variable que contiene una dirección, hasta la variable ubicada en esa dirección de memoria.
7
El apuntador cuentaPtr
hace referencia indirecta a una
variable que contiene el valor 7
cuenta
cuentaPtr
7
cuenta hace referencia directa
a una variable que contiene el valor 7
cuenta
Fig. 8.1  Referencia directa e indirecta a una variable.
Declaración de apuntadores
Al igual que las demás variables, los apuntadores se deben declarar antes de poder usarlos. Por ejemplo,
para el apuntador en la figura 8.1, la declaración
int *cuentaPtr, cuenta;
declara la variable cuentaPtr como de tipo int * (es decir, un apuntador a un valor int) y se lee así
(de derecha a izquierda): “cuentaPtr es un apuntador a un int”. Además, la variable cuenta en la decla-
ración anterior se declara como un int, y no como un apuntador a un int. El * en la declaración se
aplica sólo a cuentaPtr. A cada variable que se declara como apuntador se le debe anteponer un asterisco
(*). Por ejemplo, la declaración
double *xPtr, *yPtr;
indica que tanto xPtr como yPtr son apuntadores a valores double. Cuando el * aparece en una decla-
ración, no es un operador; en vez de ello, indica que la variable que se está declarando es un apuntador.
Los apuntadores se pueden declarar de manera que apunten a objetos de cualquier tipo de datos.
Error común de programación 8.1
Asumirqueel* queseutilizaparadeclararaunapuntadorsedistribuyeatodoslosnombres
de variables en la lista separada por comas de variables de una declaración puede provocar
errores. Cada apuntador se debe declarar con el * antepuesto al nombre (ya sea con o sin un
espacio entre ellos). Si declaramos sólo una variable por cada declaración, evitamos estos
tipos de errores y mejoramos la legibilidad de los programas.
Buena práctica de programación 8.1
Aunque no es un requerimiento, incluir las letras Ptr en los nombres de las variables apunta-
dores deja claro que estas variables son apuntadores, y que deben tratarse de manera acorde.
Inicialización de apuntadores
Los apuntadores se deben inicializar con nullptr (nuevo en C++11) o con una dirección del tipo co-
rrespondiente, ya sea cuando se declaran o en una asignación. Un apuntador con el valor nullptr “no
apunta a nada”, y se conoce como apuntador nulo. De aquí en adelante, cuando hagamos referencia a
un “apuntador nulo” nos estaremos refiriendo a un apuntador con el valor nullptr.
Tip para prevenir errores 8.1
Inicialice todos los apuntadores para evitar que apunten hacia áreas desconocidas o no inicia-
lizadas de la memoria.
8.3 Operadores de apuntadores 337
Apuntadores nulos anteriores a C++11
En versiones anteriores de C++, el valor especificado para un apuntador nulo era 0 o NULL. Este último se
define en varios encabezados de la biblioteca estándar para representar el valor 0. Inicializar un apuntador
a NULL es equivalente a inicializar un apuntador a 0, pero antes de C++11, se utilizaba 0 por convención.
El 0 es el único valor entero que puede asignarse directamente a una variable apuntador sin primero con-
vertir el entero a un tipo apuntador.
8.3Operadores de apuntadores
Operador dirección ()
El operador dirección () es un operador unario que obtiene la dirección de memoria de su operando. Por
ejemplo, teniendo en cuenta las siguientes declaraciones:
int y = 5; // declara la variable y
int *yPtr = nullptr; // declara la variable apuntador yPtr
la instrucción
yPtr = y; // asigna la dirección de y a yPtr
asigna la dirección de la variable y a la variable apuntador yPtr. Entonces, se dice que la variable yPtr
“apunta a” y. Ahora, yPtr hace referencia indirecta al valor de la variable y. Observe que el uso del signo
 en la instrucción anterior no es el mismo que el uso del  en la declaración de una variable de referencia,
a la cual siempre se le antepone el nombre de un tipo de datos. Al declarar una referencia, el  forma
parte del tipo. En una expresión como y, el  es el operador dirección.
La figura 8.2 muestra una representación de la memoria después de la asignación anterior. La
“relación de señalamiento” se indica mediante el dibujo de una flecha desde el cuadro que representa al
apuntador yPtr en la memoria, hasta el cuadro que representa a la variable y en la memoria.
La figura 8.3 muestra otra representación del apuntador en memoria, con la variable entera y alma-
cenada en la ubicación de memoria 600000 y la variable apuntador yPtr almacenada en la ubicación de
memoria 500000. El operando del operador dirección debe ser un lvalue; el operador dirección no se
puede aplicar a constantes o expresiones que produzcan valores temporales (como los resultados de los
cálculos).
5
y
yPtr
Fig. 8.2  Representación gráfica de un apuntador que apunta a una variable en memoria.
5
y
600000
ubicación
500000
yPtr
ubicación
600000
Fig. 8.3  Representación de y y yPtr en memoria.
Operador de indirección (*)
El operador * unario, que se conoce comúnmente como el operador de indirección u operador de
desreferencia, devuelve un lvalue que representa al objeto al que apunta su operando apuntador. Por ejem-
plo (haciendo referencia otra vez a la figura 8.2), la instrucción
338 Capítulo 8 Apuntadores
cout  *yPtr  endl;
imprime el valor de la variable y (en este caso, 5), al igual que la instrucción.
cout  y  endl;
Alprocesodeutilizarel* deestamanera,seleconocecomodesreferenciarun apuntador.Unapuntador
desreferenciado también se puede usar en el lado izquierdo de una instrucción de asignación, como en
*yPtr = 9;
lo cual asignaría 9 a y en la figura 8.3. El apuntador desreferenciado también se puede utilizar para recibir
un valor de entrada, como en
ci
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf
Cómo programar C++, 9na Edición - Paul Deitel.pdf

Más contenido relacionado

DOCX
Ejercicios de diagramas de flujo en raptor
PPSX
Lenguaje c diapositivas
PDF
Programacion en c de byron
PDF
Programación orientada a objetos con C++ by Ceballos Sierra, Javier (z-lib.or...
DOC
Ejemplos de algoritmos en C básicos (aprendiendo a programar)
PDF
Arreglos o dimensiones en pseint
PDF
EJERCICIOS DE ALGORITMOS RESUELTOS
Ejercicios de diagramas de flujo en raptor
Lenguaje c diapositivas
Programacion en c de byron
Programación orientada a objetos con C++ by Ceballos Sierra, Javier (z-lib.or...
Ejemplos de algoritmos en C básicos (aprendiendo a programar)
Arreglos o dimensiones en pseint
EJERCICIOS DE ALGORITMOS RESUELTOS

La actualidad más candente (20)

PPTX
Bases de datos1_2015
PPS
Identificadores, variables y constantes
PDF
11 estructuras de repeticion-tema11
PPTX
ODP
01 el lenguaje Python
PDF
geometría plana calvache
PPTX
Tutorial de CodeBlocks
PDF
ejemplos de pruebas unitarias y de integracion
PDF
Conexión Mysql -Prolog
ODP
Extreme Programming-Fases
PDF
3.4. Logica de predicados
PDF
Practicas prolog
PPTX
Tipos de datos en power designer
ODT
Fecha y hora cmd
PDF
Cap13 matrices
DOCX
taller de la espol sobre la primera unidad
PPTX
Introducción a Python
PDF
Calidad de software Unidad 3
PDF
Presentacion sistema binario
PDF
P. estructurada vs. programación orientada a objetos
Bases de datos1_2015
Identificadores, variables y constantes
11 estructuras de repeticion-tema11
01 el lenguaje Python
geometría plana calvache
Tutorial de CodeBlocks
ejemplos de pruebas unitarias y de integracion
Conexión Mysql -Prolog
Extreme Programming-Fases
3.4. Logica de predicados
Practicas prolog
Tipos de datos en power designer
Fecha y hora cmd
Cap13 matrices
taller de la espol sobre la primera unidad
Introducción a Python
Calidad de software Unidad 3
Presentacion sistema binario
P. estructurada vs. programación orientada a objetos
Publicidad

Similar a Cómo programar C++, 9na Edición - Paul Deitel.pdf (20)

PDF
C++ Como Programar - Deitel 6edi.pdf
PDF
pensar_en_cpp-vol1.pdf
PDF
Pensar en cpp
PDF
Como programar en java 9na completo
PDF
El C++ por la práctica, introducción al lenguaje y su filosofía
DOCX
Como programar en java
PDF
Metodologia orientada a objeto - libro
DOCX
DOCX
PDF
Como programar java, 9na edicion deitel
PDF
Como programar java, 9na edicion deitel
DOCX
Fci 171 introducción a la programación en lenguaje c.
DOCX
Fci 171 Introducción a la programación en lenguaje c.
DOCX
Fci 171 Introducción a la programación en lenguaje c.
PDF
Programación: El C++ por la practica introducción al lenguaje y su filosofia
PDF
Como programar en Java - 7ma Edicion - P. J. Deitel.pdf
PDF
Como programar en Java - 7ma Edicion - P. J. Deitel (1).pdf
PDF
Como programar en Java - 7ma Edicion - P. J. Deitel.pdf
PPTX
c++ introduccion
PDF
Libro programacion orientada_a_objetos_p
C++ Como Programar - Deitel 6edi.pdf
pensar_en_cpp-vol1.pdf
Pensar en cpp
Como programar en java 9na completo
El C++ por la práctica, introducción al lenguaje y su filosofía
Como programar en java
Metodologia orientada a objeto - libro
Como programar java, 9na edicion deitel
Como programar java, 9na edicion deitel
Fci 171 introducción a la programación en lenguaje c.
Fci 171 Introducción a la programación en lenguaje c.
Fci 171 Introducción a la programación en lenguaje c.
Programación: El C++ por la practica introducción al lenguaje y su filosofia
Como programar en Java - 7ma Edicion - P. J. Deitel.pdf
Como programar en Java - 7ma Edicion - P. J. Deitel (1).pdf
Como programar en Java - 7ma Edicion - P. J. Deitel.pdf
c++ introduccion
Libro programacion orientada_a_objetos_p
Publicidad

Último (20)

PDF
GUÍA PARA LA IMPLEMENTACIÓN DEL PLAN PARA LA REDUCCIÓN DEL RIESGO DE DESASTRES
DOCX
Cumplimiento normativo y realidad laboral
PPTX
Contexto Normativo NSR10, presentacion 2025
PDF
1132-2018 espectrofotometro uv visible.pdf
PPTX
1 CONTAMINACION AMBIENTAL EN EL PLANETA.pptx
PDF
Sustitucion_del_maiz_por_harina_integral_de_zapall.pdf
PDF
Informe Estudio Final Apagon del 25 de febrero
PDF
5 Presentación de PowerPointGENERACIÓN DESECHOS UIS 18-02-2023 (1).pdf
PPTX
Notificacion e investigación de incidentes y accidentes de trabajo.pptx
PDF
NORMATIVA Y DESCRIPCION ALCANTARILLADO PLUVIAL.pdf
PDF
Copia de Presentación Propuesta de Marketing Corporativo Blanco y Negro.pdf
PPTX
clase MICROCONTROLADORES ago-dic 2019.pptx
PPTX
Cortinas-en-Presas-de-Gravedad-Vertedoras-y-No-Vertedoras.pptx
PPTX
CAPACITACIÓN DE USO ADECUADO DE EPP.pptx
PPT
PRIMEROS AUXILIOS EN EL SECTOR EMPRESARIAL
PDF
fulguracion-medicina-legal-418035-downloable-2634665.pdf lesiones por descarg...
DOCX
CONCEPTOS BASICOS DE LA PROGRAMACION STEP
PDF
FIJA NUEVO TEXTO DE LA ORDENANZA GENERAL DE LA LEY GENERAL DE URBANISMO Y CON...
PPTX
GEOLOGIA, principios , fundamentos y conceptos
PDF
SEC formula cargos al Consejo Directivo del Coordinador y a ocho eléctricas p...
GUÍA PARA LA IMPLEMENTACIÓN DEL PLAN PARA LA REDUCCIÓN DEL RIESGO DE DESASTRES
Cumplimiento normativo y realidad laboral
Contexto Normativo NSR10, presentacion 2025
1132-2018 espectrofotometro uv visible.pdf
1 CONTAMINACION AMBIENTAL EN EL PLANETA.pptx
Sustitucion_del_maiz_por_harina_integral_de_zapall.pdf
Informe Estudio Final Apagon del 25 de febrero
5 Presentación de PowerPointGENERACIÓN DESECHOS UIS 18-02-2023 (1).pdf
Notificacion e investigación de incidentes y accidentes de trabajo.pptx
NORMATIVA Y DESCRIPCION ALCANTARILLADO PLUVIAL.pdf
Copia de Presentación Propuesta de Marketing Corporativo Blanco y Negro.pdf
clase MICROCONTROLADORES ago-dic 2019.pptx
Cortinas-en-Presas-de-Gravedad-Vertedoras-y-No-Vertedoras.pptx
CAPACITACIÓN DE USO ADECUADO DE EPP.pptx
PRIMEROS AUXILIOS EN EL SECTOR EMPRESARIAL
fulguracion-medicina-legal-418035-downloable-2634665.pdf lesiones por descarg...
CONCEPTOS BASICOS DE LA PROGRAMACION STEP
FIJA NUEVO TEXTO DE LA ORDENANZA GENERAL DE LA LEY GENERAL DE URBANISMO Y CON...
GEOLOGIA, principios , fundamentos y conceptos
SEC formula cargos al Consejo Directivo del Coordinador y a ocho eléctricas p...

Cómo programar C++, 9na Edición - Paul Deitel.pdf

  • 1. Visítenos en: www.pearsonenespañol.com ISBN 978-607-32-2739-1 Una introducción completa y autorizada del código activo DEITEL® a C++, la programación orientada a objetos (POO) y el diseño orientado a objetos (DOO) con UML™ Bienvenido a la programación con C++, uno de los lenguajes de programación orientada a objetos más populares. Este libro utiliza las tecnologías de computación de vanguardia, y su base es el reconocido método de código activo de Deitel, donde los conceptos se presentan en el contexto de progra- mas funcionales completos, seguidos de ejecuciones de ejemplo, en lugar de hacerlo a través de fragmentos separados de código. A lo largo del texto encontrará recuadros con tips de programación, para prevenir errores, y de rendimiento, así como buenas prácticas de programación, que resultan de gran utilidad. Estándar C++11 Entre las características clave del nuevo estándar C++11 que se presenta en esta nueva edición destacan las siguientes: • Cumple con el nuevo estándar C++11. Con una extensa cobertura de las nue- vas características de C++11. • Apuntadores inteligentes. Los apuntadores inteligentes ayudan a evitar los errores de administración de memoria dinámica al proveer una funcionalidad adicional a la de los apuntadores integrados. • Cobertura anticipada de los contenedores, iteradores y algoritmos de la Biblioteca Estándar, mejorada con capacidades de C++11. La gran mayo- ría de las necesidades de los programadores en cuanto a estructuras de datos pueden satisfacerse mediante la reutilización de las capacidades de la Biblioteca Estándar. Este libro se adhiere al estándar de codificación segura en C++ de CERT. Para mayor información visite: www.pearsonenespañol.com/deitel NOVENA EDICIÓN NOVENA EDICIÓN
  • 2. ACCESO A LOS CAPÍTULOS ADICIONALES DEL LIBRO Para acceder a los capítulos 14 a 17 (en español) y 18 a 23 (en inglés) mencionados en el texto, visite el sitio web del libro: www.pearsonenespañol.com/deitel Utilice una moneda para descubrir el código de acceso. (No use objetos filosos porque podría dañarlo). Deitel / Cómo programar en C++ IMPORTANTE: ¡Este código de acceso tiene vigencia de 2 días! Asegúrese que el código no aparezca dañado ya que sólo puede usarse una vez y no será reemplazado en ningún caso.
  • 3. novena edición CÓMO PROGRAMAR Paul Deitel Deitel & Associates, Inc. Harvey Deitel Deitel & Associates, Inc. Traducción Alfonso Vidal Romero Elizondo Ingeniero en Sistemas Electrónicos Tecnológico de Monterrey, Campus Monterrey Revisión técnica Sergio Fuenlabrada Velázquez Edna Martha Miranda Chávez José Luis López Goytia Judith Sonck Ledezma Mario Alberto Sesma Martínez Mario Oviedo Galdeano Oskar Armando Gómez Coronel Academia de Computación Unidad Profesional Interdisciplinaria de Ingeniería y Ciencias Sociales y Administrativas (UPIICSA) Instituto Politécnico Nacional, México Sandra Luz Gómez Coronel Academia de Electrónica Unidad Profesional Interdisciplinaria de Ingeniería y Tecnologías Avanzadas (UPIITA) Instituto Politécnico Nacional, México
  • 4. Authorized translation from the English language edition entitled C++ HOW TO PROGRAM, 9th edition, by (HARVEY & PAUL) DEITEL & ASSOCIATES; HARVEY DEITEL, published by Pearson Education, Inc., publishing as Pearson, Copyright © 2014. All rights reserved. ISBN 9780133378719 Traducción autorizada de la edición en idioma inglés titulada C++ HOW TO PROGRAM, 9ª edición, por (HARVEY & PAUL) DEITEL & ASSOCIATES; HARVEY DEITEL, publicada por Pearson Education, Inc., publicada como Pearson, Copyright © 2014. Todos los derechos reservados. Esta edición en español es la única autorizada. Edición en español Dirección General: Philip de la Vega Dirección Educación Superior: Santiago Gutiérrez Editor Sponsor: Luis M. Cruz Castillo luis.cruz@pearson.com Editor de Desarrollo: Bernardino Gutiérrez Hernández Supervisor de Producción: Enrique Trejo Hernández Gerencia Editorial Educación Superior: Marisa de Anta Novena edición, 2014 D.R. © 2014 por Pearson Educación de México, S.A. de C.V. Atlacomulco 500-5o. piso Col. Industrial Atoto 53519, Naucalpan de Juárez, Estado de México Cámara Nacional de la Industria Editorial Mexicana. Reg. núm. 1031. Reservados todos los derechos. Ni la totalidad ni parte de esta publicación pueden reproducirse, registrarse o transmitirse, por un sistema de recuperación de información, en ninguna forma ni por ningún medio, sea electrónico, mecánico, foto- químico, magnético o electroóptico, por fotocopia, grabación o cualquier otro, sin permiso previo por escrito del editor. El préstamo, alquiler o cualquier otra forma de cesión de uso de este ejemplar requerirá también la autorización del editor o de sus representantes. ISBN 978-607-32-2739-1 ISBN e-book 978-607-32-2740-7 ISBN e-chapter 978-607-32-2741-4 Impreso en México. Printed in Mexico. 1 2 3 4 5 6 7 8 9 0 - 17 16 15 14 'DWRVGHFDWDORJDFLyQELEOLRJUiILFD 3iJLQDV 3($5621('8$,Ð10p[LFR ,6%1 )RUPDWR×FP yPRSURJUDPDUHQ 1RYHQDHGLFLyQ DEITEL, PAUL y HARVEY DEITEL www.pearsonenespañol.com ISBN 978-607-32-2739-1
  • 5. En memoria de Dennis Ritchie, creador del lenguaje de programación C, uno de los lenguajes clave que inspiraron a C++. Paul y Harvey Deitel
  • 6. Marcas registradas Deitel, el insecto con los dos pulgares hacia arriba y Dive Into son marcas registradas de Deitel Associates, Inc. Carnegie Mellon Software Engineering InstituteTM es marca registrada de Carnegie Mellon University. CERT® está registado en la U.S. Patent and Trademark Office por Carnegie Mellon University. Microsoft® y Windows® son marcas registradas de Microsoft Corporation en E.U.A. y otros países. Las capturas de pantalla y los iconos son reproducidos con permiso de Microsoft Corporation. Este libro no es patrocinado ni respaldado ni está afiliado a Microsoft Corporation. UNIX es una marca registrada de The Open Group. A lo largo de este libro se utilizan marcas. En lugar de poner un símbolo de marca registrada en cada ocurrencia de un nombre de esa marca registrada, afirmamos que estamos usando los nombres únicamente de forma editorial y para beneficio del propietario de la marca comercial, sin ninguna intención de infracción de derechos de la marca registrada.
  • 7. Prefacio xvii 1 Introducción a las computadoras y a C++ 1 1.1 Introducción 2 1.2 Las computadoras e Internet en la industria y la investigación 3 1.3 Hardware y software 5 1.3.1 La Ley de Moore 6 1.3.2 Organización de la computadora 6 1.4 Jerarquía de datos 7 1.5 Lenguajes máquina, lenguajes ensambladores y lenguajes de alto nivel 9 1.6 C++ 10 1.7 Lenguajes de programación 11 1.8 Introducción a la tecnología de objetos 14 1.9 Entorno de desarrollo común en C++ 17 1.10 Prueba de una aplicación de C++ 19 1.11 Sistemas operativos 25 1.11.1 Windows: un sistema operativo propietario 25 1.11.2 Linux: un sistema operativo de código fuente abierto 26 1.11.3 OS X de Apple: iOS de Apple para dispositivos iPhone® , iPad® y iPod Touch® 26 1.11.4 Android de Google 27 1.12 Internet y World Wide Web 27 1.13 Cierta terminología clave de desarrollo de software 29 1.14 C++11 y las bibliotecas Boost de código fuente abierto 31 1.15 Mantenerse actualizado con las tecnologías de la información 32 1.16 Recursos Web 33 2 Introducción a la programación en C++, entrada/salida y operadores 38 2.1 Introducción 39 2.2 Su primer programa en C++: imprimir una línea de texto 39 2.3 Modificación de nuestro primer programa en C++ 43 2.4 Otro programa en C++: suma de enteros 44 2.5 Conceptos acerca de la memoria 48 Contenido
  • 8. vi Contenido 2.6 Aritmética 49 2.7 Toma de decisiones: operadores de igualdad y relacionales 53 2.8 Conclusión 57 3 Introducción a las clases, objetos y cadenas 66 3.1 Introducción 67 3.2 Definición de una clase con una función miembro 67 3.3 Definición de una función miembro con un parámetro 70 3.4 Datos miembros, funciones establecer y obtener 74 3.5 Inicialización de objetos mediante constructores 79 3.6 Colocar una clase en un archivo separado para fines de reutilización 83 3.7 Separar la interfaz de la implementación 87 3.8 Validación de datos mediante funciones establecer 92 3.9 Conclusión 97 4 Instrucciones de control, parte I: operadores de asignación, ++ y -- 104 4.1 Introducción 105 4.2 Algoritmos 105 4.3 Seudocódigo 106 4.4 Estructuras de control 107 4.5 Instrucción de selección if 110 4.6 Instrucción de selección doble if...else 112 4.7 Instrucción de repetición while 116 4.8 Cómo formular algoritmos: repetición controlada por un contador 118 4.9 Cómo formular algoritmos: repetición controlada por un centinela 124 4.10 Cómo formular algoritmos: instrucciones de control anidadas 134 4.11 Operadores de asignación 139 4.12 Operadores de incremento y decremento 140 4.13 Conclusión 143 5 Instrucciones de control, parte 2: operadores lógicos 157 5.1 Introducción 158 5.2 Fundamentos de la repetición controlada por un contador 158 5.3 Instrucción de repetición for 159 5.4 Ejemplos acerca del uso de la instrucción for 163 5.5 Instrucción de repetición do...while 168 5.6 Instrucción de selección múltiple switch 169 5.7 Instrucciones break y continue 178 5.8 Operadores lógicos 180 5.9 Confusión entre los operadores de igualdad (==) y de asignación (=) 185 5.10 Resumen de programación estructurada 186 5.11 Conclusión 191
  • 9. Contenido vii 6 Funciones y una introducción a la recursividad 201 6.1 Introducción 202 6.2 Componentes de los programas en C++ 203 6.3 Funciones matemáticas de la biblioteca 204 6.4 Definiciones de funciones con varios parámetros 205 6.5 Prototipos de funciones y coerción de argumentos 210 6.6 Encabezados de la Biblioteca estándar de C++ 212 6.7 Caso de estudio: generación de números aleatorios 214 6.8 Caso de estudio: juego de probabilidad; introducción a enum 219 6.9 Números aleatorios de C++11 224 6.10 Clases y duración de almacenamiento 225 6.11 Reglas de alcance 228 6.12 La pila de llamadas a funciones y los registros de activación 231 6.13 Funciones con listas de parámetros vacías 235 6.14 Funciones en línea 236 6.15 Referencias y parámetros de referencias 237 6.16 Argumentos predeterminados 240 6.17 Operador de resolución de ámbito unario 242 6.18 Sobrecarga de funciones 243 6.19 Plantillas de funciones 246 6.20 Recursividad 248 6.21 Ejemplo sobre el uso de la recursividad: serie de Fibonacci 252 6.22 Comparación entre recursividad e iteración 255 6.23 Conclusión 258 7 Plantillas de clase array y vector; cómo atrapar excepciones 278 7.1 Introducción 279 7.2 Arreglos 279 7.3 Declaración de arreglos 281 7.4 Ejemplos acerca del uso de los arreglos 281 7.4.1 Declaración de un arreglo y uso de un ciclo para inicializar los elementos del arreglo 281 7.4.2 Inicialización de un arreglo en una declaración mediante una lista inicializadora 282 7.4.3 Especificación del tamaño de un arreglo con una variable constante y establecimiento de los elementos de un arreglo con cálculos 283 7.4.4 Suma de los elementos de un arreglo 286 7.4.5 Uso de gráficos de barra para mostrar los datos de un arreglo en forma gráfica 286 7.4.6 Uso de los elementos de un arreglo como contadores 288 7.4.7 Uso de arreglos para sintetizar los resultados de una encuesta 289 7.4.8 Arreglos locales estáticos y arreglos locales automáticos 291
  • 10. viii Contenido 7.5 Instrucción for basada en rango 293 7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para almacenar las calificaciones 295 7.7 Búsqueda y ordenamiento de datos en arreglos 302 7.8 Arreglos multidimensionales 304 7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional 307 7.10 Introducción a la plantilla de clase vector de la Biblioteca estándar de C++ 314 7.11 Conclusión 320 8 Apuntadores 334 8.1 Introducción 335 8.2 Declaraciones e inicialización de variables apuntadores 335 8.3 Operadores de apuntadores 337 8.4 Paso por referencia mediante apuntadores 339 8.5 Arreglos integrados 344 8.6 Uso de const con apuntadores 346 8.6.1 Apuntador no constante a datos no constantes 347 8.6.2 Apuntador no constante a datos constantes 347 8.6.3 Apuntador constante a datos no constantes 348 8.6.4 Apuntador constante a datos constantes 349 8.7 Operador sizeof 350 8.8 Expresiones y aritmética de apuntadores 353 8.9 Relación entre apuntadores y arreglos integrados 355 8.10 Cadenas basadas en apuntador 358 8.11 Conclusión 361 9 Clases, un análisis más detallado: lanzar excepciones 377 9.1 Introducción 378 9.2 Caso de estudio con la clase Tiempo 379 9.3 Alcance de las clases y acceso a los miembros de una clase 385 9.4 Funciones de acceso y funciones utilitarias 386 9.5 Caso de estudio de la clase Tiempo: constructores con argumentos predeterminados 387 9.6 Destructores 393 9.7 Cuándo se hacen llamadas a los constructores y destructores 393 9.8 Caso de estudio con la clase Tiempo: una trampa sutil (devolver una referencia o un apuntador a un miembro de datos private) 397 9.9 Asignación predeterminada a nivel de miembros 400 9.10 Objetos const y funciones miembro const 402 9.11 Composición: objetos como miembros de clases 404 9.12 Funciones friend y clases friend 410
  • 11. Contenido ix 9.13 Uso del apuntador this 412 9.14 Miembros de clase static 418 9.15 Conclusión 423 10 Sobrecarga de operadores: la clase string 433 10.1 Introducción 434 10.2 Uso de los operadores sobrecargados de la clase string de la Biblioteca estándar 435 10.3 Fundamentos de la sobrecarga de operadores 438 10.4 Sobrecarga de operadores binarios 439 10.5 Sobrecarga de los operadores binarios de inserción de flujo y extracción de flujo 440 10.6 Sobrecarga de operadores unarios 444 10.7 Sobrecarga de los operadores unarios prefijo y postfijo ++ y -- 445 10.8 Caso de estudio: una clase Fecha 446 10.9 Administración dinámica de memoria 451 10.10 Caso de estudio: la clase Array 453 10.10.1 Uso de la clase Array 454 10.10.2 Definición de la clase Array 458 10.11 Comparación entre los operadores como funciones miembro y no miembro 466 10.12 Conversión entre tipos 466 10.13 Constructores explicit y operadores de conversión 468 10.14 Sobrecarga del operador () de llamada a función 470 10.15 Conclusión 471 11 Programación orientada a objetos: herencia 482 11.1 Introducción 483 11.2 Clases base y clases derivadas 483 11.3 Relación entre las clases base y las clases derivadas 486 11.3.1 Creación y uso de una clase EmpleadoPorComision 486 11.3.2 Creación de una clase EmpleadoBaseMasComision sin usar la herencia 491 11.3.3 Creación de una jerarquía de herencia EmpleadoPorComision-EmpleadoBaseMasComision 497 11.3.4 La jerarquía de herencia EmpleadoPorComision- EmpleadoBaseMasComision mediante el uso de datos protected 501 11.3.5 La jerarquía de herencia EmpleadoPorComision- EmpleadoBaseMasComision mediante el uso de datos private 504 11.4 Constructores y destructores en clases derivadas 509 11.5 Herencia public, protected y private 511 11.6 Ingeniería de software mediante la herencia 512 11.7 Repaso 512
  • 12. x Contenido 12 Programación orientada a objetos: polimorfismo 517 12.1 Introducción 518 12.2 Introducción al polimorfismo: videojuego polimórfico 519 12.3 Relaciones entre los objetos en una jerarquía de herencia 519 12.3.1 Invocación de funciones de la clase base desde objetos de una clase derivada 520 12.3.2 Cómo orientar los apuntadores de una clase derivada a objetos de la clase base 523 12.3.3 Llamadas a funciones miembro de una clase derivada a través de apuntadores de la clase base 524 12.3.4 Funciones y destructores virtuales 526 12.4 Tipos de campos e instrucciones switch 533 12.5 Clases abstractas y funciones virtual puras 533 12.6 Caso de estudio: sistema de nómina mediante el uso de polimorfismo 535 12.6.1 Creación de la clase base abstracta Empleado 536 12.6.2 Creación de la clase derivada concreta EmpleadoAsalariado 540 12.6.3 Creación de la clase derivada concreta EmpleadoPorComision 542 12.6.4 Creación de la clase derivada concreta indirecta EmpleadoBaseMasComision 544 12.6.5 Demostración del procesamiento polimórfico 546 12.7 (Opcional) Polimorfismo, funciones virtuales y vinculación dinámica “detrás de las cámaras” 550 12.8 Caso de estudio: sistema de nómina mediante el uso de polimorfismo e información de tipos en tiempo de ejecución con conversión descendente, dynamic_cast, typeid y type_info 553 12.9 Conclusión 557 13 Entrada/salida de flujos: un análisis detallado 562 13.1 Introducción 563 13.2 Flujos 564 13.2.1 Comparación entre flujos clásicos y flujos estándar 564 13.2.2 Encabezados de la biblioteca iostream 565 13.2.3 Clases y objetos de entrada/salida de flujos 565 13.3 Salida de flujos 567 13.3.1 Salida de variables char * 568 13.3.2 Salida de caracteres mediante la función miembro put 568 13.4 Entrada de flujos 569 13.4.1 Funciones miembro get y getline 569 13.4.2 Funciones miembro peek, putback e ignore de istream 572 13.4.3 E/S con seguridad de tipos 572 13.5 E/S sin formato mediante el uso de read, write y gcount 572 13.6 Introducción a los manipuladores de flujos 573 13.6.1 Base de flujos integrales: dec, oct, hex y setbase 574
  • 13. Contenido xi 13.6.2 Precisión de punto flotante (precision, setprecision) 574 13.6.3 Anchura de campos (width, setw) 576 13.6.4 Manipuladores de flujos de salida definidos por el usuario 577 13.7 Estados de formato de flujos y manipuladores de flujos 578 13.7.1 Ceros a la derecha y puntos decimales (showpoint) 579 13.7.2 Justificación (left, right e internal) 580 13.7.3 Relleno de caracteres (fill, setfill) 582 13.7.4 Base de flujos integrales (dec, oct, hex, showbase) 583 13.7.5 Números de punto flotante; notación científica y fija (scientific, fixed) 584 13.7.6 Control de mayúsculas/minúsculas (uppercase) 585 13.7.7 Especificación de formato booleano (boolalpha) 585 13.7.8 Establecer y restablecer el estado de formato mediante la función miembro flags 586 13.8 Estados de error de los flujos 587 13.9 Enlazar un flujo de salida a un flujo de entrada 590 13.10 Conclusión 590 14 Procesamiento de archivos 599 14.1 Introducción 600 14.2 Archivos y flujos 600 14.3 Creación de un archivo secuencial 601 14.4 Cómo leer datos de un archivo secuencial 605 14.5 Actualización de archivos secuenciales 611 14.6 Archivos de acceso aleatorio 611 14.7 Creación de un archivo de acceso aleatorio 612 14.8 Cómo escribir datos al azar a un archivo de acceso aleatorio 617 14.9 Cómo leer de un archivo de acceso aleatorio en forma secuencial 619 14.10 Caso de estudio: un programa para procesar transacciones 621 14.11 Serialización de objetos 628 14.12 Conclusión 628 15 Contenedores e iteradores de la biblioteca estándar 638 15.1 Introducción 639 15.2 Introducción a los contenedores 640 15.3 Introducción a los iteradores 644 15.4 Introducción a los algoritmos 649 15.5 Contenedores de secuencia 649 15.5.1 Contenedor de secuencia vector 650 15.5.2 Contenedor de secuencia list 658 15.5.3 Contenedor de secuencia deque 662 Los capítulos 15, 16 y 17 se encuentran, en español en el sitio web del libro
  • 14. xii Contenido 15.6 Contenedores asociativos 664 15.6.1 Contenedor asociativo multiset 665 15.6.2 Contenedor asociativo set 668 15.6.3 Contenedor asociativo multimap 669 15.6.4 Contenedor asociativo map 671 15.7 Adaptadores de contenedores 673 15.7.1 Adaptador stack 673 15.7.2 Adaptador queue 675 15.7.3 Adaptador priority_queue 676 15.8 La clase bitset 677 15.9 Conclusión 679 16 Algoritmos de la biblioteca estándar 690 16.1 Introducción 691 16.2 Requisitos mínimos para los iteradores 691 16.3 Algoritmos 693 16.3.1 fill, fill_n, generate y generate_n 693 16.3.2 equal, mismatch y lexicographical_compare 695 16.3.3 remove, remove_if, remove_copy y remove_copy_if 697 16.3.4 replace, replace_if, replace_copy y replace_copy_if 700 16.3.5 Algoritmos matemáticos 702 16.3.6 Algoritmos básicos de búsqueda y ordenamiento 706 16.3.7 swap, iter_swap y swap_ranges 710 16.3.8 copy_backward, merge, unique y reverse 711 16.3.9 inplace_merge, unique_copy y reverse_copy 714 16.3.10 Operaciones establecer (set) 716 16.3.11 lower_bound, upper_bound y equal_range 719 16.3.12 Ordenamiento de montón (heapsort) 721 16.3.13 min, max, minmax y minmax_element 724 16.4 Objetos de funciones 726 16.5 Expresiones lambda 729 16.6 Resumen de algoritmos de la Biblioteca estándar 730 16.7 Conclusión 732 17 Manejo de excepciones: un análisis más detallado 740 17.1 Introducción 741 17.2 Ejemplo: manejo de un intento de dividir entre cero 741 17.3 Volver a lanzar una excepción 747 17.4 Limpieza de la pila 748 17.5 Cuándo utilizar el manejo de excepciones 750 17.6 Constructores, destructores y manejo de excepciones 751 17.7 Excepciones y herencia 752 17.8 Procesamiento de las fallas de new 752
  • 15. Contenido xiii 17.9 La clase unique_ptr y la asignación dinámica de memoria 755 17.10 Jerarquía de excepciones de la biblioteca estándar 758 17.11 Conclusión 759 Los capítulos 18, 19, 20, 21, 22 y 23 se encuentran, en inglés en el sitio web del libro 18 Introduction to Custom Templates 765 18.1 Introduction 766 18.2 Class Templates 766 18.3 Function Template to Manipulate a Class-Template Specialization Object 771 18.4 Nontype Parameters 773 18.5 Default Arguments for Template Type Parameters 773 18.6 Overloading Function Templates 774 18.7 Wrap-Up 774 19 Custom Templatized Data Structures 777 19.1 Introduction 778 19.2 Self-Referential Classes 779 19.3 Linked Lists 780 19.4 Stacks 794 19.5 Queues 799 19.6 Trees 803 19.7 Wrap-Up 811 20 Searching and Sorting 822 20.1 Introduction 823 20.2 Searching Algorithms 824 20.2.1 Linear Search 824 20.2.2 Binary Search 827 20.3 Sorting Algorithms 831 20.3.1 Insertion Sort 832 20.3.2 Selection Sort 834 20.3.3 Merge Sort (A Recursive Implementation) 837 20.4 Wrap-Up 843 21 Class string and String Stream Processing: A Deeper Look 849 21.1 Introduction 850 21.2 string Assignment and Concatenation 851
  • 16. xiv Contenido 21.3 Comparing strings 853 21.4 Substrings 856 21.5 Swapping strings 856 21.6 string Characteristics 857 21.7 Finding Substrings and Characters in a string 859 21.8 Replacing Characters in a string 861 21.9 Inserting Characters into a string 863 21.10 Conversion to Pointer-Based char * Strings 864 21.11 Iterators 865 21.12 String Stream Processing 867 21.13 C++11 Numeric Conversion Functions 870 21.14 Wrap-Up 871 22 Bits, Characters, C Strings and structs 879 22.1 Introduction 880 22.2 Structure Definitions 880 22.3 typedef 882 22.4 Example: Card Shuffling and Dealing Simulation 882 22.5 Bitwise Operators 885 22.6 Bit Fields 894 22.7 Character-Handling Library 897 22.8 C String-Manipulation Functions 903 22.9 C String-Conversion Functions 910 22.10 Search Functions of the C String-Handling Library 915 22.11 Memory Functions of the C String-Handling Library 919 22.12 Wrap-Up 923 23 Other Topics 938 23.1 Introduction 939 23.2 const_cast Operator 939 23.3 mutable Class Members 941 23.4 namespaces 943 23.5 Operator Keywords 946 23.6 Pointers to Class Members (.* and -*) 948 23.7 Multiple Inheritance 950 23.8 Multiple Inheritance and virtual Base Classes 955 23.9 Wrap-Up 959
  • 17. Contenido xv A Precedencia y asociatividad de operadores 965 B Conjunto de caracteres ASCII 967 C Tipos fundamentales 968 D Sistemas numéricos 970 D.1 Introducción 971 D.2 Abreviatura de los números binarios como números octales y hexadecimales 974 D.3 Conversión de números octales y hexadecimales a binarios 975 D.4 Conversión de un número binario, octal o hexadecimal a decimal 975 D.5 Conversión de un número decimal a binario, octal o hexadecimal 976 D.6 Números binarios negativos: notación de complemento a dos 978 E Preprocesador 983 E.1 Introducción 984 E.2 La directiva de preprocesamiento #include 984 E.3 La directiva de preprocesamiento #define: constantes simbólicas 985 E.4 La directiva de preprocesamiento #define: macros 985 E.5 Compilación condicional 987 E.6 Las directivas de preprocesamiento #error y #pragma 988 E.7 Los operadores # y ## 989 E.8 Constantes simbólicas predefinidas 989 E.9 Aserciones 990 E.10 Conclusión 990 Índice 995
  • 19. “El principal mérito del lenguaje es la claridad…” —Galen Bienvenido al lenguaje de programación C++ y a Cómo programar en C++, novena edición. Este libro, que presenta las tecnologías de computación de vanguardia, es apropiado para secuencias de cursos in- troductorios basados en las recomendaciones curriculares de dos organizaciones profesionales clave: la ACM y el IEEE. La base del libro es el reconocido método de código activo de Deitel: los conceptos se presentan en el contexto de programas funcionales completos seguidos de ejecuciones de ejemplo, en lugar de hacerlo a través de fragmentos separados de código. En la sección Antes de empezar (vea el sitio web de este libro) encontrará información para configurar su computadora basada en Linux, Windows o Apple OS X para ejecutar los ejemplos de código.Todo el código fuente está disponible en el sitio web de este libro (si requiere los códigos en inglés, puede obtenerlos de: www.deitel.com/books/cpphtp9 y en www.pearsonhighered.com/deitel). Use el código fuente que le proporcionamos para ejecutar cada programa a medida que lo vaya estudiando. Creemos que este libro y sus materiales de apoyo le brindarán una introducción informativa, desafiante y entretenida a C++. Si surge alguna duda o pregunta a medida que lea este libro, puede comunicarse con nosotros en deitel@deitel.com; le responderemos a la brevedad. Síganos en Face- book (www deitel.com/deitelfan) y Twitter (@deitel); también puede suscribirse al boletín de correo electrónico Deitel® Buzz Online (www.deitel.com/newsletter/subscribe.html). Estándar C++ 11 El nuevo estándar C++11 que se publicó en 2011 nos motivó a escribir esta nueva edición de Cómo programar en C++. A lo largo del libro, cada una de las nuevas características de C++11 se identifica con el icono “11” como se muestra aquí en el margen. A continuación le presentamos algunas de las carac- terísticas clave de C++11 en esta nueva edición: • CumpleconelnuevoestándarC++11.ExtensacoberturadelasnuevascaracterísticasdeC++11 (figura 1). • El código se probó de manera exhaustiva en tres compiladores de C++ populares a nivel indus- trial. Probamos los ejemplos de código en GNU™ C++ 4.7, Microsoft® Visual C++® 2012 y Apple® LLVM en Xcode® 4.5. • Apuntadores inteligentes. Los apuntadores inteligentes nos ayudan a evitar errores de admi- nistración de memoria dinámica al proveer una funcionalidad adicional a la de los apuntadores integrados. En el capítulo 17 (en el sitio web) hablaremos sobre unique_ptr. Prefacio
  • 20. xviii Prefacio Características de C++11 en Cómo programar en C++, novena edición Algoritmo all_of Algoritmo any_of Contenedor array auto para inferencia de tipos Funciones begin/end Funciones miembro de contenedor cbegin/cend Compilador fix for en tipos de plantillas Algoritmo copy_if Algoritmo copy_n Funciones miembro de contenedor crbegin/cend decltype Argumentos de tipo predeterminado en plantillas de funciones Funciones miembro predeterminadas (default) Delegación de constructores Funciones miembro eliminadas ( delete) Operadores de conversión explicit Clases final Funciones miembro final Algoritmo find_if_not Contenedor forward_list Claves inmutables en contenedores asociativos Inicializadores en la clase Heredar constructores de la clase base Funciones miembro de contenedor insert devuelven iteradores Algoritmo is_heap Algoritmo is_heap_until Palabras clave nuevas en C++11 Expresiones lambda Inicialización de listas de pares clave-valor Inicialización de listas de objetos pair Inicialización de listas de valores de retorno Lista inicializadora de un arreglo asignado en forma dinámica Lista inicializadora de un vector Inicializadores de listas en llamadas a constructores Tipo long long inte Algoritmos min y max con parámetros initializer_list Algoritmo minmax Algoritmo minmax_element Algoritmo move Operadores de asignación de movimiento Algoritmo move_backward Constructores de movimiento noexcept Generación de números aleatorios no determinísticos Algoritmo none_of Funciones de conversión numérica nullptr Palabra clave override Instrucción for basada en rangos Expresiones regulares Referencias rvalue Enumeraciones (enum) con alcance Apuntador inteligente shared_ ptr Función miembro shrink_to_ fit vector/deque Especificar el tipo de las constantes de una enumeración Objetos static_assert para nombres de archivo Objetos string para nombres de archivo Función no miembro swap Funciones for con tipos de retorno al final Plantilla variádica tuple Apuntador inteligente unique_ ptr long long int sin signo Apuntador inteligente weak_ptr Fig. 1  Una muestra de las características de C++11 en Cómo programar en C++, novena edición. • Cobertura anticipada de los contenedores, iteradores y algoritmos de la Biblioteca Estándar, mejorada con capacidades de C++11. Transferimos el tratamiento de los contenedores, itera- dores y algoritmos de la Biblioteca de plantillas Estándar del capítulo 22, en la edición anterior, a los capítulos 15 y 16, y lo mejoramos con las características adicionales de C++11. La gran mayoría de las necesidades de los programadores en cuanto a estructuras de datos pueden sa- tisfacerse mediante la reutilización de estas capacidades de la Biblioteca Estándar. En el capítu- lo 19 (en inglés en el sitio web) le indicaremos cómo crear sus propias estructuras de datos personalizadas. • Generación de números aleatorios, simulación y juegos. Para ayudar a que los programas sean más seguros, agregamos un tratamiento de las nuevas herramientas de generación de números aleatorios no deterministas.
  • 21. Programación orientada a objetos xix Programación orientada a objetos • Metodología de presentación de objetos en los primeros capítulos. El libro presenta los conceptos básicos y la terminología de la tecnología de objetos en el capítulo 1. Usted desarrollará sus pri- meras clases y objetos personalizados en el capítulo 3; de esta manera hacemos que usted “piense acerca de los objetos” de inmediato y domine estos conceptos con más profundidad.1 • Objetos string de la Biblioteca Estándar de C++. C++ ofrece dos tipos de cadenas: los objetos de la clase string (que comenzaremos a usar en el capítulo 3) y las cadenas de C. Reemplaza- mos la mayoría de las ocurrencias de las cadenas de C con instancias de la clase string de C++ para que los programas sean más robustos y para eliminar muchos de los problemas de seguri- dad de las cadenas de C. Seguiremos hablando sobre las cadenas de C más adelante en el libro para preparar al lector a trabajar con el código heredado que encontrará en la industria. En cuanto al desarrollo de nuevos programas, es preferible usar objetos string. • Objetos array de la Biblioteca Estándar de C++. Nuestro principal tratamiento de los arreglos usa ahora la plantilla de la clase array de la Biblioteca Estándar en vez de los arreglos integra- dos estilo C, basados en apuntadores. De todas formas cubriremos los arreglos integrados ya que siguen siendo útiles en C++ y para que el lector pueda leer el código heredado. C++ ofrece tres tipos de arreglos: objetos array y vector (que comenzaremos a usar en el capítulo 7) y arreglos estilo C basados en apuntadores, que veremos en el capítulo 8. Según sea apropiado, usaremos la plantilla de clase array en vez de arreglos de C a lo largo del libro. En cuanto al desarrollo de nuevos programas, es preferible usar objetos de la plantilla de clase array. • Creación de clases valiosas. Un objetivo clave de este libro es preparar al lector para crear clases valiosas. En el ejemplo práctico del capítulo 10, usted creará su propia clase Array personali- zada (y luego en los ejercicios del capítulo 18, en inglés, la convertirá en una plantilla de clase). Aquí es donde apreciará en verdad el concepto de las clases. El capítulo 10 comienza con una prueba práctica de la plantilla de clase string, de modo que pueda ver un uso elegante de la sobrecarga de operadores antes de implementar su propia clase personalizada con operadores sobrecargados. • Ejemplos prácticos en programación orientada a objetos. Ofrecemos ejemplos prácticos que abarcan varias secciones y capítulos, además de cubrir el ciclo de vida de desarrollo de software. Entre éstos se incluye la clase LibroCalificaciones en los capítulos 3 a 7, la clase Tiempo en el capítulo 9 y la clase Empleado en los capítulos 11 y 12. El capítulo 12 contiene un diagrama detallado y una explicación de cómo puede C++ implementar internamente el polimorfismo, las funciones virtual y la vinculación dinámica. • Ejemplo práctico opcional: uso de UML para desarrollar un diseño orientado a objetos y la implementación en C++ de un ATM. El UML™ (Lenguaje unificado de modelado™ ) es el lenguaje gráfico estándar de la industria para los sistemas de modelado orientados a objetos. Presentamos el UML en los primeros capítulos. Diseñamos e implementamos el software para un cajero automático simple (ATM). Analizamos un documento de requerimientos típico que 1 En los cursos que requieren una metodología en la que se presenten los objetos en capítulos posteriores, le recomenda- mos el libro C++ How to Program, Late Objects (versión en inglés), el cual empieza con los primeros seis capítulos sobre losfundamentosdelaprogramación(incluyendodossobreinstruccionesdecontrol)ycontinúaconsietecapítulosque introducen los conceptos de programación orientada a objetos en forma gradual.
  • 22. xx Prefacio especifica el sistema a construir. Determinamos las clases necesarias para implementar ese sis- tema, los atributos que necesitan tener las clases, los comportamientos que deben exhibir y especificamos la forma en que deben interactuar esas clases entre sí para cumplir con los reque- rimientos del sistema. A partir del diseño producimos una implementación completa en C++. A menudo los estudiantes reportan que el ejemplo práctico les ayuda a “enlazarlo todo” y comprender en verdad la orientación a objetos. • Manejo de excepciones. Integramos el manejo básico de excepciones en los primeros capítu- los del libro. Los instructores pueden extraer con facilidad más material del capítulo 17, Mane- jo de excepciones: un análisis detallado. • Estructuras de datos basadas en plantillas personalizadas. Ofrecemos un tratamiento extenso de varios capítulos sobre las estructuras de datos: consulte el módulo Estructuras de datos en el gráfico de dependencia de los capítulos (figura 6). • Tres paradigmas de programación. Hablamos sobre la programación estructurada, la programa- ción orientada a objetos y la programación genérica. Características pedagógicas • Extensa cobertura de los fundamentos de C++. Incluimos un tratamiento conciso de dos capí- tulos sobre las instrucciones de control y el desarrollo de algoritmos. • El capítulo 2 ofrece una introducción simple a la programación en C++. • Ejemplos: incluimos un amplio rango de programas de ejemplo seleccionados de temas como ciencias computacionales, negocios, simulación, juegos y demás (figura 2). Ejemplos Ejemplo práctico de la clase Array Clase Autor Programa de cuenta bancaria Programa para imprimir gráficos de barras Clase EmpleadoBaseMasComision Creación y recorrido de árboles binarios Programa de prueba BusquedaBinaria Barajar y repartir cartas Clase DatosCliente Clase EmpleadoPorComision Comparación de objetos string Proceso de compilación y vinculación Cálculos del interés compuesto con for Conversión de objetos string a cadenas de C Repetición controlada por contador Simulación del juego de dados “craps” Programa de consulta de crédito Clase Fecha Conversión de tipos descendente (downcasting) e información de tipos en tiempo de ejecución Clase Empleado Constructor explicit Función fibonacci Algoritmos fill Especializaciones de plantillas de funciones de la plantilla de función imprimirArreglo Algoritmos generate Clase LibroCalificaciones Inicialización de un arreglo en una declaración Entrada a partir de un objeto istringstream Solución de factorial iterativa Fig. 2  Una muestra de los ejemplos del libro (parte 1 de 2).
  • 23. Características pedagógicas xxi Ejemplos Expresiones lambda Manipulación de listas enlazadas Plantilla de clase map Algoritmos matemáticos de la Biblioteca Estándar Plantilla de función maximum Programa de ordenamiento por combinación Plantilla de clase multiset new lanza bad_alloc durante una falla Clase NumeroTelefonico Programa de análisis de encuestas Demostración del polimorfismo Preincremento y postincremento Clase de adaptador priority_queue Clase de adaptador queue Archivos de acceso aleatorio Generación de números aleatorios Función recursiva factorial Tirar un dado 6,000,000 de veces Clase EmpleadoAsalariado Clase Vendedor Algoritmos de búsqueda y ordenamiento de la Biblioteca Estándar Archivos secuenciales Plantilla de clase set Programa shared_ptr Clase de adaptador stack Clase Stack Desenredo de pila (stack unwinding) Programa de la clase string de la Biblioteca Estándar Manipulador de flujos showbase Asignación y concatenación de objetos string Función miembro substr de string Suma de enteros con la instrucción for Clase Tiempo Objeto unique_ptr que administra la memoria asignada en forma dinámica Validar la entrada del usuario con expresiones regulares Plantilla de clase vector • Audiencia. Los ejemplos son accesibles para estudiantes de ciencias computacionales, tecno- logía de la información, ingeniería de software y de negocios en cursos de C++ para estudiantes de nivel principiante a intermedio, aunque también puede ser de gran utilidad para los progra- madores profesionales. • Ejercicios de autoevaluación y respuestas. Se incluyen ejercicios extensos de autoevaluación con sus respuestas para autoestudio. • Ejercicios interesantes, entretenidos y desafiantes. Cada capítulo concluye con un amplio con- junto de ejercicios: recordatorios simples de la terminología y los conceptos importantes, que sirven para identificar los errores en ejemplos de código, escribir instrucciones individuales de programas, escribir pequeñas porciones de clases de C++ y funciones tanto miembro como no miembro, escribir programas completos e implementar proyectos grandes. La figura 3 contiene una lista de muestra de los ejercicios del libro, incluyendo nuestros ejercicios Hacer la diferencia, que animan al lector a usar las computadoras e Internet para investigar y resolver problemas so- ciales importantes. Esperamos que usted los aborde con sus propios valores, políticas y creencias. Ejercicios Sistema de reservaciones de una aerolínea Ejercicios avanzados de manipulación de cadenas Ordenamiento de burbuja Cree su propio compilador Cree su propia computadora Cálculo de los salarios Clase abstracta de la huella de carbono: polimorfismo Fig. 3  Una muestra de los ejercicios del libro (parte 1 de 2). Fig. 2  Una muestra de los ejemplos del libro (parte 2 de 2).
  • 24. xxii Prefacio Ejercicios Barajar y repartir cartas Instrucción asistida por computadora Instrucción asistida por computadora: niveles de dificultad Instrucción asistida por computadora: monitoreo del desempeño de los estudiantes Instrucción asistida por computadora: reducción de la fatiga de los estudiantes Instrucción asistida por computadora: variación de los tipos de problemas Cocinar con ingredientes más saludables Modificación del juego de “craps” Límites de crédito Generador de crucigramas Criptogramas Leyes de De Morgan Tiro de dados Ocho reinas Respuesta de emergencia Implementar la privacidad con la criptografía Crecimiento de la base de usuarios de Facebook Serie de Fibonacci Kilometraje de gasolina Cuestionario de hechos de advertencia global Juego de adivinar el número Juego del colgado Registros médicos, Paseo del caballo Quintillas Recorrido de laberinto: generación de laberintos al azar Código Morse Modificación al sistema de nóminas Problema de Peter Minuit Explorador de “phishing” Latín cerdo Programa bancario polimórfico mediante el uso de la jerarquía de cuentas Triples de Pitágoras Calculadora de salarios Criba de Eratóstenes Desencriptado simple Encriptado simple Lenguaje SMS Explorador de “spam” Corrector ortográfico Calculadora de la frecuencia cardiaca esperada Alternativas para el plan fiscal: el “impuesto justo” Generador de palabras de números telefónicos Canción “Los doce días de Navidad” Simulación de la tortuga y la liebre Torres de Hanoi Crecimiento de la población mundial • Ilustraciones y figuras. Se incluyen muchas tablas, dibujos lineales, diagramas de UML, pro- gramas y resultados de los programas. La figura 4 incluye una muestra de los dibujos y diagra- mas del libro. Dibujos y diagramas Dibujos y diagramas del texto principal Jerarquía de datos Proceso de compilación y vinculación para programas con varios archivos de código fuente Orden en el que se evalúa un polinomio de segundo grado Diagramas de la clase LibroCalificaciones Diagrama de actividad de la instrucción if de selección simple Diagrama de actividad de la instrucción if...else de selección doble Diagrama de actividad de UML de la instrucción de repetición while Diagrama de actividad de UML de la instrucción de repetición for Diagrama de actividad de UML de la instrucción de repetición do...while Diagrama de actividad de la instrucción switch de selección múltiple Instrucciones de secuencia, selección y repetición de una sola entrada/una sola salida de C++ Análisis de un programa de paso por valor y paso por referencia Diagramas de la jerarquía de herencia Pila de llamadas a funciones y registros de activación Llamadas recursivas a la función fibonacci Diagramas de aritmética de apuntadores Jerarquía de herencia de MiembroDeLaComunidad Jerarquía de herencia de Figura Fig. 3  Una muestra de los ejercicios del libro (parte 2 de 2). Fig. 4  Una muestra de los dibujos y diagramas del libro (parte 1 de 2).
  • 25. Programación segura en C++ xxiii Dibujos y diagramas Herencia public, protected y private Diagrama de clases de UML de la jerarquía Empleado Cómo funcionan las llamadas a funciones virtual Jerarquía de plantillas de E/S de flujos Dos objetos de clases autorreferenciadas enlazados entre sí Representación gráfica de una lista Operación insertarAlFrente representada en forma gráfica Operación insertarAlFinal representada en forma gráfica Operación quitarDelFrente representada en forma gráfica Operación quitarDelFinal representada en forma gráfica Lista circular enlazada simple Lista doblemente enlazada Lista circular doblemente enlazada Representación gráfica de un árbol binario Dibujos y diagramas del ejemplo práctico del ATM Diagrama de caso-uso para el sistema del ATM desde la perspectiva del Usuario Diagrama de clases que muestra una asociación entre clases Diagrama de clases que muestra relaciones de composición Diagrama de clases para el modelo del sistema del ATM Clases con atributos Diagrama de estado para el ATM Diagrama de actividad para una transacción SolicitudSaldo Diagrama de actividad para una transacción Retiro Clases en el sistema del ATM con atributos y operaciones Diagrama de comunicaciones del ATM ejecutando una solicitud de saldo Diagrama de comunicación para ejecutar una solicitud de saldo Diagrama de secuencia que modela la ejecución de un Retiro Diagrama de caso-uso para una versión modificada de nuestro sistema ATM que también permite a los usuarios transferir dinero entre cuentas Diagrama de clases que muestra las relaciones de composición de una clase Auto Diagrama de clases para el modelo del sistema del ATM que incluye la clase Deposito Diagrama de actividad para una transacción Deposito Diagrama de secuencia que modela la ejecución de un Deposito Otras características • Apuntadores. Ofrecemosunacoberturaextensadelasherramientasintegradasdeapuntadores y la relación íntima entre los apuntadores integrados, las cadenas de C y los arreglos integrados. • Presentación visual de la búsqueda y el ordenamiento con una explicación simple de Big O. • Material adicional en línea. Se incluyen varios capítulos (unos en español y otros en inglés) disponibles en formato PDF con capacidad de búsqueda en el sitio Web de este libro, consulte con su proveedor de Pearson cómo acceder a ellos. Programación segura en C++ Es difícil crear sistemas con solidez industrial que resistan los ataques de los virus, gusanos y otros tipos de “malware”. En la actualidad dichos ataques pueden ser instantáneos y de alcance global debido a Internet. Al integrar la seguridad en el software desde el comienzo del ciclo de desarrollo es posible re- ducir la vulnerabilidad de manera considerable. Fig. 4  Una muestra de los dibujos y diagramas del libro (parte 2 de 2).
  • 26. xxiv Prefacio El Centro de coordinación CERT® (www.cert.org) se creó para analizar y responder a los ataques de manera oportuna. CERT (el equipo de respuesta a emergencias cibernéticas) es una organización finan- ciada por el gobierno de Estados Unidos, dentro del Carnegie Mellon University Software Engineering Institute™ . CERT publica y promueve estándares seguros de codificación para varios lenguajes de pro- gramación populares para ayudar a los desarrolladores de software a implementar sistemas con solidez industrial que eviten las prácticas de programación que dejan los sistemas expuestos a ataques. Agradecemos el apoyo de Robert C. Seacord, gerente de codificación segura en CERT y profesor adjunto en la Carnegie Mellon University School of Computer Science. El señor Seacord fue revisor téc- nico de nuestro libro Cómo programar en C, séptima edición, y revisó nuestros programas en C desde una perspectiva de seguridad, y recomendó que nos adhiriéramos al Estándar de codificación segura en C de CERT. Con este libro hicimos lo mismo: nos adherimos al Estándar de codificación segura en C++ de CERT, el cual puede encontrar en: www.securecoding.cert.org Nos llenó de orgullo descubrir que ya estábamos recomendando muchas de esas prácticas de codifica- ción en nuestros libros. Actualizamos nuestro código y los análisis del mismo para cumplir con estas prácticas, según lo apropiado para un libro de texto de nivel introductorio/intermedio. Si piensa crear sistemas en C++ con solidez industrial, considere leer el libro Secure Coding in C and C++, Second Edi- tion (de Robert Seacord, Addison-Wesley Professional). Contenido en línea Para acceder al sitio Web del libro, vaya a www.pearsonenespañol.com/deitel En el sitio web encontrará los siguientes capítulos en formato PDF con capacidad de búsqueda. Los tres primeros se encuentran en español, y los restantes en idioma inglés: • 15 Contenedores e iteradores de la biblioteca estándar • 16 Algoritmos de la biblioteca estándar • 17 Manejo de excepciones: un análisis más detallado • 18 Introduction to Custom Templates • 19 Custom Templatized Data Structures • 20 Seearching and Sorting • 21 Class string and String Stream Processing: A Deeper Look • 22 Bits, Characters, C Strings and structs • 23 Other Topics El sitio web también incluye: • Descripciones del ejercicio Build Your Own Compiler del capítulo 19. • Prueba práctica del capítulo 1 para Mac OS X.
  • 27. Métodos de enseñanza xxv Gráfico de dependencias El gráfico de la siguiente página (figura 6) muestra la dependencia entre los capítulos de este libro, el cual puede ser de mucha ayuda para los profesores que desean planear sus programas de estudio con este texto. El gráfico muestra la organización modular del libro. Métodos de enseñanza Esta edición contiene una extensa colección de ejemplos. Hacemos hincapié en la claridad de los pro- gramas y nos concentramos en crear software bien diseñado. Método de código activo. El libro está lleno de ejemplos de “código activo”: la mayoría de los nuevos conceptos se presentan en aplicaciones de C++ funcionales completas, seguidos de una o más ejecuciones que muestran las entradas y salidas de los programas. En algunos casos, en donde utilizamos un frag- mento de código, para asegurar que esté correcto lo evaluamos en un programa funcional completo para después copiarlo y pegarlo en el libro. Coloreo de sintaxis. Para mejorar la legibilidad, resaltamos la sintaxis de todo el código de C++, en tonos de gris, de manera similar a como lo hace la mayoría de los entornos de desarrollo integrados de C++ y los editores de código. Nuestras convenciones de sintaxis son las siguientes: los comentarios aparecen así las palabras clave aparecen así las constantes y los valores literales aparecen así el resto del código aparece en este tipo Resaltado de código. Colocamos rectángulos sombreados de color gris alrededor de los segmentos de código clave en cada programa. Uso de fuentes para dar énfasis. Colocamos la ocurrencia de definición de cada término clave en ne- gritas para facilitar su referencia. Enfatizamos los componentes en pantalla en la fuente Helvetica en negritas (porejemplo,elmenúArchivo)yeltextodelprogramadeC++enlafuenteLucida (porejemplo, int x = 5;). Objetivos. Las citas de apertura van seguidas de una lista de objetivos del capítulo. Tips de programación. Incluimos tips de programación para ayudarle a enfocarse en los aspectos im- portantes del desarrollo de programas. Estos tips y prácticas representan lo mejor que hemos podido recabar a lo largo de siete décadas combinadas de experiencia en la programación y la enseñanza. Buenas prácticas de programación Las Buenas prácticas de programación llaman la atención hacia técnicas que le ayudarán a producir programas más claros, comprensibles y fáciles de mantener. Errores comunes de programación Al poner atención en estos Errores comunes de programación se reduce la probabilidad de que pueda cometerlos.
  • 28. xxvi Prefacio Fig. 6 Gráfico de dependencia de los capítulos Introducción Temas heredados de C Flujos, archivos y cadenas Otros temas y características de C++11 1 Introducción a las computadoras y a C++ 22 Bits, Characters, C-Strings and struct 13 Entrada/salida de flujos: un análisis más detalladoI 14 Procesamiento de archivos 23 Other Topics 21 Class string and String Stream Processing: A Deeper Look [Nota: las flechas que apuntan hacia un capítulo indican las dependencias de ese capítulo]. Introducción a la programación, clases y objetos Instrucciones de control, métodos y arreglos Programación orientada a objetos Estructuras de datos 2 Introducción a la programación en C++, entrada/salida y operadores 4 Instrucciones de control, parte I: operadores de asignación, ++ y -- 9 Clases, un análisis más detallado: lanzar excepciones 15 Contenedores e iteradores de la biblioteca estándar 3 Introducción a las clases, objetos y cadenas 5 Instrucciones de control, parte 2: operadores lógicos 10 Sobrecarga de operadores: la clase string 16Algoritmosdelabiblioteca estándar 6 Funciones y una introducción a la recursividad 11 POO: herencia 6.20-6.22 Recursividad 7Plantillasdeclasearray y vector:cómoatraparexcepciones 12POO:polimorfismo 18IntrotoCustomTemplates 8 Apuntadores 17 Manejo de excepciones: un análisis más detallado 19 Custom Templatized Data Structures 20 Searching and Sorting 1. La mayoría del capítulo 13 puede leerse después del capítulo 7. Una pequeña parte requiere los capítulos 11 y 18.
  • 29. Suplementos para el profesor xxvii Tips para prevenir errores Estos tips contienen sugerencias para exponer los errores y eliminarlos de sus programas; muchos de ellos describen aspectos de C++ que evitan que los errores siquiera entren a los programas. Tips de rendimiento Estos tips resaltan las oportunidades para hacer que sus programas se ejecuten más rápido, o para minimizar la cantidad de memoria que ocupan. Tips de portabilidad Los Tips de portabilidad le ayudan a escribir código que pueda ejecutarse en varias plata- formas. Observaciones de Ingeniería de Software LasObservacionesdeIngenieríadeSoftwareresaltanlosasuntosdearquitecturaydiseño, lo cual afecta la construcción de los sistemas de software, especialmente los de gran escala. Viñetas de resumen. Presentamos un resumen detallado del capítulo, estilo lista con viñetas, sección por sección. Para facilitar su consulta, incluimos el número de página donde aparece la definición de los términos clave del capítulo. Índice. Incluimos un amplio índice en donde las definiciones de los términos clave se resaltan con un número en negritas. Cómo obtener el software utilizado en este libro Escribimos los ejemplos de código en Cómo programar en C++, novena edición mediante el uso de las siguientes herramientas de desarrollo de C++: • El programa gratuito Visual Studio Express 2012 de Microsoft para Windows Desktop, que incluye Visual C++ junto con otras herramientas de desarrollo de Microsoft. Se ejecuta en Windows 7 y 8, y está disponible para su descarga en www.microsoft.com/visualstudio/esn/downloads# d-express-windows-desktop • El programa gratuito GNU C++ de GNU (gcc.gnu.org/install/binaries.html), que ya se encuentra instalado en la mayoría de los sistemas Linux y puede instalarse también en siste- mas Mac OS X y Windows. • El programa Xcode gratuito de Apple, que los usuarios de Mac OS X pueden descargar de la App Store de Mac. Suplementos para el profesor (todos en inglés) Los siguientes suplementos están disponibles sólo para profesores calificados a través del Centro de recur- sos para el profesor de Pearson (http://www.pearsonhighered.com/deitel/): • Manual de soluciones: contiene soluciones para la mayoría de los ejercicios de final de capí- tulo. Agregamos muchos ejercicios Making a Difference (Hacer la diferencia), la mayoría con soluciones. El acceso está restringido para los profesores que lleven este libro como texto en su curso. Los profesores pueden obtener acceso a través de los representantes de Pearson. Si usted no es un miembro docente registrado, póngase en contacto con alguno de nuestros representantes o contacte al editor. No se proporcionan soluciones a los ejercicios
  • 30. xxviii Prefacio de “proyectos”. Consulte nuestro Centro de recursos de proyectos de programación, en donde encontrará muchos ejercicios y proyectos adicionales: www.deitel.com/ProgrammingProjects • TestItemFile(Archivodeprueba)depreguntasdeopciónmúltiple(aproximadamentedospor cada sección del libro). • Diapositivas de PowerPoint® personalizables que contienen todo el código (en inglés) y las figu- ras del texto (también en inglés), además de elementos en viñetas que sintetizan los puntos clave. Agradecimientos Queremos agradecer a Abbey Deitel y Barbara Deitel, de Deitel Associates, Inc., por la gran cantidad de horas que dedicaron a este proyecto. Abbey fue coautora del capítulo 1; además, junto con Barbara investigaron de manera minuciosa las nuevas herramientas de C++11. Somos afortunados al haber trabajado con el dedicado equipo de editores profesionales en Pearson. Apreciamos la orientación, inteligencia y energía de Tracy Johnson, editor ejecutivo de ciencias compu- tacionales. Carole Snyder hizo un extraordinario trabajo de reclutar a los revisores del libro y se hizo cargo del proceso de revisión. Bob Engelhardt hizo un maravilloso trabajo para llevar el libro a su publicación. Revisores Queremos agradecer los esfuerzos de nuestros revisores. Este libro fue revisado por los miembros actua- les y anteriores del comité de estándares de C++ que desarrolló C++11, académicos que imparten cursos sobre C++ y expertos en la industria. Todos ellos proporcionaron incontables sugerencias para mejorar la presentación. Cualquier error en el libro es por culpa nuestra. Revisores de la novena edición: Dean Michael Berris (Google, Miembro del comité ISO C++), Danny Kalev (experto en C++, analista de sistemas certificado y ex miembro del Comité de estándares de C++), Linda M. Krause (Elmhurst College), James P. McNellis (Microsoft Corporation), Robert C. Seacord (gerente de codificación segura en SEI/CERT, autor de Secure Coding in C and C++) y José Antonio González Seco (Parlamento de Andalucía). Revisores de ediciones anteriores: Virginia Bailey (Jackson State University),Thomas J. Borrelli (Ro- chester Institute ofTechnology), Ed Brey (Kohler Co.), Chris Cox (Adobe Systems), Gregory Dai (eBay), Peter J. DePasquale (The College of New Jersey), John Dibling (SpryWare), Susan Gauch (University of Arkansas), Doug Gregor (Apple, Inc.), Jack Hagemeister (Washington State University), Williams M. Higdon (University of Indiana), Anne B. Horton (Lockheed Martin),Terrell Hull (Logicalis Integration Solutions), Ed James Beckham (Borland), Wing-Ning Li (University of Arkansas), Dean Mathias (Utah State University), Robert A. McLain (Tidewater Community College), Robert Myers (Florida State Uni- versity), Gavin Osborne (Saskatchewan Inst. of App. Sci. and Tech.), Amar Raheja (California State Po- lytechnic University, Pomona), April Reagan (Microsoft), Raymond Stephenson (Microsoft), Dave Topham (Ohlone College), Anthony Williams (autor y miembro del Comité de estándares de C++) y Chad Willwerth (University Washington,Tacoma). Apreciaremos con sinceridad sus comentarios, críticas, correcciones y sugerencias para mejorar el texto. Dirija toda su correspondencia a: deitel@deitel.com Le responderemos a la brevedad posible. Disfrutamos mucho el escribir Cómo programar en C++, nove- na edición. ¡Esperamos que usted disfrute el leerlo! Paul Deitel Harvey Deitel
  • 31. Acerca de los autores xxix Acerca de los autores Paul J. Deitel, CEO y Director Técnico de Deitel Associates, Inc., es egresado del MIT, en donde estudió Tecnología de la Información. A través de Deitel Associates, Inc., ha impartido cientos de cursos de programación a empresas de la industria como: Cisco, IBM, Siemens, Sun Microsystems, Dell, Fidelity, NASA (en el Centro Espacial Kennedy), National Severe Storm Laboratory, White Sands Missile Range, Rogue Wave Software, Boeing, SunGard Higher Education, Nortel Networks, Puma, iRobot, Invensys, entre muchos más. Él y su coautor, el Dr. Harvey M. Deitel, son autores de los libros de texto, libros profesionales y videos sobre lenguajes de programación más vendidos en el mundo. Dr. Harvey M. Deitel, Presidente y Consejero de Estrategia de Deitel Associates, Inc., tiene 50 años de experiencia en el campo de la computación. El Dr. Deitel tiene una licenciatura y una maestría en ingeniería eléctrica por el MIT y un doctorado en matemáticas de la Boston University. Tiene mu- chos años de experiencia como profesor universitario, la cual incluye un puesto vitalicio y el haber sido presidente del departamento de Ciencias de la computación en el Boston College antes de fundar, con su hijo Paul J. Deitel, Deitel Associates, Inc. Los textos de los Deitel se han ganado el reconocimien- tointernacionalyhansidotraducidosalchino,coreano,japonés,alemán,ruso,español,francés,polaco, italiano, portugués, griego, urdú y turco. El Dr. Deitel ha impartido cientos de cursos de programación a clientes corporativos, académicos, gubernamentales y militares. Capacitación corporativa de Deitel Associates, Inc. Deitel Associates, Inc., fundada por Paul Deitel y Harvey Deitel, es una empresa reconocida a nivel mundial, dedicada al entrenamiento corporativo y la creación de contenido, especializada en lenguajes de programación de computadora, tecnología de objetos, desarrollo de aplicaciones móviles y tecnolo- gíadesoftwaredeInternetyWeb. Susclientesincluyenmuchasdelasempresasmásgrandes delmundo, agencias gubernamentales, sectores del ejército e instituciones académicas. La empresa proporciona cursos, en las instalaciones de sus clientes en todo el mundo, sobre la mayoría de los lenguajes y plata- formas de programación, como C++, Visual C++® , C, Java™ , Visual C#® , Visual Basic® , XML® , Python® , tecnología de objetos, programación en Internet y Web, desarrollo de aplicaciones para An- droid, desarrollo de aplicaciones de Objective-C y para iPhone, y una lista cada vez mayor de cursos adicionales de programación y desarrollo de software. A lo largo de su sociedad editorial de más de 36 años con Prentice Hall/Pearson, Deitel Associa- tes, Inc. ha publicado libros de texto de vanguardia sobre programación, libros profesionales y cursos en video de LiveLessons. Puede contactarse con Deitel Associates, Inc. y con los autores por medio del correo electrónico: deitel@deitel.com Para conocer más sobre la Serie de Capacitación Corporativa Dive Into® de Deitel, visite: www.deitel.com/training Para solicitar una propuesta de capacitación impartida en el sitio de su organización, en cualquier parte del mundo, envíe un correo a deitel@deitel.com. Quien desee comprar libros de Deitel y cursos en video de LiveLessons puede hacerlo a través de www.deitel.com. Las empresas, el gobierno, las instituciones militares y académicas que deseen realizar pedidos en masa deben hacerlo directamente con Pearson. Para obtener más información, visite www.pearsonenespañol.com
  • 33. Introducción a las computadoras y a C++ 1 El hombre sigue siendo la computadora más extraordinaria de todas. —John F. Kennedy Un buen diseño es un buen negocio. —Thomas J. Watson, fundador de IBM Qué maravilloso es que nadie necesite esperar un solo momento para empezar a mejorar el mundo. —Anne Frank O b j e t i v o s En este capítulo aprenderá a: n Familiarizarse con los emocionantes y recientes desarrollos en el campo de las computadoras. n Conocer los fundamentos del hardware, software y redes de computadoras. n Emplear la jerarquía de datos. n Reconocer distintos tipos de lenguajes de programación. n Familiarizarse con algunos conceptos básicos de tecnología de objetos. n Apreciar algunos fundamentos de Internet y World Wide Web. n Conocer un entorno de desarrollo común de programas en C++. n Probar una aplicación en C++. n Familiarizarse con algunas de las recientes tecnologías clave de software. n Comprender cómo pueden las computadoras ayudarlo a hacer la diferencia.
  • 34. 2 Capítulo 1 Introducción a las computadoras y a C++ 1.1Introducción Bienvenido a C++: un poderoso lenguaje de programación de computadoras apropiado para las perso- nas con orientación técnica con poca o ninguna experiencia de programación, y para los programadores experimentados que desarrollan sistemas de información de tamaño considerable. Usted ya está fami- liarizadoconlaspoderosastareasquerealizanlascomputadoras.Medianteestelibroaprenderáaescribir instrucciones para ordenar a las computadoras que realicen esos tipos de tareas. El software (es decir, las instrucciones que usted escribe) controla el hardware (es decir, las computadoras). Aprenderá sobre la programación orientada a objetos: la metodología de programación clave de la actualidad. En este texto creará muchos objetos de software que modelan las cosas del mundo real. C++esunodeloslenguajesdedesarrollodesoftwaremáspopulares.Estelibroleproporcionaunaintro- ducción a la programación en C++11: la versión más reciente estandarizada mediante la Organización In- ternacional para la Estandarización (ISO) y la Comisión Electrotécnica Internacional (IEC). En la actualidad hay en uso más de mil millones de computadoras de propósito general, además de miles de millones de teléfonos celulares, teléfonos inteligentes (smartphones) y dispositivos portátiles (como las computadoras tipo tableta). De acuerdo con un estudio realizado por eMarketer, el número de usuarios móviles de Internet sobrepasará los 134 millones en 2014.1 Las ventas de teléfonos inte- ligentes excedieron a las ventas de computadoras personales en 2011.2 Se espera que para 2015, las ventas de las tabletas representen cerca del 20% de todas las ventas de computadoras personales.3 Se espera que en 2014 el mercado de las aplicaciones de teléfonos inteligentes exceda los $40 mil millones.4 Este explosivo crecimiento está creando oportunidades importantes para la programación de aplicacia- nes móviles. 1 www.circleid.com/posts/mobile_internet_users_to_reach_134_million_by_2013/. 2 www.mashable.com/2012/02/03/smartphone-sales-overtake-pcs. 3 www.forrester.com/ER/Press/Release/0,1769,1340,00.html. 4 Inc. diciembre de 2010/enero de 2011, páginas 116-123. 1.1 Introducción 1.2 Las computadoras e Internet en la industria y la investigación 1.3 Hardware y software 1.3.1 La Ley de Moore 1.3.2 Organización de la computadora 1.4 Jerarquía de datos 1.5 Lenguajes máquina, lenguajes ensambladores y lenguajes de alto nivel 1.6 C++ 1.7 Lenguajes de programación 1.8 Introducción a la tecnología de objetos 1.9 Entorno de desarrollo común en C++ 1.10 Prueba de una aplicación de C++ 1.11 Sistemas operativos 1.11.1 Windows: un sistema operativo propietario 1.11.2 Linux: un sistema operativo de código fuente abierto 1.11.3 OS X de Apple: iOS de Apple para dispositivos iPhone®, iPad® y iPod Touch® 1.11.4 Android de Google 1.12 Internet y World Wide Web 1.13 Cierta terminología clave de desarrollo de software 1.14 C++11 y las bibliotecas Boost de código fuente abierto 1.15 Mantenerse actualizado con las tecnologías de la información 1.16 Recursos Web Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios | Hacer la diferencia | Recursos para hacer la diferencia
  • 35. 1.2 Las computadoras e Internet en la industria y la investigación 3 1.2Las computadoras e Internet en la industria y la investigación Éstos son tiempos emocionantes en el campo de la computación. Muchas de las empresas más influyen- tes y exitosas de las últimas dos décadas son compañías de tecnología, como Apple, IBM, Hewlett Packard, Dell, Intel, Motorola, Cisco, Microsoft, Google, Amazon, Facebook, Twitter, Groupon, Foursquare, Yahoo!, eBay y muchas más. Estas empresas son importantes fuentes de empleo para las personas que estudian ciencias computacionales, ingeniería computacional, sistemas de información o disciplinas relacionadas. En 2013, Apple era la compañía más valiosa del mundo. La figura 1.1 provee unos cuantos ejemplos de las formas en que las computadoras están mejorando las vidas de las personas en la investigación, la industria y la sociedad. Nombre Descripción Registros de salud electrónicos Podrían incluir el historial médico de un paciente, prescripciones, vacunas, resultados de laboratorio, alergias, información de seguros y demás. Al poner esa información a disposición de los proveedores de servicios médicos a través de una red segura, logramos mejorar el cuidado de los pacientes, se reduce la probabilidad de error y en general, aumenta la eficiencia del sistema de servicios médicos. Proyecto Genoma Humano El Proyecto Genoma Humano se fundó para identificar y analizar los más de 20 000 genes en el ADN humano. El proyecto utilizó programas de computadora para analizar datos genéticos complejos, determinar las secuencias de los miles de millones de pares base químicos que conforman el ADN humano y almacenar la información en bases de datos disponibles a través de Internet para los investigadores en muchos campos. AMBER™ Alert El sistema de alerta AMBER (Niños norteamericanos extraviados: transmisión de respuesta a emergencias) se utiliza para buscar niños secuestrados. Las autoridades notifican a los funcionarios de transporte estatal y a las transmisoras de TV y radio, quienes a su vez transmiten alertas en TV, radio, señales de tráfico computarizadas, Internet y dispositivos inalámbricos. AMBER Alert se asoció hace poco con Facebook, cuyos usuarios pueden “dar Like” a las páginas de AMBER Alert por ubicación para recibir alertas en sus fuentes de noticias. World Community Grid Personas de todo el mundo pueden donar su poder de procesamiento de cómputo que no utilicen, mediante la instalación de un programa de software seguro gratuito que permite a World Community Grid (www.worldcommunitygrid.org) aprovechar la capacidad que no se utilice. Este poder de cómputo, al cual se accede a través de Internet, se utiliza en lugar de costosas supercomputadoras para realizar proyectos científicos de investigación que están haciendo la diferencia: ya sea proporcionar agua potable a países del tercer mundo, combatir el cáncer, cultivar arroz más nutritivo para regiones que combaten el hambre y otras cosas más. Computación en nube La computación en nube nos permite usar software, hardware e información almacenada en la “nube” (es decir, se accede desde computadoras remotas a través de Internet y está disponible bajo demanda) en vez de tener que almacenarla en nuestra computadora personal. Estos servicios, que le permiten aumentar o disminuir los recursos para satisfacer sus necesidades en un momento dado, son por lo general más efectivos en costos que comprar hardware costoso para asegurarse de tener el suficiente almacenamiento y poder de procesamiento para satisfacer sus necesidades en sus niveles máximos. Al usar los servicios de computación en nube traspasamos la carga de tener que manejar estas aplicaciones de nuestra empresa al proveedor de servicios, con lo cual ahorramos dinero. Fig. 1.1  Unos cuantos usos para las computadoras (parte 1 de 3).
  • 36. 4 Capítulo 1 Introducción a las computadoras y a C++ Nombre Descripción Imágenes para diagnóstico médico Las exploraciones por tomografía computarizada (CT) con rayos X, también conocidas como CAT (tomografía axial computarizada), toman rayos X del cuerpo desde cientos de ángulos distintos. Se utilizan computadoras para ajustar la intensidad del rayo X, con lo cual se optimiza la exploración para cada tipo de tejido, para después combinar toda la información y crear una imagen tridimensional (3D). Los escáneres MRI usan una técnica conocida como imágenes de resonancia magnética, también para producir imágenes internas de una manera no invasiva. GPS Los dispositivos con Sistema de posicionamiento global (GPS) utilizan una red de satélites para obtener información basada en la ubicación. Varios satélites envían señales con etiquetas de tiempo al dispositivo GPS, el cual calcula la distancia hacia cada satélite con base en la hora en que la señal salió del satélite y la hora en que se recibió la señal. Esta información se utiliza para determinar la ubicación exacta del dispositivo. Los dispositivos GPS pueden proveer indicaciones paso a paso y ayudarle a localizar negocios cercanos (restaurantes, gasolineras, etc.) y puntos de interés. El sistema GPS se utiliza en numerosos servicios de Internet basados en la ubicación, como las aplicaciones de registro (check-in) en línea, para que usted pueda encon- trar a sus amigos (por ejemplo, Foursquare y Facebook), en aplicaciones para hacer ejercicio como RunKeeper, que rastrean el tiempo, la distancia y la velocidad promedio de su rutina de trotar en exteriores, aplicaciones de citas que le ayudan a buscar una pareja cercana y aplicaciones que actualizan en forma dinámica las condiciones cambiantes del tráfico. Robots Los robots se pueden utilizar para tareas diarias (por ejemplo, la aspiradora Roomba de iRobot), de entretenimiento (como las mascotas robóticas), combate militar, exploración espacial y en la profundidad del océano (como el trotamundos Curiosity de la NASA), y otras más. RoboEarth (www.roboearth.org) es “una World Wide Web para robots”. Permite a los robots aprender unos de otros mediante la compartición de información, con lo cual mejoran sus habilidades para realizar tareas, navegar, reconocer objetos y demás. Correo electrónico, mensajería instantánea, chat de video y FTP Los servidores basados en Internet soportan toda su mensajería en línea. Los mensajes de correo electrónico pasan por un servidor de correo que también almacena esos mensajes. Las aplicaciones de mensajería instantánea (IM) y chat de video, como AIM, Skype, Yahoo! Messenger, GoogleTalk,Trillian, Microsoft Messenger y otras más, le permiten comunicarse con otras personas en tiempo real, mediante el envío de mensajes y video a través de los servidores. FTP (protocolo de transferencia de archivos) le permite intercambiar archivos entre varias computadoras (por ejemplo, una computadora cliente como su escritorio y un servidor de archivos) a través de Internet. TV por Internet Los dispositivos de TV por Internet (como Apple TV, Google TV y TiVo) le permiten acceder a una enorme cantidad de contenido bajo demanda, como juegos, noticias, películas, programas de televisión, etcétera, además de que le ayudan a asegurar que el contenido se transmita en flujo continuo hacia su TV sin problemas. Servicios de transmisión de música por flujo continuo Los servicios de transmisión de música por flujo continuo (como Pandora, Spotify, Last.fm y más) le permiten escuchar grandes catálogos de música a través de Web, crear “estaciones de radio” personalizadas y descubrir nueva música con base en la información que usted les retroalimente. Fig. 1.1  Unos cuantos usos para las computadoras (parte 2 de 3).
  • 37. 1.3 Hardware y software 5 Nombre Descripción Programación de juegos Los analistas esperan que los ingresos mundiales por juegos de video lleguen a $91 mil millones en 2015 (www.vg247.com/2009/06/23/global-industry- analysts-predicts-gaming-market-to-reach-91-billion-by-2015/). El desarrollo de los juegos más sofisticados puede costar hasta $100 millones. Call of Duty: Black Ops de Activision (uno de los juegos más vendidos de todos los tiempos) ¡obtuvo $360 millones en sólo un día (www.forbes.com/sites/insertcoin/2011/03/11/call-of- duty-black-ops-now-the-best-selling-video-game-of-all-time/)! Los juegos sociales en línea, que permiten a usuarios en todo el mundo competir entre sí a través de Internet, están creciendo con rapidez. Zynga (creador de juegos en línea populares, como Words with Friends, CityVille y otros) se fundó en 2007 y ya tiene más de 300 millones de usuarios al mes. Para dar cabida al aumento en tráfico, ¡Zynga agregará cerca de 1000 servidores cada semana (techcrunch. com/2010/09/22/zynga-moves-1-petabyte-of-data-daily-adds-1000-servers-a- week/)! Fig. 1.1  Unos cuantos usos para las computadoras (parte 3 de 3). 1.3Hardware y software Las computadoras pueden realizar cálculos y tomar decisiones lógicas con una rapidez increíblemente mayor que los humanos. Actualmente, muchas de las computadoras personales pueden realizar miles de millones de cálculos en un segundo —más de lo que un humano podría realizar en toda su vida. ¡Las supercomputadoras ya pueden realizar miles de billones de instrucciones por segundo! ¡La supercomputa- dora Sequoia de IBM puede realizar más de 16 mil billones de cálculos por segundo (16.32 petaflops)!5 Dicho de otra forma, ¡la supercomputadora Sequoia de IBM puede realizar en un segundo alrededor de 1.5 millones de cálculos para cada uno de los habitantes del planeta! ¡Y estos “límites superiores” están aumen- tando con rapidez! Las computadoras procesan datos bajo el control de conjuntos de instrucciones conocidas como programas de computadora. Estos programas guían a la computadora a través de conjuntos ordenados de acciones especificadas por gente conocida como programadores de computadoras. A los programas que se ejecutan en una computadora se les denomina software. En este libro aprenderá una metodolo- gía de programación clave que mejora la productividad del programador, con lo cual se reducen los costos de desarrollo del software: programación orientada a objetos. Una computadora consiste en varios dispositivos conocidos como hardware (teclado, pantalla, ratón, discos duros, memoria, unidades de DVD y unidades de procesamiento). Los costos de las computadoras han disminuido en forma espectacular, debido a los rápidos desarrollos en las tecnolo- gías de hardware y software. Las computadoras que ocupaban grandes habitaciones y que costaban millones de dólares hace algunas décadas, ahora pueden colocarse en las superficies de chips de silicio más pequeños que una uña, y con un costo de quizá unos cuantos dólares cada uno. Aunque suene irónico, el silicio es uno de los materiales más abundantes en el planeta: es un ingrediente en la arena común. La tecnología de los chips de silicio ha vuelto tan económica a la tecnología de la compu- tación que en la actualidad las computadoras se han vuelto un producto básico. 5 www.top500.org/.
  • 38. 6 Capítulo 1 Introducción a las computadoras y a C++ 1.3.1La Ley de Moore Es probable que cada año espere pagar por lo menos un poco más por la mayoría de los productos y ser- vicios. En el caso de los campos de las computadoras y las comunicaciones se ha dado lo opuesto, en es- pecial con relación a los costos del hardware que da soporte a estas tecnologías. Los costos del hardware han disminuido con rapidez durante varias décadas. Aproximadamente, cada uno o dos años, las capaci- dades de las computadoras se duplican sin que el precio se incremente. Esta notable tendencia se conoce en el ámbito común como la Ley de Moore, y debe su nombre a la persona que la identificó en la década de 1960: Gordon Moore, cofundador de Intel —el principal fabricante de procesadores para las compu- tadoras y los sistemas incrustados de la actualidad. La Ley de Moore y las observaciones relacionadas son especialmente ciertas en cuanto a la cantidad de memoria que tienen las computadoras para los progra- mas, la cantidad de almacenamiento secundario (como el almacenamiento en disco) que tienen para guardar los programas y datos durante periodos extendidos, y las velocidades de sus procesadores —las velocidades con que las computadoras ejecutan sus programas (es decir, realizan su trabajo). Se ha produ- cido un crecimiento similar en el campo de las comunicaciones, en donde los costos se han desplomado a medida que la enorme demanda por el ancho de banda de las comunicaciones (es decir, la capacidad de transmisión de información) atrae una competencia intensa. No conocemos otros campos en los que la tecnología mejore con tanta rapidez y los costos disminuyan de una manera tan drástica. Dicha mejora fenomenal está fomentando sin duda la Revolución de la información. 1.3.2Organización de la computadora Independientemente de las diferencias en su apariencia física, las computadoras pueden concebirse como divididas en varias unidades lógicas o secciones (figura 1.2). Unidad lógica Descripción Unidad de entrada Esta sección “receptora” obtiene información (datos y programas de cómputo) de los dispositivos de entrada y pone esta información a disposición de las otras unidades para que pueda procesarse. La mayor parte de la información se introduce en las computadoras a través de los teclados y ratones. Hay otras formas de entrada: recibir comandos de voz, digitalizar imágenes y códigos de barra, leer desde dispositivos de almacenamiento secundarios (como discos duros, unidades de DVD, unidades de Blu-ray Disc™ y unidades flash USB), recibir video de una cámara Web y hacer que su computadora reciba informa- ción de Internet (como cuando transmite videos en flujo continuo desde YouTube™ o descarga libros electrónicos de Amazon). Las formas más recientes de entrada incluyen los datos de posición de un dispositivo GPS, la información de movimiento y orientación de un acelerómetro en un Smartphone o controla- dor de juegos (como Microsoft® Kinect™ , Wii™ Remote y Move de Sony PlayStation® ). Unidad de salida Esta sección de “embarque” toma información que ya ha sido procesada por la computadora y la coloca en varios dispositivos de salida, para que esté disponi- ble fuera de la computadora. Hoy en día, la mayor parte de la información de salida de las computadoras se despliega en pantallas, se imprime en papel (el “movimiento ecológico” desaprueba esta opción), se reproduce como audio o video en computadoras personales y reproductores de medios (como los populares iPod de Apple) y pantallas gigantes en estadios deportivos, se transmite a través de Internet o se usa para controlar otros dispositivos, como robots y aparatos “inteligentes”. Fig. 1.2  Unidades lógicas de una computadora (parte 1 de 2).
  • 39. 1.4 Jerarquía de datos 7 Unidad lógica Descripción Unidad de memoria Esta sección de “almacén” de acceso rápido, pero con relativa baja capacidad, retiene la información que se introduce a través de la unidad de entrada, para que pueda estar disponible de manera inmediata para procesarla cuando sea necesario. La unidad de memoria también retiene la información procesada hasta que la unidad de salida pueda colocarla en los dispositivos de salida. La informa- ción en la unidad de memoria es volátil: por lo general se pierde cuando se apaga la computadora. Con frecuencia, a esta unidad de memoria se le llama memoria o memoria primaria. Muchas memorias en computadoras de escritorio y tipo notebook contienen comúnmente hasta 16 GB (esta medida significa gigabytes; un gigabyte equivale aproximadamente mil millones de bytes). Unidad aritmética y lógica (ALU) Esta sección de “manufactura” realiza cálculos como suma, resta, multiplicación y división.También contiene los mecanismos de decisión que permiten a la computadora hacer cosas como, por ejemplo, comparar dos elementos de la unidad de memoria para determinar si son iguales o no. Por lo general, en los sistemas actuales la ALU se implementa como parte de la siguiente unidad lógica, la CPU. Unidad central de procesamiento (CPU) Ésta sección “administrativa” coordina y supervisa la operación de las demás secciones. La CPU le indica a la unidad de entrada cuándo debe grabarse la información dentro de la unidad de memoria, a la ALU cuándo debe utilizarse la información de la unidad de memoria para los cálculos, y a la unidad de salida cuándo enviar la información desde la unidad de memoria hasta ciertos dispositivos de salida. Muchas de las computadoras actuales contienen múltiples CPU y, por lo tanto, pueden realizar muchas operaciones de manera simultánea. Un procesador multinúcleo implementa varios procesadores en un solo chip de circuitos integrados; un procesador de doble núcleo (dual-core) tiene dos CPU y un procesador de cuádruple núcleo (quad-core) tiene cuatro CPU. Las computadoras de escritorio de la actualidad tienen procesadores que pueden ejecutar miles de millones de instrucciones por segundo. Unidad de almacenamiento secundario Ésta es la sección de “almacén” de alta capacidad y de larga duración. Los programas o datos que no utilizan las demás unidades con frecuencia se colocan en dispositivos de almacenamiento secundario (por ejemplo, el disco duro) hasta que se requieran de nuevo, lo cual puede llegar a ser horas, días, meses o incluso años después. La información en los dispositivos de almacenamiento secundario es persistente: se conserva aun y cuando se apaga la computadora. El tiempo para acceder a la información en almacenamiento secundario es mucho mayor que el necesario para acceder a la de la memoria principal, pero el costo por unidad de memoria secundaria es mucho menor que el correspondiente a la unidad de memoria principal. Las unidades de CD, DVD y Flash USB son ejemplos de dispositivos de almacenamiento secundario, algunos de los cuales pueden contener hasta 768 GB.Los discos duros típicos en las computadoras de escritorio y portátiles pueden contener hasta 2 TB (TB se refiere a terabytes; un terabyte equivale aproximadamente a un billón de bytes). 1.4Jerarquía de datos Los elementos de datos que procesan las computadoras forman una jerarquía de datos que se vuelve cada vez más grande y compleja en estructura, a medida que progresamos primero a bits, luego a carac- teres, después a campos y así en lo sucesivo. La figura 1.3 ilustra una porción de la jerarquía de datos. La figura 1.4 sintetiza los niveles de la jerarquía de datos. Fig. 1.2  Unidades lógicas de una computadora (parte 2 de 2).
  • 40. 8 Capítulo 1 Introducción a las computadoras y a C++ Tom Azul Sally Negro Judy Verde Archivo J u d y Campo Byte (carácter J ASCII) Registro Iris Naranja Randy Rojo 01001010 1 Bit Judy Verde Fig. 1.3  Jerarquía de datos. Nivel Descripción Bits El elemento de datos más pequeño en una computadora puede asumir el valor 0 o el valor 1. A dicho elemento de datos se le denomina bit (abreviación de “dígito binario”: un dígito que puede asumir uno de dos valores). Es notable que las impresionantes funciones que realizan las computadoras sólo impliquen las manipulaciones más simples de 0s y 1s: examinar el valor de un bit, establecer el valor de un bit e invertir el valor de un bit (de 1 a 0 o de 0 a 1). Caracteres Es tedioso para las personas trabajar con datos en el formato de bajo nivel de los bits. En cambio, prefieren trabajar con dígitos decimales (0–9), letras (A–Z y a–z) y símbolos especiales (por ejemplo, $, @, %, , *, (, ), –, +, “, :, ? y /). Los dígitos, letras y símbolos especiales se conocen como caracteres. El conjunto de caracteres de la computadora es el conjunto de todos los caracteres que se utilizan para escribir programas y representar elementos de datos. Las computadoras sólo procesan 1s y 0s, por lo que cada carácter se representa como un patrón de 1s y 0s. El conjunto de caracteres Unicode® contiene caracteres para muchos de los idiomas en el mundo. C++ soporta varios conjuntos de caracteres, incluyendo los caracteres Unicode® de 16 bits que están compuestos de dos bytes, cada uno de los cuales se compone a su vez de ocho bits. En el apéndice B obtendrá más información sobre el conjunto de caracteres ASCII (Código estándar estadounidense para el intercambio de información): el popular subconjunto de Unicode que representa las letras mayúsculas y minúsculas, los dígitos y algunos caracteres especiales comunes. Campos Así como los caracteres están compuestos de bits, los campos están compuestos de caracteres o bytes.Un campo es un grupo de caracteres o bytes que transmiten un significado. Por ejemplo, un campo compuesto de letras mayúsculas y minúsculas se puede usar para representar el nombre de una persona, y un campo compuesto de dígitos decimales podría representar la edad de esa persona. Fig. 1.4  Niveles de la jerarquía de datos (parte 1 de 2).
  • 41. 1.5 Lenguajes máquina, lenguajes ensambladores y lenguajes de alto nivel 9 Nivel Descripción Registros Se pueden usar varios campos relacionados para componer un registro. Por ejemplo, en un sistema de nómina, el registro de un empleado podría consistir en los siguientes campos (los posibles tipos para estos campos se muestran entre paréntesis): • Número de identificación del empleado (un número entero) • Nombre (una cadena de caracteres) • Dirección (una cadena de caracteres) • Salario por horas (un número con punto decimal) • Ingresos del año a la fecha (un número con punto decimal) • Monto de impuestos retenidos (un número con punto decimal) Así, un registro es un grupo de campos relacionados. En el ejemplo anterior, todos los campos pertenecen al mismo empleado. Una compañía podría tener muchos empleados y un registro de nómina para cada uno. Archivos Un archivo es un grupo de registros relacionados. [Nota: dicho en forma más general, un archivo contiene datos arbitrarios en formatos arbitrarios. En algunos sistemas operativos, un archivo se ve tan sólo como una secuencia de bytes: cualquier organización de esos bytes, como cuando se organizan los datos en registros, es una vista creada por el programador de la aplicación]. Es muy común que una organización tenga muchos archivos, algunos de los cuales pueden contener miles de millones, o incluso billones de caracteres de información. Base de datos Una base de datos es una colección electrónica de datos organizada para facilitar su acceso y manipulación. El modelo de base de datos más popular es la base de datos relacional, en la cual los datos se almacenan en simples tablas. Una tabla incluye registros y campos. Por ejemplo, una tabla de estudiantes podría incluir el primer nombre, apellido paterno, carrera, número de ID de estudiante y promedio de calificaciones. Los datos para cada estudiante conforman un registro y las piezas individuales de información en cada registro son los campos. Es posible realizar búsquedas, ordenar y manipular los datos con base en su relación con varias tablas o bases de datos. Por ejemplo, una universidad podría usar datos de la base de datos de estudiantes en combinación con las bases de datos de los cursos, alojamiento en el campus, planes de alimentación, etc. 1.5Lenguajes máquina, lenguajes ensambladores y lenguajes de alto nivel Los programadores escriben instrucciones en diversos lenguajes de programación, algunos de los cuales los comprende directamente la computadora, mientras que otros requieren pasos intermedios de tra- ducción. Lenguajes máquina Cualquier computadora puede entender de manera directa sólo su propio lenguaje máquina (también conocido como código máquina), el cual se define según su diseño de hardware. Por lo general, los len- guajes máquina consisten en cadenas de números (que finalmente se reducen a 1s y 0s). Dichos lengua- jes son difíciles de comprender para los humanos. Lenguajes ensambladores La programación en lenguaje máquina era demasiado lenta y tediosa para la mayoría de los progra- madores. Por lo tanto, empezaron a utilizar abreviaturas del inglés para representar las operaciones Fig. 1.4  Niveles de la jerarquía de datos (parte 2 de 2).
  • 42. 10 Capítulo 1 Introducción a las computadoras y a C++ elementales. Estas abreviaturas formaron la base de los lenguajes ensambladores. Se desarrollaron programas traductores conocidos como ensambladores para convertir los programas que se encontra- ban en lenguaje ensamblador a lenguaje máquina. Aunque el código en lenguaje ensamblador es más claro para los humanos, las computadoras no lo pueden entender sino hasta que se traduce en lengua- je máquina. Lenguajes de alto nivel Para agilizar el proceso de programación se desarrollaron los lenguajes de alto nivel, en donde podían escribirse instrucciones individuales para realizar tareas importantes. Los lenguajes de alto nivel, como C++, Java, C# y Visual Basic nos permiten escribir instrucciones que son muy similares al inglés y contienen expresiones matemáticas de uso común. Los programas traductores llamados compila- dores convierten los programas que se encuentran en lenguaje de alto nivel a programas en lenguaje máquina. El proceso de compilación de un programa escrito en lenguaje de alto nivel a un lenguaje máqui- na puede tardar un tiempo considerable en la computadora. Los programas intérpretes se desarrolla- ron para ejecutar programas en lenguaje de alto nivel de manera directa (sin el retraso de la compila- ción), aunque con más lentitud de la que se ejecutan los programas compilados. Los lenguajes de secuencias de comandos, como los populares lenguajes JavaScript y PHP para Web, son procesados por intérpretes. Tip de rendimiento 1.1 Los intérpretes tienen una ventaja sobre los compiladores en las secuencias de comandos de Internet. Un programa interpretado puede comenzar a ejecutarse tan pronto como se des- carga en la máquina cliente, sin necesidad de compilarse antes de poder ejecutarse. Por otra parte, las secuencias de comandos interpretadas generalmente se ejecutan con más lentitud que el código compilado. 1.6C++ C++ evolucionó a partir de C, que fue desarrollado por Dennis Ritchie en los laboratorios Bell. C está disponible para la mayoría de las computadoras y es independiente del hardware. Con un dise- ño cuidadoso, es posible escribir programas en C que sean portables para la mayoría de las compu- tadoras. Por desgracia, el amplio uso de C con diversos tipos de computadoras (a las que algunas veces se les denomina plataformas de hardware) produjo muchas variaciones. Era necesaria una versión estándar de C. El Instituto nacional estadounidense de estándares (ANSI) cooperó con la Organización internacio- nal para la estandarización (ISO) para estandarizar C a nivel mundial; el documento estándar colectivo se publicó en 1990, y se conoce como ANSI/ISO 9899: 1990. C11 es el estándar más reciente de ANSI para el lenguaje de programación C. Se desarrolló para que C evolucionara y se mantuviera a la par con el hardware cada vez más poderoso y los requerimientos de los usuarios, que cada vez son más exigentes. C11 también hace a C más consistente con C++. Para obtener más información sobre C y C11, consulte nuestro libro C How to Program, séptima edición y nuestro Centro de recursos de C (que se encuentra en www.deitel.com/C). El lenguaje C++, que es una extensión de C, lo desarrolló Bjarne Stroustrup en 1979, en los labo- ratorios Bell. Conocido en un principio como “C con clases”, se cambió su nombre a C++ a principios de la década de 1980. C++ ofrece varias características que “pulen” al lenguaje C pero, lo más importan- te es que proporciona las capacidades de una programación orientada a objetos. El lector comenzará a desarrollar objetos y clases personalizadas reutilizables en el capítulo 3, Intro- ducción a las clases, objetos y cadenas. El libro está orientado a objetos, en donde sea apropiado, desde el principio y a lo largo del texto.
  • 43. 1.7 Lenguajes de programación 11 También proporcionamos un caso de estudio opcional sobre un cajero automático (ATM) en los capítulos 25 y 26, el cual contiene una implementación completa en C++. El caso de estudio presenta una introducción cuidadosamente pautada al diseño orientado a objetos mediante el UML: un lenguaje de modelado gráfico estándar en la industria para desarrollar sistemas orientados a objetos. Lo guiamos a través de una experiencia de diseño amigable enfocada hacia el principiante. Biblioteca estándar de C++ Los programas en C++ consisten de piezas llamadas clases y funciones. Usted puede programar cada pieza por su cuenta, pero la mayoría de los programadores de C++ aprovechan las extensas colecciones de clases y funciones existentes en la Biblioteca estándar de C++. Por ende, en realidad hay dos partes que debemos conocer en el “mundo” de C++. La primera es aprender acerca del lenguaje C++ en sí; la segunda es aprender a utilizar las clases y funciones en la Biblioteca estándar de C++. A lo largo del libro, hablaremos sobre muchas de estas clases y funciones. El libro de P. J. Plauger, titulado The Standard C Library (Upper Saddle River, NJ: Prentice Hall PTR, 1992) es una lectura obligatoria para los progra- madores que requieren una comprensión detallada de las funciones de la biblioteca de ANSI C que se incluyen en C++. Hay muchas bibliotecas de clases de propósito especial que proporcionan los distri- buidores de software independientes. Observación de Ingeniería de Software 1.1 Utilice un método de “construcción en bloques” para crear programas. Evite reinventar la rueda.Usepiezasexistentessiemprequeseaposible.Estaprácticadenominadareutilización de software es el fundamento de la programación orientada a objetos. Observación de Ingeniería de Software 1.2 Cuando programe en C++, generalmente utilizará los siguientes bloques de construcción: clases y funciones de la Biblioteca estándar de C++, clases y funciones creadas por usted mismo y sus colegas, y clases y funciones de varias bibliotecas populares desarrolladas por terceros. La ventaja de crear sus propias funciones y clases es que sabe exactamente cómo funcionan. Podrá examinar el código de C++. La desventaja es el tiempo que consumen y el esfuerzo complejo que se requiere para diseñar, desarrollar y dar mantenimiento a las nuevas funciones y clases que sean correc- tas y operen con eficiencia. Tip de rendimiento 1.2 Utilizar las funciones y clases de la Biblioteca estándar de C++ en vez de escribir sus propias versiones puede mejorar el rendimiento de sus programas, ya que están escritas cuidadosa- mente para funcionar de manera eficiente. Esta técnica también reduce el tiempo de desa- rrollo de los programas. Tip de portabilidad 1.1 Utilizar las funciones y clases de la Biblioteca estándar de C++ en vez de escribir sus propias versiones mejora la portabilidad de sus programas, ya que estas funciones y clases se incluyen en todas las implementaciones de C++. 1.7Lenguajes de programación En esta sección proporcionaremos comentarios breves sobre varios lenguajes de programación popula- res (figura 1.5).
  • 44. 12 Capítulo 1 Introducción a las computadoras y a C++ Lenguaje de programación Descripción Fortran Fortran (FORmula TRANslator, Traductor de fórmulas) fue desarrollado por IBM Corporation a mediados de la década de 1950 para utilizarse en aplicaciones científicas y de ingeniería que requerían cálculos matemáticos complejos. Aún se utiliza mucho y sus versiones más recientes soportan la programación orientada a objetos. COBOL COBOL (COmmon Business Oriented Language, Lenguaje común orientado a negocios) fue desarrollado a finales de la década de 1950 por fabricantes de computadoras, el gobierno estadounidense y usuarios de computadoras de la industria, con base en un lenguaje desarrollado por Grace Hopper, un oficial de la Marina de Estados Unidos y científico informático. COBOL aún se utiliza mucho en aplicaciones comerciales que requieren de una manipulación precisa y eficiente de grandes volúmenes de datos. Su versión más reciente soporta la programación orientada a objetos. Pascal La investigación en la década de 1960 dio como resultado la programación estructurada: un método disciplinado para escribir programas que sean más claros, fáciles de probar y depurar, y más fáciles de modificar que los programas extensos producidos con técnicas anteriores. Uno de los resultados más tangibles de esta investigación fue el desarrollo de Pascal por el profesor Niklaus Wirth en 1971. Se diseñó para la enseñanza de la programación estructurada y fue popular en los cursos universitarios durante varias décadas. Ada Ada, un lenguaje basado en Pascal, se desarrolló bajo el patrocinio del Departamento de Defensa (DOD) de Estados Unidos durante la década de 1970 y a principios de la década de 1980. El DOD quería un solo lenguaje que pudiera satisfacer la mayoría de sus necesidades. El nombre de este lenguaje basado en Pascal es en honor de Lady Ada Lovelace, hija del poeta Lord Byron. A ella se le atribuye el haber escrito el primer programa para computadoras en el mundo, a principios de la década de 1800 (para la Máquina Analítica, un dispositivo de cómputo mecánico diseñado por Charles Babbage). Ada también soporta la programación orientada a objetos. Basic Basic se desarrolló en la década de 1960 en el Dartmouth College, para que los principiantes se familiarizaran con las técnicas de programación. Muchas de sus versiones más recientes son orientadas a objetos. C C fue implementado en 1972 por Dennis Ritchie en los laboratorios Bell. En un principio se hizo muy popular como el lenguaje de desa- rrollo del sistema operativo UNIX. En la actualidad, la mayoría del código para los sistemas operativos de propósito general se escribe en C o C++. Objective-C Objective-C es un lenguaje orientado a objetos basado en C. Se desarrolló a principios de la década de 1980 y después fue adquirido por la empresa NeXT, que a su vez fue adquirida por Apple. Se ha convertido en el lenguaje de programación clave para el sistema operativo Mac OS X y todos los dispositivos operados por el iOS (como los dispositivos iPod, iPhone e iPad). Fig. 1.5  Algunos otros lenguajes de programación (parte 1 de 3).
  • 45. 1.7 Lenguajes de programación 13 Lenguaje de programación Descripción Java Sun Microsystems patrocinó en 1991 un proyecto de investigación corporativo interno dirigido por James Gosling, que resultó en el lenguaje de programación orientado a objetos basado en C++, conocido como Java. Un objetivo clave de Java es poder escribir programas que se ejecuten en una gran variedad de sistemas de computadora y dispositivos para controlar computadoras. A esto se le conoce algunas veces como “escribir una vez, ejecutar en donde sea”. Java se utiliza para desarrollar aplicaciones empresariales a gran escala, mejorar la funcionalidad de servidores Web (las computadoras que proveen el contenido que vemos en nuestros navegadores Web), proveer aplicaciones en dispositivos para el consumidor (smartphones, tabletas, receptores digitales multimedia, aparatos, automóviles y otros más) y para muchos otros propósitos. Java es también el lenguaje clave para desarrollar aplicaciones Android para smartphones y tabletas. Visual Basic El lenguaje Visual Basic de Microsoft se introdujo a principios de la década de 1990 para simplificar el desarrollo de aplicaciones para Microsoft Windows. Sus versiones más recientes soportan la programación orientada a objetos. C# Los tres principales lenguajes de programación orientados a objetos de Microsoft son Visual Basic (basado en el Basic original), Visual C++ (basado en C++) y C# (basado en C++ y Java; desarrollado para integrar Internet y Web en las aplicaciones de computadora). PHP PHP es un lenguaje orientado a objetos de “secuencias de comandos” y “código fuente abierto” (vea la sección 1.11.2), el cual recibe soporte por medio de una comunidad de usuarios y desarrolladores; se utiliza en numerosos sitios Web, incluyendo Wikipedia y Facebook. PHP es independiente de la plataforma —existen implementaciones para todos los principales sistemas operativos UNIX, Linux, Mac y Windows. PHP también soporta muchas bases de datos, incluyendo MySQL. Perl Perl (Lenguaje práctico para la extracción e informes), uno de los lenguajes de secuencias de comandos orientados a objetos más utilizados para la programación Web, fue desarrollado en 1987 por Larry Wall. Cuenta con extensas herramientas de procesamiento de texto y mucha flexibilidad. Python Python, otro lenguaje de secuencias de comandos orientado a objetos, se liberó al público en 1991. Fue desarrollado por Guido van Rossum del Instituto Nacional de Investigación para las Matemáticas y Ciencias Computacionales en Amsterdam (CWI); la mayor parte de Python se basa en Modula-3 —un lenguaje de programación de sistemas. Python es “extensible”: puede extenderse a través de clases e interfaces de programación. JavaScript JavaScript es el lenguaje de secuencias de comandos más utilizado en el mundo. Su principal uso es para agregar capacidad de programación a las páginas Web; por ejemplo, animaciones e interactividad con el usuario. Se incluye en todos los principales navegadores Web. Fig. 1.5  Algunos otros lenguajes de programación (parte 2 de 3).
  • 46. 14 Capítulo 1 Introducción a las computadoras y a C++ Lenguaje de programación Descripción Ruby on Rails Ruby fue creado a mediados de la década de 1990 por Yukihiro Matsumoto; es un lenguaje de programación orientado a objetos de código fuente abierto, con una sintaxis simple que es similar a Perl y Python. Ruby onRails combina el lenguaje de secuencias de comandos Ruby con el marco de trabajo de aplicaciones Web Rails, desarrollado por 37Signals. Su libro, Getting Real (disponible sin costo en gettingreal.37signals.com/toc.php), es una lectura obligatoria para los desarrolladores Web. Muchos desarrolladores de Ruby onRails han reportado ganancias de productividad superiores a las de otros lenguajes, al utilizar aplicaciones Web que trabajan de manera intensiva con bases de datos. Ruby on Rails se utilizó para crear la interfaz de usuario de Twitter. Scala Scala (www.scala-lang.org/node/273) abreviación en inglés de “lenguaje escalable”, fue diseñado por Martin Odersky, un profesor en la École Polytechnique Fédérale de Lausanne (EPFL) en Suiza. Se lanzó al público en 2003; utiliza los paradigmas de orientación a objetos y de programación funcional, y está diseñado para integrarse con Java. Si programa en Scala, podrá reducir de manera considerable la cantidad de código en sus aplicaciones. Twitter y Foursquare usan Scala. Fig. 1.5  Algunos otros lenguajes de programación (parte 3 de 3). 1.8Introducción a la tecnología de objetos Crear software en forma rápida, correcta y económica sigue siendo un objetivo difícil de alcanzar en una época en que la demanda de software nuevo y más poderoso va en aumento.Los objetos, o dicho en forma más precisa (como veremos en el capítulo 3), las clases de las que provienen los objetos, son en esencia componentes de software reutilizables. Existen objetos de fecha, de hora, de audio, de video, de automóviles, de personas, etc. Casi cualquier sustantivo se puede representar de manera razonable como un objeto de software en términos de sus atributos (como el nombre, color y tamaño) y comporta- mientos (por ejemplo, calcular, moverse y comunicarse). Los desarrolladores de software han descubier- to que al usar una metodología de diseño e implementación orientada a objetos y modular, pueden crear grupos de desarrollo de software más productivos de lo que era posible con las técnicas anteriores; por lo general los programas orientados a objetos son más fáciles de comprender, corregir y modificar. El automóvil como un objeto Empecemos con una analogía simple. Suponga que desea conducir un automóvil y hacer que vaya más rápido al oprimir el pedal del acelerador. ¿Qué debe ocurrir para que usted pueda hacer esto? Bueno, antes de que pueda conducir un automóvil, alguien tiene que diseñarlo. Por lo general, un automóvil empieza en forma de dibujos de ingeniería, similares a los planos de construcción que describen el diseño de una casa. Estos dibujos de ingeniería incluyen el diseño del pedal del acelerador. El pedal oculta al conductor los complejos mecanismos que se encargan de que el automóvil aumente su velocidad, de igual forma que el pedal del freno oculta los mecanismos que disminuyen la velocidad del automóvil y el volante oculta los mecanismos que hacen que el automóvil dé vuelta. Esto permite que las personas con poco o nada de conocimiento acerca de cómo funcionan los motores, los frenos y los mecanismos de la dirección puedan conducir un automóvil con facilidad.
  • 47. 1.8 Introducción a la tecnología de objetos 15 Antes de poder conducir un automóvil, éste debe construirse a partir de los dibujos de ingeniería que lo describen. Un automóvil completo tendrá un pedal acelerador verdadero para hacer que aumente su velocidad, pero aun así no es suficiente; el automóvil no acelerará por su propia cuenta (¡esperemos que así sea!), así que el conductor debe oprimir el pedal del acelerador para aumentar la velocidad del automóvil. Funciones miembro y clases Ahora vamos a utilizar nuestro ejemplo del automóvil para introducir algunos conceptos clave de la programación orientada a objetos. Para realizar una tarea en una aplicación se requiere una función miembro. Esa función miembro aloja las instrucciones del programa que se encargan de realizar sus tareas. Oculta al usuario estas tareas, de la misma forma que el pedal del acelerador de un automóvil oculta al conductor los mecanismos para hacer que el automóvil vaya más rápido. En C++ creamos una unidad de programa llamada clase para alojar el conjunto de funciones miembro que realizan las tareas de esa clase. Por ejemplo, una clase que representa a una cuenta bancaria podría contener una función miembro para depositar dinero en una cuenta, otra para retirar dinero de una cuenta y una tercera para solicitar el saldo actual de la cuenta. Una clase es similar en concepto a los dibujos de ingeniería de un automóvil, que contienen el diseño de un pedal acelerador, volante de dirección, etcétera. Instanciamiento Así como alguien tiene que construir un automóvil a partir de sus dibujos de ingeniería para que alguien pueda conducirlo después, también es necesario crear un objeto de una clase para que un programa pue- da realizar las tareas definidas por los métodos de esa clase. Al proceso de hacer esto se le denomina instanciamiento. Entonces, un objeto viene siendo una instancia de su clase. Reutilización Así como los dibujos de ingeniería de un automóvil se pueden reutilizar muchas veces para construir muchos automóviles, también es posible reutilizar una clase muchas veces para crear muchos objetos. Al reutilizar las clases existentes para crear nuevas clases y programas, ahorramos tiempo y esfuerzo. La reutilización también nos ayuda a crear sistemas más confiables y efectivos, debido a que con frecuen- cia las clases y los componentes existentes pasan por un extenso proceso de prueba, depuración y opti- mización del desempeño. De la misma manera en que la noción de piezas intercambiables fue crucial para la Revolución Industrial, las clases reutilizables son cruciales para la revolución de software incitada por la tecnología de objetos. Mensajes y llamadas a funciones miembro Cuando conduce un automóvil, al oprimir el pedal del acelerador envía un mensaje al automóvil para que realice una tarea: aumentar la velocidad. De manera similar, es posible enviar mensajes a un objeto. Cada mensaje se implementa como llamada a función miembro, para indicar a una función miembro del objeto que realice su tarea. Por ejemplo, un programa podría llamar al método depositar de un obje- to cuenta de banco específico para incrementar el saldo de esa cuenta. Atributos y miembros de datos Además de tener capacidades para realizar tareas, un automóvil también tiene atributos: color, número de puertas, cantidad de gasolina en el tanque, velocidad actual y registro del total de kilómetros recorri- dos (es decir, la lectura de su velocímetro). Al igual que sus capacidades, los atributos del automóvil se representan como parte de su diseño en sus diagramas de ingeniería (que, por ejemplo, incluyen un velocímetro y un indicador de combustible). Al conducir un automóvil real, estos atributos se llevan junto con el automóvil. Cada automóvil mantiene sus propios atributos. Por ejemplo, cada uno sabe cuánta gasolina hay en su tanque, pero no cuánta hay en los tanques de otros automóviles.
  • 48. 16 Capítulo 1 Introducción a las computadoras y a C++ De manera similar, un objeto tiene atributos que lleva consigo a medida que se utiliza en un progra- ma. Estos atributos se especifican como parte de la clase del objeto. Por ejemplo, un objeto cuenta ban- caria tiene un atributo saldo que representa la cantidad de dinero en la cuenta. Cada objeto cuenta banca- ria conoce el saldo de la cuenta que representa, pero no los saldos de las otras cuentas en el banco. Los atributos se especifican mediante los miembros de datos de la clase. Encapsulamiento Las clases encapsulan (envuelven) los atributos y las funciones miembro en objetos; los atributos y las funciones miembro de un objeto están muy relacionados entre sí. Los objetos se pueden comunicar entre sí, pero por lo general no se les permite saber cómo están implementados otros objetos; los detalles de implementación están ocultos dentro de los mismos objetos. Este ocultamiento de información es, como veremos, crucial para la buena ingeniería de software. Herencia Es posible crear una nueva clase de objetos con rapidez y de manera conveniente mediante la heren- cia: la nueva clase absorbe las características de una clase existente, con la posibilidad de personalizar- las y agregar características únicas propias. En nuestra analogía del automóvil, sin duda un objeto de la clase “convertible” es un objeto de la clase más general llamada “automóvil” pero, de manera más específica, el techo puede ponerse o quitarse. Análisis y diseño orientado a objetos (A/DOO) Pronto escribirá programas en C++. ¿Cómo creará el código (es decir, las instrucciones) para sus programas? Tal vez, al igual que muchos programadores, sólo encenderá su computadora y empezará a escribir. Quizás este método funcione para pequeños programas (como los que presentamos en los primeros capítulos del libro), pero ¿qué tal si le pidieran crear un sistema de software para controlar miles de cajeros automáticos para un banco importante? O ¿qué tal si le piden que trabaje con un equipo de miles de desarrolladores de software para crear el nuevo sistema de control de tráfico aéreo en Estados Unidos? Para proyectos tan grandes y complejos, no es conveniente tan sólo sentarse y empezar a escribir programas. Para crear las mejores soluciones, debe seguir un proceso de análisis detallado para determinar los requerimientos de su proyecto (definir qué se supone que debe hacer el sistema) y desarrollar un diseño que los satisfaga (decidir cómo debe hacerlo el sistema). Lo ideal sería pasar por este proceso y revisar el diseño con cuidado (además de pedir a otros profesionales de software que revisen su diseño) antes de escribir cualquier código. Si este proceso implica analizar y diseñar su sistema desde un punto de vista orientado a objetos, se denomina proceso de análisis y diseño orientado a objetos (A/DOO). Los lenguajes como C++ son orientados a objetos. La programación en un lenguaje de este tipo, conocida como programación orientada a objetos (POO), le permite implementar un diseño orientado a ob- jetos como un sistema funcional. El UML (Lenguaje Unificado de Modelado) Aunque existen muchos procesos de A/DOO distintos, hay un solo lenguaje gráfico para comunicar los resultados de cualquier proceso de A/DOO que se utiliza en la mayoría de los casos. Este lenguaje, co- nocido como Lenguaje Unificado de Modelado (UML), es en la actualidad el esquema gráfico más utilizado para modelar sistemas orientados a objetos. Presentamos nuestros primeros diagramas de UML en los capítulos 3 y 4; después los utilizamos en nuestro análisis más detallado de la programación orientada a objetos en el capítulo 12. En nuestro caso de estudio opcional de ingeniería de software del ATM en los capítulos 25 y 26 presentamos un subconjunto simple de las características del UML, mientras lo guiamos por una experiencia de diseño orientada a objetos.
  • 49. 1.9 Entorno de desarrollo común en C++ 17 1.9Entorno de desarrollo común en C++ Por lo general, los sistemas de C++ consisten en tres partes: un entorno de desarrollo de programas, el lenguaje y la Biblioteca estándar de C++. Comúnmente, los programas en C++ pasan a través de seis fases: edición, preprocesamiento, compilación, enlace, carga y ejecución. A continuación explicaremos un típico entorno de desarrollo de programas en C++. Fase 1: Edición (Creación) de un programa La fase 1 consiste en editar un archivo con un programa de edición, conocido comúnmente como editor (figura1.6).UstedescribeunprogramaenC++(conocidoporlogeneralcomocódigofuente)utilizando el editor, realiza las correcciones necesarias y guarda el programa en un dispositivo de almacenamiento secundario, tal como su disco duro. A menudo, los nombres de archivos de código fuente en C++ terminan con las extensiones .cpp, .cxx, .cc o .C (observe que C está en mayúsculas), para indicar que un archivo contiene código fuente en C++. Consulte la documentación para su compilador de C++ si desea obtener más información acerca de las extensiones de nombres de archivos. Disco Editor Fase 1: El programador crea el programa en el editor y lo almacena en disco. Fig. 1.6  Entorno de desarrollo común en C++: fase de edición. Dos de los editores que se utilizan ampliamente en sistemas Linux son vi y emacs. Los paquetes de software de C++ para Microsoft Windows tales como Microsoft Visual C++ (microsoft.com/express eninglés,ywww.microsoft.com/visualstudio/esn#products/visual-studio-express-products en español) tienen editores integrados en el entorno de programación.También puede utilizar un editor de texto simple, como el Bloc de notas en Windows, para escribir su código de C++. Para las organizaciones que desarrollan sistemas de información de un tamaño considerable, hay entornos integrados de desarrollo (IDE) disponibles de muchos de los principales proveedores de software. Los IDE ofrecen herramientas para apoyar el proceso de desarrollo de software, incluyendo editores para escribir y editar programas, y depuradores para localizar errores lógicos: errores que pro- vocan que los programas se ejecuten en forma incorrecta. Los IDE populares son: Microsoft® Visual Studio 2012 Express Edition, Dev C++, NetBeans, Eclipse, Xcode de Apple y CodeLite. Fase 2: Preprocesamiento de un programa en C++ En la fase 2, se introduce el comando para compilar el programa (figura 1.7). En un sistema de C++, unprogramapreprocesadorseejecutademaneraautomáticaantesdequeempiecelafasedetraducción del compilador (por lo que a la fase 2 la llamamos preprocesamiento, y a la fase 3 la llamamos compila- ción). El preprocesador de C++ obedece a comandos denominados directivas del preprocesador, las cuales indican que deben realizarse ciertas manipulaciones en el programa, antes de compilarlo. Por lo general, estas manipulaciones incluyen la compilación de otros archivos de texto y realizan varios reem- plazos de texto. Las directivas más comunes del preprocesador se describen en los primeros capítulos; en el apéndice E, Preprocesador, aparece una discusión detallada acerca de las características del preproce- sador. Disco Preprocesador Fase 2: El programa preprocesador procesa el código. Fig. 1.7  Entorno de desarrollo común en C++: fase del preprocesador.
  • 50. 18 Capítulo 1 Introducción a las computadoras y a C++ Fase 3: Compilación de un programa de C++ En la fase 3, el compilador traduce el programa de C++ en código de lenguaje máquina (también cono- cido como código objeto) (figura 1.8). Disco Compilador Fase 3: El compilador crea el código objeto y lo almacena en disco. Fig. 1.8  Entorno de desarrollo común en C++: fase de compilación. Fase 4: Enlace Alafase4selellamaenlace.Porlogeneral,losprogramasenC++contienenreferenciasafuncionesydatos definidos en otra parte, como en las bibliotecas estándar o en las bibliotecas privadas de grupos de programadores que trabajan sobre un proyecto específico (figura 1.9). El código objeto producido por el compilador de C++ comúnmente contiene “huecos”, debido a estas partes faltantes. Un enlazador relaciona el código objeto con el código para las funciones faltantes, de manera que se produzca un programa ejecutable (sin piezas faltantes). Si el programa se compila y se enlaza de manera correcta, se produce una imagen ejecutable. Disco Enlazador Fase 4: El enlazador relaciona el código objeto con las bibliotecas, crea un archivo ejecutable y lo almacena en disco. Fig. 1.9  Entorno de desarrollo común en C++: fase de enlace. Fase 5: Carga A la fase 5 se le conoce como carga. Antes de poder ejecutar un programa, primero se debe colocar en la memoria (figura 1.10). Esto se hace mediante el cargador, que toma la imagen ejecutable del disco y la transfiere a la memoria.También se cargan los componentes adicionales de bibliotecas compartidas que dan soporte al programa. Disco Cargador Fase 5: El cargador coloca el programa en la memoria. ... Memoria principal ... Fig. 1.10  Entorno de desarrollo común en C++: fase de carga.
  • 51. 1.10 Prueba de una aplicación de C++ 19 Fase 6: Ejecución Por último, la computadora, bajo el control de su CPU, ejecuta el programa una instrucción a la vez (figura 1.11). Algunas arquitecturas de cómputo modernas pueden ejecutar varias instrucciones en paralelo. CPU Fase 6: La CPU toma cada instrucción y la ejecuta; posiblemente almacena nuevos valores de datos a medida que se ejecuta el programa. ... Memoria principal ... Fig. 1.11  Entorno de desarrollo común en C++: fase de ejecución. Problemas que pueden ocurrir en tiempo de ejecución Esprobable que losprogramasnofuncionen la primera vez. Cada una de las fases anteriores puede fallar, debido a diversos errores que describiremos en este texto. Por ejemplo, un programa en ejecución podría intentar una división entre cero (una operación ilegal para la aritmética con números enteros en C++). Esto haría que el programa de C++ mostrara un mensaje de error. Si esto ocurre, tendría que regresar a la fase de edición, hacer las correcciones necesarias y proseguir con las fases restantes de nuevo, para determinar que las correcciones hayan resuelto el(los) problema(s). [Nota: la mayoría de los programas en C++ reciben y/o producen datos. Ciertas funciones de C++ toman su entrada de cin (el flujo están- dar de entrada; se pronuncia “c-in”), que por lo general es el teclado, pero cin puede redirigirse a otro dispositivo. A menudo los datos se envían a cout (el flujo estándar de salida), que por lo general es la pantalla de la computadora, pero cout puede redirigirse a otro dispositivo. Cuando decimos que un programa imprime un resultado, por lo general nos referimos a que el resultado se despliega en una pantalla. Los datos pueden enviarse a otros dispositivos, como los discos y las impresoras.También hay un flujo estándar de error, conocido como cerr. El flujo cerr (que por lo general se conecta a la pan- talla) se utiliza para mostrar mensajes de error. Error común de programación 1.1 Los errores, como la división entre cero, ocurren a medida que se ejecuta un programa, por lo cual se les llama errores en tiempo de ejecución. Los errores fatales en tiempo de ejecución hacen que los programas terminen inmediatamente, sin haber realizado correc- tamente su trabajo. Los errores no fatales en tiempo de ejecución permiten a los programas ejecutarse hasta terminar su trabajo, lo que a menudo produce resultados incorrectos. 1.10Prueba de una aplicación de C++ En esta sección, ejecutará su primera aplicación en C++ e interactuará con ella. Empezará ejecutando un divertido juego de “adivinar el número”, el cual elije un número del 1 al 1000 y le pide que lo adivi- ne. Si su elección es la correcta, el juego termina. Si no es correcta, la aplicación le indica si su elección
  • 52. 20 Capítulo 1 Introducción a las computadoras y a C++ es mayor o menor que el número correcto. No hay límite en cuanto al número de intentos que puede realizar[Nota:sóloparaestapruebahemosmodificadoestaaplicacióndelejercicioquelepediremosque cree en el capítulo 6, Funciones y una introducción a la recursividad. Por lo general, esta aplicación se- lecciona al azar la respuesta correcta cuando usted ejecuta el programa. La aplicación modificada utiliza la misma respuesta correcta cada vez que el programa se ejecuta (aunque esto podría variar según el compilador), por lo que puede utilizar las mismas elecciones que utilizamos en esta sección, y verá los mismos resultados a medida que interactúe con su primera aplicación en C++]. Demostraremos cómo ejecutar una aplicación de C++ mediante el Símbolo del sistema de Windows y mediante un shell en Linux. La aplicación se ejecuta de manera similar en ambas pla- taformas. Hay muchos entornos de desarrollo disponibles en los cuales los lectores pueden compi- lar, generar y ejecutar aplicaciones de C++, como GNU C++, Microsoft Visual C++, Apple Xcode, Dev C++, CodeLite, NetBeans, Eclipse, etc. Consulte con su instructor para obtener información acerca de su entorno de desarrollo específico. En los siguientes pasos, ejecutará la aplicación y escribirá varios números para adivinar el nú- mero correcto. Los elementos y la funcionalidad que puede ver en esta aplicación son típicos de los que aprenderá a programar en este libro. A lo largo del mismo, utilizamos distintos tipos de letra para diferenciar entre las características que se pueden ver en la pantalla (como el Símbolo del siste- ma) y los elementos que no están directamente relacionados con la pantalla. Enfatizamos las carac- terísticas de la pantalla, como los títulos y los menús (como el menú Archivo menu) en un tipo de letra Helvetica sans-serif en semi-negritas, y enfatizamos los nombres de archivo, el texto desplegado por una aplicación y los valores que debe introducir en una aplicación (como AdivinarNumero o 500) en un tipo de letra Lucida sans-serif. Como tal vez ya se ha dado cuenta, la ocurrencia de definición de cada término se establece en negritas. En las figuras en esta sección, señalamos las partes importantes de la aplicación. Para aumentar la visibilidad de estas características, modifica- mos el color de fondo de la ventana del Símbolo del sistema (sólo para la prueba en Windows). Para modificar los colores del Símbolo del sistema en su sistema, abra una ventana Símbolo del sistema seleccionando Inicio Todos los programas Accesorios Símbolo del sistema, después haga clic con el botón derecho del ratón en la barra de título y seleccione Propiedades. En el cuadro de diálogo Propiedades de Símbolo del sistema que aparezca, haga clic en la ficha Colores y seleccione sus colores de texto y fondo preferidos. Ejecución de una aplicación de C++ desde el Símbolo del sistema de Windows 1. Revise su configuración. Es importante que lea la sección Antes de empezar en www.deitel. com/books/cpphtp9/ para confirmar que haya copiado correctamente los ejemplos del libro en su disco duro. 2. Localice la aplicación completa. Abra una ventana Símbolo del sistema window. Para cam- biar al directorio de la aplicación completa AdivinarNumero, escriba cd C:ejemploscap01 AdivinarNumeroWindows y después oprima Intro (figura 1.12). El comando cd se utiliza para cambiar de directorio. Fig. 1.12  Abrir una ventana Símbolo del sistema y cambiar de directorio.
  • 53. 1.10 Prueba de una aplicación de C++ 21 3. Ejecute la aplicación AdivinarNumero. Ahora que se encuentra en el directorio que contiene la aplicación AdivinarNumero escriba el comando AdivinarNumero (figura 1.13) y oprima Intro. [Nota: AdivinarNumero.exe es el nombre real de la aplicación; sin embargo, Windows asume la extensión .exe de manera predeterminada]. Fig. 1.13  Ejecución de la aplicación AdivinarNumero. 4. Escriba su primer intento. La aplicación muestra el mensaje Escriba su primer intento. y después muestra un signo de interrogación (?) como un indicador en la siguiente línea (figu- ra 1.13). En el indicador, escriba 200 (figura 1.14). Fig. 1.14  Escriba su primer intento. 5. Intente de nuevo. La aplicación muestra Demasiado alto. Intente de nuevo. lo cual significa que el valor que escribió es mayor que el número que eligió la aplicación como la respuesta correcta. Por lo tanto, debe escribir un número menor como su siguiente in- tento. En el indicador, escriba 100 (figura 1.15). La aplicación muestra de nuevo el men- saje Demasiado alto. Intente de nuevo., ya que el valor que escribió sigue siendo mayor que el número que eligió la aplicación como la respuesta correcta. Fig. 1.15  Intente nuevamente y reciba retroalimentación. 6. Escriba más elecciones. Continúe el juego, escribiendo valores hasta que adivine el número correcto. La aplicación mostrará el mensaje Excelente! Adivino el numero! (figura1.16).
  • 54. 22 Capítulo 1 Introducción a las computadoras y a C++ Fig. 1.16  Escriba más elecciones y adivine el número correcto. 7. Juegue de nuevo o salga de la aplicación. Una vez que adivine la respuesta correcta, la aplicación le preguntará si desea jugar otra vez (figura 1.16). En el indicador Le gustaria jugar de nuevo (s o n)? al escribir el carácter s la aplicación elegirá un nuevo número y mostrará el mensaje Escriba su primer intento. seguido de un signo de interrogación como indicador (figura 1.17), de manera que pueda escribir su primer intento en el nuevo juego. Al escribir el carácter n la aplicación termina y el sistema nos regresa al directorio de la aplicación en el Símbolo del sistema (figura 1.18). Cada vez que ejecute esta aplicación desde el principio (es decir, desde el paso 3), elegirá los mismos números para que usted adivine. 8. Cierre la ventana Símbolo del sistema. Fig. 1.17  Juegue de nuevo. Fig. 1.18  Salga del juego.
  • 55. 1.10 Prueba de una aplicación de C++ 23 Ejecución de una aplicación de C++ mediante GNU C++ con Linux Para esta prueba, vamos a suponer que usted sabe cómo copiar los ejemplos en su directorio de inicio. Consulte con su instructor si tiene dudas acerca de cómo copiar los archivos en su sistema Linux. Ade- más, para las figuras en esta sección utilizamos texto resaltado en negritas para indicar la entrada del usuariorequeridaencadapaso.Elindicadorenelshellennuestrosistemautilizaelcaráctertilde(~)para representar el directorio de inicio, y cada indicador termina con el carácter de signo de dólar ($). El in- dicador puede variar de un sistema Linux a otro. 1. Localice la aplicación completa. Desde un shell en Linux, cambie al directorio de la aplica- ción AdivinarNumero completa (figura 1.19); para ello escriba cd Ejemplos/cap01/AdivinarNumero/GNU_Linux y oprima Intro. El comando cd se utiliza para cambiar de directorio. ~$ cd ejemplos/cap01/AdivinarNumero/GNU_Linux ~/ejemplos/cap01/AdivinarNumero/GNU_Linux$ Fig. 1.19  Cambie al directorio de la aplicación AdivinarNumero. 2. Compile la aplicación AdivinarNumero. Para ejecutar una aplicación en el compilador GNU C++, primero debe compilarla; para ello escriba g++ AdivinarNumero.cpp –o AdivinarNumero como en la figura 1.20. Este comando compila la aplicación y produce un archivo ejecutable, llamado AdivinarNumero. ~/ejemplos/cap01/AdivinarNumero/GNU_Linux$ g++ AdivinarNumero.cpp –o Adivinar- Numero ~/ejemplos/cap01/AdivinarNumero/GNU_Linux$ Fig. 1.20  Compile la aplicación AdivinarNumero usando el comando g++. 3. Ejecute la aplicación AdivinarNumero. Para ejecutar el archivo AdivinarNumero, escriba ./AdivinarNumero en el siguiente indicador y luego oprima Intro (figura 1.21). ~/ejemplos/cap01/AdivinarNumero/GNU_Linux$ ./AdivinarNumero Tengo un numero entre 1 y 1000. Puede adivinar mi numero? Escriba su primer intento. ? Fig. 1.21  Ejecución de la aplicacióne AdivinarNumero. 4. Escribasuprimerintento.LaaplicaciónmuestraelmensajeEscriba su primer intento., y después muestra un signo de interrogación (?) como un indicador en la siguiente línea (fi- gura 1.21). En el indicador, escriba 100 (figura 1.22). [Nota: ésta es la misma aplicación que modificamos y probamos para Windows, pero los resultados podrían variar, dependiendo del compilador que se utilice].
  • 56. 24 Capítulo 1 Introducción a las computadoras y a C++ 5. Intente de nuevo. La aplicación muestra Demasiado alto. Intente de nuevo., lo cual significa que el valor que escribió es mayor que el número que eligió la aplicación como la respuesta correcta (figura 1.22). En el siguiente indicador, escriba 30 (figura 1.23). Esta vez la aplicación muestra el mensaje Demasiado bajo. Intente de nuevo., ya que el valor que escribió es menor que el número que la respuesta correcta. 6. Escriba más elecciones. Continúe el juego (figura 1.24), escribiendo valores hasta que adivi- ne el número correcto. Cuando adivine la respuesta correcta, la aplicación mostrará el mensa- je Excelente! Adivino el numero. ~/ejemplos/cap01/AdivinarNumero/GNU_Linux$ ./AdivinarNumero Tengo un numero entre 1 y 1000. Puede adivinar mi numero? Escriba su primer intento. ? 100 Demasiado alto. Intente de nuevo. ? Fig. 1.22  Escriba su elección inicial. ~/ejemplos/cap01/AdivinarNumero/GNU_Linux$ ./AdivinarNumero Tengo un numero entre 1 y 1000. Puede adivinar mi numero? Escriba su primer intento. ? 100 Demasiado alto. Intente de nuevo. ? 30 Demasiado bajo. Intente de nuevo. ? Fig. 1.23  Intente de nuevo y reciba retroalimentación. Demasiado bajo. Intente de nuevo. ? 40 Demasiado bajo. Intente de nuevo. ? 60 Demasiado alto. Intente de nuevo. ? 50 Demasiado alto. Intente de nuevo. ? 45 Demasiado alto. Intente de nuevo. ? 41 Demasiado bajo. Intente de nuevo. ? 44 Demasiado alto. Intente de nuevo. ? 43 Demasiado alto. Intente de nuevo. ? 42 Excelente! Adivino el numero! Le gustaria jugar de nuevo (s o n)? Fig. 1.24  Escriba más elecciones y adivine el número correcto.
  • 57. 1.11 Sistemas operativos 25 7. Juegue de nuevo o salga de la aplicación. Una vez que adivine la respuesta correcta, la aplicación le preguntará si desea jugar otra vez. En el indicador Le gustaria jugar de nuevo (s o n)? al escribir el carácter s la aplicación elegirá un nuevo número y mostrará el mensa- je “Escriba su primer intento.” seguido de un signo de interrogación como indicador (fi- gura 1.25), de manera que pueda escribir su primera elección en el nuevo juego. Al escribir el carácter n la aplicación termina y el sistema nos regresa al directorio de la aplicación en el shell (figura 1.26). Cada vez que ejecute esta aplicación desde el principio (es decir, desde el paso 3), elegirá los mismos números para que usted adivine. Excelente! Adivino el numero! Le gustaria jugar de nuevo (s o n)? s Tengo un numero entre 1 y 1000. Puede adivinar mi numero? Escriba su primer intento. ? Fig. 1.25  Juegue de nuevo. Excelente! Adivino el numero! Le gustaria jugar de nuevo (s o n)? n ~/ejemplos/cap01/AdivinarNumero/GNU_Linux$ Fig. 1.26  Salga del juego. 1.11Sistemas operativos Los sistemas operativos son sistemas de software que se encargan de hacer más conveniente el uso de las computadoras para los usuarios, desarrolladores de aplicaciones y administradores de sistemas. Pro- veen servicios que permiten a cada aplicación ejecutarse en forma segura, eficiente y concurrente (es decir, en paralelo) con otras aplicaciones. El software que contiene los componentes básicos del sistema operativo se denomina kernel. Los sistemas operativos de escritorio populares son: Linux, Windows y OS X (conocido anteriormente como Mac OS X); utilizamos estos tres en el desarrollo del libro. Los sistemasoperativosmóvilespopularesqueseutilizanensmartphonesytabletasson:AndroiddeGoogle, Apple iOS (para sus dispositivos iPhone, iPad e iPod Touch), BlackBerry OS y Windows Phone. Es posible desarrollar aplicaciones en C++ para los siguientes sistemas operativos clave, incluyendo varios de los más recientes sistemas operativos móviles. 1.11.1Windows: un sistema operativo propietario A mediados de la década de 1980 Microsoft desarrolló el sistema operativo Windows, el cual consiste en una interfaz gráfica de usuario creada sobre DOS: un sistema operativo de computadora personal muy popular con el que los usuarios interactuaban al teclear comandos. Windows tomó prestados mu- chos conceptos (como los iconos, menús y ventanas) desarrollados en un principio por Xerox PARC y que se hicieron populares gracias a los primeros sistemas operativos Apple Macintosh. Windows 8 es el
  • 58. 26 Capítulo 1 Introducción a las computadoras y a C++ sistema operativo más reciente de Microsoft; sus características incluyen mejoras en la interfaz de usua- rio, un arranque más veloz, un mayor grado de refinamiento en cuanto a las características de seguridad, soporte para pantalla táctil y multitáctil, y otras cosas más.Windows es un sistema operativo propietario; está bajo el control exclusivo de Microsoft. Windows es por mucho el sistema operativo de escritorio más utilizado en el mundo. 1.11.2Linux: un sistema operativo de código fuente abierto El sistema operativo Linux es tal vez el más grande éxito del movimiento de código fuente abierto. El software de código fuente abierto se desvía del estilo de desarrollo de software propietario, el cual predominó durante los primeros años del software. Con el desarrollo de código fuente abierto, indivi- duos y compañías contribuyen sus esfuerzos para desarrollar, mantener y evolucionar el software a cam- bio del derecho de usarlo para sus propios fines, por lo general sin costo. A menudo el código fuente abierto es escudriñado por una audiencia mucho mayor que la del software propietario, de modo que casi siempre los errores se eliminan con más rapidez. El código fuente abierto también fomenta una mayor innovación. Las compañías de sistemas empresariales como IBM, Oracle y muchas otras, han realizado inversiones considerables en el desarrollo del código fuente abierto de Linux. Algunas organizaciones clave en la comunidad de código fuente abierto son: la fundación Eclipse (el Entorno integrado de desarrollo Eclipse ayuda a los programadores a desarrollar software de manera conveniente), la fundación Mozilla (creadores del navegador Web Firefox), la fundación de software Apache (creadores del servidor Web Apache que se utiliza para desarrollar aplicaciones basadas en Web) y SourceForge (quien proporciona las herramientas para administrar proyectos de código fuente abier- to; cientos de miles de estos proyectos en desarrollo). Las rápidas mejoras en la computación y las comu- nicaciones, la reducción en costos y el software de código fuente abierto han logrado que sea mucho más fácil y económico crear un negocio basado en software en la actualidad de lo que era hace tan sólo una década. Facebook es un gran ejemplo de ello; este sitio se inició desde un dormitorio universitario y se creó con software de código fuente abierto. El kernel de Linux es el núcleo del sistema operativo de código fuente abierto más popular y lleno de funcionalidades, que se distribuye en forma gratuita. Es desarrollado por un equipo de vo- luntarios organizados de manera informal; es popular en servidores, computadoras personales y sis- temas incrustados. A diferencia de los sistemas operativos propietarios como Microsoft Windows y Apple OS X, el código fuente de Linux (el código del programa) está disponible al público para que lo examinen y modifiquen; además se puede descargar e instalar sin costo. Como resultado, los usua- rios de Linux se benefician de una comunidad de desarrolladores que depuran y mejoran el kernel de manera continua, y de la habilidad de poder personalizar el sistema operativo para cumplir necesida- des específicas. Son varias cuestiones —el poder de mercado de Microsoft, el pequeño número de aplicaciones Linux amigables para los usuarios y la diversidad de distribuciones de Linux, tales como Red Hat Linux, Ubuntu Linux y muchas más— las que han impedido que se popularice el uso de Linux en las compu- tadoras de escritorio. Pero este sistema operativo se ha vuelto muy popular en servidores y sistemas in- crustados, como los teléfonos inteligentes basados en Android de Google. 1.11.3OS X de Apple: iOS de Apple para dispositivos iPhone®, iPad® y iPod Touch® SteveJobsySteveWozniakfundaronAppleen1976,queseconvirtiórápidamenteenlíderenlacompu- tación personal. En 1979, Jobs y varios empleados de Apple visitaron Xerox PARC (Centro de investi- gación de Palo Alto) para aprender sobre la computadora de escritorio de Xerox que contaba con una interfaz gráfica de usuario (GUI). Esa GUI sirvió como inspiración para la Apple Macintosh, que se lanzó con muchas fanfarrias en un memorable anuncio del Súper Tazón de 1984.
  • 59. 1.12 Internet y World Wide Web 27 El lenguaje de programación Objective-C, creado por Brad Cox y Tom Love en Stepstone a prin- cipios de la década de 1980, agregó capacidades de programación orientada a objetos (OOP) al lengua- jedeprogramaciónC.Almomentodeescribirestelibro,Objective-Csecomparabaenpopularidadcon C++.6 Steve Jobs dejó Apple en 1985 y fundó NeXT, Inc. En 1988, NeXT obtuvo la licencia para Ob- jective-C de StepStone y desarrolló un compilador de Objective-C además de varias bibliotecas, lo cual se usó como la plataforma para la interfaz de usuario el sistema operativo NEXTSTEP y para Interface Builder (que se usaba para construir interfaces gráficas de usuario). Jobs regresó a Apple en 1996, cuando esta empresa compró NeXT. El sistema operativo OS X de Apple es descendiente de NeXTSTEP. El sistema operativo propietario de Apple, iOS, se deriva del Apple OS X y se utiliza en los dispositivos iPhone, iPad y iPod Touch. 1.11.4Android de Google Android —el sistema operativo para dispositivos móviles y smartphones, cuyo crecimiento ha sido el más rápido hasta ahora— está basado en el kernel de Linux y en Java. Los programadores experimenta- dos de Java no tienen problemas para entrar y participar con rapidez en el desarrollo de aplicaciones para Android. Un beneficio de desarrollar este tipo de aplicaciones es el grado de apertura de la plataforma. El sistema operativo es gratuito y de código fuente abierto. El sistema operativo Android fue desarrollado por Android, Inc., compañía que adquirió Google en 2005. En 2007 se formó la Alianza para los dispositivos móviles abiertos™ (OHA) —un consor- cio de 34 compañías en un principio que creció a 84 para 2011—, para continuar con el desarrollo de Android. Al mes de junio de 2012, ¡se activaban más de 900000 teléfonos inteligentes con An- droid a diario!7 Ahora los smartphones Android se venden más que los iPhone en Estados Unidos.8 El sistema operativo Android se utiliza en varios smartphones (Motorola Droid, HTC One S, Sam- sung Galaxy Nexus y muchos más), dispositivos lectores electrónicos (como el Kindle Fire y el Nook™ de Barnes and Noble), computadoras tipo tableta (como Dell Streak y Samsung Galaxy Tab), quioscos con pantallas táctiles dentro de las tiendas, automóviles, robots, reproductores multi- media y demás. 1.12Internet y World Wide Web Internet (una red global de computadoras) se hizo posible gracias a la convergencia de las tecnologías de la computación y las comunicaciones. A finales de la década de 1960, ARPA (la Agencia de proyectos de investigación avanzados) extendió los planos para conectar en red los sistemas de cómputo principales de alrededor de una docena de universidades e instituciones de investigación patrocinadas por la ARPA. La investigación académica estaba a punto de dar un gigantesco paso hacia delante. La ARPA procedió a implementar la ARPANET, que eventualmente se convirtió en la Internet que conocemos. Pronto se volvió claro que el primer beneficio clave de ARPANET era la capacidad de comunicarse con rapidez y facilidad mediante correo electrónico. Esto es cierto incluso en Internet de la actualidad, ya que facilita las comunicaciones de todo tipo entre los usuarios de Internet a nivel mundial. Conmutación de paquetes Uno de los principales objetivos de ARPANET fue permitir que múltiples usuarios enviaran y recibieran información al mismo tiempo y a través de las mismas rutas de comunicaciones (por ejemplo, las líneas 6 www.tiobe.com/index.php/content/paperinfo/tpci/index.html. 7 mashable.com/2012/06/11/900000-android-devices/. 8 www.pcworld.com/article/196035/android_outsells_the_iphone_no_big_surprise.html.
  • 60. 28 Capítulo 1 Introducción a las computadoras y a C++ telefónicas). La red operaba mediante una técnica conocida como conmutación de paquetes, en don- de los datos digitales se enviaban en pequeños grupos llamados paquetes. Los paquetes contenían in- formación de dirección, control de errores y secuencia. La información de la dirección permitía enrutar los paquetes hacia sus destinos. La información de secuencia ayudaba a volver a ensamblar los paquetes —ya que debido a los complejos mecanismos de enrutamiento podrían llegar desordenados— en su orden originalparapresentarlosalreceptor.Lospaquetesdedistintosemisoresseentremezclabanenlasmismas líneas para usar con eficiencia el ancho de banda disponible. Esta técnica de conmutación de paquetes redujo de manera considerable los costos de transmisión, en comparación con el costo de las líneas de comunicaciones dedicadas. La red se diseñó para operar sin un control centralizado. Si fallaba una parte de la red, el resto de las porciones funcionales podrían seguir enrutando paquetes de los emisores a los receptores a través de rutas alternativas para mejorar la confiabilidad. TCP/IP El protocolo (conjunto de reglas) para comunicarse a través de ARPANET se conoció como TCP: Protocolo de control de transmisión.TCP aseguraba que los mensajes se enrutaran en forma correcta del emisor hacia el receptor, y que llegaran intactos. A medida que evolucionó Internet, organizaciones de todo el mundo estaban implementando sus propias redes. Un desafío fue lograr que estas distintas redes se comunicaran. La ARPA lo logró con el desarrollo de IP: el Protocolo de Internet, con lo que verdaderamente creó una red de redes, la arqui- tectura actual de Internet. El conjunto combinado de protocolos se conoce ahora comúnmente como TCP/IP. World Wide Web, HTML, HTTP WorldWideWeb nos permite localizar y ver documentos basados en multimedia sobre casi cualquier tema en Internet. La Web es una creación relativamente reciente. En 1989, Tim Berners-Lee de CERN (la Organización Europea para la Investigación Nuclear) empezó a desarrollar una tecnología para compartir información a través de documentos de texto con hipervínculos. A esta invención, Berners-Lee la llamó Lenguaje de marcado de hipertexto (HTML). También escribió protocolos de comunicación para formar la espina dorsal de su nuevo sistema de información, al cual llamó World Wide Web. En especial escribió el Protocolo de transferencia de hipertexto (HTTP): un protocolo de comunicaciones utilizado para enviar información a través de Web. El URL (Localiza- dor uniforme de recursos) especifica la dirección (ubicación) de la página Web que se visualiza en la ventana del navegador. Cada página Web en Internet se asocia con un URL único. El Protocolo seguro de transferencia de hipertexto (HTTPS) es el estándar para transferir datos cifrados enWeb. Mosaic, Netscape, surgimiento de Web 2.0 El uso de Web explotó con la disponibilidad en 1993 del navegador Mosaic, el cual incluía una interfaz grá- fica amigable para el usuario. Marc Andreessen, cuyo equipo en el Centro nacional de aplicaciones de super- computación (NCSA) desarrolló Mosaic, fue el fundador de Netscape, la empresa que muchas personas consideran fue la mecha que encendió la explosiva economía de Internet a finales de la década de 1990. En 2003 hubo un cambio considerable en cuanto a la forma en que las personas y las empresas usaban laWeb y desarrollaban aplicaciones basadas enWeb. Dale Dougherty de O’Reilly Media9 inven- tó el término Web 2.0 en 2003 para describir esta tendencia. Por lo general, las compañíasWeb 2.0 usan la Web como una plataforma para crear sitios colaborativos basados en comunidades (como sitios de redes sociales, blogs y wikis). 9 T. O’Reilly, “What isWeb 2.0: Design Patterns and Business Models for the Next Generation of Software.” September 2005 http://www.oreillynet.com/pub/a/oreilly/tim/news/2005/09/30/what-is-web-20.html?page=1.
  • 61. 1.13 Cierta terminología clave de desarrollo de software 29 Algunas empresas con características de Web 2.0 son: Google (búsqueda Web), YouTube (compar- tición de videos), Facebook (redes sociales), Twitter (microblogs), Groupon (comercio social), Fours- quare (registro móvil), Salesforce (software de negocios que se ofrece en forma de servicios en línea “en la nube”), Craigslist (en su mayoría, anuncios clasificados gratuitos), Flickr (compartición de fotogra- fías), Skype (telefonía, videollamadas y conferencias por Internet) y Wikipedia (una enciclopedia gra- tuita en línea). Web 2.0 involucra a los usuarios; no sólo crean contenido con frecuencia, sino que ayudan a orga- nizarlo, compartirlo, volver a mezclarlo, criticarlo, actualizarlo, etc. Web 2.0 es una conversación, en donde todos tienen la oportunidad de hablar y compartir opiniones. Las empresas que entienden la Web 2.0 saben que sus productos y servicios son conversaciones también. Arquitectura de participación La Web 2.0 abarca una arquitectura de participación: un diseño que fomenta la interacción del usua- rio y las contribuciones comunitarias. Usted el usuario es el aspecto más importante de Web 2.0; de hecho, es tan importante que en 2006 la “Persona del año” de la revista TIME fue “usted”.10 El artícu- lo reconoció el fenómeno social de Web 2.0: el cambio de unos cuantos poderosos a muchos empoderados. Ahora varios blogs populares compiten con los poderosos medios tradicionales y muchas empresas Web 2.0 están basadas casi por completo en contenido generado por el usuario. Para sitios Web como Facebook,Twitter,YouTube, eBay yWikipedia, los usuarios crean el contenido, mientras que las empresas proporcionan las plataformas en las cuales se va a introducir, manipular y compartir la información. 1.13Cierta terminología clave de desarrollo de software La figura 1.27 muestra una lista de palabras de moda que escuchará en la comunidad de desarrollo de software. Creamos Centros de Recursos sobre la mayoría de estos temas, y hay muchos por venir. Tecnología Descripción Ajax Ajax es una de las tecnologías de software Premier de Web 2.0. Ajax ayuda a que las aplicaciones basadas en Internet se ejecuten como aplicaciones de escritorio; una tarea difícil, dado que dichas aplicaciones sufren de retrasos en la transmisión a medida que los datos se intercambian entre su computadora y los servidores en Internet. Desarrollo ágil de software El desarrollo ágil de software es un conjunto de metodologías que tratan de implementar software con más rapidez y menos recursos que las metodologías anteriores. Visite los sitios de Agile Alliance (www.agilealliance.org) y Agile Manifesto (www.agilemanifesto.org). También puede visitar el sitio en español (www.agile-spain.com). Refactorización La refactorización implica reformular programas para hacerlos más claros y fáciles de mantener, al tiempo que se preserva su funcionalidad e integridad. Es muy utilizado en las metodologías de desarrollo ágil. Muchos IDE contienen herra- mientas de refactorización integradas para realizar la mayor parte del proceso de refactorización de manera automática. Fig. 1.27  Tecnologías de software (parte 1 de 2). 10 www.time.com/time/magazine/article/0,9171,1570810,00.html.
  • 62. 30 Capítulo 1 Introducción a las computadoras y a C++ Tecnología Descripción Patrones de diseño Los patrones de diseño son arquitecturas probadas para construir software orientado a objetos flexible y que pueda mantenerse. El campo de los patrones de diseño trata de enumerar a los patrones recurrentes, y de alentar a los diseñadores de software para que los reutilicen y puedan desarrollar un software de mejor calidad con menos tiempo, dinero y esfuerzo. LAMP LAMP es un acrónimo para el conjunto de tecnologías de código fuente abierto que usan muchos desarrolladores en la creación de aplicaciones Web: se refiere a Linux, Apache, MySQL y PHP (o Perl, o Python; otros dos lenguajes que se usan para propósitos similares). MySQL es un sistema de administración de bases de datos de código fuente abierto. PHP es el lenguaje de “secuencias de comandos” del lado servidor de código fuente abierto más popular para el desarrollo de aplicaciones basadas en Internet. Software como un servicio (SaaS) Por lo general, el software siempre se ha visto como un producto; la mayoría del software aún se ofrece de esta forma. Para ejecutar una aplicación, hay que comprar el paquete a un distribuidor de software; por lo general un CD, DVD o descarga Web. Después instalamos ese software en la computadora y la ejecutamos cuando sea necesario. A medida que aparecen nuevas versiones, actualizamos el software, lo cual genera con frecuencia un tiempo y un gasto considerables. Este proceso puede ser incómodo para las organizaciones con decenas de miles de sistemas, a los que se debe dar mantenimiento en una diversa selección de equipo de cómputo. En el Software como un servicio (SaaS), el software se ejecuta en servidores ubicados en cualquier parte de Internet. Cuando se actualizan esos servidores, los clientes en todo el mundo ven las nuevas capacidades; no se requiere una instalación local. Podemos acceder al servicio a través de un navegador. Los navegadores son bastante portables, por lo que podemos ver las mismas aplicaciones en una amplia variedad de computadoras desde cualquier parte del mundo. Salesforce.com, Google, Microsoft Office Live y Windows Live ofrecen SaaS. Esta tecnología es una herramienta de la computación en nube. Plataforma como un servicio (SaaS) La Plataforma como un servicio (PaaS), otra herramienta de la computación en nube, provee una plataforma de cómputo para desarrollar y ejecutar aplicacio- nes como un servicio a través de Web, en vez de instalar las herramientas en su computadora. Los proveedores de PaaS más importantes son: Google App Engine, Amazon EC2, Bungee Labs, entre otros. Kit de desarrollo de software (SDK) Los Kits de desarrollo de software (SDK) incluyen tanto las herramientas como la documentación que utilizan los desarrolladores para programar aplicaciones. Fig. 1.27  Tecnologías de software (parte 2 de 2). La figura 1.28 describe las categorías de liberación de versiones de los productos de software. Versión Descripción Alpha El software alfa es la primera versión de un producto de software cuyo desarrollo aún se encuentra activo. Por lo general las versiones alfa tienen muchos errores, son incompletas e inestables; además se liberan a un pequeño número de desarrolladores para que evalúen las nuevas características, para obtener retroalimentación lo más pronto posible, etc. Fig. 1.28  Terminología de liberación de versiones de productos de software (parte 1 de 2).
  • 63. 1.14 C++11 y las bibliotecas Boost de código fuente abierto 31 Versión Descripción Beta Las versiones beta se liberan a un número mayor de desarrolladores en una etapa posterior del proceso de desarrollo, una vez que se ha corregido la mayoría de los errores importantes y las nuevas características están casi completas. El software beta es más estable, pero todavía puede sufrir cambios. Candidatos para liberación (Release Candidates) En general, los candidatos para liberación tienen todas sus características completas, están (supuestamente) libres de errores y listos para que la comunidad los utilice, con lo cual se logra un entorno de prueba diverso —el software se utiliza en distintos sistemas, con restricciones variables y para muchos fines diferentes. Cualquier error que aparezca se corrige y, en un momento dado, el producto final se libera al público en general. A menudo, las compañías de software distribuyen actualizaciones incrementales a través de Internet. Beta permanente El software que se desarrolla mediante este método por lo general no tiene números de versión (por ejemplo, la búsqueda de Google o Gmail). Este software, que se aloja en la nube (no se instala en su computadora), evoluciona de manera constante de modo que los usuarios siempre dispongan de la versión más reciente. 1.14C++11 y las bibliotecas Boost de código fuente abierto C++11 (anteriormente conocido como C++0x) es el estándar más reciente del lenguaje de programa- ción C++. Las organizaciones ISO/IEC lo publicaron en 2011. Bjarne Stroustrup, el creador de C++, expresó su visión para el futuro del lenguaje: las principales metas eran facilitar el aprendizaje de C++, mejorar las herramientas para generar bibliotecas e incrementar la compatibilidad con el lenguaje de programación C. El nuevo estándar extiende la Biblioteca estándar de C++ e incluye varias herra- mientas y mejoras para incrementar el rendimiento y la seguridad. Los principales distribuidores de compiladores de C++ ya implementaron muchas de las nuevas características de C++11 (figura 1.29). A lo largo del libro hablaremos sobre varias características clave de C++11. Para obtener más informa- ción, visite el sitio Web del Comité de Estándares de C++ en www.open-std.org/jtc1/sc22/wg21/ e isocpp.org.PuedecomprarcopiasdelaespecificacióndellenguajeC++11(ISO/IEC14882:2011)en: http://bit.ly/CPlusPlus11Standard Compilador de C++ URL de las descripciones de las características de C++11 Características de C++11 implementadas en cada uno de los principales compiladores de C++. wiki.apache.org/stdcxx/C%2B%2B0xCompilerSupport Microsoft® Visual C++ msdn.microsoft.com/en-us/library/hh567368.aspx Coleccióndecompiladores deGNU(g++) gcc.gnu.org/projects/cxx0x.html Fig. 1.28  Terminología de liberación de versiones de productos de software (parte 2 de 2). Fig. 1.29  Compiladores de C++ que implementaron las principales porciones de C++11 (parte 1 de 2).
  • 64. 32 Capítulo 1 Introducción a las computadoras y a C++ Compilador de C++ URL de las descripciones de las características de C++11 Compilador Intel® C++ software.intel.com/en-us/articles/c0x-featu- res-supported-by-intel-c-compiler/ IBM® XL C/C++ www.ibm.com/developerworks/mydeveloperworks/ blogs/5894415f-be62-4bc0-81c5-3956e82276f3/ entry/xlc_compiler_s_c_11_support50?lang=en Clang clang.llvm.org/cxx_status.html EDG ecpp www.edg.com/docs/edg_cpp.pdf Bibliotecas Boost de C++ Las Bibliotecas Boost de C++ son bibliotecas gratuitas de código fuente abierto, creadas por miembros delacomunidaddeC++.Sonrevisadasporexpertosyportablesentremuchoscompiladoresyplataformas. El tamaño de Boost ha crecido hasta más de 100 bibliotecas, y se agregan más con regularidad. Hoy en día haymilesdeprogramadoresenlacomunidaddecódigofuentedeBoost.Boostofrecealosprogramadores de C++ bibliotecas útiles que funcionan bien con la Biblioteca estándar de C++ existente. Las bibliotecas Boost las pueden utilizar los programadores de C++ que trabajan en una amplia variedad de plataformas, con muchos compiladores distintos. Algunas de las nuevas características de la Biblioteca estándar de C++11sederivarondelasbibliotecasdeBoostcorrespondientes.Nosotrosveremoslasgeneralidadessobre las bibliotecas y proporcionaremos ejemplos de código para las bibliotecas de “expresiones regulares” y “apuntadores inteligentes”, entre otras. Las expresiones regulares se utilizan para relacionar patrones de caracteres específicos en el texto. Pueden usarse para validar datos y asegurar que se encuentren en un formato específico, para reemplazar partes de una cadena con otra, o para dividir una cadena. Muchos errores comunes en el código de C y C++ están relacionados con los apuntadores, una poderosa herramienta de programación que C++ absorbió de C. Como veremos, los apuntadores in- teligentes nos ayudan a evitar errores asociados con los apuntadores tradicionales. 1.15Mantenerse actualizado con las tecnologías de la información La figura 1.30 muestra una lista de las publicaciones técnicas y comerciales clave que le ayudarán a permane- ceractualizadoconlatecnología,lasnoticiasylastendenciasmásrecientes.Tambiénencontraráunalistacada vezmásgrandedeCentrosderecursosrelacionadosconInternetyWebenwww.deitel.com/resourcecen- ters.html. Publicación URL ACM TechNews technews.acm.org/ ACM Transactions on Accessible Computing www.gccis.rit.edu/taccess/index.html ACM Transactions on Internet Technology toit.acm.org/ Bloomberg BusinessWeek www.businessweek.com Fig. 1.30  Publicaciones técnicas y comerciales (parte 1 de 2). Fig. 1.29  Compiladores de C++ que implementaron las principales porciones de C++11 (parte 2 de 2).
  • 65. Publicación URL CNET news.cnet.com Communications of the ACM cacm.acm.org/ Computerworld www.computerworld.com Engadget www.engadget.com eWeek www.eweek.com Fast Company www.fastcompany.com/ Fortune money.cnn.com/magazines/fortune/ IEEE Computer www.computer.org/portal/web/computer IEEE Internet Computing www.computer.org/portal/web/internet/home InfoWorld www.infoworld.com Mashable mashable.com PCWorld www.pcworld.com SD Times www.sdtimes.com Slashdot slashdot.org/ Smarter Technology www.smartertechnology.com Technology Review technologyreview.com Techcrunch techcrunch.com Wired www.wired.com 1.16Recursos Web Esta sección proporciona vínculos a nuestros Centros de recursos sobre C++ y temas relacionados que le serán de utilidad a medida que aprenda C++. Los sitios blogs, artículos, documentos técnicos, com- piladores,herramientasdedesarrollo,descargas,preguntasfrecuentes(FAQ),tutoriales,webcasts,wikis y vínculos a recursos de programación de juegos en C++. Para actualizaciones sobre publicaciones Dei- tel, Centros de recursos, cursos de capacitación, ofertas para socios y demás, síganos por Facebook® en www.facebook.com/deitelfan/,Twitter® @deitel, Google+ en gplus.to/deitel y LinkedIn en bit. ly/DeitelLinkedIn. Sitios Web de Deitel Associates www.deitel.com/books/cpphtp9/ El sitio Web de Cómo programar en C++, 9/e de Deitel Associates. Aquí encontrará vínculos a los ejemplos del libro y otros recursos. www.deitel.com/cplusplus/ www.deitel.com/visualcplusplus/ www.deitel.com/codesearchengines/ www.deitel.com/programmingprojects/ ReviseestosCentrosdeRecursosenbuscadecompiladores,descargasdecódigo,tutoriales,documentación,libros, libros electrónicos, artículos, blogs, canales (feeds) RSS y demás, que le ayudarán a desarrollar aplicaciones de C++. www.deitel.com Revise este sitio en busca de actualizaciones, correcciones y recursos adicionales para todas las publicaciones Deitel. www.deitel.com/newsletter/subscribe.html Suscríbase al boletín de correo electrónico gratuito Deitel® Buzz Online, para seguir el programa de publicaciones de Deitel Associates, incluyendo actualizaciones y fe de erratas para este libro. 1.16 Recursos Web 33 Fig. 1.30  Publicaciones técnicas y comerciales (parte 2 de 2).
  • 66. 34 Capítulo 1 Introducción a las computadoras y a C++ Ejercicios de autoevaluación 1.1 Complete las siguientes oraciones: a) Las computadoras procesan los datos bajo el control de conjuntos de instrucciones llamadas ____________. b) Las unidades lógicas clave de la computadora son __________, __________, __________, __________, __________ y __________. c) Los tres tipos de lenguajes descritos en este capítulo son __________, __________ y __________. d) Los programas que traducen programas de lenguaje de alto nivel a lenguaje máquina se denominan ______ ____. e) __________ es un sistema operativo para dispositivos móviles, basado en el kernel de Linux y en Java. f) Porlogeneral,elsoftware__________tienetodassuscaracterísticascompletasy(supuestamente)está libre de errores, listo para que la comunidad lo utilice. g) El Wii Remote, así como muchos smartphones, usa un(a) __________ que permite al dispositivo responder al movimiento. 1.2 Complete cada una de las siguientes oraciones relacionadas con el entorno de C++: a) Por lo general, los programas de C++ se escriben en una computadora mediante el uso de un programa ______. b) En un sistema de C++, un programa ______ se ejecuta antes de que empiece la fase de traducción del compilador. c) El programa _________ combina la salida del compilador con varias funciones de biblioteca para producir una imagen ejecutable. d) El programa _________ transfiere el programa ejecutable del disco a la memoria. 1.3 Complete cada una de las siguientes oraciones (basándose en la sección 1.8): a) Los objetos tienen una propiedad que se conoce como __________; aunque éstos pueden saber cómo comunicarse con los demás objetos a través de interfaces bien definidas, generalmente no se les permi- te saber cómo están implementados los otros objetos. b) Los programadores de C++ se concentran en crear __________, que contienen miembros de datos y las funciones miembro que manipulan a esos miembros de datos y proporcionan servicios a los clientes. c) Al proceso de analizar y diseñar un sistema desde un punto de vista orientado a objetos se le conoce como _______. d) Con la __________, se derivan nuevas clases de objetos absorbiendo las características de las clases existentes, y después se agregan sus propias características. e) ________ es un lenguaje gráfico, que permite a las personas que diseñan sistemas de software utilizar una notación estándar en la industria para representarlos. f) El tamaño, forma, color y peso de un objeto se consideran ________ de la clase de ese objeto. Respuestas a los ejercicios de autoevaluación 1.1 a) programas. b) unidad de entrada, unidad de salida, unidad de memoria, unidad central de procesamien- to, unidad aritmética y lógica, unidad de almacenamiento secundario. c) lenguajes máquina, lenguajes ensambla- dores y lenguajes de alto nivel. d) compiladores. e) Android. f) Candidato para liberación. g) acelerómetro. 1.2 a) editor. b) preprocesador. c) enlazador. d) cargador. 1.3 a) ocultamiento de información. b) clases. c) análisis y diseño orientados a objetos (A/DOO). d) heren- cia. e) El Lenguaje Unificado de Modelado (UML). f) atributos. Ejercicios 1.4 Complete cada una de las siguientes oraciones: a) La unidad lógica de la computadora que recibe información desde el exterior de la computadora para que ésta la utilice es la __________. b) El proceso de indicar a la computadora cómo resolver un problema se llama __________. c) __________ es un tipo de lenguaje computacional que utiliza abreviaturas del inglés para las instruc- ciones de lenguaje máquina.
  • 67. d) __________ es una unidad lógica de la computadora envía información, que ya ha sido procesada por la computadora, a varios dispositivos, de manera que la información pueda utilizarse fuera de la compu- tadora. e) ________ y ________ son unidades lógicas de la computadora que retienen información. f) ________ es una unidad lógica de la computadora que realiza cálculos. g) ________ es una unidad lógica de la computadora que toma decisiones lógicas. h) Los lenguajes _________ son más convenientes para que el programador pueda escribir programas rápida y fácilmente. i) Al único lenguaje que una computadora puede entender directamente se le conoce como el _____ _____ de esa computadora. j) __________ es una unidad lógica de la computadora que coordina las actividades de todas las demás unidades lógicas. 1.5 Complete los siguientes enunciados: a) ________ en un principio se hizo popular como el lenguaje de desarrollo del sistema operativo Unix. b) El lenguaje de programación ________ fue desarrollado por Bjarne Stroustrup a principios de la dé- cada de 1980 en los laboratorios Bell. 1.6 Complete los siguientes enunciados: a) Por lo general los programas de C++ pasan por seis fases: __________, __________, __________, __________, __________ y __________. b) Un(a) __________ proporciona muchas herramientas para apoyar el proceso de desarrollo de soft- ware, como editores para escribir y editar programas, depuradores para localizar errores lógicos en los programas y muchas otras características. 1.7 Probablemente usted lleve en su muñeca uno de los tipos de objetos más comunes en el mundo: un reloj. Hable acerca de cómo se aplica cada uno de los siguientes términos y conceptos a la noción de un reloj: objeto, atributos, comportamientos, clase, herencia (considere, por ejemplo, un reloj con alarma), modelado, mensajes, encapsulamiento, interfaz y ocultamiento de información. Hacer la diferencia Hemos incluido en este libro ejercicios Hacer la diferencia, en los que le pediremos que trabaje con problemas que son de verdad importantes para individuos, comunidades, países y el mundo. Para obtener más información sobre las organizaciones a nivel mundial que trabajan para hacer la diferencia, y para obtener ideas sobre proyectos de programación relacionados, visite nuestro Centro de recursos para hacer la diferencia en www.deitel.com/makin- gadifference. 1.8 (Prueba práctica: calculadora de impacto ambiental del carbono) Algunos científicos creen que las emisiones de carbono, sobre todo las que se producen al quemar combustibles fósiles, contribuyen de manera considerable al calentamiento global y que esto se puede combatir si las personas tomamos conciencia y limitamos el uso de los combustibles basados en carbono. Varias organizaciones e individuos se preocupan cada vez más por el “impacto ambiental debido al carbono”. Los sitios Web como Terra Pass www.terrapass.com/carbon-footprint-calculator/ y Carbon Footprint www.carbonfootprint.com/calculator.aspx ofrecen calculadoras de impacto ambiental del carbono. Pruebe estas calculadoras para determinar el impacto que provoca usted en el ambiente debido al carbono. Los ejercicios en capítulos posteriores le pedirán que programe su propia calculadora de impacto ambiental del carbono. Como preparación, le sugerimos investigar las fórmulas para calcular el impacto ambiental del carbono. 1.9 (Prueba práctica: calculadora del índice de masa corporal) Según las estimaciones recientes, dos terceras partes de las personas que viven en Estados Unidos padecen de sobrepeso; la mitad de estas personas son obesas. Esto provocaaumentosconsiderablesenelnúmerodepersonasconenfermedadescomoladiabetesylascardiopatías.Para determinar si una persona tiene sobrepeso o padece de obesidad, puede usar una medida conocida como índice de Hacer la diferencia 35
  • 68. 36 Capítulo 1 Introducción a las computadoras y a C++ masacorporal(IMC).ElDepartamentodesaludyservicioshumanosdeEstadosUnidosproporcionaunacalculado- ra del IMC en www.nhlbisupport.com/bmi/. Úsela para calcular su propio IMC. Un ejercicio del capítulo 2 le pedirá que programe su propia calculadora del IMC. Como preparación, le sugerimos investigar las fórmulas para calcular el IMC. 1.10 (Atributos de los vehículos híbridos) En este capítulo aprendió sobre los fundamentos de las clases. Aho- ra empezará a describir con detalle los aspectos de una clase conocida como “Vehículo híbrido”. Los vehículos híbridos se están volviendo cada vez más populares, puesto que por lo general pueden ofrecer mucho más kilome- traje que los vehículos operados sólo por gasolina. Navegue en Web y estudie las características de cuatro o cinco de los automóviles híbridos populares en la actualidad; después haga una lista de todos los atributos relacionados con sus características de híbridos que pueda encontrar. Por ejemplo, algunos de los atributos comunes son los kilómetros por litro en ciudad y los kilómetros por litro en carretera. También puede hacer una lista de los atribu- tos de las baterías (tipo, peso, etc.). 1.11 (Neutralidad de género) Muchas personas desean eliminar el sexismo de todas las formas de comuni- cación. Usted ha recibido la tarea de crear un programa que pueda procesar un párrafo de texto y reemplazar palabras que tengan un género específico con palabras neutrales en cuanto al género. Suponiendo que recibió una lista de palabras con género específico y sus reemplazos con neutralidad de género (por ejemplo, reemplace “esposa” por “cónyuge”, “hombre” por “persona”, “hija” por “descendiente”, y así en lo sucesivo), explique el procedimiento que utilizaría para leer un párrafo de texto y realizar estos reemplazos en forma manual. ¿Cómo podría su procedimiento generar un término extraño tal como “woperchild”, que aparece listado en el Diccio- nario urbano (www.urbandictionary.com)? En el capítulo 4 aprenderá que un término más formal para “proce- dimiento” es “algoritmo”, y que un algoritmo especifica los pasos a realizar, además del orden en el que se deben llevar a cabo. 1.12 (Privacidad) Algunos servicios de correo electrónico en línea guardan toda la correspondencia electróni- ca durante cierto periodo de tiempo. Suponga que un empleado disgustado de uno de estos servicios de correo electrónico en línea publicara en Internet todas las correspondencias de correo electrónico de millones de personas, incluyendo la suya. Analice las consecuencias. 1.13 (Responsabilidad ética y legal del programador) Como programador en la industria, tal vez llegue a desarrollar software que podría afectar la salud de otras personas, o incluso sus vidas. Suponga que un error de software en uno de sus programas provocara que un paciente de cáncer recibiera una dosis excesiva durante la te- rapia de radiación y resultara gravemente lesionada o muriera. Analice las consecuencias. 1.14 (El “Flash Crash” de 2010) Un ejemplo de las consecuencias de nuestra excesiva dependencia con respecto a las computadoras es el denominado “flash crash”, que ocurrió el 6 de mayo de 2010, cuando el mer- cado de valores de Estados Unidos se derrumbó de manera precipitada en cuestión de minutos, al borrarse billones de dólares de inversiones que se volvieron a recuperar pocos minutos después. Use Internet para inves- tigar las causas de este derrumbe y analice las consecuencias que genera. Recursos para hacer la diferencia La Copa Imagine de Microsoft es una competencia global en la que los estudiantes usan la tecnología para intentar resolver algunos de los problemas más difíciles del mundo, como la sostenibilidad ambiental, acabar con la ham- bruna, la respuesta a emergencias, la alfabetización, combatir el HIV/SIDA y otros más. Visite www.imaginecup. com/about para obtener más información sobre la competencia y para aprender sobre los proyectos desarrollados por los anteriores ganadores.También encontrará varias ideas de proyectos enviadas por organizaciones de caridad a nivel mundial. Si desea obtener ideas adicionales para proyectos de programación que puedan hacer la diferencia, busque en Web el tema “hacer la diferencia” y visite www.un.org/millenniumgoals El proyecto Milenio de Naciones Unidas busca soluciones para los principales problemas mundiales, como la sostenibilidad ambiental, la igualdad de sexos, la salud infantil y materna, la educación universal y otros más. www.ibm.com/smarterplanet/ El sitio Web Smarter Planet de IBM® habla sobre cómo es que IBM utiliza la tecnología para resolver problemas relacionados con los negocios, la computación en nube, la educación, la sostenibilidad y otros más.
  • 69. Recursos para hacer la diferencia 37 www.gatesfoundation.org/Pages/home.aspx La Fundación Bill y Melinda Gates ofrece becas a las organizaciones que trabajan para mitigar el hambre, la pobre- zaylasenfermedadesenlospaísesendesarrollo.EnEstadosUnidos,lafundaciónseenfocaenmejorarlaeducación pública, en especial para las personas con bajos recursos. www.nethope.org/ NetHope es una colaboración de organizaciones humanitarias en todo el mundo, que trabajan para resolver los problemas relacionados con la tecnología, como la conectividad y la respuesta a las emergencias, entre otros. www.rainforestfoundation.org/home La Fundación Rainforest trabaja para preservar los bosques tropicales y proteger los derechos de los indígenas que consideran a estos bosques como su hogar. El sitio incluye una lista de cosas que usted puede hacer para ayudar. www.undp.org/ El Programa de las Naciones Unidas para el Desarrollo (UNDP) busca soluciones a los desafíos globales, como la prevención y recuperación de crisis, la energía y el ambiente, la gobernanza democrática y otros más. www.unido.org La Organización de las Naciones Unidas para el Desarrollo Industrial (UNIDO) busca reducir la pobreza, dar a los países en desarrollo la oportunidad de participar en el comercio global y promover tanto la eficiencia de la energía como la sostenibilidad. www.usaid.gov/ USAID promueve la democracia global, la salud, el crecimiento económico, la prevención de conflictos y la ayuda humanitaria, entre otras cosas. www.toyota.com/ideas-for-good/ El sitio Web Ideas for Good deToyota describe varias tecnologías de esta empresa que están haciendo la diferencia; entre éstas se incluyen su Sistema avanzado de asistencia de estacionamiento (Advanced Parking Guidance Sys- tem), la tecnología Hybrid Synergy Drive® , el Sistema de ventilación operado por energía solar (Solar Powered Ventilation System), el modelo T.H.U.M.S. (Modelo humano total para la seguridad) y Touch Tracer Display. Usted puede participar en el desafío de Ideas for Good; envíe un breve ensayo o un video que describa cómo se pueden usar estas tecnologías para otros buenos propósitos.
  • 70. Introducción a la programación en C++, entrada/salida y operadores 2 ¿Qué hay en un nombre? A eso a lo que llamamos rosa, si le diéramos otro nombre conservaría su misma fragancia dulce. —William Shakespeare Los pensamientos elevados deben tener un lenguaje elevado. —Aristófanes Una persona puede hacer una diferencia y todas las personas deberían intentarlo. —John F. Kennedy O b j e t i v o s En este capítulo aprenderá a: n Escribir programas de cómputo simples en C++. n Escribir instrucciones simples de entrada y salida. n Utilizar los tipos fundamentales. n Familiarizarse con los conceptos básicos de la memoria de la computadora. n Utilizar los operadores aritméticos. n Reconocer precedencia de los operadores aritméticos. n Escribir instrucciones simples para tomar decisiones.
  • 71. 2.2 Su primer programa en C++: imprimir una línea de texto 39 2.1Introducción Ahora presentaremos la programación en C++, que facilita una metodología disciplinada para el diseño deprogramas.LamayoríadelosprogramasenC++queestudiaráenestelibroprocesandatosymuestran resultados. En este capítulo le presentaremos cinco ejemplos que señalan cómo sus programas pueden mostrar mensajes y obtener datos del usuario para procesarlos. Los primeros tres ejemplos simplemente muestran mensajes en la pantalla. El siguiente ejemplo obtiene dos números de un usuario, calcula su suma y muestra el resultado. La discusión que acompaña a este ejemplo le muestra cómo realizar varios cálculos aritméticos y guardar sus resultados para utilizarlos posteriormente. El quinto ejemplo demues- tra la toma de decisiones, al mostrarle cómo comparar dos números y después mostrar mensajes con base en los resultados de la comparación. Analizaremos cada programa, una línea a la vez, para ayudarle a facilitar el proceso de aprender a programar en C++. Compilar y ejecutar programas En www.deitel.com/books/cpphtp9, publicamos videos que demuestran cómo compilar y ejecutar programas en Microsoft Visual C++, GNU C++ y Xcode. 2.2Su primer programa en C++: imprimir una línea de texto Considere un programa simple que imprime una línea de texto (figura 2.1). Este programa ilustra varias características importantes del lenguaje C++. El texto en las líneas 1 a 11 es el código fuente (o código) del programa. Los números de línea no son parte del código fuente. 1 // Fig. 2.1: fig02_01.cpp 2 // Programa para imprimir texto. 3 #include iostream // permite al programa imprimir datos en la pantalla 4 5 // la función main comienza la ejecución del programa 6 int main() 7 { 8 std::cout “Bienvenido a C++!n”; // muestra un mensaje 9 10 rewrn 0; // indica que el programa terminó con éxito 11 } // fin de la función main Bienvenido a C++! Fig. 2.1  Programa para imprimir texto. 2.1 Introducción 2.2 Su primer programa en C++: imprimir una línea de texto 2.3 Modificación de nuestro primer programa en C++ 2.4 Otro programa en C++: suma de enteros 2.5 Conceptos acerca de la memoria 2.6 Aritmética 2.7 Toma de decisiones: operadores de igualdad y relacionales 2.8 Conclusión Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios | Hacer la diferencia
  • 72. 40 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores Comentarios Las líneas 1y 2 // Fig. 2.1: fig02_01.cpp // Programa para imprimir texto. comienzan con //, lo cual indica que el resto de cada línea es un comentario. Insertamos comentarios para documentar nuestros programas y ayudar a que otras personas puedan leerlos y comprenderlos. Los comentarios no hacen que la computadora realice ninguna acción cuando se ejecuta el programa; el compilador de C++ los ignora y no genera ningún código objeto en lenguaje máquina. El comentario Programa para imprimir texto describe el propósito del programa. A un comentario que empieza con // se le llama comentario de una sola línea, ya que termina al final de la línea actual. [Nota: también puede usar comentarios que contienen una o más líneas encerradas entre /* y */]. Buena práctica de programación 2.1 Todo programa debe comenzar con un comentario que describa su propósito. La directiva de preprocesamiento #include La línea 3 #include iostream // permite al programa imprimir datos en la pantalla esunadirectivadelpreprocesador,lacualesunmensajeparaelpreprocesadordeC++(quepresentamos en la sección 1.9). Las líneas que empiezan con # son procesadas por el preprocesador antes de que se compile el programa. Esta línea indica al preprocesador que debe incluir en el programa el contenido del encabezado de flujos de entrada/salida iostream. Este encabezado es un archivo que contiene información que el compilador usa al compilar cualquier programa que muestre datos en la pantalla o que reciba datos del teclado, mediante el uso de la entrada/salida de flujos al estilo C++. El programa de la figura 2.1 muestra datos en la pantalla, como pronto veremos. En el capítulo 6 hablaremos con más detalle sobre encabezados, y en el capítulo 13 explicaremos el contenido de iostream. Error común de programación 2.1 Olvidar incluir el archivo de encabezado iostream en un programa que reciba datos del teclado, o que envíe datos a la pantalla, hace que el compilador genere un mensaje de error. Líneas en blanco y espacio en blanco La línea 4 es simplemente una línea en blanco. Los programadores usan líneas en blanco, caracteres de espacio y caracteres de tabulación (es decir, “tabuladores”) para facilitar la lectura de los programas. En conjunto, estos caracteres se conocen como espacio en blanco. Por lo general, el compilador ignora los caracteres de espacio en blanco. La función main La línea 5 // la función main comienza la ejecución del programa es otro comentario de una sola línea, el cual indica que la ejecución del programa empieza en la siguiente línea. La línea 6 int main()
  • 73. 2.2 Su primer programa en C++: imprimir una línea de texto 41 forma parte de todo programa en C++. Los paréntesis después de main indican que éste es un bloque de construcción denominado function. Los programas en C++ comúnmente consisten en una o más funciones y clases (como aprenderá en el capítulo 3). Exactamente una función en cada programa debe ser main. La figura 2.1 contiene sólo una función. Los programas en C++ empiezan a ejecutarse en la función main, aún si main no es la primera función definida en el programa. La palabra clave int a la izquierda de main indica que “devuelve” un valor entero. Una palabra clave es una palabra en código reservada para C++, para un uso específico. En la figura 4.3 encontrará la lista completa de palabras clave de C++. Explicaremos lo que significa que una función “devuelva un valor” cuando le demostre- mos cómo crear sus propias funciones en la sección 3.3. Por ahora, simplemente incluya la palabra clave int a la izquierda de main en todos sus programas. La llave izquierda, {, (línea 7) debe comenzar el cuerpo de toda función. Su correspondiente llave derecha, }, (línea 11) debe terminar el cuerpo de cada función. Una instrucción de salida La línea 8 std::cout “Bienvenido a C++!n”; // muestra un mensaje indica a la computadora que debe realizar una acción; a saber, imprimir los caracteres contenidos entre las comillas dobles. En conjunto, a las comillas y los caracteres entre ellas se les conoce como cadena (string), cadena de caracteres o literal de cadena. En este libro nos referimos a los caracteres entre comillas dobles simplemente como cadenas. El compilador no ignora los caracteres de espacio en blan- co en las cadenas. A la línea 8 completa, incluyendo std::cout, el operador , la cadena “Bienvenido a C++!n” y el punto y coma (;), se le conoce como instrucción. La mayoría de las instrucciones en C++ termi- nan con un punto y coma, lo que también se conoce como terminador de instrucciones (pronto veremos algunas excepciones a esto). Las directivas del preprocesador (como #include) no terminan con un punto y coma. Por lo general, en C++ las operaciones de entrada y salida se realizan mediante flujos de caracteres. Por ende, cuando se ejecuta la instrucción anterior, envía el flujo de caracteres Bienvenido a C++!n al objeto flujo estándar de salida (std::cout) el cual por lo general está “co- nectado” a la pantalla. Error común de programación 2.2 Omitir el punto y coma al final de una instrucción de C++ es un error de sintaxis. La sintaxis de un lenguaje de programación especifica las reglas para crear programas apro- piados en ese lenguaje. Un error de sintaxis ocurre cuando el compilador encuentra có- digo que viola las reglas del lenguaje C++ (es decir, su sintaxis). Por lo general, el compi- lador genera un mensaje de error para ayudarnos a localizar y corregir el código incorrecto. A los errores de sintaxis también se les llama errores del compilador, errores en tiempo de compilación o errores de compilación, ya que el compilador los detecta durante la fase de compilación. No podrá ejecutar su programa sino hasta que corrija todos los errores de sintaxis que contenga. Como veremos más adelante, algunos errores de compilación no son errores de sintaxis. Buena práctica de programación 2.2 Aplique sangría al cuerpo de cada función un nivel dentro de las llaves que delimitan el cuerpo de la función. Esto hace que la estructura funcional de un programa resalte y hace que sea más fácil de leer. Buena práctica de programación 2.3 Establezca una convención para el tamaño de la sangría que prefiera y luego aplíquela de manera uniforme. La tecla de tabulación puede usarse para crear sangrías, pero los topes de tabulación pueden variar. Nosotros preferimos tres espacios por cada nivel de sangría.
  • 74. 42 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores El espacio de nombres std La instrucción std:: antes de cout se requiere cuando utilizamos nombres que hemos traído al progra- ma por la directiva del preprocesador #include iostream. La notación std::cout especifica que estamos usando un nombre (en este caso cout) que pertenece al espacio de nombres std. Los nombres cin (el flujo de entrada estándar) y cerr (el flujo de error estándar), que presentamos en el capítulo 1, también pertenecen al espacio de nombres std. (Los espacios de nombres son una característica avan- zada de C++ que puede consultar en el capítulo 23, Other Topics, que se encuentra en inglés en el sitio web de este libro). Por ahora, simplemente debe recordar incluir std:: antes de cada mención de cout, cin y cerr en un programa. Esto puede ser incómodo; en el siguiente ejemplo introduciremos las de- claraciones using y la directiva using, la cual nos permitirá omitir std:: antes de cada uso de un nom- bre en el espacio de nombres std. El operador de inserción de flujo y las secuencias de escape En el contexto de una instrucción de salida, el operador se conoce como el operador de inserción de flujo. Cuando este programa se ejecuta, el valor a la derecha del operador (el operando derecho) se inserta en el flujo de salida. Observe que el operador apunta en la dirección hacia la que van los datos. Por lo general los caracteres de la literal de cadena se imprimen exactamente como aparecen entre las comillas dobles. Sin embargo, los caracteres n no se imprimen en la pantalla (figura 2.1). A la barra diagonal inversa () se le llama carácter de escape, e indica que se va a imprimir en pantalla un carácter “especial”. Cuando se encuentra una barra diagonal inversa en una cadena de caracteres, el siguiente carácter se combina con la barra diagonal inversa para formar una secuencia de escape. La secuencia de escape n representa una nueva línea. Hace que el cursor (es decir, el indicador de la posición actual en la pantalla) se desplace al principio de la siguiente línea. Algunas secuencias de escape comunes se listan en la figura 2.2. Secuencia de escape Descripción n Nueva línea. Coloca el cursor al inicio de la siguiente línea. t Tabulador horizontal. Desplaza el cursor hasta la siguiente posición de tabulación. r Retorno de carro. Coloca el cursor de la pantalla al inicio de la línea actual; no avanza a la siguiente línea. a Alerta. Suena la campana del sistema. Barra diagonal inversa. Se usa para imprimir un carácter de barra diagonal inversa. ' Comilla sencilla. Se usa para imprimir un carácter de comilla sencilla. Doble comilla. Se usa para imprimir un carácter de doble comilla. Fig. 2.2  Secuencias de escape. La instrucción return La línea 10 return 0; // indica que el programa terminó con éxito es uno de varios medios que utilizaremos para salir de una función. Cuando se utiliza la instrucción return al final de main, como se muestra aquí, el valor 0 indica que el programa ha terminado correcta- mente. La llave derecha, }, (línea 11) indica el fin de la función main. De acuerdo con el estándar de C++,
  • 75. 2.3 Modificación de nuestro primer programa en C++ 43 si la ejecución del programa llega al final de main sin encontrar una instrucción return se asume que el programa terminó con éxito: exactamente como cuando la última instrucción en main es una instruc- ción return con el valor 0. Por esta razón, omitiremos la instrucción return al final de main en el resto de los programas. Una observación sobre los comentarios Al escribir un nuevo programa o modificar uno existente, debe mantener actualizados sus comentarios con el código del programa. A menudo será necesario realizar cambios en los programas existentes; por ejemplo, para corregir errores (comúnmente conocidos como bugs) que evitan que un programa funcio- ne de manera correcta, o para mejorar un programa. Al actualizar sus comentarios a medida que realiza cambios en el código, ayuda a asegurar que los comentarios reflejen con precisión lo que hace el código. Esto hará que sus programas sean más fáciles de comprender y modificar en el futuro. 2.3Modificación de nuestro primer programa en C++ Ahora le presentaremos dos ejemplos que modifican el programa de la figura 2.1 para imprimir texto en una línea utilizando varias instrucciones, y para imprimir texto en varias líneas utilizando una sola ins- trucción. Cómo mostrar una sola línea de texto con varias instrucciones Bienvenido a la programacion en C++! puede mostrarse en varias formas. Por ejemplo, la figura 2.3 realiza la inserción de flujos en varias instrucciones (líneas 8 y 9), y produce el mismo resultado que el programa de la figura 2.1. [Nota: de aquí en adelante, utilizaremos un fondo color gris claro para resaltar las características clave que se introduzcan en cada programa]. Cada inserción de flujo reanuda la im- presión en donde se detuvo la anterior. La primera inserción de flujo (línea 8) imprime la palabra Bien- venido seguida de un espacio, y puesto que esta cadena no terminó con n, la segunda inserción de flujo (línea 9) empieza a imprimir en la misma línea, justo después del espacio. 1 // Fig. 2.3: fig02_03.cpp 2 // Imprimir una línea de texto con varias instrucciones. 3 #include iostream // permite que el programa envíe datos a la pantalla 4 5 // la función main comienza la ejecución del programa 6 int main() 7 { 8 std::cout Bienvenido ; 9 std::cout a C++!n; 10 } // fin de la función main Bienvenido a C++! Fig. 2.3  Impresión de una línea de texto con varias instrucciones. Cómo mostrar varias líneas de texto con una sola instrucción Una sola instrucción puede mostrar varias líneas mediante el uso de caracteres de nueva línea, como en la línea 8 de la figura 2.4. Cada vez que se encuentra la secuencia de escape n (nueva línea) en el flujo de salida, el cursor de la pantalla se coloca al inicio de la siguiente línea. Para obtener una línea en blanco en sus resultados, coloque dos caracteres de nueva línea, uno después del otro, como en la línea 8.
  • 76. 44 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores 1 // Fig. 2.4: fig02_04.cpp 2 // Impresión de varias líneas de texto con una sola instrucción. 3 #include iostream // permite al programa imprimir datos en la pantalla 4 5 // la función main empieza la ejecución del programa 6 int main() 7 { 8 std::cout BienvenidonannC++!n; 9 } // fin de la función main Bienvenido a C++! Fig. 2.4  Impresión de varias líneas de texto con una sola instrucción. 2.4Otro programa en C++: suma de enteros Nuestro siguiente programa obtiene dos enteros escritos por el usuario mediante el teclado, calcula la suma de esos valores e imprime el resultado mediante el uso de std::cout. En la figura 2.5 se muestra el programa, junto con los datos de entrada y salida de ejemplo. En la ejecución de ejemplo resaltamos la entrada del usuario en negritas. El programa empieza a ejecutarse con la función main (línea 6). La llave izquierda (línea 7) marca el inicio del cuerpo de main y la correspondiente llave derecha (línea 22) marca el fin. 1 // Fig. 2.5: fig02_05.cpp 2 // Programa que muestra la suma de dos enteros. 3 #include iostream // permite al programa realizar operaciones de entrada y salida 4 5 // la función main empieza la ejecución del programa 6 int main() 7 { 8 // declaraciones de variables 9 int numero1 = 0; // primer entero a sumar (se inicializa con 0) 10 int numero2 = 0; // segundo entero a sumar (se inicializa con 0) 11 int suma = 0; // suma de numero1 y numero2 (se inicializa con 0) 12 13 std::cout Escriba el primer entero ; // pide los datos al usuario 14 std::cin numero1; // lee el primer entero del usuario y lo coloca en numero1 15 16 std::cout Escriba el segundo entero: ; // pide los datos al usuario 17 std::cin numero2; // lee el segundo entero del usuario y lo coloca en numero2 18 19 suma = numero1 + numero2; // suma los números; almacena el resultado en suma 20 21 std::cout La suma es suma std::endl; // muestra la suma; fin de línea 22 } // fin de la función main Escriba el primer entero: 45 Escriba el segundo entero: 72 La suma es 117 Fig. 2.5  Programa que muestra la suma de dos enteros.
  • 77. 2.4 Otro programa en C++: suma de enteros 45 Declaraciones de variables Las líneas 9 a 11 int numeror1 = 0; // primer entero a sumar (se inicializa con 0) int numero2 = 0; // segundo entero a sumar (se inicializa con 0) int suma = 0; // suma de numero1 y numero2 (se inicializa con 0) son declaraciones. Los identificadores numero1, numero2 y suma son nombres de variables. Una variable es una ubicación en la memoria de la computadora, en la que puede almacenarse un valor para usarlo mediante un programa. Estas declaraciones especifican que las variables numero1, numero2 y suma son datos de tipo int, lo cual significa que estas variables contienen valores enteros; es decir, números enteros tales como 7, –11, 0 y 31914. Las declaraciones también inicializan cada una de estas variables con 0. Tip para prevenir errores 2.1 Aunque no siempre es necesario inicializar cada variable de manera explícita, hacerlo evitará muchos tipos de problemas. Todas las variables se deben declarar con un nombre y un tipo de datos antes de poder usarlas en un programa. Pueden declararse diversas variables del mismo tipo en una declaración, o en varias declara- ciones.Podríamoshaberdeclaradolastresvariablesenunadeclaraciónmedianteunalistaseparadapor comas, como se muestra a continuación: int numeror1 = 0, numero2 = 0, suma = 0; Esto reduce la legibilidad del programa y evita que podamos proporcionar comentarios que describan el propósito de cada variable. Buena práctica de programación 2.4 Declare sólo una variable en cada declaración y proporcione un comentario que explique el propósito de la variable en el programa. Tipos fundamentales Pronto hablaremos sobre el tipo de datos double para especificar números reales, y sobre el tipo de datos char para especificar datos tipo carácter. Los números reales son números con puntos decimales, como 3.4, 0.0 y –11.19. Una variable char puede guardar sólo una letra minúscula, una letra mayúscula, un dígito o un carácter especial (como $ o *). Los tipos como int, double y char se conocen comúnmente como tipos fundamentales. Los nombres de los tipos fundamentales consisten de una o más palabras clave y, por lo tanto, deben aparecer todos en minúsculas. El apéndice C contiene la lista completa de tipos fundamentales. Identificadores El nombre de una variable (como numero1) es cualquier identificador válido que no sea una palabra clave.Unidentificadoresunaseriedecaracteresqueconsistenenletras,dígitosysímbolosdeguiónbajo (_) que no empieza con un dígito. C++ es sensible a mayúsculas y minúsculas: las letras mayúsculas y minúsculas son distintas, por lo que a1 y A1 se consideran identificadores distintos. Tip de portabilidad 2.1 C++ permite identificadores de cualquier longitud, pero tal vez la implementación de C++ que usted utilice imponga algunas restricciones en cuanto a la longitud de los identificadores. Use identificadores de 31 caracteres o menos, para asegurar la portabilidad.
  • 78. 46 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores Buena práctica de programación 2.5 Seleccionar nombres significativos para los identificadores ayuda a que un programa se autodocumente: que una persona pueda comprender el programa con sólo leerlo, en lu- gar de tener que consultar los comentarios o la documentación del programa. Buena práctica de programación 2.6 Evite usar abreviaciones en los identificadores. Esto mejora la legibilidad del programa. Buena práctica de programación 2.7 No use identificadores que empiecen con guiones bajos y dobles guiones bajos, ya que los compiladores de C++ pueden, de manera interna, utilizar nombres como esos para sus propios fines. Esto evitará que los nombres que usted elija se confundan con los nombres que elijan los compiladores. Colocación de las declaraciones de variables Las declaraciones de las variables se pueden colocar casi en cualquier parte dentro de un programa, pero deben aparecer antes de que sus correspondientes variables se utilicen en el programa. Por ejemplo, en el programa de la figura 2.5, la declaración en la línea 9 int numero1 = 0; // primer entero a sumar (se inicializa con 0) se podría haber colocado justo antes de la línea 14 std::cin numero1; // lee el primer entero del usuario y lo coloca en numero1 la declaración en la línea 10 int numero2 = 0; // segundo entero a sumar (se inicializa con 0) se podría haber colocado justo antes de la línea 17 std::cin numero2; // lee el segundo entero del usuario y lo coloca en numero2 y la declaración en la línea 11 int suma = 0; // suma de numero1 y numero2 (se inicializa con 0) se podría haber colocado justo antes de la línea 19 suma = numero1 + numero2; // suma los números; almacena el resultado en suma Obtener el primer valor del usuario La línea 13 std::cout Escriba el primer entero: ; // pide los datos al usuario imprime la cadena Escriba el primer entero: en la pantalla, seguida de un espacio. A este mensaje se le conoce como indicador debido a que indica al usuario que debe realizar una acción específica. Nos gusta pronunciar la instrucción anterior como “std::cout obtiene la cadena de caracteres Escriba el primer entero: .” La línea 14 std::cin numero1; // lee el primer entero del usuario y lo coloca en numero1 utiliza el objeto flujo de entrada estándar cin (del espacio de nombres std) y el operador de extrac- ción de flujo, , para obtener un valor del teclado. Al usar el operador de extracción de flujo con
  • 79. 2.4 Otro programa en C++: suma de enteros 47 std::cin se toma la entrada de caracteres del flujo de entrada estándar, que por lo general es el teclado. Nos gusta pronunciar la instrucción anterior como “std::cin proporciona un valor a numero1”, o sim- plemente como “std::cin proporciona numero1”. Cuando la computadora ejecuta la instrucción anterior, espera a que el usuario introduzca un valor para la variable numero1. El usuario responde escribiendo un entero (en forma de caracteres), y después oprime la tecla Intro (a la que algunas veces se le conoce como tecla Return) para enviar los caracteres a la computadora. Ésta a su vez convierte la representación de caracteres del número en un entero, y asig- na (copia) este número (o valor) a la variable numero1. Cualquier referencia posterior a numero1 en este programa utilizará este mismo valor. Los objetos flujo std::cout y std::cin facilitan la interacción entre el usuario y la computadora. Claro que los usuarios pueden introducir datos inválidos desde el teclado. Por ejemplo, cuando su programa espera que el usuario introduzca un entero, éste podría introducir caracteres alfabéticos, sím- bolos especiales (como # o @) o un número con un punto decimal (como 73.5), entre otros. En estos primeros programas asumimos que el usuario introduce datos válidos. A medida que progrese por el li- bro, aprenderá varias técnicas para lidiar con el amplio rango de posibles problemas de introducción de datos. Obtener el segundo valor del usuario La línea 16 std::cout Escriba el segundo entero: ; // pide los datos al usuario imprime el mensaje Escriba el segundo entero: en la pantalla, pidiendo al usuario que realice una acción. En la línea 17 std::cin numero2; // lee el segundo entero del usuario y lo coloca en numero2 se obtiene un valor para la variable numero2 de parte del usuario. Calcular la suma de los valores introducidos por el usuario La instrucción de asignación en la línea 19 suma = numero1 + numero2; // suma los números; almacena el resultado en suma calcula la suma de las variables numero1 y numero2, y asigna el resultado a la variable suma mediante el uso del operador de asignación =. La instrucción se lee como “suma obtiene el valor de numero1 + numero2.” Lamayoríadeloscálculosserealizaneninstruccionesdeasignación.Losoperadores= y+ seconocencomo operadores binarios, ya que cada uno de ellos tiene dos operandos. En el caso del operador +, los dos operandos son numero1 y numero2. En el caso del operador = anterior, los dos operandos son suma y el valor de la expresión numero1 + numero2. Buena práctica de programación 2.8 Coloque espacios en cualquier lado de un operador binario. Esto hace que el operador resalte y mejora la legibilidad del programa. Mostrar el resultado La línea 21 std::cout La suma es suma std::endl; // muestra la suma; fin de línea muestra a la cadena de caracteres La suma es, seguida del valor numérico de la variable suma seguido de std::endl, a éste último se le conoce como manipulador de flujos. El nombre endl es una abreviación
  • 80. 48 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores de “fin de línea” (“end of line”, en inglés) y pertenece al espacio de nombres std. El manipulador de flujos std::endl imprime una nueva línea y después “vacía el búfer de salida”. Esto simplemente signi- fica que, en algunos sistemas en donde los datos de salida se acumulan en el equipo hasta que haya sufi- cientes como para que “valga la pena” mostrarlos en pantalla, std::endl obliga a que todos los datos de salida acumulados se muestren en ese momento. Esto puede ser importante cuando los datos de salida piden al usuario una acción, como introducir datos. La instrucción anterior imprime múltiples valores de distintos tipos. El operador de inserción de flujo “sabe” cómo imprimir cada tipo de datos. Al uso de varios operadores de inserción de flujo () en unasolainstrucciónseleconocecomooperacionesdeinsercióndeflujoencascada,concatenamien- to o encadenamiento. Los cálculos también se pueden realizar en instrucciones de salida. Podríamos haber combinado las instrucciones en las líneas 19 y 21 en la instrucción std::cout La suma es numero1 + numero2 std::endl; con lo cual se elimina la necesidad de usar la variable suma. Una poderosa característica de C++ es que los usuarios pueden crear sus propios tipos de datos conocidos como clases (presentaremos esta herramienta en el capítulo 3 y la exploraremos con detalle en el capítulo 9). Los usuarios pueden entonces “enseñar” a C++ cómo debe recibir y mostrar valores de estos nuevos tipos de datos, usando los operadores y (a esto se le conoce como sobrecarga de operadores; un tema que exploraremos en el capítulo 10). 2.5Conceptos acerca de la memoria Los nombres de variables como numero1, numero2 y suma en realidad corresponden a las ubicaciones en la memoria de la computadora. Cada variable tiene un nombre, un tipo, un tamaño y un valor. En el programa de suma de la figura 2.5, cuando se ejecuta la instrucción std::cin numero1; // lee el primer entero del usuario y lo coloca en numero1 en la línea 14, el entero que escribe el usuario se coloca en una ubicación de memoria a la que el compi- lador haya asignado el nombre numero1. Suponga que el usuario introduce el número 45 como el valor para numero1. La computadora colocará el 45 en la ubicación numero1, como se muestra en la figura 2.6. Cuando se coloca un valor en una ubicación en memoria, ese valor sobrescribe al valor anterior en esa ubicación; por ende, se dice que la acción de colocar un nuevo valor en una ubicación en memoria es una operación destructiva. 45 numero1 Fig. 2.6  Ubicación de memoria que muestra el nombre y el valor de la variable numero1. Regresando a nuestro programa de suma, suponga que el usuario introduce el valor 72 cuando se ejecuta la instrucción std::cin numero2; // lee el segundo entero del usuario y lo coloca en numero2 Este valor se coloca en la ubicación numero2, y la memoria aparece como se muestra en la figura 2.7. Las ubicaciones de las variables no necesariamente están adyacentes en la memoria. Una vez que el programa obtiene valores para numero1 y numero2, los suma y coloca el resultado de esta suma en la variable suma. La instrucción suma = numero1 + numeror2; // suma los números; almacena el resultado en suma
  • 81. 2.6 Aritmética 49 45 72 numero1 numero2 Fig. 2.7  Ubicaciones de memoria, después de almacenar valores en las variables para numero1 y numero2. reemplaza el valor que estaba almacenado en suma. La suma calculada de numero1 y numero2 se coloca en la variable suma sin importar qué valor haya tenido suma anteriormente; ese valor se pierde. Después de calcular suma, la memoria aparece como se muestra en la figura 2.8. Los valores de numero1 y numero2 aparecen exactamente como estaban antes del cálculo. Se utilizaron estos valores pero no se destruyeron, en el momento en el que la computadora realizó el cálculo. Por ende, cuando se lee un valor de una ubicación de memoria, la operación es no destructiva. 45 72 117 numero1 numero2 suma Fig. 2.8  Ubicaciones de memoria, después de calcular y almacenar la suma numero1 y numero2. 2.6Aritmética La mayoría de los programas realizan cálculos aritméticos. Los operadores aritméticos de C++ se sin- tetizan en la figura 2.9. Observe el uso de varios símbolos especiales que no se utilizan en álgebra. El asterisco (*) indica la multiplicación y el signo de porcentaje (%) es el operador módulo que describire- mos en breve. Los operadores aritméticos en la figura 2.9 son operadores binarios; es decir, funcionan con dos operandos. Por ejemplo, la expresión numero1 + numero2 contiene el operador binario + y los dos operandos numero1 y numero2. La división de enteros (es decir, en donde tanto el numerador como el denominador son enteros) produce un cociente entero; por ejemplo, la expresión 7 / 4 da como resultado 1 y la expresión 17 / 5 da Operación en C++ Operador aritmético de C++ Expresión algebraica Expresión en C++ Suma + f + 7 f + 7 Resta - p – c p - c Multiplicación * bm o b ×m b * m División / x / y o x y o x ÷ y x / y Módulo % r mod s r % s Fig. 2.9  Operadores aritméticos.
  • 82. 50 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores como resultado 3. Cualquier parte fraccionaria en una división de enteros se trunca (es decir, se descarta); no ocurre un redondeo. C++ proporciona el operador módulo, %, el cual produce el residuo después de la división entera. El operador módulo sólo se puede utilizar con operandos enteros. La expresión x % y produce el residuo después de que x se divide entre y. Por lo tanto, 7 % 4 produce 3 y 17 % 5 produce 2. En capítulos poste- riores consideramos muchas aplicaciones interesantes del operador módulo, como determinar si un número es múltiplo de otro (un caso especial de esta aplicación es determinar si un número es par o impar). Expresiones aritméticas en formato de línea recta En la computadora, las expresiones aritméticas en C++ deben escribirse en formato de línea recta. Por lo tanto, las expresiones como “a dividida entre b” deben escribirse como a / b, de manera que todas las constantes, variables y operadores aparezcan en línea recta. La siguiente notación algebraica: a b no es generalmente aceptable para los compiladores, aunque ciertos paquetes de software de propósito especial soportan una notación más natural para las expresiones matemáticas complejas. Paréntesis para agrupar subexpresiones Los paréntesis se utilizan en las expresiones en C++ de la misma manera que en las expresiones algebrai- cas. Por ejemplo, para multiplicar a por la cantidad b + c, escribimos a * ( b + c ). Reglas de precedencia de operadores C++ aplica los operadores en expresiones aritméticas en una secuencia precisa, determinada por las si- guientes reglas de precedencia de operadores, que generalmente son las mismas que las que se utilizan en álgebra: 1. Los operadores en las expresiones contenidas dentro de pares de paréntesis se evalúan primero. Se dice que los paréntesis tienen el “nivel más alto de precedencia”. En casos de paréntesis anidados o incrustados, como: ( a * ( b + c ) ) los operadores en el par más interno de paréntesis se aplican primero. 2. Las operaciones de multiplicación, división y módulo se aplican a continuación. Si una expre- sión contiene varias de esas operaciones, los operadores se aplican de izquierda a derecha. Se dice que los operadores de multiplicación, división y módulo tienen el mismo nivel de prece- dencia. 3. Las operaciones de suma y resta se aplican al último. Si una expresión contiene varias de esas operaciones, los operadores se aplican de izquierda a derecha. Los operadores de suma y resta tienen el mismo nivel de precedencia. Las reglas de precedencia de operadores definen el orden en el que C++ aplica los operadores. Cuan- do decimos que ciertos operadores se aplican de izquierda a derecha, nos referimos a su asociatividad. Por ejemplo, los operadores de suma (+) en la expresión a + b + c se asocian de izquierda a derecha, por lo que a + b se calcula primero, y después se agrega c a esa suma para determinar el valor de toda la expresión. Más adelante veremos que algunos operadores se asocian de derecha a izquierda. En la figura 2.10 se sintetizan estas reglas de precedencia de operadores. Expan- diremos esta tabla a medida que introduzcamos operadores adicionales de C++. En el apéndice A se incluye una tabla de precedencia completa.
  • 83. 2.6 Aritmética 51 Operador(es) Operación(es) Orden de evaluación (precedencia) ( ) Paréntesis Se evalúa primero. Si los paréntesis son anidados, como en la expresión (a * ( b + c / d + e ) ), la expresión en el par más interno se evalúa primero. [Precaución: si tiene una expresión como (a + b) * (c - d) en donde dos conjuntos de paréntesis no están anidados, pero aparecen “en el mismo nivel”, en C++ estándar no no especifica el orden en el que se evaluarán estas subexpresiones entre paréntesis]. * / % Multiplicación División Módulo Se evalúan en segundo lugar. Si hay varios operadores de este tipo, se evalúan de izquierda a derecha. + - Suma Resta Se evalúan al último. Si hay varios operadores de este tipo, se evalúan de izquierda a derecha. Fig. 2.10  Precedencia de los operadores aritméticos. Ejemplos de expresiones algebraicas y de C++ Ahora, consideremos varias expresiones en vista de las reglas de precedencia de operadores. Cada ejem- plo lista una expresión algebraica y su equivalente en C++. El siguiente es un ejemplo de una media (promedio) aritmética de cinco términos: Álgebra: a + b + c + d + e 5 m = C++: m = ( a + b + c + d + e ) / 5; Los paréntesis son obligatorios, ya que la división tiene una mayor precedencia que la suma. La canti- dad completa ( a + b + c + d + e ) va a dividirse entre 5. Si los paréntesis se omiten por error, obtenemos a + b + c + d + e / 5, que se evalúa incorrectamente como: a + b + c + d + e 5 El siguiente es un ejemplo de la ecuación de una línea recta: Álgebra: y = mx + b C++: y = m * x + b; No se requieren paréntesis. El operador de multiplicación se aplica primero, ya que la multiplicación tiene una mayor precedencia sobre la suma. El siguiente ejemplo contiene las operaciones módulo (%), multiplicación, división, suma, resta y de asignación: z 6 1 2 4 3 5 = p * r % q + w / x - y; z = pr%q + w/x – y Álgebra: C++:
  • 84. 52 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores Los números dentro de los círculos bajo la instrucción indican el orden en el que C++ aplica los opera- dores. Las operaciones de multiplicación, residuo y división se evalúan primero en orden de izquierda a derecha (es decir, se asocian de izquierda a derecha), ya que tienen mayor precedencia que la suma y la resta. Las operaciones de suma y resta se aplican a continuación. Estas operaciones también se aplican de izquierda a derecha. El operador de asignación se aplica al último, ya que su precedencia es menor que la de cualquiera de los operadores aritméticos. Evaluación de un polinomio de segundo grado Para desarrollar una mejor comprensión de las reglas de precedencia de operadores, considere la evalua- ción de un polinomio de segundo grado y = ax 2 + bx + c: Los números dentro de los círculos debajo de la instrucción indican el orden en el que C++ aplica los operadores. En C++ no hay operador aritmético para la exponenciación, por lo que hemos representado a x 2 como x * x. En el capítulo 5 hablaremos sobre la función pow (“power”o potencia) de la biblioteca estándar que realiza la exponenciación. Suponga que las variables a, b, c y x en el polinomio de segundo grado anterior se inicializan como sigue: a = 2, b = 3, c = 7 y x = 5. La figura 2.11 muestra el orden en el que se aplican los operadores y el valor final de la expresión. (Multiplicación de más a la izquierda) (Multiplicación de más a la izquierda) (Multiplicación antes de la suma) (Suma de más a la izquierda) (Última suma) (Última operación; colocar 72 en y) Paso 1. y = 2 * 5 * 5 + 3 * 5 + 7; 2 * 5 es 10 Paso 2. y = 10 * 5 + 3 * 5 + 7; 10 * 5 es 50 Paso 3. y = 50 + 3 * 5 + 7; 3 * 5 es 15 Paso 4. y = 50 + 15 + 7; 50 + 15 es 65 Paso 5. y = 65 + 7; 65 + 7 es 72 Paso 6. y = 72 Fig. 2.11  Orden en el cual se evalúa un polinomio de segundo grado. 6 1 2 4 3 5 y = a * x * x + b * x + c;
  • 85. 2.7 Toma de decisiones: operadores de igualdad y relacionales 53 Paréntesis redundantes Al igual que en álgebra, es aceptable colocar paréntesis innecesarios en una expresión para hacer que ésta sea más clara. A dichos paréntesis innecesarios se les llama paréntesis redundantes. Por ejemplo, la instrucción de asignación anterior podría colocarse entre paréntesis, de la siguiente manera: y = ( a * x * x ) + ( b * x ) + c; 2.7Toma de decisiones: operadores de igualdad y relacionales Ahora presentaremos una versión simple de la instrucción if de C++, la cual permite que un programa tome una acción alternativa, con base en la verdad o falsedad de cierta condición. Si la condición es verdadera, se ejecuta la instrucción que está en el cuerpo de la instrucción if. Si la condición es falsa, el cuerpo no se ejecuta. En breve veremos un ejemplo. Las condiciones en las instrucciones if pueden formarse utilizando los operadores de igualdad y los operadores relacionales, que se sintetizan en la figura 2.12. Todos los operadores relacionales tienen el mismo nivel de precedencia y se asocian de izquierda a derecha. Ambos operadores de igualdad tienen el mismo nivel de precedencia, que es menor que la precedencia de los operadores relacionales, y se asocian de izquierda a derecha. Operador algebraico de igualdad o relacional Operador de igualdad o relacional de C++ Ejemplo de condición en C++ Significado de la condición en C++ Operadores relacionales x y x es mayor que y x y x es menor que y ≥ = x = y x es mayor o igual que y ≤ = x = y x es menor o igual que y Operadores de igualdad = == x == y x es igual a y ≠ != x != y x no es igual a y Fig. 2.12  Operadores de igualdad y relacionales. Error común de programación 2.3 Invertir el orden del par de símbolos en cualquiera de los operadores !=, = y = (al escri- birlos como =!, = y =, respectivamente) es comúnmente un error de sintaxis. En algunos casos, escribir != como =! no será un error de sintaxis, sino casi con certeza será un error lógico, el cual tiene un efecto en tiempo de ejecución. En el capítulo 5 comprenderá esto, cuando aprenda acerca de los operadores lógicos. Un error lógico fatal hace que un progra- ma falle y termine antes de tiempo. Un error lógico no fatal permite que un programa continúe ejecutándose, pero por lo general produce resultados incorrectos. Error común de programación 2.4 Confundir el operador de igualdad == con el operador de asignación = produce errores lógi- cos. El operador de igualdad se debe leer como “es igual a” o “doble igual”, y el operador de asignación se debe leer como “obtiene” u “obtiene el valor de”, o “se le asigna el valor de”. Como veremos en la sección 5.9, confundir estos operadores no necesariamente puede provo- car un error de sintaxis fácil de reconocer, pero puede producir errores lógicos sutiles.
  • 86. 54 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores Uso de la instrucción if El siguiente ejemplo (figura 2.13) utiliza seis instrucciones if para comparar dos números introducidos porelusuario.Sisesatisfacelacondiciónencualquieradeestasinstruccionesif,seejecutalainstrucción de salida asociada con esa instrucción if. 1 // Fig. 2.13: fig02_13.cpp 2 // Comparación de enteros mediante instrucciones if, operadores 3 // relacionales y operadores de igualdad. 4 #include iostream // permite al programa realizar operaciones de entrada y salida 5 6 using std::cout; // el programa usa cout 7 using std::cin; // el programa usa cin 8 using std::endl; // el programa usa endl 9 10 // la función main empieza la ejecución del programa 11 int main() 12 { 13 int numero1 = 0; // primer entero a comparar (se inicializa con 0) 14 int numero2 = 0; // segundo entero a comparar (se inicializa con 0) 15 16 cout Escriba dos enteros a comparar: ; // pide los datos al usuario 17 cin numero1 numero2; // lee dos enteros del usuario 18 19 if ( numero1 == numero2 ) 20 cout numero1 “ == “ numero2 endl; 21 22 if ( numero1 != numero2 ) 23 cout numero1 “ != “ numero2 endl; 24 25 if ( numero1 numero2 ) 26 cout numero1 “ “ numero2 endl; 27 28 if ( numero1 numero2 ) 29 cout numero1 “ “ numero2 endl; 30 31 if ( numero1 = numero2 ) 32 cout numero1 “ = “ numero2 endl; 33 34 if ( numero1 = numero2 ) 35 cout numero1 “ = “ numero2 endl; 36 } // fin de la función main Escriba dos enteros a comparar: 3 7 3 != 7 3 7 3 = 7 Escriba dos enteros a comparar: 22 12 22 != 12 22 12 22 = 12 Fig. 2.13  Comparación de enteros mediante instrucciones if, operadores relacionales y operadores de igualdad (parte 1 de 2).
  • 87. 2.7 Toma de decisiones: operadores de igualdad y relacionales 55 Escriba dos enteros a comparar: 7 7 7 == 7 7 = 7 7 = 7 Declaraciones using Las líneas 6 a 8: using std::cout; // el programa usa cout using std::cin; // el programa usa cin using std::endl; // el programa usa endl son declaraciones using que eliminan la necesidad de repetir el prefijo std:: que usamos en programas anteriores. Ahora podemos escribir cout en vez de std::cout, cin en vez de std::cin y endl en vez de std::endl, respectivamente, en el resto del programa. En lugar de las líneas 6 a 8, muchos programadores prefieren usar la directiva using: using namespace std; la cual permite que un programa utilice todos los nombres en cualquier encabezado estándar de C++ (como iostream) que un programa pudiera incluir. Desde este punto en adelante en el libro, usare- mos la directiva anterior en nuestros programas.1 Declaraciones de variables y lectura de las entradas del usuario Las líneas 13 y 14: int numero1 = 0; // primer entero a comparar (se inicializa con 0) int numero2 = 0; // segundo entero a comparar (se inicializa con 0) declaran las variables utilizadas en el programa y las inicializan con 0. El programa usa operaciones de extracción de flujos en cascada (línea 17) para recibir dos enteros como entrada. Recuerde que podemos escribir cin (en vez de std::cin) debido a la línea 7. Primero se lee un valor y se coloca en la variable number1, luego se lee un valor y se coloca en la variable number2. Comparación de números La instrucción if en las líneas 19 y 20: if ( numero1 == numero2 ) cout numero1 == numero2 endl; compara los valores de las variables numero1 y numero2 para probar su igualdad. Si los valores son igua- les, la instrucción en la línea 20 muestra una línea de texto que indica que los números son iguales. Si las condiciones son true en una o más de las instrucciones if que empiezan en las líneas 22, 25, 28, 31 y 34, la correspondiente instrucción del cuerpo muestra una línea de texto apropiada. Cada instrucción if en la figura 2.13 tiene una sola instrucción en su cuerpo y cada instrucción del cuerpo tiene sangría. En el capítulo 4 le mostraremos cómo especificar instrucciones if con cuerpos con múltiples instrucciones (encerrando las instrucciones del cuerpo en un par de llaves, { }, para crear lo que se conoce como instrucción compuesta o bloque). Fig. 2.13  Comparación de enteros mediante instrucciones if, operadores relacionales y operadores de igualdad (parte 2 de 2). 1 En el capítulo 23, Other Topics (en inglés, que se encuentra en el sitio web), hablaremos sobre algunas directivas using en sistemas de gran escala.
  • 88. 56 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores Buena práctica de programación 2.9 Aplique sangría a la(s) instrucción(es) en el cuerpo de una instrucción if para mejorar la legibilidad. Error común de programación 2.5 Colocar un punto y coma justo después del paréntesis derecho que va después de la condi- ción en una instrucción if es, generalmente, un error lógico (aunque no es un error de sintaxis). El punto y coma hace que el cuerpo de la instrucción if esté vacío, por lo que esta instrucción no realiza ninguna acción, sin importar que la condición sea verdadera o no. Peor aún, la instrucción del cuerpo original de la instrucción if ahora se convierte en una instrucción en secuencia con la instrucción if y siempre se ejecuta, lo cual a me- nudo ocasiona que el programa produzca resultados incorrectos. Espacio en blanco Observe el uso del espacio en blanco en la figura 2.13. Recuerde que, por lo general, el compilador ig- nora los caracteres de espacio en blanco, como tabuladores, nuevas líneas y espacios. Por lo tanto, las instrucciones pueden dividirse en varias líneas y espaciarse de acuerdo con las preferencias del progra- mador. Es un error de sintaxis dividir identificadores, cadenas (como hola) y constantes (como el número 1000) a través de varias líneas. Buena práctica de programación 2.10 Una instrucción larga puede esparcirse en varias líneas. Si hay que dividir una sola instrucción entre varias líneas, elija puntos que tengan sentido para hacer la división, como después de una coma en una lista separada por comas, o después de un operación en una expresión larga. Si una instrucción se divide entre dos o más líneas, aplique sangría a todas las líneas subsecuentes y alinee a la izquierda el grupo de líneas con sangría. Precedencia de operadores La figura 2.14 muestra la precedencia y asociatividad de los operadores que se presentan en este capítu- lo. Los operadores se muestran de arriba a abajo, en orden descendente de precedencia. Todos estos operadores, con la excepción del operador de asignación, =, se asocian de izquierda a derecha. La suma es asociativa a la izquierda, por lo que una expresión como x + y + z se evalúa como si se hubiera escrito así: (x + y) + z. El operador de asignación = asocia de derecha a izquierda, por lo que una expresión tal como x = y = 0 se evalúa como si se hubiera escrito así: x = (y = 0), donde, como pronto veremos, prime- ro se asigna el 0 a y y luego se asigna el resultado de esa asignación (0) a x. Operadores Asociatividad Tipo () [vea la precaución en la figura 2.10] paréntesis para agrupar * / % izquierda a derecha multiplicativa + - izquierda a derecha suma izquierda a derecha inserción/extracción de flujo = = izquierda a derecha relacional == != izquierda a derecha igualdad = derecha a izquierda asignación Fig. 2.14  Precedencia y asociatividad de los operadores descritos hasta ahora.
  • 89. 2.8 Conclusión 57 Buena práctica de programación 2.11 Consulte la tabla de precedencia y asociatividad de operadores (apéndice A) cuando escri- ba expresiones que contengan muchos operadores. Confirme que los operadores en la ex- presión se ejecuten en el orden que usted espera. Si no está seguro en cuanto al orden de evaluación en una expresión compleja, divida la expresión en instrucciones más pequeñas o use paréntesis para forzar el orden de evaluación de la misma forma como lo haría en una expresión algebraica. Asegúrese de observar que ciertos operadores, como la asignación (=), se asocien de derecha a izquierda en lugar de hacerlo de izquierda a derecha. 2.8Conclusión En este capítulo aprendió muchas de las herramientas básicas importantes de C++, como mostrar datos enlapantalla,recibirdatosdeltecladoydeclararvariablesdetiposfundamentales.Enespecial,aprendió a utilizar el objeto flujo de salida cout y el objeto flujo de entrada cin para crear programas interactivos simples. Le explicamos cómo se almacenan y recuperan las variables de memoria. También aprendió a usar los operadores aritméticos para realizar cálculos. Hablamos sobre el orden en el que C++ aplica los operadores (es decir, las reglas de precedencia de operadores), así como de la asociatividad de éstos. Además aprendió cómo la instrucción if de C++ permite a un programa tomar decisiones. Por último le presentamos los operadores de igualdad y relacionales, que se utilizan para formar condiciones en las instrucciones if. Las aplicaciones no orientadas a objetos que presentamos en este capítulo lo introdujeron a los conceptos básicos de programación. Como verá en el capítulo 3, las aplicaciones de C++ por lo general contienen sólo unas cuantas líneas de código en la función main: estas instrucciones normalmente crean los objetos que realizan el trabajo de la aplicación y después los objetos “se hacen cargo a partir de aquí”. En el capítulo 3 aprenderá a implementar sus propias clases y a usar objetos de esas clases en las aplica- ciones. Resumen Sección 2.2 Su primer programa en C++: imprimir una línea de texto • Los comentarios de una sola línea (pág. 40) comienzan con //. Insertamos comentarios para documentar los programas y mejorar su legibilidad. • Los comentarios no provocan que la computadora realice ninguna acción (pág. 41) cuando se ejecuta el progra- ma: el compilador los ignora y no provocan que se genere ningún código objeto en lenguaje máquina. • Una directiva de preprocesamiento (pág. 40) comienza con # y es un mensaje para el preprocesador de C++. Las directivas de preprocesamiento se procesan antes de que se compile el programa. • La línea #include iostream (pág. 40) indica al preprocesador de C++ que debe incluir el contenido del enca- bezado del flujo de entrada/salida, que contiene la información necesaria para compilar programas que usen std::cin (pág. 46) y std::cout (pág. 41) junto con los operadores de inserción de flujo (, pág. 42) y de ex- tracción de flujo (, pág. 46). • El espacio en blanco (es decir líneas en blanco, caracteres de espacio y caracteres de tabulación, pág. 40) hace que los programas sean más fáciles de leer. El compilador ignora los caracteres de espacio en blanco que se en- cuentran fuera de las literales de cadena. • Los programas en C++ empiezan a ejecutarse en main (pág. 41), incluso aunque main no aparezca primero en el programa. • La palabra clave int a la izquierda de main indica que esta función “devuelve” un valor entero.
  • 90. 58 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores • El cuerpo (pág. 41) de toda función debe estar encerrado entre llaves ({ y }). • Una cadena ( string) (pág. 41) entre comillas dobles se conoce algunas veces como cadena de caracteres, men- saje o literal de cadena. El compilador no ignora los caracteres de espacio en blanco en las cadenas. • La mayoría de las instrucciones de C++ (pág. 41) terminan con un punto y coma, también conocido como terminador de instrucción (pronto veremos algunas excepciones a esto). • En C++, las operaciones de entrada y salida se realizan mediante flujos (pág. 41) de caracteres. • El objeto flujo de salida std::cout (que por lo general está conectado a la pantalla) se utiliza para mostrar datos. Es posible mostrar múltiples elementos de datos mediante la concatenación de operadores de inserción de flujo (). • Elobjetoflujodeentradastd::cin (queporlogeneralestáconectadoalteclado)seutilizaparaintroducirdatos. Es posible introducir múltiples elementos de datos mediante la concatenación de operadores de extracción de flujo (). • La notación std::cout especifica que utilizamos cout del “espacio de nombres” std. • Cuando hay una barra diagonal (es decir, un carácter de escape) en una cadena de caracteres, el siguiente carác- ter se combina con la barra diagonal para formar una secuencia de escape (pág. 42). • La secuencia de escape de nueva línea n (pág. 42) mueve el cursor al principio de la siguiente línea en la pantalla. • Un mensaje que indica al usuario que realice una acción específica se conoce como indicador (prompt) (pág. 46). • La palabra return de C++ (pág. 42) es uno de varios medios para salir de una función Sección 2.4 Otro programa en C++: suma de enteros • Hay que declarar todas las variables (pág. 45) en un programa en C++ antes de poder usarlas. • El nombre de una variable es cualquier identificador válido (pág. 45) que no sea una palabra clave. Un identifi- cadoresunaseriedecaracteresqueconsistenenletras,dígitosyguionesbajos(_).Losidentificadoresnopueden comenzar con un dígito. Su nombre puede ser de cualquier longitud, aunque tal vez algunos sistemas o imple- mentaciones de C++ impongan restricciones en cuanto al largo. • C++ es sensible al uso de mayúsculas y minúsculas (pág. 45). • La mayoría de los cálculos se realizan en instrucciones de asignación (pág. 47). • Una variable es una ubicación en memoria (pág. 48) en donde se puede almacenar un valor para que lo utilice un programa. • Las variables de tipo int (pág. 45) contienen valores enteros; es decir, números integer como 7, –11, 0, 31914. Sección 2.5 Conceptos acerca de la memoria • Toda variable almacenada en la memoria de la computadora tiene un nombre, valor, tipo y tamaño. • Cada vez que se coloca un nuevo valor en una ubicación de memoria, el proceso es destructivo (pág. 48); es decir, el nuevo valor reemplaza al valor anterior en esa ubicación. El valor anterior se pierde. • Cuando se lee un valor de la memoria, el proceso es no destructivo (pág. 49); es decir, se lee una copia del valor y el original queda sin ser perturbado en la ubicación de memoria. • El manipulador de flujos std::endl (pág. 47) imprime una nueva línea y luego “vacía el búfer de salida”. Sección 2.6 Aritmética • C++ evalúa las expresiones aritméticas (pág. 49) en una secuencia precisa determinada por las reglas de prece- dencia de operadores (pág. 50) y la asociatividad (pág. 50). • Pueden usarse paréntesis para agrupar expresiones. • La división de enteros (pág. 49) produce un cociente entero. Cualquier parte fraccional en la división de enteros se trunca. • El operador módulo % (pág. 50) produce el residuo después de la división de enteros.
  • 91. Ejercicios de autoevaluación 59 Sección 2.7 Toma de decisiones: operadores de igualdad y relacionales • La instrucción if (pág. 53) permite a un programa tomar una acción alternativa con base en el cumplimiento de una condición. El formato para una instrucción if es: if ( condición ) instrucción; Si la condición es verdadera, se ejecuta la instrucción en el cuerpo del if. Si la condición no se cumple (es falsa), se brinca el cuerpo de la instrucción. • Por lo general, las condiciones en las instrucciones if se forman mediante el uso de operadores de igualdad y relacionales (pág. 53). El resultado de usar esos operadores siempre es un valor verdadero o falso. • La siguiente declaración using (pág. 55): using std::cout; informa al compilador en dónde encontrar cout (namespace std) y elimina la necesidad de repetir std:: pre- fijo. La siguiente directiva using (pág. 55): using namespace std; permitealprogramausarenelencabezadotodoslosnombresincluidosdecualquierbibliotecaestándardeC++. Ejercicios de autoevaluación 2.1 Complete las siguientes oraciones: a) Todo programa en C++ empieza su ejecución en la función __________. b) Un(a) __________ empieza el cuerpo de toda función y un(a) __________ lo termina. c) La mayoría de las instrucciones de C++ terminan con un(a) __________. d) La secuencia de escape n representa el carácter __________, el cual hace que el cursor se posicione al principio de la siguiente línea en la pantalla. e) La instrucción __________ se utiliza para tomar decisiones. 2.2 Indique si cada una de las siguientes instrucciones es verdadera o falsa. Si es falsa, explique por qué. Asuma que se usa la instrucción using std::cout. a) Los comentarios hacen que la computadora imprima, en la pantalla, el texto que va después de los caracteres / /, al ejecutarse el programa. b) Cuando la secuencia de escape n, se imprime con cout y el operador de inserción de flujo causa que el cursor se posicione al principio de la siguiente línea en la pantalla. c) Todas las variables deben declararse antes de utilizarlas. d) Todas las variables deben ser de un tipo al declararlas. e) C++ considera que las variables numero y NuMeRo son idénticas. f) Las declaraciones pueden aparecer casi en cualquier parte del cuerpo de una función de C++. g) El operador módulo (%) se puede utilizar sólo con operandos enteros. h) Los operadores aritméticos *, /, %, + y – tienen todos el mismo nivel de precedencia. i) Un programa en C++ que imprime tres líneas de salida debe contener tres instrucciones en las que se utilicen cout y el operador de inserción de flujo. 2.3 Escriba una sola instrucción en C++ para realizar cada una de las siguientes tareas (suponga que no se han utilizado declaraciones using ni la directiva using): a) Declarar las variables c, estaEsUnaVariable, q76354 y numero como de tipo int (en una instrucción). b) Pedir al usuario que introduzca un entero.Termine su mensaje con un signo de dos puntos (:) seguido de un espacio, y deje el cursor posicionado después del espacio. c) Recibir un entero como entrada del usuario mediante el teclado y almacenarlo en la variable entera edad. d) Si la variable numero no es igual a 7, imprimir La variable numero no es igual a 7. e) Imprimir en una línea el mensaje Este es un programa en C++. f) Imprimir en dos líneas el mensaje Este es un programa en C++.Termine la primera línea con C++.
  • 92. 60 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores g) Imprimir el mensaje Este es un programa en C++ con cada palabra en una línea separada. h) Imprimir el mensaje Este es un programa en C++. Separe una palabra de otra mediante un tabu- lador. 2.4 Escriba una declaración (o comentario) para realizar cada una de las siguientes tareas (suponga que se han utilizado declaraciones using para cin, cout y endl): a) Indicar que un programa calculará el producto de tres enteros. b) Declarar las variables x, y, z y resultado de tipo int (en instrucciones separadas) e inicializar cada una con 0. c) Pedir al usuario que escriba tres enteros. d) Recibir tres enteros del teclado y almacenarlos en las variables x, y y z. e) Calcular el producto de los tres enteros contenidos en las variables x, y y z, y asignar el resultado a la variable resultado. f) Imprimir El producto es seguido del valor de la variable resultado. g) Devolver un valor de main, para indicar que el programa terminó correctamente. 2.5 Utilizando las instrucciones que escribió en el ejercicio 2.4, escriba un programa completo que calcule e imprima el producto de tres enteros. Agregue comentarios al código donde sea apropiado. [Nota: necesitará escri- bir las declaraciones using o la directiva using que sean necesarias]. 2.6 Identifique y corrija los errores en cada una de las siguientes instrucciones (suponga que se utiliza la ins- trucción using std::cout;): a) if ( c 7 ); cout c es menor que 7n; b) if ( c = 7 ) cout c es igual o mayor que 7n; Respuestas a los ejercicios de autoevaluación 2.1 a) main. b) llave izquierda ({), llave derecha (}). c) punto y coma. d) nueva línea. e) if. 2.2 a) Falso. Los comentarios no producen ninguna acción cuando el programa se ejecuta. Se utilizan para documentar programas y mejorar su legibilidad. b) Verdadero. c) Verdadero. d) Verdadero. e) Falso. C++ es sensible a mayúsculas y minúsculas, por lo que estas variables son diferentes. f) Verdadero. g) Verdadero. h) Falso. Los operadores *, / y % se encuentran en el mismo nivel de precedencia, y los operadores + y - se encuentran en un nivel menor de precedencia. i) Falso. Una instrucción con cout y varias secuencias de escape n puede imprimir varias líneas. 2.3 a) int c, estaEsUnaVariable, q76354, numero; b) std::cout Escriba un entero: ; c) std::cin edad; d) if ( numero != 7 ) std::cout La variable numero no es igual a 7n; e) std::cout Este es un programa en C++n; f) std::cout Este es unn programa en C++n; g) std::cout EstenesnunnprogramanennC++n; h) std::cout EstetestuntprogramatentC++n;
  • 93. Ejercicios 61 2.4 a) // Calcula el producto de tres enteros b) int x = 0; int y = 0; int z = 0; int resultado = 0; c) cout Escriba tres enteros: ; d) cin x y z; e) resultado = x * y * z; f) cout El producto es resultado endl; g) return 0; 2.5 (Vea el siguiente programa). 1 // Calcula el producto de tres enteros 2 #include iostream // permite al programa realizar operaciones de entrada y salida 3 using namespace std; // el programa usa nombres del std namespace 4 5 // la función main empieza la ejecución del programa 6 int main() 7 { 8 int x = 0; // primer entero a multiplicar 9 int y = 0; // segundo entero a multiplicar 10 int z = 0; // tercer entero a multiplicar 11 int resultado = 0; // el producto de los tres enteros 12 13 cout Escriba tres enteros: ; // pide los datos al usuario 14 cin x y z; // lee tres enteros del usuario 15 resultado = x * y * z; // multiplica los tres enteros; almacena el resultado 16 cout El producto es resultado endl; // imprime el resultado; fin de línea 17 } // fin de la función main 2.6 a) Error: punto y coma después del paréntesis derecho de la condición en la instrucción if. Corrección: elimine el punto y coma después del paréntesis derecho. [Nota: el resultado de este error es que la instrucción de salida se ejecuta sin importar que la condición en la instrucción if sea verdadera o no]. El punto y coma después del paréntesis derecho es una instrucción nula (o vacía) que no hace nada. Aprenderemos más sobre la instrucción nula en el capítulo 4. b) Error: el operador relacional =. Corrección: cambie = a = y tal vez quiera cambiar “igual o mayor que” a “mayor o igual que” también. Ejercicios 2.7 Hable sobre el significado de cada uno de los siguientes objetos: a) std::cin b) std::cout 2.8 Complete las siguientes oraciones: a) __________ se utilizan para documentar un programa y mejorar su legibilidad. b) El objeto que se utiliza para imprimir información en la pantalla es ___________ . c) Una instrucción de C++ que toma una decisión es __________. d) La mayoría de los cálculos se realizan normalmente mediante instrucciones __________. e) El objeto __________ recibe valores de entrada del teclado.
  • 94. 62 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores 2.9 Escriba una sola instrucción o línea en C++ que realice cada una de las siguientes tareas: a) Imprimir el mensaje Escriba dos números. b) Asignar el producto de las variables b y c a la variable a. c) Indicar que un programa va a realizar un cálculo de nómina (es decir, usar texto que ayude a documen- tar un programa). d) Recibir tres valores de entrada del teclado y colocarlos en las variables enteras a, b y c. 2.10 Indique cuál de los siguientes enunciados es true y cuál es falso. Si es falso, explique su respuesta. a) Los operadores en C++ se evalúan de izquierda a derecha. b) Los siguientes nombres de variables son todos válidos: _barra_inferior_, m928134, t5, j7, sus_ ventas, su_cuenta_total, a, b, c, z, z2. c) La instrucción cout a = 5;; es un ejemplo típico de una instrucción de asignación. d) Una expresión aritmética válida en C++ sin paréntesis se evalúa de izquierda a derecha. e) Los siguientes nombres de variables son todos inválidos: 3g, 87, 67h2, h22, 2h. 2.11 Complete cada una de las siguientes oraciones: a) ¿Qué operaciones aritméticas se encuentran en el mismo nivel de precedencia que la multiplicación? __________. b) Cuando los paréntesis están anidados, ¿qué conjunto de paréntesis se evalúa primero en una expresión aritmética? __________. c) Una ubicación en la memoria de la computadora que puede contener distintos valores en distintos momentos durante la ejecución de un programa se llama __________. 2.12 ¿Qué se imprime (si acaso) cuando se ejecuta cada una de las siguientes instrucciones de C++? Si no se imprime nada, entonces responda “nada”. Suponga que x = 2 y y = 3. a) cout x; b) cout x + x; c) cout x=; d) cout x = x; e) cout x + y = y + x; f) z = x + y; g) cin x y; h) // cout x + y = x + y; i) cout n; 2.13 ¿Cuáles de las siguientes instrucciones de C++ contienen variables cuyos valores se reemplazan? a) cin b c d e f; b) p = i + j + k + 7; c) cout variables cuyos valores se reemplazan; d) cout a = 5; 2.14 Dada la ecuación algebraica y = ax3 + 7, ¿cuáles de las siguientes instrucciones (si acaso) en C++ son co- rrectas para esta ecuación? a) y = a * x * x * x + 7; b) y = a * x * x * ( x + 7 ); c) y = ( a * x ) * x * ( x + 7 ); d) y = (a * x) * x * x + 7; e) y = a * ( x * x * x ) + 7; f) y = a * x * ( x * x + 7 ); 2.15 (Orden de evaluación) Indique el orden de evaluación de los operadores en cada una de las siguientes instrucciones en C++ y muestre el valor de x después de ejecutar cada una de ellas. a) x = 7 + 3 * 6 / 2 - 1; b) x = 2 % 2 + 2 * 2 - 2 / 2; c) x = ( 3 * 9 * ( 3 + ( 9 * 3 / ( 3 ) ) ) );
  • 95. Ejercicios 63 2.16 (Aritmética) Escriba un programa que pida al usuario que escriba dos números, obtenga esos dos números del usuario e imprima la suma, producto, diferencia y cociente de los dos números. 2.17 (Impresión) Escriba un programa que imprima los números 1 a 4 en la misma línea con cada par de nú- meros adyacentes separado por un espacio. Haga esto de varias formas: a) Utilizando una instrucción con un operador de inserción de flujos. b) Utilizando una instrucción con cuatro operadores de inserción de flujos. c) Utilizando cuatro instrucciones. 2.18 (Comparación de enteros) Escriba un programa que pida al usuario que escriba dos enteros, obtenga los números del usuario y luego imprima el número más grande, seguido de las palabras es más grande. Si los nú- meros son iguales, imprima el mensaje Estos numeros son iguales. 2.19 (Aritmética, menor y mayor) Escriba un programa que reciba tres enteros del teclado e imprima la suma, promedio, producto, menor y mayor de estos números. El diálogo de la pantalla debe aparecer de la siguiente manera: Introduzca tres enteros distintos: 13 27 14 La suma es 54 El promedio es 18 El producto es 4914 El menor es 13 El mayor es 27 2.20 (Diámetro, circunferencia y área de un círculo) Escriba un programa que lea el radio de un círculo como un entero e imprima el diámetro del círculo, la circunferencia y el área. Use el valor constante 3.14159 para p. Realice todos los cálculos en instrucciones de salida. [Nota: en este capítulo sólo hemos visto constantes y variables tipo entero. En el capítulo 4 hablaremos sobre los números de punto flotante; es decir, valores que pueden tener puntos decimales]. 2.21 (Mostrar figuras con asteriscos) Escriba un programa que imprima un cuadro, un óvalo, una flecha y un diamante como se indica a continuación: ********* *** * * * * * * *** * * * * * * ***** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ********* *** * * 2.22 ¿Qué imprime el siguiente código? cout “*n**n***n****n*****” endl; 2.23 (Enteros mayor y menor) Escriba un programa que lea cinco enteros, determine e imprima los enteros mayor y menor en el grupo. Use sólo las técnicas de programación que aprendió en este capítulo. 2.24 (Impar o par) Escriba un programa que lea un entero, determine e imprima si es impar o par. [Sugerencia: use el operador módulo. Un número par es un múltiplo de dos. Cualquier múltiplo de dos deja un residuo de cero cuando se divide entre 2]. 2.25 (Múltiplos) Escriba un programa que lea dos enteros, determine e imprima si el primero es múltiplo del segundo. [Sugerencia: use el operador módulo].
  • 96. 64 Capítulo 2 Introducción a la programación en C++, entrada/salida y operadores 2.26 (Patrón de tablero de damas) Muestre el siguiente patrón de tablero de damas con ocho instrucciones de salida, y después muestre el mismo patrón utilizando el menor número de instrucciones posible. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2.27 (Equivalente entero de un carácter) He aquí un adelanto. En este capítulo aprendió sobre los enteros y el tipo int. C++ también puede representar letras mayúsculas, minúsculas y una considerable variedad de símbo- los especiales. C++ utiliza enteros pequeños de manera interna para representar cada uno de los distintos caracte- res. Al conjunto de caracteres que utiliza una computadora y las correspondientes representaciones enteras para esos caracteres se le conoce como el conjunto de caracteres de esa computadora. Puede imprimir un carácter encerrándolo entre comillas sencillas, como en el siguiente ejemplo: cout ‘A’; // imprimir una letra A mayúscula Puede imprimir el equivalente entero de un carácter mediante el uso de using static_cast, como en el siguiente ejemplo: cout static_cast int ( ‘A’ ); // imprime 'A' como un entero A esto se le conoce como operación de conversión (en el capítulo 4 presentaremos de manera formal las conver- siones). Cuando se ejecuta la instrucción anterior, imprime el valor 65 (en sistemas que utilizan el conjunto de caracteres ASCII). Escriba un programa que imprima el equivalente entero de un carácter escrito en el teclado. Almacene la entrada en una variable de tipo char. Pruebe su programa varias veces utilizando letras mayúsculas, minúsculas, dígitos y caracteres especiales (como $). 2.28 (Dígitos de un entero) Escriba un programa que reciba como entrada un entero de cinco dígitos, que se- pare ese número en sus dígitos individuales y los imprima, cada uno separado de los demás por tres espacios. [Su- gerencia: use los operadores de división entera y módulo]. Por ejemplo, si el usuario escribe el número 42339, el programa debe imprimir: 4 2 3 3 9 2.29 (Tabla) Utilice las técnicas de este capítulo para escribir un programa que calcule los cuadrados y cubos de los enteros del 0 al 10. Use tabuladores para imprimir la siguiente tabla ordenada de valores: entero cuadrado cubo 0 0 0 1 1 1 2 4 8 3 9 27 4 16 64 5 25 125 6 36 216 7 49 343 8 64 512 9 81 729 10 100 1000
  • 97. Hacer la diferencia 65 Hacer la diferencia 2.30 (Calculadora del índice de masa corporal) En el ejercicio 1.9 introdujimos la calculadora del índice de masa corporal (BMI). Las fórmulas para calcular el BMI son 1.6 703 BMI pesoEnLibras alturaEnPulgadas alturaEnPulgadas = × × o BMI pesoEnKilogramos alturaEnMetros alturaEnMetros = × Cree una aplicación de calculadora del BMI que lea el peso del usuario en libras y la altura en pulgadas (o, si lo prefiere, el peso del usuario en kilogramos y la altura en metros), para que luego calcule y muestre el índice de masa corporal del usuario. La aplicación debe mostrar además la siguiente información del Departamento de Salud y Servicios Humanos/Instituto Nacional de Salud para que el usuario pueda evaluar su BMI: VALORES DE BMI Bajo peso: menos de 18.5 Normal: entre 18.5 y 24.9 Sobrepeso: entre 25 y 29.9 Obeso: 30 o más [Nota: en este capítulo aprendió a usar el tipo int para representar números enteros. Cuando se realizan los cálcu- los del BMI con valores int se producen resultados en números enteros. En el capítulo 4 aprenderá a usar el tipo double, para representar a los números con puntos decimales. Cuando se realizan los cálculos del BMI con valo- res double, producen números con puntos decimales; a éstos se les conoce como números de punto flotante]. 2.31 (Calculadora de ahorro por viajes compartidos en automóvil) Investigue varios sitios Web de viajes com- partidos en automóvil. Cree una aplicación que calcule su costo diario al conducir su automóvil, de modo que pueda estimar cuánto dinero puede ahorrar si comparte los viajes en automóvil, lo cual también tiene otras ven- tajas, como la reducción de las emisiones de carbono y la reducción de la congestión de tráfico. La aplicación debe recibir como entrada la siguiente información y mostrar el costo por día para el usuario por conducir al trabajo: a) Total de kilómetros conducidos por día. b) Costo por litro de gasolina. c) Promedio de kilómetros por litro. d) Cuotas de estacionamiento por día. e) Peaje por día.
  • 98. Introducción a las clases, objetos y cadenas 3 Nada puede tener valor sin ser un objeto de utilidad. —Karl Marx Sus sirvientes públicos le sirven bien. —Adlai E. Stevenson Saber cómo responder a alguien que habla, Contestar a alguien que envía un mensaje. —Amenemopel O b j e t i v o s En este capítulo aprenderá a: n Definir una clase y utilizarla para crear un objeto. n Implementar los comportamientos de una clase como funciones miembro. n Implementar los atributos de una clase como datos miembros. n Llamar a una función miembro de un objeto para realizar una tarea. n Diferenciar entre los datos miembros de una clase y las variables locales de una función. n Utilizar un constructor para inicializar los datos de un objeto al momento de crear el objeto. n Maquinar la interfaz de la implementación de una clase y fomentar la reutilización. n Usar objetos de la clase string.
  • 99. 3.2 Definición de una clase con una función miembro 67 3.1Introducción En el capítulo 2 creó programas simples que mostraban mensajes al usuario, obtenían información del usuario, realizaban cálculos y tomaban decisiones. En este capítulo empezará a escribir programas que emplean los conceptos básicos de la programación orientada a objetos que presentamos en la sección 1.8. Una característica común de todos los programas del capítulo 2 fue que todas las instrucciones que ejecutaban tareas se encontraban en la función main. Por lo general, los programas que desarrollará en este libro consistirán de la función main y una o más clases, cada una de las cuales puede contener datos miembros y funciones miembro. Si usted se integra a un equipo de desarrollo en la industria, podría tra- bajar en sistemas de software que contengan cientos, o incluso miles, de clases. En este capítulo desarro- llaremos un marco de trabajo simple y bien maquinado para organizar los programas orientados a obje- tos en C++. Presentaremos una secuencia cuidadosamente pautada de programas funcionales completos para demostrarle cómo crear y utilizar sus propias clases. Estos ejemplos empiezan nuestro caso de estudio integrado acerca de cómo desarrollar una clase tipo libro de calificaciones, que los instructores pueden utilizar para mantener las calificaciones de las pruebas de sus estudiantes. También presentaremos la clase string de la biblioteca estándar de C++. 3.2Definición de una clase con una función miembro Vamos a empezar con un ejemplo (figura 3.1) que consiste en la clase LibroCalificaciones (líneas 8 a 16) que, cuando se desarrolle en su totalidad en el capítulo 7, representará a un libro de calificaciones que un instructor puede utilizar para mantener las calificaciones de los exámenes de sus estudiantes, y una función main (líneas 19 a 23) que crea un objeto LibroCalificaciones. La función main utiliza este objeto y su función miembro mostrarMensaje (líneas 12 a 15) para mostrar un mensaje en la pan- talla y dar la bienvenida al instructor al programa del libro de calificaciones. 1 // Fig. 3.1: fig03_01.cpp 2 // Define la clase LibroCalificaciones con una función miembro llamada mostrarMensaje 3 // crea un objeto LibroCalificaciones y llama a su función mostrarMensaje. 4 #include iostream 5 using namespace std; 3.1 Introducción 3.2 Definición de una clase con una función miembro 3.3 Definición de una función miembro con un parámetro 3.4 Datos miembros, funciones establecer y obtener 3.5 Inicialización de objetos mediante constructores 3.6 Colocar una clase en un archivo separado para fines de reutilización 3.7 Separar la interfaz de la implementación 3.8 Validación de datos mediante funciones establecer 3.9 Conclusión Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios | Hacer la diferencia Fig. 3.1  Define la clase LibroCalificaciones con una función miembro llamada mostrarMensaje, crea un objeto LibroCalificaciones y llama a su función mostrarMensaje (parte 1 de 2).
  • 100. 68 Capítulo 3 Introducción a las clases, objetos y cadenas 6 7 // Definición de la clase LibroCalificaciones 8 class LibroCalificaciones 9 { 10 public: 11 // función que muestra un mensaje de bienvenida para el usuario de LibroCalificaciones 12 void mostrarMensaje() const 13 { 14 cout Bienvenido al Libro de calificaciones! endl; 15 } // fin de la función mostrarMensaje 16 }; // fin de la clase LibroCalificaciones 17 18 // la función main empieza la ejecución del programa 19 int main() 20 { 21 LibroCalificaciones miLibroCalificaciones; // crea un objeto LibroCalificaciones llamado miLibroCalificaciones 22 miLibroCalificaciones.mostrarMensaje(); // llama a la función mostrarMensaje del objeto 23 } // fin de main Bienvenido al Libro de calificaciones! La clase LibroCalificaciones Antes de que la función main (líneas 19 a 23) pueda crear un objeto de la clase LibroCalificaciones, debemos indicar al compilador qué funciones miembro y cuáles datos miembros pertenecen a la clase. La definición de la clase LibroCalificaciones (líneas 8 a 16) contiene una función miembro llama- da mostrarMensaje (líneas 12 a 15), la cual muestra un mensaje en la pantalla (línea 14). Necesitamos crear un objeto de la clase LibroCalificaciones (línea 21) y llamar a su función miembro mostrar- Mensaje (línea 22) para hacer que se ejecute la línea 14 y se muestre el mensaje de bienvenida. Pronto explicaremos las líneas 21 y 22 con detalle. La definición de la clase empieza en la línea 8 con la palabra clave class, seguida del nombre de la claseLibroCalificaciones.Porconvención,elnombredeunaclasedefinidaporelusuarioempiezacon una letra mayúscula, y por legibilidad, cada palabra subsiguiente en el nombre de la clase empieza con una letra mayúscula. Este estilo de capitalización se conoce comúnmente como nomenclatura de Pascal, debido a que esta convención era muy utilizada en el lenguaje de programación Pascal. Las oca- sionales letras mayúsculas se asemejan a las jorobas de un camello. En términos más generales, el estilo de capitalización conocido como nomenclatura de camello permite que la primera letra sea mayúscula o minúscula (como miLibroCalificaciones en la línea 21). El cuerpo de cada clase va encerrado entre un par de llaves izquierda y derecha ({ y }), como en las líneas 9 y 16. La definición de la clase termina con un punto y coma (línea 16). Error común de programación 3.1 Olvidar el punto y coma al final de la definición de una clase es un error de sintaxis. Recuerde que la función main siempre se llama de manera automática cuando ejecutamos un programa. Lamayoríadelasfuncionesnosellamandemaneraautomática.Comoprontoveremos,debemosllamar a la función miembro mostrarMensaje de manera explícita para indicarle que debe realizar su tarea. Fig. 3.1  Define la clase LibroCalificaciones con una función miembro llamada mostrarMensaje, crea un objeto LibroCalificaciones y llama a su función mostrarMensaje (parte 2 de 2).
  • 101. 3.2 Definición de una clase con una función miembro 69 La línea 10 contiene la palabra clave public, que es un especificador de acceso. En las líneas 12 a 15 se define la función miembro mostrarMensaje. Esta función miembro aparece después del especifi- cador de acceso public: para indicar que la función está “disponible para el público”; es decir, otras funciones en el programa (como main) la pueden llamar, y también las funciones miembro de otras clases (si las hay). Los especificadores de acceso siempre van seguidos de un signo de dos puntos (:). En el resto del libro, cuando hagamos referencia al especificador de acceso public, omitiremos el punto y coma como en esta oración. En la sección 3.4 presentaremos el especificador de acceso private. Más adelante en el libro estudiaremos el especificador de acceso protected. Cada función en un programa realiza una tarea y puede devolver un valor cuando complete su tarea; por ejemplo, una función podría realizar un cálculo y después devolver el resultado del mismo. Al defi- nir una función, debemos especificar un tipo de valor de retorno para indicar el tipo de valor que de- vuelve la función cuando completa su tarea. En la línea 12, la palabra clave void a la izquierda del nombre de la función mostrarMensaje es el tipo de valor de retorno de ésta. El tipo de valor de retorno void indica que mostrarMensaje no devolverá datos a la función que la llamó (en este ejemplo, la línea 22 de main, como veremos en unos momentos) cuando complete su tarea. En la figura 3.5 veremos un ejemplo de una función que sí devuelve un valor. El nombre de la función miembro, mostrarMensaje, va después del tipo de valor de retorno (línea 12). Por convención, los nombres de nuestras funciones usan el estilo de nomenclatura de camello con la primera letra en minúscula. Los paréntesis después del nombre de la función miembro indican que ésta es una función. Un conjunto vacío de paréntesis, como se muestra en la línea 12, indica que esta función miembro no requiere datos adicionales para realizar su tarea. En la sección 3.3 veremos un ejemplo de una función miembro que sí requiere datos adicionales. Declaramos la función miembro mostrarMensaje como const en la línea 12 debido a que, en el proceso de mostrar el mensaje Bienvenido al libro de calificaciones! la función no modifica (y no debería hacerlo) el objeto LibroCalificaciones sobre el cual se llama. Al declarar mostrar- Mensaje como const indicamos al compilador que “esta función no debe modificar el objeto sobre el cual se llama; y si lo hace, hay que generar un error de compilación”. Esto puede ayudar al programador a localizar errores en caso de insertar por accidente código en mostrarMensaje que pudiera modificar el objeto. La línea 12 se conoce comúnmente como encabezado de función. El cuerpo de todas las funciones está delimitado por las llaves izquierda y derecha ({ y }), como en las líneas 13 y 15. El cuerpo de la función contiene instrucciones que realizan la tarea de esa función. En este caso, la función miembro mostrarMensaje contiene una instrucción (línea 14) que muestra el mensaje Bienvenido al Libro de calificaciones!. Una vez que se ejecute esta instrucción, la función habrá completado su tarea. Prueba de la clase LibroCalificaciones A continuación nos gustaría utilizar la clase LibroCalificaciones en un programa. Como aprendió en el capítulo 2, la función main (líneas 19 a 23) empieza la ejecución de todos los programas. En este programa queremos llamar a la función miembro mostrarMensaje de la clase LibroCali- ficaciones para mostrar el mensaje de bienvenida. Por lo general, no podemos llamar a una función miembro de una clase, sino hasta crear un objeto de esa clase. (Como veremos en la sección 9.14, las funciones miembro static son una excepción). En la línea 21 se crea un objeto de la clase LibroCali- ficaciones, llamado miLibroCalificaciones. El tipo de la variable es LibroCalificaciones: la cla- se que definimos en las líneas 8 a 16. Cuando declaramos variables de tipo int, como en el capítulo 2, el compilador sabe lo que es int: un tipo fundamental que está “integrado” a C++. Sin embargo, en la línea 21 el compilador no sabe automáticamente qué tipo corresponde a LibroCalificaciones: es un tipo definido por el usuario. Para indicar al compilador qué es LibroCalificaciones, incluimos la definición de la clase (líneas 8 a 16). Si omitiéramos estas líneas, el compilador generaría un mensaje de error. Cada nueva clase que creamos se convierte en un nuevo tipo que puede usarse para crear objetos. Los programadores pueden definir nuevos tipos de clases según lo necesiten; ésta es una razón por la cual C++ se conoce como un lenguaje de programación extensible.
  • 102. 70 Capítulo 3 Introducción a las clases, objetos y cadenas En la línea 22 se hace una llamada a la función miembro mostrarMensaje, usando la variable mi- LibroCalificaciones seguida del operador punto (.), el nombre de la función mostrarMensaje y un conjunto vacío de paréntesis. Esta llamada hace que la función mostrarMensaje realice su tarea. Al principio de la línea 22, “miLibroCalificaciones” indica que main debe usar el objeto LibroCalifi- caciones que se creó en la línea 21. Los paréntesis vacíos en la línea 12 indican que la función miembro mostrarMensaje no requiere datos adicionales para realizar su tarea, razón por la cual llamamos a esta función con paréntesis vacíos en la línea 22 (en la sección 3.3 veremos cómo pasar datos a una función). Cuando mostrarMensaje completa su tarea, el programa llega al final de main (línea 23) y termina. Diagrama de clases de UML para la clase LibroCalificaciones En la sección 1.8 vimos que UML es un lenguaje gráfico estandarizado, utilizado por los desarrolladores de software para representar sistemas orientados a objetos. En UML, cada clase se modela en un diagra- ma de clases de UML en forma de un rectángulo con tres compartimientos. La figura 3.2 presenta un diagrama de clases para la clase LibroCalificaciones (figura 3.1). El compartimiento superior contiene el nombre de la clase, centrado en forma horizontal y en negrita. El compartimiento de en medio contie- ne los atributos de la clase, que en C++ corresponden a los datos miembros. En estos momentos el com- partimiento está vacío, ya que la clase LibroCalificaciones no tiene atributos todavía (en la sección 3.4 presentaremos una versión de la clase LibroCalificaciones con un atributo). El compartimiento inferior contiene las operaciones de la clase, que en C++ corresponden a las funciones miembro. Para modelar las operaciones, UML lista el nombre de la operación seguido de un conjunto de paréntesis. La clase LibroCalificaciones tiene una sola función miembro llamada mostrarMensaje, por lo que el compartimiento inferior de la figura 3.2 lista una operación con este nombre. La función miembro mostrarMensaje no requiere información adicional para realizar sus tareas, por lo que los paréntesis que van después de mostrarMensaje en el diagrama de clases están vacíos, de igual forma que como apare- cieron en el encabezado de la función miembro, en la línea 12 de la figura 3.1. El signo más (+) que va antes del nombre de la operación indica que mostrarMensaje es una operación public en UML (es decir, una función miembro public en C++). LibroCalificaciones + mostrarMensaje( ) Fig. 3.2  Diagrama de clases de UML, el cual indica que la clase LibroCalificaciones tiene una operación mostrarMensaje pública. 3.3Definición de una función miembro con un parámetro En nuestra analogía del automóvil de la sección 1.8, hablamos sobre el hecho de que al oprimir el pedal del acelerador se envía un mensaje al automóvil para que realice una tarea: hacer que vaya más rápido. Pero ¿qué tan rápido debería acelerar el automóvil? Como sabe, entre más oprima el pedal, mayor será la aceleración del automóvil. Por lo tanto, el mensaje para el automóvil en realidad incluye tanto la tarea a realizarcomoinformaciónadicionalqueayudaalautomóvilarealizarsutarea.Alainformaciónadicional se le conoce como parámetro; el valor del parámetro ayuda al automóvil a determinar qué tan rápido debe acelerar. De manera similar, una función miembro puede requerir uno o más parámetros que re- presentan la información adicional que necesita para realizar su tarea. La llamada a una función propor- ciona valores llamados argumentos para cada uno de los parámetros de esa función. Por ejemplo, para realizar un depósito en una cuenta bancaria, suponga que una función miembro llamada depositar de una clase Cuenta especifica un parámetro que representa el monto a depositar. Cuando se hace una lla-
  • 103. 3.3 Definición de una función miembro con un parámetro 71 mada a la función miembro deposito, se copia al parámetro de la función miembro un valor como argumento, que representa el monto a depositar. Después, la función miembro suma esa cantidad al saldo de la cuenta. Definición y prueba de la clase LibroCalificaciones Nuestro siguiente ejemplo (figura 3.3) redefine la clase LibroCalificaciones (líneas 9 a 18) con una función miembro mostrarMensaje (líneas 13 a 17) que muestra el nombre del curso como parte del mensaje de bienvenida. La nueva versión de mostrarMensaje requiere un parámetro (nombreCurso en la línea 13) que representa el nombre del curso a imprimir en pantalla. 1 // Fig. 3.3: fig03_03.cpp 2 // Define la clase LibroCalificaciones con una función miembro que recibe un parámetro, 3 // crea un objeto LibroCalificaciones y llama a su función mostrarMensaje. 4 #include iostream 5 #include string // el programa usa la clase string estándar de C++ 6 using namespace std; 7 8 // definición de la clase LibroCalificaciones 9 class LibroCalificaciones 10 { 11 public: 12 // función que muestra un mensaje de bienvenida para el usuario de LibroCalificaciones 13 void mostrarMensaje( string nombreCurso ) const 14 { 15 cout Bienvenido al libro de calificaciones paran nombreCurso ! 16 endl; 17 } // fin de la función mostrarMensaje 18 }; // fin de la clase LibroCalificaciones 19 20 // la función main empieza la ejecución del programa 21 int main() 22 { 23 string nombreDelCurso; // cadena de caracteres que almacena el nombre del curso 24 LibroCalificaciones miLibroCalificaciones; // crea un objeto LibroCalificaciones llamado mi LibroCalificaciones 25 26 // pide y recibe el nombre del curso como entrada 27 cout Escriba el nombre del curso: endl; 28 getline( cin, nombreDelCurso ); // lee el nombre de un curso con espacios en blanco 29 cout endl; // imprime una línea en blanco 30 31 // llama a la función mostrarMensaje de miLibroCalificaciones 32 // y pasa nombreDelCurso como argumento 33 miLibroCalificaciones.mostrarMensaje( nombreDelCurso ); 34 } // fin de main Fig. 3.3  Define la clase LibroCalificaciones con una función miembro que recibe un parámetro, crea un objeto LibroCalificaciones y llama a su función mostrarMensaje (parte 1 de 2).
  • 104. 72 Capítulo 3 Introducción a las clases, objetos y cadenas Escriba el nombre del curso: CS101 Introduccion a la programacion en C++ Bienvenido al libro de calificaciones para CS101 Introduccion a la programacion en C++! Antes de hablar sobre las nuevas características de la clase LibroCalificaciones, veamos cómo se utiliza la nueva clase en main (líneas 21 a 34). En la línea 23 se crea una variable de tipo string llamada nombreDelCurso, la cual se utilizará para almacenar el nombre del curso que escriba el usuario. Una variable de tipo string representa a una cadena de caracteres tal como CS101 Introduccion a la programacion en C++. En realidad, una cadena es un objeto de la clase string, de la Biblioteca están- dar de C++. Esta clase se define en el encabezado string, y el nombre string (al igual que cout) pertenece al espacio de nombres std. Para que las líneas 13 y 23 se puedan compilar, en la línea 5 se incluye el encabezado string. La directiva using en la línea 6 nos permite escribir simplemente string en la línea 23, en vez de std::string. Por ahora, puede considerar a las variables string como las variables de otros tipos como int. En la sección 3.8 y en el capítulo 21 (en inglés, en el sitio web) aprenderá acerca de las herramientas adicionales de string. En la línea 24 se crea un objeto de la clase LibroCalificaciones, llamado miLibroCalificacio- nes. En la línea 27 se pide al usuario que escriba el nombre de un curso. En la línea 28 se lee el nombre del usuario y se asigna a la variable nombreDelCurso, usando la función de biblioteca getline para llevar a cabo la entrada. Antes de explicar esta línea de código, veamos por qué no podemos simplemente es- cribir cin nombreDelCurso; para obtener el nombre del curso. En la ejecución de ejemplo de nuestro programa, utilizamos el nombre “CS101 Introduccion a la programacion en C++”, el cual contiene varias palabras separadas por espacios en blanco (recuerde que resaltamos la entrada que suministra el usuario en negrita). Cuando se lee un objeto string con el ope- rador de extracción de flujo, cin lee caracteres hasta que se llega al primer carácter de espacio en blanco. Por ende, la instrucción anterior sólo leería “CS101”. El resto del nombre del curso tendría que leerse mediante operaciones de entrada subsiguientes. En este ejemplo, nos gustaría que el usuario escribiera el nombre completo del curso y que oprimie- ra Intro para enviarlo al programa, y nos gustaría almacenar el nombre completo del curso en la variable string llamada nombreDelCurso. La llamada a la función getline( cin, nombreDelCurso) en la línea 28 lee caracteres (incluyendo los caracteres de espacio que separan las palabras en la entrada) del objeto flujo de entrada estándar cin (es decir, el teclado) hasta encontrar el carácter de nueva línea, coloca los caracteresenlavariablestring llamadanombreDelCurso ydescartaelcarácterdenuevalínea.Aloprimir Intro mientras se introducen los datos, se inserta una nueva línea en el flujo de entrada. Debe incluirse el encabezado string en el programa para usar la función getline, que pertenece al espacio de nom- bres std. En la línea 33 se hace una llamada a la función miembro mostrarMensaje de miLibroCalifica- ciones. La variable nombreDelCurso entre paréntesis es el argumento que se pasa a la función miembro mostrarMensaje para que pueda realizar su tarea. El valor de la variable nombreDelCurso en main se copia al parámetro nombreCurso de la función miembro mostrarMensaje en la línea 13. Al ejecutar este programa, la función miembro mostrarMensaje imprime, como parte del mensaje de bienvenida, el Fig. 3.3  Define la clase LibroCalificaciones con una función miembro que recibe un parámetro, crea un objeto LibroCalificaciones y llama a su función mostrarMensaje (parte 2 de 2).
  • 105. 3.3 Definición de una función miembro con un parámetro 73 nombre del curso que usted escriba (en nuestra ejecución de ejemplo, CS101 Introduccion a la pro- gramacion en C++). Más sobre los argumentos y los parámetros Para especificar en la definición de una función que ésta requiere datos para realizar su tarea, hay que colocar información adicional en la lista de parámetros de la función, la cual se encuentra en los parén- tesis que van después del nombre de la función. La lista de parámetros puede contener cualquier núme- ro de parámetros, incluso ninguno (lo que se representa mediante los paréntesis vacíos, como en la línea 12 de la figura 3.1), para indicar que una función no requiere parámetros. La lista de parámetros de la función miembro mostrarMensaje (figura 3.3, línea 13) declara que la función requiere un parámetro. Cada parámetro debe especificar un tipo y un identificador. El tipo string y el identificador nombreCurso indican que la función miembro mostrarMensaje requiere un objeto string para realizar su tarea. El cuerpo de la función miembro utiliza el parámetro nombreDelCurso para acceder al valor que se pasa a la función en la llamada (línea 33 en main). En las líneas 15 y 16 se muestra el valor del parámetro nombreDelCurso como parte del mensaje de bienvenida. El nombre de la variable de parámetro (nombre- Curso en la línea 13) puede ser igual o distinto al nombre de la variable de argumento (nombreDelCurso en la línea 33); en el capítulo 6 aprenderá por qué. Una función puede especificar múltiples parámetros; sólo hay que separar un parámetro de otro mediante una coma. El número y el orden de los argumentos en la llamada a una función deben coincidir conelnúmeroyordendelosparámetrosenlalistadeparámetrosdelencabezadodelafunciónmiembro que se llamó. Además, los tipos de los argumentos en la llamada a la función deben ser consistentes con los tipos de los parámetros correspondientes al encabezado de la función (como veremos en capítulos posteriores, no siempre se requiere que el tipo de un argumento y el tipo de su correspondiente paráme- tro sean idénticos, pero deben ser “consistentes”). En nuestro ejemplo, el único argumento string en la llamada a la función (es decir, nombreDelCurso) coincide exactamente con el único parámetro string en la definición de la función miembro (es decir, nombreCurso). Diagrama de clases de UML actualizado para la clase LibroCalificaciones El diagrama de clases de UML de la figura 3.4 modela la clase LibroCalificaciones de la figura 3.3. Al igual que la clase LibroCalificaciones definida en la figura 3.1, esta clase LibroCalificaciones contiene la función miembro public llamada mostrarMensaje. Sin embargo, esta versión de mostrar- Mensaje tiene un parámetro. Para modelar un parámetro, UML lista el nombre del parámetro, seguido de dos puntos y del tipo del parámetro entre paréntesis, después del nombre de la operación. UML tiene sus propios tipos de datos similares a los de C ++. UML es independiente del lenguaje —se utiliza con mu- chos lenguajes de programación distintos— por lo que su terminología no coincide exactamente con la de C++. Por ejemplo, el tipo String de UML corresponde al tipo string de C++. La función miembro mostrarMensaje delaclaseLibroCalificaciones (figura3.3,líneas13a17)tieneunparámetrostring llamado nombreCurso, por lo que en la figura 3.4 se lista a nombreCurso : String entre los paréntesis que van después del nombre de la operación mostrarMensaje. Esta versión de la clase LibroCalificaciones aún no tiene datos miembros. LibroCalificaciones + mostrarMensaje (nombreCurso : String ) Fig. 3.4  Diagrama de clases de UML, que indica que la clase LibroCalificaciones tiene una operación pública llamada mostrarMensaje, con un parámetro llamado nombreCurso de tipo String de UML.
  • 106. 74 Capítulo 3 Introducción a las clases, objetos y cadenas 3.4Datos miembros, funciones establecer y obtener En el capítulo 2 declaramos todas las variables de un programa en su función main. Las variables que se declaran en el cuerpo de la definición de una función se conocen como variables locales, y sólo se pueden utilizar desde la línea de su declaración en la función, hasta la llave derecha de cierre (}) del bloque en el que se declaran. Una variable local se debe declarar antes de poder utilizarla en una fun- ción. No se puede acceder a una variable local fuera de la función en la que está declarada. Cuando una función termina, se pierden los valores de sus variables locales (en el capítulo 6 veremos una excepción a esto, cuando hablemos sobre las variables locales static). Por lo general, una clase consiste en una o más funciones miembro que manipulan los atributos pertenecientes a un objeto específico de la clase. Los atributos se representan como variables en la defi- nición de una clase. Dichas variables se llaman datos miembros y se declaran dentro de la definición de una clase, pero fuera de los cuerpos de las definiciones de las funciones miembro de la clase. Cada obje- to de una clase mantiene sus propios atributos en memoria. Estos atributos existen durante toda la vida del objeto. El ejemplo en esta sección demuestra una clase LibroCalificaciones, que contiene un dato miembro llamado nombreCurso para representar el nombre del curso de un objeto LibroCalificacio- nes específico. Si crea más de un objeto LibroCalificaciones, cada uno tendrá su propio miembro de datos nombreCurso, y éstos pueden tener diferentes valores. La clase LibroCalificaciones con un dato miembro, y funciones establecer y obtener En nuestro siguiente ejemplo, la clase LibroCalificaciones (figura 3.5) mantiene el nombre del curso como un dato miembro, para que pueda usarse o modificarse durante la ejecución de un programa. Esta clase contiene las funciones miembro establecerNombreCurso, obtenerNombreCurso y mostrar- Mensaje. La función miembro establecerNombreCurso almacena el nombre de un curso en un miem- bro de datos de LibroCalificaciones. La función miembro obtenerNombreCurso obtiene el nombre del curso de ese dato miembro. La función miembro mostrarMensaje, que en este caso no especifica parámetros, sigue mostrando un mensaje de bienvenida que incluye el nombre del curso. Pero como veremosmásadelante,lafunciónahoraobtieneelnombredelcursomedianteunallamadaaotrafunción en la misma clase: obtenerNombreCurso. 1 // Fig. 3.5: fig03_05.cpp 2 // Define la clase LibroCalificaciones que contiene un miembro de datos 3 // nombreCurso y funciones miembro para establecer y obtener su valor; 4 // Crea y manipula un objeto LibroCalificaciones con estas funciones. 5 #include iostream 6 #include string // el programa usa la clase string estándar de C++ 7 using namespace std; 8 9 // definición de la clase LibroCalificaciones 10 class LibroCalificaciones 11 { 12 public: 13 // función que establece el nombre del curso 14 void establecerNombreCurso( string nombre ) 15 { 16 nombreCurso = nombre; // almacena el nombre del curso en el objeto 17 } // fin de la función establecerNombreCurso Fig. 3.5  Definición y prueba de la clases LibroCalificaciones con un miembro de datos y funciones establecer y obtener (parte 1 de 2).
  • 107. 3.4 Datos miembros, funciones establecer y obtener 75 18 19 // función que obtiene el nombre del curso 20 string obtenerNombreCurso() const 21 { 22 return nombreCurso; // devuelve el nombreCurso del objeto 23 } // fin de la función obtenerNombreCurso 24 25 // función que muestra un mensaje de bienvenida 26 void mostrarMensaje() const 27 { 28 // esta instrucción llama a obtenerNombreCurso para obtener el 29 // nombre del curso que representa este LibroCalificaciones 30 cout Bienvenido al libro de calificaciones paran obtenerNombreCurso() ! 31 endl; 32 } // fin de la función mostrarMensaje 33 private: 34 string nombreCurso; // nombre del curso para este LibroCalificaciones 35 }; // fin de la clase LibroCalificaciones 36 37 // la función main empieza la ejecución del programa 38 int main() 39 { 40 string nombreDelCurso; // cadena de caracteres para almacenar el nombre del curso 41 LibroCalificaciones miLibroCalificaciones; // crea un objeto LibroCalificaciones llamado miLibroCalificaciones 42 43 // muestra el valor inicial de nombreCurso 44 cout El nombre inicial del curso es: miLibroCalificaciones.obtenerNombreCurso() 45 endl; 46 47 // pide, recibe y establece el nombre del curso 48 cout nEscriba el nombre del curso: endl; 49 getline( cin, nombreDelCurso ); // lee el nombre de un curso con espacios en blanco 50 miLibroCalificaciones.establecerNombreCurso( nombreDelCurso ) // establece el nombre del curso 51 52 cout endl; // imprime una línea en blanco 53 miLibroCalificaciones.mostrarMensaje(); // muestra un mensaje con el nuevo nombre del curso 54 } // fin de main El nombre inicial del curso es: Escriba el nombre del curso: CS101 Introduccion a la programacion en C++ Bienvenido al libro de calificaciones parar CS101 Introduccion a la programacion en C++! Fig. 3.5  Definición y prueba de la clases LibroCalificaciones con un miembro de datos y funciones establecer y obtener (parte 2 de 2).
  • 108. 76 Capítulo 3 Introducción a las clases, objetos y cadenas Un instructor típico enseña varios cursos, cada uno con su propio nombre. En la línea 34 se declara que nombreCurso es una variable de tipo string. Como la variable se declara en la definición de la clase (líneas 10 a 35) pero fuera de los cuerpos de las definiciones de las funciones miembro de ésta (líneas 14 a 17, 20 a 23 y 26 a 32), la variable es un dato miembro. Cada instancia (es decir, objeto) de la clase LibroCalificaciones contiene cada uno de los datos miembros de la clase; si hay dos objetos Libro- Calificaciones, cada objeto tiene su propio nombreCurso (uno por cada objeto), como veremos en el ejemplo de la figura 3.7. Un beneficio de hacer de nombreCurso un dato miembro es que todas las fun- ciones miembro de la clase pueden manipular cualquier dato miembro que aparezca en la definición de la clase (en este caso, nombreCurso). Los especificadores de acceso public y private La mayoría de las declaraciones de datos miembros aparecen después del especificador de accesos pri- vate. Las variables o funciones declaradas después del especificador de acceso private (y antes del si- guiente especificador de acceso, si hay uno) son accesibles sólo para las funciones miembro de la clase en la que se declaran (o para las “amigas” de la clase, como veremos en el capítulo 9). Así, el miembro de datos nombreCurso sólo puede utilizarse en las funciones miembro establecerNombreCurso, obtener- NombreCurso y mostrarMensaje de la clase LibroCalificaciones (o en las “amigas” de la clase, si las hay). Tip para prevenir errores 3.1 Al hacer que los datos miembros de una clase sean private y las funciones miembro de la clase sean public, se facilita la depuración debido a que los problemas con las manipulacio- nes de datos se localizan, ya sea en las funciones miembro de la clase o en las amigas de ésta. Error común de programación 3.2 Si una función, que no sea miembro de una clase específica (o amiga de esa clase), intenta acceder a un miembro private de esa clase, se produce un error de compilación. El acceso predeterminado para los datos miembros es private, de manera que todos los miembros después del encabezado de la clase y antes del primer especificador de acceso son private. Los especifi- cadores de acceso public y private pueden repetirse, pero esto es innecesario y puede ser confuso. El proceso de declarar datos miembros con el modificador de acceso private se conoce como ocultamiento de datos. Cuando un programa crea un objeto de la clase LibroCalificaciones, el dato miembro nombreCurso se encapsula (oculta) en el objeto, y sólo está accesible para las funciones miem- bro de la clase de ese objeto. En la clase LibroCalificaciones, las funciones miembro establecer- NombreCurso y obtenerNombreCurso manipulan al dato miembro nombreCurso directamente. Las funciones miembro establecerNombreCurso y obtenerNombreCurso La función miembro obtenerNombreCurso (líneas 14 a 17) no devuelve datos cuando completa su tarea, por lo que su tipo de valor de retorno es void. La función miembro recibe un parámetro nombre, el cual representa el nombre del curso que recibirá como argumento (como veremos en la línea 50 de main). La línea 16 asigna nombre al dato miembro nombreCurso, con lo cual se modifica el objeto; por esta razón no declaramos a establecerNombreCurso como const. En este ejemplo, establecerNombreCurso no vali- da el nombre del curso; es decir, la función no verifica que el nombre del curso se apegue a cierto formato en especial, o que siga cualquier otra regla en relación con la apariencia que debe tener un nombre de curso “válido”. Por ejemplo, suponga que una universidad puede imprimir certificados de los estudiantes que contengan los nombres de cursos de sólo 25 caracteres o menos. En este caso, es conveniente que la clase LibroCalificaciones asegure que su dato miembro nombreCurso nunca contendrá más de 25 caracteres. En la sección 3.8 hablaremos sobre la validación. La función miembro obtenerNombreCurso (definida en las líneas 20 a 23) devuelve un valor de nombreCurso de un objeto LibroCalificaciones específico, sin modificar el objeto; por esta razón declaramos a obtenerNombreCurso como const. La función miembro tiene una lista de parámetros
  • 109. 3.4 Datos miembros, funciones establecer y obtener 77 vacía, por lo que no requiere información adicional para realizar su tarea. La función específica que de- vuelve un objeto string. Cuando se hace una llamada a una función que especifica un tipo de valor de retorno distinto de void, y ésta completa su tarea, la función usa una instrucción return (como en la línea 22) para devolver un resultado a la función que la llamó. Por ejemplo, cuando usted va a un caje- ro automático (ATM) y solicita el saldo de su cuenta, espera que el ATM le devuelva un valor que repre- senta su saldo. De manera similar, cuando una instrucción llama a la función miembro obtener- NombreCurso en un objeto LibroCalificaciones, la instrucción espera recibir el nombre del curso de LibroCalificaciones (en este caso, un objeto string, como se especifica en el tipo de valor de retorno de la función). Si tiene una función llamada cuadrado que devuelve el cuadrado de su argumento, es de esperarse que la instrucción resultado = cuadrado( 2 ); devuelva 4 de la función cuadrado y asigne el valor 4 a la variable resultado. Si tiene una función lla- mada maximo que devuelve el mayor de tres argumentos enteros, la siguiente instrucción mayor = maximo( 27, 114, 51 ); devuelve 114 de la función maximo y asigna este valor a la variable mayor. Las instrucciones en las líneas 16 y 22 utilizan la variable nombreCurso (línea 34), aun y cuando esta variable no se declaró en ninguna de las funciones miembro. Podemos hacer esto ya que nombreCurso es un miembro de datos de la clase y éstos son accesibles desde las funciones miembro de una clase. La función miembro mostrarMensaje La función miembro mostrarMensaje (líneas 26 a 32) no devuelve datos cuando completa su tarea, por lo que su tipo de valor de retorno es void. Esta función no recibe parámetros, por lo que su lista de parámetros está vacía. En las líneas 30 y 31 se imprime un mensaje de bienvenida, que incluye el valor del dato miembro nombreCurso. La línea 30 llama a la función miembro obtenerNombreCurso para obtener el valor de nombreCurso. La función miembro mostrarMensaje también podría acceder al dato miembro nombreCurso directamente, así como las funciones miembro establecerNombre- Curso y obtenerNombreCurso. En breve explicaremos por qué es preferible, desde la perspectiva de ingeniería de software, llamar a la función miembro obtenerNombreCurso para obtener el valor de nombreCurso. Prueba de la clase LibroCalificaciones La función main (líneas 38 a 54) crea un objeto de la clase LibroCalificaciones y utiliza cada una de sus funciones miembro. En la línea 41 se crea un objeto LibroCalificaciones llamado miLibroCali- ficaciones.Enlaslíneas44a45semuestraelnombreinicialdelcurso,llamandoalafunciónmiembro obtenerNombreCurso del objeto. La primera línea de la salida no muestra un nombre de curso, ya que al principio el dato miembro nombreCurso del objeto (es decir, un string) está vacío; de manera prede- terminada, el valor inicial de un objeto string es lo que se denomina cadena vacía (una cadena que no contiene caracteres). No aparece nada en la pantalla cuando se muestra una cadena vacía. En la línea 48 se pide al usuario que escriba el nombre de un curso. La variable string local nom- breDelCurso (declarada en la línea 40) se inicializa con el nombre del curso que escribió el usuario, el cual se devuelve mediante la llamada a la función getline (línea 49). En la línea 50 se hace una lla- mada a la función miembro establecerNombreCurso del objeto miLibroCalificaciones y se provee nombreDelCurso como argumento para la función. Cuando se hace la llamada a la función, el valor del argumento se copia al parámetro nombre (línea 14) de la función miembro establecerNombreCurso. Después, el valor del parámetro se asigna al dato miembro nombreCurso (línea 16). En la línea 52 se salta una línea en la salida; después en la línea 53 se hace una llamada a la función mostrarMensaje del objeto miLibroCalificaciones para mostrar en pantalla el mensaje de bienvenida, que contiene el nombre del curso.
  • 110. 78 Capítulo 3 Introducción a las clases, objetos y cadenas Ingeniería de software mediante las funciones establecer y obtener Los datos miembros private de una clase pueden manipularse sólo mediante las funciones miembro de esa clase (y por los “amigos” de la clase, como veremos en el capítulo 9). Por lo tanto, un cliente de un objeto (es decir, cualquier instrucción que llame a las funciones miembro del objeto desde su exterior) llama a las funciones miembro public de la clase para solicitar los servicios de la clase para objetos espe- cíficos de ésta. Esto explica por qué las instrucciones en la función main llaman a las funciones miembro establecerNombreCurso, obtenerNombreCurso y mostrarMensaje en un objeto LibroCalificacio- nes. A menudo, las clases proporcionan funciones miembro public para permitir a los clientes de la clase establecer (es decir, asignar valores a) u obtener (es decir, obtener los valores de) datos miembros private. Los nombres de estas funciones miembro no necesitan empezar con establecer u obtener, pero esta convención de nomenclatura es común. En este ejemplo, la función miembro que establece el dato miembro nombreCurso se llama establecerNombreCurso, y la función miembro que obtiene el valor del miembro de datos nombreCurso se llama obtenerNombreCurso. Las funciones establecer se conocen también como mutadores (porque mutan, o modifican, valores) y las funciones obtener se conocen también como accesores (porque acceden a los valores). Recuerde que al declarar datos miembros con el especificador de acceso private se cumple con la ocultación de datos. Al proporcionar funciones establecer y obtener public, permitimos que los clientes de una clase accedan a los datos ocultos, pero sólo en forma indirecta. El cliente sabe que está intentan- do modificar u obtener los datos de un objeto, pero no sabe cómo el objeto lleva a cabo estas operaciones. En algunos casos, una clase puede representar internamente una pieza de datos de cierta forma, pero puede exponer esos datos a los clientes de una forma distinta. Por ejemplo, suponga que una clase Reloj representa la hora del día como un miembro de datos private int llamado hora, que almacena el nú- mero de segundos transcurridos desde media noche. Sin embargo, cuando un cliente llama a la función miembro obtenerHora de un objeto Reloj, el objeto podría devolver la hora con horas, minutos y se- gundos en un objeto string, en el formato HH:MM:SS. De manera similar, suponga que la clase Reloj cuenta con una función establecer llamada establecerHora, que recibe un parámetro string en el formato HH:MM:SS. Mediante el uso de las herramientas de la clase string (que puede ver en el capí- tulo 21, el cual se encuentra en inglés en el sitio web), la función establecerHora podría convertir este objeto string en un número de segundos, que la función almacena en su dato miembro private. La función establecer también podría verificar que el valor que recibe represente una hora válida (por ejem- plo, 12:30:45 es válida, pero 42:85:70 no). Las funciones establecer y obtener permiten que un cliente interactúe con un objeto, pero los datos private del objeto permanecen encapsulados (ocultos) de una manera segura dentro del mismo objeto. Las funciones establecer y obtener de una clase también deben utilizarlas otras funciones miembro dentro de la clase, para manipular los datos private de ésta, aunque estas funciones miembro pueden acceder a los datos private directamente. En la figura 3.5, las funciones miembro establecerNombre- Curso y obtenerNombreCurso son funciones miembro public, por lo que están accesibles para los clientes de la clase, así como para la misma clase. La función miembro mostrarMensaje llama a la fun- ción miembro obtenerNombreCurso para obtener el valor del dato miembro nombreCurso y mostrarlo en pantalla, aun y cuando mostrarMensaje puede acceder directamente a nombreCurso; al acceder a un dato miembro a través de su función obtener se crea una clase más robusta y mejor (es decir, una clase que sea fácil de mantener y menos propensa a dejar de trabajar). Si decidimos cambiar el dato miembro nombreCurso en cierta forma, la definición de mostrarMensaje no requerirá modificación; sólo los cuerpos de las funciones establecer y obtener, que manipulan directamente al dato miembro, tendrán que cambiar. Por ejemplo, suponga que decidimos representar el nombre del curso como dos datos miembro separados:nombreCurso (porejemplo,CS101)ytituloCurso (porejemplo,Introduccion a la pro- gramacion en C++). La función miembro mostrarMensaje puede aún emitir una sola llamada a la función miembro obtenerNombreCurso para obtener el nombre completo del curso y mostrarlo como parte del mensaje de bienvenida. En este caso, obtenerNombreCurso necesitaría crear y devolver un objeto string que contenga el nombreCurso seguido del tituloCurso. La función miembro mostrar- Mensaje seguiría mostrando el título completo del curso “CS101 Introduccion a la programacion
  • 111. 3.5 Inicialización de objetos mediante constructores 79 en C++”. Los beneficios de llamar a una función establecer desde otra función miembro de la clase se volverán más claros cuando hablemos sobre la validación en la sección 3.8. Buena práctica de programación 3.1 Trate siempre de localizar los efectos de las modificaciones a los datos miembros de una clase, utilizando y manipulando los datos miembros a través de sus correspondientes funciones obtener y establecer. Observación de Ingeniería de Software 3.1 Escriba programas que sean comprensibles y fáciles de mantener. El cambio es la regla, en vez de la excepción. Debemos anticipar que nuestro código será modificado en el futuro. Diagrama de clases de UML para la clase LibroCalificaciones con un dato miembro, y funciones establecer y obtener La figura 3.6 contiene un diagrama de clases de UML actualizado para la versión de la clase Libro- Calificaciones de la figura 3.5. Este diagrama modela el miembro de datos nombreCurso de la clase LibroCalificaciones comounatributoenelcompartimientointermedio.UMLrepresentaalosdatos miembros como atributos, listando el nombre del atributo, seguido de dos puntos y del tipo del atributo. El tipo de UML del atributo nombreCurso es String, que corresponde al tipo string en C++. El miembro de datos nombreCurso es private en C++, por lo que el diagrama de clases lista un signo menos (-) en frente del nombre del atributo correspondiente. La clase LibroCalificaciones contiene tres funciones miembro public, por lo que el diagrama de clases lista tres operaciones en el tercer compar- timiento. La operación establecerNombreCurso tiene un parámetro String llamado nombre. UML indica el tipo de valor de retorno de una operación colocando dos puntos y el tipo de valor de retorno después de los paréntesis que le siguen al nombre de la operación. La función miembro obtener- NombreCurso de la clase LibroCalificaciones tiene un tipo de valor de retorno string en C++, por lo que el diagrama de clases muestra un tipo de valor de retorno String en UML. Las operaciones esta- blecerNombreCurso y mostrarMensaje no devuelven valores (es decir, devuelven void en C++), por lo que el diagrama de clases de UML no especifica un tipo de valor de retorno después de los paréntesis de estas operaciones. LibroCalificaciones – nombreCurso : String + establecerNombreCurso ( nombre : String ) + obtenerNombreCurso( ) : String + mostrarMensaje( ) Fig. 3.6  Diagrama de clases de UML para la clase LibroCalificaciones, con un atributo privado nombreCurso y operaciones públicas establecerNombreCurso, obtenerNombreCurso y mostrarMensaje. 3.5Inicialización de objetos mediante constructores Como mencionamos en la sección 3.4, cuando se crea un objeto de la clase LibroCalificaciones (fi- gura 3.5), su miembro de datos nombreCurso se inicializa con la cadena vacía de manera predetermina- da. ¿Qué pasa si usted desea proporcionar el nombre de un curso a la hora de crear un objeto Libro- Calificaciones? Cada clase que usted declare puede proporcionar uno o más constructores, los cuales pueden utilizarse para inicializar un objeto de una clase al momento de crear ese objeto. Un constructor es una función miembro especial que debe definirse con el mismo nombre que el de la clase, de manera
  • 112. 80 Capítulo 3 Introducción a las clases, objetos y cadenas que el compilador pueda diferenciarlo de las demás funciones miembro de la clase. Una importante diferencia entre los constructores y las otras funciones es que los primeros no pueden devolver valores, por lo cual no pueden especificar un tipo de valor de retorno (ni siquiera void). Normalmente los construc- tores son declarados public. En los primeros capítulos, por lo general nuestras clases tendrán un cons- tructor; en capítulos posteriores veremos cómo crear clases con más de un constructor, mediante la técnica de sobrecarga de funciones, que presentaremos en la sección 6.18. C++ llama de manera automática a un constructor para cada objeto que se crea, lo cual ayuda a ase- gurar que cada objeto se inicialice antes de utilizarlo en un programa. La llamada al constructor ocurre cuando se crea el objeto. Si una clase no incluye constructores en forma explícita, el compilador propor- ciona un constructor predeterminado sin parámetros. Por ejemplo, cuando en la línea 41 de la figura 3.5 se crea un objeto LibroCalificaciones, se hace una llamada al constructor predeterminado. Este constructor proporcionado por el compilador crea un objeto LibroCalificaciones sin proporcionar ningún valor inicial para los datos miembros de tipos fundamentales del objeto. Para los datos miembros que son objetos de otras clases, el constructor predeterminado llama de manera implícita al constructor predeterminado de cada dato miembro, para asegurar que ese dato miembro se inicialice en forma apro- piada. Ésta es la razón por la cual el dato miembro string llamado nombreCurso (en la figura 3.5) se inicializó con la cadena vacía; el constructor predeterminado para la clase string asigna al valor del ob- jeto string a la cadena vacía. En el ejemplo de la figura 3.7, especificamos el nombre de un curso para un objeto LibroCalifica- ciones cuando se crea el objeto (línea 47). En este caso, el argumento CS101 Introduccion a la pro- gramacion en C++ se pasa al constructor del objeto LibroCalificaciones (líneas 14 a 18) y se utiliza para inicializar el nombreCurso. En la figura 3.7 se define una clase LibroCalificaciones modificada, la cual contiene un constructor con un parámetro string que recibe el nombre inicial del curso. 1 // Fig. 3.7: fig03_07.cpp 2 // Creación de instancias de varios objetos de la clase LibroCalificaciones y uso 3 // de su constructor para especificar el nombre del curso 4 // cuando se crea cada objeto LibroCalificaciones. 5 #include iostream 6 #include string // el programa usa la clase string estándar de C++ 7 using namespace std; 8 9 // definición de la clase LibroCalificaciones 10 class LibroCalificaciones 11 { 12 public: 13 // el constructor inicializa a nombreCurso con la cadena que se suministra como argumento 14 explicit LibroCalificaciones( string nombre ) 15 : nombreCurso ( nombre ) // inicializador de miembro para inicializar nombreCurso 16 { 17 // cuerpo vacío 18 } // fin del constructor de LibroCalificaciones 19 20 // función para establecer el nombre del curso 21 void establecerNombreCurso( string nombre ) 22 { 23 nombreCurso = nombre; // almacena el nombre del curso en el objeto 24 } // fin de la función establecerNombreCurso Fig. 3.7  Creación de instancias de varios objetos de la clase LibroCalificaciones y uso de su constructor para especificar el nombre del curso cuando se crea cada objeto LibroCalificaciones (parte 1 de 2).
  • 113. 3.5 Inicialización de objetos mediante constructores 81 25 26 // función para obtener el nombre del curso 27 string obtenerNombreCurso() const 28 { 29 return nombreCurso; // devuelve el nombreCurso del objeto 30 } // fin de la función obtenerNombreCurso 31 32 // muestra un mensaje de bienvenida para el usuario de LibroCalificaciones 33 void mostrarMensaje() const 34 { 35 // llama a obtenerNombreCurso para obtener el nombreCurso 36 cout Bienvenido al libro de calificaciones paran obtenerNombreCurso() 37 ! endl; 38 } // fin de la función mostrarMensaje 39 private: 40 string nombreCurso; // nombre del curso para este LibroCalificaciones 41 }; // fin de la clase LibroCalificaciones 42 43 // la función main empieza la ejecución del programa 44 int main() 45 { 46 // crea dos objetos LibroCalificaciones 47 LibroCalificaciones libroCalificaciones1( CS101 Introduccion a la programacion en C++ ); 48 LibroCalificaciones libroCalificaciones2( CS102 Estructuras de datos en C++ ); 49 50 // muestra el valor inicial de nombreCurso para cada LibroCalificaciones 51 cout libroCalificaciones1 se creo para el curso: libroCalificaciones1.obtenerNombreCurso() 52 nlibroCalificaciones2 se creo para el curso: libroCalificaciones2.obtenerNombreCurso() 53 endl; 54 } // fin de main libroCalificaciones1 se creo para el curso: CS101 Introduccion a la programacion en C++ libroCalificaciones2 se creo para el curso: CS102 Estructuras de datos en C++ Definición de un constructor En las líneas 14 a 18 de la figura 3.7 se define un constructor para la clase LibroCalificaciones. El constructor tiene el mismo nombre que su clase, LibroCalificaciones. Un constructor especifica en su lista de parámetros los datos que requiere para realizar su tarea. Al crear un nuevo objeto, el programador coloca estos datos en los paréntesis que van después del nombre del objeto (como hicimos en las líneas 47 y 48). En la línea 14 se indica que el constructor de la clase LibroCalificaciones tiene un parámetro string llamado nombre. Declaramos este constructor como explicit debido a que recibe un solo pará- metro: esto es importante por razones sutiles que conocerá en la sección 10.13. Por ahora, sólo declara todos los constructores de un solo parámetro como explicit. En la línea 14 no se especifica un tipo de valor de retorno, ya que los constructores no pueden devolver valores (ni siquiera void). Además, los constructores no pueden declararse como const (ya que al inicializar un objeto, éste se modifica). Fig. 3.7  Creación de instancias de varios objetos de la clase LibroCalificaciones y uso de su constructor para especificar el nombre del curso cuando se crea cada objeto LibroCalificaciones (parte 2 de 2).
  • 114. 82 Capítulo 3 Introducción a las clases, objetos y cadenas Elconstructorusaunalistadeinicializadoresdemiembros(línea15)parainicializareldatomiem- bro nombreCurso con el valor del parámetro nombre del constructor. Los inicializadores de miembros aparecen entre la lista de parámetros de un constructor y la llave izquierda que comienza el cuerpo del constructor. La lista de inicializadores de miembros se separa de la lista de parámetros con un signo de dos puntos (:). Un inicializador de miembro consiste en el nombre de la variable de un dato miembro seguido de paréntesis que contienen el valor inicial del miembro. En este ejemplo, nombreCurso se inicializa con el valor del parámetro nombre. Si una clase contiene más de un miembro de datos, el inicializador de cada miembro de datos se separa del siguiente con una coma. La lista de inicializadores de miembros se ejecu- ta antes de que se ejecute el cuerpo del constructor. Podemos realizar la inicialización en el cuerpo del constructor, pero más adelante aprenderá en el libro que es más eficiente hacerlo con inicializadores miembro; además, algunos tipos de datos miembros deben inicializarse de esta forma. Observe que tanto el constructor (línea 14) como la función establecerNombreCurso (línea 21) utilizan un parámetro llamado nombre. Puede usar los mismos nombres de parámetros en distintas fun- ciones, ya que los parámetros son locales para cada función; no interfieren unos con otros. Prueba de la clase LibroCalificaciones En las líneas 44 a 54 de la figura 3.7 se define la función main que prueba la clase LibroCalificaciones y demuestra cómo inicializar objetos LibroCalificaciones mediante el uso de un constructor. En la línea 47, se crea y se inicializa un objeto LibroCalificaciones llamado libroCalificaciones1. Cuando se ejecuta esta línea, se hace una llamada al constructor de LibroCalificaciones (líneas 14 a 18) con el argumento CS101 Introduccion a la programacion en C++ para inicializar el nombre del curso de libroCalificaciones1. En la línea 48 se repite este proceso para el objeto LibroCalifi- caciones llamado libroCalificaciones2, pero esta vez se pasa el argumento CS102 Estructuras de datos en C++ para inicializar el nombre del curso de libroCalificaciones2. En las líneas 51 y 52 se utiliza la función miembro obtenerNombreCurso de cada objeto para obtener los nombres de los cursos y mostrar que, sin duda, se inicializaron al momento de crear los objetos. La salida confirma que cada objeto LibroCalificaciones mantiene su propia copia del miembro de datos nombreCurso. Formas de proporcionar un constructor predeterminado para una clase Cualquier constructor que no recibe argumentos se llama constructor predeterminado. Una clase puede recibir un constructor predeterminado en una de varias formas: 1. El compilador crea de manera implícita un constructor predeterminado en cada clase que no tenga constructores definidos por el usuario. El constructor predeterminado no inicializa los datos miembros de la clase, pero llama al constructor predeterminado para cada dato miembro que sea un objeto de otra clase. Una variable sin inicializar contiene un valor indefinido (“ba- sura”). 2. Usted como programador define en forma explícita un constructor que no recibe argumentos. Dicho constructor llamará al constructor predeterminado para cada dato miembro que sea un objeto de otra clase y realizará la inicialización adicional especificada por usted. 3. Si define constructores con argumentos, C++ no creará de manera implícita un constructor prede- terminado para esa clase. Más tarde le mostraremos que C++11 le permite forzar al compilador a crear el constructor predeterminado, incluso aunque no haya definido constructores no predeterminados. Para cada versión de la clase LibroCalificaciones en las figuras 3.1, 3.3 y 3.5, el compilador definió de manera implícita un constructor predeterminado. Tip para prevenir errores 3.2 A menos que no sea necesario inicializar los datos miembros de su clase (casi nunca), debe proporcionar constructores para asegurar que los datos miembros de su clase se inicialicen con valores significativos al momento de crear cada nuevo objeto de su clase.
  • 115. 3.6 Colocar una clase en un archivo separado para fines de reutilización 83 Observación de Ingeniería de Software 3.2 Los datos miembros se pueden inicializar en un constructor, o sus valores pueden establecer- se más adelante, después de crear el objeto. Sin embargo, es una buena práctica de ingeniería de software asegurarse que un objeto esté inicializado por completo antes de que el código cliente invoque las funciones miembro de ese objeto. En general, no debemos depender del código cliente para asegurar que un objeto se inicialice de manera apropiada. Agregar el constructor al diagrama de clases de UML de la clase LibroCalificaciones El diagrama de clases de UML de la figura 3.8 modela la clase LibroCalificaciones de la figura 3.7, la cual tiene un constructor con un parámetro nombre de tipo string (representado por el tipo String en UML). Al igual que las operaciones, el UML modela a los constructores en el tercer compartimiento de una clase en un diagrama de clases. Para diferenciar a un constructor de las operaciones de la clase, UML coloca la palabra “constructor” entre los signos « y » antes del nombre del constructor. Es costumbre enlistar el constructor de la clase antes de todas las operaciones en el tercer compartimiento. LibroCalificaciones – nombreCurso : String «constructor» + LibroCalificaciones( nombre : String ) + establecerNombreCurso( nombre : String ) + obtenerNombreCurso( ) : String + mostrarMensaje( ) Fig. 3.8  Diagrama de clases de UML, el cual indica que la clase LibroCalificaciones tiene un constructor con un parámetro llamado nombre de tipo String de UML. 3.6Colocar una clase en un archivo separado para fines de reutilización Uno de los beneficios de crear definiciones de clases es que, cuando se empaquetan en forma apropia- da, nuestras clases pueden ser reutilizadas por otros programadores. Por ejemplo, podemos reutilizar el tipo string de la Biblioteca estándar de C++ en cualquier programa en C++ al incluir el encabeza- do string (y, como veremos, al poder enlazarnos con el código objeto de la biblioteca). LosprogramadoresquedeseenutilizarnuestraclaseLibroCalificaciones nopuedensimplemen- te incluir el archivo de la figura 3.7 en otro programa. Como aprendió en el capítulo 2, la función main empieza la ejecución de todo programa, y cada programa debe tener sólo y solamente una función main. Si otros programadores incluyen el código de la figura 3.7, recibirán “equipaje” adicional (nuestra fun- ción main) y sus programas tendrán entonces dos funciones main. Si intentamos compilar un programa con dos funciones main se producirá un error. Por lo tanto, al colocar main en el mismo archivo con una definición de clase, evitamos que esa clase pueda ser reutilizada por otros programas. En esta sección de- mostraremos cómo hacer la clase LibroCalificaciones reutilizable, al separarla de la función main y colocarla en otro archivo. Encabezados Cada uno de los ejemplos anteriores en el capítulo consiste de un solo archivo .cpp, al cual se le conoce también como archivo de código fuente, y contiene la definición de la clase LibroCalificaciones y una función main. Al construir un programa en C++ orientado a objetos, es costumbre definir el código fuente reutilizable (como una clase) en un archivo que, por convención, tiene la extensión .h; a éste se le conoce como encabezado. Los programas utilizan las directivas del preprocesador #include para incluir encabezados y aprovechar los componentes de software reutilizables, como el tipo string que se
  • 116. 84 Capítulo 3 Introducción a las clases, objetos y cadenas proporciona en la Biblioteca Estándar de C++, y los tipos definidos por el usuario como la clase Libro- Calificaciones. En nuestro siguiente ejemplo, separamos el código de la figura 3.7 en dos archivos: LibroCa- lificaciones.h (figura 3.9) y fig03_10.cpp (figura 3.10). Cuando analice el encabezado de la figura 3.9, observe que sólo contiene la definición de la clase LibroCalificaciones (líneas 7 a 38) y los enca- bezados de los que depende la clase. La función main que utiliza a la clase LibroCalificaciones se define en el archivo de código fuente fig03_10.cpp (figura 3.10), en las líneas 8 a 18. Para ayudarlo a prepararse para los programas más extensos que encontrará más adelante en este libro y en la industria, a menudo utilizamos un archivo de código fuente separado que contiene la función main para probar nuestras clases (a éste se le conoce como programa controlador). Pronto aprenderá cómo un archivo de código fuente con main puede utilizar la definición de una clase que se encuentra en un encabezado para crear objetos de esa clase. 1 // Fig. 3.9: LibroCalificaciones.h 2 // Definición de la clase LibroCalificaciones en un archivo separado de main. 3 #include iostream 4 #include string // la clase LibroCalificaciones utiliza la clase string estándar de C++ 5 6 // definición de la clase LibroCalificaciones 7 class LibroCalificaciones 8 { 9 public: 10 // el constructor inicializa nombreCurso con la cadena que se suministra como argumento 11 explicit LibroCalificaciones( std::string nombre ) 12 : nombreCurso( nombre ) // inicializador de miembro para inicializar nombreCurso 13 { 14 // cuerpo vacío 15 } // fin del constructor de LibroCalificaciones 16 17 // función para establecer el nombre del curso 18 void establecerNombreCurso( std::string nombre ) 19 { 20 nombreCurso = nombre; // almacena el nombre del curso en el objeto 21 } // fin de la función establecerNombreCurso 22 23 // función para obtener el nombre del curso 24 std::string obtenerNombreCurso() const 25 { 26 return nombreCurso; // devuelve el nombreCurso del objeto 27 } // fin de la función obtenerNombreCurso 28 29 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones 30 void mostrarMensaje() const 31 { 32 // llama a obtenerNombreCurso para obtener el nombreCurso 33 std::cout Bienvenido al libro de calificaciones paran obtenerNombreCurso() 34 ! std::endl; 35 } // fin de la función mostrarMensaje 36 private: 37 std::string nombreCurso; // nombre del curso para este LibroCalificaciones 38 }; // fin de la clase LibroCalificaciones Fig. 3.9  Definición de la clase LibroCalificaciones en un archivo separado de main.
  • 117. 3.6 Colocar una clase en un archivo separado para fines de reutilización 85 1 // Fig. 3.10: fig03_10.cpp 2 // Inclusión de la clase LibroCalificaciones del archivo LibroCalificaciones.h para usarla en main. 3 #include iostream 4 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 5 using namespace std; 6 7 // la función main empieza la ejecución del programa 8 int main() 9 { 10 // crea dos objetos LibroCalificaciones 11 LibroCalificaciones libroCalificaciones1( CS101 Introduccion a la programacion en C++ ); 12 LibroCalificaciones libroCalificaciones2( CS102 Estructuras de datos en C++ ); 13 14 // muestra el valor inicial de nombreCurso para cada LibroCalificaciones 15 cout libroCalificaciones1 creado para el curso: libroCalificaciones1.obtenerNombreCurso() 16 nlibroCalificaciones2 creado para el curso: libroCalificaciones2. obtenerNombreCurso() 17 endl; 18 } // fin de main libroCalificaciones1 creado para el curso: CS101 Introduccion a la programacion en C++ libroCalificaciones2 creado para el curso: CS102 Estructuras de datos en C++ Fig. 3.10  Inclusión de la clase LibroCalificaciones del archivo LibroCalificaciones.h para usarla en main. Usar std:: con los componentes de la biblioteca estándar en los encabezados En el encabezado (figura 3.9) utilizamos std:: al referirnos a un objeto string (líneas 11, 18, 24 y 37), cout (línea 33) y endl (línea 34). Por razones sutiles que explicaremos en un capítulo posterior, los en- cabezados nunca deben contener directivas using o declaraciones using (sección 2.7). Incluir un archivo de encabezado que contiene una clase definida por el usuario Un archivo de encabezado tal como LibroCalificaciones.h (figura 3.9) no puede usarse como un programa completo, ya que no contiene una función main. Para probar la clase LibroCalificaciones (definida en la figura 3.9), debe escribir un archivo de código fuente separado que contenga una función main (como la figura 3.10), la cual debe instanciar y utilizar objetos de la clase. El compilador no sabe qué es un LibroCalificaciones ya que es un tipo definido por el usuario. De hecho, el compilador ni siquiera conoce las clases de la Biblioteca estándar de C++. Para ayudarlo a comprender cómo usar una clase, debemos proporcionar en forma explícita al compilador la definición de la clase; ésta es la razón por la que, para que un programa pueda usar un tipo string, debe incluir el encabezadostring.Estopermitealcompiladordeterminarlacantidaddememoriaquedebereservar paracadaobjetodelaclase,yasegurarqueunprogramallamealasfuncionesmiembrodelaclasestring de una forma correcta. ParacrearlosobjetosLibroCalificaciones llamadoslibroCalificaciones1 ylibroCalificacio- nes2 en las líneas 11 y 12 de la figura 3.10, el compilador debe conocer el tamaño de un objeto LibroCali- ficaciones. Aunque en concepto los objetos contienen datos miembros y funciones miembro, los objetos de C++ sólo contienen datos. El compilador sólo crea una copia de las funciones miembro de la clase y comparteesacopiaentretodoslosobjetosdelaclase.Desdeluegoquecadaobjetonecesitasuspropiosdatos
  • 118. 86 Capítulo 3 Introducción a las clases, objetos y cadenas miembros, ya que su contenido puede variar de un objeto a otro (como dos objetos CuentaBanco distintos, que tienen dos saldos distintos). Sin embargo, el código de la función miembro no se puede modificar, por lo que puede compartirse entre todos los objetos de la clase. Por lo tanto, el tamaño de un objeto depende de la cantidad de memoria requerida para almacenar los datos miembros de la clase. Al incluir a LibroCalificaciones.h en la línea 4, proporcionamos acceso al compilador para que utilice la informa- ción que necesita (figura 3.9, línea 37) para determinar el tamaño de un objeto LibroCalificaciones y determinarsilosobjetosdelaclaseseutilizancorrectamente(enlaslíneas11a12y15a16delafigura3.10). En la línea 4 se indica al preprocesador de C++ que reemplace la directiva con una copia del contenido de LibroCalificaciones.h (es decir, la definición de la clase LibroCalificaciones) antes de compilar el programa. Cuando se compila el archivo de código fuente fig03_10.cpp, ahora contiene la definición de la clase LibroCalificaciones (debido a la instrucción #include), y el compilador es capaz de crear objetos LibroCalificaciones y revisar que se hagan llamadas a sus funciones miembro en forma adecua- da. Ahora que la definición de la clase está en un encabezado (sin una función main), podemos incluir ese encabezado en cualquier programa que necesite reutilizar nuestra clase LibroCalificaciones. Cómo se localizan los encabezados Observe que el nombre del encabezado LibroCalificaciones.h en la línea 4 de la figura 3.10 se encie- rra entre comillas ( ) en vez de usar los signos y . Por lo general, los archivos de código fuente de un programa y los encabezados definidos por el usuario se colocan en el mismo directorio. Cuando el prepro- cesador encuentra el nombre de un encabezado entre comillas, intenta localizar el encabezado en el mismo directorio que el archivo en el que aparece la directiva #include. Si el preprocesador no puede encontrar el encabezado en ese directorio, lo busca en la(s) misma(s) ubicación(es) que los encabezados de la Biblioteca Estándar de C++. Cuando el preprocesador encuentra el nombre de un encabezado entre los signos y (como iostream), asume que el encabezado forma parte de la Biblioteca Estándar de C++ y no busca en el directorio del programa que se está procesando. Tip para prevenir errores 3.3 Para asegurar que el preprocesador pueda localizar los encabezados en forma correcta, en las directivas del preprocesador #include se deben colocar los nombres de los encabezados definidos por el usuario entre comillas (como LibroCalificaciones.h), y se deben colocar los nombres de los archivos de encabezado de la Biblioteca Estándar de C++ entre los signos y (como iostream). Cuestiones adicionales sobre Ingeniería de Software AhoraquelaclaseLibroCalificaciones estádefinidaenunarchivodeencabezado,puedereutilizarse.Por desgracia, al colocar la definición de una clase en un archivo de encabezado como en la figura 3.9, se sigue revelandotodalaimplementacióndelaclasealosclientesdelamisma;LibroCalificaciones.h essimplemen- teunarchivodetextoquecualquierapuedeabriryleer.LasabiduríadelaIngenieríadesoftwareconvencio- nal nos dice que para usar un objeto de la clase, el código cliente necesita saber sólo qué funciones miembro debellamar,quéargumentosdebeproporcionaracadafunciónmiembroyquétipodevalorderetornodebe esperar de cada función miembro. El código cliente no necesita saber cómo se implementan esas funciones. Si el código cliente sabe cómo se implementa una clase, el programador podría escribir código cliente basado en los detalles de implementación de la clase. Lo ideal sería que, si cambia la implemen- tación, los clientes de la clase no tengan que cambiar. Al ocultar los detalles de implementación de la clase, facilitamos la tarea de cambiar la implementación de la clase al mismo tiempo que minimizamos (y con suerte, eliminamos) los cambios al código cliente. En la sección 3.7 le mostraremos cómo descomponer la clase LibroCalificaciones en dos archi- vos, de manera que: 1. la clase sea reutilizable, 2. los clientes de la clase sepan qué funciones miembro proporciona la clase, cómo llamarlas y qué tipo de valores de retorno esperar, y 3. los clientes no sepan cómo se implementan las funciones miembro de la clase.
  • 119. 3.7 Separar la interfaz de la implementación 87 3.7Separar la interfaz de la implementación En la sección anterior le mostramos cómo fomentar la reutilización de software al separar la definición de la clase del código cliente (por ejemplo, la función main) que utiliza esa clase. Ahora presentaremos otro principio fundamental de la buena Ingeniería de software: separar la interfaz de la implementación. La interfaz de una clase Las interfaces definen y estandarizan las formas en las que las personas y los sistemas interactúan entre sí. Por ejemplo, los controles de un radio sirven como una interfaz entre los usuarios del radio y sus componentes internos. Los controles permiten a los usuarios realizar un conjunto limitado de opera- ciones (como cambiar la estación, ajustar el volumen y elegir entre una estación en AM o una en FM). Varias radios pueden implementar estas operaciones de manera distinta; algunos proporcionan boto- nes, otros perillas y algunas incluso soportan comandos de voz. La interfaz especifica qué operaciones permite realizar un radio a los usuarios, pero no especifica cómo se implementan estas operaciones en su interior. De manera similar, la interfaz de una clase describe qué servicios pueden usar los clientes de la clase y cómo solicitar esos servicios, pero no cómo lleva a cabo la clase esos servicios. La interfaz public de una clase consiste en las funciones miembro public de la clase (también conocidas como servicios públicos). Por ejemplo, la interfaz de la clase LibroCalificaciones (figura 3.9) contiene un construc- tor y las funciones miembro establecerNombreCurso, obtenerNombreCurso y mostrarMensaje. Los clientes de LibroCalificaciones (por ejemplo, main en la figura 3.10) utilizan estas funciones para solicitar los servicios de la clase. Como pronto veremos, podemos especificar la interfaz de una clase al escribir una definición de clase que sólo enliste los nombres de las funciones miembro, los tipos de los valores de retorno y los tipos de los parámetros. Separar la interfaz de la implementación En nuestros ejemplos anteriores, la definición de cada clase contenía las definiciones completas de las funciones miembro public de la clase y las declaraciones de sus datos miembros private. Sin embargo, una mejor ingeniería de software es definir las funciones miembro fuera de la definición de la clase, de manera que sus detalles de implementación se puedan ocultar del código cliente. Esta práctica asegura que los programadores no escriban código cliente que dependa de los detalles de implementación de la clase. El programa de las figuras 3.11 a 3.13 separa la interfaz de LibroCalificaciones de su implemen- tación, para lo cual divide la definición de la clase de la figura 3.9 en dos archivos: el encabeza- do LibroCalificaciones.h (figura 3.11) en el que se define la clase LibroCalificaciones, y el archivo de código fuente LibroCalificaciones.cpp (figura 3.12) en el que se definen las funciones miembro de LibroCalificaciones. Por convención, las definiciones de las funciones miembro se colocan en un archivo de código fuente con el mismo nombre base (por ejemplo, LibroCalificaciones) que el enca- bezado de la clase, pero con una extensión de archivo .cpp. El archivo de código fuente fig03_13.cpp (figura 3.13) define la función main (el código cliente). El código y la salida de la figura 3.13 son idén- ticos a los de la figura 3.10. En la figura 3.14 se muestra cómo se compila este programa de tres archivos, desde las perspectivas del programador de la clase LibroCalificaciones y del programador del código cliente; explicaremos esta figura con detalle. LibroCalificaciones.h: definición de la interfaz de una clase mediante prototipos de funciones El archivo de encabezado LibroCalificaciones.h (figura 3.11) contiene otra versión de la definición de la clase LibroCalificaciones (líneas 8 a 17). Esta versión es similar a la de la figura 3.9, pero las definiciones de las funciones en la figura 3.9 se reemplazan aquí con prototipos de funciones (líneas 11 a 14) que describen la interfaz public de la clase sin revelar las implementaciones de sus funciones miem- bro. Un prototipo de función es una declaración de una función que indica al compilador el nombre de
  • 120. 88 Capítulo 3 Introducción a las clases, objetos y cadenas la función, su tipo de valor de retorno y los tipos de sus parámetros. Además, el encabezado sigue espe- cificando el dato miembro private de la clase (línea 16) también. De nuevo, el compilador debe cono- cer los datos miembros de la clase para determinar cuánta memoria debe reservar para cada objeto de la misma. Al incluir el encabezado LibroCalificaciones.h en el código cliente (línea 5 de la figura 3.13), el compilador obtiene la información que necesita para asegurar que el código cliente llame a las funcio- nes miembro de la clase LibroCalificaciones en forma correcta. 1 // Fig. 3.11: LibroCalificaciones.h 2 // Definición de la clase LibroCalificaciones. Este archivo presenta la interfaz 3 // public de LibroCalificaciones sin revelar las implementaciones de sus funciones 4 // miembro, que están definidas en LibroCalificaciones.cpp. 5 #include string // la clase LibroCalificaciones utiliza la clase string estándar de C++ 6 7 // definición de la clase LibroCalificaciones 8 class LibroCalificaciones 9 { 10 public: 11 explicit LibroCalificaciones( std::string ); // constructor que inicializa a nombreCurso 12 void establecerNombreCurso( std::string ); // establece el nombre del curso 13 std::obtenerNombreCurso() const; // obtiene el nombre del curso 14 void mostrarMensaje() const; // muestra un mensaje de bienvenida 15 private: 16 std::string nombreCurso; // nombre del curso para este LibroCalificaciones 17 }; // fin de la clase LibroCalificaciones Fig. 3.11  Definición de la clase LibroCalificaciones que contiene prototipos de funciones que especifican la interfaz de la clase. El prototipo de función en la línea 11 (figura 3.11) indica que el constructor requiere un parámetro string. Recuerde que los constructores no tienen tipos de valores de retorno, por lo que no aparece ningún tipo de valor de retorno en el prototipo de la función. El prototipo de la función miembro establecerNombreCurso indica que requiere un parámetro string y no devuelve un valor (es decir, su tipo de valor de retorno es void). El prototipo de la función miembro obtenerNombreCurso indica que la función no requiere parámetros y devuelve un string. Por último, el prototipo de la función mostrarMensaje (línea 14) especifica que mostrarMensaje no requiere parámetros y no devuelve un valor. Estos prototipos de funciones son iguales que las primeras líneas de las correspondientes defini- ciones de funciones en la figura 3.9, sólo que los nombres de los parámetros (que son opcionales en los prototipos) no se incluyen y cada prototipo de función debe terminar con un punto y coma. Buena práctica de programación 3.2 Aunque los nombres de los parámetros en los prototipos de funciones son opcionales (el compilador los ignora), muchos programadores utilizan estos nombres para fines de docu- mentación. LibroCalificaciones.cpp: definir las funciones miembro en un archivo de código fuente separado El archivo de código fuente LibroCalificaciones.cpp (figura 3.12) define las funciones miembro de la clase LibroCalificaciones, que se declararon en las líneas 11 a 14 de la figura 3.11. Las definiciones aparecen en las líneas 9 a 33 y son casi idénticas a las definiciones de las funciones miembro en las líneas 11 a 35 de la figura 3.9. Cabe mencionar que la palabra clave const debe aparecer tanto en los prototipos
  • 121. 3.7 Separar la interfaz de la implementación 89 de función (figura 3.11, líneas 13 y 14) y las definiciones de las funciones obtenerNombreCurso y mos- trarMensaje (líneas 22 y 28). 1 // Fig. 3.12: LibroCalificaciones.cpp 2 // Definiciones de las funciones miembro de LibroCalificaciones. Este archivo contiene 3 // implementaciones de las funciones miembro cuyo prototipo está en LibroCalificaciones.h. 4 #include iostream 5 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 6 using namespace std; 7 8 // el constructor inicializa nombreCurso con el objeto string suministrado como argumento 9 LibroCalificaciones::LibroCalificaciones( string nombre ) 10 : nombreCurso( nombre ) // inicializador de miembro para inicializar nombreCurso 11 { 12 // cuerpo vacío 13 } // fin del constructor de LibroCalificaciones 14 15 // función para establecer el nombre del curso 16 void LibroCalificaciones::establecerNombreCurso( string nombre ) 17 { 18 nombreCurso = nombre; // almacena el nombre del curso en el objeto 19 } // fin de la función establecerNombreCurso 20 21 // función para obtener el nombre del curso 22 string LibroCalificaciones::obtenerNombreCurso() const 23 { 24 return nombreCurso; // devuelve el nombreCurso del objeto 25 } // fin de la función obtenerNombreCurso 26 27 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones 28 void LibroCalificaciones::mostrarMensaje() const 29 { 30 // llama a obtenerNombreCurso para obtener el nombreCurso 31 cout Bienvenido al libro de calificaciones paran obtenerNombreCurso() 32 ! endl; 33 } // fin de la función mostrarMensaje Fig. 3.12  Las definiciones de las funciones miembro de LibroCalificaciones representan la implementación de la clase LibroCalificaciones. Al nombre de cada función miembro (líneas 9, 16, 22 y 28) se le antepone el nombre de la clase y el símbolo ::, que se conoce como el operador de resolución de ámbito. Esto “enlaza” a cada función miembro con la definición (ahora separada) de la clase LibroCalificaciones (figura 3.11), la cual declara las funciones miembro y los datos miembros. Sin “LibroCalificaciones::” antes de cada nombre de función, el compilador no las reconocería como funciones miembro de la clase LibroCali- ficaciones; las consideraría como funciones “libres” o “sueltas”, al igual que main. A éstas también se les conoce como funciones globales. Dichas funciones no pueden acceder a los datos private de Libro- Calificaciones ni llamar a las funciones miembro de la clase, sin especificar un objeto. Por lo tanto, el compilador no podría compilar estas funciones. Por ejemplo, en las líneas 18 y 24 en la figura 3.12 que acceden a la variable nombreCurso se producirían errores de compilación, ya que nombreCurso no está declarada como una variable local en cada función; el compilador no sabría que nombreCurso ya está de- clarada como dato miembro de la clase LibroCalificaciones.
  • 122. 90 Capítulo 3 Introducción a las clases, objetos y cadenas Error común de programación 3.3 Al definir las funciones miembro de una clase fuera de la misma, si se omite el nombre de la clase y el operador de resolución de ámbito (::) antes de los nombres de las funciones se producen errores de compilación. Para indicar que las funciones miembro en LibroCalificaciones.cpp forman parte de la clase LibroCalificaciones, debemos primero incluir el archivo de encabezado LibroCalificaciones.h (línea 5 de la figura 3.12), Esto nos permite acceder al nombre de la clase LibroCalificaciones en el archivo LibroCalificaciones.cpp. Al compilar LibroCalificaciones.cpp, el compilador utiliza la información en LibroCalificaciones.h para asegurar que 1. la primera línea de cada función miembro (líneas 9, 16, 22 y 28) coincida con su prototipo en el archivo LibroCalificaciones.h; por ejemplo, el compilador asegura que obtenerNombre- Curso no acepte parámetros y devuelva un valor string, y que 2. cada función miembro sepa acerca de los datos miembros y otras funciones miembro de la clase; por ejemplo, en las líneas 18 y 24 se puede acceder a la variable nombreCurso, ya que está declaradaenLibroCalificaciones.h comodatomiembrodelaclaseLibroCalificaciones, y en la línea 31 se puede llamar a la función obtenerNombreCurso, ya que se declara como función miembro de la clase en LibroCalificaciones.h (y debido a que la llamada se confor- ma con el prototipo correspondiente). Prueba de la clase LibroCalificaciones En la figura 3.13 se realizan las mismas manipulaciones de objetos LibroCalificaciones que en la fi- gura3.10.AlsepararlainterfazdeLibroCalificaciones delaimplementacióndesusfuncionesmiem- bro, no se ve afectada la forma en que este código cliente utiliza la clase. Sólo afecta la forma en que se compila y enlaza el programa, lo cual veremos en breve. 1 // Fig. 3.13: fig03_13.cpp 2 // Demostración de la clase LibroCalificaciones después de separar 3 // su interfaz de su implementación. 4 #include iostream 5 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones GradeBook 6 using namespace std; 7 8 // la función main empieza la ejecución del programa 9 int main() 10 { 11 // crea dos objetos LibroCalificaciones 12 LibroCalificaciones libroCalificaciones1( CS101 Introduccion a la programacion en C++ ); 13 LibroCalificaciones libroCalificaciones2( CS102 Estructuras de datos en C++ ); 14 15 // muestra el valor inicial de nombreCurso para cada LibroCalificaciones 16 cout libroCalificaciones1 creado para el curso: libroCalificaciones1.obtenerNombreCurso() 17 nlibroCalificaciones2 creado para el curso: libroCalificaciones2.obtenerNombreCurso() 18 endl; 19 } // fin de main Fig. 3.13  Demostración de la clase LibroCalificaciones después de separar la interfaz de su implementación (parte 1 de 2).
  • 123. 3.7 Separar la interfaz de la implementación 91 libroCalificaciones1 creado para el curso: CS101 Introduccion a la programacion en C++ libroCalificaciones2 creado para el curso: CS102 Estructuras de datos en C++ Al igual que en la figura 3.10, en la línea 5 de la figura 3.13 se incluye el archivo de encabezado LibroCalificaciones.h, de manera que el compilador pueda asegurar que los objetos LibroCalifica- ciones se creen y manipulen correctamente en el código cliente. Antes de ejecutar este programa, deben compilarse los archivos de código fuente de las figuras 3.12 y 3.13, y después se deben enlazar; es decir, las llamadas a las funciones miembro en el código cliente necesitan enlazarse con las implementaciones de las funciones miembro de la clase; un trabajo que realiza el enlazador. El proceso de compilación y enlace El diagrama de la figura 3.14 muestra el proceso de compilación y enlace que produce una aplicación LibroCalificaciones ejecutable, que los instructores pueden utilizar. Lo común es que un programa- dor cree y compile la interfaz e implementación de una clase, y que otro programador implemente el código cliente para utilizar esa clase. Así, el diagrama muestra lo que requieren tanto el programador de la implementación de la clase, como el programador del código cliente. Las líneas punteadas en el dia- grama muestran las piezas requeridas por el programador de la implementación de la clase, el progra- mador del código cliente y el usuario de la aplicación LibroCalificaciones, respectivamente. [Nota: la figura 3.14 no es un diagrama de UML]. El programador de la implementación de una clase, responsable de crear una clase LibroCalificacio- nes reutilizable,creaelencabezadoLibroCalificaciones.h yelarchivodecódigofuenteLibroCalifica- ciones.cpp que incluye (mediante #include) el encabezado, y después compila el archivo de código fuente para crear el código objeto de LibroCalificaciones. Para ocultar los detalles de la implementación de las funcionesmiembrodeLibroCalificaciones,elprogramadordelaimplementacióndelaclaseproporciona al programador del código cliente el encabezado LibroCalificaciones.h (que especifica la interfaz y los datos miembros de la clase) y el código objeto de LibroCalificaciones (que contiene las instrucciones en lenguajemáquina que representan a lasfuncionesmiembrode LibroCalificaciones). El programador del códigoclientenorecibeLibroCalificaciones.cpp,porloquedesconocecómoseimplementanlasfuncio- nes miembro de LibroCalificaciones. El programador del código cliente sólo necesita conocer la interfaz de LibroCalificaciones para usar la clase, y debe tener la capacidad de enlazar su código objeto. Como la interfaz de la clase es parte de su definición en el encabezado LibroCalificaciones.h, el programador del código cliente debe tener acceso a este archivo e incluirlo (mediante #include) en el archivo de código fuente del cliente. Cuando se compila el código cliente, el compilador usa la definición de la clase en LibroCalificaciones.h para asegurar que la función main cree y manipule objetos de la clase LibroCalificaciones correctamente. Para crear la aplicación LibroCalificaciones ejecutable, el último paso es enlazar 1. el código objeto para la función main (es decir, el código cliente), 2. el código objeto para las implementaciones de las funciones miembro de la clase LibroCali- ficaciones y 3. el código objeto de la Biblioteca estándar de C++ para las clases de C++ (como string) que utilicen tanto el programador de la implementación de la clase, como el programador del có- digo cliente. La salida del enlazador es la aplicación LibroCalificaciones ejecutable, que los instructores pueden utilizar para administrar las calificaciones de sus estudiantes. Por lo general los compiladores y los IDE invocan al enlazador por el programador después de compilar su código. Fig. 3.13  Demostración de la clase LibroCalificaciones después de separar la interfaz de su implementación (parte 2 de 2).
  • 124. 92 Capítulo 3 Introducción a las clases, objetos y cadenas Usuario de la aplicación LibroCalificaciones Programador de la implementación de la clase Programador del código cliente aplicación ejecutable LibroCalificaciones definición/interfaz de la clase LibroCalificaciones.h función main (código fuente cliente) código objeto de la clase LibroCalificaciones código objeto de la función main compilador compilador enlazador Archivo de implementación de LibroCalificaciones.cpp código objeto de la Biblioteca estándar de C++ Fig. 3.14  Proceso de compilación y enlace que produce una aplicación ejecutable. Para obtener más información acerca de cómo compilar programas con varios archivos de có- digo fuente, consulte la documentación de su compilador. En nuestro Centro de recursos de C++ en www.deitel.com/cplusplus/ proporcionamos vínculos a varios compiladores de C++. 3.8Validación de datos mediante funciones establecer En la sección 3.4 presentamos funciones establecer para permitir a los clientes de una clase modificar el valor de un dato miembro private. En la figura 3.5, la clase LibroCalificaciones define la función miembro establecerNombreCurso tan sólo para asignar un valor recibido en su parámetro nombre al dato miembro nombreCurso. Esta función miembro no asegura que el nombre del curso se adhiera a algún formato específico, o que siga cualquier otra regla en relación con la apariencia que debe tener un nombre de curso “válido”. Suponga que una universidad puede imprimir certificados de los estudiantes que contengan nombres de cursos con 25 caracteres o menos. Si la universidad utiliza un sistema que
  • 125. 3.8 Validación de datos mediante funciones establecer 93 contenga objetos LibroCalificaciones para generar los certificados, tal vez sea conveniente que la clase LibroCalificaciones asegure que su miembro de datos nombreCurso nunca contenga más de 25 caracteres. El programa de las figuras 3.15 a 3.17 mejora la función miembro establecerNombreCurso de la clase LibroCalificaciones para realizar esta validación (lo que también se conoce como verifi- cación de validez). Definición de la clase LibroCalificaciones La definición de la clase LibroCalificaciones (figura 3.15) y por ende, su interfaz es idéntica a la de la figura 3.11. Como la interfaz permanece sin cambios, los clientes de esta clase no necesitan modi- ficarse a la hora que se modifica la definición de la función establecerNombreCurso. Esto permite a los clientes aprovechar la clase LibroCalificaciones mejorada, con sólo enlazar el código cliente con el código objeto actualizado de LibroCalificaciones. 1 // Fig. 3.15: LibroCalificaciones.h 2 // Definición de la clase LibroCalificaciones presenta la interfaz public 3 // de la clase. Las definiciones de las funciones miembro aparecen en LibroCalificaciones.cpp. 4 #include string // el programa usa la clase string estándar de C++ 5 6 // Definición de la clase LibroCalificaciones 7 class LibroCalificaciones 8 { 9 public: 10 explicit LibroCalificaciones( std::string ); // constructor que inicializa nombreCurso 11 void establecerNombreCurso( std::string ); // establece el nombre del curso 12 std::string obtenerNombreCurso() const; // obtiene el nombre del curso 13 void mostrarMensaje() const; // muestra un mensaje de bienvenida 14 private: 15 std::string nombreCurso; // nombre del curso para este LibroCalificaciones 16 }; // fin de la clase LibroCalificaciones Fig. 3.15  La definición de la clase LibroCalificaciones presenta la interfaz public de la clase. Validar el nombre del curso con la función miembro establecerNombreCurso de LibroCalificaciones Las modificaciones a la clase LibroCalificaciones están en las definiciones del constructor (figura 3.16, líneas 9 a 12) y en establecerNombreCurso (líneas 16 a 29). En vez de usar un inicializador de miembro, el constructor ahora llama a establecerNombreCurso. En general, todos los datos miembros deberían inicializarse con inicializadores de miembros. Sin embargo, algunas veces un constructor debe también validar su(s) argumento(s); a menudo esto se maneja en el cuerpo del constructor (línea 11). La llamada a establecerNombreCurso valida el argumento del constructor y establece el dato miembro nombreCurso. En un principio, el valor de nombreCurso se establecerá en la cadena vacía antes de que se ejecute el cuerpo del constructor, y luego establecerNombreCurso modificará el valor de nombreCurso. En establecerNombreCurso, la instrucción if en las líneas 18 y 19 determina si el parámetro nombre contiene un nombre de curso válido (es decir, un string de 25 caracteres o menos). Si el nom- bre del curso es válido, en la línea 19 se almacena el nombre del curso en el miembro de datos nombre- Curso. Observe la expresión nombre.size() en la línea 18. Ésta es una llamada a la función miembro, justo igual que miLibroCalificaciones.mostrarMensaje(). La clase string de la biblioteca estándar
  • 126. 94 Capítulo 3 Introducción a las clases, objetos y cadenas de C++ define a una función miembro size que devuelve el número de caracteres en un objeto string. El parámetro nombre es un objeto string, por lo que la llamada a nombre.size() devuelve el número de caracteres en nombre. Si este valor es menor o igual que 25, nombre es válido y se ejecuta la línea 19. 1 // Fig. 3.16: LibroCalificaciones.cpp 2 // Implementaciones de las definiciones de las funciones miembro de LibroCalificaciones. 3 // La función establecerNombreCurso realiza la validación. 4 #include iostream 5 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 6 using namespace std; 7 8 // el constructor inicializa nombreCurso con la cadena que se suministra como argumento 9 LibroCalificaciones::LibroCalificaciones( string nombre ) 10 { 11 establecerNombreCurso( nombre ); // valida y almacena nombreCurso 12 } // fin del constructor de LibroCalificaciones 13 14 // función que establece el nombre del curso; 15 // asegura que el nombre del curso tenga como máximo 25 caracteres 16 void LibroCalificaciones::establecerNombreCurso( string nombre ) 17 { 18 if ( nombre.size() = 25 ) // si nombre tiene 25 caracteres o menos 19 nombreCurso = nombre; // almacena el nombre del curso en el objeto 20 21 if ( nombre.size() 25 ) // si nombre tiene más de 25 caracteres 22 { 23 // establece nombreCurso a los primeros 25 caracteres del parámetro nombre 24 nombreCurso = nombre.substr( 0, 25 ); // empieza en 0, longitud de 25 25 26 cerr El nombre nombre excede la longitud maxima (25).n 27 Se limito nombreCurso a los primeros 25 caracteres.n endl; 28 } // fin de if 29 } // fin de la función establecerNombreCurso 30 31 // función para obtener el nombre del curso 32 string LibroCalificaciones::obtenerNombreCurso() const 33 { 34 return nombreCurso; // devuelve el nombreCurso del objeto 35 } // fin de la función obtenerNombreCurso 36 37 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones 38 void LibroCalificaciones::mostrarMensaje() const 39 { 40 // llama a obtenerNombreCurso para obtener el nombreCurso 41 cout Bienvenido al libro de calificaciones paran obtenerNombreCurso() 42 ! endl; 43 } // fin de la función mostrarMensaje Fig. 3.16  Definiciones de las funciones miembro para la clase LibroCalificaciones, con una función establecer que valida la longitud del dato miembro nombreCurso. La instrucción if en las líneas 21 a 28 se encarga del caso en el que establecerNombreCurso recibe un nombre de curso inválido (es decir, un nombre que tenga más de 25 caracteres). Aun si el parámetro nombre es demasiado largo, de todas formas queremos que el objeto LibroCalificaciones quede en un estado consistente; es decir, un estado en el que el dato miembro nombreCurso del objeto contenga
  • 127. 3.8 Validación de datos mediante funciones establecer 95 un valor válido (un string de 25 caracteres o menos). Por ende, truncamos (reducimos) el nombre del curso especificado y asignamos los primeros 25 caracteres de nombre al dato miembro nombreCurso (por desgracia, esto podría truncar el nombre del curso de una manera extraña). La clase string estándar proporciona la función miembro substr (“substring”, o subcadena), la cual devuelve un nuevo objeto string que se crea al copiar parte de un objeto string existente. La llamada en la línea 24 (es decir, nombre.substr(0,25)) pasa dos enteros (0 y 25) a la función miembro substr de nombre. Estos argu- mentos indican la porción de la cadena nombre que substr debe devolver. El primer argumento especi- fica la posición inicial en el objeto string original desde el que se van a copiar los caracteres; en todas las cadenas se considera que el primer carácter se encuentra en la posición 0. El segundo argumento espe- cifica el número de caracteres a copiar. Por lo tanto, la llamada en la línea 24 devuelve una subcadena de 25 caracteres de nombre, empezando en la posición 0 (es decir, los primeros 25 caracteres en nombre). Por ejemplo, si nombre contiene el valor CS101 Introduccion a la programacion en C++, substr devuelve CS101 Introduccion a la p. Después de la llamada a substr, en la línea 24 se asigna la subcadena devuelta por substr al dato miembro nombreCurso. De esta forma, establecerNombre- Curso asegura que a nombreCurso siempre se le asigne una cadena que contenga 25 caracteres o menos. Si la función miembro tiene que truncar el nombre del curso para hacerlo válido, en las líneas 26 y 27 se muestra un mensaje de advertencia mediante el uso de cerr, como se menciona en el capítulo 1. La instrucción if en las líneas 21 a 28 contiene dos instrucciones en su cuerpo: una para establecer el nombreCurso a los primeros 25 caracteres del parámetro nombre y una para imprimir un mensaje complementario al usuario. Ambas instrucciones deben ejecutarse cuando nombre sea demasiado largo, por lo que las colocaremos en un par de llaves, { }. En el capítulo 2 vimos que esto crea un bloque. En el capítulo 4 aprenderá más acerca de cómo colocar varias instrucciones en el cuerpo de una instrucción de control. La instrucción en las líneas 26 y 27 también podría aparecer sin un operador de inserción de flujo al principio de la segunda línea de la instrucción, como en cerr El nombre nombre excede la longitud maxima (25).n Se limito nombreCurso a los primeros 25 caracteres.n endl; El compilador de C++ combina las literales de cadena adyacentes, aun si aparecen en líneas separadas de un programa. Por ende, en la instrucción anterior, el compilador de C++ combinaría las literales de cadena excede la longitud maxima (25).n y Se limito nombreCurso a los primeros 25 caracteres.n en una sola literal de cadena que produzca una salida idéntica a la de las líneas 26 y 27 de la figura 3.16. Este comportamiento nos permite imprimir cadenas extensas, al descomponerlas en varias líneas en nuestro programa, sin necesidad de incluir operaciones de inserción de flujo adicionales. Prueba de la clase LibroCalificaciones En la figura 3.17 se demuestra la versión modificada de la clase LibroCalificaciones (figuras 3.15 a 3.16) que incluye la validación. En la línea 12 se crea un objeto LibroCalificaciones llamado libro- Calificaciones1. Recuerde que el constructor de LibroCalificaciones llama a establecerNombre- Curso para inicializar el dato miembro nombreCurso. En versiones anteriores de la clase, el beneficio de llamar a establecerNombreCurso en el constructor no era evidente. Sin embargo, ahora el constructor aprovecha la validación que proporciona establecerNombreCurso. El constructor simplemente llama a establecerNombreCurso, en vez de duplicar su código de validación. Cuando en la línea 12 de la figura 3.17 se pasa un nombre inicial de CS101 Introduccion a la programacion en C++ al constructor de LibroCalificaciones, el constructor pasa este valor a establecerNombreCurso, en donde ocurre la inicialización en sí. Como este nombre contiene más de 25 caracteres, se ejecuta el cuerpo de la segun- da instrucción if, lo cual hace que nombreCurso se inicialice con el nombre truncado del curso de 25 caracteres de CS101 Introduccion a la p [la parte truncada se resalta con rojo (en su pantalla) en la línea 12]. La salida en la figura 3.17 contiene el mensaje de advertencia que producen las líneas 26 y 27
  • 128. 96 Capítulo 3 Introducción a las clases, objetos y cadenas de la figura 3.16 en la función miembro establecerNombreCurso. En la línea 13 se crea otro objeto LibroCalificaciones llamado libroCalificaciones2; el nombre válido del curso que se pasa al constructor es exactamente de 25 caracteres. 1 // Fig. 3.17: fig03_17.cpp 2 // Crea y manipula un objeto LibroCalificaciones; ilustra la validación. 3 #include iostream 4 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 5 using namespace std; 6 7 // la función main empieza la ejecución del programa 8 int main() 9 { 10 // crea dos objetos LibroCalificaciones; 11 // el nombre inicial del curso de libroCalificaciones1 es demasiado largo 12 LibroCalificaciones libroCalificaciones1( CS101 Introduccion a la programacion en C++ ); 13 LibroCalificaciones libroCalificaciones2( CS102 C++:Estruc de datos ); 14 15 // muestra el nombreCurso de cada LibroCalificaciones 16 cout el nombre inicial del curso de libroCalificaciones1 es: 17 libroCalificaciones1.obtenerNombreCurso() 18 nel nombre inicial del curso de libroCalificaciones2 es: 19 libroCalificaciones2.obtenerNombreCurso() endl; 20 21 // modifica el nombreCurso de libroCalificaciones1 (con una cadena con longitud válida) 22 libroCalificaciones1.establecerNombreCurso( CS101 Programacion en C++ 23 24 // muestra el nombreCurso de cada LibroCalificaciones 25 cout nel nombre del curso de libroCalificaciones1 es: 26 libroCalificaciones1.obtenerNombreCurso() 27 nel nombre del curso de libroCalificaciones2 es: 28 libroCalificaciones2.obtenerNombreCurso() endl; 29 } // fin de main El nombre CS101 Introduccion a la programacion en C++ excede la longitud maxima (25). Se limito nombreCurso a los primeros 25 caracteres. el nombre inicial del curso de libroCalificaciones1 es: CS101 Introduccion a la p el nombre inicial del curso de libroCalificaciones2 es: CS102 C++:Estruc de datos el nombre del curso de libroCalificaciones1 es: CS101 Programacion en C++ el nombre del curso de libroCalificaciones2 es: CS102 C++:Estruc de datos Fig. 3.17  Creación y manipulación de un objeto LibroCalificaciones en el que el nombre del curso está limitado a una longitud de 25 caracteres. En las líneas 16 a 19 de la figura 3.17 se muestra el nombre del curso truncado para libroCalifica- ciones1 (se resalta esto en color azul en la salida de su pantalla) y el nombre del curso para libroCali- ficaciones2. En la línea 22 se hace una llamada a la función miembro establecerNombreCurso de libroCalificaciones1 directamente, para modificar el nombre del curso en el objeto LibroCalifi- caciones a un nombre más corto, que no necesite truncarse. Después, en las líneas 25 a 28 se imprimen los nombres de los cursos para los objetos LibroCalificaciones de nuevo.
  • 129. 3.9 Conclusión 97 Observaciones adicionales acerca de las funciones establecer Una función establecer public tal como establecerNombreCurso debe escudriñar cuidadosamente cualquier intento por modificar el valor de un dato miembro (por ejemplo, nombreCurso) para asegurar que el nuevo valor sea apropiado para ese elemento de datos. Por ejemplo, un intento por establecer el día del mes en 37 debe rechazarse, un intento por establecer el peso de una persona en cero o en un valor negativo debe rechazarse, un intento por establecer una calificación en un examen en 185 (cuando el rango apropiado es de cero a 100) debe rechazarse, y así en lo sucesivo. Observación de Ingeniería de Software 3.3 Hacer los datos miembros private y controlar el acceso, en especial el acceso de escritura, a esos datos miembros a través de funciones miembro public, ayuda a asegurar la integri- dad de los datos. Tip para prevenir errores 3.4 Los beneficios de la integridad de datos no son automáticos sólo porque los datos miembros se hacen private; debemos proporcionar una verificación de validez apropiada y reportar los errores. Una función establecer podría devolver un valor para indicar que se realizó un intento por asignar datosinválidosalobjetodeunaclase.Unclientepodríaentoncesprobarelvalorderetornodelafunción establecer para determinar si el intento del cliente por modificar el objeto fue exitoso, y para realizar la acción apropiada en caso contrario. En capítulos posteriores haremos eso, después de que introduzca- mos un poco más de tecnología de programación. En C++, se puede notificar a los clientes de objetos sobre los problemas a través del mecanismo de manejo de excepciones, que veremos ligeramente en el ca- pítulo 7 y presentaremos de manera detallada en el capítulo 17 (en el sitio web). 3.9Conclusión En este capítulo aprendió a crear clases definidas por el usuario, y a crear y utilizar objetos de esas clases. Declaramos datos miembros de una clase para mantener los datos para cada objeto de la misma. Tam- bién definimos funciones miembro que operan con esos datos. Aprendió que las funciones miembro que no modifican los datos de una clase deben declararse como const. Le mostramos cómo llamar a las funciones miembro de un objeto para solicitar los servicios que éste proporciona, y cómo pasar datos a esas funciones miembro como argumentos. Hablamos sobre la diferencia entre una variable local de una función miembro y un dato miembro de una clase.También le mostramos cómo usar un constructor y una lista inicializadora de miembros para asegurar que todos los objetos se inicialicen correctamente. Aprendió que un constructor con un solo parámetro debe declararse como explicit, y que un cons- tructor no puede declararse como const debido a que modifica el objeto que se va a inicializar. Le demostramos cómo separar la interfaz de una clase de su implementación, para fomentar la buena in- geniería de software. Aprendió que nunca se deben colocar las directivas using y las declaraciones using enlosencabezados.Presentamosundiagramaquemuestralosarchivosquenecesitanlosprogramadores de la implementación de la clase y los programadores del código cliente para compilar el código que escriben. Demostramos cómo se pueden utilizar las funciones establecer para validar los datos de un objeto y asegurar que los objetos se mantengan en un estado consistente. Además, se utilizaron diagra- mas de clases de UML para modelar las clases y sus constructores, las funciones miembro y los datos miembros. En el siguiente capítulo empezaremos nuestra introducción a las instrucciones de control, las cuales especifican el orden en el que se realizan las acciones de una función.
  • 130. 98 Capítulo 3 Introducción a las clases, objetos y cadenas Resumen Sección 3.2 Definición de una clase con una función miembro • La definición de una clase (pág. 68) contiene los datos miembro y las funciones miembro que definen los atri- butos y comportamientos de esa clase, respectivamente. • La definición de una clase empieza con la palabra clave class, seguida inmediatamente por el nombre de la clase. • Por convención, el nombre de una clase definida por el usuario (pág. 69) empieza con una letra mayúscula, y por legibilidad, cada palabra subsiguiente en el nombre de la clase empieza con una letra mayúscula. • El cuerpo de cada clase (pág. 68) va encerrado entre un par de llaves izquierda y derecha ({ y }), y termina con un punto y coma. • Las funciones miembro que aparecen después del especificador de acceso public (pág. 68) pueden ser llamadas por otras funciones en un programa, y por las funciones miembro de otras clases. • Los especificadores de acceso siempre van seguidos de un punto y coma (;). • La palabra clave void (pág. 69) es un tipo de valor de retorno especial, el cual indica que una función realizará una tarea, pero no devolverá datos a la función que la llamó cuando complete su tarea. • Por convención, los nombres de las funciones (pág. 69) empiezan con la primera letra en minúscula, y todas las palabras subsiguientes en el nombre empiezan con letra mayúscula. • Un conjunto vacío de paréntesis después del nombre de una función indica que ésta no requiere datos adicio- nales para realizar su tarea. • Una función que no modifica, y no debe hacerlo, el objeto en el cual se llama debe declararse como const. • Por lo general, no se puede llamar a una función miembro sino hasta que se crea un objeto de su clase. • Cada nueva clase que creamos se convierte en un nuevo tipo en C++. • En UML, cada clase se modela en un diagrama de clases (pág. 70) como un rectángulo con tres compartimien- tos, que (de arriba hacia abajo) contienen el nombre de la clase, los atributos y las operaciones, respectivamente. • UML modela las operaciones como el nombre de la operación, seguido de paréntesis. Un signo más (+) enfren- te del nombre de la operación indica que ésta es una operación public (es decir, una función miembro public en C++). Sección 3.3 Definición de una función miembro con un parámetro • Una función miembro puede requerir uno o más parámetros (pág. 70) para representar los datos adicionales que necesita para realizar su tarea. La llamada a una función suministra un argumento (pág. 71) para cada uno de los parámetros de esa función. • Para llamar a una función miembro, se coloca después del nombre del objeto un operador punto (.), el nombre de la función y un conjunto de paréntesis que contienen los argumentos de la misma. • Una variable de la clase string (pág. 72) de la biblioteca estándar de C++ representa a una cadena de caracteres. Esta clase se define en el encabezado string, y el nombre string pertenece al espacio de nombres std. • La función getline (del encabezado string, pág. 72) lee caracteres de su primer argumento hasta encontrar un carácter de nueva línea, y después coloca los caracteres (sin incluir la nueva línea) en la variable string que se especifica como su segundo argumento. El carácter de nueva línea se descarta. • Una lista de parámetros (pág. 73) puede contener cualquier número de parámetros, incluyendo ninguno (lo cual se representa por paréntesis vacíos) para indicar que una función no requiere parámetros. • El número de argumentos en la llamada a una función debe coincidir con el número de parámetros en la lista de parámetros del encabezado de la función miembro a la que se llamó. Además, los tipos de los argumentos en la llamada a la función deben ser consistentes con los tipos de los parámetros correspondientes en el encabezado de la función. • Para modelar un parámetro de una operación, UML lista el nombre del parámetro, seguido de dos puntos y del tipo del parámetro entre los paréntesis que van después del nombre de la operación. • UML tiene sus propios tipos de datos. No todos los tipos de datos de UML tienen los mismos nombres que los tipos de C++ correspondientes. El tipo String de UML corresponde al tipo string de C++.
  • 131. Resumen 99 Sección 3.4 Datos miembros, funciones establecer y obtener • Las variables que se declaran en el cuerpo de una función son variables locales (pág. 74) y sólo pueden utilizarse desde el punto en el que se declararon en la función, hasta la llave de cierre derecha (}) del bloque en el que se declararon. • Una variable local debe declararse antes de poder utilizarse en una función. Una variable local no puede utili- zarse fuera de la función en la que está declarada. • Por lo general, los datos miembros (pág. 74) son private (pág. 76). Las variables o funciones que se declaran private son accesibles sólo para las funciones miembro de la clase en la que están declaradas, o para las funcio- nes amigas de la clase. • Cuando un programa crea (instancia) un objeto de una clase, sus datos miembros private se encapsulan (ocul- tan, pág. 76) en el objeto y sólo las funciones miembro de la clase del objeto pueden utilizarlos (o las “amigas” de la clase, como veremos en el capítulo 9). • Cuando se hace una llamada a una función que especifica un tipo de valor de retorno distinto de void y ésta completa su tarea, la función devuelve un resultado a la función que la llamó. • De manera predeterminada, el valor inicial de un objeto string es la cadena vacía (pág. 77); es decir, una cade- na que no contenga caracteres. No aparece nada en la pantalla cuando se muestra una cadena vacía. • A menudo, una clase proporciona funciones miembro public para permitir que los clientes de la clase establez- can u obtengan (pág. 78) datos miembros private. Los nombres de esas funciones miembro comúnmente empiezan con establecer u obtener (set o get, en inglés). • Las funciones establecer y obtener permiten a los clientes de una clase utilizar de manera indirecta los datos ocultos. El cliente no sabe cómo realiza el objeto estas operaciones. • Las funciones establecer y obtener de una clase deben ser utilizadas por otras funciones miembro de la clase para manipular los datos private de esa clase. Si la representación de los datos de la clase cambia, las funciones miembro que acceden a los datos sólo a través de las funciones establecer y obtener no requerirán modificación. • Una función establecer pública debe escudriñar cuidadosamente cualquier intento por modificar el valor de un dato miembro, para asegurar que el nuevo valor sea apropiado para ese elemento de datos. • UML representa a los datos miembros como atributos, para lo cual enlista el nombre del atributo, seguido de dos puntos y del tipo del atributo. En UML, se coloca un signo menos (-) antes de los atributos privados. • Para indicar el tipo de valor de retorno de una operación en UML, se coloca un signo de dos puntos y el tipo de valor de retorno después de los paréntesis que siguen del nombre de la operación. • Los diagramas de clases de UML no especifican tipos de valores de retorno para las operaciones que no devuel- ven valores. Sección 3.5 Inicialización de objetos mediante constructores • Cada clase debe proporcionar uno o más constructores (pág, 79) para inicializar un objeto de la clase cuando el objeto se crea. Un constructor se debe definir con el mismo nombre que la clase. • Una diferencia entre los constructores y las funciones es que los constructores no pueden devolver valores, por lo que no pueden especificar un tipo de valor de retorno (ni siquiera void). Por lo general, los constructores se declaran como public. • C++ llama automáticamente a un constructor por cada objeto que se crea, lo cual ayuda a asegurar que todo objeto se inicialice antes de usarlo en un programa. • Un constructor sin parámetros es un constructor predeterminado (pág. 80). Si el programador no proporciona un constructor, el compilador proporciona un constructor predeterminado.También podemos definir un cons- tructor predeterminado de manera explícita. Si define un constructor para una clase, C++ no creará un construc- tor predeterminado. • Un constructor con un solo parámetro debe declararse como explicit. • Un constructor usa una lista inicializadora de miembros para inicializar los datos miembros de una clase. Los inicializadores de miembros aparecen entre la lista de parámetros y la llave izquierda que comienza el cuerpo del constructor. La lista inicializadora de miembros se separa de la lista de parámetros con un signo de dos puntos (:). Un inicializador de miembro consiste en el nombre de variable de un miembro de datos seguido de paréntesis que contienen el valor inicial del miembro. Podemos realizar la inicialización en el cuerpo del constructor, pero
  • 132. 100 Capítulo 3 Introducción a las clases, objetos y cadenas más adelante en el libro veremos que es más eficiente hacerlo con inicializadores de miembros; además algunos tipos de datos miembros deben inicializarse de esta forma. • UML modela a los constructores como operaciones en el tercer compartimiento de un diagrama de clases, con la palabra “constructor” entre los signos « y » antes del nombre del constructor. Sección 3.6 Colocar una clase en un archivo separado para fines de reutilización • Cuando las definiciones de clases se empaquetan en forma apropiada, los programadores de todo el mundo pueden reutilizarlas. • Es costumbre definir una clase en un encabezado (pág. 83) que tenga una extensión de nombre de archivo .h. Sección 3.7 Separar la interfaz de la implementación • Si cambia la implementación de la clase, los clientes de ésta no tienen que cambiar. • Las interfaces definen y estandarizan las formas en que deben interactuar las cosas como las personas y los siste- mas. • La interfaz public de una clase (pág. 87) describe las funciones miembro public que están disponibles para los clientes de la clase. La interfaz describe qué servicios (pág. 87) pueden usar los clientes y cómo reutilizar esos servicios, pero no especifica cómo es que la clase lleva a cabo los servicios. • Al separar la interfaz de la implementación (pág. 87) se facilita la modificación de los programas. Los cambios en la implementación de la clase no afectan al cliente, mientras que la interfaz de la clase permanezca sin cambios. • Nunca se deben colocar directivas using ni declaraciones using en los encabezados. • El prototipo de una función (pág. 87) contiene el nombre de una función, su tipo de valor de retorno y el nú- mero, tipos y orden de los parámetros que la función espera recibir. • Una vez que se define una clase y se declaran sus funciones miembro (a través de los prototipos de función), las funciones miembro deben definirse en un archivo de código fuente separado. • Para cada función miembro definida fuera de su correspondiente definición de clase, hay que anteponer al nombre de la función el nombre de la clase y el operador de resolución de ámbito (::, pág. 89). Sección 3.8 Validación de datos mediante funciones establecer • La función miembro size de la clase string (pág. 93) devuelve el número de caracteres en un objeto string. • La función miembro substr (pág. 95) de la clase string devuelve un nuevo objeto string que contiene una copia de parte de un objeto string existente. El primer argumento especifica la posición inicial en el objeto string original. El segundo argumento especifica el número de caracteres a copiar. Ejercicios de autoevaluación 3.1 Complete las siguientes oraciones: a) Cada declaración de clase contiene la palabra clave __________, seguida inmediatamente por el nombre de la clase. b) Por lo general, la definición de una clase se almacena en un archivo con la extensión de archivo __________. c) Cada parámetro en un encabezado de función debe especificar un(a) ___________________ y un(a) __________________. d) Cuando cada objeto de una clase mantiene su propia versión de un atributo, la variable que represen- ta a este atributo se conoce también como __________. e) La palabra clave public es un(a) __________. f) El tipo de valor de retorno __________ indica que una función realizará una tarea, pero no devolverá información cuando complete su tarea. g) La función __________ de la biblioteca string lee caracteres hasta encontrar una nueva línea, y después copia esos caracteres en el objeto string especificado. h) Cuandosedefineunafunciónmiembrofueradeladefinicióndeunaclase,elencabezadodelafunción debe incluir el nombre de la clase y el ________, seguido del nombre de la función para “enlazar” la función miembro con la definición de la clase.
  • 133. Ejercicios 101 i) El archivo de código fuente, y cualquier otro archivo que utilice una clase, pueden incluir el archivo de encabezado de la clase mediante una directiva del preprocesador __________. 3.2 Conteste con verdadero o falso a cada una de las siguientes proposiciones; en caso de ser falso, explique por qué. a) Por convención, los nombres de las funciones empiezan con la primera letra en mayúscula y todas las palabras subsiguientes en el nombre empiezan con la primera letra en mayúscula. b) Los paréntesis vacíos que van después del nombre de una función en un prototipo de función indican que ésta no requiere parámetros para realizar su tarea. c) Los datos miembros o las funciones miembro que se declaran con el modificador de acceso private son accesibles para las funciones miembro de la clase en la que se declaran. d) Las variables que se declaran en el cuerpo de una función miembro específica se conocen como datos miembros, y pueden utilizarse en todas las funciones miembro de la clase. e) El cuerpo de toda función está delimitado por las llaves izquierda y derecha ({ y }). f) Cualquier archivo de código fuente que contenga int main() puede usarse para ejecutar un programa. g) Los tipos de los argumentos en la llamada a una función deben ser consistentes con los tipos de los parámetros correspondientes en la lista de parámetros del prototipo de la función. 3.3 ¿Cuál es la diferencia entre una variable local y un dato miembro? 3.4 Explique el propósito de un parámetro de una función. ¿Cuál es la diferencia entre un parámetro y un argumento? Respuestas a los ejercicios de autoevaluación 3.1 a) class. b) .h. c) tipo, nombre. d) miembro de datos. e) especificador de acceso. f) void. g) getline. h) operador de resolución de ámbito (::). i) #include. 3.2 a) Falso. Los nombres de las funciones empiezan con una primera letra en minúscula y todas las palabras subsiguientes en el nombre empiezan con una letra en mayúscula. b) Verdadero. c) Verdadero. d) Falso. Dichas variables son variables locales y sólo pueden usarse en la función miembro en la que están declaradas. e) Verdadero. f) Verdadero. g) Verdadero. 3.3 Una variable local se declara en el cuerpo de una función, y sólo puede utilizarse desde el punto en el que se declaró, hasta la llave de cierre del bloque correspondiente. Un miembro de datos se declara en una clase, pero no en el cuerpo de alguna de las funciones miembro de la clase. Cada objeto de una clase tiene una copia separada de los datos miembros de la clase. Los datos miembros están accesibles para todas las funciones miembro de la clase. 3.4 Un parámetro representa la información adicional que requiere una función para realizar su tarea. Cada parámetro requerido por una función está especificado en el encabezado de la función. Un argumento es el valor que se suministra en la llamada a la función. Cuando se llama a la función, el valor del argumento se pasa al pará- metro de la función, para que ésta pueda realizar su tarea. Ejercicios 3.5 (Prototipos y definiciones de funciones) Explique la diferencia entre un prototipo de función y la definición de una función. 3.6 (Constructor predeterminado) ¿Qué es un constructor predeterminado? ¿Cómo se inicializan los datos miembros de un objeto, si una clase sólo tiene un constructor predeterminado definido en forma implícita? 3.7 (Datos miembros) Explique el propósito de un dato miembro. 3.8 (Encabezado y archivos de código fuente) ¿Qué es un encabezado? ¿Qué es un archivo de código fuente? Hable sobre el propósito de cada uno. 3.9 (Uso de una clase sin una directiva using) Explique cómo puede usar un programa una clase string sin insertar una directiva using.
  • 134. 102 Capítulo 3 Introducción a las clases, objetos y cadenas 3.10 (Funciones establecer y obtener) Explique por qué una clase podría proporcionar una función establecer y una función obtener para un dato miembro. 3.11 (Modificación de la clase LibroCalificaciones) Modifique la clase LibroCalificaciones (figuras 3.11 a 3.12) de la siguiente manera: a) Incluya un segundo miembro de datos string, que represente el nombre del instructor del curso. b) Proporcione una función establecer para modificar el nombre del instructor, y una función obtener para obtenerlo. c) Modifique el constructor para especificar dos parámetros: uno para el nombre del curso y otro para el nombre del instructor. d) Modifique la función mostrarMensaje, de tal forma que primero imprima el mensaje de bienvenida y el nombre del curso, y que después imprima Este curso es presentado por: , seguido del nombre del instructor. Use su clase modificada en un programa de prueba que demuestre las nuevas capacidades de la clase. 3.12 (Clase Cuenta) Cree una clase llamada Cuenta que podría ser utilizada por un banco para representar las cuentas bancarias de sus clientes. Incluya un miembro de datos de tipo int para representar el saldo de la cuenta. [Nota: en los siguientes capítulos, utilizaremos números que contienen puntos decimales (por ejemplo, 2.75), a los cuales se les conoce como valores de punto flotante, para representar montos en dólares]. Proporcione un cons- tructor que reciba un saldo inicial y lo utilice para inicializar el dato miembro. El constructor debe validar el saldo inicial para asegurar que sea mayor o igual que 0. De no ser así, establezca el saldo en 0 y muestre un mensaje de error,indicandoqueelsaldoinicialerainválido.Proporcionetresfuncionesmiembro.Lafunciónmiembroabonar debe agregar un monto al saldo actual. La función miembro cargar deberá retirar dinero del objeto Cuenta y asegurarse que el monto a cargar no exceda el saldo de Cuenta. Si lo hace, el saldo debe permanecer sin cambio y la función debe imprimir un mensaje que indique El monto a cargar excede el saldo de la cuenta. La función miembro obtenerSaldo debe devolver el saldo actual. Cree un programa que cree dos objetos Cuenta y evalúe las funciones miembro de la clase Cuenta. 3.13 (Clase Factura) Cree una clase llamada Factura, que una ferretería podría utilizar para representar una factura por un artículo vendido en la tienda. Una Factura debe incluir cuatro datos miembros: un número de pieza(tipostring),ladescripcióndelapieza(tipostring),lacantidaddeartículosdeesetipoquesevanacomprar (tipo int)yelprecioporartículo(tipo int).[Nota: enlossiguientescapítulos,utilizaremosnúmerosquecontienen puntos decimales (por ejemplo, 2.75), a los cuales se les conoce como valores de punto flotante, para representar montos en dólares]. Su clase debe tener un constructor que inicialice los cuatro datos miembros. Un constructor que recibe múltiples argumentos se define mediante la siguiente forma: nombreClase( nombreTipo1 nombreParámetro1, nombreTipo2 nombreParámetro2, …) Proporcione una función establecer y una función obtener para cada miembro de datos. Además, proporcione una función miembro llamada obtenerMontoFactura, que calcule el monto de la factura (es decir, que multiplique la cantidad por el precio por artículo) y después devuelva ese monto como un valor int. Si la cantidad no es positiva, debe establecerse en 0. Si el precio por artículo no es positivo, debe establecerse en 0. Escriba un programa de prueba que demuestre las capacidades de la clase Factura. 3.14 (Clase Empleado) Cree una clase llamada Empleado, que incluya tres piezas de información como datos miembros: un primer nombre (tipo string), un apellido paterno (tipo string) y un salario mensual (tipo int). [Nota: en los siguientes capítulos, utilizaremos números que contienen puntos decimales (por ejemplo, 2.75), a los cuales se les conoce como valores de punto flotante, para representar montos en dólares]. Su clase debe tener un constructor que inicialice los tres datos miembros. Proporcione una función establecer y una función obtener para cada dato miembro. Si el salario mensual no es positivo, establézcalo en 0. Escriba un programa de prueba que demuestre las capacidades de la clase Empleado. Cree dos objetos Empleado y muestre el salario anual de cada objeto. Después, proporcione a cada Empleado un aumento del 10% y muestre el salario anual de cada Empleado otra vez. 3.15 (Clase Fecha) CreeunaclasellamadaFecha,queincluyatrespiezasdeinformacióncomodatosmiembros: un mes (tipo int), un día (tipo int) y un año (tipo int). Su clase debe tener un constructor con tres parámetros, los cuales debe utilizar para inicializar los tres datos miembros. Para los fines de este ejercicio, suponga que los va- lores que se proporcionan para el año y el día son correctos, pero asegúrese que el valor del mes se encuentre en el rango de 1 a 12; de no ser así, establezca el mes en 1. Proporcione una función establecer y una función obtener para
  • 135. Hacer la diferencia 103 cada miembro de datos. Proporcione una función miembro mostrarFecha, que muestre el mes, día y año, separa- dos por barras diagonales (/). Escriba un programa de prueba que demuestre las capacidades de la clase Fecha. Hacer la diferencia 3.16 (Calculadora de la frecuencia cardiaca esperada) Mientras se ejercita, puede usar un monitor de frecuen- cia cardiaca para ver que su corazón permanezca dentro de un rango seguro sugerido por sus entrenadores y doc- tores. De acuerdo con la Asociación Estadounidense del Corazón (AHA) (www.americanheart.org/presenter. jhtml?identifier=4736), la fórmula para calcular su frecuencia cardiaca máxima en pulsos por minuto es 220 menos su edad en años. Su frecuencia cardiaca esperada es un rango que está entre el 50 y el 85% de su frecuencia cardiaca máxima. [Nota: estas fórmulas son estimaciones proporcionadas por la AHA. Las frecuencias cardiacas máxima y esperada pueden variar con base en la salud, condición física y sexo del individuo. Siempre debe consultar un médico o a un profesional de la salud antes de empezar o modificar un programa de ejercicios]. Cree una clase llamada Frecuen- ciasCardiacas. Los atributos de la clase deben incluir el primer nombre de la persona, su apellido y fecha de naci- miento (la cual debe consistir de atributos separados para el mes, día y año de nacimiento). Su clase debe tener un constructor que reciba estos datos como parámetros. Para cada atributo debe proveer funciones establecer y obtener. La clase también debe incluir una función obtenerEdad que calcule y devuelva la edad de la persona (en años), una función obtenerFrecuenciaCardiacaMaxima que calcule y devuelva la frecuencia cardiaca máxima de esa persona, y una función obtenerFrecuenciaCardiacaEsperada que calcule y devuelva la frecuencia cardiaca esperada de la persona. Puesto que todavía no sabe cómo obtener la fecha actual de la computadora, la función obtenerEdad debe pedir al usuario que introduzca el mes, día y año actual antes de calcular la edad de la persona. Escriba una aplica- ción que pida la información de la persona, cree una instancia de un objeto de la clase FrecuenciasCardiacas e imprima la información a partir de ese objeto (incluyendo el primer nombre de la persona, su apellido y fecha de nacimiento), y que después calcule e imprima la edad de la persona en (años), frecuencia cardiaca máxima y rango de frecuencia cardiaca esperada. 3.17 (Computarización de los registros médicos) Un problema relacionado con la salud que ha estado última- mente en las noticias es la computarización de los registros médicos. Esta posibilidad se está tratando con mucho cuidado,debidoalasdelicadascuestionesdeprivacidadyseguridad,entreotrascosas.[Trataremosesascuestiones en ejercicios posteriores]. La computarización de los registros médicos puede facilitar a los pacientes el proceso de compartir sus perfiles e historiales médicos con los diversos profesionales de la salud que consulten. Esto podría mejorar la calidad del servicio médico, ayudar a evitar conflictos de fármacos y prescripciones erróneas, reducir los costos y, en emergencias, podría ayudar a salvar vidas. En este ejercicio usted diseñará una clase “inicial” lla- mada PerfilMedico para una persona. Los atributos de la clase deben incluir el primer nombre de la persona, su apellido, sexo, fecha de nacimiento (que debe consistir de atributos separados para el día, mes y año de nacimien- to), altura (en centímetros) y peso (en kilogramos). Su clase debe tener un constructor que reciba estos datos. Para cada atributo, debe proveer las funciones establecer y obtener. La clase también debe incluir métodos que calculen y devuelvan la edad del usuario en años, la frecuencia cardiaca máxima y el rango de frecuencia cardiaca esperada (vea el ejercicio 3.16), además del índice de masa corporal (BMI; vea el ejercicio 2.30). Escriba una aplicación que pida la información de la persona, cree una instancia de un objeto de la clase PerfilMedico para esa persona e imprima la información de ese objeto (incluyendo el primer nombre de la persona, apellido, sexo, fecha de nacimiento, altura y peso), y que después calcule e imprima la edad de esa persona en años, junto con el BMI, la frecuencia cardiaca máxima y el rango de frecuencia cardiaca esperada.También debe mostrar la tabla de “valores del BMI” del ejercicio 2.30. Use la misma técnica que en el ejercicio 3.16 para calcular la edad de la persona.
  • 136. Instrucciones de control, parte I: operadores de asignación, ++ y –– 4 Desplacémonos un lugar. —Lewis Carroll La rueda se convirtió en un círculo completo. —William Shakespeare Toda la evolución que conocemos procede de lo vago a lo definido. —Charles Sanders Peirce O b j e t i v o s En este capítulo aprenderá a: n Comprender las técnicas básicas para solucionar problemas. n Desarrollar algoritmos mediante el proceso de mejoramiento de arriba a abajo, paso a paso. n Utilizar las estructuras de selección if e if...else para elegir entre distintas acciones alternativas. n Utilizar la estructura de repetición while para ejecutar instrucciones de manera repetitiva dentro de un programa. n Comprender la repetición controlada por un contador y la repetición controlada por un centinela. n Utilizar los operadores de incremento, decremento y asignación.
  • 137. 4.2 Algoritmos 105 4.1Introducción Antes de escribir un programa que dé solución a un problema, es necesario tener una comprensión de- tallada de todo el problema, además de una metodología cuidadosamente planeada para resolverlo. Al escribir un programa, también debemos comprender los tipos de bloques de construcción disponibles, y emplear las técnicas comprobadas para construirlos. En éste y en el capítulo 5, “Instrucciones de con- trol, parte 2: operadores lógicos”, hablaremos sobre estas cuestiones cuando presentemos la teoría y los principios de la programación estructurada. Los conceptos aquí presentados son imprescindibles para crear clases efectivas y manipular objetos. En este capítulo presentamos las instrucciones if, if...else y while de C++, tres de los bloques de construcción que permiten a los programadores especificar la lógica requerida para que las funciones miembro realicen sus tareas. Dedicamos una parte de este capítulo (y de los capítulos 5 a 7) para desa- rrollar más la clase LibroCalificaciones. En especial, agregamos a dicha clase una función miembro que utiliza instrucciones de control para calcular el promedio de un conjunto de calificaciones de estu- diantes. Otro ejemplo demuestra formas adicionales de combinar instrucciones de control. Presenta- mos los operadores de asignación, incremento y decremento de C++. Estos operadores adicionales abrevian y simplifican muchas instrucciones de los programas. 4.2Algoritmos Cualquier problema de computación puede resolverse ejecutando una serie de acciones en un orden específico. Un procedimiento para resolver un problema en términos de 1. las acciones a ejecutar y 2. el orden en el que se ejecutan estas acciones se conoce como un algoritmo. El siguiente ejemplo demuestra que es importante especificar de mane- ra correcta el orden en el que se ejecutan las acciones. Considere el “algoritmo para levantarse y arreglarse” que sigue un ejecutivo para levantarse de la cama e ir a trabajar: (1) levantarse, (2) quitarse la pijama, (3) bañarse, (4) vestirse, (5) desayunar, (6) transportarse al trabajo. Esta rutina logra que el ejecutivo llegue al trabajo bien preparado para tomar decisiones críticas. Suponga que los mismos pasos se realizan en un orden ligeramente distinto: (1) le- vantarse; (2) quitarse la pijama; (3) vestirse; (4) bañarse; (5) desayunar; (6) transportarse al trabajo. En este caso, nuestro ejecutivo llegará al trabajo todo mojado. Al proceso de especificar el orden en el que 4.1 Introducción 4.2 Algoritmos 4.3 Seudocódigo 4.4 Estructuras de control 4.5 Instrucción de selección if 4.6 Instrucción de selección doble if...else 4.7 Instrucción de repetición while 4.8 Cómo formular algoritmos: repetición controlada por un contador 4.9 Cómo formular algoritmos: repetición controlada por un centinela 4.10 Cómo formular algoritmos: instrucciones de control anidadas 4.11 Operadores de asignación 4.12 Operadores de incremento y decremento 4.13 Conclusión Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios | Hacer la diferencia
  • 138. 106 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– se ejecutan las instrucciones (acciones) en un programa de computadora, se le llama control del pro- grama. En este capítulo investigaremos el control de los programas mediante el uso de las instrucciones de control de C++. 4.3Seudocódigo El seudocódigo (o “imitación” de código) es un lenguaje artificial e informal que ayuda a los programa- dores a desarrollar algoritmos sin tener que preocuparse por los detalles de la sintaxis del lenguaje C++. El seudocódigo que presentaremos aquí es útil para desarrollar algoritmos que se convertirán en progra- mas estructurados de C++. El seudocódigo es similar al lenguaje cotidiano; es conveniente y amigable con el usuario, aunque no es realmente un lenguaje de programación de computadoras. El seudocódigo no se ejecuta en las computadoras. En vez de ello, ayuda al programador a “organi- zar” un programa antes de intentar escribirlo en un lenguaje de programación como C++. El estilo de seudocódigo que presentaremos consiste solamente en caracteres, de manera que los programadores puedan escribir el seudocódigo convenientemente, utilizando cualquier programa edi- tor de texto. Un programa en seudocódigo preparado de manera cuidadosa puede convertirse fácilmen- te en su correspondiente programa en C++. En muchos casos, esto requiere tan sólo reemplazar las instrucciones en seudocódigo con sus instrucciones equivalentes en C++. Por lo general, el seudocódigo describe sólo las instrucciones ejecutables, las cuales provocan que ocurran acciones específicas después de que un programador convierte un programa de seudocódigo a C++, y el programa se compila y ejecuta en una computadora. Las declaraciones (que no tienen inicia- lizadores, o que no implican llamadas a un constructor) no son instrucciones ejecutables. Por ejemplo, la declaración int contador; indica al compilador el tipo de la variable contador y lo instruye para que reserve espacio en memoria para esa variable. Esta declaración no hace que ocurra ninguna acción (como una operación de entrada, salida o un cálculo) cuando el programa se ejecuta. Por lo general no incluimos las declaraciones de va- riables en nuestro seudocódigo. Algunos programadores optan por listar las variables y mencionar sus propósitos al principio de sus programas en seudocódigo. Veamos un ejemplo de seudocódigo que se puede escribir para ayudar a un programador a crear el programa de suma de la figura 2.5. Este seudocódigo (figura 4.1) corresponde al algoritmo que recibe como entrada dos enteros del usuario, los suma y muestra el resultado en pantalla. Aunque mostramos aquí el listado completo en seudocódigo, más adelante le mostraremos cómo crear seudocódigo a partir del enunciado de un problema. Las líneas 1 y 2 corresponden a las instrucciones 13 y 14 de la figura 2.5 Observe que las instruccio- nes en seudocódigo son simplemente instrucciones en lenguaje cotidiano que representan la tarea que se debe realizar en C++. De igual forma, las líneas 4 y 5 corresponden a las instrucciones en las líneas 16 y 17, y las líneas 7 y 8 corresponden a las instrucciones en las líneas 19 y 21 de la figura 2.5. 1 Pedir al usuario que introduzca el primer entero 2 Recibir como entrada el primer entero 3 4 Pedir al usuario que introduzca el segundo entero 5 Recibir como entrada el segundo entero 6 7 Sumar el primer entero con el segundo entero, almacenar el resultado 8 Mostrar el resultado en pantalla Fig. 4.1  Seudocódigo para el programa de suma de la figura 2.5.
  • 139. 4.4 Estructuras de control 107 4.4Estructuras de control Por lo general, en un programa las instrucciones se ejecutan una después de otra, en el orden en que están escritas. Este proceso se conoce como ejecución secuencial. Varias instrucciones en C++ que pronto veremos, permiten al programador especificar que la siguiente instrucción a ejecutarse tal vez no sea la si- guiente en la secuencia. Esto se conoce como transferencia de control. Durante la década de 1960, se hizo evidente que el uso indiscriminado de las transferencias de control era el origen de muchas de las dificultades que experimentaban los grupos de desarrollo de soft- ware. A quien se señaló como culpable fue a la instrucción goto, la cual permite al programador espe- cificar la transferencia de control a uno de los muchos posibles destinos dentro de un programa (crean- do lo que se conoce comúnmente como “código espagueti”). La noción de la llamada programación estructurada se hizo casi un sinónimo de la “eliminación del goto”. Las investigaciones de Böhm y Jacopini1 demostraron que los programas podían escribirse sin instrucciones goto. El reto de la época para los programadores fue cambiar sus estilos a una “progra- mación sin goto”. No fue sino hasta la década de 1970 cuando los programadores tomaron en serio la programación estructurada. Los resultados han sido impresionantes, ya que los grupos de desarrollo de software han reportado reducciones en los tiempos de desarrollo, mayor incidencia de entregas de sistemas a tiempo y más proyectos de software finalizados sin salirse del presupuesto. La clave para estos logros es que los programas estructurados son más claros, más fáciles de depurar, probar y modificar, y hay más probabilidad de que estén libres de errores desde el principio. El trabajo de Böhm y Jacopini demostró que todos los programas podían escribirse en términos de tres estructuras de control solamente: la de secuencia, la de selección y la de repetición. El término “estructuras de control” proviene del campo de las ciencias computacionales. Cuando presentemos las implementaciones en C++ de las estructuras de control, nos referiremos a ellas en la terminología del documento del estándar de C++ como “instrucciones de control”. Estructura de secuencia en C++ La estructura de secuencia está integrada en C++. A menos que se le indique lo contrario, la computado- ra ejecuta las instrucciones en C++ una después de otra, en el orden en que estén escritas; es decir, en secuencia. El diagrama de actividad en UML de la figura 4.2 ilustra una estructura de secuencia típica, sumar 1 al contador sumar calificación al total Instrucción en C++: total = total + calificacion; Instrucción en C++: contador = contador + 1; Fig. 4.2  Diagrama de actividad de una estructura de secuencia. 1 Böhm, C. y G. Jacopini, “Flow Diagrams,Turing Machines, and Languages with OnlyTwo Formation Rules”, Com- munications of the ACM, vol. 9, No. 5, mayo de 1996, páginas 366-371.
  • 140. 108 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– en la que se realizan dos cálculos en orden. C++ permite tantas acciones como deseemos en una estruc- tura de secuencia. Como veremos pronto, en donde quiera que se coloque una sola acción, podrán colo- carse varias acciones en secuencia. En esta figura, las dos instrucciones implican sumar una calificación a una variable llamada total, y sumar el valor 1 a una variable llamada contador. Dichas instrucciones podrían aparecer en un pro- grama que obtenga el promedio de varias calificaciones de estudiantes. Para calcular un promedio, el total de las calificaciones que se van a promediar se divide entre el número de calificaciones. Podría usarse una variable contador para llevar la cuenta del número de valores a promediar. En el programa de la sección 4.8 verá instrucciones similares. Undiagramadeactividadmodelaelflujo de trabajo(tambiénconocidocomolaactividad)deuna parte de un sistema de software. Dichos flujos de trabajo pueden incluir una porción de un algoritmo, como la estructura de secuencia de la figura 4.2. Los diagramas de actividad están compuestos por sím- bolos de propósito especial, como los símbolos de estado de acción (un rectángulo cuyo lado izquier- do y derecho se reemplazan con arcos hacia fuera), rombos (diamantes) y pequeños círculos; estos símbolos se conectan mediante flechas de transición, que representan el flujo de la actividad. Los diagramas de actividad muestran claramente cómo operan las estructuras de control. Consi- dere el diagrama de actividad para la estructura de secuencia de la figura 4.2. Este diagrama contiene dos estados de acción que representan las acciones a realizar. Cada estado de acción contiene una expresión de acción (por ejemplo, “sumar calificación a total” o “sumar 1 al contador”), que especifi- ca una acción particular a realizar. Otras acciones podrían incluir cálculos u operaciones de entrada/ salida. Las flechas en el diagrama de actividad se llaman flechas de transición. Estas flechas representan transiciones, las cuales indican el orden en el que ocurren las acciones representadas por los estados de acción; el programa que implementa las actividades ilustradas por el diagrama de actividad de la figu- ra 4.2 primero suma calificacion a total, y después suma 1 a contador. El círculo relleno que se encuentra en la parte superior del diagrama de actividad representa el estado inicial de la actividad: el inicio del flujo de trabajo antes de que el programa realice las activida- des modeladas. El círculo sólido rodeado por una circunferencia que aparece en la parte inferior del diagrama de actividad representa el estado final; es decir, el final del flujo de trabajo después de que el programa realiza sus actividades. La figura 4.2 también incluye rectángulos que tienen la esquina superior derecha doblada. En UML, a estos rectángulos se les llama notas: comentarios con explicaciones que describen el propósito de los símbolos en el diagrama. La figura 4.2 utiliza las notas de UML para mostrar el código en C++ asociado con cada uno de los estados de acción en el diagrama de actividad. Una línea punteada conecta cada nota con el elemento que ésta describe. Por lo general, los diagramas de actividad no muestran el código de C++ que imple- menta la actividad. En este libro utilizamos las notas con este propósito, para mostrar cómo se relaciona el diagrama con el código en C++. Para obtener más información sobre UML, vea nuestro caso de estu- dio opcional (pero ampliamente recomendado), que aparece en los capítulos 25 y 26, y visite nuestro Centro de recursos sobre UML en www.deitel.com/UML/. Instrucciones de selección en C++ C++ tiene tres tipos de instrucciones de selección (las cuales se describen en este capítulo y en el siguien- te). La instrucción de selección if realiza (selecciona) una acción si una condición es verdadera, o evita la acción si la condición es falsa. La instrucción de selección if...else realiza una acción si una condi- ción es verdadera, o realiza una acción distinta si la condición es falsa. La instrucción de selección switch (capítulo 5) realiza una de entre varias acciones distintas, dependiendo del valor de una expresión entera. La instrucción de selección if es una instrucción de selección simple, ya que selecciona o ignora una sola acción (o, como pronto veremos, un solo grupo de acciones). La instrucción if...else se cono- ce como instrucción de selección doble, ya que selecciona entre dos acciones distintas (o grupos de acciones). La instrucción switch es una estructura de selección múltiple, ya que selecciona entre muchas acciones distintas (o grupos de acciones).
  • 141. 4.4 Estructuras de control 109 Instrucciones de repetición en C++ C++ cuenta con tres tipos de instrucciones de repetición (también llamadas instrucciones de ciclo o ciclos) para ejecutar instrucciones en forma repetida, siempre y cuando una condición (llamada la condición de continuación del ciclo) siga siendo verdadera. Éstas son las instrucciones while, do... while y for (en el capítulo 5 presentaremos las instrucciones do...while y for, y en el capítulo 7 pre- sentaremos una versión especializada de la instrucción for, que se utiliza con lo que se conoce como arreglos y contenedores). Las instrucciones while y for realizan la acción (o grupo de acciones) en sus cuerpos, cero o más veces; si la condición de continuación del ciclo es inicialmente falsa, no se ejecutará la acción (o grupo de acciones). La instrucción do...while realiza la acción (o grupo de acciones) en su cuerpo, por lo menos una vez. Las palabras if, else, switch, while, do y for son palabras clave en C++. Las palabras clave no pue- den usarse como identificadores, como los nombres de variables, y deben escribirse sólo en minúsculas. En la figura 4.3 aparece una lista completa de las palabras clave en C++. Palabras clave de C++ Palabras clave comunes para los lenguajes de programación C y C++ auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while Palabras clave sólo de C++ and and_eq asm bitand bitor bool catch class compl const_cast delete dynamic_cast explicit export false friend inline mutable namespace new not not_eq operator or or_eq private protected public reinterpret_cast static_cast template this throw true try typeid typename using virtual wchar_t xor xor_eq Palabras clave de C++11 alignas alignof char16_t char32_t constexpr decltype noexcept nullptr static_assert thread_local Fig. 4.3  Palabras clave de C++.
  • 142. 110 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– Resumen de las instrucciones de control en C++ C++ sólo tiene tres tipos de estructuras de control, a las cuales nos referiremos de aquí en adelante como instrucciones de control: la instrucción de secuencia, las instrucciones de selección (tres tipos: if, if...else y switch) y las instrucciones de repetición (tres tipos: while, for y do...while). Cada programa combina tantas de estas instrucciones de control como sea apropiado para el algoritmo que implemente el programa. Podemos modelar cada una de las instrucciones de control como un diagrama de actividad, con estados iniciales y finales que representan los puntos de entrada y salida de la instruc- ción de control, respectivamente. Estas instrucciones de control de una sola entrada/una sola salida facilitan la creación de programas; las instrucciones de control están unidas entre sí mediante la cone- xión del punto de salida de una instrucción de control, al punto de entrada de la siguiente. Este proce- dimiento es similar a la manera en que un niño apila los bloques de construcción, así que a esto le lla- mamos apilamiento de instrucciones de control. En breve aprenderemos que sólo hay una manera alternativa de conectar las instrucciones de control: el anidamiento de instrucciones de control, en el cual una instrucción de control aparece dentro de otra. Observación de Ingeniería de Software 4.1 Cualquier programa de C++ puede construirse a partir de sólo siete tipos distintos de instrucciones de control (secuencia, if, if...else, switch, while, do...while y for), combinadas en sólo dos formas (apilamiento de instrucciones de control y anidamiento de instrucciones de control). 4.5Instrucción de selección if Los programas utilizan instrucciones de selección para elegir entre los cursos alternativos de acción. Por ejemplo, suponga que la calificación para aprobar un examen es de 60. La instrucción en seudocódigo Si la calificación del estudiante es mayor o igual a 60 Imprimir “Aprobado” determina si la condición “la calificación del estudiante es mayor o igual a 60” es verdadera (true) o falsa (false). Si la condición es verdadera se imprime “Aprobado”, y se “ejecuta” en orden la siguiente instrucción en seudocódigo (recuerde que el seudocódigo no es un verdadero lenguaje de programa- ción). Si la condición es falsa se ignora la instrucción para imprimir, y se ejecuta en orden la siguiente instrucción en seudocódigo. La sangría de la segunda línea es opcional, pero se recomienda ya que en- fatiza la estructura inherente de los programas estructurados. La instrucción anterior if en seudocódigo puede escribirse en C++ de la siguiente manera: if ( calificacion = 60 ) cout Aprobado; El código en C++ corresponde en gran medida con el seudocódigo. Ésta es una de las propiedades que hace del seudocódigo una herramienta de desarrollo de programas tan útil. Es importante mencionar aquí que estamos suponiendo por casualidad que calificacion contie- ne un valor válido: un entero en el rango de 0 a 100. A lo largo del libro presentaremos muchas técnicas de validación importantes. Tip para prevenir errores 4.1 En el código que se utiliza en la industria, siempre hay que validar todas las entradas.
  • 143. 4.5 Instrucción de selección if 111 La figura 4.4 muestra la instrucción if de selección simple. Esta figura contiene lo que quizá sea el símbolo más importante en un diagrama de actividad: el rombo o símbolo de decisión, el cual indica que se va a tomar una decisión. Un símbolo de decisión indica que el flujo de trabajo continuará a lo largo de una ruta determinada por las condiciones de guardia asociadas de ese símbolo, que pueden ser verdaderas o falsas. Cada flecha de transición que sale de un símbolo de decisión tiene una condición de guardia especificada entre corchetes, por encima o a un lado de la flecha de transición. Si cierta condición de guardia es verdadera, el flujo de trabajo entra al estado de acción al que apunta la flecha de transición. En la figura 4.4, si la calificación es mayor o igual a 60, el programa imprime “Aprobado” en la pantalla, y luego se dirige al estado final de esta actividad. Si la calificación es menor a 60, el programa se dirige inmediatamente al estado final sin mostrar ningún mensaje. imprimir Aprobado [calificacion = 60] [calificacion 60] Fig. 4.4  Diagrama de actividad de la instrucción if de selección simple. En el capítulo 2 aprendimos que las decisiones se pueden basar en condiciones que contengan operadores de igualdad o relacionales. En realidad, en C++ una decisión se puede basar en cualquier expresión; si la expresión se evalúa como cero, se considera como falsa; si la expresión se evalúa como un número distinto de cero, se considera verdadera. C++ proporciona el tipo de datos bool para las variables que sólo pueden contener los valores true y false; cada una de éstas es una palabra clave de C++. Tip de portabilidad 4.1 Para tener compatibilidad con versiones anteriores de C, en las que se utilizan enteros para los valores booleanos, el valor bool true también se puede representar mediante cualquier valor distinto de cero (por lo general, los compiladores utilizan 1) y el valor bool false también se puede representar como el valor cero. La instrucción if es una instrucción de una sola entrada/una sola salida. Pronto veremos que los diagramas de actividad para las instrucciones de control restantes también contienen estados iniciales, flechas de transición, estados de acción que indican las acciones a realizar, símbolos de decisión (con sus condiciones de guardia asociadas) que indican las decisiones a tomar, y estados finales. Imagine siete cajones, en donde cada uno contiene diagramas de actividad de UML vacíos de uno de los siete tipos de instrucciones de control. Su tarea es ensamblar un programa a partir de los diagramas de actividad de tantas instrucciones de control de cada tipo como lo requiera el algoritmo, combinando esas instrucciones de control en sólo dos formas posibles (apilando o anidando), y después llenando los estados de acción y las decisiones con expresiones de acción y condiciones de guardia, en una manera que sea apropiada para formar una implementación estructurada para el algoritmo. Seguiremos hablan- do sobre la variedad de formas en que pueden escribirse las acciones y las decisiones.
  • 144. 112 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– 4.6Instrucción de selección doble if…else La instrucción if de selección simple realiza una acción indicada solamente cuando la condición es verdadera (true); de no ser así, se evita dicha acción. La instrucción if...else de selección doble per- mite al programador especificar una acción a realizar cuando la condición es verdadera, y otra distinta cuando la condición es falsa (false). Por ejemplo, la instrucción en seudocódigo Si la calificación del estudiante es mayor o igual a 60 Imprimir “Aprobado” De lo contrario Imprimir “Reprobado” imprime “Aprobado” si la calificación del estudiante es mayor o igual a 60, e imprime “Reprobado” si la calificación del estudiante es menor a 60. En cualquier caso, después de que ocurre la impresión se “eje- cuta” la siguiente instrucción en seudocódigo en la secuencia. La instrucción anterior if...else en seudocódigo puede escribirse en C++ como if ( calificacion = 60 ) cout Aprobado; else cout Reprobado; El cuerpo de la instrucción else también tiene sangría. Buena práctica de programación 4.1 Si hay varios niveles de sangría, en cada nivel debe aplicarse la misma cantidad de espacio adicional para promover la legibilidad y facilidad de mantenimiento. La figura 4.5 muestra el flujo de control en la instrucción if...else. imprimir Aprobado imprimir Reprobado [calificacion = 60] [calificacion 60] Fig. 4.5  Diagrama de actividad de la instrucción if...else de selección doble. Operador condicional (?:) C++ cuenta con el operador condicional (?:), que está estrechamente relacionado con la instrucción if...else. Éste es el único operador ternario de C++: recibe tres operandos. En conjunto, los operan-
  • 145. 4.6 Instrucción de selección doble if...else 113 dos y el operador condicional forman una expresión condicional. El primer operando es una condi- ción, el segundo es el valor de la expresión condicional si la condición es true, y el tercero es el valor de toda la expresión condicional si la condición es false. Por ejemplo, la instrucción de salida cout ( calificacion = 60 ? Aprobado : Reprobado ); contiene una expresión condicional, calificacion = 60 ? Aprobado : Reprobado, que se evalúa como la cadena Reprobado si la condición calificacion = 60 es true, pero se evalúa como la ca- dena Reprobado si la condición es false. Por lo tanto, la instrucción con el operador condicional realiza en esencia la misma función que la instrucción if...else anterior. Como veremos más adelan- te, la precedencia del operador condicional es baja, por lo cual los paréntesis en la expresión anterior son obligatorios. Tip para prevenir errores 4.2 Para evitar problemas de precedencia (y por claridad), coloque las expresiones condicionales (que aparezcan en expresiones más grandes) entre paréntesis. Los valores en una expresión condicional también pueden ser acciones a ejecutar. Por ejemplo, la siguiente expresión condicional también imprime Aprobado o Reprobado: calificacion = 60 ? cout Aprobado : cout Reprobado; La expresión condicional anterior se lee como: “si calificacion es mayor o igual que 60, entonces cout Aprobado; en caso contrario, cout Reprobado”. Esto también puede compararse con la instrucción if...else anterior. Las expresiones condicionales pueden aparecer en algunas partes de los programas en las que no se pueden utilizar instrucciones if...else. Instrucciones if…else anidadas Las instrucciones if...else anidadas pueden evaluar varios casos, al colocar instrucciones de selec- ción if...else dentro de otras instrucciones if...else. Por ejemplo, la siguiente instrucción if...else en seudocódigo imprime A para las calificaciones de exámenes mayores o iguales a 90, B para las que están en el rango de 80 a 89, C en el rango de 70 a 79, D si están en el rango de 60 a 69 y F para todas las demás calificaciones: Si la calificación del estudiante es mayor o igual a 90 Imprimir “A” de lo contrario Si la calificación del estudiante es mayor o igual a 80 Imprimir “B” de lo contrario Si la calificación del estudiante es mayor o igual a 70 Imprimir “C” de lo contrario Si la calificación del estudiante es mayor o igual a 60 Imprimir “D” de lo contrario Imprimir “F”
  • 146. 114 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– Este seudocódigo puede escribirse en C++ como if ( calificacionEstudiante = 90 ) // 90 o más recibe una A cout A; else if ( calificacionEstudiante = 80 ) // 80 a 89 recibe una B cout B; else if ( calificacionEstudiante = 70 ) // 70 a 79 recibe C cout C; else if ( calificacionEstudiante = 60 ) // 60 a 69 recibe D cout D; else // menos de 60 recibe F cout F; Si calificacionEstudiante es mayor o igual a 90, las primeras cuatro condiciones serán true, pero sólo se ejecutará la instrucción después de la primera prueba. Después, el programa evita la parte else de la instrucción if...else más “externa”. La mayoría de los programadores escriben la instrucción if...else anterior así: if ( calificacionEstudiante = 90 ) // 90 o más recibe una A cout A; else if ( calificacionEstudiante = 80 ) // 80 a 89 recibe una B cout B; else if ( calificacionEstudiante = 70 ) // 70 a 79 recibe C cout C; else if ( calificacionEstudiante = 60 ) // 60 a 69 recibe D cout D; else // menos de 60 recibe F cout F; Las dos formas son idénticas, excepto por el espaciado y la sangría, que el compilador ignora. La segun- da forma es más popular, ya que evita usar mucha sangría hacia la derecha en el código lo cual puede forzar a que las líneas se dividan. Tip de rendimiento 4.1 Unainstrucciónif...else anidadapuedeejecutarseconmuchamásrapidezqueunaserie de instrucciones if de selección simple, debido a la posibilidad de salir antes de tiempo, una vez que se cumple una de las condiciones. Tip de rendimiento 4.2 En una instrucción if...else anidada, debemos evaluar las condiciones que tengan más probabilidades de ser true al principio de la instrucción anidada. Esto permite que la ins- trucción if...else anidada se ejecute con más rapidez, al salir antes de lo esperado si se evaluaran primero los casos que ocurren con menos frecuencia. Problema del else suelto El compilador de C++ siempre asocia un else con el if que le precede inmediatamente, a menos que se le indique otra cosa mediante la colocación de llaves ({ y }). Este comportamiento puede ocasionar lo que se conoce como el problema del else suelto. Por ejemplo, if ( x 5 ) if ( y 5 ) cout x e y son 5; else cout x es = 5;
  • 147. 4.6 Instrucción de selección doble if...else 115 parece indicar que si x es mayor que 5, la instrucción if anidada determina si y es también mayor que 5. De ser así, se produce como resultado la cadena x y y son 5. De lo contrario, parece ser que si x no es mayor que 5, la instrucción else que es parte del if...else produce como resultado la cadena x es = 5. ¡Cuidado! Esta instrucción if...else anidada no se ejecuta como parece ser. El compilador en realidad interpreta la instrucción así: if ( x 5 ) if ( y 5 ) cout x y y son 5; else cout x es = 5; en donde el cuerpo del primer if es un if...else anidado. La instrucción if más externa evalúa si x es mayor que 5. De ser así, la ejecución continúa evaluando si y es también mayor que 5. Si la segunda condición es verdadera, se muestra la cadena apropiada (x y y son 5). No obstante, si la segun- da condición es falsa se muestra la cadena x es = 5, aun y cuando sabemos que x es mayor que 5. Para forzar a que la instrucción if...else anidada se ejecute como se tenía pensado originalmen- te, podemos escribirla de la siguiente manera: if ( x 5 ) { if ( y 5 ) cout x y y son 5; } else cout x es = 5; Las llaves ({ }) indican al compilador que la segunda instrucción if se encuentra en el cuerpo del primer if, y que el else está asociado con el primer if. Los ejercicios 4.23 a 4.24 investigan con más detalle el problema del else suelto. Bloques La instrucción de selección if normalmente espera sólo una instrucción en su cuerpo. De manera similar, las partes if y else de una instrucción if...else esperan sólo una instrucción en su cuerpo. Para incluir variasinstruccionesenelcuerpodeunif oencualquierpartedeunif...else,encierrelasinstrucciones entre llaves ({ y }). A un conjunto de instrucciones contenidas dentro de un par de llaves se le llama ins- trucción compuesta o bloque. De aquí en adelante utilizaremos el término “bloque”. Observación de Ingeniería de Software 4.2 Unbloquepuedecolocarseencualquierpartedeunprogramaendondepuedacolocarseuna sola instrucción. El siguiente ejemplo incluye un bloque en la parte else de una instrucción if...else: if ( calificacionEstudiante = 60 ) cout Aprobado.n; else { cout Reprobado.n; cout Debe tomar este curso otra vez.n; }
  • 148. 116 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– En este caso, si calificacionEstudiante es menor que 60, el programa ejecuta ambas instrucciones en el cuerpo del else e imprimes Reprobado. Debe tomar este curso otra vez. Observe las llaves que rodean a las dos instrucciones en la cláusula else. Estas llaves son importantes. Sin ellas, la instrucción cout “Debe tomar este curso otra vez.n”; estaríafueradelcuerpodelaparteelse delainstrucciónif yseejecutaríasinimportarquelacalificación fuera menor a 60. Éste es un error lógico. Así como un bloque puede colocarse en cualquier parte en donde pueda colocarse una sola instruc- ción individual, también es posible no tener instrucción alguna; a ésta se le conoce como instrucción nula (o instrucción vacía). Para representar a la instrucción nula, se coloca un punto y coma (;) en donde normalmente iría una instrucción. Error común de programación 4.1 Colocar un punto y coma después de la condición en una instrucción if produce un error lógico en las instrucciones if de selección simple, y un error de sintaxis en las instrucciones if...else deseleccióndoble(cuandolapartedelif contieneunainstrucciónenelcuerpo). 4.7Instrucción de repetición while Una instrucción de repetición especifica que un programa debe repetir una acción mientras cierta condición sea verdadera. La instrucción en seudocódigo Mientras existan más artículos en mi lista de compras Comprar el siguiente artículo y quitarlo de mi lista describe la repetición que ocurre durante una salida de compras. La condición “existan más artículos en mi lista de compras” puede ser verdadera o falsa. Si es verdadera, entonces se realiza la acción “Comprar el siguiente artículo y quitarlo de mi lista”. Esta acción se realizará en forma repetida mientras la condi- ción sea verdadera. La instrucción contenida en la instrucción de repetición Mientras (while) constituye el cuerpo de esta estructura, el cual puede ser una sola instrucción o un bloque. En algún momento, la condición se hará falsa (cuando el último artículo de la lista de compras sea adquirido y eliminado de la lista). En este punto la repetición terminará y se ejecutará la primera instrucción que esté después de la instrucción de repetición. Como ejemplo de la instrucción de repetición while en C++, considere un segmento de programa diseñado para encontrar la primera potencia de 3 que sea mayor a 100. Suponga que la variable produc- to de tipo entero se inicializa en 3. Cuando la siguiente instrucción while termine de ejecutarse, pro- ducto contendrá el resultado: int producto = 3; while ( producto = 100 ) producto = 3 * producto; Cuando esta instrucción while comienza a ejecutarse, el valor de producto es 3. Cada repetición de la instrucción while multiplica a producto por 3, por lo que producto toma los valores de 9, 27, 81 y 243,
  • 149. 4.7 Instrucción de repetición while 117 sucesivamente. Cuando producto se vuelve 243, la condición de la instrucción while (producto = 100) se torna falsa. Esto termina la repetición, por lo que el valor final de producto es 243. En este punto, la ejecución del programa continúa con la siguiente instrucción después de while. Error común de programación 4.2 Un error lógico conocido como ciclo infinito, en el que la instrucción de repetición nunca termina, ocurre si no se proporciona una acción en el cuerpo de una instrucción while que ocasione que en algún momento la condición del while se torne falsa por lo general. Esto puede hacer que un programa parezca “quedar colgado” o “congelarse” si el cuerpo del ciclo no contiene instrucciones que interactúen con el usuario. El diagrama de actividad de UML de la figura 4.6 muestra el flujo de control que corresponde a la instrucción while anterior. Una vez más, los símbolos en el diagrama (aparte del estado inicial, las fle- chas de transición, un estado final y tres notas) representan un estado de acción y una decisión. Este diagrama también introduce el símbolo de fusión de UML, el cual une dos flujos de actividad en un solo flujo de actividad. UML representa tanto al símbolo de fusión como al símbolo de decisión como rombos. En este diagrama, el símbolo de fusión une las transiciones del estado inicial y del estado de acción, de manera que ambas fluyan en la decisión que determina si el ciclo debe empezar a ejecutarse (o seguir ejecutándose). Los símbolos de decisión y de fusión pueden diferenciarse por el número de flechas de transición “entrantes” y “salientes”. Un símbolo de decisión tiene una flecha de transición que apunta hacia el rombo y dos o más flechas que apuntan hacia afuera del rombo, para indicar las posibles transiciones desde ese punto. Además, cada flecha que apunta hacia fuera de un símbolo de decisión tiene una condición de guardia junto a ella. Un símbolo de fusión tiene dos o más flechas de transición que apuntan hacia el rombo, y sólo una que apunta hacia fuera del rombo, para indicar múl- tiples flujos de actividad que se fusionan para continuar la actividad. A diferencia del símbolo de deci- sión, el de fusión no tiene su contraparte en el código de C++. triplicar valor de producto Instrucción correspondiente en C++: producto = 3 * producto; decisión [producto = 100] [producto 100] fusión Fig. 4.6  Diagrama de actividad de UML de la instrucción de repetición while. El diagrama de la figura 4.6 muestra claramente la repetición de la instrucción while que vimos antes en esta sección. La flecha de transición que emerge del estado de acción apunta a la fusión, desde la cual regresa a la decisión que se evalúa en cada iteración del ciclo, hasta que la condición de guardia producto 100 se vuelva verdadera. Entonces, la instrucción while termina (llega a su estado final) y el control pasa a la siguiente instrucción en la secuencia del programa.
  • 150. 118 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– Tip de rendimiento 4.3 Una pequeña mejora en el rendimiento para el código que se ejecuta muchas veces en un ciclo puede producir una mejora considerable en el rendimiento en general. 4.8Cómo formular algoritmos: repetición controlada por un contador Parailustrarlaformaenquelosprogramadoresdesarrollanlosalgoritmos,enestasecciónyenlasiguien- te resolveremos dos variantes de un problema que promedia las calificaciones de una clase. Considere el siguiente enunciado del problema: A una clase de diez estudiantes se les aplicó un examen. Las calificaciones (de 0 a 100) para este examen están disponibles para que usted las analice. Calcule y muestre el total de las calificaciones de todos los estu- diantes y el promedio de la clase para este examen. El promedio de la clase es igual a la suma de las calificaciones, dividida entre el número de estudiantes. El algoritmo para resolver este problema en una computadora debe recibir como entrada cada una de las calificaciones, calcular el promedio e imprimir el resultado. Algoritmo de seudocódigo con repetición controlada por un contador Emplearemos seudocódigo para listar las acciones a ejecutar y especificar el orden en el que deben ocurrir. Usaremosunarepetición controlada por contadorparaintroducirlascalificaciones,unaporuna.Esta técnica utiliza una variable llamada contador para controlar el número de veces que debe ejecutarse un conjunto de instrucciones (a lo cual se le conoce también como el número de iteraciones del ciclo). A la repetición controlada por contador se le llama comúnmente repetición definida, ya que el número de repeticiones se conoce antes de que el ciclo empiece a ejecutarse. En este ejemplo, la repeti- ción termina cuando el contador excede a 10. Esta sección presenta un algoritmo de seudocódigo completamente desarrollado (figura 4.7), y una versión de la clase LibroCalificaciones (figuras 4.8 y 4.9) que implementa el algoritmo en una función miembro de C++. Después presentamos una aplica- ción (figura 4.10) que demuestra el algoritmo en acción. En la sección 4.9 demostraremos cómo utilizar el seudocódigo para desarrollar dicho algoritmo desde cero. Observación de Ingeniería de Software 4.3 La parte más difícil para la resolución de un problema en una computadora es desarrollar el algoritmo. El proceso de producir un programa funcional en C++ a partir de dicho algoritmo es, por lo general, relativamente sencillo. 1 Asignar a total el valor de cero 2 Asignar al contador de calificaciones el valor de uno 3 4 Mientras que el contador de calificaciones sea menor o igual a diez 5 Pedir al usuario que introduzca la siguiente calificación 6 Obtener como entrada la siguiente calificación 7 Sumar la calificación al total 8 Sumar uno al contador de calificaciones 9 10 Asignar al promedio de la clase el total dividido entre diez 11 Imprimir el total de las calificaciones para todos los estudiantes en la clase 12 Imprimir el promedio de la clase Fig. 4.7  Algoritmo en seudocódigo que utiliza la repetición controlada por contador para resolver el problema del promedio de una clase.
  • 151. 4.8 Cómo formular algoritmos: repetición controlada por un contador 119 Observe las referencias en el algoritmo de seudocódigo de la figura 4.7 para un total y un contador. Un total es una variable que se utiliza para acumular la suma de varios valores. Un contador es una variable que se utiliza para contar; en este caso, el contador de calificaciones indica cuál de las 10 califi- caciones está a punto de escribir el usuario. Por lo general, las variables que se utilizan para guardar to- tales deben inicializarse en cero antes de utilizarse en un programa; de lo contrario, la suma incluiría el valor anterior almacenado en la ubicación de memoria del total. Recuerde que en el capítulo 2 vimos que todas las variables deben inicializarse. Mejora a la validación de LibroCalificaciones Consideremos una mejora que hicimos a nuestra clase LibroCalificaciones. En la figura 3.16, la forma en que nuestra función miembro establecerNombreCurso validaría el nombre del curso sería probar primero si la longitud del mismo es menor o igual a 25 caracteres, mediante el uso de una ins- trucción if. Si esto fuera cierto, se establecería el nombre del curso. Después, a este código le seguiría otra instrucción if que evaluaría si la longitud del nombre del curso es mayor que 25 caracteres (en cuyo caso, el nombre del curso se reduciría). La condición de la segunda instrucción if es el opuesto exacto de la condición de la primera instrucción if. Si una condición se evalúa como true, la otra se debe evaluar como false. Dicha situación es ideal para una instrucción if...else, por lo que hemos modi- ficado nuestro código, reemplazando las dos instrucciones if por una instrucción if...else, como se muestra en las líneas 18 a 25 de la figura 4.9). Implementación de la repetición controlada por contador en la clase LibroCalificaciones La clase LibroCalificaciones (figuras 4.8 y 4.9) contiene un constructor (declarado en la línea 10 de la figura 4.8 y definido en las líneas 9 a 12 de la figura 4.9) que asigna un valor al miembro de datos nombreCurso (declarado en la línea 16 de la figura 4.8) de la clase. En las líneas 16 a 26, 29 a 32 y 35 a 39 de la figura 4.9 se definen las funciones miembro establecerNombreCurso, obtenerNombreCurso y mostrarMensaje, respectivamente. En las líneas 42 a 64 se define la función miembro determinarPro- medioClase, la cual implementa el algoritmo para sacar el promedio de la clase, descrito por el seudo- código de la figura 4.7. 1 // Fig. 4.8: LibroCalificaciones.h 2 // Definición de la clase LibroCalificaciones que determina el promedio de una clase. 3 // Las funciones miembro se definen en LibroCalificaciones.cpp 4 #include string // el programa usa la clase string estándar de C++ 5 6 // definición de la clase LibroCalificaciones 7 class LibroCalificaciones 8 { 9 public: 10 explicit LibroCalificaciones( std::string ); // inicializa el nombre del curso 11 void establecerNombreCurso( std::string ); // establece el nombre del curso 12 std::string obtenerNombreCurso() const; // obtener el nombre del curso 13 void mostrarMensaje() const; // muestra un mensaje de bienvenida 14 void determinarPromedioClase() const; // promedia las calificaciones escritas por el usuario 15 private: 16 std::string nombreCurso; // nombre del curso para este LibroCalificaciones 17 }; // fin de la clase LibroCalificaciones Fig. 4.8  Problema del promedio de una clase utilizando la repetición controlada por contador: encabezado de LibroCalificaciones.
  • 152. 120 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– 1 // Fig. 4.9: LibroCalificaciones.cpp 2 // Definiciones de funciones miembro para la clase LibroCalificaciones que resuelve 3 // el programa del promedio de la clase con repetición controlada por contador. 4 #include iostream 5 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 6 using namespace std; 7 8 // el constructor inicializa a nombreCurso con la cadena que se suministra como argumento 9 LibroCalificaciones::LibroCalificaciones( string nombre ) 10 { 11 establecerNombreCurso( nombre ); // valida y almacena nombreCurso 12 } // fin del constructor de LibroCalificaciones 13 14 // función para establecer el nombre del curso; 15 // asegura que el nombre del curso tenga cuando menos 25 caracteres 16 void LibroCalificaciones::establecerNombreCurso( string nombre ) 17 { 18 if ( nombre.size() = 25 ) // si nombre tiene 25 caracteres o menos 19 nombreCurso = nombre; // almacena el nombre del curso en el objeto 20 else // si nombre es mayor de 25 caracteres 21 { // establece nombreCurso a los primeros 25 caracteres del parámetro nombre 22 nombreCurso = nombre.substr( 0, 25 ); // selecciona los primeros 25 caracteres 23 cerr El nombre nombre excede la longitud maxima (25).n 24 Se limito nombreCurso a los primeros 25 caracteres.n endl; 25 } // fin de if...else 26 } // fin de la función establecerNombreCurso 27 28 // función para obtener el nombre del curso 29 string LibroCalificaciones::obtenerNombreCurso() const 30 { 31 return nombreCurso; 32 } // fin de la función obtenerNombreCurso 33 34 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones 35 void LibroCalificaciones::mostrarMensaje() const 36 { 37 cout Bienvenido al libro de calificaciones para n obtenerNombreCurso() !n 38 endl; 39 } // fin de la función mostrarMensaje 40 41 // determina el promedio de la clase, con base en las 10 calificaciones escritas por el usuario 42 void LibroCalificaciones::determinarPromedioClase() const 43 { 44 // fase de inicialización 45 int total = 0; // suma de las calificaciones introducidas por el usuario 46 unsigned int contadorCalif = 1; // número de la calificación a introducir a continuación 47 48 // fase de procesamiento 49 while ( contadorCalif = 10 ) // itera 10 veces 50 { 51 cout Escriba una calificacion: ; // pide la entrada Fig. 4.9  Problema del promedio de una clase utilizando la repetición controlada por contador: archivo de código fuente de LibroCalificaciones (parte 1 de 2).
  • 153. 4.8 Cómo formular algoritmos: repetición controlada por un contador 121 52 int calificacion = 0; // valor de la calificación introducida por el usuario 53 cin calificacion; // recibe como entrada la siguiente calificación 54 total = total + calificacion; // suma la calificación al total 55 contadorCalif = contadorCalif + 1; // incrementa el contador por 1 56 } // fin de while 57 58 // fase de terminación 59 int promedio = total / 10; // está bien mezclar la declaración con el cálculo 60 61 // muestra el total y el promedio de las calificaciones 62 cout nEl total de las 10 calificaciones es total endl; 63 cout El promedio de la clase es promedio endl; 64 } // fin de la función determinarPromedioClase Como la variable contadorCalif (figura 4.9, línea 46) se usa para contar de 1 a 10 en este programa (todos son valores positivos), declaramos la variable como unsigned int, que puede almacenar sólo valores no negativos (es decir, de 0 en adelante). Las variables locales total (figura 4.9, línea 45), cali- ficacion (línea 52) y promedio (línea 59) son de tipo int. La variable calificacion almacena la en- trada del usuario. Observe que las declaraciones anteriores aparecen en el cuerpo de la función miembro determinarPromedioClase. Además, la variable calificacion se declara en el cuerpo de la instrucción while debido a que se utiliza sólo en el ciclo; en general, hay que declarar las variables justo antes de que se utilicen. Inicializamos calificacion con 0 (línea 52) como una buena práctica, incluso aunque se introduzca de inmediato un nuevo valor para la calificación en la línea 53. Buena práctica de programación 4.2 Declare cada variable en una línea separada con su propio comentario, para mejorar la legibilidad. En las versiones de la clase LibroCalificaciones de este capítulo, simplemente leemos y procesa- mos un conjunto de calificaciones. El cálculo del promedio se realiza en la función miembro determi- narPromedioClase, usando variables locales; no preservamos información acerca de las calificaciones de los estudiantes en los miembros de datos de la clase. En el capítulo 7, modificaremos la clase Libro- Calificaciones para mantener las calificaciones en memoria, utilizando un miembro de datos que hace referencia a una estructura de datos conocida como arreglo. Esto permite que un objeto Libro- Calificaciones realice varios cálculos sobre un conjunto de calificaciones, sin requerir que el usuario escriba las calificaciones varias veces. En las líneas 45 y 46 se inicializan total a 0 y contadorCalif a 1 antes de usarse en los cálculos. Por lo general, las variables contador se inicializan con cero o uno, dependiendo de su uso en un algoritmo. Una variable no inicializada contiene un valor “basura” (también conocido como valor indefinido): el último valor almacenado en la ubicación de memoria reservada para esa variable. Tip para prevenir errores 4.3 Inicialice siempre las variables al momento de declararlas. Esto le ayudará a evitar los errores lógicos que ocurren al realizar cálculos con variables sin inicializar. Tip para prevenir errores 4.4 En algunos casos, los compiladores emiten una advertencia si usted intenta usar el valor de una variable sin inicializar. Siempre hay que obtener una compilación limpia, al resolver todos los errores y advertencias. Fig. 4.9  Problema del promedio de una clase utilizando la repetición controlada por contador: archivo de código fuente de LibroCalificaciones (parte 2 de 2).
  • 154. 122 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– La línea 49 indica que la instrucción while debe continuar ejecutando el ciclo (lo que también se conoce como iterar), siempre y cuando el valor de contadorCalif sea menor o igual a 10. Mientras esta condición sea verdadera, la instrucción while ejecutará en forma repetida las instrucciones entre las llaves que delimitan su cuerpo (líneas 49 a 56). En la línea 51 se muestra el indicador Escriba una calificacion:. Esta línea corresponde a la instrucción en seudocódigo “Pedir al usuario que introduzca la siguiente calificación”. En la línea 53 se lee la calificación escrita por el usuario y se asigna a la variable calificacion. Esta línea corresponde a la instruc- ción en seudocódigo “Obtener como entrada la siguiente calificación”. En la línea 54 se suma la nueva cali- ficacion escrita por el usuario al total, y se asigna el resultado a total, que sustituye su valor anterior. En la línea 55 se suma 1 a contadorCalif para indicar que el programa ha procesado la calificación actual y está listo para recibir la siguiente calificación del usuario. Al incrementar a contadorCalif en cada iteración, en un momento dado su valor excederá a 10. En ese momento, el ciclo while termina debido a que su condición (línea 49) se vuelve falsa. Cuando el ciclo termina, en la línea 59 se realiza el cálculo del promedio y se asigna su resultado a la variable promedio. En la línea 62 se muestra el texto El total de las 10 calificaciones es , segui- do del valor de la variable total. Después, en la línea 63 se muestra el texto El promedio de la clase es , seguido del valor de la variable promedio. A continuación, la función miembro determinarProme- dioClase devuelve el control a la función que hizo la llamada (es decir, a main en la figura 4.10). Demostración de la clase LibroCalificaciones La figura 4.10 contiene la función main de esta aplicación, la cual crea un objeto de la clase LibroCali- ficaciones y demuestra sus capacidades. En la línea 9 de la figura 4.10 se crea un nuevo objeto Libro- Calificaciones llamado miLibroCalificaciones. La cadena en la línea 9 se pasa al constructor de LibroCalificaciones (líneas 9 a 12 de la figura 4.9). En la línea 11 de la figura 4.10 se hace una llama- da a la función miembro mostrarMensaje de miLibroCalificaciones para mostrar un mensaje de bienvenida al usuario. Después, en la línea 12 se hace una llamada a la función miembro determinar- PromedioClase de miLibroCalificaciones para permitir que el usuario introduzca 10 calificaciones, para las cuales la función miembro posteriormente calcula e imprime el promedio; la función miembro ejecuta el algoritmo que se muestra en el seudocódigo de la figura 4.7. 1 // Fig. 4.10: fig04_10.cpp 2 // Crea un objeto LibroCalificaciones e invoca a su función determinarPromedioClase. 3 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 4 5 int main() 6 { 7 // crea un objeto LibroCalificaciones llamado miLibroCalificaciones y 8 // pasa el nombre del curso al constructor 9 LibroCalificaciones miLibroCalificaciones( CS101 Programacion en C++ ); 10 11 miLibroCalificaciones.mostrarMensaje(); // muestra el mensaje de bienvenida 12 miLibroCalificaciones.determinarPromedioClase(); // busca el promedio de 10 calificaciones 13 } // fin de main Bienvenido al libro de calificaciones para CS101 Programacion en C++ Fig. 4.10  Problema del promedio de una clase utilizando la repetición controlada por contador: creación de un objeto de la clase LibroCalificaciones (figuras 4.8 y 4.9) e invocación de su función miembro determinarPromedioClase (parte 1 de 2).
  • 155. 4.8 Cómo formular algoritmos: repetición controlada por un contador 123 Escriba una calificacion: 67 Escriba una calificacion: 78 Escriba una calificacion: 89 Escriba una calificacion: 67 Escriba una calificacion: 87 Escriba una calificacion: 98 Escriba una calificacion: 93 Escriba una calificacion: 85 Escriba una calificacion: 82 Escriba una calificacion: 100 El total de las 10 calificaciones es 846 El promedio de la clase es 84 Observaciones acerca de la división de enteros y el truncamiento El cálculo del promedio realizado en respuesta a la llamada a la función en la línea 12 de la figura 4.10, produce un resultado entero. La ejecución de ejemplo indica que la suma de los valores de las califica- ciones es 846, que al dividirse entre 10, debe producir 84.6; un número con un punto decimal. Sin embargo, el resultado del cálculo total / 10 (línea 59 de la figura 4.9) es el entero 84, ya que total y 10 son enteros. Al dividir dos enteros se produce una división entera: se trunca cualquier parte fraccio- naria del cálculo (es decir, se descarta). En la siguiente sección veremos cómo obtener un resultado que incluye un punto decimal a partir del cálculo del promedio. Error común de programación 4.3 Asumir que la división entera redondea (en vez de truncar) puede producir resultados erróneos. Por ejemplo, 7 ÷ 4 produce 1.75 en la aritmética convencional, pero trunca la parte de punto flotante (.75) en la aritmética entera. Por lo tanto, el resultado es 1. De manera similar, –7 ÷ 4 produce –1. En la figura 4.9, si en la línea 59 se utilizará contadorCalif en vez de 10, el resultado para este programa mostraría un valor incorrecto, 76. Esto ocurriría debido a que en la iteración final de la ins- trucción while, contadorCalif se incrementó al valor 11 en la línea 55. Error común de programación 4.4 El uso de una variable de control tipo contador de un ciclo en un cálculo después del ciclo produce un error lógico, conocido como error de desplazamiento en 1. En un ciclo con- trolado por contador que cuenta en uno cada vez que recorre el ciclo, éste termina cuando el valor del contador es uno más que su último valor legítimo (es decir, 11 en el caso de contar del 1 al 10). Observaciones sobre el desbordamiento aritmético En la figura 4.9, la línea 54 total = total + calificacion; // suma la calificación al total sumó cada calificacion introducida por el usuario al total. Incluso esta instrucción simple tiene un problema potencial: sumar los enteros podría producir un valor que sea demasiado grande como para almacenarlo en una variable int. Esto se conoce como desbordamiento aritmético y provoca un Fig. 4.10  Problema del promedio de una clase utilizando la repetición controlada por contador: creación de un objeto de la clase LibroCalificaciones (figuras 4.8 y 4.9) e invocación de su función miembro determinarPromedioClase (parte 2 de 2).
  • 156. 124 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– comportamiento indefinido, que puede producir resultados inesperados (en.wikipedia.org/wiki/ Integer_overflow#Secutiry-ramifications). El programa de suma de la figura 2.5 tiene el mismo problema en la línea 19, que calcula la suma de dos valores int introducidos por el usuario: suma = numero1 + numero2; // suma los números; almacena el resultado en suma Los valores máximo y mínimo que pueden almacenarse en una variable int se representan median- te las constantes INT_MAX e INT_MIN, respectivamente, que se definen en el encabezado climits. Hay constantes similares para los otros tipos enteros y para los tipos de punto flotante. Puede ver los valores de su plataforma para estas constantes si abre los encabezados climits y cfloat en un editor de texto (puede buscar estos archivos en su sistema de archivos). Se considera una buena práctica asegurarse de que, antes de realizar cálculos aritméticos como los de la línea 54 de la figura 4.9 y la línea 19 de la figura 2.5, no se desborden. El código para esto se mues- tra en el sitio Web de CERT www.securecoding.cert.org; sólo tiene que buscar el lineamiento “INT32-CPP”. El código utiliza los operadores (AND lógico) y || (OR lógico), que se introducen en el capítulo 5. En el código de calidad industrial, hay que realizar revisiones como éstas para todos los cálculos. Un análisis más detallado del proceso de recibir los datos de entrada del usuario Cada vez que un programa recibe la entrada del usuario, podrían ocurrir varios problemas. Por ejemplo, en la línea 53 de la figura 4.9 cin calificacion; // recibe como entrada la siguiente calificacion asumimos que el usuario introducirá una calificación entera en el rango de 0 a 100. Sin embargo, la persona que introduce una calificación podría introducir un entero menor a 0, un entero mayor a 100, un entero fuera del rango de valores que pueden almacenarse en una variable int, un número que contenga un punto decimal o un valor que contenga letras o símbolos especiales que ni siquiera sea un entero. Para asegurar que la entrada del usuario sea válida, los programas de calidad industrial deben probar todos los casos erróneos posibles. A medida que avance por el libro, aprenderá diversas técnicas para li- diar con el amplio rango de posibles problemas de entrada. 4.9Cómo formular algoritmos: repetición controlada por un centinela Generalicemos el problema para los promedios de una clase. Considere el siguiente problema: Desarrollar un programa que calcule el promedio de una clase y que procese las calificaciones para un número arbitrario de estudiantes cada vez que se ejecute. En el ejemplo anterior, el enunciado del problema especificó el número de estudiantes, por lo que se conocía el número de calificaciones (10) por adelantado. En este ejemplo no se indica cuántas califica- ciones va a introducir el usuario durante la ejecución del programa. El programa debe procesar un nú- mero arbitrario de calificaciones. ¿Cómo puede el programa determinar cuándo terminar de introducir calificaciones? ¿Cómo sabrá cuándo calcular e imprimir el promedio de la clase? Para resolver este problema podemos utilizar un valor especial denominado valor centinela (tam- biénllamadovalordeseñal,valordepruebaovalordebandera)paraindicarel“findelaintroducción de datos”. Después de escribir las calificaciones que desea, el usuario escribe el valor centinela para indi- car que se introdujo la última calificación. A la repetición controlada por centinela a menudo se le llama repetición indefinida, ya que el número de repeticiones no se conoce antes de que comience la ejecu- ción del ciclo.
  • 157. 4.9 Cómo formular algoritmos: repetición controlada por un centinela 125 Debe elegirse un valor centinela de tal forma que no pueda confundirse con un valor de entrada permitido. Por lo general, las calificaciones de un examen son enteros positivos, por lo que –1 es un valor centinela aceptable para este problema. Por lo tanto, una ejecución del programa podría procesar una cadena de entradas como 95, 96, 75, 74, 89 y –1. El programa entonces calcularía e imprimiría el promedio de la clase para las calificaciones 95, 96, 75, 74 y 89. Como –1 es el valor centinela, no debe entrar en el cálculo del promedio. Desarrollo del algoritmo en seudocódigo con el método de mejoramiento de arriba a abajo, paso a paso: la primera mejora (cima) Vamos a desarrollar el programa para promediar clases con una técnica llamada mejoramiento de arri- ba a abajo, paso a paso, la cual es esencial para el desarrollo de programas bien estructurados. Comen- zamos con una representación en seudocódigo de la cima, una sola instrucción que transmite la función del programa en general: Determinar el promedio de la clase para el examen, para un número arbitrario de estudiantes La cima es, en efecto, la representación completa de un programa. Desafortunadamente, la cima (como en este caso) pocas veces transmite los detalles suficientes como para escribir un programa. Por lo tanto, ahora comenzaremos el proceso de mejora. Dividiremos la cima en una serie de tareas más pequeñas y las enlistaremos en el orden en el que se van a realizar. Esto arroja como resultado la siguiente primera mejora: Inicializar variables Introducir, sumar y contar las calificaciones del examen Calcular e imprimir el total de las calificaciones de todos los estudiantes y el promedio de la clase Esta mejora utiliza sólo la estructura de secuencia; estos pasos se ejecutan en orden. Observación de Ingeniería de Software 4.4 Cada mejora, así como la cima en sí, es una especificación completa del algoritmo; sólo varía el nivel del detalle. Observación de Ingeniería de Software 4.5 Muchos programas pueden dividirse lógicamente en tres fases: una fase de inicialización en la que se inicializan las variables del programa; una fase de procesamiento en la que se introducen los valores de los datos y se ajustan las variables del programa (como conta- dores y totales) según sea necesario; y una fase de terminación, que calcula y produce los resultados finales. Cómo proceder a la segunda mejora La anterior Observación de Ingeniería de Software es a menudo todo lo que usted necesita para la prime- ra mejora en el proceso de arriba a abajo. En la segunda mejora, nos comprometemos a usar variables específicas. En este ejemplo necesitamos el total actual de los números, una cuenta de cuántos números se han procesado, una variable para recibir el valor de cada calificación, a medida que el usuario las vaya introduciendo, y una variable para almacenar el promedio calculado. La instrucción en seudocódigo Inicializar las variables puede mejorarse como sigue: Inicializar total en cero Inicializar contador en cero
  • 158. 126 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– La instrucción en seudocódigo Introducir, sumar y contar las calificaciones del examen requiere una estructura de repetición (es decir, un ciclo) que introduzca cada calificación en forma su- cesiva. No sabemos de antemano cuántas calificaciones van a procesarse, por lo que utilizaremos la re- petición controlada por centinela. El usuario introduce las calificaciones una por una. Después de introducir la última calificación, introduce el valor centinela. El programa evalúa el valor centinela después de la introducción de cada calificación, y termina el ciclo cuando el usuario introduce el valor centinela. Entonces, la segunda mejora de la instrucción anterior en seudocódigo sería Pedir al usuario que introduzca la primera calificación Recibir como entrada la primera calificación (puede ser el centinela) While (mientras) el usuario no haya introducido aún el centinelal Sumar esta calificación al total actual Sumar uno al contador de calificaciones Pedir al usuario que introduzca la siguiente calificación Recibir como entrada la siguiente calificación (puede ser el centinela) En seudocódigo no utilizamos llaves alrededor de las instrucciones que forman el cuerpo de la estructu- ra While. Simplemente aplicamos sangría a las instrucciones bajo el Mientras para mostrar que pertene- cenaestainstrucción.Denuevo,elseudocódigoessolamenteunaherramientainformalparadesarrollar programas. La instrucción en seudocódigo Calcular e imprimir el total de las calificaciones de todos los estudiantes y el promedio de la clase puede redefinirse de la siguiente manera: Si el contador no es igual a cero Asignar al promedio el total dividido entre el contador Imprimir el total de las calificaciones para todos los estudiantes en la clase Imprimir el promedio de la clase de lo contrario Imprimir “No se introdujeron calificaciones” Evaluamos la posibilidad de una división entre cero; por lo general esto es un error lógico fatal que, si no se detecta, haría que el programa fallara (a lo que a menudo se le conoce como “crashing”). La se- gunda mejora completa del seudocódigo para el problema del promedio de una clase se muestra en la figura 4.11. Error común de programación 4.5 Un intento de dividir entre cero produce un comportamiento indefinido y generalmente un error fatal en tiempo de ejecución. 1 Inicializar total en cero 2 Inicializar contador en cero 3 4 Pedir al usuario que introduzca la primera calificación 5 Recibir como entrada la primera calificación (puede ser el centinela) Fig. 4.11  Algoritmo en seudocódigo del problema para promediar una clase, con una repetición controlada por centinela (parte 1 de 2).
  • 159. 4.9 Cómo formular algoritmos: repetición controlada por un centinela 127 6 7 Mientras el usuario no haya introducido aún el centinela 8 Sumar esta calificación al total actual 9 Sumar uno al contador de calificaciones 10 Pedir al usuario que introduzca la siguiente calificación 11 Recibir como entrada la siguiente calificación (puede ser el centinela) 12 13 Si el contador no es igual a cero 14 Asignar al promedio el total dividido entre el contador 15 Imprimir el total de las calificaciones para todos los estudiantes de la clase 16 Imprimir el promedio de la clase 17 de lo contrario 18 Imprimir “No se introdujeron calificaciones” Tip para prevenir errores 4.5 Al realizar una división entre una expresión cuyo valor pudiera ser cero, debemos evaluar explícitamente esta posibilidad y manejarla de manera apropiada en el programa (como imprimir un mensaje de error), en vez de permitir que ocurra el error fatal. Hablaremos más sobre cómo lidiar con estos tipos de errores cuando veamos el manejo de excepciones (capítulos 7, 9 y 17 este último en el sitio web). El seudocódigo en la figura 4.11 resuelve el problema más general para promediar una clase. Este algo- ritmo sólo requirió dos niveles de mejoramiento. En ocasiones, se requieren más niveles de mejoramiento. Observación de Ingeniería de Software 4.6 Termine el proceso de mejoramiento de arriba a abajo, paso a paso, cuando haya especificado el algoritmo en seudocódigo con el detalle suficiente como para poder convertir el seudocódigo en C++. Por lo general, la implementación del programa en C++ después de esto es mucho más sencilla. Observación de Ingeniería de Software 4.7 Muchos programadores experimentados escriben programas sin utilizar herramientas de desarrollo de programas como el seudocódigo. Estos programadores sienten que su meta final es resolver el problema en una computadora y que usar herramientas de desarrollo de programas como el seudocódigo simplemen- te retarda la producción de los resultados finales. Aunque este método pudiera funcionar para problemas sencillos y conocidos, tiende a ocasionar graves errores y retrasos en proyectos grandes y complejos. Implementación de la repetición controlada por centinela en la clase LibroCalificaciones En las figuras 4.12 y 4.13 se muestra la clase LibroCalificaciones que contiene la función miembro determinarPromedioClase, la cual implementa el algoritmo en seudocódigo de la figura 4.11 (esta clase se demuestra en la figura 4.14). Aunque cada calificación introducida es un valor entero, existe la proba- bilidad de que el cálculo del promedio produzca un número con un punto decimal; en otras palabras, un número real o número de punto flotante (por ejemplo, 7.33, 0.0975 o 1000.12345). El tipo int no puede representar un número de este tipo, por lo que esta clase debe usar otro tipo para hacerlo. C++ proporciona varios tipos de datos para almacenar números de punto flotante en la memoria, incluyendo float y double. La principal diferencia entre estos tipos es que, en comparación con las variables float, las variables double pueden almacenar comúnmente números con una mayor magnitud y un detalle más fino (es decir, más dígitos a la derecha del punto decimal; a esto se le conoce también como la precisión del número). Este programa introduce un operador especial llamado operador de conversión, para forzar a que el cálculo del promedio produzca un resultado numérico de punto flotante. Fig. 4.11  Algoritmo en seudocódigo del problema para promediar una clase, con una repetición controlada por centinela (parte 2 de 2).
  • 160. 128 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– 1 // Fig. 4.12: LibroCalificaciones.h 2 // Definición de la clase LibroCalificaciones que determina el promedio de una clase. 3 // Las funciones miembro se definen en LibroCalificaciones.cpp 4 #include string // el programa usa la clase string estándar de C++ 5 6 // definición de la clase LibroCalificaciones 7 class LibroCalificaciones 8 { 9 public: 10 explicit LibroCalificaciones( std::string ); // inicializa el nombre del curso 11 void establecerNombreCurso( std::string ); // establece el nombre del curso 12 std::string obtenerNombreCurso() const; // obtiene el nombre del curso 13 void mostrarMensaje() const; // muestra un mensaje de bienvenida 14 void determinarPromedioClase() const; // promedia las calificaciones escritas por el usuarios 15 private: 16 std::string nombreCurso; // nombre del curso para este LibroCalificaciones 17 }; // fin de la clase LibroCalificaciones Fig. 4.12  Problema del promedio de una clase utilizando la repetición controlada por centinela: encabezado de LibroCalificaciones. 1 // Fig. 4.13: LibroCalificaciones.cpp 2 // Definiciones de funciones miembro para la clase LibroCalificaciones que resuelve 3 // el programa del promedio de la clase con repetición controlada por centinela. 4 #include iostream 5 #include iomanip // manipuladores de flujo parametrizados 6 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 7 using namespace std; 8 9 // el constructor inicializa a nombreCurso con la cadena que se suministra como argumento 10 LibroCalificaciones::LibroCalificaciones( string nombre ) 11 { 12 establecerNombreCurso( nombre ); // valida y almacena nombreCurso 13 } // fin del constructor de LibroCalificaciones 14 15 // función para establecer el nombre del curso; 16 // asegura que el nombre del curso tenga cuando mucho 25 caracteres 17 void LibroCalificaciones::establecerNombreCurso( string nombre ) 18 { 19 if ( nombre.size() = 25 ) // si el nombre tiene 25 caracteres o menos 20 nombreCurso = nombre; // almacena el nombre del curso en el objeto 21 else // si el nombre es mayor de 25 caracteres 22 { // establece nombreCurso a los primeros 25 caracteres del parámetro nombre 23 nombreCurso = nombre.substr( 0, 25 ); // selecciona los primeros 25 caracteres 24 cerr El nombre nombre excede la longitud maxima (25).n 25 Se limito nombreCurso a los primeros 25 caracteres.n endl; 26 } // fin de if...else 27 } // fin de la función establecerNombreCurso Fig. 4.13  Problema del promedio de una clase utilizando la repetición controlada por centinela: archivo de código fuente de LibroCalificaciones (parte 1 de 3).
  • 161. 4.9 Cómo formular algoritmos: repetición controlada por un centinela 129 28 29 // función para obtener el nombre del curso 30 string LibroCalificaciones::obtenerNombreCurso() const 31 { 32 return nombreCurso; 33 } // fin de la función obtenerNombreCurso 34 35 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones 36 void LibroCalificaciones::mostrarMensaje() const 37 { 38 cout Bienvenido al libro de calificaciones paran obtenerNombreCurso() !n 39 endl; 40 } // fin de la función mostrarMensaje 41 42 // determina el promedio de la clase con base en las 10 calificaciones escritas por el usuario 43 void LibroCalificaciones::determinarPromedioClase() const 44 { 45 // fase de inicialización 46 int total = 0; // suma de las calificaciones introducidas por el usuario 47 unsigned int contadorCalif = 0; // número de calificaciones introducidas 48 49 // fase de procesamiento 50 // pide la entrada y lee la calificación del usuario 51 cout Escriba la calificacion o -1 para salir: ; 52 int calificacion = 0; // valor de la calificación 53 cin calificacion; // recibe como entrada la calificacion o el valor centinela 54 55 // itera hasta leer el valor centinela del usuario 56 while ( calificacion != -1 ) // mientras calificacion no sea -1 57 { 58 total = total + calificacion; // suma la calificacion al total 59 contadorCalif = contadorCalif + 1; // incrementa el contador 60 61 // pide la entrada y lee la siguiente calificación del usuario 62 cout Escriba la calificacion o -1 para salir: ; 63 cin calificacion; // recibe como entrada la calificacion o el valor centinela 64 } // fin de while 65 66 // fase de terminación 67 if ( contadorCalif != 0 ) // si el usuario introdujo al menos una calificacion... 68 { 69 // calcula el promedio de todas las calificaciones introducidas 70 double promedio = static_cast double ( total ) / contadorCalif; 71 72 // muestra el total y el promedio (con dos dígitos de precisión) 73 cout nEl total de las contadorCalif calificaciones introducidas es 74 total endl; 75 cout setprecision( 2 ) fixed; 76 cout El promedio de la clase es promedio endl; 77 } // fin de if Fig. 4.13  Problema del promedio de una clase utilizando la repetición controlada por centinela: archivo de código fuente de LibroCalificaciones (parte 2 de 3).
  • 162. 130 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– 78 else // no se introdujeron calificaciones, por lo que imprime el mensaje apropiado 79 cout No se introdujeron calificaciones endl; 80 } // fin de la función determinarPromedioClase 1 // Fig. 4.14: fig04_14.cpp 2 // Crea un objeto LibroCalificaciones e invoca a su función determinarPromedioClase. 3 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 4 5 int main() 6 { 7 // crea el objeto LibroCalificaciones llamado miLibroCalificaciones y 8 // pasa el nombre del curso al constructor 9 LibroCalificaciones miLibroCalificaciones( CS101 Programacion en C++ ); 10 11 miLibroCalificaciones.mostrarMensaje(); // muestra el mensaje de bienvenida 12 miLibroCalificaciones.determinarPromedioClase(); // busca el promedio de 10 calificaciones 13 } // fin de main Bienvenido al libro de calificaciones para CS101 Programacion en C++! Escriba la calificacion o -1 para salir: 97 Escriba la calificacion o -1 para salir: 88 Escriba la calificacion o -1 para salir: 72 Escriba la calificacion o -1 para salir: -1 El total de las 3 calificaciones introducidas es 257 El promedio de la clase es 85.67 Fig. 4.14  Problema del promedio de una clase utilizando la repetición controlada por centinela: creación de un objeto de la clase LibroCalificaciones e invocación de su función miembro determinarPromedioClase. En este ejemplo vemos que las estructuras de control pueden apilarse una encima de otra; la ins- trucción while (líneas 56 a 64 de la figura 4.13) va seguida por una instrucción if...else (líneas 67 a 79) en secuencia. La mayor parte del código en este programa es idéntico al código de la figura 4.9, por lo que nos concentraremos en las nuevas características y conceptos. Las líneas 46 y 47 inicializan las variables total y contadorCalif en 0, ya que no se han introdu- cido calificaciones todavía. Recuerde que este programa utiliza la repetición controlada por centinela. Para mantener un registro preciso del número de calificaciones introducidas, el programa incrementa contadorCalif sólo cuando el usuario introduce un valor para la calificación que no sea el valor centi- nela y el programa completa el procesamiento de la calificación. Declaramos e inicializamos las variables calificacion (línea 52) y promedio (línea 70) en donde se utilizan. Cabe mencionar que la línea 70 declara la variable promedio como de tipo double. Recuerde que usamos una variable int en el ejemplo anterior para almacenar el promedio de la clase. Al usar el tipo double en el ejemplo anterior podemos almacenar el resultado del cálculo del promedio de la clase como un número de punto flotante. Por úl- timo, observe que antes de ambas instrucciones (líneas 53 y 63) se coloca una instrucción de salida que pide al usuario los datos de entrada. Fig. 4.13  Problema del promedio de una clase utilizando la repetición controlada por centinela: archivo de código fuente de LibroCalificaciones (parte 3 de 3).
  • 163. 4.9 Cómo formular algoritmos: repetición controlada por un centinela 131 Buena práctica de programación 4.3 Pida al usuario cada dato de entrada del teclado. El indicador debe indicar la forma de la entrada y cualquier valor de entrada especial. En un ciclo controlado por centinela, los indicadores que solicitan datos de entrada deben recordar explícitamente al usuario cuál es el valor centinela. Comparación entre la lógica del programa para la repetición controlada por centinela y la repetición controlada por contador Compare la lógica del programa para la repetición controlada por centinela con la repetición contro- lada por contador en la figura 4.9. En la repetición controlada por contador, cada iteración de la ins- trucción while (líneas 49 a 56 de la figura 4.9) lee un valor del usuario, para el número especificado de iteraciones. En la repetición controlada por centinela, el programa lee el primer valor (líneas 51 a 53 de la figura 4.13) antes de llegar al while. Este valor determina si el flujo de control del programa debe entrar al cuerpo del while. Si la condición es falsa, el usuario introdujo el valor centinela, por lo que el cuerpo no se ejecuta (es decir, no se introdujeron calificaciones). Si, por otro lado, la condición es verdadera, el cuerpo comienza a ejecutarse y el ciclo suma el valor de calificacion al total (línea 58) e incrementa contadorCalif (línea 59). Después, en las líneas 62 y 63 en el cuerpo del ciclo se pide y recibe el siguiente valor del usuario. A continuación, el control del programa se acerca a la llave de- recha de terminación (}) del cuerpo del while en la línea 64, por lo que la ejecución continúa con la evaluación de la condición del while (línea 56). La condición utiliza el valor más reciente de califi- cacion que acaba de introducir el usuario, para determinar si el cuerpo de la instrucción debe ejecu- tarse otra vez. El valor de la variable calificacion siempre lo introduce el usuario inmediatamente antes de que el programa evalúe la condición del while. Esto permite al programa determinar si el valor que acaba de introducir el usuario es el valor centinela, antes de que el programa procese ese valor (es decir, que lo sume al total e incremente contadorCalif). Si el valor introducido es el valor centi- nela, el ciclo termina y el programa no suma el valor –1 al total. Una vez que termina el ciclo, se ejecuta la instrucción if...else (líneas 67 a 79). La condición en la línea 67 determina si se introdujeron calificaciones o no. Si no se introdujo ninguna, se ejecuta la parte del else (líneas 78 y 89) de la instrucción if...else y muestra el mensaje “No se introdujeron calificaciones”, y la función miembro devuelve el control a la función que la llamó. Observe el bloque de la instrucción while en la figura 4.13. Sin las llaves, las últimas tres instruc- ciones en el cuerpo del ciclo quedarían fuera de éste, ocasionando que la computadora interpretara el código incorrectamente, como se muestra a continuación: // itera hasta leer el valor centinela del usuario while ( calificacion != -1 ) total = total + calificacion; // suma calificacion al total contadorCalif = contadorCalif + 1; // incrementa el contador // pide la entrada y lee la siguiente calificación del usuario cout Escriba calificacion o -1 para salir: ; cin calificacion; El código anterior ocasionaría un ciclo infinito en el programa si el usuario no introduce el centinela –1 para la primera calificación (en línea 53). Error común de programación 4.6 Omitir las llaves que delimitan a un bloque puede provocar errores lógicos, como ciclos infinitos. Para prevenir este problema, algunos programadores encierran el cuerpo de todas las instrucciones de control con llaves, aun si el cuerpo sólo contiene una instrucción.
  • 164. 132 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– Precisión de los números de punto flotante y requerimientos de memoria Las variables de tipo float representan números de punto flotante con precisión simple y tienen alrededor de siete dígitos significativos en la mayoría de los sistemas actuales. Las variables de tipo dou- ble representan números de punto flotante con precisión doble. Estos requieren el doble de memo- ria que las variables float y pueden proporcionar alrededor de 15 dígitos significativos en la mayoría de los sistemas actuales; aproximadamente el doble de precisión que las variables float. La mayoría de los programadores representan a los números de punto flotante con el tipo double. De hecho, C++ consi- dera a todos los números de punto flotante que escribimos en el código fuente de un programa (como 7.33 y 0.0975) como valores double de manera predeterminada. Dichos valores en el código fuente se conocen como constantes de punto flotante. En el apéndice C,Tipos fundamentales, podrá consultar los rangos de valores para los números float y double. En la aritmética convencional, los números de punto flotante surgen con frecuencia como resulta- do de la división; cuando dividimos 10 entre 3, el resultado es 3.3333333..., donde la secuencia de números 3 se repite en forma infinita. La computadora asigna sólo una cantidad fija de espacio para guardar ese valor, por lo que evidentemente el valor de punto flotante guardado almacenado sólo puede ser una aproximación. Error común de programación 4.7 Usar números de punto flotante de una manera que suponga que se representan con pre- cisión de manera exacta (por ejemplo, usándolos en las comparaciones de igualdad) puede producir resultados imprecisos. Los números de punto flotante se representan sólo en forma aproximada. Aunque los números de punto flotante no siempre son 100 por ciento precisos, tienen numerosas aplicaciones. Por ejemplo, cuando hablamos de una temperatura corporal “normal” de 36.7 grados centígrados, no necesitamos tener una precisión con una gran cantidad de dígitos. Cuando leemos la temperatura en un termómetro como 36.7, en realidad podría ser 36.6999473210643. Considerar a este número simplemente como 36.7 está bien para la mayoría de las aplicaciones en las que se utilizan temperaturas corporales. Debido a la naturaleza imprecisa de los números de punto flotante, se prefiere el tipo double al tipo float, ya que las variables double pueden representar números de punto flotante con más precisión. Por esta razón, usamos el tipo double en el resto del libro. Conversión explícita e implícita entre los tipos fundamentales La variable promedio se declara como de tipo double (línea 70 de la figura 4.13) para capturar el resul- tado fraccionario de nuestro cálculo. Sin embargo, total y contadorCalif son variables enteras. Re- cuerde que al dividir dos enteros se produce una división entera, en la cual cualquier parte fraccionaria del cálculo se trunca. En la siguiente instrucción: double promedio = total / contadorCalif; primero se realiza el cálculo de la división, por lo que se pierde la parte fraccionaria del resultado antes de asignarlo a promedio. Para realizar un cálculo de punto flotante con valores enteros, debemos crear va- lores temporales de punto flotante. C++ cuenta con el operador static_cast para llevar a cabo esta tarea. En la línea 70 se utiliza el operador de conversión de tipo static_castdouble(total) para crear una copia de punto flotante temporal de su operando entre paréntesis: total. Utilizar un operador de conversión de tipo de esta forma es un proceso que se denomina conversión explícita. El valor al- macenado en total sigue siendo un entero. El cálculo ahora consiste de un valor de punto flotante (la versión temporal double de total) divi- dido entre el entero contadorCalif. El compilador sabe cómo evaluar sólo expresiones en las que los tipos de datos de los operandos sean idénticos. Para asegurar que los operandos sean del mismo tipo, el
  • 165. 4.9 Cómo formular algoritmos: repetición controlada por un centinela 133 compilador realiza una operación llamada promoción (o conversión implícita) en los operandos se- leccionados.Porejemplo,enunaexpresiónquecontengavaloresdelostiposdedatosint ydouble,C++ promueve los operandos int a valores double. En nuestro ejemplo, tratamos a total como double (mediante el operador static_cast), por lo que el compilador promueve el valor de contadorCalif al tipo double, con lo cual permite realizar el cálculo; el resultado de la división de punto flotante se asigna a promedio. En el capítulo 6, Funciones y una introducción a la recursividad, hablaremos sobre todos los tipos de datos fundamentales y su orden de promoción. Los operadores de conversión de tipo están disponibles para usarse con cualquier tipo de datos, además de los tipos de clases. El operador static_cast se forma colocando la palabra clave static_ cast entre los signos y , alrededor del nombre de un tipo de datos. El operador static_cast es un operador unario; un operador que toma sólo un operando. En el capítulo 2 estudiamos los operadores aritméticos binarios. C++ también soporta las versiones unarias de los operadores de suma (+) y resta (–), por lo que el programador puede escribir expresiones como –7 o +5. Los operadores de conversión de tipo tienen mayor precedencia que los demás operadores unarios, como el + unario y el – unario. Esta precedencia es un nivel mayor que la de los operadores de multiplicación *, / y %, y menor que la de los paréntesis. En nuestras tablas de precedencia, indicamos el operador de conversión de tipos con la notación static_casttipo(). Formato para los números de punto flotante Aquí veremos brevemente las herramientas de formato en la figura 4.13, y las explicaremos con detalle en el capítulo 13, Entrada/salida de flujos: un análisis detallado. La llamada a setprecision en la línea 75 (con un argumento de 2) indica que la variable double llamada promedio debe imprimirse con dos dígitos de precisión a la derecha del punto decimal (por ejemplo, 92.37). A esta llamada se le conoce como manipulador de flujo parametrizado (debido al 2 entre paréntesis). Los programas que utilizan estas llamadas deben contener la directiva del preprocesador (línea 5): #include iomanip El manipulador endl es un manipulador de flujos no parametrizado (ya que no va seguido de un valor o expresión entre paréntesis), por lo cual no requiere el encabezado iomanip. Si no se especifica la precisión, los valores de punto flotante se imprimen generalmente con seis dígitos de precisión (es decir, la precisión predeterminada en la mayoría de los sistemas actuales), aunque en un momento veremos una excepción a esto. El manipulador de flujo fixed (línea 75) indica que los valores de punto flotante deben imprimir- se en lo que se denomina formato de punto fijo, en oposición a la notación científica. La notación científica es una forma de mostrar un número como valor de punto flotante entre los valores de 1.0 y 10.0, multiplicado por una potencia de 10. Por ejemplo, el valor 3100.0 se mostraría en notación cien- tífica como 3.1  103 . La notación científica es útil cuando se muestran valores muy grandes o muy pequeños. En el capítulo 13 hablaremos sobre el formato mediante el uso de la notación científica. Por otra parte, el formato de punto fijo se utiliza para forzar a que un número de punto flotante muestre un número específico de dígitos. Al especificar el formato de punto fijo también forzamos a que se imprima el punto decimal y los ceros a la derecha, aun si el valor es una cantidad entera, como 88.00. Sin la opción de formato de punto fijo, dicho valor se imprime en C++ como 88, sin los ceros a la derecha ni el punto decimal. Al utilizar los manipuladores de flujos fixed y setprecision en un programa, el valor impre- so se redondea al número de posiciones decimales indicado por el valor que se pasa a setprecision (por ejemplo, el valor 2 en la línea 75), aunque el valor en memoria permanece sin cambios. Por ejemplo, los valores 87.946 y 67.543 se imprimen como 87.95 y 67.54, respectivamente.También es posible forzar a que aparezca un punto decimal mediante el uso del manipulador de flujos showpoint. Si se especifica showpoint sin fixed, entonces no se imprimirán ceros a la derecha. Al igual que endl, los manipulado- res fixed y showpoint no usan parámetros y no requieren el encabezado iomanip. Ambos se encuen- tran en el encabezado iostream.
  • 166. 134 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– En las líneas 75 y 76 de la figura 4.13 se imprime el promedio de la clase redondeado a la centésima más cercana, y lo imprimimos con sólo dos dígitos a la derecha del punto decimal. El manipulador de flujos parametrizado (línea 75) indica que el valor de la variable promedio se debe mostrar con dos dígitos de precisión a la derecha del punto decimal; esto se indica mediante setprecision(2). Las tres calificaciones introducidas durante la ejecución de ejemplo del programa de la figura 4.14 dan un total de 257, que a su vez produce el promedio 85.666… y se imprime con redondeo como 85.67. Una nota sobre los enteros sin signo En la línea 46 de la figura 4.9, se declaró la variable contadorCalif como unsigned int debido a que sólo puede asumir los valores de 1 a 11 (el 11 termina el ciclo), que son todos valores positivos. En ge- neral, los contadores que deben almacenar sólo valores no negativos deberían declararse con tipos unsigned. Las variables de tipos enteros unsigned pueden representar valores desde 0 hasta aproxima- damente el doble del rango positivo de los tipos enteros con signo correspondientes. Puede determinar el valor unsigned int máximo de su plataforma con la constante UINT_MAX de climits. La figura 4.9 podría haber declarado también como unsigned int las variables calificacion, total y promedio. Por lo general, las calificaciones son valores de 0 a 100, por lo que total y promedio deberían ser cada uno mayor o igual a 0. Declaramos esas variables como int debido a que no podemos controlar lo que el usuario introduzca en realidad; incluso podría introducir valores negativos. Peor aún, el usuario podría introducir un valor que ni siquiera sea un número (más adelante en el libro le mostra- remos cómo lidiar con dichas entradas erróneas). Algunas veces los ciclos controlados por centinela usan valores inválidos de manera intencional para terminar un ciclo. Por ejemplo, en la línea 56 de la figura 4.13 terminamos el ciclo cuando el usuario introduce el valor centinela -1 (una calificación inválida), por lo que sería inapropiado declarar la varia- ble calificacion como unsigned int. Como veremos más adelante, el indicador de fin de archivo (EOF) (que presentaremos en el siguiente capítulo y que se utiliza a menudo para terminar ciclos contro- lados por centinela) también se implementa de manera interna en el compilador como un número ne- gativo. 4.10 Cómo formular algoritmos: instrucciones de control anidadas En el siguiente ejemplo formularemos una vez más un algoritmo utilizando seudocódigo y el mejora- miento de arriba a abajo, paso a paso, y después escribiremos el correspondiente programa en C++. Hemos visto que las instrucciones de control pueden apilarse una encima de otra (en secuencia). Aquí examinaremos la otra forma en la que pueden conectarse las instrucciones de control, a saber, mediante el anidamiento de una instrucción de control dentro de otra. Considere el siguiente enunciado de un problema: Una universidad ofrece un curso que prepara a los estudiantes para el examen estatal de certificación del estado como corredores de bienes raíces. El año pasado, diez de los estudiantes que completaron este curso tomaron el examen. La universidad desea saber qué tan bien se desempeñaron sus estu- diantes en el examen. A usted se le ha pedido que escriba un programa para sintetizar los resultados. Se le dio una lista de estos 10 estudiantes. Junto a cada nombre hay un 1 escrito, si el estudiante aprobó el examen, o un 2 si lo reprobó. Su programa debe analizar los resultados del examen de la siguiente manera: 1. Introducir cada resultado de la prueba (es decir, un 1 o un 2). Mostrar el mensaje “Escriba el resulta- do” en la pantalla, cada vez que el programa solicite otro resultado de la prueba. 2. Contar el número de resultados de la prueba, de cada tipo. 3. Mostrar un resumen de los resultados de la prueba, indicando el número de estudiantes que aprobaron y el número de estudiantes que reprobaron. 4. Si más de ocho estudiantes aprobaron el examen, imprimir el mensaje “Bono para el instructor”.
  • 167. 4.10 Cómo formular algoritmos: instrucciones de control anidadas 135 Después de leer cuidadosamente el enunciado del programa, hacemos las siguientes observa- ciones: 1. El programa debe procesar los resultados de la prueba para 10 estudiantes. Puede usarse un ciclo controlado por contador, ya que el número de resultados de la prueba se conoce de ante- mano. 2. Cada resultado de la prueba es un número; ya sea 1 o 2. Cada vez que el programa lee un re- sultado de la prueba, debe determinar si el número es 1 o 2. Para fines de simplificación, no- sotros sólo evaluamos un 1 en nuestro algoritmo. Si el número no es 1, suponemos que es un 2 (asegúrese de hacer el ejercicio 4.20, en donde se consideran las consecuencias de esta suposición). 3. Se utilizan dos contadores para llevar el registro de los resultados del examen: uno para contar el número de estudiantes que aprobaron el examen y uno para contar el número de estudiantes que reprobaron el examen. 4. Una vez que el programa ha procesado todos los resultados, debe decidir si más de ocho estu- diantes aprobaron el examen. Veamos ahora el mejoramiento de arriba a abajo, paso a paso. Comencemos con la representación del seudocódigo de la cima: Analizar los resultados del examen y decidir si hay que pagar un bono o no Una vez más, es importante enfatizar que la cima es una representación completa del programa, pero es probable que se necesiten varias mejoras antes de que el seudocódigo pueda evolucionar de manera natural en un programa en C++. Nuestra primera mejora es Inicializar variables Introducir las 10 calificaciones del examen, y contar los aprobados y reprobados Imprimir un resumen de los resultados del examen y decidir si debe pagarse un bono Aquí también, aun y cuando tenemos una representación completa de todo el programa, es necesario mejorarla. Ahora nos comprometemos con variables específicas. Se necesitan contadores para registrar los aprobados y reprobados; utilizaremos un contador para controlar el proceso de los ciclos y necesita- remos una variable para guardar la entrada del usuario. La instrucción en seudocódigo Inicializar variables puede mejorarse de la siguiente manera: Inicializar aprobados en cero Inicializar reprobados en cero Inicializar contador de estudiantes en uno Observe que sólo se inicializan los contadores al principio del algoritmo. La instrucción en seudocódigo Introducir las 10 calificaciones del examen, y contar los aprobados y reprobados
  • 168. 136 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– requiere un ciclo en el que se introduzca sucesivamente el resultado de cada examen. Sabemos de ante- mano que hay precisamente 10 resultados del examen, por lo que es apropiado utilizar un ciclo contro- lado por contador. Dentro del ciclo (es decir, anidado dentro del ciclo), una instrucción if...else determinará si cada resultado del examen es aprobado o reprobado, e incrementará el contador apropia- do. Entonces, la mejora al seudocódigo anterior es Mientras el contador de estudiantes sea menor o igual a 10 Pedir al usuario que introduzca el siguiente resultado del examen Recibir como entrada el siguiente resultado del examen Si el estudiante aprobó Sumar uno a aprobados De lo contrario Sumar uno a reprobados Sumar uno al contador de estudiantes Nosotros utilizamos líneas en blanco para aislar la estructura de control Si...De lo contrario, lo cual me- jora la legibilidad. La instrucción en seudocódigo Imprimir un resumen de los resultados de los exámenes y decidir si debe pagarse un bono puede mejorarse de la siguiente manera: Imprimir el número de aprobados Imprimir el número de reprobados Si más de ocho estudiantes aprobaron Imprimir “Bono para el instructor” La segunda mejora completa aparece en la figura 4.15. Se utilizan líneas en blanco para separar la estruc- tura Mientras y mejorar la legibilidad del programa. Este seudocódigo está ahora lo suficientemente mejorado para su conversión a C++. 1 Inicializar aprobados en cero 2 Inicializar reprobados en cero 3 Inicializar contador de estudiantes en uno 4 5 Mientras el contador de estudiantes sea menor o igual a 10 6 Pedir al usuario que introduzca el siguiente resultado del examen 7 Recibir como entrada el siguiente resultado del examen 8 9 Si el estudiante aprobó 10 Sumar uno a aprobados 11 De lo contrario 12 Sumar uno a reprobados 13 14 Sumar uno al contador de estudiantes 15 16 Imprimir el número de aprobados 17 Imprimir el número de reprobados Fig. 4.15  El seudocódigo para el problema de los resultados del examen (parte 1 de 2).
  • 169. 4.10 Cómo formular algoritmos: instrucciones de control anidadas 137 18 If more than eight students passed 19 Si más de ocho estudiantes aprobaron 20 Imprimir “Bono para el instructor” Análisis de conversión a la clase El programa que implementa el algoritmo en seudocódigo se muestra en la figura 4.16. Este ejemplo no contiene una clase; sólo contiene un archivo de código fuente en donde la función main realiza todo el trabajo de la aplicación. En éste y en el capítulo 3, hemos visto ejemplos que consisten en una clase (in- cluyendo los archivos de encabezado y código fuente para esta clase), así como otro archivo de código fuente para probar la clase. Este archivo de código fuente contenía la función main, que creaba un objeto de la clase y llamaba a sus funciones miembro. En ocasiones, cuando no tenga sentido tratar de crear una clase reutilizable para demostrar un concepto, usaremos un ejemplo contenido totalmente dentro de la función main de un sólo archivo de código fuente. En las líneas 9 a 11 y 18 se declaran e inicializan las variables que se utilizan para procesar los resul- tados del examen. Algunas veces los programas de iteración requieren la inicialización al principio de cada repetición; dicha reinicialización se realiza mediante instrucciones de asignación en vez de decla- raciones, o se pueden mover las declaraciones adentro de los cuerpos de los ciclos. 1 // Fig. 4.16: fig04_16.cpp 2 // Problema de los resultados del examen: instrucciones de control anidadas. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 // inicialización de las variables en las declaraciones 9 unsigned int aprobados = 0; // número de aprobados 10 unsigned int reprobados = 0; // número de reprobados 11 unsigned int contadorEstudiantes = 1; // contador de estudiantes 12 13 // procesa 10 estudiantes usando el ciclo controlado por contador 14 while ( contadorEstudiantes = 10 ) 15 { 16 // pide datos de entrada y obtiene el valor del usuario 17 cout Escriba el resultado (1 = aprobado, 2 = reprobado): ; 18 int resultado = 0; // resultado de un examen (1 = aprobado, 2 = reprobado) 19 cin resultado; // recibe como entrada el resultado 20 21 // if...else anidado en la instrucción while 22 if ( resultado == 1 ) // si resultado es 1, 23 aprobados = aprobados + 1; // incrementa aprobados; 24 else // else resultado no es 1, por lo que 25 reprobados = reprobados + 1; // incrementa reprobados 26 27 // incrementa contadorEstudiantes para que el ciclo termine en cierto momento 28 contadorEstudiantes = contadorEstudiantes + 1; 29 } // fin de while Fig. 4.15  El seudocódigo para el problema de los resultados del examen (parte 2 de 2). Fig. 4.16  Problema de los resultados de un examen: instrucciones de control anidadas (parte 1 de 2).
  • 170. 138 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– 30 31 // fase de terminación; muestra el número de aprobados y reprobados 32 cout Aprobados aprobados nReprobados reprobados endl; 33 34 // determina si aprobaron más de ocho estudiantes 35 if ( aprobados 8 ) 36 cout Bono para el instructor endl; 37 } // fin de main Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 2 Escriba el resultado (1 = aprobado, 2 = reprobado): 2 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 2 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 2 Aprobados 6 Reprobados 4 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 2 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Escriba el resultado (1 = aprobado, 2 = reprobado): 1 Aprobados 9 Reprobados 1 Bono para el instructor La instrucción while (líneas 14 a 29) itera 10 veces. Durante cada iteración, el ciclo recibe y proce- sa un resultado del examen. La instrucción if...else (líneas 22 a 25) para procesar cada resultado se anida en la instrucción while. Si resultado es 1, la instrucción if...else incrementa a aprobados; en caso contrario, asume que resultado es 2 e incrementa reprobados. La línea 28 incrementa contador- Estudiantes antes de que se evalúe otra vez la condición del ciclo, en la línea 15. Después de introducir 10 valores, el ciclo termina y en la línea 32 se muestra el número de aprobados y de reprobados. La instrucción if de las líneas 35 a 36 determina si más de ocho estudiantes aprobaron el examen y, de ser así, imprime el mensaje Bono para el instructor. En la figura 4.16 se muestra la entrada y la salida de dos ejecuciones de ejemplo del programa. Al final de la segunda ejecución de ejemplo, la condición en la línea 35 es verdadera: más de ocho estudian- tes aprobaron el examen, por lo que el programa imprime un mensaje en pantalla indicando que el instructor debe recibir un bono. Fig. 4.16  Problema de los resultados de un examen: instrucciones de control anidadas (parte 2 de 2).
  • 171. 4.11 Operadores de asignación 139 Inicialización de listas en C++11 C++11 introduce una nueva sintaxis de inicialización de variables. La inicialización de listas (también conocida como inicialización uniforme) nos permite usar una sintaxis para inicializar una variable de cualquier tipo. Considere la línea 11 de la figura 4.16: unsigned int contadorEstudiantes = 1; En C++11 podemos escribir esto como unsigned int contadorEstudiantes = { 1 }; o unsigned int contadorEstudiantes{ 1 }; Las llaves ({ y }) representan el inicializador de listas. Para una variable de tipo fundamental, sólo se coloca un valor en el inicializador de listas. Para un objeto, el inicializador de listas puede ser una lista de valores separada por comas que se pasan al constructor del objeto. Por ejemplo, el ejercicio 3.14 le pidió crear una clase Empleado que representara el primer nombre, apellido y salario de un empleado. Suponiendo que la clase define un constructor que recibe objetos string para el primer nombre y el apellido, y un double para el salario, podría inicializar el objeto Empleado de la siguiente forma: Empleado empleado1{ Bob, Blue, 1234.56 }; Empleado empleado2 = { Sue, Green, 2143.65 }; Para las variables de tipos fundamentales, la sintaxis de inicialización de listas también evita lo que se conoce como conversiones de reducción (narrowing), que podrían provocar pérdida de datos. Por ejemplo, antes era posible escribir la siguiente instrucción: int x = 12.7; la cual intenta asignar el valor double 12.7 a la variable int x. Para convertir un valor double en int, se trunca la parte de punto flotante (.7) y se produce una pérdida de información: una conversión de reducción. El valor real asignado a x es 12. Muchos compiladores generan una advertencia para esta instrucción, pero de todas formas permiten que se compile. Pero si utilizamos la inicialización de listas, como en int x = { 12.7 }; o int x{ 12.7 }; se produce un error de compilación, el cual le ayuda a evitar un error lógico potencialmente sutil. Por ejemplo, el compilador Xcode LLVM de Apple proporciona el error Type ‘double’ cannot be narrowed to 'int' in initializer list En capítulos posteriores hablaremos sobre características adicionales de los inicializadores de listas. 4.11 Operadores de asignación C++ cuenta con varios operadores de asignación para abreviar las expresiones de asignación. Por ejem- plo, la instrucción c = c + 3; puede abreviarse mediante el operador de asignación de suma, +=, de la siguiente manera: c += 3;
  • 172. 140 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– este operador suma el valor de la expresión que está a la derecha del operador, al valor de la variable que está a la izquierda del operador, y almacena el resultado en la variable que está a la izquierda del operador. Cualquier instrucción de la forma variable = variable operador expresión; en donde la misma variable aparece en ambos lados del operador de asignación y operador es uno de los operadores binarios +, -, *, / o % (u otros que veremos más adelante en el libro), puede escribirse de la siguiente forma: variable operador= expresión; Por lo tanto, la expresión de asignación c += 3 suma 3 a c. La figura 4.17 muestra los operadores de asignación aritméticos, algunas expresiones de ejemplo en las que se utilizan los operadores y las expli- caciones de lo que estos operadores hacen. Operador de asignación Expresión de ejemplo Explicación Asigna Suponer que: int c = 3, d = 5, e = 4, f = 6, g = 12; += c += 7 c = c + 7 10 a c -= d -= 4 d = d - 4 1 a d *= e *= 5 e = e * 5 20 a e /= f /= 3 f = f / 3 2 a f %= g %= 9 g = g % 9 3 a g Fig. 4.17  Operadores de asignación aritméticos. 4.12 Operadores de incremento y decremento Además de los operadores de asignación aritméticos, C++ proporciona dos operadores unarios para sumar 1, o restar 1, al valor de una variable numérica. Estos operadores son el operador de incremento unario, ++, y el operador de decremento unario, --, los cuales se sintetizan en la figura 4.18. Un pro- grama puede incrementar en 1 el valor de una variable llamada c, utilizando el operador de incremento, ++, en lugar de usar la expresión c = c + 1 o c +=1. A un operador de incremento o decremento que se coloca antes de una variable se le llama operador de preincremento o predecremento, respectivamen- te.Aunoperadordeincrementoodecrementoquesecolocadespuésdeunavariableselellamaoperador de postincremento o postdecremento, respectivamente. Operador Llamado Expresión de ejemplo Explicación ++ preincremento ++a Incrementar a en 1, después utilizar el nuevo valor de a en la expresión en que esta variable reside. ++ postincremento a++ Usar el valor actual de a en la expresión en la que esta variable reside, después incrementar a en 1. Fig. 4.18  Los operadores de incremento y decremento (parte 1 de 2).
  • 173. 4.12 Operadores de incremento y decremento 141 Operador Llamado Expresión de ejemplo Explicación -- predecremento --b Decrementar b en 1, después utilizar el nuevo valor de b en la expresión en que esta variable reside. -- postdecremento b-- Usar el valor actual de b en la expresión en la que esta variable reside, después decrementar b en 1. Al proceso de utilizar el operador de preincremento (o postdecremento) para sumar (o restar) 1 a una variable, se le conoce como preincrementar (o predecrementar) la variable. Al preincrementar (o predecrementar) una variable, ésta se incrementa (o decrementa) en 1, y después el nuevo valor de la variable se utiliza en la expresión en la que aparece. Al proceso de utilizar el operador de postincre- mento (o postdecremento) para sumar (o restar) 1 a una variable, se le conoce como postincrementar (o postdecrementar) la variable. Al postincrementar (o postdecrementar) una variable, el valor actual de la variable se utiliza en la expresión en la que aparece y después el valor de la variable se incrementa (o decrementa) en 1. Buena práctica de programación 4.4 A diferencia de los operadores binarios, los operadores unarios de incremento y decremen- to deben colocarse enseguida de sus operandos, sin espacios entre ellos. En la figura 4.19 se demuestra la diferencia entre la versión de preincremento y la versión de predecremento del operador de incremento ++. El operador de decremento (--) funciona de manera similar. 1 // Fig. 4.19: fig04_19.cpp 2 // Preincremento y postincremento. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 // demuestra el postincremento 9 int c = 5; // asigna 5 a c 10 cout c endl; // imprime 5 11 cout c++ endl; // imprime 5 y después postincrementa 12 cout c endl; // imprime 6 13 14 cout endl; // salta una línea 15 16 // demuestra el preincremento 17 c = 5; // asigna 5 a c 18 cout c endl; // imprime 5 19 cout ++c endl; // preincrementa y después imprime 6 20 cout c endl; // imprime 6 21 } // fin de main Fig. 4.18  Los operadores de incremento y decremento (parte 2 de 2). Fig. 4.19  Preincrementar y postdecrementar (parte 1 de 2).
  • 174. 142 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– 5 5 6 5 6 6 En la línea 9 se inicializa c con 5, y en la línea 10 se imprime el valor inicial de c. En la línea 11 se imprime el valor de la expresión c++. Esta expresión postincrementa la variable c, por lo que se imprime su valor original (5) y después se incrementa su valor. Así, en la línea 11 se imprime el valor inicial de c (5) otra vez. En la línea 12 se imprime el nuevo valor de c (6) para mostrar que se incrementó el valor de la variable en la línea 11. En la línea 17 se restablece el valor de c a 5 y en la línea 18 se imprime ese valor. En la línea 19 se imprime el valor de la expresión ++c. Esta expresión preincrementa a c, por lo que se incrementa su valor y luego se imprime el nuevo valor (6). En la línea 20 se imprime el valor de c otra vez para mostrar que sigue siendo 6 después de que se ejecuta la línea 19. Los operadores de asignación aritméticos y los operadores de incremento y decremento pueden utilizarse para simplificar las instrucciones de los programas. Las tres instrucciones de asignación de la figura 4.16: aprobados = aprobados + 1; reprobados = reprobados + 1; contadorEstudiantes = contadorEstudiantes + 1; se pueden escribir en forma más concisa con operadores de asignación, de la siguiente manera: aprobados += 1; reprobados += 1; contadorEstudiantes += 1; con operadores de preincremento de la siguiente forma: ++aprobados; ++reprobados; ++contadorEstudiantes; o con operadores de postincremento de la siguiente forma: aprobados++; reprobados++; contadorEstudiantes++; Al incrementar (++) o decrementar (--) una variable entera que se encuentre en una instrucción por sí sola, las formas preincremento y postincremento tienen el mismo efecto lógico, y las formas predecre- mento y postdecremento tienen el mismo efecto lógico. Solamente cuando una variable aparece en el contextodeunaexpresiónmásgrandeescuandolosoperadorespreincrementoypostincrementotienen distintos efectos (y lo mismo se aplica a los operadores de predecremento y postdecremento). Error común de programación 4.8 Tratar de usar el operador de incremento o decremento en una expresión que no sea un nombre de variable que pueda modificarse [por ejemplo, escribir ++(x + 1)] es un error de sintaxis. Fig. 4.19  Preincrementar y postdecrementar (parte 2 de 2).
  • 175. 4.13 Conclusión 143 En la figura 4.20 se muestra la precedencia y la asociatividad de los operadores que se han presen- tado hasta este punto. Los operadores se muestran de arriba a abajo, en orden descendente de preceden- cia. La segunda columna indica la asociatividad de los operadores en cada nivel de precedencia. Cabe mencionar que el operador condicional (?:), los operadores unarios de preincremento (++), predecre- mento (--), suma (+) y resta (-), y los operadores de asignación =, +=, -=, *=, /= y %= se asocian de dere- cha a izquierda. Todos los demás operadores en la figura 4.20 se asocian de izquierda a derecha. La ter- cera columna enlista los diversos grupos de operadores. Operadores Asociatividad Tipo :: () izquierda a derecha [Vea la precaución en la figura 2.10 con respecto al agrupamiento de paréntesis] primario ++ -- static_casttype() izquierda a derecha postfijo ++ -- + - derecha a izquierda unario (prefijo) * / % izquierda a derecha multiplicativo + - izquierda a derecha aditivo izquierda a derecha inserción/extracción = = izquierda a derecha relacional == != izquierda a derecha igualdad ?: derecha a izquierda condicional = += -= *= /= %= derecha a izquierda asignación Fig. 4.20  Precedencia de los operadores vistos hasta ahora en el libro. 4.13 Conclusión Este capítulo presentó las técnicas básicas de solución de problemas que los programadores utilizan para crear clases y desarrollar funciones miembro para estas clases. Demostramos cómo construir un algorit- mo (es decir, una metodología para resolver un problema) en seudocódigo, y después cómo refinar el algoritmo a través de diversas fases de desarrollo de seudocódigo, lo cual produce código en C++ que puede ejecutarse como parte de una función. En este capítulo aprendió a utilizar el método de mejora- miento de arriba a abajo, paso a paso, para planear las acciones específicas que debe realizar una función, y el orden en el que debe realizar estas acciones. Aprendióquesólohaytrestiposdeestructurasdecontrol(secuencia,selecciónyrepetición)necesarias para desarrollar cualquier algoritmo. Demostramos dos de las instrucciones de selección de C++: la ins- trucción de selección simple if y la instrucción de selección doble if...else. La instrucción if se utiliza para ejecutar un conjunto de instrucciones basadas en una condición; si la condición es verdadera, se eje- cutan las instrucciones; si no, se omiten. La instrucción de selección doble if...else se utiliza para ejecu- tar un conjunto de instrucciones si se cumple una condición, y otro conjunto de instrucciones si la condi- ción es falsa. Después vimos la instrucción de repetición while, en donde un conjunto de instrucciones se ejecutan de manera repetida, mientras que una condición sea verdadera. Utilizamos el apilamiento de instruccionesdecontrolparacalculareltotalyelpromediodeunconjuntodecalificacionesdeestudiantes, mediante la repetición controlada por un contador y controlada por un centinela, y utilizamos el anida- miento de instrucciones de control para analizar y tomar decisiones con base en un conjunto de resultados deunexamen.Vimosunaintroducciónalosoperadoresdeasignación,quepuedenutilizarseparaabreviar instrucciones. Presentamos los operadores de incremento y decremento, los cuales se pueden utilizar para sumar o restar el valor 1 de una variable. En el siguiente capítulo continuaremos nuestra discusión acerca de las instrucciones de control, en donde presentaremos las instrucciones for, do...while y switch.
  • 176. 144 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– Resumen Sección 4.2 Algoritmos • Un algoritmo (pág. 105) es un procedimiento para resolver un problema, en términos de las acciones a ejecutar y el orden en el que se ejecutan. • El proceso de especificar el orden en el que se ejecutan las instrucciones en un programa se denomina control del programa (pág. 106). Sección 4.3 Seudocódigo • El seudocódigo (pág. 106) ayuda al programador a idear un programa antes de intentar escribirlo en un lengua- je de programación. Sección 4.4 Estructuras de control • Un diagrama de actividad modela el flujo de trabajo (también conocido como la actividad; pág. 108) de un sistema de software. • Los diagramas de actividad (pág. 107) se componen de símbolos tales como los símbolos de estados de acción, rombos y pequeños círculos, que se conectan mediante flechas de transición, las cuales representan el flujo de la actividad. • Al igual que el seudocódigo, los diagramas nos ayudan a desarrollar y representar algoritmos. • Un estado de acción se representa como un rectángulo en el que sus lados izquierdo y derecho se sustituyen con arcos que se curvean hacia fuera. La expresión de acción (pág. 108) aparece dentro del estado de acción. • Las flechas en un diagrama de actividad representan las transiciones (pág. 108), que indican el orden en el que ocurren las acciones representadas por los estados de acción. • El círculo relleno en un diagrama de actividad representa el estado inicial (pág. 108): el comienzo del flujo de trabajo antes de que el programa realice las acciones modeladas. • El círculo sólido rodeado por una circunferencia, que aparece en la parte inferior del diagrama de actividad, representa el estado final (pág. 108): el término del flujo de trabajo después de que el programa realiza sus ac- ciones. • Los rectángulos con las esquinas superiores derechas dobladas se llaman notas (pág. 108) en UML. Una línea punteada (pág. 108) conecta a cada nota con el elemento que ésta describe. • Existen tres tipos de estructuras de control (pág. 107): secuencia, selección y repetición. • La estructura de secuencia está integrada en C++; de manera predeterminada, las instrucciones se ejecutan en el orden en el que aparecen. • Una estructura de selección elige uno de varios cursos alternativos de acción. Sección 4.5 Instrucción de selección if • La instrucción if de selección simple (pág. 110) ejecuta (selecciona) una acción si una condición es verdadera, o la ignora si la condición es falsa. • Un símbolo de decisión (pág. 111) en un diagrama de actividad indica que se debe tomar una decisión. El flujo de trabajo sigue una ruta determinada por las condiciones de guardia asociadas. Cada flecha de transmisión que sale de un símbolo de decisión tiene una condición de guardia. Si una condición de guardia es verdadera, el flujo de trabajo entra al estado de acción al que apunta la flecha de transición. Sección 4.6 Instrucción de selección doble if…else • La instrucción if...else de selección doble (pág. 112) ejecuta (selecciona) una acción cuando la condición es verdadera, y otra acción distinta cuando la condición es falsa. • Para incluir varias instrucciones en el cuerpo del if (o en el cuerpo del else para una instrucción if...else), encierre las instrucciones entre llaves ({ y }). A un conjunto de instrucciones contenidas dentro de un par de llaves se le llama bloque (pág. 115). Un bloque puede colocarse en cualquier parte de un programa en donde se pueda colocar una sola instrucción. • Una instrucción nula (pág. 116), la cual implica que no puede realizarse ninguna acción, se indica mediante un punto y coma (;).
  • 177. Resumen 145 Sección 4.7 Instrucción de repetición while • Una instrucción de repetición (pág. 116) repite una acción mientras cierta condición sea verdadera. • En un símbolo de fusión de UML (pág. 117) hay dos o más flechas de transición que apuntan al rombo y sólo una que sale de él, para indicar que se fusionan múltiples flujos de actividad para continuar con la actividad. Sección 4.8 Cómo formular algoritmos: repetición controlada por un contador • La repetición controlada por un contador (pág. 118) se utiliza cuando se conoce el número de repeticiones antes de que un ciclo empiece a ejecutarse; es decir, cuando hay una repetición definida. • Una suma de enteros puede producir un valor que sea demasiado grande como para almacenarlo en una variable int. A esto se le conoce como desbordamiento aritmético y provoca un comportamiento impredecible en tiem- po de ejecución. • Los valores máximo y mínimo que pueden almacenarse en una variable int se representan mediante las cons- tantes INT_MAX e INT_MIN, respectivamente, del encabezado climits. • Se considera una buena práctica asegurar que los cálculos aritméticos no se desborden antes de realizarlos. En código de uso industrial, hay que realizar revisiones para todos los cálculos que puedan producir un desborda- miento o subdesbordamiento. Sección 4.9 Cómo formular algoritmos: repetición controlada por un centinela • El proceso de mejoramiento de arriba a abajo, paso a paso (pág. 125) es un proceso para refinar el seudocódigo, manteniendo una representación completa del programa durante cada mejoramiento. • La repetición controlada por un centinela (pág. 126) se utiliza cuando no se conoce el número de repeticiones antes de que un ciclo se empiece a ejecutar; es decir, cuando hay repetición indefinida. • Un valor que contiene una parte fraccionaria se conoce como número de punto flotante y se representa de manera aproximada mediante tipos de datos como float y double (pág. 127). • El operador de conversión de tipos static_castdouble (pág. 132) se puede utilizar para crear una copia temporal de punto flotante del operando. • Los operadores unarios (pág. 133) sólo reciben un operando; los operadores binarios reciben dos. • El manipulador de flujo parametrizado setprecision (pág. 133) indica el número de dígitos de precisión que deben mostrarse a la derecha del punto decimal. • El manipulador de flujo fixed (pág. 133) indica que los valores de punto flotante se deben imprimir en lo que se denomina formato de punto fijo, en oposición a la notación científica. • Engeneral,cualquiervariableenteraquedebaalmacenarsólovaloresnonegativosdebedeclararsecon unsigned antes del tipo entero. Las variables de tipos unsigned pueden representar valores de 0 hasta aproximadamente el doble del rango positivo del tipo entero con signo correspondiente. • Puede determinar el valor unsigned int máximo de su plataforma con la constante UINT_MAX de climits. Sección 4.10 Cómo formular algoritmos: instrucciones de control anidadas • Una instrucción de control anidada (pág. 134) aparece en el cuerpo de otra instrucción de control. • C++11 introduce la nueva inicialización de listas para inicializar variables en sus declaraciones, como en: int contadorEstudiantes = { 1 }; o int contadorEstudiantes{ 1 }; • Las llaves ({ y }) representan el inicializador de listas. Para una variable de tipo fundamental, sólo se coloca un valor en el inicializador de listas. Para un objeto, el inicializador de listas puede ser una lista de valores separada por comas, que se pasan al constructor del objeto. • Para las variables de tipo fundamental, la sintaxis de inicialización de listas también previene lo que se conoce como conversiones de reducción, que podrían provocar pérdidas de datos.
  • 178. 146 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– Sección 4.11 Operadores de asignación • Los operadores aritméticos +=, -=, *=, /= y %= abrevian las expresiones de asignación (pág. 140). Sección 4.12 Operadores de incremento y decremento • Los operadores de incremento (++) y de decremento (--) (pág. 140) incrementan o decrementan una variable en 1, respectivamente. Si el operador se coloca antes de la variable, ésta se incrementa o decrementa en 1 pri- mero, y después su nuevo valor se utiliza en la expresión en la que aparece. Si el operador se coloca después de la variable, ésta se utiliza primero en la expresión en la que aparece, y después su valor se incrementa o decre- menta en 1. Ejercicios de autoevaluación 4.1 Complete los siguientes enunciados: a) Todoslosprogramaspuedenescribirseentérminosdetrestiposdeestructurasdecontrol:__________, __________ y __________. b) La instrucción de selección __________ se utiliza para ejecutar una acción cuando una condición es verdadera, y otra acción cuando esa condición es falsa. c) Al proceso de repetir un conjunto de instrucciones un número específico de veces se le llama repetición __________. d) Cuando no se sabe de antemano cuántas veces se repetirá un conjunto de instrucciones, se puede usar un valor __________ para terminar la repetición. 4.2 Escriba cuatro instrucciones distintas en C++, en donde cada una sume 1 a la variable entera x. 4.3 Escriba instrucciones en C++ para realizar cada una de las siguientes tareas: a) En una instrucción, asignar la suma del valor actual de x y y a z, y postincrementar el valor de x. b) Determinar si el valor de la variable cuenta es mayor que 10. De ser así, imprimir Cuenta es mayor que 10. c) Predecrementar la variable x en 1, luego restarla a la variable total. d) Calcular el residuo después de dividir q entre divisor, y asignar el resultado a q. Escriba esta instruc- ción de dos maneras distintas. 4.4 Escriba instrucciones en C++ para realizar cada una de las siguientes tareas: a) Declarar la variable suma como de tipo unsigned int e inicializarla con 0. b) Declarar la variable x como de tipo unsigned int e inicializarla con 1. c) Sumar la variable x a suma y asignar el resultado a la variable suma. d) Imprimir la cadena La suma es: , seguida del valor de la variable suma. 4.5 Combine las instrucciones que escribió en el ejercicio 4.4 para formar un programa que calcule e imprima la suma de los enteros del 1 al 10. Use una instrucción while para iterar a través de las instrucciones de cálculo e incremento. El ciclo debe terminar cuando el valor de x se vuelva 11. 4.6 Determine el valor de cada una de estas variables unsigned int después de realizar el cálculo. Suponga que, cuando se empieza a ejecutar cada una de las instrucciones, todas las variables tienen el valor 5 entero. a) producto *= x++; b) cociente /= ++x; 4.7 Escriba instrucciones individuales de C++ o porciones de instrucciones que realicen lo siguiente: a) Recibir como entrada la variable unsigned int x con cin y . b) Recibir como entrada la variable unsigned int y con cin y . c) Declarar la variable unsigned int i e inicializarla con 1. d) Declarar la variable entera potencia e inicializarla con 1. e) Multiplicar la variable potencia por x y asignar el resultado a potencia. f) Preincrementar la variable i en 1. g) Determinar si i es menor o igual a y. h) Imprimir la variable entera potencia con cout y . 4.8 Escriba un programa en C++ que utilice las instrucciones del ejercicio 4.7 para calcular x elevada a la y potencia. El programa debe tener una instrucción de repetición while.
  • 179. Respuestas a los ejercicios de autoevaluación 147 4.9 Identifique y corrija los errores en cada uno de los siguientes fragmentos de código: a) while ( c = 5 ) { product *= c; ++c; b) cin valor; c) if ( genero == 1 ) cout Mujer endl; else; cout Hombre endl; 4.10 ¿Qué está mal en la siguiente instrucción de repetición while? while ( z = 0 ) suma += z; Respuestas a los ejercicios de autoevaluación 4.1 a) Secuencia, selección y repetición. b) if...else c) Controlada por contador o definida. d) Centinela, de señal, de bandera o de prueba. 4.2 x = x + 1; x += 1; ++x; x++; 4.3 a) z = x++ + y; b) if ( cuenta 10 ) cout Cuenta es mayor que 10 endl; c) total -= --x; d) q %= divisor; q = q % divisor; 4.4 a) unsigned int suma = 0; b) unsigned int x = 1; c) suma += x; o suma = suma + x; d) cout La suma es: suma endl; 4.5 Vea el siguiente código: 1 // Solución al ejercicio 4.5: ej04_05.cpp 2 // Calcula la suma de los enteros del 1 al 10. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 unsigned int suma = 0; // almacena la suma de los enteros del 1 al 10 9 unsigned int x = 1; // contador 10 11 while ( x = 10 ) // itera 10 veces 12 { 13 suma += x; // suma x a suma 14 ++x; // incrementa x 15 } // fin de while 16 17 cout La suma es: suma endl; 18 } // fin de main La suma es: 55
  • 180. 148 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– 4.6 a) producto = 25, x = 6; b) quotient = 0, x = 6; 4.7 a) cin x; b) cin y; c) unsigned int i = 1; d) unsigned int potencia = 1; e) potencia *= x; o potencia = potencia * x; f) ++i; g) if ( i = y ) h) cout potencia endl; 4.8 Vea el siguiente código: 1 // Solución al ejercicio 4.8: ej04_08.cpp 2 // Eleva x a la y potencia. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 unsigned int i = 1; // inicializa i para comenzar a contar desde 1 9 unsigned int potencia = 1; // inicializa potencia 10 11 cout Escriba la base como un entero: ; // pide la base 12 unsigned int x; // base 13 cin x; // recibe la base como entrada 14 15 cout Escriba el exponente como un entero: ; // pide el exponente 16 unsigned int y; // exponente 17 cin y; // recibe el exponente como entrada 18 19 // cuenta de 1 a y y multiplica potencia por x cada vez 20 while ( i = y ) 21 { 22 potencia *= x; 23 ++i; 24 } // fin de while 25 26 cout potencia endl; // muestra el resultado 27 } // fin de main Escriba la base como un entero: 2 Escriba el exponente como un entero: 3 8 4.9 a) Error: falta la llave derecha de cierre del cuerpo de la instrucción while. Corrección: Agregar una llave derecha de cierre después de la instrucción ++c;. b) Error: Se utiliza inserción de flujo, en vez de extracción de flujo. Corrección: cambie a . c) Error: El punto y coma después de else produce un error lógico. La segunda instrucción de salida siempre se ejecutará. Corrección: Quitar el punto y coma después de else. 4.10 El valor de la variable z nunca se cambia en la instrucción while. Por lo tanto, si la condición de continua- ción de ciclo ( z = 0 ) es en un principio verdadera, se crea un ciclo infinito. Para evitar que ocurra un ciclo infi- nito, z debe decrementarse de manera que eventualmente se vuelva menor que 0.
  • 181. Ejercicios 149 Ejercicios 4.11 (Corrija los errores de código) Identifique y corrija los errores en cada uno de los siguientes fragmentos de código: a) if ( edad = 65 ); cout Edad es mayor o igual que 65 endl; else cout Edad es menor que 65 endl; b) if ( edad = 65 ) cout Edad es mayor o igual que 65 endl; else; cout Edad es menor que 65 endl; c) unsigned int x = 1; unsigned int total; while ( x = 10 ) { total += x; ++x; } d) While ( x = 100 ) total += x; ++x; e) while ( y 0 ) { cout y endl; ++y; } 4.12 (¿Qué hace este programa?) ¿Qué es lo que imprime el siguiente programa? 1 // Ejercicio 4.12: ej04_12.cpp 2 // ¿Qué imprime este programa? 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 unsigned int y = 0; // declara e inicializa y 9 unsigned int x = 1; // declara e inicializa x 10 unsigned int total = 0; // declara e inicializa el total 11 12 while ( x = 10 ) // itera 10 veces 13 { 14 y = x * x; // realiza el cálculo 15 cout y endl; // imprime el resultado 16 total += y; // suma y al total 17 ++x; // incrementa el contador x 18 } // fin de while 19 20 cout El total es total endl; // muestra el resultado 21 } // fin de main Para los ejercicios 4.13 a 4.16, realice cada uno de los siguientes pasos: a) Lea el enunciado del problema. b) Formule el algoritmo utilizando seudocódigo y el proceso de mejoramiento de arriba a abajo, paso a paso. c) Escriba un programa en C++. d) Pruebe, depure y ejecute el programa en C++.
  • 182. 150 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– 4.13 (Kilometraje de gasolina) Los conductores se preocupan acerca del kilometraje de sus automóviles. Un conductor ha llevado el registro de varios viajes, anotando los kilómetros conducidos y los litros usados en ca- da viaje. Desarrolle un programa en C++ que utilice una instrucción while para recibir como entrada los kilóme- tros conducidos y los litros usados por cada viaje, y que imprima el total de kilómetros por litro obtenidos en todos los reabastecimientos hasta este punto. Escriba los kilometros usados (-1 para salir): 287 Escriba los litros: 13 KPL en este reabastecimiento: 22.076923 Total KPL: 22.076923 Escriba los kilometros usados (-1 para salir): 200 Escriba los litros: 10 KPL en este reabastecimiento: 20.000000 Total KPL: 21.173913 Escriba los kilometros usados (-1 para salir): 120 Escriba los litros: 5 KPL en este reabastecimiento: 24.000000 Total KPL: 21.678571 Escriba los kilometros usados (-1 para salir): -1 4.14 (Límites de crédito) Desarrolle una aplicación en C++ que determine si alguno de los clientes de una tienda de departamentos se ha excedido del límite de crédito en una cuenta. Para cada cliente se tienen los siguien- tes datos: a) Número de cuenta (un entero) b) Saldo al inicio del mes c) Total de todos los artículos cargados por el cliente en el mes d) Total de todos los créditos aplicados a la cuenta del cliente en el mes e) Límite de crédito permitido. El programa debe usar una instrucción while para recibir como entrada cada uno de estos datos, debe calcu- lar el nuevo saldo (= saldo inicial + cargos – créditos) y determinar si éste excede el límite de crédito del cliente. Para los clientes cuyo límite de crédito se ha excedido, el programa debe mostrar el número de cuenta del cliente, su lí- mite de crédito, el nuevo saldo y el mensaje “Se excedio el limite de su credito”. Introduzca el numero de cuenta (o -1 para salir): 100 Introduzca el saldo inicial: 5394.78 Introduzca los cargos totales: 1000.00 Introduzca los creditos totales: 500.00 Introduzca el limite de credito: 5500.00 El nuevo saldo es 5894.78 Cuenta: 100 Limite de credito: 5500.00 Saldo: 5894.78 Se excedio el limite de su credito. Introduzca el numero de cuenta (o -1 para salir): 200 Introduzca el saldo inicial: 1000.00 Introduzca los cargos totales: 123.45 Introduzca los creditos totales: 321.00 Introduzca el limite de credito: 1500.00 El nuevo saldo es 802.45 Introduzca el numero de cuenta (o -1 para salir): -1 4.15 (Calculadora de comisiones de ventas) Una empresa grande paga a sus vendedores mediante comisiones. Los vendedores reciben $200 por semana, más el 9% de sus ventas brutas durante esa semana. Por ejemplo, un vendedor que vende $5000 de productos químicos en una semana, recibe $200 más el 9% de $5000, o un total de $650. Desarrolle un programa en C++ que utilice una instrucción while para recibir como entrada las ventas
  • 183. Ejercicios 151 brutas de cada vendedor de la semana anterior, y que calcule y muestre los ingresos de ese vendedor. Procese las cifras de un vendedor a la vez. Introduzca las ventas en dolares (-1 para salir): 5000.00 El salario es: $650.00 Introduzca las ventas en dolares (-1 para salir): 6000.00 El salario es: $740.00 Introduzca las ventas en dolares (-1 para salir): 7000.00 El salario es: $830.00 Introduzca las ventas en dolares (-1 para salir): -1 4.16 (Calculadora de salario) Desarrolle un programa en C++ que utilice una instrucción while para determi- nar el sueldo bruto para cada uno de varios empleados. La empresa paga la “cuota normal” en las primeras 40 horas de trabajo de cada empleado, y paga “cuota y media” en todas las horas trabajadas que excedan de 40. Usted recibe una lista de los empleados de la empresa, el número de horas que trabajó cada empleado la semana pasada y la ta- rifa por horas de cada empleado. Su programa debe recibir como entrada esta información para cada empleado, debe determinar y mostrar el sueldo bruto de cada empleado. Introduzca las horas trabajadas (-1 para salir): 39 Introduzca la tarifa por horas del empleado ($00.00): 10.00 El salario es $390.00 Introduzca las horas trabajadas (-1 para salir): 40 Introduzca la tarifa por horas del empleado ($00.00): 10.00 El salario es $400.00 Introduzca las horas trabajadas (-1 para salir): 41 Introduzca la tarifa por horas del empleado ($00.00): 10.00 El salario es $415.00 Introduzca las horas trabajadas (-1 para salir): -1 4.17 (Encontrar el más grande) El proceso de encontrar el número más grande (es decir, el máximo de un gru- po de números) se utiliza frecuentemente en aplicaciones de computadora. Por ejemplo, un programa para deter- minar el ganador de un concurso de ventas recibe como entrada el número de unidades vendidas por cada vende- dor. El vendedor que haya vendido más unidades es el que gana el concurso. Escriba un programa en seudocódigo y después una aplicación en C++ que utilice una instrucción while para determinar e imprimir el mayor número de una serie de 10 números introducidos por el usuario. Su programa debe utilizar tres variables, como se muestra a continuación: contador: Un contador para contar hasta 10 (es decir, para llevar el registro de cuántos números se han introducido, y para detectar cuando se hayan procesado los 10 números). numero: El número actual que se introduce al programa. mayor: El número más grande encontrado hasta ahora. 4.18 (Salida tabular) Escriba un programa en C++ que utilice una instrucción while y la secuencia de escape de tabulación t para imprimir la siguiente tabla de valores: N 10*N 100*N 1000*N 1 10 100 1000 2 20 200 2000 3 30 300 3000 4 40 400 4000 5 50 500 5000
  • 184. 152 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– 4.19 (Encontrar los dos números más grandes) Utilizando una metodología similar a la del ejercicio 4.17, en- cuentre los dos valores más grandes de los 10 que se introdujeron. [Nota: debe introducir cada número sólo una vez]. 4.20 (Validar la entrada del usuario) El programa de resultados de un examen de la figura 4.16 asume que cualquier valor introducido por el usuario que no sea un 1 debe ser un 2. Modifique la aplicación para validar sus entradas. Para cualquier entrada, si el valor introducido es distinto de 1 o 2, debe seguir iterando hasta que el usua- rio introduzca un valor correcto. 4.21 (¿Qué hace este programa?) ¿Qué es lo que imprime el siguiente programa? 1 // Ejercicio 4.21: ej04_21.cpp 2 // ¿Qué es lo que imprime este programa? 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 unsigned int cuenta = 1; // inicializa cuenta 9 10 while ( cuenta = 10 ) // itera 10 veces 11 { 12 // imprime una línea de texto 13 cout ( cuenta % 2 ? **** : ++++++++ ) endl; 14 ++cuenta; // incrementa cuenta 15 } // fin de while 16 } // fin de main 4.22 (¿Qué hace este programa?) ¿Qué es lo que imprime el siguiente programa? 1 // Ejercicio 4.22: ej04_22.cpp 2 // ¿Qué es lo que imprime este programa? 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 unsigned int fila = 10; // inicializa fila 9 10 while ( fila = 1 ) // itera hasta que fila 1 11 { 12 unsigned int columna = 1; // establece columna a 1 cuando empieza la iteración 13 14 while ( columna = 10 ) // itera 10 vecess 15 { 16 cout ( fila % 2 ? : ); // salida 17 ++columna; // incrementa columna 18 } // fin de while interior 19 20 --fila; // decrementa fila 21 cout endl; // empieza nueva línea de salida 22 } // fin de while exterior 23 } // fin de main 4.23 (Problema del else suelto) Determine la salida de cada uno de los siguientes conjuntos de código, cuando x es 11 y y es 9. El compilador ignora la sangría en un programa en C++. El compilador de C++ siempre asocia un else con el if anterior, a menos que se le indique de otra forma mediante la colocación de llaves ({}). A primera vista, el programador tal vez no esté seguro de cuál if corresponde a cuál else; esta situación se conoce como el
  • 185. Ejercicios 153 “problema del else suelto”. Hemos eliminado la sangría del siguiente código para hacer el problema más retador. [Sugerencia: aplique las convenciones de sangría que aprendió]. a) if ( x 10 ) if ( y 10 ) cout ***** endl; else cout ##### endl; cout $$$$$ endl; b) if ( x 10 ) { if ( y 10 ) cout ***** endl; } else { cout ##### endl; cout $$$$$ endl; } 4.24 (Otro problema de else suelto) Modifique el siguiente código para producir la salida que se muestra. Utilice las técnicas de sangría apropiadas. No debe hacer modificaciones en el código, sólo insertar llaves. El com- pilador ignora la sangría en un programa en C++. Hemos eliminado la sangría en el código dado, para hacer el problema más retador. [Nota: es posible que no se requieran modificaciones]. if ( y == 8 ) if ( x == 5 ) cout @@@@@ endl; else cout ##### endl; cout $$$$$ endl; cout endl; a) Suponiendo que x = 5 y y = 8, se produce la siguiente salida: @@@@@ $$$$$ b) Suponiendo que x = 5 y y = 8, se produce la siguiente salida: @@@@@ c) Suponiendo que x = 5 y y = 8, se produce la siguiente salida: @@@@@ d) Suponiendo que x = 5 y y = 7, se produce la siguiente salida. [Nota: las tres últimas instrucciones de salida después del else forman parte de un bloque]. ##### $$$$$
  • 186. 154 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– 4.25 (Cuadrado de asteriscos) Escriba un programa que pida al usuario que introduzca el tamaño del lado de un cuadrado y que muestre un cuadrado hueco de ese tamaño, compuesto de asteriscos y espacios en blanco. Su programa debe funcionar con cuadrados que tengan lados de todas las longitudes entre 1 y 20. Por ejemplo, si su programa lee un tamaño de 5, debe imprimir ***** * * * * * * ***** 4.26 (Palíndromos) Un palíndromo es un número o una frase de texto que se lee igual al derecho y al revés. Por ejemplo, cada uno de los siguientes enteros de cinco dígitos es un palíndromo: 12321, 55555, 45554 y 11611. Escriba una aplicación que lea un entero de cinco dígitos y determine si es un palíndromo. [Sugerencia: use los operadores de división y módulo para separar el número en sus dígitos individuales]. 4.27 (Imprimir el equivalente decimal de un número binario) Escriba un programa que reciba como entrada un entero que contenga sólo ceros y unos (es decir, un entero “binario”), y que imprima su equivalente decimal. Use los operadores módulo y división para elegir los dígitos del número “binario” uno a la vez, de derecha a izquier- da. En forma parecida al sistema numérico decimal, en donde el dígito más a la derecha tiene un valor posicional de 1 y el siguiente dígito a la izquierda tiene un valor posicional de 10, después 100, después 1000, etcétera, en el sistema numérico binario, el dígito más a la derecha tiene un valor posicional de 1, el siguiente dígito a la izquierda tiene un valor posicional de 2, luego 4, luego 8, etcétera. Así, el número decimal 234 se puede interpretar como 2 * 100 + 3 * 10 + 4 * 1. El equivalente decimal del número binario 1101 es 1 * 1 + 0 * 2 + 1 * 4 + 1 * 8, o 1 + 0 + 4 + 8, o 13. [Nota: para aprender más acerca de los números binarios, consulte el apéndice D]. 4.28 (Patróndeajedrezdeasteriscos) Escribaunprogramaquemuestreelsiguientepatróndetablerodedamas. Su programa debe utilizar sólo tres instrucciones de salida, una para cada una de las siguientes formas: cout * ; cout ' '; cout endl; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 4.29 (Múltiplos de 2 con un ciclo infinito) Escriba un programa que imprima las potencias del entero 2; a saber, 2, 4, 8, 16, 32, 64, etcétera. Su ciclo while no debe terminar (es decir, debe crear un ciclo infinito). Para ello, sim- plemente use la palabra clave true como la expresión para la instrucción while. ¿Qué ocurre cuando ejecuta este programa? 4.30 (Calcular el diámetro, la circunferencia y el área de un círculo) Escriba un programa que lea el radio de un círculo (como un valor double), calcule e imprima el diámetro, la circunferencia y el área. Use el valor 3.14159 para . 4.31 ¿Qué está mal con la siguiente instrucción? Proporcione la instrucción correcta para realizar lo que proba- blemente el programador trataba de hacer. cout ++( x + y );
  • 187. Hacer la diferencia 155 4.32 (Lados de un triángulo) Escriba un programa que lea tres valores double distintos de cero, y que determi- ne e imprima si podrían representar los lados de un triángulo. 4.33 (Lados de un triángulo recto) Escriba un programa que lea tres enteros distintos de cero, y que determine e imprima si podrían ser los lados de un triángulo recto. 4.34 (Factorial) El factorial de un entero n no negativo se escribe como n! (n factorial) y se define de la siguien- te manera: n! = n · (n – 1) · (n – 2) · … · 1 (para valores de n mayores o iguales a 1) y n! = 1 (para n = 0 o n = 1). Por ejemplo, 5! = 5 · 4 · 3 · 2 · 1, que es 120. Use instrucciones while en cada uno de los siguientes casos: a) Escriba un programa que lea un entero no negativo, que calcule e imprima su factorial. b) Escriba un programa que estime el valor de la constante matemática e, utilizando la fórmula: 1.11 1 1 1! 1 2! 1 3! e  = + + + + Pida al usuario la precisión deseada de e (es decir, el número de términos en la suma). c) Escriba una aplicación que calcule el valor de ex , utilizando la fórmula 1 1! 2! 3! 2 3 e x x x x  = + + + + Pida al usuario la precisión deseada de e (es decir, el número de términos en la suma). 4.35 (Inicializadores de listas de C++) Escriba instrucciones que usen la inicialización de listas de C++ para realizar cada una de las siguientes tareas: a) Inicializar la variable unsigned int contadorEstudiantes con 0. b) Inicializar la variable double saldoInicial con 1000.0 c) Inicializar un objeto de la clase Cuenta que proporcione un constructor que reciba un unsigned int, dos string y un double para inicializar los miembros de datos numeroCuenta, primerNombre, apelli- do y saldo. Hacer la diferencia 4.36 (Implementar la privacidad con la criptografía) El crecimiento explosivo de las comunicaciones de Inter- net y el almacenamiento de datos en computadoras conectadas a Internet, ha incrementado de manera considera- ble los problemas de privacidad. El campo de la criptografía se dedica a la codificación de datos para dificultar (y, mediante los esquemas más avanzados, tratar de imposibilitar) su lectura a los usuarios no autorizados. En este ejercicio, usted investigará un esquema simple para cifrar y descifrar datos. Una compañía que desea enviar datos porInternetlepidióqueescribieraunprogramaqueloscifre,demodoquesepuedantransmitirconmásseguridad. Todos los datos se transmiten como enteros de cuatro dígitos. Su aplicación debe leer un entero de cuatro dígitos introducido por el usuario, y cifrarlo de la siguiente manera: Reemplace cada dígito con el resultado de sumarle 7 y obtenga el residuo después de dividir el nuevo valor entre 10. Después intercambie el primer dígito con el terce- ro, y el segundo dígito con el cuarto. Luego imprima el entero cifrado. Escriba una aplicación separada que reciba como entrada el número entero de cuatro dígitos cifrado y lo descifre (invirtiendo el esquema de cifrado) para formar el número original. [Proyecto de lectura opcional: investigue la “criptografía de clave pública” en general y el esquemadeclavepúblicaespecíficoPGP(Privacidadbastantebuena).Talveztambiénquierainvestigarelesquema RSA, que se utiliza mucho en las aplicaciones de nivel industrial]. 4.37 (Crecimiento de la población mundial) La población mundial ha crecido de manera considerable a través de los siglos. El crecimiento continuo podría, en un momento dado, desafiar los límites del aire respirable, el agua potable, la tierra cultivable y otros recursos limitados. Hay evidencia de que el crecimiento se ha reducido en años
  • 188. 156 Capítulo 4 Instrucciones de control, parte I: operadores de asignación, ++ y –– recientes, y que la población mundial podría llegar a su valor máximo en algún momento de este siglo, para luego empezar a disminuir. Para este ejercicio, investigue en línea las cuestiones sobre el crecimiento de la población mundial. Asegúrese de investigar varios puntos de vista. Obtenga estimaciones de la población mundial actual y su tasa de crecimiento (el porcentaje por el cual es probable que aumente este año). Escriba un programa que calcule el crecimiento anual de la población mundial durante los siguientes 75 años, utilizando la suposición simplificada de que la tasa de creci- miento actual permanecerá constante. Imprima los resultados en una tabla. La primera columna debe mostrar el año, desde el año 1 hasta el año 75. La segunda columna debe mostrar la población mundial anticipada al final de ese año. La tercera columna deberá mostrar el aumento numérico en la población mundial que ocurriría ese año. Use sus resultados para determinar el año en el que el tamaño de la población será del doble del actual, si fuera a persis- tir la tasa de crecimiento de este año.
  • 189. Instrucciones de control, parte 2: operadores lógicos 5 ¿Quién puede controlar su destino? —William Shakespeare La llave usada siempre brilla. —Benjamín Franklin O b j e t i v o s En este capítulo aprenderá a: n Conocer los fundamentos de la repetición controlada por un contador. n Usar las instrucciones de repetición for y do...while para ejecutar instrucciones repetidas veces en un programa. n Implementar la selección múltiple mediante el uso de la instrucción de selección switch. n Usar break y continue para alterar el flujo de control. n Usar los operadores lógicos para formar expresiones condicionales complejas en las instrucciones de control. n Evitar las consecuencias de confundir los operadores de igualdad y de asignación.
  • 190. 158 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos 5.1Introducción En este capítulo, continuaremos nuestra presentación de la programación estructurada, presentando el resto de las instrucciones de control en C++. Las instrucciones de control que estudiaremos aquí y las que vimos en el capítulo 4 son útiles para crear y manipular objetos. Continuaremos con nuestro énfa- sis anticipado sobre la programación orientada a objetos, que empezó con una discusión de los concep- tos básicos en el capítulo 1, además de muchos ejemplos y ejercicios de código orientado a objetos en los capítulos 3 y 4. En este capítulo demostraremos las instrucciones for, do...while y switch. A través de una serie de ejemplos cortos en los que utilizaremos las instrucciones while y for, exploraremos la repetición controlada por contador. Expandiremos la clase LibroCalificaciones que utiliza una instrucción switch para contar el número de calificaciones equivalentes de A, B, C, D y F, en un conjunto de cali- ficaciones numéricas introducidas por el usuario. Presentaremos las instrucciones de control de progra- ma break y continue. Hablaremos sobre los operadores lógicos, que nos permiten utilizar expresiones condicionales más complejas. También examinaremos el error común de confundir los operadores de igualdad (==) y desigualdad (=), y cómo evitarlo. 5.2Fundamentos de la repetición controlada por un contador Esta sección utiliza la instrucción de repetición while, presentada en el capítulo 4, para formalizar los elementos requeridos para llevar a cabo la repetición controlada por contador: 1. el nombre de una variable de control (o contador de ciclo) 2. el valor inicial de la variable de control 3. la condición de continuación de ciclo, que evalúa el valor final de la variable de control (es decir, determina si el ciclo debe continuar o no) 4. el incremento (o decremento) con el que se modifica la variable de control cada vez que pasa por el ciclo. El programa simple de la figura 5.1 imprime los números del 1 al 10. La declaración en la línea 8 nombra a la variable de control (contador), la declara como unsigned int, reserva espacio para ella en memoria y la establece a un valor inicial de 1. Las declaraciones que requieren inicialización son instruc- ciones ejecutables. En C++, es más preciso llamar a una declaración de variable que también reserva memoria a una definición. Como las definiciones también son declaraciones, utilizaremos el término “declaración” excepto cuando la distinción sea importante En la línea 13 se incrementa el contador del ciclo en 1 cada vez que se ejecuta el cuerpo del ciclo. La condición de continuación de ciclo (línea 10) en la instrucción while determina si el valor de la variable 5.1 Introducción 5.2 Fundamentos de la repetición controlada por un contador 5.3 Instrucción de repetición for 5.4 Ejemplos acerca del uso de la instrucción for 5.5 Instrucción de repetición do...while 5.6 Instrucción de selección múltiple switch 5.7 Instrucciones break y continue 5.8 Operadores lógicos 5.9 Confusión entre los operadores de igualdad (==) y de asignación (=) 5.10 Resumen de programación estructurada 5.11 Conclusión Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios | Hacer la diferencia
  • 191. 5.3 Instrucción de repetición for 159 1 // Fig. 5.1: fig05_01.cpp 2 // Repetición controlada por un contador. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 unsigned int contador = 1; // declara e inicializa la variable de control 9 10 while ( contador = 10 ) // condición de continuación de ciclo 11 { 12 cout contador ; 13 ++contador; // incrementa la variable de control en 1 14 } // fin de while 15 16 cout endl; // imprime una nueva línea 17 } // fin de main 1 2 3 4 5 6 7 8 9 10 Fig. 5.1  Repetición controlada por contador. de control es menor o igual que 10 (el valor final para el que la condición es true). El cuerpo de este while se ejecuta, aún y cuando la variable de control sea 10. El ciclo termina cuando la variable de con- trol es mayor a 10 (es decir, cuando contador se convierte en 11). La figura 5.1 se puede hacer más concisa si se inicializa contador con 0 y se sustituye la instrucción while con: contador = 0; while ( ++contador = 10 ) // condición de continuación de ciclo cout contador ; Este código ahorra una instrucción, ya que el incremento se realiza de manera directa en la condición del while, antes de evaluarla. Además, el código elimina las llaves alrededor del cuerpo del while, ya que éste ahora sólo contiene una instrucción. La codificación de tal forma condensada puede producir pro- gramas que sean más difíciles de leer, depurar, modificar y mantener. Tip para prevenir errores 5.1 Los valores de punto flotante son aproximados, por lo que controlar los ciclos de conteo con variables de punto flotante puede producir valores de contador imprecisos y pruebas inco- rrectas para la terminación. Controle los ciclos de conteo con valores enteros. Por separado, ++ y -- pueden usarse sólo con operandos enteros. 5.3Instrucción de repetición for Además de while, C++ cuenta con la instrucción de repetición for, la cual especifica los detalles de la repetición controlada por contador en una sola línea de código. Para ilustrar el poder del for, vamos a modificar el programa de la figura 5.1. El resultado se muestra en la figura 5.2. Cuando la instrucción for (líneas 10 y 11) se empieza a ejecutar, la variable de control contador se declara e inicializa en 1. A continuación, el programa verifica la condición de continuación de ciclo (línea 10 entre los signos de punto y coma) contador = 10. Como el valor inicial de contador es 1, la condición se satisface y la instrucción del cuerpo (línea 11) imprime el valor de contador, que es 1.
  • 192. 160 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos 1 // Fig. 5.2: fig05_02.cpp 2 // Repetición controlada por contador con la instrucción for. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 // el encabezado de la instrucción for incluye la inicialización 9 // la condición de continuación del ciclo y el incremento. 10 for ( unsigned int contador = 1; contador = 10; ++contador ) 11 cout contador ; 12 13 cout endl; // imprime una nueva línea 14 } // fin de main 1 2 3 4 5 6 7 8 9 10 Fig. 5.2  Repetición controlada por contador con la instrucción for. Después, la expresión ++contador incrementa la variable de control contador y el ciclo empieza de nuevo, con la prueba de continuación de ciclo. Ahora la variable de control es igual a 2, por lo que no se excede del valor final y el programa ejecuta la instrucción del cuerpo otra vez. Este proceso continúa hasta que el cuerpo del ciclo se haya ejecutado 10 veces y la variable de control contador se incremente a 11, con lo cual falla la prueba de continuación de ciclo y termina la repetición. El programa continúa, ejecutando la primera instrucción después de la instrucción for (en este caso, la instrucción de salida en la línea 13). Componentes del encabezado de la instrucción for La figura 5.3 muestra un análisis más detallado del encabezado de la instrucción for (línea 10) de la fi- gura 5.2. Observe que el encabezado de la instrucción for “se encarga de todo”: especifica cada uno de los elementos necesarios para la repetición controlada por contador con una variable de control. Si hay más de una instrucción en el cuerpo del for, se requieren llaves para encerrar el cuerpo del ciclo. Por lo general, las instrucciones for se utilizan para la repetición controlada por contador y las instrucciones while se usan para la repetición controlada por centinela. Valor inicial de la variable de control Condición de continuación de ciclo Incremento de la variable de control Palabra clave for Nombre de variable de control Separador de punto y coma requerido Separador de punto y coma requerido Valor final de la variable de control para la cual la condición es verdadera for ( unsigned int contador = 1; contador = 10; ++contador ) Fig. 5.3  Componentes del encabezado de la instrucción for.
  • 193. 5.3 Instrucción de repetición for 161 Errores por desplazamiento en uno Si usted especificara por error contador 10 como la condición de continuación de ciclo en la figura 5.2, el ciclo sólo se ejecutaría 9 veces. A este error lógico común se le conoce como error por desplaza- miento en 1. Error común de programación 5.1 Utilizar un operador relacional incorrecto o un valor final incorrecto de un contador de ciclo en la condición de continuación de ciclo de una instrucción while o for puede pro- ducir errores por desplazamiento en 1. Buena práctica de programación 5.1 Utilizar el valor final en la condición de una instrucción while o for con el operador rela- cional = nos ayuda a evitar los errores por desplazamiento en 1. Por ejemplo, para un ciclo que imprime los valores del 1 al 10, la condición de continuación de ciclo debe ser contador = 10, en vez de contador 10 (lo cual produce un error por desplazamiento en uno) o contador 11 (que es correcto). Muchos programadores prefieren el llamado conteo con base cero, en el cual para contar 10 veces, contador se inicializaría a cero y la prueba de continuación de ciclo sería contador 10. Formato general de una instrucción for El formato general de la instrucción for es for ( inicialización; condiciónDeContinuaciónDeCiclo; incremento ) instrucción en donde la expresión inicialización inicializa la variable de control del ciclo, la condiciónDeContinua- ciónDeCiclo determina si el ciclo debe seguir ejecutándose y el incremento aumenta el valor de la variable de control. En la mayoría de los casos, la instrucción for se puede representar mediante una instrucción while equivalente, como se muestra a continuación: inicialización; while ( condiciónDeContinuaciónDeCiclo ) { instrucción incremento; } Hay una excepción a esta regla, que veremos en la sección 5.7. Si la expresión de inicialización declara la variable de control (es decir, si el tipo de la variable de control se especifica antes del nombre de la variable), ésta puede utilizarse sólo en el cuerpo de esa ins- trucción for; ya que sería desconocida fuera de la instrucción for. Este uso restringido del nombre de la variable de control se conoce como el alcance de la variable. El alcance de una variable especifica en dónde puede utilizarse en un programa. En el capítulo 6 veremos con detalle el concepto de alcance. Listas de expresiones separadas por comas Las expresiones inicialización e incremento pueden ser listas de expresiones separadas por comas. Las comas, según el uso que se les da en estas expresiones, son operadores coma, los cuales garantizan que las listas de expresiones se evalúen de izquierda a derecha. El operador coma tiene la menor precedencia de todos los operadores de C++. El valor y tipo de una lista de expresiones separadas por comas es el valor y tipo de la expresión que está más a la derecha. El operador coma se utiliza con frecuencia en las instruccio- nes for. Su principal aplicación es permitir al programador utilizar varias expresiones de inicialización
  • 194. 162 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos y/o varias expresiones de incremento. Por ejemplo, puede haber distintas variables de control en una sola instrucción for que deban inicializarse e incrementarse. Buena práctica de programación 5.2 Coloque sólo expresiones que involucren a las variables de control en las secciones de inicia- lización e incremento de una instrucción for. Las expresiones en el encabezado de la instrucción for son opcionales Las tres expresiones en el encabezado de la instrucción for son opcionales (pero los dos separadores de punto y coma son obligatorios). Si se omite la condiciónDeContinuaciónDeCiclo, C++ asume que esta condición es verdadera, con lo cual se crea un ciclo infinito. Podríamos omitir la expresión de inicializa- ción si el programa inicializa la variable de control antes del ciclo. Podríamos omitir la expresión de in- cremento si el programa calcula el incremento mediante instrucciones dentro del cuerpo del for, o si no se necesita un incremento. La expresión de incremento actúa como una instrucción independiente La expresión de incremento en una instrucción for actúa como si fuera una instrucción independiente al final del cuerpo de la instrucción for. Por lo tanto, para contadores enteros, las expresiones contador = contador + 1 contadorr += 1 ++contador contador++ son todas equivalentes en la expresión de incremento (cuando no aparece ningún otro código ahí). La variable que se incrementa aquí no aparece en una expresión más grande, por lo que los operadores de preincremento y postdecremento tienen en realidad el mismo efecto. Error común de programación 5.2 Al colocar un punto y coma justo a la derecha del paréntesis derecho del encabezado de un for, el cuerpo de esa instrucción for se convierte en una instrucción vacía. Por lo general, esto es un error lógico. Instrucción for: notas y observaciones Las expresiones de inicialización, condición de continuación de ciclo e incremento de una instrucción for pueden contener expresiones aritméticas. Por ejemplo, si x = 2 y y = 10, y además, x y y no se mo- difican en el cuerpo del ciclo, el siguiente encabezado de for: for ( unsigned int j = x; j = 4 * x * y; j += y / x ) es equivalente a la instrucción for ( unsigned int j = 2; j = 80; j += 5 ) El “incremento” de una instrucción for también puede ser negativo, en cuyo caso sería realmente un decremento y el ciclo contaría en orden descendente (como se muestra en la sección 5.4). Si la condición de continuación de ciclo es inicialmente falsa, el programa no ejecutará el cuerpo de la instrucción for, sino que la ejecución continuará con la instrucción que siga inmediatamente después del for. Con frecuencia, la variable de control se imprime o utiliza en cálculos dentro del cuerpo de una instrucción for, pero este uso no es obligatorio. Por lo general, la variable de control se utiliza para controlar la repetición sin que se le mencione dentro del cuerpo de la instrucción for.
  • 195. 5.4 Ejemplos acerca del uso de la instrucción for 163 Tip para prevenir errores 5.2 Aunque el valor de la variable de control puede cambiarse en el cuerpo de una instrucción for, evite hacerlo, ya que esta práctica puede llevarlo a cometer errores sutiles. Diagrama de actividad de UML de la instrucción for El diagrama de actividad de UML de la instrucción for es similar al de la instrucción while (figura 4.6). La figura 5.4 muestra el diagrama de actividad de la instrucción for de la figura 5.2. El diagrama hace evidente que la inicialización ocurre sólo una vez antes de evaluar la condición de continuación de ciclo por primera vez, y que el incremento ocurre cada vez que se realiza una iteración, después de que se eje- cuta la instrucción del cuerpo. Observe que (además de un estado inicial, flechas de transición, una fusión, un estado final y varias notas) el diagrama sólo contiene estados de acción y una decisión. Determina si el ciclo debe continuar [contador 10] [contador = 10] unsigned int contador = 1 ++contador Muestra en pantalla el valor del contador Inicializa la variable de control Incrementa la variable de control cout counter ; cout contador ; Fig. 5.4  Diagrama de actividad de UML para la instrucción for de la figura 5.2. 5.4Ejemplos acerca del uso de la instrucción for Los siguientes ejemplos muestran métodos para modificar la variable de control en una instrucción for. En cada caso, escribimos el encabezado for apropiado. Observe el cambio en el operador relacional para los ciclos que decrementan la variable de control. a) Modificar la variable de control de 1 a 100 en incrementos de 1. for ( unsigned int i = 1; i = 100; ++i ) b) Modificar la variable de control de 100 a 0 en decrementos de 1. Cabe mencionar que usamos el tipo int para la variable de control en este encabezado de for. La condición no se vuelve
  • 196. 164 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos falsa sino hasta que la variable de control contenga -1, por lo que ésta debe ser capaz de alma- cenar números tanto positivos como negativos. for ( int i = 100; i = 0; --i ) c) Modificar la variable de control de 7 a 77 en incrementos de 7. for ( unsigned int i = 7; i = 77; i += 7 ) d) Modificar la variable de control de 20 a 2 en incrementos de -2. for ( unsigned int i = 20; i = 2; i -= 2 ) e) Modificar la variable de control con la siguiente secuencia de valores: 2, 5, 8, 11, 14, 17. for ( unsigned int i = 2; i = 17; i += 3 ) f) Modificar la variable de control con la siguiente secuencia de valores: 99, 88, 77, 66, 55. for ( unsigned int i = 99; i = 55; i -= 11 ) Error común de programación 5.3 No utilizar el operador relacional apropiado en la condición de continuación de un ciclo que cuente en forma regresiva (como usar incorrectamente i = 1 en vez de i = 1 en un ciclo que cuente en forma regresiva hasta llegar a 1) es generalmente un error lógico que produce resultados incorrectos al momento de ejecutar el programa. Error común de programación 5.4 No use operadores de igualdad (!= o ==) en una condición de continuación de ciclo si la variable de control del ciclo se incrementa o decrementa por más de 1. Por ejemplo, consi- dere el encabezado de la instrucción for ( unsigned int contador = 1; contador != 10; contador += 2 ). La prueba de continuación de ciclo contador != 10; nunca se volverá falsa (lo que produce un ciclo infinito) debido a que contador se incrementa por 2 después de cada iteración. Aplicación: sumar los enteros pares del 2 al 20 El programa de la figura 5.5 utiliza una instrucción for para sumar los enteros pares del 2 al 20. Cada iteración del ciclo (líneas 11 y 12) suma el valor de la variable de control numero a la variable total. 1 // Fig. 5.5: fig05_05.cpp 2 // Suma de enteros con la instrucción for. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 unsigned int total = 0; // inicializa el total 9 10 // obtiene el total de los enteros pares del 2 al 20 11 for ( unsigned int numero = 2; numero = 20; numero += 2 ) 12 total += numero; 13 14 cout La suma es total endl; // muestra los resultados 15 } // fin de main Fig. 5.5  Suma de enteros con la instrucción for (parte 1 de 2).
  • 197. 5.4 Ejemplos acerca del uso de la instrucción for 165 La suma es 110 El cuerpo de la instrucción for de la figura 5.5 podría mezclarse con la porción del incremento del encabezado for mediante el uso del operador coma, como se muestra a continuación: for ( unsigned int numero = 2; // inicialización numero = 20; // condición de continuación de ciclo total += numero, numero += 2 ) // calcula el total e incrementa ; // cuerpo vacío Buena práctica de programación 5.3 Aunque las instrucciones antes de un for y las instrucciones en el cuerpo de un for común- mente se pueden fusionar en el encabezado del for, esto podría hacer que el programa fuera más difícil de leer, mantener, modificar y depurar. Aplicación: cálculo del interés compuesto Considere el siguiente enunciado del problema: Una persona invierte $1000.00 en una cuenta de ahorro que produce el 5 por ciento de interés. Suponiendo que todo el interés se deposita en la cuenta, calcule e imprima el monto de dinero en la cuenta al final de cada año, durante 10 años. Use la siguiente fórmula para determinar los montos: c = p ( 1 + r )n en donde p es el monto que se invirtió originalmente (es decir, el monto principal) t es la tasa de interés anual n es el número de años y c es la cantidad depositada al final del nésimo año. La instrucción for (figura 5.6, líneas 21 a 28) ejecuta el cálculo indicado por cada uno de los 10 años que el dinero permanece en depósito, variando una variable de control de 1 a 10, en incrementos de 1. C++ no incluye un operador de exponenciación, por lo que utilizamos la función pow de la biblioteca estándar (línea 24). La función pow(x, y) calcula el valor de x elevado a la yésima potencia. En este ejem- plo, la expresión algebraica (1 + r)n se escribe como pow(1.0 + tasa, anio), en donde la variable tasa representa a r y la variable anio representa a n. La función pow recibe dos argumentos de tipo double y devuelve un valor double. 1 // Fig. 5.6: fig05_06.cpp 2 // Cálculo del interés compuesto con for. 3 #include iostream 4 #include iomanip 5 #include cmath // biblioteca de matemáticas estándar 6 using namespace std; 7 8 int main() 9 { Fig. 5.5  Suma de enteros con la instrucción for (parte 2 de 2). Fig. 5.6  Cálculo del interés compuesto con for (parte 1 de 2).
  • 198. 166 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos 10 double monto; // monto a depositar al final de cada año 11 double principal = 1000.0; // monto inicial antes del interés 12 double tasa = .05; // atasa de interés anual 13 14 // muestra los encabezados 15 cout Anio setw( 21 ) Monto en deposito endl; 16 17 // establece el formato de número de punto flotante 18 cout fixed setprecision( 2 ); 19 20 // calcula el monto en depósito para cada uno de los diez años 21 for ( unsigned int anio = 1; anio = 10; ++anio ) 22 { 23 // calcula el nuevo monto para el año especificado 24 monto = principal * pow( 1.0 + tasa, anio ); 25 26 // muestra el año y el monto 27 cout setw( 4 ) anio setw( 21 ) monto endl; 28 } // fin de for 29 } // fin de main Anio Monto en deposito 1 1050.00 2 1102.50 3 1157.63 4 1215.51 5 1276.28 6 1340.10 7 1407.10 8 1477.46 9 1551.33 10 1628.89 Este programa no se compilará si no se incluye el encabezado cmath (línea 5). La función pow requiere dos argumentos double. La variable anio es un entero. El encabezado cmath incluye infor- mación que indica al compilador cómo convertir el valor de anio en una representación double tempo- ral antes de llamar a la función. Esta información se incluye en el prototipo de la función pow. En el capítulo 6 veremos un resumen de las demás funciones matemáticas de la biblioteca. Error común de programación 5.5 Olvidar incluir el encabezado apropiado al utilizar funciones de la biblioteca estándar (por ejemplo, cmath en un programa que utilice funciones matemáticas de la biblioteca) es un error de compilación. Una advertencia en relación con el uso de los tipos float o double para cantidades monetarias En las líneas 10 a 12 se declaran las variables double llamadas monto, principal y tasa. Hicimos esto para simplificar, ya que estamos tratando con partes fraccionarias de dólares, y necesitamos un tipo que permita puntos decimales en sus valores. Por desgracia, esto puede ocasionar problemas. He aquí una explicación simple de lo que puede salir mal al utilizar float o double para represen- tar montos en dólares (asumiendo que se utiliza setprecision(2) para especificar dos dígitos de Fig. 5.6  Cálculo del interés compuesto con for (parte 2 de 2).
  • 199. 5.4 Ejemplos acerca del uso de la instrucción for 167 precisión a la hora de imprimir): dos montos en dólares almacenados en el equipo podrían ser 14.234 (que se imprime como 14.23) y 18.673 (que se imprime como 18.67). Al sumar estos montos, producen la suma interna 32.907, la cual se imprime como 32.91. Por ende, el resultado podría aparecer como 14.23 + 18.67 ------- 32.91 pero ¡alguien que sumara los números individuales que aparecen impresos esperaría la suma de 32.90! ¡Ya ha sido advertido! En los ejercicios exploramos el uso de enteros para realizar cálculos monetarios. [Nota: algunos distribuidores independientes venden bibliotecas de clases de C++ que realizan cálculos monetarios precisos]. Uso de manipuladores de flujo para dar formato a los resultados numéricos La instrucción de salida en la línea 18 antes del ciclo for, y la instrucción de salida en la línea 27 en el ciclo for se combinan para imprimir los valores de las variables anio y monto, con el formato especifi- cado por los manipuladores de flujo parametrizados setprecision y setw, y por el manipulador de flujo no parametrizado fixed. El manipulador de flujo setw(4) especifica que el siguiente valor a im- primir debe aparecer en una anchura de campo de 4; por ejemplo, cout imprime el valor con al menos 4 posiciones de caracteres. Si el valor a imprimir es menor que 4 caracteres de ancho, de manera prede- terminada se imprime justificado a la derecha. Si el valor a imprimir es mayor que 4 caracteres, la an- chura de campo se extiende a la derecha para dar cabida a todo el valor. Para indicar que los valores deben imprimirse justificados a la izquierda, simplemente imprima el manipulador de flujo no parametri- zado left (que se encuentra en el encabezado iostream). La justificación a la derecha se puede res- taurar al imprimir el manipulador de flujo no parametrizado right. El otro formato en las instrucciones de salida indica que la variable monto se imprime como un valor de punto fijo con un punto decimal (especificado en la línea 18 con el manipulador de flujo fixed), justificado a la derecha en un campo de 21 posiciones de caracteres (especificado en la línea 27 con setw(21)) y dos dígitos de precisión a la derecha del punto decimal (especificado en la línea 18 con el manipulador setprecision(2)). Aplicamos los manipuladores de flujo fixed y setprecision al flujo de salida (es decir, cout) antes del ciclo for, ya que estas opciones de formato están en vigor hasta que se modifican; a dichas opciones se les conoce como opciones pegajosas y no necesitan aplicarse durante cada iteración del ciclo. Sin embargo, la anchura de campo especificada con setw sólo se aplica al siguien- te valor que se imprime. En el capítulo 13, Entrada/salida de flujos: un análisis detallado, hablaremos sobre las poderosas herramientas de formato de entrada/salida de C++. El cálculo 1.0 + tasa, que aparece como argumento para la función pow, está contenido en el cuerpo de la instrucción for. De hecho, este cálculo produce el mismo resultado durante cada iteración del ciclo, por lo que repetirlo es un desperdicio; debería realizarse una vez antes del ciclo. Asegúrese de probar nuestro problema de Peter Minuit en el ejercicio 5.29. Este problema demues- tra las maravillas del interés compuesto. Tip de rendimiento 5.1 Evite colocar expresiones cuyos valores no cambien dentro de los ciclos. Si lo hace, mu- chos de los compiladores optimizadores sofisticados de la actualidad colocarán de mane- ra automática dichas expresiones fuera de los ciclos en el código de lenguaje máquina generado.
  • 200. 168 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos Tip de rendimiento 5.2 Muchos compiladores contienen características de optimización que mejoran el rendi- miento del código que el programador escribe, sin embargo, es mejor escribir buen código desde el principio. 5.5 Instrucción de repetición do...while La instrucción de repetición do...while es similar a la instrucción while. En la instrucción while, la evaluación de la condición de continuación de ciclo ocurre al principio del ciclo, antes de ejecutar su cuerpo. La instrucción do...while evalúa la condición de continuación de ciclo después de ejecutar el cuerpo del ciclo; por lo tanto, el cuerpo del ciclo siempre se ejecutará cuando menos una vez. La figura 5.7 utiliza una instrucción do...while para imprimir los números del 1 al 10. Al entrar a la instrucción do...while, en la línea 12 se imprime el valor de contador y en la línea 13 se incremen- ta contador. Después el programa evalúa la prueba de continuación de ciclo al final del mismo (línea 14). Si la condición es verdadera, el ciclo continúa a partir de la primera instrucción del cuerpo en la instrucción do...while (línea 12). Si la condición es falsa, el ciclo termina y el programa continúa con la siguiente instrucción después del ciclo (línea 16). 1 // Fig. 5.7: fig05_07.cpp 2 // La instrucción de repetición do...while. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 unsigned int contador = 1; // inicializa contador 9 10 do 11 { 12 cout contador ; // muestra contador 13 ++counter; // incrementa contador 14 } while ( contador = 10 ); // fin de do...while 15 16 cout endl; // imprime una nueva línea 17 } // fin de main 1 2 3 4 5 6 7 8 9 10 Fig. 5.7  La instrucción de repetición do...while. Diagrama de actividad de UML de la instrucción do...while La figura 5.8 contiene el diagrama de actividad de UML para la instrucción do...while, el cual hace evidente que la condición de continuación de ciclo no se evalúa sino hasta después que el ciclo ejecuta su cuerpo, por lo menos una vez. Compare este diagrama de actividad con el de la instrucción while (figura 4.6). Llaves en una instrucción do...while No es necesario utilizar llaves en la instrucción do...while si sólo hay una instrucción en el cuerpo; sin embargo, la mayoría de los programadores incluyen las llaves para evitar la confusión entre las instruc- ciones while y do...while. Por ejemplo: while ( condición )
  • 201. 5.6 Instrucción de selección múltiple switch 169 Determina si debe continuar el ciclo [contador 10] [contador = 10] ++contador Muestra el valor del contador Incrementa la variable de control cout contador ; Fig. 5.8  Diagrama de actividad de UML de la instrucción de repetición do...while de la figura 5.7. generalmente se utiliza como encabezado de una instrucción while. Una instrucción do...while sin llaves, alrededor de un cuerpo con una sola instrucción, aparece así: do instrucción while ( condición ); lo cual puede ser confuso. Podríamos malinterpretar la última línea [while( condición );] como una instrucción while que contiene como cuerpo una instrucción vacía. Por ello, el do...while con una instrucción se escribe a menudo de la siguiente manera, para evitar confusión: do { instrucción } while ( condición ); 5.6 Instrucción de selección múltiple switch C++ cuenta con la instrucción switch de selección múltiple para realizar muchas acciones distintas, con base en los posibles valores de una variable o expresión. Cada acción se asocia con un valor de una expresión integral constante (es decir, una combinación de constantes tipo carácter y constantes en- teras que se evalúan como un valor entero constante). La clase LibroCalificaciones con la instrucción switch para contar las calificaciones A, B, C, D y F Esta siguiente versión de la clase LibroCalificaciones pide al usuario que introduzca un conjunto de calificacionesenformadeletrasydespuésmuestraunresumendelnúmerodeestudiantesquerecibieron
  • 202. 170 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos cada calificación. La clase utiliza una instrucción switch para determinar si cada calificación introduci- da es el equivalente de A, B, C, D o F, y para incrementar el contador de la calificación apropiada. La clase LibroCalificaciones está definida en la figura 5.9, y las definiciones de sus funciones miembro aparecen en la figura 5.10. La figura 5.11 muestra entradas y salidas de ejemplo del programa main que utiliza la clase LibroCalificaciones para procesar un conjunto de calificaciones. Al igual que las versiones anteriores de la definición de la clase, esta definición de LibroCalificaciones (figura5.9)contieneprototiposdefuncionesparalasfuncionesmiembroestablecerNombreCurso (línea11), obtenerNombreCurso (línea12)ymostrarMensaje (línea13),asícomoelconstructordelaclase(línea10).La definicióndelaclasetambiéndeclaraelmiembrodedatosprivate llamadonombreCurso (línea17). Encabezado de la clase LibroCalificaciones Ahora la clase LibroCalificaciones (figura 5.9) contiene cinco miembros de datos private adiciona- les (líneas 18 a 22): variables contador para cada categoría de calificación (A, B, C, D y F). La clase también contiene dos funciones miembro public adicionales: recibirCalificaciones y mostrar- ReporteCalificaciones. La función miembro recibirCalificaciones (declarada en la línea 14) lee un número arbitrario de calificaciones de letra del usuario mediante el uso de la repetición controlada por centinela, y actualiza el contador de calificaciones apropiado para cada calificación introducida. La función miembro mostrarReporteCalificaciones (declarada en la línea 15) imprime un reporte que contiene el número de estudiantes que recibieron cada calificación de letra. 1 // Fig. 5.9: LibroCalificaciones.h 2 // Definición de la clase LibroCalificaciones que cuenta calificaciones de letras. 3 // Las funciones miembro se definen en LibroCalificaciones.cpp 4 #include string // el programa usa la clase string estándar de C++ 5 6 // definición de la clase LibroCalificaciones 7 class LibroCalificaciones 8 { 9 public: 10 explicit LibroCalificaciones( std::string ); // inicializa el nombre del curso 11 void establecerNombreCurso( std::string ); // establece el nombre del curso 12 std::string obtenerNombreCurso() const; // obtiene el nombre del curso 13 void mostrarMensaje() const; // muestra un mensaje de bienvenida 14 void recibirCalificaciones(); // recibe un número arbitrario de calificaciones del usuariouser 15 void mostrarReporteCalificaciones() const; // muestra un reporte con base en la entrada del usuario 16 private: 17 std::string nombreCurso; // nombre del curso para este LibroCalificaciones 18 unsigned int aCuenta; // cuenta de calificaciones A 19 unsigned int bCuenta; // cuenta de calificaciones B 20 unsigned int cCuenta; // cuenta de calificaciones C 21 unsigned int dCuenta; // cuenta de calificaciones D 22 unsigned int fCuenta; // cuenta de calificaciones F 23 }; // fin de la clase LibroCalificaciones Fig. 5.9  Definición de la clase LibroCalificaciones que cuenta calificaciones de letras. Archivo de código fuente de la clase LibroCalificaciones El archivo de código fuente LibroCalificaciones.cpp (figura 5.10) contiene las definiciones de las funciones miembro para la clase LibroCalificaciones. Las líneas 11 a 15 en el constructor inicializan los cinco contadores de calificaciones con 0; cuando se crea un objeto LibroCalificaciones por pri- mera vez, no se han introducido aún calificaciones. Estos contadores se incrementan en la función miembro recibirCalificaciones a medida que el usuario introduce las calificaciones. Las definicio-
  • 203. 5.6 Instrucción de selección múltiple switch 171 nes de las funciones miembro establecerNombreCurso, obtenerNombreCurso y mostrarMensaje son idénticas a las de las versiones anteriores de la clase LibroCalificaciones. 1 // Fig. 5.10: LibroCalificaciones.cpp 2 // Definiciones de las funciones miembro para la clase LibroCalificaciones que 3 // utiliza una instrucción switch para contar calificaciones A, B, C, D y F. 4 #include iostream 5 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 6 using namespace std; 7 8 // el constructor inicializa nombreCurso con la cadena suministrada como 9 // argumento; inicializa los miembros de datos contadores a 0 10 LibroCalificaciones::LibroCalificaciones( string nombre ) 11 : aCuenta( 0 ), // inicializa cuenta de calificaciones A con 0 12 bCuenta( 0 ), // inicializa cuenta de calificaciones B con 0 13 cCuenta( 0 ), // inicializa cuenta de calificaciones C con 0 14 dCuenta( 0 ), // inicializa cuenta de calificaciones D con 0 15 fCuenta( 0 ) // inicializa cuenta de calificaciones F con 0 16 { 17 establecerNombreCurso( nombre ); 18 } // fin del constructor de LibroCalificaciones 19 20 // función para establecer el nombre del curso; limita el nombre a 25 caracteres o menos 21 void LibroCalificaciones::establecerNombreCurso( string nombre ) 22 { 23 if ( nombre.size() = 25 ) // si nombre tiene 25 caracteres o menos 24 nombreCurso = nombre; // almacena el nombre del curso en el objeto 25 else // si el nombre es mayor que 25 caracteres 26 { // establece nombreCurso a los primeros 25 caracteres del parámetro nombre 27 nombreCurso = nombre.substr( 0, 25 ); // selecciona los primeros 25 caracteres 28 cerr El nombre nombre excede la longitud maxima (25).n 29 Se limito nombreCurso a los primeros 25 caracteres.n endl; 30 } // fin de if...else 31 } // fin de la función establecerNombreCurso 32 33 // función para obtener el nombre del curso 34 string LibroCalificaciones::obtenerNombreCurso() const 35 { 36 return nombreCurso; 37 } // fin de la función obtenerNombreCursoe 38 39 // muestra un mensaje de bienvenida para el usuario de LibroCalificaciones 40 void LibroCalificaciones::mostrarMensaje() const 41 { 42 // esta instrucción llama a obtenerNombreCurso para obtener el 43 // nombre del curso que representa este LibroCalificaciones 44 cout Bienvenido al libro de calificaciones paran obtenerNombreCurso() !n 45 endl; 46 } // fin de la función mostrarMensaje 47 48 // recibe un número arbitrario de calificaciones del usuario; actualiza el contador de calificaciones 49 void LibroCalificaciones::recibirCalificaciones() 50 { Fig. 5.10  Clase LibroCalificaciones que usa la instrucción switch para contar calificaciones de letras (parte 1 de 3).
  • 204. 172 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos 51 int calificacion; // calificacion introducida por el usuario 52 53 cout Escriba las calificaciones de letra. endl 54 Escriba el caracter EOF para terminar la entrada. endl; 55 56 // itera hasta que el usuario escriba la secuencia de fin de archivo 57 while ( ( calificacion = cin.get() ) != EOF ) 58 { 59 // determina cuál calificación se introdujo 60 switch ( calificacion ) // instrucción switch anidada en el while 61 { 62 case 'A': // calificacion fue A mayúscula 63 case 'a': // o a minúscula 64 ++aCuenta; // incrementa aCuenta 65 break; // es necesario salir del switch 66 67 case 'B': // calificacion fue B mayúscula 68 case 'b': // o b minúscula 69 ++bCuenta; // incrementa bCuenta 70 break; // sale del switch 71 72 case 'C': // calificacion fue C mayúscula 73 case 'c': // o c minúscula 74 ++cCuenta; // incrementa cCuenta 75 break; // sale del switch 76 77 case 'D': // calificacion fue D mayúscula 78 case 'd': // o d minúscula 79 ++dCuenta; // incrementa dCuenta 80 break; // sale del switch 81 82 case 'F': // calificacion fue F mayúscula 83 case 'f': // o f minúscula 84 ++fCuenta; // incrementa fCuenta 85 break; // sale del switch 86 87 case 'n': // ignora caracteres de nueva línea, 88 case 't': // tabuladores, 89 case ' ': // y espacios en la entrada 90 break; // sale del switch 91 92 default: // atrapa todos los demás caracteres 93 cout Se introdujo una letra de calificacion incorrecta. 94 Escriba una nueva calificación. endl; 95 break; // opcional; saldrá del switch de todas formas 96 } // fin de switch 97 } // fin de while 98 } // fin de la función recibirCalificaciones 99 100 // muestra un reporte con base en las calificaciones introducidas por el usuario 101 void LibroCalificaciones::mostrarReporteCalificaciones() const 102 { Fig. 5.10  Clase LibroCalificaciones que usa la instrucción switch para contar calificaciones de letras (parte 2 de 3).
  • 205. 5.6 Instrucción de selección múltiple switch 173 103 // imprime resumen de resultados 104 cout nnNumero de estudiantes que recibieron cada calificacion de letra: 105 nA: aCuenta // muestra el número de calificaciones A 106 nB: bCuenta // muestra el número de calificaciones B 107 nC: cCuenta // muestra el número de calificaciones C 108 nD: dCuenta // muestra el número de calificaciones D 109 nF: fCuenta // muestra el número de calificaciones F 110 endl; 111 } // fin de la función mostrarReporteCalificaciones Lectura de los datos de entrada tipo carácter El usuario introduce las calificaciones de letras para un curso en la función miembro recibirCalifi- caciones (líneas 49 a 98). En el encabezado del while, en la línea 57, la asignación entre paréntesis ( calificacion = cin.get() ) se ejecuta primero. La función cin.get() lee un carácter del teclado y lo almacena en la variable entera calificacion (declarada en la línea 51). Por lo general, los caracteres se almacenan en las variables de tipo char; sin embargo, los caracteres se pueden almacenar en cualquier tipo de datos entero, ya que se garantiza que los tipos short, int, long y long long son por lo menos tan grandes como el tipo char. Por ende, podemos tratar a un carácter como entero o como carácter, dependiendo de su uso. Por ejemplo, la instrucción cout El caracter ( 'a' ) tiene el valor static_cast int ( 'a' ) endl; imprime el carácter a y su valor entero de la siguiente manera: El caracter (a) tiene el valor 97 El entero 97 es la representación numérica del carácter en la computadora. En el apéndice B se muestran los caracteres y sus equivalentes decimales del conjunto de caracteres ASCII (Código estándar esta- dounidense para el intercambio de información). En general, las instrucciones de asignación tienen el valor que se asigna a la variable en el lado iz- quierdo del signo =. Por ende, el valor de la expresión de asignación calificacion = cin.get( ) es el mismo que el valor devuelto por cin.get( ) y que se asigna a la variable calificacion. El hecho de que las expresiones de asignación tengan valores puede ser útil para asignar el mismo valor a distintas variables. Por ejemplo, a = b = c = 0; evalúa primero la asignación c = 0 (ya que el operador = asocia de derecha a izquierda). Después, a la variable b se le asigna el valor de c = 0 (que es 0). Luego, a la variable a se le asigna el valor de b = (c = 0) (que también es 0). En el programa, el valor de calificacion = cin.get() se compara con el valor de EOF (un símbolo cuyo acrónimo significa “fin de archivo”). Utilizamos EOF (que por lo general tiene el valor –1) como el valor centinela. Sin embargo, no debe escribir el valor –1, ni las letras EOF, como el valor centinela. En vez de ello, debe escribir una combinación de teclas dependiente del sistema, que repre- sente el “fin de archivo” para indicar que no hay más datos que introducir. EOF es una constante entera simbólica que se incluye en el programa mediante el encabezado iostream1 . Si el valor asignado a calificacion es igual a EOF, el ciclo while (líneas 57 a 97) termina. Hemos optado por representar los caracteres introducidos en este programa como valores int, ya que EOF tiene el tipo int. 1 Para compilar este programa, algunos compiladores requieren el encabezado cstdio que define a EOF. Fig. 5.10  Clase LibroCalificaciones que usa la instrucción switch para contar calificaciones de letras (parte 3 de 3).
  • 206. 174 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos Introducción del indicador EOF En los sistemas OS X/Linux/UNIX y muchos otros, el fin de archivo se introduce escribiendo la se- cuencia Ctrl d en una línea por sí sola. Esta notación significa que hay que oprimir al mismo tiempo la tecla Ctrl y la tecla d. En otros sistemas como Microsoft Windows, para introducir el fin de archivo se escribe Ctrl z [Nota: en algunos casos, hay que oprimir Intro después de escribir la secuencia de teclas anterior. Ade- más, generalmente se muestran los caracteres ^Z en la pantalla para representar el fin de archivo, como se muestra en la figura 5.11]. Tip de portabilidad 5.1 Las combinaciones de teclas para introducir el fin de archivo son dependientes del sistema. Tip de portabilidad 5.2 La acción de evaluar la constante simbólica EOF en vez de -1 hace que los programas sean más portables. El estándar de C, del cual C++ adopta la definición de EOF, establece que EOF es un valor integral negativo, por lo que EOF podría tener distintos valores en diferen- tes sistemas. En este programa, el usuario introduce las calificaciones mediante el teclado. Cuando el usuario oprime la tecla Intro (o Return), la función cin.get() lee los caracteres, uno a la vez. Si el carácter intro- ducido no es el fin de archivo, el flujo de control entra a la instrucción switch (figura 5.10, líneas 60 a 96), la cual incrementa el contador de calificaciones de letras apropiado. Detalles acerca de la instrucción switch La instrucción switch consiste en una serie de etiquetas case y un caso default opcional. Estas eti- quetas se utilizan en este ejemplo para determinar qué contador incrementar, con base en una califica- ción. Cuando el flujo de control llega a la instrucción switch, el programa evalúa la expresión entre paréntesis (es decir, calificacion) que está después de la palabra clave switch (línea 60). A ésta se le conoce como expresión de control. La instrucción switch compara el valor de la expresión de control con cada etiqueta case. Suponga que el usuario introduce la letra C como una calificación. El programa compara C con cada case en la instrucción switch. Si ocurre una coincidencia (case 'C' : en la línea 72), el programa ejecuta las instrucciones para esa etiqueta case. Para la letra C, en la línea 74 se incrementa cCuenta en 1. La instrucción break (línea 75) hace que el control del programa se rea- nude en la primera instrucción después del switch; en este programa, el control se transfiere a la línea 97. Esta línea marca el final del cuerpo del ciclo while que recibe las calificaciones (líneas 57 a 97), por lo que el control fluye hasta la condición del while (línea 57) para determinar si el ciclo debe continuar ejecutándose. Las etiquetas case en nuestra instrucción switch evalúan explícitamente las versiones en mi- núscula y mayúscula de las letras A, B, C, D y F. Observe las etiquetas case en las líneas 62 y 63, que evalúan los valores 'A' y 'a' (ambos representan la calificación A). Al listar las etiquetas case de esta forma consecutiva, sin instrucciones entre ellas, pueden ejecutar el mismo conjunto de instrucciones; cuando la expresión de control se evalúe como 'A' o 'a', se ejecutarán las instrucciones de las líneas 64 y 65. Cada etiqueta case puede tener varias instrucciones. La instrucción de selección switch no requiere llaves alrededor de varias instrucciones en cada case.
  • 207. 5.6 Instrucción de selección múltiple switch 175 Sin instrucciones break, cada vez que ocurra una concordancia en la instrucción switch, se ejecu- tarán las instrucciones para esa etiqueta case y todas las etiquetas case subsiguientes, hasta llegar a una instrucción break o al final de la instrucción switch. Esta característica es perfecta para escribir un programa conciso, que muestre la canción iterativa “Los Doce Días de Navidad” en el ejercicio 5.28. Error común de programación 5.6 Olvidar una instrucción break cuando se necesita una en una instrucción switch es un error lógico. Error común de programación 5.7 Omitir el espacio entre la palabra case y el valor integral que se está evaluando en una instrucción switch (como escribir case3: en vez de case 3:) es un error lógico. La instruc- ción switch no ejecutará las acciones apropiadas cuando su expresión de control tenga un valor de 3. Proporcionar un caso default Si no ocurre una coincidencia entre el valor de la expresión de control y una etiqueta case, se ejecuta el caso default (líneas 92 a 95). Utilizamos el caso default en este ejemplo para procesar todos los valores de la expresión de control que no sean calificaciones válidas o caracteres de nueva línea, tabuladores o espacios. Si no ocurre una coincidencia, se ejecuta el caso default y las líneas 93-94 imprimen un men- saje de error indicando que se introdujo una letra de calificación incorrecta. Si no ocurre una coinciden- cia en una instrucción switch que no contenga un caso default, el control del programa continúa con la primera instrucción después de la instrucción switch. Tip para prevenir errores 5.3 Proporcione un caso default en las instrucciones switch. Los casos que no se evalúen en forma explícita en una instrucción switch sin un caso default deben ignorarse. Al in- cluir un caso default, nos enfocamos en la necesidad de procesar las condiciones excepcio- nales. Existen situaciones en las que no se necesita un procesamiento default. Aunque las cláusulas case y la cláusula del caso default en una instrucción switch pueden ocurrir en cualquier orden, es una práctica común colocar la cláusula default al último. Buena práctica de programación 5.4 El último case en una instrucción switch no requiere una instrucción break. Algunos programadores incluyen este break por claridad y por simetría con otros casos. Ignorar los caracteres de nueva línea, tabuladores y de espacio en blanco en la entrada Las líneas 87 a 90 en la instrucción switch de la figura 5.10 hacen que el programa omita los caracteres de nueva línea, tabuladores y espacios en blanco. Al leer los caracteres uno a la vez, pueden producirse ciertos problemas. Para hacer que el programa lea los caracteres, debemos enviarlos a la computadora, oprimiendo la tecla Intro en el teclado. Con esto se coloca un carácter de nueva línea en la entrada, después del carácter que deseamos procesar. A menudo, este carácter de nueva línea se debe procesar de una manera especial. Al incluir las cláusulas case anteriores en nuestra instrucción switch, evitamos que se imprima el mensaje de error en el caso default cada vez que nos encontramos un carácter de nueva línea, tabulador o espacio. Prueba de la clase LibroCalificaciones En la figura 5.11 se crea un objeto LibroCalificaciones (línea 8). En la línea 10 se invoca la función miembromostrarMensaje delobjetoparaimprimirenpantallaunmensajedebienvenidaparaelusuario. En la línea 11 se invoca la función miembro recibirCalificaciones del objeto para leer un conjunto de calificaciones del usuario y llevar el registro de cuántos estudiantes recibieron cada calificación. La
  • 208. 176 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos ventana de resultados en la figura 5.11 muestra un mensaje de error en respuesta a la acción de introducir una calificación inválida (es decir, E). En la línea 12 se invoca la función miembro mostrarReporteCali- ficaciones de LibroCalificaciones (definida en las líneas 101 a 111 de la figura 5.10), la cual imprime enpantallaunreporteconbaseenlascalificacionesintroducidas(comoenlosresultadosdelafigura5.11). 1 // Fig. 5.11: fig05_11.cpp 2 // Creación de un objeto LibroCalificaciones e invocación de sus funciones miembro. 3 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 4 5 int main() 6 { 7 // crea un objeto LibroCalificaciones 8 LibroCalificaciones miLibroCalificaciones( CS101 Programacion en C++ ); 9 10 miLibroCalificaciones.mostrarMensaje(); // muestra el mensaje de bienvenida 11 miLibroCalificaciones.recibirCalificaciones(); // lee las calificaciones del usuario 12 miLibroCalificaciones.mostrarReporteCalificaciones(); // muestra el reporte con base en las calificaciones 13 } // fin de main Bienvenido al libro de calificaciones para CS101 Programacion en C++! Escriba las calificaciones de letra. Escriba el caracter EOF para terminar la entrada. a B c C A d f C E Se introdujo una letra de calificacion incorrecta. Escriba una nueva calificacion. D A b ^Z Numero de estudiantes que recibieron cada calificacion de letra: A: 3 B: 2 C: 3 D: 2 F: 1 Fig. 5.11  Creación de un objeto LibroCalificaciones e invocación de sus funciones miembro. Diagrama de actividad de UML de la instrucción switch La figura 5.12 muestra el diagrama de actividad de UML para la instrucción de selección múltiple switch general.Lamayoríadelasinstruccionesswitch utilizanunainstrucciónbreak encadacase para
  • 209. 5.6 Instrucción de selección múltiple switch 177 terminar la instrucción switch después de procesar el case. La figura 5.12 enfatiza esto al incluir ins- trucciones break en el diagrama de actividad. Sin la instrucción break, después de procesar un case el control no se transferiría a la primera instrucción después de la instrucción switch. En vez de ello, se transferiría a las acciones del siguiente case. ... Acción(es) del caso default Acción(es) del case a Acción(es) del case z Acción(es) del case b break break break case b case z case a [falso] [verdadero] [verdadero] [verdadero] [falso] [falso] Fig. 5.12  Diagrama de actividad de UML de la instrucción switch de selección múltiple con instrucciones break. El diagrama hace evidente que la instrucción break al final de un case hace que el control salga de la instrucción switch de inmediato. De nuevo, observe que (además de un estado inicial, flechas de tran- sición, un estado final y varias notas) el diagrama contiene estados de acción y decisiones. Además, el diagrama utiliza símbolos de fusión para fusionar las transiciones de las instrucciones break hacia el es- tado final. Cuando utilice la instrucción switch, recuerde que cada case sólo se puede usar para evaluar una expresión integral constante: cualquier combinación de constantes carácter y enteras que se evalúen como un valor entero constante. Una constante de tipo carácter se representa como el carácter específico entre comillas sencillas, como 'A'. Una constante entera es simplemente un valor entero. Además, cada etiqueta case puede especificar sólo una expresión integral constante. Error común de programación 5.8 Especificar una expresión integral no constante en la etiqueta case de una instrucción switch es un error de sintaxis. Error común de programación 5.9 Proporcionar etiquetas case idénticas en una instrucción switch es un error de compi- lación.
  • 210. 178 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos En el capítulo 12 presentaremos una manera más elegante de implementar la lógica con switch. Utilizaremos una técnica llamada polimorfismo para crear programas que sean con frecuencia más claros, concisos, fáciles de mantener y de extender que los programas que utilizan la lógica de switch. Observaciones sobre los tipos de datos C++cuentacontiposdedatosdetamañosflexibles(veaelapéndiceC,Tiposfundamentales).Porejemplo, distintas aplicaciones podrían requerir enteros de distintos tamaños. C++ proporciona varios tipos de enteros. El rango de valores enteros para cada tipo de datos depende de la plataforma. Además de los tipos int y char, C++ proporciona los tipos short (una abreviación de short int), long (una abrevia- ción de long int) y long long (una abreviación de long long int). El rango mínimo de valores para los enteros short es de -32767 a 32767. Para la amplia mayoría de los cálculos con enteros, basta con usarenteroslong.Elrangomínimodevaloresparalosenteroslong esde-2147483647a2147483647. En la mayoría de las computadoras, los valores int son equivalentes a short o long. El rango de valores para un int es por lo menos el mismo que para los enteros short, y no más grande que para los enteros long. El tipo de datos char puede utilizarse para representar cualquiera de los caracteres en el conjunto de caracteres de la computadora. También se puede utilizar para representar enteros pequeños. Inicializadores dentro de la clase en C++11 C++ nos permite proporcionar un valor predeterminado para un miembro de datos al declararlo en la declaración de la clase. Por ejemplo, las líneas 19 a 23 de la figura 5.9 podrían haber inicializado los miembros de datos aCuenta, bCuenta, cCuenta, dCuenta y fCuenta con 0, como se muestra a conti- nuación: unsigned int aCuenta = 0; // cuenta de calificaciones A unsigned int bCuenta = 0; // cuenta de calificaciones B unsigned int cCuenta = 0; // cuenta de calificaciones C unsigned int dCuenta = 0; // cuenta de calificaciones D unsigned int fCuenta = 0; // cuenta de calificaciones F en vez de inicializarlos en el constructor de la clase (figura 5.10, líneas 10 a 18). En capítulos posterio- res seguiremos hablando sobre los inicializadores dentro de la clase y le mostraremos cómo nos permi- ten realizar ciertas inicializaciones de miembros de datos que no eran posibles en versiones anteriores de C++. 5.7 Instrucciones break y continue C++ proporciona también las instrucciones break y continue para alterar el flujo de control. La sección anterior mostró cómo se puede utilizar break para terminar la ejecución de la instrucción switch. En esta sección veremos cómo usar break en una instrucción de repetición. Instrucción break Cuando la instrucción break se ejecuta en una instrucción while, for, do...while, o switch, ocasiona la salida inmediata de esa instrucción. La ejecución del programa continúa con la siguiente instrucción. Los usos comunes de la instrucción break son para escapar anticipadamente de un ciclo, o para omitir el resto de una instrucción switch. La figura 5.13 demuestra el uso de una instrucción break (línea 13) para salir de una instrucción de repetición for. Cuando la instrucción if determina que conteo es 5, se ejecuta la instrucción break. Esto termina la instrucción for y el programa continúa a la línea 18 (inmediatamente después de la instrucción for), la cual muestra un mensaje indicando el valor de la variable de control cuando terminó el ciclo. La ins- trucción for ejecuta su cuerpo por completo sólo cuatro veces en vez de 10. La variable de control
  • 211. 5.7 Instrucciones break y continue 179 1 // Fig. 5.13: fig05_13.cpp 2 // instrucción break para salir de una instrucción for. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 unsigned int cuenta; // la variable de control también se usa después de que termina el ciclo 9 10 for ( cuenta = 1; cuenta = 10; ++cuenta ) // itera 10 veces 11 { 12 if ( cuenta == 5 ) 13 break; // termina el ciclo sólo si cuenta es 5 14 15 cout cuenta ; 16 } // fin de for 17 18 cout nSalio del ciclo en cuenta = cuenta endl; 19 } // fin de main 1 2 3 4 Salio del ciclo en cuenta = 5 Fig. 5.13  Instrucción break para salir de una instrucción for. cuenta se define fuera del encabezado de la instrucción for, por lo que podemos usar la variable de control tanto en el cuerpo del ciclo como después de que el ciclo completa su ejecución. Instrucción continue Cuando la instrucción continue se ejecuta en una instrucción while, for o do...while, omite las ins- trucciones restantes en el cuerpo de esa instrucción y continúa con la siguiente iteración del ciclo. En las instrucciones while y do...while, la prueba de continuación de ciclo se evalúa justo después de que se ejecuta la instrucción continue. En una instrucción for se ejecuta la expresión de incremento y después el programa evalúa la prueba de continuación de ciclo. La figura 5.14 utiliza la instrucción continue (línea 11) en una instrucción for para omitir la instrucción de salida (línea 13) cuando la instrucción if anidada (líneas 10 y 11) determina que el valor de cuenta es 5. Cuando se ejecuta la instrucción continue, el control del programa continúa con el incremento de la variable de control en el encabezado de la instrucción for (línea 8) e itera cinco veces más. 1 // Fig. 5.14: fig05_14.cpp 2 // instrucción continue para terminar una iteración de una instrucción for. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 for ( unsigned int cuenta = 1; cuenta = 10; ++cuenta ) // itera 10 veces 9 { Fig. 5.14  Instrucción continue para terminar una iteración de una instrucción for (parte 1 de 2).
  • 212. 180 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos 10 if ( cuenta == 5 ) // si cuenta es 5, 11 continue; // omite el código restante en el ciclo 12 13 cout cuenta ; 14 } // fin de for 15 16 cout nSe uso continue para no imprimir el 5 endl; 17 } // fin de main 1 2 3 4 6 7 8 9 10 Se uso continue para omitir imprimir 5 En la sección 5.3 declaramos que la instrucción while puede utilizarse, en la mayoría de los casos, para representar a la instrucción for. La única excepción ocurre cuando la expresión de incremento en la instrucción while va después de la instrucción continue. En este caso, el incremento no se ejecuta antes de que el programa evalúe la condición de continuación de ciclo, por lo que el while no se ejecu- ta de la misma manera que el for. Buena práctica de programación 5.5 Algunos programadores sienten que las instrucciones break y continue violan la progra- mación estructurada. Como pronto veremos, pueden lograrse los mismos efectos de estas instrucciones con las técnicas de programación estructurada, por lo que estos programadores prefieren no utilizar instrucciones break o continue. La mayoría de los programado- res consideran aceptable el uso de break en las instrucciones switch. Observación de Ingeniería de Software 5.1 Existe una tensión entre lograr la ingeniería de software de calidad y lograr el software con mejor desempeño. A menudo, una de estas metas se logra a expensas de la otra. Para todas las situaciones excepto las que demanden el mayor rendimiento, aplique la siguiente regla empírica: primero, asegúrese de que su código sea simple y correcto; después hágalo rápido y pequeño, pero sólo si es necesario. 5.8 Operadores lógicos Hasta ahora sólo hemos estudiado las condiciones simples, como contador = 10, total 1000 y numero != valorCentinela. Expresamos esas condiciones en términos de los operadores relacionales , , = y =, y los operadores de igualdad == y !=. Cada decisión evaluó precisamente una condición. Para evaluar varias condiciones a la hora de tomar una decisión, realizamos estas pruebas en instruccio- nes separadas, o en instrucciones if o if...else anidadas. C++ proporciona operadores lógicos que se utilizan para formar condiciones más complejas, al combinar condiciones simples. Los operadores lógicos son (AND lógico), || (OR lógico) y ! (NOT lógico, también se conoce como negación lógica). Operador AND lógico () Suponga que deseamos asegurar en cierto punto de una aplicación que dos condiciones sean ambas verdaderas, antes de elegir cierta ruta de ejecución. En este caso, podemos utilizar el operador (AND lógico) de la siguiente manera: Fig. 5.14  Instrucción continue para terminar una iteración de una instrucción for (parte 2 de 2).
  • 213. 5.8 Operadores lógicos 181 if ( genero == FEMENINO edad = 65 ) ++mujeresMayores; Esta instrucción if contiene dos condiciones simples. La condición genero == FEMENINO se utiliza aquí para determinar si una persona es de género femenino. La condición edad = 65 determina si una per- sona es un ciudadano mayor. La condición simple a la izquierda del operador se evalúa primero. Si es necesario, la condición simple a la derecha del operador se evalúa a continuación. Como veremos en breve, el lado derecho de una expresión AND lógica se evalúa sólo si el lado izquierdo es verdadero (true). Después, la instrucción if considera la condición combinada genero == FEMENINO edad = 65 Esta condición es true si, y sólo si ambas condiciones simples son true. Por último, si esta condición combinada es evidentemente true, la instrucción en el cuerpo de la instrucción if incrementa la cuen- ta de mujeresMayores. Si alguna de esas condiciones simples es false (o ambas lo son), el programa omite el incremento y procede a la instrucción que va después del if. La condición combinada anterior puede hacerse más legible si se agregan paréntesis redundantes: ( genero == FEMENINO ) ( edad = 65 ) Error común de programación 5.10 Aunque 3 x 7 es una condición matemáticamente correcta, no se evalúa como podría esperarse en C++. Use ( 3 x x 7 ) para obtener la evaluación apropiada en C++.. En la figura 5.15 se sintetiza el operador . Esta tabla muestra las cuatro combinaciones posibles de valores false y true para expresión1 y expresión2. A dichas tablas se les conoce comúnmente como tablas de verdad. C++ evalúa todas las expresiones que incluyen operadores relacionales, de igualdad y/o lógicos como true o false. expresión1 expresión2 expresión1 expresión2 false false false false true false true false false true true true Fig. 5.15  Tabla de verdad del operador (AND lógico). Operador OR lógico (||) Ahora considere el operador || (OR lógico). Suponga que deseamos asegurar que de dos condiciones, una de ellas o ambas serán true antes de elegir cierta ruta de ejecución. En este caso, utilizamos el ope- rador ||, como en el siguiente segmento de un programa: if ( ( promedioSemestre = 90 ) || ( examenFinal = 90 ) ) cout La calificacion del estudiante es A endl; Esta instrucción también contiene dos condiciones simples. La condición simple promedioSemestre = 90 se evalúa para determinar si el estudiante merece una “A” en el curso, debido a que tuvo un sóli- do rendimiento a lo largo del semestre. La condición simple examenFinal = 90 se evalúa para deter-
  • 214. 182 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos minar si el estudiante merece una “A” en el curso debido a un desempeño sobresaliente en el examen final. Después, la instrucción if considera la condición combinada ( promedioSemestre = 90 ) || ( examenFinal = 90 ) y otorga una “A” al estudiante si una o ambas de las condiciones simples son true. El mensaje “La ca- lificación del estudiante es A” se imprime a menos que ambas condiciones simples sean false. La figura 5.16 es una tabla de verdad para el operador OR condicional (||). expresión1 expresión2 expresión1 || expresión2 false false false false true true true false true true true true Fig. 5.16  Tabla de verdad del operador || (OR lógico). El operador tiene mayor precedencia que el operador ||. Ambos operadores se asocian de iz- quierda a derecha. Una expresión que contiene operadores o || se evalúa sólo si se conoce verdad o falsedad de la expresión. Por ende, la evaluación de la expresión ( genero == FEMENINO ) ( edad = 65 ) se detiene de inmediato si genero no es igual a FEMENINO (es decir, toda la expresión es false) y continúa si genero es igual a FEMENINO (es decir, toda la expresión podría ser aún true si la condición edad = 65 es true). Esta característica de rendimiento para la evaluación de las expresiones con AND lógico y OR lógico se conoce como evaluación en corto circuito. Tip de rendimiento 5.3 En las expresiones que utilizan el operador , si las condiciones separadas son independien- tes una de otra, haga que la condición que tenga más probabilidad de ser false sea la condición de más a la izquierda. En expresiones que utilicen el operador ||, haga que la condición que tenga más probabilidad de ser true sea la condición de más a la izquier- da. Este uso de la evaluación en corto circuito puede reducir el tiempo de ejecución de un programa. Operador lógico de negación (!) C++ cuenta con el operador ! (NOT lógico, también conocido como negación lógica) para que un programador pueda “invertir” el significado de una condición. El operador lógico de negación unario sólo tiene una condición como operando. Este operador se coloca antes de una condición cuando nos interesa elegir una ruta de ejecución si la condición original (sin el operador lógico de negación) es false, como en el siguiente segmento de un programa: if ( !( calificacion == valorCentinela ) ) cout La siguiente calificacion es calificacion endl; Los paréntesis alrededor de la condición calificacion == valorCentinela son necesarios, ya que el operador lógico de negación tiene mayor precedencia que el operador de igualdad.
  • 215. 5.8 Operadores lógicos 183 En la mayoría de los casos, puede evitar el operador ! mediante el uso de un operador relacional o de igualdad apropiado. Por ejemplo, la instrucción if anterior también puede escribirse de la siguiente manera: if ( calificacion != valorCentinela ) cout La siguiente calificación es calificacion endl; Con frecuencia, esta flexibilidad puede ayudar a un programador a expresar una condición de una ma- nera más “natural” o conveniente. La figura 5.17 es una tabla de verdad para el operador lógico de ne- gación (!). expresión !expresión false true true false Fig. 5.17  Tabla de verdad del operador ! (negación lógica). Ejemplo de los operadores lógicos La figura 5.18 demuestra el uso de los operadores lógicos; para ello produce sus tablas de verdad. Los resultados muestran cada expresión que se evalúa y su resultado boolean. De manera predeterminada, los valores bool true y false se muestran mediante cout y el operador de inserción de flujo como 1 y 0, respectivamente. Utilizamos el manipulador de flujo boolalpha (un manipulador pegajoso) en la línea 9 para especificar que el valor de cada expresión bool se debe mostrar como la palabra “true” o la palabra “false”. Por ejemplo, el resultado de la expresión false false en la línea 10 es false, así que la segunda línea de salida incluye la palabra “false”. Las líneas 9 a 13 producen la tabla de verdad para el . Las líneas 16 a 20 producen la tabla de verdad para el ||. Las líneas 23 a 25 producen la tabla de verdad para el !. 1 // Fig. 5.18: fig05_18.cpp 2 // Operadores lógicos. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 // crea la tabla de verdad para el operador (AND lógico) 9 cout boolalpha AND logico () 10 nfalse false: ( false false ) 11 nfalse true: ( false true ) 12 ntrue false: ( true false ) 13 ntrue true: ( true true ) nn; 14 15 // crea la tabla de verdad para el operador || (OR lógico) 16 cout OR logico (||) 17 nfalse || false: ( false || false ) 18 nfalse || true: ( false || true ) 19 ntrue || false: ( true || false ) 20 ntrue || true: ( true || true ) nn; Fig. 5.18  Operadores lógicos (parte 1 de 2).
  • 216. 184 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos 21 22 // crea la tabla de verdad para el operador ! (negación lógica) 23 cout NOT logico (!) 24 n!false: ( !false ) 25 n!true: ( !true ) endl; 26 } // fin de main AND logico () false false: false false true: false true false: false true true: true OR logico (||) false || false: false false || true: true true || false: true true || true: true NOT logico (!) !false: true !true: false Resumen de precedencia y asociatividad de los operadores La figura 5.19 agrega los operadores lógicos y de coma a la tabla de precedencia y asociatividad de los operadores. Los operadores se muestran de arriba hacia abajo, en orden descendente de prece- dencia. Operadores Asociatividad Tipo :: () izquierda a derecha [Vea la precaución en la figura 2.10 con respecto al agrupamiento de paréntesis]. primario ++ -- static_cast type () izquierda a derecha postfijo ++ -- + - ! derecha a izquierda unario (prefijo) * / % izquierda a derecha multiplicativo + - izquierda a derecha aditivo izquierda a derecha inserción/extracción = = izquierda a derecha relacional == != izquierda a derecha igualdad izquierda a derecha AND lógico || izquierda a derecha OR lógico ?: derecha a izquierda condicional = += -= *= /= %= derecha a izquierda asignación , izquierda a derecha coma Fig. 5.19  Precedencia/asociatividad de los operadores. Fig. 5.18  Operadores lógicos (parte 2 de 2).
  • 217. 5.9 Confusión entre los operadores de igualdad (==) y de asignación (=) 185 5.9 Confusión entre los operadores de igualdad (==) y de asignación (=) Hay un tipo de error que los programadores de C++, sin importar su experiencia, tienden a cometer con tanta frecuencia que creemos requiere una sección separada. Ese error es el de intercambiar accidental- mente los operadores == (igualdad) y = (asignación). Lo que hace a estos intercambios tan peligrosos es el hecho de que por lo general no producen errores sintácticos; las instrucciones con estos errores tienden a compilarse correctamente y el programa se ejecuta hasta completarse, generando a menudo resultados incorrectos a través de errores lógicos en tiempo de ejecución. Algunos compiladores generan una adverten- cia cuando se utiliza = en un contexto en el que normalmente se espera ==. Hay dos aspectos de C++ que contribuyen a estos problemas. Uno de ellos establece que cualquier expresión que produce un valor se puede utilizar en la porción correspondiente a la decisión de cual- quier instrucción de control. Si el valor de la expresión es cero, se trata como false, y si el valor es distin- to de cero, se trata como true. El segundo establece que las asignaciones producen un valor; a saber, el valor asignado a la variable del lado izquierdo del operador de asignación. Por ejemplo, suponga que tratamos de escribir if ( codigoPago == 4 ) // bien cout Obtuvo un bono! endl; pero accidentalmente escribimos if ( codigoPago = 4 ) // mal cout Obtuvo un bono! endl; La primera instrucción if otorga apropiadamente un bono a la persona cuyo codigoPago sea igual a 4. La segunda instrucción if (la del error) evalúa la expresión de asignación en la condición if a la cons- tante 4. Cualquier valor distinto de cero se interpreta como true, por lo que esta condición siempre se evalúa como true, ¡y la persona siempre recibe un bono sin importar cuál sea el código de pago! Aún peor, ¡el código de pago se ha modificado, cuando se supone que sólo debía examinarse! Error común de programación 5.11 El uso del operador == para asignación y el uso del operador = para igualdad son errores lógicos. Tip para prevenir errores 5.4 Por lo general, los programadores escriben condiciones como x == 7 con el nombre de la variable a la izquierda y la constante a la derecha. Al colocar la constante a la izquierda, como en 7 == x, estará protegido por el compilador si accidentalmente sustituye el opera- dor == con =. El compilador trata esto como un error de compilación, ya que no se puede modificar el valor de una constante. Esto evitará la potencial devastación de un error lógico en tiempo de ejecución. lvalues y rvalues Se dice que los nombres de las variables son lvalues (por “valores a la izquierda”), ya que pueden usarse del lado izquierdo de un operador de asignación. Se dice que las constantes son rvalues (por “valores a la derecha”), ya que sólo se pueden usar del lado derecho de un operador de asignación. Los lvalues tam- bién se pueden usar como rvalues, pero no al revés.
  • 218. 186 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos Hay otra situación que es igual de incómoda. Suponga que desea asignar un valor a una variable con una instrucción simple, como: x = 1; pero en vez de ello, escribe x == 1; Aquí podemos ver también que esto no es un error sintáctico. En vez de ello, el compilador simplemen- te evalúa la expresión condicional. Si x es igual a 1, la condición es true y la expresión se evalúa con el valor true. Si x no es igual a 1, la condición es false y la expresión se evalúa con el valor false. Sin importar el valor de la expresión, no hay operador de asignación, por lo que el valor sólo se pierde. El valor de x permanece sin alteraciones, lo que probablemente produzca un error lógico en tiempo de ejecución. ¡Por desgracia, no tenemos un truco disponible para ayudarlo con este problema! Tip para prevenir errores 5.5 Use su editor de texto para buscar todas las ocurrencias de = en su programa, y compruebe que tenga el operador de asignación u operador lógico correcto en cada lugar. 5.10 Resumen de programación estructurada Así como los arquitectos diseñan edificios, empleando la sabiduría colectiva de su profesión, de igual forma los programadores deben diseñar programas. Nuestro campo es mucho más joven que la arqui- tectura, y nuestra sabiduría colectiva es mucho más escasa. Hemos aprendido que la programación es- tructurada produce programas que son más fáciles de entender, probar, depurar, modificar que los programas no estructurados, e incluso probar que son correctos en sentido matemático. La figura 5.20 utiliza diagramas de actividad para sintetizar las instrucciones de control de C++. Los estados inicial y final indican el único punto de entrada y el único punto de salida de cada instrucción de control. Si conectamos los símbolos individuales de un diagrama de actividad en forma arbitrara, existe la posibilidad de que se produzcan programas no estructurados. Por lo tanto, la profesión de la programación utiliza sólo un conjunto limitado de instrucciones de control que pueden combinarse sólo de dos formas simples, para crear programas estructurados. Por cuestión de simpleza, sólo se utilizan instrucciones de control de una sola entrada/una sola salida; sólo hay una forma de entrar y una de salir de cada instrucción de control. Es sencillo conectar instruc- ciones de control en secuencia para formar programas estructurados: el estado final de una instrucción de control se conecta al estado inicial de la siguiente instrucción de control; es decir, las instrucciones de control se colocan una después de la otra en un programa. A esto le llamamos apilamiento de ins- trucciones de control. Las reglas para formar programas estructurados también permiten anidar las instrucciones de control. La figura 5.21 muestra las reglas para formar programas estructurados. Las reglas suponen que pueden utilizarse estados de acción para indicar cualquier acción. Además, suponen que comenzamos con el diagrama de actividad más sencillo (figura 5.22), que consiste solamente de un estado inicial, un estado de acción, un estado final y flechas de transición. Al aplicar las reglas de la figura 5.21, siempre se obtiene un diagrama de actividad con una agrada- ble apariencia de bloque de construcción. Por ejemplo, si se aplica la regla 2 repetidamente al diagrama de actividad más sencillo, se obtiene un diagrama de actividad que contiene muchos estados de acción en secuencia (figura 5.23). La regla 2 genera una pila de estructuras de control, por lo que la llamaremos regla de apilamiento. Las líneas punteadas verticales en la figura 5.23 no son parte de UML; las utili- zamos para separar los cuatro diagramas de actividad que demuestran cómo se aplica la regla 2 de la fi- gura 5.21.
  • 219. 5.10 Resumen de programación estructurada 187 break break [v] [v] [f] [f] instrucción if...else (selección doble) instrucción if...else (selección doble) instrucción if (selección simple) instrucción if (selección simple) [v] [v] [f] [f] [v] [v] [f] [f] break break [v] [v] break break [v] [v] [f] [f] [f] [f] instrucción switch con instrucciones break (selección múltiple) instrucción switch con instrucciones break (selección múltiple) Secuencia Selección Repetición procesamiento default procesamiento default inicialización inicialización incremento incremento ... ... ... ... [v] [v] [f] [f] instrucción for instrucción for [v] [v] [f] [f] instrucción while instrucción while [v] [v] [f] [f] instrucción do...while instrucción do...while cuerpo cuerpo Fig. 5.20  Instrucciones de secuencia, selección y repetición de una sola entrada/una sola salida de C++.
  • 220. 188 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos Reglas para formar programas estructurados 1) Comenzar con el “diagrama de actividad más sencillo” (figura 5.22). 2) Cualquier estado de acción puede reemplazarse por dos estados de acción en secuencia. 3) Cualquier estado de acción puede reemplazarse por cualquier instrucción de control (secuencia, if, if...else, switch, while, do...while o for). 4) Las reglas 2 y 3 pueden aplicarse tantas veces como se desee y en cualquier orden. Fig. 5.21  Reglas para formar programas estructurados. estado de acción Fig. 5.22  El diagrama de actividad más sencillo. estado de acción estado de acción estado de acción estado de acción estado de acción estado de acción estado de acción estado de acción ... aplicar regla 2 aplicar regla 2 aplicar regla 2 Fig. 5.23  El resultado de aplicar la regla 2 de la figura 5.21 repetidamente al diagrama de actividad más sencillo. La regla 3 se conoce como la regla de anidamiento. Al aplicarla repetidamente al diagrama de ac- tividad más sencillo, se obtiene un diagrama de actividad con instrucciones de control perfectamente anidadas. Por ejemplo, en la figura 5.24 el estado de acción en el diagrama de actividad más sencillo se reemplaza con una instrucción de selección doble (if...else). Luego la regla 3 se aplica otra vez a los
  • 221. 5.10 Resumen de programación estructurada 189 estados de acción en la instrucción de selección doble, reemplazando cada uno de estos estados con una instrucción de selección doble. Los símbolos punteados de estado de acción alrededor de cada una de las instrucciones de selección doble representan el estado de acción que se reemplazó en el diagrama de actividad anterior. [Nota: las flechas punteadas y los símbolos punteados de estado de acción que se muestran en la figura 5.24 no son parte de UML. Aquí se utilizan como dispositivos pedagógicos para ilustrar que cualquier estado de acción puede reemplazarse con una instrucción de control]. estado de acción estado de acción estado de acción estado de acción estado de acción estado de acción aplicar regla 3 aplicar regla 3 aplicar regla 3 estado de acción Fig. 5.24  Aplicación de la regla 3 de la figura 5.21 varias veces al diagrama de actividad más sencillo. La regla 4 genera instrucciones más grandes, más implicadas y más profundamente anidadas. Los diagramas que surgen debido a la aplicación de las reglas de la figura 5.21 constituyen el conjunto de todos los posibles diagramas de actividad estructurados y, por lo tanto, el conjunto de todos los posibles programas estructurados. La belleza de la metodología estructurada es que utilizamos solamente siete instrucciones de control simples de una sola entrada/una sola salida, y las ensamblamos en una de sólo dos formas simples.
  • 222. 190 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos Si se siguen las reglas de la figura 5.21, no podrá crearse un diagrama de actividad con una sintaxis ilegal(comoeldelafigura5.25).Siustednoestásegurodequeciertodiagramaseaestructurado,aplique las reglas de la figura 5.21 en orden inverso para tratar de reducir el diagrama al diagrama de actividad más sencillo. Si puede reducirlo, entonces el diagrama original es estructurado; de lo contrario, no es estructurado. estado de acción estado de acción estado de acción estado de acción Fig. 5.25  Diagrama de actividad con sintaxis ilegal. La programación estructurada promueve la simpleza. Böhm y Jacopini nos han dado el resultado de que sólo se necesitan tres formas de control: • Secuencia • Selección • Repetición La estructura de secuencia es trivial. Simplemente enliste las instrucciones a ejecutar en el orden en el que deben ejecutarse. La selección se implementa en una de tres formas: • instrucción if (selección simple) • instrucción if...else (selección doble) • instrucción switch (selección múltiple) Es sencillo demostrar que la instrucción if más simple es suficiente para proporcionar cualquier forma de selección; todo lo que pueda hacerse con las instrucciones if...else y switch puede imple- mentarse si se combinan instrucciones if (aunque tal vez no con tanta claridad y eficiencia). La repetición se implementa en una de tres maneras: • instrucción while • instrucción do...while • instrucción for Es sencillo demostrar que la instrucción while es suficiente para proporcionar cualquier forma de repeti- ción.Todo lo que puede hacerse con las instrucciones do...while y for, puede hacerse también con la instrucción while (aunque tal vez no sea tan sencillo). Si se combinan estos resultados, se demuestra que cualquier forma de control necesaria en un pro- grama en C++ puede expresarse en términos de:
  • 223. Resumen 191 • secuencia • instrucción if (selección) • instrucción while (repetición) y que estas tres instrucciones de control pueden combinarse en sólo dos formas: apilamiento y anida- miento. Evidentemente, la programación estructurada es la esencia de la simpleza. 5.11 Conclusión Hemoscompletadonuestraintroducciónalasinstruccionesdecontrol,lascualesnospermitencontrolar el flujo de la ejecución en los programas. El capítulo 4 trató acerca de las instrucciones de control if, if...else y while. En este capítulo demostramos las instrucciones de control for, do...while y switch. Mostramos que cualquier algoritmo puede desarrollarse mediante el uso de combinaciones de la estructuradesecuencia,lostrestiposdeinstruccionesdeselección(if,if...else yswitch)ylostrestipos de instrucciones de repetición (while, do...while y for). Hablamos acerca de cómo puede combinar estos bloques de construcción para utilizar las técnicas, ya probadas, de construcción de programas y solu- ción de problemas. Aprendió a usar las instrucciones break y continue para alterar el flujo de control de una instrucción de repetición. En este capítulo también se introdujeron los operadores lógicos, que nos permiten utilizar expresiones condicionales más complejas en las instrucciones de control. Por último, examinamos los errores comunes al confundir los operadores de igualdad y asignación, y proporcionamos sugerencias para evitar estos errores. En el capítulo 6 examinaremos las funciones con más detalle. Resumen Sección 5.2 Fundamentos de la repetición controlada por un contador • En C++, es más preciso llamar definición a una declaración de variable que también reserva memoria (pág. 158). Sección 5.3 Instrucción de repetición for • La instrucción de repetición for (pág. 159) maneja todos los detalles de la repetición controlada por con- tador. • El formato general de la instrucción for es for ( inicialización; condiciónDeContinuacionDeCiclo; incremento ) instrucción en donde la expresión inicialización inicializa la variable de control del ciclo, condiciónDeContinuaciónDe- Ciclo determina si el ciclo debe continuar su ejecución, e incremento incrementa o decrementa la variable de control. • Por lo general, las instrucciones for se utilizan para la repetición controlada por contador y las instrucciones while para la repetición controlada por centinela. • El alcance de una variable (pág. 161) especifica en qué parte de un programa se puede utilizar. • El operador coma (pág. 161) tiene la menor precedencia de todos los operadores de C++. El valor y tipo de una lista de expresiones separadas por comas es el valor y tipo de la expresión que esté más a la derecha en la lista. • Las expresiones de inicialización, condición de continuación de ciclo e incremento de una instrucción for pueden contener expresiones aritméticas. Además, el incremento de una instrucción for puede ser negativo. • Si en un principio la condición de continuación de ciclo en un encabezado for es false, no se ejecuta el cuerpo de la instrucción for. En vez de ello, la ejecución se reanuda en la instrucción después del for.
  • 224. 192 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos Sección 5.4 Ejemplos acerca del uso de la instrucción for • La función pow(x, y) de la biblioteca estándar (pág. 165) calcula el valor de x elevado a la yésima potencia. La función pow recibe dos argumentos de tipo double y devuelve un valor double. • El manipulador de flujo parametrizado setw (pág. 167) especifica la anchura de campo en la que debe aparecer el siguiente valor a imprimir, justificado a la derecha de manera predeterminada. Si el valor a imprimir es mayor que la anchura de campo, ésta se extiende para dar cabida al valor completo. El manipulador de flujo left (pág. 167) hace que un valor se justifique a la izquierda en un campo, y right (pág. 167) se puede usar para restaurar la justificación a la derecha. • Las opciones pegajosas (pág. 167) son las opciones de formato que permanecen en vigor hasta que se modi- fican. Sección 5.5 Instrucción de repetición do…while • La instrucción de repetición do...while evalúa la condición de continuación de ciclo al final de éste, por lo que el cuerpo del ciclo se ejecutará al menos una vez. El formato para la instrucción do...while es do { instrucción } while ( condición ); Sección 5.6 Instrucción de selección múltiple switch • La instrucción switch de selección múltiple (pág. 169) realiza distintas acciones, con base en el valor de su ex- presión de control. • La función cin.get() lee un carácter del teclado. Por lo general, los caracteres se almacenan en variables de tipo char (pág. 173). Un carácter se puede tratar ya sea como entero o como carácter. • Una instrucción switch consiste de una serie de etiquetas case (pág. 174) y un caso default opcional (pág. 174). • La expresión entre paréntesis después de la palabra clave switch se llama expresión de control (pág. 174). La instrucción switch compara el valor de la expresión de control con cada etiqueta case. • Las etiquetas case consecutivas, sin instrucciones entre ellas, ejecutan el mismo conjunto de instrucciones. • Cada etiqueta case sólo puede especificar una expresión integral constante. • Cada etiqueta case puede tener varias instrucciones. La instrucción de selección switch difiere de otras instruc- ciones de control, en cuanto a que no requiere llaves alrededor de varias instrucciones en cada case. • C++ proporciona varios tipos de datos para representar enteros: int, char, short y long. El rango de valores enteros para cada tipo depende de la plataforma. • C++11 nos permite proporcionar un valor predeterminado para un miembro de datos al declararlo en la decla- ración de la clase. Sección 5.7 Instrucciones break y continue • Cuando la instrucción break (pág. 178) se ejecuta en una de las instrucciones de repetición (for, while y do...while), provoca la salida inmediata de esa instrucción. • Cuando la instrucción continue (pág. 179) se ejecuta en una instrucción de repetición, omite el resto de las instrucciones en el cuerpo del ciclo y continúa con la siguiente iteración del mismo. En una instrucción while o do...while, la ejecución continúa con la siguiente evaluación de la condición. En una instrucción for, la ejecución continúa con la expresión de incremento en el encabezado de la instrucción for. Sección 5.8 Operadores lógicos • Los operadores lógicos (pág. 180) nos permiten formar condiciones complejas mediante la combinación de condiciones simples. Los operadores lógicos son (AND lógico), || (OR lógico) y ! (negación lógica). • El operador (AND lógico, pág. 180) asegura que dos condiciones sean ambas true.
  • 225. Ejercicios de autoevaluación 193 • El operador || (OR lógico, pág. 181) asegura que de dos condiciones, una o ambas sean true. • Una expresión que contenga los operadores o || se evalúa sólo hasta que se conoce si la expresión es verda- dera o falsa. Esta característica de rendimiento para la evaluación de las expresiones AND lógico y OR lógico se conoce como evaluación de corto circuito (pág. 182). • El operador ! (NOT lógico, también conocido como negación lógica; pág. 182) permite a un programador “invertir” el significado de una condición. El operador de negación lógico unario se coloca antes de una condi- ción, para elegir una ruta de ejecución si la condición original (sin el operador de negación lógico) es false. En la mayoría de los casos, podemos evitar el uso de la negación lógica si expresamos la condición con un operador relacional o de igualdad apropiado. • Cuando se utiliza en una condición, cualquier valor distinto de cero se convierte de manera implícita en true; 0 (cero) se convierte de manera implícita en false. • De manera predeterminada, cout muestra a los valores bool true y false como 1 y 0, respectivamente. El ma- nipulador de flujo boolalpha (pág. 183) especifica que el valor de cada expresión bool debe mostrarse, ya sea como la palabra “true” o la palabra “false”. Sección 5.9 Confusión entre los operadores de igualdad (==) y de asignación (=) • Cualquier expresión que produzca un valor se puede utilizar en la porción de decisión de cualquier instruc- ción de control. Si el valor de la expresión es cero, se trata como false, y si es distinto de cero, se trata como true. • Una asignación produce un valor; a saber, el valor asignado a la variable del lado izquierdo del operador de asignación. Sección 5.10 Resumen de programación estructurada • Cualquier forma de control se puede expresar en términos de instrucciones de secuencia, selección y repetición, y éstas pueden combinarse sólo en dos formas: apilamiento y anidamiento. Ejercicios de autoevaluación 5.1 Conteste con verdadero o falso a cada una de las siguientes proposiciones; en caso de ser falso, explique por qué. a) El caso default es requerido en la instrucción de selección switch. b) La instrucción break es requerida en el caso default de una instrucción de selección switch, para salir del switch de manera apropiada. c) La expresión ( x y a b ) es true si la expresión x y es true, o si la expresión a b es true. d) Una expresión que contiene el operador || es true si uno o ambos de sus operandos son true. 5.2 Escriba una instrucción o un conjunto de instrucciones en C++, para realizar cada una de las siguientes tareas: a) Sumar los enteros impares entre 1 y 99 utilizando una instrucción for. Use las variables unsigned int de nombre suma y cuenta. b) Imprimir el valor 333.546372 en una anchura de campo de 15 caracteres, con precisiones de 1, 2 y 3. Imprimir cada número en la misma línea. Justificar a la izquierda cada número en su campo. ¿Cuáles son los tres valores que se imprimen? c) Calcular el valor de 2.5 elevado a la potencia de 3, utilizando la función pow. Imprimir el resultado con una precisión de 2 en una anchura de campo de 10 posiciones. ¿Qué se imprime? d) Imprimir los enteros del 1 al 20, utilizando un ciclo while y la variable contador unsigned int x. Imprimir solamente cinco enteros por línea. [Sugerencia: cuando x % 5 sea 0, imprima un carácter de nueva línea; de lo contrario, imprima un carácter de tabulación]. e) Repita el ejercicio 5.2 (d), usando una instrucción for.
  • 226. 194 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos 5.3 Encuentre los errores en cada uno de los siguientes segmentos de código, y explique cómo corregirlos: a) unsigned int x = 1; while ( x = 10 ); ++x; } b) for ( double y = 0.1; y != 1.0; y += .1 ) cout y endl; c) switch ( n ) { case 1: cout El numero es 1 endl; case 2: cout El numero es 2 endl; break; default: cout El número no es 1 ni 2 endl; break; } d) El siguiente código debe imprimir los valores 1 a 10: unsigned int n = 1; while ( n 10 ) cout n++ endl; Respuestas a los ejercicios de autoevaluación 5.1 a) Falso. El caso default es opcional. Sin embargo, se considera buena ingeniería de software proporcio- nar siempre un caso default. b) Falso. La instrucción break se utiliza para salir de la instrucción switch. La instrucción break no se requiere cuando el caso default es el último.Tampoco se requerirá la instrucción break si tiene senti- do hacer que el control se reanude en el siguiente caso. c) Falso. Al utilizar el operador , ambas expresiones relacionales deben ser true para que toda la expre- sión sea true. d) Verdadero. 5.2 a) unsigned int suma = 0; for ( unsigned int count = 1; count = 99; count += 2 ) suma += cuenta; b) cout fixed left setprecision( 1 ) setw( 15 ) 333.546372 setprecision( 2 ) setw( 15 ) 333.546372 setprecision( 3 ) setw( 15 ) 333.546372 endl; La salida es: 333.5 333.55 333.546 c) cout fixed setprecision( 2 ) setw( 10 ) pow( 2.5, 3 ) endl; La salida es: 15.63 d) unsigned int x = 1; while ( x = 20 ) {
  • 227. Ejercicios 195 if ( x % 5 == 0 ) cout x endl; else cout x 't'; ++x; } e) for ( unsigned int x = 1; x = 20; ++x ) { if ( x % 5 == 0 ) cout x endl; else cout x 't'; } 5.3 a) Error: El punto y coma después del encabezado while provoca un ciclo infinito. Corrección: Reemplazar el punto y coma por una llave izquierda ({), o eliminar tanto el punto y coma (;) como la llave derecha (}). b) Error: Utilizar un número de punto flotante para controlar una instrucción de repetición for. Corrección: Utilice un unsigned int y realice el cálculo apropiado para poder obtener los valores deseados. for ( unsigned int y = 1; y != 10; ++y ) cout ( static_cast double ( y ) / 10 ) endl; c) Error: Falta una instrucción break en el primer case. Corrección: Agregue una instrucción break al final del primer case. Esto no es un error, si el programa- dor desea que la instrucción del case 2: se ejecute siempre que lo haga la instrucción del case 1:. d) Error: Se está utilizando un operador relacional inadecuado en la condición de continuación de ciclo. Corrección: Use = en vez de , o cambie 10 a 11. Ejercicios 5.4 (Encuentre los errores en el código) Encuentre el (los) error(es), si los hay, en cada uno de los siguientes fragmentos de código: a) For ( unsigned int x = 100, x = 1, ++x ) cout x endl; b) El siguiente código debe imprimirse sin importar que el entero valor sea par o impar: switch ( valor % 2 ) { case 0: cout Entero par endl; case 1: cout Entero impar endl; } c) El siguiente código debe imprimir los enteros impares del 19 al 1: for ( unsigned int x = 19; x = 1; x += 2 ) cout x endl; d) El siguiente código debe imprimir los enteros pares del 2 al 100: unsigned int counter = 2; do { cout contador endl; contador += 2; } While ( contador 100 );
  • 228. 196 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos 5.5 (Suma de enteros) Escriba un programa que utilice una instrucción for para sumar una secuencia de en- teros. Suponga que el primer entero leído especifica el número de valores que quedan por introducir. Su programa debe leer sólo un valor por cada instrucción de entrada. Una secuencia típica de entrada podría ser 5 100 200 300 400 500 en donde el 5 indica que se van a sumar los 5 valores subsiguientes. 5.6 (Promedio de enteros) Escriba un programa que utilice una instrucción for para calcular e imprimir el promedio de varios enteros. Suponga que el último valor leído es el valor centinela 9999. Por ejemplo, la secuencia 10 8 11 7 9 9999 indica que el programa debe calcular el promedio de todos los valores antes del 9999. 5.7 (¿Qué hace el siguiente programa?) ¿Qué hace el siguiente programa? 1 // Ejercicio 5.7: ex05_07.cpp 2 // ¿Qué hace este programa? 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 unsigned int x; // declara x 9 unsigned int y; // declara y 10 11 // pide al usuario los datos de entrada 12 cout Escriba dos enteros en el rango 1 a 20: ; 13 cin x y; // lee valores para x y y 14 15 for ( unsigned int i = 1; i = y; ++i ) // cuenta desde 1 hasta y 16 { 17 for ( unsigned int j = 1; j = x; ++j ) // cuenta desde 1 hasta x 18 cout '@'; // imprime @ 19 20 cout endl; // empieza nueva línea 21 } // fin de for exterior 22 } // fin de main 5.8 (Encontrar el entero más chico) Escriba un programa que utilice una instrucción for para encontrar el menor de varios enteros. Suponga que el primer valor leído especifica el número de valores restantes. 5.9 (Producto de enteros impares) Escriba un programa que utilice una instrucción for para calcular e impri- mir el producto de los enteros impares del 1 al 15. 5.10 (Factoriales) La función factorial se utiliza frecuentemente en los problemas de probabilidad. Utilizando la definición de factorial del ejercicio 4.34, escriba un programa que utilice una función for para evaluar los facto- riales de los enteros del 1 al 5. Muestre los resultados en formato tabular. ¿Qué dificultad podría impedir que usted calculara el factorial de 20? 5.11 (Interés compuesto) Modifique el programa de interés compuesto de la sección 5.4, repitiendo sus pasos para las tasas de interés del 5, 6, 7, 8, 9 y 10%. Use una instrucción for para variar la tasa de interés. 5.12 (Patrones de dibujo con ciclos for anidados) Escriba un programa que utilice ciclos for para imprimir los siguientes patrones por separado, uno debajo del otro. Use ciclos for para generar los patrones. Todos los aste- riscos (*) deben imprimirse mediante una sola instrucción de la forma cout '*'; (esto hace que los asteriscos se impriman uno al lado del otro). [Sugerencia: los últimos dos patrones requieren que cada línea empiece con un número apropiado de espacios en blanco. Crédito adicional: combine su código de los cuatro problemas separados enunsoloprogramaqueimprimaloscuatropatrones,unoalladodelotro,haciendounusointeligentedelosciclos for anidados].
  • 229. Ejercicios 197 (a) (b) (c) (d) * ********** ********** * ** ********* ********* ** *** ******** ******** *** **** ******* ******* **** ***** ****** ****** ***** ****** ***** ***** ****** ******* **** **** ******* ******** *** *** ******** ********* ** ** ********* ********** * * ********** 5.13 (Gráfico de barras) Una aplicación interesante de las computadoras es dibujar gráficos convencionales y de barra. Escriba un programa que lea cinco números (cada uno entre 1 y 30). Suponga que el usuario sólo intro- duce valores válidos. Por cada número leído, su programa debe imprimir una línea que contenga ese número de asteriscos adyacentes. Por ejemplo, si su programa lee el número 7, debe mostrar *******. 5.14 (Cálculo de las ventas totales) Un almacén de pedidos por correo vende cinco productos distintos, cuyos precios de venta son los siguientes: producto 1, $2.98; producto 2, $4.50; producto 3, $9.98; producto 4, $4.49 y producto 5, $6.87. Escriba un programa que lea una serie de pares de números, como se muestra a continuación: a) número del producto b) cantidad vendida Su programa debe utilizar una instrucción switch para determinar el precio de venta de cada producto. Debe calcular y mostrar el valor total de venta de todos los productos vendidos. Use un ciclo controlado por centinela para determinar cuándo debe el programa dejar de iterar para mostrar los resultados finales. 5.15 (Modificación de LibroCalificaciones) Modifique el programa LibroCalificaciones de las figuras 5.9 a 5.11, de manera que calcule el promedio de puntos de calificaciones. Una calificación A vale 4 puntos, B vale 3 puntos, etcétera. 5.16 (Cálculo del interés compuesto) Modifique el programa de la figura 5.6, de manera que se utilicen sólo enteros para calcular el interés compuesto. [Sugerencia: trate todas las cantidades monetarias como números de centavos. Luego “divida” el resultado en su porción de dólares y su porción de centavos, utilizando las operaciones de división y módulo. Inserte un punto]. 5.17 (¿Qué se imprime?) Suponga que i = 1, j = 2, k = 3 y m = 2. ¿Qué es lo que imprime cada una de las siguientes instrucciones? a) cout ( i == 1 ) endl; b) cout ( j == 3 ) endl; c) cout ( i = 1 j 4 ) endl; d) cout ( m = 99 k m ) endl; e) cout ( j = i || k == m ) endl; f) cout ( k + m j || 3 - j = k ) endl; g) cout ( !m ) endl; h) cout ( !( j - m ) ) endl; i) cout ( !( k m ) ) endl; 5.18 (Tabla de sistemas numéricos) Escriba un programa que imprima una tabla de los equivalentes binario, octal y hexadecimal de los números decimales en el rango de 1 a 256. Si no está familiarizado con estos sistemas numéricos, lea el apéndice D. [Sugerencia: puede usar los manipuladores de flujo dec, oct y hex para mostrar los enteros en los formatos decimal, octal y hexadecimal, respectivamente]. 5.19 Calcule el valor de π a partir de la serie infinita  = 4– 4 3 + 4 5 + 4 7 + 4 9 – 4 11 + π Imprima una tabla que muestre el valor aproximado de π, después de cada uno de los primeros 1000 términos de esta serie.
  • 230. 198 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos 5.20 (Triples de Pitágoras) Un triángulo recto puede tener lados cuyas longitudes sean valores enteros. Un conjunto de tres valores enteros para los lados de un triángulo recto se conoce como triple de Pitágoras. Estos tres lados deben satisfacer la relación que establece que la suma de los cuadrados de dos lados es igual al cuadrado de la hipotenusa. Encuentre todos los triples de Pitágoras para lado1, lado2, y la hipotenusa, que no sean mayores de 500. Use un ciclo for triplemente anidado para probar todas las posibilidades. Este método es un ejemplo de la computacióndefuerza bruta.Encursosdecienciascomputacionalesmásavanzadosaprenderáqueexistenmuchos problemas interesantes para los cuales no hay otra metodología algorítmica conocida, sólo el uso de la fuerza bruta. 5.21 (Cálculo de salarios) Una empresa paga a sus empleados como gerentes (quienes reciben un salario sema- nal fijo), trabajadores por horas (que reciben un sueldo fijo por hora para las primeras 40 horas que trabajen y “tiempo y medio”: 1.5 veces su sueldo por horas, para las horas extra trabajadas), empleados por comisión (que reciben $250 más el 5.7 por ciento de sus ventas totales por semana), o trabajadores por piezas (que reciben una cantidad fija de dinero por cada artículo que producen; cada trabajador por piezas en esta empresa trabaja sólo en un tipo de artículo). Escriba un programa para calcular el sueldo semanal para cada empleado. No necesita saber cuántos empleados hay de antemano. Cada tipo de empleado tiene su propio código de pago: los gerentes tienen el código 1, los trabajadores por horas tienen el código 2, los trabajadores por comisión tienen el código 3 y los trabajadores por piezas tienen el código 4. Use una instrucción switch para calcular el sueldo de cada empleado, de acuerdo con el código de pago de cada uno. Dentro del switch, pida al usuario (es decir, el cajero de nóminas) que introduzca los hechos apropiados que su programa necesita para calcular el sueldo de cada empleado, de acuer- do con su código de pago. 5.22 (Leyes de De Morgan) En este capítulo hemos hablado sobre los operadores lógicos , || y !. Algunas veces, las leyes de De Morgan pueden hacer que sea más conveniente para nosotros expresar una expresión lógica. Estas leyes establecen que la expresión !( condición1 condición2 ) es lógicamente equivalente a la expresión ( !condición1 || !condición2 ). Además, la expresión !( condición1 || condición2 ) es lógicamente equivalente a la expresión ( !condición1 !condición2 ). Use las leyes de De Morgan para escribir expresiones equivalentes para cada una de las siguientes expresiones, luego escriba un programa que demuestre que, tanto la expresión original como la nueva expresión, son equivalentes en cada caso: a) !( x 5 ) !( y = 7 ) b) !( a == b ) || !( g != 5 ) c) !( ( x = 8 ) ( y 4 ) ) d) !( ( i 4 ) || ( j = 6 ) ) 5.23 (Rombo de asteriscos) Escriba un programa que imprima la siguiente figura de rombo. Puede utilizar instrucciones de salida que impriman un solo asterisco (*), un solo espacio en blanco o un solo carácter de nue- va línea. Maximice el uso de la repetición (con instrucciones for anidadas) y minimice el número de instruccio- nes de salida. * *** ***** ******* ********* ******* ***** *** * 5.24 (Modificación del rombo de asteriscos) Modifique el programa que escribió en el ejercicio 5.23, para que lea un número impar en el rango de 1 a 19, de manera que especifique el número de filas en el rombo y después muestre un rombo del tamaño apropiado. 5.25 (Eliminación de break y continue) Una crítica de las instrucciones break y continue es que ninguna es estructurada. En realidad, estas instrucciones pueden reemplazarse en todo momento por instrucciones estruc- turadas. Describa, en general, cómo eliminaría la instrucción break de un ciclo en un programa, para reempla-
  • 231. Hacer la diferencia 199 zarlas con alguna de las instrucciones estructuradas equivalentes. [Sugerencia: la instrucción break se sale de un ciclo desde el cuerpo de éste. La otra forma de salir es que falle la prueba de continuación de ciclo. Considere utilizar en la prueba de continuación de ciclo una segunda prueba que indique una “salida anticipada debido a una condición de ‘interrupción’”]. Use la técnica que desarrolló aquí para eliminar la instrucción break de la aplicación de la figura 5.13. 5.26 (¿Qué hace este código?) ¿Qué hace el siguiente segmento de programa? 1 for ( unsigned int i = 1; i = 5; ++i ) 2 { 3 for ( unsigned int j = 1; j = 3; ++j ) 4 { 5 for ( unsigned int k = 1; k = 4; ++k ) 6 cout ‘*’; 7 8 cout endl; 9 } // fin del for interior 10 11 cout endl; 12 } // fin del for exterior 5.27 (Eliminación de la instrucción continue) Describa, en general, cómo eliminaría las instrucciones conti- nue de un ciclo en un programa, para reemplazarlas con uno de sus equivalentes estructurados. Use la técnica que desarrolló aquí para eliminar la instrucción continue del programa de la figura 5.14. 5.28 (Canción “Los Doce Días de Navidad”) Escriba un programa que utilice instrucciones de repetición y switch para imprimir la canción “Los Doce Días de Navidad”. Una instrucción switch debe utilizarse para impri- mir el día (es decir, “primer”, “segundo”, etcétera). Una instrucción switch separada debe utilizarse para imprimir el resto de cada verso. Visite el sitio Web http://www.12days.com/library/carols/ para obtener la letra comple- ta de la canción. 5.29 (Problema de Peter Minuit) La leyenda establece que, en 1626, Peter Minuit compró la isla de Man- hattan por $24.00 en un trueque. ¿Hizo él una buena inversión? Para responder a esta pregunta, modifique el programa de interés compuesto de la figura 5.6, para empezar con un monto principal de $24.00 y calcular el monto de interés en depósito, si ese dinero se había mantenido en depósito hasta este año (por ejemplo, 387 años hasta 2013). Coloque el ciclo for que realiza el cálculo del interés compuesto en un ciclo for exterior que varíe la tasa de interés del 5 al 10%, para observar las maravillas del interés compuesto. Hacer la diferencia 5.30 (Examen rápido sobre hechos del calentamiento global) La controversial cuestión del calentamiento global obtuvo una gran publicidad gracias a la película An InconvenientTruth en la que aparece el anterior vicepre- sidente Al Gore. El señor Gore y una red de científicos de Naciones Unidas (N.U.), el Panel Intergubernamental sobre el Cambio Climático, compartieron el Premio Nobel a la Paz de 2007 en reconocimiento por “sus esfuerzos al generar y diseminar un mayor conocimiento sobre el cambio climatológico provocado por el hombre”. Investi- gue ambos lados de la cuestión del calentamiento global en línea (tal vez quiera buscar frases como “escépticos del calentamiento global”). Cree un examen rápido de opción múltiple con cinco preguntas sobre el calentamiento global; cada pregunta debe tener cuatro posibles respuestas (enumeradas del 1 al 4). Sea objetivo y trate de repre- sentar con imparcialidad ambos lados de la cuestión. Después escriba una aplicación que administre el examen rápido, calcule el número de respuestas correctas (de cero a cinco) y devuelva un mensaje al usuario. Si éste respon- de de manera correcta a las cinco preguntas, imprima el mensaje «Excelente»; si responde a cuatro, imprima «Muy bien»; si responde a tres o menos, imprima «Es tiempo de aprender más sobre el calentamiento global», e incluya una lista de algunos de los sitios Web en donde encontró esos hechos.
  • 232. 200 Capítulo 5 Instrucciones de control, parte 2: operadores lógicos 5.31 (Alternativas para el plan fiscal: el “impuesto justo”) Existen muchas propuestas para que los impuestos sean más justos. Consulte la iniciativa FairTax (impuestos justos) de Estados Unidos en el sitio: www.fairtax.org/site/PageServer?pagename=calculator Investigue cómo funciona la iniciativa FairTax que se propone. Nuestra sugerencia es eliminar los impuestos sobre los ingresos y otros impuestos más a favor de un 23% de impuestos sobre el consumo en todos los productos y servicios que usted compre. Algunos opositores a FairTax cuestionan la cifra del 23% y dicen que, debido a la forma en que se calculan los impuestos, sería más preciso decir que la tasa sea del 30%; revise esto con cuidado. Escriba un programa que pida al usuario que introduzca sus gastos en diversas categorías de gastos disponibles (por ejemplo, alojamiento, comida, ropa, transporte, educación, servicios médicos, vacaciones) y que después imprima el impuesto FairTax estimado que esa persona pagaría. 5.32 (Crecimiento de la base de usuarios de Facebook) Al mes de enero de 2013, hay aproximadamente 2500 millones de personas en Internet. Facebook llegó a los mil millones de usuarios en octubre de 2012. En este ejerci- cio, escribirá un programa para determinar cuándo llegará Facebook a 2500 millones de personas con tasas de crecimiento mensuales del 2, 3, 4 o 5%. Use las técnicas que aprendió en la figura 5.6.
  • 233. Funciones y una introducción a la recursividad 6 La forma siempre va después de la función. —Louis Henri Sullivan E pluribus unum. (Uno compuesto de varios). —Virgilio ¡Oh! recuerdos del ayer, tratar de regresar el tiempo. —William Shakespeare Respóndeme en una palabra. —William Shakespeare Hay un punto en el cual los métodos se devoran a sí mismos. —Frantz Fanon O b j e t i v o s En este capítulo aprenderá a: n Crear programas en forma modular, a partir de funciones. n Utilizar las funciones matemáticas comunes de la biblioteca. n Conocer los mecanismos para pasar datos a funciones y devolver resultados. n Comprender cómo el mecanismo de llamadas a/ regreso de funciones está soportado por la pila de llamadas a funciones y los registros de activación. n Usar la generación de números aleatorios para implementar aplicaciones de juegos. n Comprender cómo la visibilidad de los identificadores está limitada a regiones específicas de programas. n Escribir y usar funciones recursivas.
  • 234. 202 Capítulo 6 Funciones y una introducción a la recursividad 6.1Introducción La mayoría de los programas de computadora que resuelven problemas del mundo real son mucho más grandes que los programas que se presentan en los primeros capítulos de este libro. La experiencia ha demostrado que la mejor forma de desarrollar y mantener un programa extenso es construirlo a partir de piezas (o componentes) simples y pequeñas. A esta técnica se le conoce como divide y vencerás. Veremos las generalidades de una porción de las funciones matemáticas de la Biblioteca estándar de C++. Después, el lector aprenderá a declarar una función con más de un parámetro.También presenta- remos información adicional acerca de los prototipos de funciones y la forma en que el compilador los utiliza para convertir el tipo de argumento en la llamada a una función, al tipo especificado en la lista de parámetros de una función, si es necesario. Luego, daremos un pequeño giro hacia las técnicas de simulación con la generación de números aleatorios, y desarrollaremos una versión de un popular juego de dados de casino, el cual utiliza la ma- yoría de las técnicas de programación que el lector ha aprendido hasta este punto. Posteriormente, presentaremos las clases de almacenamiento y reglas de alcance de C++. Éstas de- terminan el periodo durante el cual un objeto existe en la memoria, y en dónde se puede hacer referen- cia a su identificador en un programa.También aprenderá cómo C++ es capaz de llevar el registro de cuál función se ejecuta en un momento dado, cómo se mantienen los parámetros y otras variables locales de las funciones en la memoria, y cómo sabe una función a dónde regresar, una vez que termina su ejecu- ción. Hablaremos sobre dos temas que ayudan a mejorar el rendimiento de los programas: las funciones en línea que pueden eliminar la sobrecarga de la llamada a una función, y parámetros de referencia que pueden usarse para pasar elementos extensos de datos a las funciones con eficiencia. Muchas de las aplicaciones que desarrollará tendrán más de una función con el mismo nombre. Esta técnica, llamada sobrecarga de funciones, se utiliza para implementar funciones que realicen tareas similares para los argumentos de distintos tipos, o posiblemente para distintos números de argumentos. Consideremos las plantillas de funciones: un mecanismo para definir una familia de funciones sobre- 6.1 Introducción 6.2 Componentes de los programas en C++ 6.3 Funciones matemáticas de la biblioteca 6.4 Definiciones de funciones con varios parámetros 6.5 Prototipos de funciones y coerción de argumentos 6.6 Encabezados de la Biblioteca estándar de C++ 6.7 Caso de estudio: generación de números aleatorios 6.8 Caso de estudio: juego de probabilidad; introducción a enum 6.9 Números aleatorios de C++11 6.10 Clases y duración de almacenamiento 6.11 Reglas de alcance 6.12 La pila de llamadas a funciones y los registros de activación 6.13 Funciones con listas de parámetros vacías 6.14 Funciones en línea 6.15 Referencias y parámetros de referencias 6.16 Argumentos predeterminados 6.17 Operador de resolución de ámbito unario 6.18 Sobrecarga de funciones 6.19 Plantillas de funciones 6.20 Recursividad 6.21 Ejemplo sobre el uso de la recursividad: serie de Fibonacci 6.22 Comparación entre recursividad e iteración 6.23 Conclusión Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios | Hacer la diferencia
  • 235. 6.2 Componentes de los programas en C++ 203 cargadas. Este capítulo concluye con una discusión de las funciones que se llaman a sí mismas, ya sea en forma directa o indirecta (a través de otra función): un tema conocido como recursividad. 6.2Componentes de los programas en C++ Como hemos visto, por lo general los programas en C++ se escriben mediante la combinación de nuevas funciones y clases que escribimos con funciones y clases “preempaquetadas”, disponibles en la Biblio- teca estándar de C++, que proporciona una extensa colección de funciones para realizar cálculos mate- máticos comunes, manipulaciones de cadenas, manipulaciones de caracteres, entrada/salida, compro- bación de errores y muchas otras operaciones útiles. Las funciones nos permiten modularizar un programa, al separar sus tareas en unidades autoconte- nidas. Ya ha utilizado una combinación de funciones de biblioteca y sus propias funciones en todos los programas que ha escrito. A las funciones que usted escribe se les conoce como funciones definidas por el usuario. Las instrucciones en los cuerpos de las funciones se escriben sólo una vez, se pueden reutili- zar tal vez desde varias ubicaciones en un programa y están ocultas de las demás funciones. Hay varias razones para modularizar un programa con funciones: • Una de ellas es la metodología divide y vencerás. • Otra de ellas es la reutilización de software. Por ejemplo, en los programas anteriores no tuvi- mos que definir cómo leer una línea de texto del teclado; C++ proporciona esta herramienta a través de la función getline del archivo de encabezado string. • Una tercera motivación es la de evitar repetir código. • Además, al dividir un programa en funciones significativas, es más fácil depurarlo y darle man- tenimiento. Observación de Ingeniería de Software 6.1 Para promover la reutilización de software, toda función debe limitarse a realizar una sola tarea bien definida, y el nombre de la función debe expresar esa tarea con eficiencia. Como sabemos, una función se invoca mediante una llamada, y cuando la función a la que se llamó completa su tarea, devuelve un resultado o simplemente devuelve el control a la función que la lla- mó. Una analogía a esta estructura de un programa es la forma jerárquica de la administración (figura 6.1). Un jefe (similar a la función que hace la llamada) pide a un trabajador (similar a la función que se llamó) que realice una tarea y reporte (devuelva) los resultados, después de completarla. La función jefe no sabe cómo realiza la función trabajador sus tareas designadas. El trabajador también podría llamar a otras funciones trabajador, sin que el jefe supiera. Este ocultamiento de los detalles de implementación promueve la buena ingeniería de software. La figura 6.1 muestra cómo la función jefe se comunica con jefe trabajador2 trabajador3 trabajador1 trabajador5 trabajador4 Fig. 6.1  Relación jerárquica entre la función jefe y las funciones trabajador.
  • 236. 204 Capítulo 6 Funciones y una introducción a la recursividad varias funciones trabajador. La función jefe divide las responsabilidades entre las diversas funciones trabajador, y trabajador1 actúa como “función jefe” para trabajador4 y trabajador5. 6.3Funciones matemáticas de la biblioteca Algunas veces, las funciones como main no son miembros de una clase. A éstas se les conoce como funciones globales. Al igual que las funciones miembro de una clase, los prototipos para las funciones globales se colocan en encabezados, de manera que las funciones globales se puedan reutilizar en cual- quier programa que incluya el archivo de encabezado y pueda crear un enlace con el código objeto de la función. Por ejemplo, recuerde que utilizamos la función pow del archivo de encabezado cmath para elevar un valor a una potencia en la figura 5.6. Introduciremos aquí varias funciones del archivo de en- cabezado cmath para presentar el concepto de las funciones globales que no pertenecen a una clase específica. El encabezado cmath proporciona una colección de funciones que nos permiten realizar cálculos matemáticos comunes. Por ejemplo, puede calcular la raíz cuadrada de 900.0 con la siguiente llamada a la función: sqrt( 900.0 ) La expresión anterior se evalúa como 30.0. La función sqrt recibe un argumento de tipo double y devuelve un resultado double. No hay necesidad de crear objetos antes de llamar a la función sqrt. Además, todas las funciones en el encabezado cmath son globales; por lo tanto, para llamar a cada una de ellas sólo hay que especificar el nombre de la función, seguido de paréntesis que contienen los argu- mentosdelamisma.Sillamamosasqrt conunargumentonegativo,lafunciónestableceunavariableglobal llamada errno con el valor constante EDOM. La variable errno y la constante EDOM se definen en el en- cabezado cerrno. En la sección 6.10 hablaremos sobre las variables globales Tip para prevenir errores 6.1 No llame a sqrt con un argumento negativo. Para el código de nivel industrial, revise siempre que los argumentos que pasa a las funciones matemáticas sean válidos. Los argumentos de una función pueden ser constantes, variables o expresiones más complejas. Si c = 13.0, d = 3.0 y f = 4.0, entonces la instrucción: cout sqrt( c + d * f ) endl; muestra en pantalla la raíz cuadrada de 13.0 + 3.0 * 4.0 = 25.0; a saber, 5.0. Algunas funciones matemáticas de la biblioteca se sintetizan en la figura 6.2. En la figura, las variables x y y son de tipo double. Función Descripción Ejemplo ceil( x ) redondea x al valor más pequeño que no sea menor que x ceil( 9.2 ) es 10.0 ceil( -9.8 ) es -9.0 cos( x ) coseno trigonométrico de x (x está en radianes) cos( 0.0 ) es 1.0 Fig. 6.2  Funciones matemáticas de la biblioteca (parte 1 de 2).
  • 237. 6.4 Definiciones de funciones con varios parámetros 205 Función Descripción Ejemplo exp( x ) función exponencial ex exp( 1.0 ) es 2.718282 exp( 2.0 ) es 7.389056 fabs( x ) valor absoluto de x fabs( 5.1 ) es 5.1 fabs( 0.0 ) es 0.0 fabs( -8.76 ) es 8.76 floor( x ) redondea x al entero más grande, no mayor a x floor( 9.2 ) es 9.0 floor( -9.8 ) es -10.0 fmod( x, y ) residuo de x/y como número de punto flotante fmod( 2.6, 1.2 ) es 0.2 log( x ) logaritmo natural de x (base e) log( 2.718282 ) es 1.0 log( 7.389056 ) es 2.0 log10( x ) logaritmo de x (base 10) log10( 10.0 ) es 1.0 log10( 100.0 ) es 2.0 pow( x, y ) x elevado a la potencia y (xy ) pow( 2, 7 ) es 128 pow( 9, .5 ) es 3 sin( x ) seno trigonométrico de x (x en radianes) sin( 0.0 ) es 0 sqrt( x ) raíz cuadrada de x (en donde x es un valor no negativo) sqrt( 9.0 ) es 3.0 tan( x ) tangente trigonométrica de x (x en radianes) tan( 0.0 ) es 0 6.4Definiciones de funciones con varios parámetros Consideremos las funciones con varios parámetros. El programa en las figuras 6.3 a 6.5 modifica la clase LibroCalificacones, al incluir una función definida por el usuario llamada maximo, la cual deter- mina y devuelve la mayor de tres calificaciones int. Cuando la aplicación empieza su ejecución, la función main (líneas 5 a 13 de la figura 6.5) crea un objeto de la clase LibroCalificaciones (línea 8) y llama a la función miembro recibirCalificaciones del objeto (línea 11) para leer tres calificaciones enteras del usuario. En el archivo de implementación de la clase LibroCalificaciones (figura 6.4), en las líneas 52 y 53 de la función miembro recibirCalificaciones se pide al usuario que introduzca tres valores enteros y los recibe de éste. En la línea 56 se hace una llamada a la función miembro maximo (definida en las líneas 60 a 73). La función maximo determina el valor más grande, y después la instruc- ción return (línea 72) devuelve el valor al punto en el cual la función recibirCalificaciones invocó a maximo (línea 56). Entonces, la función miembro recibirCalificaciones almacena el valor de re- torno de maximo en el miembro de datos calificacionMaxima. Después, este valor se imprime llaman- do a la función mostrarReporteCalificaciones (línea 12 de la figura 6.5). [Nota: a esta función la llamamos mostrarReporteCalificaciones, ya que versiones subsiguientes de la clase LibroCalifi- caciones utilizarán esta función para mostrar un reporte completo de calificaciones, incluyendo las calificaciones máxima y mínima]. En el capítulo 7 mejoraremos la clase LibroCalificaciones para procesar conjuntos de calificaciones. Fig. 6.2  Funciones matemáticas de la biblioteca (parte 2 de 2).
  • 238. 206 Capítulo 6 Funciones y una introducción a la recursividad 1 // Fig. 6.3: LibroCalificaciones.h 2 // Definición de la clase LibroCalificaciones que encuentra el máximo de tres calificaciones. 3 // Las funciones miembro están definidas en LibroCalificaciones.cpp 4 #include string // el programa usa la clase string estándar de C++ 5 6 // definición de la clase LibroCalificaciones 7 class LibroCalificaciones 8 { 9 public: 10 explicit LibroCalificaciones( std::string ); // inicializa el nombre del curso 11 void establecerNombreCurso( std::string ); // establece el nombre del curso 12 std::string obtenerNombreCurso() const; //obtiene el nombre del curso 13 void mostrarMensaje() const; // muestra un mensaje de bienvenida 14 void recibirCalificaciones(); // recibe las tres calificaciones del usuario 15 void mostrarReporteCalificaciones() const; // muestra un reporte con base en las calificaciones 16 int maximo( int, int, int ) const; // determina el máximo de 3 valores 17 private: 18 std::string nombreCurso; // nombre del curso para este LibroCalificaciones 19 int calificacionMaxima; // valor máximo de las tres calificaciones 20 }; // fin de la clase LibroCalificaciones Fig. 6.3  Definición de la clase LibroCalificaciones que encuentra el máximo de tres calificaciones. 1 // Fig. 6.4: LibroCalificaciones.cpp 2 // Definiciones de las funciones miembro para la clase LibroCalificaciones 3 // que determina el máximo de tres calificaciones. 4 #include iostream 5 using namespace std; 6 7 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 8 9 // el constructor inicializa nombreCurso con la cadena suministrada como 10 // argumento; inicializa calificacionMaxima a 0 11 LibroCalificaciones::LibroCalificaciones( string nombre ) 12 : calificacionMaxima( 0 ) // este valor se reemplazará por la calificación máxima 13 { 14 establecerNombreCurso( nombre ); // valida y almacena nombreCurso 15 } // fin del constructor LibroCalificaciones 16 17 // función para establecer el nombre del curso; limita nombre a 25 o menos caracteres 18 void LibroCalificaciones::establecerNombreCurso( string nombre ) 19 { 20 if ( nombre.size() = 25 ) // si nombre tiene 25 o menos caracteres 21 nombreCurso = nombre; // almacena el nombre del curso en el objeto 22 else // si nombre es mayor que 25 caracteres 23 { // establece nombreCurso a los primeros 25 caracteres del parámetro nombre 24 nombreCurso = nombre.substr( 0, 25 ); // selecciona los primeros 25 caracteres 25 cerr El nombre name excede la longitud maxima (25).n 26 Se limito nombreCurso a los primeros 25 caracteres.n endl; 27 } // fin de if...else 28 } // fin de la función establecerNombreCurso Fig. 6.4  Definiciones de las funciones miembro para la clase LibroCalificaciones que determina el máximo de tres calificaciones (parte 1 de 2).
  • 239. 6.4 Definiciones de funciones con varios parámetros 207 29 30 // función para obtener el nombre del curso 31 string LibroCalificaciones::obtenerNombreCurso() const 32 { 33 return nombreCurso; 34 } // fin de la función obtenerNombreCurso 35 36 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones 37 void LibroCalificaciones::mostrarMensaje() const 38 { 39 // esta instrucción llama a obtenerNombreCurso para obtener el 40 // name of the course this GradeBook represents 41 cout Bienvenido al libro de calificaciones paran obtenerNombreCurso() !n 42 endl; 43 } // fin de la función mostrarMensaje 44 45 // recibe tres calificaciones del usuario; determina el valor máximo 46 void LibroCalificaciones::recibirCalificaciones() 47 { 48 int calificacion1; // primera calificación introducida por el usuario 49 int calificacion2; // segunda calificación introducida por el usuario 50 int calificacion3; // tercera calificación introducida por el usuario 51 52 cout Introduzca tres calificaciones enteras: ; 53 cin calificacion1 calificacion2 calificacion3; 54 55 // almacena el valor máximo en el miembro calificacionMaxima 56 calificacionMaxima = maximo( calificacion1, calificacion2, calificacion3 ); 57 } // fin de la función recibirCalificaciones 58 59 // devuelve el máximo de sus tres parámetros enteros 60 int LibroCalificaciones::maximo( int x, int y, int z ) const 61 { 62 int valorMaximo = x; // supone que x es el mayor para empezar 63 64 // determina si y es mayor que valorMaximo 65 if ( y valorMaximo ) 66 valorMaximo = y; // hace a y el nuevo valorMaximo 67 68 // determina si z es mayor que valorMaximo 69 if ( z valorMaximo ) 70 valorMaximo = z; // hace a z el nuevo valorMaximo 71 72 return valorMaximo; 73 } // fin de la función maximo 74 75 // muestra un reporte con base en las calificaciones introducidas por el usuario 76 void LibroCalificaciones::mostrarReporteCalificaciones() const 77 { 78 // imprime el máximo de las calificaciones introducidas 79 cout Calificacion maxima introducida: calificacionMaxima endl; 80 } // fin de la función mostrarReporteCalificaciones Fig. 6.4  Definiciones de las funciones miembro para la clase LibroCalificaciones que determina el máximo de tres calificaciones (parte 2 de 2).
  • 240. 208 Capítulo 6 Funciones y una introducción a la recursividad 1 // Fig. 6.5: fig06_05.cpp 2 // Crea un objeto LibroCalificaciones, introducir calificaciones y mostrar reporte. 3 #include LibroCalificaciones.h // incluye la definición de la clase LibroCalificaciones 4 5 int main() 6 { 7 // crea un objeto LibroCalificaciones 8 LibroCalificaciones miLibroCalificaciones( CS101 Programacion en C++ ); 9 10 miLibroCalificaciones.mostrarMensaje(); // muestra mensaje de bienvenida 11 miLibroCalificaciones.recibirCalificaciones(); // lee calificaciones del usuario 12 miLibroCalificaciones.mostrarReporteCalificaciones(); // muestra reporte con base en las calificaciones 13 } // fin de main Bienvenido al libro de calificaciones para CS101 Programacion en C++! Introduzca tres calificaciones enteras: 86 67 75 Calificacion maxima introducida: 86 Bienvenido al libro de calificaciones para CS101 Programacion en C++! Introduzca tres calificaciones enteras: 67 86 75 Calificacion maxima introducida: 86 Bienvenido al libro de calificaciones para CS101 Programacion en C++! Introduzca tres calificaciones enteras: 67 75 86 Calificacion maxima introducida: 86 Fig. 6.5  Crear objeto LibroCalificaciones, introducir calificaciones y mostrar reporte. Observación de Ingeniería de Software 6.2 Las comas utilizadas en la línea 56 de la figura 6.4 para separar los argumentos de la fun- ción maximo no son operadores coma, como vimos en la sección 5.3. El operador coma ga- rantiza que sus operandos se evalúen de izquierda a derecha. Sin embargo, el estándar de C++ no especifica el orden de evaluación de los argumentos de una función. Por ende, dis- tintos compiladores pueden evaluar los argumentos de una función en distintos órdenes. El estándardeC++garantizaquetodoslosargumentosenlallamadaaunafunciónseevalúen antes de ejecutar la función que se va a llamar. Tip de portabilidad 6.1 Algunas veces, cuando los argumentos de una función son expresiones, como las de las lla- madas a otras funciones, el orden en el que el compilador evalúa los argumentos podría afectar a los valores de uno o más de los argumentos. Si el orden de evaluación cambia entre un compilador y otro, los valores de los argumentos que se pasan a la función podrían variar, lo cual produciría errores lógicos sutiles.
  • 241. 6.4 Definiciones de funciones con varios parámetros 209 Tip para prevenir errores 6.2 Si tiene dudas acerca del orden de evaluación de los argumentos de una función, y de si el orden afectaría a los valores que se pasan a la función, evalúe los argumentos en instruc- ciones de asignación separadas antes de la llamada a la función, asigne el resultado de cada expresión a una variable local y después pase esas variables como argumentos para la función. Prototipo de función para maximo El prototipo de la función miembro maximo (figura 6.3, línea 16) indica que la función devuelve un valor entero, que el nombre de la función es maximo y que requiere tres parámetros enteros para realizar su tarea. La primera línea de la función maximo (figura 6.4, línea 60) concuerda con el prototipo de función e indica que los nombres de los parámetros son x, y y z. Cuando se hace una llamada a maximo (figura 6.4, línea 56), el parámetro x se inicializa con el valor del argumento calificacion1, el paráme- tro y se inicializa con el valor del argumento calificacion2 y el parámetro z se inicializa con el valor del argumento calificacion3. Debe haber un argumento en la llamada a la función para cada paráme- tro (también conocido como parámetro formal) en la definición de la función. Observe que varios parámetros se especifican en el prototipo de función y en el encabezado de la función como una lista separada por comas. El compilador hace referencia al prototipo de la función para comprobar que las llamadas a maximo contengan el número y tipos de argumentos correctos, y que los tipos estén en el orden correcto. Además, el compilador usa el prototipo para asegurar que el valor devuelto por la función se pueda utilizar de manera correcta en la expresión que llamó a la función (por ejemplo, la llamada a una función que devuelve void no se puede utilizar como el lado derecho de una instrucción de asignación). Cada argumento debe ser consistente con el tipo del parámetro correspon- diente.Porejemplo,unparámetrodetipodouble puederecibirvalorescomo7.35,22o–0.03456,pero no una cadena como «hola». Si los argumentos que se pasan a una función no concuerdan con los tipos especificados en el prototipo de la función, el compilador trata de convertir los argumentos a esos ti- pos. En la sección 6.5 hablaremos sobre esta conversión. Error común de programación 6.1 Declarar los parámetros de funciones del mismo tipo como double x, y en vez de double x, double y es un error de sintaxis; se requiere un tipo para cada parámetro en la lista de parámetros. Error común de programación 6.2 Si el prototipo de función, el encabezado y las llamadas a la función no concuerdan en el número, tipo y orden de argumentos y parámetros, además del tipo de retorno, se produ- cen errores de compilación. También pueden ocurrir errores en el enlazador y otros tipos de errores, como veremos más adelante en el libro. Observación de Ingeniería de Software 6.3 Unafunciónquetienemuchosparámetrospuedeestarrealizandomuchastareas.Considere dividir la función en funciones más pequeñas que realicen las tareas por separado. Limite el encabezado de la función a una línea, si es posible. Lógica de la función máximo Para determinar el valor máximo (líneas 60 a 73 de la figura 6.4), empezamos con la suposición de que el parámetro x contiene el valor más grande, por lo que en la línea 62 de la función maximo se declara la variable local valorMaximo y se inicializa con el valor del parámetro x. Desde luego, es posible que el
  • 242. 210 Capítulo 6 Funciones y una introducción a la recursividad parámetro y o z contenga el valor más grande actual, por lo que debemos comparar cada uno de estos valores con valorMaximo. La instrucción if en las líneas 65 y 66 determina si y es mayor que valor- Maximo y, de ser así, asigna y a valorMaximo. La instrucción if en las líneas 69 y 70 determina si z es mayor que valorMaximo y, de ser así, asigna z a valorMaximo. En este punto, el mayor de los tres valo- res está en valorMaximo, por lo que la línea 72 devuelve ese valor a la llamada en la línea 56. Cuando el control del programa regresa al punto en donde se llamó a maximo, los parámetros x, y y z de maximo no están accesibles ya para el programa. Devolver el control de una función a quien la llamó Hay varias formas de devolver el control al punto en el que se invocó a una función. Si la función no devuelve un resultado (es decir, si la función tiene un tipo de valor de retorno void), el control regresa cuando el programa llega a la llave derecha de fin de la función, o mediante la ejecución de la instrucción return; Si la función devuelve un resultado, la instrucción return expresion; evalúa expresion y devuelve el valor de expresion a la función que hizo la llamada. Algunos compiladores generan errores y otros generan advertencias si no se proporciona una instrucción return apropiada en una función que se supone debe devolver un resultado. 6.5Prototipos de funciones y coerción de argumentos Un prototipo de función (también conocido como declaración de función) indica al compilador el nombre de una función, el tipo de datos devuelto por la función, el número de parámetros que espera recibir, los tipos de esos parámetros y el orden en el que éstos se esperan. Observación de Ingeniería de Software 6.4 Los prototipos de función son obligatorios, a menos que la función se defina antes de usarla. Use directivas #include del preprocesador para obtener prototipos de función para las funciones de la Biblioteca estándar de C++ de los encabezados para las bibliotecas apropia- das (por ejemplo, el prototipo para sqrt está en encabezado cmath; una lista parcial de los encabezados de la Biblioteca estándar de C++ aparece en la sección 6.6). Use también #include para obtener encabezados que contengan prototipos de función escritos por usted, o por otros programadores. Error común de programación 6.3 Si se define una función antes de invocarla, entonces su definición también sirve como el prototipo de la misma, por lo que no es necesario un prototipo separado. Si se invoca una función antes de definirla, y no tiene un prototipo de función, se produce un error de com- pilación. Observación de Ingeniería de Software 6.5 Siempre debemos proporcionar prototipos de funciones, aun cuando es posible omitirlas cuandosedefinenlasfunciones antesdeutilizarlas.Alproporcionarlosprototipos,evitamos fijar el código al orden en el que se definen las funciones (lo cual puede cambiar fácilmente, a medida que un programa evoluciona).
  • 243. 6.5 Prototipos de funciones y coerción de argumentos 211 Firmas de funciones La porción de un prototipo de función que incluya el nombre de la función y los tipos de sus argumentos se conoce como la firma de la función, o simplemente firma. La firma de la función no especifica el tipo de valor de retorno de la función. Las funciones en el mismo alcance deben tener firmas únicas. El al- cance de una función es la región del programa en la que la función se conoce y es accesible. En la sección 6.11 hablaremos más acerca del alcance. En la figura 6.3, si el prototipo en la línea 16 se hubiera escrito como: void maximo( int, int, int ); el compilador reportaría un error, ya que el tipo de valor de retorno void en el prototipo de la función sería distinto del tipo de valor de retorno int en el encabezado de la función. De manera similar, dicho prototipo haría que la instrucción cout maximo( 6, 7, 0 ); genere un error de compilación, ya que esa instrucción depende de maximo para devolver un valor que se va a mostrar en pantalla. Coerción de argumentos Una característica importante de los prototipos de función es la coerción de argumentos; es decir, obligar a que los argumentos tengan los tipos especificados por las declaraciones de los parámetros. Por ejemplo, un programa puede llamar a una función con un argumento entero, aun cuando el prototipo de función especifique un argumento double; la función de todas maneras trabajará correctamente. Reglas de promoción de argumentos y conversiones implícitas1 Algunas veces, los valores de los argumentos que no corresponden precisamente a los tipos de los pará- metros en el prototipo de función pueden ser convertidos por el compilador al tipo apropiado, antes de que se haga una llamada a la función. Estas conversiones ocurren según lo especificado por las reglas de promoción de C++. Las reglas de promoción indican las conversiones implícitas que el compila- dor puede realizar entre los tipos fundamentales. Un int se puede convertir en double. Un double también puede convertirse en un int, pero se trunca la parte fraccionaria del valor double. Tenga en cuenta que las variables double pueden contener números de una magnitud mucho mayor que las va- riables int, por lo que la pérdida de datos puede ser considerable. Los valores también pueden modifi- carse al convertir tipos de enteros largos en tipos de enteros pequeños (por ejemplo, de long a short), números con signo a números sin signo, o viceversa. Los enteros sin signo varían desde 0 a un valor aproximado del doble del rango positivo del tipo con signo correspondiente. Las reglas de promoción se aplican a expresiones que contienen valores de dos o más tipos de datos; dichas expresiones se conocen también como expresiones de tipo mixto. El tipo de cada valor en una expresión de tipo mixto se promueve al tipo “más alto” en la expresión (en realidad, se crea y se utiliza una versión temporal de cada valor para la expresión; los valores originales permanecen sin cambios). La promoción también ocurre cuando el tipo de un argumento de función no concuerda con el tipo de parámetro especificado en la definición o prototipo de la función. La figura 6.6 lista los tipos de datos aritméticos en orden del “tipo más alto” al “tipo más bajo”. 1 Las promociones y conversiones son temas complejos que se ven en la sección 4 y al principio de la sección 5 del estándar de C++. Puede comprar una copia del estándar en bit.ly/CPlusPlus11Standard.
  • 244. 212 Capítulo 6 Funciones y una introducción a la recursividad Tipos de datos long double double float unsigned long long int (sinónimo con unsigned long long) long long int (sinónimo con long long) unsigned long int (sinónimo con unsigned long) long int (sinónimo con long) unsigned int (sinónimo con unsigned) int unsigned short int (sinónimo con unsigned short) short int (sinónimo con short) unsigned char char y signed char bool Fig. 6.6  Jerarquía de promociones para los tipos de datos fundamentales. Las conversiones pueden producir valores incorrectos La conversión de valores a los tipos fundamentales más bajos puede producir valores incorrectos. Por lo tanto, un valor se puede convertir en un tipo fundamental menor sólo si se asigna de manera explícita el valor a una variable de tipo inferior (algunos compiladores generarán una advertencia en este caso), o mediante el uso de un operador de conversión (vea la sección 4.9). Los valores de los argumentos de una función se convierten a los tipos de los parámetros en un prototipo de función, como si se hubieran asignado de manera directa a las variables de esos tipos. Si se hace una llamada a una función cuadrado, que utiliza un parámetro entero, con un argumento de punto flotante, el argumento se convierte a int (un tipo más bajo) y cuadrado podría devolver un valor incorrecto. Por ejemplo, cuadrado(4.5) de- vuelve 16, no 20.25. Error común de programación 6.4 Si los argumentos en la llamada a una función no concuerdan con el número y tipos de los parámetros declarados en el prototipo de función correspondiente, se produce un error de compilación.Tambiénesunerrorsielnúmerodeargumentosenlallamadaconcuerda,pero los argumentos no se pueden convertir de manera implícita a los tipos esperados. 6.6Encabezados de la Biblioteca estándar de C++ LaBibliotecaestándardeC++estádivididaenmuchasporciones,cadaunaconsupropioencabezado.Los encabezadoscontienenlosprototiposdefunciónparalasfuncionesrelacionadasqueformancadaporción de la biblioteca. Los encabezados también contienen definiciones de varios tipos de clases y funciones, así comolasconstantesquenecesitanesasfunciones.Unencabezado“instruye”alcompiladoracercadecómo interconectarse con los componentes de la biblioteca y los componentes escritos por el usuario. En la figura 6.7 se listan algunos encabezados comunes de la Biblioteca estándar de C++, la mayoría de los cuales veremos más adelante en el libro.
  • 245. 6.6 Encabezados de la Biblioteca estándar de C++ 213 Encabezado de la Biblioteca estándar de C++ Explicación iostream Contiene prototipos de función para las funciones de entrada y salida estándar de C++, presentadas en el capítulo 2, y que se tratan con más detalle en el capítulo 13, Entrada/salida de flujos: un análisis detallado. iomanip Contiene prototipos de función para los manipuladores de flujo que dan formato a flujos de datos. Este encabezado se utiliza primero en la sección 4.9 y se analiza con más detalle en el capítulo 13, Entrada/salida de flujos: un análisis detallado. cmath Contiene prototipos de función para las funciones de la biblioteca de matemáticas (sección 6.3). cstdlib Contiene prototipos de función para las conversiones de números a texto, de texto a números, asignación de memoria, números aleatorios y varias otras funciones utilitarias. En la sección 6.7 veremos partes de este encabezado; también en el capítulo 11, Sobrecarga de operadores; la clase string; en el capítulo 17 (en el sitio web), Manejo de excepciones: un análisis más detallado; en el capítulo 22 (en inglés, en el sitio web), Bits, caracteres, cadenas tipo C y tipos struct; y en el apéndice F, Temas sobre código heredado de C. ctime Contiene prototipos de función y tipos para manipular la hora y la fecha. Este encabezado se utiliza en la sección 6.7. array, vector, list, forward_list, deque, queue, stack, map, unordered_map, unordered_set, set, bitset Estos encabezados contienen clases que implementan los contenedores de la Biblioteca estándar de C++. Los contenedores almacenan datos durante la ejecución de un programa. El encabezado vector se introduce por primera vez en el capítulo 7, Plantillas de clase array y vector; cómo atrapar excepciones. En el capítulo 15 (en el sitio web), Contenedores e iteradores de la Biblioteca estándar hablaremos sobre todos estos encabezados. cctype Contiene prototipos de función para las funciones que evalúan caracteres en base a ciertas propiedades (por ejemplo, si el carácter es un dígito o un signo de puntua- ción), y prototipos de funciones que se pueden utilizar para convertir letras minúsculas a mayúsculas y viceversa. Hablaremos sobre estos temas en el capítulo 22 (en inglés, en el sitio web), Bits, caracteres, cadenas tipo C y tipos struct. cstring Contiene prototipos de funciones para las funciones de procesamiento de cadenas estilo C. Este encabezado se utiliza en el capítulo 10, Sobrecarga de operadores; la clase string. typeinfo Contiene clases para la identificación de tipos en tiempo de ejecución (determinar los tipos de datos en tiempo de ejecución). Este archivo de encabezado se describe en la sección 12.8. exception, stdexcept Estosencabezadoscontienenclasesqueseutilizanparamanejarexcepciones(sedescriben enelcapítulo17(enelsitioweb),Manejodeexcepciones:unanálisismásdetallado). memory Contiene clases y funciones utilizadas por la Biblioteca estándar de C++ para asignar memoria a los contenedores de la Biblioteca estándar de C++. Este encabezado se utiliza en el capítulo 17 (en el sitio web), Manejo de excepciones: un análisis más detallado. fstream Contiene prototipos de funciones para las funciones que realizan operaciones de entrada desde archivos en disco, y operaciones de salida hacia archivos en disco (que veremos en el capítulo 14, Procesamiento de archivos). string Contiene la definición de la clase string de la Biblioteca estándar de C++ (que veremos en el capítulo 21 [en inglés, en el sitio web], La clase String y el procesa- miento de flujos de cadena). Fig. 6.7  Encabezados de la Biblioteca estándar de C++ (parte 1 de 2).
  • 246. 214 Capítulo 6 Funciones y una introducción a la recursividad Encabezado de la Biblioteca estándar de C++ Explicación sstream Contiene prototipos de función para las funciones que realizan operaciones de entrada a partir de cadenas en memoria, y operaciones de salida hacia cadenas en memoria (que veremos en el capítulo 21(en inglés, en el sitio web). (La clase string y el procesamiento de flujos de cadena). functional Contiene las clases y funciones utilizadas por algoritmos de la Biblioteca estándar de C++. Este encabezado se utiliza en el capítulo 15. iterator Contiene clases para acceder a los datos en los contenedores de la Biblioteca estándar de C++. Este encabezado se utiliza en el capítulo 15. algorithm Contiene las funciones para manipular los datos en los contenedores de la Biblioteca estándar de C++. Este encabezado se utiliza en el capítulo 15. cassert Contiene macros para agregar diagnósticos que ayuden a depurar programas (debug). Este encabezado se utiliza en el apéndice E, Preprocesador. cfloat Contiene los límites del sistema en cuanto al tamaño de los números de punto flotante. climits Contiene los límites del sistema en cuanto al tamaño de los números enteros. cstdio Contiene los prototipos de función para las funciones de la biblioteca de entrada/ salida estándar estilo C. locale Contiene clases y funciones que se utilizan comúnmente en el procesamiento de flujos, para procesar datos en la forma natural para distintos lenguajes (por ejemplo, formatos monetarios, almacenamiento de cadenas, presentación de caracteres, etcétera). limits Contiene clases para definir los límites de los tipos de datos numéricos en cada plataforma computacional. utility Contiene clases y funciones utilizadas por muchos encabezados de la Biblioteca estándar de C++. 6.7Caso de estudio: generación de números aleatorios [Nota: las técnicas de generación de números aleatorios que veremos en esta sección y en la sección 6.8 se incluyen para los lectores que todavía no utilizan compiladores de C++11. En la sección 6.9 presen- taremos las herramientas mejoradas para generar números aleatorios de C++11]. Ahora analizaremos de manera breve una parte divertida de un tipo popular de aplicaciones de la pro- gramación: simulación y juegos. En ésta y en la siguiente sección desarrollaremos un programa de un juego que incluye varias funciones. El elemento de azar puede introducirse en las aplicaciones computacionales mediante el uso de la función rand de la Biblioteca estándar de C++. Considere la siguiente instrucción: i = rand(); La función rand genera un entero sin signo entre 0 y RAND_MAX (una constante simbólica definida en el encabezado cstdlib. Para determinar el valor de RAND_MAX para su sistema, sólo tiene que mostrar la constante. Si rand produce verdaderamente enteros al azar, cada número entre 0 y RAND_MAX tiene una oportunidad (o probabilidad) igual de ser elegido cada vez que se llame a rand. Fig. 6.7  Encabezados de la Biblioteca estándar de C++ (parte 2 de 2).
  • 247. 6.7 Caso de estudio: generación de números aleatorios 215 El rango de valores producidos directamente por la función rand es a menudo distinto de lo que requiere una aplicación específica. Por ejemplo, un programa que simula el lanzamiento de una moneda sólo requiere 0 para “águila” y 1 para “sol”. Un programa para simular el tiro de un dado de seis lados requeriría enteros aleatorios en el rango de 1 a 6. Un programa que adivine en forma aleatoria el siguien- te tipo de nave espacial (de cuatro posibilidades distintas) que volará a lo largo del horizonte en un vi- deojuego requeriría números aleatorios en el rango de 1 a 4. Tirar un dado de seis lados Para demostrar la función rand, en la figura 6.8 se simulan 20 tiros de un dado de seis lados, y se mues- tra el valor de cada tiro. El prototipo de la función rand se encuentra en cstdlib. Para producir va- lores enteros en el rango de 0 a 5, usamos el operador módulo (%) con rand, como se muestra a conti- nuación: rand() % 6 A esto se le conoce como escalar. El número 6 se conoce como el factor de escala. Después desplaza- mos el rango de números producidos sumando 1 a nuestro resultado anterior. En la figura 6.8 se con- firma que los resultados están en el rango de 1 a 6. Si ejecuta este programa más de una vez, podrá comprobarqueproducelosmismosvalores“aleatorios”cadavez.Enlafigura6.10lemostraremoscómo corregir esto. 1 // Fig. 6.8: fig06_08.cpp 2 // Enteros desplazados y escalados, producidos por 1 + rand() % 6. 3 #include iostream 4 #include iomanip 5 #include cstdlib // contiene el prototipo de funcion para rand 6 using namespace std; 7 8 int main() 9 { 10 // itera 20 veces 11 for ( unsigned int contador = 1; contador = 20; ++contador ) 12 { 13 // elije un número aleatorio de 1 a 6 y lo imprime 14 cout setw( 10 ) ( 1 + rand() % 6 ); 15 16 // si contador puede dividirse entre 5, empieza una nueva línea de salida 17 if ( contador % 5 == 0 ) 18 cout endl; 19 } // fin de for 20 } // fin de main 6 6 5 5 6 5 1 1 5 3 6 6 2 4 2 6 2 3 4 1 Fig. 6.8  Enteros desplazados y escalados, producidos por 1 + rand() % 6. Tirar un dado de seis lados 6000000 veces Para mostrar que los números que produce la función rand ocurren con una probabilidad aproximada- mente igual, la figura 6.9 simula 6000000 de tiros de un dado. Cada entero en el rango de 1 a 6 debe aparecer aproximadamente 1000000 veces. Esto se confirma mediante la salida del programa.
  • 248. 216 Capítulo 6 Funciones y una introducción a la recursividad 1 // Fig. 6.9: fig06_09.cpp 2 // Tiro de un dado de seis lados 6 000 000 veces. 3 #include iostream 4 #include iomanip 5 #include cstdlib // contiene el prototipo de la función rand 6 using namespace std; 7 8 int main() 9 { 10 unsigned int frecuencia1 = 0; // cuenta las veces que se tiró 1 11 unsigned int frecuencia2 = 0; // cuenta las veces que se tiró 2 12 unsigned int frecuencia3 = 0; // cuenta las veces que se tiró 3 13 unsigned int frecuencia4 = 0; // cuenta las veces que se tiró 4 14 unsigned int frecuencia5 = 0; // cuenta las veces que se tiró 5 15 unsigned int frecuencia6 = 0; // cuenta las veces que se tiró 6 16 17 // sintetiza los resultados de tirar un dado 6 000 000 veces 18 for ( unsigned int tiro = 1; tiro = 6000000; ++tiro ) 19 { 20 unsigned int cara = 1 + rand() % 6; // número aleatorio del 1 al 6 21 22 // determina el valor del tiro de 1 a 6 e incrementa el contador apropiado 23 switch ( cara ) 24 { 25 case 1: 26 ++frecuencia1; // incrementa el contador de 1 27 break; 28 case 2: 29 ++frecuencia2; // incrementa el contador de 2 30 break; 31 case 3: 32 ++frecuencia3; // incrementa el contador de 3 33 break; 34 case 4: 35 ++frecuencia4; // incrementa el contador de 4 36 break; 37 case 5: 38 ++frecuencia5; // incrementa el contador de 5 39 break; 40 case 6: 41 ++frecuencia6; // incrementa el contador de 6 42 break; 43 default: // valor inválido 44 cout El programa nunca debio llegar aqui!; 45 } // fin de switch 46 } // fin de for 47 48 cout Cara setw( 13 ) Frecuencia endl; // imprime encabezados 49 cout 1 setw( 13 ) frecuencia1 50 n 2 setw( 13 ) frecuencia2 51 n 3 setw( 13 ) frecuencia3 52 n 4 setw( 13 ) frecuencia4 Fig. 6.9  Tiro de un dado de seis lados 6000000 veces (parte 1 de 2).
  • 249. 6.7 Caso de estudio: generación de números aleatorios 217 53 n 5 setw( 13 ) frecuencia5 54 n 6 setw( 13 ) frecuencia6 endl; 55 } // fin de main Cara Frecuencia 1 999702 2 1000823 3 999378 4 998898 5 1000777 6 1000422 Como se muestra en los resultados del programa, al escalar y desplazar los valores producidos por rand, podemos simular el tiro de un dado de seis lados. El programa nunca debe llegar al caso default (líneas 43 y 44) en la estructura switch, ya que la expresión de control del switch (cara) siempre tiene valores en el rango de 1 a 6; sin embargo, proporcionamos el caso default como una cuestión de buena práctica. Una vez que estudiemos los arreglos en el capítulo 7, le mostraremos cómo reem- plazar toda la estructura switch de la figura 6.9 de una manera elegante, con una instrucción de una sola línea. Tip para prevenir errores 6.3 Hay que proporcionar un caso default en una instrucción switch para atrapar errores, ¡incluso si estamos absoluta y positivamente seguros de no tener errores! Randomización del generador de números aleatorios Al ejecutar el programa de la figura 6.8 otra vez, se produce lo siguiente: 6 6 5 5 6 5 1 1 5 3 6 6 2 4 2 6 2 3 4 1 El programa imprime exactamente la misma secuencia de valores que se muestra en la figura 6.8. ¿Cómo pueden ser estos números aleatorios? Al depurar un programa de simulación, esta repetitividad es esencial para demostrar que las correcciones al programa funcionan en forma apropiada. En realidad, la función rand genera números seudoaleatorios. Si se llama repetidas veces a rand, se produce una secuencia de números que parecen ser aleatorios. No obstante, la secuencia se repite a sí misma cada vez que se ejecuta el programa. Una vez que un programa se ha depurado extensivamen- te, puede condicionarse para producir una secuencia diferente de números aleatorios para cada ejecu- ción. A esto se le conoce como randomización, y se logra mediante la función srand de la Biblioteca estándar de C++. La función srand recibe un argumento entero unsigned y siembra la función rand para que produzca una secuencia distinta de números aleatorios para cada ejecución del programa. C++11 proporciona herramientas adicionales para números aleatorios que pueden producir números aleatorios no determinísticos: un conjunto de números aleatorios que no pueden producirse. Dichos generadores de números aleatorios se utilizan en simulaciones y escenarios de seguridad en donde la predictibilidad es indeseable. La sección 6.9 introduce las herramientas de generación de números aleatorios de C++11. Fig. 6.9  Tiro de un dado de seis lados 6000000 veces (parte 2 de 2).
  • 250. 218 Capítulo 6 Funciones y una introducción a la recursividad Buena práctica de programación 6.1 Asegúrese de que su programa siembre el generador de números aleatorios de manera distin- ta (y sólo una vez) cada vez que se ejecute el programa; de lo contrario, un atacante podría determinar con facilidad la secuencia de números seudoaleatorios que se producirían. Sembrar el generador de números aleatorios con srand En la figura 6.10 se demuestra el uso de la función srand. El programa utiliza el tipo de datos unsigned int. Un valor int se representa cuando menos por dos bytes; por lo general, cuatro bytes en los sistemas de 32 bits y puede ser de hasta ocho bytes en sistemas de 64 bits. Un int puede tener valores positivos y negativos. Una variable de tipo unsigned int también se almacena en al menos dos bytes de memoria. Un valor unsigned int de cuatro bytes puede tener sólo valores no negativos en el rango de 0 a 4294967295. La función srand recibe un valor unsigned int como argumento. El prototipo para la función srand se encuentra en el encabezado cstdlib. 1 // Fig. 6.10: fig06_10.cpp 2 // Randomización del programa para tirar dados. 3 #include iostream 4 #include iomanip 5 #include cstdlib // contiene los prototipos para las funciones srand y rand 6 using namespace std; 7 8 int main() 9 { 10 unsigned int semilla = 0; // almacena la semilla introducida por el usuario 11 12 cout Introduzca la semilla: ; 13 cin semilla; 14 srand( semilla ); // siembra el generador de números aleatorios 15 16 // itera 10 veces 17 for ( unsigned int contador = 1; contador = 10; ++contador ) 18 { 19 // elije un número aleatorio entre 1 y 6, y lo imprime 20 cout setw( 10 ) ( 1 + rand() % 6 ); 21 22 // si contador puede dividirse entre 5, empieza una nueva línea de salida 23 if ( contador % 5 == 0 ) 24 cout endl; 25 } // fin de for 26 } // fin de main Introduzca la semilla: 67 6 1 4 6 2 1 6 1 6 4 Introduzca la semilla: 432 4 6 3 1 6 3 1 5 4 2 Fig. 6.10  Randomización del programa para tirar dados (parte 1 de 2).
  • 251. 6.8 Caso de estudio: juego de probabilidad; introducción a enum 219 Introduzca la semilla: 67 6 1 4 6 2 1 6 1 6 4 El programa produce una secuencia distinta de números aleatorios cada vez que se ejecuta, siempre y cuando el usuario introduzca una semilla distinta. Utilizamos la misma semilla en la primera y la ter- cera ventana de resultados, por lo que se muestra la misma serie de 10 números en cada uno de esos re- sultados. Sembrar el generador de números aleatorios con la hora actual Para randomizar sin tener que introducir una semilla cada vez, podemos usar una instrucción como la siguiente: srand( static_castunsigned int( time( 0 ) ) ); Esto hace que la computadora lea su reloj para obtener el valor para la semilla. Por lo general, la función time (con el argumento 0, como se escribe en la instrucción anterior) devuelve la hora actual como el número de segundos transcurridos desde enero 1, 1970, a media noche en Tiempo del Meridiano de Greenwich(GMT).Estevalor(queesdetipotime_t)seconvierteenununsigned int yseutilizacomo semilla para el generador de números aleatorios; la palabra clave static_cast en la instrucción anterior elimina una advertencia del compilador que se genera si pasamos un valor time_t a una función que espera un valor unsigned int. El prototipo de la función time está en ctime. Escalamiento y desplazamiento de números aleatorios Anteriormente simulamos cómo tirar un dado de seis lados con la instrucción: cara = 1 + rand() % 6; la cual siempre asigna un entero (al azar) a la variable cara en el rango 1 ≤ cara ≤ 6. La amplitud de este rango (es decir, el número de enteros consecutivos en el rango) es 6, y el número inicial en el rango es 1. Si hacemos referencia a la instrucción anterior, podemos ver que la amplitud del rango se determi- na en base al número que se utiliza para escalar rand con el operador módulo (es decir, 6), y el número inicial del rango es igual al número (es decir, 1) que se agrega a la expresión rand % 6. Podemos gene- ralizar este resultado de la siguiente manera: numero = valorDesplazamiento + rand() % factorEscala; en donde valorDesplazamiento es igual al primer número en el rango deseado de enteros consecutivos y factorEscala es igual a la amplitud del rango deseado de enteros consecutivos. 6.8Caso de estudio: juego de probabilidad; introducción a enum Uno de los juegos de azar más populares es el juego de dados conocido como “craps”, el cual se juega en casinos y callejones por todo el mundo. Las reglas del juego son simples: Un jugador tira dos dados. Cada dado tiene seis caras, las cuales contienen 1, 2, 3, 4, 5 y 6 puntos negros. Unavezquelosdadosdejandemoverse,secalculalasumadelospuntosnegrosenlasdoscarassuperiores. Si la suma es 7 u 11 en el primer tiro, el jugador gana. Si la suma es 2, 3 o 12 en el primer tiro (llamado “craps”), el jugador pierde (es decir, la “casa” gana). Si la suma es 4, 5, 6, 8, 9 o 10 en el primer tiro, esta sumaseconvierteenel“punto”deljugador.Paraganar,eljugadordebeseguirtirandolosdadoshastaque salga otra vez “su punto”. El jugador pierde si tira un 7 antes de llegar a su punto. Fig. 6.10  Randomización del programa para tirar dados (parte 2 de 2).
  • 252. 220 Capítulo 6 Funciones y una introducción a la recursividad El programa de la figura 6.11 simula el juego de craps. En las reglas del juego, observe que el jugador debe tirar dos dados en el primer tiro y en todos los tiros subsiguientes. Definimos la función tirar- Dados (líneas 62 a 74) para tirar el dado, calcular e imprimir la suma. La función está definida sólo una vez, pero se llama desde las líneas 20 y 44. La función no recibe argumentos y devuelve la suma de los dos dados, por lo que se indican paréntesis vacíos y el tipo de valor de retorno unsigned int en el pro- totipo de la función (línea 8) y en el encabezado de la misma (línea 62). 1 // Fig. 6.11: fig06_11.cpp 2 // Simulación del juego de craps. 3 #include iostream 4 #include cstdlib // contiene los prototipos para las funciones srand y rand 5 #include ctime // contiene el prototipo para la función time 6 using namespace std; 7 8 unsigned int rollDice(); // tira los dados, calcula y muestra la suma 9 10 int main() 11 { 12 // enumeración con constantes que representa el estado del juego 13 enum Estado { CONTINUAR, GANO, PERDIO }; // todas las letras mayúsculas en las constantes 14 15 // randomiza el generador de números aleatorios, usando la hora actual 16 srand( static_castunsigned int( time( 0 ) ) ); 17 18 unsigned int miPunto = 0; // punto si no se gana o pierde en el primer tiro 19 Estado estadoJuego = CONTINUAR; // puede contener CONTINUAR, GANO o PERDIO 20 unsigned int sumaDeDados = tirarDados() ; // primer tiro del dado 21 22 // determina el estado del juego y el punto (si es necesario), con base en el primer tiro 23 switch ( sumaDeDados ) 24 { 25 case 7: // gana con 7 en el primer tiro 26 case 11: // gana con 11 en el primer tiro 27 estadoJuego = GANO; 28 break; 29 case 2: // pierde con 2 en el primer tiro 30 case 3: // pierde con 3 en el primer tiro 31 case 12: // pierde con 12 en el primer tiro 32 estadoJuego = PERDIO; 33 break; 34 default: // no gano ni perdio, por lo que recuerda el punto 35 estadoJuego = CONTINUAR; // el juego no ha terminado 36 miPunto = sumaDeDados; // recuerda el punto 37 cout El punto es miPunto endl; 38 break; // opcional al final del switch 39 } // fin de switch 40 41 // mientras el juego no esté completo 42 while ( CONTINUAR == estadoJuego ) // no GANO ni PERDIO 43 { 13 Fig. 6.11  Simulación del juego de “craps” (parte 1 de 3).
  • 253. 6.8 Caso de estudio: juego de probabilidad; introducción a enum 221 44 sumaDeDados = tirarDados(); // tira los dados de nuevo 45 46 // determina el estado del juego 47 if ( sumaDeDados == miPunto ) // gana al hacer un punto 48 estadoJuego = GANO; 49 else 50 if ( sumaDeDados == 7 ) // pierde al tirar 7 antes del punto 51 estadoJuego = PERDIO; 52 } // fin de while 53 54 // muestra mensaje de que ganó o perdió 55 if ( GANO == estadoJuego ) 56 cout El jugador gana endl; 57 else 58 cout El jugador pierde endl; 59 } // fin de main 60 61 // tira los dados, calcula la suma y muestra los resultados 62 unsigned int tirarDados() 63 { 64 // elige valores aleatorios para el dado 65 unsigned int dado1 = 1 + rand() % 6; // tiro del primer dado 66 unsigned int dado2 = 1 + rand() % 6; // tiro del segundo dado 67 68 unsigned int suma = dado1 + dado2; // calcula la suma de valores de los dados 69 70 // muestra los resultados de este tiro 71 cout El jugador tiro dado1 + dado2 72 = suma endl; 73 return suma; // devuelve la suma de los dados 74 } // fin de la función tirarDados El jugador tiro 2 + 5 = 7 El jugador gana El jugador tiro 6 + 6 = 12 El jugador pierde El jugador tiro 1 + 3 = 4 El punto es 4 El jugador tiro 4 + 6 = 10 El jugador tiro 2 + 4 = 6 El jugador tiro 6 + 4 = 10 El jugador tiro 2 + 3 = 5 El jugador tiro 2 + 4 = 6 El jugador tiro 1 + 1 = 2 El jugador tiro 4 + 4 = 8 El jugador tiro 4 + 3 = 7 El jugador pierde Fig. 6.11  Simulación del juego de “craps” (parte 2 de 3).
  • 254. 222 Capítulo 6 Funciones y una introducción a la recursividad El jugador tiro 3 + 3 = 6 El punto es 6 El jugador tiro 5 + 3 = 8 El jugador tiro 4 + 5 = 9 El jugador tiro 2 + 1 = 3 El jugador tiro 1 + 5 = 6 El jugador gana El tipo de enum Estado El jugador puede ganar o perder en el primer tiro, o en cualquier tiro subsiguiente. El programa utiliza la variable estadoJuego para llevar la cuenta de esto. La variable estadoJuego se declara como del nue- vo tipo Estado. En la línea 13 se declara un tipo definido por el usuario, llamado enumeración, que se introduce mediante la palabra clave enum (por enumeración) y va seguida de un nombre de tipo (en este caso, Estado), además de un conjunto de constantes enteras representadas por identificadores. Los va- lores de estas constantes de enumeración empiezan en 0, a menos que se especifique lo contrario, y se incrementan en 1. En la enumeración anterior, la constante CONTINUAR tiene el valor 0, GANO tiene el valor 1 y PERDIO tiene el valor 2. Los identificadores en una enumeración deben ser únicos, pero las constantes de enumeración separadas pueden tener el mismo valor entero. Buena práctica de programación 6.2 La primera letra de un identificador que se utilice como un nombre de tipo definido por el usuario debe ir en mayúscula. Buena práctica de programación 6.3 Use sólo letras mayúsculas en los nombres de las constantes de enumeración. Esto hace que resalten y le recuerdan que las constantes de enumeración no son variables. Las variables del tipo Estado definido por el usuario pueden recibir sólo uno de los tres valores declarados en la enumeración. Cuando se gana el juego, el programa establece la variable estadoJuego a GANO (líneas 27 y 48). Cuando se pierde el juego, el programa establece la variable estadoJuego a PERDIO (líneas 32 y 51). En caso contrario, el programa establece la variable estadoJuego a CONTINUAR (línea 35) para indicar que el dado se debe tirar de nuevo. Error común de programación 6.5 Asignar el equivalente entero de una constante de enumeración (en vez de la misma constante de enumeración) a una variable del tipo de enumeración es un error de com- pilación. Otra enumeración popular es enum Meses { ENE = 1, FEB, MAR, ABR, MAY, JUN, JUL, AGO, SEP, OCT, NOV, DIC }; la cual crea el tipo Meses definido por el usuario, con constantes de enumeración que representan los meses del año. El primer valor en la enumeración anterior se establece explícitamente en 1, por lo que el resto de los valores se incrementan a partir de 1, lo cual produce los valores del 1 al 12. Cualquier cons- tante de enumeración puede recibir un valor entero en la definición de la enumeración, y cada una de las constantes de enumeración subsiguientes tiene un valor igual a 1 más que la constante anterior en la lista, hasta la siguiente configuración explícita. Fig. 6.11  Simulación del juego de “craps” (parte 3 de 3).
  • 255. 6.8 Caso de estudio: juego de probabilidad; introducción a enum 223 Tip para prevenir errores 6.4 Use valores únicos para las constantes de una enumeración, para ayudar a evitar los erro- res lógicos difíciles de localizar. Ganar o perder en el primer tiro Después del primer tiro, si se gana o pierde el juego, el programa omite el cuerpo de la instrucción while (líneas 42 a 52) debido a que estadoJuego no es igual a CONTINUAR. El programa pasa a la instruc- ción if...else en las líneas 55 a 58, que imprime El jugador gana si estadoJuego es igual a GANO y El jugador pierde si estadoJuego es igual a PERDIO. Continuar los tiros Después del primer tiro, si el juego no ha terminado, el programa guarda la suma en miPunto (línea 36). La ejecución continúa con la instrucción while, ya que estadoJuego es igual a CONTINUAR. Durante cada iteración del while, el programa llama a tirarDados para producir una nueva suma. Si suma con- cuerda con miPunto, el programa establece estadoJuego en GANO (línea 48), la prueba del while falla, la instrucción if...else imprime El jugador gana y termina la ejecución. Si suma es igual a 7, el programa establece estadoJuego a PERDIO (línea 51), falla la prueba del while, la instrucción if...else imprime El jugador pierde y termina la ejecución. El programa de “craps” utiliza dos funciones (main y tirarDados) y las instrucciones switch, while, if...else, if...else anidadas e if anidadas. En los ejercicios investigamos varias característi- cas interesantes del juego de “craps”. C++11: enum con alcance En la figura 6.11 presentamos las enumeraciones (enum). Un problema con las enumeraciones (que también se conocen como enumeraciones sin alcance) es que varias enumeraciones pueden contener los mismos identificadores. Si se utilizan dichas enumeraciones en el mismo programa, se pueden producir conflictos de nombres y errores lógicos. Para eliminar estos problemas, C++11 introduce lo que se co- noce como enumeraciones con alcance, que se declaran con las palabras clave enum class (o con el sinónimo enum struct). Por ejemplo, podemos definir la enumeración Estado de la figura 6.11 como: enum class Estado { CONTINUAR, GANO, PERDIO }; Para hacer referencia a una constante enum con alcance, debemos calificar la constante con el nombre del tipoenumconalcance(Estado)yeloperadorderesolucióndeámbito(::),comoenEstado::CONTINUAR. Esto identifica explícitamente a CONTINUAR como una constante en el alcance de la enum class Estado. De esta forma, si otra enum con alcance contiene el mismo identificador para una de sus constantes, siempre queda claro qué versión de la constante se está utilizando. Tip para prevenir errores 6.5 Use enum con alcance para evitar conflictos de nombres y errores lógicos potenciales debido a las enumeraciones sin alcance que contienen los mismos identificadores. C++11: especificar el tipo de las constantes de una enumeración Las constantes en una enum se representan como enteros. De manera predeterminada, el tipo integral subyacente de una enum sin alcance depende de los valores de sus constantes; se garantiza que el tipo será lo bastante grande como para almacenar los valores constantes especificados. De manera predetermina- da, el tipo integral subyacente de una enum con alcance es int. C++11 nos permite especificar el ti- po integral subyacente de una enum siguiendo el nombre del tipo de enum con dos puntos (:) además del tipo integral. Por ejemplo, podemos especificar que las constantes en la enum class Estado tengan el tipo unsigned int, como en: enum class Estado : unsigned int { CONTINUAR, GANO, PERDIO };
  • 256. 224 Capítulo 6 Funciones y una introducción a la recursividad Error común de programación 6.6 Si el valor de la constante de una enum está fuera del rango que puede representarse por el tipo subyacente de esa enum, se produce un error de compilación. 6.9Números aleatorios de C++11 De acuerdo con el CERT, la función rand no tiene “buenas propiedades estadísticas” y puede ser prede- cible, lo que hace a los programas que usan rand menos seguros (Lineamiento de CERT MSC30-CPP). Como mencionamos en la sección 6.7, C++11 cuenta con una nueva y más segura biblioteca de herra- mientasdegeneracióndenúmerosaleatoriosquepuedenproducirnúmerosaleatoriosnodeterminísticos para simulaciones y casos de seguridad, en donde no es conveniente la predictibilidad. Estas nuevas he- rramientas se encuentran en el encabezado random de la Biblioteca estándar de C++. La generación de números aleatorios es un tema matemático sofisticado, para el cual los matemáti- cos han desarrollado muchos algoritmos de generación de números aleatorios con diferentes propieda- des estadísticas. Por cuestión de flexibilidad, con base en cómo se utilizan los números aleatorios en los programas, C++11 cuenta con muchas clases que representan diversos motores de generación de núme- ros aleatorios y distribuciones. Un motor implementa un algoritmo de generación de números aleatorios que produce números seudoaleatorios. Una distribución controla el rango de valores producidos por un motor, los tipos de esos valores (por ejemplo, int, double, etc.) y las propiedades estadísticas de los valores. En esta sección, usaremos el motor de generación de números aleatorios predeterminado (default_random_engine) y la distribución uniform_int_distribution, que distribuye uniforme- mente los enteros seudoaleatorios a través de un rango especificado de valores. El rango predeterminado es de 0 al valor máximo de un int en su plataforma. Tirar un dado de seis lados La figura 6.12 usa default_random_engine y uniform_int_distribution para tirar un dado de seis lados. La línea 14 crea un objeto default_random_engine llamado motor. El argumento de su construc- tor siembra el motor de generación de números aleatorios con la hora actual. Si no pasamos un valor al constructor, se utilizará la semilla predeterminada y el programa producirá la misma secuencia de núme- ros cada vez que se ejecute. La línea 15 crea intAleatorio: un objeto uniform_int_distribution que produce valores unsigned int (según lo especificado por unsigned int) en el rango de 1 a 6 (como lo especifican los argumentos del constructor). La expresión intAleatorio(motor) (línea 21) devuelve un valor unsigned int en el rango de 1 a 6. 1 // Fig. 6.12: fig06_12.cpp 2 // Uso de un motor de generación de números aleatorios y una distribución 3 // de C++11 para tirar un dado con seis lados. 4 #include iostream 5 #include iomanip 6 #include random // contiene herramientas de generación de números aleatorios de C++11 7 #include ctime 8 using namespace std; 9 10 int main() 11 { 6 Fig. 6.12  Uso de un motor de generación de números aleatorios y una distribución de C++11 para tirar un dado con seis lados (parte 1 de 2).
  • 257. 6.10 Clases y duración de almacenamiento 225 12 // usa el motor de generación de números aleatorios predeterminado para 13 // producir valores int seudoaleatorios del 1 al 6, distribuidos de manera uniforme 14 default_random_engine motor( static_castunsigned int( time(0) ) ); 15 uniform_int_distributionunsigned int intAleatorio( 1, 6 ); 16 17 // itera 10 veces 18 for ( unsigned int contador = 1; contador = 10; ++contador ) 19 { 20 // elije un número aleatorio del 1 al 6 y lo imprime 21 cout setw( 10 ) intAleatorio( motor ); 22 23 // si contador es divisible entre 5, comienza una nueva línea de salida 24 if ( contador % 5 == 0 ) 25 cout endl; 26 } // fin de for 27 } // fin de main 2 1 2 3 5 6 1 5 6 4 La notación unsigned int en la línea 15 indica que uniform_int_distribution es una plantilla de clase. En este caso, puede especificarse cualquier tipo entero entre los signos y . En el capítulo 18 (en inglés, en el sitio web) veremos cómo crear plantillas de clases y en otros capítulos le mostraremos cómo usar las plantillas de clases existentes de la Biblioteca estándar de C++. Por ahora, siéntase cómodo al usar la plantilla de clase uniform_int_distribution, imitando la sintaxis que se muestra en el ejemplo. 6.10Clases y duración de almacenamiento Los programas que hemos visto hasta ahora utilizan identificadores para nombres de variables y funcio- nes. Los atributos de las variables incluyen su nombre, tipo, tamaño y valor. Cada identificador en un programa tiene también otros atributos, incluyendo la duración del almacenamiento, el alcance y la vinculación. C++ proporciona cinco especificadores de clase de almacenamiento que determinan la dura- ción del almacenamiento de una variable: register, extern, mutable, static y thread_local. En esta sección hablaremos sobre los especificadores de clase de almacenamiento register, extern y static. El especificador de clase de almacenamiento mutable se utiliza exclusivamente con las clases y thread_local se utiliza en las aplicaciones multihilo (hablaremos sobre ellos en los capítulos 23 y 24 [en inglés, en el sitio web], respectivamente). Duración del almacenamiento La duración del almacenamiento de un identificador determina el periodo durante el cual éste existe en la memoria. Algunos identificadores existen brevemente, algunos se crean y destruyen repetidas veces, y otros existen durante toda la ejecución de un programa. Primero hablaremos sobre las clases de almace- namiento static y automatic. Alcance El alcance de un identificador es la parte en la que se puede hacer referencia a éste en un programa. Se puede hacer referencia a algunos identificadores a lo largo de un programa; otros identificadores sólo se pueden referenciar desde ciertas partes limitadas de un programa. En la sección 6.11 hablaremos sobre el alcance de los identificadores. 13 Fig. 6.12  Uso de un motor de generación de números aleatorios y una distribución de C++11 para tirar un dado con seis lados (parte 2 de 2).
  • 258. 226 Capítulo 6 Funciones y una introducción a la recursividad Enlace El enlace de un identificador determina si se conoce sólo en el archivo fuente en el que se declara, o en varios archivos fuente que se compilen y después se enlacen. El especificador de clase de almacenamiento de un identificador ayuda a determinar la duración de su almacenamiento y su enlace. Duración del almacenamiento Los especificadores de clases de almacenamiento se pueden dividir en cuatro duraciones de almacena- miento: automática, estática, dinámica y de hilo. Las duraciones de almacenamiento automática y está- tica se describen a continuación. En el capítulo 10 aprenderá que puede solicitar memoria adicional en su programa durante la ejecución del mismo; a esto se le conoce como asignación dinámica de memoria. Las variables que se asignan en forma dinámica tienen duración de almacenamiento dinámica. En el ca- pítulo 24 (en inglés, en el sitio web) hablaremos sobre la duración de almacenamiento de hilo. Variables locales y duración de almacenamiento automática Las variables con duración de almacenamiento automática incluyen: • variables locales declaradas en funciones • parámetros de funciones • variables locales o parámetros de funciones declarados con register Dichas variables se crean cuando la ejecución del programa entra al bloque en el que se definen, existen mientras el bloque esté activo y se destruyen cuando el programa se sale del bloque. Una variable auto- mática existe sólo en el par circundante más cercano de llaves dentro del cuerpo de la función en la cual aparece la definición, o durante todo el cuerpo de la función en el caso de un parámetro de función. Las variables locales son de duración de almacenamiento automática de manera predeterminada. En el resto del libro, nos referiremos a las variables de duración de almacenamiento automática simplemente como variables automáticas. Tip de rendimiento 6.1 El almacenamiento automático es un medio de conservar la memoria, ya que las variables de duración de almacenamiento automática existen en memoria sólo cuando se ejecuta el bloque en el cual están definidas. Observación de Ingeniería de Software 6.6 El almacenamiento automático es un ejemplo del principio del menor privilegio. En el contexto de una aplicación, el principio establece que el código debe recibir sólo el nivel de privilegio y acceso que requiere para realizar su tarea designada, pero no más. ¿Por qué deberíamos tener variables almacenadas en memoria y accesibles cuando no se necesitan? Buena práctica de programación 6.4 Declare las variables lo más cerca posible de donde se utilicen por primera vez. Variables de registro Los datos en la versión de lenguaje máquina de un programa se cargan generalmente en los registros, para cálculos y otros tipos de procesamiento. El compilador podría ignorar las declaraciones register. Por ejemplo, tal vez no haya un número suficiente de registros disponibles. La siguiente definición sugiere que la variableunsigned int contador se coloque en uno de los registros de la computadora; sin importar que el compilador haga esto o no, contador se inicializa en 1:
  • 259. 6.10 Clases y duración de almacenamiento 227 register unsigned int contador = 1; La palabra clave register se puede utilizar sólo con variables locales y parámetros de funciones. Tip de rendimiento 6.2 El especificador de clase de almacenamiento register se puede colocar antes de la decla- ración de una variable automática, para sugerir que el compilador debe mantener la va- riable en uno de los registros de hardware de alta velocidad de la computadora, en vez de hacerlo en memoria. Si las variables de uso intensivo, como los contadores o totales, se mantienen en los registros de hardware, se elimina la sobrecarga de cargar de manera re- petitiva las variables de memoria hacia los registros, y almacenar los resultados de vuelta a la memoria. Tip de rendimiento 6.3 Por lo general, es innecesario el uso de register. Los compiladores optimizadores de la actualidad pueden reconocer las variables de uso frecuente y colocarlas en registros, sin necesitad de una declaración register. Duración de almacenamiento estática Las palabras clave extern y static declaran identificadores para variables con duración de almacenamien- to estática y para funciones. Las variables con duración de almacenamiento estática existen en memoria a partirdelpuntoen elque elprograma empiezaaejecutarse,y dejan deexistircuandoterminaelprograma. Dicha variable se inicializa una vez al encontrar su declaración. Para las funciones, el nombre de la función existe cuando el programa empieza a ejecutarse. Aun cuando los nombres de las funciones y las variables de duración estática existen desde el inicio de la ejecución del programa, esto no implica que estos identi- ficadores se puedan utilizar a lo largo de todo el programa. La duración del almacenamiento y el alcance (en dónde se puede usar un nombre) son cuestiones separadas, como veremos en la sección 6.11. Identificadores con duración de almacenamiento estática Hay dos tipos de identificadores con duración de almacenamiento estática; los identificadores externos (como las variables globales) y las variables locales declaradas con el especificador de clase de almacena- miento static. Para crear variables globales, se colocan declaraciones de variables fuera de cualquier definición de clase o función. Las variables globales retienen sus valores a lo largo de la ejecución del programa. Las variables y las funciones globales se pueden referenciar mediante cualquier función que siga sus declaraciones o definiciones en el archivo fuente. Observación de Ingeniería de Software 6.7 Al declarar una variable como global en vez de local, se permite la ocurrencia de efectos secundarios inesperados cuando una función que no requiere acceso a la variable la modifica en forma accidental o premeditada. Esto es otro ejemplo del principio del menor privilegio. En general, con la excepción de los recursos verdaderamente globales como cin y cout, debe evitarse el uso de variables globales, excepto en ciertas situaciones con reque- rimientos de rendimiento únicos. Observación de Ingeniería de Software 6.8 Las variables que se utilizan sólo en una función específica deben declararse como locales en esa función, en vez de declararlas como variables globales. Variables locales static Las variables locales que se declaran como static siguen siendo conocidas sólo en la función en la que se declaran, pero a diferencia de las variables automáticas, las variables locales static retienen sus valores cuando la función regresa a la función que la llamó. La próxima vez que se hace una llamada a la función,
  • 260. 228 Capítulo 6 Funciones y una introducción a la recursividad las variables locales static contienen los valores que tenían cuando la función se ejecutó por última vez. La siguiente instrucción declara la variable local cuenta como static, y la inicializa en 1: static unsigned int cuenta = 1; Todas las variables numéricas con duración de almacenamiento estática se inicializan con cero de mane- ra predeterminada, pero sin duda es una buena práctica inicializar todas las variables en forma explícita. Los especificadores de clase de almacenamiento extern y static tienen un significado especial cuando se aplican de manera explícita a los identificadores externos, como las variables globales y los nombres de funciones globales. En el apéndice F, Temas sobre código heredado de C, hablaremos sobre el uso de extern y static con identificadores externos y programas con varios archivos de código fuente. 6.11Reglas de alcance La porción del programa en la que se puede utilizar un identificador se conoce como su alcance. Por ejemplo, cuando declaramos una variable local en un bloque, sólo se puede referenciar en ese bloque y en los bloques anidados dentro de ese mismo bloque. En esta sección hablaremos sobre alcance de bloque, alcance de función, alcance de espacio de nombres global y alcance de prototipo de función. Más adelante veremos otros dos tipos de alcance: alcance de clase (capítulo 9) y alcance de espacio de nom- bres (capítulo 23, en inglés, en el sitio web). Alcance de bloque Los identificadores que se declaran dentro de un bloque tienen alcance de bloque, el cual empieza en la de- claración del identificador y termina en la llave derecha de finalización (}) del bloque en el que se declara el identificador. Las variables locales tienen alcance de bloque, al igual que los parámetros de las funciones. Cualquier bloque puede contener declaraciones de variables. Cuando los bloques están anidados y un identificador en un bloque exterior tiene el mismo nombre que un identificador en un bloque interior, el identificador del bloque exterior se “oculta” hasta que termine el bloque interior. El bloque interior “ve” el valor de su propio identificador local y no el valor del identificador del bloque circundante que tiene el nombre idéntico. Las variables locales declaradas como static siguen teniendo alcance de bloque, aun cuando existan desde el momento en que el programa empieza su ejecución. La duración del almacena- miento no afecta al alcance de un identificador. Error común de programación 6.7 El uso accidental del mismo nombre para un identificador en un bloque interior, que se utiliza para un identificador en un bloque exterior, cuando de hecho deseamos que el identificador en el bloque exterior esté activo durante la ejecución del bloque interior, es comúnmente un error lógico. Tip para prevenir errores 6.6 Evite los nombres de variables que ocultan nombres en alcances exteriores. Alcance de función Las etiquetas (identificadores seguidos por dos puntos, como inicio:, o una etiqueta case en una instrucción switch) son los únicos identificadores con alcance de función. Las etiquetas se pueden utili- zar en cualquier parte en la función en la que aparecen, pero no se pueden referenciar fuera del cuerpo de la función.
  • 261. 6.11 Reglas de alcance 229 Alcance de espacio de nombres global Un identificador que se declara fuera de cualquier función o clase tiene alcance de espacio de nombres global. Dicho identificador se “conoce” en todas las funciones a partir del punto en el que se declara, hasta llegar al final del archivo. Las variables globales, las definiciones de funciones y los prototipos de funciones que se colocan fuera de una función tienen alcance de archivo. Alcance de prototipo de función Los únicos identificadores con alcance de prototipo de función son los que se utilizan en la lista de pará- metros de un prototipo de función. Como se mencionó antes, los prototipos de función no requieren nombres en la lista de parámetros; sólo los tipos. El compilador ignora los nombres que aparecen en la lista de parámetros de un prototipo de función. Los identificadores que se utilizan en un prototipo de función se pueden reutilizar en cualquier otra parte del programa sin ambigüedad. Demostración del alcance El programa de la figura 6.13 demuestra cuestiones sobre el alcance con las variables globales, variables locales automáticas y variables locales static. En la línea 10 se declara e inicializa la variable global x en 1. Esta variable local está oculta en cualquier bloque (o función) que declare una variable llamada x. En main, la línea 14 muestra el valor de la variable global x. En la línea 16 se declara una variable local x y se inicializa en 5. En la línea 18 se imprime esta variable para mostrar que la x global está oculta en main. A continuación, en las líneas 20 a 24 se define un nuevo bloque en main, en el cual otra variable local x se inicializa con 7 (línea 21). En la línea 23 se imprime esta variable, para mostrar que oculta a x en el bloque exterior de main, así como la x global. Cuando el bloque termina, la variable x que tiene el valor 7 se destruye automáticamente. A continuación, en la línea 26 se imprime la variable local x en el bloque exterior de main, para mostrar que ya no está oculta. 1 // Fig. 6.13: fig06_13.cpp 2 // Ejemplo sobre el alcance. 3 #include iostream 4 using namespace std; 5 6 void usarLocal(); // prototipo de función 7 void usarLocalStatic(); // prototipo de función 8 void usarGlobal(); // prototipo de función 9 10 int x = 1; // variable global 11 12 int main() 13 { 14 cout la x global en main es x endl; 15 16 int x = 5; // variable local para main 17 18 cout la x local en el alcance exterior de main es x endl; 19 20 { // empieza nuevo alcance 21 int x = 7; // oculta la x en el alcance exterior y la x global 22 23 cout la x local en el alcance interior de main es x endl; 24 } // termina nuevo alcance Fig. 6.13  Ejemplo sobre el alcance (parte 1 de 3).
  • 262. 230 Capítulo 6 Funciones y una introducción a la recursividad 25 26 cout la x local en el alcance exterior de main es x endl; 27 28 usarLocal(); // usarLocal tiene la x local 29 usarLocalStatic(); // usarLocalStatic tiene la x local estática 30 usarGlobal(); // usarGlobal usa la x global 31 usarLocal(); // usarLocal reinicializa su x local 32 usarLocalStatic(); // la x local estática retiene su valor anterior 33 usarGlobal(); // la x global también retiene su valor anterior 34 35 cout nla x local en main es x endl; 36 } // fin de main 37 38 // usarLocal reinicializa la variable x local durante cada llamada 39 void usarLocal() 40 { 41 int x = 25; // se inicializa cada vez que se llama a usarLocal 42 43 cout nla x local es x al entrar a usarLocal endl; 44 ++x; 45 cout la x local es x al salir de usarLocal endl; 46 } // fin de la función usarLocal 47 48 // usarLocalStatic inicializa la variable x local estática sólo la 49 // primera vez que se llama a la función; el valor de x se guarda 50 // entre las llamadas a esta función 51 void usarLocalStatic() 52 { 53 static int x = 50; // se inicializa la primera vez que se llama a usarLocalStatic 54 55 cout nla x local estatica es x al entrar a usarLocalStatic 56 endl; 57 ++x; 58 cout la x local estatica es x al salir de usarLocalStatic 59 endl; 60 } // fin de la función usarLocalStatic 61 62 // usarGlobal modifica la variable global x durante cada llamada 63 void usarGlobal() 64 { 65 cout nla x global es x al entrar a usarGlobal endl; 66 x *= 10; 67 cout la x global es x al salir de usarGlobal endl; 68 } // fin de la función usarGlobal la x global en main es 1 la x local en el alcance exterior de main es 5 la x local en el alcance interior de main es 7 la x local en el alcance exterior de main es 5 la x local es 25 al entrar a usarLocal la x local es 26 al salir de usarLocal 53 Fig. 6.13  Ejemplo sobre el alcance (parte 2 de 3).
  • 263. 6.12 La pila de llamadas a funciones y los registros de activación 231 la x local estatica es 50 al entrar a usarLocalStatic la x local estatica es 51 al salir de usarLocalStatic la x global es 1 al entrar a usarGlobal la x global es 10 al salir de usarGlobal la x local es 25 al entrar a usarLocal la x local es 26 al salir de usarLocal la x local estatica es 51 al entrar a usarLocalStatic la x local estatica es 52 al salir de usarLocalStatic la x global es 10 al entrar a usarGlobal la x global es 100 al salir de usarGlobal la x local en main es 5 Para demostrar otros alcances, el programa define tres funciones, cada una de las cuales no recibe argumento y no devuelve nada. La función usarLocal (líneas 39 a 46) declara la variable automática x (línea 41) y la inicializa en 25. Cuando el programa llama a usarLocal, la función imprime la variable, laincrementaylavuelveaimprimirantesdequelafuncióndevuelvaelcontroldelprogramaalafunción que la llamó. Cada vez que el programa llama a esta función, ésta vuelve a crear la variable automática x y la vuelve a inicializar en 25. La función usarLocalStatic (líneas 51 a 60) declara la variable static x y la inicializa con 50. Las variables locales que se declaran como static retienen sus valores aun cuando estén fuera de alcan- ce (es decir, la función en la que se declaran no se está ejecutando). Cuando el programa llama a usarLocalStatic, la función imprime x, la incrementa y la vuelve a imprimir antes de que la función devuelva el control del programa a la función que la llamó. En la siguiente llamada a esta función, la variable local static x contiene el valor 51. La inicialización en la línea 53 ocurre sólo una vez: la prime- ra vez que se llama a usarLocalStatic. La función usarGlobal (líneas 63 a 68) no declara ninguna variable. Por lo tanto, cuando hace referencia a la variable x, se utiliza la x global (línea 10, antes de main). Cuando el programa llama a usarGlobal, la función imprime la variable global x, la multiplica por 10 y la imprime de nuevo, antes de que la función devuelva el control del programa a la función que la llamó. La siguiente vez que el programa llama a usarGlobal, se modifica el valor de la variable global, 10. Después de ejecutar las funciones usarLocal, usarLocalStatic y usarGlobal dos veces cada una, el programa imprime la variable local x en main, de nuevo para mostrar que ninguna de las llamadas a la función modificó el valor de x en main, debido a que todas las funciones hicieron referencia a las variables en otros alcances. 6.12La pila de llamadas a funciones y los registros de activación Para comprender la forma en que C++ realiza las llamadas a las funciones, primero necesitamos consi- derar una estructura de datos (es decir, colección de elementos de datos relacionados) conocida como pila. Piense en una pila como la analogía a una pila de platos. Cuando se coloca un plato en la pila, por lo general se coloca en la parte superior: lo que se conoce como meter el plato en la pila. De manera similar, cuando se extrae un plato de la pila, siempre se extrae de la parte superior: lo que se conoce como sacar el plato de la pila. Las pilas se denominan estructuras de datos “último en entrar, pri- mero en salir” (UEPS); el último elemento que se mete (inserta) en la pila es el primero que se saca (extrae) de ella. Fig. 6.13  Ejemplo sobre el alcance (parte 3 de 3).
  • 264. 232 Capítulo 6 Funciones y una introducción a la recursividad La pila de llamadas a funciones Uno de los mecanismos más importantes que los estudiantes de ciencias computacionales deben com- prender es la pila de llamadas a funciones (conocida algunas veces como la pila de ejecución del programa). Esta estructura de datos (que trabaja en segundo plano) soporta el mecanismo de llamada a/regreso de las funciones. También soporta la creación, mantenimiento y destrucción de las variables automáticas de cada función a la que se llama. Como veremos en las figuras 6.15 a 6.17, este compor- tamiento “último en entrar, primero en salir” (UEPS) es exactamente lo que hace una función cuando regresa a la función que la llamó. Marcos de pila A medida que se hace la llamada a cada función, ésta puede a su vez, llamar a otras funciones, las cuales, a su vez, pueden llamar a otras funciones; todo ello antes de que regrese alguna de las funciones. En cierto momento, cada función debe regresar el control a la función que la llamó. Por ende, de alguna manera debemos llevar el registro de las direcciones de retorno que requiere cada función para regresar el control a la función que la llamó. La pila de llamadas a funciones es la estructura de datos perfecta para manejar esta información. Cada vez que una función llama a otra función, se mete una entrada en la pila. Esta entrada, conocida como marco de pila o registro de activación, contiene la dirección de retorno que necesita la función a la que se llamó para poder regresar a la función que hizo la llamada. También contiene cierta información adicional que veremos en breve. Si la función a la que se llamó regresa, en vez de llamar a otra función antes de regresar, se saca el marco de pila para la llamada a la función, y el control se transfiere a la dirección de retorno en el marco de la pila que se sacó. La belleza de la pila de llamadas es que cada función a la que se ha llamado siempre encuentra la información que requiere para regresar a la función que la llamó en la parte superior de la pila de llama- das. Y, si una función hace una llamada a otra función, simplemente se mete a la pila de llamadas un marco de pila para esa nueva llamada a una función. Por ende, la dirección de retorno requerida por la función recién llamada para regresar a la función que la llamó se encuentra ahora en la parte superior de la pila. Variables automáticas y marcos de pila Los marcos de pila tienen otra responsabilidad importante. La mayoría de las funciones tienen variables automáticas: parámetros y cualquier variable local que declare la función. Las variables automáticas necesitan existir mientras una función se está ejecutando. Necesitan permanecer activas si la función hace llamadas a otras funciones. Pero cuando una función a la que se llamó regresa a la función que la llamó, las variables automáticas de la función a la que se llamó necesitan “desaparecer”. El marco de pila de la función a la que se llamó es un lugar perfecto para reservar la memoria para las variables auto- máticas de la función a la que se llamó. Ese marco de pila existe mientras la función a la que se llamó esté activa. Cuando esa función regresa (y ya no necesita sus variables automáticas locales) su marco de pila se saca de la pila, y esas variables automáticas ya no son conocidas para el programa. Desbordamiento de pila Desde luego que la cantidad de memoria en una computadora es finita, por lo cual sólo se puede usar cierta cantidad de memoria para almacenar registros de activación en la pila de llamadas a funciones. Si ocurren más llamadas a funciones de las que se puedan guardar sus registros de activación en la pila de llamadas a funciones, se produce un error conocido como desbordamiento de pila. La pila de llamadas a funciones en acción Ahora vamos a considerar cómo la pila de llamadas ofrece soporte para la operación de una función cua- drado llamada por main (líneas 9 a 14 de la figura 6.14). Primero, el sistema operativo llama a main; esto hace que se meta un registro de activación en la pila (lo cual se muestra en la figura 6.15). El registro de activación indica a main cómo debe regresar al sistema operativo (es decir, transferir el control a la direc- ción de retorno R1) y contiene el espacio para la variable automática de main (a, que se inicializa en 10).
  • 265. 6.12 La pila de llamadas a funciones y los registros de activación 233 1 // Fig. 6.14: fig06_14.cpp 2 // Función cuadrado utilizada para demostrar la pila 3 // de llamadas a funciones y los registros de activación. 4 #include iostream 5 using namespace std; 6 7 int cuadrado( int ); // prototipo para la función cuadrado 8 9 int main() 10 { 11 int a = 10; // valor para cuadrado (variable local automática en main) 12 13 cout a al cuadrado: cuadrado( a ) endl; // muestra a al cuadrado 14 } // fin de main 15 16 // devuelve el cuadrado de un entero 17 int cuadrado( int x ) // x es una variable local 18 { 19 return x * x; // calcula el cuadrado y devuelve el resultado 20 } // fin de la función cuadrado 10 cuadrado: 100 Fig. 6.14  Función cuadrado utilizada para demostrar la pila de llamadas a funciones y los registros de activación. Llamada a función después del paso 1 Registro de activación para la función main Parte superior de la pila Dirección de retorno: R1 Variables automáticas: a 10 Líneas que representan al sistema operativo ejecutando instrucciones Clave Paso 1: El sistema operativo invoca a main para ejecutar la aplicación. Sistema operativo { int a = 10; cout a al cuadrado: cuadrado( a ) endl; } Dirección de retorno R1 int main() Fig. 6.15  La pila de llamadas a funciones, después de que el sistema operativo invoca a main para ejecutar el programa.
  • 266. 234 Capítulo 6 Funciones y una introducción a la recursividad La función main (antes de regresar al sistema operativo) llama ahora a la función cuadrado en la línea 13 de la figura 6.14. Esto hace que se meta un marco de pila para cuadrado (líneas 17 a 20) en la pila de llamadas a funciones (figura 6.16). Este marco de pila contiene la dirección de retorno que cuadrado necesita para regresar a main (es decir, R2) y la memoria para la variable automática de cua- drado (es decir, x). Dirección de retorno R2 Registro de activación para la función cuadrado Registro de activación para la función main Paso 2: main invoca a la función cuadrado para realizar el cálculo Dirección de retorno: R1 Variables automáticas: a 10 Dirección de retorno: R2 Variables automáticas: x 10 Parte superior de la pila { int a = 10; cout a al cuadrado: cuadrado( a ) endl; } int main() { return x * x; } int cuadrado( int x ) Llamada a función después del paso 2 Fig. 6.16  La pila de llamadas a funciones, después de que la función cuadrado regresa a main. Una vez que cuadrado calcula el cuadrado de su argumento, necesita regresar a main; y ya no nece- sita la memoria para su variable automática, x. Por ende, el marco de pila de cuadrado se saca de la pila, con lo cual se proporciona a cuadrado la dirección de retorno en main (es decir, R2) y se pierde la varia- ble automática de cuadrado. La figura 6.17 muestra la pila de llamadas a funciones después de sacar el registro de activación de cuadrado. Ahora la función main muestra el resultado de llamar a cuadrado (figura 6.14, línea 13). Al llegar a la llave derecha de cierre de main, su marco de pila se saca de la pila, proporciona a main la dirección que requiere para regresar al sistema operativo (es decir, R1 en la figura 6.15) y, en este punto, la variable automática de main (es decir, a) ya no existe. Ahora hemos visto qué tan valiosa es la estructura de datos tipo pila para implementar un mecanis- mo clave que ofrezca soporte para la ejecución de un programa. Las estructuras de datos tienen muchas aplicaciones importantes en las ciencias computacionales. En el capítulo 15, Contenedores e iteradores de la Biblioteca estándar y en el capítulo 19, CustomTemplatized Data Structures (en inglés, en el sitio web), hablaremos sobre las pilas, colas, listas, árboles y otras estructuras de datos.
  • 267. 6.13 Funciones con listas de parámetros vacías 235 Pila de llamadas a funciones después del paso 3 Dirección de retorno R2 Registro de activación para la función main Paso 3: cuadrado devuelve su resultado a main Dirección de retorno: R1 Variables automáticas: a 10 Parte superior de la pila { int a = 10; cout a al cuadrado: cuadrado( a ) endl; } int main() { return x * x; } int cuadrado( int x ) Fig. 6.17  La pila de llamadas a funciones, después de que la función cuadrado regresa a main. 6.13 Funciones con listas de parámetros vacías En C++, una lista de parámetros vacía se especifica escribiendo void o nada entre paréntesis. El pro- totipo void imprimir(); especifica que la función imprimir no recibe argumentos y no devuelve un valor. La figura 6.18 demues- tra ambas formas de declarar y usar funciones con listas de parámetros vacías. 1 // Fig. 6.18: fig06_18.cpp 2 // Funciones que no reciben argumentos. 3 #include iostream 4 using namespace std; 5 6 void funcion1(); // función que no recibe argumentos 7 void funcion2( void ); // función que no recibe argumentos 8 9 int main() 10 { 11 funcion1(); // llama a funcion1 sin argumentos 12 funcion2(); // llama a funcion2 sin argumentos 13 } // fin de main 14 Fig. 6.18  Funciones que no reciben argumentos (parte 1 de 2).
  • 268. 236 Capítulo 6 Funciones y una introducción a la recursividad 15 // funcion1 usa una lista de parámetros vacía para especificar que 16 // la función no recibe argumentos 17 void funcion1() 18 { 19 cout funcion1 no recibe argumentos endl; 20 } // fin de funcion1 21 22 // funcion2 usa una lista de parámetros vacía para especificar que 23 // la función no recibe argumentos 24 void funcion2( void ) 25 { 26 cout funcion2 tampoco recibe argumentos endl; 27 } // fin de funcion2 funcion1 no recibe argumentos funcion2 tampoco recibe argumentos 6.14 Funciones en línea Es bueno implementar un programa como un conjunto de funciones desde el punto de vista de la inge- niería de software, pero las llamadas a funciones implican una sobrecarga en tiempo de ejecución. C++ cuenta con las funciones en línea para ayudar a reducir la sobrecarga de las llamadas a funciones. Al colocar el calificador inline antes del tipo de valor de retorno de la función en su definición, se aconse- ja al compilador para que genere una copia del código del cuerpo de la función en cada lugar en donde se llame a la función (cuando sea apropiado), para evitar la llamada a una función. Con frecuencia, esto hace que el tamaño del programa aumente. El compilador puede ignorar el calificador inline, y por lo general lo hace para todas las funciones, excepto las más pequeñas. Por lo general las funciones inline reutilizables se colocan en encabezados, de modo que puedan incluirse sus definiciones en cada archivo de código fuente que las utilice. Observación de Ingeniería de Software 6.9 Si cambia la definición de una función inline, deberá volver a compilar todos los clien- tes de esa función. Tip de rendimiento 6.4 Los compiladores pueden poner código en línea para el que no hayamos utilizado de ma- nera explícita la palabra clave inline. Los compiladores optimizadores actuales son tan sofisticados que es mejor dejarles las decisiones sobre poner o no código en línea. La figura 6.19 utiliza la función inline llamada cubo (líneas 9 a 12) para calcular el volumen de un cubo. La palabra clave const en la lista de parámetros de la función cubo (línea 9) indica al compila- dor que la función no modifica la variable lado. Esto asegura que la función no modifique el valor de lado cuando se realice el cálculo (la palabra clave const se describe con detalle en los capítulos 7 a 9). Observación de Ingeniería de Software 6.10 El calificador const se debe utilizar para hacer valer el principio del menor privilegio. El uso de este principio para diseñar software de manera apropiada puede reducir considera- blemente el tiempo de depuración y los efectos secundarios inadecuados; además puede facilitar la modificación y el mantenimiento de un programa. Fig. 6.18  Funciones que no reciben argumentos (parte 2 de 2).
  • 269. 6.15 Referencias y parámetros de referencias 237 1 // Fig. 6.19: fig06_19.cpp 2 // Función inline que calcula el volumen de un cubo. 3 #include iostream 4 using namespace std; 5 6 // Definición de la función en línea cubo. La definición de la función aparece 7 // antes de llamar a la función, por lo que no se requiere un prototipo de función. 8 // La primera línea de la función actúa como el prototipo. 9 inline double cubo( const double lado ) 10 { 11 return lado * lado * lado; // calcula el cubo 12 } // fin de la función cubo 13 14 int main() 15 { 16 double valorLado; // almacena el valor introducido por el usuario 17 cout Escriba la longitud del lado de su cubo: ; 18 cin valorLado; // lee el valor del usuario 19 20 // calcula el cubo de valorLado y muestra el resultado 21 cout El volumen del cubo con un lado de 22 valorLado es cubo( valorLado ) endl; 23 } // end main Escriba la longitud del lado de su cubo: 3.5 El volumen del cubo con un lado de 3.5 es 42.875 Fig. 6.19  Función inline que calcula el volumen de un cubo. 6.15Referencias y parámetros de referencias Dos formas de pasar argumentos a las funciones en muchos lenguajes de programación son el paso por valor y el paso por referencia. Cuando se pasa un argumento por valor, se crea una copia del valor del argumento y se pasa (en la pila de llamadas a funciones) a la función que se llamó. Las modificaciones a la copia no afectan al valor de la variable original en la función que hizo la llamada. Esto evita los efectos secundarios accidentales que tanto obstaculizan el desarrollo de sistemas de software correctos y confia- bles. Hasta ahora, cada argumento en el libro se ha pasado por valor. Tip de rendimiento 6.5 Una desventaja del paso por valor es que, si se va a pasar un elemento de datos extenso, el proceso de copiar esos datos puede requerir una cantidad considerable de tiempo de ejecu- ción y espacio en memoria. Parámetros por referencia En esta sección presentamos los parámetros por referencia: el primero de los dos medios que proporcio- na C++ para realizar el paso por referencia. Mediante el paso por referencia, la función que hace la llama- da proporciona a la función que llamó la habilidad de acceder directamente a los datos de la primera, y de modificarlos. Tip de rendimiento 6.6 El paso por referencia es bueno por cuestiones de rendimiento, ya que puede eliminar la sobrecarga de copiar grandes cantidades de datos en el paso por valor.
  • 270. 238 Capítulo 6 Funciones y una introducción a la recursividad Observación de Ingeniería de Software 6.11 El paso por referencia puede debilitar la seguridad, ya que la función a la que se llamó puede corromper los datos de la función que hizo la llamada. Más adelante veremos cómo lograr la ventaja de rendimiento que ofrece el paso por referencia, al tiempo que se obtiene la ventaja de ingeniería de software de evitar que la corrupción de los datos de la función que hizo la llamada. Un parámetro por referencia es un alias para su correspondiente argumento en la llamada a una función. Para indicar que un parámetro de función se pasa por referencia, simplemente hay que colocar un signo después del tipo del parámetro en el prototipo de la función; use la misma convención al listar el tipo del parámetro en el encabezado de la función. Por ejemplo, la siguiente declaración en el encabezado de una función: int cuenta si se lee de izquierda a derecha, significa que “cuenta es una referencia a un valor int”. En la llamada a la función, simplemente hay que mencionar la variable por su nombre para pasarla por referencia. Des- pués, al mencionar la variable por el nombre de su parámetro en el cuerpo de la función a la que se llamó, en realidad se refiere a la variable original en la función que hizo la llamada, y esta variable original se puede modificar directamente en la función a la que se llamó. Como siempre, el prototipo de función y el encabezado deben concordar. Paso de argumentos por valor y por referencia En la figura 6.20 se compara el paso por valor y el paso por referencia con los parámetros por referencia. Los “estilos” de los argumentos en las llamadas a la función cuadradoPorValor y la función cuadrado- PorReferencia son idénticos; ambas variables sólo se mencionan por nombre en las llamadas a las funciones. Sin comprobar los prototipos o las definiciones de las funciones, no es posible deducir sólo de las llamadas si cada función puede modificar sus argumentos. Como los prototipos de función son obligato- rios, el compilador no tiene problemas para resolver la ambigüedad. Error común de programación 6.8 Como los parámetros por referencia se mencionan sólo por su nombre en el cuerpo de la fun- ción a la que se llama, podríamos tratar de manera inadvertida los parámetros por referen- cia como parámetros de paso por valor. Esto puede provocar efectos secundarios inesperados si la función modifica las copias originales de las variables. 1 // Fig. 6.20: fig06_20.cpp 2 // Paso de argumentos por valor y por referencia. 3 #include iostream 4 using namespace std; 5 6 int cuadradoPorValor( int ); // prototipo de función (paso por valor) 7 void cuadradoPorReferencia( int ); // prototipo de función (paso por referencia) 8 9 int main() 10 { 11 int x = 2; // valor para cuadrado usando cuadradoPorValor 12 int z = 4; // valor para cuadrado usando cuadradoPorReferencia 13 Fig. 6.20  Paso de argumentos por valor y por referencia (parte 1 de 2).
  • 271. 6.15 Referencias y parámetros de referencias 239 14 // demuestra cuadradoPorValor 15 cout x = x antes de cuadradoPorValorn; 16 cout Valor devuelto por cuadradoPorValor: 17 cuadradoPorValor( x ) endl; 18 cout x = x despues de cuadradoPorValorn endl; 19 20 // demuestra cuadradoPorReferencia 21 cout z = z antes de cuadradoPorReferencia endl; 22 cuadradoPorReferencia( z ); 23 cout z = z despues de cuadradoPorReferencia endl; 24 } // fin de main 25 26 // cuadradoPorValor multiplica el número por sí mismo, almacena el 27 // resultado en el número y devuelve el nuevo valor del número 28 int cuadradoPorValor( int numero ) 29 { 30 return numero *= numero; // no se modificó el argumento de la función que hizo la llamada 31 } // fin de la función cuadradoPorValor 32 33 // cuadradoPorReferencia multiplica a refNumero por sí solo y almacena el resultado 34 // en la variable a la que refNumero hace referencia en la función main 35 void cuadradoPorReferencia( int refNumero ) 36 { 37 refNumero *= refNumero; // se modificó el argumento de la función que hizo la llamada 38 } // fin de la función cuadradoPorReferencia x = 2 antes de cuadradoPorValor Valor devuelto por cuadradoPorValor: 4 x = 2 despues de cuadradoPorValor z = 4 antes de cuadradoPorReferencia z = 16 despues de cuadradoPorReferencia El capítulo 8 habla sobre los apuntadores; éstos proporcionan una forma alternativa del paso por referencia, en la cual el estilo de la llamada indica claramente el paso por referencia (y el potencial de modificar los argumentos de la función que hace la llamada). Tip de rendimiento 6.7 Para pasar objetos extensos, use un parámetro por referencia constante para simular la apariencia y seguridad del paso por valor, evitando así la sobrecarga de pasar una copia del objeto extenso. Para especificar que una referencia no debe modificar el argumento, coloque el calificador const antes del especificador de tipo en la declaración del parámetro. Observe la colocación del signo en la lista de parámetros de la función cuadradoPorReferencia (línea 35, figura 6.20). Algunos programa- dores de C++ prefieren escribir la forma equivalente int refNumero. Referencias como alias dentro de una función Las referencias también se pueden usar como alias para otras variables dentro de una función (aunque por lo general se utilizan con las funciones, como se muestra en la figura 6.20). Por ejemplo, el código Fig. 6.20  Paso de argumentos por valor y por referencia (parte 2 de 2). 37 33 30
  • 272. 240 Capítulo 6 Funciones y una introducción a la recursividad int cuenta = 1; // declara la variable entera cuenta int cRef = cuenta; // crea cRef como alias para cuenta ++cRef; // incrementa cuenta (usando su alias cRef) incrementa la variable cuenta mediante el uso de su alias cRef. Las variables de referencia deben inicia- lizarse en sus declaraciones y no se pueden reasignar como alias para otras variables. Una vez que se de- clara una referencia como alias para otra variable, todas las operaciones que supuestamente se realizan en el alias (es decir, la referencia) en realidad se realizan en la variable original. El alias es simplemente otro nombre para la variable original. A menos que sea una referencia a una constante, un argumento por referencia debe ser un lvalue (por ejemplo, el nombre de una variable), no una constante o expresión que devuelva un rvalue (por ejemplo, el resultado de un cálculo). Devolver una referencia de una función Las funciones pueden devolver referencias, pero esto puede ser peligroso. Al devolver una referencia a una variable declarada en la función que se llamó, a menos que esa variable se declare como static, la referencia se refiere a una variable automática que se descarta cuando termina la función. El intento de acceder a dicha variable produce un comportamiento indefinido. Las referencias a variables indefinidas se llaman referencias sueltas. Error común de programación 6.9 Devolver una referencia a una variable automática en una función a la que se ha llama- do es un error lógico. Por lo general, los compiladores generan una advertencia cuando esto ocurre. Para el código de nivel industrial, siempre hay que eliminar todas las adverten- cias de compilación para poder producir código ejecutable. 6.16Argumentos predeterminados Para un programa, es algo común el invocar una función repetidas veces con el mismo valor de argumen- to para un parámetro específico. En tales casos, podemos especificar que dicho parámetro tiene un argumento predeterminado;esdecir,quetieneunvalorpredeterminadoquedebepasaraeseparámetro. Cuando un programa omite un argumento para un parámetro con un argumento predeterminado en la llamada a una función, el compilador vuelve a escribir la llamada a la función e inserta el valor predeter- minado de ese argumento. Los argumentos predeterminados deben ser los argumentos de más a la derecha en la lista de pa- rámetros de una función. Al llamar a una función con dos o más argumentos predeterminados, si se omite un argumento que no sea el de más a la derecha en la lista de argumentos, entonces también deben omitirse todos los argumentos que estén a la derecha de ese argumento. Los argumentos predetermina- dos deben especificarse con la primera ocurrencia del nombre de la función; por lo general, en el proto- tipo de la función. Si el prototipo se omite debido a que la definición de la función también actúa como el prototipo, entonces los argumentos predeterminados se deben especificar en el encabezado de la función. Los valores predeterminados pueden ser cualquier expresión, incluyendo constantes, variables globales o llamadas a funciones. Los argumentos predeterminados también se pueden usar con funcio- nes inline. En la figura 6.21 se demuestra el uso de argumentos predeterminados para calcular el volumen de una caja. El prototipo de función para volumenCaja (línea 7) especifica que los tres parámetros reciben valores predeterminados de 1. Proporcionamos nombres de variables en el prototipo de función para mejorar la legibilidad. Como siempre, los nombres de las variables no se requieren en los prototipos de función. La primera llamada a volumenCaja (línea 13) especifica que no hay argumentos, con lo cual se utilizan los tres valores predeterminados de 1. En la segunda llamada (línea 17) sólo se pasa un argumen- to longitud, con lo cual se utilizan los valores predeterminados de 1 para los argumentos anchura y
  • 273. 6.16 Argumentos predeterminados 241 1 // Fig. 6.21: fig06_21.cpp 2 // Uso de argumentos predeterminados. 3 #include iostream 4 using namespace std; 5 6 // prototipo de función que especifica argumentos predeterminados 7 unsigned int volumenCaja( unsigned int longitud = 1, unsigned int anchura = 1, 8 unsigned int altura = 1 ); 9 10 int main() 11 { 12 // sin argumentos--usa valores predeterminados para todas las medidas 13 cout El volumen predeterminado de la caja es: volumenCaja(); 14 15 // especifica la longitud; anchura y altura predeterminadas 16 cout nnEl volumen de una caja con longitud 10,n 17 anchura 1 y altura 1 es: volumenCaja( 10 ); 18 19 // especifica longitud y anchura; altura predeterminada 20 cout nnEl volumen de una caja con longitud 10,n 21 anchura 5 y altura 1 es: volumenCaja( 10, 5 ); 22 23 // especifica todos los argumentos 24 cout nnEl volumen de una caja con longitud 10,n 25 anchura 5 y altura 2 es: volumenCaja( 10, 5, 2 ) 26 endl; 27 } // fin de main 28 29 // la función volumenCaja calcula el volumen de una caja 30 unsigned int volumenCaja( unsigned int longitud, unsigned int anchura, 31 unsigned int altura ) 32 { 33 return longitud * anchura * altura; 34 } // fin de la función volumenCaja El volumen predeterminado de la caja es: 1 El volumen de una caja con longitud 10, anchura 1 y altura 1 es: 10 El volumen de una caja con longitud 10, anchura 5 y altura 1 es: 50 El volumen de una caja con longitud 10, anchura 5 y altura 2 es: 100 Fig. 6.21  Uso de argumentos predeterminados. altura. La tercera llamada (línea 21) pasa argumentos sólo para longitud y anchura, con lo cual se utiliza un valor predeterminado de 1 para el argumento altura. La última llamada (línea 25) pasa argu- mentos para longitud, anchura y altura, con lo cual no utiliza valores predeterminados. Cualquier argumento que se pasa a la función de manera explícita se asigna a los parámetros de la función, de iz- quierda a derecha. Por lo tanto, cuando volumenCaja recibe un argumento, la función asigna el valor de ese argumento a su parámetro longitud (es decir, el parámetro de más a la izquierda en la lista de pará- metros). Cuando volumenCaja recibe dos argumentos, la función asigna los valores de esos argumentos
  • 274. 242 Capítulo 6 Funciones y una introducción a la recursividad a sus parámetros longitud y anchura en ese orden. Por último, cuando volumenCaja recibe los tres argumentos, la función asigna los valores de esos argumentos a sus parámetros longitud, anchura y altura, respectivamente. Buena práctica de programación 6.5 El uso de argumentos predeterminados puede simplificar la escritura de las llamadas a funciones. Sin embargo, algunos programadores sienten que es más claro especificar de manera explícita todos los argumentos. 6.17Operador de resolución de ámbito unario Es posible declarar variables locales y globales con el mismo nombre. C++ proporciona el operador de resolución de ámbito binario (::) para acceder a una variable global cuando una variable local con el mismo nombre se encuentra dentro del alcance. El operador de resolución de ámbito unario no se puede utilizar para acceder a una variable local con el mismo nombre en un bloque exterior. Se puede acceder a una variable global directamente sin el operador de resolución de ámbito unario, si el nombre de la varia- ble global no es el mismo que el de una variable local dentro del alcance. En la figura 6.22 se demuestra el operador de resolución de ámbito unario con variables local y global con el mismo nombre (líneas 6 y 10). Para enfatizar que las versiones local y global de la variable numero son distintas, el programa declara una variable de tipo int y la otra de tipo double. 1 // Fig. 6.22: fig06_22.cpp 2 // Operador de resolución de ámbito unario. 3 #include iostream 4 using namespace std; 5 6 int numero = 7; // variable global llamada numero 7 8 int main() 9 { 10 double numero = 10.5; // variable local llamada numero 11 12 // muestra los valores de las variables local y global 13 cout Valor local double de numero = numero 14 nValor global int de numero = ::numero endl; 15 } // fin de main Valor local double de numero = 10.5 Valor global int de numero = 7 Fig. 6.22  Operador de resolución de ámbito unario. Buena práctica de programación 6.6 Si utiliza siempre el operador de resolución de ámbito unario (::) para hacer referencia a las variables globales, establece claramente que está tratando de acceder a una variable global, en vez de una variable no global. Observación de Ingeniería de Software 6.12 Al utilizar siempre el operador de resolución de ámbito unario (::) para hacer referencia a las variables globales, se facilita la modificación de los programas, al reducir el riesgo de conflictos de nombres con variables no globales.
  • 275. 6.18 Sobrecarga de funciones 243 Tip para prevenir errores 6.7 Al utilizar siempre el operador de resolución de ámbito unario (::) para hacer referencia a una variable global, se eliminan los posibles errores lógicos que podrían ocurrir si una variable no global oculta a la variable global. Tip para prevenir errores 6.8 Evite usar variables con el mismo nombre para distintos propósitos en un programa. Aunque esto se permite en diversas circunstancias, puede producir errores. 6.18Sobrecarga de funciones C++ permite definir varias funciones con el mismo nombre, siempre y cuando éstas tengan distintas firmas. A esta capacidad se le conoce como sobrecarga de funciones. El compilador de C++ selecciona la función apropiada al examinar el número, tipos y orden de los argumentos en la llamada. La sobre- carga de funciones se utiliza para crear varias funciones con el mismo nombre que realicen tareas simila- res, pero con distintos tipos de datos. Por ejemplo, muchas funciones en la biblioteca de matemáticas están sobrecargadas para distintos tipos de datos numéricos; el estándar de C++ requiere versiones so- brecargadas float, double y long double de las funciones matemáticas de la biblioteca que se describen en la sección 6.3. Buena práctica de programación 6.7 Al sobrecargar las funciones que realizan tareas estrechamente relacionadas, los programas pueden ser más fáciles de leer y comprender. Funciones cuadrado sobrecargadas Lafigura6.23utilizafuncionescuadrado sobrecargadasparacalcularelcuadradodeunvalorint (líneas 7a 11) y el cuadrado de un valor double (líneas 14 a 18). En la línea 22 se invoca a la versión int de la función cuadrado, para lo cual se le pasa el valor literal 7. C++ trata a los valores literales numéricos enteros como de tipo int. De manera similar, en la línea 24 se invoca a la versión double de la función cuadrado, para lo cual se le pasa el valor literal 7.5, que C++ trata como valor double. En cada caso, el compilador elije la función apropiada que va a llamar, con base en el tipo del argumento. Las últimas dos líneas en la ventana de salida confirman que se llamó a la función apropiada en cada caso. 1 // Fig. 6.23: fig06_23.cpp 2 // Funciones cuadrado sobrecargadas. 3 #include iostream 4 using namespace std; 5 6 // función cuadrado para valores int 7 int cuadrado( int x ) 8 { 9 cout el cuadrado del entero x es ; 10 return x * x; 11 } // fin de la función cuadrado con argumento int 12 Fig. 6.23  Funciones cuadrado sobrecargadas (parte 1 de 2).
  • 276. 244 Capítulo 6 Funciones y una introducción a la recursividad 13 // función cuadrado para valores double 14 double cuadrado( double y ) 15 { 16 cout el cuadrado del double y es ; 17 return y * y; 18 } // fin de la función cuadrado con argumento 19 20 int main() 21 { 22 cout cuadrado( 7 ); // llama a la versión int 23 cout endl; 24 cout cuadrado( 7.5 ); // llama a la versión double 25 cout endl; 26 } // fin de main el cuadrado del entero 7 es 49 el cuadrado del double 7.5 es 56.25 Cómo diferencia el compilador las funciones sobrecargadas Las funciones sobrecargadas se diferencian mediante sus firmas. Una firma es una combinación del nombre de una función y los tipos de sus parámetros (en orden). El compilador codifica cada identifi- cador de función con los tipos de sus parámetros (lo que algunas veces se conoce como manipulación de nombres o decoración de nombres) para permitir el enlace seguro de tipos. Este tipo de enlace aseguraquesellamealafunciónsobrecargadaapropiada,yquelostiposdelosargumentosseconformen a los tipos de los parámetros. La figura 6.24 se compiló con GNU C++. En vez de mostrar los resultados de la ejecución del programa (como se haría normalmente), mostramos los nombres manipulados de la función que GNU C++ produce en lenguaje ensamblador. Cada nombre manipulado (distinto de main) empieza con dos guiones bajos (__) seguidos de la letra Z, un número y el nombre de la función. El número después de Z especifica cuántos caracteres hay en el nombre de la función. Por ejemplo, la función cuadrado tiene 8 caracteres en su nombre, por lo que su nombre manipulado recibe el prefijo __Z8. Después, el nombre de la función va seguido de una codificación de su lista de parámetros. En la lista de parámetros para la función nada2 (línea 25; vea la cuarta línea de salida), c representa a un valor char, i representa a un valor int, Rf representa a un valor float (es decir, una referencia a un va- lor float) y Rd representa a un valor double (es decir, una referencia a un valor double). En la lista de parámetros para la función nada1, i representa a un valor int, f representa a un valor float, c representa a un valor char y Ri representa a un valor int . Las dos funciones cuadrado se diferencian en base a sus listas de parámetros; una especifica d para double y la otra especifica i para int. Los tipos de valores de retorno de las funciones no se especifican en los nombres manipulados. Las funciones so- brecargadas pueden tener distintos tipos de valores de retorno, pero si es así, también deben tener distintas listas de parámetros. De nuevo, no se pueden tener dos funciones con la misma firma y distintos tipos de valores de retorno. La manipulación de nombres de funciones es específica para cada compilador. Además, la función main no está manipulada, ya que no puede sobrecargarse. Error común de programación 6.10 Crear funciones sobrecargadas con las listas de parámetros idénticos y distintos tipos de valores de retorno es un error de compilación. Fig. 6.23  Funciones cuadrado sobrecargadas (parte 2 de 2).
  • 277. 6.18 Sobrecarga de funciones 245 1 // Fig. 6.24: fig06_24.cpp 2 // Manipulación de nombres para permitir la vinculación segura de tipos. 3 4 // función cuadrado para valores int 5 int cuadrado( int x ) 6 { 7 return x * x; 8 } // fin de la función cuadrado 9 10 // función cuadrado para valores double 11 double cuadrado( double y ) 12 { 13 return y * y; 14 } // fin de la función cuadrado 15 16 // función que obtiene argumentos de los tipos 17 // int, float, char e int 18 void nada1( int a, float b, char c, int d ) 19 { 20 // cuerpo vacío de la función 21 } // fin de la función nada1 22 23 // función que recibe argumentos de los tipos 24 // char, int, float y double 25 int nada2( char a, int b, float c, double d ) 26 { 27 return 0; 28 } // fin de la función nada2 29 30 int main() 31 { 32 } // fin de main __Z8cuadradoi __Z8cuadradod __Z5nada1ifcRi __Z5nada2ciRfRd main Fig. 6.24  Manipulación de nombres para permitir la vinculación segura de tipos. Elcompiladorutilizasólolaslistasdeparámetrosparadiferenciarentrelasfuncionessobrecargadas. Dichas funciones no necesitan tener el mismo número de parámetros. Hay que tener cuidado al sobre- cargar funciones con parámetros predeterminados, ya que esto puede provocar ambigüedades. Error común de programación 6.11 Una función en la que se omiten sus argumentos predeterminados se podría invocar de una manera idéntica a otra función sobrecargada; esto es un error de compilación. Por ejemplo, al tener en un programa tanto una función que no reciba argumentos de mane- ra explícita, como una función con el mismo nombre que contenga todos los argumentos predeterminados, se produce un error de compilación cuando tratamos de usar el nombre de esa función en una llamada en la que no se pasen argumentos. El compilador no sabe cuál versión de la función elegir.
  • 278. 246 Capítulo 6 Funciones y una introducción a la recursividad Operadores sobrecargados En el capítulo 10 hablaremos acerca de cómo sobrecargar operadores, para definir la forma en que de- ben operar con objetos de tipos de datos definidos por el usuario (de hecho, hemos estado utilizando muchos operadores sobrecargados hasta este punto, incluyendo el operador de inserción de flujo y el operador de extracción de flujo , que se sobrecargan para todos los tipos fundamentales. En el capítu- lo 10 hablaremos más acerca de cómo sobrecargar los operadores y para poder manejar objetos de tipos definidos por el usuario). 6.19Plantillas de funciones Por lo general, las funciones sobrecargadas se utilizan para realizar operaciones similares que involucren distintos tipos de lógica de programa en distintos tipos de datos. Si la lógica del programa y las opera- ciones son idénticas para cada tipo de datos, la sobrecarga se puede llevar a cabo de una forma más compacta y conveniente, mediante el uso de plantillas de funciones. El programador escribe una sola definición de plantilla de función. Dados los tipos de los argumentos que se proporcionan en las llama- das a esta función, C++ genera de manera automática especializaciones de plantilla de función sepa- radas para manejar cada tipo de llamada de manera apropiada. Por ende, al definir una sola plantilla de función, en esencia se define toda una familia de funciones sobrecargadas. La figura 6.25 define una plantilla de función maximo (líneas 3 a 17) que determina el mayor de tres valores. Todas las definiciones de plantillas de función empiezan con la palabra clave template (línea 3), seguidas de una lista de parámetros de plantilla para la plantilla de función encerrada entre los paréntesis angulares ( y ). Antes de cada parámetro en la lista de parámetros (que a menudo se conoce como un parámetro de tipo formal) se coloca la palabra clave typename o la palabra clave class (que son sinónimos en este contexto). Los parámetros de tipo formal son receptáculos para los tipos fundamentales, o los tipos definidos por el usuario. Estos receptáculos, en este caso T, se utilizan para especificar los tipos de los parámetros de la función (línea 4), para especificar el tipo de valor de retorno de la función (línea 4) y para declarar variables dentro del cuerpo de la definición de la función (línea 6). Una plantilla de función se define de igual forma que cualquier otra función, pero utiliza los parámetros de tipo formal como receptáculos para los tipos de datos actuales. 1 // Fig. 6.25: maximo.h 2 // Archivo de encabezado de la plantilla de la función maximo. 3 template typename T // o template class T 4 T maximo( T valor1, T valor2, T valor3 ) 5 { 6 T valorMaximo = valor1; // asume que valor1 es maximo 7 8 // determina si valor2 es mayor que valorMaximo 9 if ( valor2 valorMaximo ) 10 valorMaximo = valor2; 11 12 // determina si valor3 es mayor que valorMaximo 13 if ( valor3 valorMaximo ) 14 valorMaximo = valor3; 15 16 return valorMaximo; 17 } // fin de la plantilla de función maximo Fig. 6.25  Archivo de encabezado de la plantilla de la función maximo.
  • 279. 6.19 Plantillas de funciones 247 La plantilla de función declara un solo parámetro de tipo formal T (línea 3) como receptáculo para el tipo de datos a evaluar por la función maximo. El nombre de un parámetro de tipo debe ser único en la lista de parámetros de la plantilla para una definición específica de ésta. Cuando el compilador detecta una invocación a maximo en el código fuente del programa, el tipo de los datos que se pasan a maximo se sustituye por T en toda la definición de la plantilla, y C++ crea una función completa para determinar el máximo de tres valores del tipo de datos especificado; los tres deben tener el mismo tipo, ya que sólo usamos un parámetro de tipo en este ejemplo. Después se compila la función recién creada; las plantillas son un medio para generar código. La figura 6.26 utiliza la plantilla de función maximo para determinar el mayor de tres valores int, tresvaloresdouble ytresvaloreschar,respectivamente(líneas17,27y37).Secreanfuncionesseparadas como resultado de las llamadas en las líneas 17, 27 y 37: esperar tres valores int, tres valores double y tres valores char, respectivamente. 1 // Fig. 6.26: fig06_26.cpp 2 // Programa de prueba de la plantilla de función maximo. 3 #include iostream 4 #include maximo.h // incluye la definición de la plantilla de función maximo 5 using namespace std; 6 7 int main() 8 { 9 // demuestra la función maximo con valores int 10 int int1, int2, int3; 11 12 cout Introduzca tres valores enteros: ; 13 cin int1 int2 int3; 14 15 // invoca a la versión int de maximo 16 cout El valor int de maximo es: 17 maximo( int1, int2, int3 ); 18 19 // demuestra la función maximo con valores double 20 double double1, double2, double3; 21 22 cout nnIntroduzca tres valores double: ; 23 cin double1 double2 double3; 24 25 // invoca a la versión double de maximo 26 cout El valor double de maximo es: 27 maximo( double1, double2, double3 ); 28 29 // demuestra la función maximo con valores char 30 char char1, char2, char3; 31 32 cout nnIntroduzca tres caracteres: ; 33 cin char1 char2 char3; 34 35 // invoca a la versión char de maximo 36 cout El valor char de maximo es: 37 maximo( char1, char2, char3 ) endl; 38 } // fin de main Fig. 6.26  Programa de prueba de la plantilla de función maximo (parte 1 de 2).
  • 280. 248 Capítulo 6 Funciones y una introducción a la recursividad Introduzca tres valores enteros: 1 2 3 El valor int de maximo es: 3 Introduzca tres valores double: 3.3 2.2 1.1 El valor double de maximo es: 3.3 Introduzca tres caracteres: A C B El valor char de maximo es: C La especialización de plantilla de función que se crea para el tipo int reemplaza cada ocurrencia de T con int, como se muestra a continuación: int maximo( int valor1, int valor2, int valor3 ) { int valorMaximo = valor1; // asume que valor1 es el máximo // determina si valor2 es mayor que valorMaximo if ( valor2 valorMaximo ) valorMaximo = valor2; // determina si valor3 es mayor que valorMaximo if ( valor3 valorMaximo ) valorMaximo = valor3; return valorMaximo; } // fin de la plantilla de función maximo C++11: tipos de valores de retorno al final para las funciones C++ introduce los tipos de valores de retorno al final para las funciones. Para especificar un tipo de valor de retorno al final, se coloca la palabra clave auto antes del nombre de la función, luego se coloca después de la lista de parámetros de la función el símbolo - y el tipo de valor de retorno. Por ejemplo, para especificar un tipo de valor de retorno al final para la plantilla de función maximo (figura 6.25), escribiríamos: template typename T auto maximo( T x, T y, T z ) - T A medida que construya plantillas de función más complejas, habrá casos en los que sólo se permitan tipos de valores de retorno al final. Dichas plantillas de función complejas están más allá del alcance de este libro. 6.20Recursividad Para algunos problemas, es conveniente hacer que las funciones se llamen a sí mismas. Una función re- cursiva es una función que se llama a sí misma, ya sea en forma directa o indirecta (a través de otra función). [Nota: el estándar de C++ indica que main no debe llamarse dentro de un programa ni en forma recursiva. Su único propósito es servir de punto inicial para la ejecución del programa]. En esta sección y en la siguiente presentaremos ejemplos simples de recursividad. La recursividad se discute extensamente en los cursos de ciencias computacionales de nivel superior. En la figura 6.32 (al final de la sección 6.22) se sintetizan los ejemplos y ejercicios de recursividad que se incluyen en el libro. Conceptos de recursividad Primero hablaremos sobre la recursividad en forma conceptual, y después analizaremos programas que contienen funciones recursivas. Las metodologías recursivas de solución de problemas tienen varios ele- mentos en común. Se hace una llamada a una función recursiva para resolver un problema. La función Fig. 6.26  Programa de prueba de la plantilla de función maximo (parte 2 de 2).
  • 281. 6.20 Recursividad 249 en realidad sabe cómo resolver sólo el (los) caso(s) más simple(s), o caso(s) base. Si se hace la llamada a la función con un caso base, ésta simplemente devuelve un resultado. Si se hace la llamada a la función con un problema más complejo, la función comúnmente divide el problema en dos piezas conceptuales: una pieza que sabe cómo resolver y otra pieza que no sabe cómo resolver. Para que la recursividad sea factible, esta última pieza debe ser similar al problema original, pero una versión ligeramente más sencilla o simple del mismo. Debido a que este nuevo problema se parece al problema original, la función llama a una copiadesímisma para trabajar en elproblemamáspequeño;aestoseleconocecomollamada recursiva, y también como paso recursivo. Por lo general, el paso recursivo incluye la palabra clave return, ya que su resultado se combina con la parte del problema que la función supo cómo resolver, para formar un resultado que se pasará de vuelta a la función original que hizo la llamada, que posiblemente sea main. Error común de programación 6.12 Omitir el caso base o escribir el paso de recursividad en forma incorrecta, de modo que no converja en el paso base, producirá un error de recursividad infinito y tal vez un desbor- damiento de pila. Esto es análogo al problema de un ciclo infinito en una solución itera- tiva (no recursiva). El paso recursivo se ejecuta mientras siga “abierta” la llamada original a la función (es decir, que no haya terminado su ejecución). Este paso puede producir muchas llamadas recursivas más, a medida que la función divide cada nuevo subproblema, con el que se llama a la función, en dos piezas concep- tuales. Para que la recursividad termine en un momento dado, cada vez que la función se llama a sí misma con una versión más simple del problema original, esta secuencia de problemas cada vez más pequeños debe converger en un caso base. En ese punto, la función reconoce el caso base y devuelve un resultado a la copia anterior de la función; después se origina una secuencia de retornos, hasta que la llamada a la función original devuelve el resultado final al método main. Todo esto suena bastante ex- travagante, en comparación con el tipo de solución de problemas que hemos usado hasta este punto. Como ejemplo de estos conceptos en la práctica, vamos a escribir un programa recursivo para realizar un popular cálculo matemático. Factorial El factorial de un entero positivo n, que se escribe como n! (y se pronuncia como “factorial de n”), viene siendo el producto n · (n – 1) · (n – 2) · … · 1 en donde 1! es igual a 1 y 0! se define como 1. Por ejemplo, 5! es el producto 5 ⋅ 4 ⋅ 3 ⋅ 2 ⋅ 1, que es igual a 120. Factorial iterativo El factorial de un numero entero más grande o igual a 0, puede calcularse de manera iterativa (sin recur- sividad), usando una instrucción for de la siguiente manera: factorial = 1; for ( unsigned int contador = numero; contador = 1; --contador ) factorial *= contador; Factorial recursivo Podemos llegar a una definición recursiva de la función factorial, si observamos la siguiente relación algebraica: n! = n · (n – 1)!
  • 282. 250 Capítulo 6 Funciones y una introducción a la recursividad Por ejemplo, 5! es sin duda igual a 5 * 4!, como se muestra en las siguientes ecuaciones: 5! = 5 · 4 · 3 · 2 · 1 5! = 5 · (4 · 3 · 2 · 1) 5! = 5 · (4!) Evaluación de 5! La evaluación de 5! procedería como se muestra en la figura 6.27, que ilustra cómo procede la sucesión de llamadas recursivas hasta que 1! se evalúa como 1, lo cual termina la recursividad. La figura 6.27(b) muestra los valores devueltos de cada llamada recursiva a la función que hizo la llamada, hasta que se calcula y devuelve el valor final. (a) Procesión de llamadas recursivas. 5 * 4! 4 * 3! 3 * 2! 2 * 1! 5! 1 (b) Valores devueltos de cada llamada recursiva. Valor final = 120 se devuelve 5! = 5 * 24 = 120 se devuelve 4! = 4 * 6 = 24 se devuelve 3! = 3 * 2 = 6 se devuelve 2! = 2 * 1 = 2 se devuelve 1 5 * 4! 4 * 3! 3 * 2! 2 * 1! 5! 1 Fig. 6.27  Evaluación recursiva de 5!. Uso de una función factorial recursiva para calcular los factoriales La figura 6.28 utiliza la recursividad para calcular e imprimir los factoriales de los enteros del 0 al 10. (En unos momentos explicaremos la elección del tipo de datos unsigned long). La función recursiva factorial (líneas 18 a 24) determina primero si la condición de terminación numero = 1 (línea 20) es verdadera. Si numero es menor o igual que 1, la función factorial devuelve 1 (línea 21), ya no es necesaria más recursividad y la función termina. Si numero es mayor que 1, en la línea 23 se expresa el problema como el producto de numero y una llamada recursiva a factorial en la que se evalúa el factorial de numero – 1, que es un problema un poco más simple que el cálculo original, factorial (numero). Por qué elegimos el tipo unsigned long en este ejemplo La función factorial se ha declarado para recibir un parámetro de tipo unsigned long y devolver un resultado de tipo unsigned long. Ésta es una notación abreviada para unsigned long int. El estándar de C++ requiere que una variable de tipo unsigned long int sea por lo menos tan grande como un valor
  • 283. 6.20 Recursividad 251 1 // Fig. 6.28: fig06_28.cpp 2 // Función recursiva factorial. 3 #include iostream 4 #include iomanip 5 using namespace std; 6 7 unsigned long factorial( unsigned long ); // prototipo de función 8 9 int main() 10 { 11 // calcula los factoriales del 0 al 10 12 for ( unsigned int contador = 0; contador = 10; ++contador ) 13 cout setw( 2 ) contador ! = factorial( contador ) 14 endl; 15 } // fin de main 16 17 // definición recursiva de la función factorial 18 unsigned long factorial( unsigned long numero ) 19 { 20 if ( numero = 1 ) // evalúa el caso base 21 return 1; // casos base: 0! = 1 y 1! = 1 22 else // paso recursivo 23 return numero * factorial( numero - 1 ); 24 } // fin de la función factorial 0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3628800 Fig. 6.28  Función recursiva factorial. int. Por lo general, un valor unsigned long int se almacena en por lo menos cuatro bytes (32 bits); dicha variable puede contener un valor en el rango desde 0 hasta (por lo menos) 4294967295. (El tipo de datos long int también se almacena por lo menos en cuatro bytes, y puede contener un valor en por lo menos el rango de –2147483647 a 2147483647). Como se puede ver en la figura 6.28, los valores de los factoriales aumentan su tamaño rápidamente. Elegimos el tipo de datos unsigned long de ma- nera que el programa pueda calcular factoriales mayores que 7! en las computadoras con enteros peque- ños (como los de dos bytes). Por desgracia, la función factorial produce valores extensos con tanta rapidez que, incluso el tipo unsigned long no nos ayuda a calcular muchos valores de factorial antes de que se exceda el valor de una variable unsigned long. El tipo unsigned long long int de C++11 El nuevo tipo unsigned long long int de C++ (que puede abreviarse como unsigned long long) en algunos sistemas nos permite almacenar valores en 8 bytes (64 bits), para guardar números tan grandes como 18446744073709551615.
  • 284. 252 Capítulo 6 Funciones y una introducción a la recursividad Representar números aún más grandes Podrían usarse variables de tipo de datos double para calcular los factoriales de números más grandes. Esto apunta a una debilidad en la mayoría de los lenguajes de programación, a saber, que los lenguajes no se extienden fácilmente para manejar los requerimientos únicos de varias aplicaciones. Como veremos cuando hablemos sobre la programación orientada a objetos con más detalle, C++ es un lenguaje exten- sible que nos permite crear clases que puedan representar enteros arbitrariamente grandes, si lo deseamos. Dichas clases ya están disponibles en bibliotecas de clases populares, y trabajaremos en nuestras propias clases similares en los ejercicios 9.14 y 10.9. 6.21Ejemplo sobre el uso de la recursividad: serie de Fibonacci La serie de Fibonacci, 0, 1, 1, 2, 3, 5, 8, 13, 21, … empieza con 0 y 1, y tiene la propiedad de que cada número subsiguiente de Fibonacci es la suma de los dos números Fibonacci anteriores. Esta serie ocurre en la naturaleza y, en específico, describe una forma de espiral. La proporción de números de Fibonacci sucesivos converge en un valor constante de 1.618…. Este número también ocurre con frecuencia en la naturaleza, y se le ha denominado proporción dorada, o media dorada. Los humanos tienden a descubrir que la media dorada es estéticamente placentera. A menudo, los ar- quitectos diseñan ventanas, cuartos y edificios cuya longitud y anchura se encuentran en la proporción de la media dorada. A menudo se diseñan tarjetas postales con una proporción de anchura/altura de media dorada. Definición recursiva de Fibonacci La serie de Fibonacci se puede definir de manera recursiva como: fibonacci(0) = 0 fibonacci(1) = 1 fibonacci(n) = fibonacci(n – 1) + fibonacci(n – 2) El programa de la figura 6.29 calcula el n-ésimo número de Fibonacci en forma recursiva, usando la función fibonacci. Los números de Fibonacci tienden a aumentar con bastante rapidez, aunque son más lentos que los factoriales. Por lo tanto, elegimos el tipo de datos unsigned long para el tipo del parámetroyeltipodevalorderetornoenlafunciónfibonacci.Enlafigura6.29semuestralaejecución del programa, que muestra los valores de Fibonacci para varios números. La aplicación empieza con una instrucción for que calcula y muestra los valores de Fibonacci para los enteros 0 a 10, y va seguida de tres llamadas para calcular los valores Fibonacci de los enteros 20, 30 y 35 (líneas 16 a 18). Las llamadas a la función fibonacci (líneas 13 y 16 a 18) de main no son llamadas recursivas, pero las llamadas de la línea 27 de fibonacci son recursivas. Cada vez que el programa invo- ca a fibonacci (líneas 22 a 28), la función evalúa de inmediato el caso base para determinar si numero es igual a 0 o 1 (línea 24). Si esto es verdadero, en la línea 25 se devuelve numero. Lo interesante es que, si numero es mayor que 1, el paso recursivo (línea 27) genera dos llamadas recursivas, cada una para un problema ligeramente más pequeño que la llamada original a fibonacci. 1 // Fig. 6.29: fig06_29.cpp 2 // Función recursiva fibonacci. 3 #include iostream Fig. 6.29  Función recursiva fibonacci (parte 1 de 2).
  • 285. 6.21 Ejemplo sobre el uso de la recursividad: serie de Fibonacci 253 4 using namespace std; 5 6 unsigned long fibonacci( unsigned long ); // prototipo de función 7 8 int main() 9 { 10 // calcula los valores de fibonacci del 0 al 10 11 for ( unsigned int contador = 0; contador = 10; ++contador ) 12 cout fibonacci( contador ) = 13 fibonacci( contador ) endl; 14 15 // muestra valores de fibonacci mayores 16 cout “nfibonacci( 20 ) = “ fibonacci( 20 ) endl; 17 cout “fibonacci( 30 ) = “ fibonacci( 30 ) endl; 18 cout “fibonacci( 35 ) = “ fibonacci( 35 ) endl; 19 } // fin de main 20 21 // método fibonacci recursivo 22 unsigned long fibonacci( unsigned long numero ) 23 { 24 if ( ( 0 == numero ) || ( 1 == numero ) ) // casos base 25 return numero; 26 else // paso recursivo 27 return fibonacci( numero - 1 ) + fibonacci( numero - 2 ); 28 } // fin de la función fibonacci fibonacci( 0 ) = 0 fibonacci( 1 ) = 1 fibonacci( 2 ) = 1 fibonacci( 3 ) = 2 fibonacci( 4 ) = 3 fibonacci( 5 ) = 5 fibonacci( 6 ) = 8 fibonacci( 7 ) = 13 fibonacci( 8 ) = 21 fibonacci( 9 ) = 34 fibonacci( 10 ) = 55 fibonacci( 20 ) = 6765 fibonacci( 30 ) = 832040 fibonacci( 35 ) = 9227465 Evaluación de fibonacci(3) La figura 6.30 muestra cómo la función fibonacci evaluaría fibonacci(3). Esta figura genera ciertas preguntas interesantes, en cuanto al orden en el que los compiladores de C++ evalúan los operandos de los operadores. Este orden es distinto del orden en el que se aplican los operadores a sus operandos; a saber, el orden que dictan las reglas de la precedencia y asociatividad de los operadores. La figura 6.30 muestra que al evaluar fibonacci(3), se producen dos llamadas recursivas: fibonacci(2) y fibonacci(1). ¿En qué orden se hacen estas llamadas? Fig. 6.29  Función recursiva fibonacci (parte 2 de 2).
  • 286. 254 Capítulo 6 Funciones y una introducción a la recursividad fibonacci( 3 ) fibonacci( 2 ) fibonacci( 1 ) return + fibonacci( 1 ) fibonacci( 0 ) return 1 return 0 return 1 return + Fig. 6.30  Conjunto de llamadas recursivas a la función fibonacci. Orden de evaluación de los operandos La mayoría de los programadores simplemente suponen que los operandos se evalúan de izquierda a derecha. C++ no especifica el orden en el que se van a evaluar los operandos de la mayoría de los opera- dores (incluyendo +). Por lo tanto, no debemos hacer suposiciones en cuanto al orden en el que se eva- luarán estas llamadas. De hecho, se podría ejecutar fibonacci(2) primero y después fibonacci(1), o se podrían ejecutar en el orden inverso: fibonacci(1) y luego fibonacci(2). En este programa y en la mayoría de los otros programas, el resultado sería el mismo. Sin embargo, en algunos programas la evaluación de un operando puede tener efectos secundarios (cambios en los valores de los datos) que podrían afectar al resultado final de la expresión. C++ especifica el orden de evaluación de los operandos sólo para cuatro operadores: , ||, el ope- rador coma (,) y ?:. Los primeros tres son operadores binarios, y se garantiza que sus dos operandos se evaluarán de izquierda a derecha. El último operador es el único operador ternario de C++. Su operando de más a la izquierda siempre se evalúa primero; si se evalúa como verdadero, el operando intermedio se evalúaacontinuaciónyseignoraelúltimooperando;sieloperandodemásalaizquierdaseevalúacomo falso, el tercer operando se evalúa a continuación y se ignora el operando intermedio. Tip de portabilidad 6.2 Los programas que dependen del orden de evaluación de los operandos de operadores dis- tintos de , ||, ?: y el operador coma (,) pueden funcionar de manera distinta en siste- mas con distintos compiladores y producir errores lógicos. Error común de programación 6.13 La escritura de programas que dependan del orden de evaluación de los operandos de operadores distintos de , ||, ?: y el operador coma (,) pueden producir errores lógicos. Tip para prevenir errores 6.9 No dependa del orden en el que se evalúan los operandos. Para asegurar que los efectos secundarios se apliquen en el orden correcto, divida las expresiones complejas en instruc- ciones separadas.
  • 287. 6.22 Comparación entre recursividad e iteración 255 Error común de programación 6.14 Recuerde que los operadores y || usan la evaluación de corto circuito. Colocar una expresión con un efecto secundario del lado derecho de un operador o || es un error lógico si esa expresión siempre debe evaluarse. Complejidad exponencial Hay que tener cuidado con los programas recursivos, como el que usamos aquí para generar números de Fibonacci. Cada nivel de recursividad en la función fibonacci tiene un efecto de duplicación sobre el número de llamadas a funciones; es decir, el número de llamadas recursivas que se requieren para calcular el n-ésimo número de Fibonacci se encuentra en el orden de 2n . Esto se sale rápidamente de control. Para calcular sólo el 20vo. número de Fibonacci se requeriría un orden de 220 , o cerca de un millón de llamadas, para calcular el 30vo. número de Fibonacci se requeriría un orden de 230 , o aproxi- madamente mil millones de llamadas, y así en lo sucesivo. Los científicos computacionales se refieren a esto como complejidad exponencial. Los problemas de esta naturaleza pueden humillar incluso hasta a las computadoras más poderosas del mundo. Las cuestiones relacionadas con la complejidad, tanto en general como en particular, se discuten con detalle en un curso del plan de estudios de ciencias compu- tacionales de nivel superior, al que generalmente se le llama “Algoritmos”. Tip de rendimiento 6.8 Evite los programas recursivos al estilo Fibonacci que produzcan una “explosión” exponen- cial de llamadas. 6.22Comparación entre recursividad e iteración En las dos secciones anteriores, estudiamos dos funciones recursivas que también pueden implementar- se mediante programas iterativos simples. En esta sección comparamos las dos metodologías, y habla- mos acerca del por qué podríamos elegir una metodología sobre la otra en una situación específica. • Tanto la iteración como la recursividad se basan en una instrucción de control: la iteración utili- za una estructura de repetición; la recursividad utiliza una estructura de selección. • Tanto la iteración como la recursividad implican la repetición: la iteración utiliza en forma ex- plícita una estructura de repetición; la recursividad logra la repetición a través de llamadas repe- tidas a una función. • La iteración y la recursividad implican una prueba de terminación: la iteración termina cuando fallalacondicióndecontinuacióndeciclo;larecursividadterminacuandosereconoceuncasobase. • La iteración mediante la repetición controlada por un contador y la recursividad se acercan gradualmente a la terminación: la iteración modifica un contador hasta que éste asuma un valor que haga que falle la condición de continuación de ciclo; la recursividad produce versiones más simples del problema original hasta que se llega al caso base. • Tanto la iteración como la recursividad pueden ocurrir infinitamente: un ciclo infinito ocurre con la iteración si la prueba de continuación de ciclo nunca se vuelve falsa; la recursividad infi- nita ocurre si el paso recursivo no reduce el problema durante cada llamada recursiva, de forma tal que llegue a converger en el caso base. Implementación del factorial iterativo Para ilustrar las diferencias entre la iteración y la recursividad, vamos a examinar una solución itera- tiva para el problema del factorial (figura 6.31). Se utiliza una instrucción de repetición (líneas 23 y 24 de la figura 6.31) en vez de la instrucción de selección de la solución recursiva (líneas 20 a 23 de la
  • 288. 256 Capítulo 6 Funciones y una introducción a la recursividad figura 6.28). Ambas soluciones usan una prueba de terminación. En la solución recursiva, en la línea 20 (figura 6.28) se evalúa el caso base. En la solución iterativa, en la línea 23 (figura 6.31) se evalúa la condición de continuación de ciclo; si la prueba falla, el ciclo termina. Por último, en vez de producir versiones cada vez más simples del problema original, la solución iterativa utiliza un contador que se modifica hasta que la condición de continuación de ciclo se vuelve falsa. 1 // Fig. 6.31: fig06_31.cpp 2 // Función iterativa factorial. 3 #include iostream 4 #include iomanip 5 using namespace std; 6 7 unsigned long factorial( unsigned int ); // prototipo de función 8 9 int main() 10 { 11 // calcula los factoriales del 0 al 10 12 for ( unsigned int contador = 0; contador = 10; ++contador ) 13 cout setw( 2 ) contador ! = factorial( contador ) 14 endl; 15 } // fin de main 16 17 // función factorial iterativa 18 unsigned long factorial( unsigned int numero ) 19 { 20 unsigned long resultado = 1; 21 22 // cálculo iterativo del factorial 23 for ( unsigned int i = numero; i = 1; --i ) 24 resultado *= i; 25 26 return resultado; 27 } // fin de la función factorial 0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3628800 Fig. 6.31  Función iterativa factorial. Desventajas de la recursividad La recursividad tiene muchas desventajas. Invoca al mecanismo en forma repetida, y en consecuencia se produce una sobrecarga de las llamadas a la función. Esta repetición puede ser perjudicial, en términos de tiempo del procesador y espacio de la memoria. Cada llamada recursiva crea otra copia de las variables de la función; esto puede consumir una cantidad considerable de memoria. Como la iteración ocurre
  • 289. 6.22 Comparación entre recursividad e iteración 257 comúnmente dentro de una función, se evita la sobrecarga de las llamadas repetidas a la función y la asignación adicional de memoria. Entonces, ¿por qué elegir la recursividad? Observación de Ingeniería de Software 6.13 Cualquier problema que se pueda resolver mediante la recursividad, se puede resolver también mediante la iteración (sin recursividad). Por lo general se prefiere un método recursivo a uno iterativo cuando el primero refleja con más naturalidad el problema, y se produce un programa más fácil de entender y de depurar. Otra razón por la que es prefe- rible elegir una solución recursiva es que una iterativa podría no ser aparente. Tip de rendimiento 6.9 Evite usar la recursividad en situaciones en las que se requiera un alto rendimiento. Las llamadas recursivas requieren tiempo y consumen memoria adicional. Error común de programación 6.15 Hacer que una función no recursiva se llame a sí misma por accidente, ya sea en forma directa o indirecta (a través de otra función), es un error lógico. Resumen de los ejemplos y ejercicios de recursividad en este libro En la figura 6.32 se sintetizan los ejemplos de recursividad y los ejercicios que se incluyen en el libro. Ubicación en el libro Ejemplos y ejercicios de recursividad Capítulo 6 Sección 6.20, fig. 6.28 Función factorial Sección 6.21, fig. 6.29 Función Fibonacci Ejercicio 6.36 Exponenciación recursiva Ejercicio 6.38 Torres de Hanoi Ejercicio 6.40 Visualización de la recursividad Ejercicio 6.41 Máximo común divisor Ejercicios 6.44 y 6.45 Ejercicio “¿Qué hace este programa?” Capítulo 7 Ejercicio 7.17 Ejercicio “¿Qué hace este programa?” Ejercicio 7.20 Ejercicio “¿Qué hace este programa?” Ejercicio 7.28 Determinar si una cadena es un palíndromo Ejercicio 7.29 Ocho reinas Ejercicio 7.30 Imprimir un arreglo Ejercicio 7.31 Imprimir una cadena en forma inversa Ejercicio 7.32 Valor mínimo en un arreglo Ejercicio 7.33 Recorrido de laberinto Ejercicio 7.34 Generación de laberintos al azar Fig. 6.32  Resumen de los ejemplos y ejercicios de recursividad en el libro (parte 1 de 2).
  • 290. 258 Capítulo 6 Funciones y una introducción a la recursividad Ubicación en el libro Ejemplos y ejercicios de recursividad Capítulo 19 (en el sitio web) Sección 19.6, figs. 19.20 y 19.22 Inserción de árboles binarios Sección 19.6, figs. 19.20 y 19.22 Recorrido preorden de un árbol binario Sección 19.6, figs. 19.20 y 19.22 Recorrido inorden de un árbol binario Sección 19.6, figs. 19.20 y 19.22 Recorrido postorden de un árbol binario Ejercicio 19.20 Imprimir una lista enlazada en forma inversa Ejercicio 19.21 Buscar en una lista enlazada Ejercicio 19.22 Eliminación de árboles binarios Ejercicio 19.23 Búsqueda de árboles binarios Ejercicio 19.24 Recorrido por orden de nivel de un árbol binario Ejercicio 19.25 Árbol de impresión Capítulo 20 (en el sitio web) Sección 20.3.3, fig. 20.6 Ordenamiento por combinación Ejercicio 20.8 Búsqueda lineal Ejercicio 20.9 Búsqueda binaria Ejercicio 20.10 Quicksort 6.23Conclusión En este capítulo aprendió más acerca de las declaraciones de funciones, incluyendo los prototipos, las firmas, los encabezados y los cuerpos de las funciones. Vimos las generalidades sobre las funciones matemáticas de la biblioteca. Aprendió acerca de la coerción de argumentos, o la acción de forzar a que los argumentos sean de los tipos apropiados que se especifiquen mediante las declaraciones de los pa- rámetros de una función. Demostramos cómo usar las funciones rand y srand para generar conjuntos de números aleatorios que se puedan utilizar para las simulaciones. Le mostramos cómo definir con- juntos de constantes mediante enum. Aprendió también acerca del alcance de las variables, los especi- ficadores de clase de almacenamiento y la duración del almacenamiento. Vimos dos formas distintas de pasar argumentos a las funciones: paso por valor y paso por referencia. Para el paso por referencia, las referencias se utilizan como un alias para una variable. Le mostramos cómo implementar funciones en línea y funciones que reciban argumentos predeterminados. Aprendió que varias funciones en una clase se pueden sobrecargar, usando el mismo nombre y distintas firmas. Dichas funciones se pueden utilizar para realizar las mismas tareas (o similares), usando distintos tipos o números de parámetros. Después, demostramos una manera más simple de sobrecargar funciones mediante las plantillas de función, en donde una función se define una sola vez, pero se puede usar para varios tipos distintos. Posteriormente le presentamos el concepto de la recursividad, en donde una función se llama a sí mis- ma para resolver un problema. En el capítulo 7, aprenderá a mantener listas y tablas de datos en arreglos y vectores (vector) orien- tados a objetos. Veremos una implementación basada en arreglo más elegante de la aplicación para tirar dados, y dos versiones mejoradas del caso de estudio LibroCalificaciones que estudiamos en los ca- pítulos 3 a 6, en donde utilizaremos arreglos para almacenar las calificaciones introducidas. Fig. 6.32  Resumen de los ejemplos y ejercicios de recursividad en el libro (parte 2 de 2).
  • 291. Resumen 259 Resumen Sección 6.1 Introducción • La experiencia ha demostrado que la mejor forma de desarrollar y mantener un programa extenso es construir- lo a partir de piezas simples y pequeñas. A esta técnica se le conoce como divide y vencerás (pág. 202). Sección 6.2 Componentes de los programas en C++ • Por lo general, los programas en C++ se escriben mediante la combinación de nuevas funciones y clases que escribimos con funciones “pre-empaquetadas”, y clases disponibles en la Biblioteca estándar de C++. • Las funciones permiten al programador modularizar un programa, al separar sus tareas en unidades autocon- tenidas. • Las instrucciones en los cuerpos de las funciones se escriben sólo una vez, se pueden reutilizar tal vez desde varias ubicaciones en un programa y además están ocultas de las demás funciones. Sección 6.3 Funciones matemáticas de la biblioteca • Algunas veces las funciones no son miembros de una clase. A dichas funciones se les conoce como funciones globales (pág. 204). • A menudo, los prototipos para las funciones globales se colocan en encabezados, de manera que las funciones globales se puedan reutilizar en cualquier programa que incluya el encabezado y se pueda crear un enlace con el código objeto de la función. Sección 6.4 Definiciones de funciones con varios parámetros • El compilador hace referencia al prototipo de función, para comprobar que las llamadas a una función tengan el número y tipos de argumentos correctos, que los tipos de los argumentos estén en el orden correcto y que el valor devuelto por la función se pueda utilizar de manera correcta en la expresión que llamó a la función. • Si una función no devuelve un resultado, el control regresa cuando el programa llega a la llave derecha de fin de la función, o mediante la ejecución de la instrucción return; Si una función devuelve un resultado, la instrucción return expresión; evalúa expresión y devuelve el valor de expresión a la función que hizo la llamada. Sección 6.5 Prototipos de funciones y coerción de argumentos • La porción de un prototipo de función que incluye el nombre de la función y los tipos de sus argumentos se conoce como la firma de la función (pág. 211), o simplemente firma. • Una característica importante de los prototipos de función es la coerción de argumentos (pág. 211); es decir, obligar a que los argumentos tengan los tipos especificados por las declaraciones de los parámetros. • El compilador puede convertir los argumentos a los tipos de parámetros según lo especificado por las reglas de promoción de C++ (pág. 211). Las reglas de promoción indican las conversiones implícitas que el compilador puede realizar entre tipos fundamentales. Sección 6.6 Encabezados de la Biblioteca estándar de C++ • La Biblioteca estándar de C++ está dividida en muchas porciones, cada una con su propio encabezado. Los encabezados también contienen definiciones de varios tipos de clases, funciones y constantes. • Un encabezado “instruye” al compilador acerca de cómo interconectarse con los componentes de la biblioteca. Sección 6.7 Caso de estudio: generación de números aleatorios • Llamar a rand (pág. 214) en forma repetida produce una secuencia de números seudoaleatorios (pág. 217). La secuencia se repite cada vez que se ejecute el programa.
  • 292. 260 Capítulo 6 Funciones y una introducción a la recursividad • Para randomizar los números producidos por rand, se pasa un argumento unsigned integer (por lo general de la función time; pág. 219) a la función srand (pág. 217), la cual siembra la función rand. • Los números aleatorios en un rango se pueden generar de la siguiente manera: numero = valorDesplazamiento+ rand() % factorEscala; en donde valorDesplazamiento (pág. 219) especifica el primer número en el rango deseado de enteros consecu- tivos y factorEscala (pág. 219) es igual a la anchura del rango deseado de enteros consecutivos. Sección 6.8 Caso de estudio: juego de probabilidad: introducción a las enum • Una enumeración, que se introduce mediante la palabra clave enum y va seguida de un nombre de tipo (pág. 222), es un conjunto de constantes enteras con nombres (pág. 222) que empiezan en 0, a menos que se especifique lo contrario, y se incrementan en 1. • enum sinalcancepuedenproducirconflictosdenombresyerroreslógicos.Paraeliminarestosproblemas,C++11 introduce enum con alcance (pág. 223), que se declaran con las palabras clave enum class (o con el sinónimo enum struct). • Para hacer referencia a una constante enum con alcance, debemos calificar la constante con el nombre del tipo de la enum con alcance y con el operador de resolución de ámbito (::). Si otra enum con alcance contiene el mismo identificador que para una de sus constantes, siempre queda claro cuál versión de la constante se está utilizando. • Las constantes en una enum se representan como enteros. • El tipo integral subyacente de una enum sin alcance depende de los valores de sus constantes; se garantiza que el tipo será lo bastante grande como para poder almacenar los valores constantes especificados. • El tipo integral subyacente de la enum con alcance es int, de manera predeterminada. • C++11 nos permite especificar el tipo integral subyacente de una enum, colocando después del nombre del tipo de enum dos puntos (:) y el tipo integral. • Se produce un error de compilación si el valor de la constante de una enum está fuera del rango que puede repre- sentarse por el tipo subyacente de la enum. Sección 6.9 Números aleatorios de C++11 • De acuerdo con CERT, la función rand no tiene “buenas propiedades estadísticas” y puede ser predecible, lo que hace a los programas que usan rand menos seguros. • C++11 cuenta con una nueva biblioteca más segura de herramientas de números aleatorios, que puede producir números aleatorios no determinísticos para simulaciones y casos de seguridad, en donde la predictibilidad es indeseable.Estasnuevasherramientasseencuentranenelencabezadorandom delaBibliotecaestándardeC++. • Para tener flexibilidad con base en la forma en que se utilizan los números aleatorios en los programas, C++11 provee muchas clases que representan varios motores de generación de números aleatorios y distribuciones. Un motor implementa un algoritmo de generación de números aleatorios que produce números seudoaleatorios. Una distribución controla el rango de valores producidos por un motor, los tipos de esos valores y las propieda- des estadísticas de los valores. • El tipo default_random_engine (pág. 224) representa el motor de generación de números aleatorios predeter- minado. • La distribución uniform_int_distribution (pág. 224) distribuye de manera uniforme los enteros seudoalea- torios a través de un rango especificado de valores. El rango predeterminado es desde 0 hasta el valor máximo de un int en su plataforma. Sección 6.10 Clases y duración de almacenamiento • La duración de almacenamiento de un identificador (pág. 225) determina el periodo durante el cual éste existe en la memoria. • El alcance de un identificador es la parte en la que se puede hacer referencia a éste en un programa. • El enlace de un identificador (pág. 225) determina si se conoce sólo en el archivo fuente en el que se declara, o en varios archivos fuente que se compilan y después se enlazan juntos.
  • 293. Resumen 261 • Las variables con duración de almacenamiento automática incluyen las variables locales declaradas en las fun- ciones, los parámetros de función y las variables locales o los parámetros de función declarados con register (pág. 225). Dichas variables se crean cuando la ejecución del programa entra en el bloque en el que están defi- nidas, existen mientras el bloque está activo y se destruyen cuando el programa sale del bloque. • Las palabras clave extern (pág. 225) y static declaran identificadores para variables de la duración de almace- namiento estática (pág. 225) y para funciones. Las variables de duración de almacenamiento estática existen a partir del punto en el que el programa empieza a ejecutarse, y dejan de existir cuando termina el programa. • El almacenamiento de una variable con duración de almacenamiento estático se asigna cuando el programa empieza su ejecución. Dicha variable se inicializa una vez al encontrar su declaración. Para las funciones, el nom- bre de la función existe cuando el programa empieza a ejecutarse, de igual forma que para las otras funciones. • Los identificadores externos (como las variables globales) y las variables locales declaradas con el especificador de clase de almacenamiento static tienen duración de almacenamiento estática (pág. 225). • Las declaraciones de variables globales (pág. 227) se colocan fuera de cualquier definición de clase o función. Las variables globales retienen sus valores a lo largo de la ejecución del programa. Las variables y las funciones globales se pueden referenciar mediante cualquier función que siga sus declaraciones o definiciones. • A diferencia de las variables automáticas, las variables locales static retienen sus valores cuando la función en la que se declaran regresa a la que hizo la llamada. Sección 6.11 Reglas de alcance • Un identificador que se declara fuera de cualquier función o clase tiene alcance de espacio de nombres global (pág. 228). • Los identificadores que se declaran dentro de un bloque tienen alcance de bloque (pág. 228), el cual empieza en la declaración del identificador y termina en la llave derecha de finalización (}) del bloque en el que se declara el identificador. • Las etiquetas son los únicos identificadores con alcance de función (pág. 228). Las etiquetas pueden usarse en cualquier parte de la función en la que aparezcan, pero no pueden referenciarse fuera del cuerpo de la función. • Un identificador que se declara fuera de una función o clase tiene alcance de espacio de nombres global. Dicho identificador es “conocido” en todas las funciones, desde el punto en el que se declara hasta el final del archivo. • Los identificadores en la lista de parámetros de un prototipo de función tienen alcance de prototipo de función (pág. 228). Sección 6.12 La pila de llamadas a funciones y los registros de activación • Las pilas (pág. 231) se denominan estructuras de datos “último en entrar, primero en salir” (UEPS); el último elemento que se mete (inserta; pág. 231) en la pila es el primero que se saca (extrae; pág. 231) de ella. • La pila de llamadas a funciones (pág. 232) soporta el mecanismo de llamadas/regresos a funciones y la creación, mantenimiento y destrucción de las variables automáticas de cada función a la que se llama. • Cada vez que una función llama a otra, se mete un marco de pila o registro de activación (pág. 232) a la pila, el cual contiene la dirección de retorno que necesita la función a la que se llamó para poder regresar a la función que hizo la llamada, junto con las variables automáticas y parámetros de la llamada a la función. • El marco de pila (pág. 232) existe mientras la función a la que se llamó esté activa. Cuando esa función regresa, su marco de pila se saca de la pila y sus variables automáticas locales dejan de existir. Sección 6.13 Funciones con listas de parámetros vacías • En C++, una lista de parámetros vacía se especifica mediante void o nada entre paréntesis. Sección 6.14 Funciones en línea • C++ cuenta con las funciones en línea (pág. 236) para ayudar a reducir la sobrecarga de las llamadas a funciones; en especial para las funciones pequeñas. Al colocar el calificador inline (pág. 236) antes del tipo de valor de retorno de la función en su definición, se aconseja al compilador para que genere una copia del código de la función en cada lugar en donde se llame a la función, para evitar la llamada a una función.
  • 294. 262 Capítulo 6 Funciones y una introducción a la recursividad • Los compiladores pueden poner código en línea para el que no hayamos utilizado de manera explícita la palabra clave inline. Los compiladores optimizadores modernos son tan sofisticados, que es mejor dejar a ellos las de- cisiones sobre poner código en línea. Sección 6.15 Referencias y parámetros por referencia • Cuando se pasa un argumento por valor (pág. 237), se crea una copia del valor del argumento y se pasa a la función que se llamó. Las modificaciones a la copia no afectan al valor de la variable original en la función que hizo la llamada. • Mediante el paso por referencia (pág. 237), la función que hace la llamada proporciona a la función que llamó lahabilidaddeaccederdirectamentealosdatosdelaprimera,ydemodificaresosdatosencasodequelafunción que se llamó así lo decida. • Un parámetro por referencia (pág. 238) es un alias para su correspondiente argumento en la llamada a una función. • Para indicar que un parámetro de función se pasa por referencia, simplemente hay que colocar un signo “” después del tipo del parámetro en el prototipo de la función y en su encabezado. • Todas las operaciones que se realizan en una referencia en realidad se realizan en la variable original. Sección 6.16 Argumentos predeterminados • Cuando una función se invoca repetidas veces con el mismo argumento para un parámetro específico, podemos especificar que dicho parámetro tiene un argumento predeterminado (pág. 240). • Cuando un programa omite un argumento para un parámetro con un argumento predeterminado, el compila- dor inserta el valor predeterminado del argumento para pasarlo a la llamada a la función. • Los argumentos predeterminados deben ser los argumentos de más a la derecha en la lista de parámetros de una función. • Los argumentos predeterminados deben especificarse en el prototipo de la función. Sección 6.17 Operador de resolución de ámbito unario • C++ proporciona el operador de resolución de ámbito unario (::) (pág. 242) para acceder a una variable global cuando una variable local con el mismo nombre se encuentra dentro del alcance. Sección 6.18 Sobrecarga de funciones • C++ permite definir varias funciones con el mismo nombre, siempre y cuando éstas tengan diferentes conjuntos de parámetros. A esta capacidad se le conoce como sobrecarga de funciones (pág. 243). • Cuando se hace una llamada a una función sobrecargada, el compilador de C++ selecciona la función apropia- da al examinar el número, tipos y orden de los argumentos en la llamada. • Las funciones sobrecargadas se diferencian mediante sus firmas. • El compilador codifica cada identificador de función con los tipos de sus parámetros para permitir un enlace seguro de tipos (pág. 244). Este tipo de enlace asegura que se llame a la función sobrecargada apropiada, y que los tipos de los argumentos se conformen a los tipos de los parámetros. Sección 6.19 Plantillas de funciones • Por lo general, las funciones sobrecargadas realizan operaciones similares que involucren distintos tipos de ló- gica de programa en distintos tipos de datos. Si la lógica del programa y las operaciones son idénticas para cada tipo de datos, la sobrecarga se puede llevar a cabo de una forma más compacta y conveniente, mediante el uso de plantillas de funciones (pág. 246). • Dados los tipos de los argumentos que se proporcionan en las llamadas a una plantilla de función, C++ genera de manera automática especializaciones de plantilla de función separadas (pág. 246) para manejar cada tipo de llamada de manera apropiada. • Todas las definiciones de plantillas de función empiezan con la palabra clave template (pág. 246) seguida de una lista de parámetros de plantilla (pág. 246) para la plantilla de función encerrada entre los paréntesis angu- lares ( y ).
  • 295. Ejercicios de autoevaluación 263 • Los parámetros de tipo formal (pág. 246) son precedidos por la palabra clave typename (o class) y son recep- táculos para los tipos fundamentales, o los tipos definidos por el usuario. Estos receptáculos se utilizan para especificar los tipos de los parámetros de la función, para especificar el tipo de valor de retorno de la función y para declarar variables dentro del cuerpo de la definición de la función. • C++11 introduce los tipos de valores de retorno al final para las funciones. Para especificar este tipo de valor de retorno al final, coloque la palabra clave auto antes del nombre de la función y coloque después de la lista de parámetros de la función el signo - y el tipo de valor de retorno. Sección 6.20 Recursividad • Una función recursiva (pág. 248) es una función que se llama a sí misma, ya sea en forma directa o indirecta. • Una función recursiva sabe cómo resolver sólo el (los) caso(s) más simple(s), o caso(s) base. Si se hace la llamada a la función con un caso base (pág. 249), ésta simplemente devuelve un resultado. • Si se hace la llamada a la función con un problema más complejo, la función comúnmente divide el problema en dos piezas conceptuales: una que sabe cómo resolver y otra que no sabe cómo. Para que la recursividad sea factible, esta última pieza debe ser similar al problema original, pero una versión ligeramente más sencilla o simple del mismo. • Para que la recursividad termine, la secuencia llamadas recursivas (pág. 249) debe converger en el caso base. • El nuevo tipo unsigned long long int de C++11 (que puede abreviarse como unsigned long long) en algunos sistemas nos permite almacenar valores en 8 bytes (64 bits), que pueden contener números tan grandes como 18446744073709551615. Sección 6.21 Ejemplo sobre el uso de la recursividad: serie de Fibonacci • La proporción de números de Fibonacci sucesivos converge en un valor constante de 1.618… Este número también ocurre con frecuencia en la naturaleza, y se le ha denominado proporción dorada, o media dorada (pág. 252). Sección 6.22 Comparación entre recursividad e iteración • La iteración (pág. 249) y la recursividad tienen muchas similitudes: ambas se basan en una instrucción de con- trol, implican la repetición, implican una prueba de terminación, se acercan gradualmente a la terminación y pueden ocurrir infinitamente. • La recursividad invoca al mecanismo en forma repetida, y en consecuencia se produce una sobrecarga de las llamadas a la función. Cada llamada recursiva (pág. 249) crea otra copia de las variables de la función; este conjunto de copias puede consumir una cantidad considerable de espacio en memoria. Ejercicios de autoevaluación 6.1 Complete las siguientes oraciones: a) En C++, los componentes de un programa se llaman ________ y ________. b) Una función se invoca con un(a) __________. c) Aunavariablequeseconocesólodentrodelafunciónenlaqueestádeclarada,selellama__________. d) La instrucción __________ en una función a la que se llamó pasa el valor de una expresión, de vuelta a la función que hizo la llamada. e) La palabra clave __________ se utiliza en un encabezado de función para indicar que una función no devuelve ningún valor, o para indicar que esa función no contiene parámetros. f) El ________ de un identificador es la porción del programa en la que puede usarse. g) Las tres formas de regresar el control de una llamada a una función a la función que la llamó son __________, __________ y __________. h) Un __________ permite al compilador comprobar el número, tipos y orden de los argumentos que se pasan a una función. i) La función ________ se utiliza para producir números aleatorios. j) La función _________ se utiliza para establecer la semilla de números aleatorios, para randomizar la secuencia de números generada por la función rand.
  • 296. 264 Capítulo 6 Funciones y una introducción a la recursividad k) El especificador de clase de almacenamiento ________ es una recomendación que se hace al compi- lador para que almacene una variable en uno de los registros de la computadora. l) Una variable que se declara fuera de cualquier bloque o función es una variable ________. m) Para que una variable local en una función retenga su valor entre las llamadas a la función, debe decla- rarse con el especificador de clase de almacenamiento ________. n) Una función que se llama a sí misma, ya sea en forma directa o indirecta (a través de otra función), es una función ________. o) Por lo general, una función recursiva tiene dos componentes: uno que proporciona el medio para que termine la recursividad, al evaluar un caso ______, y uno que expresa el problema como una llamada recursiva para un problema más simple que el de la llamada original. p) Es posible tener varias funciones con el mismo nombre, que operen con distintos tipos o números de argumentos. A esto se le conoce como ________ de funciones. q) El __________ permite acceder a una variable global con el mismo nombre que una variable en el alcance actual. r) El calificador ________ se usa para declarar variables de sólo lectura. s) Una ________ de función permite definir una sola función para realizar una tarea en muchos tipos de datos distintos. 6.2 Para el programa de la figura 6.33, indique el alcance (ya sea de función, de espacio de nombres global, de bloque o de prototipo de función) de cada uno de los siguientes elementos: a) La variable x en main. b) La variable y en cubo. c) La función cubo. d) La función main. e) El prototipo de función para cubo. f) El identificador y en el prototipo de función para cubo. 1 // Ejercicio 6.2: ej06_02.cpp 2 #include iostream 3 using namespace std; 4 5 int cubo( int y ); // prototipo de función 6 7 int main() 8 { 9 int x = 0; 10 11 for ( x = 1; x = 10; x++ ) // itera 10 veces 12 cout cubo( x ) endl; // calcula el cubo de x e imprime los resultados 13 } // fin de main 14 15 // definición de la función cubo 16 int cubo( int y ) 17 { 18 return y * y * y; 19 } // fin de la función cubo Fig. 6.33  Programa para el ejercicio 6.2. 6.3 Escriba un programa que pruebe si los ejemplos de las llamadas a las funciones matemáticas de la biblio- teca que se muestran en la figura 6.2 realmente producen los resultados indicados. 6.4 Proporcione el encabezado para cada una de las siguientes funciones: a) La función hipotenusa, que toma dos argumentos de punto flotante con doble precisión, llamados lado1 y lado2, y que devuelve un resultado de punto flotante, con doble precisión. b) La función menor, que toma tres enteros x, y y z, y devuelve un entero. c) La función instrucciones, que no recibe argumentos y no devuelve ningún valor. [Nota: dichas fun- ciones se utilizan comúnmente para mostrar instrucciones a un usuario].
  • 297. Ejercicios de autoevaluación 265 d) La función intADouble, que recibe un argumento entero llamado numero y devuelve un resultado de punto flotante, con doble precisión. 6.5 Proporcione el prototipo de función (sin nombres de parámetros) para cada una de las siguientes situa- ciones: a) La función descrita en el ejercicio 6.4(a). b) La función descrita en el ejercicio 6.4(b). c) La función descrita en el ejercicio 6.4(c). d) La función descrita en el ejercicio 6.4(d). 6.6 Escriba una declaración para cada uno de los siguientes casos: a) Una variable int llamada cuenta, que deba mantenerse en un registro. Inicialice cuenta en 0. b) La variable de punto flotante con doble precisión llamada ultimoVal, que debe retener su valor entre las llamadas a la función en la que está definida. 6.7 Encuentre el o los errores en cada uno de los siguientes segmentos de programas, y explique cómo se pue- den corregir (vea también el ejercicio 6.47): a) int g() { cout Dentro de la funcion g endl; int h() { cout Dentro de la función h endl; } } b) int suma( int x, int y ) { int resultado = 0; resultado = x + y; } c) int suma( int n ) { if ( 0 == n ) return 0; else n + suma( n - 1 ); } d) void f( double a ); { float a; cout a endl; } e) void producto() { int a = 0; int b = 0; int c = 0; cout Escribe tres enteros: ; cin a b c; int resultado = a * b * c; cout El resultado es resultado; return resultado; } 6.8 ¿Por qué un prototipo de función podría contener la declaración del tipo de un parámetro, como double ?
  • 298. 266 Capítulo 6 Funciones y una introducción a la recursividad 6.9 (Verdadero/Falso) Todos los argumentos a las llamadas a funciones en C++ se pasan por valor. 6.10 Escriba un programa completo que pida al usuario el radio de una esfera, calcule e imprima el volumen de esa esfera. Use una función inline llamada volumenEsfera que devuelva el resultado de la siguiente expresión: (4.0 / 3.0 * 3.14159 * pow(radio, 3)). Respuestas a los ejercicios de autoevaluación 6.1 a) funciones, clases. b) llamada a una función. c) variable local. d) return. e) void. f) alcance. g) return; return expresión; o encontrar la llave derecha de cierre de una función. h) prototipo de función. i) rand. j) srand. k) register. l) global. m) static. n) recursiva. o) base. p) sobrecarga q) operador de resolución de ámbito unario (::). r) const. s) plantilla. 6.2 a) alcance de bloque. b) alcance de bloque. c) alcance de espacio de nombres global. d) alcance de espacio de nombres global. e) alcance de espacio de nombres global. f) alcance de prototipo de función. 6.3 Vea el siguiente programa: 1 // Ejercicio 6.3: ej06_03.cpp 2 // Prueba de las funciones matemáticas de la biblioteca. 3 #include iostream 4 #include iomanip 5 #include cmath 6 using namespace std; 7 8 int main() 9 { 10 cout fixed setprecision( 1 ); 11 12 cout sqrt( 9.0 ) = sqrt( 9.0 ); 13 cout nexp( 1.0 ) = setprecision( 6 ) 14 exp( 1.0 ) nexp( setprecision( 1 ) 2.0 15 ) = setprecision( 6 ) exp( 2.0 ); 16 cout nlog( 2.718282 ) = setprecision( 1 ) 17 log( 2.718282 ) 18 nlog( setprecision( 6 ) 7.389056 ) = 19 setprecision( 1 ) log( 7.389056 ); 20 cout nlog10( 10.0 ) = log10( 10.0 ) 21 nlog10( 100.0 ) = log10( 100.0 ) ; 22 cout nfabs( 5.1 ) = fabs( 5.1 ) 23 nfabs( 0.0 ) = fabs( 0.0 ) 24 nfabs( -8.76 ) = fabs( -8.76 ); 25 cout nceil( 9.2 ) = ceil( 9.2 ) 26 nceil( -9.8 ) = ceil( -9.8 ); 27 cout nfloor( 9.2 ) = floor( 9.2 ) 28 nfloor( -9.8 ) = floor( -9.8 ); 29 cout npow( 2.0 , 7.0 ) = 30 pow( 2.0, 7.0 ) npow( 9.0 , 31 0.5 ) = pow( 9.0, 0.5 ); 32 cout setprecision( 3 ) nfmod( 33 2.6 , 1.2 ) = 34 fmod( 2.6, 1.2 ) setprecision( 1 ); 35 cout nsin( 0.0 ) = sin( 0.0 ); 36 cout ncos( 0.0 ) = cos( 0.0 ); 37 cout ntan( 0.0 ) = tan( 0.0 ) endl; 38 } // fin de main
  • 299. Respuestas a los ejercicios de autoevaluación 267 sqrt(9.0) = 3.0 exp(1.0) = 2.718282 exp(2.0) = 7.389056 log(2.718282) = 1.0 log(7.389056) = 2.0 log10(10.0) = 1.0 log10(100.0) = 2.0 fabs(5.1) = 5.1 fabs(0.0) = 0.0 fabs(-8.8) = 8.8 ceil(9.2) = 10.0 ceil(-9.8) = -9.0 floor(9.2) = 9.0 floor(-9.8) = -10.0 pow(2.0, 7.0) = 128.0 pow(9.0, 0.5) = 3.0 fmod(2.600, 1.200) = 0.200 sin(0.0) = 0.0 cos(0.0) = 1.0 tan(0.0) = 0.0 6.4 a) double hipotenusa( double lado1, double lado2 ) b) int menor( int x, int y, int z ) c) void instrucciones() d) double intADouble( int numero ) 6.5 a) double hipotenusa( double, double ); b) int menor( int, int, int ); c) void instrucciones(); d) double intADouble( int ); 6.6 a) register int cuenta = 0; b) static double ultimoVal; 6.7 a) Error: la función h está definida en la función g. Corrección: mueva la definición de h fuera de la definición de g. b) Error: se supone que la función debe devolver un entero, pero no es así. Corrección: coloque una instrucción return resultado; al final del cuerpo de la función, o elimine la variable resultado y coloque la siguiente instrucción en la función: return x + y; c) Error: no se devuelve el resultado de n + suma( n – 1 ); suma devuelve un resultado incorrecto. Corrección: vuelva a escribir la instrucción en la cláusula else de la siguiente manera: return n + sum( n - 1 ); d) Errores: el punto y coma que va después del paréntesis derecho de la lista de parámetros y la redefini- ción del parámetro a en la definición de la función. Correcciones: elimine el punto y coma que va después del paréntesis derecho de la lista de parámetros, y elimine la declaración float a;. e) Error: la función devuelve un valor cuando no debe hacerlo. Corrección: elimine la instrucción return o cambie el tipo de valor de retorno. 6.8 Esto crea un parámetro de referencia de tipo “referencia a double”, el cual permite que la función modifi- que la variable original en la función que hace la llamada. 6.9 Falso. C++ permite el paso por referencia mediante el uso de parámetros por referencia (y apuntadores, como veremos en el capítulo 8).
  • 300. 268 Capítulo 6 Funciones y una introducción a la recursividad 6.10 Vea el siguiente programa: 1 // Solución al ejercicio 6.10: Ej06_10.cpp 2 // Función en línea que calcula el volumen de una esfera. 3 #include iostream 4 #include cmath 5 using namespace std; 6 7 const double PI = 3.14159; // define la constante global PI 8 9 // calcula el volumen de una esfera 10 inline double volumenEsfera( const double radio ) 11 { 12 return 4.0 / 3.0 * PI * pow( radio, 3 ); 13 } // fin de la función en línea volumenEsfera 14 15 int main() 16 { 17 double valorRadio = 0; 18 19 // pide el radio al usuario 20 cout Escriba la longitud del radio de su esfera: ; 21 cin valorRadio; // recibe el radio 22 23 // usa valorRadio para calcular el volumen de la esfera y mostrar el resultado 24 cout El volumen de la esfera con radio valorRadio 25 es volumenEsfera( valorRadio ) endl; 26 } // fin de main Ejercicios 6.11 Muestre el valor de x después de ejecutar cada una de las siguientes instrucciones: a) x = fabs( 7.5 ) b) x = floor( 7.5 ) c) x = fabs( 0.0 ) d) x = ceil( 0.0 ) e) x = fabs( -6.4 ) f) x = ceil( -6.4 ) g) x = ceil( -fabs( -8 + floor( -5.5 ) ) ) 6.12 (Cargos de estacionamiento) Un estacionamiento cobra una cuota mínima de $2.00 por estacionarse hasta tres horas. El estacionamiento cobra $0.50 adicionales por cada hora o fracción que se pase de tres horas. El cargo máximo para cualquier periodo dado de 24 horas es de $10.00. Suponga que ningún automóvil se estaciona durante más de 24 horas a la vez. Escriba un programa que calcule e imprima los cargos por estacionamiento para cada uno de tres clientes que estacionaron su automóvil ayer en este estacionamiento. Debe introducir las horas de estacionamientoparacadacliente.Elprogramadebeimprimirlosresultadosenunformatotabularordenado,debe calcular e imprimir el total de los recibos de ayer. El programa debe utilizar la función calcularCargos para deter- minar el cargo para cada cliente. Sus resultados deben aparecer en el siguiente formato: Automóvil Horas Cargo 1 1.5 2.00 2 4.0 2.50 3 24.0 10.00 TOTAL 29.5 14.50
  • 301. Ejercicios 269 6.13 (Redondeo de números) Una aplicación de la función floor es redondear un valor al siguiente entero. La instrucción y = floor( x + 0.5 ); redondea el número x al entero más cercano y asigna el resultado a y. Escriba un programa que lea varios números y que utilice la instrucción anterior para redondear cada uno de los números a su entero más cercano. Para cada número procesado, muestre tanto el número original como el redondeado. 6.14 (Redondeo de números) La función floor puede utilizarse para redondear un número hasta un lugar de- cimal específico. La instrucción y = floor( x * 10 + 0.5 ) / 10; redondea x en la posición de las décimas (la primera posición a la derecha del punto decimal). La instrucción y = floor( x * 100 + 0.5 ) / 100; redondea x en la posición de las centésimas (la segunda posición a la derecha del punto decimal). Escriba un pro- grama que defina cuatro funciones para redondear un número x en varias formas: a) redondearAEntero( numero ) b) redondearADecimas( numero ) c) redondearACentesimas( numero ) d) redondearAMilesimas( numero ) Para cada valor leído, su programa debe imprimir el valor original, el número redondeado al entero más cer- cano, el número redondeado a la décima más cercana, el número redondeado a la centésima más cercana y el nú- mero redondeado a la milésima más cercana. 6.15 (Preguntas con respuestas cortas) Responda a cada una de las siguientes preguntas: a) ¿Qué significa elegir números “al azar”? b) ¿Por qué es la función rand útil para simular juegos al azar? c) ¿Por qué se debe randomizar un programa mediante srand? ¿Bajo qué circunstancias es aconsejable no randomizar? d) ¿Por qué a menudo es necesario escalar o desplazar los valores producidos por rand? e) ¿Por qué es la simulación computarizada de las situaciones reales una técnica útil? 6.16 (Números aleatorios) Escriba instrucciones que asignen enteros aleatorios a la variable n en los siguientes rangos: a) 1 ≤ n ≤ 2 b) 1 ≤ n ≤ 100 c) 0 ≤ n ≤ 9 d) 1000 ≤ n ≤ 1112 e) –1 ≤ n ≤ 1 f) –3 ≤ n ≤ 11 6.17 (Números aleatorios) Escriba una sola instrucción que imprima un número al azar de cada uno de los si- guientes conjuntos: a) 2, 4, 6, 8, 10. b) 3, 5, 7, 9, 11. c) 6, 10, 14, 18, 22. 6.18 (Exponenciación) Escriba una función llamada enteroPotencia( base, exponente ) que devuelva el valor de baseexponente Por ejemplo, enteroPotencia(3, 4) = 3 * 3 * 3 * 3. Suponga que exponente es un entero positivo distinto de cero y que base es un entero. No utilice ninguna función matemática de la biblioteca.
  • 302. 270 Capítulo 6 Funciones y una introducción a la recursividad 6.19 (Cálculos de hipotenusa) Defina una función llamada hipotenusa que calcule la hipotenusa de un trián- gulo recto, cuando se proporcionen las longitudes de los otros dos lados. La función debe recibir dos argumentos double y devolver la hipotenusa como double. Use esta función en un programa para determinar la hipotenusa para cada uno de los triángulos que se muestran a continuación. Triángulo Lado 1 Lado 2 1 3.0 4.0 2 5.0 12.0 3 8.0 15.0 6.20 (Múltiples) Escriba una función llamada multiple que determine, para un par de enteros, si el segundo entero es múltiplo del primero. La función debe tomar dos argumentos enteros y devolver true si el segundo es múltiplo del primero, y false en caso contrario. Use esta función en un programa que reciba como entrada una serie de pares de enteros. 6.21 (Números pares) Escriba un programa que reciba una serie de enteros y los pase, uno a la vez, a una función llamada esPar, que utilice el operador módulo para determinar si un entero dado es par. La función debe tomar un argumento entero y devolver true si el entero es par, y false en caso contrario. 6.22 (Cuadrado de asteriscos) Escriba una función que muestre en el margen izquierdo de la pantalla un cua- drado relleno de asteriscos, cuyo lado se especifique en el parámetro entero lado. Por ejemplo, si lado es 4, la fun- ción debe mostrar lo siguiente: **** **** **** **** 6.23 (Cuadrado de cualquier carácter) Modifique la función creada en el ejercicio 6.22 para formar el cuadra- do de cualquier carácter que esté contenido en el parámetro tipo carácter caracterRelleno. Por ejemplo, si lado es 5 y caracterRelleno es “#”, esta función debe imprimir lo siguiente: ##### ##### ##### ##### ##### 6.24 (Separar dígitos) Escriba segmentos de programas que realicen cada una de las siguientes tareas: a) Calcular la parte entera del cociente, cuando el entero a se divide entre el entero b. b) Calcular el residuo entero cuando el entero a se divide entre el entero b. c) Utilizar las piezas de los programas desarrollados en las partes (a) y (b) para escribir una función que reciba un entero entre 1 y 32767, y que lo imprima como una serie de dígitos, separando cada par de dígitos por dos espacios. Por ejemplo, el entero 4562 debe imprimirse de la siguiente manera: 4 5 6 2 6.25 (Calcular el número de segundos) Escriba una función que reciba la hora en forma de tres argumentos enteros (horas, minutos y segundos) y devuelva el número de segundos transcurridos desde la última vez que el reloj “marcó las 12”. Use esta función para calcular el monto de tiempo en segundos entre dos horas, las cuales deben estar dentro de un ciclo de 12 horas del reloj.
  • 303. Ejercicios 271 6.26 (Temperaturas en Centígrados y Fahrenheit) Implemente las siguientes funciones enteras: a) La función centigrados que devuelve la equivalencia en grados Centígrados de una temperatura en grados Fahrenheit. b) La función fahrenheit que devuelve la equivalencia en grados Fahrenheit de una temperatura en grados Centígrados. c) Utilice estas funciones para escribir un programa que imprima gráficos que muestren los equivalentes en grados Fahrenheit de todas las temperaturas en grados Centígrados, desde 0 hasta 100, y los equi- valentes en grados Centígrados de todas las temperaturas en grados Fahrenheit, desde 32 hasta 212. Imprima los resultados en un formato tabular ordenado que minimice el número de líneas de salida, al tiempo que permanezca legible. 6.27 (Encontrar el mínimo) Escriba un programa que reciba tres números de punto flotante de doble precisión, y que los pase a una función que devuelva el número más pequeño. 6.28 (Números perfectos) Se dice que un número entero es un número perfecto si la suma de sus divisores, inclu- yendo 1 (pero no el número en sí), es igual al número. Por ejemplo, 6 es un número perfecto ya que 6 = 1 + 2 + 3. Escriba una función llamada esPerfecto que determine si el parámetro numero es un número perfecto. Use esta función en un programa que determine e imprima todos los números perfectos entre 1 y 1000. Imprima los divi- sores de cada número perfecto para confirmar que el número sea realmente perfecto. Ponga a prueba el poder de su computadora, evaluando números mucho más grandes que 1000. 6.29 (Números primos) Se dice que un entero es primo si puede dividirse solamente por 1 y por sí mismo. Por ejemplo, 2, 3, 5 y 7 son primos, pero 4, 6, 8 y 9 no. a) Escriba una función que determine si un número es primo. b) Useestafunciónenunprogramaquedetermineeimprimatodoslosnúmerosprimosentre2y10,000. ¿Cuántos de estos números hay que probar realmente para asegurarse de encontrar todos los números primos? c) Al principio podría pensarse que n/2 es el límite superior para evaluar si un número es primo, pero lo máximo que se necesita es ir hasta la raíz cuadrada de n. ¿Por qué? Vuelva a escribir el programa y eje- cútelo de ambas formas. Estime la mejora en el rendimiento. 6.30 (Dígitos inversos) Escriba una función que reciba un valor entero y devuelva el número con sus dígitos invertidos. Por ejemplo, para el número 7631, la función debe regresar 1367. 6.31 (Máximo común divisor) El máximo común divisor (MCD) de dos enteros es el entero más grande que puede dividir uniformemente a cada uno de los dos números. Escriba una función llamada mcd que devuelva el máximo común divisor de dos enteros. 6.32 (Puntos de calidad para calificaciones numéricas) Escriba una función llamada puntosCalidad que reciba como entrada el promedio de un estudiante y devuelva 4 si el promedio se encuentra entre 90 y 100, 3 si el prome- dio se encuentra entre 80 y 89, 2 si el promedio se encuentra entre 70 y 79, 1 si el promedio se encuentra entre 60 y 69, y 0 si el promedio es menor de 60. 6.33 (Lanzar monedas) Escriba un programa que simule el lanzamiento de monedas. Cada vez que se lance la moneda, el programa debe imprimir Cara o Cruz. Deje que el programa lance la moneda 100 veces y cuente el númerodevecesqueaparezcacadaunodelosladosdelamoneda.Imprimalosresultados.Elprogramadebellamar a una función separada llamada tirar, que no reciba argumentos y devuelva 0 en caso de cara y 1 en caso de cruz. [Nota: si el programa simula en forma realista el lanzamiento de monedas, cada lado de la moneda debe aparecer aproximadamente la mitad del tiempo`]. 6.34 (Juego “Adivina el número”) Escriba un programa que juegue a “adivina el número” de la siguiente mane- ra: su programa elige el número a adivinar, seleccionando un entero aleatorio en el rango de 1 a 1000. Después, el programa muestra lo siguiente: Tengo un numero entre 1 y 1000. Puedes adivinar mi numero? Por favor escribe tu primera respuesta.
  • 304. 272 Capítulo 6 Funciones y una introducción a la recursividad El jugador escribe su primer intento. El programa responde con uno de los siguientes mensajes: 1. Excelente! Adivinaste el numero! Te gustaria jugar de nuevo (s/n)? 2. Demasiado bajo. Intenta de nuevo. 3. Demasiado alto. Intenta de nuevo. Si la respuesta del jugador es incorrecta, su programa deberá iterar hasta que el jugador adivine correctamente. Su programa deberá seguir indicando al jugador los mensajes Demasiado alto o Demasiado bajo, para ayudar a que el jugador “se acerque” a la respuesta correcta. 6.35 (Modificación del juego “Adivina el número”) Modifique el programa del ejercicio 6.34 para contar el número de intentos que haga el jugador. Si el número es 10 o menos, imprima el mensaje “O ya sabia usted el secreto, o tuvo suerte!”. Si el jugador adivina el número en 10 intentos, imprima el mensaje “Aja! Sabía usted el secreto!”. Si el jugador hace más de 10 intentos, imprima el mensaje “Deberia haberlo hecho mejor!”. ¿Por qué no se deben requerir más de 10 intentos? Bueno, en cada “buen intento”, el jugador debe poder eliminar la mitad de los números. Ahora muestre por qué cualquier número de 1 a 1000 puede adivinarse en 10 intentos o menos. 6.36 (Exponenciación recursiva) Escriba una función recursiva llamada potencia( base, exponente ) que, cuando sea llamada, devuelva base exponente Por ejemplo, potencia( 3, 4 ) = 3 * 3 * 3 * 3. Suponga que exponente es un entero mayor o igual que 1. Suge- rencia: el paso recursivo debe utilizar la relación base exponente = base · base exponente — 1 y la condición de terminación ocurre cuando exponente es igual a 1, ya que base 1 = base 6.37 (Serie de Fibonacci: solución iterativa) Escriba una versión no recursiva de la función fibonacci de la figura 6.29. 6.38 (Torres de Hanoi) En este capítulo estudiamos funciones que pueden implementarse con facilidad, tanto en forma recursiva como iterativa. En este ejercicio presentamos un problema cuya solución recursiva demuestra la elegancia de la recursividad, y cuya solución iterativa tal vez no sea tan aparente. Las Torres de Hanoi son uno de los problemas clásicos más famosos con los que todo científico computacio- nal en ciernes tiene que lidiar. Cuenta la leyenda que en un templo del Lejano Oriente, los sacerdotes intentan mover una pila de discos dorados, de una aguja de diamante a otra (figura 6.34). La pila inicial tiene 64 discos in- sertados en una aguja y se ordenan de abajo hacia arriba, de mayor a menor tamaño. Los sacerdotes intentan mover la pila de una aguja a otra, con las restricciones de que sólo se puede mover un disco a la vez, y en ningún momen- to se puede colocar un disco más grande encima de uno más pequeño. Se cuenta con tres agujas, una de las cuales se utiliza para almacenar discos temporalmente. Se supone que el mundo acabará cuando los sacerdotes completen su tarea, por lo que hay pocos incentivos para que nosotros podamos facilitar sus esfuerzos. Vamos a suponer que los sacerdotes intentan mover los discos de la aguja 1 a la aguja 3. Deseamos desarrollar un algoritmo que imprima la secuencia precisa de transferencias de los discos de una aguja a otra. Siatacamosesteproblemaconlosmétodosconvencionales,rápidamenteterminaríamos “atados”manejando los discos sin esperanza. En vez de ello, si atacamos este problema teniendo en mente la recursividad, los pasos serán mássimples.Laaccióndemover ndiscospuedeverse entérminosdemoversólo n–1 discos (deahí larecursividad) de la siguiente forma: a) Mover n – 1 discos de la aguja 1 a la aguja 2, usando la aguja 3 como un área de almacenamiento temporal. b) Mover el último disco (el más grande) de la aguja 1 a la aguja 3. c) Mover n – 1 discos de la aguja 2 a la aguja 3, usando la aguja 1 como área de almacenamiento tem- poral.
  • 305. Ejercicios 273 aguja 1 aguja 2 aguja 3 Fig. 6.34  Las Torres de Hanoi para el caso con cuatro discos. El proceso termina cuando la última tarea implica mover n = 1 disco (es decir, el caso base). Esta tarea se logra con sólo mover el disco, sin necesidad de un área de almacenamiento temporal. Escriba un programa para resolver el problema de las Torres de Hanoi. Use una función recursiva con cuatro parámetros: a) El número de discos a mover. b) La aguja en la que están insertados estos discos en un principio. c) La aguja a la que se va a mover esta pila de discos. d) La aguja que se va a utilizar como área de almacenamiento temporal. Imprima las instrucciones precisas para mover los discos de la aguja inicial a la aguja de destino. Para mover una pila de tres discos de la aguja 1 a la aguja 3, el programa muestra la siguiente serie de movimientos: 1 → 3 (Esto significa mover un disco de la aguja 1 a la aguja 3). 1 → 2 3 → 2 1 → 3 2 → 1 2 → 3 1 → 3 6.39 (Torres de Hanoi: versión iterativa) Cualquier programa que se pueda implementar en forma recursiva se puede implementar en forma iterativa, aunque algunas veces con mayor dificultad y menor claridad. Pruebe escri- bir una versión iterativa de lasTorres de Hanoi. Si tiene éxito, compare su versión iterativa con la versión recursiva desarrollada en el ejercicio 6.38. Investigue las cuestiones relacionadas con el rendimiento, la claridad y su habili- dad de demostrar que los programas estén correctos. 6.40 (Visualización de la recursividad) Es interesante observar la recursividad “en acción”. Modifique la fun- ción factorial de la figura 6.28 para imprimir su variable local y su parámetro de llamada recursiva. Para cada lla- mada recursiva, muestre los resultados en una línea separada y agregue un nivel de sangría. Haga su máximo esfuer- zo por hacer que los resultados sean claros, interesantes y significativos. Su meta aquí es diseñar e implementar un formato de salida que ayude a una persona a comprender mejor la recursividad. Tal vez desee agregar dichas capa- cidades de visualización a otros ejemplos y ejercicios recursivos a lo largo de este libro. 6.41 (Máximo común divisor recursivo) El máximo común divisor de los enteros x y y es el entero más grande que se puede dividir entre x y y de manera uniforme. Escriba una función recursiva llamada mcd, que devuelva el máximo común divisor de x y y, definida mediante la recursividad, de la siguiente manera: si y es igual a 0, enton- ces mcd(x,y) es x; en caso contrario, mcd(x,y) es mcd(y, x % y), en donde % es el operador módulo. [Nota: para este algoritmo, x debe ser mayor que y].
  • 306. 274 Capítulo 6 Funciones y una introducción a la recursividad 6.42 (Distancia entre puntos) Escriba una función llamada distancia que calcule la distancia entre dos puntos (x1, y1) y (x2, y2). Todos los números y valores de retorno deben ser de tipo double. 6.43 ¿Qué error tiene el siguiente programa? 1 // Ejercicio 6.43: ej06_43.cpp 2 // ¿Qué error tiene este programa? 3 #include iostream 4 using namespace std; 5 6 int main() 7 { 8 int c = 0; 9 10 if ( ( c = cin.get() ) != EOF ) 11 { 12 main(); 13 cout c; 14 } // fin de if 15 } // fin de main 6.44 ¿Qué hace el siguiente programa? 1 // Ejercicio 6.44: ej06_44.cpp 2 // ¿Qué hace este programa? 3 #include iostream 4 using namespace std; 5 6 int misterio( int, int ); // prototipo de función 7 8 int main() 9 { 10 int x = 0; 11 int y = 0; 12 13 cout Escriba dos enteros: ; 14 cin x y; 15 cout El resultado es misterio( x, y ) endl; 16 } // fin de main 17 18 // el parámetro b debe ser un entero positivo para prevenir la recursividad infinita 19 int misterio( int a, int b ) 20 { 21 if ( 1 == b ) // caso base 22 return a; 23 else // paso recursivo 24 return a + misterio( a, b - 1 ); 25 } // fin de la función misterio 6.45 Una vez que determine qué es lo que hace el programa del ejercicio 6.44, modifíquelo para que funcione de manera apropiada, después de eliminar la restricción de que el segundo argumento no debe ser negativo. 6.46 (Funciones matemáticas de la biblioteca) Escriba un programa que evalúe todas, si es posible, las funcio- nes matemáticas de la biblioteca en la figura 6.2. Ejercite cada una de estas funciones, haciendo que su programa imprima tablas de valores de retornos para una diversidad de valores de los argumentos. 6.47 (Encuentre el error) Encuentre el error en cada uno de los siguientes segmentos de programa, y explique cómo corregirlo:
  • 307. Ejercicios 275 a) float cubo( float ); // prototipo de función cubo( float numero ) // definición de función { return numero * numero * numero; } b) int numeroAleatorio = srand(); c) float y = 123.45678; int x; x = y; cout static_cast float ( x ) endl; d) double cuadrado( double number ) { double numero = 0; return numero * numero; } e) int suma( int n ) { if ( 0 == n ) return 0; else return n + suma( n ); } (Modificación del juego de Craps) Modifique el programa Craps de la figura 6.11 para permitir apuestas. Empaquete como función la parte del programa que ejecuta un juego de craps. Inicialice la variable saldoBanco con 1000 dólares. Pida al jugador que introduzca una apuesta. Use un ciclo while para comprobar que esa apuesta sea menor o igual al saldoBanco y, si no lo es, haga que el usuario vuelva a introducir la apuesta hasta que se introduzca un valor válido. Después de esto, comience un juego de craps. Si el jugador gana, agregue la apuesta al saldoBanco e imprima el nuevo saldoBanco. Si el jugador pierde, reste la apuesta al saldoBanco, imprima el nuevo saldoBanco, compruebe si saldoBanco se ha vuelto cero y, de ser así, imprima el mensaje Lo siento. Se quedo sin fondos!. A medida que el juego progrese, imprima varios mensajes para crear algo de “charla”, como Oh, se esta yendo a la quiebra, verdad?, o Oh, vamos, arriesguese!, o La hizo en grande. Ahora es tiempo de cambiar sus fichas por efectivo!. 6.48 (Área de círculo) Escriba un programa en C++ que pida al usuario el radio de un círculo y después llame a la función inline areaCirculo para calcular el área de ese círculo. 6.49 (Paso por valor y paso por referencia) Escriba un programa completo en C++ con las dos funciones alter- nativas que se especifican a continuación, de las cuales cada una simplemente triplica la variable cuenta definida en main. Después compare y contraste ambos métodos. Estas dos funciones son: a) la función triplicarPorValor, que pasa una copia de cuenta por valor, triplica la copia y devuelve el nuevo valor, y b) la función triplicarPorReferencia, que pasa cuenta por referencia a través de un parámetro por referencia y triplica el valor original de cuenta a través de su alias (es decir, el parámetro por refe- rencia). 6.50 ¿Cuál es el propósito del operador de resolución de ámbito unario? 6.51 (Plantilla de función minimo) Escriba un programa que use una plantilla de función llamada minimo para determinar el menor de dos argumentos. Pruebe el programa usando argumentos tipo entero, carácter y número de punto flotante.
  • 308. 276 Capítulo 6 Funciones y una introducción a la recursividad 6.52 (Plantilla de función maximo) Escriba un programa que utilice una plantilla de función llamada maximo para determinar el mayor de dos argumentos. Pruebe el programa usando argumentos tipo entero, carácter y nú- mero de punto flotante. 6.53 (Encuentreelerror) Determinesilossiguientessegmentosdeprogramacontienenerrores.Paracadaerror, explique cómo puede corregirse. [Nota: para un segmento de programa específico, es posible que no haya errores presentes en el segmento]. a) template class A int suma( int num1, int num2, int num3 ) { return num1 + num2 + num3; } b) void imprimirResultados( int x, int y ) { cout La suma es x + y 'n'; return x + y; } c) template A A producto( A num1, A num2, A num3 ) { return num1 * num2 * num3; } d) double cubo( int ); int cubo( int ); 6.54 (Números aleatorios de C++11: juego de craps modificado) Modifique el programa de la figura 6.11 para usar las nuevas herramientas de generación de números aleatorios que se muestran en la sección 6.9. 6.55 (Enumeración con alcance de C++11) Cree una enum con alcance llamada TipoCuenta que contenga las constantes llamadas AHORROS, CHEQUES e INVERSION. Hacer la diferencia A medida que disminuyen los costos de las computadoras, aumenta la posibilidad de que cada estudiante, sin importar su economía, tenga una y la utilice en la escuela. Esto crea excitantes posibilidades para mejorar la expe- riencia educativa de todos los estudiantes a nivel mundial, según lo sugieren los siguientes cinco ejercicios. [Nota: vea nuestras iniciativas, como el proyecto One Laptop Per Child (www.laptop.org). Investigue también acerca de las laptops “verdes” o ecológicas y observe las características “ecológicas” clave de estos dispositivos. Investigue también la Herramienta de evaluación ambiental de productos electrónicos (www.epeat.net), que le puede ayudar a evaluar las características “ecológicas” de las computadoras de escritorio, notebooks y monitores para poder de- cidir qué productos comprar]. 6.56 (Instrucción asistida por computadora) El uso de las computadoras en la educación se conoce como ins- trucción asistida por computadora (CAI). Escriba un programa que ayude a un estudiante de escuela primaria, para que aprenda a multiplicar. Use la función rand para producir dos enteros positivos de un dígito. El programa debe entonces mostrar una pregunta al usuario, como: ¿Cuánto es 6 por 7? El estudiante entonces debe escribir la respuesta. Luego, el programa debe verificar la respuesta del estudiante. Si es correcta, muestre el mensaje Muy bien! y haga otra pregunta de multiplicación. Si la respuesta es incorrecta, muestre el mensaje No. Por favor intenta de nuevo. y deje que el estudiante intente la misma pregunta varias veces, hasta que esté correcta. Debe utilizarse una función separada para generar cada pregunta nueva. Esta función debe llamarse una vez cuando la aplicación empiece a ejecutarse, y cada vez que el usuario responda correctamente a la pregunta.
  • 309. Hacer la diferencia 277 6.57 (Instrucción asistida por computadora: reducción de la fatiga de los estudiantes) Un problema que se de- sarrolla en los entornos CAI es la fatiga de los estudiantes. Este problema puede eliminarse si se varían las contes- taciones de la computadora para mantener la atención del estudiante. Modifique el programa del ejercicio 6.57 de manera que se muestren diversos comentarios para cada respuesta, de la siguiente manera: Posibles contestaciones a una respuesta correcta: Muy bien! Excelente! Buen trabajo! Sigue asi! Posibles contestaciones a una respuesta incorrecta: No. Por favor intenta de nuevo. Incorrecto. Intenta una vez mas. No te rindas! No. Sigue intentando. Use la generación de números aleatorios para elegir un número entre 1 y 4 que se utilice para seleccionar una de las cuatro contestaciones apropiadas a cada respuesta correcta o incorrecta. Use una instrucción switch para emitir las contestaciones. 6.58 (Instrucción asistida por computadora: supervisión del rendimiento de los estudiantes) Los sistemas de instrucción asistida por computadora más sofisticados supervisan el rendimiento del estudiante durante cierto tiempo. La decisión de empezar un nuevo tema se basa a menudo en el éxito del estudiante con los temas anterio- res. Modifique el programa del ejercicio 6.58 para contar el número de respuestas correctas e incorrectas introdu- cidas por el estudiante. Una vez que el estudiante escriba 10 respuestas, su programa debe calcular el porcentaje de respuestas correctas. Si éste es menor del 75%, imprima Por favor pida ayuda adicional a su profesor y reinicie el programa, para que otro estudiante pueda probarlo. Si el porcentaje es del 75% o mayor, muestre el mensaje Felicidades, esta listo para pasar al siguiente nivel! y luego reinicie el programa, para que otro estudiante pueda probarlo. 6.59 (Instrucción asistida por computadora: niveles de dificultad) En los ejercicios 6.57 al 6.59 se desarrolló un programa de instrucción asistida por computadora para enseñar a un estudiante de escuela primara cómo multi- plicar. Modifique el programa para que permita al usuario introducir un nivel de dificultad. Un nivel de 1 significa que el programa debe usar sólo números de un dígito en los problemas; un nivel 2 significa que el programa debe utilizar números de dos dígitos máximo, y así en lo sucesivo. 6.60 (Instrucción asistida por computadora: variación de los tipos de problemas) Modifique el programa del ejercicio 6.60 para permitir al usuario que elija el tipo de problemas aritméticos que desea estudiar. Una opción de 1 significa problemas de suma solamente, 2 significa problemas de resta, 3 significa problemas de multiplicación, 4 significa problemas de división y 5 significa una mezcla aleatoria de problemas de todos estos tipos.
  • 310. Plantillas de clase array y vector; cómo atrapar excepciones 7 Ahora ve, escríbelo ante ellos en una tabla, y anótalo en un libro. —Isaías 30:8 Comienza en el principio… y continúa hasta que llegues al final; después detente. —Lewis Carroll Ir más allá es tan malo como no llegar. —Confucio O b j e t i v o s En este capítulo aprenderá a: n Utilizar la plantilla de clase array de la Biblioteca estándar de C++: una colección de tamaño fijo de elementos de datos relacionados. n Utilizar arreglos para almacenar, ordenar y buscar datos en listas y tablas de valores. n Declarar arreglos, inicializarlos y hacer referencia a elementos individuales de los arreglos. n Usar la instrucción for basada en rango. n Pasar arreglos a las funciones. n Declarar y manipular arreglos multidimensionales. n Utilizar la plantilla de clase vector de la Biblioteca estándar de C++: una colección de tamaño variable de elementos de datos relacionados.
  • 311. 7.2 Arreglos 279 7.1Introducción En este capítulo presentamos el tema de las estructuras de datos: colecciones de elementos de datos re- lacionados. Hablaremos sobre los arreglos, que son colecciones de tamaño fijo que consisten de ele- mentos de datos del mismo tipo, y de los vectores, que son colecciones (también de elementos de datos del mismo tipo) que pueden aumentar y reducir su tamaño en forma dinámica en tiempo de ejecución. Tanto array como vector son plantillas de clase de la biblioteca estándar de C++. Para usarlas, hay que incluir los encabezados array y vector, respectivamente. Después de hablar acerca de cómo se declaran, se crean y se inicializan los arreglos, presentaremos ejemplos que demuestran varias manipulaciones comunes de ellos. Le mostraremos cómo realizar bús- quedas en los arreglos para encontrar elementos específicos, y cómo ordenar los arreglos para poner sus datos en orden. Mejoraremos la clase LibroCalificaciones mediante el uso de arreglos unidimensionales y bidi- mensionales para mantener un conjunto de calificaciones en memoria y analizar las calificaciones de dis- tintosexámenes.Introduciremoselmecanismodemanejodeexcepcionesylousaremosparaqueunprogra- ma pueda seguir ejecutándose cuando intente acceder al elemento de un vector o array que no exista. 7.2Arreglos Un arreglo es un grupo contiguo de localidades de memoria, todas ellas del mismo tipo. Para hacer refe- rencia a una localidad o elemento específico en el arreglo, especificamos su nombre y el número de posición del elemento en el arreglo. La figura 7.1 muestra un arreglo de enteros llamado c, el cual contiene 12 elementos. Para hacer referencia a cualquiera de estos elementos en un programa, se proporciona el nombre del arreglo segui- do del número de posición del elemento específico entre corchetes ([]). Al número de posición se le conoce más formalmente como el índice o subíndice (este número especifica el número de elementos a partir del inicio del arreglo). El primer elemento tiene el subíndice 0 (cero) y se conoce algunas veces como el elemento cero. Por ende, los elementos del array c son c[0] (se pronuncia como “c sub cero”), 7.1 Introducción 7.2 Arreglos 7.3 Declaración de arreglos 7.4 Ejemplos acerca del uso de los arreglos 7.4.1 Declaración de un arreglo y uso de un ciclo para inicializar los elementos del arreglo 7.4.2 Inicialización de un arreglo en una declaración mediante una lista inicializadora 7.4.3 Especificación del tamaño de un arreglo con una variable constante y establecimiento de los elementos de un arreglo con cálculos 7.4.4 Suma de los elementos de un arreglo 7.4.5 Uso de gráficos de barra para mostrar los datos de un arreglo en forma gráfica 7.4.6 Uso de los elementos de un arreglo como contadores 7.4.7 Uso de arreglos para sintetizar los resultados de una encuesta 7.4.8 Arreglos locales estáticos y arreglos locales automáticos 7.5 Instrucción for basada en rango 7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para almacenar las calificaciones 7.7 Búsqueda y ordenamiento de datos en arreglos 7.8 Arreglos multidimensionales 7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional 7.10 Introducción a la plantilla de clase vector de la Biblioteca estándar de C++ 7.11 Conclusión Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios | Ejercicios de recursividad | Hacer la diferencia
  • 312. 280 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones c[1], c[2] y así en lo sucesivo. El subíndice más alto en el arreglo c es 11, el cual es 1 menos que el nú- mero de elementos en el arreglo (12). Los nombres de los arreglos siguen las mismas convenciones que los demás nombres de variables. 0 -45 62 -3 1 6453 78 0 -89 1543 72 6 c[ 0 ] El nombre del arreglo es c Número de posición del elemento dentro del arreglo c c[ 7 ] c[ 8 ] c[ 9 ] c[ 10 ] c[ 11 ] c[ 6 ] c[ 5 ] c[ 4 ] c[ 3 ] c[ 2 ] c[ 1 ] Valor Nombre de un elemento individual del arreglo Fig. 7.1  Un arreglo con 12 elementos. Un subíndice debe ser un entero o una expresión entera (usando cualquier tipo integral). Si un programa utiliza una expresión como un subíndice, entonces el programa evalúa la expresión para de- terminar el subíndice. Por ejemplo, si suponemos que la variable a es igual a 5 y que la variable b es igual a 6, entonces la instrucción c[ a + b ] += 2; suma 2 al elemento c[11] del arreglo. El nombre del arreglo con subíndice es un lvalue: se puede utilizar en el lado izquierdo de una asignación, de igual forma que los nombres de las variables que no son arreglos. Examinaremoselarregloc delafigura7.1conmásdetalle.Elnombredelarreglocompletoesc.Cada arreglo conoce su propio tamaño, que puede determinarse mediante una llamada a su función miembro size, como en c.size(). La manera en que se hace referencia a los 12 elementos de este arreglo es de c[0] a c[11]. El valor de c[0] es -45, el valor de c[7] es 62 y el valor de c[11] es 78. Para imprimir la suma de los valores contenidos en los primeros tres elementos del arreglo c, escribiríamos lo siguiente: cout c[ 0 ] + c[ 1 ] + c[ 2 ] endl; Para dividir el valor de c[6] entre 2 y asignar el resultado a la variable x, escribiríamos lo siguiente: x = c[ 6 ] / 2; Error común de programación 7.1 Observe la diferencia entre el “séptimo elemento del arreglo” y el “elemento 7 del arreglo”. Los subíndices de los arreglos empiezan en 0, por lo que el “séptimo elemento del arreglo” tiene un subíndice de 6, mientras que el “elemento 7 del arreglo” tiene un subíndice de 7 y es en realidad el octavo elemento del arreglo. Por desgracia, esta distinción genera con fre- cuencia errores de desplazamiento en 1. Para evitar dichos errores, nos referimos explí- citamente a los elementos específicos de un arreglo por medio del nombre del arreglo y el número de subíndice (por ejemplo, c[6] o c[7]). Los corchetes que se utilizan para encerrar el subíndice de un arreglo son en realidad un operador que tiene la misma precedencia que los paréntesis. En la figura 7.2 se muestran la precedencia y la aso-
  • 313. 7.4 Ejemplos acerca del uso de los arreglos 281 ciatividad de los operadores introducidos hasta ahora. Los operadores se muestran de arriba hacia abajo, en orden descendente de precedencia, con su asociatividad y su tipo. Operadores Asociatividad Tipo :: () izquierdaaderecha [Vealaprecauciónenlafigura2.10 conrespectoalagrupamientodeparéntesis]. primario () [] ++ -- static_casttipo(operando) izquierda a derecha postfijo ++ -- + - ! derecha a izquierda unario (prefijo) * / % izquierda a derecha multiplicativo + - izquierda a derecha aditivo izquierda a derecha inserción/extracción = = izquierda a derecha relacional == != izquierda a derecha igualdad izquierda a derecha AND lógico || izquierda a derecha OR lógico ?: derecha a izquierda condicional = += -= *= /= %= derecha a izquierda asignación , izquierda a derecha coma Fig. 7.2  Precedencia y asociatividad de los operadores introducidos hasta ahora. 7.3Declaración de arreglos Los arreglos (objetos array) ocupan espacio en memoria. Para especificar el tipo de los elementos y el número de elementos requerido por un arreglo, use una declaración de la forma: array tipo, tamañoArreglo nombreArreglo; La notación tipo, tamañoArreglo indica que el array es una plantilla de clase. El compilador reserva la cantidad apropiada de memoria con base en el tipo de los elementos y el tamañoArreglo. (Recuerde que una declaración que reserva memoria se conoce en forma más apropiada como definición). El tamaño- Arreglo debe ser un entero sin signo. Para indicar al compilador que debe reservar 12 elementos para el arreglo c de enteros, use la siguiente declaración: array int, 12 c; // c es un arreglo de 12 valores enteros Se pueden declarar arreglos para contener valores de la mayoría de los tipos de datos. Por ejemplo, es posible usar un arreglo de tipo string para almacenar cadenas de caracteres. 7.4Ejemplos acerca del uso de los arreglos Los siguientes ejemplos demuestran cómo declarar, inicializar y manipular arreglos. 7.4.1Declaración de un arreglo y uso de un ciclo para inicializar los elementos del arreglo El programa de la figura 7.3 declara el arreglo n con cinco elementos enteros (línea 10). La línea 5 inclu- ye el encabezado array, que contiene la definición de la plantilla de clase array. En las líneas 13 y 14
  • 314. 282 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones se utiliza una instrucción for para inicializar los elementos del arreglo con cero. Al igual que otras varia- bles automáticas, los arreglos automáticos no se inicializan de manera implícita con cero, aunque los arreglos static sí. La primera instrucción de salida (línea 16) muestra los encabezados de columna para las columnas impresas en la instrucción for subsiguiente (líneas 19 y 20), la cual imprime el arreglo en formato tabular. Recuerde que setw especifica la anchura de campo en la que sólo se va a imprimir el si- guiente valor. 1 // Fig. 7.3: fig07_03.cpp 2 // Inicialización de los elementos de un arreglo con ceros, e impresión del arreglo. 3 #include iostream 4 #include iomanip 5 #include array 6 using namespace std; 7 8 int main() 9 { 10 array int, 5 n; // n es un arreglo de 5 valores int 11 12 // inicializa los elementos del arreglo n con 0 13 for ( size_t i = 0; i n.size(); ++i ) 14 n[ i ] = 0; // establece el elemento en la ubicación i a 0 15 16 cout Elemento setw( 13 ) Valor endl; 17 18 // imprime el valor de cada elemento del arreglo 19 for ( size_t j = 0; j n.size(); ++j ) 20 cout setw( 7 ) j setw( 13 ) n[ j ] endl; 21 } // fin de main Elemento Valor 0 0 1 0 2 0 3 0 4 0 Fig. 7.3  Inicialización de los elementos de un arreglo con ceros, e impresión del arreglo. En este programa, las variables de control i (línea 13) y j (línea 19) que especifican los subíndices del arreglo se declaran como de tipo size_t. De acuerdo con el C++ estándar, size_t representa un tipo integralsinsigno.Estetiposerecomiendaparacualquiervariablequerepresentaeltamañodeunarreglo o los subíndices de éste. El tipo size_t está definido en el espacio nombre std y se encuentran en el encabezado cstddef, que se incluye a través de muchos otros encabezados. Si intenta compilar un programa que utilice el tipo size_t y recibe errores que indiquen que no está definido, sólo tiene que incluir cstddef en su programa. 7.4.2Inicialización de un arreglo en una declaración mediante una lista inicializadora Los elementos de un arreglo también se pueden inicializar en la declaración del arreglo, para lo cual colocamos después del nombre del arreglo un signo igual y entre llaves, una lista de inicializadores se- parados por comas. El programa de la figura 7.4 utiliza una lista inicializadora para inicializar un arreglo de enteros con cinco valores (línea 11) y lo imprime en formato tabular (líneas 13 a 17).
  • 315. 7.4 Ejemplos acerca del uso de los arreglos 283 1 // Fig. 7.4: fig07_04.cpp 2 // Inicialización de un arreglo en una declaración. 3 #include iostream 4 #include iomanip 5 #include array 6 using namespace std; 7 8 int main() 9 { 10 // usa la lista inicializadora para inicializar el arreglo n 11 array int, 5 n = { 32, 27, 64, 18, 95 }; 12 13 cout Elemento setw( 13 ) Valor endl; 14 15 // imprime el valor de cada elemento del arreglo 16 for ( size_t i = 0; i n.size(); ++i ) 17 cout setw( 7 ) i setw( 13 ) n[ i ] endl; 18 } // fin de main Elemento Valor 0 32 1 27 2 64 3 18 4 95 Fig. 7.4  Inicialización de un arreglo en una declaración. Si hay menos inicializadores que elementos en el arreglo, el resto de los elementos del arreglo se inicializan con cero. Por ejemplo, los elementos del arreglo n en la figura 7.3 podrían haberse inicializa- do en ceros con la declaración array int, 5 n = {}; // inicializa los elementos del arreglo n con 0 la declaración inicializa los elementos con cero, ya que hay menos inicializadores (ninguno en este caso) que elementos en el arreglo. Esta técnica sólo se puede utilizar en la declaración del arreglo, mientras que la técnica de inicialización que se muestra en la figura 7.3 se puede utilizar de manera repetida durante la ejecución del programa, para “reinicializar” los elementos de un arreglo. Si se especifican el tamaño del arreglo y una lista inicializadora en la declaración de un arreglo, el número de inicializadores debe ser menor o igual que el tamaño del arreglo. La declaración del arreglo array int, 5 n = { 32, 27, 64, 18, 95, 14 }; produce un error de compilación, ya que hay seis inicializadores y sólo cinco elementos en el arreglo. 7.4.3Especificación del tamaño de un arreglo con una variable constante y establecimiento de los elementos de un arreglo con cálculos En la figura 7.5 se establecen los cinco elementos de un arreglo s con los enteros pares 2, 4, 6, 8 y 10 (líneas 15 y 16), y se imprime el arreglo en formato tabular (líneas 18 a 22). Para generar estos núme- ros (línea 16), se multiplica cada valor sucesivo del contador de ciclo por 2, y se le suma 2. En la línea 11 se utiliza el calificador const para declarar lo que se conoce como una variable constante llamada tamanioArreglo con el valor 5. Una variable constante que se utiliza para especificar el tamaño de un arreglo debe inicializarse con una expresión constante cuando se declara y no puede
  • 316. 284 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones 1 // Fig. 7.5: fig07_05.cpp 2 // Establece el arreglo s con los enteros pares del 2 al 10. 3 #include iostream 4 #include iomanip 5 #include array 6 using namespace std; 7 8 int main() 9 { 10 // la variable constante se puede usar para especificar el tamaño de los arreglos 11 const size_t tamanioArreglo = 5; // debe inicializarse en la declaración 12 13 array int, tamanioArreglo s; // el arreglo s tiene 5 elementos 14 15 for ( size_t i = 0; i s.size(); ++i ) // establece los valores 16 s[ i ] = 2 + 2 * i; 17 18 cout Elemento setw( 13 ) Valor endl; 19 20 // imprime el contenido del arreglo s en formato tabular 21 for ( size_t j = 0; j s.size(); ++j ) 22 cout setw( 7 ) j setw( 13 ) s[ j ] endl; 23 } // fin de main Elemento Valor 0 2 1 4 2 6 3 8 4 10 Fig. 7.5  Establece el arreglo s con los enteros pares del 2 al 10. modificarse de ahí en adelante (como se muestra en las figuras 7.6 y 7.7). Estas variables también se conocen como constantes con nombre o variables de sólo lectura. Error común de programación 7.2 Si no se inicializa una variable constante al momento de declararla, se produce un error de compilación. Error común de programación 7.3 Asignar un valor a una variable constante en una instrucción ejecutable es un error de compilación. 1 // Fig. 7.6: fig07_06.cpp 2 // Uso de una variable constante inicializada en forma apropiada. 3 #include iostream 4 using namespace std; 5 6 int main() 7 { Fig. 7.6  Uso de una variable constante inicializada en forma apropiada (parte 1 de 2).
  • 317. 7.4 Ejemplos acerca del uso de los arreglos 285 8 const int x = 7; // variable constante inicializada 9 10 cout El valor de la variable constante x es: x endl; 11 } // fin de main El valor de la variable constante x es: 7 1 // Fig. 7.7: fig07_07.cpp 2 // Una variable const debe inicializarse. 3 4 int main() 5 { 6 const int x; // Error: x debe inicializarse 7 8 x = 7; // Error: no se puede modificar una variable const 9 } // fin de main Mensaje de error del compilador de Microsoft Visual C++: error C2734: 'x' : const object must be initialized if not extern error C3892: 'x' : you cannot assign to a variable that is const Mensaje de error del compilador GNU C++: fig07_07.cpp:6:14: error: uninitialized const 'x' [-fpermissive] fig07_07.cpp:8:8: error: assignment of read-only variable 'x' Mensaje de error del compilador LLVM: Default initialization of an object of const type 'const int' Fig. 7.7  Una variable const debe inicializarse. En la figura 7.7, el error de compilación producido por Microsoft Visual C++ se refiere a la variable int x como un “objeto const”. El estándar de C++ define a un “objeto” como una “región de almace- namiento”. Al igual que los objetos de las clases, las variables de tipo fundamental también ocupan es- pacio en memoria, por lo que se conocen comúnmente como “objetos”. Las variables constantes se pueden colocar en cualquier parte en la que se espera una expresión constante. En la figura 7.5, la variable constante tamanioArreglo especifica el tamaño del arreglo s en la línea 13. Buena práctica de programación 7.1 Definir el tamaño de un arreglo como una variable constante, en vez de una constante lite- ral, hace a los programas más claros. Esta técnica elimina lo que se conoce como números mágicos: valores numéricos que no se explican. Al usar una variable constante podemos proporcionar un nombre para una constante literal; esto ayuda a explicar el propósito del valor en el programa. Fig. 7.6  Uso de una variable constante inicializada en forma apropiada (parte 2 de 2).
  • 318. 286 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones 7.4.4Suma de los elementos de un arreglo A menudo, los elementos de un arreglo representan una serie de valores para ser utilizados en un cálcu- lo. Por ejemplo, si los elementos de un arreglo representan calificaciones de un examen, tal vez un pro- fesor desea obtener el total de los elementos del arreglo y usar esa suma para calcular el promedio de la clase para el examen. El programa en la figura 7.8 suma los valores contenidos en el arreglo a de cuatro elementos enteros. El programa declara, crea e inicializa el arreglo en la línea 10. La instrucción for (líneas 14 y 15) realiza los cálculos. Los valores que se suministran como inicializadores para el arreglo a también se podrían haber pedido, en el programa, al usuario mediante el teclado, o mediante un archivo en el disco (vea el capítulo 14, Procesamiento de archivos). Por ejemplo, la instrucción for for ( size_t j = 0; j a.size(); ++j ) cin a[ j ]; lee un valor a la vez del teclado y lo almacena en el elemento a[j]. 1 // Fig. 7.8: fig07_08.cpp 2 // Cálculo de la suma de los elementos de un arreglo. 3 #include iostream 4 #include array 5 using namespace std; 6 7 int main() 8 { 9 const size_t tamanioArreglo = 4; // especifica el tamaño del arreglo 10 array int, tamanioArreglo a = { 10, 20, 30, 40 }; 11 int total = 0; 12 13 // suma el contenido del arreglo a 14 for ( size_t i = 0; i a.size(); ++i ) 15 total += a[ i ]; 16 17 cout Total de elementos del arreglo: total endl; 18 } // fin de main Total de elementos del arreglo: 100 Fig. 7.8  Cálculo de la suma de los elementos de un arreglo. 7.4.5Uso de gráficos de barra para mostrar los datos de un arreglo en forma gráfica Muchos programas presentan datos a los usuarios en forma gráfica. Por ejemplo, los valores numéricos se muestran comúnmente como barras en un gráfico de barras. En dicho gráfico, las barras más extensas representan valores numéricos proporcionalmente más grandes. Una manera simple de mostrar datos numéricos en forma gráfica es mediante un gráfico de barras que muestra cada valor numérico como una barra de asteriscos (*). A menudo, a los profesores les gusta examinar la distribución de calificaciones en un examen. Un profesor podría graficar el número de calificaciones en cada una de varias categorías, para visualizar la distribución de calificaciones. Suponga que las calificaciones fueron 87, 68, 94, 100, 83, 78, 85, 91, 76 y 87. Hubo una calificación de 100, dos calificaciones entre 90 y 99, cuatro entre 80 y 89, dos entre 70 y 79, una calificación entre 60 y 69, y ninguna menor a 60. Nuestro siguiente programa (figura 7.9) al- macena estos datos en un arreglo de 11 elementos, cada uno de los cuales corresponde a una categoría de calificaciones. Por ejemplo, n[0] indica el número de calificaciones en el rango de 0 a 9, n[7] indica
  • 319. 7.4 Ejemplos acerca del uso de los arreglos 287 1 // Fig. 7.9: fig07_09.cpp 2 // Programa para imprimir gráficos de barra. 3 #include iostream 4 #include iomanip 5 #include array 6 using namespace std; 7 8 int main() 9 { 10 const size_t tamanioArreglo = 11; 11 array unsigned int, tamanioArreglo n = 12 { 0, 0, 0, 0, 0, 0, 1, 2, 4, 2, 1 }; 13 14 cout Distribucion de calificaciones: endl; 15 16 // para cada elemento del arreglo n, imprime una barra del gráfico 17 for ( size_t i = 0; i n.size(); ++i ) 18 { 19 // imprime etiquetas de las barras (0-9:, ..., 90-99:, 100: ) 20 if ( 0 == i ) 21 cout 0-9: ; 22 else if ( 10 == i ) 23 cout 100: ; 24 else 25 cout i * 10 “-” ( i * 10 ) + 9 : ; 26 27 // imprime barra de asteriscos 28 for ( unsigned int estrellas = 0; estrellas n[ i ]; ++estrellas ) 29 cout '*'; 30 31 cout endl; // inicia una nueva línea de salida 32 } // fin de for externo 33 } // fin de main Distribución de calificaciones: 0-9: 10-19: 20-29: 30-39: 40-49: 50-59: 60-69: * 70-79: ** 80-89: **** 90-99: ** 100: * Fig. 7.9  Programa para imprimir gráficos de barra. el número de calificaciones en el rango de 70 a 79 y n[10] indica el número de calificaciones de 100. Las versiones de la clase LibroCalificaciones de las figuras 7.15 y 7.16, y de las figuras 7.22 y 7.23, con- tienen código que calcula estas frecuencias de calificaciones, con base en un conjunto de calificaciones. Por ahora crearemos el arreglo en forma manual, analizando el conjunto de calificaciones. Elprograma lee losnúmerosdelarregloygraficalainformacióncomoungráficodebarras,mostran- do cada rango de calificaciones seguido de una barra de asteriscos, los cuales indican el número de califi- caciones en ese rango. Para etiquetar cada barra, en las líneas 20 a 25 se imprime un rango de calificacio- nes (por ejemplo, 70-79: ) con base en el valor actual de la variable contador i. La instrucción for
  • 320. 288 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones anidada (líneas 28 y 29) imprime las barras. Observe la condición de continuación de ciclo en la línea 28 (estrellas n[i]). Cada vez que el programa llega al for interior, el ciclo cuenta desde 0 hasta n[i], con lo cual usa un valor en el arreglo n para determinar el número de asteriscos a mostrar. En este ejemplo, n[0] – n[5] contiene ceros, ya que ningún estudiante recibió una califica- ción menor a 60. Por ende, el programa no muestra asteriscos enseguida de los primeros seis rangos de calificaciones. 7.4.6Uso de los elementos de un arreglo como contadores Algunas veces, los programas usan variables contadores para sintetizar datos, como los resultados de una encuesta. En la figura 6.9, utilizamos contadores separados en nuestro programa para tirar dados, para rastrear el número de ocurrencias de cada lado de un dado, a medida que el programa tiraba el dado 6000000veces.Enlafigura7.10semuestraunaversióndeesteprograma,enlaqueseutilizaunarreglo. Esta versión también utiliza las nuevas herramientas de generación de números aleatorios de C++11 que se introdujeron en la sección 6.9. La figura 7.10 utiliza el arreglo frecuencia (línea 18) para contar las ocurrencias de cada lado del dado. La instrucción individual en la línea 22 de este programa reemplaza a la instrucción switch en las líneas 23 a 45 de la figura 6.9. En la línea 22 se utiliza un valor aleatorio para determinar cuál elemento de frecuencia incrementar durante cada iteración del ciclo. El cálculo en la línea 22 produce un subín- dice aleatorio de 1 a 6, por lo que el arreglo frecuencia debe ser lo suficientemente grande como para almacenar seis contadores. Sin embargo, usamos un arreglo de siete elementos en el que ignoramos 1 // Fig. 7.10: fig07_10.cpp 2 // Programa para tirar dados que utiliza un arreglo en vez de una instrucción switch. 3 #include iostream 4 #include iomanip 5 #include array 6 #include random 7 #include ctime 8 using namespace std; 9 10 int main() 11 { 12 // usa el motor predeterminado de generación de números aleatorios para 13 // producir valores int seudoaleatorios distribuidos de manera uniforme de 1 a 6 14 default_random_engine motor( static_cast unsigned int ( time(0) ) ); 15 uniform_int_distribution unsigned int intAleatorio( 1, 6 ); 16 17 const size_t tamanioArreglo = 7; // ignora el elemento cero 18 array unsigned int, tamanioArreglo frecuencia = {}; // inicializa con ceros 19 20 // tira el dado 6 000 000 de veces; usa el valor del dado como índice de frecuencia 21 for ( unsigned int tiro = 1; tiro = 6000000; ++tiro ) 22 ++frecuencia[ intAleatorio( motor ) ]; 23 24 cout Cara setw( 13 ) Frecuencia endl; 25 26 // imprime el valor de cada elemento del arreglo 27 for ( size_t cara = 1; cara frecuencia.size(); ++cara ) 28 cout setw( 4 ) cara setw( 13 ) frecuencia[ cara ] 29 endl; 30 } // fin de main Fig. 7.10  Programa para tirar dados que utiliza un arreglo en vez de una instrucción switch (parte 1 de 2).
  • 321. 7.4 Ejemplos acerca del uso de los arreglos 289 Cara Frecuencia 1 1000167 2 1000149 3 1000152 4 998748 5 999626 6 1001158 frecuencia[0]; es más lógico hacer que la cara del dado con el valor 1 incremente a frecuencia[1] que a frecuencia[0]. Por ende, el valor de cada cara se utiliza como subíndice para el arreglo frecuen- cia.También reemplazamos las líneas 49 a 54 de la figura 6.9, iterando a través del arreglo frecuencia para imprimir los resultados (figura 7.10, líneas 27 a 29). 7.4.7Uso de arreglos para sintetizar los resultados de una encuesta Nuestro siguiente ejemplo utiliza arreglos para sintetizar los resultados de los datos recolectados en una encuesta. Considere el siguiente enunciado del problema: Se pidió a veinte estudiantes que calificaran la calidad de la comida en la cafetería estudiantil, en una escala del 1 al 5, en donde 1 significa “pésimo” y 5 significa “excelente”. Coloque las 20 respuestas en un arreglo entero y determine la frecuencia de cada calificación. Ésta es un tipo popular de aplicación de procesamiento de arreglos (figura 7.11). Deseamos resumir el número de respuestas de cada tipo (es decir, del 1 al 5). El arreglo respuestas (líneas 15 a 16) es un arreglo entero de 20 elementos, y contiene las respuestas de los estudiantes a la encuesta. El arreglo respuestas se declara como const, ya que sus valores no cambian (y no deben hacerlo). Utilizamos un arreglo de seis elementos llamado frecuencia (línea 19) para contar el número de ocurrencias de cada respuesta. Cada elemento del arreglo se utiliza como un contador para una de las respuestas de la encues- ta, y se inicializa con cero. Al igual que en la figura 7.10, ignoramos frecuencia[0]. 1 // Fig. 7.11: fig07_11.cpp 2 // Programa para analizar encuestas. 3 #include iostream 4 #include iomanip 5 #include array 6 using namespace std; 7 8 int main() 9 { 10 // define los tamaños de los arreglos 11 const size_t tamanioRespuesta = 20; // tamaño del arreglo respuestas 12 const size_t tamanioFrecuencia = 6; // tamaño del arreglo frecuencia 13 14 // coloca las respuestas de la encuesta en el arreglo respuestas 15 const array unsigned int, tamanioRespuesta respuestas = 16 { 1, 2, 5, 4, 3, 5, 2, 1, 3, 1, 4, 3, 3, 3, 2, 3, 3, 2, 2, 5 }; Fig. 7.10  Programa para tirar dados que utiliza un arreglo en vez de una instrucción switch (parte 2 de 2). Fig. 7.11  Programa para analizar encuestas (parte 1 de 2).
  • 322. 290 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones 17 18 // inicializa los contadores de frecuencia con 0 19 array unsigned int, tamanioFrecuencia frecuencia = {}; 20 21 // para cada respuesta, selecciona el elemento de respuestas y usa ese valor 22 // acomo subíndice de frecuencia para determinar el elemento a incrementar 23 for ( size_t respuesta = 0; respuesta respuestas.size(); ++respuesta ) 24 ++frecuencia[ respuestas[ respuesta ] ]; 25 26 cout Calificacion setw( 17 ) Frecuencia endl; 27 28 // imprime el valor de cada elemento del arreglo 29 for ( size_t calificacion = 1; calificacion frecuencia.size(); ++calificacion ) 30 cout setw( 6 ) calificacion setw( 17 ) frecuencia[ calificacion ] 31 endl; 32 } // fin de main Calificacion Frecuencia 1 3 2 5 3 7 4 2 5 3 La primera instrucción for (líneas 23 y 24) recibe las respuestas, una a la vez, del arreglo respuestas eincrementaunodeloscincocontadoresenelarreglofrecuencia (defrecuencia[1] afrecuencia[5]). La instrucción clave en el ciclo es la línea 24, la cual incrementa el contador de frecuencia apropiado, dependiendo del valor de respuestas[respuesta]. Consideraremos varias iteraciones del ciclo for. Cuando la variable de control respuesta es 0, el valor de respuestas[respuesta] es el valor de respuestas[0] (es decir, 1 en la línea 16), por lo que el programa interpreta a ++frecuencia[respuestas[respuesta]] como ++frecuencia[ 1 ] con lo cual se incrementa el valor en el elemento 1 del arreglo. Para evaluar la expresión, empiece con el valor en el conjunto más interno de corchetes (respuesta). Una vez que conozca el valor de respuesta (que viene siendo el valor de la variable de control de ciclo en la línea 23), insértelo en la expresión y evalúe el siguiente conjunto más externo de corchetes (respuestas[respuesta], que es un valor selec- cionado del arreglo respuestas en las líneas 15 a 16). Después utilice el valor resultante como subíndi- ce del arreglo frecuencia, para especificar cuál contador se va a incrementar. Cuando respuesta es 1, respuestas[respuesta] es el valor de respuestas[1], que es 2, por lo que el programa interpreta a ++frecuencia[respuestas[respuesta]] como ++frecuencia[ 2 ] con lo cual se incrementa el elemento 2 del arreglo. Cuando respuesta es 2, respuestas[respuesta] es el valor de respuestas[2], que es 5, por lo que el programa interpreta a ++frecuencia[respuestas[respuesta]] como ++frecuencia[ 5 ] Fig. 7.11  Programa para analizar encuestas (parte 2 de 2).
  • 323. 7.4 Ejemplos acerca del uso de los arreglos 291 con lo cual se incrementa el elemento 5 del arreglo, y así en lo sucesivo. Sin importar el número de res- puestas procesadas en la encuesta, el programa sólo requiere un arreglo de seis elementos (en el cual se ignora el elemento cero) para resumir los resultados, ya que todos los valores de las respuestas se encuen- tran entre 1 y 5, y los valores de subíndice para un arreglo de seis elementos son del 0 al 5. Comprobación de límites para los subíndices de un arreglo Si los datos en el arreglo respuestas tuvieran valores inválidos, como 13, el programa trataría de sumar 1 a frecuencia[13], lo cual se encuentra fuera de los límites del arreglo. Cuando usamos el operador [] paraaccederalelementodeunarreglo,C++nocuentaconcomprobacióndelímitesautomáticadearreglospara evitar que hagamos referencia a un elemento que no existe. Por lo tanto, un programa en ejecución puede “salirse” de cualquier extremo de un arreglo sin advertencia. En la sección 7.10 demostraremos la fun- ción at de la plantilla de clase vector, que realiza la comprobación de límites por el usuario. La planti- lla de clase array también tiene una función at. Es importante asegurar que cada subíndice que utilicemos para acceder al elemento de un arreglo se encuentre dentro de los límites de ese arreglo; es decir, mayor o igual a 0 y menor que el número de elementos del arreglo. A la acción de permitir que los programas lean de, o escriban en, los elementos de un arreglo fuera de los límites, se le conoce como falla de seguridad. Si se intentan leer elementos fuera de los límites de un arreglo, el programa puede fallar o incluso tal vez parezca ejecutarse correctamente al utilizar datos inco- rrectos.Siescribimosenunelementofueradeloslímites(loqueseconocecomodesbordamientodebúfer), los datos del programa en memoria podrían corromperse, el programa podría fallar y dejar que algún atacante explote el sistema y ejecute su propio código. Para obtener más información sobre desborda- mientos de búfer, consulte en.wikipedia.org/wiki/Buffer_overflow (http://es.wikipedia.org/ wiki/Desbordamiento_de_b%C3%BAfer en español). Error común de programación 7.4 Hacer referencia a un elemento fuera de los límites del arreglo es un error lógico en tiem- po de ejecución. No es un error de sintaxis. Tip para prevenir errores 7.1 Al iterar a través de un arreglo, el subíndice del arreglo debe ser mayor o igual a 0 y siempre debe ser menor que el número total de elementos en el arreglo (uno menos que el tamaño del arreglo). Asegúrese que la condición de terminación de ciclo evite acceder a los elementos fuera de este rango. En los capítulos 15 y 16 aprenderá sobre los iteradores, que pueden ayudar a evitar el acceso a los elementos fuera de los límites de un arreglo (o de otro contenedor). 7.4.8Arreglos locales estáticos y arreglos locales automáticos En el capítulo 6 hablamos sobre el especificador de clase de almacenamiento static. Una variable local static en la definición de una función existe durante todo el programa, pero sólo puede verse en el cuerpo de la función. Tip de rendimiento 7.1 Podemos aplicar static a la declaración de un arreglo local, de manera que el arreglo no se cree e inicialice cada vez que el programa llame a la función, y no se destruya cada vez que termine la función en el programa. Esto puede mejorar el rendimiento, en especial cuando se utilizan arreglos extensos. Un programa inicializa los arreglos locales static la primera vez que encuentra sus declaraciones. Si el programador no inicializa un arreglo static de manera explícita, el compilador inicializa con cero cada elemento de ese arreglo al momento de su creación. Recuerde que C++ no realiza dicha inicializa- ción predeterminada para las variables automáticas.
  • 324. 292 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones La figura 7.12 demuestra la función inicArregloStatic (líneas 24 a 40) con un arreglo local static (línea 27) y la función inicArregloAutomatico (líneas 43 a 59) con un arreglo local automáti- co (línea 46). 1 // Fig. 7.12: fig07_12.cpp 2 // Inicialización de un arreglo static e inicialización de un arreglo automático. 3 #include iostream 4 #include array 5 using namespace std; 6 7 void inicArregloStatic(); // prototipo de función 8 void inicArregloAutomatico(); // prototipo de función 9 const size_t tamanioArreglo = 3; 10 11 int main() 12 { 13 cout Primera llamada a cada funcion:n; 14 inicArregloStatic(); 15 inicArregloAutomatico(); 16 17 cout nnSegunda llamada a cada funcion:n; 18 inicArregloStatic(); 19 inicArregloAutomatico(); 20 cout endl; 21 } // fin de main 22 23 // función para demostrar un arreglo local static 24 void inicArregloStatic( void ) 25 { 26 // inicializa los elementos con 0 la primera vez que se llama a la función 27 static array int, tamanioArreglo arreglo1; // arreglo local static 28 29 cout nValores al entrar en inicArregloStatic:n; 30 31 // imprime el contenido de arreglo1 32 for ( size_t i = 0; i arreglo1.size(); ++i ) 33 cout arreglo1[ i ] = arreglo1[ i ] ; 34 35 cout nValores al salir de inicArregloStatic:n; 36 37 // modifica e imprime el contenido de arreglo1 38 for ( size_t j = 0; j arreglo1.size(); ++j ) 39 cout arreglo1[ j ] = ( arreglo1[ j ] += 5 ) ; 40 } // fin de la función inicArregloStatic 41 42 // función para demostrar un arreglo local automático 43 void inicArregloAutomatico( void ) 44 { 45 // inicializa los elementos cada vez que se llama a la función 46 array int, tamanioArreglo arreglo2 = { 1, 2, 3 }; // arreglo local automático 47 48 cout nnValores al entrar a inicArregloAutomatico:n; Fig. 7.12  Inicialización de un arreglo static e inicialización de un arreglo automático (parte 1 de 2).
  • 325. 7.5 Instrucción for basada en rango 293 49 50 // imprime el contenido de arreglo2 51 for ( size_t i = 0; i arreglo2.size(); ++i ) 52 cout arreglo2[ i ] = arreglo2[ i ] ; 53 54 cout nValores al salir de inicArregloAutomatico:n; 55 56 // modifica e imprime el contenido de arreglo2 57 for ( size_t j = 0; j arreglo2.size(); ++j ) 58 cout arreglo2[ j ] = ( arreglo2[ j ] += 5 ) ; 59 } // fin de la función inicArregloAutomatico Primera llamada a cada funcion: Valores al entrar en inicArregloStatic: arreglo1[0] = 0 arreglo1[1] = 0 arreglo1[2] = 0 Valores al salir de inicArregloStatic: arreglo1[0] = 5 arreglo1[1] = 5 arreglo1[2] = 5 Valores al entrar a inicArregloStatic: arreglo2[0] = 1 arreglo2[1] = 2 arreglo2[2] = 3 Valores al salir de inicArregloStatic: arreglo2[0] = 6 arreglo2[1] = 7 arreglo2[2] = 8 Segunda llamada a cada funcion: Valores al entrar en inicArregloStatic: arreglo1[0] = 5 arreglo1[1] = 5 arreglo1[2] = 5 Valores al salir de inicArregloStatic: arreglo1[0] = 10 arreglo1[1] = 10 arreglo1[2] = 10 Valores al entrar a inicArregloAutomatico: arreglo2[0] = 1 arreglo2[1] = 2 arreglo2[2] = 3 Valores al salir de inicArregloAutomatico: arreglo2[0] = 6 arreglo2[1] = 7 arreglo2[2] = 8 La función inicArregloStatic se llama dos veces (líneas 14 y 18). El compilador inicializa el arreglo local static arreglo1 con cero la primera vez que se hace una llamada a la función. Ésta impri- me el arreglo, suma 5 a cada elemento e imprime el arreglo de nuevo. La segunda vez que se llama a la función, el arreglo static contiene los valores modificados que se almacenan durante la primera llama- da a la función. La función inicArregloAutomatico también se llama dos veces (líneas 15 y 19). Los elementos del arreglo local automático arreglo2 se inicializan (línea 46) con los valores 1, 2 y 3. La función imprime el arreglo, suma 5 a cada elemento e imprime el arreglo de nuevo. La segunda vez que se llama a la función, los elementos del arreglo se reinicializan con 1, 2 y 3. El arreglo tiene una duración de almacenamiento automática, por lo que se vuelve a crear y se reinicializa durante cada llamada a inicArregloAutomatico. 7.5Instrucción for basada en rango Como hemos visto, es común procesar todos los elementos de un arreglo. La nueva instrucción for basada en rango de C++11 nos permite hacerlo sin usar un contador, con lo que evitamos la posibilidad de “salirnos” del arreglo y eliminamos la necesidad de implementar nuestra propia comprobación de límites. Fig. 7.12  Inicialización de un arreglo static e inicialización de un arreglo automático (parte 2 de 2).
  • 326. 294 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones Tip para prevenir errores 7.2 Al procesar todos los elementos de un arreglo, si no necesita acceso al subíndice de los ele- mentos del mismo, use la instrucción for basada en rango. La sintaxis de la instrucción for basada en rango es: for ( declaracionVariableRango : expresión ) instrucción en donde declaracionVariableRango tiene un tipo y un identificador (como int elemento), y expresión es el arreglo a través del cual se va a iterar. El tipo en la declaracionVariableRango debe ser consistente con el tipo de los elementos del arreglo. El identificador representa los valores sucesivos de los elementos del arregloeniteracionessucesivasdelciclo.Podemosusarlainstrucciónfor basadaenrangoconlamayoría delasestructurasdedatospreconstruidasdelaBibliotecaestándardeC++(queseconocencomúnmente como contenedores), incluyendo las clases array y vector. Lafigura7.13usalainstrucciónfor basadaenrangoparamostrarelcontenidodeunarreglo(líneas 13 y 14; líneas 22 y 23) y para multiplicar cada uno de los valores de los elementos del arreglo por 2 (líneas 17 y 18). 1 // Fig. 7.13: fig07_13.cpp 2 // Uso de la instrucción for basada en rango para multiplicar los elementos de un arreglo por 2. 3 #include iostream 4 #include array 5 using namespace std; 6 7 int main() 8 { 9 array int, 5 items = { 1, 2, 3, 4, 5 }; 10 11 // muestra los items antes de modificarlos 12 cout items antes de modificarlos: ; 13 for ( int item : items ) 14 cout item ; 15 16 // multiplica los elementos de los ítems por 2 17 for ( int refItem : items ) 18 refItem *= 2; 19 20 // muestra los ítems después de modificarlos 21 cout nitems despues de modificarlos: ; 22 for ( int item : items ) 23 cout item ; 24 25 cout endl; 26 } // fin de main items antes de modificarlos: 1 2 3 4 5 items después de modificarlos: 2 4 6 8 10 Fig. 7.13  Uso de la instrucción for basada en rango para multiplicar los elementos de un arreglo por 2. Uso del for basado en rango para mostrar el contenido de un arreglo La instrucción for basada en rango simplifica el código para iterar por un arreglo. La línea 13 puede leerse como “para cada iteración, asignar el siguiente elemento de items a la variable item, después
  • 327. 7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para... 295 ejecutar la siguiente instrucción”. Así, para cada iteración, el identificador item representa un elemento en items. Las líneas 13 y 14 son equivalentes a la siguiente repetición controlada por contador: for ( int contador = 0; contador items.size(); ++contador ) cout items[ contador ] ; Uso de la instrucción for basada en rango para modificar el contenido de un arreglo Las líneas 17 y 18 usan una instrucción for basada en rango para multiplicar cada elemento de items por 2. En la línea 17, la declaracionVariableRango indica que refItem es una referencia int (). Recuerde que una referencia es un alias para otra variable en memoria; en este caso, uno de los elementos del arreglo. Usamos una referencia int debido a que items contiene valores int y queremos modificar el valor de cada elemento; ya que refItem se declara como referencia, cualquier modificación realizada en refItem cambia el valor del elemento correspondiente en el arreglo. Uso del subíndice de un elemento La instrucción for basada en rango puede usarse en vez de la instrucción for controlada por contador, cada vez que el código que itera a través de un arreglo no requiera acceso al subíndice del elemento. Por ejemplo, para obtener el total de los enteros en un arreglo (como en la figura 7.8) se requiere acceso sólo a los valores de los elementos; sus subíndices son irrelevantes. No obstante, si un programa debe usar subíndices por alguna razón que no sea iterar a través de un arreglo (por ejemplo, para imprimir un número de subíndice enseguida del valor de cada elemento del arreglo, como en los primeros ejemplos de este capítulo), debe usar la instrucción for controlada por contador. 7.6Caso de estudio: la clase LibroCalificaciones que usa un arreglo para almacenar las calificaciones En esta sección desarrollaremos aún más la clase LibroCalificaciones, que presentamos en el capítulo 3 y ampliamos en los capítulos 4 a 6. Recuerde que esta clase representa un libro de calificaciones utilizado por un instructor para almacenar y analizar un conjunto de calificaciones de estudiantes. Las versiones anteriores de esta clase procesan un conjunto de calificaciones introducidas por el usuario, pero no man- tienen los valores de las calificaciones individuales en los miembros de datos de la clase. Por ende, los cálculos repetidos requieren que el usuario vuelva a introducir las calificaciones. Una manera de resol- ver este problema sería almacenar cada calificación introducida por el usuario en un miembro de datos individual de la clase. Por ejemplo, podríamos crear los miembros de datos calificacion1, califica- cion2, …, calificacion10 en la clase LibroCalificaciones para almacenar 10 calificaciones de estu- diantes. No obstante, el código para totalizar las calificaciones y determinar el promedio de la clase sería voluminoso. En esta sección resolvemos este problema, almacenando las calificaciones en un arreglo. Almacenar las calificaciones de los estudiantes en un arreglo en la clase LibroCalificaciones La figura 7.14 muestra la salida que sintetiza las 10 calificaciones que almacenamos en un objeto de la siguienteversióndelaclaseLibroCalificaciones (figuras7.15y7.16)queutilizaunarreglodeenteros para almacenar las calificaciones de 10 estudiantes en un solo examen. Esto elimina la necesidad de introducir varias veces el mismo conjunto de calificaciones. El arreglo calificaciones se declara como miembro de datos en la línea 28 de la figura 7.15; por lo tanto, cada objeto LibroCalificaciones mantiene su propio conjunto de calificaciones. Bienvenido al libro de calificaciones para CS101 Introduccion a la programacion en C++! Fig.7.14  SalidadelejemplodeLibroCalificaciones quealmacenalascalificacionesenunarreglo(parte1de2).
  • 328. 296 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones Las calificaciones son: Estudiante 1: 87 Estudiante 2: 68 Estudiante 3: 94 Estudiante 4: 100 Estudiante 5: 83 Estudiante 6: 78 Estudiante 7: 85 Estudiante 8: 91 Estudiante 9: 76 Estudiante 10: 87 El promedio de la clase es 84.90 La calificacion mas baja es 68 La calificacion mas alta es 100 Distribucion de calificaciones: 0-9: 10-19: 20-29: 30-39: 40-49: 50-59: 60-69: * 70-79: ** 80-89: **** 90-99: ** 100: * 1 // Fig. 7.15: LibroCalificaciones.h 2 // Definición de la clase LibroCalificaciones que usa un arreglo para almacenar calificaciones de una prueba. 3 // Las funciones miembro se definen en LibroCalificaciones.cpp 4 #include string 5 #include array 6 7 // definición de la clase LibroCalificaciones 8 class LibroCalificaciones 9 { 10 public: 11 // constante -- número de estudiantes que tomaron la prueba 12 static const size_t estudiantes = 10; // observe los datos públicos 13 14 // el constructor inicializa el nombre del curso y el arreglo de calificaciones 15 LibroCalificaciones( const std::string , const std::array int, estudiantes ); 16 17 void establecerNombreCurso( const std::string ); // establece el nombre del curso 18 string obtenerNombreCurso() const; // obtiene el nombre del curso 19 void mostrarMensaje() const; // muestra un mensaje de bienvenida 20 void procesarCalificaciones() const; // realiza varias operaciones con los datos de las calificaciones 21 int obtenerMinimo() const; // busca la calificación mínima para la prueba 22 int obtenerMaximo() const; // busca la calificación máxima para la prueba Fig. 7.14  Salida del ejemplo de LibroCalificaciones que almacena las calificaciones en un arreglo (parte 2 de 2). Fig. 7.15  Definición de la clase LibroCalificaciones que usa un arreglo para almacenar calificaciones de una prueba (parte 1 de 2).
  • 329. 7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para... 297 23 double obtenerPromedio() const; // determina la calificación promedio para la prueba 24 void imprimirGraficoBarras() const; // imprime gráfico de barras de la distribución de calificaciones 25 void imprimirCalificaciones() const; // imprime el contenido del arreglo calificaciones 26 private: 27 std::string nombreCurso; // nombre del curso para este libro de calificaciones 28 std::array int, estudiantes calificaciones; // arreglo de calificaciones de estudiantes 29 }; // fin de la clase LibroCalificaciones 1 // Fig. 7.16: LibroCalificaciones.cpp 2 // Funciones miembro para la clase LibroCalificaciones 3 // que manipulan un arreglo de calificaciones. 4 #include iostream 5 #include iomanip 6 #include LibroCalificaciones.h // definición de la clase LibroCalificaciones 7 using namespace std; 8 9 // el constructor inicializa nombreCurso y el arreglo calificaciones 10 LibroCalificaciones::LibroCalificaciones( const string nombre, 11 const array int, estudiantes arregloCalificaciones ) 12 : nombreCurso( nombre ), calificaciones( arregloCalificaciones ) 13 { 14 } // fin del constructor de LibroCalificaciones 15 16 // función para establecer el nombre del curso 17 void LibroCalificaciones::establecerNombreCurso( const string nombre ) 18 { 19 nombreCurso = nombre; // almacena el nombre del curso 20 } // fin de la función establecerNombreCurso 21 22 // función para obtener el nombre del curso 23 string LibroCalificaciones::obtenerNombreCurso() const 24 { 25 return nombreCurso; 26 } // fin de la función obtenerNombreCurso 27 28 // muestra un mensaje de bienvenida para el usuario de LibroCalificaciones 29 void LibroCalificaciones::mostrarMensaje() const 30 { 31 // esta instrucción llama a obtenerNombreCurso para obtener el 32 // nombre del curso que representa este LibroCalificaciones 33 cout Bienvenido al libro de calificaciones paran obtenerNombreCurso() ! 34 endl; 35 } // fin de la función mostrarMensaje 36 37 // realiza varias operaciones con los datos 38 void LibroCalificaciones::procesarCalificaciones() const 39 { 40 // imprime el arreglo calificaciones 41 imprimirCalificaciones(); 42 Fig. 7.15  Definición de la clase LibroCalificaciones que usa un arreglo para almacenar calificaciones de una prueba (parte 2 de 2). 28 Fig. 7.16  Funciones miembro de la clase LibroCalificaciones que manipulan un arreglo de calificaciones (parte 1 de 3).
  • 330. 298 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones 43 // llama a la función obtenerPromedio para calcular la calificacion promedio 44 cout setprecision( 2 ) fixed; 45 cout nEl promedio de la clase es obtenerPromedio() endl; 46 47 // llama a las funciones obtenerMinimo y obtenerMaximo 48 cout La calificacion mas baja es obtenerMinimo() nLa calificacion mas alta es 49 obtenerMaximo() endl; 50 51 // llama a la función imprimirGraficoBarras para imprimir el gráfico de ditribución de calificaciones 52 imprimirGraficoBarras(); 53 } // fin de la función procesarCalificaciones 54 55 // busca la calificación mínima 56 int LibroCalificaciones::obtenerMinimo() const 57 { 58 int calificacionInf = 100; // asume que la calificación más baja es 100 59 60 // itera a través del arreglo calificaciones 61 for ( int calificacion : calificaciones ) 62 { 63 // si la calificación actual es menor que calificacionInf, la asigna a calificacionInf 64 if ( calificacion calificacionInf ) 65 calificacionInf = calificacion; // nueva calificación más baja 66 } // fin de for 67 68 return calificacionInf; // devuelve la calificación más baja 69 } // fin de la función obtenerMinimo 70 71 // busca la calificación máxima 72 int LibroCalificaciones::obtenerMaximo() const 73 { 74 int calificacionSup = 0; // asume que la calificación más alta es 0 75 76 // itera a través del arreglo calificaciones 77 for ( int calificacion : calificaciones ) 78 { 79 // si la calificación actual es mayor que calificacionSup, la asigna a calificacionSup 80 if ( calificacion calificacionSup ) 81 calificacionSup = calificacion; // nueva calificación más alta 82 } // fin de for 83 84 return calificacionSup; // devuelve la calificación más alta 85 } // fin de la función obtenerMaximo 86 87 // determina la calificación promedio para la prueba 88 double LibroCalificaciones::obtenerPromedio() const 89 { 90 int total = 0; // inicializa el total 91 92 // suma las calificaciones en el arreglo 93 for ( int calificacion : calificaciones ) 94 total += calificacion; 95 Fig. 7.16  Funciones miembro de la clase LibroCalificaciones que manipulan un arreglo de calificaciones (parte 2 de 3).
  • 331. 7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para... 299 96 // devuelve el promedio de las calificaciones 97 return static_cast double ( total ) / calificaciones.size(); 98 } // fin de la función obtenerPromedio 99 100 // imprime gráfico de barras que muestra la distribución de las calificaciones 101 void LibroCalificaciones::imprimirGraficoBarras() const 102 { 103 cout nDistribucion de calificaciones: endl; 104 105 // almacena la frecuencia de calificaciones en cada rango de 10 calificaciones 106 const size_t tamanioFrecuencia = 11; 107 array unsigned int, tamanioFrecuencia frecuencia = {}; // inicializa elementos con 0 108 109 // para cada calificación, incrementa la frecuencia apropiada 110 for ( int calificacion : calificaciones ) 111 ++frecuencia[ calificacion / 10 ]; 112 113 // para cada frecuencia de calificación, imprime barra en el gráfico 114 for ( size_t cuenta = 0; cuenta tamanioFrecuencia; ++cuenta ) 115 { 116 // imprime etiquetas de las barras (0-9:, ..., 90-99:, 100: ) 117 if ( 0 == cuenta ) 118 cout 0-9: ; 119 else if ( 10 == cuenta ) 120 cout 100: ; 121 else 122 cout cuenta * 10 - ( cuenta * 10 ) + 9 : ; 123 124 // imprime barra de asteriscos 125 for ( unsigned int estrellas = 0; estrellas frecuencia[ cuenta ]; ++estrellas ) 126 cout '*'; 127 128 cout endl; // empieza una nueva línea de salida 129 } // fin de for exterior 130 } // fin de la función imprimirGraficoBarras 131 132 // imprime el contenido del arreglo calificaciones 133 void LibroCalificaciones::imprimirCalificaciones() const 134 { 135 cout nLas calificaciones son:nn; 136 137 // imprime la calificación de cada estudiante 138 for ( size_t estudiante = 0; estudiante calificaciones.size(); ++estudiante ) 139 cout Estudiante setw( 2 ) estudiante + 1 : setw( 3 ) 140 calificaciones[ estudiante ] endl; 141 } // fin de la función imprimirCalificaciones El tamaño del arreglo en la línea 28 de la figura 7.15 se especifica mediante el miembro de datos public static const llamado estudiantes (declarado en la línea 12), el cual es public, de manera que sea accesible para los clientes de la clase. Pronto veremos un ejemplo de un programa cliente que utiliza esta constante. Al declarar a estudiantes con el calificador const, indicamos que este miembro de datos es constante; su valor no se puede modificar después de inicializarlo. La palabra clave static en esta declaración de variable indica que el miembro de datos es compartido por todos los objetos de la Fig. 7.16  Funciones miembro de la clase LibroCalificaciones que manipulan un arreglo de calificaciones (parte 3 de 3).
  • 332. 300 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones clase; así que, en esta implementación específica de la clase LibroCalificaciones, todos los objetos LibroCalificaciones almacenan calificaciones para el mismo número de estudiantes. En la sección 3.4 vimos que cuando cada objeto de una clase mantiene su propia copia de un atributo, la variable que representa a ese atributo se conoce como miembro de datos; cada objeto (instancia) de la clase tiene una copia separada de la variable en memoria. Hay variables para las que cada objeto de una clase no tiene una copia separada. Éste es el caso con los miembros de datos static, que también se conocen como variables de clase. Cuando se crean los objetos de una clase que contiene miembros de datos static, todos los objetos comparten una copia de los miembros de datos static de la clase. Se puede acceder a un miembro de datos static dentro de la definición de la clase y de las definiciones de las funciones miembro al igual que con cualquier otro miembro de datos. Como veremos más adelante, también se puede acceder a un miembro de datos public static desde el exterior de la clase, aun cuando no existan objetos de la misma; para ello se utiliza el nombre de la clase, seguido del operador de resolución de ámbito (::) y el nombre del miembro de datos. En el capítulo 9 aprenderá más acerca de los miembros de datos static. Constructor El constructor de la clase (declarado en la línea 15 de la figura 7.15 y definido en las líneas 10 a 14 de la figura 7.16) tiene dos parámetros: el nombre del curso y una referencia a un arreglo de calificaciones. Cuando un programa crea un objeto LibroCalificaciones (por ejemplo, en la línea 15 de la figura 7.17), el programa pasa un arreglo int existente al constructor, el cual copia los valores del arreglo en el miembro de datos calificaciones (línea 12 de la figura 7.16). Los valores de las calificaciones en el arreglo que se pasa podrían haberse recibido de un usuario, o de un archivo en disco (como veremos en el capítulo 14, Procesamiento de archivos). En nuestro programa de prueba, simplemente inicializamos un arreglo con un conjunto de valores de calificaciones (figura 7.17, líneas 11 y 12). Una vez que las calificaciones se almacenan en el miembro de datos calificaciones de la clase LibroCalificaciones, todas las funciones miembro de la clase pueden acceder al arreglo calificaciones según sea necesario, para realizar varios cálculos. Cabe mencionar que el constructor recibe tanto el parámetro string como el arreglo por referencia; esto es más eficiente que recibir copias del objeto string original y del arreglo originales. El constructor no necesita modificar el objeto string ni el arreglo originales, por lo que también declaramos cada parámetro como const, para asegurar que el constructor no modifique acci- dentalmente los datos originales en la función que hizo la llamada. También modificamos la función establecerNombreCurso para que reciba su argumento string por referencia. La función miembro procesarCalificaciones La función miembro procesarCalificaciones (declarada en la línea 20 de la figura 7.15 y definida en las líneas 38 a 53 de la figura 7.16) contiene una serie de llamadas a funciones miembro que pro- duce un reporte en el que se resumen las calificaciones. La línea 41 llama a la función miembro imprimirCalificaciones para imprimir el contenido del arreglo calificaciones. Las líneas 138 a 140 en la función miembro imprimirCalificaciones utilizan una instrucción for para imprimir la calificación de cada estudiante. Aunque los subíndices de los arreglos empiezan en 0, lo común es que el profesor enumere a los estudiantes empezando desde 1. Por ende, las líneas 139 y 140 imprimen estudiante + 1 como el número de estudiante para producir las etiquetas Estudiante 1: , Estudiante 2: , y así en lo sucesivo. La función miembro obtenerPromedio A continuación, la función miembro procesarCalificaciones llama a la función miembro obtener- Promedio (línea 45) para obtener el promedio de las calificaciones. La función miembro obtenerPro- medio (declarada en la línea 23 de la figura 7.15 y definida en las líneas 88 a 98 de la figura 7.16) totali- za los valores en el arreglo calificaciones antes de calcular el promedio. El cálculo del promedio en la línea 97 utiliza calificaciones.size() para determinar el número de calificaciones que se van a promediar.
  • 333. 7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para... 301 Las funciones miembro obtenerMinimo y obtenerMaximo Las líneas 48 y 49 en procesarCalificaciones llaman a las funciones miembro obtenerMinimo y obtenerMaximo paradeterminarlascalificacionesmásbajaymásaltadecualquierestudianteenelexamen. Vamos a examinar la forma en que la función miembroobtenerMinimo encuentra la calificación más baja. Comolacalificaciónmásaltapermitidaes100,empezamosporsuponerque100eslacalificaciónmásbaja (línea 58). Después comparamos cada uno de los elementos en el arreglo con la calificación más ba- ja, buscando valores más pequeños. En las líneas 61 a 66 de la función miembro obtenerMinimo se itera a través del arreglo, y en la línea 64 se compara cada calificación con calificacionInf. Si una calificación esmenorquecalificacionInf,acalificacionInf seleasignaesacalificación.Cuandoseejecutalalínea 68,calificacionInf contienelacalificaciónmásbajaenelarreglo.LafunciónmiembroobtenerMaximo (líneas 72 a 85) funciona de manera similar a la función miembro obtenerMinimo. La función miembro imprimirGraficoBarras Por último, la línea 52 en la función miembro procesarCalificaciones llama a la función miembro imprimirGraficoBarras para imprimir un gráfico de distribución de los datos de las calificaciones, usando una técnica similar a la de la figura 7.9. En ese ejemplo, calculamos en forma manual el número de calificaciones en cada categoría (es decir, 0-9, 10-19, …, 90-99 y 100), para lo cual simplemente analizamos un conjunto de calificaciones. En las líneas 110 y 111, de este ejemplo, se utiliza una técni- ca similar a la de las figuras 7.10 y 7.11 para calcular la frecuencia de calificaciones en cada categoría. En la línea 107 se declara y crea el arreglo frecuencia de 11 valores del tipo unsigned int para almacenar la frecuencia de calificaciones en cada categoría. Para cada calificacion en el arreglo calificaciones, en las líneas 110 y 111 se incrementa el elemento apropiado del arreglo frecuencia. Para determinar qué elemento se debe incrementar, en la línea 111 se divide la calificacion actual entre 10, usando la división entera. Por ejemplo, si calificacion es 85, en la línea 111 se incrementa frecuencia[8] para actualizar la cuenta de calificaciones en el rango de 80 a 89. Después, en las líneas 114 a 129 se imprime el gráfico de barras (vea la figura 7.17) con base en los valores en el arreglo frecuencia. Al igual que en las líneas 28 y 29 de la figura 7.9, en las líneas 125 y 126 de la figura 7.16 se utiliza un valor en el arreglo frecuencia para determinar el número de asteriscos a mostrar en cada barra. Prueba de la clase LibroCalificaciones El programa de la figura 7.17 crea un objeto de la clase LibroCalificaciones (figuras 7.15 y 7.16) me- diante el uso del arreglo int calificaciones (que se declara y se inicializa en las líneas 11 y 12). Usamos el operador de resolución de ámbito (::) en la expresión “LibroCalificaciones::estudiantes” (línea 11) para acceder a la constante static llamada estudiantes, de la clase LibroCalificaciones. Utiliza- mos aquí esta constante para crear un arreglo que sea del mismo tamaño que el arreglo que se almacena como miembro de datos en la clase LibroCalificaciones.En la línea 13 se declara un objeto string que representa el nombre de un curso. En la línea 15 se pasa el nombre del curso y el arreglo de calificacio- nes al constructor de LibroCalificaciones. En la línea 16 se imprime un mensaje de bienvenida, y en la línea 17 se invoca la función miembro procesarCalificaciones del objeto LibroCalificaciones. 1 // Fig. 7.17: fig07_17.cpp 2 // Crea un objeto LibroCalificaciones usando un arreglo de calificaciones. 3 #include array 4 #include LibroCalificaciones.h // definición de la clase LibroCalificaciones 5 using namespace std; 6 7 // la función main empieza la ejecución del programa 8 int main() 9 { Fig. 7.17  Crea un objeto LibroCalificaciones usando un arreglo de calificaciones, y después invoca a la función miembro procesarCalificaciones para analizarlas (parte 1 de 2).
  • 334. 302 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones 10 // arreglo de calificaciones de estudiantes 11 const array int, LibroCalificaciones::estudiantes calificaciones = 12 { 87, 68, 94, 100, 83, 78, 85, 91, 76, 87 }; 13 string nombreCurso = CS101 Introduccion a la programacion en C++; 14 15 LibroCalificaciones miLibroCalificaciones( nombreCurso, calificaciones ); 16 miLibroCalificaciones.mostrarMensaje(); 17 miLibroCalificaciones.procesarCalificaciones(); 18 } // fin de main 7.7Búsqueda y ordenamiento de datos en arreglos En esta sección vamos a usar la función sort de la Biblioteca estándar de C++ para ordenar los elemen- tos de un arreglo en forma ascendente, y la función binary_search integrada para determinar si un valor se encuentra o no en el arreglo. Ordenamiento El ordenamiento de los datos (colocarlos en orden ascendente o descendente) es una de las aplicaciones de cómputo más importantes. Un banco ordena todos los cheques por número de cuenta, para poder preparar los estados bancarios individuales al final de cada mes. Las compañías telefónicas ordenan sus directorios por apellido; y cuando las entradas contienen el mismo apellido, las ordenan por nombre de pila para facilitar la búsqueda de números telefónicos. Casi todas las empresas deben ordenar ciertos datos y, en algunos casos, cantidades masivas de ellos. El ordenamiento de datos es un problema intri- gante que ha atraído algunos de los esfuerzos de investigación más intensos en el campo de las ciencias computacionales. En el capítulo 20 (en inglés en el sitio web) vamos a investigar e implementar algunos esquemas de ordenamiento, analizaremos su rendimiento y presentaremos la notación Big O para ca- racterizar lo duro que debe trabajar cada esquema para realizar su tarea. Búsqueda A menudo puede ser necesario determinar si un arreglo contiene un valor que concuerde con cierto valor clave. Al proceso de buscar un elemento específico de un arreglo se le llama búsqueda. En el ca- pítulo 20 (en inglés en el sitio web) vamos a investigar e implementar dos algoritmos de búsqueda: la búsqueda lineal, que es simple pero lenta y se usa para buscar en un arreglo desordenado, y la búsqueda binaria, que es más compleja pero mucho más rápida y se usa para buscar en un arreglo ordenado. Demostración de las funciones sort y binary_search La figura 7.18 comienza por crear un arreglo desordenado de objetos string (líneas 13 y 14) y muestra el contenido del arreglo (líneas 17 a 19). A continuación, en la línea 21 se usa la función sort de la Biblioteca estándar de C++ para ordenar los elementos del arreglo colores en forma ascendente. Los argumentosdelafunciónsort especificanelrangodeelementosquedebenordenarse;enestecaso,todo el arreglo. En capítulos posteriores hablaremos sobre los detalles completos de las funciones begin y end de la plantilla de clase array. Como veremos más adelante, la función sort puede usarse para ordenar los elementos de varios tipos diferentes de estructuras de datos. Las líneas 24 a 26 muestran el contenido del arreglo ordenado. Las líneas 29 y 34 demuestran el uso de binary_search para determinar si un valor está en el arre- glo. La secuencia de valores debe ordenarse primero en forma ascendente; binary_search no verifica esto por nosotros. Los primeros dos argumentos de la función representan el rango de elementos a Fig. 7.17  Crea un objeto LibroCalificaciones usando un arreglo de calificaciones, y después invoca a la función miembro procesarCalificaciones para analizarlas (parte 2 de 2).
  • 335. 7.7 Búsqueda y ordenamiento de datos en arreglos 303 buscar y el tercero es la clave de búsqueda: el valor a localizar en el arreglo. La función devuelve un valor bool para indicar si se encontró el valor o no. En el capítulo 16 usaremos la función find estándar de C++ para obtener la ubicación de la clave de búsqueda en un arreglo. 1 // Fig. 7.18: fig07_18.cpp 2 // Búsqueda y ordenamiento de arreglos. 3 #include iostream 4 #include iomanip 5 #include array 6 #include string 7 #include algorithm // contiene sort y binary_search 8 using namespace std; 9 10 int main() 11 { 12 const size_t tamanioArreglo = 7; // tamaño del arreglo colores 13 array string, tamanioArreglo colores = { rojo, naranja, amarillo, 14 verde, azul, indigo, violeta }; 15 16 // imprime el arreglo original 17 cout Arreglo desordenado:n; 18 for ( string color : colores ) 19 cout color “ “; 20 21 sort( colores.begin(), colores.end() ); // ordena el contenido de colores 22 23 // imprime el arreglo ordenado 24 cout nArreglo ordenado:n; 25 for ( string elemento : colores ) 26 cout elemento ; 27 28 // busca indigo en colores 29 bool encontro = binary_search( colores.begin(), colores.end(), indigo ); 30 cout nnindigo ( encontro ? se : no se ) 31 encuentra en colores endl; 32 33 // busca cian en colores 34 encontro = binary_search( colores.begin(), colores.end(), cyan ); 35 cout cian ( encontro ? se : no se ) 36 encuentra en colores endl; 37 } // fin de main Arreglo desordenado: rojo naranja amarillo verde azul indigo violeta Arreglo ordenado: amarillo azul indigo naranja rojo verde violeta indigo se encuentra en colores cian no se encuentra en colores Fig. 7.18  Búsqueda y ordenamiento de arreglos.
  • 336. 304 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones 7.8Arreglos multidimensionales Es posible usar arreglos de dos dimensiones (subíndices) para representar tablas de valores, las cuales consisten en información ordenada en filas y columnas. Para identificar un elemento específico de una tabla, debemos especificar dos subíndices. Por convención, el primero identifica la fila del elemento y el segundo su columna. Los arreglos que requieren dos subíndices para identificar un elemento específico se llaman arreglos bidimensionales o arreglos 2-D. Los arreglos con dos o más dimensiones se cono- cen como arreglos multidimensionales y pueden tener más de dos dimensiones. La figura 7.19 ilustra un arreglo bidimensional a, que contiene tres filas y cuatro columnas (es decir, un arreglo de tres por cuatro). En general, a un arreglo con m filas y n columnas se le llama arreglo de m por n. Fila 0 Fila 1 Fila 2 Subíndice de columna Subíndice de fila Nombre del arreglo a[ 0 ][ 0 ] a[ 1 ][ 0 ] a[ 2 ][ 0 ] a[ 0 ][ 1 ] a[ 1 ][ 1 ] a[ 2 ][ 1 ] a[ 0 ][ 2 ] a[ 1 ][ 2 ] a[ 2 ][ 2 ] a[ 0 ][ 3 ] Columna 0 Columna 1 Columna 2 Columna 3 a[ 1 ][ 3 ] a[ 2 ][ 3 ] Fig. 7.19  Arreglo bidimensional con tres filas y cuatro columnas. Cada elemento en el arreglo a se identifica en la figura 7.19 mediante el nombre de un elemento de la forma a[i][j], donde a es el nombre del arreglo, i y j son los subíndices que identifican en forma única a cada elemento en el arreglo a. Observe que los nombres de los elementos en la fila 0 tienen todos un primer subíndice de 0, y los nombres de los elementos en la columna 3 tienen un segundo subíndice de 3. Error común de programación 7.5 Es un error hacer referencia al elemento a[x][y] de un arreglo bidimensional en forma incorrecta como a[x, y]. En realidad, a[x, y] se trata como a[y], ya que C++ evalúa la expresión x, y (que contiene un operador coma) simplemente como y (la última de las expresiones separadas por coma). La figura 7.20 demuestra cómo inicializar arreglos bidimensionales en las declaraciones. Las líneas 13 y 14 declaran cada una un arreglo de arreglos, con dos filas y tres columnas. Observe la declaración de tipo array anidada. En cada array se especifica el tipo de sus elementos como: array int, columnas para indicar que cada array contiene como elementos arreglos de tres elementos de valores int; la constante columnas tiene el valor de 3. 1 // Fig. 7.20: fig07_20.cpp 2 // Inicialización de arreglos multidimensionales. 3 #include iostream Fig. 7.20  Inicialización de arreglos multidimensionales (parte 1 de 2).
  • 337. 7.8 Arreglos multidimensionales 305 4 #include array 5 using namespace std; 6 7 const size_t filas = 2; 8 const size_t columnas = 3; 9 void imprimirAreglo( const array array int, columnas , filas ); 10 11 int main() 12 { 13 array array int, columnas , filas arreglo1 = { 1, 2, 3, 4, 5, 6 }; 14 array array int, columnas , filas arreglo2 = { 1, 2, 3, 4, 5 }; 15 16 cout Los valores en el arreglo1 por fila son: endl; 17 imprimirArreglo( arreglo1 ); 18 19 cout nLos valores en el arreglo2 por fila son: endl; 20 imprimirArreglo( arreglo2 ); 21 } // fin de main 22 23 // imprime arreglo con dos filas y tres columnas 24 void imprimirArreglo( const array array int, columnas , filas a ) 25 { 26 // itera a través de las filas del arreglo 27 for ( auto const fila : a ) 28 { 29 // itera por las columnas de la fila actual 30 for ( auto const elemento : fila ) 31 cout elemento ' '; 32 33 cout endl; // inicia nueva línea de salida 34 } // fin de for externo 35 } // fin de la función imprimirArreglo Los valores en el arreglo1 por fila son: 1 2 3 4 5 6 Los valores en el arreglo2 por fila son: 1 2 3 4 5 0 La declaración de arreglo1 (línea 13) proporciona seis inicializadores. El compilador inicializa los elementos de la fila 0 seguidos de los elementos de la fila 1. Así, los primeros tres valores inicializan los elementos de la fila 0 con 1, 2 y 3, y los últimos tres inicializan los elementos de la fila 1 con 4, 5 y 6. La declaración de arreglo2 (línea 14) proporciona sólo cinco inicializadores. Éstos se asignan a la fila 0, después a la fila 1. Cualquier elemento que no tenga un inicializador explícito se inicializa con cero, por lo que arreglo2[1][2] es 0. El programa llama a la función imprimirArreglo para imprimir cada uno de los elementos del arreglo. Observe que el prototipo de la función (línea 9) y la definición (líneas 24 a 35) especifican que la función recibe un arreglo de dos filas y tres columnas. El parámetro recibe el arreglo por referencia y se declara como const, ya que la función no modifica los elementos del arreglo. Instrucciones for anidadas basadas en el rango Para procesar los elementos de un arreglo bidimensional, usamos un ciclo anidado en donde el ciclo exter- no itera a través de las filas y el ciclo interno itera a través de las columnas de una fila dada. El ciclo anidado Fig. 7.20  Inicialización de arreglos multidimensionales (parte 2 de 2).
  • 338. 306 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones de la función imprimirArreglo se implementa con instrucciones for basadas en el rango. Las líneas 27 y 30 introducen la palabra clave auto de C++, la cual indica al compilador que infiera (determine) el tipo de datos de una variable con base en el valor inicializador de ésta. La variable de rango fila del ciclo inferior se inicializa con un elemento del parámetro a. Si analizamos la declaración del arreglo, podemos ver que contiene elementos del tipo array int, columnas de modo que el compilador infiera que fila se refiere a un arreglo de tres elementos de valores int (de nuevo, columnas es 3). La parte const en la declaración de fila indica que la referencia no puede usarse para modificar las filas y evita que cada fila se copie a la variable de rango. La variable de rango elemento del ciclo interno se inicializa con un elemento del arreglo representado por fila, por lo que el compilador infiere que elemento hace referencia a un int debido a que cada fila contiene tres valores int. En un IDE, por lo general pasamos el ratón sobre una variable declarada con auto y el IDE mues- tra el tipo inferido por la variable. La línea 31 muestra el valor de una fila y columna dadas. Instrucciones for anidadas, controladas por contador Podríamos haber implementado el ciclo anidado con una repetición controlada por contador de la si- guiente manera: for ( size_t fila = 0; fila a.size(); ++fila ) { for ( size_t columna = 0; columna a[ fila ].size(); ++columna ) cout a[ fila ][ columna ] ' '; cout endl; } // fin de for externo Otras manipulaciones comunes de arreglos Muchas manipulaciones comunes en los arreglos utilizan instrucciones de repetición for. Por ejemplo, la siguiente instrucción for establece todos los elementos en la fila 2 del arreglo a de la figura 7.19 con cero: for ( size_t columna = 0; columna 4; ++columna ) a[ 2 ][ columna ] = 0; La instrucción for sólo varía el segundo subíndice (es decir, el subíndice de la columna). La instrucción for anterior es equivalente a las siguientes instrucciones de asignación: a[ 2 ][ 0 ] = 0; a[ 2 ][ 1 ] = 0; a[ 2 ][ 2 ] = 0; a[ 2 ][ 3 ] = 0; La siguiente instrucción for controlada por contador anidada determina el total de todos los elementos en el arreglo a de la figura 7.19: total = 0; for ( size_t fila = 0; fila a.size(); ++fila ) for ( size_t columna = 0; columna a[ fila ].size(); ++columna ) total += a[ fila ][ columna ];
  • 339. 7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional 307 La instrucción for calcula el total de los elementos del arreglo, una fila a la vez. La instrucción for exte- rior empieza estableciendo fila (es decir, el subíndice de la fila) en 0, de manera que la instrucción for interior pueda calcular el total de los elementos de la fila 0. Después, la instrucción for exterior incre- menta fila a 1, de manera que se pueda obtener el total de los elementos de la fila 1. Luego, la instruc- ción for exterior incrementa fila a 2, de manera que pueda obtenerse el total de los elementos de la fila 2. Cuando termina la instrucción for anidada, total contiene la suma de todos los elementos del arreglo. Este ciclo anidado puede implementarse con instrucciones for basadas en el rango, como: total = 0; for ( auto fila : a ) // por cada fila for ( auto columna : fila ) // por cada columna en la fila total += columna; 7.9Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional En la sección 7.6 presentamos la clase LibroCalificaciones (figuras 7.15 y 7.16), la cual utilizó un arreglo unidimensional para almacenar las calificaciones de los estudiantes de un solo examen. En la mayoría de los semestres, los estudiantes presentan varios exámenes. Es probable que los profesores quieran analizar las calificaciones a lo largo de todo el semestre, tanto para un solo estudiante como para la clase en general. Cómo almacenar las calificaciones de los estudiantes en un arreglo bidimensional en la clase LibroCalificaciones La figura 7.21 muestra el resultado en donde se sintetizan las calificaciones de 10 estudiantes de tres exámenes. Almacenamos las calificaciones como un arreglo bidimensional en un objeto de la siguiente versión de la clase LibroCalificaciones de las figuras 7.22 y 7.23. Cada fila del arreglo representa las calificaciones de un solo estudiante durante todo el curso, y cada columna representa todas las califica- ciones que obtuvieron los estudiantes para un examen específico. Un programa cliente, como el de la figura 7.24, pasa el arreglo como argumento para el constructor de LibroCalificaciones. Como hay 10 estudiantes y tres exámenes, usamos un arreglo de tres por diez para almacenar las calificaciones. Bienvenido al libro de calificaciones para CS101 Introduccion a la programacion en C++! Las calificaciones son: Prueba 1 Prueba 2 Prueba 3 Promedio Estudiante 1 87 96 70 84.33 Estudiante 2 68 87 90 81.67 Estudiante 3 94 100 90 94.67 Estudiante 4 100 81 82 87.67 Estudiante 5 83 65 85 77.67 Estudiante 6 78 87 65 76.67 Estudiante 7 85 75 83 81.00 Estudiante 8 91 94 100 95.00 Estudiante 9 76 72 84 77.33 Estudiante 10 87 93 73 84.33 Fig. 7.21  Salida de LibroCalificaciones que usa dos arreglos bidimensionales (parte 1 de 2).
  • 340. 308 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones La calificacion mas baja en el libro de calificaciones es 65 La calificacion mas alta en el libro de calificaciones es 100 Distribucion general de calificaciones: 0-9: 10-19: 20-29: 30-39: 40-49: 50-59: 60-69: *** 70-79: ****** 80-89: *********** 90-99: ******* 100: *** 1 // Fig. 7.22: LibroCalificaciones.h 2 // Definición de la clase LibroCalificaciones que utiliza un 3 // arreglo bidimensional para almacenar calificaciones de una prueba. 4 // Las funciones miembro se definen en LibroCalificaciones.cpp 5 #include array 6 #include string 7 8 // definición de la clase LibroCalificaciones 9 class LibroCalificaciones 10 { 11 public: 12 // constantes 13 static const size_t estudiantes = 10; // número de estudiantes 14 static const size_t pruebas = 3; // número de pruebas 15 16 // el constructor inicializa el nombre del curso y el arreglo de calificaciones 17 LibroCalificaciones( const std::string , 18 std::array std::array int, pruebas, estudiantes ); 19 20 void establecerNombreCurso( const std::string ); // establece el nombre del curso 21 std::string obtenerNombreCurso() const; // obtiene el nombre del curso 22 void mostrarMensaje() const; // muestra un mensaje de bienvenida 23 void procesarCalificaciones() const; // realiza varias operaciones en los datos de las calificaciones 24 int obtenerMinimo() const; // encuentra el valor mínimo en el libro de calificaciones 25 int obtenerMaximo() const; // encuentra el valor máximo en el libro de calificaciones 26 double obtenerPromedio( const std::array int, pruebas ) const; 27 void imprimirGraficoBarras() const; // imprime gráfico de barras de la distribución de calificaciones 28 void imprimirCalificaciones() const; // imprime el contenido del arreglo calificaciones 29 private: 30 std::string nombreCurso; // nombre del curso para este libro de calificaciones 31 std::array std::array int, pruebas , estudiantes calificaciones; // arreglo bidimensional de calificaciones 32 }; // fin de la clase LibroCalificaciones Fig.7.22  Definición de la clase LibroCalificaciones que utiliza un arreglo bidimensional para almacenar calificaciones de una prueba. Fig. 7.21  Salida de LibroCalificaciones que usa dos arreglos bidimensionales (parte 2 de 2). 31
  • 341. 7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional 309 1 // Fig. 7.23: LibroCalificaciones.cpp 2 // Definiciones de las funciones miembro de LibroCalificaciones, 3 // que utiliza un arreglo bidimensional para almacenar calificaciones. 4 #include iostream 5 #include iomanip // manipuladores de flujo parametrizados 6 using namespace std; 7 8 // incluye la definición de la clase LibroCalificaciones de LibroCalificaciones.h 9 #include LibroCalificaciones.h // definición de la clase LibroCalificaciones 10 11 // constructor con dos argumentos que inicializa nombreCurso y el arreglo calificaciones 12 LibroCalificaciones::LibroCalificaciones( const string nombre, 13 std::array std::array int, pruebas , estudiantes arregloCalificaciones ) 14 : nombreCurso( nombre ), calificaciones( arregloCalificaciones ) 15 { 16 } // fin del constructor de LibroCalificaciones con dos argumentos 17 18 // función para establecer el nombre del curso 19 void LibroCalificaciones::establecerNombreCurso( const string nombre ) 20 { 21 nombreCurso = nombre; // almacena el nombre del curso 22 } // fin de la función establecerNombreCurso 23 24 // función para obtener el nombre del curso 25 string LibroCalificaciones::obtenerNombreCurso() const 26 { 27 return nombreCurso; 28 } // fin de la función obtenerNombreCurso 29 30 // muestra un mensaje de bienvenida al usuario de LibroCalificaciones 31 void LibroCalificaciones::mostrarMensaje() const 32 { 33 // esta instrucción llama a obtenerNombreCurso para obtener el 34 // nombre del curso que representa este LibroCalificaciones 35 cout Bienvenido al libro de calificaciones paran obtenerNombreCurso() ! 36 endl; 37 } // fin de la función mostrarMensaje 38 39 // realiza varias operaciones con los datos 40 void LibroCalificaciones::procesarCalificaciones() const 41 { 42 // imprime el arreglo calificaciones 43 imprimirCalificaciones(); 44 45 // llama a las funciones obtenerMinimo y obtenerMaximo 46 cout nLa calificacion mas baja en el libro de calificaciones es obtenerMinimo() 47 nLa calificacion mas alta en el libro de calificaciones es obtenerMaximo() endl; 48 49 // imprime gráfico de distribución de todas las calificaciones en todas las pruebas 50 imprimirGraficoBarras(); 51 } // fin de la función procesarCalificaciones Fig. 7.23  Definiciones de las funciones miembro de LibroCalificaciones, que utiliza un arreglo bidimensional para almacenar calificaciones (parte 1 de 4).
  • 342. 310 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones 52 53 // encuentra la calificación más baja en todo el libro de calificaciones 54 int LibroCalificaciones::obtenerMinimo() const 55 { 56 int calificacionInf = 100; // asume que la calificación más baja es 100 57 58 // itera a través de las filas del arreglo calificaciones 59 for ( auto const estudiante : calificaciones ) 60 { 61 // itera a través de las columnas de la fila actual 62 for ( auto const calificacion : estudiante ) 63 { 64 // si la calificación actual es menor que calificacionInf, la asigna a calificacionInf 65 if ( calificacion calificacionInf ) 66 calificacionInf = calificacion; // nueva calificación más baja 67 } // fin de for interior 68 } // fin de for exterior 69 70 return calificacionInf; // devuelve la calificación más baja 71 } // fin de la función obtenerMinimo 72 73 // busca la calificación más alta en todo el libro de calificaciones 74 int LibroCalificaciones::obtenerMaximo() const 75 { 76 int calificacionSup = 0; // asume que la calificación más alta es 0 77 78 // itera a través de las filas del arreglo calificaciones 79 for ( auto const estudiante : calificaciones ) 80 { 81 // itera a través de las columnas de la fila actual 82 for ( auto const calificacion : estudiante ) 83 { 84 // si la calificación actual es mayor que calificacionSup, la asigna a calificacionSup 85 if ( calificacion calificacionSup ) 86 calificacionSup = calificacion; // nueva calificación más alta 87 } // fin de for interior 88 } // fin de for exterior 89 90 return calificacionSup; // devuelve la calificación más alta 91 } // fin de la función obtenerMaximo 92 93 // determina la calificación promedio para un conjunto específico de calificaciones 94 double LibroCalificaciones::obtenerPromedio( const arrayint, pruebas conjuntoDeCalificaciones ) const 95 { 96 int total = 0; // inicializa el total 97 98 // suma las calificaciones en el arreglo 99 for ( int calificacion : conjuntoDeCalificaciones ) 100 total += calificacion; 101 Fig. 7.23  Definiciones de las funciones miembro de LibroCalificaciones, que utiliza un arreglo bidimensional para almacenar calificaciones (parte 2 de 4).
  • 343. 7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional 311 102 // devuelve el promedio de las calificaciones 103 return static_cast double ( total ) / conjuntoDeCalificaciones.size(); 104 } // fin de la función obtenerPromedio 105 106 // imprime gráfico de barras que muestra la distribución de las calificaciones 107 void LibroCalificaciones::imprimirGraficoBarras() const 108 { 109 cout nDistribucion general de calificaciones: endl; 110 111 // almacena la frecuencia de las calificaciones en cada rango de 10 calificaciones 112 const size_t tamanioFrecuencia = 11; 113 array unsigned int, tamanioFrecuencia frecuencia = {}; // inicializa con ceros 114 115 // para cada calificación, incrementa la frecuencia apropiada 116 for ( auto const estudiante : calificaciones ) 117 for ( auto const prueba : estudiante ) 118 ++frecuencia[ prueba / 10 ]; 119 120 // para cada frecuencia de calificaciones, imprime la barra en el gráfico 121 for ( size_t cuenta = 0; cuenta tamanioFrecuencia; ++cuenta ) 122 { 123 // imprime las etiquetas de las barras (0-9:, ..., 90-99:, 100: ) 124 if ( 0 == cuenta ) 125 cout 0-9: ; 126 else if ( 10 == cuenta ) 127 cout 100: ; 128 else 129 cout cuenta * 10 - ( cuenta * 10 ) + 9 : ; 130 131 // imprime barra de asteriscos 132 for ( unsigned int estrellas = 0; estrellas frecuencia[ cuenta ]; estrellas++ ) 133 cout '*'; 134 135 cout endl; // empieza una nueva línea de salida 136 } // fin de for exterior 137 } // fin de la función imprimirGraficoBarras 138 139 // imprime el contenido del arreglo calificaciones 140 void LibroCalificaciones::imprimirCalificaciones() const 141 { 142 cout nLas calificaciones son:nn; 143 cout ; // alinea los encabezados de las columnas 144 145 // crea un encabezado de columna para cada una de las pruebas 146 for ( size_t prueba = 0; prueba pruebas; ++prueba ) 147 cout Prueba prueba + 1 ; 148 149 cout Promedio endl; // encabezado de la columna de promedio de estudiantes 150 Fig. 7.23  Definiciones de las funciones miembro de LibroCalificaciones, que utiliza un arreglo bidimensional para almacenar calificaciones (parte 3 de 4).
  • 344. 312 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones 151 // crea filas/columnas de texto que representan el arreglo calificaciones 152 for ( size_t estudiante = 0; estudiante calificaciones.size(); ++estudiante ) 153 { 154 cout Estudiante setw( 2 ) estudiante + 1; 155 156 // imprime las calificaciones del estudiante 157 for ( size_t prueba = 0; prueba calificaciones[ estudiante ].size(); ++prueba ) 158 cout setw( 8 ) calificaciones[ estudiante ][ prueba ]; 159 160 // llama a la función miembro obtenerPromedio para calcular el promedio del 161 // estudiante; pasa la fila de calificaciones como argumento 162 double promedio = obtenerPromedio( calificaciones[ estudiante ] ); 163 cout setw( 9 ) setprecision( 2 ) fixed promedio endl; 164 } // fin de for exterior 165 } // fin de la función imprimirCalificaciones Generalidades de las funciones de la clase LibroCalificaciones Cinco funciones miembro (declaradas en las líneas 24 a 28 de la figura 7.22) realizan manipulaciones de arreglos para procesar las calificaciones. Cada una de estas funciones miembro es similar a su contraparte en la versión anterior de la clase LibroCalificaciones con un arreglo unidimensional (figuras 7.15 y 7.16). La función miembro obtenerMinimo (definida en las líneas 54 a 71 de la figura 7.23) determina la calificación más baja de cualquier estudiante durante el semestre. La función miembro obtenerMaximo (definida en las líneas 74 a 91 de la figura 7.23) determina la calificación más alta de cualquier estudiante durante el semestre. La función miembro obtenerPromedio (líneas 94 a 104 de la figura 7.23) determina elpromedio semestralde un estudiante específico.La funciónmiembro imprimirGraficoBarras (líneas 107 a 137 de la figura 7.23) imprime un gráfico de barras de la distribución de todas las calificaciones de los estudiantes durante el semestre. La función miembro imprimirCalificaciones (líneas 140 a 165 de la figura 7.23) imprime el arreglo bidimensional en formato tabular, junto con el promedio se- mestral de cada estudiante. Las funciones obtenerMinimo y obtenerMaximo Cada una de las funciones miembro obtenerMinimo, obtenerMaximo, imprimirGraficoBarras e imprimirCalificaciones iteranatravésdelarreglo calificaciones medianteelusodeinstrucciones for anidadas basadas en rango, o de instrucciones for controladas por contador. Por ejemplo, consi- dere la instrucción for anidada en la función miembro obtenerMinimo (líneas 59 a 68). La instrucción for exterior itera por las filas que representan a cada estudiante, y la instrucción for interior itera a través de las calificaciones de un estudiante específico. Cada calificación se compara con la variable calificacionInf en el cuerpo de la instrucción for interior. Si una calificación es menor que cali- ficacionInf, a calificacionInf se le asigna esa calificación. Esto se repite hasta que se hayan reco- rrido todas las filas y columnas de calificaciones. Cuando se completa la ejecución de la instrucción anidada, calificacionInf contiene la calificación más baja de todo el arreglo bidimensional. La función miembro obtenerMaximo funciona de manera similar a la función miembro obtenerMinimo. La función miembro imprimirGraficoBarras La función miembro imprimirGraficoBarras en la figura 7.23 es casi idéntica a la de la figura 7.16. Sin embargo,paraimprimirladistribucióndecalificacionesengeneraldurantetodounsemestre,lafunción utiliza una instrucción for anidada (líneas 116 a 118) para incrementar los elementos del arreglo uni- Fig. 7.23  Definiciones de las funciones miembro de LibroCalificaciones, que utiliza un arreglo bidimensional para almacenar calificaciones (parte 4 de 4).
  • 345. 7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional 313 dimensional frecuencia, con base en todas las calificaciones en el arreglo bidimensional. El resto del código en cada una de las dos funciones miembro imprimirGraficoBarras que muestran el gráfico es idéntico. La función imprimirCalificaciones La función miembro imprimirCalificaciones (líneas 140 a 165) utiliza instrucciones for anidadas, controladas por contador, para imprimir valores del arreglo calificaciones, además del promedio semestral de cada estudiante. La salida en la figura 7.21 muestra el resultado, el cual se asemeja al forma- to tabular del libro de calificaciones real de un profesor. Las líneas 146 y 147 imprimen los encabezados de columna para cada prueba. Aquí utilizamos una instrucción for controlada por contador, para poder identificar cada prueba con un número. De manera similar, la instrucción for en las líneas 152 a 164 imprime primero una etiqueta de fila mediante el uso de una variable contador para identificar a cada estudiante (línea 154). Aunque los subíndices de los arreglos empiezan en 0, en las líneas 147 y 154 se imprimen prueba + 1 y estudiante + 1 en forma respectiva, para producir números de prueba y estu- diante que empiecen en 1 (vea la figura 7.21) La instrucción for interior en las líneas 157 y 158 utiliza la variable contador estudiante de la instrucción for exterior para iterar a través de una fila específica del arreglo calificaciones, e imprime la calificación de la prueba de cada estudiante. Por último, en la línea 162 se obtiene el promedio semestral de cada estudiante, para lo cual se pasa la fila actual de calificaciones (es decir, calificaciones[estudiante]) a la función miembro obtenerPromedio. La función obtenerPromedio La función miembro obtenerPromedio (líneas 94 a 104) toma como argumento un arreglo unidimensio- nalderesultadosdelapruebaparaunestudianteespecífico.Cuandolalínea162llamaaobtenerPromedio, el primer argumento es calificaciones[ estudiante ], el cual especifica que debe pasarse una fila espe- cífica del arreglo bidimensional calificaciones a obtenerPromedio. Por ejemplo, con base en el arreglo creado en la figura 7.24, el argumento calificaciones[1] representa los tres valores (un arreglo unidi- mensional de calificaciones) almacenados en la fila 1 del arreglo bidimensional calificaciones. Los elementos de un arreglo bidimensional son arreglos unidimensionales. La función miembro obtener- Promedio calcula la suma de los elementos del arreglo, divide el total entre el número de resultados de la prueba y devuelve el resultado de punto flotante como un valor double (línea 103). Prueba de la clase LibroCalificaciones El programa de la figura 7.24 crea un objeto de la clase LibroCalificaciones (figuras 7.22 y 7.23) mediante el uso del arreglo bidimensional de valores int llamado arregloCalif (el cual se declara y se inicializa en las líneas 11 a 21). En la línea 11 se accede a las constantes static llamadas estudiantes ypruebas delaclase LibroCalificaciones paraindicareltamañodecadadimensióndelarreglo arre- gloCalificaciones. En las líneas 23 y 24 se pasan el nombre de un curso y calificaciones al cons- tructor de LibroCalificaciones. Después, en las líneas 25 y 26 se invocan las funciones miembro mostrarMensaje y procesarCalificaciones de miLibroCalificaciones, para mostrar un mensaje de bienvenida y obtener un informe que sintetice las calificaciones de los estudiantes para el semestre, respectivamente. 1 // Fig. 7.24: fig07_24.cpp 2 // Crea un objeto LibroCalificaciones mediante el uso de un arreglo bidimensional de calificaciones. 3 #include array 4 #include LibroCalificaciones.h // definición de la clase LibroCalificaciones 5 using namespace std; 6 Fig. 7.24  Crea un objeto LibroCalificaciones mediante el uso de un arreglo bidimensional de calificaciones, y después invoca a la función miembro procesarCalificaciones para analizarlas (parte 1 de 2).
  • 346. 314 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones 7 // la función main empieza la ejecución del programa 8 int main() 9 { 10 // arreglo bidimensional de calificaciones de estudiantes 11 array array int, LibroCalificaciones::pruebas , LibroCalificaciones::estudiantes calificaciones = 12 { 87, 96, 70, 13 68, 87, 90, 14 94, 100, 90, 15 100, 81, 82, 16 83, 65, 85, 17 78, 87, 65, 18 85, 75, 83, 19 91, 94, 100, 20 76, 72, 84, 21 87, 93, 73 }; 22 23 LibroCalificaciones miLibroCalificaciones( 24 CS101 Introduccion a la programacion en C++, calificaciones ); 25 miLibroCalificaciones.mostrarMensaje(); 26 miLibroCalificaciones.procesarCalificaciones(); 27 } // fin de main 7.10Introducción a la plantilla de clase vector de la Biblioteca estándar de C++ Ahora introduciremos la plantilla de clase vector de la Biblioteca estándar de C++, que es similar a la plantilla de clase array, pero también soporta el ajuste de tamaño dinámico. Con la excepción de las características que modifican a un vector, las demás características que se muestran en la figura 7.25 también funcionan en los arreglos. La plantilla de clase estándar vector está definida en el encabezado vector (línea 5) y pertenece al espacio de nombres std. En el capítulo 15 hablaremos sobre la funcio- nalidad completa de vector. Al final de esta sección demostraremos las herramientas de comprobación de límites de la clase vector e introduciremos el mecanismo de manejo de excepciones de C++, que puede usarse para detectar y manejar el índice fuera de límites de un objeto vector. 1 // Fig. 7.25: fig07_25.cpp 2 // Demostración de la plantilla de clase vector de la Biblioteca estándar de C++. 3 #include iostream 4 #include iomanip 5 #include vector 6 #include stdexcept 7 using namespace std; 8 9 void imprimirVector( const vector int ); // muestra el vector 10 void recibirVector( vector int ); // introduce los valores en el vector 11 12 int main() 13 { Fig. 7.24  Crea un objeto LibroCalificaciones mediante el uso de un arreglo bidimensional de calificaciones, y después invoca a la función miembro procesarCalificaciones para analizarlas (parte 2 de 2). Fig. 7.25  Demostración de la plantilla de clase vector de la Biblioteca estándar de C++ (parte 1 de 4).
  • 347. 7.10 Introducción a la plantilla de clase vector de la Biblioteca estándar de C++ 315 14 vector int enteros1( 7 ); // vector de 7 elementos int 15 vector int enteros2( 10 ); // vector de 10 elementos int 16 17 // imprime el tamaño y el contenido de enteros1 18 cout El tamanio del vector enteros1 es enteros1.size() 19 nvector despues de la inicializacion: endl; 20 imprimirVector( enteros1 ); 21 22 // imprime el tamaño y el contenido de enteros2 23 cout nEl tamanio del vector enteros2 es enteros2.size() 24 nvector despues de la inicializacion: endl; 25 imprimirVector( enteros2 ); 26 27 // recibe e imprime enteros1 y enteros2 28 cout nEscriba 17 enteros: endl; 29 recibirVector( enteros1 ); 30 recibirVector( enteros2 ); 31 32 cout nDespues de la entrada, los vectores contienen:n 33 enteros1: endl; 34 imprimirVector( enteros1 ); 35 cout enteros2: endl; 36 imprimirVector( enteros2 ); 37 38 // usa el operador de desigualdad (!=) con objetos vector 39 cout nEvaluacion: enteros1 != enteros2 endl; 40 41 if ( enteros1 != enteros2 ) 42 cout enteros1 y enteros2 no son iguales endl; 43 44 // crea el vector enteros3 usando enteros1 como un 45 // inicializador; imprime el tamaño y el contenido 46 vector int enteros3( enteros1 ); // constructor de copia 47 48 cout nEl tamanio del vector enteros3 es enteros3.size() 49 nvector despues de la inicializacion: endl; 50 imprimirVector( enteros3 ); 51 52 // usa el operador de asignacion (=) sobrecargado 53 cout nAsignacion de enteros2 a enteros1: endl; 54 enteros1 = enteros2; // asigna enteros2 a enteros1 55 56 cout enteros1: endl; 57 imprimirVector( enteros1 ); 58 cout enteros2: endl; 59 imprimirVector( enteros2 ); 60 61 // usa el operador de igualdad (==) con objetos vector 62 cout nEvaluacion: enteros1 == enteros2 endl; 63 64 if ( enteros1 == enteros2 ) 65 cout enteros1 y enteros2 son iguales endl; 66 Fig. 7.25  Demostración de la plantilla de clase vector de la Biblioteca estándar de C++ (parte 2 de 4).
  • 348. 316 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones 67 // usa corchetes para asignar el valor en la ubicación 5 como un rvalue 68 cout nenteros1[5] es enteros1[ 5 ]; 69 70 // usa corchetes para crear lvalue 71 cout nnAsignacion de 1000 a enteros1[5] endl; 72 enteros1[ 5 ] = 1000; 73 cout enteros1: endl; 74 imprimirVector( enteros1 ); 75 76 // intenta usar subíndice fuera de rango 77 try 78 { 79 cout nIntento de mostrar enteros1.at( 15 ) endl; 80 cout enteros1.at( 15 ) endl; // ERROR: fuera de rango 81 } // fin de try 82 catch ( out_of_range ex ) 83 { 84 cerr Ocurrio una excepcion: ex.what() endl; 85 } // fin de catch 86 87 // cambiar el tamaño de un vector 88 cout nEl tamanio actual de enteros3 es: enteros3.size() endl; 89 enteros3.push_back( 1000 ); // agrega 1000 al final del vector 90 cout El tamanio nuevo de enteros3 es: enteros3.size() endl; 91 cout Ahora enteros3 contiene: ; 92 imprimirVector( enteros3 ); 93 } // fin de main 94 95 // imprime el contenido del vector 96 void imprimirVector( const vector int arreglo ) 97 { 98 for ( int elemento : elementos ) 99 cout elemento ; 100 101 cout endl; 102 } // fin de la función imprimirVector 103 104 // recibe el contenido del vector 105 void recibirVector( vector int arreglo ) 106 { 107 for ( int elemento : elementos ) 108 cin elemento; 109 } // fin de la función recibirVector El tamanio del vector enteros1 es 7 vector despues de la inicializacion: 0 0 0 0 0 0 0 El tamanio del vector enteros2 es 10 vector despues de la inicializacion: 0 0 0 0 0 0 0 0 0 0 Fig. 7.25  Demostración de la plantilla de clase vector de la Biblioteca estándar de C++ (parte 3 de 4).
  • 349. 7.10 Introducción a la plantilla de clase vector de la Biblioteca estándar de C++ 317 Escriba 17 enteros: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Despues de la entrada, los vectores contienen: enteros1: 1 2 3 4 5 6 7 enteros2: 8 9 10 11 12 13 14 15 16 17 Evaluacion: enteros1 != enteros2 enteros1 y enteros2 no son iguales El tamanio del vector enteros3 es 7 vector despues de la inicializacion: 1 2 3 4 5 6 7 Asignacion de enteros2 a enteros1: enteros1: 8 9 10 11 12 13 14 15 16 17 enteros2: 8 9 10 11 12 13 14 15 16 17 Evaluacion: enteros1 == enteros2 enteros1 y enteros2 son iguales enteros1[5] es 13 Asignacion de 1000 a enteros1[5] enteros1: 8 9 10 11 12 1000 14 15 16 17 Intento de mostrar enteros1.at( 15 ) An exception ocurred: invalid vectorT subscript El tamanio actual de enteros3 es: 7 El tamanio nuevo de enteros3 es: 8 Ahora enteros3 contiene: 1 2 3 4 5 6 7 1000 Creación de objetos vector En las líneas 14 y 15 se crean dos objetos vector que almacenan valores de tipo int: enteros1 contie- nesieteelementos,yenteros2 contiene10elementos.Demanerapredeterminada,todosloselementos de cada objeto vector se establecen en 0. Al igual que los arreglos, pueden definirse objetos vector para almacenar la mayoría de los tipos de datos, al sustituir int en vector int con el tipo de datos apro- piado. Función miembro size de vector; función imprimirVector En la línea 18 se utiliza la función miembro size de vector para obtener el tamaño (es decir, el núme- ro de elementos) de enteros1. En la línea 20 se pasa enteros1 a la función imprimirVector (líneas 96 a 102), la cual usa una instrucción for basada en rango para obtener el valor en cada elemento del vector para imprimirlo. Al igual que con la plantilla de clase array, es posible hacer esto mediante un ciclo controlado por contador y el operador de subíndice ([]).En las líneas 23 y 25 se realizan las mismas tareas para enteros2. Fig. 7.25  Demostración de la plantilla de clase vector de la Biblioteca estándar de C++ (parte 4 de 4).
  • 350. 318 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones Función recibirVector En las líneas 29 y 30 se pasan los objetos enteros1 y enteros2 a la función recibirVector (líneas 105 a 109) para que el usuario introduzca los valores de los elementos de cada vector. La función utiliza una instrucción for basada en rango, con una variable de rango que es una referencia a un int para formar lvalues que se utilizan para almacenar los valores de entrada en cada elemento vector. Comparación de desigualdad de objetos vector En la línea 41 se demuestra que los objetos vector se pueden comparar entre sí mediante el operador !=. Si el contenido de dos objetos vector no es igual, el operador devuelve true; en caso contrario devuelve false. Inicialización de un vector con el contenido de otro La plantilla de clase vector de la Biblioteca estándar de C++ nos permite crear un nuevo objeto vector que se inicializa con el contenido de un vector existente. En la línea 46 se crea un objeto vector llama- do enteros3 y se inicializa con una copia de enteros1. Esto invoca al constructor de copia de vector para realizar la operación de copia. En el capítulo 10 aprenderá con detalle acerca de los constructores de copia. En las líneas 48 a 50 se imprime el tamaño y el contenido de enteros3 para demostrar que se inicializó en forma correcta. Asignar objetos vector y comparar si dos objetos vector son iguales En la línea 54 se asigna enteros2 a enteros1, lo cual demuestra que el operador de asignación (=) se puede usar con objetos vector. En las líneas 56 a 59 se imprime el contenido de ambos objetos para mostrar que ahora contienen valores idénticos. Después, en la línea 64 se compara enteros1 con enteros2 mediante el operador de igualdad (==) para determinar si el contenido de los dos objetos es el mismo después de la asignación en la línea 54 (lo cual es cierto). Uso del operador [] para acceder a los elementos del vector y modificarlos En las líneas 68 y 70 se utilizan corchetes ([]) para obtener un elemento de vector y usarlo como rva- lue y lvalue, respectivamente. En la sección 5.9 vimos que un rvalue no se puede modificar, pero un lvalue sí. Como es el caso con los arreglos, C++ no realiza comprobación de límites cuando se accede a elementos de un objeto vector mediante el uso de corchetes.1 Por lo tanto, el programador debe asegurar que las operaciones en las que se utilizan los corchetes ([]) no traten accidentalmente de manipular elementos fuera de los límites del vector. Sin embargo, la plantilla de clase estándar vector cuenta con lacapacidaddecomprobarloslímitesensufunciónmiembroat (aligualquelaplantilladeclasearray), que usamos en la línea 80 y describiremos en breve. Manejo de excepciones: procesamiento de un subíndice fuera de rango Unaexcepciónindicaunproblemaqueocurremientrasseejecutaunprograma.Elnombre“excepción” sugiere que el problema ocurre con poca frecuencia; si la “regla” es que una instrucción se ejecuta nor- malmente en forma correcta, entonces el problema representa la “excepción a la regla”. El manejo de excepciones nos permite crear programas tolerantes a errores que puedan resolver (o manejar) excep- ciones. En muchos casos, esto permite a un programa seguirse ejecutando como si no hubiera encontra- do problemas. Por ejemplo, la figura 7.25 se ejecuta hasta completarse, aun cuando se trató de acceder a un subíndice fuera de rango. Los problemas más severos podrían evitar que un programa continúe su ejecución normal y tenga que notificar al usuario sobre el problema, para después terminar. Cuando una 1 Algunos compiladores tienen opciones para comprobación de límites y ayudar a evitar desbordamientos de búfer.
  • 351. 7.10 Introducción a la plantilla de clase vector de la Biblioteca estándar de C++ 319 función detecta un problema, como el subíndice inválido de un arreglo o un argumento inválido, lanza una excepción; es decir, ocurre una excepción. Aquí presentamos brevemente el manejo de excepciones. Hablaremos con detalle en el capítulo 17 (en el sitio web), Manejo de excepciones: un análisis más de- tallado. La instrucción try Para manejar una excepción, coloque el código que pudiera lanzar una excepción en una instrucción try (líneas 77 a 85). El bloque try (líneas 77 a 81) contiene el código que podría lanzar una excepción, y el bloque catch (líneas 82 a 85) contiene el código que maneja la excepción, en caso de que ocurra. Como veremos en el capítulo 17, puede tener varios bloques catch para manejar diferentes tipos de excepciones que podrían lanzarse en el bloque try correspondiente. Si el código en el bloque try se ejecuta con éxito, se ignoran las líneas 82 a 85. Las llaves que delimitan los cuerpos de los bloques try y catch son obligatorias. La función miembro at de vector cuenta con comprobación de límites y lanza una excepción si su argumento es un subíndice inválido. De manera predeterminada, esto hace que un programa de C++ termine. Si el subíndice es válido, la función at devuelve el elemento en la ubicación especificada como un lvalue modificable o un lvalue no modificable. Un lvalue no modificable es una expresión que iden- tifica a un objeto en memoria (como un elemento en un vector), pero no puede usarse para modificar ese objeto. Si se hace una llamada a at en un arreglo const o mediante una referencia declarada como const, la función devuelve un lvalue no modificable. Ejecución del bloque catch Cuando el programa llama a la función miembro at de vector con el argumento 15 (línea 80), la fun- ción intenta acceder al elemento en la ubicación 15, que se encuentra fuera de los límites del vector; enteros1 tiene sólo 10 elementos en este punto. Puesto que la comprobación de límites se realiza en tiempo de ejecución, la función miembro at de vector genera una excepción; específicamente, la línea 80 lanza una excepción out_of_range (del encabezado stdexcept) para notificar al programa sobre este problema. En este punto, el bloque try termina de inmediato y el bloque catch comienza a ejecu- tarse; si declaró variables en el bloque try, ahora están fuera de alcance y no pueden accederse en el bloque catch. Elbloquecatch declarauntipo(out_of_range)yunparámetrodeexcepción(ex)querecibecomo referencia. El bloque catch puede manejar excepciones del tipo especificado. Dentro del bloque puede usar el identificador del parámetro para interactuar con un objeto de excepción capturado. Función miembro what del parámetro de excepción Cuando las líneas 82 a 85 atrapan la excepción, el programa muestra un mensaje para indicar el proble- ma que ocurrió. La línea 84 llama a la función miembro what del objeto excepción para obtener el mensaje de error que está almacenado en este objeto y mostrarlo. Una vez que se muestra el mensaje en este ejemplo, se considera que se manejó la excepción y el programa continúa con la siguiente instruc- ción después de la llave de cierre del bloque catch. En este ejemplo, las líneas 88 a 92 se ejecutan a continuación. Usaremos el manejo de excepciones de nuevo en los capítulos 9 a 12; el capítulo 17 pre- senta un análisis más detallado sobre el manejo de excepciones. Cambiar el tamaño de un vector Una de las diferencias clave entre un vector y un array es que un vector puede crecer en forma diná- mica para dar cabida a más elementos. Para demostrar esto, en la línea 88 se muestra el tamaño actual de enteros3, en la línea 89 se llama a la función miembro push_back de vector para agregar un nuevo elementoquecontiene1000alfinaldelvector yenlalínea90semuestraelnuevotamañodeenteros3. Después en la línea 92 se muestra el nuevo contenido de enteros3.
  • 352. 320 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones C++11: Lista para inicializar un vector Muchos de los ejemplos con array en este capítulo utilizan incializadores de listas para especificar los valores iniciales de los elementos de un arreglo. C++11 también permite esto para los objetos vector (y otras estructuras de datos de la Biblioteca estándar de C++). Al momento de escribir este libro, no había soporte para los inicializadores de listas para objetos vector en Visual C++. 7.11Conclusión Enestecapítuloempezónuestraintroducciónalasestructurasdedatos,explorandoelusodelasplantillas de clase array y vector para almacenar datos y obtenerlos de listas y tablas de valores. Los ejemplos de este capítulo demostraron cómo declarar un arreglo, inicializarlo y hacer referencia a los elementos indi- viduales de un arreglo. Pasamos arreglos a las funciones por referencia y utilizamos el calificador const para evitar que la función a la que se llamó modificara los elementos del arreglo, para hacer valer el prin- cipio del menor privilegio. Aprendió a usar la nueva instrucción for basada en rango para manipular todosloselementosdeunarreglo.Tambiénlemostramoscómousarlasfuncionessort ybinary_search de la Biblioteca estándar de C++ para ordenar y buscar en un arreglo, respectivamente. Aprendió a decla- rar y manipular arreglos multidimensionales de arreglos. Utilizamos instrucciones for anidadas, contro- ladas por contador y basadas en rango, para iterar a través de todas las filas y columnas de un arreglo bi- dimensional. Además le mostramos cómo usar auto para inferir el tipo de una variable con base en su valor inicializador. Finalmente, demostramos las herramientas de la plantilla de clase vector de la Biblio- tecaestándardeC++.Eneseejemplo,vimoscómoaccederaloselementosdeobjetosarray yvector con la comprobación de límites y demostramos los conceptos básicos sobre manejo de excepciones. En capí- tulos posteriores continuaremos nuestra cobertura de las estructuras de datos. Ya le hemos presentado los conceptos básicos de las clases, los objetos, las instrucciones de control, las funciones y los objetos array. En el capítulo 8 presentaremos una de las herramientas más poderosas de C++: el apuntador. Los apuntadores llevan la cuenta de la ubicación en donde se almacenan los datos ylasfuncionesenmemoria,locualnospermitemanipularesoselementosenformasinteresantes.Como veremos más adelante, C++ también cuenta con un elemento del lenguaje conocido como arreglo (di- ferente de la plantilla de clase array), que está muy relacionado con los apuntadores. En el código de C++ contemporáneo, se considera una mejor práctica usar la plantilla de clase array de C++11 que los arreglos tradicionales. Resumen Sección 7.1 Introducción • Las estructuras de datos (pág. 279) son colecciones de elementos de datos relacionados. Los arreglos (pág. 279) son estructuras de datos que consisten en elementos de datos relacionados del mismo tipo. Los arreglos son entidades “estáticas”, en cuanto a que permanecen del mismo tamaño a lo largo de la ejecu- ción del programa. Sección 7.2 Arreglos • Un arreglo es un grupo consecutivo de localidades de memoria que comparten el mismo tipo. • Cadaarregloconocesupropiotamaño,elcualpuededeterminarsemedianteunallamadaasufunciónmiembro size (pág. 280). • Para hacer referencia a una ubicación específica o elemento en un arreglo, especificamos el nombre del arreglo y el número de posición del elemento específico en el arreglo. • Para hacer referencia a cualquiera de los elementos de un arreglo, un programa proporciona el nombre del arreglo seguido del índice (pág. 279) del elemento específico entre corchetes ([]).
  • 353. Resumen 321 • El primer elemento en cada arreglo tiene el índice cero (pág. 279), y algunas veces se le llama el elemento cero. • Un índice debe ser un entero o una expresión entera (que utilice cualquier tipo integral). • Los corchetes que se utilizan para encerrar el subíndice de un arreglo son un operador con la misma precedencia que los paréntesis. Sección 7.3 Declaración de arreglos • Los arreglos ocupan espacio en memoria. El programador especifica el tipo de cada elemento y el número de elementos requeridos de la siguiente manera: array tipo, tamañoArreglo nombreArreglo; y el compilador reserva la cantidad de memoria apropiada. • Los arreglos se pueden declarar de manera que contengan cualquier tipo de datos. Por ejemplo, un arreglo de tipo char se puede utilizar para almacenar una cadena de caracteres. Sección 7.4 Ejemplos acerca del uso de arreglos • Los elementos de un arreglo se pueden inicializar en la declaración de arreglos, seguida del nombre del arreglo con un signo de igual y una lista inicializadora (pág. 282): una lista separada por comas (encerrada entre llaves) de inicializadores constantes (pág. 282). • Al inicializar un arreglo con una lista inicializadora, si hay menos inicializadores que elementos en el arreglo, el resto de los elementos se inicializa con cero. El número de inicializadores debe ser menor o igual que el tamaño del arreglo. • Una variable constante que se utiliza para especificar el tamaño de un arreglo debe inicializarse con una expre- sión constante al momento de declararse, y no puede modificarse en lo sucesivo. • C++ no cuenta con comprobación de límites de los arreglos (pág. 291). Hay que asegurar que todas las referen- cias al arreglo permanezcan dentro de los límites del mismo. • Una variable local static en la definición de una función existe durante el tiempo que se ejecute el programa, pero sólo es visible en el cuerpo de la función. • Un programa inicializa los arreglos locales static la primera vez que encuentra sus declaraciones. Si el progra- mador no inicializa en forma explícita un arreglo static, el compilador inicializa con cero cada elemento de ese arreglo a la hora de crearlo. Sección 7.5 Instrucción for basada en rango • La nueva instrucción for basada en rango de C++ (pág. 293) nos permite manipular todos los elementos de un arreglo sin usar un contador, con lo cual se evita la posibilidad de “salirse” del arreglo y se elimina la necesidad de que implementemos nuestra propia comprobación de límites. • La sintaxis de una instrucción for basada en rango es: for ( declaracionVariableRango : expresion ) instruccion en donde declaracionVariableRango tiene un tipo y un identificador, y expresión es el arreglo a través del cual se va a iterar. El tipo en la declaracionVariableRango debe ser consistente con el tipo de los elementos del arreglo. El identificador representa los elementos sucesivos de un arreglo en iteraciones sucesivas del ciclo. Puede usar la instrucción for basada en rango con la mayoría de las estructuras de datos preconstruidas de la Biblioteca es- tándar de C++ (las que se conocen comúnmente como contenedores), incluyendo las clases array y vector. • Podemos usar una instrucción for basada en rango para modificar cada elemento, convirtiendo a definicionVa- riableRango en una referencia. • La instrucción for basada en rango puede usarse en vez de la instrucción for controlada por contador, cada vez que la iteración del código a través de un arreglo no requiera acceso al subíndice del elemento.
  • 354. 322 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones Sección 7.6 Caso de estudio: la clase LibroCalificaciones que usa un arreglo para almacenar las calificaciones • Las variables de clase (miembros de datos static, pág. 300) son compartidas por todos los objetos de la clase en la que se declaran las variables. • Se puede acceder a un miembro de datos static dentro de la definición de la clase y de las definiciones de las funciones miembro, al igual que con cualquier otro miembro de datos. • También se puede acceder a un miembro de datos public static desde el exterior de la clase, aún y cuando no existan objetos de la misma; para ello se utiliza el nombre de la clase, seguido del operador de resolución de ámbito binario (::) y el nombre del miembro de datos. Sección 7.7 Búsqueda y ordenamiento de datos en arreglos • Ordenar los datos (colocarlos en orden ascendente o descendente) es una de las aplicaciones de cómputo más importantes. • Al proceso de buscar un elemento específico de un arreglo se le denomina búsqueda. • La funcion sort de la Biblioteca estándar de C++ ordena los elementos de un arreglo en forma ascendente. Los argumentos de la función especifican el rango de elementos que deben ordenarse. Más adelante verá que la función sort puede usarse en otros tipos de contenedores también. • La función binary_search de la Biblioteca estándar de C++ determina si un valor está en un arreglo. La secuen- cia de valores debe ordenarse en forma ascendente primero. Los primeros dos argumentos de la función repre- sentan el rango de elementos a buscar y el tercero es la clave de búsqueda: el valor a localizar. La función devuel- ve un bool para indicar si se encontró el valor o no. Sección 7.8 Arreglos multidimensionales • Los arreglos multidimensionales (pág. 304) de dos dimensiones se utilizan con frecuencia para representar ta- blas de valores (pág. 304), las cuales consisten en información ordenada en filas y columnas. • Los arreglos que requieren dos subíndices para identificar a un elemento específico se llaman arreglos bidimen- sionales (pág. 304). Un arreglo con m filas y n columnas se llama arreglo de m por n (pág. 304). Sección 7.9 Caso de estudio: la clase LibroCalificaciones que usa un arreglo bidimensional • En una declaración de variable, puede usarse la palabra clave auto (pág. 306) en vez de un nombre de tipo, para inferir el tipo de la variable con base en el valor inicializador de la misma. Sección 7.10 Introducción a la plantilla de clase vector de la Biblioteca estándar de C++ • La plantilla de clase vector (pág. 314) de la Biblioteca estándar de C++ representa una alternativa más comple- ta a los arreglos, ya que cuenta con muchas capacidades que no se proporcionan para los arreglos basados en apuntador estilo C. • De manera predeterminada, todos los elementos de un objeto vector entero se establecen en 0. • Un vector se puede definir de manera que almacene cualquier tipo de datos, mediante el uso de una declaración como la siguiente: vector tipo nombre( tamaño ); • La función miembro size (pág. 317) de la plantilla de clase vector devuelve el número de elementos en el vector en el que se invoca. • Para acceder al valor de un elemento de un vector (o para modificarlo), se utilizan corchetes ([]). • Los objetos de la plantilla de clase estándar vector se pueden comparar de manera directa con los operadores de igualdad (==) y desigualdad (!=). El operador de asignación (=) también se puede usar con objetos vector. • Un lvalue no modificable es una expresión que identifica a un objeto en memoria (como un elemento en un vector), pero no se puede utilizar para modificar ese objeto. Un lvalue modificable también identifica a un objeto en memoria, pero se puede usar para modificar el objeto. • Una excepción (pág. 318) indica un problema que ocurre mientras se ejecuta un programa. El nombre “excep- ción” sugiere que el problema ocurre con poca frecuencia; si la “regla” es que una instrucción se ejecute normal- mente en forma correcta, entonces el problema representa la “excepción a la regla”.
  • 355. Ejercicios de autoevaluación 323 • El manejo de excepciones (pág. 318) nos permite crear programas tolerantes a errores (pág. 318) que pueden resolver excepciones. • Para manejar una excepción, coloque cualquier código que pueda lanzar una excepción (pág. 319) en una ins- trucción try. • El bloque try (pág. 319) contiene el código que podría lanzar una excepción, y el bloque catch (pág. 319) contiene el código que maneja la excepción, en caso de que ocurra. • Cuando termina un bloque try, las variables declaradas en ese bloque quedan fuera de alcance. • Un bloque catch declara un tipo y un parámetro de excepción. Dentro del bloque catch es posible usar el identificador del parámetro para interactuar con un objeto excepción atrapado. • El método what de un objeto excepción (pág. 319) devuelve el mensaje de error de la excepción. Ejercicios de autoevaluación 7.1 (Llene los espacios en blanco) Complete las siguientes oraciones: a) Las listas y tablas de valores pueden guardarse en __________ o ________. b) Los elementos de un arreglo están relacionados por el hecho de que tienen el mismo ________ y ________. c) El número utilizado para referirse a un elemento específico de un arreglo se conoce como el __________ de ese elemento. d) Un(a) __________ debe usarse para declarar el tamaño de un arreglo, ya que elimina los números mágicos. e) Al proceso de colocar los elementos de un arreglo en orden se le conoce como ______ el arreglo. f) Al proceso de determinar si un arreglo contiene un valor clave específico se le conoce como ________ el arreglo. g) Un arreglo que usa dos subíndices se conoce como un arreglo __________. 7.2 (Verdadero o falso) Conteste con verdadero o falso a cada una de las siguientes proposiciones; en caso de ser falso, explique por qué. a) Un arreglo puede guardar muchos tipos distintos de valores. b) El subíndice de un arreglo debe ser generalmente de tipo float. c) Si hay menos inicializadores en una lista inicializadora que el número de elementos en el arreglo, el resto de los elementos se inicializa con el último valor en la lista inicializadora. d) Es un error si una lista inicializadora contiene más inicializadores que elementos en el arreglo. 7.3 (Escriba instrucciones de C++) Escriba una o más instrucciones que realicen las siguientes tareas para un arreglo llamado fracciones: a) Defina una variable constante entera llamada tamanioArreglo para representar el tamaño de un arre- glo y que se inicialice con 10. b) Declare un arreglo con tamanioArreglo elementos de tipo double, e inicialice los elementos con 0. c) Nombre el cuarto elemento del arreglo. d) Haga referencia al elemento 4 del arreglo. e) Asigne el valor 1.667 al elemento 9 del arreglo. f) Asigne el valor 3.333 al séptimo elemento del arreglo. g) Imprima los elementos 6 y 9 del arreglo con dos dígitos de precisión a la derecha del punto decimal, y muestre los resultados que aparecen realmente en la pantalla. h) Imprima todos los elementos del arreglo usando una instrucción for. Defina la variable entera i como variable de control para el ciclo. Muestre la salida. i) Imprima todos los elementos del arreglo separados por espacios, usando una instrucción for basada en rango. 7.4 (Preguntas sobre arreglos bidimensionales) Responda a las siguientes preguntas en relación con un arreglo llamado tabla: a) Declare el arreglo para que almacene valores int y cuente con tres filas y tres columnas. Suponga que se ha declarado la variable tamanioArreglo con el valor de 3. b) ¿Cuántos elementos contiene el arreglo?
  • 356. 324 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones c) Utilice una instrucción for controlada por contador para inicializar cada elemento del arreglo con la suma de sus subíndices. d) Escriba una instrucción for anidada que muestre los valores de cada elemento del arreglo tabla en formato tabular, con 3 filas y 3 columnas. Cada fila y columna deben etiquetarse con el número de fila o columna correspondiente. Suponga que el arreglo se inicializó con una lista inicializadora que con- tiene los valores del 1 al 9 en orden. Muestre la salida. 7.5 (Encuentre el error) Encuentre y corrija el error en cada uno de los siguientes segmentos de programa: a) #include iostream; b) tamanioArreglo = 10; // tamanioArreglo se declaró como const c) Suponga que array int, 10 b = {}; for ( size_t i = 0; i = b.size(); ++i ) b[ i ] = 1; d) Suponga que a es un arreglo bidimensional de valores int con dos filas y dos columnas: a[ 1, 1 ] = 5; Respuestas a los ejercicios de autoevaluación 7.1 a) arreglos, objetos vector. b) nombre de arreglo, tipo. c) subíndice o índice. d) variable constante. e) ordenamiento. f) búsqueda. g) bidimensional. 7.2 a) Falso. Un arreglo sólo puede guardar valores del mismo tipo. b) Falso. El subíndice de un arreglo debe ser un entero o una expresión entera. c) Falso. El resto de los elementos se inicializa con cero. d) Verdadero. 7.3 a) const size_t tamanioArreglo = 10; b) array double, tamanioArreglo fracciones = { 0.0 }; c) fracciones[ 3 ] d) fracciones[ 4 ] e) fracciones[ 9 ] = 1.667; f) fracciones[ 6 ] = 3.333; g) cout fixed setprecision( 2 ); cout fracciones[ 6 ] ' ' fracciones[ 9 ] endl; Salida: 3.33 1.67 h) for ( size_t i = 0; i fracciones.size(); ++i ) cout fracciones[ i ] = fracciones[ i ] endl; Salida: fracciones[ 0 ] = 0.0 fracciones[ 1 ] = 0.0 fracciones[ 2 ] = 0.0 fracciones[ 3 ] = 0.0 fracciones[ 4 ] = 0.0 fracciones[ 5 ] = 0.0 fracciones[ 6 ] = 3.333 fracciones[ 7 ] = 0.0 fracciones[ 8 ] = 0.0 fracciones[ 9 ] = 1.667 i) for ( double element : fracciones ) cout element ' '; 7.4 a) array array int, tamanioArreglo , tamanio Arreglo tabla; b) Nueve.
  • 357. Ejercicios 325 c) for ( size_t fila = 0; fila tamanioArreglo(); ++fila ) for ( size_t columna = 0; columna tabla[ fila ].size(); ++columna ) tabla[ fila ][ columna ] = fila + columna; d) cout [0] [1] [2] endl; for ( size_t i = 0; i tamanioArreglo; ++i ) { cout '[' i ] ; for ( size_t j = 0; j tamanioArreglo; ++j ) cout setw( 3 ) tabla[ i ][ j ] ; cout endl; } Salida: [0] [1] [2] [0] 1 8 0 [1] 2 4 6 [2] 5 0 0 7.5 a) Error: punto y coma al final de la directiva del preprocesador #include. Corrección: elimina el punto y coma. b) Error: asignar un valor a una variable constante que utiliza una instrucción de asignación. Corrección: inicializa la variable constante en una declaración const size_t tamanioArreglo. c) Error: hacer referencia a un elemento del arreglo fuera de sus límites (b[10]). Corrección: cambie la condición de continuación de ciclo para usar en vez de =. d) Error: el subíndice del arreglo se escribió en forma incorrecta. Corrección: cambie la instrucción por a[ 1 ][ 1 ] = 5;. Ejercicios 7.6 (Llene los espacios en blanco) Complete las siguientes oraciones: a) Los nombres de los cuatro elementos del arreglo p son ______, ______, ______ y ______. b) Al proceso de nombrar un arreglo, declarar su tipo y especificar el número de elementos se le conoce como __________ el arreglo. c) Por convención, el primer subíndice en un arreglo bidimensional identifica el(la) __________ de un elemento y el segundo índice identifica el(la) __________ del elemento. d) Un arreglo de m por n contiene __________ filas, __________ columnas y __________ elementos. e) El nombre del elemento en la fila 3 y la columna 5 del arreglo d es __________. 7.7 (Verdadero o falso) Conteste con verdadero o falso a cada una de las siguientes proposiciones; en caso de ser falso, explique por qué. a) Para referirnos a una ubicación o elemento específico dentro de un arreglo, especificamos el nombre del arreglo y el valor del elemento específico. b) La definición de un arreglo reserva espacio para el mismo. c) Para indicar que deben reservarse 100 ubicaciones para el arreglo entero p, el programador escribe la declaración p[ 100 ]; d) Hayqueusarunainstrucciónfor parainicializarconceroloselementosdeunarreglode15elementos. e) Hay que usar instrucciones for anidadas para sumar el total de los elementos de un arreglo bidimen- sional. 7.8 (Escriba instrucciones en C++) Escriba instrucciones en C++ que realicen cada una de las siguientes tareas: a) Mostrar el valor del elemento 6 del arreglo de caracteres alfabeto. b) Recibir un valor y colocarlo en el elemento 4 del arreglo de punto flotante unidimensional llamado calificaciones. c) Inicializar con 8 cada uno de los 5 elementos del arreglo entero unidimensional.
  • 358. 326 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones d) Sumar el total e imprimir los elementos del arreglo temperaturas de punto flotante con 100 ele- mentos. e) Copiar el arreglo a en la primera parte del arreglo b. Suponga que ambos arreglos contienen valores double y que los arreglos a y b tienen 11 y 34 elementos, respectivamente. f) Determinar e imprimir los valores menor y mayor contenidos en el arreglo w con 99 elementos de punto flotante. 7.9 (Preguntas sobre arreglos bidimensionales) Considere un arreglo entero t de 2 por 3. a) Escriba una declaración para t. b) ¿Cuántas filas tiene t? c) ¿Cuántas columnas tiene t? d) ¿Cuántos elementos tiene t? e) Escriba los nombres de todos los elementos en la fila 1 de t. f) Escriba los nombres de todos los elementos en la columna 2 de t. g) Escriba una instrucción que asigne cero al elemento de t en la primera fila y la segunda columna. h) Escriba una serie de instrucciones que inicialice cada elemento de t con cero. No utilice un ciclo. i) Escriba una instrucción for anidada y controlada por contador, que inicialice cada elemento de t con cero. j) Escriba una instrucción for anidada y basada en rango, que inicialice cada elemento de t con cero. k) Escriba una instrucción que reciba como entrada los valores para los elementos de t desde el teclado. l) Escriba una serie de instrucciones que determine e imprima el valor más pequeño en el arreglo t. m) Escriba una instrucción que muestre los elementos en la fila 0 de t. n) Escriba una instrucción que totalice los elementos de la columna 2 de t. o) Escriba una serie de instrucciones para imprimir el contenido de t en formato tabular ordenado. En- liste los subíndices de columna como encabezados a lo largo de la parte superior, y enliste los subíndi- ces de fila a la izquierda de cada fila. 7.10 (Rangos de salarios de vendedores) Utilice un arreglo unidimensional para resolver el siguiente problema. Una compañía paga a sus vendedores por comisión. Los vendedores reciben $200 por semana más el 9% de sus ventastotalesdeesasemana.Porejemplo,unvendedorqueacumule$5000enventasenunasemana,recibirá$200 más el 9% de $5000, o un total de $650. Escriba un programa (utilizando un arreglo de contadores) que determi- ne cuántos vendedores recibieron salarios en cada uno de los siguientes rangos (suponga que el salario de cada vendedor se trunca a una cantidad entera): a) $200–299 b) $300–399 c) $400–499 d) $500–599 e) $600–699 f) $700–799 g) $800–899 h) $900–999 i) $1000 en adelante 7.11 (Preguntas sobre arreglos unidimensionales) Escriba instrucciones individuales que realicen las siguientes operaciones con arreglos unidimensionales: a) Inicializar con cero los 10 elementos del arreglo cuentas de tipo entero. b) Sumar uno a cada uno de los 15 elementos del arreglo bono de tipo entero. c) Leer 12 valores para el arreglo de valores double llamado temperaturasMensuales mediante el te- clado. d) Imprimir los 5 valores del arreglo entero mejoresPuntuaciones en formato de columnas. 7.12 (Encontrar los errores) Encuentre el (los) error(es) en cada una de las siguientes instrucciones: a) Asuma que a es un arreglo de tres valores int: cout a[ 1 ] a[ 2 ] a[ 3 ] endl; b) array double, 3 f = { 1.1, 10.01, 100.001, 1000.0001 };
  • 359. Ejercicios 327 c) Asuma que d es un arreglo de valores double con dos filas y 10 columnas. d[ 1, 9 ] = 2.345; 7.13 (Eliminacióndeduplicadoscon array)Useunarreglounidimensionalpararesolverelsiguienteproblema. Recibir como entrada 20 números, cada uno de los cuales debe estar entre 10 y 100, inclusive. A medida que se lea cada número, validarlo y almacenarlo en el arreglo, sólo si no es un duplicado de un número ya leído. Después de leer todos los valores, mostrar sólo los valores únicos que el usuario introdujo. Prepárese para el “peor caso”, en el que los 20 números son diferentes. Use el arreglo más pequeño que sea posible para resolver este problema. 7.14 (Eliminación de duplicados con vector) Reimplemente el ejercicio 7.13, usando ahora un vector. Co- mience con un vector vacío y use su función push_back para agregar cada valor único al vector. 7.15 (Inicialización de arreglos bidimensionales) Etiquete los elementos del arreglo bidimensional ventas de 3 por 5, para indicar el orden en el que se establecen en cero, mediante el siguiente fragmento de programa: for ( size_t fila = 0; fila ventas.size(); ++fila ) for ( size_t columna = 0; columna sales[ fila ].size(); ++columna ) ventas[ fila ][ columna ] = 0; 7.16 (Tirar dados) Escriba un programa para simular el tiro de dos dados. Después debe calcularse la suma de los dos valores. [Nota: cada dado puede mostrar un valor entero del 1 al 6, por lo que la suma de los valores variará del 2 al 12, siendo 7 la suma más frecuente, mientras que 2 y 12 serán las sumas menos frecuentes]. En la figura 7.26 se muestran las 36 posibles combinaciones de los dos dados. Su programa debe tirar los dados 36000 veces. Utilice un arreglo unidimensional para registrar el número de veces que aparezca cada una de las posibles sumas. Imprima los resultados en formato tabular. Determine además si los totales son razonables (es decir, hay seis formas de tirar un 7, por lo que aproximadamente una sexta parte de los tiros deben ser 7). 2 1 3 4 5 6 3 2 4 5 6 7 4 3 5 6 7 8 5 4 6 7 8 9 6 5 7 8 9 10 7 6 8 9 10 11 8 7 9 10 11 12 3 2 1 6 5 4 Fig. 7.26  Los 36 posibles resultados de tirar dos dados. 7.17 (¿Qué hace este código) ¿Qué hace el siguiente programa? 1 // Ej. 7.17: ej07_17.cpp 2 // ¿Qué hace este programa? 3 #include iostream 4 #include array 5 using namespace std; 6 7 const size_t tamanioArreglo = 10; 8 int queEsEsto( const array int, tamanioArreglo , size_t ); // prototipo 9 10 int main() 11 { 12 array int, tamanioArreglo a = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  • 360. 328 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones 13 14 int resultado = queEsEsto( a, tamanioArreglo ); 15 16 cout El resultado es resultado endl; 17 } // fin de main 18 19 // ¿Qué hace esta función? 20 int queEsEsto( const array int, tamanioArreglo b, size_t tamanio ) 21 { 22 if ( tamanio == 1 ) // caso base 23 return b[ 0 ]; 24 else // paso recursivo 25 return b[ tamanio - 1 ] + queEsEsto( b, tamanio - 1 ); 26 } // fin de la función queEsEsto 7.18 (Modificación al juego de Craps) Modifique el programa de la figura 6.11 para ejecutar 1000 juegos de craps. El programa debe mantener un registro de las estadísticas y responder a las siguientes preguntas: a) ¿Cuántos juegos se ganan en el primer tiro, en el segundo tiro, …, en el vigésimo tiro y después de éste? b) ¿Cuántos juegos se pierden en el primer tiro, en el segundo tiro, …, en el vigésimo tiro y después de éste? c) ¿Cuáles son las probabilidades de ganar en craps? [Nota: con el tiempo descubrirá que craps es uno de los juegos de casino más justos. ¿Qué cree usted que significa esto?] d) ¿Cuál es la duración promedio de un juego de craps? e) ¿Las probabilidades de ganar mejoran con la duración del juego? 7.19 (Conversión del ejemplo del vector de la sección 7.10 a un arreglo) Convierta el ejemplo de un vector de la figura 7.26 para que use arreglos. Elimine las características que sean sólo para vectores. 7.20 (¿Qué hace este código) ¿Qué hace el siguiente programa? 1 // Ej. 7.20: ej07_20.cpp 2 // ¿Qué hace este programa? 3 #include iostream 4 #include array 5 using namespace std; 6 7 const size_t tamanioArreglo = 10; 8 void unaFuncion( const array int, tamanioArreglo , size_t ); // prototipo 9 10 int main() 11 { 12 array int, tamanioArreglo a = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 13 14 cout Los valores en el arreglo son: endl; 15 unaFuncion( a, 0 ); 16 cout endl; 17 } // fin de main 18 19 // ¿Qué hace esta función? 20 void unaFuncion( const array int, tamanioArreglo b, size_t actual ) 21 { 22 if ( actual b.size() ) 23 { 24 unaFuncion( b, actual + 1 ); 25 cout b[ actual ] ; 26 } // fin de if 27 } // fin de la función unaFuncion
  • 361. Ejercicios 329 7.21 (Resumen de ventas) Use un arreglo bidimensional para resolver el siguiente problema: una compañía tiene cuatro vendedores (1 a 4) que venden cinco productos distintos (1 a 5). Una vez al día, cada vendedor pasa una nota por cada tipo de producto vendido. Cada nota contiene lo siguiente: a) El número del vendedor. b) El número del producto. c) El valor total en dólares de ese producto vendido en ese día. Así, cada vendedor pasa entre 0 y 5 notas de venta por día. Suponga que está disponible la información sobre todas las notas del mes pasado. Escriba un programa que lea toda esta información para las ventas del último mes (los datos de un vendedor a la vez) y que resuma las ventas totales por vendedor, por producto.Todos los totales deben guardarse en el arreglo bidimensional ventas. Después de procesar toda la información del mes pasado, muestre los resultados en formato tabular, en donde cada columna represente a un vendedor específico y cada fila represen- te a un producto. Saque el total de cada fila para obtener las ventas totales de cada producto durante el último mes. Saque el total de cada columna para obtener las ventas totales por cada vendedor durante el último mes. Su impre- sión tabular debe incluir estos totales cruzados a la derecha de las filas totalizadas, y en la parte inferior de las columnas totalizadas. 7.22 (Paseo del caballo) Uno de los enigmas más interesantes para los entusiastas del ajedrez es el problema del Paseo del caballo. La pregunta es: ¿Puede la pieza de ajedrez, conocida como caballo, moverse alrededor de un ta- blero de ajedrez vacío y tocar cada una de las 64 posiciones una y sólo una vez? A continuación estudiaremos deta- lladamente este intrigante problema. El caballo realiza solamente movimientos en forma de L (dos espacios en una dirección y un espacio en una dirección perpendicular). Por lo tanto, como se muestra en la figura 7.27, desde una posición cerca del centro de un tablero de ajedrez vacío, el caballo puede hacer ocho movimientos distintos (numerados del 0 al 7). 0 5 4 3 2 1 C 2 1 3 4 0 7 5 6 1 2 0 3 4 5 6 7 6 7 Fig. 7.27  Los ocho posibles movimientos del caballo. a) Dibuje un tablero de ajedrez de 8 por 8 en una hoja de papel, e intente realizar un Paseo del caballo en forma manual. Ponga un 1 en la posición inicial, un 2 en la segunda posición, un 3 en la tercera, etc. Antes de empezar el paseo, estime qué tan lejos podrá avanzar, recordando que un paseo completo consta de 64 movimientos. ¿Qué tan lejos llegó? ¿Estuvo esto cerca de su estimación? b) Ahora desarrollaremos un programa para mover el caballo alrededor de un tablero de ajedrez. El tablero estará representado por un arreglo bidimensional llamado tablero, de ocho por ocho. Cada posición se inicializará con cero. Describiremos cada uno de los ocho posibles movimientos en términos de sus componentes horizontales y verticales. Por ejemplo, un movimiento de tipo 0, como se muestra en la figura 7.27, consiste en mover dos posiciones horizontalmente a la derecha y una posición verticalmente hacia arriba. Un movimiento de tipo 2 consiste en mover una posición horizontalmente a la izquierda y dos posiciones verticalmente hacia arriba. Los movimientos hori- zontal a la izquierda y vertical hacia arriba se indican con números negativos. Los ocho movimientos
  • 362. 330 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones pueden describirse mediante dos arreglos unidimensionales llamados horizontal y vertical, de la siguiente manera: horizontal[ 0 ] = 2 vertical[ 0 ] = -1 horizontal[ 1 ] = 1 vertical[ 1 ] = -2 horizontal[ 2 ] = -1 vertical[ 2 ] = -2 horizontal[ 3 ] = -2 vertical[ 3 ] = -1 horizontal[ 4 ] = -2 vertical[ 4 ] = 1 horizontal[ 5 ] = -1 vertical[ 5 ] = 2 horizontal[ 6 ] = 1 vertical[ 6 ] = 2 horizontal[ 7 ] = 2 vertical[ 7 ] = 1 Haga que las variables filaActual y columnaActual indiquen la fila y columna, respectivamente, de la posición actual del caballo. Para hacer un movimiento de tipo numeroMovimiento, en donde numeroMovimiento puede estar entre 0 y 7, su programa debe utilizar las instrucciones filaActual += vertical[ numeroMovimiento ]; columnaActual += horizontal[ numeroMovimiento ]; Utilice un contador que varíe de 1 a 64. Registre la última cuenta en cada posición a la que se mueva el caballo. Evalúe cada movimiento potencial para ver si el caballo ya visitó esa posición y, desde luego, pruebe cada movimiento potencial para asegurarse que el caballo no se salga del tablero de ajedrez. Ahora escriba un programa para desplazar el caballo por el tablero. Ejecute el programa. ¿Cuántos movimientos hizo el caballo? c) Después de intentar escribir y ejecutar un programa de Paseo del caballo, probablemente haya desa- rrollado algunas ideas valiosas. Utilizaremos estas ideas para desarrollar una heurística (o estrategia) para mover el caballo. La heurística no garantiza el éxito, pero una heurística cuidadosamente desarro- llada mejora considerablemente la probabilidad de tener éxito. Probablemente usted ya observó que las posiciones externas son más difíciles que las posiciones cercanas al centro del tablero. De hecho, las posiciones más difíciles o inaccesibles son las cuatro esquinas. La intuición sugiere que usted debe intentar mover primero el caballo a las posiciones más pro- blemáticas y dejar pendientes aquellas a las que sea más fácil llegar, de manera que cuando el tablero se congestione cerca del final del paseo, habrá una mayor probabilidad de éxito. Podríamos desarrollar una “heurística de accesibilidad” clasificando cada una de las posiciones de acuerdo a qué tan accesibles son y luego mover siempre el caballo (usando los movimientos en L del caballo)alaposiciónmásinaccesible.Etiquetaremosunarreglobidimensionalllamadoaccesibilidad con números que indiquen desde cuántas posiciones es accesible una posición determinada. En un ta- blero de ajedrez en blanco, cada una de las 16 posiciones más cercanas al centro se clasifican con 8; cada posición en la esquina se clasifica con 2; y las demás posiciones tienen números de accesibilidad 3, 4 o 6, de la siguiente manera: 2 3 4 4 4 4 3 2 3 4 6 6 6 6 4 3 4 6 8 8 8 8 6 4 4 6 8 8 8 8 6 4 4 6 8 8 8 8 6 4 4 6 8 8 8 8 6 4 3 4 6 6 6 6 4 3 2 3 4 4 4 4 3 2 Escriba una nueva versión del programa del Paseo del caballo, utilizando la heurística de acce- sibilidad. El caballo deberá moverse siempre a la posición con el número de accesibilidad más bajo. En caso de un empate, el caballo podrá moverse a cualquiera de las posiciones empatadas. Por lo tanto, el paseo puede empezar en cualquiera de las cuatro esquinas. [Nota: al ir moviendo el caballo alrededor del tablero, su aplicación deberá reducir los números de accesibilidad a medida que se va- yan ocupando más posiciones. De esta manera y en cualquier momento dado durante el paseo, el número de accesibilidad de cada una de las posiciones disponibles seguirá siendo igual al número preciso de posiciones desde las que se puede llegar a esa posición]. Ejecute esta versión de su progra-
  • 363. Ejercicios 331 ma. ¿Logró completar el paseo? Ahora modifique el programa para realizar 64 paseos, en donde cada uno empiece desde una posición distinta en el tablero. ¿Cuántos paseos completos logró realizar? d) Escriba una versión del programa del Paseo del caballo que, al encontrarse con un empate entre dos o más posiciones, decida qué posición elegir buscando más adelante aquellas posiciones que se puedan alcanzar desde las posiciones “empatadas”. Su aplicación debe mover el caballo a la posición empatada para la cual el siguiente movimiento lo lleve a una posición con el número de accesibilidad más bajo. 7.23 (Paseo del caballo: métodos de fuerza bruta) En el ejercicio 7.22, desarrollamos una solución al problema del Paseo del caballo. El método utilizado, llamado “heurística de accesibilidad”, genera muchas soluciones y se ejecuta con eficiencia. A medida que se incremente de manera continua la potencia de las computadoras, seremos capaces de resolver más problemas con menos potencia y algoritmos relativamente menos sofisticados. A éste le podemos llamar el método de la “fuerza bruta” para resolver problemas. a) Utilice la generación de números aleatorios para permitir que el caballo se desplace a lo largo del table- ro (mediante sus movimientos legítimos en L) en forma aleatoria. Su programa debe ejecutar un paseo e imprimir el tablero final. ¿Qué tan lejos llegó el caballo? b) La mayoría de las veces, el programa anterior produce un paseo relativamente corto. Ahora modifique su aplicación para intentar 1000 paseos. Use un arreglo unidimensional para llevar el registro del nú- mero de paseos de cada longitud. Cuando su programa termine de intentar los 1000 paseos, deberá imprimir esta información en un formato tabular ordenado. ¿Cuál fue el mejor resultado? c) Es muy probable que el programa anterior le haya brindado algunos paseos “respetables”, pero no completos.Ahoradejequesuaplicaciónseejecutehastaqueproduzcaunpaseocompleto.[Precaución: esta versión del programa podría ejecutarse durante horas en una computadora poderosa]. Una vez más, mantenga una tabla del número de paseos de cada longitud e imprímala cuando se encuentre el primer paseo completo. ¿Cuántos paseos intentó su programa antes de producir uno completo? ¿Cuánto tiempo se tomó? d) Compare la versión de la fuerza bruta del Paseo del caballo con la versión heurística de accesibilidad. ¿Cuál requirió un estudio más cuidadoso del problema? ¿Qué algoritmo fue más difícil de desarro- llar? ¿Cuál requirió más poder de cómputo? ¿Podríamos tener la certeza (por adelantado) de obtener un paseo completo mediante el método de la heurística de accesibilidad? ¿Podríamos tener la certe- za (por adelantado) de obtener un paseo completo mediante el método de la fuerza bruta? Argumen- te las ventajas y desventajas de solucionar el problema mediante la fuerza bruta en general. 7.24 (Ocho reinas) Otro enigma para los entusiastas del ajedrez es el problema de las Ocho reinas, el cual pre- gunta lo siguiente: ¿es posible colocar ocho reinas en un tablero de ajedrez vacío, de tal manera que ninguna reina “ataque” a cualquier otra (es decir, que no haya dos reinas en la misma fila, en la misma columna o a lo largo de la misma diagonal)? Use la idea desarrollada en el ejercicio 7.22 para formular una heurística y resolver el problema de las Ocho reinas. Ejecute su programa. [Sugerencia: es posible asignar un valor a cada una de las posiciones en el tablero de ajedrez, para indicar cuántas posiciones de un tablero vacío se “eliminan” si una reina se coloca en esa posición. A cada una de las esquinas se le asignaría el valor 22, como se demuestra en la figura 7.28. Una vez que estos “números de eliminación” se coloquen en las 64 posiciones, una heurística apropiada podría ser la siguiente: coloque la siguiente reina en la posición con el número de eliminación más pequeño. ¿Por qué esta estrategia es intuitivamente atractiva?]. 7.25 (Ocho reinas: métodos de fuerza bruta) En este ejercicio usted desarrollará varios métodos de fuerza bruta para resolver el problema de las Ocho reinas que presentamos en el ejercicio 7.24. a) Utilice la técnica de la fuerza bruta aleatoria desarrollada en el ejercicio 7.23, para resolver el problema de las Ocho reinas. b) Utilice una técnica exhaustiva (es decir, pruebe todas las combinaciones posibles de las ocho reinas en el tablero). c) ¿Por qué supone que el método de la fuerza bruta exhaustiva podría no ser apropiado para resolver el problema del Paseo del caballo? d) Compare y contraste el método de la fuerza bruta aleatoria con el de la fuerza bruta exhaustiva en ge- neral.
  • 364. 332 Capítulo 7 Plantillas de clase array y vector; cómo atrapar excepciones * * * * * * * * * * * * * * * * * * * * * * Fig. 7.28  Las 22 posiciones eliminadas al colocar una reina en la esquina superior izquierda. 7.26 (Paseo del caballo: prueba del paseo cerrado) En el Paseo del caballo se lleva a cabo un paseo completo cuan- do el caballo hace 64 movimientos, en los que toca cada esquina del tablero una sola vez. Un paseo cerrado ocurre cuando el movimiento 64 se encuentra a un movimiento de distancia de la posición en la que el caballo empezó el paseo. Modifique el programa del Paseo del caballo que escribió en el ejercicio 7.22 para probar si el paseo ha sido completo, y si se trató de un paseo cerrado. 7.27 (La criba de Eratóstenes) Un entero primo es cualquier entero divisible sólo por sí mismo y por el número 1. La Criba de Eratóstenes es un método para encontrar números primos, el cual opera de la siguiente manera: a) Cree un arreglo con todos los elementos inicializados en 1 (verdadero). Los elementos del arreglo con subíndices primos permanecerán como 1. Cualquier otro elemento del arreglo eventualmente cam- biará a cero. En este ejercicio, ignoraremos los elementos 0 y 1. b) Empezandoconelsubíndice2delarreglo,cadavezqueseencuentreunelementodelarreglocuyovalorsea1, itere a través del resto del arreglo y asigne cero a todo elemento cuyo subíndice sea múltiplo del subíndice del elemento que tiene el valor 1. Para el subíndice 2 del arreglo, todos los elementos más allá del elemento 2 en el arreglo que tengan subíndices múltiplos de 2 (los índices 4, 6, 8, 10, etcétera) se establecerán en cero; para el subíndice 3 del arreglo, todos los elementos más allá del elemento 3 en el arreglo que sean múltiplos de3(losíndices6,9,12,15,etcétera)seestableceránencero;yasísucesivamente. Cuando este proceso termine, los elementos del arreglo que aún sean uno indicarán que el subíndice es un número primo. Estos subíndices pueden entonces imprimirse. Escriba un programa que utilice un arreglo de 1000 elemen- tos para determinar e imprimir los números primos entre 2 y 999. Ignore el elemento 0 del arreglo. Ejercicios de recursividad 7.28 (Palíndromos) Un palíndromo es una cadena que se escribe de la misma forma tanto al derecho como al revés. Algunos ejemplos de palíndromos son “radar”, “reconocer” y (si se ignoran los espacios) “anita lava la tina”. Escriba una función recursiva llamada probarPalindromo, que devuelva true si la cadena almacenada en el arreglo es un palíndromo, y false en caso contrario. Cabe mencionar que, al igual que un arreglo, es posible usar el ope- rador de corchetes ([]) para iterar a través de los caracteres en un objeto string. 7.29 (Ocho reinas) Modifique el programa de las Ocho reinas que creó en el ejercicio 7.24 para resolver el pro- blema en forma recursiva. 7.30 (Imprimir un arreglo) Escriba una función recursiva llamada imprimirArreglo que reciba un arreglo, un subíndiceinicialyunsubíndicefinalcomoargumentos,quenodevuelvanadayqueimprimaelarreglo.Lafunción deberá dejar de procesar y deberá regresar cuando el subíndice inicial sea igual al subíndice final. 7.31 (Imprimir una cadena en forma inversa) Escriba una función recursiva llamada cadenaInversa, que reciba un arreglo de caracteres que contenga una cadena y un subíndice inicial como argumentos, imprima la cadena en forma inversa y no devuelva nada. La función deberá dejar de procesar y deberá regresar al encontrar la cadena nula de terminación. Cabe mencionar que, al igual que un arreglo, es posible usar el operador de corchetes ([]) para iterar a través de los caracteres en un objeto string.
  • 365. Hacer la diferencia 333 7.32 (Buscar el valor mínimo en un arreglo) Escriba una función recursiva llamada minimoRecursivo que reci- ba un arreglo de enteros, un subíndice inicial y un subíndice final como argumentos, y que devuelva el elemento más pequeño del arreglo. La función deberá dejar de procesar y deberá regresar al encontrar la cadena nula de ter- minación. 7.33 (Recorrido de laberinto) La cuadrícula de signos # y puntos (.) en la figura 7.29 es la representación de un arreglo bidimensional integrado de un laberinto. En este arreglo bidimensional integrado, los signos # representan las paredes del laberinto y los puntos representan posiciones en las posibles rutas por el laberinto. Sólo pueden hacerse movimientos hacia una ubicación en el arreglo integrado que contenga un punto. Hay un algoritmo simple para recorrer un laberinto que garantiza encontrar la salida (suponiendo que la haya). Si no hay una salida, volverá a la misma ubicación inicial de nuevo. Coloque su mano derecha en la pared a su derecha y comience a caminar hacia delante. Nunca quite su mano de la pared. Si el laberinto da vuelta a la derecha, siga la pared a su derecha. Mientras no quite su mano de la pared, finalmente llegará a la salida del labe- rinto. Puede haber una ruta más corta que la que usted eligió, pero se garantiza que saldrá del laberinto si sigue el algoritmo. # # # # # # # # # # # # # . . . # . . . . . . # . . # . # . # # # # . # # # # . # . . . . # . # # . . . . # # # . # . . # # # # . # . # . # . # # . . # . # . # . # . # # # . # . # . # . # . # # . . . . . . . . # . # # # # # # # . # # # . # # . . . . . . # . . . # # # # # # # # # # # # # Fig. 7.29  Representación de un laberinto en un arreglo integrado bidimensional. Escriba la función recursiva recorrerLaberinto para caminar por el laberinto. La función debe recibir argu- mentos que incluyan un arreglo integrado de 12 por 12 de valores char, que representan el laberinto y la ubicación inicial del mismo. A medida que recorrerLaberinto intente localizar la salida del laberinto, debe colocar el carác- ter X en cada posición en la ruta. La función debe mostrar el laberinto después de cada movimiento, para que el usuario pueda observar a medida que se vaya resolviendo. 7.34 (Generación de laberintos al azar) Escriba una función llamada generadorLaberinto que produzca un laberinto al azar. La función debe recibir como argumentos un arreglo bidimensional integrado de 12 por 12 de valores char y apuntadoras a las variables int que representan la fila y la columna del punto de entrada del laberin- to. Pruebe su función recorrerLaberinto del ejercicio 7.33, usando varios laberintos generados al azar. Hacer la diferencia 7.35 (Votaciones)) InternetyWebpermitenquecadavezhayamáspersonasconectadasenred,unidasporunacausa, expresen sus opiniones, etcétera. En 2012, los candidatos presidenciales usaron Internet para expresar sus mensajes y recaudar dinero para sus campañas. En este ejercicio, usted escribirá un pequeño programa de votaciones que permite a los usuarios calificar cinco asuntos de conciencia social, desde 1 (menos importante) hasta 10 (más importante). Elija cinco causas (por ejemplo, asuntos políticos, asuntos sobre el entorno global). Use un arreglo string unidimensional llamado temas para almacenar las causas. Para sintetizar las respuestas de la encuesta, use un arreglo bidimensional de 5filasy10columnasllamadorespuestas (detipoint),endondecadafilacorrespondaaunelementodelarreglotemas. Cuandoseejecuteelprograma,debepediralusuarioquecalifiquecadaasunto.Hagaquesusamigosyfamiliaresrespon- dan a la encuesta. Después haga que el programa muestre un resumen de los resultados, incluyendo: a) Un informe tabular con los cinco temas del lado izquierdo y las 10 calificaciones a lo largo de la parte superior, listando en cada columna el número de calificaciones recibidas para cada tema. b) A la derecha de cada fila, muestre el promedio de las calificaciones para cada asunto específico. c) ¿Qué asunto recibió la mayor puntuación total? Muestre tanto el asunto como la puntuación total. d) ¿Qué asunto recibió la menor puntuación total? Muestre tanto el asunto como la puntuación total.
  • 366. Apuntadores 8 Recibimos direcciones para ocultar nuestro paradero. —Saki (H. H. Munro) Averigua la dirección mediante la indirección. —William Shakespeare Muchas cosas, teniendo referencia completa A un consentimiento, pueden trabajar en forma contraria —William Shakespeare ¡Descubrirá que es una muy buena práctica verificar siempre sus referencias, señor! —Dr. Routh O b j e t i v o s En este capítulo aprenderá a: n Distinguir qué son los apuntadores. n Conocer las similitudes y diferencias entre los apuntadores y las referencias. n Utilizar apuntadores para pasar argumentos a las funciones por referencia. n Conocer las estrechas relaciones entre los apuntadores y los arreglos integrados. n Utilizar cadenas basadas en apuntador. n Utilizar los arreglos integrados. n Utilizar las herramientas de C++11, incluyendo nullptr y las funciones begin y end de la Biblioteca estándar.
  • 367. 8.2 Declaraciones e inicialización de variables apuntadores 335 8.1Introducción En este capítulo hablaremos sobre los apuntadores: una de las características más poderosas y desafiantes de usar del lenguaje de programación C++. Nuestros objetivos aquí son ayudarle a determinar cuándo es apropiado usar apuntadores y mostrarle cómo usarlos de manera correcta y responsable. En el capítulo 6, vimos que se pueden utilizar referencias para realizar el paso por referencia. Los apuntadores también permiten el paso por referencia, y se pueden utilizar para crear y manipular estruc- turas dinámicas de datos que pueden crecer y reducirse, como las listas enlazadas, colas, pilas y árboles. En este capítulo explicaremos los conceptos básicos sobre los apuntadores. En el capítulo 19 (en inglés en el sitio web) presentaremos ejemplos de cómo crear y usar estructuras de datos dinámicas que se implementan con apuntadores. También le mostraremos la estrecha relación entre los arreglos integrados y los apuntadores. C++ heredó los arreglos integrados del lenguaje de programación C. Como vimos en el capítulo 7, las clases array y vector de la Biblioteca estándar de C++ proporcionan implementaciones de los arreglos como objetos completos; de hecho, tanto array como vector almacenan sus elementos en arreglos integra- dos. En los proyectos nuevos de desarrollo de software, es mejor usar objetos array y vector en vez de los arreglos integrados. De manera similar, C++ en realidad ofrece dos tipos de cadenas: objetos de la clase string (que hemos estado usando desde el capítulo 3) y cadenas basadas en apuntador, estilo C (cadenas de C). Este capítulo introduce brevemente las cadenas C para que el lector amplíe su conocimiento sobre los apun- tadores y los arreglos integrados. Las cadenas de C se utilizaban ampliamente en software antiguo de C y C++. Hablaremos con detalle sobre ellas en el apéndice F. En los proyectos nuevos de desarrollo de soft- ware, es mejor usar objetos de la clase string. En el capítulo 12 examinaremos el uso de los apuntadores con los objetos de clases, en donde vere- mos que lo que se denomina “procesamiento polimórfico” de la programación orientada a objetos se lleva a cabo mediante apuntadores y referencias. 8.2Declaraciones e inicialización de variables apuntadores Indirección Las variables apuntadores contienen direcciones de memoria como sus valores. Por lo general, una varia- ble contiene directamente un valor específico. Un apuntador contiene la dirección de memoria de una variable que, a su vez, contiene un valor específico. En este sentido, el nombre de una variable hace re- ferencia directa a un valor, y un apuntador hace referencia indirecta a un valor (figura 8.1). Al 8.1 Introducción 8.2 Declaraciones e inicialización de variables apuntadores 8.3 Operadores de apuntadores 8.4 Paso por referencia mediante apuntadores 8.5 Arreglos integrados 8.6 Uso de const con apuntadores 8.6.1 Apuntador no constante a datos no constantes 8.6.2 Apuntador no constante a datos constantes 8.6.3 Apuntador constante a datos no constantes 8.6.4 Apuntador constante a datos constantes 8.7 Operador sizeof 8.8 Expresiones y aritmética de apuntadores 8.9 Relación entre apuntadores y arreglos integrados 8.10 Cadenas basadas en apuntador 8.11 Conclusión Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios | Sección especial: construya su propia computadora
  • 368. 336 Capítulo 8 Apuntadores proceso de hacer referencia a un valor a través de un apuntador se le conoce comúnmente como indi- rección. Por lo general, los diagramas representan un apuntador en forma de una flecha que parte de la variable que contiene una dirección, hasta la variable ubicada en esa dirección de memoria. 7 El apuntador cuentaPtr hace referencia indirecta a una variable que contiene el valor 7 cuenta cuentaPtr 7 cuenta hace referencia directa a una variable que contiene el valor 7 cuenta Fig. 8.1  Referencia directa e indirecta a una variable. Declaración de apuntadores Al igual que las demás variables, los apuntadores se deben declarar antes de poder usarlos. Por ejemplo, para el apuntador en la figura 8.1, la declaración int *cuentaPtr, cuenta; declara la variable cuentaPtr como de tipo int * (es decir, un apuntador a un valor int) y se lee así (de derecha a izquierda): “cuentaPtr es un apuntador a un int”. Además, la variable cuenta en la decla- ración anterior se declara como un int, y no como un apuntador a un int. El * en la declaración se aplica sólo a cuentaPtr. A cada variable que se declara como apuntador se le debe anteponer un asterisco (*). Por ejemplo, la declaración double *xPtr, *yPtr; indica que tanto xPtr como yPtr son apuntadores a valores double. Cuando el * aparece en una decla- ración, no es un operador; en vez de ello, indica que la variable que se está declarando es un apuntador. Los apuntadores se pueden declarar de manera que apunten a objetos de cualquier tipo de datos. Error común de programación 8.1 Asumirqueel* queseutilizaparadeclararaunapuntadorsedistribuyeatodoslosnombres de variables en la lista separada por comas de variables de una declaración puede provocar errores. Cada apuntador se debe declarar con el * antepuesto al nombre (ya sea con o sin un espacio entre ellos). Si declaramos sólo una variable por cada declaración, evitamos estos tipos de errores y mejoramos la legibilidad de los programas. Buena práctica de programación 8.1 Aunque no es un requerimiento, incluir las letras Ptr en los nombres de las variables apunta- dores deja claro que estas variables son apuntadores, y que deben tratarse de manera acorde. Inicialización de apuntadores Los apuntadores se deben inicializar con nullptr (nuevo en C++11) o con una dirección del tipo co- rrespondiente, ya sea cuando se declaran o en una asignación. Un apuntador con el valor nullptr “no apunta a nada”, y se conoce como apuntador nulo. De aquí en adelante, cuando hagamos referencia a un “apuntador nulo” nos estaremos refiriendo a un apuntador con el valor nullptr. Tip para prevenir errores 8.1 Inicialice todos los apuntadores para evitar que apunten hacia áreas desconocidas o no inicia- lizadas de la memoria.
  • 369. 8.3 Operadores de apuntadores 337 Apuntadores nulos anteriores a C++11 En versiones anteriores de C++, el valor especificado para un apuntador nulo era 0 o NULL. Este último se define en varios encabezados de la biblioteca estándar para representar el valor 0. Inicializar un apuntador a NULL es equivalente a inicializar un apuntador a 0, pero antes de C++11, se utilizaba 0 por convención. El 0 es el único valor entero que puede asignarse directamente a una variable apuntador sin primero con- vertir el entero a un tipo apuntador. 8.3Operadores de apuntadores Operador dirección () El operador dirección () es un operador unario que obtiene la dirección de memoria de su operando. Por ejemplo, teniendo en cuenta las siguientes declaraciones: int y = 5; // declara la variable y int *yPtr = nullptr; // declara la variable apuntador yPtr la instrucción yPtr = y; // asigna la dirección de y a yPtr asigna la dirección de la variable y a la variable apuntador yPtr. Entonces, se dice que la variable yPtr “apunta a” y. Ahora, yPtr hace referencia indirecta al valor de la variable y. Observe que el uso del signo en la instrucción anterior no es el mismo que el uso del en la declaración de una variable de referencia, a la cual siempre se le antepone el nombre de un tipo de datos. Al declarar una referencia, el forma parte del tipo. En una expresión como y, el es el operador dirección. La figura 8.2 muestra una representación de la memoria después de la asignación anterior. La “relación de señalamiento” se indica mediante el dibujo de una flecha desde el cuadro que representa al apuntador yPtr en la memoria, hasta el cuadro que representa a la variable y en la memoria. La figura 8.3 muestra otra representación del apuntador en memoria, con la variable entera y alma- cenada en la ubicación de memoria 600000 y la variable apuntador yPtr almacenada en la ubicación de memoria 500000. El operando del operador dirección debe ser un lvalue; el operador dirección no se puede aplicar a constantes o expresiones que produzcan valores temporales (como los resultados de los cálculos). 5 y yPtr Fig. 8.2  Representación gráfica de un apuntador que apunta a una variable en memoria. 5 y 600000 ubicación 500000 yPtr ubicación 600000 Fig. 8.3  Representación de y y yPtr en memoria. Operador de indirección (*) El operador * unario, que se conoce comúnmente como el operador de indirección u operador de desreferencia, devuelve un lvalue que representa al objeto al que apunta su operando apuntador. Por ejem- plo (haciendo referencia otra vez a la figura 8.2), la instrucción
  • 370. 338 Capítulo 8 Apuntadores cout *yPtr endl; imprime el valor de la variable y (en este caso, 5), al igual que la instrucción. cout y endl; Alprocesodeutilizarel* deestamanera,seleconocecomodesreferenciarun apuntador.Unapuntador desreferenciado también se puede usar en el lado izquierdo de una instrucción de asignación, como en *yPtr = 9; lo cual asignaría 9 a y en la figura 8.3. El apuntador desreferenciado también se puede utilizar para recibir un valor de entrada, como en ci