You are on page 1of 35

POO 2004

P Introducción a la POO en C++


M Algunas diferencias entre C y C++
Objetos, métodos, encapsulado.
T Clases

2
e Métodos
m Constructores y Destructores
a Sobrecarga
Amigos
Entrada y salida en C++
Espacios de nombres
Miembros estáticos
PM. 2. POO (1)

Características de la POO en C++

Respecto a la POO, las características más importantes


de C++ son:
• Herencia múltiple
• Sobrecarga de operadores y funciones
• Derivación
• Funciones virtuales
• Plantillas
• Gestión de excepciones

PM. 2. POO (2)


Pequeñas diferencias C-C++ (1)

Argumentos con valor por defecto

int a(int a=1, int b=2, int c) NO


a(4,5) ??

int a(int c, int a=1, int b=2)


hace que a(1), ó a(1,2) ó a(1,2,3) no sean ambiguos

PM. 2. POO (3)

Pequeñas diferencias C-C++ (2)

Funciones inline frente a macros de preprocesador


supongamos definido
#define cuadrado(x) (x*x)
entonces
void f(double d, int i) {
r = cuadrado(d); // va bien
r = cuadrado(i++); // va mal: significa (i++*i++);
r = cuadrado(d+1); // va mal: significa (d+1*d+1);
// que es, (d+d+1);

iría mejor
#define cuadrado(x) ((x)*(x)) // va mejor

PM. 2. POO (4)


Pequeñas difs. C-C++: funciones inline

Pero sigue sin resolver cuadrado(i++)


Así que C++ aporta una solución:
inline int icuadrado(int x) {
return x * x;
}
que expande C++ (no el procesador), sí resuelve el problema:

es como una función normal pero que se expande


entre las líneas
del código que la llama
PM. 2. POO (5)

Pequeñas difs. C-C++: funciones inline

La ventaja de las funciones inline es que no retrasan con


llamadas a función. Son expresiones que se repinten en
el código y no se quieren convertir en función para no
recargar el número de llamadas a función.

icuadrado(i++) // va bien

PM. 2. POO (6)


C no tiene parámetros por referencia

C C++
void swap(int *pa, int *pb) void swap(int & a, int & b)
{ int t = *pa; *pa=*pb; *pb=t; } { int t = a; a=b; b=t; }

main() main()
{ swap(&x, &y);} { swap(x, y);}

El modificador & en los parámetros formales en C++ indica que


ese parámetro es pasado por referencia. Algo que en C es
imposible. En C no existía el paso por referencia, había que
pasar la dirección (operador &) de la variable.

PM. 2. POO (7)

Operadores new y delete

int *pint, *parr;


pint = new int;
parr = new int[10]
// …
delete pint;
delete[] parr;

Son operadores y por lo tanto el compilador interviene


"inteligentemente" decidiendo el tipo de solicitud de memoria
dinámica y el tamaño necesitado.
Esto es más adecuado en la creación de objetos
PM. 2. POO (8)
Operadores new y delete, ventajas

• No hace falta calcular los tamaños con sizeof():


pa = new int; // malloc(sizeof(int))

• El propio delete se encarga de poner a 0 (NULL)


el puntero y new de poner a 0 la memoria
adquirida
delete pint; // deja 0 modificando pint
// antes era: free(pint); pint=NULL;

PM. 2. POO (9)

Equivalencia entre new y malloc

truco en C
#define NEW(X) (X *) malloc(sizeof(X))
pa = NEW(X);

equivale en C++ a
pa = new X;

PM. 2. POO (10)


Classes

• La mayor diferencia entre C y C++ está en


soporte que este último ofrece para las clases
• El nombre original de C++ era "C with Classes"
• Esencialmente una clase es un tipo abstracto de
datos (TAD): colección de datos junto a operadores
para acceder y modificarlos

PM. 2. POO (11)

Clases, uso

Se puede definir una clase "Complejo" y variables


de tipo Complejo:
Complejo c1, c2, c3;
y entonces seguir operando no sólo con funciones
sino también con operadores:
c3 = c1 / c2; c3 = c1 + c2; c3++;…
en la que "/", "+", "++" se han sobredefinido para
operar con Complejo.

PM. 2. POO (12)


Clases, declaración

La declaración es muy parecida a la declaración


de estructuras, de la que son extensión:
class Complejo {
float parteReal;
float parteImaginaria;
};

PM. 2. POO (13)

Clases, declaración

La declaración es muy parecida a la declaración


de estructuras, de la que son extensión:
class Complejo {
float parteReal;
float parteImaginaria;
};

Los campos son ahora llamados

PM. 2. POO (13)


Clases, declaración

La declaración es muy parecida a la declaración


de estructuras, de la que son extensión:
class Complejo {
float parteReal;
float parteImaginaria;
};

Los campos son ahora llamados


atributos
PM. 2. POO (13)

Clases, declaración

La declaración es muy parecida a la declaración


de estructuras, de la que son extensión:
class Complejo {
float parteReal;
float parteImaginaria;
};

Los campos son ahora llamados


atributos
de la clase.
PM. 2. POO (13)
Clases, uso

Una vez declarada la clase, se pueden crear


objetos de esa clase:

Complejo c1, c2;

no es necesario decir Class Complejo c1…


Los objetos c1 y c2 son instancias de la clase
Complejo
Una instancia de cualquier clase se denomina
objeto
PM. 2. POO (14)

Los atributos están ocultos


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
defecto son inaccesibles, de manera que las siguientes
acciones son ilegales:
c1.parteReal = 0.0; // ilegal, no accesible
im = c2.parteImaginaria; // ilegal, no accesible

PM. 2. POO (15)


Los atributos están ocultos
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
defecto son inaccesibles, de manera que las siguientes
acciones son ilegales:
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.

PM. 2. POO (15)

Atributos public

Si queremos permitir el class Complejo {


acceso desde fuera a public:
float parteReal;
los atributos de una
float parteImaginaria;
clase hay que
};
declararlos públicos:

class Complejo {
Podemos combinar public:
partes públicas y float parteReal;
privadas de una private:
clase: float parteImaginaria;
};
PM. 2. POO (16)
Miembros

Si no son visibles los miembros privados de una clase…


¿cómo se pueden modificar o ver sus valores?
La respuesta es simple:

PM. 2. POO (17)

Miembros

Si no son visibles los miembros privados de una clase…


¿cómo se pueden modificar o ver sus valores?
La respuesta es simple:

Las funciones que necesiten acceder a los


datos miembros de una clase deben estar
declarados
dentro de la misma clase

PM. 2. POO (17)


Miembros

Si no son visibles los miembros privados de una clase…


¿cómo se pueden modificar o ver sus valores?
La respuesta es simple:

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.
PM. 2. POO (17)

Diferencia con las estructuras

En resumen
• Para controlar el uso de la información que
determina el estado de un objeto se deben usar
métodos
• Un objeto tiene un estado interno sólo
reconocible por las respuestas a las llamadas a
sus métodos
• Para modificar el estado de un objeto se debe
llamar a sus métodos de acceso

PM. 2. POO (18)


Ejemplo de métodos

class Complejo {
public:
void crear(int preal, int pimag);
void print();
private:
float parteReal;
float parteImaginaria;
};
PM. 2. POO (19)

Métodos públicos y privados

Vemos que los métodos para pueden ser


declarados:

• en la parte pública: son la interfaz de uso


• en la parte privada: procedimientos internos
necesarios para el funcionamiento del
objeto.

PM. 2. POO (20)


Ejemplo

class Fraccion {
public:
interfaz de uso
void crear(int num, int denom);
void print();
private:
procedimientos
internos void reduce();
atributos int numerador, denominador;
}

PM. 2. POO (21)

Métodos especializados

• En la mayoría de las clases es necesario dar


valores iniciales a sus atributos:
– Constructor

• Liberar memoria, cerrar canales, etc. al


destruirlo:
– Destructor

PM. 2. POO (22)


Métodos especializados

• Constructor
– Sería el miembro público cuyo nombre es el mismo
que el de la clase, sin devolver nada.
– Puede llevar parámetros
• Destructor
– Sería el miembro público cuyo nombre es el mismo
que el de la clase con un ~ delante, sin devolver nada,
ni void
• Constructor de copia
– Sería el miembro público cuyo nombre es el mismo
que el de la clase y cuyo único argumento es un
objeto de la clase en modo const &
PM. 2. POO (23)

Constructor

• Constructor
– Sería el miembro público cuyo nombre es el mismo
que el de la clase, sin devolver nada.
– Puede llevar parámetros
– Puede haber varios con distintas signaturas.
– El Constructor de copia es uno de los posibles

pueden inicializar directamente los atributos con la


notación
: atrib1(parám1), atrib2(parám2)
que se pone tras la declaración.
PM. 2. POO (24)
Ejemplo: complejo.h

class Complejo {
public: constructor automático

Complejo(int re=0, int im=1) :


real(re), imag(im) { }; equivale a
interfaz de uso
{real=re; imag=im;}
void print() const; // no modif. objeto
void pon(const int re=0, const int im=1);
private:
atributos int real, imag;
};
Complejo c1(3,2), c2;
PM. 2. POO (25)

Constructor, casos

• Ejemplos de uso de Constructor


Según encajen pueden ser llamados un
constructor u otro.

Complejo c(10, -2), c2(5,4), c3[8];

nótese que al tenerse valores por defecto, cuando no se ponen


parámetros como en el caso de los 8 elementos del array de
Complejo c3, todos toman el valor, los valores por defecto.

PM. 2. POO (26)


Cuerpo de los métodos

La mayoría de los métodos no se desarrollan en


línea, como los constructores sino que el
desarrollo (los cuerpos de los métodos) se
desarrollan en el fichero pareja de la cabecera .h y
que sería .cpp
.h
.cpp

en él se accede a los miembros de la clase


mediante la notación de ámbito
nombreClase::
PM. 2. POO (27)

Cuerpo de los métodos

.h

class Nave {
public:
Nave(const int x=0, const int y=0) {X=x;Y=y;};
Mover(const int dx, const int dy);
...

} Nave:: Mover(const int dx, const int dy) {


X += dx;
.cpp Y += dy;
}

PM. 2. POO (28)


Ejemplo: complejo.cpp

#include <iostream.h>
#include "complejo.h" // siempre al final

Complejo::pon(const int re, const int im) {


real = re;
imag = im;
}
Complejo::print() {
cout << real <<"+ i "<<imag;
}

PM. 2. POO (29)

Uso de una clase

Para usar una clase bastará entonces importar su


descripción (la declaración de la clase que está en
la cabecera .h) y crear objetos, etcétera, en el
programa:

PM. 2. POO (30)


Uso de una clase

ojo: se importa clase.h tanto en


clase.cpp como en prueba.cpp
clase.h

#include "clase.h" #include "clase.h"

clase.cpp prueba.cpp

PM. 2. POO (31)

Ejemplo: prucomp.cpp

#include <iostream.h>
#include "complejo.h"

llamada a miembro
int main() {
del objeto
Complejo c1;
c1.print(); cout << endl;
c1.pon(2,-3);
c1.print(); cout << endl;
}

PM. 2. POO (32)


Métodos destructor

• Destructor
– Sería el miembro público cuyo nombre es el mismo
que el de la clase con un ~ delante, sin devolver nada,
ni void

el destructor no siempre hace falta, sólo cuando se


llame a rutinas que modifiquen el estado del
sistema y antes de destruir el objeto haya que
reponer ese estado

PM. 2. POO (33)

Ejemplo: destructor en matriz.h

class Matriz {
public:
Matriz(int n, int m) :
N(n), M(m), datos(new int[n*m]) { };
~Matriz(void) { delete[ ] datos; };

int ver(const int fila,const int col);


void pon(const int fila,const int col,const int x);
void dims(int &n, int &m) {n=N;m=M;};
private:
int N, M;
int *datos;
};

PM. 2. POO (34)


Destrucción implícita

La destrucción implícita se llama sóla,


automáticamente, cuando el objeto deja de estar
accesible, por ejemplo, al acabar el ámbito de su
visibilidad

La destrucción puede llamarse explícitamente llamando al método


de destrucción aunque esto último suele ser innecesario

PM. 2. POO (35)

Construcción por copia de objetos

• Constructor por copia


– Sería el miembro público cuyo nombre es el mismo
que el de la clase y cuyo único argumento es un
objeto de la clase en modo const clase&

No siempre hace falta.


Al igual que el destructor, el método de copia hace
falta cuando la creación solicita memoria al
sistema, abre ficheros o cosas así, en general,
modificando el estado externo del sistema.
PM. 2. POO (36)
Construcción por copia de objetos

La construcción por copia se "ejecuta" con la declaraci:


• implícita en la creación:
Matriz m2 = m1;
donde m2, obsérvese que no lleva parámetros (obligados) del
constructor, por hacerse mediante copia.
• explícita en la creación:
Matriz m3(m1);
donde de nuevo, no se usan los parámetros del constructor
automático por llamarse explícitamente al método
constructor por copia

PM. 2. POO (37)

Ejemplo: matriz.h

class Matriz {
public:
Matriz(int n, int m) :
N(n), M(m), datos(new int[n*m]) { };
~Matriz(void) { delete[ ] datos; };
Matriz::Matriz(const Matriz& origen);

int ver(const int fila,const int col);


void pon(const int fila,const int col,const int x);
void dims(int &n, int &m) {n=N;m=M;};
private:
int N, M;
int *datos;
};
PM. 2. POO (38)
Ejemplo: matriz.cpp
Matriz::Matriz(const Matriz& origen) {
int i, j;
N = origen.N; M = origen.M;
datos = new int[N*M];
for (i=0;i<N;i++)
for (j=0;j<M;j++)
pon(i,j, origen.ver(i,j));
}

int Matriz::ver(const int fil,const int col) {


return datos[fil*M + col];
}
void Matriz::pon(const int fil,const int col,
const int x) {
datos[fil*M + col] = x;
}
PM. 2. POO (39)

Ejemplo: prumatriz.cpp

int main()
{
Matriz m(10, 20);

m.pon(2,2,1);
printf("en m 2,2: %d\n", m.ver(2,2));

Matriz mm = m; // actúa el constructor de copia implícito


printf("en mm 2,2: %d\n", mm.ver(2,2));

Matriz mmm(m); // constructor de copia explícita


printf("en mmm 2,2: %d\n", mmm.ver(2,2));

return 0;
}
PM. 2. POO (40)
Copia de objetos
La iniciación de objetos durante la construcción admite pues diversas
formas:

ejemplo:
class T {
public:
T(): dato(valor), ... {}; // Creador sin parámetros
T(tipo uno): dato(uno), ... {}; // Creador con un parámetro
T(const T& de): dato(de.dato), ... {}; // constructor por copia
....

pero una operación como:


...
t1 = t2;
...
hará sólo la copia de los atributos, y no será correcta en general

PM. 2. POO (41)

Peligro de la copia de objetos en parámetros

Peor aún, si hacemos una llamada a una función externa


como:
...
pinta(t1);
...
siendo

void pinta(const T t1)

los resultados serán desastrosos.

¿Por qué?

PM. 2. POO (42)


Peligro de la copia de objetos en parámetros

El motivo es que pinta(t1) construye una copia de t1 para el


parámetro local de pinta y no sólo hace esa copia sino
que al acabar pinta() destruye esa copia.

Una solución es hacer

void pinta(const T& t)

• sea, declarar el parámetro como referencia constante

– No se copia
– No se cambia

PM. 2. POO (43)

Copia de objetos en parámetros

Al recibirse el objeto por referencia no hay


destrucción automática al acabar el procedimiento
y no habrá sorpresas en los casos de memoria
dinámica enlazada.

void pinta(const T& t)

PM. 2. POO (44)


Copia por asignación de objetos en general

Pero el problema de la copia es más general: sólo


hemos visto como evitarlo durante la creación por
copia y durante el paso de parámetros.

Para que la operación = funcione correctamente con


nuestros objetos particulares hace falta hará falta
sobrecargar el operador asignación con nuestro
algoritmo particular de copiado.

T& operator= (const T& orig);

PM. 2. POO (45)

Copia por asignación de objetos en general

ejemplo:

class T {
public:
T(); // Creador sin parámetros
T(tipo uno); // Creador con un parámetro
T(const T& de); // constructor por copia

T& operator= (const T& de); // operador de copia


....

PM. 2. POO (46)


Miembros amigos

¿Puede ser posible que otra clase acceda a los miembros de


una clase?: Sí

permiso garantizado

class Una { class Otra {


friend class Otra; void metodo2(Una u) {
private: u.atri = 33;
int atri; u.metodo();
void metodo(); }
}; };

PM. 2. POO (47)

Sobrecarga de <<

Si se define la clase << como amiga

friend ostream& operator<<(ostream &os, const Text& orig);

y se desarrolla como por ejemplo:


ostream& operator<<(ostream &os, const Text& orig)
{
os << orig.s; // accede a su parte privada
return os;
}

PM. 2. POO (48)


class Text {
public:
Text(const char* txtPtr);
Text() : s(0) {};
~Text();
Text(const Text& orig);
Ejemplo +completo

bool operator== (const Text& derecho) const;


char operator[ ] (unsigned long indice) const;
unsigned long Longitud() const;
void pinta(const bool nl = true) const;
friend Text operator+ (const Text& ls, const Text& rs);
operator const char*() const;
Text& operator= (const Text& orig);
Text& operator= (const char *str);
friend ostream& operator<<(ostream &os, const Text& orig);
private:
char *s; // la cadena C
unsigned long l;
unsigned long strlen(const char *str) const;
void strasigna(const char *desde);
bool strcmp(const char *con) const;
}; PM. 2. POO (49)

class Text {
public:
Text(const char* txtPtr); uso:
Text() : s(0) {};
~Text();
Text(const Text& orig);
bool operator== (const Text& derecho) const; t1 == t2
char operator[ ] (unsigned long indice) const; t1[33]
Ejemplo +completo

unsigned long Longitud() const;


void pinta(const bool nl = true) const;
friend Text operator+ (const Text& ls, const Text& rs); t3 = t1 + t2
operator const char*() const; (char*) (t2)
Text& operator= (const Text& orig);
t1 = t2
Text& operator= (const char *str); t1 = "abc"
friend ostream& operator<<(ostream &os, const Text& orig); cout << t1;
private:
char *s; // la cadena C
unsigned long l;
unsigned long strlen(const char *str) const;
void strasigna(const char *desde);
bool strcmp(const char *con) const;
}; PM. 2. POO (50)
#include <iostream.h> bool Text::operator== (const Text& derecho)
#include "text.h" const {
if (derecho.l == 0 && 0 == l)
// Constructor desde cadena C return true;
Text::Text(const char* txtPtr) : s(0), l(0) { else
if (txtPtr != 0) return strcmp(derecho.s);
strasigna(txtPtr); }
Ejemplo +completo

} char Text::operator[] (unsigned long indice) const


// Constructor de copia {
Text::Text(const Text& orig) : s(0), l(orig.l) { if (0 <= indice && indice < l)
if (orig.l != 0) return s[indice];
strasigna(orig.s); else
} return '\0';
// destructor }
Text::~Text() { // averigua la longitud
delete[] s; unsigned long Text::Longitud() const {
} return l;
}
// Adapta el tipo (cast) a cadena C
Text::operator const char*() const {
return s;
}

PM. 2. POO (51)

// Operador de asignación unsigned long Text::strlen(const char *str) const {


Text& Text::operator= (const Text& orig) { unsigned long n = 0;
// evita a = a; while (str[n])
if (this == &orig) n++;
return *this; return n;
if (l != 0) { }
void Text::strasigna(const char *desde) {
delete[] s;
unsigned long i = 0;
l = 0;
Ejemplo +completo

l = strlen(desde);
}
s = new char[l+1];
if (orig.l)
while (s[i] = desde[i])
strasigna(orig.s);
i++;
return *this; }
} bool Text::strcmp(const char *con) const
// Operador de asignación desde una str {
Text& Text::operator= (const char *str) { unsigned long i = 0;
if (l != 0) { while (s[i] && con[i] && s[i] == con[i])
delete[] s; i++;
l = 0; return s[i] == '\0' && con[i] == '\0';
} }
if (str != 0)
strasigna(str);
return *this;
}

PM. 2. POO (52)


// amigo que permite sobrecargar '+' para concatenar Texts
Text operator+ (const Text& ls, const Text& rs) {
Text ret;
unsigned long i=0, ii=0;
ret.l = ls.l + rs.l;
ret.s = new char[ret.l+1];
Ejemplo +completo

ret.s[0] = '\0';
if (ls.s != NULL) {
while (ret.s[i] = ls.s[i])
i++;
ii = i-1;
i = 0;
}
if (rs.s != NULL)
while (ret.s[ii] = rs.s[i])
i++; ii++;
return ret;
}
ostream& operator<<(ostream &os, const Text& orig) {
os << orig.s; return os; }
PM. 2. POO (53)

espacios de nombres

Una declaración accesible en todo el programa es


una declaración global.

La colección de todas las declaraciones se


denomina espacio de nombres global.

PM. 2. POO (54)


using namespace
C++, sin embargo, proporciona a los programadores la
posibilidad de situar las declaraciones en espacios de
nombres definidos por el programa.

namespace nombre {
void f() { cout << desde f en nombre<<endl;
};

la llamada hay que hacerla:

nombre::f();

excepto que se ponga antes:

using namespace nombre;


PM. 2. POO (55)

librerías con namespace

Las cabeceras que utilizan el espacio de nombres


global suelen acabarse como clásicamente: .h

Las librerías que sitúan sus declaraciones dentro


de espacios de nombres (no globales) no usan
extensión.

Por ejemplo: <iostream> es un archivo de


cabecera cuyas declaraciones están en el
espacio de nombres std
PM. 2. POO (56)
librerías con namespace

Por eso se suele hacer:


#include <iostream>
#include <iomanip>
using namespace std;
int main () {
cout << setw (10);
cout << 77 << endl;
return 0;
}
PM. 2. POO (57)

Miembros estáticos

PM. 2. POO (58)


Miembros estáticos
Se pueden definir miembros (atributos o métodos) con el
atributo static:

PM. 2. POO (58)

Miembros estáticos
Se pueden definir miembros (atributos o métodos) con el
atributo static:

• Tal miembro es común a todos los objetos de tal clase.

PM. 2. POO (58)


Miembros estáticos
Se pueden definir miembros (atributos o métodos) con el
atributo static:

• Tal miembro es común a todos los objetos de tal clase.


• Tales miembros existen aunque no haya ningún objeto
de tal clase

PM. 2. POO (58)

Miembros estáticos
Se pueden definir miembros (atributos o métodos) con el
atributo static:

• Tal miembro es común a todos los objetos de tal clase.


• Tales miembros existen aunque no haya ningún objeto
de tal clase
• Para llamarlos se puede usar un objeto cualquiera de
los de la clase o usar el especificador de ámbito
Clase::metod()

PM. 2. POO (58)


Miembros estáticos
Se pueden definir miembros (atributos o métodos) con el
atributo static:

• Tal miembro es común a todos los objetos de tal clase.


• Tales miembros existen aunque no haya ningún objeto
de tal clase
• Para llamarlos se puede usar un objeto cualquiera de
los de la clase o usar el especificador de ámbito
Clase::metod()
• Pueden servir, por ejemplo, para mantener información
global, como la cuenta de todos los objetos. No son
datos globales, ya que mantienen los criterios de
visibilidad.
PM. 2. POO (58)

FIN

PM. 2. POO (59)

You might also like