Ampliado el 04/09/2024 en azul y el 05/09/24 en rojo
Retomo tras varios años la serie de programación de mi querido QL, seguiremos con los árboles, pero antes hablemos de conceptos básicos sobre punteros, necesarios para entender como montar los árboles.
Aunque esta entrada entrá en la serie del QL, realmente es general sobre programación, debes entender estos conceptos ya que son válidos para cualquier lenguaje, y en la siguiente hablaré sobre punteros.
Compiladores e Intérpretes
Escribimos nuestros programas en algún lenguaje, en donde usamos variables como un sistema para almacenar los datos que manipulemos y la lógica que usamos para su manipulación. Para ejecutar el programa podemos usar dos sistemas:
- En BASIC normalmente se usa un interprete, que va leyendo el programa fuente línea a línea y ejecutándola, esto es bastante simple y necesita poco espacio en memoria.
- Los compiladores en cambio convierte el programa fuente en otro que reconozca la máquina (normalmente código máquina, pero en algunos como JAVA generan un código intermedio que luego se procesan usando un ejecutor o una máquina virtual). Para ello se usa un programa que va leyendo el programa y ejecutando el proceso en una o varias pasadas, montando una serie de tablas para poder manejar cosas que todavía no se han definido. Esto requiere mas memoria para disponer del compilador, del buffer donde almacenar el programa que vamos leyendo, las tablas auxiliares, etc.
Variables
En ambos casos se necesita reconocer las variables, de forma que siempre que encontremos una podamos usar su valor correcto. Para ello en ambos sistemas se crea una tabla de variables, en donde se almacenan los datos necesarios para manejarlas, lo que se denominan atributos. En esta tabla se incluyen varios atributos importantes:
- Nombre: El nombre que le damos a una variables para nosotros es su principal atributo, ya que es con el que interactuamos con ellas. Su longitud es muy variable entre lenguajes, en los primeros BASIC constaba de una letra o de una letra y un solo dígito (A, B$, C2, D4 ...), en los TRS-80 su BASIC permitía nombre largos, pero solo consideraba los dos primeros (lo que solo es un poco mejor), y por experiencia personal se puede programar así solo requiere disciplina. En lenguajes antiguos se comenzaron usando hasta 6, luego se aumentó a 8 (bastantes años más tarde, y en las primeras versiones del ABAL, un dialecto del BASIC usado por BULL, se mantenían ilimitados pero solo consideraba los 8 primeros), rápidamente creció el tamaño hasta 256, y hoy día podemos decir que los lenguajes modernos tienen longitud ilimitada. Los nombres deben comenzar por una letra, o por el símbolo de subrayado más una letra. Hay lenguajes que distinguen mayúsculas y minúsculas en los nombre (lo que ocasiona errores si te equivocas, por lo que se recomienda usar siempre mayúsculas o minúsculas).
-
Tipo de Contenido: En los lenguajes antiguos teníamos variables numéricas de tipo entero o de tipo decimal, y variables alfanuméricas, de manera similar a lo que soportan los propios ordenadores internamente, luego se han ido ampliando los tipos:
- Alfanuméricas: Almacenan caracteres alfabéticos, caracteres numéricos y símbolos especiales (espacio, punto, +, *, &, etc.). En general hay estos tipos:
- Carácter o char: Un solo caracter
- Cadena o string: Una lista de caracteres, que puede ser de longitud fija (tienen un máximo que no se puede sobrepasar, hay lenguajes que rellenan con espacios por la derecha y otros que no lo hacen, marcando el final de la cadena), o de longitud variable (la cadena ocupa su longitud al crearla, si se amplia su contenido se amplia la cadena a su vez).
- Enteras: Un valor numérico sin decimales, son los más rápidos de manejo por lo que debe priorizarse su manejo. Internamente se guardan en binario, usando o bien un bit para el signo o bien usando complementos (esto lo explicaré mas adelante), su representación interna siempre es exacta, pero están limitados en rango por su tamaño, así en 8 bits y usando uno para el signo, disponemos de valores entre -127 a +127 (realmente -128 a 127, cosas del binario, pasará lo mismo con el resto de tamaños), con 16 bits ampliamos a ±32.767, con 32 bits alcanzamos ±2.147.483.647, con 64 bits ±9.223.372.036.854.775.808 (ojo, el punto es el separador de miles que usamos en España, en otros países serían comas). Realmente hay que
- Entero: Por defecto del tamaño de registro del procesador (8,16,32,64)
- Entero sin signo: Si no necesitamos números negativos, con este tipo ampliamos en 1 bit el tamaño, por lo que con 8 bits serían de 0 a 256, con 16 bits de 0 a 65.536, etc.
- Entero corto y largo: Cuando las máquinas eran de 8 bits no había otra opción, pero al ir ampliando el tamaño a 16, para reducir ocupación en memoria que era un recurso muy limitado siempre, se definieron enteros cortos que ocupaban la mitad, y enteros largos que ocupaban el doble.
- Carácter: Representa el valor numérico de un carácter alfanumérico en el código ASCII, pero en ciertos lenguajes podemos usarlo como un entero corto de 8 bits (o 16 si es en Unicode), soportando también su uso con o sin signo.
- Booleanas: Un entero en donde 0=Falso y 1=Cierto, o más generalmente cero falso y diferente a cero cierto. Ocupan un byte completo para solo usar un bit, lo que en máquinas de 64 bits es mucho espacio desaprovechado.
- Fecha, hora, fecha y hora: Son valores enteros que guardan realmente el número de días desde una fecha inicial dada, la hora del día en décimas de segundo, o ambos simultáneamente en dos enteros.
- Punteros: Esto lo veremos luego.
- De coma fija: Estos son números que tienen una cantidad de decimales predefinida, y su valor se guarda directamente en binario como un entero, redondeando el valor a los decimales que aceptan, ajustando los decimales truncando o añadiendo ceros por la derecha para alcanzar los definidos, y luego eliminando el punto decimal, así serán enteros y de esta manera se pueden sumar directamente entre sí, añadiendo ceros por la derecha si es necesario igualar el número de los decimales entre las variables (esto es muy rápido en los ordenadores).
- Decimales: Un valor numérico que admite decimales. Internamente se guarda con una codificación científica, del tipo smEn, donde s es el signo, m la mantisa (se guarda el valor completo como todos sus dígitos sin punto decimal), y n sería el exponente (donde se ubica el signo decimal). Hay varios tipos según su tamaño, lo que cambia es la cantidad de cifras significativas de la mantisa y del exponente.:
- La codificación normalizada IEEE 754 ocupa 32 bits, denominándose de simple precisión
- Las de media precisión ocupan 16 bits
- Las de doble precisión ocupan 64 bits
- Las de cuádruple precisión o doble precisión largas, con 128 bits
- Hay hasta las de octuple precisión con 256 bits.
- Tipos especiales: Aquí cada lenguaje tiene sus variantes, podemos hablar en general de:
- Puntero: que guarda una posición de memoria, es el objetivo de esta entrada
- Tipo indefinido (void): representa que no usaremos su valor por lo que solo nos interesa su nombre (en C una función se comporta como una variable, ya que dispone de su entrada en la tabla de Variables, las funciones son las que retornan un valor, los procedimientos no las retornan y se definen de tipo void)
- Cualquier tipo (any): que indica que guardaremos cualquier posible valor en la variable (usar con cuidado ya que ralentiza mucho el programa). Son usuales en JavaScript.
- Objeto: realmente son punteros, pero que apuntan a una estructura en memoria compleja que dispone de código y datos.
- Ubicación en memoria: Todos los datos deben estar ubicados en algún lugar de la memoria del ordenador, en general se reserva una zona denominada "pila del programa" o de forma similar, en donde se guardan todas las variables que se utilicen. Para variables simples apuntará directamente a donde está su valor, pero para variables mas complejas apunta al inicio de la zona en que está ubicada. Para crear la entrada para un nuevo valor, se mira la posición de la última entrada y se le suma la ocupación, así tenemos la ubicación de la nueva variable. Si fuera la primera variable y por tanto la tabla está vacía, se rellena con la dirección base de la "pila del programa".
- Por un lado tenemos la lista de nombres de las variables, procedimientos y funciones. La primera vez que las definimos se guarda en esta lista, y a partir de entonces al escribir su nombre el sistema lo ajustará para que coincida con lo que has escrito en cuanto a mayúsculas y minúsculas en las líneas. Esto hace que si queremos cambiar la variable nododerecho por NodoDerecho el sistema no nos deje, pues ya está en la lista.
- Otra lista contiene los tipos de cada variable, procedimiento o función, para las locales incluye el procedimiento o función en que tienen vigencia, junto al puntero en la memoria donde está ubicado su valor.
- Una tercera es la que contiene los valores de las variables.
Definición explicita de variables
Variables globales y variables locales
En el BASIC estándar todas las variables son globales, eso significa que las podemos usar en cualquier parte del programa sin problema. Evidentemente en lenguajes donde definimos las variables antes de comenzar el programa en un bloque separado, como COBOL o en el ABAL de Bull, todas las variables son también globales.(3) int i=6;
(6) } while (i<10)
Qué bueno que continue la saga de temas!
ResponderEliminar