Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
leer
ndice general
1 Introduccin 1
1.1 Conceptos Bsicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
[1]
1.1.1 Qu es un Lenguaje de Programacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Historia de C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2.1 Qu es C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Herramientas Necesarias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 Consejos iniciales antes de programar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.5 Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.6 Referencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Lo ms bsico 4
2.1 Estructura de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.1 Directivas de preprocesador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.2 Declaraciones globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.1.3 Declaracin de funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 Proceso de desarrollo de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2.1 Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.2 Utilizacin de la consola o terminal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3 Sintaxis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.1 El punto y coma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.2 Espacios y tabuladores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4 Tipos primitivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4.1 Espacio que ocupan la variables (en mquinas x86) . . . . . . . . . . . . . . . . . . . . . . . 12
2.5 Tipos enumerados (enum) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.6 Tipos denidos por el usuario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.7 Variables y constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3 Iteraciones y decisiones 15
3.1 Sentencias de decisin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2 Sentencia if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
i
ii NDICE GENERAL
4 Estructuras 22
5 Estructuras de datos 23
5.1 Estructuras bsicas en C, C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.1.1 variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.2 Matrices o Arreglos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.2.1 Matrices estticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.2.2 Acceso a los miembros de una matriz de datos: . . . . . . . . . . . . . . . . . . . . . . . 25
5.2.3 Matrices dinmicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.3 Estructuras compuestas (struct, union, class) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.3.1 Acceso a los miembros de una estructura . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
5.3.2 Estructuras anidadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.3.3 Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.3.4 Estructura de campos de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.4 union: Sintaxis general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.5 class: sintaxis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.6 struct vs. class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6 Funciones 31
7 Funciones 32
7.1 Deniendo una funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
7.2 Parmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
7.3 Llamar a una funcin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
7.4 Funciones void . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
7.5 Funciones anidadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7.6 Funciones de tipo puntero (*) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7.7 Variables estticas y automticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7.8 Parmetros constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7.9 Parmetros con valor por defecto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
NDICE GENERAL iii
8 Streams 38
13 Objetos y Clases 52
13.1 Clases y Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
13.2 Fundamentos de Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
13.2.1 Resolucin de mbito . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
13.2.2 Acceso a las funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
iv NDICE GENERAL
13.2.3 Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
13.3 Miembros de una clase ( mtodos y atributos ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
13.4 Visibilidad de los miembros de una clase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
13.5 Subclases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
13.6 Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
13.6.1 Herencia por extensin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
13.6.2 Agregacion o composicin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
13.7 Constructores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
13.8 Destructores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
14 Sobrecarga de Operadores 59
14.1 Sobrecarga de operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
14.2 Mi primer sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
14.3 Sintaxis general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
14.4 Sobrecarga permitida de operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
14.5 Sobrecarga del operador << ( iostream ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
14.6 Sobrecarga del operador >> ( istream ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
14.7 Operadores amigos ( friend ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
14.8 Sobrecarga de operadores dentro de una clase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
15 Herencia 63
15.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
15.2 INTRODUCIENDO LA HERENCIA. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
15.3 CONTROL DE ACCESO DE LA CLASE BASE. . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
15.3.1 USANDO MIEMBROS PROTEGIDOS. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
15.3.2 USANDO PROTECTED PARA LA HERENCIA DE UNA CLASE BASE. . . . . . . . . . . 68
15.3.3 REVISANDO public, protected, y private . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
15.3.4 HEREDANDO MULTIPLES CLASES BASE . . . . . . . . . . . . . . . . . . . . . . . . . 68
15.3.5 CONSTRUCTORES, DESTRUCTORES, Y HERENCIA . . . . . . . . . . . . . . . . . . . 69
15.3.6 PASANDO PARAMETROS A LOS CONSTRUCTORES DE LA CLASE BASE . . . . . . . 70
15.3.7 GARANTIZANDO ACCESO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
15.3.8 LEYENDO GRAFICOS DE HERENCIA EN c++ . . . . . . . . . . . . . . . . . . . . . . . 72
15.3.9 CLASES BASE VIRTUALES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
16 Funciones virtuales 74
16.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
16.2 PUNTEROS A TIPOS DERIVADOS. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
16.2.1 REFERENCIAS A TIPOS DERIVADOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
16.2.2 FUNCIONES VIRTUALES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
NDICE GENERAL v
17 Punteros 82
17.1 Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
17.1.1 Sintaxis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
17.1.2 Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
17.1.3 Punteros y vectores (arrays) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
17.1.4 Aritmtica de Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
17.1.5 Matrices de punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
17.1.6 Formas de pasar un valor por referencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
17.1.7 Punteros a funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
17.1.8 Ordenamiento burbuja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
17.1.9 Proyecto de colas para un banco . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
18 Estructuras II 87
19 Introduccin 88
19.1 Pilas o Stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
19.1.1 Pila en arreglo esttico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
19.1.2 Pila dinmica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
19.2 Colas o Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
19.2.1 Cola en un arreglo esttico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
19.3 Colas de doble enlace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
20 Plantillas 94
21 Plantillas 95
21.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
21.2 Un paso hacia adelante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
21.3 Una mejor solucin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
21.4 La clase vector desde la perspectiva de la POO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
21.5 Una plantilla para la clase vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
22 Excepciones 99
28 Vectores 111
28.1 C++ vector estndar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
28.2 Colas de doble n ( deque ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
28.3 Tabla de mtodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
29 Pilas 113
30 Colas 114
30.1 Origen del texto y las imgenes, colaboradores y licencias . . . . . . . . . . . . . . . . . . . . . . . . 115
30.1.1 Texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
30.1.2 Imgenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
30.1.3 Licencia del contenido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Captulo 1
Introduccin
1. C
2. C++
3. Basic
4. Ada
5. Java
6. Pascal
7. Python
8. Fortran
9. Smalltalk
1
2 CAPTULO 1. INTRODUCCIN
Sin embargo, aunque esto en un inicio se puede convertir en un problema, en la prctica es su mayor virtud, ya que permite
al programador un mayor control sobre lo que est haciendo. Aos ms tarde, un programador llamado Bjarne Stroustrup,
creo lo que se conoce como C++.
Necesitaba ciertas facilidades de programacin, incluidas en otros lenguajes pero que C no soportaba, al menos direc-
tamente, como son las llamadas clases y objetos, principios usados en la programacin actual. Para ello redise C,
ampliando sus posibilidades pero manteniendo su mayor cualidad, la de permitir al programador en todo momento tener
controlado lo que est haciendo, consiguiendo as una mayor rapidez que no se conseguira en otros lenguajes.
C++ pretende llevar a C a un nuevo paradigma de clases y objetos con los que se realiza una comprensin ms humana
basndose en la construccin de objetos, con caractersticas propias solo de ellos, agrupados en clases. Es decir, si yo
quisiera hacer un programa sobre animales, creara una clase llamada animales, en la cual cada animal, por ejemplo un
pato, sera un objeto, de tal manera que se ve el intento de esta forma de programar por ser un el reejo de cmo los
humanos (en teora) manejamos la realidad[referencia].
Se dice que nuestro cerebro trabaja de forma relacional[referencia] (relacionando hechos), es por ello que cada vez que
recuerdas algo, (cuentas un hecho), termina siendo diferente (se agregan u omiten partes).
1.2.1 Qu es C++
C++ es un lenguaje de programacin orientado a objetos que toma la base del lenguaje C y le agrega la capacidad de
abstraer tipos como en Smalltalk.
La intencin de su creacin fue el extender al exitoso lenguaje de programacin C con mecanismos que permitieran la
manipulacin de objetos. En ese sentido, desde el punto de vista de los lenguajes orientados a objetos, el C++ es un
lenguaje hbrido [por? fusionar con el siguiente].
Posteriormente se aadieron facilidades de programacin genrica, que se sum a los otros dos paradigmas que ya estaban
admitidos (programacin estructurada y la programacin orientada a objetos). Por esto se suele decir que el C++ es un
lenguaje de programacin multiparadigma.
ii. Kate
iii. KDevelop
iv. Code::Blocks
v. SciTE
vi. GVim
(c) Mac:
i. Xcode (con el compilador trae una IDE para poder programar)
Adicional
1. Ingls (Recomendado)
Es recomendable tener conocimientos de C, debido a que C++ es una mejora de C, tener los conocimientos sobre este te
permitira avanzar mas rapido y comprender aun mas. Tambien, hay que recordar que C++, admite C, por lo que se puede
programar (reutilizar), funciones de C que se puedan usar en C++.
Aunque No es obligacion aprender C, es recomendable tener nociones sobre la programacin orientada a objetos en el
caso de no tener conocimientos previos de programacin estructurada. Asimismo, muchos programadores recomiendan
no saber C para saber C++, por ser el primero de ellos un lenguaje imperativo o procedimental y el segundo un lenguaje
de programacin orientado a objetos.
1.5 Ejemplos
/* Programa que nos permite saber si el nmero ingresado es par o no */ #include <stdio.h> #include <conio.h> int main
() { int numero; printf (Ingrese el valor de numero: "); scanf ("%d, &numero); if(numero%2 == 0) { printf ("\n***El
numero es par\n); } else { printf ("\n***El numero es impar\n); } getch(); }
1.6 Referencias
[1] Lenguaje de Programacion Denicion de Wikipedia
Captulo 2
Lo ms bsico
1. Directivas de preprocesador
2. declaraciones globales
3. declaracin de funciones
Los compiladores de C++ proporcionan bibliotecas de funciones, y cada biblioteca de funciones tiene asociada un archivo
de denicin que se denomina cabecera. Para utilizar algo de una biblioteca en un programa (por ejemplo, una funcin),
hay que colocar al principio del programa una directiva de preprocesamiento seguida de la cabecera de la biblioteca entre
signos de menor que y mayor que (<>).
A continuacin se muestra un tpico ejemplo de una directiva de preprocesador:
#include <iostream>
En el ejemplo anterior, la directiva invocada es include y la cabecera iostream, e indican al preprocesador que debe incluir
la libreria iostream a nuestro programa. Cabe sealar que todas las directivas comienzan con el simbolo numeral #. Dentro
de las directivas de preprocesdor se encuentran:
3. Control (#line)
4. Error (#error)
6. Pragma (#pragma)
4
2.1. ESTRUCTURA DE UN PROGRAMA 5
Macros de preprocesador
Cuando el procesador encuentra esta directiva, este reemplaza todas las ocurrencias del identicador y son sustituidas por
reemplazo. Cuando se requiere desactivar una macro, a partir de cierta parte del cdigo, se utiliza la directiva #undef. La
sintaxis es:
#undef nombre_macro
Inclusiones condicionales
Estas directivas permiten incluir o descartar partes de codigo, si se cumplen algunas condiciones.
#ifdef: Permite que una seccion del programa sea compilada solo si la macro especicada como parametro ha sido
denida, sin importar el valor de esta. Por ejemplo:
El ejemplo anterior indica que, si la macro TABLE_SIZE se encuentra denida, entonces procede a la creacin de una
matriz de enteros de tamao TABLE_SIZE. Cabe sealar que el n para esta directiva es la directiva #endif
#ifndef: Permite realizar exactamente todo lo contrario a #ifdef. La lineas de cdigo que se encuentren entre #ifndef
y #endif, sern ejecutadas siempre y cuando la macro sealada como parmetro de #ifndef no se encuentre denida
aun. Por ejemplo:
En el ejemplo anterior, se indica que si la macro TABLE_SIZE no est denida an, entonces se dene. Cabe sealar que
el n para la directiva #ifndef es, al igual que #ifdef, #endif.
#if, #else y #elif (o else if): Estas directivas tienen el mismo signicado que los comandos condicionales de cualquier
lenguaje de programacion. Por ejemplo:
#if TABLE_SIZE>200 #undef TABLE_SIZE #dene TABLE_SIZE 200 #elif TABLE_SIZE<50 #undef TABLE_SIZE
#dene TABLE_SIZE 50 #else #undef TABLE_SIZE #dene TABLE_SIZE 100 #endif int table[TABLE_SIZE];
En el ejemplo anterior, se muestra el uso de todas las directivas condicionales del preprocesador.
Control
Cuando compilamos un programa y un error ocurre durante el proceso de compilacin, el compilador muestra un mensaje
de error con las referencias al nombre del archivo donde ocurri el error y un nmero de lnea, por lo que es ms fcil
encontrar el cdigo que genera el error. La directiva #line nos permite controlar ambas cosas, los nmeros de lnea en los
archivos de cdigo, as como el nombre del archivo que queremos que aparece cuando un error ocurre. Su formato es:
#line numero nombre_de_archivo
6 CAPTULO 2. LO MS BSICO
Donde nmero es el nuevo nmero de lnea que se asignar a la siguiente lnea de cdigo. Los nmeros de lnea de las lneas
sucesivas se incrementarn uno por uno desde este punto en adelante."nombre_de_archivo es un parmetro opcional que
permite redenir el nombre del archivo que se mostrar. Por ejemplo:
#line 20 asignacion de variable int a?;
Este cdigo generar un error que se mostrar como un error en el archivo de asignacin de variable, lnea 20.
Error
Esta directiva cancela la compilacin cuando es encontrada, generando un error de compilacin que puede ser especicado
segn un parmetro de un ejercicio. Por ejemplo:
#ifndefcplusplus #error A Se requiere compilador de C++ #endif
En el ejemplo anterior, la compilacin es cancelada si la macro __cplusplus no est denida (Esta macro es denida por
defecto en todos los los compiladores de C++).
Inclusin de chero
Cuando el preprocesador encuentra la directiva #include, este reemplaza todas las ocurrencias de ella por el archivo o
cabecera especicada. Existen dos formas de utilizar #include:
#include <cabecera>: Es usado para incluir las cabeceras proporcionadas por defecto, por ejemplo, la librera
estndar (string, iostream, etc.).
Pragma
la directiva #pragma es utilizada para especicar las distintas opciones del compilador. Estas opciones son especicadas
dependiendo del compilador que se utilice. Si el compilador no permite un argumento para #pragma, esto es ignorado y
no genera un error de sintaxis.
En el ejemplo anterior, se denen (ademas de las directivas de preprocesador, vistas en la seccin anterior) una variable y
una funcin. La variable autor podr ser utilizada por todas las funciones del programa. Mientras que la funcin adicion
solo fue declarada de la forma inline. Una funcin se dice declarada inline cuando solo se seala su nombre y sus
tipos de entrada y salida. Su denicin completa se reserva para mas adelante.
se permite la denicin completa de las funciones declaradas en la seccin de declaraciones globales. En el siguiente
ejemplo, mostraremos la estructura denitiva de un programa y la declaracin completa de sus funciones:
#include <iostream> #dene PI 3.1415 std::string autor: Wikibooks"; int adicion(int, int); int main(int argc, char **argv)
{ std::cout<<"El resultado de la suma de 1 y 2 es "<<adicion(1,2)<<std::endl; return 0; } int adicion(int a, int b) { return
a+b; }
Los parmetros de entrada de la funcin main es algo que se abordar mas adelante.
1. Escribir con un editor de texto plano un programa sintcticamente vlido o usar un entorno de desarrollo (IDE)
apropiado para tal n
Este ltimo paso es el ms costoso, por que en programas grandes, averiguar si hay o no un fallo prcticamente puede ser
una tarea totmica.
Como ejemplo, si se desea escribir un archivo con el nombre hola.cpp y en l escribir un programa con emacs, por ejemplo,
que es un programa de edicin de textos, se puede, en GNU, ejecutar el siguiente comando:
$emacs hola.cpp &
Para otros sistemas operativos u otros entornos de desarrollo, no necesariamente se sigue este paso.
A continuacin se escribe el siguiente cdigo en C++:
Ejemplo
// Aqu generalmente se suele indicar qu se quiere con el programa a hacer // Programa que muestra 'Hola mundo' por
pantalla y naliza // Aqu se sitan todas las librerias que se vayan a usar con #include, // que se ver posteriormente
// Las librerias del sistema son las siguientes #include <iostream> // Funcin main // Recibe: void // Devuelve: int //
Funcin principal, encargada de mostrar Hola Mundo,por pantalla int main(void) { // Este tipo de lneas de cdigo que
comienzan por '//' son comentarios // El compilador los omite, y sirven para ayudar a otros programadores o // a uno
mismo en caso de volver a revisar el cdigo // Es una prctica sana poner comentarios donde se necesiten, std::cout <<
Hola Mundo << std::endl; // Mostrar por std::cout el mensaje Hola Mundo y comienza una nueva lnea return 0; // se
devuelve un 0. //que en este caso quiere decir que la salida se ha efectuado con xito. }
Mediante simple inspeccin, el cdigo parece enorme, pero el compilador lo nico que leer para la creacin del progra-
ma es lo siguiente:
Ejemplo
#include <iostream> int main(void){ std::cout << Hola Mundo << std::endl; return 0; }
Como se puede observar, este cdigo y el original no dieren en mucho salvo en los saltos de lnea y que los comentarios,
de los que se detallan posteriormente, estn omitidos y tan slo ha quedado el esqueleto del cdigo legible para el
compilador. Para el compilador, todo lo dems, sobra.
8 CAPTULO 2. LO MS BSICO
O este otro, que es, en parte, como el lenguaje C, en su versin C99, es:
Ejemplo
#include <stdio.h> #include <stdlib.h> int main(void) { printf( Hola Mundo\n ); return EXIT_SUCCESS; // 'EXIT_SUCCESS'
es una denicin que est dentro de 'stdlib.h' // tambien funciona return 0 }
Nota: si se usa Windows, el cdigo es el mismo, pero debemos agregar un metodo mas para que el programa se mantenga
abierto y no se cierre la consola, cosa que en GNU, no es necesaria por que la consola ya esta abierta (al mandar a ejecutar).
Para esto podemos usar cin.get() que nos permitira leer del teclado, por lo que el programa no nalizara, hasta que el
usuario pulse enter.
Ejemplo
#include <iostream> int main(void) { std::cout << Hola Mundo << std::endl; std::cin.get(); //con 'std::cin.get();' lo que
se hace es esperar hasta que el usuario pulse enter. return 0; }
Los pasos siguientes son para una compilacin en GNU o sistema operativo Unix. En Windows tampoco es aplicable.
Con ctrl-x ctrl-s se guarda el archivo. Ahora para generar el ejecutable del programa se compila con g++ de la siguiente
forma:
$ g++ hola.cpp -o hola
Para poder ver los resultados del programa en accin, se ejecuta el programa de la siguiente forma:
$./hola
Y a continuacin se debe mostrar algo como lo siguiente:
Hola Mundo
2.2.1 Comentarios
Cuando se escriben programas es muy til agregar comentarios que ayuden a explicar lo que realiza un programa. En
C++ se pueden utilizar tres tipos de comentarios: al estilo C, al estilo C++ y usando preprocesador.
Los comentarios al estilo C se caracterizan por lo siguiente: comenzar el bloque de comentarios con /* y terminar dicho
bloque de comentarios con */
Ej:
/* Este es un comentario al estilo C. Todo lo escrito dentro de las etiquetas de apertura y cierre es un comentario. A estos
comentarios se le llaman multilinea, logicamente por el hecho de permitir varias lineas de comentarios. */
Si se usan este tipo de etiquetas de comentarios, hay que tener cuidado con el cierre (*/), por que el compilador puede
tomar todo el texto como comentario, o cerrar antes de lo deseado.
Usando el estilo de cdigo de C++ slo pueden ocupar una lnea como en el siguiente cdigo:
// Este es un comentario al estilo C++
Una buena prctica de programacin es pensar que se programa a sabiendas de que otro programador, tal vez el lector
mismo en un futuro, tenga que desencriptar qu quiso hacer ah y por qu.
Otra posible forma de comentar cdigo es usando el preprocesador. Esto se detallar ms adelante en profundidad, por
ahora la parte til del preprocesador que interesa es la siguiente:
#if 0 Comentarios sobre el programa /parte del programa. Pueden ocupar mltiples lneas. Ms complicado de visualizar
2.2. PROCESO DE DESARROLLO DE UN PROGRAMA 9
Este tipo de comentarios se usan rara vez. Generalmente son difciles de localizar, incluso para programadores experi-
mentados que trabajan en papel, y son fciles de distinguir en casi cualquier IDE. Es recomendable indicar que se tratan
de comentarios, o directamente no usarlos, salvo si son grandes cantidades de comentarios. Se ver ms adelante que
tambin puede tener otros usos.
En los ejemplos anteriores se utilizaron 'std::cout'. 'std::cout' es un objeto que permite escribir en la consola (la terminal
en GNU/Unix/MacOSX), solo se puede utilizar gracias a que se ha incluido deniciones de su uso con la lnea de cdigo
'#include <iostream>'.
std::cout << ALGO;
Donde ALGO puede ser lo que sea que 'std::cout' sea capaz de mostrar por consola. Ms adelante se trata ms sobre ello
en detalle y aclarando posibles dudas que ahora puedan surgir. Tambin se utiliz 'std::endl', esto permite que el texto se
escriba en una nueva lnea.
Un ejemplo ms completo sobre la escritura en consola es el siguiente. Ha de tenerse en cuenta que se han eliminado
algunos comentarios superuos del primer programa debido a que ahora se est tratando con ms detalle acerca del uso
de imprimir texto en la consola:
Ejemplo
// Programa que muestra diversos textos por consola // Las libreras del sistema usadas son las siguientes #include <ios-
tream> // Funcin: main // Recibe: void // Devuelve: int // Es la funcin principal encargada de mostrar por consola
diferentes textos int main(void) { // Ejemplo con una nica lnea, se muestra el uso de std::cout y std::endl std::cout <<
Bienvenido. Soy un programa. Estoy en una linea de codigo. << std::endl; // Ejemplo con una nica lnea de cdigo que
se puede fraccionar mediante el uso de '<<' std::cout << Ahora " << estoy fraccionado en el codigo, pero en la consola
me muestro como una unica frase. << std::endl; // Uso de un cdigo largo, que cuesta leer para un programador, y que se
ejecutar sin problemas. // *** No se recomienda hacer lneas de esta manera, esta forma de programar no es apropiada
*** std::cout << Un gran texto puede ocupar muchas lineas. << std::endl << Pero eso no frena al programador a que
todo se pueda poner en una unica linea de codigo y que << std::endl << el programa, al ejecutarse, lo situe como el
programador quiso << std::endl; return 0; // Y se termina con xito. }
Se reta a compilar este cdigo y a observar sus resultados. En este momento el lector est capacitado para escribir pro-
gramas que impriman por pantalla el mensaje que se quiera.
Atencin: Se hace hincapi en la posibilidad de que las palabras acentuadas no se puedan mostrar en la consola. Depende
completamente del compilador que se pueda ver lo siguiente:
std::cout << programacin";
Con algunos compiladores, ver 'programacin', pero con otros puede ver incluso 'programaci n'.
Advertencia: cout puede ser utilizado sin tener std:: de forma previa porque se puede introducir una directiva, denomi-
nada 'using', que acomoda todos los cout. De otro modo habra un error de compilador. Este tema se trata en detalle ms
adelante.
using namespace std;
10 CAPTULO 2. LO MS BSICO
2.3 Sintaxis
Sintaxis es la forma correcta en que se deben escribir las instrucciones para el computador en un lenguaje de programacin
especco. C++ hereda la sintaxis de C estndar, es decir, la mayora de programas escritos para el C estndar pueden ser
compilados en C++.
El punto y coma es uno de los simblos ms usados en C, C++; y se usa con el n de indicar el nal de una lnea de
instruccin. El punto y coma es de uso obligatorio.
ejemplo
clrscr(); //Limpiar pantalla, funciona solo con la librera conio de Borland C++ x = a + b; string IP = 127.0.0.1"; //
Variable IP tipo string cout << IP << endl; // Devuelve 127.0.0.1 char Saludo[5] = Hola"; // Variable Saludo tipo char
cout << Saludo[0] << endl; // Igual a H cout << Saludo[1] << endl; // Igual a o cout << Saludo[2] << endl; // Igual a l
cout << Saludo[3] << endl; // Igual a a
El punto y coma se usa tambin para separar contadores, condicionales e incrementadores dentro de una sentencia for
ejemplo
Usar caracteres extras de espaciado o tabuladores ( caracteres tab ) es un mecanismo que nos permite ordenar de manera
ms clara el cdigo del programa que estemos escribiendo, sin embargo, el uso de estos es opcional ya que el compilador
ignora la presencia de los mismos. Por ejemplo, el segundo de los ejemplos anteriores se podra escribir como:
for (int i=0; i < 10; i++) { cout << i * x; x++; }
Los valores dependen de la arquitectura utilizada. Los mostrados son los que generalmente se encuentran en una
mquina tpica de arquitectura 32 bits.
El modicador long
El modicador long le indica al compilador que el tipo debe utilizar ms bits que los normalmente utilizados por ejemplo
si tenemos en una maquina de 32 bits como un Pentium de Intel, normalmente de un int ocupara 32 bits, pero si al declarar
un entero le antecedemos long, este entero ocupa 64 bits, el siguiente cdigo muestra como utilizar este modicador:
int corto; // Entero de 32 bits long int largo; // Entero de 64 bits
2.4. TIPOS PRIMITIVOS 11
El Modicador short
Similar al anterior, pero indica que se deben utilizar menos bits. Por ejemplo, en un computador de 32 bits, un short int
ocupa 16 bits.
El Modicador unsigned
El modicador unsigned es utilizado nicamente con los enteros, su utilizacin permite utilizar en los enteros nicamente
la parte positiva,
int a; // Almacena valores entre 32768 y 32767 unsigned int a; // Almacena valores entre 0 y 65535
El Modicador register
Este modicador sobre una variable le indica al compilador que la variable debe almacenarse en un registro en el compi-
lador, que para el caso de los IA32, es un registro real de la propia CPU, y por tanto el tiempo de acceso es ms rpido
respecto a la memoria RAM. Hoy en da apenas se utiliza este modicador, ya que los compiladores son capaces de
determinar de manera ptima la asignacin de registros a variables del programa.
El Modicador volatile
Al contrario que el modicador registrer, volatile obliga al compilador a forzar el cdigo resultante de manera que la
variable modicada con este modicador, sea almacenada siempre en la memoria. El efecto que tiene es que cuando la
variable se modica con otro valor, dicha variable se almacena directamente en memoria y no queda localizado el valor
slo en el registro de la CPU como pasaba en el caso de register o en condiciones normales. Un uso muy comn en el que
se suele emplear este modicador, es para acceder a variables que estn siendo utilizadas por drivers o por perifricos, ya
que si no declarsemos esta propiedad, podra darse el caso que la CPU usase el valor de la variable, por lo que la cach
guarda el valor, y poco despus la CPU usase de nuevo dicha variable, pero como sta est en cache, la CPU coge el valor
que existe en la cach, que puede ser bien distinta al real puesto que un posible perifrico puede haber modicado su
valor.
El Modicador static
Dependiendo del entorno donde se declare la variable que la modiquemos como static, puede signicar dos cosas muy
distintas:
1. Si declaramos una variable static dentro del cuerpo de una funcin, lo que estamos indicndole al compilador es
que dicha variable sea inicializada solo una vez (la primera vez que se llama a la funcin), y el resto de veces que
se llame a la funcin, la variable contendr el ltimo valor asignado. Esta variable slo podr ser visible desde la
funcin que declara dicha variable. Por ejemplo:
void mifuncion(){ static int i=0; cout<<"En la entrada i vale "<<i<<endl; for(int j=0;j<10;j++) i++; cout<<"En la salida
i vale "<<i<<endl; }
1. Si declaramos una variable static fuera del cuerpo de una funcin, lo que le estamos indicando al compilador, es
que dicha variable es privada para el modulo donde se implementa el cdigo del contexto de la variable, es decir,
que otro chero objeto binario, no podr tener acceso a dicha variable utilizando el modicador extern. Ejemplo:
#import prueba.h //variable privada para prueba.cpp static int i=0; void mifuncion(){ cout<<"En la entrada i vale
"<<i<<endl; for(int j=0;j<10;j++) i++; cout<<"En la salida i vale "<<i<<endl; }
12 CAPTULO 2. LO MS BSICO
Tenga en cuenta que para este ltimo caso, usted podr acceder a la variable y desde cualquier funcin que dena dentro
de prueba.cpp, pero no tendr acceso desde cualquier chero objeto o fuente que no sea prueba.cpp
El espacio en bits que ocupan en la computadora una variable de este tipo se puede ver en la siguiente tabla:
El rango que puede almacenar los tipos primitivos en C++ es muy importante, para poder saber cual es el rango de
valores que puede almacenar un tipo es necesario conocer el nmero de bits del tipo. El caso para enteros y para otantes
es distinto. Para enteros se debe saber si el tipo es con signo o sin signo. si es sin signo el rango de valores que puede
almacenar es el siguiente:
[0, 2Bits 1]
Si el tipo es con signo el rango es el siguiente
[2Bits1 , 2Bits1 1]
Para ilustrar lo anterior supongamos que tenemos un entero de 16 bits sin signo, entonces el rango de valores que puede
almacenar es el siguiente:
[0, 216 1] = [0, 65535]
Para obtener el rango de un entero de 32 bits con signo se puede realizar el siguiente calculo:
[231 , 231 1] = [2147483648, 2147483647]
El caso de los nmeros otantes es distinto y depende en gran manera del compilador y el procesador que este utili-
zando. Sin embargo hoy en da la mayora de los compiladores y los procesadores utilizan en estndar de la IEEE para
representacin en coma otante. Para saber mas al respecto ver IEEE oating-point standard.
En el ejemplo anterior se dene por medio de enum el tipo enumerado dias, en el mismo ejemplo se debe observar que
dentro de la construccin se denen las constantes simblicas: domingo, lunes, ... sabado; y que a las mismas el compilador
les asignar respectivamente y por defecto los valores: 0, 1, 2, 3, 4, 5, 6. Es decir, las constantes mencionadas pueden
usarse dentro del programa y este sabr a que valor hace referencia cada una de las mismas. Por ejemplo, la instruccin:
cout << domingo; desplegar 0 en la pantalla. El siguiente ejemplo muestra como usar las constantes enumeradas en un
ciclo for.
for (int d = domingo; d <= sabado; d++) cout << d;
En el siguiente ejemplo se dene por medio de enum el tipo enumerado colores, en el mismo se debe de observar la
elinacin del comportamiento por defecto ya que a la primera constante (gris) se le asigna en forma especca el valor de
7, de tal manera que la siguiente constante (grisoscuro) tendr un valor igual a 8 y la constante amarillo ser igual a 14.
Ejemplo 2
2.6. TIPOS DEFINIDOS POR EL USUARIO 13
Luego, podremos declarar variables, constantes y funciones del tipo entero. Por ejemplo,
entero edad = 33;
Un uso ms til y comn es el empleo de typedef para denir datos estructurados. Por ejemplo, supongamos que deseamos
denir un tipo estructurado llamado persona y que contenga nombre, edad y sexo. Entonces podemos denir persona
como:
typedef struct persona { char nombre[32]; int edad; char sexo; };
Una vez que un tipo ya se ha denido, el mismo puede emplearse para declarar variables y constantes de este. Por ejemplo,
con la instruccin:
persona hombre;
se est declarando la variable hombre del tipo persona. Luego, para acceder a cada elemento de la variable hombre
usaremos un mecanismo conocido como direccionamiento directo por medio del carcter de punto ( . ). Por ejemplo,
la edad de hombre se debe indicar como:
hombre.edad
A diferencia de las constantes declaradas con la palabra const los smbolos denidos con #dene no
ocupan espacio en la memoria del cdigo ejecutable resultante.
El tipo de la variable o constante puede ser cualquiera de los listados en Tipos primitivos, o bien de un
tipo denido por el usuario.
Las constantes son usadas a menudo con un doble propsito, el primero es con el n de hacer ms legible el cdigo
del programa, es decir, si se tiene (por ejemplo) la constante numerica 3.1416 y esta representa al nmero pi, entonces
podemos hacer declaraciones tales como:
14 CAPTULO 2. LO MS BSICO
#dene pi 3.1416
En este caso podremos usar la palabra pi en cualquier parte del programa y el compilador se encargar de cambiar dicho
simbolo por 3.1416.
o bien,
const pi = 3.1416;
En este otro caso podremos usar la palabra pi en cualquier parte del programa y el compilador se encargar de cambiar
dicho smbolo por una referencia a la constante pi guardada en la memoria.
Captulo 3
Iteraciones y decisiones
En algn momento dentro de nuestros algoritmos, es preciso cambiar el ujo de ejecucin de las instrucciones, es decir,
el orden en que las instrucciones son ejecutadas. Muchas de las veces tenemos que tomar una decisin en cuanto a que se
debe ejecutar basndonos en una respuesta de verdadero o falso (condicion).
La ejecucin de las instrucciones incluyendo una estructura de control como el condicional funcionan de esta manera:
Las instrucciones comienzan a ejecutarse de forma secuencial (en orden) y cuando se llega a una estructura condi-
cional, la cual esta asociada a una condicion, se decide que camino tomar dependiendo siempre del resultado de la
condicion siendo esta falsa o verdadera.
Cuando se termina de ejecutar este bloque de instrucciones se reanuda la ejecucin en la instruccin siguiente a la
de la condicional.
3.2 Sentencia if
La instruccin if es, por excelencia, la ms utilizada para construir estructuras de control de ujo.
SINTAXIS
Primera Forma
Ahora bin, la sintaxis utilizada en la programacin de C++ es la siguiente:
if (condicion) { Set de instrucciones }
siendo condicion el lugar donde se pondr la condicin que se tiene que cumplir para que sea verdadera la sentencia y
as proceder a realizar el set de instrucciones o cdigo contenido dentro de la sentencia.
Segunda Forma
Ahora veremos la misma sintaxis pero ahora le aadiremos la parte Falsa de la sentencia:
if (condicion) { Set de instrucciones //PARTE VERDADERA } else { Set de instrucciones 2 //Parte FALSA }
15
16 CAPTULO 3. ITERACIONES Y DECISIONES
Estructura de control IF
La forma mostrada anteriormente muestra la union de la parte VERDADERA con la nueva secuencia la cual es la parte
FALSA de la sentencia de decision IF en la cual esta compuesta por el:
else { Set de instrucciones 2 //Parte FALSA }
la palabra else o De lo contrario indica al lenguaje que de lo contrario al no ser verdadera o no se cumpla la parte
verdadera entonces realizara el set de instrucciones 2.
EJEMPLOS DE SENTENCIAS IF...
Ejemplo 1:
if(numero == 0) //La condicion indica que tiene que ser igual a Cero { cout<<"El Numero Ingresado es Igual a Cero"; }
Ejemplo 2:
if(numero > 0) // la condicion indica que tiene que ser mayor a Cero { cout<<"El Numero Ingresado es Mayor a Cero"; }
3.2. SENTENCIA IF 17
Crystal_Clear_app_kedit.png
Ejemplo 3:
if(numero < 0) // la condicion indica que tiene que ser menor a Cero { cout<<"El Numero Ingresado es Menor a Cero";
}
Ahora uniremos todos estos ejemplos para formar un solo programa mediante la utilizacin de la sentencia Else e
introduciremos el hecho de que se puede escribir en este espacio una sentencia if ya que podemos ingresar cualquier tipo
de cdigo dentro de la sentencia escrita despus de un Else.
Ejemplo 4:
if(numero == 0) //La condicion indica que tiene que ser igual a Cero { cout<<"El Numero Ingresado es Igual a Cero"; }
else { if(numero > 0) // la condicion indica que tiene que ser mayor a Cero { cout<<"El Numero Ingresado es Mayor a
Cero"; } else { if(numero < 0) // la condicion indica que tiene que ser menor a Cero { cout<<"El Numero Ingresado es
Menor a Cero"; } } }
18 CAPTULO 3. ITERACIONES Y DECISIONES
switch es otra de las instrucciones que permiten la construccin de estructuras de control. A diferencia de if, para controlar
el ujo por medio de una sentencia switch se debe de combinar con el uso de las sentencias case y break.
Notas: cualquier nmero de casos a evaluar por switch as como la sentencia default son opcionales. La
sentencia switch es muy til en los casos de presentacin de menus.
Sintaxis:
switch (condicin) { case primer_caso: bloque de instrucciones 1 break; case segundo_caso: bloque de instrucciones 2
break; case caso_n: bloque de instrucciones n break; default: bloque de instrucciones por defecto }
Ejemplo 1
switch (numero) { case 0: cout << numero es cero"; }
Ejemplo 2
switch (opcion) { case 0: cout << Su opcion es cero"; break; case 1: cout << Su opcion es uno"; break; case 2: cout <<
Su opcion es dos"; }
Ejemplo 3
switch (opcion) { case 1: cout << Su opcion es 1"; break; case 2: cout << Su opcion es 2"; break; case 3: cout << Su
opcion es 3"; break; default: cout << Elija una opcion entre 1 y 3"; }
En C/C++, existe el operador condicional ( ?: ) el cual es conocido por su estructura como ternario. El comportamiento
de dicho operador es el mismo que una estructura if - then - else del lenguaje BASIC (y de la funcin IIf de Visual
Basic). El operador condicional ?: es til para evaluar situaciones tales como:
Si se cumple tal condicin entonces haz esto, de lo contrario haz esto otro.
Sintaxis:
( (condicion) ? proceso1 : proceso2 )
En donde, condicion es la expresin que se evalua, proceso1 es la tarea a realizar en el caso de que la evaluacin resulte
verdadera, y proceso2 es la tarea a realizar en el caso de que la evaluacin resulte falsa.
Ejemplo 1
int edad; cout << Cual es tu edad: "; cin >> edad; cout << ( (edad < 18) ? Eres joven aun : Ya tienes la mayora de
edad );
Ejemplo 2
Vamos a suponer que deseamos escribir una funcin que opere sobre dos valores numricos y que la misma ha de regresar
1 (true) en caso de que el primer valor pasado sea igual al segundo valor; en caso contrario la funcin debe retornar 0
(false).
3.3. SENTENCIAS DE ITERACIN 19
donde:
2. nal es la condicin que se evalua para nalizar el ciclo (puede ser independiente del contador)
Hay que tener en cuenta que el for evalua la condicin de nalizacin igual que el while, es decir, mientras esta se
cumpla continuaran las repeticiones.
Ejemplo 1:
for(int i=1; i<=10; i++) { cout<<"Hola Mundo"; }
Esto indica que el contador i inicia desde 1 y continuar iterando mientras i sea menor o igual a 10 ( en este caso llegar
hasta 10) e i++" realiza la sumatoria por unidad lo que hace que el for y el contador se sumen. repitiendo 10 veces
HOLA MUNDO en pantalla.
Ejemplo 2:
for(int i=10; i>=0; i--) { cout<<"Hola Mundo"; }
Este ejemplo hace lo mismo que el primero, salvo que el contador se inicializa a 10 en lugar de 1; y por ello cambia la
condicin que se evalua as como que el contador se decrementa en lugar de ser incrementado.
La condicin tambin puede ser independiente del contador:
Ejemplo 3:
int j = 20; for(int i=0; j>0; i++){ cout<<"Hola"<<i<<" - "<<j<<endl; j--; }
En este ejemplo las iteraciones continuaran mientras j sea mayor que 0, sin tener en cuenta el valor que pueda tener i.
20 CAPTULO 3. ITERACIONES Y DECISIONES
donde:
Ejemplo 1:
int contador = 0; while(contador<=10) { contador=contador+1; cout<<"Hola Mundo"; }
El contador Indica que hasta que este llegue a el total de 10 entonces se detendr y ya no se realizar el cdigo contenido
dentro de la sentencia while, de lo contrario mientras el contador sea menor o igual a 10 entonces el cdigo contenido
se ejecutar desplegando hasta 11 veces Hola Mundo en pantalla.
Observe cmo en el caso de do la condicin es evaluada al nal en lugar de al principio del bloque de instrucciones y, por
lo tanto, el cdigo que le sigue al do se ejecuta al menos la primera vez.
3.4.1 break
La sentencia break se usa para forzar un salto hacia el nal de un ciclo controlado por for o por while.
Ejemplo:
En el siguiente fragmento de cdigo la sentencia break cierra el ciclo for cuando la variable ( i ) es igual a 5. La salida
para el mismo ser:
01234
for (int i=0; i<10; i++) { if (i == 5) break; cout << i << " "; }
3.4. SENTENCIAS BREAK Y CONTINUE 21
3.4.2 continue
La sentencia continue se usa para ignorar una iteracin dentro de un ciclo controlado por for o por while.
Ejemplo:
En el siguiente fragmento de cdigo la sentencia continue ignora la iteracin cuando la variable ( i ) es igual a 5. La salida
para el mismo ser:
012346789
for (int i=0; i<10; i++) { if (i == 5) continue; cout << i << " "; }
Nota: no deje de observar que la construccin del ciclo while para el caso de la sentencia continue es
diferente, esto para garantizar que el ciclo no vaya a caer en una iteracin innita.
break
int i = 0; while (i<10) { if (i == 5) break; cout << i << " "; i++; }
continue
int i = 1; while (i<10) { i++; if (i == 5) continue; cout << i << " "; }
Captulo 4
Estructuras
22
Captulo 5
Estructuras de datos
Las estructuras de datos se emplean con el objetivo principal de organizar los datos contenidos dentro de la memoria
del ordenador. As, nuestra primera experiencia con estructuras comienza desde el momento mismo en que usamos en
nuestros programas variables de tipos primitivos (char, short, int, oat, etc). A la memoria del ordenador se le puede
considerar como un gran bloque compuesto por una serie de BYTES dispuestos secuencialmente uno detrs de otro. por
ejemplo, si un ordenador posee una memoria de 128MB (128 megas) entonces se le puede leer o escribir desde el BYTE
0 hasta el BYTE 128MB - 1 ( 0000000H .. 7FFFFFFH ).
La idea de ver la memoria como un serie de bytes es buena, sin embargo no es suciente ya que en la misma podemos
guardar nmeros, cadenas de caracteres, funciones, objetos, etc. de tal manera que surge la necesidad de establecer los
mecanismos adecuados para dar cuenta de la forma, tamao y objetivo de los datos almacenados. Segn el tipo de mi-
croprocesador, estos tienen la capacidad para manipular o direccionar estructuras compuestas por uno, dos, cuatro, etc,
bytes; de donde se derivan los tipos que comunmente se conocen como: BYTE, WORD, DWORD, QWORD y TWORD.
La estructura mnima de informacin manipulable en un sistema de computacin es el BIT el cual se agrupa normalmente
en bloques de 8 para formar un BYTE. Cabe mencionar que los BITS no son direccionables directamente, sino a travs
de compuertas AND, OR, NOT, XOR, las cuales en C y C++ se escriben como &, |, ~ y ^, conocidos como Bitwise
operators u Operadores de manipulacin de bits.
En C,C++ existe una serie de estructuras bsicas o tipos primitivos, los cuales pueden ser usados por el programador
para declarar variables, y tambin son el fundamento sobre el cual se crean estructuras complejas. El tamao de los tipos
primitivos no es estndar ya que los mismos dependen de factores tales como:
El compilador
Sin embargo, en la actualidad, la mayoria de compiladores de C y C++ soportan los siguientes tipos con la longitud
indicada:
5.1.1 variables
23
24 CAPTULO 5. ESTRUCTURAS DE DATOS
De acuerdo con la tabla anterior y segn las instrucciones anteriores, con la primera, o sea ( char c;), se le est indicando
al ordenador que reserve en la memoria un espacio de tipo char (8 bits) y que el mismo ser identicado bajo el nombre
de c. La segunda instruccin ( int i;) le indica al ordenador que reserve en la memoria un espacio de tipo int (16 bits) y
que el mismo ser identicado bajo el nombre de i. Luego, la instruccin ( oat f;) le indica al ordenador que reserve en
la memoria un espacio de tipo oat (32 bits) y que el mismo ser identicado bajo el nombre de f. Por ltimo, se le indica
al compilador que reserve espacio para otras tres variables enteras identicadas como: x, y, z. As, se puede dar cuenta
cmo los tipos primitivos sirven con el propsito de estructurar los datos dentro de la memoria y con la idea de referirnos
a los mismos mediante nombres usamos identicadores de variables.
tipo se reere al tipo de datos que contendr la matriz. El tipo puede ser cualquiera de los tipos estndar (char, int,
oat, etc.) o un tipo denido por el usuario. Es ms, el tipo de la matriz puede ser de una estructura creada con:
struct, union y class.
tamao es opcional e indica el nmero de elementos que contendr la matriz. Si una matriz se declara sin
tamao, la misma no podr contener elemento alguno a menos que en la declaracin se emplee una lista de
inicializacin.
lista de inicializacin es opcional y se usa para establecer valores para cada uno de los componentes de la
matriz. Si la matriz es declarada con un tamao especifco, el nmero de valores inicializados no podr ser
mayor a dicho tamao.
5.3. ESTRUCTURAS COMPUESTAS (STRUCT, UNION, CLASS) 25
Ejemplos:
int intA[5]; long longA[5] = { 1, 2, 3, 4, 5 }; char charA[] = { 'a', 'b', 'c' };
struct: esta orden se emplea para agrupar variables de tipos iguales o diferentes en un solo registro, con
la misma se pueden crear estructuras annimas, estructuras con nombre, y un tipo especial de estructura
conocida como bit-elds ( banderas o campos de bits ).
union: una union es similar a una struct, salvo que en una estructura creada con union los campos o
variables comparten una direccin de almacenamiento comn.
class: una clase es una estructura en donde se agrupan variables y funciones, la misma es usada en Pro-
gramacin Orientada al Objeto. Las clases no son soportadas por el C estndar.
Nota: tanto las estructuras como las uniones y las clases pueden ser annimas, pero lo ms recomendable es
darle a las mismas un nombre. Si una estructura, union o clase posee nombre, esta pueden ser empleadas para
declarar variables de la misma y, lo ms importante, puede ser usada para el paso de parmetros a funciones.
Sintaxis general: struct
struct [ <nombre tipo de estructura > ] { [ <tipo> <nombre-variable[, nombre-variable, ...]> ] ; [ <tipo> <nombre-
variable[, nombre-variable, ...]> ] ; ... } [ <variables de estructura> ] ;
De acuerdo con la sintaxis general de la orden struct es posible crear estructuras de datos annimas. Solamente
hay que tener en cuenta que en una declaracin annima se debe denir al menos una variable al nal de la
declaracin. Por ejemplo, con el siguiente fragmento de cdigo:
struct { int a, b; } p1;
se declara y dene la variable estructurada p1, misma que se compone por los miembros a y b; ambos del tipo int.
Ahora bien, la sintaxis mostrada arriba no es tan comn ni conveniente, ya que con la misma solamente se esta
creando una variable estructurada pero no un nuevo tipo. Es decir, si desearamos tener otra variable que tuviera
las mismas caracteristicas que posee la variable p1, necesitariamos escribir exactamente la misma instruccin,
salvo que cambiando el nombre de la variable. Por ejemplo:
struct { int a, b; } p2;
Por supuesto, en una misma lnea de instruccin podemos denir ms de una variable. Ejemplo:
struct { int a, b; } p1, p2;
Entonces, para crear nuevos tipos con struct deberemos de modicar la sintaxis mostrada en los ejemplos ante-
riores.
Sintaxis: struct ( variacin dos, estructura con nombre )
Observe que, la sintaxis para declarar estructuras con nombre es bastante parecida a la sintaxis para declarar
estructuras annimas; salvo que en una declaracin de estructura con nombre se debe especicar el nombre
deseado para la misma. Adems, en una declaracin de estructura con nombre la o las variables denidas al nal
de la misma son opcionales.
Ejemplos:
struct pareja { int a, b; } p1;
En el fragmento de cdigo anterior se declara la estructura identicada como pareja, misma que se compone
de los miembros a y b, ambos de tipo int. En el mismo ejemplo, se dene la variable p1; la cual es una variable
estructurada de tipo pareja.
Una vez que una estructura con nombre ha sido creada, la misma puede ser usada para declarar cualquier nmero
de variables. Por ejemplo, en el siguiente fragmento de cdigo se crea la estructura tiempo compuesta por los
miembros hora, minuto y segundo; todos del tipo int. En el mismo ejemplo, se declaran las variables t1 y t2.
/* declaracin de estructura tiempo */ struct tiempo { int hora, minuto, segundo; }; /* declaracin de variables
de tipo tiempo */ struct tiempo t1, t2;
Nota: en C++ puede obviarse la palabra struct a la hora de declarar variables. As, en C++ la lnea de instrucin
struct tiempo t1, t2; ( del ejemplo anterior) puede escibirse como: tiempo t1, t2;
En orden de poder leer o escribir uno de los miembros de una variable estructurada, se debe usar el operador de
acceso ( . ); o sea, el nombre de la variable seguida por un punto seguido por el nombre del miembro o componente
deseado de la estructura. Por ejemplo, para acceder a los miembros de la variable t1 (mostrada arriba) podemos
hacerlo de la siguiente manera:
t1.hora = 12; t1.minuto = 0; t1.segundo = 0; printf ("%i\n, t1.hora); cout << t1.minuto << endl;
5.3. ESTRUCTURAS COMPUESTAS (STRUCT, UNION, CLASS) 27
Los miembros de una estructura pueden ser ellos mismos, otra estructura previamente identicada, o bien una
estructura annima. Por ejemplo, en el siguiente fragmento de cdigo, se crean las estructuras pareja y pareja2.
Obsrvese cmo dentro de los miembros de pareja2, se declara el miembro X, que a su vez es una estructura del
tipo pareja. Luego, las variables declaradas a raz de la estructura pareja2 poseern los miembros variables a y
b heredados de pareja, y c.
struct pareja { int a, b ; }; struct pareja2 { struct pareja X; int c; } P3;
Ahora bien, para acceder a los miembros de una estructura dentro de otra estructura se emplea el mismo meca-
nismo de acceso (el punto). Por ejemplo, para desplegar el miembro a de la variable P3 declarada en el ejemplo
anterior, lo haremos ms o menos as:
printf( "%i\n, P3.X.a );
5.3.3 Herencia
El trmino herencia se usa con gran frecuencia en Programacin Orientada a Objetos, y se le relaciona principal-
mente con las clases. Sin embargo, la herencia est presente siempre y cuando una estructura struct, union
o class posea a otra estructura. En ese sentido, en C++ se presentan dos tipos de herencia:
Por ejemplo, en la denicin de las estructuras pareja y pareja2 del ejemplo anterior, se dice que pareja2 hereda
por composicin todos los miembros de pareja. Ahora, en el siguiente ejemplo se usa la sintaxis para que la
estructura pareja2 herede por extensin los miembros de pareja:
// solo C++ struct pareja { int a, b ; }; struct pareja2 : pareja { int c; } P3;
Con esta forma de herencia, la estructura pareja2 hereda de pareja los miembros a y b, y adems agrega un
miembro c. Y a diferencia del ejemplo anterior, para acceder a alguno de sus miembros heredados, basta con
utilizar el mecanismo de acceso (el punto).
// solo C++ cout << P3.a << P3.b ;
Un campo de bit es un elemento de una estructura denido en terminos de bits. Usando un tipo
especial de denicin de struct, se pueden declarar elementos de estructura con rangos de 1 a 16 de
largo (dependiendo de la arquitectura de la PC y del compilador, el rango para una estructura de
campos de bits puede ser de 1 a 16, 1 a 32, 1 a 64).
Antes de ver un ejemplo del uso de struct para crear estructuras de campos de bits, consideremos el caso en
donde se tiene una variable del tipo short (16 bits) y que para la misma se desea que los bits tengan signicados
especcos. Digamos que el primer bit servir para controlar alguna condicin; los siguientes cuatro bits, o sea
del segundo al quinto bit, controlarn otra condicin; el bit 6 tendr otra funcin; y el resto, o sea del sptimo al
decimosexto bit se emplearn para controlar otra condicin. De tal manera que si queremos, por ejemplo, saber
si el primer bit de la variable tiene almacenado un 1 o un 0, podemos emplear la siguiente sintaxis:
28 CAPTULO 5. ESTRUCTURAS DE DATOS
la cosa parece sencilla, pero ahora consideremos el caso en el cual deseamos saber cual es el valor contenido por
el grupo de bits ( segundo al quinto ), entonces nos daremos cuenta que no basta con una prueba mediante AND
( X & 1 ), sino que hay que realizar otros pasos.
Precisamente, para problemas como el planteado arriba es por los que los lenguajes C y C++ soportan las es-
tructuras de campos de bits. Por ejemplo, la estructura
struct campo_de_bit { int bit_1 : 1; int bits_2_a_5 : 4; int bit_6 : 1; int bits_7_a_16 : 10; } bit_var;
El mecanismo de estructuras de campos de bits soportado por C,C++ es una herramienta til y poderosa, sobre
todo en programacin de bajo nivel; ya que mediante el mismo es posible aislar y dar nombres a todos y cada
uno de los bits de un dato, y tambin crear en un mismo campo grupos de bits (como se mostr arriba). Ahora
bien, no hay que olvidar que la estructura mnima de informacin en un sistema de memoria de un PC es el bit, y
que para aislar a cada uno de estos se puede emplear el operador AND ( &, en C ), pero mediante el mecanismo
mencionado el compilador genera los algoritmos necesarios para llevar a cabo el aislamiento de los bits. Por
ejemplo, para escribir y leer bit identicado como bit_1 de la variable bit_var del ejemplo anterior, podemos
emplear las siguientes instrucciones:
bit_var.bit_1 = 1; printf("%i\n, bit_var.bit_1 );
Nota: acerca de las estructuras de campos de bits hay que aclarar que, aunque cada uno de los campos de la
estructura pueden declararse como enteros con signo o enteros sin signo, la misma no tendr una longitud mayor
a un entero largo.
De la misma manera que con la orden struct, con la orden union se pueden crear estructuras con nombre
y estructuras sin nombre.
El mecanismo de acceso a los miembros de una union es igual al mecanismo de acceso a los miembros de
una struct.
En una union, el compilador reserva el espacio de almacenamiento para la misma de acuerdo con el tipo
de la variable de mayor tamao.
5.5. CLASS: SINTAXIS 29
En el ejemplo anterior se declara la variable u1, la cual es una estructura tipo union. El espacio de almacenamiento
para la variable a es compartido por la variable b, en consecuencia, al escribir sobre cualquiera de estas dos
variables se altera el contenido de ambas.
Ejemplo: union con nombre
union ux { short a; long b; } u1;
En el ejemplo anterior se declara la variable u1, la cual es una estructura tipo union. El espacio de almacenamiento
para la variable a es compartido por la variable b. Es decir, el compilador reservar espacio en la memoria para la
variable de mayor tamao (que para ste caso es b ). Ahora bin, suponiendo que en su equipo el tipo long ocupa
32 bits y que el tipo short ocupa 16 bits, entonces la variable a ocupar solamente los 16 bits menos signicativos,
mientras que la variable b ocupar todo el espacio, o sea los 32 bits; Observe que en la sintaxis se ha especicado
el nombre ux, mismo que puede ser empleado para declarar cualquier nmero de variables de la union. Por
ejemplo, a continuacin se declaran las variables u2 y u3 del tipo union ux creado en el ejemplo anterior.
union ux u2, u3;
El mecanismo para acceder a los miembros de una class es igual que aquel utilizado para acceder a los
miembros de una struct
Las clases son algo as como super estructuras capaces de agrupar no solo datos miembros sino tambin fun-
ciones miembros. En el lenguaje comn a los datos miembros de una clase se les conoce como atributos; mientras
que a las funciones miembros de una clase se les llama mtodos. Normalmente, los mtodos se emplean para leer
o escribir los atributos. Es decir, la norma general es no permitir el acceso directo a los atributos de una clase,
con la idea de aumentar la seguridad de los datos.
En seguida se mostrar el cdigo para crear la clase Pareja, misma que poseer los atributos a y b, y los mtodos
setA(), setB(); getA(), getB(), y mostrar();
class Pareja { int a, b; public: void setA(int n) { a = n; } void setB(int n) { b = n; } int getA() { return a; } int
getB() { return b; } void mostrar() { cout << a = " << a << "; b = " << b << endl; } } p1;
30 CAPTULO 5. ESTRUCTURAS DE DATOS
Nota: por omisin, los miembros de una clase son privados, lo cual signica que los objetos instanciados de dicha
clase no tendrn acceso a los mismos. As, en el ejemplo anterior se est creando la clase Pareja, y al mismo
tiempo el objeto p1. Luego, para leer o escribir los atributos de p1 se debe hacer a traves de los mtodos denidos
con tal objetivo. Por ejemplo, con el siguiente fragmento de cdigo se establecen respectivamente a 100 y a 200
los atributos a y b; y posteriormente se despliegan por medio del mtodo mostrar().
p1.setA(100); p1.setB(200); p1.mostrar();
Los miembros de una struct son pblicos por defecto, mientras que los miembros de una class son privados
por defecto.
Los parmetros-argumentos struct se pasan normalmente por copia, los parmetros-argumentos class se
pasan normalmente por referencia.
// Este programa ha sido probado en Dev-C++, Borland C++ y Code::Blocks #include <iostream> using names-
pace std; // estructura tipo clase base struct Par { int a, b; // constructor base Par() { a = b = 0; } // destructor
base ~Par() { cout << hecho... << endl; } void setA(int n) { a = n; } void setB(int n) { b = n; } void mostrar()
{ cout << a = " << a << ", b = " << b << "; suma = " << a+b << endl; } }; // estructura tipo clase hija //
ParHijo es una extensin de Par, y por lo tanto hereda los miembros de Par struct ParHijo : Par { // constructor
del hijo ParHijo(int a, int b) { this->a = a; this->b = b; } }; // prueba void test00() { ParHijo p1(100, 200); //
p1 es instancia de ParHijo p1.mostrar(); // se enva mensaje al mtodo mostrar() de p1 } // funcion principal int
main() { test00(); cin.get(); return 0; }
Captulo 6
Funciones
31
Captulo 7
Funciones
Una funcin es un conjunto de lneas de cdigo que realizan una tarea especca y puede retornar un valor. Las funciones
pueden tomar parmetros que modiquen su funcionamiento. Las funciones son utilizadas para descomponer grandes
problemas en tareas simples y para implementar operaciones que son comnmente utilizadas durante un programa y de
esta manera reducir la cantidad de cdigo. Cuando una funcin es invocada se le pasa el control a la misma, una vez que
esta naliz con su tarea el control es devuelto al punto desde el cual la funcin fue llamada.
<tipo> [clase::] <nombre> ( [Parmetros] ) { cuerpo; }
Ejemplo de una funcin
32
7.2. PARMETROS 33
Para comenzar, vamos a considerar el caso en el cual se desea crear la funcin cuadrado(), que deber devolver el cuadrado
de un nmero real (de punto otante), es decir, cuadrado() aceptar nmeros de punto otante y regresar una respuesta
como nmero otante.
Nota: aunque para la funcin que veremos el tipo de retorno coincide con el tipo de parmetro pasado, algunas veces las
cosas pueden cambiar, es decir, no es obligatorio que una funcin reciba un parmetro de un tipo y que tenga que regresar
una respuesta de dicho tipo.
// regresar el cuadrado de un nmero double Cuadrado(double n) { return n*n; }
7.2 Parmetros
Normalmente, las funciones operan sobre ciertos valores pasados a las mismas ya sea como constantes literales o como
variables, aunque se pueden denir funciones que no reciban parmetros. Existen dos formas en C++ de pasar parmetros
a una funcin; por referencia o por valor. El hecho es que si en una declaracin de funcin se declaran parmetros
por referencia, a los mismos no se les podr pasar valores literales ya que las referencias apuntan a objetos (variables o
funciones) residentes en la memoria; por otro lado, si un parmetro es declarado para ser pasado por valor, el mismo puede
pasarse como una constante literal o como una variable. Los parmetros pasados por referencia pueden ser alterados por
la funcin que los reciba, mientras que los parmetros pasados por valor o copia no pueden ser alterados por la funcin
que los recibe, es decir, la funcin puede manipular a su antojo al parmetro, pero ningn cambio hecho sobre este se
reejar en el parmetro original.
Parmetros por valor
La funcin cuadrado() (ver arriba) es un clsico ejemplo que muestra el paso de parmetros por valor, en ese sentido la
funcin cuadrado() recibe una copia del parmetro n. En la misma funcin se puede observar que se realiza un calculo
( n*n ), sin embargo el parmetro original no sufrir cambio alguno, esto seguir siendo cierto an cuando dentro de la
funcin hubiera una instruccin parecida a n = n * n; o n*=n;.
Parmetros por referencia
Para mostrar un ejemplo del paso de parmetros por referencia, vamos a retomar el caso de la funcin cuadrado, salvo
que en esta ocasin cambiaremos ligeramente la sintaxis para denir la misma. Veamos:
// regresar el cuadrado de un nmero double cuadrado2(double &n) { n *= n; return n; }
Al poner a prueba las funciones cuadrado() y cuadrado2() se podr vericar que la primera de estas no cambia el valor
del parmetro original, mientras que la segunda s lo hace.
// esta funcin requiere de la librera iostream void pausa(void) { cout << Por favor presione <Enter> HOLA..."; cin.get();
cin.ignore(255, '\n'); // rechazar caracteres introducidos antes de <Enter> }
;
Notas: se debe de aclarar que el uso de la palabra void dentro de los parentesis es opcional al momento de declarar
una funcin. As, la funcin pausa() podra haberse declarado como void pausa(), y la misma puede invocarse como:
pausa();.
Ejemplo:
int funcionX( const int n ); void printstr( const char *str );
ambas reciben un puntero o referencia a un objeto de tipo entero, por lo tanto cualquiera de las funciones del ejemplo
puede cambiar el valor de la variable entera apuntada por X, la diferencia radica en la forma en que cada una de las mismas
lleva cabo la tarea. Si en la funcin puntero() en lugar de usar *X = 100; se usara X = 100; se le asignara 100 al puntero
X, ms no al objeto apuntado por X, y esto podra ser la causa de que el programa se terminara de manera abrupta.
Ahora, pensemos que deseamos escribir una funcin para imprimir variables del tipo empleado. As, la funcin puede
escribirse de las tres maneras siguientes:
void ImprimeEmpleadoV( empleado e) { cout << Nombre: " << e.nombre << endl; cout << Edad: " << e.edad << endl;
cout << Sexo: " << e.sexo << endl; } // Parametro empleado pasado por referencia void ImprimeEmpleadoR( empleado
&e ) { cout << Nombre: " << e.nombre << endl; cout << Edad: " << e.edad << endl; cout << Sexo: " << e.sexo <<
endl; } // Parametro empleado pasado como puntero void ImprimeEmpleadoP( empleado *e ) { cout << Nombre: " <<
e->nombre << endl; cout << Edad: " << e->edad << endl; cout << Sexo: " << e->sexo << endl; }
La sintaxis que usaremos para declarar funciones con lista de parmetros variables es:
1) tipo nombrefuncion(...) 2) tipo nombrefuncion(int num, ...)
donde:
Nota: observe que la primera forma de declaracin es realmente variable el nmero de parmetros a procesar y en estos
casos se debe establecer el mecanismo para determinar cuando se ha procesado el ltimo de los argumentos, en el segundo
tipo de declaracin el nmero total de parmetros a procesar es igual al valor del parmetro num.
En el siguiente programa, por ejemplo, se dene una funcin ( printstr ) que despliega una lista variable de cadenas de
caracteres.
#include <iostream.h> #include <stdarg.h> // despliega una lista de cadenas, la ultima debe ser NULL void printstr(...)
{ va_list ap; char *arg; va_start(ap, 0); while ( (arg = va_arg(ap, char*) ) != NULL) { cout << arg; } va_end(ap); } int
main() { printstr(Hola, ", Esta es\n, una prueba\n, NULL); cin.get(); return 0; }
En el programa que se listar en seguida, se dene la funcin suma(), misma que operar sobre listas de nmeros enteros,
la funcin devolver la suma de dichos nmeros.
#include <iostream>//entrada y salida #include <stdarg.h> using namespace std; // Esta funcin opera sobre una lista
variable de nmeros enteros int suma( int num, ... ) { int total = 0; va_list argptr; va_start( argptr, num ); while( num > 0
) { total += va_arg( argptr, int ); num--; } va_end( argptr ); return( total ); } int main() { cout << suma(4, 100, 200, 300,
400) << endl; cin.get(); return 0; }
Captulo 8
Streams
38
Captulo 9
En este captulo abordaremos el tema de la manipulacin de datos a travs de los dispositivos de entrada y salida estndar
por medio de ciertos componentes lgicos conocidos como: streams. A manera de denicin, un stream es una especie
de canal a travs del cual uyen los datos. Tcnicamente, un stream es el enlace lgico utilizado por el programador en
C, C++ para leer o escribir datos desde y hacia los dispositivos estndar conectados a la PC. Normalmente, el dispositivo
estndar para manipular entradas es el teclado y este, en C++, est asociado al objeto cin; el dispositivo estndar de salida
est asociado (generalmente) con la pantalla o monitor de despliegue de la PC y, el mismo, en C++ se puede acceder por
medio del objeto cout. El dispositivo estndar para mensajes de error es el cerr, y el mismo est asociado por defecto con
la pantalla o monitor de despliegue. Otro dispositivo estndar es la impresora, pero este ltimo no es soportado por los
objetos de la iostream. Los programadores que hayan tenido alguna experiencia al programar en C estndar, notarn que
los tres objetos mencionados coinciden con los dispositivos: stdin, stdout y stderr. La tabla que se muestra en seguida
puede servirnos de base.
9.1 La iostream
La iostream es la biblioteca estndar en C++ para poder tener acceso a los dispositivos estndar de entrada y/o salida.
En sus programas, si usted desea hacer uso de los objetos cin, cout, cerr y clog tendr que incluir ( por medio de la
directiva #include ) el uso de la biblioteca iostream. En la iostream se encuentran denidas las clases ios ( misma que
es la base para las clases que implementen operaciones de entrada y/o salida de datos ), istream ( para operaciones de
entrada ) y ostream ( para operaciones de salida ). Aparte de las clases mencionadas, en la iostream se encuentra una
lista de variables y constantes ( atributos ) que son accesibles por el usuario a travs del operador de mbito ( :: ).
Si usted usa la directiva #include <iostream.h> o #include <iostream> en sus programas, automticamente la iostream
pone a su disposicin los objetos cin, cout, clog y cerr en el mbito estndar (std), de tal manera que usted puede comenzar
a enviar o recibir informacin a travs de los mismos sin siquiera preocuparse de su creacin. Asi, un sencillo ejemplo del
uso de los objetos mencionados se muestra en seguida.
// De nuevo con el hola mundo... #include <iostream.h> int main() { std::cout << Hola mundo"; // imprimir mensaje
(en la pantalla) std::cin.get(); // lectura ( entrada del teclado ) return 0; }
39
40 CAPTULO 9. STREAMS, ENTRADA Y SALIDA DE DATOS
Observe que si en una misma lnea de comando se desea leer o escribir sobre varios campos a la vez, no es necesario
nombrar ms de una vez al stream. Ejemplos:
cout << Hola, << nombre; cin >> A >> B >> C;
C++ dene algunas banderas de formato para entradas y salidas estndar, las cuales pueden ser manipuladas a travs de
la funciones (mtodos) ags(), setf(), y unsetf(). Por ejemplo,
cout.setf(ios::left);
activa la justicacin a la izquierda para todas las salidas dirigidas hacia cout.
A continuacin se muestra una tabla de referencia de las banderas de I/O.
Manipulando la lista de banderas de I/O de C++ (mostrada arriba) se pueden controlar los aspectos relacionados a la
forma con la cual se desean presentar los datos en la salida. Por ejemplo, el programa que se muestra en seguida, activa
la bandera boolalpha para mostrar los resultados de operaciones booleanas como true o false en lugar de 0 o 1
como es lo normal.
// Programacin con C++ // programa Banderas01.cpp; // probado en Dev-Cpp Versin 4.9.9.2 #include <iostream>
using namespace std; int main(int argc, char *argv[]) { cout <<"\n0 > 1 ? "<<"\t"<<(0>1)<< endl; cout <<"\n5 > 1 ?
"<<"\t"<<(5>1)<< endl; cout.setf(ios::boolalpha); // activar bandera cout <<"\n0 > 1 ? "<<"\t"<<(0>1)<< endl; cout
<<"\n5 > 1 ? "<<"\t"<<(5>1)<< endl; cin.get(); return 0; }
9.4.2 Manipuladores
Las banderas tambin pueden manipularse directamente usando los siguientes manipuladores. Seguramente usted ya
estar familiarizado con el manipulador endl, el mismo puede darle una idea de cmo son usados los manipuladores. Por
ejemplo, usted puede establecer la bandera de nmeros decimales usando el comando:
9.4. BANDERAS DE I/O 41
Por ltimo, y para terminar esta seccin, hablaremos de los manipuladores denidos en la biblioteca <iomanip>. Estos
operan directamente igual que los que se viern anteriormente, salvo que son parametrizados, es decir, operan en lnea
de salida mediante el operador <<, pero los mismos operan a manera de funciones, o sea con parmetros.
Atendiendo a las tablas de banderas, as como a las tablas de manipuladores para las mismas mostradas arriba, usted puede
(con practica y perceverancia) lograr salidas con muy buena presentacin hacia los dispositivos estndar. Por ejemplo, el
programa que se mostrar a continuacin muestra una de las formas de emplear manipuladores para formatear nmeros
de punto otante, en el programa se despliega una lista de valores numricos y se establece el campo de salida a una
longitud de 12 caracteres y dos decimales.
// Programacin con C++ // programa Banderas03.cpp; // probado en Dev-Cpp Versin 4.9.9.2 #include <iostream> #in-
clude <iomanip> using namespace std; int main() { double data[] = { 347.25, 45.75, 124.50, 456.80, 1500.90 }; double
total = 0; int ancho = 12; cout.precision(2); cout.setf(ios::xed); for (int c = 0; c < 5; c++) { cout << setw(ancho) <<
data[c] << endl; total += data[c]; } cout.ll('-'); cout << setw(ancho) << "" << endl; cout.ll(' '); cout << setw(ancho) <<
total << endl; cout << Por favor oprime Enter..."; cin.get(); return 0; }
Captulo 10
Por denicin, un archivo es una coleccin de datos almacenados en algn lugar. En C++, el programador puede considerar
que un archivo es un stream, de tal manera que los objetos vistos en la seccin anterior ( cin, cout, cerr y clog ) son una
especie de archivo manipulados por streams. Este punto es de suma importancia, ya que todo lo que se ha mencionado
acerca de los atributos, banderas y manipuladores; que son miembros de los objetos ( stream ) para entrada y salida
estndar, son aplicables a los streams que usemos para trabajar con archivos en disco y/o cualquier otro dispositivo de
almacenamiento. Hasta este momento, solamente se ha mencionado que los streams poseen banderas y que las mismas
pueden alterarse a traves de los manipuladores, sin embargo, hace falta hablar acerca de los mtodos o funciones que son
miembros de dichos streams. As, antes de ver un ejemplo para mostrar el uso de archivos en disco listaremos una tabla
ms, o sea, la de los mtodos aplicables a los streams.
Observe que la sintaxis para crear objetos de las tres clases mostradas arriba es la misma. Es decir,
1. El primer mtodo constructor crea un objeto que no est (an) asociado a un archivo en disco, en estos casos, se
tendr que usar el mtodo stream.open(nombre de archivo); para establecer la conexin entre el stream y el
archivo en disco.
2. El segundo mtodo constructor crea un objeto asociado a un archivo en disco, en estos casos, el archivo en el disco
queda abierto y asociado al stream. En este, el parmetro char * apunta a una cadena de caracteres con el nombre
del archivo.
3. El tecer mtodo crea un stream a raiz de un identicador de archivo.
4. El cuarto mtodo crea un stream a raiz de un identicador de archivo, un buer y tamao de buer especcos.
42
10.1. ABRIR Y CERRAR ARCHIVO 43
Para nuestro primer ejemplo usaremos el segundo de los constructores mencionados. As, el programa que se muestra en
seguida realiza las siguientes tareas:
// Ejemplo de ifstream y ofstream // En este programa se demuestra la forma de crear un archivo // en disco por medio
de objeto ofstream, y la manera abrir y // leer un archivo por medio de un objeto ifstream. #include <iostream> #include
<fstream> #include <string.h> using namespace std; char *lename = test.$$$"; char *data = Esta lnea de texto se
guardar en el archivo test.$$$"; // crear un archivo en disco cuyo nombre es dado por lename int crearArchivo(char
*lename) { ofstream chero(lename); // crear o rescribir archivo // vericar la creacin del archivo if ( chero.bad()
) { cout << Error al tratar de abrir archivo"; cin.get(); return 1; } // escribir datos al archivo for (unsigned int t = 0; t <
strlen(data); t++ ) chero.put(data[t] ); chero.close(); cout << archivo creado exitosamente << endl; return 0; } // abrir
un archivo en disco cuyo nombre es dado por lename int leerArchivo(char *lename) { ifstream chero(lename); //
abrir archivo para lectura // vericar la apertura del archivo if ( chero.bad() ) { cout << Error al tratar de abrir archivo";
cin.get(); return 1; } // lectura de datos while ( ! chero.eof() ) cout << (char)chero.get(); chero.close(); cout << endl
<< archivo leido exitosamente << endl; return 0; } int main() { crearArchivo(lename); leerArchivo(lename); cout
<< endl << Presione <Enter>..."; cin.get(); return 0; }
Encargadas de leer datos del archivo de entrada y de escribir los mismos en el archivo de salida.
#include <iostream.h> #include <fstream.h> char *lename = test.$$$"; // abre y lee datos de un archivo en disco cuyo
nombre es dado por lename int leerArchivo(char *lename) { ifstream chero(lename); // abrir archivo para lectura
// vericar la apertura del archivo if ( chero.bad() ) { cout << Error al tratar de abrir archivo"; cin.get(); return 1; } //
lectura de datos while ( ! chero.eof() ) cout << (char)chero.get(); chero.close(); cout << endl << archivo leido exito-
samente << endl; return 0; } // crea una copia del archivo test.$$$ hacia test.bak // Nota: ningn carcter de espaciado
en el origen le es tranferido al destino int backup(char *lename) { char newname[80]; int i = 0; // copia el nombre del
archivo original y remplaza la extensin por BAK. while (lename[i] != '.' ) newname[i] = lename[i++]; newname[i]
= '\0'; strcat(newname, ".BAK); ifstream n( lename ); // abrir archivo de entrada ofstream fout( newname, ios::trunc
); // abrir archivo de salida char temp; while ( ! n.eof() ) { n >> temp; fout << temp ; } n.close(); fout.close(); return
0; } int main() { backup(lename); leerArchivo(test.bak); cout << endl << Presione <Enter>..."; cin.get(); return 0; }
Captulo 11
44
11.4. DECLARACIN DE ARRAYS EN C, C++ 45
de ellos emplearemos un nmero para indicar la posicin del mismo dentro de la lista. Por ejemplo, consideremos el caso
de la tabla o array VentaSemanal, la cual est pensada para registrar las ventas de cada uno de los das de la semana. De
manera conceptual podemos ver el array como se muestra a continuacin:
Nota: en C++ los arrays estn basados en 0 ( cero ), es decir, el primer elemento de un array se indexa
mediante el 0, y el ndice para el ltimo de los elementos es igual al nmero de componentes menos uno.
array: VentaSemanal +------+ | dato | <-- componente 0, ( la 0 ) |------| | dato | <-- componente 1, ( la 1 ) |------| | dato |
... |------| | dato | ... |------| | dato | ... |------| | dato | ... |------| | dato | <-- componente 6, ( la 6 ) |------|
Si en el array VentaSemanal queremos que el elemento 4 ( por ejemplo ) contenga el valor de 8987 lo podemos lograr
con la instruccin: VentaSemanal[4] = 8987; y el estado del array sera:
array: VentaSemanal +------+ | dato | |------| | dato | |------| | dato | |------| | dato | |------| | 8987 | <--- componente 4 |------|
| dato | |------| | dato | |------|
tipo se reere al tipo de datos que contendr el array. El tipo puede ser cualquiera de los tipos estndar (char, int,
oat, etc.) o un tipo denido por el usuario. Es ms, el tipo del array puede ser de una estructura creada con: struct,
union y class.
tamao es opcional e indica el nmero de elementos que contendr el array. Si un array se declara sin tamao, el
mismo no podr contener elemento alguno a menos que en la declaracin se emplee una lista de inicializacin.
46 CAPTULO 11. ARRAYS Y CADENAS DE TEXTO
lista de inicializacin es opcional y se usa para establecer valores para cada uno de los componentes del array. Si el
array es declarado con un tamao especco, el nmero de valores inicializados no podr ser mayor a dicho tamao.
Ejemplos:
int intA[5]; long longA[5] = { 1, 2, 3, 4, 5 }; char charA[3] = { 'a', 'b', 'c' };
Nota: por motivos de simplicacin el programa est escrito al estilo de C estndar. Sin embargo puede
ser compilado y ejecutado en un compilador de C++.
#include <stdio.h> #include <stdlib.h> #dene FILAS 7 int main() { oat ventas[FILAS] = { 123.50, 234.60, 345.45,
321.40, 345.00, 456.65, 0.0 }; oat total = 0; int i; puts(Ventas de la semana); puts("-------------------"); for (i=0;
i<FILAS; i++) { total += ventas[i]; printf( "%8.2f\n, ventas[i] ); } puts("--------"); printf("%8.2f\n, total ); system(pause);
return 0; } Esta es la salida del programa: Ventas de la semana ------------------- 123.50 234.60 345.45 321.40 345.00
456.65 0.00 -------- 1826.60
cualquier otro tipo de array, sin embargo, es preferible hacer uso de una librera estndar especialmente escrita para
manipulacion de cadenas de caracteres, me reero a la librera <string.h> y que viene incluida con todo compilador de
C, C++.
Para comenzar y antes de ver algunas de las funciones de la mencionada librera, tenemos los siguientes ejemplos:
1. char nombre[] = Oscar"; 2. char nombre2[] = { 'O', 's, 'c', 'a', 'r', '\0' };
En el ejemplo 1 se est declarando la variable nombre como una cadena de caracteres y cuyo contenido inicial es
Oscar.
En el ejemplo 2 se est declarando la variable nombre2 como una cadena de caracteres y cuyo contenido inicial es
{ 'O', 's, 'c', 'a', 'r', '\0' };.
En ambos casos el resultado es el mismo, es decir, al nal se obtiene la misma cadena, pero usted debe poner atencin al
hecho de que toda cadena de caracteres en C, C++ debe terminar con el carcter NULL, que normalmente es igual a cero
y se puede escribir como '\0'. Ahora bien, cuando usted usa la sintaxis mostrada en el ejemplo 1 no tiene que preocuparse
por agregar el caracter NULL, ya que esto lo hace el compilador automticamente.
Los compiladores de C, C++ dan soporte a la biblioteca de funciones <string.h>, a la que accede por medio de la directiva
#include <string.h>. No veremos en detalle todas las funciones contenidas en dicha biblioteca, y nos limitaremos a
mostrar algunos ejemplos de ciertas funciones importantes.
strlen(): Obtener longitud de cadenas
Ejemplo:
char *nombre = Oscar E. Palacios"; cout << strlen(nombre) << endl;
Comentarios: stpcpy copia la cadena src hacia dest, la funcin termina hasta haber encontrado en
src el carcter de terminacin null.
Ejemplo:
char *nombre = Oscar E. Palacios"; char copia[80]; strcpy(copia, nombre); cout << copia << endl;
Comentarios: strcat agrega la cadena src a dest, la funcin termina hasta haber encontrado en src
el carcter de terminacin null.
48 CAPTULO 11. ARRAYS Y CADENAS DE TEXTO
Ejemplo:
char nombre[] = Oscar E."; char copia[80] = " Palacios"; strcat(copia, nombre); cout << copia << endl;
Comentarios: strlwr convierte todos los caracteres alfabticos ( 'A' .. 'Z' ) en dest a sus corres-
pondientes caracteres alfabticos ( 'a' .. 'z' ).
Ejemplo:
char nombre[] = Oscar E. Palacios"; strlwr(nombre); cout << nombre << endl;
Comentarios: strupr convierte todos los caracteres alfabticos ( 'a' .. 'z' ) en dest a sus correspon-
dientes caracteres alfabticos ( 'A' .. 'Z' ).
Comentarios: strchr busca en s el caracter c. La busqueda se lleva a cabo desde el inicio hasta el
nal de s.
Ejemplo:
char nombre[] = Oscar E. Palacios"; char *p; p = strchr(nombre, 'E'); if (p) { cout << nombre contiene a E << endl;
cout << indice = " << (p - nombre) << endl; } else cout << E no est en nombre << endl;
Comentarios: strchr busca en s el caracter c. La busqueda se lleva a cabo desde el nal hasta el
inicio de s.
Ejemplo:
char nombre[] = Oscar E. Palacios"; char *p; p = strrchr(nombre, 'E'); if (p) { cout << nombre contiene a E << endl;
cout << indice = " << (p - nombre) << endl; } else cout << E no est en nombre << endl;
Comentarios: strstr busca en s1 la subcadena s2. La bsqueda se lleva a cabo desde el inicio hasta
el nal de s1.
Ejemplo:
char s[] = Un barco de tristeza"; char *p; p = strstr(s, barco); if (p) { cout << barco est en s << endl; cout << indice
= " << (p - s) << endl; } else cout << barco no est en s << endl;
Nota: Bloodshed Dev-C++ es un IDE (Editor con Depurador Integrado) para programar en C++
en un ambiente grco para Windows, distibuido gratuitamente bajo licencia GPL GNU y usted
puede encontrarlo aqu: www.bloodshed.net. Actualmente (febrero de 2008) se recomienda bajar
la versin Dev-C++ 4.9.9.2.
Una de las ventajas que ofrece la clase cstring es que, a diferencia de las cadenas estndar, sta posee la capacidad
de crecer o disminuir su tamao en tiempo de ejecucin. Adems, entre otras caracteristicas destacables, la clase string
soporta operaciones de asignacin tales como: =, +, +=, etc.; y de comparacin tales como: ==, <=, etc.
Para tener una idea bsica sobre las cadenas en C++ veamos el siguiente programa:
Nota: en el programa se debe de observar el uso del operador de asignacin +=, algo que no es
posible hacer con las cadenas estndar.
Un estudio exhaustivo sobre la clase string requiere de un captulo completo, ya que la misma, segn el manual de refe-
rencia de C++, posee aproximadamente 33 mtodos y unos 7 constructores; adems de los atributos.
Objeto. Es una entidad que tiene atributos o propiedades y que presenta un comportamiento. Puede ser un objeto
del mundo real, o una abstraccin al mundo informtico. Ejemplo, un automvil, una persona, un animal, etc.
Clase. Los humanos percibimos el mundo como un entorno compuesto de cosas, objetos, les llamamos. Interactua-
mos con tantos, que lo natural es agruparlos de acuerdo a sus caractersticas ms relevantes o generales. En otras
palabras, los clasicamos en clases. Todos los objetos que pertenecen a una clase, contiene todos los elementos
comunes a todos los objeto y no particulariza en los elementos que distinguen a los objetos entre s; ejemplo, la
clase automvil, comprende todos los automviles que circulan en este momento por las calles. Incluira elemen-
tos que asociamos a los automviles, como llantas, puertas, asientos, volante, etctera, pero no particularizara en
cuestiones como color, nmero de registro o caractersticas individuales a cada auto.
Propiedad. Una caracterstica asociada a una clase. Ejemplo, todos los autos tienen un color, o un registro aunque
esta propiedad vare entre objetos.
Mtodo. Posible accin que los objetos de una clase pueden realizar. En trminos informticos, un algoritmo que
pueden ejecutar. Ejemplo: todos los autos pueden acelerar, frenar, etc.
Polimorsmo. Una vez que aprendemos a conducir un auto, podemos manejar prcticamente cualquier auto. Esto
se debe a que la interfaz que presentan los objetos auto a los usuarios, es ms o menos la misma para cada auto.
50
12.2. CARACTERSTICAS DE LA PROGRAMACIN ORIENTADA A OBJETOS 51
Todos tiene mtodos para acelerar, y si bien, posiblemente los pormenores de como acelera cada auto, sean sustan-
cialmente diferentes; el usuario hace referencia a un mismo mtodo en todos los autos. En trminos informticos,
objetos de clases diferentes; incluso en la misma clase; pueden tener mtodos homnimos, que hagan cosas equi-
valentes aunque los detalles del como, sean diferentes.
Herencia. La clase auto es una clase muy general; se puede hacer un desglose ms pormenorizado, por ejemplo, si
hacemos subclases de autos deportivos, utilitarios, compactos, manuales, automticos, de lujo, etc. Cada subclase
toma de su clase base (o superclase, en la jerga informtica) las propiedades y mtodos ms generales y agrega
las propias. As, como todos los autos, tienen color y registro, as que stas estaran contenidas en la superclase
auto. Propiedades como Capacidad en toneladas, sistema estreo o cantidad de velocidades, estaran delegadas a
las subclases ms particulares.
Captulo 13
Objetos y Clases
52
13.2. FUNDAMENTOS DE CLASES 53
Para hacer pblica una parte de la clase ( accesible a otras partes del programa ), se debe declarar con la palabra clave
public. Todas las variables o funciones denidas despus de la declaracin pblica son accesibles por todas las dems
funciones en el programa. En nuestra clase CRender, la funcin m_Renderizar() es pblica. Tpicamente, su programa
acceder a los miembros privados de una clase a travs de sus funciones pblicas. Note que la palabra clave public es
seguida con : . Mantenga en mente que un objeto forma una relacin entre cdigo y datos. Una funcin miembro tiene
acceso a los elementos privados de su clase. Esto signica que m_Renderizar tiene acceso a buer en nuestro ejemplo.
Para aadir una funcin miembro a la clase, debe especicar su prototipo en la denicin de la misma.
Una vez que se ha denido una clase, se puede crear un objeto de ese tipo usando el nombre de la clase. El nombre de la
clase se convierte en un especicador del nuevo tipo. Por ejemplo la siguiente declaracin crea 2 objetos llamados render1
y render2 del tipo CRender.
CRender render1, render2;
Cuando un objeto de la clase es creado, ste tendr su propia copia de las variables miembros que contiene la clase. Esto
signica que render1 y render2 tendrn su propia e independiente copia de buer. Los datos asociados con render1 son
distintos y separados de los datos asociados con render2.
Recordemos: En C++, una clase es un nuevo tipo de dato que puede ser usado para crear objetos. Especcamente, una
clase crea una consistencia lgica que dene una relacin entre sus miembros. Cuando se declara una variable de una
clase, se est creando un objeto. Un objeto tiene existencia fsica, y es una instancia especca de una clase. ( Esto es,
un objeto ocupa espacio de memoria, pero una denicin de tipo no ). Adems, cada objeto de una clase tiene su propia
copia de los datos denidos dentro de esa clase.
Dentro de la declaracin de CRender, el prototipo de una funcin es especicado. Ya que las funciones miembros son
prototipadas dentro de la denicin de la clase, no necesitan ser prototipadas en otro lugar cualquiera.
Para implementar una funcin que es un miembro de una clase, debe indicarle al compilador a cual clase pertenece la
funcin calicando el nombre de la funcin con el nombre de la clase. Por ejemplo, esta es una manera de codicar la
funcin m_Renderizar().
void CRender::m_Renderizar() { strcpy(buer, C++ en wikibooks); return; }
13.2.3 Ejemplo
// Programa OPP01.CPP #include <iostream> #include <cstring> using std::cout; using std::endl; // Esto dene la clase
CRender class CRender { public: char buer[256]; void m_Renderizar(const char *cadena); }; /* implementar m_Renderizar()
para la c;*/ void CRender::m_Renderizar(const char *cadena){ strcpy(buer, cadena);//copia la cadena return; } int main
(int argc, char **argv){ // crear 2 objetos CRender CRender render1, render2; render1.m_Renderizar(Inicializando el
objeto render1); render2.m_Renderizar(Inicializando el objeto render2); cout << buer en render1: "; cout << ren-
der1.buer << endl; // tenemos acceso a buer ya que es publico. cout << buer en render2: "; cout << render2.buer
<< endl; return (0); }
En comparacin con la programacin tradicional, un mtodo es lo mismo que una funcin cualquiera, salvo
que como los mtodos se declaran para pertenecer a una clase especca, se dice que todos los mtodos
de dicha clase son miembros de la misma. Por lo dems, la declaracin y denicin de los mtodos es
exactamente igual que declarar y denir cualquier otra funcin.
Atributos:
En comparacin con la programacin tradicional, un atributo es lo mismo que una variable cualquiera, salvo
que como los atributos se declaran para pertenecer a una clase especca, se dice que todos los atributos de
dicha clase son miembros de la misma. Por lo dems, la declaracin de los atributos es exactamente igual
que declarar cualquier otra variable.
Miembros:
A partir de este momento usaremos la palabra miembro para referirnos al hecho de que un mtodo o un
atributo pertenece a tal o cual clase.
Por Ejemplo, en el programa OOP01.CPP ( visto anteriormente ) la Clase CRender posee dos miembros, buer que es
un atributo; y m_Renderizar que es un mtodo.
class CRender { public: char buer[256]; // atributo void m_Renderizar(const char *cadena); // mtodo };
Un miembro pblico signica que el acceso al mismo puede darse dentro del interior de la clase, dentro de una
subclase, y desde un objeto instanciado de cualquiera de estas. Por ejemplo, los miembros de la clase CRender son
accesibles dentro de la misma y podrn accederse desde cualquier otra clase que se derive de CRender, as como
desde cualquier objeto instanciado de estas.
Un miembro privado signica que el acceso al mismo puede darse solamente dentro del interior de la clase que lo
posee. Normalmente, el programador creador de una clase declara a los atributos de la clase como privados y a los
mtodos como pblicos, esto con la idea de que el usuario de la clase no pueda tener acceso a los atributos sino es
a traves de los mtodos denidos para el caso.
Un miembro protegido se comporta de manera parecida a un miembro privado, salvo que estos son accesibles
dentro de la clase que lo posee y desde las clases derivadas, pero no desde los objetos instanciados a raiz de dichas
clases.
13.5 Subclases
Una subclase es una clase que se deriva de otra. La clase que sirve de base suele conocerse como parent (padre), y a la
subclase se le llama child (hija). En C++ cada clase que es creada se convierte en candidata para servir de base de donde
se deriven otras. Por ejemplo, la clase Pareja es candidata para convertirse en la base para las subclases Suma, Resta,
Multiplica, Divide, y otras posibles subclases en donde se utilice un par de valores numricos.
Para poner un ejemplo, pensemos en que deseamos crear la clase Suma, misma que ser utilizada para obtener la suma
de dos nmeros. Puesto que la clase Pareja posee dos atributos nmericos puede ser usada como base para la clase que
estamos proyectando. As, el siguiente ejemplo se constituye en un caso de clases derivadas.
Nota: Observe que la sintaxis para crear una subclase es: class hija : [public | private] padre // Qu signican public y
private aqu? { ... }; Donde padre es la clase base e hija es la subclase.
class Suma : public Pareja { // atributos de Suma double resultado; public: // mtodos de Suma double calcular(); }; //
implementacin de Suma // double Suma::calcular() { return getA() + getB(); }
13.6 Herencia
La herencia es uno de los mecanismos ms tiles de la programacin orientada al objeto, ya que por medio de la misma
se puede llevar a cabo la reutilizacin de cdigo. Es decir, puesto que toda clase denida se convierte en candidata para ser
usada como base de donde se deriven otras, esto da como resultado que las clases derivadas hereden todos los miembros de
la clase base. Por ejemplo, la clase Suma vista en la seccin anterior, hereda todos los miembros de la clase Pareja puesto
56 CAPTULO 13. OBJETOS Y CLASES
que Suma es una extensin de Pareja. En ese sentido, podemos decir que existen dos tipos de herencia, por extensin y
por agregacin o composicin. En el caso de las clases Pareja y Suma, se dice que Suma es una extensin de Pareja.
Vista grcamente, la herencia por extensin se puede representar as:
Al tipo de diagrama mostrado arriba (Herencia por extensin) se le conoce como UML y es utilizado para mostrar de
forma graca la relacin existente entre una clase hija con la clase padre. En el caso del ejemplo, se muestra que la clase
Suma es una extensin de la clase Pareja y, en consecuencia, Suma posee a los miembros { a, b, getA(), getB(), setA(),
setB() } heredados de la clase Pareja. Observe como la clase Suma posee otros dos miembros no heredados, { resultado,
y calcular() }, y es precisamente a este tipo de situacin por lo que se dice que Suma es una extensin de Pareja, ya que
Suma, adems de poseer a todos los miembros de Pareja, se extiende para poseer otros dos miembros.
13.6.2 Agregacion o composicin
La composicin se da en los casos en donde una clase posee un objeto que es una instancia de otra clase. Por ejemplo, la
clase Suma podra escribirse de la siguiente forma:
class Suma { // atributo privado double resultado; public: // mtodo pblico double calcular(); // atributo pblico Pareja
p; }; // implementacin del metodo calcular de la clase Suma. double Suma::calcular() { return p.getA() + p.getB(); }
Luego, si usted presta atencin, notar que el miembro p de la clase Suma es un objeto o instancia de la clase Pareja, en
consecuencia, la clase Suma puede acceder a los miembros de la clase Pareja a travs de la variable p. Tambin se debe
observar que la implementacin del mtodo calcular() es diferente que el mismo de la clase Suma original. Si usted desea
poner a prueba a la nueva clase Suma, compile y ejecute el siguiente programa.
// programa herencia_por_composicion.CPP #include <iostream> using namespace std; class Pareja { // atributos double
a, b; public: // mtodos double getA(); double getB(); void setA(double n); void setB(double n); }; // implementacin de los
mtodos de la clase Pareja double Pareja::getA() { return a; } double Pareja::getB() { return b; } void Pareja::setA(double
n) { a = n; } void Pareja::setB(double n) { b = n; } class Suma { // atributo privado double resultado; public: // mtodo
pblico double calcular(); // atributo pblico Pareja p; }; // implementacin del metodo calcular de la clase Suma. double
Suma::calcular() { return p.getA() + p.getB(); } int main() { Suma s; s.p.setA(80); s.p.setB(100); cout << s.p.getA() <<
" + " << s.p.getB() << " = " << s.calcular() << endl; cin.get(); return 0; }
////SALIDA////
80 + 100 = 180
13.7 Constructores
Un constructor es un mtodo que pertenece a una clase y el cual (en C++) debe tener el mismo nombre de la clase
a la que pertenece. A diferencia de los otros mtodos de la clase, un constructor deber ser del tipo void, es decir, el
mismo no regresar valor alguno. Una clase puede tener ms de un mtodo constructor. Cada clase debe tener al menos
un constructor, tanto as que si el programador creador de una clase no dene un mtodo constructor para la misma, el
sistema, o sea el compilador, crear de manera automtica un constructor nulo.
El objetivo principal del constructor es establecer las condiciones necesarias dentro de la memoria y crear una copia del
objeto mismo dentro de la memoria.
Los constructores suelen usarse para la inicializacin de los atributos de los objetos instanciados. Por ejemplo, con las
instrucciones:
Suma s; s.setA(80); s.setB(100);
13.8. DESTRUCTORES 57
del programa OOP02.CPP, se declara el objeto s de la clase Suma y luego se inicializan los atributos del objeto por
medio de los mtodos setA() y setB(). En este caso, es necesario hacer uso de dichos mtodos debido al hecho de que
no se ha denido un constructor para la clase Suma. Ahora bien, para evitar este tipo de situaciones podemos denir un
mtodo constructor para Suma y que este se encargue de inicializar los atributos. Veamos.
Notas: ya que Suma es una extensin de Pareja, se ha denido el mtodo constructor de Pareja y ste a la vez es invocado
por el constructor de Suma. Otro punto de inters es el hecho de la denicin de un constructor base para Pareja, ya que
de acuerdo con las reglas de programacin en C++ toda clase en donde se dena un constructor parametrizado deber
denir un constructor base; esta regla se aplica en los casos en donde la clase proyectada servir de base para otras.
// programa OOP04.CPP // Ejemplo: clases Pareja y Suma, ambas con constructor #include <iostream> using names-
pace std; //------------------------ class Pareja { // atributos double a, b; public: // constructor de base ( null ) Pareja() {}
// constructror parametrizado Pareja(double x, double y) : a(x), b(y) {} // mtodos double getA(); double getB(); void
setA(double n); void setB(double n); }; // implementacin de los mtodos de la clase Pareja // double Pareja::getA() {
return a; } double Pareja::getB() { return b; } void Pareja::setA(double n) { a = n; } void Pareja::setB(double n) { b
= n; } //------------------------ class Suma : public Pareja { // atributos de Suma double resultado; public: // constructor
Suma(double a, double b) : Pareja(a, b) {} // mtodos de Suma double calcular(); }; // implementacin de Suma // double
Suma::calcular() { return getA() + getB(); } //------------------------ int main() { Suma s(80, 100); cout << s.getA() << "
+ " << s.getB() << " = " << s.calcular() << endl; cin.get(); return 0; }
13.8 Destructores
Un destructor es un mtodo que pertenece a una clase y el cual (en C++) debe tener el mismo nombre de la clase a la
que pertenece. A diferencia de los otros mtodos de la clase, un destructor deber ser del tipo void, es decir, el mismo no
regresar valor alguno. Para diferenciar a un mtodo destructor de un mtodo constructor, al nombre del destructor se le
debe anteponer el caracter ~ (Alt + 126).
El objetivo principal del destructor es el de retirar de la memoria al objeto, o sea, el destructor hace todo lo contrario que
el constructor.
Los destructores suelen usarse para liberar memoria que haya sido solicitada por el objeto a travs de las ordenes malloc(),
new, etc. En tales casos se deber incluir dentro del mtodo destructor la orden free, delete, etc., segn sea el caso.
// clase Pareja con constructor y destructor class Pareja { // atributos double a, b; public: // constructor de base ( nulo )
Pareja() {} // constructor parametrizado Pareja(double x, double y) : a(x), b(y) {} // destructor ~Pareja() {} // mtodos
double getA(); double getB(); void setA(double n); void setB(double n); };
58 CAPTULO 13. OBJETOS Y CLASES
Herencia
Captulo 14
Sobrecarga de Operadores
Ahora bien:
en el primer caso el compilador determina que el tipo de operacin requerida es una suma de enteros debido a que
los dos miembros ( A y B ) de la suma son del tipo entero.
en el segundo caso parece ser que las cosas no son tan claras, ya que en este los miembros involucrados en la suma
son de tipos diferentes, sin embargo el compilador determinar el tipo de operacin requerida y depositar en el
resultado ( R )el valor resultante redondeado.
en el tercero y ltimo de los casos el compilador determina que el tipo de operacin requerida es una suma de reales
debido a que los dos miembros ( X e Y ) de la suma son del tipo double. Tambin en este caso el resultado de la
suma se redondea antes de ponerlo en R.
Si usted trata de compilar el programa anterior descubrir que el compilador se queja, es decir, el compilador no sabe
que hacer en el caso de la instruccin Pareja C = A + B; aunque es evidente que se trata de la declaracion del objeto C y
que el mismo se espera que sea igual a la suma de los objetos A y B. El error se debe al hecho de que el tipo Pareja es en
realidad una clase y no un tipo primitivo y, en consecuencia, se debe de adiestrar al mismo compilador para que ste sepa
59
60 CAPTULO 14. SOBRECARGA DE OPERADORES
de que manera hara la suma de dos objetos del tipo Pareja. As, para solucionar el error vamos a sobrecargar el operador
+ para trabajar la suma de dos de dichos objetos. Vamos:
// Titulo..: programa sobrecarga01.cpp // Objetivo: demostracin de sobrecarga de operadores // Autor...: Oscar E. Pa-
lacios #include <iostream> using namespace std; class Pareja { public: double a, b; // constructor parametrizado Pare-
ja(const double a,const double b) { this->a = a; this->b = b; } }; // Sobrecarga del operador + Pareja& operator +(const
Pareja &p1,const Pareja &p2) { return *(new Pareja(p1.a + p2.a, p1.b + p2.b) ); } int main() { Pareja A(50, 75 ); Pareja
B(150, 175 ); Pareja C = A + B; cout << A = " << A.a << ',' << A.b << "\n"; cout << B = " << B.a << ',' << B.b <<
"\n"; cout << C = " << C.a << ',' << C.b << "\n"; return 0; }
2. operator es una palabra reservada y debe aparecer en toda declaracin de sobrecarga de operadores
4. lista de parmetros indica los argumentos sobre los que operarar la funcin de sobrecarga
Nota: en el caso de que el operador sobrecargado se hace para una clase especca la sintaxis se extiende de la siguiente
manera:
tipo nombre_clase::operator + (lista de parmetros);
Regresando al caso de la clase Pareja, especcamente al operador + de sobrecarga para dicha clase, nos damos cuenta
que dicho operador opera sobre dos objetos de dicha clase y tambin devuelve un resultado de la misma clase. Esto es lo
ms recomendable ya que si usted recuerda el compilador regresa un resultado entero si es que (por ejemplo) se le da una
instruccin como: R = 100 + 23;. En todo caso, el resultado devuelto por los operadores sobrecargados es responsabilidad
del que esta programando
cout << A = " << A.a << ',' << A.b << "\n"; cout << B = " << B.a << ',' << B.b << "\n"; cout << C = " << C.a << ','
<< C.b << "\n";
Usted puede ver cmo para cada uno de los miembros de los objetos se debe de usar el operador de direccin ( . ), pues
bien, nuestro objetivo es lograr que dado un objeto de la clase Pareja ste pueda ser desplegado por cout y para ello
haremos la sobrecarga de operador << con el n de que pueda operar con objetos de la clase mencionada. Veamos:
// Titulo..: programa sobrecarga03.cpp // Objetivo: demostracin de sobrecarga de operadores // Autor...: Oscar E. Pa-
lacios #include <iostream> using namespace std; class Pareja { public: double a, b; // constructor parametrizado Pare-
ja(const double a,const double b) { this->a = a; this->b = b; } }; // Sobrecarga del operador + Pareja& operator +(const
Pareja &p1,const Pareja &p2) { return *(new Pareja(p1.a + p2.a, p1.b + p2.b) ); } // Sobrecarga del operador << para
la clase Pareja ostream& operator << (ostream &o,const Pareja &p) { o << "(" << p.a << ", " << p.b << ")"; return o; }
int main() { Pareja A(50, 75 ); Pareja B(150, 175 ); Pareja C = A + B; cout << A = " << A << "\n"; cout << B = " <<
B << "\n"; cout << C = " << C << "\n"; return 0; }
<< A << "\n"; cout << B = " << B << "\n"; cout << C = " << C << "\n"; return 0; }
1. Los operadores binarios se declaran con un solo parmetro, ya que el primer parmetro es pasado por el programa
como this, es decir, un puntero al mismo objeto.
2. Los operadores unarios se declaran sin paramtros, ya que el nico parmetro es pasado por el programa como
this.
Nota: Los operadores binarios son aquellos que poseen dos partes ( izquierda y derecha), por ejemplo, una operacin de
suma requiere dos operandos ( o1 + o2 ). Los operadores unarios son aquellos que poseen solo una parte, por ejemplo,
una operacin de incremento ( o1 ++ ).
Con el propsito de ver un ejemplo prctico vamos a retomar una vez ms la tan famosa clase Pareja, salvo que en esta
ocasin vamos a sobrecargar dentro de la misma a los operadores binarios: + (suma), - (resta), * (multiplicacin) y /
(divisin); el operador de asignacin (=); el operador de incremento (++) y el operador de comparacin (==).
// Programa: sobrecarga05.cpp // Objetivo: mostrar sobrecarga de operadores // Autor...: Oscar Edmundo Palacios. #in-
clude <iostream> using namespace std; class Pareja { private: int a, b; public: // constructor base Pareja() : a(0), b(0)
{} // constructor parametrizado Pareja(const int a,const int b) { this->a = a; this->b = b; } // constructor de copia Pare-
ja(const Pareja&); // operadores miembros Pareja& operator + (const Pareja &p); Pareja& operator - (const Pareja &p);
Pareja& operator * (const Pareja &p); Pareja& operator / (const Pareja &p); Pareja& operator = (const Pareja &p); Pa-
reja& operator ++(); bool operator ==(const Pareja &p) const; // operadores no miembros friend ostream& operator <<
(ostream &o,const Pareja &p); friend istream& operator >> (istream &o, Pareja &p); }; // implementacion de los opera-
dores para la clase Pareja //.................................... Pareja::Pareja(const Pareja &p) { *this=p; } //....................................
Pareja& Pareja::operator + (const Pareja &p) { this->a += p.a; this->b += p.b; return *this; } //....................................
Pareja& Pareja::operator - (const Pareja &p) { this->a -= p.a; this->b -= p.b; return *this; } //....................................
Pareja& Pareja::operator * (const Pareja &p) { this->a *= p.a; this->b *= p.b; return *this; } //....................................
Pareja& Pareja::operator / (const Pareja &p) { if (p.a != 0) this->a /= p.a; if (p.b != 0) this->b /= p.b; return *this;
} //.................................... Pareja& Pareja::operator = (const Pareja &p) { if(this!=&p){ //Comprueba que no se es-
t intentanod igualar un objeto a s mismo if (p.a != 0) this->a = p.a; if (p.b != 0) this->b = p.b; } return *this; }
//.................................... Pareja& Pareja::operator ++ () { this->a ++; this->b ++; return *this; } //....................................
bool Pareja::operator == (const Pareja &p) const { return this->a == p.a && this->b == p.b; } // implemetacin de ope-
radores no miembros ostream& operator << (ostream &o,const Pareja &p) { o << "(" << p.a << ", " << p.b << ")";
return o; } istream& operator >> (istream &i, Pareja &p) { cout << Introducir valores para ( a, b) :"; i >> p.a >> p.b;
i.ignore(); return i; } // prueba para la clase Pareja int main() { Pareja A(50, 75); Pareja B(100, 15); Pareja C; cout <<
A = " << A << "\n"; cout << B = " << B << "\n"; cout << "......................... << endl; C = A * B; cout << A = " << A
<< "\n"; cout << C = " << C << endl; cout << "......................... << endl; ++C; cout << C = " << C << endl; cout <<
A == B " << ( (A==B) ? Si": No ); cin.get(); return 0; }
Captulo 15
Herencia
Editores:
15.1 Introduccin
La 'herencia' es una de las piedras angulares de la POO ya que sta permite la creacin de clasicaciones jerrquicas. Con
la herencia, es posible crear una clase general que dena tratos comunes a una serie de elementos relacionados. Esta clase
podra luego ser heredada por otras clases ms especcas, cada una agregando solo aquellas cosas que son nicas para la
clase 'heredera'.
En terminologa estndar C++, una clase que es heredada es referida como la clase 'base'. La clase que efecta la herencia
es llamada la clase 'derivada'. Adems, una clase derivada puede ser usada como una clase base por otra clase derivada.
De esta manera, una jerarqua multicapa de clases puede ser lograda.
63
64 CAPTULO 15. HERENCIA
Se puede usar esta amplia denicin de un vehculo rodante para ayudar a denir tipos especcos de vehculos. Por
ejemplo, el fragmento de cdigo mostrado aqui podra usarse para ser heredado por una clase derivada llamada 'Camion'.
// Denicin de una clase 'Camion' derivada de la clase base 'VehiculoRodante'. class Camion : public VehiculoRodante {
public: // CICLO DE VIDA /* En este lugar se sitan los constructores, los destructores, y/o los constructores copia */ //
OPERADORES /* Aqu van los mtodos que se apliquen sobre operadores */ // OPERACIONES /* Aqu van los mto-
dos de esta clase que no sean ni de acceso ni de peticin o tratamiento */ // ACCESO /* Aqu van las funciones de acceso
a los datos miembro o variables propias del objeto */ /* * Funcin 'set_carga' * Recibe: size como int * Devuelve: void *
Asigna al dato miembro 'mCarga' el valor 'size' */ void set_carga(int size) { this->mCarga = size; } /* * Funcin 'get_carga'
* Recibe: void * Devuelve: int * Devuelve el valor que hay dentro del dato miembro 'mCarga' */ int get_carga(void) {
return this->mCarga; } /* * Funcin 'Mostrar' * Recibe: void * Devuelve: void * Muestra por pantalla las ruedas, pasa-
jeros y la capacidad de carga del objeto 'Camion' */ void Mostrar(void); // PETICIONES/TRATAMIENTOS /* Aqu
van las funciones del tipo Is, que generalmente devuelven true/false */ private: /* Generalmente en 'private' se sitan
los datos miembros */ int mCarga; };
Como 'Camion' hereda de 'VehiculoRodante', 'Camion' incluye todo de 'vehiculo_rodante'. Entonces agrega 'carga' a la
misma, en conjunto con las funciones miembros que trabajen sobre este dato.
Ntese como 'VehiculoRodante' es heredado. La forma general para la herencia se muestra aqu:
class ClaseDerivada : acceso ClaseBase { //cuerpo de la nueva clase }
Aqu, 'acceso' es opcional. Sin embargo, si se encuentra presente, debe ser 'public', 'private', o 'protected'. Se aprender
ms sobre estas opciones ms tarde, por ahora para todas las clases heredadas se usar el acceso 'public'. Usar public
signica que todos los miembros pblicos de la clase base sern tambin miembros pblicos de la clase derivada. Por
tanto, en el ejemplo anterior, miembros de la clase 'Camion' tienen acceso a los miembros pblicos de 'VehiculoRodante',
justo como si ellos hubieran sido declarados dentro de 'Camion'. Sin embargo, 'camion' no tiene acceso a los miembros
privados de 'VehiculoRodante'. Por ejemplo, 'Camion' no tiene acceso a ruedas.
He aqu un programa que usa herencia para crear dos subclases de 'VehiculoRodante'. Una es 'Camion' y la otra es
'Automovil'.
// Programa que demuestra la herencia. // INCLUDES DE SISTEMA // #include <iostream> // INCLUDES DEL PRO-
YECTO // // INCLUDES LOCALES // // DECLARACIONES // // Denicin de una clase base para vehiculos class
VehiculoRodante { public: // CICLO DE VIDA /* En este lugar se sitan los constructores, los destructores, y/o los cons-
tructores copia */ // OPERADORES /* Aqu van los mtodos que se apliquen sobre operadores */ // OPERACIONES
/* Aqu van los mtodos de esta clase que no sean ni de acceso ni de peticin o tratamiento */ /* * Funcin 'set_ruedas *
Recibe: num como int * Devuelve: void * Asigna al dato miembro 'mRuedas el valor 'num' */ void set_ruedas(int num) {
this->mRuedas = num; } /* * Funcin 'get_ruedas * Recibe: void * Devuelve: int * Devuelve el valor que hay dentro del
dato miembro 'mRuedas */ int get_ruedas(void) { return this->mRuedas; } /* * Funcin 'set_pasajeros * Recibe: num
como int * Devuelve: void * Asigna al dato miembro 'mPasajeros el valor 'num' */ void set_pasajeros(int num) { this-
>mPasajeros = num; } /* * Funcin 'get_pasajeros * Recibe: void * Devuelve: int * Devuelve el valor que hay dentro del
dato miembro 'mPasajeros */ int get_pasajeros(void) { return this->mPasajeros; } // PETICIONES/TRATAMIENTOS
/* Aqu van las funciones del tipo Is, que generalmente devuelven true/false */ private: /* Generalmente en 'private' se
sitan los datos miembros */ int mRuedas; int mPasajeros; }; // Denicin de una clase 'Camion' derivada de la clase
base 'VehiculoRodante'. class Camion : public VehiculoRodante { public: // CICLO DE VIDA /* En este lugar se si-
tan los constructores, los destructores, y/o los constructores copia */ // OPERADORES /* Aqu van los mtodos que
se apliquen sobre operadores */ // OPERACIONES /* Aqu van los mtodos de esta clase que no sean ni de acceso ni
de peticin o tratamiento */ // ACCESO /* Aqu van las funciones de acceso a los datos miembro o variables propias
del objeto */ /* * Funcin 'set_carga' * Recibe: size como int * Devuelve: void * Asigna al dato miembro 'mCarga' el
valor 'size' */ void set_carga(int size) { this->mCarga = size; } /* * Funcin 'get_carga' * Recibe: void * Devuelve: int *
Devuelve el valor que hay dentro del dato miembro 'mCarga' */ int get_carga(void) { return this->mCarga; } /* * Fun-
cin 'Mostrar' * Recibe: void * Devuelve: void * Muestra por pantalla las ruedas, pasajeros y la capacidad de carga del
objeto 'Camion' */ void Mostrar(void); // PETICIONES/TRATAMIENTOS /* Aqu van las funciones del tipo Is, que
generalmente devuelven true/false */ private: /* Generalmente en 'private' se sitan los datos miembros */ int mCarga; };
void Camion::Mostrar(void) { std::cout << ruedas: " << this->get_ruedas() << std::endl; std::cout << pasajeros: " <<
15.3. CONTROL DE ACCESO DE LA CLASE BASE. 65
this->get_pasajeros() << std::endl; std::cout << Capacidad de carga en pies cbicos: " << this->get_carga() << std::endl;
} /* * Este enumerador sirve para denir diferentes tipos de automvil */ enum tipo {deportivo, berlina, turismo}; // De-
nicin de una clase 'Automovil' derivada de la clase base 'VehiculoRodante'. class Automovil : public VehiculoRodante
{ public: // CICLO DE VIDA /* En este lugar se sitan los constructores, los destructores, y/o los constructores copia
*/ // OPERADORES /* Aqu van los mtodos que se apliquen sobre operadores */ // OPERACIONES /* Aqu van los
mtodos de esta clase que no sean ni de acceso ni de peticin o tratamiento */ // ACCESO /* Aqu van las funciones
de acceso a los datos miembro o variables propias del objeto */ /* * Funcin 'set_tipo' * Recibe: t como tipo * Devuel-
ve: void * Asigna al dato miembro 'mTipoDeAutomovil' el valor 't' */ void set_tipo(tipo t) { this->mTipoDeAutomovil
= t; } /* * Funcin 'get_tipo' * Recibe: void * Devuelve: tipo * Devuelve el valor que hay dentro del dato miembro
'mTipoDeAutomovil' */ enum tipo get_tipo(void) { return this->mTipoDeAutomovil; }; /* * Funcin 'Mostrar' * Re-
cibe: void * Devuelve: void * Muestra por pantalla las ruedas, pasajeros y la capacidad de carga del objeto 'Camion'
*/ void Mostrar(void); private: enum tipo mTipoDeAutomovil; }; void Automovil::Mostrar(void) { std::cout << rue-
das: " << this->get_ruedas() << std::endl; std::cout << pasajeros: " << this->get_pasajeros() << std::endl; std::cout <<
tipo: "; switch(this->get_tipo()) { case deportivo: std::cout << deportivo"; break; case berlina: std::cout << berlina";
break; case turismo: std::cout << turismo"; } std::cout << std::endl; } /* * Funcin 'main' * Recibe: void * Devuel-
ve: int * El cdigo es una posible implementacin para claricar el uso efectivo de clases bases y clases derivadas. *
Muestra por pantalla valores de la clase base y de las clases derivadas. */ int main(void) { Camion Camion1; Camion
Camion2; Automovil Automovil1; Camion1.set_ruedas(18); Camion1.set_pasajeros(2); Camion1.set_carga(3200); Ca-
mion2.set_ruedas(6); Camion2.set_pasajeros(3); Camion2.set_carga(1200); Camion1.Mostrar(); std::cout << std::endl;
Camion2.Mostrar(); std::cout << std::endl; Automovil1.set_ruedas(4); Automovil1.set_pasajeros(6); Automovil1.set_tipo(tipo::deportivo);
Automovil1.Mostrar(); return 0; }
ruedas: 6
pasajeros: 3
Capacidad de carga en pies cbicos: 1200
ruedas: 4
pasajeros: 6
tipo: deportivo
Como este programa muestra, la mayor ventaja de la herencia es que permite crear una clase base que puede ser in-
corporada en clases ms especcas. De esta manera, cada clase derivada puede ser precisamente ajustada a las propias
necesidades y aun siendo parte de la clasicacin general.
Por otra parte, ntese que ambos, 'Camion' y 'Automovil', incluyen una funcin miembro llamada 'Mostrar()', la cual
muestra informacin sobre cada objeto. Esto ilustra un aspecto del polimorsmo. Como cada funcin 'Mostrar()' esta
enlazada con su propia clase, el compilador puede fcilmente indicar cul llamar para cualquier objeto dado.
Ahora que se ha visto los procedimientos bsicos por los cules una clase hereda de otra, se examinar la herencia en
detalle.
derivada es una 'struct' entonces 'public' es el acceso por defecto por la ausencia de un expecicador de acceso explcito.
Examinemos las ramicaciones de usar accesos public o private. ( El especicador 'protected' se describe en la prxima
seccin.)
Cuando una clase base es heredada como 'public', todos los miembros pblicos de la clase base se convierten en miembros
de la clase derivada. En todos los casos, los elementos privados de la clase base se mantienen de esa forma para esta clase,
y no son accesibles por miembros de la clase derivada. Por ejemplo, en el siguiente programa, los miembros pblicos de
'base' se convierten en miembros publics de 'derivada'. Encima, son accesibles por otras partes del programa.
#include <iostream> using namespace std; class base { int i, j; public: void set(int a, int b) { i = a; j = b; } void mostrar()
{ cout << i << " " << j << "\n"; } }; class derivada : public base { int k; public: derivada(int x) { k = x; } void mostrar_k()
{ cout << k << "\n"; } }; int main() { derivada obj(3); obj.set(1, 2); // accesar a miembro de base obj.mostrar(); // accesar
a miembro de base obj.mostrar_k(); // usa miembro de la clase derivada return 0; }
Como set() y mostrar() son heredadas como 'public', ellas pueden ser llamadas en un objeto del tipo 'derivada' desde
main(). Como i y j son especicadas como 'private', ellas se mantienen privadas a base.
El opuesto de herencia publica es herencia privada. Cuando la clase base es heredad como privada, entonces todos los
miembros pblicos de la clase base se convierten en miembros privados de la clase derivada. Por ejemplo, el programa
mostrado a continuacin no compilara, porque set() y mostrar() son ahora miembros privados de 'derivada', y por ende
no pueden ser llamados desde main().
TIP:"Cuando una clase base es heredada como 'private' sus miembros pblicos se convierten en miembros privados de
la clase derivada.
// Este programa no compilara. #include <iostream> using namespace std; class base { int i, j; public: void set(int a, int
b) { i = a; j = b; } void mostrar() { cout << i << " " << j << "\n"; } }; // Miembros publicos de 'base' son privados en
'derivada' class derivada : private base { int k; public: derivada(int x) { k = x; } void mostrar_k() { cout << k << "\n";
} }; int main() { derivada obj(3); obj.set(1, 2); // Error, no se puede acceder a set() obj.mostrar(); // Error, no se puede
acceder a mostrar() return 0; }
La clave a recordar es que cuando una clase base es heredada como 'private', los miembros pblicos de la clase base se
convierten en miebros privados de la clase derivada. Esto signica que aun ellos son accesibles por miembros de la clase
derivada, pero no pueden ser accedidos por otras partes de su programa.
En adicin a public y private, un miembro de la clase puede ser declarado como protegido. Adems, una clase base
puede ser heredada como protegida. Ambas de estas acciones son cumplidas por el especicador de acceso 'protected'.
La palabra clave 'protected' esta incluida en C++ para proveer gran exibilidad para el mecanismo de herencia.
Cuando un miembro de una clase es declarado como 'protected', ese miembro, no es accesible a otros elementos no-
miembros de la clase en el programa. Con una importante excepcin, el acceso a un miembro protegido es lo mismo
que el acceso a un miembro privado, este puede ser accedido solo por otros miembros de la clase de la cual es parte. La
nica excepcin a esta regla es cuando un miembro protegido es heredado. En este caso, un miembro protegido diere
sustancialmente de uno privado.
Como debe conocer, un miembro privado de una clase base no es accesible por cualquier otra parte de su programa,
incluyendo cualquier clase derivada. Sin embargo, los miembros protegidos se comportan diferente. Cuando una clase
base es heredada como publica, los miembros protegidos en la clase base se convierten en miembros protegidos de la
clase derivada, y 'son' accesibles a la clase derivada. Adems, usando 'protected' usted puede crear miebros de clases que
son privados para su clase, pero que aun asi pueden ser heredados y accedidos por una clase derivada.
Considere este programa de ejemplo:
#include <iostream> using namespace std; class base { protected: int i, j; // privados a base, pero accesibles a derivada.
public: void set(int a, int b) { i = a; j = b; } void mostrar() { cout << i << " " << j << "\n"; } }; class derivada : public base
{ int k; public: // derivada puede accesar en base a 'j' e 'i' void setk() { k = i * j; } void mostrark() { cout << k << "\n";
15.3. CONTROL DE ACCESO DE LA CLASE BASE. 67
} }; int main() { derivada obj; obj.set(2, 3); // OK, conocido por derivada. obj.mostrar(); // OK, conocido por derivada.
obj.setk(); obj.mostrark(); return 0; }
Aqu, porque 'base' es heredada por 'derivada' como pblica, y porque j e i son declaradas como protegidas, la funcin
setk() en 'derivada' puede acceder a ellas. Si j e i hubieran sido declaradas como privadas por 'base', entonces 'derivada'
no tuviera acceso a ellas, y el programa no compilara.
RECUERDE: El especicador 'protected' le permite crear un miembro de la clase que es accesible desde la jerarqua de
la clase, pero de otra manera es privado.
Cuando una clase derivada es usada como clase base para otra clase derivada, entonces cualquier miembro protegido de
la clase base inicial que es heredado ( como public ) por la primera clase derivada puede ser heredado nuevamente, como
miembro protegido, por una segunda clase derivada. Por ejemplo, el siguiente programa es correcto, y derivada2 tiene,
de hecho, acceso a 'j' e 'i':
#include <iostream> using namespace std; class base { protected: int i, j; public: void set(int a, int b) { i = a; j = b; } void
mostrar() { cout << i << " " << j << "\n"; } }; // j e i se heredan como 'protected' class derivada1 : public base { int k;
public: void setk() { k = i*j; } // legal void mostrark() { cout << k << "\n"; } }; // j e i se heredan indirectamente a travs
de derivada1 class derivada2 : public derivada1 { int m; public: void setm() { m = i-j; } // legal void mostrarm() { cout <<
m << "\n"; } }; int main() { derivada1 obj1; derivada2 obj2; obj1.set(2, 3); obj1.mostrar(); obj1.setk(); obj1.mostrark();
obj2.set(3, 4); obj2.mostrar(); obj2.setk(); obj2.setm(); obj2.mostrark(); obj2.mostrarm(); return 0; }
Cuando una clase base es heredada como 'private', miembros protegidos de la clase base se convierten en miembros
privados de la clase derivada. Adems, en el ejemplo anterior, si 'base' fuera heredada como 'private', entonces todos los
miembros de 'base' se hubieran vuelto miembros privados de derivada1, signicando que ellos no podran estar accesibles
a derivada2. ( Sin embargo, j e i podran aun ser accesibles a derivada1.) Esta situacin es ilustrada por el siguiente
programa, el cual es un error ( y no compilara ). Los comentarios describen cada error.
// Este programa no compilara. #include <iostream> using namespace std; class base { protected: int i, j; public: void
set(int a, int b) { i = a; j = b; } void mostrar() { cout << i << " " << j << "\n"; } }; // Ahora todos los elementos de
base son privados en derivada1. class derivada1 : private base { int k; public: // Esto es legal porque j e i son privadas
a derivada1 void setk() { k = i*j; } // OK void mostrark() { cout << k << "\n"; } }; // Acceso a j, i, set() y mostrar no
heredado class derivada2 : public derivada1 { int m; public: // Ilegal porque j e i son privadas a derivada1 setm() { m =
j-i; } // error void mostrarm() { cout << m << "\n"; } }; int main() { derivada1 obj1; derivada2 obj2; obj1.set(1, 2); //
Error, no se puede usar set() obj1.mostrar(); // Error, no se puede usar show() obj2.set(3, 4); // Error, no se puede usar
set() obj2.mostrar(); // Error, no se puede usar show() return 0; }
Incluso aunque 'base' es heredada como privada por derivada1, derivada2 aun tiene acceso a los elementos publicos y
protegidos de 'base'. Sin embargo, no puede sobrepasar este privilegio a todo lo largo. Esta es la razon de las partes
'protected' del lenguaje C++. Este provee un medio de proteger ciertos miembros de ser modicados por funciones no-
miembros, pero les permite ser heredadas.
El especicador 'protected' puede ser usado con estructuras. Sin embargo no puede ser usado con una 'union' porque la
union no puede heredar otra clase o ser heredad. ( Algunos compiladores aceptaran su uso en una declaracion en una
union, pero como las uniones no pueden participar en la herencia, 'protected' es lo mismo que 'private' en este contexto.)
El especicador 'protected' puede ocurrir en cualquier lugar en la declaracion de una clase, aunque tipicamente ocurre
despues de ( por defecto ) que los miembros privados son declarados, y antes de los miebros publicos. Ademas, la mayor
forma completa de la declaraciond de una clase es
class nombre-clase { miembros privados protected: miembros protegidos public: miembros publicos };
Por supuesto, la categoria protected es opcional.
68 CAPTULO 15. HERENCIA
Como adicin a especicar el estado protegido para los miembros de una clase, la palabra clave 'protected' tambien puede
ser usada para heredar una clase base. Cuando una clase base es heredada como protected, todos los miembros publicos
y protegidos de la clase base se convierten en miembros protegidos de la clase derivada. Aqui hay un ejemplo:
// Demuestra la herencia de una clase base protegida #include <iostream> using namespace std; class base { int i; protec-
ted: int j; public: int k; void seti(int a) { i = a; } int geti() { return i; } }; // heredar 'base' como protected. class derivada
: protected base { public: void setj(int a) { j = a; }; // j es protected aqui. void setk(int a) { k = a; }; // k es tambien
protected. int getj() { return j; } int getk() { return k; } }; int main() { derivada obj; /* La proxima linea es ilegal porque
seti() es un miembro protegido de derivada, lo cual lo hace inaccesible fuera de derivada. */ // obj.seti(10); // cout <<
obj.geti(); // ilegal -- geti() es protected. // obj.k = 10; // tambien ilegal porque k es protected. // estas declaraciones son
correctas obj.setk(10); cout << obj.getk() << " "; obj.setj(12); cout << obj.getj() << " "; return 0; }
Como puede ver leyendo los comentarios en este programa, k,j, seti() y geti() en 'base' se convierten miembros 'protected'
en 'derivada'. Esto signica que ellos no pueden ser accesados por codigo fuera de 'derivada'. Ademas, dentro de main(),
referencias a estos miembros a traves de obj son ilegales.
Ya que los permisos de acceso que son denidos por 'public', 'protected', y 'private' son fundamentales para la programa-
cin en C++, volvamos a revisar sus signicados.
Cuando una clase miembro es declarada como 'public', esta puede ser accedida por cualquier otra parte del programa.
Cuando un miembro es declarado como 'private', este puede ser accedido solo por miembros de su clase. Adems, clases
derivadas no tienen acceso a miembros privados de la clase base. Cuando un miembro es declarado como 'protected', este
puede ser accedido solo por miembros de su clase, o por clases derivadas. Adems, protected permite que un miembro
sea heredado, pero que se mantenga privado en la jerarqua de la clase.
Cuando una clase base es heredada por el uso de 'public', sus miembros publicos se convierten en miebros publicos de la
clase derivada, y sus miembros protected se convierten en miembros protected de la clase derivada.
Cuando una clase base es heredada por el uso de 'protected', sus miembros publicos y protegidos se convierten en miem-
bros protected de la clase derivada.
Cuando una clase base es heredada por el uso de 'private', sus miembros publicos y protegidos se convierten en miebros
private de la clase derivada.
En todos los casos, los miembros privados de la clase base se mantienen privados a la clase base, y no son heredados.
Es posible para una clase derivada heredar dos o mas clases base. Por ejemplo, en este corto programa, derivada hereda
de ambas clases base1 y base2:
// Un ejemplo de multiples clases base #include <iostream> using namespace std; class base1 { protected: int x; int m;
public: void showx() { cout << x << "\n"; } }; class base2 { protected: int y; public: void showy() { cout << y << "\n"; }
}; // Heredar multiples clases base. class derivada : public base1, public base2 { public: void set(int i, int j) { x= i; y = j;
}; }; int main() { derivada obj; obj.set(10, 20); // proveida por derivada. obj.showx(); // desde base1 obj.showy(); // desde
base2 return 0; }
Como este ejemplo ilustra, para causar que mas de una clase base sea heredad, debe usarse una lista separada por comas.
Ademas, asegurese de usar un especicador de acceso para cada clase heredada.
15.3. CONTROL DE ACCESO DE LA CLASE BASE. 69
Existen dos importantes preguntas relativas a los constructores y destructores cuando la herencia se encuentra implicada.
Primera, dnde son llamados los constructores y destructores de las clases base y clases derivadas? Segunda, como se
pueden pasar los parmetros al constructor de una clase base? Esta seccin responde estas preguntas.
Examine este corto programa:
#include <iostream> using namespace std; class base { public: base() { cout << Construyendo base\n"; } ~base() { cout
<< Destruyendo base\n"; } }; class derivada : public base { public: derivada() { cout << Construyendo derivada\n";
} ~derivada() { cout << Destruyendo derivada\n"; } }; int main() { derivada obj; // no hacer nada mas que construir y
destruir obj return 0; }
Como el comentario en main() indica, este programa simplemente construye y destruye un objeto llamado obj, el cual es
de la clase 'derivada'. Cuando se ejecuta, este programa muestra:
Construyendo base Construyendo derivada Destruyendo derivada Destruyendo base
Como puede ver, el constructor de 'base' es ejecutado, seguido por el constructor en 'derivada'. A continuacin ( ya que
obj es inmediatamente destruido en este programa), el destructor en 'derivada' es llamado, seguido por el de 'base'.
El resultado del experimento anterior puede ser generalizado como lo siguiente: Cuando un objeto de una clase derivada
es creado, el constructor de la clase base es llamado primero, seguido por el constructor de la clase derivada. Cuando un
objeto derivada es destruido, su destructor es llamado primero, seguido por el destructor de la clase base. Vindolo de otra
manera, los constructores son ejecutados en el orden de su derivacin. Los destructores son ejecutado en orden inverso
de su derivacin.
Si lo piensa un poco, tiene sentido que las funciones constructor sean ejecutadas en el orden de su derivacin. Porque
una clase base no tiene conocimiento de las clases derivadas, cualquier inicializacin que necesite realizar es separada de,
y posiblemente un prerequisito a, cualquier inicializacin realizada por la clase derivada. Adems, esta debe ejecutarse
primero.
Asimismo, es bastante sensible que los destructores sean ejecutados en orden inverso a su derivacin. Ya que la clase
base contiene una clase derivada, la destruccion de una clase base implica la destruccion de su clase derivada. Adems,
el constructor derivado debe ser llamado antes de que el objeto sea completamente destruido.
En el caso de una gran jerarqua de clases ( ej: cuando una clase derivada se convierta en la base clase de otra clase derivada
), la regla general se aplica: Los constructores son llamados en orden de su derivacion, los destructores son llamados en
orden inverso. Por ejemplo, este programa
#include <iostream> using namespace std; class base { public: base() { cout << construyendo base\n"; } ~base() { cout
<< Destruyendo base\n"; } }; class derivada1 : public base { public: derivada1() { cout << Construyendo derivada1\n";
} ~derivada1() { cout << destruyendo derivada1\n"; } }; class derivada2 : public derivada1 { public: derivada2() { cout
<< Construyendo derivada2\n"; } ~derivada2() { cout << Destruyendo derivada2\n"; } }; int main() { derivada2 obj;
// construir y destruir obj return 0; }
Muestra la salida:
construyendo base Construyendo derivada1 Construyendo derivada2 Destruyendo derivada2 destruyendo derivada1 Des-
truyendo base
La misma regla general se aplica en situaciones en que se ven implicadas multiples clases base. Por ejemplo, este programa
#include <iostream> using namespace std; class base1 { public: base1() { cout << Construyendo base1\n"; } ~base1() {
cout << Desstruyendo base1\n"; } }; class base2 { public: base2() { cout << Construyendo base2\n"; } ~base2() { cout
<< Destruyendo base2\n"; } }; class derivada : public base1, public base2 { public: derivada() { cout << Construyendo
derivada\n"; } ~derivada() { cout << Destruyendo derivada\n"; } }; int main() { derivada obj; // construir y destruir obj
return 0; }
70 CAPTULO 15. HERENCIA
Produce la salida:
Construyendo base1 Construyendo base2 Construyendo derivada Destruyendo derivada Destruyendo base2 Destruyendo
base1
Como puede ver, los constructores son llamados en el orden de su derivacion, de izquierda a derecha, como se especico
en la lista de herencia en derivada. Los destructores son llamados en orden inverso, de derecha a izquierda. Esto signica
que si base2 fuera especicada antes de base1 en la lista de derivada, como se muestra aqui:
class derivada: public base2, public base1 {
entonces la salida del programa anterior hubiera sido asi:
Construyendo base2 Construyendo base1 Construyendo derivada Destruyendo derivada Destruyendo base1 Derstruyendo
base2
Hasta ahora, ninguno de los ejemplos anteriores han incluido constructores que requieran argumentos. En casos donde
solo el constructor de la clase derivada requiera uno o mas argumentos, simplemente use la sintaxis estandarizada de
parametrizacion del constructor. Pero, como se le pasan argumentos a un constructor en una clase base? La respuesta es
usar una forma expandida de la declaracion de la clase derivada, la cual pasa argumentos entre uno o mas constructores
de la clase base. La forma general de esta declaracion expandida se muestra aqui:
constructor-derivada(lista-argumentos) : base1(lista-argumentos), base2(lista-argumentos), baseN(lista-argumentos) { cuer-
po del constructor derivado }
Aqui, desde base1 hasta baseN son los nombres de las clases base heredadas por la clase derivada. Notese los dos puntos
separando la declaracion del constructor de la clase derivada de las clases base, y que las clases base estan separadas cada
una de la otra por comas, en el caso de multiples clases base.
Considere este programa de ejemplo:
#include <iostream> using namespace std; class base { protected: int i; public: base(int x) { i = x; cout << Construyendo
base\n"; } ~base() { cout << Destruyendo base\n"; } }; class derivada : public base { int j; public: // derivada usa x, y
es pasado en consjunto a base derivada(int x, int y) : base(y) { j = x; cout << Construyendo derivada\n"; } ~derivada()
{ cout << Destruyendo derivada\n"; } void mostrar() { cout << i << " " << j << "\n"; } }; int main() { derivada obj(3,
4); obj.mostrar(); // muestra 4 3 return 0; }
Aqui, el constructor en derivada es declarado para que tome 2 parametros, x, y. Sin embargo, derivada() usa solo x,
y es pasada a base(). En general, el constructor de la clase derivada debe declarar el/los parametro(s) que esta clase
requiere, tamnbien cualquiera requerido por la clase base. Como el ejemplo anterior ilustra, cualquiera de los parametrosr
requeridos por la clase base son pasados a esta en la lista de argumentos de la clase base, especicadas despues de los dos
puntos.
Aqui tenemos un programa de ejemplo que usa multiples clases base:
#include <iostream> using namespace std; class base1 { protected: int i; public: base1(int x) { i = x; cout << Constru-
yendo base1\n"; } ~base1() { cout << Destruyendo base1\n"; } }; class base2 { protected: int k; public: base2(int x) {
k = x; cout << construyendo base2\n"; } ~base2() { cout << Destuyendo base2\n"; } }; class derivada : public base1,
public base2 { int j; public: derivada(int x, int y, int z) : base1(y), base2(z) { j = x; cout << construyendo derivada\n";
} ~derivada() { cout << Destruyendo derivada\n"; } void mostrar() { cout << i << " " << j << " " << k << "\n"; } }; int
main() { derivada obj(3, 4, 5); obj.mostrar(); // mostrar 3 4 5 return 0; }
Es importante comprender que los argumentos al constructor de la clase base son pasado via argumentos al constructor de
la clase derivada. Ademas, incluso si el constructor de una clase derivada no usa ningun argumento, este aun debe declarar
uno o mas argumentos si la clase base toma uno o mas argumentos. En esta situacion, los argumentos pasado a la clase
derivada son simplemente pasados hacia la clase base. Por ejemplo en el siguiente programa, el constructor en derivada
no toma argumentos, pero base1() y base2() lo hacen:
15.3. CONTROL DE ACCESO DE LA CLASE BASE. 71
#include <iostream> using namespace std; class base1 { protected: int i; public: base1(int x) { i=x; cout << Construyendo
base1\n"; } ~base1() { cout << Destruyendo base1\n"; } }; class base2 { protected: int k; public: base2(int x) { k=x; cout
<< construyendo base2\n"; } ~base2() { cout << Destruyendo base2\n"; } }; class derivada : public base1, public base2
{ public: /* el constructor en derivada no usa parametros, pero aun asi debe ser declarado para tomarlos y pasarselos a
las clases base. */ derivada(int x, int y) : base1(x), base2(y) { cout << Construyendo derivada\n"; } ~derivada() { cout
<< Destruyendo derivada\n"; } void mostrar() { cout << i << "" << k << "\n"; } }; int main() { derivada obj(3, 4);
obj.mostrar(); // mostrar 3 4 return 0; }
El constructor de la clasde derivada es libre de usar cualquiera de todos los parameteos que se declara que toma, ya sea
que uno o mas sean pasados a una clase base. Viendolo diferente, solo porque un argumento es pasado a la clase base no
declara su uso en la clase derivada. Por ejemplo, este fragmento es perfectamente valido:
class derivada : public base { int j; public: // derivada usa x, y, y tambien las pasa a base derivada(int x, int y) : base(x,y)
{ j = x*y; cout << construyendo derivada\n"; } // ...
Un punto nal a tener en mente cuando se pase argumentos a los constructores de la clase base: Un argumento siendo
pasado puede consistir de cualquier expresion valida en ese momento, incluyendo llamadas a funciones y variables. Esto
es para sostener el hecho de que C++ permite la inicializacion dinamica.
Cuando una clase base es heredada como private, todos los miembros de esa clase (public, protected o private) se con-
vierten en miembros privates de la clase derivada. Sin embargo, en ciertas circunstancias, podra restaurar uno o mas
miembros heredados a su especicacion de acceso original. Por ejemplo, quizas desee permitir a ciertos miembros pu-
blicos de la clase base tener un estado public en la clase derivada, incluso aunque la clase es heredada como private. Hay
dos maneras de lograr esto. Primero, usted deberia usar una declaracion 'using' con la clase derivada. Este es el metodo
recomendado por los estandares C++ para uso no nuevo codigo. Sin embargo, una discusion de 'using' se retrasa hasta
nales de este libro donde los 'namespaces son examinados. ( La primera razon para usar using es para dar soporte a
namespaces. ) La segunda manera para ajustar el acceso a un miembro heredado es emplear una declaracion de acceso.
Las declaraciones de acceso son aun soportadas por el estandar C++, pero han sido marcadas como deprecadas, lo que
signica que ellas no deben ser usadas para nuevo codigo, Como aun son usadas en codigo existente, una discusion de las
declaraciones de acceso es presentada aqui:
Una declaracion de acceso toma esta forma general:
clase-base::miembro;
La declaracion de acceso es ubicada bajo los apropiados encabezados de acceso en la clase derivada. Notese que no se
requiere ( o permite ) una declaracion de tipo en una declaracion de acceso.
para ver como una declaracion de acceso funciona, comenzemos con este corto fragmento:
class base { public: int j; // public en base }; // heredar base como private class derivada : private base { public: // aqui
esta la declaracion de acceso base::j; // hacer j publica nuevamente. // ... };
Como base es heredada como private por derivada, la variable publica j es hecha una variable 'private' en derivada. Sin
embargo, la inclusion de esta declaracion de acceso
base::j;
Puede usar una declaracion de acceso para restaturar los derechos de acceso de miembros 'public' y 'protected', Sin
embargo, no puede usar una declaracion de acceso para elevar o disminuir un estado de acceso de un miembro. Por
ejemplo, un miembro declarado como private dentro de una clase base no puede ser hecho public por una clase derivada.
( Permitir esto destruirira la encapsulacion!)
El siguiente programa ilustra el uso en declaraciones de acceso.
#include <iostream> using namespace std; class base { int i; // private en base public: int j, k; void seti(int x) { i = x;
72 CAPTULO 15. HERENCIA
} int geti() { return i; } }; // Heredar base como private class derivada : private base { public: /* las siguientes tres de-
claraciones sobreescriben la herencia de base como private y restablece j, seti() y geti() a publico acceso */ base::j; //
hacer j public nuevamente - pero no k base::seti; // hacer seti() public base::geti; // hacer geti() public // base::i; // ilegal
- no se puede elevar el acceso int a; // public }; int main() { derivada obj; // obj.i = 10; // ilegal porque i es private en
derivada obj.j = 20; // legal porque j es hecho public en derivada //obj.k = 30; // ilegal porque k es private en derivada
obj.a = 40; // legal porque a es public en derivada obj.seti(10); cout << obj.geti() << " " << obj.j << " " << obj.a; return 0; }
Notese como este programa usa declaraciones de acceso para restaurar j, seti(), y geti() a un estadi 'public'. Los comen-
tarios describen otras varias restricciones de acceso.
C++ provee la habilidad de ajustar acceso a los miembros heredados para acomodar aquellas situaciones especiales en las
cuales la mayoria de una clase heredad esta pensada para ser private, pero unos cuantos miembros se hacen para mantener
su estado public o protected. Es mejor usar esta caracteristica escasamente.
Como indican los comentarios en este programa, ambos derivada1 y derivada2 heredan base. Sin embargo, derivada3
hereda ambos, derivada1 y derivada2. Como resultado existen dos copias de base presenten en un objeto del tipo derivada3,
asi pues una expresion como
obj.i = 20;
hacia cual i se esta haciendo referencia? La que se encuentra en derivada1 o derivada2? Como existen dos copias de base
presentes en el objeto 'obj' entonces hays dos obj.is. Como puede ver, la declaracion es inherentemente ambigua.
15.3. CONTROL DE ACCESO DE LA CLASE BASE. 73
Existen dos maneras de remediar el anterior programa. La primera es aplicar el operador de resolucion de ambito para
manualmente seleccionar una 'i'. Por ejemplo, la siguiente version el programa compilara y se ejecutara como se esperaba:
// Este programa usa el la resolucion de // ambito explicita para seleccionar 'i' #include <iostream> using namespace std;
class base { public: int i; }; // derivada 1 hereda base class derivada1 : public base { public: int j; }; // derivada2 hereda
base class derivada2 : public base { public: int k; }; /* derivada3 hereda de ambas, derivada1 y derivada2 esto signica
que hay 2 copias de base en derivada3! */ class derivada3 : public derivada1, public derivada2 { public: int sum; }; int
main() { derivada3 obj; obj.derivada1::i = 10; // ambito resuelto, se usa la 'i' derivada1 obj.j = 20; obj.k = 30; // ambito
resuelto obj.sum = obj.derivada1::i + obj.j + obj.k; // tambien resuelto aqui.. cout << obj.derivada1::i << " "; cout << obj.j
<< " " << obj.k << " "; cout << obj.sum; return 0; }
Aplicando ::, el programa manualmente selecciona a derivada1 como version de base. Sin embargo, esta solucion levanta
un detalle profundo: Que pasa si solo una copia de base es en realidad requerida? Existe alguna forma de prevenir que dos
copias sean incluidas en derivada3? La respuesta, como probablemente ha adivinado se logra con 'clases base virtuales.
Cuando dos o mas objetos son derivados de una clase base comn, puede prevenir mltiples copias de la clase base de
estar presentes en un objeto derivado por esas clases, declarando la clase base como virtual cuando esta es heredada. para
hacer esto, se precede el nombre de la clase base con la palabra virtual cuando esta es heredada.
Para ilustrar este proceso, aqui esta otra version del programa de ejemplo. Esta vez, derivada3 contiene solo una copia de
base.
// Este programa usa clases base virtuales. #include <iostream> using namespace std; class base { public: int i; }; // de-
rivada1 hereda base como virtual class derivada1 : virtual public base { public: int j; }; // derivada2 hereda base como
virtual class derivada2 : virtual public base { public: int k; }; /* derivada3 hereda ambas, derivada1 y derivada2. Esta
vez, solo existe una copia de la clase base. */ class derivada3 : public derivada1, public derivada2 { public: int sum; }; int
main() { derivada3 obj; obj.i = 10; // ahora no-ambiguo obj.j = 20; obj.k = 30; // no-ambiguo obj.sum = obj.i + obj.j +
obj.k; // no-ambiguo cout << obj.i << " "; cout << obj.j << " " << obj.k << " "; cout << obj.sum; return 0; }
Como puede ver la palabra clave 'virtual' precede el resto de la especicacin de la clase heredada. Ahora que ambas,
derivada1 y derivada2 han heredado base como virtual, cualquier herencia multiple que las incluya causar que slo una
copia de base se encuentre presente. Adems, en derivada3 existe slo una copia de base, y obj.i = 10 es perfectamente
vlida y no-ambiguo.
Otro punto a tener en mente: incluso cuando ambas, derivada1 y derivada2 espcican base como virtual, base sigue
presente en los dos tipos derivados. Por ejemplo, la siguiente secuencia es perfectamente vlida:
// Denir una clase del tipo derivada1 derivada1 miclase; miclase.i = 88;
La diferencia entre una clase base normal y una virtual se hace evidente slo cuando un objeto hereda la clase base mas
de una vez; si la clase base ha sido declarada como virtual, entonces slo una instancia de esta estar presente en el objeto
heredado. De otra manera, mltiples copias seran encontradas.
Captulo 16
Funciones virtuales
Editores:
16.1 Introduccin
Una de las tres mayores facetas de la programacin orientada a objetos es el polimorsmo. Aplicado a C++, el trmino
polimorsmo describe el proceso por el cual diferentes implementaciones de una funcin pueden ser accedidas a travs
del mismo nombre. Por esta razn, el polimorsmo es en ocasiones caracterizado por la frase Un interfaz, mltiples
mtodos. Esto signica que cada miembro de una clase general de operaciones puede ser accedido del mismo modo,
incluso cuando las acciones especcas con cada operacin puedan diferir.
En C++, el polimorsmo es soportado en tiempo de ejecucin y en tiempo de compilacin. La sobrecarga de operadores
y funciones son ejemplos de polimorsmo en tiempo de compilacin. Aunque la sobrecarga de operadores y funciones es
muy poderosa, stos no pueden realizar todas las tareas requeridas por un lenguaje realmente orientado a objetos. Adems,
C++ tambin permite polimorsmo en tiempo de ejecucin a travs del uso de clases derivadas y funciones virtuales, que
son los principales temas de este captulo.
Este captulo comienza con una corta discusin de punteros a tipos derivados, ya que stos dan soporte al polimorsmo
en tiempo de ejecucin.
74
16.2. PUNTEROS A TIPOS DERIVADOS. 75
En este ejemplo, 'p' puede ser usado para acceder a todos los elementos de objD heredados de objB. Sin embargo elementos
especicos a objD no pueden ser referenciados con 'p'.
Para un ejemplo mas concreto, considere el siguiente programa, el cual dene una clase llamada clase_B y una clase
derivada llamada clase_D. Este programa usa una simple jerarquia para almacenar autores y titulos.
// Usando punteros base en objetos de una clase derivada #include <iostream> #include <cstring> using namespace std;
class clase_B { public: void put_autor(char *s) { strcpy(autor, s); } void mostrar_autor() { cout << autor << "\n"; } pri-
vate: char autor[80]; }; class clase_D : public clase_B { public: void put_titulo(char *num) { strcpy(titulo, num); } void
mostrar_titulo() { cout << Titulo: "; cout << titulo << "\n"; } private: char titulo[80]; }; int main() { clase_B *p; clase_B
objB; clase_D *dp; clase_D objD; p = &objB; // direccion la clase base // Accesar clase_B via puntero p->put_autor(Tom
Clancy); // Accesar clase_D via puntero base p = &objD; p->put_autor(William Shakespeare); // Mostrar cada autor
a traves de su propio objeto. objB.mostrar_autor(); objD.mostrar_autor(); cout << "\n"; /* Como put_titulo() y mos-
trar_titulo() no son parte de la clase base, ellos no son accesibles a traves del puntero base 'p' y deben ser accedidas
directamente, o, como se muestra aqui, a traves de un puntero al tipo derivado. */ *dp = objD; dp->put_titulo(La Tem-
pestad); p->mostrar_autor(); // los dos, 'p' o 'dp' pueden ser usados aqui. dp->mostrar_titulo(); return 0; }
Los parentesis externos son necesarios para asociar el cast con 'p y no con el tipo de retorno de mostrar_titulo(). Aunque
no hay nada tecnicamente erroneo con castear un puntero de esta manera, es probablemente mejor evitarlo, ya que este
simplemente agrega confusion a sus codigo. ( En realidad, la mayoria de los programadores de C++ consideran esto como
mala forma.)
Otro punto a comprender es que, mientras un puntero base puede ser usado para apuntar a cualquier objeto derivado, no
es posible hacerlo de manera inversa. Esto es, no puede acceder a un objeto de tipo base usando un puntero a una clase
derivada.
Un puntero es incrementado y decrementado relativamente a su tipo base. Por lo tanto, cuando un puntero de la clase
base esta apuntado a un objeto derivado, incrementarlo o decrementarlo no hara que apunte al siguiente objeto de la clase
derivada. En vez de eso, apuntara a ( lo que piense que es ) el proximo objeto de la clase base. Por lo tanto, deberia
considerar invalido incrementar o decrementar un puntero de clase base cuando esta apuntando a un objeto derivado.
El hecho de que un puntero a un tipo base pueda ser usado para apuntar a cualquier objeto derivado de la base es extrema-
damente importante, y fundamental para C++. Como aprendera muy pronto, esta exibilidad es crucial para la manera
en que C++ implementa su polimorsmo en tiempo de ejecucion.
Similar a la accin de punteros ya descritas, una referencia a la clase base puede ser usada para referirse a un objeto de
un tipo derivado. La mas comun aplicacion de esto es encontrada en los parametros de la funciones. Un parametro de
referencia de la clase base puede recibir objetos de la clase base asi como tambien otro tipo derivado de esa misma base.
76 CAPTULO 16. FUNCIONES VIRTUALES
El polimorsmo en tiempo de ejecucion es logrado por una combinacion de dos caracteristicas: 'Herencia y funciones
virtuales. Aprendio sobre la herencia en el capitulo precedente. Aqui, aprendera sobre funcion virtual.
Una funcin virtual es una funcion que es declarada como 'virtual' en una clase base y es redenida en una o mas clases
derivadas. Ademas, cada clase derivada puede tener su propia version de la funcion virtual. Lo que hace interesantes a
las funciones virtuales es que sucede cuando una es llamada a traves de un puntero de clase base ( o referencia ). En esta
situacion, C++ determina a cual version de la funcion llamar basandose en el tipo de objeto apuntado por el puntero.
Y, esta determinacion es hecha en 'tiempo de ejecucion'. Ademas, cuando diferentes objetos son apuntados, diferentes
versiones de la funcion virtual son ejecutadas. En otras palabras es el tipo de objeto al que esta siendo apuntado ( no el
tipo del puntero ) lo que determina cual version de la funcion virtual sera ejecutada. Ademas, si la clase base contiene
una funcion virtual, y si dos o mas diferentes clases son derivadas de esa clase base, entonces cuando tipos diferentes
de objetos estan siendo apuntados a traves de un puntero de clase base, diferentes versiones de la funcion virtual son
ejecutadas. Lo mismo ocurre cuando se usa una refrencia a la clase base.
Se declara una funcion como virtual dentro de la clase base precediendo su declaracion con la palabra clave virtual.
Cuando una funcion virtual es redenida por una clase derivada, la palabra clave 'virtual' no necesita ser repetida ( aunque
no es un error hacerlo ).
Una clase que incluya una funcion virtual es llamada una 'clase polimorca'. Este termino tambien aplica a una clase que
hereda una clase base conteniendo una funcion virtual.
Examine este corto programa, el cual demuestra el uso de funciones virtuales:
// Un ejemplo corto que usa funciones virtuales. #include <iostream> using namespace std; class base { public: virtual
void quien() {cout << Base << endl;} // especicar una clase virtual }; class primera_d : public base { public: // rede-
nir quien() relativa a primera_d void quien() {cout << Primera derivacion << endl;} }; class segunda_d : public base {
public: // redenir quien relativa a segunda_d void quien() {cout << Segunda derivacion << endl;} }; int main() { base
obj_base; base *p; primera_d obj_primera; segunda_d obj_segundo; p = &obj_base; p->quien(); // acceder a quien() en
base p = &obj_primera; p->quien(); // acceder a quien() en primera_d p = &obj_segundo; p->quien(); // acceder a quien()
en segunda_d return 0; }
Sin embargo, llamar a una funcion virtual de esta manera ignora sus atributos polimorcos. Es solo cuando una funcion
virtual es accesada por un puntero de clase base que el polimorsmo en tiempo de ejecucion es logrado.
A primera vista, la redenicion de una funcion virtual en una clase derivada parece ser una forma especial de sobrecarga
de funcion. Sin embargo, este no es el caso. De hecho, los dos procesos son fundamentalmente diferentes. Primero,
una funcion sobrecargada debe diferir en su tipo y/o numero de parametros, mientras que una funcion virtual redenida
debe tener exactamente el mismo tipo y numero de parametros. De hecho, los prototipos para una funcion virtual y sus
redeniciones debe ser exactamente los mismos. Si los prototipos dieren, entonces la funcion simplemente se considera
sobrecargada, y su naturaleza virtual se pierde. Otra restriccion es que una funcion virtual debe ser un miembro, no una
funcion 'friend', de la clase para la cual es denida. Sin embargo, una funcion virtual puede ser una funcion 'friend' de
otra clase. Tambien, es permisible para funciones destructores ser virtuales, pero esto no es asi para los constructores.
Cuando una funcion virtual es redenida en una clase derivada, se dice que es una funcion 'overriden' ( redenida )"
Por las restricciones y diferencias entre sobrecargar funciones normales y redenir funciones virtuales, el termino 'ove-
rriding' es usado para describir la redenicion de una funcion virtual.
Una vez que una funcion es declarada como virtual, esta se mantiene virtual sin importar cuantas capas de clases derivadas
esta debe perdurar. Por ejemplo, si 'segunda_d' es derivada de 'primera_d' en vez de 'base', como se muestra en el proximo
ejemplo, entonces quien() es aun virtual y la version apropiada es aun correctamente seleccionada.
// Derivar de primera_d, no de base. class segunda_d : public primera_d { public: void quien() { // denir 'quien()' relativa
a 'segunda_d' cout << Segunda derivacion\n"; } };
Cuando una clase derivada no redene una funcion virtual, entonces la funcion, como se denicio en la clase base, es
usada. Por ejemplo intente esta version del programa precedente en el cual 'segunda_d' no redene 'quien()':
#include <iostream> using namespace std; class base { public: virtual void quien() { cout << Base\n"; } }; class pri-
mera_d : public base { public: void quien() { cout << Primera derivacion\n"; } }; class segunda_d : public base {
public: // quien() no denida }; int main() { base obj_base; base *p; primera_d obj_primera; segunda_d obj_segunda; p
= &obj_base; p->quien(); // acceder a quien() en 'base' p = &obj_primera; p->quien(); // acceder a quien() en 'primera_d'
p = &obj_segunda; p->quien(); /* acceder a quien() en 'base' porque segunda_d no la redene */ return 0; }
Como puede ver, 'segunda_d' ahora usa la version 'quien()' de 'primera_d' porque esa version es la mas cercana en la
cadena de herencia.
Como se declaraba en el inicio de este captulo, las funciones virtuales en combinacin con tipos derivados le permiten a
C++ soportar polimorsmo en tiempo de ejecucin. El polimorsmo es esencial para la programacin orientada a objetos
por una razn: Esta permite a una clase generalizada especicar aquellas funciones que sern comunes a todas las derivadas
de esa clase, mientras que permite a una clase derivada denir la implementacin especca de algunos o todas de esas
funciones. A veces esta idea es expresada como: La clase base dicta la 'interface' general que cualquier objeto derivado
de esa clase tendr, pero permite a la clase derivada denir el mtodo actual usado para implementar esa interface. De
ah que la frase una interface mltiples mtodos sea usada a menudo para describir el polimorsmo.
Parte del truco de aplicar el polimorsmo de una manera satisfactoria es comprender que la clase base y derivada forman
una jerarqua, la cual se mueve de mayor a menor generalizacin (base a derivada). Diseada correctamente, la clase
base provee todos los elementos que una clase derivada puede usar directamente. Tambin dene cuales funciones la
clase derivada debe implementar por su cuenta. Esto permite a la clase derivada la exibilidad para denir sus propios
mtodos, y aun mantener un interface consistente. Eso es, como la forma de la interface es denida por la clase base,
cualquier clase derivada compartir esa interface comn. Adems, el uso de funciones virtuales hace posible para la clase
base denir interfaces genricas que sern usada por todas las clases derivadas. En este punto, usted debe preguntarse a
si mismo porque una consistente interface con mltiples implementaciones es importante. La respuesta, nuevamente, no
lleva a la fuerza central manejadora detrs de la programacin orientada a objetos: Esta ayuda al programador a manejar
programas de complejidad creciente. Por ejemplo, si usted desarrolla su programa correctamente, entonces usted sabr
que todos los objetos que usted derive de una clase base son accedidos en la misma manera general, incluso si las acciones
especcas varan de una clase derivada a la prxima. Esto signica que usted necesita solo recordar una interface, en vez de
varias. Tambin, su clase derivada es libre de usar cualquiera o toda la funcionalidad provista por la clase base. No necesita
reinventa esos elementos. Por tanto, la separacin de interface e implementacin permite la creacin de libreras de clases,
las cuales pueden ser provistas por un tercero. Si estas libreras son implementadas correctamente, ellas proveern una
interface comn que usted puede usar para derivar clases suyas propias que cumplan sus necesidades especicas. Por
ejemplo, tanto como las Microsoft Fundation Classes ( MFC ) y la librera de clases .NET Framework Windows Forms
soporta programacin en Windows. Usando estas clases, su programa puede heredar mucha de la funcionalidad requerida
por un programa de Windows. Usted necesita agregar solo las caractersticas nicas a su aplicacin. Este es un mayor
benecio cuando se programa en sistemas complejos.
Para tener una idea del poder del concepto una interface, multiples metodos, examinemos el siguiente programa. Este
crea una clase base llamada 'gura'. Esta clase almacena las dimensiones de varios objetos de 2-dimensiones y calcula sus
areas. La funcion 'set_dim()' es una funcion miembro estandar porque esta operacion sera comun a las clases derivadas.
Sin embargo, 'mostrar_area()' es declarada como virtual porque el metodo de computar el area de cada objeto puede
variar. El programa usa 'gura' para derivar dos clases especicas llamadas 'rectangulo' y 'triangulo'.
#include <iostream> using namespace std; class gura { protected: double x, y; public: void set_dim(double i, double j) {
x = i; y = j; } virtual void mostrar_area() { cout << No hay calculo de area denido "; cout << " para esta clase.\n"; } };
class triangulo : public gura { public: void mostrar_area() { cout << Triangulo con alto "; cout << x << " y base " << y;
cout << " tiene un area de "; cout << x * 0.5 * y << ".\n"; } }; class rectangulo : public gura { public: void mostrar_area()
{ cout << Rectangulo con dimensiones "; cout << x << " x " << y; cout << " tiene un area de "; cout << x * y << ".\n";
} }; int main() { gura *p; // crear un puntero al tipo base. triangulo t; // crear objetos de tipos derivados rectangulo r; p
= &t; p->set_dim(10.0, 5.0); p->mostrar_area(); p = &r; p->set_dim(10.0, 5.0); p->mostrar_area(); return 0; }
En el programa, notese que ambas interfaces, 'rectangulo' y 'triangulo', son la misma, incluso ambas proveen sus propios
metodos para computar el area de cada uno de sus objetos.
Dada la declaracion para 'gura', es posible derivar una clase llamada 'circulo' que computara el area de un circulo, dado
en radio? La respuesta es 'Si'. Todo lo que necesita hacer es crear un nuevo tipo derivado que calcule el area de un circulo.
El poder de las funciones virtuales esta basado en el hecho de que usted puede facilmente derivar un nuevo tipo que
mantendra un interface comun con otros objetos relaciones. Por ejemplo, esta es una manera de hacerlo:
class circulo : public gura { public: void mostrar_area() { cout << Circulo con radio "; cout << x; cout << " tiene un
area de "; cout << 3.14 * x * x; } };
Antes de intentar usar 'circulo', vea de cerca la denicion de 'mostrar_area()'. Note que esta usa solo el valor de x, el
cual se asume que almacena el radio. ( Recuerde, el area de un circulo es calculada usando la formula 'PI*R a la 2'.)
Sin embargo, la funcion 'set_dim()' como se dene en 'gura', asume que seran pasados dos valores, no solo uno. Como
'circulo' no requiere este segundo valor, que tipo de accin podemos tomar?
Hay dos manera de resolver este problema. La primera y peor, usted simplemente llama a 'set_dim()' usando un valor
falso como segundo parametro cuando use un objeto 'circulo'. Esto tiene la desventaja de ser chapucero, en conjunto
requiriendo que recuerde una excepcion especial, la cual viola la losoa una interface, muchos metodos.
Una mejor manera de resolver este problema es pasarle al parametro 'y' dentro de 'set_dim()' un valor por defecto. En-
tonces, cuando se llame a 'set_dim()' para un circulo, necesita especicar solo el radio. Cuando llame a 'set_dim()' para
un triangulo o un rectangulo, especicara ambos valores. El programa expandido, el cual usa este metodo, es mostrado
aqui:
#include <iostream> using namespace std; class gura { protected: double x, y; public: void set_dim(double i, double j=0)
{ x = i; y = j; } virtual void mostrar_area() { cout << No hay calculo de area denido "; cout << " para esta clase.\n";
} }; class triangulo : public gura { public: void mostrar_area() { cout << Triangulo con alto "; cout << x << " y base
" << y; cout << " tiene un area de "; cout << x * 0.5 * y << ".\n"; } }; class rectangulo : public gura { public: void
mostrar_area() { cout << Rectangulo con dimensiones "; cout << x << " x " << y; cout << " tiene un area de "; cout <<
x * y << ".\n"; }; }; class circulo : public gura { public: void mostrar_area() { cout << Circulo con radio "; cout << x;
cout << " tiene un area de "; cout << 3.14 * x * x; } }; int main() { gura *p; // crear un puntero al tipo base triangulo
t; // crear objetos de tipos derivada rectangulo r; circulo c; p = &t; p->set_dim(10.0, 5.0); p->mostrar_area(); p = &r;
p->set_dim(10.0, 5.0); p->mostrar_area(); p = &c; p->set_dim(9.0); p->mostrar_area(); return 0; }
Como se ha visto, cuando una funcion virtual que no es redenida en una clase derivada es llamada por un objeto de
esa clase derivada, la version de la funcion como se ha denido en la clase base es utilizada. Sin embargo, en muchas
circunstancias, no habra una declaracion con signicado en una funcion virtual dentro de la clase base. Por ejemplo, en
la clase base 'gura' usada en el ejemplo anterior, la denicion de 'mostrar_area()' es simplemente un sustituto sintetico.
No computara ni mostrara el area de ningun tipo de objeto. Como vera cuando cree sus propias librerias de clases no es
poco comun para una funcion virtual tener una denicion sin signicado en el contexto de su clase base.
Cuando esta situacion ocurre, hay dos manera en que puede manejarla. Una manera, como se muestra en el ejemplo, es
simplemente hacer que la funcion reporte un mensaje de advertencia. Aunque esto puede ser util en ocasiones, no es el
apropiado en muchas circunstancias. Por ejemplo, puede haber funciones virtuales que simplemente deben ser denidas
por la clase derivada para que la clase derivada tenga algun signicado. Considere la clase 'triangulo'. Esta no tendria
80 CAPTULO 16. FUNCIONES VIRTUALES
signicado si 'mostrar_area()' no se encuentra denida. En este caso, usted desea algun metodo para asegurarse de que
una clase derivada, de hecho, dena todas las funciones necesarias. En C++, la solucion a este problema es la 'funcion
virtual pura.'
Una 'funcion virtual pura' es una funcin declarada en una clase base que no tiene denicion relativa a la base. Como
resultado, cualquier tipo derivado debe denir su propia version -- esta simplemente no puede usar la version denida en
la base. Para declarar una funcion virtual pura use esta forma general:
virtual 'tipo' 'nombre_de_funcion'(lista_de_parametros) = 0;
Aqui, 'tipo' es el tipo de retorno de la funcion y 'nombre_de_funcion' es el nombre de la funcion. Es el = 0 que designa la
funcion virtual como pura. Por ejemplo, la siguiente version de 'gura', 'mostrar_area()' es una funcion virtual pura.
class gura { double x, y; public: void set_dim(double i, double j=0) { x = i; y = j; } virtual void mostrar_area() = 0; //
pura };
Declarando una funcion virtual como pura, se fuerza a cualquier clase derivada a denir su propia implementacin. Si
una clase falla en hacerlo, el compilador reportara un error. Por ejemplo, intente compilar esta version modicando del
programa de guras, en el cual la denicin de mostrar_area() ha sido removida de la clase 'circulo':
/* Este programa no compilara porque la clase circulo no redenio mostrar_area() */ #include <iostream> using names-
pace std; class gura { protected: double x, y; public: void set_dim(double i, double j=0) { x = i; y = j; } virtual void
mostrar_area() = 0; // pura }; class triangulo : public gura { public: void mostrar_area() { cout << Triangulo con alto
"; cout << x << " y base " << y; cout << " tiene un area de "; cout << x * 0.5 * y << ".\n"; } }; class rectangulo : public
gura { public: void mostrar_area() { cout << Rectangulo con dimensiones "; cout << x << " x " << y; cout << " tiene
un area de "; cout << x * y << ".\n"; }; }; class circulo : public gura { // la no denicion de mostrar_area() causara un
error }; int main() { gura *p; // crear un puntero al tipo base triangulo t; // crear objetos de tipos derivada rectangulo r;
circulo c; // ilegal -- no puedo crearla! p = &t; p->set_dim(10.0, 5.0); p->mostrar_area(); p = &r; p->set_dim(10.0, 5.0);
p->mostrar_area(); return 0; }
Si una clase tiene al menos una funcion virtual pura, entonces esa clase se dice que es 'abstracta'. Una clase abstracta tiene
una caracteristica importante: No puede haber objetos de esa clase. En vez de eso, una clase abstracta debe ser usada
solo como una base que otras clases heredaran. La razon por la cual una clase abstracta no puede ser usada para declarar
un objeto es, por supuesto, que una o mas de sus funciones no tienen denicion. Sin embargo, incluso si la clase base es
abstracta, la puede usar aun para declarar punteros o referencias, los cuales son necesarios para soportar el polimorsmo
en tiempo de ejecucion.
Existen dos terminos que son comunmente usado cuando se discute sobre programacin orientada a objetos: Enlace
temprano y Enlace Tardio ( del ingles early binding and late binding ). Relativo a C++, estos terminos se reeren a
eventos que ocurren en tiempo de compilacion y eventos que ocurren en tiempo de ejecucion, respectivamente.
Enlace temprano signica que una llamada a una funcion es resuelta en tiempo de compilacion. Esto es, toda la informacion
necesaria para llamar a una funcion es conocida cuando el programa es compilado. Ejemplos de enlace temprano incluyen
llamadas a funciones estandar, llamadas a funciones sobrecargadas y llamadas a funciones de operadores sobrecargados.
La principal ventaja del enlace temprano es la eciencia -- es rapido, y a menudo requiere menos memoria. Su desventaja
es falta de exibilidad.
Enlace tardio signica que una llamada a la funcion es resuelta en tiempo de ejecucin. Mas precisamente a que la llamada
a la funcion es determinada al vuelo mientras el programa se ejecuta. Enlace tardio es logrado en C++ a traves del uso
de funciones virtuales y tipos derivados. La ventaja del enlace tardio es que permite gran exibilidad. Puede ser usada
para soportar una interface comn, mientras que se permite a varios objetos utilizar esa interface para denir sus propias
implementaciones. Por tanto, puede ser usada para ayudar a crear librerias de clases, las cuales pueden ser reusadas y
extendidas. Su desventaja, sin embargo, es una ligera perdida de velocidad de ejecucion.
16.2. PUNTEROS A TIPOS DERIVADOS. 81
Punteros
Editores:
17.1 Punteros
Los punteros permiten simular el paso por referencia, crear y manipular estructuras dinamicas de datos, tales como listas
enlazadas, pilas, colas y rboles. Generalmente las variables contienen valores especicos. Los punteros son variables
pero en vez de contener un valor especico, contienen las direcciones de las variables a las que apuntan. Para obtener o
modicar el valor de la variable a la que apuntan se utiliza el operador de indireccin. Los punteros, al ser variables deben
ser declaradas como punteros antes de ser utilizadas.
17.1.1 Sintaxis
ptrID es un puntero a int, mientras que la variable ID es solo una variable del tipo int. Todo puntero debe ser precedido
por un asterisco (*) en la declaracin.
Se puede declarar ms de un puntero en la misma sentencia. En el ejemplo que sigue se ve la declaracin de dos punteros
a int.
int *ptrY, *ptrX;
17.1.2 Operadores
Existen dos operadores a tener en cuenta cuando trabajamos con punteros. El operador de direccin (&) que devuelve
la direccin de memoria de su operando y el operador de indireccin (*) que devuelve un alias para el objeto al cual
apunta el operando del puntero.
En el siguiente ejemplo vemos como se inicializa una variable X con el valor 15. Luego se crea un puntero a int y por
ltimo el puntero pasa a apuntar a la variable X. Esto es, ptrX es un puntero a X.
int X = 15; int *ptrX; ptrX = &X;
82
17.1. PUNTEROS 83
Los vectores son punteros constantes. Un vector sin subindice es un puntero al primer elemento del vector. Una matriz
es un vector de vectores. (Ej: int M[3][3];) de manera que en cada elemento del primer vector se cuelga otro vector,
pudiendo hacer as referencia a las y columnas.
int X[15]; int *ptrX; ptrX = X; // ptrX recibe la direccin del primer elemento ( 0 ) de X
Se pueden utilizar distintos elementos del vector teniendo en cuenta la sintaxis de punteros.
int X[15], Y, *ptrX; ptrX = X; Y = *( ptrX + 7 );
En este caso puede verse que Y toma el valor del elemento 7 del vector X, siendo 7 el desplazamiento dentro del vector. El
operador de indireccin queda fuera del parntesis porque tiene una prioridad superior a la del operador +. De no existir
los parntesis, se sumaria 7 al elemento X[0]. Teniendo en cuenta que los vectoresson punteros constantes, el nombre del
vector puede tratarse como un puntero:
Y = *( X + 7 );
Al usar punteros a matrices, hay que tener en cuenta que la aritmtica cambia sensiblemente.
#include <iostream> using std::cout; using std::endl; void main() { int X[6] = { 1, 2, 3, 4, 5, 6 }; int *prtX; prtX = X;
// incializo el valor del puntero. cout << endl << *prtX; prtX += 2; cout << endl << *prtX; prtX -= 2; cout << endl <<
*prtX; prtX++; cout << endl << *prtX; } atte: Oscar Torres & David Paz
En el siguiente cdigo, primero se crea un puntero a un arreglo de 6 elementos y si inicializa el puntero prtX al primer
elemento del arreglo X[0]. Si tenemos en cuenta que el siguiente ejemplo se ejecuta en una computadora con enteros de 4
bytes, el segundo elemento de la matriz tendr en memoria un desplazamiento de 4 bytes, el 2 de ocho y asi sucesivamente.
La operacin prtX += 2; produce un desplazamiento llevndolo al 3 elemento dentro del arreglo. Debe entenderse que prtX
ahora apunta a una direccin de memoria y la instruccin cambia esta direccin de memoria sumndole 2 multiplicado
por el tamao del tipo de dato del arreglo que en este supuesto sera de 4. (dir = ( dir + 2 * 4 )), dando por resultado un
desplazamiento de 8 bytes. Sera igual que ejecutar la operacin prtX = &X[2];. La operacin prtX -= 2 obedece a la misma
lgica estableciendo el puntero al primer elemento del array X[0] y el operador ++ modica el puntero desplazndolo 4
bytes y asignndole el segundo elemento de la matriz.
Para realizar una estructura de datos dinmica, se puede utilizar una matriz donde sus elementos sean punteros. Supo-
niendo que queramos hacer un calendario y lo dividamos por semanas. Podramos utilizar una matriz con los das de la
semana.
const char *dias[7] = { Domingo, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado }
Cada da de la semana, no es un elemento de la matriz, sino que la expresin dias[7] crea una matriz de siete elementos
como punteros a char.
84 CAPTULO 17. PUNTEROS
Teniendo en cuenta que el nombre de una funcin es en realidad la direccin de memoria donde comienza el cdigo, los
punteros a funciones contienen la direccin de memoria de la funcin a la que apunta, y se pueden pasar y retornar entre
funciones.
#include <iostream> using namespace std; bool functionA( int, int, bool (*)( int ) ); //Prototipo bool functionB( int );
//Prototipo void main() { int x = 113, y = 226; if ( functionA( x, y, functionB ) ) cout << "\nEl resultado es verdadero";
else cout << "\nEl resultado es falso"; } bool functionA( int param1, int param2, bool (*vericar)( int ) ) { if ( ( (*veri-
car)( param1 ) ) && ( (*vericar)( param2 ) ) ) return true; } bool functionB( int param ) { if ( param > 100 ) return
true; else return false; }
En el ejemplo anterior podr ver que en la denicin de los prototipos, la funcin functionA recibe tres parmetros,
siendo el tercer parmetro un puntero a la funcion functionB, practicamente copiando el prototipo sin el nombre. Cuando
se ejecuta functionA, se le pasa como parametro el nombre de la funcin
cout << "\nElementos a ordenar\n"; for ( int j = 0; j < nSize; j++ ) cout << setw( 5 ) << a[ j ]; orden( a, nSize ); // ordena
el arreglo cout << "\nElementos ordenados\n"; for ( int j = 0; j < nSize; j++ ) cout << setw( 5 ) << a[ j ]; cout << endl;
return 0; // indica terminacin exitosa } void orden( int *matriz, const int nSize ) { for ( int pasada = 0; pasada < nSize - 1;
pasada++ ) { for ( int k = 0; k < nSize - 1; k++ ) { if ( matriz[ k ] > matriz[ k + 1 ] ) { swap( &matriz[ k ], &matriz[ k + 1 ]
); } } } } void swap( int * const ptrElemento1, int * const ptrElemento2 ) { int mantiene = *ptrElemento1; *ptrElemento1
= *ptrElemento2; *ptrElemento2 = mantiene; }
Estructuras II
Editores:
Oscar E. Palacios
87
Captulo 19
Introduccin
Algunos autores comienzan por denir los conceptos de estructura de datos a raiz de estructuras conocidas como listas.
En el mismo contexto, suele suceder que a dichas listas tambin se les conoce como secuencias y/o colecciones de datos.
Hay que decir que dichos autores estn (en parte) en lo correcto, ya que una lista (de cualquier tipo) es una estructura
ideada con el propsito de albergar datos agrupados bajo un mismo nombre. Al respecto, podemos pensar que las listas son
como arreglos de datos, es decir, para hacer una introduccin al manejo y programacin de listas encadenadas podemos
tomar como punto de partida a los arreglos estticos. Es as como en esta secccin se descubrir la forma de operacin
de tres tipos comnes de listas conocidas como: PILAS, COLAS Y DOBLE COLA (STACK, QUEUE, DQUEUE).
En programacin, el uso de listas es una prctica tan extendida que lenguajes tales como (por ejemplo) Java, Python y
C++ soportan los mecanismos necesarios para trabajar con estructuras de: Vectores, Pilas, Colas, Listas, etc. En C++,
los programadores que usen Dev-Cpp ( Bloodshed.software -Dev-C++ ) pueden aprovechar las ventajas que ofrecen las
STL (Standard Templates Libraries) dentro de la cual se pueden encontrar plantillas para la manipulacin de listas tales
como: Vectores, Listas, Sets, Maps, etc. Por otro lado, los usuarios de Borland C++ ( Turbo C++ version 1.01 ) pueden
hacer uso de la CLASSLIB, misma que posee las librerias para los propsitos mencionados.
Nota: En las siguientes secciones se presentarn seis programas, tres para simular listas basadas en arreglos estticos y tres
para simular listas por medio de enlaces dinmicos (punteros). En cuanto al material incluido se debe hacer las siguientes
declaraciones:
1. Puesto que el material es puramente didctico, cada programa se escribe en un mismo archivo. La idea es no perder
de vista el objetivo. Los entendidos sabrn que normalmente se deben escribir archivos de cabecera, archivos de
implementacion y archivos de prueba por separado.
2. Para cada una de las clases creadas en los programas se han elegido nombres en ingles. La idea es que gran parte de
la documentacin e implementacin referente a listas est en dicho idioma, as, se le da al estudiante la idea bsica
de como operar con las libreras soportadas por los compiladores Dev-Cpp, Borlan C++, y otros.
3. Igual, se debe observar que los mtodos de las clases tienen nombres en ingles, y que con el objetivo de establecer
cierta estandarizacin todas las clases poseen los mismos mtodos, aunque cada una de ellas implementa los mismos
a su manera.
88
19.1. PILAS O STACKS 89
En el programa que se ver en seguida, se simula el comportamiento de una estructura de pila. Aunque en el mismo se
usa un arreglo esttico de tamao jo se debe mencionar que normalmente las implementaciones hechas por fabricantes
y/o terceras personas se basan en listas dinmicas o enlazadas.
Para la implementacin de la clase Stack se han elegido los mtodos:
put(), poner un elemento en la pila get(), retirar un elemento de la pila empty(), regresa 1 (TRUE) si la pila esta vacia
size(), nmero de elementos en la pila El atributo SP de la clase Stack es el puntero de lectura/escritura, es decir, el SP
indica la posicin dentro de la pila en donde la funcin put() insertar el siguiente dato, y la posicin dentro de la pila de
donde la funcin get() leer el siguiente dato. Cada vez que put() inserta un elemento el SP se decrementa. Cada vez que
get() retira un elemento el SP se incrementa.
En el siguente ejemplo se analiza lo que sucede con el SP (puntero de pila) cuando se guardan en la pila uno por uno los
caracteres 'A', 'B', 'C' y 'D'. Observe que al principio el SP es igual al tamao de la pila.
Llenando la pila.
SP | +---+---+---+---+---+ | | | | | | al principio (lista vacia) +---+---+---+---+---+ SP | +---+---+---+---+---+ push('A'); | | |
| | A | despus de haber agregado el primer elemento +---+---+---+---+---+
...
SP | +---+---+---+---+---+ | | D | C | B | A | despus de haber agregado cuatro elementos +---+---+---+---+---+
Vaciando la pila.
SP | +---+---+---+---+---+ pop(); | | D | C | B | A | despus de haber retirado un elemento +---+---+---+---+---+
...
SP | +---+---+---+---+---+ | | D | C | B | A | despus de haber retirado todos los elementos +---+---+---+---+---+ Nota:
observe que al nal la lista est vacia, y que dicho estado se debe a que el puntero est al nal de la pila y no al hecho de
borrar fsicamente cada elemento de la pila.
Ejemplo: Pila basada en un arreglo esttico
#include <iostream> using namespace std; #dene STACK_SIZE 256 /* capacidad mxima */ typedef char arreglo[STACK_SIZE];
class Stack { int sp; /* puntero de lectura/escritura */ int items; /* nmero de elementos en lista */ int itemsize; /* tamao
del elemento */ arreglo pila; /* el arreglo */ public: // constructor Stack() { sp = STACK_SIZE-1; items = 0; itemsize =
1; } // destructor ~Stack() {}; /* regresa el nmero de elementos en lista */ int size() { return items; } /* regresa 1 si no
hay elementos en la lista, o sea, si la lista est vacia */ int empty() { return items == 0; } /* insertar elemento a la lista
*/ int put(char d) { if ( sp >= 0) { pila[sp] = d; sp --; items ++; } return d; } /* retirar elemento de la lista */ int get() {
if ( ! empty() ) { sp ++; items --; } return pila[sp]; } }; // n de clase Stack // probando la pila. // Nota: obseve cmo los
elementos se ingresan en orden desde la A hasta la Z, // y como los mismos se recupern en orden inverso. int main() {
int d; Stack s; // s es un objeto (instancia) de la clase Stack // llenando la pila for (d='A'; d<='Z'; d++) s.put(d); cout <<
Items =" << s.size() << endl; // vaciando la pila while ( s.size() ) cout << (char)s.get() << " "; cout << "\nPara terminar
oprima <Enter>..."; cin.get(); return 0; } }
En el siguiente programa se presenta una implementacin de una estructura dinmica tipo pila o stack. Es importante
hacer notar que, a diferencia de una pila basada en un arreglo esttico, una pila enlazadada dinmicamente no posee de
forma natural el mecanismo de acceso por ndices, en ese sentido, el programador puede crear los algoritmos necesarios
para permitir tal comportamiento. En la clase que presentaremos en el ejemplo no se ha implementado el mecanismo de
acceso por ndices, ya que la misma se presenta como una alternativa para la simulacin de una pila o stack.
Uno de los puntos ms destacables en cuando al uso de listas enlazadas dinmicamente es el hecho de crear estructuras
conocidas como nodos. Un nodo es una especie de eslabn ( similar al de una cadena de bicicleta ), es decir, cada nodo
90 CAPTULO 19. INTRODUCCIN
se enlaza con otro a travs de un puntero que apunta a una estructura del mismo tipo que el nodo. Por ejemplo, para crear
una estructura de nodo para almacenar enteros y a la vez para apuntar a otro posible nodo podemos emplear la sintaxis:
struct nodo { int data; nodo *siguiente; };
observe que con la declaracin anterior estamos creando el tipo estructurado nodo, mismo que posee a los miembros: data
para guardar valores enteros, y siguiente para apuntar o enlazar a un supuesto siguiente nodo.
Ya que las listas dinmicas inicialmente se encuentran vacias, y ms an, una lista dinmica no posee una direccin esta-
blecida en tiempo de compilacin ya que las direccin de memoria que ocupar cada uno de los elementos se establecer
en tiempo de ejecucin, entonces cmo determinar la condicin de vacio ?. En nuestro ejemplo usaremos un contador
( ITEMS ) que dicho sea de paso, si ITEMS = 0, entonces la lista est vacia. ( la condicin de vacio tambin podra
determinarse al vericar el SP, es decir, si el SP = NULL, signica que la lista no posee elementos ).
Al hacer un anlisis previo de los eventos que acontecern en la pila y su puntero de lectura y escritura (SP, que en esta
ocasin es una estructura tipo nodo), se tiene lo siguiente:
1) Al principio la lista est vacia, en ese caso el SP es igual a NULL y, en consecuencia, el puntero next
tambin es NULL.
SP = asignado 2 1 +------+------+ +------+------+ | data | next |--> | data | next |--> NULL +------+------+ +------+------+
Ejemplo: Pila basada en un arreglo dinmico
/*---------------------------------------------------------------+ + ejemplo de una pila ( STACK ) enlazada dinmicamente +
+ + + Autor: Oscar E. Palacios + + email: oscarpalacios1@yahoo.com.mx + + + + Maniesto: + + Este programa pue-
de distribuirse, copiarse y modicarse de + + forma libre. + +---------------------------------------------------------------*/
#include <iostream> //#include <conio.h> using namespace std; /* tipo de dato que contendr la lista */ typedef char
DATA_TYPE; // declaracin de estructura nodo struct nodo { DATA_TYPE data; nodo *next; }; class StackDin { // atri-
butos int ITEMS; /* nmero de elementos en la lista */ int ITEMSIZE; /* tamao de cada elemento */ nodo *SP; /* puntero
de lectura/escritura */ public: // constructor StackDin() : SP(NULL), ITEMS(0), ITEMSIZE(sizeof(DATA_TYPE)) {}
// destructor ~StackDin() {} /* agregar componente a la lista */ DATA_TYPE put(DATA_TYPE valor) { nodo *temp;
temp = new nodo; if (temp == NULL) return 1; temp->data = valor; temp->next = SP; SP = temp; ITEMS ++; re-
turn valor; } int empty() { return ITEMS == 0; } /* retirar elemento de la lista */ DATA_TYPE get() { nodo *temp;
DATA_TYPE d; if ( empty() ) return 1; d = SP->data; temp = SP->next; if (SP) delete SP; SP = temp; ITEMS --;
return d; } }; // n de la clase StackDin /* punto de prueba para la clase StackDin */ int main() { //clrscr(); StackDin s;
DATA_TYPE d; for (d='A'; d<='Z'; d++) s.put(d); while ( ! s.empty() ) cout << (DATA_TYPE)s.get() << " "; cout <<
"\nPara terminar presione <Enter>..."; cin.get(); return 0; }
Cuando en el teclado se oprime una tecla, el cdigo del carcter ingresado es trasladado y depositado en una rea de
memoria intermedia conocida como el buer del teclado, para esto el microprocedador llama a una rutina especca.
Luego, para leer el carcter depositado en el buer existe otra funcin, es decir, hay una rutina para escribir y otra para
leer los caracteres del buer cada una de las cuales posee un puntero; uno para saber en donde dentro del buer se escribir
el siguiente cdigo y otro para saber de donde dentro del buer se leer el siguiente cdigo.
En el programa que se ve en seguida, se simula el comportamiento de una estructura de cola simple. Aunque en el mismo se
usa un arreglo esttico de tamaoo jo se debe mencionar que normalmente las implementaciones hechas por fabricantes
y/o terceras personas se basan en listas dinmicas o dinamicamente enlazadas.
Para la implementacin de la clase Queue se han elegido los mtodos:
put(), poner un elemento en la cola get(), retirar un elemento de la cola empty(), regresa 1 (TRUE) si la cola est vacia
size(), nmero de elementos en la cola El atributo cabeza de la clase Queue es el puntero de lectura. El atributo cola de
la clase Queue es el puntero de escritura.
Es decir, la cola indica la posicin dentro de la lista en donde la funcin put() insertar el siguiente dato, y la cabeza indica
la posicin dentro de la lista de donde la funcin get() leer el siguiente dato.
Cada vez que put() inserta un elemento la cola se incrementa. Cada vez que get() retira un elemento la cabeza se incre-
menta.
En el siguente ejemplo se analiza lo que sucede con la cola y la cabeza (punteros de escritura y de lectura de la Lista)
cuando se guardan en la cola uno por uno los caracteres 'A', 'B', 'C' y 'D'. Observe que al principio: cola = cabeza = cero.
Llenando la cola.
cola | +---+---+---+---+---+ | | | | | | al principio +---+---+---+---+---+ | cabeza cola | +---+---+---+---+---+ put('A'); | A | |
| | | despus de haber agregado el primer elemento +---+---+---+---+---+ | cabeza
...
cola | +---+---+---+---+---+ | A | B | C | D | | despus de haber agregado cuatro elementos +---+---+---+---+---+ | cabeza
Vaciando la cola.
cabeza | +---+---+---+---+---+ | A | B | C | D | | antes de haber retirado elementos +---+---+---+---+---+ cabeza | +---+---
+---+---+---+ get(); | A | B | C | D | | despus de haber retirado un elemento +---+---+---+---+---+
...
cabeza | +---+---+---+---+---+ al nal | A | B | C | D | | despus de haber retirado todos los elementos +---+---+---+---+---+
| cola
Observese que al nal el cabeza apunta hacia el mismo elemento que la cola, es decir, la cola vuelve a estar vacia. Puesto
que la cola que estamos proyectando reside en un arreglo esttico los componentes del arreglo an estn dentro de la
misma, salvo que para su recuperacin se debera escribir otro mtodo. En una cola dinmica (como se demostrar ms
adelante) los elementos retirados de la misma se eliminan de la memoria y podra no ser posible su recuperacin posterior.
Nota: En el programa que aparece en seguida, al tipo de lista implementado por la clase Queue se le conoce como lista
circular debido al comportamiento de sus punteros. Es decir si los mtodos para escribir o leer detectan que el puntero
correspondiente ha sobrepasado el tamao mximo de elementos permitidos dentro de la cola, ste es puesto a cero.
Ejemplo: cola en un arreglo esttico
/*---------------------------------------------------------------+ + ejemplo de una cola (QUEUE) basada en un arreglo esttico
+ + + + Autor: Oscar E. Palacios + + email: oscarpalacios1@yahoo.com.mx + + + + Maniesto: + + Este programa
puede distribuirse, copiarse y modicarse de + + forma libre. + +---------------------------------------------------------------
*/ #include <iostream.h> #dene MAX_SIZE 256 /* capacidad mxima */ typedef char almacen[MAX_SIZE]; class
Queue { int cabeza; /* puntero de lectura */ int cola; /* puntero de escritura */ int ITEMS; /* nmero de elementos en
la lista */ int ITEMSIZE; /* tamao de cada elemento */ almacen alma; /* el almacen */ public: // constructor Queue()
92 CAPTULO 19. INTRODUCCIN
{ cabeza = 0; cola = 0; ITEMS = 0; ITEMSIZE = 1; } // destructor ~Queue() {} // regresa 1 (true) si la lista est vacia
int empty() { return ITEMS == 0; } // insertar elemento a la lista int put(int d) { if ( ITEMS == MAX_SIZE) return
1; if ( cola >= MAX_SIZE) { cola = 0; } alma[cola] = d; cola ++; ITEMS ++; return d; } // retirar elemento de la
lista int get() { char d; if ( empty() ) return 1; if ( cabeza >= MAX_SIZE ) { cabeza = 0; } d = alma[cabeza]; cabe-
za ++; ITEMS --; return d; } // regresa el nmero de elementos en lista int size() { return ITEMS; } }; // n de la clase
Queue // probando la cola int main() { int d; Queue q; for (d='A'; d<='Z'; d++) q.put(d); cout << Items = " << q.size() <<
endl; while ( q.size() ) { cout << (char)q.get() << " "; } cout << "\nPara terminar oprima <Enter> ..."; cin .get(); return 0; }
return items; } /* agregar componente en la parte tracera de la lista */ DATA_TYPE put_back(DATA_TYPE valor) { if
(items == MAX_SIZE) return t_error; alma[cola] = valor; items ++; cola ++; return valor; } /* agregar componente en
la parte delantera de la lista */ DATA_TYPE put_front(DATA_TYPE valor) { if (items == MAX_SIZE) return t_error;
memmove((void *)&alma[cabeza+1], (void*)&alma[cabeza], items*itemsize); alma[cabeza] = valor; items ++; cola ++;
return valor; } /* retirar elemento de la parte frontal de la lista */ DATA_TYPE get_front() { DATA_TYPE d; if ( em-
pty() ) return t_error; items --; cola --; d = alma[cabeza]; memmove((void*)&alma[cabeza], (void*)&alma[cabeza+1],
items*itemsize); return d; } /* retirar elemento de la parte tracera de la lista */ DATA_TYPE get_back() { DATA_TYPE
d; if ( empty() ) return t_error; items--; cola --; d = alma[cola]; return d; } }; // n de la clase SDQueue /* punto de prue-
ba */ int main() { SDQueue s; DATA_TYPE d; for (d='A'; d<='Z'; d++) s.put_back(d); while ( ! s.empty() ) cout <<
(char)s.get_front() << " "; cout << "\nPara terminar presione <Enter>..."; cin.get(); return 0; }
Una cola doblemente encadenada es una estructuras en donde cada elemento puede ser insertado y recuperado por la parte
del frente (cabeza) o por la parte de atras (cola) de la lista. A diferencia de una cola sencilla, en donde solo se necesita un
puntero a un siguiente elemento, la estructura del nodo para una doble cola debe poseer un puntero a un posible siguiente
elemento y un puntero a otro posible anterior elemento. Por ejemplo, para crear una estructura de nodo con doble enlace
para coleccionar nmeros enteros podemos usar la sintaxis:
struct nodo { int data; nodo *next, *prev; };
Grcamente podemos imaginar la estructura anterior como:
+------+------+------+ <--| prev | data | next |--> +------+------+------+
En el programa que se ver en seguida, se simula el comportamiento de una estructura de cola de doble enlace. Para la
implementacin de la clase DDqueue en el programa se han elegido los mtodos:
put_front(), poner un elemento en el frente de la cola put_back(), poner un elemento en la parte tracera de la cola
get_front(), retirar un elemento de la parte frontal de la cola get_back(), retirar un elemento de la parte tracera de la cola
empty(), regresa 1 (TRUE) si la cola est vacia size(), nmero de elementos en la cola
/*---------------------------------------------------------------+ + ejemplo de una cola doblemente enlazada (Dqueue) basada en
un + + arreglo dinmico + + + + Autor: Oscar E. Palacios + + email: oscarpalacios1@yahoo.com.mx + + + + Maniesto:
+ + Este programa puede distribuirse, copiarse y modicarse de + + forma libre. + +---------------------------------------
------------------------*/ #include <iostream.h> #include <conio.h> // using namespace std; typedef char DATA_TYPE;
struct nodo { DATA_TYPE data; nodo *next, *prev; }; class DDqueue { int itemsize, items; nodo *cola, *cabeza; pu-
blic: // constructor DDqueue() : cola(NULL), cabeza(NULL), items(0), itemsize(sizeof(DATA_TYPE)) {} // destructor
~DDqueue() {} /* agregar componente en la parte tracera de la lista */ DATA_TYPE put_back(DATA_TYPE valor) {
nodo *temp; temp = new nodo; if (temp == NULL) return 1; temp->data = valor; items ++; if (cabeza == NULL ) {
temp->next = NULL; temp->prev = NULL; cabeza = temp; cola = temp; } else { cola->next = temp; temp->prev = cola;
cola = temp; cola->next = NULL; } return valor; } /* agregar componente en la parte frontal de la lista */ DATA_TYPE
put_front(DATA_TYPE valor) { nodo *temp; temp = new nodo; if (temp == NULL) return 1; temp->data = valor;
items ++; if (cabeza == NULL ) { temp->next = NULL; temp->prev = NULL; cabeza = temp; cola = temp; } else {
cabeza->prev = temp; temp->next = cabeza; cabeza = temp; cabeza->prev = NULL; } return valor; } // regresa true si la
lista est vacia int empty() { return items == 0; } /* retirar elemento de la parte frontal lista */ DATA_TYPE get_front()
{ nodo *temp; DATA_TYPE d; if ( empty() ) return 1; items --; d = cabeza->data; temp = cabeza->next; if (cabeza)
delete cabeza; cabeza = temp; return d; } /* retirar elemento de la parte tracera de la lista */ DATA_TYPE get_back() {
nodo *temp; DATA_TYPE d; if ( empty() ) return 1; items--; d = cola->data; temp = cola->prev; if (cola) delete cola;
cola = temp; return d; } }; // n de la clase DDqueue /* punto de prueba */ int main() { clrscr(); DDqueue s; DATA_TYPE
d; // insertando elementos en la parte tracera for (d='A'; d<='Z'; d++) s.put_back(d); // insertando en la parte delantera
for (d=9; d>=0; d--)s.put_front(d+'0'); // vaciando la lista while ( ! s.empty() ) cout << (DATA_TYPE)s.get_front() <<
" "; cout << "\nPara terminar presione <Enter>..."; cin.get(); return 0; }
Captulo 20
Plantillas
Editores:
Oscar E. Palacios
94
Captulo 21
Plantillas
21.1 Introduccin
Con el objeto de explicar la razn de la necesidad de la existencia de las plantillas debemos reexionar sobre tres pa-
radgmas de programacin anteriores, estas son: programacin clsica o procedimental, programacin estructurada y
programacin orientada al objeto POO.
Programacin clsica
En el tipo de programacin conocida como clsica existe una clara diferenciacin entre los datos y su manipulacin,
es decir, entre los datos y el conjunto de algoritmos para manejarlos. Los datos son tipos muy simples y generalmente
los algoritmos se agrupan en funciones orientadas de forma muy especca a los datos que deben manejar. Por ejem-
plo, si se escribe una funcin ( sort ) para ordenar en forma ascendente o descendente los nmeros contenidos en un
arreglo de nmeros enteros, dicha funcin puede aplicarse a cualquier arreglo de enteros ms no a arreglos de otro tipo.
An as, la programacin clsica provee el soporte necesario para la reutilizacin de cdigo ya que el cdigo de la fun-
cin se escribe solamente una vez y su reutilizacin se da por medio de un mecanismo conocido como llamada de funcin.
Programacin estructurada
En la medida en que los datos que haba de manipular se iban haciendo cada vez ms complejos se busco la forma de
agruparlos de alguna manera bajo un mismo nombre, es as como surjen las estructuras de datos. Muchos autores se
reeren a la programacin estructura como a la suma de funciones y/o procedimientos ms estructura de datos.
Notas: El ocultamiento de cdigo as como la herencia estn presentes ( en una forma simple ) en la
programacin estructurada, y los mismos adquieren mucha ms relevancia en la POO. Por ejemplo, en la
programacin estructurada si usted escribe una librera de funciones, al usuario de dicha librera solamente
le informar de la existencia de tal o cual funcin, as como el objetivo y la forma correcta para comunicarse
con estas, pero no es necesario que se le explique el funcionamiento interno de las mismas. Por otro lado,
es bien conocido que lenguajes tales como Pascal y C, por ejemplo, dan el soporte para la creacin de datos
estructurados ( Record en Pascal, y struct en C ), y que dichas estructuras pueden contener a otras estructuras
previamente denidas. De tal manera vemos como a los usuarios de las funciones se les oculta el cdigo de
95
96 CAPTULO 21. PLANTILLAS
las mismas, y que una estructura que contiene a otra hereda los miembros de la estructura contenida.
Programacin genrica
La programacin genrica est mucho ms centrada en los algoritmos que en los datos, y su postulado fundamental puede
sintetizarse en una palabra: generalizacin. Signica que, en la medida de lo posible, los algoritmos deben ser parametri-
zados al mximo y expresados de la forma ms independiente posible de detalles concretos, permitiendo as que puedan
servir para la mayor variedad posible de tipos y estructuras de datos.
Con el objetivo de mostrar de una manera practica las diferencias entre los tres tipos de programacin mencionados arriba
tomaremos como base el modelo de una vieja estructura de datos amiga de los programadores, me reero a un array,
tambin conocida por muchos como arreglo, tabla o lista. Para no entrar en polemicas de estndarizacin de nombrado
en este captulo diremos que la estructura modelo con la que trabajaremos es un vector ( arreglo unidimensional ).
Pues bien, dado un vector cualquiera se presenta la necesidad de crear un cierto nmero de funciones que sean las encar-
gadas de la manipulacin de los datos dentro del vector. Para comenzar, podemos pensar en las funciones:
3. buscar, para determinar la presencia o ausencia de un determinado elemento dentro del vector.
Al hacer un anlisis muy detallado del problema planteado y al tratar de resolver el mismo mediante la programacin
clsica, veremos que, si bien es cierto que se puede llegar a la solucin, para lograrlo se tendran que establecer una serie
de medidas que nos permitiern controlar de alguna manera detalles tales como: el total de elementos soportados por el
vector y el nmero de elementos actuales contenidos por el vector. Por ejemplo, con la instruccin:
int vector[120];
se crea un arreglo con capacidad para 120 componentes de tipo entero . Ahora bien, el compilador sabe perfectamente
que deber reservar un espacio de 120 enteros para la memoria del vector, pero no se garantiza que cualquier funcin trate
de leer o escribir datos fuera de los limites del vector. Es decir, nuestro programa no podra saber el tamao del vector
a menos que usaramos algn truco, por ejemplo usar el primer elemento del vector para contener el tamao del mismo.
Otra forma de alcanzar una solucin, sera que a cada una de las funciones que tengan que ver con el vector se le pasar
los parmetros: puntero de vector y nmero de elementos en el vector, de tal manera que, por ejemplo, la funcin mostrar
podra declararse como:
int mostrar(int *vector, int cuenta);
21.2. UN PASO HACIA ADELANTE 97
Si continuamos en el mbito de la programacin clsica podemos dar un paso ms si es que nos valemos de tipos es-
tructurados ms elaborados. Por ejemplo, podemos denir una estructura llamada vector la cual posea los miembros:
capacidad, cuenta y data como se muestra en seguida:
typedef struct vector { int capacidad; int cuenta; int *data; };
De tal manera que podriamos escribir funciones que operen sobre un solo parmetro del tipo estructurado vector. Por
ejemplo, la funcin mostrar podra declararse como:
int mostrar(vector *v);
En este punto tendramos que detenernos y pensar en lo siguiente:
La estructura vector ( denida arriba con typedef ) es solamente un nuevo tipo de dato, es decir, podemos declarar tantas
copias ( variables ) de la misma como sean necesarias, pero carece de un mtodo constructor adecuado. Por ejemplo, la
declaracin:
vector nombre_var;
es vlida siempre y cuando que el tipo vector ya haya sido denido, pero la variable nombre_var contendr slo basura
hasta que no se establezcan los valores adecuados para cada uno de sus miembros ( capacidad, cuenta y data ).
Para el tipo de problemas como en el ejemplo vector y otros similares, surge la POO, misma que facilita en gran medida
la solucin del mismo, pero an queda pendiente la resolucin a otro problema, es decir, hasta aqu hemos mencionado la
estructura vector como un contenedor de nmeros enteros, pero un vector podra contener caracteres, nmeros de punto
otante y otros tipos estructurados; y los mismos algoritmos usados para ( mostrar, ordenar, buscar, etc. ) empleados en
un vector de enteros, tendran su aplicacin sobre vectores de cualquier tipo. Es as como surge lo que se conoce como
PLANTILLAS o lo que es lo mismo, la programacin genrica.
Con el objetivo de mostrar en la prctica los conceptos que hemos venido mencionando presentaremos un pequea im-
plementacin de la clase vector. Se debe aclarar que la implementacin de la misma se har para vectores contenedores
de datos tipo int solamente. Para simplicar el ejemplo, para la clase vector solamente se implementarn los mtodos
mostrar(), ordenar() e insertar(), as como un mtodo constructor base y un mtodo destructor.
#include <iostream> #include <cstdio> #include <ctime> using namespace std; #dene VECTOR_DEFAULT_SIZE 128
class vector { // atributos int capacidad; int cuenta; int *data; public: // constructor base vector() { capacidad = VEC-
TOR_DEFAULT_SIZE; cuenta = 0; data = new int[VECTOR_DEFAULT_SIZE]; } // destructor ~vector() { delete[]
data; } // despliega todos los elementos contenidos por el vector void mostrar() { for (int t = 0; t < cuenta; t++) cout
<< elemento " << t << ", valor " << data[t] << endl; } // inserta un elemento al vector int insertar(int d) { if (cuenta
== capacidad) return 1; data[cuenta] = d; cuenta ++; return cuenta; } // ordena en forma ascendente los elementos del
vector void ordenar() { int i, j, temp; int n = cuenta; i = 0; while (i < n ) { for ( j = i ; j < n-1; j++) if ( data[j] >
data[j+1] ) { temp = data[j]; data[j] = data[j+1]; data[j+1] = temp; } n --; } } }; #dene MAX 10 int main() { vector
v; srand( time(NULL) ); for (int r = 0; r < MAX; r++) v.insertar( rand() % 100); cout << "\nvector v sin ordenar\n";
v.mostrar(); v.ordenar(); cout << "\nvector v ordenado\n"; v.mostrar(); getchar(); return 0; }
98 CAPTULO 21. PLANTILLAS
Excepciones
99
Captulo 23
Primeramente, antes de entrar en el tema de las excepciones en programacin, se ha de matizar en el concepto de qu son
las excepciones, vistas desde un punto de vista fuera y dentro del mundo de la programacin.
En el lenguaje humano, una excepcin es un elemento excluyente de una regla, y de forma convencional se ha extendido
esta denicin. En el lenguaje mquina, una excepcin se trata, de forma general, de algo que no se espera que ocurra,
pero que puede ocurrir, similar al tratamiento de errores, pero de los errores en tiempo de ejecucin.
A veces estas excepciones, para una mquina, no son casos que no deberan estar contemplados, tal y como un programador
se lo asigna, sino que pueden ser indicadores para comprobar que realmente todo est marchando bien o no.
En los programas de ordenador hechos en C existi durante mucho tiempo la costumbre de usar el comando goto
(tambin implementada en C++), pero ste se ha ido eliminando progresivamente de casi todos y cada uno de los cdigos
y programas que han ido surgiendo. El signicado de la funcin 'goto' no forma parte del libro actual, pero se pueden
buscar referencias por internet donde se especique con ms detalle qu es.
Como una de las formas de control de errores ms usuales era con goto, se usaron otras variantes, como las aserciones de
cdigo (assertions, en ingls) o, con la llegada de la programacin orientada a objetos, de los comandos try, catch y throw.
100
Captulo 24
Por norma general, los comandos try y catch conforman bloques de cdigo.
Cada uno de estos bloques se recomienda, aunque sea de una nica lnea, envolverlos en llaves, como muestra el siguiente
ejemplo:
// ...cdigo previo... try { // bloque de cdigo a comprobar } catch( tipo ) // Ha ocurrido un suceso en el try que se ha
terminado //la ejecucin del bloque y catch recoge y analiza lo sucedido { // bloque de cdigo que analiza lo que ha
lanzado el try } // ...cdigo posterior...
Generalmente entre el try y el catch no se suele insertar cdigo, pero se insta al lector a que lo intente con su compilador
habitual y que compruebe si hay errores o no de compilacin.
101
Captulo 25
Control de excepciones
Una excepcin es un error que puede ocurrir debido a una mala entrada por parte del usuario, un mal funcionamiento en
el hardware, un argumento invlido para un clculo matemtico, etc. Para remediar esto, el programador debe estar atento
y escribir los algoritmos necesarios para evitar a toda costa que un error de excepcin pueda hacer que el programa se
interrumpa de manera inesperada. C++ soporta una forma ms directa y fcil de ver tanto para el programador como para
los revisores del cdigo en el manejo de excepciones que su smil en el C estndar y esta consiste, tratndose del lenguaje
C++, en el mecanismo try, throw y catch.
La lgica del mecanismo mencionado consiste en:
1. Dentro de un bloque try se pretende evaluar una o ms expresiones y si dentro de dicho bloque se produce un algo
que no se espera se lanza por medio de throw una excepcin, la misma que deber ser capturada por un catch
especco.
2. Puesto que desde un bloque try pueden ser lanzados diferentes tipos de errores de excepcin es que puede haber
ms de un catch para capturar a cada uno de los mismos.
3. Si desde un try se lanza una excepcin y no existe el mecanismo catch para tratar dicha excepcin el programa
se interumpir abruptamente despues de haber pasado por todos los catchs que se hayan denido y de no haber
encontrado el adecuado.
4. Los tipos de excepciones lazados pueden ser de un tipo primitivo tal como: int, oat, char, etc. aunque normal-
mente las exepciones son lanzadas por alguna clase escrita por el usuario o por una clase de las que vienen incluidas
con el compilador.
En el programa que se listar a continuacin muestra un ejemplo de como lanzar una excepcin de tipo int dentro del
bloque try, y cmo capturar la excepcin por medio de catch.
Ejemplo
// Demostracin de los comandos try, throw y catch #include <iostream> // Funcin: main // Recibe: void // Devuelve:
int // En la funcin principal se tratarn los comandos try, throw y catch int main(void) { try // Se intenta hacer el si-
guiente cdigo { // Aqu puede ir ms cdigo... throw 125; //...aunque directamente en este caso se lanza una excepcin.
} catch(int) // Se captura con un catch de enteros (podra usarse long o char, por ejemplo) { std::cout << Ha surgido
una excepcin de tipo entero << std::endl; // y se muestra por pantalla } std::cin.get(); // Y el programa naliza. return 0; }
102
25.1. EXCEPCIONES GENRICAS 103
// Compilado y probado exitosamente con Dev-C++ // Demostracin: excepcin de la clase string #include <iostream>
#include <string> using namespace std; int main() { string s = Hola"; try { cout << s.at(100) << endl; } catch(exception&
e) { cout << e.what() << endl; } cin.get(); return 0; }
En el programa anterior el mtodo at de la clase string lanzar ( throw-up ) un error de excepcin, ya que la instruccin
s.at(100) trata de acceder a un elemento fuera de los limites del objeto ( s ).
Siguiendo la misma metodologa mostrada por el programa anterior, usted puede crear clases independientes para cap-
turar errores de excepciones especcas. Por ejemplo, si se desea crear una serie de funciones matemticas se deben
considerar los sucesos de errores tales como: Divisin por cero, Error de dominio, Error de rango, etc. As, el siguiente
programa puede ser un buen ejemplo para que usted escriba sus propios controladores de mensajes de error. Observe
cmo en el programa se crea la clase ErrorMat y dentro de la misma la funcin porque() la cual se encargar de des-
104 CAPTULO 25. CONTROL DE EXCEPCIONES
plegar el mensaje de error. Aunque ErrorMat solo ha sido pensada para tratar los posibles errores de rango y errores de
dominio, la misma puede rescribirse para capturar todos los errores posibles que puedan resultar a raiz de operaciones
matemticas.
Nota: No deje de observar tambin, cmo la funcin independiente logaritmo() verica si el parmetro pasado a la
misma es 0 o menor que 0 y en tales circunstancias se lanzara (throw) un error de excepcin del tipo ErrorMat ya que
el dominio para la funcin log() es el de los nmeros positivos y el logaritmo de cero no est denido en los nmeros reales.
// Demostracin: try, throw y catch #include <iostream> #include <cmath> using namespace std; static const int EDO-
MINIO=100; static const int ERANGO=101; class ErrorMat { public: ErrorMat() : motivo(0) {}; ErrorMat(int m) :
motivo(m) {}; const char* porque() const throw(); private: int motivo; }; const char* ErrorMat::porque() const throw() {
switch (motivo) { case EDOMINIO: return Error de Dominio ";break; case ERANGO: return Error de Rango ";break;
default: return Error Desconocido"; //En rigor no debera ocurrir } } double logaritmo(const double n) { try { if (n < 0)
throw(ErrorMat(EDOMINIO) ); if (n == 0) throw(ErrorMat(ERANGO) ); return log(n); } catch(ErrorMat& e) { cout
<< e.porque(); } return 0; } int main() { double r = 100; cout << log(" << r << ") = " << logaritmo(r) << endl; cout <<
log(-" << r << ") = " << logaritmo(-r) << endl; cin.get(); return 0; }
Captulo 26
Editores:
Oscar E. Palacios
1. C++ Vectors
2. C++ Lists
3. C++ Double-Ended Queues
1. C++ Stacks
2. C++ Queues
3. C++ Priority Queues
1. C++ Bitsets
2. C++ Maps
3. C++ Multimaps
4. C++ Sets
5. C++ Multisets
105
106 CAPTULO 26. LIBRERA ESTNDAR DE PLANTILLAS
La idea detras de la STL de C++ es que la parte dicil en el uso de estructuras complejas de datos ya ha sido previamente
completada. Por ejemplo, si un programador desea usar un stack de enteros, todo lo que tiene que hacer es escribir el
cdigo:
stack<int> myStack;
Con un minimo de esfuerzo, l o ella puede usar la funcin push() para ingresar enteros al stack; y la funcin pop() para
retirar enteros del stack. A travez de la magia de las plantillas de C++, se puede especicar cualquier tipo de dato, no slo
enteros. La clase Stack de la STL provee la funcionalidad genrica de un stack, sin importar el tipo de dato en el stack.
26.2 Iteradores
El termino iterar signica (en palabras sencillas) el mtodo o forma que se usa para poder navegar sobre los elementos de
una lista especca. Luego, un iterador es como una especie de puntero especial que nos permite leer o escribir valores
sobre cada uno de los elementos en una lista. Los iteradores pueden ser comparados con los ndices que se emplean para
leer o escribir valores sobre los elementos de una lista. Igual a lo que sucede con los tipos de datos dentro de un arreglo
primitivo en donde para leer o escribir valores sobre los elementos se tiene que prestar atencin al tipo de dato, un iterador
tiene que ser del tipo de dato que posee la lista. En ese sentido, si tenemos por ejemplo un vector que posee datos de tipo
entero (int) entonces el iterador tendr que ser de tipo entero; si por el contrario se tiene un vector cuyos datos son del
tipo cadena (string) entonces el iterador tiene que ser del tipo cadena.
Sintaxis
La sintaxis general para la declaracin de un iterador es:
NombrePlantilla<tipo>::iterator varid;
Donde,
Por ejemplo, para obtener un iterador a un vector de tipo int podemos usar la instruccion:
vector <int>::iterator el_iterador;
A manera de ejemplo, vamos a mostrar un programa en el que se usar un iterador de tipo char.
Nota: en el programa se hace uso de los mtodos begin para obtener un iterador hacia el inicio
del vector, y end para obtener un iterador hacia el nal del mismo.
// Demostracion del uso de iteradores // probado en: Dev-C++ 4.9.9.2 #include <cstdlib> #include <iostream> #inclu-
de <vector> using namespace std; int main(int argc, char *argv[]) { vector<char> v; for (int x = 'A'; x <= 'Z'; x++)
v.push_back(x); // obtenemos un iterator del tipo char vector<char>::iterator it; // lectura y despliegue de datos cout <<
"\ndesplegando datos << endl; for( it = v.begin(); it != v.end(); it++ ) cout << *it << endl; system(PAUSE); return
EXIT_SUCCESS; }
Se debe de observar que para obtener iteradores normales se usan (generalmente) los mtodos begin() y end(),
mientras que que para obtener iteradores reversos se emplean los mtodos rbegin() y rend().
26.3. ITERADORES REVERSOS 107
Los iteradores reversos se comportan de manera inversa. Por ejemplo, para obtener un iterador que vaya desde
el nal hasta el inicio de los elementos, se usa la funcin rbegin() la cual regresar un iterador para procesar la
lista desde el ltimo hasta el primero de los elementos. En contraparte, la funcin begin() regresa un iterador para
procesar la lista desde el primero hasta el ltimo de los elementos. Es decir, begin() regresa una referencia hacia el
primer elemento, mientras que rbegin() regresa una referencia hacia el ltimo elemento.
// Demostracion del uso de iteradores reversos // probado en: Dev-C++ 4.9.9.2 #include <cstdlib> #include <iostream>
#include <list> using namespace std; int test() { list<char> v; for (int x = 'A'; x <= 'Z'; x++) v.push_back(x); cout << endl;
cout << orden original << endl; // creamos un iterator normal list<char>::iterator i = v.begin(); while(i != v.end() ) { cout
<< *i++ << " "; } cout << endl; cout << orden inverso << endl; // creamos un iterator reverso list<char>::reverse_iterator
ri = v.rbegin(); while(ri != v.rend() ) { cout << *ri++ << " "; } cout << endl; return 0; } int main(int argc, char *argv[])
{ test(); system(PAUSE); return EXIT_SUCCESS; }
Captulo 27
Editores:
Oscar E. Palacios
1. C++ Vectors
2. C++ Lists
3. C++ Double-Ended Queues
1. C++ Stacks
2. C++ Queues
3. C++ Priority Queues
1. C++ Bitsets
2. C++ Maps
3. C++ Multimaps
4. C++ Sets
5. C++ Multisets
108
27.2. ITERADORES 109
La idea detras de la STL de C++ es que la parte dicil en el uso de estructuras complejas de datos ya ha sido previamente
completada. Por ejemplo, si un programador desea usar un stack de enteros, todo lo que tiene que hacer es escribir el
cdigo:
stack<int> myStack;
Con un minimo de esfuerzo, l o ella puede usar la funcin push() para ingresar enteros al stack; y la funcin pop() para
retirar enteros del stack. A travs de la magia de las plantillas de C++, se puede especicar cualquier tipo de dato, no slo
enteros. La clase Stack de la STL provee la funcionalidad genrica de un stack, sin importar el tipo de dato en el stack.
27.2 Iteradores
El termino iterar signica (en palabras sencillas) el mtodo o forma que se usa para poder navegar sobre los elementos de
una lista especca. Luego, un iterador es como una especie de puntero especial que nos permite leer o escribir valores
sobre cada uno de los elementos en una lista. Los iteradores pueden ser comparados con los ndices que se emplean para
leer o escribir valores sobre los elementos de una lista. Igual a lo que sucede con los tipos de datos dentro de un arreglo
primitivo en donde para leer o escribir valores sobre los elementos se tiene que prestar atencin al tipo de dato, un iterador
tiene que ser del tipo de dato que posee la lista. En ese sentido, si tenemos por ejemplo un vector que posee datos de tipo
entero (int) entonces el iterador tendr que ser de tipo entero; si por el contrario se tiene un vector cuyos datos son del
tipo cadena (string) entonces el iterador tiene que ser del tipo cadena.
Sintaxis
La sintaxis general para la declaracin de un iterador es:
NombrePlantilla<tipo>::iterator varid;
Donde,
Por ejemplo, para obtener un iterador a un vector de tipo int podemos usar la instruccion:
vector <int>::iterator el_iterador;
A manera de ejemplo, vamos a mostrar un programa en el que se usar un iterador de tipo char.
Nota: en el programa se hace uso de los mtodos begin para obtener un iterador hacia el inicio
del vector, y end para obtener un iterador hacia el nal del mismo.
// Demostracion del uso de iteradores // probado en: Dev-C++ 4.9.9.2 #include <cstdlib> #include <iostream> #inclu-
de <vector> using namespace std; int main(int argc, char *argv[]) { vector<char> v; for (int x = 'A'; x <= 'Z'; x++)
v.push_back(x); // obtenemos un iterator del tipo char vector<char>::iterator it; // lectura y despliegue de datos cout <<
"\ndesplegando datos << endl; for( it = v.begin(); it != v.end(); it++ ) cout << *it << endl; system(PAUSE); return
EXIT_SUCCESS; }
Se debe de observar que para obtener iteradores normales se usan (generalmente) los mtodos begin() y end(),
mientras que que para obtener iteradores reversos se emplean los mtodos rbegin() y rend().
110 CAPTULO 27. BIBLIOTECA ESTNDAR DE PLANTILLAS
Los iteradores reversos se comportan de manera inversa. Por ejemplo, para obtener un iterador que vaya desde
el nal hasta el inicio de los elementos, se usa la funcin rbegin() la cual regresar un iterador para procesar la
lista desde el ltimo hasta el primero de los elementos. En contraparte, la funcin begin() regresa un iterador para
procesar la lista desde el primero hasta el ltimo de los elementos. Es decir, begin() regresa una referencia hacia el
primer elemento, mientras que rbegin() regresa una referencia hacia el ltimo elemento.
// Demostracion del uso de iteradores reversos // probado en: Dev-C++ 4.9.9.2 #include <cstdlib> #include <iostream>
#include <list> using namespace std; int test() { list<char> v; for (int x = 'A'; x <= 'Z'; x++) v.push_back(x); cout << endl;
out << orden original << endl; // creamos un iterator normal list<char>::iterator i = v.begin(); while(i != v.end() ) { cout
<< *i++ << " "; } cout << endl; cout << orden inverso << endl; // creamos un iterator reverso list<char>::reverse_iterator
ri = v.rbegin(); while(ri != v.rend() ) { cout << *ri++ << " "; } cout << endl; return 0; } int main(int argc, char *argv[])
{ test(); system(PAUSE); return EXIT_SUCCESS; }
Captulo 28
Vectores
Editores:
Oscar E. Palacios
De acuerdo con la referencia de ayuda de Dev-C++ es ms seguro emplear el mtodo at() en lugar el eperador [] para
leer o escribir componentes en un vector, ya que at() no permite ndices fuera del vector, y el operador [] s. Por ejemplo,
si V es un vector cuyo nmero de componentes es de 3, entonces la instruccin V[5]; es sumamente peligrosa ya que el
ndice 5 est fuera del rango (0 a 2 ) de los componentes de V; por otro lado, la instruccin V.at(5); tambin est fuera
de rango, salvo que at() en lugar de leer o escribir el componente referenciado lanzar (throw) un error de excepcin, de
tal manera que en el programa se pueda controlar la condicin de error por medio de un catch. El mtodo at() actua de
manera parecida al operador [], y para ver un ejemplo de su uso compile y ejecute el siguiente programa.
// Demostracion del uso del mtodo at() // probado en: Dev-C++ 4.9.9.2 #include <cstdlib> #include <iostream> #include
<vector> using namespace std; int main(int argc, char *argv[]) { vector<char> v; // llenamos el vector v con valores desde
la 'A' hasta la 'Z' for (int x = 'A'; x <= 'Z'; x++) v.push_back(x); // despliegue de los elementos del vector v // mediante
el operador []. for(int i = 0; i < v.size(); i++) cout << v[i] << " "; // despliegue de los elementos del vector v // mediante
el mtodo at(). for(int i = 0; i < v.size(); i++) cout << v.at(i) << " "; cout << endl; cin.get(); return EXIT_SUCCESS; }
111
112 CAPTULO 28. VECTORES
Pilas
#include <iostream> #include <stack> using namespace std; main() { stack < int> s; s.push(1); s.push(2); s.push(3); whi-
le(!s.empty()){ cout<<" " <<s.top()<<endl; s.pop(); } cout<<" "; system(pause); }
113
Captulo 30
Colas
#include <iostream> #include <queue> using namespace std; main() { queue < int> s; s.push(1); s.push(2); s.push(3);
while(!s.empty()){ cout<<" " <<s.front()<<endl; s.pop(); } cout<<" "; system(pause); /*tomado como ejemplo http:
//es.wikibooks.org/wiki/Programaci%C3%B3n_en_C%2B%2B/Biblioteca_Est%C3%A1ndar_de_Plantillas/Pilas*/ }
114
30.1. ORIGEN DEL TEXTO Y LAS IMGENES, COLABORADORES Y LICENCIAS 115
30.1.2 Imgenes
Archivo:25%.svg Fuente: https://upload.wikimedia.org/wikipedia/commons/3/34/25%25.svg Licencia: Public domain Colaboradores: Image:
25%.png redone in svg. Artista original: Karl Wick
Archivo:50%.svg Fuente: https://upload.wikimedia.org/wikipedia/commons/c/c2/50%25.svg Licencia: Public domain Colaboradores: Based
on the XML code of Image:25%.svg Artista original: Siebrand
Archivo:75%.svg Fuente: https://upload.wikimedia.org/wikipedia/commons/4/49/75%25.svg Licencia: Public domain Colaboradores: Based
on the XML code of Image:25%.svg Artista original: Siebrand
Archivo:Crystal_Clear_app_kedit.png Fuente: https://upload.wikimedia.org/wikipedia/commons/7/70/Crystal_Clear_app_kedit.png Licen-
cia: LGPL Colaboradores: All Crystal icons were posted by the author as LGPL on kde-look Artista original: Everaldo Coelho and YellowIcon
Archivo:Desicion.png Fuente: https://upload.wikimedia.org/wikipedia/commons/9/90/Desicion.png Licencia: GFDL Colaboradores: Traba-
jo propio Artista original: MyName (El lobo)
Archivo:Funciones.png Fuente: https://upload.wikimedia.org/wikipedia/commons/2/2c/Funciones.png Licencia: GFDL Colaboradores: Tra-
bajo propio Artista original: El lobo
Archivo:Herencia01.png Fuente: https://upload.wikimedia.org/wikipedia/commons/6/64/Herencia01.png Licencia: CC-BY-SA-3.0 Colabo-
radores: Trabajo propio Artista original: El lobo
Archivo:Struct01.gif Fuente: https://upload.wikimedia.org/wikipedia/commons/9/9e/Struct01.gif Licencia: GFDL Colaboradores: Trabajo
propio Artista original: El lobo