Palabras clave: Datos estructurados
, Registros
.
1. Concepto de registro
Son registros: tu DNI, un contacto de tu agenda telefónica, los datos de un alumno en una lista de asistencia a clase, el billete de entrada al cine o el del vuelo de un avión, etc … Todos tienen en común que recopilan las diferentes características de determinados objetos.
Un registro recolecta varios datos y no todos los datos tienen que ser del mismo tipo. |
Cada uno de los datos del registro recibe el nombre de campo del registro.
2. Declaración de registros
Un registro se declara en Processing como:
class TipoRegistro { <tipo_de_dato> campo1; <tipo_de_dato> campo2; ... }
donde cada campo se declara como una variable que se corresponde a un tipo de dato.
Imagina que se quiere tener un registro de las fechas de una cierta actividad. Para ello podemos utilizar 3 variables de tipo de dato entero:
1
2
3 int dia;
int mes;
int año;
Pero así declarado nada nos dice de que los valores alamacenados en cada una de estas variables se corresponde a un fecha concreta (una fecha es única y relaciona un día, un mes y un año). Para poner de manifiesto el vínculo de estas 3 variables utilizaremos un registro, al que llamaremos Fecha
(empieza en mayúsculas):
1
2
3
4
5 clase Fecha {
int dia;
int mes;
int año;
}
3. Datos de tipo de dato registro
Cuando defines un registro defines un nuevo tipo de dato (estructurado) que será reconocido por el lenguaje de programación y por tanto puedes declarar variables que respondan a ese tipo. La sintaxis general es:
TipoRegistro variable;
Continuando con el ejemplo anterior, podemos definir una variable, llamémosla unaFecha
,
que puede ser del tipo de dato Fecha
. Basta añadir al código anterior la instrucción:
1 Fecha unaFecha;
Cuando declaramos una variable, dicha variable toma el valor NULL para indicar que no sabe a qué parte de la memoria (del Heap) tiene que referenciar para recuperar o guardar datos. |
|
4. Inicialización de registros
Cuando declaras una variable de "tipo Registro", la variable está preparada para almacenar una referencia al lugar de la memoria donde se almacenarán los valores de sus campos.
Cuando se trabaja con estructuras de datos siempre se tiene que reservar de forma explícita la memoria necesaria para almacenar todos los datos que guardará el tipo de dato estructurado. |
Siempre hemos de usar la instrucción new
con el fin de reservar una porción de memoria a cada una de los campos del registro y se le asigne una referencia a la variable para "que apunte" a esa porción de memoria y así poder acceder a sus diferentes campos (o valores).
La sintaxis general es:
variable = new TipoRegistro(); // Observa los paréntesis ()
Continuando con el ejemplo anterior, para inicializar la variable unaFecha
,
escribiremos la instrucción:
1 unaFecha = new Fecha();
Cuando usamos la instrucción |
|
No olvides añadir los paréntesis después del tipo de dato cuando uses la instrucción new .
|
5. Acceso a los campos de un registro
Si en un programa utilizaras un variable de tipo registro, como por ejemplo,
class TipoRegistro { <tipo_de_dato> campo1; <tipo_de_dato> campo2; ... } TipoRegistro = variable;
necesitarás almacenar y recuperar la información de cada uno de sus campos; es decir, tendrás que
"acceder" a los distintos campos de la variable variable
.
El acceso a los valores de los distintos campos usa las expresiones
variable.campo1 variable.campo2 ...
y utiliza dichas expresiones igual que si fueran variables simples.
5.1. Asignación de valores a los campos de un registro
Si la asignación de una variable simple es:
variable = valor;
la asignación de valores a los campos de una varible de tipo registro es
variable.campo = valor;
Observa que el lado izquierdo de =
responde al acceso al campo campo
del registro registro
.
Continuando con el ejemplo anterior, para asignar a la variable unaFecha
la fecha "18 de febrero de 2001"
escribiremos la instrucción:
1
2
3 unaFecha.dia = 18;
unaFecha.mes = 2;
unaFecha.año = 2001;
Cuando usamos la instrucción de asignación con la notación punto, se guarda en el campo indicado el valor del resultado de la expresión del lado derecho de |
|
5.2. Recuperación de valores de los campos de un registro
Si la recuperación del dato almacenado en un variable se consigue mencionando a la variable, en un variable de tipo registro bastará mencionar o acceder a su campo para recuperar su valor almacenado.
Mencionar a la variable de tipo registro no recupera todos los datos asociados al registro. Recuerda que una variable de tipo registro almacena una referencia al lugar de la memoria donde se encuentran todos los valores asociados a sus campos. |
Continuando con el ejemplo anterior, para imprimir (previa recuperación) los valores de los campos de la variable unaFecha
basta escribir las instrucciones:
1
2
3 print("Dia: "); println(unaFecha.dia);
print("Mes: "); println(unaFecha.mes);
print("Año: "); println(unaFecha.fecha);
Analiza el siguiente código que muestra la animación de una simulación simple de un objeto que se deplaza en el plano.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 /* Introducción al Sw Científico y a la Programación
Bota la bola
*/
// Creamos un tipo de dato nuevo.
// Es un registro, al que llamamos Bola,
// y que está formado por:
class Bola {
float x; // Coordenada x en la pantalla
float y; // Coordenada y en la pantalla
float vel; // La velocidad de la bola en la pantalla
float diametro;// El diámetro de la bola
}
// Y definimos una variable global de tipo Bola
// Observa que se llaman igual, pero Processing las distingue:
// -- una está en mayúscula, Bola, es un registro; y
// -- la otra está en minúscula, bola, es una variable
Bola bola;
// La función principal se encarga de inicializar las variables
void setup () {
size(200, 200); // Tamaño de la ventana gráfica: 300px de lado.
bola = new Bola(); // Reservamos espacio de memoria en el Heap
bola.x = width/2; // Colocamos la bola en el centro de la pantalla
bola.y = height/2;
bola.vel = 3; // En cada frame "avanza" 3px
bola.diametro = 15;// Establecemos un diámetro de 15px
}
// La función de dibujo hará lo siguiente:
void draw() {
background(255); // Aplica un fondo de color blanco.
// Dibuja una esfera con los datos de la bola
ellipse(bola.x, bola.y, bola.diametro, bola.diametro);
// Actualizamos la posición de la bola de forma proporcional a
// su velocidad (con alteraciones aleatorias).
bola.x = bola.x + bola.vel*random(0,1);
bola.y = bola.y + bola.vel*random(0,1);
// Si las nuevas coordenadas hacen que la bola salga de la pantalla,
// entonces cambiamos la dirección de su velocidad
if ((bola.x<0) || (bola.x>width) || (bola.y<0) || (bola.y>height))
bola.vel = -1*bola.vel;
}
Observa la secuencia de pasos:
-
se define un nuevo tipo de dato (de tipo registro),
-
se declara una variable de tipo estructurado,
-
se reserva la memoria correspondiente para dicha variable,
-
para acceder a las variables del registro se usa la sintaxis:`var.campo`.
Predice el comportamiento de la bola y comprueba tus conclusiones:
6. Matemáticas y registros
Podemos definir muchos objetos y expresiones matemáticas como registros. Por ejemplo: los puntos del plano, los vectores, propiedades cinemáticas de objetos físicos, la ecuación de una recta o de un plano, las figuras geométricas, los números racionales o complejos, curvas polares, intervalos de la recta real, cada una de las ecuaciones de un sistema de ecuaciones, una función, los grafos, los cuaterniones, autómatas celulares, proposiciones lógicas, datos estadísticos, representaciones gráficas, desigualdades, etc …
7. Registros y funciones
No olvides que si en una función
-
Si en una función un parámetro responde a registro, puedes usarlo como una vía de entrada de datos a la función, pero también nos sirve para retornar datos de salida.
-
Si este es tu propósito, nunca apliques un
new
sobre el parámetro, pues perderás la referencia original. -
No olvides nunca que si tienes aliasing sobre el argumento de una función, la modificación de los valores asociados a este argumento también modificará a la otra variable.
-
-
Si en una función quieres retornar un tipo de dato registro tendrás que
-
declarar una variable local,
var
, del tipo estructurado, -
reservar la memoria correspondiente para dicha variable,
var = new TipoRegistro();
, -
asignar el conjunto de valores a la variable mediante
var.campo
, -
retornar el conjunto de valores con
return var;
-
Se define el producto escalar de dos vectores de dimensión \(n\) como
Además se sabe que ambos vectores son paralelos si \(\mathbf{v}\cdot\mathbf{w} = \|\mathbf{v}\|\cdot \|\mathbf{w}\|\), donde \(\|\mathbf{v}\|=\sqrt{\sum_{i=1}^{n} v_i^2}\).
Queremos hacer un programa que:
-
determine si dos vectores 2D, y aleatorios, son paralelos o no. Utiliza una función sin argumentos para generar dichos vectores y una función que determine dicho paralelismo.
-
modique un vector dado para que tenga el doble de su módulo mediante una función.
Comenzamos definiendo un nuevo tipo de dato que represente a los vectores 2D. Todo vector 2D tiene dos componentes, así que definimos un registro con dos campos.
1
2
3
4
5 // Definimos el tipo de dato vector en 2D
class Vector {
float x; // Dirección en X
float y; // Dirección en Y
}
Para crear un vector nuevo necesitamos declarar una variables, reservar memoria y asinar valores a los campos. Para ello utilizaremos una función sin argumentos. Esta función es:
1
2
3
4
5
6
7
8
9
10 * Función generadora de vectores aleatorios
* @return, Un nuevo vector 2D
*/
Vector generaVector() {
Vector aux; // Declaramos una variable de tipo rector
aux = new Vector(); // Reservamos memoria y guardamos la referencia
aux.x=random(0, 1000); // Inicializamos el campo x
aux.y=random(0, 1000); // Inicializamos el campo y
return aux; // Retornamos la referencia del nuevo vector
}
Esta función cada vez que es invocada retorna un vector aleatorio nuevo. La utilizaremos en la función principal para crear dos vectores como puedes ver en el siguiente código:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 /**
* Función principal
*/
void setup() {
// Declaramos dos variables de tipo vector
Vector v, w;
// Generamos vectores aleatorios
v = generaVector();
w = generaVector();
if (sonParalelos(v, w))
println("Son vectores paralelos");
else
println("NO son vectores paralelos");
}
La función principal no solo crea dos vectores. También invoca a una función para comprobar si ambos vectores son paralelos o no (mostrando el mensaje pertinente). Dicha función se define como sigue:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 /**
* Función que comprueba si dos vectores son paralelos
* @param a, uno de los vectores
* @param b, el otro vector
* @return, true si son paralelos y false en otro caso
*/
boolean sonParalelos(Vector a, Vector b) {
// Calculamos el producto escalar de los dos vectores
float escalar = productoEscalar(a, b);
// Comprobamos si está muy próximo a cero
if (abs(escalar)< pow(10, -6)) return true;
else return false;
}
Esta función a su vez invoca a la función que calcula el producto escalar de dos números. Como sabemos que se generan errores de aproximación será muy difícil que el producto escalar valga exáctamente cero, por eso buscamos que su valor sea muy próximo a él.
La función que calcula el producto escalar es la más sencilla de todas, y se reduce a una línea.
1
2
3
4
5
6
7
8
9
10 /**
* Calcula el producto escalar de dos vectores.
* @param a, uno de los vectores
* @param b, el otro vector
* @return el producto escalar.
*/
float productoEscalar(Vector a, Vector b) {
// Calculamos y retornamos el producto escalar de los dos vectores
return a.x*b.x + a.y*b.y;
}
Para la segunda parte, como se trata de modicar un vector dado, diseñaremos una función que contemple como parámetro un vector que se utilizara como via de entrada de datos y de salida de datos. En concreto construimos esta función:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 /**
* Función que duplica el módulo de un vector
* @param a, vector a imprimir
*/
void duplicaVector(Vector a) {
// Calculamos el módulo del vector
float modulo = sqrt(productoEscalar(a,a));
// Calculamos el ángulo del vector respecto de la horizontal
float angulo = atan2(a.y, a.x);
// Modificamos los valores para duplicar el módulo.
a.x = 2 * modulo * cos(angulo);
a.y = 2 * modulo * sin(angulo);
}
que será invocada en el programa principal como:
1
2 // Duplicamos la magnitud del vector v
duplicaVector(v);
8. Repaso
-
Un registro es una estructura formada por un conjunto de datos que se llaman campos.
-
Cada campo se declara como una variable, que puede responder a un tipo de dato simple o a un tipo de dato estructurado.
-
Antes de usar una variable de tipo registro siempre hay que usar la instrucción
new()
. -
Para acceder a un campo se usa la notación punto:
nombreVariable.nombreCampo
. -
Si usas un registro como parámetro en una función lo puedes usar para suministrar datos de entrada o para modificar valores del argumento.
-
Si una función retornara un registro, deberás de declarar una variable, reservar memoria, asignar valores a los campos y aplicar
return
a dicha variable.