Fundamentos de Programacion Con C++.New
Fundamentos de Programacion Con C++.New
Fundamentos de Programaci
´on con
el Lenguaje de Programacio´n
C++
• 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.
Pr´ologo................................................................................................................................................9
I Programaci´on B´asica 11
1. Un Programa C++ 13
2. Tipos Simples 17
2.1. Declaraci´on Vs. Definici´on............................................................................................................................17
2.2. Tipos Simples Predefinidos.................................................................................................................................17
2.3. Tipos Simples Enumerados.................................................................................................................................19
2.4. Constantes y Variables.......................................................................................................................................20
2.5. Operadores 21
2.6. Conversiones Autom´aticas (Impl´ıcitas) de Tipos..........................................................................................23
2.7. Conversiones Expl´ıcitas de Tipos.......................................................................................................................23
2.8. Tabla ASCII 25
2.9. Algunas Consideraciones Respecto a Operaciones con Nu´meros Reales....................................................25
4. Estructuras de Control 33
4.1. Sentencia, Secuencia y Bloque...........................................................................................................................33
4.2. Declaraciones Globales y Locales.......................................................................................................................33
4.3. Sentencias de Asignaci´on.................................................................................................................................34
4.4. Sentencias de Selecci´on....................................................................................................................................35
4.5. Sentencias de Iteraci´on. Bucles........................................................................................................................37
4.6. Programaci´on Estructurada.............................................................................................................................39
4.7. Ejemplos 39
3
´INDICE GENERAL 1
6. Tipos Compuestos 51
6.1. Paso de Par´ametros de Tipos Compuestos....................................................................................................51
6.2. Cadenas de Caracteres en C++: el Tipo String.............................................................................................52
6.3. Registros o Estructuras.......................................................................................................................................59
6.4. Agregados: el Tipo Array...................................................................................................................................62
6.5. Resoluci´on de Problemas Utilizando Tipos Compuestos...............................................................................71
7. Bu´squeda y Ordenaci´on 77
7.1. Bu´squeda Lineal (Secuencial)...........................................................................................................................77
7.2. Bu´squeda Binaria..............................................................................................................................................78
7.3. Ordenaci´on por Intercambio (Burbuja)...........................................................................................................79
7.4. Ordenaci´on por Selecci´on..............................................................................................................................80
7.5. Ordenaci´on por Inserci´on..............................................................................................................................80
7.6. Ordenaci´on por Inserci´on Binaria...................................................................................................................81
7.7. Aplicaci´on de los Algoritmos de Bu´squeda y Ordenaci´on......................................................................82
20.6. Especializaciones...............................................................................................................................................258
20.7. Meta-programaci´on.......................................................................................................................................259
20.8.SFINAE 260
24.10.Multimap.................................................................................................................................297
24.11.Set..............................................................................................................................................298
24.12.Multiset 299
24.13.Bitset 299
24.14.Iteradores 300
24.15.Directos 300
24.16.Inversos 301
24.17.Inserters 302
24.18. Stream Iterators.......................................................................................................................302
24.19.Operaciones sobre Iteradores...........................................................................................................................306
24.20. Objetos Funci´on y Predicados............................................................................................306
24.21.Algoritmos 309
24.22. Garant´ıas (Excepciones) de Operaciones sobre Contenedores............................................311
24.23. Num´ericos..............................................................................................................................312
24.24. L´ımites....................................................................................................................................312
24.25. Run Time Type Information (RTTI)....................................................................................313
D. El Preprocesador 329
G. Bibliograf´ıa 335
´Indice 335
Este manual pretende ser una gu´ıa de referencia para la utilizaci´on del lenguaje de programaci
´on C++ en el desarrollo de programas. Es t ´a orientada a alumnos de primer curso de
programaci´on de Ingenier´ıa Inform´atica. No obstante, hay algunos cap´ıtulos y secciones que
presentan conceptos avanzados y requieren mayores conocimientos por parte del lector, y por lo
tanto exceden de los conocimientos b´asicos requeridos para un alumno de primer curso de
programaci´on. Estos cap´ıtulos y secciones que presentan o requieren conceptos avanzados se
encuentran marcados con un s´ımbolo A , y no es necesario que sean le´ıdos por el lector, a los
cuales po dr´a acceder en cualquier otro momento posterior. Otros cap´ıtulos y secciones
muestran estructuras y t´ecnicas de programaci´on obsoletas y no recomendadas en C++, sin
embargo son mostradas en este manual por completitud.
Estos cap´ıtulos y secciones est´an marcados con el s´ımboloOBS .
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
“ensen˜ar 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.
ES UNA VERSIO´ N PRELIMINAR, INCOMPLETA Y SE ENCUENTRA AC-
TUALMENTE BAJO DESARROLLO. Se difunde en la creencia de que puede ser u´ til, a u´ n
siendo una versi´on preliminar.
La u´ltima versi´on de este documento puede ser descargada desde la siguiente p´agina web:
http://www.lcc.uma.es/%7Evicente/docencia/index.html
1
1 ´INDICE GENERAL
1
Parte I
Programacio´n B´asica
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 ´a s adelante consideraremos programas complejos cuyo
c´odigo se encuentra distribuido entre varios ficheros (v´ease 11).
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 ser ´a global a todo el fichero (desde el punto donde ha sido declarado
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 nu´ mero entero que indica al Sistema Operativo el estado de terminaci´on
tras la ejecuci´on del programa (un nu´mero 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.
cuya ejecuci´on podr´a ser como se indica a continuaci´on, donde el texto enmarcado
corresponde a una entrada de datos del usuario:
13
1 CAP´ITULO 1. UN PROGRAMA C++
Ejemplo de un programa que imprime los nu´meros menores que uno dado por teclado.
Palabras reservadas
Son un conjunto de palabras que tienen un significado predeterminado para el compilador,
y s ´o lo 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.
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:
• 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’
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/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.
El tipo bool se utiliza para representar valores l´ogicos o booleanos, es decir, los valores
“Ver- dadero” o “Falso” o las constantes l´ogicas true y false. Suele almacenarse en el taman˜o
de palabra m ´a s pequen˜o posible direccionable (normalmente 1 byte).
El tipo char se utiliza para representar los caracteres, es decir, s´ımbolos alfanum´ericos (d´ıgitos
y letras mayu´sculas y minu´sculas), de puntuaci´on, espacios, control, etc. Normalmente
utiliza un espacio de almacenamiento de 1 byte (8 bits) y puede representar 256 valores
diferentes.
17
1 CAP´ITULO 2. TIPOS SIMPLES
El tipo int se utiliza para representar los nu´meros Enteros. Su representaci´on suele
coincidir con la definida por el taman˜o de palabra del procesador sobre el que va a ser
ejecutado, hoy d´ıa puede ser de 4 bytes (32 bits), aunque actualmente en los ordenadores m ´a s
modernos, 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 nu´meros Naturales
(en- teros positivos) utilizando el modificador unsigned.
Tanto el tipo float como el double se utilizan para representar nu´meros 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 nu´meros 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). 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 u´nico antecesor y un u´nico
sucesor. A
´estos se les conoce como tipos Ordinales (en terminolog´ıa C++, tambi´en se les conoce como
tipos integrales, o enteros).
Biblioteca climits
#include <climits>
Biblioteca cfloat
#include <cfloat>
Para ver el taman˜o (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 nu´mero 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)]:
enum Color {
ROJO,
AZUL,
AMARILLO
};
De esta forma definimos el tipo Color, que definir´a una entidad (constante o variable) que
po dr´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:
enum Meses {
Enero, Febrero, Marzo, Abril, Mayo, Junio, Julio,
Agosto, Septiembre, Octubre, Noviembre, Diciembre
};
Es posible asignar valores concretos a las enumeraciones. Aquellos valores que no se especifiquen
ser´an consecutivos a los anteriores. Si no se especifica ningu´n valor, al primer valor se le
asigna el cero.
enum Color {
ROJO = 1,
AZUL,
AMARILLO = 4
};
cout << ROJO << " " << AZUL << " " << AMARILLO << endl; // 1 2 4
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
As´ı mismo, ciertos caracteres constantes tienen un significado especial (caracteres de escape):
"Hola Pepe"
"Hola\nJuan\n"
"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:
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 (=). 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 po dr´a
asignar un valor inicial en la definici´on (mediante el s´ımbolo =), si no se les asigna ningu´n
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 (de mayor a menor orden de precedencia):
L´ogicos, s´o lo aplicable operandos de tipo booleano. Tanto el operador && como el operador
|| se evalu´an 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 logico (Si valor1 es true entonces true, en otro caso valor2)
x ! x x y x && y x || y
F F F F F
T F T F T
T F F T
T T T T
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.
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 nu´mero 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.
2. En otro caso, se realizan promociones enteras (v´ease sec. 2.6) sobre ambos operandos:
enum Color {
ROJO, AZUL, AMARILLO
};
int main()
{
Color c = ROJO;
c = Color(c + 1);
// ahora c tiene el valor AZUL
}
Esta distinci´on permite al compilador aplicar una verificaci´on de tipos m´ınima para static_cast
y haga m ´a s f´acil al programador la bu´squeda de conversiones peligrosas representadas por
reinterpret_cast. Algunos static_cast son portables, pero pocos reinterpret_cast lo son.
#include <iostream>
#include <string>
using namespace std;
inline double abs(double x)
{
return x < 0 ? -x : x;
}
inline bool iguales(double x, double y)
{
const double ERROR_PREC = 1e-8;
return abs(x - y) < ERROR_PREC;
}
void check_iguales(double x, double y, const string& op1, const string& op2)
{
//--------------------------------
if (x == y) {
cout << " Correcto ("<<op1<<" == "<<op2<<"): ("<<x<<" == "<<y<<")"
<<endl;
} else {
cout << " Distintos ("<<op1<<" != "<<op2<<"): ("<<x<<" != "<<y
<<") Error. Diferencia: "<< abs(x-y) <<endl;
}
//--------------------------------
if (iguales(x, y)) {
cout << " Funci´on: Iguales("<<op1<<", "<<op2<<") Correcta"<<endl;
} else {
cout << " Funci´on: Iguales("<<op1<<", "<<op2<<") Error !!"<<endl;
}
//--------------------------------
}
int main()
{
//--------------------------------
// Error en conversi´on float a double
cout << "* Check Conversi´on float a double" << endl;
check_iguales(0.1F, 0.1, "0.1F", "0.1");
//--------------------------------
// Error en p´erdida de precisi´on
cout << "* Check Precisi´on" << endl;
check_iguales(0.01, (0.1*0.1), "0.01", "(0.1*0.1)");
//--------------------------------
// Error de apreciaci´on
cout << "* Check Apreciaci´on" << endl;
check_iguales(((10e30+ -10e30)+1.0), (10e30+(-10e30+1.0)),
"((10e30+ -10e30)+1.0)", "(10e30+(-10e30+1.0))");
//--------------------------------
}
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 11), 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;
}
27
1 CAP´ITULO 3. ENTRADA Y SALIDA DE DATOS BA
´ SICA
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:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
bool x = true;
cout << boolalpha << x; // escribe los booleanos como ’false’ o ’true’
Adem´as, es posible activar o desactivar las siguientes marcas (flags) para modificar el compor-
tamiento de la salida de datos:
cout.setf(0, ios::floatfield);
cout << setprecision(2) << 4.567; // imprime 4.6
cout.setf(ios::fixed, ios::floatfield);
cout << setprecision(2) << 4.567; // imprime 4.57
cout.setf(ios::scientific, ios::floatfield);
cout << setprecision(2) << 4.567; // imprime 4.57e+00
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
...
}
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 nu´mero determinado de caracteres del flujo de entrada, o hasta 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
cin.ignore(numeric_limits<streamsize>::max(), ’\n’) ; // elimina hasta nueva-l´ınea
}
Para utilizar numeric_limits es necesario incluir al principio del programa la biblioteca limits.
La entrada y salida de cadenas de caracteres se puede ver en los cap´ıtulos correspondientes
(cap. 6.2 y cap. 8.2).
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
1 CAP´ITULO 3. ENTRADA Y SALIDA DE DATOS BA
´ SICA
3.3. El “Buffer” de Entrada y el “Buffer” de Salida A
Ningu´n 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 “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;
Dado un flujo de entrada, es posible conocer el pr´oximo car´acter (sin eliminarlo del
buffer de entrada) que se leer´a en la siguiente operaci´on de entrada:
char c = char(cin.peek());
As´ı mismo, tambi´en es posible devolver al flujo (buffer) de entrada el u´ltimo car´acter
previamente le´ıdo.
cin.unget();
o incluso devolver al flujo (buffer) de entrada otro car´acter diferente al previamente le´ıdo:
cin.putback(c);
En caso de haber fallado, para comprobar si se ha alcanzado el final de la entrada de datos (final
de fichero), o el flujo se encuentra en mal estado:
cin >> valor;
if (cin.fail()) {
// ocurri´o un error y pr´oxima operaci´on fallar´a
// (cin.rdstate() & (ios::badbit | ios::failbit))!=0
if (cin.bad()) {
// flujo en mal estado
//(cin.rdstate() & ios::badbit) != 0
} else if (cin.eof()) {
// fin de fichero
// (cin.rdstate() & ios::eofbit) != 0
} else {
// ocurri´o un error de formato de entrada
//(cin.rdstate() & ios::failbit) != 0
}
} else {
// lectura de datos correcta
// ...
}
Como se ha visto en el ejemplo, la operaci´on rdstate() sobre el flujo de datos proporciona infor-
maci´on sobre el motivo del error, comprob´andose con respecto a las siguientes marcas (flags):
goodbit: Indica que todo es correcto (goodbit == iostate(0)).
eofbit: Indica que una operaci´on de entrada alcanz´o el final de una secuencia de entrada.
failbit: Indica que una operaci´on de entrada fall´o al leer los caracteres esperados, o que una
operaci´on de salida fall´o al generar los caracteres deseados.
badbit: Indica p´erdida de integridad en una secuencia de entrada o salida (tal como un error irrecu-
perable de un fichero).
Es posible recuperar el estado err´oneo de un determinado flujo mediante la operaci´on clear()
o setstate() sobre el flujo de datos.
FLAGS: [ios::goodbit | ios::failbit | ios::eofbit | ios:badbit]
Por ejemplo, para leer un nu´mero y restaurar el estado en caso de lectura err´onea:
void leer(int& numero, bool& ok)
{
cin >> numero;
if (cin.fail()) {
ok = false;
if (! cin.eof() && ! cin.bad()) {
cin.clear();
cin.ignore(10000, ’\n’);
}
Es posible configurar el sistema de entrada y salida de C++ para hacer que lance una excepci´on
(v´ease 12) del tipo ios::failure cuando ocurra algu´n error:
ios::iostate old_state = cin.exceptions();
cin.exceptions(ios::badbit | ios::failbit );
cout.exceptions(ios::badbit | ios::failbit );
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´ol o
veremos algunas de ellas y utilizadas en contextos y situaciones restringidas.
33
1 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;
}
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
}
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 evalu´ a 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. Por ejemplo:
Sentencia Equivalencia
++variable; variable = variable + 1;
--variable; variable = variable - 1;
variable++; variable = variable + 1;
variable--; variable = variable - 1;
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.
#include <iostream>
using namespace std;
int main ()
{
unsigned a, b, c;
cin >> a >> b >> c;
unsigned mayor = a;
if (b > mayor) {
mayor = b;
}
if (c > mayor) {
mayor = c;
}
cout << mayor << endl;
}
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;
}
}
}
en la cual se evalu´ a la expresi´on, y si su valor coincide con <valor 1> entonces se ejecuta la
<secuencia de sentencias 1> . Si su valor coincide con <valor 2> o con <valor 3> se ejecuta
la <secuencia de sentencias 2> y as´ı sucesivamente. Si el valor de la expresi´on no
coincide con ningu´n 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. Ejemplo:
#include <iostream>
using namespace std;
enum Dia_Semana {
lunes, martes, miercoles, jueves, viernes, sabado, domingo
};
int main ()
{
Dia_Semana dia;
...
// ’dia’ tiene alg´un valor v´alido
switch (dia) {
case lunes:
cout << "Lunes" << endl;
break;
case martes:
cout << "Martes" << endl;
break;
case miercoles:
cout << "Mi´ercoles" << endl;
break;
case jueves:
cout << "Jueves" << endl;
break;
case viernes:
cout << "Viernes" << endl;
break;
case sabado:
cout << "S´abado" << endl;
break;
case domingo:
cout << "Domingo" << endl;
break;
default:
cout << "Error" << endl;
break;
}
}
#include <iostream>
using namespace std;
int main ()
{
unsigned 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;
}
}
// i ya no es visible aqu´ı
cout << endl;
}
4.7. Ejemplos
Ejemplo 1
Programa que multiplica dos nu´meros mediante sumas acumulativas:
#include <iostream>
using namespace std;
int main ()
{
cout << "Introduzca dos n´umeros: ";
unsigned m, n;
cin >> m >> n;
// Sumar: m+m+m+...+m (n veces)
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
1 CAP´ITULO 4. ESTRUCTURAS DE CONTROL
unsigned total = 0;
for (unsigned i = 0; i < n; ++i) {
// Proceso iterativo: acumular el valor de ’m’ al total
total = total + m;
}
cout << total << endl;
}
Ejemplo 2
Programa que suma los nu´meros naturales menor a uno dado:
#include <iostream>
using namespace std;
int main ()
{
cout << "Introduzca un n´umero: ";
unsigned n;
cin >> n;
// Sumar: 1 2 3 4 5 6 7 ... n
unsigned suma = 0;
for (unsigned i = 1; i <= n; ++i) {
// Proceso iterativo: acumular el valor de ’i’ al total
suma = suma + i;
}
cout << suma << endl;
}
Ejemplo 3
Programa que divide dos nu´meros mediante restas sucesivas:
#include <iostream>
using namespace std;
int main ()
{
cout << "Introduzca dos n´umeros: ";
unsigned dividendo, divisor;
cin >> dividendo >> divisor;
if (divisor == 0) {
cout << "El divisor no puede ser cero" << endl;
} else {
unsigned resto = dividendo;
unsigned cociente = 0;
while (resto >= divisor) {
resto -= divisor;
++cociente;
}
cout << cociente << " " << resto << endl;
}
}
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 disen˜o 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
tam- bi´en pueden ser vistos como una ampliaci´on/elevaci´on del conjunto de operaciones b
´asicas (ac- ciones primitivas) del lenguaje de programaci´on, proporcion´andole nuevos
mecanismos para re- solver nuevos problemas.
41
1 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;
}
• 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)
{
return (a < b) ? a : b ;
}
• 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 ´a s adelante.
• 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;
Para ello, los par´ametros se declaran como se especific´o anteriormente para el paso por
referencia, pero anteponiendo la palabra reservada const.
void escribir(const Persona& a)
{
cout << a.nombre << " " << a.telefono << endl;
}
Tipos
Simples Compuestos
(⇓) Entrada P.Valor P.Ref.Cte
(unsigned x) (const Persona& x)
(⇑) Salida, (m) E/S P.Ref P.Ref
(unsigned& x) (Persona& x)
En la llamada a un subprograma, se deben cumplir los siguientes requisitos:
El nu´mero de par´ametros actuales debe coincidir con el nu´mero 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 (m) E/S (⇓) Ent (⇑) Sal (m)
Parametro Formal P.Valor P.Referencia P.Ref.Constante E/S
(unsigned x) (unsigned& x) (const Persona& x)
P.Referencia
(Persona& x)
Constante Constante
Parametro Actual Variable Variable Variable Variable
Expresi´on Expresi´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 ´a s adecuada.
Este mecanismo s´o l o es adecuado cuando el cuerpo del subprograma es muy pequen˜o, de tal
forma que el coste asociado a la invocaci´on dominar´ıa respecto a la ejecuci´on del cuerpo del
mismo.
enum Color {
rojo, amarillo, azul
};
inline void operator ++(Color& x)
{
x = Color(x + 1);
}
#include <iostream>
using namespace std;
void leer_int(istream& entrada, int& dato)
{
entrada >> dato;
}
void escribir_int(ostream& salida, int dato)
{
salida << "Valor: " << dato << endl;
}
int main()
{
int x;
leer_int(cin, x);
escribir_int(cout, x);
escribir_int(cerr, x);
}
//---------------------------
bool es_par(int num)
{
return num % 2 == 0;
}
//---------------------------
void dividir_par(int dividendo, int divisor, int& cociente, int& resto)
{
assert(es_par(dividendo) && (divisor != 0)); // PRE-CONDICI ´ON
cociente = dividendo / divisor;
resto = dividendo % divisor;
assert(dividendo == (divisor * cociente + resto)); // POST-CONDICI ´ON
}
5.12. Ejemplos
Ejemplo 1
Ejemplo de un programa que imprime los nu´meros 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;
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
1 CAP´ITULO 5. SUBPROGRAMAS. FUNCIONES Y PROCEDIMIENTOS
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;
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);
}
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
algu´ n tipo adicional para representar las “cadenas de caracteres”.
Tipos
Simples Compuestos
(⇓) Entrada P.Valor P.Ref.Cte
(unsigned x) (const Persona& p)
(⇑) Salida, (m) E/S P.Ref P.Ref
(unsigned& x) (Persona& p)
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 ´a s adecuado que el subprograma devuelva el valor de tipo
compuesto como un par´ametro de salida mediante el paso por referencia.
51
6.2. CADENAS DE CARACTERES EN C++: EL TIPO STRING 1
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 ("").
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:
#include <iostream>
#include <string>
using namespace std;
int main()
{
cout << "Introduzca el nombre: ";
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
2 CAP´ITULO 6. TIPOS COMPUESTOS
string nombre;
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 algu´n 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 fin de l´ınea, 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;
cin >> n;
cout << "Introduzca el nombre: ";
string nombre;
getline(cin, nombre);
cout << "N´umero: " << n << " Nombre: " << nombre << endl;
}
Para evitar este problema, el siguiente subprograma leer´a una cadena que sea distinta de la vac´ıa:
#include <iostream>
#include <string>
using namespace std;
inline void leer_linea_no_vacia(istream& ent, string& linea)
{
ent >> ws; // salta los espacios en blanco y fin de l´ınea
getline(ent, linea); // leer´a la primera l´ınea no vac´ıa
}
int main()
{
cout << "Introduzca n´umero: ";
int n;
cin >> n;
cout << "Introduzca el nombre (NO puede ser vac´ıo): ";
string nombre;
leer_linea_no_vacia(cin, nombre);
cout << "N´umero: " << n << " Nombre: " << nombre << endl;
}
Por el contrario, en caso de que la cadena vac´ıa sea una entrada v´alida, ser´a necesario
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, el subprograma leer_int elimina
los caracteres del buffer despu´es de leer un dato de tipo int:
#include <iostream>
#include <string>
#include <limits>
using namespace std;
inline void leer_int(istream& ent, int& dato)
{
ent >> dato; // lee el dato
ent.ignore(numeric_limits<streamsize>::max(), ’\n’); // elimina los caracteres del buffer
// ent.ignore(10000, ’\n’); // otra posibilidad de eliminar los caracteres del buffer
}
int main()
{
cout << "Introduzca n´umero: ";
int n;
leer_int(cin, n);
cout << "Introduzca el nombre (puede ser vac´ıo): ";
string nombre;
getline(cin, nombre);
cout << "N´umero: " << n << " Nombre: " << nombre << endl;
}
T´engase en cuenta que para utilizar numeric_limits<...>, es necesario incluir la biblioteca est
´andar <limits>.
// ...
nombre = AUTOR;
}
Es posible realizar la comparaci´on lexicogr´afica1 entre cadenas de caracteres del tipo string
mediante los operadores relacionales (==, !=, >, >=, <, <=). Por ejemplo:
#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’;
}
• if (nombre.empty()) { /*...*/ }
Para acceder al nu´mero de caracteres que componen la cadena:
Ejemplos
Ejemplo 1
Programa que convierte una cadena de caracteres a mayu´sculas:
#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 (unsigned i = 0; i < 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 minu´sculas), y escribe su plural segu´n las
siguientes reglas:
Si acaba en vocal se le an˜ade la letra ’s’.
Si acaba en consonante se le an˜aden 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 minu´sculas.
#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";
}
}
}
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
Disen˜e 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)
{ unsigned i = 0;
unsigned j = palabra.size() - 1;
while ((i < j) && (palabra[i] == palabra[j])) {
++i;
--j;
}
ok = i >= j;
}
return ok;
}
Ejemplo 4
Disen˜e 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).
s1.assign(s2); // asignar
s1.assign(s2, i2);
s1.assign(s2, i2, sz2);
s1.assign("pepe");
s1.assign("pepe", sz2);
s1.assign(sz2, ’a’);
s1.append(s2); //
a~nadir s1.append(s2, i2);
s1.append(s2, i2, sz2);
s1.append("pepe");
s1.append("pepe", sz2);
s1.append(sz2, ’a’);
s1.erase(i1); // eliminar
s1.erase(i1, sz1);
char dest_c[MAX];
s1.copy(dest_c, sz1, i1 = 0); // sz1 <= MAX ; no pone ’\0’
En las siguientes bu´squedas, el patr´on a buscar puede ser un string, cadena de caracteres
constante o un car´acter. Si la posici´on indicada est ´a fuera de los l´ımites del string,
entonces se lanza una excepci´on out_of_range.
unsigned i1 = s1.find(’a’, i1=0); // buscar el patr´on exacto
unsigned i1 = s1.find(s2, i1=0);
unsigned i1 = s1.find("cd", i1=0);
unsigned i1 = s1.find("cd", i1, sz2);
Meses mes;
unsigned anyo;
};
Los valores del tipo Fecha se componen de tres elementos concretos (el d´ıa de tipo unsigned,
el mes de tipo Meses y el a n˜ o 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
{ unsigned codigo;
unsigned sueldo;
Fecha fecha_ingreso;
};
enum Palo {
Oros, Copas, Espadas, Bastos
};
struct Carta
{ Palo palo;
unsigned valor;
};
struct Tiempo
{ unsigned
hor; unsigned
min; unsigned
seg;
};
// -- Principal --------
int main ()
{
Empleado e;
Carta c;
Tiempo t1, t2;
// ...
}
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.
int main ()
{ dia: 18 dia: 18
Fecha f_nac, hoy; mes: Octubre mes: Octubre
f_nac.dia = 18; anyo: 2001 anyo: 2001
f_nac.mes = Octubre;
f_nac.anyo = 2001; f nac hoy
hoy = f_nac;
}
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;
}
En caso de ser necesario, es posible definir los operadores relacionales para registros:
inline bool operator == (const Tiempo& t1, const Tiempo& t2)
{
return (t1.hor == t2.hor) && (t1.min == t2.min) && (t1.seg == t2.seg);
}
inline bool operator != (const Tiempo& t1, const Tiempo& t2)
{
return ! (t1 == t2);
}
inline bool operator < (const Tiempo& t1, const Tiempo& t2)
{
return ((t1.hor < t2.hor)
|| ((t1.hor == t2.hor) && ((t1.min < t2.min)
|| ((t1.min == t2.min) && (t1.seg < t2.seg)))));
}
inline bool operator > (const Tiempo& t1, const Tiempo& t2)
{
return (t2 < t1);
}
inline bool operator <= (const Tiempo& t1, const Tiempo& t2)
{
return ! (t2 < t1);
}
inline bool operator >= (const Tiempo& t1, const Tiempo& t2)
{
return ! (t1 < t2);
}
As´ı como tambi´en es posible definir los operadores de entrada y salida para registros:
inline ostream& operator << (ostream& out, const Tiempo& t)
{
return out << t.hor << " " << t.min << " " << t.seg ;
}
inline istream& operator >> (istream& in, Tiempo& t)
{
return in >> t.hor >> t.min >> t.seg ;
}
int main()
{
Tiempo t1, t2;
cin >> t1 >> t2;
if (t1 < t2) {
cout << t1 << endl;
} else {
cout << t2 << endl;
}
}
N´otese como en la definici´on de los operadores de entrada y salida para registros, estos
mismos retornan un valor de tipo referencia, el cual es un concepto complejo que s er ´a
explicado m ´a s adelante (v´ease 13).
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 u´tiles en todas aquellas circunstancias en que necesitamos tener
almacenados una colecci´on de valores (un nu´mero 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 TR1 para definir agregados. Para ello, se
debe incluir la biblioteca <tr1/array>, as´ı como utilizar el espacio de nombres de std::tr1.
La definici´on de agregados de este tipo permite definir agregados mas robustos y con mejores
caracter´ısticas que los agregados predefinidos explicados en el cap´ıtulo 8.
Un tipo agregado se especifica declarando el tipo base de los elementos que lo componen y
el nu´mero 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 dobles):
#include <tr1/array>
using namespace std::tr1;
// -- Constantes -------
const unsigned NELMS = 5;
// Tipos
typedef array<int, NELMS> Vector;
// -- Constantes -------
const Vector PRIMOS = {{ 2, 3, 5, 7, 11 }}; PRIMOS: 2 3 5 7 11
// -- Principal -------- 0 1 2 3 4
int main ()
{
Vector v; v: ? ? ? ? ?
} 0 1 2 3 4
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:
#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 = {{
CUMPLEANYOS:
{ 1, 1, 2001 },
{ 2, 2, 2002 }, 1 2 3 4
{ 3, 3, 2003 }, 1 2 3 4
{ 4, 4, 2004 } 2001 2002 2003 2004
}}; 0 1 2 3
int main()
{
Citas cit;
// ...
cit = CUMPLEANYOS;
}
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.
Ejemplo 1
#include <iostream>
#include <tr1/array>
using namespace std;
using namespace std::tr1;
// -- Constantes -------
const unsigned NELMS = 5;
// Tipos
typedef array<int, NELMS> Vector;
// -- Subalgoritmos ----
void leer (Vector& v)
{
for (unsigned i = 0; i < v.size(); ++i)
{ cin >> v[i];
}
}
unsigned sumar (const Vector& v)
{
int suma = 0;
for (unsigned i = 0; i < 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 ´a s un incentivo que s er´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 unsigned 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 (unsigned i = 0; i < v.size(); ++i) {
suma += v[i];
}
return suma / double(v.size());
}
inline double porcentaje (double p, double valor)
{
return (p * valor) / 100.0;
}
void leer_ventas (Ventas& v)
{
for (unsigned i = 0; i < 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 (unsigned i = 0; i < 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 taman˜o mayor
que el nu´mero 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 separacion entre los elementos actuales v´alidos de los elementos vac´ıos con algu´n
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 nu´mero 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 unsigned 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 {
unsigned nelms;
Datos elm;
};
// -- Subalgoritmos ----
double calc_media (const Ventas& v)
{
double suma = 0.0;
for (unsigned 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 (unsigned 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)
{
unsigned nag;
cout << "Introduzca total de agentes: ";
cin >> nag;
if (nag > v.elm.size())
{ v.nelms = 0;
cout << "Error" << endl;
} else {
v.nelms = nag;
for (unsigned 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 < v.elm.size())&&(vent_ag > 0))
{ v.elm[v.nelms] = vent_ag;
++v.nelms;
leer_ventas_ag(v.nelms+1, vent_ag);
}
}
//
void imprimir_sueldos (const Ventas& v)
{
double umbral = PROMEDIO * calc_media(ventas);
for (unsigned i = 0; i < v.nelms; ++i) {
double sueldo = SUELDO_FIJO;
if (v.elm[i] >= umbral) {
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 mu´ltiples 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:
#include <iostream>
#include <tr1/array>
using namespace std;
using namespace std::tr1;
// -- Constantes -------
const unsigned NFILAS = 3;
const unsigned NCOLUMNAS = 5;
// Tipos
typedef array<unsigned, NCOLUMNAS> Fila;
typedef array<Fila, NFILAS> Matriz;
// -- Principal --------
int main ()
{ m:
Matriz m; 0 00 01 02 03 04
for (unsigned f = 0; f < m.size(); ++f ) { 1 10 11 12 13 14
for (unsigned c = 0; c < m[f ].size(); ++c) { 2 20 21 22 23 24
m[f][c] = (f * 10) + c; 0 1 2 3 4
}
}
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 }}
unsigned 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 unsigned). Del mismo modo, el nu´mero de filas de la matriz m es igual a
m.size(), y el nu´mero de elementos de la fila f de la matriz m es igual a m[f].size().
Ejemplo 1
Disen˜ar un programa que lea una matriz de ×
3 5 de nu´meros enteros (fila a fila), almacen
´andolos en un array bidimensional, finalmente imprima la matriz segu´ n el siguiente formato:
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 u´nica fila, y sin embargo no es posible pasar como par´ametro una u
´nica columna.
#include <iostream>
#include <tr1/array>
using namespace std;
using namespace std::tr1;
// -- Constantes -------
const unsigned NFILAS = 3;
const unsigned NCOLUMNAS = 5;
// Tipos
typedef array<int, NCOLUMNAS> Fila;
typedef array<Fila, NFILAS> Matriz;
// -- Subalgoritmos ----
int sumar_fila (const Fila& fil)
{
int suma = 0;
for (unsigned c = 0; c < fil.size(); ++c) {
suma += fil[c];
}
return suma;
}
int sumar_columna (const Matriz& m, unsigned c)
{
int suma = 0;
for (unsigned f = 0; f < m.size(); ++f ) {
suma += m[f ][c];
}
return suma;
}
void escribir_fila (const Fila& fil)
{
for (unsigned c = 0; c < fil.size(); ++c)
{ cout << fil[c] << " ";
}
}
void escribir_matriz_formato (const Matriz& m)
{
for (unsigned f = 0; f < m.size(); ++f ) {
escribir_fila(m[f]);
cout << sumar_fila(m[f]);
cout << endl;
}
for (unsigned c = 0; c < m[0].size(); ++c)
{ cout << sumar_columna(m, c) << " ";
}
cout << endl;
}
void leer_matriz (Matriz& m)
{
cout << "Escribe fila a fila" << endl;
for (unsigned f = 0; f < m.size(); ++f) {
for (unsigned c = 0; c < m[f].size(); ++c)
{ cin >> m[f][c];
}
}
}
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
8 CAP´ITULO 6. TIPOS COMPUESTOS
// -- Principal --------
int main ()
{
Matriz m;
leer_matriz(m);
escribir_matriz_formato(m);
}
Ejemplo 2
Disen˜e 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 unsigned MAX = 10;
// Tipos
typedef array<double, MAX> Fila;
typedef array<Fila, MAX> Tabla;
struct Matriz {
unsigned n_fil;
unsigned n_col;
Tabla datos;
};
// -- Subalgoritmos ----
void leer_matriz (Matriz& m)
{
cout << "Dimensiones?: ";
cin >> m.n_fil >> m.n_col;
assert(m.n_fil <= m.datos.size() && m.n_col <= m.datos[0].size());
cout << "Escribe valores fila a fila:" << endl;
for (unsigned f = 0; f < m.n_fil; ++f) {
for (unsigned c = 0; c < m.n_col; ++c)
{ cin >> m.datos[f][c];
}
}
}
void escribir_matriz (const Matriz& m)
{
for (unsigned f = 0; f < m.n_fil; ++f) {
for (unsigned 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, unsigned f, unsigned c)
{
assert(x.n_col == y.n_fil); // PRE-COND
double suma = 0.0;
for (unsigned k = 0; k < x.n_col; ++k)
{ suma += x.datos[f][k] * y.datos[k][c];
}
return suma;
}
void mult_matriz (Matriz& m, const Matriz& a, const Matriz& b)
{
#include <iostream>
#include <string>
#include <cassert>
#include <tr1/array>
using namespace std;
using namespace std::tr1;
// -- Constantes -------
const unsigned MAX_PERSONAS = 50;
// Tipos
struct Direccion {
unsigned num;
string calle;
string piso;
string cp;
string ciudad;
};
struct Persona {
string nombre;
string tel;
Direccion direccion;
};
// Tipos
}
//---------------------------
void Imprimir_Agenda (const Agenda& ag, Cod_Error& ok)
{
for (unsigned 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();
switch (opcion)
{ case ’a’:
cout << "Introduzca los datos de la Persona" << endl;
cout << "(nombre, tel, calle, num, piso, cod_postal, ciudad)" << endl;
Bu´squeda y Ordenaci´on
Los algoritmos de bu´squeda de un elemento en una colecci´on de datos, as´ı como los
algoritmos de ordenaci´on, son muy utilizados comu´nmente, por lo que merecen un estudio expl
´ıcito. En el caso de los algoritmos de bu´squeda, ´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 algu´n criterio de ordenaci´on. Los algoritmos de ordenaci´on
que se ver´an en este cap´ıtulo son los m ´a s f´aciles de programar, pero sin embargo son los m
´a s ineficientes.
77
1 CAP´ITULO 7. BU´ SQUEDA Y ORDENACIO
´N
// busca la posici´on del primer elemento > x
//-------------
unsigned buscar_may(int x, const Vector& v)
{
unsigned i = 0;
while ((i < v.size())&&(x >= v[i])) {
++i;
}
return i;
}
//--------------------------------
}
}
return i;
}
//----------------------------
// busca la posici´on del primer elemento > x
//-------------
unsigned buscar_bin_may(int x, const Vector& v)
{
unsigned i = 0;
unsigned f = v.size();
while (i < f ) {
unsigned m = (i + f ) / 2;
if (x < v[m]) {
f = m;
} else {
i = m + 1;
}
}
return i;
}
//--------------------------------
Se hacen mu´ltiples recorridos sobre la zona no ordenada del array, ordenando los
elementos consecutivos, trasladando en cada uno de ellos al elemento m ´a s pequen˜o 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, unsigned pos)
{
for (unsigned i = v.size()-1; i > pos; --i) {
if (v[i] < v[i-1]) {
intercambio(v[i], v[i-1]);
}
}
}
//--------------------------------
void burbuja(Vector& v)
{
for (unsigned pos = 0; pos < v.size()-1; ++pos)
{ subir_menor(v, pos);
}
}
//--------------------------------
//--------------------------------
void abrir_hueco(Vector& v, unsigned p_hueco, unsigned p_elm)
{
for (unsigned i = p_elm; i > p_hueco; --i)
{ v[i] = v[i-1];
}
}
//--------------------------------
void insercion(Vector& v)
{
for (unsigned pos = 1; pos < v.size(); ++pos)
{ unsigned p_hueco = buscar_posicion(v,
pos); if (p_hueco != pos) {
int aux = v[pos];
abrir_hueco(v, p_hueco, pos);
v[p_hueco] = aux;
}
}
}
//--------------------------------
#include <iostream>
#include <string>
#include <cassert>
#include <tr1/array>
using namespace std;
using namespace std::tr1;
// -- Constantes -------
const unsigned MAX_PERSONAS = 50;
// Tipos
struct Direccion {
unsigned num;
string calle;
string piso;
string cp;
string ciudad;
};
struct Persona {
string nombre;
string tel;
Direccion direccion;
};
// Tipos
typedef array<Persona, MAX_PERSONAS> Personas;
struct Agenda {
unsigned 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;
// -- Constantes -------
const unsigned NELMS = 5;
// Tipos
typedef int Vector[NELMS];
// -- Constantes -------
const Vector PRIMOS = { 2, 3, 5, 7, 11 }; PRIMOS: 2 3 5 7 11
// -- Principal -------- 0 1 2 3 4
int main ()
{
Vector v; v: ? ? ? ? ?
} 0 1 2 3 4
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:
87
8.1. AGREGADOS O ARRAYS PREDEFINIDOS 1
struct Fecha
{ unsigned dia;
unsigned mes;
unsigned anyo;
};
const int N_CITAS = 4;
typedef Fecha Citas[N_CITAS];
const Citas CUMPLEANYOS = {
{ 1, 1, 2001 }, CUMPLEANYOS:
{ 2, 2, 2002 }, 1 2 3 4
{ 3, 3, 2003 }, 1 2 3 4
{ 4, 4, 2004 } 2001 2002 2003 2004
};
0 1 2 3
int main()
{
Citas cit;
}
No es posible asignar variables de tipo array predefinido. Del mismo modo, tampoco se le pueden
aplicar los operadores relacionales (==, !=, >, >=, <, <=) a entidades de tipo array predefinido.
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 u´ltimo elemento ocupa la posici´on del nu´mero de elementos
menos 1. Por ejemplo cit[0] y cit[N_CITAS-1] aluden al primer y u´ltimo elemento del
agregado respectiva- mente. Un determinado 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
agrega- do son correctos y se encuentran dentro de los l´ımites v´alidos del array predefinido,
por lo que ser´a responsabilidad del programador comprobar que as´ı sea.
struct Fecha
{ unsigned dia;
unsigned mes;
unsigned anyo;
};
const int N_CITAS = 4;
typedef Fecha Citas[N_CITAS];
int main()
{
Citas cit;
cit[0].dia = 18;
cit[0].mes = 10;
cit[0].anyo = 2001;
for (int i = 0; i < N_CITAS; ++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
// ...
}
Ejemplo 1
#include <iostream>
using namespace std;
// -- Constantes -------
const unsigned NELMS = 5;
// Tipos
Ejemplo 2
Programa que lee las ventas de cada “agente” e imprime su sueldo que se calcula como una
cantidad fija (1000 C) m ´a s un incentivo que s er´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>
using namespace std;
// -- Constantes -------
const unsigned NAGENTES = 20;
const double SUELDO_FIJO = 1000.0;
const double INCENTIVO = 10.0;
const double PROMEDIO = 2.0 / 3.0;
// Tipos
typedef double Ventas[NAGENTES];
// -- Subalgoritmos ----
double calc_media (const Ventas& v)
{
double suma = 0.0;
for (unsigned i = 0; i < NAGENTES; ++i) {
suma += v[i];
}
return suma / double(NAGENTES);
}
inline double porcentaje (double p, double valor)
{
return (p * valor) / 100.0;
}
void leer_ventas (Ventas& v)
{
for (unsigned i = 0; i < NAGENTES; ++i) {
cout << "Introduzca ventas del Agente " << i << ": ";
cin >> v[i];
}
}
void imprimir_sueldos (const Ventas& v)
{
double umbral = PROMEDIO * calc_media(ventas);
for (unsigned i = 0; i < NAGENTES; ++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 taman˜o mayor
que el nu´mero 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 separacion entre los elementos actuales v´alidos de los elementos vac´ıos con algu´n
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 nu´mero de elementos actuales v
´alidos consecutivos que contiene suele ser m´as adecuado.
Ejemplo
#include <iostream>
using namespace std;
// -- Constantes -------
const unsigned MAX_AGENTES = 20;
const double SUELDO_FIJO = 1000.0;
const double INCENTIVO = 10.0;
const double PROMEDIO = 2.0 / 3.0;
// Tipos
typedef double Datos[MAX_AGENTES];
struct Ventas {
unsigned nelms;
Datos elm;
};
// -- Subalgoritmos ----
double calc_media (const Ventas& v)
{
double suma = 0.0;
for (unsigned i = 0; i < v.nelms; ++i) {
suma += 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 mu´ltiples 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:
#include <iostream>
using namespace std;
// -- Constantes -------
const unsigned NFILAS = 3;
const unsigned NCOLUMNAS = 5;
// Tipos
typedef unsigned Fila[NCOLUMNAS];
typedef Fila Matriz[NFILAS];
// -- Principal --------
int main ()
{ m:
Matriz m; 0 00 01 02 03 04
for (unsigned f = 0; f < NFILAS; ++f ) { 1 10 11 12 13 14
for (unsigned c = 0; c < NCOLUMNAS; ++c) { 2 20 21 22 23 24
m[f][c] = (f * 10) + c; 0 1 2 3 4
}
}
}
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 unsigned).
Ejemplo 1
Disen˜ar un programa que lea una matriz de ×
3 5 de nu´meros enteros (fila a fila), almacen
´andolos en un array bidimensional, finalmente imprima la matriz segu´ n el siguiente formato:
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 u´nica fila, y sin embargo no es posible pasar como par´ametro una u
´nica columna.
#include <iostream>
using namespace std;
// -- Constantes -------
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6 CAP´ITULO 8. OTROS TIPOS COMPUESTOS EN C
Ejemplo 2
Disen˜e un programa que realice el producto de 2 matrices de m´aximo 10 × 10 elementos:
#include <iostream>
#include <cassert>
using namespace std;
// -- Constantes -------
const unsigned MAX = 10;
// Tipos
typedef double Fila[MAX];
typedef Fila Tabla[MAX];
struct Matriz {
unsigned n_fil;
unsigned n_col;
Tabla datos;
};
// -- Subalgoritmos ----
void leer_matriz (Matriz& m)
{
cout << "Dimensiones?: ";
cin >> m.n_fil >> m.n_col;
assert(m.n_fil <= MAX && m.n_col <= MAX);
cout << "Escribe valores fila a fila:" << endl;
for (unsigned f = 0; f < m.n_fil; ++f ) {
for (unsigned c = 0; c < m.n_col; ++c)
{ cin >> m.datos[f][c];
}
}
}
void escribir_matriz (const Matriz& m)
{
for (unsigned f = 0; f < m.n_fil; ++f) {
for (unsigned 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, unsigned f, unsigned c)
{
assert(x.n_col == y.n_fil); // PRE-COND
double suma = 0.0;
for (unsigned k = 0; k < x.n_col; ++k)
{ suma += x.datos[f][k] * y.datos[k][c];
}
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 (unsigned f = 0; f < m.n_fil; ++f) {
for (unsigned 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);
}
}
Hay que tener en cuenta que si se utiliza la sintaxis del paso por valor, se pasar´an realmente
por referencia, pero de este modo ser´a m ´a s propenso a errores, ya que en este caso no se
comprueban que los par´ametros sean del mismo tipo, siendo posible pasar un array de un
taman˜o diferente al especificado, con los errores que ello conlleva. Por ejemplo:
Nota: S´o lo es v´alido declarar agregados abiertos como par´ametros. No es posible declarar
variables como agregados abiertos.
El paso de par´ametros de arrays predefinidos sin el s´ımbolo ampersand (&) es propenso a errores
no detectables en tiempo de compilaci´on.
En caso de paso de par´ametros de arrays predefinidos sin el s´ımbolo ampersand (&), dentro del
sub- programa se pueden utilizar los operadores de asignaci´on (=) y relacionales, pero su
comportamiento y los resultados producidos no son los esperados.
Los arrays predefinidos tienen una conversi´on autom´atica al tipo puntero, que es indeseable
en muchos casos.
#include <iostream>
using namespace std;
const unsigned NELMS_1 = 9;
const unsigned NELMS_2 = 5;
typedef int Vector_1[NELMS_1];
typedef int Vector_2[NELMS_2];
void escribir(const Vector_1 v)
{
for (unsigned i = 0; i < NELMS_1; ++i) {
cout << v[i] << " ";
}
cout << endl;
}
void copiar(Vector_1 destino, Vector_1 origen)
{
destino = origen; // Error: comportamiento inesperado
}
bool es_menor(const Vector_1 v1, const Vector_1 v2)
{
return v1 < v2; // Error: comportamiento inesperado
}
int main()
{
Vector_2 v2;
Vector_1 vx;
Vector_1 v1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//-----
// vx = v1; // Error de Compilaci´on
// v2 = v1; // Error de Compilaci´on
if (v1 < vx) {
cout << "v1 < vx" << endl;
}
if (v1 < v2) {
cout << "v1 < v2" << endl;
}
//-----
copiar(vx, v1);
copiar(v2, v1); // Error al pasar una variable de tipo Vector_2
//-----
if (es_menor(v1, vx)) {
cout << "v1 es menor que vx" << endl;
}
if (es_menor(v1, v2)) {
cout << "v1 es menor que v2" << endl;
}
//-----
escribir(v1);
escribir(vx);
escribir(v2); // Error al pasar una variable de tipo Vector_2
}
Cuya ejecuci´on produce el siguiente resultado:
v1 < vx
v1 < v2
v1 es menor que vx
v1 es menor que v2
1 2 3 4 5 6 7 8 9
134519591 1 65535 3221154088 134524397 3076926329 134530372 3221154104 134519004
3077695113 134530372 3221154136 134524297 3078529616 3221154144 3221154232 3075855445 134524272
Por otra parte, el tratamiento del tipo array de la biblioteca est´andar es consistente con los
otros tipos de datos. Es posible la asignaci´on (=) de variables de dicho tipo, y los operadores
relacionales tienen el comportamiento esperado, as´ı como es posible su devoluci´on desde una
funci´on, y el paso de par´ametros, tanto por valor como por referencia y referencia constante,
es seguro y mantiene su sem´antica previamente establecida. Adem´as, es posible acceder al nu
´mero de elementos que lo componen mediante el operador size(), 1 as´ı como tambi´en es
posible acceder a un determinado elemento con comprobaci´on de rango (at(i)).
int main()
{
Cadena nombre = "Pepe";
for (unsigned i = 0; nombre[i] != ’\0’; ++i)
{ cout << nombre[i];
}
cout << endl;
}
Nota: tambi´en es posible realizar el paso de par´ametros de cadenas de caracteres como char*
y const char* con la misma sem´antica que la especificada anteriormente, sin embargo la
sintaxis con los corchetes ([]) representa mejor el significado de este paso de par´ametros en
estas circun- stancias, por lo que se desaconseja la siguiente sintaxis para el paso de cadenas
de caracteres como par´ametros:
#include <iostream>
using namespace std;
const int MAX_CADENA = 10;
typedef char Cadena[MAX_CADENA];
void escribir(const char* cad)
{
for (unsigned i = 0; cad[i] != ’\0’; ++i)
{ cout << cad[i] ;
}
}
unsigned longitud(const char* cad)
{
unsigned i = 0;
while (cad[i] != ’\0’) {
++i;
}
return i;
}
void copiar(char* destino, const char* origen, unsigned sz)
{
unsigned i;
for (i = 0; (i < sz-1)&&(origen[i] != ’\0’); ++i)
{ destino[i] = origen[i];
}
destino[i] = ’\0’;
}
int main()
{
Cadena c1 = "Pepe";
Cadena c2;
unsigned l = longitud(c1);
copiar(c2, c1, MAX_CADENA);
escribir(c2);
copiar(c2, "Luis", MAX_CADENA);
escribir(c2);
}
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 del tipo
cadena de caracteres. Sin embargo, para evitar sobrepasar el l´ımite del array predefinido
durante la lectura, es conveniente utilizar el manipulador setw. Por ejemplo:
#include <iostream>
#include <iomanip>
using namespace std;
const int MAX_CADENA = 10;
typedef char Cadena[MAX_CADENA];
int main()
{
cout << "Introduzca el nombre: ";
Cadena nombre;
cin >> setw(MAX_CADENA) >> 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 lea tantos caracteres como se
especifica en setw, o hasta que encuentre algu´n 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 fin de l´ınea, desde el flujo
de entrada,
sin eliminar los espacios iniciales:
#include <iostream>
using namespace std;
const int MAX_CADENA = 10;
typedef char Cadena[MAX_CADENA];
int main()
{
cout << "Introduzca el nombre: ";
Cadena nombre;
cin.getline(nombre, MAX_CADENA);
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>
using namespace std;
const int MAX_CADENA = 10;
typedef char Cadena[MAX_CADENA];
const char DELIMITADOR = ’.’; int
main()
{
cout << "Introduzca el nombre: ";
Cadena nombre;
cin.getline(nombre, MAX_CADENA, 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>
using namespace std;
const int MAX_CADENA = 10;
typedef char Cadena[MAX_CADENA];
int main()
{
cout << "Introduzca n´umero: ";
int n;
cin >> n;
cout << "Introduzca el nombre: ";
Cadena nombre;
cin.getline(nombre, MAX_CADENA);
cout << "N´umero: " << n << " Nombre: " << nombre << endl;
}
Para evitar este problema, el siguiente subprograma leer´a una cadena que sea distinta de la vac´ıa:
#include <iostream>
using namespace std;
const int MAX_CADENA = 10;
typedef char Cadena[MAX_CADENA];
inline void leer_linea_no_vacia(istream& ent, char linea[], unsigned sz)
{
ent >> ws; // salta los espacios en blanco y fin de l´ınea
ent.getline(linea, sz); // leer´a la primera l´ınea no vac´ıa
}
int main()
{
cout << "Introduzca n´umero: ";
int n;
cin >> n;
cout << "Introduzca el nombre (NO puede ser vac´ıo): ";
Cadena nombre;
leer_linea_no_vacia(cin, nombre, MAX_CADENA);
cout << "N´umero: " << n << " Nombre: " << nombre << endl;
}
Por el contrario, en caso de que la cadena vac´ıa sea una entrada v´alida, ser´a necesario
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, el subprograma leer_int elimina
los caracteres del buffer despu´es de leer un dato de tipo int:
#include <iostream>
#include <limits>
using namespace std;
const int MAX_CADENA = 10;
typedef char Cadena[MAX_CADENA];
inline void leer_int(istream& ent, int& dato)
{
ent >> dato; // lee el dato
ent.ignore(numeric_limits<streamsize>::max(), ’\n’); // elimina los caracteres del buffer
// ent.ignore(10000, ’\n’); // otra posibilidad de eliminar los caracteres del buffer
}
int main()
{
cout << "Introduzca n´umero: ";
int n;
leer_int(cin, n);
cout << "Introduzca el nombre (puede ser vac´ıo): ";
Cadena nombre;
cin.getline(nombre, MAX_CADENA);
cout << "N´umero: " << n << " Nombre: " << nombre << endl;
}
T´engase en cuenta que para utilizar numeric_limits<...>, es necesario incluir la biblioteca est
´andar <limits>.
8.3. Uniones
Otra construcci´on u´ til, aunque no muy utilizada, son las uniones, y sirven para
compactar varias entidades de diferentes tipos en la misma zona de memoria. Es decir, todos las
entidades definidas dentro de una uni´on compartir´an la misma zona de memoria, y por lo tanto
su utilizaci´on ser ´a excluyente. Se utilizan dentro de las estructuras:
#include <iostream>
using namespace std;
enum Tipo {
COCHE,
MOTOCICLETA,
BICICLETA
};
struct Vehiculo
{ Tipo vh;
union {
double capacidad;// para el caso de tipo COCHE
int cilindrada; // para el caso de tipo MOTOCICLETA
int talla; // para el caso de tipo BICICLETA
};
};
int main()
{
Vehiculo xx;
Vehiculo yy;
xx.vh = COCHE;
xx.capacidad = 1340.25;
yy.vh = MOTOCICLETA;
yy.cilindrada = 600;
}
obviamente, los tipos de los campos de la uni´on pueden ser tanto simples como compuestos.
Es responsabilidad del programador utilizar los campos adecuados en funci´on del tipo que se
est´e al- macenando.
#include <iostream>
#include <iomanip>
#include <cstring>
#include <cassert>
using namespace std;
// -- Constantes -------
const unsigned MAX_CADENA = 60;
const unsigned MAX_PERSONAS = 50;
// Tipos
}
//---------------------------
// Busca una Persona en la Agenda
// Devuelve su posici´on si se encuentra, o bien >= ag.n_pers en otro caso
unsigned Buscar_Persona (const char nombre[], const Agenda& ag)
{
unsigned i = 0;
while ((i < ag.n_pers) && (strcmp(nombre, ag.pers[i].nombre) != 0)) {
++i;
}
return i;
}
//---------------------------
void Anyadir (Agenda& ag, const Persona& per)
{
assert(ag.n_pers < MAX_PERSONAS);
ag.pers[ag.n_pers] = per;
++ag.n_pers;
}
//---------------------------
void Eliminar (Agenda& ag, unsigned 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)
{
unsigned i = Buscar_Persona(per.nombre, ag);
if (i < ag.n_pers) {
ok = YA_EXISTE;
} else if (ag.n_pers == MAX_PERSONAS) {
ok = AG_LLENA;
} else {
ok = OK;
Anyadir(ag, per);
}
}
//---------------------------
void Borrar_Persona (const char nombre[], Agenda& ag, Cod_Error& ok)
{
unsigned i = Buscar_Persona(nombre, ag);
if (i >= ag.n_pers) {
ok = NO_ENCONTRADO;
} else {
ok = OK;
Eliminar(ag, i);
}
}
//---------------------------
void Modificar_Persona (const char nombre[], const Persona& nuevo, Agenda& ag, Cod_Error& ok)
{
unsigned i = Buscar_Persona(nombre, ag);
if (i >= ag.n_pers) {
ok = NO_ENCONTRADO;
} else {
int main ()
{
Agenda ag;
char opcion;
Persona per;
Cadena 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 >> setw(MAX_CADENA) >> nombre;
Imprimir_Persona(nombre, ag, ok);
Escribir_Cod_Error(ok);
break;
case ’c’:
cout << "Introduzca Nombre" << endl;
cin >> setw(MAX_CADENA) >> nombre;
Borrar_Persona(nombre, ag, ok);
Escribir_Cod_Error(ok);
break;
case ’d’:
cout << "Introduzca Nombre" << endl;
cin >> setw(MAX_CADENA) >> 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’ );
}
cmath
La biblioteca <cmath> proporciona principalmente algunas funciones matem´aticas u´tiles:
#include <cmath>
using namespace std;
cctype
La biblioteca <cctype> proporciona principalmente caracter´ısticas sobre los valores de tipo
char:
#include <cctype>
using namespace std;
1
112 CAP´ITULO 9. ALGUNAS BIBLIOTECAS U´ TILES
cstdlib
La biblioteca <cstdlib> proporciona principalmente algunas funciones generales u´tiles:
#include <cstdlib>
using namespace std;
#include <cstdlib>
#include <ctime>
using namespace std;
//
// 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);
}
//
Programacio´n Intermedia
1
Cap´ıtulo
10
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´arquicamente y
acceder a los ficheros, y estos u´ltimos 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.
Tipos de Ficheros
Los ficheros se pueden clasificar de mu´ltiples 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, 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- c i´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
1
2 CAP´ITULO 10. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS
10
T´engase en cuenta que el ordenador almacena los nu´meros 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 nu´mero 1992 se almacena
en una vari- able de tipo unsigned y se representa internamente como el siguiente nu´mero
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,
disen˜ado 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
mu´sica suelen estar, en su mayor´ıa, almacenados en formato binario.
Por el contrario, un flujo de salida de datos en modo texto actu´ a como un sumidero que
recibe una secuencia de caracteres (usualmente a trav´es de un buffer de almacenamiento
intermedio) a la que se env´ıan los caracteres que representan a los datos de salida, que
previamente han sido convertidos al formato de texto adecuado.
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
El car´acter terminador de fin de l´ınea no es visible, aunque se aprecian sus efectos al mostrarse los
3
4 CAP´ITULO 10. ALMACENAMIENTO EN MEMORIA SECUNDARIA: FICHEROS
siguientes caracteres en la siguiente l´ınea.
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, salida, o ambos) para que actu´en como
manejadores de fichero.
3. Abrir el flujo de datos vinculando la variable correspondiente con el fichero especificado. Esta
op- eraci´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
informa- ci´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 algu´ n 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 (v´ease 3.5).
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, 5.10, 6.2, 8.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 (v´ease 3.5).
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 a´mbito de vida de la variable vinculada, el flujo ser´a
cerrado automaticamente, y su vinculaci´on liberada.
Cuando sea necesario, una variable de tipo flujo, tanto de entrada, salida o ambos, puede ser
pasada como par´ametro por referencia (no constante) a cualquier subprograma.
Nota: si se va a abrir un fichero utilizando la misma variable que ya ha sido previamente
utilizada para procesar otro fichero (pero posteriormente desvinculada tras cerrar el flujo), se
deber´a uti- lizar el m´etodo clear() para limpiar los flags de estado (v´ease 3.5). Aunque esta
pr´actica de utilizar la misma variable para procesar distintos ficheros est ´a totalmente
desaconsejada, siendo una t´ecnica mejor definir y vincular variables diferentes, en distintos
subprogramas que representen adecuadamente la abstracci´on del programa.
5. Realizar la entrada de datos con los operadores y subprogramas correspondientes, as´ı como
procesar la informacion 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 iteracion 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
{
...
f_ent >> datos;
while (! f_ent.fail() ... )
{ procesar(datos, ...);
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 nu´meros 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)
{
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);
}
6. Comprobar que el procesamiento del fichero se realiz´o correctamente, es decir, el fichero se encuentra
en buen estado.
if (f_sal.good()) { ... }
f_sal.close();
Por ejemplo, un programa que lee nu´meros 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.good())
{ f_sal << numero << endl;
cin >> numero
}
if (f_sal.good()) {
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:
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);
}
10.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.good()) {
f_sal.put(ch);
f_ent.get(ch);
}
if (f_ent.eof() && f_sal.good()) {
ok = OK;
} else {
ok = ERROR_FORMATO;
}
f_sal.close(); // no es necesario
}
f_ent.close(); // no es necesario
}
}
void codigo_error(Codigo ok)
{
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 unsigned MAX = 100;
typedef array<Persona, MAX> APers;
struct Agenda {
unsigned nelms;
APers elm;
};
//-------------------------------------------------------------------------
void inic_agenda(Agenda& ag)
{
ag.nelms = 0;
}
void anyadir_persona(Agenda& ag, const Persona& p, bool& ok)
{
if (ag.nelms < 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 (unsigned i = 0; i < ag.nelms; ++i) {
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>
// ...
//------------------------------------------------------------------------ -
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;
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 {
unsigned i = 0;
while ((i < ag.nelms) && (fich.good()))
{ escribir_persona(fich, ag.elm[i]);
++i;
}
ok = fich.good();
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;
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’);
}
//------------------------------------------------------------------------ -
El formato binario de las cadenas de caracteres coincide con su representaci´on textual, por
lo que utilizaremos el mismo mecanismo que para la entrada textual de cadenas de caracteres,
pero considerando que el final de la cadena de caracteres est´a marcado por la inclusi´on en el
fichero de un determinado delimitador segu´ n se realiz´o el proceso de almacenamiento en el
fichero (’\0’).
getline(f_ent, nombre, ’\0’);
La entrada de variables de tipo estructurado (registros, clases y arrays de registros y clases) debe
realizarse componente a componente.
La entrada y salida binaria de datos utiliza la representaci´on interna (en memoria principal)
de los datos para su almacenamiento en memoria secundaria, y esta representaci´on interna
depende de como el hardware (el procesador) representa dicha informaci´on internamente. Por
este motivo, la entrada y salida binaria de datos puede dar lugar a diversos errores de
compatibilidad cuando se realiza con ordenadores que utilizan diferentes representaciones
internas de los datos (big-endian y little-endian).
Valor num´erico Representaci´on interna
Decimal Hexadecimal Binario (bytes) byte order
987654321 0x3ade68b1 00111010:11011110:01101000:10110001 big-endian (xdr, network-byte-order )
987654321 0x3ade68b1 10110001:01101000:11011110:00111010 little-endian (Intel )
Por ello, y para evitar este tipo de problemas, en determinadas circunstancias se puede optar
por realizar la entrada y salida binaria en una determinada representaci´on independiente de la
representaci´on interna utilizada por el procesador (big-endian o little-endian). Por ejemplo,
para entrada y salida de un dato de tipo int (es v´alida para los tipos simples integrales):
#include <fstream>
#include <climits>
using namespace std;
//------------------------------------
typedef int Tipo;
//------------------------------------
void escribir_big_endian(ostream& out, Tipo x)
{
unsigned char memoria[sizeof(x)];
for (int i = sizeof(x)-1; i >= 0; --i) {
memoria[i] = (unsigned char)(x & ((1U<<CHAR_BIT)-1U));
x = Tipo(x >> CHAR_BIT);
}
out.write((const char*)memoria, sizeof(x));
}
void leer_big_endian(istream& in, Tipo& x)
{
unsigned char memoria[sizeof(x)];
in.read((char*)memoria, sizeof(x));
x = 0;
for (unsigned i = 0; i < sizeof(x); ++i) {
x = Tipo((x << CHAR_BIT) | unsigned(memoria[i]));
}
}
//------------------------------------
void escribir_little_endian(ostream& out, Tipo x)
{
unsigned char memoria[sizeof(x)];
for (unsigned i = 0; i < sizeof(x); ++i) {
memoria[i] = (unsigned char)(x & ((1U<<CHAR_BIT)-1U));
x = Tipo(x >> CHAR_BIT);
}
out.write((const char*)memoria, sizeof(x));
}
void leer_little_endian(istream& in, Tipo& x)
{
unsigned char memoria[sizeof(x)];
in.read((char*)memoria, sizeof(x));
x = 0;
for (int i = sizeof(x)-1; i >= 0; --i) {
x = Tipo((x << CHAR_BIT) | unsigned(memoria[i]));
}
}
//------------------------------------
//------------------------------------
void escribir_big_endian(ostream& out, const string& x)
{
escribir_big_endian(out, x.size());
out << nombre << ’\0’;
}
void leer_big_endian(istream& in, string& x)
{
unsigned sz;
leer_big_endian(in, sz);
x.reserve(sz);
getline(f_ent, x, ’\0’);
assert(sz == x.size());
}
//------------------------------------
void escribir_little_endian(ostream& out, const string& x)
{
escribir_little_endian(out, x.size());
out << nombre << ’\0’;
}
void leer_little_endian(istream& in, string& x)
{
unsigned sz;
leer_little_endian(in, sz);
x.reserve(sz);
getline(f_ent, x, ’\0’);
assert(sz == x.size());
}
//------------------------------------
As´ı, almacenar el nu´mero de caracteres permite poder alojar el espacio en memoria suficiente
para almacenar la secuencia de caracteres que vaya a ser le´ıda, aunque ´esto no es necesario si se
utiliza el tipo string de C++, no obstante es conveniente.
Nota: estos subprogramas son adecuados si la cadena de caracteres no contiene el car´acter ’\0’
como un elemento v´alido de la secuencia de caracteres.
La operaci´on seekg(pos) aplicada a un flujo de entrada permite poner la posici´on del ´ındice
de lectura, en una posici´on absoluta desde el inicio del fichero:
cin.seekg(pos);
La operaci´on seekg(offset, desde) aplicada a un flujo de entrada permite poner la posici´on del
´ındice de lectura, en una posici´on relativa como un desplazamiento desde una posici´on conocida
del fichero, el cual puede ser desde el inicio (ios::beg), desde el final (ios::end) o desde la posici
´on actual (ios::cur):
cin.seekg(offset, desde);
La operaci´on seekp(pos) aplicada a un flujo de salida permite poner la posici´on del ´ındice
de escritura, en una posici´on absoluta desde el inicio del fichero:
cout.seekp(pos);
La operaci´on seekp(offset, desde) aplicada a un flujo de salida permite poner la posici´on del
´ındice de lectura, en una posici´on relativa como un desplazamiento desde una posici´on conocida
del fichero, el cual puede ser desde el inicio (ios::beg), desde el final (ios::end) o desde la posici
´on actual (ios::cur):
cout.seekp(offset, desde);
y podremos realizar tanto operaciones de entrada como de salida de datos sobre el mismo. Todo
lo explicado en los cap´ıtulos anteriores es aplicable a este tipo de flujos, especialmente las
operaciones de acceso directo para posicionar adecuadamente el ´ındice de lectura y el de
escritura.
Es importante considerar que en el caso de flujos abiertos en modo de lectura y escritura
simult´anea, puede suceder que el ´ındice de lectura sea el mismo que el ´ındice de escritura,
por lo que cualquier operaci´on realizada sobre el ´ındice de lectura tambi´en afectar´a al ´ındice de
escritura, y viceversa.
Para utilizar un flujo de entrada de datos desde cadenas de caracteres, se declara una
variable de tipo istringstream sobre la que se puede realizar la entrada de datos como se
realiza de forma habitual con los flujos de entrada. El contenido del flujo desde el que se realizar´a
la entrada de datos se puede especificar durante la propia definici´on de la variable, o mediante el
operador str("..."). Por ejemplo:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
{
string datos = "123 456";
istringstream fent(datos);
int val;
fent >> val;
while (fent) {
cout << "Valor: " << val<<endl;
fent >> val;
}
bool ok = fent.eof();
cout << boolalpha << "Estado: " << ok << endl;
//--------------------------------
fent.clear();
fent.str(" 789 345 ");
fent >> val;
while (fent) {
cout << "Valor: " << val<<endl;
fent >> val;
}
cout << boolalpha << "Estado: " << fent.eof() << endl;
}
Para utilizar un flujo de salida de datos a cadenas de caracteres, se declara una variable de
tipo ostringstream sobre la que se puede realizar la salida de datos como se realiza de forma
habitual con los flujos de salida. El operador str() permite obtener la cadena de caracteres
correspondiente a la salida de datos realizada. As´ı mismo, una llamada a str("") reinicializa el
flujo de salida de datos:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
ostringstream fsal;
for (int i = 0; i < 10; ++i)
{ fsal << i << " ";
}
string salida = fsal.str();
cout << "Salida: " << salida << endl;
//--------------------------------
fsal.str("");
for (int i = 0; i < 10; ++i)
{ fsal << i << " ";
}
salida = fsal.str();
cout << "Salida: " << salida << endl;
}
M´odulos y Bibliotecas
133
vector.cpp
vector.hpp main.cpp
(Implementaci´on)
(Interfaz)
(Principal)
#ifndef
#include
#includevector"vector.hpp"
"vector.hpp"
hpp
////implementaci´on
#define utilizaci´on
vector de vector
hppde int main()
vector
////{ interfaz
privadode vector
···
//· · · p´ublico
·· }·· ··
#endif
···
1 CAP´ITULO 11. MO´ DULOS Y
BIBLIOTECAS
#include "vector.hpp"
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:
Estas bibliotecas no son m ´a s 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.
As´ı mismo, en sistemas Unix existe otro mecanismo (make y makefiles) que permite establecer
relaciones de dependencia entre diversos m´odulos, de tal forma que es capaz de discernir
cuando es necesario compilar los diferentes m´odulos dependiendo de si se produce algu´ n
cambio en los m´odulos de los que dependen (v´ease 11.4).
A Hay que tener en cuenta que un mismo espacio de nombres puede especificarse mu´ltiples
veces, desde diversos ficheros, para an˜adir nuevas entidades al mismo.
A As´ı mismo, tambi´en existen espacios de nombre an´onimos que permiten definir
entidades privadas internas a los m´odulos de implementaci´on, de tal forma que no puedan
producir col- isiones con las entidades pu´blicas del sistema completo. De esta forma,
cualquier declaraci´on y definici´on realizada dentro de un espacio de nombres an´onimo
ser´a u´nicamente visible en el m´odulo de implementaci´on donde se encuentre
(privada), pero no ser´a visible en el exterior del m´odulo.
Por conveniencia, existe un mecanismo intermedio entre los dos m´etodos generales
expli- cados anteriormente. El m´etodo consiste en utilizar la declaraci´on using seguido
por la cualificaci´on expl´ıcita del identificador, de tal forma que la utilizaci´on de dicho
identificador queda disponible para ser utilizada directamente sin cualificaci´on
(cualificaci´on impl´ıcita). Este mecanismo se puede utilizar en ficheros de implementaci
´on, y en el caso de ficheros de encabezamiento siempre se debe utilizar dentro de otro
espacio de nombres, para hacer pu´blico dicho identificador dentro de este nuevo espacio
de nombres. Por ejemplo:
namespace umalcc {
using std::tr1::array;
}
Finalmente, tambi´en es posible crear un alias para un espacio de nombres para facilitar la
cualificaci´on:
Ejemplo
M´odulo Vector
En los sistemas Unix (Linux, MacOS-X, etc.) existe una herramienta para la ayuda en la gesti
´on del proceso de compilaci´on separada. Esta herramienta es capaz de generar autom
´aticamente las dependencias de los ficheros (utilizando para ello makedepend), de tal forma que
cuando algu´ n fichero de encabezamiento cambia, la herramienta make invocar´a la compilaci
´on de s´olo aquellos m´odulos que se vean afectados por los cambios realizados desde la u
´ltima compilaci´on realizada. Por ejemplo, para los siguientes ficheros que componen un
programa completo:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
2 CAP´ITULO 11. MO´ DULOS Y
BIBLIOTECAS
//- fichero: vector.hpp //- fichero: stack.hpp
#ifndef _vector_hpp_ #ifndef _stack_hpp_
#define _vector_hpp_ #define _stack_hpp_
#include <tr1/array> #include "vector.hpp"
namespace umalcc { namespace umalcc {
// ... // ...
} }
#endif #endif
Se define el siguiente fichero Makefile que contiene la enumeraci´on de los ficheros fuente (SRCS) que
componen el programa completo, as´ı como las librer´ıas externas necesarias (LIBS). Este ejemplo
sigue la sintaxis utilizada por GNU Make (donde el s representa el car´acter tabulador ):
7→
´ımbolo
#- Makefile
SRCS=main.cpp vector.cpp
LIBS=-ljpeg
#----------------------------------
PROGNAME=
#------------------------------------------------------------------ -
ifeq ($(strip $(PROGNAME)),)
PROGNAME=$(basename $(notdir $(firstword $(SRCS))))
endif
ifdef NDEBUG
CXXDBGFLAGS=-DNDEBUG -O2
else
CXXDBGFLAGS=-g
endif
#----------------------------------
CXXFLAGS=-ansi -Wall -Werror $(CXXDBGFLAGS)
ALLDEFINES=$(CXXFLAGS)
DEPENDFLAGS=-Y
DEPEND=makedepend
OBJS=$(SRCS:.cpp=.o)
#----------------------------------
all: $(PROGNAME)
ndebug:
7→ make NDEBUG=1
$(PROGNAME): $(OBJS)
7→ $(LINK.cc) -o $@ $(OBJS) $(LIBS)
depend:
7→ -@$(DEPEND) $(DEPENDFLAGS) -- $(ALLDEFINES) $(DEPEND_DEFINES) -- $(SRCS) >& /dev/null
#
# DO NOT DELETE THIS LINE -- make depend depends on it.
an˜ade autom´aticamente las siguientes l´ıneas de dependencias al final del fichero Makefile:
main.o: stack.hpp vector.hpp
vector.o: vector.hpp
Posteriormente, algu´n cambio en los ficheros main.cpp o stack.hpp har´a que la invocaci´on a make
realice la siguiente compilaci´on:
g++ -ansi -Wall -Werror -g -c -o main.o main.cpp
g++ -ansi -Wall -Werror -g -o main main.o vector.o -ljpeg
Sin embargo, si se modifica el fichero vector.hpp, entonces se compilar´a todo de nuevo, ya que
vector.hpp es incluido por todos los m´odulos del programa.
g++ -ansi -Wall -Werror -g -c -o main.o main.cpp
g++ -ansi -Wall -Werror -g -c -o vector.o vector.cpp
g++ -ansi -Wall -Werror -g -o main main.o vector.o -ljpeg
Durante la ejecuci´on de un programa, ´este debe tratar con situaciones an´omalas que suelen
ser excepcionales, es decir, no forman parte del flujo de ejecuci´on normal del programa en
situaciones normales. Sin embargo, estas situaciones, a pesar de ser excepcionales, ocurren, y el
programa debe estar preparado para tratar con ellas.
Dentro de estas situaciones an´omalas, podr´ıamos diferenciar dos grandes grupos, por un
lado est´an aquellas situaciones que se producen debido a errores de programaci´on, que deben ser
evitadas mediante un adecuado disen˜o y programaci´on, y por otro lado est´an aquellas
producidas por situaciones an´omalas excepcionales, que no pueden ser anticipadas, evitadas o
tratadas hasta que dicha situaci´on sucede durante la ejecuci´on del programa.
Respecto a los errores de programaci´on, es algo que no deber´ıa suceder en un programa
bien disen˜ado, pero la realidad ensen˜a que el desarrollador de software debe convivir con
ellos. Por lo tanto, debe estar preparado para tratar con ellos, y disen˜ar estrategias que
minimicen los errores de programaci´on, as´ı como su influencia.
Los asertos constituyen una ayuda importante para la detecci´on de errores de programaci´on
du- rante la fase de desarrollo y depuraci´on del proyecto software. Los asertos comprueban que el
estado del programa sea consistente frente a errores de programaci´on. Hacen referencia a pre-
condiciones, post-condiciones, invariantes, etc. especificando, mediante expresiones l´ogicas,
que el estado del programa en un determinado punto de su ejecuci´on deber´ıa cumplir con unas
determinadas condi- ciones especificadas segu´n el correcto comportamiento te´orico del
programa (v´ease 5.11).
En caso de que las condiciones especificadas en los asertos no se cumplan, entonces reflejan
errores de programaci´on, por lo que el aserto que no se cumpla abortar´a la ejecuci´on del
programa indicando la situaci´on donde fall´o dicha comprobaci´on del estado.
Para poder utilizar asertos, hay que incluir la biblioteca est´andar <cassert>. As´ı, en el
siguiente ejemplo, el aserto que expresa la pre-condici´on del subprograma fallar´a si su invocaci
´on no respeta las restricciones especificadas por la definici´on del mismo. Por ello es necesario que
el programa que hace la invocaci´on implemente algu´n mecanismo (en este caso la sentencia
if (...) { ... }) que garantice que la llamada cumplir´a la restricci´on especificada por la pre-
condici´on. Por otra parte, si no se cumple el aserto que expresa la post-condici´on del
subprograma, entonces habr´a algu´ n error en su programaci´on que deber´a ser subsanado.
145
12.3. GESTIO´ N DE ERRORES MEDIANTE 1
EXCEPCIONES
#include <iostream>
#include <cassert>
using namespace std;
//--------------------------------------------------- -
// retorna el cociente y resto resultado de dividir
// dividendo entre divisor
// Precond: (divisor != 0)
//--------------------------------------------------- -
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
}
//--------------------------------------------------- -
int main()
{
int dividendo, divisor, cociente, resto;
cout << "Introduzca Dividendo y Divisor ";
cin >> dividendo >> divisor;
if (divisor == 0) {
cout << "Error, divisi´on por cero no permitida" << endl;
} else {
dividir(dividendo, divisor, cociente, resto);
cout << "Dividendo: " << dividendo << endl;
cout << "Divisor: " << divisor << endl;
cout << " Cociente: " << cociente << endl;
cout << " Resto: " << resto << endl;
}
}
Los asertos son u´tiles durante el desarrollo y depuraci´on del programa, sin embargo se
suelen desactivar cuando se han corregido todos los errores de programaci´on y se implanta el
software desarrollado. En GNU GCC es posible desactivar la comprobaci´on de asertos mediante la
siguiente directiva de compilaci´on, que ser´a utilizada para la generaci´on del c´odigo final a
implantar:
g++ -DNDEBUG -ansi -Wall -Werror -o programa programa.cpp
Como norma general de disen˜o, un subprograma (o m´etodo) debe suponer que su pre-
condici´on se cumple a su invocaci´on, y as´ı, en este caso, si no es capaz de realizar su tarea
1
RAII (cap. 22.1) es una t´ecnica para la gesti´on autom´atica de los recursos en presencia de excepciones.
Para lanzar (tambi´en conocido como elevar) una excepci´on desde una determinada parte
del c´odigo, la siguiente sentencia lanza una excepci´on del tipo de valor, con el valor
especificado:
throw valor;
Es as´ı mismo tambi´en posible, dentro de un bloque catch, relanzar la excepci´on que est´a siendo
manejada, para que pueda ser tratada en un nivel externo superior, de la siguiente forma:
} catch ( ... ) { // captura cualquier excepci´on
// codigo que maneja una excepci´on sin tipo especificado
throw; // relanza la excepci´on
}
Tambi´en es posible lanzar excepciones de tipos compuestos, los cuales podr´an ser sin campos
internos si s ´ol o interesa el tipo de la excepci´on. Por ejemplo:
#include <iostream>
#include <cassert>
using namespace std;
//--------------------------------------------------- -
struct Division_por_Cero {}; // Tipo de la Excepci´on
//--------------------------------------------------- -
// retorna el cociente y resto resultado de dividir
// dividendo entre divisor
// Lanza excep. Division_por_Cero si (divisor == 0)
//--------------------------------------------------- -
void dividir(int dividendo, int divisor, int& cociente, int& resto)
{
if (divisor == 0) {
throw Division_por_Cero();
}
cociente = dividendo / divisor;
resto = dividendo % divisor;
assert(dividendo == (divisor * cociente + resto)); // POST-CONDICI ´ON
}
//--------------------------------------------------- -
int main()
{
try {
int dividendo, divisor, cociente, resto;
cout << "Introduzca Dividendo y Divisor ";
cin >> dividendo >> divisor;
dividir(dividendo, divisor, cociente, resto);
cout << "Dividendo: " << dividendo << endl;
cout << "Divisor: " << divisor << endl;
Sin embargo, tambi´en es posible que las excepciones de tipos compuestos contengan valores que
puedan informar al entorno superior de la naturaleza del error. Por ejemplo:
#include <iostream>
#include <cassert>
using namespace std;
//--------------------------------------------------- -
struct Division_por_Cero { // Tipo de la Excepci´on
int dividendo;
int divisor;
};
//--------------------------------------------------- -
// retorna el cociente y resto resultado de dividir
// dividendo entre divisor
// Lanza excep. Division_por_Cero si (divisor == 0)
//--------------------------------------------------- -
void dividir(int dividendo, int divisor, int& cociente, int& resto)
{
if (divisor == 0)
{ Division_por_Cero e;
e.dividendo = dividendo;
e.divisor = divisor;
throw e;
}
cociente = dividendo / divisor;
resto = dividendo % divisor;
assert(dividendo == (divisor * cociente + resto)); // POST-CONDICI ´ON
}
//--------------------------------------------------- -
int main()
{
try {
int dividendo, divisor, cociente, resto;
cout << "Introduzca Dividendo y Divisor ";
cin >> dividendo >> divisor;
dividir(dividendo, divisor, cociente, resto);
cout << "Dividendo: " << dividendo << endl;
cout << "Divisor: " << divisor << endl;
cout << " Cociente: " << cociente << endl;
cout << " Resto: " << resto << endl;
} catch (const Division_por_Cero& e)
{ cerr << "Error: Divisi´on por Cero: "
<< e.dividendo <<" / "<< e.divisor<< endl;
} catch ( ... ) {
cerr << "Error inesperado" << endl;
}
}
Considerando el disen˜o del software, es muy importante tomar una adecuada decisi´on
respecto a si una determinada circunstancia debe ser especificada mediante pre-condiciones (y
por lo tanto gestionada dentro del flujo normal de ejecuci´on del programa para evitar la
invocaci´on en dichas situaciones), o por el contrario no especificar la pre-condici´on y lanzar
una excepci´on en el caso de que surja la situaci´on. Normalmente, si la situaci´on es
excepcional dentro de las circunstancias de ejecuci´on del programa, y requiere un tratamiento
excepcional, entonces puede ser m ´a s ade- cuado considerar su tratamiento mediante
excepciones. Sin embargo, si la situaci´on es bastante usual dentro de las circunstancias de
ejecuci´on del programa, entonces probablemente requiera un tratamiento dentro del flujo de
ejecuci´on normal del programa. Una t´ecnica usual de programaci´on robusta respecto a la gesti
´on de recursos en un entorno con excepciones es RAII (v´ease 22.1).
N´otese que es normalmente un s´ıntoma de mal disen˜o el hecho de que el c´odigo del
progra- ma est´e repleto de bloques try/catch, ya que en ese caso mostrar´ıa una situaci´on
donde dichas excepciones no son tan excepcionales, sino que forman parte del flujo normal de
ejecuci´on. No ser´ıa natural que cada llamada a un subprograma tuviera un bloque
try/catch para capturar las excepciones lanzadas por ´este, ya que normalmente dichas
excepciones ser´an excepcionales y normalmente ser´an tratadas a otro nivel.
Por ejemplo, en el disen˜o de una estructura de datos stack (pila), es una situaci´on usual
com- probar si la pila e st ´a vac´ıa, y en caso contrario, sacar un elemento de ella para su
procesamiento. Por lo tanto, el caso de que la pila est´e vac´ıa es una situaci´on normal, y se
deber´a tratar en el flujo de ejecuci´on normal del programa. Sin embargo, es inusual el hecho
de que la pila se llene, ya que normalmente se dimensiona lo suficientemente amplia para poder
resolver un determinado problema. En el caso excepcional de que la pila se llene, este hecho ser
´a un escollo para resolver el problema, por lo que se puede indicar mediante una excepci´on.
//- fichero: stack.hpp
#ifndef _stack_hpp_
#define _stack_hpp_
#include <cassert>
#include <tr1/array>
namespace umalcc {
const unsigned MAX_ELMS = 30;
std::tr1::array<int, MAX_ELMS> Datos;
struct Stack {
unsigned nelms;
Datos elm;
};
//------------------------------------------------
struct Stack_Full {}; // Tipo de la Excepci´on
//------------------------------------------------
inline void init(Stack& s)
{
s.nelms = 0;
}
inline bool empty(const Stack& s)
{ return (s.nelms == 0);
}
inline void push(Stack& s, int e)
{
if (s.nelms >= s.elm.size()) {
throw Stack_Full();
}
s.elm[s.nelms] = e;
++s.nelms;
}
inline int pop(Stack& s)
{
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
8 CAP´ITULO 12. MANEJO DE ERRORES. EXCEPCIONES
Una posible utilizaci´on del m´odulo stack definido anteriormente podr´ıa ser:
#include "stack.hpp"
#include <iostream>
using namespace std;
using namespace umalcc;
void almacenar_datos(Stack& s)
{
for (int x = 0; x < 10; ++x)
{ push(s, x);
}
}
void imprimir(Stack& s)
{
while (! empty(s)) {
cout << pop(s) << " ";
}
cout << endl;
}
int main()
{
Stack s;
try {
init(s);
almacenar_datos(s);
imprimir(s);
} catch (const Stack_Full& e) {
cout << "Error: Pila Llena"<< endl;
} catch ( ... ) {
cout << "Error: Inesperado"<< endl;
}
}
try {
. . .
} catch (const exception& e) {
cout << "Error: " << e.what() << endl;
} catch ( ... ) {
cout << "Error: Inesperado"<< endl;
}
As´ı mismo, el sistema tiene un nu´mero de excepciones predefinidas (derivadas del tipo
exception anterior), que pueden, a su vez, ser base para nuevas excepciones definidas por el
programador (cap. 17):
A El programador puede extender este conjunto de excepciones derivando nuevas clases basadas en
estas excepciones est´andares.
A Nota: para ver una descripci´on de los requisitos que deben cumplir las clases para comportarse
adecuadamente en un entorno de manejo de excepciones v´ease 13.3
#include <iostream>
#include <stdexcept>
#include <cassert>
using namespace std;
//--------------------------------------------------- -
// retorna el cociente y resto resultado de dividir
// dividendo entre divisor
// Lanza excep. domain_error si (divisor == 0)
//--------------------------------------------------- -
void dividir(int dividendo, int divisor, int& cociente, int& resto)
{
if (divisor == 0) {
throw domain_error("divisi´on por cero");
}
cociente = dividendo / divisor;
resto = dividendo % divisor;
assert(dividendo == (divisor * cociente + resto)); // POST-CONDICI ´ON
}
//--------------------------------------------------- -
int main()
{
try {
int dividendo, divisor, cociente, resto;
cout << "Introduzca Dividendo y Divisor ";
cin >> dividendo >> divisor;
dividir(dividendo, divisor, cociente, resto);
cout << "Dividendo: " << dividendo << endl;
cout << "Divisor: " << divisor << endl;
cout << " Cociente: " << cociente << endl;
cout << " Resto: " << resto << endl;
} catch (const domain_error& e)
{ cerr << e.what() << endl;
A medida que aumenta la complejidad del problema a resolver, del mismo modo deben aumen-
tar los niveles de abstracci´on necesarios para disen˜ar y construir su soluci´on algor´ıtmica. As
´ı, la abstracci´on procedimental permite aplicar adecuadamente t´ecnicas de disen˜o
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.
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 TAD
que representa un determinado tipo de datos, y la sem´antica (el
op1()
significado) de las operaciones que se le pueden aplicar, pero donde su
representaci´on e im- plementaci´on internas permanecen ocultas e op2()
inaccesibles desde el exterior, de tal forma que no son necesarias para su
op3()
utilizaci´on. As´ı, podemos consid- erar 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 disen˜o y reutilizaci´on de
software fiable y robusto.
Para un determinado tipo abstracto de datos, se pueden distinguir tres niveles:
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.
155
13.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 1
y se puede definir la clase Stack dentro del espacio de nombres umalcc de la siguiente forma:1
//-stack.hpp
#ifndef _stack_hpp_
#define _stack_hpp_
#include <tr1/array>
#include <cassert>
#include <stdexcept>
namespace umalcc {
class Stack {
private:
// -- Constantes Privadas -- ··
static const unsigned MAX = 256; ·
// -- Tipos Privados -- ·· ←− top
typedef std::tr1::array<int, MAX> Datos; ·
// -- Atributos Privados -- 3
unsigned top; 2
Datos data; 1
data
public:
// -- Constructor por Defecto P´ublico --
Stack() : top(0), data() {}
// -- M´etodos P´ublicos
-- bool empty() const {
return (top == 0);
}
void push(int e) {
if (top >= data.size()) {
throw std::length_error("Stack::push length error");
}
data[top] = e;
1
N´otese que el delimitador punto y coma (;) debe especificarse despu´es de la llave de cierre de la definici´on de
la clase.
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
2 CAP´ITULO 13. TIPOS ABSTRACTOS DE DATOS
++top;
}
int pop() { // Pre-condici´on: (! empty())
assert(! empty());
--top;
return data[top];
}
}; // El delimitador ; termina la definici´on
}
#endif
//------------------------------------------------------------------------ -
Atributos
Los atributos componen la representaci´on interna de la clase, y se definen de igual forma a
los campos de los registros (v´ease 6.3). En nuestro ejemplo, el atributo top es un nu´mero
natural que representa el ´ındice de la cima de la pila, y el atributo data es un array de nu
´meros enteros que contiene los elementos almacenados en la pila, de tal forma que un nuevo
elemento se inserta en la cima de la pila, y el u´ltimo elemento se extrae tambi´en desde la
cima, incrementando o disminuyendo la cima de la pila en estas operaciones.
De igual modo a los registros y sus campos, cada instancia de la clase (objeto) que se defina
almacenar´a su propia representaci´on interna de los atributos de forma independiente a las
otras instancias de la clase (v´ease el apartado m ´a s adelante referido a instancias de clase:
objetos).
Constructores
El constructor de una clase permite construir e inicializar una instancia de la clase (un
objeto). En este ejemplo se ha definido el constructor por defecto, que es utilizado como
mecanismo por defecto para construir objetos de este tipo cuando no se especifica otro m´etodo
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
13.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 3
de construcci´on.
int x = s1.pop();
}
s2.push(7);
s2.push(5);
#include "stack.hpp"
#include <iostream>
using namespace std;
using namespace umalcc;
void almacenar_datos(Stack& s)
{
for (int x = 0; x < 10; ++x) {
s.push(x);
}
}
void imprimir(Stack& s)
{
while (! s.empty() ) {
cout << s.pop() << " ";
}
cout << endl;
}
int main()
{
try {
Stack s;
almacenar_datos(s);
imprimir(s);
} catch (const length_error& e) {
cout << "Error: " << e.what() << endl;
} catch ( ... ) {
cout << "Error: Inesperado"<< endl;
}
}
//- vector.hpp
#ifndef _vector_hpp_
#define _vector_hpp_
#include <iostream>
#include <tr1/array>
#include <cassert>
namespace umalcc {
class Vector {
public:
~Vector() {} // destructor
Vector() : sz(0), v() {} // constructor por defecto
Vector(const Vector& orig); // constructor de copia
Vector(const Vector& orig, unsigned i, unsigned nelms = MAX); // constructor
explicit Vector(unsigned n); // constructor expl´ıcito
Vector& operator =(const Vector& orig); // asignaci´on
unsigned size() const { return sz; };
void clear() { sz = 0; }
void push_back(int e);
void pop_back(); // Pre-condici´on: (size() > 0)
const int& operator [](unsigned i) const { // Pre-condici´on: (i < size())
assert(i < size());
return v[i];
}
int& operator [](unsigned i) { // Pre-condici´on: (i < size())
assert(i < size());
return v[i];
}
Vector subvector(unsigned i, unsigned nelms = MAX) const;
Vector& operator +=(const Vector& vect);
Vector& operator +=(int e) { push_back(e); return *this; }
friend Vector operator + (const Vector& v1, const Vector& v2);
friend bool operator == (const Vector& v1, const Vector& v2);
friend bool operator < (const Vector& v1, const Vector& v2);
friend bool operator != (const Vector& v1, const Vector& v2) { return ! (v1 == v2); }
friend bool operator > (const Vector& v1, const Vector& v2) { return (v2 < v1); }
friend bool operator >= (const Vector& v1, const Vector& v2) { return ! (v1 < v2); }
friend bool operator <= (const Vector& v1, const Vector& v2) { return ! (v2 < v1); }
friend std::ostream& operator << (std::ostream& out, const Vector& vect);
friend std::istream& operator >> (std::istream& in, Vector& vect);
private:
static const unsigned MAX = 256;
typedef std::tr1::array<int, MAX> Datos;
// -- Atributos --
unsigned sz;
Datos v;
};
}
#endif
//------------------------------------------------------------------ -
Atributos
Los atributos componen la representaci´on interna de la clase, de tal forma que cada objeto
se compone de un nu´ mero natural (sz) que indica cuantos elementos tiene almacenados, y de
un array (v) donde se almacenan los elementos de forma consecutiva desde el comienzo del
mismo. Es importante recordar que cada instancia de la clase (objeto) contiene su propia
representaci´on interna (atributos) independiente de las otras instancias.
Destructor
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 (~Vector()).
Este destructor s er ´a invocado autom´aticamente (sin par´ametros actuales) para una
determina- da instancia (objeto) de esta clase cuando dicho objeto deba ser destruido,
normalmente suced- e r ´a cuando el flujo de ejecuci´on del programa salga del ´ambito de
visibilidad de dicho objeto.
{
Vector v1; // construcci´on del objeto ’v1’
... // utilizaci´on del objeto ’v1’
} // destrucci´on del objeto ’v1’
En el cuerpo del destructor se especificar´an las sentencias necesarias para destruir el objeto, tales
co- mo liberaciones de los recursos que contenga, etc. Posteriormente, el destructor invoca autom
´atica- mente a los destructores de los atributos miembros del objeto para que ´estos sean
destruidos. En el caso del ejemplo no es necesario realizar ninguna acci´on adicional, por lo
que el cuerpo del de- structor se define en l´ınea como vac´ıo. Esto u´ltimo es equivalente a no
definir el destructor de la clase, en cuyo caso el compilador lo define autom´aticamente como vac
´ıo. N´otese que un destructor nunca deber´a lanzar excepciones.
Constructores
Los constructores de la clase especifican las diferentes formas de crear (construir) un determina-
do objeto y establecen su invariante. Los constructores se denominan con el mismo identificador de
la clase, seguidamente se especifican entre par´entesis los par´ametros necesarios para la construcci
´on. Para su implementaci´on, tras el delimitador (:) se especifica la lista de inicializaci´on,
donde aparecen segu´n el orden de declaraci´on todos los atributos miembros del objeto, as´ı como
los valores que toman especificados entre par´entesis (se invoca al constructor adecuado segu´ n 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.
Para una determinada clase, pueden existir mu´ltiples definiciones de constructores,
diferen- ci´andose entre ellos por los par´ametros. De entre todos ellos, hay dos constructores
especiales: el constructor por defecto, y el constructor de copia.
N´otese como en este segundo ejemplo se invoca directamente al constructor para construir un
objeto.
Constructor de Copia
El constructor de copia recibe como par´ametro un objeto de la misma clase y especifica
como crear un nuevo objeto que sea una copia del objeto que se recibe. Este constructor se
utiliza en las inicializaciones de objetos de este tipo y en el paso de par´ametros por valor a
subprogramas (aunque este caso est´a desaconsejado, prefiri´endose en esta situaci´on el paso
por referencia constante). Si el programador no define expl´ıcitamente el constructor de copia,
entonces el compilador define autom´aticamente un constructor de copia que realiza una copia
individual de todos los atributos del objeto, invocando impl´ıcitamente a los constructores de
copia de cada atributo miembro.
Por ejemplo, se puede crear un objeto de tipo Vector mediante copia de otro objeto de tipo
Vector de la siguiente forma:
Vector v3(v1);
o tambi´en:
Vector v4 = v1;
o tambi´en:
Vector v5 = Vector(v1);
N´otese como en este u´ltimo ejemplo se invoca directamente al constructor para construir un objeto.
A Constructores Expl´ıcitos
o tambi´en:
Vector v7 = Vector(5);
Operador de Asignaci´on
El operador de asignaci´on define como se realiza esta operaci´on para objetos de esta
clase. En caso de que el programador no defina este m´etodo, entonces el compilador define
autom´atica- mente un operador de asignaci´on que invoca autom´aticamente al operador de
asignaci´on para cada atributo miembro de la 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 ningu´n valor, mientras
que en el caso del operador de asignaci´on, el objeto ya tiene previamente un valor que deber´a
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
6 CAP´ITULO 13. TIPOS ABSTRACTOS DE DATOS
ser sustituido por
el nuevo valor. Este valor previo deber´a, en ocasiones, ser destruido antes de realizar la asignaci
´on del nuevo valor.
A En el lenguaje de programaci´on C++, el operador de asignaci´on devuelve una referencia al
propio objeto que est ´a siendo asignado. Esto es as´ı porque se permite encadenar sucesivas
asignaciones en la misma sentencia, aunque realizar este encadenamiento puede traer consigo
problemas de legibilidad, por lo que en general se desaconseja su utilizaci´on.
A N´otese que devolver una referencia es diferente de la devoluci´on normal donde se devuelve
un determinado valor temporal. En caso de devolver una referencia, se devuelve el propio
objeto y se le puede aplicar un m´etodo directamente, o incluso puede serle asignado un nuevo
valor (puede aparecer en la parte izquierda de una sentencia de asignaci´on).
mientras que los operadores se aplican utilizando la notaci´on correspondiente (infija, post-fija
o prefija) segu´ n el operador especificado. Por ejemplo:
Vector v1(7);
v1.push_back(5);
Vector v2;
v2 = v1.subvector(3, 2);
v2 += Vector(3);
El ejemplo muestra como se define el operador de indexaci´on ([]) para el tipo Vector, el cual
se define de forma independiente para ser aplicado a objetos constantes y para ser aplicado a
objetos variables. Ambos reciben como par´ametro un ´ındice, y devuelven una referencia al
elemento que ocupa dicha posici´on en el vector (una referencia constante en caso de ser
aplicado a un objeto constante).
Como se ha explicado anteriormente, devolver una referencia es diferente de la devoluci´on
normal donde se devuelve un determinado valor temporal. En caso de devolver una referencia,
se devuelve el propio objeto y se le puede aplicar un m´etodo directamente, o incluso, en caso
de
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
8 CAP´ITULO 13. TIPOS ABSTRACTOS DE DATOS
no ser una referencia constante, puede serle asignado un nuevo valor (puede aparecer en la parte
izquierda de una sentencia de asignaci´on).
Ambos m´etodos devuelven una referencia a un elemento, este hecho hace posible que el
resultado de dicho m´etodo pueda aparecer en la parte izquierda de una asignaci´on, por
ejemplo
v1[3] = 5;
int x = v1[3];
Vector v1(7);
v1.push_back(5);
Vector v2;
v2 = v1.subvector(3, 2);
Vector v3 = v1 + v2;
if (v1 < v2) {
cout << "Menor" << endl;
}
cout << "Introduzca el contenido de un vector entre llaves:" << endl;
cin >> v1;
cout << "Vector: " << v1 << endl;
Implementaci´on Separada
En este ejemplo, realizaremos la implementaci´on de los m´etodos de la clase Vector en un
fichero independiente, diferenciando claramente los tres niveles de los tipos abstractos de datos
(interfaz, implementaci´on y utilizaci´on). Adem´as, se facilita la ocultaci´on de la
implementaci´on, el proceso de compilaci´on separada, y la distribuci´on del m´odulo de
biblioteca.
Se deber´an definir los m´etodos, operadores y amigos de la clase en el mismo espacio de
nombres donde est´a definida la clase, y el identificador de cada m´etodo y operador debe ser
cualificado con el identificador de la clase a la que corresponde (utilizando el delimitador (::).
No obstante, los subprogramas y operadores amigos no ser´an cualificados, ya que no son
miembros de la clase.
En nuestro ejemplo, la implementaci´on del m´odulo del tipo abstracto de datos Vector
comienza con un espacio de nombres an´onimo donde se definen los subprogramas privados
necesarios para la implementaci´on del m´odulo. Dentro del espacio de nombres umalcc se
definen los m´etodos de la clase Vector. Para ello, cada nombre de m´etodo o operador se
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
13.1. TIPOS ABSTRACTOS DE DATOS EN C++: CLASES 9
cualifica con el nombre de la clase a la que pertenece (Vector seguido por el delimitador ::).
//-vector.cpp
#include "vector.hpp"
#include <stdexcept>
namespace { // an´onimo. zona privada al m´odulo
inline unsigned minimo(unsigned x, unsigned y)
{
return (x < y) ? x : y ;
}
}
namespace umalcc {
Vector::Vector(const Vector& orig)
: sz(0), v()
{
for (sz = 0; sz < orig.sz; ++sz)
{ v[sz] = orig.v[sz];
}
}
Vector::Vector(const Vector& orig, unsigned inicio, unsigned nelms)
: sz(0), v()
{
if (inicio > orig.sz) {
throw std::out_of_range("Vector::Vector out of range");
}
unsigned n = minimo(nelms, orig.sz - inicio);
for (sz = 0; sz < n; ++sz) {
v[sz] = orig.v[inicio + sz];
}
}
Vector::Vector(unsigned n)
: sz(0), v()
{
if (n > v.size()) {
throw std::length_error("Vector::Vector length error");
}
for (sz = 0; sz < n; ++sz)
{ v[sz] = 0;
}
}
Vector& Vector::operator = (const Vector& orig)
{
if (this != &orig) { // comprobar autoasignaci´on
// destruir el valor antiguo y copiar el nuevo
for (sz = 0; sz < orig.sz; ++sz) {
v[sz] = orig.v[sz];
}
}
return *this;
}
void Vector::push_back(int e) {
if (sz >= v.size()) {
throw std::length_error("Vector::push_back length error");
}
v[sz] = e;
++sz;
}
void Vector::pop_back() {
assert(size() > 0); // Pre-condici´on
--sz;
}
La implementaci´on de los constructores, los m´etodos y los operadores sigue la misma pauta
que lo explicado anteriormente, considerando que cuando se hace referencia a un determinado
atributo o m´etodo, ´estos se refieren (o aplican) a los del propio objeto sobre los que se
aplique el m´etodo en cuesti´on.
Adem´as, en caso de que un objeto de la misma clase se reciba como par´ametro, entonces
es posible acceder a sus atributos e invocar a sus m´etodos mediante el operador punto (.), como
por ejemplo en el constructor de copia de la clase Vector, donde se accede a los atributos sz
y v del objeto recibido como par´ametro (orig) de la siguiente forma: orig.sz y orig.v[...].
Implementaci´on de Constructores
El constructor de copia se define creando un vector vac´ıo en la lista de inicializaci´on, donde
se inicializa cada atributo, segu´ n el orden en que han sido declarados, con el valor adecuado
invocando al constructor especificado.Posteriormente se van asignando los elementos del vector
especificado como par´ametro (orig) al vector que se est ´a construyendo. N´otese que se
accede a los atributos del vector especificado como par´ametro utilizando el operador punto
(orig.sz y orig.v[...]), sin embargo, se accede a los atributos del vector que se est´a creando
especificando u´nicamente sus identificadores (sz y v). Tambi´en se define una un constructor
que copia una parte del contenido de otro vector, en este
caso, la implementaci´on comprueba que los valores especificados son correctos y copia los
elementos especificados del vector orig al vector que se es t ´a construyendo.
En la implementaci´on separada del constructor expl´ıcito no se debe utilizar el especificador
explicit, ya que fue especificada durante la definici´on de la clase. Esta implementaci´on
comprue- ba que los valores especificados como par´ametros son correctos, y an˜ ade al vector
el nu´mero de elementos especificado con el valor cero (0).
As´ı, this representa la direcci´on en memoria del objeto que recibe la asignaci´on, y
&orig representa la direcci´on en memoria del objeto que se recibe como par´ametro. Si
ambas di- recciones son diferentes, entonces significa que son variables diferentes y se
puede realizar la asignaci´on.
2. Una vez que se ha comprobado que se es t ´a asignando un objeto diferente, se debe
destruir el valor antiguo del objeto receptor de la asignaci´on. Esta operaci´on a veces no
es necesaria realizarla expl´ıcitamente, ya que el siguiente paso puede realizarla de forma
impl´ıcita.
2
2
N´otese que no es posible invocar expl´ıcitamente al constructor de copia u operador de asignaci´on para un atributo miembro de tipo
array predefinido, sin embargo si es posible hacerlo si es de tipo array de tr1.
As´ı, se invoca al constructor de copia (Clase(orig)) para copiar el valor a asignar a una vari-
able temporal, y despu´es se intercambian los valores de esta variable temporal con los del
propio objeto (.swap(*this)). De esta forma el objeto recibir´a la copia de los valores de orig,
y pasa- do sus antiguos valores a la variable temporal. Posteriormente, la variable temporal se
destruye, destruy´endose de esta forma el valor anterior del objeto asignado.
de incremento (+=) para que an˜ada el contenido del segundo vector al vector resultado que ser
´a fi- nalmente devuelto como resultado de la funci´on.
Tambi´en se definen los operadores de comparaci´on de igualdad (==) y menor (<),
realizando una comparaci´on lexicogr´afica de los elementos de ambos vectores recibidos como
par´ametros. Los dem´as operadores relacionales se implementan utilizando ´estos dos operadores
implementados.
Por u´ltimo se implementan los operadores de entrada y salida, de tal forma que
considerando que el nu´mero de elementos del vector es variable, se deben mostrar y leer el nu
´mero de elementos contenidos en el vector, seguido por una secuencia de los elementos del
mismo, separados por espacios. Por ejemplo, si el vector contiene los elementos
{ 1, 2, 3, 4, 5, 6 }
//-vector.cpp
namespace umalcc {
// ..........
std::ostream& operator << (std::ostream& out, const Vector& vect)
{
out << "{ ";
for (unsigned i = 0; i < vect.sz; ++i)
{ out << vect.v[i] << " ";
}
out << "}";
return out;
}
std::istream& operator >> (std::istream& in, Vector& vect)
{
char ch = ’\0’;
in >> ch;
if (ch != ’{’) {
in.unget();
in.setstate(std::ios::failbit);
} else if ( ! in.fail() ) {
vect.clear();
in >> ch;
while ((! in.fail()) && (ch != ’}’) ) {
in.unget();
int x;
in >> x;
if ( ! in.fail() ) {
vect.push_back(x);
}
in >> ch;
}
}
return in;
}
}
//------------------------------------------------------------------------ -
As´ı, en primer lugar el subprograma de entrada de datos lee un car´acter desde el flujo de
entrada, el cual si no es igual al car´acter ’{’ significa que la entrada de datos es err´onea (no
concuerda con el formato de entrada especificado), por lo que el car´acter previamente le´ıdo se
devuelve al flujo de entrada y el flujo de entrada se pone al estado err´oneo.
std::istream& operator >> (std::istream& in, Vector& vect)
{
char ch = ’\0’;
in >> ch;
if (ch != ’{’) {
in.unget();
in.setstate(std::ios::failbit);
En otro caso, si el car´acter le´ıdo es igual a ’{’ entonces se procede a leer los datos que se
an˜ adir´an al vector. Para ello, se inicializa el vector a vac´ıo mediante la llamada al m´etodo
clear(). Poste- riormente se lee un car´acter desde el flujo de entrada, con el objetivo de poder
detectar el final de los datos de entrada mediante el car´acter ’}’:
} else if ( ! in.fail() ) {
vect.clear();
in >> ch;
Si la lectura fue correcta y distinta del car´acter ’}’ entonces significa que hay datos para leer, por
lo que el car´acter previamente le´ıdo se devuelve al flujo de entrada, y se intenta ahora leer el
dato (un nu´mero entero) desde el flujo de entrada, para an˜adirlo al vector en caso de que se
realice una lectura correcta:
while ((! in.fail()) && (ch != ’}’) ) {
in.unget();
int x;
in >> x;
if ( ! in.fail() ) {
vect.push_back(x);
}
A continuaci´on se vuelve a leer otro car´acter con el objetivo de poder detectar el final de los
datos de entrada mediante el car´acter ’}’, y se vuelve al comienzo del bucle while:
in >> ch;
}
Cuando el car´acter le´ıdo sea igual al car´acter ’}’ entonces se sale del bucle de lectura y los
datos le´ıdos se habr´an an˜adido al vector. En caso de que se haya producido algu´n error
de formato durante la entrada de datos, entonces el flujo de entrada se pondr´a en un estado
err´oneo.
Finalmente, se devuelve el flujo de entrada para que se puedan encadenar en cascada las
invo- caciones a este operador, como se realiza habitualmente:
}
return in;
}
v2 = v1.subvector(3, 2);
cout << "v2: " << v2 << endl;
//v3 = v4; // Error de Compilaci´on
if (3 < v1.size()) {
v1[3] = 9;
cout << "v1: " << v1 << endl;
}
//v3[3] = 9; // Error de Compilaci´on
v5 += v1;
cout << "v5: " << v5 << endl;
Vector v6 = v1 + v2;
cout << "v6: " << v6 << endl;
Antes de lanzar una excepci´on, se deber´a liberar todos los recursos adquiridos que no
pertenez- can a ningu´n otro objeto. (Utilizar la t´ecnica RAII “adquisici´on de recursos es
inicializaci´on” sec. 22.1).
Antes de lanzar una excepci´on, se deber´a asegurar de que cada atributo se encuentra
en un estado “v´alido”. Es decir, dejar cada objeto en un estado en el que pueda ser
destruido por su destructor de forma coherente y sin lanzar ninguna excepci´on.
N´otese que un constructor es un caso especial, ya que cuando lanza una excepci´on no
deja ningu´n objeto “creado” para destruirse posteriormente, por lo que se debe asegurar de
liberar todos los recursos adquiridos durante la construcci´on fallida antes de lanzar la
excepci´on (RAII “adquisici´on de recursos es inicializaci´on”).
Cuando se lanza una excepci´on dentro de un constructor, se ejecutar´an los destructores
aso- ciados a los atributos que hayan sido previamente inicializados al llamar a sus
constructores en la lista de inicializaci´on. Sin embargo, los recursos obtenidos dentro del
cuerpo del con- structor deber´an liberarse expl´ıcitamente en caso de que alguna excepci
´on sea lanzada. (RAII “adquisici´on de recursos es inicializaci´on”).
A Atributos Mutables
#include <iostream>
using namespace std;
class X {
public:
X(int v) : valor(v) { ++cnt; }
static unsigned cuenta() { // M´etodo est´atico
return cnt;
}
friend ostream& operator << (ostream& out, const X& x) {
return out << x.valor ;
}
private:
static unsigned cnt; // Atributo est´atico
int valor;
};
//------------------
unsigned X::cnt = 0; // definici´on e inicializaci´on del atributo est´atico
//------------------
int main()
{
X x1(1);
X x2 = 2;
unsigned num_objs = X::cuenta(); // invocaci´on de m´etodo est´atico
cout << "N´umero de Objetos de Clase X creados: " << num_objs << endl;
cout << x1 << " " << x2 << endl;
}
Se pueden definir los operadores de incremento y decremento, tanto prefijo, como post-fijo.
#include <iostream>
using namespace std;
class X {
public:
X(int v) : valor(v) {}
X& operator++ () { // Incremento Prefijo
++valor;
return *this;
}
X& operator-- () { // Decremento Prefijo
--valor;
return *this;
}
X operator++ (int) { // Incremento Postfijo
X anterior(*this); // copia del valor anterior
++valor;
return anterior; // devoluci´on del valor anterior
}
X operator-- (int) { // Decremento Postfijo
X anterior(*this); // copia del valor anterior
--valor;
return anterior; // devoluci´on del valor anterior
}
friend ostream& operator << (ostream& out, const X& x) {
return out << x.valor ;
}
private:
int valor;
};
int main()
{
X x1 = 1;
++x1;
x1++;
cout << x1 << endl;
--x1;
x1--;
cout << x1 << endl;
}
A Conversiones de Tipo
Es posible definir conversiones de tipo impl´ıcitas para un determinado objeto de una clase.
El tipo destino puede ser cualquier tipo, ya sea predefinido o definido por el usuario. Por
ejemplo, se puede definir una conversi´on directa e impl´ıcita de un objeto de la clase X definida
a continuaci´on a un tipo entero.
#include <iostream>
using namespace std;
class X {
public:
X(int v) : valor(v) {} // constructor
operator int () const { // operador de conversi´on a int
return valor;
}
private:
int valor;
};
int main()
{
const X x1(1); // invocaci´on expl´ıcita al constructor
X x2 = 2; // invocaci´on impl´ıcita al constructor
cout << x1 << " " << x2 << endl; // invocaci´on impl´ıcita al op de conversi´on
int z1 = x1; // invocaci´on impl´ıcita al op de conversi´on
int z2 = int(x2); // invocaci´on expl´ıcita al op de conversi´on
cout << z1 << " " << z2 << endl;
X x3 = x1 + x2; // inv. impl. al op de conversi´on y constructor
cout << x3 << endl; // invocaci´on impl´ıcita al op de conversi´on
}
En este ejemplo se aprecia como los valores de x1 y x2 se convierten tanto impl´ıcitamente como
expl´ıcitamente a valores enteros al mostrarlos por pantalla, y tambi´en al asignarlos a variables
de tipo entero (z1 y z2). Adem´as, tambi´en hay una conversi´on impl´ıcita cuando se suman los
valores de x1 y x2, y posteriormente el resultado de tipo int se convierte y asigna a un objeto de
clase X mediante el constructor que recibe un entero como par´ametro.
As´ı, se puede apreciar que los m´etodos de conversi´on de tipos permiten definir la conversi
´on impl´ıcita de un determinado tipo abstracto de datos a otro determinado tipo especificado
por el operador de conversi´on correspondiente. Adem´as, Los constructores tambi´en ofrecen
otro tipo de conversi´on impl´ıcita (o expl´ıcita si se define mediante el especificador explicit)
desde valores de otros tipos al tipo abstracto de datos para el que se define el constructor.
Por lo tanto, en el caso del ejemplo anterior, el constructor X(int v) ofrece una conversi´on
impl´ıcita de un valor de tipo int a un objeto de tipo X. As´ı mismo, el operador de conversi´on
operator int () ofrece una conversi´on impl´ıcita de un objeto de tipo X a un valor de tipo int.
Sin embargo, si el constructor de la clase X se define como explicit, entonces es necesario
invocarlo expl´ıcitamente para realizar la conversi´on de tipo int al tipo X, por ejemplo:
#include <iostream>
using namespace std;
class X {
public:
explicit X(int v) : valor(v) {} // constructor expl´ıcito
operator int () const { // operador de conversi´on a int
return valor;
}
private
int valor;
};
int main()
{
const X x1(1); // invocaci´on expl´ıcita al constructor
X x2(2); // invocaci´on expl´ıcita al constructor
cout << x1 << " " << x2 << endl; // invocaci´on impl´ıcita al op de conversi´on
int z1 = x1; // invocaci´on impl´ıcita al op de conversi´on
int z2 = int(x2); // invocaci´on expl´ıcita al op de conversi´on
cout << z1 << " " << z2 << endl;
X x3 = X(x1 + x2); // inv. impl. al op de conv. y expl. al ctor
cout << x3 << endl; // invocaci´on impl´ıcita al op de conversi´on
}
Los siguientes operadores ya tienen un significado predefinido para cualquier tipo que se defina,
aunque pueden ser redefinidos:
operator= operator& operator,
La conversi´on de tipos y los siguientes operadores s´olo podr´an definirse como funciones
miembros de objetos:
operator= operator[] operator() operator->
double a = 7.5;
179
14.2. TIPOS ABSTRACTOS DE DATOS GENE 1
´ RICOS
double b = maximo(a, 12.0);
intercambio(a, b);
En el ejemplo se puede ver que los par´ametros de entrada a los subprogramas se pasan por
referencia constante, 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. Nota: el siguiente ejemplo se proporciona
para ilustrar esta caracter´ıstica, aunque no debe ser tomado como ejemplo de disen˜o, ya que
este ejemplo en concreto carece de utilidad pr´actica.
template <typename TipoDestino, typename TipoOrigen>
inline TipoDestino convertir(const TipoOrigen& x)
{
return TipoDestino(x);
}
int main()
{
int x = convertir<int>(3.14);
}
};
inline bool operator!=(const string& n, const Persona& p)
{
return n != p.nombre;
}
typedef array<Persona, 5> APers;
void prueba2()
{
APers a = {{ { "pepe", "111" }, { "juan", "222" }, { "mar´ıa", "333" },
{ "marta", "444" }, { "lucas", "555" }
}};
unsigned i = buscar("mar´ıa", a);
cout << i << endl;
}
//-------------------------------------
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.
//-vector.hpp
#ifndef _vector_hpp_
#define _vector_hpp_
#include <tr1/array>
#include <cassert>
#include <stdexcept>
namespace umalcc {
template <typename Tipo, unsigned SIZE>
class Vector {
public:
Vector() : sz(0), v() {}
unsigned size() const {
return sz;
}
void clear()
{ sz = 0;
}
void push_back(const Tipo& e) {
if (sz >= v.size()) {
throw std::runtime_error("Vector::push_back");
}
v[sz] = e;
++sz;
}
void pop_back() {
assert(size() > 0); // Pre-condici´on
--sz;
}
const Tipo& operator [] (unsigned i) const {
assert(i < size()); // Pre-condici´on
return v[i];
}
Tipo& operator [] (unsigned i)
{ assert(i < size()); // Pre-condici
´on return v[i];
}
friend std::ostream& operator << (std::ostream& out, const Vector& vect)
{ out << vect.sz << " ";
for (unsigned i = 0; i < vect.sz; ++i)
{ out << vect.v[i] << " ";
}
return out;
}
friend std::istream& operator >> (std::istream& in, Vector& vect)
{
vect.clear();
unsigned nelms;
in >> nelms;
for (unsigned i = 0; (i < nelms)&&(!in.fail()); ++i) {
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
14.2. TIPOS ABSTRACTOS DE DATOS GENE 5
´ RICOS
Tipo x;
in >> x;
if ( ! in.fail() ) {
vect.push_back(x);
}
}
return in;
}
private:
typedef std::tr1::array<Tipo, SIZE> Datos;
// -- Atributos --
unsigned sz;
Datos v;
};
}
#endif
As´ı, se puede utilizar el vector definido anteriormente como base para implementar un tipo
Stack. En este caso, se utilizan los constructores por defecto y de copia, as´ı como el operador de
asignaci´on y el destructor de la clase generados autom´aticamente por el compilador. En el
ejemplo puede apreciarse como se realiza una instanciaci´on expl´ıcita de un tipo gen´erico para
la definici´on del atributo v.
//-stack.hpp
#ifndef _stack_hpp_
#define _stack_hpp_
#include <cassert>
#include "vector.hpp"
namespace umalcc {
template <typename Tipo, unsigned SIZE>
class Stack {
public:
bool empty() const
{ return v.size() == 0;
}
void clear()
{ v.clear();
}
void push(const Tipo& e) {
v.push_back(e);
}
void pop(Tipo& e) {
assert(size() > 0); // Pre-condici´on
e = v[v.size() - 1];
v.pop_back();
}
private:
// -- Atributos --
Vector<Tipo, SIZE> v;
};
}
#endif
Ambos tipos abstractos de datos gen´ericos se pueden utilizar de la siguiente forma, donde se
definen dos tipos (Vect y Pila) como instanciaciones expl´ıcitas de los tipos gen´ericos
previamente definidos.
#include <iostream>
#include "vector.hpp"
#include "stack.hpp"
int main()
{
try {
Pila s;
almacenar_datos(s, 10);
imprimir(s);
Vect v;
anyadir(v, 4);
eliminar(v, 1);
} catch (const length_error& e) {
cout << "Error: " << e.what() << endl;
} catch ( ... ) {
cout << "Error: Inesperado"<< endl;
}
}
//-vector.hpp
#ifndef _vector_hpp_
#define _vector_hpp_
#include <iostream>
#include <tr1/array>
#include <cassert>
#include <stdexcept>
namespace umalcc {
//-----------------------------
// Declaraci´on adelantada de la clase Vector
template <typename Tipo, unsigned SIZE>
class Vector;
//-----------------------------
// Prototipo del operador de entrada y salida para la clase Vector
template <typename Tipo, unsigned SIZE>
std::ostream& operator <<(std::ostream&, const Vector<Tipo,SIZE>&);
template <typename Tipo, unsigned SIZE>
std::istream& operator >>(std::istream&, Vector<Tipo,SIZE>&);
//-----------------------------
template <typename Tipo, unsigned SIZE>
class Vector {
public:
Vector() ;
unsigned size() const ;
void clear() ;
void push_back(const Tipo& e) ;
void pop_back() ;
const Tipo& operator [] (unsigned i) const ;
Tipo& operator [] (unsigned i) ;
friend std::ostream& operator << <>(std::ostream&, const Vector<Tipo,SIZE>&);
friend std::istream& operator >> <>(std::istream&, Vector<Tipo,SIZE>&);
private:
typedef std::tr1::array<Tipo, SIZE> Datos;
// -- Atributos --
unsigned sz;
Datos v;
};
//-----------------------------
template <typename Tipo, unsigned SIZE>
Vector<Tipo,SIZE>::Vector()
: sz(0), v()
{}
//-----------------------------
Por ejemplo, para definir e implementar un tipo Subrango de otro tipo b´asico integral, de tal
forma que si se asigna un valor incorrecto fuera del rango especificado, se lance una excepci
´on. En este ejemplo, el compilador genera autom´aticamente el constructor de copia, el
operador de asignaci´on y el destructor de la clase.
//-fichero: subrango.hpp
#include <iostream>
#include <stdexcept>
namespace umalcc {
// Declaraci´on adelantada de la clase Subrango
template<typename Tipo, Tipo menor, Tipo mayor>
class Subrango;
: valor(i)
{
if ((valor < menor) || (valor > mayor)) {
throw std::range_error("Subrango::Subrango range error");
}
}
int main()
{
try {
Dato x, z;
Subrango<char, ’a’, ’z’> y;
x = 17;
x = 25; // fuera de rango
y = ’m’;
y = ’M’; // fuera de rango
z = x;
x = x + 5; // fuera de rango
cout << x << " " << y << " " << z << endl;
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 e st´a condicionado por el ´ambito de
su declaraci´on. Adem´as, el nu´mero de variables autom´aticas utilizadas en un determinado
programa es t ´a especificado expl´ıcitamente en el propio programa, y por lo tanto su capacidad
de almace- namiento es t ´a tambi´en especificada y predeterminada por lo especificado expl
´ıcitamente en el pro- grama. Es decir, con la utilizaci´on u´ nica de variables autom´aticas, la
capacidad de almacenamiento de un determinado programa es t ´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).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 segu´ n 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 acompan˜ada 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 s e r´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).
1
1
En 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.
191
1 CAP´ITULO 15. MEMORIA DINA´ MICA.
PUNTEROS
15.1. Punteros
El tipo puntero es un tipo simple que permite a un determinado programa acceder a posi-
ciones concretas de memoria, y m ´a s espec´ıficamente a determinadas zonas de la memoria din
´ami- ca. Aunque el lenguaje de programaci´on C++ permite otras utilizaciones m ´a s
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;
struct Persona
{ string nombre;
string telefono;
unsigned edad;
};
typedef Persona* PPersona;
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 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 al entrar el flujo de ejecuci´on en el
´ambito de visibilidad de la variable, y posteriormente se destruyen autom´aticamente 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:
int main()
{
p1: ?
PInt p1;
PPersona p2; 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. As´ı, se pueden
definir las variables p1 y p2 e inicializarlas a un valor indicando que no apuntan a nada.
int main()
{
p1:
PInt p1 = NULL;
PPersona p2 = NULL; p2:
}
A Alternativamente, tambi´en es posible declarar directamente las variables de tipo puntero sin necesi-
dad de definir expl´ıcitamente un tipo puntero:
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
15.2. GESTIO´ N DE MEMORIA DINA´ MICA 193
int main()
{
int* p1 = NULL;
Persona* p2 = NULL;
}
int main()
{ ◦
ptr: −→
???
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:
// manipulaci´on ptr:
X
?
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 s er´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.
// manipulaci´on
perdida
pepe
111
// no se libera la memoria din´amica apuntada por ptr
// se destruye la variable local ptr 5
}
Persona p = *ptr;
*ptr = p;
delete ptr;
}
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, se
puede utilizar el operador de desreferenciaci´on (->) m ´a s utilizado comu´nmente. 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:
Numero(int v) : val(v) {}
int valor() const { return val; }
private:
int val;
};
typedef Numero* PNumero;
int main()
{
PNumero ptr = new Numero(5);
delete ptr;
}
int main()
{
PPersona p1, p2;
// ...
if (p1 == p2) {
// ...
}
if (p1 != NULL) {
// ...
}
// ...
}
A Alternativamente, tambi´en es posible declarar directamente los par´ametros de tipo puntero sin
necesidad de definir expl´ıcitamente un tipo puntero:
struct Persona
{ string nombre;
string telefono;
};
typedef Persona* PPersona;
typedef const Persona* PCPersona;
void datos(PCPersona ptr)
{
ptr->nombre = "pepe"; // error, no es posible modificar entidad constante
ptr->telefono = "111"; // error, no es posible modificar entidad constante
}
int main()
{
PPersona ptr = new Persona;
datos(ptr);
}
A Alternativamente, tambi´en es posible declarar directamente los par´ametros de tipo puntero con-
stante sin necesidad de definir expl´ıcitamente un tipo puntero:
struct Persona
{ string nombre;
string telefono;
};
void datos(const Persona* ptr)
{
ptr->nombre = "pepe"; // error, no es posible modificar entidad constante
ptr->telefono = "111"; // error, no es posible modificar entidad constante
}
int main()
{
Persona* ptr = new Persona;
datos(ptr);
}
lista: ◦ −→ ◦ −→ ◦ −
mar´ıa lista: ◦ −→
pepe pepe → ◦ −→
juan mar´ıa
◦ − ◦ −
pepe pepe
◦Q
pepe Q
◦
pepe
◦
pepe
◦ −
pepe
lista: ◦ −→ → lista: ◦ −→ → ◦ −→
mar´ıa juan mar´ıa
X ptr: ?
X
lista: ◦ −→ lista: ◦ −→ →
mar´ıa mar´ıa
{
if (lista != NULL) {
eliminar_nodo(lista);
}
}
//------------------------
void eliminar_ultimo()
{
if (lista != NULL) {
if (lista->sig == NULL) {
eliminar_nodo(lista);
} else {
PNodo ant = lista;
PNodo ptr = lista->sig;
while (ptr->sig != NULL)
{ ant = ptr;
ptr = ptr->sig;
}
eliminar_nodo(ant->sig);
}
}
}
//------------------------
void eliminar_elm(const Tipo& d)
{
if (lista != NULL) {
if (d == lista->dato) {
eliminar_nodo(lista);
} else {
PNodo ant = lista;
PNodo ptr = lista->sig;
while ((ptr != NULL)&&(d != ptr->dato))
{ ant = ptr;
ptr = ptr->sig;
}
if (ptr != NULL) {
eliminar_nodo(ant->sig);
}
}
}
}
//------------------------
void eliminar(unsigned p)
{
if (lista != NULL) {
if (p == 0) {
eliminar_nodo(lista);
} else {
unsigned i = 1;
PNodo ptr = lista;
while ((ptr != NULL)&&(i < p))
{ ptr = ptr->sig;
++i;
}
if ((ptr != NULL)&&(ptr->sig != NULL)) {
eliminar_nodo(ptr->sig);
}
}
}
}
return in;
}
//----------------------------
private:
//----------------------------
struct Nodo; // declaraci´on adelantada
typedef Nodo* PNodo;
struct Nodo {
PNodo sig; // enlace
Tipo dato;
//--------
// Nodo(const Tipo& d, PNodo s = NULL) : sig(s), dato(d) {} // Insertar_Nodo Alternativo
//--------
};
//---------------
//-- Atributos --
//---------------
unsigned sz;
PNodo lista;
//------------------------
// M´etodos Privados
//------------------------
void insertar_nodo(PNodo& ptr, const Tipo& d)
{
//--------
// ptr = new Nodo(d, ptr); // Insertar_Nodo Alternativo
//--------
PNodo aux = new Nodo;
aux->dato = d;
aux->sig = ptr;
ptr = aux;
//--------
++sz;
//--------
}
//------------------------
void eliminar_nodo(PNodo& ptr)
{
assert(ptr != NULL);
//--------
--sz;
//--------
PNodo aux = ptr;
ptr = ptr->sig;
delete aux;
//--------
}
//------------------------
void destruir()
{
while (lista != NULL) {
eliminar_nodo(lista);
}
}
//------------------------
void duplicar(const Lista& o)
{
assert(this != &o);
#include "lista.hpp"
#include <iostream>
using namespace std;
using namespace umalcc;
int main()
{
try {
Lista<int> lista;
int dato;
copia = lista;
//-lista.hpp
namespace umalcc {
// ..........
template <typename Tipo>
class Lista {
//- lista_raii.hpp
#ifndef _lista_raii_hpp_
#define _lista_raii_hpp_
#include <iostream>
#include <cassert>
namespace umalcc {
//--------------------------------
namespace lista_raii_impl {
template <typename Tipo>
inline void intercambiar(Tipo& a, Tipo& b)
{
Tipo x = a;
a = b;
b = x;
}
}
//--------------------------------
template <typename Tipo>
class Lista {
public:
~Lista() {
RAII_Lista aux;
aux.swap(lista);
}
//-------------------------
Lista() : sz(0), lista(NULL) {}
//------------------------
Lista(const Lista& o)
: sz(0), lista(NULL)
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
15.8. GESTIO´ N DE MEMORIA DINA´ MICA EN PRESENCIA DE 3
EXCEPCIONES{
ser´ıa susceptible de perder la memoria alojada para el nodo en caso de que la asignaci´on del dato
lanzase una excepci´on. Por el contrario, la siguiente definici´on es m ´a s robusta ante excepciones:
ya que en caso de que la construcci´on del objeto fallase, se liberar´ıa autom´aticamente la memoria
previamente alojada para dicho objeto.
class X {
public:
~X() { ++cnt_dtor; }
X(int v = 0) : valor(v) {
if (cnt_ctor == 11) {
cerr << "Error" << endl;
throw runtime_error("X::X");
}
++cnt_ctor;
}
X(const X& v) : valor(v.valor) {
if (cnt_ctor == 11) {
cerr << "Error" << endl;
throw runtime_error("X::X");
}
++cnt_ctor;
}
friend bool operator == (const X& a, const X& b)
{ return a.valor == b.valor;
}
friend bool operator != (const X& a, const X& b)
{ return a.valor != b.valor;
}
friend ostream& operator << (ostream& out, const X& x) {
return out << x.valor ;
}
friend istream& operator >> (istream& in, X& x)
{ return in >> x.valor ;
}
//-----------------------------
static unsigned cnt_construidos() { return cnt_ctor; }
static unsigned cnt_destruidos() { return cnt_dtor; }
//-----------------------------
private:
static unsigned cnt_ctor;
static unsigned cnt_dtor;
int valor;
//-----------------------------
};
//------------------
unsigned X::cnt_ctor = 0; // definici´on e inicializaci´on del atributo est´atico
unsigned X::cnt_dtor = 0; // definici´on e inicializaci´on del atributo est´atico
//------------------
int main()
{
try {
Lista<X> lista;
int dato;
Donde la ejecuci´on con la implementaci´on de Lista sin considerar las excepciones (v´ease 15.7)
produce el siguiente resultado:
Introduzca lista entre llaves: {1 2 3}
{ 1 2 3 }
Insertar al inicio. 0
Ctor de Copia
Error
Error: X::X
Objetos construidos: 11
Objetos destruidos: 8
ERROR, el n´umero de NEW [9] es diferente del n´umero de DELETE [6]
Mientras que la implementaci´on de Lista utilizando la clase auxiliar RAII_Lista (v´ease 15.8)
produce el siguiente resultado:
Introduzca lista entre llaves: {1 2 3}
{ 1 2 3 }
Insertar al inicio. 0
Ctor de Copia
Error
Error: X::X
Objetos construidos: 11
Objetos destruidos: 11
OK, el n´umero de NEW [9] es igual al n´umero de DELETE [9]
Con ayuda del programa de la secci´on 15.9 se puede realizar f´acilmente la comprobaci´on del nu
´mero total de nodos alojados y desalojados.
//------------------------------------------------------------------------ -
//- newdelcnt.cpp
//------------------------------------------------------------------------ -
#include <iostream>
#include <cstdlib>
#include <new>
//------------------------------------------------------------------------ -
namespace {
static unsigned inc_new(unsigned n = 0) throw()
{
static unsigned cnt = 0;
return (cnt += n);
}
//------------------------------------
static unsigned inc_delete(unsigned n = 0) throw()
{
static unsigned cnt = 0;
return (cnt += n);
}
int main()
{
int x = 3;
int* p = &x;
Fecha f;
Fecha* pf = &f;
pf->dia = 21;
pf->mes = 02;
pf->anyo = 2011;
}
Hay que tener presente que en estos casos, la variable de tipo puntero es t´a apuntando a una zona
de memoria gestionada por el compilador, por lo que el programador no debe utilizar los
operadores new ni delete. As´ı mismo, tambi´en puede suceder que la variable de tipo puntero
apunte a una variable autom´atica que haya desaparecido debido a que su ´ambito y tiempo de
vida hayan expirado con anterioridad.
N´otese que esta caracter´ıstica es una estructura de programaci´on de muy bajo nivel que
aumenta a u´ n m ´a s la complejidad de los programas, ya que a la complejidad de la gesti´on de los
punteros y la memoria din´amica se an˜ade la complejidad de gestionar tambi´en punteros a
direcciones de memoria gestionadas por el compilador, y el programador debe discernir entre
ambos tipos, as´ı como sobre el tiempo de vida de las variables gestionadas por el compilador.
Introduccio´n a los
Contenedores de la Biblioteca
Est´andar (STL)
16.1. Vector
El contenedor de tipo vector<...> representa una secuencia de elementos homog´eneos
opti- mizada para el acceso directo a los elementos segu´ n su posici´on. 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 taman˜o 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 taman˜o fijo y predeterminado en tiempo de compilaci´on respecto al nu´mero de
elementos que pueda contener.
El nu´mero m´aximo de elementos que se pueden almacenar en una variable de tipo vector
no e st´a especificado, y se pueden almacenar elementos mientras haya capacidad suficiente en
la memoria del ordenador donde se ejecute el programa.
221
1CAP´ITULO 16. INTRODUCCIO´ N A LOS CONTENEDORES DE LA BIBLIOTECA ESTA´ NDAR (STL)
N´otese que en los siguientes ejemplos, por simplicidad, tanto el nu´mero 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.
Las siguientes definiciones declaran el tipo Matriz como un vector de dos dimensiones de nu´meros
enteros.
int main()
{
Vect_Int v1; // vector de enteros vac´ıo
std::vector<int> 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 an˜adir y eliminar elementos cuando sea necesario.
Tambi´en es posible crear un objeto vector con un nu´mero inicial de elementos con un valor
inicial por defecto, al que posteriormente se le podr´an an˜adir nuevos elementos. Este nu
´mero inicial de elementos puede ser tanto una constante, como el valor de una variable
calculado en tiempo de ejecuci´on.
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 por defecto 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:
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
// ...
}
As´ı mismo, tambi´en es posible intercambiar (swap en ingl´es) el contenido entre dos vectores uti-
lizando 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 }
}
16.2. Stack
El contenedor de tipo stack<...> representa el tipo abstracto de datos Pila, como una
colec- ci´on ordenada (segu´n 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 u
´ltimo 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 po dr´a sacar ningu´n elemento de una pila vac
´ıa. Para utilizar un 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 nu´mero 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.
El constructor por defecto del tipo stack crea un objeto stack inicialmente vac´ıo, sin elementos.
Posteriormente se podr´an an˜adir 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
// ...
}
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 }
while (! s.empty()) {
cout << s.top() << " "; // muestra: 9 7 1
s.pop();
} // s = { }
cout << endl;
}
16.3. Queue
El contenedor de tipo queue<...> representa el tipo abstracto de datos Cola, como una colec-
ci ´on ordenada (segu´n 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 or- den 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 ningu´n elemento de una cola vac
´ıa. Para utilizar un 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 nu´mero m´aximo de elementos que se pueden almacenar en una variable de tipo queue no es t
´a es- pecificado, y se pueden introducir elementos mientras haya capacidad suficiente en la
memoria del ordenador donde se ejecute el programa.
c.front() = 6; // c = { 6, 2, 3 }
c.back() = 5; // c = { 6, 2, 5 }
c.pop(); // c = { 2, 5 }
c.pop(); // c = { 5 }
c.push(7); // c = { 5, 7 }
c.push(9); // c = { 5, 7, 9 }
while (! c.empty()) {
cout << c.front() << " "; // muestra: 5 7 9
c.pop();
} // c = { }
Dpto. Lenguajes y Ciencias de la Computaci´on Universidad de M´alaga
2CAP´ITULO 16. INTRODUCCIO´ N A LOS CONTENEDORES DE LA BIBLIOTECA ESTA´ NDAR (STL)
struct Agente {
string nombre;
double ventas;
};
int main ()
{
VAgentes v;
leer(v);
eliminar(v, media(v));
imprimir(v);
}
//----------------------------------------------------------------- -
void leer(Matriz& m)
{
unsigned nf, nc;
cout << "Introduzca el numero de filas: ";
cin >> nf;
cout << "Introduzca el numero de columnas: ";
cin >> nc;
m = Matriz(nf, Fila (nc));
cout << "Introduzca los elementos: " << endl;
for (unsigned f = 0; f < m.size(); ++f){
for (unsigned c = 0; c < m[f].size(); ++c)
{ cin >> m[f][c];
}
}
}
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);
}
}
//----------------------------------------------------------------- -
Programacio´n Avanzada
233
235
#include <iostream>
using namespace std;
//------------------------------------
//------------------------------------
class Cloneable
{ public:
virtual Cloneable* clone() const =0;
};
//------------------------------------
class Volador { // : public virtual XXX
public:
virtual void despega() =0;
virtual void vuela(int ix, int iy) =0;
virtual void aterriza() =0;
};
//------------------------------------
class Insectivoro {
public:
virtual void comer() =0;
};
//------------------------------------
//------------------------------------
class Animal : public virtual Cloneable {
public:
virtual ~Animal() {
cout << "Muerto en ";
print();
}
virtual void print() const {
cout << "[ " << x << ", " << y << " ]" << endl;
}
virtual void mover(int ix, int iy)
{ x += ix; y += iy;
}
virtual void comer() =0;
protected:
Animal(int _x = 0, int _y = 0) : x(_x), y(_y)
{ cout << "Nace en ";
print();
}
Animal(const Animal& a) : x(a.x), y(a.y) { // C++0x
cout << "Nace en ";
print();
237
1 CAP´ITULO 17. PROGRAMACIO´ N ORIENTADA A
OBJETOS
}
private:
int x, y;
const Animal& operator=(const Animal& o);
};
//------------------------------------
class Mamifero : public Animal {
public:
virtual void mover(int ix, int iy) {
cout << "Andando" << endl;
Animal::mover(ix, iy);
}
virtual void print() const
{ cout << "Estoy en ";
Animal::print();
}
};
//------------------------------------
class Murcielago : public Mamifero,
public virtual Volador, public virtual Insectivoro {
public:
Murcielago() : Mamifero() {}
virtual Murcielago* clone() const {
return new Murcielago(*this);
}
virtual void mover(int ix, int iy)
{ cout << "Volando" << endl;
Animal::mover(ix, iy);
}
virtual void comer() {
cout << "Comiendo mosquitos ";
Animal::print();
}
virtual void despega() {
cout << "Despega ";
Animal::print();
}
virtual void vuela(int ix, int iy)
{ mover(ix, iy);
}
virtual void aterriza() {
cout << "Aterriza ";
Animal::print();
}
protected:
Murcielago(const Murcielago& o) : Mamifero(o) {}
};
//------------------------------------
//------------------------------------
int main()
{
Murcielago* m = new Murcielago();
m->despega();
m->vuela(5, 7);
m->comer();
m->aterriza();
m->print();
Animal* a = m->clone();
a->mover(11, 13);
a->comer();
a->print();
Murcielago* m2 = dynamic_cast<Murcielago*>(a);
if (m2 != NULL) {
m2->vuela(5, 7);
}
delete m;
delete a;
}
//------------------------------------
Nota: En el caso de punteros, no hay diferencia en el tipo entre un puntero a un elemento simple,
y un puntero a un agregado de elementos, por lo que ser´a el propio programador el responsable
de diferenciarlos, y de hecho liberarlos de formas diferentes (con los corchetes en caso de
agregados). Una posibilidad para facilitar dicha tarea al programador consiste en definir el tipo
de tal forma que indique que es un agregado din´amico, y no un puntero a un elemento.
Ejemplo:
#include <iostream>
typedef int* AD_Enteros;
241
1 CAP´ITULO 18. MEMORIA DINA´ MICA
AVANZADA
cout << "Introduce " << n << " elementos " << endl;
for (int i = 0; i < n; ++i) {
cin >> numeros[i];
}
}
double media(int n, const int numeros[])
{
double suma = 0.0;
for (int i = 0; i < n; ++i) {
suma += numeros[i];
}
return suma / double(n);
}
int main()
{
int nelm;
AD_Enteros numeros; // agregado din´amico
int main()
{
PtrFun salida; // vble puntero a funci´on (devuelve void y recibe int)
shared_ptr<...> weak_ptr<......>
otros definidos por los usuarios
#include <iostream>
using namespace std;
struct Clase_X {
int v;
void miembro() { cout << v << " " << this << endl; }
};
int main()
{
PointerToClaseXMemberFunction pmf = &Clase_X::miembro;
PointerToClaseXMemberAttr pma = &Clase_X::v;
Clase_X x;
Clase_X* px = &x;
245
19.2. OCULTAR LA IMPLEMENTACIO´ N 1
Puntero a la Implementaci´on
// elem.hpp
#ifndef _elem_hpp_
#define _elem_hpp_
namespace elem {
class Elem {
public:
~Elem() throw();
Elem();
Elem(const Elem& o);
Elem& operator=(const Elem& o);
int get_val() const;
void set_val(int);
void swap(Elem& o);
private:
class ElemImpl;
ElemImpl* pimpl;
};// class Elem
} //namespace elem
#endif
// main.cpp
#include <iostream>
#include "elem.hpp"
using namespace std;
using namespace elem;
int main()
{
Elem e;
e.set_val(7);
cout << e.get_val() << endl;
Elem e2(e);
e2.set_val(e2.get_val()+2);
cout << e2.get_val() << endl;
e2 = e;
cout << e2.get_val() << endl;
}
// elem.cpp
#include "elem.hpp"
#include <iostream>
using namespace std;
namespace elem {
class Elem::ElemImpl {
public:
~ElemImpl() throw() { cout << "destrucci´on de: " << val << endl; }
ElemImpl() : val(0) {}
//ElemImpl(const Elem& o) : val(o.val) {}
//ElemImpl& operator=(const ElemImpl& o) { val = o.val; return *this; }
int get_val() const { return val; }
void set_val(int v) { val = v; }
private:
int val;
};// class ElemImpl
Elem::~Elem() throw()
{
delete pimpl;
}
Elem::Elem()
: pimpl(new ElemImpl)
{}
Elem::Elem(const Elem& o)
: pimpl(new ElemImpl(*o.pimpl))
{}
Elem& Elem::operator=(const Elem& o)
{
if (this != &o) {
Elem(o).swap(*this);
}
return *this;
}
int Elem::get_val() const
{
return pimpl->get_val();
}
void Elem::set_val(int v)
{
pimpl->set_val(v);
}
void Elem::swap(Elem& o)
{
ElemImpl* aux = o.pimpl;
o.pimpl = pimpl;
pimpl = aux;
}
}// namespace elem
// fin
Puntero a la Implementaci´on
//- complejo.hpp
#ifndef _complejo_hpp_
#define _complejo_hpp_
#include <iostream>
namespace umalcc {
class Complejo
{ public:
~Complejo();
Complejo();
Complejo(double r, double i);
Complejo(const Complejo& c);
Complejo& operator=(const Complejo& c);
double get_real() const;
double get_imag() const;
Complejo& operator+=(const Complejo& c);
Complejo& swap(Complejo& c);
private:
struct Hidden* impl;
};
inline Complejo operator+(const Complejo& c1, const Complejo& c2) {
Complejo res = c1;
res += c2;
return res;
}
inline std::ostream& operator<<(std::ostream& out, const Complejo& c)
{ out << "[ " << c.get_real() << "," << c.get_imag() << " ]";
return out;
}
}// namespace umalcc
#endif
//- main.cpp
#include <iostream>
#include <string>
#include "complejo.hpp"
using namespace std;
using namespace umalcc;
int main()
{
Complejo c1;
Complejo c2;
Complejo c3 = c1 + c2;
cout << c1 << " " << c2 << " " << c3 << endl;
c3 += Complejo(4, 5);
cout << c3 << endl;
c3 = Complejo(411, 535);
cout << c3 << endl;
}
//- complejo.cpp
#include "complejo.hpp"
namespace umalcc {
struct Hidden {
double real, imag;
};
Complejo::~Complejo() {
delete impl;
}
Complejo::Complejo() : impl() {}
Complejo::Complejo(double r, double i) : impl() {
impl = new Hidden;
impl->real = r;
impl->imag = i;
}
Complejo::Complejo(const Complejo& c) : impl() {
if (c.impl != NULL) {
impl = new Hidden;
impl->real = c.impl->real;
impl->imag = c.impl->imag;
}
}
Complejo& Complejo::operator=(const Complejo& c) {
if (this != &c) {
Complejo(c).swap(*this);
}
return *this;
}
Complejo& Complejo::swap(Complejo& c) {
swap(impl, c.impl);
}
double Complejo::get_real() const {
return (impl == NULL) ? 0.0 : impl->real;
}
double Complejo::get_imag() const {
return (impl == NULL) ? 0.0 : impl->imag;
}
Complejo& Complejo::operator+=(const Complejo& c) {
if (impl == NULL) {
*this = c;
} else {
impl->real += c.get_real();
impl->imag += c.get_imag();
}
return *this;
}
}// namespace umalcc
//------------------------------------------------------------------- -
Base Abstracta
// elem.hpp
#ifndef _elem_hpp_
#define _elem_hpp_
namespace elem {
class Elem {
public:
static Elem* create();
virtual Elem* clone() const = 0;
virtual ~Elem() throw();
virtual int get_val() const = 0;
virtual void set_val(int) = 0;
};// class Elem
} //namespace elem
#endif
// main.cpp
#include <iostream>
#include <memory>
#include "elem.hpp"
using namespace std;
using namespace elem;
int main()
{
auto_ptr<Elem> e(Elem::create());
e->set_val(7);
cout << e->get_val() << endl;
auto_ptr<Elem> e2(e->clone());
e2->set_val(e2->get_val()+2);
cout << e2->get_val() << endl;
e2 = auto_ptr<Elem>(e->clone());
cout << e2->get_val() << endl;
}
// elem.cpp
//#include "elem.hpp"
#include <iostream>
using namespace std;
namespace {
class ElemImpl : public elem::Elem
{ public:
virtual ElemImpl* clone() const { return new ElemImpl(*this); }
virtual ~ElemImpl() throw()
{ cout << "destrucci´on de: " << val << endl; }
ElemImpl() : _ClaseBase(), val(0) {}
//ElemImpl(const Elem& o) : _ClaseBase(o), val(o.val) {}
//ElemImpl& operator=(const ElemImpl& o) { val = o.val; return *this; }
virtual int get_val() const { return val; }
virtual void set_val(int v) { val = v; }
private:
typedef elem::Elem _ClaseBase;
int val;
};// class ElemImpl
}
namespace elem {
Elem* Elem::create() { return new ElemImpl(); }
Elem::~Elem() throw() {}
}// namespace elem
// fin
// matriz.hpp
#ifndef _matriz_hpp_
#define _matriz_hpp_
/*
* Matriz Dispersa
*
* Definici´on de tipos
*
* typedef Matriz<int> Mat_Int;
*
* Definici´on de Variables
*
* Mat_Int mat(NFIL, NCOL);
*
* Definici´on de Constantes
*
* const Mat_Int aux2(mat);
*
* Operaciones
*
* aux1 = mat; // asignaci´on
* print(mat); // paso de par´ametros
* unsigned nfil = mat.getnfil(); // n´umero de filas
* unsigned ncol = mat.getncol(); // n´umero de columnas
* unsigned nelm = mat.getnelm(); // n´umero de elementos almacenados
* mat(1U, 3U) = 4; // asignaci´on a elemento
* x = mat(1U, 3U); // valor de elemento
*
* Excepciones
*
* OutOfRange
*/
#include <map>
#include <iterator>
namespace matriz {
class RefC_Elm;
// Matriz
template <typename Tipo>
class Matriz {
private:
friend class Ref_Elm<Tipo>;
friend class RefC_Elm<Tipo>;
//
typedef Tipo Tipo_Elm;
typedef Ref_Elm<Tipo_Elm> Tipo_Ref_Elm;
typedef RefC_Elm<Tipo_Elm> Tipo_RefC_Elm;
}
}
}
// Ref_Elm
template <typename Tipo>
Ref_Elm<Tipo>::operator RefC_Elm<Tipo> () const
{
return RefC_Elm<Tipo>(mat, fil, col);
}
template <typename Tipo>
Ref_Elm<Tipo>& Ref_Elm<Tipo>::operator=(const Tipo_Elm& e)
{
if (e == Tipo_Elm()) {
eliminar_elemento_si_existe();
} else {
typename Matriz_Elm::Lista_Elm& fila = mat->elm[fil];
fila[col] = e;
}
return *this;
}
template <typename Tipo>
void Ref_Elm<Tipo>::eliminar_elemento_si_existe()
{
typedef typename Matriz_Elm::ItElm ItElm;
typedef typename Matriz_Elm::ItFil ItFil;
#include "matriz.hpp"
void
print(const Mat_Int& mat)
{
cout << "N´umero de filas reales: " << mat.getnfilalm() << endl;
cout << "N´umero de elementos almacenados: " << mat.size() << endl;
mat(1U, 3U) = 4;
mat(2U, 2U) = 5;
mat(1U, 1U) = 3;
print(mat);
Mat_Int aux1(NFIL,NCOL);
aux1 = mat;
aux1(2U, 2U) = 0;// elimina un elemento y una fila
print(aux1);
// Fuera de Rango
mat(NFIL, NCOL) = 5;
} catch ( OutOfRange ) {
cerr << "excepci´on fuera de rango" << endl;
} catch ( ... ) {
cerr << "excepci´on inesperada" << endl;
}
}
// fin
Las plantillas (“templates” en ingl´es) son u´tiles a la hora de realizar programaci´on gen
´erica. Esto es, definir clases y funciones de forma gen´erica que se instancien a tipos particulares en
funci´on de la utilizaci´on de ´estas.
Los par´ametros de las plantillas podr´an ser tanto tipos como valores constantes. Veamos
un ejemplo de definiciones de funciones gen´ericas:
255
1 CAP´ITULO 20. PROGRAMACIO´ N GENE´ RICA
AVANZADA
return T(x);
}
// ...
};
int main()
{
Dato<int> d;
double z = d.template metodo<double>(3);
}
in >> val;
i = val;
return in;
}
// constraint.hpp
// Tomado de: B. Stroustrup Technical FAQ
#ifndef _constraint_hpp_
#define _constraint_hpp_
#ifndef _UNUSED_
#ifdef GNUC
#define _UNUSED_ attribute ((unused))
#else
#define _UNUSED_
#endif
#endif
namespace constraint {
template<typename T, typename B> struct Derived_From {
static void constraints(T* p) { B* pb = p; }
Derived_From() { void(*p)(T*) _UNUSED_ = constraints; }
};
template<typename T1, typename T2> struct Can_Copy {
static void constraints(T1 a, T2 b) { T2 c = a; b = a; }
Can_Copy() { void(*p)(T1,T2) _UNUSED_ = constraints; }
};
template<typename T1, typename T2 = T1> struct Can_Compare
{ static void constraints(T1 a, T2 b) { a==b; a!=b; a<b; }
Can_Compare() { void(*p)(T1,T2) _UNUSED_ = constraints; }
};
template<typename T1, typename T2, typename T3 = T1> struct Can_Multiply {
static void constraints(T1 a, T2 b, T3 c) { c = a*b; }
Un ejemplo de su utilizaci´on:
#include <iostream>
using namespace std;
struct B { };
struct D : B { };
struct DD : D { };
struct X { };
template<typename T>
class Nuevo : Is_Vector<T> {
// ...
};
int main()
{
Derived_from<D,B>();
Derived_from<DD,B>();
Derived_from<X,B>();
Derived_from<int,B>();
Derived_from<X,int>();
Can_compare<int,double>();
Can_compare<X,B>();
Can_multiply<int,double>();
Can_multiply<int,double,double>();
Can_multiply<B,X>();
Can_copy<D*,B*>();
Can_copy<D,B*>();
Can_copy<int,B*>();
}
20.6. Especializaciones
Veamos otro ejemplo de una definici´on gen´erica (tomado de GCC 3.3) con especializaci´on:
//-fichero: stl_hash_fun.hpp
namespace gnu_cxx {
using std::size_t;
} // namespace gnu_cxx
//-fin: stl_hash_fun.hpp
Una posible ventaja de hacer esta especializaci´on sobre un tipo que sobre una funci´on, es que
la funci´on estar´a definida para cualquier tipo, y ser ´a en tiempo de ejecuci´on donde rompa si
se utiliza sobre un tipo no v´alido. De esta forma, es en tiempo de compilaci´on cuando falla.
Otra ventaja es que puede ser pasada como par´ametro a un “template”.
20.7. Meta-programacio´n
template <unsigned X, unsigned Y>
struct GcdX {
static const unsigned val = (Y==0)?X:GcdX<Y, X%Y>::val ;
};
template <unsigned X>
struct GcdX<X,0> {
static const unsigned val = X;
};
template <unsigned X, unsigned Y>
struct Gcd {
static const unsigned val = (X>=Y)?GcdX<X,Y>::val:GcdX<Y,X>::val ;
};
//-------------------------------------------------------------------------
#ifndef _sfinae_hpp_
#define _sfinae_hpp_
namespace umalcc {
//---------------------------------------------------------------- -
// SFINAE ("Substitution Failure Is Not An Error" principle)
//------------------------------------
// has member type: type
template<typename Tipo>
class has_type_type {
public:
enum { value = (sizeof(test<Tipo>(0)) == sizeof(Yes)) };
private:
struct Yes { char a[1]; };
struct No { char a[2]; };
template <typename TT> class Helper{};
template<typename TT> static Yes test(Helper<typename TT::type> const*);
template<typename TT> static No test(...);
};
//---------------------------------------------------------------- -
// has member data: int value
template <typename Tipo>
class has_data_value {
public:
enum { value = (sizeof(test<Tipo>(0)) == sizeof(Yes)) };
private:
struct Yes { char a[1]; };
struct No { char a[2]; };
template <typename TT, TT t> class Helper{};
template<typename TT> static Yes test(Helper<int TT::*, &TT::value> const*);
template<typename TT> static No test(...);
};
//---------------------------------------------------------------- -
// has static member data: static int value
template <typename Tipo>
class has_static_value {
public:
enum { value = (sizeof(test<Tipo>(0)) == sizeof(Yes)) };
private:
struct Yes { char a[1]; };
struct No { char a[2]; };
template <typename TT, TT t> class Helper{};
template<typename TT> static Yes test(Helper<int *, &TT::value> const*);
template<typename TT> static No test(...);
};
//---------------------------------------------------------------- -
// has member function: RT foo()
template <typename Tipo>
class has_function_foo
{ public:
enum { value = (sizeof(test<Tipo>(0)) == sizeof(Yes)) };
private:
struct Yes { char a[1]; };
struct No { char a[2]; };
template <typename TT, TT t> class Helper{};
template<typename TT> static Yes test(Helper<void (TT::*)(), &TT::foo> const*);
template<typename TT> static No test(...);
};
//---------------------------------------------------------------- -
} //namespace umalcc
#endif
Como se vio anteriormente, para realizar operaciones de entrada y salida es necesario incluir
las definiciones correspondientes en nuestro programa. para ello realizaremos la siguiente acci´on al
comienzo de nuestro programa:
#include <iostream>
using namespace std;
{
ostream::sentry centinela(cout); // creaci´on del centinela en <<
if (!centinela) {
setstate(failbit);
return(cout);
}
...
}
263
1 CAP´ITULO 21. BUFFER Y FLUJOS DE ENTRADA Y
SALIDA
int n = cin.get(); // lee un car´acter de entrada o EOF
cin.get(char& c); // lee un car´acter de entrada
int n = cin.peek(); // devuelve el prox car´acter (sin leerlo)
N´otese que realizar una operaci´on getline despu´es de una operaci´on con >> puede tener
com- plicaciones, ya que >> dejara los separadores en el buffer, que ser´an le´ıdos por getline.
Para evitar este problema (leer´a una cadena que sea distinta de la vac´ıa):
Tambi´en es posible leer un dato y eliminar el resto (separadores) del buffer de entrada.
{
istream::sentry centinela(cin); // creaci´on del centinela en >>
if (!centinela) {
setstate(failbit);
return(cin);
}
...
}
std::ios::sync_with_stdio(false);
Para hacer que lance una excepci´on cuando ocurra algu´n error:
21.3. Buffer
Se puede acceder al buffer de un stream, tanto para obtener su valor como para modificarlo.
El buffer es el encargado de almacenar los caracteres (antes de leerlos o escribirlos) y definen
adem´as su comportamiento. Es donde reside la inteligencia de los streams. Es posible la
redirecci´on de los streams gracias a los buffers (v´ease 21.4).
//------------------------------------------------------------------------ -
//#include <iostream>
#include <iosfwd>
using namespace std;
//------------------------------------------------------------------------ -
namespace umalcc {
//-------------------------------------------------------------------- -
inline unsigned verbose_level(unsigned l = -1U)
{ static unsigned verb_level = 1;
unsigned res = verb_level;
if (l != -1U) {
verb_level = l;
}
return res;
}
//-------------------------------------------------------------------- -
namespace _verbose_ {
//---------------------------------------------------------------- -
// ostrcap.hpp
#ifndef _ostrcap_hpp_
#define _ostrcap_hpp_
#include <iostream>
#include <string>
#include <sstream>
#include <stdexcept>
namespace ostrcap {
class OStreamCapture {
public:
OStreamCapture()
: str_salida(), output_stream(), output_streambuf_ptr() {}
void capture(std::ostream& os) {
if (output_streambuf_ptr != NULL)
{ throw Capture_Error();
}
str_salida.str("");
output_stream = &os;
output_streambuf_ptr = output_stream->rdbuf(str_salida.rdbuf());
}
std::string release() {
if (output_streambuf_ptr == NULL) {
throw Release_Error();
#include "ostrcap.hpp"
void salida(int x)
{
cout << "Informaci´on " << x << endl;
}
int main()
{
OStreamCapture ostrcap;
ostrcap.capture(cout);
salida(34);
cout << "La salida es: " << ostrcap.release();
ostrcap.capture(cout);
salida(36);
cout << "La salida es: " << ostrcap.release();
}
// fin
21.5. Ficheros
Las operaciones vistas anteriormente sobre los flujos de entrada y salida est´andares se
pueden aplicar tambi´en sobre ficheros de entrada y de salida, simplemente cambiando las
variables cin y cout por las correspondientes variables que especifican cada fichero. Para ello es
necesario incluir la siguiente definici´on:
#include <fstream>
ios::in // entrada
ios::out // salida
ios::app // posicionarse al final antes de cada escritura
ios::binary // fichero binario
fichero_entrada.close();
Nota: si se va a abrir un fichero utilizando la misma variable que ya ha sido utilizada (tras el
close()), se deber´a utilizar el m´etodo clear() para limpiar los flags de estado.
Para posicionar el puntero de lectura del fichero (puede coincidir o no con el puntero de escrit-
ura):
donde los modos posibles, combinados con OR de bits (|) son los anteriormente especificados.
Otra posibilidad es declarar la variable y abrir el fichero simult´aneamente:
fichero_salida.close();
Nota: si se va a abrir un fichero utilizando la misma variable que ya ha sido utilizada (tras el
close()), se deber´a utilizar el m´etodo clear() para limpiar los flags de estado.
Para posicionar el puntero de escritura del fichero (puede coincidir o no con el puntero de
lectura):
void
copiar_fichero_2(const string& salida, const string& entrada, bool& ok)
{
ok = false;
ifstream f_ent(entrada.c_str());
if (f_ent) {
ofstream f_sal(salida.c_str());
if (f_sal) {
f_sal << f_ent.rdbuf();
ok = f_ent.eof() && f_sal.good();
f_sal.close(); // no es necesario
}
f_ent.close(); // no es necesario
}
}
#include <sstream>
istringstream str_entrada(entrada);
str_entrada.str("nueva entrada")
#include <sstream>
ostringstream str_salida;
T´ecnicas de Programaci´on
Usuales en C++
#include <iostream>
#include <memory>
using namespace std;
class Dato {
public:
273
22.1. ADQUISICIO´ N DE RECURSOS ES INICIALIZACIO´ N 1
(RAII)
~Dato() throw() { cout << "Destruye dato " << val << endl; }
Dato(unsigned v = 0): val(v) { cout << "Construye dato " << val << endl; }
unsigned get() const { return val; }
private:
unsigned val;
};
int main()
{
auto_ptr<Dato> ptr1(new Dato(5));
auto_ptr<Dato> ptr2;
cout << "Valor1: " << ptr1->get() << endl;
ptr2 = ptr1;
cout << "Valor2: " << (*ptr2).get() << endl;
}
#include <memory>
class X {
public:
~X();
X();
...
private:
int* ptr;
};
X::~X() throw{} { delete ptr; }
X::X()
: ptr(0)
{
auto_ptr<int> paux(new int);
// utilizaci´on de paux. zona posible de excepciones
// *paux paux-> paux.get() paux.reset(p)
ptr = paux.release();
}
#include <iostream>
using namespace std;
class Dato {
public:
~Dato() throw() { cout << "Destruye dato " << val << endl; }
Dato(unsigned v = 0): val(v) { cout << "Construye dato " << val << endl; }
unsigned get() const { return val; }
private:
unsigned val;
};
int main()
{
RaiiPtr<Dato> ptr1(new Dato(5));
RaiiPtr<Dato> ptr2(new Dato(7));
cout << "Valor1: " << ptr1->get() << endl;
cout << "Valor2: " << ptr2->get() << endl;
ptr1.swap(ptr2);
cout << "Valor1: " << (*ptr1).get() << endl;
cout << "Valor2: " << (*ptr2).get() << endl;
}
int main()
{
// adquisici´on de recurso
Recurso rec_1 = acquire_resource();
// liberaci´on automatica de recurso si surge una excepci´on
SGuard guarda_rec_1 = mk_guard(funcion, arg);
// ...
// Acciones que pueden elevar una excepci´on
// ...
guarda_rec_1.release();
// Anulaci´on de liberaci´on automatica del recurso
}
y crea un objeto (constructor por defecto) de dicho tipo. Devuelve un puntero al objeto creado.
Tambi´en existe la posibilidad de crear un objeto llamando a otro constructor: new Tipo(args).
El operador delete ptr se encarga de destruir el objeto apuntado (llamando al destructor) y
de liberar la memoria ocupada llamando a
void operator delete(void *) throw();
El operador new Tipo[nelms] reserva espacio en memoria para contener nelms elementos
del tipo especificado llamando a
void *operator new[](std::size_t) throw (std::bad_alloc);
y crea dichos elementos llamando al constructor por defecto. Devuelve un puntero al primer objeto
especificado.
El operador delete [] ptr se encarga de destruir los objetos apuntados (llamando al
destruc- tor) en el orden inverso en el que fueron creados y de liberar la memoria ocupada por ellos
llamando a
void operator delete[](void *) throw();
279
1 CAP´ITULO 23. GESTIO´ N DINA´ MICA DE
MEMORIA
El operador new (ptr) Tipo (new con emplazamiento) no reserva memoria, simplemente crea
el objeto del tipo especificado (utilizando el constructor por defecto u otra clase de constructor
con la sintaxis new (ptr) Tipo(args) en la zona de memoria especificada por la llamada con
argumento ptr a
inline void *operator new(std::size_t, void *place) throw() { return place; }
#include <new>
y los comportamientos por defecto podr´ıan ser los siguientes para los operadores new y delete:
Los operadores new y delete encargados de alojar y desalojar zonas de memoria pueden
ser redefinidos por el programador, tanto en el ´ambito global como para clases espec´ıficas.
Una
definici´on est´andar puede ser como se indica a continuaci´on: (fuente: M. Cline, C++ FAQ-Lite,
http://www.parashift.com/c++-faq-lite/)
#include <new>
/*
* ********************************
* Tipo* ptr = new Tipo(lista_val);
* ********************************
*
* ptr = static_cast<Tipo*>(::operator new(sizeof(Tipo))); // alojar mem
* try {
* new (ptr) Tipo(lista_val); // llamada al constructor
* } catch (...) {
* ::operator delete(ptr);
* throw;
* }
*/
void*
operator new (std::size_t sz) throw(std::bad_alloc)
{
void* p;
}
p = static_cast<void*>(malloc(sz));
while (p == 0) {
std::new_handler handler = new_handler;
if (handler == 0) {
#ifdef EXCEPTIONS
throw std::bad_alloc();
#else
std::abort();
#endif
}
handler();
p = static_cast<void*>(malloc(sz));
}
return p;
}
/*
* ********************************
* delete ptr;
* ********************************
*
* if (ptr != 0) {
* ptr->~Tipo(); // llamada al destructor
* ::operator delete(ptr); // liberar zona de memoria
* }
*
* ********************************
*/
void
operator delete (void* ptr) throw()
{
if (ptr) {
free(ptr);
}
}
/*
* *****************************
* Tipo* ptr = new Tipo [NELMS];
* *****************************
*
* // alojar zona de memoria
* char* tmp = (char*)::operator new[](WORDSIZE+NELMS*sizeof(Tipo));
* ptr = (Tipo*)(tmp+WORDSIZE);
* *(size_t*)tmp = NELMS;
* size_t i;
* try {
* for (i = 0; i < NELMS; ++i) {
* new (ptr+i) Tipo(); // llamada al constructor
* }
* } catch (...) {
* while (i-- != 0) {
* (p+i)->~Tipo();
* }
* ::operator delete((char*)ptr - WORDSIZE);
* throw;
* }
*
* ********************************
*/
de esta forma para que sean liberadas autom´aticamente en caso de que se eleve una excepci
´on. En caso de que haya pasado la zona donde se pueden elevar excepciones, entonces se puede
revocar dicha caracter´ıstica. Ejemplo:
#include <memory>
class X {
public:
~X();
X();
...
private:
int* ptr;
};
X::~X() throw{} { delete ptr; }
X::X()
: ptr(0)
{
auto_ptr<int> paux(new int);
// utilizaci´on de paux. zona posible de excepciones
// *paux paux-> paux.get() paux.reset(p)
ptr = paux.release();
}
//------------------------------------------------------------------------ -
//- newdelcnt.cpp
//------------------------------------------------------------------------ -
#include <iostream>
#include <cstdlib>
#include <new>
//------------------------------------------------------------------------ -
namespace {
static unsigned inc_new(unsigned n = 0) throw()
{
static unsigned cnt = 0;
return (cnt += n);
}
//------------------------------------
static unsigned inc_delete(unsigned n = 0) throw()
{
static unsigned cnt = 0;
return (cnt += n);
}
//------------------------------------
static void check_new_delete() throw()
{
if (inc_new() != inc_delete()) {
La biblioteca est´andar de C++ se define dentro del espacio de nombres std y proporciona:
Soporte para las caracter´ısticas del lenguaje, tales como manejo de memoria y la informaci´on
de tipo en tiempo de ejecuci´on.
Informaci´on sobre aspectos del lenguaje definidos por la implementaci´on, tal como el
valor del mayor double.
Funciones que no se pueden implementar de forma ´optima en el propio lenguaje para cada
sistema, tal como sqrt y memmove.
Facilidades no primitivas sobre las cuales un programador se puede basar para portabilidad,
tal como vector, list, map, ordenaciones, entrada/salida, etc.
La base comu´n para otras bibliotecas.
285
1 CAP´ITULO 24. BIBLIOTECA ESTA´ NDAR DE C++.
STL
#include <map>
#include <iterator>
#include <utility>
#include <functional>
#include <algorithm>
24.1.2. Contenedores
vector<tipo_base> vec; // vector de tama~no variable
list<tipo_base> ls; // lista doblemente enlazada
deque<tipo_base> dqu; // cola doblemente terminada
24.1.4. Iteradores
c.begin() // apunta al primer elemento
c.end() // apunta al (´ultimo+1) elemento
c.rbegin() // apunta al primer elemento (orden inverso)
c.rend() // apunta al (´ultimo+1) elemento (orden inverso)
contenedor<tipo_base>::iterator i = ri.base(); // ri + 1
24.1.5. Acceso
c.front() // primer elemento
c.back() // ´ultimo elemento
v[i] // elemento de la posici´on ’i’ (vector, deque y map)
v.at[i] // elem de la posici´on ’i’ => out_of_range (vector y deque)
24.1.8. Operaciones
c.size() // n´umero de elementos
c.empty() // (c.size() == 0)
c.max_size() // tama~no del mayor posible contenedor
c.capacity() // espacio alojado para el vector (s´olo vector)
c.reserve(n) // reservar espacio para expansi´on (s´olo vector)
c.resize(n) // cambiar el tam del cont (s´olo vector, list y deque)
c.swap(y) // intercambiar
== // igualdad
!= // desigualdad
< // menor
24.1.9. Constructores
cont() // contenedor vac´ıo
cont(n) // n elementos con valor por defecto (no cont asoc)
cont(n, x) // n copias de x (no cont asoc)
24.1.10. Asignaci´on
x = y; // copia los elementos de y a x
c.assign(n, x) // asigna n copias de x (no cont asoc)
c.assign(f, l) // asigna de [f:l)
24.1.12. Resumen
[] op.list op.front op.back iter
stack const
queue const const
prque O(log(n)) O(log(n))
24.2. Contenedores
Los contenedores proporcionan un m´etodo est´andar para almacenar y acceder a elementos,
proporcionando diferentes caracter´ısticas.
24.3. Vector
Es una secuencia optimizada para el acceso aleatorio a los elementos. Proporciona, as´ı mismo,
iteradores aleatorios.
#include <vector>
Construcci´on:
Acceso a elementos:
Operaciones de Pila
Operaciones de Lista
// s´olo vector
v1.reserve(n); // reservar n elementos (prealojados) sin inicializar valores
n = v1.capacity(); // n´umero de elementos reservados
assert(&v[0] + n == &v[n]); // Se garantiza que la memoria es contigua
// aunque puede ser realojada por necesidades de nuevo espacio
Otras operaciones:
v1 == v2 , v1 != v2 , v1 < v2 , v1 <= v2 , v1 > v2 , v1 >=
v2 v1.swap(v2);
swap(v1 , v2);
24.4. List
Es una secuencia optimizada para la inserci´on y eliminaci´on de elementos. Proporciona, as´ı
mis- mo, iteradores bidireccionales.
#include <list>
Construcci´on:
typedef std::list<int> list_int;
Acceso a elementos:
Operaciones de Pila
Operaciones de Lista
Nu´mero de elementos:
Otras operaciones:
l1.sort(); // ordena l1
l1.sort(cmp2); // ordena l1 seg´un bool cmp2(e1, e2)
Tanto los predicados como las funciones de comparaci´on pueden ser funciones (unarias
o binarias) que reciben los par´ametros (1 o 2) del tipo del elemento del contenedor y
devuelven un bool resultado de la funci´on, o un objeto de una clase que tenga el operador
() definido. Ejemplo:
int main()
{
. . .
l1.remove_if(mayor_que_5);
}
bool operator()(const Tipo& arg1, const Tipo& arg2) {}
int main()
{
std::list<int> l1;
. . .
l1.remove_if(mayor_que(5));
l1.remove_if(mayor_que(3));
}
int main()
{
std::list<int> l1;
. . .
l1.remove_if(bind2nd(greater<int>(), 5));
l1.remove_if(bind2nd(greater<int>(), 3));
}
bool
mayor1(const string& s1, const string& s2)
{
return s1 > s2;
}
int
main()
{
std::list<string> l1;
. . .
l1.sort(); // ordena de menor a mayor
l1.sort(mayor1); // ordena de mayor a menor (utiliza mayor1)
l1.sort(mayor2()); // ordena de mayor a menor (utiliza mayor2())
l1.sort(greater<string>()); // ordena de mayor a menor (utiliza greater<>())
}
24.5. Deque
Secuencia optimizada para que las operaciones de inserci´on y borrado en ambos extremos
sean tan eficientes como en una lista, y el acceso aleatorio tan eficiente como un vector.
Proporciona iteradores aleatorios.
#include <deque>
Construcci´on:
Acceso a elementos:
Operaciones de Pila
Operaciones de Lista
Nu´mero de elementos:
Otras operaciones:
24.6. Stack
Proporciona el “Tipo Abstracto de Datos Pila”, y se implementa sobre un deque. No propor-
ciona iteradores.
#include <stack>
int
main()
{
pila_c p1; // []
p1.push(’a’); // [a]
p1.push(’b’); // [a b]
p1.push(’c’); // [a b c]
if (p1.size() != 3) {
// imposible
}
if (p1.top() != ’c’) {
// imposible
}
p1.top() = ’C’; // [a b C]
if (p1.size() != 3) {
// imposible
}
p1.pop(); // [a b]
p1.pop(); // [a]
p1.pop(); // []
if (! p1.empty()) {
// imposible
}
}
24.7. Queue
Proporciona el “Tipo Abstracto de Datos Cola”, y se implementa sobre un deque. No propor-
ciona iteradores.
#include <queue>
int
main()
{
cola_c c1; // []
c1.push(’a’); // [a]
c1.push(’b’); // [a b]
c1.push(’c’); // [a b c]
if (c1.size() != 3) {
// imposible
}
if (c1.front() != ’a’) {
// imposible
}
c1.front() = ’A’; // [A b c]
if (c1.back() != ’c’) {
// imposible
}
c1.pop(); // [b c]
c1.pop(); // [c]
c1.pop(); // []
if (! c1.empty()) {
// imposible
}
}
24.8. Priority-Queue
Proporciona el “Tipo Abstracto de Datos Cola con Prioridad”, y se implementa sobre un
vector. No proporciona iteradores.
#include <queue>
//#include <functional>
int
main()
{
pcola_i c1; // []
c1.push(5); // [5]
c1.push(3); // [5 3]
c1.push(7); // [7 5 3]
c1.push(4); // [7 5 4 3]
if (c1.size() != 4) {
// imposible
}
if (c1.top() != 7) {
// imposible
}
c1.pop(); // [5 4 3]
c1.pop(); // [4 3]
c1.pop(); // [3]
c1.pop(); // []
if (! c1.empty()) {
// imposible
}
}
24.9. Map
Secuencia de pares <clave, valor> optimizada para el acceso r´apido basado en clave. Clave
u´ nica. Proporciona iteradores bidireccionales.
#include <map>
//#include <functional>
map_str_int m1;
n = m1.size();
n = m1.max_size();
bool b = m1.empty();
m1.swap(m2);
clave = it->first;
valor = it->second;
#include <utility>
pair<clave, valor> p = make_pair(f, s);
p.first // clave
p.second // valor
24.10. Multimap
Secuencia de pares <clave, valor> optimizada para el acceso r´apido basado en clave. Permite
claves duplicadas. Proporciona iteradores bidireccionales.
#include <map>
//#include <functional>
mmap_str_int m1;
n = m1.size();
n = m1.max_size();
bool b = m1.empty();
m1.swap(m2);
it = m1.insert(make_pair(clave, val));
it = m1.insert(it_pos, make_pair(clave, val));
m1.insert(it_ini, it_fin);
m1.erase(it_pos);
int n = m1.erase(clave);
m1.erase(it_ini, it_fin);
m1.clear();
#include <utility>
std::pair<clave, valor> p = std::make_pair(f, s);
p.first // clave
p.second // valor
24.11. Set
Como un map donde los valores no importan, s´ol o aparecen las claves. Proporciona iteradores
bidireccionales.
#include <set>
//#include <functional>
set_str s1;
n = s1.size();
n = s1.max_size();
bool b = s1.empty();
s1.swap(s2);
#include <utility>
std::pair<tipo1, tipo2> p = std::make_pair(f, s);
p.first
p.second
24.12. Multiset
Como set, pero permite elementos duplicados. Proporciona iteradores bidireccionales.
#include <set>
//#include <functional>
mset_str s1;
n = s1.size();
n = s1.max_size();
bool b = s1.empty();
s1.swap(s2);
it = s1.insert(elem);
it = s1.insert(it_pos, elem);
s1.insert(it_ini, it_fin);
s1.erase(it_pos);
int n = s1.erase(clave);
s1.erase(it_ini, it_fin);
s1.clear();
#include <utility>
std::pair<tipo1, tipo2> p = std::make_pair(f, s);
p.first
p.second
24.13. Bitset
Un bitset<N> es un array de N bits.
#include <bitset>
m3[2] == 1;
mask m4 = "0100000000110100";
m4 <<= 2; // 0000000011010000
m4 >>= 5; // 0000000000000110
m3.set(); // 1111111111111111
m2.reset(); // 0000000000000000
m4.reset(5); // 0000000000000010
m4.flip(); // 1111111111111101
m4.flip(3); // 1111111111110101
24.14. Iteradores
Proporcionan el nexo de uni´on entre los contenedores y los algoritmos, proporcionando
una visi´on abstracta de los datos de forma que un determinado algoritmo sea independiente
de los detalles concernientes a las estructuras de datos.
Los iteradores proporcionan la visi´on de los contenedores como secuencias de objetos.
Los iteradores proporcionan, entre otras, las siguientes operaciones:
#include <iterator>
24.15. Directos
typedef std::vector<int> vect_int;
typedef vect_int::iterator vi_it;
typedef vect_int::const_iterator vi_cit;
int n = 0;
for (vi_it i = vi.begin(); i != vi.end(); ++i) {
*i = n;
++n;
}
// vi = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }
24.16. Inversos
typedef std::vector<int> vect_int;
typedef vect_int::iterator vi_it;
typedef vect_int::reverse_iterator vi_rit;
typedef vect_int::const_reverse_iterator vi_crit;
int n = 0;
for (vi_rit i = vi.rbegin(); i != vi.rend(); ++i) {
*i = n;
++n;
}
// vi = { 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 }
24.17. Inserters
“Inserters” producen un iterador que al ser utilizado para asignar objetos, alojan espacio para
´el en el contenedor, aumentando as´ı su taman˜o.
istream_iterator<int> is(cin);
istream_iterator<int> fin_entrada;
int i1 = *is;
++is;
int i2 = *is;
copy(is, fin_entrada, back_inserter(v));
//------------------------------------
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
namespace std{
template <typename Tipo>
inline ostream& operator<<(ostream& out, const vector<Tipo>& v) {
copy(v.begin(), v.end(), ostream_iterator<Tipo>(out, " "));
return out;
}
}
using namespace std;
int main()
{
vector<int> libros(5, 33);
vector< vector<int> > bib(2, libros);
cout << bib << ’;’<< endl;
bib[1].erase(bib[1].begin());
cout << bib << ’;’<< endl;
}
//------------------------------------
#include <iostream>
#include <fstream>
#include <algorithm>
#include <iterator>
void
copiar_fichero(const string& salida, const string& entrada, bool& ok)
{
typedef istream_iterator<char> Iterador_Entrada;
typedef ostream_iterator<char> Iterador_Salida;
ok = false;
ifstream f_ent(entrada.c_str());
if (f_ent) {
ofstream f_sal(salida.c_str());
if (f_sal) {
entrada.unsetf(ios::skipws);
Iterador_Entrada it_ent(f_ent);
Iterador_Entrada fit_ent;
Iterador_Salida it_sal(f_sal);
f_sal.close(); // no es necesario
}
f_ent.close(); // no es necesario
}
}
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <algorithm>
//------------------------------------
struct FileNotFound : public FileIOError {
explicit FileNotFound(const char* w) : FileIOError(w) {}
explicit FileNotFound(const string& w) : FileIOError(w) {}
};
struct FileFormatError : public FileIOError {
explicit FileFormatError(const char* w) : FileIOError(w) {}
explicit FileFormatError(const string& w) : FileIOError(w) {}
};
struct FileIOError : public std::runtime_error {
explicit FileIOError(const char* w) : std::runtime_error(w) {}
explicit FileIOError(const string& w) : std::runtime_error(w) {}
};
//------------------------------------
template <typename Contenedor>
void cargar(const string& filename, Contenedor& c)
throw(FileNotFound, FileFormatError)
{
typedef istream_iterator<typename Contenedor::value_type> InputIt;
ifstream in(filename.c_str());
if (in.fail()) { throw FileNotFound(filename); }
InputIt entrada(in);
InputIt fin_entrada;
//c.assign(entrada, fin_entrada);
//c.clear();
std::copy(entrada, fin_entrada, back_inserter(c));
if (!in.eof()) { throw FileFormatError(filename); }
}
template <typename Contenedor, typename Predicate1>
void cargar(const string& filename, Contenedor& c, const Predicate1& pred1)
throw(FileNotFound, FileFormatError)
{
typedef istream_iterator<typename Contenedor::value_type> InputIt;
ifstream in(filename.c_str());
if (in.fail()) { throw FileNotFound(filename); }
InputIt entrada(in);
InputIt fin_entrada;
//c.clear();
std::remove_copy_if(entrada, fin_entrada, back_inserter(c), not1(pred1));
if (!in.eof()) { throw FileFormatError(filename); }
}
template <typename Procedure1>
void aplicar(const string& filename, const Procedure1& proc1)
throw(FileNotFound, FileFormatError)
{
typedef istream_iterator<typename Procedure1::argument_type> InputIt;
ifstream in(filename.c_str());
if (in.fail()) { throw FileNotFound(filename); }
InputIt entrada(in);
InputIt fin_entrada;
std::for_each(entrada, fin_entrada, proc1);
if (!in.eof()) { throw FileFormatError(filename); }
}
bool pred1(obj);
bool pred2(obj1, obj2);
bool cmp2(obj1, obj2); // bool less<Tipo>(o1, o2)
void proc1(obj);
void proc2(obj1, obj2);
tipo func1(obj);
tipo func2(obj1, obj2);
struct persona {
string nombre;
. . .
};
Binder: permite que una funci´on de 2 argumentos se utilice como una funci´on de 1 argumento,
ligando un argumento a un valor fijo.
Adapter: convierte la llamada a una funci´on con un objeto como argumento a una
llamada a un m´etodo de dicho objeto. Ej: of(obj) se convierte a obj->m1().
objeto_funcion_unario = mem_fun(ptr_funcion_miembro_0_arg);
objeto_funcion_unario = mem_fun_ref(ptr_funcion_miembro_0_arg);
// l es un contenedor<Shape*>
for_each(l.begin(), l.end(), mem_fun(&Shape::draw)); // elem->draw()
// l es un contenedor<Shape>
for_each(l.begin(), l.end(), mem_fun_ref(&Shape::draw)); // elem.draw()
// l es un contenedor<string>
it = find_if(l.begin(), l.end(), mem_fun_ref(&string::empty)); // elem.empty()
objeto_funcion_binario = mem_fun1(ptr_funcion_miembro_1_arg);
objeto_funcion_binario = mem_fun1_ref(ptr_funcion_miembro_1_arg);
// l es un contenedor<Shape*> elem->rotate(30)
for_each(l.begin(), l.end(), bind2nd(mem_fun1(&Shape::rotate), 30));
// l es un contenedor<Shape> elem.rotate(30)
for_each(l.begin(), l.end(), bind2nd(mem_fun1_ref(&Shape::rotate), 30));
Un adaptador de puntero a funci´on crea un objeto funci´on equivalente a una funci´on. Para
utilizar funciones con los “binders” y “adapters”.
objeto_funcion_unario = ptr_fun(ptr_funcion_1_arg);
objeto_funcion_binario = ptr_fun(ptr_funcion_2_arg);
Ejemplo:
#include <iostream>
#include <string>
#include <list>
#include <algorithm>
/*
* Busca la primera ocurrencia de ’cad’ en lista
*/
const char*
buscar(const List_str& ls, const char* cad)
{
typedef List_str::const_iterator LI;
LI p = find_if(ls.begin(), ls.end(),
not1(bind2nd(ptr_fun(strcmp), cad)) );
/*
* LI p = find_if(ls.begin(), ls.end(),
* bind2nd(equal_to<string>(), cad) );
*/
if (p == ls.end())
{ return NULL;
} else {
return *p;
}
}
int
main()
{
List_str lista;
lista.push_back("mar´ıa");
lista.push_back("lola");
lista.push_back("pepe");
lista.push_back("juan");
const char* nm = buscar(lista, "lola");
if (nm == NULL) {
cout << "No encontrado" << endl;
} else {
cout << nm << endl;
}
}
Objetos Predicados
24.21. Algoritmos
#include <algorithm>
Ordenaciones
Conjuntos
Heap
make_heap(f, l)
push_heap(f, l)
pop_heap(f, l)
sort_heap(f, l)
Comparaciones
x = min(a, b)
x = min(a, b, cmp2)
x = max(a, b)
x = max(a, b, cmp2)
x = min_element(f, l)
x = min_element(f, l, cmp2)
x = max_element(f, l)
x = max_element(f, l, cmp2)
b = lexicographical_compare(f, l, ff, ll) // <
b = lexicographical_compare(f, l, ff, ll, cmp2)
Permutaciones
b = next_permutation(f, l)
b = next_permutation(f, l, cmp2)
b = prev_permutation(f, l)
b = prev_permutation(f, l, cmp2)
Las siguientes garant´ıas se ofrecen bajo la condici´on de que las operaciones suministradas
por el usuario (asignaciones, swap, etc) no dejen a los elementos del contenedor en un estado inv
´alido, que no pierdan recursos, y que los destructores no eleven excepciones.
24.23. Num´ericos
#include <numeric>
24.24. L´ımites
#include <limits>
numeric_limits<char>::is_specialized = true;
numeric_limits<char>::digits = 7; // d´ıgitos excluyendo signo
numeric_limits<char>::is_signed = true;
numeric_limits<char>::is_integer = true;
numeric_limits<char>::min() { return -128; }
numeric_limits<char>::max() { return 128; }
numeric_limits<float>::is_specialized = true;
numeric_limits<float>::radix = 2; // base del exponente
numeric_limits<float>::digits = 24; // n´umero de d´ıgitos (radix) en mantisa
numeric_limits<float>::digits10 = 6; // n´umero de d´ıgitos (base10) en mantisa
numeric_limits<float>::is_signed = true;
numeric_limits<float>::is_integer = false;
numeric_limits<float>::is_exact = false;
numeric_limits<float>::min() { return 1.17549435E-38F; }
numeric_limits<float>::max() { return 3.40282347E+38F; }
numeric_limits<float>::epsilon() { return 1.19209290E-07F; } // 1+epsilon-1
numeric_limits<float>::round_error() { return 0.5; }
numeric_limits<float>::infinity() { return xx; }
numeric_limits<float>::quiet_NaN() { return xx; }
numeric_limits<float>::signaling_NaN() { return xx; }
numeric_limits<float>::denorm_min() { return min(); }
numeric_limits<float>::min_exponent = -125;
numeric_limits<float>::min_exponent10 = -37;
numeric_limits<float>::max_exponent = +128;
numeric_limits<float>::max_exponent10 = +38;
numeric_limits<float>::has_infinity = true;
numeric_limits<float>::has_quiet_NaN = true;
numeric_limits<float>::has_signaling_NaN = true;
numeric_limits<float>::has_denorm = denorm_absent;
numeric_limits<float>::has_denorm_loss = false;
numeric_limits<float>::is_iec559 = true;
numeric_limits<float>::is_bounded = true;
numeric_limits<float>::is_modulo = false;
numeric_limits<float>::traps = true;
numeric_limits<float>::tinyness_before = true;
numeric_limits<float>::round_style = round_to_nearest;
class type_info {
public:
virtual ~type_info();
1
Ap´endice A
Precedencia de Operadores en C
+----------------------------------------------------------------------- +
| Precedencia de Operadores y Asociatividad |
+-----------------------------------------------+----------------------- +
| Operador | Asociatividad |
+-----------------------------------------------+----------------------- +
| () [] -> . | izq. a dch. |
| ! ~ ++ -- - (tipo) * & sizeof | [[[ dch. a izq. ]]] |
| * / % | izq. a dch. |
| + - | izq. a dch. |
| << >> | izq. a dch. |
| < <= > >= | izq. a dch. |
| == != | izq. a dch. |
| & | izq. a dch. |
| ^ | izq. a dch. |
| | | izq. a dch. |
| && | izq. a dch. |
| || | izq. a dch. |
| ?: | [[[ dch. a izq. ]]] |
| = += -= *= /= %= &= ^= |= <<= >>= | [[[ dch. a izq. ]]] |
| , | izq. a dch. |
+-----------------------------------------------+----------------------- +
+----------------------------------------------------------------------- +
| Orden de Evaluaci´on |
+----------------------------------------------------------------------- +
| Pag. 58-59 K&R 2 Ed. |
| Como muchos lenguajes, C no especifica el orden en el cual |
| Los operandos de un operador seran evaluados (excepciones |
| son && || ?: ,). La coma se evalua de izda a dcha y el valor |
| que toma es el de la derecha. |
| Asi mismo, el orden en que se evaluan los argumentos de una |
| funci´on no esta especificado. |
+----------------------------------------------------------------------- +
+----------------------------------------------------------------------- +
| Constantes Car´acter: |
| [\n=NL] [\t=TAB] [\v=VTAB] [\b=BS] [\r=RC] [\f=FF] [\a=BEL] |
+----------------------------------------------------------------------- +
1
Ap´endice B
Precedencia de Operadores en
C++
===============================================================================
nombre_clase::miembro Resoluci´on de ambito
nombre_esp_nombres::miembro Resoluci´on de ambito
::nombre Ambito global
::nombre_calificado Ambito global
===============================================================================
Notas
Los operadores de casillas mas altas tienen mayor precedencia que los
operadores de casillas mas bajas.
C.1. cassert
#include <cassert>
C.2. cctype
#include <cctype>
C.3. cmath
#include <cmath>
321
1 APE´ NDICE C. BIBLIOTECA BA´ SICA ANSI-C (+
CONIO)
double asin(double x); // arco seno de x, x en [-1,1]
double acos(double x); // arco coseno de x, x en [-1,1]
double atan(double x); // arco tangente de x
double atan2(double y, double x); // arco tangente de y/x
double sinh(double rad); // seno hiperb´olico de rad
double cosh(double rad); // coseno hiperb´olico de rad
double tanh(double rad); // tangente hiperb´olica de rad
double exp(double x); // e elevado a x
double log(double x); // logaritmo neperiano ln(x), x > 0
double log10(double x); // logaritmo decimal log10(x), x > 0
double pow(double x, double y); // x elevado a y
double sqrt(double x); // raiz cuadrada de x, x >= 0
double ceil(double x); // menor entero >= x
double floor(double x); // mayor entero <= x
double fabs(double x); // valor absoluto de x
double ldexp(double x, int n); // x * 2 elevado a n
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
C.4. cstdlib
#include <cstdlib>
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
void *bsearch(const void *key, const void *base, size_t nitems, size_t size,
int (*compar)(const void *, const void *));
C.5. climits
#include <climits>
CHAR_BIT = 8
C.6. cfloat
#include <cfloat>
C.7. ctime
#include <ctime>
clock_t clock();
// devuelve el tiempo de procesador empleado por el programa
// para pasar a segundos: double(clock())/CLOCKS_PER_SEC
// en un sistema de 32 bits donde CLOCKS_PER_SEC = 1000000
// tiene un rango de 72 minutos aproximadamente. (c2-c1)
time_t time(time_t* tp);
// devuelve la fecha y hora actual del calendario
double difftime(time_t t2, time_t t1);
// devuelve t2 - t1 en segundos
time_t mktime(struct tm* tp);
struct tm* gmtime(const time_t* tp);
C.8. cstring
#include <cstring>
C.9. cstdio
#include <cstdio>
FILE *tmpfile(void);
char *tmpnam(char *str);
C.10. cstdarg
#include <cstdarg>
C.11. conio.h
#include <conio.h> (no es estandar ANSI)
- Movimientos de bloques
int gettext(int _left, int _top, int _right, int _bottom, void*_destin);
// copia texto de pantalla a memoria
int puttext(int _left, int _top, int _right, int _bottom, void*_source);
// copia texto de memoria a pantalla
- Control de ventanas
void textmode(int _mode);
// pone el modo texto
void window(int _left, int _top, int _right, int _bottom);
// define una ventana de texto
void _set_screen_lines(int nlines);
void _setcursortype(int _type); // _NOCURSOR, _SOLIDCURSOR, _NORMALCURSOR
- Control de intensidad
void intensevideo(void);
void highvideo(void);
// alta intensidad
void lowvideo(void);
// baja intensidad
void normvideo(void);
// intensidad normal
void blinkvideo(void);
// parpadeo
- Informaci´on
void gettextinfo(struct text_info *_r);
// informaci´on sobre la ventana actual
int wherex(void);
// valor de la coordenada x del cursor
int wherey(void);
// valor de la coordenada y del cursor
struct text_info {
unsigned char winleft;
unsigned char wintop;
unsigned char winright;
unsigned char winbottom;
unsigned char attribute;
unsigned char normattr;
unsigned char currmode;
unsigned char screenheight; // N´umero de l´ıneas de la pantalla
unsigned char screenwidth; // N´umero de columnas de la pantalla
unsigned char curx; // Columna donde se encuentra el cursor
unsigned char cury; // Fila donde se encuentra el cursor
};
1
Ap´endice D
El Preprocesador
Directivas de pre-procesamiento:
#include <system_header>
#include "user_header"
#line xxx
#error mensaje
#pragma ident
//------------------------------------------------------------------- -
#ifndef STDC
#ifdef cplusplus
#ifndef NDEBUG
#if ! ( defined( GNUC ) \
&& ( ( GNUC >= 4) || (( GNUC == 3)&&( GNUC_MINOR >= 2))) \
&& ( defined linux || defined MINGW32 ))
#error "Debe ser GCC versi´on 3.2 o superior sobre GNU/Linux o MinGW/Windows "
#error "para usar tipos_control"
#elif defined STDC_VERSION && STDC_VERSION >= 199901L
#else
#endif
331
Ap´endice E
debe ser:
debe ser:
switch (x)
{ case 0:
case 1:
cout << "Caso primero" << endl;
break;
case 2:
cout << "Caso segundo" << endl;
break;
default:
Al alojar agregados din´amicos para contener cadenas de caracteres, no solicitar espacio para
contener el terminador ’\0’
char cadena[] = "Hola Pepe";
debe ser:
Al alojar con
desalojar con
delete p;
en vez de con
delete [] p;
333
Ap´endice F
Caracter´ısticas no Contempladas
335
Ap´endice G
Bibliograf´ıa
El Lenguaje de Programaci´on C.
2.Ed. B.Kernighan, D. Ritchie
Prentice Hall 1991
C++ FAQ-Lite
M. Cline http://www.parashift.com/c+
+-faq-lite/
336
´INDICE ALFABE´ TICO 337
if, 35
switch, 36
throw, 148
tipo, 17
tipos
cuadro resumen, 19
puntero, 192
a subprogramas, 242
acceso, 194
operaciones, 194
par´ametros, 196
punteroagregado
par´ametros, 241
tipos compuestos, 17, 51
array, 62, 63
acceso, 64
multidimensional, 68
predefinido, 87
size, 64
cadenas estilo-C,
98 campos de bits,
105 par´ametros,
51
struct, 59
uniones, 104
tipos simples, 17
enumerado, 19
escalares, 18
ordinales, 18
predefinidos, 17
bool, 17
char, 17
double, 18
float, 18
int, 18
long, 18
long long, 18
short, 18
unsigned, 18
try, 148
uni´on, 104
using, 137
using namespace, 137
variables
declaraci´on, 21