SlideShare una empresa de Scribd logo
UNIVERSIDAD DE M ´ALAGA
Dpt. Lenguajes y CC. Computaci´on
E.T.S.I. Inform´atica
Ingenier´ıa Inform´atica
Fundamentos de Programaci´on
con
el Lenguaje de Programaci´on
C++
Vicente Benjumea y Manuel Rold´an
26 de junio de 2012
2
Este obra est´a bajo una licencia Reconocimiento-NoComercial-CompartirIgual 3.0 Un-
ported de Creative Commons: No se permite un uso comercial de la obra original ni
de las posibles obras derivadas, la distribuci´on de las cuales se debe hacer con una li-
cencia igual a la que regula la obra original. Para ver una copia de esta licencia, visite
http://creativecommons.org/licenses/by-nc-sa/3.0/deed.es ES o envie una carta a Cre-
ative Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA.
Usted es libre de:
• Copiar, distribuir y comunicar p´ublicamente la obra.
• Hacer obras derivadas.
Bajo las siguientes condiciones:
• Reconocimiento (Attribution) – Debe reconocer los cr´editos de la obra de la manera
especificada por el autor o el licenciador (pero no de una manera que sugiera que tiene
su apoyo o apoyan el uso que hace de su obra).
• No comercial (Non commercial) – No puede utilizar esta obra para fines comerciales.
• Compartir bajo la misma licencia (Share alike) – Si altera o transforma esta obra,
o genera una obra derivada, s´olo puede distribuir la obra generada bajo una licencia
id´entica a ´esta.
Entendiendo que:
• Renuncia – Alguna de estas condiciones puede no aplicarse si se obtiene el permiso del
titular de los derechos de autor
• Dominio P´ublico – Cuando la obra o alguno de sus elementos se halle en el dominio
p´ublico seg´un la ley vigente aplicable, esta situaci´on no quedar´a afectada por la licencia.
• Otros derechos – Los derechos siguientes no quedan afectados por la licencia de ninguna
manera:
◦ Los derechos derivados de usos leg´ıtimos u otras limitaciones reconocidas por ley
no se ven afectados por lo anterior.
◦ Los derechos morales del autor
◦ Derechos que pueden ostentar otras personas sobre la propia obra o su uso, como
por ejemplo derechos de imagen o de privacidad.
• Aviso – Al reutilizar o distribuir la obra, tiene que dejar bien claro los t´erminos de la
licencia de esta obra.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
´Indice general
Pr´ologo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
I Programaci´on B´asica 9
1. Un Programa C++ 11
2. Tipos Simples 15
2.1. Declaraci´on Vs. Definici´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2. Tipos Simples Predefinidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3. Tipos Simples Enumerados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.4. Constantes y Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.5. Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.6. Conversiones Autom´aticas (Impl´ıcitas) de Tipos . . . . . . . . . . . . . . . . . . . . 20
2.7. Conversiones Expl´ıcitas de Tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.8. Tabla ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.9. Algunas Consideraciones Respecto a Operaciones con N´umeros Reales . . . . . . . 22
3. Entrada y Salida de Datos B´asica 25
3.1. Salida de Datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2. Entrada de Datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3. El “Buffer” de Entrada y el “Buffer” de Salida . . . . . . . . . . . . . . . . . . . . 28
4. Estructuras de Control 29
4.1. Sentencia, Secuencia y Bloque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.2. Declaraciones Globales y Locales . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.3. Sentencias de Asignaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.4. Sentencias de Selecci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.5. Sentencias de Iteraci´on. Bucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.6. Programaci´on Estructurada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.7. Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5. Subprogramas. Funciones y Procedimientos 37
5.1. Funciones y Procedimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.2. Definici´on de Subprogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
5.3. Ejecuci´on de Subprogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.4. Paso de Par´ametros. Par´ametros por Valor y por Referencia . . . . . . . . . . . . . 39
5.5. Criterios de Modularizaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.6. Subprogramas “en L´ınea” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.7. Declaraci´on de Subprogramas. Prototipos . . . . . . . . . . . . . . . . . . . . . . . 42
5.8. Sobrecarga de Subprogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
5.9. Pre-Condiciones y Post-Condiciones . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.10. Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3
4 ´INDICE GENERAL
6. Tipos Compuestos 47
6.1. Paso de Par´ametros de Tipos Compuestos . . . . . . . . . . . . . . . . . . . . . . . 47
6.2. Cadenas de Caracteres en C++: el Tipo String . . . . . . . . . . . . . . . . . . . . 48
6.3. Registros o Estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.4. Agregados: el Tipo Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
6.5. Resoluci´on de Problemas Utilizando Tipos Compuestos . . . . . . . . . . . . . . . 64
7. B´usqueda y Ordenaci´on 69
7.1. B´usqueda Lineal (Secuencial) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
7.2. B´usqueda Binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
7.3. Ordenaci´on por Intercambio (Burbuja) . . . . . . . . . . . . . . . . . . . . . . . . . 70
7.4. Ordenaci´on por Selecci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
7.5. Ordenaci´on por Inserci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
7.6. Aplicaci´on de los Algoritmos de B´usqueda y Ordenaci´on . . . . . . . . . . . . . . . 72
8. Algunas Bibliotecas ´Utiles 77
II Programaci´on Intermedia 81
9. Almacenamiento en Memoria Secundaria: Ficheros 83
9.1. Flujos de Entrada y Salida Asociados a Ficheros . . . . . . . . . . . . . . . . . . . 84
9.2. Entrada de Datos desde Ficheros de Texto . . . . . . . . . . . . . . . . . . . . . . . 85
9.3. Salida de Datos a Ficheros de Texto . . . . . . . . . . . . . . . . . . . . . . . . . . 87
9.4. Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
10.M´odulos y Bibliotecas 95
10.1. Interfaz e Implementaci´on del M´odulo . . . . . . . . . . . . . . . . . . . . . . . . . 95
10.2. Compilaci´on Separada y Enlazado . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
10.3. Espacios de Nombre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
11.Tipos Abstractos de Datos 105
11.1. Tipos Abstractos de Datos en C++: Clases . . . . . . . . . . . . . . . . . . . . . . 106
11.1.1. Definici´on de Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
11.1.2. Utilizaci´on de Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
11.1.3. Implementaci´on de Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
11.1.4. Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
11.2. Tipos Abstractos de Datos en C++: M´as sobre Clases . . . . . . . . . . . . . . . . 116
11.2.1. Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
12.Introducci´on a la Programaci´on Gen´erica. Plantillas 129
12.1. Subprogramas Gen´ericos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
12.2. Tipos Abstractos de Datos Gen´ericos . . . . . . . . . . . . . . . . . . . . . . . . . . 132
13.Memoria Din´amica. Punteros 139
13.1. Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
13.2. Gesti´on de Memoria Din´amica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
13.3. Operaciones con Variables de Tipo Puntero . . . . . . . . . . . . . . . . . . . . . . 142
13.4. Paso de Par´ametros de Variables de Tipo Puntero . . . . . . . . . . . . . . . . . . 144
13.5. Listas Enlazadas Lineales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
13.6. Abstracci´on en la Gesti´on de Memoria Din´amica . . . . . . . . . . . . . . . . . . . 148
13.7. Tipo Abstracto de Datos Lista Enlazada Gen´erica . . . . . . . . . . . . . . . . . . 149
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
´INDICE GENERAL 5
14.Introducci´on a los Contenedores de la Biblioteca Est´andar (STL) 155
14.1. Vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
14.2. Deque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
14.3. Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
14.4. Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
14.5. Resoluci´on de Problemas Utilizando Contenedores . . . . . . . . . . . . . . . . . . 167
15.Bibliograf´ıa 171
´Indice 171
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6 ´INDICE GENERAL
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Pr´ologo
Este manual pretende ser una gu´ıa de referencia para la utilizaci´on del lenguaje de programaci´on
C++ en el desarrollo de programas. Est´a orientada a alumnos de primer curso de programaci´on
de Ingenier´ıa Inform´atica.
Este manual se concibe como material de apoyo a la docencia, y requiere de las explicaciones im-
partidas en clase por el profesor para su aprendizaje. As´ı mismo, este manual no pretende “ense˜nar
a programar”, supone que el lector posee los fundamentos necesarios relativos a la programaci´on,
y simplemente muestra como aplicarlos utilizando el Lenguaje de Programaci´on C++.
El lenguaje de programaci´on C++ es un lenguaje muy flexible y vers´atil, y debido a ello, si se
utiliza sin rigor puede dar lugar a construcciones y estructuras de programaci´on complejas, dif´ıciles
de comprender y propensas a errores. Debido a ello, restringiremos tanto las estructuras a utilizar
como la forma de utilizarlas.
No pretende ser una gu´ıa extensa del lenguaje de programaci´on C++. Adem´as, dada la amplitud
del lenguaje, hay caracter´ısticas del mismo que no han sido contempladas por exceder lo que
entendemos que es un curso de programaci´on elemental.
Este manual ha sido elaborado en el Dpto. de Lenguajes y Ciencias de la Computaci´on de la
Universidad de M´alaga.
La ´ultima versi´on de este documento puede ser descargada desde la siguiente p´agina web:
http://www.lcc.uma.es/%7Evicente/docencia/index.html
o directamente desde la siguiente direcci´on:
http://www.lcc.uma.es/%7Evicente/docencia/cppdoc/programacion_cxx.pdf
7
8 ´INDICE GENERAL
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Parte I
Programaci´on B´asica
9
El lenguaje de programación c++
Cap´ıtulo 1
Un Programa C++
En principio, un programa C++ se almacena en un fichero cuya extensi´on ser´a una de las sigu-
ientes: “.cpp”, “.cxx”, “.cc”, etc. M´as adelante consideraremos programas complejos cuyo c´odigo
se encuentra distribuido entre varios ficheros (v´ease 10).
Dentro de este fichero, normalmente, aparecer´an al principio unas l´ıneas para incluir las defini-
ciones de los m´odulos de biblioteca que utilice nuestro programa. Posteriormente, se realizar´an
declaraciones y definiciones de tipos, de constantes (v´ease 2) y de subprogramas (v´ease 5) cuyo
´ambito de visibilidad (v´ease 4.2) ser´a global a todo el fichero (desde el punto donde ha sido declara-
do hasta el final del fichero).
De entre las definiciones de subprogramas, debe definirse una funci´on principal, denominada
main, que indica donde comienza la ejecuci´on del programa. Al finalizar, dicha funci´on devolver´a un
n´umero entero que indica al Sistema Operativo el estado de terminaci´on tras la ejecuci´on del
programa (un n´umero 0 indica terminaci´on normal). En caso de no aparecer expl´ıcitamente el
valor de retorno de main, el sistema recibir´a por defecto un valor indicando terminaci´on normal.
Ejemplo de un programa que convierte una cantidad determinada de euros a su valor en pesetas.
//- fichero: euros.cpp --------------------------------------------
#include <iostream>
using namespace std ;
const double EUR_PTS = 166.386 ;
int main()
{
cout << "Introduce la cantidad (en euros): " ;
double euros ;
cin >> euros ;
double pesetas = euros * EUR_PTS ;
cout << euros << " Euros equivalen a " << pesetas << " Pts" << endl ;
// return 0 ;
}
//- fin: euros.cpp ------------------------------------------------
Se deber´a compilar el programa (c´odigo fuente) contenido en el fichero euros.cpp para tra-
ducirlo a un programa ejecutable mediante un compilador. En caso de utilizar el compilador GNU
GCC, la compilaci´on se realizar´a de la siguiente forma:
g++ -ansi -Wall -Werror -o euros euros.cpp
Una vez compilado correctamente el programa, su ejecuci´on podr´a ser como se indica a contin-
uaci´on, donde el texto enmarcado corresponde a una entrada de datos del usuario:
Introduce la cantidad (en euros): 3.5 ENTER
3.5 Euros equivalen a 582.351 Pts
11
12 CAP´ITULO 1. UN PROGRAMA C++
En algunos entornos de programaci´on, por ejemplo Dev-C++ en Windows, puede ser necesario
pausar el programa antes de su terminaci´on, para evitar que desaparezca la ventana de ejecuci´on.
En este caso el programa anterior quedar´ıa:
//- fichero: euros.cpp --------------------------------------------
#include <iostream>
#include <cstdlib>
using namespace std ;
const double EUR_PTS = 166.386 ;
int main()
{
cout << "Introduce la cantidad (en euros): " ;
double euros ;
cin >> euros ;
double pesetas = euros * EUR_PTS ;
cout << euros << " Euros equivalen a " << pesetas << " Pts" << endl ;
system("pause") ; // llamada para que el S.O. Windows pause el programa
// return 0 ;
}
//- fin: euros.cpp ------------------------------------------------
Ejemplo de un programa que imprime los n´umeros menores que uno dado por teclado.
//- fichero: numeros.cpp --------------------------------------------
#include <iostream> // biblioteca de entrada/salida
using namespace std ; // utilizaci´on del espacio de nombres de la biblioteca
// -------------------------------------
// Imprime los n´umeros menores a ’n’
// -------------------------------------
void imprimir_numeros(int n)
{
for (int i = 0; i < n; ++i) {
cout << i << " " ; // escribe el valor de ’i’
}
cout << endl ; // escribe ’fin de l´ınea’
}
// -------------------------------------
// Imprime los n´umeros menores a ’n’
// -------------------------------------
int main()
{
int maximo ;
cout << "Introduce un n´umero: " ;
cin >> maximo ;
imprimir_numeros(maximo) ;
// return 0 ;
}
//- fin: numeros.cpp ------------------------------------------------
En un programa C++ podemos distinguir los siguientes elementos b´asicos, considerando que
las letras min´usculas se consideran diferentes de las letras may´usculas:
Palabras reservadas
Son un conjunto de palabras que tienen un significado predeterminado para el compilador, y
s´olo pueden ser utilizadas con dicho sentido. Por ejemplo: using, namespace, const, double,
int, char, bool, void, for, while, do, if, switch, case, default, return, typedef, enum,
struct, etc.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
13
Identificadores
Son nombres elegidos por el programador para representar entidades (tipos, constantes, vari-
ables, funciones, etc) en el programa.
Se construyen mediante una secuencia de letras y d´ıgitos, siendo el primer car´acter una letra.
El car´acter ’_’ se considera como una letra, sin embargo, los nombres que comienzan con
dicho car´acter se reservan para situaciones especiales, por lo que no deber´ıan utilizarse en
programas.
En este manual, seguiremos la siguiente convenci´on para los identificadores:
Constantes Simb´olicas: S´olo se utilizar´an letras may´usculas, d´ıgitos y el car´acter ’_’.
Ejemplo: EUR_PTS
Tipos: Comenzar´an por una letra may´uscula seguida por letras may´usculas, min´usculas,
d´ıgitos o ’_’. Deber´a contener al menos una letra min´uscula. Ejemplo: Persona
Variables: S´olo se utilizar´an letras min´usculas, d´ıgitos y el car´acter ’_’. Ejemplo: euros,
pesetas, n, i1, etc.
Funciones: S´olo se utilizar´an letras min´usculas, d´ıgitos y el car´acter ’_’. Ejemplo: imprimir_numeros
Campos de Registros: S´olo se utilizar´an letras min´usculas, d´ıgitos y el car´acter ’_’. Ejem-
plo: nombre
Constantes literales
Son valores que aparecen expl´ıcitamente en el programa, y podr´an ser l´ogicos, num´ericos,
caracteres y cadenas. Ejemplo: true, false, 0, 25, 166.386, " Pts", ’ ’, etc.
Operadores
S´ımbolos con significado propio seg´un el contexto en el que se utilicen. Ejemplo: = << >> * /
% + - < > <= >= == != ++ -- . , etc.
Delimitadores
S´ımbolos que indican comienzo o fin de una entidad. Ejemplo: ( ) { } ; , < >
Comentarios y espacios en blanco
Los espacios en blanco, tabuladores, nueva l´ınea, retorno de carro, avance de p´agina y los
comentarios son ignorados por el compilador, excepto en el sentido en que separan elementos.
Los comentarios en un programa es texto que el programador escribe para facilitar la com-
prensi´on, o remarcar alg´un hecho importante a un lector humano, y son, por lo tanto, igno-
rados por el compilador.
Los comentarios en C++ se expresan de dos formas diferentes:
• Comentarios hasta fin de l´ınea: los s´ımbolos // marcan el comienzo del comentario, que
se extiende hasta el final de la l´ınea.
// acumula el siguiente n´umero
suma = suma + n ; // acumula el valor de ’n’
• Comentarios enmarcados: los s´ımbolos /* marcan el comienzo del comentario, que se
extiende hasta los s´ımbolos del fin del comentario */
/*
* acumula el siguiente n´umero
*/
suma = suma + n ; /* acumula el valor de ’n’ */
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
14 CAP´ITULO 1. UN PROGRAMA C++
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 2
Tipos Simples
El tipo define las caracter´ısticas que tiene una determinada entidad, de tal forma que toda
entidad manipulada por un programa lleva asociado un determinado tipo. Las caracter´ısticas que
el tipo define son:
El rango de posibles valores que la entidad puede tomar.
El conjunto de operaciones y manipulaciones aplicables a la entidad.
El espacio de almacenamiento necesario para almacenar dichos valores.
La interpretaci´on del valor almacenado.
Los tipos se pueden clasificar en tipos simples y tipos compuestos. Los tipos simples se carac-
terizan porque sus valores son indivisibles, es decir, no se puede acceder o modificar parte de ellos
(aunque ´esto se pueda realizar indirectamente mediante operaciones de bits) y los tipos compuestos
se caracterizan por estar formados como un agregado o composici´on de otros tipos, ya sean simples
o compuestos.
2.1. Declaraci´on Vs. Definici´on
Con objeto de clarificar la terminolog´ıa, en C++ una declaraci´on “presenta” un identificador
para el cual la entidad a la que hace referencia deber´a ser definida posteriormente.
Una definici´on “establece las caracter´ısticas” de una determinada entidad para el identificador
al cual se refiere. Toda definici´on es a su vez tambi´en una declaraci´on.
Es obligatorio que por cada entidad, s´olo exista una ´unica definici´on en la unidad de compi-
laci´on, aunque pueden existir varias declaraciones. As´ı mismo, tambi´en es obligatorio la declaraci´on
de las entidades que se manipulen en el programa, especificando su tipo, identificador, valores, etc.
antes de que sean utilizados.
2.2. Tipos Simples Predefinidos
Los tipos simples predefinidos en el lenguaje de programaci´on C++ son:
bool char int float double
El tipo bool se utiliza para representar valores l´ogicos (o booleanos), es decir, los valores
“Verdadero” o “Falso” o las constantes l´ogicas true y false. Suele almacenarse en el tama˜no de
palabra m´as peque˜no posible direccionable (normalmente 1 byte).
El tipo char se utiliza para representar los caracteres, es decir, s´ımbolos alfanum´ericos (d´ıgitos
y letras may´usculas y min´usculas), de puntuaci´on, espacios, control, etc. Normalmente utiliza un
espacio de almacenamiento de 1 byte (8 bits) y puede representar 256 valores diferentes (v´ease 2.8).
15
16 CAP´ITULO 2. TIPOS SIMPLES
El tipo int se utiliza para representar los n´umeros Enteros. Su representaci´on suele coincidir
con la definida por el tama˜no de palabra del procesador sobre el que va a ser ejecutado, hoy d´ıa
suele ser de 4 bytes (32 bits), aunque en determinados ordenadores puede ser de 8 bytes (64 bits).
Puede ser modificado para representar un rango de valores menor mediante el mod-
ificador short (normalmente 2 bytes [16 bits]) o para representar un rango de valores
mayor mediante el modificador long (normalmente 4 bytes [32 bits] u 8 bytes [64 bits])
y long long (normalmente 8 bytes [64 bits]).
Tambi´en puede ser modificado para representar solamente n´umeros Naturales (en-
teros positivos) utilizando el modificador unsigned.
Tanto el tipo float como el double se utilizan para representar n´umeros reales en formato de
punto flotante diferenci´andose en el rango de valores que representan, utiliz´andose el tipo double
(normalmente 8 bytes [64 bits]) para representar n´umeros de punto flotante en “doble precisi´on” y
el tipo float (normalmente 4 bytes [32 bits]) para representar la “simple precisi´on”. El tipo double
tambi´en puede ser modificado con long para representar “cu´adruple precisi´on” (normalmente 12
bytes [96 bits]).
Todos los tipos simples tienen la propiedad de ser indivisibles y adem´as mantener una relaci´on
de orden entre sus elementos (se les pueden aplicar los operadores relacionales 2.5). Se les conoce
tambi´en como tipos Escalares. Todos ellos, salvo los de punto flotante (float y double), tienen
tambi´en la propiedad de que cada posible valor tiene un ´unico antecesor y un ´unico sucesor. A
´estos se les conoce como tipos Ordinales (en terminolog´ıa C++, tambi´en se les conoce como tipos
integrales, o enteros).
A Valores L´ımites de los Tipos Predefinidos
Tanto la biblioteca est´andar climits como cfloat definen constantes, accesibles por los progra-
mas, que proporcionan los valores l´ımites que pueden contener las entidades (constantes y variables)
de tipos simples. Para ello, si un programa necesita acceder a alg´un valor definido, deber´a incluir
la biblioteca correspondiente, y utilizar las constantes adecuadas.
Biblioteca climits
#include <climits>
char short int long long long
unsigned m´aximo UCHAR_MAX USHRT_MAX UINT_MAX ULONG_MAX ULLONG_MAX
signed m´aximo SCHAR_MAX SHRT_MAX INT_MAX LONG_MAX LLONG_MAX
m´ınimo SCHAR_MIN SHRT_MIN INT_MIN LONG_MIN LLONG_MIN
m´aximo CHAR_MAX
m´ınimo CHAR_MIN
n bits CHAR_BIT
Biblioteca cfloat
#include <cfloat>
FLT_EPSILON Menor n´umero float x tal que 1,0 + x = 1,0
FLT_MAX M´aximo n´umero float de punto flotante
FLT_MIN M´ınimo n´umero float normalizado de punto flotante
DBL_EPSILON Menor n´umero double x tal que 1,0 + x = 1,0
DBL_MAX M´aximo n´umero double de punto flotante
DBL_MIN M´ınimo n´umero double normalizado de punto flotante
LDBL_EPSILON Menor n´umero long double x tal que 1,0 + x = 1,0
LDBL_MAX M´aximo n´umero long double de punto flotante
LDBL_MIN M´ınimo n´umero long double normalizado de punto flotante
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
2.3. TIPOS SIMPLES ENUMERADOS 17
Para ver el tama˜no (en bytes) que ocupa un determinado tipo/entidad en memoria, podemos
aplicarle el siguiente operador:
unsigned sz = sizeof(tipo) ;
unsigned sz = sizeof(variable) ;
Por definici´on, sizeof(char) es 1. El n´umero de bits que ocupa un determinado tipo se puede
calcular de la siguiente forma:
#include <iostream>
#include <climits>
using namespace std ;
int main()
{
unsigned nbytes = sizeof(int) ;
unsigned nbits = sizeof(int) / sizeof(char) * CHAR_BIT ;
cout << "int: "<<nbytes<<" "<<nbits<<" "<<INT_MIN<<" "<<INT_MAX<<endl ;
}
Veamos un cuadro resumen con los tipos predefinidos, su espacio de almacenamiento y el rango
de valores para una m´aquina de 32 bits, donde para una representaci´on de n bits, en caso de
ser un tipo entero con signo, sus valores m´ınimo y m´aximo vienen especificados por el rango
[(−2n−1
) · · · (+2n−1
− 1)], y en caso de ser un tipo entero sin signo, sus valores m´ınimo y m´aximo
vienen especificados por el rango [0 · · · (+2n
− 1)]:
Tipo Bytes Bits Min.Valor Max.Valor
bool 1 8 false true
char 1 8 -128 127
short 2 16 -32768 32767
int 4 32 -2147483648 2147483647
long 4 32 -2147483648 2147483647
long long 8 64 -9223372036854775808 9223372036854775807
unsigned char 1 8 0 255
unsigned short 2 16 0 65535
unsigned 4 32 0 4294967295
unsigned long 4 32 0 4294967295
unsigned long long 8 64 0 18446744073709551615
float 4 32 1.17549435e-38 3.40282347e+38
double 8 64 2.2250738585072014e-308 1.7976931348623157e+308
long double 12 96 3.36210314311209350626e-4932 1.18973149535723176502e+4932
2.3. Tipos Simples Enumerados
Adem´as de los tipos simples predefinidos, el programador puede definir nuevos tipos simples
que expresen mejor las caracter´ısticas de las entidades manipuladas por el programa. As´ı, dicho
tipo se definir´a en base a una enumeraci´on de los posibles valores que pueda tomar la entidad
asociada. A dicho tipo se le denomina tipo enumerado, es un tipo simple ordinal, y se define de la
siguiente forma:
enum Color {
ROJO,
AZUL,
AMARILLO
} ;
De esta forma definimos el tipo Color, que definir´a una entidad (constante o variable) que
podr´a tomar cualquiera de los diferentes valores enumerados. Los tipos enumerados, al ser tipos
definidos por el programador, no tiene entrada ni salida predefinida por el lenguaje, sino que
deber´a ser el programador el que especifique (programe) como se realizar´a la entrada y salida de
datos en caso de ser necesaria. Otro ejemplo de enumeraci´on:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
18 CAP´ITULO 2. TIPOS SIMPLES
enum Meses {
Enero, Febrero, Marzo, Abril, Mayo, Junio, Julio,
Agosto, Septiembre, Octubre, Noviembre, Diciembre
} ;
2.4. Constantes y Variables
Podemos dividir las entidades que nuestro programa manipula en dos clases fundamentales:
aquellos cuyo valor no var´ıa durante la ejecuci´on del programa (constantes) y aquellos otros cuyo
valor puede ir cambiando durante la ejecuci´on del programa (variables).
Constantes
Las constantes pueden aparecer a su vez como constantes literales, son aquellas cuyo valor
aparece directamente en el programa, y como constantes simb´olicas, aquellas cuyo valor se asocia
a un identificador, a trav´es del cual se representa.
Ejemplos de constantes literales:
Constantes l´ogicas (bool):
false, true
Constantes car´acter (char), el s´ımbolo constante aparece entre comillas simples:
’a’, ’b’, ..., ’z’,
’A’, ’B’, ..., ’Z’,
’0’, ’1’, ..., ’9’,
’ ’, ’.’, ’,’, ’:’, ’;’, ...
As´ı mismo, ciertos caracteres constantes tienen un significado especial (caracteres de escape):
• ’n’: fin de l´ınea (newline)
• ’r’: retorno de carro (carriage-return)
• ’b’: retroceso (backspace)
• ’t’: tabulador horizontal
• ’v’: tabulador vertical
• ’f’: avance de p´agina (form-feed)
• ’a’: sonido (audible-bell)
• ’0’: fin de cadena
• ’137’, ’x5F’: car´acter correspondiente al valor especificado en notaci´on octal y hexadecimal
respectivamente
Constantes cadenas de caracteres literales, la secuencia de caracteres aparece entre comillas
dobles (puede contener caracteres de escape):
"Hola Pepe"
"HolanJuann"
"Hola " "Mar´ıa"
Constantes enteras, pueden ser expresadas en decimal (base 10), hexadecimal (base 16) y
octal (base 8). El sufijo L se utiliza para especificar long, el sufijo LL se utiliza para es-
pecificar long long, el sufijo U se utiliza para especificar unsigned, el sufijo UL especifica
unsigned long, y el sufijo ULL especifica unsigned long long:
123, -1520, 2345U, 30000L, 50000UL, 0x10B3FC23 (hexadecimal), 0751 (octal)
Constantes reales, n´umeros en punto flotante. El sufijo F especifica float, y el sufijo L
especifica long double:
3.1415, -1e12, 5.3456e-5, 2.54e-1F, 3.25e200L
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
2.5. OPERADORES 19
Constantes Simb´olicas
Las constantes simb´olicas se declaran indicando la palabra reservada const seguida por su
tipo, el nombre simb´olico (o identificador) con el que nos referiremos a ella y el valor asociado
tras el s´ımbolo (=). Usualmente se definen al principio del programa (despu´es de la inclusi´on de
las cabeceras de las bibliotecas), y ser´an visibles desde el punto de declaraci´on, hasta el final del
programa. Ejemplos de constantes simb´olicas:
const bool OK = true ;
const char SONIDO = ’a’ ;
const short ELEMENTO = 1000 ;
const int MAXIMO = 5000 ;
const long ULTIMO = 100000L ;
const long long TAMANO = 1000000LL ;
const unsigned short VALOR = 100U ;
const unsigned FILAS = 200U ;
const unsigned long COLUMNAS = 200UL ;
const unsigned long long NELMS = 2000ULL ;
const float N_E = 2.7182F ;
const double LOG10E = log(N_E) ;
const long double N_PI = 3.141592L ;
const Color COLOR_DEFECTO = ROJO ;
Variables
Las variables se definen, dentro de un bloque de sentencias (v´ease 4.1), especificando su tipo
y el identificador con el que nos referiremos a ella, y ser´an visibles desde el punto de declaraci´on
hasta el final del cuerpo (bloque) donde han sido declaradas. Se les podr´a asignar un valor inicial
en la definici´on (mediante el s´ımbolo =), si no se les asigna ning´un valor inicial, entonces tendr´an
un valor inespecificado. Su valor podr´a cambiar mediante la sentencia de asignaci´on (v´ease 4.3)
o mediante una sentencia de entrada de datos (v´ease 3.2).
{
char letra ; // valor inicial inespecificado
int contador = 0 ;
double total = 5.0 ;
...
}
2.5. Operadores
Los siguientes operadores se pueden aplicar a los datos, donde las siguiente tabla los muestra
ordenados de mayor a menor orden de precedencia, as´ı como tambi´en muestra su asociatividad:
Operador Tipo de Operador Asociatividad
[] -> . Binarios Izq. a Dch.
! ~ - * Unarios Dch. a Izq.
* / % Binarios Izq. a Dch.
+ - Binarios Izq. a Dch.
<< >> Binarios Izq. a Dch.
< <= > >= Binarios Izq. a Dch.
== != Binarios Izq. a Dch.
& Binario Izq. a Dch.
^ Binario Izq. a Dch.
| Binario Izq. a Dch.
&& Binario Izq. a Dch.
|| Binario Izq. a Dch.
?: Ternario Dch. a Izq.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
20 CAP´ITULO 2. TIPOS SIMPLES
Significado de los operadores:
Aritm´eticos. El resultado es del mismo tipo que los operandos (v´ease 2.6):
- valor Menos unario
valor * valor Producto (multiplicaci´on)
valor / valor Divisi´on (entera o real seg´un el tipo de operandos)
valor % valor M´odulo (resto de la divisi´on) (s´olo tipos enteros)
valor + valor Suma
valor - valor Resta
Relacionales/Comparaciones. El resultado es de tipo bool
valor < valor Comparaci´on menor
valor <= valor Comparaci´on menor o igual
valor > valor Comparaci´on mayor
valor >= valor Comparaci´on mayor o igual
valor == valor Comparaci´on igualdad
valor != valor Comparaci´on desigualdad
Operadores de Bits, s´olo aplicable a operandos de tipos enteros. El resultado es del mismo
tipo que los operandos (v´ease 2.6):
~ valor Negaci´on de bits (complemento)
valor << despl Desplazamiento de bits a la izq.
valor >> despl Desplazamiento de bits a la dch.
valor & valor AND de bits
valor ^ valor XOR de bits
valor | valor OR de bits
L´ogicos, s´olo aplicable operandos de tipo booleano. Tanto el operador && como el operador
|| se eval´uan en cortocircuito. El resultado es de tipo bool:
! valor Negaci´on l´ogica (Si valor es true entonces false, en otro caso true)
valor1 && valor2 AND l´ogico (Si valor1 es false entonces false, en otro caso valor2)
valor1 || valor2 OR l´ogico (Si valor1 es true entonces true, en otro caso valor2)
x ! x
F T
T F
x y x && y x || y
F F F F
F T F T
T F F T
T T T T
Condicional. El resultado es del mismo tipo que los operandos:
cond ? valor1 : valor2 Si cond es true entonces valor1, en otro caso valor2
2.6. Conversiones Autom´aticas (Impl´ıcitas) de Tipos
Es posible que nos interese realizar operaciones en las que se mezclen datos de tipos diferentes.
El lenguaje de programaci´on C++ realiza conversiones de tipo autom´aticas (“castings”), de tal
forma que el resultado de la operaci´on sea del tipo m´as amplio de los implicados en ella. Siempre
que sea posible, los valores se convierten de tal forma que no se pierda informaci´on.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
2.7. CONVERSIONES EXPL´ICITAS DE TIPOS 21
Promociones Enteras
Son conversiones impl´ıcitas que preservan valores. Antes de realizar una operaci´on aritm´etica,
se utiliza promoci´on a entero para crear int a partir de otros tipos integrales mas cortos. Para los
siguientes tipos origen: bool, char, signed char, unsigned char, short, unsigned short
Si int puede representar todos los valores posibles del tipo origen, entonces sus valores
promocionan a int.
En otro caso, promocionan a unsigned int
Conversiones Enteras
Si el tipo destino es unsigned, el valor resultante es el menor unsigned congruente con el valor
origen m´odulo 2n
, siendo n el n´umero de bits utilizados en la representaci´on del tipo destino (en
representaci´on de complemento a dos, simplemente tiene tantos bits del origen como quepan en el
destino, descartando los de mayor orden).
Si el tipo destino es signed, el valor no cambia si se puede representar en el tipo destino, si no,
viene definido por la implementaci´on.
Conversiones Aritm´eticas Impl´ıcitas Habituales
Se realizan sobre los operandos de un operador binario para convertirlos a un tipo com´un que
ser´a el tipo del resultado:
1. Si alg´un operando es de tipo de punto flotante (real):
a) Si alg´un operando es de tipo long double, el otro se convierte a long double.
b) En otro caso, si alg´un operando es double el otro se convierte a double.
c) En otro caso, si alg´un operando es float el otro se convierte a float.
2. En otro caso, se realizan promociones enteras (v´ease sec. 2.6) sobre ambos operandos:
a) Si alg´un operando es de tipo unsigned long, el otro se convierte a unsigned long.
b) En otro caso, si alg´un operando es long int y el otro es unsigned int, si un long int
puede representar todos los valores de un unsigned int, el unsigned int se convierte
a long int; en caso contrario, ambos se convierten a unsigned long int.
c) En otro caso, si alg´un operando es long el otro se convierte a long.
d) En otro caso, si alg´un operando es unsigned el otro se convierte a unsigned.
e) En otro caso, ambos operandos son int
2.7. Conversiones Expl´ıcitas de Tipos
Tambi´en es posible realizar conversiones de tipo expl´ıcitas. Para ello, se escribe el tipo al que
queremos convertir y entre par´entesis la expresi´on cuyo valor queremos convertir. Por ejemplo:
char x = char(65) ; produce el car´acter ’A’
int x = int(’a’) ; convierte el car´acter ’a’ a su valor entero (97)
int x = int(ROJO) ; produce el entero 0
int x = int(AMARILLO) ; produce el entero 2
int x = int(3.7) ; produce el entero 3
double x = double(2) ; produce el real (doble precisi´on) 2.0
Color x = Color(1) ; produce el Color AZUL
Color x = Color(c+1) ; si c es de tipo Color, produce el siguiente valor de la enumeraci´on
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
22 CAP´ITULO 2. TIPOS SIMPLES
El tipo enumerado se convierte autom´aticamente a entero, aunque la conversi´on inversa no se
realiza de forma autom´atica. As´ı, para incrementar una variable de tipo color se realizar´a de la
siguiente forma:
enum Color {
ROJO, AZUL, AMARILLO
} ;
int main()
{
Color c = ROJO ;
c = Color(c + 1) ;
// ahora c tiene el valor AZUL
}
2.8. Tabla ASCII
La tabla ASCII es com´unmente utilizada como base para la representaci´on de los caracteres,
donde los n´umeros del 0 al 31 se utilizan para representar caracteres de control, y los n´umeros del
128 al 255 se utilizan para representar caracteres extendidos.
Rep Simb Rep Simb Rep Simb Rep Simb
0 0 32 SP 64 @ 96 ‘
1 SOH 33 ! 65 A 97 a
2 STX 34 " 66 B 98 b
3 ETX 35 # 67 C 99 c
4 EOT 36 $ 68 D 100 d
5 ENQ 37 % 69 E 101 e
6 ACK 38 & 70 F 102 f
7 a 39 ’ 71 G 103 g
8 b 40 ( 72 H 104 h
9 t 41 ) 73 I 105 i
10 n 42 * 74 J 106 j
11 v 43 + 75 K 107 k
12 f 44 , 76 L 108 l
13 r 45 - 77 M 109 m
14 SO 46 . 78 N 110 n
15 SI 47 / 79 O 111 o
16 DLE 48 0 80 P 112 p
17 DC1 49 1 81 Q 113 q
18 DC2 50 2 82 R 114 r
19 DC3 51 3 83 S 115 s
20 DC4 52 4 84 T 116 t
21 NAK 53 5 85 U 117 u
22 SYN 54 6 86 V 118 v
23 ETB 55 7 87 W 119 w
24 CAN 56 8 88 X 120 x
25 EM 57 9 89 Y 121 y
26 SUB 58 : 90 Z 122 z
27 ESC 59 ; 91 [ 123 {
28 FS 60 < 92  124 |
29 GS 61 = 93 ] 125 }
30 RS 62 > 94 ^ 126 ~
31 US 63 ? 95 _ 127 DEL
2.9. Algunas Consideraciones Respecto a Operaciones con
N´umeros Reales
La representaci´on de los n´umeros reales es limitada en cuanto al rango de valores y a la precisi´on
de los mismos, ya que el n´umero de d´ıgitos decimales que puede almacenar es finito. Adem´as, la
representaci´on de n´umeros reales en base 2 hace que la representaci´on de determinados n´umeros en
base 10 sea inexacta, por lo tanto la realizaci´on de operaciones aritm´eticas en punto flotante puede
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
2.9. ALGUNAS CONSIDERACIONES RESPECTO A OPERACIONES CON N ´UMEROS REALES23
dar lugar a p´erdidas de precisi´on que den lugar a resultados inesperados, distintas operaciones que
matem´aticamente son equivalentes pueden ser computacionalmente diferentes. As´ı, la comparaci´on
directa de igualdad o desigualdad de n´umeros reales debe ser evitada. Por ejemplo, el siguiente
programa:
#include <iostream>
using namespace std ;
int main()
{
bool ok = (3.0 * (0.1 / 3.0)) == ((3.0 * 0.1) / 3.0) ;
cout << "Resultado de (3.0 * (0.1 / 3.0)) == ((3.0 * 0.1) / 3.0): "
<< boolalpha << ok << endl ;
}
produce un resultado distinto a lo que cabr´ıa esperar:
Resultado de (3.0 * (0.1 / 3.0)) == ((3.0 * 0.1) / 3.0): false
El siguiente programa:
#include <iostream>
using namespace std ;
int main()
{
double x, y ;
double a, b ;
cout << "Introduce 3 n´umeros reales: " ;
cin >> a >> b >> y ;
x = a * b ;
bool cmp = (x == y) ;
cout << a << " * " << b << " == " << y << " "
<< boolalpha << cmp
<< endl ;
}
produce los siguientes resultados en una m´aquina de 32 bits para los valores de a, b y c:
a: 1, b: 1, c: 1 ⇒ true
a: 2, b: 2, c: 4 ⇒ true
a: 3, b: 3, c: 9 ⇒ true
a: 3, b: 0.1, c: 0.3 ⇒ false
a: 2, b: 0.2, c: 0.4 ⇒ true
a: 3, b: 0.3, c: 0.9 ⇒ false
a: 0.1, b: 0.1, c: 0.01 ⇒ false
a: 0.2, b: 0.2, c: 0.04 ⇒ false
a: 0.3, b: 0.3, c: 0.09 ⇒ false
As´ı, la comparaci´on de igualdad entre n´umeros reales se deber´ıa sustituir por una comparaci´on
de proximidad, de tal forma que consideraremos que dos n´umeros reales (x, y) son “iguales” si
est´an lo suficientemente pr´oximos:
#include <iostream>
#include <cmath>
using namespace std ;
int main()
{
double x, y ;
double a, b ;
cout << "Introduce 3 n´umeros reales: " ;
cin >> a >> b >> y ;
x = a * b ;
bool cmp = fabs(x - y) < 1e-6 ;
cout << a << " * " << b << " == " << y << " "
<< boolalpha << cmp
<< endl ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
24 CAP´ITULO 2. TIPOS SIMPLES
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 3
Entrada y Salida de Datos B´asica
La entrada y salida de datos permite a un programa recibir informaci´on desde el exterior
(usualmente el teclado), la cual ser´a transformada mediante un determinado procesamiento, y
posteriormente permitir´a mostrar al exterior (usualmente la pantalla del monitor) el resultado de
la computaci´on.
Para poder realizar entrada y salida de datos b´asica es necesario incluir la biblioteca iostream
que contiene las declaraciones de tipos y operaciones que la realizan. Todas las definiciones y
declaraciones de la biblioteca est´andar se encuentran bajo el espacio de nombres std (ver cap´ıtu-
lo 10), por lo que para utilizarlos adecuadamente habr´a que utilizar la directiva using al comienzo
del programa.
#include <iostream> // inclusi´on de la biblioteca de entrada/salida
using namespace std ; // utilizaci´on del espacio de nombres de la biblioteca
const double EUR_PTS = 166.386 ;
int main()
{
cout << "Introduce la cantidad (en euros): " ;
double euros ;
cin >> euros ;
double pesetas = euros * EUR_PTS ;
cout << euros << " Euros equivalen a " << pesetas << " Pts" << endl ;
}
3.1. Salida de Datos
La salida de datos permite mostrar informaci´on al exterior, y se realiza a trav´es de los flujos
de salida, as´ı el flujo de salida asociado a la salida est´andar (usualmente la pantalla o terminal de
la consola) se denomina cout. De esta forma, la salida de datos a pantalla se realiza utilizando el
operador << sobre el flujo cout especificando el dato cuyo valor se mostrar´a. Por ejemplo:
cout << "Introduce la cantidad (en euros): " ;
escribir´a en la salida est´andar el mensaje correspondiente a la cadena de caracteres especificada. El
siguiente ejemplo escribe en la salida est´andar el valor de las variables euros y pesetas, as´ı como un
mensaje para interpretarlos adecuadamente. El s´ımbolo endl indica que la sentencia deber´a escribir
un fin de l´ınea (lo que se muestre a continuaci´on se realizar´a en una nueva l´ınea).
cout << euros << " Euros equivalen a " << pesetas << " Pts" << endl ;
Salida de Datos Formateada
Es posible especificar el formato bajo el que se realizar´a la salida de datos. Para ello se debe
incluir la biblioteca est´andar iomanip. Por ejemplo:
25
26 CAP´ITULO 3. ENTRADA Y SALIDA DE DATOS B ´ASICA
#include <iostream>
#include <iomanip>
using namespace std ;
int main()
{
bool x = true ;
cout << boolalpha << x ; // escribe los booleanos como ’false’ o ’true’
cout << dec << 27 ; // escribe 27 (decimal)
cout << hex << 27 ; // escribe 1b (hexadecimal)
cout << oct << 27 ; // escribe 33 (octal)
cout << setprecision(2) << 4.567 ; // escribe 4.6
cout << setw(5) << 234 ; // escribe " 234"
cout << setfill(’#’) << setw(5) << 234 ; // escribe "##234"
}
donde el manipulador boolalpha especifica que los valores l´ogicos se mostrar´an mediante los valores
false y true, y los manipuladores dec, hex, oct especifican que la salida se realizar´a utilizando
el sistema de numeraci´on decimal, hexadecimal o octal respectivamente. Por otra parte, el ma-
nipulador setprecision(...) especifica la cantidad de d´ıgitos significativos (precisi´on) que se
mostrar´a en la salida de n´umeros reales, el manipulador setw(...) especifica la anchura (width)
que como m´ınimo ocupar´a la salida da datos (permite mostrar la informaci´on de forma tabulada),
y el manipulador setfill(...) especifica el car´acter de relleno (fill) que se utilizar´a en caso de
ser necesario para ocupar toda la anchura del campo de salida.
3.2. Entrada de Datos
La entrada de datos permite recibir informaci´on desde el exterior, y se realiza a trav´es de los
flujos de entrada, as´ı el flujo de entrada asociado a la entrada est´andar (usualmente el teclado) se
denomina cin. De esta forma, la entrada de datos desde el teclado se realiza mediante el operador
>> sobre el flujo cin especificando la variable donde almacenar el valor de entrada le´ıdo desde el
teclado:
cin >> euros ;
incluso es posible leer varios valores consecutivamente en la misma sentencia de entrada:
cin >> minimo >> maximo ;
Dicho operador de entrada se comporta de la siguiente forma: elimina los espacios en blanco que
hubiera al principio de la entrada de datos, y lee dicha entrada hasta que encuentre alg´un car´acter
no v´alido para dicha entrada, que no ser´a le´ıdo y permanecer´a disponible en el buffer de entrada
(v´ease 3.3) hasta la pr´oxima operaci´on de entrada. En caso de que durante la entrada surja alguna
situaci´on de error, dicha entrada se detiene y el flujo de entrada se pondr´a en un estado err´oneo.
Se consideran espacios en blanco los siguientes caracteres: espacio en blanco, tabuladores, retorno
de carro y nueva l´ınea (’ ’, ’t’, ’v’, ’f’, ’r’, ’n’).
A Entrada de Datos Avanzada
A diferencia del comportamiento especificado anteriormente, tambi´en es posible leer un car´acter,
desde el flujo de entrada, sin eliminar los espacios iniciales:
{
char c ;
cin.get(c) ; // lee un car´acter sin eliminar espacios en blanco iniciales
...
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
3.2. ENTRADA DE DATOS 27
En caso de querer eliminar los espacios iniciales expl´ıcitamente:
{
char c ;
cin >> ws ; // elimina los espacios en blanco iniciales
cin.get(c) ; // lee sin eliminar espacios en blanco iniciales
...
}
Es posible tambi´en eliminar un n´umero determinado de caracteres del flujo de entrada, o hasta
que se encuentre un determinado car´acter:
{
cin.ignore() ; // elimina el pr´oximo car´acter
cin.ignore(5) ; // elimina los 5 pr´oximos caracteres
cin.ignore(1000, ’n’) ; // elimina 1000 caracteres o hasta nueva-l´ınea
}
La entrada y salida de cadenas de caracteres se puede ver en los cap´ıtulos correspondientes
(cap. 6.2).
A Control del Estado del Flujo
Cuando se realiza una entrada de datos err´onea, el flujo de entrada se pondr´a en un estado de
error, de tal forma que cualquier operaci´on de entrada de datos sobre un flujo de datos en estado
err´oneo tambi´en fallar´a. Por ejemplo, la ejecuci´on del siguiente programa entrar´a en un “bucle
sin fin” en caso de que se introduzca una letra en vez de un n´umero, ya que el valor que tome la
variable n no estar´a en el rango adecuado, y cualquier otra operaci´on de entrada tambi´en fallar´a,
por lo que el valor de n nunca podr´a tomar un valor v´alido dentro del rango espcificado:
int main()
{
int n = 0;
do {
cout << "Introduzca un numero entre [1..9]: ";
cin >> n;
} while (! (n > 0 && n < 10));
cout << "Valor: " << n << endl;
}
Sin embargo, es posible comprobar el estado de un determinado flujo de datos, y en caso de que
se encuentre en un estado de error, es posible restaurarlo a un estado correcto, por ejemplo:
int main()
{
int n = 0;
do {
cout << "Introduzca un numero [1..9]: ";
cin >> n;
while (cin.fail()) { // ¿ Estado Err´oneo ?
cin.clear(); // Restaurar estado
cin.ignore(1000, ’n’); // Eliminar la entrada de datos anterior
cout << "Error: Introduzca un numero [1..9]: ";
cin >> n;
}
} while (! (n > 0 && n < 10));
cout << "Valor: " << n << endl;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
28 CAP´ITULO 3. ENTRADA Y SALIDA DE DATOS B ´ASICA
3.3. El “Buffer” de Entrada y el “Buffer” de Salida A
Ning´un dato de entrada o de salida en un programa C++ se obtiene o env´ıa directamente del/al
hardware, sino que se realiza a trav´es de unas zonas de memoria intermedia (“buffers”) de entrada
y salida respectivamente controlados por el Sistema Operativo y son independientes de nuestro
programa.
As´ı, cuando se pulsa alguna tecla, el Sistema Operativo almacena en secuencia las teclas pul-
sadas en una zona de memoria intermedia: el “buffer” de entrada. Cuando un programa realiza
una operaci´on de entrada de datos (cin >> valor), accede al “buffer” de entrada y obtiene los
valores all´ı almacenados si los hubiera, o esperar´a hasta que los haya (se pulsen una serie de teclas
seguidas por la tecla “enter”). Una vez obtenidos las teclas pulsadas (caracteres), se convertir´an a
un valor del tipo especificado por la operaci´on de entrada, y dicho valor se asignar´a a la variable
especificada.
De igual forma, cuando se va a mostrar alguna informaci´on de salida dichos datos no van direc-
tamente a la pantalla, sino que se convierten a un formato adecuado para ser impresos (caracteres)
y se almacenan en una zona de memoria intermedia denominada “buffer” de salida, desde donde
el Sistema Operativo tomar´a la informaci´on para ser mostrada por pantalla.
cout << "Valor: " << val << endl ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 4
Estructuras de Control
Las estructuras de control en el lenguaje de programaci´on C++ son muy flexibles, sin embargo,
la excesiva flexibilidad puede dar lugar a estructuras complejas. Por ello s´olo veremos algunas de
ellas y utilizadas en contextos y situaciones restringidas.
4.1. Sentencia, Secuencia y Bloque
En C++ la unidad b´asica de acci´on es la sentencia, y expresamos la composici´on de sentencias
como una secuencia de sentencias terminadas cada una de ellas por el car´acter “punto y coma”
(;), de tal forma que su flujo de ejecuci´on es secuencial, es decir, se ejecuta una sentencia, y cuando
´esta termina, entonces se ejecuta la siguiente sentencia, y as´ı sucesivamente.
Un bloque es una unidad de ejecuci´on mayor que la sentencia, y permite agrupar una secuencia
de sentencias como una unidad. Para ello enmarcamos la secuencia de sentencias entre dos llaves
para formar un bloque. Es posible el anidamiento de bloques.
int main()
{
<sentencia_1> ;
<sentencia_2> ;
{
<sentencia_3> ;
<sentencia_4> ;
. . .
}
<sentencia_n> ;
}
4.2. Declaraciones Globales y Locales
Como ya se vio en el cap´ıtulo anterior, es obligatoria la declaraci´on de las entidades manipuladas
en el programa. Distinguiremos dos clases de declaraciones: globales y locales.
Entidades globales son aquellas que han sido definidas fuera de cualquier bloque. Su ´ambito
de visibilidad comprende desde el punto en el que se definen hasta el final del fichero. Respecto a
su tiempo de vida, se crean al principio de la ejecuci´on del programa y se destruyen al finalizar
´este. Normalmente ser´an constantes simb´olicas, definiciones de tipos, declaraci´on de prototipos de
subprogramas y definiciones de subprogramas.
Entidades locales son aquellas que se definen dentro de un bloque. Su ´ambito de visibilidad
comprende desde el punto en el que se definen hasta el final de dicho bloque. Respecto a su tiempo
de vida, se crean en el punto donde se realiza la definici´on, y se destruyen al finalizar el bloque.
Normalmente ser´an constantes simb´olicas y variables locales.
29
30 CAP´ITULO 4. ESTRUCTURAS DE CONTROL
#include <iostream>
using namespace std ;
const double EUR_PTS = 166.386 ; // Declaraci´on de constante GLOBAL
int main()
{
cout << "Introduce la cantidad (en euros): " ;
double euros ; // Declaraci´on de variable LOCAL
cin >> euros ;
double pesetas = euros * EUR_PTS ; // Declaraci´on de variable LOCAL
cout << euros << " Euros equivalen a " << pesetas << " Pts" << endl ;
}
Respecto al ´ambito de visibilidad de una entidad, en caso de declaraciones de diferentes enti-
dades con el mismo identificador en diferentes niveles de anidamiento, la entidad visible ser´a aquella
que se encuentre declarada en el bloque de nivel de anidamiento m´as interno. Es decir, cuando
se solapa el ´ambito de visibilidad de dos entidades con el mismo identificador, en dicha zona de
solapamiento ser´a visible el identificador declarado o definido en el bloque m´as interno. Sin embar-
go, no es una buena pr´actica de programaci´on ocultar identificadores al redefinirlos en niveles de
anidamiento mas internos, ya que conduce a programas dif´ıciles de leer y propensos a errores, por
lo que procuraremos evitar esta pr´actica.
int main()
{
int x = 3 ;
int z = x * 2 ; // x es vble de tipo int con valor 3
{
double x = 5.0 ;
double n = x * 2 ; // x es vble de tipo double con valor 5.0
}
int y = x + 4 ; // x es vble de tipo int con valor 3
}
4.3. Sentencias de Asignaci´on
La sentencia de asignaci´on permite asignar a una variable el resultado de evaluar una expresi´on
aritm´etica expresada en notaci´on infija, de tal forma que primero se eval´ua la expresi´on, con-
siderando las reglas de precedencia y asociatividad de los operadores (v´ease 2.5), y a continuaci´on
el valor resultante se asigna a la variable, que perder´a el valor anterior que tuviese.
{
...
<variable> = <expresi´on_aritm´etica> ;
...
}
Por ejemplo:
const int MAXIMO = 15 ;
int main()
{
int cnt ; // valor inicial inespecificado
cnt = 30 * MAXIMO + 1 ; // asigna a cnt el valor 451
cnt = cnt + 10 ; // cnt pasa ahora a contener el valor 461
}
Adem´as, se definen las siguientes sentencias de incremento y decremento, as´ı como su equiva-
lencia en la siguiente tabla :
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
4.4. SENTENCIAS DE SELECCI ´ON 31
Sentencia Equivalencia
++variable; variable = variable + 1;
--variable; variable = variable - 1;
variable++; variable = variable + 1;
variable--; variable = variable - 1;
variable += expresion; variable = variable + (expresion);
variable -= expresion; variable = variable - (expresion);
variable *= expresion; variable = variable * (expresion);
variable /= expresion; variable = variable / (expresion);
variable %= expresion; variable = variable % (expresion);
variable &= expresion; variable = variable & (expresion);
variable ^= expresion; variable = variable ^ (expresion);
variable |= expresion; variable = variable | (expresion);
variable <<= expresion; variable = variable << (expresion);
variable >>= expresion; variable = variable >> (expresion);
Nota: las sentencias de asignaci´on vistas anteriormente se pueden utilizar en otras formas muy
diversas, pero nosotros restringiremos su utilizaci´on a la expresada anteriormente, debido a que
otras utilizaciones pueden dificultar la legibilidad y aumentar las posibilidades de cometer errores
de programaci´on.
4.4. Sentencias de Selecci´on
Las sentencias de selecci´on alteran el flujo secuencial de ejecuci´on de un programa, de tal
forma que permiten seleccionar flujos de ejecuci´on alternativos y excluyentes dependiendo del valor
que tomen determinadas expresiones l´ogicas. La m´as simple de todas es la sentencia de selecci´on
condicional if cuya sintaxis es la siguiente:
int main()
{
if ( <expresi´on_l´ogica> ) {
<secuencia_de_sentencias> ;
}
}
y cuya sem´antica consiste en evaluar la expresi´on l´ogica, y si su resultado es Verdadero (true)
entonces se ejecuta la secuencia de sentencias entre las llaves. Ejemplo de programa que imprime
el valor mayor de tres n´umeros:
#include <iostream>
using namespace std ;
int main ()
{
int a, b, c ;
cin >> a >> b >> c ;
int mayor = a ;
if (b > mayor) {
mayor = b ;
}
if (c > mayor) {
mayor = c ;
}
cout << mayor << endl ;
}
Otra posibilidad es la sentencia de selecci´on condicional compuesta, que tiene la siguiente
sintaxis:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
32 CAP´ITULO 4. ESTRUCTURAS DE CONTROL
int main()
{
if ( <expresi´on_l´ogica> ) {
<secuencia_de_sentencias_v> ;
} else {
<secuencia_de_sentencias_f> ;
}
}
y cuya sem´antica consiste en evaluar la expresi´on l´ogica, y si su resultado es Verdadero (true)
entonces se ejecuta la <secuencia de sentencias v> . Sin embargo, si el resultado de evaluar la
expresi´on l´ogica es Falso (false) entonces se ejecuta la <secuencia de sentencias f> .
La sentencia de selecci´on condicional se puede encadenar de la siguiente forma con el flujo de
control esperado:
#include <iostream>
using namespace std ;
int main ()
{
double nota ;
cin >> nota ;
if ( ! ((nota >= 0.0) && (nota <= 10.0))) {
cout << "Error: 0 <= n <= 10" << endl ;
} else if (nota >= 9.5) {
cout << "Matr´ıcula de Honor" << endl ;
} else if (nota >= 9.0) {
cout << "Sobresaliente" << endl ;
} else if (nota >= 7.0) {
cout << "Notable" << endl ;
} else if (nota >= 5.0) {
cout << "Aprobado" << endl ;
} else {
cout << "Suspenso" << endl ;
}
}
La sentencia switch es otro tipo de sentencia de selecci´on en la cual la secuencia de sentencias
alternativas a ejecutar no se decide en base a expresiones l´ogicas, sino en funci´on del valor que tome
una determinada expresi´on de tipo ordinal (v´ease 2.2), es decir, una relaci´on de igualdad entre el
valor de una expresi´on y unos determinados valores constantes de tipo ordinal especificados. Su
sintaxis es la siguiente:
int main()
{
switch ( <expresi´on> ) {
case <valor_cte_1> :
<secuencia_de_sentencias_1> ;
break ;
case <valor_cte_2> :
case <valor_cte_3> :
<secuencia_de_sentencias_2> ;
break ;
case <valor_cte_4> :
<secuencia_de_sentencias_3> ;
break ;
. . .
default:
<secuencia_de_sentencias_d> ;
break ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
4.5. SENTENCIAS DE ITERACI ´ON. BUCLES 33
}
en la cual se eval´ua la expresi´on, y si su valor coincide con <valor cte 1> entonces se ejecuta
la <secuencia de sentencias 1> . Si su valor coincide con <valor cte 2> o con <valor cte 3>
se ejecuta la <secuencia de sentencias 2> y as´ı sucesivamente. Si el valor de la expresi´on no
coincide con ning´un valor especificado, se ejecuta la secuencia de sentencias correspondiente a
la etiqueta default (si es que existe). N´otese que la sentencia break; termina la secuencia de
sentencias a ejecutar para cada caso, en caso de que falte, el comportamiento no ser´a el deseado.
Ejemplo:
#include <iostream>
using namespace std ;
int main ()
{
int dia ;
...
// ’dia’ tiene alg´un valor v´alido
switch (dia) {
case 1:
cout << "Lunes" << endl ;
break ;
case 2:
cout << "Martes" << endl ;
break ;
case 3:
cout << "Mi´ercoles" << endl ;
break ;
case 4:
cout << "Jueves" << endl ;
break ;
case 5:
cout << "Viernes" << endl ;
break ;
case 6:
cout << "S´abado" << endl ;
break ;
case 7:
cout << "Domingo" << endl ;
break ;
default:
cout << "Error" << endl ;
break ;
}
}
4.5. Sentencias de Iteraci´on. Bucles
Vamos a utilizar tres tipos de sentencias diferentes para expresar la repetici´on de la ejecuci´on
de un grupo de sentencias: while, for y do-while.
La sentencia while es la m´as utilizada y su sintaxis es la siguiente:
int main()
{
while ( <expresi´on_l´ogica> ) {
<secuencia_de_sentencias> ;
}
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
34 CAP´ITULO 4. ESTRUCTURAS DE CONTROL
en la cual primero se eval´ua la expresi´on l´ogica, y si es cierta, se ejecuta la secuencia de sentencias
entre llaves completamente. Posteriormente se vuelve a evaluar la expresi´on l´ogica y si vuelve a ser
cierta se vuelve a ejecutar la secuencia de sentencias entre llaves. Este ciclo iterativo consistente en
evaluar la condici´on y ejecutar las sentencias se realizar´a MIENTRAS que la condici´on se eval´ue
a Verdadera y finalizar´a cuando la condici´on se eval´ue a Falsa. Ejemplo:
#include <iostream>
using namespace std ;
int main ()
{
int num, divisor ;
cin >> num ;
if (num <= 1) {
divisor = 1 ;
} else {
divisor = 2 ;
while ((num % divisor) != 0) {
++divisor ;
}
}
cout << "El primer divisor de " << num << " es " << divisor << endl ;
}
La sentencia for es semejante a la estructura FOR de Pascal o Modula-2, aunque en C++
toma una dimensi´on m´as amplia y flexible. En realidad se trata de la misma construcci´on while
vista anteriormente pero con una sintaxis diferente para hacer m´as expl´ıcito los casos en los que
la iteraci´on est´a controlada por los valores que toma una determinada variable de control, de tal
forma que existe una clara inicializaci´on y un claro incremento de la variable de control hasta llegar
al caso final. La sintaxis es la siguiente:
int main()
{
for ( <inicializaci´on> ; <expresi´on_l´ogica> ; <incremento> ) {
<secuencia_de_sentencias> ;
}
}
y es equivalente a:
int main()
{
<inicializaci´on> ;
while ( <expresi´on_l´ogica> ) {
<secuencia_de_sentencias> ;
<incremento> ;
}
}
Nota: es posible y adecuado declarar e inicializar la variable de control del bucle en el lugar de la
inicializaci´on. En este caso especial, el ´ambito de visibilidad de la variable de control del bucle es
solamente hasta el final del bloque de la estructura for.
#include <iostream>
using namespace std ;
int main ()
{
int n ;
cin >> n ;
for (int i = 0 ; i < n ; ++i) {
cout << i << " " ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
4.6. PROGRAMACI ´ON ESTRUCTURADA 35
}
// i ya no es visible aqu´ı
cout << endl ;
}
La sentencia do-while presenta la siguiente estructura
int main()
{
do {
<secuencia_de_sentencias> ;
} while ( <expresi´on_l´ogica> ) ;
}
tambi´en expresa la iteraci´on en la ejecuci´on de la secuencia de sentencias, pero a diferencia de la
primera estructura iterativa ya vista, donde primero se eval´ua la expresi´on l´ogica y despu´es, en
caso de ser cierta, se ejecuta la secuencia de sentencias, en esta estructura se ejecuta primero la
secuencia de sentencias y posteriormente se eval´ua la expresi´on l´ogica, y si ´esta es cierta se repite
el proceso.
#include <iostream>
using namespace std;
int main ()
{
int num ;
do {
cin >> num ;
} while ((num % 2) != 0) ;
cout << "El n´umero par es " << num << endl ;
}
4.6. Programaci´on Estructurada
Un programa sigue una metodolog´ıa de programaci´on estructurada si todas las estructuras de
control que se utilizan (secuencia, selecci´on, iteraci´on y modularizaci´on) tienen un ´unico punto
de entrada y un ´unico punto de salida. Esta caracter´ıstica hace posible que se pueda aplicar la
abstracci´on para su dise˜no y desarrollo. La abstracci´on se basa en la identificaci´on de los elementos
a un determinado nivel, ignorando los detalles especificados en niveles inferiores. Un algoritmo que
use tan s´olo las estructuras de control tratadas en este tema, se denomina estructurado.
Bohm y Jacopini demostraron que todo problema computable puede resolverse usando ´unica-
mente estas estructuras de control. ´Esta es la base de la programaci´on estructurada. La abstracci´on
algor´ıtmica va un paso m´as all´a y considera que cada nivel de refinamiento corresponde con un
subprograma independiente
4.7. Ejemplos
Ejemplo 1
Programa que multiplica dos n´umeros mediante sumas acumulativas:
#include <iostream>
using namespace std ;
int main ()
{
cout << "Introduzca dos n´umeros: " ;
int m, n ;
cin >> m >> n ;
// Sumar: m+m+m+...+m (n veces)
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
36 CAP´ITULO 4. ESTRUCTURAS DE CONTROL
int total = 0 ;
for (int i = 0 ; i < n ; ++i) {
// Proceso iterativo: acumular el valor de ’m’ al total
total = total + m ; // total += m ;
}
cout << total << endl ;
}
Ejemplo 2
Programa que calcula el factorial de un n´umero dado:
#include <iostream>
using namespace std ;
int main ()
{
cout << "Introduzca un n´umero: " ;
int n ;
cin >> n ;
// Multiplicar: 1 2 3 4 5 6 7 ... n
int fact = 1 ;
for (int i = 2 ; i <= n ; ++i) {
// Proceso iterativo: acumular el valor de ’i’ al total
fact = fact * i ; // fact *= i ;
}
cout << fact << endl ;
}
Ejemplo 3
Programa que divide dos n´umeros mediante restas sucesivas:
#include <iostream>
using namespace std ;
int main ()
{
cout << "Introduzca dos n´umeros: " ;
int dividendo, divisor ;
cin >> dividendo >> divisor ;
if (divisor == 0) {
cout << "El divisor no puede ser cero" << endl ;
} else {
int resto = dividendo ;
int cociente = 0 ;
while (resto >= divisor) {
resto -= divisor ;
++cociente ;
}
cout << cociente << " " << resto << endl ;
}
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 5
Subprogramas. Funciones y
Procedimientos
La abstracci´on es una herramienta mental que nos permite analizar, comprender y construir sis-
temas complejos. As´ı, identificamos y denominamos conceptos abstractos y aplicamos refinamientos
sucesivos hasta comprender y construir el sistema completo.
Los subprogramas constituyen una herramienta del lenguaje de programaci´on que permite al
programador aplicar expl´ıcitamente la abstracci´on al dise˜no y construcci´on del software, propor-
cionando abstracci´on algor´ıtmica (procedimental) a la construcci´on de programas.
Los subprogramas pueden ser vistos como un mini programa encargado de resolver algor´ıtmi-
camente un subproblema que se encuentra englobado dentro de otro mayor. En ocasiones tambi´en
pueden ser vistos como una ampliaci´on o elevaci´on del conjunto de operaciones b´asicas (accio-
nes primitivas) del lenguaje de programaci´on, proporcion´andole nuevos mecanismos para resolver
nuevos problemas.
5.1. Funciones y Procedimientos
Los subprogramas codifican la soluci´on algor´ıtmica a un determinado problema, de tal forma
que cuando es necesario resolver dicho problema en un determinado momento de la computaci´on,
se invocar´a a una instancia del subprograma (mediante una llamada al subprograma) para que
resuelva el problema para unos determinados par´ametros. As´ı, en la invocaci´on al subprograma, se
transfiere la informaci´on que necesita para resolver el problema, entonces se ejecuta el c´odigo del
subprograma que resolver´a el problema y produce unos resultados que devuelve al lugar donde ha
sido requerido. Podemos distinguir dos tipos de subprogramas:
Procedimientos: encargados de resolver un problema computacional general. En el siguiente
ejemplo el procedimiento ordenar ordena los valores de las variables pasadas como par´amet-
ros (x e y), de tal forma que cuando termine el procedimiento, las variables x e y tendr´an el
menor y el mayor valor respectivamente de los valores originales:
int main()
{
int x = 8 ;
int y = 4 ;
ordenar(x, y) ;
cout << x << " " << y << endl ;
}
Funciones: encargadas de realizar un c´alculo computacional y generar un ´unico resultado,
normalmente calculado en funci´on de los datos recibidos. En el siguiente ejemplo, la fun-
ci´on calcular_menor calcula (y devuelve) el menor valor de los dos valores recibidos como
par´ametros:
37
38 CAP´ITULO 5. SUBPROGRAMAS. FUNCIONES Y PROCEDIMIENTOS
int main()
{
int x = 8 ;
int y = 4 ;
int z = calcular_menor(x, y) ;
cout << "Menor: " << z << endl ;
}
La llamada (invocaci´on) a un subprograma se realiza mediante el nombre seguido por los
par´ametros actuales entre par´entesis, considerando que:
La llamada a un procedimiento constituye por s´ı sola una sentencia independiente que puede
ser utilizada como tal en el cuerpo de otros subprogramas (y del programa principal).
La llamada a una funci´on no constituye por s´ı sola una sentencia, por lo que debe aparecer
dentro de alguna sentencia que utilice el valor resultado de la funci´on.
5.2. Definici´on de Subprogramas
Los subprogramas codifican la soluci´on algor´ıtmica parametrizada a un determinado problema,
es decir, especifica la secuencia de acciones a ejecutar para resolver un determinado problema
dependiendo de unos determinados par´ametros formales. Donde sea necesaria la resoluci´on de
dicho problema, se invocar´a a una instancia del subprograma para unos determinados par´ametros
actuales.
Considerando la norma de C++ de que antes de utilizar una determinada entidad en un pro-
grama, esta entidad deber´a estar previamente declarada, normalmente deberemos definir los sub-
programas en una posici´on previa a donde sean utilizados. No obstante, esta disposici´on de los
subprogramas puede ser alterada como se indica en la secci´on 5.7. La definici´on de los subprogra-
mas presentados anteriormente podr´ıa ser como se indica a continuaci´on:
#include <iostream>
using namespace std ;
int calcular_menor(int a, int b)
{
int menor ;
if (a < b) {
menor = a ;
} else {
menor = b ;
}
return menor ;
}
void ordenar(int& a, int& b)
{
if (a > b) {
int aux = a ;
a = b ;
b = aux ;
}
}
int main()
{
int x = 8 ;
int y = 4 ;
int z = calcular_menor(x, y) ;
cout << "Menor: " << z << endl ;
ordenar(x, y) ;
cout << x << " " << y << endl ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
5.3. EJECUCI ´ON DE SUBPROGRAMAS 39
La definici´on de un subprograma comienza con un encabezamiento en la que se especifica
en primer lugar el tipo del valor devuelto por ´este si es una funci´on, o void en caso de ser un
procedimiento. A continuaci´on vendr´a el nombre del subprograma seguido por la declaraci´on de
sus par´ametros formales entre par´entesis.
Los par´ametros formales especifican como se realiza la transferencia de informaci´on entre el
(sub)programa llamante y el subprograma llamado (v´ease 5.4). Normalmente la soluci´on de un
subproblema depender´a del valor de algunos datos, modificar´a el valor de otros datos, y posible-
mente generar´a nuevos valores. Todo este flujo de informaci´on se realiza a trav´es de los par´ametros
del subprograma.
El cuerpo del subprograma especifica la secuencia de acciones a ejecutar necesarias para resolver
el subproblema especificado, y podr´a definir tantas variables locales como necesite para desempe˜nar
su misi´on. En el caso de una funci´on, el valor que devuelve (el valor que toma tras la llamada)
vendr´a dado por el resultado de evaluar la expresi´on de la sentencia return. Aunque C++ es m´as
flexible, nosotros s´olo permitiremos una ´unica utilizaci´on de la sentencia return y deber´a ser al
final del cuerpo de la funci´on. As´ı mismo, un procedimiento no tendr´a ninguna sentencia return
en su cuerpo.
5.3. Ejecuci´on de Subprogramas
Cuando se produce una llamada (invocaci´on) a un subprograma:
1. Se establecen las v´ıas de comunicaci´on entre los algoritmos llamante y llamado por medio de
los par´ametros.
2. Posteriormente el flujo de ejecuci´on pasa a ejecutar la primera sentencia del cuerpo del
subprograma llamado, ejecut´andose ´este.
3. Cuando sea necesario, se crean las variables locales especificadas en el cuerpo de la definici´on
del subprograma.
4. Cuando finaliza la ejecuci´on del subprograma, las variables locales y par´ametros previamente
creados se destruyen, el flujo de ejecuci´on retorna al (sub)programa llamante, y contin´ua la
ejecuci´on por la sentencia siguiente a la llamada realizada.
5.4. Paso de Par´ametros. Par´ametros por Valor y por Re-
ferencia
Todo el intercambio y transferencia de informaci´on entre el programa llamante y el subprograma
llamado se debe realizar a trav´es de los par´ametros. Los par´ametros formales son los que aparecen
en la definici´on del subprograma, mientras que los par´ametros actuales son los que aparecen en la
llamada (invocaci´on) al subprograma.
Denominamos par´ametros de entrada a aquellos par´ametros que se utilizan para recibir la
informaci´on necesaria para realizar una computaci´on. Por ejemplo los par´ametros a y b de
la funci´on calcular_menor anterior.
• Los par´ametros de entrada se definen mediante paso por valor (cuando son de tipos
simples), que significa que los par´ametros formales son variables independientes que
toman sus valores iniciales como copias de los valores de los par´ametros actuales de la
llamada en el momento de la invocaci´on al subprograma. Se declaran especificando el
tipo y el identificador asociado.
int calcular_menor(int a, int b)
{
int menor ;
if (a < b) {
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
40 CAP´ITULO 5. SUBPROGRAMAS. FUNCIONES Y PROCEDIMIENTOS
menor = a ;
} else {
menor = b ;
}
return menor ;
}
• Cuando los par´ametros de entrada son de tipos compuestos (v´ease 6), entonces se definen
mediante paso por referencia constante que ser´a explicado m´as adelante.
Denominamos par´ametros de salida a aquellos par´ametros que se utilizan para transferir al
programa llamante informaci´on producida como parte de la computaci´on/soluci´on realizada
por el subprograma.
• Los par´ametros de salida se definen mediante paso por referencia que significa que el
par´ametro formal es una referencia a la variable que se haya especificado como par´ametro
actual de la llamada en el momento de la invocaci´on al subprograma. Es decir, cualquier
acci´on dentro del subprograma que se haga sobre el par´ametro formal es equivalente a
que se realice sobre la variable referenciada que aparece como par´ametro actual en la
llamada al subprograma. Se declaran especificando el tipo, el s´ımbolo “ampersand” (&)
y el identificador asociado.
En el siguiente ejemplo, el procedimiento dividir recibe dos valores sobre los cuales
realizar´a la operaci´on de divisi´on (dividendo y divisor son par´ametros de entrada y
son pasados por valor), y devuelve dos valores como resultado de la divisi´on (cociente
y resto son par´ametros de salida y son pasados por referencia):
void dividir(int dividendo, int divisor, int& coc, int& resto)
{
coc = dividendo / divisor ;
resto = dividendo % divisor ;
}
int main()
{
int cociente ;
int resto ;
dividir(7, 3, cociente, resto) ;
// ahora ’cociente’ valdr´a 2 y ’resto’ valdr´a 1
}
Denominamos par´ametros de entrada/salida a aquellos par´ametros que se utilizan para recibir
informaci´on necesaria para realizar la computaci´on, y que tras ser modificada se transfiere
al lugar de llamada como parte de la informaci´on producida resultado de la computaci´on del
subprograma. Por ejemplo los par´ametros a y b del procedimiento ordenar anterior.
Los par´ametros de entrada/salida se definen mediante paso por referencia y se declaran como
se especific´o anteriormente.
void ordenar(int& a, int& b)
{
if (a > b) {
int aux = a ;
a = b ;
b = aux ;
}
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
5.5. CRITERIOS DE MODULARIZACI ´ON 41
Tipos
Simples Compuestos
(⇓) Entrada
P.Valor
(int x)
P.Ref.Cte
(const Persona& x)
(⇑) Salida, ( ) E/S
P.Ref
(int& x)
P.Ref
(Persona& x)
En la llamada a un subprograma, se deben cumplir los siguientes requisitos:
El n´umero de par´ametros actuales debe coincidir con el n´umero de par´ametros formales.
La correspondencia entre par´ametros actuales y formales es posicional.
El tipo del par´ametro actual debe coincidir con el tipo del correspondiente par´ametro formal.
Un par´ametro formal de salida o entrada/salida (paso por referencia) requiere que el par´ametro
actual sea una variable.
Un par´ametro formal de entrada (paso por valor o paso por referencia constante) requiere
que el par´ametro actual sea una variable, constante o expresi´on.
Tipos Simples Tipos Compuestos
(⇓) Ent (⇑) Sal ( ) E/S (⇓) Ent (⇑) Sal ( ) E/S
Par´ametro Formal P.Valor P.Referencia P.Ref.Constante P.Referencia
(int x) (int& x) (const Persona& x) (Persona& x)
Constante Constante
Par´ametro Actual Variable Variable Variable Variable
Expresi´on Expresi´on
5.5. Criterios de Modularizaci´on
No existen m´etodos objetivos para determinar como descomponer la soluci´on de un problema
en subprogramas, es una labor subjetiva. No obstante, se siguen algunos criterios que pueden
guiarnos para descomponer un problema y modularizar adecuadamente. El dise˜nador de software
debe buscar un bajo acoplamiento entre los subprogramas y una alta cohesi´on dentro de cada
uno.
Acoplamiento: Un objetivo en el dise˜no descendente es crear subprogramas aislados e inde-
pendientes. Sin embargo, debe haber alguna conexi´on entre los subprogramas para formar un
sistema coherente. A dicha conexi´on se conoce como acoplamiento. Por lo tanto, maximizar
la independencia entre subprogramas ser´a minimizar el acoplamiento.
Cohesi´on: Hace referencia al grado de relaci´on entre las diferentes partes internas dentro
de un mismo subprograma. Si la cohesi´on es muy d´ebil, la diversidad entre las distintas
tareas realizadas dentro del subprograma es tal que posteriores modificaciones podr´an resultar
complicadas. Se busca maximizar la cohesi´on dentro de cada subprograma
Si no es posible analizar y comprender un subprograma de forma aislada e independiente del resto,
entonces podemos deducir que la divisi´on modular no es la m´as adecuada.
5.6. Subprogramas “en L´ınea” A
La llamada a un subprograma conlleva un peque˜no coste debido al control y gesti´on de la misma
que ocasiona cierta p´erdida de tiempo de ejecuci´on.
Hay situaciones en las que el subprograma es tan peque˜no que el coste asociado a la invocaci´on
es superior al coste asociado a computar la soluci´on del mismo, de tal forma que en estas situaciones
interesa eliminar el coste asociado a su invocaci´on. En ese caso se puede especificar que el subpro-
grama se traduzca como c´odigo en l´ınea en vez de como una llamada a un subprograma. Para ello
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
42 CAP´ITULO 5. SUBPROGRAMAS. FUNCIONES Y PROCEDIMIENTOS
se especificar´a la palabra reservada inline justo antes del tipo. De esta forma, se mantiene los
beneficios proporcionados por la abstracci´on, pero se eliminan los costes asociados a la invocaci´on.
inline int calcular_menor(int a, int b)
{
int menor ;
if (a < b) {
menor = a ;
} else {
menor = b ;
}
return menor ;
}
Este mecanismo s´olo es adecuado cuando el cuerpo del subprograma es muy peque˜no, de tal forma
que el coste asociado a la invocaci´on dominar´ıa respecto a la ejecuci´on del cuerpo del mismo.
5.7. Declaraci´on de Subprogramas. Prototipos A
Los subprogramas, al igual que los tipos, constantes y variables, deben ser declarados antes de
ser utilizados. Dicha declaraci´on se puede realizar de dos formas: una de ellas consiste simplemente
en definir el subprograma antes de utilizarlo. La otra posibilidad consiste en declarar el prototipo
del subprograma antes de su utilizaci´on, y definirlo posteriormente. El ´ambito de visibilidad del
subprograma ser´a global al fichero, es decir, desde el lugar donde ha sido declarado hasta el final
del fichero. Para declarar un subprograma habr´a que especificar el tipo del valor devuelto (o void
si es un procedimiento) seguido por el nombre y la declaraci´on de los par´ametros formales igual
que en la definici´on del subprograma, pero sin definir el cuerpo del mismo. En lugar de ello se
terminar´a la declaraci´on con el car´acter “punto y coma” (;).
int calcular_menor(int a, int b) ; // prototipo de ’calcular_menor’
int main()
{
int x = 8 ;
int y = 4 ;
int z = calcular_menor(x, y) ;
}
int calcular_menor(int a, int b) // definici´on de ’calcular_menor’
{
int menor ;
if (a < b) {
menor = a ;
} else {
menor = b ;
}
return menor ;
}
5.8. Sobrecarga de Subprogramas A
Se denomina sobrecarga cuando distintos subprogramas se denominan con el mismo identifi-
cador u operador, pero se aplican a par´ametros distintos. En el lenguaje de programaci´on C++
es posible sobrecargar tanto subprogramas como operadores siempre y cuando tengan par´ametros
diferentes, y el compilador pueda discriminar entre ellos por la especificaci´on de la llamada.
void imprimir(int x)
{
cout << "entero: " << x << endl ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
5.9. PRE-CONDICIONES Y POST-CONDICIONES 43
}
void imprimir(double x)
{
cout << "real: " << x << endl ;
}
inline double media(int x, int y, int z)
{
return double(x + y + z) / 3.0 ;
}
inline double media(int x, int y)
{
return double(x + y) / 2.0 ;
}
5.9. Pre-Condiciones y Post-Condiciones A
Pre-condici´on es un enunciado que debe ser cierto antes de la llamada a un subprograma.
Especifica las condiciones bajo las cuales se ejecutar´a dicho subprograma.
Post-condici´on es un enunciado que debe ser cierto tras la ejecuci´on de un subprograma.
Especifica el comportamiento de dicho subprograma.
Codificar las pre/post-condiciones mediante asertos proporciona una valiosa documentaci´on,
y tiene varias ventajas:
• Hace al programador expl´ıcitamente consciente de los prerrequisitos y del objetivo del
subprograma.
• Durante la depuraci´on, las pre-condiciones comprueban que la llamada al subprograma
se realiza bajo condiciones validas.
• Durante la depuraci´on, las post-condiciones comprueban que el comportamiento del
subprograma es adecuado.
• Sin embargo, a veces no es posible codificarlas f´acilmente.
En C++, las pre-condiciones y post-condiciones se pueden especificar mediante asertos, para los
cuales es necesario incluir la biblioteca cassert. Por ejemplo:
#include <iostream>
#include <cassert>
using namespace std ;
//---------------------------
void dividir(int dividendo, int divisor, int& cociente, int& resto)
{
assert(divisor != 0) ; // PRE-CONDICI´ON
cociente = dividendo / divisor ;
resto = dividendo % divisor ;
assert(dividendo == (divisor * cociente + resto)) ; // POST-CONDICI´ON
}
Nota: en GNU GCC es posible desactivar la comprobaci´on de asertos mediante la siguiente directiva
de compilaci´on:
g++ -DNDEBUG -ansi -Wall -Werror -o programa programa.cpp
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
44 CAP´ITULO 5. SUBPROGRAMAS. FUNCIONES Y PROCEDIMIENTOS
5.10. Ejemplos
Ejemplo 1
Ejemplo de un programa que imprime los n´umeros primos existentes entre dos valores le´ıdos
por teclado:
//- fichero: primos.cpp --------------------------------------------
#include <iostream>
using namespace std ;
void ordenar(int& menor, int& mayor)
{
if (mayor < menor) {
int aux = menor ;
menor = mayor ;
mayor = aux ;
}
}
inline bool es_divisible(int x, int y)
{
return ( x % y == 0 ) ;
}
bool es_primo(int x)
{
int i ;
for (i = 2 ; ((i <= x/2) && ( ! es_divisible(x, i))) ; ++i) {
// vac´ıo
}
return (i == x/2+1) ;
}
void primos(int min, int max)
{
cout << "N´umeros primos entre " << min << " y " << max << endl ;
for (int i = min ; i <= max ; ++i) {
if (es_primo(i)) {
cout << i << " " ;
}
}
cout << endl ;
}
int main()
{
int min, max ;
cout << "Introduzca el rango de valores " ;
cin >> min >> max ;
ordenar(min, max) ;
primos(min, max) ;
}
//- fin: primos.cpp ------------------------------------------------
Ejemplo 2
Ejemplo de un programa que convierte grados sexagesimales a radianes:
//- fichero: gradrad.cpp --------------------------------------------
#include <iostream>
#include <string>
using namespace std ;
// -- Constantes -------
const double PI = 3.1416 ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
5.10. EJEMPLOS 45
const int PI_GRAD = 180 ;
const int MIN_GRAD = 60 ;
const int SEG_MIN = 60 ;
const int SEG_GRAD = SEG_MIN * MIN_GRAD ;
// -- Subalgoritmos ----
void leer_grados (int& grad, int& min, int& seg)
{
cout << "Grados, minutos y segundos " ;
cin >> grad >> min >> seg ;
}
//---------------------------
void escribir_radianes (double rad)
{
cout << "Radianes: " << rad << endl ;
}
//---------------------------
double calc_rad (double grad_tot)
{
return (grad_tot * PI) / double(PI_GRAD) ;
}
//---------------------------
double calc_grad_tot (int grad, int min, int seg)
{
return double(grad) + (double(min) / double(MIN_GRAD)) + (double(seg) / double(SEG_GRAD)) ;
}
//---------------------------
double transf_gr_rad (int grad, int min, int seg)
{
double gr_tot = calc_grad_tot(grad, min, seg) ;
return calc_rad(gr_tot) ;
}
// -- Principal --------
int main ()
{
int grad, min, seg ;
leer_grados(grad, min, seg) ;
double rad = transf_gr_rad(grad, min, seg) ;
escribir_radianes(rad) ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
46 CAP´ITULO 5. SUBPROGRAMAS. FUNCIONES Y PROCEDIMIENTOS
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 6
Tipos Compuestos
Los tipos compuestos surgen de la composici´on y/o agregaci´on de otros tipos para formar nuevos
tipos de mayor entidad. Existen dos formas fundamentales para crear tipos de mayor entidad: la
composici´on de elementos, que denominaremos “Registros” o “Estructuras” y la agregaci´on de
elementos del mismo tipo, y se conocen como “Agregados”, “Arreglos” o mediante su nombre en
ingl´es “Arrays”. Adem´as de los tipos compuestos definidos por el programador mencionados ante-
riormente, los lenguajes de programaci´on suelen proporcionar alg´un tipo adicional para representar
las “cadenas de caracteres”.
6.1. Paso de Par´ametros de Tipos Compuestos
Los lenguajes de programaci´on normalmente utilizan el paso por valor y el paso por referencia
para implementar la transferencia de informaci´on entre subprogramas descrita en el interfaz. Para
la transferencia de informaci´on de entrada, el paso por valor supone duplicar y copiar el valor del
par´ametro actual en el formal. En el caso de tipos simples, el paso por valor es adecuado para la
transferencia de informaci´on de entrada, sin embargo, si el tipo de dicho par´ametro es compuesto, es
posible que dicha copia implique una alta sobrecarga, tanto en espacio de memoria como en tiempo
de ejecuci´on. El lenguaje de programaci´on C++ permite realizar de forma eficiente la transferencia
de informaci´on de entrada para tipos compuestos mediante el paso por referencia constante.
As´ı, en el paso por referencia constante el par´ametro formal es una referencia al par´ametro
actual especificado en la llamada, tomando as´ı su valor, pero no puede ser modificado al ser una
referencia constante, evitando de esta forma la sem´antica de salida asociada al paso por referencia.
El paso por referencia constante suele utilizarse para el paso de par´ametros de entrada con tipos
compuestos, ya que evita la duplicaci´on de memoria y la copia del valor, que en el caso de tipos
compuestos suele ser costosa. Para ello, los par´ametros se declaran como se especific´o anteriormente
para el paso por referencia, pero anteponiendo la palabra reservada const.
void imprimir(const Fecha& fech)
{
cout << fech.dia << (int(fech.mes)+1) << fech.anyo << endl ;
}
Tipos
Simples Compuestos
(⇓) Entrada
P.Valor
(int x)
P.Ref.Cte
(const Persona& p)
(⇑) Salida, ( ) E/S
P.Ref
(int& x)
P.Ref
(Persona& p)
47
48 CAP´ITULO 6. TIPOS COMPUESTOS
Funciones que Retornan Tipos Compuestos
Por la misma raz´on y como norma general, salvo excepciones, tampoco es adecuado que una
funci´on retorne un valor de tipo compuesto, debido a la sobrecarga que generalmente ´esto conlleva.
En estos casos, suele ser m´as adecuado que el subprograma devuelva el valor de tipo compuesto
como un par´ametro de salida mediante el paso por referencia.
6.2. Cadenas de Caracteres en C++: el Tipo String
Las cadenas de caracteres representan una sucesi´on o secuencia de caracteres. Es un tipo de
datos muy vers´atil, y es ´util para representar informaci´on muy diversa:
Informaci´on textual (caracteres)
Entrada de datos y salida de resultados en forma de secuencia de caracteres.
Informaci´on abstracta por medio de una secuencia de caracteres
Es posible utilizar el tipo string de la biblioteca est´andar para representar cadenas de caracteres
de longitud finita limitada por la implementaci´on. Para ello, se debe incluir la biblioteca est´andar
<string>, as´ı como utilizar el espacio de nombres de std. La definici´on de cadenas de carac-
teres mediante el tipo string permite definir cadenas de caracteres mas robustas y con mejores
caracter´ısticas que las cadenas de caracteres predefinidas al estilo-C (arrays de caracteres).
Es posible definir tanto constantes simb´olicas como variables y par´ametros de tipo string.
Una cadena de caracteres literal se representa mediante una sucesi´on de caracteres entre comillas
dobles. As´ı mismo, tambi´en es posible la asignaci´on de cadenas de caracteres:
AUTOR: J o s e L u i s
0 1 2 3 4 5 6 7 8
nombre: P e p e
0 1 2 3
nombre: J o s e L u i s
0 1 2 3 4 5 6 7 8
#include <iostream>
#include <string>
using namespace std ;
const string AUTOR = "Jos´e Luis" ;
int main()
{
string nombre = "Pepe" ;
// ...
nombre = AUTOR ;
}
Si no se le asigna un valor inicial a una variable de tipo string, entonces la variable tendr´a como
valor por defecto la cadena vac´ıa ("").
Entrada y Salida de Cadenas de Caracteres
El operador << aplicado a un flujo de salida (cout para el flujo de salida est´andar, usualmente
el terminal) permite mostrar el contenido de las cadenas de caracteres, tanto constantes como
variables. Por ejemplo:
#include <iostream>
#include <string>
using namespace std ;
const string AUTOR = "Jos´e Luis" ;
int main()
{
string nombre = "Pepe" ;
cout << "Nombre: " << nombre << " " << AUTOR << endl ;
}
El operador >> aplicado a un flujo de entrada (cin para el flujo de entrada est´andar, usualmente
el teclado) permite leer secuencias de caracteres y almacenarlas en variables de tipo string. Por
ejemplo:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6.2. CADENAS DE CARACTERES EN C++: EL TIPO STRING 49
#include <iostream>
#include <string>
using namespace std ;
int main()
{
cout << "Introduzca el nombre: " ;
string nombre ;
cin >> nombre ;
cout << "Nombre: " << nombre << endl ;
}
Este operador de entrada (>>) se comporta (como se especific´o en el cap´ıtulo 3.2 dedicado a
la Entrada y Salida b´asica) de la siguiente forma: elimina los espacios en blanco que hubiera al
principio de la entrada de datos, y lee dicha entrada hasta que encuentre alg´un car´acter de espacio
en blanco, que no ser´a le´ıdo y permanecer´a en el buffer de entrada (v´ease 3.3) hasta la pr´oxima
operaci´on de entrada. En caso de que durante la entrada surja alguna situaci´on de error, dicha
entrada se detiene y el flujo de entrada se pondr´a en un estado err´oneo. Se consideran espacios
en blanco los siguientes caracteres: espacio en blanco, tabuladores, retorno de carro y nueva l´ınea
(’ ’, ’t’, ’v’, ’f’, ’r’, ’n’).
Tambi´en es posible leer una l´ınea completa, hasta leer el car´acter de fin de l´ınea (ENTER),
desde el flujo de entrada, sin eliminar los espacios iniciales:
#include <iostream>
#include <string>
using namespace std ;
int main()
{
cout << "Introduzca el nombre: " ;
string nombre ;
getline(cin, nombre) ;
cout << "Nombre: " << nombre << endl ;
}
Tambi´en es posible leer una l´ınea completa, hasta leer un delimitador especificado, desde el
flujo de entrada, sin eliminar los espacios iniciales:
#include <iostream>
#include <string>
using namespace std ;
const char DELIMITADOR = ’.’ ;
int main()
{
cout << "Introduzca el nombre: " ;
string nombre ;
getline(cin, nombre, DELIMITADOR) ;
cout << "Nombre: " << nombre << endl ;
}
N´otese que realizar una operaci´on getline despu´es de una operaci´on con >> puede tener compli-
caciones, ya que >> dejara los espacios en blanco (y fin de l´ınea) en el buffer, que ser´an le´ıdos por
getline. Por ejemplo:
#include <iostream>
#include <string>
using namespace std ;
int main()
{
//-----------------------------------------------------
cout << "Introduzca n´umero: " ;
int n ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
50 CAP´ITULO 6. TIPOS COMPUESTOS
cin >> n ;
//-----------------------------------------------------
cout << "Introduzca el nombre: " ;
string nombre ;
getline(cin, nombre) ;
//-----------------------------------------------------
cout << "N´umero: " << n << " Nombre: [" << nombre << "]" << endl ;
}
Para evitar este problema, eliminaremos los caracteres de espacios en blanco (y fin de l´ınea) del
buffer de entrada antes de leer la entrada de datos con getline(...), de tal forma que leer´a una
secuencia de caracteres que sea distinta de la vac´ıa:
#include <iostream>
#include <string>
using namespace std ;
int main()
{
//-----------------------------------------------------
cout << "Introduzca n´umero: " ;
int n ;
cin >> n ;
//-----------------------------------------------------
cout << "Introduzca el nombre (NO puede ser vac´ıo): " ;
string nombre ;
ent >> ws ; // salta los espacios en blanco y fin de l´ınea
getline(cin, nombre) ; // leer´a la primera l´ınea no vac´ıa
//-----------------------------------------------------
cout << "N´umero: " << n << " Nombre: " << nombre << endl ;
}
Por el contrario, en caso de que la cadena vac´ıa sea una entrada v´alida posible, entonces ser´a nece-
sario eliminar el resto de caracteres (incluyendo los espacios en blanco y fin de l´ınea) del buffer de
entrada, despu´es de leer un dato con >>, de tal forma que el buffer est´e limpio antes de realizar la
entrada de la cadena de caracteres con getline. Por ejemplo:
#include <iostream>
#include <string>
#include <limits>
using namespace std ;
int main()
{
//-----------------------------------------------------
cout << "Introduzca n´umero: " ;
int n ;
cin >> n ;
cin.ignore(10000, ’n’) ; // elimina todos los caracteres del buffer hasta ’n’
//-----------------------------------------------------
cout << "Introduzca el nombre (puede ser vac´ıo): " ;
string nombre ;
getline(cin, nombre) ;
//-----------------------------------------------------
cout << "N´umero: " << n << " Nombre: " << nombre << endl ;
}
Operaciones con Cadenas de Caracteres
Las cadenas de caracteres se pueden asignar a variables de dicho tipo. Por ejemplo:
#include <iostream>
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6.2. CADENAS DE CARACTERES EN C++: EL TIPO STRING 51
#include <string>
using namespace std ;
const string AUTOR = "Jos´e Luis" ;
int main()
{
string nombre = "Pepe" ;
// ...
nombre = AUTOR ;
}
Es posible realizar la comparaci´on lexicogr´afica1
entre cadenas de caracteres del tipo string
mediante los operadores relacionales (==, !=, >, >=, <, <=). Por ejemplo:
• if (nombre >= AUTOR) { /*...*/ }
Es posible la concatenaci´on de cadenas y caracteres mediante los operadores de concatenaci´on
(+, +=):
#include <iostream>
#include <string>
using namespace std ;
const string AUTOR = "Jos´e Luis" ;
int main ()
{
string nombre = AUTOR + "L´opez" ;
nombre += "V´azque" ;
nombre += ’z’ ;
nombre = AUTOR + ’s’ ;
}
Para acceder al n´umero de caracteres que componen la cadena:
• unsigned ncar = nombre.size();
Comprobar si la cadena de caracteres est´a vac´ıa:
• if (nombre.size() == 0) { /*...*/ }
Para acceder al i-´esimo car´acter de la cadena (de tipo char):
• char c = nombre[i]; donde i ∈ [0..nombre.size()-1]
• nombre[i] = ’z’; donde i ∈ [0..nombre.size()-1]
Para acceder al i-´esimo car´acter de la cadena (de tipo char), comprobando que el valor del
´ındice (i) es adecuado, de tal forma que si el ´ındice (i) se encuentra fuera de rango, entonces
lanza la excepci´on out_of_range (abortar´a la ejecuci´on del programa):
• char c = nombre.at(i); donde i ∈ [0..nombre.size()-1].
• nombre.at(i) = ’z’; donde i ∈ [0..nombre.size()-1].
Obtener una nueva subcadena (de tipo string) a partir del ´ındice i, con un tama˜no especifi-
cado por sz. Si no se especifica el tama˜no, o (sz > nombre.size()-i), entonces se toma la
subcadena desde el ´ındice hasta el final. Si el ´ındice (i) se encuentra fuera de rango, entonces
lanza la excepci´on out_of_range (abortar´a la ejecuci´on del programa):
• string sb = nombre.substr(i); donde i ∈ [0..nombre.size()]
• string sb = nombre.substr(i, sz); donde i ∈ [0..nombre.size()]
• N´otese que no es v´alida la asignaci´on a una subcadena: nombre.substr(i, sz) = "...";
En GNU G++, la opci´on de compilaci´on -D_GLIBCXX_DEBUG permite comprobar los ´ındices
de acceso.
1Comparaci´on lexicogr´afica se basa en la ordenaci´on alfab´etica, y es com´unmente utilizada en los diccionarios.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
52 CAP´ITULO 6. TIPOS COMPUESTOS
Ejemplos
Ejemplo 1
Programa que convierte una cadena de caracteres a may´usculas:
#include <iostream>
#include <string>
using namespace std ;
// -- Subalgoritmos ----
void mayuscula (char& letra)
{
if ((letra >= ’a’) && (letra <= ’z’)) {
letra = char(letra - ’a’ + ’A’) ;
}
}
void mayusculas (string& palabra)
{
for (int i = 0 ; i < int(palabra.size()) ; ++i) {
mayuscula(palabra[i]) ;
}
}
// -- Principal --------
int main ()
{
string palabra ;
cin >> palabra ;
mayusculas(palabra) ;
cout << palabra << endl ;
}
Ejemplo 2
Programa que lee una palabra (formada por letras min´usculas), y escribe su plural seg´un las
siguientes reglas:
Si acaba en vocal se le a˜nade la letra ’s’.
Si acaba en consonante se le a˜naden las letras ’es’. Si la consonante es la letra ’z’, se sustituye por
la letra ’c’
Suponemos que la palabra introducida es correcta y est´a formada por letras min´usculas.
#include <iostream>
#include <string>
using namespace std ;
// -- Subalgoritmos ----
bool es_vocal (char c)
{
return (c == ’a’) || (c == ’e’) || (c == ’i’) || (c == ’o’) || (c == ’u’) ;
}
void plural_1 (string& palabra)
{
if (palabra.size() > 0) {
if (es_vocal(palabra[palabra.size() - 1])) {
palabra += ’s’ ;
} else {
if (palabra[palabra.size() - 1] == ’z’) {
palabra[palabra.size() - 1] = ’c’ ;
}
palabra += "es" ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6.2. CADENAS DE CARACTERES EN C++: EL TIPO STRING 53
}
}
void plural_2 (string& palabra)
{
if (palabra.size() > 0) {
if (es_vocal(palabra[palabra.size() - 1])) {
palabra += ’s’ ;
} else if (palabra[palabra.size() - 1] == ’z’) {
palabra = palabra.substr(0, palabra.size() - 1) + "ces" ;
} else {
palabra += "es" ;
}
}
}
// -- Principal --------
int main ()
{
string palabra ;
cin >> palabra ;
plural_1(palabra) ;
cout << palabra << endl ;
}
Ejemplo 3
Dise˜ne una funci´on que devuelva verdadero si la palabra recibida como par´ametro es “pal´ındro-
mo” y falso en caso contrario.
bool es_palindromo (const string& palabra)
{
bool ok = false ;
if (palabra.size() > 0) {
int i = 0 ;
int j = int(palabra.size()) - 1 ;
while ((i < j) && (palabra[i] == palabra[j])) {
++i ;
--j ;
}
ok = i >= j ;
}
return ok ;
}
Ejemplo 4
Dise˜ne un subprograma que reemplace una parte de la cadena, especificada por un ´ındice y una
longitud, por otra cadena.
void reemplazar (string& str, unsigned i, unsigned sz, const string& nueva)
{
if (i + sz < str.size()) {
str = str.substr(0, i) + nueva + str.substr(i + sz, str.size() - (i + sz)) ;
} else if (i <= str.size()) {
str = str.substr(0, i) + nueva ;
}
}
Este subprograma es equivalente a la operaci´on str.replace(i, sz, nueva)
str.replace(i, 0, nueva) es equivalente a str.insert(i, nueva)
str.replace(i, sz, "") es equivalente a str.erase(i, sz).
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
54 CAP´ITULO 6. TIPOS COMPUESTOS
6.3. Registros o Estructuras
El tipo registro se utiliza para la definici´on de un nuevo tipo mediante la composici´on de un
n´umero determinado de elementos que pueden ser de distintos tipos (simples y compuestos).
Un tipo registro se especifica enumerando los elementos (campos) que lo componen, indicando
su tipo y su identificador con el que referenciarlo. Una vez definido el tipo, podremos utilizar la
entidad (constante o variable) de dicho tipo como un todo o acceder a los diferentes elementos
que lo componen. Por ejemplo, podemos definir un nuevo tipo que represente el concepto de Fecha
como composici´on de d´ıa, mes y a˜no.
struct Fecha {
unsigned dia ;
unsigned mes ;
unsigned anyo ;
} ;
y posteriormente utilizarlo para definir constantes:
const Fecha f_nac = { 20 , 2, 2001} ;
o utilizarlo para definir variables:
Fecha f_nac ;
Los valores del tipo Fecha se componen de tres elementos concretos (el d´ıa de tipo unsigned, el
mes de tipo unsigned y el a˜no de tipo unsigned). Los identificadores dia, mes y anyo representan
los nombres de sus elementos componentes, denominados campos, y su ´ambito de visibilidad se
restringe a la propia definici´on del registro. Los campos de un registro pueden ser de cualquier tipo
de datos, simple o estructurado. Por ejemplo:
// -- Tipos ------------
struct Empleado {
string nombre ;
unsigned codigo ;
unsigned sueldo ;
Fecha fecha_ingreso ;
} ;
// -- Principal --------
int main ()
{
Empleado e ;
// ...
}
Una vez declarada una entidad (constante o variable) de tipo registro, por ejemplo la variable
f nac, podemos referirnos a ella en su globalidad (realizando asignaciones y pasos de par´ametros)
o acceder a sus componentes (campos) especific´andolos tras el operador punto (.), donde un
determinado componente podr´a utilizarse en cualquier lugar en que resulten v´alidas las variables
de su mismo tipo.
f nac
18
10
2001
hoy
18
10
2001
int main ()
{
Fecha f_nac, hoy ;
f_nac.dia = 18 ;
f_nac.mes = 10 ;
f_nac.anyo = 2001 ;
hoy = f_nac ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6.3. REGISTROS O ESTRUCTURAS 55
Ejemplo
#include <iostream>
#include <string>
using namespace std ;
// -- Constantes -------
const unsigned SEGMIN = 60 ;
const unsigned MINHOR = 60 ;
const unsigned MAXHOR = 24 ;
const unsigned SEGHOR = SEGMIN * MINHOR ;
// -- Tipos ------------
struct Tiempo {
unsigned horas ;
unsigned minutos ;
unsigned segundos ;
} ;
// -- Subalgoritmos ----
unsigned leer_rango (unsigned inf, unsigned sup)
{
unsigned num ;
do {
cin >> num ;
} while ( ! ((num >= inf) && (num < sup))) ;
return num ;
}
void leer_tiempo (Tiempo& t)
{
t.horas = leer_rango(0, MAXHOR) ;
t.minutos = leer_rango(0, MINHOR) ;
t.segundos = leer_rango(0, SEGMIN) ;
}
void escribir_tiempo (const Tiempo& t)
{
cout << t.horas << ":" << t.minutos << ":" << t.segundos ;
}
unsigned tiempo_a_seg (const Tiempo& t)
{
return (t.horas * SEGHOR) + (t.minutos * SEGMIN) + (t.segundos) ;
}
void seg_a_tiempo (unsigned sg, Tiempo& t)
{
t.horas = sg / SEGHOR ;
t.minutos = (sg % SEGHOR) / SEGMIN ;
t.segundos = (sg % SEGHOR) % SEGMIN ;
}
void diferencia (const Tiempo& t1, const Tiempo& t2, Tiempo& dif)
{
seg_a_tiempo(tiempo_a_seg(t2) - tiempo_a_seg(t1), dif) ;
}
// -- Principal --------
int main ()
{
Tiempo t1, t2, dif ;
leer_tiempo(t1) ;
leer_tiempo(t2) ;
diferencia(t1, t2, dif) ;
escribir_tiempo(dif) ;
cout << endl ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
56 CAP´ITULO 6. TIPOS COMPUESTOS
6.4. Agregados: el Tipo Array
El tipo array se utiliza para la definici´on de un nuevo tipo mediante la agregaci´on de entidades
menores del mismo tipo, es decir, se define como una colecci´on de un n´umero determinado (definido
en tiempo de compilaci´on) de elementos de un mismo tipo de datos, de tal forma que se puede
acceder a cada elemento individual de la colecci´on de forma parametrizada mediante ´ındices.
Los arrays son ´utiles en todas aquellas circunstancias en que necesitamos tener almacenados
una colecci´on de valores (un n´umero fijo predeterminado en tiempo de compilaci´on) a los cuales
pretendemos acceder de forma parametrizada, normalmente para aplicar un proceso iterativo.
Es posible utilizar el tipo array de la biblioteca est´andar para definir agregados. Para ello,
se debe incluir la biblioteca <tr1/array>, as´ı como utilizar el espacio de nombres de std::tr1.2
La definici´on de agregados de este tipo permite definir agregados mas robustos y con mejores
caracter´ısticas que los agregados predefinidos.
Un tipo agregado se especifica declarando el tipo base de los elementos que lo componen y el
n´umero de elementos (constante especificada en tiempo de compilaci´on) de que consta dicha agre-
gaci´on. As´ı, por ejemplo, podemos definir un nuevo tipo Vector como un agregado de 5 elementos,
cada uno del tipo int, y definir variables y constantes de dicho tipo (n´otese que los elementos
constantes del tipo array se especifican entre llaves dobles3
):
PRIMOS: 2 3 5 7 11
0 1 2 3 4
v: ? ? ? ? ?
0 1 2 3 4
#include <tr1/array>
using namespace std::tr1 ;
// -- Constantes -------
const int NELMS = 5 ;
// -- Tipos ------------
typedef array<int, NELMS> Vector ;
// -- Constantes -------
const Vector PRIMOS = {{ 2, 3, 5, 7, 11 }} ;
// -- Principal --------
int main ()
{
Vector v ;
}
El tipo base (de los elementos) del array puede ser de tipo simple o compuesto, as´ı, por ejemplo,
podemos definir un nuevo tipo Citas como un agregado de 4 elementos, cada uno del tipo Fecha,
y definir variables y constantes de dicho tipo:
CUMPLEANYOS:
1
1
2001
2
2
2002
3
3
2003
4
4
2004
0 1 2 3
#include <tr1/array>
using namespace std::tr1 ;
struct Fecha {
unsigned dia ;
unsigned mes ;
unsigned anyo ;
} ;
const int N_CITAS = 4 ;
typedef array<Fecha, N_CITAS> Citas ;
const Citas CUMPLEANYOS = {{
{ 1, 1, 2001 },
{ 2, 2, 2002 },
{ 3, 3, 2003 },
{ 4, 4, 2004 }
}} ;
int main()
{
Citas cit ;
// ...
cit = CUMPLEANYOS ;
}
2Cuando los compiladores se adapten al nuevo est´andar de C++ (2011), entonces se deber´a incluir la biblioteca
<array>, utilizar el espacio de nombres std y ser´a suficiente especificar los elementos entre llaves simples.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6.4. AGREGADOS: EL TIPO ARRAY 57
Un agregado de tipo array<...> acepta la asignaci´on (=) entre variables de dicho tipo. Tambi´en
se le pueden aplicar los operadores relacionales (==, !=, >, >=, <, <=) a entidades del mismo tipo
array<...> siempre y cuando a los elementos del agregado (del tipo base del array) se le puedan
aplicar dichos operadores.
Para conocer el n´umero de elementos que componen un determinado agregado, la operaci´on
cit.size() proporciona dicho valor, que en este ejemplo es 4.
Para acceder a un elemento concreto del agregado, especificaremos entre corchetes ([ y ])
el ´ındice de la posici´on que ocupa el mismo, teniendo en cuenta que el primer elemento ocupa
la posici´on 0 (cero) y el ´ultimo elemento ocupa la posici´on a.size()-1. Por ejemplo cit[0] y
cit[cit.size()-1] aluden al primer y ´ultimo elemento del agregado respectivamente. Un deter-
minado elemento puede utilizarse en cualquier lugar donde sea v´alido una variable de su mismo
tipo base.
El lenguaje de programaci´on C++ no comprueba que los accesos a los elementos de un agregado
sean correctos y se encuentren dentro de los l´ımites v´alidos del array, por lo que ser´a responsabilidad
del programador comprobar que as´ı sea.
Sin embargo, en GNU G++, la opci´on de compilaci´on -D_GLIBCXX_DEBUG permite comprobar
los ´ındices de acceso (descargar la biblioteca de p´agina web de la asignatura).
Tambi´en es posible acceder a un determinado elemento mediante la operaci´on at(i), de tal
forma que si el valor del ´ındice i est´a fuera del rango v´alido, entonces se lanzar´a una excepci´on
out_of_range. Se puede tanto utilizar como modificar el valor de este elemento.
#include <tr1/array>
using namespace std::tr1 ;
struct Fecha {
unsigned dia ;
unsigned mes ;
unsigned anyo ;
} ;
const int N_CITAS = 4 ;
typedef array<Fecha, N_CITAS> Citas ;
int main()
{
Citas cit ;
cit[0].dia = 18 ;
cit[0].mes = 10 ;
cit[0].anyo = 2001 ;
for (int i = 0 ; i < int(cit.size()) ; ++i) {
cit[i].dia = 1 ;
cit[i].mes = 1 ;
cit[i].anyo = 2002 ;
}
cit[N_CITAS] = { 1, 1, 2002 } ; // ERROR. Acceso fuera de los l´ımites
cit.at(N_CITAS) = { 1, 1, 2002 } ; // ERROR. Lanza excepci´on out_of_range
// ...
}
Ejemplo 1
#include <iostream>
#include <tr1/array>
using namespace std ;
using namespace std::tr1 ;
// -- Constantes -------
const int NELMS = 5 ;
// -- Tipos ------------
typedef array<int, NELMS> Vector ;
// -- Subalgoritmos ----
void leer (Vector& v)
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
58 CAP´ITULO 6. TIPOS COMPUESTOS
{
for (int i = 0 ; i < int(v.size()) ; ++i) {
cin >> v[i] ;
}
}
int sumar (const Vector& v)
{
int suma = 0 ;
for (int i = 0 ; i < int(v.size()) ; ++i) {
suma += v[i] ;
}
return suma ;
}
// -- Principal --------
int main ()
{
Vector v1, v2 ;
leer(v1) ;
leer(v2) ;
if (sumar(v1) == sumar(v2)) {
cout << "Misma suma" << endl ;
}
if (v1 < v2) {
cout << "Vector Menor" << endl ;
}
v1 = v2 ; // Asignaci´on
if (v1 == v2) {
cout << "Vectores Iguales" << endl ;
}
}
Ejemplo 2
Programa que lee las ventas de cada “agente” e imprime su sueldo que se calcula como una
cantidad fija (1000 C) m´as un incentivo que ser´a un 10 % de las ventas que ha realizado. Dicho
incentivo s´olo ser´a aplicable a aquellos agentes cuyas ventas superen los 2/3 de la media de ventas
del total de los agentes.
#include <iostream>
#include <tr1/array>
using namespace std ;
using namespace std::tr1 ;
// -- Constantes -------
const int NAGENTES = 20 ;
const double SUELDO_FIJO = 1000.0 ;
const double INCENTIVO = 10.0 ;
const double PROMEDIO = 2.0 / 3.0 ;
// -- Tipos ------------
typedef array<double, NAGENTES> Ventas ;
// -- Subalgoritmos ----
double calc_media (const Ventas& v)
{
double suma = 0.0 ;
for (int i = 0 ; i < int(v.size()) ; ++i) {
suma += v[i] ;
}
return suma / double(v.size()) ;
}
inline double porcentaje (double p, double valor)
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6.4. AGREGADOS: EL TIPO ARRAY 59
{
return (p * valor) / 100.0 ;
}
void leer_ventas (Ventas& v)
{
for (int i = 0 ; i < int(v.size()) ; ++i) {
cout << "Introduzca ventas del Agente " << i << ": " ;
cin >> v[i] ;
}
}
void imprimir_sueldos (const Ventas& v)
{
double umbral = PROMEDIO * calc_media(ventas) ;
for (int i = 0 ; i < int(v.size()) ; ++i) {
double sueldo = SUELDO_FIJO ;
if (v[i] >= umbral) {
sueldo += porcentaje(INCENTIVO, v[i]) ;
}
cout << "Agente: " << i << " Sueldo: " << sueldo << endl ;
}
}
// -- Principal --------
int main ()
{
Ventas ventas ;
leer_ventas(ventas) ;
imprimir_sueldos(ventas) ;
}
Agregados Incompletos
Hay situaciones donde un array se define en tiempo de compilaci´on con un tama˜no mayor que
el n´umero de elementos actuales v´alidos que contendr´a durante el Tiempo de Ejecuci´on.
Gestionar el array con huecos durante la ejecuci´on del programa suele ser, en la mayor´ıa de los casos,
complejo e ineficiente.
Mantener los elementos actuales v´alidos consecutivos al comienzo del array suele ser m´as adecuado:
• Marcar la separaci´on entre los elementos actuales v´alidos de los elementos vac´ıos con alg´un
valor de adecuado suele ser, en la mayor´ıa de los casos, complejo e ineficiente.
• Definir un registro que contenga tanto el array, como el n´umero de elementos actuales v´alidos
consecutivos que contiene suele ser m´as adecuado.
Ejemplo
#include <iostream>
#include <tr1/array>
using namespace std ;
using namespace std::tr1 ;
// -- Constantes -------
const int MAX_AGENTES = 20 ;
const double SUELDO_FIJO = 1000.0 ;
const double INCENTIVO = 10.0 ;
const double PROMEDIO = 2.0 / 3.0 ;
// -- Tipos ------------
typedef array<double, MAX_AGENTES> Datos ;
struct Ventas {
int nelms ;
Datos elm ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
60 CAP´ITULO 6. TIPOS COMPUESTOS
} ;
// -- Subalgoritmos ----
double calc_media (const Ventas& v)
{
double suma = 0.0 ;
for (int i = 0 ; i < v.nelms ; ++i) {
suma += v.elm[i] ;
}
return suma / double(v.nelms) ;
}
inline double porcentaje (double p, double valor)
{
return (p * valor) / 100.0 ;
}
void leer_ventas_ag (int i, double& v)
{
cout << "Introduzca ventas Agente " << i << ": " ;
cin >> v ;
}
// -----------------------------------
// Dos m´etodos diferentes de leer un
// vector incompleto:
// -----------------------------------
// M´etodo-1: cuando se conoce a priori el n´umero
// de elementos que lo componen
// -----------------------------------
void leer_ventas_2 (Ventas& v)
{
int nag ;
cout << "Introduzca total de agentes: " ;
cin >> nag ;
if (nag > int(v.elm.size())) {
v.nelms = 0 ;
cout << "Error" << endl ;
} else {
v.nelms = nag ;
for (int i = 0 ; i < v.nelms ; ++i) {
leer_ventas_ag(i, v.elm[i]) ;
}
}
}
// -----------------------------------
// M´etodo-2: cuando NO se conoce a priori el n´umero
// de elementos que lo componen, y este
// n´umero depende de la propia lectura de
// los datos
// -----------------------------------
void leer_ventas_1 (Ventas& v)
{
double vent_ag ;
v.nelms = 0 ;
leer_ventas_ag(v.nelms+1, vent_ag) ;
while ((v.nelms < int(v.elm.size()))&&(vent_ag > 0)) {
v.elm[v.nelms] = vent_ag ;
++v.nelms ;
leer_ventas_ag(v.nelms+1, vent_ag) ;
}
}
// -----------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6.4. AGREGADOS: EL TIPO ARRAY 61
void imprimir_sueldos (const Ventas& v)
{
double umbral = PROMEDIO * calc_media(ventas) ;
for (int i = 0 ; i < v.nelms ; ++i) {
double sueldo = SUELDO_FIJO ;
if (v.elm[i] >= umbral) {
sueldo += porcentaje(INCENTIVO, v.elm[i]) ;
}
cout << "Agente: " << i << " Sueldo: " << sueldo << endl ;
}
}
// -- Principal --------
int main ()
{
Ventas ventas ;
leer_ventas(ventas) ;
imprimir_sueldos(ventas) ;
}
Agregados Multidimensionales
El tipo Base de un array puede ser tanto simple como compuesto, por lo tanto puede ser otro
array, dando lugar a arrays con m´ultiples dimensiones. As´ı, cada elemento de un array puede ser
a su vez otro array.
Los agregados anteriormente vistos se denominan de una dimensi´on. As´ı mismo, es posible
declarar agregados de varias dimensiones. Un ejemplo de un agregado de dos dimensiones:
m:
0 00 01 02 03 04
1 10 11 12 13 14
2 20 21 22 23 24
0 1 2 3 4
#include <iostream>
#include <tr1/array>
using namespace std ;
using namespace std::tr1 ;
// -- Constantes -------
const int NFILAS = 3 ;
const int NCOLUMNAS = 5 ;
// -- Tipos ------------
typedef array<int, NCOLUMNAS> Fila ;
typedef array<Fila, NFILAS> Matriz ;
// -- Principal --------
int main ()
{
Matriz m ;
for (int f = 0 ; f < int(m.size()) ; ++f) {
for (int c = 0 ; c < int(m[f].size()) ; ++c) {
m[f][c] = (f * 10) + c ;
}
}
Matriz mx = m ; // asigna a mx los valores de la Matriz m
Fila fil = m[0] ; // asigna a fil el array con valores {{ 00, 01, 02, 03, 04 }}
int n = m[2][4] ; // asigna a n el valor 24
}
Donde m hace referencia a una variable de tipo Matriz, m[f] hace referencia a la fila f de la matriz
m (que es de tipo Fila), y m[f][c] hace referencia al elemento c de la fila f de la matriz m (que es
de tipo int). Del mismo modo, el n´umero de filas de la matriz m es igual a m.size(), y el n´umero
de elementos de la fila f de la matriz m es igual a m[f].size().
Ejemplo 1
Dise˜nar un programa que lea una matriz de 3×5 de n´umeros enteros (fila a fila), almacen´andolos
en un array bidimensional, finalmente imprima la matriz seg´un el siguiente formato:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
62 CAP´ITULO 6. TIPOS COMPUESTOS
a a a a a b
a a a a a b
a a a a a b
c c c c c
donde a representa los elementos de la matriz le´ıda desde el teclado, b representa el resultado de
sumar todos los elementos de la fila correspondiente, y c representa el resultado de sumar todos los
elementos de la columna donde se encuentran. N´otese en el ejemplo como es posible pasar como
par´ametro una ´unica fila, y sin embargo no es posible pasar como par´ametro una ´unica columna.
#include <iostream>
#include <tr1/array>
using namespace std ;
using namespace std::tr1 ;
// -- Constantes -------
const int NFILAS = 3 ;
const int NCOLUMNAS = 5 ;
// -- Tipos ------------
typedef array<int, NCOLUMNAS> Fila ;
typedef array<Fila, NFILAS> Matriz ;
// -- Subalgoritmos ----
int sumar_fila (const Fila& fil)
{
int suma = 0 ;
for (int c = 0 ; c < int(fil.size()) ; ++c) {
suma += fil[c] ;
}
return suma ;
}
int sumar_columna (const Matriz& m, int c)
{
int suma = 0 ;
for (int f = 0 ; f < int(m.size()) ; ++f) {
suma += m[f][c] ;
}
return suma ;
}
void escribir_fila (const Fila& fil)
{
for (int c = 0 ; c < int(fil.size()) ; ++c) {
cout << fil[c] << " " ;
}
}
void escribir_matriz_formato (const Matriz& m)
{
for (int f = 0 ; f < int(m.size()) ; ++f) {
escribir_fila(m[f]) ;
cout << sumar_fila(m[f]) ;
cout << endl ;
}
for (int c = 0 ; c < int(m[0].size()) ; ++c) {
cout << sumar_columna(m, c) << " " ;
}
cout << endl ;
}
void leer_matriz (Matriz& m)
{
cout << "Escribe fila a fila" << endl ;
for (int f = 0 ; f < int(m.size()) ; ++f) {
for (int c = 0 ; c < int(m[f].size()) ; ++c) {
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6.4. AGREGADOS: EL TIPO ARRAY 63
cin >> m[f][c] ;
}
}
}
// -- Principal --------
int main ()
{
Matriz m ;
leer_matriz(m) ;
escribir_matriz_formato(m) ;
}
Ejemplo 2
Dise˜ne un programa que realice el producto de 2 matrices de m´aximo 10 × 10 elementos:
#include <iostream>
#include <cassert>
#include <tr1/array>
using namespace std ;
using namespace std::tr1 ;
// -- Constantes -------
const int MAX = 10 ;
// -- Tipos ------------
typedef array<double, MAX> Fila ;
typedef array<Fila, MAX> Tabla ;
struct Matriz {
int n_fil ;
int n_col ;
Tabla datos ;
} ;
// -- Subalgoritmos ----
void leer_matriz (Matriz& m)
{
cout << "Dimensiones?: " ;
cin >> m.n_fil >> m.n_col ;
assert(m.n_fil <= int(m.datos.size()) && m.n_col <= int(m.datos[0].size())) ;
cout << "Escribe valores fila a fila:" << endl ;
for (int f = 0 ; f < m.n_fil ; ++f) {
for (int c = 0 ; c < m.n_col ; ++c) {
cin >> m.datos[f][c] ;
}
}
}
void escribir_matriz (const Matriz& m)
{
for (int f = 0 ; f < m.n_fil ; ++f) {
for (int c = 0 ; c < m.n_col ; ++c) {
cout << m.datos[f][c] << " " ;
}
cout << endl ;
}
}
double suma_fila_por_col (const Matriz& x, const Matriz& y, int f, int c)
{
assert(x.n_col == y.n_fil) ; // PRE-COND
double suma = 0.0 ;
for (int k = 0 ; k < x.n_col ; ++k) {
suma += x.datos[f][k] * y.datos[k][c] ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
64 CAP´ITULO 6. TIPOS COMPUESTOS
return suma ;
}
void mult_matriz (Matriz& m, const Matriz& a, const Matriz& b)
{
assert(a.n_col == b.n_fil) ; // PRE-COND
m.n_fil = a.n_fil ;
m.n_col = b.n_col ;
for (int f = 0 ; f < m.n_fil ; ++f) {
for (int c = 0 ; c < m.n_col ; ++c) {
m.datos[f][c] = suma_fil_por_col(a, b, f, c) ;
}
}
}
// -- Principal --------
int main ()
{
Matriz a,b,c ;
leer_matriz(a) ;
leer_matriz(b) ;
if (a.n_col != b.n_fil) {
cout << "No se puede multiplicar." << endl ;
} else {
mult_matriz(c, a, b) ;
escribir_matriz(c) ;
}
}
6.5. Resoluci´on de Problemas Utilizando Tipos Compuestos
Dise˜ne un programa para gestionar una agenda personal que contenga la siguiente informa-
ci´on: Nombre, Tel´efono, Direcci´on, Calle, N´umero, Piso, C´odigo Postal y Ciudad, y las siguientes
operaciones:
A˜nadir los datos de una persona.
Acceder a los datos de una persona a partir de su nombre.
Borrar una persona a partir de su nombre.
Modificar los datos de una persona a partir de su nombre.
Listar el contenido completo de la agenda.
#include <iostream>
#include <string>
#include <cassert>
#include <tr1/array>
using namespace std ;
using namespace std::tr1 ;
// -- Constantes -------
const int MAX_PERSONAS = 50 ;
// -- Tipos ------------
struct Direccion {
unsigned num ;
string calle ;
string piso ;
string cp ;
string ciudad ;
} ;
struct Persona {
string nombre ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6.5. RESOLUCI ´ON DE PROBLEMAS UTILIZANDO TIPOS COMPUESTOS 65
string tel ;
Direccion direccion ;
} ;
// -- Tipos ------------
typedef array<Persona, MAX_PERSONAS> Personas ;
struct Agenda {
int n_pers ;
Personas pers ;
} ;
enum Cod_Error {
OK, AG_LLENA, NO_ENCONTRADO, YA_EXISTE
} ;
// -- Subalgoritmos ----
void Inicializar (Agenda& ag)
{
ag.n_pers = 0 ;
}
//---------------------------
void Leer_Direccion (Direccion& dir)
{
cin >> dir.calle ;
cin >> dir.num ;
cin >> dir.piso ;
cin >> dir.cp ;
cin >> dir.ciudad ;
}
//---------------------------
void Escribir_Direccion (const Direccion& dir)
{
cout << dir.calle << " " ;
cout << dir.num << " " ;
cout << dir.piso << " " ;
cout << dir.cp << " " ;
cout << dir.ciudad << " " ;
}
//---------------------------
void Leer_Persona (Persona& per)
{
cin >> per.nombre ;
cin >> per.tel ;
Leer_Direccion(per.direccion) ;
}
//---------------------------
void Escribir_Persona (const Persona& per)
{
cout << per.nombre << " " ;
cout << per.tel << " " ;
Escribir_Direccion(per.direccion) ;
cout << endl ;
}
//---------------------------
// Busca una Persona en la Agenda
// Devuelve su posici´on si se encuentra, o bien >= ag.n_pers en otro caso
int Buscar_Persona (const string& nombre, const Agenda& ag)
{
int i = 0 ;
while ((i < ag.n_pers) && (nombre != ag.pers[i].nombre)) {
++i ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
66 CAP´ITULO 6. TIPOS COMPUESTOS
return i ;
}
//---------------------------
void Anyadir (Agenda& ag, const Persona& per)
{
assert(ag.n_pers < int(ag.pers.size())) ;
ag.pers[ag.n_pers] = per ;
++ag.n_pers ;
}
//---------------------------
void Eliminar (Agenda& ag, int pos)
{
assert(pos < ag.n_pers) ;
if (pos < ag.npers-1) {
ag.pers[pos] = ag.pers[ag.n_pers - 1] ;
}
--ag.n_pers ;
}
//---------------------------
void Anyadir_Persona (const Persona& per, Agenda& ag, Cod_Error& ok)
{
int i = Buscar_Persona(per.nombre, ag) ;
if (i < ag.n_pers) {
ok = YA_EXISTE ;
} else if (ag.n_pers >= int(ag.pers.size())) {
ok = AG_LLENA ;
} else {
ok = OK ;
Anyadir(ag, per) ;
}
}
//---------------------------
void Borrar_Persona (const string& nombre, Agenda& ag, Cod_Error& ok)
{
int i = Buscar_Persona(nombre, ag) ;
if (i >= ag.n_pers) {
ok = NO_ENCONTRADO ;
} else {
ok = OK ;
Eliminar(ag, i) ;
}
}
//---------------------------
void Modificar_Persona (const string& nombre, const Persona& nuevo, Agenda& ag, Cod_Error& ok)
{
int i = Buscar_Persona(nombre, ag) ;
if (i >= ag.n_pers) {
ok = NO_ENCONTRADO ;
} else {
Eliminar(ag, i) ;
Anyadir_Persona(nuevo, ag, ok) ;
}
}
//---------------------------
void Imprimir_Persona (const string& nombre, const Agenda& ag, Cod_Error& ok)
{
int i = Buscar_Persona(nombre, ag) ;
if (i >= ag.n_pers) {
ok = NO_ENCONTRADO ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6.5. RESOLUCI ´ON DE PROBLEMAS UTILIZANDO TIPOS COMPUESTOS 67
} else {
ok = OK ;
Escribir_Persona(ag.pers[i]) ;
}
}
//---------------------------
void Imprimir_Agenda (const Agenda& ag, Cod_Error& ok)
{
for (int i = 0 ; i < ag.n_pers ; ++i) {
Escribir_Persona(ag.pers[i]) ;
}
ok = OK ;
}
//---------------------------
char Menu ()
{
char opcion ;
cout << endl ;
cout << "a. - A~nadir Persona" << endl ;
cout << "b. - Buscar Persona" << endl ;
cout << "c. - Borrar Persona" << endl ;
cout << "d. - Modificar Persona" << endl ;
cout << "e. - Imprimir Agenda" << endl ;
cout << "x. - Salir" << endl ;
do {
cout << "Introduzca Opci´on: " ;
cin >> opcion ;
} while ( ! (((opcion >= ’a’) && (opcion <= ’e’)) || (opcion == ’x’))) ;
return opcion ;
}
//---------------------------
void Escribir_Cod_Error (Cod_Error cod)
{
switch (cod) {
case OK:
cout << "Operaci´on correcta" << endl ;
break ;
case AG_LLENA:
cout << "Agenda llena" << endl ;
break ;
case NO_ENCONTRADO:
cout << "La persona no se encuentra en la agenda" << endl ;
break ;
case YA_EXISTE:
cout << "La persona ya se encuentra en la agenda" << endl ;
break ;
}
}
// -- Principal --------
int main ()
{
Agenda ag ;
char opcion ;
Persona per ;
string nombre ;
Cod_Error ok ;
Inicializar(ag) ;
do {
opcion = Menu() ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
68 CAP´ITULO 6. TIPOS COMPUESTOS
switch (opcion) {
case ’a’:
cout << "Introduzca los datos de la Persona" << endl ;
cout << "(nombre, tel, calle, num, piso, cod_postal, ciudad)" << endl ;
Leer_Persona(per) ;
Anyadir_Persona(per, ag, ok) ;
Escribir_Cod_Error(ok) ;
break ;
case ’b’:
cout << "Introduzca Nombre" << endl ;
cin >> nombre ;
Imprimir_Persona(nombre, ag, ok) ;
Escribir_Cod_Error(ok) ;
break ;
case ’c’:
cout << "Introduzca Nombre" << endl ;
cin >> nombre ;
Borrar_Persona(nombre, ag, ok) ;
Escribir_Cod_Error(ok) ;
break ;
case ’d’:
cout << "Introduzca Nombre" << endl ;
cin >> nombre ;
cout << "Nuevos datos de la Persona" << endl ;
cout << "(nombre, tel, calle, num, piso, cod_postal, ciudad)" << endl ;
Leer_Persona(per) ;
Modificar_Persona(nombre, per, ag, ok) ;
Escribir_Cod_Error(ok) ;
break ;
case ’e’:
Imprimir_Agenda(ag, ok) ;
Escribir_Cod_Error(ok) ;
break ;
}
} while (opcion != ’x’ ) ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 7
B´usqueda y Ordenaci´on
Los algoritmos de b´usqueda de un elemento en una colecci´on de datos, as´ı como los algoritmos
de ordenaci´on, son muy utilizados com´unmente, por lo que merecen un estudio expl´ıcito. En el caso
de los algoritmos de b´usqueda, ´estos normalmente retornan la posici´on, dentro de la colecci´on de
datos, del elemento buscado, y en caso de no ser encontrado, retornan una posici´on no v´alida. Por
otra parte, los algoritmos de ordenaci´on organizan una colecci´on de datos de acuerdo con alg´un
criterio de ordenaci´on. Los algoritmos de ordenaci´on que se ver´an en este cap´ıtulo son los m´as
f´aciles de programar, pero sin embargo son los m´as ineficientes.
7.1. B´usqueda Lineal (Secuencial)
La b´usqueda lineal es adecuada como mecanismo de b´usqueda general en colecciones de datos
sin organizaci´on conocida. Consiste en ir recorriendo secuencialmente la colecci´on de datos hasta
encontrar el elemento buscado, o en ´ultima instancia recorrer toda la colecci´on completa, en cuyo
caso el elemento buscado no habr´a sido encontrado. En los siguientes ejemplos se presentan los
algoritmos b´asicos, los cuales pueden ser adaptados seg´un las circunstancias.
//--------------------------------
typedef array<int, MAXIMO> Vector ;
//--------------------------------
// busca la posici´on del primer elemento == x
// si no encontrado, retorna v.size()
//-------------
int buscar(int x, const Vector& v)
{
int i = 0 ;
while ((i < int(v.size()))&&(x != v[i])) {
++i ;
}
return i ;
}
//--------------------------------
7.2. B´usqueda Binaria
La b´usqueda binaria es adecuada como mecanismo de b´usqueda cuando las colecciones de datos
se encuentran ordenadas por alg´un criterio. Consiste en comprobar si el elemento buscado es igual,
menor o mayor que el elemento que ocupa la posici´on central de la colecci´on de datos, en caso de
ser mayor o menor que dicho elemento, se descartan los elementos no adecuados de la colecci´on de
datos, y se repite el proceso hasta encontrar el elemento o hasta que no queden elementos adecuados
69
70 CAP´ITULO 7. B ´USQUEDA Y ORDENACI ´ON
en la colecci´on, en cuyo caso el elemento no habr´a sido encontrado. En los siguientes ejemplos se
presentan los algoritmos b´asicos, los cuales pueden ser adaptados seg´un las circunstancias.
//--------------------------------
typedef array<int, MAXIMO> Vector ;
//--------------------------------
// busca la posici´on del primer elemento == x
// si no encontrado, retorna v.size()
//-------------
int buscar_bin(int x, const Vector& v)
{
int i = 0 ;
int f = int(v.size()) ;
int res = int(v.size()) ;
while (i < f) {
int m = (i + f) / 2 ;
if (x == v[m]) {
res = i = f = m ;
} else if (x < v[m]) {
f = m ;
} else {
i = m + 1 ;
}
}
return res ;
}
//--------------------------------
7.3. Ordenaci´on por Intercambio (Burbuja)
Se hacen m´ultiples recorridos sobre la zona no ordenada del array, ordenando los elementos
consecutivos, trasladando en cada uno de ellos al elemento m´as peque˜no hasta el inicio de dicha
zona.
//--------------------------------
typedef array<int, MAXIMO> Vector ;
//--------------------------------
inline void intercambio(int& x, int& y)
{
int a = x ;
x = y ;
y = a ;
}
//--------------------------------
void subir_menor(Vector& v, int pos)
{
for (int i = int(v.size())-1 ; i > pos ; --i) {
if (v[i] < v[i-1]) {
intercambio(v[i], v[i-1]) ;
}
}
}
//--------------------------------
void burbuja(Vector& v)
{
for (int pos = 0 ; pos < int(v.size())-1 ; ++pos) {
subir_menor(v, pos) ;
}
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
7.4. ORDENACI ´ON POR SELECCI ´ON 71
//--------------------------------
7.4. Ordenaci´on por Selecci´on
Se busca el elemento m´as peque˜no de la zona no ordenada del array, y se traslada al inicio
dicha zona, repitiendo el proceso hasta ordenar completamente el array.
//--------------------------------
typedef array<int, MAXIMO> Vector ;
//--------------------------------
inline void intercambio(int& x, int& y)
{
int a = x ;
x = y ;
y = a ;
}
//--------------------------------
int posicion_menor(const Vector& v, int pos)
{
int pos_menor = pos ;
for (int i = pos_menor+1 ; i < int(v.size()) ; ++i) {
if (v[i] < v[pos_menor]) {
pos_menor = i ;
}
}
return pos_menor ;
}
//--------------------------------
inline void subir_menor(Vector& v, int pos)
{
int pos_menor = posicion_menor(v, pos) ;
if (pos != pos_menor) {
intercambio(v[pos], v[pos_menor]) ;
}
}
//--------------------------------
void seleccion(Vector& v)
{
for (int pos = 0 ; pos < int(v.size())-1 ; ++pos) {
subir_menor(v, pos) ;
}
}
//--------------------------------
7.5. Ordenaci´on por Inserci´on
Se toma el primer elemento de la zona no ordenada del array, y se inserta en la posici´on
adecuada de la zona ordenada del array, repitiendo el proceso hasta ordenar completamente el
array.
//--------------------------------
typedef array<int, MAXIMO> Vector ;
//--------------------------------
int buscar_posicion(const Vector& v, int posicion)
{
int i = 0 ;
while (/*(i < posicion)&&*/ (v[posicion] > v[i])) {
++i ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
72 CAP´ITULO 7. B ´USQUEDA Y ORDENACI ´ON
}
return i ;
}
//--------------------------------
void abrir_hueco(Vector& v, int p_hueco, int p_elm)
{
for (int i = p_elm ; i > p_hueco ; --i) {
v[i] = v[i-1] ;
}
}
//--------------------------------
void insercion(Vector& v)
{
for (int pos = 1 ; pos < int(v.size()) ; ++pos) {
int p_hueco = buscar_posicion(v, pos) ;
if (p_hueco != pos) {
int aux = v[pos] ;
abrir_hueco(v, p_hueco, pos) ;
v[p_hueco] = aux ;
}
}
}
//--------------------------------
7.6. Aplicaci´on de los Algoritmos de B´usqueda y Ordenaci´on
Dise˜ne un programa para gestionar una agenda personal ordenada que contenga la siguiente
informaci´on: Nombre, Tel´efono, Direcci´on, Calle, N´umero, Piso, C´odigo Postal y Ciudad, y las
siguientes operaciones:
A˜nadir los datos de una persona.
Acceder a los datos de una persona a partir de su nombre.
Borrar una persona a partir de su nombre.
Modificar los datos de una persona a partir de su nombre.
Listar el contenido completo de la agenda.
#include <iostream>
#include <string>
#include <cassert>
#include <tr1/array>
using namespace std ;
using namespace std::tr1 ;
// -- Constantes -------
const int MAX_PERSONAS = 50 ;
// -- Tipos ------------
struct Direccion {
unsigned num ;
string calle ;
string piso ;
string cp ;
string ciudad ;
} ;
struct Persona {
string nombre ;
string tel ;
Direccion direccion ;
} ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
7.6. APLICACI ´ON DE LOS ALGORITMOS DE B ´USQUEDA Y ORDENACI ´ON 73
// -- Tipos ------------
typedef array<Persona, MAX_PERSONAS> Personas ;
struct Agenda {
int n_pers ;
Personas pers ;
} ;
enum Cod_Error {
OK, AG_LLENA, NO_ENCONTRADO, YA_EXISTE
} ;
// -- Subalgoritmos ----
void Inicializar (Agenda& ag)
{
ag.n_pers = 0 ;
}
//---------------------------
void Leer_Direccion (Direccion& dir)
{
cin >> dir.calle ;
cin >> dir.num ;
cin >> dir.piso ;
cin >> dir.cp ;
cin >> dir.ciudad ;
}
//---------------------------
void Escribir_Direccion (const Direccion& dir)
{
cout << dir.calle << " " ;
cout << dir.num << " " ;
cout << dir.piso << " " ;
cout << dir.cp << " " ;
cout << dir.ciudad << " " ;
}
//---------------------------
void Leer_Persona (Persona& per)
{
cin >> per.nombre ;
cin >> per.tel ;
Leer_Direccion(per.direccion) ;
}
//---------------------------
void Escribir_Persona (const Persona& per)
{
cout << per.nombre << " " ;
cout << per.tel << " " ;
Escribir_Direccion(per.direccion) ;
cout << endl ;
}
//---------------------------
// Busca una Persona en la Agenda Ordenada
// Devuelve su posici´on si se encuentra, o bien >= ag.n_pers en otro caso
int Buscar_Persona (const string& nombre, const Agenda& ag)
{
int i = 0 ;
int f = ag.n_pers ;
int res = ag.n_pers ;
while (i < f) {
int m = (i + f) / 2 ;
int cmp = nombre.compare(ag.pers[m].nombre) ;
if (cmp == 0) {
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
74 CAP´ITULO 7. B ´USQUEDA Y ORDENACI ´ON
res = i = f = m ;
} else if (cmp < 0) {
f = m ;
} else {
i = m + 1 ;
}
}
return res ;
}
//---------------------------
int Buscar_Posicion (const string& nombre, const Agenda& ag)
{
int i = 0 ;
while ((i < ag.n_pers) && (nombre > ag.pers[i].nombre)) {
++i ;
}
return i ;
}
//---------------------------
void Anyadir_Ord (Agenda& ag, int pos, const Persona& per)
{
for (int i = ag.n_pers ; i > pos ; --i) {
ag.pers[i] = ag.pers[i - 1] ;
}
ag.pers[pos] = per ;
++ag.n_pers ;
}
//---------------------------
void Eliminar_Ord (Agenda& ag, int pos)
{
--ag.n_pers ;
for (int i = pos ; i < ag.n_pers ; ++i) {
ag.pers[i] = ag.pers[i + 1] ;
}
}
//---------------------------
void Anyadir_Persona (const Persona& per, Agenda& ag, Cod_Error& ok)
{
int pos = Buscar_Posicion(per.nombre, ag) ;
if ((pos < ag.n_pers) && (per.nombre == ag.pers[pos].nombre)) {
ok = YA_EXISTE ;
} else if (ag.n_pers >= int(ag.pers.size())) {
ok = AG_LLENA ;
} else {
ok = OK ;
Anyadir_Ord(ag, pos, per) ;
}
}
//---------------------------
void Borrar_Persona (const string& nombre, Agenda& ag, Cod_Error& ok)
{
int i = Buscar_Persona(nombre, ag) ;
if (i >= ag.n_pers) {
ok = NO_ENCONTRADO ;
} else {
ok = OK ;
Eliminar_Ord(ag, i) ;
}
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
7.6. APLICACI ´ON DE LOS ALGORITMOS DE B ´USQUEDA Y ORDENACI ´ON 75
//---------------------------
void Modificar_Persona (const string& nombre, const Persona& nuevo, Agenda& ag, Cod_Error& ok)
{
int i = Buscar_Persona(nombre, ag) ;
if (i >= ag.n_pers) {
ok = NO_ENCONTRADO ;
} else {
ok = OK ;
Eliminar_Ord(ag, i) ;
Anyadir_Persona(nuevo, ag, ok) ;
}
}
//---------------------------
void Imprimir_Persona (const string& nombre, const Agenda& ag, Cod_Error& ok)
{
int i = Buscar_Persona(nombre, ag) ;
if (i >= ag.n_pers) {
ok = NO_ENCONTRADO ;
} else {
ok = OK ;
Escribir_Persona(ag.pers[i]) ;
}
}
//---------------------------
void Imprimir_Agenda (const Agenda& ag, Cod_Error& ok)
{
for (int i = 0 ; i < ag.n_pers ; ++i) {
Escribir_Persona(ag.pers[i]) ;
}
ok = OK ;
}
//---------------------------
char Menu ()
{
char opcion ;
cout << endl ;
cout << "a. - A~nadir Persona" << endl ;
cout << "b. - Buscar Persona" << endl ;
cout << "c. - Borrar Persona" << endl ;
cout << "d. - Modificar Persona" << endl ;
cout << "e. - Imprimir Agenda" << endl ;
cout << "x. - Salir" << endl ;
do {
cout << "Introduzca Opci´on: " ;
cin >> opcion ;
} while ( ! (((opcion >= ’a’) && (opcion <= ’e’)) || (opcion == ’x’))) ;
return opcion ;
}
//---------------------------
void Escribir_Cod_Error (Cod_Error cod)
{
switch (cod) {
case OK:
cout << "Operaci´on correcta" << endl ;
break ;
case AG_LLENA:
cout << "Agenda llena" << endl ;
break ;
case NO_ENCONTRADO:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
76 CAP´ITULO 7. B ´USQUEDA Y ORDENACI ´ON
cout << "La persona no se encuentra en la agenda" << endl ;
break ;
case YA_EXISTE:
cout << "La persona ya se encuentra en la agenda" << endl ;
break ;
}
}
// -- Principal --------
int main ()
{
Agenda ag ;
char opcion ;
Persona per ;
string nombre ;
Cod_Error ok ;
Inicializar(ag) ;
do {
opcion = Menu() ;
switch (opcion) {
case ’a’:
cout << "Introduzca los datos de la Persona"<<endl ;
cout << "(nombre, tel, calle, num, piso, cod_postal, ciudad)" << endl ;
Leer_Persona(per) ;
Anyadir_Persona(per, ag, ok) ;
Escribir_Cod_Error(ok) ;
break ;
case ’b’:
cout << "Introduzca Nombre" << endl ;
cin >> nombre ;
Imprimir_Persona(nombre, ag, ok) ;
Escribir_Cod_Error(ok) ;
break ;
case ’c’:
cout << "Introduzca Nombre" << endl ;
cin >> nombre ;
Borrar_Persona(nombre, ag, ok) ;
Escribir_Cod_Error(ok) ;
break ;
case ’d’:
cout << "Introduzca Nombre" << endl ;
cin >> nombre ;
cout << "Nuevos datos de la Persona" << endl ;
cout << "(nombre, tel, calle, num, piso, cod_postal, ciudad)" << endl ;
Leer_Persona(per) ;
Modificar_Persona(nombre, per, ag, ok) ;
Escribir_Cod_Error(ok) ;
break ;
case ’e’:
Imprimir_Agenda(ag, ok) ;
Escribir_Cod_Error(ok) ;
break ;
}
} while (opcion != ’x’ ) ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 8
Algunas Bibliotecas ´Utiles
En este cap´ıtulo se muestra superficialmente algunas funciones b´asicas de la biblioteca est´andar.
cmath
La biblioteca <cmath> proporciona principalmente algunas funciones matem´aticas ´utiles:
#include <cmath>
using namespace std ;
double sin(double r) ; seno, sin r (en radianes)
double cos(double r) ; coseno, cos r (en radianes)
double tan(double r) ; tangente, tan r (en radianes)
double asin(double x) ; arco seno, arcsin x, x ∈ [−1, 1]
double acos(double x) ; arco coseno, arc cos x, x ∈ [−1, 1]
double atan(double x) ; arco tangente, arctan x
double atan2(double y, double x) ; arco tangente, arctan y/x
double sinh(double r) ; seno hiperb´olico, sinh r
double cosh(double r) ; coseno hiperb´olico, cosh r
double tanh(double r) ; tangente hiperb´olica, tanh r
double sqrt(double x) ;
√
x, x ≥ 0
double pow(double x, double y) ; xy
double exp(double x) ; ex
double log(double x) ; logaritmo neperiano, ln x, x > 0
double log10(double x) ; logaritmo decimal, log x, x > 0
double ceil(double x) ; menor entero ≥ x, x
double floor(double x) ; mayor entero ≤ x, x
double fabs(double x) ; valor absoluto de x, |x|
double ldexp(double x, int n) ; x2n
double frexp(double x, int* exp) ; inversa de ldexp
double modf(double x, double* ip) ; parte entera y fraccionaria
double fmod(double x, double y) ; resto de x/y
cctype
La biblioteca <cctype> proporciona principalmente caracter´ısticas sobre los valores de tipo
char:
#include <cctype>
using namespace std ;
77
78 CAP´ITULO 8. ALGUNAS BIBLIOTECAS ´UTILES
bool isalnum(char ch) ; (isalpha(ch) || isdigit(ch))
bool isalpha(char ch) ; (isupper(ch) || islower(ch))
bool iscntrl(char ch) ; caracteres de control
bool isdigit(char ch) ; d´ıgito decimal
bool isgraph(char ch) ; caracteres imprimibles excepto espacio
bool islower(char ch) ; letra min´uscula
bool isprint(char ch) ; caracteres imprimibles incluyendo espacio
bool ispunct(char ch) ; carac. impr. excepto espacio, letra o d´ıgito
bool isspace(char ch) ; espacio, ’r’, ’n’, ’t’, ’v’, ’f’
bool isupper(char ch) ; letra may´uscula
bool isxdigit(char ch) ; d´ıgito hexadecimal
char tolower(char ch) ; retorna la letra min´uscula correspondiente a ch
char toupper(char ch) ; retorna la letra may´uscula correspondiente a ch
ctime
La biblioteca <ctime> proporciona principalmente algunas funciones generales relacionadas con
el tiempo:
#include <ctime>
using namespace std ;
clock_t clock() ; retorna el tiempo de CPU utilizado (CLOCKS_PER_SEC)
time_t time(0) ; retorna el tiempo de calendario (en segundos)
#include <iostream>
#include <ctime>
using namespace std ;
// -------------------------------------
int main()
{
time_t t1 = time(0) ;
clock_t c1 = clock() ;
// ... procesamiento ...
clock_t c2 = clock() ;
time_t t2 = time(0) ;
cout << "Tiempo de CPU: " << double(c2 - c1)/double(CLOCKS_PER_SEC) << " seg" << endl ;
cout << "Tiempo total: " << (t2 - t1) << " seg" << endl ;
}
// -------------------------------------
cstdlib
La biblioteca <cstdlib> proporciona principalmente algunas funciones generales ´utiles:
#include <cstdlib>
using namespace std ;
int abs(int n) ; retorna el valor absoluto del n´umero int n
long labs(long n) ; retorna el valor absoluto del n´umero long n
int system(const char orden[]) ; orden a ejecutar por el sistema operativo
void exit(int estado) ; termina la ejecuci´on del programa actual (EXIT_SUCCESS, EXIT_FAILURE)
void abort() ; aborta la ejecuci´on del programa actual
void srand(unsigned semilla) ; inicializa el generador de n´umeros aleatorios
int rand() ; retorna un aleatorio entre 0 y RAND_MAX (ambos inclusive)
#include <cstdlib>
#include <ctime>
using namespace std ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
79
// -------------------------------------
// inicializa el generador de n´umeros aleatorios
inline unsigned ini_aleatorio()
{
srand(time(0)) ;
}
// -------------------------------------
// Devuelve un n´umero aleatorio entre 0 y max (exclusive)
inline unsigned aleatorio(unsigned max)
{
return unsigned(max*double(rand())/(RAND_MAX+1.0)) ;
}
// -------------------------------------
// Devuelve un n´umero aleatorio entre min y max (ambos inclusive)
inline unsigned aleatorio(unsigned min, unsigned max)
{
return min + aleatorio(max-min+1) ;
}
// -------------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
80 CAP´ITULO 8. ALGUNAS BIBLIOTECAS ´UTILES
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Parte II
Programaci´on Intermedia
81
El lenguaje de programación c++
Cap´ıtulo 9
Almacenamiento en Memoria
Secundaria: Ficheros
Los programas de ordenador usualmente trabajan con datos almacenados en la memoria prin-
cipal (RAM). Esta memoria principal tiene como principales caracter´ısticas que tiene un tiempo
de acceso (para lectura y escritura) muy eficiente, sin embargo este tipo de memoria es vol´atil,
en el sentido de que los datos almacenados en ella desaparecen cuando termina la ejecuci´on del
programa o se apaga el ordenador. Los ordenadores normalmente almacenan su informaci´on de
manera permanente en dispositivos de almacenamiento de memoria secundaria, tales como dispos-
itivos magn´eticos (discos duros, cintas), discos ´opticos (CDROM, DVD), memorias permanentes
de estado s´olido (memorias flash USB), etc.
Estos dispositivos suelen disponer de gran capacidad de almacenamiento, por lo que es nece-
sario alguna organizaci´on que permita gestionar y acceder a la informaci´on all´ı almacenada. A
esta organizaci´on se la denomina el sistema de ficheros, y suele estar organizado jer´arquicamente
en directorios (a veces denominados tambi´en carpetas) y ficheros (a veces denominados tambi´en
archivos), donde los directorios permiten organizar jer´arquicamente1
y acceder a los ficheros, y estos
´ultimos almacenan de forma permanente la informaci´on, que puede ser tanto programas (software)
como datos que ser´an utilizados por los programas. As´ı, los programas acceden y almacenan la
informaci´on de manera permanente por medio de los ficheros, que son gestionados por el Sistema
Operativo dentro de la jerarqu´ıa del sistema de ficheros.
raiz
bin
gedit g++ agenda.cpp agenda agenda.txt
system.cfgsrc
Tipos de Ficheros
Los ficheros se pueden clasificar de m´ultiples formas dependiendo de los criterios seleccionados.
En nuestro caso, nos centraremos en la clasificaci´on por la codificaci´on o formato en el que al-
macenan la informaci´on que contienen. As´ı, podemos distinguir los ficheros de texto y los ficheros
binarios. En los ficheros de texto, la informaci´on se almacena utilizando una codificaci´on y formato
adecuados para que puedan ser procesados (y le´ıdos), adem´as de por un programa de ordenador,
por un ser humano. Por lo tanto, los ficheros de texto almacenan la informaci´on utilizando una
codificaci´on textual como secuencia de caracteres (usualmente basada en la codificaci´on ASCII,
1Un directorio puede contener m´ultiples ficheros y, a su vez, tambi´en m´ultiples directorios.
83
84 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS
UTF-8, etc), y en un formato que permita su legibilidad y procesamiento. Por otra parte, los
ficheros binarios son procesados autom´aticamente por la ejecuci´on de programas, sin la interven-
ci´on humana, por lo que no necesitan representar la informaci´on en un formato legible para el
ser humano. Por ello, suelen codificar la informaci´on en un formato orientado a ser procesado efi-
cientemente por los ordenadores, y en ese caso utilizan la representaci´on en el c´odigo binario que
utilizan internamente los ordenadores para representar los datos. En este caso, pueden surgir prob-
lemas de compatibilidad en aquellos casos en los que estos ficheros son procesados por programas
ejecut´andose en ordenadores que utilizan distintas representaciones internas de los datos.
As´ı por ejemplo, un fichero de texto denominado fechas.txt podr´ıa estar almacenado en una
determinada posici´on en la jerarqu´ıa del sistema de ficheros (/home/alumno/documentos/fechas.txt)
y contener informaci´on sobre las fechas de nacimiento de determinadas personas seg´un el siguiente
formato, donde cada l´ınea se encuentra terminada por un car´acter terminador de fin de l´ınea:2
Juan L´opez 12 3 1992
Lola Mart´ınez 23 7 1987
Pepe Jim´enez 17 8 1996
T´engase en cuenta que el ordenador almacena los n´umeros internamente en formato binario, y
que ´estos han sido convertidos a su representaci´on textual como secuencia de caracteres cuando
fueron escritos al fichero de texto, y que la conversi´on inversa se produce cuando se leen desde
el fichero de texto para su procesamiento. Por ejemplo, el n´umero 1992 se almacena en una vari-
able de tipo unsigned y se representa internamente como el siguiente n´umero binario de 32 bits
(00000000000000000000011111001000), sin embargo, su representaci´on textual en el fichero de texto se
compone de la siguiente secuencia de cuatro caracteres: ’1’ ’9’ ’9’ ’2’.
El contenido de un fichero de texto, adem´as de ser procesado por un programa espec´ıfico,
dise˜nado para su procesamiento considerando su formato, tambi´en puede ser visualizado y editado
mediante un programa de edici´on de textos de prop´osito general, tales como gedit, kate, gvim,
emacs, etc. en Linux, textedit en MacOS-X y notepad en Windows, entre otros.
En el caso del software, los programas en c´odigo fuente codificados en un lenguaje de progra-
maci´on suelen ser almacenados como ficheros de texto. Sin embargo, el resultado de compilar estos
programas fuente a programas ejecutables se almacenan en ficheros binarios (ejecutables por el
Sistema Operativo). As´ı mismo, los ficheros que contienen im´agenes, v´ıdeo y m´usica suelen estar,
en su mayor´ıa, almacenados en formato binario.
9.1. Flujos de Entrada y Salida Asociados a Ficheros
En el cap´ıtulo 3 se explic´o que un programa codificado en C++ realiza la entrada y salida
de informaci´on (con el entorno exterior del programa) a trav´es de los flujos (stream en ingl´es) de
entrada y salida respectivamente.
En dicho cap´ıtulo se muestra como se realiza la entrada y salida de datos a trav´es de los flujos
est´andares de entrada y salida respectivamente (cin y cout), usualmente conectados con el teclado
y la pantalla de la consola. Todo lo explicado anteriormente respecto a la entrada y salida b´asica
con los flujos est´andares (cap 3), y entrada y salida de cadenas de caracteres (cap 6.2) es tambi´en
aplicable a los flujos de entrada y salida vinculados a ficheros que veremos en este cap´ıtulo.
Un flujo de entrada de datos en modo texto act´ua como una fuente que proporciona una
secuencia de caracteres (usualmente a trav´es de un buffer de almacenamiento intermedio) desde
el que se extraen los caracteres que representan a los datos de entrada, que posteriormente ser´an
convertidos a la representaci´on interna adecuada.
Juan L´opez 12 3 1992 ← Lola Mart´ınez 23 7 1987 ← Pepe Jim´enez 17 8 1996 ←
Por el contrario, un flujo de salida de datos en modo texto act´ua como un sumidero que recibe
una secuencia de caracteres (usualmente a trav´es de un buffer de almacenamiento intermedio) a
2El car´acter terminador de fin de l´ınea no es visible, aunque se aprecian sus efectos al mostrarse los siguientes
caracteres en la siguiente l´ınea.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
9.2. ENTRADA DE DATOS DESDE FICHEROS DE TEXTO 85
la que se env´ıan los caracteres que representan a los datos de salida, que previamente han sido
convertidos al formato de texto adecuado.
Datos Datos
Datos DatosJuan 333
Maria 222
Pepe 111
Juan 555
Maria 666
Pepe 444
(cout)
(f_salida)
(cin)
(f_entrada)
PROGRAMA
agenda.txt
agenda.txt
En el caso de entrada y salida a ficheros, el lenguaje de programaci´on C++ posee mecanismos
para asociar y vincular estos flujos con ficheros almacenados en memoria secundaria en la jerarqu´ıa
del sistema de ficheros. As´ı, toda la entrada y salida de informaci´on se realiza a trav´es de estos flujos
vinculados a ficheros, denominados manejadores de ficheros. De este modo, cuando un programa
quiere realizar una entrada o salida de datos con un determinado fichero, debe realizar las siguientes
acciones:
1. Incluir la biblioteca <fstream> donde se definen los tipos correspondientes, y utilizar el espacio de
nombres std.
2. Declarar variables del tipo de flujo adecuado (para entrada o salida) para que act´uen como mane-
jadores de fichero.
3. Abrir el flujo de datos vinculando la variable correspondiente con el fichero especificado. Esta ope-
raci´on establece un v´ınculo entre la variable (manejador de fichero) definida en nuestro programa con
el fichero gestionado por el sistema operativo, de tal forma que toda transferencia de informaci´on que
el programa realice con el fichero, se realizar´a a trav´es de la variable manejador de fichero vinculada
con el mismo.
4. Comprobar que la apertura del fichero del paso previo se realiz´o correctamente. Si la vinculaci´on con
el fichero especificado no pudo realizarse por alg´un motivo (por ejemplo, el fichero no existe, en el
caso de entrada de datos, o no es posible crear el fichero, en el caso de salida de datos), entonces la
operaci´on de apertura fallar´ıa.
5. Realizar la transferencia de informaci´on (de entrada o de salida) con el fichero a trav´es de la variable
de flujo vinculada al fichero. Para esta transferencia de informaci´on (entrada y salida) se pueden
utilizar los mecanismos vistos en los cap´ıtulos anteriores (3, 6.2). En el caso de salida de datos, ´estos
deber´an escribirse siguiendo un formato adecuado que permita su posterior lectura, por ejemplo
escribiendo los separadores adecuados para ello entre los diferentes valores almacenados.
Normalmente, tanto la entrada como la salida de datos implican un proceso iterativo, que en el caso
de entrada se suele realizar hasta leer y procesar todo el contenido del fichero.
6. Comprobar que el procesamiento del fichero del paso previo se realiz´o correctamente, de tal forma
que si el procesamiento consist´ıa en entrada de datos, el estado de la variable vinculada al fichero se
encuentre en un estado indicando que se ha alcanzado el final del fichero, y si el procesamiento era
de salida, el estado de la variable vinculada al fichero se deber´a encontrar en un estado correcto.
7. Finalmente cerrar el flujo liberando la variable de su vinculaci´on con el fichero. Si no se cierra el
flujo de fichero, cuando termine el ´ambito de vida de la variable vinculada, el flujo ser´a cerrado
autom´aticamente, y su vinculaci´on liberada.
8. Nota: es importante tener en cuenta que cuando un flujo pasa al estado err´oneo (fail()), entonces
cualquier operaci´on de entrada o salida que se realice sobre ´el tambi´en fallar´a. La operaci´on clear()
restaura el estado del flujo.
Cuando sea necesario, una variable de tipo flujo, tanto de entrada, como de salida, puede ser
pasada como par´ametro por referencia (no constante) a cualquier subprograma.
9.2. Entrada de Datos desde Ficheros de Texto
Para realizar la entrada de datos desde un fichero de texto, el programa debe:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
86 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS
1. Incluir la biblioteca <fstream> donde se definen los tipos correspondientes, y utilizar el espacio de
nombres std.
#include <fstream>
using namespace std ;
2. Definir una variable manejador de fichero del tipo de flujo de entrada (ifstream –input file stream).
ifstream f_ent ;
3. Abrir el flujo vinculando la variable correspondiente con el fichero especificado.
f_ent.open(nombre_fichero.c_str()) ;
4. Comprobar que la apertura del fichero se realiz´o correctamente.
if (f_ent.fail()) { ... }
5. Realizar la entrada de datos con los operadores y subprogramas correspondientes, as´ı como procesar
la informaci´on le´ıda.
f_ent >> nombre >> apellidos >> dia >> mes >> anyo ;
f_ent.ignore(1000, ’n’) ;
f_ent >> ws ;
getline(f_ent, linea) ;
f_ent.get(c) ;
Usualmente es un proceso iterativo que se realiza hasta que la operaci´on de entrada de datos falla,
usualmente debido a haber alcanzado el final del fichero. Este proceso iterativo usualmente consiste
en la iteraci´on del siguiente proceso:
Lectura de datos
Si la lectura no ha sido correcta, entonces terminar el proceso iterativo.
En otro caso, procesamiento de los datos le´ıdos, y vuelta al proceso iterativo, leyendo nuevos
datos
{
...
leer(f_ent, datos) ;
while (! f_ent.fail() ... ) {
procesar(datos, ...) ;
leer(f_ent, datos) ;
}
}
6. Comprobar que el procesamiento del fichero se realiz´o correctamente, es decir, el fichero se ley´o com-
pletamente hasta el final de mismo (eof representa end-of-file).
if (f_ent.eof()) { ... }
7. Finalmente cerrar el flujo liberando la variable de su vinculaci´on.
f_ent.close() ;
Por ejemplo, un programa que lee n´umeros desde un fichero de texto y los procesa (en este caso
simplemente los muestra por pantalla):
#include <iostream>
#include <fstream>
#include <string>
using namespace std ;
enum Codigo {
OK, ERROR_APERTURA, ERROR_FORMATO
} ;
void procesar(int num)
{
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
9.3. SALIDA DE DATOS A FICHEROS DE TEXTO 87
cout << num << endl ;
}
void leer_fich(const string& nombre_fichero, Codigo& ok)
{
ifstream f_ent ;
f_ent.open(nombre_fichero.c_str()) ;
if (f_ent.fail()) {
ok = ERROR_APERTURA ;
} else {
int numero ;
f_ent >> numero ;
while (! f_ent.fail()) {
procesar(numero) ;
f_ent >> numero ;
}
if (f_ent.eof()) {
ok = OK ;
} else {
ok = ERROR_FORMATO ;
}
f_ent.close() ;
}
}
void codigo_error(Codigo ok)
{
switch (ok) {
case OK:
cout << "Fichero procesado correctamente" << endl ;
break ;
case ERROR_APERTURA:
cout << "Error en la apertura del fichero" << endl ;
break ;
case ERROR_FORMATO:
cout << "Error de formato en la lectura del fichero" << endl ;
break ;
}
}
int main()
{
Codigo ok ;
string nombre_fichero ;
cout << "Introduzca el nombre del fichero: " ;
cin >> nombre_fichero ;
leer_fich(nombre_fichero, ok) ;
codigo_error(ok) ;
}
9.3. Salida de Datos a Ficheros de Texto
Para realizar la salida de datos a un fichero de texto, el programa debe:
1. Incluir la biblioteca <fstream> donde se definen los tipos correspondientes, y utilizar el espacio de
nombres std.
#include <fstream>
using namespace std ;
2. Definir una variable manejador de fichero del tipo de flujo de salida (ofstream –output file stream).
ofstream f_sal ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
88 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS
3. Abrir el flujo vinculando la variable correspondiente con el fichero especificado.
f_sal.open(nombre_fichero.c_str()) ;
4. Comprobar que la apertura del fichero se realiz´o correctamente.
if (f_sal.fail()) { ... }
5. Realizar la salida de datos con los operadores y subprogramas correspondientes, teniendo en cuenta
los separadores que se deben escribir para que puedan ser le´ıdos adecuadamente.
f_sal << nombre << " " << apellidos << " " << dia << " " << mes << " " << anyo << endl ;
Usualmente ´este es un proceso iterativo que se realiza hasta que se escriben en el fichero todos los
datos apropiados y mientras el estado del flujo sea correcto.
while ( ... ! f_sal.fail() ) { ... }
6. Comprobar que el procesamiento del fichero se realiz´o correctamente, es decir, el fichero se encuentra
en buen estado.
if ( ! f_sal.fail()) { ... }
7. Finalmente cerrar el flujo liberando la variable de su vinculaci´on.
f_sal.close() ;
Por ejemplo, un programa que lee n´umeros de teclado (hasta introducir un cero) y los escribe
a un fichero de texto:
#include <iostream>
#include <fstream>
#include <string>
using namespace std ;
enum Codigo {
OK, ERROR_APERTURA, ERROR_FORMATO
} ;
void escribir_fich(const string& nombre_fichero, Codigo& ok)
{
ofstream f_sal ;
f_sal.open(nombre_fichero.c_str()) ;
if (f_sal.fail()) {
ok = ERROR_APERTURA ;
} else {
int numero ;
cin >> numero ;
while ((numero > 0) && ! cin.fail() && ! f_sal.fail()) {
f_sal << numero << endl ;
cin >> numero ;
}
if ( ! f_sal.fail()) {
ok = OK ;
} else {
ok = ERROR_FORMATO ;
}
f_sal.close() ;
}
}
void codigo_error(Codigo ok)
{
switch (ok) {
case OK:
cout << "Fichero guardado correctamente" << endl ;
break ;
case ERROR_APERTURA:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
9.4. EJEMPLOS 89
cout << "Error en la apertura del fichero" << endl ;
break ;
case ERROR_FORMATO:
cout << "Error de formato al escribir al fichero" << endl ;
break ;
}
}
int main()
{
Codigo ok ;
string nombre_fichero ;
cout << "Introduzca el nombre del fichero: " ;
cin >> nombre_fichero ;
escribir_fich(nombre_fichero, ok) ;
codigo_error(ok) ;
}
9.4. Ejemplos
Ejemplo 1
Ejemplo de un programa que copia el contenido de un fichero a otro, car´acter a car´acter:
#include <iostream>
#include <fstream>
#include <string>
using namespace std ;
enum Codigo {
OK, ERROR_APERTURA_ENT, ERROR_APERTURA_SAL, ERROR_FORMATO
} ;
void copiar_fichero(const string& salida, const string& entrada, Codigo& ok)
{
ifstream f_ent ;
f_ent.open(entrada.c_str()) ;
if (f_ent.fail()) {
ok = ERROR_APERTURA_ENT ;
} else {
ofstream f_sal ;
f_sal.open(salida.c_str()) ;
if (f_sal.fail()) {
ok = ERROR_APERTURA_SAL ;
} else {
char ch ;
f_ent.get(ch) ;
while (! f_ent.fail() && ! f_sal.fail()) {
f_sal.put(ch) ;
f_ent.get(ch) ;
}
if (f_ent.eof() && ! f_sal.fail()) {
ok = OK ;
} else {
ok = ERROR_FORMATO ;
}
f_sal.close() ; // no es necesario
}
f_ent.close() ; // no es necesario
}
}
void codigo_error(Codigo ok)
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
90 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS
{
switch (ok) {
case OK:
cout << "Fichero procesado correctamente" << endl ;
break ;
case ERROR_APERTURA_ENT:
cout << "Error en la apertura del fichero de entrada" << endl ;
break ;
case ERROR_APERTURA_SAL:
cout << "Error en la apertura del fichero de salida" << endl ;
break ;
case ERROR_FORMATO:
cout << "Error de formato en la lectura del fichero" << endl ;
break ;
}
}
int main()
{
Codigo ok ;
string entrada, salida ;
cout << "Introduzca el nombre del fichero de entrada: " ;
cin >> entrada ;
cout << "Introduzca el nombre del fichero de salida: " ;
cin >> salida ;
copiar_fichero(salida, entrada, ok) ;
codigo_error(ok) ;
}
Ejemplo 2
Ejemplo de un programa que crea, guarda y carga una agenda personal.
//-------------------------------------------------------------------------
#include <iostream>
#include <fstream>
#include <string>
#include <tr1/array>
#include <cctype>
using namespace std ;
using namespace std::tr1 ;
//-------------------------------------------------------------------------
struct Fecha {
unsigned dia ;
unsigned mes ;
unsigned anyo ;
} ;
struct Persona {
string nombre ;
string tfn ;
Fecha fnac ;
} ;
const int MAX = 100 ;
typedef array<Persona, MAX> APers ;
struct Agenda {
int nelms ;
APers elm ;
} ;
//-------------------------------------------------------------------------
void inic_agenda(Agenda& ag)
{
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
9.4. EJEMPLOS 91
ag.nelms = 0 ;
}
void anyadir_persona(Agenda& ag, const Persona& p, bool& ok)
{
if (ag.nelms < int(ag.elm.size())) {
ag.elm[ag.nelms] = p ;
++ag.nelms ;
ok = true ;
} else {
ok = false ;
}
}
//-------------------------------------------------------------------------
void leer_fecha(Fecha& f)
{
cout << "Introduza fecha de nacimiento (dia mes a~no): " ;
cin >> f.dia >> f.mes >> f.anyo ;
}
void leer_persona(Persona& p)
{
cout << "Introduza nombre: " ;
cin >> ws ;
getline(cin, p.nombre) ;
cout << "Introduza tel´efono: " ;
cin >> p.tfn ;
leer_fecha(p.fnac) ;
}
void nueva_persona(Agenda& ag)
{
bool ok ;
Persona p ;
leer_persona(p) ;
if (! cin.fail()) {
anyadir_persona(ag, p, ok) ;
if (!ok) {
cout << "Error al introducir la nueva persona" << endl ;
}
} else {
cout << "Error al leer los datos de la nueva persona" << endl ;
cin.clear() ;
cin.ignore(1000, ’n’) ;
}
}
//-------------------------------------------------------------------------
void escribir_fecha(const Fecha& f)
{
cout << f.dia << ’/’ << f.mes << ’/’ << f.anyo ;
}
void escribir_persona(const Persona& p)
{
cout << "Nombre: " << p.nombre << endl ;
cout << "Tel´efono: " << p.tfn << endl ;
cout << "Fecha nac: " ;
escribir_fecha(p.fnac) ;
cout << endl ;
}
void escribir_agenda(const Agenda& ag)
{
for (int i = 0 ; i < ag.nelms ; ++i) {
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
92 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS
cout << "----------------------------------------" << endl ;
escribir_persona(ag.elm[i]) ;
}
cout << "----------------------------------------" << endl ;
}
//-------------------------------------------------------------------------
// FORMATO DEL FICHERO DE ENTRADA:
//
// <nombre> <RC>
// <tel´efono> <dia> <mes> <a~no> <RC>
// <nombre> <RC>
// <tel´efono> <dia> <mes> <a~no> <RC>
// ...
//-------------------------------------------------------------------------
void leer_fecha(ifstream& fich, Fecha& f)
{
fich >> f.dia >> f.mes >> f.anyo ;
}
void leer_persona(ifstream& fich, Persona& p)
{
fich >> ws ;
getline(fich, p.nombre) ;
fich >> p.tfn ;
leer_fecha(fich, p.fnac) ;
}
//----------------------------------------------
// Otra posible implementaci´on
// void leer_persona(ifstream& fich, Persona& p)
// {
// getline(fich, p.nombre) ;
// fich >> p.tfn ;
// leer_fecha(fich, p.fnac) ;
// fich.ignore(1000, ’n’) ;
// }
//----------------------------------------------
void leer_agenda(const string& nombre_fich, Agenda& ag, bool& ok)
{
ifstream fich ;
Persona p ;
fich.open(nombre_fich.c_str()) ;
if (fich.fail()) {
ok = false ;
} else {
ok = true ;
inic_agenda(ag) ;
leer_persona(fich, p) ;
while (!fich.fail() && ok) {
anyadir_persona(ag, p, ok) ;
leer_persona(fich, p) ;
}
ok = ok && fich.eof() ;
fich.close() ;
}
}
void cargar_agenda(Agenda& ag)
{
bool ok ;
string nombre_fich ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
9.4. EJEMPLOS 93
cout << "Introduce el nombre del fichero: " ;
cin >> nombre_fich ;
leer_agenda(nombre_fich, ag, ok) ;
if (!ok) {
cout << "Error al cargar el fichero" << endl ;
}
}
//-------------------------------------------------------------------------
// FORMATO DEL FICHERO DE SALIDA:
//
// <nombre> <RC>
// <tel´efono> <dia> <mes> <a~no> <RC>
// <nombre> <RC>
// <tel´efono> <dia> <mes> <a~no> <RC>
// ...
//-------------------------------------------------------------------------
void escribir_fecha(ofstream& fich, const Fecha& f)
{
fich << f.dia << ’ ’ << f.mes << ’ ’ << f.anyo ;
}
void escribir_persona(ofstream& fich, const Persona& p)
{
fich << p.nombre << endl ;
fich << p.tfn << ’ ’ ;
escribir_fecha(fich, p.fnac) ;
fich << endl ;
}
void escribir_agenda(const string& nombre_fich, const Agenda& ag, bool& ok)
{
ofstream fich ;
fich.open(nombre_fich.c_str()) ;
if (fich.fail()) {
ok = false ;
} else {
int i = 0 ;
while ((i < ag.nelms) && (! fich.fail())) {
escribir_persona(fich, ag.elm[i]) ;
++i ;
}
ok = ! fich.fail() ;
fich.close() ;
}
}
void guardar_agenda(const Agenda& ag)
{
bool ok ;
string nombre_fich ;
cout << "Introduce el nombre del fichero: " ;
cin >> nombre_fich ;
escribir_agenda(nombre_fich, ag, ok) ;
if (!ok) {
cout << "Error al guardar el fichero" << endl ;
}
}
//-------------------------------------------------------------------------
char menu()
{
char op ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
94 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS
cout << endl ;
cout << "C. Cargar Agenda" << endl ;
cout << "M. Mostrar Agenda" << endl ;
cout << "N. Nueva Persona" << endl ;
cout << "G. Guardar Agenda" << endl ;
cout << "X. Fin" << endl ;
do {
cout << endl << " Opci´on: " ;
cin >> op ;
op = char(toupper(op)) ;
} while (!((op == ’C’)||(op == ’M’)||(op == ’N’)||(op == ’G’)||(op == ’X’))) ;
cout << endl ;
return op ;
}
//-------------------------------------------------------------------------
int main()
{
Agenda ag ;
char op ;
inic_agenda(ag) ;
do {
op = menu() ;
switch (op) {
case ’C’:
cargar_agenda(ag) ;
break ;
case ’M’:
escribir_agenda(ag) ;
break ;
case ’N’:
nueva_persona(ag) ;
break ;
case ’G’:
guardar_agenda(ag) ;
break ;
}
} while (op != ’X’) ;
}
//-------------------------------------------------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 10
M´odulos y Bibliotecas
Cuando se desarrollan programas de complejidad media/alta, el c´odigo fuente normalmente
no se encuentra en un ´unico fichero, sino que se encuentra distribuido entre varios m´odulos. Una
primera ventaja de la existencia de m´odulos es que permiten aumentar la localidad y cohesi´on del
c´odigo y aislarlo del exterior, es decir, poner todo el c´odigo encargado de resolver un determinado
problema en un m´odulo nos permite aislarlo del resto, con lo que futuras modificaciones ser´an m´as
f´aciles de realizar.
Otra ventaja adicional de los m´odulos es el hecho de que si se modifica algo interno en un
determinado m´odulo, s´olo ser´a necesario volver a compilar dicho m´odulo, y no todo el programa
completo, lo que se convierte en una ventaja indudable en caso de programas grandes (compilaci´on
separada).
Adem´as, esta divisi´on modular es una pieza fundamental para la reutilizaci´on del c´odigo, ya que
permite la utilizaci´on de bibliotecas del sistema, as´ı como la creaci´on y distribuci´on de bibliotecas
de utilidades que podr´an ser utilizadas por m´ultiples programas. Esta distribuci´on de bibliotecas
se puede hacer en c´odigo objeto, por lo que no es necesario distribuir el c´odigo fuente de la misma.
GUI MP3 JPG
I/O
Kernel S.O.
ScktsFile
Math
Mem Proc
M2M1 Main Programa
API S.O.
S.O.
Bbl. Utilidades
As´ı, vemos que en la figura un determinado programa se compone de varios m´odulos de progra-
ma (Main, M1 y M2) en los cuales est´a dividida la soluci´on principal del problema, varios m´odulos
de bibliotecas proporcionan utilidades gr´aficas, matem´aticas y tratamiento de im´agenes ; as´ı como
varios m´odulos de biblioteca dan acceso a servicios de entrada/salida y comunicaciones por Internet
proporcionados por el sistema operativo.
10.1. Interfaz e Implementaci´on del M´odulo
En el lenguaje de programaci´on C++, normalmente un m´odulo se compone de dos ficheros:
uno donde aparece el c´odigo que resuelve un determinado problema o conjunto de problemas
(implementaci´on – parte privada), y un fichero que contiene las definiciones de tipos, constantes y
prototipos de subprogramas que el m´odulo ofrece (interfaz – parte p´ublica). As´ı, se denomina la
implementaci´on del m´odulo al fichero que contiene la parte privada del m´odulo, y se denomina la
interfaz del m´odulo al fichero que contiene la parte p´ublica del mismo. A este fichero tambi´en se
le denomina “fichero de encabezamiento” o “fichero de cabecera” (header file en ingl´es).
95
96 CAP´ITULO 10. M ´ODULOS Y BIBLIOTECAS
main.cpp (Principal)
#include "complejo.hpp"
// utilizaci´on de complejo
int main()
{
· · ·
}
complejo.hpp (Interfaz)
#ifndef complejo hpp
#define complejo hpp
// interfaz de complejo
// p´ublico
· · ·
#endif
complejo.cpp (Implementaci´on)
#include "complejo.hpp"
// implementaci´on de complejo
// privado
· · ·
· · ·
· · ·
Por lo tanto, un programa completo normalmente se compone de varios m´odulos, cada uno
con su fichero de encabezamiento (interfaz) y de implementaci´on, y de un m´odulo principal donde
reside la funci´on principal main. Para que un determinado m´odulo pueda hacer uso de las uti-
lidades que proporciona otro m´odulo, deber´a incluir el fichero de encabezamiento (interfaz) del
m´odulo que se vaya a utilizar, de tal forma que tenga acceso a las declaraciones p´ublicas de ´este.
As´ı mismo, el fichero de implementaci´on de un determinado m´odulo tambi´en deber´a especificar al
comienzo del mismo la inclusi´on del fichero de encabezamiento de su propio m´odulo, con objeto
de obtener y contrastar las definiciones all´ı especificadas, es decir, el fichero de encabezamiento del
propio m´odulo debe ser el primer fichero que se incluya en el fichero de implementaci´on del mis-
mo m´odulo. Normalmente los ficheros de implementaci´on tendr´an una extensi´on “.cpp” (tambi´en
suelen utilizarse otras extensiones como “.cxx” y “.cc”) y los ficheros de encabezamiento tendr´an
una extensi´on “.hpp” (tambi´en suelen utilizarse otras extensiones como “.hxx”, “.hh” y “.h”). As´ı,
para incluir el fichero de encabezamiento (interfaz) de un m´odulo complejo se utiliza la siguiente
directiva:
#include "complejo.hpp"
N´otese que cuando se incluyen ficheros de encabezamiento (interfaz) de la biblioteca est´andar (o
del sistema), el nombre del fichero se especifica entre los siguientes s´ımbolos <...>, pero cuando
se incluyen ficheros de encabezamiento de m´odulos y bibliotecas locales (no est´andares), entonces
el nombre del fichero se especifica entre los siguientes s´ımbolos "...". De esta forma, los ficheros
de la biblioteca est´andar y del sistema se buscan en directorios del sistema, pero los ficheros y
bibliotecas locales se buscan en el directorio local de trabajo.
Guardas en un Fichero de Encabezamiento
Las definiciones en los ficheros de encabezamiento (interfaz) ser´an especificadas entre las guardas
(directivas de compilaci´on condicional) para evitar la inclusi´on duplicada de las definiciones all´ı con-
tenidas. El nombre de la guarda usualmente se deriva del nombre del fichero, como se indica en
el siguiente ejemplo donde el m´odulo complejo tendr´a los siguientes ficheros de encabezamiento
y de implementaci´on (en determinadas circunstancias, puede ser conveniente que al nombre de
la guarda se le a˜nada tambi´en el nombre del espacio de nombres que se explicar´a en la siguiente
secci´on):
Fichero: complejo.hpp (Interfaz) Fichero: complejo.cpp (Implementaci´on)
// Guarda para evitar inclusi´on duplicada
#ifndef _complejo_hpp_
#define _complejo_hpp_
// Definiciones P´ublicas de:
// * Constantes
// * Tipos (Enum, Registros, Clases)
// * Prototipos de Subprogramas
#endif // Fin de guarda
#include "complejo.hpp"
// Implementaciones Privadas de:
// * Constantes Privadas
// * Tipos Privados
// * Subprogramas
// * Clases
Directrices para el Dise˜no de Ficheros de Encabezamiento
Las siguientes directrices deben ser tenidas en cuenta con objeto de organizar adecuadamente
el dise˜no de los ficheros de encabezamiento de los m´odulos:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
10.2. COMPILACI ´ON SEPARADA Y ENLAZADO 97
Un fichero de encabezamiento s´olo deber´a contener definiciones de constantes, definiciones
de tipos y prototipos de subprogramas que exporta (parte p´ublica) el propio m´odulo. No
deber´a contener definiciones de variables globales, ni la implementaci´on de c´odigo (de sub-
programas y m´etodos). Esto ´ultimo salvo algunas excepciones, tales como la definici´on de
subprogramas simples “en l´ınea” (v´ease 5.6) y la definici´on de subprogramas y clases gen´ericas
(v´ease 12).
El mecanismo de inclusi´on de ficheros de encabezamiento debe ser robusto ante posibles
inclusiones duplicadas. Para ello siempre se utilizar´a el mecanismo de guardas explicado
anteriormente.
Un fichero de encabezamiento debe incluir todos los ficheros de encabezamiento de otros
m´odulos que necesite para su propia definici´on, de forma tal que el orden de inclusi´on de los
ficheros de encabezamiento no sea importante.
10.2. Compilaci´on Separada y Enlazado
main.cpp
Programa
Principal
C.Fuente C++
string
Interfaz
Público
C.Fuente C++
iostream
Interfaz
Público
C.Fuente C++
C.Objeto
main.o
Programa
Principal
bblstd.o
Público
Implementación
C.Objeto
bblstd.cpp
Implementación
Privado
C.Fuente C++
C.Ejecutable
main
Programa
Completo
mat.hpp
C.Fuente C++
Interfaz +
Implementación
Público
Interfaz
Público
C.Fuente C++
complejo.hpp
Privado
Implementación
C.Fuente C++
complejo.cpp
complejo.o
Público
Implementación
C.Objeto
Compilación CompilaciónCompilación
Enlazado
Cuando se compila un m´odulo de forma independiente (compilaci´on separada), se compila su
fichero de implementaci´on, por ejemplo complejo.cpp, y produce como resultado un fichero en
c´odigo objeto, por ejemplo complejo.o, considerando que el c´odigo fuente en C++ compilado es el
contenido del fichero de implementaci´on junto con el contenido de todos los ficheros de encabeza-
miento incluidos durante el proceso de compilaci´on. Por ejemplo, mediante el siguiente comando
se compila un m´odulo de implementaci´on de c´odigo fuente en C++ utilizando el compilador GNU
GCC para generar el correspondiente c´odigo objeto:
g++ -ansi -Wall -Werror -c complejo.cpp
g++ -ansi -Wall -Werror -c main.cpp
y el enlazado de los c´odigos objeto para generar el c´odigo ejecutable:
g++ -ansi -Wall -Werror -o main main.o complejo.o
Aunque tambi´en es posible realizar la compilaci´on y enlazado en el mismo comando:
g++ -ansi -Wall -Werror -o main main.cpp complejo.cpp
o incluso mezclar compilaci´on de c´odigo fuente y enlazado de c´odigo objeto:
g++ -ansi -Wall -Werror -o main main.cpp complejo.o
Hay que tener en cuenta que el compilador enlaza autom´aticamente el c´odigo generado con
las bibliotecas est´andares de C++, y por lo tanto no es necesario que ´estas se especifiquen ex-
pl´ıcitamente. Sin embargo, en caso de ser necesario, tambi´en es posible especificar el enlazado con
bibliotecas externas:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
98 CAP´ITULO 10. M ´ODULOS Y BIBLIOTECAS
g++ -ansi -Wall -Werror -o main main.cpp complejo.cpp -ljpeg
Estas bibliotecas no son m´as que una agregaci´on de m´odulos compilados a c´odigo objeto, y orga-
nizadas adecuadamente para que puedan ser reutilizados por muy diversos programas.
10.3. Espacios de Nombre
Cuando se trabaja con m´ultiples m´odulos y bibliotecas, es posible que se produzcan colisiones
en la definici´on de entidades diferentes con los mismos identificadores proporcionadas por diferentes
m´odulos y bibliotecas. Este hecho no est´a permitido por el lenguaje de programaci´on C++. Para
evitar estas posibles colisiones existen los espacios de nombre (namespace en ingl´es), que permiten
agrupar bajo una misma denominaci´on (jerarqu´ıa) un conjunto de declaraciones y definiciones,
de tal forma que dicha denominaci´on ser´a necesaria para identificar y diferenciar cada entidad
declarada. As´ı, para definir un espacio de nombres se utiliza la palabra reservada namespace
seguida por el identificador del espacio de nombres, y entre llaves las declaraciones y definiciones
que deban estar bajo dicha jerarqu´ıa del espacio de nombres.
Estos espacios de nombre pueden ser ´unicos para un determinado m´odulo, o por el contrario
pueden abarcar m´ultiples m´odulos y bibliotecas gestionados por el mismo proveedor, por ejemplo
todas las entidades definidas en la biblioteca est´andar se encuentran bajo el espacio de nombres
std. As´ı, el identificador del espacio de nombres puede ser derivado del propio nombre del fichero,
puede incluir una denominaci´on relativa al proveedor del m´odulo, o alguna otra denominaci´on m´as
compleja que garantice que no habr´a colisiones en el identificador del espacio de nombres. Por
ejemplo, podemos definir el m´odulo complejo dentro del espacio de nombres umalcc, que har´ıa
referencia a un proveedor del departamento de Lenguajes y Ciencias de la Computaci´on de la
Universidad de M´alaga.
main.cpp (Principal)
#include <iostream>
#include "complejo.hpp"
using namespace std ;
using namespace umalcc ;
// utilizaci´on de complejo
int main()
{
· · ·
}
complejo.hpp (Interfaz)
#ifndef complejo hpp
#define complejo hpp
#include <...otros...>
// interfaz de complejo
namespace umalcc {
· · ·
· · ·
}
#endif
complejo.cpp (Implementaci´on)
#include "complejo.hpp"
#include <...otros...>
// implementaci´on de complejo
namespace umalcc {
· · ·
· · ·
· · ·
· · ·
}
N´otese que la inclusi´on de ficheros de encabezamiento se debe realizar externamente a la defini-
ci´on de los espacios de nombre.
Utilizaci´on de Espacios de Nombre
Una vez que las entidades han sido definidas dentro de un espacio de nombres, ´estas no pueden
ser utilizadas directamente, sino que es necesario alg´un tipo de cualificaci´on que permita referenciar
e identificar a las entidades dentro de los espacios de nombre en los que han sido definidas. Ello
se puede realizar de varias formas, dependiendo de las circunstancias donde se produzca esta
utilizaci´on:
Todos los identificadores definidos dentro de un espacio de nombres determinado son visi-
bles y accesibles directamente desde dentro del mismo espacio de nombres, sin necesidad de
cualificaci´on.
En la implementaci´on de los m´odulos (ficheros de implementaci´on .cpp), mediante la directiva
using namespace se ponen disponibles (accesibles) todos los identificadores de dicho espacio
de nombres completo, que podr´an ser accedidos directamente, sin necesidad de cualificaci´on
expl´ıcita. Por ejemplo:
using namespace std ;
using namespace umalcc ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
10.3. ESPACIOS DE NOMBRE 99
En los ficheros de encabezamiento (.hpp) cada identificador externo (perteneciente a otro
espacio de nombres) se debe utilizar cualificado con el espacio de nombres al que pertenece
(cualificaci´on expl´ıcita) utilizando para ello el identificador del espacio de nombres, seguido
por el operador :: y del identificador de la entidad que se est´e utilizando, como en el siguiente
ejemplo para utilizar el tipo string del espacio de nombres std, o el tipo array del espacio
de nombres std::tr1.
namespace umalcc {
struct Persona {
std::string nombre ;
int edad ;
} ;
typedef std::tr1::array<int, 20> Vector ;
void leer(std::string& nombre) ;
}
Si se utilizan (mediante using namespace) varios espacios de nombre simult´aneamente y ambos
definen el mismo identificador, si dicho identificador no se utiliza, entonces no se produce colisi´on.
Sin embargo en caso de que se utilice dicho identificador, entonces se produce una colisi´on ya que
el mismo identificador se encuentra definido (y es accesible) en dos espacios de nombre diferentes,
por lo que el compilador no puede discernir por s´ı mismo a que entidad se refiere. En este ´ultimo
caso, el programador debe utilizar la cualificaci´on expl´ıcita para este identificador y eliminar de
esta forma la ambig¨uedad en su utilizaci´on.
main.cpp
#include <iostream>
#include <string>
#include "datos.hpp"
using namespace std ;
using namespace umalcc ;
// utilizaci´on de datos
int main()
{
string colision ;
std::string nombre 1 ;
umalcc::string nombre 2 ;
· · ·
}
datos.hpp
#ifndef datos hpp
#define datos hpp
#include <tr1/array>
#include <...otros...>
// interfaz de datos
namespace umalcc {
struct string {
std::tr1::array<char, 50> datos ;
int size ;
} ;
· · ·
}
#endif
datos.cpp
#include "datos.hpp"
#include <...otros...>
using namespace std ;
namespace umalcc {
· · ·
· · ·
· · ·
· · ·
· · ·
· · ·
· · ·
· · ·
}
Es importante remarcar que no es adecuado aplicar la directiva using namespace dentro de
ficheros de encabezamiento, ya que si es utilizada en un fichero de encabezamiento que se incluye
por m´ultiples m´odulos, entonces pondr´ıa disponible (accesible) todos los identificadores de dicho
espacio de nombres para todos los ficheros que incluyan (include) dicho fichero de encabezamiento,
algo que podr´ıa provocar colisiones inesperadas, y esto ser´ıa un efecto colateral no deseado para
aquellos que utilizasen dicho m´odulo (incluyeran dicho fichero de encabezamiento).
Espacios de Nombre An´onimos
Los espacios de nombre an´onimos permiten definir entidades privadas internas a los m´odulos
de implementaci´on, de tal forma que no puedan producir colisiones con las entidades p´ublicas del
sistema completo. De esta forma, cualquier declaraci´on y definici´on realizada dentro de un espacio
de nombres an´onimo ser´a ´unicamente visible en el m´odulo de implementaci´on donde se encuentre
(privada), pero no ser´a visible en el exterior del m´odulo.
Adicionalmente, tambi´en es posible definir espacios de nombre anidados (dentro de otros espa-
cios de nombre), pudiendo, de esta forma, definir jerarqu´ıas de espacios de nombre.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
100 CAP´ITULO 10. M ´ODULOS Y BIBLIOTECAS
Ejemplo
M´odulo N´umeros Complejos
Ejemplo de m´odulo para definir operaciones con n´umeros complejos, donde el fichero de en-
cabezamiento podr´ıa ser:
//- fichero: complejos.hpp ------------------------------------------
#ifndef _complejos_hpp_
#define _complejos_hpp_
namespace umalcc {
//----------------------------------
const double ERROR_PRECISION = 1e-6 ;
//----------------------------------
struct Complejo {
double real ; // parte real del numero complejo
double imag ; // parte imaginaria del numero complejo
} ;
//----------------------------------
void sumar(Complejo& r, const Complejo& a, const Complejo& b) ;
// Devuelve un numero complejo (r) que contiene el resultado de
// sumar los numeros complejos (a) y (b).
//----------------------------------
void restar(Complejo& r, const Complejo& a, const Complejo& b) ;
// Devuelve un numero complejo (r) que contiene el resultado de
// restar los numeros complejos (a) y (b).
//----------------------------------
void multiplicar(Complejo& r, const Complejo& a, const Complejo& b) ;
// Devuelve un numero complejo (r) que contiene el resultado de
// multiplicar los numeros complejos (a) y (b).
//----------------------------------
void dividir(Complejo& r, const Complejo& a, const Complejo& b) ;
// Devuelve un numero complejo (r) que contiene el resultado de
// dividir los numeros complejos (a) y (b).
//----------------------------------
bool iguales(const Complejo& a, const Complejo& b) ;
// Devuelve true si los numeros complejos (a) y (b) son iguales.
//----------------------------------
void escribir(const Complejo& a) ;
// muestra en pantalla el numero complejo (a)
//----------------------------------
void leer(Complejo& a) ;
// lee de teclado el valor del numero complejo (a).
// lee la parte real y la parte imaginaria del numero
//----------------------------------
}
#endif
//- fin: complejos.hpp ----------------------------------------------
La implementaci´on del m´odulo se realiza en un fichero independiente, donde adem´as se puede
apreciar la utilizaci´on de un espacio de nombres an´onimo:
//- fichero: complejos.cpp ------------------------------------------
#include "complejos.hpp"
#include <iostream>
using namespace std ;
using namespace umalcc ;
//------------------------------------------------------------------------------
// Espacio de nombres anonimo. Es una parte privada de la
// implementacion. No es accesible desde fuera del modulo
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
10.3. ESPACIOS DE NOMBRE 101
//------------------------------------------------------------------------------
namespace {
//----------------------------------
//-- Subprogramas Auxiliares -------
//----------------------------------
// cuadrado de un numero (a^2)
inline double sq(double a)
{
return a*a ;
}
//----------------------------------
// Valor absoluto de un numero
inline double abs(double a)
{
if (a < 0) {
a = -a ;
}
return a ;
}
//----------------------------------
// Dos numeros reales son iguales si la distancia que los
// separa es lo suficientemente pequenya
inline bool iguales(double a, double b)
{
return abs(a-b) <= ERROR_PRECISION ;
}
}
//------------------------------------------------------------------------------
// Espacio de nombres umalcc.
// Aqui reside la implementacion de la parte publica del modulo
//------------------------------------------------------------------------------
namespace umalcc {
//----------------------------------
//-- Implementaci´on ----------------
//----------------------------------
// Devuelve un numero complejo (r) que contiene el resultado de
// sumar los numeros complejos (a) y (b).
void sumar(Complejo& r, const Complejo& a, const Complejo& b)
{
r.real = a.real + b.real ;
r.imag = a.imag + b.imag ;
}
//----------------------------------
// Devuelve un numero complejo (r) que contiene el resultado de
// restar los numeros complejos (a) y (b).
void restar(Complejo& r, const Complejo& a, const Complejo& b)
{
r.real = a.real - b.real ;
r.imag = a.imag - b.imag ;
}
//----------------------------------
// Devuelve un numero complejo (r) que contiene el resultado de
// multiplicar los numeros complejos (a) y (b).
void multiplicar(Complejo& r, const Complejo& a, const Complejo& b)
{
r.real = (a.real * b.real) - (a.imag * b.imag) ;
r.imag = (a.real * b.imag) + (a.imag * b.real) ;
}
//----------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
102 CAP´ITULO 10. M ´ODULOS Y BIBLIOTECAS
// Devuelve un numero complejo (r) que contiene el resultado de
// dividir los numeros complejos (a) y (b).
void dividir(Complejo& r, const Complejo& a, const Complejo& b)
{
double divisor = sq(b.real) + sq(b.imag) ;
if (::iguales(0.0, divisor)) {
r.real = 0 ;
r.imag = 0 ;
} else {
r.real = ((a.real * b.real) + (a.imag * b.imag)) / divisor ;
r.imag = ((a.imag * b.real) - (a.real * b.imag)) / divisor ;
}
}
//----------------------------------
// Devuelve true si los numeros complejos (a) y (b) son iguales.
bool iguales(const Complejo& a, const Complejo& b)
{
return ::iguales(a.real, b.real) && ::iguales(a.imag, b.imag) ; }
//----------------------------------
// muestra en pantalla el numero complejo (a)
void escribir(const Complejo& a)
{
cout << "{ " << a.real << ", " << a.imag << " }" ;
}
//----------------------------------
// lee de teclado el valor del numero complejo (a).
// lee la parte real y la parte imaginaria del numero
void leer(Complejo& a)
{
cin >> a.real >> a.imag ;
}
//----------------------------------
}
//- fin: complejos.cpp ----------------------------------------------
Un ejemplo de utilizaci´on del m´odulo de n´umeros complejos podr´ıa ser:
//- fichero: main.cpp ------------------------------------------
#include <iostream>
#include "complejos.hpp"
using namespace std ;
using namespace umalcc ;
//------------------------------------
void leer(Complejo& c)
{
cout << "Introduzca un numero complejo { real img }: " ;
// cualificacion expl´ıcita del espacio de nombres uamalcc
// para evitar colisi´on en invocaci´on a subprograma
// leer(Complejo& c) del espacio de nombres umalcc
umalcc::leer(c) ;
}
//------------------------------------
void prueba_suma(const Complejo& c1, const Complejo& c2)
{
Complejo c0 ;
sumar(c0, c1, c2) ;
escribir(c1) ;
cout <<" + " ;
escribir(c2) ;
cout <<" = " ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
10.3. ESPACIOS DE NOMBRE 103
escribir(c0) ;
cout << endl ;
Complejo aux ;
restar(aux, c0, c2) ;
if (! iguales(c1, aux)) {
cout << "Error en operaciones de suma/resta"<< endl ;
}
}
//------------------------------------
void prueba_resta(const Complejo& c1, const Complejo& c2)
{
Complejo c0 ;
restar(c0, c1, c2) ;
escribir(c1) ;
cout <<" - " ;
escribir(c2) ;
cout <<" = " ;
escribir(c0) ;
cout << endl ;
Complejo aux ;
sumar(aux, c0, c2) ;
if (! iguales(c1, aux)) {
cout << "Error en operaciones de suma/resta"<< endl ;
}
}
//------------------------------------
void prueba_mult(const Complejo& c1, const Complejo& c2)
{
Complejo c0 ;
multiplicar(c0, c1, c2) ;
escribir(c1) ;
cout <<" * " ;
escribir(c2) ;
cout <<" = " ;
escribir(c0) ;
cout << endl ;
Complejo aux ;
dividir(aux, c0, c2) ;
if (! iguales(c1, aux)) {
cout << "Error en operaciones de mult/div"<< endl ;
}
}
//------------------------------------
void prueba_div(const Complejo& c1, const Complejo& c2)
{
Complejo c0 ;
dividir(c0, c1, c2) ;
escribir(c1) ;
cout <<" / " ;
escribir(c2) ;
cout <<" = " ;
escribir(c0) ;
cout << endl ;
Complejo aux ;
multiplicar(aux, c0, c2) ;
if (! iguales(c1, aux)) {
cout << "Error en operaciones de mult/div"<< endl ;
}
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
104 CAP´ITULO 10. M ´ODULOS Y BIBLIOTECAS
//------------------------------------
int main()
{
Complejo c1, c2 ;
// cualificaci´on expl´ıcita del espacio de nombres global
// para evitar colisi´on en invocaci´on al subprograma
// leer(Complejo& c) del espacio de nombres global
::leer(c1) ;
::leer(c2) ;
//--------------------------------
prueba_suma(c1, c2) ;
prueba_resta(c1, c2) ;
prueba_mult(c1, c2) ;
prueba_div(c1, c2) ;
//--------------------------------
}
//- fin: main.cpp ----------------------------------------------
Su compilaci´on separada y enlazado en GNU GCC:
g++ -ansi -Wall -Werror -c complejos.cpp
g++ -ansi -Wall -Werror -c main.cpp
g++ -ansi -Wall -Werror -o main main.o complejos.o
Alternativamente se puede realizar en dos pasos:
g++ -ansi -Wall -Werror -c complejos.cpp
g++ -ansi -Wall -Werror -o main main.cpp complejos.o
o incluso en un ´unico paso:
g++ -ansi -Wall -Werror -o main main.cpp complejos.cpp
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 11
Tipos Abstractos de Datos
A medida que aumenta la complejidad del problema a resolver, del mismo modo deben aumen-
tar los niveles de abstracci´on necesarios para dise˜nar y construir su soluci´on algor´ıtmica. As´ı, la
abstracci´on procedimental permite aplicar adecuadamente t´ecnicas de dise˜no descendente y refi-
namientos sucesivos en el desarrollo de algoritmos y programas. La programaci´on modular permite
aplicar la abstracci´on a mayor escala, permitiendo abstraer sobre conjuntos de operaciones y los
datos sobre los que se aplican. De esta forma, a medida que aumenta la complejidad del problema a
resolver, aumenta tambi´en la complejidad de las estructuras de datos necesarias para su resoluci´on,
y este hecho requiere, as´ı mismo, la aplicaci´on de la abstracci´on a las estructuras de datos.
TAD
op1()
op2()
op3()
La aplicaci´on de la abstracci´on a las estructuras de datos da lugar a
los Tipos Abstractos de Datos (TAD), donde se especifica el concepto que
representa un determinado tipo de datos, y la sem´antica (el significado) de
las operaciones que se le pueden aplicar, pero donde su representaci´on e im-
plementaci´on internas permanecen ocultas e inaccesibles desde el exterior,
de tal forma que no son necesarias para su utilizaci´on. As´ı, podemos consi-
derar que un tipo abstracto de datos encapsula una determinada estructura
abstracta de datos, impidiendo su manipulaci´on directa, permitiendo sola-
mente su manipulaci´on a trav´es de las operaciones especificadas. De este modo, los tipos abstractos
de datos proporcionan un mecanismo adecuado para el dise˜no y reutilizaci´on de software fiable y
robusto.
Para un determinado tipo abstracto de datos, se pueden distinguir tres niveles:
Nivel de utilizaci´on, donde se utilizan objetos de un determinado tipo abstracto de datos,
bas´andose en la especificaci´on del mismo, de forma independiente a su implementaci´on y
representaci´on concretas. As´ı, estos objetos se manipulan mediante la invocaci´on a las ope-
raciones especificadas en el TAD.
Nivel de especificaci´on, donde se especifica el tipo de datos, el
concepto abstracto que representa y la sem´antica y restricciones
de las operaciones que se le pueden aplicar. Este nivel representa
el interfaz p´ublico del tipo abstracto de datos.
Utilizaci´on de TAD
Especificaci´on de TAD
Implementaci´on de TAD
Nivel de implementaci´on, donde se define e implementa tanto las estructuras de datos que
soportan la abstracci´on, como las operaciones que act´uan sobre ella seg´un la sem´antica es-
pecificada. Este nivel interno permanece privado, y no es accesible desde el exterior del tipo
abstracto de datos.
N´otese que para una determinada especificaci´on de un tipo abstracto de datos, su implementaci´on
puede cambiar sin que ello afecte a la utilizaci´on del mismo.
105
106 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
11.1. Tipos Abstractos de Datos en C++: Clases
En el lenguaje de programaci´on C++, las clases dan la posibilidad al programador de definir
tipos abstractos de datos, de tal forma que permiten definir su representaci´on interna (compuesta
por sus atributos miembros), la forma en la que se crean y se destruyen, como se asignan y se
pasan como par´ametros, y las operaciones que se pueden aplicar (denominadas funciones miembro
o simplemente m´etodos). De esta forma se hace el lenguaje extensible. As´ı mismo, la definici´on
de tipos abstractos de datos mediante clases puede ser combinada con la definici´on de m´odulos
(v´ease 10), haciendo de este modo posible la reutilizaci´on de estos nuevos tipos de datos.
As´ı, en C++ una determinada clase define un determinado tipo abstracto de datos, y un objeto
se corresponde con una determinada instancia de una clase, de igual forma que una variable se
corresponde con una determinada instancia de un tipo de datos.
Aunque C++ permite implementar las clases utilizando una definici´on en l´ınea, en este cap´ıtulo
nos centraremos en la implementaci´on separada de los m´etodos de las clases. Adem´as, combinare-
mos la especificaci´on de la clase y su implementaci´on con los conceptos de programaci´on modular
vistos en el cap´ıtulo anterior (v´ease 10), de tal forma que la definici´on de la clase se realizar´a en el
fichero de cabecera (hpp) de un determinado m´odulo, y la implementaci´on de la clase se realizar´a en
el fichero de implementaci´on (cpp) del m´odulo.
11.1.1. Definici´on de Clases
La definici´on de la clase se realizar´a en el fichero de cabecera (hpp) de un determinado m´odulo,
dentro de las guardas y espacio de nombres adecuado. Para ello, se especifica la palabra reservada
class seguida por el identificador de la nueva clase (tipo) que se est´a definiendo, y entre llaves
la definici´on de los atributos (miembros) que lo componen y de los m´etodos (funciones miembros)
que se le pueden aplicar directamente a los objetos de la clase. Finalmente el delimitador punto y
coma (;) debe seguir al delimitador cierra-llaves (}).
//- fichero: complejos.hpp ------------------------------------------
#ifndef _complejos_hpp_
#define _complejos_hpp_
namespace umalcc {
class Complejo {
// ...
} ;
}
#endif
//- fin: complejos.hpp ----------------------------------------------
Zona Privada y Zona P´ublica
En la definici´on de una clase, se pueden distinguir dos ´ambitos de visibilidad (accesibilidad),
la parte privada, cuyos miembros s´olo ser´an accesibles desde un ´ambito interno a la propia clase,
y la parte p´ublica, cuyos miembros son accesibles tanto desde un ´ambito interno como desde un
´ambito externo a la clase.
La parte privada comprende desde el principio de la definici´on de la clase hasta la etiqueta
public:, y la parte p´ublica comprende desde esta etiqueta hasta que se encuentra otra etiqueta
private:. Cada vez que se especifica una de las palabras reservadas public: o private:, las
declaraciones que la siguen adquieren el atributo de visibilidad dependiendo de la etiqueta especi-
ficada.
class Complejo {
public:
// ... zona p´ublica ...
private:
// ... zona privada ...
} ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
11.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 107
Atributos
Los atributos componen la representaci´on interna de la clase, y se definen usualmente en la
zona de visibilidad privada de la clase, con objeto de proteger el acceso a dicha representaci´on
interna. Su definici´on se realiza de igual forma a los campos de los registros (v´ease 6.3).
De igual modo a los registros y sus campos, cada objeto que se defina, almacenar´a su propia
representaci´on interna de los atributos de forma independiente a los otros objetos (instancias de
la misma clase).
class Complejo {
public:
// ...
private:
double real ; // parte real del numero complejo
double imag ; // parte imaginaria del numero complejo
} ;
El Constructor por Defecto
El constructor de una clase permite construir e inicializar un objeto. El constructor por defecto
es el mecanismo por defecto utilizado para construir objetos de este tipo cuando no se especifica
ninguna forma expl´ıcita de construcci´on. As´ı, ser´a el encargado de construir el objeto con los valores
iniciales adecuados en el momento en que sea necesaria dicha construcci´on, por ejemplo cuando el
flujo de ejecuci´on alcanza la declaraci´on de una variable de dicho tipo (v´ease 11.1.2).
Los constructores se declaran con el mismo identificador de la clase, seguidamente se especifican
entre par´entesis los par´ametros necesarios para la construcci´on, que en el caso del constructor por
defecto, ser´an vac´ıos.
class Complejo {
public:
Complejo() ; // Constructor por Defecto
// ...
private:
// ...
} ;
M´etodos Generales y M´etodos Constantes
Los m´etodos se corresponden con las operaciones que permiten manipular de muy diversa forma
el estado interno de un determinado objeto como instancia de una determinada clase. Puede haber
m´etodos definidos en el ´ambito p´ublico de la clase, en cuyo caso podr´an ser invocados tanto desde
m´etodos internos de la clase, como desde el exterior de la clase, y m´etodos definidos en el ´ambito
privado de la clase, en cuyo caso s´olo podr´an ser invocados desde m´etodos internos de la clase. Estos
m´etodos definidos en el ´ambito privado de la clase suelen ser definidos como m´etodos auxiliares
que facilitan la implementaci´on de otros m´etodos m´as complejos.
Los m´etodos se declaran como los prototipos de los subprogramas (v´ease 5.7), pero teniendo
en cuenta son aplicados a un objeto instancia de la clase a la que pertenece, y que por lo tanto no
es necesario que sea recibido como par´ametro.
Los m´etodos de una clase pueden tener el cualificador const especificado despu´es de los
par´ametros, en cuyo caso indica que el m´etodo no modifica el estado interno del objeto, por lo
que se puede aplicar tanto a objetos constantes como variables. En otro caso, si dicho cualificador
no aparece, entonces significa que el m´etodo si modifica el estado interno del objeto, por lo que
s´olo podr´a ser aplicado a objetos variables, y por el contrario no podr´a ser aplicado a objetos
constantes.
class Complejo {
public:
// ...
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
108 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
//----------------------------
void sumar(const Complejo& a, const Complejo& b) ;
// asigna al numero complejo (actual) el resultado de
// sumar los numeros complejos (a) y (b).
//----------------------------
bool igual(const Complejo& b) const ;
// Devuelve true si el numero complejo (actual) es
// igual al numero complejo (b)
//----------------------------
void escribir() const ;
// muestra en pantalla el numero complejo (actual)
//----------------------------
void leer() ;
// lee de teclado el valor del numero complejo (actual).
// lee la parte real y la parte imaginaria del numero
//------------------------------
// ...
private:
// ...
} ;
11.1.2. Utilizaci´on de Clases
Un tipo abstracto de datos, definido como una clase encapsulada dentro de un m´odulo, puede ser
utilizado por cualquier otro m´odulo que lo necesite. Para ello, deber´a incluir el fichero de cabecera
donde se encuentra la definici´on de la clase, y podr´a definir tantos objetos (instancias de dicha clase)
como sean necesarios, para ello, deber´a utilizar cualificaci´on expl´ıcita o impl´ıcita dependiendo del
contexto de su utilizaci´on (ficheros de encabezamiento o de implementaci´on respectivamente).
Instancias de Clase: Objetos
Un objeto es una instancia de una clase, y podremos definir tantos objetos cuyo tipo sea
de una determinada clase como sea necesario, de tal modo que cada objeto contiene su propia
representaci´on interna de forma independiente del resto.
La definici´on de un objeto de una determinada clase se realiza de igual forma a la definici´on de
una variable (o constante) de un determinado tipo, de tal forma que cada objeto ser´a una instancia
independiente de una determinada clase (tipo abstracto de datos).
real:
imag:
0.0
0.0
c1
real:
imag:
0.0
0.0
c2
//- fichero: main.cpp -----------------------------------------------
#include <iostream>
#include "complejos.hpp"
using namespace std ;
using namespace umalcc ;
int main()
{
Complejo c1, c2 ;
// ...
}
//- fin: main.cpp ---------------------------------------------------
Es importante remarcar que cada objeto, definido de una determinada clase, es una instancia
independiente de los otros objetos definidos de la misma clase, con su propia memoria para contener
de forma independiente el estado de su representaci´on interna.
Tiempo de Vida de los Objetos
Durante la ejecuci´on del programa, cuando el flujo de ejecuci´on llega a la sentencia donde se
define un determinado objeto, entonces se reserva espacio en memoria para contener a dicho objeto,
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
11.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 109
y se invoca al constructor especificado (si no se especifica ning´un constructor, entonces se invoca
al constructor por defecto) para construir adecuadamente al objeto, siendo de esta forma accesible
desde este punto de construcci´on hasta que el flujo de ejecuci´on alcanza el final de bloque donde el
objeto ha sido definido, en cuyo caso el objeto se destruye (invocando a su destructor) y se libera
la memoria que ocupaba, pasando de este modo a estar inaccesible.
//- fichero: main.cpp -----------------------------------------------
#include <iostream>
#include "complejos.hpp"
using namespace std ;
using namespace umalcc ;
int main()
{
Complejo c1 ; // construcci´on de c1 (1 vez)
for (int i = 0 ; i < 3 ; ++i) {
Complejo c2 ; // construcci´on de c2 (3 veces)
// ...
} // destrucci´on de c2 (3 veces)
// ...
} // destrucci´on de c1 (1 vez)
//- fin: main.cpp ---------------------------------------------------
Manipulaci´on de los Objetos
Una vez que un objeto es accesible, se puede manipular invocando a los m´etodos p´ublicos
definidos en su interfaz. Esta invocaci´on de los m´etodos se aplica sobre un determinado objeto en
concreto, y se realiza especificando el identificador del objeto sobre el que recae la invocaci´on al
m´etodo, seguido por el s´ımbolo punto (.) y por la invocaci´on al m´etodo en cuesti´on, es decir, el
identificador del m´etodo y los par´ametros actuales necesarios entre par´entesis.
real:
imag:
5.3
2.4
c1
real:
imag:
2.5
7.3
c2
real:
imag:
7.8
9.7
c3
//- fichero: main.cpp -----------------------------------------------
#include <iostream>
#include "complejos.hpp"
using namespace std ;
using namespace umalcc ;
int main()
{
Complejo c1, c2, c3 ; // construcci´on de c1, c2, c3
c1.leer() ;
c2.leer() ;
c3.sumar(c1, c2) ;
c3.escribir() ;
} // destrucci´on de c1, c2, c3
//- fin: main.cpp ---------------------------------------------------
Paso de Par´ametros de Objetos
Es importante considerar que las clases (tipos abstractos de datos) son tipos compuestos, y por
lo tanto deben seguir las mismas convenciones para el paso de par´ametros de tipos compuestos
(v´ease 6.1), es decir, los par´ametros de salida o entrada/salida se pasan por referencia, y los
par´ametros de entrada se pasan por referencia constante.
11.1.3. Implementaci´on de Clases
La implementaci´on de los m´etodos de la clase se realizar´a en el fichero de implementaci´on
(cpp) del m´odulo correspondiente, dentro del mismo espacio de nombres en el que fue realizada la
definici´on de la clase en el fichero de cabecera.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
110 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
En el fichero de implementaci´on se podr´an definir, dentro del espacio de nombres adecuado, las
constantes, tipos y subprogramas auxiliares necesarios que nos faciliten la implementaci´on de los
m´etodos de la clase.
Para implementar un determinado constructor o m´etodo de la clase, dentro del mismo espacio
de nombres que la definici´on de la clase, se cualificar´a expl´ıcitamente el identifidor del m´etodo con
el identificador de la clase a la que pertenece.
//- fichero: complejos.cpp ------------------------------------------
#include "complejos.hpp"
namespace umalcc {
// ...
//----------------------------
Complejo::Complejo() // Constructor por Defecto
// ...
//----------------------------
void Complejo::sumar(const Complejo& a, const Complejo& b)
// ...
//----------------------------
bool Complejo::igual(const Complejo& b) const
// ...
//----------------------------
void Complejo::escribir() const
// ...
//----------------------------
void Complejo::leer()
// ...
//----------------------------
// ...
}
//- fin: complejos.cpp ----------------------------------------------
M´etodos
En la implementaci´on de un determinado m´etodo de una clase, ´este m´etodo puede invocar
directamente a cualquier otro m´etodo de la clase sin necesidad de aplicar el operador punto (.).
As´ı mismo, un m´etodo de la clase puede acceder directamente a los atributos del objeto sobre el que
se invoque dicho m´etodo, sin necesidad de aplicar el operador punto (.), ni necesidad de recibirlo
como par´ametro. Por ejemplo:
void Complejo::sumar(const Complejo& a, const Complejo& b)
{
real = a.real + b.real ;
imag = a.imag + b.imag ;
}
Sin embargo, para acceder a los atributos de los objetos recibidos como par´ametros, si son accesibles
desde la implementaci´on de una determinada clase, es necesario especificar el objeto (mediante su
identificador) seguido por el operador punto (.) y a continuaci´on el identificador del atributo en
cuesti´on.
As´ı, podemos ver como para calcular la suma de n´umeros complejos, se asigna a las partes
real e imaginaria del n´umero complejo que estamos calculando (el objeto sobre el que se aplica el
m´etodo sumar) la suma de las partes real e imaginaria respectivamente de los n´umeros complejos
que recibe como par´ametros. Por ejemplo, cuando se ejecuta la sentencia:
c3.sumar(c1, c2) ;
la sentencia correspondiente a la implementaci´on del m´etodo sumar(...):
real = a.real + b.real ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
11.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 111
almacenar´a en el atributo real del n´umero complejo c3 el resultado de sumar los valores del
atributo real de los n´umeros complejos c1 y c2. De igual modo suceder´a con el atributo imag del
n´umero complejo c3, que almacenar´a el resultado de sumar los valores del atributo imag de los
n´umeros complejos c1 y c2.
Constructores
En la implementaci´on de los constructores de la clase, tambi´en ser´a cualificado expl´ıcitamente
con el identificador de la clase correspondiente. Despu´es de la definici´on de los par´ametros, a
continuaci´on del delimitador (:), se especifica la lista de inicializaci´on, donde aparecen, separados
por comas y seg´un el orden de declaraci´on, todos los atributos miembros del objeto, as´ı como
los valores con los que ser´an inicializados especificados entre par´entesis (se invoca al constructor
adecuado seg´un los par´ametros especificados entre par´entesis, de tal forma que los par´entesis vac´ıos
representan la construcci´on por defecto). A continuaci´on se especifican entre llaves las sentencias
pertenecientes al cuerpo del constructor para realizar las acciones adicionales necesarias para la
construcci´on del objeto. Si no es necesario realizar ninguna acci´on adicional, entonces el cuerpo del
constructor se dejar´a vac´ıo.
Por ejemplo, implementaremos el constructor por defecto de la clase Complejo para que asigne
el valor cero a cada componente (parte real e imaginaria) del objeto que se construya.
Complejo::Complejo() // Constructor por Defecto
: real(0.0), imag(0.0) { }
11.1.4. Ejemplo
Por ejemplo, el TAD n´umero complejo representa el siguiente concepto matem´atico de n´umero
complejo:
Un n´umero complejo representa un punto en el plano complejo, compuesto por dos
componentes que representan la parte real y la parte imaginaria del n´umero (abcisa y
ordenada respectivamente en el plano cartesiano), al cual se le pueden aplicar las ope-
raciones de suma, resta, multiplicaci´on y divisi´on, as´ı como la comparaci´on de igualdad.
Definici´on
//- fichero: complejos.hpp ------------------------------------------
#ifndef _complejos_hpp_
#define _complejos_hpp_
namespace umalcc {
//----------------------------------
const double ERROR_PRECISION = 1e-6 ;
//----------------------------------
class Complejo {
public:
//----------------------------------------------------------
//-- M´etodos P´ublicos --------------------------------------
//----------------------------------------------------------
Complejo() ; // Constructor por Defecto
//----------------------------
double parte_real() const ;
// devuelve la parte real del numero complejo
//----------------------------
double parte_imag() const ;
// devuelve la parte imaginaria del numero complejo
//----------------------------
void sumar(const Complejo& a, const Complejo& b) ;
// asigna al numero complejo (actual) el resultado de
// sumar los numeros complejos (a) y (b).
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
112 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
//----------------------------
void restar(const Complejo& a, const Complejo& b) ;
// asigna al numero complejo (actual) el resultado de
// restar los numeros complejos (a) y (b).
//----------------------------
void multiplicar(const Complejo& a, const Complejo& b) ;
// asigna al numero complejo (actual) el resultado de
// multiplicar los numeros complejos (a) y (b).
//----------------------------
void dividir(const Complejo& a, const Complejo& b) ;
// asigna al numero complejo (actual) el resultado de
// dividir los numeros complejos (a) y (b).
//----------------------------
bool igual(const Complejo& b) const ;
// Devuelve true si el numero complejo (actual) es
// igual al numero complejo (b)
//----------------------------
void escribir() const ;
// muestra en pantalla el numero complejo (actual)
//----------------------------
void leer() ;
// lee de teclado el valor del numero complejo (actual).
// lee la parte real y la parte imaginaria del numero
//------------------------------
private:
//----------------------------------------------------------
//-- Atributos Privados ------------------------------------
//----------------------------------------------------------
double real ; // parte real del numero complejo
double imag ; // parte imaginaria del numero complejo
//----------------------------------------------------------
} ;
}
#endif
//- fin: complejos.hpp ----------------------------------------------
Implementaci´on
//- fichero: complejos.cpp ------------------------------------------
#include "complejos.hpp"
#include <iostream>
using namespace std ;
using namespace umalcc ;
//------------------------------------------------------------------------------
// Espacio de nombres anonimo. Es una parte privada de la
// implementacion. No es accesible desde fuera del modulo
//------------------------------------------------------------------------------
namespace {
//----------------------------------------------------------
//-- Subprogramas Auxiliares -------------------------------
//----------------------------------------------------------
// cuadrado de un numero (a^2)
inline double sq(double a)
{
return a*a ;
}
//-------------------------
// Valor absoluto de un numero
inline double abs(double a)
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
11.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 113
{
return (a >= 0) ? a : -a ;
}
//-------------------------
// Dos numeros reales son iguales si la distancia que los
// separa es lo suficientemente pequenya
inline bool iguales(double a, double b)
{
return abs(a-b) <= ERROR_PRECISION ;
}
}
//------------------------------------------------------------------------------
// Espacio de nombres umalcc.
// Aqui reside la implementacion de la parte publica del modulo
//------------------------------------------------------------------------------
namespace umalcc {
//----------------------------------------------------------
//-- M´etodos P´ublicos --------------------------------------
//----------------------------------------------------------
Complejo::Complejo() // Constructor por Defecto
: real(0.0), imag(0.0) { }
//----------------------------
// devuelve la parte real del numero complejo
double Complejo::parte_real() const
{
return real ;
}
//----------------------------
// devuelve la parte imaginaria del numero complejo
double Complejo::parte_imag() const
{
return imag ;
}
//----------------------------
// asigna al numero complejo (actual) el resultado de
// sumar los numeros complejos (a) y (b).
void Complejo::sumar(const Complejo& a, const Complejo& b)
{
real = a.real + b.real ;
imag = a.imag + b.imag ;
}
//----------------------------
// asigna al numero complejo (actual) el resultado de
// restar los numeros complejos (a) y (b).
void Complejo::restar(const Complejo& a, const Complejo& b)
{
real = a.real - b.real ;
imag = a.imag - b.imag ;
}
//----------------------------
// asigna al numero complejo (actual) el resultado de
// multiplicar los numeros complejos (a) y (b).
void Complejo::multiplicar(const Complejo& a, const Complejo& b)
{
real = (a.real * b.real) - (a.imag * b.imag) ;
imag = (a.real * b.imag) + (a.imag * b.real) ;
}
//----------------------------
// asigna al numero complejo (actual) el resultado de
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
114 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
// dividir los numeros complejos (a) y (b).
void Complejo::dividir(const Complejo& a, const Complejo& b)
{
double divisor = sq(b.real) + sq(b.imag) ;
if (iguales(0.0, divisor)) {
real = 0.0 ;
imag = 0.0 ;
} else {
real = ((a.real * b.real) + (a.imag * b.imag)) / divisor ;
imag = ((a.imag * b.real) - (a.real * b.imag)) / divisor ;
}
}
//----------------------------
// Devuelve true si el numero complejo (actual) es
// igual al numero complejo (b)
bool Complejo::igual(const Complejo& b) const
{
return iguales(real, b.real) && iguales(imag, b.imag) ;
}
//----------------------------
// muestra en pantalla el numero complejo (actual)
void Complejo::escribir() const
{
cout << "{ " << real << ", " << imag << " }" ;
}
//----------------------------
// lee de teclado el valor del numero complejo (actual).
// lee la parte real y la parte imaginaria del numero
void Complejo::leer()
{
cin >> real >> imag ;
}
//----------------------------
}
//- fin: complejos.cpp ----------------------------------------------
Utilizaci´on
//- fichero: main.cpp -----------------------------------------------
#include <iostream>
#include "complejos.hpp"
using namespace std ;
using namespace umalcc ;
//------------------------------------
void leer(Complejo& c)
{
cout << "Introduzca un numero complejo { real img }: " ;
c.leer() ;
}
//------------------------------------
void prueba_suma(const Complejo& c1, const Complejo& c2)
{
Complejo c0 ;
c0.sumar(c1, c2) ;
c1.escribir() ;
cout <<" + " ;
c2.escribir() ;
cout <<" = " ;
c0.escribir() ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
11.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 115
cout << endl ;
Complejo aux ;
aux.restar(c0, c2) ;
if ( ! c1.igual(aux)) {
cout << "Error en operaciones de suma/resta"<< endl ;
}
}
//------------------------------------
void prueba_resta(const Complejo& c1, const Complejo& c2)
{
Complejo c0 ;
c0.restar(c1, c2) ;
c1.escribir() ;
cout <<" - " ;
c2.escribir() ;
cout <<" = " ;
c0.escribir() ;
cout << endl ;
Complejo aux ;
aux.sumar(c0, c2) ;
if ( ! c1.igual(aux)) {
cout << "Error en operaciones de suma/resta"<< endl ;
}
}
//------------------------------------
void prueba_mult(const Complejo& c1, const Complejo& c2)
{
Complejo c0 ;
c0.multiplicar(c1, c2) ;
c1.escribir() ;
cout <<" * " ;
c2.escribir() ;
cout <<" = " ;
c0.escribir() ;
cout << endl ;
Complejo aux ;
aux.dividir(c0, c2) ;
if ( ! c1.igual(aux)) {
cout << "Error en operaciones de mult/div"<< endl ;
}
}
//------------------------------------
void prueba_div(const Complejo& c1, const Complejo& c2)
{
Complejo c0 ;
c0.dividir(c1, c2) ;
c1.escribir() ;
cout <<" / " ;
c2.escribir() ;
cout <<" = " ;
c0.escribir() ;
cout << endl ;
Complejo aux ;
aux.multiplicar(c0, c2) ;
if ( ! c1.igual(aux)) {
cout << "Error en operaciones de mult/div"<< endl ;
}
}
//------------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
116 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
int main()
{
Complejo c1, c2 ;
leer(c1) ;
leer(c2) ;
//--------------------------------
prueba_suma(c1, c2) ;
prueba_resta(c1, c2) ;
prueba_mult(c1, c2) ;
prueba_div(c1, c2) ;
//--------------------------------
}
//- fin: main.cpp ---------------------------------------------------
11.2. Tipos Abstractos de Datos en C++: M´as sobre Clases
Constantes de ´Ambito de Clase
Las constantes de ´ambito de clase se definen especificando los cualificadores static const,
seguidos por el tipo, el identificador y el valor de la constante. Estas constantes ser´an comunes y
accesibles a todas las instancias (objetos) de la clase. Por ejemplo, para definir la constante MAX
con un valor de 256 en la zona privada de la clase:
class ListaInt {
public:
// ...
private:
static const int MAX = 256 ;
// ...
} ;
Usualmente las constantes se definen en la zona privada de la clase, por lo que usualmente s´olo
ser´an accesibles internamente desde dentro de la clase. Sin embargo, en algunas situaciones puede
ser conveniente definir la constante en la zona p´ublica de la clase, entonces en este caso la constante
podr´a ser accedida desde el exterior de la clase, y ser´a utilizada mediante cualificaci´on expl´ıcita
utilizando el identificador de la clase. Por ejemplo:
class ListaInt {
public:
static const int MAX = 256 ;
// ...
private:
// ...
} ;
// ...
int main()
{
int x = ListaInt::MAX ;
// ...
}
Tipos de ´Ambito de Clase
Tambi´en se pueden definir tipos internos de ´ambito de clase de igual forma a como se hace
externamente a la clase, pero en este caso su ´ambito de visibilidad estar´a restringido a la clase
donde se defina. Estos tipos ser´an ´utiles en la definici´on de los atributos miembros de la clase, o
para definir elementos auxiliares en la implementaci´on del tipo abstracto de datos. Por ejemplo,
para definir un tipo Datos como un array de 256 n´umeros enteros:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 117
#include <tr1/array>
// ...
class ListaInt {
public:
// ...
private:
static const int MAX = 256 ;
typedef tr1::std::array<int, MAX> Datos ;
struct Elemento {
// ...
} ;
// ...
} ;
Usualmente los tipos se definen en la zona privada de la clase, por lo que usualmente s´olo ser´an
accesibles internamente desde dentro de la clase. Sin embargo, en algunas situaciones puede ser
conveniente definir el tipo en la zona p´ublica de la clase, entonces en este caso el tipo podr´a ser
accedido desde el exterior de la clase, y ser´a utilizado mediante cualificaci´on expl´ıcita utilizando el
identificador de la clase. Por ejemplo:
#include <tr1/array>
// ...
class ListaInt {
public:
static const int MAX = 256 ;
typedef tr1::std::array<int, MAX> Datos ;
// ...
private:
// ...
} ;
// ...
int main()
{
ListaInt::Datos d ;
// ...
}
Constructores Espec´ıficos
Los constructores de una clase permiten construir e inicializar un objeto. Anteriormente se ha
explicado el constructor por defecto, el cual se invoca cuando se crea un determinado objeto, y no
se especifica que tipo de construcci´on se debe realizar. C++ permite, adem´as, la definici´on e imple-
mentaci´on de tantos constructores espec´ıficos como sean necesarios, para ello, se debe especificar
en la lista de par´ametros, aquellos que sean necesarios para poder construir el objeto adecuada-
mente en cada circunstancia espec´ıfica, de tal forma que ser´a la lista de par´ametros formales la
que permita discriminar que constructor ser´a invocado dependiendo de los par´ametros actuales
utilizados en la invocaci´on al constructor.
Por ejemplo, podemos definir un constructor espec´ıfico para que reciba dos n´umeros reales
como par´ametros (parte real e imaginaria respectivamente de un n´umero complejo), los cuales
ser´an utilizados para dar los valores iniciales a cada atributo correspondiente del objeto que se
construya. As´ı, su definici´on podr´ıa ser:
class Complejo {
public:
Complejo(double p_real, double p_imag) ; // Constructor Espec´ıfico
} ;
A continuaci´on se puede ver como ser´ıa la implementaci´on de este constructor espec´ıfico:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
118 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
Complejo::Complejo(double p_real, double p_imag) // Constructor espec´ıfico
: real(p_real), imag(p_imag) { }
Finalmente, a continuaci´on podemos ver un ejemplo de como ser´ıa una posible invocaci´on a dicho
constructor espec´ıfico (para c2), junto a una invocaci´on al constructor por defecto (para c1):
real:
imag:
0.0
0.0
c1
real:
imag:
2.5
7.3
c2
//- fichero: main.cpp -----------------------------------------------
#include <iostream>
#include "complejos.hpp"
using namespace std ;
using namespace umalcc ;
int main()
{
Complejo c1 ;
Complejo c2(2.5, 7.3) ;
// ...
}
//- fin: main.cpp ---------------------------------------------------
Constructor por Defecto
Como se explic´o anteriormente (v´ease 11.1.1 y 11.1.3), el constructor por defecto es el mecan-
ismo por defecto utilizado para construir objetos de este tipo cuando no se especifica ninguna
forma expl´ıcita de construcci´on. As´ı, ser´a invocado autom´aticamente cuando se deba construir un
determinado objeto, sin especificar expl´ıcitamente el tipo de construcci´on requerido, en el momen-
to en que sea necesaria dicha construcci´on, por ejemplo cuando el flujo de ejecuci´on alcanza la
declaraci´on de una variable de dicho tipo (v´ease 11.1.2).
El constructor por defecto es un m´etodo especial de la clase, ya que si el programador no
define ning´un constructor para una determinada clase, entonces el compilador generar´a e im-
plementar´a autom´aticamente dicho constructor con el comportamiento por defecto de invocar
autom´aticamente al constructor por defecto para cada atributo de tipo compuesto miembro de la
clase. N´otese, sin embargo, que en el caso atributos de tipo simple, la implementaci´on autom´atica
del compilador los dejar´a sin inicializar.
No obstante, el programador puede definir el constructor por defecto para una determinada
clase cuando el comportamiento generado autom´aticamente por el compilador no sea el deseado.
Para ello, la definici´on del constructor por defecto se corresponde con la definici´on de un constructor
que no recibe ning´un par´ametro, y la implementaci´on depender´a de las acciones necesarias para
inicializar por defecto el estado interno del objeto que se est´a creando. Por ejemplo, para la clase
Complejo:
class Complejo {
public:
Complejo() ; // Constructor por Defecto
} ;
A continuaci´on se puede ver como ser´ıa la implementaci´on del constructor por defecto:
Complejo::Complejo() // Constructor por Defecto
: real(0.0), imag(0.0) { }
Otra posible implementaci´on podr´ıa ser la siguiente, que invoca expl´ıcitamente al constructor por
defecto para cada atributo miembro de la clase (que en este caso se incializar´a a cero):
Complejo::Complejo() // Constructor por Defecto
: real(), imag() { }
Finalmente, a continuaci´on podemos ver un ejemplo de como ser´ıa una invocaci´on a dicho cons-
tructor por defecto:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 119
real:
imag:
0.0
0.0
c1
real:
imag:
0.0
0.0
c2
//- fichero: main.cpp -----------------------------------------------
#include <iostream>
#include "complejos.hpp"
using namespace std ;
using namespace umalcc ;
int main()
{
Complejo c1, c2 ;
// ...
}
//- fin: main.cpp ---------------------------------------------------
Constructor de Copia
El constructor de copia es el constructor que permite inicializar un determinado objeto como una
copia de otro objeto de su misma clase. As´ı, se invoca automaticamente al inicializar el contenido
de un objeto con el valor de otro objeto de su misma clase, y tambi´en es invocado autom´aticamente
cuando un objeto de dicho tipo se pasa como par´ametro por valor a subprogramas, aunque esto
´ultimo, como se ha explicado previamente, est´a desaconsejado, ya que lo usual es pasar los tipos
compuestos por referencia o por referencia constante.
El constructor de copia es un m´etodo especial de la clase, ya que si el programador no define
dicho constructor de copia para una determinada clase, entonces el compilador generar´a e im-
plementar´a autom´aticamente dicho constructor de copia con el comportamiento por defecto de
invocar autom´aticamente al constructor de copia para cada atributo miembro de la clase, en este
caso, tanto para atributos de tipo simple como de tipo compuesto.
No obstante, el programador puede definir el constructor de copia para una determinada clase
cuando el comportamiento generado autom´aticamente por el compilador no sea el deseado. Para
ello, la definici´on del constructor de copia se corresponde con la definici´on de un constructor
que recibe como ´unico par´ametro por referencia constante un objeto del mismo tipo que la clase
del constructor, y la implementaci´on depender´a de las acciones necesarias para copiar el estado
interno del objeto recibido como par´ametro al objeto que se est´a creando. Por ejemplo, para la
clase Complejo:
class Complejo {
public:
Complejo(const Complejo& c) ; // Constructor de Copia
} ;
y su implementaci´on podr´ıa ser la siguiente, que en este caso coincide con la implementaci´on que
generar´ıa automaticamente el compilador en caso de que no fuese implementado por el progra-
mador:
Complejo::Complejo(const Complejo& o) // Constructor de Copia
: real(o.real), imag(o.imag) { } // Implementaci´on autom´atica
Finalmente, a continuaci´on podemos ver un ejemplo de como ser´ıa una invocaci´on al constructor de
copia (para c3 y c4), junto a una invocaci´on a un constructor espec´ıfico (para c2) y una invocaci´on
al constructor por defecto (para c1), as´ı como la construcci´on por copia (para c5 y c6) de objetos
construidos invocando expl´ıcitamente a los constructores adecuados:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
120 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
real:
imag:
0.0
0.0
c1
2.5
7.3
c2
0.0
0.0
c3
2.5
7.3
c4
0.0
0.0
c5
3.1
4.2
c6
//- fichero: main.cpp -----------------------------------------------
#include <iostream>
#include "complejos.hpp"
using namespace std ;
using namespace umalcc ;
int main()
{
Complejo c1 ; // Construcci´on por defecto
Complejo c2(2.5, 7.3) ; // Construcci´on espec´ıfica
Complejo c3(c1) ; // Construcci´on de copia (de c1)
Complejo c4 = c2 ; // Construcci´on de copia (de c2)
Complejo c5 = Complejo(); // Construcci´on de copia de Complejo por Defecto
Complejo c6 = Complejo(3.1, 4.2); // Construcci´on de copia de Complejo Espec´ıfico
// ...
}
//- fin: main.cpp ---------------------------------------------------
Destructor
El destructor de una clase ser´a invocado autom´aticamente (sin par´ametros actuales) para una
determinada instancia (objeto) de esta clase cuando dicho objeto deba ser destruido, normalmente
´esto suceder´a cuando el flujo de ejecuci´on del programa salga del ´ambito de visibilidad de dicho
objeto (v´ease 11.1.2).
El destructor es un m´etodo especial de la clase, ya que si el programador no define dicho de-
structor para una determinada clase, entonces el compilador generar´a e implementar´a autom´atica-
mente dicho destructor con el comportamiento por defecto de invocar autom´aticamente al destruc-
tor para cada atributo de tipo compuesto miembro de la clase.
No obstante, el programador puede definir el destructor para una determinada clase cuando
el comportamiento generado autom´aticamente por el compilador no sea el deseado. Para ello, el
destructor de la clase se define mediante el s´ımbolo ~ seguido del identificador de la clase y una
lista de par´ametros vac´ıa, y la implementaci´on depender´a de las acciones necesarias para destruir y
liberar los recursos asociados al estado interno del objeto que se est´a destruyendo. Posteriormente,
el destructor invoca autom´aticamente a los destructores de los atributos miembros del objeto para
que ´estos sean destruidos. Por ejemplo, para la clase Complejo:
class Complejo {
public:
~Complejo() ; // Destructor
} ;
y su implementaci´on podr´ıa ser la siguiente, que en este caso coincide con la implementaci´on que
generar´ıa automaticamente el compilador en caso de que no fuese implementado por el progra-
mador:
Complejo::~Complejo() { } // Destructor: Implementaci´on autom´atica
Finalmente, a continuaci´on podemos ver un ejemplo de como se invoca autom´aticamente al de-
structor de los objetos cuando termina su tiempo de vida (para c1, c2, c3 y c4):
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 121
real:
imag:
0.0
0.0
c1
X real:
imag:
2.5
7.3
c2
X real:
imag:
0.0
0.0
c3
X real:
imag:
2.5
7.3
c4
X
//- fichero: main.cpp -----------------------------------------------
#include <iostream>
#include "complejos.hpp"
using namespace std ;
using namespace umalcc ;
int main()
{
Complejo c1 ; // Construcci´on por defecto
Complejo c2(2.5, 7.3) ; // Construcci´on espec´ıfica
Complejo c3(c1) ; // Construcci´on de copia (de c1)
Complejo c4 = c2 ; // Construcci´on de copia (de c2)
// ...
} // Destrucci´on autom´atica de c4, c3, c2 y c1
//- fin: main.cpp ---------------------------------------------------
Operador de Asignaci´on
El operador de asignaci´on define como se realiza la asignaci´on (=) para objetos de esta clase.
No se debe confundir el operador de asignaci´on con el constructor de copia, ya que el constructor
de copia construye un nuevo objeto que no tiene previamente ning´un valor, mientras que en el caso
del operador de asignaci´on, el objeto ya tiene previamente un valor que deber´a ser sustituido por
el nuevo valor. Este valor previo deber´a, en ocasiones, ser destruido antes de realizar la asignaci´on
del nuevo valor.
El operador de asignaci´on (=) es un m´etodo especial de la clase, ya que si el programador no
define dicho operador de asignaci´on para una determinada clase, entonces el compilador generar´a e
implementar´a autom´aticamente dicho operador de asignaci´on con el comportamiento por defecto
de invocar autom´aticamente al operador de asignaci´on para cada atributo miembro de la clase,
tanto para atributos de tipo simple como de tipo compuesto.
No obstante, el programador puede definir el operador de asignaci´on para una determinada
clase cuando el comportamiento generado autom´aticamente por el compilador no sea el deseado.
Para ello, la definici´on del operador de asignaci´on se corresponde con la definici´on de un oper-
ador = que recibe como ´unico par´ametro por referencia constante un objeto del mismo tipo que
la clase del constructor, devuelve una referencia al propio objeto que recibe la asignaci´on, y la
implementaci´on depender´a de las acciones necesarias para destruir el estado interno del objeto que
recibe la asignaci´on y para asignar el estado interno del objeto recibido como par´ametro al objeto
que se est´a creando. Por ejemplo, para la clase Complejo:
class Complejo {
public:
Complejo& operator=(const Complejo& o) ; // Operador de Asignaci´on
} ;
y su implementaci´on podr´ıa ser la siguiente, que en este caso coincide con la implementaci´on que
generar´ıa automaticamente el compilador en caso de que no fuese implementado por el progra-
mador:
Complejo& Complejo::operator=(const Complejo& o) // Operador de Asignacion
{ // Implementaci´on autom´atica
real = o.real ;
imag = o.imag ;
return *this ;
}
El operador de asignaci´on debe devolver el objeto actual (return *this) sobre el que recae la
asignaci´on.
Finalmente, a continuaci´on podemos ver un ejemplo de como ser´ıa una invocaci´on al operador
de asignaci´on (para c3 y c4), junto a una invocaci´on a un constructor espec´ıfico (para c2) y una
invocaci´on al constructor por defecto (para c1), as´ı como la asignaci´on (para c5 y c6) de objetos
construidos invocando expl´ıcitamente a los constructores adecuados:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
122 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
real:
imag:
0.0
0.0
c1
2.5
7.3
c2
0.0
0.0
c3
2.5
7.3
c4
0.0
0.0
c5
3.1
4.2
c6
//- fichero: main.cpp -----------------------------------------------
#include <iostream>
#include "complejos.hpp"
using namespace std ;
using namespace umalcc ;
int main()
{
Complejo c1, c3, c4, c5, c6; // Construcci´on por defecto de c1, c3, c4
Complejo c2(2.5, 7.3) ; // Construcci´on espec´ıfica
c3 = c1 ; // Asignaci´on de c1 a c3
c4 = c2 ; // Asignaci´on de c2 a c4
c5 = Complejo(); // Asignaci´on de Complejo por Defecto
c6 = Complejo(3.1, 4.2); // Asignaci´on de Complejo Espec´ıfico
// ...
}
//- fin: main.cpp ---------------------------------------------------
Hay situaciones en las que los objetos que se asignan tienen representaciones internas complejas,
y en estos casos puede ser necesario destruir el estado interno del objeto que recibe la asignaci´on
antes de asignar el nuevo valor. En este caso, es conveniente comprobar que no se est´a produciendo
una auto-asignaci´on del mismo objeto (x = x), ya que en este caso se destruir´ıa la representaci´on
interna del objeto antes de haberla asignado, con los errores que ello trae asociado. Por lo tanto,
suele ser habitual que el operador de asignaci´on implemente una condici´on para evitar la asignaci´on
en el caso de que se produzca una auto-asignaci´on, de la siguiente forma:
Complejo& Complejo::operator=(const Complejo& o) // Operador de Asignacion
{
if (this != &o) {
// destruir el valor anterior (en este caso no es necesario)
real = o.real ;
imag = o.imag ;
}
return *this ;
}
As´ı, this representa la direcci´on en memoria del objeto que recibe la asignaci´on, y &o representa la
direcci´on en memoria del objeto que se recibe como par´ametro. Si ambas direcciones son diferentes,
entonces significa que son variables diferentes y se puede realizar la asignaci´on.
11.2.1. Ejemplo
Veamos un Tipo Abstracto de Datos Lista de enteros, la cual permite almacenar una secuencia
de n´umero enteros, permitiendo insertar, eliminar, acceder y modificar elementos seg´un la posici´on
que ocupen en la secuencia de n´umeros.
Definici´on
//- fichero: lista.hpp ------------------------------------------
#ifndef _lista_hpp_
#define _lista_hpp_
#include <tr1/array>
namespace umalcc {
class ListaInt {
public:
//----------------------------------------------------------
//-- M´etodos P´ublicos --------------------------------------
//----------------------------------------------------------
// ~ListaInt() ; // Destructor Autom´atico
//------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 123
ListaInt() ;
ListaInt(const ListaInt& o) ;
ListaInt& operator = (const ListaInt& o) ;
//------------------------------
bool llena() const ;
int size() const ;
void clear() ;
//------------------------------
void insertar(int pos, int dato) ;
// PRECOND: ( ! llena() && pos >= 0 && pos <= size())
void eliminar(int pos) ;
// PRECOND: (pos >= 0 && pos < size())
//------------------------------
int acceder(int pos) const ;
// PRECOND: (pos >= 0 && pos < size())
void modificar(int pos, int dato);
// PRECOND: (pos >= 0 && pos < size())
//----------------------------------------------------------
private:
//----------------------------------------------------------
//-- Ctes y Tipos Privados ---------------------------------
//----------------------------------------------------------
static const int MAX = 100;
typedef std::tr1::array<int, MAX> Datos;
//----------------------------------------------------------
//-- Metodos Privados --------------------------------------
//----------------------------------------------------------
void abrir_hueco(int pos) ;
void cerrar_hueco(int pos) ;
//----------------------------------------------------------
//-- Atributos Privados ------------------------------------
//----------------------------------------------------------
int sz; // numero de elementos de la lista
Datos v; // contiene los elementos de la lista
//----------------------------------------------------------
};
}
#endif
//- fin: lista.hpp ----------------------------------------------
Implementaci´on
//- fichero: lista.cpp ------------------------------------------
#include "lista.hpp"
#include <cassert>
namespace umalcc {
//----------------------------------------------------------
//-- M´etodos P´ublicos --------------------------------------
//----------------------------------------------------------
// ListaInt::~ListaInt() { } // Destructor Autom´atico
//----------------------------------
ListaInt::ListaInt() : sz(0), v() { } // Constructor por Defecto
//----------------------------------
ListaInt::ListaInt(const ListaInt& o) // Constructor de Copia
: sz(o.sz), v()
{
for (int i = 0; i < sz; ++i) {
v[i] = o.v[i] ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
124 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
}
//----------------------------------
ListaInt& ListaInt::operator = (const ListaInt& o) // Op. de Asignaci´on
{
if (this != &o) {
sz = o.sz ;
for (int i = 0; i < sz; ++i) {
v[i] = o.v[i] ;
}
}
return *this ;
}
//----------------------------------
bool ListaInt::llena() const
{
return sz == int(v.size());
}
//----------------------------------
int ListaInt::size() const
{
return sz ;
}
//----------------------------------
void ListaInt::clear()
{
sz = 0 ;
}
//----------------------------------
void ListaInt::insertar(int pos, int dato)
{
assert( ! llena() && pos >= 0 && pos <= size()) ;
abrir_hueco(pos) ;
v[pos] = dato ;
}
//----------------------------------
void ListaInt::eliminar(int pos)
{
assert(pos >= 0 && pos < size()) ;
cerrar_hueco(pos) ;
}
//----------------------------------
int ListaInt::acceder(int pos) const
{
assert(pos >= 0 && pos < size()) ;
return v[pos] ;
}
//----------------------------------
void ListaInt::modificar(int pos, int dato)
{
assert(pos >= 0 && pos < size()) ;
v[pos] = dato;
}
//----------------------------------------------------------
//-- Metodos Privados --------------------------------------
//----------------------------------------------------------
void ListaInt::abrir_hueco(int pos)
{
assert(sz < int(v.size())) ;
for (int i = sz; i > pos; --i) {
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 125
v[i] = v[i-1];
}
++sz; // Ahora hay un elemento m´as
}
//----------------------------------
void ListaInt::cerrar_hueco(int pos)
{
assert(sz > 0) ;
--sz; // Ahora hay un elemento menos
for (int i = pos; i < sz; ++i) {
v[i] = v[i+1];
}
}
//----------------------------------
}
//- fin: lista.cpp ----------------------------------------------
Utilizaci´on
//- fichero: main.cpp -----------------------------------------------
#include <iostream>
#include <cctype>
#include <cassert>
#include "lista.hpp"
using namespace std ;
using namespace umalcc ;
//------------------------------------------------------------------
void leer_pos(int& pos, int limite)
{
assert(limite > 0);
do {
cout << "Introduzca posicion ( < " << limite << " ): " ;
cin >> pos;
} while (pos < 0 || pos >= limite);
}
//---------------------------------
void leer_dato(int& dato)
{
cout << "Introduzca un dato: " ;
cin >> dato;
}
//---------------------------------
void leer(ListaInt& lista)
{
int dato ;
lista.clear() ;
cout << "Introduzca datos (0 -> FIN): " << endl ;
cin >> dato ;
while ((dato != 0)&&( ! lista.llena())) {
lista.insertar(lista.size(), dato) ;
cin >> dato ;
}
}
//---------------------------------
void escribir(const ListaInt& lista)
{
cout << "Lista: " ;
for (int i = 0 ; i < lista.size() ; ++i) {
cout << lista.acceder(i) << " " ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
126 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
}
cout << endl ;
}
//---------------------------------
void prueba_asg(const ListaInt& lista)
{
cout << "Constructor de Copia" << endl ;
ListaInt lst(lista) ;
escribir(lst) ;
cout << "Operador de Asignacion" << endl ;
lst = lista ;
escribir(lst) ;
}
//-------------------------------------------------------------------------
char menu()
{
char op ;
cout << endl ;
cout << "X. Fin" << endl ;
cout << "A. Leer Lista" << endl ;
cout << "B. Borrar Lista" << endl ;
cout << "C. Insertar Posicion" << endl ;
cout << "D. Eliminar Posicion" << endl ;
cout << "E. Acceder Posicion" << endl ;
cout << "F. Modificar Posicion" << endl ;
cout << "G. Prueba Copia y Asignacion" << endl ;
do {
cout << endl << " Opcion: " ;
cin >> op ;
op = char(toupper(op)) ;
} while (!((op == ’X’)||((op >= ’A’)&&(op <= ’G’)))) ;
cout << endl ;
return op ;
}
//-------------------------------------------------------------------------
int main()
{
ListaInt lista ;
int dato ;
int pos ;
char op = ’ ’ ;
do {
op = menu() ;
switch (op) {
case ’A’:
leer(lista) ;
escribir(lista) ;
break ;
case ’B’:
lista.clear() ;
escribir(lista) ;
break ;
case ’C’:
if (lista.llena()) {
cout << "Error: Lista llena" << endl ;
} else {
leer_pos(pos, lista.size()+1) ;
leer_dato(dato) ;
lista.insertar(pos, dato) ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 127
escribir(lista) ;
}
break ;
case ’D’:
if (lista.size() == 0) {
cout << "Error: lista vacia" << endl ;
} else {
leer_pos(pos, lista.size()) ;
lista.eliminar(pos) ;
escribir(lista) ;
}
break ;
case ’E’:
if (lista.size() == 0) {
cout << "Error: lista vacia" << endl ;
} else {
leer_pos(pos, lista.size()) ;
cout << "Lista[" << pos << "]: " << lista.acceder(pos) << endl ;
escribir(lista) ;
}
break ;
case ’F’:
if (lista.size() == 0) {
cout << "Error: lista vacia" << endl ;
} else {
leer_pos(pos, lista.size()) ;
leer_dato(dato) ;
lista.modificar(pos, dato) ;
escribir(lista) ;
}
break ;
case ’G’:
prueba_asg(lista) ;
break ;
}
} while (op != ’X’) ;
}
//- fin: main.cpp ---------------------------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
128 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 12
Introducci´on a la Programaci´on
Gen´erica. Plantillas
El lenguaje de programaci´on C++ proporciona soporte a la programaci´on gen´erica mediante
las plantillas (“templates” en ingl´es). Las plantillas proporcionan un mecanismo eficaz para definir
c´odigo (constantes, tipos y subprogramas) gen´ericos parametrizados, que puedan ser instanciados
en “tiempo de compilaci´on”. Estos par´ametros gen´ericos de las plantillas podr´an ser instanciados
con tipos y valores constantes concretos especificados en tiempo de compilaci´on.
Las definiciones gen´ericas deber´an, por lo general, estar visibles en el lugar donde sean ins-
tanciadas, por lo que en el caso de definirse en m´odulos diferentes, deber´an estar definidas en
los ficheros de encabezamiento, para que puedan ser incluidas por todos aquellos m´odulos que las
necesiten.
La definici´on de plantillas, tanto de subprogramas como de tipos comienza con la palabra reser-
vada template, seguida entre delimitadores < ... > por los par´ametros gen´ericos de la definici´on.
Estos par´ametros gen´ericos pueden ser tanto tipos (precedidos por la palabra reservada typename),
como constantes de tipos integrales (char, short, int, unsigned, long) o de tipos gen´ericos
parametrizados con anterioridad (que deben ser instanciados a tipos integrales).
12.1. Subprogramas Gen´ericos
Los subprogramas gen´ericos son ´utiles cuando definen procesamientos gen´ericos que son inde-
pendientes de los tipos concretos sobre los que se aplican. En este caso, simplemente se define
el subprograma utilizando tanto los tipos como constantes gen´ericas mediante sus identificadores
declarados en la directiva template <...>. Posteriormente, cuando se utilicen estos subprogramas
gen´ericos, tanto los tipos como constantes gen´ericas ser´an instanciadas seg´un los tipos y constantes
actuales utilizados en la invocaci´on a dichos subprogramas.
Veamos algunos ejemplos de definici´on de subprogramas gen´ericos:
template <typename Tipo>
inline Tipo maximo(const Tipo& x, const Tipo& y)
{
Tipo max;
if (x > y) {
max = x ;
} else {
max = y ;
}
return max ;
}
template <typename Tipo>
129
130 CAP´ITULO 12. INTRODUCCI ´ON A LA PROGRAMACI ´ON GEN´ERICA. PLANTILLAS
inline void intercambio(Tipo& x, Tipo& y)
{
Tipo aux = x ;
x = y ;
y = aux ;
}
int main()
{
int x = 4 ;
int y = maximo(x, 8) ;
intercambio(x, y) ;
double a = 7.5 ;
double b = maximo(a, 12.0) ;
intercambio(a, b) ;
double c = maximo(a, 12) ; // Error: maximo(double, int) no esta definido
}
En el ejemplo se puede ver que los par´ametros de entrada a los subprogramas se pasan por referencia
(constante o variable), ya que al ser un tipo gen´erico podr´ıa ser tanto un tipo simple como un tipo
estructurado.
Tambi´en puede apreciarse que la instanciaci´on de subprogramas gen´ericos a tipos concretos se
realiza autom´aticamente a partir de la invocaci´on a los mismos, de tal forma que la instanciaci´on de
los par´ametros gen´ericos se realiza por deducci´on a partir del tipo de los par´ametros especificados
en la invocaci´on a los subprogramas.
Sin embargo, hay situaciones donde los par´ametros gen´ericos no pueden ser deducidos de la
propia invocaci´on al subprograma. En este caso, los par´ametros gen´ericos involucrados deben ser
especificados expl´ıcitamente en la llamada.
int main()
{
double a = 7.5 ;
double b = maximo(a, 12.0) ;
double c = maximo<double>(a, 12) ;
}
El siguiente ejemplo de subprograma gen´erico muestra la utilizaci´on de par´ametros gen´ericos
constantes (de tipo integral).
#include <iostream>
#include <string>
#include <tr1/array>
using namespace std ;
using namespace std::tr1 ;
//-------------------------------------
template <typename TipoBase, unsigned SIZE>
void asignar(array<TipoBase, SIZE>& v, const TipoBase& x)
{
for (int i = 0 ; i < int(v.size()) ; ++i) {
v[i] = x ;
}
}
//-------------------------------------
// Requiere que el TipoBase se pueda escribir con <<
template <typename TipoBase, unsigned SIZE>
void escribir(const array<TipoBase, SIZE>& v)
{
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
12.1. SUBPROGRAMAS GEN´ERICOS 131
for (int i = 0 ; i < int(v.size()) ; ++i) {
cout << v[i] << " ";
}
cout << endl ;
}
//-------------------------------------
typedef array<int, 5> AInt ;
void prueba1()
{
AInt a = {{ 1, 2, 3, 4, 5 }};
escribir(a);
asignar(a, 5) ;
escribir(a);
}
//-------------------------------------
typedef array<string, 3> APers ;
void prueba2()
{
APers a = {{ "pepe", "juan", "mar´ıa" }} ;
escribir(a);
asignar(a, "lola") ;
escribir(a);
}
//-------------------------------------
Errores de Instanciaci´on de Par´ametros Gen´ericos
En el caso de que la definici´on de un determinado subprograma sea incorrecta para una deter-
minada instanciaci´on concreta de los par´ametros gen´ericos, se producir´a un error de compilaci´on
indicando el tipo de error. Por ejemplo, si compilamos el siguiente c´odigo, se produce un error de
compilaci´on, ya que el tipo Persona no tiene definido el operador de salida (<<).
#include <iostream>
#include <string>
#include <tr1/array>
using namespace std ;
using namespace std::tr1 ;
//-------------------------------------
// Requiere que el TipoBase se pueda escribir con <<
template <typename TipoBase, unsigned SIZE>
void escribir(const array<TipoBase, SIZE>& v)
{
for (int i = 0 ; i < int(v.size()) ; ++i) {
cout << v[i] << " "; // l´ınea 12
}
cout << endl ;
}
//-------------------------------------
struct Persona {
string nombre ;
string telefono ;
} ;
typedef array<Persona, 4> APersona ;
void prueba3()
{
APersona a = {{
{ "pepe", "00" },
{ "juan", "11" },
{ "mar´ıa", "22" },
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
132 CAP´ITULO 12. INTRODUCCI ´ON A LA PROGRAMACI ´ON GEN´ERICA. PLANTILLAS
{ "carmen", "33" }
}} ;
escribir(a); // l´ınea 30
}
//-------------------------------------
Produce el siguiente mensaje de error de compilaci´on (con GNU GCC):
main.cpp: In function ‘void escribir(const std::tr1::array<_Tp, _Nm>&)
[with TipoBase = Persona, unsigned int SIZE = 4u]’:
main.cpp:30: instantiated from here
main.cpp:12: error: no match for ‘operator<<’ in ‘...’
[......]
Cuando se trabaja con plantillas en C++, hay que tener presente que, a veces, los mensajes de error
pueden ser bastante complicados de interpretar en el caso de errores producidos por instanciaciones
de par´ametros gen´ericos. En estos casos, el primer mensaje de error suele ser el m´as ´util para
guiarnos en su correcci´on.
12.2. Tipos Abstractos de Datos Gen´ericos
Las plantillas tambi´en pueden ser utilizadas para la definici´on de tipos gen´ericos, tanto registros
como clases gen´ericas (TADs gen´ericos).
Como se explic´o al comienzo del cap´ıtulo, las definiciones gen´ericas deben, por lo general, estar
visibles en el lugar donde sean instanciadas, y por lo tanto, en el caso de tipos abstractos gen´ericos
tanto la definici´on como la implementaci´on se realizar´a completamente dentro de los ficheros de
encabezamiento. Adem´as, en esta secci´on introductoria a la programaci´on gen´erica s´olo veremos
los tipos abstractos de datos gen´ericos definidos e implementados “en l´ınea”, y en su versi´on m´as
simple.
La definici´on de una clase gen´erica se realiza de forma similar a la definici´on de una clase no
gen´erica, pero con algunas diferencias:
Se precede la definici´on de la clase con la palabra reservada template seguida entre delim-
itadores < ... > por los par´ametros gen´ericos de la definici´on. Estos par´ametros gen´ericos
pueden ser tanto tipos (precedidos por la palabra reservada typename), como constantes de
tipos integrales (char, short, int, unsigned, long) o de tipos gen´ericos parametrizados con
anterioridad (que deben ser instanciados a tipos integrales).
Los tipos y constantes gen´ericas de la plantilla pueden ser utilizados en la definici´on de la
clase mediante sus identificadores declarados en la directiva template <...>.
La implementaci´on de los m´etodos se debe realizar “en-l´ınea”, es decir, el cuerpo del m´etodo
tambi´en se definir´a dentro de la definici´on de la clase, a diferencia de como se ha visto hasta
ahora, en la cual los m´etodos se implementaban de forma independiente en un fichero de
implementaci´on aparte.
En la instanciaci´on de tipos abstractos gen´ericos, ser´a necesaria la instanciaci´on expl´ıcita de los
par´ametros de los mismos, a diferencia de la instanciaci´on de subprogramas gen´ericos vista an-
teriormente, en la cual los par´ametros gen´ericos se instancian autom´aticamente a trav´es de la
invocaci´on a los subprogramas.
Veamos algunos ejemplos de definici´on y utilizaci´on de tipos abstractos gen´ericos.
Ejemplo 1
Consideremos el tipo abstracto gen´erico denominado Par, que almacena dos elementos de tipos
gen´ericos, y proporciona m´etodos tanto para conocer sus valores, como para su modificaci´on.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
12.2. TIPOS ABSTRACTOS DE DATOS GEN´ERICOS 133
//- par.hpp -------------------------------------------------------------
#ifndef _par_hpp_
#define _par_hpp_
namespace umalcc {
template <typename Tipo_1, typename Tipo_2>
class Par {
public:
//------------------------------
// Destructor Autom´atico
// ~Par() { }
//------------------------------
// Constructor Copia Autom´atico
// Par(const Par& o) : elem_1(o.elem_1), elem_2(o.elem_2) { }
//------------------------------
// Operador Asignaci´on Autom´atico
// Par& operator = (const Par& o)
// {
// if (this != &o) {
// elem_1 = o.elem_1;
// elem_2 = o.elem_2;
// }
// return *this;
// }
//------------------------------
Par() : elem_1(), elem_2() { }
//------------------------------
Par(const Tipo_1& valor_1, const Tipo_2& valor_2)
: elem_1(valor_1), elem_2(valor_2) { }
//------------------------------
Tipo_1 primero() const
{
// Devuelve el valor del primer elemento del par
return elem_1;
}
//------------------------------
Tipo_2 segundo() const
{
// Devuelve el valor del segundo elemento del par
return elem_2;
}
//------------------------------
void asg_primero(const Tipo_1& valor)
{
// Asigna un valor al primer elemento del par
elem_1 = valor;
}
//------------------------------
void asg_segundo(const Tipo_2& valor)
{
// Asigna un valor al segundo elemento del par
elem_2 = valor;
}
//------------------------------
private:
Tipo_1 elem_1;
Tipo_2 elem_2;
};
}
#endif
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
134 CAP´ITULO 12. INTRODUCCI ´ON A LA PROGRAMACI ´ON GEN´ERICA. PLANTILLAS
A continuaci´on podemos ver un ejemplo de su utilizaci´on:
//- main.hpp -------------------------------------------------------------
#include <iostream>
#include <string>
#include "par.hpp"
using namespace std;
using namespace umalcc;
//--------------------------------------
typedef Par<int, int> ParInt;
typedef Par<string, int> ParPer;
//--------------------------------------
int main()
{
ParInt x(3, 7) ; // Crea un Par de enteros con los valores 3 y 7
ParPer p("pepe", 23) ; // Crea un Par de Persona con los valores "pepe" y 23
ParPer q = p; // Copia el par de p a q
cout << x.primero() << " " << x.segundo() << endl;
p.asg_primero("juan");
cout << p.primero() << " " << p.segundo() << endl;
}
//--------------------------------------
Ejemplo 2
En el cap´ıtulo anterior (v´ease 11.2.1) se vio un ejemplo de un TAD Lista de n´umero enteros.
Definici´on e Implementaci´on
Podemos definir una TAD gen´erico Lista que permita almacenar elementos homog´eneos de
un tipo de datos gen´erico, y donde su capacidad m´axima tambi´en est´e parametrizada de forma
gen´erica:
//- fichero: lista.hpp ------------------------------------------
#ifndef _lista_hpp_
#define _lista_hpp_
#include <tr1/array>
#include <cassert>
namespace umalcc {
template <typename TipoBase, unsigned SIZE>
class Lista {
public:
//----------------------------------------------------------
//-- M´etodos P´ublicos --------------------------------------
//----------------------------------------------------------
// ~Lista() { } // Destructor Autom´atico
//------------------------------
Lista() : sz(0), v() { } // Constructor por Defecto
//------------------------------
Lista(const Lista& o) // Constructor de Copia
: sz(o.sz), v()
{
for (int i = 0; i < sz; ++i) {
v[i] = o.v[i] ;
}
}
//------------------------------
Lista& operator = (const Lista& o) // Operador de Asignaci´on
{
if (this != &o) {
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
12.2. TIPOS ABSTRACTOS DE DATOS GEN´ERICOS 135
sz = o.sz ;
for (int i = 0; i < sz; ++i) {
v[i] = o.v[i] ;
}
}
return *this ;
}
//------------------------------
bool llena() const
{
return sz == int(v.size());
}
//------------------------------
int size() const
{
return sz ;
}
//------------------------------
void clear()
{
sz = 0 ;
}
//------------------------------
// PRECOND: ( ! llena() && pos >= 0 && pos <= size())
void insertar(int pos, const TipoBase& dato)
{
assert( ! llena() && pos >= 0 && pos <= size()) ;
abrir_hueco(pos) ;
v[pos] = dato ;
}
//------------------------------
// PRECOND: (pos >= 0 && pos < size())
void eliminar(int pos)
{
assert(pos >= 0 && pos < size()) ;
cerrar_hueco(pos) ;
}
//------------------------------
// PRECOND: (pos >= 0 && pos < size())
TipoBase acceder(int pos) const
{
assert(pos >= 0 && pos < size()) ;
return v[pos] ;
}
//------------------------------
// PRECOND: (pos >= 0 && pos < size())
void modificar(int pos, const TipoBase& dato)
{
assert(pos >= 0 && pos < size()) ;
v[pos] = dato;
}
//----------------------------------------------------------
private:
//----------------------------------------------------------
//-- Ctes y Tipos Privados ---------------------------------
//----------------------------------------------------------
typedef std::tr1::array<TipoBase, SIZE> Datos;
//----------------------------------------------------------
//-- Metodos Privados --------------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
136 CAP´ITULO 12. INTRODUCCI ´ON A LA PROGRAMACI ´ON GEN´ERICA. PLANTILLAS
//----------------------------------------------------------
void abrir_hueco(int pos)
{
assert(sz < int(v.size())) ;
for (int i = sz; i > pos; --i) {
v[i] = v[i-1];
}
++sz; // Ahora hay un elemento m´as
}
//------------------------------
void cerrar_hueco(int pos)
{
assert(sz > 0) ;
--sz; // Ahora hay un elemento menos
for (int i = pos; i < sz; ++i) {
v[i] = v[i+1];
}
}
//----------------------------------------------------------
//-- Atributos Privados ------------------------------------
//----------------------------------------------------------
int sz; // numero de elementos de la lista
Datos v; // contiene los elementos de la lista
//----------------------------------------------------------
};
}
#endif
//- fin: lista.hpp ----------------------------------------------
Utilizaci´on
Veamos a continuaci´on un ejemplo de instanciaci´on y utilizaci´on del TAD Lista gen´erica visto
anteriormente:
//- fichero: main.cpp -----------------------------------------------
#include <iostream>
#include <cctype>
#include <cassert>
#include "lista.hpp"
using namespace std ;
using namespace umalcc ;
//------------------------------------------------------------------
// Instanciaci´on de la Lista gen´erica para almacenar hasta 100 n´umeros enteros
typedef Lista<int, 100> ListaInt;
//------------------------------------------------------------------
void leer_pos(int& pos, int limite)
{
assert(limite > 0);
do {
cout << "Introduzca posicion ( < " << limite << " ): " ;
cin >> pos;
} while (pos < 0 || pos >= limite);
}
//---------------------------------
void leer_dato(int& dato)
{
cout << "Introduzca un dato: " ;
cin >> dato;
}
//---------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
12.2. TIPOS ABSTRACTOS DE DATOS GEN´ERICOS 137
void leer(ListaInt& lista)
{
int dato ;
lista.clear() ;
cout << "Introduzca datos (0 -> FIN): " << endl ;
cin >> dato ;
while ((dato != 0)&&( ! lista.llena())) {
lista.insertar(lista.size(), dato) ;
cin >> dato ;
}
}
//---------------------------------
void escribir(const ListaInt& lista)
{
cout << "Lista: " ;
for (int i = 0 ; i < lista.size() ; ++i) {
cout << lista.acceder(i) << " " ;
}
cout << endl ;
}
//---------------------------------
void prueba_asg(const ListaInt& lista)
{
cout << "Constructor de Copia" << endl ;
ListaInt lst(lista) ;
escribir(lst) ;
cout << "Operador de Asignacion" << endl ;
lst = lista ;
escribir(lst) ;
}
//-------------------------------------------------------------------------
char menu()
{
char op ;
cout << endl ;
cout << "X. Fin" << endl ;
cout << "A. Leer Lista" << endl ;
cout << "B. Borrar Lista" << endl ;
cout << "C. Insertar Posicion" << endl ;
cout << "D. Eliminar Posicion" << endl ;
cout << "E. Acceder Posicion" << endl ;
cout << "F. Modificar Posicion" << endl ;
cout << "G. Prueba Copia y Asignacion" << endl ;
do {
cout << endl << " Opcion: " ;
cin >> op ;
op = char(toupper(op)) ;
} while (!((op == ’X’)||((op >= ’A’)&&(op <= ’G’)))) ;
cout << endl ;
return op ;
}
//-------------------------------------------------------------------------
int main()
{
ListaInt lista ;
int dato ;
int pos ;
bool ok ;
char op = ’ ’ ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
138 CAP´ITULO 12. INTRODUCCI ´ON A LA PROGRAMACI ´ON GEN´ERICA. PLANTILLAS
do {
op = menu() ;
switch (op) {
case ’A’:
leer(lista) ;
escribir(lista) ;
break ;
case ’B’:
lista.clear() ;
escribir(lista) ;
break ;
case ’C’:
if (lista.llena()) {
cout << "Error: Lista llena" << endl ;
} else {
leer_pos(pos, lista.size()+1) ;
leer_dato(dato) ;
lista.insertar(pos, dato) ;
escribir(lista) ;
}
break ;
case ’D’:
if (lista.size() == 0) {
cout << "Error: lista vacia" << endl ;
} else {
leer_pos(pos, lista.size()) ;
lista.eliminar(pos) ;
escribir(lista) ;
}
break ;
case ’E’:
if (lista.size() == 0) {
cout << "Error: lista vacia" << endl ;
} else {
leer_pos(pos, lista.size()) ;
cout << "Lista[" << pos << "]: " << lista.acceder(pos) << endl ;
escribir(lista) ;
}
break ;
case ’F’:
if (lista.size() == 0) {
cout << "Error: lista vacia" << endl ;
} else {
leer_pos(pos, lista.size()) ;
leer_dato(dato) ;
lista.modificar(pos, dato) ;
escribir(lista) ;
}
break ;
case ’G’:
prueba_asg(lista) ;
break ;
}
} while (op != ’X’) ;
}
//- fin: main.cpp ---------------------------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 13
Memoria Din´amica. Punteros
Hasta ahora, todos los programas que se han visto en cap´ıtulos anteriores almacenan su esta-
do interno por medio de variables que son autom´aticamente gestionadas por el compilador. Las
variables son creadas cuando el flujo de ejecuci´on entra en el ´ambito de su definici´on (se reserva
espacio en memoria y se crea el valor de su estado inicial), posteriormente se manipula el estado de
la variable (accediendo o modificando su valor almacenado), y finalmente se destruye la variable
cuando el flujo de ejecuci´on sale del ´ambito donde fue declarada la variable (liberando los recursos
asociados a ella y la zona de memoria utilizada). A este tipo de variables gestionadas autom´atica-
mente por el compilador se las suele denominar variables autom´aticas (tambi´en variables locales),
y residen en una zona de memoria gestionada autom´aticamente por el compilador, la pila de eje-
cuci´on, donde se alojan y desalojan las variables locales (autom´aticas) pertenecientes al ´ambito de
ejecuci´on de cada subprograma.
As´ı, el tiempo de vida de una determinada variable est´a condicionado por el ´ambito de su
declaraci´on. Adem´as, el n´umero de variables autom´aticas utilizadas en un determinado programa
est´a especificado expl´ıcitamente en el propio programa, y por lo tanto su capacidad de almace-
namiento est´a tambi´en especificada y predeterminada por lo especificado expl´ıcitamente en el pro-
grama. Es decir, con la utilizaci´on ´unica de variables autom´aticas, la capacidad de almacenamiento
de un determinado programa est´a predeterminada desde el momento de su programaci´on (tiempo
de compilaci´on), y no puede adaptarse a las necesidades reales de almacenamiento surgidas durante
la ejecuci´on del programa (tiempo de ejecuci´on).1
La gesti´on de memoria din´amica surge como un mecanismo para que el propio programa, du-
rante su ejecuci´on (tiempo de ejecuci´on), pueda solicitar (alojar) y liberar (desalojar) memoria
seg´un las necesidades surgidas durante una determinada ejecuci´on, dependiendo de las circunstan-
cias reales de cada momento de la ejecuci´on del programa en un determinado entorno. Esta ventaja
adicional viene acompa˜nada por un determinado coste asociado a la mayor complejidad que requiere
su gesti´on, ya que en el caso de las variables autom´aticas, es el propio compilador el encargado de
su gesti´on, sin embargo en el caso de las variables din´amicas es el propio programador el que debe,
mediante c´odigo software, gestionar el tiempo de vida de cada variable din´amica, cuando debe
ser alojada y creada, como ser´a utilizada, y finalmente cuando debe ser destruida y desalojada.
Adicionalmente, como parte de esta gesti´on de la memoria din´amica por el propio programador, la
memoria din´amica pasa a ser un recurso que debe gestionar el programador, y se debe preocupar
de su alojo y de su liberaci´on, poniendo especial cuidado y ´enfasis en no perder recursos (perder
zonas de memoria sin liberar y sin capacidad de acceso).
1En realidad esto no es completamente cierto, ya que en el caso de subprogramas recursivos, cada invocaci´on
recursiva en tiempo de ejecuci´on tiene la capacidad de alojar nuevas variables que ser´an posteriormente desalojadas
autom´aticamente cuando la llamada recursiva finaliza.
139
140 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS
13.1. Punteros
El tipo puntero es un tipo simple que permite a un determinado programa acceder a posi-
ciones concretas de memoria, y m´as espec´ıficamente a determinadas zonas de la memoria din´ami-
ca. Aunque el lenguaje de programaci´on C++ permite otras utilizaciones m´as diversas del tipo
puntero, en este cap´ıtulo s´olo se utilizar´a el tipo puntero para acceder a zonas de memoria din´amica.
As´ı, una determinada variable de tipo puntero apunta (o referencia) a una determinada entidad
(variable) de un determinado tipo alojada en la zona de memoria din´amica. Por lo tanto, para un
determinado tipo puntero, se debe especificar tambi´en el tipo de la variable (en memoria din´amica)
a la que apunta, el cual define el espacio que ocupa en memoria y las operaciones (y m´etodos) que
se le pueden aplicar, entre otras cosas.
De este modo, cuando un programa gestiona la memoria din´amica a trav´es de punteros, debe
manejar y gestionar por una parte la propia variable de tipo puntero, y por otra parte la variable
din´amica apuntada por ´este.
Un tipo puntero se define utilizando la palabra reservada typedef seguida del tipo de la variable
din´amica apuntada, un asterisco para indicar que es un puntero a una variable de dicho tipo, y
el identificador que denomina al tipo. Por ejemplo:
typedef int* PInt ; // Tipo Puntero a Entero
struct Persona { // Tipo Persona
string nombre ;
string telefono ;
int edad ;
} ;
typedef Persona* PPersona ; // Tipo Puntero a Persona
As´ı, el tipo PInt es el tipo de una variable que apunta a una variable din´amica de tipo int. Del
mismo modo, el tipo PPersona es el tipo de una variable que apunta a una variable din´amica de
tipo Persona.
Es importante remarcar que el tipo puntero, en s´ı mismo, es un tipo simple, aunque el tipo
apuntado puede ser tanto un tipo simple, como un tipo compuesto.
Es posible definir variables de los tipos especificados anteriormente. N´otese que estas variables
(p1 y p2 en el siguiente ejemplo) son variables autom´aticas (gestionadas autom´aticamente por el
compilador), es decir, se crean autom´aticamente (con un valor indeterminado) al entrar el flujo
de ejecuci´on en el ´ambito de visibilidad de la variable, y posteriormente se destruyen autom´atica-
mente cuando el flujo de ejecuci´on sale del ´ambito de visibilidad de la variable. Por otra parte, las
variables apuntadas por ellos son variables din´amicas (gestionadas por el programador), es decir
el programador se encargar´a de solicitar la memoria din´amica cuando sea necesaria y de liberarla
cuando ya no sea necesaria, durante la ejecuci´on del programa. En el siguiente ejemplo, si las
variables se definen sin inicializar, entonces tendr´an un valor inicial inespecificado:
p1: ?
p2: ?
int main()
{
PInt p1 ;
PPersona p2 ;
}
La constante NULL es una constante especial de tipo puntero que indica que una determinada
variable de tipo puntero no apunta a nada, es decir, especifica que la variable de tipo puntero
que contenga el valor NULL no apunta a ninguna zona de la memoria din´amica. Para utilizar la
constante NULL se debe incluir la biblioteca est´andar <cstddef>. As´ı, se pueden definir las variables
p1 y p2 e inicializarlas a un valor indicando que no apuntan a nada.
p1:
p2:
#include <cstddef>
int main()
{
PInt p1 = NULL ;
PPersona p2 = NULL ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
13.2. GESTI ´ON DE MEMORIA DIN ´AMICA 141
13.2. Gesti´on de Memoria Din´amica
La memoria din´amica la debe gestionar el propio programador, por lo que cuando necesite crear
una determinada variable din´amica, debe solicitar memoria din´amica con el operador new seguido
por el tipo de la variable din´amica a crear. Este operador (new) realiza dos acciones principales,
primero aloja (reserva) espacio en memoria din´amica para albergar a la variable, y despu´es crea
(invocando al constructor especificado) el contenido de la variable din´amica. Finalmente, a la
variable ptr se le asigna el valor del puntero (una direcci´on de memoria) que apunta a la variable
din´amica creada por el operador new. Por ejemplo, para crear una variable din´amica del tipo
Persona definido anteriormente utilizando el constructor por defecto de dicho tipo.
ptr: −−→◦
???
int main()
{
PPersona ptr = new Persona ;
}
En caso de que el tipo de la variable din´amica tenga otros constructores definidos, es posible
utilizarlos en la construcci´on del objeto en memoria din´amica. Por ejemplo, suponiendo que el tipo
Persona tuviese un constructor que reciba el nombre, tel´efono y edad de la persona:
ptr: −−→◦ pepe
111
5
int main()
{
PPersona ptr = new Persona("pepe", "111", 5) ;
}
Posteriormente, tras manipular adecuadamente, seg´un las caracter´ısticas del programa, la memoria
din´amica alojada, llegar´a un momento en que dicha variable din´amica ya no sea necesaria, y su
tiempo de vida llegue a su fin. En este caso, el programador debe liberar expl´ıcitamente dicha
variable din´amica mediante el operador delete de la siguiente forma:
ptr: −−→◦ pepe
111
5
ptr: ?
X
liberada
int main()
{
PPersona ptr = new Persona("pepe", "111", 5) ;
// manipulaci´on ...
delete ptr ;
}
La sentencia delete ptr realiza dos acciones principales, primero destruye la variable din´amica
(invocando a su destructor), y despu´es desaloja (libera) la memoria din´amica reservada para
dicha variable. Finalmente la variable local ptr queda con un valor inespecificado, y ser´a destruida
autom´aticamente por el compilador cuando el flujo de ejecuci´on salga de su ´ambito de declaraci´on.
Si se ejecuta la operaci´on delete sobre una variable de tipo puntero que tiene el valor NULL,
entonces esta operaci´on no hace nada.
En caso de que no se libere (mediante el operador delete) la memoria din´amica apuntada por la
variable ptr, y esta variable sea destruida al terminar su tiempo de vida (su ´ambito de visibilidad),
entonces se perder´a la memoria din´amica a la que apunta, con la consiguiente p´erdida de recursos
que ello conlleva.
ptr: −−→◦ pepe
111
5
pepe
111
5
perdida
int main()
{
PPersona ptr = new Persona("pepe", "111", 5) ;
// manipulaci´on ...
// no se libera la memoria din´amica apuntada por ptr
// se destruye la variable local ptr
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
142 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS
13.3. Operaciones con Variables de Tipo Puntero
Desreferenciaci´on de una Variable de Tipo Puntero
Para acceder a una variable din´amica apuntada por una variable de tipo puntero, se utiliza el
operador unario asterisco (*) precediendo al nombre de la variable de tipo puntero a trav´es de
la cual es apuntada. Por ejemplo, si ptr es una variable local de tipo puntero que apunta a una
variable din´amica de tipo Persona, entonces *ptr es la variable din´amica apuntada, y se trata de
igual forma que cualquier otra variable de tipo Persona.
int main()
{
PPersona ptr = new Persona("pepe", "111", 5) ;
Persona p = *ptr ; // Asigna el contenido de la variable din´amica a la variable p
*ptr = p ; // Asigna el contenido de la variable p a la variable din´amica
delete ptr ; // destruye la variable din´amica y libera su espacio de memoria
}
Sin embargo, si una variable de tipo puntero tiene el valor NULL, entonces desreferenciar la variable
produce un error en tiempo de ejecuci´on que aborta la ejecuci´on del programa.
Es posible, as´ı mismo, acceder a los elementos de la variable apuntada mediante el operador de
desreferenciaci´on. Por ejemplo:
int main()
{
PPersona ptr = new Persona ;
(*ptr).nombre = "pepe" ;
(*ptr).telefono = "111" ;
(*ptr).edad = 5 ;
delete ptr ;
}
N´otese que el uso de los par´entesis es obligatorio debido a que el operador punto (.) tiene mayor
precedencia que el operador de desreferenciaci´on (*). Por ello, en el caso de acceder a los campos
de un registro en memoria din´amica a trav´es de una variable de tipo puntero, es m´as adecuado
utilizar el operador de desreferenciaci´on (->). Por ejemplo:
int main()
{
PPersona ptr = new Persona ;
ptr->nombre = "pepe" ;
ptr->telefono = "111" ;
ptr->edad = 5 ;
delete ptr ;
}
Este operador tambi´en se utiliza para invocar a m´etodos de un objeto si ´este se encuentra alojado
en memoria din´amica. Por ejemplo:
#include <iostream>
using namespace std ;
class Numero {
public:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
13.3. OPERACIONES CON VARIABLES DE TIPO PUNTERO 143
Numero(int v) : val(v) {}
int valor() const { return val ; }
private:
int val ;
} ;
typedef Numero* PNumero ;
int main()
{
PNumero ptr = new Numero(5) ;
cout << ptr->valor() << endl ;
delete ptr ;
}
Asignaci´on de Variables de Tipo Puntero
El puntero nulo (NULL) se puede asignar a cualquier variable de tipo puntero. Por ejemplo:
p1: ?
p1:
int main()
{
PPersona p1 ;
// ...
p1 = NULL ;
// ...
}
El resultado de crear una variable din´amica con el operador new se puede asignar a una variable
de tipo puntero al tipo de la variable din´amica creada. Por ejemplo:
p1: ?
p1: −−→◦ pepe
111
5
int main()
{
PPersona p1 ;
// ...
p1 = new Persona("pepe", "111", 5) ;
// ...
}
As´ı mismo, a una variable de tipo puntero se le puede asignar el valor de otra variable puntero. En
este caso, ambas variables de tipo puntero apuntar´an a la misma variable din´amica, que ser´a com-
partida por ambas. Si se libera la variable din´amica apuntada por una de ellas, la variable din´amica
compartida se destruye, su memoria se desaloja y ambas variables locales de tipo puntero quedan
con un valor inespecificado.
p1: −−→◦
p2: −−→◦
pepe
111
5
p1: ?
p2: ?
X
liberada
int main()
{
PPersona p1 = new Persona("pepe", "111", 5) ;
PPersona p2 ;
// ...
p2 = p1 ;
// ...
delete p1 ;
}
En la operaci´on de asignaci´on, el valor anterior que tuviese la variable de tipo puntero se pierde,
por lo que habr´a que tener especial cuidado de que no se pierda la variable din´amica que tuviese
asignada, si tuviese alguna.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
144 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS
p1: −−→◦ pepe
111
5
p1: pepe
111
5
perdida
int main()
{
PPersona p1 = new Persona("pepe", "111", 5) ;
// ...
p1 = NULL ; // se pierde el valor anterior
// ...
delete p1 ;
}
Comparaci´on de Variables de Tipo Puntero
Las variables del mismo tipo puntero se pueden comparar entre ellas por igualdad (==) o
desigualdad (!=), para comprobar si apuntan a la misma variable din´amica. As´ı mismo, tambi´en
se pueden comparar por igualdad o desigualdad con el puntero nulo (NULL) para saber si apunta a
alguna variable din´amica, o por el contrario no apunta a nada. Por ejemplo:
int main()
{
PPersona p1, p2 ;
// ...
if (p1 == p2) {
// ...
}
if (p1 != NULL) {
// ...
}
}
13.4. Paso de Par´ametros de Variables de Tipo Puntero
El tipo puntero es un tipo simple, y por lo tanto se tratar´a como tal. En caso de paso de
par´ametros de tipo puntero, si es un par´ametro de entrada, entonces se utilizar´a el paso por valor,
y si es un par´ametro de salida o de entrada/salida, entonces se utilizar´a el paso por referencia.
Hay que ser consciente de que un par´ametro de tipo puntero puede apuntar a una variable
din´amica, y en este caso, a partir del par´ametro se puede acceder a la variable apuntada.
As´ı, si el par´ametro se pasa por valor, entonces se copia el valor del puntero del par´ametro
actual (en la invocaci´on) al par´ametro formal (en el subprograma), por lo que ambos apuntar´an
a la misma variable din´amica compartida, y en este caso, si se modifica el valor almacenado en la
variable din´amica, este valor se ver´a afectado, as´ı mismo, en el exterior del subprograma, aunque
el par´ametro haya sido pasado por valor.
Por otra parte, las funciones tambi´en pueden devolver valores de tipo puntero.
void modificar(PPersona& p) ;
PPersona buscar(PPersona l, const string& nombre) ;
13.5. Listas Enlazadas Lineales
Una de las principales aplicaciones de la Memoria Din´amica es el uso de estructuras enlazadas,
de tal forma que un campo o atributo de la variable din´amica es a su vez tambi´en de tipo puntero,
por lo que puede apuntar a otra variable din´amica que tambi´en tenga un campo o atributo de
tipo puntero, el cual puede volver a apuntar a otra variable din´amica, y as´ı sucesivamente, tantas
veces como sea necesario, hasta que un puntero con el valor NULL indique el final de la estructura
enlazada (lista enlazada).
As´ı, en este caso, vemos que un campo de la estructura es de tipo puntero a la propia estructura,
por lo que es necesario definir el tipo puntero antes de definir la estructura. Sin embargo, la
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
13.5. LISTAS ENLAZADAS LINEALES 145
estructura todav´ıa no ha sido definida, por lo que no se puede definir un puntero a ella. Por
ello es necesario realizar una declaraci´on adelantada de un tipo incompleto del tipo de la variable
din´amica, donde se declara que un determinado identificador es una estructura o clase, pero no se
definen sus componentes.
lista: −−→◦ −−−−−→◦
pepe
−−−−−→◦
juan mar´ıa
struct Nodo ; // Declaraci´on adelantada del tipo incompleto Nodo
typedef Nodo* PNodo ; // Definici´on de tipo Puntero a tipo incompleto Nodo
struct Nodo { // Definici´on del tipo Nodo
PNodo sig ; // Enlace a la siguiente estructura din´amica
string dato ; // Dato almacenado en la lista
} ;
void escribir(PNodo lista)
{
PNodo ptr = lista;
while (ptr != NULL) {
cout << ptr->dato << endl ;
}
}
PNodo buscar(PNodo lista, const string& dt)
{
PNodo ptr = lista ;
while ((ptr != NULL)&&(ptr->dato != dt)) {
ptr = ptr->sig ;
}
return ptr ;
}
void leer_inversa(PNodo& lista)
{
lista = NULL ;
string dt ;
cin >> dt ;
while (dt != "fin") {
PNodo ptr = new Nodo ;
ptr->dato = dt ;
ptr->sig = lista ;
lista = ptr ;
cin >> dt ;
}
}
void destruir(PNodo& lista)
{
while (lista != NULL) {
PNodo ptr = lista ;
lista = lista->sig ;
delete ptr ;
}
}
int main()
{
PNodo lista ;
leer_inversa(lista) ;
escribir(lista) ;
PNodo ptr = buscar(lista, "juan");
if (ptr != NULL) {
cout << ptr->dato << endl;
}
destruir(lista) ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
146 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS
Insertar al Principio
lista: ◦ -
mar´ıa
PNodo ptr = new Nodo("pepe") ;
lista: ◦ -
mar´ıa
ptr: −−→◦ ◦
pepe
ptr->sig = lista ;
lista: ◦ -
mar´ıa
ptr: −−→◦ ◦
pepe


3
lista = ptr ;
lista: ◦
@
@R mar´ıa
ptr: −−→◦ ◦
pepe


3
lista: −−→◦
mar´ıa
−−−−−→◦
pepe
Insertar Detr´as
ant: −−→◦
mar´ıa
◦
pepe
-
PNodo ptr = new Nodo(juan) ;
ant: −−→◦
mar´ıa
◦
pepe
-
ptr: ◦ - ◦
juan
ptr-sig = ant-sig ;
ant: −−→◦
mar´ıa
◦
pepe
-
ptr: ◦ - ◦
juan


3
ant-sig = ptr ;
ant: −−→◦
mar´ıa
◦
pepe
Q
Q
Qs
ptr: ◦ - ◦
juan


3
ant: −−→◦
mar´ıa
−−−−−→◦
pepe
−−−−−→◦
juan
struct Nodo ;
typedef Nodo* PNodo ;
struct Nodo {
PNodo sig ;
string dato ;
} ;
void insertar_principio(PNodo lista, const string dt)
{
PNodo ptr = new Nodo ;
ptr-dato = dt ;
ptr-sig = lista ;
lista = ptr ;
}
void insertar_final(PNodo ant, const string dt)
{
PNodo ptr = new Nodo ;
ptr-dato = dt ;
ptr-sig = NULL ;
if (lista == NULL) {
lista = ptr ;
} else {
PNodo act = lista ;
while (act-sig != NULL) {
act = act-sig ;
}
act-sig = ptr ;
}
}
PNodo situar(PNodo lista, int pos)
{
PNodo ptr = lista;
while ((ptr != NULL)(pos  0)) {
ptr = ptr-sig;
--pos;
}
return ptr;
}
void insertar_pos(PNodo lista, int pos, const string dt)
{
PNodo ptr = new Nodo ;
ptr-dato = dt ;
if (pos  1) {
ptr-sig = lista ;
lista = ptr ;
} else {
PNodo ant = situar(lista, pos - 1);
if (ant != NULL) {
ptr-sig = ant-sig ;
ant-sig = ptr ;
}
}
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
13.5. LISTAS ENLAZADAS LINEALES 147
Eliminar del Principio
lista: −−→◦
mar´ıa
−−−−−→◦
pepe
ptr = lista ;
lista: ◦
@@R mar´ıa
ptr: −−→◦ ◦
pepe


3
lista = lista-sig ;
lista: ◦ -
mar´ıa
ptr: −−→◦ ◦
pepe


3
delete ptr ;
lista: ◦ -
mar´ıa
ptr: ?
Xliberada
lista: −−→◦
mar´ıa
Eliminar de Detr´as
ant: −−→◦
mar´ıa
−−−−−→◦
pepe
−−−−−→◦
juan
ptr = ant-sig ;
ant: −−→◦
mar´ıa
◦
pepe
Q
Q
Qs
ptr: ◦ - ◦
juan


3
ant-sig = ptr-sig ;
ant: −−→◦
mar´ıa
◦
pepe
-
ptr: ◦ - ◦
juan


3
delete ptr ;
ant: −−→◦
mar´ıa
◦
pepe
-
ptr: ?
Xliberada
ant: −−→◦
mar´ıa
−−−−−→◦
pepe
void eliminar_primero(PNodo lista)
{
if (lista != NULL) {
PNodo ptr = lista ;
lista = lista-sig ;
delete ptr ;
}
}
void eliminar_ultimo(PNodo lista)
{
if (lista != NULL) {
if (lista-sig == NULL) {
delete lista ;
lista = NULL ;
} else {
PNodo ant = lista ;
PNodo act = ant-sig ;
while (act-sig != NULL) {
ant = act ;
act = act-sig ;
}
delete act ;
ant-sig = NULL ;
}
}
}
void eliminar_pos(PNodo lista, int pos)
{
if (lista != NULL) {
if (pos  1) {
PNodo ptr = lista ;
lista = lista-sig ;
delete ptr ;
} else {
PNodo ant = situar(lista, pos - 1) ;
if ((ant != NULL)(ant-sig != NULL)) {
PNodo ptr = ant-sig ;
ant-sig = ptr-sig ;
delete ptr ;
}
}
}
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
148 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS
void insertar_ord(PNodo lista,
const string dt)
{
PNodo ptr = new Nodo ;
ptr-dato = dt ;
if ((lista==NULL)||(dt  lista-dato)) {
ptr-sig = lista ;
lista = ptr ;
} else {
PNodo ant = lista ;
PNodo act = ant-sig ;
while ((act!=NULL)(act-dato=dt)){
ant = act ;
act = act-sig ;
}
ptr-sig = ant-sig ;
ant-sig = ptr ;
}
}
void eliminar_elem(PNodo lista, const string dt)
{
if (lista != NULL) {
if (lista-dato == dt) {
PNodo ptr = lista ;
lista = lista-sig ;
delete ptr ;
} else {
PNodo ant = lista ;
PNodo act = ant-sig ;
while ((act != NULL)(act-dato != dt)) {
ant = act ;
act = act-sig ;
}
if (act != NULL) {
ant-sig = act-sig ;
delete act ;
}
}
}
}
PNodo duplicar(PNodo lista)
{
PNodo nueva = NULL;
if (lista != NULL) {
nueva = new Nodo ;
nueva-dato = lista-dato ;
PNodo u = nueva ;
PNodo p = lista-sig ;
while (p != NULL) {
u-sig = new Nodo ;
u-sig-dato = p-dato ;
u = u-sig ;
p = p-sig ;
}
u-sig = NULL ;
}
return nueva;
}
void purgar(PNodo lista, const string dt)
{
while ((lista != NULL)(dt == lista-dato)) {
PNodo ptr = lista ;
lista = lista-sig ;
delete ptr ;
}
if (lista != NULL) {
PNodo ant = lista;
PNodo act = lista-sig;
while (act != NULL) {
if (dt == act-dato) {
ant-sig = act-sig ;
delete act ;
} else {
ant = act;
}
act = ant-sig;
}
}
}
13.6. Abstracci´on en la Gesti´on de Memoria Din´amica
La gesti´on de memoria din´amica por parte del programador se basa en estructuras de progra-
maci´on de bajo nivel, las cuales son propensas a errores de programaci´on y p´erdida de recursos de
memoria. Adem´as, entremezclar sentencias de gesti´on de memoria, de bajo nivel, con sentencias
aplicadas al dominio de problema a resolver suele dar lugar a c´odigo no legible y propenso a errores.
Por lo tanto se hace necesario aplicar niveles de abstracci´on que aislen la gesti´on de memo-
ria din´amica (de bajo nivel) del resto del c´odigo m´as directamente relacionado con la soluci´on
del problema. Para ello, los tipos abstractos de datos proporcionan el mecanismo adecuado para
aplicar la abstracci´on a estas estructuras de datos basadas en la gesti´on de memoria din´amica,
adem´as de proporcionar una herramienta adecuada para la gesti´on de memoria din´amica, ya que
los destructores se pueden encargar de liberar los recursos asociados a un determinado objeto. Por
ejemplo:
template typename Tipo
class Lista {
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
13.7. TIPO ABSTRACTO DE DATOS LISTA ENLAZADA GEN´ERICA 149
public:
~Lista() { destruir() ; }
Lista() : sz(0), lista(NULL) { }
void insertar(int pos, const Tipo d) { ... }
void eliminar(int pos) { ... }
// ...
} ;
13.7. Tipo Abstracto de Datos Lista Enlazada Gen´erica
Aunque las listas enlazadas se pueden programar directamente entremezcladas con el c´odigo
de resoluci´on del problema en cuesti´on, es conveniente que su gesti´on se realice dentro de una
abstracci´on que aisle su tratamiento y permita una mejor gesti´on de sus recursos.
Con objeto de facilitar su estudio, el siguiente ejemplo es una implementaci´on simplificada del
tipo abstracto de datos lista gen´erica de elementos homog´eneos. N´otese, sin embargo, que otras
implementaciones pueden mejorar notablemente su eficiencia.
//- lista.hpp -------------------------------------------------------------
#ifndef _lista_hpp_
#define _lista_hpp_
#include cstddef
#include cassert
namespace umalcc {
template typename Tipo
class Lista {
public:
//-- M´etodos P´ublicos ----------
// Destructor
~Lista() { destruir(lista) ; }
// Constructor por Defecto
Lista() : sz(0), lista(NULL) { }
// Constructor de Copia
Lista(const Lista o)
: sz(o.sz), lista(duplicar(o.lista)) { }
// Operador de Asignaci´on
Lista operator = (const Lista o)
{
if (this != o) {
destruir(lista) ;
sz = o.sz ;
lista = duplicar(o.lista) ;
}
return *this ;
}
// Elimina todos los elementos de la lista actual (queda vacia)
void clear()
{
destruir(lista) ;
sz = 0 ;
}
// Devuelve el numero de elementos almacenados
int size() const
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
150 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS
{
return sz ;
}
// Devuelve true si el numero de elementos almacenados
// alcanza la capacidad maxima de almacenamiento
bool llena() const
{
return false;
}
// PRECOND: ( ! llena()  0 = pos  pos = size())
// Inserta (dato) en la lista actual en la posicion (pos)
void insertar(int pos, const Tipo d)
{
assert(! llena()
 0 = pos  pos = size()) ;
insertar_pos(lista, pos, d) ;
++sz ;
}
// PRECOND: (0 = pos  pos  size())
// Elimina de la lista actual el elemento que ocupa la posicion (pos)
void eliminar(int pos)
{
assert(0 = pos  pos  size()) ;
eliminar_pos(lista, pos) ;
--sz ;
}
// PRECOND: (0 = pos  pos  size())
// Devuelve el elemento de la lista actual que ocupa la posicion (pos)
Tipo acceder(int pos) const
{
assert(0 = pos  pos  size()) ;
PNodo ptr = situar(lista, pos) ;
assert(ptr != NULL) ;
return ptr-dato ;
}
// PRECOND: (0 = pos  pos  size())
// Asigna (dato) al elemento de la lista actual que ocupa la posicion (pos)
void modificar(int pos, const Tipo d)
{
assert(0 = pos  pos  size()) ;
PNodo ptr = situar(lista, pos) ;
assert(ptr != NULL) ;
ptr-dato = d ;
}
private:
//-- Tipos Privados ------
struct Nodo ;
typedef Nodo* PNodo ;
struct Nodo {
PNodo sig ;
Tipo dato ;
} ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
13.7. TIPO ABSTRACTO DE DATOS LISTA ENLAZADA GEN´ERICA 151
//-- Atributos privados --
int sz ;
PNodo lista ;
//-- M´etodos Privados ----------
void destruir(PNodo lst) const
{
while (lst != NULL) {
PNodo ptr = lst ;
lst = lst-sig ;
delete ptr ;
}
}
PNodo situar(PNodo lst, int pos) const
{
PNodo ptr = lst;
while ((ptr != NULL)(pos  0)) {
ptr = ptr-sig;
--pos;
}
return ptr;
}
void insertar_pos(PNodo lst, int pos,
const Tipo dt) const
{
PNodo ptr = new Nodo ;
ptr-dato = dt ;
if (pos  1) {
ptr-sig = lst ;
lst = ptr ;
} else {
PNodo ant = situar(lst, pos - 1);
if (ant != NULL) {
ptr-sig = ant-sig ;
ant-sig = ptr ;
}
}
}
void eliminar_pos(PNodo lst, int pos) const
{
if (lst != NULL) {
if (pos  1) {
PNodo ptr = lst ;
lst = lst-sig ;
delete ptr ;
} else {
PNodo ant = situar(lst, pos - 1) ;
if ((ant != NULL)(ant-sig != NULL)) {
PNodo act = ant-sig ;
ant-sig = act-sig ;
delete act ;
}
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
152 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS
}
}
PNodo duplicar(PNodo lst) const
{
PNodo nueva = NULL;
if (lst != NULL) {
nueva = new Nodo ;
nueva-dato = lst-dato ;
PNodo u = nueva ;
PNodo p = lst-sig ;
while (p != NULL) {
u-sig = new Nodo ;
u-sig-dato = p-dato ;
u = u-sig ;
p = p-sig ;
}
u-sig = NULL ;
}
return nueva;
}
} ; // class
} // namespace
#endif
//-------------------------------------------------------------------------
Se puede apreciar como tanto el constructor de copia, como el operador de asignaci´on duplican la
lista almacenada, y por el contrario tanto el destructor como el m´etodo clear() liberan todos los
recursos que el objeto tenga asignados. As´ı mismo, el m´etodo duplicar invoca a la destrucci´on de
los recursos que tuviese antes de duplicar y copiar la nueva lista.
A continuaci´on se puede ver un ejemplo de utilizaci´on del tipo abstracto de datos lista gen´erica
definido anteriormente.
//- fichero: main.cpp -----------------------------------------------
#include iostream
#include cctype
#include cassert
#include lista.hpp
using namespace std ;
using namespace umalcc ;
//------------------------------------------------------------------
// Instanciaci´on de la Lista gen´erica para almacenar n´umeros enteros
typedef Listaint ListaInt;
//------------------------------------------------------------------
void leer_pos(int pos, int limite)
{
assert(limite  0);
do {
cout  Introduzca posicion (    limite   ):  ;
cin  pos;
} while (pos  0 || pos = limite);
}
//---------------------------------
void leer_dato(int dato)
{
cout  Introduzca un dato:  ;
cin  dato;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
13.7. TIPO ABSTRACTO DE DATOS LISTA ENLAZADA GEN´ERICA 153
//---------------------------------
void leer(ListaInt lista)
{
int dato ;
lista.clear() ;
cout  Introduzca datos (0 - FIN):   endl ;
cin  dato ;
while ((dato != 0)( ! lista.llena())) {
lista.insertar(lista.size(), dato) ;
cin  dato ;
}
}
//---------------------------------
void escribir(const ListaInt lista)
{
cout  Lista:  ;
for (int i = 0 ; i  lista.size() ; ++i) {
cout  lista.acceder(i)    ;
}
cout  endl ;
}
//---------------------------------
void prueba_asg(const ListaInt lista)
{
cout  Constructor de Copia  endl ;
ListaInt lst(lista) ;
escribir(lst) ;
cout  Operador de Asignacion  endl ;
lst = lista ;
escribir(lst) ;
}
//-------------------------------------------------------------------------
char menu()
{
char op ;
cout  endl ;
cout  X. Fin  endl ;
cout  A. Leer Lista  endl ;
cout  B. Borrar Lista  endl ;
cout  C. Insertar Posicion  endl ;
cout  D. Eliminar Posicion  endl ;
cout  E. Acceder Posicion  endl ;
cout  F. Modificar Posicion  endl ;
cout  G. Prueba Copia y Asignacion  endl ;
do {
cout  endl   Opcion:  ;
cin  op ;
op = char(toupper(op)) ;
} while (!((op == ’X’)||((op = ’A’)(op = ’G’)))) ;
cout  endl ;
return op ;
}
//-------------------------------------------------------------------------
int main()
{
ListaInt lista ;
int dato ;
int pos ;
char op = ’ ’ ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
154 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS
do {
op = menu() ;
switch (op) {
case ’A’:
leer(lista) ;
escribir(lista) ;
break ;
case ’B’:
lista.clear() ;
escribir(lista) ;
break ;
case ’C’:
if (lista.llena()) {
cout  Error: Lista llena  endl ;
} else {
leer_pos(pos, lista.size()+1) ;
leer_dato(dato) ;
lista.insertar(pos, dato) ;
escribir(lista) ;
}
break ;
case ’D’:
if (lista.size() == 0) {
cout  Error: lista vacia  endl ;
} else {
leer_pos(pos, lista.size()) ;
lista.eliminar(pos) ;
escribir(lista) ;
}
break ;
case ’E’:
if (lista.size() == 0) {
cout  Error: lista vacia  endl ;
} else {
leer_pos(pos, lista.size()) ;
cout  Lista[  pos  ]:   lista.acceder(pos)  endl ;
escribir(lista) ;
}
break ;
case ’F’:
if (lista.size() == 0) {
cout  Error: lista vacia  endl ;
} else {
leer_pos(pos, lista.size()) ;
leer_dato(dato) ;
lista.modificar(pos, dato) ;
escribir(lista) ;
}
break ;
case ’G’:
prueba_asg(lista) ;
break ;
}
} while (op != ’X’) ;
}
//- fin: main.cpp ---------------------------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 14
Introducci´on a los Contenedores
de la Biblioteca Est´andar (STL)
Los contenedores de la biblioteca est´andar proporcionan un m´etodo general para almacenar
y acceder a una colecci´on de elementos homog´eneos, proporcionando cada uno de ellos diferentes
caracter´ısticas que los hacen adecuados a diferentes necesidades.
En este cap´ıtulo introductorio se mostrar´an las principales operaciones que se pueden realizar
con los siguientes contenedores: el tipo vector y el tipo deque (para el tipo array v´ease 6.4).
As´ı como con los siguientes adaptadores de contenedores: el tipo stack y el tipo queue de la
biblioteca est´andar, que implementan el TAD Pila y el TAD Cola respectivamente.
La biblioteca est´andar tambi´en define otros tipos de contenedores optimizados para diferentes
circunstancias, pero no ser´an explicados debido a que su estudio requiere mayores conocimientos
que los obtenidos en un curso introductorio.
Contenedor Tipo Acceso Inserci´on Eliminaci´on
stack (adaptador) TAD Pila Directo (al final) Al final Al final
queue (adaptador) TAD Cola Directo (al principio) Al final Al principio
array Secuencia Directo (pos) – –
vector Secuencia Directo (pos) Al final Al final
deque Secuencia Directo (pos) Al final + al principio Al final + Al principio
list Secuencia Secuencial (bidir) Cualquier posici´on Cualquier posici´on
forward_list Secuencia Secuencial (fw) Cualquier posici´on Cualquier posici´on
map Asociativo Binario por clave Por Clave Por Clave
set Asociativo Binario por clave Por Clave Por Clave
multimap Asociativo Binario por clave Por Clave Por Clave
multiset Asociativo Binario por clave Por Clave Por Clave
unordered_map Asociativo Hash por clave Por Clave Por Clave
unordered_set Asociativo Hash por clave Por Clave Por Clave
unordered_multimap Asociativo Hash por clave Por Clave Por Clave
unordered_multiset Asociativo Hash por clave Por Clave Por Clave
Paso de Par´ametros de Contenedores
Los contenedores de la biblioteca est´andar se pueden pasar como par´ametros a subprogramas
como cualquier otro tipo compuesto, y por lo tanto se aplican los mecanismos de paso de par´ametros
para tipos compuestos explicados en la secci´on 6.1. Es decir, los par´ametros de entrada se pasar´an
por referencia constante, mientras que los par´ametros de salida y entrada/salida se pasar´an por
referencia.
As´ı mismo, como norma general, salvo excepciones, no es adecuado que las funciones retornen
valores de tipos de los contenedores, debido a la sobrecarga que generalmente conlleva dicha op-
eraci´on para el caso de los tipos compuestos. En estos casos suele ser m´as adecuado que el valor
se devuelva como un par´ametro por referencia.
155
156CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL)
14.1. Vector
El contenedor de tipo vector... representa una secuencia de elementos homog´eneos opti-
mizada para el acceso directo a los elementos seg´un su posici´on, as´ı como tambi´en para la inserci´on
de elementos al final de la secuencia y para la eliminaci´on de elementos del final de la secuencia.
Para utilizar un contenedor de tipo vector se debe incluir la biblioteca est´andar vector, de tal
forma que sus definiciones se encuentran dentro del espacio de nombres std:
#include vector
El tipo vector es similar al tipo array, salvo en el hecho de que los vectores se caracterizan
porque su tama˜no puede crecer en tiempo de ejecuci´on dependiendo de las necesidades surgidas
durante la ejecuci´on del programa. Por ello, a diferencia de los arrays, no es necesario especificar
un tama˜no fijo y predeterminado en tiempo de compilaci´on respecto al n´umero de elementos que
pueda contener.
El n´umero m´aximo de elementos que se pueden almacenar en una variable de tipo vector
no est´a especificado, y se pueden almacenar elementos mientras haya capacidad suficiente en la
memoria del ordenador donde se ejecute el programa.
N´otese que en los siguientes ejemplos, por simplicidad, tanto el n´umero de elementos como el
valor inicial de los mismos est´an especificados mediante valores constantes, sin embargo, tambi´en
se pueden especificar como valores de variables y expresiones calculados en tiempo de ejecuci´on.
Instanciaci´on del Tipo Vector
Se pueden definir expl´ıcitamente instanciaciones del tipo vector para tipos de elementos con-
cretos mediante la declaraci´on typedef. Por ejemplo la siguiente definici´on declara el tipo Vect_Int
como un tipo vector de n´umeros enteros.
typedef std::vectorint Vect_Int ;
Las siguientes definiciones declaran el tipo Matriz como un vector de dos dimensiones de n´umeros
enteros.
typedef std::vectorint Fila ;
typedef std::vectorFila Matriz ;
Construcci´on de un Objeto de Tipo Vector
Se pueden definir variables de un tipo vector previamente definido expl´ıcitamente, o directa-
mente de la instanciaci´on del tipo. Por ejemplo, el siguiente c´odigo define dos variables (v1 y v2)
de tipo vector de n´umeros enteros, as´ı como la variable m de tipo vector de dos dimensiones de
n´umeros enteros.
int main()
{
Vect_Int v1 ; // vector de enteros vac´ıo
std::vectorint v2 ; // vector de enteros vac´ıo
Matriz m ; // vector de dos dimensiones de enteros vac´ıo
// ...
}
El constructor por defecto del tipo vector crea un objeto vector inicialmente vac´ıo, sin elementos.
Posteriormente se podr´an a˜nadir y eliminar elementos cuando sea necesario.
Tambi´en es posible crear un objeto vector con un n´umero inicial de elementos con un valor inicial
por defecto, al que posteriormente se le podr´an a˜nadir nuevos elementos. Este n´umero inicial de
elementos puede ser tanto una constante, como el valor de una variable calculado en tiempo de
ejecuci´on.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
14.1. VECTOR 157
int main()
{
Vect_Int v1(10) ; // vector con 10 enteros con valor inicial 0
Matriz m(10, Fila(5)) ; // matriz de 10x5 enteros con valor inicial 0
// ...
}
As´ı mismo, tambi´en se puede especificar el valor que tomar´an los elementos creados inicialmente.
int main()
{
Vect_Int v1(10, 3) ; // vector con 10 enteros con valor inicial 3
Matriz m(10, Fila(5, 3)) ; // matriz de 10x5 enteros con valor inicial 3
// ...
}
Tambi´en es posible inicializar un vector con el contenido de otro vector de igual tipo, invocando al
constructor de copia:
int main()
{
Vect_Int v1(10, 3) ; // vector con 10 enteros con valor inicial 3
Vect_Int v2(v1) ; // vector con el mismo contenido de v1
Vect_Int v3 = v1 ; // vector con el mismo contenido de v1
Vect_Int v4 = Vect_Int(7, 5) ; // vector con 7 elementos de valor 5
// ...
}
Asignaci´on de un Objeto de Tipo Vector
Es posible la asignaci´on de vectores de igual tipo. En este caso, se destruye el valor anterior del
vector destino de la asignaci´on.
int main()
{
Vect_Int v1(10, 3) ; // vector con 10 enteros con valor inicial 3
Vect_Int v2 ; // vector de enteros vac´ıo
v2 = v1 ; // asigna el contenido de v1 a v2
v2.assign(5, 7) ; // asigna 5 enteros con valor inicial 7
v2 = Vect_Int(5, 7) ; // asigna un vector con 5 elementos de valor 7
}
As´ı mismo, tambi´en es posible intercambiar (swap en ingl´es) de forma eficiente el contenido entre
dos vectores utilizando el m´etodo swap. Por ejemplo:
int main()
{
Vect_Int v1(10, 5) ; // v1 = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 }
Vect_Int v2(5, 7) ; // v2 = { 7, 7, 7, 7, 7 }
v1.swap(v2) ; // v1 = { 7, 7, 7, 7, 7 }
// v2 = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 }
}
Control sobre los Elementos de un Vector
El n´umero de elementos actualmente almacenados en un vector se obtiene mediante el m´etodo
size(). Por ejemplo:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
158CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL)
int main()
{
Vect_Int v1(10, 3) ; // vector con 10 enteros con valor inicial 3
int n = v1.size() ; // n´umero de elementos de v1
}
Es posible tanto a˜nadir un elemento al final de un vector mediante el m´etodo push_back(...),
como eliminar el ´ultimo elemento del vector mediante el m´etodo pop_back() (en este caso el vector
no debe estar vac´ıo). As´ı mismo, el m´etodo clear() elimina todos los elementos del vector. Por
ejemplo:
int main()
{
Vect_Int v(5) ; // v = { 0, 0, 0, 0, 0 }
for (int i = 1 ; i = 3 ; ++i) {
v.push_back(i) ;
} // v = { 0, 0, 0, 0, 0, 1, 2, 3 }
for (int i = 0 ; i  int(v.size()) ; ++i) {
cout  v[i]    ;
} // muestra: 0 0 0 0 0 1 2 3
cout  endl ;
while (v.size()  3) {
v.pop_back() ;
} // v = { 0, 0, 0 }
v.clear() ; // v = { }
}
Tambi´en es posible cambiar el tama˜no del n´umero de elementos almacenados en el vector. As´ı,
el m´etodo resize(...) reajusta el n´umero de elementos contenidos en un vector. Si el n´umero
especificado es menor que el n´umero actual de elementos, se eliminar´an del final del vector tantos
elementos como sea necesario para reducir el vector hasta el n´umero de elementos especificado. Si
por el contrario, el n´umero especificado es mayor que el n´umero actual de elementos, entonces se
a˜nadir´an al final del vector tantos elementos como sea necesario para alcanzar el nuevo n´umero de
elementos especificado (con el valor especificado o con el valor por defecto). Por ejemplo:
int main()
{
Vect_Int v(10, 1) ; // v = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
v.resize(5) ; // v = { 1, 1, 1, 1, 1 }
v.resize(9, 2) ; // v = { 1, 1, 1, 1, 1, 2, 2, 2, 2 }
v.resize(7, 3) ; // v = { 1, 1, 1, 1, 1, 2, 2 }
v.resize(10) ; // v = { 1, 1, 1, 1, 1, 2, 2, 0, 0, 0 }
}
Acceso a los Elementos de un Vector
Es posible acceder a cada elemento del vector individualmente, seg´un el ´ındice de la posici´on
que ocupe, tanto para obtener su valor almacenado, como para modificarlo mediante el operador
de indexaci´on []. El primer elemento ocupa la posici´on cero (0), y el ´ultimo elemento almacenado
en el vector v ocupa la posici´on v.size()-1. Por ejemplo:
int main()
{
Vect_Int v(10) ;
for (int i = 0 ; i  int(v.size()) ; ++i) {
v[i] = i ;
}
for (int i = 0 ; i  int(v.size()) ; ++i) {
cout  v[i]    ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
14.2. DEQUE 159
cout  endl ;
}
El lenguaje de programaci´on C++ no comprueba que los accesos a los elementos de un vector
sean correctos y se encuentren dentro de los l´ımites v´alidos del vector, por lo que ser´a responsabil-
idad del programador comprobar que as´ı sea.
Sin embargo, en GNU G++, la opci´on de compilaci´on -D_GLIBCXX_DEBUG permite comprobar
los ´ındices de acceso.
Tambi´en es posible acceder a un determinado elemento mediante el m´etodo at(i), de tal
forma que si el valor del ´ındice i est´a fuera del rango v´alido, entonces se lanzar´a una excepci´on
out_of_range que abortar´a la ejecuci´on del programa. Se puede tanto utilizar como modificar el
valor de este elemento.
int main()
{
Vect_Int v(10) ;
for (int i = 0 ; i  int(v.size()) ; ++i) {
v.at(i) = i ;
}
for (int i = 0 ; i  int(v.size()) ; ++i) {
cout  v.at(i)    ;
}
cout  endl ;
}
Comparaci´on Lexicogr´afica entre Vectores
Es posible realizar la comparaci´on lexicogr´afica (==, !=, , =, , =) entre vectores del mismo
tipo siempre y cuando los operadores de comparaci´on est´en definidos para el tipo de los compo-
nentes del vector. Por ejemplo:
int main()
{
Vect_Int v1(10, 7) ; // v1 = { 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 }
Vect_Int v2(5, 3) ; // v2 = { 3, 3, 3, 3, 3 }
if (v1 == v2) {
cout  Iguales  endl ;
} else {
cout  Distintos  endl ;
}
if (v1  v2) {
cout  Menor  endl ;
} else {
cout  Mayor o Igual  endl ;
}
}
14.2. Deque
El contenedor de tipo deque... representa una secuencia de elementos homog´eneos opti-
mizada para el acceso directo a los elementos seg´un su posici´on, as´ı como tambi´en para la inserci´on
de elementos al principio y al final de la secuencia y para la eliminaci´on de elementos del principio
y del final de la secuencia. Para utilizar un contenedor de tipo deque se debe incluir la biblioteca
est´andar deque, de tal forma que sus definiciones se encuentran dentro del espacio de nombres
std:
#include deque
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
160CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL)
El contenedor deque presenta el mismo interfaz p´ublico que el contenedor vector, pero a˜nade
dos m´etodos nuevos para facilitar la inserci´on y eliminaci´on de elementos al principio de la secuencia
(push_front(...) y pop_front()).
El n´umero m´aximo de elementos que se pueden almacenar en una variable de tipo deque
no est´a especificado, y se pueden almacenar elementos mientras haya capacidad suficiente en la
memoria del ordenador donde se ejecute el programa.
Instanciaci´on del Tipo Deque
Se pueden definir expl´ıcitamente instanciaciones del tipo deque para tipos de elementos concre-
tos mediante la declaraci´on typedef. Por ejemplo la siguiente definici´on declara el tipo Deque_Int
como un tipo deque de n´umeros enteros.
typedef std::dequeint Deque_Int ;
Construcci´on de un Objeto de Tipo Deque
Se pueden definir variables de un tipo deque previamente definido expl´ıcitamente, o directa-
mente de la instanciaci´on del tipo. Por ejemplo, el siguiente c´odigo define dos variables (v1 y v2)
de tipo deque de n´umeros enteros.
int main()
{
Deque_Int v1 ; // deque de enteros vac´ıo
std::dequeint v2 ; // deque de enteros vac´ıo
// ...
}
El constructor por defecto del tipo deque crea un objeto deque inicialmente vac´ıo, sin elementos.
Posteriormente se podr´an a˜nadir y eliminar elementos cuando sea necesario.
Tambi´en es posible crear un objeto deque con un n´umero inicial de elementos con un valor inicial
por defecto, al que posteriormente se le podr´an a˜nadir nuevos elementos. Este n´umero inicial de
elementos puede ser tanto una constante, como el valor de una variable calculado en tiempo de
ejecuci´on.
int main()
{
Deque_Int v1(10) ; // deque con 10 enteros con valor inicial 0
// ...
}
As´ı mismo, tambi´en se puede especificar el valor que tomar´an los elementos creados inicialmente.
int main()
{
Deque_Int v1(10, 3) ; // deque con 10 enteros con valor inicial 3
// ...
}
Tambi´en es posible inicializar un deque con el contenido de otro deque de igual tipo, invocando al
constructor de copia:
int main()
{
Deque_Int v1(10, 3) ; // deque con 10 enteros con valor inicial 3
Deque_Int v2(v1) ; // deque con el mismo contenido de v1
Deque_Int v3 = v1 ; // deque con el mismo contenido de v1
Deque_Int v4 = Deque_Int(7, 5) ; // deque con 7 elementos de valor 5
// ...
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
14.2. DEQUE 161
Asignaci´on de un Objeto de Tipo Deque
Es posible la asignaci´on de deques de igual tipo. En este caso, se destruye el valor anterior del
deque destino de la asignaci´on.
int main()
{
Deque_Int v1(10, 3) ; // deque con 10 enteros con valor inicial 3
Deque_Int v2 ; // deque de enteros vac´ıo
v2 = v1 ; // asigna el contenido de v1 a v2
v2.assign(5, 7) ; // asigna 5 enteros con valor inicial 7
v2 = Deque_Int(5, 7) ; // asigna un deque con 5 elementos de valor 7
}
As´ı mismo, tambi´en es posible intercambiar (swap en ingl´es) de forma eficiente el contenido entre
dos deques utilizando el m´etodo swap. Por ejemplo:
int main()
{
Deque_Int v1(10, 5) ; // v1 = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 }
Deque_Int v2(5, 7) ; // v2 = { 7, 7, 7, 7, 7 }
v1.swap(v2) ; // v1 = { 7, 7, 7, 7, 7 }
// v2 = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 }
}
Control sobre los Elementos de un Deque
El n´umero de elementos actualmente almacenados en un deque se obtiene mediante el m´etodo
size(). Por ejemplo:
int main()
{
Deque_Int v1(10, 3) ; // deque con 10 enteros con valor inicial 3
int n = v1.size() ; // n´umero de elementos de v1
}
Es posible tanto a˜nadir un elemento al final de un deque mediante el m´etodo push_back(...),
como eliminar el ´ultimo elemento del deque mediante el m´etodo pop_back() (en este caso el deque
no debe estar vac´ıo). As´ı mismo, el m´etodo clear() elimina todos los elementos del deque. Por
ejemplo:
int main()
{
Deque_Int v(5) ; // v = { 0, 0, 0, 0, 0 }
for (int i = 1 ; i = 3 ; ++i) {
v.push_back(i) ;
} // v = { 0, 0, 0, 0, 0, 1, 2, 3 }
for (int i = 1 ; i = 2 ; ++i) {
v.push_front(i) ;
} // v = { 2, 1, 0, 0, 0, 0, 0, 1, 2, 3 }
for (int i = 0 ; i  int(v.size()) ; ++i) {
cout  v[i]    ;
} // muestra: 2 1 0 0 0 0 0 1 2 3
cout  endl ;
while (v.size()  5) {
v.pop_back() ;
} // v = { 2, 1, 0, 0, 0 }
while (v.size()  3) {
v.pop_front() ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
162CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL)
} // v = { 0, 0, 0 }
v.clear() ; // v = { }
}
Tambi´en es posible cambiar el tama˜no del n´umero de elementos almacenados en el deque. As´ı,
el m´etodo resize(...) reajusta el n´umero de elementos contenidos en un deque. Si el n´umero
especificado es menor que el n´umero actual de elementos, se eliminar´an del final del deque tantos
elementos como sea necesario para reducir el deque hasta el n´umero de elementos especificado. Si
por el contrario, el n´umero especificado es mayor que el n´umero actual de elementos, entonces se
a˜nadir´an al final del deque tantos elementos como sea necesario para alcanzar el nuevo n´umero de
elementos especificado (con el valor especificado o con el valor por defecto). Por ejemplo:
int main()
{
Deque_Int v(10, 1) ; // v = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
v.resize(5) ; // v = { 1, 1, 1, 1, 1 }
v.resize(9, 2) ; // v = { 1, 1, 1, 1, 1, 2, 2, 2, 2 }
v.resize(7, 3) ; // v = { 1, 1, 1, 1, 1, 2, 2 }
v.resize(10) ; // v = { 1, 1, 1, 1, 1, 2, 2, 0, 0, 0 }
}
Acceso a los Elementos de un Deque
Es posible acceder a cada elemento del deque individualmente, seg´un el ´ındice de la posici´on
que ocupe, tanto para obtener su valor almacenado, como para modificarlo mediante el operador
de indexaci´on []. El primer elemento ocupa la posici´on cero (0), y el ´ultimo elemento almacenado
en el deque v ocupa la posici´on v.size()-1. Por ejemplo:
int main()
{
Deque_Int v(10) ;
for (int i = 0 ; i  int(v.size()) ; ++i) {
v[i] = i ;
}
for (int i = 0 ; i  int(v.size()) ; ++i) {
cout  v[i]    ;
}
cout  endl ;
}
El lenguaje de programaci´on C++ no comprueba que los accesos a los elementos de un deque
sean correctos y se encuentren dentro de los l´ımites v´alidos del deque, por lo que ser´a responsabil-
idad del programador comprobar que as´ı sea.
Sin embargo, en GNU G++, la opci´on de compilaci´on -D_GLIBCXX_DEBUG permite comprobar
los ´ındices de acceso.
Tambi´en es posible acceder a un determinado elemento mediante el m´etodo at(i), de tal
forma que si el valor del ´ındice i est´a fuera del rango v´alido, entonces se lanzar´a una excepci´on
out_of_range que abortar´a la ejecuci´on del programa. Se puede tanto utilizar como modificar el
valor de este elemento.
int main()
{
Deque_Int v(10) ;
for (int i = 0 ; i  int(v.size()) ; ++i) {
v.at(i) = i ;
}
for (int i = 0 ; i  int(v.size()) ; ++i) {
cout  v.at(i)    ;
}
cout  endl ;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
14.3. STACK 163
Comparaci´on Lexicogr´afica entre Deques
Es posible realizar la comparaci´on lexicogr´afica (==, !=, , =, , =) entre deques del mismo tipo
siempre y cuando los operadores de comparaci´on est´en definidos para el tipo de los componentes
del deque. Por ejemplo:
int main()
{
Deque_Int v1(10, 7) ; // v1 = { 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 }
Deque_Int v2(5, 3) ; // v2 = { 3, 3, 3, 3, 3 }
if (v1 == v2) {
cout  Iguales  endl ;
} else {
cout  Distintos  endl ;
}
if (v1  v2) {
cout  Menor  endl ;
} else {
cout  Mayor o Igual  endl ;
}
}
14.3. Stack
El adaptador de contenedor de tipo stack... representa el tipo abstracto de datos Pila,
como una colecci´on ordenada (seg´un el orden de inserci´on) de elementos homog´eneos donde se
pueden introducir elementos (manteniendo el orden de inserci´on) y sacar elementos de ella (en
orden inverso al orden de inserci´on), de tal forma que el primer elemento que sale de la pila es el
´ultimo elemento que ha sido introducido en ella. Adem´as, tambi´en es posible comprobar si la pila
contiene elementos, de tal forma que no se podr´a sacar ning´un elemento de una pila vac´ıa. Para
utilizar un adaptador de contenedor de tipo stack se debe incluir la biblioteca est´andar stack,
de tal forma que sus definiciones se encuentran dentro del espacio de nombres std:
#include stack
El n´umero m´aximo de elementos que se pueden almacenar en una variable de tipo stack no est´a es-
pecificado, y se pueden introducir elementos mientras haya capacidad suficiente en la memoria del
ordenador donde se ejecute el programa.
Instanciaci´on del Tipo Stack
Se pueden definir expl´ıcitamente instanciaciones del tipo stack para tipos de elementos concre-
tos mediante la declaraci´on typedef. Por ejemplo la siguiente definici´on declara el tipo Stack_Int
como un tipo pila de n´umeros enteros.
typedef std::stackint Stack_Int ;
Construcci´on de un Objeto de Tipo Pila
Se pueden definir variables de un tipo pila previamente definido expl´ıcitamente, o directamente
de la instanciaci´on del tipo. Por ejemplo, el siguiente c´odigo define dos variables (s1 y s2) de tipo
pila de n´umeros enteros.
int main()
{
Stack_Int s1 ; // stack de enteros vac´ıo
std::stackint s2 ; // stack de enteros vac´ıo
// ...
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
164CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL)
El constructor por defecto del tipo stack crea un objeto stack inicialmente vac´ıo, sin elementos.
Posteriormente se podr´an a˜nadir y eliminar elementos cuando sea necesario.
Tambi´en es posible inicializar una pila con el contenido de otra pila de igual tipo:
int main()
{
Stack_Int s1 ; // stack de enteros vac´ıo
// ...
Stack_Int s2(s1) ; // stack con el mismo contenido de s1
Stack_Int s3 = s1 ; // stack con el mismo contenido de s1
Stack_Int s4 = Stack_Int() ; // copia el contenido de stack vac´ıo
// ...
}
Asignaci´on de un Objeto de Tipo Pila
Es posible la asignaci´on de pilas de igual tipo. En este caso, se destruye el valor anterior de la
pila destino de la asignaci´on.
int main()
{
Stack_Int s1 ; // stack de enteros vac´ıo
Stack_Int s2 ; // stack de enteros vac´ıo
s2 = s1 ; // asigna el contenido de s1 a s2
s2 = Stack_Int() ; // asigna el contenido de stack vac´ıo
}
Control y Acceso a los Elementos de una Pila
Es posible tanto a˜nadir un elemento una pila mediante el m´etodo push(...), como eliminar
el ´ultimo elemento introducido en la pila mediante el m´etodo pop() (en este caso la pila no debe
estar vac´ıa).
Por otra parte, el m´etodo empty() indica si una pila est´a vac´ıa o no, mientras que el n´umero
de elementos actualmente almacenados en una pila se obtiene mediante el m´etodo size().
As´ı mismo, se puede acceder al ´ultimo elemento introducido en la pila mediante el m´etodo
top(). Se puede tanto utilizar como modificar el valor de este elemento (en este caso la pila no
debe estar vac´ıa).
Por ejemplo:
int main()
{
Stack_Int s ; // s = { }
for (int i = 1 ; i = 3 ; ++i) {
s.push(i) ;
} // s = { 1, 2, 3 }
s.top() = 5 ; // s = { 1, 2, 5 }
s.pop() ; // s = { 1, 2 }
s.pop() ; // s = { 1 }
s.push(7) ; // s = { 1, 7 }
s.push(9) ; // s = { 1, 7, 9 }
cout  s.size()  endl ; // muestra: 3
while (! s.empty()) {
cout  s.top()    ; // muestra: 9 7 1
s.pop() ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
14.4. QUEUE 165
} // s = { }
cout  endl ;
}
Comparaci´on Lexicogr´afica entre Pilas
Es posible realizar la comparaci´on lexicogr´afica (==, !=, , =, , =) entre pilas del mismo tipo
siempre y cuando los operadores de comparaci´on est´en definidos para el tipo de los componentes
de la pila. Por ejemplo:
int main()
{
Stack_Int s1 ;
Stack_Int s2 ;
// ...
if (s1 == s2) {
cout  Iguales  endl ;
} else {
cout  Distintos  endl ;
}
if (s1  s2) {
cout  Menor  endl ;
} else {
cout  Mayor o Igual  endl ;
}
}
14.4. Queue
El adaptador de contenedor de tipo queue... representa el tipo abstracto de datos Cola,
como una colecci´on ordenada (seg´un el orden de inserci´on) de elementos homog´eneos donde se
pueden introducir elementos (manteniendo el orden de inserci´on) y sacar elementos de ella (en el
mismo orden al orden de inserci´on), de tal forma que el primer elemento que sale de la cola es el
primer elemento que ha sido introducido en ella. Adem´as, tambi´en es posible comprobar si la cola
contiene elementos, de tal forma que no se podr´a sacar ning´un elemento de una cola vac´ıa. Para
utilizar un adaptador de contenedor de tipo queue se debe incluir la biblioteca est´andar queue,
de tal forma que sus definiciones se encuentran dentro del espacio de nombres std:
#include queue
El n´umero m´aximo de elementos que se pueden almacenar en una variable de tipo queue no est´a es-
pecificado, y se pueden introducir elementos mientras haya capacidad suficiente en la memoria del
ordenador donde se ejecute el programa.
Instanciaci´on del Tipo Queue
Se pueden definir expl´ıcitamente instanciaciones del tipo queue para tipos de elementos concre-
tos mediante la declaraci´on typedef. Por ejemplo la siguiente definici´on declara el tipo Queue_Int
como un tipo cola de n´umeros enteros.
typedef std::queueint Queue_Int ;
Construcci´on de un Objeto de Tipo Cola
Se pueden definir variables de un tipo cola previamente definido expl´ıcitamente, o directamente
de la instanciaci´on del tipo. Por ejemplo, el siguiente c´odigo define dos variables (c1 y c2) de tipo
cola de n´umeros enteros.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
166CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL)
int main()
{
Queue_Int c1 ; // queue de enteros vac´ıo
std::queueint c2 ; // queue de enteros vac´ıo
// ...
}
El constructor por defecto del tipo queue crea un objeto queue inicialmente vac´ıo, sin elementos.
Posteriormente se podr´an a˜nadir y eliminar elementos cuando sea necesario.
Tambi´en es posible inicializar una cola con el contenido de otra cola de igual tipo:
int main()
{
Queue_Int c1 ; // queue de enteros vac´ıo
// ...
Queue_Int c2(c1) ; // queue con el mismo contenido de c1
Queue_Int c3 = c1 ; // queue con el mismo contenido de c1
Queue_Int c4 = Stack_Int() ; // copia el contenido de queue vac´ıo
// ...
}
Asignaci´on de un Objeto de Tipo Cola
Es posible la asignaci´on de colas de igual tipo. En este caso, se destruye el valor anterior de la
cola destino de la asignaci´on.
int main()
{
Queue_Int c1 ; // queue de enteros vac´ıo
Queue_Int c2 ; // queue de enteros vac´ıo
c2 = c1 ; // asigna el contenido de c1 a c2
c2 = Queue_Int() ; // asigna el contenido de queue vac´ıo
}
Control y Acceso a los Elementos de una Cola
Es posible tanto a˜nadir un elemento una cola mediante el m´etodo push(...), como eliminar
el primer elemento introducido en la cola mediante el m´etodo pop() (en este caso la cola no debe
estar vac´ıa).
Por otra parte, el m´etodo empty() indica si una cola est´a vac´ıa o no, mientras que el n´umero
de elementos actualmente almacenados en una cola se obtiene mediante el m´etodo size().
As´ı mismo, se puede acceder al ´ultimo elemento introducido en la cola mediante el m´etodo
back(), as´ı como al primer elemento introducido en ella mediante el m´etodo front(). Se pueden
tanto utilizar como modificar el valor de estos elementos (en este caso la cola no debe estar vac´ıa).
Por ejemplo:
int main()
{
Queue_Int c ; // c = { }
for (int i = 1 ; i = 3 ; ++i) {
c.push(i) ;
} // c = { 1, 2, 3 }
c.front() = 6 ; // c = { 6, 2, 3 }
c.back() = 5 ; // c = { 6, 2, 5 }
c.pop() ; // c = { 2, 5 }
c.pop() ; // c = { 5 }
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
14.5. RESOLUCI ´ON DE PROBLEMAS UTILIZANDO CONTENEDORES 167
c.push(7) ; // c = { 5, 7 }
c.push(9) ; // c = { 5, 7, 9 }
cout  c.size()  endl ; // muestra: 3
while (! c.empty()) {
cout  c.front()    ; // muestra: 5 7 9
c.pop() ;
} // c = { }
cout  endl ;
}
Comparaci´on Lexicogr´afica entre Colas
Es posible realizar la comparaci´on lexicogr´afica (==, !=, , =, , =) entre colas del mismo tipo
siempre y cuando los operadores de comparaci´on est´en definidos para el tipo de los componentes
de la cola. Por ejemplo:
int main()
{
Queue_Int c1 ;
Queue_Int c2 ;
// ...
if (c1 == c2) {
cout  Iguales  endl ;
} else {
cout  Distintos  endl ;
}
if (c1  c2) {
cout  Menor  endl ;
} else {
cout  Mayor o Igual  endl ;
}
}
14.5. Resoluci´on de Problemas Utilizando Contenedores
Ejemplo 1: Agentes de Ventas
Dise˜ne un programa que lea y almacene las ventas realizadas por unos agentes de ventas, de
tal forma que se eliminen aquellos agentes cuyas ventas sean inferiores a la media de las ventas
realizadas.
//------------------------------------------------------------------
#include iostream
#include vector
#include string
using namespace std ;
struct Agente {
string nombre ;
double ventas ;
} ;
typedef vectorAgente VAgentes ;
void leer (VAgentes v)
{
v.clear() ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
168CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL)
Agente a ;
cout  Introduzca Nombre:  ;
getline(cin, a.nombre) ;
while (( ! cin.fail())  (a.nombre.size()  0)) {
cout  Introduzca Ventas:  ;
cin  a.ventas ;
cin.ignore(1000, ’n’) ;
v.push_back(a) ;
cout  Introduzca Nombre:  ;
getline(cin, a.nombre) ;
}
}
double media(const VAgentes v)
{
double suma=0.0 ;
for (int i = 0 ; i  int(v.size()) ; ++i) {
suma += v[i].ventas ;
}
return suma/double(v.size()) ;
}
void purgar(VAgentes v, double media)
{
// altera el orden secuencial de los elementos
int i = 0 ;
while (i  int(v.size())) {
if (v[i].ventas  media) {
v[i] = v[v.size()-1] ;
v.pop_back() ;
} else {
++i ;
}
}
}
void purgar_ordenado(VAgentes v, double media)
{
// mantiene el orden secuencial de los elementos
int k = 0 ;
while ((k  int(v.size()))(v[k].ventas = media)) {
++k;
}
for (int i = k ; i  int(v.size()) ; ++i) {
if(v[i].ventas = media) {
v[k] = v[i] ;
++k ;
}
}
v.resize(k) ;
}
void imprimir(const VAgentes v)
{
for (int i = 0 ; i  int(v.size()) ; ++i) {
cout  v[i].nombre     v[i].ventas  endl ;
}
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
14.5. RESOLUCI ´ON DE PROBLEMAS UTILIZANDO CONTENEDORES 169
int main ()
{
VAgentes v ;
leer(v) ;
purgar(v, media(v)) ;
imprimir(v) ;
}
//------------------------------------------------------------------
Ejemplo 2: Multiplicaci´on de Matrices
Dise˜ne un programa que lea dos matrices de tama˜nos arbitrarios y muestre el resultado de
multiplicar ambas matrices.
//------------------------------------------------------------------
#include vector
#include iostream
#include iomanip
using namespace std ;
typedef vector double Fila ;
typedef vector Fila Matriz ;
void imprimir(const Matriz m)
{
for (int f = 0 ; f  int(m.size()) ; ++f) {
for (int c = 0 ; c  int(m[f].size()) ; ++c) {
cout  setw(10)  setprecision(4)
 m[f][c]    ;
}
cout  endl ;
}
}
void leer(Matriz m)
{
int nf, nc ;
cout  Introduzca el numero de filas:  ;
cin  nf ;
cout  Introduzca el numero de columnas:  ;
cin  nc ;
m = Matriz(nf, Fila (nc)) ; // copia de la matriz completa
cout  Introduzca los elementos:   endl ;
for (int f = 0 ; f  int(m.size()) ; ++f) {
for (int c = 0 ; c  int(m[f].size()) ; ++c) {
cin  m[f][c] ;
}
}
}
// otra opci´on m´as eficiente para la lectura de vectores
void leer_2(Matriz m)
{
int nf, nc ;
cout  Introduzca el numero de filas:  ;
cin  nf ;
cout  Introduzca el numero de columnas:  ;
cin  nc ;
Matriz aux(nf, Fila (nc)) ;
cout  Introduzca los elementos:   endl ;
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
170CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL)
for (int f = 0 ; f  int(aux.size()) ; ++f) {
for (int c = 0 ; c  int(aux[f].size()) ; ++c) {
cin  aux[f][c] ;
}
}
m.swap(aux) ; // evita la copia de la matriz completa
}
void multiplicar(const Matriz m1, const Matriz m2, Matriz m3)
{
m3.clear() ;
if ((m1.size()  0)  (m2.size()  0)  (m2[0].size()  0)
 (m1[0].size() == m2.size())){
Matriz aux(m1.size(), Fila(m2[0].size())) ;
for (int f = 0 ; f  int(aux.size()) ; ++f) {
for (int c = 0 ; c  int(aux[f].size()) ; ++c) {
double suma = 0.0 ;
for (int k = 0 ; k  int(m2.size()) ; ++k) {
suma += m1[f][k] * m2[k][c] ;
}
aux[f][c] = suma ;
}
}
m3.swap(aux) ; // evita la copia de la matriz completa
}
}
int main()
{
Matriz m1, m2, m3 ;
leer(m1) ;
leer(m2) ;
multiplicar(m1, m2, m3) ;
if (m3.size() == 0) {
cout  Error en la multiplicaci´on de Matrices  endl ;
} else {
imprimir(m3) ;
}
}
//------------------------------------------------------------------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
Cap´ıtulo 15
Bibliograf´ıa
El Lenguaje de Programaci´on C. 2.Ed.
B.Kernighan, D. Ritchie
Prentice Hall 1991
The C++ Programming Language. Special Edition
B. Stroustrup
Addison Wesley 2000
171
´Indice alfab´etico
::, 99
´ambito de visibilidad, 30
agregado, 56
acceso, 57
multidimensional, 61
size, 57
tama˜no, 57
array, 56
acceso, 57
multidimensional, 61
size, 57
tama˜no, 57
b´usqueda
binaria, 69
lineal, 69
biblioteca
ansic
cctype, 77
cmath, 77
cstdlib, 78
bloque, 29
buffer, 28
de entrada, 28
de salida, 28
cin, 26
comentarios, 13
compilaci´on separada, 97
constantes
literales, 18
simb´olicas, 18
declaraci´on, 19
constantes literales, 13
conversiones aritm´eticas, 21
conversiones de tipo
autom´aticas, 20
expl´ıcitas, 21
conversiones enteras, 21
cout, 25
declaraci´on
global, 29
´ambito de visibilidad, 29
local, 29
´ambito de visibilidad, 29
vs. definici´on, 15
declaracion adelantada, 145
definici´on
vs. declaraci´on, 15
delete, 141
delimitadores, 13
ejecuci´on secuencial, 29
enlazado, 97
entrada, 26
espacios de nombre
::, 99
espacios de nombre
an´onimos, 99
espacios de nombre, 98
using namespace, 98
espacios en blanco, 13
estructura, 54
fichero de encabezamiento
guardas, 96
funciones, 37
declaraci´on, 42
definici´on, 38
inline, 42
return, 39
guardas, 96
identificadores, 13
inline, 42
listas enlazadas
declaracion adelantada, 145
m´odulo
implementaci´on, 95
interfaz, 95
main, 11
memoria din´amica, 141
abstraccion, 148
delete, 141
enlaces, 144
listas enlazadas
gen´erica, 149
172
´INDICE ALFAB´ETICO 173
new, 141
new, 141
operadores, 13, 19
aritm´eticos, 20
bits, 20
condicional, 20
l´ogicos, 20
relacionales, 20
ordenaci´on
burbuja, 70
inserci´on, 71
intercambio, 70
selecci´on, 71
palabras reservadas, 12
par´ametros de entrada, 39
par´ametros de entrada/salida, 40
par´ametros de salida, 40
paso por referencia constante, 47
paso por referencia, 40
paso por valor, 39
procedimientos, 37
declaraci´on, 42
definici´on, 38
inline, 42
programa C++, 11
promociones, 21
prototipo, 42
registro, 54
return, 39
salida, 25
secuencia de sentencias, 29
sentencia
asignaci´on, 30
incremento/decremento, 30
iteraci´on, 33
do while, 35
for, 34
while, 33
selecci´on, 31
if, 31
switch, 32
tipo, 15
tipos
cuadro resumen, 17
puntero, 140
acceso, 142
operaciones, 142
par´ametros, 144
tipos compuestos
array, 56
tipos simples
escalares, 16
tipos compuestos, 15, 47
array, 56
acceso, 57
multidimensional, 61
size, 57
par´ametros, 47
struct, 54
tipos simples, 15
enumerado, 17
ordinales, 16
predefinidos, 15
bool, 15
char, 15
double, 16
float, 16
int, 16
long, 16
long long, 16
short, 16
unsigned, 16
using namespace, 98
variables
declaraci´on, 19
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga

Más contenido relacionado

PDF
Manual referencia cxx
PDF
MANUAL DE LENGUAJE C
PDF
Desarrollo proyectos-informaticos-con-java
PDF
Introduccion a la_programacion_con_c
PDF
Libro javacontapa
PDF
Manual de programacion lenguaje en C
PDF
Seminario del Operador
PDF
Scd 13-14 todo
Manual referencia cxx
MANUAL DE LENGUAJE C
Desarrollo proyectos-informaticos-con-java
Introduccion a la_programacion_con_c
Libro javacontapa
Manual de programacion lenguaje en C
Seminario del Operador
Scd 13-14 todo

La actualidad más candente (14)

PDF
PDF
Algoritmos programacion-python
PDF
Algoritmos programacion-python
PDF
Ecosys m2030dn ...535dn_og_es
PDF
Manual Jsf
PDF
Manual macromedia free hand mx
PDF
Manual sony
PDF
Fs 1035 mfp-1135mfpspog.2011.12
PDF
Tfg g3750
PDF
Pensar en cpp
PDF
PDF
Introduccion a los Sistemas Digitales
PDF
Electronicadigital
Algoritmos programacion-python
Algoritmos programacion-python
Ecosys m2030dn ...535dn_og_es
Manual Jsf
Manual macromedia free hand mx
Manual sony
Fs 1035 mfp-1135mfpspog.2011.12
Tfg g3750
Pensar en cpp
Introduccion a los Sistemas Digitales
Electronicadigital
Publicidad

Similar a El lenguaje de programación c++ (20)

PDF
Libro programación-en-c++
PDF
PDF
Introducción a la programación en c
PDF
Introducción a la programación en C
PDF
pensar_en_cpp-vol1.pdf
PDF
ApuntesC++.pdf
PDF
Introduccion al lenguaje c
PDF
Programacion en Phyton desde ce..........................ro
PDF
Fundamentos de Programacion.pdf
PDF
Manual completo python
PDF
Python desde 0
PDF
Algoritmos y programacion_i_-_con_lengua
PDF
Mprogintc++ prog(3)
PDF
Mprogintc++ regular
PDF
Apuntes de progra c++
PDF
Manual de programacion en lenguaje c++
PDF
Mprogintc++
PDF
Moolibro 110609124636-phpapp01
Libro programación-en-c++
Introducción a la programación en c
Introducción a la programación en C
pensar_en_cpp-vol1.pdf
ApuntesC++.pdf
Introduccion al lenguaje c
Programacion en Phyton desde ce..........................ro
Fundamentos de Programacion.pdf
Manual completo python
Python desde 0
Algoritmos y programacion_i_-_con_lengua
Mprogintc++ prog(3)
Mprogintc++ regular
Apuntes de progra c++
Manual de programacion en lenguaje c++
Mprogintc++
Moolibro 110609124636-phpapp01
Publicidad

Último (10)

PDF
Clase 3 - Presentación visual (Insertando objetos visuales) POWER POINT.pdf
PPTX
hojas_de_calculo_aplicado para microsoft office
PPTX
Implementación equipo monitor12.08.25.pptx
PDF
Presentacion de compiladores e interpretes
PPTX
ORIGEN DE LA IA - GRADO 1102 INTELIGENCIA
PPTX
PROPIEDADES Y METODOS DE PrOO CON PYTHON
PPTX
Control de seguridad en los sitios web.pptx
PDF
DNS_SERVIDORES PARA ASER PRACTICAS EN REDES
PPTX
presentación de introducción a las metodologías agiles .pptx
PPTX
ANALOGÍA 6.pptx9191911818181119111162363733839292
Clase 3 - Presentación visual (Insertando objetos visuales) POWER POINT.pdf
hojas_de_calculo_aplicado para microsoft office
Implementación equipo monitor12.08.25.pptx
Presentacion de compiladores e interpretes
ORIGEN DE LA IA - GRADO 1102 INTELIGENCIA
PROPIEDADES Y METODOS DE PrOO CON PYTHON
Control de seguridad en los sitios web.pptx
DNS_SERVIDORES PARA ASER PRACTICAS EN REDES
presentación de introducción a las metodologías agiles .pptx
ANALOGÍA 6.pptx9191911818181119111162363733839292

El lenguaje de programación c++

  • 1. UNIVERSIDAD DE M ´ALAGA Dpt. Lenguajes y CC. Computaci´on E.T.S.I. Inform´atica Ingenier´ıa Inform´atica Fundamentos de Programaci´on con el Lenguaje de Programaci´on C++ Vicente Benjumea y Manuel Rold´an 26 de junio de 2012
  • 2. 2 Este obra est´a bajo una licencia Reconocimiento-NoComercial-CompartirIgual 3.0 Un- ported de Creative Commons: No se permite un uso comercial de la obra original ni de las posibles obras derivadas, la distribuci´on de las cuales se debe hacer con una li- cencia igual a la que regula la obra original. Para ver una copia de esta licencia, visite http://creativecommons.org/licenses/by-nc-sa/3.0/deed.es ES o envie una carta a Cre- ative Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA. Usted es libre de: • Copiar, distribuir y comunicar p´ublicamente la obra. • Hacer obras derivadas. Bajo las siguientes condiciones: • Reconocimiento (Attribution) – Debe reconocer los cr´editos de la obra de la manera especificada por el autor o el licenciador (pero no de una manera que sugiera que tiene su apoyo o apoyan el uso que hace de su obra). • No comercial (Non commercial) – No puede utilizar esta obra para fines comerciales. • Compartir bajo la misma licencia (Share alike) – Si altera o transforma esta obra, o genera una obra derivada, s´olo puede distribuir la obra generada bajo una licencia id´entica a ´esta. Entendiendo que: • Renuncia – Alguna de estas condiciones puede no aplicarse si se obtiene el permiso del titular de los derechos de autor • Dominio P´ublico – Cuando la obra o alguno de sus elementos se halle en el dominio p´ublico seg´un la ley vigente aplicable, esta situaci´on no quedar´a afectada por la licencia. • Otros derechos – Los derechos siguientes no quedan afectados por la licencia de ninguna manera: ◦ Los derechos derivados de usos leg´ıtimos u otras limitaciones reconocidas por ley no se ven afectados por lo anterior. ◦ Los derechos morales del autor ◦ Derechos que pueden ostentar otras personas sobre la propia obra o su uso, como por ejemplo derechos de imagen o de privacidad. • Aviso – Al reutilizar o distribuir la obra, tiene que dejar bien claro los t´erminos de la licencia de esta obra. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 3. ´Indice general Pr´ologo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 I Programaci´on B´asica 9 1. Un Programa C++ 11 2. Tipos Simples 15 2.1. Declaraci´on Vs. Definici´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.2. Tipos Simples Predefinidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.3. Tipos Simples Enumerados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.4. Constantes y Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 2.5. Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.6. Conversiones Autom´aticas (Impl´ıcitas) de Tipos . . . . . . . . . . . . . . . . . . . . 20 2.7. Conversiones Expl´ıcitas de Tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 2.8. Tabla ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.9. Algunas Consideraciones Respecto a Operaciones con N´umeros Reales . . . . . . . 22 3. Entrada y Salida de Datos B´asica 25 3.1. Salida de Datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.2. Entrada de Datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.3. El “Buffer” de Entrada y el “Buffer” de Salida . . . . . . . . . . . . . . . . . . . . 28 4. Estructuras de Control 29 4.1. Sentencia, Secuencia y Bloque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 4.2. Declaraciones Globales y Locales . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 4.3. Sentencias de Asignaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.4. Sentencias de Selecci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 4.5. Sentencias de Iteraci´on. Bucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 4.6. Programaci´on Estructurada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 4.7. Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5. Subprogramas. Funciones y Procedimientos 37 5.1. Funciones y Procedimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 5.2. Definici´on de Subprogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 5.3. Ejecuci´on de Subprogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 5.4. Paso de Par´ametros. Par´ametros por Valor y por Referencia . . . . . . . . . . . . . 39 5.5. Criterios de Modularizaci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 5.6. Subprogramas “en L´ınea” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 5.7. Declaraci´on de Subprogramas. Prototipos . . . . . . . . . . . . . . . . . . . . . . . 42 5.8. Sobrecarga de Subprogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 5.9. Pre-Condiciones y Post-Condiciones . . . . . . . . . . . . . . . . . . . . . . . . . . 43 5.10. Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3
  • 4. 4 ´INDICE GENERAL 6. Tipos Compuestos 47 6.1. Paso de Par´ametros de Tipos Compuestos . . . . . . . . . . . . . . . . . . . . . . . 47 6.2. Cadenas de Caracteres en C++: el Tipo String . . . . . . . . . . . . . . . . . . . . 48 6.3. Registros o Estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 6.4. Agregados: el Tipo Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 6.5. Resoluci´on de Problemas Utilizando Tipos Compuestos . . . . . . . . . . . . . . . 64 7. B´usqueda y Ordenaci´on 69 7.1. B´usqueda Lineal (Secuencial) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 7.2. B´usqueda Binaria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 7.3. Ordenaci´on por Intercambio (Burbuja) . . . . . . . . . . . . . . . . . . . . . . . . . 70 7.4. Ordenaci´on por Selecci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 7.5. Ordenaci´on por Inserci´on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 7.6. Aplicaci´on de los Algoritmos de B´usqueda y Ordenaci´on . . . . . . . . . . . . . . . 72 8. Algunas Bibliotecas ´Utiles 77 II Programaci´on Intermedia 81 9. Almacenamiento en Memoria Secundaria: Ficheros 83 9.1. Flujos de Entrada y Salida Asociados a Ficheros . . . . . . . . . . . . . . . . . . . 84 9.2. Entrada de Datos desde Ficheros de Texto . . . . . . . . . . . . . . . . . . . . . . . 85 9.3. Salida de Datos a Ficheros de Texto . . . . . . . . . . . . . . . . . . . . . . . . . . 87 9.4. Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 10.M´odulos y Bibliotecas 95 10.1. Interfaz e Implementaci´on del M´odulo . . . . . . . . . . . . . . . . . . . . . . . . . 95 10.2. Compilaci´on Separada y Enlazado . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 10.3. Espacios de Nombre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 11.Tipos Abstractos de Datos 105 11.1. Tipos Abstractos de Datos en C++: Clases . . . . . . . . . . . . . . . . . . . . . . 106 11.1.1. Definici´on de Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 11.1.2. Utilizaci´on de Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 11.1.3. Implementaci´on de Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 11.1.4. Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 11.2. Tipos Abstractos de Datos en C++: M´as sobre Clases . . . . . . . . . . . . . . . . 116 11.2.1. Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 12.Introducci´on a la Programaci´on Gen´erica. Plantillas 129 12.1. Subprogramas Gen´ericos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 12.2. Tipos Abstractos de Datos Gen´ericos . . . . . . . . . . . . . . . . . . . . . . . . . . 132 13.Memoria Din´amica. Punteros 139 13.1. Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 13.2. Gesti´on de Memoria Din´amica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 13.3. Operaciones con Variables de Tipo Puntero . . . . . . . . . . . . . . . . . . . . . . 142 13.4. Paso de Par´ametros de Variables de Tipo Puntero . . . . . . . . . . . . . . . . . . 144 13.5. Listas Enlazadas Lineales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 13.6. Abstracci´on en la Gesti´on de Memoria Din´amica . . . . . . . . . . . . . . . . . . . 148 13.7. Tipo Abstracto de Datos Lista Enlazada Gen´erica . . . . . . . . . . . . . . . . . . 149 Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 5. ´INDICE GENERAL 5 14.Introducci´on a los Contenedores de la Biblioteca Est´andar (STL) 155 14.1. Vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 14.2. Deque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 14.3. Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 14.4. Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 14.5. Resoluci´on de Problemas Utilizando Contenedores . . . . . . . . . . . . . . . . . . 167 15.Bibliograf´ıa 171 ´Indice 171 Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 6. 6 ´INDICE GENERAL Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 7. Pr´ologo Este manual pretende ser una gu´ıa de referencia para la utilizaci´on del lenguaje de programaci´on C++ en el desarrollo de programas. Est´a orientada a alumnos de primer curso de programaci´on de Ingenier´ıa Inform´atica. Este manual se concibe como material de apoyo a la docencia, y requiere de las explicaciones im- partidas en clase por el profesor para su aprendizaje. As´ı mismo, este manual no pretende “ense˜nar a programar”, supone que el lector posee los fundamentos necesarios relativos a la programaci´on, y simplemente muestra como aplicarlos utilizando el Lenguaje de Programaci´on C++. El lenguaje de programaci´on C++ es un lenguaje muy flexible y vers´atil, y debido a ello, si se utiliza sin rigor puede dar lugar a construcciones y estructuras de programaci´on complejas, dif´ıciles de comprender y propensas a errores. Debido a ello, restringiremos tanto las estructuras a utilizar como la forma de utilizarlas. No pretende ser una gu´ıa extensa del lenguaje de programaci´on C++. Adem´as, dada la amplitud del lenguaje, hay caracter´ısticas del mismo que no han sido contempladas por exceder lo que entendemos que es un curso de programaci´on elemental. Este manual ha sido elaborado en el Dpto. de Lenguajes y Ciencias de la Computaci´on de la Universidad de M´alaga. La ´ultima versi´on de este documento puede ser descargada desde la siguiente p´agina web: http://www.lcc.uma.es/%7Evicente/docencia/index.html o directamente desde la siguiente direcci´on: http://www.lcc.uma.es/%7Evicente/docencia/cppdoc/programacion_cxx.pdf 7
  • 8. 8 ´INDICE GENERAL Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 11. Cap´ıtulo 1 Un Programa C++ En principio, un programa C++ se almacena en un fichero cuya extensi´on ser´a una de las sigu- ientes: “.cpp”, “.cxx”, “.cc”, etc. M´as adelante consideraremos programas complejos cuyo c´odigo se encuentra distribuido entre varios ficheros (v´ease 10). Dentro de este fichero, normalmente, aparecer´an al principio unas l´ıneas para incluir las defini- ciones de los m´odulos de biblioteca que utilice nuestro programa. Posteriormente, se realizar´an declaraciones y definiciones de tipos, de constantes (v´ease 2) y de subprogramas (v´ease 5) cuyo ´ambito de visibilidad (v´ease 4.2) ser´a global a todo el fichero (desde el punto donde ha sido declara- do hasta el final del fichero). De entre las definiciones de subprogramas, debe definirse una funci´on principal, denominada main, que indica donde comienza la ejecuci´on del programa. Al finalizar, dicha funci´on devolver´a un n´umero entero que indica al Sistema Operativo el estado de terminaci´on tras la ejecuci´on del programa (un n´umero 0 indica terminaci´on normal). En caso de no aparecer expl´ıcitamente el valor de retorno de main, el sistema recibir´a por defecto un valor indicando terminaci´on normal. Ejemplo de un programa que convierte una cantidad determinada de euros a su valor en pesetas. //- fichero: euros.cpp -------------------------------------------- #include <iostream> using namespace std ; const double EUR_PTS = 166.386 ; int main() { cout << "Introduce la cantidad (en euros): " ; double euros ; cin >> euros ; double pesetas = euros * EUR_PTS ; cout << euros << " Euros equivalen a " << pesetas << " Pts" << endl ; // return 0 ; } //- fin: euros.cpp ------------------------------------------------ Se deber´a compilar el programa (c´odigo fuente) contenido en el fichero euros.cpp para tra- ducirlo a un programa ejecutable mediante un compilador. En caso de utilizar el compilador GNU GCC, la compilaci´on se realizar´a de la siguiente forma: g++ -ansi -Wall -Werror -o euros euros.cpp Una vez compilado correctamente el programa, su ejecuci´on podr´a ser como se indica a contin- uaci´on, donde el texto enmarcado corresponde a una entrada de datos del usuario: Introduce la cantidad (en euros): 3.5 ENTER 3.5 Euros equivalen a 582.351 Pts 11
  • 12. 12 CAP´ITULO 1. UN PROGRAMA C++ En algunos entornos de programaci´on, por ejemplo Dev-C++ en Windows, puede ser necesario pausar el programa antes de su terminaci´on, para evitar que desaparezca la ventana de ejecuci´on. En este caso el programa anterior quedar´ıa: //- fichero: euros.cpp -------------------------------------------- #include <iostream> #include <cstdlib> using namespace std ; const double EUR_PTS = 166.386 ; int main() { cout << "Introduce la cantidad (en euros): " ; double euros ; cin >> euros ; double pesetas = euros * EUR_PTS ; cout << euros << " Euros equivalen a " << pesetas << " Pts" << endl ; system("pause") ; // llamada para que el S.O. Windows pause el programa // return 0 ; } //- fin: euros.cpp ------------------------------------------------ Ejemplo de un programa que imprime los n´umeros menores que uno dado por teclado. //- fichero: numeros.cpp -------------------------------------------- #include <iostream> // biblioteca de entrada/salida using namespace std ; // utilizaci´on del espacio de nombres de la biblioteca // ------------------------------------- // Imprime los n´umeros menores a ’n’ // ------------------------------------- void imprimir_numeros(int n) { for (int i = 0; i < n; ++i) { cout << i << " " ; // escribe el valor de ’i’ } cout << endl ; // escribe ’fin de l´ınea’ } // ------------------------------------- // Imprime los n´umeros menores a ’n’ // ------------------------------------- int main() { int maximo ; cout << "Introduce un n´umero: " ; cin >> maximo ; imprimir_numeros(maximo) ; // return 0 ; } //- fin: numeros.cpp ------------------------------------------------ En un programa C++ podemos distinguir los siguientes elementos b´asicos, considerando que las letras min´usculas se consideran diferentes de las letras may´usculas: Palabras reservadas Son un conjunto de palabras que tienen un significado predeterminado para el compilador, y s´olo pueden ser utilizadas con dicho sentido. Por ejemplo: using, namespace, const, double, int, char, bool, void, for, while, do, if, switch, case, default, return, typedef, enum, struct, etc. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 13. 13 Identificadores Son nombres elegidos por el programador para representar entidades (tipos, constantes, vari- ables, funciones, etc) en el programa. Se construyen mediante una secuencia de letras y d´ıgitos, siendo el primer car´acter una letra. El car´acter ’_’ se considera como una letra, sin embargo, los nombres que comienzan con dicho car´acter se reservan para situaciones especiales, por lo que no deber´ıan utilizarse en programas. En este manual, seguiremos la siguiente convenci´on para los identificadores: Constantes Simb´olicas: S´olo se utilizar´an letras may´usculas, d´ıgitos y el car´acter ’_’. Ejemplo: EUR_PTS Tipos: Comenzar´an por una letra may´uscula seguida por letras may´usculas, min´usculas, d´ıgitos o ’_’. Deber´a contener al menos una letra min´uscula. Ejemplo: Persona Variables: S´olo se utilizar´an letras min´usculas, d´ıgitos y el car´acter ’_’. Ejemplo: euros, pesetas, n, i1, etc. Funciones: S´olo se utilizar´an letras min´usculas, d´ıgitos y el car´acter ’_’. Ejemplo: imprimir_numeros Campos de Registros: S´olo se utilizar´an letras min´usculas, d´ıgitos y el car´acter ’_’. Ejem- plo: nombre Constantes literales Son valores que aparecen expl´ıcitamente en el programa, y podr´an ser l´ogicos, num´ericos, caracteres y cadenas. Ejemplo: true, false, 0, 25, 166.386, " Pts", ’ ’, etc. Operadores S´ımbolos con significado propio seg´un el contexto en el que se utilicen. Ejemplo: = << >> * / % + - < > <= >= == != ++ -- . , etc. Delimitadores S´ımbolos que indican comienzo o fin de una entidad. Ejemplo: ( ) { } ; , < > Comentarios y espacios en blanco Los espacios en blanco, tabuladores, nueva l´ınea, retorno de carro, avance de p´agina y los comentarios son ignorados por el compilador, excepto en el sentido en que separan elementos. Los comentarios en un programa es texto que el programador escribe para facilitar la com- prensi´on, o remarcar alg´un hecho importante a un lector humano, y son, por lo tanto, igno- rados por el compilador. Los comentarios en C++ se expresan de dos formas diferentes: • Comentarios hasta fin de l´ınea: los s´ımbolos // marcan el comienzo del comentario, que se extiende hasta el final de la l´ınea. // acumula el siguiente n´umero suma = suma + n ; // acumula el valor de ’n’ • Comentarios enmarcados: los s´ımbolos /* marcan el comienzo del comentario, que se extiende hasta los s´ımbolos del fin del comentario */ /* * acumula el siguiente n´umero */ suma = suma + n ; /* acumula el valor de ’n’ */ Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 14. 14 CAP´ITULO 1. UN PROGRAMA C++ Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 15. Cap´ıtulo 2 Tipos Simples El tipo define las caracter´ısticas que tiene una determinada entidad, de tal forma que toda entidad manipulada por un programa lleva asociado un determinado tipo. Las caracter´ısticas que el tipo define son: El rango de posibles valores que la entidad puede tomar. El conjunto de operaciones y manipulaciones aplicables a la entidad. El espacio de almacenamiento necesario para almacenar dichos valores. La interpretaci´on del valor almacenado. Los tipos se pueden clasificar en tipos simples y tipos compuestos. Los tipos simples se carac- terizan porque sus valores son indivisibles, es decir, no se puede acceder o modificar parte de ellos (aunque ´esto se pueda realizar indirectamente mediante operaciones de bits) y los tipos compuestos se caracterizan por estar formados como un agregado o composici´on de otros tipos, ya sean simples o compuestos. 2.1. Declaraci´on Vs. Definici´on Con objeto de clarificar la terminolog´ıa, en C++ una declaraci´on “presenta” un identificador para el cual la entidad a la que hace referencia deber´a ser definida posteriormente. Una definici´on “establece las caracter´ısticas” de una determinada entidad para el identificador al cual se refiere. Toda definici´on es a su vez tambi´en una declaraci´on. Es obligatorio que por cada entidad, s´olo exista una ´unica definici´on en la unidad de compi- laci´on, aunque pueden existir varias declaraciones. As´ı mismo, tambi´en es obligatorio la declaraci´on de las entidades que se manipulen en el programa, especificando su tipo, identificador, valores, etc. antes de que sean utilizados. 2.2. Tipos Simples Predefinidos Los tipos simples predefinidos en el lenguaje de programaci´on C++ son: bool char int float double El tipo bool se utiliza para representar valores l´ogicos (o booleanos), es decir, los valores “Verdadero” o “Falso” o las constantes l´ogicas true y false. Suele almacenarse en el tama˜no de palabra m´as peque˜no posible direccionable (normalmente 1 byte). El tipo char se utiliza para representar los caracteres, es decir, s´ımbolos alfanum´ericos (d´ıgitos y letras may´usculas y min´usculas), de puntuaci´on, espacios, control, etc. Normalmente utiliza un espacio de almacenamiento de 1 byte (8 bits) y puede representar 256 valores diferentes (v´ease 2.8). 15
  • 16. 16 CAP´ITULO 2. TIPOS SIMPLES El tipo int se utiliza para representar los n´umeros Enteros. Su representaci´on suele coincidir con la definida por el tama˜no de palabra del procesador sobre el que va a ser ejecutado, hoy d´ıa suele ser de 4 bytes (32 bits), aunque en determinados ordenadores puede ser de 8 bytes (64 bits). Puede ser modificado para representar un rango de valores menor mediante el mod- ificador short (normalmente 2 bytes [16 bits]) o para representar un rango de valores mayor mediante el modificador long (normalmente 4 bytes [32 bits] u 8 bytes [64 bits]) y long long (normalmente 8 bytes [64 bits]). Tambi´en puede ser modificado para representar solamente n´umeros Naturales (en- teros positivos) utilizando el modificador unsigned. Tanto el tipo float como el double se utilizan para representar n´umeros reales en formato de punto flotante diferenci´andose en el rango de valores que representan, utiliz´andose el tipo double (normalmente 8 bytes [64 bits]) para representar n´umeros de punto flotante en “doble precisi´on” y el tipo float (normalmente 4 bytes [32 bits]) para representar la “simple precisi´on”. El tipo double tambi´en puede ser modificado con long para representar “cu´adruple precisi´on” (normalmente 12 bytes [96 bits]). Todos los tipos simples tienen la propiedad de ser indivisibles y adem´as mantener una relaci´on de orden entre sus elementos (se les pueden aplicar los operadores relacionales 2.5). Se les conoce tambi´en como tipos Escalares. Todos ellos, salvo los de punto flotante (float y double), tienen tambi´en la propiedad de que cada posible valor tiene un ´unico antecesor y un ´unico sucesor. A ´estos se les conoce como tipos Ordinales (en terminolog´ıa C++, tambi´en se les conoce como tipos integrales, o enteros). A Valores L´ımites de los Tipos Predefinidos Tanto la biblioteca est´andar climits como cfloat definen constantes, accesibles por los progra- mas, que proporcionan los valores l´ımites que pueden contener las entidades (constantes y variables) de tipos simples. Para ello, si un programa necesita acceder a alg´un valor definido, deber´a incluir la biblioteca correspondiente, y utilizar las constantes adecuadas. Biblioteca climits #include <climits> char short int long long long unsigned m´aximo UCHAR_MAX USHRT_MAX UINT_MAX ULONG_MAX ULLONG_MAX signed m´aximo SCHAR_MAX SHRT_MAX INT_MAX LONG_MAX LLONG_MAX m´ınimo SCHAR_MIN SHRT_MIN INT_MIN LONG_MIN LLONG_MIN m´aximo CHAR_MAX m´ınimo CHAR_MIN n bits CHAR_BIT Biblioteca cfloat #include <cfloat> FLT_EPSILON Menor n´umero float x tal que 1,0 + x = 1,0 FLT_MAX M´aximo n´umero float de punto flotante FLT_MIN M´ınimo n´umero float normalizado de punto flotante DBL_EPSILON Menor n´umero double x tal que 1,0 + x = 1,0 DBL_MAX M´aximo n´umero double de punto flotante DBL_MIN M´ınimo n´umero double normalizado de punto flotante LDBL_EPSILON Menor n´umero long double x tal que 1,0 + x = 1,0 LDBL_MAX M´aximo n´umero long double de punto flotante LDBL_MIN M´ınimo n´umero long double normalizado de punto flotante Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 17. 2.3. TIPOS SIMPLES ENUMERADOS 17 Para ver el tama˜no (en bytes) que ocupa un determinado tipo/entidad en memoria, podemos aplicarle el siguiente operador: unsigned sz = sizeof(tipo) ; unsigned sz = sizeof(variable) ; Por definici´on, sizeof(char) es 1. El n´umero de bits que ocupa un determinado tipo se puede calcular de la siguiente forma: #include <iostream> #include <climits> using namespace std ; int main() { unsigned nbytes = sizeof(int) ; unsigned nbits = sizeof(int) / sizeof(char) * CHAR_BIT ; cout << "int: "<<nbytes<<" "<<nbits<<" "<<INT_MIN<<" "<<INT_MAX<<endl ; } Veamos un cuadro resumen con los tipos predefinidos, su espacio de almacenamiento y el rango de valores para una m´aquina de 32 bits, donde para una representaci´on de n bits, en caso de ser un tipo entero con signo, sus valores m´ınimo y m´aximo vienen especificados por el rango [(−2n−1 ) · · · (+2n−1 − 1)], y en caso de ser un tipo entero sin signo, sus valores m´ınimo y m´aximo vienen especificados por el rango [0 · · · (+2n − 1)]: Tipo Bytes Bits Min.Valor Max.Valor bool 1 8 false true char 1 8 -128 127 short 2 16 -32768 32767 int 4 32 -2147483648 2147483647 long 4 32 -2147483648 2147483647 long long 8 64 -9223372036854775808 9223372036854775807 unsigned char 1 8 0 255 unsigned short 2 16 0 65535 unsigned 4 32 0 4294967295 unsigned long 4 32 0 4294967295 unsigned long long 8 64 0 18446744073709551615 float 4 32 1.17549435e-38 3.40282347e+38 double 8 64 2.2250738585072014e-308 1.7976931348623157e+308 long double 12 96 3.36210314311209350626e-4932 1.18973149535723176502e+4932 2.3. Tipos Simples Enumerados Adem´as de los tipos simples predefinidos, el programador puede definir nuevos tipos simples que expresen mejor las caracter´ısticas de las entidades manipuladas por el programa. As´ı, dicho tipo se definir´a en base a una enumeraci´on de los posibles valores que pueda tomar la entidad asociada. A dicho tipo se le denomina tipo enumerado, es un tipo simple ordinal, y se define de la siguiente forma: enum Color { ROJO, AZUL, AMARILLO } ; De esta forma definimos el tipo Color, que definir´a una entidad (constante o variable) que podr´a tomar cualquiera de los diferentes valores enumerados. Los tipos enumerados, al ser tipos definidos por el programador, no tiene entrada ni salida predefinida por el lenguaje, sino que deber´a ser el programador el que especifique (programe) como se realizar´a la entrada y salida de datos en caso de ser necesaria. Otro ejemplo de enumeraci´on: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 18. 18 CAP´ITULO 2. TIPOS SIMPLES enum Meses { Enero, Febrero, Marzo, Abril, Mayo, Junio, Julio, Agosto, Septiembre, Octubre, Noviembre, Diciembre } ; 2.4. Constantes y Variables Podemos dividir las entidades que nuestro programa manipula en dos clases fundamentales: aquellos cuyo valor no var´ıa durante la ejecuci´on del programa (constantes) y aquellos otros cuyo valor puede ir cambiando durante la ejecuci´on del programa (variables). Constantes Las constantes pueden aparecer a su vez como constantes literales, son aquellas cuyo valor aparece directamente en el programa, y como constantes simb´olicas, aquellas cuyo valor se asocia a un identificador, a trav´es del cual se representa. Ejemplos de constantes literales: Constantes l´ogicas (bool): false, true Constantes car´acter (char), el s´ımbolo constante aparece entre comillas simples: ’a’, ’b’, ..., ’z’, ’A’, ’B’, ..., ’Z’, ’0’, ’1’, ..., ’9’, ’ ’, ’.’, ’,’, ’:’, ’;’, ... As´ı mismo, ciertos caracteres constantes tienen un significado especial (caracteres de escape): • ’n’: fin de l´ınea (newline) • ’r’: retorno de carro (carriage-return) • ’b’: retroceso (backspace) • ’t’: tabulador horizontal • ’v’: tabulador vertical • ’f’: avance de p´agina (form-feed) • ’a’: sonido (audible-bell) • ’0’: fin de cadena • ’137’, ’x5F’: car´acter correspondiente al valor especificado en notaci´on octal y hexadecimal respectivamente Constantes cadenas de caracteres literales, la secuencia de caracteres aparece entre comillas dobles (puede contener caracteres de escape): "Hola Pepe" "HolanJuann" "Hola " "Mar´ıa" Constantes enteras, pueden ser expresadas en decimal (base 10), hexadecimal (base 16) y octal (base 8). El sufijo L se utiliza para especificar long, el sufijo LL se utiliza para es- pecificar long long, el sufijo U se utiliza para especificar unsigned, el sufijo UL especifica unsigned long, y el sufijo ULL especifica unsigned long long: 123, -1520, 2345U, 30000L, 50000UL, 0x10B3FC23 (hexadecimal), 0751 (octal) Constantes reales, n´umeros en punto flotante. El sufijo F especifica float, y el sufijo L especifica long double: 3.1415, -1e12, 5.3456e-5, 2.54e-1F, 3.25e200L Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 19. 2.5. OPERADORES 19 Constantes Simb´olicas Las constantes simb´olicas se declaran indicando la palabra reservada const seguida por su tipo, el nombre simb´olico (o identificador) con el que nos referiremos a ella y el valor asociado tras el s´ımbolo (=). Usualmente se definen al principio del programa (despu´es de la inclusi´on de las cabeceras de las bibliotecas), y ser´an visibles desde el punto de declaraci´on, hasta el final del programa. Ejemplos de constantes simb´olicas: const bool OK = true ; const char SONIDO = ’a’ ; const short ELEMENTO = 1000 ; const int MAXIMO = 5000 ; const long ULTIMO = 100000L ; const long long TAMANO = 1000000LL ; const unsigned short VALOR = 100U ; const unsigned FILAS = 200U ; const unsigned long COLUMNAS = 200UL ; const unsigned long long NELMS = 2000ULL ; const float N_E = 2.7182F ; const double LOG10E = log(N_E) ; const long double N_PI = 3.141592L ; const Color COLOR_DEFECTO = ROJO ; Variables Las variables se definen, dentro de un bloque de sentencias (v´ease 4.1), especificando su tipo y el identificador con el que nos referiremos a ella, y ser´an visibles desde el punto de declaraci´on hasta el final del cuerpo (bloque) donde han sido declaradas. Se les podr´a asignar un valor inicial en la definici´on (mediante el s´ımbolo =), si no se les asigna ning´un valor inicial, entonces tendr´an un valor inespecificado. Su valor podr´a cambiar mediante la sentencia de asignaci´on (v´ease 4.3) o mediante una sentencia de entrada de datos (v´ease 3.2). { char letra ; // valor inicial inespecificado int contador = 0 ; double total = 5.0 ; ... } 2.5. Operadores Los siguientes operadores se pueden aplicar a los datos, donde las siguiente tabla los muestra ordenados de mayor a menor orden de precedencia, as´ı como tambi´en muestra su asociatividad: Operador Tipo de Operador Asociatividad [] -> . Binarios Izq. a Dch. ! ~ - * Unarios Dch. a Izq. * / % Binarios Izq. a Dch. + - Binarios Izq. a Dch. << >> Binarios Izq. a Dch. < <= > >= Binarios Izq. a Dch. == != Binarios Izq. a Dch. & Binario Izq. a Dch. ^ Binario Izq. a Dch. | Binario Izq. a Dch. && Binario Izq. a Dch. || Binario Izq. a Dch. ?: Ternario Dch. a Izq. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 20. 20 CAP´ITULO 2. TIPOS SIMPLES Significado de los operadores: Aritm´eticos. El resultado es del mismo tipo que los operandos (v´ease 2.6): - valor Menos unario valor * valor Producto (multiplicaci´on) valor / valor Divisi´on (entera o real seg´un el tipo de operandos) valor % valor M´odulo (resto de la divisi´on) (s´olo tipos enteros) valor + valor Suma valor - valor Resta Relacionales/Comparaciones. El resultado es de tipo bool valor < valor Comparaci´on menor valor <= valor Comparaci´on menor o igual valor > valor Comparaci´on mayor valor >= valor Comparaci´on mayor o igual valor == valor Comparaci´on igualdad valor != valor Comparaci´on desigualdad Operadores de Bits, s´olo aplicable a operandos de tipos enteros. El resultado es del mismo tipo que los operandos (v´ease 2.6): ~ valor Negaci´on de bits (complemento) valor << despl Desplazamiento de bits a la izq. valor >> despl Desplazamiento de bits a la dch. valor & valor AND de bits valor ^ valor XOR de bits valor | valor OR de bits L´ogicos, s´olo aplicable operandos de tipo booleano. Tanto el operador && como el operador || se eval´uan en cortocircuito. El resultado es de tipo bool: ! valor Negaci´on l´ogica (Si valor es true entonces false, en otro caso true) valor1 && valor2 AND l´ogico (Si valor1 es false entonces false, en otro caso valor2) valor1 || valor2 OR l´ogico (Si valor1 es true entonces true, en otro caso valor2) x ! x F T T F x y x && y x || y F F F F F T F T T F F T T T T T Condicional. El resultado es del mismo tipo que los operandos: cond ? valor1 : valor2 Si cond es true entonces valor1, en otro caso valor2 2.6. Conversiones Autom´aticas (Impl´ıcitas) de Tipos Es posible que nos interese realizar operaciones en las que se mezclen datos de tipos diferentes. El lenguaje de programaci´on C++ realiza conversiones de tipo autom´aticas (“castings”), de tal forma que el resultado de la operaci´on sea del tipo m´as amplio de los implicados en ella. Siempre que sea posible, los valores se convierten de tal forma que no se pierda informaci´on. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 21. 2.7. CONVERSIONES EXPL´ICITAS DE TIPOS 21 Promociones Enteras Son conversiones impl´ıcitas que preservan valores. Antes de realizar una operaci´on aritm´etica, se utiliza promoci´on a entero para crear int a partir de otros tipos integrales mas cortos. Para los siguientes tipos origen: bool, char, signed char, unsigned char, short, unsigned short Si int puede representar todos los valores posibles del tipo origen, entonces sus valores promocionan a int. En otro caso, promocionan a unsigned int Conversiones Enteras Si el tipo destino es unsigned, el valor resultante es el menor unsigned congruente con el valor origen m´odulo 2n , siendo n el n´umero de bits utilizados en la representaci´on del tipo destino (en representaci´on de complemento a dos, simplemente tiene tantos bits del origen como quepan en el destino, descartando los de mayor orden). Si el tipo destino es signed, el valor no cambia si se puede representar en el tipo destino, si no, viene definido por la implementaci´on. Conversiones Aritm´eticas Impl´ıcitas Habituales Se realizan sobre los operandos de un operador binario para convertirlos a un tipo com´un que ser´a el tipo del resultado: 1. Si alg´un operando es de tipo de punto flotante (real): a) Si alg´un operando es de tipo long double, el otro se convierte a long double. b) En otro caso, si alg´un operando es double el otro se convierte a double. c) En otro caso, si alg´un operando es float el otro se convierte a float. 2. En otro caso, se realizan promociones enteras (v´ease sec. 2.6) sobre ambos operandos: a) Si alg´un operando es de tipo unsigned long, el otro se convierte a unsigned long. b) En otro caso, si alg´un operando es long int y el otro es unsigned int, si un long int puede representar todos los valores de un unsigned int, el unsigned int se convierte a long int; en caso contrario, ambos se convierten a unsigned long int. c) En otro caso, si alg´un operando es long el otro se convierte a long. d) En otro caso, si alg´un operando es unsigned el otro se convierte a unsigned. e) En otro caso, ambos operandos son int 2.7. Conversiones Expl´ıcitas de Tipos Tambi´en es posible realizar conversiones de tipo expl´ıcitas. Para ello, se escribe el tipo al que queremos convertir y entre par´entesis la expresi´on cuyo valor queremos convertir. Por ejemplo: char x = char(65) ; produce el car´acter ’A’ int x = int(’a’) ; convierte el car´acter ’a’ a su valor entero (97) int x = int(ROJO) ; produce el entero 0 int x = int(AMARILLO) ; produce el entero 2 int x = int(3.7) ; produce el entero 3 double x = double(2) ; produce el real (doble precisi´on) 2.0 Color x = Color(1) ; produce el Color AZUL Color x = Color(c+1) ; si c es de tipo Color, produce el siguiente valor de la enumeraci´on Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 22. 22 CAP´ITULO 2. TIPOS SIMPLES El tipo enumerado se convierte autom´aticamente a entero, aunque la conversi´on inversa no se realiza de forma autom´atica. As´ı, para incrementar una variable de tipo color se realizar´a de la siguiente forma: enum Color { ROJO, AZUL, AMARILLO } ; int main() { Color c = ROJO ; c = Color(c + 1) ; // ahora c tiene el valor AZUL } 2.8. Tabla ASCII La tabla ASCII es com´unmente utilizada como base para la representaci´on de los caracteres, donde los n´umeros del 0 al 31 se utilizan para representar caracteres de control, y los n´umeros del 128 al 255 se utilizan para representar caracteres extendidos. Rep Simb Rep Simb Rep Simb Rep Simb 0 0 32 SP 64 @ 96 ‘ 1 SOH 33 ! 65 A 97 a 2 STX 34 " 66 B 98 b 3 ETX 35 # 67 C 99 c 4 EOT 36 $ 68 D 100 d 5 ENQ 37 % 69 E 101 e 6 ACK 38 & 70 F 102 f 7 a 39 ’ 71 G 103 g 8 b 40 ( 72 H 104 h 9 t 41 ) 73 I 105 i 10 n 42 * 74 J 106 j 11 v 43 + 75 K 107 k 12 f 44 , 76 L 108 l 13 r 45 - 77 M 109 m 14 SO 46 . 78 N 110 n 15 SI 47 / 79 O 111 o 16 DLE 48 0 80 P 112 p 17 DC1 49 1 81 Q 113 q 18 DC2 50 2 82 R 114 r 19 DC3 51 3 83 S 115 s 20 DC4 52 4 84 T 116 t 21 NAK 53 5 85 U 117 u 22 SYN 54 6 86 V 118 v 23 ETB 55 7 87 W 119 w 24 CAN 56 8 88 X 120 x 25 EM 57 9 89 Y 121 y 26 SUB 58 : 90 Z 122 z 27 ESC 59 ; 91 [ 123 { 28 FS 60 < 92 124 | 29 GS 61 = 93 ] 125 } 30 RS 62 > 94 ^ 126 ~ 31 US 63 ? 95 _ 127 DEL 2.9. Algunas Consideraciones Respecto a Operaciones con N´umeros Reales La representaci´on de los n´umeros reales es limitada en cuanto al rango de valores y a la precisi´on de los mismos, ya que el n´umero de d´ıgitos decimales que puede almacenar es finito. Adem´as, la representaci´on de n´umeros reales en base 2 hace que la representaci´on de determinados n´umeros en base 10 sea inexacta, por lo tanto la realizaci´on de operaciones aritm´eticas en punto flotante puede Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 23. 2.9. ALGUNAS CONSIDERACIONES RESPECTO A OPERACIONES CON N ´UMEROS REALES23 dar lugar a p´erdidas de precisi´on que den lugar a resultados inesperados, distintas operaciones que matem´aticamente son equivalentes pueden ser computacionalmente diferentes. As´ı, la comparaci´on directa de igualdad o desigualdad de n´umeros reales debe ser evitada. Por ejemplo, el siguiente programa: #include <iostream> using namespace std ; int main() { bool ok = (3.0 * (0.1 / 3.0)) == ((3.0 * 0.1) / 3.0) ; cout << "Resultado de (3.0 * (0.1 / 3.0)) == ((3.0 * 0.1) / 3.0): " << boolalpha << ok << endl ; } produce un resultado distinto a lo que cabr´ıa esperar: Resultado de (3.0 * (0.1 / 3.0)) == ((3.0 * 0.1) / 3.0): false El siguiente programa: #include <iostream> using namespace std ; int main() { double x, y ; double a, b ; cout << "Introduce 3 n´umeros reales: " ; cin >> a >> b >> y ; x = a * b ; bool cmp = (x == y) ; cout << a << " * " << b << " == " << y << " " << boolalpha << cmp << endl ; } produce los siguientes resultados en una m´aquina de 32 bits para los valores de a, b y c: a: 1, b: 1, c: 1 ⇒ true a: 2, b: 2, c: 4 ⇒ true a: 3, b: 3, c: 9 ⇒ true a: 3, b: 0.1, c: 0.3 ⇒ false a: 2, b: 0.2, c: 0.4 ⇒ true a: 3, b: 0.3, c: 0.9 ⇒ false a: 0.1, b: 0.1, c: 0.01 ⇒ false a: 0.2, b: 0.2, c: 0.04 ⇒ false a: 0.3, b: 0.3, c: 0.09 ⇒ false As´ı, la comparaci´on de igualdad entre n´umeros reales se deber´ıa sustituir por una comparaci´on de proximidad, de tal forma que consideraremos que dos n´umeros reales (x, y) son “iguales” si est´an lo suficientemente pr´oximos: #include <iostream> #include <cmath> using namespace std ; int main() { double x, y ; double a, b ; cout << "Introduce 3 n´umeros reales: " ; cin >> a >> b >> y ; x = a * b ; bool cmp = fabs(x - y) < 1e-6 ; cout << a << " * " << b << " == " << y << " " << boolalpha << cmp << endl ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 24. 24 CAP´ITULO 2. TIPOS SIMPLES Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 25. Cap´ıtulo 3 Entrada y Salida de Datos B´asica La entrada y salida de datos permite a un programa recibir informaci´on desde el exterior (usualmente el teclado), la cual ser´a transformada mediante un determinado procesamiento, y posteriormente permitir´a mostrar al exterior (usualmente la pantalla del monitor) el resultado de la computaci´on. Para poder realizar entrada y salida de datos b´asica es necesario incluir la biblioteca iostream que contiene las declaraciones de tipos y operaciones que la realizan. Todas las definiciones y declaraciones de la biblioteca est´andar se encuentran bajo el espacio de nombres std (ver cap´ıtu- lo 10), por lo que para utilizarlos adecuadamente habr´a que utilizar la directiva using al comienzo del programa. #include <iostream> // inclusi´on de la biblioteca de entrada/salida using namespace std ; // utilizaci´on del espacio de nombres de la biblioteca const double EUR_PTS = 166.386 ; int main() { cout << "Introduce la cantidad (en euros): " ; double euros ; cin >> euros ; double pesetas = euros * EUR_PTS ; cout << euros << " Euros equivalen a " << pesetas << " Pts" << endl ; } 3.1. Salida de Datos La salida de datos permite mostrar informaci´on al exterior, y se realiza a trav´es de los flujos de salida, as´ı el flujo de salida asociado a la salida est´andar (usualmente la pantalla o terminal de la consola) se denomina cout. De esta forma, la salida de datos a pantalla se realiza utilizando el operador << sobre el flujo cout especificando el dato cuyo valor se mostrar´a. Por ejemplo: cout << "Introduce la cantidad (en euros): " ; escribir´a en la salida est´andar el mensaje correspondiente a la cadena de caracteres especificada. El siguiente ejemplo escribe en la salida est´andar el valor de las variables euros y pesetas, as´ı como un mensaje para interpretarlos adecuadamente. El s´ımbolo endl indica que la sentencia deber´a escribir un fin de l´ınea (lo que se muestre a continuaci´on se realizar´a en una nueva l´ınea). cout << euros << " Euros equivalen a " << pesetas << " Pts" << endl ; Salida de Datos Formateada Es posible especificar el formato bajo el que se realizar´a la salida de datos. Para ello se debe incluir la biblioteca est´andar iomanip. Por ejemplo: 25
  • 26. 26 CAP´ITULO 3. ENTRADA Y SALIDA DE DATOS B ´ASICA #include <iostream> #include <iomanip> using namespace std ; int main() { bool x = true ; cout << boolalpha << x ; // escribe los booleanos como ’false’ o ’true’ cout << dec << 27 ; // escribe 27 (decimal) cout << hex << 27 ; // escribe 1b (hexadecimal) cout << oct << 27 ; // escribe 33 (octal) cout << setprecision(2) << 4.567 ; // escribe 4.6 cout << setw(5) << 234 ; // escribe " 234" cout << setfill(’#’) << setw(5) << 234 ; // escribe "##234" } donde el manipulador boolalpha especifica que los valores l´ogicos se mostrar´an mediante los valores false y true, y los manipuladores dec, hex, oct especifican que la salida se realizar´a utilizando el sistema de numeraci´on decimal, hexadecimal o octal respectivamente. Por otra parte, el ma- nipulador setprecision(...) especifica la cantidad de d´ıgitos significativos (precisi´on) que se mostrar´a en la salida de n´umeros reales, el manipulador setw(...) especifica la anchura (width) que como m´ınimo ocupar´a la salida da datos (permite mostrar la informaci´on de forma tabulada), y el manipulador setfill(...) especifica el car´acter de relleno (fill) que se utilizar´a en caso de ser necesario para ocupar toda la anchura del campo de salida. 3.2. Entrada de Datos La entrada de datos permite recibir informaci´on desde el exterior, y se realiza a trav´es de los flujos de entrada, as´ı el flujo de entrada asociado a la entrada est´andar (usualmente el teclado) se denomina cin. De esta forma, la entrada de datos desde el teclado se realiza mediante el operador >> sobre el flujo cin especificando la variable donde almacenar el valor de entrada le´ıdo desde el teclado: cin >> euros ; incluso es posible leer varios valores consecutivamente en la misma sentencia de entrada: cin >> minimo >> maximo ; Dicho operador de entrada se comporta de la siguiente forma: elimina los espacios en blanco que hubiera al principio de la entrada de datos, y lee dicha entrada hasta que encuentre alg´un car´acter no v´alido para dicha entrada, que no ser´a le´ıdo y permanecer´a disponible en el buffer de entrada (v´ease 3.3) hasta la pr´oxima operaci´on de entrada. En caso de que durante la entrada surja alguna situaci´on de error, dicha entrada se detiene y el flujo de entrada se pondr´a en un estado err´oneo. Se consideran espacios en blanco los siguientes caracteres: espacio en blanco, tabuladores, retorno de carro y nueva l´ınea (’ ’, ’t’, ’v’, ’f’, ’r’, ’n’). A Entrada de Datos Avanzada A diferencia del comportamiento especificado anteriormente, tambi´en es posible leer un car´acter, desde el flujo de entrada, sin eliminar los espacios iniciales: { char c ; cin.get(c) ; // lee un car´acter sin eliminar espacios en blanco iniciales ... } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 27. 3.2. ENTRADA DE DATOS 27 En caso de querer eliminar los espacios iniciales expl´ıcitamente: { char c ; cin >> ws ; // elimina los espacios en blanco iniciales cin.get(c) ; // lee sin eliminar espacios en blanco iniciales ... } Es posible tambi´en eliminar un n´umero determinado de caracteres del flujo de entrada, o hasta que se encuentre un determinado car´acter: { cin.ignore() ; // elimina el pr´oximo car´acter cin.ignore(5) ; // elimina los 5 pr´oximos caracteres cin.ignore(1000, ’n’) ; // elimina 1000 caracteres o hasta nueva-l´ınea } La entrada y salida de cadenas de caracteres se puede ver en los cap´ıtulos correspondientes (cap. 6.2). A Control del Estado del Flujo Cuando se realiza una entrada de datos err´onea, el flujo de entrada se pondr´a en un estado de error, de tal forma que cualquier operaci´on de entrada de datos sobre un flujo de datos en estado err´oneo tambi´en fallar´a. Por ejemplo, la ejecuci´on del siguiente programa entrar´a en un “bucle sin fin” en caso de que se introduzca una letra en vez de un n´umero, ya que el valor que tome la variable n no estar´a en el rango adecuado, y cualquier otra operaci´on de entrada tambi´en fallar´a, por lo que el valor de n nunca podr´a tomar un valor v´alido dentro del rango espcificado: int main() { int n = 0; do { cout << "Introduzca un numero entre [1..9]: "; cin >> n; } while (! (n > 0 && n < 10)); cout << "Valor: " << n << endl; } Sin embargo, es posible comprobar el estado de un determinado flujo de datos, y en caso de que se encuentre en un estado de error, es posible restaurarlo a un estado correcto, por ejemplo: int main() { int n = 0; do { cout << "Introduzca un numero [1..9]: "; cin >> n; while (cin.fail()) { // ¿ Estado Err´oneo ? cin.clear(); // Restaurar estado cin.ignore(1000, ’n’); // Eliminar la entrada de datos anterior cout << "Error: Introduzca un numero [1..9]: "; cin >> n; } } while (! (n > 0 && n < 10)); cout << "Valor: " << n << endl; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 28. 28 CAP´ITULO 3. ENTRADA Y SALIDA DE DATOS B ´ASICA 3.3. El “Buffer” de Entrada y el “Buffer” de Salida A Ning´un dato de entrada o de salida en un programa C++ se obtiene o env´ıa directamente del/al hardware, sino que se realiza a trav´es de unas zonas de memoria intermedia (“buffers”) de entrada y salida respectivamente controlados por el Sistema Operativo y son independientes de nuestro programa. As´ı, cuando se pulsa alguna tecla, el Sistema Operativo almacena en secuencia las teclas pul- sadas en una zona de memoria intermedia: el “buffer” de entrada. Cuando un programa realiza una operaci´on de entrada de datos (cin >> valor), accede al “buffer” de entrada y obtiene los valores all´ı almacenados si los hubiera, o esperar´a hasta que los haya (se pulsen una serie de teclas seguidas por la tecla “enter”). Una vez obtenidos las teclas pulsadas (caracteres), se convertir´an a un valor del tipo especificado por la operaci´on de entrada, y dicho valor se asignar´a a la variable especificada. De igual forma, cuando se va a mostrar alguna informaci´on de salida dichos datos no van direc- tamente a la pantalla, sino que se convierten a un formato adecuado para ser impresos (caracteres) y se almacenan en una zona de memoria intermedia denominada “buffer” de salida, desde donde el Sistema Operativo tomar´a la informaci´on para ser mostrada por pantalla. cout << "Valor: " << val << endl ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 29. Cap´ıtulo 4 Estructuras de Control Las estructuras de control en el lenguaje de programaci´on C++ son muy flexibles, sin embargo, la excesiva flexibilidad puede dar lugar a estructuras complejas. Por ello s´olo veremos algunas de ellas y utilizadas en contextos y situaciones restringidas. 4.1. Sentencia, Secuencia y Bloque En C++ la unidad b´asica de acci´on es la sentencia, y expresamos la composici´on de sentencias como una secuencia de sentencias terminadas cada una de ellas por el car´acter “punto y coma” (;), de tal forma que su flujo de ejecuci´on es secuencial, es decir, se ejecuta una sentencia, y cuando ´esta termina, entonces se ejecuta la siguiente sentencia, y as´ı sucesivamente. Un bloque es una unidad de ejecuci´on mayor que la sentencia, y permite agrupar una secuencia de sentencias como una unidad. Para ello enmarcamos la secuencia de sentencias entre dos llaves para formar un bloque. Es posible el anidamiento de bloques. int main() { <sentencia_1> ; <sentencia_2> ; { <sentencia_3> ; <sentencia_4> ; . . . } <sentencia_n> ; } 4.2. Declaraciones Globales y Locales Como ya se vio en el cap´ıtulo anterior, es obligatoria la declaraci´on de las entidades manipuladas en el programa. Distinguiremos dos clases de declaraciones: globales y locales. Entidades globales son aquellas que han sido definidas fuera de cualquier bloque. Su ´ambito de visibilidad comprende desde el punto en el que se definen hasta el final del fichero. Respecto a su tiempo de vida, se crean al principio de la ejecuci´on del programa y se destruyen al finalizar ´este. Normalmente ser´an constantes simb´olicas, definiciones de tipos, declaraci´on de prototipos de subprogramas y definiciones de subprogramas. Entidades locales son aquellas que se definen dentro de un bloque. Su ´ambito de visibilidad comprende desde el punto en el que se definen hasta el final de dicho bloque. Respecto a su tiempo de vida, se crean en el punto donde se realiza la definici´on, y se destruyen al finalizar el bloque. Normalmente ser´an constantes simb´olicas y variables locales. 29
  • 30. 30 CAP´ITULO 4. ESTRUCTURAS DE CONTROL #include <iostream> using namespace std ; const double EUR_PTS = 166.386 ; // Declaraci´on de constante GLOBAL int main() { cout << "Introduce la cantidad (en euros): " ; double euros ; // Declaraci´on de variable LOCAL cin >> euros ; double pesetas = euros * EUR_PTS ; // Declaraci´on de variable LOCAL cout << euros << " Euros equivalen a " << pesetas << " Pts" << endl ; } Respecto al ´ambito de visibilidad de una entidad, en caso de declaraciones de diferentes enti- dades con el mismo identificador en diferentes niveles de anidamiento, la entidad visible ser´a aquella que se encuentre declarada en el bloque de nivel de anidamiento m´as interno. Es decir, cuando se solapa el ´ambito de visibilidad de dos entidades con el mismo identificador, en dicha zona de solapamiento ser´a visible el identificador declarado o definido en el bloque m´as interno. Sin embar- go, no es una buena pr´actica de programaci´on ocultar identificadores al redefinirlos en niveles de anidamiento mas internos, ya que conduce a programas dif´ıciles de leer y propensos a errores, por lo que procuraremos evitar esta pr´actica. int main() { int x = 3 ; int z = x * 2 ; // x es vble de tipo int con valor 3 { double x = 5.0 ; double n = x * 2 ; // x es vble de tipo double con valor 5.0 } int y = x + 4 ; // x es vble de tipo int con valor 3 } 4.3. Sentencias de Asignaci´on La sentencia de asignaci´on permite asignar a una variable el resultado de evaluar una expresi´on aritm´etica expresada en notaci´on infija, de tal forma que primero se eval´ua la expresi´on, con- siderando las reglas de precedencia y asociatividad de los operadores (v´ease 2.5), y a continuaci´on el valor resultante se asigna a la variable, que perder´a el valor anterior que tuviese. { ... <variable> = <expresi´on_aritm´etica> ; ... } Por ejemplo: const int MAXIMO = 15 ; int main() { int cnt ; // valor inicial inespecificado cnt = 30 * MAXIMO + 1 ; // asigna a cnt el valor 451 cnt = cnt + 10 ; // cnt pasa ahora a contener el valor 461 } Adem´as, se definen las siguientes sentencias de incremento y decremento, as´ı como su equiva- lencia en la siguiente tabla : Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 31. 4.4. SENTENCIAS DE SELECCI ´ON 31 Sentencia Equivalencia ++variable; variable = variable + 1; --variable; variable = variable - 1; variable++; variable = variable + 1; variable--; variable = variable - 1; variable += expresion; variable = variable + (expresion); variable -= expresion; variable = variable - (expresion); variable *= expresion; variable = variable * (expresion); variable /= expresion; variable = variable / (expresion); variable %= expresion; variable = variable % (expresion); variable &= expresion; variable = variable & (expresion); variable ^= expresion; variable = variable ^ (expresion); variable |= expresion; variable = variable | (expresion); variable <<= expresion; variable = variable << (expresion); variable >>= expresion; variable = variable >> (expresion); Nota: las sentencias de asignaci´on vistas anteriormente se pueden utilizar en otras formas muy diversas, pero nosotros restringiremos su utilizaci´on a la expresada anteriormente, debido a que otras utilizaciones pueden dificultar la legibilidad y aumentar las posibilidades de cometer errores de programaci´on. 4.4. Sentencias de Selecci´on Las sentencias de selecci´on alteran el flujo secuencial de ejecuci´on de un programa, de tal forma que permiten seleccionar flujos de ejecuci´on alternativos y excluyentes dependiendo del valor que tomen determinadas expresiones l´ogicas. La m´as simple de todas es la sentencia de selecci´on condicional if cuya sintaxis es la siguiente: int main() { if ( <expresi´on_l´ogica> ) { <secuencia_de_sentencias> ; } } y cuya sem´antica consiste en evaluar la expresi´on l´ogica, y si su resultado es Verdadero (true) entonces se ejecuta la secuencia de sentencias entre las llaves. Ejemplo de programa que imprime el valor mayor de tres n´umeros: #include <iostream> using namespace std ; int main () { int a, b, c ; cin >> a >> b >> c ; int mayor = a ; if (b > mayor) { mayor = b ; } if (c > mayor) { mayor = c ; } cout << mayor << endl ; } Otra posibilidad es la sentencia de selecci´on condicional compuesta, que tiene la siguiente sintaxis: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 32. 32 CAP´ITULO 4. ESTRUCTURAS DE CONTROL int main() { if ( <expresi´on_l´ogica> ) { <secuencia_de_sentencias_v> ; } else { <secuencia_de_sentencias_f> ; } } y cuya sem´antica consiste en evaluar la expresi´on l´ogica, y si su resultado es Verdadero (true) entonces se ejecuta la <secuencia de sentencias v> . Sin embargo, si el resultado de evaluar la expresi´on l´ogica es Falso (false) entonces se ejecuta la <secuencia de sentencias f> . La sentencia de selecci´on condicional se puede encadenar de la siguiente forma con el flujo de control esperado: #include <iostream> using namespace std ; int main () { double nota ; cin >> nota ; if ( ! ((nota >= 0.0) && (nota <= 10.0))) { cout << "Error: 0 <= n <= 10" << endl ; } else if (nota >= 9.5) { cout << "Matr´ıcula de Honor" << endl ; } else if (nota >= 9.0) { cout << "Sobresaliente" << endl ; } else if (nota >= 7.0) { cout << "Notable" << endl ; } else if (nota >= 5.0) { cout << "Aprobado" << endl ; } else { cout << "Suspenso" << endl ; } } La sentencia switch es otro tipo de sentencia de selecci´on en la cual la secuencia de sentencias alternativas a ejecutar no se decide en base a expresiones l´ogicas, sino en funci´on del valor que tome una determinada expresi´on de tipo ordinal (v´ease 2.2), es decir, una relaci´on de igualdad entre el valor de una expresi´on y unos determinados valores constantes de tipo ordinal especificados. Su sintaxis es la siguiente: int main() { switch ( <expresi´on> ) { case <valor_cte_1> : <secuencia_de_sentencias_1> ; break ; case <valor_cte_2> : case <valor_cte_3> : <secuencia_de_sentencias_2> ; break ; case <valor_cte_4> : <secuencia_de_sentencias_3> ; break ; . . . default: <secuencia_de_sentencias_d> ; break ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 33. 4.5. SENTENCIAS DE ITERACI ´ON. BUCLES 33 } en la cual se eval´ua la expresi´on, y si su valor coincide con <valor cte 1> entonces se ejecuta la <secuencia de sentencias 1> . Si su valor coincide con <valor cte 2> o con <valor cte 3> se ejecuta la <secuencia de sentencias 2> y as´ı sucesivamente. Si el valor de la expresi´on no coincide con ning´un valor especificado, se ejecuta la secuencia de sentencias correspondiente a la etiqueta default (si es que existe). N´otese que la sentencia break; termina la secuencia de sentencias a ejecutar para cada caso, en caso de que falte, el comportamiento no ser´a el deseado. Ejemplo: #include <iostream> using namespace std ; int main () { int dia ; ... // ’dia’ tiene alg´un valor v´alido switch (dia) { case 1: cout << "Lunes" << endl ; break ; case 2: cout << "Martes" << endl ; break ; case 3: cout << "Mi´ercoles" << endl ; break ; case 4: cout << "Jueves" << endl ; break ; case 5: cout << "Viernes" << endl ; break ; case 6: cout << "S´abado" << endl ; break ; case 7: cout << "Domingo" << endl ; break ; default: cout << "Error" << endl ; break ; } } 4.5. Sentencias de Iteraci´on. Bucles Vamos a utilizar tres tipos de sentencias diferentes para expresar la repetici´on de la ejecuci´on de un grupo de sentencias: while, for y do-while. La sentencia while es la m´as utilizada y su sintaxis es la siguiente: int main() { while ( <expresi´on_l´ogica> ) { <secuencia_de_sentencias> ; } } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 34. 34 CAP´ITULO 4. ESTRUCTURAS DE CONTROL en la cual primero se eval´ua la expresi´on l´ogica, y si es cierta, se ejecuta la secuencia de sentencias entre llaves completamente. Posteriormente se vuelve a evaluar la expresi´on l´ogica y si vuelve a ser cierta se vuelve a ejecutar la secuencia de sentencias entre llaves. Este ciclo iterativo consistente en evaluar la condici´on y ejecutar las sentencias se realizar´a MIENTRAS que la condici´on se eval´ue a Verdadera y finalizar´a cuando la condici´on se eval´ue a Falsa. Ejemplo: #include <iostream> using namespace std ; int main () { int num, divisor ; cin >> num ; if (num <= 1) { divisor = 1 ; } else { divisor = 2 ; while ((num % divisor) != 0) { ++divisor ; } } cout << "El primer divisor de " << num << " es " << divisor << endl ; } La sentencia for es semejante a la estructura FOR de Pascal o Modula-2, aunque en C++ toma una dimensi´on m´as amplia y flexible. En realidad se trata de la misma construcci´on while vista anteriormente pero con una sintaxis diferente para hacer m´as expl´ıcito los casos en los que la iteraci´on est´a controlada por los valores que toma una determinada variable de control, de tal forma que existe una clara inicializaci´on y un claro incremento de la variable de control hasta llegar al caso final. La sintaxis es la siguiente: int main() { for ( <inicializaci´on> ; <expresi´on_l´ogica> ; <incremento> ) { <secuencia_de_sentencias> ; } } y es equivalente a: int main() { <inicializaci´on> ; while ( <expresi´on_l´ogica> ) { <secuencia_de_sentencias> ; <incremento> ; } } Nota: es posible y adecuado declarar e inicializar la variable de control del bucle en el lugar de la inicializaci´on. En este caso especial, el ´ambito de visibilidad de la variable de control del bucle es solamente hasta el final del bloque de la estructura for. #include <iostream> using namespace std ; int main () { int n ; cin >> n ; for (int i = 0 ; i < n ; ++i) { cout << i << " " ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 35. 4.6. PROGRAMACI ´ON ESTRUCTURADA 35 } // i ya no es visible aqu´ı cout << endl ; } La sentencia do-while presenta la siguiente estructura int main() { do { <secuencia_de_sentencias> ; } while ( <expresi´on_l´ogica> ) ; } tambi´en expresa la iteraci´on en la ejecuci´on de la secuencia de sentencias, pero a diferencia de la primera estructura iterativa ya vista, donde primero se eval´ua la expresi´on l´ogica y despu´es, en caso de ser cierta, se ejecuta la secuencia de sentencias, en esta estructura se ejecuta primero la secuencia de sentencias y posteriormente se eval´ua la expresi´on l´ogica, y si ´esta es cierta se repite el proceso. #include <iostream> using namespace std; int main () { int num ; do { cin >> num ; } while ((num % 2) != 0) ; cout << "El n´umero par es " << num << endl ; } 4.6. Programaci´on Estructurada Un programa sigue una metodolog´ıa de programaci´on estructurada si todas las estructuras de control que se utilizan (secuencia, selecci´on, iteraci´on y modularizaci´on) tienen un ´unico punto de entrada y un ´unico punto de salida. Esta caracter´ıstica hace posible que se pueda aplicar la abstracci´on para su dise˜no y desarrollo. La abstracci´on se basa en la identificaci´on de los elementos a un determinado nivel, ignorando los detalles especificados en niveles inferiores. Un algoritmo que use tan s´olo las estructuras de control tratadas en este tema, se denomina estructurado. Bohm y Jacopini demostraron que todo problema computable puede resolverse usando ´unica- mente estas estructuras de control. ´Esta es la base de la programaci´on estructurada. La abstracci´on algor´ıtmica va un paso m´as all´a y considera que cada nivel de refinamiento corresponde con un subprograma independiente 4.7. Ejemplos Ejemplo 1 Programa que multiplica dos n´umeros mediante sumas acumulativas: #include <iostream> using namespace std ; int main () { cout << "Introduzca dos n´umeros: " ; int m, n ; cin >> m >> n ; // Sumar: m+m+m+...+m (n veces) Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 36. 36 CAP´ITULO 4. ESTRUCTURAS DE CONTROL int total = 0 ; for (int i = 0 ; i < n ; ++i) { // Proceso iterativo: acumular el valor de ’m’ al total total = total + m ; // total += m ; } cout << total << endl ; } Ejemplo 2 Programa que calcula el factorial de un n´umero dado: #include <iostream> using namespace std ; int main () { cout << "Introduzca un n´umero: " ; int n ; cin >> n ; // Multiplicar: 1 2 3 4 5 6 7 ... n int fact = 1 ; for (int i = 2 ; i <= n ; ++i) { // Proceso iterativo: acumular el valor de ’i’ al total fact = fact * i ; // fact *= i ; } cout << fact << endl ; } Ejemplo 3 Programa que divide dos n´umeros mediante restas sucesivas: #include <iostream> using namespace std ; int main () { cout << "Introduzca dos n´umeros: " ; int dividendo, divisor ; cin >> dividendo >> divisor ; if (divisor == 0) { cout << "El divisor no puede ser cero" << endl ; } else { int resto = dividendo ; int cociente = 0 ; while (resto >= divisor) { resto -= divisor ; ++cociente ; } cout << cociente << " " << resto << endl ; } } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 37. Cap´ıtulo 5 Subprogramas. Funciones y Procedimientos La abstracci´on es una herramienta mental que nos permite analizar, comprender y construir sis- temas complejos. As´ı, identificamos y denominamos conceptos abstractos y aplicamos refinamientos sucesivos hasta comprender y construir el sistema completo. Los subprogramas constituyen una herramienta del lenguaje de programaci´on que permite al programador aplicar expl´ıcitamente la abstracci´on al dise˜no y construcci´on del software, propor- cionando abstracci´on algor´ıtmica (procedimental) a la construcci´on de programas. Los subprogramas pueden ser vistos como un mini programa encargado de resolver algor´ıtmi- camente un subproblema que se encuentra englobado dentro de otro mayor. En ocasiones tambi´en pueden ser vistos como una ampliaci´on o elevaci´on del conjunto de operaciones b´asicas (accio- nes primitivas) del lenguaje de programaci´on, proporcion´andole nuevos mecanismos para resolver nuevos problemas. 5.1. Funciones y Procedimientos Los subprogramas codifican la soluci´on algor´ıtmica a un determinado problema, de tal forma que cuando es necesario resolver dicho problema en un determinado momento de la computaci´on, se invocar´a a una instancia del subprograma (mediante una llamada al subprograma) para que resuelva el problema para unos determinados par´ametros. As´ı, en la invocaci´on al subprograma, se transfiere la informaci´on que necesita para resolver el problema, entonces se ejecuta el c´odigo del subprograma que resolver´a el problema y produce unos resultados que devuelve al lugar donde ha sido requerido. Podemos distinguir dos tipos de subprogramas: Procedimientos: encargados de resolver un problema computacional general. En el siguiente ejemplo el procedimiento ordenar ordena los valores de las variables pasadas como par´amet- ros (x e y), de tal forma que cuando termine el procedimiento, las variables x e y tendr´an el menor y el mayor valor respectivamente de los valores originales: int main() { int x = 8 ; int y = 4 ; ordenar(x, y) ; cout << x << " " << y << endl ; } Funciones: encargadas de realizar un c´alculo computacional y generar un ´unico resultado, normalmente calculado en funci´on de los datos recibidos. En el siguiente ejemplo, la fun- ci´on calcular_menor calcula (y devuelve) el menor valor de los dos valores recibidos como par´ametros: 37
  • 38. 38 CAP´ITULO 5. SUBPROGRAMAS. FUNCIONES Y PROCEDIMIENTOS int main() { int x = 8 ; int y = 4 ; int z = calcular_menor(x, y) ; cout << "Menor: " << z << endl ; } La llamada (invocaci´on) a un subprograma se realiza mediante el nombre seguido por los par´ametros actuales entre par´entesis, considerando que: La llamada a un procedimiento constituye por s´ı sola una sentencia independiente que puede ser utilizada como tal en el cuerpo de otros subprogramas (y del programa principal). La llamada a una funci´on no constituye por s´ı sola una sentencia, por lo que debe aparecer dentro de alguna sentencia que utilice el valor resultado de la funci´on. 5.2. Definici´on de Subprogramas Los subprogramas codifican la soluci´on algor´ıtmica parametrizada a un determinado problema, es decir, especifica la secuencia de acciones a ejecutar para resolver un determinado problema dependiendo de unos determinados par´ametros formales. Donde sea necesaria la resoluci´on de dicho problema, se invocar´a a una instancia del subprograma para unos determinados par´ametros actuales. Considerando la norma de C++ de que antes de utilizar una determinada entidad en un pro- grama, esta entidad deber´a estar previamente declarada, normalmente deberemos definir los sub- programas en una posici´on previa a donde sean utilizados. No obstante, esta disposici´on de los subprogramas puede ser alterada como se indica en la secci´on 5.7. La definici´on de los subprogra- mas presentados anteriormente podr´ıa ser como se indica a continuaci´on: #include <iostream> using namespace std ; int calcular_menor(int a, int b) { int menor ; if (a < b) { menor = a ; } else { menor = b ; } return menor ; } void ordenar(int& a, int& b) { if (a > b) { int aux = a ; a = b ; b = aux ; } } int main() { int x = 8 ; int y = 4 ; int z = calcular_menor(x, y) ; cout << "Menor: " << z << endl ; ordenar(x, y) ; cout << x << " " << y << endl ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 39. 5.3. EJECUCI ´ON DE SUBPROGRAMAS 39 La definici´on de un subprograma comienza con un encabezamiento en la que se especifica en primer lugar el tipo del valor devuelto por ´este si es una funci´on, o void en caso de ser un procedimiento. A continuaci´on vendr´a el nombre del subprograma seguido por la declaraci´on de sus par´ametros formales entre par´entesis. Los par´ametros formales especifican como se realiza la transferencia de informaci´on entre el (sub)programa llamante y el subprograma llamado (v´ease 5.4). Normalmente la soluci´on de un subproblema depender´a del valor de algunos datos, modificar´a el valor de otros datos, y posible- mente generar´a nuevos valores. Todo este flujo de informaci´on se realiza a trav´es de los par´ametros del subprograma. El cuerpo del subprograma especifica la secuencia de acciones a ejecutar necesarias para resolver el subproblema especificado, y podr´a definir tantas variables locales como necesite para desempe˜nar su misi´on. En el caso de una funci´on, el valor que devuelve (el valor que toma tras la llamada) vendr´a dado por el resultado de evaluar la expresi´on de la sentencia return. Aunque C++ es m´as flexible, nosotros s´olo permitiremos una ´unica utilizaci´on de la sentencia return y deber´a ser al final del cuerpo de la funci´on. As´ı mismo, un procedimiento no tendr´a ninguna sentencia return en su cuerpo. 5.3. Ejecuci´on de Subprogramas Cuando se produce una llamada (invocaci´on) a un subprograma: 1. Se establecen las v´ıas de comunicaci´on entre los algoritmos llamante y llamado por medio de los par´ametros. 2. Posteriormente el flujo de ejecuci´on pasa a ejecutar la primera sentencia del cuerpo del subprograma llamado, ejecut´andose ´este. 3. Cuando sea necesario, se crean las variables locales especificadas en el cuerpo de la definici´on del subprograma. 4. Cuando finaliza la ejecuci´on del subprograma, las variables locales y par´ametros previamente creados se destruyen, el flujo de ejecuci´on retorna al (sub)programa llamante, y contin´ua la ejecuci´on por la sentencia siguiente a la llamada realizada. 5.4. Paso de Par´ametros. Par´ametros por Valor y por Re- ferencia Todo el intercambio y transferencia de informaci´on entre el programa llamante y el subprograma llamado se debe realizar a trav´es de los par´ametros. Los par´ametros formales son los que aparecen en la definici´on del subprograma, mientras que los par´ametros actuales son los que aparecen en la llamada (invocaci´on) al subprograma. Denominamos par´ametros de entrada a aquellos par´ametros que se utilizan para recibir la informaci´on necesaria para realizar una computaci´on. Por ejemplo los par´ametros a y b de la funci´on calcular_menor anterior. • Los par´ametros de entrada se definen mediante paso por valor (cuando son de tipos simples), que significa que los par´ametros formales son variables independientes que toman sus valores iniciales como copias de los valores de los par´ametros actuales de la llamada en el momento de la invocaci´on al subprograma. Se declaran especificando el tipo y el identificador asociado. int calcular_menor(int a, int b) { int menor ; if (a < b) { Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 40. 40 CAP´ITULO 5. SUBPROGRAMAS. FUNCIONES Y PROCEDIMIENTOS menor = a ; } else { menor = b ; } return menor ; } • Cuando los par´ametros de entrada son de tipos compuestos (v´ease 6), entonces se definen mediante paso por referencia constante que ser´a explicado m´as adelante. Denominamos par´ametros de salida a aquellos par´ametros que se utilizan para transferir al programa llamante informaci´on producida como parte de la computaci´on/soluci´on realizada por el subprograma. • Los par´ametros de salida se definen mediante paso por referencia que significa que el par´ametro formal es una referencia a la variable que se haya especificado como par´ametro actual de la llamada en el momento de la invocaci´on al subprograma. Es decir, cualquier acci´on dentro del subprograma que se haga sobre el par´ametro formal es equivalente a que se realice sobre la variable referenciada que aparece como par´ametro actual en la llamada al subprograma. Se declaran especificando el tipo, el s´ımbolo “ampersand” (&) y el identificador asociado. En el siguiente ejemplo, el procedimiento dividir recibe dos valores sobre los cuales realizar´a la operaci´on de divisi´on (dividendo y divisor son par´ametros de entrada y son pasados por valor), y devuelve dos valores como resultado de la divisi´on (cociente y resto son par´ametros de salida y son pasados por referencia): void dividir(int dividendo, int divisor, int& coc, int& resto) { coc = dividendo / divisor ; resto = dividendo % divisor ; } int main() { int cociente ; int resto ; dividir(7, 3, cociente, resto) ; // ahora ’cociente’ valdr´a 2 y ’resto’ valdr´a 1 } Denominamos par´ametros de entrada/salida a aquellos par´ametros que se utilizan para recibir informaci´on necesaria para realizar la computaci´on, y que tras ser modificada se transfiere al lugar de llamada como parte de la informaci´on producida resultado de la computaci´on del subprograma. Por ejemplo los par´ametros a y b del procedimiento ordenar anterior. Los par´ametros de entrada/salida se definen mediante paso por referencia y se declaran como se especific´o anteriormente. void ordenar(int& a, int& b) { if (a > b) { int aux = a ; a = b ; b = aux ; } } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 41. 5.5. CRITERIOS DE MODULARIZACI ´ON 41 Tipos Simples Compuestos (⇓) Entrada P.Valor (int x) P.Ref.Cte (const Persona& x) (⇑) Salida, ( ) E/S P.Ref (int& x) P.Ref (Persona& x) En la llamada a un subprograma, se deben cumplir los siguientes requisitos: El n´umero de par´ametros actuales debe coincidir con el n´umero de par´ametros formales. La correspondencia entre par´ametros actuales y formales es posicional. El tipo del par´ametro actual debe coincidir con el tipo del correspondiente par´ametro formal. Un par´ametro formal de salida o entrada/salida (paso por referencia) requiere que el par´ametro actual sea una variable. Un par´ametro formal de entrada (paso por valor o paso por referencia constante) requiere que el par´ametro actual sea una variable, constante o expresi´on. Tipos Simples Tipos Compuestos (⇓) Ent (⇑) Sal ( ) E/S (⇓) Ent (⇑) Sal ( ) E/S Par´ametro Formal P.Valor P.Referencia P.Ref.Constante P.Referencia (int x) (int& x) (const Persona& x) (Persona& x) Constante Constante Par´ametro Actual Variable Variable Variable Variable Expresi´on Expresi´on 5.5. Criterios de Modularizaci´on No existen m´etodos objetivos para determinar como descomponer la soluci´on de un problema en subprogramas, es una labor subjetiva. No obstante, se siguen algunos criterios que pueden guiarnos para descomponer un problema y modularizar adecuadamente. El dise˜nador de software debe buscar un bajo acoplamiento entre los subprogramas y una alta cohesi´on dentro de cada uno. Acoplamiento: Un objetivo en el dise˜no descendente es crear subprogramas aislados e inde- pendientes. Sin embargo, debe haber alguna conexi´on entre los subprogramas para formar un sistema coherente. A dicha conexi´on se conoce como acoplamiento. Por lo tanto, maximizar la independencia entre subprogramas ser´a minimizar el acoplamiento. Cohesi´on: Hace referencia al grado de relaci´on entre las diferentes partes internas dentro de un mismo subprograma. Si la cohesi´on es muy d´ebil, la diversidad entre las distintas tareas realizadas dentro del subprograma es tal que posteriores modificaciones podr´an resultar complicadas. Se busca maximizar la cohesi´on dentro de cada subprograma Si no es posible analizar y comprender un subprograma de forma aislada e independiente del resto, entonces podemos deducir que la divisi´on modular no es la m´as adecuada. 5.6. Subprogramas “en L´ınea” A La llamada a un subprograma conlleva un peque˜no coste debido al control y gesti´on de la misma que ocasiona cierta p´erdida de tiempo de ejecuci´on. Hay situaciones en las que el subprograma es tan peque˜no que el coste asociado a la invocaci´on es superior al coste asociado a computar la soluci´on del mismo, de tal forma que en estas situaciones interesa eliminar el coste asociado a su invocaci´on. En ese caso se puede especificar que el subpro- grama se traduzca como c´odigo en l´ınea en vez de como una llamada a un subprograma. Para ello Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 42. 42 CAP´ITULO 5. SUBPROGRAMAS. FUNCIONES Y PROCEDIMIENTOS se especificar´a la palabra reservada inline justo antes del tipo. De esta forma, se mantiene los beneficios proporcionados por la abstracci´on, pero se eliminan los costes asociados a la invocaci´on. inline int calcular_menor(int a, int b) { int menor ; if (a < b) { menor = a ; } else { menor = b ; } return menor ; } Este mecanismo s´olo es adecuado cuando el cuerpo del subprograma es muy peque˜no, de tal forma que el coste asociado a la invocaci´on dominar´ıa respecto a la ejecuci´on del cuerpo del mismo. 5.7. Declaraci´on de Subprogramas. Prototipos A Los subprogramas, al igual que los tipos, constantes y variables, deben ser declarados antes de ser utilizados. Dicha declaraci´on se puede realizar de dos formas: una de ellas consiste simplemente en definir el subprograma antes de utilizarlo. La otra posibilidad consiste en declarar el prototipo del subprograma antes de su utilizaci´on, y definirlo posteriormente. El ´ambito de visibilidad del subprograma ser´a global al fichero, es decir, desde el lugar donde ha sido declarado hasta el final del fichero. Para declarar un subprograma habr´a que especificar el tipo del valor devuelto (o void si es un procedimiento) seguido por el nombre y la declaraci´on de los par´ametros formales igual que en la definici´on del subprograma, pero sin definir el cuerpo del mismo. En lugar de ello se terminar´a la declaraci´on con el car´acter “punto y coma” (;). int calcular_menor(int a, int b) ; // prototipo de ’calcular_menor’ int main() { int x = 8 ; int y = 4 ; int z = calcular_menor(x, y) ; } int calcular_menor(int a, int b) // definici´on de ’calcular_menor’ { int menor ; if (a < b) { menor = a ; } else { menor = b ; } return menor ; } 5.8. Sobrecarga de Subprogramas A Se denomina sobrecarga cuando distintos subprogramas se denominan con el mismo identifi- cador u operador, pero se aplican a par´ametros distintos. En el lenguaje de programaci´on C++ es posible sobrecargar tanto subprogramas como operadores siempre y cuando tengan par´ametros diferentes, y el compilador pueda discriminar entre ellos por la especificaci´on de la llamada. void imprimir(int x) { cout << "entero: " << x << endl ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 43. 5.9. PRE-CONDICIONES Y POST-CONDICIONES 43 } void imprimir(double x) { cout << "real: " << x << endl ; } inline double media(int x, int y, int z) { return double(x + y + z) / 3.0 ; } inline double media(int x, int y) { return double(x + y) / 2.0 ; } 5.9. Pre-Condiciones y Post-Condiciones A Pre-condici´on es un enunciado que debe ser cierto antes de la llamada a un subprograma. Especifica las condiciones bajo las cuales se ejecutar´a dicho subprograma. Post-condici´on es un enunciado que debe ser cierto tras la ejecuci´on de un subprograma. Especifica el comportamiento de dicho subprograma. Codificar las pre/post-condiciones mediante asertos proporciona una valiosa documentaci´on, y tiene varias ventajas: • Hace al programador expl´ıcitamente consciente de los prerrequisitos y del objetivo del subprograma. • Durante la depuraci´on, las pre-condiciones comprueban que la llamada al subprograma se realiza bajo condiciones validas. • Durante la depuraci´on, las post-condiciones comprueban que el comportamiento del subprograma es adecuado. • Sin embargo, a veces no es posible codificarlas f´acilmente. En C++, las pre-condiciones y post-condiciones se pueden especificar mediante asertos, para los cuales es necesario incluir la biblioteca cassert. Por ejemplo: #include <iostream> #include <cassert> using namespace std ; //--------------------------- void dividir(int dividendo, int divisor, int& cociente, int& resto) { assert(divisor != 0) ; // PRE-CONDICI´ON cociente = dividendo / divisor ; resto = dividendo % divisor ; assert(dividendo == (divisor * cociente + resto)) ; // POST-CONDICI´ON } Nota: en GNU GCC es posible desactivar la comprobaci´on de asertos mediante la siguiente directiva de compilaci´on: g++ -DNDEBUG -ansi -Wall -Werror -o programa programa.cpp Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 44. 44 CAP´ITULO 5. SUBPROGRAMAS. FUNCIONES Y PROCEDIMIENTOS 5.10. Ejemplos Ejemplo 1 Ejemplo de un programa que imprime los n´umeros primos existentes entre dos valores le´ıdos por teclado: //- fichero: primos.cpp -------------------------------------------- #include <iostream> using namespace std ; void ordenar(int& menor, int& mayor) { if (mayor < menor) { int aux = menor ; menor = mayor ; mayor = aux ; } } inline bool es_divisible(int x, int y) { return ( x % y == 0 ) ; } bool es_primo(int x) { int i ; for (i = 2 ; ((i <= x/2) && ( ! es_divisible(x, i))) ; ++i) { // vac´ıo } return (i == x/2+1) ; } void primos(int min, int max) { cout << "N´umeros primos entre " << min << " y " << max << endl ; for (int i = min ; i <= max ; ++i) { if (es_primo(i)) { cout << i << " " ; } } cout << endl ; } int main() { int min, max ; cout << "Introduzca el rango de valores " ; cin >> min >> max ; ordenar(min, max) ; primos(min, max) ; } //- fin: primos.cpp ------------------------------------------------ Ejemplo 2 Ejemplo de un programa que convierte grados sexagesimales a radianes: //- fichero: gradrad.cpp -------------------------------------------- #include <iostream> #include <string> using namespace std ; // -- Constantes ------- const double PI = 3.1416 ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 45. 5.10. EJEMPLOS 45 const int PI_GRAD = 180 ; const int MIN_GRAD = 60 ; const int SEG_MIN = 60 ; const int SEG_GRAD = SEG_MIN * MIN_GRAD ; // -- Subalgoritmos ---- void leer_grados (int& grad, int& min, int& seg) { cout << "Grados, minutos y segundos " ; cin >> grad >> min >> seg ; } //--------------------------- void escribir_radianes (double rad) { cout << "Radianes: " << rad << endl ; } //--------------------------- double calc_rad (double grad_tot) { return (grad_tot * PI) / double(PI_GRAD) ; } //--------------------------- double calc_grad_tot (int grad, int min, int seg) { return double(grad) + (double(min) / double(MIN_GRAD)) + (double(seg) / double(SEG_GRAD)) ; } //--------------------------- double transf_gr_rad (int grad, int min, int seg) { double gr_tot = calc_grad_tot(grad, min, seg) ; return calc_rad(gr_tot) ; } // -- Principal -------- int main () { int grad, min, seg ; leer_grados(grad, min, seg) ; double rad = transf_gr_rad(grad, min, seg) ; escribir_radianes(rad) ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 46. 46 CAP´ITULO 5. SUBPROGRAMAS. FUNCIONES Y PROCEDIMIENTOS Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 47. Cap´ıtulo 6 Tipos Compuestos Los tipos compuestos surgen de la composici´on y/o agregaci´on de otros tipos para formar nuevos tipos de mayor entidad. Existen dos formas fundamentales para crear tipos de mayor entidad: la composici´on de elementos, que denominaremos “Registros” o “Estructuras” y la agregaci´on de elementos del mismo tipo, y se conocen como “Agregados”, “Arreglos” o mediante su nombre en ingl´es “Arrays”. Adem´as de los tipos compuestos definidos por el programador mencionados ante- riormente, los lenguajes de programaci´on suelen proporcionar alg´un tipo adicional para representar las “cadenas de caracteres”. 6.1. Paso de Par´ametros de Tipos Compuestos Los lenguajes de programaci´on normalmente utilizan el paso por valor y el paso por referencia para implementar la transferencia de informaci´on entre subprogramas descrita en el interfaz. Para la transferencia de informaci´on de entrada, el paso por valor supone duplicar y copiar el valor del par´ametro actual en el formal. En el caso de tipos simples, el paso por valor es adecuado para la transferencia de informaci´on de entrada, sin embargo, si el tipo de dicho par´ametro es compuesto, es posible que dicha copia implique una alta sobrecarga, tanto en espacio de memoria como en tiempo de ejecuci´on. El lenguaje de programaci´on C++ permite realizar de forma eficiente la transferencia de informaci´on de entrada para tipos compuestos mediante el paso por referencia constante. As´ı, en el paso por referencia constante el par´ametro formal es una referencia al par´ametro actual especificado en la llamada, tomando as´ı su valor, pero no puede ser modificado al ser una referencia constante, evitando de esta forma la sem´antica de salida asociada al paso por referencia. El paso por referencia constante suele utilizarse para el paso de par´ametros de entrada con tipos compuestos, ya que evita la duplicaci´on de memoria y la copia del valor, que en el caso de tipos compuestos suele ser costosa. Para ello, los par´ametros se declaran como se especific´o anteriormente para el paso por referencia, pero anteponiendo la palabra reservada const. void imprimir(const Fecha& fech) { cout << fech.dia << (int(fech.mes)+1) << fech.anyo << endl ; } Tipos Simples Compuestos (⇓) Entrada P.Valor (int x) P.Ref.Cte (const Persona& p) (⇑) Salida, ( ) E/S P.Ref (int& x) P.Ref (Persona& p) 47
  • 48. 48 CAP´ITULO 6. TIPOS COMPUESTOS Funciones que Retornan Tipos Compuestos Por la misma raz´on y como norma general, salvo excepciones, tampoco es adecuado que una funci´on retorne un valor de tipo compuesto, debido a la sobrecarga que generalmente ´esto conlleva. En estos casos, suele ser m´as adecuado que el subprograma devuelva el valor de tipo compuesto como un par´ametro de salida mediante el paso por referencia. 6.2. Cadenas de Caracteres en C++: el Tipo String Las cadenas de caracteres representan una sucesi´on o secuencia de caracteres. Es un tipo de datos muy vers´atil, y es ´util para representar informaci´on muy diversa: Informaci´on textual (caracteres) Entrada de datos y salida de resultados en forma de secuencia de caracteres. Informaci´on abstracta por medio de una secuencia de caracteres Es posible utilizar el tipo string de la biblioteca est´andar para representar cadenas de caracteres de longitud finita limitada por la implementaci´on. Para ello, se debe incluir la biblioteca est´andar <string>, as´ı como utilizar el espacio de nombres de std. La definici´on de cadenas de carac- teres mediante el tipo string permite definir cadenas de caracteres mas robustas y con mejores caracter´ısticas que las cadenas de caracteres predefinidas al estilo-C (arrays de caracteres). Es posible definir tanto constantes simb´olicas como variables y par´ametros de tipo string. Una cadena de caracteres literal se representa mediante una sucesi´on de caracteres entre comillas dobles. As´ı mismo, tambi´en es posible la asignaci´on de cadenas de caracteres: AUTOR: J o s e L u i s 0 1 2 3 4 5 6 7 8 nombre: P e p e 0 1 2 3 nombre: J o s e L u i s 0 1 2 3 4 5 6 7 8 #include <iostream> #include <string> using namespace std ; const string AUTOR = "Jos´e Luis" ; int main() { string nombre = "Pepe" ; // ... nombre = AUTOR ; } Si no se le asigna un valor inicial a una variable de tipo string, entonces la variable tendr´a como valor por defecto la cadena vac´ıa (""). Entrada y Salida de Cadenas de Caracteres El operador << aplicado a un flujo de salida (cout para el flujo de salida est´andar, usualmente el terminal) permite mostrar el contenido de las cadenas de caracteres, tanto constantes como variables. Por ejemplo: #include <iostream> #include <string> using namespace std ; const string AUTOR = "Jos´e Luis" ; int main() { string nombre = "Pepe" ; cout << "Nombre: " << nombre << " " << AUTOR << endl ; } El operador >> aplicado a un flujo de entrada (cin para el flujo de entrada est´andar, usualmente el teclado) permite leer secuencias de caracteres y almacenarlas en variables de tipo string. Por ejemplo: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 49. 6.2. CADENAS DE CARACTERES EN C++: EL TIPO STRING 49 #include <iostream> #include <string> using namespace std ; int main() { cout << "Introduzca el nombre: " ; string nombre ; cin >> nombre ; cout << "Nombre: " << nombre << endl ; } Este operador de entrada (>>) se comporta (como se especific´o en el cap´ıtulo 3.2 dedicado a la Entrada y Salida b´asica) de la siguiente forma: elimina los espacios en blanco que hubiera al principio de la entrada de datos, y lee dicha entrada hasta que encuentre alg´un car´acter de espacio en blanco, que no ser´a le´ıdo y permanecer´a en el buffer de entrada (v´ease 3.3) hasta la pr´oxima operaci´on de entrada. En caso de que durante la entrada surja alguna situaci´on de error, dicha entrada se detiene y el flujo de entrada se pondr´a en un estado err´oneo. Se consideran espacios en blanco los siguientes caracteres: espacio en blanco, tabuladores, retorno de carro y nueva l´ınea (’ ’, ’t’, ’v’, ’f’, ’r’, ’n’). Tambi´en es posible leer una l´ınea completa, hasta leer el car´acter de fin de l´ınea (ENTER), desde el flujo de entrada, sin eliminar los espacios iniciales: #include <iostream> #include <string> using namespace std ; int main() { cout << "Introduzca el nombre: " ; string nombre ; getline(cin, nombre) ; cout << "Nombre: " << nombre << endl ; } Tambi´en es posible leer una l´ınea completa, hasta leer un delimitador especificado, desde el flujo de entrada, sin eliminar los espacios iniciales: #include <iostream> #include <string> using namespace std ; const char DELIMITADOR = ’.’ ; int main() { cout << "Introduzca el nombre: " ; string nombre ; getline(cin, nombre, DELIMITADOR) ; cout << "Nombre: " << nombre << endl ; } N´otese que realizar una operaci´on getline despu´es de una operaci´on con >> puede tener compli- caciones, ya que >> dejara los espacios en blanco (y fin de l´ınea) en el buffer, que ser´an le´ıdos por getline. Por ejemplo: #include <iostream> #include <string> using namespace std ; int main() { //----------------------------------------------------- cout << "Introduzca n´umero: " ; int n ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 50. 50 CAP´ITULO 6. TIPOS COMPUESTOS cin >> n ; //----------------------------------------------------- cout << "Introduzca el nombre: " ; string nombre ; getline(cin, nombre) ; //----------------------------------------------------- cout << "N´umero: " << n << " Nombre: [" << nombre << "]" << endl ; } Para evitar este problema, eliminaremos los caracteres de espacios en blanco (y fin de l´ınea) del buffer de entrada antes de leer la entrada de datos con getline(...), de tal forma que leer´a una secuencia de caracteres que sea distinta de la vac´ıa: #include <iostream> #include <string> using namespace std ; int main() { //----------------------------------------------------- cout << "Introduzca n´umero: " ; int n ; cin >> n ; //----------------------------------------------------- cout << "Introduzca el nombre (NO puede ser vac´ıo): " ; string nombre ; ent >> ws ; // salta los espacios en blanco y fin de l´ınea getline(cin, nombre) ; // leer´a la primera l´ınea no vac´ıa //----------------------------------------------------- cout << "N´umero: " << n << " Nombre: " << nombre << endl ; } Por el contrario, en caso de que la cadena vac´ıa sea una entrada v´alida posible, entonces ser´a nece- sario eliminar el resto de caracteres (incluyendo los espacios en blanco y fin de l´ınea) del buffer de entrada, despu´es de leer un dato con >>, de tal forma que el buffer est´e limpio antes de realizar la entrada de la cadena de caracteres con getline. Por ejemplo: #include <iostream> #include <string> #include <limits> using namespace std ; int main() { //----------------------------------------------------- cout << "Introduzca n´umero: " ; int n ; cin >> n ; cin.ignore(10000, ’n’) ; // elimina todos los caracteres del buffer hasta ’n’ //----------------------------------------------------- cout << "Introduzca el nombre (puede ser vac´ıo): " ; string nombre ; getline(cin, nombre) ; //----------------------------------------------------- cout << "N´umero: " << n << " Nombre: " << nombre << endl ; } Operaciones con Cadenas de Caracteres Las cadenas de caracteres se pueden asignar a variables de dicho tipo. Por ejemplo: #include <iostream> Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 51. 6.2. CADENAS DE CARACTERES EN C++: EL TIPO STRING 51 #include <string> using namespace std ; const string AUTOR = "Jos´e Luis" ; int main() { string nombre = "Pepe" ; // ... nombre = AUTOR ; } Es posible realizar la comparaci´on lexicogr´afica1 entre cadenas de caracteres del tipo string mediante los operadores relacionales (==, !=, >, >=, <, <=). Por ejemplo: • if (nombre >= AUTOR) { /*...*/ } Es posible la concatenaci´on de cadenas y caracteres mediante los operadores de concatenaci´on (+, +=): #include <iostream> #include <string> using namespace std ; const string AUTOR = "Jos´e Luis" ; int main () { string nombre = AUTOR + "L´opez" ; nombre += "V´azque" ; nombre += ’z’ ; nombre = AUTOR + ’s’ ; } Para acceder al n´umero de caracteres que componen la cadena: • unsigned ncar = nombre.size(); Comprobar si la cadena de caracteres est´a vac´ıa: • if (nombre.size() == 0) { /*...*/ } Para acceder al i-´esimo car´acter de la cadena (de tipo char): • char c = nombre[i]; donde i ∈ [0..nombre.size()-1] • nombre[i] = ’z’; donde i ∈ [0..nombre.size()-1] Para acceder al i-´esimo car´acter de la cadena (de tipo char), comprobando que el valor del ´ındice (i) es adecuado, de tal forma que si el ´ındice (i) se encuentra fuera de rango, entonces lanza la excepci´on out_of_range (abortar´a la ejecuci´on del programa): • char c = nombre.at(i); donde i ∈ [0..nombre.size()-1]. • nombre.at(i) = ’z’; donde i ∈ [0..nombre.size()-1]. Obtener una nueva subcadena (de tipo string) a partir del ´ındice i, con un tama˜no especifi- cado por sz. Si no se especifica el tama˜no, o (sz > nombre.size()-i), entonces se toma la subcadena desde el ´ındice hasta el final. Si el ´ındice (i) se encuentra fuera de rango, entonces lanza la excepci´on out_of_range (abortar´a la ejecuci´on del programa): • string sb = nombre.substr(i); donde i ∈ [0..nombre.size()] • string sb = nombre.substr(i, sz); donde i ∈ [0..nombre.size()] • N´otese que no es v´alida la asignaci´on a una subcadena: nombre.substr(i, sz) = "..."; En GNU G++, la opci´on de compilaci´on -D_GLIBCXX_DEBUG permite comprobar los ´ındices de acceso. 1Comparaci´on lexicogr´afica se basa en la ordenaci´on alfab´etica, y es com´unmente utilizada en los diccionarios. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 52. 52 CAP´ITULO 6. TIPOS COMPUESTOS Ejemplos Ejemplo 1 Programa que convierte una cadena de caracteres a may´usculas: #include <iostream> #include <string> using namespace std ; // -- Subalgoritmos ---- void mayuscula (char& letra) { if ((letra >= ’a’) && (letra <= ’z’)) { letra = char(letra - ’a’ + ’A’) ; } } void mayusculas (string& palabra) { for (int i = 0 ; i < int(palabra.size()) ; ++i) { mayuscula(palabra[i]) ; } } // -- Principal -------- int main () { string palabra ; cin >> palabra ; mayusculas(palabra) ; cout << palabra << endl ; } Ejemplo 2 Programa que lee una palabra (formada por letras min´usculas), y escribe su plural seg´un las siguientes reglas: Si acaba en vocal se le a˜nade la letra ’s’. Si acaba en consonante se le a˜naden las letras ’es’. Si la consonante es la letra ’z’, se sustituye por la letra ’c’ Suponemos que la palabra introducida es correcta y est´a formada por letras min´usculas. #include <iostream> #include <string> using namespace std ; // -- Subalgoritmos ---- bool es_vocal (char c) { return (c == ’a’) || (c == ’e’) || (c == ’i’) || (c == ’o’) || (c == ’u’) ; } void plural_1 (string& palabra) { if (palabra.size() > 0) { if (es_vocal(palabra[palabra.size() - 1])) { palabra += ’s’ ; } else { if (palabra[palabra.size() - 1] == ’z’) { palabra[palabra.size() - 1] = ’c’ ; } palabra += "es" ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 53. 6.2. CADENAS DE CARACTERES EN C++: EL TIPO STRING 53 } } void plural_2 (string& palabra) { if (palabra.size() > 0) { if (es_vocal(palabra[palabra.size() - 1])) { palabra += ’s’ ; } else if (palabra[palabra.size() - 1] == ’z’) { palabra = palabra.substr(0, palabra.size() - 1) + "ces" ; } else { palabra += "es" ; } } } // -- Principal -------- int main () { string palabra ; cin >> palabra ; plural_1(palabra) ; cout << palabra << endl ; } Ejemplo 3 Dise˜ne una funci´on que devuelva verdadero si la palabra recibida como par´ametro es “pal´ındro- mo” y falso en caso contrario. bool es_palindromo (const string& palabra) { bool ok = false ; if (palabra.size() > 0) { int i = 0 ; int j = int(palabra.size()) - 1 ; while ((i < j) && (palabra[i] == palabra[j])) { ++i ; --j ; } ok = i >= j ; } return ok ; } Ejemplo 4 Dise˜ne un subprograma que reemplace una parte de la cadena, especificada por un ´ındice y una longitud, por otra cadena. void reemplazar (string& str, unsigned i, unsigned sz, const string& nueva) { if (i + sz < str.size()) { str = str.substr(0, i) + nueva + str.substr(i + sz, str.size() - (i + sz)) ; } else if (i <= str.size()) { str = str.substr(0, i) + nueva ; } } Este subprograma es equivalente a la operaci´on str.replace(i, sz, nueva) str.replace(i, 0, nueva) es equivalente a str.insert(i, nueva) str.replace(i, sz, "") es equivalente a str.erase(i, sz). Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 54. 54 CAP´ITULO 6. TIPOS COMPUESTOS 6.3. Registros o Estructuras El tipo registro se utiliza para la definici´on de un nuevo tipo mediante la composici´on de un n´umero determinado de elementos que pueden ser de distintos tipos (simples y compuestos). Un tipo registro se especifica enumerando los elementos (campos) que lo componen, indicando su tipo y su identificador con el que referenciarlo. Una vez definido el tipo, podremos utilizar la entidad (constante o variable) de dicho tipo como un todo o acceder a los diferentes elementos que lo componen. Por ejemplo, podemos definir un nuevo tipo que represente el concepto de Fecha como composici´on de d´ıa, mes y a˜no. struct Fecha { unsigned dia ; unsigned mes ; unsigned anyo ; } ; y posteriormente utilizarlo para definir constantes: const Fecha f_nac = { 20 , 2, 2001} ; o utilizarlo para definir variables: Fecha f_nac ; Los valores del tipo Fecha se componen de tres elementos concretos (el d´ıa de tipo unsigned, el mes de tipo unsigned y el a˜no de tipo unsigned). Los identificadores dia, mes y anyo representan los nombres de sus elementos componentes, denominados campos, y su ´ambito de visibilidad se restringe a la propia definici´on del registro. Los campos de un registro pueden ser de cualquier tipo de datos, simple o estructurado. Por ejemplo: // -- Tipos ------------ struct Empleado { string nombre ; unsigned codigo ; unsigned sueldo ; Fecha fecha_ingreso ; } ; // -- Principal -------- int main () { Empleado e ; // ... } Una vez declarada una entidad (constante o variable) de tipo registro, por ejemplo la variable f nac, podemos referirnos a ella en su globalidad (realizando asignaciones y pasos de par´ametros) o acceder a sus componentes (campos) especific´andolos tras el operador punto (.), donde un determinado componente podr´a utilizarse en cualquier lugar en que resulten v´alidas las variables de su mismo tipo. f nac 18 10 2001 hoy 18 10 2001 int main () { Fecha f_nac, hoy ; f_nac.dia = 18 ; f_nac.mes = 10 ; f_nac.anyo = 2001 ; hoy = f_nac ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 55. 6.3. REGISTROS O ESTRUCTURAS 55 Ejemplo #include <iostream> #include <string> using namespace std ; // -- Constantes ------- const unsigned SEGMIN = 60 ; const unsigned MINHOR = 60 ; const unsigned MAXHOR = 24 ; const unsigned SEGHOR = SEGMIN * MINHOR ; // -- Tipos ------------ struct Tiempo { unsigned horas ; unsigned minutos ; unsigned segundos ; } ; // -- Subalgoritmos ---- unsigned leer_rango (unsigned inf, unsigned sup) { unsigned num ; do { cin >> num ; } while ( ! ((num >= inf) && (num < sup))) ; return num ; } void leer_tiempo (Tiempo& t) { t.horas = leer_rango(0, MAXHOR) ; t.minutos = leer_rango(0, MINHOR) ; t.segundos = leer_rango(0, SEGMIN) ; } void escribir_tiempo (const Tiempo& t) { cout << t.horas << ":" << t.minutos << ":" << t.segundos ; } unsigned tiempo_a_seg (const Tiempo& t) { return (t.horas * SEGHOR) + (t.minutos * SEGMIN) + (t.segundos) ; } void seg_a_tiempo (unsigned sg, Tiempo& t) { t.horas = sg / SEGHOR ; t.minutos = (sg % SEGHOR) / SEGMIN ; t.segundos = (sg % SEGHOR) % SEGMIN ; } void diferencia (const Tiempo& t1, const Tiempo& t2, Tiempo& dif) { seg_a_tiempo(tiempo_a_seg(t2) - tiempo_a_seg(t1), dif) ; } // -- Principal -------- int main () { Tiempo t1, t2, dif ; leer_tiempo(t1) ; leer_tiempo(t2) ; diferencia(t1, t2, dif) ; escribir_tiempo(dif) ; cout << endl ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 56. 56 CAP´ITULO 6. TIPOS COMPUESTOS 6.4. Agregados: el Tipo Array El tipo array se utiliza para la definici´on de un nuevo tipo mediante la agregaci´on de entidades menores del mismo tipo, es decir, se define como una colecci´on de un n´umero determinado (definido en tiempo de compilaci´on) de elementos de un mismo tipo de datos, de tal forma que se puede acceder a cada elemento individual de la colecci´on de forma parametrizada mediante ´ındices. Los arrays son ´utiles en todas aquellas circunstancias en que necesitamos tener almacenados una colecci´on de valores (un n´umero fijo predeterminado en tiempo de compilaci´on) a los cuales pretendemos acceder de forma parametrizada, normalmente para aplicar un proceso iterativo. Es posible utilizar el tipo array de la biblioteca est´andar para definir agregados. Para ello, se debe incluir la biblioteca <tr1/array>, as´ı como utilizar el espacio de nombres de std::tr1.2 La definici´on de agregados de este tipo permite definir agregados mas robustos y con mejores caracter´ısticas que los agregados predefinidos. Un tipo agregado se especifica declarando el tipo base de los elementos que lo componen y el n´umero de elementos (constante especificada en tiempo de compilaci´on) de que consta dicha agre- gaci´on. As´ı, por ejemplo, podemos definir un nuevo tipo Vector como un agregado de 5 elementos, cada uno del tipo int, y definir variables y constantes de dicho tipo (n´otese que los elementos constantes del tipo array se especifican entre llaves dobles3 ): PRIMOS: 2 3 5 7 11 0 1 2 3 4 v: ? ? ? ? ? 0 1 2 3 4 #include <tr1/array> using namespace std::tr1 ; // -- Constantes ------- const int NELMS = 5 ; // -- Tipos ------------ typedef array<int, NELMS> Vector ; // -- Constantes ------- const Vector PRIMOS = {{ 2, 3, 5, 7, 11 }} ; // -- Principal -------- int main () { Vector v ; } El tipo base (de los elementos) del array puede ser de tipo simple o compuesto, as´ı, por ejemplo, podemos definir un nuevo tipo Citas como un agregado de 4 elementos, cada uno del tipo Fecha, y definir variables y constantes de dicho tipo: CUMPLEANYOS: 1 1 2001 2 2 2002 3 3 2003 4 4 2004 0 1 2 3 #include <tr1/array> using namespace std::tr1 ; struct Fecha { unsigned dia ; unsigned mes ; unsigned anyo ; } ; const int N_CITAS = 4 ; typedef array<Fecha, N_CITAS> Citas ; const Citas CUMPLEANYOS = {{ { 1, 1, 2001 }, { 2, 2, 2002 }, { 3, 3, 2003 }, { 4, 4, 2004 } }} ; int main() { Citas cit ; // ... cit = CUMPLEANYOS ; } 2Cuando los compiladores se adapten al nuevo est´andar de C++ (2011), entonces se deber´a incluir la biblioteca <array>, utilizar el espacio de nombres std y ser´a suficiente especificar los elementos entre llaves simples. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 57. 6.4. AGREGADOS: EL TIPO ARRAY 57 Un agregado de tipo array<...> acepta la asignaci´on (=) entre variables de dicho tipo. Tambi´en se le pueden aplicar los operadores relacionales (==, !=, >, >=, <, <=) a entidades del mismo tipo array<...> siempre y cuando a los elementos del agregado (del tipo base del array) se le puedan aplicar dichos operadores. Para conocer el n´umero de elementos que componen un determinado agregado, la operaci´on cit.size() proporciona dicho valor, que en este ejemplo es 4. Para acceder a un elemento concreto del agregado, especificaremos entre corchetes ([ y ]) el ´ındice de la posici´on que ocupa el mismo, teniendo en cuenta que el primer elemento ocupa la posici´on 0 (cero) y el ´ultimo elemento ocupa la posici´on a.size()-1. Por ejemplo cit[0] y cit[cit.size()-1] aluden al primer y ´ultimo elemento del agregado respectivamente. Un deter- minado elemento puede utilizarse en cualquier lugar donde sea v´alido una variable de su mismo tipo base. El lenguaje de programaci´on C++ no comprueba que los accesos a los elementos de un agregado sean correctos y se encuentren dentro de los l´ımites v´alidos del array, por lo que ser´a responsabilidad del programador comprobar que as´ı sea. Sin embargo, en GNU G++, la opci´on de compilaci´on -D_GLIBCXX_DEBUG permite comprobar los ´ındices de acceso (descargar la biblioteca de p´agina web de la asignatura). Tambi´en es posible acceder a un determinado elemento mediante la operaci´on at(i), de tal forma que si el valor del ´ındice i est´a fuera del rango v´alido, entonces se lanzar´a una excepci´on out_of_range. Se puede tanto utilizar como modificar el valor de este elemento. #include <tr1/array> using namespace std::tr1 ; struct Fecha { unsigned dia ; unsigned mes ; unsigned anyo ; } ; const int N_CITAS = 4 ; typedef array<Fecha, N_CITAS> Citas ; int main() { Citas cit ; cit[0].dia = 18 ; cit[0].mes = 10 ; cit[0].anyo = 2001 ; for (int i = 0 ; i < int(cit.size()) ; ++i) { cit[i].dia = 1 ; cit[i].mes = 1 ; cit[i].anyo = 2002 ; } cit[N_CITAS] = { 1, 1, 2002 } ; // ERROR. Acceso fuera de los l´ımites cit.at(N_CITAS) = { 1, 1, 2002 } ; // ERROR. Lanza excepci´on out_of_range // ... } Ejemplo 1 #include <iostream> #include <tr1/array> using namespace std ; using namespace std::tr1 ; // -- Constantes ------- const int NELMS = 5 ; // -- Tipos ------------ typedef array<int, NELMS> Vector ; // -- Subalgoritmos ---- void leer (Vector& v) Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 58. 58 CAP´ITULO 6. TIPOS COMPUESTOS { for (int i = 0 ; i < int(v.size()) ; ++i) { cin >> v[i] ; } } int sumar (const Vector& v) { int suma = 0 ; for (int i = 0 ; i < int(v.size()) ; ++i) { suma += v[i] ; } return suma ; } // -- Principal -------- int main () { Vector v1, v2 ; leer(v1) ; leer(v2) ; if (sumar(v1) == sumar(v2)) { cout << "Misma suma" << endl ; } if (v1 < v2) { cout << "Vector Menor" << endl ; } v1 = v2 ; // Asignaci´on if (v1 == v2) { cout << "Vectores Iguales" << endl ; } } Ejemplo 2 Programa que lee las ventas de cada “agente” e imprime su sueldo que se calcula como una cantidad fija (1000 C) m´as un incentivo que ser´a un 10 % de las ventas que ha realizado. Dicho incentivo s´olo ser´a aplicable a aquellos agentes cuyas ventas superen los 2/3 de la media de ventas del total de los agentes. #include <iostream> #include <tr1/array> using namespace std ; using namespace std::tr1 ; // -- Constantes ------- const int NAGENTES = 20 ; const double SUELDO_FIJO = 1000.0 ; const double INCENTIVO = 10.0 ; const double PROMEDIO = 2.0 / 3.0 ; // -- Tipos ------------ typedef array<double, NAGENTES> Ventas ; // -- Subalgoritmos ---- double calc_media (const Ventas& v) { double suma = 0.0 ; for (int i = 0 ; i < int(v.size()) ; ++i) { suma += v[i] ; } return suma / double(v.size()) ; } inline double porcentaje (double p, double valor) Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 59. 6.4. AGREGADOS: EL TIPO ARRAY 59 { return (p * valor) / 100.0 ; } void leer_ventas (Ventas& v) { for (int i = 0 ; i < int(v.size()) ; ++i) { cout << "Introduzca ventas del Agente " << i << ": " ; cin >> v[i] ; } } void imprimir_sueldos (const Ventas& v) { double umbral = PROMEDIO * calc_media(ventas) ; for (int i = 0 ; i < int(v.size()) ; ++i) { double sueldo = SUELDO_FIJO ; if (v[i] >= umbral) { sueldo += porcentaje(INCENTIVO, v[i]) ; } cout << "Agente: " << i << " Sueldo: " << sueldo << endl ; } } // -- Principal -------- int main () { Ventas ventas ; leer_ventas(ventas) ; imprimir_sueldos(ventas) ; } Agregados Incompletos Hay situaciones donde un array se define en tiempo de compilaci´on con un tama˜no mayor que el n´umero de elementos actuales v´alidos que contendr´a durante el Tiempo de Ejecuci´on. Gestionar el array con huecos durante la ejecuci´on del programa suele ser, en la mayor´ıa de los casos, complejo e ineficiente. Mantener los elementos actuales v´alidos consecutivos al comienzo del array suele ser m´as adecuado: • Marcar la separaci´on entre los elementos actuales v´alidos de los elementos vac´ıos con alg´un valor de adecuado suele ser, en la mayor´ıa de los casos, complejo e ineficiente. • Definir un registro que contenga tanto el array, como el n´umero de elementos actuales v´alidos consecutivos que contiene suele ser m´as adecuado. Ejemplo #include <iostream> #include <tr1/array> using namespace std ; using namespace std::tr1 ; // -- Constantes ------- const int MAX_AGENTES = 20 ; const double SUELDO_FIJO = 1000.0 ; const double INCENTIVO = 10.0 ; const double PROMEDIO = 2.0 / 3.0 ; // -- Tipos ------------ typedef array<double, MAX_AGENTES> Datos ; struct Ventas { int nelms ; Datos elm ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 60. 60 CAP´ITULO 6. TIPOS COMPUESTOS } ; // -- Subalgoritmos ---- double calc_media (const Ventas& v) { double suma = 0.0 ; for (int i = 0 ; i < v.nelms ; ++i) { suma += v.elm[i] ; } return suma / double(v.nelms) ; } inline double porcentaje (double p, double valor) { return (p * valor) / 100.0 ; } void leer_ventas_ag (int i, double& v) { cout << "Introduzca ventas Agente " << i << ": " ; cin >> v ; } // ----------------------------------- // Dos m´etodos diferentes de leer un // vector incompleto: // ----------------------------------- // M´etodo-1: cuando se conoce a priori el n´umero // de elementos que lo componen // ----------------------------------- void leer_ventas_2 (Ventas& v) { int nag ; cout << "Introduzca total de agentes: " ; cin >> nag ; if (nag > int(v.elm.size())) { v.nelms = 0 ; cout << "Error" << endl ; } else { v.nelms = nag ; for (int i = 0 ; i < v.nelms ; ++i) { leer_ventas_ag(i, v.elm[i]) ; } } } // ----------------------------------- // M´etodo-2: cuando NO se conoce a priori el n´umero // de elementos que lo componen, y este // n´umero depende de la propia lectura de // los datos // ----------------------------------- void leer_ventas_1 (Ventas& v) { double vent_ag ; v.nelms = 0 ; leer_ventas_ag(v.nelms+1, vent_ag) ; while ((v.nelms < int(v.elm.size()))&&(vent_ag > 0)) { v.elm[v.nelms] = vent_ag ; ++v.nelms ; leer_ventas_ag(v.nelms+1, vent_ag) ; } } // ----------------------------------- Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 61. 6.4. AGREGADOS: EL TIPO ARRAY 61 void imprimir_sueldos (const Ventas& v) { double umbral = PROMEDIO * calc_media(ventas) ; for (int i = 0 ; i < v.nelms ; ++i) { double sueldo = SUELDO_FIJO ; if (v.elm[i] >= umbral) { sueldo += porcentaje(INCENTIVO, v.elm[i]) ; } cout << "Agente: " << i << " Sueldo: " << sueldo << endl ; } } // -- Principal -------- int main () { Ventas ventas ; leer_ventas(ventas) ; imprimir_sueldos(ventas) ; } Agregados Multidimensionales El tipo Base de un array puede ser tanto simple como compuesto, por lo tanto puede ser otro array, dando lugar a arrays con m´ultiples dimensiones. As´ı, cada elemento de un array puede ser a su vez otro array. Los agregados anteriormente vistos se denominan de una dimensi´on. As´ı mismo, es posible declarar agregados de varias dimensiones. Un ejemplo de un agregado de dos dimensiones: m: 0 00 01 02 03 04 1 10 11 12 13 14 2 20 21 22 23 24 0 1 2 3 4 #include <iostream> #include <tr1/array> using namespace std ; using namespace std::tr1 ; // -- Constantes ------- const int NFILAS = 3 ; const int NCOLUMNAS = 5 ; // -- Tipos ------------ typedef array<int, NCOLUMNAS> Fila ; typedef array<Fila, NFILAS> Matriz ; // -- Principal -------- int main () { Matriz m ; for (int f = 0 ; f < int(m.size()) ; ++f) { for (int c = 0 ; c < int(m[f].size()) ; ++c) { m[f][c] = (f * 10) + c ; } } Matriz mx = m ; // asigna a mx los valores de la Matriz m Fila fil = m[0] ; // asigna a fil el array con valores {{ 00, 01, 02, 03, 04 }} int n = m[2][4] ; // asigna a n el valor 24 } Donde m hace referencia a una variable de tipo Matriz, m[f] hace referencia a la fila f de la matriz m (que es de tipo Fila), y m[f][c] hace referencia al elemento c de la fila f de la matriz m (que es de tipo int). Del mismo modo, el n´umero de filas de la matriz m es igual a m.size(), y el n´umero de elementos de la fila f de la matriz m es igual a m[f].size(). Ejemplo 1 Dise˜nar un programa que lea una matriz de 3×5 de n´umeros enteros (fila a fila), almacen´andolos en un array bidimensional, finalmente imprima la matriz seg´un el siguiente formato: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 62. 62 CAP´ITULO 6. TIPOS COMPUESTOS a a a a a b a a a a a b a a a a a b c c c c c donde a representa los elementos de la matriz le´ıda desde el teclado, b representa el resultado de sumar todos los elementos de la fila correspondiente, y c representa el resultado de sumar todos los elementos de la columna donde se encuentran. N´otese en el ejemplo como es posible pasar como par´ametro una ´unica fila, y sin embargo no es posible pasar como par´ametro una ´unica columna. #include <iostream> #include <tr1/array> using namespace std ; using namespace std::tr1 ; // -- Constantes ------- const int NFILAS = 3 ; const int NCOLUMNAS = 5 ; // -- Tipos ------------ typedef array<int, NCOLUMNAS> Fila ; typedef array<Fila, NFILAS> Matriz ; // -- Subalgoritmos ---- int sumar_fila (const Fila& fil) { int suma = 0 ; for (int c = 0 ; c < int(fil.size()) ; ++c) { suma += fil[c] ; } return suma ; } int sumar_columna (const Matriz& m, int c) { int suma = 0 ; for (int f = 0 ; f < int(m.size()) ; ++f) { suma += m[f][c] ; } return suma ; } void escribir_fila (const Fila& fil) { for (int c = 0 ; c < int(fil.size()) ; ++c) { cout << fil[c] << " " ; } } void escribir_matriz_formato (const Matriz& m) { for (int f = 0 ; f < int(m.size()) ; ++f) { escribir_fila(m[f]) ; cout << sumar_fila(m[f]) ; cout << endl ; } for (int c = 0 ; c < int(m[0].size()) ; ++c) { cout << sumar_columna(m, c) << " " ; } cout << endl ; } void leer_matriz (Matriz& m) { cout << "Escribe fila a fila" << endl ; for (int f = 0 ; f < int(m.size()) ; ++f) { for (int c = 0 ; c < int(m[f].size()) ; ++c) { Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 63. 6.4. AGREGADOS: EL TIPO ARRAY 63 cin >> m[f][c] ; } } } // -- Principal -------- int main () { Matriz m ; leer_matriz(m) ; escribir_matriz_formato(m) ; } Ejemplo 2 Dise˜ne un programa que realice el producto de 2 matrices de m´aximo 10 × 10 elementos: #include <iostream> #include <cassert> #include <tr1/array> using namespace std ; using namespace std::tr1 ; // -- Constantes ------- const int MAX = 10 ; // -- Tipos ------------ typedef array<double, MAX> Fila ; typedef array<Fila, MAX> Tabla ; struct Matriz { int n_fil ; int n_col ; Tabla datos ; } ; // -- Subalgoritmos ---- void leer_matriz (Matriz& m) { cout << "Dimensiones?: " ; cin >> m.n_fil >> m.n_col ; assert(m.n_fil <= int(m.datos.size()) && m.n_col <= int(m.datos[0].size())) ; cout << "Escribe valores fila a fila:" << endl ; for (int f = 0 ; f < m.n_fil ; ++f) { for (int c = 0 ; c < m.n_col ; ++c) { cin >> m.datos[f][c] ; } } } void escribir_matriz (const Matriz& m) { for (int f = 0 ; f < m.n_fil ; ++f) { for (int c = 0 ; c < m.n_col ; ++c) { cout << m.datos[f][c] << " " ; } cout << endl ; } } double suma_fila_por_col (const Matriz& x, const Matriz& y, int f, int c) { assert(x.n_col == y.n_fil) ; // PRE-COND double suma = 0.0 ; for (int k = 0 ; k < x.n_col ; ++k) { suma += x.datos[f][k] * y.datos[k][c] ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 64. 64 CAP´ITULO 6. TIPOS COMPUESTOS return suma ; } void mult_matriz (Matriz& m, const Matriz& a, const Matriz& b) { assert(a.n_col == b.n_fil) ; // PRE-COND m.n_fil = a.n_fil ; m.n_col = b.n_col ; for (int f = 0 ; f < m.n_fil ; ++f) { for (int c = 0 ; c < m.n_col ; ++c) { m.datos[f][c] = suma_fil_por_col(a, b, f, c) ; } } } // -- Principal -------- int main () { Matriz a,b,c ; leer_matriz(a) ; leer_matriz(b) ; if (a.n_col != b.n_fil) { cout << "No se puede multiplicar." << endl ; } else { mult_matriz(c, a, b) ; escribir_matriz(c) ; } } 6.5. Resoluci´on de Problemas Utilizando Tipos Compuestos Dise˜ne un programa para gestionar una agenda personal que contenga la siguiente informa- ci´on: Nombre, Tel´efono, Direcci´on, Calle, N´umero, Piso, C´odigo Postal y Ciudad, y las siguientes operaciones: A˜nadir los datos de una persona. Acceder a los datos de una persona a partir de su nombre. Borrar una persona a partir de su nombre. Modificar los datos de una persona a partir de su nombre. Listar el contenido completo de la agenda. #include <iostream> #include <string> #include <cassert> #include <tr1/array> using namespace std ; using namespace std::tr1 ; // -- Constantes ------- const int MAX_PERSONAS = 50 ; // -- Tipos ------------ struct Direccion { unsigned num ; string calle ; string piso ; string cp ; string ciudad ; } ; struct Persona { string nombre ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 65. 6.5. RESOLUCI ´ON DE PROBLEMAS UTILIZANDO TIPOS COMPUESTOS 65 string tel ; Direccion direccion ; } ; // -- Tipos ------------ typedef array<Persona, MAX_PERSONAS> Personas ; struct Agenda { int n_pers ; Personas pers ; } ; enum Cod_Error { OK, AG_LLENA, NO_ENCONTRADO, YA_EXISTE } ; // -- Subalgoritmos ---- void Inicializar (Agenda& ag) { ag.n_pers = 0 ; } //--------------------------- void Leer_Direccion (Direccion& dir) { cin >> dir.calle ; cin >> dir.num ; cin >> dir.piso ; cin >> dir.cp ; cin >> dir.ciudad ; } //--------------------------- void Escribir_Direccion (const Direccion& dir) { cout << dir.calle << " " ; cout << dir.num << " " ; cout << dir.piso << " " ; cout << dir.cp << " " ; cout << dir.ciudad << " " ; } //--------------------------- void Leer_Persona (Persona& per) { cin >> per.nombre ; cin >> per.tel ; Leer_Direccion(per.direccion) ; } //--------------------------- void Escribir_Persona (const Persona& per) { cout << per.nombre << " " ; cout << per.tel << " " ; Escribir_Direccion(per.direccion) ; cout << endl ; } //--------------------------- // Busca una Persona en la Agenda // Devuelve su posici´on si se encuentra, o bien >= ag.n_pers en otro caso int Buscar_Persona (const string& nombre, const Agenda& ag) { int i = 0 ; while ((i < ag.n_pers) && (nombre != ag.pers[i].nombre)) { ++i ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 66. 66 CAP´ITULO 6. TIPOS COMPUESTOS return i ; } //--------------------------- void Anyadir (Agenda& ag, const Persona& per) { assert(ag.n_pers < int(ag.pers.size())) ; ag.pers[ag.n_pers] = per ; ++ag.n_pers ; } //--------------------------- void Eliminar (Agenda& ag, int pos) { assert(pos < ag.n_pers) ; if (pos < ag.npers-1) { ag.pers[pos] = ag.pers[ag.n_pers - 1] ; } --ag.n_pers ; } //--------------------------- void Anyadir_Persona (const Persona& per, Agenda& ag, Cod_Error& ok) { int i = Buscar_Persona(per.nombre, ag) ; if (i < ag.n_pers) { ok = YA_EXISTE ; } else if (ag.n_pers >= int(ag.pers.size())) { ok = AG_LLENA ; } else { ok = OK ; Anyadir(ag, per) ; } } //--------------------------- void Borrar_Persona (const string& nombre, Agenda& ag, Cod_Error& ok) { int i = Buscar_Persona(nombre, ag) ; if (i >= ag.n_pers) { ok = NO_ENCONTRADO ; } else { ok = OK ; Eliminar(ag, i) ; } } //--------------------------- void Modificar_Persona (const string& nombre, const Persona& nuevo, Agenda& ag, Cod_Error& ok) { int i = Buscar_Persona(nombre, ag) ; if (i >= ag.n_pers) { ok = NO_ENCONTRADO ; } else { Eliminar(ag, i) ; Anyadir_Persona(nuevo, ag, ok) ; } } //--------------------------- void Imprimir_Persona (const string& nombre, const Agenda& ag, Cod_Error& ok) { int i = Buscar_Persona(nombre, ag) ; if (i >= ag.n_pers) { ok = NO_ENCONTRADO ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 67. 6.5. RESOLUCI ´ON DE PROBLEMAS UTILIZANDO TIPOS COMPUESTOS 67 } else { ok = OK ; Escribir_Persona(ag.pers[i]) ; } } //--------------------------- void Imprimir_Agenda (const Agenda& ag, Cod_Error& ok) { for (int i = 0 ; i < ag.n_pers ; ++i) { Escribir_Persona(ag.pers[i]) ; } ok = OK ; } //--------------------------- char Menu () { char opcion ; cout << endl ; cout << "a. - A~nadir Persona" << endl ; cout << "b. - Buscar Persona" << endl ; cout << "c. - Borrar Persona" << endl ; cout << "d. - Modificar Persona" << endl ; cout << "e. - Imprimir Agenda" << endl ; cout << "x. - Salir" << endl ; do { cout << "Introduzca Opci´on: " ; cin >> opcion ; } while ( ! (((opcion >= ’a’) && (opcion <= ’e’)) || (opcion == ’x’))) ; return opcion ; } //--------------------------- void Escribir_Cod_Error (Cod_Error cod) { switch (cod) { case OK: cout << "Operaci´on correcta" << endl ; break ; case AG_LLENA: cout << "Agenda llena" << endl ; break ; case NO_ENCONTRADO: cout << "La persona no se encuentra en la agenda" << endl ; break ; case YA_EXISTE: cout << "La persona ya se encuentra en la agenda" << endl ; break ; } } // -- Principal -------- int main () { Agenda ag ; char opcion ; Persona per ; string nombre ; Cod_Error ok ; Inicializar(ag) ; do { opcion = Menu() ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 68. 68 CAP´ITULO 6. TIPOS COMPUESTOS switch (opcion) { case ’a’: cout << "Introduzca los datos de la Persona" << endl ; cout << "(nombre, tel, calle, num, piso, cod_postal, ciudad)" << endl ; Leer_Persona(per) ; Anyadir_Persona(per, ag, ok) ; Escribir_Cod_Error(ok) ; break ; case ’b’: cout << "Introduzca Nombre" << endl ; cin >> nombre ; Imprimir_Persona(nombre, ag, ok) ; Escribir_Cod_Error(ok) ; break ; case ’c’: cout << "Introduzca Nombre" << endl ; cin >> nombre ; Borrar_Persona(nombre, ag, ok) ; Escribir_Cod_Error(ok) ; break ; case ’d’: cout << "Introduzca Nombre" << endl ; cin >> nombre ; cout << "Nuevos datos de la Persona" << endl ; cout << "(nombre, tel, calle, num, piso, cod_postal, ciudad)" << endl ; Leer_Persona(per) ; Modificar_Persona(nombre, per, ag, ok) ; Escribir_Cod_Error(ok) ; break ; case ’e’: Imprimir_Agenda(ag, ok) ; Escribir_Cod_Error(ok) ; break ; } } while (opcion != ’x’ ) ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 69. Cap´ıtulo 7 B´usqueda y Ordenaci´on Los algoritmos de b´usqueda de un elemento en una colecci´on de datos, as´ı como los algoritmos de ordenaci´on, son muy utilizados com´unmente, por lo que merecen un estudio expl´ıcito. En el caso de los algoritmos de b´usqueda, ´estos normalmente retornan la posici´on, dentro de la colecci´on de datos, del elemento buscado, y en caso de no ser encontrado, retornan una posici´on no v´alida. Por otra parte, los algoritmos de ordenaci´on organizan una colecci´on de datos de acuerdo con alg´un criterio de ordenaci´on. Los algoritmos de ordenaci´on que se ver´an en este cap´ıtulo son los m´as f´aciles de programar, pero sin embargo son los m´as ineficientes. 7.1. B´usqueda Lineal (Secuencial) La b´usqueda lineal es adecuada como mecanismo de b´usqueda general en colecciones de datos sin organizaci´on conocida. Consiste en ir recorriendo secuencialmente la colecci´on de datos hasta encontrar el elemento buscado, o en ´ultima instancia recorrer toda la colecci´on completa, en cuyo caso el elemento buscado no habr´a sido encontrado. En los siguientes ejemplos se presentan los algoritmos b´asicos, los cuales pueden ser adaptados seg´un las circunstancias. //-------------------------------- typedef array<int, MAXIMO> Vector ; //-------------------------------- // busca la posici´on del primer elemento == x // si no encontrado, retorna v.size() //------------- int buscar(int x, const Vector& v) { int i = 0 ; while ((i < int(v.size()))&&(x != v[i])) { ++i ; } return i ; } //-------------------------------- 7.2. B´usqueda Binaria La b´usqueda binaria es adecuada como mecanismo de b´usqueda cuando las colecciones de datos se encuentran ordenadas por alg´un criterio. Consiste en comprobar si el elemento buscado es igual, menor o mayor que el elemento que ocupa la posici´on central de la colecci´on de datos, en caso de ser mayor o menor que dicho elemento, se descartan los elementos no adecuados de la colecci´on de datos, y se repite el proceso hasta encontrar el elemento o hasta que no queden elementos adecuados 69
  • 70. 70 CAP´ITULO 7. B ´USQUEDA Y ORDENACI ´ON en la colecci´on, en cuyo caso el elemento no habr´a sido encontrado. En los siguientes ejemplos se presentan los algoritmos b´asicos, los cuales pueden ser adaptados seg´un las circunstancias. //-------------------------------- typedef array<int, MAXIMO> Vector ; //-------------------------------- // busca la posici´on del primer elemento == x // si no encontrado, retorna v.size() //------------- int buscar_bin(int x, const Vector& v) { int i = 0 ; int f = int(v.size()) ; int res = int(v.size()) ; while (i < f) { int m = (i + f) / 2 ; if (x == v[m]) { res = i = f = m ; } else if (x < v[m]) { f = m ; } else { i = m + 1 ; } } return res ; } //-------------------------------- 7.3. Ordenaci´on por Intercambio (Burbuja) Se hacen m´ultiples recorridos sobre la zona no ordenada del array, ordenando los elementos consecutivos, trasladando en cada uno de ellos al elemento m´as peque˜no hasta el inicio de dicha zona. //-------------------------------- typedef array<int, MAXIMO> Vector ; //-------------------------------- inline void intercambio(int& x, int& y) { int a = x ; x = y ; y = a ; } //-------------------------------- void subir_menor(Vector& v, int pos) { for (int i = int(v.size())-1 ; i > pos ; --i) { if (v[i] < v[i-1]) { intercambio(v[i], v[i-1]) ; } } } //-------------------------------- void burbuja(Vector& v) { for (int pos = 0 ; pos < int(v.size())-1 ; ++pos) { subir_menor(v, pos) ; } } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 71. 7.4. ORDENACI ´ON POR SELECCI ´ON 71 //-------------------------------- 7.4. Ordenaci´on por Selecci´on Se busca el elemento m´as peque˜no de la zona no ordenada del array, y se traslada al inicio dicha zona, repitiendo el proceso hasta ordenar completamente el array. //-------------------------------- typedef array<int, MAXIMO> Vector ; //-------------------------------- inline void intercambio(int& x, int& y) { int a = x ; x = y ; y = a ; } //-------------------------------- int posicion_menor(const Vector& v, int pos) { int pos_menor = pos ; for (int i = pos_menor+1 ; i < int(v.size()) ; ++i) { if (v[i] < v[pos_menor]) { pos_menor = i ; } } return pos_menor ; } //-------------------------------- inline void subir_menor(Vector& v, int pos) { int pos_menor = posicion_menor(v, pos) ; if (pos != pos_menor) { intercambio(v[pos], v[pos_menor]) ; } } //-------------------------------- void seleccion(Vector& v) { for (int pos = 0 ; pos < int(v.size())-1 ; ++pos) { subir_menor(v, pos) ; } } //-------------------------------- 7.5. Ordenaci´on por Inserci´on Se toma el primer elemento de la zona no ordenada del array, y se inserta en la posici´on adecuada de la zona ordenada del array, repitiendo el proceso hasta ordenar completamente el array. //-------------------------------- typedef array<int, MAXIMO> Vector ; //-------------------------------- int buscar_posicion(const Vector& v, int posicion) { int i = 0 ; while (/*(i < posicion)&&*/ (v[posicion] > v[i])) { ++i ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 72. 72 CAP´ITULO 7. B ´USQUEDA Y ORDENACI ´ON } return i ; } //-------------------------------- void abrir_hueco(Vector& v, int p_hueco, int p_elm) { for (int i = p_elm ; i > p_hueco ; --i) { v[i] = v[i-1] ; } } //-------------------------------- void insercion(Vector& v) { for (int pos = 1 ; pos < int(v.size()) ; ++pos) { int p_hueco = buscar_posicion(v, pos) ; if (p_hueco != pos) { int aux = v[pos] ; abrir_hueco(v, p_hueco, pos) ; v[p_hueco] = aux ; } } } //-------------------------------- 7.6. Aplicaci´on de los Algoritmos de B´usqueda y Ordenaci´on Dise˜ne un programa para gestionar una agenda personal ordenada que contenga la siguiente informaci´on: Nombre, Tel´efono, Direcci´on, Calle, N´umero, Piso, C´odigo Postal y Ciudad, y las siguientes operaciones: A˜nadir los datos de una persona. Acceder a los datos de una persona a partir de su nombre. Borrar una persona a partir de su nombre. Modificar los datos de una persona a partir de su nombre. Listar el contenido completo de la agenda. #include <iostream> #include <string> #include <cassert> #include <tr1/array> using namespace std ; using namespace std::tr1 ; // -- Constantes ------- const int MAX_PERSONAS = 50 ; // -- Tipos ------------ struct Direccion { unsigned num ; string calle ; string piso ; string cp ; string ciudad ; } ; struct Persona { string nombre ; string tel ; Direccion direccion ; } ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 73. 7.6. APLICACI ´ON DE LOS ALGORITMOS DE B ´USQUEDA Y ORDENACI ´ON 73 // -- Tipos ------------ typedef array<Persona, MAX_PERSONAS> Personas ; struct Agenda { int n_pers ; Personas pers ; } ; enum Cod_Error { OK, AG_LLENA, NO_ENCONTRADO, YA_EXISTE } ; // -- Subalgoritmos ---- void Inicializar (Agenda& ag) { ag.n_pers = 0 ; } //--------------------------- void Leer_Direccion (Direccion& dir) { cin >> dir.calle ; cin >> dir.num ; cin >> dir.piso ; cin >> dir.cp ; cin >> dir.ciudad ; } //--------------------------- void Escribir_Direccion (const Direccion& dir) { cout << dir.calle << " " ; cout << dir.num << " " ; cout << dir.piso << " " ; cout << dir.cp << " " ; cout << dir.ciudad << " " ; } //--------------------------- void Leer_Persona (Persona& per) { cin >> per.nombre ; cin >> per.tel ; Leer_Direccion(per.direccion) ; } //--------------------------- void Escribir_Persona (const Persona& per) { cout << per.nombre << " " ; cout << per.tel << " " ; Escribir_Direccion(per.direccion) ; cout << endl ; } //--------------------------- // Busca una Persona en la Agenda Ordenada // Devuelve su posici´on si se encuentra, o bien >= ag.n_pers en otro caso int Buscar_Persona (const string& nombre, const Agenda& ag) { int i = 0 ; int f = ag.n_pers ; int res = ag.n_pers ; while (i < f) { int m = (i + f) / 2 ; int cmp = nombre.compare(ag.pers[m].nombre) ; if (cmp == 0) { Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 74. 74 CAP´ITULO 7. B ´USQUEDA Y ORDENACI ´ON res = i = f = m ; } else if (cmp < 0) { f = m ; } else { i = m + 1 ; } } return res ; } //--------------------------- int Buscar_Posicion (const string& nombre, const Agenda& ag) { int i = 0 ; while ((i < ag.n_pers) && (nombre > ag.pers[i].nombre)) { ++i ; } return i ; } //--------------------------- void Anyadir_Ord (Agenda& ag, int pos, const Persona& per) { for (int i = ag.n_pers ; i > pos ; --i) { ag.pers[i] = ag.pers[i - 1] ; } ag.pers[pos] = per ; ++ag.n_pers ; } //--------------------------- void Eliminar_Ord (Agenda& ag, int pos) { --ag.n_pers ; for (int i = pos ; i < ag.n_pers ; ++i) { ag.pers[i] = ag.pers[i + 1] ; } } //--------------------------- void Anyadir_Persona (const Persona& per, Agenda& ag, Cod_Error& ok) { int pos = Buscar_Posicion(per.nombre, ag) ; if ((pos < ag.n_pers) && (per.nombre == ag.pers[pos].nombre)) { ok = YA_EXISTE ; } else if (ag.n_pers >= int(ag.pers.size())) { ok = AG_LLENA ; } else { ok = OK ; Anyadir_Ord(ag, pos, per) ; } } //--------------------------- void Borrar_Persona (const string& nombre, Agenda& ag, Cod_Error& ok) { int i = Buscar_Persona(nombre, ag) ; if (i >= ag.n_pers) { ok = NO_ENCONTRADO ; } else { ok = OK ; Eliminar_Ord(ag, i) ; } } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 75. 7.6. APLICACI ´ON DE LOS ALGORITMOS DE B ´USQUEDA Y ORDENACI ´ON 75 //--------------------------- void Modificar_Persona (const string& nombre, const Persona& nuevo, Agenda& ag, Cod_Error& ok) { int i = Buscar_Persona(nombre, ag) ; if (i >= ag.n_pers) { ok = NO_ENCONTRADO ; } else { ok = OK ; Eliminar_Ord(ag, i) ; Anyadir_Persona(nuevo, ag, ok) ; } } //--------------------------- void Imprimir_Persona (const string& nombre, const Agenda& ag, Cod_Error& ok) { int i = Buscar_Persona(nombre, ag) ; if (i >= ag.n_pers) { ok = NO_ENCONTRADO ; } else { ok = OK ; Escribir_Persona(ag.pers[i]) ; } } //--------------------------- void Imprimir_Agenda (const Agenda& ag, Cod_Error& ok) { for (int i = 0 ; i < ag.n_pers ; ++i) { Escribir_Persona(ag.pers[i]) ; } ok = OK ; } //--------------------------- char Menu () { char opcion ; cout << endl ; cout << "a. - A~nadir Persona" << endl ; cout << "b. - Buscar Persona" << endl ; cout << "c. - Borrar Persona" << endl ; cout << "d. - Modificar Persona" << endl ; cout << "e. - Imprimir Agenda" << endl ; cout << "x. - Salir" << endl ; do { cout << "Introduzca Opci´on: " ; cin >> opcion ; } while ( ! (((opcion >= ’a’) && (opcion <= ’e’)) || (opcion == ’x’))) ; return opcion ; } //--------------------------- void Escribir_Cod_Error (Cod_Error cod) { switch (cod) { case OK: cout << "Operaci´on correcta" << endl ; break ; case AG_LLENA: cout << "Agenda llena" << endl ; break ; case NO_ENCONTRADO: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 76. 76 CAP´ITULO 7. B ´USQUEDA Y ORDENACI ´ON cout << "La persona no se encuentra en la agenda" << endl ; break ; case YA_EXISTE: cout << "La persona ya se encuentra en la agenda" << endl ; break ; } } // -- Principal -------- int main () { Agenda ag ; char opcion ; Persona per ; string nombre ; Cod_Error ok ; Inicializar(ag) ; do { opcion = Menu() ; switch (opcion) { case ’a’: cout << "Introduzca los datos de la Persona"<<endl ; cout << "(nombre, tel, calle, num, piso, cod_postal, ciudad)" << endl ; Leer_Persona(per) ; Anyadir_Persona(per, ag, ok) ; Escribir_Cod_Error(ok) ; break ; case ’b’: cout << "Introduzca Nombre" << endl ; cin >> nombre ; Imprimir_Persona(nombre, ag, ok) ; Escribir_Cod_Error(ok) ; break ; case ’c’: cout << "Introduzca Nombre" << endl ; cin >> nombre ; Borrar_Persona(nombre, ag, ok) ; Escribir_Cod_Error(ok) ; break ; case ’d’: cout << "Introduzca Nombre" << endl ; cin >> nombre ; cout << "Nuevos datos de la Persona" << endl ; cout << "(nombre, tel, calle, num, piso, cod_postal, ciudad)" << endl ; Leer_Persona(per) ; Modificar_Persona(nombre, per, ag, ok) ; Escribir_Cod_Error(ok) ; break ; case ’e’: Imprimir_Agenda(ag, ok) ; Escribir_Cod_Error(ok) ; break ; } } while (opcion != ’x’ ) ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 77. Cap´ıtulo 8 Algunas Bibliotecas ´Utiles En este cap´ıtulo se muestra superficialmente algunas funciones b´asicas de la biblioteca est´andar. cmath La biblioteca <cmath> proporciona principalmente algunas funciones matem´aticas ´utiles: #include <cmath> using namespace std ; double sin(double r) ; seno, sin r (en radianes) double cos(double r) ; coseno, cos r (en radianes) double tan(double r) ; tangente, tan r (en radianes) double asin(double x) ; arco seno, arcsin x, x ∈ [−1, 1] double acos(double x) ; arco coseno, arc cos x, x ∈ [−1, 1] double atan(double x) ; arco tangente, arctan x double atan2(double y, double x) ; arco tangente, arctan y/x double sinh(double r) ; seno hiperb´olico, sinh r double cosh(double r) ; coseno hiperb´olico, cosh r double tanh(double r) ; tangente hiperb´olica, tanh r double sqrt(double x) ; √ x, x ≥ 0 double pow(double x, double y) ; xy double exp(double x) ; ex double log(double x) ; logaritmo neperiano, ln x, x > 0 double log10(double x) ; logaritmo decimal, log x, x > 0 double ceil(double x) ; menor entero ≥ x, x double floor(double x) ; mayor entero ≤ x, x double fabs(double x) ; valor absoluto de x, |x| double ldexp(double x, int n) ; x2n double frexp(double x, int* exp) ; inversa de ldexp double modf(double x, double* ip) ; parte entera y fraccionaria double fmod(double x, double y) ; resto de x/y cctype La biblioteca <cctype> proporciona principalmente caracter´ısticas sobre los valores de tipo char: #include <cctype> using namespace std ; 77
  • 78. 78 CAP´ITULO 8. ALGUNAS BIBLIOTECAS ´UTILES bool isalnum(char ch) ; (isalpha(ch) || isdigit(ch)) bool isalpha(char ch) ; (isupper(ch) || islower(ch)) bool iscntrl(char ch) ; caracteres de control bool isdigit(char ch) ; d´ıgito decimal bool isgraph(char ch) ; caracteres imprimibles excepto espacio bool islower(char ch) ; letra min´uscula bool isprint(char ch) ; caracteres imprimibles incluyendo espacio bool ispunct(char ch) ; carac. impr. excepto espacio, letra o d´ıgito bool isspace(char ch) ; espacio, ’r’, ’n’, ’t’, ’v’, ’f’ bool isupper(char ch) ; letra may´uscula bool isxdigit(char ch) ; d´ıgito hexadecimal char tolower(char ch) ; retorna la letra min´uscula correspondiente a ch char toupper(char ch) ; retorna la letra may´uscula correspondiente a ch ctime La biblioteca <ctime> proporciona principalmente algunas funciones generales relacionadas con el tiempo: #include <ctime> using namespace std ; clock_t clock() ; retorna el tiempo de CPU utilizado (CLOCKS_PER_SEC) time_t time(0) ; retorna el tiempo de calendario (en segundos) #include <iostream> #include <ctime> using namespace std ; // ------------------------------------- int main() { time_t t1 = time(0) ; clock_t c1 = clock() ; // ... procesamiento ... clock_t c2 = clock() ; time_t t2 = time(0) ; cout << "Tiempo de CPU: " << double(c2 - c1)/double(CLOCKS_PER_SEC) << " seg" << endl ; cout << "Tiempo total: " << (t2 - t1) << " seg" << endl ; } // ------------------------------------- cstdlib La biblioteca <cstdlib> proporciona principalmente algunas funciones generales ´utiles: #include <cstdlib> using namespace std ; int abs(int n) ; retorna el valor absoluto del n´umero int n long labs(long n) ; retorna el valor absoluto del n´umero long n int system(const char orden[]) ; orden a ejecutar por el sistema operativo void exit(int estado) ; termina la ejecuci´on del programa actual (EXIT_SUCCESS, EXIT_FAILURE) void abort() ; aborta la ejecuci´on del programa actual void srand(unsigned semilla) ; inicializa el generador de n´umeros aleatorios int rand() ; retorna un aleatorio entre 0 y RAND_MAX (ambos inclusive) #include <cstdlib> #include <ctime> using namespace std ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 79. 79 // ------------------------------------- // inicializa el generador de n´umeros aleatorios inline unsigned ini_aleatorio() { srand(time(0)) ; } // ------------------------------------- // Devuelve un n´umero aleatorio entre 0 y max (exclusive) inline unsigned aleatorio(unsigned max) { return unsigned(max*double(rand())/(RAND_MAX+1.0)) ; } // ------------------------------------- // Devuelve un n´umero aleatorio entre min y max (ambos inclusive) inline unsigned aleatorio(unsigned min, unsigned max) { return min + aleatorio(max-min+1) ; } // ------------------------------------- Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 80. 80 CAP´ITULO 8. ALGUNAS BIBLIOTECAS ´UTILES Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 83. Cap´ıtulo 9 Almacenamiento en Memoria Secundaria: Ficheros Los programas de ordenador usualmente trabajan con datos almacenados en la memoria prin- cipal (RAM). Esta memoria principal tiene como principales caracter´ısticas que tiene un tiempo de acceso (para lectura y escritura) muy eficiente, sin embargo este tipo de memoria es vol´atil, en el sentido de que los datos almacenados en ella desaparecen cuando termina la ejecuci´on del programa o se apaga el ordenador. Los ordenadores normalmente almacenan su informaci´on de manera permanente en dispositivos de almacenamiento de memoria secundaria, tales como dispos- itivos magn´eticos (discos duros, cintas), discos ´opticos (CDROM, DVD), memorias permanentes de estado s´olido (memorias flash USB), etc. Estos dispositivos suelen disponer de gran capacidad de almacenamiento, por lo que es nece- sario alguna organizaci´on que permita gestionar y acceder a la informaci´on all´ı almacenada. A esta organizaci´on se la denomina el sistema de ficheros, y suele estar organizado jer´arquicamente en directorios (a veces denominados tambi´en carpetas) y ficheros (a veces denominados tambi´en archivos), donde los directorios permiten organizar jer´arquicamente1 y acceder a los ficheros, y estos ´ultimos almacenan de forma permanente la informaci´on, que puede ser tanto programas (software) como datos que ser´an utilizados por los programas. As´ı, los programas acceden y almacenan la informaci´on de manera permanente por medio de los ficheros, que son gestionados por el Sistema Operativo dentro de la jerarqu´ıa del sistema de ficheros. raiz bin gedit g++ agenda.cpp agenda agenda.txt system.cfgsrc Tipos de Ficheros Los ficheros se pueden clasificar de m´ultiples formas dependiendo de los criterios seleccionados. En nuestro caso, nos centraremos en la clasificaci´on por la codificaci´on o formato en el que al- macenan la informaci´on que contienen. As´ı, podemos distinguir los ficheros de texto y los ficheros binarios. En los ficheros de texto, la informaci´on se almacena utilizando una codificaci´on y formato adecuados para que puedan ser procesados (y le´ıdos), adem´as de por un programa de ordenador, por un ser humano. Por lo tanto, los ficheros de texto almacenan la informaci´on utilizando una codificaci´on textual como secuencia de caracteres (usualmente basada en la codificaci´on ASCII, 1Un directorio puede contener m´ultiples ficheros y, a su vez, tambi´en m´ultiples directorios. 83
  • 84. 84 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS UTF-8, etc), y en un formato que permita su legibilidad y procesamiento. Por otra parte, los ficheros binarios son procesados autom´aticamente por la ejecuci´on de programas, sin la interven- ci´on humana, por lo que no necesitan representar la informaci´on en un formato legible para el ser humano. Por ello, suelen codificar la informaci´on en un formato orientado a ser procesado efi- cientemente por los ordenadores, y en ese caso utilizan la representaci´on en el c´odigo binario que utilizan internamente los ordenadores para representar los datos. En este caso, pueden surgir prob- lemas de compatibilidad en aquellos casos en los que estos ficheros son procesados por programas ejecut´andose en ordenadores que utilizan distintas representaciones internas de los datos. As´ı por ejemplo, un fichero de texto denominado fechas.txt podr´ıa estar almacenado en una determinada posici´on en la jerarqu´ıa del sistema de ficheros (/home/alumno/documentos/fechas.txt) y contener informaci´on sobre las fechas de nacimiento de determinadas personas seg´un el siguiente formato, donde cada l´ınea se encuentra terminada por un car´acter terminador de fin de l´ınea:2 Juan L´opez 12 3 1992 Lola Mart´ınez 23 7 1987 Pepe Jim´enez 17 8 1996 T´engase en cuenta que el ordenador almacena los n´umeros internamente en formato binario, y que ´estos han sido convertidos a su representaci´on textual como secuencia de caracteres cuando fueron escritos al fichero de texto, y que la conversi´on inversa se produce cuando se leen desde el fichero de texto para su procesamiento. Por ejemplo, el n´umero 1992 se almacena en una vari- able de tipo unsigned y se representa internamente como el siguiente n´umero binario de 32 bits (00000000000000000000011111001000), sin embargo, su representaci´on textual en el fichero de texto se compone de la siguiente secuencia de cuatro caracteres: ’1’ ’9’ ’9’ ’2’. El contenido de un fichero de texto, adem´as de ser procesado por un programa espec´ıfico, dise˜nado para su procesamiento considerando su formato, tambi´en puede ser visualizado y editado mediante un programa de edici´on de textos de prop´osito general, tales como gedit, kate, gvim, emacs, etc. en Linux, textedit en MacOS-X y notepad en Windows, entre otros. En el caso del software, los programas en c´odigo fuente codificados en un lenguaje de progra- maci´on suelen ser almacenados como ficheros de texto. Sin embargo, el resultado de compilar estos programas fuente a programas ejecutables se almacenan en ficheros binarios (ejecutables por el Sistema Operativo). As´ı mismo, los ficheros que contienen im´agenes, v´ıdeo y m´usica suelen estar, en su mayor´ıa, almacenados en formato binario. 9.1. Flujos de Entrada y Salida Asociados a Ficheros En el cap´ıtulo 3 se explic´o que un programa codificado en C++ realiza la entrada y salida de informaci´on (con el entorno exterior del programa) a trav´es de los flujos (stream en ingl´es) de entrada y salida respectivamente. En dicho cap´ıtulo se muestra como se realiza la entrada y salida de datos a trav´es de los flujos est´andares de entrada y salida respectivamente (cin y cout), usualmente conectados con el teclado y la pantalla de la consola. Todo lo explicado anteriormente respecto a la entrada y salida b´asica con los flujos est´andares (cap 3), y entrada y salida de cadenas de caracteres (cap 6.2) es tambi´en aplicable a los flujos de entrada y salida vinculados a ficheros que veremos en este cap´ıtulo. Un flujo de entrada de datos en modo texto act´ua como una fuente que proporciona una secuencia de caracteres (usualmente a trav´es de un buffer de almacenamiento intermedio) desde el que se extraen los caracteres que representan a los datos de entrada, que posteriormente ser´an convertidos a la representaci´on interna adecuada. Juan L´opez 12 3 1992 ← Lola Mart´ınez 23 7 1987 ← Pepe Jim´enez 17 8 1996 ← Por el contrario, un flujo de salida de datos en modo texto act´ua como un sumidero que recibe una secuencia de caracteres (usualmente a trav´es de un buffer de almacenamiento intermedio) a 2El car´acter terminador de fin de l´ınea no es visible, aunque se aprecian sus efectos al mostrarse los siguientes caracteres en la siguiente l´ınea. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 85. 9.2. ENTRADA DE DATOS DESDE FICHEROS DE TEXTO 85 la que se env´ıan los caracteres que representan a los datos de salida, que previamente han sido convertidos al formato de texto adecuado. Datos Datos Datos DatosJuan 333 Maria 222 Pepe 111 Juan 555 Maria 666 Pepe 444 (cout) (f_salida) (cin) (f_entrada) PROGRAMA agenda.txt agenda.txt En el caso de entrada y salida a ficheros, el lenguaje de programaci´on C++ posee mecanismos para asociar y vincular estos flujos con ficheros almacenados en memoria secundaria en la jerarqu´ıa del sistema de ficheros. As´ı, toda la entrada y salida de informaci´on se realiza a trav´es de estos flujos vinculados a ficheros, denominados manejadores de ficheros. De este modo, cuando un programa quiere realizar una entrada o salida de datos con un determinado fichero, debe realizar las siguientes acciones: 1. Incluir la biblioteca <fstream> donde se definen los tipos correspondientes, y utilizar el espacio de nombres std. 2. Declarar variables del tipo de flujo adecuado (para entrada o salida) para que act´uen como mane- jadores de fichero. 3. Abrir el flujo de datos vinculando la variable correspondiente con el fichero especificado. Esta ope- raci´on establece un v´ınculo entre la variable (manejador de fichero) definida en nuestro programa con el fichero gestionado por el sistema operativo, de tal forma que toda transferencia de informaci´on que el programa realice con el fichero, se realizar´a a trav´es de la variable manejador de fichero vinculada con el mismo. 4. Comprobar que la apertura del fichero del paso previo se realiz´o correctamente. Si la vinculaci´on con el fichero especificado no pudo realizarse por alg´un motivo (por ejemplo, el fichero no existe, en el caso de entrada de datos, o no es posible crear el fichero, en el caso de salida de datos), entonces la operaci´on de apertura fallar´ıa. 5. Realizar la transferencia de informaci´on (de entrada o de salida) con el fichero a trav´es de la variable de flujo vinculada al fichero. Para esta transferencia de informaci´on (entrada y salida) se pueden utilizar los mecanismos vistos en los cap´ıtulos anteriores (3, 6.2). En el caso de salida de datos, ´estos deber´an escribirse siguiendo un formato adecuado que permita su posterior lectura, por ejemplo escribiendo los separadores adecuados para ello entre los diferentes valores almacenados. Normalmente, tanto la entrada como la salida de datos implican un proceso iterativo, que en el caso de entrada se suele realizar hasta leer y procesar todo el contenido del fichero. 6. Comprobar que el procesamiento del fichero del paso previo se realiz´o correctamente, de tal forma que si el procesamiento consist´ıa en entrada de datos, el estado de la variable vinculada al fichero se encuentre en un estado indicando que se ha alcanzado el final del fichero, y si el procesamiento era de salida, el estado de la variable vinculada al fichero se deber´a encontrar en un estado correcto. 7. Finalmente cerrar el flujo liberando la variable de su vinculaci´on con el fichero. Si no se cierra el flujo de fichero, cuando termine el ´ambito de vida de la variable vinculada, el flujo ser´a cerrado autom´aticamente, y su vinculaci´on liberada. 8. Nota: es importante tener en cuenta que cuando un flujo pasa al estado err´oneo (fail()), entonces cualquier operaci´on de entrada o salida que se realice sobre ´el tambi´en fallar´a. La operaci´on clear() restaura el estado del flujo. Cuando sea necesario, una variable de tipo flujo, tanto de entrada, como de salida, puede ser pasada como par´ametro por referencia (no constante) a cualquier subprograma. 9.2. Entrada de Datos desde Ficheros de Texto Para realizar la entrada de datos desde un fichero de texto, el programa debe: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 86. 86 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS 1. Incluir la biblioteca <fstream> donde se definen los tipos correspondientes, y utilizar el espacio de nombres std. #include <fstream> using namespace std ; 2. Definir una variable manejador de fichero del tipo de flujo de entrada (ifstream –input file stream). ifstream f_ent ; 3. Abrir el flujo vinculando la variable correspondiente con el fichero especificado. f_ent.open(nombre_fichero.c_str()) ; 4. Comprobar que la apertura del fichero se realiz´o correctamente. if (f_ent.fail()) { ... } 5. Realizar la entrada de datos con los operadores y subprogramas correspondientes, as´ı como procesar la informaci´on le´ıda. f_ent >> nombre >> apellidos >> dia >> mes >> anyo ; f_ent.ignore(1000, ’n’) ; f_ent >> ws ; getline(f_ent, linea) ; f_ent.get(c) ; Usualmente es un proceso iterativo que se realiza hasta que la operaci´on de entrada de datos falla, usualmente debido a haber alcanzado el final del fichero. Este proceso iterativo usualmente consiste en la iteraci´on del siguiente proceso: Lectura de datos Si la lectura no ha sido correcta, entonces terminar el proceso iterativo. En otro caso, procesamiento de los datos le´ıdos, y vuelta al proceso iterativo, leyendo nuevos datos { ... leer(f_ent, datos) ; while (! f_ent.fail() ... ) { procesar(datos, ...) ; leer(f_ent, datos) ; } } 6. Comprobar que el procesamiento del fichero se realiz´o correctamente, es decir, el fichero se ley´o com- pletamente hasta el final de mismo (eof representa end-of-file). if (f_ent.eof()) { ... } 7. Finalmente cerrar el flujo liberando la variable de su vinculaci´on. f_ent.close() ; Por ejemplo, un programa que lee n´umeros desde un fichero de texto y los procesa (en este caso simplemente los muestra por pantalla): #include <iostream> #include <fstream> #include <string> using namespace std ; enum Codigo { OK, ERROR_APERTURA, ERROR_FORMATO } ; void procesar(int num) { Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 87. 9.3. SALIDA DE DATOS A FICHEROS DE TEXTO 87 cout << num << endl ; } void leer_fich(const string& nombre_fichero, Codigo& ok) { ifstream f_ent ; f_ent.open(nombre_fichero.c_str()) ; if (f_ent.fail()) { ok = ERROR_APERTURA ; } else { int numero ; f_ent >> numero ; while (! f_ent.fail()) { procesar(numero) ; f_ent >> numero ; } if (f_ent.eof()) { ok = OK ; } else { ok = ERROR_FORMATO ; } f_ent.close() ; } } void codigo_error(Codigo ok) { switch (ok) { case OK: cout << "Fichero procesado correctamente" << endl ; break ; case ERROR_APERTURA: cout << "Error en la apertura del fichero" << endl ; break ; case ERROR_FORMATO: cout << "Error de formato en la lectura del fichero" << endl ; break ; } } int main() { Codigo ok ; string nombre_fichero ; cout << "Introduzca el nombre del fichero: " ; cin >> nombre_fichero ; leer_fich(nombre_fichero, ok) ; codigo_error(ok) ; } 9.3. Salida de Datos a Ficheros de Texto Para realizar la salida de datos a un fichero de texto, el programa debe: 1. Incluir la biblioteca <fstream> donde se definen los tipos correspondientes, y utilizar el espacio de nombres std. #include <fstream> using namespace std ; 2. Definir una variable manejador de fichero del tipo de flujo de salida (ofstream –output file stream). ofstream f_sal ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 88. 88 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS 3. Abrir el flujo vinculando la variable correspondiente con el fichero especificado. f_sal.open(nombre_fichero.c_str()) ; 4. Comprobar que la apertura del fichero se realiz´o correctamente. if (f_sal.fail()) { ... } 5. Realizar la salida de datos con los operadores y subprogramas correspondientes, teniendo en cuenta los separadores que se deben escribir para que puedan ser le´ıdos adecuadamente. f_sal << nombre << " " << apellidos << " " << dia << " " << mes << " " << anyo << endl ; Usualmente ´este es un proceso iterativo que se realiza hasta que se escriben en el fichero todos los datos apropiados y mientras el estado del flujo sea correcto. while ( ... ! f_sal.fail() ) { ... } 6. Comprobar que el procesamiento del fichero se realiz´o correctamente, es decir, el fichero se encuentra en buen estado. if ( ! f_sal.fail()) { ... } 7. Finalmente cerrar el flujo liberando la variable de su vinculaci´on. f_sal.close() ; Por ejemplo, un programa que lee n´umeros de teclado (hasta introducir un cero) y los escribe a un fichero de texto: #include <iostream> #include <fstream> #include <string> using namespace std ; enum Codigo { OK, ERROR_APERTURA, ERROR_FORMATO } ; void escribir_fich(const string& nombre_fichero, Codigo& ok) { ofstream f_sal ; f_sal.open(nombre_fichero.c_str()) ; if (f_sal.fail()) { ok = ERROR_APERTURA ; } else { int numero ; cin >> numero ; while ((numero > 0) && ! cin.fail() && ! f_sal.fail()) { f_sal << numero << endl ; cin >> numero ; } if ( ! f_sal.fail()) { ok = OK ; } else { ok = ERROR_FORMATO ; } f_sal.close() ; } } void codigo_error(Codigo ok) { switch (ok) { case OK: cout << "Fichero guardado correctamente" << endl ; break ; case ERROR_APERTURA: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 89. 9.4. EJEMPLOS 89 cout << "Error en la apertura del fichero" << endl ; break ; case ERROR_FORMATO: cout << "Error de formato al escribir al fichero" << endl ; break ; } } int main() { Codigo ok ; string nombre_fichero ; cout << "Introduzca el nombre del fichero: " ; cin >> nombre_fichero ; escribir_fich(nombre_fichero, ok) ; codigo_error(ok) ; } 9.4. Ejemplos Ejemplo 1 Ejemplo de un programa que copia el contenido de un fichero a otro, car´acter a car´acter: #include <iostream> #include <fstream> #include <string> using namespace std ; enum Codigo { OK, ERROR_APERTURA_ENT, ERROR_APERTURA_SAL, ERROR_FORMATO } ; void copiar_fichero(const string& salida, const string& entrada, Codigo& ok) { ifstream f_ent ; f_ent.open(entrada.c_str()) ; if (f_ent.fail()) { ok = ERROR_APERTURA_ENT ; } else { ofstream f_sal ; f_sal.open(salida.c_str()) ; if (f_sal.fail()) { ok = ERROR_APERTURA_SAL ; } else { char ch ; f_ent.get(ch) ; while (! f_ent.fail() && ! f_sal.fail()) { f_sal.put(ch) ; f_ent.get(ch) ; } if (f_ent.eof() && ! f_sal.fail()) { ok = OK ; } else { ok = ERROR_FORMATO ; } f_sal.close() ; // no es necesario } f_ent.close() ; // no es necesario } } void codigo_error(Codigo ok) Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 90. 90 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS { switch (ok) { case OK: cout << "Fichero procesado correctamente" << endl ; break ; case ERROR_APERTURA_ENT: cout << "Error en la apertura del fichero de entrada" << endl ; break ; case ERROR_APERTURA_SAL: cout << "Error en la apertura del fichero de salida" << endl ; break ; case ERROR_FORMATO: cout << "Error de formato en la lectura del fichero" << endl ; break ; } } int main() { Codigo ok ; string entrada, salida ; cout << "Introduzca el nombre del fichero de entrada: " ; cin >> entrada ; cout << "Introduzca el nombre del fichero de salida: " ; cin >> salida ; copiar_fichero(salida, entrada, ok) ; codigo_error(ok) ; } Ejemplo 2 Ejemplo de un programa que crea, guarda y carga una agenda personal. //------------------------------------------------------------------------- #include <iostream> #include <fstream> #include <string> #include <tr1/array> #include <cctype> using namespace std ; using namespace std::tr1 ; //------------------------------------------------------------------------- struct Fecha { unsigned dia ; unsigned mes ; unsigned anyo ; } ; struct Persona { string nombre ; string tfn ; Fecha fnac ; } ; const int MAX = 100 ; typedef array<Persona, MAX> APers ; struct Agenda { int nelms ; APers elm ; } ; //------------------------------------------------------------------------- void inic_agenda(Agenda& ag) { Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 91. 9.4. EJEMPLOS 91 ag.nelms = 0 ; } void anyadir_persona(Agenda& ag, const Persona& p, bool& ok) { if (ag.nelms < int(ag.elm.size())) { ag.elm[ag.nelms] = p ; ++ag.nelms ; ok = true ; } else { ok = false ; } } //------------------------------------------------------------------------- void leer_fecha(Fecha& f) { cout << "Introduza fecha de nacimiento (dia mes a~no): " ; cin >> f.dia >> f.mes >> f.anyo ; } void leer_persona(Persona& p) { cout << "Introduza nombre: " ; cin >> ws ; getline(cin, p.nombre) ; cout << "Introduza tel´efono: " ; cin >> p.tfn ; leer_fecha(p.fnac) ; } void nueva_persona(Agenda& ag) { bool ok ; Persona p ; leer_persona(p) ; if (! cin.fail()) { anyadir_persona(ag, p, ok) ; if (!ok) { cout << "Error al introducir la nueva persona" << endl ; } } else { cout << "Error al leer los datos de la nueva persona" << endl ; cin.clear() ; cin.ignore(1000, ’n’) ; } } //------------------------------------------------------------------------- void escribir_fecha(const Fecha& f) { cout << f.dia << ’/’ << f.mes << ’/’ << f.anyo ; } void escribir_persona(const Persona& p) { cout << "Nombre: " << p.nombre << endl ; cout << "Tel´efono: " << p.tfn << endl ; cout << "Fecha nac: " ; escribir_fecha(p.fnac) ; cout << endl ; } void escribir_agenda(const Agenda& ag) { for (int i = 0 ; i < ag.nelms ; ++i) { Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 92. 92 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS cout << "----------------------------------------" << endl ; escribir_persona(ag.elm[i]) ; } cout << "----------------------------------------" << endl ; } //------------------------------------------------------------------------- // FORMATO DEL FICHERO DE ENTRADA: // // <nombre> <RC> // <tel´efono> <dia> <mes> <a~no> <RC> // <nombre> <RC> // <tel´efono> <dia> <mes> <a~no> <RC> // ... //------------------------------------------------------------------------- void leer_fecha(ifstream& fich, Fecha& f) { fich >> f.dia >> f.mes >> f.anyo ; } void leer_persona(ifstream& fich, Persona& p) { fich >> ws ; getline(fich, p.nombre) ; fich >> p.tfn ; leer_fecha(fich, p.fnac) ; } //---------------------------------------------- // Otra posible implementaci´on // void leer_persona(ifstream& fich, Persona& p) // { // getline(fich, p.nombre) ; // fich >> p.tfn ; // leer_fecha(fich, p.fnac) ; // fich.ignore(1000, ’n’) ; // } //---------------------------------------------- void leer_agenda(const string& nombre_fich, Agenda& ag, bool& ok) { ifstream fich ; Persona p ; fich.open(nombre_fich.c_str()) ; if (fich.fail()) { ok = false ; } else { ok = true ; inic_agenda(ag) ; leer_persona(fich, p) ; while (!fich.fail() && ok) { anyadir_persona(ag, p, ok) ; leer_persona(fich, p) ; } ok = ok && fich.eof() ; fich.close() ; } } void cargar_agenda(Agenda& ag) { bool ok ; string nombre_fich ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 93. 9.4. EJEMPLOS 93 cout << "Introduce el nombre del fichero: " ; cin >> nombre_fich ; leer_agenda(nombre_fich, ag, ok) ; if (!ok) { cout << "Error al cargar el fichero" << endl ; } } //------------------------------------------------------------------------- // FORMATO DEL FICHERO DE SALIDA: // // <nombre> <RC> // <tel´efono> <dia> <mes> <a~no> <RC> // <nombre> <RC> // <tel´efono> <dia> <mes> <a~no> <RC> // ... //------------------------------------------------------------------------- void escribir_fecha(ofstream& fich, const Fecha& f) { fich << f.dia << ’ ’ << f.mes << ’ ’ << f.anyo ; } void escribir_persona(ofstream& fich, const Persona& p) { fich << p.nombre << endl ; fich << p.tfn << ’ ’ ; escribir_fecha(fich, p.fnac) ; fich << endl ; } void escribir_agenda(const string& nombre_fich, const Agenda& ag, bool& ok) { ofstream fich ; fich.open(nombre_fich.c_str()) ; if (fich.fail()) { ok = false ; } else { int i = 0 ; while ((i < ag.nelms) && (! fich.fail())) { escribir_persona(fich, ag.elm[i]) ; ++i ; } ok = ! fich.fail() ; fich.close() ; } } void guardar_agenda(const Agenda& ag) { bool ok ; string nombre_fich ; cout << "Introduce el nombre del fichero: " ; cin >> nombre_fich ; escribir_agenda(nombre_fich, ag, ok) ; if (!ok) { cout << "Error al guardar el fichero" << endl ; } } //------------------------------------------------------------------------- char menu() { char op ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 94. 94 CAP´ITULO 9. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS cout << endl ; cout << "C. Cargar Agenda" << endl ; cout << "M. Mostrar Agenda" << endl ; cout << "N. Nueva Persona" << endl ; cout << "G. Guardar Agenda" << endl ; cout << "X. Fin" << endl ; do { cout << endl << " Opci´on: " ; cin >> op ; op = char(toupper(op)) ; } while (!((op == ’C’)||(op == ’M’)||(op == ’N’)||(op == ’G’)||(op == ’X’))) ; cout << endl ; return op ; } //------------------------------------------------------------------------- int main() { Agenda ag ; char op ; inic_agenda(ag) ; do { op = menu() ; switch (op) { case ’C’: cargar_agenda(ag) ; break ; case ’M’: escribir_agenda(ag) ; break ; case ’N’: nueva_persona(ag) ; break ; case ’G’: guardar_agenda(ag) ; break ; } } while (op != ’X’) ; } //------------------------------------------------------------------------- Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 95. Cap´ıtulo 10 M´odulos y Bibliotecas Cuando se desarrollan programas de complejidad media/alta, el c´odigo fuente normalmente no se encuentra en un ´unico fichero, sino que se encuentra distribuido entre varios m´odulos. Una primera ventaja de la existencia de m´odulos es que permiten aumentar la localidad y cohesi´on del c´odigo y aislarlo del exterior, es decir, poner todo el c´odigo encargado de resolver un determinado problema en un m´odulo nos permite aislarlo del resto, con lo que futuras modificaciones ser´an m´as f´aciles de realizar. Otra ventaja adicional de los m´odulos es el hecho de que si se modifica algo interno en un determinado m´odulo, s´olo ser´a necesario volver a compilar dicho m´odulo, y no todo el programa completo, lo que se convierte en una ventaja indudable en caso de programas grandes (compilaci´on separada). Adem´as, esta divisi´on modular es una pieza fundamental para la reutilizaci´on del c´odigo, ya que permite la utilizaci´on de bibliotecas del sistema, as´ı como la creaci´on y distribuci´on de bibliotecas de utilidades que podr´an ser utilizadas por m´ultiples programas. Esta distribuci´on de bibliotecas se puede hacer en c´odigo objeto, por lo que no es necesario distribuir el c´odigo fuente de la misma. GUI MP3 JPG I/O Kernel S.O. ScktsFile Math Mem Proc M2M1 Main Programa API S.O. S.O. Bbl. Utilidades As´ı, vemos que en la figura un determinado programa se compone de varios m´odulos de progra- ma (Main, M1 y M2) en los cuales est´a dividida la soluci´on principal del problema, varios m´odulos de bibliotecas proporcionan utilidades gr´aficas, matem´aticas y tratamiento de im´agenes ; as´ı como varios m´odulos de biblioteca dan acceso a servicios de entrada/salida y comunicaciones por Internet proporcionados por el sistema operativo. 10.1. Interfaz e Implementaci´on del M´odulo En el lenguaje de programaci´on C++, normalmente un m´odulo se compone de dos ficheros: uno donde aparece el c´odigo que resuelve un determinado problema o conjunto de problemas (implementaci´on – parte privada), y un fichero que contiene las definiciones de tipos, constantes y prototipos de subprogramas que el m´odulo ofrece (interfaz – parte p´ublica). As´ı, se denomina la implementaci´on del m´odulo al fichero que contiene la parte privada del m´odulo, y se denomina la interfaz del m´odulo al fichero que contiene la parte p´ublica del mismo. A este fichero tambi´en se le denomina “fichero de encabezamiento” o “fichero de cabecera” (header file en ingl´es). 95
  • 96. 96 CAP´ITULO 10. M ´ODULOS Y BIBLIOTECAS main.cpp (Principal) #include "complejo.hpp" // utilizaci´on de complejo int main() { · · · } complejo.hpp (Interfaz) #ifndef complejo hpp #define complejo hpp // interfaz de complejo // p´ublico · · · #endif complejo.cpp (Implementaci´on) #include "complejo.hpp" // implementaci´on de complejo // privado · · · · · · · · · Por lo tanto, un programa completo normalmente se compone de varios m´odulos, cada uno con su fichero de encabezamiento (interfaz) y de implementaci´on, y de un m´odulo principal donde reside la funci´on principal main. Para que un determinado m´odulo pueda hacer uso de las uti- lidades que proporciona otro m´odulo, deber´a incluir el fichero de encabezamiento (interfaz) del m´odulo que se vaya a utilizar, de tal forma que tenga acceso a las declaraciones p´ublicas de ´este. As´ı mismo, el fichero de implementaci´on de un determinado m´odulo tambi´en deber´a especificar al comienzo del mismo la inclusi´on del fichero de encabezamiento de su propio m´odulo, con objeto de obtener y contrastar las definiciones all´ı especificadas, es decir, el fichero de encabezamiento del propio m´odulo debe ser el primer fichero que se incluya en el fichero de implementaci´on del mis- mo m´odulo. Normalmente los ficheros de implementaci´on tendr´an una extensi´on “.cpp” (tambi´en suelen utilizarse otras extensiones como “.cxx” y “.cc”) y los ficheros de encabezamiento tendr´an una extensi´on “.hpp” (tambi´en suelen utilizarse otras extensiones como “.hxx”, “.hh” y “.h”). As´ı, para incluir el fichero de encabezamiento (interfaz) de un m´odulo complejo se utiliza la siguiente directiva: #include "complejo.hpp" N´otese que cuando se incluyen ficheros de encabezamiento (interfaz) de la biblioteca est´andar (o del sistema), el nombre del fichero se especifica entre los siguientes s´ımbolos <...>, pero cuando se incluyen ficheros de encabezamiento de m´odulos y bibliotecas locales (no est´andares), entonces el nombre del fichero se especifica entre los siguientes s´ımbolos "...". De esta forma, los ficheros de la biblioteca est´andar y del sistema se buscan en directorios del sistema, pero los ficheros y bibliotecas locales se buscan en el directorio local de trabajo. Guardas en un Fichero de Encabezamiento Las definiciones en los ficheros de encabezamiento (interfaz) ser´an especificadas entre las guardas (directivas de compilaci´on condicional) para evitar la inclusi´on duplicada de las definiciones all´ı con- tenidas. El nombre de la guarda usualmente se deriva del nombre del fichero, como se indica en el siguiente ejemplo donde el m´odulo complejo tendr´a los siguientes ficheros de encabezamiento y de implementaci´on (en determinadas circunstancias, puede ser conveniente que al nombre de la guarda se le a˜nada tambi´en el nombre del espacio de nombres que se explicar´a en la siguiente secci´on): Fichero: complejo.hpp (Interfaz) Fichero: complejo.cpp (Implementaci´on) // Guarda para evitar inclusi´on duplicada #ifndef _complejo_hpp_ #define _complejo_hpp_ // Definiciones P´ublicas de: // * Constantes // * Tipos (Enum, Registros, Clases) // * Prototipos de Subprogramas #endif // Fin de guarda #include "complejo.hpp" // Implementaciones Privadas de: // * Constantes Privadas // * Tipos Privados // * Subprogramas // * Clases Directrices para el Dise˜no de Ficheros de Encabezamiento Las siguientes directrices deben ser tenidas en cuenta con objeto de organizar adecuadamente el dise˜no de los ficheros de encabezamiento de los m´odulos: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 97. 10.2. COMPILACI ´ON SEPARADA Y ENLAZADO 97 Un fichero de encabezamiento s´olo deber´a contener definiciones de constantes, definiciones de tipos y prototipos de subprogramas que exporta (parte p´ublica) el propio m´odulo. No deber´a contener definiciones de variables globales, ni la implementaci´on de c´odigo (de sub- programas y m´etodos). Esto ´ultimo salvo algunas excepciones, tales como la definici´on de subprogramas simples “en l´ınea” (v´ease 5.6) y la definici´on de subprogramas y clases gen´ericas (v´ease 12). El mecanismo de inclusi´on de ficheros de encabezamiento debe ser robusto ante posibles inclusiones duplicadas. Para ello siempre se utilizar´a el mecanismo de guardas explicado anteriormente. Un fichero de encabezamiento debe incluir todos los ficheros de encabezamiento de otros m´odulos que necesite para su propia definici´on, de forma tal que el orden de inclusi´on de los ficheros de encabezamiento no sea importante. 10.2. Compilaci´on Separada y Enlazado main.cpp Programa Principal C.Fuente C++ string Interfaz Público C.Fuente C++ iostream Interfaz Público C.Fuente C++ C.Objeto main.o Programa Principal bblstd.o Público Implementación C.Objeto bblstd.cpp Implementación Privado C.Fuente C++ C.Ejecutable main Programa Completo mat.hpp C.Fuente C++ Interfaz + Implementación Público Interfaz Público C.Fuente C++ complejo.hpp Privado Implementación C.Fuente C++ complejo.cpp complejo.o Público Implementación C.Objeto Compilación CompilaciónCompilación Enlazado Cuando se compila un m´odulo de forma independiente (compilaci´on separada), se compila su fichero de implementaci´on, por ejemplo complejo.cpp, y produce como resultado un fichero en c´odigo objeto, por ejemplo complejo.o, considerando que el c´odigo fuente en C++ compilado es el contenido del fichero de implementaci´on junto con el contenido de todos los ficheros de encabeza- miento incluidos durante el proceso de compilaci´on. Por ejemplo, mediante el siguiente comando se compila un m´odulo de implementaci´on de c´odigo fuente en C++ utilizando el compilador GNU GCC para generar el correspondiente c´odigo objeto: g++ -ansi -Wall -Werror -c complejo.cpp g++ -ansi -Wall -Werror -c main.cpp y el enlazado de los c´odigos objeto para generar el c´odigo ejecutable: g++ -ansi -Wall -Werror -o main main.o complejo.o Aunque tambi´en es posible realizar la compilaci´on y enlazado en el mismo comando: g++ -ansi -Wall -Werror -o main main.cpp complejo.cpp o incluso mezclar compilaci´on de c´odigo fuente y enlazado de c´odigo objeto: g++ -ansi -Wall -Werror -o main main.cpp complejo.o Hay que tener en cuenta que el compilador enlaza autom´aticamente el c´odigo generado con las bibliotecas est´andares de C++, y por lo tanto no es necesario que ´estas se especifiquen ex- pl´ıcitamente. Sin embargo, en caso de ser necesario, tambi´en es posible especificar el enlazado con bibliotecas externas: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 98. 98 CAP´ITULO 10. M ´ODULOS Y BIBLIOTECAS g++ -ansi -Wall -Werror -o main main.cpp complejo.cpp -ljpeg Estas bibliotecas no son m´as que una agregaci´on de m´odulos compilados a c´odigo objeto, y orga- nizadas adecuadamente para que puedan ser reutilizados por muy diversos programas. 10.3. Espacios de Nombre Cuando se trabaja con m´ultiples m´odulos y bibliotecas, es posible que se produzcan colisiones en la definici´on de entidades diferentes con los mismos identificadores proporcionadas por diferentes m´odulos y bibliotecas. Este hecho no est´a permitido por el lenguaje de programaci´on C++. Para evitar estas posibles colisiones existen los espacios de nombre (namespace en ingl´es), que permiten agrupar bajo una misma denominaci´on (jerarqu´ıa) un conjunto de declaraciones y definiciones, de tal forma que dicha denominaci´on ser´a necesaria para identificar y diferenciar cada entidad declarada. As´ı, para definir un espacio de nombres se utiliza la palabra reservada namespace seguida por el identificador del espacio de nombres, y entre llaves las declaraciones y definiciones que deban estar bajo dicha jerarqu´ıa del espacio de nombres. Estos espacios de nombre pueden ser ´unicos para un determinado m´odulo, o por el contrario pueden abarcar m´ultiples m´odulos y bibliotecas gestionados por el mismo proveedor, por ejemplo todas las entidades definidas en la biblioteca est´andar se encuentran bajo el espacio de nombres std. As´ı, el identificador del espacio de nombres puede ser derivado del propio nombre del fichero, puede incluir una denominaci´on relativa al proveedor del m´odulo, o alguna otra denominaci´on m´as compleja que garantice que no habr´a colisiones en el identificador del espacio de nombres. Por ejemplo, podemos definir el m´odulo complejo dentro del espacio de nombres umalcc, que har´ıa referencia a un proveedor del departamento de Lenguajes y Ciencias de la Computaci´on de la Universidad de M´alaga. main.cpp (Principal) #include <iostream> #include "complejo.hpp" using namespace std ; using namespace umalcc ; // utilizaci´on de complejo int main() { · · · } complejo.hpp (Interfaz) #ifndef complejo hpp #define complejo hpp #include <...otros...> // interfaz de complejo namespace umalcc { · · · · · · } #endif complejo.cpp (Implementaci´on) #include "complejo.hpp" #include <...otros...> // implementaci´on de complejo namespace umalcc { · · · · · · · · · · · · } N´otese que la inclusi´on de ficheros de encabezamiento se debe realizar externamente a la defini- ci´on de los espacios de nombre. Utilizaci´on de Espacios de Nombre Una vez que las entidades han sido definidas dentro de un espacio de nombres, ´estas no pueden ser utilizadas directamente, sino que es necesario alg´un tipo de cualificaci´on que permita referenciar e identificar a las entidades dentro de los espacios de nombre en los que han sido definidas. Ello se puede realizar de varias formas, dependiendo de las circunstancias donde se produzca esta utilizaci´on: Todos los identificadores definidos dentro de un espacio de nombres determinado son visi- bles y accesibles directamente desde dentro del mismo espacio de nombres, sin necesidad de cualificaci´on. En la implementaci´on de los m´odulos (ficheros de implementaci´on .cpp), mediante la directiva using namespace se ponen disponibles (accesibles) todos los identificadores de dicho espacio de nombres completo, que podr´an ser accedidos directamente, sin necesidad de cualificaci´on expl´ıcita. Por ejemplo: using namespace std ; using namespace umalcc ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 99. 10.3. ESPACIOS DE NOMBRE 99 En los ficheros de encabezamiento (.hpp) cada identificador externo (perteneciente a otro espacio de nombres) se debe utilizar cualificado con el espacio de nombres al que pertenece (cualificaci´on expl´ıcita) utilizando para ello el identificador del espacio de nombres, seguido por el operador :: y del identificador de la entidad que se est´e utilizando, como en el siguiente ejemplo para utilizar el tipo string del espacio de nombres std, o el tipo array del espacio de nombres std::tr1. namespace umalcc { struct Persona { std::string nombre ; int edad ; } ; typedef std::tr1::array<int, 20> Vector ; void leer(std::string& nombre) ; } Si se utilizan (mediante using namespace) varios espacios de nombre simult´aneamente y ambos definen el mismo identificador, si dicho identificador no se utiliza, entonces no se produce colisi´on. Sin embargo en caso de que se utilice dicho identificador, entonces se produce una colisi´on ya que el mismo identificador se encuentra definido (y es accesible) en dos espacios de nombre diferentes, por lo que el compilador no puede discernir por s´ı mismo a que entidad se refiere. En este ´ultimo caso, el programador debe utilizar la cualificaci´on expl´ıcita para este identificador y eliminar de esta forma la ambig¨uedad en su utilizaci´on. main.cpp #include <iostream> #include <string> #include "datos.hpp" using namespace std ; using namespace umalcc ; // utilizaci´on de datos int main() { string colision ; std::string nombre 1 ; umalcc::string nombre 2 ; · · · } datos.hpp #ifndef datos hpp #define datos hpp #include <tr1/array> #include <...otros...> // interfaz de datos namespace umalcc { struct string { std::tr1::array<char, 50> datos ; int size ; } ; · · · } #endif datos.cpp #include "datos.hpp" #include <...otros...> using namespace std ; namespace umalcc { · · · · · · · · · · · · · · · · · · · · · · · · } Es importante remarcar que no es adecuado aplicar la directiva using namespace dentro de ficheros de encabezamiento, ya que si es utilizada en un fichero de encabezamiento que se incluye por m´ultiples m´odulos, entonces pondr´ıa disponible (accesible) todos los identificadores de dicho espacio de nombres para todos los ficheros que incluyan (include) dicho fichero de encabezamiento, algo que podr´ıa provocar colisiones inesperadas, y esto ser´ıa un efecto colateral no deseado para aquellos que utilizasen dicho m´odulo (incluyeran dicho fichero de encabezamiento). Espacios de Nombre An´onimos Los espacios de nombre an´onimos permiten definir entidades privadas internas a los m´odulos de implementaci´on, de tal forma que no puedan producir colisiones con las entidades p´ublicas del sistema completo. De esta forma, cualquier declaraci´on y definici´on realizada dentro de un espacio de nombres an´onimo ser´a ´unicamente visible en el m´odulo de implementaci´on donde se encuentre (privada), pero no ser´a visible en el exterior del m´odulo. Adicionalmente, tambi´en es posible definir espacios de nombre anidados (dentro de otros espa- cios de nombre), pudiendo, de esta forma, definir jerarqu´ıas de espacios de nombre. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 100. 100 CAP´ITULO 10. M ´ODULOS Y BIBLIOTECAS Ejemplo M´odulo N´umeros Complejos Ejemplo de m´odulo para definir operaciones con n´umeros complejos, donde el fichero de en- cabezamiento podr´ıa ser: //- fichero: complejos.hpp ------------------------------------------ #ifndef _complejos_hpp_ #define _complejos_hpp_ namespace umalcc { //---------------------------------- const double ERROR_PRECISION = 1e-6 ; //---------------------------------- struct Complejo { double real ; // parte real del numero complejo double imag ; // parte imaginaria del numero complejo } ; //---------------------------------- void sumar(Complejo& r, const Complejo& a, const Complejo& b) ; // Devuelve un numero complejo (r) que contiene el resultado de // sumar los numeros complejos (a) y (b). //---------------------------------- void restar(Complejo& r, const Complejo& a, const Complejo& b) ; // Devuelve un numero complejo (r) que contiene el resultado de // restar los numeros complejos (a) y (b). //---------------------------------- void multiplicar(Complejo& r, const Complejo& a, const Complejo& b) ; // Devuelve un numero complejo (r) que contiene el resultado de // multiplicar los numeros complejos (a) y (b). //---------------------------------- void dividir(Complejo& r, const Complejo& a, const Complejo& b) ; // Devuelve un numero complejo (r) que contiene el resultado de // dividir los numeros complejos (a) y (b). //---------------------------------- bool iguales(const Complejo& a, const Complejo& b) ; // Devuelve true si los numeros complejos (a) y (b) son iguales. //---------------------------------- void escribir(const Complejo& a) ; // muestra en pantalla el numero complejo (a) //---------------------------------- void leer(Complejo& a) ; // lee de teclado el valor del numero complejo (a). // lee la parte real y la parte imaginaria del numero //---------------------------------- } #endif //- fin: complejos.hpp ---------------------------------------------- La implementaci´on del m´odulo se realiza en un fichero independiente, donde adem´as se puede apreciar la utilizaci´on de un espacio de nombres an´onimo: //- fichero: complejos.cpp ------------------------------------------ #include "complejos.hpp" #include <iostream> using namespace std ; using namespace umalcc ; //------------------------------------------------------------------------------ // Espacio de nombres anonimo. Es una parte privada de la // implementacion. No es accesible desde fuera del modulo Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 101. 10.3. ESPACIOS DE NOMBRE 101 //------------------------------------------------------------------------------ namespace { //---------------------------------- //-- Subprogramas Auxiliares ------- //---------------------------------- // cuadrado de un numero (a^2) inline double sq(double a) { return a*a ; } //---------------------------------- // Valor absoluto de un numero inline double abs(double a) { if (a < 0) { a = -a ; } return a ; } //---------------------------------- // Dos numeros reales son iguales si la distancia que los // separa es lo suficientemente pequenya inline bool iguales(double a, double b) { return abs(a-b) <= ERROR_PRECISION ; } } //------------------------------------------------------------------------------ // Espacio de nombres umalcc. // Aqui reside la implementacion de la parte publica del modulo //------------------------------------------------------------------------------ namespace umalcc { //---------------------------------- //-- Implementaci´on ---------------- //---------------------------------- // Devuelve un numero complejo (r) que contiene el resultado de // sumar los numeros complejos (a) y (b). void sumar(Complejo& r, const Complejo& a, const Complejo& b) { r.real = a.real + b.real ; r.imag = a.imag + b.imag ; } //---------------------------------- // Devuelve un numero complejo (r) que contiene el resultado de // restar los numeros complejos (a) y (b). void restar(Complejo& r, const Complejo& a, const Complejo& b) { r.real = a.real - b.real ; r.imag = a.imag - b.imag ; } //---------------------------------- // Devuelve un numero complejo (r) que contiene el resultado de // multiplicar los numeros complejos (a) y (b). void multiplicar(Complejo& r, const Complejo& a, const Complejo& b) { r.real = (a.real * b.real) - (a.imag * b.imag) ; r.imag = (a.real * b.imag) + (a.imag * b.real) ; } //---------------------------------- Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 102. 102 CAP´ITULO 10. M ´ODULOS Y BIBLIOTECAS // Devuelve un numero complejo (r) que contiene el resultado de // dividir los numeros complejos (a) y (b). void dividir(Complejo& r, const Complejo& a, const Complejo& b) { double divisor = sq(b.real) + sq(b.imag) ; if (::iguales(0.0, divisor)) { r.real = 0 ; r.imag = 0 ; } else { r.real = ((a.real * b.real) + (a.imag * b.imag)) / divisor ; r.imag = ((a.imag * b.real) - (a.real * b.imag)) / divisor ; } } //---------------------------------- // Devuelve true si los numeros complejos (a) y (b) son iguales. bool iguales(const Complejo& a, const Complejo& b) { return ::iguales(a.real, b.real) && ::iguales(a.imag, b.imag) ; } //---------------------------------- // muestra en pantalla el numero complejo (a) void escribir(const Complejo& a) { cout << "{ " << a.real << ", " << a.imag << " }" ; } //---------------------------------- // lee de teclado el valor del numero complejo (a). // lee la parte real y la parte imaginaria del numero void leer(Complejo& a) { cin >> a.real >> a.imag ; } //---------------------------------- } //- fin: complejos.cpp ---------------------------------------------- Un ejemplo de utilizaci´on del m´odulo de n´umeros complejos podr´ıa ser: //- fichero: main.cpp ------------------------------------------ #include <iostream> #include "complejos.hpp" using namespace std ; using namespace umalcc ; //------------------------------------ void leer(Complejo& c) { cout << "Introduzca un numero complejo { real img }: " ; // cualificacion expl´ıcita del espacio de nombres uamalcc // para evitar colisi´on en invocaci´on a subprograma // leer(Complejo& c) del espacio de nombres umalcc umalcc::leer(c) ; } //------------------------------------ void prueba_suma(const Complejo& c1, const Complejo& c2) { Complejo c0 ; sumar(c0, c1, c2) ; escribir(c1) ; cout <<" + " ; escribir(c2) ; cout <<" = " ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 103. 10.3. ESPACIOS DE NOMBRE 103 escribir(c0) ; cout << endl ; Complejo aux ; restar(aux, c0, c2) ; if (! iguales(c1, aux)) { cout << "Error en operaciones de suma/resta"<< endl ; } } //------------------------------------ void prueba_resta(const Complejo& c1, const Complejo& c2) { Complejo c0 ; restar(c0, c1, c2) ; escribir(c1) ; cout <<" - " ; escribir(c2) ; cout <<" = " ; escribir(c0) ; cout << endl ; Complejo aux ; sumar(aux, c0, c2) ; if (! iguales(c1, aux)) { cout << "Error en operaciones de suma/resta"<< endl ; } } //------------------------------------ void prueba_mult(const Complejo& c1, const Complejo& c2) { Complejo c0 ; multiplicar(c0, c1, c2) ; escribir(c1) ; cout <<" * " ; escribir(c2) ; cout <<" = " ; escribir(c0) ; cout << endl ; Complejo aux ; dividir(aux, c0, c2) ; if (! iguales(c1, aux)) { cout << "Error en operaciones de mult/div"<< endl ; } } //------------------------------------ void prueba_div(const Complejo& c1, const Complejo& c2) { Complejo c0 ; dividir(c0, c1, c2) ; escribir(c1) ; cout <<" / " ; escribir(c2) ; cout <<" = " ; escribir(c0) ; cout << endl ; Complejo aux ; multiplicar(aux, c0, c2) ; if (! iguales(c1, aux)) { cout << "Error en operaciones de mult/div"<< endl ; } } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 104. 104 CAP´ITULO 10. M ´ODULOS Y BIBLIOTECAS //------------------------------------ int main() { Complejo c1, c2 ; // cualificaci´on expl´ıcita del espacio de nombres global // para evitar colisi´on en invocaci´on al subprograma // leer(Complejo& c) del espacio de nombres global ::leer(c1) ; ::leer(c2) ; //-------------------------------- prueba_suma(c1, c2) ; prueba_resta(c1, c2) ; prueba_mult(c1, c2) ; prueba_div(c1, c2) ; //-------------------------------- } //- fin: main.cpp ---------------------------------------------- Su compilaci´on separada y enlazado en GNU GCC: g++ -ansi -Wall -Werror -c complejos.cpp g++ -ansi -Wall -Werror -c main.cpp g++ -ansi -Wall -Werror -o main main.o complejos.o Alternativamente se puede realizar en dos pasos: g++ -ansi -Wall -Werror -c complejos.cpp g++ -ansi -Wall -Werror -o main main.cpp complejos.o o incluso en un ´unico paso: g++ -ansi -Wall -Werror -o main main.cpp complejos.cpp Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 105. Cap´ıtulo 11 Tipos Abstractos de Datos A medida que aumenta la complejidad del problema a resolver, del mismo modo deben aumen- tar los niveles de abstracci´on necesarios para dise˜nar y construir su soluci´on algor´ıtmica. As´ı, la abstracci´on procedimental permite aplicar adecuadamente t´ecnicas de dise˜no descendente y refi- namientos sucesivos en el desarrollo de algoritmos y programas. La programaci´on modular permite aplicar la abstracci´on a mayor escala, permitiendo abstraer sobre conjuntos de operaciones y los datos sobre los que se aplican. De esta forma, a medida que aumenta la complejidad del problema a resolver, aumenta tambi´en la complejidad de las estructuras de datos necesarias para su resoluci´on, y este hecho requiere, as´ı mismo, la aplicaci´on de la abstracci´on a las estructuras de datos. TAD op1() op2() op3() La aplicaci´on de la abstracci´on a las estructuras de datos da lugar a los Tipos Abstractos de Datos (TAD), donde se especifica el concepto que representa un determinado tipo de datos, y la sem´antica (el significado) de las operaciones que se le pueden aplicar, pero donde su representaci´on e im- plementaci´on internas permanecen ocultas e inaccesibles desde el exterior, de tal forma que no son necesarias para su utilizaci´on. As´ı, podemos consi- derar que un tipo abstracto de datos encapsula una determinada estructura abstracta de datos, impidiendo su manipulaci´on directa, permitiendo sola- mente su manipulaci´on a trav´es de las operaciones especificadas. De este modo, los tipos abstractos de datos proporcionan un mecanismo adecuado para el dise˜no y reutilizaci´on de software fiable y robusto. Para un determinado tipo abstracto de datos, se pueden distinguir tres niveles: Nivel de utilizaci´on, donde se utilizan objetos de un determinado tipo abstracto de datos, bas´andose en la especificaci´on del mismo, de forma independiente a su implementaci´on y representaci´on concretas. As´ı, estos objetos se manipulan mediante la invocaci´on a las ope- raciones especificadas en el TAD. Nivel de especificaci´on, donde se especifica el tipo de datos, el concepto abstracto que representa y la sem´antica y restricciones de las operaciones que se le pueden aplicar. Este nivel representa el interfaz p´ublico del tipo abstracto de datos. Utilizaci´on de TAD Especificaci´on de TAD Implementaci´on de TAD Nivel de implementaci´on, donde se define e implementa tanto las estructuras de datos que soportan la abstracci´on, como las operaciones que act´uan sobre ella seg´un la sem´antica es- pecificada. Este nivel interno permanece privado, y no es accesible desde el exterior del tipo abstracto de datos. N´otese que para una determinada especificaci´on de un tipo abstracto de datos, su implementaci´on puede cambiar sin que ello afecte a la utilizaci´on del mismo. 105
  • 106. 106 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS 11.1. Tipos Abstractos de Datos en C++: Clases En el lenguaje de programaci´on C++, las clases dan la posibilidad al programador de definir tipos abstractos de datos, de tal forma que permiten definir su representaci´on interna (compuesta por sus atributos miembros), la forma en la que se crean y se destruyen, como se asignan y se pasan como par´ametros, y las operaciones que se pueden aplicar (denominadas funciones miembro o simplemente m´etodos). De esta forma se hace el lenguaje extensible. As´ı mismo, la definici´on de tipos abstractos de datos mediante clases puede ser combinada con la definici´on de m´odulos (v´ease 10), haciendo de este modo posible la reutilizaci´on de estos nuevos tipos de datos. As´ı, en C++ una determinada clase define un determinado tipo abstracto de datos, y un objeto se corresponde con una determinada instancia de una clase, de igual forma que una variable se corresponde con una determinada instancia de un tipo de datos. Aunque C++ permite implementar las clases utilizando una definici´on en l´ınea, en este cap´ıtulo nos centraremos en la implementaci´on separada de los m´etodos de las clases. Adem´as, combinare- mos la especificaci´on de la clase y su implementaci´on con los conceptos de programaci´on modular vistos en el cap´ıtulo anterior (v´ease 10), de tal forma que la definici´on de la clase se realizar´a en el fichero de cabecera (hpp) de un determinado m´odulo, y la implementaci´on de la clase se realizar´a en el fichero de implementaci´on (cpp) del m´odulo. 11.1.1. Definici´on de Clases La definici´on de la clase se realizar´a en el fichero de cabecera (hpp) de un determinado m´odulo, dentro de las guardas y espacio de nombres adecuado. Para ello, se especifica la palabra reservada class seguida por el identificador de la nueva clase (tipo) que se est´a definiendo, y entre llaves la definici´on de los atributos (miembros) que lo componen y de los m´etodos (funciones miembros) que se le pueden aplicar directamente a los objetos de la clase. Finalmente el delimitador punto y coma (;) debe seguir al delimitador cierra-llaves (}). //- fichero: complejos.hpp ------------------------------------------ #ifndef _complejos_hpp_ #define _complejos_hpp_ namespace umalcc { class Complejo { // ... } ; } #endif //- fin: complejos.hpp ---------------------------------------------- Zona Privada y Zona P´ublica En la definici´on de una clase, se pueden distinguir dos ´ambitos de visibilidad (accesibilidad), la parte privada, cuyos miembros s´olo ser´an accesibles desde un ´ambito interno a la propia clase, y la parte p´ublica, cuyos miembros son accesibles tanto desde un ´ambito interno como desde un ´ambito externo a la clase. La parte privada comprende desde el principio de la definici´on de la clase hasta la etiqueta public:, y la parte p´ublica comprende desde esta etiqueta hasta que se encuentra otra etiqueta private:. Cada vez que se especifica una de las palabras reservadas public: o private:, las declaraciones que la siguen adquieren el atributo de visibilidad dependiendo de la etiqueta especi- ficada. class Complejo { public: // ... zona p´ublica ... private: // ... zona privada ... } ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 107. 11.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 107 Atributos Los atributos componen la representaci´on interna de la clase, y se definen usualmente en la zona de visibilidad privada de la clase, con objeto de proteger el acceso a dicha representaci´on interna. Su definici´on se realiza de igual forma a los campos de los registros (v´ease 6.3). De igual modo a los registros y sus campos, cada objeto que se defina, almacenar´a su propia representaci´on interna de los atributos de forma independiente a los otros objetos (instancias de la misma clase). class Complejo { public: // ... private: double real ; // parte real del numero complejo double imag ; // parte imaginaria del numero complejo } ; El Constructor por Defecto El constructor de una clase permite construir e inicializar un objeto. El constructor por defecto es el mecanismo por defecto utilizado para construir objetos de este tipo cuando no se especifica ninguna forma expl´ıcita de construcci´on. As´ı, ser´a el encargado de construir el objeto con los valores iniciales adecuados en el momento en que sea necesaria dicha construcci´on, por ejemplo cuando el flujo de ejecuci´on alcanza la declaraci´on de una variable de dicho tipo (v´ease 11.1.2). Los constructores se declaran con el mismo identificador de la clase, seguidamente se especifican entre par´entesis los par´ametros necesarios para la construcci´on, que en el caso del constructor por defecto, ser´an vac´ıos. class Complejo { public: Complejo() ; // Constructor por Defecto // ... private: // ... } ; M´etodos Generales y M´etodos Constantes Los m´etodos se corresponden con las operaciones que permiten manipular de muy diversa forma el estado interno de un determinado objeto como instancia de una determinada clase. Puede haber m´etodos definidos en el ´ambito p´ublico de la clase, en cuyo caso podr´an ser invocados tanto desde m´etodos internos de la clase, como desde el exterior de la clase, y m´etodos definidos en el ´ambito privado de la clase, en cuyo caso s´olo podr´an ser invocados desde m´etodos internos de la clase. Estos m´etodos definidos en el ´ambito privado de la clase suelen ser definidos como m´etodos auxiliares que facilitan la implementaci´on de otros m´etodos m´as complejos. Los m´etodos se declaran como los prototipos de los subprogramas (v´ease 5.7), pero teniendo en cuenta son aplicados a un objeto instancia de la clase a la que pertenece, y que por lo tanto no es necesario que sea recibido como par´ametro. Los m´etodos de una clase pueden tener el cualificador const especificado despu´es de los par´ametros, en cuyo caso indica que el m´etodo no modifica el estado interno del objeto, por lo que se puede aplicar tanto a objetos constantes como variables. En otro caso, si dicho cualificador no aparece, entonces significa que el m´etodo si modifica el estado interno del objeto, por lo que s´olo podr´a ser aplicado a objetos variables, y por el contrario no podr´a ser aplicado a objetos constantes. class Complejo { public: // ... Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 108. 108 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS //---------------------------- void sumar(const Complejo& a, const Complejo& b) ; // asigna al numero complejo (actual) el resultado de // sumar los numeros complejos (a) y (b). //---------------------------- bool igual(const Complejo& b) const ; // Devuelve true si el numero complejo (actual) es // igual al numero complejo (b) //---------------------------- void escribir() const ; // muestra en pantalla el numero complejo (actual) //---------------------------- void leer() ; // lee de teclado el valor del numero complejo (actual). // lee la parte real y la parte imaginaria del numero //------------------------------ // ... private: // ... } ; 11.1.2. Utilizaci´on de Clases Un tipo abstracto de datos, definido como una clase encapsulada dentro de un m´odulo, puede ser utilizado por cualquier otro m´odulo que lo necesite. Para ello, deber´a incluir el fichero de cabecera donde se encuentra la definici´on de la clase, y podr´a definir tantos objetos (instancias de dicha clase) como sean necesarios, para ello, deber´a utilizar cualificaci´on expl´ıcita o impl´ıcita dependiendo del contexto de su utilizaci´on (ficheros de encabezamiento o de implementaci´on respectivamente). Instancias de Clase: Objetos Un objeto es una instancia de una clase, y podremos definir tantos objetos cuyo tipo sea de una determinada clase como sea necesario, de tal modo que cada objeto contiene su propia representaci´on interna de forma independiente del resto. La definici´on de un objeto de una determinada clase se realiza de igual forma a la definici´on de una variable (o constante) de un determinado tipo, de tal forma que cada objeto ser´a una instancia independiente de una determinada clase (tipo abstracto de datos). real: imag: 0.0 0.0 c1 real: imag: 0.0 0.0 c2 //- fichero: main.cpp ----------------------------------------------- #include <iostream> #include "complejos.hpp" using namespace std ; using namespace umalcc ; int main() { Complejo c1, c2 ; // ... } //- fin: main.cpp --------------------------------------------------- Es importante remarcar que cada objeto, definido de una determinada clase, es una instancia independiente de los otros objetos definidos de la misma clase, con su propia memoria para contener de forma independiente el estado de su representaci´on interna. Tiempo de Vida de los Objetos Durante la ejecuci´on del programa, cuando el flujo de ejecuci´on llega a la sentencia donde se define un determinado objeto, entonces se reserva espacio en memoria para contener a dicho objeto, Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 109. 11.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 109 y se invoca al constructor especificado (si no se especifica ning´un constructor, entonces se invoca al constructor por defecto) para construir adecuadamente al objeto, siendo de esta forma accesible desde este punto de construcci´on hasta que el flujo de ejecuci´on alcanza el final de bloque donde el objeto ha sido definido, en cuyo caso el objeto se destruye (invocando a su destructor) y se libera la memoria que ocupaba, pasando de este modo a estar inaccesible. //- fichero: main.cpp ----------------------------------------------- #include <iostream> #include "complejos.hpp" using namespace std ; using namespace umalcc ; int main() { Complejo c1 ; // construcci´on de c1 (1 vez) for (int i = 0 ; i < 3 ; ++i) { Complejo c2 ; // construcci´on de c2 (3 veces) // ... } // destrucci´on de c2 (3 veces) // ... } // destrucci´on de c1 (1 vez) //- fin: main.cpp --------------------------------------------------- Manipulaci´on de los Objetos Una vez que un objeto es accesible, se puede manipular invocando a los m´etodos p´ublicos definidos en su interfaz. Esta invocaci´on de los m´etodos se aplica sobre un determinado objeto en concreto, y se realiza especificando el identificador del objeto sobre el que recae la invocaci´on al m´etodo, seguido por el s´ımbolo punto (.) y por la invocaci´on al m´etodo en cuesti´on, es decir, el identificador del m´etodo y los par´ametros actuales necesarios entre par´entesis. real: imag: 5.3 2.4 c1 real: imag: 2.5 7.3 c2 real: imag: 7.8 9.7 c3 //- fichero: main.cpp ----------------------------------------------- #include <iostream> #include "complejos.hpp" using namespace std ; using namespace umalcc ; int main() { Complejo c1, c2, c3 ; // construcci´on de c1, c2, c3 c1.leer() ; c2.leer() ; c3.sumar(c1, c2) ; c3.escribir() ; } // destrucci´on de c1, c2, c3 //- fin: main.cpp --------------------------------------------------- Paso de Par´ametros de Objetos Es importante considerar que las clases (tipos abstractos de datos) son tipos compuestos, y por lo tanto deben seguir las mismas convenciones para el paso de par´ametros de tipos compuestos (v´ease 6.1), es decir, los par´ametros de salida o entrada/salida se pasan por referencia, y los par´ametros de entrada se pasan por referencia constante. 11.1.3. Implementaci´on de Clases La implementaci´on de los m´etodos de la clase se realizar´a en el fichero de implementaci´on (cpp) del m´odulo correspondiente, dentro del mismo espacio de nombres en el que fue realizada la definici´on de la clase en el fichero de cabecera. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 110. 110 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS En el fichero de implementaci´on se podr´an definir, dentro del espacio de nombres adecuado, las constantes, tipos y subprogramas auxiliares necesarios que nos faciliten la implementaci´on de los m´etodos de la clase. Para implementar un determinado constructor o m´etodo de la clase, dentro del mismo espacio de nombres que la definici´on de la clase, se cualificar´a expl´ıcitamente el identifidor del m´etodo con el identificador de la clase a la que pertenece. //- fichero: complejos.cpp ------------------------------------------ #include "complejos.hpp" namespace umalcc { // ... //---------------------------- Complejo::Complejo() // Constructor por Defecto // ... //---------------------------- void Complejo::sumar(const Complejo& a, const Complejo& b) // ... //---------------------------- bool Complejo::igual(const Complejo& b) const // ... //---------------------------- void Complejo::escribir() const // ... //---------------------------- void Complejo::leer() // ... //---------------------------- // ... } //- fin: complejos.cpp ---------------------------------------------- M´etodos En la implementaci´on de un determinado m´etodo de una clase, ´este m´etodo puede invocar directamente a cualquier otro m´etodo de la clase sin necesidad de aplicar el operador punto (.). As´ı mismo, un m´etodo de la clase puede acceder directamente a los atributos del objeto sobre el que se invoque dicho m´etodo, sin necesidad de aplicar el operador punto (.), ni necesidad de recibirlo como par´ametro. Por ejemplo: void Complejo::sumar(const Complejo& a, const Complejo& b) { real = a.real + b.real ; imag = a.imag + b.imag ; } Sin embargo, para acceder a los atributos de los objetos recibidos como par´ametros, si son accesibles desde la implementaci´on de una determinada clase, es necesario especificar el objeto (mediante su identificador) seguido por el operador punto (.) y a continuaci´on el identificador del atributo en cuesti´on. As´ı, podemos ver como para calcular la suma de n´umeros complejos, se asigna a las partes real e imaginaria del n´umero complejo que estamos calculando (el objeto sobre el que se aplica el m´etodo sumar) la suma de las partes real e imaginaria respectivamente de los n´umeros complejos que recibe como par´ametros. Por ejemplo, cuando se ejecuta la sentencia: c3.sumar(c1, c2) ; la sentencia correspondiente a la implementaci´on del m´etodo sumar(...): real = a.real + b.real ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 111. 11.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 111 almacenar´a en el atributo real del n´umero complejo c3 el resultado de sumar los valores del atributo real de los n´umeros complejos c1 y c2. De igual modo suceder´a con el atributo imag del n´umero complejo c3, que almacenar´a el resultado de sumar los valores del atributo imag de los n´umeros complejos c1 y c2. Constructores En la implementaci´on de los constructores de la clase, tambi´en ser´a cualificado expl´ıcitamente con el identificador de la clase correspondiente. Despu´es de la definici´on de los par´ametros, a continuaci´on del delimitador (:), se especifica la lista de inicializaci´on, donde aparecen, separados por comas y seg´un el orden de declaraci´on, todos los atributos miembros del objeto, as´ı como los valores con los que ser´an inicializados especificados entre par´entesis (se invoca al constructor adecuado seg´un los par´ametros especificados entre par´entesis, de tal forma que los par´entesis vac´ıos representan la construcci´on por defecto). A continuaci´on se especifican entre llaves las sentencias pertenecientes al cuerpo del constructor para realizar las acciones adicionales necesarias para la construcci´on del objeto. Si no es necesario realizar ninguna acci´on adicional, entonces el cuerpo del constructor se dejar´a vac´ıo. Por ejemplo, implementaremos el constructor por defecto de la clase Complejo para que asigne el valor cero a cada componente (parte real e imaginaria) del objeto que se construya. Complejo::Complejo() // Constructor por Defecto : real(0.0), imag(0.0) { } 11.1.4. Ejemplo Por ejemplo, el TAD n´umero complejo representa el siguiente concepto matem´atico de n´umero complejo: Un n´umero complejo representa un punto en el plano complejo, compuesto por dos componentes que representan la parte real y la parte imaginaria del n´umero (abcisa y ordenada respectivamente en el plano cartesiano), al cual se le pueden aplicar las ope- raciones de suma, resta, multiplicaci´on y divisi´on, as´ı como la comparaci´on de igualdad. Definici´on //- fichero: complejos.hpp ------------------------------------------ #ifndef _complejos_hpp_ #define _complejos_hpp_ namespace umalcc { //---------------------------------- const double ERROR_PRECISION = 1e-6 ; //---------------------------------- class Complejo { public: //---------------------------------------------------------- //-- M´etodos P´ublicos -------------------------------------- //---------------------------------------------------------- Complejo() ; // Constructor por Defecto //---------------------------- double parte_real() const ; // devuelve la parte real del numero complejo //---------------------------- double parte_imag() const ; // devuelve la parte imaginaria del numero complejo //---------------------------- void sumar(const Complejo& a, const Complejo& b) ; // asigna al numero complejo (actual) el resultado de // sumar los numeros complejos (a) y (b). Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 112. 112 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS //---------------------------- void restar(const Complejo& a, const Complejo& b) ; // asigna al numero complejo (actual) el resultado de // restar los numeros complejos (a) y (b). //---------------------------- void multiplicar(const Complejo& a, const Complejo& b) ; // asigna al numero complejo (actual) el resultado de // multiplicar los numeros complejos (a) y (b). //---------------------------- void dividir(const Complejo& a, const Complejo& b) ; // asigna al numero complejo (actual) el resultado de // dividir los numeros complejos (a) y (b). //---------------------------- bool igual(const Complejo& b) const ; // Devuelve true si el numero complejo (actual) es // igual al numero complejo (b) //---------------------------- void escribir() const ; // muestra en pantalla el numero complejo (actual) //---------------------------- void leer() ; // lee de teclado el valor del numero complejo (actual). // lee la parte real y la parte imaginaria del numero //------------------------------ private: //---------------------------------------------------------- //-- Atributos Privados ------------------------------------ //---------------------------------------------------------- double real ; // parte real del numero complejo double imag ; // parte imaginaria del numero complejo //---------------------------------------------------------- } ; } #endif //- fin: complejos.hpp ---------------------------------------------- Implementaci´on //- fichero: complejos.cpp ------------------------------------------ #include "complejos.hpp" #include <iostream> using namespace std ; using namespace umalcc ; //------------------------------------------------------------------------------ // Espacio de nombres anonimo. Es una parte privada de la // implementacion. No es accesible desde fuera del modulo //------------------------------------------------------------------------------ namespace { //---------------------------------------------------------- //-- Subprogramas Auxiliares ------------------------------- //---------------------------------------------------------- // cuadrado de un numero (a^2) inline double sq(double a) { return a*a ; } //------------------------- // Valor absoluto de un numero inline double abs(double a) Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 113. 11.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 113 { return (a >= 0) ? a : -a ; } //------------------------- // Dos numeros reales son iguales si la distancia que los // separa es lo suficientemente pequenya inline bool iguales(double a, double b) { return abs(a-b) <= ERROR_PRECISION ; } } //------------------------------------------------------------------------------ // Espacio de nombres umalcc. // Aqui reside la implementacion de la parte publica del modulo //------------------------------------------------------------------------------ namespace umalcc { //---------------------------------------------------------- //-- M´etodos P´ublicos -------------------------------------- //---------------------------------------------------------- Complejo::Complejo() // Constructor por Defecto : real(0.0), imag(0.0) { } //---------------------------- // devuelve la parte real del numero complejo double Complejo::parte_real() const { return real ; } //---------------------------- // devuelve la parte imaginaria del numero complejo double Complejo::parte_imag() const { return imag ; } //---------------------------- // asigna al numero complejo (actual) el resultado de // sumar los numeros complejos (a) y (b). void Complejo::sumar(const Complejo& a, const Complejo& b) { real = a.real + b.real ; imag = a.imag + b.imag ; } //---------------------------- // asigna al numero complejo (actual) el resultado de // restar los numeros complejos (a) y (b). void Complejo::restar(const Complejo& a, const Complejo& b) { real = a.real - b.real ; imag = a.imag - b.imag ; } //---------------------------- // asigna al numero complejo (actual) el resultado de // multiplicar los numeros complejos (a) y (b). void Complejo::multiplicar(const Complejo& a, const Complejo& b) { real = (a.real * b.real) - (a.imag * b.imag) ; imag = (a.real * b.imag) + (a.imag * b.real) ; } //---------------------------- // asigna al numero complejo (actual) el resultado de Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 114. 114 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS // dividir los numeros complejos (a) y (b). void Complejo::dividir(const Complejo& a, const Complejo& b) { double divisor = sq(b.real) + sq(b.imag) ; if (iguales(0.0, divisor)) { real = 0.0 ; imag = 0.0 ; } else { real = ((a.real * b.real) + (a.imag * b.imag)) / divisor ; imag = ((a.imag * b.real) - (a.real * b.imag)) / divisor ; } } //---------------------------- // Devuelve true si el numero complejo (actual) es // igual al numero complejo (b) bool Complejo::igual(const Complejo& b) const { return iguales(real, b.real) && iguales(imag, b.imag) ; } //---------------------------- // muestra en pantalla el numero complejo (actual) void Complejo::escribir() const { cout << "{ " << real << ", " << imag << " }" ; } //---------------------------- // lee de teclado el valor del numero complejo (actual). // lee la parte real y la parte imaginaria del numero void Complejo::leer() { cin >> real >> imag ; } //---------------------------- } //- fin: complejos.cpp ---------------------------------------------- Utilizaci´on //- fichero: main.cpp ----------------------------------------------- #include <iostream> #include "complejos.hpp" using namespace std ; using namespace umalcc ; //------------------------------------ void leer(Complejo& c) { cout << "Introduzca un numero complejo { real img }: " ; c.leer() ; } //------------------------------------ void prueba_suma(const Complejo& c1, const Complejo& c2) { Complejo c0 ; c0.sumar(c1, c2) ; c1.escribir() ; cout <<" + " ; c2.escribir() ; cout <<" = " ; c0.escribir() ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 115. 11.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 115 cout << endl ; Complejo aux ; aux.restar(c0, c2) ; if ( ! c1.igual(aux)) { cout << "Error en operaciones de suma/resta"<< endl ; } } //------------------------------------ void prueba_resta(const Complejo& c1, const Complejo& c2) { Complejo c0 ; c0.restar(c1, c2) ; c1.escribir() ; cout <<" - " ; c2.escribir() ; cout <<" = " ; c0.escribir() ; cout << endl ; Complejo aux ; aux.sumar(c0, c2) ; if ( ! c1.igual(aux)) { cout << "Error en operaciones de suma/resta"<< endl ; } } //------------------------------------ void prueba_mult(const Complejo& c1, const Complejo& c2) { Complejo c0 ; c0.multiplicar(c1, c2) ; c1.escribir() ; cout <<" * " ; c2.escribir() ; cout <<" = " ; c0.escribir() ; cout << endl ; Complejo aux ; aux.dividir(c0, c2) ; if ( ! c1.igual(aux)) { cout << "Error en operaciones de mult/div"<< endl ; } } //------------------------------------ void prueba_div(const Complejo& c1, const Complejo& c2) { Complejo c0 ; c0.dividir(c1, c2) ; c1.escribir() ; cout <<" / " ; c2.escribir() ; cout <<" = " ; c0.escribir() ; cout << endl ; Complejo aux ; aux.multiplicar(c0, c2) ; if ( ! c1.igual(aux)) { cout << "Error en operaciones de mult/div"<< endl ; } } //------------------------------------ Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 116. 116 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS int main() { Complejo c1, c2 ; leer(c1) ; leer(c2) ; //-------------------------------- prueba_suma(c1, c2) ; prueba_resta(c1, c2) ; prueba_mult(c1, c2) ; prueba_div(c1, c2) ; //-------------------------------- } //- fin: main.cpp --------------------------------------------------- 11.2. Tipos Abstractos de Datos en C++: M´as sobre Clases Constantes de ´Ambito de Clase Las constantes de ´ambito de clase se definen especificando los cualificadores static const, seguidos por el tipo, el identificador y el valor de la constante. Estas constantes ser´an comunes y accesibles a todas las instancias (objetos) de la clase. Por ejemplo, para definir la constante MAX con un valor de 256 en la zona privada de la clase: class ListaInt { public: // ... private: static const int MAX = 256 ; // ... } ; Usualmente las constantes se definen en la zona privada de la clase, por lo que usualmente s´olo ser´an accesibles internamente desde dentro de la clase. Sin embargo, en algunas situaciones puede ser conveniente definir la constante en la zona p´ublica de la clase, entonces en este caso la constante podr´a ser accedida desde el exterior de la clase, y ser´a utilizada mediante cualificaci´on expl´ıcita utilizando el identificador de la clase. Por ejemplo: class ListaInt { public: static const int MAX = 256 ; // ... private: // ... } ; // ... int main() { int x = ListaInt::MAX ; // ... } Tipos de ´Ambito de Clase Tambi´en se pueden definir tipos internos de ´ambito de clase de igual forma a como se hace externamente a la clase, pero en este caso su ´ambito de visibilidad estar´a restringido a la clase donde se defina. Estos tipos ser´an ´utiles en la definici´on de los atributos miembros de la clase, o para definir elementos auxiliares en la implementaci´on del tipo abstracto de datos. Por ejemplo, para definir un tipo Datos como un array de 256 n´umeros enteros: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 117. 11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 117 #include <tr1/array> // ... class ListaInt { public: // ... private: static const int MAX = 256 ; typedef tr1::std::array<int, MAX> Datos ; struct Elemento { // ... } ; // ... } ; Usualmente los tipos se definen en la zona privada de la clase, por lo que usualmente s´olo ser´an accesibles internamente desde dentro de la clase. Sin embargo, en algunas situaciones puede ser conveniente definir el tipo en la zona p´ublica de la clase, entonces en este caso el tipo podr´a ser accedido desde el exterior de la clase, y ser´a utilizado mediante cualificaci´on expl´ıcita utilizando el identificador de la clase. Por ejemplo: #include <tr1/array> // ... class ListaInt { public: static const int MAX = 256 ; typedef tr1::std::array<int, MAX> Datos ; // ... private: // ... } ; // ... int main() { ListaInt::Datos d ; // ... } Constructores Espec´ıficos Los constructores de una clase permiten construir e inicializar un objeto. Anteriormente se ha explicado el constructor por defecto, el cual se invoca cuando se crea un determinado objeto, y no se especifica que tipo de construcci´on se debe realizar. C++ permite, adem´as, la definici´on e imple- mentaci´on de tantos constructores espec´ıficos como sean necesarios, para ello, se debe especificar en la lista de par´ametros, aquellos que sean necesarios para poder construir el objeto adecuada- mente en cada circunstancia espec´ıfica, de tal forma que ser´a la lista de par´ametros formales la que permita discriminar que constructor ser´a invocado dependiendo de los par´ametros actuales utilizados en la invocaci´on al constructor. Por ejemplo, podemos definir un constructor espec´ıfico para que reciba dos n´umeros reales como par´ametros (parte real e imaginaria respectivamente de un n´umero complejo), los cuales ser´an utilizados para dar los valores iniciales a cada atributo correspondiente del objeto que se construya. As´ı, su definici´on podr´ıa ser: class Complejo { public: Complejo(double p_real, double p_imag) ; // Constructor Espec´ıfico } ; A continuaci´on se puede ver como ser´ıa la implementaci´on de este constructor espec´ıfico: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 118. 118 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS Complejo::Complejo(double p_real, double p_imag) // Constructor espec´ıfico : real(p_real), imag(p_imag) { } Finalmente, a continuaci´on podemos ver un ejemplo de como ser´ıa una posible invocaci´on a dicho constructor espec´ıfico (para c2), junto a una invocaci´on al constructor por defecto (para c1): real: imag: 0.0 0.0 c1 real: imag: 2.5 7.3 c2 //- fichero: main.cpp ----------------------------------------------- #include <iostream> #include "complejos.hpp" using namespace std ; using namespace umalcc ; int main() { Complejo c1 ; Complejo c2(2.5, 7.3) ; // ... } //- fin: main.cpp --------------------------------------------------- Constructor por Defecto Como se explic´o anteriormente (v´ease 11.1.1 y 11.1.3), el constructor por defecto es el mecan- ismo por defecto utilizado para construir objetos de este tipo cuando no se especifica ninguna forma expl´ıcita de construcci´on. As´ı, ser´a invocado autom´aticamente cuando se deba construir un determinado objeto, sin especificar expl´ıcitamente el tipo de construcci´on requerido, en el momen- to en que sea necesaria dicha construcci´on, por ejemplo cuando el flujo de ejecuci´on alcanza la declaraci´on de una variable de dicho tipo (v´ease 11.1.2). El constructor por defecto es un m´etodo especial de la clase, ya que si el programador no define ning´un constructor para una determinada clase, entonces el compilador generar´a e im- plementar´a autom´aticamente dicho constructor con el comportamiento por defecto de invocar autom´aticamente al constructor por defecto para cada atributo de tipo compuesto miembro de la clase. N´otese, sin embargo, que en el caso atributos de tipo simple, la implementaci´on autom´atica del compilador los dejar´a sin inicializar. No obstante, el programador puede definir el constructor por defecto para una determinada clase cuando el comportamiento generado autom´aticamente por el compilador no sea el deseado. Para ello, la definici´on del constructor por defecto se corresponde con la definici´on de un constructor que no recibe ning´un par´ametro, y la implementaci´on depender´a de las acciones necesarias para inicializar por defecto el estado interno del objeto que se est´a creando. Por ejemplo, para la clase Complejo: class Complejo { public: Complejo() ; // Constructor por Defecto } ; A continuaci´on se puede ver como ser´ıa la implementaci´on del constructor por defecto: Complejo::Complejo() // Constructor por Defecto : real(0.0), imag(0.0) { } Otra posible implementaci´on podr´ıa ser la siguiente, que invoca expl´ıcitamente al constructor por defecto para cada atributo miembro de la clase (que en este caso se incializar´a a cero): Complejo::Complejo() // Constructor por Defecto : real(), imag() { } Finalmente, a continuaci´on podemos ver un ejemplo de como ser´ıa una invocaci´on a dicho cons- tructor por defecto: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 119. 11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 119 real: imag: 0.0 0.0 c1 real: imag: 0.0 0.0 c2 //- fichero: main.cpp ----------------------------------------------- #include <iostream> #include "complejos.hpp" using namespace std ; using namespace umalcc ; int main() { Complejo c1, c2 ; // ... } //- fin: main.cpp --------------------------------------------------- Constructor de Copia El constructor de copia es el constructor que permite inicializar un determinado objeto como una copia de otro objeto de su misma clase. As´ı, se invoca automaticamente al inicializar el contenido de un objeto con el valor de otro objeto de su misma clase, y tambi´en es invocado autom´aticamente cuando un objeto de dicho tipo se pasa como par´ametro por valor a subprogramas, aunque esto ´ultimo, como se ha explicado previamente, est´a desaconsejado, ya que lo usual es pasar los tipos compuestos por referencia o por referencia constante. El constructor de copia es un m´etodo especial de la clase, ya que si el programador no define dicho constructor de copia para una determinada clase, entonces el compilador generar´a e im- plementar´a autom´aticamente dicho constructor de copia con el comportamiento por defecto de invocar autom´aticamente al constructor de copia para cada atributo miembro de la clase, en este caso, tanto para atributos de tipo simple como de tipo compuesto. No obstante, el programador puede definir el constructor de copia para una determinada clase cuando el comportamiento generado autom´aticamente por el compilador no sea el deseado. Para ello, la definici´on del constructor de copia se corresponde con la definici´on de un constructor que recibe como ´unico par´ametro por referencia constante un objeto del mismo tipo que la clase del constructor, y la implementaci´on depender´a de las acciones necesarias para copiar el estado interno del objeto recibido como par´ametro al objeto que se est´a creando. Por ejemplo, para la clase Complejo: class Complejo { public: Complejo(const Complejo& c) ; // Constructor de Copia } ; y su implementaci´on podr´ıa ser la siguiente, que en este caso coincide con la implementaci´on que generar´ıa automaticamente el compilador en caso de que no fuese implementado por el progra- mador: Complejo::Complejo(const Complejo& o) // Constructor de Copia : real(o.real), imag(o.imag) { } // Implementaci´on autom´atica Finalmente, a continuaci´on podemos ver un ejemplo de como ser´ıa una invocaci´on al constructor de copia (para c3 y c4), junto a una invocaci´on a un constructor espec´ıfico (para c2) y una invocaci´on al constructor por defecto (para c1), as´ı como la construcci´on por copia (para c5 y c6) de objetos construidos invocando expl´ıcitamente a los constructores adecuados: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 120. 120 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS real: imag: 0.0 0.0 c1 2.5 7.3 c2 0.0 0.0 c3 2.5 7.3 c4 0.0 0.0 c5 3.1 4.2 c6 //- fichero: main.cpp ----------------------------------------------- #include <iostream> #include "complejos.hpp" using namespace std ; using namespace umalcc ; int main() { Complejo c1 ; // Construcci´on por defecto Complejo c2(2.5, 7.3) ; // Construcci´on espec´ıfica Complejo c3(c1) ; // Construcci´on de copia (de c1) Complejo c4 = c2 ; // Construcci´on de copia (de c2) Complejo c5 = Complejo(); // Construcci´on de copia de Complejo por Defecto Complejo c6 = Complejo(3.1, 4.2); // Construcci´on de copia de Complejo Espec´ıfico // ... } //- fin: main.cpp --------------------------------------------------- Destructor El destructor de una clase ser´a invocado autom´aticamente (sin par´ametros actuales) para una determinada instancia (objeto) de esta clase cuando dicho objeto deba ser destruido, normalmente ´esto suceder´a cuando el flujo de ejecuci´on del programa salga del ´ambito de visibilidad de dicho objeto (v´ease 11.1.2). El destructor es un m´etodo especial de la clase, ya que si el programador no define dicho de- structor para una determinada clase, entonces el compilador generar´a e implementar´a autom´atica- mente dicho destructor con el comportamiento por defecto de invocar autom´aticamente al destruc- tor para cada atributo de tipo compuesto miembro de la clase. No obstante, el programador puede definir el destructor para una determinada clase cuando el comportamiento generado autom´aticamente por el compilador no sea el deseado. Para ello, el destructor de la clase se define mediante el s´ımbolo ~ seguido del identificador de la clase y una lista de par´ametros vac´ıa, y la implementaci´on depender´a de las acciones necesarias para destruir y liberar los recursos asociados al estado interno del objeto que se est´a destruyendo. Posteriormente, el destructor invoca autom´aticamente a los destructores de los atributos miembros del objeto para que ´estos sean destruidos. Por ejemplo, para la clase Complejo: class Complejo { public: ~Complejo() ; // Destructor } ; y su implementaci´on podr´ıa ser la siguiente, que en este caso coincide con la implementaci´on que generar´ıa automaticamente el compilador en caso de que no fuese implementado por el progra- mador: Complejo::~Complejo() { } // Destructor: Implementaci´on autom´atica Finalmente, a continuaci´on podemos ver un ejemplo de como se invoca autom´aticamente al de- structor de los objetos cuando termina su tiempo de vida (para c1, c2, c3 y c4): Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 121. 11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 121 real: imag: 0.0 0.0 c1 X real: imag: 2.5 7.3 c2 X real: imag: 0.0 0.0 c3 X real: imag: 2.5 7.3 c4 X //- fichero: main.cpp ----------------------------------------------- #include <iostream> #include "complejos.hpp" using namespace std ; using namespace umalcc ; int main() { Complejo c1 ; // Construcci´on por defecto Complejo c2(2.5, 7.3) ; // Construcci´on espec´ıfica Complejo c3(c1) ; // Construcci´on de copia (de c1) Complejo c4 = c2 ; // Construcci´on de copia (de c2) // ... } // Destrucci´on autom´atica de c4, c3, c2 y c1 //- fin: main.cpp --------------------------------------------------- Operador de Asignaci´on El operador de asignaci´on define como se realiza la asignaci´on (=) para objetos de esta clase. No se debe confundir el operador de asignaci´on con el constructor de copia, ya que el constructor de copia construye un nuevo objeto que no tiene previamente ning´un valor, mientras que en el caso del operador de asignaci´on, el objeto ya tiene previamente un valor que deber´a ser sustituido por el nuevo valor. Este valor previo deber´a, en ocasiones, ser destruido antes de realizar la asignaci´on del nuevo valor. El operador de asignaci´on (=) es un m´etodo especial de la clase, ya que si el programador no define dicho operador de asignaci´on para una determinada clase, entonces el compilador generar´a e implementar´a autom´aticamente dicho operador de asignaci´on con el comportamiento por defecto de invocar autom´aticamente al operador de asignaci´on para cada atributo miembro de la clase, tanto para atributos de tipo simple como de tipo compuesto. No obstante, el programador puede definir el operador de asignaci´on para una determinada clase cuando el comportamiento generado autom´aticamente por el compilador no sea el deseado. Para ello, la definici´on del operador de asignaci´on se corresponde con la definici´on de un oper- ador = que recibe como ´unico par´ametro por referencia constante un objeto del mismo tipo que la clase del constructor, devuelve una referencia al propio objeto que recibe la asignaci´on, y la implementaci´on depender´a de las acciones necesarias para destruir el estado interno del objeto que recibe la asignaci´on y para asignar el estado interno del objeto recibido como par´ametro al objeto que se est´a creando. Por ejemplo, para la clase Complejo: class Complejo { public: Complejo& operator=(const Complejo& o) ; // Operador de Asignaci´on } ; y su implementaci´on podr´ıa ser la siguiente, que en este caso coincide con la implementaci´on que generar´ıa automaticamente el compilador en caso de que no fuese implementado por el progra- mador: Complejo& Complejo::operator=(const Complejo& o) // Operador de Asignacion { // Implementaci´on autom´atica real = o.real ; imag = o.imag ; return *this ; } El operador de asignaci´on debe devolver el objeto actual (return *this) sobre el que recae la asignaci´on. Finalmente, a continuaci´on podemos ver un ejemplo de como ser´ıa una invocaci´on al operador de asignaci´on (para c3 y c4), junto a una invocaci´on a un constructor espec´ıfico (para c2) y una invocaci´on al constructor por defecto (para c1), as´ı como la asignaci´on (para c5 y c6) de objetos construidos invocando expl´ıcitamente a los constructores adecuados: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 122. 122 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS real: imag: 0.0 0.0 c1 2.5 7.3 c2 0.0 0.0 c3 2.5 7.3 c4 0.0 0.0 c5 3.1 4.2 c6 //- fichero: main.cpp ----------------------------------------------- #include <iostream> #include "complejos.hpp" using namespace std ; using namespace umalcc ; int main() { Complejo c1, c3, c4, c5, c6; // Construcci´on por defecto de c1, c3, c4 Complejo c2(2.5, 7.3) ; // Construcci´on espec´ıfica c3 = c1 ; // Asignaci´on de c1 a c3 c4 = c2 ; // Asignaci´on de c2 a c4 c5 = Complejo(); // Asignaci´on de Complejo por Defecto c6 = Complejo(3.1, 4.2); // Asignaci´on de Complejo Espec´ıfico // ... } //- fin: main.cpp --------------------------------------------------- Hay situaciones en las que los objetos que se asignan tienen representaciones internas complejas, y en estos casos puede ser necesario destruir el estado interno del objeto que recibe la asignaci´on antes de asignar el nuevo valor. En este caso, es conveniente comprobar que no se est´a produciendo una auto-asignaci´on del mismo objeto (x = x), ya que en este caso se destruir´ıa la representaci´on interna del objeto antes de haberla asignado, con los errores que ello trae asociado. Por lo tanto, suele ser habitual que el operador de asignaci´on implemente una condici´on para evitar la asignaci´on en el caso de que se produzca una auto-asignaci´on, de la siguiente forma: Complejo& Complejo::operator=(const Complejo& o) // Operador de Asignacion { if (this != &o) { // destruir el valor anterior (en este caso no es necesario) real = o.real ; imag = o.imag ; } return *this ; } As´ı, this representa la direcci´on en memoria del objeto que recibe la asignaci´on, y &o representa la direcci´on en memoria del objeto que se recibe como par´ametro. Si ambas direcciones son diferentes, entonces significa que son variables diferentes y se puede realizar la asignaci´on. 11.2.1. Ejemplo Veamos un Tipo Abstracto de Datos Lista de enteros, la cual permite almacenar una secuencia de n´umero enteros, permitiendo insertar, eliminar, acceder y modificar elementos seg´un la posici´on que ocupen en la secuencia de n´umeros. Definici´on //- fichero: lista.hpp ------------------------------------------ #ifndef _lista_hpp_ #define _lista_hpp_ #include <tr1/array> namespace umalcc { class ListaInt { public: //---------------------------------------------------------- //-- M´etodos P´ublicos -------------------------------------- //---------------------------------------------------------- // ~ListaInt() ; // Destructor Autom´atico //------------------------------ Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 123. 11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 123 ListaInt() ; ListaInt(const ListaInt& o) ; ListaInt& operator = (const ListaInt& o) ; //------------------------------ bool llena() const ; int size() const ; void clear() ; //------------------------------ void insertar(int pos, int dato) ; // PRECOND: ( ! llena() && pos >= 0 && pos <= size()) void eliminar(int pos) ; // PRECOND: (pos >= 0 && pos < size()) //------------------------------ int acceder(int pos) const ; // PRECOND: (pos >= 0 && pos < size()) void modificar(int pos, int dato); // PRECOND: (pos >= 0 && pos < size()) //---------------------------------------------------------- private: //---------------------------------------------------------- //-- Ctes y Tipos Privados --------------------------------- //---------------------------------------------------------- static const int MAX = 100; typedef std::tr1::array<int, MAX> Datos; //---------------------------------------------------------- //-- Metodos Privados -------------------------------------- //---------------------------------------------------------- void abrir_hueco(int pos) ; void cerrar_hueco(int pos) ; //---------------------------------------------------------- //-- Atributos Privados ------------------------------------ //---------------------------------------------------------- int sz; // numero de elementos de la lista Datos v; // contiene los elementos de la lista //---------------------------------------------------------- }; } #endif //- fin: lista.hpp ---------------------------------------------- Implementaci´on //- fichero: lista.cpp ------------------------------------------ #include "lista.hpp" #include <cassert> namespace umalcc { //---------------------------------------------------------- //-- M´etodos P´ublicos -------------------------------------- //---------------------------------------------------------- // ListaInt::~ListaInt() { } // Destructor Autom´atico //---------------------------------- ListaInt::ListaInt() : sz(0), v() { } // Constructor por Defecto //---------------------------------- ListaInt::ListaInt(const ListaInt& o) // Constructor de Copia : sz(o.sz), v() { for (int i = 0; i < sz; ++i) { v[i] = o.v[i] ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 124. 124 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS } //---------------------------------- ListaInt& ListaInt::operator = (const ListaInt& o) // Op. de Asignaci´on { if (this != &o) { sz = o.sz ; for (int i = 0; i < sz; ++i) { v[i] = o.v[i] ; } } return *this ; } //---------------------------------- bool ListaInt::llena() const { return sz == int(v.size()); } //---------------------------------- int ListaInt::size() const { return sz ; } //---------------------------------- void ListaInt::clear() { sz = 0 ; } //---------------------------------- void ListaInt::insertar(int pos, int dato) { assert( ! llena() && pos >= 0 && pos <= size()) ; abrir_hueco(pos) ; v[pos] = dato ; } //---------------------------------- void ListaInt::eliminar(int pos) { assert(pos >= 0 && pos < size()) ; cerrar_hueco(pos) ; } //---------------------------------- int ListaInt::acceder(int pos) const { assert(pos >= 0 && pos < size()) ; return v[pos] ; } //---------------------------------- void ListaInt::modificar(int pos, int dato) { assert(pos >= 0 && pos < size()) ; v[pos] = dato; } //---------------------------------------------------------- //-- Metodos Privados -------------------------------------- //---------------------------------------------------------- void ListaInt::abrir_hueco(int pos) { assert(sz < int(v.size())) ; for (int i = sz; i > pos; --i) { Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 125. 11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 125 v[i] = v[i-1]; } ++sz; // Ahora hay un elemento m´as } //---------------------------------- void ListaInt::cerrar_hueco(int pos) { assert(sz > 0) ; --sz; // Ahora hay un elemento menos for (int i = pos; i < sz; ++i) { v[i] = v[i+1]; } } //---------------------------------- } //- fin: lista.cpp ---------------------------------------------- Utilizaci´on //- fichero: main.cpp ----------------------------------------------- #include <iostream> #include <cctype> #include <cassert> #include "lista.hpp" using namespace std ; using namespace umalcc ; //------------------------------------------------------------------ void leer_pos(int& pos, int limite) { assert(limite > 0); do { cout << "Introduzca posicion ( < " << limite << " ): " ; cin >> pos; } while (pos < 0 || pos >= limite); } //--------------------------------- void leer_dato(int& dato) { cout << "Introduzca un dato: " ; cin >> dato; } //--------------------------------- void leer(ListaInt& lista) { int dato ; lista.clear() ; cout << "Introduzca datos (0 -> FIN): " << endl ; cin >> dato ; while ((dato != 0)&&( ! lista.llena())) { lista.insertar(lista.size(), dato) ; cin >> dato ; } } //--------------------------------- void escribir(const ListaInt& lista) { cout << "Lista: " ; for (int i = 0 ; i < lista.size() ; ++i) { cout << lista.acceder(i) << " " ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 126. 126 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS } cout << endl ; } //--------------------------------- void prueba_asg(const ListaInt& lista) { cout << "Constructor de Copia" << endl ; ListaInt lst(lista) ; escribir(lst) ; cout << "Operador de Asignacion" << endl ; lst = lista ; escribir(lst) ; } //------------------------------------------------------------------------- char menu() { char op ; cout << endl ; cout << "X. Fin" << endl ; cout << "A. Leer Lista" << endl ; cout << "B. Borrar Lista" << endl ; cout << "C. Insertar Posicion" << endl ; cout << "D. Eliminar Posicion" << endl ; cout << "E. Acceder Posicion" << endl ; cout << "F. Modificar Posicion" << endl ; cout << "G. Prueba Copia y Asignacion" << endl ; do { cout << endl << " Opcion: " ; cin >> op ; op = char(toupper(op)) ; } while (!((op == ’X’)||((op >= ’A’)&&(op <= ’G’)))) ; cout << endl ; return op ; } //------------------------------------------------------------------------- int main() { ListaInt lista ; int dato ; int pos ; char op = ’ ’ ; do { op = menu() ; switch (op) { case ’A’: leer(lista) ; escribir(lista) ; break ; case ’B’: lista.clear() ; escribir(lista) ; break ; case ’C’: if (lista.llena()) { cout << "Error: Lista llena" << endl ; } else { leer_pos(pos, lista.size()+1) ; leer_dato(dato) ; lista.insertar(pos, dato) ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 127. 11.2. TIPOS ABSTRACTOS DE DATOS EN C++: M ´AS SOBRE CLASES 127 escribir(lista) ; } break ; case ’D’: if (lista.size() == 0) { cout << "Error: lista vacia" << endl ; } else { leer_pos(pos, lista.size()) ; lista.eliminar(pos) ; escribir(lista) ; } break ; case ’E’: if (lista.size() == 0) { cout << "Error: lista vacia" << endl ; } else { leer_pos(pos, lista.size()) ; cout << "Lista[" << pos << "]: " << lista.acceder(pos) << endl ; escribir(lista) ; } break ; case ’F’: if (lista.size() == 0) { cout << "Error: lista vacia" << endl ; } else { leer_pos(pos, lista.size()) ; leer_dato(dato) ; lista.modificar(pos, dato) ; escribir(lista) ; } break ; case ’G’: prueba_asg(lista) ; break ; } } while (op != ’X’) ; } //- fin: main.cpp --------------------------------------------------- Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 128. 128 CAP´ITULO 11. TIPOS ABSTRACTOS DE DATOS Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 129. Cap´ıtulo 12 Introducci´on a la Programaci´on Gen´erica. Plantillas El lenguaje de programaci´on C++ proporciona soporte a la programaci´on gen´erica mediante las plantillas (“templates” en ingl´es). Las plantillas proporcionan un mecanismo eficaz para definir c´odigo (constantes, tipos y subprogramas) gen´ericos parametrizados, que puedan ser instanciados en “tiempo de compilaci´on”. Estos par´ametros gen´ericos de las plantillas podr´an ser instanciados con tipos y valores constantes concretos especificados en tiempo de compilaci´on. Las definiciones gen´ericas deber´an, por lo general, estar visibles en el lugar donde sean ins- tanciadas, por lo que en el caso de definirse en m´odulos diferentes, deber´an estar definidas en los ficheros de encabezamiento, para que puedan ser incluidas por todos aquellos m´odulos que las necesiten. La definici´on de plantillas, tanto de subprogramas como de tipos comienza con la palabra reser- vada template, seguida entre delimitadores < ... > por los par´ametros gen´ericos de la definici´on. Estos par´ametros gen´ericos pueden ser tanto tipos (precedidos por la palabra reservada typename), como constantes de tipos integrales (char, short, int, unsigned, long) o de tipos gen´ericos parametrizados con anterioridad (que deben ser instanciados a tipos integrales). 12.1. Subprogramas Gen´ericos Los subprogramas gen´ericos son ´utiles cuando definen procesamientos gen´ericos que son inde- pendientes de los tipos concretos sobre los que se aplican. En este caso, simplemente se define el subprograma utilizando tanto los tipos como constantes gen´ericas mediante sus identificadores declarados en la directiva template <...>. Posteriormente, cuando se utilicen estos subprogramas gen´ericos, tanto los tipos como constantes gen´ericas ser´an instanciadas seg´un los tipos y constantes actuales utilizados en la invocaci´on a dichos subprogramas. Veamos algunos ejemplos de definici´on de subprogramas gen´ericos: template <typename Tipo> inline Tipo maximo(const Tipo& x, const Tipo& y) { Tipo max; if (x > y) { max = x ; } else { max = y ; } return max ; } template <typename Tipo> 129
  • 130. 130 CAP´ITULO 12. INTRODUCCI ´ON A LA PROGRAMACI ´ON GEN´ERICA. PLANTILLAS inline void intercambio(Tipo& x, Tipo& y) { Tipo aux = x ; x = y ; y = aux ; } int main() { int x = 4 ; int y = maximo(x, 8) ; intercambio(x, y) ; double a = 7.5 ; double b = maximo(a, 12.0) ; intercambio(a, b) ; double c = maximo(a, 12) ; // Error: maximo(double, int) no esta definido } En el ejemplo se puede ver que los par´ametros de entrada a los subprogramas se pasan por referencia (constante o variable), ya que al ser un tipo gen´erico podr´ıa ser tanto un tipo simple como un tipo estructurado. Tambi´en puede apreciarse que la instanciaci´on de subprogramas gen´ericos a tipos concretos se realiza autom´aticamente a partir de la invocaci´on a los mismos, de tal forma que la instanciaci´on de los par´ametros gen´ericos se realiza por deducci´on a partir del tipo de los par´ametros especificados en la invocaci´on a los subprogramas. Sin embargo, hay situaciones donde los par´ametros gen´ericos no pueden ser deducidos de la propia invocaci´on al subprograma. En este caso, los par´ametros gen´ericos involucrados deben ser especificados expl´ıcitamente en la llamada. int main() { double a = 7.5 ; double b = maximo(a, 12.0) ; double c = maximo<double>(a, 12) ; } El siguiente ejemplo de subprograma gen´erico muestra la utilizaci´on de par´ametros gen´ericos constantes (de tipo integral). #include <iostream> #include <string> #include <tr1/array> using namespace std ; using namespace std::tr1 ; //------------------------------------- template <typename TipoBase, unsigned SIZE> void asignar(array<TipoBase, SIZE>& v, const TipoBase& x) { for (int i = 0 ; i < int(v.size()) ; ++i) { v[i] = x ; } } //------------------------------------- // Requiere que el TipoBase se pueda escribir con << template <typename TipoBase, unsigned SIZE> void escribir(const array<TipoBase, SIZE>& v) { Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 131. 12.1. SUBPROGRAMAS GEN´ERICOS 131 for (int i = 0 ; i < int(v.size()) ; ++i) { cout << v[i] << " "; } cout << endl ; } //------------------------------------- typedef array<int, 5> AInt ; void prueba1() { AInt a = {{ 1, 2, 3, 4, 5 }}; escribir(a); asignar(a, 5) ; escribir(a); } //------------------------------------- typedef array<string, 3> APers ; void prueba2() { APers a = {{ "pepe", "juan", "mar´ıa" }} ; escribir(a); asignar(a, "lola") ; escribir(a); } //------------------------------------- Errores de Instanciaci´on de Par´ametros Gen´ericos En el caso de que la definici´on de un determinado subprograma sea incorrecta para una deter- minada instanciaci´on concreta de los par´ametros gen´ericos, se producir´a un error de compilaci´on indicando el tipo de error. Por ejemplo, si compilamos el siguiente c´odigo, se produce un error de compilaci´on, ya que el tipo Persona no tiene definido el operador de salida (<<). #include <iostream> #include <string> #include <tr1/array> using namespace std ; using namespace std::tr1 ; //------------------------------------- // Requiere que el TipoBase se pueda escribir con << template <typename TipoBase, unsigned SIZE> void escribir(const array<TipoBase, SIZE>& v) { for (int i = 0 ; i < int(v.size()) ; ++i) { cout << v[i] << " "; // l´ınea 12 } cout << endl ; } //------------------------------------- struct Persona { string nombre ; string telefono ; } ; typedef array<Persona, 4> APersona ; void prueba3() { APersona a = {{ { "pepe", "00" }, { "juan", "11" }, { "mar´ıa", "22" }, Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 132. 132 CAP´ITULO 12. INTRODUCCI ´ON A LA PROGRAMACI ´ON GEN´ERICA. PLANTILLAS { "carmen", "33" } }} ; escribir(a); // l´ınea 30 } //------------------------------------- Produce el siguiente mensaje de error de compilaci´on (con GNU GCC): main.cpp: In function ‘void escribir(const std::tr1::array<_Tp, _Nm>&) [with TipoBase = Persona, unsigned int SIZE = 4u]’: main.cpp:30: instantiated from here main.cpp:12: error: no match for ‘operator<<’ in ‘...’ [......] Cuando se trabaja con plantillas en C++, hay que tener presente que, a veces, los mensajes de error pueden ser bastante complicados de interpretar en el caso de errores producidos por instanciaciones de par´ametros gen´ericos. En estos casos, el primer mensaje de error suele ser el m´as ´util para guiarnos en su correcci´on. 12.2. Tipos Abstractos de Datos Gen´ericos Las plantillas tambi´en pueden ser utilizadas para la definici´on de tipos gen´ericos, tanto registros como clases gen´ericas (TADs gen´ericos). Como se explic´o al comienzo del cap´ıtulo, las definiciones gen´ericas deben, por lo general, estar visibles en el lugar donde sean instanciadas, y por lo tanto, en el caso de tipos abstractos gen´ericos tanto la definici´on como la implementaci´on se realizar´a completamente dentro de los ficheros de encabezamiento. Adem´as, en esta secci´on introductoria a la programaci´on gen´erica s´olo veremos los tipos abstractos de datos gen´ericos definidos e implementados “en l´ınea”, y en su versi´on m´as simple. La definici´on de una clase gen´erica se realiza de forma similar a la definici´on de una clase no gen´erica, pero con algunas diferencias: Se precede la definici´on de la clase con la palabra reservada template seguida entre delim- itadores < ... > por los par´ametros gen´ericos de la definici´on. Estos par´ametros gen´ericos pueden ser tanto tipos (precedidos por la palabra reservada typename), como constantes de tipos integrales (char, short, int, unsigned, long) o de tipos gen´ericos parametrizados con anterioridad (que deben ser instanciados a tipos integrales). Los tipos y constantes gen´ericas de la plantilla pueden ser utilizados en la definici´on de la clase mediante sus identificadores declarados en la directiva template <...>. La implementaci´on de los m´etodos se debe realizar “en-l´ınea”, es decir, el cuerpo del m´etodo tambi´en se definir´a dentro de la definici´on de la clase, a diferencia de como se ha visto hasta ahora, en la cual los m´etodos se implementaban de forma independiente en un fichero de implementaci´on aparte. En la instanciaci´on de tipos abstractos gen´ericos, ser´a necesaria la instanciaci´on expl´ıcita de los par´ametros de los mismos, a diferencia de la instanciaci´on de subprogramas gen´ericos vista an- teriormente, en la cual los par´ametros gen´ericos se instancian autom´aticamente a trav´es de la invocaci´on a los subprogramas. Veamos algunos ejemplos de definici´on y utilizaci´on de tipos abstractos gen´ericos. Ejemplo 1 Consideremos el tipo abstracto gen´erico denominado Par, que almacena dos elementos de tipos gen´ericos, y proporciona m´etodos tanto para conocer sus valores, como para su modificaci´on. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 133. 12.2. TIPOS ABSTRACTOS DE DATOS GEN´ERICOS 133 //- par.hpp ------------------------------------------------------------- #ifndef _par_hpp_ #define _par_hpp_ namespace umalcc { template <typename Tipo_1, typename Tipo_2> class Par { public: //------------------------------ // Destructor Autom´atico // ~Par() { } //------------------------------ // Constructor Copia Autom´atico // Par(const Par& o) : elem_1(o.elem_1), elem_2(o.elem_2) { } //------------------------------ // Operador Asignaci´on Autom´atico // Par& operator = (const Par& o) // { // if (this != &o) { // elem_1 = o.elem_1; // elem_2 = o.elem_2; // } // return *this; // } //------------------------------ Par() : elem_1(), elem_2() { } //------------------------------ Par(const Tipo_1& valor_1, const Tipo_2& valor_2) : elem_1(valor_1), elem_2(valor_2) { } //------------------------------ Tipo_1 primero() const { // Devuelve el valor del primer elemento del par return elem_1; } //------------------------------ Tipo_2 segundo() const { // Devuelve el valor del segundo elemento del par return elem_2; } //------------------------------ void asg_primero(const Tipo_1& valor) { // Asigna un valor al primer elemento del par elem_1 = valor; } //------------------------------ void asg_segundo(const Tipo_2& valor) { // Asigna un valor al segundo elemento del par elem_2 = valor; } //------------------------------ private: Tipo_1 elem_1; Tipo_2 elem_2; }; } #endif Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 134. 134 CAP´ITULO 12. INTRODUCCI ´ON A LA PROGRAMACI ´ON GEN´ERICA. PLANTILLAS A continuaci´on podemos ver un ejemplo de su utilizaci´on: //- main.hpp ------------------------------------------------------------- #include <iostream> #include <string> #include "par.hpp" using namespace std; using namespace umalcc; //-------------------------------------- typedef Par<int, int> ParInt; typedef Par<string, int> ParPer; //-------------------------------------- int main() { ParInt x(3, 7) ; // Crea un Par de enteros con los valores 3 y 7 ParPer p("pepe", 23) ; // Crea un Par de Persona con los valores "pepe" y 23 ParPer q = p; // Copia el par de p a q cout << x.primero() << " " << x.segundo() << endl; p.asg_primero("juan"); cout << p.primero() << " " << p.segundo() << endl; } //-------------------------------------- Ejemplo 2 En el cap´ıtulo anterior (v´ease 11.2.1) se vio un ejemplo de un TAD Lista de n´umero enteros. Definici´on e Implementaci´on Podemos definir una TAD gen´erico Lista que permita almacenar elementos homog´eneos de un tipo de datos gen´erico, y donde su capacidad m´axima tambi´en est´e parametrizada de forma gen´erica: //- fichero: lista.hpp ------------------------------------------ #ifndef _lista_hpp_ #define _lista_hpp_ #include <tr1/array> #include <cassert> namespace umalcc { template <typename TipoBase, unsigned SIZE> class Lista { public: //---------------------------------------------------------- //-- M´etodos P´ublicos -------------------------------------- //---------------------------------------------------------- // ~Lista() { } // Destructor Autom´atico //------------------------------ Lista() : sz(0), v() { } // Constructor por Defecto //------------------------------ Lista(const Lista& o) // Constructor de Copia : sz(o.sz), v() { for (int i = 0; i < sz; ++i) { v[i] = o.v[i] ; } } //------------------------------ Lista& operator = (const Lista& o) // Operador de Asignaci´on { if (this != &o) { Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 135. 12.2. TIPOS ABSTRACTOS DE DATOS GEN´ERICOS 135 sz = o.sz ; for (int i = 0; i < sz; ++i) { v[i] = o.v[i] ; } } return *this ; } //------------------------------ bool llena() const { return sz == int(v.size()); } //------------------------------ int size() const { return sz ; } //------------------------------ void clear() { sz = 0 ; } //------------------------------ // PRECOND: ( ! llena() && pos >= 0 && pos <= size()) void insertar(int pos, const TipoBase& dato) { assert( ! llena() && pos >= 0 && pos <= size()) ; abrir_hueco(pos) ; v[pos] = dato ; } //------------------------------ // PRECOND: (pos >= 0 && pos < size()) void eliminar(int pos) { assert(pos >= 0 && pos < size()) ; cerrar_hueco(pos) ; } //------------------------------ // PRECOND: (pos >= 0 && pos < size()) TipoBase acceder(int pos) const { assert(pos >= 0 && pos < size()) ; return v[pos] ; } //------------------------------ // PRECOND: (pos >= 0 && pos < size()) void modificar(int pos, const TipoBase& dato) { assert(pos >= 0 && pos < size()) ; v[pos] = dato; } //---------------------------------------------------------- private: //---------------------------------------------------------- //-- Ctes y Tipos Privados --------------------------------- //---------------------------------------------------------- typedef std::tr1::array<TipoBase, SIZE> Datos; //---------------------------------------------------------- //-- Metodos Privados -------------------------------------- Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 136. 136 CAP´ITULO 12. INTRODUCCI ´ON A LA PROGRAMACI ´ON GEN´ERICA. PLANTILLAS //---------------------------------------------------------- void abrir_hueco(int pos) { assert(sz < int(v.size())) ; for (int i = sz; i > pos; --i) { v[i] = v[i-1]; } ++sz; // Ahora hay un elemento m´as } //------------------------------ void cerrar_hueco(int pos) { assert(sz > 0) ; --sz; // Ahora hay un elemento menos for (int i = pos; i < sz; ++i) { v[i] = v[i+1]; } } //---------------------------------------------------------- //-- Atributos Privados ------------------------------------ //---------------------------------------------------------- int sz; // numero de elementos de la lista Datos v; // contiene los elementos de la lista //---------------------------------------------------------- }; } #endif //- fin: lista.hpp ---------------------------------------------- Utilizaci´on Veamos a continuaci´on un ejemplo de instanciaci´on y utilizaci´on del TAD Lista gen´erica visto anteriormente: //- fichero: main.cpp ----------------------------------------------- #include <iostream> #include <cctype> #include <cassert> #include "lista.hpp" using namespace std ; using namespace umalcc ; //------------------------------------------------------------------ // Instanciaci´on de la Lista gen´erica para almacenar hasta 100 n´umeros enteros typedef Lista<int, 100> ListaInt; //------------------------------------------------------------------ void leer_pos(int& pos, int limite) { assert(limite > 0); do { cout << "Introduzca posicion ( < " << limite << " ): " ; cin >> pos; } while (pos < 0 || pos >= limite); } //--------------------------------- void leer_dato(int& dato) { cout << "Introduzca un dato: " ; cin >> dato; } //--------------------------------- Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 137. 12.2. TIPOS ABSTRACTOS DE DATOS GEN´ERICOS 137 void leer(ListaInt& lista) { int dato ; lista.clear() ; cout << "Introduzca datos (0 -> FIN): " << endl ; cin >> dato ; while ((dato != 0)&&( ! lista.llena())) { lista.insertar(lista.size(), dato) ; cin >> dato ; } } //--------------------------------- void escribir(const ListaInt& lista) { cout << "Lista: " ; for (int i = 0 ; i < lista.size() ; ++i) { cout << lista.acceder(i) << " " ; } cout << endl ; } //--------------------------------- void prueba_asg(const ListaInt& lista) { cout << "Constructor de Copia" << endl ; ListaInt lst(lista) ; escribir(lst) ; cout << "Operador de Asignacion" << endl ; lst = lista ; escribir(lst) ; } //------------------------------------------------------------------------- char menu() { char op ; cout << endl ; cout << "X. Fin" << endl ; cout << "A. Leer Lista" << endl ; cout << "B. Borrar Lista" << endl ; cout << "C. Insertar Posicion" << endl ; cout << "D. Eliminar Posicion" << endl ; cout << "E. Acceder Posicion" << endl ; cout << "F. Modificar Posicion" << endl ; cout << "G. Prueba Copia y Asignacion" << endl ; do { cout << endl << " Opcion: " ; cin >> op ; op = char(toupper(op)) ; } while (!((op == ’X’)||((op >= ’A’)&&(op <= ’G’)))) ; cout << endl ; return op ; } //------------------------------------------------------------------------- int main() { ListaInt lista ; int dato ; int pos ; bool ok ; char op = ’ ’ ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 138. 138 CAP´ITULO 12. INTRODUCCI ´ON A LA PROGRAMACI ´ON GEN´ERICA. PLANTILLAS do { op = menu() ; switch (op) { case ’A’: leer(lista) ; escribir(lista) ; break ; case ’B’: lista.clear() ; escribir(lista) ; break ; case ’C’: if (lista.llena()) { cout << "Error: Lista llena" << endl ; } else { leer_pos(pos, lista.size()+1) ; leer_dato(dato) ; lista.insertar(pos, dato) ; escribir(lista) ; } break ; case ’D’: if (lista.size() == 0) { cout << "Error: lista vacia" << endl ; } else { leer_pos(pos, lista.size()) ; lista.eliminar(pos) ; escribir(lista) ; } break ; case ’E’: if (lista.size() == 0) { cout << "Error: lista vacia" << endl ; } else { leer_pos(pos, lista.size()) ; cout << "Lista[" << pos << "]: " << lista.acceder(pos) << endl ; escribir(lista) ; } break ; case ’F’: if (lista.size() == 0) { cout << "Error: lista vacia" << endl ; } else { leer_pos(pos, lista.size()) ; leer_dato(dato) ; lista.modificar(pos, dato) ; escribir(lista) ; } break ; case ’G’: prueba_asg(lista) ; break ; } } while (op != ’X’) ; } //- fin: main.cpp --------------------------------------------------- Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 139. Cap´ıtulo 13 Memoria Din´amica. Punteros Hasta ahora, todos los programas que se han visto en cap´ıtulos anteriores almacenan su esta- do interno por medio de variables que son autom´aticamente gestionadas por el compilador. Las variables son creadas cuando el flujo de ejecuci´on entra en el ´ambito de su definici´on (se reserva espacio en memoria y se crea el valor de su estado inicial), posteriormente se manipula el estado de la variable (accediendo o modificando su valor almacenado), y finalmente se destruye la variable cuando el flujo de ejecuci´on sale del ´ambito donde fue declarada la variable (liberando los recursos asociados a ella y la zona de memoria utilizada). A este tipo de variables gestionadas autom´atica- mente por el compilador se las suele denominar variables autom´aticas (tambi´en variables locales), y residen en una zona de memoria gestionada autom´aticamente por el compilador, la pila de eje- cuci´on, donde se alojan y desalojan las variables locales (autom´aticas) pertenecientes al ´ambito de ejecuci´on de cada subprograma. As´ı, el tiempo de vida de una determinada variable est´a condicionado por el ´ambito de su declaraci´on. Adem´as, el n´umero de variables autom´aticas utilizadas en un determinado programa est´a especificado expl´ıcitamente en el propio programa, y por lo tanto su capacidad de almace- namiento est´a tambi´en especificada y predeterminada por lo especificado expl´ıcitamente en el pro- grama. Es decir, con la utilizaci´on ´unica de variables autom´aticas, la capacidad de almacenamiento de un determinado programa est´a predeterminada desde el momento de su programaci´on (tiempo de compilaci´on), y no puede adaptarse a las necesidades reales de almacenamiento surgidas durante la ejecuci´on del programa (tiempo de ejecuci´on).1 La gesti´on de memoria din´amica surge como un mecanismo para que el propio programa, du- rante su ejecuci´on (tiempo de ejecuci´on), pueda solicitar (alojar) y liberar (desalojar) memoria seg´un las necesidades surgidas durante una determinada ejecuci´on, dependiendo de las circunstan- cias reales de cada momento de la ejecuci´on del programa en un determinado entorno. Esta ventaja adicional viene acompa˜nada por un determinado coste asociado a la mayor complejidad que requiere su gesti´on, ya que en el caso de las variables autom´aticas, es el propio compilador el encargado de su gesti´on, sin embargo en el caso de las variables din´amicas es el propio programador el que debe, mediante c´odigo software, gestionar el tiempo de vida de cada variable din´amica, cuando debe ser alojada y creada, como ser´a utilizada, y finalmente cuando debe ser destruida y desalojada. Adicionalmente, como parte de esta gesti´on de la memoria din´amica por el propio programador, la memoria din´amica pasa a ser un recurso que debe gestionar el programador, y se debe preocupar de su alojo y de su liberaci´on, poniendo especial cuidado y ´enfasis en no perder recursos (perder zonas de memoria sin liberar y sin capacidad de acceso). 1En realidad esto no es completamente cierto, ya que en el caso de subprogramas recursivos, cada invocaci´on recursiva en tiempo de ejecuci´on tiene la capacidad de alojar nuevas variables que ser´an posteriormente desalojadas autom´aticamente cuando la llamada recursiva finaliza. 139
  • 140. 140 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS 13.1. Punteros El tipo puntero es un tipo simple que permite a un determinado programa acceder a posi- ciones concretas de memoria, y m´as espec´ıficamente a determinadas zonas de la memoria din´ami- ca. Aunque el lenguaje de programaci´on C++ permite otras utilizaciones m´as diversas del tipo puntero, en este cap´ıtulo s´olo se utilizar´a el tipo puntero para acceder a zonas de memoria din´amica. As´ı, una determinada variable de tipo puntero apunta (o referencia) a una determinada entidad (variable) de un determinado tipo alojada en la zona de memoria din´amica. Por lo tanto, para un determinado tipo puntero, se debe especificar tambi´en el tipo de la variable (en memoria din´amica) a la que apunta, el cual define el espacio que ocupa en memoria y las operaciones (y m´etodos) que se le pueden aplicar, entre otras cosas. De este modo, cuando un programa gestiona la memoria din´amica a trav´es de punteros, debe manejar y gestionar por una parte la propia variable de tipo puntero, y por otra parte la variable din´amica apuntada por ´este. Un tipo puntero se define utilizando la palabra reservada typedef seguida del tipo de la variable din´amica apuntada, un asterisco para indicar que es un puntero a una variable de dicho tipo, y el identificador que denomina al tipo. Por ejemplo: typedef int* PInt ; // Tipo Puntero a Entero struct Persona { // Tipo Persona string nombre ; string telefono ; int edad ; } ; typedef Persona* PPersona ; // Tipo Puntero a Persona As´ı, el tipo PInt es el tipo de una variable que apunta a una variable din´amica de tipo int. Del mismo modo, el tipo PPersona es el tipo de una variable que apunta a una variable din´amica de tipo Persona. Es importante remarcar que el tipo puntero, en s´ı mismo, es un tipo simple, aunque el tipo apuntado puede ser tanto un tipo simple, como un tipo compuesto. Es posible definir variables de los tipos especificados anteriormente. N´otese que estas variables (p1 y p2 en el siguiente ejemplo) son variables autom´aticas (gestionadas autom´aticamente por el compilador), es decir, se crean autom´aticamente (con un valor indeterminado) al entrar el flujo de ejecuci´on en el ´ambito de visibilidad de la variable, y posteriormente se destruyen autom´atica- mente cuando el flujo de ejecuci´on sale del ´ambito de visibilidad de la variable. Por otra parte, las variables apuntadas por ellos son variables din´amicas (gestionadas por el programador), es decir el programador se encargar´a de solicitar la memoria din´amica cuando sea necesaria y de liberarla cuando ya no sea necesaria, durante la ejecuci´on del programa. En el siguiente ejemplo, si las variables se definen sin inicializar, entonces tendr´an un valor inicial inespecificado: p1: ? p2: ? int main() { PInt p1 ; PPersona p2 ; } La constante NULL es una constante especial de tipo puntero que indica que una determinada variable de tipo puntero no apunta a nada, es decir, especifica que la variable de tipo puntero que contenga el valor NULL no apunta a ninguna zona de la memoria din´amica. Para utilizar la constante NULL se debe incluir la biblioteca est´andar <cstddef>. As´ı, se pueden definir las variables p1 y p2 e inicializarlas a un valor indicando que no apuntan a nada. p1: p2: #include <cstddef> int main() { PInt p1 = NULL ; PPersona p2 = NULL ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 141. 13.2. GESTI ´ON DE MEMORIA DIN ´AMICA 141 13.2. Gesti´on de Memoria Din´amica La memoria din´amica la debe gestionar el propio programador, por lo que cuando necesite crear una determinada variable din´amica, debe solicitar memoria din´amica con el operador new seguido por el tipo de la variable din´amica a crear. Este operador (new) realiza dos acciones principales, primero aloja (reserva) espacio en memoria din´amica para albergar a la variable, y despu´es crea (invocando al constructor especificado) el contenido de la variable din´amica. Finalmente, a la variable ptr se le asigna el valor del puntero (una direcci´on de memoria) que apunta a la variable din´amica creada por el operador new. Por ejemplo, para crear una variable din´amica del tipo Persona definido anteriormente utilizando el constructor por defecto de dicho tipo. ptr: −−→◦ ??? int main() { PPersona ptr = new Persona ; } En caso de que el tipo de la variable din´amica tenga otros constructores definidos, es posible utilizarlos en la construcci´on del objeto en memoria din´amica. Por ejemplo, suponiendo que el tipo Persona tuviese un constructor que reciba el nombre, tel´efono y edad de la persona: ptr: −−→◦ pepe 111 5 int main() { PPersona ptr = new Persona("pepe", "111", 5) ; } Posteriormente, tras manipular adecuadamente, seg´un las caracter´ısticas del programa, la memoria din´amica alojada, llegar´a un momento en que dicha variable din´amica ya no sea necesaria, y su tiempo de vida llegue a su fin. En este caso, el programador debe liberar expl´ıcitamente dicha variable din´amica mediante el operador delete de la siguiente forma: ptr: −−→◦ pepe 111 5 ptr: ? X liberada int main() { PPersona ptr = new Persona("pepe", "111", 5) ; // manipulaci´on ... delete ptr ; } La sentencia delete ptr realiza dos acciones principales, primero destruye la variable din´amica (invocando a su destructor), y despu´es desaloja (libera) la memoria din´amica reservada para dicha variable. Finalmente la variable local ptr queda con un valor inespecificado, y ser´a destruida autom´aticamente por el compilador cuando el flujo de ejecuci´on salga de su ´ambito de declaraci´on. Si se ejecuta la operaci´on delete sobre una variable de tipo puntero que tiene el valor NULL, entonces esta operaci´on no hace nada. En caso de que no se libere (mediante el operador delete) la memoria din´amica apuntada por la variable ptr, y esta variable sea destruida al terminar su tiempo de vida (su ´ambito de visibilidad), entonces se perder´a la memoria din´amica a la que apunta, con la consiguiente p´erdida de recursos que ello conlleva. ptr: −−→◦ pepe 111 5 pepe 111 5 perdida int main() { PPersona ptr = new Persona("pepe", "111", 5) ; // manipulaci´on ... // no se libera la memoria din´amica apuntada por ptr // se destruye la variable local ptr } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 142. 142 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS 13.3. Operaciones con Variables de Tipo Puntero Desreferenciaci´on de una Variable de Tipo Puntero Para acceder a una variable din´amica apuntada por una variable de tipo puntero, se utiliza el operador unario asterisco (*) precediendo al nombre de la variable de tipo puntero a trav´es de la cual es apuntada. Por ejemplo, si ptr es una variable local de tipo puntero que apunta a una variable din´amica de tipo Persona, entonces *ptr es la variable din´amica apuntada, y se trata de igual forma que cualquier otra variable de tipo Persona. int main() { PPersona ptr = new Persona("pepe", "111", 5) ; Persona p = *ptr ; // Asigna el contenido de la variable din´amica a la variable p *ptr = p ; // Asigna el contenido de la variable p a la variable din´amica delete ptr ; // destruye la variable din´amica y libera su espacio de memoria } Sin embargo, si una variable de tipo puntero tiene el valor NULL, entonces desreferenciar la variable produce un error en tiempo de ejecuci´on que aborta la ejecuci´on del programa. Es posible, as´ı mismo, acceder a los elementos de la variable apuntada mediante el operador de desreferenciaci´on. Por ejemplo: int main() { PPersona ptr = new Persona ; (*ptr).nombre = "pepe" ; (*ptr).telefono = "111" ; (*ptr).edad = 5 ; delete ptr ; } N´otese que el uso de los par´entesis es obligatorio debido a que el operador punto (.) tiene mayor precedencia que el operador de desreferenciaci´on (*). Por ello, en el caso de acceder a los campos de un registro en memoria din´amica a trav´es de una variable de tipo puntero, es m´as adecuado utilizar el operador de desreferenciaci´on (->). Por ejemplo: int main() { PPersona ptr = new Persona ; ptr->nombre = "pepe" ; ptr->telefono = "111" ; ptr->edad = 5 ; delete ptr ; } Este operador tambi´en se utiliza para invocar a m´etodos de un objeto si ´este se encuentra alojado en memoria din´amica. Por ejemplo: #include <iostream> using namespace std ; class Numero { public: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 143. 13.3. OPERACIONES CON VARIABLES DE TIPO PUNTERO 143 Numero(int v) : val(v) {} int valor() const { return val ; } private: int val ; } ; typedef Numero* PNumero ; int main() { PNumero ptr = new Numero(5) ; cout << ptr->valor() << endl ; delete ptr ; } Asignaci´on de Variables de Tipo Puntero El puntero nulo (NULL) se puede asignar a cualquier variable de tipo puntero. Por ejemplo: p1: ? p1: int main() { PPersona p1 ; // ... p1 = NULL ; // ... } El resultado de crear una variable din´amica con el operador new se puede asignar a una variable de tipo puntero al tipo de la variable din´amica creada. Por ejemplo: p1: ? p1: −−→◦ pepe 111 5 int main() { PPersona p1 ; // ... p1 = new Persona("pepe", "111", 5) ; // ... } As´ı mismo, a una variable de tipo puntero se le puede asignar el valor de otra variable puntero. En este caso, ambas variables de tipo puntero apuntar´an a la misma variable din´amica, que ser´a com- partida por ambas. Si se libera la variable din´amica apuntada por una de ellas, la variable din´amica compartida se destruye, su memoria se desaloja y ambas variables locales de tipo puntero quedan con un valor inespecificado. p1: −−→◦ p2: −−→◦ pepe 111 5 p1: ? p2: ? X liberada int main() { PPersona p1 = new Persona("pepe", "111", 5) ; PPersona p2 ; // ... p2 = p1 ; // ... delete p1 ; } En la operaci´on de asignaci´on, el valor anterior que tuviese la variable de tipo puntero se pierde, por lo que habr´a que tener especial cuidado de que no se pierda la variable din´amica que tuviese asignada, si tuviese alguna. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 144. 144 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS p1: −−→◦ pepe 111 5 p1: pepe 111 5 perdida int main() { PPersona p1 = new Persona("pepe", "111", 5) ; // ... p1 = NULL ; // se pierde el valor anterior // ... delete p1 ; } Comparaci´on de Variables de Tipo Puntero Las variables del mismo tipo puntero se pueden comparar entre ellas por igualdad (==) o desigualdad (!=), para comprobar si apuntan a la misma variable din´amica. As´ı mismo, tambi´en se pueden comparar por igualdad o desigualdad con el puntero nulo (NULL) para saber si apunta a alguna variable din´amica, o por el contrario no apunta a nada. Por ejemplo: int main() { PPersona p1, p2 ; // ... if (p1 == p2) { // ... } if (p1 != NULL) { // ... } } 13.4. Paso de Par´ametros de Variables de Tipo Puntero El tipo puntero es un tipo simple, y por lo tanto se tratar´a como tal. En caso de paso de par´ametros de tipo puntero, si es un par´ametro de entrada, entonces se utilizar´a el paso por valor, y si es un par´ametro de salida o de entrada/salida, entonces se utilizar´a el paso por referencia. Hay que ser consciente de que un par´ametro de tipo puntero puede apuntar a una variable din´amica, y en este caso, a partir del par´ametro se puede acceder a la variable apuntada. As´ı, si el par´ametro se pasa por valor, entonces se copia el valor del puntero del par´ametro actual (en la invocaci´on) al par´ametro formal (en el subprograma), por lo que ambos apuntar´an a la misma variable din´amica compartida, y en este caso, si se modifica el valor almacenado en la variable din´amica, este valor se ver´a afectado, as´ı mismo, en el exterior del subprograma, aunque el par´ametro haya sido pasado por valor. Por otra parte, las funciones tambi´en pueden devolver valores de tipo puntero. void modificar(PPersona& p) ; PPersona buscar(PPersona l, const string& nombre) ; 13.5. Listas Enlazadas Lineales Una de las principales aplicaciones de la Memoria Din´amica es el uso de estructuras enlazadas, de tal forma que un campo o atributo de la variable din´amica es a su vez tambi´en de tipo puntero, por lo que puede apuntar a otra variable din´amica que tambi´en tenga un campo o atributo de tipo puntero, el cual puede volver a apuntar a otra variable din´amica, y as´ı sucesivamente, tantas veces como sea necesario, hasta que un puntero con el valor NULL indique el final de la estructura enlazada (lista enlazada). As´ı, en este caso, vemos que un campo de la estructura es de tipo puntero a la propia estructura, por lo que es necesario definir el tipo puntero antes de definir la estructura. Sin embargo, la Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 145. 13.5. LISTAS ENLAZADAS LINEALES 145 estructura todav´ıa no ha sido definida, por lo que no se puede definir un puntero a ella. Por ello es necesario realizar una declaraci´on adelantada de un tipo incompleto del tipo de la variable din´amica, donde se declara que un determinado identificador es una estructura o clase, pero no se definen sus componentes. lista: −−→◦ −−−−−→◦ pepe −−−−−→◦ juan mar´ıa struct Nodo ; // Declaraci´on adelantada del tipo incompleto Nodo typedef Nodo* PNodo ; // Definici´on de tipo Puntero a tipo incompleto Nodo struct Nodo { // Definici´on del tipo Nodo PNodo sig ; // Enlace a la siguiente estructura din´amica string dato ; // Dato almacenado en la lista } ; void escribir(PNodo lista) { PNodo ptr = lista; while (ptr != NULL) { cout << ptr->dato << endl ; } } PNodo buscar(PNodo lista, const string& dt) { PNodo ptr = lista ; while ((ptr != NULL)&&(ptr->dato != dt)) { ptr = ptr->sig ; } return ptr ; } void leer_inversa(PNodo& lista) { lista = NULL ; string dt ; cin >> dt ; while (dt != "fin") { PNodo ptr = new Nodo ; ptr->dato = dt ; ptr->sig = lista ; lista = ptr ; cin >> dt ; } } void destruir(PNodo& lista) { while (lista != NULL) { PNodo ptr = lista ; lista = lista->sig ; delete ptr ; } } int main() { PNodo lista ; leer_inversa(lista) ; escribir(lista) ; PNodo ptr = buscar(lista, "juan"); if (ptr != NULL) { cout << ptr->dato << endl; } destruir(lista) ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 146. 146 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS Insertar al Principio lista: ◦ - mar´ıa PNodo ptr = new Nodo("pepe") ; lista: ◦ - mar´ıa ptr: −−→◦ ◦ pepe ptr->sig = lista ; lista: ◦ - mar´ıa ptr: −−→◦ ◦ pepe 3 lista = ptr ; lista: ◦ @ @R mar´ıa ptr: −−→◦ ◦ pepe 3 lista: −−→◦ mar´ıa −−−−−→◦ pepe Insertar Detr´as ant: −−→◦ mar´ıa ◦ pepe - PNodo ptr = new Nodo(juan) ; ant: −−→◦ mar´ıa ◦ pepe - ptr: ◦ - ◦ juan ptr-sig = ant-sig ; ant: −−→◦ mar´ıa ◦ pepe - ptr: ◦ - ◦ juan 3 ant-sig = ptr ; ant: −−→◦ mar´ıa ◦ pepe Q Q Qs ptr: ◦ - ◦ juan 3 ant: −−→◦ mar´ıa −−−−−→◦ pepe −−−−−→◦ juan struct Nodo ; typedef Nodo* PNodo ; struct Nodo { PNodo sig ; string dato ; } ; void insertar_principio(PNodo lista, const string dt) { PNodo ptr = new Nodo ; ptr-dato = dt ; ptr-sig = lista ; lista = ptr ; } void insertar_final(PNodo ant, const string dt) { PNodo ptr = new Nodo ; ptr-dato = dt ; ptr-sig = NULL ; if (lista == NULL) { lista = ptr ; } else { PNodo act = lista ; while (act-sig != NULL) { act = act-sig ; } act-sig = ptr ; } } PNodo situar(PNodo lista, int pos) { PNodo ptr = lista; while ((ptr != NULL)(pos 0)) { ptr = ptr-sig; --pos; } return ptr; } void insertar_pos(PNodo lista, int pos, const string dt) { PNodo ptr = new Nodo ; ptr-dato = dt ; if (pos 1) { ptr-sig = lista ; lista = ptr ; } else { PNodo ant = situar(lista, pos - 1); if (ant != NULL) { ptr-sig = ant-sig ; ant-sig = ptr ; } } } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 147. 13.5. LISTAS ENLAZADAS LINEALES 147 Eliminar del Principio lista: −−→◦ mar´ıa −−−−−→◦ pepe ptr = lista ; lista: ◦ @@R mar´ıa ptr: −−→◦ ◦ pepe 3 lista = lista-sig ; lista: ◦ - mar´ıa ptr: −−→◦ ◦ pepe 3 delete ptr ; lista: ◦ - mar´ıa ptr: ? Xliberada lista: −−→◦ mar´ıa Eliminar de Detr´as ant: −−→◦ mar´ıa −−−−−→◦ pepe −−−−−→◦ juan ptr = ant-sig ; ant: −−→◦ mar´ıa ◦ pepe Q Q Qs ptr: ◦ - ◦ juan 3 ant-sig = ptr-sig ; ant: −−→◦ mar´ıa ◦ pepe - ptr: ◦ - ◦ juan 3 delete ptr ; ant: −−→◦ mar´ıa ◦ pepe - ptr: ? Xliberada ant: −−→◦ mar´ıa −−−−−→◦ pepe void eliminar_primero(PNodo lista) { if (lista != NULL) { PNodo ptr = lista ; lista = lista-sig ; delete ptr ; } } void eliminar_ultimo(PNodo lista) { if (lista != NULL) { if (lista-sig == NULL) { delete lista ; lista = NULL ; } else { PNodo ant = lista ; PNodo act = ant-sig ; while (act-sig != NULL) { ant = act ; act = act-sig ; } delete act ; ant-sig = NULL ; } } } void eliminar_pos(PNodo lista, int pos) { if (lista != NULL) { if (pos 1) { PNodo ptr = lista ; lista = lista-sig ; delete ptr ; } else { PNodo ant = situar(lista, pos - 1) ; if ((ant != NULL)(ant-sig != NULL)) { PNodo ptr = ant-sig ; ant-sig = ptr-sig ; delete ptr ; } } } } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 148. 148 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS void insertar_ord(PNodo lista, const string dt) { PNodo ptr = new Nodo ; ptr-dato = dt ; if ((lista==NULL)||(dt lista-dato)) { ptr-sig = lista ; lista = ptr ; } else { PNodo ant = lista ; PNodo act = ant-sig ; while ((act!=NULL)(act-dato=dt)){ ant = act ; act = act-sig ; } ptr-sig = ant-sig ; ant-sig = ptr ; } } void eliminar_elem(PNodo lista, const string dt) { if (lista != NULL) { if (lista-dato == dt) { PNodo ptr = lista ; lista = lista-sig ; delete ptr ; } else { PNodo ant = lista ; PNodo act = ant-sig ; while ((act != NULL)(act-dato != dt)) { ant = act ; act = act-sig ; } if (act != NULL) { ant-sig = act-sig ; delete act ; } } } } PNodo duplicar(PNodo lista) { PNodo nueva = NULL; if (lista != NULL) { nueva = new Nodo ; nueva-dato = lista-dato ; PNodo u = nueva ; PNodo p = lista-sig ; while (p != NULL) { u-sig = new Nodo ; u-sig-dato = p-dato ; u = u-sig ; p = p-sig ; } u-sig = NULL ; } return nueva; } void purgar(PNodo lista, const string dt) { while ((lista != NULL)(dt == lista-dato)) { PNodo ptr = lista ; lista = lista-sig ; delete ptr ; } if (lista != NULL) { PNodo ant = lista; PNodo act = lista-sig; while (act != NULL) { if (dt == act-dato) { ant-sig = act-sig ; delete act ; } else { ant = act; } act = ant-sig; } } } 13.6. Abstracci´on en la Gesti´on de Memoria Din´amica La gesti´on de memoria din´amica por parte del programador se basa en estructuras de progra- maci´on de bajo nivel, las cuales son propensas a errores de programaci´on y p´erdida de recursos de memoria. Adem´as, entremezclar sentencias de gesti´on de memoria, de bajo nivel, con sentencias aplicadas al dominio de problema a resolver suele dar lugar a c´odigo no legible y propenso a errores. Por lo tanto se hace necesario aplicar niveles de abstracci´on que aislen la gesti´on de memo- ria din´amica (de bajo nivel) del resto del c´odigo m´as directamente relacionado con la soluci´on del problema. Para ello, los tipos abstractos de datos proporcionan el mecanismo adecuado para aplicar la abstracci´on a estas estructuras de datos basadas en la gesti´on de memoria din´amica, adem´as de proporcionar una herramienta adecuada para la gesti´on de memoria din´amica, ya que los destructores se pueden encargar de liberar los recursos asociados a un determinado objeto. Por ejemplo: template typename Tipo class Lista { Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 149. 13.7. TIPO ABSTRACTO DE DATOS LISTA ENLAZADA GEN´ERICA 149 public: ~Lista() { destruir() ; } Lista() : sz(0), lista(NULL) { } void insertar(int pos, const Tipo d) { ... } void eliminar(int pos) { ... } // ... } ; 13.7. Tipo Abstracto de Datos Lista Enlazada Gen´erica Aunque las listas enlazadas se pueden programar directamente entremezcladas con el c´odigo de resoluci´on del problema en cuesti´on, es conveniente que su gesti´on se realice dentro de una abstracci´on que aisle su tratamiento y permita una mejor gesti´on de sus recursos. Con objeto de facilitar su estudio, el siguiente ejemplo es una implementaci´on simplificada del tipo abstracto de datos lista gen´erica de elementos homog´eneos. N´otese, sin embargo, que otras implementaciones pueden mejorar notablemente su eficiencia. //- lista.hpp ------------------------------------------------------------- #ifndef _lista_hpp_ #define _lista_hpp_ #include cstddef #include cassert namespace umalcc { template typename Tipo class Lista { public: //-- M´etodos P´ublicos ---------- // Destructor ~Lista() { destruir(lista) ; } // Constructor por Defecto Lista() : sz(0), lista(NULL) { } // Constructor de Copia Lista(const Lista o) : sz(o.sz), lista(duplicar(o.lista)) { } // Operador de Asignaci´on Lista operator = (const Lista o) { if (this != o) { destruir(lista) ; sz = o.sz ; lista = duplicar(o.lista) ; } return *this ; } // Elimina todos los elementos de la lista actual (queda vacia) void clear() { destruir(lista) ; sz = 0 ; } // Devuelve el numero de elementos almacenados int size() const Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 150. 150 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS { return sz ; } // Devuelve true si el numero de elementos almacenados // alcanza la capacidad maxima de almacenamiento bool llena() const { return false; } // PRECOND: ( ! llena() 0 = pos pos = size()) // Inserta (dato) en la lista actual en la posicion (pos) void insertar(int pos, const Tipo d) { assert(! llena() 0 = pos pos = size()) ; insertar_pos(lista, pos, d) ; ++sz ; } // PRECOND: (0 = pos pos size()) // Elimina de la lista actual el elemento que ocupa la posicion (pos) void eliminar(int pos) { assert(0 = pos pos size()) ; eliminar_pos(lista, pos) ; --sz ; } // PRECOND: (0 = pos pos size()) // Devuelve el elemento de la lista actual que ocupa la posicion (pos) Tipo acceder(int pos) const { assert(0 = pos pos size()) ; PNodo ptr = situar(lista, pos) ; assert(ptr != NULL) ; return ptr-dato ; } // PRECOND: (0 = pos pos size()) // Asigna (dato) al elemento de la lista actual que ocupa la posicion (pos) void modificar(int pos, const Tipo d) { assert(0 = pos pos size()) ; PNodo ptr = situar(lista, pos) ; assert(ptr != NULL) ; ptr-dato = d ; } private: //-- Tipos Privados ------ struct Nodo ; typedef Nodo* PNodo ; struct Nodo { PNodo sig ; Tipo dato ; } ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 151. 13.7. TIPO ABSTRACTO DE DATOS LISTA ENLAZADA GEN´ERICA 151 //-- Atributos privados -- int sz ; PNodo lista ; //-- M´etodos Privados ---------- void destruir(PNodo lst) const { while (lst != NULL) { PNodo ptr = lst ; lst = lst-sig ; delete ptr ; } } PNodo situar(PNodo lst, int pos) const { PNodo ptr = lst; while ((ptr != NULL)(pos 0)) { ptr = ptr-sig; --pos; } return ptr; } void insertar_pos(PNodo lst, int pos, const Tipo dt) const { PNodo ptr = new Nodo ; ptr-dato = dt ; if (pos 1) { ptr-sig = lst ; lst = ptr ; } else { PNodo ant = situar(lst, pos - 1); if (ant != NULL) { ptr-sig = ant-sig ; ant-sig = ptr ; } } } void eliminar_pos(PNodo lst, int pos) const { if (lst != NULL) { if (pos 1) { PNodo ptr = lst ; lst = lst-sig ; delete ptr ; } else { PNodo ant = situar(lst, pos - 1) ; if ((ant != NULL)(ant-sig != NULL)) { PNodo act = ant-sig ; ant-sig = act-sig ; delete act ; } } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 152. 152 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS } } PNodo duplicar(PNodo lst) const { PNodo nueva = NULL; if (lst != NULL) { nueva = new Nodo ; nueva-dato = lst-dato ; PNodo u = nueva ; PNodo p = lst-sig ; while (p != NULL) { u-sig = new Nodo ; u-sig-dato = p-dato ; u = u-sig ; p = p-sig ; } u-sig = NULL ; } return nueva; } } ; // class } // namespace #endif //------------------------------------------------------------------------- Se puede apreciar como tanto el constructor de copia, como el operador de asignaci´on duplican la lista almacenada, y por el contrario tanto el destructor como el m´etodo clear() liberan todos los recursos que el objeto tenga asignados. As´ı mismo, el m´etodo duplicar invoca a la destrucci´on de los recursos que tuviese antes de duplicar y copiar la nueva lista. A continuaci´on se puede ver un ejemplo de utilizaci´on del tipo abstracto de datos lista gen´erica definido anteriormente. //- fichero: main.cpp ----------------------------------------------- #include iostream #include cctype #include cassert #include lista.hpp using namespace std ; using namespace umalcc ; //------------------------------------------------------------------ // Instanciaci´on de la Lista gen´erica para almacenar n´umeros enteros typedef Listaint ListaInt; //------------------------------------------------------------------ void leer_pos(int pos, int limite) { assert(limite 0); do { cout Introduzca posicion ( limite ): ; cin pos; } while (pos 0 || pos = limite); } //--------------------------------- void leer_dato(int dato) { cout Introduzca un dato: ; cin dato; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 153. 13.7. TIPO ABSTRACTO DE DATOS LISTA ENLAZADA GEN´ERICA 153 //--------------------------------- void leer(ListaInt lista) { int dato ; lista.clear() ; cout Introduzca datos (0 - FIN): endl ; cin dato ; while ((dato != 0)( ! lista.llena())) { lista.insertar(lista.size(), dato) ; cin dato ; } } //--------------------------------- void escribir(const ListaInt lista) { cout Lista: ; for (int i = 0 ; i lista.size() ; ++i) { cout lista.acceder(i) ; } cout endl ; } //--------------------------------- void prueba_asg(const ListaInt lista) { cout Constructor de Copia endl ; ListaInt lst(lista) ; escribir(lst) ; cout Operador de Asignacion endl ; lst = lista ; escribir(lst) ; } //------------------------------------------------------------------------- char menu() { char op ; cout endl ; cout X. Fin endl ; cout A. Leer Lista endl ; cout B. Borrar Lista endl ; cout C. Insertar Posicion endl ; cout D. Eliminar Posicion endl ; cout E. Acceder Posicion endl ; cout F. Modificar Posicion endl ; cout G. Prueba Copia y Asignacion endl ; do { cout endl Opcion: ; cin op ; op = char(toupper(op)) ; } while (!((op == ’X’)||((op = ’A’)(op = ’G’)))) ; cout endl ; return op ; } //------------------------------------------------------------------------- int main() { ListaInt lista ; int dato ; int pos ; char op = ’ ’ ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 154. 154 CAP´ITULO 13. MEMORIA DIN ´AMICA. PUNTEROS do { op = menu() ; switch (op) { case ’A’: leer(lista) ; escribir(lista) ; break ; case ’B’: lista.clear() ; escribir(lista) ; break ; case ’C’: if (lista.llena()) { cout Error: Lista llena endl ; } else { leer_pos(pos, lista.size()+1) ; leer_dato(dato) ; lista.insertar(pos, dato) ; escribir(lista) ; } break ; case ’D’: if (lista.size() == 0) { cout Error: lista vacia endl ; } else { leer_pos(pos, lista.size()) ; lista.eliminar(pos) ; escribir(lista) ; } break ; case ’E’: if (lista.size() == 0) { cout Error: lista vacia endl ; } else { leer_pos(pos, lista.size()) ; cout Lista[ pos ]: lista.acceder(pos) endl ; escribir(lista) ; } break ; case ’F’: if (lista.size() == 0) { cout Error: lista vacia endl ; } else { leer_pos(pos, lista.size()) ; leer_dato(dato) ; lista.modificar(pos, dato) ; escribir(lista) ; } break ; case ’G’: prueba_asg(lista) ; break ; } } while (op != ’X’) ; } //- fin: main.cpp --------------------------------------------------- Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 155. Cap´ıtulo 14 Introducci´on a los Contenedores de la Biblioteca Est´andar (STL) Los contenedores de la biblioteca est´andar proporcionan un m´etodo general para almacenar y acceder a una colecci´on de elementos homog´eneos, proporcionando cada uno de ellos diferentes caracter´ısticas que los hacen adecuados a diferentes necesidades. En este cap´ıtulo introductorio se mostrar´an las principales operaciones que se pueden realizar con los siguientes contenedores: el tipo vector y el tipo deque (para el tipo array v´ease 6.4). As´ı como con los siguientes adaptadores de contenedores: el tipo stack y el tipo queue de la biblioteca est´andar, que implementan el TAD Pila y el TAD Cola respectivamente. La biblioteca est´andar tambi´en define otros tipos de contenedores optimizados para diferentes circunstancias, pero no ser´an explicados debido a que su estudio requiere mayores conocimientos que los obtenidos en un curso introductorio. Contenedor Tipo Acceso Inserci´on Eliminaci´on stack (adaptador) TAD Pila Directo (al final) Al final Al final queue (adaptador) TAD Cola Directo (al principio) Al final Al principio array Secuencia Directo (pos) – – vector Secuencia Directo (pos) Al final Al final deque Secuencia Directo (pos) Al final + al principio Al final + Al principio list Secuencia Secuencial (bidir) Cualquier posici´on Cualquier posici´on forward_list Secuencia Secuencial (fw) Cualquier posici´on Cualquier posici´on map Asociativo Binario por clave Por Clave Por Clave set Asociativo Binario por clave Por Clave Por Clave multimap Asociativo Binario por clave Por Clave Por Clave multiset Asociativo Binario por clave Por Clave Por Clave unordered_map Asociativo Hash por clave Por Clave Por Clave unordered_set Asociativo Hash por clave Por Clave Por Clave unordered_multimap Asociativo Hash por clave Por Clave Por Clave unordered_multiset Asociativo Hash por clave Por Clave Por Clave Paso de Par´ametros de Contenedores Los contenedores de la biblioteca est´andar se pueden pasar como par´ametros a subprogramas como cualquier otro tipo compuesto, y por lo tanto se aplican los mecanismos de paso de par´ametros para tipos compuestos explicados en la secci´on 6.1. Es decir, los par´ametros de entrada se pasar´an por referencia constante, mientras que los par´ametros de salida y entrada/salida se pasar´an por referencia. As´ı mismo, como norma general, salvo excepciones, no es adecuado que las funciones retornen valores de tipos de los contenedores, debido a la sobrecarga que generalmente conlleva dicha op- eraci´on para el caso de los tipos compuestos. En estos casos suele ser m´as adecuado que el valor se devuelva como un par´ametro por referencia. 155
  • 156. 156CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL) 14.1. Vector El contenedor de tipo vector... representa una secuencia de elementos homog´eneos opti- mizada para el acceso directo a los elementos seg´un su posici´on, as´ı como tambi´en para la inserci´on de elementos al final de la secuencia y para la eliminaci´on de elementos del final de la secuencia. Para utilizar un contenedor de tipo vector se debe incluir la biblioteca est´andar vector, de tal forma que sus definiciones se encuentran dentro del espacio de nombres std: #include vector El tipo vector es similar al tipo array, salvo en el hecho de que los vectores se caracterizan porque su tama˜no puede crecer en tiempo de ejecuci´on dependiendo de las necesidades surgidas durante la ejecuci´on del programa. Por ello, a diferencia de los arrays, no es necesario especificar un tama˜no fijo y predeterminado en tiempo de compilaci´on respecto al n´umero de elementos que pueda contener. El n´umero m´aximo de elementos que se pueden almacenar en una variable de tipo vector no est´a especificado, y se pueden almacenar elementos mientras haya capacidad suficiente en la memoria del ordenador donde se ejecute el programa. N´otese que en los siguientes ejemplos, por simplicidad, tanto el n´umero de elementos como el valor inicial de los mismos est´an especificados mediante valores constantes, sin embargo, tambi´en se pueden especificar como valores de variables y expresiones calculados en tiempo de ejecuci´on. Instanciaci´on del Tipo Vector Se pueden definir expl´ıcitamente instanciaciones del tipo vector para tipos de elementos con- cretos mediante la declaraci´on typedef. Por ejemplo la siguiente definici´on declara el tipo Vect_Int como un tipo vector de n´umeros enteros. typedef std::vectorint Vect_Int ; Las siguientes definiciones declaran el tipo Matriz como un vector de dos dimensiones de n´umeros enteros. typedef std::vectorint Fila ; typedef std::vectorFila Matriz ; Construcci´on de un Objeto de Tipo Vector Se pueden definir variables de un tipo vector previamente definido expl´ıcitamente, o directa- mente de la instanciaci´on del tipo. Por ejemplo, el siguiente c´odigo define dos variables (v1 y v2) de tipo vector de n´umeros enteros, as´ı como la variable m de tipo vector de dos dimensiones de n´umeros enteros. int main() { Vect_Int v1 ; // vector de enteros vac´ıo std::vectorint v2 ; // vector de enteros vac´ıo Matriz m ; // vector de dos dimensiones de enteros vac´ıo // ... } El constructor por defecto del tipo vector crea un objeto vector inicialmente vac´ıo, sin elementos. Posteriormente se podr´an a˜nadir y eliminar elementos cuando sea necesario. Tambi´en es posible crear un objeto vector con un n´umero inicial de elementos con un valor inicial por defecto, al que posteriormente se le podr´an a˜nadir nuevos elementos. Este n´umero inicial de elementos puede ser tanto una constante, como el valor de una variable calculado en tiempo de ejecuci´on. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 157. 14.1. VECTOR 157 int main() { Vect_Int v1(10) ; // vector con 10 enteros con valor inicial 0 Matriz m(10, Fila(5)) ; // matriz de 10x5 enteros con valor inicial 0 // ... } As´ı mismo, tambi´en se puede especificar el valor que tomar´an los elementos creados inicialmente. int main() { Vect_Int v1(10, 3) ; // vector con 10 enteros con valor inicial 3 Matriz m(10, Fila(5, 3)) ; // matriz de 10x5 enteros con valor inicial 3 // ... } Tambi´en es posible inicializar un vector con el contenido de otro vector de igual tipo, invocando al constructor de copia: int main() { Vect_Int v1(10, 3) ; // vector con 10 enteros con valor inicial 3 Vect_Int v2(v1) ; // vector con el mismo contenido de v1 Vect_Int v3 = v1 ; // vector con el mismo contenido de v1 Vect_Int v4 = Vect_Int(7, 5) ; // vector con 7 elementos de valor 5 // ... } Asignaci´on de un Objeto de Tipo Vector Es posible la asignaci´on de vectores de igual tipo. En este caso, se destruye el valor anterior del vector destino de la asignaci´on. int main() { Vect_Int v1(10, 3) ; // vector con 10 enteros con valor inicial 3 Vect_Int v2 ; // vector de enteros vac´ıo v2 = v1 ; // asigna el contenido de v1 a v2 v2.assign(5, 7) ; // asigna 5 enteros con valor inicial 7 v2 = Vect_Int(5, 7) ; // asigna un vector con 5 elementos de valor 7 } As´ı mismo, tambi´en es posible intercambiar (swap en ingl´es) de forma eficiente el contenido entre dos vectores utilizando el m´etodo swap. Por ejemplo: int main() { Vect_Int v1(10, 5) ; // v1 = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 } Vect_Int v2(5, 7) ; // v2 = { 7, 7, 7, 7, 7 } v1.swap(v2) ; // v1 = { 7, 7, 7, 7, 7 } // v2 = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 } } Control sobre los Elementos de un Vector El n´umero de elementos actualmente almacenados en un vector se obtiene mediante el m´etodo size(). Por ejemplo: Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 158. 158CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL) int main() { Vect_Int v1(10, 3) ; // vector con 10 enteros con valor inicial 3 int n = v1.size() ; // n´umero de elementos de v1 } Es posible tanto a˜nadir un elemento al final de un vector mediante el m´etodo push_back(...), como eliminar el ´ultimo elemento del vector mediante el m´etodo pop_back() (en este caso el vector no debe estar vac´ıo). As´ı mismo, el m´etodo clear() elimina todos los elementos del vector. Por ejemplo: int main() { Vect_Int v(5) ; // v = { 0, 0, 0, 0, 0 } for (int i = 1 ; i = 3 ; ++i) { v.push_back(i) ; } // v = { 0, 0, 0, 0, 0, 1, 2, 3 } for (int i = 0 ; i int(v.size()) ; ++i) { cout v[i] ; } // muestra: 0 0 0 0 0 1 2 3 cout endl ; while (v.size() 3) { v.pop_back() ; } // v = { 0, 0, 0 } v.clear() ; // v = { } } Tambi´en es posible cambiar el tama˜no del n´umero de elementos almacenados en el vector. As´ı, el m´etodo resize(...) reajusta el n´umero de elementos contenidos en un vector. Si el n´umero especificado es menor que el n´umero actual de elementos, se eliminar´an del final del vector tantos elementos como sea necesario para reducir el vector hasta el n´umero de elementos especificado. Si por el contrario, el n´umero especificado es mayor que el n´umero actual de elementos, entonces se a˜nadir´an al final del vector tantos elementos como sea necesario para alcanzar el nuevo n´umero de elementos especificado (con el valor especificado o con el valor por defecto). Por ejemplo: int main() { Vect_Int v(10, 1) ; // v = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } v.resize(5) ; // v = { 1, 1, 1, 1, 1 } v.resize(9, 2) ; // v = { 1, 1, 1, 1, 1, 2, 2, 2, 2 } v.resize(7, 3) ; // v = { 1, 1, 1, 1, 1, 2, 2 } v.resize(10) ; // v = { 1, 1, 1, 1, 1, 2, 2, 0, 0, 0 } } Acceso a los Elementos de un Vector Es posible acceder a cada elemento del vector individualmente, seg´un el ´ındice de la posici´on que ocupe, tanto para obtener su valor almacenado, como para modificarlo mediante el operador de indexaci´on []. El primer elemento ocupa la posici´on cero (0), y el ´ultimo elemento almacenado en el vector v ocupa la posici´on v.size()-1. Por ejemplo: int main() { Vect_Int v(10) ; for (int i = 0 ; i int(v.size()) ; ++i) { v[i] = i ; } for (int i = 0 ; i int(v.size()) ; ++i) { cout v[i] ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 159. 14.2. DEQUE 159 cout endl ; } El lenguaje de programaci´on C++ no comprueba que los accesos a los elementos de un vector sean correctos y se encuentren dentro de los l´ımites v´alidos del vector, por lo que ser´a responsabil- idad del programador comprobar que as´ı sea. Sin embargo, en GNU G++, la opci´on de compilaci´on -D_GLIBCXX_DEBUG permite comprobar los ´ındices de acceso. Tambi´en es posible acceder a un determinado elemento mediante el m´etodo at(i), de tal forma que si el valor del ´ındice i est´a fuera del rango v´alido, entonces se lanzar´a una excepci´on out_of_range que abortar´a la ejecuci´on del programa. Se puede tanto utilizar como modificar el valor de este elemento. int main() { Vect_Int v(10) ; for (int i = 0 ; i int(v.size()) ; ++i) { v.at(i) = i ; } for (int i = 0 ; i int(v.size()) ; ++i) { cout v.at(i) ; } cout endl ; } Comparaci´on Lexicogr´afica entre Vectores Es posible realizar la comparaci´on lexicogr´afica (==, !=, , =, , =) entre vectores del mismo tipo siempre y cuando los operadores de comparaci´on est´en definidos para el tipo de los compo- nentes del vector. Por ejemplo: int main() { Vect_Int v1(10, 7) ; // v1 = { 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 } Vect_Int v2(5, 3) ; // v2 = { 3, 3, 3, 3, 3 } if (v1 == v2) { cout Iguales endl ; } else { cout Distintos endl ; } if (v1 v2) { cout Menor endl ; } else { cout Mayor o Igual endl ; } } 14.2. Deque El contenedor de tipo deque... representa una secuencia de elementos homog´eneos opti- mizada para el acceso directo a los elementos seg´un su posici´on, as´ı como tambi´en para la inserci´on de elementos al principio y al final de la secuencia y para la eliminaci´on de elementos del principio y del final de la secuencia. Para utilizar un contenedor de tipo deque se debe incluir la biblioteca est´andar deque, de tal forma que sus definiciones se encuentran dentro del espacio de nombres std: #include deque Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 160. 160CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL) El contenedor deque presenta el mismo interfaz p´ublico que el contenedor vector, pero a˜nade dos m´etodos nuevos para facilitar la inserci´on y eliminaci´on de elementos al principio de la secuencia (push_front(...) y pop_front()). El n´umero m´aximo de elementos que se pueden almacenar en una variable de tipo deque no est´a especificado, y se pueden almacenar elementos mientras haya capacidad suficiente en la memoria del ordenador donde se ejecute el programa. Instanciaci´on del Tipo Deque Se pueden definir expl´ıcitamente instanciaciones del tipo deque para tipos de elementos concre- tos mediante la declaraci´on typedef. Por ejemplo la siguiente definici´on declara el tipo Deque_Int como un tipo deque de n´umeros enteros. typedef std::dequeint Deque_Int ; Construcci´on de un Objeto de Tipo Deque Se pueden definir variables de un tipo deque previamente definido expl´ıcitamente, o directa- mente de la instanciaci´on del tipo. Por ejemplo, el siguiente c´odigo define dos variables (v1 y v2) de tipo deque de n´umeros enteros. int main() { Deque_Int v1 ; // deque de enteros vac´ıo std::dequeint v2 ; // deque de enteros vac´ıo // ... } El constructor por defecto del tipo deque crea un objeto deque inicialmente vac´ıo, sin elementos. Posteriormente se podr´an a˜nadir y eliminar elementos cuando sea necesario. Tambi´en es posible crear un objeto deque con un n´umero inicial de elementos con un valor inicial por defecto, al que posteriormente se le podr´an a˜nadir nuevos elementos. Este n´umero inicial de elementos puede ser tanto una constante, como el valor de una variable calculado en tiempo de ejecuci´on. int main() { Deque_Int v1(10) ; // deque con 10 enteros con valor inicial 0 // ... } As´ı mismo, tambi´en se puede especificar el valor que tomar´an los elementos creados inicialmente. int main() { Deque_Int v1(10, 3) ; // deque con 10 enteros con valor inicial 3 // ... } Tambi´en es posible inicializar un deque con el contenido de otro deque de igual tipo, invocando al constructor de copia: int main() { Deque_Int v1(10, 3) ; // deque con 10 enteros con valor inicial 3 Deque_Int v2(v1) ; // deque con el mismo contenido de v1 Deque_Int v3 = v1 ; // deque con el mismo contenido de v1 Deque_Int v4 = Deque_Int(7, 5) ; // deque con 7 elementos de valor 5 // ... } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 161. 14.2. DEQUE 161 Asignaci´on de un Objeto de Tipo Deque Es posible la asignaci´on de deques de igual tipo. En este caso, se destruye el valor anterior del deque destino de la asignaci´on. int main() { Deque_Int v1(10, 3) ; // deque con 10 enteros con valor inicial 3 Deque_Int v2 ; // deque de enteros vac´ıo v2 = v1 ; // asigna el contenido de v1 a v2 v2.assign(5, 7) ; // asigna 5 enteros con valor inicial 7 v2 = Deque_Int(5, 7) ; // asigna un deque con 5 elementos de valor 7 } As´ı mismo, tambi´en es posible intercambiar (swap en ingl´es) de forma eficiente el contenido entre dos deques utilizando el m´etodo swap. Por ejemplo: int main() { Deque_Int v1(10, 5) ; // v1 = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 } Deque_Int v2(5, 7) ; // v2 = { 7, 7, 7, 7, 7 } v1.swap(v2) ; // v1 = { 7, 7, 7, 7, 7 } // v2 = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 } } Control sobre los Elementos de un Deque El n´umero de elementos actualmente almacenados en un deque se obtiene mediante el m´etodo size(). Por ejemplo: int main() { Deque_Int v1(10, 3) ; // deque con 10 enteros con valor inicial 3 int n = v1.size() ; // n´umero de elementos de v1 } Es posible tanto a˜nadir un elemento al final de un deque mediante el m´etodo push_back(...), como eliminar el ´ultimo elemento del deque mediante el m´etodo pop_back() (en este caso el deque no debe estar vac´ıo). As´ı mismo, el m´etodo clear() elimina todos los elementos del deque. Por ejemplo: int main() { Deque_Int v(5) ; // v = { 0, 0, 0, 0, 0 } for (int i = 1 ; i = 3 ; ++i) { v.push_back(i) ; } // v = { 0, 0, 0, 0, 0, 1, 2, 3 } for (int i = 1 ; i = 2 ; ++i) { v.push_front(i) ; } // v = { 2, 1, 0, 0, 0, 0, 0, 1, 2, 3 } for (int i = 0 ; i int(v.size()) ; ++i) { cout v[i] ; } // muestra: 2 1 0 0 0 0 0 1 2 3 cout endl ; while (v.size() 5) { v.pop_back() ; } // v = { 2, 1, 0, 0, 0 } while (v.size() 3) { v.pop_front() ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 162. 162CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL) } // v = { 0, 0, 0 } v.clear() ; // v = { } } Tambi´en es posible cambiar el tama˜no del n´umero de elementos almacenados en el deque. As´ı, el m´etodo resize(...) reajusta el n´umero de elementos contenidos en un deque. Si el n´umero especificado es menor que el n´umero actual de elementos, se eliminar´an del final del deque tantos elementos como sea necesario para reducir el deque hasta el n´umero de elementos especificado. Si por el contrario, el n´umero especificado es mayor que el n´umero actual de elementos, entonces se a˜nadir´an al final del deque tantos elementos como sea necesario para alcanzar el nuevo n´umero de elementos especificado (con el valor especificado o con el valor por defecto). Por ejemplo: int main() { Deque_Int v(10, 1) ; // v = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } v.resize(5) ; // v = { 1, 1, 1, 1, 1 } v.resize(9, 2) ; // v = { 1, 1, 1, 1, 1, 2, 2, 2, 2 } v.resize(7, 3) ; // v = { 1, 1, 1, 1, 1, 2, 2 } v.resize(10) ; // v = { 1, 1, 1, 1, 1, 2, 2, 0, 0, 0 } } Acceso a los Elementos de un Deque Es posible acceder a cada elemento del deque individualmente, seg´un el ´ındice de la posici´on que ocupe, tanto para obtener su valor almacenado, como para modificarlo mediante el operador de indexaci´on []. El primer elemento ocupa la posici´on cero (0), y el ´ultimo elemento almacenado en el deque v ocupa la posici´on v.size()-1. Por ejemplo: int main() { Deque_Int v(10) ; for (int i = 0 ; i int(v.size()) ; ++i) { v[i] = i ; } for (int i = 0 ; i int(v.size()) ; ++i) { cout v[i] ; } cout endl ; } El lenguaje de programaci´on C++ no comprueba que los accesos a los elementos de un deque sean correctos y se encuentren dentro de los l´ımites v´alidos del deque, por lo que ser´a responsabil- idad del programador comprobar que as´ı sea. Sin embargo, en GNU G++, la opci´on de compilaci´on -D_GLIBCXX_DEBUG permite comprobar los ´ındices de acceso. Tambi´en es posible acceder a un determinado elemento mediante el m´etodo at(i), de tal forma que si el valor del ´ındice i est´a fuera del rango v´alido, entonces se lanzar´a una excepci´on out_of_range que abortar´a la ejecuci´on del programa. Se puede tanto utilizar como modificar el valor de este elemento. int main() { Deque_Int v(10) ; for (int i = 0 ; i int(v.size()) ; ++i) { v.at(i) = i ; } for (int i = 0 ; i int(v.size()) ; ++i) { cout v.at(i) ; } cout endl ; } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 163. 14.3. STACK 163 Comparaci´on Lexicogr´afica entre Deques Es posible realizar la comparaci´on lexicogr´afica (==, !=, , =, , =) entre deques del mismo tipo siempre y cuando los operadores de comparaci´on est´en definidos para el tipo de los componentes del deque. Por ejemplo: int main() { Deque_Int v1(10, 7) ; // v1 = { 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 } Deque_Int v2(5, 3) ; // v2 = { 3, 3, 3, 3, 3 } if (v1 == v2) { cout Iguales endl ; } else { cout Distintos endl ; } if (v1 v2) { cout Menor endl ; } else { cout Mayor o Igual endl ; } } 14.3. Stack El adaptador de contenedor de tipo stack... representa el tipo abstracto de datos Pila, como una colecci´on ordenada (seg´un el orden de inserci´on) de elementos homog´eneos donde se pueden introducir elementos (manteniendo el orden de inserci´on) y sacar elementos de ella (en orden inverso al orden de inserci´on), de tal forma que el primer elemento que sale de la pila es el ´ultimo elemento que ha sido introducido en ella. Adem´as, tambi´en es posible comprobar si la pila contiene elementos, de tal forma que no se podr´a sacar ning´un elemento de una pila vac´ıa. Para utilizar un adaptador de contenedor de tipo stack se debe incluir la biblioteca est´andar stack, de tal forma que sus definiciones se encuentran dentro del espacio de nombres std: #include stack El n´umero m´aximo de elementos que se pueden almacenar en una variable de tipo stack no est´a es- pecificado, y se pueden introducir elementos mientras haya capacidad suficiente en la memoria del ordenador donde se ejecute el programa. Instanciaci´on del Tipo Stack Se pueden definir expl´ıcitamente instanciaciones del tipo stack para tipos de elementos concre- tos mediante la declaraci´on typedef. Por ejemplo la siguiente definici´on declara el tipo Stack_Int como un tipo pila de n´umeros enteros. typedef std::stackint Stack_Int ; Construcci´on de un Objeto de Tipo Pila Se pueden definir variables de un tipo pila previamente definido expl´ıcitamente, o directamente de la instanciaci´on del tipo. Por ejemplo, el siguiente c´odigo define dos variables (s1 y s2) de tipo pila de n´umeros enteros. int main() { Stack_Int s1 ; // stack de enteros vac´ıo std::stackint s2 ; // stack de enteros vac´ıo // ... } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 164. 164CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL) El constructor por defecto del tipo stack crea un objeto stack inicialmente vac´ıo, sin elementos. Posteriormente se podr´an a˜nadir y eliminar elementos cuando sea necesario. Tambi´en es posible inicializar una pila con el contenido de otra pila de igual tipo: int main() { Stack_Int s1 ; // stack de enteros vac´ıo // ... Stack_Int s2(s1) ; // stack con el mismo contenido de s1 Stack_Int s3 = s1 ; // stack con el mismo contenido de s1 Stack_Int s4 = Stack_Int() ; // copia el contenido de stack vac´ıo // ... } Asignaci´on de un Objeto de Tipo Pila Es posible la asignaci´on de pilas de igual tipo. En este caso, se destruye el valor anterior de la pila destino de la asignaci´on. int main() { Stack_Int s1 ; // stack de enteros vac´ıo Stack_Int s2 ; // stack de enteros vac´ıo s2 = s1 ; // asigna el contenido de s1 a s2 s2 = Stack_Int() ; // asigna el contenido de stack vac´ıo } Control y Acceso a los Elementos de una Pila Es posible tanto a˜nadir un elemento una pila mediante el m´etodo push(...), como eliminar el ´ultimo elemento introducido en la pila mediante el m´etodo pop() (en este caso la pila no debe estar vac´ıa). Por otra parte, el m´etodo empty() indica si una pila est´a vac´ıa o no, mientras que el n´umero de elementos actualmente almacenados en una pila se obtiene mediante el m´etodo size(). As´ı mismo, se puede acceder al ´ultimo elemento introducido en la pila mediante el m´etodo top(). Se puede tanto utilizar como modificar el valor de este elemento (en este caso la pila no debe estar vac´ıa). Por ejemplo: int main() { Stack_Int s ; // s = { } for (int i = 1 ; i = 3 ; ++i) { s.push(i) ; } // s = { 1, 2, 3 } s.top() = 5 ; // s = { 1, 2, 5 } s.pop() ; // s = { 1, 2 } s.pop() ; // s = { 1 } s.push(7) ; // s = { 1, 7 } s.push(9) ; // s = { 1, 7, 9 } cout s.size() endl ; // muestra: 3 while (! s.empty()) { cout s.top() ; // muestra: 9 7 1 s.pop() ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 165. 14.4. QUEUE 165 } // s = { } cout endl ; } Comparaci´on Lexicogr´afica entre Pilas Es posible realizar la comparaci´on lexicogr´afica (==, !=, , =, , =) entre pilas del mismo tipo siempre y cuando los operadores de comparaci´on est´en definidos para el tipo de los componentes de la pila. Por ejemplo: int main() { Stack_Int s1 ; Stack_Int s2 ; // ... if (s1 == s2) { cout Iguales endl ; } else { cout Distintos endl ; } if (s1 s2) { cout Menor endl ; } else { cout Mayor o Igual endl ; } } 14.4. Queue El adaptador de contenedor de tipo queue... representa el tipo abstracto de datos Cola, como una colecci´on ordenada (seg´un el orden de inserci´on) de elementos homog´eneos donde se pueden introducir elementos (manteniendo el orden de inserci´on) y sacar elementos de ella (en el mismo orden al orden de inserci´on), de tal forma que el primer elemento que sale de la cola es el primer elemento que ha sido introducido en ella. Adem´as, tambi´en es posible comprobar si la cola contiene elementos, de tal forma que no se podr´a sacar ning´un elemento de una cola vac´ıa. Para utilizar un adaptador de contenedor de tipo queue se debe incluir la biblioteca est´andar queue, de tal forma que sus definiciones se encuentran dentro del espacio de nombres std: #include queue El n´umero m´aximo de elementos que se pueden almacenar en una variable de tipo queue no est´a es- pecificado, y se pueden introducir elementos mientras haya capacidad suficiente en la memoria del ordenador donde se ejecute el programa. Instanciaci´on del Tipo Queue Se pueden definir expl´ıcitamente instanciaciones del tipo queue para tipos de elementos concre- tos mediante la declaraci´on typedef. Por ejemplo la siguiente definici´on declara el tipo Queue_Int como un tipo cola de n´umeros enteros. typedef std::queueint Queue_Int ; Construcci´on de un Objeto de Tipo Cola Se pueden definir variables de un tipo cola previamente definido expl´ıcitamente, o directamente de la instanciaci´on del tipo. Por ejemplo, el siguiente c´odigo define dos variables (c1 y c2) de tipo cola de n´umeros enteros. Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 166. 166CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL) int main() { Queue_Int c1 ; // queue de enteros vac´ıo std::queueint c2 ; // queue de enteros vac´ıo // ... } El constructor por defecto del tipo queue crea un objeto queue inicialmente vac´ıo, sin elementos. Posteriormente se podr´an a˜nadir y eliminar elementos cuando sea necesario. Tambi´en es posible inicializar una cola con el contenido de otra cola de igual tipo: int main() { Queue_Int c1 ; // queue de enteros vac´ıo // ... Queue_Int c2(c1) ; // queue con el mismo contenido de c1 Queue_Int c3 = c1 ; // queue con el mismo contenido de c1 Queue_Int c4 = Stack_Int() ; // copia el contenido de queue vac´ıo // ... } Asignaci´on de un Objeto de Tipo Cola Es posible la asignaci´on de colas de igual tipo. En este caso, se destruye el valor anterior de la cola destino de la asignaci´on. int main() { Queue_Int c1 ; // queue de enteros vac´ıo Queue_Int c2 ; // queue de enteros vac´ıo c2 = c1 ; // asigna el contenido de c1 a c2 c2 = Queue_Int() ; // asigna el contenido de queue vac´ıo } Control y Acceso a los Elementos de una Cola Es posible tanto a˜nadir un elemento una cola mediante el m´etodo push(...), como eliminar el primer elemento introducido en la cola mediante el m´etodo pop() (en este caso la cola no debe estar vac´ıa). Por otra parte, el m´etodo empty() indica si una cola est´a vac´ıa o no, mientras que el n´umero de elementos actualmente almacenados en una cola se obtiene mediante el m´etodo size(). As´ı mismo, se puede acceder al ´ultimo elemento introducido en la cola mediante el m´etodo back(), as´ı como al primer elemento introducido en ella mediante el m´etodo front(). Se pueden tanto utilizar como modificar el valor de estos elementos (en este caso la cola no debe estar vac´ıa). Por ejemplo: int main() { Queue_Int c ; // c = { } for (int i = 1 ; i = 3 ; ++i) { c.push(i) ; } // c = { 1, 2, 3 } c.front() = 6 ; // c = { 6, 2, 3 } c.back() = 5 ; // c = { 6, 2, 5 } c.pop() ; // c = { 2, 5 } c.pop() ; // c = { 5 } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 167. 14.5. RESOLUCI ´ON DE PROBLEMAS UTILIZANDO CONTENEDORES 167 c.push(7) ; // c = { 5, 7 } c.push(9) ; // c = { 5, 7, 9 } cout c.size() endl ; // muestra: 3 while (! c.empty()) { cout c.front() ; // muestra: 5 7 9 c.pop() ; } // c = { } cout endl ; } Comparaci´on Lexicogr´afica entre Colas Es posible realizar la comparaci´on lexicogr´afica (==, !=, , =, , =) entre colas del mismo tipo siempre y cuando los operadores de comparaci´on est´en definidos para el tipo de los componentes de la cola. Por ejemplo: int main() { Queue_Int c1 ; Queue_Int c2 ; // ... if (c1 == c2) { cout Iguales endl ; } else { cout Distintos endl ; } if (c1 c2) { cout Menor endl ; } else { cout Mayor o Igual endl ; } } 14.5. Resoluci´on de Problemas Utilizando Contenedores Ejemplo 1: Agentes de Ventas Dise˜ne un programa que lea y almacene las ventas realizadas por unos agentes de ventas, de tal forma que se eliminen aquellos agentes cuyas ventas sean inferiores a la media de las ventas realizadas. //------------------------------------------------------------------ #include iostream #include vector #include string using namespace std ; struct Agente { string nombre ; double ventas ; } ; typedef vectorAgente VAgentes ; void leer (VAgentes v) { v.clear() ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 168. 168CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL) Agente a ; cout Introduzca Nombre: ; getline(cin, a.nombre) ; while (( ! cin.fail()) (a.nombre.size() 0)) { cout Introduzca Ventas: ; cin a.ventas ; cin.ignore(1000, ’n’) ; v.push_back(a) ; cout Introduzca Nombre: ; getline(cin, a.nombre) ; } } double media(const VAgentes v) { double suma=0.0 ; for (int i = 0 ; i int(v.size()) ; ++i) { suma += v[i].ventas ; } return suma/double(v.size()) ; } void purgar(VAgentes v, double media) { // altera el orden secuencial de los elementos int i = 0 ; while (i int(v.size())) { if (v[i].ventas media) { v[i] = v[v.size()-1] ; v.pop_back() ; } else { ++i ; } } } void purgar_ordenado(VAgentes v, double media) { // mantiene el orden secuencial de los elementos int k = 0 ; while ((k int(v.size()))(v[k].ventas = media)) { ++k; } for (int i = k ; i int(v.size()) ; ++i) { if(v[i].ventas = media) { v[k] = v[i] ; ++k ; } } v.resize(k) ; } void imprimir(const VAgentes v) { for (int i = 0 ; i int(v.size()) ; ++i) { cout v[i].nombre v[i].ventas endl ; } } Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 169. 14.5. RESOLUCI ´ON DE PROBLEMAS UTILIZANDO CONTENEDORES 169 int main () { VAgentes v ; leer(v) ; purgar(v, media(v)) ; imprimir(v) ; } //------------------------------------------------------------------ Ejemplo 2: Multiplicaci´on de Matrices Dise˜ne un programa que lea dos matrices de tama˜nos arbitrarios y muestre el resultado de multiplicar ambas matrices. //------------------------------------------------------------------ #include vector #include iostream #include iomanip using namespace std ; typedef vector double Fila ; typedef vector Fila Matriz ; void imprimir(const Matriz m) { for (int f = 0 ; f int(m.size()) ; ++f) { for (int c = 0 ; c int(m[f].size()) ; ++c) { cout setw(10) setprecision(4) m[f][c] ; } cout endl ; } } void leer(Matriz m) { int nf, nc ; cout Introduzca el numero de filas: ; cin nf ; cout Introduzca el numero de columnas: ; cin nc ; m = Matriz(nf, Fila (nc)) ; // copia de la matriz completa cout Introduzca los elementos: endl ; for (int f = 0 ; f int(m.size()) ; ++f) { for (int c = 0 ; c int(m[f].size()) ; ++c) { cin m[f][c] ; } } } // otra opci´on m´as eficiente para la lectura de vectores void leer_2(Matriz m) { int nf, nc ; cout Introduzca el numero de filas: ; cin nf ; cout Introduzca el numero de columnas: ; cin nc ; Matriz aux(nf, Fila (nc)) ; cout Introduzca los elementos: endl ; Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 170. 170CAP´ITULO 14. INTRODUCCI ´ON A LOS CONTENEDORES DE LA BIBLIOTECA EST ´ANDAR (STL) for (int f = 0 ; f int(aux.size()) ; ++f) { for (int c = 0 ; c int(aux[f].size()) ; ++c) { cin aux[f][c] ; } } m.swap(aux) ; // evita la copia de la matriz completa } void multiplicar(const Matriz m1, const Matriz m2, Matriz m3) { m3.clear() ; if ((m1.size() 0) (m2.size() 0) (m2[0].size() 0) (m1[0].size() == m2.size())){ Matriz aux(m1.size(), Fila(m2[0].size())) ; for (int f = 0 ; f int(aux.size()) ; ++f) { for (int c = 0 ; c int(aux[f].size()) ; ++c) { double suma = 0.0 ; for (int k = 0 ; k int(m2.size()) ; ++k) { suma += m1[f][k] * m2[k][c] ; } aux[f][c] = suma ; } } m3.swap(aux) ; // evita la copia de la matriz completa } } int main() { Matriz m1, m2, m3 ; leer(m1) ; leer(m2) ; multiplicar(m1, m2, m3) ; if (m3.size() == 0) { cout Error en la multiplicaci´on de Matrices endl ; } else { imprimir(m3) ; } } //------------------------------------------------------------------ Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
  • 171. Cap´ıtulo 15 Bibliograf´ıa El Lenguaje de Programaci´on C. 2.Ed. B.Kernighan, D. Ritchie Prentice Hall 1991 The C++ Programming Language. Special Edition B. Stroustrup Addison Wesley 2000 171
  • 172. ´Indice alfab´etico ::, 99 ´ambito de visibilidad, 30 agregado, 56 acceso, 57 multidimensional, 61 size, 57 tama˜no, 57 array, 56 acceso, 57 multidimensional, 61 size, 57 tama˜no, 57 b´usqueda binaria, 69 lineal, 69 biblioteca ansic cctype, 77 cmath, 77 cstdlib, 78 bloque, 29 buffer, 28 de entrada, 28 de salida, 28 cin, 26 comentarios, 13 compilaci´on separada, 97 constantes literales, 18 simb´olicas, 18 declaraci´on, 19 constantes literales, 13 conversiones aritm´eticas, 21 conversiones de tipo autom´aticas, 20 expl´ıcitas, 21 conversiones enteras, 21 cout, 25 declaraci´on global, 29 ´ambito de visibilidad, 29 local, 29 ´ambito de visibilidad, 29 vs. definici´on, 15 declaracion adelantada, 145 definici´on vs. declaraci´on, 15 delete, 141 delimitadores, 13 ejecuci´on secuencial, 29 enlazado, 97 entrada, 26 espacios de nombre ::, 99 espacios de nombre an´onimos, 99 espacios de nombre, 98 using namespace, 98 espacios en blanco, 13 estructura, 54 fichero de encabezamiento guardas, 96 funciones, 37 declaraci´on, 42 definici´on, 38 inline, 42 return, 39 guardas, 96 identificadores, 13 inline, 42 listas enlazadas declaracion adelantada, 145 m´odulo implementaci´on, 95 interfaz, 95 main, 11 memoria din´amica, 141 abstraccion, 148 delete, 141 enlaces, 144 listas enlazadas gen´erica, 149 172
  • 173. ´INDICE ALFAB´ETICO 173 new, 141 new, 141 operadores, 13, 19 aritm´eticos, 20 bits, 20 condicional, 20 l´ogicos, 20 relacionales, 20 ordenaci´on burbuja, 70 inserci´on, 71 intercambio, 70 selecci´on, 71 palabras reservadas, 12 par´ametros de entrada, 39 par´ametros de entrada/salida, 40 par´ametros de salida, 40 paso por referencia constante, 47 paso por referencia, 40 paso por valor, 39 procedimientos, 37 declaraci´on, 42 definici´on, 38 inline, 42 programa C++, 11 promociones, 21 prototipo, 42 registro, 54 return, 39 salida, 25 secuencia de sentencias, 29 sentencia asignaci´on, 30 incremento/decremento, 30 iteraci´on, 33 do while, 35 for, 34 while, 33 selecci´on, 31 if, 31 switch, 32 tipo, 15 tipos cuadro resumen, 17 puntero, 140 acceso, 142 operaciones, 142 par´ametros, 144 tipos compuestos array, 56 tipos simples escalares, 16 tipos compuestos, 15, 47 array, 56 acceso, 57 multidimensional, 61 size, 57 par´ametros, 47 struct, 54 tipos simples, 15 enumerado, 17 ordinales, 16 predefinidos, 15 bool, 15 char, 15 double, 16 float, 16 int, 16 long, 16 long long, 16 short, 16 unsigned, 16 using namespace, 98 variables declaraci´on, 19 Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga