De los TADs a los Objetos

Anuncio
De los TADs a los Objetos
Andrés Becerra Sandoval
28 de julio de 2014
1
TAD frac
Los Tipos Abstractos de Datos modelan conjuntos de datos, sus invariantes y las operaciones permitidas
sobre ellos. Un ejemplo es el TAD frac para representar números racionales:
TAD frac
num den
invariante: num y den son enteros, den6=0
crear: entero x entero . frac
suma: frac x frac . frac
producto: frac x frac . frac
comparar: frac x frac . entero
Su cabecera o interfaz, escrita en C, se encuentra a continuación:
frac.h
#i f n d e f FRAC H
#d e f i n e FRAC H
typedef struct f r a c f r a c ;
struct f r a c {
int num ;
int den ;
};
f r a c c r e a r ( int num , int den ) ;
void p r i n t F r a c ( f r a c f ) ;
f r a c suma ( f r a c a , f r a c b ) ;
f r a c producto ( f r a c a , f r a c b ) ;
int comparar ( f r a c a , f r a c b ) ;
#endif
Y su implementación es:
1
frac.c
#include<f r a c . h>
#include<s t d i o . h>
int e u c l i d e s ( int a , int b ) {
i f ( a%b==0) return b ;
else
return e u c l i d e s ( b , a%b ) ;
}
f r a c c r e a r ( int num , int den ) {
frac res ;
int mdc ;
mdc = e u c l i d e s (num , den ) ;
r e s . num = num/mdc ;
r e s . den = den /mdc ;
return r e s ;
}
void p r i n t F r a c ( f r a c f ) {
p r i n t f ( ”%d/%d” , f . num , f . den ) ;
}
f r a c suma ( f r a c a , f r a c b ) {
int num , den ;
num = a . num ∗ b . den + a . den ∗ b . num ;
den = a . den ∗ b . den ;
return c r e a r (num , den ) ;
}
f r a c producto ( f r a c a , f r a c b){
int num , den ;
num = a . num ∗ b . num ;
den = a . den ∗ b . den ;
return c r e a r (num , den ) ;
}
int comparar ( f r a c a , f r a c b ) {
int x , y ;
x = a . num ∗ b . den ;
y = a . den ∗ b . num ;
i f ( x<y )
return −1;
i f ( x==y )
return 0 ;
i f ( x>y )
return 1 ;
}
2
Una pequeña prueba del TAD frac es:
main.c
#include <s t d i o . h>
#include <f r a c . h>
int main ( ) {
f r a c uno , dos , t r e s ;
p r i n t f ( ”Empezando\n” ) ;
uno = c r e a r ( 2 , 3 ) ;
dos = c r e a r ( 1 , 4 ) ;
t r e s = suma ( uno , dos ) ;
p r i n t F r a c ( uno ) ; p r i n t f ( ”+” ) ; p r i n t F r a c ( dos ) ;
p r i n t f ( ”=” ) ; p r i n t F r a c ( t r e s ) ;
t r e s = p r o d u c t o ( uno , dos ) ;
p r i n t f ( ” \n” ) ;
p r i n t F r a c ( uno ) ; p r i n t f ( ”∗ ” ) ; p r i n t F r a c ( dos ) ;
p r i n t f ( ”=” ) ; p r i n t F r a c ( t r e s ) ;
p r i n t f ( ” \n” ) ;
p r i n t f ( ”%d” , comparar ( uno , dos ) ) ;
return 0 ;
}
2
Empezando a usar C++
El lenguaje C++ expande las capacidades lenguaje C. El TAD frac puede declararse e implementarse en
C++ como sigue:
3
frac.h
#i f n d e f FRAC H
#d e f i n e FRAC H
using namespace s t d ;
int e u c l i d e s ( int a , int b ) {
i f ( a%b==0) return b ;
else
return e u c l i d e s ( b , a%b ) ;
}
struct f r a c {
int num ;
int den ;
// c o n s t r u c t o r
f r a c ( int num , int den ) {
int mdc = e u c l i d e s (num , den ) ;
this−>num = num/mdc ;
this−>den = den /mdc ;
}
void p r i n t F r a c ( ) {
c o u t << num << ” / ” << den ;
}
f r a c suma ( f r a c o ) {
int n = this−>num ∗ o . den + den ∗ o . num ;
int d = this−>den ∗ o . den ;
return f r a c ( n , d ) ;
}
f r a c producto ( f r a c o ){
int num , den ;
num = this−>num ∗ o . num ;
den = this−>den ∗ o . den ;
return f r a c (num , den ) ;
}
bool comparar ( f r a c o ) {
int x , y ;
x = num ∗ o . den ;
y = den ∗ o . num ;
return x==y ;
}
};
#endif
4
Observe que en C++:
• las operaciones pueden estar adentro del struct
• las operaciones se llaman con la sintaxis v.operacion(argumentos), donde v es una variable del tipo del
struct. Esto se verá en la implementación mas adelante
• En las operaciones suma, producto y comparar no usamos dos parámetros frac, porque la variable
actual, de tipo frac, ya tiene un numerador y un denominador. Este se opera con el otro frac que llega
como parámetro.
• Cuando se define suma con un solo parámetro o, se toma el numerador y denominador de quien esté
haciendo el llamado y se operan con o.num y o.den.
• this es un puntero que, dentro de una operación en un struct, hace referencia a quien esté haciendo el
llamado
• hay una operación frac, con el mismo nombre del struct (un constructor!)
• existen espacios de nombres (using namespace std;)
• la salida se hace con cout, no con print
• existe el tipo de dato bool
La prueba, escrita en C++ queda ası́:
5
main.cc
#include <i o s t r e a m >
#include <f r a c . h>
using namespace s t d ;
int main ( ) {
c o u t << ”Empezando” << e n d l ;
f r a c uno ( 2 , 3 ) ;
f r a c dos ( 1 , 4 ) ;
frac tres (0 ,1);
t r e s = uno . suma ( dos ) ;
uno . p r i n t F r a c ( ) ; c o u t << ”+” ; dos . p r i n t F r a c ( ) ;
c o u t << ”=” ; t r e s . p r i n t F r a c ( ) ;
c o u t << e n d l ;
t r e s = uno . p r o d u c t o ( dos ) ;
uno . p r i n t F r a c ( ) ; c o u t << ” ∗ ” ; dos . p r i n t F r a c ( ) ;
c o u t << ”=” ; t r e s . p r i n t F r a c ( ) ;
c o u t << e n d l ;
c o u t << ”Es ” ; uno . p r i n t F r a c ( ) ;
c o u t << ” i g u a l a ” ;
dos . p r i n t F r a c ( ) ; c o u t << ” ? ” << e n d l ;
c o u t << uno . comparar ( dos ) ;
return 0 ;
}
Observe que:
• cuando se declara una variable de tipo frac, se llama la operación frac (el constructor)
• todas las variables de tipo frac tienen adentro todas las operaciones. Cuando se llama uno.suma(dos)
la variable uno usa su operación suma, con el argumento dos. Este argumento va a reemplazar el
parámetro o, en la definición de suma.
• Cuando se llama a uno.suma(dos), el puntero this hace referencia la variable uno.
3
Separando el contrato de la implementación
Nuestro TAD frac queda ahora separado en:
6
frac.h
#i f n d e f FRAC H
#d e f i n e FRAC H
struct f r a c {
int num ;
int den ;
f r a c ( int num , int den ) ;
void p r i n t F r a c ( ) ;
f r a c suma ( f r a c o ) ;
f r a c producto ( f r a c o ) ;
bool comparar ( f r a c o ) ;
};
#endif
La implementación, en la siguiente página, usa el operador :: que identifica a que struct pertenece una
función declarada en una cabecera. Este operador es el de resolución de ámbito.
Aparte del uso del operador ::, el código es el mismo que en la versión anterior.
7
frac.cc
#include<f r a c . h>
#include<i o s t r e a m >
using namespace s t d ;
int e u c l i d e s ( int a , int b ) {
i f ( a%b==0) return b ;
else
return e u c l i d e s ( b , a%b ) ;
}
f r a c : : f r a c ( int num , int den ) {
int mdc ;
mdc = e u c l i d e s (num , den ) ;
this−>num = num/mdc ;
this−>den = den /mdc ;
}
void f r a c : : p r i n t F r a c ( ) {
c o u t << num << ” / ” << den ;
}
f r a c f r a c : : suma ( f r a c o ) {
int n = this−>num ∗ o . den + den ∗ o . num ;
int d = this−>den ∗ o . den ;
return f r a c ( n , d ) ;
}
f r a c f r a c : : producto ( f r a c o ){
int num , den ;
num = this−>num ∗ o . num ;
den = this−>den ∗ o . den ;
return f r a c (num , den ) ;
}
bool f r a c : : comparar ( f r a c o ) {
int x , y ;
x = num ∗ o . den ;
y = den ∗ o . num ;
return x==y ;
}
8
4
Mas detalles de C++
• Un struct es casi idéntico a una clase en C++.
• Algunos elementos de una clase pueden declararse públicos y otros privados, esto permite el encapsulamiento
• Una operación puede definirse de varias formas, variando los parámetros, esto permite el polimorfismo
(un nombre, varias implementaciones)
• A los operadores del lenguaje como a << y == se le pueden dar nuevos significados por medio de
operaciones, esto permite la sobrecarga
frac.h
#i f n d e f FRAC H
#d e f i n e FRAC H
#i n c l u d e <i o s t r e a m >
class frac {
private :
int num ;
int den ;
public :
frac ();
f r a c ( int n ) ;
f r a c ( int num , int den ) ;
friend s t d : : ostream& operator<<(s t d : : ostream& os , const f r a c & f ) ;
f r a c suma ( f r a c o ) ;
f r a c producto ( f r a c o ) ;
bool operator== ( const f r a c& o ) const ;
};
#endif
La implementación:
• sobrecarga los operadores antes mencionados
• usa ostream, un flujo de salida
• utiliza el operador de paso por referencia (&), una caracterı́stica que no existe en el lenguaje C
9
frac.cc
#include<f r a c . h>
#include<i o s t r e a m >
using namespace s t d ;
int e u c l i d e s ( int a , int b ) {
i f ( a%b==0) return b ;
else
return e u c l i d e s ( b , a%b ) ;
}
frac : : frac (){
num = 0 ;
den = 1 ;
}
f r a c : : f r a c ( int n ) {
num = n ;
den = 1 ;
}
f r a c : : f r a c ( int num , int den ) {
int mdc ;
mdc = e u c l i d e s (num , den ) ;
this−>num = num/mdc ;
this−>den = den /mdc ;
}
s t d : : ostream& operator<<(s t d : : ostream& os , const f r a c & f ) {
o s << f . num << ” / ” << f . den ;
return o s ;
}
f r a c f r a c : : suma ( f r a c o ) {
int n = this−>num ∗ o . den + den ∗ o . num ;
int d = this−>den ∗ o . den ;
return f r a c ( n , d ) ;
}
f r a c f r a c : : producto ( f r a c o ){
int num , den ;
num = this−>num ∗ o . num ;
den = this−>den ∗ o . den ;
return f r a c (num , den ) ;
}
bool f r a c : : operator== ( const f r a c& o ) 10
const {
int x , y ;
x = num ∗ o . den ;
y = den ∗ o . num ;
return x==y ;
}
La prueba del TAD frac ahora permite que variables de tipo frac puedan ser operadas con los operadores
<< y ==. Además usa las tres versiones del constructor con 0, 1 y 2 argumentos.
main.cc
#include <i o s t r e a m >
#include <f r a c . h>
using namespace s t d ;
int main ( ) {
c o u t << ”Empezando” << e n d l ;
f r a c uno ( 1 , 4 ) ;
f r a c dos ( 2 ) ;
frac tres ();
c o u t << ”Es ” << uno
<< ” i g u a l a ” <<
dos << ” ? ” << ( uno==dos ) ;
return 0 ;
}
5
No se preocupe
Si no ha comprendido bien la implementación del TAD frac en C++, vamos a tener tiempo durante todo
el semestre. Lo que si debe hacer es compilar y ejecutar las diferentes implementaciones del TAD para las
secciones anteriores y dejar que la curiosidad lo invada!
El código está en:
http://cic.javerianacali.edu.co/~abecerra/files/tadFrac.tar.gz
sección carpeta
1
frac
2
frac++0
3
frac++1
4
frac++2
También, ejecute la implementación del TAD frac en python (probado con la versión 2.7, no con python
3). Allı́ el parámetro self hace las veces del puntero this en C++.
11
fraccion.py
# −∗− c o d i n g : u t f −8 −∗−
class frac :
def
i n i t ( s e l f , num , den =1):
s e l f . num = num
s e l f . den = den
str ( self ):
def
return ”%d/%d” % ( s e l f . num , s e l f . den )
def
mul ( s e l f , otro ) :
i f type ( o t r o ) == type ( 5 ) :
otro = frac ( otro )
return f r a c ( s e l f . num ∗ o t r o . num , s e l f . den ∗ o t r o . den )
rmul
=
mul
#suma de f r a c e s
def
add ( s e l f , otro ) :
i f type ( o t r o ) == type ( 5 ) :
otro = frac ( otro )
return f r a c ( s e l f . num ∗ o t r o . den + s e l f . den ∗ o t r o . num , s e l f . den ∗ o t r o . den )
radd
=
add
def
i n i t ( s e l f , num , den =1):
g=e u c l i d e s (num , den )
s e l f . num=num / g
s e l f . den=den / g
def
cmp ( s e l f , otro ) :
d i f = ( s e l f . num ∗ o t r o . den −
o t r o . num ∗ s e l f . den )
return d i f
d e f e u c l i d e s (m, n ) :
i f m%n==0:
return n
else :
return e u c l i d e s ( n ,m%n )
a = frac (8 ,4)
b = frac (2 ,3)
print a
print b
p r i n t a+b
p r i n t a ∗b
p r i n t 5∗ a
p r i n t b∗5
p r i n t b+2
12
Conceptos
valor: un patrón de bits que representa algo del mundo real. Puede ser primitivo en el lenguaje de programación o creado por el programador. Ej: 5, 2.1
tipo: un tipo abstracto de datos modela un conjunto de valores, sus invariantes y las operaciones permitidas. Además tiene soporte en el lenguaje de programación. Hay tipos primitivos y creados por el
programador. Ej: int, frac.
clase: tipo definido por el programador. Ej: frac.
objeto: valor cuyo tipo es una clase.
variable: un valor, cargado en la memoria, al que se puede acceder mediante un identificador (nombre).
Tienen un tipo primitivo o creado por el programador.
polimorfismo: usar un nombre único para acceder a varias operaciones diferentes.
encapsulamiento: proteger la estructura interna de los objetos.
sobrecarga: dar varios significados a una misma entidad.
13
Descargar