El documento presenta una introducción a la programación orientada a objetos. Explica conceptos clave como objetos, clases, mensajes, métodos y atributos. También describe elementos como la abstracción, el encapsulamiento y cómo las clases permiten agrupar datos y operaciones relacionadas.
2. 3.1 Elementos fundamentales de la programación
orientada a objetos.
3.2 Representación gráfica (UML.)
Unidad III: Introducción a la
Programación Orientada a Objetos.
4. Bibliografía
C++ para Ingeniería y
Ciencia. Editorial
Cengage Learning
Editores S. A. de C. V.
Segunda edición. 2007.
Gary J. Bronson. Ficha
9a . Páginas 64 – 88.
5. Bibliografía
Fundamentos de
Programación con el
Lenguaje de
Programación C++.
Dpto. Lenguajes y CC.
Computación E.T.S.I.
Informática. 2017.
Vicente Benjumea y
Manuel Roldán. Capítulo
13. Páginas: 167 - 168.
6. Bibliografía
C++ / OOP. Un enfoque
práctico. Ricardo Devis
Botella. Capítulo 1.
Páginas: 7 – 10, 13 – 15,
17 – 20, 22 – 26.
7. Introducción.
Por los años 70 (s. XX) una nueva metodología
denominada "desarrollo estructurado", basada en
la independencia de datos y funciones o
métodos, permitió superar la llamada "crisis del
software". El diseño "top-down" se convirtió en
sinónimo de enfoque estructurado: un problema se
asimila a una función o procedimiento, que se
descompone en problemas más pequeños, que a su
vez se descomponen en otras funciones más
pequeñas, hasta llegar a problemas no
descomponibles. Gracias a estos métodos los
software fueron poco a poco creciendo en tamaño y
complejidad, de forma que al final los problemas
solucionados por el desarrollo estructurado
generaron nuevos problemas más difícil de
solucionar.
8. Introducción.
Por los años 80 las empresas dilapidaban su
presupuesto en el mantenimiento de verdaderas
monstruosidades de software: las labores de
depuración se volvían más costosas según
aumentaba el tamaño de los programas; además,
cualquier modificación repercutía normalmente en
todos los módulos, de manera que un programa
bien chequeado pasaba a ser, después de una
pequeña reforma, motivo de nuevos y largos
testeos y validaciones de estabilidad. El
desarrollo de librerías, potenciado sobremanera,
había llegado a un punto crítico de ineficacia: si
una función se ajusta exactamente a lo que
queremos se usará; si no, habrá que codificarla de
nuevo.
9. Introducción.
Todo el tiempo que el equipo de desarrollo
pudiera dedicar a la prototipación y pruebas de
un sistema se perdía, por otra parte,
inevitablemente, había escasa posibilidad de
reutilización del código. Para intentar salir de
este círculo vicioso surgen en un primer
momento lenguajes modulares como Ada,
Modula-2, etc., que solucionan algunos de los
problemas planteados. La situación es, sin
embargo, todavía insatisfactoria, así que la lógica
evolución de los conceptos de modularidad y
reutilización del código origina un nuevo
paradigma que se llamará OOP.
10. Introducción.
Hay que pensar, pues, que la OOP es un
escalón muy avanzado en el desarrollo de la
programación estructurada, pero no tan
avanzado que le permita prescindir de ésta. De
hecho muchos autores de la comunidad OOP
opinan que habría que tender a un más feliz
acercamiento entre ambas metodologías. La
Programación Orientada a Objetos extiende los
conceptos fundamentales de abstracción y
encapsulación de los Tipos Abstractos de Datos
(TADs), añadiendo los conceptos de Herencia,
Polimorfismo, y Vinculación Dinámica.
11. Introducción.
Se mantiene el concepto de objetos como
instancias de clases, los cuales son entidades
activas que encapsulan datos y algoritmos, donde
los atributos contienen el estado y la representación
interna del objeto, cuyo acceso está restringido, y
los métodos permiten la manipulación e
interacción entre objetos. Así, una clase define
una abstracción, y los métodos definen su
comportamiento. Así, las características de un
determinado objeto, su estado y su
comportamiento, están determinadas por la
clase a la que pertenece. Del mismo modo, el
objeto podrá ser manipulado e interactuar con
otros objetos a través de los métodos definidos
por la clase a la que pertenece.
12. Introducción.
La POO proporciona un mecanismo
adecuado para el diseño y desarrollo de
software complejo, modular, reusable,
adaptable y extensible. Por ello la OOP es,
simplificando, un sistema de programación
que utiliza objetos para la resolución de
problemas, interrelacionándolos mediante
mensajes. Modelamos nuestra visión del
problema a través de objetos, que se
relacionan entre sí por canales, de forma
que la comunicación de mensajes se
pretende alta y clara.
13. Introducción.
En definitiva se pretende que los enlaces entre
distintos objetos (o entidades) sean
transparentes para el observador exterior.
Naturalmente esto favorece sobremanera la
comunicación entre los responsables de las
distintas etapas de desarrollo de software. La
programación estructurada no ha muerto, en
realidad la OOP podría considerarse como una
extensión natural de la programación
estructurada, que surgió debido a las carencias y
debilidades de aquella. La OOP es únicamente
un momento en la evolución de las técnicas de
programación, aunque quizá en estos momentos
sea el estadio más avanzado.
14. Introducción.
Pero en informática, aún más rápidamente que
en otros campos, no existen metodologías
inamovibles, sino únicamente peldaños en una
de tantas escaleras. Pues bien, para el caso de
C++, un lenguaje se califica como orientado a
objetos cuando reúne las características de:
abstracción, encapsulamiento, herencia y
polimorfismo; gestionando los conceptos
básicos que las informan: objetos, mensajes,
clases, instancias y métodos. En un programa
hay que operar con los datos, y hasta ahora las
funciones que operan con los datos se han
definido externas a dichos datos.
15. Objetos.
Un objeto es un encapsulamiento abstracto de
información, junto con los métodos o
procedimientos para manipularla. "Un objeto
contiene operaciones que definen su
comportamiento y variables que definen su estado
entre las llamadas a las operaciones". Ejemplo: un
objeto del tipo "sala de cine" donde los datos,
variables o información estarían constituidos por
los espectadores; imaginemos también que los
espectadores no pudieran moverse por sí solos
dentro de la sala. ¿Quiénes serían los
manipuladores, métodos, procedimientos u
operaciones encargados de manipular a los
espectadores? Indudablemente los
acomodadores de la sala, aunque también el
personal directivo de la misma.
16. Mensajes, clases y métodos.
Un mensaje representa una acción a tomar por un
determinado objeto. En el ejemplo anterior, la orden
"que comience la proyección de la película" podría
ser un mensaje dirigido a un objeto del tipo "sala de
cine". Otro mensaje podría ser la orden de "desalojo
de la sala" en funciones no-continuas. Notemos que
el mensaje se refiere únicamente a la orden, y no
tiene que ver con la forma como ésta es respondida.
Una clase equivale a la generalización o
abstracción de un tipo específico de objetos.
Un método consiste en la implementación en una
clase de un protocolo de respuesta a los
mensajes dirigidos a los objetos de la misma. La
respuesta a tales mensajes puede incluir el envío por
el método de mensajes al propio objeto y aun a otros,
también como el cambio del estado interno del objeto.
17. Métodos y atributos.
Los métodos son algoritmos especiales definidos
por la clase, y se aplican sobre los objetos,
manipulando el estado interno del objeto sobre el
que se aplican. La invocación a métodos puede
llevar parámetros asociados, así como producir un
resultado, además de manipular el estado interno
del objeto sobre el que se aplica. Para invocar a
un determinado método sobre un objeto, ese
método debe estar definido por la clase a la que
el objeto pertenece. Los atributos están protegidos,
de tal forma que sólo se permite su acceso y
manipulación a través de los métodos definidos por
la clase. Los atributos almacenan los valores del
estado interno de cada objeto, considerando que
cada objeto tiene un estado interno asociado,
independiente de los otros objetos.
18. Abstracción.
Es una cualidad de los seres humanos que les permite encontrar lo
básico que hay de común en una serie de objetos. Imaginémonos,
por ejemplo, a un niño frente a una cesta de fruta: aunque nunca
antes hubiera visto esos objetos, en muy poco tiempo podría
separar las frutas del mismo tipo. El niño pondría en un montoncito
los plátanos, en otro las naranjas, en otro las ciruelas, etc. En
definitiva por medio de la abstracción conseguimos no pararnos en
el detalle concreto ..., pudiendo así generalizar y operar con "entes
abstractos". O sea, cuando decimos "a mí me gustan las naranjas"
no queremos decir que nos gustan "todas" las naranjas, de
cualquier tipo y en cualquier estado, sino que indicamos que nos
gusta el sabor, la textura, el aroma que por lo general poseen las
naranjas. Vemos, de esta manera, que el fenómeno de la
abstracción consiste en la generalización conceptual de un
determinado conjunto de objetos y de sus atributos y propiedades,
dejando en un segundo término los detalles concretos de cada
objeto.
19. Descripción de una clase.
Dado que algunos datos tienen asociadas
operaciones de forma natural, suma de
complejos, productos, etc. ¿no sería más
lógico pensar que los datos y sus
operaciones deben ir juntos? En este caso la
abstracción no se limita a la organización
de la información, sino que también incluye
el concepto de lo que se puede hacer con
ella. En este contexto surge el concepto de
clase (class) como abstracción que reúne
datos y operaciones con los datos.
20. Descripción de una clase.
class Racional
{ // …
private:
int numerador; int
denominador;
// ...
void simplificaFraccion();
// ...
public:
// ...
void estableceNumerador( int );
void estableceDenominador( int );
// ...
};
21. Descripción de una clase.
class Racional
{ // …
private:
int numerador;
int denominador;
// ...
void simplificaFraccion();
// ...
public:
// ...
void
estableceNumerador( int
);
void
estableceDenominador(
int );
// ...
};
En la clase Racional,
abstracción de los números
racionales, de su
representación formal (x/y:
equis dividido por y) y
operaciones (a/b + c/d),
aparecen encapsulados los
datos internos (numerador,
denominador) y las funciones
miembro para el tratamiento de
éstos (el operador suma de
quebrados, el procedimiento
para cambiar el denominador,
el método para simplificar
fracciones, etc.)
22. Descripción de otra clase.
#include <iostream.h>
class complex
{ public:
double real, imag;
complex (double a, double b)
{real=a; imag=b;}
~ complex () {}};
int main (void)
{ complex a (1.,1.);
cout <<"complex="
<<a.real<<
"+" <<a.imag << ”i" << endl;
return 0; }
23. Ejemplo: Encapsulamiento private, constructor
copia y redifinición de operadores.
int main (void) //programa principal
{ complex a(1.,1.);
//se define un objeto a complejo
complex b; //se define un objeto b
complejo
b=a; //se asigna un complejo a otro
complex c(a) ; //se copia un complejo
de otro
cout <<"complex a ="<< a.get_real() <<
"+" <<a.get_imag()<<"i"<<endl;
cout <<"complex b
="<<b.get_real()<<"+" <<
b.get_imag()<<"i""<<endl;
cout << c << endl;
return 0; }
24. Descripción de otra clase.
#include <iostream.h>
class complex
{ public:
double real, imag;
complex (double a, double b)
{real=a; imag=b;}
~ complex () {}};
int main (void)
{ complex a (1.,1.);
cout <<"complex="
<<a.real<<
"+" <<a.imag << ”i" << endl;
return 0; }
Una clase es un conjunto de
datos (atributos) y
operaciones (métodos.) Los
elementos definidos dentro de
una clase se llaman miembros
(atributos y métodos.) Los
atributos de una clase son
variables que pueden ser tipos
básico del lenguaje, punteros,
tipos definidos por el usuario,
etc. Los métodos son
funciones que operan con los
datos de la clase y con datos
externos que se pasan como
argumentos de la función.
25. Descripción de otra clase.
#include <iostream.h>
class complex
{ public:
double real, imag;
complex (double a, double b)
{real=a; imag=b;}
~ complex () {}};
int main (void)
{ complex a (1.,1.);
cout <<"complex="
<<a.real<<
"+" <<a.imag << ”i" << endl;
return 0; }
Las clases en C++ tienen las
siguientes características:
1. Un nombre identificador
(complex).
2. Un conjunto de atributos
(datos miembro) (real e imag,
ambos de tipo double).
3. Un conjunto de métodos
(funciones
miembro)(complex (double a,
double b)).
4. Unos niveles de acceso
para proteger ciertas partes
de la clase (public).
26. Encapsulamiento.
Es encerrar algo en una “cápsula”. Puede decirse
que encapsulamiento se refiere a la capacidad de
agrupar y condensar en un entorno con límites
bien definidos distintos elementos. En lo que a C++
se refiere, la unidad de abstracción y
encapsulamiento está representada por la clase,
(class). Por un lado es una abstracción pues, es en
ésta donde se definen las propiedades y atributos
genéricos de determinados objetos con
características comunes. La clase es, por otro lado,
un encapsulamiento porque constituye una cápsula
que encierra y amalgama de forma clara tanto los
datos de que constan los objetos como los
procedimientos que permiten manipularlos. Las
clases se constituyen, así, en abstracciones
encapsuladas.
27. Encapsulamiento.
Las clases permiten al programador la libertad de
encapsular variables y métodos en una única
estructura, de forma que los únicos que podrán
acceder a estos datos y operaciones son los objetos
de esa clase. Con esto se consigue el
encapsulamiento, lo que implica seguridad e
independencia. Se define campo o ámbito a la
parte del programa donde es accesible un
identificador, variable, función, etc. y puede
restringirse a los miembros de la clase. Por lo tanto,
el campo de clase extiende la definición de ámbito
a los miembros de una clase; en consecuencia, al
mismo tiempo que permite el acceso a otros
miembros de la misma clase, impide el acceso a
los miembros de otras clases. De esta forma se
consigue el encapsulamiento; cualquier error o
modificación de una clase no interfiere en las demás.
28. Herencia.
La herencia permite modelar relaciones “es
un” entre clases, definiendo jerarquías de
clases. Permite definir una nueva clase derivada
(o sub-clase) como una especialización o
extensión de una clase base (o super-clase)
más general, donde la clase derivada hereda
los atributos y los métodos definidos por la
clase base (reusabilidad del código), y la clase
derivada puede definir nuevos atributos y
nuevos métodos (extensibilidad), así como
también redefinir métodos de la clase base.
Las relaciones de herencia pueden mostrarse
adecuadamente utilizando los diagramas UML
de clases.
29. Herencia.
Fundamentos de Programación con el Lenguaje de Programación C++. Dpto.
Lenguajes y CC. Computación E.T.S.I. Informática. 2017. Vicente Benjumea y
Manuel Roldán. Capítulo 13. Página: 168.
30. Herencia.
Fundamentos de Programación con el Lenguaje de Programación C++. Dpto.
Lenguajes y CC. Computación E.T.S.I. Informática. 2017. Vicente Benjumea y
Manuel Roldán. Capítulo 13. Página: 168.
31. Herencia.
En C++, la herencia se aplica sobre las clases. O
sea, de alguna forma las clases pueden tener
descendencia, y ésta herederará algunas
características de las clases "padres". Si
disponemos las clases con un formato de árbol
genealógico, tendremos lo que se denomina una
estructura jerarquizada de clases. Las clases
pueden heredar diferencialmente de otras
clases (denominadas "superclases") determinadas
características, mientras que, a la vez, pueden
definir las suyas propias. Tales clases pasan, así,
a denominarse "subclases" de aquéllas. En un
esquema simplista basado en grafos de árbol, las
clases con las características más generales
ocuparían la base de la estructura, mientras que las
subclases más especializadas florecerían en los
nodos terminales.
33. Herencia.
En C++ la herencia se implementa mediante un
mecanismo que se denomina derivación de clases:
las superclases pasan a llamarse clases base,
mientras que las subclases se constituyen en
clases derivadas. El mecanismo de herencia está
fuertemente entroncado con la reutilización del
código en OOP. Una clase derivada posibilita, en
C++, el fácil uso de código ya creado en cualquiera
de las clases base ya existentes, de la misma
manera que uno de nuestros hijos puede heredar
alguna de nuestras aptitudes. El concepto de
herencia constituye un estrato básico del paradigma
de objetos, pero esto no significa que todas las
relaciones entre clases en OOP se deban ajustar
siempre a este modelo jerárquico. Es necesario
establecer si la pretendida relación entre objetos
es de pertenencia o de derivación.
34. Herencia.
En una relación típica de pertenencia un objeto
contiene al otro. La herencia puede ser simple
o múltiple (soportada en C++ a partir de la
versión 2.0), dependiendo del número de
superclases que se constituyan en base de
una dada. La herencia puede establecerse,
también como pública, privada o protegida
(esta última a partir de la versión 3.0),
dependiendo de la calificación del acceso a lo
heredado. En resumen, el principal beneficio
de la herencia en C++, consiste en la fácil
reutilización de los componentes de un
determinado sistema a través de la captura
explícita de la generalización y de la flexibilidad
en la incorporación de cambios a la estructura.
35. Polimorfismo.
El polimorfismo permite que un objeto de una
clase derivada pueda ser considerado y
utilizado como si fuera un objeto de la clase
base, proporcionando un soporte adecuado
para el principio de sustitución, mediante el
cual, un objeto de la clase derivada puede
sustituir a un objeto de la clase base, allí
donde sea necesario. Sin embargo, la
dirección de correspondencia opuesta no se
mantiene, ya que no todos los objetos de la
clase base son también objetos de la clase
derivada.
36. Polimorfismo.
Es la posibilidad de acceder a varias funciones
distintas con la misma interfaz. O sea, un mismo
identificador puede tener distintas formas (distintos
cuerpos de función, distintos comportamientos)
dependiendo, en general, del contexto en el que se
halle inserto. En C++ el polimorfismo se establece
mediante la sobrecarga de identificadores y
operadores, la ligadura dinámica y las funciones
virtuales. La sobrecarga se refiere al uso del mismo
identificador u operador en distintos contextos y con
distintos significados. La sobrecarga de funciones
conduce a que un mismo nombre pueda representar
distintas funciones con distinto tipo y número de
argumentos. En el ámbito de la OOP, la sobrecarga de
funciones equivale que un mismo mensaje puede ser
enviado a objetos de diferentes clases de forma que
cada objeto respondería al mensaje apropiadamente.
37. Vinculación dinámica.
Permite que las clases derivadas puedan
redefinir el comportamiento de los métodos
definidos en la clase base. Así, en contextos
polimórficos, gracias a la vinculación dinámica,
los métodos invocados se seleccionan
adecuadamente, en tiempo de ejecución,
dependiendo del tipo real del objeto, y no del tipo
aparente. Es decir, si se invoca a un determinado
método sobre un objeto de una clase derivada
que haya redefinido la implementación de ese
método, entonces se ejecutará el código del
método redefinido en la clase derivada, incluso
aunque la invocación se haya producido en un
contexto polimórfico donde el objeto de la clase
derivada haya sustituido a un objeto de la clase
base.
38. Constructores y destructores.
Un constructor en C++ es una operación, una
función, que inicializa los atributos del objeto
en el momento de su creación. Cuando se crea
un objeto surge la necesidad de asignar valores
iniciales a las variables que contiene, esa es la
misión del constructor. Para definir un costructor
se utiliza el propio nombre de la clase; complex;.
Se pueden definir varios constructores para una
misma clase, todos ellos tendrán el mismo nombre
(el de la clase) pero, en general, se distinguirán
por sus argumentos. Si no se define ningún
constructor, en general el compilador creará uno
por defecto complex () {}, un constructor vacío que
no hace nada.
39. Constructores y destructores.
Los destructores sirven para que el objeto libere
memoria y termine con asuntos pendientes
como cerrar ficheros, etc. antes de ser destruido. El
destructor se llama automáticamente justo antes
de que el objeto que lo contiene sea destruido.
Tal y como sucedía con el constructor, si no se
define un destructor el compilador creará uno
por defecto. Se define el destructor con el nombre
de la clase precedido por el carácter ~. El
destructor no puede tener parámetros (argumentos)
ni retornar valores (ni siquiera void), es decir, el
destructor es único. En el ejemplo, ~complex (){},
se define el destructor como una función que en
este caso no realiza ninguna acción.
40. Ejemplo de una clases para puntos, segmentos,
cuadriláteros.
#include <iostream.h>
//clase punto
class Point
{ public:
int x,y; //datos tipo entero
Point (void){}
//constructor sin argumento y vacio
(punto origen)
Point (int a, int b) {x=a; y=b;}
//constr.con dos argumentos enteros
Point (int a) { x=a; y=a;}
//constr.con un argum.(punto diagonal)
~Point (){} //Destructor
};
41. Ejemplo de una clases para puntos, segmentos,
cuadriláteros.
//clase segmento
class Segment
{
public:
Point P1,P2; // datos tipo punto
Segment (void) {}
// constructor sin argumento y vacio
Segment (Point A1, Point
A2){P1=A1; P2=A2;}
//constr. dos argumentos
~Segment //destructor
};
42. Ejemplo de una clases para puntos, segmentos,
cuadriláteros.
//clase cuadrado
class Quad
{
public:
Segment S1,S2;
// datos tipo segmento
Quad (Segment A1, Segment
A2) {S1=A1;S2=A2;}
//constr. dos argum.
~Quad //destructor
};
43. Ejemplo de una clases para puntos, segmentos,
cuadriláteros.
//programa principal
int main (void)
{
Point Ori (0);
//objetos de la clase punto. Punto
origen (0,0)
Point P1 (1);//Punto (1,1)
Point P2 (2,5);//Punto (2,5)
Segment S1 (Ori, P1);
//objetos de la clase segmento
Segment S2 (P1, P2);
Quad C (S1,S2);//objeto de la
clase cuadrado
return 0;
}
44. Ejemplo: Concepto matriz mediante una clase.
#include <iostream.h>
#define MAX_SIZE 25
class Matrix
{ public:
// ***************************** datos
int size; double s[MAX_SIZE];
// ***************************** operaciones
// constructores
Matrix () : size(0) {}
Matrix (int m)
{ if (m>MAX_SIZE)
{ cerr << "la dimension maxima es
"
<< MAX_SIZE << endl;
m=MAX_SIZE;}
size=m; for (int i=0; i<size; i++) s[i]=0.;
for (i=0; i<size; i++)
{cout << endl << "coef (" << i <<
")="; cin >> s[i];} }
~Matrix() { size=0; }
void Suma_de_Matrices (Matrix B);};
45. Ejemplo: Concepto matriz mediante una clase.
// funcion suma de matrices
void Matrix :: Suma_de_Matrices (Matrix A)
{ if (A.size != size)
{ cerr << "suma
con dimensiones diferentes.
Error" << endl;
return;}
for (int i=0; i<size; i++)
cout << "suma(" << i << ")="
<< s[i] + A.s[i] <<
endl;
return; }
// el programa ejecutable
int main()
{ Matrix A(3);
Matrix B(3);
A.Suma_de_Matrices (B);
return 0; }
46. Ejemplo: Encapsulamiento private, constructor
copia y redifinición de operadores.
class complex
{ private: double real, imag;
public: complex (double a, double b)
//constructor
{ real=a ; imag =b;}
~complex () {} //destructor
complex (complex& a) {} //constructor copia
{ real=a.get_real(); imag=a.get_imag();}
complex& operator = (complex& m) //asignación
{ real=m.get_real(); imag=m.get_imag(); return
*this;}
double get_real (void) {return real;}
//acceso al encapsulamiento
double get_imag (void) {return imag;}
friend ostream& operator << (ostream& os,
complex& a)
{ //redefinición del operador << os << "Es un
Complex=";
os << a.get_real() <<"+"<< a.get_imag ()
<<"i"<<endl;
return os;} };
47. Niveles de acceso a los miembros de una clase:
1. Public: Público, accesibles por métodos de cualquier
clase.
2. Protected: Protegido, accesible sólo por métodos de
su clase y descendientes.
3.. Private: Privado, sólo accesible por métodos de su
clase.
Se debe destacar que por defecto, el acceso a los
miembros de una clase es privado, es decir, no son
accesibles desde el exterior de la clase. Los miembros
privados de una clase están encapsulados y su
modificación y uso se realiza a través de los métodos
de la propia clase. Por tanto, si desea modificar el valor
de las variables private se deben definir métodos de
acceso para operar con ellas. La llamada a estos
métodos se realiza mediante el operador de acceso (.),
a.get_real( ) (sería el acceso a la parte real del objeto a).
48. Funciones amigas
Dentro de una clase podemos definir una función
amiga (friend) que permita compartir los datos o
funciones con otras clases, es decir que permita
acceder a sus miembros privados. En este tipo de
declaración, la clase es quien establece sus
amigos y limita aquellos que pueden acceder a sus
miembros privados. De este modo ninguna función
se puede autodefinir como amiga y acceder a la
privacidad de una clase sin tener conocimiento de
ella dentro de la propia clase. Con esto se asegura
que continue la seguridad y protección que
proporcionan las clases. friend ostream& operator
<< (ostream& os, complex& a) Esta función
permite utilizar el operador << de escritura en
stream para escribir un formato de salida de datos
de la propia clase. Nótese que las funciones amigas
no pertenecen a ninguna clase