Lenguajes de Programación Capítulo 2. Valores y Tipos

Anuncio
Lenguajes de Programación
Capítulo 2.
Valores y Tipos
Carlos Ureña Almagro
Curso 2011-12
Contents
1 Introducción
2
2 Tipos Primitivos
6
2.1
Tipo Lógico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
2.2
Caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
2.3
Naturales y Enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
2.4
Reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
2.5
Tipos enumerados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
3 Tipos Compuestos
14
3.1
Producto Cartesiano . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
3.2
Unión y unión disjunta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
3.3
Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
4 Tipos recursivos
30
4.1
Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
4.2
Árboles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
32
5 Equivalencia de tipos
34
1
LP (11-12) 2. Valores y Tipos
6 El principio de completitud de tipos
creado October 10, 2011
35
página 2.2 / 35
LP (11-12) 2. Valores y Tipos
1
Introducción
El concepto de Valor.
Un valor es una entidad que:
• Obtenemos como resultado de evaluar una expresión, o
• Podamos almacenar, o
• Incorporar a una estructura de datos, o
• Pasar como argumento a subprogramas, o
• Obtener como resultado de una función
El concepto de Valor.
En definitiva, un valor es cualquier entidad que pueda ser procesada de alguna manera mediante las instrucciones que cada lenguajes de programación incorpore.
Ejemplos. Valores en Pascal (1)
• Valores primitivos (lógicos, caracteres, enumerados, enteros y reales)
• Valores compuestos (registros, arrays, conjuntos y ficheros)
• Punteros
Ejemplos. Valores en Pascal (2)
• Referencias a variables (únicamente como parámetros a subprogramas)
• Subprogramas (procedimientos y funciones)
Ejemplos. Valores en C.
• Valores primitivos (caracteres, enumerados, enteros y reales)
• Valores compuestos (registros, arrays)
• Punteros
• Subprogramas (funciones)
creado October 10, 2011
página 2.3 / 35
LP (11-12) 2. Valores y Tipos
El concepto de tipo
Para hacer más tratable el conjunto de todos los valores posibles en cada lenguaje, clasificamos los valores
en tipos.
En principio, un tipo es, por tanto, un conjunto de valores.
Sin embargo, y como veremos, un tipo es algo más.
El concepto de tipo
Normalmente, la agrupación de valores para formar tipos se hace teniendo en cuenta:
• un modelo matemático común
• la existencia de un conjunto común de operaciones aplicables a todos los valores del tipo.
• la existencia de un método común para asociar una representación como secuencia de bits a cada
elemento del conjunto.
Elementos de un tipo
Un tipo es un conjunto de valores con:
• un modelo matemático asociado (un conjunto S)
• un conjunto de operaciones bien definido, cada una de ellas con un modelo matemático asociado (una
función)
• un conjunto de algoritmos que operan sobre las secuencias e implementan las operaciones anteriores.
• una función (matemática) de representación que asocia secuencias de bits a los valores del conjunto S
Representación de valores
Para cada tipo A existirá una función matemática, biunívoca, bien definida, que asocia, a cada valor posible,
su representación como una única secuencia finita de dígitos binarios.
Representación de valores
• A la función se le llamará repr A , al conjunto de representaciones posibles se le llamará R A
• La función es biyectiva entre A y R A
creado October 10, 2011
página 2.4 / 35
LP (11-12) 2. Valores y Tipos
Representaciones ambigüas
• Cada valor de A tiene asociado un único valor de R A
• Una representación que no cumple esto es ambigua, ya que existirá algún valor con un conjunto de
representaciones posibles.
Representación de valores
Valores distintos deben tener representaciones distintas:
x 6= y
=⇒
repr A ( x ) 6= repr A (y)
1
Esto implica que existe la función repr −
A , es decir, que la interpretación de cada secuencia de bits en R A
es un único valor de A.
Ejemplo de representación
Números naturales del 0 al 7: Cada uno se puede representar como una secuencia de tres bits, el más
significativo primero.
Ejemplo de Representación.
Números naturales del 0 al 7 (A), y sus correspondientes representaciones (R A )
Número de elementos y tamaño
Sea A un conjunto cualquiera:
• El número de valores distintos en A se nota como car ( A) (por cardinalidad)
• Si las representaciones son todas del mismo tamaño, el número de bytes que tiene cada representación
se nota como tam( A).
creado October 10, 2011
página 2.5 / 35
LP (11-12) 2. Valores y Tipos
Operaciones sobre los valores y su implementación
• Para cada tipo, existirá un conjunto de operaciones matemáticas (funciones) bien definidas, que actúan
sobre dichos valores.
• Cada operación definida sobre los valores se corresponde (biunívocamente) con una operación equivalente definida sobre las representaciones de los valores.
Ejemplo de Operación (sumar 1) y su implementación
Ejemplo de operación (sumar 1).
f
: A → A
i → f (i ) = (i + 1) mod 8
g : RA → RA
( a, b, c) → g( a, b, c) = ( a0 , b0 , c0 )
donde:
a0 = a xor (b and c)
b0 = b xor c
c0 = no (c)
Implementación válida
creado October 10, 2011
página 2.6 / 35
LP (11-12) 2. Valores y Tipos
Dada una operación
f :A→A
cualquiera, g será una implementación válida de f , si y solo si
para todo valor x en A se cumple que:
repr A ( f ( x )) = g(repr A ( x ))
Clasificación de los tipos
• Tipos primitivos: lógicos, caracteres, naturales y enteros, reales, tipos enumerados
• Tipos compuestos: producto cartesiano, unión, aplicaciones, conjuntos de partes
• Tipos recursivos
Descriptores de tipo
• Un descriptor de tipo es una frase (un trozo de texto) del lenguaje que describe un tipo de datos
• Hay descriptores de una sola palabra (tipos simples) hasta descriptores de cientos o miles de líneas
(por ejemplo, la definición de clases)
2
Tipos Primitivos
Tipos Primitivos
• Son los tipos más simples, en el sentido que contienen valores que no se pueden descomponer
• Lo anterior implica que cada valor de uno de estos tipos es tratado como un todo, no se puede acceder
a una de sus partes independientemente de las otras.
2.1
Tipo Lógico
Tipo Lógico
• Es el más sencillo de los tipos simples.
• Es el tipo formado por los dos valores true y false
creado October 10, 2011
página 2.7 / 35
LP (11-12) 2. Valores y Tipos
• El conjunto está dotado de las operadores lógicos and, or, not
• Presente en la mayoría de los lenguajes (Pascal, Ada, C++, Java,C#, python)
Representación ambigua de valores lógicos en C
• El lenguaje C no contempla este tipo
• Es usual en C el recurrir a otros tipos (enteros), para representar los valores lógicos.
• La representación basada en enteros es ambigua, lo cual hace fácil cometer errores.
Representación ambigua de valores lógicos en C
Existe valores que no son ni true ni false.
typedef i n t logico ;
const logico true = 1 ;
const logico f a l s e = 0 ;
logico a = 5 ;
....
i f ( a != t r u e )
p r i n t f ( " a es f a l s e " ) ;
El tipo lógico en varios lenguajes:
Leng.
Ada
C++
Java
C#
Python
2.2
Nombre
Boolean
bool
boolean
bool
bool
Valores
True, False
true, false
true, false
true, false
True, False
Caracteres
Caracteres
• Los valores de este tipo son el conjunto de caracteres imprimibles en pantalla, impresora, o cualquier
dispositivo de salida.
creado October 10, 2011
página 2.8 / 35
LP (11-12) 2. Valores y Tipos
• Usualmente se recurre al código ASCII, que define un conjunto de 128 caracteres básicos, y su forma
de representarlos en 1 byte. Los lenguajes Ada, Pascal, C usan el código ASCII
Códigos ASCII y Unicode
• El código ASCII esta limitado: solo contiene caracteres occidentales. Además, hay 128 códigos (del
128 al 255) con varias interpretaciones posibles, según el contexto.
• Los lenguajes modernos (Java, C#) recurren a la representación por el estándar UNICODE, de 2 bytes
por carácter, y con caracteres arábigos, chinos, cirílicos, etc..
El tipo carácter en varios lenguajes
Leng.
Ada
C++
Java
C#
Nombre
Character
char
char
char
(en python no existe este tipo)
2.3
Naturales y Enteros
Naturales y Enteros
• Estos tipos son subconjuntos finitos de los enteros y naturales.
• Estos subconjuntos contienen rangos de valores consecutivos, normalmente incluyendo al cero.
• La representación suele ser simplemente en binario (naturales) o en complemento a dos (enteros).
Longitud predeterminada
• Algunos lenguajes fijan la longitud (en bits) de la representación de estos tipos
• Se prima ante todo la portabilidad, se pierde eficiencia.
Longitud predeterminada
En estos lenguajes, los rangos de valores de los tipos de datos enteros son conocidos a priori y fijos,
determinados en la especificación del lenguaje.
creado October 10, 2011
página 2.9 / 35
LP (11-12) 2. Valores y Tipos
Longitud dependiente de la implementación
• Otros lenguajes no predeterminan la longitud de la representación (se permite que dependa de la
implementación, S.O., hardware, etc?)
• Se permite mejorar la eficiencia, a costa de más complicado lograr portabilidad.
Longitud dependiente de la implementación
En estos casos, para lograr portabilidad deben de existir construcciones del lenguaje que permiten averiguar
el rango de valores asociado a cada tipo.
(normalmente esto ocurre en tiempo de compilación)
Longitud y formato de la representación:
• Los lenguajes Ada, C y C++ no fijan la longitud (aunque se establecen algunas restricciones)
• Los lenguajes modernos (Java y C#) fijan la longitud.
Tipos enteros en C/C++
Nombre
signed char
[signed] short [int]
[signed] int
[signed] long [int]
R. (C)
SCHAR_MIN - SCHAR_MAX
SHRT_MIN - SHRT_MAX
INT_MIN - INT_MAX
LONG_MIN - LONG_MAX
R.mín.
(−27 , 27 )
(−215 , 215 )
(−215 , 215 )
(−231 , 231 )
• El número de bits de la representación debe ser no decreciente
• El rango de valores debe contener al rango mínimo indicado
Tipos naturales en C/C++
Nombre
unsigned
unsigned
unsigned
unsigned
char
short [int]
[int]
long [int]
R. (C)
0 - UCHAR_MAX
0 - USHRT_MAX
0 - UINT_MAX
0 - ULONG_MAX
R.mín.
[0, 28 )
[0, 216 )
[0, 216 )
[0, 231 )
• El número de bits de la representación debe ser no decreciente
• El rango de valores debe contener al rango mínimo indicado
creado October 10, 2011
página 2.10 / 35
LP (11-12) 2. Valores y Tipos
Tipos enteros y naturales en C/C++
• char puede ser equivalente a unsigned char o a signed char, dependiendo de la implementación
• El valor mínimo y máximo indicado son constantes definidas en limits.h.
• En C++, se puede acceder mediante funciones a los valores mínimo y máximo:
– std::numeric_limits<T>.min()
– std::numeric_limits<T>.max()
donde T es uno de los nombres de tipo indicados más arriba. (estas funciones están definidas en el
archivo limits)
Tipos enteros en Ada
Nombre
Short_Integer
Integer
Long_Integer
Disponible
opcionalmente
siempre
opcionalmente
• Los longitudes en bits de cada tipo no están fijas, pero deben ser no decrecientes. Los rangos deben
de estar centrados en cero.
• El valor mínimo y máximo se puede acceder con T’First y T’Last, donde T designa el nombre de
cualquiera de estos tipos.
Tipos naturales en Ada
Nombre
Natural
Positive
Rango
0 - Integer’Last
1 - Integer’Last
• Como se observa, el rango de valores está ligado al rango de valores de los enteros.
Tipos enteros en Java
Nombre
byte
short
int
long
creado October 10, 2011
Rango
[−27 , 27 )
[−215 , 215 )
[−231 , 231 )
[−263 , 263 )
página 2.11 / 35
LP (11-12) 2. Valores y Tipos
• El numero de bits esta fijado por los rangos indicados.
• La representación debe ser en complemento a dos.
Tipos enteros en C#
Nombre
sbyte
short
int
long
Rango
[−27 , 27 )
[−215 , 215 )
[−231 , 231 )
[−263 , 263 )
• El numero de bits esta fijado por los rangos indicados.
• Los valores minimo y máximo se pueden acceder como T’MinValue y T’MaxValue, donde T designa
el nombre de cualquiera de estos tipos.
Tipos naturales en C#
Nombre
byte
ushort
uint
ulong
Rango
[0, 28 )
[0, 216 )
[0, 232 )
[0, 264 )
• El numero de bits esta fijado por los rangos indicados.
• Los valores mínimo y máximo se pueden acceder como T.MinValue y T.MaxValue, donde T designa
el nombre de cualquiera de estos tipos.
Tipos enteros en Python
No hay naturales, y hay dos tipos de enteros:
• Enteros (plain integers): coinciden con los long int de C, y por lo tanto, el rango asociado debe
ser, como mínimo, desde −23 1 hasta 23 1
• Enteros largos (long integers): de rango ilimitado (representados en memoria como una secuencia de
dígitos de longitud suficiente para representar un número)
(en este lenguaje no existe descriptores de tipos pues no hay declaración de variables)
creado October 10, 2011
página 2.12 / 35
LP (11-12) 2. Valores y Tipos
2.4
Reales
Números reales
• Estos tipos representan subconjuntos finitos del conjunto de los números reales.
• Se suele usar la representación en coma flotante, por tanto con precisión adaptativa (mayor en el
entorno de 1.0, decreciente al crecer el valor absoluto de los números).
Números Reales
• Al igual que ocurre con los enteros, la representación puede estar preestablecida en el lenguaje (Java,
C#), o no (Ada, C/C++).
• Si no está preestablecida, se usa la que mejor se adapta a la FPU de la CPU (en la actualidad suele
ser el formato IEEE-754)
Reales en C/C++
• Se contemplan: float, double, long double
• La representaciones concretas no están especificadas, pero deben ser de precisión y rango no decrecientes.
Reales en Ada
• Se contemplan: Float, (y, opcionalmente: Short_Float, Long_Float)
• La representaciones concretas no están especificadas, pero deben ser de precisión y rango no decrecientes.
Reales en Java y en C#
Ambos lenguajes adoptan el mismo convenio:
• El formato obligado es el establecido en la norma IEEE 754.
• Se contemplan:
– float (32 bits)
– double (64 bits).
creado October 10, 2011
página 2.13 / 35
LP (11-12) 2. Valores y Tipos
Reales en Python
• En este lenguaje solo hay un tipo para los números reales, cuya precisión y rango está dictado por el
tipo flotante de máxima precisión que este directamente soportado por el hardware donde se ejecutan
los programas.
• En la práctica lo anterior implica que el tipo tiene la precisión y rango de los double de C (en
implementaciones del lenguaje basadas en C, que es lo más frecuente).
2.5
Tipos enumerados
Tipos enumerados
• En el programa se incluye, asociado cada tipo enumerado, una secuencia de identificadores distintos.
• Cada identificador está biunivocamente asociado a un valor del tipo (representa el valor)
Tipos enumerados
• Un tipo enumerado se pueden implementar en un programa como un conjunto de identificadores asociados a constantes de tipo enteros.
• Esta solución plantea problemas de ambigüedad de la representación y/o de la interpretación de los
valores enteros
Representación de tipos enumerados
• La representación de los tipos enumerados suele estar basada en los tipos naturales o enteros
• La selección de la representación se realiza automáticamente en tiempo de compilación, en función
sobre todo del número de valores del tipo.
Tipos enumerados en varios lenguajes
• C (ANSI +) y C++
typedef enum { id1 , id2 ,. . . ,idn } idtipo ;
• Ada
type idtipo is { id1 ,id2 ,. . .,idn } ;
creado October 10, 2011
página 2.14 / 35
LP (11-12) 2. Valores y Tipos
• Java (JDK 1.5 +)
enum idtipo { id1 ,id2 ,. . .,idn } ;
• C#
Enum idtipo { id1 ,id2 ,. . ., idn } ;
• Python: no está incorporado (se rechazó una propuesta de hacerlo)
3
Tipos Compuestos
Tipos compuestos y álgebra de conjuntos
Los tipos compuestos se obtienen al aplicar varios operadores del álgebra de conjuntos a otros tipos.
1. Producto cartesiano: (conjunto de pares construido a partir de dos conjuntos)
2. Unión y unión disjunta: (conjunto unión de otros dos conjuntos)
3. Aplicaciones: (conjunto de aplicaciones de un conjunto en otro)
3.1
Producto Cartesiano
Si A y B son dos tipos (conjuntos) cualquiera, entonces podemos definir el tipo C como:
A× B
C =
En concreto, los valores de C son pares:
C =
{ ( a, b)
t.q.
a∈A
y
b∈B}
El producto cartesiano es asociativo, lo cual implica que puede extenderse de forma natural a secuencias de
conjuntos:
C = A1 × A2 × · · · × A n
( n > 1)
Los elementos de C son ahora tuplas:
C =
{ ( a1 , a2 , . . . , a n )
t.q.
ai ∈ Ai (∀i = 1 . . . n) }
• El producto cartesiano es una construcción fundamental para la creación de tipos.
• Esta contemplado, de una forma u otra, en todos los lenguajes de programación.
creado October 10, 2011
página 2.15 / 35
LP (11-12) 2. Valores y Tipos
Operaciones sobre tuplas
La operación fundamental sobre las tuplas es la consulta del i-ésimo elemento (Ci ) (existe una función
distinta Ci para cada valor distinto de i, con i ∈ {1 . . . n}
Ci ∈ A1 × A2 × · · · × An → Ai
( a1 , a2 , . . . , a n )
→ ai
Esta operación se corresponde con lo que en matemáticas se conoce como una función de proyección
Representación de las tuplas
La forma de representar un tupla es concatenar las representaciones de sus elementos. Supongamos que
a ∈ A y b ∈ B, y que repr A ( a) = r a y repr B (b) = rb . En estas condiciones:
repr A× B (( a, b)) = r a ◦ rb
(aquí, ◦ es un operador que actua sobre dos secuencias de bits y produce otra secuencia que es la concatenación de las originales).
Cardinalidad y tamaño del producto cartesiano
En el caso del producto cartesiano, dados dos conjuntos cualesquiera A y B, se cumple que:
car ( A × B) = car ( A) car ( B)
tam( A × B) = tam( A) + tam( B)
Conjuntos de tuplas en ML
En algunos lenguajes se implementa el producto cartesiano tal cual:
type A = i n t ∗ r e a l ;
type C = i n t ∗ r e a l ∗ char ∗ i n t ;
El problema es acceder a los elementos individuales de las tuplas (se accede con un índice numérico)
Tuplas en Python
• Las tuplas están directamente soportadas en este lenguaje, sus elementos son accesibles usando un
valor numérico que comienza en cero.
• Se incluye la tupla vacía
• Las tuplas homogéneas pueden verse como arrays
creado October 10, 2011
página 2.16 / 35
LP (11-12) 2. Valores y Tipos
Registros y estructuras
Lo más usual es etiquetar cada componente de las tuplas con un identificador:
• Facilita las referencias a los elementos individuales de una tupla, independientemente de su posición,
por tanto, mejora la facilidad de escritura y legibilidad de código, así como su fiabilidad.
• Las etiquetas forman parte del tipo: facilita la comprobación de tipos. Tuplas semejantes con etiquetas
distintas forman tipos distintos.
Registros y estructuras en Ada
type C i s record
edad : I n t e g e r ;
peso : F l o a t ;
end r e c o r d ;
• Aquí, C = Integer × Float.
• En este caso, las etiquetas asociadas a las dos componentes de las tuplas son edad y peso, respectivamente
Registros y estructuras en C
La estructura similar a la anterior sería como sigue:
struct C
{
int
edad ;
f l o a t peso ;
} ;
Nota: el nombre del tipo es struct C
Registros y estructuras en C ANSI /C++
En estos lenguajes una estructura se puede declarar como sigue:
typedef s t r u c t
{
int
edad ;
f l o a t peso ;
}
C ;
creado October 10, 2011
página 2.17 / 35
LP (11-12) 2. Valores y Tipos
Registros y estructuras C++
Adicionalemtne, en C++, se puede usar una forma de declaración de clase que equivale a una struct:
class C
{
public :
int
edad ;
f l o a t peso ;
} ;
Registros y estructuras en Java
class C
{
p u b l i c i n t edad ;
p u b l i c f l o a t peso ;
} ;
En este lenguaje se usa el mecanismo de definición de clases.
Registros y estructuras en C#
struct C
{
p u b l i c i n t edad ;
p u b l i c f l o a t peso ;
} ;
Registros y estructuras en Python
• Este tipo de datos no está soportado directamente en el lenguaje (ocurre lo mismo en otros lenguajes
parecidos)
• En las últimas versiones de Python, se puede usar el mecanismo de las clases, aunque hay que tener
en cuenta que existe un único tipo (de nombre class) que comprende todas las estructuras posibles
con cualquier conjunto de componentes.
creado October 10, 2011
página 2.18 / 35
LP (11-12) 2. Valores y Tipos
Ejemplo de clase como estructura en Python
c l a s s Persona :
edad = 0
peso = 0 . 0
nombre = " "
alguien
alguien
alguien
alguien
= Persona ( )
. nombre = " J o s e Perez "
. edad = 45
. peso = 8 2 . 5
p r i n t a l g u i e n . peso ∗ 2
3.2
Unión y unión disjunta
Unión y unión disjunta
• Estas construcciones son útiles cuando queremos considerar un valor que puede ser de un tipo entre
varios.
• Es útil cuando se busca eficiencia en uso de memoria.
• Los lenguajes más modernos (Java, C#) no contemplan esto.
• En Python y otros lenguajes, el sistema de tipos permite en la práctica uniones disjuntas de forma
implícita aunque no haya un mecanismo explícito.
Unión
Si A y B son dos tipos (conjuntos) cualquiera, entonces podemos definir el conjunto C como la unión de
ambos:
C = A∪ B
En concreto, los valores de C son los siguientes:
C = { x
t.q.
x ∈ A ∨ x ∈ B }
Una implementación bastante obvia de la unión consiste en representar cada elemento de la unión usando
C usando alguna la representación repr A o repr B que corresponda.
creado October 10, 2011
página 2.19 / 35
LP (11-12) 2. Valores y Tipos
Unión: ambigüedad en la representación
Si A y B no son disjuntos, puede ocurrir ambigüedad en la representación.
• Si x ∈ A ∪ B, entonces podemos usar tanto repr A como repr B para representarlo.
• En el lenguaje C/C++, esto no ocurre pues toda expresión tiene un tipo fijo conocido en tiempo de
compilación (que determina la representación) luego todo valor producido al evaluar una expresión es
de un tipo determinado.
Unión: ambigüedad en la interpretación
Puede ocurrir ambigüedad en la interpretación cuando los dos conjuntos R A y R B no son disjuntos.
• Ocurre cuando existe alguna secuencia de bits que puede ser interpretable como representación de un
A o un B.
• En el lenguaje C/C++, este problema si puede darse
Ambigüedad en la representación: esquema
x ∈ A∩B
creado October 10, 2011
repr A ( x ) 6= repr B ( x )
página 2.20 / 35
LP (11-12) 2. Valores y Tipos
Unión. Ambigüedad en la interpretación.
s ∈ R A ∩ RB
1
−1
a = repr −
A ( s ) 6 = repr B ( s ) = b
Unión en C
En el lenguaje C se contempla la unión:
union C
{
i n t edad ;
f l o a t peso ;
} ;
Por si sola, esta construcción no es útil, esencialmente por los problemas de ambigüedad de la interpretación.
Unión Disjunta
Si A y B son dos tipos (conjuntos) cualquiera, entonces podemos definir el conjunto C como la unión disjunta
de ambos:
C = A + B = ({ f alse} × A) ∪ ({true} × B)
es decir:
C =
creado October 10, 2011
{ (b, x ) t.q. (b = f alse ∧ x ∈ A) ∨ (b = true ∧ x ∈ B) }
página 2.21 / 35
LP (11-12) 2. Valores y Tipos
Unión disjunta de varios conjuntos (1)
• Los valores true y false actúan como etiquetas, que informan acerca de a que subconjunto de A + B
(A ó B) pertenece cada valor.
• Los conjuntos de etiquetas pueden tener un número arbitrario de valores, y por tanto la unión disjunta
se puede extender a una serie de conjuntos.
Unión disjunta de varios conjuntos (2)
Supongamos, por ejemplo, que A = {1, 2} y B = {2, 3}. En este caso, el conjunto A + B es:
{( f alse, 1), ( f alse, 2), (true, 2), (true, 3)}
Unión disjunta de varios conjuntos (2)
Podemos definir la unión disjunta de n conjuntos:
A1 + A2 + · · · + A n
= ({e1 } × A1 ) ∪ ({e2 } × A2 ) ∪ · · · ∪ ({en } × An )
donde
E = { e1 , e2 , . . . , e n }
es un conjunto cualquiera de n valores, que se usan como etiquetas (todos estos valores han de ser del mismo
tipo)
Representación de la unión disjunta.
Cada valor de la forma (ei , a) ( con a ∈ Ai y ei ∈ E), perteneciente a una unión disjunta, se representa como
una secuencia de bits obtenida al concatenar tres secuencias de bits:
1. La representación de ei
2. La representación de a
3. Una secuencia arbitraria de bits de relleno
Representación de la unión disjunta
Supongamos que U = A1 + A2 + · · · + An (siendo el conjunto de etiquetas E = {e1 , e2 , . . . , en } ), sea
a ∈ Ai . En estas condiciones:
reprU (ei , a) = repr E (ei ) ◦ repr Ai ( a) ◦ 0| .{z
. . 0}
n bits
creado October 10, 2011
página 2.22 / 35
LP (11-12) 2. Valores y Tipos
Donde n es el número de bits de relleno
n
=
max (tam( A1 ), tam( A2 ), . . . , tam( An )) − tam( Ai )
que hace que el tamaño de la representación sea fijo
Cardinalidad y tamaño de la unión disjunta
Respecto al tamaño y el número de elementos, se cumple que:
car ( A1 + · · · + An ) = car ( A1 ) + · · · + car ( An )
tam( A1 + · · · + An ) = tam( E) + max (tam( A1 ), . . . , tam( An ))
Operaciones de la unión disjunta
Es posible consular la etiqueta o el valor asociado (proporcionando un entero i que identifica el componente
que queremos), de forma independiente:
Cetiq ∈ A1 + · · · + An → E
(d, x ) → d
Ci ∈ A1 + · · · + An → Ai
x
si ei = d
(d, x ) →
error en otro caso
Implementación de la unión disjunta
En lenguajes sin unión disjunta, podemos implementarla combinando unión y producto cartesiano:
A1 + · · · + A n
⊂ ( E × A1 ) ∪ ( E × A2 ) ∪ · · · ∪ ( E × A n )
= E × ( A1 ∪ A2 ∪ · · · ∪ A n )
nótese que este conjunto incluye pares donde el valor de la etiqueta no se corresponde con el tipo del
dato.
Unión disjunta: posibles problemas (1/3)
El esquema anterior puede presentar problemas:
• Las etiquetas permiten al programador resolver la ambigüedad en la interpretación de las representaciones, pero esto debe llevarse a cabo explícitamente en el programa, y por tanto puede hacerse
erróneamente.
creado October 10, 2011
página 2.23 / 35
LP (11-12) 2. Valores y Tipos
Unión disjunta: posibles problemas (1/3)
• Es posible producir (por error) alguno de los valores del conjunto anterior que no están en la unión
disjunta. Las representaciones de estos valores se interpretan erróneamente.
Unión disjunta: posibles problemas (1/3)
• El conjunto de etiquetas puede tener un número de valores distinto del número de conjuntos que
intervienen
Unión disjunta en C/C++
Se debe combinar una struct y una union
struct idtipo {
E idetiqueta ;
union {
A1 id1 ;
A2 id2 ;
···
An idn ;
};
};
Ejemplo de unión disjunta en C
struct T
{
int etiq ;
union
{
f l o a t f ; / ∗ e t i q == 1 ∗ /
c h a r c ; / ∗ e t i q == 2 ∗ /
} ;
} ;
Implementación de la unión disjunta en Pascal
Se establece una correspondencia entre valores de las etiquetas y tipos (no se comprueba en tiempo de
ejecución)
creado October 10, 2011
página 2.24 / 35
LP (11-12) 2. Valores y Tipos
type idtipo =
case idetiqueta : E of
begin
e1 : ( id1 : A1 ) ;
e2 : ( id2 : A2 ) ;
···
en : ( idn : An )
end
Ejemplo de unión disjunta en Pascal
type T =
case e t i q : I n t e g e r o f
begin
1 : ( f : Real ) ;
2 : ( c : Char )
end
Implementación de la unión disjunta en Ada
Este lenguaje contempla explícitamente la unión disjunta, asegurando que en tiempo de ejecución nunca se
producen los problemas que aparecen en C o Pascal.
Implementación de la unión disjunta en Ada
type idtipo ( idetiq : E [ := expr ] ) is record
case idetiq is
when e1
==> id1 : A1 ;
when e2
==> id2 : A2 ;
···
when en−1
==> idn−1 : An−1 ;
[ when others
==> idn : An ; ]
end case
end record
Ejemplo de unión disjunta en Ada
creado October 10, 2011
página 2.25 / 35
LP (11-12) 2. Valores y Tipos
type T ( e t i q : I n t e g e r
case e t i q i s
when 1
==>
when 2
==>
when o t h e r s ==>
end case
end r e c o r d
3.3
) is record
f : Real
;
c : Char
;
i : Integer ;
Aplicaciones
Aplicaciones
Si A y B son dos tipos (conjuntos) cualquiera, entonces podemos definir el tipo C como el conjunto de todas
las aplicaciones de A en B
C = A → B
En concreto, los valores de C son aplicaciones que tienen como dominio A, y como rango B
Cardinalidad de A → B
El número de aplicaciones posibles de A en B es
car ( A → B) = car ( B)car( A)
(al primer elemento de A se le puede asignar cualquiera de los car ( B) elementos de B, igual para el segundo
elemento de A, el tercero, y así sucesivamente hasta car ( A) veces)
Operaciones sobre las aplicaciones
La operación esencial es la consulta de la imagen de un elemento del conjunto índice.
C : ( A → B) × A → B
( g, a) → g( a)
Representación de aplicaciones
En un programa, una aplicación f ∈ A → B se puede representar como:
• Un subprograma que acepta un parámetro de tipo A y devuelve un resultado de tipo B. (una función)
• Una tupla de tantos elementos como valores tenga A. Cada elemento de la tupla tiene la imagen por
f de un elemento de A. (un array, o formación, o vector, o matriz)
creado October 10, 2011
página 2.26 / 35
LP (11-12) 2. Valores y Tipos
Las aplicaciones como arrays (formaciones)
Cuando se usan formaciones como medio de representación:
• Al conjunto A se le suele llamar conjunto de índices, y a sus valores índices.
• Este conjunto debe ser numerable, es decir, podemos escribir sus elementos como
A = { a1 , a2 , . . . , a n }
donde n = car ( A) (por ejemplo, esto implica que no pueden ser números reales).
Las aplicaciones como arrays
La representación de una aplicación f ∈ A → B es la concatenación de las representaciones de las imágenes
por f de cada uno de los elementos de A
repr A→ B ( f )
= repr B ( f ( a1 )) ◦ repr B ( f ( a2 )) ◦ · · · ◦ repr B ( f ( an ))
Por tanto, se cumple que:
tam( A → B) = tam( B)tam( A)
Posibles conjuntos de índices:
• El conjunto índice puede ser un rango de enteros, naturales, caracteres o un tipo enumerado
• En estos casos se les suele llamar vectores, o arrays unidimensionales.
Arrays en C/C++
En estos lenguajes, el conjunto de índices es un rango de naturales que comienza en cero (incluido)
int
char
a [ 3 4 ] ; / / a r r a y de e n t e r o s ,
/ / i n d i c e s d e l 0 a l 33
b [ 2 0 ] ; / / a r r a y de 20 c a r a c t e r e s
los arrays se representan en memoria como una secuencia entradas consecutivas en posiciones consecutivas
de memoria.
creado October 10, 2011
página 2.27 / 35
LP (11-12) 2. Valores y Tipos
Arrays en Java y C#
En estos lenguajes, los arrays son tipos-referencia, lo cual (en este contexto) quiere decir que un array se
representa como una referencia (posiblemente nula) a una zona de memoria donde se almacena el numero
de entradas y a continuación las entradas consecutivas.
i n t [ ] a = new i n t [ 3 4 ] ; / / a r r a y de e n t e r o s ,
/ / i n d i c e s d e l 0 a l 33
c h a r [ ] b = new i n t [ 2 0 ] ; / / a r r a y de 20 c a r a c t e r e s
Arrays en Ada
El conjunto de índices puede ser un rango de valores de un tipo primitivo numerable (naturales, enteros,
caracteres, lógicos, enumerados)
type t1 i s array ( −2..45 ) o f Character ;
t y p e t 2 i s a r r a y ( 0 . . 3 4 ) o f Persona ;
type t3 i s array ( ’ a ’ . . ’ z ’ ) of Float ;
t y p e DiasSem i s ( l u n e s , m ar tes , m i e r c o l e s ,
j u e v e s , v i e r n e s , sabado ,
domingo ) ;
t y p e t 3 i s Array ( DiasSem ) o f I n t e g e r ;
t y p e t 4 i s Array ( l u n e s . . v i e r n e s ) o f I n t e g e r ;
se representan en memoria almacenando el tamaño seguido de la secuencia de entradas, almacenadas
consecutivas.
Arrays multidimensionales
• El conjunto índice puede ser el producto cartesiano de n rangos de entre los citados en la transparencia
anterior, posiblemente rango distintos de tipos distintos. Cada índice es por tanto una tupla.
• Se les llama: matrices o arrays n-dimensionales.
• Se les puede ver también como arrays cuyo conjunto rango es a su vez un array (arrays de arrays), ya
que se cumple que:
( A × B) → C = A → ( B → C )
• La representación es la que se deduce de la igualdad anterior
Arrays multidimensionales en C/C++
En estos lenguajes, un array multidimensional se debe implementar como un array de arrays
creado October 10, 2011
página 2.28 / 35
LP (11-12) 2. Valores y Tipos
int arr [ 5 ] [ 1 0 ]
;
aquí arr es una variable de tipo array con 10 entradas: cada una de ellas es un array de 5 enteros. Todos
los datos se almacenan de forma consecutiva en memoria.
Arrays multidimensionales en Ada
Se pueden tener arrays de arrays, o bien usar una construcción mas simple en el cual se expresa que los
subíndices son tuplas.
t y p e A r r a y 5 i s Array ( 0 . . 4 ) o f I n t e g e r ;
t y p e A r r M u l t i d i m 1 i s Array ( 0 . . 9 ) o f A r r a y 5 ;
t y p e A r r M u l t i d i m 2 i s Array ( 0 . . 9 , 0 . . 4 ) o f I n t e g e r ;
estos tipos denotan arrays con 10 entradas: cada una de ellas es un array de 5 enteros.
Arrays multidimensionales en Java y C#
Se pueden declarar como arrays de arrays de la siguiente forma:
i n t [ ] [ ] a = new i n t [ 1 0 ] [ 5 ]
; / / J a v a y C#
este descriptor de tipo corresponde al tipo de los arrays con 10 entradas, siendo cada una de ellas un array
de 5 enteros (nótese que los tamaños van al revés que en C)
Arrays dinámicos
En algunos casos, el conjunto de índices es heterogéneo, por ejemplo, podemos considerar todos los arrays
unidimensionales con índices enteros
∞
[
({0, 1, . . . , n − 1} → B)
n =1
es decir, este tipo son todos los arrays de 0 elementos, o de 1 elementos, o de 2 elementos, etc...
Arrays dinámicos
• En estos casos, el tamaño de la representación no es igual para todas los arrays. Se les suele llamar
arrays dinámicos
• La representación incluye una componente que proporcione información sobre que valores son índices
correctos (es decir, cual es la longitud del array). En caso contrario, ocurrirá una ambigüedad en la
interpretación.
creado October 10, 2011
página 2.29 / 35
LP (11-12) 2. Valores y Tipos
• Para que una secuencia de este tipo pueda ser interpretada correctamente y de forma eficiente, su
parte inicial debe codificar la longitud.
• Existe una operación que consiste en consultar el número de elementos de un array
Arrays dinámicos en C/C++
• En los lenguajes C/C++, estos arrays se representan como punteros a la secuencia de elementos.
• No están dotados con información sobre la longitud, que debe de almacenarse por separado y ser
tenida en cuenta
• Esto provoca muchos problemas de fiabilidad por accesos fuera de los límites de los arrays
• Existen librerías (como la STL) que si proporcionan arrays fiables
Arrays dinámicos en Ada
Un ejemplo de descriptor de tipo para arrays dinámicos de índices enteros y elementos de tipo carácter es
este:
t y p e A r r D i n i s Array ( I n t e g e r range <>) o f C h a r a c t e r ;
• Se indica que el tipo comprende todos los arrays de caracteres cuyos índices son rangos finitos de
enteros.
• Al igual que los arrays normales, se pueden usar otros tipos como tipo índice.
Arrays dinámicos en Java/C#
Todos los arrays en Java y C# (al ser de tipos-referencia) pueden considerarse como dinámicos :
char [ ] a ;
a = new i n t [ 2 0 ] ;
a = new i n t [ 3 0 ] ; / / e l a r r a y ’ a ’ ha cambiado de tamaño
Arrays dinámicos dentados en Java y C#
En estos lenguajes podemos tener arrays de arrays dinámicos en estos casos, los (sub-)arrays que forman el
array global pueden tener distinta longitud (incluyendo longitud 0), se denominan arrays dentados (jagged
arrays)
char [ ] [ ] a ;
/ / a r r a y que c o n t i e n e 20 a r r a y s d i n á m i c o s de c a r a c t e r e s
a [ 0 ] = new i n t [ 1 0 ] ;
a [ 1 ] = new i n t [ 2 0 ] ;
creado October 10, 2011
página 2.30 / 35
LP (11-12) 2. Valores y Tipos
Arrays rectangulares en C#
Este lenguaje incorpora una forma especifica de indicar que un array es multidimensional (al igual que los
arrays multidimensionales de Ada o C++) en lugar de un array de arrays posiblemente dentado.
char [ , ] a ;
/ / array rectangular
a = new i n t [ 1 0 , 2 0 ] ;
a = new i n t [ 2 0 , 1 ] ;
se puede generalizar a mas de dos subindices.
Las aplicaciones como diccionarios
• Existen otras posibilidades para el conjunto índice, como las cadenas de caracteres (p.ej. en Perl).
• En Python se pueden usar como conjunto de índices conjuntos heterogéneos de enteros, flotantes,
cadenas o tuplas.
• En estos casos no se usa la representación con arrays (dado que el conjunto de índices posibles tiene
una cardinalidad elevada, y no todos ellos tienen asociada una imagen). La representación no tiene
longitud fija.
• A estas aplicaciones se les suele llamar diccionarios
4
Tipos recursivos
• Los tipos recursivos son los basados en listas y árboles.
• Tienen una representación con un tamaño que no es igual para todos los valores posibles.
• Se les suele llamar estructuras de datos dinámicas
4.1
Listas
El tipo recursivo más sencillo son las listas homogéneas de elementos. Si A es un tipo cualquiera, el conjunto
de las listas de A es el menor conjunto L que cumple:
L = {null } + ( A × L)
Donde null es un valor especial, arbitrario, y donde menor se refiere a menor en el sentido de la inclusión
de conjuntos. Si se cumple esto, decimos que:
L = listas( A)
La anterior definición de las listas implica que, si l ∈ listas( A) , entonces una (y solo una) de estas dos
condiciones se deben cumplir:
creado October 10, 2011
página 2.31 / 35
LP (11-12) 2. Valores y Tipos
• l = ( f alse, null )
en este caso decimos que l es la lista vacía
• l = (true, ( a, l 0 )), donde a ∈ A y l 0 ∈ listas( A)
en este caso, decimos que:
– a es la cabecera de l
– l 0 es el resto de l
Operaciones sobre listas
Una de las operaciones más frecuentes sobre las listas es la consulta sobre si la lista es la lista vacía o no
vacia ∈ listas( A) → { f alse, true}
(b, x ) → not(b)
Operaciones sobre listas
Otras operaciones son la consulta de la cabecera y del resto:
cab ∈ listas( A) − {null } → A
(true, ( a, l )) → a
resto ∈ listas( A) − {null } → A
(true, ( a, l )) → l
Listas en varios lenguajes
En C++, Java y C#
1. En C++, la libreria STL incluye las plantillas para clases list, que es un tipo de contenedor
(container class) que contiene una secuencia de valores arbitrarios.
2. En Java, existe la clase List como un tipo de colección (implementaciones del interfaz AbstractCollection
en java.util), en concreto constituye una secuencia de referencias a instancias de cualquier clase.
3. En C#, existe la plantilla para clases List (en el namespace System.Collections.Generic)
Listas en Python
Este lenguaje incorpora las listas como tipo de datos, y permite expresiones de tipo lista.
• Las listas son heterogéneas: sus elementos pueden ser de tipos distintos y arbitrarios
creado October 10, 2011
página 2.32 / 35
LP (11-12) 2. Valores y Tipos
• Las listas son accesibles con un índice numérico, que comienza en cero para el primer elemento. Esto
le proporciona la posibilidad de ser usadas como arrays a todos los efectos.
• Las expresiones de tipo lista se escriben usando corchetes que encierran secuencias de expresiones
separadas por comas.
Listas en Python
Incorpora bastantes operaciones:
1. acceso de lectura/escritura a elementos arbitrarios
2. extracción de sub-listas (slices)
3. concatenación de listas para formar otras
4. inserción de elementos o listas en listas
4.2
Árboles
Árboles binarios
Sea A un conjunto cualquiera, el conjunto de los árboles binarios con elementos de tipo A, es el menor
conjunto T que cumple:
T = {null } + ( A × T × T )
En este caso, se escribe:
L = arboles-bin( A)
Árboles binarios
Según la definición anterior, si t es un árbol binario con elementos de tipo A, solo una de estas dos
condiciones se cumple:
• t = ( f alse, null ) decimos que t es el árbol vacío
• t = (true, ( a, tizq , tder )) decimos que:
– a es la raíz de t,
– tizq es el subárbol izquierdo de t
– tder es el subárbol derecho de t
creado October 10, 2011
página 2.33 / 35
LP (11-12) 2. Valores y Tipos
Árboles n-arios
Un árbol binario, si no está vacío, tiene dos subárboles. Esta definición puede generalizarse a árboles con
n subárboles (árboles n-arios).
T = {null } + ( A × |T × T ×{z· · · × T})
n
Finalmente, podemos considerar árboles donde el número de subárboles no es fijo
T = {null } + ( A × listas( T ))
Operaciones sobre árboles
Las operaciones básicas más frecuentes son:
• Consulta sobre si un árbol está vacío o no
• Consulta del elemento de A en la raíz de un árbol (si no está vacío)
• Consulta del i-ésimo subarbol de un árbol (si no está vacío)
Representación de tipos recursivos:
Hay dos opciones:
• Usando secuencias contiguas de bits, de tamaño no fijo.
• La representación en memoria suele hacerse por bloques de bits no contiguos, para mejorar la eficiencia
en tiempo de las actualizaciones. (se verá más adelante)
Tipos recursivos en los lenguajes de programación
• Los lenguajes imperativos no contemplan estos tipos (aunque se pueden y suelen implementar usando
tipos definidos por el usuario y/o clases, a partir TADs)
• Los lenguajes funcionales suelen incorporar construcciones explícitas para listas y árboles.
Tipos recursivos en los lenguajes de progr.
Ejemplo en ML
Supongamos que queremos designar con arbol a los árboles binarios de enteros (int). Se declararía en
ML como:
creado October 10, 2011
página 2.34 / 35
LP (11-12) 2. Valores y Tipos
datatype arbol =
vacio
| novacio of ( int ∗ arbol ∗ arbol ) ;
5
Equivalencia de tipos
• En los lenguajes de programación es necesario saber cuando dos valores pertenecen a tipos equivalentes o no
• La forma de determinar si dos tipos son equivalentes constituye el mecanismo de equivalencia de tipos
de un lenguaje.
Equivalencia estructural.
• Dos tipos A y B son equivalentes estructuralmente si contienen los mismos valores (es decir A = B)
o bien existe una correspondencia biunívoca trivial entre ambos conjuntos de valores
• En estos casos escribimos A ≡ B
Equivalencia estructural.
Existirá una correspondencia trivial cuando:
• Ambos tipos son primitivos, y contienen los mismos valores
• Ambos tipos son compuestos o recursivos, y están definidos con el mismo operador de conjuntos,
aplicado a conjuntos que a su vez son estructuralmente equivalentes entre ellos.
Equivalencia estructural
Tipos compuestos y recursivos
Dados cuatro tipos arbitrarios A,B,A0 ,B0 , en donde A ≡ A0 y B ≡ B0 , entonces: se cumple que:
A×B
A+B
A→B
listas( A)
arboles( A)
creado October 10, 2011
≡
≡
≡
≡
≡
A0 × B0
A0 + B0
A0 → B0
listas( A0 )
arboles( A0 )
página 2.35 / 35
LP (11-12) 2. Valores y Tipos
Equivalencia nominal
Cada valor manejado en un programa tiene asociado a un tipo. Este tipo puede:
• Tener asociado un identificador (su nombre), se dice que es un tipo con nombre.
• No tener asociado un identificador, pero si una declaración donde se especifica su estructura (es un
tipo anónimo)
Equivalencia nominal
• Dos tipos con nombre son equivalentes nominalmente si sus nombres coinciden.
• Dos tipos anónimos son equivalentes nominalmente si su estructura se define en la misma declaración
(la equivalencia nominal implica equivalencia estructural).
Valoracion de las formas de equivalencia de tipos
• La equivalencia nominal es más fácil de implementar, y además produce programas más fáciles de
comprender y menos sujetos a errores. La mayoría de los lenguajes modernos usan equivalencia
nominal en la mayoría de los casos.
• En algunos casos concretos se usa equivalencia estructural (por ejemplo, en el paso a subprogramas
de parámetros por referencia de tipo array). Tiene la ventaja de que el programa es más conciso.
Equivalencia de tipos en varios lenguajes.
• C/C++ equivalencia estructural (excepto clases en C++, con equivalencia nominal).
• Ada equivalencia nominal
• Jaca, C#: equivalencia nominal
Hay excepciones puntuales.
6
El principio de completitud de tipos
El principio de completitud de tipos debería ser seguido en cualquier lenguaje. Establece que:
Ninguna operación debería ser prohibida (arbitrariamente) a valores de un tipo determinado.
Un ejemplo en el que no se cumple:
Una función en C puede devolver un valor de cualquier tipo, excepto el tipo array.
creado October 10, 2011
página 2.36 / 35
Descargar