1. Programaci´n Modular. ETSIT. 1o C.
o
Apuntes del profesor Juan Falgueras.
Curso 2001/02
versi´n: 28 de abril de 2003
o
5
Programaci´n orientada a objetos
o
Contenido
5. Programaci´n orientada a objetos
o 1
5.1. Introducci´n a C++ . . . . . . . .
o . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
5.2. Diferencias entre C y C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
5.3. Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
5.4. Definici´n de clases . . . . . . . .
o . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
5.5. M´todos . . . . . . . . . . . . . .
e . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
5.6. Constructores y Destructores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5.7. Sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5.8. Entrada y salida en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
5. Programaci´n orientada a objetos
o
5.1. Introducci´n a C++
o
El lenguaje C++ fue desarrollado por Bjarne Stroustrup1 de AT&T Bell Laboratories durante
los 80. El autor expandi´ enormemente el lenguaje C para dar soporte a principios de dise˜o m´s
o n a
modernos. La diferencia m´s importante entre C y C++ est´ en el soporte para clases, pero son
a a
fundamentales tambi´n:
e
1. Sobrecarga de operadores, que hace posible dar significados a˜adidos a los operadores tradi-
n
cionales.
2. Plantillas (templates), que permiten escribir pre-c´digo muy reutilizable sin concretar a´n
o u
elementos variables.
3. Gesti´n de excepciones, uniformando dando soporte a la detecci´n y respuesta a los errores
o o
de ejecuci´n.
o
Sin embargo uno de los objetivos que se propuso el autor fue el de mantener a´n compatibilidad con
u
el lenguaje germinal C en lo posible. As´ todas las posibilidades del est´ndard C est´n igualmente
ı a a
presentes en C++. Esto, sin embargo no significa que todos los programas escritos en C compilen
en C++; existen restricciones en C++ respecto a C sobre todo en aras de una mayor seguridad.
5.2. Diferencias entre C y C++
Adem´s de las grandes novedades relativas a la programaci´n orientada a objetos (clases,
a o
sobrecarga, derivaci´n, funciones virtuales, plantillas y gesti´n de excepciones), C++ a˜ade a C
o o n
peque˜as diferencias que conviene conocer.
n
1 http://www.research.att.com/~bs/homepage.html
2. 5.2 Diferencias entre C y C++ 2
Comentarios C++ tiene comentarios de ´mbito limitado a una s´la l´
a o ınea, frente a C que no acaba
el comentario al terminar la l´
ınea. Como ya habremos visto, los comentarios de C++ empiezan con
// y terminan con el final de la l´ ınea. Esto no quiere decir, por otra parte, que no se puedan
utilizar tambi´n la t´cnica de comentarios de C.
e e
Etiquetas frente a nombres de tipo Los identificadores de estructuras, uniones y enumerados
bastan para definir en C++ el propio tipo sin necesidad de crear un tipo con typedef. As´ en vez
ı
de
typedef struct {int numerador, denominador;} Fracc;
basta (en C++) escribir:
struct Fracc {int numerador, denominador;};
pudi´ndose desde entonces definir estructuras de tipo Fracc;.
e
Funciones sin argumentos No hay necesidad de usar la palabra void cuando se definen fun-
ciones sin argumentos:
int leeInt(void); // en C
int leeInt(); // es suficiente en C++
Argumentos con valor por defecto C++ permite que un argumento tome un valor por defecto
cuando no se especifique un valor real en la llamada. Por ejemplo:
void wrLn(int n = 1) { // tiene un par´metro que toma por defecto el valor 1
a
while (n-- > 0) putchar(’n’);
}
puede ser llamado con o sin par´metros
a
wrLn(3); // escribir´a tres saltos de l´nea
ı ı
wrLn(); // escribir´a uno
ı
Si se define la funci´n int a(int a=1, int b=2, int c), ¿qu´ significa a(4,5)? Podr´ significar
o e ıa
varias cosas con lo que los argumentos con valores por defecto siempre deben estar juntos al final
de la lista de argumentos formales. int a(int c, int a=1, int b=2) hace que a(1), ´ a(1,2)
o
´ a(1,2,3) sean todos no-ambiguos.2
o
Funciones inline El lenguaje C explot´ enormemente el uso del preprocesado l´xico. El prepro-
o e
cesador act´a antes que el compilador substituyendo sint´cticamente los elementos #define’dos
u a
por su equivalente, sin que el preprocesador interprete el significado sino s´lo los tokens l´xicos que
o e
intervienen. Esta idea proven´ de los antiguos lenguajes ensambladores de los que C era cercano
ıa
heredero. La #define’ci´n de s´
o ımbolos es s´lo el mecanismo m´s simple; cpp, el preprocesador,
o a
previo a C, permite la expansi´n de macros. Una macro es un s´
o ımbolo con argumentos. Entonces
se sustituye el s´
ımbolo por la expresi´n y los argumentos se intercalan en la expresi´n all´ d´nde
o o ı o
se indique.
La potencia sint´ctica del preprocesado de macros de C tiene sin embargo algunos puntos
a
oscuros. Imagin´monos un macro
e
#define cuadrado(x) (x*x)
2 En otros lenguajes es posible poner nombres a los argumentos en la propia llamada, a(1, c=>3) permitir´ que
ıa
b tomase libremente su valor por defecto. Esto ayuda a manejar funciones con muchos argumentos superfluos.
3. 5.2 Diferencias entre C y C++ 3
que significa que all´ en el fuente en C d´nde aparezca cuadrado(algo) lo debe substituir por
ı o
cuadrado(algo*algo). Insistimos, el preprocesador no entra en interpretar lo que signifique nin-
guna de las expresiones anteriores, eso se lo deja al compilador que recoje el resultado del prepro-
cesado. Ve´mosla en acci´n en diversas ocasiones como:
a o
void f(double d, int i)
{
r = cuadrado(d); // bien
r = cuadrado(i++); // mal: significa (i++*i++)
r = cuadrado(d+1); // mal: significa (d+1*d+1); que es, (d+d+1)
// ...
}
El problema de “d+1” se resuelve mediante el a˜adido de par´ntesis en el macro:
n e
#define cuadrado(x) ((x)*(x)) /* mejor */
pero el de “i++” no tiene soluci´n en C. C++ a˜ade un tipo de “funciones” que se expanden
o n
(sint´cticamente) en el momento de ser utilizadas. As´ no es un preprocesador, el preprocesador de
a ı
C, que no tiene en cuenta la sem´ntica de las operaciones C, el que se encarga de expandir estas
a
construcciones sint´cticas sino el propio compilador en C++ el que expande de manera correcta
a
estos macros, teniendo sobre todo en cuenta la manipulaci´n correcta de los argumentos.
o
inline int icuadrado(int x) {return x * x;}
El hecho de que sea el propio compilador el que expanda el cuerpo de la funci´n obliga, sin embargo,
o
a que el tipo del argumento sea conocido, algo que C++ no puede resolver excepto con el uso de
plantillas, que son, sin embargo, inadecuadas para este prop´sito.
o
Paso de par´metros por referencia En C todos los par´metros siempre son pasados por
a a
valor, esto es, copiando el par´metro que se ponga en la llamada, par´metro actual, a la variable
a a
receptora en el par´metro formal ya como variable local del procedimiento. Esto nos obliga a
a
utilizar a poner las direcciones de las variables (& en los par´metros de la llamada y a apuntar
a
mediante esas direcciones (*) dentro del procedimiento para poder modificar los valores de las
variables pasadas.
void intercambia(int *a, int *b) {
int temp= *a;
*a = *b;
*b = temp;
}
y es llamado mediante intercambia(&i, &j);. O la tan famosa scanf que require referencias a
los elementos a leer. Por ejemplo:
for (i=0; i<N; i++)
scanf("%d", &a[i]);
Esta t´cnica es inc´moda e insegura3 . C++ detalla y permite un mayor control del paso de
e o
par´metros a˜adiendo la posibilidad del paso de par´metros por referencia. En este caso, el par´me-
a n a a
tro actual no necesita llevar el indicador de que se va modificar (&) sino que es en el par´metro
a
3 Aunque esta t´cnica es inc´moda, tambi´n hay que decir que es m´s expl´
e o e a ıcita en cuanto a la peligrosidad de
uso de este tipo de modificaciones no locales. Es m´s expl´
a ıcita porque manifiesta el cambio mediante el signo *
en el procedimiento. Sin embargo respecto a los arrays el tema es m´s oscuro y el cambio se produce siempre ya
a
que el propio nombre del array es la direcci´n del bloque de datos del mismo. Recordemos que en C los par´ntesis
o e
cuadrados [] act´ an como operadores de indirecci´n siempre: x[i] equivale *(x+i)
u o
4. 5.2 Diferencias entre C y C++ 4
formal de la declaraci´n del procedimiento donde se indica que se va a recibir al par´metro por
o a
referencia. Esto es mucho m´s c´mo en muchos casos y es preferido por la mayor´ de los pro-
a o ıa
gramadores. Es un mecanismo a˜adido. El compilador se encarga de los aspectos ‘sucios’ de la
n
modificaci´n del par´metro actual. No hay que usar & cada vez que se llame en el par´metro
o a a
formal ni * en la funci´n cada vez que se quiera acceder al valor del par´metro. En el caso del
o a
procedimiento de intercambio anterior escribiremos:
void intercambia(int& a, int& b) {
int temp= a;
a = b;
b = temp;
}
y es llamado mediante intercambia(i, j);. N´tese que aparece una nueva notaci´n: se declara
o o
un par´metro que es una referencia a un tipo de elemento. C carece de las posibilidades de la
a
referencia y por ello usa los punteros siempre.
Operadores new y delete En vez de las funciones malloc, calloc, realloc y free, aunque
C++ las puede utilizar, C++ introduce un operador (y por lo tanto una sintaxis diferente) para
adquirir nuevos bloques de memoria y/o crear nuevos objetos en ella. Por ejemplo:
int *pint, *pin2, *aint;
pint = new int; // adquiere memoria adecuada para un entero
pin2 = new int(33); // .. e inicializa su contenido a 33
aint = new int[10]; // ´dem para un array de 10 enteros
ı
Como es de esperar, new devolver´ un puntero nulo (0 ´ NULL) cuando no pueda encontrar un
a o
espacio suficiente de memoria. El tama˜o del espacio lo calcula el propio compilador seg´n el
n u
objeto a ubicar. En esto est´ la mayor diferencia con C. Por otro lado
a
delete pint; // libera la memoria conseguida
delete[] aint; // requiere [] con arrays
El hecho de que sea un operador hace m´s c´modo su uso ya que tampoco hace falta el incluir
a o
<stdlib.h> con el prototipo (interfaz) de malloc, etc. sino que el propio compilador expande el
operador al estilo de un macro:
pa = new int; // (int *) malloc(sizeof(int))
es equivalente al macro:
#define new(X) (X *) malloc(sizeof(X))
encarg´ndose el compilador de C++ de estos detalles.
a
Conversi´n de tipos El operador de conversi´n de tipos de C se ve ampliado en C++ y adem´s
o o a
se usa con una sintaxis un poco diferente a´n en el caso sencillo de adaptaci´n de tipos sencilla
u o
com´n:
u
int num = 33;
char c1 = (char) num; // estilo C
char c2 = char (num); // estilo C++
pero existen en C++ otros conversores de tipo como son conversi´n de atributo const, reinterpretar
o
el significado de los bytes (sin cambio de valores) y conversores entre clases, de una sem´ntica m´s
a a
compleja de la que se pretende cubrir en esta introducci´n.
o
5. 5.3 Clases 5
5.3. Clases
La mayor diferencia entre C y C++ est´ en soporte que el ultimo ofrece para las clases 4 .
a ´
Esencialmente una clase es un tipo abstracto de datos: colecci´n de datos junto a operadores para
o
acceder y modificarlos. Con las clases podremos, pues construir nuevos tipos de datos, que siendo
cuidadosos en su construcci´n, ser´n equivalentes en su trato, con los tipos predefinidos por el
o a
lenguaje. As´ podr´
ı ıamos definir una clase Complejo y variables
Complejo c1, c2, c3;
y, mediante la sobrecarga de operadores que C++ permite, operar correctamente con nuestros
propios operadores de multiplicaci´n, etc´tera
o e
c3 = c1 * c2;
adem´s del resto de operaciones que queramos definir sobrecargando los operadores propios del
a
lenguaje.
Pero el uso de las clases es a´n m´s interesante desde el punto de vista de la programaci´n
u a o
orientada a objetos. Si estamos desarrollando una interfaz de usuario, prob´blemente tengamos
a
un objeto panel que puede siembre abrirse, cerrarse, ocultarse, etc. Pero despu´s veremos que
e
un di´logo es un panel m´s otra serie de operaciones como la de interactuar con el teclado. Una
a a
ventana ser´ otro tipo de panel que tendr´ barra de desplazamiento, etc. Luego ser´n todos objetos
a a a
derivados del primero con comportamientos heredados de aqu´l m´s particularidades propias. Si
e a
lo que estamos desarrollando es una aplicaci´n de contabilidad, podremos tener una clase Cuenta
o
con operaciones comunes como deposito, retiro, etc.
Las clases facilitan la programaci´n de aplicaciones complejas haciendolas m´s legibles y con-
o a
trolables. Sin embargo el precio es una mayor dificultad de aprendizaje del lenguaje. La progra-
maci´n orientada a objetos es adecuada en aplicaciones medianas a grandes, no tanto en peque˜as
o n
aplicaciones.
5.4. Definici´n de clases
o
Definir una clase en C++ es muy parecido a definir una structura en C. En su forma m´s a
simple, la el aspecto de una clase es id´ntico al de una estructura, usando la palabra class en vez
e
de struct:
class Complejo {
float parteReal;
float parteImaginaria;
};
dici´ndose que parteReal y parteImaginaria son miembros de la clase Complejo. La convenci´n
e o
de comenzar los nombres de los tipos con may´sculas es adecuada tambi´n aqu´ pero por supuesto
u e ı,
depende de las normas de estilo del grupo de programadores, no del lenguaje.
En una terminolog´ m´s general en programaci´n orientada a objetos, los datos miembros
ıa a o
son los atributos de la clase.
Una vez definida la clase, su nombre se puede usar para declarar variables como se hac´ con
ıa
las estructuras:
Complejo c1, c2;
N´tese que el identificador (la etiqueta) sirve en C++ como nombre del tipo y no es necesario
o
escribir class Complejo c1;.
A las variables c1 y c2 se les llama instancias de la clase Complejo. Una instancia de
cualquier clase se denomina objeto.
Los miembros de una estructura son accedidos mediante los operadores . y ->. En una clase,
por otro lado, los miembros est´n ocultos, mientras no se indique lo contrario, o sea, que por
a
defecto son inaccesibles, de manera que las siguientes acciones son ilegales:
4 El nombre original de C++ era C with Classes: C + C.
6. 5.5 M´todos
e 6
c1.parteReal = 0.0; // ilegal, no accesible
im = c2.parteImaginaria; // ilegal, no accesible
Decimos que parteReal y parteImaginaria son miembros privados de la clase Complejo.
Pero si as´ lo queremos, podemos hacer accesibles los miembros de una clase declar´ndolos
ı a
public:
class Complejo {
public:
float parteReal;
float parteImaginaria;
};
Podemos mezclar partes p´blicas y privadas de una clase:
u
class Complejo {
public:
float parteReal;
private:
float parteImaginaria;
};
N´tese el uso de private para comenzar una secci´n de declaraciones ocultas. Al principio de la
o o
declaraci´n si no se indica otra cosa se supone impl´
o ıcitamente la palabra private, por lo que lo
anterior es equivalente a:
class Complejo {
float parteImaginaria;
public:
float parteReal;
};
5.5. M´todos
e
Si no son visibles los miembros privados de una clase, ¿c´mo se pueden modificar o ver sus
o
valores? La respuesta es ingenua: las funciones que necesiten acceder a los datos miembros de una
clase deben estar declarados dentro de la misma clase. Son las funciones miembro o m´todos.
e
Esta es la t´cnica que C++ utiliza para ver/modificar sus datos miembro. La diferencia entre
e
las clases y las estructuras empieza realmente aqu´ para controlar el uso de la informaci´n que
ı, o
determina el estado de un objeto se deben usar m´todos que son las funciones, v´ de acceso
e ıas,
seguras para controlar y presentar la informaci´n oculta de manera adecuada. As´ un objeto tiene
o ı
un estado interno reconocible por su comportamiento ante las llamadas a sus m´todos.
e
As´ı:
class Complejo {
public:
void crear(int preal int pimag);
void print();
private:
float parteReal;
float parteImaginaria;
};
tienen los m´todos crear y print que son miembros p´blicos, accesibles desde fuera de la clase.
e u
Para acceder a los m´todos de los objetos se utiliza como para acceder sus datos miembro, la
e
notaci´n “punto”:
o
c1.crear(0.0, 1.0); // c1 contiene el n´mero complejo 0 + 1 i
u
c1.print(); // imprimir´a 0 + 1 i
ı
7. 5.5 M´todos
e 7
Respecto al estilo empleado en C, estas llamadas son extra˜as ya que se conoce al objeto al que
n
se llama, no poni´ndolo como par´metro, sino anteponi´ndolos como prefijo.
e a e
No todos los m´todos de una clase tienen que ser p´blicos. Por ejemplo:
e u
class Fraccion {
public:
void crear(int num, int denom);
void print();
private:
void reduce();
int numerador, denominador;
La filosof´ subyacente es la de ofertar (hacer p´blico) s´lo lo necesario e imprescindible para,
ıa u o
por un lado no confundir innecesariamente al usuario del objeto y, por otro, evitar errores en la
manipulaci´n de los mismos.
o
Hasta ahora s´lo hemos definido los m´todos de las clases y objetos de aqu´llas, pero ¿d´nde
o e e o
est´n los algoritmos que realizan tales m´todos?
a e
Una posibilidad es definir cada m´todo m´s adelante, fuera de la definici´n de la clase. Por
e a o
ejemplo, en el caso de la funci´n crear de las fracciones:
o
void Fraccion.crear(int num, int denom)
{
numerador = num;
denominador = denom;
reduce();
}
N´tese c´mo Fraccion:: precede el nombre de la funci´n. Esta es la notaci´n que C++ emplea
o o o o
para distinguir funciones ordinarias de los m´todos propios de una clase. Observar que crear tiene
e
acceso directo a los datos miembros de la clase propia. En general esto es as´ ya sean los datos
ı
p´blicos o privados.
u
En vez de definir posteriormente los m´todos de una clase, se pueden resolver inline sobre la
e
marcha en la propia definici´n de la clase:
o
class Fraccion {
public:
void crear(int num, int denom)
{numerador = num; denominador = denom; reduce();};
void print();
private:
void reduce();
int numerador, denominador;
esto es conveniente, s´lo si el cuerpo del m´todo es corto.
o e
A˜adamos el m´todo mul para multiplicar modificando ´sta por otra fracci´n:
n e e o
class Fraccion {
public:
void crear(int num, int denom);
void print();
Fraccion mul(Fraccion f); // m´todo para multiplicar
e
private:
void reduce();
int numerador, denominador;
Despu´s tendr´
e ıamos que escribir la definici´n (antes escribimos la declaraci´n) de la funci´n
o o o
mul:
8. 5.6 Constructores y Destructores 8
Fraccion Fraccion::mul(Fraccion f)
{
Fraccion res;
res.numerador = numerador * f.numerador;
res.denominador = denominador * f.denominador;
res.reduce();
return res;
}
Inicialmente la funci´n mul puede parecer algo misteriosa: est´ claro cu´l es uno de los mun-
o a a
tiplicandos, el argumento f, pero ¿d´nde est´ el otro? La respuesta est´ en la forma en la que mul
o a a
es llamado:
f3 = f1.mul(f2);
5.6. Constructores y Destructores
Para asegurar que las instancias de una clase se inicializan adecuadamente la clase puede tener
unos funciones especiales denominadas constructores. As´ ımismo la clase puede tener destruc-
tores, funciones que arreglan y dejan las cosas como estaban antes de eliminarse el objeto. Lo
interesante en principio respecto a los constructores y destructores es que son llamados autom´ti-a
camente sin una llamada expl´ ıcita. Hay que tener cuidado con esto.
Nosotros en nuestro ejemplo, hasta ahora, hemos usado una llamada expl´ ıcita crear para
asignar valores a los atributos. Dada la importancia de la inicializaci´n y la limpieza posterior C++
o
soporta el uso de funciones contructores y destructores autom´ticamente llamados en el momento
a
de la creaci´n de una nueva instancia de la misma y en el momento de su destrucci´n, en el
o o
momento en que la instancia deja de existir, generalmente esto ultimo ocurrir´ cuando la funci´n
´ a o
en la que est´ el objeto local deje de existir.
a
La sintaxis elegida en C++ es de que toda funci´n con el mismo nombre de la clase que
o
no devuelve nada (ni void siquiera) es un constructor. Mientras que toda funci´n con el mismo
o
nombre de la clase antecedido de una tilde ~ es un destructor.
En nuestro ejemplo:
class Fraccion {
public:
Fraccion(int num, int denom)
{numerador = num; denominador = denom; reduce(); }
...
};
Notemos que el constructor va en la parte p´blica de la clase. Los constructores pueden ser llama-
u
dos expl´ıcitamente como los otros m´todos pero lo m´s interesante de ellos es que son llamados
e a
impl´
ıcitamente en el momento de crear una nueva instancia del objeto:
Fraccion f(7,3); // declara e inicializa f a 7/3
Pero ¿qu´ ocurrir´ con la declaraci´n?
e ıa o
Fraccion muchasFracs[3];
El compilador se quejar´ inmediatamente de que no estamos poniendo los argumentos exigidos
ıa
en la llamada impl´
ıcita de construcci´n de cada instancia de fracci´n.
o o
Una primera soluci´n a esto (como veremos en §5.7) ser´ crear un constructor m´s sobrecar-
o ıa a
gando el anterior pero sin par´metros. Como veremos esta es una soluci´n factible, pero la m´s
a o a
adecuada es la de usar par´metros por defecto:
a
9. 5.6 Constructores y Destructores 9
class Fraccion {
public:
Fraccion(int num=0, int denom=1)
{numerador = num; denominador = denom; reduce(); }
...
};
que, como sabemos permite mucha mayor flexibilidad en las llamadas. Podr´
ıamos crear:
Fraccion f(7,3); // 7/3
Fraccion f(7); // 7/1
Fraccion f; // 0/1
Fraccion f[10]; // 10 fracciones f[0]..f[9] inicializadas a 0/1
Los constructores y destructores son especialmente importantes cuando los objetos necesitan
adquirir din´micamente memoria del sistema para almacenar sus datos (atributos) mediante new
a
y delete.
Veamos un ejemplo. La clase Cadena puede contener cadenas de caracteres de cualquier lon-
gitud. La podemos definir:
class Cadena {
..
private:
char *texto;
int long;
};
O sea un puntero a la ristra de caracteres y, optimizando mucho su c´lculo, una variable que recoja
a
su longitud.
La manera m´s adecuada de usar estos objetos ser´ inicializ´ndolos con un valor y posterior-
a ıa a
mente modific´ndolos. Tendr´
a ıamos:
class Cadena {
public:
Cadena(const char *s); // constructor
private:
char *texto;
int long;
}
Y entonces:
Cadena::Cadena(const char *s)
{
long = strlen(s);
text = new char[long+1];
strcpy(text, s);
}
Despu´s de calcular la longitud de la cadena a la que apunta s el constructor llama al operador
e
new para conseguir espacio de memoria suficiente para copiar la cadena; finalmente el constructor
copia la cadena en el espacio reci´n conseguido.
e
Al construir objetos de tipo Cadena har´ ıamos:
Cadena c1("Hola"), c2("y adi´s.");
o
Para que esta clase tenga alg´n inter´s habr´ l´gicamente que a˜adirle m´todos para modificar y
u e ıa o n e
leer la cadena. As´
ımismo podr´ ıamos tener un constructor con valor por defecto nulo:
10. 5.7 Sobrecarga 10
class Cadena {
public:
Cadena(const char *s = 0); // constructor
...
}
y despu´s:
e
Cadena::Cadena(const char *s)
{
if (s == 0) {
long = 0;
text = 0;
} else {
long = strlen(s);
text = new char[long+1];
strcpy(text, s);
}
}
La cuesti´n viene cuando este objeto deje de existir, ya sea porque era local a una funci´n
o o
o porque se acabe el programa, etc. Entonces habr´ que eliminar la memoria conseguida con
ıa
new mediante el delete adecuado. C++ aporta un destructor por defecto que si se dice nada lo
unico que hace es liberar la memoria que ocupaban los miembros de la instancia de la clase. Esto
´
es lo usual tambi´n con variables est´ticas. Sin embargo C++ es incapaz de invertir el algoritmo
e a
de construcci´n empleado para almacenar la cadena de caracteres en el objeto y as´ liberar la
o ı
memoria que usaba. En este caso es pues necesario a˜adir un desctructor expl´
n ıcito a la clase que
ser´ llamado autom´ticamente por C++ cuando el objeto desaparezca:
a a
class Cadena {
public:
Cadena(const char *s = 0);
~Cadena() { delete[] text; } // destructor
...
}
5.7. Sobrecarga
En C++ dos o m´s funciones dentro del mismo ´mbito pueden compartir el mismo nombre.
a a
Cuando las funciones est´n sobrecargadas es el compilador el que decide en base a la firma
a
(signature) de la funci´n cu´l es la que debe ser llamada. S´lo en caso de ambig¨edad el compi-
o a o u
lador no podr´ decidir. La firma est´ compuesta del nombre, argumentos, tipo y modo de uso de
ıa a
los argumentos y tipo y modo del posible valor devuelto.
As´ podr´
ı ıamos tener:
void intercambiar(int& a, int &b);
void intercambiar(char& a, char &b); // sobrecargada
y despu´s
e
intercambiar(a_int, b_int); // llamar´a a la primera
ı
intercambiar(letraa, letrab); // llamar´a a la segunda
ı
Las funciones miembro de una clase tambi´n pueden ser sobrecargadas (lo cual es el uso m´s
e a
frecuente de la sobrecarga). En el caso de Cadena es m´s f´cil utilizar la sobrecarga que considerar
a a
aparte el caso de no tener par´metros el constructor:
a
11. 5.8 Entrada y salida en C++ 11
class Cadena {
public:
Cadena(const char *s = 0);
Cadena() {text = 0; long =0; } // sobrecargada
...
}
Adem´s de admitir la sobrecarga de funciones, C++ admite la sobrecarga de operadores. De
a
hecho esto ya es habitual en el caso de los tipos simples predefinidos. Por ejemplo, podemos hacer
3*2 llamando as´ al operador multiplicaci´n de enteros o 3.1*2.0 que llama a un operador distinto,
ı o
el multiplicador de n´meros reales. La sobrecarga de operadores es particularmente interesante
u
para redefinir los operadores usuales ampliando sus posibles argumentos con los nuevos objetos. El
resultado es un aspecto mucho m´s natural de las operaciones que reutilizan nombres u operadores
a
conocidos en vez de tener que recordar multitud de nombres nuevos para cada tipo u objeto nuevo.
En el caso de Fraccion ser´ mucho m´s f´cil el uso de la multiplicaci´n reemplazando el
ıa a a o
m´todo mul por el operador *. Hacer esto es tan f´cil como substituir el nombre mul por operator*
e a
en la declaraci´n y definici´n del m´todo:
o o e
class Fraccion {
public:
...
Fraccion operator*(Fraccion f); // m´todo para multiplicar
e
private:
...
La implementaci´n de operator* es id´ntica que la de mul.
o e
Cuando un operador se define como m´todo de una clase uno de sus operandos est´ siempre
e a
impl´ıcito. As´ el operador que acabamos de definir es un operador binario, no unario y cuando
ı,
escribimos sentencias como:
f1 * f2;
el compilador nota que f1 es un objeto Fraccion y mira en la clase Fraccion buscando un operador
operator* para convertir la expresi´n en:
o
f1.operator*(f2);
5.8. Entrada y salida en C++
Aunque los programas en C++ pueden utilizar perfectamente <stdio.h>, C++ dispone de una
alternativa a la cl´sica librer´ de entrada y salida. La cabecera m´s importante de la nueva librer´
a ıa a ıa
es <iostream.h> que define varias clases, incluyendo istream (canal de entrada, teclado, etc.) y
ostream (canal de salida, pantalla, etc.). Los programas simples que toman su entrada del teclado
y presentan sus resultados en la pantalla usan el objeto cin para la entrada y el objeto cout para
la salida. cin es una instancia de la clase istream y cout es una instancia de la clase ostream.
Las clases istream y ostream tienen muchos operadores sobrecargados para permitir la salida
y entrada de los tipos est´ndard. La clase istream sobrecarga >> mientras que la clase ostream
a
sobrecarga a <<. De esta forma una sesi´n interactiva ser´
o ıa:
cout << "Introducir un n´mero: ";
u
cin >> n;
cout << "su cuadrado es: " << n*n << endl;
Recordemos que estamos llamando a los operadores ostream::operator<< de la instancia cout
de la clase ostream y al operador istream::operator>> de la instancia cin de la clase istream.
Estos est´n fuertemente recargados, para argumentos de tipo cadena, n´mero, etc´tera.
a u e
Entre las ventajas de usar la librer´ iostream est´n:
ıa a
12. 5.8 Entrada y salida en C++ 12
Podemos ampliar la entrada y salida us´ndo los mismos operadores para las clases que
a
definamos.
Es m´s segura ya que no se producen nunca incoherencias entre el tipo de datos esperado en
a
el formato de printf o scanf con el de los par´metros pasados. Estas incoherencias llevan
a
f´cilmente, en el caso de scanf a graves fallos de funcionamiento.
a
Es m´s r´pida ya que la decisi´n de qu´ algoritmo de salida para el tipo hay que usar se toma
a a o e
en el momento de compilar y no hay que interpretar el formato en momento de la ejecuci´n. o
Hay que decir tambi´n que printf y scanf son m´s flexibles y potentes.
e a
La jerarqu´ de clases de la que deriva <iostream> se esquematiza en la Figura 1:
ıa
ios_base
ios
istream ostream
iostream
ifstream ofstream
istringstream ostringstream
fstream stringstream
Figura 1: Jerarqu´ de la que derivan las clases para manipulaci´n de la entrada y salida est´ndares
ıa o a
<iostream> y de ficheros fstream.
Juan Falgueras
Dpto. Lenguajes y Ciencias de la Computaci´n
o
Universidad de M´laga
a
Despacho 3.2.32