Documente Academic
Documente Profesional
Documente Cultură
Capítulo 2.
Valores y Tipos
Carlos Ureña Almagro
Curso 2011-12
Contents
1 Introducción 2
2 Tipos Primitivos 6
2.2 Caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.4 Reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3 Tipos Compuestos 14
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
1 Introducción
El concepto de Valor.
• Podamos almacenar, o
El concepto de Valor.
En definitiva, un valor es cualquier entidad que pueda ser procesada de alguna manera mediante las in-
strucciones que cada lenguajes de programación incorpore.
• Punteros
Ejemplos. Valores en C.
• Punteros
• Subprogramas (funciones)
El concepto de tipo
Para hacer más tratable el conjunto de todos los valores posibles en cada lenguaje, clasificamos los valores
en tipos.
El concepto de tipo
• 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 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
Representaciones ambigüas
• 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
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.
• Si las representaciones son todas del mismo tamaño, el número de bytes que tiene cada representación
se nota como tam( A).
• 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 equiv-
alente definida sobre las representaciones de los valores.
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
f :A→A
repr A ( f ( x )) = g(repr A ( x ))
• 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.
Tipo Lógico
• Es usual en C el recurrir a otros tipos (enteros), para representar los valores lógicos.
logico a = 5 ;
....
i f ( a != t r u e )
p r i n t f ( " a es f a l s e " ) ;
2.2 Caracteres
Caracteres
• Los valores de este tipo son el conjunto de caracteres imprimibles en pantalla, impresora, o cualquier
dispositivo de salida.
• 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
• 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..
Leng. Nombre
Ada Character
C++ char
Java char
C# char
Naturales y Enteros
Longitud predeterminada
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.
En estos casos, para lograr portabilidad deben de existir construcciones del lenguaje que permiten averiguar
el rango de valores asociado a cada tipo.
• Los lenguajes Ada, C y C++ no fijan la longitud (aunque se establecen algunas restricciones)
• Los lenguajes modernos (Java y C#) fijan la longitud.
• char puede ser equivalente a unsigned char o a signed char, dependiendo de la imple-
mentación
– 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)
Nombre Disponible
Short_Integer opcionalmente
Integer siempre
Long_Integer 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.
Nombre Rango
Natural 0 - Integer’Last
Positive 1 - Integer’Last
• Como se observa, el rango de valores está ligado al rango de valores de los enteros.
Nombre Rango
byte [−27 , 27 )
short [−215 , 215 )
int [−231 , 231 )
long [−263 , 263 )
Tipos enteros en C#
Nombre Rango
sbyte [−27 , 27 )
short [−215 , 215 )
int [−231 , 231 )
long [−263 , 263 )
• 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 Rango
byte [0, 28 )
ushort [0, 216 )
uint [0, 232 )
ulong [0, 264 )
• 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.
• 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)
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++
• La representaciones concretas no están especificadas, pero deben ser de precisión y rango no decre-
cientes.
Reales en Ada
• La representaciones concretas no están especificadas, pero deben ser de precisión y rango no decre-
cientes.
Reales en Java y en C#
• Se contemplan:
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).
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
• La representación de los tipos enumerados suele estar basada en los tipos naturales o enteros
• C (ANSI +) y C++
• Ada
• C#
3 Tipos Compuestos
Los tipos compuestos se obtienen al aplicar varios operadores del álgebra de conjuntos a otros tipos.
Si A y B son dos tipos (conjuntos) cualquiera, entonces podemos definir el tipo C como:
C = A× 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) }
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
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 concate-
nación de las originales).
En el caso del producto cartesiano, dados dos conjuntos cualesquiera A y B, se cumple que:
Conjuntos de tuplas en ML
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
Registros y estructuras
• 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.
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 ;
Registros y estructuras en 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 ;
} ;
class 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 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 ;
} ;
• 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.
c l a s s Persona :
edad = 0
peso = 0 . 0
nombre = " "
alguien = Persona ( )
alguien . nombre = " J o s e Perez "
alguien . edad = 45
alguien . peso = 8 2 . 5
p r i n t a l g u i e n . peso ∗ 2
• Estas construcciones son útiles cuando queremos considerar un valor que puede ser de un tipo entre
varios.
• 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.
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
s ∈ R A ∩ RB a = repr − 1 −1
A ( s ) 6 = repr B ( s ) = b
Unión en C
Por si sola, esta construcción no es útil, esencialmente por los problemas de ambigüedad de la inter-
pretació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 = { (b, x ) t.q. (b = f alse ∧ x ∈ A) ∨ (b = true ∧ x ∈ B) }
• 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.
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)}
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)
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
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
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.
• 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.
• El conjunto de etiquetas puede tener un número de valores distinto del número de conjuntos que
intervienen
struct idtipo {
E idetiqueta ;
union {
A1 id1 ;
A2 id2 ;
···
An idn ;
};
};
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 ∗ /
} ;
} ;
Se establece una correspondencia entre valores de las etiquetas y tipos (no se comprueba en tiempo de
ejecución)
type idtipo =
case idetiqueta : E of
begin
e1 : ( id1 : A1 ) ;
e2 : ( id2 : A2 ) ;
···
en : ( idn : An )
end
type T =
case e t i q : I n t e g e r o f
begin
1 : ( f : Real ) ;
2 : ( c : Char )
end
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.
type T ( e t i q : I n t e g e r ) is record
case e t i q i s
when 1 ==> f : Real ;
when 2 ==> c : Char ;
when o t h e r s ==> i : Integer ;
end case
end r e c o r d
3.3 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
(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)
C : ( A → B) × A → B
( g, a) → g( a)
Representación de aplicaciones
• 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)
• 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).
repr A→ B ( f )
= repr B ( f ( a1 )) ◦ repr B ( f ( a2 )) ◦ · · · ◦ repr B ( f ( an ))
• El conjunto índice puede ser un rango de enteros, naturales, caracteres o un tipo enumerado
Arrays en C/C++
En estos lenguajes, el conjunto de índices es un rango de naturales que comienza en cero (incluido)
int 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
char 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.
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 ;
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 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 )
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.
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.
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.
• 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
• 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
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.
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
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 ] ;
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 ] ;
• 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:
• l = ( f alse, null )
en este caso decimos que l es la lista vacía
– a es la cabecera de l
– l 0 es el resto de l
Una de las operaciones más frecuentes sobre las listas es la consulta sobre si la lista es la lista vacía o no
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.
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
• 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
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:
– a es la raíz de t,
– tizq es el subárbol izquierdo de t
– tder es el subárbol derecho de t
Á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 × listas( T ))
• 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)
• 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.
Ejemplo en ML
Supongamos que queremos designar con arbol a los árboles binarios de enteros (int). Se declararía en
ML como:
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 equiva-
lentes 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
Equivalencia estructural.
• 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
Dados cuatro tipos arbitrarios A,B,A0 ,B0 , en donde A ≡ A0 y B ≡ B0 , entonces: se cumple que:
A×B ≡ A0 × B0
A+B ≡ A0 + B0
A→B ≡ A0 → B0
listas( A) ≡ listas( A0 )
arboles( A) ≡ arboles( A0 )
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).
• 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.
El principio de completitud de tipos debería ser seguido en cualquier lenguaje. Establece que:
Una función en C puede devolver un valor de cualquier tipo, excepto el tipo array.